UNPKG

9.88 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 const pkgManagerInfo = {
28 name: pkgManagerName,
29 };
30 const targetOS = depTree.targetOS;
31 if (targetOS) {
32 pkgManagerInfo.repositories = [
33 {
34 alias: `${targetOS.name}:${targetOS.version}`,
35 },
36 ];
37 }
38 const builder = new builder_1.DepGraphBuilder(pkgManagerInfo, rootPkg);
39 await buildGraph(builder, depTree, depTree.name, true);
40 const depGraph = await builder.build();
41 return shortenNodeIds(depGraph);
42}
43exports.depTreeToGraph = depTreeToGraph;
44async function buildGraph(builder, depTree, pkgName, isRoot = false, memoizationMap = new Map()) {
45 if (memoizationMap.has(depTree)) {
46 return memoizationMap.get(depTree);
47 }
48 const getNodeId = (name, version, hashId) => `${name}@${version || ''}|${hashId}`;
49 const depNodesIds = [];
50 const hash = crypto.createHash('sha1');
51 if (depTree.versionProvenance) {
52 hash.update(objectHash(depTree.versionProvenance));
53 }
54 if (depTree.labels) {
55 hash.update(objectHash(depTree.labels));
56 }
57 const deps = depTree.dependencies || {};
58 // filter-out invalid null deps (shouldn't happen - but did...)
59 const depNames = Object.keys(deps).filter((d) => !!deps[d]);
60 for (const depName of depNames.sort()) {
61 const dep = deps[depName];
62 const subtreeHash = await buildGraph(builder, dep, depName, false, memoizationMap);
63 const depPkg = {
64 name: depName,
65 version: dep.version,
66 };
67 const depNodeId = getNodeId(depPkg.name, depPkg.version, subtreeHash);
68 depNodesIds.push(depNodeId);
69 const nodeInfo = {};
70 if (dep.versionProvenance) {
71 nodeInfo.versionProvenance = dep.versionProvenance;
72 }
73 if (dep.labels) {
74 nodeInfo.labels = dep.labels;
75 }
76 builder.addPkgNode(depPkg, depNodeId, nodeInfo);
77 hash.update(depNodeId);
78 }
79 const treeHash = hash.digest('hex');
80 let pkgNodeId;
81 if (isRoot) {
82 pkgNodeId = builder.rootNodeId;
83 }
84 else {
85 // we don't assume depTree has a .name to support output of `npm list --json`
86 const pkg = {
87 name: pkgName,
88 version: depTree.version,
89 };
90 pkgNodeId = getNodeId(pkg.name, pkg.version, treeHash);
91 const nodeInfo = {};
92 if (depTree.versionProvenance) {
93 nodeInfo.versionProvenance = depTree.versionProvenance;
94 }
95 if (depTree.labels) {
96 nodeInfo.labels = depTree.labels;
97 }
98 builder.addPkgNode(pkg, pkgNodeId, nodeInfo);
99 }
100 for (const depNodeId of depNodesIds) {
101 builder.connectDep(pkgNodeId, depNodeId);
102 }
103 if (depNodesIds.length > 0 && event_loop_spinner_1.eventLoopSpinner.isStarving()) {
104 await event_loop_spinner_1.eventLoopSpinner.spin();
105 }
106 memoizationMap.set(depTree, treeHash);
107 return treeHash;
108}
109async function shortenNodeIds(depGraph) {
110 const builder = new builder_1.DepGraphBuilder(depGraph.pkgManager, depGraph.rootPkg);
111 const nodesMap = {};
112 // create nodes with shorter ids
113 for (const pkg of depGraph.getPkgs()) {
114 const nodeIds = depGraph.getPkgNodeIds(pkg);
115 for (let i = 0; i < nodeIds.length; i++) {
116 const nodeId = nodeIds[i];
117 if (nodeId === depGraph.rootNodeId) {
118 continue;
119 }
120 const nodeInfo = depGraph.getNode(nodeId);
121 let newNodeId;
122 if (nodeIds.length === 1) {
123 newNodeId = `${trimAfterLastSep(nodeId, '|')}`;
124 }
125 else {
126 newNodeId = `${trimAfterLastSep(nodeId, '|')}|${i + 1}`;
127 }
128 nodesMap[nodeId] = newNodeId;
129 builder.addPkgNode(pkg, newNodeId, nodeInfo);
130 }
131 if (event_loop_spinner_1.eventLoopSpinner.isStarving()) {
132 await event_loop_spinner_1.eventLoopSpinner.spin();
133 }
134 }
135 // connect nodes
136 for (const pkg of depGraph.getPkgs()) {
137 for (const nodeId of depGraph.getPkgNodeIds(pkg)) {
138 for (const depNodeId of depGraph.getNodeDepsNodeIds(nodeId)) {
139 const parentNode = nodesMap[nodeId] || nodeId;
140 const childNode = nodesMap[depNodeId] || depNodeId;
141 builder.connectDep(parentNode, childNode);
142 }
143 }
144 if (event_loop_spinner_1.eventLoopSpinner.isStarving()) {
145 await event_loop_spinner_1.eventLoopSpinner.spin();
146 }
147 }
148 return builder.build();
149}
150/**
151 * @deprecated Don't use dep trees. You should adapt your code to use graphs,
152 * and enhance the dep-graph library if there is missing functionality from
153 * the graph structure
154 */
155async function graphToDepTree(depGraphInterface, pkgType, opts = { deduplicateWithinTopLevelDeps: false }) {
156 const depGraph = depGraphInterface;
157 const [depTree] = await buildSubtree(depGraph, depGraph.rootNodeId, opts.deduplicateWithinTopLevelDeps ? null : false);
158 depTree.type = depGraph.pkgManager.name;
159 depTree.packageFormatVersion = constructPackageFormatVersion(pkgType);
160 const targetOS = constructTargetOS(depGraph);
161 if (targetOS) {
162 depTree.targetOS = targetOS;
163 }
164 return depTree;
165}
166exports.graphToDepTree = graphToDepTree;
167function constructPackageFormatVersion(pkgType) {
168 if (pkgType === 'maven') {
169 pkgType = 'mvn';
170 }
171 return `${pkgType}:0.0.1`;
172}
173function constructTargetOS(depGraph) {
174 if (['apk', 'apt', 'deb', 'rpm', 'linux'].indexOf(depGraph.pkgManager.name) ===
175 -1) {
176 // .targetOS is undefined unless its a linux pkgManager
177 return;
178 }
179 if (!depGraph.pkgManager.repositories ||
180 !depGraph.pkgManager.repositories.length ||
181 !depGraph.pkgManager.repositories[0].alias) {
182 throw new Error('Incomplete .pkgManager, could not create .targetOS');
183 }
184 const [name, version] = depGraph.pkgManager.repositories[0].alias.split(':');
185 return { name, version };
186}
187async function buildSubtree(depGraph, nodeId, maybeDeduplicationSet = false, // false = disabled; null = not in deduplication scope yet
188ancestors = [], memoizationMap = new Map()) {
189 if (!maybeDeduplicationSet) {
190 const memoizedDepTree = memiozation_1.getMemoizedDepTree(nodeId, ancestors, memoizationMap);
191 if (memoizedDepTree) {
192 return [memoizedDepTree, undefined];
193 }
194 }
195 const isRoot = nodeId === depGraph.rootNodeId;
196 const nodePkg = depGraph.getNodePkg(nodeId);
197 const nodeInfo = depGraph.getNode(nodeId);
198 const depTree = {};
199 depTree.name = nodePkg.name;
200 depTree.version = nodePkg.version;
201 if (nodeInfo.versionProvenance) {
202 depTree.versionProvenance = nodeInfo.versionProvenance;
203 }
204 if (nodeInfo.labels) {
205 depTree.labels = Object.assign({}, nodeInfo.labels);
206 }
207 const depInstanceIds = depGraph.getNodeDepsNodeIds(nodeId);
208 if (!depInstanceIds || depInstanceIds.length === 0) {
209 memoizationMap.set(nodeId, { depTree });
210 return [depTree, undefined];
211 }
212 const cycle = cycles_1.getCycle(ancestors, nodeId);
213 if (cycle) {
214 // This node starts a cycle and now it's the second visit.
215 addLabel(depTree, 'pruned', 'cyclic');
216 return [depTree, [cycle]];
217 }
218 if (maybeDeduplicationSet) {
219 if (maybeDeduplicationSet.has(nodeId)) {
220 if (depInstanceIds.length > 0) {
221 addLabel(depTree, 'pruned', 'true');
222 }
223 return [depTree, undefined];
224 }
225 maybeDeduplicationSet.add(nodeId);
226 }
227 const cycles = [];
228 for (const depInstId of depInstanceIds) {
229 // Deduplication of nodes occurs only within a scope of a top-level dependency.
230 // Therefore, every top-level dep gets an independent set to track duplicates.
231 if (isRoot && maybeDeduplicationSet !== false) {
232 maybeDeduplicationSet = new Set();
233 }
234 const [subtree, subtreeCycles] = await buildSubtree(depGraph, depInstId, maybeDeduplicationSet, ancestors.concat(nodeId), memoizationMap);
235 if (subtreeCycles) {
236 for (const cycle of subtreeCycles) {
237 cycles.push(cycle);
238 }
239 }
240 if (!subtree) {
241 continue;
242 }
243 if (!depTree.dependencies) {
244 depTree.dependencies = {};
245 }
246 depTree.dependencies[subtree.name] = subtree;
247 }
248 if (event_loop_spinner_1.eventLoopSpinner.isStarving()) {
249 await event_loop_spinner_1.eventLoopSpinner.spin();
250 }
251 const partitionedCycles = cycles_1.partitionCycles(nodeId, cycles);
252 memiozation_1.memoize(nodeId, memoizationMap, depTree, partitionedCycles);
253 return [depTree, partitionedCycles.cyclesWithThisNode];
254}
255function trimAfterLastSep(str, sep) {
256 return str.slice(0, str.lastIndexOf(sep));
257}
258//# sourceMappingURL=index.js.map
\No newline at end of file