import { Injectable } from '@nestjs/common' ; import * as assert from 'assert' ; import * as get from 'lodash.get' ; import * as set from 'lodash.set' ; import { DotenvConfigOptions , config as dotenv } from 'dotenv' ; import * as path from 'path' ; import { Glob , sync as globSync } from 'glob' ; import { ProxyProperty } from '../decorators/proxy' ; export interface ModuleConfig { [ key ] : any ; } export interface Config { [ key ] : ModuleConfig ; } export type CustomHelper = { [ key ] : ( ... args : any [ ] ) => any ; } ; export interface ConfigOptions extends Partial < DotenvConfigOptions > { modifyConfigName ? : ( name : string ) => string ; } @ Injectable ( ) @ ProxyProperty ( 'helpers' ) export class ConfigService { [ key ] : Config | CustomHelper | ( ( ... args : any [ ] ) => any ) | any ; private static config : Config ; private readonly helpers : CustomHelper = { } ; public static rootPath ? : string ; deprecated public static srcPath ? : string ; param { Config } config constructor ( config = { } ) { this . bindCustomHelpers ( config ) ; ConfigService . config = config ; } param glob param { DotenvOptions } options returns { Promise < any > } static async load ( glob , options ? ) { const configs = await this . loadConfigAsync ( glob , options ) ; return new ConfigService ( configs ) ; } param { string } glob param { DotenvOptions | false } options static loadSync ( glob , options ? ) { const configs = this . loadConfigSync ( glob , options ) ; return new ConfigService ( configs ) ; } param { String } key param { any } value returns { any | undefined } static get ( param , value = undefined ) { const configValue = get ( ConfigService . config , param ) ; if ( configValue === undefined ) { return value ; } return configValue ; } param param param { any } value returns { any } get ( param , value = undefined ) { return ConfigService . get ( param , value ) ; } param { string } param param value returns { Config } set ( param , value = null ) { return set ( ConfigService . config , param , value ) ; } param param returns { boolean } has ( param ) { return get ( ConfigService . config , param ) !== undefined ; } param glob param options async merge ( glob , options ? ) { const config = await ConfigService . loadConfigAsync ( glob , options ) ; Object . keys ( config ) . forEach ( ( configName ) => { ConfigService . config [ configName ] = config [ configName ] ; } ) ; } param { string } glob param { DotenvOptions } options returns { ConfigService } mergeSync ( glob , options ? ) { const config = ConfigService . loadConfigSync ( glob , options ) ; Object . keys ( config ) . forEach ( ( configName ) => { ConfigService . config [ configName ] = config [ configName ] ; } ) ; return this ; } param { string } name param { CustomHelper } fn returns { ConfigService } registerHelper ( name , fn ) { this . helpers [ name ] = fn . bind ( this ) ; return this ; } param { string } dir returns { string } public static root ( dir = '' ) { const rootPath = this . rootPath || this . srcPath || path . resolve ( process . cwd ( ) ) ; return path . resolve ( rootPath , dir ) ; } param { string } dir returns { string } deprecated public static src ( dir = '' ) { console . log ( `\x1b[33m%s\x1b[0m` , `WARNING: Method 'src' has been deprecated. Please use 'root'` ) ; return this . root ( dir ) ; } param { string } startPath public static resolveRootPath ( startPath ) { assert . ok ( path . isAbsolute ( startPath ) , 'Start path must be an absolute path.' ) ; if ( ! this . rootPath ) { const root = this . root ( ) ; let workingDir = startPath ; let parent = path . dirname ( startPath ) ; while ( workingDir !== root && parent !== root && parent !== workingDir ) { workingDir = parent ; parent = path . dirname ( workingDir ) ; } this . rootPath = workingDir ; this . srcPath = workingDir ; } return this ; } param startPath deprecated public static resolveSrcPath ( startPath ) { console . log ( `\x1b[33m%s\x1b[0m` , `WARNING: Method 'resolveSrcPath' has been deprecated. Please use 'resolveRootPath'` ) ; return this . resolveRootPath ( startPath ) ; } param { string | string [ ] } glob param { DotenvOptions | false } options returns { Promise < Config > } protected static loadConfigAsync ( glob , options ? ) { glob = this . root ( glob ) ; return new Promise ( ( resolve , reject ) => { new Glob ( glob , { } , ( err , matches ) => { if ( err ) { reject ( err ) ; } else { this . loadEnv ( options ) ; const configs = this . configGraph ( matches , options && options . modifyConfigName ) ; resolve ( configs ) ; } } ) ; } ) ; } param { string } glob param { DotenvOptions | false } options returns { Config } protected static loadConfigSync ( glob , options ? ) { glob = this . root ( glob ) ; const matches = globSync ( glob ) ; this . loadEnv ( options ) ; return this . configGraph ( matches , options && options . modifyConfigName ) ; } param configPaths param modifyConfigName returns { any } protected static configGraph ( configPaths , modifyConfigName ? ) { return configPaths . reduce ( ( configs , file ) => { const module = require ( file ) ; const config = module . default || module ; const configName = modifyConfigName ? modifyConfigName ( this . getConfigName ( file ) ) : this . getConfigName ( file ) ; configs [ configName ] = config ; return configs ; } , { } ) ; } param config returns { string } protected bindCustomHelpers ( config ) { return Object . keys ( config ) . reduce ( ( configObj , configName ) => { if ( typeof configObj [ configName ] === 'function' ) { const helper = configObj [ configName ] . bind ( this ) ; configObj [ configName ] = helper ; this . helpers [ `_${ configName }` ] = helper ; } if ( typeof configObj [ configName ] === 'object' && configObj [ configName ] !== null ) { configObj [ configName ] = this . bindCustomHelpers ( configObj [ configName ] ) ; } return configObj ; } , config ) ; } param { string } file returns { string } protected static getConfigName ( file ) { const ext = path . extname ( file ) ; return path . basename ( file , ext ) ; } param { DotenvConfigOptions | false } options protected static loadEnv ( options ? ) { if ( options !== false ) { dotenv ( options || ConfigService . defaultDotenvConfig ( ) ) ; } } returns { { path : string } } protected static defaultDotenvConfig ( ) { return { path : path . join ( process . cwd ( ) , '.env' ) , } ; } }