Refactor app reload logic into reloadApp function

This commit is contained in:
Chris Wanstrath 2026-05-11 15:52:01 -07:00
parent 3743a01c28
commit 31ca1d9849
2 changed files with 70 additions and 18 deletions

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 { loadGitignore } from '@gitignore'
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')
if (!appName) return c.json({ error: 'App name required' }, 400)
emit({ type: 'app:reload', app: appName })
// Register new app or restart existing
const app = allApps().find(a => a.name === appName)
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)
try {
await reloadApp(appName)
} catch (e) {
return c.json({ error: `Failed to reload app: ${e instanceof Error ? e.message : String(e)}` }, 500)
}
emit({ type: 'app:reload', app: appName })
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 }> {
const app = _apps.get(oldName)
if (!app) return { ok: false, error: 'App not found' }