import childProcess from "node:child_process"; import crypto from "node:crypto"; import { mkdir, readFile, realpath } from "node:fs/promises"; import path from "node:path"; import { promisify } from "node:util"; import type { CapabilityRepositoryResolver } from "./assembly.js"; const execFile = promisify(childProcess.execFile); const sourceKey = (kind: string, repository: string, commit: string) => `${kind}\0${repository}\0${commit.toLowerCase()}`; const checkoutName = (kind: string, repository: string, commit: string) => { const digest = crypto .createHash("sha256") .update(sourceKey(kind, repository, commit)) .digest("hex") .slice(0, 24); return `${kind}-${digest}`; }; export const createGitCapabilityResolver = async (options: { checkoutRoot: string; snapshotMap?: string; }): Promise => { await mkdir(options.checkoutRoot, { recursive: true }); const checkoutRoot = await realpath(options.checkoutRoot); const snapshots = new Map(); if (options.snapshotMap) { const snapshotMapPath = await realpath(options.snapshotMap); const document = JSON.parse(await readFile(snapshotMapPath, "utf8")) as { resources?: Array<{ kind: string; repository: string; commit: string; directory: string; }>; }; for (const entry of document.resources ?? []) { if (entry.kind !== "interface" && entry.kind !== "package") { throw new Error(`Snapshot map has unsupported resource kind ${entry.kind}`); } const key = sourceKey(entry.kind, entry.repository, entry.commit); if (snapshots.has(key)) throw new Error(`Snapshot map repeats ${key}`); const directory = path.resolve(path.dirname(snapshotMapPath), entry.directory); snapshots.set(key, await realpath(directory)); } } const checkouts = new Map>(); return async (source, kind) => { const key = sourceKey(kind, source.repository, source.commit); const snapshot = snapshots.get(key); if (snapshot) return { directory: snapshot }; const existing = checkouts.get(key); if (existing) return await existing; const pending = (async () => { const directory = path.join( checkoutRoot, checkoutName(kind, source.repository, source.commit), ); await execFile("git", [ "-c", "advice.detachedHead=false", "clone", "--depth", "1", "--single-branch", "--branch", `quixos-reachability/${source.commit.toLowerCase()}`, source.repository, directory, ]); const { stdout } = await execFile("git", ["-C", directory, "rev-parse", "HEAD"]); if (stdout.trim().toLowerCase() !== source.commit.toLowerCase()) { throw new Error( `Locked commit mismatch for ${source.repository}: ` + `wanted ${source.commit}, fetched ${stdout.trim()}`, ); } return { directory }; })(); checkouts.set(key, pending); return await pending; }; };