Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(config): rename stabilityDays to minimumReleaseAge #21376

Merged
merged 11 commits into from
Apr 12, 2023
111 changes: 58 additions & 53 deletions docs/usage/configuration-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -1518,14 +1518,14 @@ 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`.
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

Expand Down Expand Up @@ -1608,6 +1608,60 @@ 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 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.

<!-- prettier-ignore -->
!!! note
Configuring this option will add a `renovate/stability-days` option to the status checks.

There are a couple of uses for `minimumReleaseAge`:

<!-- markdownlint-disable MD001 -->

#### 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.
We recommend that you set `dependencyDashboard=true` so you can see 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 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.

<!-- markdownlint-enable MD001 -->

## minor

Add to this object if you wish to define rules that apply only to minor updates.
Expand Down Expand Up @@ -2583,7 +2637,7 @@ This is why we configured an upper limit for how long we wait until creating a P

<!-- prettier-ignore -->
!!! 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

Expand Down Expand Up @@ -3192,55 +3246,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.

## stabilityDays

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.

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

There are a couple of uses for `stabilityDays`:

<!-- markdownlint-disable MD001 -->

#### 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.
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:

```json
{
"packageRules": [
{
"matchDatasources": ["npm"],
"stabilityDays": 3
}
]
}
```

#### Await X days 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.
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.

<!-- markdownlint-enable MD001 -->

## stopUpdatingLabel

This feature only works on supported platforms, check the table above.
Expand Down
30 changes: 30 additions & 0 deletions lib/config/migrations/custom/stability-days-migration.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { StabilityDaysMigration } from './stability-days-migration';

describe('config/migrations/custom/stability-days-migration', () => {
it('migrates', () => {
RahulGautamSingh marked this conversation as resolved.
Show resolved Hide resolved
expect(StabilityDaysMigration).toMigrate(
{
stabilityDays: 0,
},
{
minimumReleaseAge: null,
}
);
expect(StabilityDaysMigration).toMigrate(
{
stabilityDays: 2,
},
{
minimumReleaseAge: '2 days',
}
);
expect(StabilityDaysMigration).toMigrate(
{
stabilityDays: 1,
},
{
minimumReleaseAge: '1 day',
}
);
});
});
25 changes: 25 additions & 0 deletions lib/config/migrations/custom/stability-days-migration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
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)) {
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);
}
}
}
2 changes: 2 additions & 0 deletions lib/config/migrations/migrations-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -143,6 +144,7 @@ export class MigrationsService {
SemanticPrefixMigration,
MatchDatasourcesMigration,
DatasourceMigration,
StabilityDaysMigration,
];

static run(originalConfig: RenovateConfig): RenovateConfig {
Expand Down
13 changes: 6 additions & 7 deletions lib/config/options/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: null,
},
{
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,
},
Expand Down Expand Up @@ -1719,7 +1718,7 @@ const options: RenovateOptions[] = [
groupName: null,
schedule: [],
dependencyDashboardApproval: false,
stabilityDays: 0,
minimumReleaseAge: null,
rangeStrategy: 'update-lockfile',
commitMessageSuffix: '[SECURITY]',
branchTopic: `{{{datasource}}}-{{{depName}}}-vulnerability`,
Expand Down
2 changes: 1 addition & 1 deletion lib/config/presets/internal/npm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export const presets: Record<string, Preset> = {
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',
},
},
};
14 changes: 13 additions & 1 deletion lib/util/date.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { getElapsedDays, getElapsedHours, getElapsedMinutes } from './date';
import {
rarkins marked this conversation as resolved.
Show resolved Hide resolved
getElapsedDays,
getElapsedHours,
getElapsedMinutes,
getElapsedMs,
} from './date';

const ONE_MINUTE_MS = 60 * 1000;
const ONE_HOUR_MS = 60 * ONE_MINUTE_MS;
Expand Down Expand Up @@ -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);
});
});
});
4 changes: 4 additions & 0 deletions lib/util/date.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
2 changes: 2 additions & 0 deletions lib/util/pretty-time.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ exports[`workers/repository/init/vulnerability detectVulnerabilityAlerts() retur
"commitMessageSuffix": "[SECURITY]",
"dependencyDashboardApproval": false,
"groupName": null,
"minimumReleaseAge": null,
"prCreation": "immediate",
"rangeStrategy": "update-lockfile",
"schedule": [],
"stabilityDays": 0,
},
"isVulnerabilityAlert": true,
"matchCurrentVersion": "= 1.8.2",
Expand All @@ -51,10 +51,10 @@ go",
"commitMessageSuffix": "[SECURITY]",
"dependencyDashboardApproval": false,
"groupName": null,
"minimumReleaseAge": null,
"prCreation": "immediate",
"rangeStrategy": "update-lockfile",
"schedule": [],
"stabilityDays": 0,
},
"isVulnerabilityAlert": true,
"matchCurrentVersion": "1.8.2",
Expand All @@ -81,10 +81,10 @@ actions",
"commitMessageSuffix": "[SECURITY]",
"dependencyDashboardApproval": false,
"groupName": null,
"minimumReleaseAge": null,
"prCreation": "immediate",
"rangeStrategy": "update-lockfile",
"schedule": [],
"stabilityDays": 0,
},
"isVulnerabilityAlert": true,
"matchCurrentVersion": "== 1.6.7",
Expand Down Expand Up @@ -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": null,
"prCreation": "immediate",
"rangeStrategy": "update-lockfile",
"schedule": [],
"stabilityDays": 0,
},
"isVulnerabilityAlert": true,
"matchCurrentVersion": "2.4.2",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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": [],
Expand All @@ -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": [
Expand Down
Loading