commit dd71de60bd3faad0bb2ed2ebfb7e8c1bd4fa894c
parent 3b2ae052365d78e072708c05938bb706e09fa5f7
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Mon, 28 Sep 2026 14:22:47 -0400
homepage: E2E_EXPECT_SOURCE makes the /source specs fail on the empty state; unit tests for the loader and _headers
Review L9/L10: with E2E_EXPECT_SOURCE=1 (declared in playwright.config.ts and
the env registry) the four data tests of source.spec.ts fail instead of
accepting the empty state. app/lib/headers.test.ts pins _headers with a replica
of wrangler's parse-and-attach: every /source/tree override detaches
Content-Type before setting it, each path gets ONE value, and the rule count
and line length sit inside wrangler's limits. app/lib/source.test.ts: a
malformed, wrong-version or orphaned manifest is the empty state, never a
throw (`loadSourceManifest` takes the public dir as a seam).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Diffstat:
7 files changed, 199 insertions(+), 5 deletions(-)
diff --git a/ENVIRONMENT.md b/ENVIRONMENT.md
@@ -186,4 +186,5 @@ Read only by a test harness, a fake binary or a test-mode branch. Never set one
| `E2E_RETRIES` | `0` | Retries per shard (`--retries N` wins); 0 keeps a sharded run comparable to a serial one. | scripts/run-sharded-e2e.mjs |
| `E2E_IMAGE` | `yt-dlp-transcript-browser-e2e` | The sharded e2e run's image tag. | scripts/run-sharded-e2e.mjs |
| `E2E_SKIP_BUILD` | off | `1` reuses the sharded e2e image instead of rebuilding it (`--no-build`). | scripts/run-sharded-e2e.mjs |
+| `E2E_EXPECT_SOURCE` | off (both states pass) | `1` makes the homepage suite's `/source/` specs fail on the empty state; a gate that ran `archilyzer source publish` first sets it. | homepage/e2e/source.spec.ts |
| `E2E_HOMEPAGE_SUMMARY_FILE` | `homepage/public/homepage-summary.json` | The synthetic summary the homepage's e2e dev server reads; set by `homepage/playwright.config.ts`, ignored by a production build. | homepage/app/lib/summary.ts |
diff --git a/common/lib/envVars.ts b/common/lib/envVars.ts
@@ -185,6 +185,7 @@ const DECLARED: EnvVarDecl[] = [
{ name: "E2E_RETRIES", audience: "test", default: "`0`", readBy: "scripts/run-sharded-e2e.mjs", doc: "Retries per shard (`--retries N` wins); 0 keeps a sharded run comparable to a serial one." },
{ name: "E2E_IMAGE", audience: "test", default: "`yt-dlp-transcript-browser-e2e`", readBy: "scripts/run-sharded-e2e.mjs", doc: "The sharded e2e run's image tag." },
{ name: "E2E_SKIP_BUILD", audience: "test", default: "off", readBy: "scripts/run-sharded-e2e.mjs", doc: "`1` reuses the sharded e2e image instead of rebuilding it (`--no-build`)." },
+ { name: "E2E_EXPECT_SOURCE", audience: "test", default: "off (both states pass)", readBy: "homepage/e2e/source.spec.ts", doc: "`1` makes the homepage suite's `/source/` specs fail on the empty state; a gate that ran `archilyzer source publish` first sets it." },
{ name: "E2E_HOMEPAGE_SUMMARY_FILE", audience: "test", default: "`homepage/public/homepage-summary.json`", readBy: "homepage/app/lib/summary.ts", doc: "The synthetic summary the homepage's e2e dev server reads; set by `homepage/playwright.config.ts`, ignored by a production build." },
];
diff --git a/homepage/app/lib/headers.test.ts b/homepage/app/lib/headers.test.ts
@@ -0,0 +1,103 @@
+import { test } from "node:test";
+import assert from "node:assert/strict";
+import { readFileSync } from "node:fs";
+import path from "node:path";
+import { fileURLToPath } from "node:url";
+
+// Run with:
+// pnpm --filter homepage test
+//
+// public/_headers is what keeps the raw source tree from being served as
+// code on this origin, and nothing but the Pages edge applies it. This pins
+// its security property with a small replica of wrangler's `_headers`
+// handling (pages-shared parseHeaders + generateRulesMatcher + attachHeaders,
+// read in wrangler 4.88 and re-checked by the review in 4.142): every
+// matching rule applies in file order, `! Name` detaches a header, and a
+// header a LATER rule sets again is APPENDED ("a, b"), not replaced.
+
+const FILE = path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "public", "_headers");
+const TEXT = readFileSync(FILE, "utf8");
+
+type Rule = { path: string; set: Record<string, string>; unset: string[]; re: RegExp; lines: string[] };
+
+function parse(text: string): Rule[] {
+ const rules: Rule[] = [];
+ let rule: Rule | undefined;
+ for (const raw of text.split("\n")) {
+ const line = raw.trim();
+ if (!line || line.startsWith("#")) continue;
+ if (/^([^\s]+:\/\/|^\/)/.test(line)) {
+ if (rule) rules.push(rule);
+ const esc = (s: string) => s.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&");
+ rule = { path: line, set: {}, unset: [], re: new RegExp(`^${line.split("*").map(esc).join("(?<splat>.*)")}$`), lines: [] };
+ continue;
+ }
+ rule!.lines.push(line);
+ if (line.startsWith("! ")) {
+ rule!.unset.push(line.slice(2).toLowerCase());
+ continue;
+ }
+ const i = line.indexOf(":");
+ const name = line.slice(0, i).trim().toLowerCase();
+ const value = line.slice(i + 1).trim();
+ rule!.set[name] = rule!.set[name] ? `${rule!.set[name]}, ${value}` : value;
+ }
+ if (rule) rules.push(rule);
+ return rules;
+}
+
+function served(rules: Rule[], pathname: string, contentType: string): Headers {
+ const h = new Headers({ "content-type": contentType });
+ const setOnce = new Set<string>();
+ for (const r of rules.filter((x) => x.re.test(pathname))) {
+ for (const k of r.unset) h.delete(k);
+ for (const [k, v] of Object.entries(r.set)) {
+ if (setOnce.has(k)) h.append(k, v);
+ else {
+ h.set(k, v);
+ setOnce.add(k);
+ }
+ }
+ }
+ return h;
+}
+
+const RULES = parse(TEXT);
+
+test("wrangler's limits: at most 100 rules, no line over 2,000 characters, one splat a rule", () => {
+ assert.ok(RULES.length <= 100, `${RULES.length} rules`);
+ for (const line of TEXT.split("\n")) assert.ok(line.length <= 2000, line.slice(0, 60));
+ for (const r of RULES) assert.ok((r.path.match(/\*/g) ?? []).length <= 1, r.path);
+});
+
+test("every /source/tree override detaches Content-Type before it sets its own", () => {
+ const overrides = RULES.filter((r) => r.path.startsWith("/source/tree/") && r.path !== "/source/tree/*" && "content-type" in r.set);
+ assert.ok(overrides.length >= 7, overrides.map((r) => r.path).join(" "));
+ for (const r of overrides) {
+ const unset = r.lines.findIndex((l) => l.toLowerCase() === "! content-type");
+ const set = r.lines.findIndex((l) => /^content-type:/i.test(l));
+ assert.ok(unset !== -1 && unset < set, `${r.path} must say "! Content-Type" before it sets one`);
+ }
+});
+
+test("the raw tree is text, its pages HTML, its binaries their own type — each ONE value", () => {
+ const cases: Array<[string, string, string]> = [
+ ["/source/tree/common/lib/paths.ts", "video/mp2t", "text/plain; charset=utf-8"],
+ ["/source/tree/README.md", "text/markdown", "text/plain; charset=utf-8"],
+ ["/source/tree/", "text/html", "text/html; charset=utf-8"],
+ ["/source/tree/common/", "text/html", "text/html; charset=utf-8"],
+ ["/source/tree/umtool/report-to-video/fonts/Archivo%5Bwdth%2Cwght%5D.ttf", "font/ttf", "font/ttf"],
+ ["/source/tree/homepage/app/docs/%5Bslug%5D/page.tsx", "application/octet-stream", "text/plain; charset=utf-8"],
+ ["/source/tree/umtool/models/yunet.onnx", "application/octet-stream", "application/octet-stream"],
+ ["/source/tree/homepage/public/icon.svg", "image/svg+xml", "image/svg+xml"],
+ // A missing directory: Pages answers with a 404 page, on this rule.
+ ["/source/tree/no/such/dir/", "text/html", "text/html; charset=utf-8"],
+ ];
+ for (const [p, served0, want] of cases) {
+ const h = served(RULES, p, served0);
+ assert.equal(h.get("content-type"), want, p);
+ assert.equal(h.get("x-content-type-options"), "nosniff", p);
+ }
+ assert.match(served(RULES, "/source/tree/a.svg", "image/svg+xml").get("content-security-policy")!, /default-src 'none'/);
+ assert.equal(served(RULES, "/source/manifest.json", "application/json").get("content-type"), "application/json");
+});
diff --git a/homepage/app/lib/source.test.ts b/homepage/app/lib/source.test.ts
@@ -0,0 +1,71 @@
+import { test, after } from "node:test";
+import assert from "node:assert/strict";
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import os from "node:os";
+import path from "node:path";
+import { loadSourceManifest } from "./source";
+
+// Run with:
+// pnpm --filter homepage test
+//
+// The /source/ page's loader: the manifest, believed only when it parses AND
+// the mirror's info/refs and the tarball are beside it. Anything else is the
+// empty state — a bad file must never crash `next build` (review L10).
+
+const TMP = mkdtempSync(path.join(os.tmpdir(), "homepage-source-"));
+after(() => rmSync(TMP, { recursive: true, force: true }));
+
+const manifest = {
+ version: 1,
+ generatedAt: "2026-09-28T12:00:00.000Z",
+ branch: "main",
+ sourceCommit: "1".repeat(40),
+ mirrorHead: "2".repeat(40),
+ subject: "s",
+ files: 10,
+ bytes: 100,
+ mirror: { files: 9, bytes: 80, packs: 2 },
+ tree: { files: 5, dirs: 2, bytes: 20 },
+ tarball: { href: "/downloads/archilyzer-source.tar.gz", bytes: 7, sha256: "3".repeat(64) },
+ cloneUrl: "https://archilyzer.pages.dev/source/archilyzer.git",
+ treeHref: "/source/tree/",
+ audit: { objects: 3, commits: 1, gitleaks: "clean" },
+ tools: { git: "2.55.0", filterRepo: "x" },
+};
+
+let n = 0;
+function pub(opts: { manifest?: unknown; refs?: boolean; tarball?: boolean }): string {
+ const p = path.join(TMP, `p${n++}`);
+ mkdirSync(path.join(p, "source", "archilyzer.git", "info"), { recursive: true });
+ mkdirSync(path.join(p, "downloads"), { recursive: true });
+ if (opts.manifest !== undefined) {
+ writeFileSync(
+ path.join(p, "source", "manifest.json"),
+ typeof opts.manifest === "string" ? opts.manifest : JSON.stringify(opts.manifest),
+ );
+ }
+ if (opts.refs !== false) writeFileSync(path.join(p, "source", "archilyzer.git", "info", "refs"), "x\trefs/heads/main\n");
+ if (opts.tarball !== false) writeFileSync(path.join(p, "downloads", "archilyzer-source.tar.gz"), "t");
+ return p;
+}
+
+test("a good manifest with its files beside it is believed", () => {
+ assert.equal(loadSourceManifest(pub({ manifest }))?.mirrorHead, "2".repeat(40));
+});
+
+test("malformed, wrong-version or orphaned manifests are the empty state, never a throw", () => {
+ const cases: Array<[string, string]> = [
+ ["no manifest", pub({})],
+ ["not JSON", pub({ manifest: "{ half" })],
+ ["version 2", pub({ manifest: { ...manifest, version: 2 } })],
+ ["tree.files missing (the page reads it)", pub({ manifest: { ...manifest, tree: { dirs: 1, bytes: 1 } } })],
+ ["mirror.packs a string", pub({ manifest: { ...manifest, mirror: { ...manifest.mirror, packs: "2" } } })],
+ ["no info/refs", pub({ manifest, refs: false })],
+ ["no tarball", pub({ manifest, tarball: false })],
+ ["no public dir at all", path.join(TMP, "missing")],
+ ];
+ for (const [what, dir] of cases) {
+ assert.doesNotThrow(() => loadSourceManifest(dir), what);
+ assert.equal(loadSourceManifest(dir), null, what);
+ }
+});
diff --git a/homepage/app/lib/source.ts b/homepage/app/lib/source.ts
@@ -15,10 +15,14 @@ import {
//
// Believed only when the files it describes are here too (the snapshot.ts
// rule): a manifest beside a missing mirror or tarball would advertise a
-// clone that fails and a download that 404s.
-export function loadSourceManifest(): SourceManifest | null {
+// clone that fails and a download that 404s. A malformed or wrong-version
+// manifest is null too (parseSourceManifest checks every number the page
+// reads), so a bad file is the empty state, never a crash in `next build`.
+// `pub` is the test's seam.
+export function loadSourceManifest(
+ pub: string = path.join(process.cwd(), "public"),
+): SourceManifest | null {
try {
- const pub = path.join(process.cwd(), "public");
const manifest = parseSourceManifest(
JSON.parse(fs.readFileSync(path.join(pub, "source", "manifest.json"), "utf8")),
);
diff --git a/homepage/e2e/source.spec.ts b/homepage/e2e/source.spec.ts
@@ -10,11 +10,21 @@ import { test, expect, type Page } from "@playwright/test";
// This suite runs against `next dev`, which serves public/ from disk but does
// NOT serve a directory's index.html at `/<dir>/` (Pages does), and ignores
// _headers — so indexes are requested by name and content types are left to
-// the preview deploy's checks.
+// the preview deploy's checks (and to app/lib/headers.test.ts).
+//
+// E2E_EXPECT_SOURCE=1 (declared in playwright.config.ts) makes the empty state
+// a FAILURE of the four data tests, so a gate that published first cannot
+// pass with a publish that silently produced nothing, or a loader that always
+// says "empty".
+const EXPECT_SOURCE = process.env.E2E_EXPECT_SOURCE === "1";
async function published(page: Page): Promise<boolean> {
await page.goto("/source/");
- return (await page.getByTestId("source-clone").count()) > 0;
+ const has = (await page.getByTestId("source-clone").count()) > 0;
+ if (EXPECT_SOURCE) {
+ expect(has, "E2E_EXPECT_SOURCE=1, and /source/ shows its empty state: run `archilyzer source publish` first").toBe(true);
+ }
+ return has;
}
test("says what the source is, and that nothing here takes a push", async ({ page }) => {
diff --git a/homepage/playwright.config.ts b/homepage/playwright.config.ts
@@ -20,6 +20,10 @@ import {
// summary it reads instead of
// public/homepage-summary.json (app/lib/summary.ts,
// ignored by a production build)
+// E2E_EXPECT_SOURCE `1` (set by whoever runs the suite, after
+// `archilyzer source publish`): e2e/source.spec.ts
+// FAILS on the /source/ page's empty state instead
+// of accepting it
const PORT = portFor("HOMEPAGE_E2E_PORT");
const baseURL = `http://localhost:${PORT}`;