Skip to content

Commit 69b5bb0

Browse files
committed
feat(@angular/cli): provide more detailed error for not found builder
When a builder-based command is executed (build, serve, test, etc.) and the builder's node package cannot be found a more user-friendly error message is now displayed. In addition, when the builder's node package cannot be found, a check is performed to determine if the node packages for the workspace may have not been installed. Previously, a potentially long stacktrace was shown which did not provide much information regarding how to correct the issue. Closes: #10536
1 parent 1e81b8d commit 69b5bb0

2 files changed

Lines changed: 103 additions & 3 deletions

File tree

packages/angular/cli/models/architect-command.ts

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@
99
import { Architect, Target } from '@angular-devkit/architect';
1010
import { WorkspaceNodeModulesArchitectHost } from '@angular-devkit/architect/node';
1111
import { 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';
1215
import { parseJsonSchemaToOptions } from '../utilities/json-schema';
16+
import { getPackageManager } from '../utilities/package-manager';
1317
import { isPackageNameSafeForAnalytics } from './analytics';
1418
import { BaseCommandOptions, Command } from './command';
1519
import { 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,
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { rimraf } from '../../utils/fs';
2+
import { uninstallPackage } from '../../utils/packages';
3+
import { execAndWaitForOutputToMatch, ng } from '../../utils/process';
4+
import { expectToFail } from '../../utils/utils';
5+
6+
export default async function () {
7+
await uninstallPackage('@angular-devkit/build-angular');
8+
9+
await expectToFail(() => ng('build'));
10+
await execAndWaitForOutputToMatch(
11+
'ng',
12+
['build'],
13+
/Could not find the '@angular-devkit\/build-angular:browser' builder's node package\./,
14+
);
15+
expectToFail(() =>
16+
execAndWaitForOutputToMatch('ng', ['build'], /Node packages may not be installed\./),
17+
);
18+
19+
await rimraf('node_modules');
20+
21+
await expectToFail(() => ng('build'));
22+
await execAndWaitForOutputToMatch('ng', ['build'], /Node packages may not be installed\./);
23+
await execAndWaitForOutputToMatch(
24+
'ng',
25+
['build'],
26+
/Could not find the '@angular-devkit\/build-angular:browser' builder's node package\./,
27+
);
28+
}

0 commit comments

Comments
 (0)