Smart configuration control
npm i kuconfig
This package by default reads the config folder in your working directory and merge all json files in it into one object, with each file name(without extension) being key and corresponding content being value:
- config
- app.json
- database.json
- language.json
Loaded as
{
"app": {
/** content of app.json **/
},
"database": {
/** content of database.json **/
},
"language": {
/** content of language.json **/
}
}
const config = require('kuconfig');
console.log(config.app.name);
console.log(config.database.mysql.host);
console.log(config.language.supported[0]);
If folder config does not exist, file config.json will be checked instead.
- config.json
{
"app": {
"name": "MY APP"
}
}
const config = require('kuconfig');
console.log(config.app.name);
You can override the config file path with an environment variable CONFIG_FILE
(must be set before this module being loaded):
// read a folder
process.env.CONFIG_FILE = 'src/config';
// read a file
process.env.CONFIG_FILE = 'src/config.json';
// with absolute path
process.env.CONFIG_FILE = '/path/to/src/config.json';
const config = require('kuconfig');
console.log(config.name);
It won't be loaded again once done. To reload configs, you need to call:
// delete cached config
config.__.desolve();
// reload
config = require('kuconfig');
require('kuconfig/override');
For esmodule style use
import config from 'kuconfig/override.js'
instead
In this mode, the config/default.json
is loaded, followed by config/xxx.json
(where xxx
is equal to process.env.NODE_ENV
). Then a deep merge of those two objects happends.
src/default.json
{
"app": {
"name": "myapp",
"host": "localhost"
}
}
src/development.json
{
"app": {
"port": 8080,
"host": "myhost"
}
}
process.env.NODE_ENV = 'development';
const config = require('kuconfig/override');
This merges config files to
{
"app": {
"name": "myapp",
"port": 8080,
"host": "myhost"
}
}
By default, This package reads a .env file in your working directory and gets extra environment variables from this file.
NODE_ENV=production
CONFIG_FILE=src/config.json
You may override this behaviour by passing the following environment variable:
process.env.ENV_FILE = 'src/.env';
const config = require('kuconfig');
You may optionally set ENV_INJECT
to inject env file variables into process.env
.
Existing environment variables won't be overrided.
process.env.ENV_INJECT='true'
NODE_ENV=development npm start
# NODE_ENV won't be set because it already exists
NODE_ENV=production
CONFIG_FILE=src/config.json
A bunch of keywords can be used in config files. The syntax is similar to mongodb query. Nested syntax is supported(they will be resolved inner to outer)
{
"name": {
"$env": "APP_NAME"
},
"database": {
"port": {
"$env": ["DB_PORT", 3306]
},
"host": {
"$env": ["DB_HOST", "localhost"]
},
"username": {
"$env": "DB_USER"
},
"password": {
"$env": ["DB_PASS", { "$env": "DB_USER" }]
}
}
}
The $env
object reads environment varaibles with an optional fallback value. With the following env set:
APP_NAME=myapp
DB_HOST=10.10.10.100
DB_USER=admin
The loaded config will be
{
"name": "myapp",
"database": {
"port": 3306,
"host": "10.10.10.100",
"username": "admin",
"password": "admin"
}
}
Here is a list of available keywords:
$env
Get value from environment variables(first try env file, then process.env) with an optional fallback value
- params:
string
|[string, any]
- returns
any
$var
Get value from env file with an optional fallback value
- params:
string
|[string, any]
- returns
any
$path
Resolve the path to absolute from current working directory
- params:
string
- returns
string
$file
Resolve the path and read the file with an optional encoding
- params:
string
|[string, string]
- returns
string
|Buffer
$json
Parse the given string into object
- params:
string
- returns
any
$number
Parse the given string into a number
- params:
string
|number
- returns
number
$concat
Concat strings or arrays
- params:
string[]
|[][]
- returns
string
|any[]
$max
Return the max number in the array
- params:
number[]
- returns
number
$min
Return the min number in the array
- params:
number[]
- returns
number
$sum
Sum the numbers in the array
- params:
number[]
- returns
number
$avg
Return the average value of the array
- params:
number[]
- returns
number
$first
Return the first element in the array
- params:
any[]
- returns
any
$last
Return the last element in the array
- params:
any[]
- returns
any
$at
Return element in the array at the given index
- params:
[any[], number]
- returns
any
$asce
Sort the numbers in ascending order
- params:
number[]
- returns
number[]
$asce
Sort the numbers in descending order
- params:
number[]
- returns
number[]
$rand
Return an element at random index
- params:
any[]
- returns
any
$rands
Return the given amount of elements at random indices
- params:
[any[], number]
- returns
any[]
$reverse
Reverse an array
- params:
any[]
- returns
any[]
$slice
Slice an array from the given index to an optional end index
- params:
[any[], number]
|[any[], number, number]
- returns
any[]
$count
Return the length of an array
- params:
any[]
- returns
number
$join
Join an array to string with an optional separator
- params:
[any[]]
|[any[], string]
- returns
string
$merge
Return the merge of two objects
- params:
[any, any]
- returns
any
$keys
Return the keys of an object
- params:
any
- returns
string[]
$vals
Return the values of an object
- params:
any
- returns
any[]
$zip
Merge a series of key-value pairs into an object
- params:
[any, any][]
- returns
any
$zap
Split an object into key-value pairs
- params:
any
- returns
[any, any][]
$cond/$if
Return the second or third element based on the boolean value of the first element
- params:
[boolean, any, any]
- returns
any
$and/&&
Return true only if both two elements' boolean values are true
- params:
[boolean, boolean]
- returns
boolean
$or/||
Return true if any of the two elements' boolean value is true
- params:
[boolean, boolean]
- returns
boolean
$not
Return true only if the given value is false
- params:
boolean
- returns
boolean
$true
Return true if the given value is true or 'true'(case insensitive) or 1 or '1'
- params:
boolean|string
- returns
boolean
$null
Return true if the given value is null or undefined
- params:
any
- returns
boolean
$undefined
Return true only if the given value is undefined
- params:
any
- returns
boolean
$type
Return true only if the given value is of the given type
- params:
[any, string]
- returns
boolean
$test|!!
Return boolean test result(!!) of the given value
- params:
[any, string]
- returns
boolean
$upper
Transform input string to upper case
- params:
string
- returns
string
$lower
Transform input string to lower case
- params:
string
- returns
string
$split
Split input string into an array with optional delimiter and/or limit
- params:
string|[string, string]|[string, string, number]
- returns
string[]
$expand
Expand variables as in shellscript
- params:
string
- returns
string
$regex
Transform string to regex with optional flags
- params:
string|[string, string]
- returns
RegExp
Operators
$abs, $add(+), $sub(-), $mul(*), $div(/), $mod(%), $ceil, $floor, $round, $trunc, $sign
Comparers
$gt(>), $gte(>=), $lt(<), $lte(<=), $eq(===), $eql(==), $ne(!==), $neql(!=), $in, $ni
To skip parsing a file or a part of a file, use $skip option:
{
"$skip": true,
"key": {
"$env": "APP_KEY"
}
}
The loaded file will be
{
"key": {
"$env": "APP_KEY"
}
}
A utils object is attached to each config instance. You can acces it by the getter __
:
// make a deep cloned copy
const copy = config.__.clone(obj);
// merge target object's copy into source object's copy deeply
const merged = config.__.merge(source, target);
// load environment variables, optionally inject them into process.env
const envs = config.__.env('./.env', true);
// parse config by json string
let conf = config.__.resolve(
JSON.parse(fs.readFileSync('./config.json', 'utf8')),
envs
);
// load config by path(absolute or relative to working directory)
conf = config.__.load('./config.json');
// delete config referenced cache(the next time you reference this module, config file will be reloaded)
config.__.desolve();
// parse config with custom expressions
config.__.parse(
{
$encode(params) {
if (params == null) {
return params;
}
if (typeof params === 'string') {
return encodeURI(params);
}
throw new Error('$encode expects a string');
}
},
{
url: {
$encode: 'this is a test'
}
},
envs
);
// Expand variables as in shellscript
config.__.substitute('Today is ${date} and I am ${name}.', {
date: new Date(),
name: 'Adam'
});
// To support dynamic code execution(using v8 vm module)
config.__.substitute(
'I am ${age} years old and I will be ${age + 1} next year. You can call me ${profile.name}.',
{
age: 17,
profile: {
name: 'Tom'
}
},
true
);
You can also use utils without the config instance:
const utils = require('kuconfig/utils');
// NOTE: utils.desolve will be undefined
If this module is used in a front-end project, the loading will happen in bundling step. A parsed config object will be injected to the output bundle. Accessing the config object at runtime is nothing difference to accessing a plain object.
To integrate into webpack, there is a built-in plugin:
const KuconfigPlugin = require('kuconfig/plugins/webpack');
webpack.config.js
plugins: [new KuconfigPlugin()];
Usage in your modules:
import * as config from 'kuconfig';
Use envs mode
plugins: [new KuconfigPlugin({ mode: 'envs' })];
Same as Webpack integration, the loaded object will be a plain object at run time.
import kuconfig from 'kuconfig/plugins/vite';
vite.config.js
{
optimizeDeps: {
exclude: ['kuconfig']
},
plugins: [kuconfig()]
}
Usage in your modules:
import config from 'kuconfig';
Use envs mode
{
optimizeDeps: {
exclude: ['kuconfig']
},
plugins: [kuconfig({ mode: 'envs' })]
}
Same as Webpack integration, the loaded object will be a plain object at run time.
A metro babel transformer is provided:
metro.config.js
module.exports = {
transformer: {
babelTransformerPath: require.resolve('kuconfig/plugins/metro')
}
};
With other transformers
transformers.js
const svgTransformer = require('react-native-svg-transformer');
const configTransformer = require('kuconfig/plugins/metro');
module.exports.getCacheKey = configTransformer.getCacheKey;
module.exports.transform = function ({ src, filename, options }) {
if (filename.endsWith('.svg')) {
return svgTransformer.transform({ src, filename, options });
} else {
return configTransformer.transform({ src, filename, options });
}
};
metro.config.js
const { assetExts, sourceExts } = require('metro-config/src/defaults/defaults');
module.exports = {
transformer: {
getTransformOptions: () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: false
}
}),
babelTransformerPath: require.resolve('./transformer.js')
},
resolver: {
assetExts: assetExts.filter(ext => ext !== 'svg'),
sourceExts: [...sourceExts, 'svg']
}
};
Usage:
import * as config from 'kuconfig';
import * as config from 'kuconfig/override';
config file cannot be hot-reloaded, you must re-run
npm start
to make the changes to take effect
By default the imported config object implements Record<string, any>
.
You can add a typing file to your project to overwite kuconfig
module:
kuconfig.d.ts
declare module 'kuconfig' {
/**
* This interface will be merged with default Config interface
*/
interface Config {
/**
* Here is the definition of your config
*/
readonly name: string;
/**
* nested config
*/
readonly db: Readonly<Config.DB>;
}
namespace Config {
interface DB {
readonly host: string;
readonly port: number;
}
}
}
// don't miss this line
export {};
The corresponding config file structure:
{
"name": "myapp",
"db": {
"host": "localhost",
"port": 3306
}
}
npm test
See License