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 | 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 | }
|
43 | exports.depTreeToGraph = depTreeToGraph;
|
44 | async 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 |
|
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 |
|
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 | }
|
109 | async function shortenNodeIds(depGraph) {
|
110 | const builder = new builder_1.DepGraphBuilder(depGraph.pkgManager, depGraph.rootPkg);
|
111 | const nodesMap = {};
|
112 |
|
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 |
|
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 |
|
152 |
|
153 |
|
154 |
|
155 | async 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 | }
|
166 | exports.graphToDepTree = graphToDepTree;
|
167 | function constructPackageFormatVersion(pkgType) {
|
168 | if (pkgType === 'maven') {
|
169 | pkgType = 'mvn';
|
170 | }
|
171 | return `${pkgType}:0.0.1`;
|
172 | }
|
173 | function constructTargetOS(depGraph) {
|
174 | if (['apk', 'apt', 'deb', 'rpm', 'linux'].indexOf(depGraph.pkgManager.name) ===
|
175 | -1) {
|
176 |
|
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 | }
|
187 | async function buildSubtree(depGraph, nodeId, maybeDeduplicationSet = false, // false = disabled; null = not in deduplication scope yet
|
188 | ancestors = [], memoizationMap = new Map()) {
|
189 | if (!maybeDeduplicationSet) {
|
190 | const memoizedDepTree = (0, 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 = { ...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 = (0, cycles_1.getCycle)(ancestors, nodeId);
|
213 | if (cycle) {
|
214 |
|
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 |
|
230 |
|
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 = (0, cycles_1.partitionCycles)(nodeId, cycles);
|
252 | (0, memiozation_1.memoize)(nodeId, memoizationMap, depTree, partitionedCycles);
|
253 | return [depTree, partitionedCycles.cyclesWithThisNode];
|
254 | }
|
255 | function trimAfterLastSep(str, sep) {
|
256 | return str.slice(0, str.lastIndexOf(sep));
|
257 | }
|
258 |
|
\ | No newline at end of file |