Comparing version 4.1.0 to 5.0.0
@@ -24,3 +24,64 @@ /* | ||
return (function readFileAndResolveIncludes(fileName) { | ||
if (Array.isArray(rootFileName)) { | ||
var config = {}; | ||
rootFileName.forEach(function (configFileName) { | ||
config = cjson.extend(true, config, load(configFileName, options)); | ||
}); | ||
return config; | ||
} | ||
// Have to always keep public keys separate within resolvePublic | ||
// If a private key is overwritten by a public key, it's an error, and vice-versa. Name your config keys something else! | ||
// Empty object leaves and non-public keys are filtered out when requesting with public:true | ||
function resolvePublic(obj, isToplevel, publicOnly) { | ||
if (typeof obj === 'object' && obj !== null) { | ||
var publicObj = Array.isArray(obj) ? [] : {}; | ||
Object.keys(obj).forEach(function (key) { | ||
if (key !== '#public') { | ||
// Have to extract public and non-public keys separately, otherwise we lose information | ||
var resolvedPublic = resolvePublic(obj[key], false, true); | ||
if (typeof resolvedPublic === 'object' && resolvedPublic !== null && Object.keys(resolvedPublic).length > 0) { | ||
publicObj[key] = resolvedPublic; | ||
} | ||
obj[key] = resolvePublic(obj[key], false, false); | ||
} | ||
}); | ||
if (typeof obj['#public'] !== 'undefined') { | ||
if (!(typeof obj['#public'] === 'object' && obj['#public'] !== null && !Array.isArray(obj['#public']))) { | ||
throw new Error('#public must always be an object'); | ||
} | ||
Object.keys(obj['#public']).forEach(function (key) { | ||
if (key in obj) { | ||
throw new Error('Unsupported combination of public and non-public properties, check your tree structure'); | ||
} | ||
if (obj['#public'][key] !== 'undefined') { | ||
publicObj[key] = obj['#public'][key]; | ||
} | ||
}); | ||
delete obj['#public']; | ||
} | ||
if (isToplevel && typeof publicObj === 'object' && publicObj !== null && Object.keys(publicObj).length > 0) { | ||
obj['#public'] = publicObj; | ||
} | ||
if (publicOnly) { | ||
return publicObj; | ||
} else { | ||
return cjson.extend(true, obj, publicObj); | ||
} | ||
} | ||
return obj; | ||
} | ||
return resolvePublic((function readFileAndResolveIncludes(fileName) { | ||
var absoluteFileName = Path.resolve(cwd, fileName); | ||
@@ -36,2 +97,3 @@ | ||
if (Array.isArray(obj)) { | ||
// An array that should be resolved into an array | ||
return obj.map(resolveIncludes); | ||
@@ -41,2 +103,3 @@ } else if (typeof obj === 'object' && obj !== null) { | ||
_.flatten([obj['#include']]).reverse().forEach(function (fileNameToInclude) { | ||
// Have to resolve from location of current file, not from cwd | ||
var absoluteFileNameToInclude = Path.resolve(Path.dirname(fileName), fileNameToInclude); | ||
@@ -55,3 +118,3 @@ if (!isIgnored(absoluteFileNameToInclude)) { | ||
}(cjson.load(absoluteFileName))); | ||
}(rootFileName)); | ||
}(rootFileName)), true, options.public); | ||
}; |
@@ -5,3 +5,3 @@ { | ||
"description": "Configuration", | ||
"version": "4.1.0", | ||
"version": "5.0.0", | ||
"repository": { | ||
@@ -51,4 +51,4 @@ "type": "git", | ||
"mocha": "*", | ||
"unexpected": "7.0.0" | ||
"unexpected": "10.8.1" | ||
} | ||
} |
@@ -17,5 +17,9 @@ # OConf | ||
## Format | ||
## The `#include` directive | ||
The basic idea is to experiment with applying `#include`-statements recusively | ||
Anywhere in your cjson file, you can `#include` another cjson file. The file containing the `#include` directive overrides any values from the file being included, in case of conflicts. | ||
### Format | ||
The basic idea is to experiment with applying `#include`-directives recusively | ||
inside JSON/cJSON documents: | ||
@@ -51,3 +55,3 @@ | ||
## Structure | ||
### Structure | ||
@@ -65,2 +69,70 @@ There are no restrictions in how includes work (except no loops). Usually a | ||
## The `#public` directive | ||
With this directive, you can generate a json blob that can be safely exposed to client-side code. This is useful when some properties on the same object are safe to expose to client code, while others are not. | ||
### Restrictions | ||
In case of conflicts between a `#public` and a non-public key on the same object, an error is thrown - it is usually a sign that a new key needs to be introduced if the same key contains different values for client and server code. | ||
The `#include` directives are processed before `#public`. | ||
### Format | ||
Anywhere in your cjson file, you can add a `#public` property to an object, denoting some keys on that property you want grouped together on the `#public` property of the root of your config. | ||
```javascript | ||
// some-public-settings.cjson | ||
{ | ||
"some-setting": "default value", | ||
"value": 100, | ||
"fancy-list": { | ||
"expose-foo": true, | ||
"#public": { | ||
"scroll-timeout": 100 | ||
} | ||
} | ||
} | ||
``` | ||
Will result in a config with: | ||
```javascript | ||
{ | ||
"some-setting": "default-value", | ||
"value": 100, | ||
"fancy-list": { | ||
"expose-foo": true, | ||
"scroll-timeout": 100 | ||
}, | ||
"#public": { | ||
"fancy-list": { | ||
"scroll-timeout": 100 | ||
} | ||
} | ||
} | ||
``` | ||
### Returning only configuration marked as `#public` | ||
You can instruct `oconf.load` to only return configuration properties marked with `#public`: | ||
> var oconf = require('oconf'); | ||
> oconf.load('config/some-public-settings.cjson', { public: true }); | ||
{ | ||
"fancy-list": { | ||
"scroll-timeout": 100 | ||
} | ||
} | ||
Of course you can also just grab the `#public` property from the result of `oconf.load`: | ||
> var oconf = require('oconf'); | ||
> oconf.load('config/some-public-settings.cjson')['#public']; | ||
{ | ||
"fancy-list": { | ||
"scroll-timeout": 100 | ||
} | ||
} | ||
## Binary | ||
@@ -112,2 +184,13 @@ | ||
You can also filter out values in the `#public` blob with the `--public` flag. | ||
``` | ||
$ oconf --public some-public-settings.cjson | ||
{ | ||
"fancy-list": { | ||
"scroll-timeout": 100 | ||
} | ||
} | ||
``` | ||
## Tests | ||
@@ -114,0 +197,0 @@ |
@@ -41,3 +41,3 @@ /* global describe, it */ | ||
expect(stdout, 'to equal', ''); | ||
expect(stderr, 'to match', ''); | ||
expect(stderr, 'to equal', ''); | ||
done(); | ||
@@ -51,3 +51,3 @@ }); | ||
expect(stdout, 'to equal', 'qux\n'); | ||
expect(stderr, 'to match', ''); | ||
expect(stderr, 'to equal', ''); | ||
done(); | ||
@@ -54,0 +54,0 @@ }); |
@@ -58,2 +58,14 @@ /* global describe, it */ | ||
}); | ||
it('should support including multiple files, and print the resolved json structure to stdout', function () { | ||
return expect([testFile('extend-base'), testFile('deep')], 'when passed as arguments to oconf', 'to satisfy', { | ||
err: null, | ||
stdout: formattedJson({ | ||
foo: { | ||
bar: "qux" | ||
}, | ||
what: "this is from default.cjson." | ||
}), | ||
stderr: '' | ||
}); | ||
}); | ||
it('should fail when asked for nonexistant file', function () { | ||
@@ -92,3 +104,3 @@ return expect(testFile('nonExistent'), 'when passed as arguments to oconf', 'to satisfy', { | ||
stdout: '', | ||
stderr: expect.it('to match', /Options:/) | ||
stderr: expect.it('to match', /Options/) | ||
}); | ||
@@ -199,15 +211,2 @@ }); | ||
}); | ||
describe('missing --extract-option flag', function () { | ||
it('should return the option value and warn the user', function () { | ||
return expect([ | ||
testFile('base'), | ||
'foo' | ||
], 'when passed as arguments to oconf', 'to satisfy', { | ||
err: expect.it('to be an', Error), | ||
code: 1, | ||
stdout: '', | ||
stderr: 'Error: Did you forget the --extract-option flag?\n' | ||
}); | ||
}); | ||
}); | ||
describe('--ignore flag', function () { | ||
@@ -214,0 +213,0 @@ it('should work with no other options', function () { |
@@ -25,2 +25,16 @@ /*global describe, it, before*/ | ||
}); | ||
describe('calling load method with an array of config file names', function () { | ||
var data, dataInclude; | ||
before(function () { | ||
data = oconf.load([testFile('extend-base.cjson'), testFile('deep.cjson')]); | ||
dataInclude = oconf.load(testFile('extend-wrap.cjson')); | ||
}); | ||
it('functions like calling #include with an array of filenames in the same order', function () { | ||
expect(data, 'to equal', dataInclude); | ||
}); | ||
}); | ||
}); | ||
describe('#include behaviour', function () { | ||
describe('extend-base.cjson', function () { | ||
@@ -95,3 +109,3 @@ var data; | ||
oconf.load(testFile('loop1.cjson')); | ||
}, 'to throw', /^Loop in loaded files: /); | ||
}, 'to throw', /^Loop in loaded files/); | ||
}); | ||
@@ -101,3 +115,3 @@ it('Loading loop2.cjson throws error', function () { | ||
oconf.load(testFile('loop2.cjson')); | ||
}, 'to throw', /^Loop in loaded files:/); | ||
}, 'to throw', /^Loop in loaded files/); | ||
}); | ||
@@ -145,1 +159,236 @@ }); | ||
}); | ||
describe('#public behaviour', function () { | ||
describe('when loading with public:false (the default)', function () { | ||
describe('where the root object contains a #public property', function () { | ||
var data; | ||
before(function () { | ||
data = oconf.load(testFile('public-base.cjson')); | ||
}); | ||
it('should fold the #public properties down into the base structure', function () { | ||
expect(data, 'to equal', { | ||
foo: 'do not expose to public', | ||
what: 'what is public', | ||
'#public': { | ||
what: 'what is public' | ||
} | ||
}); | ||
}); | ||
}); | ||
describe('where a child object contains some #public properties', function () { | ||
var data; | ||
before(function () { | ||
data = oconf.load(testFile('public-deep.cjson')); | ||
}); | ||
it('should fold the #public properties down into the base structure', function () { | ||
expect(data, 'to equal', { | ||
foo: "do not expose to public", | ||
bar: { | ||
quux: "super secret" | ||
}, | ||
hello: { | ||
earth: "mostly harmless", | ||
answer: 42 | ||
}, | ||
'#public': { | ||
hello: { | ||
answer: 42 | ||
} | ||
} | ||
}); | ||
}); | ||
}); | ||
describe('where a child object in a child array contains some #public properties', function () { | ||
var data; | ||
before(function () { | ||
data = oconf.load(testFile('array-with-object-with-public.cjson')); | ||
}); | ||
it('should fold the #public properties down into the base structure', function () { | ||
expect(data, 'to equal', { | ||
foo: [ | ||
{ | ||
bar: 'quux' | ||
} | ||
], | ||
'#public': { | ||
foo: [ | ||
{ | ||
bar: 'quux' | ||
} | ||
] | ||
} | ||
}); | ||
}); | ||
}); | ||
describe('where a child object in a child array contains some #public properties', function () { | ||
var data; | ||
before(function () { | ||
data = oconf.load(testFile('array-with-object-with-array-with-object-with-public.cjson')); | ||
}); | ||
it.skip('should fold the #public properties down into the base structure', function () { | ||
expect(data, 'to exhaustively satisfy', { | ||
0: { | ||
foo: [ | ||
{ | ||
bar: 'quux' | ||
} | ||
] | ||
}, | ||
'#public': [{ | ||
foo: [ | ||
{ | ||
bar: 'quux' | ||
} | ||
] | ||
}] | ||
}).and('to be an array'); | ||
}); | ||
}); | ||
describe('when trying to overwrite a public property with a property that was declared public at another level', function () { | ||
it('should throw an error', function () { | ||
expect(function () { | ||
oconf.load(testFile('public-conflict.cjson')); | ||
}, 'to throw', /^Unsupported combination of public and non/); | ||
}); | ||
}); | ||
describe('where trying to overwrite a non-public property with a #public one', function () { | ||
it('should throw an error', function () { | ||
expect(function () { | ||
oconf.load(testFile('public-deep-with-include.cjson')); | ||
}, 'to throw', /^Unsupported combination of public and non/); | ||
}); | ||
}); | ||
describe('where trying to overwrite a #public property with a non-public one', function () { | ||
it('should throw an error', function () { | ||
expect(function () { | ||
oconf.load(testFile('public-deep-with-include.cjson')); | ||
}, 'to throw', /^Unsupported combination of public and non/); | ||
}); | ||
}); | ||
describe('where including a file that already has a #public property which we try to overwrite', function () { | ||
var data; | ||
before(function () { | ||
data = oconf.load(testFile('public-deep-with-public-include.cjson')); | ||
}); | ||
it('should overwrite the included #public property with our value', function () { | ||
expect(data, 'to equal', { | ||
foo: "do not expose to public", | ||
bar: { | ||
quux: "super secret" | ||
}, | ||
hello: { | ||
earth: "mostly harmless", | ||
answer: 'Insufficient data for meaningful answer' | ||
}, | ||
'#public': { | ||
hello: { | ||
answer: 'Insufficient data for meaningful answer' | ||
} | ||
} | ||
}); | ||
}); | ||
}); | ||
describe('when calling load method with an array of config file names containing public properties', function () { | ||
var data, dataInclude; | ||
before(function () { | ||
data = oconf.load([testFile('public-left.cjson'), testFile('public-right.cjson')]); | ||
dataInclude = oconf.load(testFile('public-left-right-wrap.cjson')); | ||
}); | ||
it('functions like calling #include with an array of filenames in the same order', function () { | ||
expect(data, 'to equal', dataInclude); | ||
}); | ||
}); | ||
}); | ||
describe('when loading with public:true', function () { | ||
describe('where the root object contains a #public property', function () { | ||
var data; | ||
before(function () { | ||
data = oconf.load(testFile('public-base.cjson'), { public: true }); | ||
}); | ||
it('should fold the #public properties down into the base structure, and omit secret properties and leaves', function () { | ||
expect(data, 'to equal', { | ||
what: 'what is public' | ||
}); | ||
}); | ||
}); | ||
describe('where a child object contains some #public properties', function () { | ||
var data; | ||
before(function () { | ||
data = oconf.load(testFile('public-deep.cjson'), { public: true }); | ||
}); | ||
it('should fold the #public properties down into the base structure, and omit secret properties and leaves', function () { | ||
expect(data, 'to equal', { | ||
hello: { | ||
answer: 42 | ||
} | ||
}); | ||
}); | ||
}); | ||
describe('where trying to overwrite a non-public property with a #public one', function () { | ||
it('should throw an error', function () { | ||
expect(function () { | ||
oconf.load(testFile('public-deep-with-include.cjson'), { public: true }); | ||
}, 'to throw', /^Unsupported combination of public and non/); | ||
}); | ||
}); | ||
describe('where trying to overwrite a #public property with a non-public one', function () { | ||
it('should throw an error', function () { | ||
expect(function () { | ||
oconf.load(testFile('public-deep-with-include.cjson'), { public: true }); | ||
}, 'to throw', /^Unsupported combination of public and non/); | ||
}); | ||
}); | ||
describe('where including a file that overwrites a #public property with another public one', function () { | ||
var data; | ||
before(function () { | ||
data = oconf.load(testFile('public-deep-with-public-include.cjson'), { public: true }); | ||
}); | ||
it('should overwrite the included #public property with our value, and omit secret properties and leaves', function () { | ||
expect(data, 'to equal', { | ||
hello: { | ||
answer: 'Insufficient data for meaningful answer' | ||
} | ||
}); | ||
}); | ||
}); | ||
describe('when there are no #public properties at all', function () { | ||
var data; | ||
before(function () { | ||
data = oconf.load(testFile('base.cjson'), { public: true }); | ||
}); | ||
it('should return an empty object', function () { | ||
expect(data, 'to equal', {}); | ||
}); | ||
}); | ||
}); | ||
describe('with public-array.cjson', function () { | ||
it('throws an error', function () { | ||
expect(function () { | ||
oconf.load(testFile('public-array.cjson')); | ||
}, 'to throw', /^\#public must always be an object/); | ||
}); | ||
}); | ||
describe('with public-non-object.cjson', function () { | ||
it('throws an error', function () { | ||
expect(function () { | ||
oconf.load(testFile('public-non-object.cjson')); | ||
}, 'to throw', /^\#public must always be an object/); | ||
}); | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
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
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
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
63014
56
963
200
4