it's alive

This commit is contained in:
Chris Wanstrath 2025-09-15 15:46:44 -07:00
parent 465ed69fc3
commit 45286920de
6 changed files with 99 additions and 16 deletions

View File

@ -1,7 +1,7 @@
import { text } from "./other"
import { css } from "@utils"
export default (c: Context) => {
export default () => {
return <>
{css`
body {

29
nose/app/routes.tsx Normal file
View File

@ -0,0 +1,29 @@
import { routes } from "@utils"
export default routes({
"GET /": index,
"GET /pets": pets
})
function index() {
return <>
<h1>Hi world!</h1>
<p>Welcome to my personal web page.</p>
<p>If you are looking for information on pets, click here:</p>
<p><a href="/pets">PETS</a></p>
</>
}
function pets(c: Context) {
return c.html(<>
<ul>
<li>dogs</li>
<li>cats</li>
<li>iguanas</li>
<li>hamsters</li>
<li>snakes</li>
<li>chickens</li>
<li>...even goats!</li>
</ul>
</>)
}

View File

@ -1,11 +1,11 @@
{
"name": "pluto",
"module": "index.ts",
"module": "src/server.tsx",
"type": "module",
"private": true,
"scripts": {
"start": "bun src/server.ts",
"dev": "bun --hot src/server.ts"
"start": "bun src/server.tsx",
"dev": "bun --hot src/server.tsx"
},
"alias": {
"@utils": "./src/utils.tsx",

View File

@ -5,7 +5,7 @@ import color from "kleur"
import { NOSE_ICON, NOSE_DIR, NOSE_BIN, NOSE_APP } from "./config"
import { transpile, isFile } from "./utils"
import { serveApp } from "./webapp"
import { apps, serveApp } from "./webapp"
//
// Hono setup
@ -43,12 +43,29 @@ app.get("/js/:path{.+}", async c => {
// app routes
//
app.get("*", async c => {
app.use("*", async (c, next) => {
const url = new URL(c.req.url)
const domains = url.hostname.split(".")
const subdomain = domains.length > 1 ? domains[0]! : "none"
const subdomain = domains.length > 1 ? domains[0]! : ""
return await serveApp(c, subdomain)
if (subdomain) {
const app = serveApp(c, subdomain)
return await app
}
return next()
})
app.get("/", c => {
const url = new URL(c.req.url)
const domain = url.hostname
const port = url.port
return c.html(<>
<h1>apps</h1>
<ul>{apps().map(app => <li><a href={`http://${app}.${domain}:${port}`}>{app}</a></li>)}
</ul>
</>)
})
//

View File

@ -1,5 +1,7 @@
import { Hono } from "hono"
import { statSync } from "node:fs"
import { stat } from "node:fs/promises"
import { type Handler, toResponse } from "./webapp"
// Is the given `path` a file?
export function isFile(path: string): boolean {
@ -26,7 +28,6 @@ export function randomID(): string {
return Math.random().toString(36).slice(2, 10)
}
const transpiler = new Bun.Transpiler({ loader: 'tsx' })
const transpileCache: Record<string, string> = {}
@ -65,3 +66,25 @@ export function js(strings: TemplateStringsArray, ...values: any[]) {
}, '')
}} />
}
// for defining routes in your NOSE webapp
// example:
// export default routes({
// "GET /": index,
// "GET /pets": pets
// })
export function routes(def: Record<string, Handler>): Hono {
const app = new Hono
for (const key in def) {
const parts = key.split(" ") // GET /path
const method = parts[0] || "GET"
const path = parts[1] || "/"
console.log(method, path, def[key])
//@ts-ignore
app.on(method, path, async c => toResponse(await def[key](c)))
}
return app
}

View File

@ -1,18 +1,32 @@
import type { Context } from "hono"
import { type Context, Hono } from "hono"
import type { Child } from "hono/jsx"
import { renderToString } from "hono/jsx/dom/server"
import { join } from "node:path"
import { readdirSync } from "node:fs"
import { NOSE_APP } from "./config"
import { isFile } from "./utils"
type App = (r: Context) => string | Child | Response
export type Handler = (r: Context) => string | Child | Response | Promise<Response>
export type App = Hono | Handler
export async function serveApp(c: Context, subdomain: string): Promise<Response> {
const app = await findApp(subdomain)
if (app)
return await toResponse(app(c))
if (!app) return c.text(`App Not Found: ${subdomain}`, 404)
return c.text(`App Not Found: ${subdomain}`, 404)
if (app instanceof Hono)
return app.fetch(c.req.raw)
else
return toResponse(app(c))
}
export function apps(): string[] {
const apps: string[] = []
for (const entry of readdirSync(NOSE_APP))
apps.push(entry.replace(/\.tsx?/, ""))
return apps
}
async function findApp(name: string): Promise<App | undefined> {
@ -43,13 +57,13 @@ async function loadApp(path: string): Promise<App | undefined> {
return mod.default as App
}
async function toResponse(source: string | Child | Response): Promise<Response> {
export function toResponse(source: string | Child | Response): Response {
if (source instanceof Response)
return source
else if (typeof source === "string")
return new Response(source)
else
return new Response(await source?.toString(), {
return new Response(renderToString(source), {
headers: {
"Content-Type": "text/html; charset=utf-8"
}