export type Schedule = | "sunday" | "monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "week" | "day" | "midnight" | "noon" | "hour" | "30 minutes" | "15 minutes" | "5 minutes" | "1 minute" | "30minutes" | "15minutes" | "5minutes" | "1minute" | 30 | 15 | 5 | 1 | (string & {}) // time strings like "7am", "7:30pm", "14:00" export type CronJob = { id: string // "appname:filename" app: string name: string // filename without .ts file: string // full path schedule: Schedule cronExpr: string state: 'idle' | 'running' | 'disabled' lastRun?: number lastDuration?: number lastExitCode?: number lastError?: string lastOutput?: string nextRun?: number } export type InvalidJob = { id: string app: string name: string file: string error: string } export const SCHEDULES = [ '1 minute', '5 minutes', '15 minutes', '30 minutes', 'hour', 'noon', 'midnight', 'day', 'week', 'sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', ] as const const SCHEDULE_MAP: Record = { sunday: '0 0 * * 0', monday: '0 0 * * 1', tuesday: '0 0 * * 2', wednesday: '0 0 * * 3', thursday: '0 0 * * 4', friday: '0 0 * * 5', saturday: '0 0 * * 6', week: '0 0 * * 0', day: '0 0 * * *', midnight: '0 0 * * *', noon: '0 12 * * *', hour: '0 * * * *', '30 minutes': '0,30 * * * *', '15 minutes': '0,15,30,45 * * * *', '5 minutes': '*/5 * * * *', '1 minute': '* * * * *', '30minutes': '0,30 * * * *', '15minutes': '0,15,30,45 * * * *', '5minutes': '*/5 * * * *', '1minute': '* * * * *', } export function isValidSchedule(value: unknown): value is Schedule { if (typeof value === 'number') { return [1, 5, 15, 30].includes(value) } if (typeof value === 'string') { return value in SCHEDULE_MAP || parseTime(value) !== null } return false } function parseTime(s: string): { hour: number, minute: number } | null { // 12h: "7am", "7pm", "7:30am", "7:30pm", "12am", "12:00pm" const m12 = s.match(/^(\d{1,2})(?::(\d{2}))?\s*(am|pm)$/i) if (m12) { let hour = parseInt(m12[1]) const minute = m12[2] ? parseInt(m12[2]) : 0 const period = m12[3].toLowerCase() if (hour < 1 || hour > 12 || minute > 59) return null if (period === 'am' && hour === 12) hour = 0 else if (period === 'pm' && hour !== 12) hour += 12 return { hour, minute } } // 24h: "14:00", "0:00", "23:59" const m24 = s.match(/^(\d{1,2}):(\d{2})$/) if (m24) { const hour = parseInt(m24[1]) const minute = parseInt(m24[2]) if (hour > 23 || minute > 59) return null return { hour, minute } } return null } export function toCronExpr(schedule: Schedule): string { if (typeof schedule === 'number') { return SCHEDULE_MAP[`${schedule}minutes`]! } if (schedule in SCHEDULE_MAP) { return SCHEDULE_MAP[schedule]! } const time = parseTime(schedule)! return `${time.minute} ${time.hour} * * *` }