Skip to content

Commit

Permalink
feat(config): rename stabilityDays to minimumReleaseAge (#21376)
Browse files Browse the repository at this point in the history
Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com>
  • Loading branch information
RahulGautamSingh and HonkingGoose committed Apr 12, 2023
1 parent d7b7f04 commit 135e6cd
Show file tree
Hide file tree
Showing 17 changed files with 194 additions and 106 deletions.
111 changes: 58 additions & 53 deletions docs/usage/configuration-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -1520,14 +1520,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 @@ -1610,6 +1610,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 @@ -2585,7 +2639,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 @@ -3194,55 +3248,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', () => {
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 {
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

0 comments on commit 135e6cd

Please sign in to comment.