Build workspace agent, capability graph, and versioned cutovers
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
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<CapabilityRepositoryResolver> => {
|
||||
await mkdir(options.checkoutRoot, { recursive: true });
|
||||
const checkoutRoot = await realpath(options.checkoutRoot);
|
||||
const snapshots = new Map<string, string>();
|
||||
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<string, Promise<{ directory: string }>>();
|
||||
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;
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user