bitburner-src/scripts/semver.js

836 lines
24 KiB
JavaScript
Raw Normal View History

/* 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.
2021-09-09 05:47:34 +02:00
src[MAINVERSION] = `(${src[NUMERICIDENTIFIER]})\\.(${src[NUMERICIDENTIFIER]})\\.(${src[NUMERICIDENTIFIER]})`;
2021-09-05 01:09:30 +02:00
src[
2021-09-22 18:56:55 +02:00
MAINVERSIONLOOSE
2021-09-05 01:09:30 +02:00
] = `(${src[NUMERICIDENTIFIERLOOSE]})\\.(${src[NUMERICIDENTIFIERLOOSE]})\\.(${src[NUMERICIDENTIFIERLOOSE]})`;
// ## Pre-release Version Identifier
// A numeric identifier, or a non-numeric identifier.
2021-09-09 05:47:34 +02:00
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.
2021-09-09 05:47:34 +02:00
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 */
2021-09-05 01:09:30 +02:00
src[
2021-09-22 18:56:55 +02:00
XRANGEPLAIN
2021-09-05 01:09:30 +02:00
] = `[v=\\s]*(${src[XRANGEIDENTIFIER]})(?:\\.(${src[XRANGEIDENTIFIER]})(?:\\.(${src[XRANGEIDENTIFIER]})(?:${src[PRERELEASE]})?${src[BUILD]}?)?)?`;
/* eslint-disable-next-line max-len */
2021-09-06 21:06:08 +02:00
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 */
2021-09-05 01:09:30 +02:00
src[
2021-09-22 18:56:55 +02:00
COERCE
2021-09-05 01:09:30 +02:00
] = `(?:^|[^\\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`
2021-09-09 05:47:34 +02:00
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.
2021-09-09 05:47:34 +02:00
src[HYPHENRANGE] = `^\\s*(${src[XRANGEPLAIN]})\\s+-\\s+(${src[XRANGEPLAIN]})\\s*$`;
2021-09-09 05:47:34 +02:00
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++) {
2021-09-22 18:56:55 +02:00
if (!re[idx]) {
re[idx] = new RegExp(src[idx]);
}
}
const ANY = {};
const isX = (id) => !id || id.toLowerCase() === "x" || id === "*";
function compareIdentifiers(left, right) {
2021-09-22 18:56:55 +02:00
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
2021-09-05 01:09:30 +02:00
function hyphenReplace($0, from, fM, fm, fp, fpr, fb, to, tM, tm, tp, tpr) {
2021-09-22 18:56:55 +02:00
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();
2021-09-09 09:17:01 +02:00
}
function replaceTilde(comp, loose) {
2021-09-22 18:56:55 +02:00
const regex = loose ? re[TILDELOOSE] : re[TILDE];
2021-09-09 09:17:01 +02:00
2021-09-22 18:56:55 +02:00
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) {
2021-09-22 18:56:55 +02:00
return comp
.trim()
.split(/\s+/)
.map((comp1) => replaceTilde(comp1, loose))
.join(" ");
}
function replaceCaret(comp, loose) {
2021-09-22 18:56:55 +02:00
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 {
2021-09-22 18:56:55 +02:00
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`;
}
2021-09-22 18:56:55 +02:00
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) {
2021-09-22 18:56:55 +02:00
return comp
.trim()
.split(/\s+/)
.map((comp1) => replaceCaret(comp1, loose))
.join(" ");
}
function replaceXRange(comp, loose) {
2021-09-22 18:56:55 +02:00
comp = comp.trim();
const regex = loose ? re[XRANGELOOSE] : re[XRANGE];
2021-09-22 18:56:55 +02:00
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;
2021-09-22 18:56:55 +02:00
if (operator === "=" && anyX) {
operator = "";
}
2021-09-22 18:56:55 +02:00
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) {
2021-09-22 18:56:55 +02:00
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;
}
2021-09-22 18:56:55 +02:00
}
2021-09-09 05:47:34 +02:00
2021-09-22 18:56:55 +02:00
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) {
2021-09-22 18:56:55 +02:00
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) {
2021-09-22 18:56:55 +02:00
// 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) {
2021-09-22 18:56:55 +02:00
comp = replaceCarets(comp, loose);
comp = replaceTildes(comp, loose);
comp = replaceXRanges(comp, loose);
comp = replaceStars(comp, loose);
2021-09-22 18:56:55 +02:00
return comp;
}
class SemVer {
2021-09-22 18:56:55 +02:00
/**
2021-09-05 01:09:30 +02:00
* 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.
*/
2021-09-22 18:56:55 +02:00
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}`);
2021-09-06 21:06:08 +02:00
}
2021-09-22 18:56:55 +02:00
if (version.length > MAX_LENGTH) {
throw new TypeError(`version is longer than ${MAX_LENGTH} characters`);
2021-09-09 09:17:01 +02:00
}
2021-09-22 18:56:55 +02:00
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(".")}`;
2021-09-09 09:17:01 +02:00
}
2021-09-09 05:47:34 +02:00
2021-09-22 18:56:55 +02:00
return this.version;
}
toString() {
return this.version;
}
/**
2021-09-05 01:09:30 +02:00
* Comares the current instance against another instance.
* @param {SemVer} other The SemVer to comare to.
* @returns {0|1|-1} A comparable value for sorting.
*/
2021-09-22 18:56:55 +02:00
compare(other) {
return this.compareMain(other) || this.comparePre(other);
}
compareMain(other) {
if (!(other instanceof SemVer)) {
other = new SemVer(other, this.loose);
}
2021-09-22 18:56:55 +02:00
return (
compareIdentifiers(this.major, other.major) ||
compareIdentifiers(this.minor, other.minor) ||
compareIdentifiers(this.patch, other.patch)
);
}
2021-09-22 18:56:55 +02:00
comparePre(other) {
if (!(other instanceof SemVer)) {
other = new SemVer(other, this.loose);
2021-09-06 21:06:08 +02:00
}
2021-09-22 18:56:55 +02:00
// 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;
2021-09-09 09:17:01 +02:00
}
2021-09-22 18:56:55 +02:00
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;
}
}
2021-09-22 18:56:55 +02:00
const compare = (leftVersion, rightVersion, loose) =>
new SemVer(leftVersion, loose).compare(new SemVer(rightVersion, loose));
2021-09-06 21:06:08 +02:00
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) {
2021-09-22 18:56:55 +02:00
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) {
2021-09-22 18:56:55 +02:00
for (let idx = 0; idx < set.length; idx++) {
if (!set[idx].test(version)) {
return false;
}
2021-09-22 18:56:55 +02:00
}
2021-09-22 18:56:55 +02:00
if (version.prerelease.length) {
2021-09-05 01:09:30 +02:00
// 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.
2021-09-22 18:56:55 +02:00
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;
}
}
2021-09-22 18:56:55 +02:00
}
2021-09-09 09:17:01 +02:00
}
2021-09-09 05:47:34 +02:00
2021-09-22 18:56:55 +02:00
// Version has a -pre, but it's not one of the ones we like.
return false;
}
return true;
}
class Comparator {
2021-09-22 18:56:55 +02:00
constructor(comp, loose) {
if (comp instanceof Comparator) {
if (comp.loose === loose) {
return comp;
}
comp = comp.value;
2021-09-09 05:47:34 +02:00
}
2021-09-22 18:56:55 +02:00
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;
2021-09-09 05:47:34 +02:00
}
2021-09-22 18:56:55 +02:00
}
2021-09-09 09:17:01 +02:00
2021-09-22 18:56:55 +02:00
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;
2021-09-09 05:47:34 +02:00
}
2021-09-22 18:56:55 +02:00
}
2021-09-22 18:56:55 +02:00
toString() {
return this.value;
}
2021-09-22 18:56:55 +02:00
test(version) {
if (this.semver === ANY) {
return true;
}
if (typeof version === "string") {
version = new SemVer(version, this.loose);
2021-09-09 05:47:34 +02:00
}
2021-09-22 18:56:55 +02:00
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 {
2021-09-22 18:56:55 +02:00
constructor(range, loose) {
if (range instanceof Range) {
if (range.loose === loose) {
return range;
}
2021-09-22 18:56:55 +02:00
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 ||
/**
2021-09-05 01:09:30 +02:00
* @type {string}
*/
2021-09-22 18:56:55 +02:00
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)));
2021-09-09 05:47:34 +02:00
}
2021-09-22 18:56:55 +02:00
set = set.map((comp) => new Comparator(comp, loose));
2021-09-22 18:56:55 +02:00
return set;
}
2021-09-22 18:56:55 +02:00
// 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);
2021-09-09 05:47:34 +02:00
}
2021-09-22 18:56:55 +02:00
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) {
2021-09-22 18:56:55 +02:00
try {
const rangeObj = new Range(range, loose);
2021-09-22 18:56:55 +02:00
return rangeObj.test(version);
} catch (er) {
return false;
}
}
module.exports.satisfies = satisfies;