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