mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-25 17:13:47 +01:00
836 lines
24 KiB
JavaScript
836 lines
24 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;
|