import path from 'path' ; import fs from 'fs' ; import mergeConfigs from '@alfred/merge-configs' ; import { requireConfig } from '@alfred/helpers' ; import { ConfigInterface , NpmClient , AlfredConfigWithResolvedSkills , AlfredConfigWithUnresolvedTasks , AlfredConfigSkill , AlfredConfigWithUnresolvedSkills , AlfredConfigRawSkill , AlfredConfigWithDefaults , PkgJson , ConfigValue , } from '@alfred/types' ; import loadJsonFile from 'load-json-file' ; import { validateAlfredConfig } from './validation' ; import Project , { formatPkgJson } from './project' ; type SkillConfigMap = Map < string , ConfigValue > ; export default class Config implements ConfigInterface { extends ? : string | Array < string > ; npmClient : NpmClient ; configsDir : string ; skills : AlfredConfigSkill [ ] ; autoInstall : boolean ; private rawConfig : AlfredConfigWithUnresolvedTasks ; static DEFAULT_CONFIG = { skills : [ ] , autoInstall : false , configsDir : '.' , npmClient : 'npm' as NpmClient , } ; constructor ( rawConfig ) { validateAlfredConfig ( rawConfig ) ; const resolvedSkills = { ... Config . DEFAULT_CONFIG , ... this . normalizeWithResolvedSkills ( this . normalizeWithResolvedExtendedConfigs ( rawConfig ) ) , } ; validateAlfredConfig ( resolvedSkills ) ; this . rawConfig = rawConfig ; this . skills = resolvedSkills . skills || [ ] ; this . configsDir = resolvedSkills . configsDir ; this . autoInstall = resolvedSkills . autoInstall ; this . npmClient = resolvedSkills . npmClient ; } getConfigWithDefaults ( ) { return { ... Config . DEFAULT_CONFIG , ... this . getConfigValues ( ) , } ; } getConfigValues ( ) { return { skills : this . skills , configsDir : this . configsDir , npmClient : this . npmClient , autoInstall : this . autoInstall , } ; } getRawConfig ( ) { return this . rawConfig ; } param pkgPath async write ( pkgPath , pkgAlfredConfig ) { Project . validatePkgPath ( pkgPath ) ; validateAlfredConfig ( pkgAlfredConfig ) ; const pkg = ( await loadJsonFile ( pkgPath ) ) as PkgJson ; this . rawConfig = pkg . alfred || { } ; return Config . writeObjToPkgJson ( pkgPath , { alfred : pkgAlfredConfig , } ) ; } private normalizeWithResolvedSkills ( config ) { if ( ! config . skills || ! config . skills . length ) return config as AlfredConfigWithResolvedSkills ; const skillsWithConfigs = config . skills . reduce ( ( skillConfigMap , skill ) => { if ( typeof skill === 'string' ) { skillConfigMap . set ( skill , { } ) ; return skillConfigMap ; } if ( Array . isArray ( skill ) ) { const [ skillName , skillConfig ] = skill ; if ( skillConfigMap . has ( skillName ) ) { skillConfigMap . set ( skillName , mergeConfigs ( { } , skillConfigMap . get ( skillName ) || { } , skillConfig ) ) ; } else { skillConfigMap . set ( skillName , skillConfig ) ; } return skillConfigMap ; } throw new Error ( `Config type not supported: ${ JSON . stringify ( skill ) }` ) ; } , new Map < string , ConfigValue > ( ) ) ; return { ... config , skills : Array . from ( skillsWithConfigs . entries ( ) ) , } ; } /**
     * Initialize a config from the root directory of an alfred project
     */ static initFromProjectRoot ( projectRoot ) { const pkgPath = path . join ( projectRoot , 'package.json' ) ; Project . validatePkgPath ( pkgPath ) ; const { alfred = { } } = loadJsonFile . sync ( pkgPath ) ; return new Config ( alfred ) ; } private normalizeWithResolvedExtendedConfigs ( config ) { if ( ! config . extends ) return config ; if ( typeof config . extends === 'string' ) { config . extends = [ config . extends ] ; } const normalizedConfigs = config . extends . map ( ( _config ) => typeof _config === 'string' ? requireConfig ( _config ) : _config ) ; if ( config . extends && ! config . extends . length ) { const newConfig = { ... config } ; delete newConfig . extends ; return newConfig ; } for ( let i = 0 ; i < normalizedConfigs . length ; i += 1 ) { normalizedConfigs [ i ] = this . normalizeWithResolvedExtendedConfigs ( normalizedConfigs [ i ] ) ; } const mergedConfig = mergeConfigs ( { } , ... normalizedConfigs , config ) ; delete mergedConfig . extends ; return mergedConfig ; } /**
     * Take a given object and append it to an Alfred project's package.json
     */ static async writeObjToPkgJson ( pkgPath , obj , pkg ? ) { Project . validatePkgPath ( pkgPath ) ; const pkgToWrite = { ... ( pkg || ( ( await loadJsonFile ( pkgPath ) ) as PkgJson ) ) , ... obj , } ; const formattedPkg = await formatPkgJson ( pkgToWrite ) ; await fs . promises . writeFile ( pkgPath , formattedPkg ) ; return formattedPkg ; } }