hins
Version:
[](https://codecov.io/gh/l-zoy/hins) [](https://github.com/l-zoy/hins/blob/master/LICENSE)  • 4.66 kB
text/typescript
import chokidar from 'chokidar'
import assert from 'assert'
import slash from 'slash'
import path from 'path'
import joi from 'joi'
import fs from 'fs'
import { compatESModuleRequire, isEqual, mergeDefault } from './tools'
import { ICoreStage } from './enum'
import type { IConfig, IReadConfig, IWorkDir } from './types'
export default class Config {
/**
* @desc possible Config name
*/
possibleConfigName: IWorkDir[]
/**
* @desc Service instance
*/
core: IReadConfig['core']
constructor(options: IReadConfig) {
this.core = options.core
this.possibleConfigName = options.possibleConfigName
}
getPluginConfig(userConfig: IConfig) {
const defaultConfig = this.getPluginDefaultConfig()
const { stage, plugins } = this.core
assert(
stage >= ICoreStage.pluginReady,
`Config.getPluginConfig() failed, it should not be executed before plugin is ready.`
)
const userConfigKeys = Object.keys(userConfig)
const keepKeys = {}
// get config
Object.keys(plugins).forEach((plugin) => {
const { key, config } = plugins[plugin]
if (!key || !config) {
return
}
const value = userConfig[key]
if (!keepKeys[key]) {
keepKeys[key] = key
} else {
throw new Error(`have multiple same ${key}`)
}
const schema = config.schema(joi)
assert(
joi.isSchema(schema),
`schema return from plugin ${plugin} is not valid schema.`
)
const { error } = schema.validate(value)
if (error) {
throw new Error(error.message)
}
// All the configurable key values are obtained above
// and the verification process is performed here.
// If all the key values are filled in
// the length of `userConfigKeys` should be `0`
const index = userConfigKeys.indexOf(key.split('.')[0])
if (index !== -1) {
userConfigKeys.splice(index, 1)
}
// update userConfig with defaultConfig
if (key in defaultConfig) {
const newValue = mergeDefault({
defaultConfig: defaultConfig[key],
config: value
})
userConfig[key] = newValue
}
})
// Same as above, if the value of `userConfigKeys` is not 0,
// an error is thrown here and prompts which keys are illegal
if (userConfigKeys.length) {
const keys = userConfigKeys.length > 1 ? 'keys' : 'key'
throw new Error(`Invalid config ${keys}: ${userConfigKeys.join(', ')}`)
}
return userConfig
}
getPluginDefaultConfig() {
const { plugins } = this.core
// collect default config
return Object.keys(plugins).reduce((memo, pluginId) => {
const { key, config } = plugins[pluginId]
if (!key || !config) return memo
if ('default' in config) memo[key] = config.default
return memo
}, {})
}
getConfigFile() {
const { cwd } = this.core
const env = process.env.HINS_CONFIG_ENV
// Get a valid file name
// I.e. check if the file exists
let configFile = this.possibleConfigName.find((file) =>
fs.existsSync(path.join(cwd, file))
)
if (configFile) {
if (env) {
const ext = path.extname(configFile)
configFile = configFile.replace(ext, `.${env}${ext}`)
}
return slash(path.join(cwd, configFile))
}
return false
}
getUserConfig(): IConfig {
const { babelRegister } = this.core
const configFile = this.getConfigFile()
if (configFile) {
// clear the require cache
// load babelRegister if there is
// babelRegister is initialized with default value processing
delete require.cache[configFile]
babelRegister(configFile)
return compatESModuleRequire(require(configFile))
}
return {}
}
watchConfig() {
const { cwd, config, args, watchConfig } = this.core
const configFile = this.getConfigFile()
if (configFile) {
const watcher = chokidar.watch(configFile, {
cwd,
ignoreInitial: true,
awaitWriteFinish: {
stabilityThreshold: 500
}
})
watcher.on('all', async (event, paths) => {
const initConfig = this.getUserConfig()
const newConfig = this.getPluginConfig(initConfig)
const isReload = !isEqual(newConfig, config)
watchConfig.changeLog(event, paths, isReload)
if (isReload) {
watchConfig.reloadLog(event, paths)
this.core.initConfig = initConfig
this.core.reset()
await watcher.close()
this.core.start({ ...args!, reloadCommand: false })
}
})
}
}
}