import { execFile as execFileCallback, spawn } from "node:child_process"; import { access } from "node:fs/promises"; import { constants } from "node:fs"; import path from "node:path"; import { promisify } from "node:util"; import { repoRoot, runIsolatedWorker } from "../dav-harness/server"; const execFile = promisify(execFileCallback); const tsxBinPath = path.join(repoRoot, "node_modules", "tsx", "dist", "cli.mjs"); const litmusBinaryPath = path.join(repoRoot, "vendor", "litmus", "litmus"); interface LitmusFixture { url: string; username: string; pat: string; host_label: string; } async function exists(filePath: string): Promise { try { await access(filePath, constants.X_OK); return true; } catch { return false; } } async function ensureLitmusBinary(): Promise { if (await exists(litmusBinaryPath)) return litmusBinaryPath; await execFile(process.execPath, [tsxBinPath, path.join(repoRoot, "scripts/litmus/setup.ts")], { cwd: repoRoot, env: { ...process.env }, timeout: 300_000, maxBuffer: 10 * 1024 * 1024, }); if (!(await exists(litmusBinaryPath))) throw new Error("litmus setup completed, but vendor/litmus/litmus was not found"); return litmusBinaryPath; } async function createLitmusFixture(port: number): Promise { const response = await fetch(`http://dab.localhost:${port}/__dab_test/fixtures/litmus`, { method: "POST" }); if (response.status !== 200) { throw new Error(`fixture route returned ${response.status}: ${await response.text()}`); } const body = (await response.json()) as Partial; if (!body.url || !body.username || !body.pat || !body.host_label) { throw new Error(`fixture route returned invalid payload: ${JSON.stringify(body)}`); } return body as LitmusFixture; } async function runLitmusClient(binaryPath: string, fixture: LitmusFixture, workingDirectory: string): Promise { await new Promise((resolve, reject) => { const child = spawn(binaryPath, [fixture.url, fixture.username, fixture.pat], { cwd: workingDirectory, stdio: "inherit", env: { ...process.env, TESTROOT: path.dirname(binaryPath) }, }); child.on("error", reject); child.on("exit", (code) => { if (code === 0) resolve(); else reject(new Error(`litmus exited with ${code}`)); }); }); } async function main(): Promise { const litmusBinary = await ensureLitmusBinary(); await runIsolatedWorker( { name: "dab-litmus", tempPrefix: "dab-litmus-", preserveEnv: "DAB_LITMUS_PRESERVE", oidcIssuer: process.env.DAB_LITMUS_OIDC_ISSUER ?? "http://localhost", d1DatabaseId: "local-litmus", r2BucketName: "dab-file-blobs-litmus", resourceSuffix: "litmus", devVars: { TESSERA_OIDC_CLIENT_ID: "dab-litmus", TESSERA_OIDC_CLIENT_SECRET: "litmus-local-client-secret", DAB_SESSION_SECRET: "litmus-local-session-secret", }, }, async ({ port, tempDir }) => { console.log(`healthz ok: http://dab.localhost:${port}/healthz`); const fixture = await createLitmusFixture(port); console.log(`litmus target: ${fixture.url}`); await runLitmusClient(litmusBinary, fixture, tempDir); }, ); } main().catch((error) => { console.error(error instanceof Error ? error.message : String(error)); process.exit(1); });