diff --git a/README.md b/README.md index 26148407..ad58b066 100644 --- a/README.md +++ b/README.md @@ -224,6 +224,36 @@ $ node example.js --foo.bar { _: [], "foo.bar": true } ``` +_Note: as of yargs-parser@18.0.0, `dot-notation` has been split into `dot-notation` (**for CLI args**) and `config-dot-notation`_ + +### config-dot-notation + +* default: `false` +* key: `config-dot-notation` + +Should keys in the config file that contain `.` split into objects? + +example config.json: +```json +{ + "foo.bar": true, + "parent": { + "foo.bar": true + } +} +``` + +```console +$ node example.js --config config.json +{ _: [], "foo.bar": true, parent: { "foo.bar": true } } +``` + +_if enabled:_ +```console +$ node example.js --config config.json +{ _: [], foo: { bar: true }, parent: { foo: { bar: true } } } +``` + ### parse numbers * default: `true` diff --git a/lib/yargs-parser-types.ts b/lib/yargs-parser-types.ts index 9e03ff2c..e1c99c55 100644 --- a/lib/yargs-parser-types.ts +++ b/lib/yargs-parser-types.ts @@ -60,6 +60,8 @@ export interface Configuration { 'combine-arrays': boolean; /** Should keys that contain `.` be treated as objects? Default is `true` */ 'dot-notation': boolean; + /** Should keys in the config file that contain `.` split into objects? Default is `false` */ + 'config-dot-notation': boolean; /** Should arguments be coerced into an array when duplicated? Default is `true` */ 'duplicate-arguments-array': boolean; /** Should array arguments be coerced into a single array when duplicated? Default is `true` */ diff --git a/lib/yargs-parser.ts b/lib/yargs-parser.ts index 764ca64e..200a6cca 100644 --- a/lib/yargs-parser.ts +++ b/lib/yargs-parser.ts @@ -70,6 +70,7 @@ export class YargsParser { 'camel-case-expansion': true, 'combine-arrays': false, 'dot-notation': true, + 'config-dot-notation': false, 'duplicate-arguments-array': true, 'flatten-duplicate-arrays': true, 'greedy-arrays': true, @@ -698,6 +699,13 @@ export class YargsParser { const value = config[key] const fullKey = prev ? prev + '.' + key : key + // shortcut the process below if the key includes `.` because `setArg()` would do + // additional processing with aliases and splitting the key, when we don't need to. + if (key.includes('.') && !configuration['config-dot-notation'] && !hasKey(argv, fullKey.split('.'))) { + setKey(argv, prev ? [prev, key] : [key], value) + return + } + // if the value is an inner object and we have dot-notation // enabled, treat inner objects in config the same as // heavily nested dot notations (foo.bar.apple). diff --git a/test/fixtures/config_with_dot_notation.json b/test/fixtures/config_with_dot_notation.json new file mode 100644 index 00000000..59ea8013 --- /dev/null +++ b/test/fixtures/config_with_dot_notation.json @@ -0,0 +1,10 @@ +{ + "a": "a", + "nested": { + "b": "b" + }, + "hello.world": true, + "parent": { + "foo.bar": true + } +} \ No newline at end of file diff --git a/test/yargs-parser.cjs b/test/yargs-parser.cjs index 492e3b90..d8e198eb 100644 --- a/test/yargs-parser.cjs +++ b/test/yargs-parser.cjs @@ -597,7 +597,7 @@ describe('yargs-parser', function () { argv.should.have.property('foo').and.deep.equal('bar') }) - it('should load options and values from a JS file when config has .js extention', function () { + it('should load options and values from a JS file when config has .js extension', function () { const jsPath = path.resolve(__dirname, './fixtures/settings.cjs') const argv = parser(['--settings', jsPath, '--foo', 'bar'], { config: ['settings'] @@ -662,6 +662,106 @@ describe('yargs-parser', function () { }) }) + it('should respect cli precedence when config-dot-notation option is disabled', function () { + const jsonPath = path.resolve(__dirname, './fixtures/config_with_dot_notation.json') + const argv = parser(["--settings", jsonPath, "--nested.b", "cli"], { + config: ["settings"], + default: { + "hello.world": false, + parent: { + "foo.bar": false, + }, + }, + }); + + argv.should.have.property('a', 'a') + argv.should.have.property('nested').and.deep.equal({ + b: 'cli' + }) + argv.should.have.property('hello.world').and.equal(true) + argv.should.have.property('parent').and.deep.equal({ + "foo.bar": true + }) + }) + + it('should respect cli precedence when dot-notation and config-dot-notation option are disabled', function () { + const jsonPath = path.resolve(__dirname, './fixtures/config_with_dot_notation.json') + const argv = parser(["--settings", jsonPath, "--hello.world", "cli", "--parent.foo.bar", "cli"], { + config: ["settings"], + default: { + "hello.world": false, + parent: { + "foo.bar": false, + }, + }, + configuration: { + 'dot-notation': false, + } + }); + + argv.should.have.property('a', 'a') + argv.should.have.property('nested').and.deep.equal({ + b: 'b' + }) + argv.should.have.property('hello.world').and.equal('cli') + argv.should.have.property('parent').and.deep.equal({ + "foo.bar": true + }) + }) + + it('should split into objects when config-dot-notation is enabled', function () { + const jsonPath = path.resolve(__dirname, './fixtures/config_with_dot_notation.json') + const argv = parser(["--settings", jsonPath], { + config: ["settings"], + default: { + "hello.world": false, + parent: { + "foo.bar": false, + }, + }, + configuration: { + "config-dot-notation": true, + } + }); + + argv.should.have.property('a', 'a') + argv.should.have.property('nested').and.deep.equal({ + b: 'b' + }) + argv.should.have.property('hello').and.deep.equal({ + "world": true, + }) + argv.should.have.property('parent').and.deep.equal({ + "foo": { "bar": true } + }) + }) + + it('should disable config-dot-notation when dot-notation is disabled', function () { + const jsonPath = path.resolve(__dirname, './fixtures/config_with_dot_notation.json') + const argv = parser(["--settings", jsonPath], { + config: ["settings"], + default: { + "hello.world": false, + parent: { + "foo.bar": false, + }, + }, + configuration: { + "config-dot-notation": true, + "dot-notation": false, + } + }); + + argv.should.have.property('a', 'a') + argv.should.have.property('nested').and.deep.equal({ + b: 'b' + }) + argv.should.have.property('hello.world').and.equal(true) + argv.should.have.property('parent').and.deep.equal({ + "foo.bar": true + }) + }) + it('allows a custom parsing function to be provided', function () { const jsPath = path.resolve(__dirname, './fixtures/config.txt') const argv = parser(['--settings', jsPath, '--foo', 'bar'], {