Skip to content

Instantly share code, notes, and snippets.

@osher
Last active April 4, 2024 14:08
Show Gist options
  • Save osher/77bcfff1e7f36d2f6812293b9c527732 to your computer and use it in GitHub Desktop.
Save osher/77bcfff1e7f36d2f6812293b9c527732 to your computer and use it in GitHub Desktop.
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