Skip to content

Commit 64b4814

Browse files
authored
fix(core): projectName should not be interpolated as undefined (#23145)
<!-- Please make sure you have read the submission guidelines before posting an PR --> <!-- https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr --> <!-- Please make sure that your commit message follows our format --> <!-- Example: `fix(nx): must begin with lowercase` --> ## Current Behavior Tokens are interpolated into arguments while merging things into the root map. Unfortunately, this means `{projectName}` is sometimes interpolated as `undefined` because the project's name isn't known yet. ## Expected Behavior Tokens are interpolated after the root map has been constructed, but still during `createProjectConfigurations` s.t. we can know that we have final project configurations afterwards. ## Related Issue(s) <!-- Please link the issue being fixed so it gets closed when this is merged. --> Fixes #
1 parent 11f30f6 commit 64b4814

File tree

3 files changed

+281
-49
lines changed

3 files changed

+281
-49
lines changed

packages/nx/src/project-graph/error-types.ts

Lines changed: 60 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export class ProjectGraphError extends Error {
1212
| MergeNodesError
1313
| CreateMetadataError
1414
| ProjectsWithNoNameError
15-
| ProjectsWithConflictingNamesError
15+
| MultipleProjectsWithSameNameError
1616
| ProcessDependenciesError
1717
| ProcessProjectGraphError
1818
| WorkspaceValidityError
@@ -25,7 +25,7 @@ export class ProjectGraphError extends Error {
2525
| CreateNodesError
2626
| MergeNodesError
2727
| ProjectsWithNoNameError
28-
| ProjectsWithConflictingNamesError
28+
| MultipleProjectsWithSameNameError
2929
| ProcessDependenciesError
3030
| ProcessProjectGraphError
3131
| CreateMetadataError
@@ -73,9 +73,9 @@ export class ProjectGraphError extends Error {
7373
}
7474
}
7575

76-
export class ProjectsWithConflictingNamesError extends Error {
76+
export class MultipleProjectsWithSameNameError extends Error {
7777
constructor(
78-
conflicts: Map<string, string[]>,
78+
public conflicts: Map<string, string[]>,
7979
public projects: Record<string, ProjectConfiguration>
8080
) {
8181
super(
@@ -92,20 +92,38 @@ export class ProjectsWithConflictingNamesError extends Error {
9292
}
9393
}
9494

95-
export function isProjectsWithConflictingNamesError(
95+
export class ProjectWithExistingNameError extends Error {
96+
constructor(public projectName: string, public projectRoot: string) {
97+
super(`The project "${projectName}" is defined in multiple locations.`);
98+
this.name = this.constructor.name;
99+
}
100+
}
101+
102+
export function isProjectWithExistingNameError(
96103
e: unknown
97-
): e is ProjectsWithConflictingNamesError {
104+
): e is ProjectWithExistingNameError {
98105
return (
99-
e instanceof ProjectsWithConflictingNamesError ||
106+
e instanceof ProjectWithExistingNameError ||
100107
(typeof e === 'object' &&
101108
'name' in e &&
102-
e?.name === ProjectsWithConflictingNamesError.prototype.name)
109+
e?.name === ProjectWithExistingNameError.name)
110+
);
111+
}
112+
113+
export function isMultipleProjectsWithSameNameError(
114+
e: unknown
115+
): e is MultipleProjectsWithSameNameError {
116+
return (
117+
e instanceof MultipleProjectsWithSameNameError ||
118+
(typeof e === 'object' &&
119+
'name' in e &&
120+
e?.name === MultipleProjectsWithSameNameError.name)
103121
);
104122
}
105123

106124
export class ProjectsWithNoNameError extends Error {
107125
constructor(
108-
projectRoots: string[],
126+
public projectRoots: string[],
109127
public projects: Record<string, ProjectConfiguration>
110128
) {
111129
super(
@@ -124,7 +142,25 @@ export function isProjectsWithNoNameError(
124142
e instanceof ProjectsWithNoNameError ||
125143
(typeof e === 'object' &&
126144
'name' in e &&
127-
e?.name === ProjectsWithNoNameError.prototype.name)
145+
e?.name === ProjectsWithNoNameError.name)
146+
);
147+
}
148+
149+
export class ProjectWithNoNameError extends Error {
150+
constructor(public projectRoot: string) {
151+
super(`The project in ${projectRoot} has no name provided.`);
152+
this.name = this.constructor.name;
153+
}
154+
}
155+
156+
export function isProjectWithNoNameError(
157+
e: unknown
158+
): e is ProjectWithNoNameError {
159+
return (
160+
e instanceof ProjectWithNoNameError ||
161+
(typeof e === 'object' &&
162+
'name' in e &&
163+
e?.name === ProjectWithNoNameError.name)
128164
);
129165
}
130166

@@ -134,7 +170,7 @@ export class ProjectConfigurationsError extends Error {
134170
| MergeNodesError
135171
| CreateNodesError
136172
| ProjectsWithNoNameError
137-
| ProjectsWithConflictingNamesError
173+
| MultipleProjectsWithSameNameError
138174
>,
139175
public readonly partialProjectConfigurationsResult: ConfigurationResult
140176
) {
@@ -143,6 +179,17 @@ export class ProjectConfigurationsError extends Error {
143179
}
144180
}
145181

182+
export function isProjectConfigurationsError(
183+
e: unknown
184+
): e is ProjectConfigurationsError {
185+
return (
186+
e instanceof ProjectConfigurationsError ||
187+
(typeof e === 'object' &&
188+
'name' in e &&
189+
e?.name === ProjectConfigurationsError.name)
190+
);
191+
}
192+
146193
export class CreateNodesError extends Error {
147194
file: string;
148195
pluginName: string;
@@ -275,7 +322,7 @@ export function isAggregateProjectGraphError(
275322
e instanceof AggregateProjectGraphError ||
276323
(typeof e === 'object' &&
277324
'name' in e &&
278-
e?.name === AggregateProjectGraphError.prototype.name)
325+
e?.name === AggregateProjectGraphError.name)
279326
);
280327
}
281328

@@ -284,7 +331,7 @@ export function isCreateMetadataError(e: unknown): e is CreateMetadataError {
284331
e instanceof CreateMetadataError ||
285332
(typeof e === 'object' &&
286333
'name' in e &&
287-
e?.name === CreateMetadataError.prototype.name)
334+
e?.name === CreateMetadataError.name)
288335
);
289336
}
290337

packages/nx/src/project-graph/utils/project-configuration-utils.spec.ts

Lines changed: 112 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import {
1515
} from './project-configuration-utils';
1616
import { NxPluginV2 } from '../plugins';
1717
import { LoadedNxPlugin } from '../plugins/internal-api';
18+
import { dirname } from 'path';
19+
import { isProjectConfigurationsError } from '../error-types';
1820

1921
describe('project-configuration-utils', () => {
2022
describe('target merging', () => {
@@ -573,7 +575,6 @@ describe('project-configuration-utils', () => {
573575
"root": "libs/lib-a",
574576
"targets": {
575577
"build": {
576-
"configurations": {},
577578
"executor": "nx:run-commands",
578579
"options": {
579580
"command": "tsc",
@@ -727,9 +728,8 @@ describe('project-configuration-utils', () => {
727728
expect(targets.echo).toMatchInlineSnapshot(`
728729
{
729730
"command": "echo lib-a",
730-
"configurations": {},
731731
"options": {
732-
"cwd": "libs/lib-a",
732+
"cwd": "{projectRoot}",
733733
},
734734
}
735735
`);
@@ -1594,6 +1594,49 @@ describe('project-configuration-utils', () => {
15941594
],
15951595
};
15961596

1597+
const fakeTargetsPlugin: NxPluginV2 = {
1598+
name: 'fake-targets-plugin',
1599+
createNodes: [
1600+
'libs/*/project.json',
1601+
(projectJsonPath) => {
1602+
const root = dirname(projectJsonPath);
1603+
return {
1604+
projects: {
1605+
[root]: {
1606+
root,
1607+
targets: {
1608+
build: {
1609+
executor: 'nx:run-commands',
1610+
options: {
1611+
command: 'echo {projectName} @ {projectRoot}',
1612+
},
1613+
},
1614+
},
1615+
},
1616+
},
1617+
};
1618+
},
1619+
],
1620+
};
1621+
1622+
const sameNamePlugin: NxPluginV2 = {
1623+
name: 'same-name-plugin',
1624+
createNodes: [
1625+
'libs/*/project.json',
1626+
(projectJsonPath) => {
1627+
const root = dirname(projectJsonPath);
1628+
return {
1629+
projects: {
1630+
[root]: {
1631+
root,
1632+
name: 'same-name',
1633+
},
1634+
},
1635+
};
1636+
},
1637+
],
1638+
};
1639+
15971640
it('should create nodes for files matching included patterns only', async () => {
15981641
const projectConfigurations = await createProjectConfigurations(
15991642
undefined,
@@ -1663,6 +1706,72 @@ describe('project-configuration-utils', () => {
16631706
},
16641707
});
16651708
});
1709+
1710+
it('should normalize targets', async () => {
1711+
const { projects } = await createProjectConfigurations(
1712+
undefined,
1713+
{},
1714+
['libs/a/project.json'],
1715+
[
1716+
new LoadedNxPlugin(fakeTargetsPlugin, 'fake-targets-plugin'),
1717+
new LoadedNxPlugin(fakeTagPlugin, 'fake-tag-plugin'),
1718+
]
1719+
);
1720+
expect(projects['libs/a'].targets.build).toMatchInlineSnapshot(`
1721+
{
1722+
"configurations": {},
1723+
"executor": "nx:run-commands",
1724+
"options": {
1725+
"command": "echo a @ libs/a",
1726+
},
1727+
}
1728+
`);
1729+
});
1730+
1731+
it('should validate that project names are unique', async () => {
1732+
const error = await createProjectConfigurations(
1733+
undefined,
1734+
{},
1735+
['libs/a/project.json', 'libs/b/project.json', 'libs/c/project.json'],
1736+
[new LoadedNxPlugin(sameNamePlugin, 'same-name-plugin')]
1737+
).catch((e) => e);
1738+
const isErrorType = isProjectConfigurationsError(error);
1739+
expect(isErrorType).toBe(true);
1740+
if (isErrorType) {
1741+
expect(error.errors).toMatchInlineSnapshot(`
1742+
[
1743+
[MultipleProjectsWithSameNameError: The following projects are defined in multiple locations:
1744+
- same-name:
1745+
- libs/a
1746+
- libs/b
1747+
- libs/c
1748+
1749+
To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.],
1750+
]
1751+
`);
1752+
}
1753+
});
1754+
1755+
it('should validate that projects have a name', async () => {
1756+
const error = await createProjectConfigurations(
1757+
undefined,
1758+
{},
1759+
['libs/a/project.json', 'libs/b/project.json', 'libs/c/project.json'],
1760+
[new LoadedNxPlugin(fakeTargetsPlugin, 'fake-targets-plugin')]
1761+
).catch((e) => e);
1762+
const isErrorType = isProjectConfigurationsError(error);
1763+
expect(isErrorType).toBe(true);
1764+
if (isErrorType) {
1765+
expect(error.errors).toMatchInlineSnapshot(`
1766+
[
1767+
[ProjectsWithNoNameError: The projects in the following directories have no name provided:
1768+
- libs/a
1769+
- libs/b
1770+
- libs/c],
1771+
]
1772+
`);
1773+
}
1774+
});
16661775
});
16671776
});
16681777

0 commit comments

Comments
 (0)