diff --git a/src/server.tsx b/src/server.tsx index 5059004..503b3b9 100644 --- a/src/server.tsx +++ b/src/server.tsx @@ -9,7 +9,7 @@ import type { Message } from "./shared/types" import { rewriteJsImports } from "./build" import { NOSE_ICON, NOSE_BIN, NOSE_DATA, NOSE_DIR, NOSE_ROOT_BIN, BUN_BIN, DEFAULT_PROJECT } from "./config" import { transpile, isFile, tilde, isDir } from "./utils" -import { serveApp, initWebapps } from "./webapp/server" +import { serveApp, initWebapps, shutdownWebapps } from "./webapp/server" import { apps } from "./webapp/utils" import { commands, commandPath, loadCommandModule } from "./commands" import { runCommandFn } from "./shell" @@ -217,6 +217,7 @@ if (process.env.BUN_HOT) { globalThis.__hot_reload_cleanup = () => { closeWebsockets() disconnectSneakers() + shutdownWebapps() } for (const sig of ["SIGINT", "SIGTERM"] as const) { diff --git a/src/webapp/server.ts b/src/webapp/server.ts index 471107b..dabe7f1 100644 --- a/src/webapp/server.ts +++ b/src/webapp/server.ts @@ -16,10 +16,14 @@ export type App = Hono | Handler const processes = new Map }>() const restarting = new Set() +let nextPort = 4000 export async function initWebapps() { await startSubprocs() startWatcher() + + process.on("SIGINT", shutdownWebapps) + process.on("SIGTERM", shutdownWebapps) } export async function serveApp(c: Context, subdomain: string): Promise { @@ -52,7 +56,7 @@ async function startApp(name: string): Promise { const existing = processes.get(name) if (existing) return existing.port - const port = String(4000 + processes.size) + const port = String(nextPort++) const proc = Bun.spawn({ cmd: [BUN_BIN, "run", "src/webapp/worker.ts", name], env: { PORT: port }, @@ -78,11 +82,16 @@ function serveStatic(path: string): Response { }) } -async function shutdown() { +export async function shutdownWebapps() { + wwwWatcher?.close() + nextPort = 4000 + for (const [name, { port, proc }] of processes) { webappLog(name, "Shutting down") try { proc.kill() } catch { } } + + processes.clear() process.exit(0) } @@ -90,6 +99,8 @@ async function restartApp(name: string) { if (restarting.has(name)) return restarting.add(name) + if (isStaticApp(name)) return + webappLog(name, "restarting") const existing = processes.get(name) if (existing) { @@ -108,7 +119,7 @@ async function startSubprocs() { } -let wwwWatcher +let wwwWatcher: any function startWatcher() { if (!expectDir(NOSE_DIR)) return @@ -116,13 +127,10 @@ function startWatcher() { if (!filename) return const [appName,] = filename.split("/") - if (appName && !filename.includes("/pub/")) + if (appName && /\.tsx?$/.test(filename)) restartApp(appName) if (/^.+\/index\.tsx?$/.test(filename)) sendAll({ type: "apps", data: apps() }) }) } - -process.on("SIGINT", shutdown) -process.on("SIGTERM", shutdown) diff --git a/src/webapp/worker.ts b/src/webapp/worker.ts index 750a800..72ea8e5 100644 --- a/src/webapp/worker.ts +++ b/src/webapp/worker.ts @@ -13,10 +13,19 @@ if (!appName) { const path = await appPath(appName) if (!path) throw `Can't find app: ${appName}` -const mod = await import(path) +let mod +try { + mod = await import(path) +} catch (err) { + webappLog(appName, `failed to import: ${err}`) + process.exit(1) +} const handler = mod.default -if (typeof handler !== "function") throw `no default export in ${appName}` +if (typeof handler !== "function") { + webappLog(appName, `no default export`) + process.exit(1) +} const app = new Hono() app.all("*", async c => toResponse(await handler(c)))