diff --git a/lib/command.ts b/lib/command.ts index 08b85a604..866eb5324 100644 --- a/lib/command.ts +++ b/lib/command.ts @@ -224,29 +224,27 @@ export class CommandInstance { helpOnly, helpOrVersionSet ); - if (isPromise(builderResult)) { - return builderResult.then(result => { - return this.applyMiddlewareAndGetResult( + return isPromise(builderResult) + ? builderResult.then(result => + this.applyMiddlewareAndGetResult( + isDefaultCommand, + commandHandler, + result.innerArgv, + currentContext, + helpOnly, + result.aliases, + yargs + ) + ) + : this.applyMiddlewareAndGetResult( isDefaultCommand, commandHandler, - result.innerArgv, + builderResult.innerArgv, currentContext, helpOnly, - result.aliases, + builderResult.aliases, yargs ); - }); - } else { - return this.applyMiddlewareAndGetResult( - isDefaultCommand, - commandHandler, - builderResult.innerArgv, - currentContext, - helpOnly, - builderResult.aliases, - yargs - ); - } } private applyBuilderUpdateUsageAndParse( isDefaultCommand: boolean, diff --git a/lib/middleware.ts b/lib/middleware.ts index 7cf22039d..b4af090dc 100644 --- a/lib/middleware.ts +++ b/lib/middleware.ts @@ -12,10 +12,11 @@ export class GlobalMiddleware { addMiddleware( callback: MiddlewareCallback | MiddlewareCallback[], applyBeforeValidation: boolean, - global = true + global = true, + mutates = false ): YargsInstance { argsert( - ' [boolean] [boolean]', + ' [boolean] [boolean] [boolean]', [callback, applyBeforeValidation, global], arguments.length ); @@ -36,6 +37,7 @@ export class GlobalMiddleware { const m = callback as Middleware; m.applyBeforeValidation = applyBeforeValidation; m.global = global; + m.mutates = mutates; this.globalMiddleware.push(callback as Middleware); } return this.yargs; @@ -53,7 +55,7 @@ export class GlobalMiddleware { else return !toCheck.includes(m.option); }); (callback as Middleware).option = option; - return this.addMiddleware(callback, true, true); + return this.addMiddleware(callback, true, true, true); } getMiddleware() { return this.globalMiddleware; @@ -92,6 +94,11 @@ export function applyMiddleware( return acc; } + if (middleware.mutates) { + if (middleware.applied) return acc; + middleware.applied = true; + } + if (isPromise(acc)) { return acc .then(initialObj => @@ -124,4 +131,6 @@ export interface Middleware extends MiddlewareCallback { applyBeforeValidation: boolean; global: boolean; option?: string; + mutates?: boolean; + applied?: boolean; } diff --git a/lib/yargs-factory.ts b/lib/yargs-factory.ts index 53da93e5e..4f602d7e8 100644 --- a/lib/yargs-factory.ts +++ b/lib/yargs-factory.ts @@ -1156,11 +1156,10 @@ export class YargsInstance { 'alias', ]; opts = objFilter(opts, (k, v) => { - let accept = supportedOpts.indexOf(k) !== -1; // type can be one of string|number|boolean. - if (k === 'type' && ['string', 'number', 'boolean'].indexOf(v) === -1) - accept = false; - return accept; + if (k === 'type' && !['string', 'number', 'boolean'].includes(v)) + return false; + return supportedOpts.includes(k); }); // copy over any settings that can be inferred from the command string. diff --git a/test/command.cjs b/test/command.cjs index fb69c0a93..fd0eefef8 100644 --- a/test/command.cjs +++ b/test/command.cjs @@ -1547,6 +1547,35 @@ describe('Command', () => { return done(); }); }); + + // addresses https://github.com/yargs/yargs/issues/1966 + it('should not be applied multiple times for nested commands', () => { + let coerceExecutionCount = 0; + + const argv = yargs('cmd1 cmd2 foo bar baz') + .command('cmd1', 'cmd1 desc', yargs => + yargs.command('cmd2 ', 'cmd2 desc', yargs => + yargs + .positional('rest', { + type: 'string', + coerce: arg => { + if (coerceExecutionCount) { + throw Error('coerce applied multiple times'); + } + coerceExecutionCount++; + return arg.join(' '); + }, + }) + .fail(() => { + expect.fail(); + }) + ) + ) + .parse(); + + argv.rest.should.equal('bar baz'); + coerceExecutionCount.should.equal(1); + }); }); describe('defaults', () => {