Refactor app reload logic into dedicated function

This commit is contained in:
Chris Wanstrath 2026-05-12 18:29:05 -07:00
parent b303fa1eef
commit 7935469776
4 changed files with 75 additions and 21 deletions

View File

@ -196,12 +196,11 @@ router.post('/:app/start', c => {
return c.json({ ok: true }) return c.json({ ok: true })
}) })
router.post('/:app/restart', c => { router.post('/:app/restart', async c => {
const appName = c.req.param('app') const appName = c.req.param('app')
if (!appName) return c.json({ error: 'App not found' }, 404) if (!appName) return c.json({ error: 'App not found' }, 404)
stopApp(appName) await restartApp(appName)
startApp(appName)
return c.json({ ok: true }) return c.json({ ok: true })
}) })

View File

@ -1,4 +1,4 @@
import { APPS_DIR, allApps, emit, registerApp, removeApp, restartApp, startApp } from '$apps' import { APPS_DIR, allApps, emit, registerApp, reloadApp, removeApp, restartApp, startApp } from '$apps'
import { computeHash, generateManifest } from '../sync' import { computeHash, generateManifest } from '../sync'
import { loadGitignore } from '@gitignore' import { loadGitignore } from '@gitignore'
import { existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, rmSync, unlinkSync, watch, writeFileSync } from 'fs' import { existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, rmSync, unlinkSync, watch, writeFileSync } from 'fs'
@ -115,25 +115,14 @@ router.post('/apps/:app/reload', async c => {
const appName = c.req.param('app') const appName = c.req.param('app')
if (!appName) return c.json({ error: 'App name required' }, 400) if (!appName) return c.json({ error: 'App name required' }, 400)
emit({ type: 'app:reload', app: appName }) try {
await reloadApp(appName)
// Register new app or restart existing } catch (e) {
const app = allApps().find(a => a.name === appName) return c.json({ error: `Failed to reload app: ${e instanceof Error ? e.message : String(e)}` }, 500)
if (!app) {
// New app - register it
registerApp(appName)
} else if (app.state === 'running') {
// Existing app - restart it
try {
await restartApp(appName)
} catch (e) {
return c.json({ error: `Failed to restart app: ${e instanceof Error ? e.message : String(e)}` }, 500)
}
} else if (app.state === 'stopped' || app.state === 'invalid') {
// App not running - try to start it
startApp(appName)
} }
emit({ type: 'app:reload', app: appName })
return c.json({ ok: true }) return c.json({ ok: true })
}) })

View File

@ -176,6 +176,69 @@ export function registerApp(dir: string) {
} }
} }
export async function reloadApp(dir: string) {
const app = _apps.get(dir)
if (!app) {
// New app — register it
registerApp(dir)
return
}
// Re-read config from disk
const { pkg, error } = loadApp(dir)
const wasStatic = app.static
const nowStatic = !!pkg.toes?.static
// Update cached metadata
app.icon = pkg.toes?.icon ?? DEFAULT_EMOJI
app.tool = pkg.toes?.tool
app.apps = pkg.toes?.apps
app.dashboard = pkg.toes?.dashboard
app.share = pkg.toes?.share
app.static = pkg.toes?.static
app.error = error
if (error) {
// App is now invalid
if (app.state === 'running' || app.state === 'starting') {
stopApp(dir)
}
app.state = 'invalid'
update()
return
}
if (nowStatic) {
// Stop process if transitioning from process-based to static
if (!wasStatic && (app.state === 'running' || app.state === 'starting')) {
stopApp(dir)
// Wait for stop
const maxWait = 10000
const poll = 100
let waited = 0
while (_apps.get(dir)?.state !== 'stopped' && waited < maxWait) {
await new Promise(r => setTimeout(r, poll))
waited += poll
}
}
// Mark as running (static apps are always "running")
app.state = 'running'
app.started = Date.now()
app.manuallyStopped = false
update()
publishApp(dir)
openTunnelIfEnabled(dir, SERVER_PORT)
return
}
// Process-based app — restart it
if (app.state === 'running' || app.state === 'starting') {
await restartApp(dir)
} else {
startApp(dir)
}
}
export async function renameApp(oldName: string, newName: string): Promise<{ ok: boolean, error?: string }> { export async function renameApp(oldName: string, newName: string): Promise<{ ok: boolean, error?: string }> {
const app = _apps.get(oldName) const app = _apps.get(oldName)
if (!app) return { ok: false, error: 'App not found' } if (!app) return { ok: false, error: 'App not found' }

View File

@ -180,6 +180,9 @@ function openTunnel(appName: string, port: number, subdomain?: string, isReconne
onRequest(req) { onRequest(req) {
const app = getApp(appName) const app = getApp(appName)
if (app?.tunnelUrl) req.headers['x-app-url'] = app.tunnelUrl if (app?.tunnelUrl) req.headers['x-app-url'] = app.tunnelUrl
// Static apps are served by the main server via subdomain routing,
// so set the Host header so extractSubdomain() can identify the app
if (app?.static) req.headers['host'] = `${appName}.localhost`
}, },
onOpen(assignedSubdomain) { onOpen(assignedSubdomain) {