UNPKG

10.6 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.DepGraphImpl = void 0;
4const _isEqual = require("lodash.isequal");
5const graphlib = require("../graphlib");
6const create_from_json_1 = require("./create-from-json");
7class DepGraphImpl {
8 constructor(_graph, _rootNodeId, _pkgs, _pkgNodes, _pkgManager) {
9 this._graph = _graph;
10 this._rootNodeId = _rootNodeId;
11 this._pkgs = _pkgs;
12 this._pkgNodes = _pkgNodes;
13 this._pkgManager = _pkgManager;
14 this._countNodePathsToRootCache = new Map();
15 this._rootPkgId = _graph.node(_rootNodeId).pkgId;
16 this._pkgList = Object.values(_pkgs);
17 this._depPkgsList = this._pkgList.filter((pkg) => pkg !== this.rootPkg);
18 }
19 static getPkgId(pkg) {
20 return `${pkg.name}@${pkg.version || ''}`;
21 }
22 get pkgManager() {
23 return this._pkgManager;
24 }
25 get rootPkg() {
26 return this._pkgs[this._rootPkgId];
27 }
28 get rootNodeId() {
29 return this._rootNodeId;
30 }
31 /**
32 * Get all unique packages in the graph (including the root package)
33 */
34 getPkgs() {
35 return this._pkgList;
36 }
37 /**
38 * Get all unique packages in the graph (excluding the root package)
39 */
40 getDepPkgs() {
41 return this._depPkgsList;
42 }
43 getPkgNodes(pkg) {
44 const pkgId = DepGraphImpl.getPkgId(pkg);
45 const nodes = [];
46 for (const nodeId of Array.from(this._pkgNodes[pkgId])) {
47 const graphNode = this.getGraphNode(nodeId);
48 nodes.push({
49 info: graphNode.info || {},
50 });
51 }
52 return nodes;
53 }
54 getNode(nodeId) {
55 return this.getGraphNode(nodeId).info || {};
56 }
57 getNodePkg(nodeId) {
58 return this._pkgs[this.getGraphNode(nodeId).pkgId];
59 }
60 getPkgNodeIds(pkg) {
61 const pkgId = DepGraphImpl.getPkgId(pkg);
62 if (!this._pkgs[pkgId]) {
63 throw new Error(`no such pkg: ${pkgId}`);
64 }
65 return Array.from(this._pkgNodes[pkgId]);
66 }
67 getNodeDepsNodeIds(nodeId) {
68 const deps = this._graph.successors(nodeId);
69 if (!deps) {
70 throw new Error(`no such node: ${nodeId}`);
71 }
72 return deps;
73 }
74 getNodeParentsNodeIds(nodeId) {
75 const parents = this._graph.predecessors(nodeId);
76 if (!parents) {
77 throw new Error(`no such node: ${nodeId}`);
78 }
79 return parents;
80 }
81 hasCycles() {
82 // `isAcyclic` is expensive, so memoize
83 if (this._hasCycles === undefined) {
84 this._hasCycles = !graphlib.alg.isAcyclic(this._graph);
85 }
86 return this._hasCycles;
87 }
88 pkgPathsToRoot(pkg, opts) {
89 const pathsToRoot = [];
90 const limit = opts === null || opts === void 0 ? void 0 : opts.limit;
91 for (const nodeId of this.getPkgNodeIds(pkg)) {
92 const pathsFromNodeToRoot = this.pathsFromNodeToRoot(nodeId, [], {
93 limit,
94 });
95 for (const path of pathsFromNodeToRoot) {
96 pathsToRoot.push(path);
97 }
98 if (limit && pathsToRoot.length >= limit) {
99 break;
100 }
101 }
102 // note: sorting to get shorter paths first -
103 // it's nicer - and better resembles older behaviour
104 return pathsToRoot.sort((a, b) => a.length - b.length);
105 }
106 countPathsToRoot(pkg, opts) {
107 let count = 0;
108 const limit = opts === null || opts === void 0 ? void 0 : opts.limit;
109 for (const nodeId of this.getPkgNodeIds(pkg)) {
110 if (this._countNodePathsToRootCache.has(nodeId)) {
111 count += this._countNodePathsToRootCache.get(nodeId);
112 }
113 else {
114 const c = this.countNodePathsToRoot(nodeId, limit);
115 // don't cache if a limit was supplied
116 if (!limit) {
117 this._countNodePathsToRootCache.set(nodeId, c);
118 }
119 count += c;
120 }
121 if (limit && count >= limit) {
122 return limit;
123 }
124 }
125 return count;
126 }
127 isTransitive(pkg) {
128 const checking = new Set(this.getPkgNodeIds(pkg));
129 for (const directDep of this.getNodeDepsNodeIds(this.rootNodeId)) {
130 if (checking.has(directDep))
131 return false;
132 }
133 return true;
134 }
135 equals(other, { compareRoot = true } = {}) {
136 let otherDepGraph;
137 if (other instanceof DepGraphImpl) {
138 otherDepGraph = other;
139 }
140 else {
141 // At runtime theoretically we can have multiple versions of
142 // @snyk/dep-graph. If "other" is not an instance of the same class it is
143 // safer to rebuild it from JSON.
144 otherDepGraph = (0, create_from_json_1.createFromJSON)(other.toJSON());
145 }
146 // In theory, for the graphs created by standard means, `_.isEquals(this._data, otherDepGraph._data)`
147 // should suffice, since node IDs will be generated in a predictable way.
148 // However, there might be different versions of graph and inconsistencies
149 // in the ordering of the arrays, so we perform a deep comparison.
150 return this.nodeEquals(this, this.rootNodeId, otherDepGraph, otherDepGraph.rootNodeId, compareRoot);
151 }
152 directDepsLeadingTo(pkg) {
153 const pkgNodes = this.getPkgNodeIds(pkg);
154 const directDeps = this.getNodeDepsNodeIds(this.rootNodeId);
155 const nodes = directDeps.filter((directDep) => {
156 const reachableNodes = graphlib.alg.postorder(this._graph, [directDep]);
157 return reachableNodes.filter((node) => pkgNodes.includes(node)).length;
158 });
159 return nodes.map((node) => this.getNodePkg(node));
160 }
161 /**
162 * Create a JSON representation of a dep graph. This is typically used to
163 * send the dep graph over the wire
164 */
165 toJSON() {
166 const nodeIds = this._graph.nodes();
167 const nodes = nodeIds.reduce((acc, nodeId) => {
168 const deps = (this._graph.successors(nodeId) || []).map((depNodeId) => ({
169 nodeId: depNodeId,
170 }));
171 const node = this._graph.node(nodeId);
172 const elem = {
173 nodeId,
174 pkgId: node.pkgId,
175 deps,
176 };
177 if (node.info && Object.keys(node.info).length > 0) {
178 elem.info = node.info;
179 }
180 acc.push(elem);
181 return acc;
182 }, []);
183 const pkgs = Object.keys(this._pkgs).map((pkgId) => ({
184 id: pkgId,
185 info: this._pkgs[pkgId],
186 }));
187 return {
188 schemaVersion: DepGraphImpl.SCHEMA_VERSION,
189 pkgManager: this._pkgManager,
190 pkgs,
191 graph: {
192 rootNodeId: this._rootNodeId,
193 nodes,
194 },
195 };
196 }
197 nodeEquals(graphA, nodeIdA, graphB, nodeIdB, compareRoot, traversedPairs = new Set()) {
198 // Skip root nodes comparison if needed.
199 if (compareRoot ||
200 (nodeIdA !== graphA.rootNodeId && nodeIdB !== graphB.rootNodeId)) {
201 const pkgA = graphA.getNodePkg(nodeIdA);
202 const pkgB = graphB.getNodePkg(nodeIdB);
203 // Compare PkgInfo (name and version).
204 if (!_isEqual(pkgA, pkgB)) {
205 return false;
206 }
207 const infoA = graphA.getNode(nodeIdA);
208 const infoB = graphB.getNode(nodeIdB);
209 // Compare NodeInfo (VersionProvenance and labels).
210 if (!_isEqual(infoA, infoB)) {
211 return false;
212 }
213 }
214 let depsA = graphA.getNodeDepsNodeIds(nodeIdA);
215 let depsB = graphB.getNodeDepsNodeIds(nodeIdB);
216 // Number of dependencies should be the same.
217 if (depsA.length !== depsB.length) {
218 return false;
219 }
220 // Sort dependencies by name@version string.
221 const sortFn = (graph) => (idA, idB) => {
222 const pkgA = graph.getNodePkg(idA);
223 const pkgB = graph.getNodePkg(idB);
224 return DepGraphImpl.getPkgId(pkgA).localeCompare(DepGraphImpl.getPkgId(pkgB));
225 };
226 depsA = depsA.sort(sortFn(graphA));
227 depsB = depsB.sort(sortFn(graphB));
228 // Compare Each dependency recursively.
229 for (let i = 0; i < depsA.length; i++) {
230 const pairKey = `${depsA[i]}_${depsB[i]}`;
231 // Prevent cycles.
232 if (traversedPairs.has(pairKey)) {
233 continue;
234 }
235 traversedPairs.add(pairKey);
236 if (!this.nodeEquals(graphA, depsA[i], graphB, depsB[i], compareRoot, traversedPairs)) {
237 return false;
238 }
239 }
240 return true;
241 }
242 getGraphNode(nodeId) {
243 const node = this._graph.node(nodeId);
244 if (!node) {
245 throw new Error(`no such node: ${nodeId}`);
246 }
247 return node;
248 }
249 pathsFromNodeToRoot(nodeId, ancestors = [], opts) {
250 const parentNodesIds = this.getNodeParentsNodeIds(nodeId);
251 const pkgInfo = this.getNodePkg(nodeId);
252 if (parentNodesIds.length === 0) {
253 return [[pkgInfo]];
254 }
255 const allPaths = [];
256 ancestors = ancestors.concat(nodeId);
257 const limit = opts.limit;
258 for (const id of parentNodesIds) {
259 if (ancestors.includes(id))
260 continue;
261 const pathsFromNodeToRoot = this.pathsFromNodeToRoot(id, ancestors, opts);
262 for (const path of pathsFromNodeToRoot) {
263 allPaths.push([pkgInfo].concat(path));
264 }
265 if (limit && allPaths.length >= limit) {
266 break;
267 }
268 }
269 return allPaths;
270 }
271 countNodePathsToRoot(nodeId, limit = 0, count = 0, visited = []) {
272 if (nodeId === this._rootNodeId) {
273 return count + 1;
274 }
275 visited = visited.concat(nodeId);
276 for (const parentNodeId of this.getNodeParentsNodeIds(nodeId)) {
277 if (!visited.includes(parentNodeId)) {
278 count = this.countNodePathsToRoot(parentNodeId, limit, count, visited);
279 if (limit && count >= limit) {
280 return limit;
281 }
282 }
283 }
284 return count;
285 }
286}
287exports.DepGraphImpl = DepGraphImpl;
288DepGraphImpl.SCHEMA_VERSION = '1.3.0';
289//# sourceMappingURL=dep-graph.js.map
\No newline at end of file