mirror of
https://github.com/angular/angular-cli.git
synced 2025-05-16 10:33:43 +08:00
feat(@angular/cli): allow flags to have deprecation
The feature comes from the "x-deprecated" field in schemas (any schema that is used to parse arguments), and can be a boolean or a string. The parser now takes a logger and will warn users when encountering a deprecated option. These options will also appear in JSON help.
This commit is contained in:
parent
9da4bdca81
commit
456614828f
@ -137,7 +137,7 @@ export abstract class ArchitectCommand<
|
||||
const builderConf = this._architect.getBuilderConfiguration(targetSpec);
|
||||
const builderDesc = await this._architect.getBuilderDescription(builderConf).toPromise();
|
||||
const targetOptionArray = await parseJsonSchemaToOptions(this._registry, builderDesc.schema);
|
||||
const overrides = parseArguments(options, targetOptionArray);
|
||||
const overrides = parseArguments(options, targetOptionArray, this.logger);
|
||||
|
||||
if (overrides['--']) {
|
||||
(overrides['--'] || []).forEach(additional => {
|
||||
|
@ -182,7 +182,7 @@ export async function runCommand(
|
||||
}
|
||||
|
||||
try {
|
||||
const parsedOptions = parser.parseArguments(args, description.options);
|
||||
const parsedOptions = parser.parseArguments(args, description.options, logger);
|
||||
Command.setCommandMap(commandMap);
|
||||
const command = new description.impl({ workspace }, description, logger);
|
||||
|
||||
|
@ -144,6 +144,12 @@ export interface Option {
|
||||
*/
|
||||
positional?: number;
|
||||
|
||||
/**
|
||||
* Deprecation. If this flag is not false a warning will be shown on the console. Either `true`
|
||||
* or a string to show the user as a notice.
|
||||
*/
|
||||
deprecated?: boolean | string;
|
||||
|
||||
/**
|
||||
* Smart default object.
|
||||
*/
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*
|
||||
*/
|
||||
import { BaseException, strings } from '@angular-devkit/core';
|
||||
import { BaseException, logging, strings } from '@angular-devkit/core';
|
||||
import { Arguments, Option, OptionType, Value } from './interface';
|
||||
|
||||
|
||||
@ -112,12 +112,15 @@ function _getOptionFromName(name: string, options: Option[]): Option | undefined
|
||||
function _assignOption(
|
||||
arg: string,
|
||||
args: string[],
|
||||
options: Option[],
|
||||
parsedOptions: Arguments,
|
||||
_positionals: string[],
|
||||
leftovers: string[],
|
||||
ignored: string[],
|
||||
errors: string[],
|
||||
{ options, parsedOptions, leftovers, ignored, errors, deprecations }: {
|
||||
options: Option[],
|
||||
parsedOptions: Arguments,
|
||||
positionals: string[],
|
||||
leftovers: string[],
|
||||
ignored: string[],
|
||||
errors: string[],
|
||||
deprecations: string[],
|
||||
},
|
||||
) {
|
||||
const from = arg.startsWith('--') ? 2 : 1;
|
||||
let key = arg.substr(from);
|
||||
@ -185,6 +188,11 @@ function _assignOption(
|
||||
const v = _coerce(value, option, parsedOptions[option.name]);
|
||||
if (v !== undefined) {
|
||||
parsedOptions[option.name] = v;
|
||||
|
||||
if (option.deprecated !== undefined && option.deprecated !== false) {
|
||||
deprecations.push(`Option ${JSON.stringify(option.name)} is deprecated${
|
||||
typeof option.deprecated == 'string' ? ': ' + option.deprecated : ''}.`);
|
||||
}
|
||||
} else {
|
||||
let error = `Argument ${key} could not be parsed using value ${JSON.stringify(value)}.`;
|
||||
if (option.enum) {
|
||||
@ -258,9 +266,14 @@ export function parseFreeFormArguments(args: string[]): Arguments {
|
||||
*
|
||||
* @param args The argument array to parse.
|
||||
* @param options List of supported options. {@see Option}.
|
||||
* @param logger Logger to use to warn users.
|
||||
* @returns An object that contains a property per option.
|
||||
*/
|
||||
export function parseArguments(args: string[], options: Option[] | null): Arguments {
|
||||
export function parseArguments(
|
||||
args: string[],
|
||||
options: Option[] | null,
|
||||
logger?: logging.Logger,
|
||||
): Arguments {
|
||||
if (options === null) {
|
||||
options = [];
|
||||
}
|
||||
@ -271,6 +284,9 @@ export function parseArguments(args: string[], options: Option[] | null): Argume
|
||||
|
||||
const ignored: string[] = [];
|
||||
const errors: string[] = [];
|
||||
const deprecations: string[] = [];
|
||||
|
||||
const state = { options, parsedOptions, positionals, leftovers, ignored, errors, deprecations };
|
||||
|
||||
for (let arg = args.shift(); arg !== undefined; arg = args.shift()) {
|
||||
if (arg == '--') {
|
||||
@ -280,7 +296,7 @@ export function parseArguments(args: string[], options: Option[] | null): Argume
|
||||
}
|
||||
|
||||
if (arg.startsWith('--')) {
|
||||
_assignOption(arg, args, options, parsedOptions, positionals, leftovers, ignored, errors);
|
||||
_assignOption(arg, args, state);
|
||||
} else if (arg.startsWith('-')) {
|
||||
// Argument is of form -abcdef. Starts at 1 because we skip the `-`.
|
||||
for (let i = 1; i < arg.length; i++) {
|
||||
@ -288,14 +304,14 @@ export function parseArguments(args: string[], options: Option[] | null): Argume
|
||||
// If the next character is an '=', treat it as a long flag.
|
||||
if (arg[i + 1] == '=') {
|
||||
const f = '-' + flag + arg.slice(i + 1);
|
||||
_assignOption(f, args, options, parsedOptions, positionals, leftovers, ignored, errors);
|
||||
_assignOption(f, args, state);
|
||||
break;
|
||||
}
|
||||
// Treat the last flag as `--a` (as if full flag but just one letter). We do this in
|
||||
// the loop because it saves us a check to see if the arg is just `-`.
|
||||
if (i == arg.length - 1) {
|
||||
const arg = '-' + flag;
|
||||
_assignOption(arg, args, options, parsedOptions, positionals, leftovers, ignored, errors);
|
||||
_assignOption(arg, args, state);
|
||||
} else {
|
||||
const maybeOption = _getOptionFromName(flag, options);
|
||||
if (maybeOption) {
|
||||
@ -352,6 +368,10 @@ export function parseArguments(args: string[], options: Option[] | null): Argume
|
||||
parsedOptions['--'] = [...positionals, ...leftovers];
|
||||
}
|
||||
|
||||
if (deprecations.length > 0 && logger) {
|
||||
deprecations.forEach(message => logger.warn(message));
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
throw new ParseArgumentException(errors, parsedOptions, ignored);
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*
|
||||
*/
|
||||
import { logging } from '@angular-devkit/core';
|
||||
import { Arguments, Option, OptionType } from './interface';
|
||||
import { ParseArgumentException, parseArguments } from './parser';
|
||||
|
||||
@ -157,4 +158,41 @@ describe('parseArguments', () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('handles deprecation', () => {
|
||||
const options = [
|
||||
{ name: 'bool', aliases: [], type: OptionType.Boolean, description: '' },
|
||||
{ name: 'depr', aliases: [], type: OptionType.Boolean, description: '', deprecated: true },
|
||||
{ name: 'deprM', aliases: [], type: OptionType.Boolean, description: '', deprecated: 'ABCD' },
|
||||
];
|
||||
|
||||
const logger = new logging.Logger('');
|
||||
const messages: string[] = [];
|
||||
|
||||
logger.subscribe(entry => messages.push(entry.message));
|
||||
|
||||
let result = parseArguments(['--bool'], options, logger);
|
||||
expect(result).toEqual({ bool: true });
|
||||
expect(messages).toEqual([]);
|
||||
|
||||
result = parseArguments(['--depr'], options, logger);
|
||||
expect(result).toEqual({ depr: true });
|
||||
expect(messages.length).toEqual(1);
|
||||
expect(messages[0]).toMatch(/\bdepr\b/);
|
||||
messages.shift();
|
||||
|
||||
result = parseArguments(['--depr', '--bool'], options, logger);
|
||||
expect(result).toEqual({ depr: true, bool: true });
|
||||
expect(messages.length).toEqual(1);
|
||||
expect(messages[0]).toMatch(/\bdepr\b/);
|
||||
messages.shift();
|
||||
|
||||
result = parseArguments(['--depr', '--bool', '--deprM'], options, logger);
|
||||
expect(result).toEqual({ depr: true, deprM: true, bool: true });
|
||||
expect(messages.length).toEqual(2);
|
||||
expect(messages[0]).toMatch(/\bdepr\b/);
|
||||
expect(messages[1]).toMatch(/\bdeprM\b/);
|
||||
expect(messages[1]).toMatch(/\bABCD\b/);
|
||||
messages.shift();
|
||||
});
|
||||
});
|
||||
|
@ -533,7 +533,7 @@ export abstract class SchematicCommand<
|
||||
schematicOptions: string[],
|
||||
options: Option[] | null,
|
||||
): Promise<Arguments> {
|
||||
return parseArguments(schematicOptions, options);
|
||||
return parseArguments(schematicOptions, options, this.logger);
|
||||
}
|
||||
|
||||
private async _loadWorkspace() {
|
||||
|
@ -248,6 +248,11 @@ export async function parseJsonSchemaToOptions(
|
||||
const visible = current.visible === undefined || current.visible === true;
|
||||
const hidden = !!current.hidden || !visible;
|
||||
|
||||
// Deprecated is set only if it's true or a string.
|
||||
const xDeprecated = current['x-deprecated'];
|
||||
const deprecated = (xDeprecated === true || typeof xDeprecated == 'string')
|
||||
? xDeprecated : undefined;
|
||||
|
||||
const option: Option = {
|
||||
name,
|
||||
description: '' + (current.description === undefined ? '' : current.description),
|
||||
@ -258,6 +263,7 @@ export async function parseJsonSchemaToOptions(
|
||||
aliases,
|
||||
...format !== undefined ? { format } : {},
|
||||
hidden,
|
||||
...deprecated !== undefined ? { deprecated } : {},
|
||||
...positional !== undefined ? { positional } : {},
|
||||
};
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user