Thoughts for convict 6.0+
brianjenkins94 opened this issue · 0 comments
This is the code that I put in front of convict
:
import * as fs from "fs";
import * as path from "path";
import convict from "convict";
const flattenedOptions = {
"env": {
"format": ["production", "development", "test"],
"default": "development",
"env": "NODE_ENV",
"arg": "env"
},
"port": {
"format": "port",
"default": 8080,
"env": "PORT",
"arg": "port"
}
};
(function bindConfigurations(directory) {
function convictify(options, namespace = []) {
for (const option of options) {
if (option["name"] === undefined) {
throw new Error("`option[\"name\"]` is required.");
}
const name = [...namespace, ...option["name"].split(/(?=[A-Z])/)].reduce(function(previous, current) {
return previous + current[0].toUpperCase() + current.substring(1);
});
const environmentVariableName = option["env"] || [...namespace, ...option["name"].split(/(?=[A-Z])/)].join("_").toUpperCase();
const commandLineArgumentName = option["arg"] || [...namespace, ...option["name"].split(/(?=[A-Z])/)].join("-").toLowerCase();
// For third-party scripts that make direct access to `process.env.*`
if (process.env[environmentVariableName] === undefined) {
if (typeof option["default"] === "object" && option["default"] !== null) {
process.env[environmentVariableName] = JSON.stringify(option["default"]);
} else {
process.env[environmentVariableName] = option["default"];
}
}
flattenedOptions[name] = {
"format": option["format"] || "String",
"default": option["default"],
"env": option["default"] && environmentVariableName,
"arg": commandLineArgumentName
};
if (option["sensitive"] !== undefined) {
flattenedOptions[name]["sensitive"] = option["sensitive"];
}
}
}
(function recurse(directory) {
for (const file of fs.readdirSync(directory)) {
if (fs.statSync(path.join(directory, file)).isDirectory()) {
recurse(path.join(directory, file));
} else if (directory === __dirname) {
if (file !== "index.ts" && path.extname(file).toLowerCase() === ".ts") {
convictify(require(path.join(directory, file))["default"], [file]);
}
} else if (path.extname(file).toLowerCase() === ".ts") {
let namespace;
if (file === "index.ts") {
namespace = directory.substring(__dirname.length + 1).split(/\//g);
} else {
namespace = [...directory.substring(__dirname.length + 1).split(/\//g), path.basename(file, path.extname(file))];
}
convictify(require(path.join(directory, file))["default"], namespace);
}
}
})(directory);
})(__dirname);
export const config = convict(flattenedOptions);
// Perform validation
config.validate({ "allowed": "strict" });
And then I setup my config
directory like so:
config
├── google
│ ├── persistenceToken.json
│ └── index.ts
├── index.ts // Shown above
└── mssql
└── index.ts
The code that I have added recursively crawls the config
directory and builds a config object for convict
with these added benefits:
- Smaller configs:
export default [
{
"name": "username",
"format": "String",
"default": "AzureDiamond"
},
{
"name": "password",
"format": "String",
"default": "hunter2"
},
];
- Consistant naming conventions
Say the above the configuration was in a directory called irc
in a file called index.ts
, the two keys above would become ircUsername
and ircPassword
. If the file was named something other than index, then that would be camelcased and inserted in between the folder name and the key name.
- Automatically set
env
andargs
properties
Unless otherwise specified, the above mentioned keys would automatically be given the env
of IRC_USERNAME
and the arg
of --irc-username
.
I have also considered adding dynamic shorthand flags on a first come, first served basis and an auto-generated help command.
I submit these ideas for your consideration 🙂