diff --git a/src/server.tsx b/src/server.tsx index 8339f1d..c0c1659 100644 --- a/src/server.tsx +++ b/src/server.tsx @@ -238,7 +238,7 @@ if (process.env.NODE_ENV === "production") { await initNoseDir() initCommands() -initWebapps() +await initWebapps() initSneakers() console.log(color.cyan(NOSE_ICON)) diff --git a/src/webapp/server.ts b/src/webapp/server.ts index e3d3e75..2c8a39d 100644 --- a/src/webapp/server.ts +++ b/src/webapp/server.ts @@ -15,8 +15,10 @@ export type Handler = (r: Context) => string | Child | Response | Promise }>() +const restarting = new Set() -export function initWebapps() { +export async function initWebapps() { + await startSubprocs() startWatcher() } @@ -45,10 +47,11 @@ export async function serveApp(c: Context, subdomain: string): Promise } async function startApp(name: string): Promise { + if (isStaticApp(name)) return + const existing = processes.get(name) if (existing) return existing.port - const port = String(4000 + processes.size) const proc = Bun.spawn({ cmd: [BUN_BIN, "run", "src/webapp/worker.ts", name], @@ -58,9 +61,9 @@ async function startApp(name: string): Promise { }) processes.set(name, { port, proc }) - proc.exited.then(() => processes.delete(name)) + proc.exited.then(() => restartApp(name)) - await Bun.sleep(50) + await Bun.sleep(100) // give it time before first request return port } @@ -83,6 +86,28 @@ async function shutdown() { process.exit(0) } +async function restartApp(name: string) { + if (restarting.has(name)) return + restarting.add(name) + + console.log(`[child:${name}]`, "restarting") + const existing = processes.get(name) + if (existing) { + try { existing.proc.kill() } catch { } + processes.delete(name) + } + + await Bun.sleep(200) // give bun time to release the port + restarting.delete(name) + await startApp(name) +} + +async function startSubprocs() { + const list = apps() + await Promise.all(list.map(app => startApp(app))) +} + + let wwwWatcher function startWatcher() { if (!expectDir(NOSE_DIR)) return @@ -90,6 +115,10 @@ function startWatcher() { wwwWatcher = watch(NOSE_DIR, { recursive: true }, async (event, filename) => { if (!filename) return + const [appName,] = filename.split("/") + if (appName && !filename.includes("/pub/")) + restartApp(appName) + if (/^.+\/index\.tsx?$/.test(filename)) sendAll({ type: "apps", data: apps() }) })