diff --git a/lib/manager/docker/package.js b/lib/manager/docker/package.js index 67d5f1f9022f5933e1402f936b806b2e1e1f283f..035ae0b885e0d1788a8b11fda9e4d4ac7202433b 100644 --- a/lib/manager/docker/package.js +++ b/lib/manager/docker/package.js @@ -1,4 +1,4 @@ -const { getMajor, isValidSemver } = require('../../versioning/semver'); +const { getMajor, isValid } = require('../../versioning/semver'); const dockerApi = require('../../datasource/docker'); const compareVersions = require('compare-versions'); @@ -57,7 +57,7 @@ async function getPackageUpdates(config) { if (currentTag) { const tagVersion = getVersion(currentTag); const tagSuffix = getSuffix(currentTag); - if (!isValidSemver(tagVersion)) { + if (!isValid(tagVersion)) { logger.info( { currentDepTag }, 'Docker tag is not valid semver - skipping' @@ -72,7 +72,7 @@ async function getPackageUpdates(config) { versionList = allTags .filter(tag => getSuffix(tag) === tagSuffix) .map(getVersion) - .filter(isValidSemver) + .filter(isValid) .filter( version => // All stable are allowed diff --git a/lib/manager/npm/lookup/filter.js b/lib/manager/npm/lookup/filter.js new file mode 100644 index 0000000000000000000000000000000000000000..2d3b17b10ac7a63aa03fe71d318506ecb92e2650 --- /dev/null +++ b/lib/manager/npm/lookup/filter.js @@ -0,0 +1,63 @@ +const { + getMajor, + isGreaterThan, + isStable, +} = require('../../../versioning/semver'); + +module.exports = { + filterVersions, + filterUnstable, + filterLatest, +}; + +function filterVersions(config, fromVersion, latestVersion, versions) { + const { ignoreUnstable, respectLatest } = config; + // Leave only versions greater than current + let filteredVersions = versions.filter(version => + isGreaterThan(version, fromVersion) + ); + filteredVersions = filterUnstable( + ignoreUnstable, + fromVersion, + filteredVersions + ); + filteredVersions = filterLatest( + respectLatest, + fromVersion, + latestVersion, + filteredVersions + ); + return filteredVersions; +} + +function filterUnstable(ignoreUnstable, fromVersion, versions) { + // Filter nothing out if we are not ignoring unstable + if (ignoreUnstable === false) { + return versions; + } + // Filter out all unstable if fromVersion is stable + if (isStable(fromVersion)) { + // Remove all unstable + return versions.filter(isStable); + } + // Allow unstable only in current major + return versions.filter( + version => isStable(version) || getMajor(version) === getMajor(fromVersion) + ); +} + +function filterLatest(respectLatest, fromVersion, latestVersion, versions) { + // No filtering if no latest + if (!latestVersion) { + return versions; + } + // No filtering if not respecting latest + if (respectLatest === false) { + return versions; + } + // No filtering if fromVersion is already past latest + if (isGreaterThan(fromVersion, latestVersion)) { + return versions; + } + return versions.filter(version => !isGreaterThan(version, latestVersion)); +} diff --git a/lib/manager/npm/lookup/index.js b/lib/manager/npm/lookup/index.js new file mode 100644 index 0000000000000000000000000000000000000000..2e25482e8baa4ae5860b61ee18562e48ffd427ff --- /dev/null +++ b/lib/manager/npm/lookup/index.js @@ -0,0 +1,151 @@ +const versioning = require('../../../versioning/semver'); +const moment = require('moment'); +const { getRollbackUpdate } = require('./rollback'); + +const { filterVersions } = require('./filter'); + +const { + getMajor, + getMinor, + isGreaterThan, + isRange, + matches, + maxSatisfyingVersion, + minSatisfyingVersion, + rangify, +} = versioning; + +module.exports = { + lookupUpdates, +}; + +function lookupUpdates(dependency, config) { + const { currentVersion, lockedVersion, rangeStrategy } = config; + const { latestVersion } = dependency; + const allVersions = Object.keys(dependency.versions); + if (allVersions.length === 0) { + const message = `No versions returned from registry for this package`; + logger.warn({ dependency }, message); + return [ + { + type: 'warning', + message, + }, + ]; + } + const allSatisfyingVersions = allVersions.filter(version => + matches(version, currentVersion) + ); + if (!allSatisfyingVersions.length) { + return getRollbackUpdate(config, allVersions); + } + const fromVersion = getFromVersion( + currentVersion, + rangeStrategy, + lockedVersion, + allVersions + ); + const updates = []; + if (isRange(currentVersion) && config.rangeStrategy === 'pin') { + updates.push({ + type: 'pin', + isPin: true, + newVersion: fromVersion, + newVersionMajor: getMajor(fromVersion), + unpublishable: false, + }); + } + const filteredVersions = filterVersions( + config, + fromVersion, + latestVersion, + allVersions + ); + if (!filteredVersions.length) { + return updates; + } + const buckets = {}; + for (const toVersion of filteredVersions) { + const update = { fromVersion, toVersion }; + update.newVersion = rangify(config, currentVersion, fromVersion, toVersion); + if (!update.newVersion || update.newVersion === currentVersion) { + continue; // eslint-disable-line no-continue + } + update.newVersionMajor = getMajor(toVersion); + update.newVersionMinor = getMinor(toVersion); + update.type = getType(config, fromVersion, toVersion); + if (isRange(update.newVersion)) { + update.isRange = true; + } + + // TODO: move unpublishable to npm-specific + const version = dependency.versions[toVersion]; + const elapsed = version ? moment().diff(moment(version.time), 'days') : 999; + update.unpublishable = elapsed > 0; + // end TODO + + const bucket = getBucket(config, update); + if (buckets[bucket]) { + if (isGreaterThan(update.toVersion, buckets[bucket].toVersion)) { + buckets[bucket] = update; + } + } else { + buckets[bucket] = update; + } + } + return updates.concat(Object.values(buckets)); +} + +function getType(config, fromVersion, toVersion) { + if (getMajor(toVersion) > getMajor(fromVersion)) { + return 'major'; + } + if (getMinor(toVersion) > getMinor(fromVersion)) { + return 'minor'; + } + if (config.separateMinorPatch) { + return 'patch'; + } + if (config.patch.automerge && !config.minor.automerge) { + return 'patch'; + } + return 'minor'; +} + +function getBucket(config, update) { + const { separateMajorMinor, separateMultipleMajor } = config; + const { type, newVersionMajor } = update; + if ( + !separateMajorMinor || + config.groupName || + config.major.automerge === true || + (config.automerge && config.major.automerge !== false) + ) { + return 'latest'; + } + if (separateMultipleMajor && type === 'major') { + return `major-${newVersionMajor}`; + } + return type; +} + +function getFromVersion( + currentVersion, + rangeStrategy, + lockedVersion, + allVersions +) { + if (!isRange(currentVersion)) { + return currentVersion; + } + logger.trace(`currentVersion ${currentVersion} is range`); + if (rangeStrategy === 'pin') { + return lockedVersion || maxSatisfyingVersion(allVersions, currentVersion); + } + if (rangeStrategy === 'bump') { + // Use the lowest version in the current range + return minSatisfyingVersion(allVersions, currentVersion); + } + // Use the highest version in the current range + return maxSatisfyingVersion(allVersions, currentVersion); +} diff --git a/lib/manager/npm/lookup/rollback.js b/lib/manager/npm/lookup/rollback.js new file mode 100644 index 0000000000000000000000000000000000000000..92724ed0d89ffab62e04543d3a6c7e6ae969e868 --- /dev/null +++ b/lib/manager/npm/lookup/rollback.js @@ -0,0 +1,46 @@ +const { + getMajor, + isLessThan, + rangify, + sortVersions, +} = require('../../../versioning/semver'); + +module.exports = { + getRollbackUpdate, +}; + +function getRollbackUpdate(config, versions) { + const { packageFile, depName, currentVersion } = config; + const lessThanVersions = versions.filter(version => + isLessThan(version, currentVersion) + ); + // istanbul ignore if + if (!lessThanVersions.length) { + logger.warn( + { packageFile, depName, currentVersion }, + 'Missing version has nothing to roll back to' + ); + return []; + } + logger.info( + { packageFile, depName, currentVersion }, + `Current version not found - rolling back` + ); + lessThanVersions.sort(sortVersions); + const toVersion = lessThanVersions.pop(); + let fromVersion; + const newVersion = rangify(config, currentVersion, fromVersion, toVersion); + return [ + { + type: 'rollback', + branchName: + '{{{branchPrefix}}}rollback-{{{depNameSanitized}}}-{{{newVersionMajor}}}.x', + commitMessageAction: 'Roll back', + isRollback: true, + newVersion, + newVersionMajor: getMajor(toVersion), + semanticCommitType: 'fix', + unpublishable: false, + }, + ]; +} diff --git a/lib/manager/npm/package.js b/lib/manager/npm/package.js index cfa17a378b18f1ce15c91701e851ba123a47a89a..f8a420226c2bab67ff41c84eb08916187d3255be 100644 --- a/lib/manager/npm/package.js +++ b/lib/manager/npm/package.js @@ -1,8 +1,8 @@ const npmApi = require('../../datasource/npm'); -const versions = require('./versions'); -const { isValidSemver } = require('../../versioning/semver'); +const lookup = require('./lookup'); +const { isValid } = require('../../versioning/semver'); const nodeManager = require('../_helpers/node/package'); -const { parseRange } = require('../../versioning/semver'); +const { parseRange } = require('semver-utils'); module.exports = { getRangeStrategy, @@ -60,7 +60,7 @@ async function getPackageUpdates(config) { ); return []; } - if (!isValidSemver(currentVersion)) { + if (!isValid(currentVersion)) { results.push({ depName, type: 'warning', @@ -76,7 +76,7 @@ async function getPackageUpdates(config) { ); const npmDep = await npmApi.getDependency(depName); if (npmDep) { - results = await versions.determineUpgrades(npmDep, { + results = lookup.lookupUpdates(npmDep, { ...config, rangeStrategy, }); diff --git a/lib/manager/npm/versions.js b/lib/manager/npm/versions.js deleted file mode 100644 index 7f40330b29453acf710a2d28250d5f6c234ca97b..0000000000000000000000000000000000000000 --- a/lib/manager/npm/versions.js +++ /dev/null @@ -1,470 +0,0 @@ -const _ = require('lodash'); -const { - getMajor, - getMinor, - getPatch, - isGreaterThan, - isRange, - isStable, - isUnstable, - isPinnedVersion, - matches, - maxSatisfyingVersion, - minSatisfyingVersion, - parseRange, - parseVersion, - stringifyRange, -} = require('../../versioning/semver'); -const moment = require('moment'); - -module.exports = { - determineUpgrades, - isPastLatest, -}; - -function determineUpgrades(npmDep, config) { - const dependency = npmDep.name; - logger.debug({ dependency }, `determineUpgrades()`); - logger.trace({ npmDep, config }); - const result = { - type: 'warning', - }; - const { lockedVersion, rangeStrategy, allowedVersions } = config; - const { versions } = npmDep; - if (!versions || Object.keys(versions).length === 0) { - result.message = `No versions returned from registry for this package`; - logger.warn({ dependency }, result.message); - return [result]; - } - let versionList = Object.keys(versions); - const allUpgrades = {}; - let { currentVersion } = config; - // filter out versions past latest - const currentIsPastLatest = isPastLatest( - npmDep, - minSatisfyingVersion(versionList, currentVersion) - ); - if (currentIsPastLatest) { - logger.debug({ name: npmDep.name, currentVersion }, 'currentIsPastLatest'); - } - versionList = versionList.filter( - version => - currentIsPastLatest || // if current is past latest then don't filter any - config.respectLatest === false || // if user has configured respectLatest to false - isPastLatest(npmDep, version) === false // if the version is less than or equal to latest - ); - let rangeOperator; - if (config.rangeStrategy === 'bump' && isRange(currentVersion)) { - logger.debug({ currentVersion }, 'bumping current range'); - const parsedRange = parseRange(currentVersion); - if (parsedRange && parsedRange.length === 1) { - const [range] = parsedRange; - if (range.major && range.minor && range.patch) { - if (range.operator === '^' || range.operator === '~') { - logger.debug('Applying in-range bump'); - currentVersion = `${range.major}.${range.minor}.${range.patch}`; - currentVersion += range.release ? `-${range.release}` : ''; - rangeOperator = range.operator; - } else { - logger.debug({ currentVersion }, 'Unsupported range type'); - } - } else { - logger.debug({ currentVersion }, 'Range is not fully specified'); - } - } else { - logger.debug({ currentVersion }, 'Skipping complex range'); - } - } - let fromVersion = currentVersion; - // Check for a current range and pin it - if (isRange(currentVersion)) { - let newVersion; - if ( - rangeStrategy === 'pin' && - lockedVersion && - isPinnedVersion(lockedVersion) - ) { - newVersion = lockedVersion; - } else { - // Pin ranges to their maximum satisfying version - logger.debug({ dependency }, 'currentVersion is range, not locked'); - const maxSatisfying = maxSatisfyingVersion(versionList, currentVersion); - if (!maxSatisfying) { - result.message = `No satisfying version found for existing dependency range "${currentVersion}"`; - logger.info( - { dependency, currentVersion }, - `Warning: ${result.message}` - ); - return [result]; - } - logger.debug({ dependency, maxSatisfying }); - newVersion = maxSatisfying; - } - - allUpgrades.pin = { - type: 'pin', - newVersion, - newVersionMajor: getMajor(newVersion), - }; - fromVersion = newVersion; - } else if (versionList.indexOf(currentVersion) === -1 && !rangeOperator) { - logger.debug({ dependency }, 'Cannot find currentVersion'); - try { - const rollbackVersion = maxSatisfyingVersion( - versionList, - `<${currentVersion}` - ); - allUpgrades.rollback = { - type: 'rollback', - newVersion: rollbackVersion, - newVersionMajor: getMajor(rollbackVersion), - semanticCommitType: 'fix', - commitMessageAction: 'Roll back', - branchName: - '{{{branchPrefix}}}rollback-{{{depNameSanitized}}}-{{{newVersionMajor}}}.x', - }; - } catch (err) /* istanbul ignore next */ { - logger.info( - { dependency, currentVersion }, - 'Warning: current version is missing from npm registry and cannot roll back' - ); - } - } - _(versionList) - // Filter out older versions as we can't upgrade to those - .filter(version => isGreaterThan(version, fromVersion)) - // fillter out non-allowed versions if preference is set - .reject(version => allowedVersions && !matches(version, allowedVersions)) - // Ignore unstable versions, unless the current version is unstable - .reject( - version => - config.ignoreUnstable && isStable(fromVersion) && isUnstable(version) - ) - // Do not jump to a new major unstable just because the current is unstable - .reject( - version => - config.ignoreUnstable && - isUnstable(version) && - getMajor(version) > getMajor(fromVersion) - ) - // Loop through all possible versions - .forEach(newVersion => { - // Group by major versions - const newVersionMajor = getMajor(newVersion); - const newVersionMinor = getMinor(newVersion); - const hasPatchOnlyAutomerge = - config.patch && - config.patch.automerge === true && - (config.minor && config.minor.automerge !== true); - let type; - if (newVersionMajor > getMajor(fromVersion)) { - type = 'major'; - } else if ( - newVersionMinor === getMinor(fromVersion) && - (config.separateMinorPatch || hasPatchOnlyAutomerge) - ) { - // Only use patch if configured to - type = 'patch'; - } else { - type = 'minor'; - } - let upgradeKey; - if ( - !config.separateMajorMinor || - config.groupName || - config.major.automerge === true - ) { - // If we're not separating releases then we use a common lookup key - upgradeKey = 'latest'; - } else if (!config.separateMultipleMajor && type === 'major') { - upgradeKey = 'major'; - } else if (type === 'patch') { - upgradeKey = `{{{newVersionMajor}}}.{{{newVersionMinor}}}`; - } else { - // Use major version as lookup key - upgradeKey = newVersionMajor; - } - // Save this, if it's a new major version or greater than the previous greatest - if ( - !allUpgrades[upgradeKey] || - isGreaterThan(newVersion, allUpgrades[upgradeKey].newVersion) - ) { - const toVersion = newVersion; - allUpgrades[upgradeKey] = { - type, - newVersion, - newVersionMajor, - newVersionMinor, - fromVersion, - toVersion, - }; - } - }); - // Return only the values - we don't need the keys anymore - let upgrades = Object.keys(allUpgrades).map(key => allUpgrades[key]); - for (const upgrade of upgrades) { - const version = versions[upgrade.newVersion]; - const elapsed = version ? moment().diff(moment(version.time), 'days') : 999; - upgrade.unpublishable = elapsed > 0; - } - - // Return now if array is empty, or we can keep pinned version upgrades - if ( - upgrades.length === 0 || - config.rangeStrategy === 'pin' || - !isRange(currentVersion) - ) { - return rangeOperator - ? upgrades.map(upgrade => ({ - ...upgrade, - newVersion: `${rangeOperator}${upgrade.newVersion}`, - isRange: true, - })) - : upgrades; - } - - logger.debug({ dependency }, 'User wants ranges - filtering out pins'); - upgrades = upgrades.filter(upgrade => upgrade.type !== 'pin'); - - // Return empty if all results were pins - if (!upgrades.length) { - logger.debug({ dependency }, 'No upgrades left - returning'); - return []; - } - - // Check if it's a range type we support - const semverParsed = parseRange(currentVersion); - // Check the "last" part, which is also the first and only if it's a simple semver - const [lastSemver] = semverParsed.slice(-1); - const secondLastSemver = semverParsed[semverParsed.length - 2]; - if (semverParsed.length > 1) { - if (lastSemver.operator === '<' || lastSemver.operator === '<=') { - logger.debug({ dependency }, 'Found less than range'); - } else if (secondLastSemver.operator === '||') { - logger.debug({ dependency }, 'Found an OR range'); - } else if (secondLastSemver.operator === '-') { - logger.info( - { dependency, currentVersion, upgrades, semverParsed }, - 'Found a hyphen range' - ); - } else { - // 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, upgrades, semverParsed }, - 'Semver warning: ' + result.message - ); - return [result]; - } - } - // Loop through all upgrades and convert to ranges - const rangedUpgrades = _(upgrades) - .map(upgrade => ({ ...upgrade, ...{ isRange: true } })) - .map(upgrade => { - const { major, minor } = parseVersion(upgrade.newVersion); - const canReplace = config.rangeStrategy !== 'widen'; - const forceReplace = config.rangeStrategy === 'replace'; - const canWiden = config.rangeStrategy !== 'replace'; - const forceWiden = config.rangeStrategy === 'widen'; - if ( - lastSemver.operator === '~' && - canReplace && - (semverParsed.length === 1 || forceReplace) - ) { - // Utilise that a.b is the same as ~a.b.0 - const minSatisfying = minSatisfyingVersion( - versionList, - `${major}.${minor}` - ); - // Add a tilde before that version number - return { ...upgrade, ...{ newVersion: `~${minSatisfying}` } }; - } else if ( - lastSemver.operator === '~' && - canWiden && - (semverParsed.length > 1 || forceWiden) - ) { - // Utilise that a.b is the same as ~a.b.0 - const minSatisfying = minSatisfyingVersion( - versionList, - `${major}.${minor}` - ); - // Add a tilde before that version number - const newVersion = `${currentVersion} || ~${minSatisfying}`; - return { - ...upgrade, - newVersion, - }; - } else if ( - lastSemver.operator === '^' && - canReplace && - (semverParsed.length === 1 || forceReplace) - ) { - let newVersion; - // Special case where major and minor are 0 - if (major === '0') { - if (minor === '0') { - newVersion = `^${upgrade.newVersion}`; - } else { - // If version is < 1, then semver treats ^ same as ~ - const newRange = `${major}.${minor}`; - const minSatisfying = minSatisfyingVersion(versionList, newRange); - // Add in the caret - newVersion = `^${minSatisfying}`; - } - } else if (major === `${getMajor(fromVersion)}`) { - newVersion = `^${upgrade.newVersion}`; - } else { - const newRange = `${major}`; - const minSatisfying = minSatisfyingVersion(versionList, newRange); - // Add in the caret - newVersion = `^${minSatisfying}`; - } - return { ...upgrade, newVersion }; - } 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 = minSatisfyingVersion(versionList, newRange); - // Add in the caret - const newVersion = `${currentVersion} || ^${minSatisfying}`; - return { - ...upgrade, - newVersion, - }; - } else if (lastSemver.operator === '<=') { - const minorZero = !lastSemver.minor || lastSemver.minor === '0'; - const patchZero = !lastSemver.patch || lastSemver.patch === '0'; - const newRange = [...semverParsed]; - if (minorZero && patchZero) { - logger.debug({ dependency }, 'Found a less than major'); - newRange[newRange.length - 1].major = String( - upgrade.newVersionMajor + 1 - ); - } else if (patchZero) { - logger.debug({ dependency }, '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({ dependency }, '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( - getPatch(upgrade.newVersion) - ); - } - let newVersion = stringifyRange(newRange); - newVersion = fixRange(newVersion, lastSemver, currentVersion); - return { ...upgrade, newVersion }; - } else if (lastSemver.operator === '<') { - const minorZero = !lastSemver.minor || lastSemver.minor === '0'; - const patchZero = !lastSemver.patch || lastSemver.patch === '0'; - const newRange = [...semverParsed]; - if (minorZero && patchZero) { - logger.debug({ dependency }, 'Found a less than major'); - newRange[newRange.length - 1].major = String( - upgrade.newVersionMajor + 1 - ); - } else if (patchZero) { - logger.debug({ dependency }, '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({ dependency }, '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( - getPatch(upgrade.newVersion) + 1 - ); - } - let newVersion = stringifyRange(newRange); - newVersion = fixRange(newVersion, lastSemver, currentVersion); - return { ...upgrade, newVersion }; - } else if (lastSemver.minor === undefined) { - // Example: 1 - const newRange = [...semverParsed]; - logger.debug({ dependency }, 'Found a standalone major'); - newRange[newRange.length - 1].major = String(upgrade.newVersionMajor); - let newVersion; - if (secondLastSemver && secondLastSemver.operator === '||') { - newVersion = `${currentVersion} || ${upgrade.newVersionMajor}`; - } else { - newVersion = stringifyRange(newRange); - // Fixes a bug with stringifyRange - newVersion = newVersion.replace(/\.0/g, ''); - } - return { ...upgrade, newVersion }; - } else if (lastSemver.minor === 'x') { - // Example: 1.x - const newRange = [...semverParsed]; - logger.debug({ dependency }, 'Found a .x'); - newRange[newRange.length - 1].major = String(upgrade.newVersionMajor); - let newVersion; - if (secondLastSemver && secondLastSemver.operator === '||') { - newVersion = `${currentVersion} || ${upgrade.newVersionMajor}.x`; - } else { - newVersion = stringifyRange(newRange); - // Fixes a bug with stringifyRange - newVersion = newVersion.replace(/\.0/g, ''); - } - return { ...upgrade, newVersion }; - } else if (lastSemver.patch === undefined) { - // Example: 1.2 - return { ...upgrade, ...{ newVersion: `${major}.${minor}` } }; - } else if (lastSemver.patch === 'x' && semverParsed.length === 1) { - // 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({ dependency }, 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]; - } - return rangedUpgrades; -} - -function fixRange(version, lastSemver, currentVersion) { - let newVersion = version; - if (!lastSemver.patch) { - newVersion = newVersion.replace(/\.0$/, ''); - } - if (!currentVersion.includes('< ')) { - newVersion = newVersion.replace(/< /g, '<'); - } - if (!currentVersion.includes('> ')) { - newVersion = newVersion.replace(/> /g, '>'); - } - if (!currentVersion.includes('>= ')) { - newVersion = newVersion.replace(/>= /g, '>='); - } - if (!currentVersion.includes('<= ')) { - newVersion = newVersion.replace(/<= /g, '<='); - } - return newVersion; -} - -function isPastLatest(npmDep, version) { - if (!version) { - return false; - } - if (npmDep['dist-tags'] && npmDep['dist-tags'].latest) { - return isGreaterThan(version, npmDep['dist-tags'].latest); - } - logger.warn(`No dist-tags.latest for ${npmDep.name}`); - return false; -} diff --git a/lib/util/package-rules.js b/lib/util/package-rules.js index 8d4e8312d3d81d2acdf5fcf716dcc7e5946814fd..647bef919c69a031f268b4edf79282c4a53ddb08 100644 --- a/lib/util/package-rules.js +++ b/lib/util/package-rules.js @@ -1,6 +1,6 @@ const minimatch = require('minimatch'); -const { intersectsSemver } = require('../versioning/semver'); +const { intersects } = require('../versioning/semver'); const { mergeChildConfig } = require('../config'); module.exports = { @@ -94,7 +94,7 @@ function applyPackageRules(inputConfig) { positiveMatch = positiveMatch || !isMatch; } if (matchCurrentVersion) { - const isMatch = intersectsSemver(currentVersion, matchCurrentVersion); + const isMatch = intersects(currentVersion, matchCurrentVersion); positiveMatch = positiveMatch || isMatch; negativeMatch = negativeMatch || !isMatch; } diff --git a/lib/versioning/semver/index.js b/lib/versioning/semver/index.js index 444e1020c8a3aeae3ad2c20b9bc1d46de751c4ad..0a3593f2d40fe3f3fda383b93ea73fba801a6ef3 100644 --- a/lib/versioning/semver/index.js +++ b/lib/versioning/semver/index.js @@ -1,22 +1,18 @@ const semver = require('semver'); const stable = require('semver-stable'); -const semverUtils = require('semver-utils'); +const { rangify } = require('./range'); const { is: isStable } = stable; -const isUnstable = input => !isStable(input); - -const { parseRange, parse: parseVersion, stringifyRange } = semverUtils; const { compare: sortVersions, - gt: isGreaterThan, - intersects: intersectsSemver, + intersects, maxSatisfying: maxSatisfyingVersion, minSatisfying: minSatisfyingVersion, minor: getMinor, - patch: getPatch, satisfies: matches, valid: isPinnedVersion, + validRange, } = semver; const padRange = range => range + '.0'.repeat(3 - range.split('.').length); @@ -26,27 +22,30 @@ const getMajor = input => { return semver.major(version); }; -const isRange = input => isValidSemver(input) && !isPinnedVersion(input); +const isRange = input => isValid(input) && !isPinnedVersion(input); // If this is left as an alias, inputs like "17.04.0" throw errors -const isValidSemver = input => semver.validRange(input); +const isValid = input => validRange(input); + +const isLessThan = (version, base) => + isPinnedVersion(base) ? semver.lt(version, base) : semver.ltr(version, base); + +const isGreaterThan = (version, base) => + isPinnedVersion(base) ? semver.gt(version, base) : semver.gtr(version, base); module.exports = { getMajor, getMinor, - getPatch, - intersectsSemver, + intersects, isGreaterThan, + isLessThan, + isPinnedVersion, isRange, isStable, - isUnstable, - isValidSemver, - isPinnedVersion, + isValid, matches, maxSatisfyingVersion, minSatisfyingVersion, - parseRange, - parseVersion, + rangify, sortVersions, - stringifyRange, }; diff --git a/lib/versioning/semver/range.js b/lib/versioning/semver/range.js new file mode 100644 index 0000000000000000000000000000000000000000..e55ade1c2437f38425b1253cb09a810489f384dc --- /dev/null +++ b/lib/versioning/semver/range.js @@ -0,0 +1,117 @@ +const { inc: increment, major, minor, valid } = require('semver'); +const { parseRange } = require('semver-utils'); + +module.exports = { + rangify, +}; + +function rangify(config, currentVersion, fromVersion, toVersion) { + const { rangeStrategy } = config; + if (rangeStrategy === 'pin' || valid(currentVersion)) { + return toVersion; + } + const parsedRange = parseRange(currentVersion); + const element = parsedRange[parsedRange.length - 1]; + if (rangeStrategy === 'widen') { + const newVersion = rangify( + { ...config, rangeStrategy: 'replace' }, + currentVersion, + fromVersion, + toVersion + ); + if (element.operator && element.operator.startsWith('<')) { + // TODO fix this + const splitCurrent = currentVersion.split(element.operator); + splitCurrent.pop(); + return splitCurrent.join(element.operator) + newVersion; + } + if (parsedRange.length > 1) { + const previousElement = parsedRange[parsedRange.length - 2]; + if (previousElement.operator === '-') { + const splitCurrent = currentVersion.split('-'); + splitCurrent.pop(); + return splitCurrent.join('-') + '- ' + newVersion; + } + if (element.operator && element.operator.startsWith('>')) { + logger.warn(`Complex ranges ending in greater than are not supported`); + return null; + } + } + return `${currentVersion} || ${newVersion}`; + } + // console.log(range); + // Simple range + if (config.rangeStrategy === 'bump') { + if (parsedRange.length === 1) { + if (element.operator === '^') { + return `^${toVersion}`; + } + if (element.operator === '~') { + return `~${toVersion}`; + } + } + logger.warn( + 'Unsupported range type for rangeStrategy=bump: ' + currentVersion + ); + return null; + } + if (element.operator === '^') { + if (fromVersion && major(toVersion) === major(fromVersion)) { + if (major(toVersion) === 0) { + if (minor(toVersion) === 0) { + return `^${toVersion}`; + } + return `^${major(toVersion)}.${minor(toVersion)}.0`; + } + return `^${toVersion}`; + } + return `^${major(toVersion)}.0.0`; + } + if (element.operator === '~') { + return `~${major(toVersion)}.${minor(toVersion)}.0`; + } + if (element.operator === '<=') { + let res; + if (element.patch) { + res = `<=${toVersion}`; + } else if (element.minor) { + res = `<=${major(toVersion)}.${minor(toVersion)}`; + } else { + res = `<=${major(toVersion)}`; + } + if (currentVersion.includes('<= ')) { + res = res.replace('<=', '<= '); + } + return res; + } + if (element.operator === '<') { + let res; + if (currentVersion.endsWith('.0.0')) { + const newMajor = major(toVersion) + 1; + res = `<${newMajor}.0.0`; + } else if (element.patch) { + res = `<${increment(toVersion, 'patch')}`; + } else if (element.minor) { + res = `<${major(toVersion)}.${minor(toVersion) + 1}`; + } else { + res = `<${major(toVersion) + 1}`; + } + if (currentVersion.includes('< ')) { + res = res.replace('<', '< '); + } + return res; + } + if (!element.operator) { + if (element.minor) { + if (element.minor === 'x') { + return `${major(toVersion)}.x`; + } + if (element.patch === 'x') { + return `${major(toVersion)}.${minor(toVersion)}.x`; + } + return `${major(toVersion)}.${minor(toVersion)}`; + } + return `${major(toVersion)}`; + } + return toVersion; +} diff --git a/test/manager/npm/__snapshots__/versions.spec.js.snap b/test/manager/npm/__snapshots__/lookup.spec.js.snap similarity index 64% rename from test/manager/npm/__snapshots__/versions.spec.js.snap rename to test/manager/npm/__snapshots__/lookup.spec.js.snap index bcf9a956ecd575c1996133cded28826d59bdd065..dbff4230ba8f5d387b7059b8dc21aade96a97cf4 100644 --- a/test/manager/npm/__snapshots__/versions.spec.js.snap +++ b/test/manager/npm/__snapshots__/lookup.spec.js.snap @@ -1,8 +1,9 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`manager/npm/versions .determineUpgrades(npmDep, config) disables major release separation (major) 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() disables major release separation (major) 1`] = ` Array [ Object { + "isPin": true, "newVersion": "0.4.4", "newVersionMajor": 0, "type": "pin", @@ -20,7 +21,7 @@ Array [ ] `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) disables major release separation (minor) 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() disables major release separation (minor) 1`] = ` Array [ Object { "fromVersion": "1.0.0", @@ -34,7 +35,7 @@ Array [ ] `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) handles prerelease jumps 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() handles prerelease jumps 1`] = ` Array [ Object { "fromVersion": "2.9.0-rc", @@ -49,9 +50,10 @@ Array [ ] `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) ignores pinning for ranges when other upgrade exists 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() ignores pinning for ranges when other upgrade exists 1`] = ` Array [ Object { + "isPin": true, "newVersion": "0.9.7", "newVersionMajor": 0, "type": "pin", @@ -69,9 +71,10 @@ Array [ ] `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) pins minor ranged versions 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() pins minor ranged versions 1`] = ` Array [ Object { + "isPin": true, "newVersion": "1.4.1", "newVersionMajor": 1, "type": "pin", @@ -80,13 +83,13 @@ Array [ ] `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) rejects complex range in-range updates 1`] = `Array []`; +exports[`manager/npm/lookup .lookupUpdates() rejects complex range in-range updates 1`] = `Array []`; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) rejects in-range unsupported operator 1`] = `Array []`; +exports[`manager/npm/lookup .lookupUpdates() rejects in-range unsupported operator 1`] = `Array []`; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) rejects non-fully specified in-range updates 1`] = `Array []`; +exports[`manager/npm/lookup .lookupUpdates() rejects non-fully specified in-range updates 1`] = `Array []`; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) rejects non-range in-range updates 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() rejects non-range in-range updates 1`] = ` Array [ Object { "fromVersion": "1.0.0", @@ -100,16 +103,9 @@ Array [ ] `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) rejects reverse ordered less than greater than 1`] = ` -Array [ - Object { - "message": "Complex semver ranges such as \\"<= 0.8.0 >= 0.5.0\\" are not yet supported so will be skipped", - "type": "warning", - }, -] -`; +exports[`manager/npm/lookup .lookupUpdates() rejects reverse ordered less than greater than 1`] = `Array []`; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) replaces major complex ranged versions if configured 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() replaces major complex ranged versions if configured 1`] = ` Array [ Object { "fromVersion": "2.7.0", @@ -124,7 +120,7 @@ Array [ ] `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) replaces minor complex ranged versions if configured 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() replaces minor complex ranged versions if configured 1`] = ` Array [ Object { "fromVersion": "1.3.0", @@ -139,22 +135,22 @@ Array [ ] `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) return warning if empty versions 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() return warning if empty versions 1`] = ` Object { "message": "No versions returned from registry for this package", "type": "warning", } `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) return warning if null versions 1`] = ` -Object { - "message": "No versions returned from registry for this package", - "type": "warning", -} -`; - -exports[`manager/npm/versions .determineUpgrades(npmDep, config) returns both updates if automerging minor 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() returns both updates if automerging minor 1`] = ` Array [ + Object { + "isPin": true, + "newVersion": "0.4.4", + "newVersionMajor": 0, + "type": "pin", + "unpublishable": false, + }, Object { "fromVersion": "0.4.4", "newVersion": "0.9.7", @@ -164,12 +160,6 @@ Array [ "type": "minor", "unpublishable": false, }, - Object { - "newVersion": "0.4.4", - "newVersionMajor": 0, - "type": "pin", - "unpublishable": false, - }, Object { "fromVersion": "0.4.4", "newVersion": "1.4.1", @@ -182,7 +172,7 @@ Array [ ] `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) returns minor update if automerging both patch and minor 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() returns minor update if automerging both patch and minor 1`] = ` Array [ Object { "fromVersion": "0.9.0", @@ -205,7 +195,7 @@ Array [ ] `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) returns minor update if separate patches not configured 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() returns minor update if separate patches not configured 1`] = ` Array [ Object { "fromVersion": "0.9.0", @@ -228,9 +218,24 @@ Array [ ] `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) returns only one update if automerging major 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() returns only one update if automerging 1`] = ` Array [ Object { + "fromVersion": "0.4.0", + "newVersion": "1.4.1", + "newVersionMajor": 1, + "newVersionMinor": 4, + "toVersion": "1.4.1", + "type": "major", + "unpublishable": false, + }, +] +`; + +exports[`manager/npm/lookup .lookupUpdates() returns only one update if automerging major 1`] = ` +Array [ + Object { + "isPin": true, "newVersion": "0.4.4", "newVersionMajor": 0, "type": "pin", @@ -248,9 +253,10 @@ Array [ ] `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) returns only one update if grouping 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() returns only one update if grouping 1`] = ` Array [ Object { + "isPin": true, "newVersion": "0.4.4", "newVersionMajor": 0, "type": "pin", @@ -268,24 +274,24 @@ Array [ ] `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) returns patch minor and major 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() returns patch minor and major 1`] = ` Array [ Object { "fromVersion": "0.8.0", - "newVersion": "0.9.7", + "newVersion": "0.8.12", "newVersionMajor": 0, - "newVersionMinor": 9, - "toVersion": "0.9.7", - "type": "minor", + "newVersionMinor": 8, + "toVersion": "0.8.12", + "type": "patch", "unpublishable": false, }, Object { "fromVersion": "0.8.0", - "newVersion": "0.8.12", + "newVersion": "0.9.7", "newVersionMajor": 0, - "newVersionMinor": 8, - "toVersion": "0.8.12", - "type": "patch", + "newVersionMinor": 9, + "toVersion": "0.9.7", + "type": "minor", "unpublishable": false, }, Object { @@ -300,7 +306,7 @@ Array [ ] `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) returns patch update if automerging patch 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() returns patch update if automerging patch 1`] = ` Array [ Object { "fromVersion": "0.9.0", @@ -323,7 +329,7 @@ Array [ ] `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) returns patch update if separateMinorPatch 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() returns patch update if separateMinorPatch 1`] = ` Array [ Object { "fromVersion": "0.9.0", @@ -346,16 +352,37 @@ Array [ ] `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) returns warning if range not found 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() returns rollback if range not found 1`] = ` Array [ Object { - "message": "No satisfying version found for existing dependency range \\"^8.4.0\\"", - "type": "warning", + "branchName": "{{{branchPrefix}}}rollback-{{{depNameSanitized}}}-{{{newVersionMajor}}}.x", + "commitMessageAction": "Roll back", + "isRollback": true, + "newVersion": "^2.0.0", + "newVersionMajor": 2, + "semanticCommitType": "fix", + "type": "rollback", + "unpublishable": false, }, ] `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) should allow unstable versions if the current version is unstable 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() should allow unstable versions if ignoreUnstable is false 1`] = ` +Array [ + Object { + "branchName": "{{{branchPrefix}}}rollback-{{{depNameSanitized}}}-{{{newVersionMajor}}}.x", + "commitMessageAction": "Roll back", + "isRollback": true, + "newVersion": "1.0.0-beta", + "newVersionMajor": 1, + "semanticCommitType": "fix", + "type": "rollback", + "unpublishable": false, + }, +] +`; + +exports[`manager/npm/lookup .lookupUpdates() should allow unstable versions if the current version is unstable 1`] = ` Array [ Object { "fromVersion": "1.0.0-beta", @@ -369,10 +396,25 @@ Array [ ] `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) should downgrade from missing versions 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() should allow unstable versions if the ignoreUnstable=false 1`] = ` +Array [ + Object { + "fromVersion": "1.0.0", + "newVersion": "1.1.0-beta", + "newVersionMajor": 1, + "newVersionMinor": 1, + "toVersion": "1.1.0-beta", + "type": "minor", + "unpublishable": false, + }, +] +`; + +exports[`manager/npm/lookup .lookupUpdates() should downgrade from missing versions 1`] = ` Object { "branchName": "{{{branchPrefix}}}rollback-{{{depNameSanitized}}}-{{{newVersionMajor}}}.x", "commitMessageAction": "Roll back", + "isRollback": true, "newVersion": "1.16.0", "newVersionMajor": 1, "semanticCommitType": "fix", @@ -381,7 +423,7 @@ Object { } `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) should treat zero zero caret ranges as pinned 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() should treat zero zero caret ranges as pinned 1`] = ` Array [ Object { "fromVersion": "0.0.34", @@ -396,7 +438,7 @@ Array [ ] `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) supports > latest versions if configured 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() supports > latest versions if configured 1`] = ` Array [ Object { "fromVersion": "1.4.1", @@ -410,7 +452,7 @@ Array [ ] `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) supports complex major hyphen ranges 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() supports complex major hyphen ranges 1`] = ` Array [ Object { "fromVersion": "2.7.0", @@ -425,7 +467,7 @@ Array [ ] `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) supports complex major ranges 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() supports complex major ranges 1`] = ` Array [ Object { "fromVersion": "2.7.0", @@ -440,7 +482,7 @@ Array [ ] `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) supports complex ranges 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() supports complex ranges 1`] = ` Object { "fromVersion": "0.8.12", "isRange": true, @@ -453,7 +495,7 @@ Object { } `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) supports complex tilde ranges 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() supports complex tilde ranges 1`] = ` Array [ Object { "fromVersion": "1.3.0", @@ -468,18 +510,22 @@ Array [ ] `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) supports future versions if already future 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() supports in-range caret updates 1`] = ` Array [ Object { - "newVersion": "2.0.3", - "newVersionMajor": 2, - "type": "pin", + "fromVersion": "1.0.0", + "isRange": true, + "newVersion": "^1.4.1", + "newVersionMajor": 1, + "newVersionMinor": 4, + "toVersion": "1.4.1", + "type": "minor", "unpublishable": false, }, ] `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) supports in-range updates 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() supports in-range tilde updates 1`] = ` Array [ Object { "fromVersion": "1.0.0", @@ -494,8 +540,15 @@ Array [ ] `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) supports minor and major upgrades for ranged versions 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() supports minor and major upgrades for ranged versions 1`] = ` Array [ + Object { + "isPin": true, + "newVersion": "0.4.4", + "newVersionMajor": 0, + "type": "pin", + "unpublishable": false, + }, Object { "fromVersion": "0.4.4", "newVersion": "0.9.7", @@ -505,12 +558,6 @@ Array [ "type": "minor", "unpublishable": false, }, - Object { - "newVersion": "0.4.4", - "newVersionMajor": 0, - "type": "pin", - "unpublishable": false, - }, Object { "fromVersion": "0.4.4", "newVersion": "1.4.1", @@ -523,8 +570,15 @@ Array [ ] `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) supports minor and major upgrades for tilde ranges 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() supports minor and major upgrades for tilde ranges 1`] = ` Array [ + Object { + "isPin": true, + "newVersion": "0.4.4", + "newVersionMajor": 0, + "type": "pin", + "unpublishable": false, + }, Object { "fromVersion": "0.4.4", "newVersion": "0.9.7", @@ -534,12 +588,6 @@ Array [ "type": "minor", "unpublishable": false, }, - Object { - "newVersion": "0.4.4", - "newVersionMajor": 0, - "type": "pin", - "unpublishable": false, - }, Object { "fromVersion": "0.4.4", "newVersion": "1.4.1", @@ -552,7 +600,22 @@ Array [ ] `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) upgrades .x major ranges without pinning 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() upgrades .x complex minor ranges without pinning 1`] = ` +Array [ + Object { + "fromVersion": "1.3.0", + "isRange": true, + "newVersion": "1.2.x - 1.4.x", + "newVersionMajor": 1, + "newVersionMinor": 4, + "toVersion": "1.4.1", + "type": "minor", + "unpublishable": false, + }, +] +`; + +exports[`manager/npm/lookup .lookupUpdates() upgrades .x major ranges without pinning 1`] = ` Array [ Object { "fromVersion": "0.9.7", @@ -567,8 +630,15 @@ Array [ ] `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) upgrades .x minor ranges 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() upgrades .x minor ranges 1`] = ` Array [ + Object { + "isPin": true, + "newVersion": "1.3.0", + "newVersionMajor": 1, + "type": "pin", + "unpublishable": false, + }, Object { "fromVersion": "1.3.0", "newVersion": "1.4.1", @@ -578,21 +648,45 @@ Array [ "type": "minor", "unpublishable": false, }, +] +`; + +exports[`manager/npm/lookup .lookupUpdates() upgrades .x minor ranges without pinning 1`] = ` +Array [ Object { - "newVersion": "1.3.0", + "fromVersion": "1.3.0", + "isRange": true, + "newVersion": "1.4.x", "newVersionMajor": 1, - "type": "pin", + "newVersionMinor": 4, + "toVersion": "1.4.1", + "type": "minor", "unpublishable": false, }, ] `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) upgrades .x minor ranges without pinning 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() upgrades less than equal major ranges 1`] = ` +Array [ + Object { + "fromVersion": "1.4.1", + "isRange": true, + "newVersion": "<= 2", + "newVersionMajor": 2, + "newVersionMinor": 0, + "toVersion": "2.0.3", + "type": "major", + "unpublishable": false, + }, +] +`; + +exports[`manager/npm/lookup .lookupUpdates() upgrades less than equal minor ranges 1`] = ` Array [ Object { "fromVersion": "1.3.0", "isRange": true, - "newVersion": "1.4.x", + "newVersion": "<= 1.4", "newVersionMajor": 1, "newVersionMinor": 4, "toVersion": "1.4.1", @@ -602,7 +696,7 @@ Array [ ] `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) upgrades less than equal ranges without pinning 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() upgrades less than equal ranges without pinning 1`] = ` Array [ Object { "fromVersion": "0.7.2", @@ -627,7 +721,22 @@ Array [ ] `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) upgrades less than ranges without pinning 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() upgrades less than major ranges 1`] = ` +Array [ + Object { + "fromVersion": "0.9.7", + "isRange": true, + "newVersion": "< 2", + "newVersionMajor": 1, + "newVersionMinor": 4, + "toVersion": "1.4.1", + "type": "major", + "unpublishable": false, + }, +] +`; + +exports[`manager/npm/lookup .lookupUpdates() upgrades less than ranges without pinning 1`] = ` Array [ Object { "fromVersion": "0.7.1", @@ -652,7 +761,7 @@ Array [ ] `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) upgrades major greater than less than ranges without pinning 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() upgrades major greater than less than ranges without pinning 1`] = ` Array [ Object { "fromVersion": "0.9.7", @@ -667,12 +776,12 @@ Array [ ] `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) upgrades major less than equal ranges 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() upgrades major less than equal ranges 1`] = ` Array [ Object { "fromVersion": "1.0.0", "isRange": true, - "newVersion": "<= 2.0.0", + "newVersion": "<= 1.4.1", "newVersionMajor": 1, "newVersionMinor": 4, "toVersion": "1.4.1", @@ -682,7 +791,7 @@ Array [ ] `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) upgrades major less than ranges without pinning 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() upgrades major less than ranges without pinning 1`] = ` Array [ Object { "fromVersion": "0.9.7", @@ -697,12 +806,12 @@ Array [ ] `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) upgrades minor greater than less than equals ranges without pinning 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() upgrades minor greater than less than equals ranges without pinning 1`] = ` Array [ Object { "fromVersion": "0.8.0", "isRange": true, - "newVersion": ">= 0.5.0 <= 0.10.0", + "newVersion": ">= 0.5.0 <= 0.9.7", "newVersionMajor": 0, "newVersionMinor": 9, "toVersion": "0.9.7", @@ -712,7 +821,7 @@ Array [ Object { "fromVersion": "0.8.0", "isRange": true, - "newVersion": ">= 0.5.0 <= 1.5.0", + "newVersion": ">= 0.5.0 <= 1.4.1", "newVersionMajor": 1, "newVersionMinor": 4, "toVersion": "1.4.1", @@ -722,7 +831,7 @@ Array [ ] `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) upgrades minor greater than less than ranges without pinning 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() upgrades minor greater than less than ranges without pinning 1`] = ` Array [ Object { "fromVersion": "0.7.2", @@ -747,8 +856,15 @@ Array [ ] `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) upgrades minor ranged versions 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() upgrades minor ranged versions 1`] = ` Array [ + Object { + "isPin": true, + "newVersion": "1.0.1", + "newVersionMajor": 1, + "type": "pin", + "unpublishable": false, + }, Object { "fromVersion": "1.0.1", "newVersion": "1.4.1", @@ -758,16 +874,10 @@ Array [ "type": "minor", "unpublishable": false, }, - Object { - "newVersion": "1.0.1", - "newVersionMajor": 1, - "type": "pin", - "unpublishable": false, - }, ] `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) upgrades multiple caret ranges without pinning 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() upgrades multiple caret ranges without pinning 1`] = ` Array [ Object { "fromVersion": "0.7.2", @@ -792,7 +902,7 @@ Array [ ] `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) upgrades multiple tilde ranges without pinning 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() upgrades multiple tilde ranges without pinning 1`] = ` Array [ Object { "fromVersion": "0.7.2", @@ -817,7 +927,7 @@ Array [ ] `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) upgrades shorthand major ranges without pinning 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() upgrades shorthand major ranges without pinning 1`] = ` Array [ Object { "fromVersion": "0.9.7", @@ -832,7 +942,7 @@ Array [ ] `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) upgrades shorthand minor ranges without pinning 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() upgrades shorthand minor ranges without pinning 1`] = ` Array [ Object { "fromVersion": "1.3.0", @@ -847,8 +957,15 @@ Array [ ] `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) upgrades tilde ranges 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() upgrades tilde ranges 1`] = ` Array [ + Object { + "isPin": true, + "newVersion": "1.3.0", + "newVersionMajor": 1, + "type": "pin", + "unpublishable": false, + }, Object { "fromVersion": "1.3.0", "newVersion": "1.4.1", @@ -858,16 +975,10 @@ Array [ "type": "minor", "unpublishable": false, }, - Object { - "newVersion": "1.3.0", - "newVersionMajor": 1, - "type": "pin", - "unpublishable": false, - }, ] `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) upgrades tilde ranges without pinning 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() upgrades tilde ranges without pinning 1`] = ` Array [ Object { "fromVersion": "1.3.0", @@ -882,8 +993,15 @@ Array [ ] `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) uses the locked version for pinning 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() uses the locked version for pinning 1`] = ` Array [ + Object { + "isPin": true, + "newVersion": "1.0.0", + "newVersionMajor": 1, + "type": "pin", + "unpublishable": false, + }, Object { "fromVersion": "1.0.0", "newVersion": "1.4.1", @@ -893,16 +1011,10 @@ Array [ "type": "minor", "unpublishable": false, }, - Object { - "newVersion": "1.0.0", - "newVersionMajor": 1, - "type": "pin", - "unpublishable": false, - }, ] `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) widens .x OR ranges 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() widens .x OR ranges 1`] = ` Array [ Object { "fromVersion": "2.7.0", @@ -917,7 +1029,7 @@ Array [ ] `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) widens major ranged versions if configured 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() widens major ranged versions if configured 1`] = ` Array [ Object { "fromVersion": "2.7.0", @@ -932,7 +1044,7 @@ Array [ ] `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) widens minor ranged versions if configured 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() widens minor ranged versions if configured 1`] = ` Array [ Object { "fromVersion": "1.3.0", @@ -947,7 +1059,7 @@ Array [ ] `; -exports[`manager/npm/versions .determineUpgrades(npmDep, config) widens stanndalone major OR ranges 1`] = ` +exports[`manager/npm/lookup .lookupUpdates() widens stanndalone major OR ranges 1`] = ` Array [ Object { "fromVersion": "2.7.0", diff --git a/test/manager/npm/versions.spec.js b/test/manager/npm/lookup.spec.js similarity index 60% rename from test/manager/npm/versions.spec.js rename to test/manager/npm/lookup.spec.js index 5da47fcedff4e50dd59b1bcd42940eea6eaf94bc..19f01519aa6e6e93fd442eb6bec31e67729e23d2 100644 --- a/test/manager/npm/versions.spec.js +++ b/test/manager/npm/lookup.spec.js @@ -1,4 +1,4 @@ -const versions = require('../../../lib/manager/npm/versions'); +const lookup = require('../../../lib/manager/npm/lookup'); const qJson = require('../../_fixtures/npm/01.json'); const helmetJson = require('../../_fixtures/npm/02.json'); const coffeelintJson = require('../../_fixtures/npm/coffeelint.json'); @@ -6,60 +6,66 @@ const webpackJson = require('../../_fixtures/npm/webpack.json'); const nextJson = require('../../_fixtures/npm/next.json'); const typescriptJson = require('../../_fixtures/npm/typescript.json'); +qJson.latestVersion = '1.4.1'; + let config; -describe('manager/npm/versions', () => { +describe('manager/npm/lookup', () => { beforeEach(() => { config = { ...require('../../../lib/config/defaults').getConfig() }; - config.rangeStrategy = 'pin'; + config.rangeStrategy = 'replace'; }); - describe('.determineUpgrades(npmDep, config)', () => { - it('return warning if null versions', () => { - config.currentVersion = '1.0.0'; - const testDep = { - name: 'q', - }; - const res = versions.determineUpgrades(testDep, config); - expect(res).toHaveLength(1); - expect(res[0]).toMatchSnapshot(); - }); + describe('.lookupUpdates()', () => { it('return warning if empty versions', () => { const testDep = { name: 'q', versions: [], }; config.currentVersion = '1.0.0'; - const res = versions.determineUpgrades(testDep, config); + const res = lookup.lookupUpdates(testDep, config); expect(res).toHaveLength(1); expect(res[0]).toMatchSnapshot(); }); - it('returns warning if range not found', () => { + it('returns rollback if range not found', () => { config.currentVersion = '^8.4.0'; - expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + expect(lookup.lookupUpdates(qJson, config)).toMatchSnapshot(); }); it('supports minor and major upgrades for tilde ranges', () => { config.currentVersion = '^0.4.0'; - expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + config.rangeStrategy = 'pin'; + expect(lookup.lookupUpdates(qJson, config)).toMatchSnapshot(); }); it('returns only one update if grouping', () => { config.groupName = 'somegroup'; config.currentVersion = '^0.4.0'; - expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + config.rangeStrategy = 'pin'; + expect(lookup.lookupUpdates(qJson, config)).toMatchSnapshot(); + }); + it('returns only one update if automerging', () => { + config.automerge = true; + config.currentVersion = '0.4.0'; + config.rangeStrategy = 'pin'; + const res = lookup.lookupUpdates(qJson, config); + expect(res).toMatchSnapshot(); + expect(res).toHaveLength(1); }); it('returns only one update if automerging major', () => { config.major = { automerge: true }; config.currentVersion = '^0.4.0'; - expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + config.rangeStrategy = 'pin'; + expect(lookup.lookupUpdates(qJson, config)).toMatchSnapshot(); }); it('returns both updates if automerging minor', () => { config.minor = { automerge: true }; config.currentVersion = '^0.4.0'; - expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + config.rangeStrategy = 'pin'; + expect(lookup.lookupUpdates(qJson, config)).toMatchSnapshot(); }); it('returns minor update if separate patches not configured', () => { config.currentVersion = '0.9.0'; - const res = versions.determineUpgrades(qJson, config); + config.rangeStrategy = 'pin'; + const res = lookup.lookupUpdates(qJson, config); expect(res).toMatchSnapshot(); expect(res.length).toBe(2); expect(res[0].type).not.toEqual('patch'); @@ -70,7 +76,8 @@ describe('manager/npm/versions', () => { automerge: true, }; config.currentVersion = '0.9.0'; - const res = versions.determineUpgrades(qJson, config); + config.rangeStrategy = 'pin'; + const res = lookup.lookupUpdates(qJson, config); expect(res).toMatchSnapshot(); expect(res[0].type).toEqual('patch'); }); @@ -82,229 +89,258 @@ describe('manager/npm/versions', () => { automerge: true, }; config.currentVersion = '0.9.0'; - const res = versions.determineUpgrades(qJson, config); + config.rangeStrategy = 'pin'; + const res = lookup.lookupUpdates(qJson, config); expect(res).toMatchSnapshot(); expect(res[0].type).toEqual('minor'); }); it('returns patch update if separateMinorPatch', () => { config.separateMinorPatch = true; config.currentVersion = '0.9.0'; - expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + config.rangeStrategy = 'pin'; + expect(lookup.lookupUpdates(qJson, config)).toMatchSnapshot(); }); it('returns patch minor and major', () => { config.separateMinorPatch = true; config.currentVersion = '0.8.0'; - const res = versions.determineUpgrades(qJson, config); + config.rangeStrategy = 'pin'; + const res = lookup.lookupUpdates(qJson, config); expect(res).toHaveLength(3); expect(res).toMatchSnapshot(); }); it('disables major release separation (major)', () => { config.separateMajorMinor = false; config.currentVersion = '^0.4.0'; - expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + config.rangeStrategy = 'pin'; + expect(lookup.lookupUpdates(qJson, config)).toMatchSnapshot(); }); it('disables major release separation (minor)', () => { config.separateMajorMinor = false; config.currentVersion = '1.0.0'; - expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + config.rangeStrategy = 'pin'; + expect(lookup.lookupUpdates(qJson, config)).toMatchSnapshot(); }); it('supports minor and major upgrades for ranged versions', () => { config.currentVersion = '~0.4.0'; - expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + config.rangeStrategy = 'pin'; + expect(lookup.lookupUpdates(qJson, config)).toMatchSnapshot(); }); it('ignores pinning for ranges when other upgrade exists', () => { config.currentVersion = '~0.9.0'; - expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + config.rangeStrategy = 'pin'; + expect(lookup.lookupUpdates(qJson, config)).toMatchSnapshot(); }); it('upgrades minor ranged versions', () => { config.currentVersion = '~1.0.0'; - expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + config.rangeStrategy = 'pin'; + expect(lookup.lookupUpdates(qJson, config)).toMatchSnapshot(); }); it('widens minor ranged versions if configured', () => { config.currentVersion = '~1.3.0'; config.rangeStrategy = 'widen'; - expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + expect(lookup.lookupUpdates(qJson, config)).toMatchSnapshot(); }); it('replaces minor complex ranged versions if configured', () => { config.currentVersion = '~1.2.0 || ~1.3.0'; config.rangeStrategy = 'replace'; - expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + expect(lookup.lookupUpdates(qJson, config)).toMatchSnapshot(); }); it('widens major ranged versions if configured', () => { config.currentVersion = '^2.0.0'; config.rangeStrategy = 'widen'; - expect(versions.determineUpgrades(webpackJson, config)).toMatchSnapshot(); + expect(lookup.lookupUpdates(webpackJson, config)).toMatchSnapshot(); }); it('replaces major complex ranged versions if configured', () => { config.currentVersion = '^1.0.0 || ^2.0.0'; config.rangeStrategy = 'replace'; - expect(versions.determineUpgrades(webpackJson, config)).toMatchSnapshot(); + expect(lookup.lookupUpdates(webpackJson, config)).toMatchSnapshot(); }); it('pins minor ranged versions', () => { config.currentVersion = '^1.0.0'; - expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + config.rangeStrategy = 'pin'; + expect(lookup.lookupUpdates(qJson, config)).toMatchSnapshot(); }); it('uses the locked version for pinning', () => { config.currentVersion = '^1.0.0'; config.lockedVersion = '1.0.0'; - expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + config.rangeStrategy = 'pin'; + expect(lookup.lookupUpdates(qJson, config)).toMatchSnapshot(); }); it('ignores minor ranged versions when not pinning', () => { config.rangeStrategy = 'replace'; config.currentVersion = '^1.0.0'; - expect(versions.determineUpgrades(qJson, config)).toHaveLength(0); + expect(lookup.lookupUpdates(qJson, config)).toHaveLength(0); }); it('upgrades tilde ranges', () => { + config.rangeStrategy = 'pin'; config.currentVersion = '~1.3.0'; - expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + expect(lookup.lookupUpdates(qJson, config)).toMatchSnapshot(); }); it('upgrades .x minor ranges', () => { config.currentVersion = '1.3.x'; - expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + config.rangeStrategy = 'pin'; + expect(lookup.lookupUpdates(qJson, config)).toMatchSnapshot(); }); it('upgrades tilde ranges without pinning', () => { config.rangeStrategy = 'replace'; config.currentVersion = '~1.3.0'; - expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + expect(lookup.lookupUpdates(qJson, config)).toMatchSnapshot(); }); it('upgrades .x major ranges without pinning', () => { config.rangeStrategy = 'replace'; config.currentVersion = '0.x'; - expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + expect(lookup.lookupUpdates(qJson, config)).toMatchSnapshot(); }); it('upgrades .x minor ranges without pinning', () => { config.rangeStrategy = 'replace'; config.currentVersion = '1.3.x'; - expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + expect(lookup.lookupUpdates(qJson, config)).toMatchSnapshot(); + }); + it('upgrades .x complex minor ranges without pinning', () => { + config.rangeStrategy = 'widen'; + config.currentVersion = '1.2.x - 1.3.x'; + expect(lookup.lookupUpdates(qJson, config)).toMatchSnapshot(); }); it('upgrades shorthand major ranges without pinning', () => { config.rangeStrategy = 'replace'; config.currentVersion = '0'; - expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + expect(lookup.lookupUpdates(qJson, config)).toMatchSnapshot(); }); it('upgrades shorthand minor ranges without pinning', () => { config.rangeStrategy = 'replace'; config.currentVersion = '1.3'; - expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + expect(lookup.lookupUpdates(qJson, config)).toMatchSnapshot(); }); it('upgrades multiple tilde ranges without pinning', () => { config.rangeStrategy = 'replace'; config.currentVersion = '~0.7.0'; - expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + expect(lookup.lookupUpdates(qJson, config)).toMatchSnapshot(); }); it('upgrades multiple caret ranges without pinning', () => { config.rangeStrategy = 'replace'; config.currentVersion = '^0.7.0'; - expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + expect(lookup.lookupUpdates(qJson, config)).toMatchSnapshot(); }); it('supports complex ranges', () => { config.rangeStrategy = 'widen'; config.currentVersion = '^0.7.0 || ^0.8.0'; - const res = versions.determineUpgrades(qJson, config); + const res = lookup.lookupUpdates(qJson, config); expect(res).toHaveLength(2); expect(res[0]).toMatchSnapshot(); }); it('supports complex major ranges', () => { config.rangeStrategy = 'widen'; config.currentVersion = '^1.0.0 || ^2.0.0'; - const res = versions.determineUpgrades(webpackJson, config); + const res = lookup.lookupUpdates(webpackJson, config); expect(res).toMatchSnapshot(); }); it('supports complex major hyphen ranges', () => { config.rangeStrategy = 'widen'; config.currentVersion = '1.x - 2.x'; - const res = versions.determineUpgrades(webpackJson, config); + const res = lookup.lookupUpdates(webpackJson, config); expect(res).toMatchSnapshot(); }); it('widens .x OR ranges', () => { config.rangeStrategy = 'widen'; config.currentVersion = '1.x || 2.x'; - const res = versions.determineUpgrades(webpackJson, config); + const res = lookup.lookupUpdates(webpackJson, config); expect(res).toMatchSnapshot(); }); it('widens stanndalone major OR ranges', () => { config.rangeStrategy = 'widen'; config.currentVersion = '1 || 2'; - const res = versions.determineUpgrades(webpackJson, config); + const res = lookup.lookupUpdates(webpackJson, config); expect(res).toMatchSnapshot(); }); it('supports complex tilde ranges', () => { config.rangeStrategy = 'widen'; config.currentVersion = '~1.2.0 || ~1.3.0'; - const res = versions.determineUpgrades(qJson, config); + const res = lookup.lookupUpdates(qJson, config); expect(res).toMatchSnapshot(); }); it('returns nothing for greater than ranges', () => { config.rangeStrategy = 'replace'; config.currentVersion = '>= 0.7.0'; - expect(versions.determineUpgrades(qJson, config)).toHaveLength(0); + expect(lookup.lookupUpdates(qJson, config)).toHaveLength(0); }); it('upgrades less than equal ranges without pinning', () => { config.rangeStrategy = 'replace'; config.currentVersion = '<= 0.7.2'; - expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + expect(lookup.lookupUpdates(qJson, config)).toMatchSnapshot(); }); it('upgrades less than ranges without pinning', () => { config.rangeStrategy = 'replace'; config.currentVersion = '< 0.7.2'; - expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + expect(lookup.lookupUpdates(qJson, config)).toMatchSnapshot(); + }); + it('upgrades less than major ranges', () => { + config.rangeStrategy = 'replace'; + config.currentVersion = '< 1'; + expect(lookup.lookupUpdates(qJson, config)).toMatchSnapshot(); + }); + it('upgrades less than equal minor ranges', () => { + config.rangeStrategy = 'replace'; + config.currentVersion = '<= 1.3'; + expect(lookup.lookupUpdates(qJson, config)).toMatchSnapshot(); + }); + it('upgrades less than equal major ranges', () => { + config.rangeStrategy = 'replace'; + config.respectLatest = false; + config.currentVersion = '<= 1'; + expect(lookup.lookupUpdates(qJson, config)).toMatchSnapshot(); }); it('upgrades major less than equal ranges', () => { config.rangeStrategy = 'replace'; config.currentVersion = '<= 1.0.0'; - const res = versions.determineUpgrades(qJson, config); + const res = lookup.lookupUpdates(qJson, config); expect(res).toMatchSnapshot(); - expect(res[0].newVersion).toEqual('<= 2.0.0'); + expect(res[0].newVersion).toEqual('<= 1.4.1'); }); it('upgrades major less than ranges without pinning', () => { config.rangeStrategy = 'replace'; config.currentVersion = '< 1.0.0'; - const res = versions.determineUpgrades(qJson, config); + const res = lookup.lookupUpdates(qJson, config); expect(res).toMatchSnapshot(); expect(res[0].newVersion).toEqual('< 2.0.0'); }); it('upgrades major greater than less than ranges without pinning', () => { - config.rangeStrategy = 'replace'; + config.rangeStrategy = 'widen'; config.currentVersion = '>= 0.5.0 < 1.0.0'; - const res = versions.determineUpgrades(qJson, config); + const res = lookup.lookupUpdates(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.rangeStrategy = 'replace'; + config.rangeStrategy = 'widen'; config.currentVersion = '>= 0.5.0 <0.8'; - const res = versions.determineUpgrades(qJson, config); + const res = lookup.lookupUpdates(qJson, config); expect(res).toMatchSnapshot(); expect(res[0].newVersion).toEqual('>= 0.5.0 <0.10'); expect(res[1].newVersion).toEqual('>= 0.5.0 <1.5'); }); it('upgrades minor greater than less than equals ranges without pinning', () => { - config.rangeStrategy = 'replace'; + config.rangeStrategy = 'widen'; config.currentVersion = '>= 0.5.0 <= 0.8.0'; - const res = versions.determineUpgrades(qJson, config); + const res = lookup.lookupUpdates(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'); + expect(res[0].newVersion).toEqual('>= 0.5.0 <= 0.9.7'); + expect(res[1].newVersion).toEqual('>= 0.5.0 <= 1.4.1'); }); it('rejects reverse ordered less than greater than', () => { - config.rangeStrategy = 'replace'; + config.rangeStrategy = 'widen'; config.currentVersion = '<= 0.8.0 >= 0.5.0'; - const res = versions.determineUpgrades(qJson, config); + const res = lookup.lookupUpdates(qJson, config); expect(res).toMatchSnapshot(); }); it('supports > latest versions if configured', () => { config.respectLatest = false; config.currentVersion = '1.4.1'; - expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); - }); - it('supports future versions if already future', () => { - config.currentVersion = '^2.0.0'; - expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + expect(lookup.lookupUpdates(qJson, config)).toMatchSnapshot(); }); it('should ignore unstable versions if the current version is stable', () => { config.currentVersion = '1.0.0'; - versions - .determineUpgrades( + lookup + .lookupUpdates( { name: 'amazing-package', versions: { @@ -316,10 +352,43 @@ describe('manager/npm/versions', () => { ) .should.eql([]); }); + it('should allow unstable versions if the ignoreUnstable=false', () => { + config.currentVersion = '1.0.0'; + config.ignoreUnstable = false; + expect( + lookup.lookupUpdates( + { + name: 'amazing-package', + versions: { + '1.0.0-beta': {}, + '1.0.0': {}, + '1.1.0-beta': {}, + }, + }, + config + ) + ).toMatchSnapshot(); + }); it('should allow unstable versions if the current version is unstable', () => { config.currentVersion = '1.0.0-beta'; expect( - versions.determineUpgrades( + lookup.lookupUpdates( + { + name: 'amazing-package', + versions: { + '1.0.0-beta': {}, + '1.1.0-beta': {}, + }, + }, + config + ) + ).toMatchSnapshot(); + }); + it('should allow unstable versions if ignoreUnstable is false', () => { + config.currentVersion = '1.0.0'; + config.ignoreUnstable = false; + expect( + lookup.lookupUpdates( { name: 'amazing-package', versions: { @@ -334,77 +403,73 @@ describe('manager/npm/versions', () => { it('should treat zero zero tilde ranges as 0.0.x', () => { config.rangeStrategy = 'replace'; config.currentVersion = '~0.0.34'; - expect(versions.determineUpgrades(helmetJson, config)).toEqual([]); + expect(lookup.lookupUpdates(helmetJson, config)).toEqual([]); }); it('should treat zero zero caret ranges as pinned', () => { config.rangeStrategy = 'replace'; config.currentVersion = '^0.0.34'; - expect(versions.determineUpgrades(helmetJson, config)).toMatchSnapshot(); + expect(lookup.lookupUpdates(helmetJson, config)).toMatchSnapshot(); }); it('should downgrade from missing versions', () => { config.currentVersion = '1.16.1'; - const res = versions.determineUpgrades(coffeelintJson, config); + const res = lookup.lookupUpdates(coffeelintJson, config); expect(res).toHaveLength(1); expect(res[0]).toMatchSnapshot(); }); it('should upgrade to only one major', () => { config.currentVersion = '1.0.0'; - const res = versions.determineUpgrades(webpackJson, config); + const res = lookup.lookupUpdates(webpackJson, config); expect(res).toHaveLength(2); }); it('should upgrade to two majors', () => { config.currentVersion = '1.0.0'; config.separateMultipleMajor = true; - const res = versions.determineUpgrades(webpackJson, config); + const res = lookup.lookupUpdates(webpackJson, config); expect(res).toHaveLength(3); }); it('does not jump major unstable', () => { config.currentVersion = '^4.4.0-canary.3'; config.rangeStrategy = 'replace'; - const res = versions.determineUpgrades(nextJson, config); + const res = lookup.lookupUpdates(nextJson, config); expect(res).toHaveLength(0); }); it('handles prerelease jumps', () => { config.currentVersion = '^2.9.0-rc'; config.rangeStrategy = 'replace'; - const res = versions.determineUpgrades(typescriptJson, config); + const res = lookup.lookupUpdates(typescriptJson, config); expect(res).toMatchSnapshot(); }); - it('supports in-range updates', () => { + it('supports in-range caret updates', () => { + config.rangeStrategy = 'bump'; + config.currentVersion = '^1.0.0'; + expect(lookup.lookupUpdates(qJson, config)).toMatchSnapshot(); + }); + it('supports in-range tilde updates', () => { config.rangeStrategy = 'bump'; config.currentVersion = '~1.0.0'; - expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + expect(lookup.lookupUpdates(qJson, config)).toMatchSnapshot(); }); it('rejects in-range unsupported operator', () => { config.rangeStrategy = 'bump'; config.currentVersion = '>=1.0.0'; - expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + expect(lookup.lookupUpdates(qJson, config)).toMatchSnapshot(); }); it('rejects non-fully specified in-range updates', () => { config.rangeStrategy = 'bump'; config.currentVersion = '1.x'; - expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + expect(lookup.lookupUpdates(qJson, config)).toMatchSnapshot(); }); it('rejects complex range in-range updates', () => { config.rangeStrategy = 'bump'; config.currentVersion = '^0.9.0 || ^1.0.0'; - expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + expect(lookup.lookupUpdates(qJson, config)).toMatchSnapshot(); }); it('rejects non-range in-range updates', () => { + config.depName = 'q'; + config.packageFile = 'package.json'; config.rangeStrategy = 'bump'; config.currentVersion = '1.0.0'; - expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); - }); - }); - describe('.isPastLatest(dep, version)', () => { - it('should return false for less than', () => { - versions.isPastLatest(qJson, '1.0.0').should.eql(false); - }); - it('should return false for equal', () => { - versions.isPastLatest(qJson, '1.4.1').should.eql(false); - }); - it('should return true for greater than', () => { - versions.isPastLatest(qJson, '2.0.3').should.eql(true); + expect(lookup.lookupUpdates(qJson, config)).toMatchSnapshot(); }); }); }); diff --git a/test/manager/npm/package.spec.js b/test/manager/npm/package.spec.js index 80241a5b685e6a4c0282c5deb359c30df97fc5da..b3b660a284cfbf9b7b562ec5272e3c24ffa361d0 100644 --- a/test/manager/npm/package.spec.js +++ b/test/manager/npm/package.spec.js @@ -1,5 +1,5 @@ const npmApi = require('../../../lib/datasource/npm'); -const versions = require('../../../lib/manager/npm/versions'); +const lookup = require('../../../lib/manager/npm/lookup'); const npm = require('../../../lib/manager/npm/package'); const defaultConfig = require('../../../lib/config/defaults').getConfig(); @@ -84,7 +84,7 @@ describe('lib/manager/npm/package', () => { }); it('returns warning if warning found', async () => { npmApi.getDependency.mockReturnValueOnce({}); - versions.determineUpgrades = jest.fn(() => [ + lookup.lookupUpdates = jest.fn(() => [ { type: 'warning', message: 'bad version', @@ -95,7 +95,7 @@ describe('lib/manager/npm/package', () => { }); it('returns array if upgrades found', async () => { npmApi.getDependency.mockReturnValueOnce({ repositoryUrl: 'some-url' }); - versions.determineUpgrades = jest.fn(() => [{}]); + lookup.lookupUpdates = jest.fn(() => [{}]); const res = await npm.getPackageUpdates(config); expect(res).toHaveLength(1); expect(Object.keys(res[0])).toMatchSnapshot(); @@ -104,7 +104,7 @@ describe('lib/manager/npm/package', () => { it('sets repositoryUrl for @types', async () => { config.depName = '@types/some-dep'; npmApi.getDependency.mockReturnValueOnce({}); - versions.determineUpgrades = jest.fn(() => [{}]); + lookup.lookupUpdates = jest.fn(() => [{}]); const res = await npm.getPackageUpdates(config); expect(res[0].repositoryUrl).toEqual( 'https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/some-dep' diff --git a/test/util/semver.spec.js b/test/util/semver.spec.js index 814b0cb23cd6406965e910302f2b5a44df0ab28c..31aa5babc45a9d193d7909bac1ecb72500f89930 100644 --- a/test/util/semver.spec.js +++ b/test/util/semver.spec.js @@ -1,28 +1,28 @@ const semver = require('../../lib/versioning/semver'); -describe('.isValidSemver(input)', () => { +describe('.isValid(input)', () => { it('should return null for irregular versions', () => { - expect(!!semver.isValidSemver('17.04.0')).toBe(false); + expect(!!semver.isValid('17.04.0')).toBe(false); }); it('should support simple semver', () => { - expect(!!semver.isValidSemver('1.2.3')).toBe(true); + expect(!!semver.isValid('1.2.3')).toBe(true); }); it('should support semver with dash', () => { - expect(!!semver.isValidSemver('1.2.3-foo')).toBe(true); + expect(!!semver.isValid('1.2.3-foo')).toBe(true); }); it('should reject semver without dash', () => { - expect(!!semver.isValidSemver('1.2.3foo')).toBe(false); + expect(!!semver.isValid('1.2.3foo')).toBe(false); }); it('should support ranges', () => { - expect(!!semver.isValidSemver('~1.2.3')).toBe(true); - expect(!!semver.isValidSemver('^1.2.3')).toBe(true); - expect(!!semver.isValidSemver('>1.2.3')).toBe(true); + expect(!!semver.isValid('~1.2.3')).toBe(true); + expect(!!semver.isValid('^1.2.3')).toBe(true); + expect(!!semver.isValid('>1.2.3')).toBe(true); }); it('should reject github repositories', () => { - expect(!!semver.isValidSemver('renovateapp/renovate')).toBe(false); - expect(!!semver.isValidSemver('renovateapp/renovate#master')).toBe(false); + expect(!!semver.isValid('renovateapp/renovate')).toBe(false); + expect(!!semver.isValid('renovateapp/renovate#master')).toBe(false); expect( - !!semver.isValidSemver('https://github.com/renovateapp/renovate.git') + !!semver.isValid('https://github.com/renovateapp/renovate.git') ).toBe(false); }); });