Skip to content

Commit 62a4ad4

Browse files
committed
feat(core): update getTouchedProjectsFromLockFile to detect which projects were changed from pnpm lock file diff
Closes nrwl#29986
1 parent 3b3b320 commit 62a4ad4

File tree

2 files changed

+172
-14
lines changed

2 files changed

+172
-14
lines changed

packages/nx/src/plugins/js/project-graph/affected/lock-file-changes.spec.ts

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import { ProjectGraph } from '../../../../config/project-graph';
22
import { WholeFileChange } from '../../../../project-graph/file-utils';
3-
import { getTouchedProjectsFromLockFile } from './lock-file-changes';
3+
import {
4+
getTouchedProjectsFromLockFile,
5+
PNPM_LOCK_FILES,
6+
} from './lock-file-changes';
7+
import { TempFs } from '../../../../internal-testing-utils/temp-fs';
8+
import { JsonDiffType } from '../../../../utils/json-diff';
9+
const tempFs = new TempFs('explicit-package-json');
410

511
describe('getTouchedProjectsFromLockFile', () => {
612
let graph: ProjectGraph;
@@ -74,4 +80,95 @@ describe('getTouchedProjectsFromLockFile', () => {
7480
});
7581
});
7682
});
83+
84+
PNPM_LOCK_FILES.forEach((lockFile) => {
85+
describe(`"${lockFile} with projectsAffectedByDependencyUpdates set to auto"`, () => {
86+
beforeAll(async () => {
87+
await tempFs.createFiles({
88+
'./nx.json': JSON.stringify({
89+
pluginsConfig: {
90+
'@nx/js': {
91+
projectsAffectedByDependencyUpdates: 'auto',
92+
},
93+
},
94+
}),
95+
});
96+
});
97+
98+
afterAll(() => {
99+
tempFs.cleanup();
100+
});
101+
102+
it(`should not return changes when "${lockFile}" is not touched`, () => {
103+
const result = getTouchedProjectsFromLockFile(
104+
[
105+
{
106+
file: 'source.ts',
107+
hash: 'some-hash',
108+
getChanges: () => [new WholeFileChange()],
109+
},
110+
],
111+
graph.nodes
112+
);
113+
expect(result).toEqual([]);
114+
});
115+
116+
it(`should not return changes when whole lock file "${lockFile}" is changed`, () => {
117+
const result = getTouchedProjectsFromLockFile(
118+
[
119+
{
120+
file: lockFile,
121+
hash: 'some-hash',
122+
getChanges: () => [new WholeFileChange()],
123+
},
124+
],
125+
graph.nodes
126+
);
127+
expect(result).toEqual([]);
128+
});
129+
130+
it(`should return only changed projects when "${lockFile}" is touched`, () => {
131+
const result = getTouchedProjectsFromLockFile(
132+
[
133+
{
134+
file: lockFile,
135+
hash: 'some-hash',
136+
getChanges: () => [
137+
{
138+
type: JsonDiffType.Modified,
139+
path: [
140+
'importers',
141+
'libs/proj1',
142+
'dependencies',
143+
'some-external-package',
144+
'version',
145+
],
146+
value: {
147+
lhs: '0.0.1',
148+
rhs: '0.0.2',
149+
},
150+
},
151+
{
152+
type: JsonDiffType.Added,
153+
path: [
154+
'importers',
155+
'apps/app1',
156+
'devDependencies',
157+
'some-other-external-package',
158+
'version',
159+
],
160+
value: {
161+
lhs: undefined,
162+
rhs: '4.0.1',
163+
},
164+
},
165+
],
166+
},
167+
],
168+
graph.nodes
169+
);
170+
expect(result).toEqual(['proj1', 'app1']);
171+
});
172+
});
173+
});
77174
});
Lines changed: 74 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,97 @@
11
import { readNxJson } from '../../../../config/configuration';
22
import { TouchedProjectLocator } from '../../../../project-graph/affected/affected-project-graph-models';
3-
import { WholeFileChange } from '../../../../project-graph/file-utils';
4-
import { JsonChange } from '../../../../utils/json-diff';
3+
import {
4+
FileChange,
5+
WholeFileChange,
6+
} from '../../../../project-graph/file-utils';
7+
import { isJsonChange, JsonChange } from '../../../../utils/json-diff';
58
import { jsPluginConfig as readJsPluginConfig } from '../../utils/config';
69
import { findMatchingProjects } from '../../../../utils/find-matching-projects';
10+
import { ProjectGraphProjectNode } from '../../../../config/project-graph';
11+
12+
export const PNPM_LOCK_FILES = ['pnpm-lock.yaml', 'pnpm-lock.yml'];
13+
14+
const ALL_LOCK_FILES = [
15+
...PNPM_LOCK_FILES,
16+
'package-lock.json',
17+
'yarn.lock',
18+
'bun.lockb',
19+
'bun.lock',
20+
];
721

822
export const getTouchedProjectsFromLockFile: TouchedProjectLocator<
923
WholeFileChange | JsonChange
1024
> = (fileChanges, projectGraphNodes): string[] => {
1125
const nxJson = readNxJson();
1226
const { projectsAffectedByDependencyUpdates } = readJsPluginConfig(nxJson);
1327

28+
const changedLockFile = fileChanges.find((f) =>
29+
ALL_LOCK_FILES.includes(f.file)
30+
);
31+
1432
if (projectsAffectedByDependencyUpdates === 'auto') {
15-
return [];
33+
const changedProjectPaths =
34+
getProjectPathsAffectedByDependencyUpdates(changedLockFile);
35+
const changedProjectNames = getProjectsNamesFromPaths(
36+
projectGraphNodes,
37+
changedProjectPaths
38+
);
39+
return changedProjectNames;
1640
} else if (Array.isArray(projectsAffectedByDependencyUpdates)) {
1741
return findMatchingProjects(
1842
projectsAffectedByDependencyUpdates,
1943
projectGraphNodes
2044
);
2145
}
2246

23-
const lockFiles = [
24-
'package-lock.json',
25-
'yarn.lock',
26-
'pnpm-lock.yaml',
27-
'pnpm-lock.yml',
28-
'bun.lockb',
29-
'bun.lock',
30-
];
31-
32-
if (fileChanges.some((f) => lockFiles.includes(f.file))) {
47+
if (changedLockFile) {
3348
return Object.values(projectGraphNodes).map((p) => p.name);
3449
}
3550
return [];
3651
};
52+
53+
/**
54+
* For pnpm projects, check lock file for changes to importers and return the project paths that have changes.
55+
*/
56+
const getProjectPathsAffectedByDependencyUpdates = (
57+
changedLockFile?: FileChange<WholeFileChange | JsonChange>
58+
): string[] => {
59+
const changedProjectPaths = new Set<string>();
60+
if (PNPM_LOCK_FILES.includes(changedLockFile.file)) {
61+
for (const change of changedLockFile.getChanges()) {
62+
if (
63+
isJsonChange(change) &&
64+
change.path[0] === 'importers' &&
65+
change.path[1] !== undefined
66+
) {
67+
changedProjectPaths.add(change.path[1]);
68+
}
69+
}
70+
}
71+
return Array.from(changedProjectPaths);
72+
};
73+
74+
const getProjectsNamesFromPaths = (
75+
projectGraphNodes: Record<string, ProjectGraphProjectNode>,
76+
projectPaths: string[]
77+
): string[] => {
78+
const lookup = new RootPathLookup(projectGraphNodes);
79+
return projectPaths.map((path) => {
80+
return lookup.findNodeNameByRoot(path);
81+
});
82+
};
83+
84+
class RootPathLookup {
85+
private rootToNameMap: Map<string, string>;
86+
87+
constructor(nodes: Record<string, ProjectGraphProjectNode>) {
88+
this.rootToNameMap = new Map();
89+
Object.entries(nodes).forEach(([name, node]) => {
90+
this.rootToNameMap.set(node.data.root, name);
91+
});
92+
}
93+
94+
findNodeNameByRoot(root: string): string | undefined {
95+
return this.rootToNameMap.get(root);
96+
}
97+
}

0 commit comments

Comments
 (0)