first commit
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* `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 { 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 = "::"): 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],
|
||||
{ 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);
|
||||
}
|
||||
Reference in New Issue
Block a user