/* 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;