UNPKG

version-range

Version:

Check version ranges like `>=N` and `X || Y || Z` with support for Node.js, Web Browsers, Deno, and TypeScript.

211 lines (201 loc) 6.15 kB
export type Version = string | number export type Range = Version | Version[] const orRegex = /\s*\|\|\s*/g const andRegex = /\s*&&\s*/g const rangeRegex = /^\s*([<>=~^]*)\s*([\d.x]+)(-[^\s]+)?\s*/ const xRegex = /[.]x/g /** * Check if the version is within the range * @param subject The version to check against the range * @param range The range to check the version against */ export default function withinVersionRange( subject: Version, range: Range ): boolean { // prepare and verify subject subject = String(subject) const [subjectMajor = null, subjectMinor = null, subjectPatch = null] = subject.split('.') if (subjectMajor === null) throw new Error(`subject was invalid: ${JSON.stringify(subject)}`) const subjectMajorNumber = Number(subjectMajor || 0) const subjectMinorNumber = Number(subjectMinor || 0) const subjectPatchNumber = Number(subjectPatch || 0) // cycle through the conditions const orRanges = Array.isArray(range) ? range.slice() : String(range).split(orRegex) for (const orRange of orRanges) { let orResult: boolean = false const andRanges = String(orRange).split(andRegex) for (const andRange of andRanges) { let andResult: boolean = false // process range const [match, comparator, targetRaw, prerelease] = String(andRange).match(rangeRegex) || [] const target = (targetRaw || '').replace(xRegex, '') // // log // console.log({ // orRange, // andRange, // match, // comparator, // target, // prerelease, // }) // prepare and verify target const [targetMajor = null, targetMinor = null, targetPatch = null] = target.split('.') if (!match || !target || targetMajor == null || prerelease) throw new Error( `range condition was invalid: ${JSON.stringify(andRange)}` ) const targetMajorNumber = Number(targetMajor || 0) const targetMinorNumber = Number(targetMinor || 0) const targetPatchNumber = Number(targetPatch || 0) // is there more range matches? add it to and condition const remainder = String(andRange).slice(match.length) if (remainder) andRanges.push(remainder) // handle comparator switch (comparator) { case '^': if (subjectMajorNumber === targetMajorNumber) { if (subjectMinorNumber === targetMinorNumber) { if (subjectPatchNumber >= targetPatchNumber) { andResult = true } } else if (subjectMinorNumber >= targetMinorNumber) { andResult = true } } break case '~': if (subjectMajorNumber === targetMajorNumber) { if ( subjectMinor !== null && subjectMinorNumber === targetMinorNumber ) { if (subjectPatchNumber >= targetPatchNumber) { andResult = true } } } break case '>=': if (subjectMajorNumber === targetMajorNumber) { if (subjectMinorNumber === targetMinorNumber) { if (subjectPatchNumber >= targetPatchNumber) { andResult = true } } else if (subjectMinorNumber >= targetMinorNumber) { andResult = true } } else if (subjectMajorNumber >= targetMajorNumber) { andResult = true } break case '>': if (subjectMajorNumber === targetMajorNumber) { if (targetMinor === null) { // x > x = false // x.y > x = false } else if (subjectMinorNumber === targetMinorNumber) { if (targetPatch === null) { // x.y > x.y = false // x.y.z > x.y = false } else if (subjectPatchNumber > targetPatchNumber) { andResult = true } } else if (subjectMinorNumber > targetMinorNumber) { andResult = true } } else if (subjectMajorNumber > targetMajorNumber) { andResult = true } break case '<': if (subjectMajorNumber === targetMajorNumber) { if (subjectMinor === null) { // x < x = false // x < x.y = false } else if (subjectMinorNumber === targetMinorNumber) { if (subjectPatch === null) { // x.y < x.y = false // x.y < x.y.z = false } else if (subjectPatchNumber < targetPatchNumber) { andResult = true } } else if (subjectMinorNumber < targetMinorNumber) { andResult = true } } else if (subjectMajorNumber < targetMajorNumber) { andResult = true } break case '<=': if (subjectMajorNumber === targetMajorNumber) { if (subjectMinor === null) { if (targetMinor === null) { // x <= x = true andResult = true } // x <= x.y = false } else if (targetMinor === null) { // x.y <= x = true andResult = true } else if (subjectMinorNumber === targetMinorNumber) { if (subjectPatch === null) { if (targetPatch === null) { // x.y <= x.y = true andResult = true } // x.y <= x.y.z = false } else if (targetPatch === null) { // x.y.z <= x.y = true andResult = true } else if (subjectPatchNumber <= targetPatchNumber) { // x.y.z <= x.y.z = true andResult = true } } else if (subjectMinorNumber <= targetMinorNumber) { andResult = true } } else if (subjectMajorNumber <= targetMajorNumber) { andResult = true } break case '=': case '': if (subjectMajor === targetMajor) { if (targetMinor === null) { andResult = true } else if (subjectMinor === targetMinor) { if (targetPatch === null || subjectPatch === targetPatch) { andResult = true } } } break default: throw new Error( `range comparator was invalid: ${JSON.stringify(andRange)}` ) } // if one of the and conditions failed, don't continue and checks, and note failure to the or condition if (!andResult) { orResult = false break } else { // otherwise note success to the or condition orResult = true } } // if the entire and conditions passed, then we can break out of the or conditions if (orResult) { return true } } // nothing passed return false }