From e635f0e2961a2cd4ff6d095735c120da802be37d Mon Sep 17 00:00:00 2001 From: RahulGautamSingh Date: Mon, 17 Jul 2023 16:59:38 +0545 Subject: [PATCH] feat: use cron-parser (#23142) Co-authored-by: Sebastian Poxhofer --- docs/usage/configuration-options.md | 2 +- .../repository/update/branch/schedule.spec.ts | 2 + .../repository/update/branch/schedule.ts | 45 ++++++++++++++----- package.json | 2 +- yarn.lock | 14 +++--- 5 files changed, 47 insertions(+), 18 deletions(-) diff --git a/docs/usage/configuration-options.md b/docs/usage/configuration-options.md index b4050fbc18fb4f..81e333292e6d52 100644 --- a/docs/usage/configuration-options.md +++ b/docs/usage/configuration-options.md @@ -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. diff --git a/lib/workers/repository/update/branch/schedule.spec.ts b/lib/workers/repository/update/branch/schedule.spec.ts index 24b8995c46ff79..f70dd7e9b508a8 100644 --- a/lib/workers/repository/update/branch/schedule.spec.ts +++ b/lib/workers/repository/update/branch/schedule.spec.ts @@ -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', () => { diff --git a/lib/workers/repository/update/branch/schedule.ts b/lib/workers/repository/update/branch/schedule.ts index 938362f8e27517..bdad745d56ce27 100644 --- a/lib/workers/repository/update/branch/schedule.ts +++ b/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'; @@ -13,6 +20,17 @@ const scheduleMappings: Record = { 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; } diff --git a/package.json b/package.json index 8128c809c62d68..ea8522a62033c9 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/yarn.lock b/yarn.lock index 170a5bac76c647..e89b1c0d9439bf 100644 --- a/yarn.lock +++ b/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==