Finished completely rewriting both library and unit tests

This commit is contained in:
Jonatan Nilsson 2021-06-22 19:25:00 +00:00
parent 1104c0d3ad
commit 1014cdec86
57 changed files with 2785 additions and 2929 deletions

View file

@ -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

View file

@ -1,2 +0,0 @@
node_modules
usage.js

View file

@ -1,11 +0,0 @@
{
"env": {
"node": true
},
"parserOptions": {
"ecmaVersion": 6
},
"rules": {
"no-unused-vars": "error"
}
}

1
.gitattributes vendored
View file

@ -1 +0,0 @@
package-lock.json binary

12
.gitignore vendored
View file

@ -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

View file

@ -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
View file

@ -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
View 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
}, {})
}

View file

@ -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
View 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

View file

@ -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;
}, {});
}

View file

@ -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;

View file

@ -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;
}

View file

@ -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;
};

View file

@ -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;
};

View file

@ -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;
};

View file

@ -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
View 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
View 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
View 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
View 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
View 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

View file

@ -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": [

View file

@ -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]"
}

View file

@ -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
View 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}"')
})
})

View file

@ -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'))
});
});
});

View file

@ -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: {

View file

@ -1,3 +0,0 @@
{
"test": "empty"
}

View file

@ -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'));

View file

@ -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'));

View file

@ -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'));

View file

@ -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')
}));

View file

@ -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');

View file

@ -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')
}));

View file

@ -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')
}));

View file

@ -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();

View file

@ -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'));

View file

@ -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'));

View file

@ -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
View 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"
}
}
}

View file

@ -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));

View 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
}
}
});
})
})

View file

@ -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 {}
}

View file

@ -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 required keys exist", function() {
var Provider = nconf.Provider
t.test('should return the provider if all keys exist', function() {
assert.strictEqual(nconf.required(['foo:bar:bazz']), nconf)
})
})
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
})
t.describe("the clear() method", function() {
t.test("should respond with the true", function() {
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)
}
})
})
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('#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.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')
})
})

View file

@ -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)
}
});
})
})
});

View file

@ -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
View 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')
})
})

View file

@ -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
View 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]
})
})
})

View file

@ -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
View 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)
})
*/

View file

@ -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);
});
});

View 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')
})
})

View file

@ -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
View 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)
})
})
})

View file

@ -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()))
});
});