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".
|
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).
|
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.
|
Renovate does not support scheduled minutes or "at an exact time" granularity.
|
||||||
|
|
||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
|
|
|
@ -110,6 +110,8 @@ describe('workers/repository/update/branch/schedule', () => {
|
||||||
|
|
||||||
it('returns true if schedule uses cron syntax', () => {
|
it('returns true if schedule uses cron syntax', () => {
|
||||||
expect(schedule.hasValidSchedule(['* 5 * * *'])[0]).toBeTrue();
|
expect(schedule.hasValidSchedule(['* 5 * * *'])[0]).toBeTrue();
|
||||||
|
expect(schedule.hasValidSchedule(['* * * * * 6L'])[0]).toBeTrue();
|
||||||
|
expect(schedule.hasValidSchedule(['* * */2 6#1'])[0]).toBeTrue();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('massages schedules', () => {
|
it('massages schedules', () => {
|
||||||
|
|
|
@ -1,6 +1,13 @@
|
||||||
import later from '@breejs/later';
|
import later from '@breejs/later';
|
||||||
import { parseCron } from '@cheap-glitch/mi-cron';
|
|
||||||
import is from '@sindresorhus/is';
|
import is from '@sindresorhus/is';
|
||||||
|
import {
|
||||||
|
CronExpression,
|
||||||
|
DayOfTheMonthRange,
|
||||||
|
DayOfTheWeekRange,
|
||||||
|
HourRange,
|
||||||
|
MonthRange,
|
||||||
|
parseExpression,
|
||||||
|
} from 'cron-parser';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import { fixShortHours } from '../../../../config/migration';
|
import { fixShortHours } from '../../../../config/migration';
|
||||||
import type { RenovateConfig } from '../../../../config/types';
|
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',
|
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] {
|
export function hasValidTimezone(timezone: string): [true] | [false, string] {
|
||||||
if (!DateTime.local().setZone(timezone).isValid) {
|
if (!DateTime.local().setZone(timezone).isValid) {
|
||||||
return [false, `Invalid schedule: Unsupported timezone ${timezone}`];
|
return [false, `Invalid schedule: Unsupported timezone ${timezone}`];
|
||||||
|
@ -36,7 +54,7 @@ export function hasValidSchedule(
|
||||||
const parsedCron = parseCron(scheduleText);
|
const parsedCron = parseCron(scheduleText);
|
||||||
if (parsedCron !== undefined) {
|
if (parsedCron !== undefined) {
|
||||||
if (
|
if (
|
||||||
parsedCron.minutes.length !== 60 ||
|
parsedCron.fields.minute.length !== 60 ||
|
||||||
scheduleText.indexOf(minutesChar) !== 0
|
scheduleText.indexOf(minutesChar) !== 0
|
||||||
) {
|
) {
|
||||||
message = `Invalid schedule: "${scheduleText}" has cron syntax, but doesn't have * as minutes`;
|
message = `Invalid schedule: "${scheduleText}" has cron syntax, but doesn't have * as minutes`;
|
||||||
|
@ -80,30 +98,37 @@ export function hasValidSchedule(
|
||||||
return [true];
|
return [true];
|
||||||
}
|
}
|
||||||
|
|
||||||
function cronMatches(cron: string, now: DateTime): boolean {
|
function cronMatches(cron: string, now: DateTime, timezone?: string): boolean {
|
||||||
const parsedCron = parseCron(cron);
|
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) {
|
if (!parsedCron) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parsedCron.hours.indexOf(now.hour) === -1) {
|
if (parsedCron.fields.hour.indexOf(now.hour as HourRange) === -1) {
|
||||||
// Hours mismatch
|
// Hours mismatch
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parsedCron.days.indexOf(now.day) === -1) {
|
if (
|
||||||
|
parsedCron.fields.dayOfMonth.indexOf(now.day as DayOfTheMonthRange) === -1
|
||||||
|
) {
|
||||||
// Days mismatch
|
// Days mismatch
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!parsedCron.weekDays.includes(now.weekday % 7)) {
|
if (
|
||||||
|
!parsedCron.fields.dayOfWeek.includes(
|
||||||
|
(now.weekday % 7) as DayOfTheWeekRange
|
||||||
|
)
|
||||||
|
) {
|
||||||
// Weekdays mismatch
|
// Weekdays mismatch
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parsedCron.months.indexOf(now.month) === -1) {
|
if (parsedCron.fields.month.indexOf(now.month as MonthRange) === -1) {
|
||||||
// Months mismatch
|
// Months mismatch
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -174,7 +199,7 @@ export function isScheduledNow(
|
||||||
const cronSchedule = parseCron(scheduleText);
|
const cronSchedule = parseCron(scheduleText);
|
||||||
if (cronSchedule) {
|
if (cronSchedule) {
|
||||||
// We have Cron syntax
|
// We have Cron syntax
|
||||||
if (cronMatches(scheduleText, now)) {
|
if (cronMatches(scheduleText, now, config.timezone)) {
|
||||||
logger.debug(`Matches schedule ${scheduleText}`);
|
logger.debug(`Matches schedule ${scheduleText}`);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -146,7 +146,6 @@
|
||||||
"@aws-sdk/client-s3": "3.363.0",
|
"@aws-sdk/client-s3": "3.363.0",
|
||||||
"@breejs/later": "4.1.0",
|
"@breejs/later": "4.1.0",
|
||||||
"@cdktf/hcl2json": "0.17.1",
|
"@cdktf/hcl2json": "0.17.1",
|
||||||
"@cheap-glitch/mi-cron": "1.0.1",
|
|
||||||
"@iarna/toml": "3.0.0",
|
"@iarna/toml": "3.0.0",
|
||||||
"@opentelemetry/api": "1.4.1",
|
"@opentelemetry/api": "1.4.1",
|
||||||
"@opentelemetry/context-async-hooks": "1.15.0",
|
"@opentelemetry/context-async-hooks": "1.15.0",
|
||||||
|
@ -180,6 +179,7 @@
|
||||||
"clean-git-ref": "2.0.1",
|
"clean-git-ref": "2.0.1",
|
||||||
"commander": "11.0.0",
|
"commander": "11.0.0",
|
||||||
"conventional-commits-detector": "1.0.3",
|
"conventional-commits-detector": "1.0.3",
|
||||||
|
"cron-parser": "4.8.1",
|
||||||
"deepmerge": "4.3.1",
|
"deepmerge": "4.3.1",
|
||||||
"dequal": "2.0.3",
|
"dequal": "2.0.3",
|
||||||
"detect-indent": "6.1.0",
|
"detect-indent": "6.1.0",
|
||||||
|
|
14
yarn.lock
14
yarn.lock
|
@ -1166,11 +1166,6 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
fs-extra "^11.1.1"
|
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":
|
"@chevrotain/types@^9.1.0":
|
||||||
version "9.1.0"
|
version "9.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@chevrotain/types/-/types-9.1.0.tgz#689f2952be5ad9459dae3c8e9209c0f4ec3c5ec4"
|
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"
|
resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
|
||||||
integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
|
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:
|
cross-env@7.0.3:
|
||||||
version "7.0.3"
|
version "7.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf"
|
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"
|
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.0.0.tgz#b9e2a6a72a129d81ab317202d93c7691df727e61"
|
||||||
integrity sha512-svTf/fzsKHffP42sujkO/Rjs37BCIsQVRCeNYIm9WN8rgT7ffoUnRtZCqU+6BqcSBdv8gwJeTz8knJpgACeQMw==
|
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"
|
version "3.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.3.0.tgz#d73ab5b5d2b49a461c47cedbc7e73309b4805b48"
|
resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.3.0.tgz#d73ab5b5d2b49a461c47cedbc7e73309b4805b48"
|
||||||
integrity sha512-An0UCfG/rSiqtAIiBPO0Y9/zAnHUZxAMiCpTd5h2smgsj7GGmcenvrvww2cqNA8/4A5ZrD1gJpHN2mIHZQF+Mg==
|
integrity sha512-An0UCfG/rSiqtAIiBPO0Y9/zAnHUZxAMiCpTd5h2smgsj7GGmcenvrvww2cqNA8/4A5ZrD1gJpHN2mIHZQF+Mg==
|
||||||
|
|
Loading…
Reference in a new issue