Comparing version 3.1.2 to 4.0.0
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" | ||
} | ||
} |
229
README.md
@@ -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 @@ |
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
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
31451
16
634
333
4
15
8
1
+ Addedprecise-typeof@^1.0.2
+ Addeddeeply@2.0.3(transitive)
+ Addedfulcon@1.0.2(transitive)
+ Addedis-buffer@1.1.6(transitive)
+ Addedprecise-typeof@1.0.2(transitive)
- Removeddeeply@1.0.0(transitive)
Updateddeeply@^2.0.1