/** * `wrnexus dev` — the development supervisor. * * The child server process owns file watching and HMR now (see @wrnexus/dev-server): * - CSS and client-island edits update the live page over a WebSocket with no * process restart and no full reload. * - When a server module changes (it can't be re-imported in-process), the * child exits with RESTART_EXIT_CODE and this supervisor respawns it. The * browser reconnects and morphs in the new HTML — no visible refresh. * * The supervisor therefore only (re)launches the child; it does not watch files. */ import { spawn, type ChildProcess } from "node:child_process"; import { resolve, join } from "node:path"; import { existsSync, watch, type FSWatcher } from "node:fs"; import { fileURLToPath } from "node:url"; import { RESTART_EXIT_CODE } from "@wrnexus/dev-server"; // Resolve the dev server child entry through the package (not a source path) so // it works whether @wrnexus/dev-server is a workspace or an installed dependency. const SERVE_ENTRY = fileURLToPath(import.meta.resolve("@wrnexus/dev-server/serve-entry")); export function runDev( appRoot: string, port: number, hostname = "::", tls?: { certFile: string; keyFile: string }, ): void { const appDir = join(resolve(appRoot), "app"); let child: ChildProcess | null = null; let shuttingDown = false; const spawnChild = (): void => { child = spawn( process.execPath, // the Bun binary [ SERVE_ENTRY, appDir, String(port), "development", hostname, "true", ...(tls ? [tls.certFile, tls.keyFile] : []), ], { stdio: "inherit" }, ); child.on("exit", (code, signal) => { if (shuttingDown || signal) return; if (code === RESTART_EXIT_CODE) { spawnChild(); // requested restart — respawn immediately return; } if (code && code !== 0) { // Crash (e.g. a syntax error). Respawn after a short delay so the // watcher comes back and the server auto-recovers once it's fixed. console.error(`[wrnexus] server exited (code ${code}); retrying in 1.2s…`); setTimeout(() => { if (!shuttingDown) spawnChild(); }, 1200); } }); }; console.log(`\n ⚡ WrNexus dev (HMR) — ${appDir}`); // Regenerate typed DB queries + typed routes once before starting, then launch. // (Best effort; rerun `wrnexus db generate` after editing .sql.) void (async () => { try { const { loadAppConfig } = await import("@wrnexus/styles"); const { regenerateAllQueries } = await import("./db.ts"); const config = await loadAppConfig(resolve(appRoot)); await regenerateAllQueries(appDir, config); // default + every named database console.log(" ↻ db queries generated"); } catch { /* non-fatal */ } try { const { regenerateRoutes } = await import("./routes.ts"); const n = regenerateRoutes(appDir); console.log(` ↻ ${n} typed routes generated`); } catch { /* non-fatal */ } spawnChild(); })(); const shutdown = () => { shuttingDown = true; child?.kill(); process.exit(0); }; process.on("SIGINT", shutdown); process.on("SIGTERM", shutdown); } export async function runProductionDev( appRoot: string, port: number, hostname = "::", ): Promise { const root = resolve(appRoot); let child: ChildProcess | undefined; let building = false; let pending = false; let timer: ReturnType | undefined; const { runBuild } = await import("./build.ts"); const rebuild = async (): Promise => { if (building) { pending = true; return; } building = true; try { await runBuild(root); child?.kill(); const { runPreview } = await import("./preview.ts"); child = runPreview(root, { port, hostname, developmentRuntime: true }); } catch (error) { console.error( "[wrnexus] production-runtime rebuild failed; keeping the last good server.", error, ); } finally { building = false; if (pending) { pending = false; void rebuild(); } } }; console.log(`\n ⚡ WrNexus dev (exact production runtime) — ${root}`); await rebuild(); const watchers: FSWatcher[] = []; for (const name of [ "app", "public", "wrnexus.config.ts", "wrnexus.config.js", "wrnexus.config.mjs", ]) { const target = join(root, name); if (!existsSync(target)) continue; watchers.push( watch(target, { recursive: true }, () => { if (timer) clearTimeout(timer); timer = setTimeout(() => void rebuild(), 120); }), ); } const shutdown = (): void => { watchers.forEach((watcher) => watcher.close()); child?.kill(); process.exit(0); }; process.on("SIGINT", shutdown); process.on("SIGTERM", shutdown); }