Finished completely rewriting both library and unit tests
This commit is contained in:
parent
1104c0d3ad
commit
1014cdec86
57 changed files with 2785 additions and 2929 deletions
|
@ -23,26 +23,10 @@ commands:
|
|||
key: v{{ .Environment.CIRCLE_CACHE_VERSION }}-{{ arch }}-npm-cache-{{ .Branch }}-{{ .Environment.CIRCLE_JOB }}-{{ checksum "package-lock.json" }}
|
||||
paths:
|
||||
- ~/.npm/_cacache
|
||||
coverage:
|
||||
steps:
|
||||
- run:
|
||||
command: npm run cover
|
||||
- run:
|
||||
command: npm run coveralls
|
||||
jobs:
|
||||
node-v8:
|
||||
node-v14:
|
||||
docker:
|
||||
- image: node:8
|
||||
steps:
|
||||
- test-nodejs
|
||||
node-v10:
|
||||
docker:
|
||||
- image: node:10
|
||||
steps:
|
||||
- test-nodejs
|
||||
node-v12:
|
||||
docker:
|
||||
- image: node:12
|
||||
- image: node:14
|
||||
steps:
|
||||
- test-nodejs
|
||||
|
||||
|
@ -50,6 +34,4 @@ workflows:
|
|||
version: 2
|
||||
node-multi-build:
|
||||
jobs:
|
||||
- node-v8
|
||||
- node-v10
|
||||
- node-v12
|
||||
- node-v14
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
node_modules
|
||||
usage.js
|
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 6
|
||||
},
|
||||
"rules": {
|
||||
"no-unused-vars": "error"
|
||||
}
|
||||
}
|
1
.gitattributes
vendored
1
.gitattributes
vendored
|
@ -1 +0,0 @@
|
|||
package-lock.json binary
|
12
.gitignore
vendored
12
.gitignore
vendored
|
@ -1,14 +1,4 @@
|
|||
.DS_Store
|
||||
config.json
|
||||
test/fixtures/*.json
|
||||
!test/fixtures/complete.json
|
||||
!test/fixtures/malformed.json
|
||||
!test/fixtures/bom.json
|
||||
!test/fixtures/no-bom.json
|
||||
!test/fixtures/secure.json
|
||||
!test/fixtures/secure-iv.json
|
||||
node_modules/
|
||||
node_modules/*
|
||||
node_modules
|
||||
npm-debug.log
|
||||
package-lock.json
|
||||
coverage
|
|
@ -1,3 +1,9 @@
|
|||
v2.0.0 / Tue, 2 Jun 2020
|
||||
========================
|
||||
|
||||
Completely redesigned and re-written with zero dependencies among other things.
|
||||
|
||||
|
||||
v1.0.0 / Tue, 2 Jun 2020
|
||||
========================
|
||||
|
||||
|
|
121
README.md
121
README.md
|
@ -1,14 +1,17 @@
|
|||
# nconf-lite
|
||||
|
||||
Hierarchical node.js configuration with files, environment variables, and atomic object merging.
|
||||
Nconf-lite is a complete re-written design of original nconf with zero dependancy, tiny and fast while maintaining most if not all of the documented features of the old nconf.
|
||||
|
||||
This is a fork of nconf without the bloated yargs dependancy.
|
||||
It is a hierarchical node.js configuration with files, environment variables, and atomic object merging.
|
||||
|
||||
Compared to nconf running at 952KB with over 220 files *installed*, nconf-lite is clocking at measly 42KB with only 11 files of easily reviewable code and a ton more unit test, testing every micro functionality.
|
||||
|
||||
## Example
|
||||
Using nconf is easy; it is designed to be a simple key-value store with support for both local and remote storage. Keys are namespaced and delimited by `:`. Let's dive right into sample usage:
|
||||
|
||||
``` js
|
||||
var nconf = require('nconf');
|
||||
import Nconf from 'nconf-lite'
|
||||
const nconf = new Nconf()
|
||||
|
||||
//
|
||||
// Setup nconf to use (in-order):
|
||||
|
@ -35,11 +38,7 @@ Using nconf is easy; it is designed to be a simple key-value store with support
|
|||
//
|
||||
// Save the configuration object to disk
|
||||
//
|
||||
nconf.save(function (err) {
|
||||
require('fs').readFile('path/to/your/config.json', function (err, data) {
|
||||
console.dir(JSON.parse(data.toString()))
|
||||
});
|
||||
});
|
||||
nconf.save()
|
||||
```
|
||||
|
||||
If you run the above script:
|
||||
|
@ -67,7 +66,8 @@ Configuration management can get complicated very quickly for even trivial appli
|
|||
A sane default for this could be:
|
||||
|
||||
``` js
|
||||
var nconf = require('nconf');
|
||||
import Nconf from 'nconf-lite'
|
||||
const nconf = new Nconf()
|
||||
|
||||
//
|
||||
// 1. any overrides
|
||||
|
@ -92,16 +92,6 @@ A sane default for this could be:
|
|||
//
|
||||
nconf.file('custom', '/path/to/config.json');
|
||||
|
||||
//
|
||||
// Or searching from a base directory.
|
||||
// Note: `name` is optional.
|
||||
//
|
||||
nconf.file(name, {
|
||||
file: 'config.json',
|
||||
dir: 'search/from/here',
|
||||
search: true
|
||||
});
|
||||
|
||||
//
|
||||
// 5. Any default values
|
||||
//
|
||||
|
@ -112,17 +102,6 @@ A sane default for this could be:
|
|||
|
||||
## API Documentation
|
||||
|
||||
The top-level of `nconf` is an instance of the `nconf.Provider` abstracts this all for you into a simple API.
|
||||
|
||||
### nconf.add(name, options)
|
||||
Adds a new store with the specified `name` and `options`. If `options.type` is not set, then `name` will be used instead:
|
||||
|
||||
``` js
|
||||
nconf.add('supplied', { type: 'literal', store: { 'some': 'config' });
|
||||
nconf.add('user', { type: 'file', file: '/path/to/userconf.json' });
|
||||
nconf.add('global', { type: 'file', file: '/path/to/globalconf.json' });
|
||||
```
|
||||
|
||||
### nconf.any(names, callback)
|
||||
Given a set of key names, gets the value of the first key found to be truthy. The key names can be given as separate arguments
|
||||
or as an array. If the last argument is a function, it will be called with the result; otherwise, the value is returned.
|
||||
|
@ -131,36 +110,21 @@ or as an array. If the last argument is a function, it will be called with the r
|
|||
//
|
||||
// Get one of 'NODEJS_PORT' and 'PORT' as a return value
|
||||
//
|
||||
var port = nconf.any('NODEJS_PORT', 'PORT');
|
||||
|
||||
//
|
||||
// Get one of 'NODEJS_IP' and 'IPADDRESS' using a callback
|
||||
//
|
||||
nconf.any(['NODEJS_IP', 'IPADDRESS'], function(err, value) {
|
||||
console.log('Connect to IP address ' + value);
|
||||
});
|
||||
let port = nconf.any('NODEJS_PORT', 'PORT');
|
||||
```
|
||||
|
||||
### nconf.use(name, options)
|
||||
Similar to `nconf.add`, except that it can replace an existing store if new options are provided
|
||||
### nconf.use(name)
|
||||
Fetch a specific store with the specified name.
|
||||
|
||||
``` js
|
||||
//
|
||||
// Load a file store onto nconf with the specified settings
|
||||
//
|
||||
nconf.use('file', { file: '/path/to/some/config-file.json' });
|
||||
|
||||
nconf.file('custom', '/path/to/config.json');
|
||||
//
|
||||
// Replace the file store with new settings
|
||||
// Grab the instance and set it to be readonly
|
||||
//
|
||||
nconf.use('file', { file: 'path/to/a-new/config-file.json' });
|
||||
```
|
||||
|
||||
### nconf.remove(name)
|
||||
Removes the store with the specified `name.` The configuration stored at that level will no longer be used for lookup(s).
|
||||
|
||||
``` js
|
||||
nconf.remove('file');
|
||||
nconf.use('custom').readOnly = true
|
||||
```
|
||||
|
||||
### nconf.required(keys)
|
||||
|
@ -185,10 +149,7 @@ config
|
|||
.file( 'oauth', path.resolve( 'configs', 'oauth', config.get( 'OAUTH:MODE' ) + '.json' ) )
|
||||
.file( 'app', path.resolve( 'configs', 'app.json' ) )
|
||||
.required([ 'LOGS_MODE']) // here you should haveLOGS_MODE, otherwise throw an error
|
||||
.add( 'logs', {
|
||||
type: 'literal',
|
||||
store: require( path.resolve( 'configs', 'logs', config.get( 'LOGS_MODE' ) + '.js') )
|
||||
} )
|
||||
.literal( 'logs', require( path.resolve( 'configs', 'logs', config.get( 'LOGS_MODE' ) + '.js') ))
|
||||
.defaults( defaults );
|
||||
```
|
||||
|
||||
|
@ -216,9 +177,6 @@ If this option is enabled, all calls to `nconf.get()` must pass in a lowercase s
|
|||
Attempt to parse well-known values (e.g. 'false', 'true', 'null', 'undefined', '3', '5.1' and JSON values)
|
||||
into their proper types. If a value cannot be parsed, it will remain a string.
|
||||
|
||||
#### `readOnly: {true|false}` (defaultL `true`)
|
||||
Allow values in the env store to be updated in the future. The default is to not allow items in the env store to be updated.
|
||||
|
||||
##### `transform: function(obj)`
|
||||
Pass each key/value pair to the specified function for transformation.
|
||||
|
||||
|
@ -248,9 +206,9 @@ If the return value is falsey, the entry will be dropped from the store, otherwi
|
|||
//
|
||||
// Can also specify a separator for nested keys (instead of the default ':')
|
||||
//
|
||||
nconf.env('__');
|
||||
nconf.env({ separator: '__' });
|
||||
// Get the value of the env variable 'database__host'
|
||||
var dbHost = nconf.get('database:host');
|
||||
let dbHost = nconf.get('database:host');
|
||||
|
||||
//
|
||||
// Can also lowerCase keys.
|
||||
|
@ -260,10 +218,10 @@ If the return value is falsey, the entry will be dropped from the store, otherwi
|
|||
|
||||
// Given an environment variable PORT=3001
|
||||
nconf.env();
|
||||
var port = nconf.get('port') // undefined
|
||||
let port = nconf.get('port') // undefined
|
||||
|
||||
nconf.env({ lowerCase: true });
|
||||
var port = nconf.get('port') // 3001
|
||||
let port = nconf.get('port') // 3001
|
||||
|
||||
//
|
||||
// Or use all options
|
||||
|
@ -281,7 +239,7 @@ If the return value is falsey, the entry will be dropped from the store, otherwi
|
|||
return obj;
|
||||
}
|
||||
});
|
||||
var dbHost = nconf.get('database:host');
|
||||
let dbHost = nconf.get('database:host');
|
||||
```
|
||||
|
||||
### Literal
|
||||
|
@ -294,7 +252,7 @@ Loads a given object literal into the configuration hierarchy. Both `nconf.defau
|
|||
```
|
||||
|
||||
### File
|
||||
Based on the Memory store, but provides additional methods `.save()` and `.load()` which allow you to read your configuration to and from file. As with the Memory store, all method calls are synchronous with the exception of `.save()` and `.load()` which take callback functions.
|
||||
Based on the Memory store, but provides additional methods `.save()` and `.load()` which allow you to read your configuration to and from file. As with the Memory store, all method calls are synchronous includ `.save()` and `.load()`.
|
||||
|
||||
It is important to note that setting keys in the File engine will not be persisted to disk until a call to `.save()` is made. Note a custom key must be supplied as the first parameter for hierarchy to work if multiple files are used.
|
||||
|
||||
|
@ -303,6 +261,10 @@ It is important to note that setting keys in the File engine will not be persist
|
|||
// add multiple files, hierarchically. notice the unique key for each file
|
||||
nconf.file('user', 'path/to/your/user.json');
|
||||
nconf.file('global', 'path/to/your/global.json');
|
||||
|
||||
// Set a variable in the user store and save it
|
||||
nconf.user('user').set('some:variable', true)
|
||||
nconf.user('user').save()
|
||||
```
|
||||
|
||||
The file store is also extensible for multiple file formats, defaulting to `JSON`. To use a custom format, simply pass a format object to the `.use()` method. This object must have `.parse()` and `.stringify()` methods just like the native `JSON` object.
|
||||
|
@ -311,7 +273,7 @@ If the file does not exist at the provided path, the store will simply be empty.
|
|||
|
||||
#### Encrypting file contents
|
||||
|
||||
As of `nconf@0.8.0` it is now possible to encrypt and decrypt file contents using the `secure` option:
|
||||
Encryption and decrypting file contents using the `secure` option:
|
||||
|
||||
``` js
|
||||
nconf.file('secure-file', {
|
||||
|
@ -340,31 +302,9 @@ This will encrypt each key using [`crypto.createCipheriv`](https://nodejs.org/ap
|
|||
}
|
||||
```
|
||||
|
||||
### Redis
|
||||
There is a separate Redis-based store available through [nconf-redis][0]. To install and use this store simply:
|
||||
|
||||
``` bash
|
||||
$ npm install nconf
|
||||
$ npm install nconf-redis
|
||||
```
|
||||
|
||||
Once installing both `nconf` and `nconf-redis`, you must require both modules to use the Redis store:
|
||||
|
||||
``` js
|
||||
var nconf = require('nconf');
|
||||
|
||||
//
|
||||
// Requiring `nconf-redis` will extend the `nconf`
|
||||
// module.
|
||||
//
|
||||
require('nconf-redis');
|
||||
|
||||
nconf.use('redis', { host: 'localhost', port: 6379, ttl: 60 * 60 * 1000 });
|
||||
```
|
||||
|
||||
## Installation
|
||||
``` bash
|
||||
npm install nconf --save
|
||||
npm install nconf-lite --save
|
||||
```
|
||||
|
||||
## Run Tests
|
||||
|
@ -374,7 +314,8 @@ Tests are written in vows and give complete coverage of all APIs and storage eng
|
|||
$ npm test
|
||||
```
|
||||
|
||||
#### Author: [Charlie Robbins](http://nodejitsu.com)
|
||||
#### Original author: [Charlie Robbins](http://nodejitsu.com)
|
||||
#### Rewriter of all that garbage: TheThing
|
||||
#### License: MIT
|
||||
|
||||
[0]: http://github.com/indexzero/nconf-redis
|
||||
[0]: http://github.com/nfp-projects/nconf-lite
|
||||
|
|
177
lib/common.mjs
Normal file
177
lib/common.mjs
Normal file
|
@ -0,0 +1,177 @@
|
|||
import fs from 'fs'
|
||||
import Memory from './stores/memory.mjs'
|
||||
|
||||
//
|
||||
// ### function validkeyvalue(key)
|
||||
// #### @key {any} key to check
|
||||
// Return string of key if valid string type key,
|
||||
// otherwise transform into new key containing
|
||||
// the error message
|
||||
export function validkeyvalue(key) {
|
||||
let type = typeof(key)
|
||||
if (key && type !== 'string' && type !== 'number') {
|
||||
return '__invalid_valuetype_of_' + type + '__'
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
//
|
||||
// ### function path (key)
|
||||
// #### @key {string} The ':' delimited key to split
|
||||
// Returns a fully-qualified path to a nested nconf key.
|
||||
// If given null or undefined it should return an empty path.
|
||||
// '' should still be respected as a path.
|
||||
//
|
||||
export function path(key, separator) {
|
||||
let invalidType = validkeyvalue(key)
|
||||
if (invalidType) {
|
||||
return [invalidType]
|
||||
}
|
||||
separator = separator || ':'
|
||||
return key == null
|
||||
|| key === ''
|
||||
? []
|
||||
: key.toString().split(separator)
|
||||
}
|
||||
|
||||
//
|
||||
// ### function key (arguments)
|
||||
// Returns a `:` joined string from the `arguments`.
|
||||
//
|
||||
export function key(...path) {
|
||||
return path.map(function(item) {
|
||||
return validkeyvalue(item) || ('' + item)
|
||||
}).join(':')
|
||||
}
|
||||
|
||||
//
|
||||
// ### function key (arguments)
|
||||
// Returns a joined string from the `arguments`,
|
||||
// first argument is the join delimiter.
|
||||
//
|
||||
export function keyed(separator, ...path) {
|
||||
return path.map(function(item) {
|
||||
return validkeyvalue(item) || ('' + item)
|
||||
}).join(separator)
|
||||
}
|
||||
|
||||
// taken from isobject npm library
|
||||
export function isObject(val) {
|
||||
return val != null && typeof val === 'object' && Array.isArray(val) === false
|
||||
}
|
||||
|
||||
// Return a new recursive deep instance of array of objects
|
||||
// or values to make sure no original object ever get touched
|
||||
export function mergeRecursiveArray(arr) {
|
||||
return arr.map(function(item) {
|
||||
if (isObject(item)) return mergeRecursive({}, item)
|
||||
if (Array.isArray(item)) return mergeRecursiveArray(item)
|
||||
return item
|
||||
})
|
||||
}
|
||||
|
||||
// Recursively merges the child into the parent.
|
||||
export function mergeRecursive(parent, child) {
|
||||
Object.keys(child).forEach(key => {
|
||||
// Arrays will always overwrite for now
|
||||
if (Array.isArray(child[key])) {
|
||||
parent[key] = mergeRecursiveArray(child[key])
|
||||
} else if (child[key] && typeof child[key] === 'object') {
|
||||
// We don't wanna support cross merging between array and objects
|
||||
// so we overwrite the old value (at least for now).
|
||||
if (parent[key] && Array.isArray(parent[key])) {
|
||||
parent[key] = mergeRecursive({}, child[key])
|
||||
} else {
|
||||
parent[key] = mergeRecursive(parent[key] || {}, child[key])
|
||||
}
|
||||
} else {
|
||||
parent[key] = child[key]
|
||||
}
|
||||
})
|
||||
|
||||
return parent
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// ### function merge (objs)
|
||||
// #### @objs {Array} Array of object literals to merge
|
||||
// Merges the specified `objs` together into a new object.
|
||||
// This differs from the old logic as it does not affect or chagne
|
||||
// any of the objects being merged.
|
||||
//
|
||||
export function merge(orgOut, orgObjs) {
|
||||
let out = orgOut
|
||||
let objs = orgObjs
|
||||
if (objs === undefined) {
|
||||
out = {}
|
||||
objs = orgOut
|
||||
}
|
||||
if (!Array.isArray(objs)) {
|
||||
throw new Error('merge called with non-array of objects')
|
||||
}
|
||||
for (let x = 0; x < objs.length; x++) {
|
||||
out = mergeRecursive(out, objs[x])
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
//
|
||||
// ### function capitalize (str)
|
||||
// #### @str {string} String to capitalize
|
||||
// Capitalizes the specified `str` if string, otherwise
|
||||
// returns the original object
|
||||
//
|
||||
export function capitalize(str) {
|
||||
if (typeof(str) !== 'string' && typeof(str) !== 'number') {
|
||||
return str
|
||||
}
|
||||
let out = str.toString()
|
||||
return out && (out[0].toString()).toUpperCase() + out.slice(1)
|
||||
}
|
||||
|
||||
//
|
||||
// ### function parseValues (any)
|
||||
// #### @any {string} String to parse as json or return as is
|
||||
// try to parse `any` as a json stringified
|
||||
//
|
||||
export function parseValues(value) {
|
||||
if (value === 'undefined') {
|
||||
return undefined
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(value)
|
||||
} catch (ignore) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// ### function transform(map, fn)
|
||||
// #### @map {object} Object of key/value pairs to apply `fn` to
|
||||
// #### @fn {function} Transformation function that will be applied to every key/value pair
|
||||
// transform a set of key/value pairs and return the transformed result
|
||||
export function transform(map, fn) {
|
||||
var pairs = Object.keys(map).map(function(key) {
|
||||
var result = fn(key, map[key])
|
||||
|
||||
if (!result) {
|
||||
return null
|
||||
} else if (result.key) {
|
||||
return result
|
||||
}
|
||||
|
||||
throw new Error('Transform function passed to store returned an invalid format: ' + JSON.stringify(result))
|
||||
})
|
||||
|
||||
|
||||
return pairs
|
||||
.filter(function(pair) {
|
||||
return pair !== null
|
||||
})
|
||||
.reduce(function(accumulator, pair) {
|
||||
accumulator[pair.key] = pair.value
|
||||
return accumulator
|
||||
}, {})
|
||||
}
|
40
lib/nconf.js
40
lib/nconf.js
|
@ -1,40 +0,0 @@
|
|||
/*
|
||||
* nconf.js: Top-level include for the nconf module
|
||||
*
|
||||
* (C) 2011, Charlie Robbins and the Contributors.
|
||||
*
|
||||
*/
|
||||
|
||||
var common = require('./nconf/common'),
|
||||
Provider = require('./nconf/provider').Provider;
|
||||
|
||||
//
|
||||
// `nconf` is by default an instance of `nconf.Provider`.
|
||||
//
|
||||
var nconf = module.exports = new Provider();
|
||||
|
||||
//
|
||||
// Expose the version from the package.json
|
||||
//
|
||||
nconf.version = require('../package.json').version;
|
||||
|
||||
//
|
||||
// Setup all stores as lazy-loaded getters.
|
||||
//
|
||||
['env', 'file', 'literal', 'memory'].forEach(function (store) {
|
||||
var name = common.capitalize(store);
|
||||
|
||||
nconf.__defineGetter__(name, function () {
|
||||
return require('./nconf/stores/' + store)[name];
|
||||
});
|
||||
});
|
||||
|
||||
//
|
||||
// Expose the various components included with nconf
|
||||
//
|
||||
nconf.key = common.key;
|
||||
nconf.path = common.path;
|
||||
nconf.loadFiles = common.loadFiles;
|
||||
nconf.loadFilesSync = common.loadFilesSync;
|
||||
nconf.formats = require('./nconf/formats');
|
||||
nconf.Provider = Provider;
|
181
lib/nconf.mjs
Normal file
181
lib/nconf.mjs
Normal file
|
@ -0,0 +1,181 @@
|
|||
import fs from 'fs'
|
||||
import { fileURLToPath } from 'url'
|
||||
import path from 'path'
|
||||
import * as common from './common.mjs'
|
||||
import Literal from './stores/literal.mjs'
|
||||
import Memory from './stores/memory.mjs'
|
||||
import File from './stores/file.mjs'
|
||||
import Env from './stores/env.mjs'
|
||||
import Argv from './stores/argv.mjs'
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const pckg = JSON.parse(fs.readFileSync(path.resolve(path.join(__dirname, '../package.json'))))
|
||||
|
||||
const AvailableStores = [
|
||||
['memory', Memory],
|
||||
['file', File],
|
||||
['defaults', Literal],
|
||||
['overrides', Literal],
|
||||
['literal', Literal],
|
||||
['env', Env],
|
||||
['argv', Argv],
|
||||
]
|
||||
|
||||
function Nconf(options) {
|
||||
let opts = options || {}
|
||||
this.sources = []
|
||||
this.using = new Map()
|
||||
this.version = pckg.version
|
||||
this.init()
|
||||
}
|
||||
|
||||
Nconf.prototype.key = common.key
|
||||
Nconf.prototype.path = common.path
|
||||
|
||||
Nconf.prototype.init = function() {
|
||||
AvailableStores.forEach((storeType) => {
|
||||
let nameCapital = common.capitalize(storeType[0])
|
||||
let nameLower = storeType[0].toLowerCase()
|
||||
|
||||
Object.defineProperty(this, nameCapital, {
|
||||
value: storeType[1],
|
||||
writable: false,
|
||||
enumerable: true,
|
||||
})
|
||||
Object.defineProperty(this, nameLower, {
|
||||
value: function(leName, leOpts) {
|
||||
let name = leName
|
||||
let options = leOpts || {}
|
||||
if (typeof(name) !== 'string') {
|
||||
name = nameLower
|
||||
options = leName || {}
|
||||
}
|
||||
this.add(name, new this[nameCapital](options))
|
||||
return this
|
||||
},
|
||||
writable: false,
|
||||
enumerable: true,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
Nconf.prototype.any = function(...items) {
|
||||
let check = items
|
||||
if (items.length === 1 && Array.isArray(items[0])) {
|
||||
check = items[0]
|
||||
}
|
||||
for (let i = 0; i < check.length; i++) {
|
||||
let found = this.get(check[i])
|
||||
if (found) return found
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
Nconf.prototype.get = function(key) {
|
||||
let out = []
|
||||
for (let i = 0; i < this.sources.length; i++) {
|
||||
let found = this.sources[i].get(key)
|
||||
if (found && !out.length && (Array.isArray(found) || typeof(found) !== 'object')) {
|
||||
return found
|
||||
}
|
||||
if (found) {
|
||||
out.push(found)
|
||||
}
|
||||
}
|
||||
if (!out.length) return undefined
|
||||
return common.merge(out.reverse())
|
||||
}
|
||||
Nconf.prototype.set = function(key, value) {
|
||||
for (let i = 0; i < this.sources.length; i++) {
|
||||
if (!this.sources[i].readOnly) {
|
||||
if (this.sources[i].set(key, value))
|
||||
return this
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
Nconf.prototype.clear = function(key) {
|
||||
for (let i = 0; i < this.sources.length; i++) {
|
||||
this.sources[i].clear(key)
|
||||
}
|
||||
if (this.get(key)) {
|
||||
return false
|
||||
}
|
||||
return this
|
||||
}
|
||||
Nconf.prototype.load = function() {
|
||||
for (let i = 0; i < this.sources.length; i++) {
|
||||
if (typeof(this.sources[i].load) === 'function') {
|
||||
this.sources[i].load()
|
||||
}
|
||||
}
|
||||
}
|
||||
Nconf.prototype.save = function() {
|
||||
for (let i = 0; i < this.sources.length; i++) {
|
||||
if (typeof(this.sources[i].save) === 'function') {
|
||||
this.sources[i].save()
|
||||
}
|
||||
}
|
||||
}
|
||||
Nconf.prototype.reset = function() {
|
||||
throw new Error('Deprecated, create new instance instead')
|
||||
}
|
||||
|
||||
Nconf.prototype.required = function(...items) {
|
||||
let check = items
|
||||
if (items.length === 1 && Array.isArray(items[0])) {
|
||||
check = items[0]
|
||||
}
|
||||
let missing = []
|
||||
for (let i = 0; i < check.length; i++) {
|
||||
if (!this.get(check[i])) {
|
||||
missing.push(check[i])
|
||||
}
|
||||
}
|
||||
|
||||
if (missing.length) {
|
||||
throw new Error('Missing required keys: ' + missing.join(', '));
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
Nconf.prototype.add = function(name, store) {
|
||||
let oldStore = this.using.get(name)
|
||||
|
||||
if (typeof(store.load) === 'function') {
|
||||
store.load()
|
||||
}
|
||||
|
||||
if (oldStore) {
|
||||
this.sources.splice(this.sources.indexOf(oldStore), 1)
|
||||
this.using.delete(name)
|
||||
}
|
||||
this.using.set(name, store)
|
||||
this.sources.push(store)
|
||||
}
|
||||
|
||||
Nconf.prototype.use = function(name) {
|
||||
return this.using.get(name)
|
||||
}
|
||||
|
||||
Nconf.register = function(name, val) {
|
||||
AvailableStores.push([name, val])
|
||||
let nameCapital = common.capitalize(name)
|
||||
Object.defineProperty(Nconf, nameCapital, {
|
||||
value: val,
|
||||
writable: false,
|
||||
enumerable: true,
|
||||
})
|
||||
}
|
||||
|
||||
AvailableStores.forEach((storeType) => {
|
||||
let nameCapital = common.capitalize(storeType[0])
|
||||
Object.defineProperty(Nconf, nameCapital, {
|
||||
value: storeType[1],
|
||||
writable: false,
|
||||
enumerable: true,
|
||||
})
|
||||
})
|
||||
|
||||
export default Nconf
|
|
@ -1,175 +0,0 @@
|
|||
/*
|
||||
* utils.js: Utility functions for the nconf module.
|
||||
*
|
||||
* (C) 2011, Charlie Robbins and the Contributors.
|
||||
*
|
||||
*/
|
||||
|
||||
var fs = require('fs'),
|
||||
async = require('async'),
|
||||
formats = require('./formats'),
|
||||
Memory = require('./stores/memory').Memory;
|
||||
|
||||
var common = exports;
|
||||
|
||||
//
|
||||
// ### function path (key)
|
||||
// #### @key {string} The ':' delimited key to split
|
||||
// Returns a fully-qualified path to a nested nconf key.
|
||||
// If given null or undefined it should return an empty path.
|
||||
// '' should still be respected as a path.
|
||||
//
|
||||
common.path = function (key, separator) {
|
||||
separator = separator || ':';
|
||||
return key == null ? [] : key.split(separator);
|
||||
};
|
||||
|
||||
//
|
||||
// ### function key (arguments)
|
||||
// Returns a `:` joined string from the `arguments`.
|
||||
//
|
||||
common.key = function () {
|
||||
return Array.prototype.slice.call(arguments).join(':');
|
||||
};
|
||||
|
||||
//
|
||||
// ### function key (arguments)
|
||||
// Returns a joined string from the `arguments`,
|
||||
// first argument is the join delimiter.
|
||||
//
|
||||
common.keyed = function () {
|
||||
return Array.prototype.slice.call(arguments, 1).join(arguments[0]);
|
||||
};
|
||||
|
||||
//
|
||||
// ### function loadFiles (files, callback)
|
||||
// #### @files {Object|Array} List of files (or settings object) to load.
|
||||
// #### @callback {function} Continuation to respond to when complete.
|
||||
// Loads all the data in the specified `files`.
|
||||
//
|
||||
common.loadFiles = function (files, callback) {
|
||||
if (!files) {
|
||||
return callback(null, {});
|
||||
}
|
||||
|
||||
var options = Array.isArray(files) ? { files: files } : files;
|
||||
|
||||
//
|
||||
// Set the default JSON format if not already
|
||||
// specified
|
||||
//
|
||||
options.format = options.format || formats.json;
|
||||
|
||||
function parseFile (file, next) {
|
||||
fs.readFile(file, function (err, data) {
|
||||
return !err
|
||||
? next(null, options.format.parse(data.toString()))
|
||||
: next(err);
|
||||
});
|
||||
}
|
||||
|
||||
async.map(options.files, parseFile, function (err, objs) {
|
||||
return err ? callback(err) : callback(null, common.merge(objs));
|
||||
});
|
||||
};
|
||||
|
||||
//
|
||||
// ### function loadFilesSync (files)
|
||||
// #### @files {Object|Array} List of files (or settings object) to load.
|
||||
// Loads all the data in the specified `files` synchronously.
|
||||
//
|
||||
common.loadFilesSync = function (files) {
|
||||
if (!files) {
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Set the default JSON format if not already
|
||||
// specified
|
||||
//
|
||||
var options = Array.isArray(files) ? { files: files } : files;
|
||||
options.format = options.format || formats.json;
|
||||
|
||||
return common.merge(options.files.map(function (file) {
|
||||
return options.format.parse(fs.readFileSync(file, 'utf8'));
|
||||
}));
|
||||
};
|
||||
|
||||
//
|
||||
// ### function merge (objs)
|
||||
// #### @objs {Array} Array of object literals to merge
|
||||
// Merges the specified `objs` using a temporary instance
|
||||
// of `stores.Memory`.
|
||||
//
|
||||
common.merge = function (objs) {
|
||||
var store = new Memory();
|
||||
|
||||
objs.forEach(function (obj) {
|
||||
Object.keys(obj).forEach(function (key) {
|
||||
store.merge(key, obj[key]);
|
||||
});
|
||||
});
|
||||
|
||||
return store.store;
|
||||
};
|
||||
|
||||
//
|
||||
// ### function capitalize (str)
|
||||
// #### @str {string} String to capitalize
|
||||
// Capitalizes the specified `str`.
|
||||
//
|
||||
common.capitalize = function (str) {
|
||||
return str && str[0].toUpperCase() + str.slice(1);
|
||||
};
|
||||
|
||||
//
|
||||
// ### function parseValues (any)
|
||||
// #### @any {string} String to parse as native data-type or return as is
|
||||
// try to parse `any` as a native data-type
|
||||
//
|
||||
common.parseValues = function (value) {
|
||||
var val = value;
|
||||
|
||||
try {
|
||||
val = JSON.parse(value);
|
||||
} catch (ignore) {
|
||||
// Check for any other well-known strings that should be "parsed"
|
||||
if (value === 'undefined'){
|
||||
val = void 0;
|
||||
}
|
||||
}
|
||||
|
||||
return val;
|
||||
};
|
||||
|
||||
//
|
||||
// ### function transform(map, fn)
|
||||
// #### @map {object} Object of key/value pairs to apply `fn` to
|
||||
// #### @fn {function} Transformation function that will be applied to every key/value pair
|
||||
// transform a set of key/value pairs and return the transformed result
|
||||
common.transform = function(map, fn) {
|
||||
var pairs = Object.keys(map).map(function(key) {
|
||||
var obj = { key: key, value: map[key]};
|
||||
var result = fn.call(null, obj);
|
||||
|
||||
if (!result) {
|
||||
return null;
|
||||
} else if (result.key) {
|
||||
return result;
|
||||
}
|
||||
|
||||
var error = new Error('Transform function passed to store returned an invalid format: ' + JSON.stringify(result));
|
||||
error.name = 'RuntimeError';
|
||||
throw error;
|
||||
});
|
||||
|
||||
|
||||
return pairs
|
||||
.filter(function(pair) {
|
||||
return pair !== null;
|
||||
})
|
||||
.reduce(function(accumulator, pair) {
|
||||
accumulator[pair.key] = pair.value;
|
||||
return accumulator;
|
||||
}, {});
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
/*
|
||||
* formats.js: Default formats supported by nconf
|
||||
*
|
||||
* (C) 2011, Charlie Robbins and the Contributors.
|
||||
*
|
||||
*/
|
||||
|
||||
var ini = require('ini');
|
||||
|
||||
var formats = exports;
|
||||
|
||||
//
|
||||
// ### @json
|
||||
// Standard JSON format which pretty prints `.stringify()`.
|
||||
//
|
||||
formats.json = {
|
||||
stringify: function (obj, replacer, spacing) {
|
||||
return JSON.stringify(obj, replacer || null, spacing || 2)
|
||||
},
|
||||
parse: JSON.parse
|
||||
};
|
||||
|
||||
//
|
||||
// ### @ini
|
||||
// Standard INI format supplied from the `ini` module
|
||||
// http://en.wikipedia.org/wiki/INI_file
|
||||
//
|
||||
formats.ini = ini;
|
|
@ -1,655 +0,0 @@
|
|||
/*
|
||||
* provider.js: Abstraction providing an interface into pluggable configuration storage.
|
||||
*
|
||||
* (C) 2011, Charlie Robbins and the Contributors.
|
||||
*
|
||||
*/
|
||||
|
||||
var async = require('async'),
|
||||
common = require('./common');
|
||||
|
||||
//
|
||||
// ### function Provider (options)
|
||||
// #### @options {Object} Options for this instance.
|
||||
// Constructor function for the Provider object responsible
|
||||
// for exposing the pluggable storage features of `nconf`.
|
||||
//
|
||||
var Provider = exports.Provider = function (options) {
|
||||
//
|
||||
// Setup default options for working with `stores`,
|
||||
// `overrides`, `process.env`.
|
||||
//
|
||||
options = options || {};
|
||||
this.stores = {};
|
||||
this.sources = [];
|
||||
this.init(options);
|
||||
};
|
||||
|
||||
//
|
||||
// Define wrapper functions for using basic stores
|
||||
// in this instance
|
||||
//
|
||||
|
||||
['env'].forEach(function (type) {
|
||||
Provider.prototype[type] = function () {
|
||||
var args = [type].concat(Array.prototype.slice.call(arguments));
|
||||
return this.add.apply(this, args);
|
||||
};
|
||||
});
|
||||
|
||||
//
|
||||
// ### function file (key, options)
|
||||
// #### @key {string|Object} Fully qualified options, name of file store, or path.
|
||||
// #### @path {string|Object} **Optional** Full qualified options, or path.
|
||||
// Adds a new `File` store to this instance. Accepts the following options
|
||||
//
|
||||
// nconf.file({ file: '.jitsuconf', dir: process.env.HOME, search: true });
|
||||
// nconf.file('path/to/config/file');
|
||||
// nconf.file('userconfig', 'path/to/config/file');
|
||||
// nconf.file('userconfig', { file: '.jitsuconf', search: true });
|
||||
//
|
||||
Provider.prototype.file = function (key, options) {
|
||||
if (arguments.length == 1) {
|
||||
options = typeof key === 'string' ? { file: key } : key;
|
||||
key = 'file';
|
||||
}
|
||||
else {
|
||||
options = typeof options === 'string'
|
||||
? { file: options }
|
||||
: options;
|
||||
}
|
||||
|
||||
options.type = 'file';
|
||||
return this.add(key, options);
|
||||
};
|
||||
|
||||
//
|
||||
// Define wrapper functions for using
|
||||
// overrides and defaults
|
||||
//
|
||||
['defaults', 'overrides'].forEach(function (type) {
|
||||
Provider.prototype[type] = function (options) {
|
||||
options = options || {};
|
||||
if (!options.type) {
|
||||
options.type = 'literal';
|
||||
}
|
||||
|
||||
return this.add(type, options);
|
||||
};
|
||||
});
|
||||
|
||||
//
|
||||
// ### function use (name, options)
|
||||
// #### @type {string} Type of the nconf store to use.
|
||||
// #### @options {Object} Options for the store instance.
|
||||
// Adds (or replaces) a new store with the specified `name`
|
||||
// and `options`. If `options.type` is not set, then `name`
|
||||
// will be used instead:
|
||||
//
|
||||
// provider.use('file');
|
||||
// provider.use('file', { type: 'file', filename: '/path/to/userconf' })
|
||||
//
|
||||
Provider.prototype.use = function (name, options) {
|
||||
options = options || {};
|
||||
|
||||
function sameOptions (store) {
|
||||
return Object.keys(options).every(function (key) {
|
||||
return options[key] === store[key];
|
||||
});
|
||||
}
|
||||
|
||||
var store = this.stores[name],
|
||||
update = store && !sameOptions(store);
|
||||
|
||||
if (!store || update) {
|
||||
if (update) {
|
||||
this.remove(name);
|
||||
}
|
||||
|
||||
this.add(name, options);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
//
|
||||
// ### function add (name, options)
|
||||
// #### @name {string} Name of the store to add to this instance
|
||||
// #### @options {Object} Options for the store to create
|
||||
// Adds a new store with the specified `name` and `options`. If `options.type`
|
||||
// is not set, then `name` will be used instead:
|
||||
//
|
||||
// provider.add('memory');
|
||||
// provider.add('userconf', { type: 'file', filename: '/path/to/userconf' })
|
||||
//
|
||||
Provider.prototype.add = function (name, options, usage) {
|
||||
options = options || {};
|
||||
var type = options.type || name;
|
||||
|
||||
if (!require('../nconf')[common.capitalize(type)]) {
|
||||
throw new Error('Cannot add store with unknown type: ' + type);
|
||||
}
|
||||
|
||||
this.stores[name] = this.create(type, options, usage);
|
||||
|
||||
if (this.stores[name].loadSync) {
|
||||
this.stores[name].loadSync();
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
//
|
||||
// ### function remove (name)
|
||||
// #### @name {string} Name of the store to remove from this instance
|
||||
// Removes a store with the specified `name` from this instance. Users
|
||||
// are allowed to pass in a type argument (e.g. `memory`) as name if
|
||||
// this was used in the call to `.add()`.
|
||||
//
|
||||
Provider.prototype.remove = function (name) {
|
||||
delete this.stores[name];
|
||||
return this;
|
||||
};
|
||||
|
||||
//
|
||||
// ### function create (type, options)
|
||||
// #### @type {string} Type of the nconf store to use.
|
||||
// #### @options {Object} Options for the store instance.
|
||||
// Creates a store of the specified `type` using the
|
||||
// specified `options`.
|
||||
//
|
||||
Provider.prototype.create = function (type, options, usage) {
|
||||
return new (require('../nconf')[common.capitalize(type.toLowerCase())])(options, usage);
|
||||
};
|
||||
|
||||
//
|
||||
// ### function init (options)
|
||||
// #### @options {Object} Options to initialize this instance with.
|
||||
// Initializes this instance with additional `stores` or `sources` in the
|
||||
// `options` supplied.
|
||||
//
|
||||
Provider.prototype.init = function (options) {
|
||||
var self = this;
|
||||
|
||||
//
|
||||
// Add any stores passed in through the options
|
||||
// to this instance.
|
||||
//
|
||||
if (options.type) {
|
||||
this.add(options.type, options);
|
||||
}
|
||||
else if (options.store) {
|
||||
this.add(options.store.name || options.store.type, options.store);
|
||||
}
|
||||
else if (options.stores) {
|
||||
Object.keys(options.stores).forEach(function (name) {
|
||||
var store = options.stores[name];
|
||||
self.add(store.name || name || store.type, store);
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
// Add any read-only sources to this instance
|
||||
//
|
||||
if (options.source) {
|
||||
this.sources.push(this.create(options.source.type || options.source.name, options.source));
|
||||
}
|
||||
else if (options.sources) {
|
||||
Object.keys(options.sources).forEach(function (name) {
|
||||
var source = options.sources[name];
|
||||
self.sources.push(self.create(source.type || source.name || name, source));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// ### function get (key, callback)
|
||||
// #### @key {string} Key to retrieve for this instance.
|
||||
// #### @callback {function} **Optional** Continuation to respond to when complete.
|
||||
// Retrieves the value for the specified key (if any).
|
||||
//
|
||||
Provider.prototype.get = function (key, callback) {
|
||||
if (typeof key === 'function') {
|
||||
// Allow a * key call to be made
|
||||
callback = key;
|
||||
key = null;
|
||||
}
|
||||
|
||||
//
|
||||
// If there is no callback we can short-circuit into the default
|
||||
// logic for traversing stores.
|
||||
//
|
||||
if (!callback) {
|
||||
return this._execute('get', 1, key, callback);
|
||||
}
|
||||
|
||||
//
|
||||
// Otherwise the asynchronous, hierarchical `get` is
|
||||
// slightly more complicated because we do not need to traverse
|
||||
// the entire set of stores, but up until there is a defined value.
|
||||
//
|
||||
var current = 0,
|
||||
names = Object.keys(this.stores).filter(x => x !== 'mock'),
|
||||
self = this,
|
||||
response,
|
||||
mergeObjs = [];
|
||||
|
||||
async.whilst(function () {
|
||||
return typeof response === 'undefined' && current < names.length;
|
||||
}, function (next) {
|
||||
var store = self.stores[names[current]];
|
||||
current++;
|
||||
|
||||
if (store.get.length >= 2) {
|
||||
return store.get(key, function (err, value) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
response = value;
|
||||
|
||||
// Merge objects if necessary
|
||||
if (response && typeof response === 'object' && !Array.isArray(response)) {
|
||||
mergeObjs.push(response);
|
||||
response = undefined;
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
response = store.get(key);
|
||||
|
||||
// Merge objects if necessary
|
||||
if (response && typeof response === 'object' && !Array.isArray(response)) {
|
||||
mergeObjs.push(response);
|
||||
response = undefined;
|
||||
}
|
||||
|
||||
next();
|
||||
}, function (err) {
|
||||
if (!err && mergeObjs.length) {
|
||||
response = common.merge(mergeObjs.reverse());
|
||||
}
|
||||
return err ? callback(err) : callback(null, response);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// ### function any (keys, callback)
|
||||
// #### @keys {array|string...} Array of keys to query, or a variable list of strings
|
||||
// #### @callback {function} **Optional** Continuation to respond to when complete.
|
||||
// Retrieves the first truthy value (if any) for the specified list of keys.
|
||||
//
|
||||
Provider.prototype.any = function (keys, callback) {
|
||||
|
||||
if (!Array.isArray(keys)) {
|
||||
keys = Array.prototype.slice.call(arguments);
|
||||
if (keys.length > 0 && typeof keys[keys.length - 1] === 'function') {
|
||||
callback = keys.pop();
|
||||
} else {
|
||||
callback = null;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// If there is no callback, use the short-circuited "get"
|
||||
// on each key in turn.
|
||||
//
|
||||
if (!callback) {
|
||||
var val;
|
||||
for (var i = 0; i < keys.length; ++i) {
|
||||
val = this._execute('get', 1, keys[i], callback);
|
||||
if (val) {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
var keyIndex = 0,
|
||||
result,
|
||||
self = this;
|
||||
|
||||
async.whilst(function() {
|
||||
return !result && keyIndex < keys.length;
|
||||
}, function(next) {
|
||||
var key = keys[keyIndex];
|
||||
keyIndex++;
|
||||
|
||||
self.get(key, function(err, v) {
|
||||
if (err) {
|
||||
next(err);
|
||||
} else {
|
||||
result = v;
|
||||
next();
|
||||
}
|
||||
});
|
||||
}, function(err) {
|
||||
return err ? callback(err) : callback(null, result);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// ### function set (key, value, callback)
|
||||
// #### @key {string} Key to set in this instance
|
||||
// #### @value {literal|Object} Value for the specified key
|
||||
// #### @callback {function} **Optional** Continuation to respond to when complete.
|
||||
// Sets the `value` for the specified `key` in this instance.
|
||||
//
|
||||
Provider.prototype.set = function (key, value, callback) {
|
||||
return this._execute('set', 2, key, value, callback);
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// ### function required (keys)
|
||||
// #### @keys {array} List of keys
|
||||
// Throws an error if any of `keys` has no value, otherwise returns `true`
|
||||
Provider.prototype.required = function (keys) {
|
||||
if (!Array.isArray(keys)) {
|
||||
throw new Error('Incorrect parameter, array expected');
|
||||
}
|
||||
|
||||
var missing = [];
|
||||
keys.forEach(function(key) {
|
||||
if (typeof this.get(key) === 'undefined') {
|
||||
missing.push(key);
|
||||
}
|
||||
}, this);
|
||||
|
||||
if (missing.length) {
|
||||
throw new Error('Missing required keys: ' + missing.join(', '));
|
||||
} else {
|
||||
return this;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
//
|
||||
// ### function reset (callback)
|
||||
// #### @callback {function} **Optional** Continuation to respond to when complete.
|
||||
// Clears all keys associated with this instance.
|
||||
//
|
||||
Provider.prototype.reset = function (callback) {
|
||||
return this._execute('reset', 0, callback);
|
||||
};
|
||||
|
||||
//
|
||||
// ### function clear (key, callback)
|
||||
// #### @key {string} Key to remove from this instance
|
||||
// #### @callback {function} **Optional** Continuation to respond to when complete.
|
||||
// Removes the value for the specified `key` from this instance.
|
||||
//
|
||||
Provider.prototype.clear = function (key, callback) {
|
||||
return this._execute('clear', 1, key, callback);
|
||||
};
|
||||
|
||||
//
|
||||
// ### function merge ([key,] value [, callback])
|
||||
// #### @key {string} Key to merge the value into
|
||||
// #### @value {literal|Object} Value to merge into the key
|
||||
// #### @callback {function} **Optional** Continuation to respond to when complete.
|
||||
// Merges the properties in `value` into the existing object value at `key`.
|
||||
//
|
||||
// 1. If the existing value `key` is not an Object, it will be completely overwritten.
|
||||
// 2. If `key` is not supplied, then the `value` will be merged into the root.
|
||||
//
|
||||
Provider.prototype.merge = function () {
|
||||
var self = this,
|
||||
args = Array.prototype.slice.call(arguments),
|
||||
callback = typeof args[args.length - 1] === 'function' && args.pop(),
|
||||
value = args.pop(),
|
||||
key = args.pop();
|
||||
|
||||
function mergeProperty (prop, next) {
|
||||
return self._execute('merge', 2, prop, value[prop], next);
|
||||
}
|
||||
|
||||
if (!key) {
|
||||
if (Array.isArray(value) || typeof value !== 'object') {
|
||||
return onError(new Error('Cannot merge non-Object into top-level.'), callback);
|
||||
}
|
||||
|
||||
return async.forEach(Object.keys(value), mergeProperty, callback || function () { })
|
||||
}
|
||||
|
||||
return this._execute('merge', 2, key, value, callback);
|
||||
};
|
||||
|
||||
//
|
||||
// ### function load (callback)
|
||||
// #### @callback {function} Continuation to respond to when complete.
|
||||
// Responds with an Object representing all keys associated in this instance.
|
||||
//
|
||||
Provider.prototype.load = function (callback) {
|
||||
var self = this;
|
||||
|
||||
function getStores () {
|
||||
var stores = Object.keys(self.stores);
|
||||
stores.reverse();
|
||||
return stores.map(function (name) {
|
||||
return self.stores[name];
|
||||
});
|
||||
}
|
||||
|
||||
function loadStoreSync(store) {
|
||||
if (!store.loadSync) {
|
||||
throw new Error('nconf store "' + store.type + '" has no loadSync() method');
|
||||
}
|
||||
|
||||
return store.loadSync();
|
||||
}
|
||||
|
||||
function loadStore(store, next) {
|
||||
if (!store.load && !store.loadSync) {
|
||||
return next(new Error('nconf store ' + store.type + ' has no load() method'));
|
||||
}
|
||||
|
||||
return store.loadSync
|
||||
? next(null, store.loadSync())
|
||||
: store.load(next);
|
||||
}
|
||||
|
||||
function loadBatch (targets, done) {
|
||||
if (!done) {
|
||||
return common.merge(targets.map(loadStoreSync));
|
||||
}
|
||||
|
||||
async.map(targets, loadStore, function (err, objs) {
|
||||
return err ? done(err) : done(null, common.merge(objs));
|
||||
});
|
||||
}
|
||||
|
||||
function mergeSources (data) {
|
||||
//
|
||||
// If `data` was returned then merge it into
|
||||
// the system store.
|
||||
//
|
||||
if (data && typeof data === 'object') {
|
||||
self.use('sources', {
|
||||
type: 'literal',
|
||||
store: data
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function loadSources () {
|
||||
var sourceHierarchy = self.sources.splice(0);
|
||||
sourceHierarchy.reverse();
|
||||
|
||||
//
|
||||
// If we don't have a callback and the current
|
||||
// store is capable of loading synchronously
|
||||
// then do so.
|
||||
//
|
||||
if (!callback) {
|
||||
mergeSources(loadBatch(sourceHierarchy));
|
||||
return loadBatch(getStores());
|
||||
}
|
||||
|
||||
loadBatch(sourceHierarchy, function (err, data) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
mergeSources(data);
|
||||
return loadBatch(getStores(), callback);
|
||||
});
|
||||
}
|
||||
|
||||
return self.sources.length
|
||||
? loadSources()
|
||||
: loadBatch(getStores(), callback);
|
||||
};
|
||||
|
||||
//
|
||||
// ### function save (callback)
|
||||
// #### @callback {function} **optional** Continuation to respond to when
|
||||
// complete.
|
||||
// Instructs each provider to save. If a callback is provided, we will attempt
|
||||
// asynchronous saves on the providers, falling back to synchronous saves if
|
||||
// this isn't possible. If a provider does not know how to save, it will be
|
||||
// ignored. Returns an object consisting of all of the data which was
|
||||
// actually saved.
|
||||
//
|
||||
Provider.prototype.save = function (value, callback) {
|
||||
if (!callback && typeof value === 'function') {
|
||||
callback = value;
|
||||
value = null;
|
||||
}
|
||||
|
||||
var self = this,
|
||||
names = Object.keys(this.stores);
|
||||
|
||||
function saveStoreSync(memo, name) {
|
||||
var store = self.stores[name];
|
||||
|
||||
//
|
||||
// If the `store` doesn't have a `saveSync` method,
|
||||
// just ignore it and continue.
|
||||
//
|
||||
if (store.saveSync) {
|
||||
var ret = store.saveSync();
|
||||
if (typeof ret == 'object' && ret !== null) {
|
||||
memo.push(ret);
|
||||
}
|
||||
}
|
||||
return memo;
|
||||
}
|
||||
|
||||
function saveStore(memo, name, next) {
|
||||
var store = self.stores[name];
|
||||
|
||||
//
|
||||
// If the `store` doesn't have a `save` or saveSync`
|
||||
// method(s), just ignore it and continue.
|
||||
//
|
||||
|
||||
if (store.save) {
|
||||
return store.save(value, function (err, data) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
if (typeof data == 'object' && data !== null) {
|
||||
memo.push(data);
|
||||
}
|
||||
|
||||
next(null, memo);
|
||||
});
|
||||
}
|
||||
else if (store.saveSync) {
|
||||
memo.push(store.saveSync());
|
||||
}
|
||||
|
||||
next(null, memo);
|
||||
}
|
||||
|
||||
//
|
||||
// If we don't have a callback and the current
|
||||
// store is capable of saving synchronously
|
||||
// then do so.
|
||||
//
|
||||
if (!callback) {
|
||||
return common.merge(names.reduce(saveStoreSync, []));
|
||||
}
|
||||
|
||||
async.reduce(names, [], saveStore, function (err, objs) {
|
||||
return err ? callback(err) : callback(null, common.merge(objs));
|
||||
});
|
||||
};
|
||||
|
||||
//
|
||||
// ### @private function _execute (action, syncLength, [arguments])
|
||||
// #### @action {string} Action to execute on `this.store`.
|
||||
// #### @syncLength {number} Function length of the sync version.
|
||||
// #### @arguments {Array} Arguments array to apply to the action
|
||||
// Executes the specified `action` on all stores for this instance, ensuring a callback supplied
|
||||
// to a synchronous store function is still invoked.
|
||||
//
|
||||
Provider.prototype._execute = function (action, syncLength /* [arguments] */) {
|
||||
var args = Array.prototype.slice.call(arguments, 2),
|
||||
callback = typeof args[args.length - 1] === 'function' && args.pop(),
|
||||
destructive = ['set', 'clear', 'merge', 'reset'].indexOf(action) !== -1,
|
||||
self = this,
|
||||
response,
|
||||
mergeObjs = [],
|
||||
keys = Object.keys(this.stores).filter(x => x !== 'mock');
|
||||
|
||||
|
||||
function runAction (name, next) {
|
||||
var store = self.stores[name];
|
||||
|
||||
if (destructive && store.readOnly) {
|
||||
return next();
|
||||
}
|
||||
|
||||
return store[action].length > syncLength
|
||||
? store[action].apply(store, args.concat(next))
|
||||
: next(null, store[action].apply(store, args));
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
return async.forEach(keys, runAction, function (err) {
|
||||
return err ? callback(err) : callback();
|
||||
});
|
||||
}
|
||||
|
||||
keys.forEach(function (name) {
|
||||
if (typeof response === 'undefined') {
|
||||
var store = self.stores[name];
|
||||
|
||||
if (destructive && store.readOnly) {
|
||||
return;
|
||||
}
|
||||
|
||||
response = store[action].apply(store, args);
|
||||
|
||||
// Merge objects if necessary
|
||||
if (response && action === 'get' && typeof response === 'object' && !Array.isArray(response)) {
|
||||
mergeObjs.push(response);
|
||||
response = undefined;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (mergeObjs.length) {
|
||||
response = common.merge(mergeObjs.reverse());
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
//
|
||||
// Throw the `err` if a callback is not supplied
|
||||
//
|
||||
function onError(err, callback) {
|
||||
if (callback) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
|
@ -1,113 +0,0 @@
|
|||
/*
|
||||
* env.js: Simple memory-based store for environment variables
|
||||
*
|
||||
* (C) 2011, Charlie Robbins and the Contributors.
|
||||
*
|
||||
*/
|
||||
|
||||
var util = require('util'),
|
||||
common = require('../common'),
|
||||
Memory = require('./memory').Memory;
|
||||
|
||||
//
|
||||
// ### function Env (options)
|
||||
// #### @options {Object} Options for this instance.
|
||||
// Constructor function for the Env nconf store, a simple abstraction
|
||||
// around the Memory store that can read process environment variables.
|
||||
//
|
||||
var Env = exports.Env = function (options) {
|
||||
Memory.call(this, options);
|
||||
|
||||
options = options || {};
|
||||
this.type = 'env';
|
||||
this.readOnly = options.readOnly !== undefined? options.readOnly : true;
|
||||
this.whitelist = options.whitelist || [];
|
||||
this.separator = options.separator || '';
|
||||
this.lowerCase = options.lowerCase || false;
|
||||
this.parseValues = options.parseValues || false;
|
||||
this.transform = options.transform || false;
|
||||
|
||||
if (({}).toString.call(options.match) === '[object RegExp]'
|
||||
&& typeof options !== 'string') {
|
||||
this.match = options.match;
|
||||
}
|
||||
|
||||
if (options instanceof Array) {
|
||||
this.whitelist = options;
|
||||
}
|
||||
if (typeof(options) === 'string' || options instanceof RegExp) {
|
||||
this.separator = options;
|
||||
}
|
||||
};
|
||||
|
||||
// Inherit from the Memory store
|
||||
util.inherits(Env, Memory);
|
||||
|
||||
//
|
||||
// ### function loadSync ()
|
||||
// Loads the data passed in from `process.env` into this instance.
|
||||
//
|
||||
Env.prototype.loadSync = function () {
|
||||
this.loadEnv();
|
||||
return this.store;
|
||||
};
|
||||
|
||||
//
|
||||
// ### function loadEnv ()
|
||||
// Loads the data passed in from `process.env` into this instance.
|
||||
//
|
||||
Env.prototype.loadEnv = function () {
|
||||
var self = this;
|
||||
|
||||
var env = process.env;
|
||||
|
||||
if (this.lowerCase) {
|
||||
env = {};
|
||||
Object.keys(process.env).forEach(function (key) {
|
||||
env[key.toLowerCase()] = process.env[key];
|
||||
});
|
||||
}
|
||||
|
||||
if (this.transform) {
|
||||
env = common.transform(env, this.transform);
|
||||
}
|
||||
|
||||
var tempWrite = false;
|
||||
|
||||
if(this.readOnly) {
|
||||
this.readOnly = false;
|
||||
tempWrite = true;
|
||||
}
|
||||
|
||||
Object.keys(env).filter(function (key) {
|
||||
if (self.match && self.whitelist.length) {
|
||||
return key.match(self.match) || self.whitelist.indexOf(key) !== -1
|
||||
}
|
||||
else if (self.match) {
|
||||
return key.match(self.match);
|
||||
}
|
||||
else {
|
||||
return !self.whitelist.length || self.whitelist.indexOf(key) !== -1
|
||||
}
|
||||
}).forEach(function (key) {
|
||||
|
||||
var val = env[key];
|
||||
|
||||
if (self.parseValues) {
|
||||
val = common.parseValues(val);
|
||||
}
|
||||
|
||||
if (self.separator) {
|
||||
self.set(common.key.apply(common, key.split(self.separator)), val);
|
||||
}
|
||||
else {
|
||||
self.set(key, val);
|
||||
}
|
||||
});
|
||||
|
||||
if (tempWrite) {
|
||||
this.readOnly = true;
|
||||
}
|
||||
|
||||
return this.store;
|
||||
};
|
|
@ -1,323 +0,0 @@
|
|||
/*
|
||||
* file.js: Simple file storage engine for nconf files
|
||||
*
|
||||
* (C) 2011, Charlie Robbins and the Contributors.
|
||||
*
|
||||
*/
|
||||
|
||||
var fs = require('fs'),
|
||||
path = require('path'),
|
||||
util = require('util'),
|
||||
crypto = require('crypto'),
|
||||
formats = require('../formats'),
|
||||
Memory = require('./memory').Memory;
|
||||
|
||||
var exists = fs.exists || path.exists,
|
||||
existsSync = fs.existsSync || path.existsSync;
|
||||
|
||||
//
|
||||
// ### function File (options)
|
||||
// #### @options {Object} Options for this instance
|
||||
// Constructor function for the File nconf store, a simple abstraction
|
||||
// around the Memory store that can persist configuration to disk.
|
||||
//
|
||||
var File = exports.File = function (options) {
|
||||
if (!options || !options.file) {
|
||||
throw new Error('Missing required option `file`');
|
||||
}
|
||||
|
||||
Memory.call(this, options);
|
||||
|
||||
this.type = 'file';
|
||||
this.file = options.file;
|
||||
this.dir = options.dir || process.cwd();
|
||||
this.format = options.format || formats.json;
|
||||
this.secure = options.secure;
|
||||
this.spacing = options.json_spacing
|
||||
|| options.spacing
|
||||
|| 2;
|
||||
|
||||
if (this.secure) {
|
||||
this.secure = Buffer.isBuffer(this.secure) || typeof this.secure === 'string'
|
||||
? { secret: this.secure.toString() }
|
||||
: this.secure;
|
||||
|
||||
this.secure.alg = this.secure.alg || 'aes-256-ctr';
|
||||
if (this.secure.secretPath) {
|
||||
this.secure.secret = fs.readFileSync(this.secure.secretPath, 'utf8');
|
||||
}
|
||||
|
||||
if (!this.secure.secret) {
|
||||
throw new Error('secure.secret option is required');
|
||||
}
|
||||
}
|
||||
|
||||
if (options.search) {
|
||||
this.search(this.dir);
|
||||
}
|
||||
};
|
||||
|
||||
// Inherit from the Memory store
|
||||
util.inherits(File, Memory);
|
||||
|
||||
//
|
||||
// ### function save (value, callback)
|
||||
// #### @value {Object} _Ignored_ Left here for consistency
|
||||
// #### @callback {function} Continuation to respond to when complete.
|
||||
// Saves the current configuration object to disk at `this.file`
|
||||
// using the format specified by `this.format`.
|
||||
//
|
||||
File.prototype.save = function (value, callback) {
|
||||
this.saveToFile(this.file, value, callback);
|
||||
};
|
||||
|
||||
//
|
||||
// ### function saveToFile (path, value, callback)
|
||||
// #### @path {string} The path to the file where we save the configuration to
|
||||
// #### @format {Object} Optional formatter, default behing the one of the store
|
||||
// #### @callback {function} Continuation to respond to when complete.
|
||||
// Saves the current configuration object to disk at `this.file`
|
||||
// using the format specified by `this.format`.
|
||||
//
|
||||
File.prototype.saveToFile = function (path, format, callback) {
|
||||
if (!callback) {
|
||||
callback = format;
|
||||
format = this.format;
|
||||
}
|
||||
|
||||
fs.writeFile(path, this.stringify(format), callback);
|
||||
};
|
||||
|
||||
//
|
||||
// ### function saveSync (value, callback)
|
||||
// Saves the current configuration object to disk at `this.file`
|
||||
// using the format specified by `this.format` synchronously.
|
||||
//
|
||||
File.prototype.saveSync = function () {
|
||||
fs.writeFileSync(this.file, this.stringify());
|
||||
return this.store;
|
||||
};
|
||||
|
||||
//
|
||||
// ### function load (callback)
|
||||
// #### @callback {function} Continuation to respond to when complete.
|
||||
// Responds with an Object representing all keys associated in this instance.
|
||||
//
|
||||
File.prototype.load = function (callback) {
|
||||
var self = this;
|
||||
|
||||
exists(self.file, function (exists) {
|
||||
if (!exists) {
|
||||
return callback(null, {});
|
||||
}
|
||||
|
||||
//
|
||||
// Else, the path exists, read it from disk
|
||||
//
|
||||
fs.readFile(self.file, function (err, data) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
try {
|
||||
// Deals with string that include BOM
|
||||
var stringData = data.toString();
|
||||
if (stringData.charAt(0) === '\uFEFF') {
|
||||
stringData = stringData.substr(1);
|
||||
}
|
||||
|
||||
self.store = self.parse(stringData);
|
||||
}
|
||||
catch (ex) {
|
||||
return callback(new Error("Error parsing your configuration file: [" + self.file + ']: ' + ex.message));
|
||||
}
|
||||
|
||||
callback(null, self.store);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
//
|
||||
// ### function loadSync (callback)
|
||||
// Attempts to load the data stored in `this.file` synchronously
|
||||
// and responds appropriately.
|
||||
//
|
||||
File.prototype.loadSync = function () {
|
||||
if (!existsSync(this.file)) {
|
||||
this.store = {};
|
||||
return this.store;
|
||||
}
|
||||
|
||||
//
|
||||
// Else, the path exists, read it from disk
|
||||
//
|
||||
try {
|
||||
// Deals with file that include BOM
|
||||
var fileData = fs.readFileSync(this.file, 'utf8');
|
||||
if (fileData.charAt(0) === '\uFEFF') {
|
||||
fileData = fileData.substr(1);
|
||||
}
|
||||
|
||||
this.store = this.parse(fileData);
|
||||
}
|
||||
catch (ex) {
|
||||
throw new Error("Error parsing your configuration file: [" + this.file + ']: ' + ex.message);
|
||||
}
|
||||
|
||||
return this.store;
|
||||
};
|
||||
|
||||
//
|
||||
// ### function stringify ()
|
||||
// Returns an encrypted version of the contents IIF
|
||||
// `this.secure` is enabled
|
||||
//
|
||||
File.prototype.stringify = function (format) {
|
||||
var data = this.store;
|
||||
if (!format) {
|
||||
format = this.format
|
||||
}
|
||||
|
||||
if (this.secure) {
|
||||
var self = this;
|
||||
data = Object.keys(data).reduce(function (acc, key) {
|
||||
var value = format.stringify(data[key]);
|
||||
var iv = crypto.randomBytes(16);
|
||||
var cipher = crypto.createCipheriv(self.secure.alg, self.secure.secret, iv);
|
||||
var ciphertext = cipher.update(value, 'utf8', 'hex');
|
||||
ciphertext += cipher.final('hex');
|
||||
acc[key] = { alg: self.secure.alg, value: ciphertext, iv: iv.toString('hex') };
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
return format.stringify(data, null, this.spacing);
|
||||
};
|
||||
|
||||
//
|
||||
// ### function parse (contents)
|
||||
// Returns a decrypted version of the contents IFF
|
||||
// `this.secure` is enabled.
|
||||
//
|
||||
File.prototype.parse = function (contents) {
|
||||
var parsed = this.format.parse(contents);
|
||||
|
||||
if (this.secure) {
|
||||
var self = this;
|
||||
var outdated = false;
|
||||
parsed = Object.keys(parsed).reduce(function (acc, key) {
|
||||
var value = parsed[key];
|
||||
var decipher = crypto.createDecipher(value.alg, self.secure.secret);
|
||||
if (value.iv) {
|
||||
// For backward compatibility, use createDecipheriv only if there is iv stored in file
|
||||
decipher = crypto.createDecipheriv(value.alg, self.secure.secret, Buffer.from(value.iv, 'hex'));
|
||||
} else {
|
||||
outdated = true;
|
||||
}
|
||||
var plaintext = decipher.update(value.value, 'hex', 'utf8');
|
||||
plaintext += decipher.final('utf8');
|
||||
acc[key] = self.format.parse(plaintext);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
if (outdated) {
|
||||
// warn user if the file is encrypted without iv
|
||||
console.warn('Your encrypted file is outdated (encrypted without iv). Please re-encrypt your file.');
|
||||
}
|
||||
}
|
||||
|
||||
return parsed;
|
||||
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// ### function search (base)
|
||||
// #### @base {string} Base directory (or file) to begin searching for the target file.
|
||||
// Attempts to find `this.file` by iteratively searching up the
|
||||
// directory structure
|
||||
//
|
||||
File.prototype.search = function (base) {
|
||||
var looking = true,
|
||||
fullpath,
|
||||
previous,
|
||||
stats;
|
||||
|
||||
base = base || process.cwd();
|
||||
|
||||
if (this.file[0] === '/') {
|
||||
//
|
||||
// If filename for this instance is a fully qualified path
|
||||
// (i.e. it starts with a `'/'`) then check if it exists
|
||||
//
|
||||
try {
|
||||
stats = fs.statSync(fs.realpathSync(this.file));
|
||||
if (stats.isFile()) {
|
||||
fullpath = this.file;
|
||||
looking = false;
|
||||
}
|
||||
}
|
||||
catch (ex) {
|
||||
//
|
||||
// Ignore errors
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
if (looking && base) {
|
||||
//
|
||||
// Attempt to stat the realpath located at `base`
|
||||
// if the directory does not exist then return false.
|
||||
//
|
||||
try {
|
||||
var stat = fs.statSync(fs.realpathSync(base));
|
||||
looking = stat.isDirectory();
|
||||
}
|
||||
catch (ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
while (looking) {
|
||||
//
|
||||
// Iteratively look up the directory structure from `base`
|
||||
//
|
||||
try {
|
||||
stats = fs.statSync(fs.realpathSync(fullpath = path.join(base, this.file)));
|
||||
looking = stats.isDirectory();
|
||||
}
|
||||
catch (ex) {
|
||||
previous = base;
|
||||
base = path.dirname(base);
|
||||
|
||||
if (previous === base) {
|
||||
//
|
||||
// If we've reached the top of the directory structure then simply use
|
||||
// the default file path.
|
||||
//
|
||||
try {
|
||||
stats = fs.statSync(fs.realpathSync(fullpath = path.join(this.dir, this.file)));
|
||||
if (stats.isDirectory()) {
|
||||
fullpath = undefined;
|
||||
}
|
||||
}
|
||||
catch (ex) {
|
||||
//
|
||||
// Ignore errors
|
||||
//
|
||||
}
|
||||
|
||||
looking = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Set the file for this instance to the fullpath
|
||||
// that we have found during the search. In the event that
|
||||
// the search was unsuccessful use the original value for `this.file`.
|
||||
//
|
||||
this.file = fullpath || this.file;
|
||||
|
||||
return fullpath;
|
||||
};
|
|
@ -1,29 +0,0 @@
|
|||
/*
|
||||
* literal.js: Simple literal Object store for nconf.
|
||||
*
|
||||
* (C) 2011, Charlie Robbins and the Contributors.
|
||||
*
|
||||
*/
|
||||
|
||||
var util = require('util'),
|
||||
Memory = require('./memory').Memory
|
||||
|
||||
var Literal = exports.Literal = function Literal (options) {
|
||||
Memory.call(this, options);
|
||||
|
||||
options = options || {}
|
||||
this.type = 'literal';
|
||||
this.readOnly = true;
|
||||
this.store = options.store || options;
|
||||
};
|
||||
|
||||
// Inherit from Memory store.
|
||||
util.inherits(Literal, Memory);
|
||||
|
||||
//
|
||||
// ### function loadSync (callback)
|
||||
// Returns the data stored in `this.store` synchronously.
|
||||
//
|
||||
Literal.prototype.loadSync = function () {
|
||||
return this.store;
|
||||
};
|
|
@ -1,229 +0,0 @@
|
|||
/*
|
||||
* memory.js: Simple memory storage engine for nconf configuration(s)
|
||||
*
|
||||
* (C) 2011, Charlie Robbins and the Contributors.
|
||||
*
|
||||
*/
|
||||
|
||||
var common = require('../common');
|
||||
|
||||
//
|
||||
// ### function Memory (options)
|
||||
// #### @options {Object} Options for this instance
|
||||
// Constructor function for the Memory nconf store which maintains
|
||||
// a nested json structure based on key delimiters `:`.
|
||||
//
|
||||
// e.g. `my:nested:key` ==> `{ my: { nested: { key: } } }`
|
||||
//
|
||||
var Memory = exports.Memory = function (options) {
|
||||
options = options || {};
|
||||
this.type = 'memory';
|
||||
this.store = {};
|
||||
this.mtimes = {};
|
||||
this.readOnly = false;
|
||||
this.loadFrom = options.loadFrom || null;
|
||||
this.logicalSeparator = options.logicalSeparator || ':';
|
||||
this.parseValues = options.parseValues || false;
|
||||
|
||||
if (this.loadFrom) {
|
||||
this.store = common.loadFilesSync(this.loadFrom);
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// ### function get (key)
|
||||
// #### @key {string} Key to retrieve for this instance.
|
||||
// Retrieves the value for the specified key (if any).
|
||||
//
|
||||
Memory.prototype.get = function (key) {
|
||||
var target = this.store,
|
||||
path = common.path(key, this.logicalSeparator);
|
||||
|
||||
//
|
||||
// Scope into the object to get the appropriate nested context
|
||||
//
|
||||
while (path.length > 0) {
|
||||
key = path.shift();
|
||||
if (target && typeof target !== 'string' && target.hasOwnProperty(key)) {
|
||||
target = target[key];
|
||||
continue;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return target;
|
||||
};
|
||||
|
||||
//
|
||||
// ### function set (key, value)
|
||||
// #### @key {string} Key to set in this instance
|
||||
// #### @value {literal|Object} Value for the specified key
|
||||
// Sets the `value` for the specified `key` in this instance.
|
||||
//
|
||||
Memory.prototype.set = function (key, value) {
|
||||
if (this.readOnly) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var target = this.store,
|
||||
path = common.path(key, this.logicalSeparator);
|
||||
|
||||
if (path.length === 0) {
|
||||
//
|
||||
// Root must be an object
|
||||
//
|
||||
if (!value || typeof value !== 'object') {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
this.reset();
|
||||
this.store = value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Update the `mtime` (modified time) of the key
|
||||
//
|
||||
this.mtimes[key] = Date.now();
|
||||
|
||||
//
|
||||
// Scope into the object to get the appropriate nested context
|
||||
//
|
||||
while (path.length > 1) {
|
||||
key = path.shift();
|
||||
if (!target[key] || typeof target[key] !== 'object') {
|
||||
target[key] = {};
|
||||
}
|
||||
|
||||
target = target[key];
|
||||
}
|
||||
|
||||
// Set the specified value in the nested JSON structure
|
||||
key = path.shift();
|
||||
if (this.parseValues) {
|
||||
value = common.parseValues.call(common, value);
|
||||
}
|
||||
target[key] = value;
|
||||
return true;
|
||||
};
|
||||
|
||||
//
|
||||
// ### function clear (key)
|
||||
// #### @key {string} Key to remove from this instance
|
||||
// Removes the value for the specified `key` from this instance.
|
||||
//
|
||||
Memory.prototype.clear = function (key) {
|
||||
if (this.readOnly) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var target = this.store,
|
||||
value = target,
|
||||
path = common.path(key, this.logicalSeparator);
|
||||
|
||||
//
|
||||
// Remove the key from the set of `mtimes` (modified times)
|
||||
//
|
||||
delete this.mtimes[key];
|
||||
|
||||
//
|
||||
// Scope into the object to get the appropriate nested context
|
||||
//
|
||||
for (var i = 0; i < path.length - 1; i++) {
|
||||
key = path[i];
|
||||
value = target[key];
|
||||
if (typeof value !== 'function' && typeof value !== 'object') {
|
||||
return false;
|
||||
}
|
||||
target = value;
|
||||
}
|
||||
|
||||
// Delete the key from the nested JSON structure
|
||||
key = path[i];
|
||||
delete target[key];
|
||||
return true;
|
||||
};
|
||||
|
||||
//
|
||||
// ### function merge (key, value)
|
||||
// #### @key {string} Key to merge the value into
|
||||
// #### @value {literal|Object} Value to merge into the key
|
||||
// Merges the properties in `value` into the existing object value
|
||||
// at `key`. If the existing value `key` is not an Object, it will be
|
||||
// completely overwritten.
|
||||
//
|
||||
Memory.prototype.merge = function (key, value) {
|
||||
if (this.readOnly) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// If the key is not an `Object` or is an `Array`,
|
||||
// then simply set it. Merging is for Objects.
|
||||
//
|
||||
if (typeof value !== 'object' || Array.isArray(value) || value === null) {
|
||||
return this.set(key, value);
|
||||
}
|
||||
|
||||
var self = this,
|
||||
target = this.store,
|
||||
path = common.path(key, this.logicalSeparator),
|
||||
fullKey = key;
|
||||
|
||||
//
|
||||
// Update the `mtime` (modified time) of the key
|
||||
//
|
||||
this.mtimes[key] = Date.now();
|
||||
|
||||
//
|
||||
// Scope into the object to get the appropriate nested context
|
||||
//
|
||||
while (path.length > 1) {
|
||||
key = path.shift();
|
||||
if (!target[key]) {
|
||||
target[key] = {};
|
||||
}
|
||||
|
||||
target = target[key];
|
||||
}
|
||||
|
||||
// Set the specified value in the nested JSON structure
|
||||
key = path.shift();
|
||||
|
||||
//
|
||||
// If the current value at the key target is not an `Object`,
|
||||
// or is an `Array` then simply override it because the new value
|
||||
// is an Object.
|
||||
//
|
||||
if (typeof target[key] !== 'object' || Array.isArray(target[key])) {
|
||||
target[key] = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
return Object.keys(value).every(function (nested) {
|
||||
return self.merge(common.keyed(self.logicalSeparator, fullKey, nested), value[nested]);
|
||||
});
|
||||
};
|
||||
|
||||
//
|
||||
// ### function reset (callback)
|
||||
// Clears all keys associated with this instance.
|
||||
//
|
||||
Memory.prototype.reset = function () {
|
||||
if (this.readOnly) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.mtimes = {};
|
||||
this.store = {};
|
||||
return true;
|
||||
};
|
||||
|
||||
//
|
||||
// ### function loadSync
|
||||
// Returns the store managed by this instance
|
||||
//
|
||||
Memory.prototype.loadSync = function () {
|
||||
return this.store || {};
|
||||
};
|
70
lib/stores/argv.mjs
Normal file
70
lib/stores/argv.mjs
Normal file
|
@ -0,0 +1,70 @@
|
|||
import util from 'util'
|
||||
import Memory from './memory.mjs'
|
||||
import * as common from '../common.mjs'
|
||||
|
||||
//
|
||||
// ### function Argv (options)
|
||||
// #### @options {Object} Options for this instance.
|
||||
// Constructor function for the Argv nconf store, a simple abstraction
|
||||
// around the Memory store that can read basic arguments.
|
||||
//
|
||||
const Argv = function(orgOpts) {
|
||||
let options = orgOpts || {}
|
||||
|
||||
Memory.call(this, options)
|
||||
|
||||
this.readOnly = true
|
||||
this.separator = options.separator || ''
|
||||
this.lowerCase = options.lowerCase || false
|
||||
this.parseValues = options.parseValues || false
|
||||
this.transform = options.transform || false
|
||||
this.prefix = options.prefix || '--'
|
||||
this.useEqualsign = options.useEqualsign || false
|
||||
|
||||
if (!this.prefix) {
|
||||
throw new Error('')
|
||||
}
|
||||
}
|
||||
|
||||
// Inherit from the Memory store
|
||||
util.inherits(Argv, Memory)
|
||||
|
||||
//
|
||||
// ### function load ()
|
||||
// Loads the data passed in from `process.env` into this instance.
|
||||
//
|
||||
Argv.prototype.load = function () {
|
||||
this.store = {}
|
||||
let args = process.argv.slice(2)
|
||||
|
||||
this.readOnly = false
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (args[i].startsWith(this.prefix)) {
|
||||
let key = args[i].slice(this.prefix.length)
|
||||
if (this.lowerCase) {
|
||||
key = key.toLowerCase()
|
||||
}
|
||||
if (this.separator) {
|
||||
key = common.key(...key.split(this.separator))
|
||||
}
|
||||
if (this.useEqualsign) {
|
||||
let equalSignIndex = key.indexOf('=')
|
||||
if (equalSignIndex > 0) {
|
||||
this.set(key.slice(0, equalSignIndex), key.slice(equalSignIndex + 1))
|
||||
}
|
||||
} else if (args[i + 1] && !args[i + 1].startsWith(this.prefix)) {
|
||||
this.set(key, args[i + 1])
|
||||
i++
|
||||
} else {
|
||||
this.set(key, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.readOnly = true
|
||||
|
||||
return this.store
|
||||
}
|
||||
|
||||
export default Argv
|
102
lib/stores/env.mjs
Normal file
102
lib/stores/env.mjs
Normal file
|
@ -0,0 +1,102 @@
|
|||
import util from 'util'
|
||||
import Memory from './memory.mjs'
|
||||
import * as common from '../common.mjs'
|
||||
|
||||
//
|
||||
// ### function Env (options)
|
||||
// #### @options {Object} Options for this instance.
|
||||
// Constructor function for the Env nconf store, a simple abstraction
|
||||
// around the Memory store that can read process environment variables.
|
||||
//
|
||||
const Env = function(orgOpts) {
|
||||
let options = orgOpts || {}
|
||||
|
||||
if (Array.isArray(options)) {
|
||||
options = { whitelist: options }
|
||||
}
|
||||
|
||||
Memory.call(this, options)
|
||||
|
||||
this.readOnly = true
|
||||
this.whitelist = options.whitelist || []
|
||||
this.separator = options.separator || ''
|
||||
this.lowerCase = options.lowerCase || false
|
||||
this.parseValues = options.parseValues || false
|
||||
this.transform = options.transform || false
|
||||
|
||||
if (!Array.isArray(this.whitelist)) {
|
||||
throw new Error('Env parameter whitelist was not an array or contained non-string elements')
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.whitelist.length; i++) {
|
||||
if (typeof(this.whitelist[i]) !== 'string') {
|
||||
throw new Error('Env parameter whitelist was not an array or contained non-string elements')
|
||||
}
|
||||
this.whitelist[i] = this.whitelist[i].toLowerCase()
|
||||
}
|
||||
|
||||
if (options.match) {
|
||||
if (typeof(options.match) === 'string') {
|
||||
options.match = new RegExp(options.match)
|
||||
}
|
||||
if (typeof(options.match.test) !== 'function') {
|
||||
throw new Error('Env parameter match was not a valid RegExp')
|
||||
}
|
||||
this.match = options.match
|
||||
}
|
||||
}
|
||||
|
||||
// Inherit from the Memory store
|
||||
util.inherits(Env, Memory)
|
||||
|
||||
//
|
||||
// ### function load ()
|
||||
// Loads the data passed in from `process.env` into this instance.
|
||||
//
|
||||
Env.prototype.load = function () {
|
||||
let env = {}
|
||||
if (this.lowerCase) {
|
||||
Object.keys(process.env).forEach(function (key) {
|
||||
env[key.toLowerCase()] = process.env[key]
|
||||
})
|
||||
} else {
|
||||
env = process.env
|
||||
}
|
||||
|
||||
if (this.transform) {
|
||||
env = common.transform(env, this.transform)
|
||||
}
|
||||
|
||||
this.readOnly = false
|
||||
|
||||
Object.keys(env).filter((key) => {
|
||||
if (this.match && this.whitelist.length) {
|
||||
return key.match(this.match) || this.whitelist.indexOf(key.toLowerCase()) !== -1
|
||||
}
|
||||
else if (this.match) {
|
||||
return key.match(this.match)
|
||||
}
|
||||
else {
|
||||
return !this.whitelist.length || this.whitelist.indexOf(key.toLowerCase()) !== -1
|
||||
}
|
||||
}).forEach((key) => {
|
||||
var val = env[key]
|
||||
|
||||
if (this.parseValues) {
|
||||
val = common.parseValues(val)
|
||||
}
|
||||
|
||||
if (this.separator) {
|
||||
this.set(common.key(...key.split(this.separator)), val)
|
||||
}
|
||||
else {
|
||||
this.set(key, val)
|
||||
}
|
||||
})
|
||||
|
||||
this.readOnly = true
|
||||
|
||||
return this.store
|
||||
}
|
||||
|
||||
export default Env
|
386
lib/stores/file.mjs
Normal file
386
lib/stores/file.mjs
Normal file
|
@ -0,0 +1,386 @@
|
|||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import util from 'util'
|
||||
import crypto from 'crypto'
|
||||
import Memory from './memory.mjs'
|
||||
|
||||
const fsPromise = fs.promises
|
||||
|
||||
//
|
||||
// ### function File (options)
|
||||
// #### @options {Object} Options for this instance
|
||||
// Constructor function for the File nconf store, a simple abstraction
|
||||
// around the Memory store that can persist configuration to disk.
|
||||
//
|
||||
const File = function (orgOpts) {
|
||||
let options = orgOpts
|
||||
if (typeof(options) === 'string') {
|
||||
options = { file: options }
|
||||
}
|
||||
if (!options || !options.file) {
|
||||
throw new Error('Missing required option `file`')
|
||||
}
|
||||
|
||||
Memory.call(this, options)
|
||||
|
||||
this.file = options.file
|
||||
// this.dir = options.dir || process.cwd()
|
||||
this.format = options.format || JSON
|
||||
this.secure = options.secure
|
||||
// this.spacing = options.json_spacing || options.spacing || 2
|
||||
|
||||
if (this.secure) {
|
||||
this.secure = typeof(this.secure === 'string')
|
||||
? { secret: this.secure.toString() }
|
||||
: this.secure
|
||||
|
||||
this.secure.alg = this.secure.alg || 'aes-256-ctr'
|
||||
// if (this.secure.secretPath) {
|
||||
// this.secure.secret = fs.readFileSync(this.secure.secretPath, 'utf8')
|
||||
// }
|
||||
|
||||
if (!this.secure.secret) {
|
||||
throw new Error('secure.secret option is required')
|
||||
}
|
||||
}
|
||||
|
||||
// if (options.search) {
|
||||
// this.search(this.dir)
|
||||
// }
|
||||
}
|
||||
|
||||
// Inherit from the Memory store
|
||||
util.inherits(File, Memory)
|
||||
|
||||
File.prototype.load = function () {
|
||||
this.store = {}
|
||||
|
||||
let fileData
|
||||
try {
|
||||
fileData = fs.readFileSync(this.file, 'utf8')
|
||||
}
|
||||
catch (ex) {
|
||||
throw new Error('Error opening ' + this.file + ': ' + ex.message)
|
||||
}
|
||||
|
||||
// Deals with file that include BOM
|
||||
if (fileData.charAt(0) === '\uFEFF') {
|
||||
fileData = fileData.substr(1)
|
||||
}
|
||||
|
||||
try {
|
||||
this.store = this.parse(fileData)
|
||||
}
|
||||
catch (ex) {
|
||||
throw new Error("Error parsing your configuration file: [" + this.file + ']: ' + ex.message)
|
||||
}
|
||||
|
||||
return this.store
|
||||
}
|
||||
|
||||
File.prototype.loadAsync = function () {
|
||||
this.store = {}
|
||||
|
||||
return fsPromise.readFile(this.file, 'utf8')
|
||||
.then(fileData => {
|
||||
let data = fileData
|
||||
if (data.charAt(0) === '\uFEFF') {
|
||||
data = data.substr(1)
|
||||
}
|
||||
|
||||
try {
|
||||
this.store = this.parse(data)
|
||||
} catch (err) {
|
||||
return Promise.reject(new Error("Error parsing your configuration file: [" + this.file + ']: ' + err.message))
|
||||
}
|
||||
|
||||
return this.store
|
||||
}, err => {
|
||||
return Promise.reject(new Error('Error opening ' + this.file + ': ' + err.message))
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// ### function parse (contents)
|
||||
// Returns a decrypted version of the contents IFF
|
||||
// `this.secure` is enabled.
|
||||
//
|
||||
File.prototype.parse = function (contents) {
|
||||
let parsed = this.format.parse(contents)
|
||||
|
||||
if (this.secure) {
|
||||
parsed = Object.keys(parsed).reduce((acc, key) => {
|
||||
let value = parsed[key]
|
||||
if (!value.iv) {
|
||||
throw new Error(`Encrypted file ${this.file} is outdated (encrypted without iv). Please re-encrypt your file.`)
|
||||
}
|
||||
let decipher = crypto.createDecipheriv(value.alg, this.secure.secret, Buffer.from(value.iv, 'hex'))
|
||||
let plaintext = decipher.update(value.value, 'hex', 'utf8')
|
||||
plaintext += decipher.final('utf8')
|
||||
acc[key] = this.format.parse(plaintext)
|
||||
return acc
|
||||
}, {})
|
||||
}
|
||||
|
||||
return parsed
|
||||
}
|
||||
|
||||
//
|
||||
// ### function save (path)
|
||||
// #### @path {string} The path to the file where we save the configuration to
|
||||
// Saves the current configuration object to disk at `this.file`
|
||||
//
|
||||
File.prototype.save = function (orgPath) {
|
||||
let path = orgPath
|
||||
if (!path) {
|
||||
path = this.file
|
||||
}
|
||||
fs.writeFileSync(path, this.stringify())
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
//
|
||||
// ### function save (path)
|
||||
// #### @path {string} The path to the file where we save the configuration to
|
||||
// Saves the current configuration object to disk at `this.file`
|
||||
//
|
||||
File.prototype.saveAsync = function(orgPath) {
|
||||
let path = orgPath
|
||||
if (!path) {
|
||||
path = this.file
|
||||
}
|
||||
|
||||
return fsPromise.writeFile(path, this.stringify()).then(() => {
|
||||
return this
|
||||
})
|
||||
}
|
||||
|
||||
//
|
||||
// ### function stringify ()
|
||||
// Returns an encrypted version of the contents IIF
|
||||
// `this.secure` is enabled
|
||||
//
|
||||
File.prototype.stringify = function () {
|
||||
let data = this.store
|
||||
|
||||
if (this.secure) {
|
||||
data = Object.keys(data).reduce((acc, key) => {
|
||||
let value = this.format.stringify(data[key])
|
||||
let iv = crypto.randomBytes(16)
|
||||
let cipher = crypto.createCipheriv(this.secure.alg, this.secure.secret, iv)
|
||||
let ciphertext = cipher.update(value, 'utf8', 'hex')
|
||||
ciphertext += cipher.final('hex')
|
||||
acc[key] = { alg: this.secure.alg, value: ciphertext, iv: iv.toString('hex') }
|
||||
return acc
|
||||
}, {})
|
||||
}
|
||||
|
||||
return this.format.stringify(data, null, this.spacing)
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
//
|
||||
// ### function save (value, callback)
|
||||
// #### @value {Object} _Ignored_ Left here for consistency
|
||||
// #### @callback {function} Continuation to respond to when complete.
|
||||
// Saves the current configuration object to disk at `this.file`
|
||||
// using the format specified by `this.format`.
|
||||
//
|
||||
File.prototype.save = function (value, callback) {
|
||||
this.saveToFile(this.file, value, callback)
|
||||
}
|
||||
|
||||
//
|
||||
// ### function saveToFile (path, value, callback)
|
||||
// #### @path {string} The path to the file where we save the configuration to
|
||||
// #### @format {Object} Optional formatter, default behing the one of the store
|
||||
// #### @callback {function} Continuation to respond to when complete.
|
||||
// Saves the current configuration object to disk at `this.file`
|
||||
// using the format specified by `this.format`.
|
||||
//
|
||||
File.prototype.saveToFile = function (path, format, callback) {
|
||||
if (!callback) {
|
||||
callback = format
|
||||
format = this.format
|
||||
}
|
||||
|
||||
fs.writeFile(path, this.stringify(format), callback)
|
||||
}
|
||||
|
||||
//
|
||||
// ### function saveSync (value, callback)
|
||||
// Saves the current configuration object to disk at `this.file`
|
||||
// using the format specified by `this.format` synchronously.
|
||||
//
|
||||
File.prototype.saveSync = function () {
|
||||
fs.writeFileSync(this.file, this.stringify())
|
||||
return this.store
|
||||
}
|
||||
|
||||
//
|
||||
// ### function load (callback)
|
||||
// #### @callback {function} Continuation to respond to when complete.
|
||||
// Responds with an Object representing all keys associated in this instance.
|
||||
//
|
||||
File.prototype.load = function (callback) {
|
||||
let self = this
|
||||
|
||||
exists(self.file, function (exists) {
|
||||
if (!exists) {
|
||||
return callback(null, {})
|
||||
}
|
||||
|
||||
//
|
||||
// Else, the path exists, read it from disk
|
||||
//
|
||||
fs.readFile(self.file, function (err, data) {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
}
|
||||
|
||||
try {
|
||||
// Deals with string that include BOM
|
||||
let stringData = data.toString()
|
||||
if (stringData.charAt(0) === '\uFEFF') {
|
||||
stringData = stringData.substr(1)
|
||||
}
|
||||
|
||||
self.store = self.parse(stringData)
|
||||
}
|
||||
catch (ex) {
|
||||
return callback(new Error("Error parsing your configuration file: [" + self.file + ']: ' + ex.message))
|
||||
}
|
||||
|
||||
callback(null, self.store)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
//
|
||||
// ### function loadSync (callback)
|
||||
// Attempts to load the data stored in `this.file` synchronously
|
||||
// and responds appropriately.
|
||||
//
|
||||
File.prototype.loadSync = function () {
|
||||
if (!existsSync(this.file)) {
|
||||
this.store = {}
|
||||
return this.store
|
||||
}
|
||||
|
||||
//
|
||||
// Else, the path exists, read it from disk
|
||||
//
|
||||
try {
|
||||
// Deals with file that include BOM
|
||||
let fileData = fs.readFileSync(this.file, 'utf8')
|
||||
if (fileData.charAt(0) === '\uFEFF') {
|
||||
fileData = fileData.substr(1)
|
||||
}
|
||||
|
||||
this.store = this.parse(fileData)
|
||||
}
|
||||
catch (ex) {
|
||||
throw new Error("Error parsing your configuration file: [" + this.file + ']: ' + ex.message)
|
||||
}
|
||||
|
||||
return this.store
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// ### function search (base)
|
||||
// #### @base {string} Base directory (or file) to begin searching for the target file.
|
||||
// Attempts to find `this.file` by iteratively searching up the
|
||||
// directory structure
|
||||
//
|
||||
File.prototype.search = function (base) {
|
||||
let looking = true,
|
||||
fullpath,
|
||||
previous,
|
||||
stats
|
||||
|
||||
base = base || process.cwd()
|
||||
|
||||
if (this.file[0] === '/') {
|
||||
//
|
||||
// If filename for this instance is a fully qualified path
|
||||
// (i.e. it starts with a `'/'`) then check if it exists
|
||||
//
|
||||
try {
|
||||
stats = fs.statSync(fs.realpathSync(this.file))
|
||||
if (stats.isFile()) {
|
||||
fullpath = this.file
|
||||
looking = false
|
||||
}
|
||||
}
|
||||
catch (ex) {
|
||||
//
|
||||
// Ignore errors
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
if (looking && base) {
|
||||
//
|
||||
// Attempt to stat the realpath located at `base`
|
||||
// if the directory does not exist then return false.
|
||||
//
|
||||
try {
|
||||
let stat = fs.statSync(fs.realpathSync(base))
|
||||
looking = stat.isDirectory()
|
||||
}
|
||||
catch (ex) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
while (looking) {
|
||||
//
|
||||
// Iteratively look up the directory structure from `base`
|
||||
//
|
||||
try {
|
||||
stats = fs.statSync(fs.realpathSync(fullpath = path.join(base, this.file)))
|
||||
looking = stats.isDirectory()
|
||||
}
|
||||
catch (ex) {
|
||||
previous = base
|
||||
base = path.dirname(base)
|
||||
|
||||
if (previous === base) {
|
||||
//
|
||||
// If we've reached the top of the directory structure then simply use
|
||||
// the default file path.
|
||||
//
|
||||
try {
|
||||
stats = fs.statSync(fs.realpathSync(fullpath = path.join(this.dir, this.file)))
|
||||
if (stats.isDirectory()) {
|
||||
fullpath = undefined
|
||||
}
|
||||
}
|
||||
catch (ex) {
|
||||
//
|
||||
// Ignore errors
|
||||
//
|
||||
}
|
||||
|
||||
looking = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Set the file for this instance to the fullpath
|
||||
// that we have found during the search. In the event that
|
||||
// the search was unsuccessful use the original value for `this.file`.
|
||||
//
|
||||
this.file = fullpath || this.file
|
||||
|
||||
return fullpath
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
export default File
|
15
lib/stores/literal.mjs
Normal file
15
lib/stores/literal.mjs
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { inherits } from 'util'
|
||||
import Memory from './memory.mjs'
|
||||
|
||||
function Literal (options) {
|
||||
Memory.call(this, options)
|
||||
|
||||
this.type = 'literal'
|
||||
this.readOnly = true
|
||||
this.store = options
|
||||
}
|
||||
|
||||
// Inherit from Memory store.
|
||||
inherits(Literal, Memory)
|
||||
|
||||
export default Literal
|
212
lib/stores/memory.mjs
Normal file
212
lib/stores/memory.mjs
Normal file
|
@ -0,0 +1,212 @@
|
|||
import * as common from '../common.mjs'
|
||||
|
||||
//
|
||||
// ### function Memory (options)
|
||||
// #### @options {Object} Options for this instance
|
||||
// Constructor function for the Memory nconf store which maintains
|
||||
// a nested json structure based on key delimiters `:`.
|
||||
//
|
||||
// e.g. `my:nested:key` ==> `{ my: { nested: { key: } } }`
|
||||
//
|
||||
function Memory(orgOpts) {
|
||||
let options = orgOpts || {}
|
||||
this.type = 'memory'
|
||||
this.store = {}
|
||||
this.readOnly = options.readOnly || false
|
||||
this.logicalSeparator = options.logicalSeparator || ':'
|
||||
this.parseValues = options.parseValues || false
|
||||
}
|
||||
|
||||
//
|
||||
// ### function get (key)
|
||||
// #### @key {string} Key to retrieve for this instance.
|
||||
// Retrieves the value for the specified key (if any).
|
||||
//
|
||||
Memory.prototype.get = function (key) {
|
||||
var target = this.store,
|
||||
path = common.path(key, this.logicalSeparator)
|
||||
|
||||
//
|
||||
// Scope into the object to get the appropriate nested context
|
||||
//
|
||||
while (path.length > 0) {
|
||||
key = path.shift()
|
||||
if (target && typeof target !== 'string' && target.hasOwnProperty(key)) {
|
||||
target = target[key]
|
||||
continue
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
return target
|
||||
}
|
||||
|
||||
//
|
||||
// ### function set (key, value)
|
||||
// #### @key {string} Key to set in this instance
|
||||
// #### @value {literal|Object} Value for the specified key
|
||||
// Sets the `value` for the specified `key` in this instance.
|
||||
//
|
||||
Memory.prototype.set = function (orgKey, orgValue) {
|
||||
if (this.readOnly) {
|
||||
return false
|
||||
}
|
||||
|
||||
let key = orgKey
|
||||
let value = orgValue
|
||||
|
||||
if (value === undefined && typeof(key) === 'object') {
|
||||
key = null
|
||||
value = orgKey
|
||||
}
|
||||
|
||||
let target = this.store
|
||||
let path = common.path(key, this.logicalSeparator)
|
||||
|
||||
if (path.length === 0) {
|
||||
//
|
||||
// Root must be an object
|
||||
//
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
||||
return false
|
||||
}
|
||||
|
||||
this.store = value
|
||||
return true
|
||||
}
|
||||
|
||||
key = path.shift()
|
||||
|
||||
//
|
||||
// Scope into the object to get the appropriate nested context
|
||||
//
|
||||
while (path.length) {
|
||||
if (!target[key]) {
|
||||
target[key] = {}
|
||||
}
|
||||
|
||||
target = target[key]
|
||||
key = path.shift()
|
||||
}
|
||||
|
||||
if (this.parseValues) {
|
||||
value = common.parseValues(value)
|
||||
}
|
||||
if (value) {
|
||||
if (Array.isArray(value)) {
|
||||
value = common.mergeRecursiveArray(value)
|
||||
} else if (typeof(value) === 'object') {
|
||||
value = common.merge([value])
|
||||
}
|
||||
}
|
||||
target[key] = value
|
||||
return true
|
||||
}
|
||||
|
||||
//
|
||||
// ### function clear (key)
|
||||
// #### @key {string} Key to remove from this instance
|
||||
// Removes the value for the specified `key` from this instance.
|
||||
//
|
||||
Memory.prototype.clear = function (key) {
|
||||
if (this.readOnly) {
|
||||
return false
|
||||
}
|
||||
|
||||
let target = this.store
|
||||
let value = target
|
||||
let path = common.path(key, this.logicalSeparator)
|
||||
|
||||
//
|
||||
// Scope into the object to get the appropriate nested context
|
||||
//
|
||||
let i = 0
|
||||
for (; i < path.length - 1; i++) {
|
||||
key = path[i]
|
||||
value = target[key]
|
||||
if (typeof value !== 'function' && typeof value !== 'object') {
|
||||
return false
|
||||
}
|
||||
target = value
|
||||
}
|
||||
|
||||
// Delete the key from the nested JSON structure
|
||||
key = path[i]
|
||||
delete target[key]
|
||||
return true
|
||||
}
|
||||
|
||||
//
|
||||
// ### function merge (key, value)
|
||||
// #### @key {string} Key to merge the value into
|
||||
// #### @value {literal|Object} Value to merge into the key
|
||||
// Merges the properties in `value` into the existing object value
|
||||
// at `key`.
|
||||
//
|
||||
Memory.prototype.merge = function (orgFullKey, orgValue) {
|
||||
if (this.readOnly) {
|
||||
return false
|
||||
}
|
||||
|
||||
let fullKey = orgFullKey
|
||||
let value = orgValue
|
||||
|
||||
// If fullkey is an object, do basic merge on root
|
||||
if (typeof(fullKey) === 'object') {
|
||||
this.store = common.merge(this.store, [fullKey])
|
||||
return true
|
||||
}
|
||||
|
||||
if (typeof(fullKey) === 'number') {
|
||||
fullKey = fullKey.toString()
|
||||
}
|
||||
|
||||
let target = this.store
|
||||
let path = common.path(fullKey, this.logicalSeparator)
|
||||
let key = path.shift()
|
||||
|
||||
//
|
||||
// Scope into the object to get the appropriate nested context
|
||||
//
|
||||
while (path.length) {
|
||||
if (!target[key]) {
|
||||
target[key] = {}
|
||||
}
|
||||
|
||||
target = target[key]
|
||||
key = path.shift()
|
||||
}
|
||||
|
||||
// Check if we actually need to do any merging. Sometimes a simple assign or "set"
|
||||
// is all that is needed. This might be instances where the value is "null" (which
|
||||
// would mean no merging is required) or if we're dealing with arrays on either side.
|
||||
if (!value || typeof(value) !== 'object' || Array.isArray(value) || !target[key] || typeof(target[key]) !== 'object' || Array.isArray(target[key])) {
|
||||
return this.set(fullKey, value)
|
||||
}
|
||||
|
||||
target[key] = common.merge(target[key], [value])
|
||||
return true
|
||||
}
|
||||
|
||||
//
|
||||
// ### function reset (callback)
|
||||
// Clears all keys associated with this instance.
|
||||
//
|
||||
Memory.prototype.reset = function () {
|
||||
if (this.readOnly) {
|
||||
return false
|
||||
}
|
||||
|
||||
this.store = {}
|
||||
return true
|
||||
}
|
||||
|
||||
//
|
||||
// ### function loadSync
|
||||
// Returns the store managed by this instance
|
||||
//
|
||||
Memory.prototype.loadSync = function () {
|
||||
return this.store || {}
|
||||
}
|
||||
|
||||
export default Memory
|
26
package.json
26
package.json
|
@ -1,14 +1,8 @@
|
|||
{
|
||||
"name": "nconf-lite",
|
||||
"description": "Hierarchical node.js configuration with files, environment variables, command-line arguments, and atomic object merging.",
|
||||
"version": "1.0.1",
|
||||
"author": "Charlie Robbins <charlie.robbins@gmail.com>",
|
||||
"contributors": [
|
||||
"Matt Hamann <matthew.hamann@gmail.com>",
|
||||
"Maciej Małecki <me@mmalecki.com>",
|
||||
"Jarrett Cruger <jcrugzz@gmail.com>",
|
||||
"Adrien Becchis"
|
||||
],
|
||||
"description": "Zero dependency hierarchical node.js configuration with files, environment variables, command-line arguments, and atomic object merging.",
|
||||
"version": "2.0.0",
|
||||
"author": "Jonatan Nilsson <jonatan@nilsson.is>",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "http://github.com/nfp-projects/nconf-lite.git"
|
||||
|
@ -18,16 +12,24 @@
|
|||
"key value store",
|
||||
"plugabble"
|
||||
],
|
||||
"dependencies": {
|
||||
"async": "^2.6.3",
|
||||
"ini": "^1.3.0"
|
||||
"watch": {
|
||||
"test": {
|
||||
"patterns": [
|
||||
"{lib,test}/*"
|
||||
],
|
||||
"extensions": "js,mjs",
|
||||
"quiet": true,
|
||||
"inherit": true
|
||||
}
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"eltro": "^1.0.2"
|
||||
},
|
||||
"main": "./lib/nconf.mjs",
|
||||
"scripts": {
|
||||
"test": "eltro test/**/*.test.mjs -r dot",
|
||||
"test:watch": "npm-watch test",
|
||||
"lint": "eslint ."
|
||||
},
|
||||
"files": [
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
{
|
||||
"extends": ["config:base"],
|
||||
|
||||
"platform": "github",
|
||||
"autodiscover": false,
|
||||
"requireConfig": true,
|
||||
|
||||
"ignoreNpmrcFile": true,
|
||||
"rangeStrategy": "replace",
|
||||
|
||||
"packageRules": [
|
||||
{
|
||||
"packagePatterns": [
|
||||
"*"
|
||||
],
|
||||
"minor": {
|
||||
"groupName": "all non-major dependencies",
|
||||
"groupSlug": "all-minor-patch"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
"commitMessagePrefix": "[dist]"
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
/*
|
||||
* common.js: Tests for common utility function in nconf.
|
||||
*
|
||||
* (C) 2011, Charlie Robbins and the Contributors.
|
||||
*
|
||||
*/
|
||||
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var helpers = require('./helpers');
|
||||
var nconf = require('../lib/nconf');
|
||||
|
||||
var mergeDir = path.join(__dirname, 'fixtures', 'merge');
|
||||
var files = fs.readdirSync(mergeDir).map(function (f) { return path.join(mergeDir, f) });
|
||||
|
||||
describe('nconf/common', () => {
|
||||
describe('Using nconf.common module', () => {
|
||||
it('the loadFiles() method should merge the files correctly', done => {
|
||||
nconf.loadFiles(files, (err, res) => {
|
||||
helpers.assertMerged(err, res);
|
||||
done();
|
||||
});
|
||||
});
|
||||
it("the loadFilesSync() method should merge the files correctly", () => {
|
||||
helpers.assertMerged(null, nconf.loadFilesSync(files));
|
||||
});
|
||||
});
|
||||
});
|
258
test/common.test.mjs
Normal file
258
test/common.test.mjs
Normal file
|
@ -0,0 +1,258 @@
|
|||
import { Eltro as t, assert} from 'eltro'
|
||||
import * as common from '../lib/common.mjs'
|
||||
|
||||
t.describe('#validkeyvalue', function() {
|
||||
t.test('should return key if valid key', function() {
|
||||
assert.strictEqual(common.validkeyvalue('asdf'), null)
|
||||
assert.strictEqual(common.validkeyvalue(''), null)
|
||||
assert.strictEqual(common.validkeyvalue(), null)
|
||||
assert.strictEqual(common.validkeyvalue(null), null)
|
||||
assert.strictEqual(common.validkeyvalue(undefined), null)
|
||||
})
|
||||
t.test('should return invalid valuetype in result', function() {
|
||||
assert.strictEqual(common.validkeyvalue([]), '__invalid_valuetype_of_object__')
|
||||
assert.strictEqual(common.validkeyvalue({}), '__invalid_valuetype_of_object__')
|
||||
assert.strictEqual(common.validkeyvalue([]), '__invalid_valuetype_of_object__')
|
||||
assert.strictEqual(common.validkeyvalue({}), '__invalid_valuetype_of_object__')
|
||||
assert.strictEqual(common.validkeyvalue(() => {}), '__invalid_valuetype_of_function__')
|
||||
assert.strictEqual(common.validkeyvalue(function() {}), '__invalid_valuetype_of_function__')
|
||||
})
|
||||
})
|
||||
|
||||
t.describe('#path()', function() {
|
||||
t.test('it should support normal operation', function() {
|
||||
assert.deepStrictEqual(common.path('a:b:c'), ['a','b','c'])
|
||||
assert.deepStrictEqual(common.path('a'), ['a'])
|
||||
})
|
||||
t.test('it should support different separator', function() {
|
||||
assert.deepStrictEqual(common.path('a:b:c', '__'), ['a:b:c'])
|
||||
assert.deepStrictEqual(common.path('a__b__c', '__'), ['a','b','c'])
|
||||
assert.deepStrictEqual(common.path('a', '__'), ['a'])
|
||||
})
|
||||
t.test('it should work with non-string keys', function() {
|
||||
assert.deepStrictEqual(common.path(1, '__'), ['1'])
|
||||
assert.deepStrictEqual(common.path(4.3, '__'), ['4.3'])
|
||||
})
|
||||
t.test('it should return invalid value on non-supported keys', function() {
|
||||
assert.deepStrictEqual(common.path([], '__'), ['__invalid_valuetype_of_object__'])
|
||||
assert.strictEqual(common.path([], '__').length, 1)
|
||||
assert.deepStrictEqual(common.path({}, '__'), ['__invalid_valuetype_of_object__'])
|
||||
assert.strictEqual(common.path({}, '__').length, 1)
|
||||
assert.deepStrictEqual(common.path([]), ['__invalid_valuetype_of_object__'])
|
||||
assert.strictEqual(common.path([]).length, 1)
|
||||
assert.deepStrictEqual(common.path({}), ['__invalid_valuetype_of_object__'])
|
||||
assert.strictEqual(common.path({}).length, 1)
|
||||
assert.deepStrictEqual(common.path(() => {}), ['__invalid_valuetype_of_function__'])
|
||||
assert.strictEqual(common.path(() => {}).length, 1)
|
||||
assert.deepStrictEqual(common.path(function() {}), ['__invalid_valuetype_of_function__'])
|
||||
assert.strictEqual(common.path(function() {}).length, 1)
|
||||
})
|
||||
t.test('it should support empty values and return empty path', function() {
|
||||
assert.deepStrictEqual(common.path(null, '__'), [])
|
||||
assert.strictEqual(common.path(null, '__').length, 0)
|
||||
assert.deepStrictEqual(common.path(undefined, '__'), [])
|
||||
assert.strictEqual(common.path(undefined, '__').length, 0)
|
||||
assert.deepStrictEqual(common.path('', '__'), [])
|
||||
assert.strictEqual(common.path('', '__').length, 0)
|
||||
assert.deepStrictEqual(common.path(null), [])
|
||||
assert.strictEqual(common.path(null).length, 0)
|
||||
assert.deepStrictEqual(common.path(undefined), [])
|
||||
assert.strictEqual(common.path(undefined).length, 0)
|
||||
assert.deepStrictEqual(common.path(''), [])
|
||||
assert.strictEqual(common.path('').length, 0)
|
||||
assert.deepStrictEqual(common.path(), [])
|
||||
assert.strictEqual(common.path().length, 0)
|
||||
})
|
||||
})
|
||||
|
||||
t.describe('#key()', function() {
|
||||
t.test('it should work with common values', function() {
|
||||
assert.strictEqual(common.key('a'), 'a')
|
||||
assert.strictEqual(common.key('a', 'b'), 'a:b')
|
||||
assert.strictEqual(common.key('a', 'b', 'c'), 'a:b:c')
|
||||
assert.strictEqual(common.key(123), '123')
|
||||
assert.strictEqual(common.key(5.4), '5.4')
|
||||
assert.strictEqual(common.key('a', 123, 'b'), 'a:123:b')
|
||||
assert.strictEqual(common.key('a', 5.4, 'b'), 'a:5.4:b')
|
||||
assert.strictEqual(common.key('a', 123, 456), 'a:123:456')
|
||||
assert.strictEqual(common.key('a', 5.4, 456), 'a:5.4:456')
|
||||
})
|
||||
t.test('it should text replace invalid keys with the invalid value string', function() {
|
||||
assert.strictEqual(common.key([]), '__invalid_valuetype_of_object__')
|
||||
assert.strictEqual(common.key({}), '__invalid_valuetype_of_object__')
|
||||
assert.strictEqual(common.key([]), '__invalid_valuetype_of_object__')
|
||||
assert.strictEqual(common.key({}), '__invalid_valuetype_of_object__')
|
||||
assert.strictEqual(common.key(() => {}), '__invalid_valuetype_of_function__')
|
||||
assert.strictEqual(common.key(function() {}), '__invalid_valuetype_of_function__')
|
||||
assert.strictEqual(common.key('a', [], 'b'), 'a:__invalid_valuetype_of_object__:b')
|
||||
assert.strictEqual(common.key('a', {}, 'b'), 'a:__invalid_valuetype_of_object__:b')
|
||||
assert.strictEqual(common.key('a', [], 'b'), 'a:__invalid_valuetype_of_object__:b')
|
||||
assert.strictEqual(common.key('a', {}, 'b'), 'a:__invalid_valuetype_of_object__:b')
|
||||
assert.strictEqual(common.key('a', () => {}, 'b'), 'a:__invalid_valuetype_of_function__:b')
|
||||
assert.strictEqual(common.key('a', function() {}, 'b'), 'a:__invalid_valuetype_of_function__:b')
|
||||
})
|
||||
})
|
||||
|
||||
t.describe('#keyed()', function() {
|
||||
t.test('it should work with common values', function() {
|
||||
assert.strictEqual(common.keyed('__', 'a'), 'a')
|
||||
assert.strictEqual(common.keyed('__', 'a', 'b'), 'a__b')
|
||||
assert.strictEqual(common.keyed('__', 'a', 'b', 'c'), 'a__b__c')
|
||||
assert.strictEqual(common.keyed('__', 123), '123')
|
||||
assert.strictEqual(common.keyed('__', 5.4), '5.4')
|
||||
assert.strictEqual(common.keyed('__', 'a', 123, 'b'), 'a__123__b')
|
||||
assert.strictEqual(common.keyed('__', 'a', 5.4, 'b'), 'a__5.4__b')
|
||||
assert.strictEqual(common.keyed('__', 'a', 123, 456), 'a__123__456')
|
||||
assert.strictEqual(common.keyed('__', 'a', 5.4, 456), 'a__5.4__456')
|
||||
})
|
||||
t.test('it should text replace invalid keys with the invalid value string', function() {
|
||||
assert.strictEqual(common.keyed('__', []), '__invalid_valuetype_of_object__')
|
||||
assert.strictEqual(common.keyed('__', {}), '__invalid_valuetype_of_object__')
|
||||
assert.strictEqual(common.keyed('__', []), '__invalid_valuetype_of_object__')
|
||||
assert.strictEqual(common.keyed('__', {}), '__invalid_valuetype_of_object__')
|
||||
assert.strictEqual(common.keyed('__', () => {}), '__invalid_valuetype_of_function__')
|
||||
assert.strictEqual(common.keyed('__', function() {}), '__invalid_valuetype_of_function__')
|
||||
assert.strictEqual(common.keyed('__', 'a', [], 'b'), 'a____invalid_valuetype_of_object____b')
|
||||
assert.strictEqual(common.keyed('__', 'a', {}, 'b'), 'a____invalid_valuetype_of_object____b')
|
||||
assert.strictEqual(common.keyed('__', 'a', [], 'b'), 'a____invalid_valuetype_of_object____b')
|
||||
assert.strictEqual(common.keyed('__', 'a', {}, 'b'), 'a____invalid_valuetype_of_object____b')
|
||||
assert.strictEqual(common.keyed('__', 'a', () => {}, 'b'), 'a____invalid_valuetype_of_function____b')
|
||||
assert.strictEqual(common.keyed('__', 'a', function() {}, 'b'), 'a____invalid_valuetype_of_function____b')
|
||||
})
|
||||
})
|
||||
|
||||
t.describe('#merge()', function() {
|
||||
t.test('should throw if not sent an array', function() {
|
||||
assert.throws(function() { common.merge({}) })
|
||||
assert.throws(function() { common.merge('asdf') })
|
||||
assert.throws(function() { common.merge(12412) })
|
||||
assert.throws(function() { common.merge(null) })
|
||||
assert.throws(function() { common.merge(undefined) })
|
||||
assert.throws(function() { common.merge() })
|
||||
})
|
||||
|
||||
t.test('it should be able to merge properly', function() {
|
||||
// Test individual check and then re-check that the original are untouched
|
||||
let fn = function() { return true }
|
||||
let a = null
|
||||
let b = null
|
||||
let c = null
|
||||
|
||||
|
||||
a = { a: 1 }
|
||||
b = { b: 2 }
|
||||
assert.deepStrictEqual(common.merge([a, b]), { a: 1, b: 2 })
|
||||
assert.deepStrictEqual(a, { a: 1 })
|
||||
assert.deepStrictEqual(b, { b: 2 })
|
||||
|
||||
a = { a: 1 }
|
||||
b = { b: 2 }
|
||||
c = { a: 3 }
|
||||
assert.deepStrictEqual(common.merge([a, b, c]), { a: 3, b: 2 })
|
||||
assert.deepStrictEqual(a, { a: 1 })
|
||||
assert.deepStrictEqual(b, { b: 2 })
|
||||
assert.deepStrictEqual(c, { a: 3 })
|
||||
|
||||
a = { a: [1, 2] }
|
||||
b = { a: [2, 3] }
|
||||
assert.deepStrictEqual(common.merge([a, b]), { a: [2, 3] })
|
||||
assert.deepStrictEqual(b, { a: [2, 3] })
|
||||
assert.deepStrictEqual(a, { a: [1, 2] })
|
||||
|
||||
a = { a: [1, 2] }
|
||||
b = { a: [2, [3, 4]] }
|
||||
assert.deepStrictEqual(common.merge([a, b]), { a: [2, [3, 4]] })
|
||||
assert.deepStrictEqual(a, { a: [1, 2] })
|
||||
assert.deepStrictEqual(b, { a: [2, [3, 4]] })
|
||||
|
||||
a = { a: fn }
|
||||
b = { b: 2 }
|
||||
assert.deepStrictEqual(common.merge([a, b]), { a: fn, b: 2 })
|
||||
assert.deepStrictEqual(a, { a: fn })
|
||||
assert.deepStrictEqual(b, { b: 2 })
|
||||
|
||||
a = { a: fn }
|
||||
b = { b: 2 }
|
||||
c = { a: 3 }
|
||||
assert.deepStrictEqual(common.merge([a, b, c]), { a: 3, b: 2 })
|
||||
assert.deepStrictEqual(a, { a: fn })
|
||||
assert.deepStrictEqual(b, { b: 2 })
|
||||
assert.deepStrictEqual(c, { a: 3 })
|
||||
|
||||
a = { apples: true, bananas: true, foo: { bar: "boo" }, candy: { something: "file1", something1: true, something2: true, something5: { first: 1, second: 2 } }, unicorn: { exists: true }}
|
||||
b = { candy: { something: "file2", something3: true, something4: true }, dates: true, elderberries: true, unicorn: null }
|
||||
assert.deepStrictEqual(common.merge([a, b]), { apples: true, bananas: true, foo: { bar: "boo" }, candy: { something: "file2", something1: true, something2: true, something3: true, something4: true, something5: { first: 1, second: 2 } }, dates: true, elderberries: true, unicorn: null })
|
||||
assert.deepStrictEqual(a, { apples: true, bananas: true, foo: { bar: "boo" }, candy: { something: "file1", something1: true, something2: true, something5: { first: 1, second: 2 } }, unicorn: { exists: true }})
|
||||
assert.deepStrictEqual(b, { candy: { something: "file2", something3: true, something4: true }, dates: true, elderberries: true, unicorn: null })
|
||||
|
||||
// weird behavior from old merge but I have no better idea to turn arrays
|
||||
// into object so this is "good enough" for now
|
||||
a = { a: 1 }
|
||||
b = { a: 2 }
|
||||
c = ['test']
|
||||
assert.deepStrictEqual(common.merge([a, b, c]), { '0': 'test', a: 2 })
|
||||
assert.deepStrictEqual(a, { a: 1 })
|
||||
assert.deepStrictEqual(b, { a: 2 })
|
||||
assert.deepStrictEqual(c, ['test'])
|
||||
})
|
||||
|
||||
t.test('it should support edge cases properly', function() {
|
||||
let a = { a: { b: 1 } }
|
||||
let b = { a: ['test'] }
|
||||
let out = common.merge([a, b])
|
||||
assert.deepStrictEqual(out, { a: ['test'] })
|
||||
b = { a: { b: 1 } }
|
||||
out = common.merge(out, [b])
|
||||
assert.deepStrictEqual(out, { a: { b: 1 } })
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
t.describe('#capitalize()', function() {
|
||||
t.test('should return original if not string', function() {
|
||||
const assertObject = {}
|
||||
const assertArray = []
|
||||
assert.strictEqual(common.capitalize(assertObject), assertObject)
|
||||
assert.strictEqual(common.capitalize(assertArray), assertArray)
|
||||
assert.strictEqual(common.capitalize(null), null)
|
||||
assert.strictEqual(common.capitalize(undefined), undefined)
|
||||
assert.strictEqual(common.capitalize(), undefined)
|
||||
})
|
||||
|
||||
t.test('should adapt value to string if value type', function() {
|
||||
assert.strictEqual(common.capitalize(12412), '12412')
|
||||
assert.strictEqual(common.capitalize(123.4), '123.4')
|
||||
})
|
||||
|
||||
|
||||
t.test('should otherwise capitalize', function() {
|
||||
assert.strictEqual(common.capitalize('asdf'), 'Asdf')
|
||||
assert.strictEqual(common.capitalize('test test'), 'Test test')
|
||||
assert.strictEqual(common.capitalize('FOO'), 'FOO')
|
||||
assert.strictEqual(common.capitalize('f'), 'F')
|
||||
assert.strictEqual(common.capitalize('F'), 'F')
|
||||
assert.strictEqual(common.capitalize(''), '')
|
||||
})
|
||||
})
|
||||
|
||||
t.describe('#parseValues()', function() {
|
||||
t.test('should special handle undefined', function() {
|
||||
assert.strictEqual(common.parseValues('undefined'), undefined)
|
||||
})
|
||||
|
||||
t.test('should normally json parse string', function() {
|
||||
assert.strictEqual(common.parseValues('null'), null)
|
||||
assert.deepStrictEqual(common.parseValues('{"a": 1}'), { a: 1 })
|
||||
assert.deepStrictEqual(common.parseValues('["a", 1]'), [ 'a', 1 ])
|
||||
assert.strictEqual(common.parseValues('123'), 123)
|
||||
assert.strictEqual(common.parseValues('"{\\"a\\": 1}"'), '{"a": 1}')
|
||||
})
|
||||
|
||||
t.test('should otherwise return original string if errors are found', function() {
|
||||
assert.strictEqual(common.parseValues('anull'), 'anull')
|
||||
assert.deepStrictEqual(common.parseValues('a{"a": 1}'), 'a{"a": 1}')
|
||||
assert.deepStrictEqual(common.parseValues('a["a", 1]'), 'a["a", 1]')
|
||||
assert.strictEqual(common.parseValues('a123'), 'a123')
|
||||
assert.strictEqual(common.parseValues('a"{\\"a\\": 1}"'), 'a"{\\"a\\": 1}"')
|
||||
})
|
||||
})
|
|
@ -1,258 +0,0 @@
|
|||
/*
|
||||
* complete-test.js: Complete test for multiple stores.
|
||||
*
|
||||
* (C) 2011, Charlie Robbins and the Contributors.
|
||||
*
|
||||
*/
|
||||
|
||||
var fs = require('fs');
|
||||
var nconf = require('../lib/nconf');
|
||||
var data = require('./fixtures/data').data;
|
||||
var helpers = require('./helpers');
|
||||
|
||||
var completeTest = helpers.fixture('complete-test.json');
|
||||
var complete = helpers.fixture('complete.json');
|
||||
|
||||
// prime the process.env
|
||||
process.env['NCONF_foo'] = 'bar';
|
||||
process.env.FOO = 'bar';
|
||||
process.env.BAR = 'zalgo';
|
||||
process.env.NODE_ENV = 'debug';
|
||||
process.env.FOOBAR = 'should not load';
|
||||
process.env.json_array = JSON.stringify(['foo', 'bar', 'baz']);
|
||||
process.env.json_obj = JSON.stringify({foo: 'bar', baz: 'foo'});
|
||||
process.env.NESTED__VALUE = 'nested';
|
||||
process.env.NESTED___VALUE_EXTRA_LODASH = '_nested_';
|
||||
|
||||
describe('nconf/multiple-stores', () => {
|
||||
describe("When using the nconf with multiple providers", () => {
|
||||
beforeAll(done => {
|
||||
helpers.cp(complete, completeTest, function () {
|
||||
nconf.env({
|
||||
// separator: '__',
|
||||
match: /^NCONF_/,
|
||||
whitelist: ['NODE_ENV', 'FOO', 'BAR']
|
||||
});
|
||||
nconf.file({file: completeTest});
|
||||
nconf.use('argv', {type: 'literal', store: data});
|
||||
done();
|
||||
});
|
||||
});
|
||||
afterAll(() => {
|
||||
fs.unlinkSync(completeTest);
|
||||
nconf.remove('file');
|
||||
nconf.remove('memory');
|
||||
nconf.remove('argv');
|
||||
nconf.remove('env');
|
||||
})
|
||||
it("should have the correct `stores`", () => {
|
||||
expect(typeof nconf.stores.env).toBe('object');
|
||||
expect(typeof nconf.stores.argv).toBe('object');
|
||||
expect(typeof nconf.stores.file).toBe('object');
|
||||
});
|
||||
it("env vars, are present", () => {
|
||||
['NODE_ENV', 'FOO', 'BAR', 'NCONF_foo'].forEach(function (key) {
|
||||
expect(nconf.get(key)).toEqual(process.env[key]);
|
||||
});
|
||||
});
|
||||
it("json vars are present", done => {
|
||||
fs.readFile(complete, 'utf8', (err, data) => {
|
||||
expect(err).toBe(null);
|
||||
data = JSON.parse(data);
|
||||
Object.keys(data).forEach(function (key) {
|
||||
expect(nconf.get(key)).toEqual(data[key]);
|
||||
});
|
||||
done();
|
||||
})
|
||||
});
|
||||
it("literal vars are present", () => {
|
||||
Object.keys(data).forEach(function (key) {
|
||||
expect(nconf.get(key)).toEqual(data[key]);
|
||||
});
|
||||
});
|
||||
describe('saving', () => {
|
||||
afterEach(() => {
|
||||
// remove the file so that we can test saving it async
|
||||
fs.unlinkSync(completeTest);
|
||||
});
|
||||
it("and saving *synchronously* correct return value, the file, saved correctly", done => {
|
||||
nconf.set('weebls', 'stuff');
|
||||
var topic = nconf.save();
|
||||
Object.keys(topic).forEach(function (key) {
|
||||
expect(topic[key]).toEqual(nconf.get(key));
|
||||
});
|
||||
fs.readFile(completeTest, 'utf8', function (err, data) {
|
||||
expect(err).toBe(null);
|
||||
data = JSON.parse(data);
|
||||
Object.keys(data).forEach(function (key) {
|
||||
expect(data[key]).toEqual(nconf.get(key));
|
||||
});
|
||||
expect(nconf.get('weebls')).toEqual('stuff');
|
||||
done();
|
||||
});
|
||||
});
|
||||
it("and saving *asynchronously* correct return value, the file, saved correctly", done => {
|
||||
nconf.set('weebls', 'crap');
|
||||
nconf.save((err, data) => {
|
||||
Object.keys(data).forEach(function (key) {
|
||||
expect(data[key]).toEqual(nconf.get(key));
|
||||
});
|
||||
fs.readFile(completeTest, 'utf8', function (err, data) {
|
||||
expect(err).toBe(null);
|
||||
data = JSON.parse(data);
|
||||
Object.keys(data).forEach(function (key) {
|
||||
expect(data[key]).toEqual(nconf.get(key));
|
||||
});
|
||||
expect(nconf.get('weebls')).toEqual('crap');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe("When using the nconf env with custom options", () => {
|
||||
|
||||
describe("When using env with lowerCase:true", () => {
|
||||
// Threw this in it's own batch to make sure it's run separately from the sync check
|
||||
beforeAll(done => {
|
||||
helpers.cp(complete, completeTest, () => {
|
||||
nconf.env({lowerCase: true});
|
||||
done();
|
||||
})
|
||||
})
|
||||
it("env vars keys also available as lower case", () => {
|
||||
Object.keys(process.env).forEach(function (key) {
|
||||
expect(nconf.get(key.toLowerCase())).toEqual(process.env[key]);
|
||||
});
|
||||
})
|
||||
afterAll(() => nconf.remove('env'))
|
||||
});
|
||||
|
||||
|
||||
describe("When using env with parseValues:true", () => {
|
||||
// Threw this in it's own batch to make sure it's run separately from the sync check
|
||||
beforeAll(done => {
|
||||
helpers.cp(complete, completeTest, () => {
|
||||
nconf.env({parseValues: true});
|
||||
done();
|
||||
})
|
||||
})
|
||||
it("JSON keys properly parsed", () => {
|
||||
Object.keys(process.env).forEach(function (key) {
|
||||
var val = process.env[key];
|
||||
|
||||
try {
|
||||
val = JSON.parse(val);
|
||||
} catch (err) {
|
||||
}
|
||||
|
||||
expect(nconf.get(key)).toEqual(val);
|
||||
})
|
||||
afterAll(() => nconf.remove('env'))
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("When using env with transform:fn", () => {
|
||||
// Threw this in it's own batch to make sure it's run separately from the sync check
|
||||
beforeAll(done => {
|
||||
function testTransform(obj) {
|
||||
if (obj.key === 'FOO') {
|
||||
obj.key = 'FOOD';
|
||||
obj.value = 'BARFOO';
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
helpers.cp(complete, completeTest, () => {
|
||||
nconf.env({transform: testTransform});
|
||||
done();
|
||||
})
|
||||
});
|
||||
it("env vars port key/value properly transformed", () => {
|
||||
expect(nconf.get('FOOD')).toEqual('BARFOO');
|
||||
});
|
||||
|
||||
afterAll(() => nconf.remove('env'))
|
||||
});
|
||||
describe("When using env with transform:fn that drops an entry", () => {
|
||||
// Threw this in it's own batch to make sure it's run separately from the sync check
|
||||
beforeAll(done => {
|
||||
function testTransform(obj) {
|
||||
if (obj.key === 'FOO') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
helpers.cp(complete, completeTest, () => {
|
||||
nconf.env({transform: testTransform});
|
||||
done();
|
||||
})
|
||||
});
|
||||
it("env vars port key/value properly transformed", () => {
|
||||
expect(nconf.get('FOO')).toBe(undefined);
|
||||
});
|
||||
|
||||
afterAll(() => nconf.remove('env'))
|
||||
});
|
||||
describe("When using env with transform:fn that return an undefined value", () => {
|
||||
// Threw this in it's own batch to make sure it's run separately from the sync check
|
||||
beforeAll(done => {
|
||||
function testTransform(obj) {
|
||||
if (obj.key === 'FOO') {
|
||||
return {key: 'FOO', value: undefined};
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
helpers.cp(complete, completeTest, () => {
|
||||
nconf.env({transform: testTransform});
|
||||
done();
|
||||
})
|
||||
});
|
||||
it("env vars port key/value properly transformed", () => {
|
||||
expect(nconf.get('FOO')).toBe(undefined);
|
||||
});
|
||||
|
||||
afterAll(() => nconf.remove('env'))
|
||||
});
|
||||
describe("When using env with bad transform:fn", () => {
|
||||
// Threw this in it's own batch to make sure it's run separately from the sync check
|
||||
it(" port key/value throws transformation error", done => {
|
||||
|
||||
function testTransform(obj) {
|
||||
return {foo: 'bar'};
|
||||
}
|
||||
|
||||
helpers.cp(complete, completeTest, () => {
|
||||
try {
|
||||
nconf.env({transform: testTransform});
|
||||
} catch (err) {
|
||||
expect(err.name).toEqual('RuntimeError')
|
||||
done();
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
afterAll(() => nconf.remove('env'))
|
||||
});
|
||||
describe("When using env with a separator", () => {
|
||||
// Threw this in it's own batch to make sure it's run separately from the sync check
|
||||
beforeAll(done => {
|
||||
helpers.cp(complete, completeTest, () => {
|
||||
nconf.env({separator: /__+/});
|
||||
done();
|
||||
})
|
||||
});
|
||||
it("can access to nested values", () => {
|
||||
expect(nconf.get('NESTED')).toEqual({VALUE: 'nested', VALUE_EXTRA_LODASH: '_nested_'});
|
||||
});
|
||||
|
||||
afterAll(() => nconf.remove('env'))
|
||||
});
|
||||
|
||||
});
|
||||
});
|
11
test/fixtures/data.js → test/fixtures/data.mjs
vendored
11
test/fixtures/data.js → test/fixtures/data.mjs
vendored
|
@ -1,11 +1,4 @@
|
|||
/*
|
||||
* data.js: Simple data fixture for configuration test.
|
||||
*
|
||||
* (C) 2011, Charlie Robbins and the Contributors.
|
||||
*
|
||||
*/
|
||||
|
||||
exports.data = {
|
||||
export const data = {
|
||||
isNull: null,
|
||||
literal: 'bazz',
|
||||
arr: ['one', 2, true, { value: 'foo' }],
|
||||
|
@ -20,7 +13,7 @@ exports.data = {
|
|||
}
|
||||
};
|
||||
|
||||
exports.merge = {
|
||||
export const merge = {
|
||||
prop1: 1,
|
||||
prop2: [1, 2, 3],
|
||||
prop3: {
|
3
test/fixtures/hierarchy/hierarchical.json
vendored
3
test/fixtures/hierarchy/hierarchical.json
vendored
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"test": "empty"
|
||||
}
|
10
test/fixtures/scripts/nconf-argv.js
vendored
10
test/fixtures/scripts/nconf-argv.js
vendored
|
@ -1,10 +0,0 @@
|
|||
/*
|
||||
* default-argv.js: Test fixture for using yargs defaults with nconf.
|
||||
*
|
||||
* (C) 2011, Charlie Robbins and the Contributors.
|
||||
*
|
||||
*/
|
||||
|
||||
var nconf = require('../../../lib/nconf').argv().env();
|
||||
|
||||
process.stdout.write(nconf.get('something'));
|
16
test/fixtures/scripts/nconf-change-argv.js
vendored
16
test/fixtures/scripts/nconf-change-argv.js
vendored
|
@ -1,16 +0,0 @@
|
|||
/*
|
||||
* nconf-change-argv.js: Test fixture for changing argv on the fly
|
||||
*
|
||||
* (C) 2011, Charlie Robbins and the Contributors.
|
||||
*
|
||||
*/
|
||||
|
||||
var nconf = require('../../../lib/nconf').argv();
|
||||
|
||||
//
|
||||
// Remove 'badValue', 'evenWorse' and 'OHNOEZ'
|
||||
//
|
||||
process.argv.splice(3, 3);
|
||||
nconf.stores['argv'].loadArgv();
|
||||
process.stdout.write(nconf.get('something'));
|
||||
|
10
test/fixtures/scripts/nconf-env.js
vendored
10
test/fixtures/scripts/nconf-env.js
vendored
|
@ -1,10 +0,0 @@
|
|||
/*
|
||||
* nconf-env.js: Test fixture for using process.env defaults with nconf.
|
||||
*
|
||||
* (C) 2011, Charlie Robbins and the Contributors.
|
||||
*
|
||||
*/
|
||||
|
||||
var nconf = require('../../../lib/nconf').env();
|
||||
|
||||
process.stdout.write(nconf.get('SOMETHING'));
|
|
@ -1,20 +0,0 @@
|
|||
var path = require('path'),
|
||||
nconf = require('../../../lib/nconf');
|
||||
|
||||
nconf
|
||||
.file('localOverrides', path.join(__dirname, '..', 'merge', 'file3.json'))
|
||||
.defaults({
|
||||
"candy": {
|
||||
"something": "a nice default",
|
||||
"something1": true,
|
||||
"something2": true,
|
||||
"something5": {
|
||||
"first": 1,
|
||||
"second": 2
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
process.stdout.write(JSON.stringify({
|
||||
candy: nconf.get('candy')
|
||||
}));
|
|
@ -1,17 +0,0 @@
|
|||
/*
|
||||
* nconf-hierarchical-file-argv.js: Test fixture for using yargs defaults and a file store with nconf.
|
||||
*
|
||||
* (C) 2011, Charlie Robbins and the Contributors.
|
||||
* (C) 2011, Sander Tolsma
|
||||
*
|
||||
*/
|
||||
|
||||
var path = require('path'),
|
||||
nconf = require('../../../lib/nconf');
|
||||
|
||||
nconf.argv();
|
||||
nconf.add('file', {
|
||||
file: path.join(__dirname, '../hierarchy/hierarchical.json')
|
||||
});
|
||||
|
||||
process.stdout.write(nconf.get('something') || 'undefined');
|
|
@ -1,19 +0,0 @@
|
|||
/*
|
||||
* nconf-hierarchical-load-merge.js: Test fixture for loading and merging nested objects across stores.
|
||||
*
|
||||
* (C) 2012, Charlie Robbins and the Contributors.
|
||||
* (C) 2012, Michael Hart
|
||||
*
|
||||
*/
|
||||
|
||||
var path = require('path'),
|
||||
nconf = require('../../../lib/nconf');
|
||||
|
||||
nconf.argv({separator: '--'})
|
||||
.env('__')
|
||||
.file(path.join(__dirname, '..', 'merge', 'file1.json'));
|
||||
|
||||
process.stdout.write(JSON.stringify({
|
||||
apples: nconf.get('apples'),
|
||||
candy: nconf.get('candy')
|
||||
}));
|
|
@ -1,18 +0,0 @@
|
|||
/*
|
||||
* nconf-hierarchical-load-merge.js: Test fixture for loading and merging nested objects across stores.
|
||||
*
|
||||
* (C) 2012, Charlie Robbins and the Contributors.
|
||||
* (C) 2012, Michael Hart
|
||||
*
|
||||
*/
|
||||
|
||||
var path = require('path'),
|
||||
nconf = require('../../../lib/nconf');
|
||||
|
||||
nconf.argv()
|
||||
.file(path.join(__dirname, '..', 'merge', 'file1.json'));
|
||||
|
||||
process.stdout.write(JSON.stringify({
|
||||
apples: nconf.get('apples'),
|
||||
candy: nconf.get('candy')
|
||||
}));
|
|
@ -1,31 +0,0 @@
|
|||
/*
|
||||
* nconf-hierarchical-load-save.js: Test fixture for using yargs, envvars and a file store with nconf.
|
||||
*
|
||||
* (C) 2011, Charlie Robbins and the Contributors.
|
||||
*
|
||||
*/
|
||||
|
||||
var path = require('path'),
|
||||
nconf = require('../../../lib/nconf');
|
||||
|
||||
//
|
||||
// Setup nconf to use (in-order):
|
||||
// 1. Command-line arguments
|
||||
// 2. Environment variables
|
||||
// 3. A file located at 'path/to/config.json'
|
||||
//
|
||||
nconf.argv()
|
||||
.env()
|
||||
.file({ file: path.join(__dirname, '..', 'load-save.json') });
|
||||
|
||||
//
|
||||
// Set a few variables on `nconf`.
|
||||
//
|
||||
nconf.set('database:host', '127.0.0.1');
|
||||
nconf.set('database:port', 5984);
|
||||
|
||||
process.stdout.write(nconf.get('foo'));
|
||||
//
|
||||
// Save the configuration object to disk
|
||||
//
|
||||
nconf.save();
|
11
test/fixtures/scripts/nconf-nested-env.js
vendored
11
test/fixtures/scripts/nconf-nested-env.js
vendored
|
@ -1,11 +0,0 @@
|
|||
/*
|
||||
* nconf-nested-env.js: Test fixture for env with nested keys.
|
||||
*
|
||||
* (C) 2012, Charlie Robbins and the Contributors.
|
||||
* (C) 2012, Michael Hart
|
||||
*
|
||||
*/
|
||||
|
||||
var nconf = require('../../../lib/nconf').env('_');
|
||||
|
||||
process.stdout.write(nconf.get('SOME:THING'));
|
12
test/fixtures/scripts/provider-argv.js
vendored
12
test/fixtures/scripts/provider-argv.js
vendored
|
@ -1,12 +0,0 @@
|
|||
/*
|
||||
* provider-argv.js: Test fixture for using yargs defaults with nconf.
|
||||
*
|
||||
* (C) 2011, Charlie Robbins and the Contributors.
|
||||
*
|
||||
*/
|
||||
|
||||
var nconf = require('../../../lib/nconf');
|
||||
|
||||
var provider = new (nconf.Provider)().argv();
|
||||
|
||||
process.stdout.write(provider.get('something'));
|
12
test/fixtures/scripts/provider-env.js
vendored
12
test/fixtures/scripts/provider-env.js
vendored
|
@ -1,12 +0,0 @@
|
|||
/*
|
||||
* provider-argv.js: Test fixture for using process.env defaults with nconf.
|
||||
*
|
||||
* (C) 2011, Charlie Robbins and the Contributors.
|
||||
*
|
||||
*/
|
||||
|
||||
var nconf = require('../../../lib/nconf');
|
||||
|
||||
var provider = new (nconf.Provider)().env();
|
||||
|
||||
process.stdout.write(provider.get('SOMETHING'));
|
28
test/fixtures/store.json
vendored
Normal file
28
test/fixtures/store.json
vendored
Normal file
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"isNull": null,
|
||||
"literal": "bazz",
|
||||
"arr": [
|
||||
"one",
|
||||
2,
|
||||
true,
|
||||
{
|
||||
"value": "foo"
|
||||
}
|
||||
],
|
||||
"obj": {
|
||||
"host": "localhost",
|
||||
"port": 5984,
|
||||
"array": [
|
||||
"one",
|
||||
2,
|
||||
true,
|
||||
{
|
||||
"foo": "bar"
|
||||
}
|
||||
],
|
||||
"auth": {
|
||||
"username": "admin",
|
||||
"password": "password"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,13 +4,13 @@ import fs from 'fs'
|
|||
import path from 'path'
|
||||
import { exec as ex } from 'child_process'
|
||||
import { fileURLToPath } from 'url'
|
||||
import nconf from '../lib/nconf.js'
|
||||
import Nconf from '../lib/nconf.mjs'
|
||||
|
||||
|
||||
let __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
export function assertMerged(err, merged) {
|
||||
merged = merged instanceof nconf.Provider
|
||||
merged = merged instanceof Nconf
|
||||
? merged.store.store
|
||||
: merged;
|
||||
|
||||
|
@ -40,7 +40,7 @@ export function cp(from, to) {
|
|||
})
|
||||
};
|
||||
|
||||
export function exec(script, prefix = 'node') {
|
||||
/*export function exec(script, prefix = 'node') {
|
||||
let command = `${prefix} ${script}`
|
||||
return new Promise(function(res, rej) {
|
||||
ex(command,
|
||||
|
@ -57,7 +57,7 @@ export function exec(script, prefix = 'node') {
|
|||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
}*/
|
||||
|
||||
export function fixture(file) {
|
||||
return path.resolve(path.join(__dirname, 'fixtures', file));
|
||||
|
|
|
@ -1,37 +1,49 @@
|
|||
import { Eltro as t, assert} from 'eltro'
|
||||
import * as helpers from './helpers.mjs'
|
||||
import nconf from '../lib/nconf.js'
|
||||
import Nconf from '../lib/nconf.mjs'
|
||||
|
||||
var globalConfig = helpers.fixture('hierarchy/global.json');
|
||||
var userConfig = helpers.fixture('hierarchy/user.json');
|
||||
var globalConfig = helpers.fixture('hierarchy/global.json')
|
||||
var userConfig = helpers.fixture('hierarchy/user.json')
|
||||
var file3 = helpers.fixture('merge/file3.json')
|
||||
|
||||
t.describe('nconf/hierarchy, When using nconf', function() {
|
||||
t.test("configured with two file stores, should have the appropriate keys present", function() {
|
||||
nconf.add('user', {type: 'file', file: userConfig});
|
||||
nconf.add('global', {type: 'file', file: globalConfig});
|
||||
nconf.load();
|
||||
t.test('configured with two file stores should work', function() {
|
||||
let nconf = new Nconf()
|
||||
nconf.file('user', {type: 'file', file: userConfig})
|
||||
nconf.file('global', {type: 'file', file: globalConfig})
|
||||
nconf.load()
|
||||
|
||||
assert.strictEqual(nconf.get('title'), 'My specific title');
|
||||
assert.strictEqual(nconf.get('color'), 'green');
|
||||
assert.strictEqual(nconf.get('movie'), 'Kill Bill');
|
||||
});
|
||||
assert.strictEqual(nconf.get('title'), 'My specific title')
|
||||
assert.strictEqual(nconf.get('color'), 'green')
|
||||
assert.strictEqual(nconf.get('movie'), 'Kill Bill')
|
||||
})
|
||||
|
||||
t.test("configured with two file stores using `file` should have the appropriate keys present", function() {
|
||||
nconf.file('user', userConfig);
|
||||
nconf.file('global', globalConfig);
|
||||
nconf.load();
|
||||
t.test('configured with two file stores with just filenames should work', function() {
|
||||
let nconf = new Nconf()
|
||||
nconf.file('user', userConfig)
|
||||
nconf.file('global', globalConfig)
|
||||
nconf.load()
|
||||
|
||||
assert.strictEqual(nconf.get('title'), 'My specific title');
|
||||
assert.strictEqual(nconf.get('color'), 'green');
|
||||
assert.strictEqual(nconf.get('movie'), 'Kill Bill');
|
||||
});
|
||||
assert.strictEqual(nconf.get('title'), 'My specific title')
|
||||
assert.strictEqual(nconf.get('color'), 'green')
|
||||
assert.strictEqual(nconf.get('movie'), 'Kill Bill')
|
||||
})
|
||||
|
||||
t.test("configured with .file(), .defaults() should deep merge objects should merge nested objects ", async function() {
|
||||
var script = helpers.fixture('scripts/nconf-hierarchical-defaults-merge.js');
|
||||
let res = await helpers.exec(script)
|
||||
t.test('configured with .file(), .defaults() should deep merge objects correctly', async function() {
|
||||
let nconf = new Nconf()
|
||||
.file('localOverrides', file3)
|
||||
.defaults({
|
||||
"candy": {
|
||||
"something": "a nice default",
|
||||
"something1": true,
|
||||
"something2": true,
|
||||
"something5": {
|
||||
"first": 1,
|
||||
"second": 2
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
assert.deepEqual(JSON.parse(res.stdout), {
|
||||
candy: {
|
||||
assert.deepStrictEqual(nconf.get('candy'), {
|
||||
something: 'much better something for you',
|
||||
something1: true,
|
||||
something2: true,
|
||||
|
@ -40,7 +52,5 @@ t.describe('nconf/hierarchy, When using nconf', function() {
|
|||
first: 1,
|
||||
second: 99
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
var util = require('util'),
|
||||
events = require('events'),
|
||||
nconf = require('../../lib/nconf');
|
||||
|
||||
var Mock = nconf.Mock = function () {
|
||||
events.EventEmitter.call(this);
|
||||
this.type = 'mock';
|
||||
};
|
||||
|
||||
// Inherit from Memory store.
|
||||
util.inherits(Mock, events.EventEmitter);
|
||||
|
||||
//
|
||||
// ### function save (value, callback)
|
||||
// #### @value {Object} _Ignored_ Left here for consistency
|
||||
// #### @callback {function} Continuation to respond to when complete.
|
||||
// Waits `1000ms` and then calls the callback and emits the `save` event.
|
||||
//
|
||||
Mock.prototype.save = function (value, callback) {
|
||||
if (!callback && typeof value === 'function') {
|
||||
callback = value;
|
||||
value = null;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
|
||||
setTimeout(function () {
|
||||
self.emit('save');
|
||||
callback();
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
Mock.prototype.loadSync = function() {
|
||||
return {}
|
||||
}
|
|
@ -1,11 +1,12 @@
|
|||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { Eltro as t, assert} from 'eltro'
|
||||
import nconf from '../lib/nconf.js'
|
||||
import Nconf from '../lib/nconf.mjs'
|
||||
import * as helpers from './helpers.mjs'
|
||||
|
||||
t.describe('nconf, When using the nconf', function() {
|
||||
t.test("should have the correct methods set", function() {
|
||||
t.describe('nconf', function() {
|
||||
t.test('should have the correct methods set', function() {
|
||||
let nconf = new Nconf()
|
||||
assert.strictEqual(typeof(nconf.key), 'function')
|
||||
assert.strictEqual(typeof(nconf.path), 'function')
|
||||
assert.strictEqual(typeof(nconf.use), 'function')
|
||||
|
@ -19,37 +20,90 @@ t.describe('nconf, When using the nconf', function() {
|
|||
assert.strictEqual(typeof(nconf.required), 'function')
|
||||
})
|
||||
|
||||
t.test("the use() method should instaniate the correct store", function() {
|
||||
nconf.use('memory')
|
||||
nconf.load()
|
||||
assert.ok(nconf.stores['memory'] instanceof nconf.Memory)
|
||||
t.test('memory() should instaniate the correct store', function() {
|
||||
let nconf = new Nconf()
|
||||
nconf.memory()
|
||||
assert.ok(nconf.use('memory') instanceof Nconf.Memory)
|
||||
})
|
||||
|
||||
t.test("nconf should have the correct version set", function () {
|
||||
t.test('should have the correct version set', function () {
|
||||
let nconf = new Nconf()
|
||||
let pckg = JSON.parse(fs.readFileSync(helpers.fixture('../../package.json')))
|
||||
assert.ok(pckg.version)
|
||||
assert.strictEqual(nconf.version, pckg.version)
|
||||
})
|
||||
})
|
||||
|
||||
t.describe("the required() method", function() {
|
||||
t.test("should throw error with missing keys", function() {
|
||||
t.describe('#required()', function() {
|
||||
let nconf = new Nconf()
|
||||
nconf.memory()
|
||||
nconf.set('foo:bar:bazz', 'buzz')
|
||||
|
||||
t.test('should throw error with missing keys', function() {
|
||||
assert.throws(function() {
|
||||
nconf.required(['missing', 'foo:bar:bazz'])
|
||||
nconf.required(['missingtest', 'foo:bar:bazz'])
|
||||
}, /missingtest/)
|
||||
})
|
||||
|
||||
t.test('should throw error with missing keys with non-array parameter', function() {
|
||||
assert.throws(function() {
|
||||
nconf.required(['missingtest', 'foo:bar:bazz'])
|
||||
}, /missingtest/)
|
||||
})
|
||||
|
||||
t.test('should return the provider if all keys exist', function() {
|
||||
assert.strictEqual(nconf.required(['foo:bar:bazz']), nconf)
|
||||
})
|
||||
})
|
||||
t.test("should return the provider if all required keys exist", function() {
|
||||
var Provider = nconf.Provider
|
||||
t.describe('#any()', function() {
|
||||
let nconf = new Nconf()
|
||||
nconf.memory()
|
||||
nconf.set('foo:bar:bazz', 'buzz')
|
||||
assert.ok(nconf.required(['foo:bar:bazz']) instanceof Provider)
|
||||
|
||||
t.test('should return if found', function() {
|
||||
assert.strictEqual(nconf.any(['missingtest', 'nope', 'foo:bar:bazz']), 'buzz')
|
||||
})
|
||||
t.test('should return first item found', function() {
|
||||
assert.deepStrictEqual(nconf.any(['missingtest', 'foo', 'nope', 'foo:bar:bazz']), {
|
||||
bar: {
|
||||
bazz: 'buzz',
|
||||
},
|
||||
})
|
||||
})
|
||||
t.describe("with the memory store", function() {
|
||||
t.describe("the set() method", function() {
|
||||
t.test("should respond with true", function() {
|
||||
assert.ok(nconf.set('foo:bar:bazz', 'buzz'))
|
||||
t.test('should return if found as paramaters', function() {
|
||||
assert.strictEqual(nconf.any('missingtest', 'nope', 'foo:bar:bazz'), 'buzz')
|
||||
})
|
||||
t.test("should respond allow access to the root and complain about non-objects", function() {
|
||||
t.test('should return undefined otherwise', function() {
|
||||
assert.strictEqual(nconf.any(['missingtest', 'nope']), undefined)
|
||||
})
|
||||
})
|
||||
t.describe('#set()', function() {
|
||||
t.test('should respond with self if success', function() {
|
||||
let nconf = new Nconf()
|
||||
nconf.memory()
|
||||
assert.strictEqual(nconf.set('foo:bar:bazz', 'buzz'), nconf)
|
||||
})
|
||||
|
||||
t.test('should respond with false if not successful', function() {
|
||||
let nconf = new Nconf()
|
||||
nconf.memory({ readOnly: true })
|
||||
assert.strictEqual(nconf.set('foo:bar:bazz', 'buzz'), false)
|
||||
})
|
||||
|
||||
t.test('should always set the first writeable store', function() {
|
||||
let nconf = new Nconf()
|
||||
nconf.memory('first')
|
||||
nconf.memory('second')
|
||||
nconf.use('second').set('foo:bar:bazz', 'buzz')
|
||||
assert.strictEqual(nconf.get('foo:bar:bazz'), 'buzz')
|
||||
nconf.set('foo:bar:bazz', 'overwritten')
|
||||
assert.strictEqual(nconf.get('foo:bar:bazz'), 'overwritten')
|
||||
assert.strictEqual(nconf.use('second').get('foo:bar:bazz'), 'buzz')
|
||||
})
|
||||
|
||||
t.test('should respond allow access to the root and complain about non-objects', function() {
|
||||
let nconf = new Nconf()
|
||||
nconf.memory()
|
||||
assert.notOk(nconf.set(null, null))
|
||||
assert.notOk(nconf.set(null, undefined))
|
||||
assert.notOk(nconf.set(null))
|
||||
|
@ -58,54 +112,83 @@ t.describe('nconf, When using the nconf', function() {
|
|||
var original = nconf.get()
|
||||
assert.ok(nconf.set(null, nconf.get()))
|
||||
assert.notStrictEqual(nconf.get(), original)
|
||||
assert.deepEqual(nconf.get(), original)
|
||||
assert.deepStrictEqual(nconf.get(), original)
|
||||
})
|
||||
})
|
||||
t.describe("the get() method", function() {
|
||||
t.test("should respond with the correct value without a callback", function() {
|
||||
t.describe('#get()', function() {
|
||||
let nconf = new Nconf()
|
||||
nconf.memory()
|
||||
nconf.set('foo:bar:bazz', 'buzz')
|
||||
|
||||
t.test('should respond with the correct value', function() {
|
||||
assert.strictEqual(nconf.get('foo:bar:bazz'), 'buzz')
|
||||
})
|
||||
t.test("should not step inside strings without a callback", function() {
|
||||
|
||||
t.test('unknown keys should return undefined', function() {
|
||||
assert.strictEqual(nconf.get('foo:bar:bazz:toString'), undefined)
|
||||
})
|
||||
|
||||
t.test('should not step inside strings', function() {
|
||||
assert.strictEqual(nconf.get('foo:bar:bazz:0'), undefined)
|
||||
})
|
||||
t.test("should respond with the correct value with a callback", function (done) {
|
||||
nconf.get('foo:bar:bazz', (err, value) => {
|
||||
try {
|
||||
assert.strictEqual(value, 'buzz')
|
||||
done()
|
||||
} catch (leErr) {
|
||||
done(leErr)
|
||||
}
|
||||
})
|
||||
})
|
||||
t.test("should respond allow access to the root", function() {
|
||||
|
||||
t.test('should respond allow access to the root', function() {
|
||||
assert.ok(nconf.get(null))
|
||||
assert.ok(nconf.get(undefined))
|
||||
assert.ok(nconf.get())
|
||||
assert.deepStrictEqual(nconf.get(), { foo: { bar: { bazz: 'buzz' } } })
|
||||
})
|
||||
|
||||
t.test('should merge stores correctly', function() {
|
||||
let testMerge = new Nconf()
|
||||
testMerge.memory('higherpriority')
|
||||
testMerge.set('foo:bar', {
|
||||
bazz: 'overwritten',
|
||||
test: 1
|
||||
})
|
||||
testMerge.memory('lowerdefaults')
|
||||
testMerge.use('lowerdefaults').set('foo:bar:bazz', 'buzz')
|
||||
testMerge.use('lowerdefaults').set('foo:bar:buzz', 'buzz')
|
||||
|
||||
assert.strictEqual(testMerge.get('foo:bar:bazz'), 'overwritten')
|
||||
assert.strictEqual(testMerge.get('foo:bar:buzz'), 'buzz')
|
||||
|
||||
assert.deepStrictEqual(testMerge.get('foo:bar'), {
|
||||
bazz: 'overwritten',
|
||||
buzz: 'buzz',
|
||||
test: 1,
|
||||
})
|
||||
})
|
||||
t.describe("the clear() method", function() {
|
||||
t.test("should respond with the true", function() {
|
||||
})
|
||||
t.describe('#clear()', function() {
|
||||
t.test('should respond with self if success', function() {
|
||||
let nconf = new Nconf()
|
||||
nconf.memory().set('foo:bar:bazz', 'buzz')
|
||||
assert.strictEqual(nconf.get('foo:bar:bazz'), 'buzz')
|
||||
assert.ok(nconf.clear('foo:bar:bazz'))
|
||||
assert.ok(typeof(nconf.get('foo:bar:bazz')) === 'undefined')
|
||||
})
|
||||
})
|
||||
t.describe("the load() method", function() {
|
||||
t.test("should respond with the merged store without a callback", function() {
|
||||
assert.deepEqual(nconf.load(), {"foo": {"bar": {}}})
|
||||
})
|
||||
t.test("should respond with the merged store", function (done) {
|
||||
nconf.load((err, store) => {
|
||||
try {
|
||||
assert.strictEqual(err, null)
|
||||
assert.deepEqual(store, {"foo": {"bar": {}}})
|
||||
done()
|
||||
} catch (leErr) {
|
||||
done(leErr)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
assert.strictEqual(nconf.clear('foo:bar:bazz'), nconf)
|
||||
assert.strictEqual(nconf.get('foo:bar:bazz'), undefined)
|
||||
})
|
||||
|
||||
t.test('should respond with self if success even with readOnly store', function() {
|
||||
let nconf = new Nconf()
|
||||
nconf
|
||||
.literal({ testetytest: 'buzz' })
|
||||
.memory()
|
||||
.set('foo:bar:bazz', 'buzz')
|
||||
|
||||
assert.strictEqual(nconf.get('foo:bar:bazz'), 'buzz')
|
||||
assert.strictEqual(nconf.get('testetytest'), 'buzz')
|
||||
assert.strictEqual(nconf.clear('foo:bar:bazz'), nconf)
|
||||
assert.strictEqual(nconf.get('foo:bar:bazz'), undefined)
|
||||
assert.strictEqual(nconf.use('literal').get('foo:bar:bazz'), undefined)
|
||||
})
|
||||
|
||||
t.test('should respond with false if clearing readonly value', function() {
|
||||
let nconf = new Nconf()
|
||||
nconf.literal({ testetytest: 'buzz' })
|
||||
|
||||
assert.strictEqual(nconf.get('testetytest'), 'buzz')
|
||||
assert.notOk(nconf.clear('testetytest'))
|
||||
assert.strictEqual(nconf.get('testetytest'), 'buzz')
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
import { Eltro as t, assert} from 'eltro'
|
||||
import nconf from '../lib/nconf.js'
|
||||
import './mocks/mock-store.js'
|
||||
|
||||
t.describe('nconf/provider/save', () => {
|
||||
t.describe("When using nconf an instance of 'nconf.Provider' with a Mock store", () => {
|
||||
var nconfMock = nconf.use('mock');
|
||||
|
||||
t.test("the save() method should actually save before responding", function(done) {
|
||||
var mock = nconf.stores.mock;
|
||||
|
||||
mock.on('save', function () {
|
||||
nconfMock.saved = true;
|
||||
});
|
||||
|
||||
nconf.save(() => {
|
||||
try {
|
||||
assert.strictEqual(nconfMock.saved, true)
|
||||
done();
|
||||
} catch (err) {
|
||||
done(err)
|
||||
}
|
||||
});
|
||||
})
|
||||
})
|
||||
});
|
|
@ -3,17 +3,18 @@ import path from 'path'
|
|||
import { spawn } from 'child_process'
|
||||
import { Eltro as t, assert} from 'eltro'
|
||||
import * as helpers from './helpers.mjs'
|
||||
import nconf from '../lib/nconf.js'
|
||||
// import nconf from '../lib/nconf-o.mjs'
|
||||
import Nconf from '../lib/nconf.mjs'
|
||||
|
||||
var files = [
|
||||
let files = [
|
||||
helpers.fixture('merge/file1.json'),
|
||||
helpers.fixture('merge/file2.json'),
|
||||
];
|
||||
var override = JSON.parse(fs.readFileSync(files[0]), 'utf8');
|
||||
let override = JSON.parse(fs.readFileSync(files[0]), 'utf8');
|
||||
|
||||
function assertSystemConf(options) {
|
||||
return new Promise(function(res, rej) {
|
||||
var env = null;
|
||||
let env = null;
|
||||
|
||||
if (options.env) {
|
||||
env = {}
|
||||
|
@ -26,31 +27,32 @@ function assertSystemConf(options) {
|
|||
});
|
||||
}
|
||||
|
||||
var child = spawn('node', [options.script].concat(options.argv), {env: env});
|
||||
let child = spawn('node', [options.script].concat(options.argv), {env: env});
|
||||
child.stdout.once('data', data => {
|
||||
res(data.toString())
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
t.describe('nconf/provider When using nconf', function() {
|
||||
t.describe("an instance of 'nconf.Provider'", function() {
|
||||
t.test("calling the use() method with the same store type and different options"
|
||||
+ " should use a new instance of the store type", function() {
|
||||
var provider = new nconf.Provider().use('file', {file: files[0]});
|
||||
var old = provider.stores['file'];
|
||||
t.describe('When using nconf', function() {
|
||||
t.test("calling add with the same store type and different options should use the new instance", function() {
|
||||
let nconf = new Nconf()
|
||||
nconf.file({ file: files[0] })
|
||||
let old = nconf.using.get('file');
|
||||
|
||||
assert.strictEqual(provider.stores.file.file, files[0]);
|
||||
provider.use('file', {file: files[1]});
|
||||
assert.ok(old)
|
||||
assert.strictEqual(old.file, files[0]);
|
||||
nconf.file({ file: files[1] });
|
||||
|
||||
assert.notStrictEqual(old, provider.stores.file);
|
||||
assert.strictEqual(provider.stores.file.file, files[1]);
|
||||
let newOne = nconf.using.get('file');
|
||||
assert.notStrictEqual(old, newOne);
|
||||
assert.strictEqual(newOne.file, files[1]);
|
||||
})
|
||||
});
|
||||
|
||||
/*
|
||||
t.test("respond with correct arg when 'env' is true", async function() {
|
||||
let result = await assertSystemConf({
|
||||
script: helpers.fixture('scripts/provider-env.js'),
|
||||
script: helpers.fixture('scripts/provider-env.mjs'),
|
||||
env: {SOMETHING: 'foobar'}
|
||||
})
|
||||
|
||||
|
@ -58,7 +60,7 @@ t.describe('nconf/provider When using nconf', function() {
|
|||
});
|
||||
|
||||
t.test("respond with correct arg when 'env' is true and 'parseValues' option is true", function() {
|
||||
var env = {
|
||||
let env = {
|
||||
SOMETHING: 'foobar',
|
||||
SOMEBOOL: 'true',
|
||||
SOMENULL: 'null',
|
||||
|
@ -67,12 +69,12 @@ t.describe('nconf/provider When using nconf', function() {
|
|||
SOMEFLOAT: '0.5',
|
||||
SOMEBAD: '5.1a'
|
||||
};
|
||||
var oenv = {};
|
||||
let oenv = {};
|
||||
Object.keys(env).forEach(function (key) {
|
||||
if (process.env[key]) oenv[key] = process.env[key];
|
||||
process.env[key] = env[key];
|
||||
});
|
||||
var provider = new nconf.Provider().use('env', {parseValues: true});
|
||||
let provider = new nconf.Provider().use('env', {parseValues: true});
|
||||
Object.keys(env).forEach(function (key) {
|
||||
delete process.env[key];
|
||||
if (oenv[key]) process.env[key] = oenv[key];
|
||||
|
@ -91,7 +93,7 @@ t.describe('nconf/provider When using nconf', function() {
|
|||
t.describe("an instance of 'nconf.Provider'", function() {
|
||||
t.describe("the merge() method", function() {
|
||||
t.test("should have the result merged in", function() {
|
||||
var provider = new nconf.Provider().use('file', {file: files[1]});
|
||||
let provider = new nconf.Provider().use('file', {file: files[1]});
|
||||
provider.load();
|
||||
provider.merge(override);
|
||||
helpers.assertMerged(null, provider.stores.file.store);
|
||||
|
@ -99,7 +101,7 @@ t.describe('nconf/provider When using nconf', function() {
|
|||
});
|
||||
|
||||
t.test("should merge Objects over null", function() {
|
||||
var provider = new nconf.Provider().use('file', {file: files[1]});
|
||||
let provider = new nconf.Provider().use('file', {file: files[1]});
|
||||
provider.load();
|
||||
provider.merge(override);
|
||||
assert.strictEqual(provider.stores.file.store.unicorn.exists, true);
|
||||
|
@ -108,7 +110,7 @@ t.describe('nconf/provider When using nconf', function() {
|
|||
})
|
||||
t.describe("the load() method", function() {
|
||||
t.test("should respect the hierarchy when sources are passed in", function() {
|
||||
var provider = new nconf.Provider({
|
||||
let provider = new nconf.Provider({
|
||||
sources: {
|
||||
user: {
|
||||
type: 'file',
|
||||
|
@ -120,16 +122,16 @@ t.describe('nconf/provider When using nconf', function() {
|
|||
}
|
||||
}
|
||||
});
|
||||
var merged = provider.load();
|
||||
let merged = provider.load();
|
||||
helpers.assertMerged(null, merged);
|
||||
assert.strictEqual(merged.candy.something, 'file1');
|
||||
})
|
||||
t.test("should respect the hierarchy when multiple stores are used", function() {
|
||||
var provider = new nconf.Provider().overrides({foo: {bar: 'baz'}})
|
||||
let provider = new nconf.Provider().overrides({foo: {bar: 'baz'}})
|
||||
.add('file1', {type: 'file', file: files[0]})
|
||||
.add('file2', {type: 'file', file: files[1]});
|
||||
|
||||
var merged = provider.load();
|
||||
let merged = provider.load();
|
||||
|
||||
helpers.assertMerged(null, merged);
|
||||
assert.strictEqual(merged.foo.bar, 'baz');
|
||||
|
@ -139,17 +141,17 @@ t.describe('nconf/provider When using nconf', function() {
|
|||
})
|
||||
t.describe("the .file() method", function() {
|
||||
t.test("should use the correct File store with a single filepath", function() {
|
||||
var provider = new nconf.Provider();
|
||||
let provider = new nconf.Provider();
|
||||
provider.file(helpers.fixture('store.json'));
|
||||
assert.strictEqual(typeof(provider.stores.file), 'object');
|
||||
});
|
||||
t.test("should use the correct File store with a name and a filepath", function() {
|
||||
var provider = new nconf.Provider();
|
||||
let provider = new nconf.Provider();
|
||||
provider.file('custom', helpers.fixture('store.json'));
|
||||
assert.strictEqual(typeof(provider.stores.custom), 'object');
|
||||
});
|
||||
t.test("should use the correct File store with a single object", function() {
|
||||
var provider = new nconf.Provider();
|
||||
let provider = new nconf.Provider();
|
||||
provider.file({
|
||||
dir: helpers.fixture(''),
|
||||
file: 'store.json',
|
||||
|
@ -160,7 +162,7 @@ t.describe('nconf/provider When using nconf', function() {
|
|||
assert.strictEqual(provider.stores.file.file, helpers.fixture('store.json'));
|
||||
});
|
||||
t.test("should use the correct File store with a name and an object", function() {
|
||||
var provider = new nconf.Provider();
|
||||
let provider = new nconf.Provider();
|
||||
provider.file('custom', {
|
||||
dir: helpers.fixture(''),
|
||||
file: 'store.json',
|
||||
|
@ -169,9 +171,9 @@ t.describe('nconf/provider When using nconf', function() {
|
|||
|
||||
assert.strictEqual(typeof(provider.stores.custom), 'object');
|
||||
assert.strictEqual(provider.stores.custom.file, helpers.fixture('store.json'));
|
||||
})
|
||||
t.describe("the any() method", function() {
|
||||
var provider = new nconf.Provider({
|
||||
})*/
|
||||
/*t.describe("the any() method", function() {
|
||||
let provider = new nconf.Provider({
|
||||
type: 'literal',
|
||||
store: {
|
||||
key: "getThisValue"
|
||||
|
@ -219,5 +221,5 @@ t.describe('nconf/provider When using nconf', function() {
|
|||
});
|
||||
})
|
||||
})
|
||||
})
|
||||
})*/
|
||||
});
|
||||
|
|
152
test/stores/argv.test.mjs
Normal file
152
test/stores/argv.test.mjs
Normal file
|
@ -0,0 +1,152 @@
|
|||
import { Eltro as t, assert} from 'eltro'
|
||||
import Nconf from '../../lib/nconf.mjs'
|
||||
|
||||
t.describe('#load()', () => {
|
||||
t.test('should support pairs of arguments together as strings', function() {
|
||||
process.argv = ['bla', 'bla',
|
||||
'--foobar', '123',
|
||||
'--testety', 'hello',
|
||||
]
|
||||
let store = new Nconf.Argv()
|
||||
store.load()
|
||||
assert.strictEqual(store.get('foobar'), '123')
|
||||
assert.strictEqual(store.get('testety'), 'hello')
|
||||
assert.deepStrictEqual(store.get(), {
|
||||
foobar: '123',
|
||||
testety: 'hello',
|
||||
})
|
||||
})
|
||||
|
||||
t.test('should support custom prefix argument', function() {
|
||||
process.argv = ['bla', 'bla',
|
||||
'-foobar', '123',
|
||||
'-testety', 'hello',
|
||||
]
|
||||
let store = new Nconf.Argv({ prefix: '-' })
|
||||
store.load()
|
||||
|
||||
assert.strictEqual(store.get('foobar'), '123')
|
||||
assert.strictEqual(store.get('testety'), 'hello')
|
||||
assert.deepStrictEqual(store.get(), {
|
||||
foobar: '123',
|
||||
testety: 'hello',
|
||||
})
|
||||
})
|
||||
|
||||
t.test('should support individual options as boolean', function() {
|
||||
process.argv = ['bla', 'bla',
|
||||
'--booleanone',
|
||||
'--foobar', '123',
|
||||
'--testety', 'hello',
|
||||
'--booleantwo',
|
||||
]
|
||||
let store = new Nconf.Argv()
|
||||
store.load()
|
||||
|
||||
assert.strictEqual(store.get('foobar'), '123')
|
||||
assert.strictEqual(store.get('testety'), 'hello')
|
||||
assert.strictEqual(store.get('booleanone'), true)
|
||||
assert.strictEqual(store.get('booleantwo'), true)
|
||||
assert.deepStrictEqual(store.get(), {
|
||||
foobar: '123',
|
||||
testety: 'hello',
|
||||
booleanone: true,
|
||||
booleantwo: true,
|
||||
})
|
||||
})
|
||||
|
||||
t.test('should support forcing everything as lowercase', function() {
|
||||
process.argv = ['bla', 'bla',
|
||||
'--FOOBAR', '123',
|
||||
'--TESTETY', 'hello',
|
||||
]
|
||||
let store = new Nconf.Argv({ lowerCase: true })
|
||||
store.load()
|
||||
|
||||
assert.strictEqual(store.get('foobar'), '123')
|
||||
assert.strictEqual(store.get('testety'), 'hello')
|
||||
assert.deepStrictEqual(store.get(), {
|
||||
foobar: '123',
|
||||
testety: 'hello',
|
||||
})
|
||||
})
|
||||
|
||||
t.test('should support making objects', function() {
|
||||
process.argv = ['bla', 'bla',
|
||||
'--foo:bar:baz', '123',
|
||||
'--foo:bar:testety', 'hello',
|
||||
]
|
||||
let store = new Nconf.Argv()
|
||||
store.load()
|
||||
assert.strictEqual(store.get('foo:bar:baz'), '123')
|
||||
assert.strictEqual(store.get('foo:bar:testety'), 'hello')
|
||||
assert.deepStrictEqual(store.get(), {
|
||||
foo: {
|
||||
bar: {
|
||||
baz: '123',
|
||||
testety: 'hello',
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
t.test('should support custom seperator', function() {
|
||||
process.argv = ['bla', 'bla',
|
||||
'--foo__bar__baz', '123',
|
||||
'--foo__bar__testety', 'hello',
|
||||
]
|
||||
let store = new Nconf.Argv({ separator: '__' })
|
||||
store.load()
|
||||
assert.strictEqual(store.get('foo:bar:baz'), '123')
|
||||
assert.strictEqual(store.get('foo:bar:testety'), 'hello')
|
||||
assert.deepStrictEqual(store.get(), {
|
||||
foo: {
|
||||
bar: {
|
||||
baz: '123',
|
||||
testety: 'hello',
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
t.test('should support parse values', function() {
|
||||
process.argv = ['bla', 'bla',
|
||||
'--foo', '123',
|
||||
'--bar', '0.123',
|
||||
'--asdf', '{"hello":"world"}',
|
||||
'--testety', 'hello',
|
||||
]
|
||||
let store = new Nconf.Argv({ parseValues: true })
|
||||
store.load()
|
||||
assert.deepStrictEqual(store.get(), {
|
||||
foo: 123,
|
||||
bar: 0.123,
|
||||
asdf: {
|
||||
hello: 'world',
|
||||
},
|
||||
testety: 'hello',
|
||||
})
|
||||
})
|
||||
|
||||
t.test('should support usage of equal sign instead', function() {
|
||||
process.argv = ['bla', 'bla',
|
||||
'--foo=123',
|
||||
'--testety=hello',
|
||||
]
|
||||
let store = new Nconf.Argv({ useEqualsign: true })
|
||||
store.load()
|
||||
assert.deepStrictEqual(store.get(), {
|
||||
foo: '123',
|
||||
testety: 'hello',
|
||||
})
|
||||
})
|
||||
|
||||
t.test('should be smart with the usage of equal sign', function() {
|
||||
process.argv = ['bla', 'bla',
|
||||
'--foo=hello=world',
|
||||
]
|
||||
let store = new Nconf.Argv({ useEqualsign: true })
|
||||
store.load()
|
||||
assert.strictEqual(store.get('foo'), 'hello=world')
|
||||
})
|
||||
})
|
|
@ -1,28 +0,0 @@
|
|||
/*
|
||||
* env-test.js: Tests for the nconf env store.
|
||||
*
|
||||
* (C) 2011, Charlie Robbins and the Contributors.
|
||||
*
|
||||
*/
|
||||
|
||||
var nconf = require('../../lib/nconf');
|
||||
|
||||
describe('nconf/stores/env, An instance of nconf.Env', () => {
|
||||
it("should have the correct methods defined", () => {
|
||||
var env = new nconf.Env();
|
||||
expect(typeof env.loadSync).toBe('function');
|
||||
expect(typeof env.loadEnv).toBe('function');
|
||||
expect(env.whitelist instanceof Array).toBeTruthy();
|
||||
expect(env.whitelist.length).toEqual(0);
|
||||
expect(env.separator).toEqual('');
|
||||
});
|
||||
it("should have the correct methods defined and with readOnly false", () => {
|
||||
var env = new nconf.Env({readOnly: false});
|
||||
expect(typeof env.loadSync).toBe('function');
|
||||
expect(typeof env.loadEnv).toBe('function');
|
||||
expect(env.whitelist instanceof Array).toBeTruthy();
|
||||
expect(env.whitelist.length).toEqual(0);
|
||||
expect(env.separator).toEqual('');
|
||||
expect(env.readOnly).toBe(false);
|
||||
});
|
||||
});
|
245
test/stores/env.test.mjs
Normal file
245
test/stores/env.test.mjs
Normal file
|
@ -0,0 +1,245 @@
|
|||
import { Eltro as t, assert} from 'eltro'
|
||||
import Nconf from '../../lib/nconf.mjs'
|
||||
|
||||
t.describe('#load()', () => {
|
||||
let backupEnv = {}
|
||||
let testEnv = {
|
||||
SOMETHING: 'foobar',
|
||||
SOMEBOOL: 'true',
|
||||
SOMENULL: 'null',
|
||||
SOMEUNDEF: 'undefined',
|
||||
SOMEINT: '3600',
|
||||
SOMEFLOAT: '0.5',
|
||||
SOMEBAD: '5.1a',
|
||||
ANOTHER__TEST__THIS: 'foobar',
|
||||
}
|
||||
|
||||
t.before(function() {
|
||||
Object.keys(testEnv).forEach(function (key) {
|
||||
if (process.env[key]) backupEnv[key] = process.env[key]
|
||||
process.env[key] = testEnv[key]
|
||||
})
|
||||
})
|
||||
|
||||
t.test("should default read everything as string", function() {
|
||||
let store = new Nconf.Env()
|
||||
store.load()
|
||||
|
||||
assert.strictEqual(store.get('SOMETHING'), 'foobar')
|
||||
assert.strictEqual(store.get('SOMEBOOL'), 'true')
|
||||
assert.strictEqual(store.get('SOMENULL'), 'null')
|
||||
assert.strictEqual(store.get('SOMEUNDEF'), 'undefined')
|
||||
assert.strictEqual(store.get('SOMEINT'), '3600')
|
||||
assert.strictEqual(store.get('SOMEFLOAT'), '0.5')
|
||||
assert.strictEqual(store.get('SOMEBAD'), '5.1a')
|
||||
assert.strictEqual(store.get('ANOTHER__TEST__THIS'), 'foobar')
|
||||
})
|
||||
|
||||
t.test("should support parseValues correctly", function() {
|
||||
let store = new Nconf.Env({parseValues: true})
|
||||
store.load()
|
||||
|
||||
assert.strictEqual(store.get('SOMETHING'), 'foobar')
|
||||
assert.strictEqual(store.get('SOMEBOOL'), true)
|
||||
assert.notStrictEqual(store.get('SOMEBOOL'), 'true')
|
||||
assert.strictEqual(store.get('SOMENULL'), null)
|
||||
assert.strictEqual(store.get('SOMEUNDEF'), undefined)
|
||||
assert.strictEqual(store.get('SOMEINT'), 3600)
|
||||
assert.strictEqual(store.get('SOMEFLOAT'), .5)
|
||||
assert.strictEqual(store.get('SOMEBAD'), '5.1a')
|
||||
assert.strictEqual(store.get('ANOTHER__TEST__THIS'), 'foobar')
|
||||
})
|
||||
|
||||
t.test("should support lowercase", function() {
|
||||
let store = new Nconf.Env({lowerCase: true})
|
||||
store.load()
|
||||
|
||||
assert.notOk(store.get('SOMETHING'))
|
||||
assert.notOk(store.get('SOMEBOOL'))
|
||||
assert.notOk(store.get('SOMENULL'))
|
||||
assert.notOk(store.get('SOMEUNDEF'))
|
||||
assert.notOk(store.get('SOMEINT'))
|
||||
assert.notOk(store.get('SOMEFLOAT'))
|
||||
assert.notOk(store.get('SOMEBAD'))
|
||||
assert.notOk(store.get('ANOTHER__TEST__THIS'))
|
||||
assert.strictEqual(store.get('something'), 'foobar')
|
||||
assert.strictEqual(store.get('somebool'), 'true')
|
||||
assert.strictEqual(store.get('somenull'), 'null')
|
||||
assert.strictEqual(store.get('someundef'), 'undefined')
|
||||
assert.strictEqual(store.get('someint'), '3600')
|
||||
assert.strictEqual(store.get('somefloat'), '0.5')
|
||||
assert.strictEqual(store.get('somebad'), '5.1a')
|
||||
assert.strictEqual(store.get('another__test__this'), 'foobar')
|
||||
})
|
||||
|
||||
t.test("should support transform", function() {
|
||||
let store = new Nconf.Env({transform: function(key, value) {
|
||||
if (!testEnv[key]) return null
|
||||
return {
|
||||
key: key[0].toUpperCase() + key.slice(1).toLowerCase(),
|
||||
value: 1,
|
||||
}
|
||||
}})
|
||||
store.load()
|
||||
|
||||
assert.strictEqual(store.get('Something'), 1)
|
||||
assert.strictEqual(store.get('Somebool'), 1)
|
||||
assert.strictEqual(store.get('Somenull'), 1)
|
||||
assert.strictEqual(store.get('Someundef'), 1)
|
||||
assert.strictEqual(store.get('Someint'), 1)
|
||||
assert.strictEqual(store.get('Somefloat'), 1)
|
||||
assert.strictEqual(store.get('Somebad'), 1)
|
||||
assert.strictEqual(store.get('Another__test__this'), 1)
|
||||
|
||||
assert.deepStrictEqual(store.get(), {
|
||||
Something: 1,
|
||||
Somebool: 1,
|
||||
Somenull: 1,
|
||||
Someundef: 1,
|
||||
Someint: 1,
|
||||
Somefloat: 1,
|
||||
Somebad: 1,
|
||||
Another__test__this: 1,
|
||||
})
|
||||
})
|
||||
|
||||
t.test("should support matches", function() {
|
||||
let store = new Nconf.Env({match: /^SOME/})
|
||||
store.load()
|
||||
|
||||
assert.strictEqual(store.get('SOMETHING'), 'foobar')
|
||||
assert.strictEqual(store.get('SOMEBOOL'), 'true')
|
||||
assert.strictEqual(store.get('SOMENULL'), 'null')
|
||||
assert.strictEqual(store.get('SOMEUNDEF'), 'undefined')
|
||||
assert.strictEqual(store.get('SOMEINT'), '3600')
|
||||
assert.strictEqual(store.get('SOMEFLOAT'), '0.5')
|
||||
assert.strictEqual(store.get('SOMEBAD'), '5.1a')
|
||||
assert.notOk(store.get('Another__test__this'))
|
||||
|
||||
assert.deepStrictEqual(store.get(), {
|
||||
SOMETHING: 'foobar',
|
||||
SOMEBOOL: 'true',
|
||||
SOMENULL: 'null',
|
||||
SOMEUNDEF: 'undefined',
|
||||
SOMEINT: '3600',
|
||||
SOMEFLOAT: '0.5',
|
||||
SOMEBAD: '5.1a',
|
||||
})
|
||||
})
|
||||
|
||||
t.test("should support whitelist", function() {
|
||||
let store = new Nconf.Env({whitelist: ['ANOTHER__TEST__THIS']})
|
||||
store.load()
|
||||
|
||||
assert.notOk(store.get('SOMETHING'), 'foobar')
|
||||
assert.notOk(store.get('SOMEBOOL'), 'true')
|
||||
assert.notOk(store.get('SOMENULL'), 'null')
|
||||
assert.notOk(store.get('SOMEUNDEF'), 'undefined')
|
||||
assert.notOk(store.get('SOMEINT'), '3600')
|
||||
assert.notOk(store.get('SOMEFLOAT'), '0.5')
|
||||
assert.notOk(store.get('SOMEBAD'), '5.1a')
|
||||
assert.strictEqual(store.get('ANOTHER__TEST__THIS'), 'foobar')
|
||||
|
||||
assert.deepStrictEqual(store.get(), {
|
||||
ANOTHER__TEST__THIS: 'foobar',
|
||||
})
|
||||
})
|
||||
|
||||
t.test("whitelist should be case insensitive", function() {
|
||||
let store = new Nconf.Env({whitelist: ['another__test__this']})
|
||||
store.load()
|
||||
|
||||
assert.strictEqual(store.get('ANOTHER__TEST__THIS'), 'foobar')
|
||||
|
||||
assert.deepStrictEqual(store.get(), {
|
||||
ANOTHER__TEST__THIS: 'foobar',
|
||||
})
|
||||
})
|
||||
|
||||
t.test("should support whitelist with match", function() {
|
||||
let store = new Nconf.Env({
|
||||
whitelist: ['another__test__this'],
|
||||
match: /^SOMEBOOL/,
|
||||
})
|
||||
store.load()
|
||||
|
||||
assert.strictEqual(store.get('ANOTHER__TEST__THIS'), 'foobar')
|
||||
assert.strictEqual(store.get('SOMEBOOL'), 'true')
|
||||
|
||||
assert.deepStrictEqual(store.get(), {
|
||||
ANOTHER__TEST__THIS: 'foobar',
|
||||
SOMEBOOL: 'true',
|
||||
})
|
||||
})
|
||||
|
||||
t.test("should support custom seperator", function() {
|
||||
let store = new Nconf.Env({
|
||||
whitelist: ['another__test__this', 'somebool'],
|
||||
separator: '__',
|
||||
})
|
||||
store.load()
|
||||
|
||||
assert.strictEqual(store.get('ANOTHER:TEST:THIS'), 'foobar')
|
||||
assert.strictEqual(store.get('SOMEBOOL'), 'true')
|
||||
|
||||
assert.deepStrictEqual(store.get(), {
|
||||
ANOTHER: {
|
||||
TEST: {
|
||||
THIS: 'foobar',
|
||||
},
|
||||
},
|
||||
SOMEBOOL: 'true',
|
||||
})
|
||||
})
|
||||
|
||||
t.test("should stay readOnly always", function() {
|
||||
let store = new Nconf.Env({whitelist: ['another__test__this']})
|
||||
assert.strictEqual(store.readOnly, true)
|
||||
store.load()
|
||||
assert.strictEqual(store.readOnly, true)
|
||||
})
|
||||
|
||||
t.test("should throw if whitelist is invalid", function() {
|
||||
assert.throws(function() {
|
||||
new Nconf.Env({whitelist: 'another__test__this'})
|
||||
}, /[Ww]hitelist.+[Aa]rray/)
|
||||
|
||||
assert.throws(function() {
|
||||
new Nconf.Env({whitelist: ['another__test__this', 123]})
|
||||
}, /[Ww]hitelist.+[Aa]rray/)
|
||||
})
|
||||
|
||||
t.test("should throw if match is invalid", function() {
|
||||
assert.throws(function() {
|
||||
new Nconf.Env({match: 1234})
|
||||
}, /[Mm]atch.+[Rr]eg[Ee]xp/)
|
||||
|
||||
assert.throws(function() {
|
||||
new Nconf.Env({match: {}})
|
||||
}, /[Mm]atch.+[Rr]eg[Ee]xp/)
|
||||
})
|
||||
|
||||
t.test("should automatically convert string match to RegExp", function() {
|
||||
let store = new Nconf.Env({match: 'asdf'})
|
||||
assert.ok(store.match)
|
||||
assert.ok(store.match.test('asdf'))
|
||||
assert.notOk(store.match.test('test'))
|
||||
})
|
||||
|
||||
t.test("should support whitelist directly in parameter", function() {
|
||||
let store = new Nconf.Env(['another__test__this'])
|
||||
store.load()
|
||||
|
||||
assert.strictEqual(store.get('ANOTHER__TEST__THIS'), 'foobar')
|
||||
|
||||
assert.deepStrictEqual(store.get(), {
|
||||
ANOTHER__TEST__THIS: 'foobar',
|
||||
})
|
||||
})
|
||||
|
||||
t.after(function() {
|
||||
Object.keys(backupEnv).forEach(function (key) {
|
||||
process.env[key] = backupEnv[key]
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,259 +0,0 @@
|
|||
/*
|
||||
* file-store-test.js: Tests for the nconf File store.
|
||||
*
|
||||
* (C) 2011, Charlie Robbins and the Contributors.
|
||||
*
|
||||
*/
|
||||
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var nconf = require('../../lib/nconf');
|
||||
var yamlFormat = require('nconf-yaml');
|
||||
var data = require('../fixtures/data').data;
|
||||
|
||||
// FIXME TO RENAME
|
||||
describe('nconf/stores/file', () => {
|
||||
describe("When using the nconf file store", () => {
|
||||
describe("with a valid JSON file", () => {
|
||||
var filePath = path.join(__dirname, '..', 'fixtures', 'store.json');
|
||||
fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
|
||||
|
||||
it("the load() method should load the data correctly", done => {
|
||||
var store = new nconf.File({file: filePath});
|
||||
store.load((err, data) => {
|
||||
expect(err).toBe(null);
|
||||
expect(data).toEqual(store.store);
|
||||
done();
|
||||
})
|
||||
});
|
||||
});
|
||||
describe("with a malformed JSON file", () => {
|
||||
var filePath = path.join(__dirname, '..', 'fixtures', 'malformed.json');
|
||||
|
||||
it("the load() method with a malformed JSON config file, should respond with an error and indicate file name",
|
||||
done => {
|
||||
var store = new nconf.File({file: filePath});
|
||||
//FIXME this.store.load(this.callback.bind(null, null));
|
||||
store.load((err) => {
|
||||
expect(err).toBeTruthy();
|
||||
expect(err.message).toMatch(/malformed\.json/);
|
||||
done();
|
||||
})
|
||||
});
|
||||
});
|
||||
describe("with a valid UTF8 JSON file that contains a BOM", () => {
|
||||
var filePath = path.join(__dirname, '..', 'fixtures', 'bom.json');
|
||||
var store = new nconf.File({file: filePath});
|
||||
|
||||
it("the load() method should load the data correctly", done => {
|
||||
store.load((err, data) => {
|
||||
expect(err).toBe(null);
|
||||
expect(data).toEqual(store.store);
|
||||
done();
|
||||
});
|
||||
});
|
||||
it("the loadSync() method should load the data correctly", () => {
|
||||
var data = store.loadSync();
|
||||
expect(data).toEqual(store.store);
|
||||
});
|
||||
});
|
||||
describe("with a valid UTF8 JSON file that contains no BOM", () => {
|
||||
var filePath = path.join(__dirname, '..', 'fixtures', 'no-bom.json');
|
||||
var store = new nconf.File({file: filePath});
|
||||
|
||||
it("the load() method should load the data correctly", done => {
|
||||
store.load((err, data) => {
|
||||
expect(err).toBe(null);
|
||||
expect(data).toEqual(store.store);
|
||||
done();
|
||||
});
|
||||
});
|
||||
it("the loadSync() method should load the data correctly", () => {
|
||||
var data = store.loadSync();
|
||||
expect(data).toEqual(store.store);
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
describe("When using the nconf file store", () => {
|
||||
var tmpPath = path.join(__dirname, '..', 'fixtures', 'tmp.json');
|
||||
|
||||
it("the save() method should save the data correctly", done => {
|
||||
var tmpStore = new nconf.File({file: tmpPath});
|
||||
|
||||
Object.keys(data).forEach(function (key) {
|
||||
tmpStore.set(key, data[key]);
|
||||
});
|
||||
|
||||
tmpStore.save(function () {
|
||||
fs.readFile(tmpStore.file, function (err, d) {
|
||||
fs.unlinkSync(tmpStore.file);
|
||||
|
||||
expect(err).toBe(null);
|
||||
expect(JSON.parse(d.toString())).toEqual(data);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
it("the saveToFile() method should save the data correctly", done => {
|
||||
var tmpStore = new nconf.File({file: tmpPath});
|
||||
var pathFile = path.join(__dirname, '..', 'fixtures', 'tmp-save-tofile.json');
|
||||
|
||||
Object.keys(data).forEach(function (key) {
|
||||
tmpStore.set(key, data[key]);
|
||||
});
|
||||
|
||||
tmpStore.saveToFile(pathFile, function () {
|
||||
fs.readFile(pathFile, function (err, d) {
|
||||
fs.unlinkSync(pathFile);
|
||||
|
||||
expect(err).toBe(null);
|
||||
expect(JSON.parse(d.toString())).toEqual(data);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
it("the saveToFile() method with custom format should save the data correctly", done => {
|
||||
var tmpStore = new nconf.File({file: tmpPath});
|
||||
var pathFile = path.join(__dirname, '..', 'fixtures', 'tmp-save-tofile.yaml');
|
||||
|
||||
Object.keys(data).forEach(function (key) {
|
||||
tmpStore.set(key, data[key]);
|
||||
});
|
||||
|
||||
tmpStore.saveToFile(pathFile, yamlFormat, function () {
|
||||
fs.readFile(pathFile, function (err, d) {
|
||||
fs.unlinkSync(pathFile);
|
||||
|
||||
expect(err).toBe(null);
|
||||
expect(yamlFormat.parse(d.toString())).toEqual(data);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("When using the nconf file store", () => {
|
||||
var tmpPath = path.join(__dirname, '..', 'fixtures', 'tmp.json');
|
||||
it("the saveSync() method should save the data correctly", done => {
|
||||
var tmpStore = new nconf.File({file: tmpPath});
|
||||
Object.keys(data).forEach(function (key) {
|
||||
tmpStore.set(key, data[key]);
|
||||
});
|
||||
|
||||
var saved = tmpStore.saveSync();
|
||||
|
||||
fs.readFile(tmpStore.file, function (err, d) {
|
||||
fs.unlinkSync(tmpStore.file);
|
||||
|
||||
expect(err).toBe(null);
|
||||
var read = JSON.parse(d.toString());
|
||||
expect(read).toEqual(data);
|
||||
expect(read).toEqual(saved);
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
describe("When using the nconf file store", () => {
|
||||
var tmpPath = path.join(__dirname, '..', 'fixtures', 'tmp.json');
|
||||
var store = new nconf.File({file: tmpPath});
|
||||
|
||||
it("the set() method should respond with true", () => {
|
||||
expect(store.set('foo:bar:bazz', 'buzz')).toBeTruthy();
|
||||
expect(store.set('falsy:number', 0)).toBeTruthy();
|
||||
expect(store.set('falsy:string', '')).toBeTruthy();
|
||||
expect(store.set('falsy:boolean', false)).toBeTruthy();
|
||||
expect(store.set('falsy:object', null)).toBeTruthy();
|
||||
});
|
||||
it("the get() method should respond with the correct value", () => {
|
||||
expect(store.get('foo:bar:bazz')).toEqual('buzz');
|
||||
expect(store.get('falsy:number')).toEqual(0);
|
||||
expect(store.get('falsy:string')).toEqual('');
|
||||
expect(store.get('falsy:boolean')).toEqual(false);
|
||||
expect(store.get('falsy:object')).toEqual(null);
|
||||
});
|
||||
it("the clear() method should respond with the true", () => {
|
||||
expect(store.get('foo:bar:bazz')).toEqual('buzz');
|
||||
expect(store.clear('foo:bar:bazz')).toBeTruthy();
|
||||
expect(typeof store.get('foo:bar:bazz') === 'undefined').toBeTruthy();
|
||||
});
|
||||
});
|
||||
describe("When using the nconf file store", () => {
|
||||
|
||||
it("the search() method when the target file exists higher in the directory tree should update the file appropriately", () => {
|
||||
var searchBase = require('os').homedir();
|
||||
var filePath = path.join(searchBase, '.nconf');
|
||||
fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
|
||||
var store = new nconf.File({
|
||||
file: '.nconf'
|
||||
});
|
||||
store.search(store.searchBase);
|
||||
expect(store.file).toEqual(filePath);
|
||||
fs.unlinkSync(filePath);
|
||||
});
|
||||
it("the search() method when the target file doesn't exist higher in the directory tree should update the file appropriately", () => {
|
||||
var filePath = path.join(__dirname, '..', 'fixtures', 'search-store.json');
|
||||
var store = new nconf.File({
|
||||
dir: path.dirname(filePath),
|
||||
file: 'search-store.json'
|
||||
});
|
||||
store.search();
|
||||
expect(store.file).toEqual(filePath);
|
||||
});
|
||||
|
||||
})
|
||||
describe("When using the nconf file store", () => {
|
||||
var secureStore = new nconf.File({
|
||||
file: path.join(__dirname, '..', 'fixtures', 'secure-iv.json'),
|
||||
secure: 'super-secret-key-32-characterszz'
|
||||
});
|
||||
|
||||
secureStore.store = data;
|
||||
|
||||
it("the stringify() method should encrypt properly", () => {
|
||||
var contents = JSON.parse(secureStore.stringify());
|
||||
Object.keys(data).forEach(key => {
|
||||
expect(typeof contents[key]).toBe('object');
|
||||
expect(typeof contents[key].value).toBe('string');
|
||||
expect(contents[key].alg).toEqual('aes-256-ctr');
|
||||
expect(typeof contents[key].iv).toBe('string');
|
||||
});
|
||||
});
|
||||
it("the parse() method should decrypt properly", () => {
|
||||
var contents = secureStore.stringify();
|
||||
var parsed = secureStore.parse(contents);
|
||||
expect(parsed).toEqual(data);
|
||||
});
|
||||
it("the load() method should decrypt properly", () => {
|
||||
secureStore.load(function (err, loaded) {
|
||||
expect(err).toBe(null);
|
||||
expect(loaded).toEqual(data);
|
||||
});
|
||||
});
|
||||
it("the loadSync() method should decrypt properly", () => {
|
||||
var loaded = secureStore.loadSync();
|
||||
expect(loaded).toEqual(data);
|
||||
});
|
||||
})
|
||||
|
||||
describe("When using the nconf file store", () => {
|
||||
var secureStore = new nconf.File({
|
||||
file: path.join(__dirname, '..', 'fixtures', 'secure.json'),
|
||||
secure: 'super-secretzzz'
|
||||
});
|
||||
|
||||
it("the load() method should decrypt legacy file properly", () => {
|
||||
secureStore.load(function (err, loaded) {
|
||||
expect(err).toBe(null);
|
||||
expect(loaded).toEqual(data);
|
||||
});
|
||||
});
|
||||
it("the loadSync() method should decrypt legacy file properly", () => {
|
||||
var loaded = secureStore.loadSync();
|
||||
expect(loaded).toEqual(data);
|
||||
});
|
||||
})
|
||||
|
||||
});
|
250
test/stores/file.test.mjs
Normal file
250
test/stores/file.test.mjs
Normal file
|
@ -0,0 +1,250 @@
|
|||
import fs from 'fs'
|
||||
import { Eltro as t, assert} from 'eltro'
|
||||
import * as helpers from '../helpers.mjs'
|
||||
import { data } from '../fixtures/data.mjs'
|
||||
import Nconf from '../../lib/nconf.mjs'
|
||||
|
||||
const fsPromise = fs.promises
|
||||
|
||||
// let yamlFormat = require('nconf-yaml')
|
||||
|
||||
t.describe('#load()', function() {
|
||||
let validFile = helpers.fixture('store.json')
|
||||
fs.writeFileSync(validFile, JSON.stringify(data, null, 2))
|
||||
let malformedFile = helpers.fixture('malformed.json')
|
||||
let bomFile = helpers.fixture('bom.json')
|
||||
let noBomPath = helpers.fixture('no-bom.json')
|
||||
|
||||
t.test('with valid json should load correctly', function() {
|
||||
let store = new Nconf.File({file: validFile})
|
||||
store.load()
|
||||
assert.deepStrictEqual(store.store, data)
|
||||
})
|
||||
|
||||
t.test('with only file string as option', function() {
|
||||
let store = new Nconf.File(validFile)
|
||||
store.load()
|
||||
assert.deepStrictEqual(store.store, data)
|
||||
})
|
||||
|
||||
t.test('malformed JSON should respond with an error and indicate file name', function() {
|
||||
let store = new Nconf.File({file: malformedFile})
|
||||
assert.throws(() => {
|
||||
store.load()
|
||||
}, /malformed\.json/)
|
||||
})
|
||||
|
||||
t.test('with a valid UTF8 JSON file that contains a BOM', function() {
|
||||
let store = new Nconf.File(bomFile)
|
||||
store.load()
|
||||
assert.strictEqual(store.get('port'), 78304)
|
||||
assert.strictEqual(store.get('host'), 'weebls-stuff.com')
|
||||
})
|
||||
|
||||
t.test('with a valid UTF8 JSON file that contains no BOM', function() {
|
||||
let store = new Nconf.File(noBomPath)
|
||||
store.load()
|
||||
assert.strictEqual(store.get('port'), 78304)
|
||||
assert.strictEqual(store.get('host'), 'weebls-stuff.com')
|
||||
})
|
||||
})
|
||||
|
||||
t.describe('#loadAsync()', function() {
|
||||
let validFile = helpers.fixture('store.json')
|
||||
fs.writeFileSync(validFile, JSON.stringify(data, null, 2))
|
||||
let malformedFile = helpers.fixture('malformed.json')
|
||||
let bomFile = helpers.fixture('bom.json')
|
||||
let noBomPath = helpers.fixture('no-bom.json')
|
||||
|
||||
t.test('with valid json should load correctly', function() {
|
||||
let store = new Nconf.File({file: validFile})
|
||||
return store.loadAsync().then(function(newData) {
|
||||
assert.strictEqual(newData, store.store)
|
||||
assert.deepStrictEqual(store.store, data)
|
||||
})
|
||||
})
|
||||
|
||||
t.test('with only file string as option', function() {
|
||||
let store = new Nconf.File(validFile)
|
||||
return store.loadAsync().then(function() {
|
||||
assert.deepStrictEqual(store.store, data)
|
||||
})
|
||||
})
|
||||
|
||||
t.test('malformed JSON should respond with an error and indicate file name', function() {
|
||||
let store = new Nconf.File({file: malformedFile})
|
||||
assert.isRejected(store.loadAsync()).then(function(err) {
|
||||
assert.match(err.message, /malformed\.json/)
|
||||
})
|
||||
})
|
||||
|
||||
t.test('with a valid UTF8 JSON file that contains a BOM', function() {
|
||||
let store = new Nconf.File(bomFile)
|
||||
return store.loadAsync().then(function() {
|
||||
assert.strictEqual(store.get('port'), 78304)
|
||||
assert.strictEqual(store.get('host'), 'weebls-stuff.com')
|
||||
})
|
||||
})
|
||||
|
||||
t.test('with a valid UTF8 JSON file that contains no BOM', function() {
|
||||
let store = new Nconf.File(noBomPath)
|
||||
return store.loadAsync().then(function() {
|
||||
assert.strictEqual(store.get('port'), 78304)
|
||||
assert.strictEqual(store.get('host'), 'weebls-stuff.com')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t.describe('#save()', function() {
|
||||
let testPath = helpers.fixture('tmp.json')
|
||||
let testSecondPath = helpers.fixture('tmp2.json')
|
||||
|
||||
t.test('should save the data correctly to original file specified', function() {
|
||||
let store = new Nconf.File({file: testPath})
|
||||
|
||||
Object.keys(data).forEach(function (key) {
|
||||
store.set(key, data[key])
|
||||
})
|
||||
|
||||
assert.strictEqual(store.save(), store)
|
||||
|
||||
let readData = JSON.parse(fs.readFileSync(store.file))
|
||||
assert.deepStrictEqual(readData, data)
|
||||
})
|
||||
|
||||
t.test('should save the data to specified file', function() {
|
||||
let store = new Nconf.File({file: testPath})
|
||||
|
||||
Object.keys(data).forEach(function (key) {
|
||||
store.set(key, data[key])
|
||||
})
|
||||
|
||||
store.save(testSecondPath)
|
||||
|
||||
let readData = JSON.parse(fs.readFileSync(testSecondPath))
|
||||
assert.deepStrictEqual(readData, data)
|
||||
})
|
||||
|
||||
t.after(function() {
|
||||
return Promise.all([
|
||||
fsPromise.unlink(testPath),
|
||||
fsPromise.unlink(testSecondPath),
|
||||
]).catch(function() {})
|
||||
})
|
||||
})
|
||||
|
||||
t.describe('#saveAsync()', function() {
|
||||
let testPath = helpers.fixture('tmp.json')
|
||||
let testSecondPath = helpers.fixture('tmp2.json')
|
||||
|
||||
t.test('should save the data correctly to original file specified', function() {
|
||||
let store = new Nconf.File({file: testPath})
|
||||
|
||||
Object.keys(data).forEach(function (key) {
|
||||
store.set(key, data[key])
|
||||
})
|
||||
|
||||
return store.saveAsync().then(function(checkStore) {
|
||||
assert.strictEqual(checkStore, store)
|
||||
return fsPromise.readFile(store.file)
|
||||
}).then(function(readData) {
|
||||
assert.deepStrictEqual(JSON.parse(readData), data)
|
||||
})
|
||||
})
|
||||
|
||||
t.test('should save the data to specified file', function() {
|
||||
let store = new Nconf.File({file: testPath})
|
||||
|
||||
Object.keys(data).forEach(function (key) {
|
||||
store.set(key, data[key])
|
||||
})
|
||||
|
||||
return store.saveAsync(testSecondPath).then(function() {
|
||||
return fsPromise.readFile(testSecondPath)
|
||||
}).then(function(readData) {
|
||||
assert.deepStrictEqual(JSON.parse(readData), data)
|
||||
})
|
||||
})
|
||||
|
||||
t.after(function() {
|
||||
return Promise.all([
|
||||
fsPromise.unlink(testPath),
|
||||
fsPromise.unlink(testSecondPath),
|
||||
]).catch(function() {})
|
||||
})
|
||||
})
|
||||
|
||||
t.describe('#secure', function() {
|
||||
t.test('the stringify() method should encrypt properly', function() {
|
||||
let secureStore = new Nconf.File({
|
||||
file: helpers.fixture('secure-iv.json'),
|
||||
secure: 'super-secret-key-32-characterszz'
|
||||
})
|
||||
secureStore.store = data
|
||||
|
||||
let contents = JSON.parse(secureStore.stringify())
|
||||
Object.keys(data).forEach(key => {
|
||||
assert.strictEqual(typeof(contents[key]), 'object')
|
||||
assert.strictEqual(typeof(contents[key].value), 'string')
|
||||
assert.strictEqual(contents[key].alg, 'aes-256-ctr')
|
||||
assert.strictEqual(typeof(contents[key].iv), 'string')
|
||||
})
|
||||
})
|
||||
|
||||
t.test('the parse() method should decrypt properly', function() {
|
||||
let secureStore = new Nconf.File({
|
||||
file: helpers.fixture('secure-iv.json'),
|
||||
secure: 'super-secret-key-32-characterszz'
|
||||
})
|
||||
secureStore.store = data
|
||||
|
||||
let contents = secureStore.stringify()
|
||||
let parsed = secureStore.parse(contents)
|
||||
assert.deepStrictEqual(parsed, data)
|
||||
})
|
||||
|
||||
t.test('the load() method should decrypt properly', function() {
|
||||
let secureStore = new Nconf.File({
|
||||
file: helpers.fixture('secure-iv.json'),
|
||||
secure: 'super-secret-key-32-characterszz'
|
||||
})
|
||||
secureStore.load()
|
||||
assert.deepStrictEqual(secureStore.store, data)
|
||||
})
|
||||
|
||||
t.test('it should throw error on legacy encrypted files', function() {
|
||||
let secureStore = new Nconf.File({
|
||||
file: helpers.fixture('secure.json'),
|
||||
secure: 'super-secretzzz'
|
||||
})
|
||||
|
||||
assert.throws(function() {
|
||||
secureStore.load()
|
||||
}, /[Oo]utdated/)
|
||||
})
|
||||
})
|
||||
|
||||
/*
|
||||
|
||||
t.test('the search() method when the target file exists higher in the directory tree should update the file appropriately', function() {
|
||||
let searchBase = require('os').homedir()
|
||||
let filePath = path.join(searchBase, '.nconf')
|
||||
fs.writeFileSync(filePath, JSON.stringify(data, null, 2))
|
||||
let store = new Nconf.File({
|
||||
file: '.nconf'
|
||||
})
|
||||
store.search(store.searchBase)
|
||||
expect(store.file).toEqual(filePath)
|
||||
fs.unlinkSync(filePath)
|
||||
})
|
||||
t.test('the search() method when the target file doesn't exist higher in the directory tree should update the file appropriately', function() {
|
||||
let filePath = helpers.fixture('search-store.json')
|
||||
let store = new Nconf.File({
|
||||
dir: path.dirname(filePath),
|
||||
file: 'search-store.json'
|
||||
})
|
||||
store.search()
|
||||
expect(store.file).toEqual(filePath)
|
||||
})
|
||||
|
||||
*/
|
|
@ -1,25 +0,0 @@
|
|||
/*
|
||||
* literal-test.js: Tests for the nconf literal store.
|
||||
*
|
||||
* (C) 2011, Charlie Robbins and the Contributors.
|
||||
*
|
||||
*/
|
||||
|
||||
var nconf = require('../../lib/nconf');
|
||||
|
||||
describe('nconf/stores/literal, An instance of nconf.Literal', () => {
|
||||
var envOptions = {foo: 'bar', one: 2};
|
||||
it("should have the correct methods defined", () => {
|
||||
var literal = new nconf.Literal(envOptions);
|
||||
expect(literal.type).toEqual('literal');
|
||||
expect(typeof literal.get).toBe('function');
|
||||
expect(typeof literal.set).toBe('function');
|
||||
expect(typeof literal.merge).toBe('function');
|
||||
expect(typeof literal.loadSync).toBe('function');
|
||||
});
|
||||
it("should have the correct values in the store", () => {
|
||||
var literal = new nconf.Literal(envOptions);
|
||||
expect(literal.store.foo).toEqual('bar');
|
||||
expect(literal.store.one).toEqual(2);
|
||||
});
|
||||
});
|
24
test/stores/literal.test.mjs
Normal file
24
test/stores/literal.test.mjs
Normal file
|
@ -0,0 +1,24 @@
|
|||
import { Eltro as t, assert} from 'eltro'
|
||||
import Nconf from '../../lib/nconf.mjs'
|
||||
|
||||
t.describe('nconf/stores/literal, An instance of nconf.Literal', function () {
|
||||
var envOptions = {foo: 'bar', one: 2}
|
||||
|
||||
t.test("should have the correct methods defined", function () {
|
||||
var literal = new Nconf.Literal()
|
||||
assert.ok(literal.readOnly)
|
||||
assert.strictEqual(literal.type, 'literal')
|
||||
assert.strictEqual(typeof(literal.get), 'function')
|
||||
assert.strictEqual(typeof(literal.set), 'function')
|
||||
assert.strictEqual(typeof(literal.merge), 'function')
|
||||
assert.strictEqual(typeof(literal.loadSync), 'function')
|
||||
})
|
||||
|
||||
t.test("should have the correct values in the store", function () {
|
||||
var literal = new Nconf.Literal(envOptions)
|
||||
assert.strictEqual(literal.store.foo, 'bar')
|
||||
assert.strictEqual(literal.store.one, 2)
|
||||
assert.notOk(literal.set('foo', 'foo'))
|
||||
assert.strictEqual(literal.store.foo, 'bar')
|
||||
})
|
||||
})
|
|
@ -1,114 +0,0 @@
|
|||
/*
|
||||
* memory-store-test.js: Tests for the nconf Memory store.
|
||||
*
|
||||
* (C) 2011, Charlie Robbins and the Contributors.
|
||||
*
|
||||
*/
|
||||
|
||||
var nconf = require('../../lib/nconf');
|
||||
var merge = require('../fixtures/data').merge;
|
||||
|
||||
describe('nconf/stores/memory', () => {
|
||||
describe("When using the nconf memory store", () => {
|
||||
var store = new nconf.Memory();
|
||||
it("the set() method should respond with true", () => {
|
||||
expect(store.set('foo:bar:bazz', 'buzz')).toBeTruthy();
|
||||
expect(store.set('falsy:number', 0)).toBeTruthy();
|
||||
expect(store.set('falsy:string:empty', '')).toBeTruthy();
|
||||
expect(store.set('falsy:string:value', 'value')).toBeTruthy();
|
||||
expect(store.set('falsy:boolean', false)).toBeTruthy();
|
||||
expect(store.set('falsy:object', null)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("the get() method should respond with the correct value", () => {
|
||||
expect(store.get('foo:bar:bazz')).toEqual('buzz');
|
||||
expect(store.get('falsy:number')).toEqual(0);
|
||||
expect(store.get('falsy:string:empty')).toEqual('');
|
||||
expect(store.get('falsy:string:value')).toEqual('value');
|
||||
expect(store.get('falsy:boolean')).toEqual(false);
|
||||
expect(store.get('falsy:object')).toEqual(null);
|
||||
});
|
||||
|
||||
describe("the get() method should not fail when retrieving non-existent keys", () => {
|
||||
it("at the root level", () => {
|
||||
expect(store.get('this:key:does:not:exist')).toEqual(undefined);
|
||||
});
|
||||
|
||||
it("within numbers", () => {
|
||||
expect(store.get('falsy:number:not:exist')).toEqual(undefined);
|
||||
});
|
||||
|
||||
it("within booleans", () => {
|
||||
expect(store.get('falsy:boolean:not:exist')).toEqual(undefined);
|
||||
});
|
||||
|
||||
it("within objects", () => {
|
||||
expect(store.get('falsy:object:not:exist')).toEqual(undefined);
|
||||
});
|
||||
|
||||
it("within empty strings", () => {
|
||||
expect(store.get('falsy:string:empty:not:exist')).toEqual(undefined);
|
||||
});
|
||||
|
||||
it("within non-empty strings", () => {
|
||||
expect(store.get('falsy:string:value:not:exist')).toEqual(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
it("the clear() method, should respond with the true", () => {
|
||||
expect(store.get('foo:bar:bazz')).toEqual('buzz');
|
||||
expect(store.clear('foo:bar:bazz')).toBeTruthy();
|
||||
expect(typeof store.get('foo:bar:bazz') === 'undefined').toBeTruthy();
|
||||
});
|
||||
|
||||
describe("the merge() method", () => {
|
||||
it("when overriding an existing literal value", () => {
|
||||
store.set('merge:literal', 'string-value');
|
||||
store.merge('merge:literal', merge);
|
||||
expect(store.get('merge:literal')).toEqual(merge);
|
||||
});
|
||||
|
||||
it("when overriding an existing Array value", () => {
|
||||
store.set('merge:array', [1, 2, 3, 4]);
|
||||
store.merge('merge:array', merge);
|
||||
expect(store.get('merge:literal')).toEqual(merge);
|
||||
});
|
||||
|
||||
it("when merging into an existing Object value", () => {
|
||||
store.set('merge:object', {
|
||||
prop1: 2,
|
||||
prop2: 'prop2',
|
||||
prop3: {
|
||||
bazz: 'bazz'
|
||||
},
|
||||
prop4: ['foo', 'bar']
|
||||
});
|
||||
store.merge('merge:object', merge);
|
||||
|
||||
expect(store.get('merge:object:prop1')).toEqual(1);
|
||||
expect(store.get('merge:object:prop2').length).toEqual(3);
|
||||
expect(store.get('merge:object:prop3')).toEqual({
|
||||
foo: 'bar',
|
||||
bar: 'foo',
|
||||
bazz: 'bazz'
|
||||
});
|
||||
expect(store.get('merge:object:prop4').length).toEqual(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe("When using the nconf memory store with different logical separator", () => {
|
||||
var store = new nconf.Memory({logicalSeparator: '||'});
|
||||
|
||||
it("when storing with : (colon), should store the config atomicly", () => {
|
||||
store.set('foo:bar:bazz', 'buzz');
|
||||
expect(typeof store.get('foo:bar') === 'undefined').toBeTruthy();
|
||||
expect(store.get('foo:bar:bazz')).toEqual('buzz');
|
||||
});
|
||||
|
||||
it("when storing with separator, should be able to read the object", () => {
|
||||
store.set('foo||bar||bazz', 'buzz');
|
||||
expect(store.get('foo||bar').bazz).toEqual('buzz');
|
||||
expect(store.get('foo').bar.bazz).toEqual('buzz');
|
||||
})
|
||||
});
|
||||
});
|
379
test/stores/memory.test.mjs
Normal file
379
test/stores/memory.test.mjs
Normal file
|
@ -0,0 +1,379 @@
|
|||
import { Eltro as t, assert} from 'eltro'
|
||||
import Nconf from '../../lib/nconf.mjs'
|
||||
import { merge } from '../fixtures/data.mjs'
|
||||
|
||||
t.describe('Memory Store', function () {
|
||||
let store = new Nconf.Memory()
|
||||
|
||||
t.describe('#set()', function() {
|
||||
t.test('should return true', function () {
|
||||
assert.ok(store.set('foo:bar:bazz', 'buzz'))
|
||||
assert.ok(store.set('falsy:number', 0))
|
||||
assert.ok(store.set('falsy:string:empty', ''))
|
||||
assert.ok(store.set('falsy:string:value', 'value'))
|
||||
assert.ok(store.set('falsy:boolean', false))
|
||||
assert.ok(store.set('falsy:object', null))
|
||||
})
|
||||
|
||||
t.test('should support numbers as key', function() {
|
||||
assert.notOk(store.get('523453'))
|
||||
assert.ok(store.set(523453, true))
|
||||
assert.ok(store.get('523453'))
|
||||
})
|
||||
|
||||
t.test('should always make sure not store direct references to objects', function() {
|
||||
const assertArray = [ 1, 2 ]
|
||||
const assertObject = { a: 1 }
|
||||
|
||||
assert.ok(store.set('reference:test:arraydirect', assertArray))
|
||||
assert.notStrictEqual(store.get('reference:test:arraydirect'), assertArray)
|
||||
assert.ok(store.set('reference:test:objectdirect', assertObject))
|
||||
assert.notStrictEqual(store.get('reference:test:objectdirect'), assertObject)
|
||||
assert.ok(store.set('reference:test:objectchild', { x: assertArray, y: assertObject }))
|
||||
assert.notStrictEqual(store.get('reference:test:objectchild:x'), assertArray)
|
||||
assert.notStrictEqual(store.get('reference:test:objectchild:y'), assertObject)
|
||||
})
|
||||
|
||||
t.test('should support numbers as key', function() {
|
||||
let inStore = new Nconf.Memory()
|
||||
inStore.set({ version: 1 })
|
||||
assert.ok(inStore.set('version', 2))
|
||||
assert.strictEqual(inStore.get('version'), 2)
|
||||
inStore.readOnly = true
|
||||
assert.notOk(inStore.set('version', 3))
|
||||
assert.strictEqual(inStore.get('version'), 2)
|
||||
})
|
||||
|
||||
t.test('should support numbers as key', function() {
|
||||
let inStore = new Nconf.Memory({ parseValues: true })
|
||||
inStore.set('test', '{"a":1}')
|
||||
assert.ok(inStore.get('test'))
|
||||
assert.deepStrictEqual(inStore.get('test'), { a: 1 })
|
||||
inStore.set('test', 'undefined')
|
||||
assert.notOk(inStore.get('test'))
|
||||
})
|
||||
|
||||
t.test('should not do anything if given invalid set root', function() {
|
||||
let inStore = new Nconf.Memory()
|
||||
inStore.set({ version: 1 })
|
||||
assert.deepStrictEqual(inStore.get(), { version: 1 })
|
||||
assert.notOk(inStore.set(null))
|
||||
assert.deepStrictEqual(inStore.get(), { version: 1 })
|
||||
assert.notOk(inStore.set())
|
||||
assert.deepStrictEqual(inStore.get(), { version: 1 })
|
||||
assert.notOk(inStore.set([1, 2]))
|
||||
assert.deepStrictEqual(inStore.get(), { version: 1 })
|
||||
})
|
||||
})
|
||||
|
||||
t.describe('#get()', function() {
|
||||
t.test('should respond with the correct value', function () {
|
||||
store.set('foo:bar:bazz', 'buzz')
|
||||
store.set('falsy:number', 0)
|
||||
store.set('falsy:string:empty', '')
|
||||
store.set('falsy:string:value', 'value')
|
||||
store.set('falsy:boolean', false)
|
||||
store.set('falsy:object', null)
|
||||
assert.strictEqual(store.get('foo:bar:bazz'), 'buzz')
|
||||
assert.strictEqual(store.get('falsy:number'), 0)
|
||||
assert.strictEqual(store.get('falsy:string:empty'), '')
|
||||
assert.strictEqual(store.get('falsy:string:value'), 'value')
|
||||
assert.strictEqual(store.get('falsy:boolean'), false)
|
||||
assert.strictEqual(store.get('falsy:object'), null)
|
||||
})
|
||||
|
||||
t.describe('should not at non-existent keys', function () {
|
||||
t.test('at the root level', function () {
|
||||
assert.strictEqual(store.get('this:key:does:not:exist'), undefined)
|
||||
})
|
||||
|
||||
t.test('within numbers', function () {
|
||||
assert.strictEqual(store.get('falsy:number:not:exist'), undefined)
|
||||
})
|
||||
|
||||
t.test('within booleans', function () {
|
||||
assert.strictEqual(store.get('falsy:boolean:not:exist'), undefined)
|
||||
})
|
||||
|
||||
t.test('within objects', function () {
|
||||
assert.strictEqual(store.get('falsy:object:not:exist'), undefined)
|
||||
})
|
||||
|
||||
t.test('within empty strings', function () {
|
||||
assert.strictEqual(store.get('falsy:string:empty:not:exist'), undefined)
|
||||
})
|
||||
|
||||
t.test('within non-empty strings', function () {
|
||||
assert.strictEqual(store.get('falsy:string:value:not:exist'), undefined)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t.describe('#clear()', function() {
|
||||
t.test('should return false if readonly', function() {
|
||||
let inStore = new Nconf.Memory()
|
||||
inStore.set({ version: 1 })
|
||||
inStore.readOnly = true
|
||||
assert.notOk(inStore.clear('version'))
|
||||
assert.strictEqual(inStore.get('version'), 1)
|
||||
})
|
||||
|
||||
t.test('the clear() should return true if success', function () {
|
||||
store.set('foo:bar:bazz', 'buzz')
|
||||
assert.strictEqual(store.get('foo:bar:bazz'), 'buzz')
|
||||
assert.ok(store.clear('foo:bar:bazz'))
|
||||
assert.strictEqual(typeof store.get('foo:bar:bazz'), 'undefined')
|
||||
})
|
||||
|
||||
t.test('should return false if not found', function() {
|
||||
store.set('this:exists', 'fornow')
|
||||
assert.strictEqual(store.get('this:exists'), 'fornow')
|
||||
assert.notOk(store.clear('this:exists:but:not:this'))
|
||||
assert.strictEqual(store.get('this:exists'), 'fornow')
|
||||
})
|
||||
})
|
||||
|
||||
t.describe('#merge()', function () {
|
||||
t.test('should return false if readonly', function() {
|
||||
let inStore = new Nconf.Memory()
|
||||
inStore.set({ version: 1 })
|
||||
inStore.readOnly = true
|
||||
assert.notOk(inStore.merge({ version: 2 }))
|
||||
assert.strictEqual(inStore.get('version'), 1)
|
||||
})
|
||||
|
||||
t.test('when overriding an existing literal value', function () {
|
||||
store.set('merge:literal', 'string-value')
|
||||
store.merge('merge:literal', merge)
|
||||
assert.deepStrictEqual(store.get('merge:literal'), merge)
|
||||
})
|
||||
|
||||
t.test('when overriding an existing Array value', function () {
|
||||
store.set('merge:array', [1, 2, 3, 4])
|
||||
store.merge('merge:array', merge)
|
||||
assert.deepStrictEqual(store.get('merge:literal'), merge)
|
||||
})
|
||||
|
||||
t.test('when merging into an existing Object value', function () {
|
||||
store.set('merge:object', {
|
||||
prop1: 2,
|
||||
prop2: 'prop2',
|
||||
prop3: {
|
||||
bazz: 'bazz'
|
||||
},
|
||||
prop4: ['foo', 'bar']
|
||||
})
|
||||
assert.strictEqual(store.get('merge:object:prop1'), 2)
|
||||
assert.strictEqual(store.get('merge:object:prop2'), 'prop2')
|
||||
assert.deepStrictEqual(store.get('merge:object:prop3'), {
|
||||
bazz: 'bazz'
|
||||
})
|
||||
assert.strictEqual(store.get('merge:object:prop4').length, 2)
|
||||
|
||||
store.merge('merge:object', merge)
|
||||
|
||||
assert.strictEqual(store.get('merge:object:prop1'), 1)
|
||||
assert.strictEqual(store.get('merge:object:prop2').length, 3)
|
||||
assert.deepStrictEqual(store.get('merge:object:prop3'), {
|
||||
foo: 'bar',
|
||||
bar: 'foo',
|
||||
bazz: 'bazz'
|
||||
})
|
||||
assert.strictEqual(store.get('merge:object:prop4').length, 2)
|
||||
})
|
||||
|
||||
t.test('when merging at root level with an object', function() {
|
||||
const assertFirst = 'herp'
|
||||
const assertAfter = 'derp'
|
||||
const newItem = { asdf: assertAfter }
|
||||
|
||||
let inStore = new Nconf.Memory()
|
||||
inStore.set({
|
||||
version: 1,
|
||||
asdf: assertFirst,
|
||||
})
|
||||
assert.strictEqual(inStore.get('asdf'), assertFirst)
|
||||
assert.strictEqual(inStore.get('version'), 1)
|
||||
assert.ok(inStore.merge(newItem))
|
||||
assert.notStrictEqual(inStore.get(), newItem)
|
||||
assert.strictEqual(inStore.get('asdf'), assertAfter)
|
||||
assert.strictEqual(inStore.get('version'), 1)
|
||||
})
|
||||
|
||||
t.test('when merging at root level with an object with null after', function() {
|
||||
const assertFirst = 'herp'
|
||||
const assertAfter = 'derp'
|
||||
const newItem = { asdf: assertAfter }
|
||||
|
||||
let inStore = new Nconf.Memory()
|
||||
inStore.set({
|
||||
version: 1,
|
||||
asdf: assertFirst,
|
||||
})
|
||||
assert.strictEqual(inStore.get('asdf'), assertFirst)
|
||||
assert.strictEqual(inStore.get('version'), 1)
|
||||
assert.ok(inStore.merge(newItem), null)
|
||||
assert.notStrictEqual(inStore.get(), newItem)
|
||||
assert.strictEqual(inStore.get('asdf'), assertAfter)
|
||||
assert.strictEqual(inStore.get('version'), 1)
|
||||
})
|
||||
|
||||
t.test('when merging at root level with array', function() {
|
||||
const newItem = 'herp'
|
||||
|
||||
let inStore = new Nconf.Memory()
|
||||
inStore.set({
|
||||
version: 1,
|
||||
})
|
||||
assert.strictEqual(inStore.get('version'), 1)
|
||||
assert.notOk(inStore.get('0'))
|
||||
assert.ok(inStore.merge([newItem]))
|
||||
assert.ok(inStore.get('0'))
|
||||
assert.strictEqual(inStore.get('0'), newItem)
|
||||
assert.strictEqual(inStore.get('version'), 1)
|
||||
})
|
||||
|
||||
t.test('when merging at root level with array and null after', function() {
|
||||
const newItem = 'herp'
|
||||
|
||||
let inStore = new Nconf.Memory()
|
||||
inStore.set({
|
||||
version: 1,
|
||||
})
|
||||
assert.strictEqual(inStore.get('version'), 1)
|
||||
assert.notOk(inStore.get('0'))
|
||||
assert.ok(inStore.merge([newItem], null))
|
||||
assert.ok(inStore.get('0'))
|
||||
assert.strictEqual(inStore.get('0'), newItem)
|
||||
assert.strictEqual(inStore.get('version'), 1)
|
||||
})
|
||||
|
||||
t.test('it should always merge at root level if key is object regardless of what comes after', function() {
|
||||
let inStore = new Nconf.Memory()
|
||||
inStore.set({ herp: 1 })
|
||||
assert.deepStrictEqual(inStore.get(), { herp: 1 })
|
||||
assert.ok(inStore.merge({ version: 2 }, null))
|
||||
assert.deepStrictEqual(inStore.get(), { herp: 1, version: 2 })
|
||||
assert.ok(inStore.merge({ test: 3 }, 1235))
|
||||
assert.deepStrictEqual(inStore.get(), { herp: 1, version: 2, test: 3 })
|
||||
assert.ok(inStore.merge({ foo: 4 }, 'sadfsadfs'))
|
||||
assert.deepStrictEqual(inStore.get(), { herp: 1, version: 2, test: 3, foo: 4 })
|
||||
assert.ok(inStore.merge({ bar: 5 }, { asdf: 1 }))
|
||||
assert.deepStrictEqual(inStore.get(), { herp: 1, version: 2, test: 3, foo: 4, bar: 5 })
|
||||
})
|
||||
|
||||
t.test('it should be robust about key type and overwriting', function() {
|
||||
let inStore = new Nconf.Memory()
|
||||
inStore.set({ herp: 1 })
|
||||
assert.deepStrictEqual(inStore.get(), { herp: 1 })
|
||||
assert.ok(inStore.merge(123, null))
|
||||
assert.deepStrictEqual(inStore.get(), { herp: 1, '123': null })
|
||||
assert.ok(inStore.merge(123, { a: 1 }))
|
||||
assert.deepStrictEqual(inStore.get(), { herp: 1, '123': { a: 1 } })
|
||||
assert.ok(inStore.merge(123, ['a', 1]))
|
||||
assert.deepStrictEqual(inStore.get(), { herp: 1, '123': ['a', 1] })
|
||||
})
|
||||
|
||||
t.test('it be able to handle basic value types with basic values', function() {
|
||||
let inStore = new Nconf.Memory()
|
||||
inStore.set({ herp: 1 })
|
||||
assert.deepStrictEqual(inStore.get(), { herp: 1 })
|
||||
assert.ok(inStore.merge({ version: 2 }, null))
|
||||
assert.deepStrictEqual(inStore.get(), { herp: 1, version: 2 })
|
||||
assert.ok(inStore.merge({ test: 3 }, 1235))
|
||||
assert.deepStrictEqual(inStore.get(), { herp: 1, version: 2, test: 3 })
|
||||
assert.ok(inStore.merge({ foo: 4 }, 'sadfsadfs'))
|
||||
assert.deepStrictEqual(inStore.get(), { herp: 1, version: 2, test: 3, foo: 4 })
|
||||
assert.ok(inStore.merge({ bar: 5 }, { asdf: 1 }))
|
||||
assert.deepStrictEqual(inStore.get(), { herp: 1, version: 2, test: 3, foo: 4, bar: 5 })
|
||||
})
|
||||
|
||||
t.test('when sending a single path with null, string or number value should overwrite', function() {
|
||||
const assertString = 'Beginning'
|
||||
const assertNumber = 358792
|
||||
|
||||
let inStore = new Nconf.Memory()
|
||||
inStore.set({
|
||||
herp: {
|
||||
derp: 123,
|
||||
},
|
||||
})
|
||||
assert.strictEqual(inStore.get('herp:derp'), 123)
|
||||
assert.ok(inStore.merge('herp:derp', null))
|
||||
assert.strictEqual(inStore.get('herp:derp'), null)
|
||||
assert.ok(inStore.merge('herp:derp', assertString))
|
||||
assert.strictEqual(inStore.get('herp:derp'), assertString)
|
||||
assert.ok(inStore.merge('herp:derp', assertNumber))
|
||||
assert.strictEqual(inStore.get('herp:derp'), assertNumber)
|
||||
})
|
||||
|
||||
t.test('when merging at nonexisting path', function() {
|
||||
const assertNewItem = { a: 1, b: 2 }
|
||||
const assertPath = 'new:path:for:item'
|
||||
|
||||
let inStore = new Nconf.Memory()
|
||||
inStore.set({
|
||||
version: 1,
|
||||
})
|
||||
assert.strictEqual(inStore.get('version'), 1)
|
||||
assert.notOk(inStore.get(assertPath))
|
||||
|
||||
assert.ok(inStore.merge(assertPath, assertNewItem))
|
||||
assert.ok(inStore.get(assertPath))
|
||||
assert.notStrictEqual(inStore.get(assertPath), assertNewItem)
|
||||
assert.deepStrictEqual(inStore.get(assertPath), assertNewItem)
|
||||
assert.deepStrictEqual(inStore.get().new.path.for.item, assertNewItem)
|
||||
})
|
||||
})
|
||||
|
||||
t.describe('#reset()', function() {
|
||||
t.test('should remove everything', function() {
|
||||
const assertRoot = {
|
||||
version: 1,
|
||||
asdf: 'test',
|
||||
}
|
||||
let inStore = new Nconf.Memory()
|
||||
inStore.set(assertRoot)
|
||||
assert.deepStrictEqual(inStore.get(), assertRoot)
|
||||
inStore.reset()
|
||||
assert.deepStrictEqual(inStore.get(), {})
|
||||
})
|
||||
|
||||
t.test('should do nothing if readonly', function() {
|
||||
const assertRoot = {
|
||||
version: 1,
|
||||
asdf: 'test',
|
||||
}
|
||||
let inStore = new Nconf.Memory()
|
||||
inStore.set(assertRoot)
|
||||
assert.deepStrictEqual(inStore.get(), assertRoot)
|
||||
|
||||
inStore.readOnly = true
|
||||
assert.notOk(inStore.reset())
|
||||
assert.deepStrictEqual(inStore.get(), assertRoot)
|
||||
})
|
||||
})
|
||||
|
||||
t.describe('options', function () {
|
||||
t.describe('logicalSeparator', function () {
|
||||
var store = new Nconf.Memory({logicalSeparator: '||'})
|
||||
|
||||
t.test('when storing with : (colon), should store the config atomicly', function () {
|
||||
store.set('foo:bar:bazz', 'buzz')
|
||||
assert.strictEqual(typeof store.get('foo:bar'), 'undefined')
|
||||
assert.strictEqual(store.get('foo:bar:bazz'), 'buzz')
|
||||
})
|
||||
|
||||
t.test('when storing with separator, should be able to read the object', function () {
|
||||
store.set('foo||bar||bazz', 'buzz')
|
||||
assert.strictEqual(store.get('foo||bar').bazz, 'buzz')
|
||||
assert.strictEqual(store.get('foo').bar.bazz, 'buzz')
|
||||
})
|
||||
})
|
||||
|
||||
t.test('should allow specifying readonly', function () {
|
||||
var store = new Nconf.Memory({ readOnly: true })
|
||||
assert.strictEqual(store.readOnly, true)
|
||||
})
|
||||
})
|
||||
})
|
50
usage.js
50
usage.js
|
@ -1,50 +0,0 @@
|
|||
var fs = require('fs'),
|
||||
path = require('path'),
|
||||
nconf = require('./lib/nconf');
|
||||
|
||||
//
|
||||
// Configure the provider with a single store and
|
||||
// support for command-line arguments and environment
|
||||
// variables.
|
||||
//
|
||||
var single = new nconf.Provider({
|
||||
env: true,
|
||||
argv: true,
|
||||
store: {
|
||||
type: 'file',
|
||||
file: path.join(__dirname, 'config.json')
|
||||
}
|
||||
});
|
||||
|
||||
//
|
||||
// Configure the provider with multiple hierarchical stores
|
||||
// representing `user` and `global` configuration values.
|
||||
//
|
||||
var multiple = new nconf.Provider({
|
||||
stores: [
|
||||
{ name: 'user', type: 'file', file: path.join(__dirname, 'user-config.json') },
|
||||
{ name: 'global', type: 'global', file: path.join(__dirname, 'global-config.json') }
|
||||
]
|
||||
});
|
||||
|
||||
//
|
||||
// Setup nconf to use the 'file' store and set a couple of values;
|
||||
//
|
||||
nconf.use('file', { file: path.join(__dirname, 'config.json') });
|
||||
nconf.set('database:host', '127.0.0.1');
|
||||
nconf.set('database:port', 5984);
|
||||
|
||||
//
|
||||
// Get the entire database object from nconf
|
||||
//
|
||||
var database = nconf.get('database');
|
||||
console.dir(database);
|
||||
|
||||
//
|
||||
// Save the configuration object to disk
|
||||
//
|
||||
nconf.save(function (err) {
|
||||
fs.readFile(path.join(__dirname, 'config.json'), function (err, data) {
|
||||
console.dir(JSON.parse(data.toString()))
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue