diff --git a/packages/angular/cli/src/command-builder/command-module.ts b/packages/angular/cli/src/command-builder/command-module.ts index c8b957890e9f..7c1b2026c7ce 100644 --- a/packages/angular/cli/src/command-builder/command-module.ts +++ b/packages/angular/cli/src/command-builder/command-module.ts @@ -10,6 +10,7 @@ import { analytics, logging, schema, strings } from '@angular-devkit/core'; import { readFileSync } from 'fs'; import * as path from 'path'; import { + Arguments, ArgumentsCamelCase, Argv, CamelCaseKey, @@ -199,6 +200,8 @@ export abstract class CommandModule implements CommandModuleI * **Note:** This method should be called from the command bundler method. */ protected addSchemaOptionsToCommand(localYargs: Argv, options: Option[]): Argv { + const booleanOptionsWithNoPrefix = new Set(); + for (const option of options) { const { default: defaultVal, @@ -223,13 +226,27 @@ export abstract class CommandModule implements CommandModuleI ...(this.context.args.options.help ? { default: defaultVal } : {}), }; + // TODO(alanagius4): remove in a major version. + // the below is an interim workaround to handle options which have been defined in the schema with `no` prefix. + let dashedName = strings.dasherize(name); + if (type === 'boolean' && dashedName.startsWith('no-')) { + dashedName = dashedName.slice(3); + booleanOptionsWithNoPrefix.add(dashedName); + + // eslint-disable-next-line no-console + console.warn( + `Warning: '${name}' option has been declared with a 'no' prefix in the schema.` + + 'Please file an issue with the author of this package.', + ); + } + if (positional === undefined) { - localYargs = localYargs.option(strings.dasherize(name), { + localYargs = localYargs.option(dashedName, { type, ...sharedOptions, }); } else { - localYargs = localYargs.positional(strings.dasherize(name), { + localYargs = localYargs.positional(dashedName, { type: type === 'array' || type === 'count' ? 'string' : type, ...sharedOptions, }); @@ -241,6 +258,19 @@ export abstract class CommandModule implements CommandModuleI } } + // TODO(alanagius4): remove in a major version. + // the below is an interim workaround to handle options which have been defined in the schema with `no` prefix. + if (booleanOptionsWithNoPrefix.size) { + localYargs.middleware((options: Arguments) => { + for (const key of booleanOptionsWithNoPrefix) { + if (key in options) { + options[`no-${key}`] = !options[key]; + delete options[key]; + } + } + }, false); + } + return localYargs; } diff --git a/tests/legacy-cli/e2e/assets/schematic-boolean-option-negated/collection.json b/tests/legacy-cli/e2e/assets/schematic-boolean-option-negated/collection.json new file mode 100644 index 000000000000..828a709d114d --- /dev/null +++ b/tests/legacy-cli/e2e/assets/schematic-boolean-option-negated/collection.json @@ -0,0 +1,9 @@ +{ + "schematics": { + "test": { + "factory": "./index.js", + "schema": "./schema.json", + "description": "test schematic that logs the options in the console." + } + } +} diff --git a/tests/legacy-cli/e2e/assets/schematic-boolean-option-negated/index.js b/tests/legacy-cli/e2e/assets/schematic-boolean-option-negated/index.js new file mode 100644 index 000000000000..59be5fd230ed --- /dev/null +++ b/tests/legacy-cli/e2e/assets/schematic-boolean-option-negated/index.js @@ -0,0 +1 @@ +exports.default = (options) => console.log(options); diff --git a/tests/legacy-cli/e2e/assets/schematic-boolean-option-negated/package.json b/tests/legacy-cli/e2e/assets/schematic-boolean-option-negated/package.json new file mode 100644 index 000000000000..5cd8325bef0c --- /dev/null +++ b/tests/legacy-cli/e2e/assets/schematic-boolean-option-negated/package.json @@ -0,0 +1,5 @@ +{ + "name": "schematic-boolean-option", + "version": "0.0.1", + "schematics": "./collection.json" +} diff --git a/tests/legacy-cli/e2e/assets/schematic-boolean-option-negated/schema.json b/tests/legacy-cli/e2e/assets/schematic-boolean-option-negated/schema.json new file mode 100644 index 000000000000..f455fb6a53bb --- /dev/null +++ b/tests/legacy-cli/e2e/assets/schematic-boolean-option-negated/schema.json @@ -0,0 +1,10 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "type": "object", + "description": "test schematic that logs the options in the console.", + "properties": { + "noWatch": { + "type": "boolean" + } + } +} diff --git a/tests/legacy-cli/e2e/tests/misc/negated-boolean-options.ts b/tests/legacy-cli/e2e/tests/misc/negated-boolean-options.ts new file mode 100644 index 000000000000..7ad94de1c907 --- /dev/null +++ b/tests/legacy-cli/e2e/tests/misc/negated-boolean-options.ts @@ -0,0 +1,24 @@ +import { copyAssets } from '../../utils/assets'; +import { execAndWaitForOutputToMatch } from '../../utils/process'; + +export default async function () { + await copyAssets('schematic-boolean-option-negated', 'schematic-boolean-option-negated'); + + await execAndWaitForOutputToMatch( + 'ng', + ['generate', './schematic-boolean-option-negated:test', '--no-watch'], + /noWatch: true/, + ); + + await execAndWaitForOutputToMatch( + 'ng', + ['generate', './schematic-boolean-option-negated:test', '--watch'], + /noWatch: false/, + ); + + await execAndWaitForOutputToMatch( + 'ng', + ['generate', './schematic-boolean-option-negated:test'], + /'noWatch' option has been declared with a 'no' prefix in the schema/, + ); +}