@enspirit/emb
Version:
A replacement for our Makefile-for-monorepos
85 lines (84 loc) • 2.63 kB
JavaScript
import graphlib from 'graphlib';
import { CircularDependencyError } from '../../errors.js';
/* ----------------- run-order helpers unchanged (for completeness) ---------------- */
export function resolveRefSet(col, ref, policy) {
if (policy === 'runAll') {
return col.matches(ref, { multiple: true }).map((t) => col.idOf(t));
}
return [col.idOf(col.matches(ref))];
}
export function collectPredecessorClosure(g, seeds) {
const seen = new Set();
const q = [];
for (const s of seeds) {
if (!seen.has(s)) {
seen.add(s);
q.push(s);
}
}
while (q.length > 0) {
const cur = q.shift();
for (const p of g.predecessors(cur) ?? []) {
if (!seen.has(p)) {
seen.add(p);
q.push(p);
}
}
}
return seen;
}
export function buildGraph(col, policy) {
const g = new graphlib.Graph({ directed: true });
for (const t of col.all) {
g.setNode(col.idOf(t));
}
for (const t of col.all) {
const toId = col.idOf(t);
for (const ref of col.depsOf(t)) {
for (const fromId of resolveRefSet(col, ref, policy)) {
g.setEdge(fromId, toId);
}
}
}
return g;
}
export function findRunOrder(selection, collection, { onAmbiguous = 'error' } = {}) {
const g = buildGraph(collection, onAmbiguous);
const cycles = graphlib.alg.findCycles(g);
if (cycles.length > 0) {
throw new CircularDependencyError(`Circular dependencies detected: ${JSON.stringify(cycles)}`, cycles);
}
const selectedIds = new Set();
for (const ref of selection) {
for (const id of resolveRefSet(collection, ref, onAmbiguous)) {
selectedIds.add(id);
}
}
if (selectedIds.size === 0) {
throw new Error('Selection resolved to no items.');
}
const include = collectPredecessorClosure(g, selectedIds.values());
const sub = new graphlib.Graph({ directed: true });
for (const id of include) {
sub.setNode(id);
}
for (const id of include) {
for (const p of g.predecessors(id) ?? []) {
if (include.has(p)) {
sub.setEdge(p, id);
}
}
}
const ids = graphlib.alg.topsort(sub);
const byId = new Map();
for (const t of collection.all) {
byId.set(collection.idOf(t), t);
}
return ids.map((id) => {
const t = byId.get(id);
if (!t) {
throw new Error(`Internal error: missing item for id "${id}"`);
}
return t;
});
}