99import { Architect , Target } from '@angular-devkit/architect' ;
1010import { WorkspaceNodeModulesArchitectHost } from '@angular-devkit/architect/node' ;
1111import { json , schema , tags } from '@angular-devkit/core' ;
12+ import { existsSync } from 'fs' ;
13+ import * as path from 'path' ;
1214import { parseJsonSchemaToOptions } from '../utilities/json-schema' ;
15+ import { getPackageManager } from '../utilities/package-manager' ;
1316import { isPackageNameSafeForAnalytics } from './analytics' ;
1417import { BaseCommandOptions , Command } from './command' ;
1518import { Arguments , Option } from './interface' ;
@@ -115,7 +118,19 @@ export abstract class ArchitectCommand<
115118 builderNames . add ( builderName ) ;
116119 }
117120
118- const builderDesc = await this . _architectHost . resolveBuilder ( builderName ) ;
121+ let builderDesc ;
122+ try {
123+ builderDesc = await this . _architectHost . resolveBuilder ( builderName ) ;
124+ } catch ( e ) {
125+ if ( e . code === 'MODULE_NOT_FOUND' ) {
126+ await this . warnOnMissingNodeModules ( this . workspace . basePath ) ;
127+ this . logger . fatal ( `Could not find the '${ builderName } ' builder's node package.` ) ;
128+
129+ return 1 ;
130+ }
131+ throw e ;
132+ }
133+
119134 const optionDefs = await parseJsonSchemaToOptions (
120135 this . _registry ,
121136 builderDesc . optionSchema as json . JsonObject ,
@@ -193,7 +208,19 @@ export abstract class ArchitectCommand<
193208 project : projectName || ( targetProjectNames . length > 0 ? targetProjectNames [ 0 ] : '' ) ,
194209 target : this . target ,
195210 } ) ;
196- const builderDesc = await this . _architectHost . resolveBuilder ( builderConf ) ;
211+
212+ let builderDesc ;
213+ try {
214+ builderDesc = await this . _architectHost . resolveBuilder ( builderConf ) ;
215+ } catch ( e ) {
216+ if ( e . code === 'MODULE_NOT_FOUND' ) {
217+ await this . warnOnMissingNodeModules ( this . workspace . basePath ) ;
218+ this . logger . fatal ( `Could not find the '${ builderConf } ' builder's node package.` ) ;
219+
220+ return 1 ;
221+ }
222+ throw e ;
223+ }
197224
198225 this . description . options . push (
199226 ...( await parseJsonSchemaToOptions (
@@ -210,6 +237,38 @@ export abstract class ArchitectCommand<
210237 }
211238 }
212239
240+ private async warnOnMissingNodeModules ( basePath : string ) : Promise < void > {
241+ // Check for a `node_modules` directory (npm, yarn non-PnP, etc.)
242+ if ( existsSync ( path . resolve ( basePath , 'node_modules' ) ) ) {
243+ return ;
244+ }
245+
246+ // Check for yarn PnP files
247+ if (
248+ existsSync ( path . resolve ( basePath , '.pnp.js' ) ) ||
249+ existsSync ( path . resolve ( basePath , '.pnp.cjs' ) ) ||
250+ existsSync ( path . resolve ( basePath , '.pnp.mjs' ) )
251+ ) {
252+ return ;
253+ }
254+
255+ const packageManager = await getPackageManager ( basePath ) ;
256+ let installSuggestion = 'Try installing with ' ;
257+ switch ( packageManager ) {
258+ case 'npm' :
259+ installSuggestion += `'npm install'` ;
260+ break ;
261+ case 'yarn' :
262+ installSuggestion += `'yarn'` ;
263+ break ;
264+ default :
265+ installSuggestion += `the project's package manager` ;
266+ break ;
267+ }
268+
269+ this . logger . warn ( `Node packages may not be installed. ${ installSuggestion } .` ) ;
270+ }
271+
213272 async run ( options : ArchitectCommandOptions & Arguments ) {
214273 return await this . runArchitectTarget ( options ) ;
215274 }
@@ -219,7 +278,19 @@ export abstract class ArchitectCommand<
219278 // overrides separately (getting the configuration builds the whole project, including
220279 // overrides).
221280 const builderConf = await this . _architectHost . getBuilderNameForTarget ( target ) ;
222- const builderDesc = await this . _architectHost . resolveBuilder ( builderConf ) ;
281+ let builderDesc ;
282+ try {
283+ builderDesc = await this . _architectHost . resolveBuilder ( builderConf ) ;
284+ } catch ( e ) {
285+ if ( e . code === 'MODULE_NOT_FOUND' ) {
286+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
287+ await this . warnOnMissingNodeModules ( this . workspace ! . basePath ) ;
288+ this . logger . fatal ( `Could not find the '${ builderConf } ' builder's node package.` ) ;
289+
290+ return 1 ;
291+ }
292+ throw e ;
293+ }
223294 const targetOptionArray = await parseJsonSchemaToOptions (
224295 this . _registry ,
225296 builderDesc . optionSchema as json . JsonObject ,
0 commit comments