mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
257 lines
8.7 KiB
JavaScript
257 lines
8.7 KiB
JavaScript
// This file is in tools/package-version-parser.js and is symlinked into
|
|
// packages/package-version-parser/package-version-parser.js. It's part
|
|
// of both the tool and the package! We don't use uniload for it because
|
|
// it needs to be used as part of initializing the uniload catalog.
|
|
var inTool = typeof Package === 'undefined';
|
|
|
|
var PV;
|
|
if (inTool) {
|
|
PV = exports;
|
|
} else {
|
|
PackageVersion = PV = {};
|
|
}
|
|
|
|
var semver = inTool ? require ('semver') : Npm.require('semver');
|
|
var __ = inTool ? require('underscore') : _;
|
|
|
|
// Takes in a meteor version, for example 1.2.3-rc5~1+12345.
|
|
//
|
|
// Returns an object composed of the following:
|
|
// semver: (ex: 1.2.3)
|
|
// wrapNum: 0 or a valid wrap number.
|
|
//
|
|
// Throws if the wrapNumber is invalid, or if the version cannot be split
|
|
// reasonably.
|
|
var extractSemverPart = function (versionString) {
|
|
if (!versionString) return { semver: "", wrapNum: -1 };
|
|
var noBuild = versionString.split('+');
|
|
var splitVersion = noBuild[0].split('~');
|
|
var wrapNum = 0;
|
|
// If we find two +s, or two ~, that's super invalid.
|
|
if (noBuild.length > 2 || splitVersion.length > 2) {
|
|
throwVersionParserError(
|
|
"Version string must look like semver (eg '1.2.3'), not '"
|
|
+ versionString + "'.");
|
|
} else if (splitVersion.length > 1) {
|
|
wrapNum = splitVersion[1];
|
|
if (!/^\d+$/.test(wrapNum)) {
|
|
throwVersionParserError(
|
|
"The wrap number (after ~) must contain only digits, so " +
|
|
versionString + " is invalid.");
|
|
} else if (wrapNum[0] === "0") {
|
|
throwVersionParserError(
|
|
"The wrap number (after ~) must not have a leading zero, so " +
|
|
versionString + " is invalid.");
|
|
}
|
|
}
|
|
return {
|
|
semver: (noBuild.length > 1) ?
|
|
splitVersion[0] + "+" + noBuild[1] :
|
|
splitVersion[0],
|
|
wrapNum: wrapNum
|
|
};
|
|
};
|
|
|
|
// Converts a meteor version into a very large number, unique to that version.
|
|
PV.versionMagnitude = function (versionString) {
|
|
// var v = semver.parse(versionString);
|
|
// return v.major * 10000 + v.minor * 100 + v.patch;
|
|
|
|
var version = extractSemverPart(versionString);
|
|
var v = semver.parse(version.semver);
|
|
// XXX: This is kind of hacky and relies on not having more than 100 wrap
|
|
// numbers, for example. Probably OK.
|
|
return v.major * 1000000 + v.minor * 10000 +
|
|
v.patch * 100 + version.wrapNum;
|
|
};
|
|
|
|
// Takes in two meteor versions. Returns true if the first one is less than the second.
|
|
PV.lessThan = function (versionOne, versionTwo) {
|
|
return PV.compare(versionOne, versionTwo) < 0;
|
|
};
|
|
|
|
// Given a string version, computes its default ECV (not counting any overrides).
|
|
//
|
|
// versionString: valid meteor version string.
|
|
PV.defaultECV = function (versionString) {
|
|
var version = extractSemverPart(versionString).semver;
|
|
var parsed = semver.parse(version);
|
|
if (! parsed)
|
|
throwVersionParserError("not a valid version: " + version);
|
|
return parsed.major + ".0.0";
|
|
}
|
|
|
|
// Takes in two meteor versions. Returns 0 if equal, 1 if v1 is greater, -1 if
|
|
// v2 is greater.
|
|
PV.compare = function (versionOne, versionTwo) {
|
|
var meteorVOne = extractSemverPart(versionOne);
|
|
var meteorVTwo = extractSemverPart(versionTwo);
|
|
|
|
// Wrap numbers only matter if the semver is equal, so if they don't even have
|
|
// wrap numbers, or if their semver is not equal, then we should let the
|
|
// semver library resolve this one.
|
|
if (meteorVOne.semver !== meteorVTwo.semver) {
|
|
return semver.compare(meteorVOne.semver, meteorVTwo.semver);
|
|
}
|
|
|
|
// If their semver components are equal, then the one with the smaller wrap
|
|
// numbers is smaller.
|
|
return meteorVOne.wrapNum - meteorVTwo.wrapNum;
|
|
};
|
|
|
|
// Conceptually we have three types of constraints:
|
|
// 1. "compatible-with" - A@x.y.z - constraints package A to version x.y.z or
|
|
// higher, as long as the version is backwards compatible with x.y.z.
|
|
// "pick A compatible with x.y.z"
|
|
// It is the default type.
|
|
// 2. "exactly" - A@=x.y.z - constraints package A only to version x.y.z and
|
|
// nothing else.
|
|
// "pick A exactly at x.y.z"
|
|
// 3. "any-reasonable" - "A"
|
|
// Basically, this means any version of A ... other than ones that have
|
|
// dashes in the version (ie, are prerelease) ... unless the prerelease
|
|
// version has been explicitly selected (which at this stage in the game
|
|
// means they are mentioned in a top-level constraint in the top-level
|
|
// call to the resolver).
|
|
PV.parseVersionConstraint = function (versionString, options) {
|
|
options = options || {};
|
|
var versionDesc = { version: null, type: "any-reasonable",
|
|
constraintString: versionString };
|
|
|
|
if (!versionString) {
|
|
return versionDesc;
|
|
}
|
|
|
|
if (versionString.charAt(0) === '=') {
|
|
versionDesc.type = "exactly";
|
|
versionString = versionString.substr(1);
|
|
} else {
|
|
versionDesc.type = "compatible-with";
|
|
}
|
|
|
|
// This will throw if the version string is invalid.
|
|
PV.getValidServerVersion(versionString);
|
|
|
|
versionDesc.version = versionString;
|
|
|
|
return versionDesc;
|
|
};
|
|
|
|
|
|
// Check to see if the versionString that we pass in is a valid meteor version.
|
|
//
|
|
// Returns a valid meteor version string that can be included in the
|
|
// server. That means that it has everything EXCEPT the build id. Throws if the
|
|
// entered string was invalid.
|
|
PV.getValidServerVersion = function (meteorVersionString) {
|
|
|
|
// Strip out the wrapper num, if present and check that it is valid.
|
|
var version = extractSemverPart(meteorVersionString);
|
|
|
|
var versionString = version.semver;
|
|
// NPM's semver spec supports things like 'v1.0.0' and considers them valid,
|
|
// but we don't. Everything before the + or - should be of the x.x.x form.
|
|
var mainVersion = versionString.split('+')[0].split('-')[0];
|
|
if (! /^\d+\.\d+\.\d+$/.test(mainVersion)) {
|
|
throwVersionParserError(
|
|
"Version string must look like semver (eg '1.2.3'), not '"
|
|
+ versionString + "'.");
|
|
};
|
|
|
|
var cleanVersion = semver.valid(versionString);
|
|
if (! cleanVersion ) {
|
|
throwVersionParserError(
|
|
"Version string must look like semver (eg '1.2.3'), not '"
|
|
+ versionString + "'.");
|
|
}
|
|
|
|
if (version.wrapNum) {
|
|
cleanVersion = cleanVersion + "~" + version.wrapNum;
|
|
}
|
|
|
|
return cleanVersion;
|
|
};
|
|
|
|
|
|
PV.parseConstraint = function (constraintString, options) {
|
|
if (typeof constraintString !== "string")
|
|
throw new TypeError("constraintString must be a string");
|
|
options = options || {};
|
|
|
|
var splitted = constraintString.split('@');
|
|
|
|
var constraint = { name: "", version: null,
|
|
type: "any-reasonable", constraintString: null };
|
|
var name = splitted[0];
|
|
var versionString = splitted[1];
|
|
|
|
if (splitted.length > 2) {
|
|
// throw error complaining about @
|
|
PV.validatePackageName('a@');
|
|
}
|
|
PV.validatePackageName(name);
|
|
|
|
constraint.name = name;
|
|
|
|
if (splitted.length === 2 && !versionString) {
|
|
throwVersionParserError(
|
|
"Version constraint for package '" + name +
|
|
"' cannot be empty; leave off the @ if you don't want to constrain " +
|
|
"the version.");
|
|
}
|
|
|
|
if (versionString) {
|
|
__.extend(constraint,
|
|
PV.parseVersionConstraint(versionString, options));
|
|
}
|
|
|
|
return constraint;
|
|
};
|
|
|
|
PV.validatePackageName = function (packageName, options) {
|
|
options = options || {};
|
|
|
|
var badChar = packageName.match(/[^a-z0-9:.\-]/);
|
|
if (badChar) {
|
|
if (options.detailedColonExplanation) {
|
|
throwVersionParserError(
|
|
"Bad character in package name: " + JSON.stringify(badChar[0]) +
|
|
".\n\nPackage names can only contain lowercase ASCII alphanumerics, " +
|
|
"dash, or dot.\nIf you plan to publish a package, it must be " +
|
|
"prefixed with your\nMeteor Developer Account username and a colon.");
|
|
}
|
|
throwVersionParserError(
|
|
"Package names can only contain lowercase ASCII alphanumerics, dash, " +
|
|
"dot, or colon, not " + JSON.stringify(badChar[0]) + ".");
|
|
}
|
|
if (!/[a-z]/.test(packageName)) {
|
|
throwVersionParserError("Package names must contain a lowercase ASCII letter.");
|
|
}
|
|
if (packageName[0] === '.') {
|
|
throwVersionParserError("Package names may not begin with a dot.");
|
|
}
|
|
};
|
|
|
|
var throwVersionParserError = function (message) {
|
|
var e = new Error(message);
|
|
e.versionParserError = true;
|
|
throw e;
|
|
};
|
|
|
|
// XXX if we were better about consistently only using functions in this file,
|
|
// we could just do this using the constraintString field
|
|
PV.constraintToVersionString = function (parsedConstraint) {
|
|
if (parsedConstraint.type === "any-reasonable")
|
|
return "";
|
|
if (parsedConstraint.type === "compatible-with")
|
|
return parsedConstraint.version;
|
|
if (parsedConstraint.type === "exactly")
|
|
return "=" + parsedConstraint.version;
|
|
throw Error("Unknown constraint type: " + parsedConstraint.type);
|
|
};
|
|
|
|
PV.constraintToFullString = function (parsedConstraint) {
|
|
return parsedConstraint.name + "@" + PV.constraintToVersionString(
|
|
parsedConstraint);
|
|
};
|