From b0aa89749da04eee43fb69b557c16fb38f61a47b Mon Sep 17 00:00:00 2001 From: Huanyi Xie <74809554+xiehuanyi@users.noreply.github.com> Date: Tue, 6 Oct 2026 22:04:34 +0300 Subject: [PATCH 1/2] fix: strip dashed keys at nested option paths --- lib/yargs-parser.ts | 13 +++++++++++-- test/yargs-parser.mjs | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/lib/yargs-parser.ts b/lib/yargs-parser.ts index 48a2d0f2..7560b813 100644 --- a/lib/yargs-parser.ts +++ b/lib/yargs-parser.ts @@ -429,8 +429,17 @@ export class YargsParser { }) if (configuration['camel-case-expansion'] && configuration['strip-dashed']) { - Object.keys(argv).filter(key => key !== '--' && key.includes('-')).forEach(key => { - delete argv[key] + const keys = new Set([...Object.keys(argv), ...Object.keys(flags.aliases)]) + keys.forEach(key => { + if (key === '--' || !key.includes('-')) return + const parts = configuration['dot-notation'] ? key.split('.') : [key] + let target = argv + for (const part of parts.slice(0, -1)) { + const property = sanitizeKey(part) + if (!target || typeof target !== 'object' || !Object.prototype.hasOwnProperty.call(target, property)) return + target = target[property] + } + if (target && typeof target === 'object') delete target[sanitizeKey(parts[parts.length - 1])] }) } diff --git a/test/yargs-parser.mjs b/test/yargs-parser.mjs index 4c692375..5c659dca 100644 --- a/test/yargs-parser.mjs +++ b/test/yargs-parser.mjs @@ -14,6 +14,39 @@ const require = createRequire(import.meta.url); should() describe('yargs-parser', function () { + it('strips dashed option keys at nested paths', function () { + const result = parser('--outer.inner-dash true --outer.deep.third-dash 5', { + configuration: { 'strip-dashed': true } + }) + expect(result.outer).to.deep.equal({ innerDash: 'true', deep: { thirdDash: 5 } }) + }) + + it('strips nested dashed configuration keys while retaining camel aliases', function () { + const result = parser([], { + configObjects: [{ outer: { 'inner-dash': 'value' } }], + configuration: { 'strip-dashed': true } + }) + expect(result.outer).to.deep.equal({ innerDash: 'value' }) + }) + + it('keeps literal dotted camel aliases when dot notation is disabled', function () { + const result = parser('--outer.inner-dash value', { + configuration: { 'strip-dashed': true, 'dot-notation': false } + }) + expect(result['outer.innerDash']).to.equal('value') + expect(result).not.to.have.property('outer.inner-dash') + }) + + it('does not strip arbitrary keys from a coerced option object', function () { + const value = { 'literal-key': 'value' } + const result = parser('--option value', { + coerce: { option: () => value }, + configuration: { 'strip-dashed': true } + }) + expect(result.option).to.equal(value) + expect(result.option['literal-key']).to.equal('value') + }) + it('should parse a "short boolean"', function () { const parse = parser(['-b']) parse.should.not.have.property('--') From 3ec58fbfb4695cbba4e7e47ca9fe3faaa10b914d Mon Sep 17 00:00:00 2001 From: Huanyi Xie <74809554+xiehuanyi@users.noreply.github.com> Date: Tue, 6 Oct 2026 22:08:31 +0300 Subject: [PATCH 2/2] fix: preserve nested fields returned by coerce --- lib/yargs-parser.ts | 1 + test/yargs-parser.mjs | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/lib/yargs-parser.ts b/lib/yargs-parser.ts index 7560b813..cbb202e4 100644 --- a/lib/yargs-parser.ts +++ b/lib/yargs-parser.ts @@ -433,6 +433,7 @@ export class YargsParser { keys.forEach(key => { if (key === '--' || !key.includes('-')) return const parts = configuration['dot-notation'] ? key.split('.') : [key] + if (parts.length > 1 && typeof checkAllAliases(parts[0], flags.coercions) === 'function') return let target = argv for (const part of parts.slice(0, -1)) { const property = sanitizeKey(part) diff --git a/test/yargs-parser.mjs b/test/yargs-parser.mjs index 5c659dca..6140028c 100644 --- a/test/yargs-parser.mjs +++ b/test/yargs-parser.mjs @@ -14,6 +14,19 @@ const require = createRequire(import.meta.url); should() describe('yargs-parser', function () { + it('preserves parsed-looking keys in plain and frozen coerce results', function () { + for (const freeze of [value => value, Object.freeze]) { + const value = freeze({ 'literal-key': 'own', 'inner-dash': 'replacement' }) + const result = parser.detailed('--outer.inner-dash value', { + coerce: { outer: () => value }, + configuration: { 'strip-dashed': true } + }) + expect(result.error).to.equal(null) + expect(result.argv.outer).to.equal(value) + expect(result.argv.outer['inner-dash']).to.equal('replacement') + } + }) + it('strips dashed option keys at nested paths', function () { const result = parser('--outer.inner-dash true --outer.deep.third-dash 5', { configuration: { 'strip-dashed': true }