UNPKG

10.2 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) {
107 let count = 0;
108 for (const nodeId of this.getPkgNodeIds(pkg)) {
109 count += this.countNodePathsToRoot(nodeId);
110 }
111 return count;
112 }
113 isTransitive(pkg) {
114 const checking = new Set(this.getPkgNodeIds(pkg));
115 for (const directDep of this.getNodeDepsNodeIds(this.rootNodeId)) {
116 if (checking.has(directDep))
117 return false;
118 }
119 return true;
120 }
121 equals(other, { compareRoot = true } = {}) {
122 let otherDepGraph;
123 if (other instanceof DepGraphImpl) {
124 otherDepGraph = other;
125 }
126 else {
127 // At runtime theoretically we can have multiple versions of
128 // @snyk/dep-graph. If "other" is not an instance of the same class it is
129 // safer to rebuild it from JSON.
130 otherDepGraph = (0, create_from_json_1.createFromJSON)(other.toJSON());
131 }
132 // In theory, for the graphs created by standard means, `_.isEquals(this._data, otherDepGraph._data)`
133 // should suffice, since node IDs will be generated in a predictable way.
134 // However, there might be different versions of graph and inconsistencies
135 // in the ordering of the arrays, so we perform a deep comparison.
136 return this.nodeEquals(this, this.rootNodeId, otherDepGraph, otherDepGraph.rootNodeId, compareRoot);
137 }
138 directDepsLeadingTo(pkg) {
139 const pkgNodes = this.getPkgNodeIds(pkg);
140 const directDeps = this.getNodeDepsNodeIds(this.rootNodeId);
141 const nodes = directDeps.filter((directDep) => {
142 const reachableNodes = graphlib.alg.postorder(this._graph, [directDep]);
143 return reachableNodes.filter((node) => pkgNodes.includes(node)).length;
144 });
145 return nodes.map((node) => this.getNodePkg(node));
146 }
147 /**
148 * Create a JSON representation of a dep graph. This is typically used to
149 * send the dep graph over the wire
150 */
151 toJSON() {
152 const nodeIds = this._graph.nodes();
153 const nodes = nodeIds.reduce((acc, nodeId) => {
154 const deps = (this._graph.successors(nodeId) || []).map((depNodeId) => ({
155 nodeId: depNodeId,
156 }));
157 const node = this._graph.node(nodeId);
158 const elem = {
159 nodeId,
160 pkgId: node.pkgId,
161 deps,
162 };
163 if (node.info && Object.keys(node.info).length > 0) {
164 elem.info = node.info;
165 }
166 acc.push(elem);
167 return acc;
168 }, []);
169 const pkgs = Object.keys(this._pkgs).map((pkgId) => ({
170 id: pkgId,
171 info: this._pkgs[pkgId],
172 }));
173 return {
174 schemaVersion: DepGraphImpl.SCHEMA_VERSION,
175 pkgManager: this._pkgManager,
176 pkgs,
177 graph: {
178 rootNodeId: this._rootNodeId,
179 nodes,
180 },
181 };
182 }
183 nodeEquals(graphA, nodeIdA, graphB, nodeIdB, compareRoot, traversedPairs = new Set()) {
184 // Skip root nodes comparision if needed.
185 if (compareRoot ||
186 (nodeIdA !== graphA.rootNodeId && nodeIdB !== graphB.rootNodeId)) {
187 const pkgA = graphA.getNodePkg(nodeIdA);
188 const pkgB = graphB.getNodePkg(nodeIdB);
189 // Compare PkgInfo (name and version).
190 if (!_isEqual(pkgA, pkgB)) {
191 return false;
192 }
193 const infoA = graphA.getNode(nodeIdA);
194 const infoB = graphB.getNode(nodeIdB);
195 // Compare NodeInfo (VersionProvenance and labels).
196 if (!_isEqual(infoA, infoB)) {
197 return false;
198 }
199 }
200 let depsA = graphA.getNodeDepsNodeIds(nodeIdA);
201 let depsB = graphB.getNodeDepsNodeIds(nodeIdB);
202 // Number of dependencies should be the same.
203 if (depsA.length !== depsB.length) {
204 return false;
205 }
206 // Sort dependencies by name@version string.
207 const sortFn = (graph) => (idA, idB) => {
208 const pkgA = graph.getNodePkg(idA);
209 const pkgB = graph.getNodePkg(idB);
210 return DepGraphImpl.getPkgId(pkgA).localeCompare(DepGraphImpl.getPkgId(pkgB));
211 };
212 depsA = depsA.sort(sortFn(graphA));
213 depsB = depsB.sort(sortFn(graphB));
214 // Compare Each dependency recursively.
215 for (let i = 0; i < depsA.length; i++) {
216 const pairKey = `${depsA[i]}_${depsB[i]}`;
217 // Prevent cycles.
218 if (traversedPairs.has(pairKey)) {
219 continue;
220 }
221 traversedPairs.add(pairKey);
222 if (!this.nodeEquals(graphA, depsA[i], graphB, depsB[i], compareRoot, traversedPairs)) {
223 return false;
224 }
225 }
226 return true;
227 }
228 getGraphNode(nodeId) {
229 const node = this._graph.node(nodeId);
230 if (!node) {
231 throw new Error(`no such node: ${nodeId}`);
232 }
233 return node;
234 }
235 pathsFromNodeToRoot(nodeId, ancestors = [], opts) {
236 const parentNodesIds = this.getNodeParentsNodeIds(nodeId);
237 const pkgInfo = this.getNodePkg(nodeId);
238 if (parentNodesIds.length === 0) {
239 return [[pkgInfo]];
240 }
241 const allPaths = [];
242 ancestors = ancestors.concat(nodeId);
243 const limit = opts.limit;
244 for (const id of parentNodesIds) {
245 if (ancestors.includes(id))
246 continue;
247 const pathsFromNodeToRoot = this.pathsFromNodeToRoot(id, ancestors, opts);
248 for (const path of pathsFromNodeToRoot) {
249 allPaths.push([pkgInfo].concat(path));
250 }
251 if (limit && allPaths.length >= limit) {
252 break;
253 }
254 }
255 return allPaths;
256 }
257 countNodePathsToRoot(nodeId, ancestors = []) {
258 if (ancestors.includes(nodeId)) {
259 return 0;
260 }
261 if (this._countNodePathsToRootCache.has(nodeId)) {
262 return this._countNodePathsToRootCache.get(nodeId) || 0;
263 }
264 const parentNodesIds = this.getNodeParentsNodeIds(nodeId);
265 if (parentNodesIds.length === 0) {
266 this._countNodePathsToRootCache.set(nodeId, 1);
267 return 1;
268 }
269 ancestors = ancestors.concat(nodeId);
270 const count = parentNodesIds.reduce((acc, parentNodeId) => {
271 return acc + this.countNodePathsToRoot(parentNodeId, ancestors);
272 }, 0);
273 this._countNodePathsToRootCache.set(nodeId, count);
274 return count;
275 }
276}
277exports.DepGraphImpl = DepGraphImpl;
278DepGraphImpl.SCHEMA_VERSION = '1.2.0';
279//# sourceMappingURL=dep-graph.js.map
\No newline at end of file