summaryrefslogtreecommitdiffhomepage
path: root/packages/kernel/src/host/dag.ts
blob: 5cde2f53c2523dfac9bee6954159b2c5b28f8d43 (plain)
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
import type { Manifest } from "../contracts/extension.js";

export function resolveActivationOrder(manifests: readonly Manifest[]): Manifest[] {
	const byId = new Map<string, Manifest>();
	for (const m of manifests) {
		if (byId.has(m.id)) {
			throw new Error(`Duplicate extension id: "${m.id}"`);
		}
		byId.set(m.id, m);
	}

	for (const m of manifests) {
		for (const dep of m.dependsOn ?? []) {
			if (!byId.has(dep)) {
				throw new Error(`Extension "${m.id}" depends on "${dep}", which is not available.`);
			}
		}
	}

	const inDegree = new Map<string, number>();
	const dependents = new Map<string, string[]>();

	for (const m of manifests) {
		inDegree.set(m.id, 0);
		dependents.set(m.id, []);
	}

	for (const m of manifests) {
		for (const dep of m.dependsOn ?? []) {
			const list = dependents.get(dep);
			if (list !== undefined) list.push(m.id);
			inDegree.set(m.id, (inDegree.get(m.id) ?? 0) + 1);
		}
	}

	const queue: string[] = [];
	for (const [id, deg] of inDegree) {
		if (deg === 0) queue.push(id);
	}

	const result: Manifest[] = [];
	let idx = 0;
	while (idx < queue.length) {
		const id = queue[idx];
		if (id === undefined) break;
		idx++;
		const m = byId.get(id);
		if (m === undefined) continue;
		result.push(m);
		for (const dep of dependents.get(id) ?? []) {
			const newDeg = (inDegree.get(dep) ?? 1) - 1;
			inDegree.set(dep, newDeg);
			if (newDeg === 0) queue.push(dep);
		}
	}

	if (result.length !== manifests.length) {
		const remaining = manifests.filter((m) => !result.some((r) => r.id === m.id));
		const ids = remaining.map((m) => m.id).join(", ");
		throw new Error(`Dependency cycle detected among extensions: ${ids}`);
	}

	return result;
}