import * as fs from 'fs'; import { merge as _merge } from 'lodash'; import * as nodePath from 'path'; import { getFsStats } from '../lib'; import { EnvConfigSettings } from '../types'; import { defaultConfigSettingsBuilderOptions } from './constants'; import { toEnvConfigSettings } from './lib'; import { SettingsFileLoader } from './SettingsFileLoader'; import { ConfigSettingsBuilderOptions } from './types'; export class EnvConfigSettingsFileLoader { private _rootPathStats: fs.Stats | null; private _rootPath: string; private _settingsFileLoader: SettingsFileLoader; private _options: ConfigSettingsBuilderOptions; constructor(rootPath: string, private baseName?: string, options?: ConfigSettingsBuilderOptions) { this._options = _merge({}, defaultConfigSettingsBuilderOptions, options); this._settingsFileLoader = new SettingsFileLoader(this._options); this._rootPath = nodePath.resolve(rootPath); this._rootPathStats = getFsStats(this._rootPath); } public load = async (): Promise | null> => { if (this._rootPathStats) { let settings: EnvConfigSettings | null = null; if (!this.baseName) { // if root path is a directory and there is no base name, we don't know what to load if (this._rootPathStats.isDirectory()) { throw new Error( `Cannot import from configs. baseName must be provided if rootPath ('${this._rootPath}' is a directory)'.` ); } else { // rootPath must be a single settings file, try to load it const directFileSettings = await this._settingsFileLoader.load(this._rootPath); settings = toEnvConfigSettings(directFileSettings); } } else { // we have a baseName const fileSettings = await this._settingsFileLoader.tryLoad(this._rootPath, this.baseName); if (fileSettings) { settings = toEnvConfigSettings(fileSettings); } else if (this.baseName) { const tryConfigDir = nodePath.join(this._rootPath, this.baseName); const tryConfigDirStats = getFsStats(tryConfigDir); if (tryConfigDirStats && tryConfigDirStats.isDirectory()) { settings = await this.tryLoadDir(tryConfigDir); } else { // rootPath + baseName doesn't exist or isn't a directory } } else { // rootPath wasn't absolute settings file and no baseName defined } } return settings; } else { // root path doesn't exist return null; } }; private transformRawSettings = async ( settings: TSettings | EnvConfigSettings ): Promise> => { // load references, convert raw TSettings to EnvConfig return toEnvConfigSettings(settings); }; private tryLoadDir = async (dir: string): Promise | null> => { const { envConfigFileOptions } = this._options; const { baseFileBaseName, envVarsFileBaseName } = envConfigFileOptions; // look for a source code file with baseName index to over-ride all other files const indexFileSettings = await this._settingsFileLoader.tryLoad>(dir, 'index', [ 'ts', 'js', ]); if (indexFileSettings) { return this.transformRawSettings(indexFileSettings); } // look for 'base' baseName const base = await this._settingsFileLoader.tryLoad(dir, baseFileBaseName); // look for files with NODE_ENV candidate names with supported extensions const nodeEnv = {}; if (process.env.NODE_ENV) { const nodeEnvVal = process.env.NODE_ENV; const settings = await this._settingsFileLoader.tryLoad(dir, nodeEnvVal); if (settings) { nodeEnv[nodeEnvVal] = settings; } } const envVars = (await this._settingsFileLoader.tryLoad(dir, envVarsFileBaseName)) || {}; // construct PackageConfigSettings object from individually loaded files if (base || Object.keys(nodeEnv).length > 0 || Object.keys(envVars).length > 0) { return { base, envVars, nodeEnv, }; } else { return null; } }; }