From feaee22c20b982d9a0b86d38aaa75a17e3ff3386 Mon Sep 17 00:00:00 2001 From: Suneil Nyamathi Date: Thu, 1 Feb 2018 13:07:36 -0800 Subject: [PATCH] Handle prerelease version nuances (#5) --- src/semver-intersect.js | 30 +++++++++++++++++++++++----- tests/unit/semver-intersect.js | 36 ++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 5 deletions(-) diff --git a/src/semver-intersect.js b/src/semver-intersect.js index 12f09fa..e15997a 100644 --- a/src/semver-intersect.js +++ b/src/semver-intersect.js @@ -47,11 +47,26 @@ function createShorthand (range) { } function ensureCompatible(range, ...bounds) { - const { version } = parseRange(range); + const { prerelease, version } = parseRange(range); + bounds.forEach(bound => { - if (bound && !semver.satisfies(version, bound)) { - throw new Error(`Range ${range} is not compatible with ${bound}`); + if (!bound || semver.satisfies(version, bound)) { + return; + } + + if (prerelease) { + if (parseRange(bound).prerelease) { + // If both bounds are pre-release versions, either can satisfy the other + if (semver.satisfies(parseRange(bound).version, range)) { + return; + } + } else if (semver.satisfies(version, `${range} ${bound}`)) { + // If only our version is a pre-release version, don't fail on 1.0.0-a <2.0.0 + return; + } } + + throw new Error(`Range ${range} is not compatible with ${bound}`); }); } @@ -75,7 +90,11 @@ function intersect (...ranges) { ranges = expandRanges(...ranges); const bounds = ranges.reduce(({ lowerBound, upperBound }, range) => { - const { condition } = parseRange(range); + const { condition, prerelease } = parseRange(range); + + if (prerelease) { + ensureCompatible(range, lowerBound, upperBound); + } // Exact version number specified, must be compatible with both bounds if (condition === '=') { @@ -127,7 +146,8 @@ function mergeBounds (range, bound) { function parseRange (range) { const condition = regex.condition.exec(range)[1] || '='; const version = regex.version.exec(range)[1]; - return { condition, version }; + const prerelease = semver.prerelease(version); + return { condition, prerelease, version }; } function union (a, b) { diff --git a/tests/unit/semver-intersect.js b/tests/unit/semver-intersect.js index e791c0f..c5a28dc 100644 --- a/tests/unit/semver-intersect.js +++ b/tests/unit/semver-intersect.js @@ -138,6 +138,39 @@ describe('intersect', () => { const result = intersect('^4.0.0', '~4.3.0'); expect(result).to.equal('~4.3.0'); }); + it('should handle pre-release versions mixed with versions', () => { + const result = intersect('^1.0.0-alpha.3', '^1.2.0'); + expect(result).to.equal('^1.2.0'); + }); + it('should handle compatible pre-release versions', () => { + const result = intersect('^1.0.0-alpha.3', '^1.0.0-alpha.4'); + expect(result).to.equal('^1.0.0-alpha.4'); + }); + it('should handle compatible pre-release versions without a dot', () => { + const result = intersect('^0.14.0-beta2', '^0.14.0-beta3'); + expect(result).to.equal('^0.14.0-beta3'); + }); + it('should handle compatible pre-release versions with the same preid', () => { + const result = intersect('^0.14.0-beta', '^0.14.0-beta4'); + expect(result).to.equal('^0.14.0-beta4'); + }); + it('should handle incompatible pre-release versions (different patch version)', () => { + const call = intersect.bind(null, '^1.9.0-alpha', '^1.9.1-alpha'); + expect(call).to.throw('Range >=1.9.1-alpha is not compatible with >=1.9.0-alpha'); + }); + it('should handle compatible pre-release versions (preid lower in alphabetical order)', () => { + expect(intersect('^1.9.0-alpha', '^1.9.0-beta')).to.equal('^1.9.0-beta'); + expect(intersect('^1.9.0-beta', '^1.9.0-alpha')).to.equal('^1.9.0-beta'); + expect(intersect('^1.9.0-alpha.1', '^1.9.0-beta.2')).to.equal('^1.9.0-beta.2'); + }); + it('should handle incompatible pre-release versions (specific version)', () => { + expect(intersect.bind(null, '1.9.0-alpha.1', '^1.9.0-alpha.2')) + .to.throw('Range >=1.9.0-alpha.2 is not compatible with <=1.9.0-alpha.1'); + expect(intersect.bind(null, '1.9.0-alpha.1', '1.9.0-alpha.0')) + .to.throw('Range 1.9.0-alpha.0 is not compatible with >=1.9.0-alpha.1'); + expect(intersect.bind(null, '1.9.0-rc3', '^1.9.0-rc4')) + .to.throw('Range >=1.9.0-rc4 is not compatible with <=1.9.0-rc3'); + }); it('should return an exact version intersected with a range', () => { const result = intersect('1.5.16', '^1.0.0'); expect(result).to.equal('1.5.16'); @@ -173,14 +206,17 @@ describe('parseRange', () => { it('return the comparison condition and version', () => { expect(parseRange('<5.0.0')).to.deep.equal({ condition: '<', + prerelease: null, version: '5.0.0' }); expect(parseRange('>=4.0.0')).to.deep.equal({ condition: '>=', + prerelease: null, version: '4.0.0' }); expect(parseRange('3.0.0')).to.deep.equal({ condition: '=', + prerelease: null, version: '3.0.0' }); });