UNPKG

10 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.graphToDepTree = exports.depTreeToGraph = void 0;
4const crypto = require("crypto");
5const event_loop_spinner_1 = require("event-loop-spinner");
6const builder_1 = require("../core/builder");
7const objectHash = require("object-hash");
8const cycles_1 = require("./cycles");
9const memiozation_1 = require("./memiozation");
10function addLabel(dep, key, value) {
11 if (!dep.labels) {
12 dep.labels = {};
13 }
14 dep.labels[key] = value;
15}
16/**
17 * @deprecated Don't use dep trees as an intermediate step, because they are
18 * large structures, resulting in high memory usage and high CPU costs from
19 * serializing / deserializing. Instead, create a graph directly with
20 * {@link DepGraphBuilder}
21 */
22async 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}
46exports.depTreeToGraph = depTreeToGraph;
47async 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 // filter-out invalid null deps (shouldn't happen - but did...)
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 // we don't assume depTree has a .name to support output of `npm list --json`
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}
115async function shortenNodeIds(depGraph) {
116 const builder = new builder_1.DepGraphBuilder(depGraph.pkgManager, depGraph.rootPkg);
117 const nodesMap = {};
118 // create nodes with shorter ids
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 // connect nodes
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 * @deprecated Don't use dep trees. You should adapt your code to use graphs,
158 * and enhance the dep-graph library if there is missing functionality from
159 * the graph structure
160 */
161async 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}
172exports.graphToDepTree = graphToDepTree;
173function constructPackageFormatVersion(pkgType) {
174 if (pkgType === 'maven') {
175 pkgType = 'mvn';
176 }
177 return `${pkgType}:0.0.1`;
178}
179function constructTargetOS(depGraph) {
180 if (['apk', 'apt', 'deb', 'rpm', 'linux'].indexOf(depGraph.pkgManager.name) ===
181 -1) {
182 // .targetOS is undefined unless its a linux pkgManager
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}
193async function buildSubtree(depGraph, nodeId, maybeDeduplicationSet = false, // false = disabled; null = not in deduplication scope yet
194ancestors = [], 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 // This node starts a cycle and now it's the second visit.
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 // Deduplication of nodes occurs only within a scope of a top-level dependency.
236 // Therefore, every top-level dep gets an independent set to track duplicates.
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}
261function trimAfterLastSep(str, sep) {
262 return str.slice(0, str.lastIndexOf(sep));
263}
264//# sourceMappingURL=index.js.map
\No newline at end of file