Last active
April 4, 2024 14:08
-
-
Save osher/77bcfff1e7f36d2f6812293b9c527732 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as requireYaml from "require-yml"; | |
import * as set from "lodash.set"; | |
import * as merge from "deepmerge"; | |
import { load as parseYaml } from "js-yaml"; | |
import { readFileSync } from "fs"; | |
type StringMap = { [key: string]: string }; | |
let config: any, configRootPath: string; | |
_resetConfig(); | |
export const configLoader = (process = global.process) => { | |
if (config) return config; | |
const { name, version } = JSON.parse( | |
readFileSync(process.cwd() + "/package.json").toString() | |
); | |
const { fromEnv = {}, ...defaults } = loadFile(configRootPath); | |
const env = parseEnvVars(process.env, fromEnv); | |
const switches = parseCliSwitches(process.argv); | |
return (config = merge.all([ | |
{ package: { name, version } }, | |
defaults, | |
env, | |
switches, | |
])); | |
}; | |
function loadFile(configRootPath) { | |
const conf = requireYaml(configRootPath); | |
if (!conf || !Object.keys(conf).length) { | |
const cwd = process.cwd(); | |
const msg = `no config found at: ${configRootPath} (cwd: ${cwd})`; | |
throw Object.assign(new ConfigError(msg), { cwd }); | |
} | |
return conf; | |
} | |
/** | |
* @param envParams | |
* any process.env equivalent | |
* @param fromEnv | |
* an object who's keys are names of env-vars, | |
* and its values are paths on the config object to override in case this env-variable is found. | |
* @returns config object | |
*/ | |
export const parseEnvVars = (envParams: StringMap, fromEnv: StringMap) => | |
Object.entries(fromEnv).reduce( | |
(config, [paramName, configPath]) => | |
envParams.hasOwnProperty(paramName) | |
? set(config, configPath, parseYaml(envParams[paramName])) | |
: config, | |
{} | |
); | |
class ConfigError extends Error { pwd: string; } | |
class CliSwitchError extends Error {} | |
/** | |
* @param argv any process.argv equivalent | |
* @returns a config object | |
*/ | |
export const parseCliSwitches = (argv: Array<string>) => { | |
let current = argv[2]; | |
const rest = argv.slice(3); | |
const config = {}; | |
rest.concat([null]).forEach((next) => { | |
if (next === null) | |
if (current == null) return; | |
else next = "true"; | |
//current is an implicit boolean | |
if (next.startsWith("-")) { | |
set(config, current, true); | |
current = next.replace(/^-{1,2}/, ""); | |
return; | |
} | |
//next is value for current | |
if (current === null) | |
throw new CliSwitchError("found a value without a preceding switch"); | |
set(config, current.replace(/^-{1,2}/, ""), parseYaml(next)); | |
current = null; | |
}); | |
return config; | |
}; | |
/** | |
for unit tests | |
*/ | |
export const resetConfig = _resetConfig; | |
function _resetConfig(crp = "./config") { | |
configRootPath = crp; | |
config = null; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment