From fa459c45eba08edb845d2457b8406fa9eeb6499e Mon Sep 17 00:00:00 2001
From: Chris Wanstrath <2+defunkt@users.noreply.github.com>
Date: Tue, 27 Jan 2026 14:53:05 -0800
Subject: [PATCH] hot
---
TODO.txt | 3 ++-
apps/basic/package.json | 2 +-
apps/profile/index.tsx | 5 +---
apps/profile/package.json | 2 +-
src/server/index.tsx | 52 ++++++++++++++++++++++++++++-----------
5 files changed, 43 insertions(+), 21 deletions(-)
diff --git a/TODO.txt b/TODO.txt
index 24cef74..80e7bb5 100644
--- a/TODO.txt
+++ b/TODO.txt
@@ -6,7 +6,8 @@
[x] scans for apps/**/package.json, scripts.toes
[x] runs that for each, giving it a PORT
[x] has GET / page that shows all the running apps/status/port
-[ ] watch each app and restart it on update
+[x] watch each app and restart it on update
+[x] watches for and adds/removes apps
## cli
diff --git a/apps/basic/package.json b/apps/basic/package.json
index 6fe680b..e8be2ee 100644
--- a/apps/basic/package.json
+++ b/apps/basic/package.json
@@ -4,7 +4,7 @@
"type": "module",
"private": true,
"scripts": {
- "toes": "bun start",
+ "toes": "bun run --watch index.tsx",
"start": "bun run index.tsx",
"dev": "bun run --hot index.tsx"
},
diff --git a/apps/profile/index.tsx b/apps/profile/index.tsx
index a7920b0..62cb5cb 100644
--- a/apps/profile/index.tsx
+++ b/apps/profile/index.tsx
@@ -2,9 +2,6 @@ import { Hype } from 'hype'
const app = new Hype
-app.get('/', c => c.html(
Hi there!
))
-
-const apps = () => {
-}
+app.get('/', c => c.html(My Profile!!!
))
export default app.defaults
diff --git a/apps/profile/package.json b/apps/profile/package.json
index 6fe680b..e8be2ee 100644
--- a/apps/profile/package.json
+++ b/apps/profile/package.json
@@ -4,7 +4,7 @@
"type": "module",
"private": true,
"scripts": {
- "toes": "bun start",
+ "toes": "bun run --watch index.tsx",
"start": "bun run index.tsx",
"dev": "bun run --hot index.tsx"
},
diff --git a/src/server/index.tsx b/src/server/index.tsx
index 5d04e6e..2dba9cf 100644
--- a/src/server/index.tsx
+++ b/src/server/index.tsx
@@ -1,6 +1,6 @@
-import { Subprocess } from 'bun'
+import type { Subprocess } from 'bun'
import { Hype } from 'hype'
-import { readdirSync, readFileSync } from 'fs'
+import { existsSync, readdirSync, readFileSync, watch } from 'fs'
import { join } from 'path'
const APPS_DIR = join(process.env.DATA_DIR ?? '.', 'apps')
@@ -13,14 +13,14 @@ type RunningApp = {
const runningApps = new Map()
-const err = (app: string, ...msg: string[]) =>
+const err = (app: string, ...msg: string[]) =>
console.error('๐พ', `${app}:`, ...msg)
-const info = (app: string, ...msg: string[]) =>
+const info = (app: string, ...msg: string[]) =>
console.log('๐พ', `${app}:`, ...msg)
-const log = (app: string, ...msg: string[]) =>
- console.log('๐พ', `${app}ยป`, ...msg)
+const log = (app: string, ...msg: string[]) =>
+ console.log(`<${app}>`, ...msg)
const appNames = () => {
return readdirSync(APPS_DIR, { withFileTypes: true })
@@ -67,16 +67,20 @@ const loadApp = (dir: string) => {
}
}
-const runApp = (dir: string, port: number) => {
+const runApp = async (dir: string, port: number) => {
const pkg = loadApp(dir)
if (!pkg.scripts?.toes) return
const cwd = join(APPS_DIR, dir)
- const cmd = ['bun', 'run', 'toes']
+
+ const needsInstall = !existsSync(join(cwd, 'node_modules'))
+ if (needsInstall) info(dir, 'Installing dependencies...')
+ const install = Bun.spawn(['bun', 'install'], { cwd, stdout: 'pipe', stderr: 'pipe' })
+ await install.exited
info(dir, `Starting on port ${port}...`)
- const proc = Bun.spawn(cmd, {
+ const proc = Bun.spawn(['bun', 'run', 'toes'], {
cwd,
env: { ...process.env, PORT: String(port) },
stdout: 'pipe',
@@ -105,15 +109,15 @@ const runApp = (dir: string, port: number) => {
// Handle process exit
proc.exited.then(code => {
- if (code !== 0)
+ if (code !== 0)
err(dir, `Exited with code ${code}`)
- else
+ else
info(dir, 'Stopped')
runningApps.delete(dir)
})
}
-const getRunningApps = () =>
+const getRunningApps = () =>
Array.from(runningApps.values()).map(({ name, port }) => ({ name, port }))
const stopApp = (dir: string) => {
@@ -124,9 +128,27 @@ const stopApp = (dir: string) => {
}
}
-console.log('๐พ Toes!')
-runApps()
+const watchAppsDir = () => {
+ watch(APPS_DIR, (event, filename) => {
+ console.log('apps dir')
+ if (!filename) return
+ if (isApp(filename) && !runningApps.has(filename)) {
+ const port = getPort()
+ runApp(filename, port)
+ }
+
+ if (runningApps.has(filename) && !isApp(filename))
+ stopApp(filename)
+ })
+}
+
+const startup = () => {
+ console.log('๐พ Toes!')
+
+ runApps()
+ watchAppsDir()
+}
const app = new Hype()
@@ -139,5 +161,7 @@ app.get('/', c => {
)
})
+startup()
+
export { getRunningApps, stopApp }
export default app.defaults