fabro/apps/fabro-web/scripts/build.test.ts
Bryan Helmkamp f4cfa50bc4
fix(server): make --watch-web honest and fast
In --watch-web dev mode the server silently fell back to the embedded SPA
snapshot whenever the disk dist/ was missing or partial, so edits to the
web app appeared not to take effect with no error anywhere. This change
makes the dev loop visible and quick:

- static_files plumbs a dev_disk_only flag from RouterOptions.watch_web
  into the fallback handler. When set, embedded fallback is skipped and
  a miss returns 503 with a "build in progress" auto-refresh page.
- The web build script writes each rebuild into apps/fabro-web/.dist-builds/<id>/
  and atomically replaces the dist symlink via rename(2), so requests
  never observe a partially-populated dist tree.
- Tailwind is invoked through node_modules/.bin/tailwindcss directly
  instead of bunx, removing a per-rebuild bun add @latest --force round-
  trip and dropping rebuild time from ~10s to ~250ms.
- load_asset no longer falls through to the workspace dist/ when an
  explicit asset_root is provided, restoring test isolation when a
  real dev build is sitting next door.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 10:36:01 -04:00

94 lines
2.6 KiB
TypeScript

import { test, expect } from "bun:test";
import { existsSync } from "node:fs";
import { lstat, readdir, readlink } from "node:fs/promises";
import { basename, join } from "node:path";
const root = Bun.fileURLToPath(new URL("..", import.meta.url));
async function runBuild() {
const process = Bun.spawn(["bun", "run", "scripts/build.ts"], {
cwd: root,
stdout: "pipe",
stderr: "pipe",
});
const code = await process.exited;
if (code !== 0) {
const stderr = await new Response(process.stderr).text();
const stdout = await new Response(process.stdout).text();
throw new Error(
`build failed with code ${code}\nstdout:\n${stdout}\nstderr:\n${stderr}`,
);
}
}
test("production build copies Pierre worker assets", async () => {
await runBuild();
const workerDist = join(root, "dist", "assets", "pierre-diffs-worker");
expect(existsSync(join(workerDist, "worker-portable.js"))).toBe(true);
const upstreamWorkerDir = join(
root,
"node_modules",
"@pierre",
"diffs",
"dist",
"worker",
);
const wasmFiles = (await readdir(upstreamWorkerDir))
.filter((file) => /^wasm-.*\.js$/.test(file))
.map((file) => basename(file));
for (const wasmFile of wasmFiles) {
expect(existsSync(join(workerDist, wasmFile))).toBe(true);
}
});
test("dist is a symlink into .dist-builds and old builds are pruned", async () => {
await runBuild();
await runBuild();
const distPath = join(root, "dist");
const stat = await lstat(distPath);
expect(stat.isSymbolicLink()).toBe(true);
const target = await readlink(distPath);
expect(target.startsWith(".dist-builds/")).toBe(true);
const buildId = target.slice(".dist-builds/".length);
const buildsRoot = join(root, ".dist-builds");
const remaining = await readdir(buildsRoot);
expect(remaining).toEqual([buildId]);
expect(existsSync(join(distPath, "index.html"))).toBe(true);
});
test("watch mode keeps running until interrupted", async () => {
const process = Bun.spawn([
"bun",
"run",
"scripts/build.ts",
"--watch",
], {
cwd: root,
stdout: "pipe",
stderr: "pipe",
});
const result = await Promise.race([
process.exited.then((code) => ({ kind: "exited" as const, code })),
Bun.sleep(1000).then(() => ({ kind: "running" as const })),
]);
if (result.kind === "exited") {
const stderr = await new Response(process.stderr).text();
const stdout = await new Response(process.stdout).text();
throw new Error(
`watch process exited unexpectedly with code ${result.code}\nstdout:\n${stdout}\nstderr:\n${stderr}`,
);
}
process.kill("SIGINT");
expect([0, 130]).toContain(await process.exited);
});