Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

configly

Package Overview
Dependencies
Maintainers
1
Versions
43
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

configly - npm Package Compare versions

Comparing version 3.1.2 to 4.0.0

compare.js

402

index.js

@@ -1,42 +0,24 @@

var Module = require('module')
, path = require('path')
, fs = require('fs')
, os = require('os')
, merge = require('deeply')
, stripBOM = require('strip-bom')
, compare = require('compare-property')
// local files
var merge = require('deeply')
// sub-modules
, 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')
, notEmpty = require('./lib/not_empty.js')
, parseTokens = require('./lib/parse_tokens.js')
// static
, hostname = process.env['HOST'] || process.env['HOSTNAME'] || os.hostname() || ''
, host = hostname.split('.')[0]
, createNew = require('./lib/create_new.js')
;
// Public API
module.exports = configly;
// singleton cache object
configly._cache = {};
configly._cache = {};
// expose helper functions
configly.load = load;
configly.merge = merge;
// "private" methods
configly._stripBOM = stripBOM;
configly._getFiles = getFiles;
configly._loadFiles = loadFiles;
configly._arrayMerge = arrayMerge;
configly._applyHooks = applyHooks;
configly._resolveDir = resolveDir;
configly._resolveExts = resolveExts;
configly._getCacheKey = getCacheKey;
configly._loadContent = loadContent;
configly._mergeLayers = mergeLayers;
configly._compare = compare;
configly._compareExtensions = compare.ascendingIgnoreCase;
configly.new = createNew;
configly.load = load;
// internal helpers
configly.compareExtensions = compare.ascendingIgnoreCase;
configly.arrayMerge = merge.adapters.array;
// defaults
configly.DEFAULTS = {
configly.defaults = {
directory : './config',

@@ -48,353 +30,13 @@ environment : 'development',

// filename chunk separator
configly.SEPARATOR = '-';
configly.separator = '-';
// file base names
configly.FILES = [
'default',
'', // allow suffixes as a basename (e.g. `environment`)
host,
hostname,
'local',
configly.DEFAULTS.customEnvVars,
'runtime'
];
configly.parsers = parsers;
// by default use just `js` and `json` parsers
configly.PARSERS = {
js : jsCompile,
json: JSON.parse
};
// post-processing hooks
// matched by the filename prefix
configly.HOOKS = {};
configly.HOOKS[configly.DEFAULTS.customEnvVars] = envVars;
configly.hooks = {};
configly.hooks[configly.defaults.customEnvVars] = envVars;
/**
* Returns config object from the cache (determined by `dir`/`parsers` arguments),
* if no cache record found, invokes `configly.load` function and caches the result
* By default it loads JSON files, also custom pluggable parsers are supported.
*
* @param {string} directory - directory to search for config files within
* @param {object} [parsers] - custom parsers to use to search and load config files with
* @returns {object} - result merged config object
*/
function configly(directory, parsers)
{
var cacheKey;
// fallback to defaults
directory = directory || configly.DEFAULTS.directory;
parsers = parsers || configly.PARSERS;
// prepare cache key
cacheKey = configly._getCacheKey(directory, parsers);
if (!configly._cache[cacheKey])
{
configly.load(directory, parsers);
}
// return immutable copy
return merge(configly._cache[cacheKey]);
}
/**
* Loads config objects from several environemnt-derived files
* and merges them in specific order.
* By default it loads JSON files, also custom pluggable parsers are supported.
*
* @param {string} directory - directory to search for config files within
* @param {object} [parsers] - custom parsers to use to search and load config files with
* @returns {object} - result merged config object
*/
function load(directory, parsers)
{
var files
, layers
, cacheKey
;
// fallback to defaults
directory = directory || configly.DEFAULTS.directory;
parsers = parsers || configly.PARSERS;
// prepare cache key
cacheKey = configly._getCacheKey(directory, parsers);
// get config files names suitable for the situation
files = configly._getFiles(process.env);
// load all available files
layers = configly._loadFiles(directory, files, parsers);
// merge loaded layers
configly._cache[cacheKey] = configly._mergeLayers(layers);
// return immutable copy
return merge(configly._cache[cacheKey]);
}
/**
* Creates list of files to load configuration from
* derived from the passed environment-like object
*
* @param {object} env - environment-like object (e.g. `process.env`)
* @returns {array} - ordered list of config files to load
*/
function getFiles(env)
{
var files = []
, environment = env['NODE_ENV'] || configly.DEFAULTS.environment
, appInstance = env['NODE_APP_INSTANCE']
;
// generate config files variations
configly.FILES.forEach(function(baseName)
{
// check for variables
// keep baseName if no variables found
baseName = parseTokens(baseName, env) || baseName;
// add base name with available suffixes
addWithSuffixes(files, baseName, appInstance);
addWithSuffixes(files, baseName, environment, appInstance);
});
return files;
}
/**
* Loads and parses config from available files
*
* @param {string} dir - directory to search in
* @param {array} files - list of files to search for
* @param {object} parsers - list of extensions to use with parsers for each
* @returns {array} - list of loaded configs in order of provided files
*/
function loadFiles(dir, files, parsers)
{
// sort extensions to provide deterministic order of loading
var extensions = configly._resolveExts(parsers)
, layers = []
;
files.forEach(function(filename)
{
var layer = {file: filename, exts: []};
extensions.forEach(function(ext)
{
var cfg, file = path.resolve(dir, filename + '.' + ext);
if (fs.existsSync(file))
{
cfg = configly._loadContent(file, parsers[ext]);
// check if any hooks needed to be applied
cfg = configly._applyHooks(cfg, filename);
layer.exts.push({
ext : ext,
config: cfg
});
}
});
// populate with non-empty layers only
if (layer.exts.length)
{
layers.push(layer);
}
});
return layers;
}
/**
* Loads and parses provided file (synchronious).
*
* @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 filepath 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;
}
/**
* Applies matched hooks
*
* @param {object} config - config object to apply hooks to
* @param {string} filename - base filename to match hooks against
* @returns {object} - modified config object
*/
function applyHooks(config, filename)
{
Object.keys(configly.HOOKS).forEach(function(hook)
{
// in order to match hook should either the same length
// as the filename or smaller
if (filename.substr(0, hook.length) === hook)
{
config = configly.HOOKS[hook](config);
}
});
return config;
}
/**
* Merges provided layers into a single config object,
* respecting order of the layers
*
* @param {array} layers - list of config objects
* @returns {object} - single config object
*/
function mergeLayers(layers)
{
var result = {};
layers.forEach(function(layer)
{
layer.exts.forEach(function(cfg)
{
// have customizable's array merge function
result = merge(result, cfg.config, configly._arrayMerge);
});
});
return result;
}
/**
* Adds new element to the list,
* adds provided suffixes iteratively
* adding them to the baseName (if not empty)
* Also checks for duplicates
*
* @param {array} list - array to add element to
* @param {string} baseName - element to compare against, affects rest of the arguments
* @param {...string} [suffix] - additional suffixes to use with the `baseName`
*/
function addWithSuffixes(list, baseName)
{
var suffixes = Array.prototype.slice.call(arguments, 2);
// don't push empty baseName by itself
notEmpty(baseName) && pushUniquely(list, baseName);
suffixes.forEach(function(suffix)
{
// filter out empty suffixes
// and extend baseName
if (notEmpty(suffix))
{
baseName += (baseName ? configly.SEPARATOR : '') + suffix;
pushUniquely(list, baseName);
}
});
}
/**
* Pushes element into the array,
* only if such element is not there yet
*
* @param {array} list - array to add element into
* @param {mixed} element - element to add
*/
function pushUniquely(list, element)
{
if (list.indexOf(element) == -1)
{
list.push(element);
}
}
/**
* Resolves `dir` argument into a absolute path
*
* @param {string} dir - directory to resolve
* @returns {string} - absolute path to the directory
*/
function resolveDir(dir)
{
return path.resolve(dir);
}
/**
* Resolves parsers object into a string
* of sorted extensions
*
* @param {object} parsers - list of parsers with corresponding extensions
* @returns {string} - sorted extensions string (e.g. `cson,json,zson`)
*/
function resolveExts(parsers)
{
return Object.keys(parsers).sort(configly._compareExtensions);
}
/**
* Generates cache key from the search directory
* and provided list of parsers
*
* @param {string} directory - search directory
* @param {object} parsers - list of parsers
* @returns {string} - cache key
*/
function getCacheKey(directory, parsers)
{
return configly._resolveDir(directory) + ':' + configly._resolveExts(parsers).join(',');
}
/**
* Compiles js content in the manner it's done
* in the node itself
*
* @param {string} content - file's content
* @param {string} file - full path of the file
* @returns {mixed} - result javascript object
*/
function jsCompile(content, file)
{
// make it as a child of this module
// Would be nice to actually make it transparent
// and pretend it to be child of the caller module
// but there is no obvious way, yet
var jsMod = new Module(file, module);
jsMod._compile(content, file);
// return just exported object
return jsMod.exports;
}
/**
* Default array merge strategy – replace `a` with `b`
*
* @param {mixed} a - one array element
* @param {mixed} b - another array element
* @returns {mixed} - result of the merge of the array
*/
function arrayMerge(a, b)
{
return b;
}
// Public API
// return protected copy
module.exports = configly.new();
{
"name": "configly",
"version": "3.1.2",
"version": "4.0.0",
"description": "A developer-friendly lightweight replacement for the 'config' module that works with custom config directory and pluggable parsers",

@@ -8,3 +8,4 @@ "main": "index.js",

"lint": "eslint index.js test/*.js",
"test": "nyc --reporter=lcov --reporter=text --check-coverage --lines 99 --functions 99 --branches 99 tap test/*.js"
"test": "nyc --reporter=lcov --reporter=text --check-coverage --lines 99 --functions 99 --branches 99 tape test/*.js | tap-spec",
"debug": "tape test/*.js | tap-spec"
},

@@ -31,2 +32,4 @@ "pre-commit": [

"directory",
"immutable",
"instance",
"pluggable",

@@ -52,3 +55,4 @@ "parser",

"compare-property": "^2.0.0",
"deeply": "^1.0.0",
"deeply": "^2.0.1",
"precise-typeof": "^1.0.2",
"strip-bom": "^2.0.0"

@@ -58,16 +62,17 @@ },

"coffee-script": "^1.10.0",
"coveralls": "^2.11.6",
"coveralls": "^2.11.9",
"cson": "^3.0.2",
"eslint": "^2.2.0",
"hjson": "^1.7.6",
"eslint": "^2.9.0",
"hjson": "^1.8.4",
"ini": "^1.3.4",
"js-yaml": "^3.5.3",
"json5": "^0.4.0",
"nyc": "^5.6.0",
"js-yaml": "^3.6.0",
"json5": "^0.5.0",
"nyc": "^6.4.2",
"pre-commit": "^1.1.2",
"properties": "^1.2.1",
"sinon": "^1.17.3",
"tap": "^5.6.0",
"sinon": "^1.17.4",
"tap-spec": "^4.1.1",
"tape": "^4.5.1",
"toml": "^2.3.0"
}
}

@@ -5,4 +5,4 @@ # 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-5.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-5.x&style=flat)](https://ci.appveyor.com/project/alexindigo/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)
[![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)

@@ -12,2 +12,4 @@ [![Coverage Status](https://img.shields.io/coveralls/alexindigo/configly/master.svg?label=code+coverage&style=flat)](https://coveralls.io/github/alexindigo/configly?branch=master)

[![bitHound Overall Score](https://www.bithound.io/github/alexindigo/configly/badges/score.svg)](https://www.bithound.io/github/alexindigo/configly)
*Notice of change of ownership: Starting version 3.0.0 this package has changed it's owner and goals. The old version (2.0.3) is still available on npm via `npm install configly@2.0.3` and on [github.com/ksmithut/configly](https://github.com/ksmithut/configly). Thank you.*

@@ -60,21 +62,48 @@

// will be alphabetically sorted
config.PARSERS = {
ini : ini.parse,
// have it as a wrapper to prevent extra arguments leaking
cson : function(str) { return cson.parse(str); },
yml : function(str) { return yaml.safeLoad(str); },
// same options as used within `config` module
properties: function(str) { return properties.parse(str, {namespaces: true, variables: true, sections: true}); },
// use json5 instead of `JSON.parse`
json : json5.parse
// keep the original one
js : config.PARSERS.js,
};
var configObj = config();
var configObj = config({
parsers: {
ini : ini.parse,
// have it as a wrapper to prevent extra arguments leaking
cson : function(str) { return cson.parse(str); },
yml : function(str) { return yaml.safeLoad(str); },
// same options as used within `config` module
properties: function(str) { return properties.parse(str, {namespaces: true, variables: true, sections: true}); },
// use json5 instead of `JSON.parse`
json : json5.parse,
// keep the original one
js : config.parsers.js
}
});
```
Since `configly` is a singleton, this setup could be done in your index file,
and the rest of the files would use it the same way as in the "Basic" example.
Or create new instance with new defaults
```javascript
var configNew = config.new({
parsers: {
ini : ini.parse,
// have it as a wrapper to prevent extra arguments leaking
cson : function(str) { return cson.parse(str); },
yml : function(str) { return yaml.safeLoad(str); },
// same options as used within `config` module
properties: function(str) { return properties.parse(str, {namespaces: true, variables: true, sections: true}); },
// use json5 instead of `JSON.parse`
json : json5.parse,
// keep the original one
js : config.parsers.js
}
});
// use it as usual
var configObj = configNew();
```
You can export newly created instance and reuse it all over your app,
it won't be affected by other instances of the `configly` even if it
used in dependencies of your app, or you module is part of the bigger app,
that uses `configly`.
### Custom Config Directory

@@ -85,3 +114,3 @@

```javascript
var config = require('configly')('./etc'); // `require('configly')('etc');` would work the same way`
var config = require('configly')('./etc'); // `require('configly')('etc');` would work the same way
```

@@ -104,10 +133,14 @@

```javascript
// index.js
// config.js
var path = require('path');
var configly = require('configly');
configly.DEFAULTS.directory = path.join(__dirname, 'etc');
module.exports = configly.new({
defaults: {
directory: path.join(__dirname, 'etc')
}
});
// app.js
var config = require('configly')();
var config = require('./config')();
```

@@ -124,15 +157,155 @@

var appConfig = configly(path.join(__dirname, 'app-config'));
// for example you have .ini config files there
var rulesConfig = configly(path.join(__dirname, 'rules-config'), {ini: ini.parse});
// "inline"
var oneConfig = configly([
path.join(__dirname, 'app-config'),
path.join(__dirname, 'rules-config')
]);
```
If there is a need to merge standalone config objects into one,
you can use `configly.merge` method manually,
in the order that suites your specific use case.
Or creating new default
```javascript
var oneConfig = configly.merge(appConfig, rulesConfig);
module.exports = configly.new({
defaults: {
directory: [
path.join(__dirname, 'app-config'),
path.join(__dirname, 'rules-config')
]
}
});
```
### Custom Files
Also `configly` can load config data from custom files (along with the [default list](lib/create_new.js#L105)),
handling them the same way – search for supported extensions and within specified directory(-ies).
```javascript
var config = configly({
files: configly.files.concat(['custom_file_A', 'custom_file_B'])
});
```
Following code will completely replace list of filenames.
```javascript
var config = configly('/etc', {
files: [
'totally',
'custom',
'list',
'of',
'files'
]
});
```
For use cases where you need to load config files within the app,
but augment it with server/environment specific config
you can add absolute path filename to the files list.
```javascript
var config = configly(path.join(__dirname, 'config'), {
// `configly` will search for `/etc/consul/env.js`, `/etc/consul/env.json`, etc
// after loading default files from local `config` folder
files: configly.files.concat('/etc/consul/env')
});
```
For bigger apps / more complex configs, combination of multiple directories
and custom files would provide needed functionality.
```javascript
var path = require('path')
, configly = require('configly')
, package = require('./package.json')
;
module.exports = configly.new({
defaults: {
directory: [
// will search local config directory
path.join(__dirname, 'config'),
// and augment with same files
// from environment specific folder
'/etc/consul'
]
},
// also will try to load config files matching current app name
// e.g. 'my-app.js', `my-app.json`, `my-app-production.js`, `my-app-production.json`,
// from both local config folder and `/etc/consul`
files: configly.files.concat(package.name)
});
```
### Migration From `config`
To fully replicate `config`'s behavior and provide easy way to include static customized config
in your app files, without resorting to `require('../../../config')`, you can create virtual node module,
based on the custom config file within your app.
#### Step 1. Create config file that exports static config object (with your custom rules)
`config/config.js`
```javascript
var path = require('path')
, configly = require('configly')
, ini = require('ini')
, yaml = require('js-yaml')
;
// run configly once with inlined modifiers
// and have it as node-cached module
module.exports = configly({
defaults: {
directory: [
// will search local config directory
'./etc',
// and augment with same files
// from environment specific folder
'/etc/consul'
]
},
// also will try to load config files matching current app name
// e.g. `my-app.ini`, 'my-app.js', `my-app.json`, `my-app.yml`,
// `my-app-production.ini`, `my-app-production.js`, `my-app-production.json`, `my-app-production.yml`,
// from both local config folder and `/etc/consul`
files: configly.files.concat('my-app'),
// throw in custom parsers as well
parsers: {
ini : ini.parse,
// have it as a wrapper to prevent extra arguments leaking
yml : function(str) { return yaml.safeLoad(str); }
}
});
```
#### Step 2. Add `package.json` for your virtual node module
`config/package.json`
```json
{
"name": "config",
"version": "0.0.0",
"main": "config.js"
}
```
#### Step 3. Add virtual node module to your app's `package.json`
```json
"dependencies": {
"config": "./config"
},
```
Now npm will copy `./config/` files into `node_modules` and execute `./config/config.js` on first require,
making it's output available for every file of your app, via `var config = require('config')`.
This way migration of your app from `config` module to `configly` will be limited to a few extra lines of code,
while providing more functionality and better separation of concerns out of the box.
### More Examples

@@ -139,0 +312,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc