https://github.com/seancheung/kuconfig
Smart configuration control
https://github.com/seancheung/kuconfig
config dotenv env
Last synced: 12 months ago
JSON representation
Smart configuration control
- Host: GitHub
- URL: https://github.com/seancheung/kuconfig
- Owner: seancheung
- License: mit
- Created: 2018-10-08T10:02:16.000Z (over 7 years ago)
- Default Branch: master
- Last Pushed: 2022-02-11T12:24:12.000Z (over 4 years ago)
- Last Synced: 2025-05-29T00:58:13.860Z (about 1 year ago)
- Topics: config, dotenv, env
- Language: JavaScript
- Size: 146 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 3
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Kuconfig
[![Master Build][travis-master]][travis-url]
Smart configuration control
[travis-master]: https://img.shields.io/travis/seancheung/kuconfig/master.svg?label=master
[travis-url]: https://travis-ci.org/seancheung/kuconfig
[中文](https://github.com/seancheung/kuconfig/blob/master/README.zh.md)
## Install
```bash
npm i kuconfig
```
## Basic Usage
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
```json
{
"app": {
/** content of app.json **/
},
"database": {
/** content of database.json **/
},
"language": {
/** content of language.json **/
}
}
```
```javascript
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
```json
{
"app": {
"name": "MY APP"
}
}
```
```javascript
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):
```javascript
// 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:
```javascript
// delete cached config
config.__.desolve();
// reload
config = require('kuconfig');
```
## Envs Mode
```javascript
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_
```json
{
"app": {
"name": "myapp",
"host": "localhost"
}
}
```
_src/development.json_
```json
{
"app": {
"port": 8080,
"host": "myhost"
}
}
```
```javascript
process.env.NODE_ENV = 'development';
const config = require('kuconfig/override');
```
This merges config files to
```json
{
"app": {
"name": "myapp",
"port": 8080,
"host": "myhost"
}
}
```
## Env File(optional)
By default, This package reads a _.env_ file in your working directory and gets extra environment variables from this file.
```bash
NODE_ENV=production
CONFIG_FILE=src/config.json
```
You may override this behaviour by passing the following environment variable:
```javascript
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.**
```bash
process.env.ENV_INJECT='true'
NODE_ENV=development npm start
```
```plain
# NODE_ENV won't be set because it already exists
NODE_ENV=production
CONFIG_FILE=src/config.json
```
## Smart Config File
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)
```json
{
"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:
```plain
APP_NAME=myapp
DB_HOST=10.10.10.100
DB_USER=admin
```
The loaded config will be
```json
{
"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`
### Other Keywords
**Operators**
$abs, $add(+), $sub(-), $mul(\*), $div(/), $mod(%), $ceil, $floor, $round, $trunc, \$sign
**Comparers**
$gt(>), $gte(>=), $lt(<), $lte(<=), $eq(===), $eql(==), $ne(!==), $neql(!=), $in, $ni
### Skip Parsing
To skip parsing a file or a part of a file, use **\$skip** option:
```json
{
"$skip": true,
"key": {
"$env": "APP_KEY"
}
}
```
The loaded file will be
```json
{
"key": {
"$env": "APP_KEY"
}
}
```
## Utils
A utils object is attached to each config instance. You can acces it by the getter `__`:
```javascript
// 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:
```javascript
const utils = require('kuconfig/utils');
// NOTE: utils.desolve will be undefined
```
## Plugins
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.
### Webpack
To integrate into webpack, there is a built-in plugin:
```javascript
const KuconfigPlugin = require('kuconfig/plugins/webpack');
```
_webpack.config.js_
```javascript
plugins: [new KuconfigPlugin()];
```
Usage in your modules:
```javascript
import * as config from 'kuconfig';
```
Use envs mode
```javascript
plugins: [new KuconfigPlugin({ mode: 'envs' })];
```
### Vite
> Same as Webpack integration, the loaded object will be a plain object at run time.
```javascript
import kuconfig from 'kuconfig/plugins/vite';
```
_vite.config.js_
```javascript
{
optimizeDeps: {
exclude: ['kuconfig']
},
plugins: [kuconfig()]
}
```
Usage in your modules:
```javascript
import config from 'kuconfig';
```
Use envs mode
```javascript
{
optimizeDeps: {
exclude: ['kuconfig']
},
plugins: [kuconfig({ mode: 'envs' })]
}
```
### Metro(React Native)
> Same as Webpack integration, the loaded object will be a plain object at run time.
A metro babel transformer is provided:
_metro.config.js_
```javascript
module.exports = {
transformer: {
babelTransformerPath: require.resolve('kuconfig/plugins/metro')
}
};
```
**With other transformers**
_transformers.js_
```javascript
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_
```javascript
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:
```javascript
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
## Using Typescript
By default the imported config object implements `Record`.
You can add a typing file to your project to overwite `kuconfig` module:
_kuconfig.d.ts_
```typescript
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;
}
namespace Config {
interface DB {
readonly host: string;
readonly port: number;
}
}
}
// don't miss this line
export {};
```
The corresponding config file structure:
```json
{
"name": "myapp",
"db": {
"host": "localhost",
"port": 3306
}
}
```
## Test
```bash
npm test
```
## License
See [License](https://github.com/seancheung/kuconfig/blob/master/LICENSE)