From 0925f59262913b12c04f9fe4c9e7bf013ec7ba35 Mon Sep 17 00:00:00 2001 From: Rhys Arkins <rhys@arkins.net> Date: Wed, 24 Jan 2018 15:26:37 +0100 Subject: [PATCH] feat: versionStrategy (#1439) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds new config option `versionStrategy` that allows config to override Renovate’s autodetection of when to “widen” an existing semver range, and when to “replace”. --- lib/config/definitions.js | 10 +++ lib/workers/package/versions.js | 32 ++++++++-- .../__snapshots__/versions.spec.js.snap | 64 +++++++++++++++++++ test/workers/package/versions.spec.js | 26 +++++++- .../2017-10-05-configuration-options.md | 20 ++++++ 5 files changed, 145 insertions(+), 7 deletions(-) diff --git a/lib/config/definitions.js b/lib/config/definitions.js index aa9ce9ceec..deadf948b8 100644 --- a/lib/config/definitions.js +++ b/lib/config/definitions.js @@ -405,6 +405,16 @@ const options = [ stage: 'package', type: 'boolean', }, + { + name: 'versionStrategy', + description: + 'Strategy for how to modify/update existing versions/semver. Possible values: auto, replace, or widen', + stage: 'package', + type: 'string', + default: 'auto', + cli: false, + env: false, + }, { name: 'branchPrefix', description: 'Prefix to use for all branch names', diff --git a/lib/workers/package/versions.js b/lib/workers/package/versions.js index 5c482a08f7..04f77db381 100644 --- a/lib/workers/package/versions.js +++ b/lib/workers/package/versions.js @@ -199,7 +199,15 @@ function determineUpgrades(npmDep, config) { .map(upgrade => ({ ...upgrade, ...{ isRange: true } })) .map(upgrade => { const { major, minor } = semverUtils.parse(upgrade.newVersion); - if (lastSemver.operator === '~' && semverParsed.length === 1) { + const canReplace = config.versionStrategy !== 'widen'; + const forceReplace = config.versionStrategy === 'replace'; + const canWiden = config.versionStrategy !== 'replace'; + const forceWiden = config.versionStrategy === 'widen'; + if ( + lastSemver.operator === '~' && + canReplace && + (semverParsed.length === 1 || forceReplace) + ) { // Utilise that a.b is the same as ~a.b.0 const minSatisfying = semver.minSatisfying( versionList, @@ -207,19 +215,27 @@ function determineUpgrades(npmDep, config) { ); // Add a tilde before that version number return { ...upgrade, ...{ newVersion: `~${minSatisfying}` } }; - } else if (lastSemver.operator === '~' && semverParsed.length > 1) { + } else if ( + lastSemver.operator === '~' && + canWiden && + (semverParsed.length > 1 || forceWiden) + ) { // Utilise that a.b is the same as ~a.b.0 const minSatisfying = semver.minSatisfying( versionList, `${major}.${minor}` ); // Add a tilde before that version number - const newVersion = `~${minSatisfying}`; + const newVersion = `${currentVersion} || ~${minSatisfying}`; return { ...upgrade, - newVersion: currentVersion + ' || ' + newVersion, + newVersion, }; - } else if (lastSemver.operator === '^' && semverParsed.length === 1) { + } else if ( + lastSemver.operator === '^' && + canReplace && + (semverParsed.length === 1 || forceReplace) + ) { let newVersion; // Special case where major and minor are 0 if (major === '0' && minor === '0') { @@ -232,7 +248,11 @@ function determineUpgrades(npmDep, config) { newVersion = `^${minSatisfying}`; } return { ...upgrade, newVersion }; - } else if (lastSemver.operator === '^' && semverParsed.length > 1) { + } else if ( + lastSemver.operator === '^' && + canWiden && + (semverParsed.length > 1 || forceWiden) + ) { // If version is < 1, then semver treats ^ same as ~ const newRange = major === '0' ? `${major}.${minor}` : `${major}`; const minSatisfying = semver.minSatisfying(versionList, newRange); diff --git a/test/workers/package/__snapshots__/versions.spec.js.snap b/test/workers/package/__snapshots__/versions.spec.js.snap index dc234de8c6..7c3ebcf440 100644 --- a/test/workers/package/__snapshots__/versions.spec.js.snap +++ b/test/workers/package/__snapshots__/versions.spec.js.snap @@ -80,6 +80,38 @@ Array [ ] `; +exports[`workers/package/versions .determineUpgrades(npmDep, config) replaces major complex ranged versions if configured 1`] = ` +Array [ + Object { + "changeLogFromVersion": "2.7.0", + "changeLogToVersion": "3.8.1", + "isMajor": true, + "isRange": true, + "newVersion": "^3.0.0", + "newVersionMajor": 3, + "newVersionMinor": 8, + "type": "major", + "unpublishable": false, + }, +] +`; + +exports[`workers/package/versions .determineUpgrades(npmDep, config) replaces minor complex ranged versions if configured 1`] = ` +Array [ + Object { + "changeLogFromVersion": "1.3.0", + "changeLogToVersion": "1.4.1", + "isMinor": true, + "isRange": true, + "newVersion": "~1.4.0", + "newVersionMajor": 1, + "newVersionMinor": 4, + "type": "minor", + "unpublishable": false, + }, +] +`; + exports[`workers/package/versions .determineUpgrades(npmDep, config) return warning if empty versions 1`] = ` Object { "message": "No versions returned from registry for this package", @@ -857,6 +889,38 @@ Array [ ] `; +exports[`workers/package/versions .determineUpgrades(npmDep, config) widens major ranged versions if configured 1`] = ` +Array [ + Object { + "changeLogFromVersion": "2.7.0", + "changeLogToVersion": "3.8.1", + "isMajor": true, + "isRange": true, + "newVersion": "^2.0.0 || ^3.0.0", + "newVersionMajor": 3, + "newVersionMinor": 8, + "type": "major", + "unpublishable": false, + }, +] +`; + +exports[`workers/package/versions .determineUpgrades(npmDep, config) widens minor ranged versions if configured 1`] = ` +Array [ + Object { + "changeLogFromVersion": "1.3.0", + "changeLogToVersion": "1.4.1", + "isMinor": true, + "isRange": true, + "newVersion": "~1.3.0 || ~1.4.0", + "newVersionMajor": 1, + "newVersionMinor": 4, + "type": "minor", + "unpublishable": false, + }, +] +`; + exports[`workers/package/versions .determineUpgrades(npmDep, config) widens stanndalone major OR ranges 1`] = ` Array [ Object { diff --git a/test/workers/package/versions.spec.js b/test/workers/package/versions.spec.js index 6afb2e66e2..c9d9a3df43 100644 --- a/test/workers/package/versions.spec.js +++ b/test/workers/package/versions.spec.js @@ -8,7 +8,7 @@ let config; describe('workers/package/versions', () => { beforeEach(() => { - config = require('../../../lib/config/defaults').getConfig(); + config = { ...require('../../../lib/config/defaults').getConfig() }; config.pinVersions = true; }); @@ -104,6 +104,30 @@ describe('workers/package/versions', () => { config.currentVersion = '~1.0.0'; expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); }); + it('widens minor ranged versions if configured', () => { + config.pinVersions = false; + config.currentVersion = '~1.3.0'; + config.versionStrategy = 'widen'; + expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + }); + it('replaces minor complex ranged versions if configured', () => { + config.pinVersions = false; + config.currentVersion = '~1.2.0 || ~1.3.0'; + config.versionStrategy = 'replace'; + expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + }); + it('widens major ranged versions if configured', () => { + config.pinVersions = false; + config.currentVersion = '^2.0.0'; + config.versionStrategy = 'widen'; + expect(versions.determineUpgrades(webpackJson, config)).toMatchSnapshot(); + }); + it('replaces major complex ranged versions if configured', () => { + config.pinVersions = false; + config.currentVersion = '^1.0.0 || ^2.0.0'; + config.versionStrategy = 'replace'; + expect(versions.determineUpgrades(webpackJson, config)).toMatchSnapshot(); + }); it('pins minor ranged versions', () => { config.currentVersion = '^1.0.0'; expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); diff --git a/website/docs/_posts/2017-10-05-configuration-options.md b/website/docs/_posts/2017-10-05-configuration-options.md index 3abd6bf4ce..4db8319de3 100644 --- a/website/docs/_posts/2017-10-05-configuration-options.md +++ b/website/docs/_posts/2017-10-05-configuration-options.md @@ -1004,6 +1004,26 @@ When schedules are in use, it generally means "no updates". However there are ca This is default true, meaning that Renovate will perform certain "desirable" updates to _existing_ PRs even when outside of schedule. If you wish to disable all updates outside of scheduled hours then set this field to false. +## versionStrategy + +Strategy for how to modify/update existing versions/semver. Possible values: auto, replace, or widen + +| name | value | +| ------- | ------ | +| type | string | +| default | 'auto' | + +npm-only. + +Renovate's "auto" strategy for updating versions is like this: + +1. If the existing version already ends with an "or" operator - e.g. `"^1.0.0 || ^2.0.0"` - then Renovate will widen it, e.g. making it into `"^1.0.0 || ^2.0.0 || ^3.0.0"`. +2. Otherwise, replace it. e.g. `"^2.0.0"` would be replaced by `"^3.0.0"` + +You can override logic either way, by setting it to `replace` or `widen`. e.g. if the currentVersion is `"^1.0.0 || ^2.0.0"` but you configure `versionStrategy=replace` then the result will be `"^3.0.0"`. + +Or for example if you configure all `peerDependencies` with `versionStrategy=widen` and have `"react": "^15.0.0"` as current version then it will be updated to `"react": "^15.0.0 || ^16.0.0"`. + ## yarnrc A string copy of yarnrc file. -- GitLab