wo-base-envar/envar-tool.js
2024-09-22 15:51:25 +08:00

282 lines
13 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const fs = require('fs')
const path = require('path')
const chokidar = require('chokidar')
const assign_deep = require('assign-deep')
//const deepmerge = require('deepmerge')
// https://github.com/jonschlinkert/assign-deep // 类似 Object.assign 直接赋值到第一个对象里。只下载2个包。assign 优选。
// https://github.com/jonschlinkert/merge-deep // 生成一个新对象。会下载78个包。
// https://github.com/TehShrike/deepmerge // 生成一个新对象。只下载1个包。
// const deepmerge = require('@fastify/deepmerge')() // https://github.com/fastify/deepmerge // // 生成一个新对象。只下载1个包。assign优选。页面上有性能测试比较超快。(他还有个 https://github.com/fastify/fast-json-stringify 也超快)
const my = { envar: {} }
module.exports = {
start_watching ({ envarFiles = ['./envar-base-dynamic.js', './envar-base-dynamic.gitignore.js'], rawEnvar = {}, interval = 1000 } = {}) {
chokidar.watch(envarFiles, { interval }).on('change', (onpath) => {
// .on('all', (event, onpath)) 但这时即使server刚启动也会调用到这里一次
console.log('envarTool.start_watching: envar file changed:', onpath)
try {
// 或者调用 get_dynamic_envar。
delete require.cache[require.resolve(path.resolve(onpath))]
let newEnvar = require(path.resolve(onpath))
if (typeof newEnvar === 'function') newEnvar = newEnvar()
// 注意assign_deep 会用 newEnvar 里的 undefined 属性覆盖掉 rawEnvar 里的原属性!所以要注意保持一致。(这和发送到前端的不一样,发送到前端的会被 JSON.stringify 以及 express 过滤掉 undefined.)
// 目前的解决方案里,已通过 base2app 参数来确保只在发送给前端时才设置 undefined但为防万一在这里通过 JSON.stringify 确保删除了 undefined
assign_deep(rawEnvar, JSON.parse(JSON.stringify(newEnvar)))
console.log(`envarTool.start_watching: OK reload ${onpath}`)
} catch (expt) {
console.log(`envarTool.start_watching: Fail reload ${onpath}`)
}
})
},
/** 合并 envar files 和 commander parameters 中的环境变量。
* @param envarFiles:
* - 字符串: 导入文件,内容应当是字符串数组,或者对象。
* - 字符串数组: 按顺序导入导入每个文件,后面文件里的变量覆盖前面的。
* - 对象: 直接添加到 rawEnvar 上。
*/
merge_envar ({
rawEnvar = {},
envarFiles = [
'./envar-base-basic.js',
'./envar-base-basic.gitignore.js',
'./envar-base-dynamic.js',
'./envar-base-dynamic.gitignore.js',
'./envar-base-secret.js',
'./envar-base-secret.gitignore.js',
],
} = {}) {
console.info({ _at: new Date().toJSON(), _from: 'merge_envar', about: `<<<<<<<< Configuring [${process.env.NODE_ENV}] Environment <<<<<<<<` }, '\n,')
console.info({ _at: new Date().toJSON(), _from: 'merge_envar', about: '- Loading Configuration Files (读取配置文件)' }, '\n,')
if (typeof envarFiles === 'string') {
// 例如当输入参数为 envarFiles = 'envar-base.js' 里面应当 module.exports 一个数组
if (fs.existsSync(path.resolve(envarFiles))) {
envarFiles = require(path.resolve(envarFiles))
} else {
console.warn({ _at: new Date().toJSON(), _from: 'merge_envar', about: ` - ${envarFiles} is missing.` }, '\n,')
envarFiles = undefined
}
}
if (Array.isArray(envarFiles)) {
for (let configFile of envarFiles) {
if (fs.existsSync(path.resolve(configFile))) {
const fileContent = require(path.resolve(configFile))
if (typeof fileContent === 'object') {
assign_deep(rawEnvar, fileContent)
} else if (typeof fileContent === 'function') {
assign_deep(rawEnvar, fileContent())
}
console.info({ _at: new Date().toJSON(), _from: 'merge_envar', about: ` - ${configFile} is loaded.` }, '\n,')
} else {
console.warn({ _at: new Date().toJSON(), _from: 'merge_envar', about: ` - ${configFile} is missing.` }, '\n,')
}
}
} else if (typeof envarFiles === 'object') {
assign_deep(rawEnvar, envarFiles)
} else if (typeof envarFiles === 'function') {
assign_deep(rawEnvar, envarFiles())
} else {
console.warn({ _at: new Date().toJSON(), _from: 'merge_envar', about: ` - unrecognized envarFiles!` }, '\n,')
}
if (process.argv.length > 2 && Array.isArray(rawEnvar.commanderOptions)) {
console.info({ _at: new Date().toJSON(), _from: 'merge_envar', about: '- Loading Command Line Parameters (载入命令行参数)' }, '\n,')
const commander = require('commander')
commander.version(rawEnvar.Base_Version || '0.0.1', '-v, --version') // 默认是 -V。如果要 -v就要加 '-v --version'
for (let [key, param, desc] of rawEnvar.commanderOptions || []) {
commander.option(param, `${desc} Default = "${rawEnvar[key]}"`)
}
commander.parse(process.argv)
// console.log({_at:new Date().toJSON(),_from:'merge_envar', about: '- Merging Command Line Parameters into Configuration (把命令行参数值合并入配置)' },'\n,')
for (let key in commander) {
if (!/^_/.test(key) && typeof commander[key] === 'string') {
// commander 自带了一批 _开头的属性过滤掉
rawEnvar[key] = commander[key]
}
}
delete rawEnvar.commanderOptions
}
console.log({ _at: new Date().toJSON(), _from: 'merge_envar', about: `>>>>>>>> Configured [${process.env.NODE_ENV}] Environment >>>>>>>>` }, '\n,')
return rawEnvar
},
/* 读取动态配置文件中的环境变量。
*/
get_dynamic_envar ({ dynamicEnvarFiles = ['./envar-base-dynamic.js', './envar-base-dynamic.gitignore.js'], base2app } = {}) {
// config file should be absolute or relative to the node process's dir.
let dynamicEnvar = {}
if (typeof dynamicEnvarFiles === 'string') {
// a file containing more files
if (fs.existsSync(path.resolve(dynamicEnvarFiles))) {
dynamicEnvarFiles = require(path.resolve(dynamicEnvarFiles))
} else {
console.warn({ _at: new Date().toJSON(), _from: 'get_dynamic_envar', about: ` - ${dynamicEnvarFiles} is missing.` }, '\n,')
dynamicEnvarFiles = undefined
}
}
if (Array.isArray(dynamicEnvarFiles)) {
for (let dynamicFile of dynamicEnvarFiles) {
if (fs.existsSync(path.resolve(dynamicFile))) {
delete require.cache[require.resolve(path.resolve(dynamicFile))] // delete require.cache[fullpath] 不起作用,必须要加 require.resolve
const fileContent = require(path.resolve(dynamicFile))
if (typeof fileContent === 'object') {
assign_deep(dynamicEnvar, fileContent)
} else if (typeof fileContent === 'function') {
assign_deep(dynamicEnvar, fileContent({ base2app }))
}
console.info({ _at: new Date().toJSON(), _from: 'get_dynamic_envar', about: ` - ${dynamicFile} is parsed.` }, '\n,')
} else {
console.warn({ _at: new Date().toJSON(), _from: 'get_dynamic_envar', about: ` - ${dynamicFile} is missing.` }, '\n,')
}
}
} else if (typeof dynamicEnvarFiles === 'object') {
dynamicEnvar = dynamicEnvarFiles
} else if (typeof dynamicEnvarFiles === 'function') {
dynamicEnvar = dynamicEnvarFiles()
} else {
console.warn({ _at: new Date().toJSON(), _from: 'get_dynamic_envar', about: ` - unrecognized dynamicEnvarFiles!` }, '\n,')
}
return dynamicEnvar
},
/* 隐藏机密配置文件中的环境变量。
* 需要输出当前环境变量时,必须调用本函数,避免机密信息被输出。
*/
mask_secret_envar ({ rawEnvar, secretEnvarFiles = ['./envar-base-secret.js', './envar-base-secret.gitignore.js'] } = {}) {
let envar = JSON.parse(JSON.stringify(rawEnvar)) // 复制一份,避免污染
let secretEnvar = {}
if (typeof secretEnvarFiles === 'string') {
if (fs.existsSync(path.resolve(secretEnvarFiles))) {
secretEnvarFiles = require(path.resolve(secretEnvarFiles))
} else {
console.warn({ _at: new Date().toJSON(), _from: 'mask_secret_envar', about: ` - ${secretEnvarFiles} is missing.` }, '\n,')
secretEnvarFiles = undefined
}
}
console.info({ _at: new Date().toJSON(), _from: 'mask_secret_envar', about: ` - Parsing secretEnvarFiles...` }, '\n,')
if (Array.isArray(secretEnvarFiles)) {
for (let secretFile of secretEnvarFiles) {
if (fs.existsSync(path.resolve(secretFile))) {
const fileContent = require(path.resolve(secretFile))
if (typeof fileContent === 'object') {
assign_deep(secretEnvar, fileContent)
} else if (typeof fileContent === 'function') {
assign_deep(secretEnvar, fileContent())
}
console.info({ _at: new Date().toJSON(), _from: 'mask_secret_envar', about: ` - ${secretFile} is parsed.` }, '\n,')
} else {
console.warn({ _at: new Date().toJSON(), _from: 'mask_secret_envar', about: ` - ${secretFile} is missing.` }, '\n,')
}
}
} else if (typeof secretEnvarFiles === 'object') {
secretEnvar = secretEnvarFiles
} else if (typeof secretEnvarFiles === 'function') {
dynamicEnvar = secretEnvarFiles()
} else {
console.warn({ _at: new Date().toJSON(), _from: 'mask_secret_envar', about: ` - unrecognized secretEnvarFiles!` }, '\n,')
}
for (let key in secretEnvar) {
envar[key] = '****** confidential ******'
}
return envar
},
// 预制方法
envar_all ({
files = [
'./envar-base-basic.js',
'./envar-base-basic.gitignore.js',
'./envar-base-dynamic.js',
'./envar-base-dynamic.gitignore.js',
'./envar-base-secret.js',
'./envar-base-secret.gitignore.js',
],
} = {}) {
let envar = this.get_envar({ inProcess: false, refresh: true, files })
if (process.argv.length > 2 && Array.isArray(envar.commanderOptions)) {
console.info({ _at: new Date().toJSON(), _from: 'envar_all', about: '- Loading Command Line Parameters (载入命令行参数)' }, '\n,')
const commander = require('commander')
commander.version(envar.Base_Version || '0.0.1', '-v, --version') // 默认是 -V。如果要 -v就要加 '-v --version'
for (let [key, param, desc] of envar.commanderOptions || []) {
commander.option(param, `${desc} Default = "${envar[key]}"`)
}
commander.parse(process.argv)
// console.log({_at:new Date().toJSON(),_from:'envar_all', about: '- Merging Command Line Parameters into Configuration (把命令行参数值合并入配置)' },'\n,')
for (let key in commander) {
if (!/^_/.test(key) && typeof commander[key] === 'string') {
// commander 自带了一批 _开头的属性过滤掉
envar[key] = commander[key]
}
}
delete envar.commanderOptions
}
return envar
},
envar_basic ({ envarKey, files = ['./envar-base-basic.js', './envar-base-basic.gitignore.js'] } = {}) {
return (my['basic'] = this.get_envar({ envarKey, inProcess: false, inCache: true, cachename: 'basic', refresh: false, files }))
},
envar_dynamic ({ envarKey, files = ['./envar-base-dynamic.js', './envar-base-dynamic.gitignore.js'] } = {}) {
return this.get_envar({ envarKey, inProcess: true, inCache: false, refresh: true, files })
},
envar_sesame ({ envarKey, files = ['./envar-base-secret.js', './envar-base-secret.gitignore.js'] } = {}) {
return (my['sesame'] = this.get_envar({ envarKey, inProcess: true, inCache: true, cachename: 'sesame', refresh: false, files }))
},
// 可定制的通用方法
get_envar ({ envarKey, inProcess, inCache, cachename, files, refresh } = {}) {
if (envarKey)
return (
(inProcess && process.env[envarKey]) ||
(inCache && my[cachename || 'envar']?.[envarKey]) ||
(files && this.merge_envar_files({ refresh, files })[envarKey])
)
else return (inCache && my[cachename || 'envar']) || (files && this.merge_envar_files({ refresh, files }))
},
merge_envar_files ({ refresh = false, files } = {}) {
let envar = {}
if (typeof files === 'string') {
// a file containing more files
if (fs.existsSync(path.resolve(files))) {
files = require(path.resolve(files))
} else {
console.warn({ _at: new Date().toJSON(), _from: 'merge_envar_files', about: ` - ${files} is missing.` }, '\n,')
files = undefined
}
}
if (Array.isArray(files)) {
for (let envarFile of files) {
if (fs.existsSync(path.resolve(envarFile))) {
if (refresh) {
delete require.cache[require.resolve(path.resolve(envarFile))] // delete require.cache[fullpath] 不起作用,必须要加 require.resolve
}
assign_deep(envar, require(path.resolve(envarFile)))
console.info({ _at: new Date().toJSON(), _from: 'merge_envar_files', about: ` - ${envarFile} is parsed.` }, '\n,')
} else {
console.warn({ _at: new Date().toJSON(), _from: 'merge_envar_files', about: ` - ${envarFile} is missing.` }, '\n,')
}
}
} else if (typeof files === 'object') {
envar = files
} else {
console.warn({ _at: new Date().toJSON(), _from: 'merge_envar_files', about: ` - unrecognized files!` }, '\n,')
}
return envar
},
}