From 9ce2995e0a3c487c0dda4144b735a9b0ecec3089 Mon Sep 17 00:00:00 2001 From: Chris Wanstrath Date: Fri, 10 Oct 2025 15:40:40 -0700 Subject: [PATCH] make it work --- bin/apps.tsx | 2 +- bin/share.ts | 2 +- bin/unshare.ts | 2 +- src/dns.ts | 2 +- src/helpers.tsx | 11 ++------ src/server.tsx | 3 ++- src/webapp/server.ts | 60 ++++++++++++++------------------------------ src/webapp/utils.ts | 50 +++++++++++++++++++++++++++++++++--- src/webapp/worker.ts | 6 ++--- 9 files changed, 77 insertions(+), 61 deletions(-) diff --git a/bin/apps.tsx b/bin/apps.tsx index b4e2e80..4377cac 100644 --- a/bin/apps.tsx +++ b/bin/apps.tsx @@ -1,7 +1,7 @@ // Show the webapps hosted on this NOSEputer. import { $ } from "bun" -import { apps } from "@/webapp/server" +import { apps } from "@/webapp/utils" const devMode = process.env.NODE_ENV !== "production" diff --git a/bin/share.ts b/bin/share.ts index af60100..928e5f8 100644 --- a/bin/share.ts +++ b/bin/share.ts @@ -1,6 +1,6 @@ // Share a webapp with the public internet. -import { apps } from "@/webapp/server" +import { apps } from "@/webapp/utils" import { connectSneaker, sneakers, sneakerUrl } from "@/sneaker" export default async function (app: string) { diff --git a/bin/unshare.ts b/bin/unshare.ts index 4110a7b..06de213 100644 --- a/bin/unshare.ts +++ b/bin/unshare.ts @@ -1,6 +1,6 @@ // Stop sharing a webapp with the public internet. -import { apps } from "@/webapp/server" +import { apps } from "@/webapp/utils" import { disconnectSneaker, sneakers } from "@/sneaker" export default async function (app: string) { diff --git a/src/dns.ts b/src/dns.ts index 8ec42cc..f0f8a77 100644 --- a/src/dns.ts +++ b/src/dns.ts @@ -2,7 +2,7 @@ // Publishes webapps as subdomains on your local network import { watch } from "fs" -import { apps } from "./webapp/server" +import { apps } from "./webapp/utils" import { expectDir } from "./utils" import { NOSE_DIR } from "./config" import { expectShellCmd } from "./utils" diff --git a/src/helpers.tsx b/src/helpers.tsx index 96ba07b..4a441e5 100644 --- a/src/helpers.tsx +++ b/src/helpers.tsx @@ -8,15 +8,8 @@ // import { css } from "@nose" import { Hono } from "hono" -import { type Handler, toResponse } from "./webapp/server" - -// -// command helpers -// - -// (none for now) - - +import { type Handler } from "./webapp/server" +import { toResponse } from "./webapp/utils" // // webapp helpers diff --git a/src/server.tsx b/src/server.tsx index 5daff01..8339f1d 100644 --- a/src/server.tsx +++ b/src/server.tsx @@ -9,7 +9,8 @@ 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, apps, initWebapps } from "./webapp/server" +import { serveApp, initWebapps } from "./webapp/server" +import { apps } from "./webapp/utils" import { commands, commandPath, loadCommandModule } from "./commands" import { runCommandFn } from "./shell" import { send, addWebsocket, removeWebsocket, closeWebsockets } from "./websocket" diff --git a/src/webapp/server.ts b/src/webapp/server.ts index ba4cec9..e3d3e75 100644 --- a/src/webapp/server.ts +++ b/src/webapp/server.ts @@ -4,11 +4,12 @@ import type { Child } from "hono/jsx" import { type Context, Hono } from "hono" import { join } from "path" -import { readdirSync, watch } from "fs" +import { watch } from "fs" import { sendAll } from "../websocket" import { expectDir } from "../utils" import { NOSE_DIR, BUN_BIN } from "../config" -import { isFile, isDir } from "../utils" +import { isFile } from "../utils" +import { apps, isApp, appDir, isStaticApp } from "./utils" export type Handler = (r: Context) => string | Child | Response | Promise export type App = Hono | Handler @@ -20,40 +21,27 @@ export function initWebapps() { } export async function serveApp(c: Context, subdomain: string): Promise { - const port = await startApp(subdomain) - if (!port) return c.text(`App not found: ${subdomain}`, 404) + if (!isApp(subdomain)) return c.text(`App not found: ${subdomain}`, 404) const staticPath = join(appDir(subdomain)!, "pub", c.req.path === "/" ? "/index.html" : c.req.path) if (isFile(staticPath)) return serveStatic(staticPath) - const res = await fetch(`http://localhost:${port}${c.req.path}`, { - method: c.req.method, - headers: c.req.raw.headers, - body: c.req.raw.body, - }) + if (isStaticApp(subdomain)) return c.text("File not found", 404) - return new Response(res.body, { status: res.status, headers: res.headers }) -} + const port = await startApp(subdomain) + if (!port) return c.text(`App not found: ${subdomain}`, 404) -export function apps(): string[] { - const apps: string[] = [] + try { + const res = await fetch(`http://localhost:${port}${c.req.path}`, { + method: c.req.method, + headers: c.req.raw.headers, + body: c.req.raw.body, + }) - for (const entry of readdirSync(NOSE_DIR)) - if (isApp(entry)) - apps.push(entry) - - return apps.sort() -} - -function isApp(name: string): boolean { - return isFile(join(NOSE_DIR, name, "index.ts")) - || isFile(join(NOSE_DIR, name, "index.tsx")) - || isDir(join(NOSE_DIR, name, "pub")) -} - -export function appDir(name: string): string | undefined { - if (isApp(name)) - return join(NOSE_DIR, name) + return new Response(res.body, { status: res.status, headers: res.headers }) + } catch { + return c.text("File not found", 404) + } } async function startApp(name: string): Promise { @@ -72,21 +60,11 @@ async function startApp(name: string): Promise { processes.set(name, { port, proc }) proc.exited.then(() => processes.delete(name)) + await Bun.sleep(50) + return port } -export async function toResponse(source: string | Child | Response): Promise { - if (source instanceof Response) - return source - else if (typeof source === "string") - return new Response(source) - else - return new Response(await source?.toString(), { - headers: { - "Content-Type": "text/html; charset=utf-8" - } - }) -} function serveStatic(path: string): Response { const file = Bun.file(path) diff --git a/src/webapp/utils.ts b/src/webapp/utils.ts index 857c22e..4bc70e0 100644 --- a/src/webapp/utils.ts +++ b/src/webapp/utils.ts @@ -1,9 +1,53 @@ import { join } from "path" +import { readdirSync } from "fs" +import type { Child } from "hono/jsx" import { NOSE_DIR } from "../config" +import { isFile, isDir } from "../utils" export async function appPath(appName: string): Promise { - const ts = join(NOSE_DIR, appName, "index.ts") - const tsx = join(NOSE_DIR, appName, "index.tsx") + const files = [join(NOSE_DIR, appName, "index.ts"), join(NOSE_DIR, appName, "index.tsx")] - return [ts, tsx].find(async file => await Bun.file(file).exists()) + for (const file of files) + if (await Bun.file(file).exists()) + return file +} + +export async function toResponse(source: string | Child | Response): Promise { + if (source instanceof Response) + return source + else if (typeof source === "string") + return new Response(source) + else + return new Response(await source?.toString(), { + headers: { + "Content-Type": "text/html; charset=utf-8" + } + }) +} + +export function apps(): string[] { + const apps: string[] = [] + + for (const entry of readdirSync(NOSE_DIR)) + if (isApp(entry)) + apps.push(entry) + + return apps.sort() +} + +export function isApp(name: string): boolean { + return isFile(join(NOSE_DIR, name, "index.ts")) + || isFile(join(NOSE_DIR, name, "index.tsx")) + || isDir(join(NOSE_DIR, name, "pub")) +} + +export function isStaticApp(name: string): boolean { + return !isFile(join(NOSE_DIR, name, "index.ts")) + && !isFile(join(NOSE_DIR, name, "index.tsx")) + && isDir(join(NOSE_DIR, name, "pub")) +} + +export function appDir(name: string): string | undefined { + if (isApp(name)) + return join(NOSE_DIR, name) } \ No newline at end of file diff --git a/src/webapp/worker.ts b/src/webapp/worker.ts index 352a1d9..178da56 100644 --- a/src/webapp/worker.ts +++ b/src/webapp/worker.ts @@ -2,11 +2,11 @@ // This is the child process that runs a single webapp. import { Hono } from "hono" -import { appPath } from "./utils" +import { appPath, toResponse } from "./utils" const appName = Bun.argv[2] if (!appName) { - console.log("usage: bun run webapp-worker ") + console.log("usage: bun run ./src/webapp/worker.ts ") process.exit(1) } @@ -19,7 +19,7 @@ const handler = mod.default if (typeof handler !== "function") throw `no default export in ${appName}` const app = new Hono() -app.all("*", handler) +app.all("*", async c => toResponse(await handler(c))) const port = Number(process.env.PORT || 4000) Bun.serve({ port, fetch: app.fetch })