import { execFileSync } from "node:child_process"; import fs from "node:fs"; import path from "node:path"; import { expect, test } from "@playwright/test"; // site.json `archives: false` is a BUILD-time switch: compose-site skips the // zips and writes no archives/manifest.json, and the Downloads page plus its // Header/Footer links all key off that manifest's presence (app/lib/archives.ts // hasArchives()). The dev server this suite drives reads the checked-out // export/public, so the switch is exercised where it acts — one compose-site // run per case into a scratch EXPORT_PUBLIC_DIR under the test's output dir, // over a scratch site with no channels. No browser. const TSX = path.resolve(process.cwd(), "node_modules", ".bin", "tsx"); const COMPOSE = path.resolve(process.cwd(), "..", "common", "bin", "compose-site.ts"); function compose(root: string, site: Record) { const sitesDir = path.join(root, "transcripts", "sites", "scratchsite"); fs.mkdirSync(sitesDir, { recursive: true }); fs.writeFileSync( path.join(sitesDir, "site.json"), JSON.stringify({ siteId: "scratchsite", siteTitle: "Scratch", siteDescription: "", headerTitle: "Scratch", homeTagline: "", groups: [{ id: "default", name: "All", selectedByDefault: true }], defaultGroupId: "default", channels: [], ...site, }), ); const publicDir = path.join(root, "public"); // A stale bundle from an earlier (archives-on) build: a disabled site must // never serve it. fs.mkdirSync(path.join(publicDir, "archives"), { recursive: true }); fs.writeFileSync( path.join(publicDir, "archives", "manifest.json"), JSON.stringify({ entries: [{ filename: "stale.zip" }] }), ); fs.writeFileSync(path.join(root, "settings.json"), "{}"); execFileSync(TSX, [COMPOSE], { cwd: process.cwd(), env: { ...process.env, SITE_ID: "scratchsite", TRANSCRIPTS_DIR: path.join(root, "transcripts"), SITES_DIR: path.join(root, "transcripts", "sites"), EXPORT_PUBLIC_DIR: publicDir, EXPORT_INDEX_DIR: path.join(root, ".export-index"), EXPORT_BUILDS_DIR: path.join(root, ".export-builds"), SETTINGS_FILE: path.join(root, "settings.json"), BUILD_ARCHIVES: "1", }, stdio: "pipe", }); return path.join(publicDir, "archives", "manifest.json"); } test("archives: false composes no zip manifest (and clears a stale one)", async ({}, testInfo) => { const manifest = compose(testInfo.outputPath("off"), { archives: false }); expect(fs.existsSync(manifest)).toBe(false); }); test("archives absent (default on) composes a manifest", async ({}, testInfo) => { const manifest = compose(testInfo.outputPath("on"), {}); expect(fs.existsSync(manifest)).toBe(true); const parsed = JSON.parse(fs.readFileSync(manifest, "utf8")) as { entries: { filename: string }[]; }; // A fresh manifest from this build, never the stale bundle. expect(parsed.entries.map((e) => e.filename)).not.toContain("stale.zip"); });