systemjs/lib/system-versions.js
2014-02-19 16:35:42 +02:00

219 lines
7.4 KiB
JavaScript

/*
SystemJS Semver Version Addon
1. Uses Semver convention for major and minor forms
Supports requesting a module from a package that contains a version suffix
with the following semver ranges:
module - any version
module@1 - major version 1, any minor (not prerelease)
module@1.2 - minor version 1.2, any patch (not prerelease)
module@1.2.3 - exact version
It is assumed that these modules are provided by the server / file system.
First checks the already-requested packages to see if there are any packages
that would match the same package and version range.
This provides a greedy algorithm as a simple fix for sharing version-managed
dependencies as much as possible, which can later be optimized through version
hint configuration created out of deeper version tree analysis.
2. Semver-compatibility syntax (caret operator - ^)
Compatible version request support is then also provided for:
module@^1.2.3 - module@1, >=1.2.3
module@^1.2 - module@1, >=1.2.0
module@^1 - module@1
module@^0.5.3 - module@0.5, >= 0.5.3
module@^0.0.1 - module@0.0.1
The ^ symbol is always normalized out to a normal version request.
This provides comprehensive semver compatibility.
3. System.versions version hints and version report
Note this addon should be provided after all other normalize overrides.
The full list of versions can be found at System.versions providing an insight
into any possible version forks.
It is also possible to create version solution hints on the System global:
System.versions = {
jquery: ['1.9.2', '2.0.3'],
bootstrap: '3.0.1'
};
Versions can be an array or string for a single version.
When a matching semver request is made (jquery@1.9, jquery@1, bootstrap@3)
they will be converted to the latest version match contained here, if present.
Prereleases in this versions list are also allowed to satisfy ranges when present.
*/
(function() {
// match x, x.y, x.y.z, x.y.z-prerelease.1
var semverRegEx = /^(\d+)(?:\.(\d+)(?:\.(\d+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?)?)?$/;
var semverCompare = function(v1, v2) {
var v1Parts = v1.split('.');
var v2Parts = v2.split('.');
var prereleaseIndex;
if (v1Parts[2] && (prereleaseIndex = v1Parts[2].indexOf('-')) != -1)
v1Parts.splice(2, 1, v1Parts[2].substr(0, prereleaseIndex), v1Parts[2].substr(prereleaseIndex + 1));
if (v2Parts[2] && (prereleaseIndex = v2Parts[2].indexOf('-')) != -1)
v2Parts.splice(2, 1, v2Parts[2].substr(0, prereleaseIndex), v2Parts[2].substr(prereleaseIndex + 1));
for (var i = 0; i < Math.max(v1Parts.length, v2Parts.length); i++) {
if (!v1Parts[i])
return 1;
else if (!v2Parts[i])
return -1;
if (v1Parts[i] != v2Parts[i])
return parseInt(v1Parts[i]) > parseInt(v2Parts[i]) ? 1 : -1;
}
return 0;
}
var systemNormalize = System.normalize;
System.versions = System.versions || {};
// hook normalize and store a record of all versioned packages
System.normalize = function(name, parentName, parentAddress) {
var packageVersions = System.versions;
// run all other normalizers first
return Promise.resolve(systemNormalize.call(this, name, parentName, parentAddress)).then(function(normalized) {
var version, semverMatch, nextChar, versions;
var index = normalized.indexOf('@');
// see if this module corresponds to a package already in our versioned packages list
// no version specified - check against the list (given we don't know the package name)
if (index == -1) {
for (var p in packageVersions) {
versions = packageVersions[p];
if (normalized.substr(0, p.length) != p)
continue;
nextChar = normalized.charAt(p.length);
if (nextChar && nextChar != '/')
continue;
// match -> take latest version
return p + '@' + (typeof versions == 'string' ? versions : versions[versions.length - 1]) + normalized.substr(p.length);
}
return normalized;
}
// get the version info
version = normalized.substr(index + 1).split('/')[0];
var versionLength = version.length;
var minVersion;
if (version.substr(0, 1) == '^') {
version = version.substr(1);
minVersion = true;
}
semverMatch = version.match(semverRegEx);
// if not a semver, we cant help
if (!semverMatch)
return normalized;
// translate '^' in range to simpler range form
if (minVersion) {
// ^0 -> 0
// ^1 -> 1
if (!semverMatch[2])
minVersion = false;
if (!semverMatch[3]) {
// ^1.1 -> ^1.1.0
if (semverMatch[2] > 0)
semverMatch[3] = '0';
// ^0.1 -> 0.1
// ^0.0 -> 0.0
else
minVersion = false;
}
}
if (minVersion) {
// >= 1.0.0
if (semverMatch[1] > 0) {
if (!semverMatch[2])
version = semverMatch[1] + '.0.0';
if (!semverMatch[3])
version = semverMatch[1] + '.0';
minVersion = version;
semverMatch = [semverMatch[1]];
}
// >= 0.1.0
else if (semverMatch[2] > 0) {
minVersion = version;
semverMatch = [0, semverMatch[2]];
}
// >= 0.0.0
else {
// NB compatible with prerelease is just prelease itself?
minVersion = false;
semverMatch = [0, 0, semverMatch[3]];
}
version = semverMatch.join('.');
}
var packageName = normalized.substr(0, index);
versions = packageVersions[packageName] || [];
if (typeof versions == 'string')
versions = [versions];
// look for a version match
// if an exact semver, theres nothing to match, just record it
if (!semverMatch[3] || minVersion)
for (var i = versions.length - 1; i >= 0; i--) {
var curVersion = versions[i];
// if I have requested x.y, find an x.y.z-b
// if I have requested x, find any x.y / x.y.z-b
if (curVersion.substr(0, version.length) == version && curVersion.charAt(version.length).match(/^[\.\-]?$/)) {
// if a minimum version, then check too
if (!minVersion || minVersion && semverCompare(curVersion, minVersion) != -1)
return packageName + '@' + curVersion + normalized.substr(packageName.length + versionLength + 1);
}
}
// no match
// record the package and semver for reuse since we're now asking the server
// x.y and x versions will now be latest by default, so they are useful in the version list
if (versions.indexOf(version) == -1) {
versions.push(version);
versions.sort(semverCompare);
normalized = packageName + '@' + version + normalized.substr(packageName.length + versionLength + 1);
// if this is an x.y.z, remove any x.y, x
// if this is an x.y, remove any x
if (semverMatch[3] && (index = versions.indexOf(semverMatch[1] + '.' + semverMatch[2])) != -1)
versions.splice(index, 1);
if (semverMatch[2] && (index = versions.indexOf(semverMatch[1])) != -1)
versions.splice(index, 1);
packageVersions[packageName] = versions.length == 1 ? versions[0] : versions;
}
return normalized;
});
}
})();