diff --git a/apps/cron/20260201-000000/index.tsx b/apps/cron/20260201-000000/index.tsx index 344b452..62d4b48 100644 --- a/apps/cron/20260201-000000/index.tsx +++ b/apps/cron/20260201-000000/index.tsx @@ -438,9 +438,11 @@ app.get('/job/:app/:name', async c => { ← Back - - {job.app}/{job.name} - {statusLabel(job)} + + + {job.app}/{job.name} + {statusLabel(job)} + {job.state === 'running' ? 'Running...' : 'Run Now'} @@ -449,8 +451,8 @@ app.get('/job/:app/:name', async c => { Schedule {job.schedule} - Last run {formatRelative(job.lastRun)} - Duration {formatDuration(job.lastDuration)} + Last run {job.state === 'running' ? 'now' : formatRelative(job.lastRun)} + Duration {job.state === 'running' ? formatDuration(Date.now() - job.lastRun!) : formatDuration(job.lastDuration)} Next run {formatRelative(job.nextRun)} {job.lastError && ( @@ -459,17 +461,21 @@ app.get('/job/:app/:name', async c => { {job.lastError} )} - {job.lastOutput && ( + {job.lastOutput ? ( Output {job.lastOutput} - )} - {!job.lastError && !job.lastOutput && job.lastRun && ( + ) : job.state === 'running' ? ( + + Output + Waiting for output... + + ) : job.lastRun && !job.lastError ? ( No output - )} + ) : null} ) }) diff --git a/apps/cron/20260201-000000/lib/executor.ts b/apps/cron/20260201-000000/lib/executor.ts index dfae391..ab86598 100644 --- a/apps/cron/20260201-000000/lib/executor.ts +++ b/apps/cron/20260201-000000/lib/executor.ts @@ -3,13 +3,28 @@ import type { CronJob } from './schedules' import { getNextRun } from './scheduler' const APPS_DIR = process.env.APPS_DIR! +const TOES_DIR = process.env.TOES_DIR! const RUNNER = join(import.meta.dir, 'runner.ts') +async function readStream(stream: ReadableStream, append: (text: string) => void) { + const reader = stream.getReader() + const decoder = new TextDecoder() + while (true) { + const { done, value } = await reader.read() + if (done) break + append(decoder.decode(value, { stream: true })) + } +} + export async function executeJob(job: CronJob, onUpdate: () => void): Promise { if (job.state === 'disabled') return job.state = 'running' job.lastRun = Date.now() + job.lastOutput = undefined + job.lastError = undefined + job.lastExitCode = undefined + job.lastDuration = undefined onUpdate() const cwd = join(APPS_DIR, job.app, 'current') @@ -17,29 +32,35 @@ export async function executeJob(job: CronJob, onUpdate: () => void): Promise, text => { + job.lastOutput = (job.lastOutput || '') + text + }), + readStream(proc.stderr as ReadableStream, text => { + job.lastError = (job.lastError || '') + text + }), ]) const code = await proc.exited job.lastDuration = Date.now() - job.lastRun job.lastExitCode = code - job.lastError = code !== 0 ? stderr || 'Non-zero exit' : undefined - job.lastOutput = stdout || undefined + if (!job.lastError && code !== 0) job.lastError = 'Non-zero exit' + if (code === 0) job.lastError = undefined + if (!job.lastOutput) job.lastOutput = undefined job.state = 'idle' job.nextRun = getNextRun(job.id) // Log result console.log(`[cron] ${job.id} finished: code=${code} duration=${job.lastDuration}ms`) - if (stdout) console.log(stdout) - if (stderr) console.error(stderr) + if (job.lastOutput) console.log(job.lastOutput) + if (job.lastError) console.error(job.lastError) } catch (e) { job.lastDuration = Date.now() - job.lastRun job.lastExitCode = 1