diff --git a/lib/config/definitions.js b/lib/config/definitions.js index aa9ce9ceecaadc67da37adc7d84e19267da14465..deadf948b8ffa2eb4b9bb2670a7b6774d5e3a403 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 5c482a08f70f7fd1f87d980f9006149389fdc221..04f77db3817a48280f4756053211cf89c79e5a82 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 dc234de8c66d0c7e0857826d384a28166f2cc6aa..7c3ebcf4403c0dd6a40f12499c7a0475a4ba5292 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 6afb2e66e22efde958f8a05986f72c0c0e9bcd70..c9d9a3df4320617f5ccd349c9157615df47f20df 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 3abd6bf4ceb4b667bd5df2f690f09fbe0c32ce00..4db8319de3e800f8e219de57b3a7a8ecdccbdd3c 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.