Fork Horse repository, listed by its owner. Publisher snapshot.

packages/forkhorse-runner/src/checks.ts

Snapshot at 34e3175

Containing directory

2,944 bytes · typescriptView raw
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