import * as fs from 'fs'; import * as yaml from 'js-yaml'; import { merge as _merge } from 'lodash'; import * as nodePath from 'path'; import { initBabel } from '../babel'; import { EnvConfigSettings } from '../types'; import { defaultConfigSettingsBuilderOptions } from './constants'; import { getFileWithBaseNameAndExtension } from './lib'; import { transformFileSettings } from './transformFileSettings'; import { ConfigSettingsBuilderOptions, SettingsFileLoaderFactory } from './types'; export class SettingsFileLoader { private get loaderFactories(): { [ext: string]: SettingsFileLoaderFactory | TSettings>; } { // tslint:disable: object-literal-sort-keys if (!this._loaderFactories) { this._loaderFactories = { ts: this.loadSettingsFromTsOrJS, js: this.loadSettingsFromTsOrJS, json: this.loadSettingsFromJson, yml: this.loadSettingsFromYaml, yaml: this.loadSettingsFromYaml, }; } return this._loaderFactories; } public get supportedFileExtensions(): string[] { return Object.keys(this.loaderFactories); } public static initBabel = () => { if (!SettingsFileLoader._isInitBabel) { initBabel(); SettingsFileLoader._isInitBabel = true; } }; private static _isInitBabel = false; private _loaderFactories: { [ext: string]: SettingsFileLoaderFactory | TSettings> }; private _options: ConfigSettingsBuilderOptions; public constructor(options?: ConfigSettingsBuilderOptions) { this._options = _merge({}, defaultConfigSettingsBuilderOptions, options); } public getSupportedExistingFile = (rootDir: string, baseName: string, extentions?: string[]): string | null => { return getFileWithBaseNameAndExtension(rootDir, baseName, this.filterSupportedExtension(extentions)); }; public tryLoad = async | TSettings>( rootDir: string, baseName: string, extentions?: string[] ): Promise => { const filePath = this.getSupportedExistingFile(rootDir, baseName, extentions); if (filePath) { return this.load(filePath); } return null; }; public load = async | TSettings>(filePath: string): Promise => { if (!fs.existsSync(filePath)) { throw new Error(`SettingsFileLoader Failed. File Not Found: '${filePath}'`); } const loaderKey = nodePath.extname(filePath).substr(1); if (!this.loaderFactories[loaderKey]) { throw new Error(`SettingsFileLoader Failed. No loader for '${loaderKey}' file. '${filePath}'`); } try { return this.loadWithFactory(loaderKey, filePath); } catch (e) { throw new Error(`SettingsFileLoader Load Failed: '${e.message}'`); } }; private loadSettingsFromJson = async (filePath: string): Promise> => { return require(filePath) as EnvConfigSettings; }; private loadSettingsFromTsOrJS = async (filePath: string): Promise> => { try { initBabel(nodePath.dirname(filePath)); const imported = require(filePath); const settings = imported.default || imported; return settings as EnvConfigSettings; } catch (e) { throw new Error(`Failed to import Settings from source file: '${filePath}': ${e.message}`); } }; private loadSettingsFromYaml = async (filePath: string): Promise> => { try { const doc = yaml.load(fs.readFileSync(filePath, 'utf8')); return doc as EnvConfigSettings; } catch (e) { throw new Error(`Failed to import Settings from Yaml file '${filePath}': ${e.message}`); } }; private filterSupportedExtension = (filter?: string[]) => { return filter ? this.supportedFileExtensions.filter(ext => { return filter.indexOf(ext) >= 0; }) : this.supportedFileExtensions; }; private loadWithFactory = async | TSettings>( loaderKey: string, filePath: string ): Promise => { const rawSettings = (await this.loaderFactories[loaderKey](filePath)) as EnvConfigSettings; if (rawSettings) { // tslint:disable-next-line: no-any return transformFileSettings(rawSettings as any, filePath, this._options); } return null; }; }