Fork Horse repository, listed by its owner. Publisher snapshot.
packages/forkhorse-runner/src/checks.ts
Snapshot at 34e3175
1import type { Check, ForkRepository } from "./types";
2
3export const CHECKS_PATH = ".forkhorse/checks";
4export const MAX_CONFIG_BYTES = 32_768;
5
6/** Absence is distinct from an invalid or unsafe check file in advisory projects. */
7export class MissingChecksError extends Error {}
8
9export function parseChecks(text: string): Check[] {
10 if (new TextEncoder().encode(text).length > MAX_CONFIG_BYTES) throw new Error("Checks file too large");
11 const value: unknown = JSON.parse(text);
12 if (!Array.isArray(value) || value.length < 1 || value.length > 16) throw new Error("Expected 1–16 checks");
13 const names = new Set<string>();
14 return value.map((item: unknown) => {
15 if (!item || typeof item !== "object") throw new Error("Invalid check");
16 const check = item as Record<string, unknown>;
17 if (typeof check.name !== "string" || !/^[a-zA-Z0-9_-]{1,64}$/.test(check.name) || names.has(check.name))
18 throw new Error("Invalid check");
19 names.add(check.name);
20 if (check.tier === "quick") {
21 if (Object.keys(check).toSorted().join() !== "module,name,tier" ||
22 typeof check.module !== "string" || !/^\.forkhorse\/quick\/[a-zA-Z0-9_-][a-zA-Z0-9_.-]*\.js$/.test(check.module) ||
23 check.module.length > 256) throw new Error("Invalid quick check");
24 return { name: check.name, tier: "quick", module: check.module };
25 }
26 if (Object.keys(check).toSorted().join() !== "image,name,run,tier" || check.tier !== "full" ||
27 typeof check.image !== "string" || !/^[a-zA-Z0-9_-]{1,64}$/.test(check.image) ||
28 typeof check.run !== "string" || !check.run.trim() || check.run.length > 8192 ||
29 check.run.includes("\0")) throw new Error("Invalid check");
30 return { name: check.name, tier: "full", image: check.image, run: check.run };
31 });
32}
33
34/** Resolve the regular checks blob from the exact authorized commit, never a moving ref. */
35export async function loadChecks(repo: ForkRepository, commit: string): Promise<Check[]> {
36 const metadata = await repo.readCommit(commit);
37 if (!metadata || metadata.hash !== commit) throw new Error("Commit unavailable");
38 const root = await repo.readTree(metadata.treeHash);
39 if (!root) throw new Error("Commit tree unavailable");
40 const dir = root.find((entry) => entry.name === ".forkhorse");
41 if (!dir) throw new MissingChecksError("Missing checks directory");
42 if (dir.type !== "tree") throw new Error("Missing checks directory");
43 const tree = await repo.readTree(dir.hash);
44 if (!tree) throw new Error("Checks tree unavailable");
45 const file = tree.find((entry) => entry.name === "checks");
46 if (!file) throw new MissingChecksError("Missing regular checks file");
47 if (file.type !== "blob") throw new Error("Missing regular checks file");
48 const blob = await repo.readBlob(file.hash);
49 if (!blob || blob.size > MAX_CONFIG_BYTES) throw new Error("Checks file unavailable or too large");
50 return parseChecks(await blob.text());
51}
52