All files / executors/client-env env-base.ts

97.5% Statements 39/40
84.61% Branches 11/13
93.75% Functions 15/16
97.5% Lines 39/40

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180                                    14x             14x                                           14x         14x         14x 14x       14x             10x             14x 14x   14x 12x 2x       12x   10x   10x 8x 2x   10x 8x   2x               6x                   6x             8x 6x 2x   8x 6x   4x 4x     2x     2x                   10x         8x 8x     2x                                 10x   8x     8x      
import { type ExecutorContext, logger } from '@nx/devkit';
import dotenv from 'dotenv';
import { constants } from 'fs';
import { access, readFile, stat, writeFile } from 'fs/promises';
 
import type { IExecutorOptions, TSupportedApp } from './schema';
 
export interface IEnvConfig {
  version: string;
}
 
/**
 * Base environment configurator for the client applications.
 */
export abstract class AppBaseEnvConfig<T = IEnvConfig> {
  /**
   * Executor context.
   */
  public options: IExecutorOptions = {
    app: 'client',
  };
 
  /**
   * Executor context.
   */
  public context: ExecutorContext = {
    cwd: process.cwd(),
    isVerbose: false,
    root: '/root',
    projectsConfigurations: {
      version: 1,
      projects: {},
    },
    nxJsonConfiguration: {},
    projectGraph: {
      nodes: {},
      dependencies: {},
    },
    configurationName: '',
    projectName: '',
    target: { executor: '' },
    targetName: '',
  };
 
  /**
   * The supported application names.
   */
  public readonly supportedApps: TSupportedApp[] = [];
 
  /**
   * The environment configuration file initial value.
   */
  protected defaultEnv: T = {
    version: 'N/A',
  } as unknown as T;
 
  constructor(options: IExecutorOptions, context: ExecutorContext) {
    this.options = { ...options };
    this.context = { ...context };
    /**
     * Load environment variables.
     */
    dotenv.config({ path: `${this.context.cwd}/.env` });
  }
 
  /**
   * Environment file path.
   */
  private environmentFilePath(app: TSupportedApp) {
    return `${this.context.cwd}/apps/${app}/src/environments/environment.config.ts`;
  }
 
  /**
   * Configures the application environment.
   */
  public async execute() {
    const reset = this.options.reset;
    const app = this.options.app;
 
    if (typeof reset === 'undefined') {
      if (!this.supportedApps.includes(app)) {
        throw new Error(`The application is not supported.\nSupported apps: ${this.supportedApps.join(' ')}`);
      }
    }
 
    const env = typeof reset !== 'undefined' ? this.defaultEnv : await this.getEnvValues();
 
    const envFilePath = this.environmentFilePath(app);
 
    const accessResult = await access(envFilePath, constants.W_OK)
      .then(() => null)
      .catch((error: NodeJS.ErrnoException) => error);
 
    if (accessResult === null) {
      await this.writeEnvironmentFile(envFilePath, env);
    } else {
      throw new Error(`Unable to write the environment configuration file:\n${accessResult.message}`);
    }
  }
 
  /**
   * Returns environment configuration file contents.
   */
  protected envConfigFileContents(options: T) {
    const envConfig = `/**
 * The application environment configuration factory.
 * @returns application environment configuration
 */
export const appEnvFactory = () => ({
  meta: {
    version: '${(options as Record<string, unknown>)['version']}',
  },
});
`;
    return envConfig;
  }
 
  /**
   * Writes environment file.
   */
  protected async writeEnvironmentFile(envFilePath: string, env: T) {
    const statResult = await stat(envFilePath)
      .then(() => this.envConfigFileContents(env))
      .catch((error: NodeJS.ErrnoException) => error);
 
    if (typeof statResult === 'string') {
      await writeFile(envFilePath, statResult)
        .then(() => {
          logger.info('Output generated at:');
          logger.info(envFilePath);
        })
        .catch((error: NodeJS.ErrnoException) => {
          throw new Error(`Unable to write environment file:\n${error.message}`);
        });
    } else {
      throw new Error(`Unable to stat environment file:\n${statResult.message}`);
    }
  }
 
  /**
   * Gets the value of the version property from the package.json file.
   * @param source the package.json file location
   * @returns the value of the version property from the package.json file
   */
  protected async getPackageVersion(source = `${this.context.cwd}/package.json`) {
    return readFile(source, 'utf8')
      .then<string>(data => {
        const packageJsonContent: {
          version: string;
          [key: string]: unknown;
        } = JSON.parse(data);
        return packageJsonContent.version;
      })
      .catch((error: NodeJS.ErrnoException) => {
        throw error;
      });
  }
 
  /**
   * Gets environment variable value.
   * @param key environment variable key
   * @returns environment variable value or key if value is null or undefined
   */
  protected getEnvValue(key: string) {
    return process.env[key] ?? key;
  }
 
  /**
   * Forms the environment variable values object.
   */
  protected async getEnvValues(): Promise<T> {
    const version = await this.getPackageVersion();
 
    const env = {
      version,
    } as unknown as T;
    return env;
  }
}