import type { Manifest } from "../contracts/extension.js"; export function resolveActivationOrder(manifests: readonly Manifest[]): Manifest[] { const byId = new Map(); 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(); const dependents = new Map(); 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; }