feat: use cron-parser (#23142)

Co-authored-by: Sebastian Poxhofer <secustor@users.noreply.github.com>
This commit is contained in:
RahulGautamSingh 2023-07-17 16:59:38 +05:45 committed by GitHub
parent 84e17317d6
commit e635f0e296
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 47 additions and 18 deletions

View file

@ -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 -->

View file

@ -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', () => {

View file

@ -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;
} }

View file

@ -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",

View file

@ -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==