UNPKG

renovate

Version:

Automated dependency updates. Flexible so you don't need to be.

181 lines • 7.47 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.hasValidTimezone = hasValidTimezone; exports.hasValidSchedule = hasValidSchedule; exports.cronMatches = cronMatches; exports.isScheduledNow = isScheduledNow; const tslib_1 = require("tslib"); const later_1 = tslib_1.__importDefault(require("@breejs/later")); const is_1 = tslib_1.__importDefault(require("@sindresorhus/is")); const croner_1 = require("croner"); const cronstrue_1 = tslib_1.__importDefault(require("cronstrue")); const luxon_1 = require("luxon"); const migration_1 = require("../../../../config/migration"); const logger_1 = require("../../../../logger"); const scheduleMappings = { 'every month': 'before 5am on the first day of the month', monthly: 'before 5am on the first day of the month', }; const minutesChar = '*'; function parseCron(scheduleText) { try { return new croner_1.CronPattern(scheduleText); } catch { return undefined; } } function hasValidTimezone(timezone) { if (!luxon_1.DateTime.local().setZone(timezone).isValid) { return [false, `Invalid schedule: Unsupported timezone ${timezone}`]; } return [true]; } function hasValidSchedule(schedule) { let message = ''; if (!schedule || schedule === 'at any time' || schedule[0] === 'at any time') { return [true]; } // check if any of the schedules fail to parse const hasFailedSchedules = schedule.some((scheduleText) => { const parsedCron = parseCron(scheduleText); if (parsedCron !== undefined) { if (parsedCron.minute.filter((v) => v !== 1).length !== 0 || !scheduleText.startsWith(minutesChar)) { message = `Invalid schedule: "${scheduleText}" has cron syntax, but doesn't have * as minutes`; return true; } // It was valid cron syntax and * as minutes return false; } const massagedText = (0, migration_1.fixShortHours)(scheduleMappings[scheduleText] || scheduleText); const parsedSchedule = later_1.default.parse.text(massagedText); if (parsedSchedule.error !== -1) { message = `Invalid schedule: Failed to parse "${scheduleText}"`; // It failed to parse return true; } if (parsedSchedule.schedules.some((s) => s.m)) { message = `Invalid schedule: "${scheduleText}" should not specify minutes`; return true; } if (!parsedSchedule.schedules.some((s) => !!s.M || s.d !== undefined || !!s.D || s.t_a !== undefined || !!s.t_b)) { message = `Invalid schedule: "${scheduleText}" has no months, days of week or time of day`; return true; } // It must be OK return false; }); if (hasFailedSchedules) { // If any fail then we invalidate the whole thing return [false, message]; } return [true]; } function cronMatches(cron, now, timezone) { const parsedCron = new croner_1.Cron(cron, { ...(timezone && { timezone }), legacyMode: false, }); // it will always parse because it is checked beforehand // istanbul ignore if if (!parsedCron) { return false; } // return the next date which matches the cron schedule const nextRun = parsedCron.nextRun(); // istanbul ignore if: should not happen if (!nextRun) { logger_1.logger.warn({ schedule: cron }, 'Invalid cron schedule. No next run is possible'); return false; } let nextDate = luxon_1.DateTime.fromJSDate(nextRun); if (timezone) { nextDate = nextDate.setZone(timezone); } return (nextDate.hour === now.hour && nextDate.day === now.day && nextDate.month === now.month); } function isScheduledNow(config, scheduleKey = 'schedule') { let configSchedule = config[scheduleKey]; logger_1.logger.debug( // TODO: types (#22198) `Checking schedule(schedule=${String(configSchedule)}, tz=${config.timezone}, now=${new Date().toISOString()})`); if (!configSchedule || configSchedule.length === 0 || configSchedule[0] === '' || configSchedule[0] === 'at any time') { logger_1.logger.debug('No schedule defined'); return true; } if (!is_1.default.array(configSchedule)) { logger_1.logger.warn({ schedule: configSchedule }, 'config schedule is not an array'); configSchedule = [configSchedule]; } const validSchedule = hasValidSchedule(configSchedule); if (!validSchedule[0]) { logger_1.logger.warn(validSchedule[1]); return true; } let now = luxon_1.DateTime.local(); logger_1.logger.trace(`now=${now.toISO()}`); // Adjust the time if repo is in a different timezone to renovate if (config.timezone) { logger_1.logger.debug(`Found timezone: ${config.timezone}`); const validTimezone = hasValidTimezone(config.timezone); if (!validTimezone[0]) { logger_1.logger.warn(validTimezone[1]); return true; } logger_1.logger.debug('Adjusting now for timezone'); now = now.setZone(config.timezone); logger_1.logger.trace(`now=${now.toISO()}`); } const currentDay = now.weekday; logger_1.logger.trace(`currentDay=${currentDay}`); // Get the number of seconds since midnight const currentSeconds = now .startOf('second') .diff(now.startOf('day'), 'seconds').seconds; logger_1.logger.trace(`currentSeconds=${currentSeconds}`); // Support a single string but massage to array for processing logger_1.logger.debug(`Checking ${configSchedule.length} schedule(s)`); // later is timezone agnostic (as in, it purely relies on the underlying UTC date/time that is stored in the Date), // which means we have to pass it a Date that has an underlying UTC date/time in the same timezone as the schedule const jsNow = now.setZone('utc', { keepLocalTime: true }).toJSDate(); // We run if any schedule matches const isWithinSchedule = configSchedule.some((scheduleText) => { const cronSchedule = parseCron(scheduleText); if (cronSchedule) { const cronScheduleSummary = cronstrue_1.default.toString(scheduleText, { throwExceptionOnParseError: false, }); logger_1.logger.debug(`Human-readable summary for cron:: ${cronScheduleSummary}`); // We have Cron syntax if (cronMatches(scheduleText, now, config.timezone)) { logger_1.logger.debug(`Matches schedule ${scheduleText}`); return true; } } else { // We have Later syntax const massagedText = scheduleMappings[scheduleText] || scheduleText; const parsedSchedule = later_1.default.parse.text((0, migration_1.fixShortHours)(massagedText)); logger_1.logger.debug({ parsedSchedule }, `Checking schedule "${scheduleText}"`); if (later_1.default.schedule(parsedSchedule).isValid(jsNow)) { logger_1.logger.debug(`Matches schedule ${scheduleText}`); return true; } } return false; }); if (!isWithinSchedule) { logger_1.logger.debug('Package not scheduled'); return false; } return true; } //# sourceMappingURL=schedule.js.map