Comparing version 0.1.4 to 0.2.0
@@ -8,2 +8,15 @@ # Confort Changelog | ||
## [v0.2.0] - 2021-01-18 | ||
### Added | ||
- support for [Deno](https://deno.land/) | ||
### Changed | ||
- entire interface to a simple funcion (`confort(...layers)`) | ||
### Removed | ||
- reloading capabilities | ||
- layer history | ||
- support to CSON format | ||
## [v0.1.4] - 2020-08-07 | ||
@@ -40,1 +53,2 @@ | ||
[v0.1.4]: https://gitlab.com/GCSBOSS/confort/-/tags/v0.1.4 | ||
[v0.2.0]: https://gitlab.com/GCSBOSS/confort/-/tags/v0.2.0 |
@@ -1,83 +0,45 @@ | ||
const fs = require('fs'); | ||
const assert = require('assert'); | ||
const deepmerge = require('deepmerge'); | ||
const EventEmitter = require('events'); | ||
const loadConf = require('./conf-loader'); | ||
const fs = require('fs') | ||
const path = require('path') | ||
function createWatches(files, cb){ | ||
return files.map( f => | ||
fs.watch(f, { persistent: false }, op => | ||
op == 'change' && cb() ) ); | ||
} | ||
function setupWatches(){ | ||
for(let w of this.watchers) | ||
w.close(); | ||
this.watchers = []; | ||
if(this.shouldReload) | ||
this.watchers = createWatches(this.files, () => this.reload()); | ||
} | ||
const isMergeableObject = val => | ||
val && typeof val === 'object' && | ||
(!val.constructor || ['Object', 'Array'].includes(val.constructor.name)); | ||
Boolean(val) && typeof val === 'object' && | ||
(!val.constructor || 'Object' === val.constructor.name) | ||
module.exports = class Confort extends EventEmitter { | ||
function deepMerge(...subjects){ | ||
constructor(firstLayer, type){ | ||
super(); | ||
this.object = {}; | ||
this.layers = []; | ||
this.files = []; | ||
this.watchers = []; | ||
const root = {} | ||
this.shouldReload = false; | ||
for(const obj of subjects){ | ||
if(!isMergeableObject(obj)) | ||
throw new Error('Cannot merge non-object') | ||
if(firstLayer) | ||
this.addLayer(firstLayer, type); | ||
for(const k in obj) | ||
root[k] = isMergeableObject(root[k]) && isMergeableObject(obj[k]) | ||
? deepMerge(root[k], obj[k]) | ||
: root[k] = obj[k] | ||
} | ||
addLayer(pathOrObject, fileType){ | ||
this.layers.push([pathOrObject, fileType]); | ||
return root | ||
} | ||
if(typeof pathOrObject == 'string'){ | ||
this.files.push(pathOrObject); | ||
pathOrObject = loadConf(pathOrObject, fileType); | ||
} | ||
const loaders = { | ||
toml: conf => require('toml').parse(conf), | ||
yaml: conf => require('yaml').parse(conf), | ||
json: conf => JSON.parse(conf) | ||
} | ||
assert.strictEqual(typeof pathOrObject, 'object'); | ||
loaders.yml = loaders.yaml; | ||
this.object = deepmerge(this.object, pathOrObject, { | ||
arrayMerge: (a, b) => b, | ||
isMergeableObject | ||
}); | ||
function loadConf(conf){ | ||
const type = path.extname(conf).substr(1); | ||
setupWatches.bind(this)(); | ||
if(typeof loaders[type] !== 'function') | ||
throw new Error('Conf type not supported: ' + type); | ||
this.emit('layer'); | ||
} | ||
return loaders[type](fs.readFileSync(conf, 'utf-8')); | ||
} | ||
reload(){ | ||
let layers = this.layers; | ||
this.clear(); | ||
layers.forEach(l => this.addLayer(...l)); | ||
this.emit('reload'); | ||
} | ||
set liveReload(enabled){ | ||
enabled = Boolean(enabled); | ||
if(enabled == this.shouldReload) | ||
return; | ||
this.shouldReload = enabled; | ||
setupWatches.bind(this)(); | ||
} | ||
clear(){ | ||
this.object = {}; | ||
this.layers = []; | ||
this.files = []; | ||
} | ||
module.exports = function confort(...subjects){ | ||
subjects = subjects.map(c => typeof c == 'string' ? loadConf(c) : c); | ||
return deepMerge(...subjects); | ||
} |
{ | ||
"name": "confort", | ||
"version": "0.1.4", | ||
"version": "0.2.0", | ||
"description": "A library for incrementally build config objects through layering config files in many formats.", | ||
@@ -18,6 +18,3 @@ "main": "lib/main.js", | ||
"layer", | ||
"env", | ||
"watch", | ||
"live", | ||
"reload" | ||
"env" | ||
], | ||
@@ -33,10 +30,7 @@ "author": "Guilherme C. Souza", | ||
}, | ||
"dependencies": { | ||
"deepmerge": "^4.0.0" | ||
}, | ||
"dependencies": {}, | ||
"devDependencies": { | ||
"cson-parser": "^4.0.2", | ||
"toml": "^3.0.0", | ||
"yaml": "^1.7.0" | ||
"yaml": "^1.10.0" | ||
} | ||
} |
@@ -9,6 +9,6 @@ # [Confort](https://gitlab.com/GCSBOSS/confort) | ||
- Merge configs chronologically | ||
- Option to watch changes in loaded config files | ||
- Supported formats so far: TOML (`toml`), JSON, CSON (`cson-parser`) and YAML (`yaml`) | ||
- Available in the Deno ecosystem (see directory `/deno`) | ||
## Get Started | ||
## Get Started on NodeJS | ||
@@ -22,27 +22,29 @@ Install with: `npm i -P confort` | ||
```js | ||
const Confort = require('confort'); | ||
const confort = require('confort'); | ||
// Constructor Forms | ||
let conf = new Confort(); // Empty conf object | ||
let conf = new Confort({ key: 'value', key2: 'value2' }); // Initial conf from objects | ||
let conf = new Confort('./my-file.toml'); // Initial conf from file | ||
let conf = confort(); // Empty conf object | ||
let conf = confort({ key: 'value', key2: 'value2' }); // Initial conf from objects | ||
let conf = confort('./my-file.toml'); // Initial conf from file | ||
// Adding incremental config layer | ||
conf.addLayer({ key: 'value' }); | ||
conf.object; // => { key: 'value' } | ||
conf.addLayer({ key: 'newValue', otherKey: 'value' }); | ||
conf.object; // => { key: 'newValue', otherKey: 'value' } | ||
conf.addLayer('./my-file.yml'); | ||
let c1 = confort(c1, { key: 'value' }); // => { key: 'value' } | ||
c1 = confort(c1, { key: 'newValue', otherKey: 'value' }); // => { key: 'newValue', otherKey: 'value' } | ||
c1 = confort(c1, './my-file.yml'); | ||
``` | ||
// Reset the configuration for reuse | ||
conf.clear(); | ||
## Get Started on [Deno](https://deno.land/) | ||
// Reset the conf and reapply all layers effectively reading changes in files | ||
conf.reload(); | ||
```js | ||
import confort from 'https://deno.land/x/confort@0.2.0/deno/main.js' | ||
// Enable auto reload when changes in loaded conf files happen | ||
conf.liveReload = true; | ||
// Constructor Forms | ||
let conf = confort(); // Empty conf object | ||
let conf = confort({ key: 'value', key2: 'value2' }); // Initial conf from objects | ||
let conf = confort('./my-file.toml'); // Initial conf from file | ||
// Disables auto reload | ||
conf.liveReload = false; | ||
// Adding incremental config layer | ||
let c1 = confort(c1, { key: 'value' }); // => { key: 'value' } | ||
c1 = confort(c1, { key: 'newValue', otherKey: 'value' }); // => { key: 'newValue', otherKey: 'value' } | ||
c1 = confort(c1, './my-file.yml'); | ||
``` | ||
@@ -49,0 +51,0 @@ |
223
test/spec.js
const fs = require('fs'); | ||
const assert = require('assert'); | ||
describe('Conf Loader', () => { | ||
const loadConf = require('../lib/conf-loader'); | ||
const confort = require('../lib/main.js'); | ||
it('Should fail if no conf file is specified', () => { | ||
assert.throws( () => loadConf() ); | ||
describe('confort', () => { | ||
it('Should fail merging non-object', () => { | ||
assert.throws(() => confort(234)); | ||
}); | ||
it('Should fail if conf file is not found', () => { | ||
assert.throws( () => loadConf('./bla.json', /couldn't open/) ); | ||
it('Should add layers if specified', () => { | ||
let conf = confort({ key: 'value' }); | ||
assert.strictEqual(conf.key, 'value'); | ||
}); | ||
it('Should fail if given conf type is not supported', () => { | ||
assert.throws( () => loadConf('./test/res/conf.xml'), /type not supported/ ); | ||
it('Should add object layer', () => { | ||
let conf = confort({}); | ||
conf = confort(conf, { key: 'value' }); | ||
assert.strictEqual(conf.key, 'value'); | ||
}); | ||
it('Should properly load a TOML file and generate an object', () => { | ||
let obj = loadConf('./test/res/conf.toml'); | ||
assert.strictEqual(obj.key, 'value'); | ||
it('Should override layer values also present in previous state', () => { | ||
let conf = confort({ key: 'value' }, { key: 'valu3' }); | ||
assert.strictEqual(conf.key, 'valu3'); | ||
}); | ||
it('Should properly load an YAML file and generate an object', () => { | ||
let obj = loadConf('./test/res/conf.yaml'); | ||
assert.strictEqual(obj.key, 'value'); | ||
it('Should preserve values not present in new layer', () => { | ||
let conf = confort({ key: 'value' }); | ||
conf = confort(conf, { newKey: 'valu3' }); | ||
assert.strictEqual(conf.key, 'value'); | ||
}); | ||
it('Should properly load an JSON file and generate an object', () => { | ||
let obj = loadConf('./test/res/conf.json'); | ||
assert.strictEqual(obj.key, 'value'); | ||
it('Should merge objects instead of just replacing', () => { | ||
let conf = confort({ key: { a: 'b', e: [ 0 ] } }); | ||
conf = confort(conf, { key: { c: 'd', e: [ 1 ] } }); | ||
assert.strictEqual(conf.key.a, 'b'); | ||
assert.strictEqual(conf.key.c, 'd'); | ||
assert.strictEqual(conf.key.e[0], 1); | ||
}); | ||
it('Should properly load an CSON file and generate an object', () => { | ||
let obj = loadConf('./test/res/conf.cson'); | ||
assert.strictEqual(obj.key, 'value'); | ||
it('Should merge objects with null prototype', () => { | ||
let o = Object.create(null); | ||
o.b = 2; | ||
let conf = confort(o); | ||
conf = confort(conf, { a: 1 }); | ||
assert.strictEqual(conf.a, 1); | ||
assert.strictEqual(conf.b, 2); | ||
}); | ||
}); | ||
describe('Confort', function(){ | ||
const Confort = require('../lib/main.js'); | ||
it('Should replace objects with a class other than Object', () => { | ||
class Foo{ constructor(){ this.a = 'foo' } } | ||
let conf = confort({ key: { a: 'a', b: 'bar' } }, { key: new Foo() }); | ||
assert.strictEqual(conf.key.a, 'foo'); | ||
assert.strictEqual(typeof conf.key.b, 'undefined'); | ||
}); | ||
describe('constructor ( pathOrObject, [fileType] )', () => { | ||
it('Should add layers if specified', () => { | ||
let conf = new Confort({ key: 'value' }); | ||
assert.strictEqual(conf.object.key, 'value'); | ||
}); | ||
it('Should fail if given conf type is not supported', () => { | ||
assert.throws(() => confort('./conf.xml')); | ||
}); | ||
describe('::addLayer ( pathOrObject, [fileType] )', () => { | ||
it('Should add object layer', () => { | ||
let conf = new Confort(); | ||
conf.addLayer({ key: 'value' }); | ||
assert.strictEqual(conf.object.key, 'value'); | ||
}); | ||
it('Should override layer values also present in previous state', () => { | ||
let conf = new Confort({ key: 'value' }); | ||
conf.addLayer({ key: 'valu3' }); | ||
assert.strictEqual(conf.object.key, 'valu3'); | ||
}); | ||
it('Should preserve values not present in new layer', () => { | ||
let conf = new Confort({ key: 'value' }); | ||
conf.addLayer({ newKey: 'valu3' }); | ||
assert.strictEqual(conf.object.key, 'value'); | ||
}); | ||
it('Should merge objects instead of just replacing', () => { | ||
let conf = new Confort({ key: { a: 'b', e: [ 0 ] } }); | ||
conf.addLayer({ key: { c: 'd', e: [ 1 ] } }); | ||
assert.strictEqual(conf.object.key.a, 'b'); | ||
assert.strictEqual(conf.object.key.c, 'd'); | ||
assert.strictEqual(conf.object.key.e[0], 1); | ||
}); | ||
it('Should merge objects with null prototype', () => { | ||
let o = Object.create(null); | ||
o.b = 2; | ||
let conf = new Confort(o); | ||
conf.addLayer({ a: 1 }); | ||
assert.strictEqual(conf.object.a, 1); | ||
assert.strictEqual(conf.object.b, 2); | ||
}); | ||
it('Should replace objects with a class other than Object', () => { | ||
class Foo{ constructor(){ this.a = 'foo'; } } | ||
let conf = new Confort({ key: { a: 'a', b: 'bar' } }); | ||
conf.addLayer({ key: new Foo() }); | ||
assert.strictEqual(conf.object.key.a, 'foo'); | ||
assert.strictEqual(typeof conf.object.key.b, 'undefined'); | ||
}); | ||
it('Should load conf file when path is specified', () => { | ||
let conf = new Confort('./test/res/conf.toml'); | ||
assert.strictEqual(conf.object.key, 'value'); | ||
}); | ||
it('Should record layers info', () => { | ||
let conf = new Confort('./test/res/conf.toml'); | ||
conf.addLayer({ newKey: 'valu3' }); | ||
assert.strictEqual(conf.layers[0][0], './test/res/conf.toml'); | ||
assert.strictEqual(conf.layers[1][0].newKey, 'valu3'); | ||
assert.strictEqual(conf.files[0], './test/res/conf.toml'); | ||
}); | ||
it('Should emit layer event', done => { | ||
let conf = new Confort('./test/res/conf.toml'); | ||
conf.on('layer', done); | ||
conf.addLayer({ newKey: 'valu3' }); | ||
}); | ||
it('Should add new files to watch list if watching', function(done){ | ||
fs.copyFileSync('./test/res/conf.toml', './node_modules/conf.toml'); | ||
let conf = new Confort(); | ||
conf.liveReload = true; | ||
conf.addLayer('./node_modules/conf.toml'); | ||
conf.on('reload', () => { | ||
conf.liveReload = false; | ||
done(); | ||
}); | ||
fs.writeFileSync('./node_modules/conf.toml', 'key = \'value2\''); | ||
}); | ||
it('Should fail if conf file is not found', () => { | ||
assert.throws(() => confort('./bla.json')); | ||
}); | ||
describe('::clear ( )', () => { | ||
it('Should remove all records and clear state', () => { | ||
let conf = new Confort('./test/res/conf.toml'); | ||
conf.addLayer({ newKey: 'valu3' }); | ||
conf.clear(); | ||
assert.strictEqual(conf.layers.length, 0); | ||
assert.strictEqual(conf.files.length, 0); | ||
assert.strictEqual(Object.keys(conf.object).length, 0); | ||
}); | ||
it('Should properly load a TOML file and generate an object', () => { | ||
fs.writeFileSync(__dirname + '/a.toml', 'key = "value"', 'utf-8'); | ||
let conf = confort(__dirname + '/a.toml'); | ||
assert.strictEqual(conf.key, 'value'); | ||
fs.unlink(__dirname + '/a.toml', Function.prototype); | ||
}); | ||
describe('::reload ( )', () => { | ||
it('Should emit reload event', done => { | ||
let conf = new Confort('./test/res/conf.toml'); | ||
conf.on('reload', done); | ||
conf.reload(); | ||
}); | ||
it('Should maintain state unchanged when no file changed', () => { | ||
let conf = new Confort('./test/res/conf.toml'); | ||
conf.addLayer({ newKey: 'valu3' }); | ||
conf.reload(); | ||
assert.strictEqual(conf.layers[0][0], './test/res/conf.toml'); | ||
assert.strictEqual(conf.layers[1][0].newKey, 'valu3'); | ||
assert.strictEqual(conf.files[0], './test/res/conf.toml'); | ||
assert.strictEqual(conf.object.key, 'value'); | ||
}); | ||
it('Should load any values changed in recorded files', () => { | ||
fs.copyFileSync('./test/res/conf.toml', './node_modules/conf.toml'); | ||
let conf = new Confort('./node_modules/conf.toml'); | ||
conf.addLayer({ newKey: 'valu3' }); | ||
fs.writeFileSync('./node_modules/conf.toml', 'key = \'value2\''); | ||
conf.reload(); | ||
assert.strictEqual(conf.object.key, 'value2'); | ||
}); | ||
it('Should properly load an YAML file and generate an object', () => { | ||
fs.writeFileSync(__dirname + '/a.yml', 'key: value', 'utf-8'); | ||
let conf = confort(__dirname + '/a.yml'); | ||
assert.strictEqual(conf.key, 'value'); | ||
fs.unlink(__dirname + '/a.yml', Function.prototype); | ||
}); | ||
describe('::liveReload ( enabled )', () => { | ||
it('Should reload on conf file changes', function(done){ | ||
fs.copyFileSync('./test/res/conf.toml', './node_modules/conf.toml'); | ||
let conf = new Confort('./node_modules/conf.toml'); | ||
conf.liveReload = true; | ||
conf.liveReload = true; | ||
fs.writeFileSync('./node_modules/conf.toml', 'key = \'value2\''); | ||
conf.on('reload', () => { | ||
assert.strictEqual(conf.object.key, 'value2'); | ||
conf.liveReload = false; | ||
done(); | ||
}); | ||
}); | ||
it('Should noop when enabling already enabled', function(){ | ||
let conf = new Confort(); | ||
conf.liveReload = true; | ||
conf.liveReload = true; | ||
conf.liveReload = false; | ||
}); | ||
it('Should properly load an JSON file and generate an object', () => { | ||
fs.writeFileSync(__dirname + '/a.json', '{"key": "value"}', 'utf-8'); | ||
let conf = confort(__dirname + '/a.json'); | ||
assert.strictEqual(conf.key, 'value'); | ||
fs.unlink(__dirname + '/a.json', Function.prototype); | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
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
Network access
Supply chain riskThis module accesses the network.
Found 4 instances in 1 package
0
2
70
2
17431
12
209
5
- Removeddeepmerge@^4.0.0
- Removeddeepmerge@4.3.1(transitive)