nyk
feat(refactor): ready for manual QA after main sync (#274)
b6ecafa unverified
export function describeCronFrequency(schedule: string): string {
const parts = schedule.replace(/\s*\([^)]+\)$/, '').trim().split(/\s+/)
if (parts.length !== 5) return schedule
const [minute, hour, dom, mon, dow] = parts
// Every minute
if (minute === '*' && hour === '*') return 'every minute'
// Every N minutes
if (minute.startsWith('*/') && hour === '*') return `every ${minute.slice(2)}m`
// Every hour at :MM
if (/^\d+$/.test(minute) && hour === '*') return `hourly at :${minute.padStart(2, '0')}`
// Every N hours
if (/^\d+$/.test(minute) && hour.startsWith('*/')) return `every ${hour.slice(2)}h`
// Specific hour(s) daily
if (/^\d+$/.test(minute) && /^\d+$/.test(hour) && dom === '*' && mon === '*') {
const h = Number(hour)
const m = Number(minute)
const time = `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}`
if (dow !== '*') return `${time} (select days)`
return `daily at ${time}`
}
// Weekly
if (dom === '*' && mon === '*' && dow !== '*') return 'weekly'
// Monthly
if (dom !== '*' && mon === '*' && dow === '*') return 'monthly'
return schedule
}
export function validateCronExpression(expr: string): string | null {
if (!expr || !expr.trim()) return 'Cron expression is required'
const parts = expr.trim().split(/\s+/)
if (parts.length !== 5) return `Expected 5 fields, got ${parts.length}`
const fieldNames = ['minute', 'hour', 'day of month', 'month', 'day of week']
const maxValues = [59, 23, 31, 12, 7]
const minValues = [0, 0, 1, 1, 0]
for (let i = 0; i < 5; i++) {
const field = parts[i]
if (field === '*') continue
// Step values: */N
if (field.startsWith('*/')) {
const step = Number(field.slice(2))
if (isNaN(step) || step < 1) return `Invalid step value in ${fieldNames[i]}: ${field}`
continue
}
// Comma-separated values and ranges
const segments = field.split(',')
for (const segment of segments) {
// Range: N-M
const rangeParts = segment.split('-')
for (const part of rangeParts) {
const num = Number(part)
if (isNaN(num)) return `Invalid value in ${fieldNames[i]}: ${part}`
if (num < minValues[i] || num > maxValues[i]) {
return `${fieldNames[i]} value ${num} out of range (${minValues[i]}-${maxValues[i]})`
}
}
}
}
return null
}
export function generateCloneName(name: string, existingNames: string[]): string {
const existing = new Set(existingNames.map(n => n.toLowerCase()))
let cloneName = `${name} (copy)`
let counter = 2
while (existing.has(cloneName.toLowerCase())) {
cloneName = `${name} (copy ${counter})`
counter++
}
return cloneName
}