From 305f094bd463e47c78a2733ecd5536d9a4ff25e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C8=9Aurcanu=20Dragomir?= <dragomirt22@gmail.com> Date: Fri, 27 Apr 2018 06:45:22 +0300 Subject: [PATCH] Added matchCurrentVersion selector to packageRules (#1835) The matchCurrentVersion option sets a range of versions that a package update can be in. If the package's current version doesn't satisfy the matchCurrentVersion range, it won't match the rule. Closes #1771 --- lib/config/definitions.js | 10 ++++ lib/config/validation.js | 13 ++++- lib/util/semver.js | 2 + lib/workers/package-file/dep-type.js | 7 +++ test/config/__snapshots__/index.spec.js.snap | 1 + .../__snapshots__/validation.spec.js.snap | 9 ++++ test/config/validation.spec.js | 16 +++++++ test/workers/package-file/dep-type.spec.js | 48 +++++++++++++++++++ .../2017-10-05-configuration-options.md | 10 ++++ 9 files changed, 115 insertions(+), 1 deletion(-) diff --git a/lib/config/definitions.js b/lib/config/definitions.js index 22564f7dce..27c8195373 100644 --- a/lib/config/definitions.js +++ b/lib/config/definitions.js @@ -418,6 +418,16 @@ const options = [ cli: false, env: false, }, + { + name: 'matchCurrentVersion', + description: + 'A version or version range to match against the current version of a package. Valid only within `packageRules` object', + type: 'string', + stage: 'depType', + mergeable: true, + cli: false, + env: false, + }, // Version behaviour { name: 'allowedVersions', diff --git a/lib/config/validation.js b/lib/config/validation.js index 3b7d31db5d..cea56a8f1a 100644 --- a/lib/config/validation.js +++ b/lib/config/validation.js @@ -169,6 +169,17 @@ async function validateConfig(config, isPreset, parentPath) { message, }); } + if ( + resolvedRule.matchCurrentVersion && + !isValidSemver(resolvedRule.matchCurrentVersion) + ) { + errors.push({ + depName: 'Configuration Error', + message: `${currentPath}: ${ + resolvedRule.matchCurrentVersion + } isn't a valid semver`, + }); + } } else { errors.push({ depName: 'Configuration Error', @@ -197,7 +208,7 @@ async function validateConfig(config, isPreset, parentPath) { } } if ( - selectors.includes(key) && + (selectors.includes(key) || key === 'matchCurrentVersion') && !(parentPath && parentPath.match(/packageRules\[\d+\]$/)) && // Inside a packageRule (parentPath || !isPreset) // top level in a preset ) { diff --git a/lib/util/semver.js b/lib/util/semver.js index b4e054df13..7c0f41224c 100644 --- a/lib/util/semver.js +++ b/lib/util/semver.js @@ -10,6 +10,7 @@ const { parseRange, parse: parseVersion, stringifyRange } = semverUtils; const { compare: semverSort, gt: isGreaterThan, + intersects: intersectsSemver, maxSatisfying: maxSatisfyingVersion, minSatisfying: minSatisfyingVersion, minor: getMinor, @@ -34,6 +35,7 @@ module.exports = { getMajor, getMinor, getPatch, + intersectsSemver, isGreaterThan, isRange, isStable, diff --git a/lib/workers/package-file/dep-type.js b/lib/workers/package-file/dep-type.js index 3e88cf5374..45d22cea6e 100644 --- a/lib/workers/package-file/dep-type.js +++ b/lib/workers/package-file/dep-type.js @@ -1,6 +1,7 @@ const configParser = require('../../config'); const pkgWorker = require('./package'); const { extractDependencies } = require('../../manager'); +const { intersectsSemver } = require('../../util/semver'); module.exports = { renovateDepType, @@ -62,6 +63,7 @@ function getDepConfig(depTypeConfig, dep) { excludePackagePatterns, packageNames, packagePatterns, + matchCurrentVersion, } = packageRule; let applyRule; if ( @@ -104,6 +106,9 @@ function getDepConfig(depTypeConfig, dep) { } } } + if (applyRule !== false && matchCurrentVersion) { + applyRule = intersectsSemver(dep.currentVersion, matchCurrentVersion); + } if (applyRule !== false && depTypeList && depTypeList.length) { applyRule = depTypeList.includes(dep.depType); } @@ -114,6 +119,8 @@ function getDepConfig(depTypeConfig, dep) { delete depConfig.packagePatterns; delete depConfig.excludePackageNames; delete depConfig.excludePackagePatterns; + delete depConfig.depTypeList; + delete depConfig.matchCurrentVersion; } }); } diff --git a/test/config/__snapshots__/index.spec.js.snap b/test/config/__snapshots__/index.spec.js.snap index 99616eb51a..15bad95a23 100644 --- a/test/config/__snapshots__/index.spec.js.snap +++ b/test/config/__snapshots__/index.spec.js.snap @@ -114,6 +114,7 @@ Object { "logLevel": "error", "major": Object {}, "managerBranchPrefix": "", + "matchCurrentVersion": null, "meteor": Object {}, "minor": Object {}, "mirrorMode": false, diff --git a/test/config/__snapshots__/validation.spec.js.snap b/test/config/__snapshots__/validation.spec.js.snap index ec7d7a889a..f5b8df6ac4 100644 --- a/test/config/__snapshots__/validation.spec.js.snap +++ b/test/config/__snapshots__/validation.spec.js.snap @@ -68,6 +68,15 @@ Array [ ] `; +exports[`config/validation validateConfig(config) invalid matchCurrentVersion triggers an error 1`] = ` +Array [ + Object { + "depName": "Configuration Error", + "message": "packageRules: >= 2.-1.4 isn't a valid semver", + }, +] +`; + exports[`config/validation validateConfig(config) returns nested errors 1`] = ` Array [ Object { diff --git a/test/config/validation.spec.js b/test/config/validation.spec.js index 136f1ed8d5..d010aaf597 100644 --- a/test/config/validation.spec.js +++ b/test/config/validation.spec.js @@ -107,5 +107,21 @@ describe('config/validation', () => { expect(errors).toMatchSnapshot(); expect(errors).toHaveLength(0); }); + it('invalid matchCurrentVersion triggers an error', async () => { + const config = { + packageRules: [ + { + packageNames: ['angular'], + matchCurrentVersion: '>= 2.-1.4', + }, + ], + }; + const { warnings, errors } = await configValidation.validateConfig( + config + ); + expect(warnings).toHaveLength(0); + expect(errors).toMatchSnapshot(); + expect(errors).toHaveLength(1); + }); }); }); diff --git a/test/workers/package-file/dep-type.spec.js b/test/workers/package-file/dep-type.spec.js index e010816452..9bcfb3be43 100644 --- a/test/workers/package-file/dep-type.spec.js +++ b/test/workers/package-file/dep-type.spec.js @@ -260,5 +260,53 @@ describe('lib/workers/package-file/dep-type', () => { expect(res.x).toBeUndefined(); expect(res.packageRules).toBeUndefined(); }); + it('checks if matchCurrentVersion selector is valid and satisfies the condition on range overlap', () => { + const config = { + packageRules: [ + { + packageNames: ['test'], + matchCurrentVersion: '<= 2.0.0', + x: 1, + }, + ], + }; + const res1 = depTypeWorker.getDepConfig(config, { + depName: 'test', + currentVersion: '^1.0.0', + }); + expect(res1.x).toBeDefined(); + }); + it('checks if matchCurrentVersion selector is valid and satisfies the condition on pinned to range overlap', () => { + const config = { + packageRules: [ + { + packageNames: ['test'], + matchCurrentVersion: '>= 2.0.0', + x: 1, + }, + ], + }; + const res1 = depTypeWorker.getDepConfig(config, { + depName: 'test', + currentVersion: '2.4.6', + }); + expect(res1.x).toBeDefined(); + }); + it('checks if matchCurrentVersion selector works with static values', () => { + const config = { + packageRules: [ + { + packageNames: ['test'], + matchCurrentVersion: '4.6.0', + x: 1, + }, + ], + }; + const res1 = depTypeWorker.getDepConfig(config, { + depName: 'test', + currentVersion: '4.6.0', + }); + expect(res1.x).toBeDefined(); + }); }); }); diff --git a/website/docs/_posts/2017-10-05-configuration-options.md b/website/docs/_posts/2017-10-05-configuration-options.md index 303b02ff45..fe1c5afc2e 100644 --- a/website/docs/_posts/2017-10-05-configuration-options.md +++ b/website/docs/_posts/2017-10-05-configuration-options.md @@ -618,6 +618,16 @@ Prefix to be added after `branchPrefix` for distinguishing between branches for This value defaults to empty string, as historically no prefix was necessary for when Renovate was JS-only. Now - for example - we use `docker-` for Docker branches, so they may look like `renovate/docker-ubuntu-16.x`. +## matchCurrentVersion + +If set in a packageRule, the rule will be applied only if the current version of the package matches against this version or range. + +| name | value | +| ---- | ------ | +| type | string | + +`matchCurrentVersion` can be an exact semver version or a semver range. + ## meteor Configuration specific for meteor updates. -- GitLab