settings-lib
Advanced tools
Comparing version 0.1.6 to 0.2.0
@@ -0,1 +1,8 @@ | ||
# v0.2.0 / 2016-04-15 | ||
* Introduced native `Promise` support | ||
* Moved to gulp for build tasks running | ||
* Moved to eslint for code lint | ||
* The first found match is applied when multiple environment override files are present | ||
# v0.1.6 / 2015-11-16 | ||
@@ -2,0 +9,0 @@ |
var | ||
fs = require('fs'), | ||
util = require('util'), | ||
path = require('path'), | ||
async = require('async'), | ||
co = require('co'); | ||
defaultOptions = { | ||
baseConfigPath : '', | ||
environmentSearchPaths : ['./', './config', './settings'], | ||
commandLineSwitches : ['--config-file'], | ||
readEnvironmentMap : {}, | ||
readCommandLineMap : {} | ||
}, | ||
const DEFAULT_ARGV_START_POSITION = 2; | ||
defaultTypesMap = {}; | ||
function buildDefaultTypesMap (source, parentPath) { | ||
/** | ||
* Primary module | ||
**/ | ||
module.exports = (function (settings) { | ||
'use strict'; | ||
var keyPath; | ||
let | ||
defaultOptions = { | ||
baseConfigPath : '', | ||
environmentSearchPaths : ['./', './config', './settings'], | ||
commandLineSwitches : ['--config-file'], | ||
readEnvironmentMap : {}, | ||
readCommandLineMap : {} | ||
}, | ||
defaultTypesMap = {}; | ||
Object.keys(source).forEach(function (key) { | ||
keyPath = parentPath ? [parentPath, key].join('.') : key; | ||
if (source[key] === null) { | ||
return; | ||
} | ||
/** | ||
* Creates an internal map used to understand the original value type of | ||
* a field within the base configuration file. Values of subsequent overrides | ||
* are coerced accordingly based on the map created. | ||
* | ||
* @param {object} source - an object | ||
* @param {string} parentPath - a namespaced field name used as a hash key | ||
* @returns {undefined} | ||
**/ | ||
function buildDefaultTypesMap (source, parentPath) { | ||
Object.keys(source).forEach((key) => { | ||
let keyPath = parentPath ? [parentPath, key].join('.') : key; | ||
// array coercion | ||
if (Object.prototype.toString.call(source[key]) === '[object Array]') { | ||
defaultTypesMap[keyPath] = function (value) { | ||
if (Array.isArray(value)) { | ||
return value; | ||
} | ||
if (source[key] === null) { | ||
return; | ||
} | ||
// remove front and back brackets, then split on commas | ||
return value.slice(1, -1).split(','); | ||
}; | ||
// array coercion | ||
if (Array.isArray(source[key])) { | ||
defaultTypesMap[keyPath] = function (value) { | ||
if (Array.isArray(value)) { | ||
return value; | ||
} | ||
return; | ||
} | ||
// remove front and back brackets, then split on commas | ||
return value.slice(1, -1).split(','); | ||
}; | ||
// date coercion | ||
if (Object.prototype.toString.call(source[key]) === '[object Date]') { | ||
defaultTypesMap[keyPath] = function (value) { | ||
return new Date(value); | ||
}; | ||
return; | ||
} | ||
return; | ||
} | ||
// date coercion | ||
if (Object.prototype.toString.call(source[key]) === '[object Date]') { | ||
defaultTypesMap[keyPath] = function (value) { | ||
return new Date(value); | ||
}; | ||
// object coercion | ||
if (typeof source[key] === 'object' && | ||
/^\[object\sObject\]$/.test(source[key].toString()) && | ||
source[key] !== null) { | ||
return buildDefaultTypesMap(source[key], keyPath); | ||
} | ||
return; | ||
} | ||
defaultTypesMap[keyPath] = function (value) { | ||
switch (typeof source[key]) { | ||
case 'boolean': | ||
return /^true|1$/i.test(value); | ||
case 'number': | ||
return Number(value); | ||
default: | ||
return value; | ||
// object coercion | ||
if (typeof source[key] === 'object' && | ||
/^\[object\sObject\]$/.test(source[key].toString()) && | ||
source[key] !== null) { | ||
return buildDefaultTypesMap(source[key], keyPath); | ||
} | ||
}; | ||
return; | ||
}); | ||
} | ||
defaultTypesMap[keyPath] = function (value) { | ||
switch (typeof source[key]) { | ||
case 'boolean': | ||
return /^true|1$/i.test(value); | ||
case 'number': | ||
return Number(value); | ||
default: | ||
return value; | ||
} | ||
}; | ||
/* * | ||
* Creates a new object and merges multiple objects together, | ||
* each subsequent object having priority | ||
* | ||
* @param objects = accepts array of objects | ||
* @param callback = function (err, clonedObject) | ||
* */ | ||
function cloneAndMerge(objects, callback) { | ||
'use strict'; | ||
return; | ||
}); | ||
} | ||
if (!Array.isArray(objects)) { | ||
return callback(null, objects); | ||
/** | ||
* Returns a Promise that can be used to determine if a file exists. | ||
* | ||
* @param {string} filePath - a path to a file | ||
* @returns {Promise} - a native Promise that resolves true or false | ||
**/ | ||
function checkIfFileExists (filePath) { | ||
return new Promise((resolve) => { | ||
return fs.exists(filePath, resolve); | ||
}); | ||
} | ||
var | ||
cloned = {}, | ||
cloner = function (source, destination) { | ||
Object.keys(source).forEach(function (key) { | ||
if (Object.prototype.toString.call(source[key]) === '[object Array]') { | ||
destination[key] = source[key]; | ||
} else if (typeof source[key] === 'object' && source[key] !== null) { | ||
destination[key] = cloner(source[key], destination[key] || {}); | ||
} else { | ||
destination[key] = source[key]; | ||
} | ||
}); | ||
return destination; | ||
}; | ||
objects.forEach(function (item) { | ||
if (typeof item === 'object') { | ||
cloned = cloner(item, cloned); | ||
/** | ||
* Creates a new object and merges multiple objects together, | ||
* each subsequent object having priority | ||
* | ||
* @param {Array} objectList - accepts array of objects | ||
* @returns {Object} a newly cloned object with the merged results | ||
**/ | ||
function cloneAndMerge (objectList) { | ||
if (!Array.isArray(objectList)) { | ||
return objectList; | ||
} | ||
}); | ||
return callback(null, cloned); | ||
} | ||
let | ||
cloned = {}, | ||
cloner = function (source, destination) { | ||
Object.keys(source).forEach(function (key) { | ||
if (Array.isArray(source[key])) { | ||
destination[key] = source[key]; | ||
} else if (typeof source[key] === 'object' && source[key] !== null) { | ||
destination[key] = cloner(source[key], destination[key] || {}); | ||
} else { | ||
destination[key] = source[key]; | ||
} | ||
}); | ||
return destination; | ||
}; | ||
/** | ||
* Creates a new object based on an expression representing a single field | ||
* in an object and applies specified value | ||
* | ||
* @param expression = string Javascript expression | ||
* @param value = value to assign to the end result of the expression | ||
**/ | ||
function createObjectWithValue(expression, value) { | ||
'use strict'; | ||
objectList.forEach(function (item) { | ||
if (typeof item === 'object') { | ||
cloned = cloner(item, cloned); | ||
} | ||
}); | ||
var | ||
current, | ||
keys = [], | ||
// matches ], '], "], ][, '][', "][", [, [' and [" | ||
regexHash = /([\'\"]?\]\[[\'\"]?)|(\[[\'\"]?)|([\'\"]?\])/g, | ||
stack = {}, | ||
transform, | ||
atEnd = function (keys, key) { | ||
return keys.indexOf(key) === keys.length - 1; | ||
}; | ||
return cloned; | ||
} | ||
if (expression) { | ||
// breaks the expression into an array which represents its stack | ||
expression | ||
.split(regexHash) | ||
.forEach(function (key) { | ||
if (key && !key.match(regexHash)) { | ||
keys.push(key); | ||
} | ||
}); | ||
/** | ||
* Creates a new object based on an expression representing a single field | ||
* in an object and applies specified value | ||
* | ||
* @param {string} expression - string Javascript expression | ||
* @param {Array|Boolean|Number|Object|string} value - value to assign to the end result of the expression | ||
* @returns {Object} - the object created | ||
**/ | ||
function createObjectWithValue (expression, value) { | ||
let | ||
current, | ||
keys = [], | ||
// matches ], '], "], ][, '][', "][", [, [' and [" | ||
regexHash = /([\'\"]?\]\[[\'\"]?)|(\[[\'\"]?)|([\'\"]?\])/g, | ||
stack = {}, | ||
transform, | ||
atEnd = function (keys, key) { | ||
return keys.indexOf(key) === keys.length - 1; | ||
}; | ||
// in the event hash notation was not used, but periods were... | ||
if (keys.length === 1) { | ||
keys = keys[0].split('.'); | ||
} | ||
if (expression) { | ||
// breaks the expression into an array which represents its stack | ||
expression | ||
.split(regexHash) | ||
.forEach(function (key) { | ||
if (key && !key.match(regexHash)) { | ||
keys.push(key); | ||
} | ||
}); | ||
// define value transform function | ||
transform = defaultTypesMap[keys.join('.')] || function (input) { | ||
return input; | ||
}; | ||
// create an object graph representing string based script notation | ||
keys.forEach(function (key) { | ||
if (!current) { | ||
current = stack; | ||
// in the event hash notation was not used, but periods were... | ||
if (keys.length === 1) { | ||
keys = keys[0].split('.'); | ||
} | ||
current[key] = atEnd(keys, key) ? transform(value) : {}; | ||
current = current[key]; | ||
}); | ||
} | ||
// define value transform function | ||
transform = defaultTypesMap[keys.join('.')] || function (input) { | ||
return input; | ||
}; | ||
return stack; | ||
} | ||
// create an object graph representing string based script notation | ||
keys.forEach(function (key) { | ||
if (!current) { | ||
current = stack; | ||
} | ||
/* * | ||
* Loads JSON configuration data from a text file | ||
* | ||
* @param path = path to JSON configuration file | ||
* @param callback = function (err, configContent) | ||
* */ | ||
function getConfigFileContents(path, callback) { | ||
'use strict'; | ||
var contents; | ||
fs.readFile(path, { encoding : 'UTF8' }, function (err, data) { | ||
if (err) { | ||
return callback(err); | ||
current[key] = atEnd(keys, key) ? transform(value) : {}; | ||
current = current[key]; | ||
}); | ||
} | ||
if (data) { | ||
try { | ||
contents = JSON.parse(data); | ||
} catch (ex) { | ||
err = new Error( | ||
util.format( | ||
'settings-lib: unable to parse JSON config: %s', | ||
ex.message)); | ||
return stack; | ||
} | ||
return callback(err); | ||
} | ||
} | ||
/** | ||
* Loads JSON configuration data from a text file | ||
* | ||
* @param {string} filePath - path to JSON configuration file | ||
* @returns {Promise} - a native Promise that resolves the file content | ||
**/ | ||
function getConfigFileContents(filePath) { | ||
return new Promise((resolve, reject) => { | ||
let | ||
chunks = [], | ||
fileRead = fs.createReadStream(filePath, { encoding : 'utf8' }), | ||
json; | ||
return callback(null, contents); | ||
}); | ||
} | ||
fileRead.on('data', (chunk) => (chunks.push(chunk))); | ||
fileRead.on('end', () => { | ||
try { | ||
json = JSON.parse(chunks.join('')); | ||
} catch (ex) { | ||
return reject( | ||
new Error( | ||
`settings-lib: unable to parse JSON settings: ${ex.message}`)); | ||
} | ||
/* * | ||
* Primary module | ||
* | ||
* | ||
* */ | ||
module.exports = (function (settings) { | ||
'use strict'; | ||
return resolve(json); | ||
}); | ||
function combineConfig(callback) { | ||
cloneAndMerge([ | ||
settings.baseConfig || {}, | ||
settings.environmentConfig || {}, | ||
settings.commandLineConfig || {} | ||
], | ||
function (err, result) { | ||
settings.config = result; | ||
return callback(err); | ||
fileRead.on('error', (err) => { | ||
return reject( | ||
new Error( | ||
`settings-lib: unable to read settings file: ${err.message}`)); | ||
}); | ||
}); | ||
} | ||
function getOptions(options) { | ||
return function (callback) { | ||
cloneAndMerge( | ||
[defaultOptions, options], | ||
function (err, options) { | ||
if (err) { | ||
return callback(err); | ||
} | ||
/** | ||
* Loads base configuration for settings | ||
* | ||
* @returns {Promise} - a native Promise | ||
**/ | ||
function loadBaseConfig () { | ||
return new Promise((resolve, reject) => { | ||
if (!settings.options.baseConfigPath) { | ||
settings.baseConfig = {}; | ||
// real work begins here | ||
settings.options = options; | ||
return callback(); | ||
}); | ||
}; | ||
} | ||
return resolve(); | ||
} | ||
function loadBaseConfig(callback) { | ||
if (settings.options.baseConfigPath) { | ||
getConfigFileContents( | ||
settings.options.baseConfigPath, | ||
function (err, contents) { | ||
if (err) { | ||
return callback(err); | ||
} | ||
// build a mapping for the type of all keys found in settings | ||
return getConfigFileContents(settings.options.baseConfigPath) | ||
.then((contents) => { | ||
buildDefaultTypesMap(contents); | ||
settings.baseConfig = contents; | ||
return callback(); | ||
}); | ||
} else { | ||
settings.baseConfig = {}; | ||
return callback(); | ||
} | ||
return resolve(); | ||
}) | ||
.catch(reject); | ||
}); | ||
} | ||
function loadCommandLineConfig(callback) { | ||
var | ||
arg, | ||
args = process.argv.slice(2), // removes node command | ||
commandLineConfigPath, | ||
i = 0; | ||
/** | ||
* Loads command line configuration file | ||
* | ||
* @returns {Promise} - a native Promise | ||
**/ | ||
function loadCommandLineConfig () { | ||
return new Promise((resolve, reject) => { | ||
let | ||
args = process.argv.slice(DEFAULT_ARGV_START_POSITION), // removes node command | ||
commandLineConfigPath; | ||
// no sense in doing this routine if no command line switches are configured | ||
if (!Array.isArray(settings.options.commandLineSwitches) || | ||
settings.options.commandLineSwitches.length < 1) { | ||
return callback(); | ||
} | ||
// no sense in doing this routine if no command line switches are configured | ||
if (!Array.isArray(settings.options.commandLineSwitches) || | ||
settings.options.commandLineSwitches.length < 1) { | ||
// iterate args from command line | ||
for (; i < args.length; i++) { | ||
arg = args[i]; | ||
return resolve(); | ||
} | ||
if (arg && | ||
settings.options.commandLineSwitches.indexOf(arg) !== -1 && | ||
args.length >= i + 1) { | ||
commandLineConfigPath = args[i + 1]; | ||
break; | ||
// iterate args from command line | ||
args.some((arg, i) => { | ||
if (!arg) { | ||
return false; | ||
} | ||
if (settings.options.commandLineSwitches.indexOf(arg) >= 0 && args[i + 1]) { | ||
commandLineConfigPath = args[i + 1]; | ||
return true; | ||
} | ||
return false; | ||
}); | ||
// if no path was identified, exit out | ||
if (!commandLineConfigPath) { | ||
return resolve(); | ||
} | ||
} | ||
// if a path was identified, load it | ||
if (commandLineConfigPath) { | ||
getConfigFileContents( | ||
commandLineConfigPath, | ||
function (err, contents) { | ||
if (err) { | ||
return callback(err); | ||
} | ||
// if a path was identified, load it | ||
return getConfigFileContents(commandLineConfigPath) | ||
.then((contents) => { | ||
settings.commandLineConfig = contents; | ||
settings.commandLineConfig = contents; | ||
return callback(); | ||
}); | ||
} else { | ||
return callback(); | ||
} | ||
return resolve(); | ||
}) | ||
.catch(reject); | ||
}); | ||
} | ||
function loadCommandLineSwitches(callback) { | ||
var | ||
arg, | ||
args = process.argv.slice(2), // removes node command | ||
commandLineMapKeys = Object.keys(settings.options.readCommandLineMap), | ||
commandLineSettings = [], | ||
i = 0; | ||
/** | ||
* Applies command line switch overrides | ||
* | ||
* @returns {Promise} - a native Promise | ||
**/ | ||
function loadCommandLineSwitches () { | ||
return new Promise((resolve) => { | ||
let | ||
args = process.argv.slice(DEFAULT_ARGV_START_POSITION), // removes node command | ||
commandLineMapKeys = Object.keys(settings.options.readCommandLineMap), | ||
commandLineSettings = []; | ||
if (commandLineMapKeys.length > 0) { | ||
for (; i < args.length; i++) { | ||
arg = args[i]; | ||
// return if there are no configured command line mappings to look for | ||
if (!commandLineMapKeys.length) { | ||
return resolve(); | ||
} | ||
if (arg && | ||
commandLineMapKeys.indexOf(arg) !== -1 && | ||
args.length >= i + 1) { | ||
// iterate each arg and associate to any configured command line overrides | ||
args.forEach((arg, i) => { | ||
if (!arg) { | ||
return; | ||
} | ||
if (commandLineMapKeys.indexOf(arg) >= 0 && args[i + 1]) { | ||
// collect command line override value | ||
commandLineSettings.push( | ||
@@ -324,67 +329,75 @@ createObjectWithValue( | ||
} | ||
} | ||
}); | ||
cloneAndMerge( | ||
[settings.commandLineConfig || {}].concat(commandLineSettings), | ||
function (err, clonedObject) { | ||
settings.commandLineConfig = clonedObject; | ||
return callback(); | ||
}); | ||
} else { | ||
return callback(); | ||
} | ||
settings.commandLineConfig = cloneAndMerge( | ||
[settings.commandLineConfig || {}].concat(commandLineSettings)); | ||
return resolve(); | ||
}); | ||
} | ||
function loadEnvironmentConfig(callback) { | ||
var environmentConfigPath; | ||
/** | ||
* Loads environment configuration file | ||
* | ||
* @returns {Promise} - a native Promise | ||
**/ | ||
function loadEnvironmentConfig () { | ||
return new Promise((resolve, reject) => { | ||
let | ||
environmentOverridePath, | ||
searchEnvironment = | ||
process.env.NODE_ENV && | ||
settings.options.environmentSearchPaths && | ||
Array.isArray(settings.options.environmentSearchPaths); | ||
if (process.env.NODE_ENV && | ||
settings.options.environmentSearchPaths && | ||
Array.isArray(settings.options.environmentSearchPaths)) { | ||
if (!searchEnvironment) { | ||
return resolve(); | ||
} | ||
async.some( | ||
settings.options.environmentSearchPaths, | ||
function (searchPath, next) { | ||
var path = searchPath + | ||
((searchPath.charAt(searchPath.length - 1) !== '/') ? '/' : '') + | ||
process.env.NODE_ENV + | ||
'.json'; | ||
return Promise.race( | ||
settings.options.environmentSearchPaths | ||
.reverse() // reversing paths so that the first found match is used | ||
.map((searchPath) => { | ||
environmentOverridePath = path.resolve([ | ||
path.join(searchPath, process.env.NODE_ENV), | ||
'.json'].join('')); | ||
if (fs.existsSync(path)) { | ||
environmentConfigPath = path; | ||
return checkIfFileExists(environmentOverridePath); | ||
}) | ||
).then((exists) => { | ||
if (!exists) { | ||
return resolve(); | ||
} | ||
return next(environmentConfigPath ? true : false); | ||
}, | ||
function (environmentConfigFound) { | ||
if (environmentConfigFound) { | ||
getConfigFileContents( | ||
environmentConfigPath, | ||
function (err, contents) { | ||
if (err) { | ||
return callback(err); | ||
} | ||
return getConfigFileContents(environmentOverridePath) | ||
.then((contents) => { | ||
settings.environmentConfig = contents; | ||
settings.environmentConfig = contents; | ||
return callback(); | ||
}); | ||
} else { | ||
return callback(); | ||
} | ||
return resolve(); | ||
}) | ||
.catch(reject); | ||
}); | ||
} else { | ||
return callback(); | ||
} | ||
}); | ||
} | ||
function loadEnvironmentVariables(callback) { | ||
var | ||
environmentKeys, | ||
environmentMapKeys = Object.keys(settings.options.readEnvironmentMap), | ||
environmentSettings = []; | ||
/** | ||
* Loads environment variable overrides | ||
* | ||
* @returns {Promise} - a native Promise | ||
**/ | ||
function loadEnvironmentVariables () { | ||
return new Promise((resolve) => { | ||
let | ||
environmentKeys, | ||
environmentMapKeys = Object.keys(settings.options.readEnvironmentMap), | ||
environmentSettings = []; | ||
if (environmentMapKeys.length > 0) { | ||
// return if there are no configured command line mappings to look for | ||
if (!environmentMapKeys.length) { | ||
return resolve(); | ||
} | ||
environmentKeys = Object.keys(process.env); | ||
environmentKeys.forEach(function (key) { | ||
if (environmentMapKeys.indexOf(key) !== -1) { | ||
environmentKeys.forEach((key) => { | ||
if (environmentMapKeys.indexOf(key) >= 0) { | ||
environmentSettings.push( | ||
@@ -397,19 +410,7 @@ createObjectWithValue( | ||
cloneAndMerge( | ||
[settings.environmentConfig || {}].concat(environmentSettings), | ||
function (err, clonedObject) { | ||
settings.environmentConfig = clonedObject; | ||
return callback(); | ||
}); | ||
} else { | ||
return callback(); | ||
} | ||
} | ||
settings.environmentConfig = cloneAndMerge( | ||
[settings.environmentConfig || {}].concat(environmentSettings)); | ||
function reset(callback) { | ||
delete settings.baseConfig; | ||
delete settings.environmentConfig; | ||
delete settings.commandLineConfig; | ||
return callback(); | ||
return resolve(); | ||
}); | ||
} | ||
@@ -421,43 +422,48 @@ | ||
* | ||
* @param {Object} options - an optional set of instructions for initialization | ||
* @param {function} callback - an optional callback function | ||
* @returns {undefined|Promise} - if callback is not supplied, a native Promise is returned | ||
**/ | ||
settings.initialize = function (options, callback) { | ||
if (!callback && typeof options === 'function') { | ||
if (typeof options === 'function') { | ||
callback = options; | ||
options = {}; | ||
} else { | ||
options = options || {}; | ||
} | ||
async.series([ | ||
// clear out existing properties | ||
async.apply(reset), | ||
// ensure options are empty if null or not set | ||
options = options || {}; | ||
// get options all sorted out | ||
async.apply(getOptions(options)), | ||
let exec = co(function *() { | ||
// clean up and prepare internal references | ||
delete settings.baseConfig; | ||
delete settings.environmentConfig; | ||
delete settings.commandLineConfig; | ||
// load base config if one exists | ||
async.apply(loadBaseConfig), | ||
// get input options sorted... | ||
settings.options = cloneAndMerge([defaultOptions, options]); | ||
// load environment configs if they exist | ||
async.apply(loadEnvironmentConfig), | ||
// start loading up and applying all layers of configuration | ||
yield loadBaseConfig(); | ||
yield loadEnvironmentConfig(); | ||
yield loadEnvironmentVariables(); | ||
yield loadCommandLineConfig(); | ||
yield loadCommandLineSwitches(); | ||
// load environment variables where they match config | ||
async.apply(loadEnvironmentVariables), | ||
// prepare the settings | ||
settings.config = cloneAndMerge([ | ||
settings.baseConfig || {}, | ||
settings.environmentConfig || {}, | ||
settings.commandLineConfig || {} | ||
]); | ||
// load command line configs if they exist | ||
async.apply(loadCommandLineConfig), | ||
return settings.config; | ||
}); | ||
// load environment variables where they match config | ||
async.apply(loadCommandLineSwitches), | ||
if (!callback) { | ||
return exec; | ||
} | ||
// combine and reduce config files | ||
async.apply(combineConfig) | ||
], | ||
function (err) { | ||
if (err) { | ||
return callback(err); | ||
} | ||
return callback(null, settings.config); | ||
}); | ||
return exec | ||
.then((result) => (callback(null, result))) | ||
.catch(callback); | ||
}; | ||
@@ -464,0 +470,0 @@ |
@@ -5,6 +5,7 @@ { | ||
"main": "./lib/settings.js", | ||
"version": "0.1.6", | ||
"version": "0.2.0", | ||
"author": "Joshua Thomas (http://github.com/brozeph)", | ||
"license": "MIT", | ||
"engines": { | ||
"node": ">= 0.8.0" | ||
"node": ">=4.0" | ||
}, | ||
@@ -19,18 +20,16 @@ "keywords": [ | ||
"dependencies": { | ||
"async": "^1.2.1" | ||
"co": "^4.6.0" | ||
}, | ||
"devDependencies": { | ||
"chai": "*", | ||
"coveralls": "*", | ||
"jscoverage": "*", | ||
"jshint": "*", | ||
"mocha": "*", | ||
"mocha-lcov-reporter": "*" | ||
"chai": "^3.5.0", | ||
"del": "^2.2.0", | ||
"gulp": "^3.9.1", | ||
"gulp-coveralls": "^0.1.4", | ||
"gulp-eslint": "^2.0.0", | ||
"gulp-istanbul": "^0.10.4", | ||
"gulp-mocha": "^2.2.0" | ||
}, | ||
"scripts": { | ||
"coverage": "rm -rf ./reports ; mkdir -p ./reports ; NODE_SETTINGSLIB_COVERAGE=true mocha -R html-cov -r ./test/common.js -u bdd ./test/lib > reports/coverage.html", | ||
"pretest": "jshint ./lib/*.js ./test/*.js ; jscoverage ./lib/settings.js ./lib-cov/settings.js", | ||
"test": "mocha --check-leaks -R spec -r ./test/common.js -u bdd ./test/lib", | ||
"posttest": "NODE_SETTINGSLIB_COVERAGE=true mocha -R mocha-lcov-reporter -r ./test/common.js -u bdd ./test/lib | ./node_modules/coveralls/bin/coveralls.js" | ||
"test": "gulp test-all" | ||
} | ||
} |
@@ -0,7 +1,14 @@ | ||
var | ||
chai = require('chai'), | ||
settingsLib = require('../../lib/settings.js'), | ||
defaultOptions, | ||
should = chai.should(); | ||
const DEFAULT_ARGV_START_POSITION = 2; | ||
describe('settings', function () { | ||
var | ||
defaultOptions, | ||
settingsLib = requireWithCoverage('settings'); | ||
beforeEach(function () { | ||
@@ -19,3 +26,3 @@ defaultOptions = { | ||
done(); | ||
return done(); | ||
}); | ||
@@ -29,3 +36,3 @@ }); | ||
done(); | ||
return done(); | ||
}); | ||
@@ -42,3 +49,3 @@ }); | ||
done(); | ||
return done(); | ||
}); | ||
@@ -53,5 +60,16 @@ }); | ||
done(); | ||
return done(); | ||
}); | ||
}); | ||
it('should load base config (Promise)', function (done) { | ||
settingsLib.initialize(defaultOptions) | ||
.then((settings) => { | ||
should.exist(settings); | ||
should.exist(settings['test-key']); | ||
return done(); | ||
}) | ||
.catch(done); | ||
}); | ||
}); | ||
@@ -72,3 +90,3 @@ | ||
done(); | ||
return done(); | ||
}); | ||
@@ -90,3 +108,3 @@ }); | ||
done(); | ||
return done(); | ||
}); | ||
@@ -108,3 +126,3 @@ }); | ||
done(); | ||
return done(); | ||
}); | ||
@@ -123,7 +141,7 @@ }); | ||
should.exist(settingsLib.environmentConfig); | ||
settings["test-key"].should.equal('test-value-override'); | ||
settings['test-key'].should.equal('test-value-override'); | ||
delete process.env.NODE_ENV; | ||
done(); | ||
return done(); | ||
}); | ||
@@ -139,7 +157,26 @@ }); | ||
should.not.exist(settingsLib.environmentConfig); | ||
settings["test-key"].should.equal('test-value'); | ||
settings['test-key'].should.equal('test-value'); | ||
done(); | ||
return done(); | ||
}); | ||
}); | ||
it('should use last match if multiple environment override files are found based on environment', function (done) { | ||
process.env.NODE_ENV = 'test'; | ||
defaultOptions.environmentSearchPaths = ['./test', './test/multiple-env-path-test']; | ||
settingsLib.initialize( | ||
defaultOptions, | ||
function (err, settings) { | ||
should.not.exist(err); | ||
should.exist(settings); | ||
should.exist(settings['test-key']); | ||
settings['test-key'].should.equal('test-value-override'); | ||
// clean up | ||
delete process.env.NODE_ENV; | ||
return done(); | ||
}); | ||
}); | ||
}); | ||
@@ -160,5 +197,5 @@ | ||
// clean up | ||
process.argv = process.argv.slice(0, process.argv.length - 2); | ||
process.argv = process.argv.slice(0, process.argv.length - DEFAULT_ARGV_START_POSITION); | ||
done(); | ||
return done(); | ||
}); | ||
@@ -178,5 +215,5 @@ }); | ||
// clean up | ||
process.argv = process.argv.slice(0, process.argv.length - 2); | ||
process.argv = process.argv.slice(0, process.argv.length - DEFAULT_ARGV_START_POSITION); | ||
done(); | ||
return done(); | ||
}); | ||
@@ -195,8 +232,8 @@ }); | ||
should.exist(settingsLib.commandLineConfig); | ||
settings["test-key"].should.equal('test-value-override-again'); | ||
settings['test-key'].should.equal('test-value-override-again'); | ||
// clean up | ||
process.argv = process.argv.slice(0, process.argv.length - 2); | ||
process.argv = process.argv.slice(0, process.argv.length - DEFAULT_ARGV_START_POSITION); | ||
done(); | ||
return done(); | ||
}); | ||
@@ -218,9 +255,9 @@ }); | ||
should.exist(settingsLib.environmentConfig); | ||
settings["test-key"].should.equal('test-value-override-again'); | ||
settings['test-key'].should.equal('test-value-override-again'); | ||
// clean up | ||
process.argv = process.argv.slice(0, process.argv.length - 2); | ||
process.argv = process.argv.slice(0, process.argv.length - DEFAULT_ARGV_START_POSITION); | ||
delete process.env.NODE_ENV; | ||
done(); | ||
return done(); | ||
}); | ||
@@ -240,5 +277,5 @@ }); | ||
// clean up | ||
process.argv = process.argv.slice(0, process.argv.length - 2); | ||
process.argv = process.argv.slice(0, process.argv.length - DEFAULT_ARGV_START_POSITION); | ||
done(); | ||
return done(); | ||
}); | ||
@@ -261,5 +298,5 @@ }); | ||
// clean up | ||
process.argv = process.argv.slice(0, process.argv.length - 2); | ||
process.argv = process.argv.slice(0, process.argv.length - DEFAULT_ARGV_START_POSITION); | ||
done(); | ||
return done(); | ||
}); | ||
@@ -285,3 +322,3 @@ }); | ||
done(); | ||
return done(); | ||
}); | ||
@@ -313,3 +350,3 @@ }); | ||
done(); | ||
return done(); | ||
}); | ||
@@ -325,3 +362,3 @@ }); | ||
process.env.COERCE_ARRAY = '[1,2,3]'; | ||
process.env.COERCE_ARRAY = '[1,,3]'; | ||
process.env.COERCE_BOOL = 'false'; | ||
@@ -351,3 +388,3 @@ process.env.COERCE_NUMBER = '1337'; | ||
done(); | ||
return done(); | ||
}); | ||
@@ -358,3 +395,3 @@ }); | ||
defaultOptions.readEnvironmentMap = { | ||
'APP_SUB_EXTRA_KEY' : 'extra-key.sub-extra-key', | ||
'APP_SUB_EXTRA_KEY' : 'extra-key.sub-extra-key' | ||
}; | ||
@@ -380,3 +417,3 @@ defaultOptions.environmentSearchPaths = ['./test']; | ||
done(); | ||
return done(); | ||
}); | ||
@@ -387,3 +424,3 @@ }); | ||
defaultOptions.readEnvironmentMap = { | ||
'APP_NO_KEY' : 'no-key.sub-no-key', | ||
'APP_NO_KEY' : 'no-key.sub-no-key' | ||
}; | ||
@@ -404,3 +441,3 @@ | ||
done(); | ||
return done(); | ||
}); | ||
@@ -426,3 +463,3 @@ }); | ||
done(); | ||
return done(); | ||
}); | ||
@@ -432,2 +469,4 @@ }); | ||
it('when specified, should read command line switches matching config', function (done) { | ||
var paramCount = 4; | ||
defaultOptions.readCommandLineMap = { | ||
@@ -456,5 +495,5 @@ '--sub-test-key' : 'sub.sub-test-key', | ||
// clean up | ||
process.argv = process.argv.slice(0, process.argv.length - 4); | ||
process.argv = process.argv.slice(0, process.argv.length - paramCount); | ||
done(); | ||
return done(); | ||
}); | ||
@@ -464,4 +503,6 @@ }); | ||
it('should apply command line switches over command line config', function (done) { | ||
var paramCount = 4; | ||
defaultOptions.readCommandLineMap = { | ||
'--sub-extra-key' : 'extra-key.sub-extra-key', | ||
'--sub-extra-key' : 'extra-key.sub-extra-key' | ||
}; | ||
@@ -486,5 +527,5 @@ | ||
// clean up | ||
process.argv = process.argv.slice(0, process.argv.length - 4); | ||
process.argv = process.argv.slice(0, process.argv.length - paramCount); | ||
done(); | ||
return done(); | ||
}); | ||
@@ -495,3 +536,3 @@ }); | ||
defaultOptions.readCommandLineMap = { | ||
'--no-key' : 'no-key.sub-no-key', | ||
'--no-key' : 'no-key.sub-no-key' | ||
}; | ||
@@ -511,5 +552,5 @@ | ||
process.argv = process.argv.slice(0, process.argv.length - 2); | ||
process.argv = process.argv.slice(0, process.argv.length - DEFAULT_ARGV_START_POSITION); | ||
done(); | ||
return done(); | ||
}); | ||
@@ -516,0 +557,0 @@ }); |
Sorry, the diff of this file is not supported yet
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 7 instances in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
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 1 instance in 1 package
38328
15
883
7
35
+ Addedco@^4.6.0
+ Addedco@4.6.0(transitive)
- Removedasync@^1.2.1
- Removedasync@1.5.2(transitive)