Skip to content

Commit 7efeeb9

Browse files
fix(@angular/cli): explain how to pick a project when a schematic cannot determine one
when project can't be resolved from cwd, like at root of workspace with no root project, schematics fail with raw `Data path "" must have required property 'project'` from schema validation now that error is replaced with message that says to pass `--project` or cd into project and lists available projects like architect commands do, empty workspace gets hint to generate application or libary first Fixes #25402 Co-authored-by: Alan Agius <alanagius@google.com> (cherry picked from commit 540a1c4)
1 parent 6298fb8 commit 7efeeb9

2 files changed

Lines changed: 56 additions & 4 deletions

File tree

‎packages/angular/cli/src/command-builder/schematics-command-module.ts‎

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,7 @@ export abstract class SchematicsCommandModule
123123
optionTransforms: [
124124
// Add configuration file defaults
125125
async (schematic, current) => {
126-
const projectName =
127-
typeof current?.project === 'string' ? current.project : this.getProjectName();
126+
const projectName = typeof current?.project === 'string' ? current.project : undefined;
128127

129128
return {
130129
...(await getSchematicDefaults(schematic.collection.name, schematic.name, projectName)),
@@ -407,7 +406,24 @@ export abstract class SchematicsCommandModule
407406
return projectName;
408407
}
409408

410-
return undefined;
409+
if (workspace.projects.size === 0) {
410+
throw new Error(
411+
'Cannot determine project for command.\n' +
412+
'This workspace does not contain any projects. ' +
413+
'Run "ng generate application" or "ng generate library" to create one.',
414+
);
415+
}
416+
417+
throw new Error(
418+
'Cannot determine project for command.\n' +
419+
'This is a multi-project workspace and more than one project supports this command. ' +
420+
'Use the "--project" option to specify a project or change the current ' +
421+
'working directory to a project directory.\n\n' +
422+
`Available projects are:\n${[...workspace.projects.keys()]
423+
.sort()
424+
.map((p) => `- ${p}`)
425+
.join('\n')}`,
426+
);
411427
}
412428

413429
private getResolvePaths(collectionName: string): string[] {

‎tests/e2e/tests/commands/project-cannot-be-determined-by-cwd.ts‎

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import assert from 'node:assert/strict';
22
import { join } from 'node:path';
3-
import { execAndWaitForOutputToMatch, ng } from '../../utils/process';
3+
import { execAndCaptureError, execAndWaitForOutputToMatch, ng } from '../../utils/process';
44
import { updateJsonFile } from '../../utils/project';
55
import { expectToFail } from '../../utils/utils';
66

@@ -23,6 +23,29 @@ export default async function () {
2323
const { message } = await expectToFail(() => ng('build'));
2424
assert.match(message, new RegExp(errorMessage));
2525

26+
// Schematics that require a project should explain how to select one
27+
const { message: generateMessage } = await expectToFail(() =>
28+
ng('generate', 'component', 'foo'),
29+
);
30+
assert.match(generateMessage, new RegExp(errorMessage));
31+
assert.match(generateMessage, /Use the "--project" option to specify a project/);
32+
assert.match(generateMessage, /Available projects are:\n- second-app\n- third-app/);
33+
34+
// The project is checked before prompting for other options
35+
const { message: interactiveMessage } = await execAndCaptureError(
36+
'ng',
37+
['generate', 'component'],
38+
{
39+
...process.env,
40+
NG_FORCE_TTY: '1',
41+
NG_CLI_ANALYTICS: 'false',
42+
NG_FORCE_AUTOCOMPLETE: 'false',
43+
},
44+
'foo\n',
45+
);
46+
assert.match(interactiveMessage, /Use the "--project" option to specify a project/);
47+
assert.doesNotMatch(interactiveMessage, /What name would you like to use for the component/);
48+
2649
// Help should still work
2750
await execAndWaitForOutputToMatch('ng', ['build', '--help'], /--configuration/);
2851

@@ -31,8 +54,21 @@ export default async function () {
3154

3255
process.chdir(join(startCwd, 'projects/second-app'));
3356
await ng('build', '--configuration=development');
57+
await ng('generate', 'component', 'foo', '--dry-run');
3458
} finally {
3559
// Restore path
3660
process.chdir(startCwd);
3761
}
62+
63+
// A project from the workspace schematic options is used and other validation errors are kept
64+
await updateJsonFile('angular.json', (workspaceJson) => {
65+
workspaceJson.schematics ??= {};
66+
workspaceJson.schematics['@schematics/angular:component'] = { project: 'second-app' };
67+
});
68+
const { message: selectorMessage } = await expectToFail(() =>
69+
ng('generate', 'component', 'foo', '--selector=1foo'),
70+
);
71+
assert.match(selectorMessage, /must match format "html-selector"/);
72+
assert.doesNotMatch(selectorMessage, /Cannot determine project/);
73+
await ng('generate', 'component', 'foo', '--dry-run');
3874
}

0 commit comments

Comments
 (0)