diff --git a/docs/usage/configuration-options.md b/docs/usage/configuration-options.md index d26d31e7c16b77..d400e1d58f4778 100644 --- a/docs/usage/configuration-options.md +++ b/docs/usage/configuration-options.md @@ -594,11 +594,38 @@ 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. +Note: 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's some examples of when a release is allowed: + +- The `package.json` declares its `engines.node` as `18` which is a subset of the package release `16 || 18` +- The `package.json` declares its `engines.node` as `^18.10.0` which is a subset of the package release `>=18` +- The `package.json` declares its `engines.node` as `^16.10.0 || >=18.0.0` which is a subset of the package release `>= 16.0.0` + +Here's some examples of when a release is filtered out (disallowed): + +- The `package.json` declares its `engines.node` as `>=16` while the package release has a narrower `16 || 18` +- The `package.json` declares its `engines.node` as `16` while the package release has a narrower `^16.10.0` + +When using with `npm`, it's recommended: + +- Use with `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 34e3b652743a38..a034717ac221e0 100644 --- a/lib/modules/datasource/index.ts +++ b/lib/modules/datasource/index.ts @@ -401,6 +401,7 @@ export async function getPkgReleases( ) === filterIndex ); if (config?.constraintsFiltering === 'strict') { + const filteredReleases: string[] = []; // Filter releases for compatibility for (const [constraintName, constraintValue] of Object.entries( config.constraints ?? {} @@ -413,7 +414,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) => @@ -421,9 +422,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 94109fcb1d2cd6..9d78607c1f10ec 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 {