mirror of
https://github.com/renovatebot/renovate.git
synced 2025-01-11 06:26:26 +00:00
feat: use cron-parser (#23142)
Co-authored-by: Sebastian Poxhofer <secustor@users.noreply.github.com>
This commit is contained in:
parent
84e17317d6
commit
e635f0e296
5 changed files with 47 additions and 18 deletions
|
@ -3369,7 +3369,7 @@ To restrict `aws-sdk` to only monthly updates, you could add this package rule:
|
|||
|
||||
Technical details: We mostly rely on the text parsing of the library [@breejs/later](https://github.com/breejs/later) but only its concepts of "days", "time_before", and "time_after".
|
||||
Read the parser documentation at [breejs.github.io/later/parsers.html#text](https://breejs.github.io/later/parsers.html#text).
|
||||
To parse Cron syntax, Renovate uses [@cheap-glitch/mi-cron](https://github.com/cheap-glitch/mi-cron).
|
||||
To parse Cron syntax, Renovate uses [cron-parser](https://github.com/harrisiirak/cron-parser).
|
||||
Renovate does not support scheduled minutes or "at an exact time" granularity.
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
|
|
|
@ -110,6 +110,8 @@ describe('workers/repository/update/branch/schedule', () => {
|
|||
|
||||
it('returns true if schedule uses cron syntax', () => {
|
||||
expect(schedule.hasValidSchedule(['* 5 * * *'])[0]).toBeTrue();
|
||||
expect(schedule.hasValidSchedule(['* * * * * 6L'])[0]).toBeTrue();
|
||||
expect(schedule.hasValidSchedule(['* * */2 6#1'])[0]).toBeTrue();
|
||||
});
|
||||
|
||||
it('massages schedules', () => {
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
import later from '@breejs/later';
|
||||
import { parseCron } from '@cheap-glitch/mi-cron';
|
||||
import is from '@sindresorhus/is';
|
||||
import {
|
||||
CronExpression,
|
||||
DayOfTheMonthRange,
|
||||
DayOfTheWeekRange,
|
||||
HourRange,
|
||||
MonthRange,
|
||||
parseExpression,
|
||||
} from 'cron-parser';
|
||||
import { DateTime } from 'luxon';
|
||||
import { fixShortHours } from '../../../../config/migration';
|
||||
import type { RenovateConfig } from '../../../../config/types';
|
||||
|
@ -13,6 +20,17 @@ const scheduleMappings: Record<string, string> = {
|
|||
monthly: 'before 5am on the first day of the month',
|
||||
};
|
||||
|
||||
function parseCron(
|
||||
scheduleText: string,
|
||||
timezone?: string
|
||||
): CronExpression | undefined {
|
||||
try {
|
||||
return parseExpression(scheduleText, { tz: timezone });
|
||||
} catch (err) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function hasValidTimezone(timezone: string): [true] | [false, string] {
|
||||
if (!DateTime.local().setZone(timezone).isValid) {
|
||||
return [false, `Invalid schedule: Unsupported timezone ${timezone}`];
|
||||
|
@ -36,7 +54,7 @@ export function hasValidSchedule(
|
|||
const parsedCron = parseCron(scheduleText);
|
||||
if (parsedCron !== undefined) {
|
||||
if (
|
||||
parsedCron.minutes.length !== 60 ||
|
||||
parsedCron.fields.minute.length !== 60 ||
|
||||
scheduleText.indexOf(minutesChar) !== 0
|
||||
) {
|
||||
message = `Invalid schedule: "${scheduleText}" has cron syntax, but doesn't have * as minutes`;
|
||||
|
@ -80,30 +98,37 @@ export function hasValidSchedule(
|
|||
return [true];
|
||||
}
|
||||
|
||||
function cronMatches(cron: string, now: DateTime): boolean {
|
||||
const parsedCron = parseCron(cron);
|
||||
function cronMatches(cron: string, now: DateTime, timezone?: string): boolean {
|
||||
const parsedCron = parseCron(cron, timezone);
|
||||
|
||||
// istanbul ignore if: doesn't return undefined but type will include undefined
|
||||
// it will always parse because it is checked beforehand
|
||||
// istanbul ignore if
|
||||
if (!parsedCron) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parsedCron.hours.indexOf(now.hour) === -1) {
|
||||
if (parsedCron.fields.hour.indexOf(now.hour as HourRange) === -1) {
|
||||
// Hours mismatch
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parsedCron.days.indexOf(now.day) === -1) {
|
||||
if (
|
||||
parsedCron.fields.dayOfMonth.indexOf(now.day as DayOfTheMonthRange) === -1
|
||||
) {
|
||||
// Days mismatch
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!parsedCron.weekDays.includes(now.weekday % 7)) {
|
||||
if (
|
||||
!parsedCron.fields.dayOfWeek.includes(
|
||||
(now.weekday % 7) as DayOfTheWeekRange
|
||||
)
|
||||
) {
|
||||
// Weekdays mismatch
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parsedCron.months.indexOf(now.month) === -1) {
|
||||
if (parsedCron.fields.month.indexOf(now.month as MonthRange) === -1) {
|
||||
// Months mismatch
|
||||
return false;
|
||||
}
|
||||
|
@ -174,7 +199,7 @@ export function isScheduledNow(
|
|||
const cronSchedule = parseCron(scheduleText);
|
||||
if (cronSchedule) {
|
||||
// We have Cron syntax
|
||||
if (cronMatches(scheduleText, now)) {
|
||||
if (cronMatches(scheduleText, now, config.timezone)) {
|
||||
logger.debug(`Matches schedule ${scheduleText}`);
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -146,7 +146,6 @@
|
|||
"@aws-sdk/client-s3": "3.363.0",
|
||||
"@breejs/later": "4.1.0",
|
||||
"@cdktf/hcl2json": "0.17.1",
|
||||
"@cheap-glitch/mi-cron": "1.0.1",
|
||||
"@iarna/toml": "3.0.0",
|
||||
"@opentelemetry/api": "1.4.1",
|
||||
"@opentelemetry/context-async-hooks": "1.15.0",
|
||||
|
@ -180,6 +179,7 @@
|
|||
"clean-git-ref": "2.0.1",
|
||||
"commander": "11.0.0",
|
||||
"conventional-commits-detector": "1.0.3",
|
||||
"cron-parser": "4.8.1",
|
||||
"deepmerge": "4.3.1",
|
||||
"dequal": "2.0.3",
|
||||
"detect-indent": "6.1.0",
|
||||
|
|
14
yarn.lock
14
yarn.lock
|
@ -1166,11 +1166,6 @@
|
|||
dependencies:
|
||||
fs-extra "^11.1.1"
|
||||
|
||||
"@cheap-glitch/mi-cron@1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@cheap-glitch/mi-cron/-/mi-cron-1.0.1.tgz#111f4ce746c269aedf74533ac806881763a68f99"
|
||||
integrity sha512-kxl7vhg+SUgyHRn22qVbR9MfSm5CzdlYZDJTbGemqFFi/Jmno/hdoQIvBIPoqFY9dcPyxzOUNRRFn6x88UQMpw==
|
||||
|
||||
"@chevrotain/types@^9.1.0":
|
||||
version "9.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@chevrotain/types/-/types-9.1.0.tgz#689f2952be5ad9459dae3c8e9209c0f4ec3c5ec4"
|
||||
|
@ -4587,6 +4582,13 @@ create-require@^1.1.0:
|
|||
resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
|
||||
integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
|
||||
|
||||
cron-parser@4.8.1:
|
||||
version "4.8.1"
|
||||
resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-4.8.1.tgz#47062ea63d21d78c10ddedb08ea4c5b6fc2750fb"
|
||||
integrity sha512-jbokKWGcyU4gl6jAfX97E1gDpY12DJ1cLJZmoDzaAln/shZ+S3KBFBuA2Q6WeUN4gJf/8klnV1EfvhA2lK5IRQ==
|
||||
dependencies:
|
||||
luxon "^3.2.1"
|
||||
|
||||
cross-env@7.0.3:
|
||||
version "7.0.3"
|
||||
resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf"
|
||||
|
@ -7488,7 +7490,7 @@ lru-cache@^7.4.4, lru-cache@^7.5.1, lru-cache@^7.7.1:
|
|||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.0.0.tgz#b9e2a6a72a129d81ab317202d93c7691df727e61"
|
||||
integrity sha512-svTf/fzsKHffP42sujkO/Rjs37BCIsQVRCeNYIm9WN8rgT7ffoUnRtZCqU+6BqcSBdv8gwJeTz8knJpgACeQMw==
|
||||
|
||||
luxon@3.3.0, luxon@^3.3.0:
|
||||
luxon@3.3.0, luxon@^3.2.1, luxon@^3.3.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.3.0.tgz#d73ab5b5d2b49a461c47cedbc7e73309b4805b48"
|
||||
integrity sha512-An0UCfG/rSiqtAIiBPO0Y9/zAnHUZxAMiCpTd5h2smgsj7GGmcenvrvww2cqNA8/4A5ZrD1gJpHN2mIHZQF+Mg==
|
||||
|
|
Loading…
Reference in a new issue