// The published source's contract: where `archilyzer source publish` // (common/publish/source.ts) puts things on the project site, and the shape of // the manifest it writes LAST, beside them (public/source/manifest.json). The // homepage's /source/ page (homepage/app/lib/source.ts) reads the same shape. // // A leaf: it imports only lib/project.ts (itself import-free), so the homepage // can pull it into a server component without dragging node: modules along. // // The mirror is a DUMB-HTTP git repository: static files only (HEAD, // info/refs, objects/info/packs, the packs), which `git clone` reads with no // server-side git at all. Its directory is `archilyzer.git`, never a path // segment named `.git` — wrangler's upload ignore list drops `**/.git` // silently, and Cloudflare's managed rules block `/.git/` paths. import { PROJECT_URL } from "./project"; export const SOURCE_MANIFEST_VERSION = 1; /** The mirror's directory under /source/. */ export const MIRROR_DIR = "archilyzer.git"; /** What a reader types: `git clone `. */ export const CLONE_URL = `${PROJECT_URL}/source/${MIRROR_DIR}`; /** The raw tree's generated index. */ export const TREE_HREF = "/source/tree/"; /** * The history pages' directory under /source/: stagit's rendering of the * mirror (publish/sourceHistory.ts) — the log, a page per commit with its * diff, the refs, the files index (into the raw tree) and two Atom feeds. */ export const HISTORY_DIR = "git"; export const HISTORY_LOG_HREF = `/source/${HISTORY_DIR}/log.html`; export const HISTORY_REFS_HREF = `/source/${HISTORY_DIR}/refs.html`; export const HISTORY_ATOM_HREF = `/source/${HISTORY_DIR}/atom.xml`; /** The manifest's public path. */ export const SOURCE_MANIFEST_HREF = "/source/manifest.json"; /** * The tarball's public path — the one the Downloads page has always linked * (homepage/app/lib/snapshot.ts SNAPSHOT_HREF). Stable by design: the date and * commit live in snapshot.json beside it. */ export const TARBALL_HREF = "/downloads/archilyzer-source.tar.gz"; export type SourceManifest = { version: typeof SOURCE_MANIFEST_VERSION; generatedAt: string; branch: "main"; // The PRIVATE repository's main, which the mirror reflects. Its history is // never rewritten; the mirror is generated from a fresh clone of it. sourceCommit: string; // The mirror's main: a different id for the same history, because paths // were scrubbed on the way out. mirrorHead: string; // The mirror head's subject line (audited like every other commit). subject: string; // Everything else the step published: the mirror, the tree and its // indexes, the tarball and snapshot.json (not this manifest itself). files: number; bytes: number; mirror: { files: number; bytes: number; packs: number }; // The tracked files of main, extracted; `files`/`bytes` do not count the // generated index.html pages, `dirs` counts the directories (each has one). tree: { files: number; dirs: number; bytes: number }; tarball: { href: string; bytes: number; sha256: string }; cloneUrl: string; treeHref: string; // What the gate read: how many git objects (commits among them), and // whether gitleaks ran. NOT how many literals it searched for: `plans/` // publishes which ones the plan put in the files, so the count would say // whether the operator added private ones. audit: { objects: number; commits: number; gitleaks: "clean" | "skipped"; }; // The tools that made it (a filter-repo upgrade may change mirrorHead). tools: { git: string; filterRepo: string }; // The history pages, when stagit rendered them (absent: no stagit on the // publishing machine, or a render that failed — the page then shows no // History links). history?: SourceHistory; }; export type SourceHistory = { // The log page (HISTORY_LOG_HREF). href: string; // How many commits have a page (the newest ones: the log lists exactly // these), of `total`, every commit of the mirror's main. Equal until main // passes the cap (publish/sourceHistory.ts SOURCE_HISTORY_MAX_COMMITS). // `head` is the one the log starts at (the manifest's mirrorHead). commits: number; total: number; head: string; // Every file under /source/git/ (the pages, the two feeds, style.css): how // many, their bytes, and one sha256 over them — each file's path, size and // sha256, in sorted path order — which the deploy check recomputes over out/. files: number; bytes: number; sha256: string; // The renderer: "stagit (sha256 )". tool: string; }; const HEX40 = /^[0-9a-f]{40}$/; const HEX64 = /^[0-9a-f]{64}$/; /** * The manifest, or null when `value` is not one this code can trust: version * 1, 40-hex commit ids, a 64-hex tarball sha, and a finite number wherever a * page reads one. A malformed or foreign manifest is the page's empty state, * never a crash in `next build`. The homepage additionally requires the files * it describes to be present (homepage/app/lib/source.ts). */ export function parseSourceManifest(value: unknown): SourceManifest | null { if (!value || typeof value !== "object") return null; const m = value as Partial; const num = (x: unknown) => typeof x === "number" && Number.isFinite(x) && x >= 0; const obj = (x: unknown): x is Record => !!x && typeof x === "object"; if (m.version !== SOURCE_MANIFEST_VERSION) return null; if (typeof m.mirrorHead !== "string" || !HEX40.test(m.mirrorHead)) return null; if (typeof m.sourceCommit !== "string" || !HEX40.test(m.sourceCommit)) return null; if (typeof m.subject !== "string" || typeof m.generatedAt !== "string") return null; if (Number.isNaN(Date.parse(m.generatedAt))) return null; if (!num(m.files) || !num(m.bytes)) return null; if (!obj(m.tarball) || typeof m.tarball.sha256 !== "string" || !HEX64.test(m.tarball.sha256)) { return null; } if (!num(m.tarball.bytes) || typeof m.tarball.href !== "string") return null; if (!obj(m.mirror) || !num(m.mirror.files) || !num(m.mirror.bytes) || !num(m.mirror.packs)) { return null; } if (!obj(m.tree) || !num(m.tree.files) || !num(m.tree.dirs) || !num(m.tree.bytes)) return null; if (!obj(m.audit)) return null; // Optional, and all-or-nothing: a history block that is present but // malformed makes the whole manifest untrusted, like any other bad field. if (m.history !== undefined) { const h = m.history as Partial | null; if (!obj(h)) return null; if (!num(h.commits) || !num(h.total) || !num(h.files) || !num(h.bytes)) return null; if ((h.total as number) < (h.commits as number)) return null; if (typeof h.head !== "string" || !HEX40.test(h.head)) return null; if (typeof h.sha256 !== "string" || !HEX64.test(h.sha256)) return null; if (typeof h.href !== "string" || typeof h.tool !== "string") return null; } return m as SourceManifest; }