// `archilyzer doctor` over temp checkouts and a stubbed PATH. // // Run with: // pnpm --filter yt-dlp-transcript-common test // // Every scenario builds its own tree under the OS temp dir, points a Paths at it // and hands the doctor a PATH holding only the fake binaries the scenario // wants. The last assertion of each is the one that matters most: the tree is // byte-for-byte and mtime-for-mtime what it was — the doctor wrote nothing. import { execFileSync } from "node:child_process"; import { test, after } from "node:test"; import assert from "node:assert/strict"; import { chmodSync, mkdirSync, mkdtempSync, readdirSync, rmSync, statSync, symlinkSync, writeFileSync, } from "node:fs"; import os from "node:os"; import path from "node:path"; import type { Paths } from "../lib/paths"; import { collectDoctorReport, parseEngineTime, renderDoctorReport, type BuildImageProbe, type DoctorDeps, type DoctorReport, } from "./doctor"; const TMP = mkdtempSync(path.join(os.tmpdir(), "doctor-")); after(() => rmSync(TMP, { recursive: true, force: true })); let n = 0; function checkout(): { root: string; bin: string; paths: Paths } { const root = path.join(TMP, `c${n++}`); mkdirSync(path.join(root, "node_modules", ".pnpm"), { recursive: true }); writeFileSync(path.join(root, "pnpm-workspace.yaml"), "packages: []\n"); mkdirSync(path.join(root, "scripts"), { recursive: true }); writeFileSync(path.join(root, "scripts", "diarize.mjs"), ""); const bin = path.join(root, ".bin"); mkdirSync(bin); const transcriptsDir = path.join(root, "transcripts"); const paths = { monorepoRoot: root, transcriptsDir, channelsDir: path.join(transcriptsDir, "channels"), lmdbPath: path.join(transcriptsDir, "index.mdb"), settingsFile: path.join(root, "settings.json"), ytdlpBin: "yt-dlp", ffmpegBin: "ffmpeg", ffprobeBin: "ffprobe", galleryDlBin: "gallery-dl", aria2cBin: "aria2c", rsyncBin: "rsync", findmntBin: "findmnt", udisksctlBin: "udisksctl", claudeBin: "claude", diarizeBin: path.join(root, "scripts", "diarize.mjs"), whisperBin: "whisper-cli", whisperModel: path.join(root, "models", "ggml-base.en.bin"), parakeetBin: path.join(root, "scripts", "parakeet-stitch.mjs"), parakeetModel: "", parakeetCliBin: "parakeet-cli", configDir: path.join(root, ".config"), exportBuildsDir: path.join(root, "export", ".export-builds"), exportIndexDir: path.join(root, "export", ".export-index"), sourceScrubFile: path.join(root, ".config", "source-scrub.txt"), sourceDenylistFile: path.join(root, ".config", "source-denylist.txt"), // Outside the checkout, as the XDG cache is: the tests that compare the // checkout's tree before and after never see it. sourceHistoryCacheDir: path.join(TMP, `${path.basename(root)}-cache`, "archilyzer", "source-history"), } as unknown as Paths; return { root, bin, paths }; } function fake(binDir: string, name: string, version = "1.2.3"): void { const p = path.join(binDir, name); writeFileSync(p, `#!/bin/sh\necho "${name} ${version}"\n`); chmodSync(p, 0o755); } // Every file and dir under root, with its size and mtime: what "wrote nothing" // is checked against. function tree(root: string): string[] { const out: string[] = []; const walk = (d: string) => { for (const e of readdirSync(d)) { const p = path.join(d, e); const st = statSync(p, { throwIfNoEntry: false }); out.push(`${path.relative(root, p)} ${st ? `${st.size} ${st.mtimeMs}` : "dangling"}`); if (st?.isDirectory()) walk(p); } }; walk(root); return out.sort(); } // No container engine unless a scenario hands one in: a unit test never asks docker. const noEngine = async (): Promise => ({ engine: false }); async function run( c: ReturnType, env: NodeJS.ProcessEnv = {}, more: Partial = {}, ): Promise { return collectDoctorReport({ env: { PATH: c.bin, ...env }, paths: c.paths, nodeVersion: "22.0.0", portBlock: async () => null, portInUse: async () => false, umtoolTools: async () => null, buildImage: noEngine, ...more, }); } const status = (r: DoctorReport, id: string) => r.checks.find((c) => c.id === id)?.status; test("a clone with no corpus, no settings and no tools is not broken", async () => { const c = checkout(); const before = tree(c.root); const r = await run(c); assert.equal(r.ok, true, renderDoctorReport(r)); assert.equal(status(r, "transcripts"), "info"); assert.equal(status(r, "settings.json"), "info"); assert.equal(status(r, "yt-dlp"), "info"); // absent, and nothing needs it assert.deepEqual(tree(c.root), before); }); test("a corpus without ffmpeg fails, and names what needs it", async () => { const c = checkout(); mkdirSync(path.join(c.paths.channelsDir, "chan"), { recursive: true }); writeFileSync(path.join(c.paths.channelsDir, "chan", "config.json"), "{}"); writeFileSync(c.paths.settingsFile, "{}"); fake(c.bin, "yt-dlp", "2026.01.01"); const before = tree(c.root); const r = await run(c); assert.equal(r.ok, false); assert.equal(status(r, "yt-dlp"), "ok"); assert.equal(status(r, "ffmpeg"), "fail"); assert.equal(status(r, "ffprobe"), "fail"); assert.match(r.checks.find((x) => x.id === "ffmpeg")!.detail, /a corpus is here/); // No index yet: a warning, and the doctor did not build one. assert.equal(status(r, "index"), "warn"); assert.deepEqual(tree(c.root), before); }); test("a settings file that does not parse fails", async () => { const c = checkout(); writeFileSync(c.paths.settingsFile, "{ not json"); const r = await run(c); assert.equal(status(r, "settings.json"), "fail"); assert.equal(r.ok, false); assert.match(renderDoctorReport(r), /FAIL settings\.json/); }); test("an enabled whisper worker needs its engine and its model — beside a corpus", async () => { const settings = { workers: [ { id: "w1", name: "GPU", kind: "local", enabled: true, priority: 0, appId: "whisper-cpp", config: { bin: "whisper-cli" } }, ], }; // No corpus yet: nothing to transcribe, so a warning, not a failure. const bare = checkout(); writeFileSync(bare.paths.settingsFile, JSON.stringify(settings)); let r = await run(bare); assert.equal(status(r, "engine:w1"), "warn"); assert.equal(r.ok, true, renderDoctorReport(r)); const c = checkout(); mkdirSync(path.join(c.paths.channelsDir, "chan"), { recursive: true }); writeFileSync(path.join(c.paths.channelsDir, "chan", "config.json"), "{}"); writeFileSync(c.paths.lmdbPath, ""); for (const b of ["yt-dlp", "ffmpeg", "ffprobe"]) fake(c.bin, b); writeFileSync(c.paths.settingsFile, JSON.stringify(settings)); r = await run(c); assert.equal(status(r, "engine:w1"), "fail"); assert.equal(status(r, "model:w1"), "fail"); assert.equal(r.ok, false); fake(c.bin, "whisper-cli"); mkdirSync(path.dirname(c.paths.whisperModel), { recursive: true }); writeFileSync(c.paths.whisperModel, "model"); const before = tree(c.root); r = await run(c); assert.equal(status(r, "engine:w1"), "ok", renderDoctorReport(r)); assert.equal(status(r, "model:w1"), "ok"); assert.equal(r.ok, true, renderDoctorReport(r)); assert.deepEqual(tree(c.root), before); }); test("a worker with no bin override is checked against the report's Paths, not the process's", async () => { // release 11 review L7: the default engine came from app.defaultBin(), i.e. // THIS process's getPaths() — "whisper-cli" here — whatever Paths the doctor // was handed. Now it is the handed Paths' whisperBin / parakeetBin, as the // model beside it always was. const c = checkout(); const p = c.paths as { whisperBin: string; parakeetBin: string }; p.whisperBin = "whisper-from-paths"; p.parakeetBin = "parakeet-from-paths"; writeFileSync( c.paths.settingsFile, JSON.stringify({ workers: [ { id: "w1", name: "CPU", kind: "local", enabled: true, priority: 0, appId: "whisper-cpp", config: {} }, { id: "w2", name: "GPU", kind: "local", enabled: true, priority: 1, appId: "parakeet", config: {} }, ], }), ); fake(c.bin, "whisper-from-paths"); fake(c.bin, "parakeet-from-paths"); const r = await run(c); const detail = (id: string) => r.checks.find((x) => x.id === id)?.detail ?? ""; assert.equal(status(r, "engine:w1"), "ok", renderDoctorReport(r)); assert.match(detail("engine:w1"), /whisper-from-paths/); assert.equal(status(r, "engine:w2"), "ok", renderDoctorReport(r)); assert.match(detail("engine:w2"), /parakeet-from-paths/); }); test("an override naming a binary that is not there fails even when nothing needs it", async () => { const c = checkout(); const r = await run(c, { YTDLP_BIN: "/nonexistent/yt-dlp" }); // The report shows the override… assert.match(r.checks.find((x) => x.id === "overrides")!.detail, /YTDLP_BIN=\/nonexistent\/yt-dlp/); // …but the probe here ran the Paths' binary name, so pass the override's // path the way getPaths() would have. const c2 = checkout(); (c2.paths as { ytdlpBin: string }).ytdlpBin = "/nonexistent/yt-dlp"; const r2 = await run(c2, { YTDLP_BIN: "/nonexistent/yt-dlp" }); assert.equal(status(r2, "yt-dlp"), "fail"); assert.equal(r2.ok, false); }); test("an unmounted drive is a warning, not an empty channel and not a failure", async () => { const c = checkout(); const chan = path.join(c.paths.channelsDir, "moved"); mkdirSync(chan, { recursive: true }); const target = path.join(c.root, "elsewhere", "moved", "media"); writeFileSync(path.join(chan, "config.json"), JSON.stringify({ mediaDir: target })); mkdirSync(path.join(chan, "data")); symlinkSync(target, path.join(chan, "media")); // the target does not exist // A channel on the retired whole-directory layout is named with its way out. const old = path.join(c.paths.channelsDir, "retired"); mkdirSync(old, { recursive: true }); const oldTarget = path.join(c.root, "elsewhere", "retired", "data"); writeFileSync(path.join(old, "config.json"), JSON.stringify({ dataDir: oldTarget })); symlinkSync(oldTarget, path.join(old, "data")); // No workers: a `{}` file would synthesize whisper workers, whose engine // this machine does not have — a real failure, and not this test's. writeFileSync(c.paths.settingsFile, JSON.stringify({ workers: [] })); for (const b of ["yt-dlp", "ffmpeg", "ffprobe"]) fake(c.bin, b); writeFileSync(c.paths.lmdbPath, ""); const before = tree(c.root); const r = await run(c); const media = r.checks.find((x) => x.id === "media")!; assert.equal(media.status, "warn"); assert.match(media.detail, /moved: unreachable/); const retired = r.checks.filter((x) => x.id === "media").map((x) => x.detail).join("\n"); assert.match(retired, /retired: legacy — .*archilyzer storage migrate-tier retired/); assert.equal(r.ok, true, renderDoctorReport(r)); assert.deepEqual(tree(c.root), before); }); test("node older than next's floor fails", async () => { const c = checkout(); const r = await collectDoctorReport({ env: { PATH: c.bin }, paths: c.paths, nodeVersion: "20.8.1", portBlock: async () => null, portInUse: async () => false, umtoolTools: async () => null, buildImage: noEngine, }); assert.equal(status(r, "node"), "fail"); }); test("umtool's table and the port block are reported, never failed", async () => { const c = checkout(); const r = await collectDoctorReport({ env: { PATH: c.bin }, paths: c.paths, nodeVersion: "22.0.0", portBlock: async () => ({ label: "worktree #3 (offset 300)", ports: { EDITOR_PORT: "3301" } }), portInUse: async (p) => p === 3301, umtoolTools: async () => [{ id: "qrencode", bin: "qrencode", neededBy: ["report-video QR"], required: true }], buildImage: noEngine, }); assert.equal(status(r, "qrencode"), "warn"); assert.match(r.checks.find((x) => x.id === "EDITOR_PORT")!.detail, /^3301 in use/); assert.equal(r.ok, true); }); // ── the build image ───────────────────────────────────────────────────────── // Build all's per-site containers run the image the settings name. The engine // is always handed in (`buildImage`); only the Dockerfile's date is ever read // for real, and then from a temp checkout. // A corpus whose tools all pass, so every warning below is the image's. function corpusCheckout(settings: Record = {}) { const c = checkout(); mkdirSync(path.join(c.paths.channelsDir, "chan"), { recursive: true }); writeFileSync(path.join(c.paths.channelsDir, "chan", "config.json"), "{}"); writeFileSync(c.paths.lmdbPath, ""); for (const b of ["yt-dlp", "ffmpeg", "ffprobe"]) fake(c.bin, b); writeFileSync(c.paths.settingsFile, JSON.stringify({ workers: [], ...settings })); writeFileSync(path.join(c.root, "Dockerfile.build"), "FROM scratch\n"); return c; } const imageLine = (r: DoctorReport) => r.checks.find((x) => x.id === "build-image")!; const JUL = new Date("2026-07-07T16:04:55Z"); const SEP = new Date("2026-09-28T17:00:00Z"); const NOW = new Date("2026-09-29T12:00:00Z"); test("the build image: no container engine is one line saying the check was skipped", async () => { const c = corpusCheckout(); const r = await run(c); const lines = r.checks.filter((x) => x.section === "build image"); assert.equal(lines.length, 1, renderDoctorReport(r)); assert.equal(lines[0].id, "build-image"); assert.equal(lines[0].status, "info"); assert.match(lines[0].detail, /^skipped: `docker version` did not answer/); assert.equal(r.ok, true, renderDoctorReport(r)); }); test("the build image: absent warns beside a corpus, with the configured tag and the one command — never a failure", async () => { const c = corpusCheckout({ buildPipeline: { dockerImage: "my-build" } }); const asked: { bin: string; image: string }[] = []; const before = tree(c.root); const r = await run(c, {}, { buildImage: async (q) => { asked.push(q); return { engine: true, image: null }; }, }); assert.deepEqual(asked, [{ bin: "docker", image: "my-build" }]); const line = imageLine(r); assert.equal(line.status, "warn", renderDoctorReport(r)); assert.match(line.detail, /^no image "my-build" — the next Build all builds it first/); assert.ok( line.detail.includes(`\nrebuild it now: cd ${c.root} && docker build -f Dockerfile.build -t my-build .`), line.detail, ); assert.equal(r.ok, true, renderDoctorReport(r)); assert.deepEqual(tree(c.root), before); // No corpus, no site to build: the same words, as a note. const bare = checkout(); const r2 = await run(bare, {}, { buildImage: async () => ({ engine: true, image: null }) }); assert.equal(status(r2, "build-image"), "info"); assert.equal(r2.ok, true); }); test("the build image: older than its Dockerfile's last change warns; newer is ok, with its age", async () => { const c = corpusCheckout(); const changed = async () => ({ at: SEP, source: "commit" as const }); let r = await run(c, {}, { buildImage: async () => ({ engine: true, image: { created: JUL, sizeBytes: 7_720_000_000 } }), dockerfileChanged: changed, now: NOW, }); let line = imageLine(r); assert.equal(line.status, "warn", renderDoctorReport(r)); assert.match( line.detail, /^"yt-dlp-transcript-browser-build" built 2026-07-07 16:04 \(84 days ago\), 7\.72 GB — before Dockerfile\.build's last change \(2026-09-28 17:00, its last commit\)/, ); assert.match(line.detail, /\nrebuild it now: cd \S+ && docker build -f Dockerfile\.build -t yt-dlp-transcript-browser-build \.$/); assert.equal(r.ok, true); r = await run(c, {}, { buildImage: async () => ({ engine: true, image: { created: new Date("2026-09-28T18:00:00Z"), sizeBytes: 1_673_611_166 }, }), dockerfileChanged: changed, now: NOW, }); line = imageLine(r); assert.equal(line.status, "ok", renderDoctorReport(r)); assert.match(line.detail, /built 2026-09-28 18:00 \(18 hours ago\), 1\.67 GB, after Dockerfile\.build's last change/); assert.doesNotMatch(line.detail, /rebuild/); }); test("the build image: DOCKER_BIN names the engine it asks and the command it prints", async () => { const c = corpusCheckout(); const asked: string[] = []; const r = await run(c, { DOCKER_BIN: "podman" }, { buildImage: async (q) => { asked.push(q.bin); return { engine: true, image: null }; }, }); assert.deepEqual(asked, ["podman"]); assert.match(imageLine(r).detail, /&& podman build -f Dockerfile\.build -t yt-dlp-transcript-browser-build \.$/); }); test("the build image: a Dockerfile the settings name that is not there warns", async () => { const c = corpusCheckout({ buildPipeline: { dockerfile: "docker/nope.Dockerfile" } }); const r = await run(c, {}, { buildImage: async () => ({ engine: true, image: { created: JUL, sizeBytes: null } }), now: NOW, }); assert.equal(status(r, "dockerfile"), "warn", renderDoctorReport(r)); assert.match(r.checks.find((x) => x.id === "dockerfile")!.detail, /^docker\/nope\.Dockerfile is not at /); assert.equal(status(r, "build-image"), "ok"); // here, and nothing to date it against }); test("the build image's Dockerfile dates from its last commit, or its mtime once edited — and git writes nothing", async () => { const git = execFileSync("sh", ["-c", "command -v git"], { encoding: "utf8" }).trim(); const c = corpusCheckout(); symlinkSync(git, path.join(c.bin, "git")); const at = "2026-08-01T00:00:00Z"; const g = (...args: string[]) => execFileSync(git, args, { cwd: c.root, env: { ...process.env, GIT_AUTHOR_DATE: at, GIT_COMMITTER_DATE: at, GIT_AUTHOR_NAME: "t", GIT_AUTHOR_EMAIL: "t@example.invalid", GIT_COMMITTER_NAME: "t", GIT_COMMITTER_EMAIL: "t@example.invalid", }, }); g("init", "-q"); // No background maintenance after the commit: a detached `git maintenance // run --auto` takes .git/objects/maintenance.lock while the doctor runs, and // the tree comparison below would blame the doctor for git's own write. g("config", "maintenance.auto", "false"); g("config", "gc.auto", "0"); g("add", "Dockerfile.build"); g("commit", "-q", "-m", "the build image"); const image = async (): Promise => ({ engine: true, image: { created: new Date("2026-09-01T00:00:00Z"), sizeBytes: null }, }); const before = tree(c.root); let r = await run(c, {}, { buildImage: image, now: NOW }); assert.equal(imageLine(r).status, "ok", renderDoctorReport(r)); assert.match(imageLine(r).detail, /Dockerfile\.build's last change \(2026-08-01 00:00, its last commit\)/); // .git included: `git status` refreshed no index (GIT_OPTIONAL_LOCKS=0). assert.deepEqual(tree(c.root), before); // An uncommitted edit is newer than any commit: its mtime (now) decides. writeFileSync(path.join(c.root, "Dockerfile.build"), "FROM scratch\nRUN true\n"); r = await run(c, {}, { buildImage: image, now: NOW }); assert.equal(imageLine(r).status, "warn", renderDoctorReport(r)); assert.match(imageLine(r).detail, /its mtime: uncommitted or no checkout\)/); }); test("an engine's image creation time parses in docker's and podman's spelling", () => { assert.equal(parseEngineTime("2026-07-07T12:04:55.302907312-04:00")?.toISOString(), "2026-07-07T16:04:55.302Z"); assert.equal(parseEngineTime("2026-07-07 12:04:55.302907312 -0400 EDT")?.toISOString(), "2026-07-07T16:04:55.302Z"); assert.equal(parseEngineTime("2026-09-28T16:35:39Z")?.toISOString(), "2026-09-28T16:35:39.000Z"); assert.equal(parseEngineTime(""), null); }); test("the source publish block: the tools, the operator files by count and mode (never their contents), the last publish — never a failure", async () => { const c = checkout(); const deps = (tools: Awaited[0]["sourceTools"]>>>) => ({ env: { PATH: c.bin }, paths: c.paths, nodeVersion: "22.0.0", portBlock: async () => null, portInUse: async () => false, umtoolTools: async () => null, buildImage: noEngine, sourceTools: async () => tools, }); // A clone that never publishes: notes, not warnings. let r = await collectDoctorReport(deps({ filterRepo: null, gitleaks: null, stagit: null })); assert.equal(r.ok, true, renderDoctorReport(r)); for (const id of ["filter-repo", "gitleaks", "stagit", "scrub rules", "denylist", "published"]) { assert.equal(status(r, id), "info", id); } assert.match(r.checks.find((x) => x.id === "filter-repo")!.detail, /install: `pipx install git-filter-repo`/); // stagit, beside git-filter-repo: where it is, or not found and how to // install it; then the render cache, where it is and how big. assert.equal( r.checks.find((x) => x.id === "stagit")!.detail, `not found (PATH, ~/.local/bin) — the source is published without its history pages (/source/git/); install it once: git clone git://git.codemadness.org/stagit && make -C stagit && cp stagit/stagit ~/.local/bin/; cache: ${c.paths.sourceHistoryCacheDir}, none yet`, ); const order = r.checks.filter((x) => x.section === "source publish").map((x) => x.id); assert.equal(order.indexOf("stagit"), order.indexOf("filter-repo") + 1, "beside git-filter-repo"); // The operator's files exist (one readable by others), pipx only, a publish. mkdirSync(path.dirname(c.paths.sourceScrubFile), { recursive: true }); writeFileSync(c.paths.sourceScrubFile, "# mine\nPLANTED-A==>x\n\nPLANTED-B==>y\n"); chmodSync(c.paths.sourceScrubFile, 0o600); writeFileSync(c.paths.sourceDenylistFile, "PLANTED-SECRET\n"); chmodSync(c.paths.sourceDenylistFile, 0o644); const pub = path.join(c.root, "homepage", "public", "source"); mkdirSync(pub, { recursive: true }); writeFileSync(path.join(pub, "manifest.json"), JSON.stringify({ version: 1, generatedAt: "2026-09-28T12:00:00.000Z", branch: "main", sourceCommit: "1".repeat(40), mirrorHead: "2".repeat(40), subject: "s", files: 2400, bytes: 1, mirror: { files: 9, bytes: 1, packs: 2 }, tree: { files: 1, dirs: 1, bytes: 1 }, tarball: { href: "/downloads/archilyzer-source.tar.gz", bytes: 1, sha256: "3".repeat(64) }, audit: { objects: 1, commits: 1, gitleaks: "clean" }, tools: {}, })); const before = tree(c.root); r = await collectDoctorReport(deps({ filterRepo: { via: "pipx", version: "1.15.0" }, gitleaks: { version: "8.28.0" }, stagit: "/opt/stagit/stagit" })); assert.equal(r.ok, true, renderDoctorReport(r)); assert.equal(status(r, "filter-repo"), "warn"); assert.match(r.checks.find((x) => x.id === "filter-repo")!.detail, /pipx run --spec git-filter-repo==2\.47\.0/); assert.equal(status(r, "gitleaks"), "ok"); assert.equal(status(r, "stagit"), "ok"); mkdirSync(path.join(c.paths.sourceHistoryCacheDir, "out"), { recursive: true }); writeFileSync(path.join(c.paths.sourceHistoryCacheDir, "out", "log.html"), Buffer.alloc(3 * 1024 * 1024)); const withCache = await collectDoctorReport(deps({ filterRepo: null, gitleaks: null, stagit: "/opt/stagit/stagit" })); assert.equal(withCache.checks.find((x) => x.id === "stagit")!.detail, `/opt/stagit/stagit; cache: ${c.paths.sourceHistoryCacheDir}, 3.0 MB`); assert.equal(status(r, "scrub rules"), "ok"); assert.match(r.checks.find((x) => x.id === "scrub rules")!.detail, /\(2 rules, mode 600\)$/); assert.equal(status(r, "denylist"), "warn"); assert.match(r.checks.find((x) => x.id === "denylist")!.detail, /\(1 literal, mode 644\) — readable by others: chmod 600/); assert.match(r.checks.find((x) => x.id === "published")!.detail, /^main 111111111111 as 222222222222, 2026-09-28T12:00:00\.000Z \(2400 files\)/); const text = renderDoctorReport(r); assert.match(text, /\nsource publish\n/); assert.ok(!/PLANTED/.test(text), "the doctor never prints an operator file's contents"); assert.deepEqual(tree(c.root), before); r = await collectDoctorReport(deps({ filterRepo: { via: "git", version: "a40bce548d2c" }, gitleaks: null, stagit: null })); assert.equal(status(r, "filter-repo"), "ok"); // A STAGIT_BIN that names nothing: a warning, never a failure. r = await collectDoctorReport({ ...deps({ filterRepo: null, gitleaks: null, stagit: null }), env: { PATH: c.bin, STAGIT_BIN: "/nowhere/stagit" } }); assert.equal(status(r, "stagit"), "warn"); assert.match(r.checks.find((x) => x.id === "stagit")!.detail, /^not found: STAGIT_BIN=\/nowhere\/stagit names no executable/); assert.equal(r.ok, true); }); // A stored icon the checker refuses is named by file and label with its // reason — never its markup — and the doctor still exits 0 (a WARN). test("social icons: a refused stored icon is named by file and label, not its markup", async () => { const c = checkout(); const sitesDir = path.join(c.paths.transcriptsDir, "sites"); mkdirSync(path.join(sitesDir, "one"), { recursive: true }); const secret = "SECRET-MARKUP-XYZ"; writeFileSync(c.paths.settingsFile, JSON.stringify({ socialLinks: [ { label: "Good", url: "https://good.example", svg: `` }, { label: "Old", url: "https://old.example", svg: `` }, ], })); writeFileSync(path.join(sitesDir, "one", "site.json"), JSON.stringify({ socialLinks: [{ label: "Site icon", url: "https://s.example", svg: `` }], })); const report = await collectDoctorReport({ env: {}, paths: { ...c.paths, sitesDir, homepageConfigFile: path.join(sitesDir, "_homepage", "homepage.json") }, probe: async (t) => ({ id: t.id, bin: t.bin ?? "", present: true, version: "1", neededBy: t.neededBy, required: t.required }) as never, portInUse: async () => false, portBlock: async () => null, umtoolTools: async () => null, buildImage: noEngine, sourceTools: async () => ({ filterRepo: null, gitleaks: null, stagit: null }), }); const line = report.checks.find((x) => x.section === "social icons")!; assert.equal(line.status, "warn"); assert.match(line.detail, /2 of 3 fail/); assert.match(line.detail, /settings\.json: "Old" — it has an element an icon has no use for \(style\)/); assert.match(line.detail, /site\.json: "Site icon" — it is not one well-formed element/); assert.ok(!line.detail.includes(secret), "no markup"); assert.ok(report.ok, "a warning, not a failure"); }); // Release 15 slice DT left doctor's line to SG: the drive-health timings are // applied before any drive is inspected (as the index and stats bins apply // them), and the corpus block says which are in force. test("the drive-health timings: applied from settings.storage.health before the corpus is inspected, and printed", async () => { const { healthTimings, applyHealthTimings } = await import("../lib/storageHealth"); const c = checkout(); mkdirSync(c.paths.channelsDir, { recursive: true }); let r = await run(c); assert.equal( r.checks.find((x) => x.id === "drive health")!.detail, "a read may take 3 s, a check every 15 s (3 s each), a stall clears on a clean check twice in a row, 4 reads in flight per drive — the defaults", ); writeFileSync(c.paths.settingsFile, JSON.stringify({ storage: { health: { budgetMs: 5000, clearAfterCleanPasses: 3 } } })); r = await run(c); assert.equal( r.checks.find((x) => x.id === "drive health")!.detail, "a read may take 5 s, a check every 15 s (3 s each), a stall clears on a clean check 3 times in a row, 4 reads in flight per drive — settings.storage.health", ); assert.equal(healthTimings().budgetMs, 5000, "applied to this process"); applyHealthTimings(); }); test("download pacing: a held platform and a raised pace warn, and nothing is written (release 17, RL)", async () => { const c = checkout(); const stateFile = path.join(c.paths.transcriptsDir, ".auto-queue", "state.json"); (c.paths as { autoQueueStateFile: string }).autoQueueStateFile = stateFile; mkdirSync(path.dirname(stateFile), { recursive: true }); const now = Date.UTC(2026, 9, 1, 12, 0); writeFileSync( stateFile, JSON.stringify({ download: { platformBackoff: { youtube: { until: now + 42 * 60_000, fails: 8 }, rumble: { until: now + 90_000, fails: 2 }, }, platformHolds: { youtube: { since: now - 60_000, probeAt: now + 42 * 60_000, rateLimited: true } }, platformPace: { youtube: { sleepRequestsSeconds: 4, baseSeconds: 1, cleanUnits: 1, steppedAt: now } }, }, }), ); const before = tree(c.root); const r = await run(c, {}, { now: new Date(now) }); assert.equal(r.ok, true, renderDoctorReport(r)); const dp = r.checks.filter((x) => x.section === "download pacing"); assert.deepEqual( dp.map((x) => [x.id, x.status]), [ ["youtube", "warn"], ["rumble", "warn"], ["youtube pace", "warn"], ], ); assert.match(dp[0].detail, /^held since .* after 8 rate-limited\/network failures in a row; next probe in 42 min/); assert.match(dp[1].detail, /^in a rate-limit cooldown for 2 min more \(attempt 2\)$/); assert.match(dp[2].detail, /^4s between requests \(base 1s\)/); assert.deepEqual(tree(c.root), before); }); // ── release 18: the downloader and publish checks ───────────────────────── const find = (r: DoctorReport, section: string, id: string) => r.checks.find((x) => x.section === section && x.id === id); // A HOME of the scenario's own, so wrangler's login config is never this // machine's. function home(c: ReturnType): string { const h = path.join(TMP, `${path.basename(c.root)}-home`); mkdirSync(h, { recursive: true }); return h; } test("downloader: a host's yt-dlp is a note; the image's is ok; an override says so, and warns beside YTDLP_AUTO_UPDATE or when it does not run", async () => { const c = checkout(); fake(c.bin, "yt-dlp", "2026.09.30"); fake(c.bin, "yt-dlp-patched", "2026.10.01.patched"); // The from-source wrapper with no checkout mounted: a sentence, exit 127. const broken = path.join(c.bin, "yt-dlp-from-source"); writeFileSync(broken, "#!/bin/sh\necho 'no yt_dlp package' >&2\nexit 127\n"); chmodSync(broken, 0o755); const before = tree(c.root); // A host install: no ARCHILYZER_IMAGE_YTDLP to compare with. let r = await run(c); assert.equal(find(r, "downloader", "yt-dlp")?.status, "info"); assert.match(find(r, "downloader", "yt-dlp")!.detail, /yt-dlp 2026\.09\.30 \(host: not in the runtime image\)$/); // The runtime image, running its own. const image = path.join(c.bin, "yt-dlp"); r = await run(c, { ARCHILYZER_IMAGE_YTDLP: image }); assert.equal(find(r, "downloader", "yt-dlp")?.status, "ok"); assert.match(find(r, "downloader", "yt-dlp")!.detail, /2026\.09\.30 \(image\)$/); // An override (the paths' binary is another file). (c.paths as { ytdlpBin: string }).ytdlpBin = path.join(c.bin, "yt-dlp-patched"); r = await run(c, { ARCHILYZER_IMAGE_YTDLP: image }); assert.equal(find(r, "downloader", "yt-dlp")?.status, "ok"); assert.match(find(r, "downloader", "yt-dlp")!.detail, new RegExp(`2026\\.10\\.01\\.patched \\(override; the image's is ${image.replace(/[.]/g, "\\.")}\\)$`)); r = await run(c, { ARCHILYZER_IMAGE_YTDLP: image, YTDLP_AUTO_UPDATE: "1" }); assert.equal(find(r, "downloader", "yt-dlp")?.status, "warn"); assert.match(find(r, "downloader", "yt-dlp")!.detail, /YTDLP_AUTO_UPDATE is set, and the entrypoint never self-updates an override/); assert.equal(r.ok, true, "a warning, never a failure"); // The entrypoint's exact-match rule: `TRUE` (or ` 1`) is off there, so here. for (const off of ["TRUE", " 1", "0", ""]) { r = await run(c, { ARCHILYZER_IMAGE_YTDLP: image, YTDLP_AUTO_UPDATE: off }); assert.equal(find(r, "downloader", "yt-dlp")?.status, "ok", JSON.stringify(off)); } // An override that does not run. (c.paths as { ytdlpBin: string }).ytdlpBin = broken; r = await run(c, { ARCHILYZER_IMAGE_YTDLP: image }); assert.equal(find(r, "downloader", "yt-dlp")?.status, "warn"); assert.match(find(r, "downloader", "yt-dlp")!.detail, /\(override; .*\) — does not run .*docker-compose\.ytdlp\.yml/); assert.deepEqual(tree(c.root), before); }); test("publish: cloudflare-auth reports a token SET (never its value), a wrangler login by path, and warns only when a site names a project", async () => { const c = checkout(); const sitesDir = path.join(c.paths.transcriptsDir, "sites"); (c.paths as { sitesDir: string }).sitesDir = sitesDir; const h = home(c); // Nothing configured to deploy, no credential: a note. let r = await run(c, { HOME: h }); assert.equal(find(r, "publish", "cloudflare-auth")?.status, "info"); // A site that deploys, no credential: a warning naming the site. mkdirSync(path.join(sitesDir, "alpha"), { recursive: true }); writeFileSync(path.join(sitesDir, "alpha", "site.json"), JSON.stringify({ title: "A", cloudflareProject: "alpha-pages" })); mkdirSync(path.join(sitesDir, "beta"), { recursive: true }); writeFileSync(path.join(sitesDir, "beta", "site.json"), JSON.stringify({ title: "B" })); const before = tree(c.root); r = await run(c, { HOME: h }); assert.equal(find(r, "publish", "cloudflare-auth")?.status, "warn"); // The deploy's own preflight sentence (lib/pagesDeploy.ts), so the two cannot disagree. assert.match(find(r, "publish", "cloudflare-auth")!.detail, /1 site names a Cloudflare project \(alpha\) — every deploy refuses \("REFUSED — no Cloudflare credentials: set CLOUDFLARE_API_TOKEN in \.env/); assert.equal(r.ok, true); // A token: ok, and the value appears nowhere in the report. const secret = "PLANTED-TOKEN-0123456789"; r = await run(c, { HOME: h, CLOUDFLARE_API_TOKEN: secret, CLOUDFLARE_ACCOUNT_ID: "PLANTED-ACCOUNT" }); assert.equal(find(r, "publish", "cloudflare-auth")?.status, "ok"); assert.match(find(r, "publish", "cloudflare-auth")!.detail, /^CLOUDFLARE_API_TOKEN is set \(never printed\); CLOUDFLARE_ACCOUNT_ID set$/); assert.ok(!/PLANTED/.test(renderDoctorReport(r)) && !/PLANTED/.test(JSON.stringify(r))); // A host's `wrangler login`: ok, by path. const cfg = path.join(h, ".config", ".wrangler", "config"); mkdirSync(cfg, { recursive: true }); writeFileSync(path.join(cfg, "default.toml"), 'oauth_token = "PLANTED-OAUTH"\n'); r = await run(c, { HOME: h }); assert.equal(find(r, "publish", "cloudflare-auth")?.status, "ok"); assert.match(find(r, "publish", "cloudflare-auth")!.detail, /wrangler's login config is at .*default\.toml/); assert.ok(!/PLANTED/.test(renderDoctorReport(r)), "the login config is located, never read"); assert.deepEqual(tree(c.root), before); }); test("publish: r2-keys appears only with a bucket, and names what is missing — never a value", async () => { const c = checkout(); const h = home(c); let r = await run(c, { HOME: h }); assert.equal(find(r, "publish", "r2-keys"), undefined, "no bucket, no check"); writeFileSync(c.paths.settingsFile, JSON.stringify({ archiveStorage: { bucket: "overflow", publicBaseUrl: "https://r2.example" } })); const before = tree(c.root); r = await run(c, { HOME: h, R2_ACCESS_KEY_ID: "PLANTED-KEY" }); assert.equal(find(r, "publish", "r2-keys")?.status, "warn"); assert.match(find(r, "publish", "r2-keys")!.detail, /"overflow" is set but R2_SECRET_ACCESS_KEY, CLOUDFLARE_ACCOUNT_ID are not/); r = await run(c, { HOME: h, R2_ACCESS_KEY_ID: "PLANTED-KEY", R2_SECRET_ACCESS_KEY: "PLANTED-SECRET", CLOUDFLARE_ACCOUNT_ID: "PLANTED-ACCOUNT" }); assert.equal(find(r, "publish", "r2-keys")?.status, "ok"); assert.ok(!/PLANTED/.test(renderDoctorReport(r))); assert.deepEqual(tree(c.root), before); }); test("publish: export-builds — not there yet is a note; bundles with under 1.5x their size free warn; not writable warns", async () => { const c = checkout(); const h = home(c); let r = await run(c, { HOME: h }, { freeBytes: () => 5e9 }); assert.equal(find(r, "publish", "export-builds")?.status, "info"); assert.match(find(r, "publish", "export-builds")!.detail, /does not exist yet — the first site build makes it \(5\.00 GB free\)/); const builds = c.paths.exportBuildsDir; for (const [id, n] of [["alpha", 3_000_000], ["_hub", 1_000_000]] as const) { mkdirSync(path.join(builds, id, "out", "sub"), { recursive: true }); writeFileSync(path.join(builds, id, "out", "sub", "f.bin"), Buffer.alloc(n)); } // Staging beside a bundle is not a bundle. mkdirSync(path.join(builds, "alpha", ".r2-staging"), { recursive: true }); writeFileSync(path.join(builds, "alpha", ".r2-staging", "big.zip"), Buffer.alloc(9_000_000)); const before = tree(c.root); r = await run(c, { HOME: h }, { freeBytes: () => 5e9 }); assert.equal(find(r, "publish", "export-builds")?.status, "ok"); assert.match(find(r, "publish", "export-builds")!.detail, /: 2 bundles, 4 MB; 5\.00 GB free$/); r = await run(c, { HOME: h }, { freeBytes: () => 5_000_000 }); assert.equal(find(r, "publish", "export-builds")?.status, "warn"); assert.match(find(r, "publish", "export-builds")!.detail, /under 1\.5× the bundles \(6 MB\)/); chmodSync(builds, 0o555); try { if (process.getuid?.() !== 0) { r = await run(c, { HOME: h }, { freeBytes: () => 5e9 }); assert.equal(find(r, "publish", "export-builds")?.status, "warn"); assert.match(find(r, "publish", "export-builds")!.detail, /is not writable/); } } finally { chmodSync(builds, 0o755); } assert.equal(r.ok, true); assert.deepEqual(tree(c.root), before); }); test("source publish: source-repo — the variable naming nothing fails, a readable main is ok, none is a note (a warning once the operator's files exist); config-dir is counted", async () => { const c = checkout(); const h = home(c); // No repository, nothing meant: notes. let r = await run(c, { HOME: h }); assert.equal(find(r, "source publish", "source-repo")?.status, "info"); assert.equal(find(r, "source publish", "config-dir")?.status, "info"); assert.match(find(r, "source publish", "config-dir")!.detail, /does not exist — ARCHILYZER_CONFIG_DIR moves it/); // The operator's files exist: no repository is now a warning. mkdirSync(path.dirname(c.paths.sourceScrubFile), { recursive: true }); writeFileSync(c.paths.sourceScrubFile, "PLANTED==>x\n"); writeFileSync(c.paths.sourceDenylistFile, "PLANTED\n"); const before = tree(c.root); r = await run(c, { HOME: h }); assert.equal(find(r, "source publish", "source-repo")?.status, "warn"); assert.match(find(r, "source publish", "source-repo")!.detail, /docker-compose\.source\.yml/); assert.equal(find(r, "source publish", "config-dir")?.status, "ok"); assert.match(find(r, "source publish", "config-dir")!.detail, /\(2 entries, writable\)$/); assert.ok(!/PLANTED/.test(renderDoctorReport(r))); // The variable naming nothing: the publish refuses, so the doctor fails. r = await run(c, { HOME: h, ARCHILYZER_SOURCE_REPO: path.join(TMP, "not-mounted.git") }); assert.equal(find(r, "source publish", "source-repo")?.status, "fail"); assert.equal(r.ok, false); // A real repository's git dir, through the default probe (git on PATH). const repo = path.join(TMP, `${path.basename(c.root)}-repo`); mkdirSync(repo); const git = (...a: string[]) => execFileSync("git", a, { cwd: repo, stdio: "pipe", env: { ...process.env, GIT_CONFIG_NOSYSTEM: "1", HOME: h } }); git("init", "-q", "-b", "main"); git("-c", "user.name=t", "-c", "user.email=t@example.invalid", "-c", "commit.gpgsign=false", "commit", "-q", "--allow-empty", "-m", "one"); const head = execFileSync("git", ["rev-parse", "HEAD"], { cwd: repo }).toString().trim(); r = await run(c, { HOME: h, PATH: `${c.bin}${path.delimiter}${process.env.PATH}`, ARCHILYZER_SOURCE_REPO: path.join(repo, ".git") }); assert.equal(find(r, "source publish", "source-repo")?.status, "ok"); assert.equal(find(r, "source publish", "source-repo")!.detail, `${path.join(repo, ".git")} (ARCHILYZER_SOURCE_REPO): main ${head.slice(0, 12)}`); // An injected probe: main that does not read is a warning naming why. r = await run(c, { HOME: h }, { sourceRepo: async () => ({ via: "env", repo: "/data/source.git", main: null, error: "fatal: detected dubious ownership" }) }); assert.equal(find(r, "source publish", "source-repo")?.status, "warn"); assert.match(find(r, "source publish", "source-repo")!.detail, /^\/data\/source\.git \(ARCHILYZER_SOURCE_REPO\): main does not read — fatal: detected dubious ownership$/); assert.deepEqual(tree(c.root), before); }); // ── release 18, second half: the lock, the index stamp, the wrangler floor ─ function indexStampJson(stampId: string, builtAt: number) { return { v: 1, stampId, generation: 7, scannedAt: builtAt - 60_000, builtAt, templatesAt: builtAt, commit: null, index: { shortCircuited: false, added: 1, changed: 0, removed: 0, heldChannels: [] }, stats: { shortCircuited: false, notIndexedYet: 0, notIndexable: 0 }, sites: {}, hubSig: "h", }; } function builtJson(target: string, indexStampId: string | null, bytes: number) { return { v: 1, stampId: `b-${target}`, target, kind: target === "_hub" ? "hub" : "site", indexStampId, inputSig: "s", builtAt: Date.UTC(2026, 9, 6, 10), commit: null, branch: null, runner: "local", audience: "public", corpusGeneratedAt: null, files: 3, bytes, archivesStaged: 0, }; } test("publish: publish-lock — free is ok, a running holder a note, a dead one stale with the rm, another host's named and never stale", async () => { const c = checkout(); const h = home(c); const builds = c.paths.exportBuildsDir; mkdirSync(builds, { recursive: true }); const file = path.join(builds, ".publish.lock"); let r = await run(c, { HOME: h }); assert.equal(find(r, "publish", "publish-lock")?.status, "ok"); const { pidStartOf } = await import("../publish/stageLock"); const hold = (holder: Record) => writeFileSync(file, JSON.stringify(holder)); // This process holds it: alive, same host. hold({ pid: process.pid, host: "doctor-host", kind: "build-site", target: "alpha", since: Date.now() - 5_000, pidStart: pidStartOf(process.pid) }); r = await run(c, { HOME: h, ARCHILYZER_HOST_ID: "doctor-host" }); assert.equal(find(r, "publish", "publish-lock")?.status, "info"); assert.match(find(r, "publish", "publish-lock")!.detail, /^held: build-site alpha \(pid \d+ on doctor-host/); // A pid that is not running, same host: stale. hold({ pid: 2 ** 22 - 3, host: "doctor-host", kind: "deploy-site", target: "alpha", since: Date.now() - 60_000 }); r = await run(c, { HOME: h, ARCHILYZER_HOST_ID: "doctor-host" }); assert.equal(find(r, "publish", "publish-lock")?.status, "warn"); assert.match(find(r, "publish", "publish-lock")!.detail, new RegExp(`^stale: deploy-site alpha .* the next stage takes it over, or clear it, when nothing is publishing: rm ${file.replace(/[.]/g, "\\.")}$`)); // The same dead pid on ANOTHER host: named, never judged. r = await run(c, { HOME: h, ARCHILYZER_HOST_ID: "this-host" }); assert.equal(find(r, "publish", "publish-lock")?.status, "warn"); assert.match(find(r, "publish", "publish-lock")!.detail, /^held by ANOTHER host: deploy-site alpha \(pid \d+ on doctor-host.*this host is "this-host"/); // A lock that does not parse: being written, then (past the grace) torn. writeFileSync(file, "{"); r = await run(c, { HOME: h }); assert.equal(find(r, "publish", "publish-lock")?.status, "info"); const before = tree(c.root); r = await run(c, { HOME: h }, { now: new Date(Date.now() + 10 * 60_000) }); assert.equal(find(r, "publish", "publish-lock")?.status, "warn"); assert.match(find(r, "publish", "publish-lock")!.detail, /does not parse.*a taker died writing it/); assert.equal(r.ok, true, "a warning, never a failure"); assert.deepEqual(tree(c.root), before, "the doctor never clears a lock"); }); test("publish: index-stamp — none is a note (a warning once something is built); its age; targets built from an older stamp; export-builds counts built.json bytes", async () => { const c = checkout(); const h = home(c); const builds = c.paths.exportBuildsDir; let r = await run(c, { HOME: h }); assert.equal(find(r, "publish", "index-stamp")?.status, "info"); assert.match(find(r, "publish", "index-stamp")!.detail, /no index stamp at .*stamp\.json — update the index first: archilyzer publish index$/); // Built bundles with no stamp: a warning. for (const [target, id, bytes] of [["alpha", "old-stamp", 7_000_000], ["_hub", "cur-stamp", 2_000_000]] as const) { mkdirSync(path.join(builds, target, "out"), { recursive: true }); writeFileSync(path.join(builds, target, "out", "index.html"), "x"); writeFileSync(path.join(builds, target, "built.json"), JSON.stringify(builtJson(target, id, bytes))); } r = await run(c, { HOME: h }, { freeBytes: () => 5e9 }); assert.equal(find(r, "publish", "index-stamp")?.status, "warn"); // export-builds sums the stamps' bytes, not the one-byte files on disk. assert.match(find(r, "publish", "export-builds")!.detail, /: 2 bundles, 9 MB; 5\.00 GB free$/); // A stamp: alpha was built from an older one. const now = Date.UTC(2026, 9, 6, 12); mkdirSync(c.paths.exportIndexDir, { recursive: true }); writeFileSync(path.join(c.paths.exportIndexDir, "stamp.json"), JSON.stringify(indexStampJson("cur-stamp", now - 3 * 3_600_000))); const before = tree(c.root); r = await run(c, { HOME: h }, { now: new Date(now) }); assert.equal(find(r, "publish", "index-stamp")?.status, "warn"); assert.match(find(r, "publish", "index-stamp")!.detail, /^cur-stamp, built 2026-10-06 09:00 \(3 hours ago\), generation 7; built from an older stamp: alpha — archilyzer publish build rebuilds each$/); // Everything from the current stamp: ok. writeFileSync(path.join(builds, "alpha", "built.json"), JSON.stringify(builtJson("alpha", "cur-stamp", 7_000_000))); const before2 = tree(c.root); r = await run(c, { HOME: h }, { now: new Date(now) }); assert.equal(find(r, "publish", "index-stamp")?.status, "ok"); assert.match(find(r, "publish", "index-stamp")!.detail, /generation 7; 2 bundles built from it$/); assert.equal(r.ok, true); assert.notDeepEqual(before, before2); assert.deepEqual(tree(c.root), before2); }); test("workspace: node is graded against the pinned wrangler's engines floor when wrangler is installed — a warning below it, never a failure", async () => { const c = checkout(); // No wrangler installed: next's floor only. let r = await run(c, {}, { nodeVersion: "20.11.0" }); assert.equal(find(r, "workspace", "node")?.status, "ok"); assert.equal(find(r, "workspace", "node")!.detail, "v20.11.0 (needs >= 20.9.0)"); const pkg = path.join(c.root, "common", "node_modules", "wrangler"); mkdirSync(pkg, { recursive: true }); writeFileSync(path.join(pkg, "package.json"), JSON.stringify({ name: "wrangler", version: "4.147.0", engines: { node: ">=22.0.0" } })); const before = tree(c.root); r = await run(c, {}, { nodeVersion: "20.11.0" }); assert.equal(find(r, "workspace", "node")?.status, "warn"); assert.match(find(r, "workspace", "node")!.detail, /^v20\.11\.0 — runs the apps .* the pinned wrangler 4\.147\.0 needs node >=22\.0\.0: every deploy refuses; use Node 22$/); assert.equal(r.ok, true); r = await run(c, {}, { nodeVersion: "22.23.3" }); assert.equal(find(r, "workspace", "node")?.status, "ok"); assert.equal(find(r, "workspace", "node")!.detail, "v22.23.3 (needs >= 20.9.0; deploys: >= 22.0.0, wrangler 4.147.0)"); r = await run(c, {}, { nodeVersion: "18.20.0" }); assert.equal(find(r, "workspace", "node")?.status, "fail"); assert.deepEqual(tree(c.root), before); }); test("publish: wrangler — the pinned binary or WRANGLER_BIN, run for its major (its debug log in a temp dir that is removed); a missing override fails", async () => { const c = checkout(); const h = home(c); const sitesDir = path.join(c.paths.transcriptsDir, "sites"); (c.paths as { sitesDir: string }).sitesDir = sitesDir; // Not installed, nothing deploys: a note naming `pnpm install`. let r = await run(c, { HOME: h }); assert.equal(find(r, "publish", "wrangler")?.status, "info"); assert.match(find(r, "publish", "wrangler")!.detail, /common\/node_modules\/\.bin\/wrangler is not there — run `pnpm install`/); // A site that deploys: a warning. mkdirSync(path.join(sitesDir, "alpha"), { recursive: true }); writeFileSync(path.join(sitesDir, "alpha", "site.json"), JSON.stringify({ title: "A", cloudflareProject: "alpha-pages" })); r = await run(c, { HOME: h }); assert.equal(find(r, "publish", "wrangler")?.status, "warn"); // WRANGLER_BIN naming nothing: a failure. r = await run(c, { HOME: h, WRANGLER_BIN: path.join(c.bin, "no-wrangler") }); assert.equal(find(r, "publish", "wrangler")?.status, "fail"); assert.equal(r.ok, false); // Fake wranglers that record where they were told to log. const seen = path.join(TMP, `${path.basename(c.root)}-wrangler-log-path`); const fakeWrangler = (name: string, body: string) => { const p = path.join(c.bin, name); writeFileSync(p, `#!/bin/sh\nprintf '%s' "$WRANGLER_LOG_PATH" > '${seen}'\n${body}\n`); chmodSync(p, 0o755); return p; }; const good = fakeWrangler("wrangler-4", "echo ' ⛅️ wrangler 4.147.0'"); const old = fakeWrangler("wrangler-3", "echo '3.114.0'"); const broken = fakeWrangler("wrangler-node20", "echo 'Wrangler requires at least Node.js v22.0.0. You are using v20.11.0.' >&2; exit 1"); const before = tree(c.root); r = await run(c, { HOME: h, WRANGLER_BIN: good }); assert.equal(find(r, "publish", "wrangler")?.status, "ok"); assert.equal(find(r, "publish", "wrangler")!.detail, `${good} 4.147.0 (WRANGLER_BIN)`); const { readFileSync, existsSync } = await import("node:fs"); const logPath = readFileSync(seen, "utf8"); assert.ok(logPath.startsWith(os.tmpdir()), `the debug log went to the temp dir, not HOME (${logPath})`); assert.equal(existsSync(logPath), false, "and that dir is removed"); r = await run(c, { HOME: h, WRANGLER_BIN: old }); assert.equal(find(r, "publish", "wrangler")?.status, "warn"); assert.match(find(r, "publish", "wrangler")!.detail, /3\.114\.0 \(WRANGLER_BIN\) — expected wrangler 4\.x/); r = await run(c, { HOME: h, WRANGLER_BIN: broken }); assert.equal(find(r, "publish", "wrangler")?.status, "warn"); assert.match(find(r, "publish", "wrangler")!.detail, /does not run: Wrangler requires at least Node\.js v22\.0\.0/); assert.deepEqual(tree(c.root), before); assert.deepEqual(readdirSync(h), [], "nothing under HOME"); }); // ── release 21 D4b: the seeder's egress ───────────────────────────────────── test("seeder: not configured is a note, and nothing is asked of the network", async () => { const c = checkout(); let asked = 0; const r = await run(c, {}, { seederEgress: async () => (asked++, { host: null, seeder: null }) }); assert.equal(find(r, "seeder", "seeder")?.status, "info"); assert.match(find(r, "seeder", "seeder")!.detail, /seeder not configured/); assert.equal(asked, 0); assert.equal(r.ok, true); }); test("seeder: the same egress as the host fails; a different one is ok; an unanswered one warns", async () => { const c = checkout(); writeFileSync(c.paths.settingsFile, JSON.stringify({ seeder: { sites: ["demo-private"] } })); const same = await run(c, {}, { seederEgress: async () => ({ host: "198.51.100.7", seeder: "198.51.100.7" }) }); assert.equal(find(same, "seeder", "egress")?.status, "fail"); assert.match(find(same, "seeder", "egress")!.detail, /SAME address as this host: it is not behind the VPN/); assert.equal(same.ok, false); const vpn = await run(c, {}, { seederEgress: async () => ({ host: "198.51.100.7", seeder: "203.0.113.9" }) }); assert.equal(find(vpn, "seeder", "egress")?.status, "ok"); const down = await run(c, {}, { seederEgress: async () => ({ host: "198.51.100.7", seeder: null, seederError: "No such container" }) }); assert.equal(find(down, "seeder", "egress")?.status, "warn"); assert.match(find(down, "seeder", "egress")!.detail, /No such container.*profile up/); });