make it work

This commit is contained in:
Chris Wanstrath 2025-10-10 15:40:40 -07:00
parent 3ec5b8d1e5
commit 9ce2995e0a
9 changed files with 77 additions and 61 deletions

View File

@ -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"

View File

@ -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) {

View File

@ -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) {

View File

@ -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"

View File

@ -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

View File

@ -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"

View File

@ -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<Response>
export type App = Hono | Handler
@ -20,12 +21,17 @@ export function initWebapps() {
}
export async function serveApp(c: Context, subdomain: string): Promise<Response> {
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)
if (isStaticApp(subdomain)) return c.text("File not found", 404)
const port = await startApp(subdomain)
if (!port) return c.text(`App not found: ${subdomain}`, 404)
try {
const res = await fetch(`http://localhost:${port}${c.req.path}`, {
method: c.req.method,
headers: c.req.raw.headers,
@ -33,27 +39,9 @@ export async function serveApp(c: Context, subdomain: string): Promise<Response>
})
return new Response(res.body, { status: res.status, headers: res.headers })
}
export function apps(): string[] {
const apps: string[] = []
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)
} catch {
return c.text("File not found", 404)
}
}
async function startApp(name: string): Promise<string | undefined> {
@ -72,21 +60,11 @@ async function startApp(name: string): Promise<string | undefined> {
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<Response> {
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)

View File

@ -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<string | undefined> {
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<Response> {
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)
}

View File

@ -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 <app-name>")
console.log("usage: bun run ./src/webapp/worker.ts <app-name>")
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 })