commit 9a22aec421d7f9b3222ce60f5370a7b9b8140544
parent 9aa6fc738f8a1cfd6ec88c837c9e9131a5967e27
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Mon, 28 Sep 2026 13:19:16 -0400
homepage: the /source/ page, a Source nav entry, _headers for the mirror and the raw tree
/source/ reads public/source/manifest.json (believed only when the mirror's
info/refs and the tarball are there too) and shows the clone command, the
mirror head, the private main it reflects, the tree and tarball links with the
sha256 — or an honest "No source published in this build." Downloads points at
the mirror for history and names `archilyzer source publish`; its Fact row is
shared. _headers serves the raw tree as text/plain + nosniff except the index
pages and the binaries. The header nav moves to `md` (five labels no longer fit
at `sm`). The docs that said "there is no public repository" (install, FAQ,
what-is, content/README) now say how to clone. source.spec.ts: five tests, two
states, like downloads.spec.ts; marketing.spec's nav list gains Source.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Diffstat:
14 files changed, 441 insertions(+), 48 deletions(-)
diff --git a/homepage/app/components/Fact.tsx b/homepage/app/components/Fact.tsx
@@ -0,0 +1,25 @@
+// One labelled fact in a page's fact list — the Downloads and Source pages'
+// rows. `testId` marks the value, for the specs that compare it with a file.
+export function Fact({
+ label,
+ children,
+ testId,
+}: {
+ label: string;
+ children: React.ReactNode;
+ testId?: string;
+}) {
+ return (
+ <div className="flex flex-col gap-1 border-t border-[var(--border)] py-3 sm:flex-row sm:gap-6">
+ <span className="label-machine sm:w-32 sm:shrink-0 sm:pt-0.5">
+ {label}
+ </span>
+ <span
+ data-testid={testId}
+ className="min-w-0 break-all text-sm text-[var(--muted-foreground)]"
+ >
+ {children}
+ </span>
+ </div>
+ );
+}
diff --git a/homepage/app/components/Header.tsx b/homepage/app/components/Header.tsx
@@ -27,7 +27,7 @@ function NavList({ className }: { className?: string }) {
);
}
-// The project site's header: a hairline bar with the wordmark and the four
+// The project site's header: a hairline bar with the wordmark and the five
// destinations. It carried NO links at all before this — the page it sat above
// was the whole site.
//
@@ -56,23 +56,23 @@ export default function Header() {
className="text-[1.3rem] leading-none tracking-[-0.01em]"
/>
</Link>
- <nav aria-label="Main" className="ml-auto hidden sm:block">
+ <nav aria-label="Main" className="ml-auto hidden md:block">
<NavList className="gap-6" />
</nav>
- <div className="ml-auto sm:ml-0 flex items-center gap-2">
+ <div className="ml-auto md:ml-0 flex items-center gap-2">
<ThemeMenu />
<ThemeToggle />
</div>
</div>
- {/* Below `sm` the bar has no room for four labels beside the wordmark and
- the theme controls, so the nav drops to its own scrollable rule rather
- than collapsing behind a menu button — four links do not earn a
- disclosure widget. Only one of the two is ever in the accessibility
+ {/* Below `md` the bar has no room for five labels beside the wordmark and
+ the theme controls (at `sm` four fitted; Source made it five), so the
+ nav drops to its own scrollable rule rather than collapsing behind a
+ menu button — five links do not earn a disclosure widget. Only one of the two is ever in the accessibility
tree (the other is display:none), but they carry distinct labels so a
test or a screen reader can never conflate them. */}
<nav
aria-label="Main, compact"
- className="sm:hidden border-t border-[var(--border)] overflow-x-auto"
+ className="md:hidden border-t border-[var(--border)] overflow-x-auto"
>
<NavList className="gap-5 px-5 h-10" />
</nav>
diff --git a/homepage/app/downloads/page.tsx b/homepage/app/downloads/page.tsx
@@ -1,6 +1,7 @@
import Link from "next/link";
import type { Metadata } from "next";
import { PageShell, PageHeading } from "../components/PageShell";
+import { Fact } from "../components/Fact";
import { loadSnapshot, formatBytes, SNAPSHOT_HREF } from "../lib/snapshot";
export const metadata: Metadata = {
@@ -9,19 +10,6 @@ export const metadata: Metadata = {
"Get the Archilyzer source as a dated snapshot tarball — MIT licensed, with a checksum.",
};
-function Fact({ label, children }: { label: string; children: React.ReactNode }) {
- return (
- <div className="flex flex-col gap-1 border-t border-[var(--border)] py-3 sm:flex-row sm:gap-6">
- <span className="label-machine sm:w-32 sm:shrink-0 sm:pt-0.5">
- {label}
- </span>
- <span className="min-w-0 break-all text-sm text-[var(--muted-foreground)]">
- {children}
- </span>
- </div>
- );
-}
-
export default function DownloadsPage() {
const snapshot = loadSnapshot();
const date = snapshot
@@ -50,6 +38,16 @@ export default function DownloadsPage() {
branches, nothing to <code className="font-mono">git pull</code>.
Updating means downloading a newer snapshot.
</p>
+ <p className="leading-[1.7] text-[var(--muted-foreground)]">
+ The history is in the{" "}
+ <Link
+ href="/source/"
+ className="underline decoration-[var(--border-strong)] underline-offset-2 hover:text-[var(--foreground)]"
+ >
+ read-only git mirror
+ </Link>
+ , published by the same build as this tarball.
+ </p>
</div>
{snapshot ? (
@@ -99,8 +97,9 @@ export default function DownloadsPage() {
</p>
<p className="text-sm text-[var(--faint)] leading-relaxed">
The tarball and its sidecar are build artefacts, generated by{" "}
- <code className="font-mono">./create-archives.sh</code> and not
- committed. A site built without running it ships no download, and
+ <code className="font-mono">archilyzer source publish</code> (which{" "}
+ <code className="font-mono">archilyzer build homepage</code> runs)
+ and not committed. A site built without it ships no download, and
says so here rather than linking to a file that isn’t there.
</p>
</div>
diff --git a/homepage/app/lib/nav.ts b/homepage/app/lib/nav.ts
@@ -1,13 +1,15 @@
// The site's navigation, declared once and rendered by both the header and the
-// footer. Order is editorial: what the software is, how to get it, what this
-// deployment has done, what changed.
+// footer. Order is editorial: what the software is, where its code lives, how
+// to get it, what this deployment has done, what changed.
//
// Every entry must resolve on a `build:nodata` tree — /stats/ renders an honest
-// no-data panel rather than 404ing — so the nav never has to be conditional.
+// no-data panel and /source/ an honest "nothing published" rather than 404ing —
+// so the nav never has to be conditional.
export type NavItem = { href: string; label: string };
export const NAV: NavItem[] = [
{ href: "/docs/", label: "Docs" },
+ { href: "/source/", label: "Source" },
{ href: "/downloads/", label: "Downloads" },
{ href: "/stats/", label: "Stats" },
{ href: "/changelog/", label: "Changelog" },
diff --git a/homepage/app/lib/snapshot.ts b/homepage/app/lib/snapshot.ts
@@ -1,8 +1,11 @@
import fs from "node:fs";
import path from "node:path";
-// Facts about the published source snapshot, written by create-archives.sh
-// beside the tarball it describes.
+// Facts about the published source snapshot, written by `archilyzer source
+// publish` (common/publish/source.ts) beside the tarball it describes. Since
+// release 12 the tarball is `git archive` of the scrubbed MIRROR's main, so
+// `commit` is a mirror id — the /source/ page names the private main it
+// reflects.
export type Snapshot = {
generatedAt: string;
commit: string;
@@ -20,7 +23,7 @@ export const SNAPSHOT_HREF = "/downloads/archilyzer-source.tar.gz";
//
// The null case is not hypothetical: the tarball and its sidecar are gitignored
// build artefacts, so a fresh unpack of the source has neither until
-// ./create-archives.sh runs. The download page MUST render no link at all in
+// `archilyzer source publish` runs (`archilyzer build homepage` runs it). The download page MUST render no link at all in
// that case — a link to a file that isn't there is worse than an explanation of
// why there isn't one.
export function loadSnapshot(): Snapshot | null {
diff --git a/homepage/app/lib/source.ts b/homepage/app/lib/source.ts
@@ -0,0 +1,32 @@
+import fs from "node:fs";
+import path from "node:path";
+import {
+ MIRROR_DIR,
+ TARBALL_HREF,
+ parseSourceManifest,
+ type SourceManifest,
+} from "yt-dlp-transcript-common/lib/sourceManifest";
+
+// The published source's manifest, written LAST by `archilyzer source publish`
+// (common/publish/source.ts) into public/source/, or null when this build has
+// none. The /source/ page renders an honest empty state for null — the mirror,
+// the tree and the tarball are gitignored build artefacts, so a fresh clone
+// (or `archilyzer build homepage --no-source`) has none of them.
+//
+// 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 {
+ try {
+ const pub = path.join(process.cwd(), "public");
+ const manifest = parseSourceManifest(
+ JSON.parse(fs.readFileSync(path.join(pub, "source", "manifest.json"), "utf8")),
+ );
+ if (!manifest) return null;
+ if (!fs.statSync(path.join(pub, "source", MIRROR_DIR, "info", "refs")).isFile()) return null;
+ if (!fs.statSync(path.join(pub, TARBALL_HREF.replace(/^\//, ""))).isFile()) return null;
+ return manifest;
+ } catch {
+ return null;
+ }
+}
diff --git a/homepage/app/source/page.tsx b/homepage/app/source/page.tsx
@@ -0,0 +1,182 @@
+import Link from "next/link";
+import type { Metadata } from "next";
+import { CLONE_URL, TREE_HREF } from "yt-dlp-transcript-common/lib/sourceManifest";
+import { PageShell, PageHeading } from "../components/PageShell";
+import { Fact } from "../components/Fact";
+import { loadSourceManifest } from "../lib/source";
+import { formatBytes } from "../lib/snapshot";
+
+export const metadata: Metadata = {
+ title: "Source",
+ description:
+ "The canonical public copy of Archilyzer's source: a read-only git mirror, its raw tree, and a tarball. MIT licensed.",
+};
+
+const linkClass =
+ "underline decoration-[var(--border-strong)] underline-offset-2 hover:text-[var(--foreground)]";
+
+// The published source (common/publish/source.ts): the mirror, the raw tree
+// and the tarball are build artefacts, so this page has two states and the
+// manifest decides which. A manifest is believed only when the files it
+// describes are here too (lib/source.ts): no clone command or link for a file
+// that isn't there.
+export default function SourcePage() {
+ const manifest = loadSourceManifest();
+ const date = manifest
+ ? new Date(manifest.generatedAt).toLocaleDateString(undefined, {
+ year: "numeric",
+ month: "long",
+ day: "numeric",
+ })
+ : null;
+
+ return (
+ <PageShell className="flex flex-col gap-10">
+ <PageHeading
+ eyebrow="Code"
+ title="Source"
+ standfirst="The canonical public copy: a read-only git mirror, its raw tree, and a tarball."
+ />
+
+ <div className="doc-measure flex flex-col gap-4">
+ <p className="leading-[1.7] text-[var(--muted-foreground)]">
+ There is no GitHub, by choice. This site is where the code lives in
+ public: a read-only mirror of the private main branch, regenerated
+ with every deploy of this site. Commit ids differ from the private
+ repository because paths were scrubbed on the way out — same history,
+ different hashes. Nothing here takes a push or a pull request.
+ </p>
+ </div>
+
+ {manifest ? (
+ <div className="flex flex-col gap-6">
+ <div className="doc-measure">
+ <p className="mb-2 label-machine">Clone it</p>
+ <pre
+ data-testid="source-clone"
+ className="p-4 rounded-[var(--radius)] bg-[var(--surface)] border border-[var(--border)] overflow-x-auto text-[0.8125rem] font-mono text-[var(--foreground)]"
+ >
+ {`git clone ${CLONE_URL}`}
+ </pre>
+ </div>
+
+ <div className="doc-measure flex flex-col">
+ <Fact label="Mirror head" testId="source-mirror-head">
+ <span className="tabular">{manifest.mirrorHead}</span>
+ </Fact>
+ <Fact label="Reflects">
+ private <code className="font-mono">main</code> at{" "}
+ <span className="tabular">{manifest.sourceCommit}</span>
+ </Fact>
+ <Fact label="Subject">{manifest.subject}</Fact>
+ <Fact label="Generated">{date}</Fact>
+ <Fact label="Size">
+ <span className="tabular">
+ {manifest.tree.files.toLocaleString()} files in the tree;{" "}
+ {formatBytes(manifest.mirror.bytes)} of history in{" "}
+ {manifest.mirror.packs} pack{manifest.mirror.packs === 1 ? "" : "s"}
+ </span>
+ </Fact>
+ </div>
+
+ <div className="doc-measure flex flex-col gap-3">
+ <p>
+ <a data-testid="source-tree-link" href={TREE_HREF} className={`text-sm ${linkClass}`}>
+ Browse the tree
+ </a>
+ <span className="text-sm text-[var(--muted-foreground)]">
+ {" "}— every tracked file of main, raw, as plain text.
+ </span>
+ </p>
+ <p className="text-sm text-[var(--muted-foreground)]">
+ <a data-testid="source-tarball-link" href={manifest.tarball.href} className={linkClass}>
+ archilyzer-source.tar.gz
+ </a>{" "}
+ <span className="tabular">
+ ({formatBytes(manifest.tarball.bytes)}, sha256{" "}
+ <span data-testid="source-tarball-sha" className="break-all">
+ {manifest.tarball.sha256}
+ </span>
+ )
+ </span>{" "}
+ — the same tree with no history; the{" "}
+ <Link href="/downloads/" className={linkClass}>
+ Downloads
+ </Link>{" "}
+ page says what is in it.
+ </p>
+ </div>
+ </div>
+ ) : (
+ // No manifest, or not every file it describes: say so and render NO
+ // clone command and no link, exactly like the Downloads page.
+ <div data-testid="source-empty" className="doc-measure faceplate p-5 flex flex-col gap-3">
+ <p className="text-[var(--muted-foreground)] leading-relaxed">
+ No source published in this build.
+ </p>
+ <p className="text-sm text-[var(--faint)] leading-relaxed">
+ The mirror, the raw tree and the tarball are build artefacts,
+ generated by{" "}
+ <code className="font-mono">archilyzer source publish</code> (which{" "}
+ <code className="font-mono">archilyzer build homepage</code> runs)
+ and not committed. A site built without it says so here rather than
+ offering a clone that would fail.
+ </p>
+ </div>
+ )}
+
+ <div className="doc-measure flex flex-col gap-8 border-t border-[var(--border)] pt-8">
+ <div className="flex flex-col gap-3">
+ <h2 className="font-display text-base font-semibold text-[var(--foreground)]">
+ What is mirrored
+ </h2>
+ <ul className="list-disc pl-5 space-y-2 text-sm leading-[1.7] text-[var(--muted-foreground)] marker:text-[var(--faint)]">
+ <li>
+ <strong className="text-[var(--foreground)]">The main branch, whole.</strong>{" "}
+ Every commit since the first, with its message and author, and
+ every tracked file. No other branches and no tags.
+ </li>
+ <li>
+ <strong className="text-[var(--foreground)]">Scrubbed on the way out.</strong>{" "}
+ Machine paths such as the home directory are rewritten to generic
+ ones, in files and in commit messages, which is why every id
+ differs from the private repository’s. The build refuses to
+ publish if anything it was told to remove survives, anywhere in
+ the history.
+ </li>
+ <li>
+ <strong className="text-[var(--foreground)]">Regenerated, not pushed to.</strong>{" "}
+ Each deploy rebuilds the mirror from the private main. The ids
+ stay put from one deploy to the next, but a change to the scrub
+ rules rewrites them all — if a{" "}
+ <code className="font-mono">git pull</code> ever refuses
+ unrelated history, clone again.
+ </li>
+ <li>
+ <strong className="text-[var(--foreground)]">Static files.</strong>{" "}
+ The clone uses git’s plain-HTTP protocol, straight from this
+ site’s files: slower than a forge, and a fetch downloads
+ whole packs, but there is no server to go down.
+ </li>
+ <li>
+ <strong className="text-[var(--foreground)]">Not the archive.</strong>{" "}
+ No transcripts, media, settings or dependencies — the same list
+ as the tarball’s.
+ </li>
+ </ul>
+ </div>
+
+ <div className="flex flex-col gap-3">
+ <h2 className="font-display text-base font-semibold text-[var(--foreground)]">
+ License
+ </h2>
+ <p className="text-sm leading-[1.7] text-[var(--muted-foreground)]">
+ MIT. Use it, change it, run it, publish with it. The full text is in{" "}
+ <code className="font-mono text-[var(--foreground)]">LICENSE</code>{" "}
+ at the root of the tree.
+ </p>
+ </div>
+ </div>
+ </PageShell>
+ );
+}
diff --git a/homepage/content/README.md b/homepage/content/README.md
@@ -9,9 +9,12 @@ exists for whoever edits the docs next.
The obvious move is to render `SETUP.md`, `PUBLISH.md` and friends
directly, and keep one copy. That was rejected for a decisive reason:
-> `SETUP.md` says `git clone <this-repo-url>`. **There is no public repository.**
-> Rendering that on a public site publishes an instruction that cannot be
-> followed — the acquisition path is a dated snapshot tarball from `/downloads/`.
+> `SETUP.md` was written for a contributor with the repository, and its
+> acquisition path is not the public one. The public copy is the read-only
+> mirror at `/source/archilyzer.git` (regenerated per deploy, with different
+> commit ids) and the tarball at `/downloads/`; `docs/install.md` says so in
+> an operator's terms. (Until release 12 there was no public repository at all,
+> which is when this rule was made.)
The root docs also lean on contributor furniture that actively confuses an
operator: `pnpm wt` worktrees, the machine-global e2e queue, `plans/`, shard
diff --git a/homepage/content/docs/faq.md b/homepage/content/docs/faq.md
@@ -78,9 +78,12 @@ gone* — never "confirmed still there".
## Where is the git repository?
-There isn't one. The source is published as a
-[dated snapshot tarball](/downloads/): a working tree with no history, no
-branches and no remote. Updating means downloading a newer snapshot.
+On this site, read-only: `git clone https://archilyzer.pages.dev/source/archilyzer.git`.
+It is the main branch with its whole history, regenerated from the private
+repository with every deploy — machine paths scrubbed on the way out, which is
+why its commit ids differ. There is no GitHub, and nothing takes a push or a
+pull request. [Source](/source/) also has every file to browse, and
+[Downloads](/downloads/) the same tree as a tarball.
## Can I use it for one video?
@@ -92,7 +95,8 @@ exists because of scale.
## What this is not
- **Not a hosted service.** There is no account, no subscription, no upload.
-- **Not a public repository.** Dated snapshots, no `git pull`.
+- **Not a forge.** A read-only mirror: clone and pull, but no pushes, issues or
+ pull requests.
- **Not a downloader.** It supplies no downloader — you install `yt-dlp`,
`ffmpeg` and a transcription backend yourself, and it drives them.
- **Not cloud transcription.** Your hardware, your electricity, your queue.
diff --git a/homepage/content/docs/install.md b/homepage/content/docs/install.md
@@ -1,8 +1,8 @@
# Install
-From an unpacked snapshot to a running editor. The web apps need very little; the
-download-and-transcribe pipeline needs the media tools, and you can add those
-later.
+From a clone (or an unpacked snapshot) to a running editor. The web apps need
+very little; the download-and-transcribe pipeline needs the media tools, and you
+can add those later.
## What you need
@@ -29,8 +29,19 @@ can't fetch anything yet.
## Get the code
-There is no public git repository. The source is published here as a dated
-snapshot tarball:
+The source lives on this site as a read-only git mirror of the main branch —
+there is no GitHub. Clone it:
+
+```sh
+git clone https://archilyzer.pages.dev/source/archilyzer.git archilyzer
+cd archilyzer
+```
+
+`git pull` brings you up to date; nothing takes a push. The commit ids differ
+from the private repository's, because machine paths are scrubbed on the way
+out. [Source](/source/) has the details and a browsable copy of every file.
+
+No git? The same tree, without history, is a tarball:
```sh
curl -LO https://archilyzer.pages.dev/downloads/archilyzer-source.tar.gz
@@ -38,9 +49,8 @@ tar xzf archilyzer-source.tar.gz
cd archilyzer
```
-That is a working tree, not a clone — no history, no branches, no remote,
-nothing to `git pull`. Updating means downloading a newer snapshot. See
-[Downloads](/downloads/) for what is and isn't inside.
+That is a working tree, not a clone. Updating means downloading a newer
+snapshot. See [Downloads](/downloads/) for what is and isn't inside.
## Install and start
@@ -122,8 +132,8 @@ up rather than looking healthy and quietly transcribing nothing.
`127.0.0.1` — the published archive included — so opening one up is a deliberate
one-line edit in `.env`. The editor has no login of its own, so the containers
refuse to start if you expose *it* without putting a password or an identity
-provider in front; the error says how. `RUNNING_IN_DOCKER.md`, in the snapshot
-you just unpacked, covers that, GPU transcription, and backups.
+provider in front; the error says how. `RUNNING_IN_DOCKER.md`, in the tree you
+just unpacked, covers that, GPU transcription, and backups.
The same stack runs on Linux and macOS. It is described here because it is the
one place where it is clearly the *better* option, not because it is
diff --git a/homepage/content/docs/what-is-archilyzer.md b/homepage/content/docs/what-is-archilyzer.md
@@ -60,8 +60,8 @@ Steps 1–3 can run unattended on a schedule. See
## What it is not
- Not a hosted service. You install it, you run it, you pay for your own hosting.
-- Not a public repository. The source ships as a
- [dated snapshot](/downloads/) — there is no `git clone` and nothing to pull.
+- Not a forge. The source is a [read-only git mirror](/source/) on this site —
+ clone and pull, but nothing takes a push or a pull request.
- Not a downloader you point at one video. It is built around back catalogues:
thousands of recordings, kept current.
- Not automatic transcription in the cloud. The transcribing happens on your
diff --git a/homepage/e2e/marketing.spec.ts b/homepage/e2e/marketing.spec.ts
@@ -42,6 +42,7 @@ test("every nav destination resolves", async ({ page }) => {
// data — which is why /stats/ always exists and degrades in place.
for (const [label, path] of [
["Docs", "/docs/"],
+ ["Source", "/source/"],
["Downloads", "/downloads/"],
["Stats", "/stats/"],
["Changelog", "/changelog/"],
diff --git a/homepage/e2e/source.spec.ts b/homepage/e2e/source.spec.ts
@@ -0,0 +1,102 @@
+import { test, expect, type Page } from "@playwright/test";
+
+// The /source/ page and the files it points at (common/publish/source.ts).
+// Like downloads.spec.ts, correct in BOTH states: the mirror, the raw tree and
+// the tarball are gitignored build artefacts, so a fresh checkout has none and
+// the page must say so rather than offer a clone that fails. The release gate
+// runs `archilyzer source publish` in the checkout first, so the published
+// branch is the one exercised there.
+//
+// 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.
+
+async function published(page: Page): Promise<boolean> {
+ await page.goto("/source/");
+ return (await page.getByTestId("source-clone").count()) > 0;
+}
+
+test("says what the source is, and that nothing here takes a push", async ({ page }) => {
+ await page.goto("/source/");
+ await expect(page.getByRole("heading", { level: 1 })).toContainText("Source");
+ await expect(page.getByText(/There is no GitHub, by choice\./)).toBeVisible();
+ await expect(page.getByText(/Nothing here takes a push or a pull request\./)).toBeVisible();
+ await expect(page.getByRole("heading", { name: "What is mirrored" })).toBeVisible();
+ await expect(page.getByRole("heading", { name: "License" })).toBeVisible();
+});
+
+test("with a manifest: the clone command, and one tarball the page, snapshot.json and manifest.json agree on — else the empty state", async ({ page }) => {
+ if (!(await published(page))) {
+ await expect(page.getByTestId("source-empty")).toContainText("No source published in this build.");
+ await expect(page.getByTestId("source-tree-link")).toHaveCount(0);
+ await expect(page.getByTestId("source-tarball-link")).toHaveCount(0);
+ return;
+ }
+ await expect(page.getByTestId("source-clone")).toHaveText(
+ "git clone https://archilyzer.pages.dev/source/archilyzer.git",
+ );
+ const manifest = await (await page.request.get("/source/manifest.json")).json();
+ const snapshot = await (await page.request.get("/downloads/snapshot.json")).json();
+ await expect(page.getByTestId("source-mirror-head")).toHaveText(manifest.mirrorHead);
+ const href = await page.getByTestId("source-tarball-link").getAttribute("href");
+ expect(href).toBe("/downloads/archilyzer-source.tar.gz");
+ const tarball = await page.request.get(href!);
+ expect(tarball.status()).toBe(200);
+ expect((await tarball.body()).length).toBe(manifest.tarball.bytes);
+ const onPage = (await page.getByTestId("source-tarball-sha").textContent())?.trim();
+ expect(onPage).toMatch(/^[0-9a-f]{64}$/);
+ expect(onPage).toBe(manifest.tarball.sha256);
+ expect(onPage).toBe(snapshot.sha256);
+ expect(snapshot.commit).toBe(manifest.mirrorHead);
+});
+
+test("the mirror is a dumb-HTTP git repository: HEAD, info/refs, objects/info/packs", async ({ page }) => {
+ if (!(await published(page))) {
+ await expect(page.getByTestId("source-empty")).toBeVisible();
+ return;
+ }
+ const manifest = await (await page.request.get("/source/manifest.json")).json();
+ const head = await page.request.get("/source/archilyzer.git/HEAD");
+ expect(head.status()).toBe(200);
+ expect(await head.text()).toBe("ref: refs/heads/main\n");
+ const refs = await page.request.get("/source/archilyzer.git/info/refs");
+ expect(await refs.text()).toContain(`${manifest.mirrorHead}\trefs/heads/main`);
+ const packs = await (await page.request.get("/source/archilyzer.git/objects/info/packs")).text();
+ const first = /^P (pack-[0-9a-f]+\.pack)$/m.exec(packs);
+ expect(first, packs).not.toBeNull();
+ const pack = await page.request.head(`/source/archilyzer.git/objects/pack/${first![1]}`);
+ expect(pack.status()).toBe(200);
+});
+
+test("the raw tree: an index per directory, encoded hrefs that resolve, brackets included", async ({ page }) => {
+ if (!(await published(page))) {
+ await expect(page.getByTestId("source-empty")).toBeVisible();
+ return;
+ }
+ const hrefs = (html: string) => [...html.matchAll(/<td class="name"><a href="([^"]+)">/g)].map((m) => m[1]);
+ const root = await page.request.get("/source/tree/index.html");
+ expect(root.status()).toBe(200);
+ const rootHrefs = hrefs(await root.text());
+ const dir = rootHrefs.find((h) => h === "common/");
+ expect(dir, rootHrefs.join(" ")).toBeTruthy();
+ expect((await page.request.get(`/source/tree/${dir}index.html`)).status()).toBe(200);
+ const file = rootHrefs.find((h) => h === "README.md");
+ expect(file).toBeTruthy();
+ const readme = await page.request.get(`/source/tree/${file}`);
+ expect(readme.status()).toBe(200);
+ expect(await readme.text()).toContain("Archilyzer");
+
+ // A variable font's name carries brackets: encoded in the href, and served.
+ const fonts = await page.request.get("/source/tree/umtool/report-to-video/fonts/index.html");
+ expect(fonts.status()).toBe(200);
+ const bracketed = hrefs(await fonts.text()).find((h) => h.includes("%5B"));
+ expect(bracketed).toBeTruthy();
+ const font = await page.request.head(`/source/tree/umtool/report-to-video/fonts/${bracketed}`);
+ expect(font.status()).toBe(200);
+});
+
+test("the Downloads page points at the mirror for the history", async ({ page }) => {
+ await page.goto("/downloads/");
+ await expect(page.getByRole("link", { name: "read-only git mirror" })).toHaveAttribute("href", "/source/");
+});
diff --git a/homepage/public/_headers b/homepage/public/_headers
@@ -6,3 +6,33 @@
# for hours while still absorbing a burst of downloads.
/downloads/*
Cache-Control: public, max-age=300, must-revalidate
+
+# The published source (`archilyzer source publish`, common/publish/source.ts).
+/source/manifest.json
+ Cache-Control: public, max-age=300, must-revalidate
+/source/archilyzer.git/*
+ Cache-Control: public, max-age=300, must-revalidate
+# The raw tree is PLAIN TEXT whatever the extension: wrangler's mime map would
+# send .ts as video/mp2t, .mjs as application/javascript, .md as text/markdown
+# and .html as a page on this origin.
+/source/tree/*
+ Content-Type: text/plain; charset=utf-8
+ X-Content-Type-Options: nosniff
+ X-Robots-Tag: noindex
+ Cache-Control: public, max-age=300, must-revalidate
+# ...except the generated directory indexes and the binary files.
+/source/tree/
+ Content-Type: text/html; charset=utf-8
+/source/tree/*/
+ Content-Type: text/html; charset=utf-8
+/source/tree/*.ttf
+ Content-Type: font/ttf
+/source/tree/*.m4a
+ Content-Type: audio/mp4
+/source/tree/*.zip
+ Content-Type: application/zip
+/source/tree/*.onnx
+ Content-Type: application/octet-stream
+/source/tree/*.svg
+ Content-Type: image/svg+xml
+ Content-Security-Policy: default-src 'none'; style-src 'unsafe-inline'