forked from lerna/lerna
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathquery-graph.js
102 lines (80 loc) · 2.52 KB
/
query-graph.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
"use strict";
const figgyPudding = require("figgy-pudding");
const PackageGraph = require("@lerna/package-graph");
const QueryGraphConfig = figgyPudding({
"graph-type": {},
graphType: "graph-type",
"reject-cycles": {},
rejectCycles: "reject-cycles",
});
class QueryGraph {
/**
* A mutable PackageGraph used to query for next available packages.
*
* @param {Array<Package>} packages An array of Packages to build the graph out of
* @param {String} [opts.graphType="allDependencies"] "dependencies" excludes devDependencies from graph
* @param {Boolean} [opts.rejectCycles] Whether or not to reject cycles
* @constructor
*/
constructor(packages, opts) {
const options = QueryGraphConfig(opts);
// Create dependency graph
this.graph = new PackageGraph(packages, options.graphType);
// Evaluate cycles
this.cycles = this.graph.collapseCycles(options.rejectCycles);
}
_getNextLeaf() {
return Array.from(this.graph.values()).filter(node => node.localDependencies.size === 0);
}
_getNextCycle() {
const cycle = Array.from(this.cycles).find(cycleNode => cycleNode.localDependencies.size === 0);
if (!cycle) {
return [];
}
this.cycles.delete(cycle);
return cycle.flatten();
}
getAvailablePackages() {
// Get the next leaf nodes
const availablePackages = this._getNextLeaf();
if (availablePackages.length > 0) {
return availablePackages;
}
return this._getNextCycle();
}
markAsTaken(name) {
this.graph.delete(name);
}
markAsDone(candidateNode) {
this.graph.remove(candidateNode);
for (const cycle of this.cycles) {
cycle.unlink(candidateNode);
}
}
}
module.exports = QueryGraph;
module.exports.toposort = toposort;
/**
* Sort the input list topologically.
*
* @param {!Array.<Package>} packages An array of Packages to build the list out of
* @param {Object} [options]
* @param {Boolean} options.graphType "allDependencies" or "dependencies", which excludes devDependencies
* @param {Boolean} options.rejectCycles Whether or not to reject cycles
*
* @returns {Array<Package>} a list of Package instances in topological order
*/
function toposort(packages, opts) {
const graph = new QueryGraph(packages, opts);
const result = [];
let batch = graph.getAvailablePackages();
while (batch.length) {
for (const node of batch) {
// no need to take() in synchronous loop
result.push(node.pkg);
graph.markAsDone(node);
}
batch = graph.getAvailablePackages();
}
return result;
}