diff --git a/packages/angular/cli/commands/lint-impl.ts b/packages/angular/cli/commands/lint-impl.ts index 8067ea26eea4..e9fb4dc801b3 100644 --- a/packages/angular/cli/commands/lint-impl.ts +++ b/packages/angular/cli/commands/lint-impl.ts @@ -6,8 +6,11 @@ * found in the LICENSE file at https://angular.io/license */ +import { spawnSync } from 'child_process'; +import * as path from 'path'; import { ArchitectCommand } from '../models/architect-command'; import { Arguments } from '../models/interface'; +import { askConfirmation } from '../utilities/prompt'; import { Schema as LintCommandSchema } from './lint'; const MissingBuilder = ` @@ -22,11 +25,33 @@ For example: export class LintCommand extends ArchitectCommand { override readonly target = 'lint'; override readonly multiTarget = true; - override readonly missingTargetError = MissingBuilder; override async initialize(options: LintCommandSchema & Arguments): Promise { if (!options.help) { return super.initialize(options); } } + + override async onMissingTarget(): Promise { + this.logger.warn(MissingBuilder); + + const shouldAdd = await askConfirmation('Would you like to add ESLint now?', true, false); + if (shouldAdd) { + // Run `ng add @angular-eslint/schematics` + const binPath = path.resolve(__dirname, '../bin/ng.js'); + const { status, error } = spawnSync( + process.execPath, + [binPath, 'add', '@angular-eslint/schematics'], + { + stdio: 'inherit', + }, + ); + + if (error) { + throw error; + } + + return status ?? 0; + } + } } diff --git a/packages/angular/cli/models/architect-command.ts b/packages/angular/cli/models/architect-command.ts index 2acf81119921..713ce5e483b5 100644 --- a/packages/angular/cli/models/architect-command.ts +++ b/packages/angular/cli/models/architect-command.ts @@ -39,6 +39,23 @@ export abstract class ArchitectCommand< target: string | undefined; missingTargetError: string | undefined; + protected async onMissingTarget(projectName?: string): Promise { + if (this.missingTargetError) { + this.logger.fatal(this.missingTargetError); + + return 1; + } + + if (projectName) { + this.logger.fatal(`Project '${projectName}' does not support the '${this.target}' target.`); + } else { + this.logger.fatal(`No projects support the '${this.target}' target.`); + } + + return 1; + } + + // eslint-disable-next-line max-lines-per-function public override async initialize(options: T & Arguments): Promise { this._registry = new json.schema.CoreSchemaRegistry(); this._registry.addPostTransform(json.schema.transforms.addUndefinedDefaults); @@ -87,21 +104,12 @@ export abstract class ArchitectCommand< } } - if (targetProjectNames.length === 0) { - this.logger.fatal( - this.missingTargetError || `No projects support the '${this.target}' target.`, - ); - - return 1; - } - if (projectName && !targetProjectNames.includes(projectName)) { - this.logger.fatal( - this.missingTargetError || - `Project '${projectName}' does not support the '${this.target}' target.`, - ); + return await this.onMissingTarget(projectName); + } - return 1; + if (targetProjectNames.length === 0) { + return await this.onMissingTarget(); } if (!projectName && commandLeftovers && commandLeftovers.length > 0) {