99import { Architect , Target } from '@angular-devkit/architect' ;
1010import { WorkspaceNodeModulesArchitectHost } from '@angular-devkit/architect/node' ;
1111import { json , schema , tags } from '@angular-devkit/core' ;
12+ import { PackageManager } from 'dist-schema/packages/angular/cli/lib/config/workspace-schema' ;
13+ import { existsSync } from 'fs' ;
14+ import * as path from 'path' ;
1215import { parseJsonSchemaToOptions } from '../utilities/json-schema' ;
16+ import { getPackageManager } from '../utilities/package-manager' ;
1317import { isPackageNameSafeForAnalytics } from './analytics' ;
1418import { BaseCommandOptions , Command } from './command' ;
1519import { Arguments , Option } from './interface' ;
@@ -115,7 +119,19 @@ export abstract class ArchitectCommand<
115119 builderNames . add ( builderName ) ;
116120 }
117121
118- const builderDesc = await this . _architectHost . resolveBuilder ( builderName ) ;
122+ let builderDesc ;
123+ try {
124+ builderDesc = await this . _architectHost . resolveBuilder ( builderName ) ;
125+ } catch ( e ) {
126+ if ( e . code === 'MODULE_NOT_FOUND' ) {
127+ await this . warnOnMissingNodeModules ( this . workspace . basePath ) ;
128+ this . logger . fatal ( `Could not find the '${ builderName } ' builder's node package.` ) ;
129+
130+ return 1 ;
131+ }
132+ throw e ;
133+ }
134+
119135 const optionDefs = await parseJsonSchemaToOptions (
120136 this . _registry ,
121137 builderDesc . optionSchema as json . JsonObject ,
@@ -193,7 +209,19 @@ export abstract class ArchitectCommand<
193209 project : projectName || ( targetProjectNames . length > 0 ? targetProjectNames [ 0 ] : '' ) ,
194210 target : this . target ,
195211 } ) ;
196- const builderDesc = await this . _architectHost . resolveBuilder ( builderConf ) ;
212+
213+ let builderDesc ;
214+ try {
215+ builderDesc = await this . _architectHost . resolveBuilder ( builderConf ) ;
216+ } catch ( e ) {
217+ if ( e . code === 'MODULE_NOT_FOUND' ) {
218+ await this . warnOnMissingNodeModules ( this . workspace . basePath ) ;
219+ this . logger . fatal ( `Could not find the '${ builderConf } ' builder's node package.` ) ;
220+
221+ return 1 ;
222+ }
223+ throw e ;
224+ }
197225
198226 this . description . options . push (
199227 ...( await parseJsonSchemaToOptions (
@@ -210,6 +238,38 @@ export abstract class ArchitectCommand<
210238 }
211239 }
212240
241+ private async warnOnMissingNodeModules ( basePath : string ) : Promise < void > {
242+ // Check for a `node_modules` directory (npm, yarn non-PnP, etc.)
243+ if ( existsSync ( path . resolve ( basePath , 'node_modules' ) ) ) {
244+ return ;
245+ }
246+
247+ // Check for yarn PnP files
248+ if (
249+ existsSync ( path . resolve ( basePath , '.pnp.js' ) ) ||
250+ existsSync ( path . resolve ( basePath , '.pnp.cjs' ) ) ||
251+ existsSync ( path . resolve ( basePath , '.pnp.mjs' ) )
252+ ) {
253+ return ;
254+ }
255+
256+ const packageManager = await getPackageManager ( basePath ) ;
257+ let installSuggestion = 'Try installing with ' ;
258+ switch ( packageManager ) {
259+ case 'npm' :
260+ installSuggestion += `'npm install'` ;
261+ break ;
262+ case 'yarn' :
263+ installSuggestion += `'yarn'` ;
264+ break ;
265+ default :
266+ installSuggestion += `the project's package manager` ;
267+ break ;
268+ }
269+
270+ this . logger . warn ( `Node packages may not be installed. ${ installSuggestion } .` ) ;
271+ }
272+
213273 async run ( options : ArchitectCommandOptions & Arguments ) {
214274 return await this . runArchitectTarget ( options ) ;
215275 }
@@ -219,7 +279,19 @@ export abstract class ArchitectCommand<
219279 // overrides separately (getting the configuration builds the whole project, including
220280 // overrides).
221281 const builderConf = await this . _architectHost . getBuilderNameForTarget ( target ) ;
222- const builderDesc = await this . _architectHost . resolveBuilder ( builderConf ) ;
282+ let builderDesc ;
283+ try {
284+ builderDesc = await this . _architectHost . resolveBuilder ( builderConf ) ;
285+ } catch ( e ) {
286+ if ( e . code === 'MODULE_NOT_FOUND' ) {
287+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
288+ await this . warnOnMissingNodeModules ( this . workspace ! . basePath ) ;
289+ this . logger . fatal ( `Could not find the '${ builderConf } ' builder's node package.` ) ;
290+
291+ return 1 ;
292+ }
293+ throw e ;
294+ }
223295 const targetOptionArray = await parseJsonSchemaToOptions (
224296 this . _registry ,
225297 builderDesc . optionSchema as json . JsonObject ,
0 commit comments