diff --git a/lib/workers/package/versions.js b/lib/workers/package/versions.js index b87e7196c3ae069672f9275159c92812a900871a..d8ac5762ab83f7b0e32ff1c7498039db2061c4e2 100644 --- a/lib/workers/package/versions.js +++ b/lib/workers/package/versions.js @@ -146,7 +146,7 @@ function determineUpgrades(npmDep, config) { } }); // Return only the values - we don't need the keys anymore - const upgrades = Object.keys(allUpgrades).map(key => allUpgrades[key]); + let upgrades = Object.keys(allUpgrades).map(key => allUpgrades[key]); for (const upgrade of upgrades) { const elapsed = moment().diff( moment(versions[upgrade.newVersion].time), @@ -160,19 +160,33 @@ function determineUpgrades(npmDep, config) { return upgrades; } - // The user prefers to maintain ranges, so we need to unpin our upgrades + logger.debug('User wanrs ranges - filtering out pins'); + upgrades = upgrades.filter(upgrade => upgrade.type !== 'pin'); + + // Return empty if all results were pins + if (!upgrades.length) { + logger.debug('No upgrades left - returning'); + return []; + } + + // Check if it's a range type we support const semverParsed = semverUtils.parseRange(currentVersion); + // Check the "last" part, which is also the first and only if it's a simple semver + const [currentSemver] = semverParsed.slice(-1); if (semverParsed.length > 1) { - // We don't know how to support complex semver ranges, so don't upgrade - result.message = `Complex semver ranges such as "${currentVersion}" are not yet supported so won't ever be upgraded`; - logger.info('Semver warning: ' + result.message); - return [result]; + if (currentSemver.operator !== '<' && currentSemver.operator !== '<=') { + // We don't know how to support complex semver ranges, so don't upgrade + result.message = `Complex semver ranges such as "${currentVersion}" are not yet supported so will be skipped`; + logger.info( + { dependency: npmDep.name, upgrades, semverParsed }, + 'Semver warning: ' + result.message + ); + return [result]; + } + logger.debug('Found less than range'); } - // We know we have a simple semver, now check which operator it is - const currentSemver = semverParsed[0]; // Loop through all upgrades and convert to ranges const rangedUpgrades = _(upgrades) - .reject(upgrade => upgrade.type === 'pin') .map(upgrade => ({ ...upgrade, ...{ isRange: true } })) .map(upgrade => { const { major, minor } = semverUtils.parse(upgrade.newVersion); @@ -200,13 +214,55 @@ function determineUpgrades(npmDep, config) { // Add in the caret return { ...upgrade, ...{ newVersion: `^${minSatisfying}` } }; } else if (currentSemver.operator === '<=') { - // Example: <= 1.2.0 - return { - ...upgrade, - ...{ - newVersion: `<= ${upgrade.newVersion}`, - }, - }; + const minorZero = !currentSemver.minor || currentSemver.minor === '0'; + const patchZero = !currentSemver.patch || currentSemver.patch === '0'; + const newRange = [...semverParsed]; + if (minorZero && patchZero) { + logger.debug('Found a less than major'); + newRange[newRange.length - 1].major = String( + upgrade.newVersionMajor + 1 + ); + } else if (patchZero) { + logger.debug('Found a less than minor'); + newRange[newRange.length - 1].major = String(upgrade.newVersionMajor); + newRange[newRange.length - 1].minor = String( + upgrade.newVersionMinor + 1 + ); + } else { + logger.debug('Found a less than full semver'); + newRange[newRange.length - 1].major = String(upgrade.newVersionMajor); + newRange[newRange.length - 1].minor = String(upgrade.newVersionMinor); + newRange[newRange.length - 1].patch = String( + semver.patch(upgrade.newVersion) + ); + } + const newVersion = semverUtils.stringifyRange(newRange); + return { ...upgrade, newVersion }; + } else if (currentSemver.operator === '<') { + const minorZero = !currentSemver.minor || currentSemver.minor === '0'; + const patchZero = !currentSemver.patch || currentSemver.patch === '0'; + const newRange = [...semverParsed]; + if (minorZero && patchZero) { + logger.debug('Found a less than major'); + newRange[newRange.length - 1].major = String( + upgrade.newVersionMajor + 1 + ); + } else if (patchZero) { + logger.debug('Found a less than minor'); + newRange[newRange.length - 1].major = String(upgrade.newVersionMajor); + newRange[newRange.length - 1].minor = String( + upgrade.newVersionMinor + 1 + ); + } else { + logger.debug('Found full semver minor'); + newRange[newRange.length - 1].major = String(upgrade.newVersionMajor); + newRange[newRange.length - 1].minor = String(upgrade.newVersionMinor); + newRange[newRange.length - 1].patch = String( + semver.patch(upgrade.newVersion) + 1 + ); + } + const newVersion = semverUtils.stringifyRange(newRange); + return { ...upgrade, newVersion }; } else if (currentSemver.minor === undefined) { // Example: 1 return { ...upgrade, ...{ newVersion: `${major}` } }; @@ -220,12 +276,16 @@ function determineUpgrades(npmDep, config) { // Example: 1.2.x return { ...upgrade, ...{ newVersion: `${major}.${minor}.x` } }; } + // istanbul ignore next result.message = `The current semver range "${currentVersion}" is not supported so won't ever be upgraded`; + // istanbul ignore next logger.warn(result.message); + // istanbul ignore next return null; }) .compact() .value(); + // istanbul ignore if if (result.message) { // There must have been an error converting to ranges return [result]; diff --git a/test/workers/package/__snapshots__/versions.spec.js.snap b/test/workers/package/__snapshots__/versions.spec.js.snap index 9e0e70b2d9ad8f1f32c1b704f9f4af9d9e4444d3..0c481d3f9d05789d3f9ba2d1b753cd1496b772d6 100644 --- a/test/workers/package/__snapshots__/versions.spec.js.snap +++ b/test/workers/package/__snapshots__/versions.spec.js.snap @@ -39,7 +39,7 @@ Array [ exports[`workers/package/versions .determineUpgrades(npmDep, config) ignores complex ranges when not pinning 1`] = ` Object { - "message": "Complex semver ranges such as \\"^0.7.0 || ^0.8.0\\" are not yet supported so won't ever be upgraded", + "message": "Complex semver ranges such as \\"^0.7.0 || ^0.8.0\\" are not yet supported so will be skipped", "type": "warning", } `; @@ -78,10 +78,10 @@ Array [ ] `; -exports[`workers/package/versions .determineUpgrades(npmDep, config) rejects less than ranges without pinning 1`] = ` +exports[`workers/package/versions .determineUpgrades(npmDep, config) rejects reverse ordered less than greater than 1`] = ` Array [ Object { - "message": "The current semver range \\"< 0.7.2\\" is not supported so won't ever be upgraded", + "message": "Complex semver ranges such as \\"<= 0.8.0 >= 0.5.0\\" are not yet supported so will be skipped", "type": "warning", }, ] @@ -511,6 +511,135 @@ Array [ ] `; +exports[`workers/package/versions .determineUpgrades(npmDep, config) upgrades less than ranges without pinning 1`] = ` +Array [ + Object { + "changeLogFromVersion": "0.7.1", + "changeLogToVersion": "0.9.7", + "isMinor": true, + "isRange": true, + "newVersion": "< 0.9.8", + "newVersionMajor": 0, + "newVersionMinor": 9, + "type": "minor", + "unpublishable": false, + }, + Object { + "changeLogFromVersion": "0.7.1", + "changeLogToVersion": "1.4.1", + "isMajor": true, + "isRange": true, + "newVersion": "< 1.4.2", + "newVersionMajor": 1, + "newVersionMinor": 4, + "type": "major", + "unpublishable": false, + }, +] +`; + +exports[`workers/package/versions .determineUpgrades(npmDep, config) upgrades major greater than less than ranges without pinning 1`] = ` +Array [ + Object { + "changeLogFromVersion": "0.9.7", + "changeLogToVersion": "1.4.1", + "isMajor": true, + "isRange": true, + "newVersion": ">= 0.5.0 < 2.0.0", + "newVersionMajor": 1, + "newVersionMinor": 4, + "type": "major", + "unpublishable": false, + }, +] +`; + +exports[`workers/package/versions .determineUpgrades(npmDep, config) upgrades major less than equal ranges 1`] = ` +Array [ + Object { + "changeLogFromVersion": "1.0.0", + "changeLogToVersion": "1.4.1", + "isMinor": true, + "isRange": true, + "newVersion": "<= 2.0.0", + "newVersionMajor": 1, + "newVersionMinor": 4, + "type": "minor", + "unpublishable": false, + }, +] +`; + +exports[`workers/package/versions .determineUpgrades(npmDep, config) upgrades major less than ranges without pinning 1`] = ` +Array [ + Object { + "changeLogFromVersion": "0.9.7", + "changeLogToVersion": "1.4.1", + "isMajor": true, + "isRange": true, + "newVersion": "< 2.0.0", + "newVersionMajor": 1, + "newVersionMinor": 4, + "type": "major", + "unpublishable": false, + }, +] +`; + +exports[`workers/package/versions .determineUpgrades(npmDep, config) upgrades minor greater than less than equals ranges without pinning 1`] = ` +Array [ + Object { + "changeLogFromVersion": "0.8.0", + "changeLogToVersion": "0.9.7", + "isMinor": true, + "isRange": true, + "newVersion": ">= 0.5.0 <= 0.10.0", + "newVersionMajor": 0, + "newVersionMinor": 9, + "type": "minor", + "unpublishable": false, + }, + Object { + "changeLogFromVersion": "0.8.0", + "changeLogToVersion": "1.4.1", + "isMajor": true, + "isRange": true, + "newVersion": ">= 0.5.0 <= 1.5.0", + "newVersionMajor": 1, + "newVersionMinor": 4, + "type": "major", + "unpublishable": false, + }, +] +`; + +exports[`workers/package/versions .determineUpgrades(npmDep, config) upgrades minor greater than less than ranges without pinning 1`] = ` +Array [ + Object { + "changeLogFromVersion": "0.7.2", + "changeLogToVersion": "0.9.7", + "isMinor": true, + "isRange": true, + "newVersion": ">= 0.5.0 < 0.10.0", + "newVersionMajor": 0, + "newVersionMinor": 9, + "type": "minor", + "unpublishable": false, + }, + Object { + "changeLogFromVersion": "0.7.2", + "changeLogToVersion": "1.4.1", + "isMajor": true, + "isRange": true, + "newVersion": ">= 0.5.0 < 1.5.0", + "newVersionMajor": 1, + "newVersionMinor": 4, + "type": "major", + "unpublishable": false, + }, +] +`; + exports[`workers/package/versions .determineUpgrades(npmDep, config) upgrades minor ranged versions 1`] = ` Array [ Object { diff --git a/test/workers/package/versions.spec.js b/test/workers/package/versions.spec.js index 133571951b9b21b2d2c616eca1876f4f61a56d67..57e1960b647a2eaa62427ae246da4c81adea304b 100644 --- a/test/workers/package/versions.spec.js +++ b/test/workers/package/versions.spec.js @@ -173,11 +173,54 @@ describe('workers/package/versions', () => { config.currentVersion = '<= 0.7.2'; expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); }); - it('rejects less than ranges without pinning', () => { + it('upgrades less than ranges without pinning', () => { config.pinVersions = false; config.currentVersion = '< 0.7.2'; expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); }); + it('upgrades major less than equal ranges', () => { + config.pinVersions = false; + config.currentVersion = '<= 1.0.0'; + const res = versions.determineUpgrades(qJson, config); + expect(res).toMatchSnapshot(); + expect(res[0].newVersion).toEqual('<= 2.0.0'); + }); + it('upgrades major less than ranges without pinning', () => { + config.pinVersions = false; + config.currentVersion = '< 1.0.0'; + const res = versions.determineUpgrades(qJson, config); + expect(res).toMatchSnapshot(); + expect(res[0].newVersion).toEqual('< 2.0.0'); + }); + it('upgrades major greater than less than ranges without pinning', () => { + config.pinVersions = false; + config.currentVersion = '>= 0.5.0 < 1.0.0'; + const res = versions.determineUpgrades(qJson, config); + expect(res).toMatchSnapshot(); + expect(res[0].newVersion).toEqual('>= 0.5.0 < 2.0.0'); + }); + it('upgrades minor greater than less than ranges without pinning', () => { + config.pinVersions = false; + config.currentVersion = '>= 0.5.0 < 0.8.0'; + const res = versions.determineUpgrades(qJson, config); + expect(res).toMatchSnapshot(); + expect(res[0].newVersion).toEqual('>= 0.5.0 < 0.10.0'); + expect(res[1].newVersion).toEqual('>= 0.5.0 < 1.5.0'); + }); + it('upgrades minor greater than less than equals ranges without pinning', () => { + config.pinVersions = false; + config.currentVersion = '>= 0.5.0 <= 0.8.0'; + const res = versions.determineUpgrades(qJson, config); + expect(res).toMatchSnapshot(); + expect(res[0].newVersion).toEqual('>= 0.5.0 <= 0.10.0'); + expect(res[1].newVersion).toEqual('>= 0.5.0 <= 1.5.0'); + }); + it('rejects reverse ordered less than greater than', () => { + config.pinVersions = false; + config.currentVersion = '<= 0.8.0 >= 0.5.0'; + const res = versions.determineUpgrades(qJson, config); + expect(res).toMatchSnapshot(); + }); it('supports > latest versions if configured', () => { config.respectLatest = false; config.currentVersion = '1.4.1';