From d6766925a904d20df2c58fa8fe2f58061f0b0f93 Mon Sep 17 00:00:00 2001 From: RahulGautamSingh Date: Fri, 7 Apr 2023 03:57:10 +0530 Subject: [PATCH 1/8] rename stabilityDays -> minimumReleaseAge --- docs/usage/configuration-options.md | 32 +++++++++---------- lib/config/options/index.ts | 13 ++++---- lib/config/presets/internal/npm.ts | 2 +- .../__snapshots__/vulnerability.spec.ts.snap | 8 ++--- .../__snapshots__/filter-checks.spec.ts.snap | 4 +-- .../process/lookup/filter-checks.spec.ts | 20 ++++++------ .../repository/process/lookup/index.spec.ts | 4 +-- .../repository/update/branch/index.spec.ts | 10 +++--- 8 files changed, 47 insertions(+), 46 deletions(-) diff --git a/docs/usage/configuration-options.md b/docs/usage/configuration-options.md index f44b2ad0db4ca7..1dcdab220d22d4 100644 --- a/docs/usage/configuration-options.md +++ b/docs/usage/configuration-options.md @@ -1518,13 +1518,13 @@ Change this setting to `true` if you want to use internal Renovate checks toward ## internalChecksFilter This setting determines whether Renovate controls when and how filtering of internal checks are performed, particularly when multiple versions of the same update type are available. -Currently this applies to the `stabilityDays` check only. +Currently this applies to the `minimumReleaseAge` check only. - `none`: No filtering will be performed, and the highest release will be used regardless of whether it's pending or not - `strict`: All pending releases will be filtered. PRs will be skipped unless a non-pending version is available - `flexible`: Similar to strict, but in the case where all versions are pending then a PR will be created with the highest pending version -The `flexible` mode can result in "flapping" of Pull Requests, where e.g. a pending PR with version `1.0.3` is first released but then downgraded to `1.0.2` once it passes `stabilityDays`. +The `flexible` mode can result in "flapping" of Pull Requests, where e.g. a pending PR with version `1.0.3` is first released but then downgraded to `1.0.2` once it passes `minimumReleaseAge`. We recommend that you use the `strict` mode, and enable the `dependencyDashboard` so that you have visibility into suppressed PRs. ## java @@ -2583,7 +2583,7 @@ This is why we configured an upper limit for how long we wait until creating a P !!! note - If the option `stabilityDays` is non-zero then Renovate disables the `prNotPendingHours` functionality. + If the option `minimumReleaseAge` is non-zero then Renovate disables the `prNotPendingHours` functionality. ## prPriority @@ -3192,51 +3192,51 @@ Configure this to `true` if you wish to get one PR for every separate major vers e.g. if you are on webpack@v1 currently then default behavior is a PR for upgrading to webpack@v3 and not for webpack@v2. If this setting is true then you would get one PR for webpack@v2 and one for webpack@v3. -## stabilityDays +## minimumReleaseAge -If this is set to a non-zero value, _and_ an update has a release timestamp header, then Renovate will check if the "stability days" have passed. +If this is set _and_ an update has a release timestamp header, then Renovate will check if the set duration has passed. -Note: Renovate will wait for the set number of `stabilityDays` to pass for each **separate** version. -Renovate does not wait until the package has seen no releases for x `stabilityDays`. -`stabilityDays` is not intended to help with slowing down fast releasing project updates. +Note: Renovate will wait for the set duration to pass for each **separate** version. +Renovate does not wait until the package has seen no releases for x time-interval(`minimumReleaseAge`). +`minimumReleaseAge` is not intended to help with slowing down fast releasing project updates. If you want to slow down PRs for a specific package, setup a custom schedule for that package. Read [our selective-scheduling help](https://docs.renovatebot.com/noise-reduction/#selective-scheduling) to learn how to set the schedule. -If the number of days since the release is less than the set `stabilityDays` a "pending" status check is added to the branch. +If the time since the release is less than the set `minimumReleaseAge` a "pending" status check is added to the branch. If enough days have passed then the "pending" status is removed, and a "passing" status check is added. Some datasources do not provide a release timestamp (in which case this feature is not compatible), and other datasources may provide a release timestamp but it's not supported by Renovate (in which case a feature request needs to be implemented). -Maven users: you cannot use `stabilityDays` if a Maven source returns unreliable `last-modified` headers. +Maven users: you cannot use `minimumReleaseAge` if a Maven source returns unreliable `last-modified` headers. -There are a couple of uses for `stabilityDays`: +There are a couple of uses for `minimumReleaseAge`: #### Suppress branch/PR creation for X days -If you combine `stabilityDays=3` and `internalChecksFilter="strict"` then Renovate will hold back from creating branches until 3 or more days have elapsed since the version was released. +If you combine `minimumReleaseAge=3 days` and `internalChecksFilter="strict"` then Renovate will hold back from creating branches until 3 or more days have elapsed since the version was released. It's recommended that you enable `dependencyDashboard=true` so you don't lose visibility of these pending PRs. #### Prevent holding broken npm packages npm packages less than 72 hours (3 days) old can be unpublished, which could result in a service impact if you have already updated to it. -Set `stabilityDays` to 3 for npm packages to prevent relying on a package that can be removed from the registry: +Set `minimumReleaseAge` to `3 days` for npm packages to prevent relying on a package that can be removed from the registry: ```json { "packageRules": [ { "matchDatasources": ["npm"], - "stabilityDays": 3 + "minimumReleaseAge": "3 days" } ] } ``` -#### Await X days before Automerging +#### Await X time duration before Automerging -If you have both `automerge` as well as `stabilityDays` enabled, it means that PRs will be created immediately but automerging will be delayed until X days have passed. +If you have both `automerge` as well as `minimumReleaseAge` enabled, it means that PRs will be created immediately but automerging will be delayed until the time-duration has passed. This works because Renovate will add a "renovate/stability-days" pending status check to each branch/PR and that pending check will prevent the branch going green to automerge. diff --git a/lib/config/options/index.ts b/lib/config/options/index.ts index faa0653fd50761..d088a6a905c1e6 100644 --- a/lib/config/options/index.ts +++ b/lib/config/options/index.ts @@ -1576,16 +1576,15 @@ const options: RenovateOptions[] = [ supportedPlatforms: ['azure', 'gitea', 'github', 'gitlab'], }, { - name: 'stabilityDays', - description: - 'Number of days required before a new release is considered stable.', - type: 'integer', - default: 0, + name: 'minimumReleaseAge', + description: 'Time required before a new release is considered stable.', + type: 'string', + default: '0', }, { name: 'internalChecksAsSuccess', description: - 'Whether to consider passing internal checks such as stabilityDays when determining branch status.', + 'Whether to consider passing internal checks such as minimumReleaseAge when determining branch status.', type: 'boolean', default: false, }, @@ -1719,7 +1718,7 @@ const options: RenovateOptions[] = [ groupName: null, schedule: [], dependencyDashboardApproval: false, - stabilityDays: 0, + minimumReleaseAge: '0', rangeStrategy: 'update-lockfile', commitMessageSuffix: '[SECURITY]', branchTopic: `{{{datasource}}}-{{{depName}}}-vulnerability`, diff --git a/lib/config/presets/internal/npm.ts b/lib/config/presets/internal/npm.ts index 57552b194e38e9..e269bbd8c77072 100644 --- a/lib/config/presets/internal/npm.ts +++ b/lib/config/presets/internal/npm.ts @@ -7,7 +7,7 @@ export const presets: Record = { description: 'Wait until the npm package is three days old before raising the update, this prevents npm unpublishing a package you already upgraded to.', npm: { - stabilityDays: 3, + minimumReleaseAge: '3 days', }, }, }; diff --git a/lib/workers/repository/init/__snapshots__/vulnerability.spec.ts.snap b/lib/workers/repository/init/__snapshots__/vulnerability.spec.ts.snap index 990d099897ce2e..3f4c46c952313b 100644 --- a/lib/workers/repository/init/__snapshots__/vulnerability.spec.ts.snap +++ b/lib/workers/repository/init/__snapshots__/vulnerability.spec.ts.snap @@ -24,7 +24,7 @@ exports[`workers/repository/init/vulnerability detectVulnerabilityAlerts() retur "prCreation": "immediate", "rangeStrategy": "update-lockfile", "schedule": [], - "stabilityDays": 0, + "minimumReleaseAge": "0", }, "isVulnerabilityAlert": true, "matchCurrentVersion": "= 1.8.2", @@ -54,7 +54,7 @@ go", "prCreation": "immediate", "rangeStrategy": "update-lockfile", "schedule": [], - "stabilityDays": 0, + "minimumReleaseAge": "0", }, "isVulnerabilityAlert": true, "matchCurrentVersion": "1.8.2", @@ -84,7 +84,7 @@ actions", "prCreation": "immediate", "rangeStrategy": "update-lockfile", "schedule": [], - "stabilityDays": 0, + "minimumReleaseAge": "0", }, "isVulnerabilityAlert": true, "matchCurrentVersion": "== 1.6.7", @@ -129,7 +129,7 @@ Ansible before versions 2.1.4, 2.2.1 is vulnerable to an improper input validati "prCreation": "immediate", "rangeStrategy": "update-lockfile", "schedule": [], - "stabilityDays": 0, + "minimumReleaseAge": "0", }, "isVulnerabilityAlert": true, "matchCurrentVersion": "2.4.2", diff --git a/lib/workers/repository/process/lookup/__snapshots__/filter-checks.spec.ts.snap b/lib/workers/repository/process/lookup/__snapshots__/filter-checks.spec.ts.snap index 3c5cc28de8b003..5bf232ab929a17 100644 --- a/lib/workers/repository/process/lookup/__snapshots__/filter-checks.spec.ts.snap +++ b/lib/workers/repository/process/lookup/__snapshots__/filter-checks.spec.ts.snap @@ -24,7 +24,7 @@ exports[`workers/repository/process/lookup/filter-checks .filterInternalChecks() } `; -exports[`workers/repository/process/lookup/filter-checks .filterInternalChecks() picks up stabilityDays settings from hostRules 1`] = ` +exports[`workers/repository/process/lookup/filter-checks .filterInternalChecks() picks up minimumReleaseAge settings from hostRules 1`] = ` { "pendingChecks": false, "pendingReleases": [], @@ -35,7 +35,7 @@ exports[`workers/repository/process/lookup/filter-checks .filterInternalChecks() } `; -exports[`workers/repository/process/lookup/filter-checks .filterInternalChecks() picks up stabilityDays settings from updateType 1`] = ` +exports[`workers/repository/process/lookup/filter-checks .filterInternalChecks() picks up minimumReleaseAge settings from updateType 1`] = ` { "pendingChecks": false, "pendingReleases": [ diff --git a/lib/workers/repository/process/lookup/filter-checks.spec.ts b/lib/workers/repository/process/lookup/filter-checks.spec.ts index 5802aced0e1fe6..4a02f766ffe1c4 100644 --- a/lib/workers/repository/process/lookup/filter-checks.spec.ts +++ b/lib/workers/repository/process/lookup/filter-checks.spec.ts @@ -67,7 +67,7 @@ describe('workers/repository/process/lookup/filter-checks', () => { it('returns non-pending latest release if internalChecksFilter=flexible and none pass checks', async () => { config.internalChecksFilter = 'flexible'; - config.stabilityDays = 10; + config.minimumReleaseAge = '10 days'; const res = await filterInternalChecks( config, versioning, @@ -82,7 +82,7 @@ describe('workers/repository/process/lookup/filter-checks', () => { it('returns pending latest release if internalChecksFilter=strict and none pass checks', async () => { config.internalChecksFilter = 'strict'; - config.stabilityDays = 10; + config.minimumReleaseAge = '10 days'; const res = await filterInternalChecks( config, versioning, @@ -97,7 +97,7 @@ describe('workers/repository/process/lookup/filter-checks', () => { it('returns non-latest release if internalChecksFilter=strict and some pass checks', async () => { config.internalChecksFilter = 'strict'; - config.stabilityDays = 6; + config.minimumReleaseAge = '6 days'; const res = await filterInternalChecks( config, versioning, @@ -112,7 +112,7 @@ describe('workers/repository/process/lookup/filter-checks', () => { it('returns non-latest release if internalChecksFilter=flexible and some pass checks', async () => { config.internalChecksFilter = 'strict'; - config.stabilityDays = 6; + config.minimumReleaseAge = '6 days'; const res = await filterInternalChecks( config, versioning, @@ -125,10 +125,12 @@ describe('workers/repository/process/lookup/filter-checks', () => { expect(res.release.version).toBe('1.0.2'); }); - it('picks up stabilityDays settings from hostRules', async () => { + it('picks up minimumReleaseAge settings from hostRules', async () => { config.internalChecksFilter = 'strict'; - config.stabilityDays = 6; - config.packageRules = [{ matchUpdateTypes: ['patch'], stabilityDays: 1 }]; + config.minimumReleaseAge = '6 days'; + config.packageRules = [ + { matchUpdateTypes: ['patch'], minimumReleaseAge: '1 day' }, + ]; const res = await filterInternalChecks( config, versioning, @@ -141,9 +143,9 @@ describe('workers/repository/process/lookup/filter-checks', () => { expect(res.release.version).toBe('1.0.4'); }); - it('picks up stabilityDays settings from updateType', async () => { + it('picks up minimumReleaseAge settings from updateType', async () => { config.internalChecksFilter = 'strict'; - config.patch = { stabilityDays: 4 }; + config.patch = { minimumReleaseAge: '4 days' }; const res = await filterInternalChecks( config, versioning, diff --git a/lib/workers/repository/process/lookup/index.spec.ts b/lib/workers/repository/process/lookup/index.spec.ts index 10c963df82a8f1..fb91a5467745b3 100644 --- a/lib/workers/repository/process/lookup/index.spec.ts +++ b/lib/workers/repository/process/lookup/index.spec.ts @@ -1002,7 +1002,7 @@ describe('workers/repository/process/lookup/index', () => { config.currentValue = '1.4.4'; config.packageName = 'some/action'; config.datasource = GithubReleasesDatasource.id; - config.stabilityDays = 14; + config.minimumReleaseAge = '14 days'; config.internalChecksFilter = 'strict'; const yesterday = new Date(); yesterday.setDate(yesterday.getDate() - 1); @@ -1025,7 +1025,7 @@ describe('workers/repository/process/lookup/index', () => { config.currentValue = '1.4.4'; config.packageName = 'some/action'; config.datasource = GithubReleasesDatasource.id; - config.stabilityDays = 3; + config.minimumReleaseAge = '3 days'; config.internalChecksFilter = 'strict'; const yesterday = new Date(); yesterday.setDate(yesterday.getDate() - 1); diff --git a/lib/workers/repository/update/branch/index.spec.ts b/lib/workers/repository/update/branch/index.spec.ts index 146312255a2e52..9635e94486f162 100644 --- a/lib/workers/repository/update/branch/index.spec.ts +++ b/lib/workers/repository/update/branch/index.spec.ts @@ -162,17 +162,17 @@ describe('workers/repository/update/branch/index', () => { }); }); - it('skips branch for fresh release with stabilityDays', async () => { + it('skips branch for fresh release with minimumReleaseAge', async () => { schedule.isScheduledNow.mockReturnValueOnce(true); config.prCreation = 'not-pending'; (config.upgrades as Partial[]) = [ { releaseTimestamp: new Date('2019-01-01').getTime().toString(), - stabilityDays: 1, + minimumReleaseAge: '1 day', }, { releaseTimestamp: new Date().toString(), - stabilityDays: 1, + minimumReleaseAge: '1 day', }, ]; @@ -185,13 +185,13 @@ describe('workers/repository/update/branch/index', () => { }); }); - it('skips branch if not stabilityDays not met', async () => { + it('skips branch if not minimumReleaseAge not met', async () => { schedule.isScheduledNow.mockReturnValueOnce(true); config.prCreation = 'not-pending'; config.upgrades = partial([ { releaseTimestamp: '2099-12-31', - stabilityDays: 1, + minimumReleaseAge: '1 day', }, ]); const res = await branchWorker.processBranch(config); From b920638b56c31e45449563f51f6a2bd39750e177 Mon Sep 17 00:00:00 2001 From: RahulGautamSingh Date: Fri, 7 Apr 2023 05:07:28 +0530 Subject: [PATCH 2/8] shift logic from days to time-duration --- lib/util/date.spec.ts | 14 +++++++++++++- lib/util/date.ts | 4 ++++ .../process/lookup/filter-checks.spec.ts | 9 +++++---- .../process/lookup/filter-checks.ts | 15 ++++++++------- lib/workers/repository/update/branch/index.ts | 19 ++++++++++--------- 5 files changed, 40 insertions(+), 21 deletions(-) diff --git a/lib/util/date.spec.ts b/lib/util/date.spec.ts index 87d3742ac28923..08d5d102c38898 100644 --- a/lib/util/date.spec.ts +++ b/lib/util/date.spec.ts @@ -1,4 +1,9 @@ -import { getElapsedDays, getElapsedHours, getElapsedMinutes } from './date'; +import { + getElapsedDays, + getElapsedHours, + getElapsedMinutes, + getElapsedMs, +} from './date'; const ONE_MINUTE_MS = 60 * 1000; const ONE_HOUR_MS = 60 * ONE_MINUTE_MS; @@ -34,4 +39,11 @@ describe('util/date', () => { expect(getElapsedHours(new Date('invalid_date_string'))).toBe(0); }); }); + + describe('getElapsedMilliseconds', () => { + it('returns elapsed time in milliseconds', () => { + const elapsedMs = new Date().getTime() - new Date(Jan1).getTime(); + expect(getElapsedMs(Jan1.toISOString())).toBe(elapsedMs); + }); + }); }); diff --git a/lib/util/date.ts b/lib/util/date.ts index 53d7877da52dc5..61fbe64b5c8f40 100644 --- a/lib/util/date.ts +++ b/lib/util/date.ts @@ -26,3 +26,7 @@ export function getElapsedHours(date: Date | string): number { const diff = DateTime.now().diff(pastDate, 'hours'); return Math.floor(diff.hours); } + +export function getElapsedMs(timestamp: string): number { + return new Date().getTime() - new Date(timestamp).getTime(); +} diff --git a/lib/workers/repository/process/lookup/filter-checks.spec.ts b/lib/workers/repository/process/lookup/filter-checks.spec.ts index 4a02f766ffe1c4..8c210db87d6526 100644 --- a/lib/workers/repository/process/lookup/filter-checks.spec.ts +++ b/lib/workers/repository/process/lookup/filter-checks.spec.ts @@ -4,6 +4,7 @@ import * as allVersioning from '../../../../modules/versioning'; import { clone } from '../../../../util/clone'; import * as _dateUtil from '../../../../util/date'; import * as _mergeConfidence from '../../../../util/merge-confidence'; +import { toMs } from '../../../../util/pretty-time'; import { filterInternalChecks } from './filter-checks'; import type { LookupUpdateConfig, UpdateResult } from './types'; @@ -44,10 +45,10 @@ describe('workers/repository/process/lookup/filter-checks', () => { config.currentVersion = '1.0.0'; sortedReleases = clone(releases); jest.resetAllMocks(); - dateUtil.getElapsedDays.mockReturnValueOnce(3); - dateUtil.getElapsedDays.mockReturnValueOnce(5); - dateUtil.getElapsedDays.mockReturnValueOnce(7); - dateUtil.getElapsedDays.mockReturnValueOnce(9); + dateUtil.getElapsedMs.mockReturnValueOnce(toMs('3 days') ?? 0); + dateUtil.getElapsedMs.mockReturnValueOnce(toMs('5 days') ?? 0); + dateUtil.getElapsedMs.mockReturnValueOnce(toMs('7 days') ?? 0); + dateUtil.getElapsedMs.mockReturnValueOnce(toMs('9 days') ?? 0); }); describe('.filterInternalChecks()', () => { diff --git a/lib/workers/repository/process/lookup/filter-checks.ts b/lib/workers/repository/process/lookup/filter-checks.ts index 4cd6ac70f6150d..32d53438ce82e1 100644 --- a/lib/workers/repository/process/lookup/filter-checks.ts +++ b/lib/workers/repository/process/lookup/filter-checks.ts @@ -3,13 +3,14 @@ import { mergeChildConfig } from '../../../../config'; import { logger } from '../../../../logger'; import type { Release } from '../../../../modules/datasource'; import type { VersioningApi } from '../../../../modules/versioning'; -import { getElapsedDays } from '../../../../util/date'; +import { getElapsedMs } from '../../../../util/date'; import { getMergeConfidenceLevel, isActiveConfidenceLevel, satisfiesConfidenceLevel, } from '../../../../util/merge-confidence'; import { applyPackageRules } from '../../../../util/package-rules'; +import { toMs } from '../../../../util/pretty-time'; import type { LookupUpdateConfig, UpdateResult } from './types'; import { getUpdateType } from './update-type'; @@ -30,7 +31,7 @@ export async function filterInternalChecks( let pendingChecks = false; let pendingReleases: Release[] = []; if (internalChecksFilter === 'none') { - // Don't care if stabilityDays or minimumConfidence are unmet + // Don't care if minimumReleaseAge or minimumConfidence are unmet release = sortedReleases.pop(); } else { // iterate through releases from highest to lowest, looking for the first which will pass checks if present @@ -51,19 +52,19 @@ export async function filterInternalChecks( ); // Apply packageRules in case any apply to updateType releaseConfig = applyPackageRules(releaseConfig); - // Now check for a stabilityDays config + // Now check for a minimumReleaseAge config const { minimumConfidence, - stabilityDays, + minimumReleaseAge, releaseTimestamp, version: newVersion, updateType, } = releaseConfig; - if (is.integer(stabilityDays) && releaseTimestamp) { - if (getElapsedDays(releaseTimestamp) < stabilityDays) { + if (is.string(minimumReleaseAge) && releaseTimestamp) { + if (getElapsedMs(releaseTimestamp) < (toMs(minimumReleaseAge) ?? 0)) { // Skip it if it doesn't pass checks logger.trace( - { depName, check: 'stabilityDays' }, + { depName, check: 'minimumReleaseAge' }, `Release ${candidateRelease.version} is pending status checks` ); pendingReleases.unshift(candidateRelease); diff --git a/lib/workers/repository/update/branch/index.ts b/lib/workers/repository/update/branch/index.ts index 188e5f31a1a666..6c8f0053d9b0b9 100644 --- a/lib/workers/repository/update/branch/index.ts +++ b/lib/workers/repository/update/branch/index.ts @@ -23,7 +23,7 @@ import { } from '../../../../modules/platform/comment'; import { scm } from '../../../../modules/platform/scm'; import { ExternalHostError } from '../../../../types/errors/external-host-error'; -import { getElapsedDays } from '../../../../util/date'; +import { getElapsedMs } from '../../../../util/date'; import { emojify } from '../../../../util/emoji'; import { checkoutBranch } from '../../../../util/git'; import { @@ -31,6 +31,7 @@ import { isActiveConfidenceLevel, satisfiesConfidenceLevel, } from '../../../../util/merge-confidence'; +import { toMs } from '../../../../util/pretty-time'; import * as template from '../../../../util/template'; import { isLimitReached } from '../../../global/limits'; import type { BranchConfig, BranchResult, PrBlockedBy } from '../../../types'; @@ -280,25 +281,25 @@ export async function processBranch( if ( config.upgrades.some( (upgrade) => - (upgrade.stabilityDays && upgrade.releaseTimestamp) || + (upgrade.minimumReleaseAge && upgrade.releaseTimestamp) || isActiveConfidenceLevel(upgrade.minimumConfidence!) ) ) { // Only set a stability status check if one or more of the updates contain - // both a stabilityDays setting and a releaseTimestamp + // both a minimumReleaseAge setting and a releaseTimestamp config.stabilityStatus = 'green'; // Default to 'success' but set 'pending' if any update is pending for (const upgrade of config.upgrades) { - if (is.number(upgrade.stabilityDays) && upgrade.releaseTimestamp) { - const daysElapsed = getElapsedDays(upgrade.releaseTimestamp); - if (daysElapsed < upgrade.stabilityDays) { + if (is.string(upgrade.minimumReleaseAge) && upgrade.releaseTimestamp) { + const timeElapsed = getElapsedMs(upgrade.releaseTimestamp); + if (timeElapsed < (toMs(upgrade.minimumReleaseAge) ?? 0)) { logger.debug( { depName: upgrade.depName, - daysElapsed, - stabilityDays: upgrade.stabilityDays, + timeElapsed, + minimumReleaseAge: upgrade.minimumReleaseAge, }, - 'Update has not passed stability days' + 'Update has not passed minimum release age' ); config.stabilityStatus = 'yellow'; continue; From 4ad6e32cfc9d0ffdf164c0d6966d45ee7845528f Mon Sep 17 00:00:00 2001 From: RahulGautamSingh Date: Fri, 7 Apr 2023 05:34:25 +0530 Subject: [PATCH 3/8] migrate --- .../custom/stability-days-migration.spec.ts | 22 +++++++++++++++++++ .../custom/stability-days-migration.ts | 16 ++++++++++++++ lib/config/migrations/migrations-service.ts | 2 ++ lib/util/pretty-time.spec.ts | 2 ++ 4 files changed, 42 insertions(+) create mode 100644 lib/config/migrations/custom/stability-days-migration.spec.ts create mode 100644 lib/config/migrations/custom/stability-days-migration.ts diff --git a/lib/config/migrations/custom/stability-days-migration.spec.ts b/lib/config/migrations/custom/stability-days-migration.spec.ts new file mode 100644 index 00000000000000..1d325341d98bec --- /dev/null +++ b/lib/config/migrations/custom/stability-days-migration.spec.ts @@ -0,0 +1,22 @@ +import { StabilityDaysMigration } from './stability-days-migration'; + +describe('config/migrations/custom/stability-days-migration', () => { + it('migrates', () => { + expect(StabilityDaysMigration).toMigrate( + { + stabilityDays: 2, + }, + { + minimumReleaseAge: '2 days', + } + ); + expect(StabilityDaysMigration).toMigrate( + { + stabilityDays: 1, + }, + { + minimumReleaseAge: '1 day', + } + ); + }); +}); diff --git a/lib/config/migrations/custom/stability-days-migration.ts b/lib/config/migrations/custom/stability-days-migration.ts new file mode 100644 index 00000000000000..14607b4cc09019 --- /dev/null +++ b/lib/config/migrations/custom/stability-days-migration.ts @@ -0,0 +1,16 @@ +import is from '@sindresorhus/is'; +import { AbstractMigration } from '../base/abstract-migration'; + +export class StabilityDaysMigration extends AbstractMigration { + override readonly deprecated = true; + override readonly propertyName = 'stabilityDays'; + + override run(value: unknown): void { + if (is.integer(value)) { + this.setSafely( + 'minimumReleaseAge', + value === 1 ? `${value} day` : `${value} days` + ); + } + } +} diff --git a/lib/config/migrations/migrations-service.ts b/lib/config/migrations/migrations-service.ts index de172fdd7c391b..08177481823d1f 100644 --- a/lib/config/migrations/migrations-service.ts +++ b/lib/config/migrations/migrations-service.ts @@ -47,6 +47,7 @@ import { SemanticCommitsMigration } from './custom/semantic-commits-migration'; import { SemanticPrefixMigration } from './custom/semantic-prefix-migration'; import { SeparateMajorReleasesMigration } from './custom/separate-major-release-migration'; import { SeparateMultipleMajorMigration } from './custom/separate-multiple-major-migration'; +import { StabilityDaysMigration } from './custom/stability-days-migration'; import { SuppressNotificationsMigration } from './custom/suppress-notifications-migration'; import { TrustLevelMigration } from './custom/trust-level-migration'; import { UnpublishSafeMigration } from './custom/unpublish-safe-migration'; @@ -143,6 +144,7 @@ export class MigrationsService { SemanticPrefixMigration, MatchDatasourcesMigration, DatasourceMigration, + StabilityDaysMigration, ]; static run(originalConfig: RenovateConfig): RenovateConfig { diff --git a/lib/util/pretty-time.spec.ts b/lib/util/pretty-time.spec.ts index a9f291e467a9c6..266089e7e03c29 100644 --- a/lib/util/pretty-time.spec.ts +++ b/lib/util/pretty-time.spec.ts @@ -15,6 +15,8 @@ describe('util/pretty-time', () => { ${'1h 1 m 1s'} | ${1 * 60 * 60 * 1000 + 1 * 60 * 1000 + 1000} ${'1hour 1 min 1s'} | ${1 * 60 * 60 * 1000 + 1 * 60 * 1000 + 1000} ${'1h 1m 1s 1ms'} | ${1 * 60 * 60 * 1000 + 1 * 60 * 1000 + 1000 + 1} + ${'1d2h3m'} | ${24 * 60 * 60 * 1000 + 2 * 60 * 60 * 1000 + 3 * 60 * 1000} + ${'1 day'} | ${24 * 60 * 60 * 1000} ${'3 days'} | ${3 * 24 * 60 * 60 * 1000} ${'1 week'} | ${7 * 24 * 60 * 60 * 1000} ${'1 month'} | ${30 * 24 * 60 * 60 * 1000} From 130cf2efa38bcd69f1286d3928ea1bf33a4c7b5b Mon Sep 17 00:00:00 2001 From: RahulGautamSingh Date: Fri, 7 Apr 2023 06:15:16 +0530 Subject: [PATCH 4/8] update order in docs and snaps --- docs/usage/configuration-options.md | 98 +++++++++---------- .../__snapshots__/vulnerability.spec.ts.snap | 8 +- 2 files changed, 53 insertions(+), 53 deletions(-) diff --git a/docs/usage/configuration-options.md b/docs/usage/configuration-options.md index 1dcdab220d22d4..1856076f15d5fc 100644 --- a/docs/usage/configuration-options.md +++ b/docs/usage/configuration-options.md @@ -1608,6 +1608,55 @@ Depending on its running schedule, Renovate may run a few times within that time Add to this object if you wish to define rules that apply only to major updates. +## minimumReleaseAge + +If this is set _and_ an update has a release timestamp header, then Renovate will check if the set duration has passed. + +Note: Renovate will wait for the set duration to pass for each **separate** version. +Renovate does not wait until the package has seen no releases for x time-duration(`minimumReleaseAge`). +`minimumReleaseAge` is not intended to help with slowing down fast releasing project updates. +If you want to slow down PRs for a specific package, setup a custom schedule for that package. +Read [our selective-scheduling help](https://docs.renovatebot.com/noise-reduction/#selective-scheduling) to learn how to set the schedule. + +If the time since the release is less than the set `minimumReleaseAge` a "pending" status check is added to the branch. +If enough days have passed then the "pending" status is removed, and a "passing" status check is added. + +Some datasources do not provide a release timestamp (in which case this feature is not compatible), and other datasources may provide a release timestamp but it's not supported by Renovate (in which case a feature request needs to be implemented). + +Maven users: you cannot use `minimumReleaseAge` if a Maven source returns unreliable `last-modified` headers. + +There are a couple of uses for `minimumReleaseAge`: + + + +#### Suppress branch/PR creation for X days + +If you combine `minimumReleaseAge=3 days` and `internalChecksFilter="strict"` then Renovate will hold back from creating branches until 3 or more days have elapsed since the version was released. +It's recommended that you enable `dependencyDashboard=true` so you don't lose visibility of these pending PRs. + +#### Prevent holding broken npm packages + +npm packages less than 72 hours (3 days) old can be unpublished, which could result in a service impact if you have already updated to it. +Set `minimumReleaseAge` to `3 days` for npm packages to prevent relying on a package that can be removed from the registry: + +```json +{ + "packageRules": [ + { + "matchDatasources": ["npm"], + "minimumReleaseAge": "3 days" + } + ] +} +``` + +#### Await X time duration before Automerging + +If you have both `automerge` as well as `minimumReleaseAge` enabled, it means that PRs will be created immediately but automerging will be delayed until the time-duration has passed. +This works because Renovate will add a "renovate/stability-days" pending status check to each branch/PR and that pending check will prevent the branch going green to automerge. + + + ## minor Add to this object if you wish to define rules that apply only to minor updates. @@ -3192,55 +3241,6 @@ Configure this to `true` if you wish to get one PR for every separate major vers e.g. if you are on webpack@v1 currently then default behavior is a PR for upgrading to webpack@v3 and not for webpack@v2. If this setting is true then you would get one PR for webpack@v2 and one for webpack@v3. -## minimumReleaseAge - -If this is set _and_ an update has a release timestamp header, then Renovate will check if the set duration has passed. - -Note: Renovate will wait for the set duration to pass for each **separate** version. -Renovate does not wait until the package has seen no releases for x time-interval(`minimumReleaseAge`). -`minimumReleaseAge` is not intended to help with slowing down fast releasing project updates. -If you want to slow down PRs for a specific package, setup a custom schedule for that package. -Read [our selective-scheduling help](https://docs.renovatebot.com/noise-reduction/#selective-scheduling) to learn how to set the schedule. - -If the time since the release is less than the set `minimumReleaseAge` a "pending" status check is added to the branch. -If enough days have passed then the "pending" status is removed, and a "passing" status check is added. - -Some datasources do not provide a release timestamp (in which case this feature is not compatible), and other datasources may provide a release timestamp but it's not supported by Renovate (in which case a feature request needs to be implemented). - -Maven users: you cannot use `minimumReleaseAge` if a Maven source returns unreliable `last-modified` headers. - -There are a couple of uses for `minimumReleaseAge`: - - - -#### Suppress branch/PR creation for X days - -If you combine `minimumReleaseAge=3 days` and `internalChecksFilter="strict"` then Renovate will hold back from creating branches until 3 or more days have elapsed since the version was released. -It's recommended that you enable `dependencyDashboard=true` so you don't lose visibility of these pending PRs. - -#### Prevent holding broken npm packages - -npm packages less than 72 hours (3 days) old can be unpublished, which could result in a service impact if you have already updated to it. -Set `minimumReleaseAge` to `3 days` for npm packages to prevent relying on a package that can be removed from the registry: - -```json -{ - "packageRules": [ - { - "matchDatasources": ["npm"], - "minimumReleaseAge": "3 days" - } - ] -} -``` - -#### Await X time duration before Automerging - -If you have both `automerge` as well as `minimumReleaseAge` enabled, it means that PRs will be created immediately but automerging will be delayed until the time-duration has passed. -This works because Renovate will add a "renovate/stability-days" pending status check to each branch/PR and that pending check will prevent the branch going green to automerge. - - - ## stopUpdatingLabel This feature only works on supported platforms, check the table above. diff --git a/lib/workers/repository/init/__snapshots__/vulnerability.spec.ts.snap b/lib/workers/repository/init/__snapshots__/vulnerability.spec.ts.snap index 3f4c46c952313b..da85ea34939d44 100644 --- a/lib/workers/repository/init/__snapshots__/vulnerability.spec.ts.snap +++ b/lib/workers/repository/init/__snapshots__/vulnerability.spec.ts.snap @@ -21,10 +21,10 @@ exports[`workers/repository/init/vulnerability detectVulnerabilityAlerts() retur "commitMessageSuffix": "[SECURITY]", "dependencyDashboardApproval": false, "groupName": null, + "minimumReleaseAge": "0", "prCreation": "immediate", "rangeStrategy": "update-lockfile", "schedule": [], - "minimumReleaseAge": "0", }, "isVulnerabilityAlert": true, "matchCurrentVersion": "= 1.8.2", @@ -51,10 +51,10 @@ go", "commitMessageSuffix": "[SECURITY]", "dependencyDashboardApproval": false, "groupName": null, + "minimumReleaseAge": "0", "prCreation": "immediate", "rangeStrategy": "update-lockfile", "schedule": [], - "minimumReleaseAge": "0", }, "isVulnerabilityAlert": true, "matchCurrentVersion": "1.8.2", @@ -81,10 +81,10 @@ actions", "commitMessageSuffix": "[SECURITY]", "dependencyDashboardApproval": false, "groupName": null, + "minimumReleaseAge": "0", "prCreation": "immediate", "rangeStrategy": "update-lockfile", "schedule": [], - "minimumReleaseAge": "0", }, "isVulnerabilityAlert": true, "matchCurrentVersion": "== 1.6.7", @@ -126,10 +126,10 @@ Ansible before versions 2.1.4, 2.2.1 is vulnerable to an improper input validati "commitMessageSuffix": "[SECURITY]", "dependencyDashboardApproval": false, "groupName": null, + "minimumReleaseAge": "0", "prCreation": "immediate", "rangeStrategy": "update-lockfile", "schedule": [], - "minimumReleaseAge": "0", }, "isVulnerabilityAlert": true, "matchCurrentVersion": "2.4.2", From cae5c702a758906211f3c80911cdbf0711d28c8b Mon Sep 17 00:00:00 2001 From: RahulGautamSingh Date: Sat, 8 Apr 2023 03:47:34 +0530 Subject: [PATCH 5/8] apply suggestions --- .../custom/stability-days-migration.spec.ts | 8 ++++++++ .../custom/stability-days-migration.ts | 17 +++++++++++++---- lib/config/options/index.ts | 4 ++-- .../__snapshots__/vulnerability.spec.ts.snap | 8 ++++---- .../repository/process/lookup/filter-checks.ts | 2 +- lib/workers/repository/update/branch/index.ts | 8 ++++++-- 6 files changed, 34 insertions(+), 13 deletions(-) diff --git a/lib/config/migrations/custom/stability-days-migration.spec.ts b/lib/config/migrations/custom/stability-days-migration.spec.ts index 1d325341d98bec..b13719bb3901ea 100644 --- a/lib/config/migrations/custom/stability-days-migration.spec.ts +++ b/lib/config/migrations/custom/stability-days-migration.spec.ts @@ -2,6 +2,14 @@ import { StabilityDaysMigration } from './stability-days-migration'; describe('config/migrations/custom/stability-days-migration', () => { it('migrates', () => { + expect(StabilityDaysMigration).toMigrate( + { + stabilityDays: 0, + }, + { + minimumReleaseAge: null, + } + ); expect(StabilityDaysMigration).toMigrate( { stabilityDays: 2, diff --git a/lib/config/migrations/custom/stability-days-migration.ts b/lib/config/migrations/custom/stability-days-migration.ts index 14607b4cc09019..49d86cdabf80ea 100644 --- a/lib/config/migrations/custom/stability-days-migration.ts +++ b/lib/config/migrations/custom/stability-days-migration.ts @@ -7,10 +7,19 @@ export class StabilityDaysMigration extends AbstractMigration { override run(value: unknown): void { if (is.integer(value)) { - this.setSafely( - 'minimumReleaseAge', - value === 1 ? `${value} day` : `${value} days` - ); + let newValue: null | string; + switch (value) { + case 0: + newValue = null; + break; + case 1: + newValue = '1 day'; + break; + default: + newValue = `${value} days`; + break; + } + this.setSafely('minimumReleaseAge', newValue); } } } diff --git a/lib/config/options/index.ts b/lib/config/options/index.ts index d088a6a905c1e6..5e49ac292e05a8 100644 --- a/lib/config/options/index.ts +++ b/lib/config/options/index.ts @@ -1579,7 +1579,7 @@ const options: RenovateOptions[] = [ name: 'minimumReleaseAge', description: 'Time required before a new release is considered stable.', type: 'string', - default: '0', + default: null, }, { name: 'internalChecksAsSuccess', @@ -1718,7 +1718,7 @@ const options: RenovateOptions[] = [ groupName: null, schedule: [], dependencyDashboardApproval: false, - minimumReleaseAge: '0', + minimumReleaseAge: null, rangeStrategy: 'update-lockfile', commitMessageSuffix: '[SECURITY]', branchTopic: `{{{datasource}}}-{{{depName}}}-vulnerability`, diff --git a/lib/workers/repository/init/__snapshots__/vulnerability.spec.ts.snap b/lib/workers/repository/init/__snapshots__/vulnerability.spec.ts.snap index da85ea34939d44..e731e34f30e745 100644 --- a/lib/workers/repository/init/__snapshots__/vulnerability.spec.ts.snap +++ b/lib/workers/repository/init/__snapshots__/vulnerability.spec.ts.snap @@ -21,7 +21,7 @@ exports[`workers/repository/init/vulnerability detectVulnerabilityAlerts() retur "commitMessageSuffix": "[SECURITY]", "dependencyDashboardApproval": false, "groupName": null, - "minimumReleaseAge": "0", + "minimumReleaseAge": null, "prCreation": "immediate", "rangeStrategy": "update-lockfile", "schedule": [], @@ -51,7 +51,7 @@ go", "commitMessageSuffix": "[SECURITY]", "dependencyDashboardApproval": false, "groupName": null, - "minimumReleaseAge": "0", + "minimumReleaseAge": null, "prCreation": "immediate", "rangeStrategy": "update-lockfile", "schedule": [], @@ -81,7 +81,7 @@ actions", "commitMessageSuffix": "[SECURITY]", "dependencyDashboardApproval": false, "groupName": null, - "minimumReleaseAge": "0", + "minimumReleaseAge": null, "prCreation": "immediate", "rangeStrategy": "update-lockfile", "schedule": [], @@ -126,7 +126,7 @@ Ansible before versions 2.1.4, 2.2.1 is vulnerable to an improper input validati "commitMessageSuffix": "[SECURITY]", "dependencyDashboardApproval": false, "groupName": null, - "minimumReleaseAge": "0", + "minimumReleaseAge": null, "prCreation": "immediate", "rangeStrategy": "update-lockfile", "schedule": [], diff --git a/lib/workers/repository/process/lookup/filter-checks.ts b/lib/workers/repository/process/lookup/filter-checks.ts index 32d53438ce82e1..83b625cffdf00f 100644 --- a/lib/workers/repository/process/lookup/filter-checks.ts +++ b/lib/workers/repository/process/lookup/filter-checks.ts @@ -60,7 +60,7 @@ export async function filterInternalChecks( version: newVersion, updateType, } = releaseConfig; - if (is.string(minimumReleaseAge) && releaseTimestamp) { + if (is.nonEmptyString(minimumReleaseAge) && releaseTimestamp) { if (getElapsedMs(releaseTimestamp) < (toMs(minimumReleaseAge) ?? 0)) { // Skip it if it doesn't pass checks logger.trace( diff --git a/lib/workers/repository/update/branch/index.ts b/lib/workers/repository/update/branch/index.ts index 6c8f0053d9b0b9..077e15eacb9b9b 100644 --- a/lib/workers/repository/update/branch/index.ts +++ b/lib/workers/repository/update/branch/index.ts @@ -281,7 +281,8 @@ export async function processBranch( if ( config.upgrades.some( (upgrade) => - (upgrade.minimumReleaseAge && upgrade.releaseTimestamp) || + (is.nonEmptyString(upgrade.minimumReleaseAge) && + upgrade.releaseTimestamp) || isActiveConfidenceLevel(upgrade.minimumConfidence!) ) ) { @@ -290,7 +291,10 @@ export async function processBranch( config.stabilityStatus = 'green'; // Default to 'success' but set 'pending' if any update is pending for (const upgrade of config.upgrades) { - if (is.string(upgrade.minimumReleaseAge) && upgrade.releaseTimestamp) { + if ( + is.nonEmptyString(upgrade.minimumReleaseAge) && + upgrade.releaseTimestamp + ) { const timeElapsed = getElapsedMs(upgrade.releaseTimestamp); if (timeElapsed < (toMs(upgrade.minimumReleaseAge) ?? 0)) { logger.debug( From a4b85fda29f0ad65352e7b9880578daf250dfdac Mon Sep 17 00:00:00 2001 From: RahulGautamSingh Date: Sun, 9 Apr 2023 04:52:39 +0530 Subject: [PATCH 6/8] add documentation --- docs/usage/configuration-options.md | 4 ++++ lib/workers/repository/update/branch/status-checks.ts | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/usage/configuration-options.md b/docs/usage/configuration-options.md index 1856076f15d5fc..47bf364e8016c6 100644 --- a/docs/usage/configuration-options.md +++ b/docs/usage/configuration-options.md @@ -1625,6 +1625,10 @@ Some datasources do not provide a release timestamp (in which case this feature Maven users: you cannot use `minimumReleaseAge` if a Maven source returns unreliable `last-modified` headers. + +!!! note + Configuring this option will add `renovate/stability-days` option to the status checks. + There are a couple of uses for `minimumReleaseAge`: diff --git a/lib/workers/repository/update/branch/status-checks.ts b/lib/workers/repository/update/branch/status-checks.ts index a880fc3a9e6e1e..6fe93414f0f6be 100644 --- a/lib/workers/repository/update/branch/status-checks.ts +++ b/lib/workers/repository/update/branch/status-checks.ts @@ -65,8 +65,8 @@ export async function setStability(config: StabilityConfig): Promise { const context = `renovate/stability-days`; const description = config.stabilityStatus === 'green' - ? 'Updates have met stability days requirement' - : 'Updates have not met stability days requirement'; + ? 'Updates have met minimum release age requirement' + : 'Updates have not met minimum release age requirement'; await setStatusCheck( config.branchName, context, From 1e9cd2b6a9f75d3f3806a1b6192f3f6ce8416195 Mon Sep 17 00:00:00 2001 From: RahulGautamSingh Date: Wed, 12 Apr 2023 13:29:32 +0530 Subject: [PATCH 7/8] Apply suggestions from code review Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com> --- docs/usage/configuration-options.md | 13 +++++++------ lib/config/options/index.ts | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/usage/configuration-options.md b/docs/usage/configuration-options.md index 47bf364e8016c6..30393d53a4892d 100644 --- a/docs/usage/configuration-options.md +++ b/docs/usage/configuration-options.md @@ -1524,8 +1524,8 @@ Currently this applies to the `minimumReleaseAge` check only. - `strict`: All pending releases will be filtered. PRs will be skipped unless a non-pending version is available - `flexible`: Similar to strict, but in the case where all versions are pending then a PR will be created with the highest pending version -The `flexible` mode can result in "flapping" of Pull Requests, where e.g. a pending PR with version `1.0.3` is first released but then downgraded to `1.0.2` once it passes `minimumReleaseAge`. -We recommend that you use the `strict` mode, and enable the `dependencyDashboard` so that you have visibility into suppressed PRs. +The `flexible` mode can result in "flapping" of Pull Requests, for example: a pending PR with version `1.0.3` is first released but then downgraded to `1.0.2` once it passes `minimumReleaseAge`. +We recommend that you use the `strict` mode, and enable the `dependencyDashboard` so that you can see suppressed PRs. ## java @@ -1621,13 +1621,14 @@ Read [our selective-scheduling help](https://docs.renovatebot.com/noise-reductio If the time since the release is less than the set `minimumReleaseAge` a "pending" status check is added to the branch. If enough days have passed then the "pending" status is removed, and a "passing" status check is added. -Some datasources do not provide a release timestamp (in which case this feature is not compatible), and other datasources may provide a release timestamp but it's not supported by Renovate (in which case a feature request needs to be implemented). +Some datasources don't have a release timestamp, in which case this feature is not compatible. +Other datasources may have a release timestamp, but Renovate does not support it yet, in which case a feature request needs to be implemented. Maven users: you cannot use `minimumReleaseAge` if a Maven source returns unreliable `last-modified` headers. !!! note - Configuring this option will add `renovate/stability-days` option to the status checks. + Configuring this option will add a `renovate/stability-days` option to the status checks. There are a couple of uses for `minimumReleaseAge`: @@ -1636,7 +1637,7 @@ There are a couple of uses for `minimumReleaseAge`: #### Suppress branch/PR creation for X days If you combine `minimumReleaseAge=3 days` and `internalChecksFilter="strict"` then Renovate will hold back from creating branches until 3 or more days have elapsed since the version was released. -It's recommended that you enable `dependencyDashboard=true` so you don't lose visibility of these pending PRs. +We recommend that you set `dependencyDashboard=true` so you can see these pending PRs. #### Prevent holding broken npm packages @@ -1656,7 +1657,7 @@ Set `minimumReleaseAge` to `3 days` for npm packages to prevent relying on a pac #### Await X time duration before Automerging -If you have both `automerge` as well as `minimumReleaseAge` enabled, it means that PRs will be created immediately but automerging will be delayed until the time-duration has passed. +If you enabled `automerge` _and_ `minimumReleaseAge`, it means that PRs will be created immediately but automerging will be delayed until the time-duration has passed. This works because Renovate will add a "renovate/stability-days" pending status check to each branch/PR and that pending check will prevent the branch going green to automerge. diff --git a/lib/config/options/index.ts b/lib/config/options/index.ts index 5e49ac292e05a8..233ba616f235a9 100644 --- a/lib/config/options/index.ts +++ b/lib/config/options/index.ts @@ -1584,7 +1584,7 @@ const options: RenovateOptions[] = [ { name: 'internalChecksAsSuccess', description: - 'Whether to consider passing internal checks such as minimumReleaseAge when determining branch status.', + 'Whether to consider passing internal checks such as `minimumReleaseAge` when determining branch status.', type: 'boolean', default: false, }, From f2c459c5606a6c00b8fe75c96d669012531597e9 Mon Sep 17 00:00:00 2001 From: RahulGautamSingh Date: Wed, 12 Apr 2023 13:31:08 +0530 Subject: [PATCH 8/8] Update lib/workers/repository/update/branch/index.spec.ts --- lib/workers/repository/update/branch/index.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/workers/repository/update/branch/index.spec.ts b/lib/workers/repository/update/branch/index.spec.ts index 9635e94486f162..0288741eab8ae7 100644 --- a/lib/workers/repository/update/branch/index.spec.ts +++ b/lib/workers/repository/update/branch/index.spec.ts @@ -185,7 +185,7 @@ describe('workers/repository/update/branch/index', () => { }); }); - it('skips branch if not minimumReleaseAge not met', async () => { + it('skips branch if minimumReleaseAge not met', async () => { schedule.isScheduledNow.mockReturnValueOnce(true); config.prCreation = 'not-pending'; config.upgrades = partial([