Refactor app reload to handle state transitions
This commit is contained in:
parent
c634d488b6
commit
b303fa1eef
|
|
@ -1,4 +1,4 @@
|
||||||
import { APPS_DIR, allApps, emit, registerApp, reloadApp, removeApp, restartApp, startApp } from '$apps'
|
import { APPS_DIR, allApps, emit, registerApp, 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,14 +115,25 @@ 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)
|
||||||
|
|
||||||
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 })
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
return c.json({ ok: true })
|
return c.json({ ok: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -176,69 +176,6 @@ 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' }
|
||||||
|
|
|
||||||
|
|
@ -180,9 +180,6 @@ 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) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user