mirror of
https://github.com/angular/angular-cli.git
synced 2025-05-16 10:33:43 +08:00
feat(@angular/cli): add subcommand to options
SubCommands are not tied to the option that triggers them. They contain a subset of a CommandDescription interface, with at least a short and long description and usage notes. These are generated from the subcommand schema (e.g. schematics in case of generate).
This commit is contained in:
parent
6622aa9d1a
commit
34818b0346
@ -8,9 +8,9 @@
|
||||
|
||||
// tslint:disable:no-global-tslint-disable no-any
|
||||
import { terminal } from '@angular-devkit/core';
|
||||
import { Arguments, Option } from '../models/interface';
|
||||
import { Arguments, SubCommandDescription } from '../models/interface';
|
||||
import { SchematicCommand } from '../models/schematic-command';
|
||||
import { parseJsonSchemaToOptions } from '../utilities/json-schema';
|
||||
import { parseJsonSchemaToSubCommandDescription } from '../utilities/json-schema';
|
||||
import { Schema as GenerateCommandSchema } from './generate';
|
||||
|
||||
export class GenerateCommand extends SchematicCommand<GenerateCommandSchema> {
|
||||
@ -21,7 +21,7 @@ export class GenerateCommand extends SchematicCommand<GenerateCommandSchema> {
|
||||
const [collectionName, schematicName] = this.parseSchematicInfo(options);
|
||||
|
||||
const collection = this.getCollection(collectionName);
|
||||
this.description.suboptions = {};
|
||||
const subcommands: { [name: string]: SubCommandDescription } = {};
|
||||
|
||||
const schematicNames = schematicName ? [schematicName] : collection.listSchematicNames();
|
||||
// Sort as a courtesy for the user.
|
||||
@ -29,24 +29,28 @@ export class GenerateCommand extends SchematicCommand<GenerateCommandSchema> {
|
||||
|
||||
for (const name of schematicNames) {
|
||||
const schematic = this.getSchematic(collection, name, true);
|
||||
let options: Option[] = [];
|
||||
let subcommand: SubCommandDescription;
|
||||
if (schematic.description.schemaJson) {
|
||||
options = await parseJsonSchemaToOptions(
|
||||
subcommand = await parseJsonSchemaToSubCommandDescription(
|
||||
name,
|
||||
schematic.description.path,
|
||||
this._workflow.registry,
|
||||
schematic.description.schemaJson,
|
||||
);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this.getDefaultSchematicCollection() == collectionName) {
|
||||
this.description.suboptions[name] = options;
|
||||
subcommands[name] = subcommand;
|
||||
} else {
|
||||
this.description.suboptions[`${collectionName}:${name}`] = options;
|
||||
subcommands[`${collectionName}:${name}`] = subcommand;
|
||||
}
|
||||
}
|
||||
|
||||
this.description.options.forEach(option => {
|
||||
if (option.name == 'schematic') {
|
||||
option.type = 'suboption';
|
||||
option.subcommands = subcommands;
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -86,7 +90,9 @@ export class GenerateCommand extends SchematicCommand<GenerateCommandSchema> {
|
||||
await super.printHelp(options);
|
||||
|
||||
this.logger.info('');
|
||||
if (Object.keys(this.description.suboptions || {}).length == 1) {
|
||||
// Find the generate subcommand.
|
||||
const subcommand = this.description.options.filter(x => x.subcommands)[0];
|
||||
if (Object.keys((subcommand && subcommand.subcommands) || {}).length == 1) {
|
||||
this.logger.info(`\nTo see help for a schematic run:`);
|
||||
this.logger.info(terminal.cyan(` ng generate <schematic> --help`));
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ import {
|
||||
CommandDescriptionMap,
|
||||
CommandScope,
|
||||
CommandWorkspace,
|
||||
Option,
|
||||
Option, SubCommandDescription,
|
||||
} from './interface';
|
||||
|
||||
export interface BaseCommandOptions {
|
||||
@ -76,6 +76,12 @@ export abstract class Command<T extends BaseCommandOptions = BaseCommandOptions>
|
||||
this.logger.info('');
|
||||
}
|
||||
|
||||
protected async printHelpSubcommand(subcommand: SubCommandDescription) {
|
||||
this.logger.info(subcommand.description);
|
||||
|
||||
await this.printHelpOptions(subcommand.options);
|
||||
}
|
||||
|
||||
protected async printHelpOptions(options: Option[] = this.description.options) {
|
||||
const args = options.filter(opt => opt.positional !== undefined);
|
||||
const opts = options.filter(opt => opt.positional === undefined);
|
||||
|
@ -88,13 +88,23 @@ export interface Option {
|
||||
* The type of option value. If multiple types exist, this type will be the first one, and the
|
||||
* types array will contain all types accepted.
|
||||
*/
|
||||
type: OptionType | 'suboption';
|
||||
type: OptionType;
|
||||
|
||||
/**
|
||||
* {@see type}
|
||||
*/
|
||||
types?: OptionType[];
|
||||
|
||||
/**
|
||||
* If this option maps to a subcommand in the parent command, will contain all the subcommands
|
||||
* supported. There is a maximum of 1 subcommand Option per command, and the type of this
|
||||
* option will always be "string" (no other types). The value of this option will map into
|
||||
* this map and return the extra information.
|
||||
*/
|
||||
subcommands?: {
|
||||
[name: string]: SubCommandDescription;
|
||||
};
|
||||
|
||||
/**
|
||||
* Aliases supported by this option.
|
||||
*/
|
||||
@ -143,26 +153,26 @@ export enum CommandScope {
|
||||
}
|
||||
|
||||
/**
|
||||
* A description of a command, its metadata.
|
||||
* A description of a command and its options.
|
||||
*/
|
||||
export interface CommandDescription {
|
||||
export interface SubCommandDescription {
|
||||
/**
|
||||
* Name of the command.
|
||||
* The name of the subcommand.
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* Short description (1-2 lines) of this command.
|
||||
* Short description (1-2 lines) of this sub command.
|
||||
*/
|
||||
description: string;
|
||||
|
||||
/**
|
||||
* A long description of the option, in Markdown format.
|
||||
* A long description of the sub command, in Markdown format.
|
||||
*/
|
||||
longDescription?: string;
|
||||
|
||||
/**
|
||||
* Additional notes about usage of this command.
|
||||
* Additional notes about usage of this sub command, in Markdown format.
|
||||
*/
|
||||
usageNotes?: string;
|
||||
|
||||
@ -172,10 +182,15 @@ export interface CommandDescription {
|
||||
options: Option[];
|
||||
|
||||
/**
|
||||
* Aliases supported for this command.
|
||||
* Aliases supported for this sub command.
|
||||
*/
|
||||
aliases: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* A description of a command, its metadata.
|
||||
*/
|
||||
export interface CommandDescription extends SubCommandDescription {
|
||||
/**
|
||||
* Scope of the command, whether it can be executed in a project, outside of a project or
|
||||
* anywhere.
|
||||
@ -191,13 +206,6 @@ export interface CommandDescription {
|
||||
* The constructor of the command, which should be extending the abstract Command<> class.
|
||||
*/
|
||||
impl: CommandConstructor;
|
||||
|
||||
/**
|
||||
* Suboptions.
|
||||
*/
|
||||
suboptions?: {
|
||||
[name: string]: Option[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface OptionSmartDefault {
|
||||
|
@ -60,8 +60,6 @@ function _coerceType(str: string | undefined, type: OptionType, v?: Value): Valu
|
||||
function _coerce(str: string | undefined, o: Option | null, v?: Value): Value | undefined {
|
||||
if (!o) {
|
||||
return _coerceType(str, OptionType.Any, v);
|
||||
} else if (o.type == 'suboption') {
|
||||
return _coerceType(str, OptionType.String, v);
|
||||
} else {
|
||||
return _coerceType(str, o.type, v);
|
||||
}
|
||||
|
@ -116,46 +116,60 @@ export abstract class SchematicCommand<
|
||||
await super.printHelp(options);
|
||||
this.logger.info('');
|
||||
|
||||
const schematicNames = Object.keys(this.description.suboptions || {});
|
||||
const subCommandOption = this.description.options.filter(x => x.subcommands)[0];
|
||||
|
||||
if (this.description.suboptions) {
|
||||
if (schematicNames.length > 1) {
|
||||
this.logger.info('Available Schematics:');
|
||||
if (!subCommandOption || !subCommandOption.subcommands) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const namesPerCollection: { [c: string]: string[] } = {};
|
||||
schematicNames.forEach(name => {
|
||||
const [collectionName, schematicName] = name.split(/:/, 2);
|
||||
const schematicNames = Object.keys(subCommandOption.subcommands);
|
||||
|
||||
if (!namesPerCollection[collectionName]) {
|
||||
namesPerCollection[collectionName] = [];
|
||||
}
|
||||
if (schematicNames.length > 1) {
|
||||
this.logger.info('Available Schematics:');
|
||||
|
||||
namesPerCollection[collectionName].push(schematicName);
|
||||
const namesPerCollection: { [c: string]: string[] } = {};
|
||||
schematicNames.forEach(name => {
|
||||
let [collectionName, schematicName] = name.split(/:/, 2);
|
||||
if (!schematicName) {
|
||||
schematicName = collectionName;
|
||||
collectionName = this.collectionName;
|
||||
}
|
||||
|
||||
if (!namesPerCollection[collectionName]) {
|
||||
namesPerCollection[collectionName] = [];
|
||||
}
|
||||
|
||||
namesPerCollection[collectionName].push(schematicName);
|
||||
});
|
||||
|
||||
const defaultCollection = this.getDefaultSchematicCollection();
|
||||
Object.keys(namesPerCollection).forEach(collectionName => {
|
||||
const isDefault = defaultCollection == collectionName;
|
||||
this.logger.info(
|
||||
` Collection "${collectionName}"${isDefault ? ' (default)' : ''}:`,
|
||||
);
|
||||
|
||||
namesPerCollection[collectionName].forEach(schematicName => {
|
||||
this.logger.info(` ${schematicName}`);
|
||||
});
|
||||
|
||||
const defaultCollection = this.getDefaultSchematicCollection();
|
||||
Object.keys(namesPerCollection).forEach(collectionName => {
|
||||
const isDefault = defaultCollection == collectionName;
|
||||
this.logger.info(
|
||||
` Collection "${collectionName}"${isDefault ? ' (default)' : ''}:`,
|
||||
);
|
||||
|
||||
namesPerCollection[collectionName].forEach(schematicName => {
|
||||
this.logger.info(` ${schematicName}`);
|
||||
});
|
||||
});
|
||||
} else if (schematicNames.length == 1) {
|
||||
this.logger.info('Options for schematic ' + schematicNames[0]);
|
||||
await this.printHelpOptions(this.description.suboptions[schematicNames[0]]);
|
||||
}
|
||||
});
|
||||
} else if (schematicNames.length == 1) {
|
||||
this.logger.info('Help for schematic ' + schematicNames[0]);
|
||||
await this.printHelpSubcommand(subCommandOption.subcommands[schematicNames[0]]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
async printHelpUsage() {
|
||||
const schematicNames = Object.keys(this.description.suboptions || {});
|
||||
if (this.description.suboptions && schematicNames.length == 1) {
|
||||
const subCommandOption = this.description.options.filter(x => x.subcommands)[0];
|
||||
|
||||
if (!subCommandOption || !subCommandOption.subcommands) {
|
||||
return;
|
||||
}
|
||||
|
||||
const schematicNames = Object.keys(subCommandOption.subcommands);
|
||||
if (schematicNames.length == 1) {
|
||||
this.logger.info(this.description.description);
|
||||
|
||||
const opts = this.description.options.filter(x => x.positional === undefined);
|
||||
@ -167,7 +181,7 @@ export abstract class SchematicCommand<
|
||||
? schematicName
|
||||
: schematicNames[0];
|
||||
|
||||
const schematicOptions = this.description.suboptions[schematicNames[0]];
|
||||
const schematicOptions = subCommandOption.subcommands[schematicNames[0]].options;
|
||||
const schematicArgs = schematicOptions.filter(x => x.positional !== undefined);
|
||||
const argDisplay = schematicArgs.length > 0
|
||||
? ' ' + schematicArgs.map(a => `<${strings.dasherize(a.name)}>`).join(' ')
|
||||
|
@ -14,7 +14,7 @@ import {
|
||||
CommandDescription,
|
||||
CommandScope,
|
||||
Option,
|
||||
OptionType,
|
||||
OptionType, SubCommandDescription,
|
||||
} from '../models/interface';
|
||||
|
||||
function _getEnumFromValue<E, T extends string>(v: json.JsonValue, e: E, d: T): T {
|
||||
@ -29,23 +29,12 @@ function _getEnumFromValue<E, T extends string>(v: json.JsonValue, e: E, d: T):
|
||||
return d;
|
||||
}
|
||||
|
||||
export async function parseJsonSchemaToCommandDescription(
|
||||
export async function parseJsonSchemaToSubCommandDescription(
|
||||
name: string,
|
||||
jsonPath: string,
|
||||
registry: json.schema.SchemaRegistry,
|
||||
schema: json.JsonObject,
|
||||
): Promise<CommandDescription> {
|
||||
// Before doing any work, let's validate the implementation.
|
||||
if (typeof schema.$impl != 'string') {
|
||||
throw new Error(`Command ${name} has an invalid implementation.`);
|
||||
}
|
||||
const ref = new ExportStringRef<CommandConstructor>(schema.$impl, dirname(jsonPath));
|
||||
const impl = ref.ref;
|
||||
|
||||
if (impl === undefined || typeof impl !== 'function') {
|
||||
throw new Error(`Command ${name} has an invalid implementation.`);
|
||||
}
|
||||
|
||||
): Promise<SubCommandDescription> {
|
||||
const options = await parseJsonSchemaToOptions(registry, schema);
|
||||
|
||||
const aliases: string[] = [];
|
||||
@ -78,20 +67,44 @@ export async function parseJsonSchemaToCommandDescription(
|
||||
usageNotes = readFileSync(unPath, 'utf-8');
|
||||
}
|
||||
|
||||
const scope = _getEnumFromValue(schema.$scope, CommandScope, CommandScope.Default);
|
||||
const type = _getEnumFromValue(schema.$type, CommandType, CommandType.Default);
|
||||
const description = '' + (schema.description === undefined ? '' : schema.description);
|
||||
const hidden = !!schema.$hidden;
|
||||
|
||||
return {
|
||||
name,
|
||||
description,
|
||||
...(longDescription ? { longDescription } : {}),
|
||||
...(usageNotes ? { usageNotes } : {}),
|
||||
hidden,
|
||||
options,
|
||||
aliases,
|
||||
};
|
||||
}
|
||||
|
||||
export async function parseJsonSchemaToCommandDescription(
|
||||
name: string,
|
||||
jsonPath: string,
|
||||
registry: json.schema.SchemaRegistry,
|
||||
schema: json.JsonObject,
|
||||
): Promise<CommandDescription> {
|
||||
const subcommand = await parseJsonSchemaToSubCommandDescription(name, jsonPath, registry, schema);
|
||||
|
||||
// Before doing any work, let's validate the implementation.
|
||||
if (typeof schema.$impl != 'string') {
|
||||
throw new Error(`Command ${name} has an invalid implementation.`);
|
||||
}
|
||||
const ref = new ExportStringRef<CommandConstructor>(schema.$impl, dirname(jsonPath));
|
||||
const impl = ref.ref;
|
||||
|
||||
if (impl === undefined || typeof impl !== 'function') {
|
||||
throw new Error(`Command ${name} has an invalid implementation.`);
|
||||
}
|
||||
|
||||
const scope = _getEnumFromValue(schema.$scope, CommandScope, CommandScope.Default);
|
||||
const hidden = !!schema.$hidden;
|
||||
|
||||
return {
|
||||
...subcommand,
|
||||
scope,
|
||||
hidden,
|
||||
impl,
|
||||
};
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user