diff --git a/README.md b/README.md index 7e1961d4..b5e35ff0 100644 --- a/README.md +++ b/README.md @@ -16,12 +16,12 @@ As a command-line utility: $ semver -h - Usage: semver [ [...]] [-r | -i | -d ] + Usage: semver [ [...]] [-r | -i | --preid | -l | -rv] Test if version(s) satisfy the supplied range(s), and sort them. Multiple versions or ranges may be supplied, unless increment - or decrement options are specified. In that case, only a single - version may be used, and it is incremented by the specified level + option is specified. In that case, only a single version may + be used, and it is incremented by the specified level Program exits successfully if any valid version satisfies all supplied ranges, and prints all satisfying versions. @@ -101,6 +101,30 @@ the user is indicating that they are aware of the risk. However, it is still not appropriate to assume that they have opted into taking a similar risk on the *next* set of prerelease versions. +#### Prerelease Identifiers + +The method `.inc` takes an additional `identifier` string argument that +will append the value of the string as a prerelease identifier: + +```javascript +> semver.inc('1.2.3', 'pre', 'beta') +'1.2.4-beta.0' +``` + +command-line example: + +```shell +$ semver 1.2.3 -i prerelease --preid beta +1.2.4-beta.0 +``` + +Which then can be used to increment further: + +```shell +$ semver 1.2.4-beta.0 -i prerelease +1.2.4-beta.1 +``` + ### Advanced Range Syntax Advanced range syntax desugars to primitive comparators in @@ -161,8 +185,6 @@ comparator. Allows minor-level changes if not. `1.2.4-beta.2` would not, because it is a prerelease of a different `[major, minor, patch]` tuple. -Note: this is the same as the `~>` operator in rubygems. - #### Caret Ranges `^1.2.3` `^0.2.5` `^0.0.4` Allows changes that do not modify the left-most non-zero digit in the @@ -226,6 +248,9 @@ strings that they parse. same as `prepatch`. It increments the patch version, then makes a prerelease. If the input version is already a prerelease it simply increments it. +* `major(v)`: Return the major version number. +* `minor(v)`: Return the minor version number. +* `patch(v)`: Return the patch version number. ### Comparison @@ -245,6 +270,9 @@ strings that they parse. `v2` is greater. Sorts in ascending order if passed to `Array.sort()`. * `rcompare(v1, v2)`: The reverse of compare. Sorts an array of versions in descending order when passed to `Array.sort()`. +* `diff(v1, v2)`: Returns difference between two versions by the release type + (`major`, `premajor`, `minor`, `preminor`, `patch`, `prepatch`, or `prerelease`), + or null if the versions are the same. ### Ranges diff --git a/bin/semver b/bin/semver index 41c148fb..c5f2e857 100755 --- a/bin/semver +++ b/bin/semver @@ -12,6 +12,7 @@ var argv = process.argv.slice(2) , inc = null , version = require("../package.json").version , loose = false + , identifier = undefined , semver = require("../semver") , reverse = false @@ -47,6 +48,9 @@ function main () { break } break + case "--preid": + identifier = argv.shift() + break case "-r": case "--range": range.push(argv.shift()) break @@ -88,7 +92,7 @@ function success () { }).map(function (v) { return semver.clean(v, loose) }).map(function (v) { - return inc ? semver.inc(v, inc, loose) : v + return inc ? semver.inc(v, inc, loose, identifier) : v }).forEach(function (v,i,_) { console.log(v) }) } @@ -111,6 +115,10 @@ function help () { ," prepatch, or prerelease. Default level is 'patch'." ," Only one version may be specified." ,"" + ,"--preid " + ," Identifier to be used to prefix premajor, preminor," + ," prepatch or prerelease version increments." + ,"" ,"-l --loose" ," Interpret versions and ranges loosely" ,"" diff --git a/package.json b/package.json index 9a1ca584..549b233f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "semver", - "version": "4.0.3", + "version": "4.3.2", "description": "The semantic version parser used by npm.", "main": "semver.js", "browser": "semver.browser.js", @@ -14,7 +14,7 @@ "uglify-js": "~2.3.6" }, "license": "BSD", - "repository": "git://github.com/isaacs/node-semver.git", + "repository": "git://github.com/npm/node-semver.git", "bin": { "semver": "./bin/semver" } diff --git a/semver.js b/semver.js index b0da846a..d265b568 100644 --- a/semver.js +++ b/semver.js @@ -20,6 +20,9 @@ if (typeof module === 'object' && module.exports === exports) // Not necessarily the package version of this code. exports.SEMVER_SPEC_VERSION = '2.0.0'; +var MAX_LENGTH = 256; +var MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991; + // The actual regexps go on exports.re var re = exports.re = []; var src = exports.src = []; @@ -233,8 +236,18 @@ for (var i = 0; i < R; i++) { exports.parse = parse; function parse(version, loose) { + if (version.length > MAX_LENGTH) + return null; + var r = loose ? re[LOOSE] : re[FULL]; - return (r.test(version)) ? new SemVer(version, loose) : null; + if (!r.test(version)) + return null; + + try { + return new SemVer(version, loose); + } catch (er) { + return null; + } } exports.valid = valid; @@ -262,6 +275,9 @@ function SemVer(version, loose) { throw new TypeError('Invalid Version: ' + version); } + if (version.length > MAX_LENGTH) + throw new TypeError('version is longer than ' + MAX_LENGTH + ' characters') + if (!(this instanceof SemVer)) return new SemVer(version, loose); @@ -279,6 +295,15 @@ function SemVer(version, loose) { this.minor = +m[2]; this.patch = +m[3]; + if (this.major > MAX_SAFE_INTEGER || this.major < 0) + throw new TypeError('Invalid major version') + + if (this.minor > MAX_SAFE_INTEGER || this.minor < 0) + throw new TypeError('Invalid minor version') + + if (this.patch > MAX_SAFE_INTEGER || this.patch < 0) + throw new TypeError('Invalid patch version') + // numberify any prerelease numeric ids if (!m[4]) this.prerelease = []; @@ -355,35 +380,35 @@ SemVer.prototype.comparePre = function(other) { // preminor will bump the version up to the next minor release, and immediately // down to pre-release. premajor and prepatch work the same way. -SemVer.prototype.inc = function(release) { +SemVer.prototype.inc = function(release, identifier) { switch (release) { case 'premajor': this.prerelease.length = 0; this.patch = 0; this.minor = 0; this.major++; - this.inc('pre'); + this.inc('pre', identifier); break; case 'preminor': this.prerelease.length = 0; this.patch = 0; this.minor++; - this.inc('pre'); + this.inc('pre', identifier); break; case 'prepatch': // If this is already a prerelease, it will bump to the next version // drop any prereleases that might already exist, since they are not // relevant at this point. this.prerelease.length = 0; - this.inc('patch'); - this.inc('pre'); + this.inc('patch', identifier); + this.inc('pre', identifier); break; // If the input is a non-prerelease version, this acts the same as // prepatch. case 'prerelease': if (this.prerelease.length === 0) - this.inc('patch'); - this.inc('pre'); + this.inc('patch', identifier); + this.inc('pre', identifier); break; case 'major': @@ -417,7 +442,7 @@ SemVer.prototype.inc = function(release) { this.prerelease = []; break; // This probably shouldn't be used publicly. - // 1.0.0 "pre" would become 1.0.0 which is the wrong direction. + // 1.0.0 "pre" would become 1.0.0-0 which is the wrong direction. case 'pre': if (this.prerelease.length === 0) this.prerelease = [0]; @@ -432,6 +457,15 @@ SemVer.prototype.inc = function(release) { if (i === -1) // didn't increment anything this.prerelease.push(0); } + if (identifier) { + // 1.2.0-beta.1 bumps to 1.2.0-beta.2, + // 1.2.0-beta.fooblz or 1.2.0-beta bumps to 1.2.0-beta.0 + if (this.prerelease[0] === identifier) { + if (isNaN(this.prerelease[1])) + this.prerelease = [identifier, 0]; + } else + this.prerelease = [identifier, 0]; + } break; default: @@ -442,14 +476,46 @@ SemVer.prototype.inc = function(release) { }; exports.inc = inc; -function inc(version, release, loose) { +function inc(version, release, loose, identifier) { + if (typeof(loose) === 'string') { + identifier = loose; + loose = undefined; + } + try { - return new SemVer(version, loose).inc(release).version; + return new SemVer(version, loose).inc(release, identifier).version; } catch (er) { return null; } } +exports.diff = diff; +function diff(version1, version2) { + if (eq(version1, version2)) { + return null; + } else { + var v1 = parse(version1); + var v2 = parse(version2); + if (v1.prerelease.length || v2.prerelease.length) { + for (var key in v1) { + if (key === 'major' || key === 'minor' || key === 'patch') { + if (v1[key] !== v2[key]) { + return 'pre'+key; + } + } + } + return 'prerelease'; + } + for (var key in v1) { + if (key === 'major' || key === 'minor' || key === 'patch') { + if (v1[key] !== v2[key]) { + return key; + } + } + } + } +} + exports.compareIdentifiers = compareIdentifiers; var numeric = /^[0-9]+$/; @@ -474,6 +540,21 @@ function rcompareIdentifiers(a, b) { return compareIdentifiers(b, a); } +exports.major = major; +function major(a, loose) { + return new SemVer(a, loose).major; +} + +exports.minor = minor; +function minor(a, loose) { + return new SemVer(a, loose).minor; +} + +exports.patch = patch; +function patch(a, loose) { + return new SemVer(a, loose).patch; +} + exports.compare = compare; function compare(a, b, loose) { return new SemVer(a, loose).compare(b); diff --git a/test/big-numbers.js b/test/big-numbers.js new file mode 100644 index 00000000..692aa241 --- /dev/null +++ b/test/big-numbers.js @@ -0,0 +1,24 @@ +var test = require('tap').test +var semver = require('../') + +test('long version is too long', function (t) { + var v = '1.2.' + new Array(256).join('1') + t.throws(function () { + new semver.SemVer(v) + }) + t.equal(semver.valid(v, false), null) + t.equal(semver.valid(v, true), null) + t.equal(semver.inc(v, 'patch'), null) + t.end() +}) + +test('big number is like too long version', function (t) { + var v = '1.2.' + new Array(100).join('1') + t.throws(function () { + new semver.SemVer(v) + }) + t.equal(semver.valid(v, false), null) + t.equal(semver.valid(v, true), null) + t.equal(semver.inc(v, 'patch'), null) + t.end() +}) diff --git a/test/index.js b/test/index.js index 68d55daa..926d560f 100644 --- a/test/index.js +++ b/test/index.js @@ -13,6 +13,7 @@ var lte = semver.lte; var satisfies = semver.satisfies; var validRange = semver.validRange; var inc = semver.inc; +var diff = semver.diff; var replaceStars = semver.replaceStars; var toComparators = semver.toComparators; var SemVer = semver.SemVer; @@ -49,7 +50,9 @@ test('\ncomparison tests', function(t) { ['1.2.3-a.10', '1.2.3-a.5'], ['1.2.3-a.b', '1.2.3-a.5'], ['1.2.3-a.b', '1.2.3-a'], - ['1.2.3-a.b.c.10.d.5', '1.2.3-a.b.c.5.d.100'] + ['1.2.3-a.b.c.10.d.5', '1.2.3-a.b.c.5.d.100'], + ['1.2.3-r2', '1.2.3-r100'], + ['1.2.3-r100', '1.2.3-R2'] ].forEach(function(v) { var v0 = v[0]; var v1 = v[1]; @@ -313,8 +316,8 @@ test('\nnegative range tests', function(t) { }); test('\nincrement versions test', function(t) { - // [version, inc, result] - // inc(version, inc) -> result +// [version, inc, result, identifier] +// inc(version, inc) -> result [['1.2.3', 'major', '2.0.0'], ['1.2.3', 'minor', '1.3.0'], ['1.2.3', 'patch', '1.2.4'], @@ -353,14 +356,87 @@ test('\nincrement versions test', function(t) { ['1.2.0', 'premajor', '2.0.0-0'], ['1.2.3-1', 'premajor', '2.0.0-0'], ['1.2.0-1', 'minor', '1.2.0'], - ['1.0.0-1', 'major', '1.0.0'] + ['1.0.0-1', 'major', '1.0.0'], + + ['1.2.3', 'major', '2.0.0', false, 'dev'], + ['1.2.3', 'minor', '1.3.0', false, 'dev'], + ['1.2.3', 'patch', '1.2.4', false, 'dev'], + ['1.2.3tag', 'major', '2.0.0', true, 'dev'], + ['1.2.3-tag', 'major', '2.0.0', false, 'dev'], + ['1.2.3', 'fake', null, false, 'dev'], + ['1.2.0-0', 'patch', '1.2.0', false, 'dev'], + ['fake', 'major', null, false, 'dev'], + ['1.2.3-4', 'major', '2.0.0', false, 'dev'], + ['1.2.3-4', 'minor', '1.3.0', false, 'dev'], + ['1.2.3-4', 'patch', '1.2.3', false, 'dev'], + ['1.2.3-alpha.0.beta', 'major', '2.0.0', false, 'dev'], + ['1.2.3-alpha.0.beta', 'minor', '1.3.0', false, 'dev'], + ['1.2.3-alpha.0.beta', 'patch', '1.2.3', false, 'dev'], + ['1.2.4', 'prerelease', '1.2.5-dev.0', false, 'dev'], + ['1.2.3-0', 'prerelease', '1.2.3-dev.0', false, 'dev'], + ['1.2.3-alpha.0', 'prerelease', '1.2.3-dev.0', false, 'dev'], + ['1.2.3-alpha.0', 'prerelease', '1.2.3-alpha.1', false, 'alpha'], + ['1.2.3-alpha.0.beta', 'prerelease', '1.2.3-dev.0', false, 'dev'], + ['1.2.3-alpha.0.beta', 'prerelease', '1.2.3-alpha.1.beta', false, 'alpha'], + ['1.2.3-alpha.10.0.beta', 'prerelease', '1.2.3-dev.0', false, 'dev'], + ['1.2.3-alpha.10.0.beta', 'prerelease', '1.2.3-alpha.10.1.beta', false, 'alpha'], + ['1.2.3-alpha.10.1.beta', 'prerelease', '1.2.3-alpha.10.2.beta', false, 'alpha'], + ['1.2.3-alpha.10.2.beta', 'prerelease', '1.2.3-alpha.10.3.beta', false, 'alpha'], + ['1.2.3-alpha.10.beta.0', 'prerelease', '1.2.3-dev.0', false, 'dev'], + ['1.2.3-alpha.10.beta.0', 'prerelease', '1.2.3-alpha.10.beta.1', false, 'alpha'], + ['1.2.3-alpha.10.beta.1', 'prerelease', '1.2.3-alpha.10.beta.2', false, 'alpha'], + ['1.2.3-alpha.10.beta.2', 'prerelease', '1.2.3-alpha.10.beta.3', false, 'alpha'], + ['1.2.3-alpha.9.beta', 'prerelease', '1.2.3-dev.0', false, 'dev'], + ['1.2.3-alpha.9.beta', 'prerelease', '1.2.3-alpha.10.beta', false, 'alpha'], + ['1.2.3-alpha.10.beta', 'prerelease', '1.2.3-alpha.11.beta', false, 'alpha'], + ['1.2.3-alpha.11.beta', 'prerelease', '1.2.3-alpha.12.beta', false, 'alpha'], + ['1.2.0', 'prepatch', '1.2.1-dev.0', 'dev'], + ['1.2.0-1', 'prepatch', '1.2.1-dev.0', 'dev'], + ['1.2.0', 'preminor', '1.3.0-dev.0', 'dev'], + ['1.2.3-1', 'preminor', '1.3.0-dev.0', 'dev'], + ['1.2.0', 'premajor', '2.0.0-dev.0', 'dev'], + ['1.2.3-1', 'premajor', '2.0.0-dev.0', 'dev'], + ['1.2.0-1', 'minor', '1.2.0', 'dev'], + ['1.0.0-1', 'major', '1.0.0', 'dev'], + ['1.2.3-dev.bar', 'prerelease', '1.2.3-dev.0', false, 'dev'] + ].forEach(function(v) { var pre = v[0]; var what = v[1]; var wanted = v[2]; var loose = v[3]; - var found = inc(pre, what, loose); - t.equal(found, wanted, 'inc(' + pre + ', ' + what + ') === ' + wanted); + var id = v[4]; + var found = inc(pre, what, loose, id); + var cmd = 'inc(' + pre + ', ' + what + ', ' + id + ')'; + t.equal(found, wanted, cmd + ' === ' + wanted); + }); + + t.end(); +}); + +test('\ndiff versions test', function(t) { +// [version1, version2, result] +// diff(version1, version2) -> result + [['1.2.3', '0.2.3', 'major'], + ['1.4.5', '0.2.3', 'major'], + ['1.2.3', '2.0.0-pre', 'premajor'], + ['1.2.3', '1.3.3', 'minor'], + ['1.0.1', '1.1.0-pre', 'preminor'], + ['1.2.3', '1.2.4', 'patch'], + ['1.2.3', '1.2.4-pre', 'prepatch'], + ['0.0.1', '0.0.1-pre', 'prerelease'], + ['0.0.1', '0.0.1-pre-2', 'prerelease'], + ['1.1.0', '1.1.0-pre', 'prerelease'], + ['1.1.0-pre-1', '1.1.0-pre-2', 'prerelease'], + ['1.0.0', '1.0.0', null] + + ].forEach(function(v) { + var version1 = v[0]; + var version2 = v[1]; + var wanted = v[2]; + var found = diff(version1, version2); + var cmd = 'diff(' + version1 + ', ' + version2 + ')'; + t.equal(found, wanted, cmd + ' === ' + wanted); }); t.end(); diff --git a/test/major-minor-patch.js b/test/major-minor-patch.js new file mode 100644 index 00000000..e9d4039c --- /dev/null +++ b/test/major-minor-patch.js @@ -0,0 +1,72 @@ +var tap = require('tap'); +var test = tap.test; +var semver = require('../semver.js'); + +test('\nmajor tests', function(t) { + // [range, version] + // Version should be detectable despite extra characters + [ + ['1.2.3', 1], + [' 1.2.3 ', 1], + [' 2.2.3-4 ', 2], + [' 3.2.3-pre ', 3], + ['v5.2.3', 5], + [' v8.2.3 ', 8], + ['\t13.2.3', 13], + ['=21.2.3', 21, true], + ['v=34.2.3', 34, true] + ].forEach(function(tuple) { + var range = tuple[0]; + var version = tuple[1]; + var loose = tuple[2] || false; + var msg = 'major(' + range + ') = ' + version; + t.equal(semver.major(range, loose), version, msg); + }); + t.end(); +}); + +test('\nminor tests', function(t) { + // [range, version] + // Version should be detectable despite extra characters + [ + ['1.1.3', 1], + [' 1.1.3 ', 1], + [' 1.2.3-4 ', 2], + [' 1.3.3-pre ', 3], + ['v1.5.3', 5], + [' v1.8.3 ', 8], + ['\t1.13.3', 13], + ['=1.21.3', 21, true], + ['v=1.34.3', 34, true] + ].forEach(function(tuple) { + var range = tuple[0]; + var version = tuple[1]; + var loose = tuple[2] || false; + var msg = 'minor(' + range + ') = ' + version; + t.equal(semver.minor(range, loose), version, msg); + }); + t.end(); +}); + +test('\npatch tests', function(t) { + // [range, version] + // Version should be detectable despite extra characters + [ + ['1.2.1', 1], + [' 1.2.1 ', 1], + [' 1.2.2-4 ', 2], + [' 1.2.3-pre ', 3], + ['v1.2.5', 5], + [' v1.2.8 ', 8], + ['\t1.2.13', 13], + ['=1.2.21', 21, true], + ['v=1.2.34', 34, true] + ].forEach(function(tuple) { + var range = tuple[0]; + var version = tuple[1]; + var loose = tuple[2] || false; + var msg = 'patch(' + range + ') = ' + version; + t.equal(semver.patch(range, loose), version, msg); + }); + t.end(); +});