9
9
import { Architect , Target } from '@angular-devkit/architect' ;
10
10
import { WorkspaceNodeModulesArchitectHost } from '@angular-devkit/architect/node' ;
11
11
import { json , schema , tags } from '@angular-devkit/core' ;
12
+ import { existsSync } from 'fs' ;
13
+ import * as path from 'path' ;
12
14
import { parseJsonSchemaToOptions } from '../utilities/json-schema' ;
15
+ import { getPackageManager } from '../utilities/package-manager' ;
13
16
import { isPackageNameSafeForAnalytics } from './analytics' ;
14
17
import { BaseCommandOptions , Command } from './command' ;
15
18
import { Arguments , Option } from './interface' ;
@@ -115,7 +118,19 @@ export abstract class ArchitectCommand<
115
118
builderNames . add ( builderName ) ;
116
119
}
117
120
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
+
119
134
const optionDefs = await parseJsonSchemaToOptions (
120
135
this . _registry ,
121
136
builderDesc . optionSchema as json . JsonObject ,
@@ -193,7 +208,19 @@ export abstract class ArchitectCommand<
193
208
project : projectName || ( targetProjectNames . length > 0 ? targetProjectNames [ 0 ] : '' ) ,
194
209
target : this . target ,
195
210
} ) ;
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
+ }
197
224
198
225
this . description . options . push (
199
226
...( await parseJsonSchemaToOptions (
@@ -210,6 +237,38 @@ export abstract class ArchitectCommand<
210
237
}
211
238
}
212
239
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
+
213
272
async run ( options : ArchitectCommandOptions & Arguments ) {
214
273
return await this . runArchitectTarget ( options ) ;
215
274
}
@@ -219,7 +278,19 @@ export abstract class ArchitectCommand<
219
278
// overrides separately (getting the configuration builds the whole project, including
220
279
// overrides).
221
280
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
+ }
223
294
const targetOptionArray = await parseJsonSchemaToOptions (
224
295
this . _registry ,
225
296
builderDesc . optionSchema as json . JsonObject ,
0 commit comments