diff --git a/lib/version.js b/lib/version.js index be4b7a7c78241..1c83e068e13ae 100644 --- a/lib/version.js +++ b/lib/version.js @@ -11,18 +11,34 @@ const semver = require('semver'); // Given a list of versions (as strings), return the latest version. // Return undefined if no version could be found. -function latest(versions) { +function latest(versions, { pre = false } = {}) { let version = ''; let origVersions = versions; - versions = versions.filter(function(version) { - return (/[0-9]/).test(version); + // return all results that are likely semver compatible versions + versions = origVersions.filter(function(version) { + return (/\d+\.\d+/).test(version); }); + // If no semver versions then look for single numbered versions + if (!versions.length){ + versions = origVersions.filter(function(version) { + return (/\d+/).test(version); + }); + } + if (!pre){ + // remove pre-releases from array + versions = versions.filter(function(version) { + return !(/\d+-\w+/).test(version); + }); + } try { - version = semver.maxSatisfying(versions, ''); + version = versions.sort((a, b) => { + // coerce to string then lowercase otherwise alpha > RC + return semver.rcompare((''+a).toLowerCase(), (''+b).toLowerCase(), /* loose */ true); + })[0]; } catch(e) { version = latestDottedVersion(versions); } - if (version === undefined) { + if (version === undefined || version === null) { origVersions = origVersions.sort(); version = origVersions[origVersions.length - 1]; } @@ -83,14 +99,14 @@ function compareDottedVersion(v1, v2) { // Slice the specified number of dotted parts from the given semver version. // e.g. slice('2.4.7', 'minor') -> '2.4' function slice(v, releaseType) { - if (! semver.valid(v)) { + if (! semver.valid(v, /* loose */ true)) { return null; } - const major = semver.major(v); - const minor = semver.minor(v); - const patch = semver.patch(v); - const prerelease = semver.prerelease(v); + const major = semver.major(v, /* loose */ true); + const minor = semver.minor(v, /* loose */ true); + const patch = semver.patch(v, /* loose */ true); + const prerelease = semver.prerelease(v, /* loose */ true); const dottedParts = { major: [major], @@ -115,7 +131,7 @@ function minor(v) { } function rangeStart(v) { - const range = new semver.Range(v); + const range = new semver.Range(v, /* loose */ true); return range.set[0][0].semver.version; } diff --git a/lib/version.spec.js b/lib/version.spec.js index f51e28a94693c..831a9852ab453 100644 --- a/lib/version.spec.js +++ b/lib/version.spec.js @@ -2,6 +2,7 @@ const { test, given } = require('sazerac'); const { latest, slice, rangeStart } = require('./version'); +const includePre = true; describe('Version helpers', function () { test(latest, () => { @@ -10,32 +11,51 @@ describe('Version helpers', function () { given(['1.0.0', '2.0.0', '3.0.0']).expect('3.0.0'); given(['0.0.1', '0.0.10', '0.0.2', '0.0.20']).expect('0.0.20'); - // Simple dotted versions. - given(['1.0.0', 'v1.0.2', 'r1.0.1', 'release-2.0.0']).expect('release-2.0.0'); - given(['1.0.0', 'v2.0.0', 'r1.0.1', 'release-1.0.3']).expect('v2.0.0'); - given(['2.0.0', 'v1.0.3', 'r1.0.1', 'release-1.0.3']).expect('2.0.0'); - given(['1.0.0', 'v1.0.2', 'r2.0.0', 'release-1.0.3']).expect('r2.0.0'); + // "not-quite-valid" semver versions + given(['1.0.00', '1.0.02', '1.0.01']).expect('1.0.02'); + given(['1.0.05', '2.0.05', '3.0.05']).expect('3.0.05'); + given(['0.0.01', '0.0.010', '0.0.02', '0.0.020']).expect('0.0.020'); + + // Mixed style versions. - include pre-releases + given(['1.0.0', 'v1.0.2', 'r1.0.1', 'release-2.0.0', 'v1.0.1-alpha.1'], { pre: includePre }).expect('release-2.0.0'); + given(['1.0.0', 'v2.0.0', 'r1.0.1', 'release-1.0.3', 'v1.0.1-alpha.1'], { pre: includePre }).expect('v2.0.0'); + given(['2.0.0', 'v1.0.3', 'r1.0.1', 'release-1.0.3', 'v1.0.1-alpha.1'], { pre: includePre }).expect('2.0.0'); + given(['1.0.0', 'v1.0.2', 'r2.0.0', 'release-1.0.3', 'v1.0.1-alpha.1'], { pre: includePre }).expect('r2.0.0'); + given(['1.0.0', 'v1.0.2', 'r2.0.0', 'release-1.0.3', 'v2.0.1-alpha.1'], { pre: includePre }).expect('v2.0.1-alpha.1'); // Versions with 'v' prefix. + given(['v1.0.0', 'v1.0.2', 'v1.0.1']).expect('v1.0.2'); + given(['v1.0.0', 'v3.0.0', 'v2.0.0']).expect('v3.0.0'); + + // Simple (2 number) versions. given(['0.1', '0.3', '0.2']).expect('0.3'); given(['0.1', '0.5', '0.12', '0.21']).expect('0.21'); given(['1.0', '2.0', '3.0']).expect('3.0'); - // Versions with '-release' prefix - given(['v1.0.0', 'v1.0.2', 'v1.0.1']).expect('v1.0.2'); - given(['v1.0.0', 'v3.0.0', 'v2.0.0']).expect('v3.0.0'); + // Simple (one-number) versions + given(['2', '10', '1']).expect('10'); + + // Include pre-releases + given(['v1.0.1-alpha.2', 'v1.0.1-alpha.1', 'v1.0.1-beta.3', 'v1.0.1-beta.1', 'v1.0.1-RC.1', 'v1.0.1-RC.2', 'v1.0.0'], { pre: includePre }).expect('v1.0.1-RC.2'); + given(['v1.0.1-alpha.2', 'v1.0.1-alpha.1', 'v1.0.1-beta.3', 'v1.0.1-beta.1', 'v1.0.1-RC.1', 'v1.0.1-RC.2','v1.0.1'], { pre: includePre }).expect('v1.0.1'); + // Exclude pre-releases + given(['v1.0.1-alpha.2', 'v1.0.1-alpha.1', 'v1.0.1-beta.3', 'v1.0.1-beta.1', 'v1.0.1-RC.1', 'v1.0.1-RC.2', 'v1.0.0']).expect('v1.0.0'); + given(['v1.0.1-alpha.2', 'v1.0.1-alpha.1', 'v1.0.1-beta.3', 'v1.0.1-beta.1', 'v1.0.1-RC.1', 'v1.0.1-RC.2','v1.0.1']).expect('v1.0.1'); - // Versions with '-release' prefix + // Versions with 'release-' prefix given(['release-1.0.0', 'release-1.0.2', 'release-1.0.20', 'release-1.0.3']).expect('release-1.0.20'); - // Simple (one-number) versions - given(['2', '10', '1']).expect('10'); + // Semver mixed with non semver versions + given(['1.0.0', '1.0.2', '1.1', '1.0', 'notaversion2', '12bcde4']).expect('1.1'); }); test(slice, () => { given('2.4.7', 'major').expect('2'); given('2.4.7', 'minor').expect('2.4'); given('2.4.7', 'patch').expect('2.4.7'); + given('02.4.7', 'major').expect('2'); + given('2.04.7', 'minor').expect('2.4'); + given('2.4.07', 'patch').expect('2.4.7'); given('2.4.7-alpha.1', 'major').expect('2-alpha.1'); given('2.4.7-alpha.1', 'minor').expect('2.4-alpha.1'); given('2.4.7-alpha.1', 'patch').expect('2.4.7-alpha.1'); diff --git a/server.js b/server.js index 7d45012c3658f..5d59e364129b9 100644 --- a/server.js +++ b/server.js @@ -2489,12 +2489,13 @@ cache(function(data, match, sendBadge, request) { })); // Dart's pub version integration. -camp.route(/^\/pub\/v\/(.*)\.(svg|png|gif|jpg|json)$/, +camp.route(/^\/pub\/v(pre)?\/(.*)\.(svg|png|gif|jpg|json)$/, cache(function(data, match, sendBadge, request) { - var userRepo = match[1]; // eg, "box2d" - var format = match[2]; - var apiUrl = 'https://pub.dartlang.org/packages/' + userRepo + '.json'; - var badgeData = getBadgeData('pub', data); + const includePre = Boolean(match[1]); + const userRepo = match[2]; // eg, "box2d" + const format = match[3]; + const apiUrl = 'https://pub.dartlang.org/packages/' + userRepo + '.json'; + let badgeData = getBadgeData('pub', data); request(apiUrl, function(err, res, buffer) { if (err != null) { badgeData.text[1] = 'inaccessible'; @@ -2505,7 +2506,7 @@ cache(function(data, match, sendBadge, request) { var data = JSON.parse(buffer); // Grab the latest stable version, or an unstable var versions = data.versions; - var version = latestVersion(versions); + var version = latestVersion(versions, { pre: includePre }); badgeData.text[1] = versionText(version); badgeData.colorscheme = versionColor(version); sendBadge(format, badgeData); @@ -3570,13 +3571,14 @@ cache(function (data, match, sendBadge, request) { })); // GitHub tag integration. -camp.route(/^\/github\/tag\/([^/]+)\/([^/]+)\.(svg|png|gif|jpg|json)$/, +camp.route(/^\/github\/tag(-?pre)?\/([^/]+)\/([^/]+)\.(svg|png|gif|jpg|json)$/, cache(function(data, match, sendBadge, request) { - var user = match[1]; // eg, expressjs/express - var repo = match[2]; - var format = match[3]; - var apiUrl = githubApiUrl + '/repos/' + user + '/' + repo + '/tags'; - var badgeData = getBadgeData('tag', data); + const includePre = Boolean(match[1]); + const user = match[2]; // eg, expressjs/express + const repo = match[3]; + const format = match[4]; + const apiUrl = githubApiUrl + '/repos/' + user + '/' + repo + '/tags'; + let badgeData = getBadgeData('tag', data); if (badgeData.template === 'social') { badgeData.logo = getLogo('github', data); } @@ -3589,7 +3591,7 @@ cache(function(data, match, sendBadge, request) { try { var data = JSON.parse(buffer); var versions = data.map(function(e) { return e.name; }); - var tag = latestVersion(versions); + var tag = latestVersion(versions, { pre: includePre }); badgeData.text[1] = versionText(tag); badgeData.colorscheme = versionColor(tag); sendBadge(format, badgeData);