From 1ffc1e555251460e0eac423e168b32807528519a Mon Sep 17 00:00:00 2001 From: Rhys Arkins Date: Sun, 18 Jun 2023 15:19:59 +0200 Subject: [PATCH] feat(npm): support constraintsFiltering=strict (#22447) Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com> --- docs/usage/configuration-options.md | 30 ++++++++++++++++++++++++-- lib/modules/datasource/index.ts | 16 +++++++++++++- lib/modules/datasource/npm/get.spec.ts | 3 +++ lib/modules/datasource/npm/get.ts | 4 ++++ lib/modules/datasource/npm/types.ts | 1 + 5 files changed, 51 insertions(+), 3 deletions(-) diff --git a/docs/usage/configuration-options.md b/docs/usage/configuration-options.md index c9a9eada63bf73..7e95c2d6c2887e 100644 --- a/docs/usage/configuration-options.md +++ b/docs/usage/configuration-options.md @@ -602,11 +602,37 @@ Renovate supports two options: - `none`: No release filtering (all releases allowed) - `strict`: If the release's constraints match the package file constraints, then it's included -We are working on adding more advanced filtering options. +More advanced filtering options may come in future. -Note: There must be a `constraints` object in your Renovate config for this to work. +There must be a `constraints` object in your Renovate config, or constraints detected from package files, for this to work. This feature is limited to `packagist`, `npm`, and `pypi` datasources. + +!!! warning + Enabling this feature may result in many package updates being filtered out silently. + See below for a description of how it works. + +When `constraintsFiltering=strict`, the following logic applies: + +- Are there `constraints` for this repository, either detected from source or from config? +- Does this package's release declare constraints of its own (e.g. `engines` in Node.js)? +- If so, filter out this release unless the repository constraint is a _subset_ of the release constraint + +Here are some examples: + +| Your repo engines.node | Dependency release engines.node | Result | +| ------------------------ | ------------------------------- | -------- | +| `18` | `16 \|\| 18` | allowed | +| `^18.10.0` | `>=18` | allowed | +| `^16.10.0 \|\| >=18.0.0` | `>= 16.0.0` | allowed | +| `>=16` | `16 \|\| 18` | filtered | +| `16` | `^16.10.0` | filtered | + +When using with `npm`, we recommend you: + +- Use `constraintsFiltering` on `dependencies`, not `devDependencies` (usually you do not need to be strict about development dependencies) +- Do _not_ enable `rollbackPrs` at the same time (otherwise your _current_ version may be rolled back if it's incompatible) + ## defaultRegistryUrls Override a datasource's default registries with this config option. diff --git a/lib/modules/datasource/index.ts b/lib/modules/datasource/index.ts index ec0f65099e4e9e..f9529ddfe1c612 100644 --- a/lib/modules/datasource/index.ts +++ b/lib/modules/datasource/index.ts @@ -399,6 +399,7 @@ export async function getPkgReleases( res.releases = uniq(res.releases, (x, y) => x.version === y.version); if (config?.constraintsFiltering === 'strict') { + const filteredReleases: string[] = []; // Filter releases for compatibility for (const [constraintName, constraintValue] of Object.entries( config.constraints ?? {} @@ -411,7 +412,7 @@ export async function getPkgReleases( return true; } - return constraint.some( + const satisfiesConstraints = constraint.some( // If the constraint value is a subset of any release's constraints, then it's OK // fallback to release's constraint match if subset is not supported by versioning (releaseConstraint) => @@ -419,9 +420,22 @@ export async function getPkgReleases( (version.subset?.(constraintValue, releaseConstraint) ?? version.matches(constraintValue, releaseConstraint)) ); + if (!satisfiesConstraints) { + filteredReleases.push(release.version); + } + return satisfiesConstraints; }); } } + if (filteredReleases.length) { + logger.debug( + `Filtered ${ + filteredReleases.length + } releases for ${packageName} due to constraintsFiltering=strict: ${filteredReleases.join( + ', ' + )}` + ); + } } // Strip constraints from releases result res.releases.forEach((release) => { diff --git a/lib/modules/datasource/npm/get.spec.ts b/lib/modules/datasource/npm/get.spec.ts index c0a7850746ca9c..907ba35ab9d8a2 100644 --- a/lib/modules/datasource/npm/get.spec.ts +++ b/lib/modules/datasource/npm/get.spec.ts @@ -334,6 +334,9 @@ describe('modules/datasource/npm/get', () => { type: 'git', url: 'https://github.com/vuejs/vue-next.git', }, + engines: { + node: '>= 8.9.0', + }, }, }, 'dist-tags': { latest: '2.0.0' }, diff --git a/lib/modules/datasource/npm/get.ts b/lib/modules/datasource/npm/get.ts index 1ff4276dc02924..30f18299df4553 100644 --- a/lib/modules/datasource/npm/get.ts +++ b/lib/modules/datasource/npm/get.ts @@ -176,6 +176,10 @@ export async function getDependency( if (res.versions?.[version].deprecated) { release.isDeprecated = true; } + const nodeConstraint = res.versions?.[version].engines?.node; + if (is.nonEmptyString(nodeConstraint)) { + release.constraints = { node: [nodeConstraint] }; + } const source = PackageSource.parse(res.versions?.[version].repository); if (source.sourceUrl && source.sourceUrl !== dep.sourceUrl) { release.sourceUrl = source.sourceUrl; diff --git a/lib/modules/datasource/npm/types.ts b/lib/modules/datasource/npm/types.ts index 64a9764705f0e5..e67dd987a0e0ef 100644 --- a/lib/modules/datasource/npm/types.ts +++ b/lib/modules/datasource/npm/types.ts @@ -17,6 +17,7 @@ export interface NpmResponseVersion { gitHead?: string; dependencies?: Record; devDependencies?: Record; + engines?: Record; } export interface NpmResponse {