Skip to content

Commit 384a1f7

Browse files
committed
feat: [OSM-1024] pnpm dep graph builder
1 parent 6af3938 commit 384a1f7

File tree

202 files changed

+342292
-124
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

202 files changed

+342292
-124
lines changed

.circleci/config.yml

+72-56
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ orbs:
44
win: circleci/[email protected]
55
prodsec: snyk/[email protected]
66

7+
filters_branches_ignore_master: &filters_branches_ignore_master
8+
filters:
9+
branches:
10+
ignore:
11+
- master
12+
713
defaults: &defaults
814
parameters:
915
node_version:
@@ -17,6 +23,9 @@ windows_defaults: &windows_defaults
1723
executor:
1824
name: win/default
1925

26+
test_matrix: &test_matrix
27+
node_version: ['12.22.12', '14.17.6', '16.13.2']
28+
2029
commands:
2130
install_deps:
2231
description: Install dependencies
@@ -66,7 +75,7 @@ jobs:
6675
name: Run lint
6776
command: npm run lint
6877

69-
test-windows:
78+
test-windows-jest:
7079
<<: *defaults
7180
<<: *windows_defaults
7281
steps:
@@ -80,9 +89,39 @@ jobs:
8089
- show_node_version
8190
- run:
8291
name: Run tests
83-
command: npm test
92+
command: npm run test:jest
8493

85-
test-unix:
94+
test-windows-tap:
95+
<<: *defaults
96+
<<: *windows_defaults
97+
steps:
98+
- run: git config --global core.autocrlf false
99+
- install_node_npm:
100+
node_version: << parameters.node_version >>
101+
- checkout
102+
- attach_workspace:
103+
at: ~/nodejs-lockfile-parser
104+
- install_deps
105+
- show_node_version
106+
- run:
107+
name: Run tests
108+
command: npm run unit-test
109+
110+
test-unix-jest:
111+
<<: *defaults
112+
docker:
113+
- image: cimg/node:<< parameters.node_version >>
114+
steps:
115+
- checkout
116+
- attach_workspace:
117+
at: ~/nodejs-lockfile-parser
118+
- install_deps
119+
- show_node_version
120+
- run:
121+
name: Run tests
122+
command: npm run test:jest
123+
124+
test-unix-tap:
86125
<<: *defaults
87126
docker:
88127
- image: cimg/node:<< parameters.node_version >>
@@ -94,7 +133,7 @@ jobs:
94133
- show_node_version
95134
- run:
96135
name: Run tests
97-
command: npm test
136+
command: npm run unit-test
98137

99138
release:
100139
<<: *defaults
@@ -121,70 +160,47 @@ workflows:
121160
name: Lint
122161
context: nodejs-install
123162
node_version: "16.13.2"
124-
filters:
125-
branches:
126-
ignore:
127-
- master
128-
- test-windows:
129-
name: Windows Tests for Node v16 support
130-
context: nodejs-install
131-
node_version: "16.13.2"
132-
requires:
133-
- Lint
134-
filters:
135-
branches:
136-
ignore:
137-
- master
138-
- test-windows:
139-
name: Windows Tests for Node v14 support
140-
context: nodejs-install
141-
node_version: "14.17.6"
142-
requires:
143-
- Lint
144-
filters:
145-
branches:
146-
ignore:
147-
- master
148-
- test-unix:
149-
name: Unix Tests for Node v16 support
163+
<<: *filters_branches_ignore_master
164+
- test-windows-jest:
165+
matrix:
166+
alias: test-windows-jest
167+
parameters:
168+
<<: *test_matrix
169+
name: Windows Tests (Jest) for Node=<< matrix.node_version >> support
150170
context: nodejs-install
151-
node_version: "16.13.2"
152171
requires:
153172
- Lint
154-
filters:
155-
branches:
156-
ignore:
157-
- master
158-
- test-unix:
159-
name: Unix Tests for Node v14 support
173+
<<: *filters_branches_ignore_master
174+
- test-windows-tap:
175+
matrix:
176+
alias: test-windows-tap
177+
parameters:
178+
<<: *test_matrix
179+
name: Windows Tests (Tap) for Node=<< matrix.node_version >> support
160180
context: nodejs-install
161-
node_version: "14.17.6"
162181
requires:
163182
- Lint
164-
filters:
165-
branches:
166-
ignore:
167-
- master
168-
- test-windows:
169-
name: Windows Tests for Node v12 support
183+
<<: *filters_branches_ignore_master
184+
- test-unix-jest:
185+
matrix:
186+
alias: test-unix-jest
187+
parameters:
188+
<<: *test_matrix
189+
name: Unix Tests (Jest) for Node=<< matrix.node_version >> support
170190
context: nodejs-install
171-
node_version: "12.22.12"
172191
requires:
173192
- Lint
174-
filters:
175-
branches:
176-
ignore:
177-
- master
178-
- test-unix:
179-
name: Unix Tests for Node v12 support
193+
<<: *filters_branches_ignore_master
194+
- test-unix-tap:
195+
matrix:
196+
alias: test-unix-tap
197+
parameters:
198+
<<: *test_matrix
199+
name: Unix Tests (Tap) for Node=<< matrix.node_version >> support
180200
context: nodejs-install
181-
node_version: "12.22.12"
182201
requires:
183202
- Lint
184-
filters:
185-
branches:
186-
ignore:
187-
- master
203+
<<: *filters_branches_ignore_master
188204
- release:
189205
name: Release
190206
context: nodejs-app-release

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ dist
99
.idea
1010
.nyc_output/
1111
.dccache
12+
coverage/

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Dep graph generation supported for:
1414

1515
- `package-lock.json` (at Versions 2 and 3)
1616
- `yarn.lock`
17+
- `pnpm-lock.yaml` (lockfileVersion 5.x or 6.x)
1718

1819
Legacy dep tree supported for:
1920

lib/dep-graph-builders/index.ts

+4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import {
1313
extractPkgsFromYarnLockV2,
1414
} from './yarn-lock-v2';
1515
import { parseNpmLockV2Project } from './npm-lock-v2';
16+
import { parsePnpmProject } from './pnpm';
17+
import { parsePkgJson } from './util';
1618

1719
export {
1820
parseNpmLockV2Project,
@@ -26,4 +28,6 @@ export {
2628
buildDepGraphYarnLockV2Simple,
2729
parseYarnLockV2Project,
2830
extractPkgsFromYarnLockV2,
31+
parsePnpmProject,
32+
parsePkgJson,
2933
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import { DepGraphBuilder } from '@snyk/dep-graph';
2+
import { getTopLevelDeps } from '../util';
3+
import type {
4+
Overrides,
5+
PnpmProjectParseOptions,
6+
PnpmWorkspaceArgs,
7+
} from '../types';
8+
import type { PackageJsonBase } from '../types';
9+
import { getPnpmChildNode } from './utils';
10+
import { eventLoopSpinner } from 'event-loop-spinner';
11+
import { PnpmLockfileParser } from './lockfile-parser/lockfile-parser';
12+
import { NormalisedPnpmPkgs, PnpmNode } from './types';
13+
14+
export const buildDepGraphPnpm = async (
15+
lockFileParser: PnpmLockfileParser,
16+
pkgJson: PackageJsonBase,
17+
options: PnpmProjectParseOptions,
18+
workspaceArgs?: PnpmWorkspaceArgs,
19+
) => {
20+
const { strictOutOfSync, includeOptionalDeps, pruneWithinTopLevelDeps } =
21+
options;
22+
23+
const depGraphBuilder = new DepGraphBuilder(
24+
{ name: 'pnpm' },
25+
{ name: pkgJson.name, version: pkgJson.version },
26+
);
27+
28+
const extractedPnpmPkgs: NormalisedPnpmPkgs =
29+
lockFileParser.extractedPackages;
30+
31+
const topLevelDeps = getTopLevelDeps(pkgJson, options);
32+
33+
const extractedTopLevelDeps =
34+
lockFileParser.extractTopLevelDependencies(options) || {};
35+
36+
for (const name of Object.keys(topLevelDeps)) {
37+
topLevelDeps[name].version = extractedTopLevelDeps[name].version;
38+
}
39+
40+
const rootNode: PnpmNode = {
41+
id: 'root-node',
42+
name: pkgJson.name,
43+
version: pkgJson.version,
44+
dependencies: topLevelDeps,
45+
isDev: false,
46+
};
47+
48+
await dfsVisit(
49+
depGraphBuilder,
50+
rootNode,
51+
extractedPnpmPkgs,
52+
strictOutOfSync,
53+
includeOptionalDeps,
54+
// we have rootWorkspaceOverrides if this is workspace pkg with overrides
55+
// at root - therefore it should take precedent
56+
// TODO: inspect if this is needed at all, seems like pnpm resolves everything in lockfile
57+
workspaceArgs?.rootOverrides || pkgJson.pnpm?.overrides || {},
58+
pruneWithinTopLevelDeps,
59+
lockFileParser,
60+
);
61+
62+
return depGraphBuilder.build();
63+
};
64+
65+
/**
66+
* Use DFS to add all nodes and edges to the depGraphBuilder and prune cyclic nodes.
67+
* The visitedMap keep track of which nodes have already been discovered during traversal.
68+
* - If a node doesn't exist in the map, it means it hasn't been visited.
69+
* - If a node is already visited, simply connect the new node with this node.
70+
*/
71+
const dfsVisit = async (
72+
depGraphBuilder: DepGraphBuilder,
73+
node: PnpmNode,
74+
extractedPnpmPkgs: NormalisedPnpmPkgs,
75+
strictOutOfSync: boolean,
76+
includeOptionalDeps: boolean,
77+
overrides: Overrides,
78+
pruneWithinTopLevel: boolean,
79+
lockFileParser: PnpmLockfileParser,
80+
visited?: Set<string>,
81+
): Promise<void> => {
82+
for (const [name, depInfo] of Object.entries(node.dependencies || {})) {
83+
if (eventLoopSpinner.isStarving()) {
84+
await eventLoopSpinner.spin();
85+
}
86+
87+
const localVisited = visited || new Set<string>();
88+
89+
const childNode: PnpmNode = getPnpmChildNode(
90+
name,
91+
depInfo,
92+
extractedPnpmPkgs,
93+
strictOutOfSync,
94+
includeOptionalDeps,
95+
lockFileParser,
96+
);
97+
98+
if (localVisited.has(childNode.id)) {
99+
if (pruneWithinTopLevel) {
100+
const prunedId = `${childNode.id}:pruned`;
101+
depGraphBuilder.addPkgNode(
102+
{ name: childNode.name, version: childNode.version },
103+
prunedId,
104+
{
105+
labels: {
106+
scope: childNode.isDev ? 'dev' : 'prod',
107+
pruned: 'true',
108+
...(node.missingLockFileEntry && {
109+
missingLockFileEntry: 'true',
110+
}),
111+
},
112+
},
113+
);
114+
depGraphBuilder.connectDep(node.id, prunedId);
115+
} else {
116+
depGraphBuilder.connectDep(node.id, childNode.id);
117+
}
118+
continue;
119+
}
120+
121+
depGraphBuilder.addPkgNode(
122+
{ name: childNode.name, version: childNode.version },
123+
childNode.id,
124+
{
125+
labels: {
126+
scope: childNode.isDev ? 'dev' : 'prod',
127+
...(node.missingLockFileEntry && {
128+
missingLockFileEntry: 'true',
129+
}),
130+
},
131+
},
132+
);
133+
depGraphBuilder.connectDep(node.id, childNode.id);
134+
localVisited.add(childNode.id);
135+
await dfsVisit(
136+
depGraphBuilder,
137+
childNode,
138+
extractedPnpmPkgs,
139+
strictOutOfSync,
140+
includeOptionalDeps,
141+
overrides,
142+
pruneWithinTopLevel,
143+
lockFileParser,
144+
localVisited,
145+
);
146+
}
147+
};

0 commit comments

Comments
 (0)