Skip to content

Commit

Permalink
feat: use cron-parser (#23142)
Browse files Browse the repository at this point in the history
Co-authored-by: Sebastian Poxhofer <secustor@users.noreply.github.com>
  • Loading branch information
RahulGautamSingh and secustor committed Jul 17, 2023
1 parent 84e1731 commit e635f0e
Show file tree
Hide file tree
Showing 5 changed files with 47 additions and 18 deletions.
2 changes: 1 addition & 1 deletion docs/usage/configuration-options.md
Expand Up @@ -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 -->
Expand Down
2 changes: 2 additions & 0 deletions lib/workers/repository/update/branch/schedule.spec.ts
Expand Up @@ -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', () => {
Expand Down
45 changes: 35 additions & 10 deletions lib/workers/repository/update/branch/schedule.ts
@@ -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';
Expand All @@ -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}`];
Expand All @@ -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`;
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
14 changes: 8 additions & 6 deletions yarn.lock
Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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==
Expand Down

0 comments on commit e635f0e

Please sign in to comment.