bitburner-src/scripts/semver.js
Steven Evans 2c0ea7e12c [chore] Script to check the required version of Node & NPM
Apparently the "engines" files of package.json is no longer checked
regularly when performing installs. With the changes and different
behaviors across different version of Node & NPM, and having no other
way to ensure everybody contributing is on the same page, this
preinstall check is necessary.

This heavily relies on "SemVer" NPM package, but in order to actually
check this prior to the first set of packages installed the code needs
to be there. So this is a slimmed down version of SemVer and syntax has
been updated.
2018-09-27 23:41:42 -05:00

822 lines
27 KiB
JavaScript

/* eslint-disable max-lines */
/* eslint-disable func-style */
/* eslint-disable function-paren-newline */
/* eslint-disable max-statements */
/* eslint-disable no-param-reassign */
/* eslint-disable complexity */
/* eslint-disable max-params */
/* eslint-disable max-depth */
/* eslint-disable prefer-destructuring */
/* eslint-disable prefer-arrow-callback */
/* eslint-disable arrow-body-style */
/* eslint-disable init-declarations */
// This is a heavily stripped down/reformatted version of https://github.com/npm/node-semver/blob/v5.5.1/semver.js
// Originally licensed under ISC (https://github.com/npm/node-semver/blob/v5.5.1/LICENSE)
// Copyright (c) Isaac Z. Schlueter and Contributors
const MAX_LENGTH = 256;
const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER;
// Max safe segment length for coercion.
const MAX_SAFE_COMPONENT_LENGTH = 16;
// The actual regexps go on re
/**
* @type {RegExp[]}
*/
const re = [];
/**
* @type {string[]}
*/
const src = [];
const NUMERICIDENTIFIER = 0;
const NUMERICIDENTIFIERLOOSE = 1;
const NONNUMERICIDENTIFIER = 2;
const MAINVERSION = 3;
const MAINVERSIONLOOSE = 4;
const PRERELEASEIDENTIFIER = 5;
const PRERELEASEIDENTIFIERLOOSE = 6;
const PRERELEASE = 7;
const PRERELEASELOOSE = 8;
const BUILDIDENTIFIER = 9;
const BUILD = 10;
const FULL = 11;
const LOOSE = 12;
const GTLT = 13;
const XRANGEIDENTIFIERLOOSE = 14;
const XRANGEIDENTIFIER = 15;
const XRANGEPLAIN = 16;
const XRANGEPLAINLOOSE = 17;
const XRANGE = 18;
const XRANGELOOSE = 19;
const COERCE = 20;
const LONETILDE = 21;
const TILDETRIM = 22;
const TILDE = 23;
const TILDELOOSE = 24;
const LONECARET = 25;
const CARET = 26;
const CARETLOOSE = 27;
const CARETTRIM = 28;
const COMPARATORLOOSE = 29;
const COMPARATOR = 30;
const COMPARATORTRIM = 31;
const HYPHENRANGE = 32;
const HYPHENRANGELOOSE = 33;
const STAR = 34;
// The following Regular Expressions can be used for tokenizing, validating, and parsing SemVer version strings.
// ## Numeric Identifier
// A single `0`, or a non-zero digit followed by zero or more digits.
/* eslint-disable operator-linebreak */
src[NUMERICIDENTIFIER] = "0|[1-9]\\d*";
src[NUMERICIDENTIFIERLOOSE] = "[0-9]+";
// ## Non-numeric Identifier
// Zero or more digits, followed by a letter or hyphen, and then zero or more letters, digits, or hyphens.
src[NONNUMERICIDENTIFIER] = "\\d*[a-zA-Z-][a-zA-Z0-9-]*";
// ## Main Version
// Three dot-separated numeric identifiers.
src[MAINVERSION] = `(${src[NUMERICIDENTIFIER]})\\.(${src[NUMERICIDENTIFIER]})\\.(${src[NUMERICIDENTIFIER]})`;
src[MAINVERSIONLOOSE] = `(${src[NUMERICIDENTIFIERLOOSE]})\\.(${src[NUMERICIDENTIFIERLOOSE]})\\.(${src[NUMERICIDENTIFIERLOOSE]})`;
// ## Pre-release Version Identifier
// A numeric identifier, or a non-numeric identifier.
src[PRERELEASEIDENTIFIER] = `(?:${src[NUMERICIDENTIFIER]}|${src[NONNUMERICIDENTIFIER]})`;
src[PRERELEASEIDENTIFIERLOOSE] = `(?:${src[NUMERICIDENTIFIERLOOSE]}|${src[NONNUMERICIDENTIFIER]})`;
// ## Pre-release Version
// Hyphen, followed by one or more dot-separated pre-release version identifiers.
src[PRERELEASE] = `(?:-(${src[PRERELEASEIDENTIFIER]}(?:\\.${src[PRERELEASEIDENTIFIER]})*))`;
src[PRERELEASELOOSE] = `(?:-?(${src[PRERELEASEIDENTIFIERLOOSE]}(?:\\.${src[PRERELEASEIDENTIFIERLOOSE]})*))`;
// ## Build Metadata Identifier
// Any combination of digits, letters, or hyphens.
src[BUILDIDENTIFIER] = "[0-9A-Za-z-]+";
// ## Build Metadata
// Plus sign, followed by one or more period-separated build metadata identifiers.
src[BUILD] = `(?:\\+(${src[BUILDIDENTIFIER]}(?:\\.${src[BUILDIDENTIFIER]})*))`;
// ## Full Version String
// A main version, followed optionally by a pre-release version and build metadata.
// Note that the only major, minor, patch, and pre-release sections of the version string are capturing groups.
// The build metadata is not a capturing group, because it should not ever be used in version comparison.
const FULLPLAIN = `v?${src[MAINVERSION]}${src[PRERELEASE]}?${src[BUILD]}?`;
src[FULL] = `^${FULLPLAIN}$`;
// Like full, but allows v1.2.3 and =1.2.3, which people do sometimes.
// Also, 1.0.0alpha1 (prerelease without the hyphen) which is pretty common in the npm registry.
const LOOSEPLAIN = `[v=\\s]*${src[MAINVERSIONLOOSE]}${src[PRERELEASELOOSE]}?${src[BUILD]}?`;
src[LOOSE] = `^${LOOSEPLAIN}$`;
src[GTLT] = "((?:<|>)?=?)";
// Something like "2.*" or "1.2.x".
// Note that "x.x" is a valid xRange identifer, meaning "any version"
// Only the first item is strictly required.
src[XRANGEIDENTIFIERLOOSE] = `${src[NUMERICIDENTIFIERLOOSE]}|x|X|\\*`;
src[XRANGEIDENTIFIER] = `${src[NUMERICIDENTIFIER]}|x|X|\\*`;
/* eslint-disable-next-line max-len */
src[XRANGEPLAIN] = `[v=\\s]*(${src[XRANGEIDENTIFIER]})(?:\\.(${src[XRANGEIDENTIFIER]})(?:\\.(${src[XRANGEIDENTIFIER]})(?:${src[PRERELEASE]})?${src[BUILD]}?)?)?`;
/* eslint-disable-next-line max-len */
src[XRANGEPLAINLOOSE] = `[v=\\s]*(${src[XRANGEIDENTIFIERLOOSE]})(?:\\.(${src[XRANGEIDENTIFIERLOOSE]})(?:\\.(${src[XRANGEIDENTIFIERLOOSE]})(?:${src[PRERELEASELOOSE]})?${src[BUILD]}?)?)?`;
src[XRANGE] = `^${src[GTLT]}\\s*${src[XRANGEPLAIN]}$`;
src[XRANGELOOSE] = `^${src[GTLT]}\\s*${src[XRANGEPLAINLOOSE]}$`;
// Coercion.
// Extract anything that could conceivably be a part of a valid semver
/* eslint-disable-next-line max-len */
src[COERCE] = `(?:^|[^\\d])(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}})(?:\\.(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?(?:\\.(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?(?:$|[^\\d])`;
// Tilde ranges.
// Meaning is "reasonably at or greater than"
src[LONETILDE] = "(?:~>?)";
src[TILDETRIM] = `(\\s*)${src[LONETILDE]}\\s+`;
re[TILDETRIM] = new RegExp(src[TILDETRIM], "g");
const tildeTrimReplace = "$1~";
src[TILDE] = `^${src[LONETILDE]}${src[XRANGEPLAIN]}$`;
src[TILDELOOSE] = `^${src[LONETILDE]}${src[XRANGEPLAINLOOSE]}$`;
// Caret ranges.
// Meaning is "at least and backwards compatible with"
src[LONECARET] = "(?:\\^)";
src[CARETTRIM] = `(\\s*)${src[LONECARET]}\\s+`;
re[CARETTRIM] = new RegExp(src[CARETTRIM], "g");
const caretTrimReplace = "$1^";
src[CARET] = `^${src[LONECARET]}${src[XRANGEPLAIN]}$`;
src[CARETLOOSE] = `^${src[LONECARET]}${src[XRANGEPLAINLOOSE]}$`;
// A simple gt/lt/eq thing, or just "" to indicate "any version"
src[COMPARATORLOOSE] = `^${src[GTLT]}\\s*(${LOOSEPLAIN})$|^$`;
src[COMPARATOR] = `^${src[GTLT]}\\s*(${FULLPLAIN})$|^$`;
// An expression to strip any whitespace between the gtlt and the thing it modifies, so that `> 1.2.3` ==> `>1.2.3`
src[COMPARATORTRIM] = `(\\s*)${src[GTLT]}\\s*(${LOOSEPLAIN}|${src[XRANGEPLAIN]})`;
// This one has to use the /g flag
re[COMPARATORTRIM] = new RegExp(src[COMPARATORTRIM], "g");
const comparatorTrimReplace = "$1$2$3";
// Something like `1.2.3 - 1.2.4`
// Note that these all use the loose form, because they'll be checked against either the strict or loose comparator form later.
src[HYPHENRANGE] = `^\\s*(${src[XRANGEPLAIN]})\\s+-\\s+(${src[XRANGEPLAIN]})\\s*$`;
src[HYPHENRANGELOOSE] = `^\\s*(${src[XRANGEPLAINLOOSE]})\\s+-\\s+(${src[XRANGEPLAINLOOSE]})\\s*$`;
// Star ranges basically just allow anything at all.
src[STAR] = "(<|>)?=?\\s*\\*";
/* eslint-enable operator-linebreak */
// Compile to actual regexp objects.
// All are flag-free, unless they were created above with a flag.
for (let idx = 0; idx <= STAR; idx++) {
if (!re[idx]) {
re[idx] = new RegExp(src[idx]);
}
}
const ANY = {};
const isX = (id) => !id || id.toLowerCase() === "x" || id === "*";
function compareIdentifiers(left, right) {
const numeric = /^[0-9]+$/;
const leftIsNumeric = numeric.test(left);
const rightIsNumeric = numeric.test(right);
if (leftIsNumeric && !rightIsNumeric) {
return -1;
}
if (rightIsNumeric && !leftIsNumeric) {
return 1;
}
if (leftIsNumeric && rightIsNumeric) {
left = Number(left);
right = Number(right);
}
if (left < right) {
return -1;
}
if (left > right) {
return 1;
}
return 0;
}
// This function is passed to string.replace(re[HYPHENRANGE])
// M, m, patch, prerelease, build
// 1.2 - 3.4.5 => >=1.2.0 <=3.4.5
// 1.2.3 - 3.4 => >=1.2.0 <3.5.0 Any 3.4.x will do
// 1.2 - 3.4 => >=1.2.0 <3.5.0
function hyphenReplace($0,
from, fM, fm, fp, fpr, fb,
to, tM, tm, tp, tpr) {
if (isX(fM)) {
from = "";
} else if (isX(fm)) {
from = `>=${fM}.0.0`;
} else if (isX(fp)) {
from = `>=${fM}.${fm}.0`;
} else {
from = `>=${from}`;
}
if (isX(tM)) {
to = "";
} else if (isX(tm)) {
to = `<${Number(tM) + 1}.0.0`;
} else if (isX(tp)) {
to = `<${tM}.${Number(tm) + 1}.0`;
} else if (tpr) {
to = `<=${tM}.${tm}.${tp}-${tpr}`;
} else {
to = `<=${to}`;
}
return `${from} ${to}`.trim();
}
function replaceTilde(comp, loose) {
const regex = loose ? re[TILDELOOSE] : re[TILDE];
return comp.replace(regex, function(match, major, minor, patch, prerelease) {
let ret;
if (isX(major)) {
ret = "";
} else if (isX(minor)) {
ret = `>=${major}.0.0 <${Number(major) + 1}.0.0`;
} else if (isX(patch)) {
// ~1.2 == >=1.2.0 <1.3.0
ret = `>=${major}.${minor}.0 <${major}.${Number(minor) + 1}.0`;
} else if (prerelease) {
if (prerelease.charAt(0) !== "-") {
prerelease = `-${prerelease}`;
}
ret = `>=${major}.${minor}.${patch}${prerelease} <${major}.${Number(minor) + 1}.0`;
} else {
// ~1.2.3 == >=1.2.3 <1.3.0
ret = `>=${major}.${minor}.${patch} <${major}.${Number(minor) + 1}.0`;
}
return ret;
});
}
// ~, ~> --> * (any, kinda silly)
// ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0 <3.0.0
// ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0 <2.1.0
// ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0 <1.3.0
// ~1.2.3, ~>1.2.3 --> >=1.2.3 <1.3.0
// ~1.2.0, ~>1.2.0 --> >=1.2.0 <1.3.0
function replaceTildes(comp, loose) {
return comp
.trim()
.split(/\s+/)
.map((comp1) => replaceTilde(comp1, loose))
.join(" ");
}
function replaceCaret(comp, loose) {
const regex = loose ? re[CARETLOOSE] : re[CARET];
return comp.replace(regex, function(match, major, minor, patch, prerelease) {
let ret;
if (isX(major)) {
ret = "";
} else if (isX(minor)) {
ret = `>=${major}.0.0 <${Number(major) + 1}.0.0`;
} else if (isX(patch)) {
if (major === "0") {
ret = `>=${major}.${minor}.0 <${major}.${Number(minor) + 1}.0`;
} else {
ret = `>=${major}.${minor}.0 <${Number(major) + 1}.0.0`;
}
} else if (prerelease) {
if (prerelease.charAt(0) !== "-") {
prerelease = `-${prerelease}`;
}
if (major === "0") {
if (minor === "0") {
ret = `>=${major}.${minor}.${patch}${prerelease} <${major}.${minor}.${Number(patch) + 1}`;
} else {
ret = `>=${major}.${minor}.${patch}${prerelease} <${major}.${Number(minor) + 1}.0`;
}
} else {
ret = `>=${major}.${minor}.${patch}${prerelease} <${Number(major) + 1}.0.0`;
}
} else if (major === "0") {
if (minor === "0") {
ret = `>=${major}.${minor}.${patch} <${major}.${minor}.${Number(patch) + 1}`;
} else {
ret = `>=${major}.${minor}.${patch} <${major}.${Number(minor) + 1}.0`;
}
} else {
ret = `>=${major}.${minor}.${patch} <${Number(major) + 1}.0.0`;
}
return ret;
});
}
// ^ --> * (any, kinda silly)
// ^2, ^2.x, ^2.x.x --> >=2.0.0 <3.0.0
// ^2.0, ^2.0.x --> >=2.0.0 <3.0.0
// ^1.2, ^1.2.x --> >=1.2.0 <2.0.0
// ^1.2.3 --> >=1.2.3 <2.0.0
// ^1.2.0 --> >=1.2.0 <2.0.0
function replaceCarets(comp, loose) {
return comp
.trim()
.split(/\s+/)
.map((comp1) => replaceCaret(comp1, loose))
.join(" ");
}
function replaceXRange(comp, loose) {
comp = comp.trim();
const regex = loose ? re[XRANGELOOSE] : re[XRANGE];
return comp.replace(regex, function(ret, operator, major, minor, patch) {
const xM = isX(major);
const xm = xM || isX(minor);
const xp = xm || isX(patch);
const anyX = xp;
if (operator === "=" && anyX) {
operator = "";
}
if (xM) {
if (operator === ">" || operator === "<") {
// Nothing is allowed
ret = "<0.0.0";
} else {
// Nothing is forbidden
ret = "*";
}
} else if (operator && anyX) {
// Replace X with 0
if (xm) {
minor = 0;
}
if (xp) {
patch = 0;
}
if (operator === ">") {
// >1 => >=2.0.0
// >1.2 => >=1.3.0
// >1.2.3 => >= 1.2.4
operator = ">=";
if (xm) {
major = Number(major) + 1;
minor = 0;
patch = 0;
} else if (xp) {
minor = Number(minor) + 1;
patch = 0;
}
} else if (operator === "<=") {
// <=0.7.x is actually <0.8.0, since any 0.7.x should pass. Similarly, <=7.x is actually <8.0.0, etc.
operator = "<";
if (xm) {
major = Number(major) + 1;
} else {
minor = Number(minor) + 1;
}
}
ret = `${operator}${major}.${minor}.${patch}`;
} else if (xm) {
ret = `>=${major}.0.0 <${Number(major) + 1}.0.0`;
} else if (xp) {
ret = `>=${major}.${minor}.0 <${major}.${Number(minor) + 1}.0`;
}
return ret;
});
}
function replaceXRanges(comp, loose) {
return comp
.split(/\s+/)
.map((comp1) => replaceXRange(comp1, loose))
.join(" ");
}
// Because * is AND-ed with everything else in the comparator, and '' means "any version", just remove the *s entirely.
function replaceStars(comp) {
// Looseness is ignored here. star is always as loose as it gets!
return comp.trim().replace(re[STAR], "");
}
// Comprised of xranges, tildes, stars, and gtlt's at this point.
// Already replaced the hyphen ranges turn into a set of JUST comparators.
function parseComparator(comp, loose) {
comp = replaceCarets(comp, loose);
comp = replaceTildes(comp, loose);
comp = replaceXRanges(comp, loose);
comp = replaceStars(comp, loose);
return comp;
}
class SemVer {
/**
* A semantic version.
* @param {string} version The version.
* @param {boolean} loose If this is a loose representation of a version.
* @returns {SemVer} a new instance.
*/
constructor(version, loose) {
if (version instanceof SemVer) {
if (version.loose === loose) {
return version;
}
version = version.version;
} else if (typeof version !== "string") {
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);
}
this.loose = loose;
const matches = version.trim().match(loose ? re[LOOSE] : re[FULL]);
if (!matches) {
throw new TypeError(`Invalid Version: ${version}`);
}
this.raw = version;
// These are actually numbers
this.major = Number(matches[1]);
this.minor = Number(matches[2]);
this.patch = Number(matches[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 (matches[4]) {
this.prerelease = matches[4].split(".").map((id) => {
if (/^[0-9]+$/.test(id)) {
const num = Number(id);
if (num >= 0 && num < MAX_SAFE_INTEGER) {
return num;
}
}
return id;
});
} else {
this.prerelease = [];
}
this.build = matches[5] ? matches[5].split(".") : [];
this.format();
}
format() {
this.version = `${this.major}.${this.minor}.${this.patch}`;
if (this.prerelease.length) {
this.version += `-${this.prerelease.join(".")}`;
}
return this.version;
}
toString() {
return this.version;
}
/**
* Comares the current instance against another instance.
* @param {SemVer} other The SemVer to comare to.
* @returns {0|1|-1} A comparable value for sorting.
*/
compare(other) {
return this.compareMain(other) || this.comparePre(other);
}
compareMain(other) {
if (!(other instanceof SemVer)) {
other = new SemVer(other, this.loose);
}
return compareIdentifiers(this.major, other.major) || compareIdentifiers(this.minor, other.minor) || compareIdentifiers(this.patch, other.patch);
}
comparePre(other) {
if (!(other instanceof SemVer)) {
other = new SemVer(other, this.loose);
}
// NOT having a prerelease is > having one
if (this.prerelease.length && !other.prerelease.length) {
return -1;
} else if (!this.prerelease.length && other.prerelease.length) {
return 1;
} else if (!this.prerelease.length && !other.prerelease.length) {
return 0;
}
let idx = 0;
do {
const thisPrelease = this.prerelease[idx];
const otherPrelease = other.prerelease[idx];
const thisPreleaseIsUndefined = typeof thisPrelease === "undefined";
const otherPreleaseIsUndefined = typeof otherPrelease === "undefined";
if (thisPreleaseIsUndefined && otherPreleaseIsUndefined) {
return 0;
} else if (otherPreleaseIsUndefined) {
return 1;
} else if (thisPreleaseIsUndefined) {
return -1;
} else if (thisPrelease === otherPrelease) {
continue;
} else {
return compareIdentifiers(thisPrelease, otherPrelease);
}
} while ((idx += 1) > 0);
// Should not hit this point, but assume equal ranking.
return 0;
}
}
const compare = (leftVersion, rightVersion, loose) => new SemVer(leftVersion, loose).compare(new SemVer(rightVersion, loose));
const gt = (leftVersion, rightVersion, loose) => compare(leftVersion, rightVersion, loose) > 0;
const lt = (leftVersion, rightVersion, loose) => compare(leftVersion, rightVersion, loose) < 0;
const eq = (leftVersion, rightVersion, loose) => compare(leftVersion, rightVersion, loose) === 0;
const neq = (leftVersion, rightVersion, loose) => compare(leftVersion, rightVersion, loose) !== 0;
const gte = (leftVersion, rightVersion, loose) => compare(leftVersion, rightVersion, loose) >= 0;
const lte = (leftVersion, rightVersion, loose) => compare(leftVersion, rightVersion, loose) <= 0;
function cmp(left, op, right, loose) {
let ret;
switch (op) {
case "===":
if (typeof left === "object") {
left = left.version;
}
if (typeof right === "object") {
right = right.version;
}
ret = left === right;
break;
case "!==":
if (typeof left === "object") {
left = left.version;
}
if (typeof right === "object") {
right = right.version;
}
ret = left !== right;
break;
case "":
case "=":
case "==":
ret = eq(left, right, loose);
break;
case "!=":
ret = neq(left, right, loose);
break;
case ">":
ret = gt(left, right, loose);
break;
case ">=":
ret = gte(left, right, loose);
break;
case "<":
ret = lt(left, right, loose);
break;
case "<=":
ret = lte(left, right, loose);
break;
default:
throw new TypeError(`Invalid operator: ${op}`);
}
return ret;
}
function testSet(set, version) {
for (let idx = 0; idx < set.length; idx++) {
if (!set[idx].test(version)) {
return false;
}
}
if (version.prerelease.length) {
// Find the set of versions that are allowed to have prereleases
// For example, ^1.2.3-pr.1 desugars to >=1.2.3-pr.1 <2.0.0
// That should allow `1.2.3-pr.2` to pass.
// However, `1.2.4-alpha.notready` should NOT be allowed, even though it's within the range set by the comparators.
for (let idx = 0; idx < set.length; idx++) {
if (set[idx].semver !== ANY) {
if (set[idx].semver.prerelease.length > 0) {
const allowed = set[idx].semver;
if (allowed.major === version.major && allowed.minor === version.minor && allowed.patch === version.patch) {
return true;
}
}
}
}
// Version has a -pre, but it's not one of the ones we like.
return false;
}
return true;
}
class Comparator {
constructor(comp, loose) {
if (comp instanceof Comparator) {
if (comp.loose === loose) {
return comp;
}
comp = comp.value;
}
if (!(this instanceof Comparator)) {
return new Comparator(comp, loose);
}
this.loose = loose;
this.parse(comp);
if (this.semver === ANY) {
this.value = "";
} else {
this.value = this.operator + this.semver.version;
}
}
parse(comp) {
const regex = this.loose ? re[COMPARATORLOOSE] : re[COMPARATOR];
const matches = comp.match(regex);
if (!matches) {
throw new TypeError(`Invalid comparator: ${comp}`);
}
this.operator = matches[1];
if (this.operator === "=") {
this.operator = "";
}
// If it literally is just '>' or '' then allow anything.
if (matches[2]) {
this.semver = new SemVer(matches[2], this.loose);
} else {
this.semver = ANY;
}
}
toString() {
return this.value;
}
test(version) {
if (this.semver === ANY) {
return true;
}
if (typeof version === "string") {
version = new SemVer(version, this.loose);
}
return cmp(version, this.operator, this.semver, this.loose);
}
}
/**
* A range object.
* @param {Range|Comparator|string} range the value to parse to a Range.
* @param {any} loose whether the range is explicit or a loose value.
* @returns {Range} the Range instace.
*/
class Range {
constructor(range, loose) {
if (range instanceof Range) {
if (range.loose === loose) {
return range;
}
return new Range(range.raw, loose);
}
if (range instanceof Comparator) {
return new Range(range.value, loose);
}
if (!(this instanceof Range)) {
return new Range(range, loose);
}
this.loose = loose;
// First, split based on boolean or ||
/**
* @type {string}
*/
this.raw = range;
// Throw out any that are not relevant for whatever reason
const hasLength = (item) => item.length;
this.set = this.raw.split(/\s*\|\|\s*/).map(function (range1) {
return this.parseRange(range1.trim());
}, this)
.filter(hasLength);
if (!this.set.length) {
throw new TypeError(`Invalid SemVer Range: ${range}`);
}
this.format();
}
format() {
this.range = this.set.map((comps) => comps.join(" ").trim())
.join("||")
.trim();
return this.range;
}
toString() {
return this.range;
}
parseRange(range) {
const loose = this.loose;
range = range.trim();
// `1.2.3 - 1.2.4` => `>=1.2.3 <=1.2.4`
const hr = loose ? re[HYPHENRANGELOOSE] : re[HYPHENRANGE];
range = range.replace(hr, hyphenReplace);
// `> 1.2.3 < 1.2.5` => `>1.2.3 <1.2.5`
range = range.replace(re[COMPARATORTRIM], comparatorTrimReplace);
// `~ 1.2.3` => `~1.2.3`
range = range.replace(re[TILDETRIM], tildeTrimReplace);
// `^ 1.2.3` => `^1.2.3`
range = range.replace(re[CARETTRIM], caretTrimReplace);
// Normalize spaces
range = range.split(/\s+/).join(" ");
// At this point, the range is completely trimmed and ready to be split into comparators.
const compRe = loose ? re[COMPARATORLOOSE] : re[COMPARATOR];
let set = range.split(" ").map((comp) => parseComparator(comp, loose))
.join(" ")
.split(/\s+/);
if (loose) {
// In loose mode, throw out any that are not valid comparators
set = set.filter((comp) => Boolean(comp.match(compRe)));
}
set = set.map((comp) => new Comparator(comp, loose));
return set;
}
// If ANY of the sets match ALL of its comparators, then pass
test(version) {
if (!version) {
return false;
}
if (typeof version === "string") {
version = new SemVer(version, this.loose);
}
for (let idx = 0; idx < this.set.length; idx++) {
if (testSet(this.set[idx], version)) {
return true;
}
}
return false;
}
}
/**
* Checks if the provided version can satisfy the provided range.
* @param {string} version The specific version.
* @param {string} range The range expression.
* @param {any} loose If the range is a loose expression.
* @returns {boolean} Whether the versions successfully satisfies the range.
*/
function satisfies(version, range, loose) {
try {
const rangeObj = new Range(range, loose);
return rangeObj.test(version);
} catch (er) {
return false;
}
}
module.exports.satisfies = satisfies;