109 lines
3.6 KiB
JavaScript
109 lines
3.6 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Runs the Fusion dashboard API server AND `vite dev` concurrently so the
|
|
* React SPA in packages/dashboard/app hot-reloads while still talking to a
|
|
* live API/WebSocket backend.
|
|
*
|
|
* Two processes:
|
|
* 1. API: `pnpm dev --prebuild=none dashboard --no-auth --port <API_PORT>`
|
|
* (source-mode API/engine; Vite serves the browser UI)
|
|
* 2. Vite: `vite dev` in packages/dashboard
|
|
* (serves app/ with HMR; proxies /api and WS to the API)
|
|
*
|
|
* Open the URL Vite prints (e.g. http://localhost:5173), NOT the API URL.
|
|
* Edits to packages/dashboard/app/** hot-reload. Edits to src/** (server
|
|
* code) still require restarting this script.
|
|
*
|
|
* Env:
|
|
* FUSION_API_PORT API port (default 4050). Vite's proxy reads the same
|
|
* var so both sides stay in sync.
|
|
* FUSION_VITE_PORT Vite dev port (default 5173).
|
|
*/
|
|
|
|
import { spawn } from "node:child_process";
|
|
import { fileURLToPath } from "node:url";
|
|
import { dirname, resolve } from "node:path";
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
const repoRoot = resolve(__dirname, "..");
|
|
const dashboardDir = resolve(repoRoot, "packages/dashboard");
|
|
|
|
const API_PORT = globalThis.process.env.FUSION_API_PORT ?? "4050";
|
|
const VITE_PORT = globalThis.process.env.FUSION_VITE_PORT ?? "5173";
|
|
|
|
const children = [];
|
|
let shuttingDown = false;
|
|
|
|
function prefix(label, color) {
|
|
const tag = `\x1b[${color}m[${label}]\x1b[0m`;
|
|
return (chunk) => {
|
|
const text = chunk.toString();
|
|
// Preserve trailing-newline semantics; prefix every non-empty line.
|
|
const lines = text.split("\n");
|
|
const last = lines.pop();
|
|
const prefixed = lines.map((l) => `${tag} ${l}`).join("\n");
|
|
globalThis.process.stdout.write(prefixed + (prefixed ? "\n" : "") + (last ? `${tag} ${last}` : ""));
|
|
};
|
|
}
|
|
|
|
function launch(name, color, command, args, options) {
|
|
const child = spawn(command, args, {
|
|
stdio: ["inherit", "pipe", "pipe"],
|
|
shell: true,
|
|
...options,
|
|
});
|
|
child.stdout.on("data", prefix(name, color));
|
|
child.stderr.on("data", prefix(name, color));
|
|
child.on("exit", (code, signal) => {
|
|
if (shuttingDown) return;
|
|
console.log(`\n[dev-hmr] ${name} exited (code=${code} signal=${signal}) — tearing down`);
|
|
shutdown(code ?? 1);
|
|
});
|
|
children.push({ name, child });
|
|
return child;
|
|
}
|
|
|
|
function shutdown(exitCode = 0) {
|
|
if (shuttingDown) return;
|
|
shuttingDown = true;
|
|
for (const { child } of children) {
|
|
if (!child.killed) {
|
|
try { child.kill("SIGINT"); } catch { void 0; }
|
|
}
|
|
}
|
|
// Hard kill after 5s if anyone is still alive.
|
|
setTimeout(() => {
|
|
for (const { child } of children) {
|
|
if (!child.killed) {
|
|
try { child.kill("SIGKILL"); } catch { void 0; }
|
|
}
|
|
}
|
|
globalThis.process.exit(exitCode);
|
|
}, 5000).unref();
|
|
}
|
|
|
|
globalThis.process.on("SIGINT", () => shutdown(0));
|
|
globalThis.process.on("SIGTERM", () => shutdown(0));
|
|
|
|
console.log(`[dev-hmr] starting API on :${API_PORT} + vite on :${VITE_PORT}`);
|
|
console.log(`[dev-hmr] open http://localhost:${VITE_PORT} for HMR (not the API URL)`);
|
|
|
|
// API: green. Vite owns the browser UI in this mode, so skip the dashboard
|
|
// client prebuild and run the API/engine directly from source.
|
|
launch(
|
|
"api",
|
|
"32",
|
|
"pnpm",
|
|
["dev", "--prebuild=none", "dashboard", "--no-auth", "--port", API_PORT, "--host", "127.0.0.1"],
|
|
{ cwd: repoRoot, env: { ...globalThis.process.env, FUSION_API_PORT: API_PORT } },
|
|
);
|
|
|
|
// Vite: cyan. Starts once; proxies /api (including WS) to the API port.
|
|
launch(
|
|
"vite",
|
|
"36",
|
|
"pnpm",
|
|
["exec", "vite", "dev", "--port", VITE_PORT],
|
|
{ cwd: dashboardDir, env: { ...globalThis.process.env, FUSION_API_PORT: API_PORT } },
|
|
);
|