Alan Agius f6135a20d1 fix(@angular/cli): don't display options multiple times in schematics help output
Previously, we disabled options in the help output multiple times.

Previous output
```
Generates and/or modifies files based on a schematic.
usage: ng generate c <name> [options]

arguments:
  schematic
    The schematic or collection:schematic to generate.
  name
    The name of the component.

options:
  --change-detection (-c)
    The change detection strategy to use in the new component.
  --defaults
    When true, disables interactive input prompts for options with a default.
  --display-block (-b)
    Specifies if the style will contain `:host { display: block; }`.
  --dry-run (-d)
    When true, runs through and reports activity without writing out results.
  --entry-component
    When true, the new component is the entry component of the declaring NgModule.
  --export
    When true, the declaring NgModule exports this component.
  --flat
    When true, creates the new files at the top level of the current project.
  --force (-f)
    When true, forces overwriting of existing files.
  --help
    Shows a help message for this command in the console.
  --inline-style (-s)
    When true, includes styles inline in the component.ts file. Only CSS styles can be included inline. By default, an external styles file is created and referenced in the component.ts file.
  --inline-template (-t)
    When true, includes template inline in the component.ts file. By default, an external template file is created and referenced in the component.ts file.
  --interactive
    When false, disables interactive input prompts.
  --lint-fix
    When true, applies lint fixes after generating the component.
  --module (-m)
    The declaring NgModule.
  --prefix (-p)
    The prefix to apply to the generated component selector.
  --project
    The name of the project.
  --selector
    The HTML selector to use for this component.
  --skip-import
    When true, does not import this component into the owning NgModule.
  --skip-selector
    Specifies if the component should have a selector or not.
  --skip-tests
    When true, does not create "spec.ts" test files for the new component.
  --style
    The file extension or preprocessor to use for style files.
  --type
    Adds a developer-defined type to the filename, in the format "name.type.ts".
  --view-encapsulation (-v)
    The view encapsulation strategy to use in the new component.

Help for schematic c
Creates a new generic component definition in the given or default project.
arguments:
  name
    The name of the component.

options:
  --change-detection (-c)
    The change detection strategy to use in the new component.
  --display-block (-b)
    Specifies if the style will contain `:host { display: block; }`.
  --entry-component
    When true, the new component is the entry component of the declaring NgModule.
  --export
    When true, the declaring NgModule exports this component.
  --flat
    When true, creates the new files at the top level of the current project.
  --inline-style (-s)
    When true, includes styles inline in the component.ts file. Only CSS styles can be included inline. By default, an external styles file is created and referenced in the component.ts file.
  --inline-template (-t)
    When true, includes template inline in the component.ts file. By default, an external template file is created and referenced in the component.ts file.
  --lint-fix
    When true, applies lint fixes after generating the component.
  --module (-m)
    The declaring NgModule.
  --prefix (-p)
    The prefix to apply to the generated component selector.
  --project
    The name of the project.
  --selector
    The HTML selector to use for this component.
  --skip-import
    When true, does not import this component into the owning NgModule.
  --skip-selector
    Specifies if the component should have a selector or not.
  --skip-tests
    When true, does not create "spec.ts" test files for the new component.
  --style
    The file extension or preprocessor to use for style files.
  --type
    Adds a developer-defined type to the filename, in the format "name.type.ts".
  --view-encapsulation (-v)
    The view encapsulation strategy to use in the new component.

To see help for a schematic run:
  ng generate <schematic> --help
```

New output
```
Generates and/or modifies files based on a schematic.
usage: ng generate c <name> [options]

arguments:
  schematic
    The schematic or collection:schematic to generate.
  name
    The name of the component.

options:
  --change-detection (-c)
    The change detection strategy to use in the new component.
  --defaults
    Disable interactive input prompts for options with a default.
  --display-block (-b)
    Specifies if the style will contain `:host { display: block; }`.
  --dry-run (-d)
    Run through and reports activity without writing out results.
  --export
    The declaring NgModule exports this component.
  --flat
    Create the new files at the top level of the current project.
  --force (-f)
    Force overwriting of existing files.
  --help
    Shows a help message for this command in the console.
  --inline-style (-s)
    Include styles inline in the component.ts file. Only CSS styles can be included inline. By default, an external styles file is created and referenced in the component.ts file.
  --inline-template (-t)
    Include template inline in the component.ts file. By default, an external template file is created and referenced in the component.ts file.
  --interactive
    Enable interactive input prompts.
  --lint-fix
    Apply lint fixes after generating the component.
  --module (-m)
    The declaring NgModule.
  --prefix (-p)
    The prefix to apply to the generated component selector.
  --project
    The name of the project.
  --selector
    The HTML selector to use for this component.
  --skip-import
    Do not import this component into the owning NgModule.
  --skip-selector
    Specifies if the component should have a selector or not.
  --skip-tests
    Do not create "spec.ts" test files for the new component.
  --style
    The file extension or preprocessor to use for style files.
  --type
    Adds a developer-defined type to the filename, in the format "name.type.ts".
  --view-encapsulation (-v)
    The view encapsulation strategy to use in the new component.

To see help for a schematic run:
  ng generate <schematic> --help
```
2021-05-03 11:39:23 -04:00

190 lines
5.9 KiB
TypeScript

/**
* @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 { analytics, logging, strings, tags } from '@angular-devkit/core';
import { colors } from '../utilities/color';
import { AngularWorkspace } from '../utilities/config';
import {
Arguments,
CommandContext,
CommandDescription,
CommandDescriptionMap,
CommandScope,
Option,
SubCommandDescription,
} from './interface';
export interface BaseCommandOptions {
help?: boolean | string;
}
export abstract class Command<T extends BaseCommandOptions = BaseCommandOptions> {
protected allowMissingWorkspace = false;
protected useReportAnalytics = true;
readonly workspace?: AngularWorkspace;
readonly analytics: analytics.Analytics;
protected static commandMap: () => Promise<CommandDescriptionMap>;
static setCommandMap(map: () => Promise<CommandDescriptionMap>) {
this.commandMap = map;
}
constructor(
protected readonly context: CommandContext,
public readonly description: CommandDescription,
protected readonly logger: logging.Logger,
) {
this.workspace = context.workspace;
this.analytics = context.analytics || new analytics.NoopAnalytics();
}
async initialize(options: T & Arguments): Promise<number | void> {}
async printHelp(): Promise<number> {
await this.printHelpUsage();
await this.printHelpOptions();
return 0;
}
async printJsonHelp(): Promise<number> {
const replacer = (key: string, value: string) =>
key === 'name' ? strings.dasherize(value) : value;
this.logger.info(JSON.stringify(this.description, replacer, 2));
return 0;
}
protected async printHelpUsage() {
this.logger.info(this.description.description);
const name = this.description.name;
const args = this.description.options.filter((x) => x.positional !== undefined);
const opts = this.description.options.filter((x) => x.positional === undefined);
const argDisplay =
args && args.length > 0 ? ' ' + args.map((a) => `<${a.name}>`).join(' ') : '';
const optionsDisplay = opts && opts.length > 0 ? ` [options]` : ``;
this.logger.info(`usage: ng ${name}${argDisplay}${optionsDisplay}`);
this.logger.info('');
}
protected async printHelpOptions(options: Option[] = this.description.options) {
const args = options.filter((opt) => opt.positional !== undefined);
const opts = options.filter((opt) => opt.positional === undefined);
const formatDescription = (description: string) =>
` ${description.replace(/\n/g, '\n ')}`;
if (args.length > 0) {
this.logger.info(`arguments:`);
args.forEach((o) => {
this.logger.info(` ${colors.cyan(o.name)}`);
if (o.description) {
this.logger.info(formatDescription(o.description));
}
});
}
if (options.length > 0) {
if (args.length > 0) {
this.logger.info('');
}
this.logger.info(`options:`);
opts
.filter((o) => !o.hidden)
.sort((a, b) => a.name.localeCompare(b.name))
.forEach((o) => {
const aliases =
o.aliases && o.aliases.length > 0
? '(' + o.aliases.map((a) => `-${a}`).join(' ') + ')'
: '';
this.logger.info(` ${colors.cyan('--' + strings.dasherize(o.name))} ${aliases}`);
if (o.description) {
this.logger.info(formatDescription(o.description));
}
});
}
}
async validateScope(scope?: CommandScope): Promise<void> {
switch (scope === undefined ? this.description.scope : scope) {
case CommandScope.OutProject:
if (this.workspace) {
this.logger.fatal(tags.oneLine`
The ${this.description.name} command requires to be run outside of a project, but a
project definition was found at "${this.workspace.filePath}".
`);
// eslint-disable-next-line no-throw-literal
throw 1;
}
break;
case CommandScope.InProject:
if (!this.workspace) {
this.logger.fatal(tags.oneLine`
The ${this.description.name} command requires to be run in an Angular project, but a
project definition could not be found.
`);
// eslint-disable-next-line no-throw-literal
throw 1;
}
break;
case CommandScope.Everywhere:
// Can't miss this.
break;
}
}
async reportAnalytics(
paths: string[],
options: Arguments,
dimensions: (boolean | number | string)[] = [],
metrics: (boolean | number | string)[] = [],
): Promise<void> {
for (const option of this.description.options) {
const ua = option.userAnalytics;
const v = options[option.name];
if (v !== undefined && !Array.isArray(v) && ua) {
dimensions[ua] = v;
}
}
this.analytics.pageview('/command/' + paths.join('/'), { dimensions, metrics });
}
abstract run(options: T & Arguments): Promise<number | void>;
async validateAndRun(options: T & Arguments): Promise<number | void> {
if (!(options.help === true || options.help === 'json' || options.help === 'JSON')) {
await this.validateScope();
}
let result = await this.initialize(options);
if (typeof result === 'number' && result !== 0) {
return result;
}
if (options.help === true) {
return this.printHelp();
} else if (options.help === 'json' || options.help === 'JSON') {
return this.printJsonHelp();
} else {
const startTime = +new Date();
if (this.useReportAnalytics) {
await this.reportAnalytics([this.description.name], options);
}
result = await this.run(options);
const endTime = +new Date();
this.analytics.timing(this.description.name, 'duration', endTime - startTime);
return result;
}
}
}