// Shared by the scene gate, the clip recorder, and the card cutter: the // Playwright chromium they all drive, a private game server on a // throwaway data dir, ffmpeg with the flags they all want, and the // reel's rewind. Run from the repo root after `npx vite build` in // packages/web. import { execFileSync, spawn } from "node:child_process"; import { existsSync, mkdtempSync, readdirSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; /** The Playwright chromium executable: $WIZWAR_CHROME, else the newest * build in the ms-playwright cache. */ export function chromiumExe() { if (process.env.WIZWAR_CHROME) return process.env.WIZWAR_CHROME; const cache = join(process.env.HOME ?? "", "Library", "Caches", "ms-playwright"); const builds = existsSync(cache) ? readdirSync(cache).filter((d) => /^chromium-\d+$/.test(d)).sort() : []; for (const build of builds.reverse()) { const exe = join(cache, build, "chrome-mac-arm64", "Google Chrome for Testing.app", "Contents", "MacOS", "Google Chrome for Testing"); if (existsSync(exe)) return exe; } throw new Error("no Playwright chromium found — run: npx playwright-core install chromium (or set WIZWAR_CHROME)"); } /** A game server on `port` with its own empty data dir. `up()` resolves * once it answers; `stop()` kills it and removes the dir. */ export function startServer(port, label = "wizwar-harness") { const dataDir = mkdtempSync(join(tmpdir(), `${label}-`)); const server = spawn("npx", ["tsx", "src/index.ts"], { cwd: join(import.meta.dirname, "..", "..", "packages", "server"), env: { ...process.env, PORT: String(port), WIZWAR_DATA_DIR: join(dataDir, "rooms") }, stdio: "ignore", }); const up = async () => { for (let i = 0; i < 60; i++) { try { if ((await fetch(`http://127.0.0.1:${port}/`)).ok) return; } catch { /* not yet */ } await new Promise((r) => setTimeout(r, 500)); } throw new Error("server never came up"); }; const stop = () => { server.kill(); rmSync(dataDir, { recursive: true, force: true }); }; return { up, stop }; } /** ffmpeg, quiet and overwriting. */ export const ff = (...args) => execFileSync("ffmpeg", ["-loglevel", "error", "-y", ...args]); /** Pause the reel and step it back to its first move. */ export async function rewindReel(page) { await page.locator("button", { hasText: "❚❚" }).click(); const count = Number((await page.locator(".replay-count").textContent()).match(/of (\d+)/)[1]); for (let i = 0; i < count; i++) await page.locator("button", { hasText: "◀" }).click(); return count; }