diff --git a/docs/usage/configuration-options.md b/docs/usage/configuration-options.md index 9686d61a9ce8261c2c9b12c626d2f9456dee1437..90ea8d7cd4751283c1b6e6af1e6330bcf8c484ac 100644 --- a/docs/usage/configuration-options.md +++ b/docs/usage/configuration-options.md @@ -1175,6 +1175,34 @@ Use this field to restrict rules to a particular datasource. e.g. `matchCurrentVersion` can be an exact semver version or a semver range. +This field also supports Regular Expressions which have to begin and end with `/`. +For example, the following will enforce that only `1.*` versions: + +```json +{ + "packageRules": [ + { + "packagePatterns": ["io.github.resilience4j"], + "matchCurrentVersion": "/^1\\./" + } + ] +} +``` + +This field also supports a special negated regex syntax for ignoring certain versions. +Use the syntax `!/ /` like the following: + +```json +{ + "packageRules": [ + { + "packagePatterns": ["io.github.resilience4j"], + "allowedVersions": "!/^0\\./" + } + ] +} +``` + ### packageNames Use this field if you want to have one or more exact name matches in your package rule. diff --git a/lib/config/__snapshots__/validation.spec.ts.snap b/lib/config/__snapshots__/validation.spec.ts.snap index 722813727ca51c237b9c5d0c8d68635cb5c97446..581510ac4589ed067e7d5738b91d1c3c4675f9cd 100644 --- a/lib/config/__snapshots__/validation.spec.ts.snap +++ b/lib/config/__snapshots__/validation.spec.ts.snap @@ -13,6 +13,19 @@ Array [ ] `; +exports[`config/validation validateConfig(config) catches invalid matchCurrentVersion regex 1`] = ` +Array [ + Object { + "depName": "Configuration Error", + "message": "Invalid regExp for packageRules[1].matchCurrentVersion: \`/***$}{]][/\`", + }, + Object { + "depName": "Configuration Error", + "message": "Invalid regExp for packageRules[3].matchCurrentVersion: \`!/***$}{]][/\`", + }, +] +`; + exports[`config/validation validateConfig(config) catches invalid templates 1`] = ` Array [ Object { diff --git a/lib/config/validation.spec.ts b/lib/config/validation.spec.ts index 258b392e724c1491523acd71910627d6f527e592..4f66b543611cf8bb20d7e0059d0656e686f7a31d 100644 --- a/lib/config/validation.spec.ts +++ b/lib/config/validation.spec.ts @@ -44,6 +44,31 @@ describe('config/validation', () => { expect(errors).toHaveLength(2); expect(errors).toMatchSnapshot(); }); + it('catches invalid matchCurrentVersion regex', async () => { + const config = { + packageRules: [ + { + packageNames: ['foo'], + matchCurrentVersion: '/^2/', + }, + { + packageNames: ['bar'], + matchCurrentVersion: '/***$}{]][/', + }, + { + packageNames: ['baz'], + matchCurrentVersion: '!/^2/', + }, + { + packageNames: ['quack'], + matchCurrentVersion: '!/***$}{]][/', + }, + ], + }; + const { errors } = await configValidation.validateConfig(config); + expect(errors).toHaveLength(2); + expect(errors).toMatchSnapshot(); + }); it('returns nested errors', async () => { const config: RenovateConfig = { foo: 1, diff --git a/lib/config/validation.ts b/lib/config/validation.ts index 8af02bc81f295ec06f84dda600fe978455530d53..0bf9cebffbb737f42e88d741907daca2401020f3 100644 --- a/lib/config/validation.ts +++ b/lib/config/validation.ts @@ -144,7 +144,10 @@ export async function validateConfig( message: `Invalid ${currentPath}: \`${errorMessage}\``, }); } - } else if (key === 'allowedVersions' && isConfigRegex(val)) { + } else if ( + ['allowedVersions', 'matchCurrentVersion'].includes(key) && + isConfigRegex(val) + ) { if (!configRegexPredicate(val)) { errors.push({ depName: 'Configuration Error', diff --git a/lib/util/package-rules.spec.ts b/lib/util/package-rules.spec.ts index 1099c49e1aa8faaf0ec5c411d5f2bffca2767df4..3b8270601cff12562b4b064e509ece6f569ae4d1 100644 --- a/lib/util/package-rules.spec.ts +++ b/lib/util/package-rules.spec.ts @@ -547,6 +547,64 @@ describe('applyPackageRules()', () => { }); expect(res1.x).toBeDefined(); }); + it('checks if matchCurrentVersion selector works with regular expressions', () => { + const config: TestConfig = { + packageRules: [ + { + packageNames: ['test'], + matchCurrentVersion: '/^4/', + x: 1, + }, + ], + }; + const res1 = applyPackageRules({ + ...config, + ...{ + depName: 'test', + currentValue: '4.6.0', + fromVersion: '4.6.0', + }, + }); + const res2 = applyPackageRules({ + ...config, + ...{ + depName: 'test', + currentValue: '5.6.0', + fromVersion: '5.6.0', + }, + }); + expect(res1.x).toBeDefined(); + expect(res2.x).toBeUndefined(); + }); + it('checks if matchCurrentVersion selector works with negated regular expressions', () => { + const config: TestConfig = { + packageRules: [ + { + packageNames: ['test'], + matchCurrentVersion: '!/^4/', + x: 1, + }, + ], + }; + const res1 = applyPackageRules({ + ...config, + ...{ + depName: 'test', + currentValue: '4.6.0', + fromVersion: '4.6.0', + }, + }); + const res2 = applyPackageRules({ + ...config, + ...{ + depName: 'test', + currentValue: '5.6.0', + fromVersion: '5.6.0', + }, + }); + expect(res1.x).toBeUndefined(); + expect(res2.x).toBeDefined(); + }); it('matches paths', () => { const config: TestConfig = { packageFile: 'examples/foo/package.json', diff --git a/lib/util/package-rules.ts b/lib/util/package-rules.ts index d70d10f51d18f8e24f90b937c46264e185ca1b6d..1ea90d29de20549ad71b9dc22d9dd9c0cbaeee98 100644 --- a/lib/util/package-rules.ts +++ b/lib/util/package-rules.ts @@ -2,7 +2,7 @@ import minimatch from 'minimatch'; import { PackageRule, UpdateType, mergeChildConfig } from '../config'; import { logger } from '../logger'; import * as allVersioning from '../versioning'; -import { regEx } from './regex'; +import { configRegexPredicate, isConfigRegex, regEx } from './regex'; // TODO: move to `../config` export interface Config extends Record<string, any> { @@ -192,7 +192,13 @@ function matchesRule(inputConfig: Config, packageRule: PackageRule): boolean { if (matchCurrentVersion) { const version = allVersioning.get(versioning); const matchCurrentVersionStr = matchCurrentVersion.toString(); - if (version.isVersion(matchCurrentVersionStr)) { + if (isConfigRegex(matchCurrentVersionStr)) { + const matches = configRegexPredicate(matchCurrentVersionStr); + if (!matches(currentValue)) { + return false; + } + positiveMatch = true; + } else if (version.isVersion(matchCurrentVersionStr)) { let isMatch = false; try { isMatch = version.matches(matchCurrentVersionStr, currentValue);