feat: Implement internal scheduler for automatic daily execution with timezone detection

This commit is contained in:
2025-11-13 15:19:35 +01:00
parent 2959fc8c73
commit 3b06b4ae83
9 changed files with 637 additions and 80 deletions

View File

@@ -257,44 +257,25 @@ function extractStringField(obj: unknown, key: string): string | undefined {
return undefined
}
function extractBooleanField(obj: unknown, key: string): boolean | undefined {
if (obj && typeof obj === 'object' && key in obj) {
const value = (obj as Record<string, unknown>)[key]
return typeof value === 'boolean' ? value : undefined
}
return undefined
}
function buildSchedulingConfig(raw: unknown): ConfigScheduling | undefined {
if (!raw || typeof raw !== 'object') return undefined
const source = raw as Record<string, unknown>
const scheduling: ConfigScheduling = {
enabled: source.enabled === true,
type: typeof source.type === 'string' ? source.type as ConfigScheduling['type'] : undefined
enabled: source.enabled === true
}
// Priority 1: Simple time format (recommended)
const timeField = extractStringField(source, 'time')
if (timeField) {
scheduling.time = timeField
}
// Priority 2: Legacy cron format (backwards compatibility)
const cronRaw = source.cron
if (cronRaw && typeof cronRaw === 'object') {
scheduling.cron = {
schedule: extractStringField(cronRaw, 'schedule'),
workingDirectory: extractStringField(cronRaw, 'workingDirectory'),
nodePath: extractStringField(cronRaw, 'nodePath'),
logFile: extractStringField(cronRaw, 'logFile'),
user: extractStringField(cronRaw, 'user')
}
}
const taskRaw = source.taskScheduler
if (taskRaw && typeof taskRaw === 'object') {
const taskSource = taskRaw as Record<string, unknown>
scheduling.taskScheduler = {
taskName: extractStringField(taskRaw, 'taskName'),
schedule: extractStringField(taskRaw, 'schedule'),
frequency: typeof taskSource.frequency === 'string' ? taskSource.frequency as 'daily' | 'weekly' | 'once' : undefined,
workingDirectory: extractStringField(taskRaw, 'workingDirectory'),
runAsUser: extractBooleanField(taskRaw, 'runAsUser'),
highestPrivileges: extractBooleanField(taskRaw, 'highestPrivileges')
schedule: extractStringField(cronRaw, 'schedule')
}
}

View File

@@ -179,16 +179,6 @@ export class StartupValidator {
}
private validateConfig(config: Config): void {
const maybeSchedule = (config as unknown as { schedule?: unknown }).schedule
if (maybeSchedule !== undefined) {
this.addWarning(
'config',
'Legacy schedule settings detected in config.jsonc.',
'Remove schedule.* entries and use your operating system scheduler.',
'docs/schedule.md'
)
}
// Headless mode in Docker
if (process.env.FORCE_HEADLESS === '1' && config.browser?.headless === false) {
this.addWarning(