import { test } from "node:test"; import assert from "node:assert/strict"; import { existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, symlinkSync, writeFileSync, } from "node:fs"; import { tmpdir } from "node:os"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { getPaths, type Paths } from "../lib/paths"; import { CLOUDFLARE_AUTH_REFUSED } from "../lib/pagesDeploy"; import { readDeployedFile as readDeployed, type DeployedFile } from "./stamps"; import { DeployStageError, runDeployStage, type BuiltStamp, type DeployStageContext, type DeployStageRequest, } from "./deployStage"; // Run with: pnpm --filter yt-dlp-transcript-common test // // The deploy stage over a temp tree, spawning the e2e FAKE wrangler // (editor/e2e/fixtures/bin/fake-wrangler.mjs) as WRANGLER_BIN — the same // binary the editor suite deploys through — and an injected fetch for the live // check. Nothing here can reach Cloudflare: the env handed to the stage is // built from scratch, and the fake deploys nothing. const FAKE_WRANGLER = path.resolve( path.dirname(fileURLToPath(import.meta.url)), "..", "..", "editor", "e2e", "fixtures", "bin", "fake-wrangler.mjs", ); const GENERATED = "2026-10-06T09:00:00.000Z"; type Fixture = { root: string; paths: Paths; home: string; cleanup: () => void }; function fixture(): Fixture { const root = mkdtempSync(path.join(tmpdir(), "deploy-stage-")); const home = path.join(root, "home"); mkdirSync(home); const sitesDir = path.join(root, "sites"); const paths: Paths = { ...getPaths(), monorepoRoot: root, // Never the checkout's corpus: the local-destination guard resolves it. transcriptsDir: path.join(root, "transcripts"), exportDir: path.join(root, "export"), exportBuildsDir: path.join(root, "builds"), sitesDir, homepageDir: path.join(sitesDir, "_homepage"), homepageConfigFile: path.join(sitesDir, "_homepage", "homepage.json"), }; mkdirSync(paths.exportDir, { recursive: true }); return { root, paths, home, cleanup: () => rmSync(root, { recursive: true, force: true }) }; } const writeJson = (file: string, value: unknown) => { mkdirSync(path.dirname(file), { recursive: true }); writeFileSync(file, JSON.stringify(value)); }; function site(fx: Fixture, siteId: string, extra: Record = {}) { writeJson(path.join(fx.paths.sitesDir, siteId, "site.json"), { siteId, siteTitle: siteId, cloudflareProject: `${siteId}-proj`, siteUrl: `https://${siteId}.example.test`, channels: [], ...extra, }); } // Every field S1's strict built.json reader asks for. function stampFields(target: string, kind: BuiltStamp["kind"]): BuiltStamp { return { v: 1, stampId: `built-${target}-1`, target, kind, indexStampId: "idx-1", inputSig: "sig", builtAt: Date.parse("2026-10-06T09:30:00.000Z"), commit: null, branch: "main", runner: "local", audience: "public", corpusGeneratedAt: GENERATED, files: 3, bytes: 100, archivesStaged: 0, }; } // A built bundle under //out and its built.json. function built( fx: Fixture, target: string, opts: { bundleOf?: string; stamp?: Partial; corpus?: Record } = {}, ): BuiltStamp { const dir = path.join(fx.paths.exportBuildsDir, target); const out = path.join(dir, "out"); mkdirSync(out, { recursive: true }); const id = opts.bundleOf ?? target; writeJson(path.join(out, "site.json"), { siteId: id }); writeJson(path.join(out, "corpus.json"), { generatedAt: GENERATED, site: { id }, ...opts.corpus }); writeFileSync(path.join(out, "index.html"), ""); const stamp: BuiltStamp = { v: 1, stampId: `built-${target}-1`, target, kind: "site", indexStampId: "idx-1", inputSig: "sig", builtAt: Date.parse("2026-10-06T09:30:00.000Z"), commit: null, branch: "main", runner: "local", audience: "public", corpusGeneratedAt: GENERATED, files: 3, bytes: 100, archivesStaged: 0, ...opts.stamp, }; writeJson(path.join(dir, "built.json"), stamp); return stamp; } // A live check that finds the build live: every URL answers this build's // corpus.json (or a tombstone body for a posts path), and records each URL. function liveFetch(asked: string[], generatedAt = GENERATED): typeof fetch { return (async (input: string | URL | Request) => { const url = String(input); asked.push(url); const body = url.includes("/posts/") ? url.includes("page-") ? [] : url.includes("/posts/manifest.json") ? { channels: [] } : { pageCount: 0 } : { generatedAt }; return new Response(JSON.stringify(body), { status: 200 }); }) as typeof fetch; } function ctx( fx: Fixture, env: Record, extra: Partial = {}, ): DeployStageContext & { lines: string[]; asked: string[]; uploads: string[] } { const lines: string[] = []; const asked: string[] = []; const uploads: string[] = []; return { paths: fx.paths, onLog: (l) => lines.push(l), signal: new AbortController().signal, env: { PATH: process.env.PATH, WRANGLER_BIN: FAKE_WRANGLER, ...env }, home: fx.home, now: () => new Date("2026-10-06T10:00:00.000Z"), liveCheck: { fetch: liveFetch(asked), sleep: async () => {} }, uploadArchives: async (_s, dir) => { uploads.push(dir); return 0; }, lines, asked, uploads, ...extra, }; } const TOKEN = { CLOUDFLARE_API_TOKEN: "test-token-not-real" }; function sidecar(fx: Fixture, target: string): { argv: string[]; branch: string }[] { const file = path.join(fx.paths.exportBuildsDir, target, ".fake-wrangler.json"); return existsSync(file) ? JSON.parse(readFileSync(file, "utf8")).invocations : []; } function deployedBytes(fx: Fixture, target: string): string | null { const file = path.join(fx.paths.exportBuildsDir, target, "deployed.json"); return existsSync(file) ? readFileSync(file, "utf8") : null; } // S1's reader: deployed.json, or null when there is none (or it is malformed). async function deployedOf(fx: Fixture, target: string): Promise { const f = await readDeployed(fx.paths, target); assert.ok(f, `no deployed.json for ${target}`); return f; } async function refused( p: Promise, exitCode: number, why: RegExp, ): Promise { await assert.rejects(p, (err: unknown) => { assert.ok(err instanceof DeployStageError, String(err)); assert.equal(err.exitCode, exitCode, err.message); assert.match(err.message, why); return true; }); } test("a preview deploy runs the pinned binary with --branch , checks the alias live and records previews[b]", async () => { const fx = fixture(); try { site(fx, "anilyzer"); const stamp = built(fx, "anilyzer"); const c = ctx(fx, TOKEN); const req: DeployStageRequest = { kind: "deploy-site", target: "anilyzer", preview: "r18" }; const out = await runDeployStage(c, req); assert.equal(out.status, "ran"); assert.equal(out.stamp, stamp.stampId); assert.match(out.summary, /deployed to preview "r18" \(anilyzer-proj\) — live check: ok\.$/); const outDir = path.join(fx.paths.exportBuildsDir, "anilyzer", "out"); assert.deepEqual(sidecar(fx, "anilyzer").map((i) => i.argv), [ ["pages", "deploy", outDir, "--project-name", "anilyzer-proj", "--branch", "r18"], ]); // The archives went first, from the per-site staging dir. assert.deepEqual(c.uploads, [path.join(fx.paths.exportBuildsDir, "anilyzer", ".r2-staging", "anilyzer", "archives")]); assert.ok(c.lines.includes("[preview] https://r18.anilyzer-proj.pages.dev (this deployment: https://r18.anilyzer-proj.pages.dev)\n"), c.lines.join("")); // The live check read the ALIAS, plain and cache-busted. assert.deepEqual(c.asked, [ "https://r18.anilyzer-proj.pages.dev/corpus.json", `https://r18.anilyzer-proj.pages.dev/corpus.json?cb=${stamp.stampId}`, ]); const rec = (await deployedOf(fx, "anilyzer")); assert.equal(rec.v, 1); assert.equal(rec.production, undefined); const p = rec.previews.r18; assert.equal(p.builtStampId, stamp.stampId); assert.equal(p.builtAt, stamp.builtAt); assert.equal(p.kind, "preview"); assert.equal(p.branch, "r18"); assert.equal(p.alias, "https://r18.anilyzer-proj.pages.dev"); assert.equal(p.url, "https://r18.anilyzer-proj.pages.dev"); assert.equal(p.at, Date.parse("2026-10-06T10:00:00.000Z")); assert.match(p.wrangler ?? "", /^WRANGLER_BIN=/); assert.equal(p.liveCheck?.verdict, "ok"); assert.equal(p.liveCheck?.expected, GENERATED); // The same build to the same slot again: a no-op, no second spawn. const again = await runDeployStage(ctx(fx, TOKEN), req); assert.equal(again.status, "noop"); assert.equal(sidecar(fx, "anilyzer").length, 1); // …unless forced. assert.equal((await runDeployStage(ctx(fx, TOKEN), { ...req, force: true })).status, "ran"); assert.equal(sidecar(fx, "anilyzer").length, 2); } finally { fx.cleanup(); } }); test("production is --branch main, checked at the site's public URL, recorded beside the previews", async () => { const fx = fixture(); try { site(fx, "jeralyzer"); built(fx, "jeralyzer"); writeJson(path.join(fx.paths.exportBuildsDir, "jeralyzer", "deployed.json"), { v: 1, target: "jeralyzer", previews: { old: { builtStampId: "x", builtAt: 1, kind: "preview", branch: "old", url: null, at: 1, liveCheck: null } }, }); const c = ctx(fx, TOKEN); await runDeployStage(c, { kind: "deploy-site", target: "jeralyzer" }); assert.deepEqual(sidecar(fx, "jeralyzer")[0].argv.slice(-2), ["--branch", "main"]); assert.equal(c.asked[0], "https://jeralyzer.example.test/corpus.json"); const rec = (await deployedOf(fx, "jeralyzer")); assert.equal(rec.production?.kind, "production"); assert.equal(rec.production?.branch, undefined); assert.equal(rec.production?.url, "https://main.jeralyzer-proj.pages.dev"); assert.ok(rec.previews.old, "the previews already recorded are kept"); assert.ok(c.lines.includes("[deployed] https://main.jeralyzer-proj.pages.dev\n")); } finally { fx.cleanup(); } }); test("every refusal leaves deployed.json untouched, and wrangler unspawned", async () => { const fx = fixture(); try { site(fx, "anilyzer"); site(fx, "mine", { audience: "private" }); site(fx, "noproj", { cloudflareProject: "" }); built(fx, "anilyzer"); built(fx, "mine"); built(fx, "noproj"); const before = JSON.stringify({ v: 1, target: "anilyzer", previews: {} }, null, 2); writeFileSync(path.join(fx.paths.exportBuildsDir, "anilyzer", "deployed.json"), before); const cases: [string, DeployStageRequest, Record, number, RegExp][] = [ ["no credential", { kind: "deploy-site", target: "anilyzer" }, {}, 1, /set CLOUDFLARE_API_TOKEN in \.env/], ["a production branch as preview", { kind: "deploy-site", target: "anilyzer", preview: "main" }, TOKEN, 2, /is the production branch/], ["the hub's target as a site", { kind: "deploy-site", target: "_hub" }, TOKEN, 2, /"_hub" is not a site — deploy it with deploy-hub/], ["a private site", { kind: "deploy-site", target: "mine" }, TOKEN, 3, /is private \(audience: private\)/], ["no Pages project", { kind: "deploy-site", target: "noproj" }, TOKEN, 3, /no Cloudflare Pages project configured/], ["never built", { kind: "deploy-site", target: "nobuild" }, TOKEN, 3, /no build of nobuild in .*builds\/nobuild — archilyzer publish build nobuild/], ["local and preview at once", { kind: "deploy-site", target: "anilyzer", to: "local", preview: "p" }, TOKEN, 2, /a local deploy has no preview branch/], ["local with nowhere to copy", { kind: "deploy-site", target: "anilyzer", to: "local" }, TOKEN, 3, /^\[deploy\] REFUSED — --to local needs ARCHILYZER_SITE_OUT/], ]; site(fx, "nobuild"); for (const [name, req, env, code, why] of cases) { const c = ctx(fx, env); await refused(runDeployStage(c, req), code, why); assert.match(c.lines.at(-1) ?? "", /^\[deploy\] REFUSED/, name); assert.equal(deployedBytes(fx, "anilyzer"), before, `${name}: deployed.json changed`); assert.equal(deployedBytes(fx, "mine"), null, name); assert.deepEqual(sidecar(fx, req.target), [], `${name}: wrangler was spawned`); assert.deepEqual(c.asked, [], `${name}: a live check ran`); } } finally { fx.cleanup(); } }); test("Cloudflare refusing the token: wrangler's error becomes the REFUSED sentence; deployed.json untouched, exit 1", async () => { const fx = fixture(); try { site(fx, "anilyzer"); built(fx, "anilyzer"); const c = ctx(fx, { ...TOKEN, E2E_FAKE_WRANGLER_AUTH_FAIL: "1" }); await refused(runDeployStage(c, { kind: "deploy-site", target: "anilyzer" }), 1, /^\[deploy\] REFUSED by Cloudflare — the API token was not accepted$/); assert.ok(c.lines.some((l) => l.includes("Authentication error [code: 10000]")), "wrangler's own line is in the log"); assert.equal(c.lines.at(-1), `${CLOUDFLARE_AUTH_REFUSED}\n`); assert.equal(deployedBytes(fx, "anilyzer"), null); assert.deepEqual(c.asked, []); } finally { fx.cleanup(); } }); test("a wrangler OAuth login on disk passes the preflight with no token", async () => { const fx = fixture(); try { site(fx, "anilyzer"); built(fx, "anilyzer"); writeJson(path.join(fx.home, ".config", ".wrangler", "config", "default.toml"), "oauth_token = \"x\""); const out = await runDeployStage(ctx(fx, {}), { kind: "deploy-site", target: "anilyzer", preview: "p" }); assert.equal(out.status, "ran"); } finally { fx.cleanup(); } }); test("production ships only a build of main; the same build may go to a preview", async () => { const fx = fixture(); try { site(fx, "anilyzer"); built(fx, "anilyzer", { stamp: { branch: "r18/feature" } }); const c = ctx(fx, TOKEN); await refused( runDeployStage(c, { kind: "deploy-site", target: "anilyzer" }), 3, /made from branch "r18\/feature", not main: production ships only a build of main/, ); assert.equal(deployedBytes(fx, "anilyzer"), null); assert.equal((await runDeployStage(ctx(fx, TOKEN), { kind: "deploy-site", target: "anilyzer", preview: "feat" })).status, "ran"); // No branch recorded (a detached HEAD, an image built without // ARCHILYZER_BRANCH) is refused for production the same way (S1's rule). site(fx, "jasolyzer"); built(fx, "jasolyzer", { stamp: { branch: null } }); await refused( runDeployStage(ctx(fx, TOKEN), { kind: "deploy-site", target: "jasolyzer" }), 3, /has no branch recorded .*production ships only a build of main/, ); } finally { fx.cleanup(); } }); test("the bundle guards: another site's bundle and a private build are refused before wrangler", async () => { const fx = fixture(); try { site(fx, "anilyzer"); built(fx, "anilyzer", { bundleOf: "jeralyzer" }); await refused( runDeployStage(ctx(fx, TOKEN), { kind: "deploy-site", target: "anilyzer" }), 3, /holds a build of "jeralyzer", not "anilyzer" \(site\.json\)\. Nothing was sent to Cloudflare Pages/, ); site(fx, "bonnellyzer"); built(fx, "bonnellyzer", { corpus: { site: { id: "bonnellyzer", audience: "private" } } }); await refused( runDeployStage(ctx(fx, TOKEN), { kind: "deploy-site", target: "bonnellyzer" }), 3, /private build of "bonnellyzer"/, ); assert.deepEqual(sidecar(fx, "anilyzer"), []); assert.deepEqual(sidecar(fx, "bonnellyzer"), []); } finally { fx.cleanup(); } }); test("--to local copies the bundle into ARCHILYZER_SITE_OUT (its contents replaced) and records local", async () => { const fx = fixture(); try { site(fx, "anilyzer"); const stamp = built(fx, "anilyzer"); const dest = path.join(fx.root, "site-out"); mkdirSync(dest); // What an earlier local deploy (or the container's placeholder) left. writeFileSync(path.join(dest, "index.html"), "old"); writeFileSync(path.join(dest, "stale.html"), "old"); const c = ctx(fx, { ARCHILYZER_SITE_OUT: dest }); const out = await runDeployStage(c, { kind: "deploy-site", target: "anilyzer", to: "local" }); assert.equal(out.status, "ran"); assert.deepEqual(readdirSync(dest).sort(), ["corpus.json", "index.html", "site.json"]); assert.deepEqual(sidecar(fx, "anilyzer"), [], "no wrangler"); assert.deepEqual(c.uploads, [], "no R2"); assert.deepEqual(c.asked, [], "no live check"); const rec = (await deployedOf(fx, "anilyzer")); assert.equal(rec.local?.builtStampId, stamp.stampId); assert.equal(rec.local?.kind, "local"); assert.equal(rec.local?.liveCheck, null); // A private site is still refused locally. site(fx, "mine", { audience: "private" }); built(fx, "mine"); await refused( runDeployStage(ctx(fx, { ARCHILYZER_SITE_OUT: dest }), { kind: "deploy-site", target: "mine", to: "local" }), 3, /is private/, ); assert.ok(existsSync(path.join(dest, "site.json"))); // A destination that does not look like a bundle the copy made is not // emptied: non-empty with no index.html, or one holding the checkout. const notOurs = path.join(fx.root, "somebody-else"); mkdirSync(notOurs); writeFileSync(path.join(notOurs, "notes.txt"), "keep"); await refused( runDeployStage(ctx(fx, { ARCHILYZER_SITE_OUT: notOurs }), { kind: "deploy-site", target: "anilyzer", to: "local", force: true }), 1, /somebody-else is not empty and holds no index\.html/, ); assert.deepEqual(readdirSync(notOurs), ["notes.txt"]); await refused( runDeployStage(ctx(fx, { ARCHILYZER_SITE_OUT: fx.root }), { kind: "deploy-site", target: "anilyzer", to: "local", force: true }), 1, /holds the checkout/, ); assert.ok(existsSync(path.join(fx.root, "sites", "anilyzer", "site.json")), "nothing was emptied"); // export/out is a LINK to the bundle built last (S1): resolved, it is the // bundle itself, inside the builds dir — refused, the bundle untouched. const bundle = path.join(fx.paths.exportBuildsDir, "anilyzer", "out"); symlinkSync(bundle, path.join(fx.paths.exportDir, "out")); const bundleBefore = readdirSync(bundle).sort(); await refused( runDeployStage(ctx(fx, { ARCHILYZER_SITE_OUT: path.join(fx.paths.exportDir, "out") }), { kind: "deploy-site", target: "anilyzer", to: "local", force: true, }), 1, /export\/out (holds|is inside) /, ); assert.deepEqual(readdirSync(bundle).sort(), bundleBefore); // …and so is any directory inside export/, the builds dir or the corpus. for (const [inner, what] of [ [path.join(fx.paths.exportDir, "site-out"), "export/"], [path.join(fx.paths.exportBuildsDir, "site"), "the builds directory"], [path.join(fx.paths.transcriptsDir, "site"), "the corpus"], ]) { await refused( runDeployStage(ctx(fx, { ARCHILYZER_SITE_OUT: inner }), { kind: "deploy-site", target: "anilyzer", to: "local", force: true }), 1, new RegExp(`is inside ${what.replace("/", "\\/")}`), ); assert.equal(existsSync(inner), false, `${inner} was made`); } } finally { fx.cleanup(); } }); test("the hub's and the homepage's targets are fixed: a mismatch is a usage error and writes nothing", async () => { const fx = fixture(); try { site(fx, "jeralyzer"); built(fx, "jeralyzer"); const before = deployedBytes(fx, "jeralyzer"); for (const req of [ { kind: "deploy-homepage", target: "jeralyzer" }, { kind: "deploy-hub", target: "jeralyzer" }, { kind: "deploy-homepage", target: "_hub" }, ] as DeployStageRequest[]) { const c = ctx(fx, TOKEN); await refused(runDeployStage(c, req), 2, new RegExp(`^\\[deploy\\] REFUSED — ${req.kind} deploys "_(hub|homepage)", not "${req.target}"\\.$`)); assert.deepEqual(c.asked, []); } assert.equal(deployedBytes(fx, "jeralyzer"), before); assert.equal(deployedBytes(fx, "jeralyzer"), null); assert.deepEqual(sidecar(fx, "jeralyzer"), []); } finally { fx.cleanup(); } }); test("wrangler's own lines reach the log as lines, each ending in a newline", async () => { const fx = fixture(); try { site(fx, "anilyzer"); built(fx, "anilyzer"); const c = ctx(fx, TOKEN); await runDeployStage(c, { kind: "deploy-site", target: "anilyzer" }); assert.ok(c.lines.some((l) => l.startsWith("✨ Deployment complete!")), c.lines.join("")); for (const l of c.lines) assert.ok(l.endsWith("\n"), JSON.stringify(l)); } finally { fx.cleanup(); } }); test("an R2 failure throws the line it logged, and nothing is spawned or recorded", async () => { const fx = fixture(); try { site(fx, "anilyzer"); built(fx, "anilyzer"); const c = ctx(fx, TOKEN, { uploadArchives: async () => 7 }); await refused( runDeployStage(c, { kind: "deploy-site", target: "anilyzer" }), 1, /^\[deploy\] FAILED — the archive R2 upload exited 7; nothing was sent to Cloudflare Pages\.$/, ); assert.equal(c.lines.at(-1), "[deploy] FAILED — the archive R2 upload exited 7; nothing was sent to Cloudflare Pages.\n"); assert.deepEqual(sidecar(fx, "anilyzer"), []); assert.equal(deployedBytes(fx, "anilyzer"), null); } finally { fx.cleanup(); } }); test("Cancel during the live check: the deploy is recorded without a check, and the stage ends cancelled", async () => { const fx = fixture(); try { site(fx, "anilyzer"); const stamp = built(fx, "anilyzer"); const cancel = new AbortController(); const c = ctx(fx, TOKEN, { signal: cancel.signal, liveCheck: { sleep: async () => {}, fetch: (async () => { cancel.abort(); return new Response(JSON.stringify({ generatedAt: GENERATED }), { status: 200 }); }) as typeof fetch, }, }); await refused(runDeployStage(c, { kind: "deploy-site", target: "anilyzer" }), 130, /cancelled during the live check/); const rec = (await deployedOf(fx, "anilyzer")); assert.equal(rec.production?.builtStampId, stamp.stampId); assert.equal(rec.production?.liveCheck, null); } finally { fx.cleanup(); } }); test("the homepage's local deploy copies homepage/out into ARCHILYZER_HOMEPAGE_OUT, and refuses without it", async () => { const fx = fixture(); try { const out = path.join(fx.root, "homepage", "out"); mkdirSync(out, { recursive: true }); writeFileSync(path.join(out, "index.html"), ""); writeJson(path.join(fx.paths.exportBuildsDir, "_homepage", "built.json"), { ...stampFields("_homepage", "homepage"), stampId: "home-1", corpusGeneratedAt: null, }); const req: DeployStageRequest = { kind: "deploy-homepage", target: "_homepage", to: "local" }; const dest = path.join(fx.root, "homepage-out"); // The source gate is asked first, local or not: a build with no /source // page (its source step refused) is never shipped. await refused( runDeployStage(ctx(fx, { ARCHILYZER_HOMEPAGE_OUT: dest }), req), 3, /homepage\/out has no \/source page/, ); // A `--no-source` build: the page's empty state and nothing else. mkdirSync(path.join(out, "source")); writeFileSync(path.join(out, "source", "index.html"), ""); // Only the SITE's directory set: the homepage is not copied beside it. await refused( runDeployStage(ctx(fx, { ARCHILYZER_SITE_OUT: path.join(fx.root, "site-out") }), req), 3, /--to local needs ARCHILYZER_HOMEPAGE_OUT/, ); assert.equal(existsSync(dest), false); assert.equal(deployedBytes(fx, "_homepage"), null); const res = await runDeployStage(ctx(fx, { ARCHILYZER_HOMEPAGE_OUT: dest }), req); assert.equal(res.status, "ran"); assert.deepEqual(readdirSync(dest).sort(), ["index.html", "source"]); assert.equal((await deployedOf(fx, "_homepage")).local?.builtStampId, "home-1"); } finally { fx.cleanup(); } }); test("the hub: its project, its bundle, and every tombstone probed plain and busted", async () => { const fx = fixture(); try { writeJson(fx.paths.homepageConfigFile, { cloudflareProject: "archilyzer-hub", siteUrl: "https://archilyzer-hub.pages.dev", }); const dir = path.join(fx.paths.exportBuildsDir, "_hub"); const out = path.join(dir, "out"); writeJson(path.join(out, "hub-sites.json"), []); writeJson(path.join(out, "corpus.json"), { kind: "hub", generatedAt: GENERATED }); writeJson(path.join(out, "posts", "manifest.json"), { channels: [], totalCount: 0 }); writeJson(path.join(out, "posts", "thequartering-X", "manifest.json"), { pageCount: 0, slugToPage: {} }); writeJson(path.join(out, "posts", "thequartering-X", "page-0000.json"), []); writeJson(path.join(dir, "built.json"), { ...stampFields("_hub", "hub"), stampId: "hub-1" }); const c = ctx(fx, TOKEN); const res = await runDeployStage(c, { kind: "deploy-hub", target: "_hub" }); assert.equal(res.status, "ran"); assert.deepEqual(sidecar(fx, "_hub")[0].argv, [ "pages", "deploy", out, "--project-name", "archilyzer-hub", "--branch", "main", ]); for (const rel of [ "corpus.json", "posts/manifest.json", "posts/thequartering-X/manifest.json", "posts/thequartering-X/page-0000.json", ]) { assert.ok(c.asked.includes(`https://archilyzer-hub.pages.dev/${rel}`), rel); assert.ok(c.asked.includes(`https://archilyzer-hub.pages.dev/${rel}?cb=hub-1`), `${rel} busted`); } const rec = await deployedOf(fx, "_hub"); assert.equal(rec.production?.liveCheck?.verdict, "ok"); assert.equal(rec.production?.liveCheck?.tombstones?.length, 3); assert.ok(c.lines.some((l) => /3 withdrawn path\(s\) read as tombstones/.test(l)), c.lines.join("")); // The homepage's project is never the hub's. writeJson(fx.paths.homepageConfigFile, { cloudflareProject: "archilyzer" }); await refused(runDeployStage(ctx(fx, TOKEN), { kind: "deploy-hub", target: "_hub", force: true }), 3, /homepage's/); await refused( runDeployStage(ctx(fx, TOKEN), { kind: "deploy-hub", target: "_hub", to: "local" }), 2, /the hub has no local target/, ); } finally { fx.cleanup(); } }); test("a stale edge is a WARNING: the deploy is recorded with its verdict and the stage succeeds", async () => { const fx = fixture(); try { site(fx, "anilyzer"); built(fx, "anilyzer"); const asked: string[] = []; const stale = (async (input: string | URL | Request) => { const url = String(input); asked.push(url); const fresh = url.includes("?cb="); return new Response(JSON.stringify({ generatedAt: fresh ? GENERATED : "2026-10-01T00:00:00.000Z" }), { status: 200, headers: { "cf-cache-status": fresh ? "MISS" : "HIT" }, }); }) as typeof fetch; const c = ctx(fx, TOKEN, { liveCheck: { fetch: stale, sleep: async () => {} } }); const res = await runDeployStage(c, { kind: "deploy-site", target: "anilyzer" }); assert.equal(res.status, "ran"); assert.match(res.summary, /live check: stale-edge\.$/); assert.ok(c.lines.some((l) => l.startsWith("[live] WARNING stale-edge"))); const rec = (await deployedOf(fx, "anilyzer")); assert.equal(rec.production?.liveCheck?.verdict, "stale-edge"); assert.equal(rec.production?.liveCheck?.plain.cfCacheStatus, "HIT"); } finally { fx.cleanup(); } });