diff --git a/packages/angular/cli/src/command-builder/command-runner.ts b/packages/angular/cli/src/command-builder/command-runner.ts index 943e1c9fc24b..822adb641e4c 100644 --- a/packages/angular/cli/src/command-builder/command-runner.ts +++ b/packages/angular/cli/src/command-builder/command-runner.ts @@ -33,6 +33,7 @@ import { PackageManagerUtils } from '../utilities/package-manager'; import { CommandContext, CommandModuleError, CommandScope } from './command-module'; import { addCommandModuleToYargs, demandCommandFailureMessage } from './utilities/command'; import { jsonHelpUsage } from './utilities/json-help'; +import { normalizeOptionsMiddleware } from './utilities/normalize-options-middleware'; const COMMANDS = [ VersionCommandModule, @@ -145,6 +146,7 @@ export async function runCommand(args: string[], logger: logging.Logger): Promis }) .demandCommand(1, demandCommandFailureMessage) .recommendCommands() + .middleware(normalizeOptionsMiddleware) .version(false) .showHelpOnFail(false) .strict() diff --git a/packages/angular/cli/src/command-builder/utilities/normalize-options-middleware.ts b/packages/angular/cli/src/command-builder/utilities/normalize-options-middleware.ts new file mode 100644 index 000000000000..c19d1c8d3038 --- /dev/null +++ b/packages/angular/cli/src/command-builder/utilities/normalize-options-middleware.ts @@ -0,0 +1,37 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import * as yargs from 'yargs'; + +/** + * A Yargs middleware that normalizes non Array options when the argument has been provided multiple times. + * + * By default, when an option is non array and it is provided multiple times in the command line, yargs + * will not override it's value but instead it will be changed to an array unless `duplicate-arguments-array` is disabled. + * But this option also have an effect on real array options which isn't desired. + * + * See: https://github.com/yargs/yargs-parser/pull/163#issuecomment-516566614 + */ +export function normalizeOptionsMiddleware(args: yargs.Arguments): void { + // `getOptions` is not included in the types even though it's public API. + // https://github.com/yargs/yargs/issues/2098 + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const { array } = (yargs as any).getOptions(); + const arrayOptions = new Set(array); + + for (const [key, value] of Object.entries(args)) { + if (key !== '_' && Array.isArray(value) && !arrayOptions.has(key)) { + const newValue = value.pop(); + // eslint-disable-next-line no-console + console.warn( + `Option '${key}' has been specified multiple times. The value '${newValue}' will be used.`, + ); + args[key] = newValue; + } + } +} diff --git a/tests/legacy-cli/e2e/tests/misc/duplicate-command-line-option.ts b/tests/legacy-cli/e2e/tests/misc/duplicate-command-line-option.ts new file mode 100644 index 000000000000..a445e9051ade --- /dev/null +++ b/tests/legacy-cli/e2e/tests/misc/duplicate-command-line-option.ts @@ -0,0 +1,19 @@ +import { ng } from '../../utils/process'; +import { expectFileToExist } from '../../utils/fs'; + +export default async function () { + const { stderr } = await ng( + 'generate', + 'component', + 'test-component', + '--style=scss', + '--style=sass', + ); + + const warningMatch = `Option 'style' has been specified multiple times. The value 'sass' will be used`; + if (!stderr.includes(warningMatch)) { + throw new Error(`Expected stderr to contain: "${warningMatch}".`); + } + + await expectFileToExist('src/app/test-component/test-component.component.sass'); +}