From efe4e4b4472af4f81e76b039b49dd68ccb1c7b5f Mon Sep 17 00:00:00 2001 From: Chris Wanstrath Date: Sat, 20 Sep 2025 21:23:53 -0700 Subject: [PATCH] living, breathing commands --- nose/bin/greet.ts | 3 +++ src/commands.ts | 19 +++++++++++++++++++ src/server.tsx | 24 +++++++++++++----------- src/shared/types.ts | 2 +- src/websocket.ts | 31 +++++++++++++++++++++++++++++++ 5 files changed, 67 insertions(+), 12 deletions(-) create mode 100644 nose/bin/greet.ts create mode 100644 src/commands.ts create mode 100644 src/websocket.ts diff --git a/nose/bin/greet.ts b/nose/bin/greet.ts new file mode 100644 index 0000000..340fb73 --- /dev/null +++ b/nose/bin/greet.ts @@ -0,0 +1,3 @@ +export default function (name: string): string { + return `Hi, ${name || "stranger"}!` +} \ No newline at end of file diff --git a/src/commands.ts b/src/commands.ts new file mode 100644 index 0000000..32df06a --- /dev/null +++ b/src/commands.ts @@ -0,0 +1,19 @@ +import { Glob } from "bun" +import { watch } from "fs" +import { NOSE_BIN } from "./config" +import { sendAll } from "./websocket" + +const cmdWatcher = watch(NOSE_BIN, async (event, filename) => { + sendAll({ type: "commands", data: await commands() }) +}) + +export async function commands(): Promise { + const glob = new Glob("**/*.{ts,tsx}") + let list: string[] = [] + + for await (const file of glob.scan(NOSE_BIN)) { + list.push(file.replace(".ts", "")) + } + + return list +} \ No newline at end of file diff --git a/src/server.tsx b/src/server.tsx index 900b000..33bab2b 100644 --- a/src/server.tsx +++ b/src/server.tsx @@ -3,11 +3,13 @@ import { serveStatic, upgradeWebSocket, websocket } from "hono/bun" import { prettyJSON } from "hono/pretty-json" import color from "kleur" +import type { Message } from "./shared/types" import { NOSE_ICON, NOSE_DIR, NOSE_BIN, NOSE_WWW } from "./config" import { transpile, isFile, tilde } from "./utils" import { apps, serveApp, publishDNS } from "./webapp" import { runCommand } from "./shell" -import type { Message } from "./shared/types" +import { commands } from "./commands" +import { send, addWebsocket, removeWebsocket, closeWebsockets, websockets } from "./websocket" import { Layout } from "./components/layout" import { Terminal } from "./components/terminal" @@ -52,13 +54,11 @@ app.on("GET", ["/js/:path{.+}", "/shared/:path{.+}"], async c => { // websocket // -const wsConnections: any[] = [] - app.get("/ws", upgradeWebSocket(async c => { return { - onOpen(_e, ws) { - wsConnections.push(ws) - ws.send(JSON.stringify({ type: "commands", data: ["hello", "echo"] })) + async onOpen(_e, ws) { + addWebsocket(ws) + send(ws, { type: "commands", data: await commands() }) }, async onMessage(event, ws) { let data: Message | undefined @@ -67,16 +67,18 @@ app.get("/ws", upgradeWebSocket(async c => { data = JSON.parse(event.data.toString()) } catch (e) { console.error("JSON parsing error", e) - ws.send(JSON.stringify({ type: "error", data: "json parsing error" })) + send(ws, { type: "error", data: "json parsing error" }) return } if (!data) return const result = await runCommand(data.data as string) - ws.send(JSON.stringify({ id: data.id, type: "output", data: result })) + send(ws, { id: data.id, type: "output", data: result }) + }, + onClose: (event, ws) => { + removeWebsocket(ws) }, - onClose: () => console.log('Connection closed'), } })) @@ -126,14 +128,14 @@ if (process.env.BUN_HOT) { // @ts-ignore globalThis.__hot_reload_cleanup = () => { - wsConnections.forEach(conn => conn?.close()) + closeWebsockets() } for (const sig of ["SIGINT", "SIGTERM"] as const) { process.on(sig, () => { // @ts-ignore globalThis.__hot_reload_cleanup?.() - process.exit() + process.exit(0) }) } } else { diff --git a/src/shared/types.ts b/src/shared/types.ts index c39505b..75f3044 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -1,7 +1,7 @@ export type Message = { session?: string id?: string - type: "input" | "output" | "commands" + type: "input" | "output" | "commands" | "error" data: CommandResult | string | string[] } diff --git a/src/websocket.ts b/src/websocket.ts new file mode 100644 index 0000000..b7867a0 --- /dev/null +++ b/src/websocket.ts @@ -0,0 +1,31 @@ +//// +// Manage open websockets, send messages... fun stuff like that. + +import type { Message } from "./shared/types" + +const wsConnections: any[] = [] + +export function send(ws: any, msg: Message) { + ws.send(JSON.stringify(msg)) +} + +export function sendAll(msg: Message) { + wsConnections.forEach(ws => send(ws, msg)) +} + +export function addWebsocket(ws: any) { + wsConnections.push(ws) +} + +export function removeWebsocket(ws: any) { + wsConnections.splice(wsConnections.indexOf(ws), 1) +} + +export function closeWebsockets() { + wsConnections.forEach(conn => conn.close()) + wsConnections.length = 0 +} + +export function websockets(): any[] { + return wsConnections +}