renovate
Version:
Automated dependency updates. Flexible so you don't need to be.
181 lines • 7.47 kB
JavaScript
;
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