Comparing version 4.0.3 to 4.1.0
27
index.js
@@ -1,10 +0,11 @@ | ||
var merge = require('deeply') | ||
var merge = require('deeply') | ||
// sub-modules | ||
, compare = require('./compare.js') | ||
, parsers = require('./parsers.js') | ||
, compare = require('./compare.js') | ||
, parsers = require('./parsers.js') | ||
// library files | ||
, configly = require('./lib/configly.js') | ||
, load = require('./lib/load.js') | ||
, envVars = require('./lib/env_vars.js') | ||
, createNew = require('./lib/create_new.js') | ||
, configly = require('./lib/configly.js') | ||
, load = require('./lib/load.js') | ||
, envVars = require('./lib/env_vars.js') | ||
, includeFiles = require('./lib/include_files.js') | ||
, createNew = require('./lib/create_new.js') | ||
; | ||
@@ -24,7 +25,12 @@ | ||
configly.defaults = { | ||
directory : './config', | ||
environment : 'development', | ||
customEnvVars: 'custom-environment-variables' | ||
directory : './config', | ||
environment : 'development', | ||
customEnvVars : 'custom-environment-variables', | ||
customIncludeFiles: 'custom-include-files' | ||
}; | ||
// no specific directory set (yet) | ||
// fallback to the default one | ||
configly.directory = null; | ||
// filename chunk separator | ||
@@ -39,2 +45,3 @@ configly.separator = '-'; | ||
configly.hooks[configly.defaults.customEnvVars] = envVars; | ||
configly.hooks[configly.defaults.customIncludeFiles] = includeFiles; | ||
@@ -41,0 +48,0 @@ // Public API |
@@ -13,3 +13,4 @@ // Public API | ||
{ | ||
Object.keys(this.hooks).forEach(function(hook) | ||
// sort hooks short first + alphabetically | ||
Object.keys(this.hooks).sort(compareHooks).forEach(function(hook) | ||
{ | ||
@@ -20,3 +21,3 @@ // in order to match hook should either the same length | ||
{ | ||
config = this.hooks[hook](config); | ||
config = this.hooks[hook].call(this, config); | ||
} | ||
@@ -27,1 +28,13 @@ }.bind(this)); | ||
} | ||
/** | ||
* Compares hook names shorter to longer and alphabetically | ||
* | ||
* @param {string} a - first key | ||
* @param {string} b - second key | ||
* @returns {number} -1 - `a` comes first, 0 - positions unchanged, 1 - `b` comes first | ||
*/ | ||
function compareHooks(a, b) | ||
{ | ||
return a.length < b.length ? -1 : (a.length > b.length ? 1 : (a < b ? -1 : (a > b ? 1 : 0))); | ||
} |
@@ -31,17 +31,16 @@ var typeOf = require('precise-typeof') | ||
// create new running context with custom options | ||
context = this.new(options || {}); | ||
// fallback to default directory | ||
directory = directory || context.defaults.directory; | ||
context = this.new(merge(options || {}, {directory: directory || this.defaults.directory})); | ||
// prepare cache key | ||
cacheKey = getCacheKey.call(context, directory); | ||
cacheKey = getCacheKey.call(context); | ||
if (!this._cache[cacheKey]) | ||
if (!(cacheKey in this._cache)) | ||
{ | ||
this.load(directory, context); | ||
this.load(context); | ||
} | ||
// return immutable copy | ||
return merge(this._cache[cacheKey]); | ||
// always return object here | ||
return merge(this._cache[cacheKey] || {}); | ||
} |
@@ -117,2 +117,3 @@ var os = require('os') | ||
'local', | ||
this.defaults.customIncludeFiles, | ||
this.defaults.customEnvVars, | ||
@@ -119,0 +120,0 @@ 'runtime' |
var getVar = require('./get_var.js') | ||
, isObject = require('./is_object.js') | ||
, isEmpty = require('./is_empty.js') | ||
, notEmpty = require('./not_empty.js') | ||
@@ -27,3 +29,3 @@ , parseTokens = require('./parse_tokens.js') | ||
// try simple match first, for non-empty value | ||
value = getVar(config[key], process.env); | ||
value = getVar.call(this, config[key], process.env); | ||
@@ -33,3 +35,3 @@ // if not, try parsing for variables | ||
{ | ||
value = parseTokens(config[key], process.env); | ||
value = parseTokens.call(this, config[key], process.env); | ||
} | ||
@@ -50,3 +52,3 @@ | ||
{ | ||
envVars(config[key], backRef.concat(key)); | ||
envVars.call(this, config[key], backRef.concat(key)); | ||
} | ||
@@ -58,27 +60,5 @@ // Unsupported entry type | ||
} | ||
}); | ||
}.bind(this)); | ||
return config; | ||
} | ||
/** | ||
* Check if provided variable is really an object | ||
* | ||
* @param {mixed} obj - variable to check | ||
* @returns {boolean} true if variable is real object, false otherwise | ||
*/ | ||
function isObject(obj) | ||
{ | ||
return (obj !== null) && (typeof obj == 'object') && !(Array.isArray(obj)); | ||
} | ||
/** | ||
* Shortcut for positive checks | ||
* | ||
* @param {string} str - string to check | ||
* @returns {boolean} - true is string is empty, false otherwise | ||
*/ | ||
function isEmpty(str) | ||
{ | ||
return !notEmpty(str); | ||
} |
@@ -14,3 +14,3 @@ var path = require('path') | ||
* | ||
* @param {string} directory - search directory | ||
* @param {string} [directory] - search directory | ||
* @returns {string} - cache key | ||
@@ -20,2 +20,5 @@ */ | ||
{ | ||
// use the one from the context | ||
directory = directory || this.directory; | ||
return resolveDir(directory) | ||
@@ -22,0 +25,0 @@ + ':' |
126
lib/load.js
@@ -1,11 +0,7 @@ | ||
var path = require('path') | ||
, fs = require('fs') | ||
, merge = require('deeply') | ||
var merge = require('deeply') | ||
, typeOf = require('precise-typeof') | ||
, stripBOM = require('stripbom') | ||
, applyHooks = require('./apply_hooks.js') | ||
, getCacheKey = require('./get_cache_key.js') | ||
, getFiles = require('./get_files.js') | ||
, loadFiles = require('./load_files.js') | ||
, mergeLayers = require('./merge_layers.js') | ||
, resolveExts = require('./resolve_exts.js') | ||
; | ||
@@ -21,4 +17,4 @@ | ||
* | ||
* @param {string} directory - directory to search for config files within | ||
* @param {object} [context] - custom context to use for search and loading config files | ||
* @param {string} [directory] - directory to search for config files within | ||
* @param {function|object} [context] - custom context to use for search and loading config files | ||
* @returns {object} - result merged config object | ||
@@ -33,11 +29,21 @@ */ | ||
context = context || {}; | ||
// function|object passed as the first argument | ||
// consider it `context` | ||
if (typeOf(directory) == 'function' || typeOf(directory) == 'object') | ||
{ | ||
context = directory; | ||
directory = context.directory; | ||
} | ||
// create new context | ||
// in case if function called directly with options object | ||
context = typeof context == 'function' ? context : this.new(context || {}); | ||
// in case if `load` called without context | ||
context = typeOf(context) == 'function' ? context : this.new(context); | ||
// fallback to defaults | ||
directory = directory || context.defaults.directory; | ||
context.directory = directory || context.directory || context.defaults.directory; | ||
// prepare cache key | ||
cacheKey = getCacheKey.call(context, directory); | ||
cacheKey = getCacheKey.call(context); | ||
@@ -48,3 +54,3 @@ // get config files names suitable for the situation | ||
// load all available files | ||
layers = loadFiles.call(context, directory, files); | ||
layers = loadFiles.call(context, context.directory, files); | ||
@@ -57,95 +63,1 @@ // merge loaded layers | ||
} | ||
/** | ||
* Loads and parses config from available files | ||
* | ||
* @param {string} dirs - directory to search in | ||
* @param {array} files - list of files to search for | ||
* @returns {array} - list of loaded configs in order of provided files | ||
*/ | ||
function loadFiles(dirs, files) | ||
{ | ||
// sort extensions to provide deterministic order of loading | ||
var _instance = this | ||
, extensions = resolveExts.call(this) | ||
, layers = [] | ||
; | ||
// treat all inputs as list of directories | ||
dirs = (typeOf(dirs) == 'array') ? dirs : [dirs]; | ||
files.forEach(function(filename) | ||
{ | ||
var layer = {file: filename, exts: []}; | ||
extensions.forEach(function(ext) | ||
{ | ||
var layerExt = {ext: ext, dirs: []}; | ||
dirs.forEach(function(dir) | ||
{ | ||
var cfg, file = path.resolve(dir, filename + '.' + ext); | ||
if (fs.existsSync(file)) | ||
{ | ||
cfg = loadContent.call(_instance, file, _instance.parsers[ext]); | ||
// check if any hooks needed to be applied | ||
cfg = applyHooks.call(_instance, cfg, filename); | ||
} | ||
if (cfg) | ||
{ | ||
layerExt.dirs.push({ | ||
dir : dir, | ||
config: cfg | ||
}); | ||
} | ||
}); | ||
// populate with non-empty layers only | ||
if (layerExt.dirs.length) | ||
{ | ||
layer.exts.push(layerExt); | ||
} | ||
}); | ||
// populate with non-empty layers only | ||
if (layer.exts.length) | ||
{ | ||
layers.push(layer); | ||
} | ||
}); | ||
return layers; | ||
} | ||
/** | ||
* Loads and parses provided file (synchronous). | ||
* | ||
* @param {string} file - absolute path to the file | ||
* @param {function} parser - function to parse provided content and return config object | ||
* @returns {object} - parsed config object | ||
*/ | ||
function loadContent(file, parser) | ||
{ | ||
var content, config; | ||
try | ||
{ | ||
content = stripBOM(fs.readFileSync(file, {encoding: 'utf8'})); | ||
// provide file path as the second argument for complex parsing, | ||
// also it matches `module._compile` nodejs API. | ||
// Note: JSON.parse accepts two arguments, but ignores anything | ||
// but function on the second place, so it's safe to pass a filename | ||
// Other parsers might need to have wrappers. | ||
config = parser(content, file); | ||
} | ||
catch (e) | ||
{ | ||
throw new Error('Config file ' + file + ' cannot be read or malformed.'); | ||
} | ||
return config; | ||
} |
@@ -16,3 +16,3 @@ var merge = require('deeply'); | ||
var _instance = this | ||
, result = {} | ||
, result = null | ||
; | ||
@@ -30,3 +30,3 @@ | ||
'array': _instance.arrayMerge | ||
}, result, cfg.config); | ||
}, result || {}, cfg.config); | ||
}); | ||
@@ -36,3 +36,5 @@ }); | ||
// return `null` if noting found | ||
// and let downstream layers to make the decision | ||
return result; | ||
} |
@@ -26,3 +26,3 @@ var getVar = require('./get_var.js') | ||
{ | ||
var value = getVar(name, env); | ||
var value = getVar.call(this, name, env); | ||
@@ -35,3 +35,3 @@ // need to have `found` counter | ||
return value; | ||
}); | ||
}.bind(this)); | ||
} | ||
@@ -38,0 +38,0 @@ |
{ | ||
"name": "configly", | ||
"version": "4.0.3", | ||
"version": "4.1.0", | ||
"description": "A developer-friendly lightweight replacement for the 'config' module that works with custom config directory and pluggable parsers", | ||
@@ -8,2 +8,3 @@ "main": "index.js", | ||
"lint": "eslint index.js test/*.js", | ||
"ci-lint": "is-node-modern && npm run lint || is-node-not-modern", | ||
"test": "nyc --reporter=lcov --reporter=text --check-coverage --lines 99 --functions 99 --branches 99 tape test/*.js | tap-spec", | ||
@@ -58,3 +59,3 @@ "debug": "tape test/*.js | tap-spec" | ||
"deeply": "^2.0.3", | ||
"fulcon": "^1.0.1", | ||
"fulcon": "^1.0.2", | ||
"precise-typeof": "^1.0.2", | ||
@@ -64,18 +65,19 @@ "stripbom": "^3.0.0" | ||
"devDependencies": { | ||
"coffee-script": "^1.10.0", | ||
"coveralls": "^2.11.12", | ||
"cson": "^3.0.2", | ||
"eslint": "^2.13.1", | ||
"hjson": "^2.0.5", | ||
"coffee-script": "^1.11.1", | ||
"coveralls": "^2.11.14", | ||
"cson-parser": "^1.3.4", | ||
"eslint": "^3.8.0", | ||
"hjson": "^2.3.1", | ||
"ini": "^1.3.4", | ||
"is-node-modern": "^1.0.0", | ||
"js-yaml": "^3.6.1", | ||
"json5": "^0.5.0", | ||
"nyc": "^8.1.0", | ||
"nyc": "^8.3.1", | ||
"pre-commit": "^1.1.3", | ||
"properties": "^1.2.1", | ||
"sinon": "^1.17.5", | ||
"sinon": "^1.17.6", | ||
"tap-spec": "^4.1.1", | ||
"tape": "^4.6.0", | ||
"tape": "^4.6.2", | ||
"toml": "^2.3.0" | ||
} | ||
} |
104
README.md
@@ -6,2 +6,3 @@ # configly [![NPM Module](https://img.shields.io/npm/v/configly.svg?style=flat)](https://www.npmjs.com/package/configly) | ||
[![Linux Build](https://img.shields.io/travis/alexindigo/configly/master.svg?label=linux:0.10-6.x&style=flat)](https://travis-ci.org/alexindigo/configly) | ||
[![MacOS Build](https://img.shields.io/travis/alexindigo/configly/master.svg?label=macos:0.10-6.x&style=flat)](https://travis-ci.org/alexindigo/configly) | ||
[![Windows Build](https://img.shields.io/appveyor/ci/alexindigo/configly/master.svg?label=windows:0.10-6.x&style=flat)](https://ci.appveyor.com/project/alexindigo/configly) | ||
@@ -53,3 +54,5 @@ | ||
var ini = require('ini'); | ||
var cson = require('cson'); | ||
// Note: recommended to use `cson-parser` over `cson` | ||
// due to https://github.com/groupon/cson-parser/issues/56 | ||
var cson = require('cson-parser'); | ||
var yaml = require('js-yaml'); | ||
@@ -236,2 +239,95 @@ var properties = require('properties'); | ||
### Custom Environment Variables | ||
It allows to combine environment variables within a single entry (e.g. `"endpoint": "${REMOTE_HOST}:${REMOTE_PORT}"`), which helps to keep application config consumption simple and to the point, and gives greater control over different environments, like using Docker environment variables for linked containers. | ||
`custom-environment-variables.json`: | ||
```json | ||
{ | ||
"Customers": | ||
{ | ||
"dbPassword" :"ENV_VAR_NAME_AS_STRING", | ||
"dbPassword2" :"${ENV_VAR_NAME_VARIABLE}" | ||
}, | ||
"CombinedWithText" : "${VARPART1}:${VARPART2}", | ||
"BothVarsEmptyWillBeSkippedCompletely" : "${NOEXISTING} + another ${EMPTY} var" | ||
} | ||
``` | ||
### Custom Include Files | ||
For better integration with other configuration systems and third-party tools that generate configs, `configly` can "mount" custom config files into specified entries, for example if you need to pull webpack manifest files into app's config). | ||
`custom-include-files.json`: | ||
```json | ||
{ | ||
"MyModule": { | ||
"WillBeIgnored": "NONEXISTENT_FILE", | ||
"and": { | ||
"here": { | ||
"some": { | ||
"nested": { | ||
"structure": "custom_file" | ||
} | ||
} | ||
} | ||
} | ||
}, | ||
"AnotherModule": { | ||
"yes": { | ||
"it": { | ||
"should": { | ||
"be": { | ||
"filename": { | ||
"no": { | ||
"extension": "../include_files/another_file" | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
``` | ||
In the above example it will check for following custom files, (given `NODE_ENV` equals `production`): | ||
- `custom_file.js` | ||
- `custom_file.json` | ||
- `custom_file-production.js` | ||
- `custom_file-production.json` | ||
- `../include_files/another_file.js` | ||
- `../include_files/another_file.json` | ||
- `../include_files/another_file-production.js` | ||
- `../include_files/another_file-production.json` | ||
And will attach their content to the respective branches. | ||
### Custom Hooks | ||
Developers can extend `configly`'s functionality with custom hooks for config files. For example if you need to pull in sensitive into your config and it couldn't be stored on disk among rest of the configuration data, you can add your own hooks to relace placeholders or attach extra values with data pull from the environment or from a secure storage. All hooks should be synchronous. | ||
```javascript | ||
var customHooks = merge(configly.hooks, { | ||
// will be applied to `local`, `local-development` and other `local*` files | ||
// expects `config` object of the parsed file | ||
local: function(config) | ||
{ | ||
iterate(config, function(value, key, node) | ||
{ | ||
// increments each value by 4 | ||
node[key] = value + 4; | ||
}); | ||
// expected to return updated object | ||
return config; | ||
} | ||
}); | ||
var config = configly(configDir, {hooks: customHooks}); | ||
``` | ||
### Migration from `config` | ||
@@ -374,4 +470,5 @@ | ||
- Configly provides deterministic (and controllable) order of the file extensions it loads from. | ||
- Configly provides post-load hooks for config files, (e.g. `custom-environment-variables` works via this mechanism). | ||
- Configly provides post-load hooks for config files, (e.g. `custom-environment-variables` and `custom-include-files` work via this mechanism). | ||
- Configly provides ability to combine environment variables within one entry (e.g. `"endpoint": "${REMOTE_HOST}:${REMOTE_PORT}"`). | ||
- Configly provides ability to "mount" custom config files into specified entries (useful to pull webpack manifest files into app's config). | ||
- Configly provides access to the underlying functions and defaults, allowing to utilize parts of the functionality for greater flexibility. | ||
@@ -387,5 +484,4 @@ | ||
## License | ||
Configly is licensed under the MIT license. | ||
Configly is released under the MIT license. |
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
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
40198
22
735
483
16
Updatedfulcon@^1.0.2