diff --git a/lib/download/npm.js b/lib/download/npm.js index e0e0a43b..9f67eb42 100644 --- a/lib/download/npm.js +++ b/lib/download/npm.js @@ -10,7 +10,6 @@ const url = require('url'); const zlib = require('zlib'); const destroy = require('destroy'); const utility = require('utility'); -const semver = require('semver'); const urlresolve = require('url').resolve; const chalk = require('chalk'); const uuid = require('uuid'); @@ -67,26 +66,13 @@ function* resolve(pkg, options) { return options.cache[cacheKey]; } - let realPkgVersion; let spec = pkg.spec; if (spec === '*') { spec = 'latest'; } - const distTags = packageMeta['dist-tags']; - // try tag first - realPkgVersion = distTags[spec]; - if (!realPkgVersion) { - const version = semver.valid(spec); - const range = semver.validRange(spec); - if (semver.satisfies(distTags.latest, spec)) { - realPkgVersion = distTags.latest; - } else if (version) { - realPkgVersion = version; - } else if (range) { - realPkgVersion = semver.maxSatisfying(packageMeta.allVersions, range); - } - } + const distTags = packageMeta['dist-tags']; + let realPkgVersion = utils.findMaxSatisfyingVersion(spec, distTags, packageMeta.allVersions); if (!realPkgVersion) { throw new Error(`[${pkg.displayName}] Can\'t find package ${pkg.name}@${pkg.rawSpec}`); diff --git a/lib/utils.js b/lib/utils.js index 7f641485..73dc9c98 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -16,6 +16,7 @@ const homedir = require('node-homedir'); const fse = require('co-fs-extra'); const destroy = require('destroy'); const normalizeData = require('normalize-package-data'); +const semver = require('semver'); const config = require('./config'); const get = require('./get'); @@ -200,6 +201,38 @@ exports.getMaxRange = function(spec) { } }; +exports.findMaxSatisfyingVersion = function(spec, distTags, allVersions) { + // try tag first + let realPkgVersion = distTags[spec]; + + if (!realPkgVersion) { + const version = semver.valid(spec); + const range = semver.validRange(spec); + if (semver.satisfies(distTags.latest, spec)) { + realPkgVersion = distTags.latest; + } else if (version) { + // use the valid version + realPkgVersion = version; + } else if (range) { + realPkgVersion = semver.maxSatisfying(allVersions, range); + if (realPkgVersion) { + // try to use latest-{major} tag version on range + // ^1.0.1 =range=> get 1.0.3 in (1.0.2, 1.0.3), but latest-1 tag is 1.0.2 + // finnaly we should use 1.0.2 on ^1.0.1 + const major = semver.major(realPkgVersion); + if (major) { + const latestMajorVersion = distTags[`latest-${major}`]; + if (latestMajorVersion && semver.satisfies(latestMajorVersion, spec)) { + realPkgVersion = latestMajorVersion; + } + } + } + } + } + + return realPkgVersion; +}; + exports.getPackageStorePath = function(storeDir, pkg) { // name => _name@1.0.0@name // @scope/name => _@scope_name@1.0.0@scope/name diff --git a/test/utils.test.js b/test/utils.test.js index 4e1f5aab..1dd387a2 100644 --- a/test/utils.test.js +++ b/test/utils.test.js @@ -31,4 +31,75 @@ describe('test/utils.test.js', () => { assert(!utils.matchPlatform('win32', [ '!win32', 'win32' ])); }); }); + + describe('findMaxSatisfyingVersion()', () => { + it('should use vaild version itself', () => { + assert(utils.findMaxSatisfyingVersion('1.0.2', { + latest: '2.0.0', + }, [ + '1.0.1', + '1.0.2', + '1.0.3', + '2.0.0', + ]) === '1.0.2'); + }); + + it('should return undefined when no version match', () => { + assert(utils.findMaxSatisfyingVersion('>= 2.0.1 < 3.0.0', { + latest: '2.0.0', + }, [ + '1.0.1', + '1.0.2', + '1.0.3', + '2.0.0', + ]) === null); + }); + + it('should use max range version', () => { + assert(utils.findMaxSatisfyingVersion('>= 1.0.1 < 2.0.0', { + latest: '2.0.0', + }, [ + '1.0.1', + '1.0.2', + '1.0.3', + '2.0.0', + ]) === '1.0.3'); + }); + + it('should return latest version', () => { + assert(utils.findMaxSatisfyingVersion('latest', { + latest: '2.0.0', + 'latest-1': '1.0.2', + }, [ + '1.0.1', + '1.0.2', + '1.0.3', + '2.0.0', + ]) === '2.0.0'); + }); + + it('should support latest version first', () => { + assert(utils.findMaxSatisfyingVersion('>= 1.0.1 < 2.0.0', { + latest: '1.0.1', + 'latest-1': '1.0.2', + }, [ + '1.0.1', + '1.0.2', + '1.0.3', + '2.0.0', + ]) === '1.0.1'); + }); + + it('should support latest-{major} version', () => { + assert(utils.findMaxSatisfyingVersion('>= 1.0.1 < 2.0.0', { + latest: '2.0.0', + 'latest-1': '1.0.2', + }, [ + '1.0.1', + '1.0.2', + '1.0.3', + '2.0.0', + ]) === '1.0.2'); + }); + }); });