import { readdir } from 'fs/promises' import { existsSync } from 'fs' import { join } from 'path' import { isValidSchedule, toCronExpr, type CronJob, type Schedule } from './schedules' const APPS_DIR = process.env.APPS_DIR! export async function getApps(): Promise { const entries = await readdir(APPS_DIR, { withFileTypes: true }) const apps: string[] = [] for (const entry of entries) { if (!entry.isDirectory()) continue // Check if it has a current symlink (valid app) if (existsSync(join(APPS_DIR, entry.name, 'current'))) { apps.push(entry.name) } } return apps.sort() } export async function discoverCronJobs(): Promise { const jobs: CronJob[] = [] const apps = await readdir(APPS_DIR, { withFileTypes: true }) for (const app of apps) { if (!app.isDirectory()) continue const cronDir = join(APPS_DIR, app.name, 'current', 'cron') if (!existsSync(cronDir)) continue const files = await readdir(cronDir) for (const file of files) { if (!file.endsWith('.ts')) continue const filePath = join(cronDir, file) const name = file.replace(/\.ts$/, '') try { const mod = await import(filePath) const schedule = mod.schedule as Schedule if (!isValidSchedule(schedule)) { console.error(`Invalid schedule in ${filePath}: ${schedule}`) continue } jobs.push({ id: `${app.name}:${name}`, app: app.name, name, file: filePath, schedule, cronExpr: toCronExpr(schedule), state: 'idle', }) } catch (e) { console.error(`Failed to load cron file ${filePath}:`, e) } } } return jobs }