Comparing version 0.0.6 to 0.1.0
@@ -5,15 +5,18 @@ const | ||
conf = convict({ | ||
var conf = convict({ | ||
ip: { | ||
doc: "The IP Address to bind.", | ||
format: 'string = "127.0.0.1"', | ||
format: "ipaddress", | ||
default: "127.0.0.1", | ||
env: "IP_ADDRESS", | ||
}, | ||
port: { | ||
format: 'integer = 0', | ||
env: "PORT", | ||
doc: "The port to bind." | ||
doc: "The port to bind.", | ||
format: 'int', | ||
default: 0, | ||
env: "PORT" | ||
} | ||
}).loadFile(__dirname + '/config.json').validate(); | ||
var server = http.createServer(function (req, res) { | ||
@@ -20,0 +23,0 @@ res.writeHead(200, {'Content-Type': 'text/plain'}); |
@@ -1,37 +0,156 @@ | ||
// TODO: | ||
// * nesting | ||
// * environment | ||
// * all sorts of error handling | ||
/* | ||
* node-convict | ||
* Configuration management with support for environmental variables, files, and validation. | ||
* | ||
*/ | ||
const | ||
orderly = require('orderly'), | ||
JSV = require("JSV").JSV.createEnvironment(), | ||
cjson = require('cjson'), | ||
fs = require('fs'); | ||
const cjson = require('cjson'); | ||
const fs = require('fs'); | ||
const validator = require('validator'); | ||
const moment = require('moment'); | ||
function buildSchema(name, o, props, fullName, env) { | ||
if (typeof o === 'string' || (o && o.format && typeof o.format === 'string')) { | ||
var fmt = (typeof o === 'string') ? o : o.format; | ||
try { | ||
var schema = orderly.parse(fmt); | ||
if (!schema.optional) schema.required = true; | ||
props[name] = schema; | ||
} catch(e) { | ||
throw "'" + fullName + "' has an invalid format: " + e.toString(); | ||
} | ||
if (o.env) { | ||
if (env[o.env]) { | ||
throw "'" + fullName + "' reuses an env variable: " + o.env | ||
var check = validator.check; | ||
// format can be a: | ||
// - predefine type, as seen below | ||
// - an array of enumerated values, e.g. ["production", "development", "testing"] | ||
// - built-in JavaScript type, i.e. Object, Array, String, Number, Boolean | ||
// - or if omitted, the Object.prototype.toString.call of the default value | ||
var types = { | ||
"*": function() { }, | ||
int: function(x) { | ||
check(x, 'must be an integer').isInt(); | ||
}, | ||
nat: function(x) { | ||
check(x, 'must be a positive integer').isInt().min(0); | ||
}, | ||
port: function(x) { | ||
check(x, 'Ports must be within range 1 - 65535').isInt().min(1).max(65535); | ||
}, | ||
url: function(x) { | ||
check(x, 'must be a URL').isUrl(); | ||
}, | ||
email: function(x) { | ||
check(x, 'must be an email address').isEmail(); | ||
}, | ||
ipaddress: function(x) { | ||
check(x, 'must be an IP address').isIP(); | ||
}, | ||
duration: function(x) { | ||
check(x, 'must be a positive integer').isInt().min(0); | ||
}, | ||
timestamp: function(x) { | ||
check(x, 'must be a positive integer').isInt().min(0); | ||
} | ||
}; | ||
function validate (instance, schema, errors) { | ||
Object.keys(schema.properties).reduce(function(previousErrors, name) { | ||
var p = schema.properties[name]; | ||
if (p.properties) { | ||
var kids = instance[name] || {}; | ||
validate(kids, p, previousErrors); | ||
} else { | ||
try { | ||
p._format(instance[name]); | ||
} catch (e) { | ||
previousErrors.push(e); | ||
} | ||
env[o.env] = fullName; | ||
} | ||
} else { | ||
return previousErrors; | ||
}, errors); | ||
return errors; | ||
} | ||
// helper for checking that a value is in the list of valid options | ||
function contains (options, x) { | ||
check(x, 'must be one of the possible values: ' + JSON.stringify(options)).isIn(options); | ||
} | ||
var BUILT_INS = [Object, Array, String, Number, Boolean]; | ||
function normalizeSchema (name, o, props, fullName, env) { | ||
if (typeof o === 'object' && !Array.isArray(o) && typeof o.default === 'undefined') { | ||
props[name] = { | ||
properties: {}, | ||
additionalProperties: false | ||
}; | ||
Object.keys(o).forEach(function(k) { | ||
buildSchema(k, o[k], props[name].properties, fullName + "." + k, env); | ||
normalizeSchema(k, o[k], props[name].properties, fullName + "." + k, env); | ||
}); | ||
return; | ||
} else if (typeof o !== 'object' || Array.isArray(o) || o === null) { | ||
o = { default: o }; | ||
} | ||
if (typeof o === 'object') { | ||
props[name] = o; | ||
if (o.env) { | ||
if (env[o.env]) { | ||
throw new Error("'" + fullName + "' reuses an env variable: " + o.env); | ||
} | ||
env[o.env] = fullName; | ||
} | ||
// store original format function | ||
var format = o.format; | ||
var newFormat; | ||
if (BUILT_INS.indexOf(format) >= 0) { | ||
// if the format property is a built-in JavaScript constructor, | ||
// check that the value is of that type | ||
newFormat = function(x) { | ||
check(Object.prototype.toString.call(x), 'must be of type ' + format.name) | ||
.equals(Object.prototype.toString.call(new format())); | ||
}; | ||
o.format = format.name.toLowerCase(); | ||
} else if (typeof format === 'string') { | ||
// store declared type | ||
if (!format in types) { | ||
throw new Error("'" + fullName + "' uses an unknown format type: " + format); | ||
} | ||
// use a predefined type | ||
newFormat = types[format]; | ||
} else if (Array.isArray(format)) { | ||
// check that the value is a valid option | ||
newFormat = contains.bind(null, format); | ||
} else if (typeof format === 'function') { | ||
newFormat = format; | ||
} else if (format && typeof format !== 'function') { | ||
throw new Error("'" + fullName + "': `format` must be a function or a known format type."); | ||
} | ||
if (!newFormat && !format) { | ||
// default format is the typeof the default value | ||
var type = Object.prototype.toString.call(o.default); | ||
newFormat = function(x) { | ||
check(Object.prototype.toString.call(x), | ||
' should be of type ' + type.replace(/\[.* |]/g, '')).equals(type); | ||
}; | ||
} | ||
o._format = function (x) { | ||
try { | ||
newFormat(x); | ||
} catch (e) { | ||
// attach the value and the property's fullName to the error | ||
e.fullName = fullName; | ||
e.value = x; | ||
throw e; | ||
} | ||
}; | ||
} else { | ||
throw new Error("'" + fullName + "' doesn't appear to be a valid schema object: " | ||
+ JSON.stringify(o)) | ||
+ '. Note: schema objects must have a default value.'; | ||
} | ||
} | ||
@@ -56,3 +175,3 @@ | ||
} else { | ||
if (!c[name] && typeof p.default !== 'undefined') c[name] = p.default | ||
if (!c[name] && typeof p.default !== 'undefined') c[name] = coerce(name, p.default, schema); | ||
} | ||
@@ -62,10 +181,10 @@ }); | ||
function overlay(from, to) { | ||
function isObj(o) { return (typeof o === 'object' && o !== null); } | ||
function isObj(o) { return (typeof o === 'object' && o !== null); } | ||
function overlay(from, to, schema) { | ||
Object.keys(from).forEach(function(k) { | ||
// leaf | ||
if (Array.isArray(from[k]) || !isObj(from[k])) to[k] = from[k]; | ||
// not | ||
else { | ||
if (Array.isArray(from[k]) || !isObj(from[k])) { | ||
to[k] = coerce(k, from[k], schema); | ||
} else { | ||
if (!isObj(to[k])) to[k] = {}; | ||
@@ -77,3 +196,3 @@ overlay(from[k], to[k]); | ||
function getType(schema, path) { | ||
function traverseSchema(schema, path) { | ||
var ar = path.split('.'); | ||
@@ -90,13 +209,42 @@ var o = schema; | ||
} | ||
return (o && o.type) ? o.type : null; | ||
return o; | ||
} | ||
module.exports = function(def) { | ||
function getFormat(schema, path) { | ||
var o = traverseSchema(schema, path); | ||
return o ? (typeof o.format === 'string' ? o.format : typeof o.default) : null; | ||
} | ||
function coerce(k, v, schema) { | ||
// magic coerceing | ||
var format = getFormat(schema, k); | ||
if (typeof v === 'string') { | ||
switch (format) { | ||
case 'port': | ||
case 'int': v = parseInt(v, 10); break; | ||
case 'number': v = parseFloat(v); break; | ||
case 'boolean': v = ((v === 'false') ? false : true); break; | ||
case 'array': v = v.split(','); break; | ||
case 'timestamp': v = moment(v).valueOf(); break; | ||
case 'duration': | ||
var split = v.split(' '); | ||
if (!split[1].match(/s$/)) split[1] += 's'; | ||
v = moment.duration(parseInt(split[0], 10), split[1]).valueOf(); | ||
break; | ||
} | ||
} | ||
return v; | ||
} | ||
module.exports = function convict(def) { | ||
var rv = { | ||
template: function() { | ||
}, | ||
toString: function() { | ||
return JSON.stringify(this._instance, null, 2) | ||
return JSON.stringify(this._instance, null, 2); | ||
}, | ||
toSchemaString: function() { | ||
return JSON.stringify(this._schema, null, 2); | ||
}, | ||
get: function(path) { | ||
@@ -112,3 +260,5 @@ var o = JSON.parse(JSON.stringify(this._instance)); | ||
} | ||
if (o === undefined) throw "cannot find configuration param '" + path + "'"; | ||
if (o === undefined) { | ||
throw new Error("cannot find configuration param '" + path + "'"); | ||
} | ||
return o; | ||
@@ -120,3 +270,3 @@ }, | ||
return true; | ||
} catch(e) { | ||
} catch (e) { | ||
return false; | ||
@@ -126,10 +276,4 @@ } | ||
set: function(k, v) { | ||
// magic casting | ||
if (typeof v === 'string') { | ||
switch (getType(this._schema, k)) { | ||
case 'integer': v = parseInt(v); break; | ||
case 'number': v = parseFloat(v); break; | ||
case 'boolean': v = ((v === 'false') ? false : true); break; | ||
} | ||
} | ||
v = coerce(k, v, this._schema); | ||
var ar = k.split('.'); | ||
@@ -146,3 +290,3 @@ var o = this._instance; | ||
load: function(conf) { | ||
overlay(conf, this._instance); | ||
overlay(conf, this._instance, this._schema); | ||
// environment always overrides config files | ||
@@ -154,3 +298,3 @@ importEnvironment(rv); | ||
var self = this; | ||
if (!Array.isArray(paths)) paths = [ paths ]; | ||
if (!Array.isArray(paths)) paths = [paths]; | ||
paths.forEach(function(path) { | ||
@@ -164,19 +308,22 @@ overlay(cjson.load(path), self._instance); | ||
validate: function() { | ||
var report = JSV.validate(this._instance, this._schema); | ||
if (report.errors.length) { | ||
var errBuf = ""; | ||
for (var i = 0; i < report.errors.length; i++) { | ||
if (errBuf.length) errBuf += "\n"; | ||
var e = report.errors[i]; | ||
// get the property name in dot notation | ||
if (e.uri) { | ||
errBuf += e.uri.split('/').slice(1).join('.') + ": "; | ||
var errors = validate(this._instance, this._schema, []); | ||
if (errors.length) { | ||
var errBuf = ''; | ||
for (var i = 0; i < errors.length; i++) { | ||
if (errBuf.length) errBuf += '\n'; | ||
var e = errors[i]; | ||
if (e.fullName) { | ||
errBuf += e.fullName + ': '; | ||
} | ||
if (e.message) errBuf += e.message + ": "; | ||
if (e.details) { | ||
errBuf += ((typeof e.details === 'string') ? | ||
e.details : JSON.stringify(e.details)); | ||
if (e.message) errBuf += e.message; | ||
if (e.value) { | ||
errBuf += ': value was ' + JSON.stringify(e.value); | ||
} | ||
} | ||
throw errBuf; | ||
throw new Error(errBuf); | ||
} | ||
@@ -186,3 +333,2 @@ return this; | ||
}; | ||
// XXX validate definition | ||
@@ -193,16 +339,13 @@ rv._def = def; | ||
rv._schema = { | ||
properties: {}, | ||
additionalProperties: false | ||
properties: {} | ||
}; | ||
rv._env = { }; | ||
rv._env = {}; | ||
Object.keys(def).forEach(function(k) { | ||
buildSchema(k, def[k], rv._schema.properties, k, rv._env); | ||
normalizeSchema(k, def[k], rv._schema.properties, k, rv._env); | ||
}); | ||
var report = JSV.validate({}, rv._schema); | ||
rv._instance = report.instance.getValue() || {}; | ||
addDefaultValues(rv._schema, rv._instance) | ||
rv._instance = {}; | ||
addDefaultValues(rv._schema, rv._instance); | ||
importEnvironment(rv); | ||
@@ -212,1 +355,2 @@ | ||
}; | ||
@@ -5,3 +5,3 @@ { | ||
"description": "Unruly configuration management for nodejs", | ||
"version": "0.0.6", | ||
"version": "0.1.0", | ||
"homepage": "https://github.com/lloyd/node-convict", | ||
@@ -17,10 +17,10 @@ "repository": { | ||
"dependencies": { | ||
"orderly": "1.0.1", | ||
"JSV": "3.5.0", | ||
"cjson": "0.0.6" | ||
"cjson": "0.2.1", | ||
"validator": "0.4.24", | ||
"moment": "1.7.2" | ||
}, | ||
"devDependencies": { | ||
"obj_diff": "0.2.0", | ||
"should": "0.5.1", | ||
"mocha": "0.10.2" | ||
"obj_diff": "0.3.0", | ||
"should": "1.2.2", | ||
"mocha": "1.8.1" | ||
}, | ||
@@ -27,0 +27,0 @@ "scripts": { |
170
README.md
@@ -1,3 +0,169 @@ | ||
## What | ||
# node-convict | ||
convict is a node | ||
Convict expands on the standard pattern of configuring node.js applications in a way that is more robust and accessible to collaborators, who may have less interest in digging through imperative code in order to inspect or modify settings. By introducting a configuration schema, convict gives project collaborators more **context** on each setting and enables **validation and early failures** for when configuration goes wrong. | ||
## Features | ||
* **Loading and merging**: configurations are loaded from disk or inline and merged. JSON files are loaded with `cjson` so comments are welcome. | ||
* **Environmental variables**: values can be set from environmental variables | ||
* **Validation**: configurations are validated against your schema, generating an error report with all errors that are found | ||
Example: | ||
var convict = require('convict'); | ||
var http = require('http'); | ||
// define a schema | ||
var conf = convict({ | ||
env: { | ||
doc: "The applicaton environment.", | ||
format: ["production", "development", "test"], | ||
default: "development", | ||
env: "NODE_ENV" | ||
}, | ||
ip: { | ||
doc: "The IP address to bind.", | ||
format: "ipaddress", | ||
default: "127.0.0.1", | ||
env: "IP_ADDRESS", | ||
}, | ||
port: { | ||
doc: "The port to bind.", | ||
format: "port", | ||
default: 0, | ||
env: "PORT" | ||
} | ||
}); | ||
// load environment dependent configuration | ||
var env = conf.get('env'); | ||
conf.loadFile('./config/' + env + '.json'); | ||
// perform validation | ||
conf.validate(); | ||
var server = http.createServer(function (req, res) { | ||
res.writeHead(200, {'Content-Type': 'text/plain'}); | ||
res.end('Hello World\n'); | ||
}); | ||
// consume | ||
server.listen(conf.get('port'), conf.get('ip'), function(x) { | ||
var addy = server.address(); | ||
console.log('running on http://' + addy.address + ":" + addy.port); | ||
}); | ||
## Schema | ||
A configuration module could look like this: | ||
config.js: | ||
var config = module.exports = convict({ | ||
env: { | ||
doc: "The application environment.", | ||
format: ["production", "development", "test"], | ||
default: "development", | ||
env: "NODE_ENV" | ||
} | ||
}); | ||
config.loadFile(['./prod.json', './config.json']); | ||
You'll notice that each setting in the schema has four possible properties, each aiding in convict's goal of being more robust and collaborator friendly. | ||
* **Type information**: the `format` property specifies either a built-in convict format (`ipaddress`, `port`, `int`, etc.), or it can be a function to check a custom format. During validation, if a format check fails it will be added to the error report. | ||
* **Default values**: Every setting *must* have a default value. | ||
* **Environmental variables**: If the variable specified by `env` has a value, it will overwrite the setting's default value. | ||
* **Documentation**: The `doc` property is pretty self-explanatory. The nice part about having it in the schema rather than as a comment is that we can call `conf.toSchemaString()` and have it displayed in the output. | ||
### Validation | ||
In order to help detect misconfigurations, convict allows you to define a format for each setting. By defualt, convict checks if the value of the property has the same `typeof` value as the default value specified in the schema. You can define a custom format checking function in the schema by setting the `format` property. | ||
convict provides serveral predefined formats for validation that you can use ([using node-validator](https://github.com/chriso/node-validator#list-of-validation-methods) and moment.js). Most of them are self-explanatory: | ||
* `*` - any value is valid | ||
* `int` | ||
* `port` | ||
* `url` | ||
* `email` | ||
* `ipaddress` - IPv4 addresses | ||
* `duration` - miliseconds or a human readable string (e.g. "5 days") | ||
* `timestamp` - Unix timestamps or date strings recognized by moment.js | ||
* `nat` - positive integer (natural number) | ||
If `format` is set to one of the built-in JavaScript constructors, `Object`, `Array`, `String`, `Number`, or `Boolean`, validation will use Object.prototype.toString.call to check that the setting is the proper type. | ||
You can also provide your own format checking function. For example: | ||
var check = require('validator').check; | ||
var conf = convict({ | ||
key: { | ||
doc: "API key", | ||
format: function (val) { | ||
check(val, 'should be a 64 character hex key').regex(/^[a-fA-F0-9]{64}$/); | ||
}, | ||
default: '3cec609c9bc601c047af917a544645c50caf8cd606806b4e0a23312441014deb' | ||
} | ||
}); | ||
### Coercion | ||
Convict will automatically coerce environmental variables from strings to their proper types when importing them. For instance, values with the format `int`, `nat`, `port`, or `Number` will become numbers after a straight forward `parseInt` or `parseFloat`. `duration` and `timestamp` are also parse and converted into numbers, though they utilize `moment.js` for date parsing. | ||
## API | ||
### `var config = convict(schema)` | ||
`convict()` takes a schema object and returns a convict configuration object. The configuration object has an API for getting and setting values, described below. | ||
### `config.get(name)` | ||
Returns the value of the `name` property. `name` can use dot notation to reference nested values. E.g.: | ||
config.get('database.host'); | ||
// or | ||
config.get('database').host; | ||
### `config.has(name)` | ||
Returns `true` if the property `name` is defined, or `false` otherwise. E.g.: | ||
if (config.has('some.property')) { | ||
// do something | ||
} | ||
### `config.set(name, value)` | ||
Sets the value of `name` to value. `name` can use dot notation to reference nested values, e.g. `"database.port"`. If objects in the chain don't yet exist, they will be initialized to empty objects. E.g.: | ||
config.set('property.that.may.not.exist.yet', 'some value'); | ||
config.get('property.that.may.not.exist.yet'); | ||
// returns "some value" | ||
### `config.load(object)` | ||
This will load and merge a JavaScript object into `config`. E.g.: | ||
config.load({ | ||
"env": "test", | ||
"ip": "127.0.0.1", | ||
"port": 80 | ||
}); | ||
### `config.loadFile(file or [file1, file2, ...])` | ||
This will load and merge one or multiple JSON configuration files into `config`. JSON files are loaded using `cjson`, so they can contain comments. E.g.: | ||
conf.loadFile('./config/' + conf.get('env') + '.json'); | ||
Or, loading multiple files at once: | ||
// CONFIG_FILES=/path/to/production.json,/path/to/secrets.json,/path/to/sitespecific.json | ||
conf.loadFile(process.env.CONFIG_FILES.split(',')); | ||
### `config.validate()` | ||
Validates `config` against the schema used to initialize it. All errors are collected and thrown at once. | ||
exports.conf = { | ||
arr: "array { any; }*" | ||
arr: { | ||
default: [], | ||
format: Array | ||
} | ||
}; |
exports.conf = { | ||
env: { | ||
format: 'string ["production", "local"] = "local"', | ||
format: ["production", "local"], | ||
default: "local", | ||
env: "NODE_ENV", | ||
@@ -8,8 +9,10 @@ doc: "The environment that we're running in." | ||
URL: { | ||
format: 'string = "https://browserid.org"', | ||
format: String, | ||
default: "https://browserid.org", | ||
env: "URL", | ||
doc: "The externally visible url of the server", | ||
doc: "The externally visible url of the server" | ||
}, | ||
use_minified_resources: { | ||
format: "boolean = false;", | ||
format: Boolean, | ||
default: false, | ||
doc: "All resources should be combined and minified", | ||
@@ -19,5 +22,6 @@ env: "MINIFIED" | ||
var_path: { | ||
format: 'string = "/home/browserid/var"', | ||
format: String, | ||
default: "/home/browserid/var", | ||
doc: "The path the the 'var' directory, where logs and such will go" | ||
} | ||
}; |
@@ -1,32 +0,52 @@ | ||
module.exports = { | ||
exports.conf = { | ||
env: { | ||
format: 'string ["production", "local"]', | ||
format: ["production", "local"], | ||
default: "production", | ||
env: "NODE_ENV" | ||
}, | ||
URL: { | ||
format: 'string = "https://browserid.org"', | ||
format: String, | ||
default: "https://browserid.org", | ||
env: 'URL' | ||
}, | ||
use_minified_resources: 'boolean = true', | ||
var_path: 'string = "/home/browserid/var"', | ||
use_minified_resources: true, | ||
var_path: "/home/browserid/var", | ||
database: { | ||
driver: 'string ["json", "mysql"] = "mysql"', | ||
user: 'string', | ||
create_schema: 'boolean', | ||
may_write: 'boolean' | ||
driver: { | ||
default: "mysql", | ||
format: ["json", "mysql"] | ||
}, | ||
user: 'browserid', | ||
create_schema: true, | ||
may_write: false | ||
}, | ||
statsd: { | ||
enabled: 'boolean' | ||
enabled: true | ||
}, | ||
bcrypt_work_factor: "integer[6,20] = 12", | ||
authentication_duration: 'string = "2 weeks"', | ||
certificate_validity: 'string = "1 day"', | ||
min_time_between_emails: 'string = "1 minute"', | ||
max_compute_duration: 'string = "10 seconds"', | ||
disable_primary_support: 'boolean = false', | ||
enable_code_version: 'boolean = false', | ||
default_lang: 'array { string; }', | ||
supported_languages: 'array { string; }', | ||
locale_directory: 'string = "locale"', | ||
express_log_format: 'string = "default"' | ||
bcrypt_work_factor: { | ||
default: 12, | ||
format: 'nat' | ||
}, | ||
authentication_duration: { | ||
default: "2 weeks", | ||
format: "duration" | ||
}, | ||
certificate_validity: { | ||
default: "1 day", | ||
format: "duration" | ||
}, | ||
min_time_between_emails: { | ||
default: "1 minute", | ||
format: "duration" | ||
}, | ||
max_compute_duration: { | ||
default: "10 seconds", | ||
format: "duration" | ||
}, | ||
disable_primary_support: false, | ||
enable_code_version: false, | ||
default_lang: [ 'en-US' ], | ||
supported_languages: [ 'en-US' ], | ||
locale_directory: "locale", | ||
express_log_format: "default" | ||
}; |
exports.conf = { | ||
ip: { | ||
format: 'string = "127.0.0.1"', | ||
default: "127.0.0.1", | ||
format: "ipaddress", | ||
env: "IP_ADDRESS" | ||
}, | ||
port: { | ||
format: 'integer = 0', | ||
default: 0, | ||
format: "port", | ||
env: "PORT" | ||
@@ -9,0 +11,0 @@ } |
exports.conf = { | ||
foo: { | ||
format: 'string = "a"', | ||
default: "a", | ||
format: String, | ||
env: "FOO" | ||
@@ -5,0 +6,0 @@ } |
exports.conf = { | ||
foo: { | ||
bar: { | ||
format: 'string ["a", "b"]', | ||
default: "a", | ||
format: ["a", "b"], | ||
env: "BAR" | ||
@@ -6,0 +7,0 @@ } |
exports.conf = { | ||
bool: { | ||
format: 'boolean', | ||
default: true, | ||
format: Boolean, | ||
env: "BOOL" | ||
}, | ||
int: { | ||
format: 'integer', | ||
default: 42, | ||
format: 'int', | ||
env: "INT" | ||
}, | ||
num: { | ||
format: 'number', | ||
default: 10.1, | ||
format: Number, | ||
env: 'NUM' | ||
}, | ||
array: { | ||
default: ['a', 'b'], | ||
format: Array, | ||
env: 'ARRAY' | ||
} | ||
@@ -19,3 +27,4 @@ }; | ||
INT: 77, | ||
NUM: 789.1011 | ||
NUM: 789.1011, | ||
ARRAY: "a,b,c" | ||
}; |
exports.conf = { | ||
ip: { | ||
doc: "The IP Address to bind.", | ||
format: 'string = "127.0.0.1"', | ||
default: "127.0.0.1", | ||
format: 'ipaddress', | ||
env: "IP_ADDRESS", | ||
}, | ||
port: { | ||
format: 'integer = 0', | ||
default: 0, | ||
format: 'port', | ||
env: "PORT", | ||
@@ -10,0 +12,0 @@ doc: "The port to bind." |
exports.conf = { | ||
top: { | ||
middle: { | ||
leaf: 'string = "foo"', | ||
leaf: "foo", | ||
leaf2: { | ||
format: 'string = "bar"', | ||
default: "bar", | ||
doc: "a second leaf" | ||
} | ||
}, | ||
internal_leaf: 'string = "baz"' | ||
internal_leaf: "baz" | ||
} | ||
}; |
@@ -10,7 +10,7 @@ const should = require('should'); | ||
foo: { | ||
bar: "number = 7", | ||
bar: 7, | ||
baz: { | ||
bing: 'string = "foo"', | ||
bing: "foo", | ||
"name with spaces": { | ||
name_with_underscores: "boolean = true" | ||
name_with_underscores: true | ||
} | ||
@@ -28,11 +28,12 @@ } | ||
it('should find a nested value', function() { | ||
(conf.get('foo.bar')).should.equal(7); | ||
var val = conf.get('foo.bar'); | ||
should.equal(val, 7); | ||
}); | ||
it('should handle three levels of nesting', function() { | ||
(conf.get('foo.baz.bing')).should.equal('foo'); | ||
should.equal(conf.get('foo.baz.bing'), 'foo'); | ||
}); | ||
it('should handle names with spaces and underscores', function() { | ||
(conf.get('foo.baz.name with spaces.name_with_underscores')).should.equal(true); | ||
should.equal(conf.get('foo.baz.name with spaces.name_with_underscores'), true); | ||
}); | ||
@@ -39,0 +40,0 @@ |
@@ -12,3 +12,4 @@ const | ||
} catch(e) { | ||
process.send({error: e.toString() }); | ||
console.error(e); | ||
process.send({error: e.message }); | ||
process.exit(1); | ||
@@ -15,0 +16,0 @@ } |
@@ -6,3 +6,3 @@ const | ||
cp = require('child_process'), | ||
obj_diff = require('obj_diff').diff, | ||
obj_diff = require('obj_diff'), | ||
mocha = require('mocha'); | ||
@@ -53,3 +53,3 @@ | ||
var n = cp.fork(path.join(__dirname + '/runner.js'), null, { env: env }); | ||
var n = cp.fork(path.join(__dirname + '/runner.js'), [], { env: env }); | ||
@@ -56,0 +56,0 @@ n.on('message', function(m) { |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
Copyleft License
License(Experimental) Copyleft license information was found.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 4 instances in 1 package
Non-permissive License
License(Experimental) A license not known to be considered permissive was found.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No License Found
License(Experimental) License information could not be found.
Found 1 instance in 1 package
40483
1101
170
35
2
70
15
4
+ Addedmoment@1.7.2
+ Addedvalidator@0.4.24
+ Addedcjson@0.2.1(transitive)
+ Addedmoment@1.7.2(transitive)
+ Addedvalidator@0.4.24(transitive)
- RemovedJSV@3.5.0
- Removedorderly@1.0.1
- RemovedJSV@3.5.0(transitive)
- Removedansi-styles@1.0.0(transitive)
- Removedchalk@0.4.0(transitive)
- Removedcjson@0.0.6(transitive)
- Removedhas-color@0.1.7(transitive)
- Removednomnom@1.8.1(transitive)
- Removedorderly@1.0.1(transitive)
- Removedstrip-ansi@0.1.1(transitive)
- Removedunderscore@1.6.0(transitive)
Updatedcjson@0.2.1