/
githubmirror
/
angular-cli
Обзор
Документация
Войти
/
githubmirror
/
angular-cli
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
packages/angular/cli/src/command-builder/architect-base-command-module.ts
297 строк
8 KB
Charles Lyding
fix(@angular/cli): ensure dependencies are resolved correctly for node modules directory check
20 ноя 2025, 01:15
20 ноя 2025, 01:15
6009030
Код
Авторство
О чём код?
/** * @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.dev/license */ import { Architect, Target } from '@angular-devkit/architect'; import { NodeModulesBuilderInfo, WorkspaceNodeModulesArchitectHost, } from '@angular-devkit/architect/node'; import { json } from '@angular-devkit/core'; import { createRequire } from 'node:module'; import { isPackageNameSafeForAnalytics } from '../analytics/analytics'; import { EventCustomDimension, EventCustomMetric } from '../analytics/analytics-parameters'; import { assertIsError } from '../utilities/error'; import { askConfirmation, askQuestion } from '../utilities/prompt'; import { isTTY } from '../utilities/tty'; import { CommandModule, CommandModuleError, CommandModuleImplementation, CommandScope, OtherOptions, } from './command-module'; import { Option, parseJsonSchemaToOptions } from './utilities/json-schema'; export interface MissingTargetChoice { name: string; value: string; } export abstract class ArchitectBaseCommandModule<T extends object> extends CommandModule<T> implements CommandModuleImplementation<T> { override scope = CommandScope.In; protected readonly missingTargetChoices: MissingTargetChoice[] | undefined; protected async runSingleTarget(target: Target, options: OtherOptions): Promise<number> { const architectHost = this.getArchitectHost(); let builderName: string; try { builderName = await architectHost.getBuilderNameForTarget(target); } catch (e) { assertIsError(e); return this.onMissingTarget(e.message); } const isAngularBuild = builderName.startsWith('@angular/build:'); const { logger } = this.context; const run = await this.getArchitect(isAngularBuild).scheduleTarget( target, options as json.JsonObject, { logger, }, ); const analytics = isPackageNameSafeForAnalytics(builderName) ? await this.getAnalytics() : undefined; let outputSubscription; if (analytics) { analytics.reportArchitectRunEvent({ [EventCustomDimension.BuilderTarget]: builderName, }); let firstRun = true; outputSubscription = run.output.subscribe(({ stats }) => { const parameters = this.builderStatsToAnalyticsParameters(stats, builderName); if (!parameters) { return; } if (firstRun) { firstRun = false; analytics.reportBuildRunEvent(parameters); } else { analytics.reportRebuildRunEvent(parameters); } }); } try { const { error, success } = await run.lastOutput; if (error) { logger.error(error); } return success ? 0 : 1; } finally { await run.stop(); outputSubscription?.unsubscribe(); } } private builderStatsToAnalyticsParameters( stats: json.JsonValue, builderName: string, ): Partial< | Record<EventCustomDimension & EventCustomMetric, string | number | undefined | boolean> | undefined > { if (!stats || typeof stats !== 'object' || !('durationInMs' in stats)) { return undefined; } const { optimization, allChunksCount, aot, lazyChunksCount, initialChunksCount, durationInMs, changedChunksCount, cssSizeInBytes, jsSizeInBytes, ngComponentCount, } = stats; return { [EventCustomDimension.BuilderTarget]: builderName, [EventCustomDimension.Aot]: aot, [EventCustomDimension.Optimization]: optimization, [EventCustomMetric.AllChunksCount]: allChunksCount, [EventCustomMetric.LazyChunksCount]: lazyChunksCount, [EventCustomMetric.InitialChunksCount]: initialChunksCount, [EventCustomMetric.ChangedChunksCount]: changedChunksCount, [EventCustomMetric.DurationInMs]: durationInMs, [EventCustomMetric.JsSizeInBytes]: jsSizeInBytes, [EventCustomMetric.CssSizeInBytes]: cssSizeInBytes, [EventCustomMetric.NgComponentCount]: ngComponentCount, }; } private _architectHost: WorkspaceNodeModulesArchitectHost | undefined; protected getArchitectHost(): WorkspaceNodeModulesArchitectHost { if (this._architectHost) { return this._architectHost; } const workspace = this.getWorkspaceOrThrow(); return (this._architectHost = new WorkspaceNodeModulesArchitectHost( workspace, workspace.basePath, )); } private _architect: Architect | undefined; protected getArchitect(skipUndefinedArrayTransform: boolean): Architect { if (this._architect) { return this._architect; } const registry = new json.schema.CoreSchemaRegistry(); if (skipUndefinedArrayTransform) { registry.addPostTransform(json.schema.transforms.addUndefinedObjectDefaults); } else { registry.addPostTransform(json.schema.transforms.addUndefinedDefaults); } registry.useXDeprecatedProvider((msg) => this.context.logger.warn(msg)); const architectHost = this.getArchitectHost(); return (this._architect = new Architect(architectHost, registry)); } protected async getArchitectTargetOptions(target: Target): Promise<Option[]> { const architectHost = this.getArchitectHost(); let builderConf: string; try { builderConf = await architectHost.getBuilderNameForTarget(target); } catch { return []; } let builderDesc: NodeModulesBuilderInfo; try { builderDesc = await architectHost.resolveBuilder(builderConf); } catch (e) { assertIsError(e); if (e.code === 'MODULE_NOT_FOUND') { this.warnOnMissingNodeModules(); throw new CommandModuleError(`Could not find the '${builderConf}' builder's node package.`); } throw e; } return parseJsonSchemaToOptions( new json.schema.CoreSchemaRegistry(), builderDesc.optionSchema as json.JsonObject, true, ); } private warnOnMissingNodeModules(): void { const basePath = this.context.workspace?.basePath; if (!basePath) { return; } const workspaceResolve = createRequire(basePath + '/').resolve; try { workspaceResolve('@angular/core'); return; } catch {} this.context.logger.warn( `Node packages may not be installed. Try installing with '${this.context.packageManager.name} install'.`, ); } protected getArchitectTarget(): string { return this.commandName; } protected async onMissingTarget(defaultMessage: string): Promise<1> { const { logger } = this.context; const choices = this.missingTargetChoices; if (!choices?.length) { logger.error(defaultMessage); return 1; } const missingTargetMessage = `Cannot find "${this.getArchitectTarget()}" target for the specified project.\n` + `You can add a package that implements these capabilities.\n\n` + `For example:\n` + choices.map(({ name, value }) => ` ${name}: ng add ${value}`).join('\n') + '\n'; if (isTTY()) { // Use prompts to ask the user if they'd like to install a package. logger.warn(missingTargetMessage); const packageToInstall = await this.getMissingTargetPackageToInstall(choices); if (packageToInstall) { // Example run: `ng add angular-eslint`. const AddCommandModule = (await import('../commands/add/cli')).default; await new AddCommandModule(this.context).run({ interactive: true, force: false, dryRun: false, defaults: false, collection: packageToInstall, }); } } else { // Non TTY display error message. logger.error(missingTargetMessage); } return 1; } private async getMissingTargetPackageToInstall( choices: MissingTargetChoice[], ): Promise<string | null> { if (choices.length === 1) { // Single choice const { name, value } = choices[0]; if (await askConfirmation(`Would you like to add ${name} now?`, true, false)) { return value; } return null; } // Multiple choice return askQuestion( `Would you like to add a package with "${this.getArchitectTarget()}" capabilities now?`, [ { name: 'No', value: null, }, ...choices, ], 0, null, ); } }