1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | exports.graphToDepTree = exports.depTreeToGraph = void 0;
|
4 | const crypto = require("crypto");
|
5 | const event_loop_spinner_1 = require("event-loop-spinner");
|
6 | const builder_1 = require("../core/builder");
|
7 | const objectHash = require("object-hash");
|
8 | const cycles_1 = require("./cycles");
|
9 | const memiozation_1 = require("./memiozation");
|
10 | function addLabel(dep, key, value) {
|
11 | if (!dep.labels) {
|
12 | dep.labels = {};
|
13 | }
|
14 | dep.labels[key] = value;
|
15 | }
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 | async function depTreeToGraph(depTree, pkgManagerName) {
|
23 | const rootPkg = {
|
24 | name: depTree.name,
|
25 | version: depTree.version || undefined,
|
26 | };
|
27 | if (depTree.purl) {
|
28 | rootPkg.purl = depTree.purl;
|
29 | }
|
30 | const pkgManagerInfo = {
|
31 | name: pkgManagerName,
|
32 | };
|
33 | const targetOS = depTree.targetOS;
|
34 | if (targetOS) {
|
35 | pkgManagerInfo.repositories = [
|
36 | {
|
37 | alias: `${targetOS.name}:${targetOS.version}`,
|
38 | },
|
39 | ];
|
40 | }
|
41 | const builder = new builder_1.DepGraphBuilder(pkgManagerInfo, rootPkg);
|
42 | await buildGraph(builder, depTree, depTree.name, true);
|
43 | const depGraph = await builder.build();
|
44 | return shortenNodeIds(depGraph);
|
45 | }
|
46 | exports.depTreeToGraph = depTreeToGraph;
|
47 | async function buildGraph(builder, depTree, pkgName, isRoot = false, memoizationMap = new Map()) {
|
48 | if (memoizationMap.has(depTree)) {
|
49 | return memoizationMap.get(depTree);
|
50 | }
|
51 | const getNodeId = (name, version, hashId) => `${name}@${version || ''}|${hashId}`;
|
52 | const depNodesIds = [];
|
53 | const hash = crypto.createHash('sha1');
|
54 | if (depTree.versionProvenance) {
|
55 | hash.update(objectHash(depTree.versionProvenance));
|
56 | }
|
57 | if (depTree.labels) {
|
58 | hash.update(objectHash(depTree.labels));
|
59 | }
|
60 | const deps = depTree.dependencies || {};
|
61 |
|
62 | const depNames = Object.keys(deps).filter((d) => !!deps[d]);
|
63 | for (const depName of depNames.sort()) {
|
64 | const dep = deps[depName];
|
65 | const subtreeHash = await buildGraph(builder, dep, depName, false, memoizationMap);
|
66 | const depPkg = {
|
67 | name: depName,
|
68 | version: dep.version,
|
69 | };
|
70 | if (dep.purl) {
|
71 | depPkg.purl = dep.purl;
|
72 | }
|
73 | const depNodeId = getNodeId(depPkg.name, depPkg.version, subtreeHash);
|
74 | depNodesIds.push(depNodeId);
|
75 | const nodeInfo = {};
|
76 | if (dep.versionProvenance) {
|
77 | nodeInfo.versionProvenance = dep.versionProvenance;
|
78 | }
|
79 | if (dep.labels) {
|
80 | nodeInfo.labels = dep.labels;
|
81 | }
|
82 | builder.addPkgNode(depPkg, depNodeId, nodeInfo);
|
83 | hash.update(depNodeId);
|
84 | }
|
85 | const treeHash = hash.digest('hex');
|
86 | let pkgNodeId;
|
87 | if (isRoot) {
|
88 | pkgNodeId = builder.rootNodeId;
|
89 | }
|
90 | else {
|
91 |
|
92 | const pkg = {
|
93 | name: pkgName,
|
94 | version: depTree.version,
|
95 | };
|
96 | pkgNodeId = getNodeId(pkg.name, pkg.version, treeHash);
|
97 | const nodeInfo = {};
|
98 | if (depTree.versionProvenance) {
|
99 | nodeInfo.versionProvenance = depTree.versionProvenance;
|
100 | }
|
101 | if (depTree.labels) {
|
102 | nodeInfo.labels = depTree.labels;
|
103 | }
|
104 | builder.addPkgNode(pkg, pkgNodeId, nodeInfo);
|
105 | }
|
106 | for (const depNodeId of depNodesIds) {
|
107 | builder.connectDep(pkgNodeId, depNodeId);
|
108 | }
|
109 | if (depNodesIds.length > 0 && event_loop_spinner_1.eventLoopSpinner.isStarving()) {
|
110 | await event_loop_spinner_1.eventLoopSpinner.spin();
|
111 | }
|
112 | memoizationMap.set(depTree, treeHash);
|
113 | return treeHash;
|
114 | }
|
115 | async function shortenNodeIds(depGraph) {
|
116 | const builder = new builder_1.DepGraphBuilder(depGraph.pkgManager, depGraph.rootPkg);
|
117 | const nodesMap = {};
|
118 |
|
119 | for (const pkg of depGraph.getPkgs()) {
|
120 | const nodeIds = depGraph.getPkgNodeIds(pkg);
|
121 | for (let i = 0; i < nodeIds.length; i++) {
|
122 | const nodeId = nodeIds[i];
|
123 | if (nodeId === depGraph.rootNodeId) {
|
124 | continue;
|
125 | }
|
126 | const nodeInfo = depGraph.getNode(nodeId);
|
127 | let newNodeId;
|
128 | if (nodeIds.length === 1) {
|
129 | newNodeId = `${trimAfterLastSep(nodeId, '|')}`;
|
130 | }
|
131 | else {
|
132 | newNodeId = `${trimAfterLastSep(nodeId, '|')}|${i + 1}`;
|
133 | }
|
134 | nodesMap[nodeId] = newNodeId;
|
135 | builder.addPkgNode(pkg, newNodeId, nodeInfo);
|
136 | }
|
137 | if (event_loop_spinner_1.eventLoopSpinner.isStarving()) {
|
138 | await event_loop_spinner_1.eventLoopSpinner.spin();
|
139 | }
|
140 | }
|
141 |
|
142 | for (const pkg of depGraph.getPkgs()) {
|
143 | for (const nodeId of depGraph.getPkgNodeIds(pkg)) {
|
144 | for (const depNodeId of depGraph.getNodeDepsNodeIds(nodeId)) {
|
145 | const parentNode = nodesMap[nodeId] || nodeId;
|
146 | const childNode = nodesMap[depNodeId] || depNodeId;
|
147 | builder.connectDep(parentNode, childNode);
|
148 | }
|
149 | }
|
150 | if (event_loop_spinner_1.eventLoopSpinner.isStarving()) {
|
151 | await event_loop_spinner_1.eventLoopSpinner.spin();
|
152 | }
|
153 | }
|
154 | return builder.build();
|
155 | }
|
156 |
|
157 |
|
158 |
|
159 |
|
160 |
|
161 | async function graphToDepTree(depGraphInterface, pkgType, opts = { deduplicateWithinTopLevelDeps: false }) {
|
162 | const depGraph = depGraphInterface;
|
163 | const [depTree] = await buildSubtree(depGraph, depGraph.rootNodeId, opts.deduplicateWithinTopLevelDeps ? null : false);
|
164 | depTree.type = depGraph.pkgManager.name;
|
165 | depTree.packageFormatVersion = constructPackageFormatVersion(pkgType);
|
166 | const targetOS = constructTargetOS(depGraph);
|
167 | if (targetOS) {
|
168 | depTree.targetOS = targetOS;
|
169 | }
|
170 | return depTree;
|
171 | }
|
172 | exports.graphToDepTree = graphToDepTree;
|
173 | function constructPackageFormatVersion(pkgType) {
|
174 | if (pkgType === 'maven') {
|
175 | pkgType = 'mvn';
|
176 | }
|
177 | return `${pkgType}:0.0.1`;
|
178 | }
|
179 | function constructTargetOS(depGraph) {
|
180 | if (['apk', 'apt', 'deb', 'rpm', 'linux'].indexOf(depGraph.pkgManager.name) ===
|
181 | -1) {
|
182 |
|
183 | return;
|
184 | }
|
185 | if (!depGraph.pkgManager.repositories ||
|
186 | !depGraph.pkgManager.repositories.length ||
|
187 | !depGraph.pkgManager.repositories[0].alias) {
|
188 | throw new Error('Incomplete .pkgManager, could not create .targetOS');
|
189 | }
|
190 | const [name, version] = depGraph.pkgManager.repositories[0].alias.split(':');
|
191 | return { name, version };
|
192 | }
|
193 | async function buildSubtree(depGraph, nodeId, maybeDeduplicationSet = false, // false = disabled; null = not in deduplication scope yet
|
194 | ancestors = [], memoizationMap = new Map()) {
|
195 | if (!maybeDeduplicationSet) {
|
196 | const memoizedDepTree = (0, memiozation_1.getMemoizedDepTree)(nodeId, ancestors, memoizationMap);
|
197 | if (memoizedDepTree) {
|
198 | return [memoizedDepTree, undefined];
|
199 | }
|
200 | }
|
201 | const isRoot = nodeId === depGraph.rootNodeId;
|
202 | const nodePkg = depGraph.getNodePkg(nodeId);
|
203 | const nodeInfo = depGraph.getNode(nodeId);
|
204 | const depTree = {};
|
205 | depTree.name = nodePkg.name;
|
206 | depTree.version = nodePkg.version;
|
207 | if (nodeInfo.versionProvenance) {
|
208 | depTree.versionProvenance = nodeInfo.versionProvenance;
|
209 | }
|
210 | if (nodeInfo.labels) {
|
211 | depTree.labels = { ...nodeInfo.labels };
|
212 | }
|
213 | const depInstanceIds = depGraph.getNodeDepsNodeIds(nodeId);
|
214 | if (!depInstanceIds || depInstanceIds.length === 0) {
|
215 | memoizationMap.set(nodeId, { depTree });
|
216 | return [depTree, undefined];
|
217 | }
|
218 | const cycle = (0, cycles_1.getCycle)(ancestors, nodeId);
|
219 | if (cycle) {
|
220 |
|
221 | addLabel(depTree, 'pruned', 'cyclic');
|
222 | return [depTree, [cycle]];
|
223 | }
|
224 | if (maybeDeduplicationSet) {
|
225 | if (maybeDeduplicationSet.has(nodeId)) {
|
226 | if (depInstanceIds.length > 0) {
|
227 | addLabel(depTree, 'pruned', 'true');
|
228 | }
|
229 | return [depTree, undefined];
|
230 | }
|
231 | maybeDeduplicationSet.add(nodeId);
|
232 | }
|
233 | const cycles = [];
|
234 | for (const depInstId of depInstanceIds) {
|
235 |
|
236 |
|
237 | if (isRoot && maybeDeduplicationSet !== false) {
|
238 | maybeDeduplicationSet = new Set();
|
239 | }
|
240 | const [subtree, subtreeCycles] = await buildSubtree(depGraph, depInstId, maybeDeduplicationSet, ancestors.concat(nodeId), memoizationMap);
|
241 | if (subtreeCycles) {
|
242 | for (const cycle of subtreeCycles) {
|
243 | cycles.push(cycle);
|
244 | }
|
245 | }
|
246 | if (!subtree) {
|
247 | continue;
|
248 | }
|
249 | if (!depTree.dependencies) {
|
250 | depTree.dependencies = {};
|
251 | }
|
252 | depTree.dependencies[subtree.name] = subtree;
|
253 | }
|
254 | if (event_loop_spinner_1.eventLoopSpinner.isStarving()) {
|
255 | await event_loop_spinner_1.eventLoopSpinner.spin();
|
256 | }
|
257 | const partitionedCycles = (0, cycles_1.partitionCycles)(nodeId, cycles);
|
258 | (0, memiozation_1.memoize)(nodeId, memoizationMap, depTree, partitionedCycles);
|
259 | return [depTree, partitionedCycles.cyclesWithThisNode];
|
260 | }
|
261 | function trimAfterLastSep(str, sep) {
|
262 | return str.slice(0, str.lastIndexOf(sep));
|
263 | }
|
264 |
|
\ | No newline at end of file |