fix(@angular/cli): avoid exceptions for expected errors in architect commands

Errors caused by invalid options or workspace configuration will now be presented as fatal console messages and the CLI will exit gracefully with an exit code of 1.
This commit is contained in:
Charles Lyding 2021-03-09 11:33:13 -05:00 committed by Filipe Silva
parent af1cc2d642
commit 06335515eb
3 changed files with 29 additions and 20 deletions

View File

@ -28,11 +28,7 @@ export class DeployCommand extends ArchitectCommand<DeployCommandSchema> {
public readonly target = 'deploy';
public readonly missingTargetError = BuilderMissing;
public async run(options: ArchitectCommandOptions & Arguments) {
return this.runArchitectTarget(options);
}
public async initialize(options: DeployCommandSchema & Arguments): Promise<void> {
public async initialize(options: DeployCommandSchema & Arguments): Promise<number | void> {
if (!options.help) {
return super.initialize(options);
}

View File

@ -35,15 +35,15 @@ export abstract class ArchitectCommand<
target: string | undefined;
missingTargetError: string | undefined;
public async initialize(options: T & Arguments): Promise<void> {
await super.initialize(options);
public async initialize(options: T & Arguments): Promise<number | void> {
this._registry = new json.schema.CoreSchemaRegistry();
this._registry.addPostTransform(json.schema.transforms.addUndefinedDefaults);
this._registry.useXDeprecatedProvider(msg => this.logger.warn(msg));
if (!this.workspace) {
throw new Error('A workspace is required for an architect command.');
this.logger.fatal('A workspace is required for this command.');
return 1;
}
this._architectHost = new WorkspaceNodeModulesArchitectHost(this.workspace, this.workspace.basePath);
@ -57,7 +57,9 @@ export abstract class ArchitectCommand<
const specifier = this._makeTargetSpecifier(options);
if (!specifier.project || !specifier.target) {
throw new Error('Cannot determine project or target for command.');
this.logger.fatal('Cannot determine project or target for command.');
return 1;
}
return;
@ -65,7 +67,9 @@ export abstract class ArchitectCommand<
let projectName = options.project;
if (projectName && !this.workspace.projects.has(projectName)) {
throw new Error(`Project '${projectName}' does not exist.`);
this.logger.fatal(`Project '${projectName}' does not exist.`);
return 1;
}
const commandLeftovers = options['--'];
@ -77,12 +81,16 @@ export abstract class ArchitectCommand<
}
if (targetProjectNames.length === 0) {
throw new Error(this.missingTargetError || `No projects support the '${this.target}' target.`);
this.logger.fatal(this.missingTargetError || `No projects support the '${this.target}' target.`);
return 1;
}
if (projectName && !targetProjectNames.includes(projectName)) {
throw new Error(this.missingTargetError ||
this.logger.fatal(this.missingTargetError ||
`Project '${projectName}' does not support the '${this.target}' target.`);
return 1;
}
if (!projectName && commandLeftovers && commandLeftovers.length > 0) {
@ -141,11 +149,13 @@ export abstract class ArchitectCommand<
}
if (!projectName && this.multiTarget && builderNames.size > 1) {
throw new Error(tags.oneLine`
this.logger.fatal(tags.oneLine`
Architect commands with command line overrides cannot target different builders. The
'${this.target}' target would run on projects ${targetProjectNames.join()} which have the
following builders: ${'\n ' + [...builderNames].join('\n ')}
`);
return 1;
}
}
@ -159,7 +169,9 @@ export abstract class ArchitectCommand<
// This is a special case where we just return.
return;
} else {
throw new Error(this.missingTargetError || 'Cannot determine project or target for command.');
this.logger.fatal(this.missingTargetError || 'Cannot determine project or target for command.');
return 1;
}
}

View File

@ -42,9 +42,7 @@ export abstract class Command<T extends BaseCommandOptions = BaseCommandOptions>
this.analytics = context.analytics || new analytics.NoopAnalytics();
}
async initialize(options: T & Arguments): Promise<void> {
return;
}
async initialize(options: T & Arguments): Promise<number | void> {}
async printHelp(): Promise<number> {
await this.printHelpUsage();
@ -169,7 +167,10 @@ export abstract class Command<T extends BaseCommandOptions = BaseCommandOptions>
if (!(options.help === true || options.help === 'json' || options.help === 'JSON')) {
await this.validateScope();
}
await this.initialize(options);
let result = await this.initialize(options);
if (typeof result === 'number' && result !== 0) {
return result;
}
if (options.help === true) {
return this.printHelp();
@ -180,7 +181,7 @@ export abstract class Command<T extends BaseCommandOptions = BaseCommandOptions>
if (this.useReportAnalytics) {
await this.reportAnalytics([this.description.name], options);
}
const result = await this.run(options);
result = await this.run(options);
const endTime = +new Date();
this.analytics.timing(this.description.name, 'duration', endTime - startTime);