Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@vltpkg/semver

Package Overview
Dependencies
Maintainers
6
Versions
60
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@vltpkg/semver - npm Package Compare versions

Comparing version
1.0.0-rc.23
to
1.0.0-rc.24
+75
dist/comparator.d.ts
import { Version } from './version.ts';
/** all comparators are expressed in terms of these operators */
export type SimpleOperator = '' | '<' | '<=' | '>' | '>=';
/** operators that are expanded to simpler forms */
export type ComplexOperator = '^' | '~' | '~>';
/** comparator expressed as a [operator,version] tuple */
export type OVTuple = [SimpleOperator, Version];
/**
* The result of parsing a version value that might be either a full
* version like `1.2.3` or an X-Range like `1.2.x`
*/
export type ParsedXRange = ParsedXMajor | ParsedXMinor | ParsedXPatch | ParsedXVersion;
/**
* a {@link ParsedXRange} that is just a `*`
*/
export type ParsedXMajor = [];
/**
* a {@link ParsedXRange} that is just a major version
*/
export type ParsedXMinor = [number];
/**
* a {@link ParsedXRange} that is just a major and minor version
*/
export type ParsedXPatch = [number, number];
/**
* a {@link ParsedXRange} that is a full version
*/
export type ParsedXVersion = [
M: number,
m: number,
p: number,
pr?: string | undefined,
b?: string | undefined
];
/**
* Class used to parse the `||` separated portions
* of a range, and evaluate versions against it.
*
* This does most of the heavy lifting of range testing, and provides
* little affordance for improperly formatted strings. It should be
* considered an internal class, and usually not accessed directly.
*/
export declare class Comparator {
#private;
/**
* does this range include prereleases, even when they do not
* match the tuple in the comparator?
*/
includePrerelease: boolean;
/** raw string used to create this comparator */
raw: string;
/** tokens extracted from the raw string input */
tokens: string[];
/**
* Either the `any` comparator, the `none` comparator, or an operator
* and a {@link ParsedXRange}
*/
tuples: (Comparator | OVTuple)[];
/** true if this comparator can not match anything */
isNone: boolean;
/**
* true if this comparator is a `'*'` type of range.
*
* Note that it still will not match versions with a prerelease value,
* unless the tuple in the version matches the tuple provided to the
* comparator, and the comparator version also has a prerelease value,
* unless `includePrerelease` is set.
*/
isAny: boolean;
/** the canonical strict simplified parsed form of this constructor */
toString(): string;
constructor(comp: string, includePrerelease?: boolean);
/** return true if the version is a match for this comparator */
test(v: Version): boolean;
}
// TODO: it might be faster to not have Version objects in the
// comparator tuples, and instead just keep the parsed number arrays?
import { syntaxError } from '@vltpkg/error-cause';
import { fastSplit } from '@vltpkg/fast-split';
import { Version } from "./version.js";
const isOperator = (o) => !!o &&
(o === '>' ||
o === '<' ||
o === '>=' ||
o === '<=' ||
o === '' ||
o === '~' ||
o === '^' ||
o === '~>');
const preJunk = new Set('=v \t');
const invalidComp = (c, message) => syntaxError(`invalid comparator: '${c}' ${message}`, { found: c }, Comparator);
const assertNumber = (value, c, field) => {
const n = Number(value);
if (n !== n) {
throw invalidComp(c, `${field} must be numeric or 'x', got: '${value}'`);
}
return n;
};
const assertVersion = (v, comp) => {
if (!v) {
throw invalidComp(comp, 'no value provided for operator');
}
};
const assertMissing = (value, c, field) => {
if (value && !isX(value)) {
throw invalidComp(c, `cannot omit '${field}' and include subsequent fields`);
}
};
const MAJOR = 0;
const MINOR = 1;
const PATCH = 2;
const isX = (c) => !c || c === 'X' || c === 'x' || c === '*';
const isFullVersion = (parsed) => undefined !== parsed[PATCH];
const isXPatch = (parsed) => undefined !== parsed[MINOR] && undefined === parsed[PATCH];
const isXMinor = (parsed) => undefined !== parsed[MAJOR] && undefined === parsed[MINOR];
const isXMajor = (parsed) => undefined === parsed[MAJOR];
/**
* Class used to parse the `||` separated portions
* of a range, and evaluate versions against it.
*
* This does most of the heavy lifting of range testing, and provides
* little affordance for improperly formatted strings. It should be
* considered an internal class, and usually not accessed directly.
*/
export class Comparator {
/**
* does this range include prereleases, even when they do not
* match the tuple in the comparator?
*/
includePrerelease;
/** raw string used to create this comparator */
raw;
/** tokens extracted from the raw string input */
tokens;
/**
* Either the `any` comparator, the `none` comparator, or an operator
* and a {@link ParsedXRange}
*/
tuples = [];
/** true if this comparator can not match anything */
isNone = false;
/**
* true if this comparator is a `'*'` type of range.
*
* Note that it still will not match versions with a prerelease value,
* unless the tuple in the version matches the tuple provided to the
* comparator, and the comparator version also has a prerelease value,
* unless `includePrerelease` is set.
*/
isAny = false;
/** the canonical strict simplified parsed form of this constructor */
toString() {
return (this.isNone ? '<0.0.0-0'
: this.isAny ? '*'
: /* c8 ignore next */
this.tuples.map(c => (isAny(c) ? '*' : c.join(''))).join(' '));
}
constructor(comp, includePrerelease = false) {
this.includePrerelease = includePrerelease;
comp = comp.trim();
this.raw = comp;
let hyphen = false;
const rawComps = fastSplit(comp, ' ', -1, (part, parts, i) => {
if (part === '-') {
if (hyphen) {
throw invalidComp(comp, 'multiple hyphen ranges not allowed');
}
if (parts.length !== 1 || i === -1) {
throw invalidComp(comp, 'hyphen must be between two versions');
}
hyphen = true;
}
else if (hyphen && parts.length !== 2) {
throw invalidComp(comp, 'hyphen range must be alone');
}
});
// remove excess spaces, `> 1 2` => `>1 2`
const comps = [];
let followingOperator = false;
let l = 0;
for (const c of rawComps) {
if (c === '')
continue;
if (!followingOperator) {
followingOperator = isOperator(c);
comps.push(c);
l++;
continue;
}
// we know this is not undefined since followingOperator guards that
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
comps[l - 1] += c;
followingOperator = false;
}
// TS mistakenly thinks hyphen is always false here
if (hyphen) {
const [min, _, max] = comps;
/* c8 ignore start - defense in depth for TS, already guaranteed */
if (!min || !max) {
throw invalidComp(comp, 'hyphen must be between two versions');
}
/* c8 ignore stop */
this.#parseHyphenRange(min, max);
}
else if (!comps.length ||
(comps.length === 1 && isX(comps[0]))) {
this.tuples.push(this.#getComparatorAny());
}
else {
for (const c of comps) {
this.#parse(c);
if (this.isNone)
break;
}
}
this.tokens = comps;
this.isAny = true;
for (const c of this.tuples) {
if (Array.isArray(c) || !c.isAny) {
this.isAny = false;
break;
}
}
}
// inclusive min
#xInclusiveMin(raw) {
const z = this.includePrerelease ? '0' : undefined;
const [M, m = 0, p = 0, pr = z, build] = this.#parseX(raw);
return M === undefined ?
this.#getComparatorAny()
: ['>=', new Version(raw, M, m, p, pr, build)];
}
// exclusive min
#xExclusiveMin(raw) {
const parsed = this.#parseX(raw);
if (isFullVersion(parsed)) {
return ['>', new Version(raw, ...parsed)];
}
const z = this.includePrerelease ? '0' : undefined;
if (isXPatch(parsed)) {
// >1.2 => >=1.3.0
return [
'>=',
new Version(raw, parsed[MAJOR], parsed[MINOR] + 1, 0, z, undefined),
];
}
if (isXMinor(parsed)) {
// >1 => >=2.0.0
return [
'>=',
new Version(raw, parsed[MAJOR] + 1, 0, 0, z, undefined),
];
}
this.isNone = true;
this.tuples.length = 0;
return comparatorNone;
}
#xInclusiveMax(raw) {
const parsed = this.#parseX(raw);
return (isFullVersion(parsed) ? ['<=', new Version(raw, ...parsed)]
: isXPatch(parsed) ?
[
'<',
new Version(raw, parsed[MAJOR], parsed[MINOR] + 1, 0, '0', undefined),
]
: isXMinor(parsed) ?
[
'<',
new Version(raw, parsed[MAJOR] + 1, 0, 0, '0', undefined),
]
: this.#getComparatorAny());
}
#xExclusiveMax(raw) {
const z = this.includePrerelease ? '0' : undefined;
const [M = 0, m = 0, p = 0, pr = z, build] = this.#parseX(raw);
if (M === 0 && m === 0 && p === 0 && pr === '0') {
this.isNone = true;
this.tuples.length = 0;
return comparatorNone;
}
return ['<', new Version(raw, M, m, p, pr, build)];
}
#validXM(raw, m, p) {
assertMissing(m, raw, 'major');
assertMissing(p, raw, 'major');
if (m === '' || p === '') {
throw invalidComp(raw, `(Did you mean '*'?)`);
}
return [];
}
#validXm(raw, M, m, p) {
assertMissing(p, raw, 'major');
if (m === '' || p === '') {
throw invalidComp(raw, `(Did you mean '${M}'?)`);
}
return [assertNumber(M, raw, 'major')];
}
#validXp(raw, M, m, p) {
if (p === '') {
throw invalidComp(raw, `(Did you mean '${M}.${m}'?)`);
}
return [
assertNumber(M, raw, 'major'),
assertNumber(m, raw, 'minor'),
];
}
#validTuple(raw, M, m, p) {
return [
assertNumber(M, raw, 'major'),
assertNumber(m, raw, 'minor'),
assertNumber(p, raw, 'patch'),
];
}
#validXbuild(raw, M, m, p, pl) {
// build, no prerelease
const patch = p.substring(0, pl);
const build = p.substring(pl + 1);
if (!patch) {
throw invalidComp(raw, 'cannot specify build without patch');
}
if (!build) {
throw invalidComp(raw, `encountered '+', but no build value`);
}
return [
assertNumber(M, raw, 'major'),
assertNumber(m, raw, 'minor'),
assertNumber(patch, raw, 'patch'),
undefined,
build,
];
}
#validXpr(raw, M, m, p, hy) {
{
// prerelease, no build
const patch = p.substring(0, hy);
const pr = p.substring(hy + 1);
if (!patch) {
throw invalidComp(raw, 'cannot specify prerelease without patch');
}
if (!pr) {
throw invalidComp(raw, `encountered '-', but no prerelease value`);
}
return [
assertNumber(M, raw, 'major'),
assertNumber(m, raw, 'minor'),
assertNumber(patch, raw, 'patch'),
pr,
undefined,
];
}
}
#validXprbuild(raw, M, m, p, hy, pl) {
// both prerelease and build
const patch = p.substring(0, hy);
const pr = p.substring(hy + 1, pl);
const build = p.substring(pl + 1);
if (!patch) {
throw invalidComp(raw, 'cannot specify prerelease without patch');
}
if (!pr) {
throw invalidComp(raw, `encountered '-', but no prerelease value`);
}
if (!build) {
throw invalidComp(raw, `encountered '+', but no build value`);
}
return [
assertNumber(M, raw, 'major'),
assertNumber(m, raw, 'minor'),
assertNumber(patch, raw, 'patch'),
pr,
build,
];
}
// pull the relevant values out of an X-range or version
// return the fields for creating a Version object.
// only call once operator is stripped off
#parseX(raw) {
let [M, m, p] = fastSplit(raw, '.', 3);
let prune = 0;
while (M && preJunk.has(M.charAt(prune)))
prune++;
if (M !== undefined && prune !== 0)
M = M.substring(prune);
// the `|| !M` is so TS knows we've handled undefined
if (!M || isX(M))
return this.#validXM(raw, m, p);
if (!m || isX(m))
return this.#validXm(raw, M, m, p);
if (!p || isX(p))
return this.#validXp(raw, M, m, p);
const hy = p.indexOf('-');
const pl = p.indexOf('+');
if (pl === -1 && hy === -1)
return this.#validTuple(raw, M, m, p);
if (pl === -1)
return this.#validXpr(raw, M, m, p, hy);
if (hy === -1)
return this.#validXbuild(raw, M, m, p, pl);
return this.#validXprbuild(raw, M, m, p, hy, pl);
}
#parseHyphenRange(min, max) {
const minv = this.#xInclusiveMin(min);
const maxv = this.#xInclusiveMax(max);
const minAny = isAny(minv);
const maxAny = isAny(maxv);
if (minAny && maxAny)
this.tuples.push(this.#getComparatorAny());
else if (minAny)
this.tuples.push(maxv);
else if (maxAny)
this.tuples.push(minv);
else
this.tuples.push(minv, maxv);
}
#parse(comp) {
const first = comp.charAt(0);
const first2 = comp.substring(0, 2);
const v1 = comp.substring(1);
const v2 = comp.substring(2);
switch (first2) {
case '~>':
assertVersion(v2, comp);
return this.#parseTilde(v2);
case '>=':
assertVersion(v2, comp);
return this.tuples.push(this.#xInclusiveMin(v2));
case '<=':
assertVersion(v2, comp);
return this.tuples.push(this.#xInclusiveMax(v2));
}
switch (first) {
case '~':
assertVersion(v1, comp);
return this.#parseTilde(v1);
case '^':
assertVersion(v1, comp);
return this.#parseCaret(v1);
case '>':
assertVersion(v1, comp);
return this.tuples.push(this.#xExclusiveMin(v1));
case '<':
assertVersion(v1, comp);
return this.tuples.push(this.#xExclusiveMax(v1));
}
return this.#parseEq(comp);
}
#parseTilde(comp) {
const parsed = this.#parseX(comp);
if (isXMajor(parsed)) {
this.tuples.push(this.#getComparatorAny());
return;
}
const z = this.includePrerelease ? '0' : undefined;
if (isXMinor(parsed)) {
const [M] = parsed;
this.tuples.push(['>=', new Version(comp, M, 0, 0, z, undefined)], ['<', new Version(comp, M + 1, 0, 0, '0', undefined)]);
return;
}
if (isXPatch(parsed)) {
const [M, m] = parsed;
const z = this.includePrerelease ? '0' : undefined;
this.tuples.push(['>=', new Version(comp, M, m, 0, z, undefined)], ['<', new Version(comp, M, m + 1, 0, '0', undefined)]);
return;
}
const [M, m, p, pr = z, build] = parsed;
this.tuples.push(['>=', new Version(comp, M, m, p, pr, build)], ['<', new Version(comp, M, m + 1, 0, '0', build)]);
}
#parseCaret(comp) {
const min = this.#xInclusiveMin(comp);
if (isAny(min)) {
this.tuples.push(min);
return;
}
const minv = min[1];
if (minv.major !== 0) {
this.tuples.push(min, [
'<',
new Version(comp, minv.major + 1, 0, 0, '0', undefined),
]);
}
else if (minv.minor !== 0) {
this.tuples.push(min, [
'<',
new Version(comp, minv.major, minv.minor + 1, 0, '0', undefined),
]);
}
else if (!minv.prerelease?.length) {
this.tuples.push(['', minv]);
}
else {
this.tuples.push(min, [
'<',
new Version(comp, minv.major, minv.minor, minv.patch + 1, '0', undefined),
]);
}
}
#parseEq(comp) {
const parsed = this.#parseX(comp);
const z = this.includePrerelease ? '0' : undefined;
if (isFullVersion(parsed)) {
this.tuples.push(['', new Version(comp, ...parsed)]);
}
else if (isXMajor(parsed)) {
this.tuples.push(this.#getComparatorAny());
}
else if (isXMinor(parsed)) {
this.tuples.push([
'>=',
new Version(comp, parsed[MAJOR], 0, 0, z, undefined),
]);
this.tuples.push([
'<',
new Version(comp, parsed[MAJOR] + 1, 0, 0, '0', undefined),
]);
}
else if (isXPatch(parsed)) {
this.tuples.push([
'>=',
new Version(comp, parsed[MAJOR], parsed[MINOR], 0, z, undefined),
], [
'<',
new Version(comp, parsed[MAJOR], parsed[MINOR] + 1, 0, '0', undefined),
]);
}
}
/** return true if the version is a match for this comparator */
test(v) {
if (this.isNone)
return false;
const ip = this.includePrerelease;
const hasPR = !!v.prerelease?.length;
let prOK = ip || !hasPR;
for (const c of this.tuples) {
if (isAny(c)) {
continue;
}
const [op, cv] = c;
prOK ||= !!cv.prerelease?.length && v.tupleEquals(cv);
switch (op) {
case '':
if (!v.equals(cv))
return false;
continue;
case '>':
if (!v.greaterThan(cv))
return false;
continue;
case '>=':
if (!v.greaterThanEqual(cv))
return false;
continue;
case '<':
if (!v.lessThan(cv))
return false;
continue;
case '<=':
if (!v.lessThanEqual(cv))
return false;
continue;
}
}
// they all passed, so it can only fail for having a prerelease
// if we allow prereleases, or saw a matching tuple, that's ok.
return prOK;
}
#getComparatorAny() {
return this.includePrerelease ? comparatorAnyPR : comparatorAny;
}
}
const isAny = (c) => c === comparatorAny || c === comparatorAnyPR;
const comparatorAny = {
isAny: true,
toString: () => '*',
includePrerelease: false,
test: (v) => !v.prerelease?.length,
};
const comparatorAnyPR = {
isAny: true,
toString: () => '*',
includePrerelease: true,
test: (_) => true,
};
const comparatorNone = {
isNone: true,
toString: () => '<0.0.0-0',
includePrerelease: false,
test: (_) => false,
};
import { Range } from './range.ts';
import { Version } from './version.ts';
import type { IncrementType } from './version.ts';
export * from './comparator.ts';
export * from './range.ts';
export * from './version.ts';
/** Return the parsed version string, or `undefined` if invalid */
export declare const parse: (version: Version | string) => Version | undefined;
/** Return the parsed version range, or `undefined` if invalid */
export declare const parseRange: (range: Range | string, includePrerelease?: boolean) => Range | undefined;
/**
* return true if the version is valid
*
* Note: do not use this if you intend to immediately parse the version if it's
* valid. Just use {@link parse}, and guard the possible undefined value, or
* use `Version.parse(..)` to throw on invalid values.
*/
export declare const valid: (version: Version | string) => boolean;
/**
* return true if the range is valid
*
* Note: do not use this if you intend to immediately parse the range if it's
* valid. Just use {@link parseRange}, and guard the possible undefined value,
* or use `new Range(..)` to throw on invalid values.
*/
export declare const validRange: (range: Range | string) => boolean;
/**
* Return true if the version satisfies the range.
*/
export declare const satisfies: (version: Version | string, range: Range | string, includePrerelease?: boolean) => boolean;
/**
* Increment the specified part of the version, and return the resulting
* object. If a Version object is provided, it will be modified in-place.
*
* See {@link Version.inc} for full description.
*/
export declare const inc: (version: Version | string, part: IncrementType, prereleaseIdentifier?: string) => Version;
/**
* The method used by {@link sort}, exported for passing directly to
* `Array.sort`.
*
* Usage:
*
* ```ts
* import { sortMethod } from '@vltpkg/semver'
* const versions = ['1.2.3', '5.2.3', '2.3.4']
* console.log(versions.sort(sortMethod))
* // ['1.2.3', '2.3.4', '5.2.3']
* ```
*/
export declare const sortMethod: (a: Version | string, b: Version | string) => number;
/**
* Sort an array of version strings or objects in ascending SemVer precedence
* order (ie, lowest versions first).
*
* Invalid version strings are sorted to the end of the array in ascending
* alphabetical order.
*
* Note: when using this method, the list is cloned prior to sorting, to
* prevent surprising mutation. To sort the list in place, see
* {@link sortMethod}.
*/
export declare const sort: <T extends Version | string = string | Version>(list: T[]) => T[];
/**
* Sort an array of version strings or objects in descending SemVer
* precedence order (ie, highest versions first).
*
* Invalid version strings are sorted to the end of the array in ascending
* alphabetical order.
*
* Note: when using this method, the list is cloned prior to sorting, to
* prevent surprising mutation. To sort the list in place, see
* {@link rsortMethod}.
*/
export declare const rsort: <T extends Version | string = string | Version>(list: T[]) => T[];
/**
* The method used by {@link rsort}, exported for passing directly to
* `Array.sort`.
*
* Usage:
*
* ```ts
* import { rsortMethod } from '@vltpkg/semver'
* const versions = ['1.2.3', '5.2.3', '2.3.4']
* console.log(versions.sort(rsortMethod))
* // ['5.2.3', '2.3.4', '1.2.3']
* ```
*/
export declare const rsortMethod: (a: Version | string, b: Version | string) => number;
/**
* Method used by {@link filter}, for use in `Array.filter` directly.
*
* Usage:
*
* ```ts
* import { filterMethod } from '@vltpkg/semver'
* const versions = ['1.2.3', '5.2.3', '2.3.4']
* console.log(versions.filter(filterMethod('>=2.x')))
* // ['5.2.3', '2.3.4']
* ```
*/
export declare const filterMethod: (range: Range | string, includePrerelease?: boolean) => ((version: Version | string) => boolean);
/**
* Filter a list of versions to find all that match a given range.
*/
export declare const filter: <T extends Version | string = string | Version>(list: T[], range: Range | string, includePrerelease?: boolean) => T[];
/**
* Find the highest-precedence match for a range within a list of versions
*
* Returns `undefined` if no match was found.
*/
export declare const highest: (list: (Version | string)[], range: Range | string, includePrerelease?: boolean) => Version | undefined;
/**
* Faster form of {@link highest}, for use when the list is sorted
* in precedence order (lower-precedence versions first).
*
* Note: This stops at the first match, and will produce incorrect results
* when the list is not properly sorted!
*/
export declare const sortedHighest: (list: (Version | string)[], range: Range | string, includePrerelease?: boolean) => Version | undefined;
/**
* Faster form of {@link highest}, for use when the list is sorted
* in reverse precedence order (higher-precedence versions first).
*
* Note: This stops at the first match, and will produce incorrect results
* when the list is not properly sorted!
*/
export declare const rsortedHighest: (list: (Version | string)[], range: Range | string, includePrerelease?: boolean) => Version | undefined;
/**
* Find the lowest-precedence match for a range within a list of versions
*
* Returns `undefined` if no match was found.
*/
export declare const lowest: (list: (Version | string)[], range: Range | string, includePrerelease?: boolean) => Version | undefined;
/**
* Faster form of {@link lowest}, for use when the list is sorted
* in precedence order (lower-precedence versions first).
*
* Note: This stops at the first match, and will produce incorrect results
* when the list is not properly sorted!
*/
export declare const sortedLowest: (list: (Version | string)[], range: Range | string, includePrerelease?: boolean) => Version | undefined;
/**
* Faster form of {@link lowest}, for use when the list is sorted
* in reverse precedence order (higher-precedence versions first).
*
* Note: This stops at the first match, and will produce incorrect results
* when the list is not properly sorted!
*/
export declare const rsortedLowest: (list: (Version | string)[], range: Range | string, includePrerelease?: boolean) => Version | undefined;
/**
* Same as {@link sortMethod}, but throws if either version is not valid.
* 1 if versionA is higher precedence than versionB
* -1 if versionA is lower precedence than versionB
* 0 if they have equal precedence
*/
export declare const compare: (versionA: Version | string, versionB: Version | string) => 0 | 1 | -1;
/**
* Inverse of {@link compare}
*
* Same as {@link rsortMethod}, but throws if either version is not valid.
*
* -1 if versionA is higher precedence than versionB
* 1 if versionA is lower precedence than versionB
* 0 if they have equal precedence
*/
export declare const rcompare: (versionA: Version | string, versionB: Version | string) => 0 | 1 | -1;
/** true if versionA is > versionB. throws on invalid values */
export declare const gt: (versionA: Version | string, versionB: Version | string) => boolean;
/** true if versionA is >= versionB. throws on invalid values */
export declare const gte: (versionA: Version | string, versionB: Version | string) => boolean;
/** true if versionA is < versionB. throws on invalid values */
export declare const lt: (versionA: Version | string, versionB: Version | string) => boolean;
/** true if versionA is &lt;= versionB. throws on invalid values */
export declare const lte: (versionA: Version | string, versionB: Version | string) => boolean;
/** true if versionA is not equal to versionB. throws on invalid values */
export declare const neq: (versionA: Version | string, versionB: Version | string) => boolean;
/** true if versionA is equal to versionB. throws on invalid values */
export declare const eq: (versionA: Version | string, versionB: Version | string) => boolean;
/** extract the major version number, or undefined if invalid */
export declare const major: (version: Version | string) => number | undefined;
/** extract the minor version number, or undefined if invalid */
export declare const minor: (version: Version | string) => number | undefined;
/** extract the patch version number, or undefined if invalid */
export declare const patch: (version: Version | string) => number | undefined;
/**
* extract the list of prerelease identifiers, or undefined if the version
* is invalid. If no prerelease identifiers are present, returns `[]`.
*/
export declare const prerelease: (version: Version | string) => (string | number)[] | undefined;
/**
* extract the list of build identifiers, or undefined if the version
* is invalid. If no build identifiers are present, returns `[]`.
*/
export declare const build: (version: Version | string) => string[] | undefined;
/** return all versions that do not have any prerelease identifiers */
export declare const stable: <T extends Version | string = string | Version>(versions: T[]) => T[];
/**
* Return true if the range r1 intersects any of the ranges r2
* r1 and r2 are either Range objects or range strings.
* Returns true if any version would satisfy both ranges.
*/
export declare const intersects: (r1: Range | string, r2: Range | string, includePrerelease?: boolean) => boolean;
import { Range } from "./range.js";
import { Version } from "./version.js";
import { syntaxError } from '@vltpkg/error-cause';
export * from "./comparator.js";
export * from "./range.js";
export * from "./version.js";
/** Return the parsed version string, or `undefined` if invalid */
export const parse = (version) => {
if (!(typeof version === 'string'))
return version;
try {
return Version.parse(version);
}
catch {
return undefined;
}
};
/** Return the parsed version range, or `undefined` if invalid */
export const parseRange = (range, includePrerelease = false) => {
if (typeof range === 'object') {
if (range.includePrerelease === includePrerelease)
return range;
range = range.raw;
}
try {
return new Range(range, includePrerelease);
}
catch {
return undefined;
}
};
/**
* return true if the version is valid
*
* Note: do not use this if you intend to immediately parse the version if it's
* valid. Just use {@link parse}, and guard the possible undefined value, or
* use `Version.parse(..)` to throw on invalid values.
*/
export const valid = (version) => !!parse(version);
/**
* return true if the range is valid
*
* Note: do not use this if you intend to immediately parse the range if it's
* valid. Just use {@link parseRange}, and guard the possible undefined value,
* or use `new Range(..)` to throw on invalid values.
*/
export const validRange = (range) => !!parseRange(range);
/**
* Return true if the version satisfies the range.
*/
export const satisfies = (version, range, includePrerelease = false) => {
if (typeof version === 'string') {
const parsed = parse(version);
if (!parsed)
return false;
version = parsed;
}
if (typeof range === 'string') {
const parsed = parseRange(range, includePrerelease);
if (!parsed)
return false;
range = parsed;
}
return version.satisfies(range);
};
/**
* Increment the specified part of the version, and return the resulting
* object. If a Version object is provided, it will be modified in-place.
*
* See {@link Version.inc} for full description.
*/
export const inc = (version, part, prereleaseIdentifier) => (typeof version === 'string' ?
Version.parse(version)
: version).inc(part, prereleaseIdentifier);
/**
* The method used by {@link sort}, exported for passing directly to
* `Array.sort`.
*
* Usage:
*
* ```ts
* import { sortMethod } from '@vltpkg/semver'
* const versions = ['1.2.3', '5.2.3', '2.3.4']
* console.log(versions.sort(sortMethod))
* // ['1.2.3', '2.3.4', '5.2.3']
* ```
*/
export const sortMethod = (a, b) => {
const pa = parse(a);
const pb = parse(b);
/* c8 ignore start - nondeterministic */
if (!pa && !pb)
return String(a).localeCompare(String(b), 'en');
if (!pa)
return 1;
if (!pb)
return -1;
/* c8 ignore stop */
return pa.compare(pb);
};
/**
* Sort an array of version strings or objects in ascending SemVer precedence
* order (ie, lowest versions first).
*
* Invalid version strings are sorted to the end of the array in ascending
* alphabetical order.
*
* Note: when using this method, the list is cloned prior to sorting, to
* prevent surprising mutation. To sort the list in place, see
* {@link sortMethod}.
*/
export const sort = (list) => list.slice().sort(sortMethod);
/**
* Sort an array of version strings or objects in descending SemVer
* precedence order (ie, highest versions first).
*
* Invalid version strings are sorted to the end of the array in ascending
* alphabetical order.
*
* Note: when using this method, the list is cloned prior to sorting, to
* prevent surprising mutation. To sort the list in place, see
* {@link rsortMethod}.
*/
export const rsort = (list) => list.slice().sort(rsortMethod);
/**
* The method used by {@link rsort}, exported for passing directly to
* `Array.sort`.
*
* Usage:
*
* ```ts
* import { rsortMethod } from '@vltpkg/semver'
* const versions = ['1.2.3', '5.2.3', '2.3.4']
* console.log(versions.sort(rsortMethod))
* // ['5.2.3', '2.3.4', '1.2.3']
* ```
*/
export const rsortMethod = (a, b) => {
const pa = parse(a);
const pb = parse(b);
/* c8 ignore start - nondeterministic */
if (!pa && !pb)
return String(a).localeCompare(String(b), 'en');
if (!pa)
return 1;
if (!pb)
return -1;
/* c8 ignore stop */
return pa.rcompare(pb);
};
/**
* Method used by {@link filter}, for use in `Array.filter` directly.
*
* Usage:
*
* ```ts
* import { filterMethod } from '@vltpkg/semver'
* const versions = ['1.2.3', '5.2.3', '2.3.4']
* console.log(versions.filter(filterMethod('>=2.x')))
* // ['5.2.3', '2.3.4']
* ```
*/
export const filterMethod = (range, includePrerelease = false) => {
const r = parseRange(range, includePrerelease);
return !r ?
() => false
: version => satisfies(version, r, r.includePrerelease);
};
/**
* Filter a list of versions to find all that match a given range.
*/
export const filter = (list, range, includePrerelease = false) => list.filter(filterMethod(range, includePrerelease));
/**
* Find the highest-precedence match for a range within a list of versions
*
* Returns `undefined` if no match was found.
*/
export const highest = (list, range, includePrerelease = false) => {
const r = parseRange(range, includePrerelease);
if (!r)
return undefined;
let max = undefined;
for (const v of list) {
const version = parse(v);
if (!version)
continue;
if (!version.satisfies(r))
continue;
if (!max)
max = version;
else if (version.greaterThan(max))
max = version;
}
return max;
};
/**
* Faster form of {@link highest}, for use when the list is sorted
* in precedence order (lower-precedence versions first).
*
* Note: This stops at the first match, and will produce incorrect results
* when the list is not properly sorted!
*/
export const sortedHighest = (list, range, includePrerelease = false) => {
const r = parseRange(range, includePrerelease);
if (!r)
return undefined;
for (let i = list.length - 1; i >= 0; i--) {
const v = list[i];
/* c8 ignore next */
if (!v)
continue;
const version = parse(v);
if (!version)
continue;
if (!version.satisfies(r))
continue;
return version;
}
};
/**
* Faster form of {@link highest}, for use when the list is sorted
* in reverse precedence order (higher-precedence versions first).
*
* Note: This stops at the first match, and will produce incorrect results
* when the list is not properly sorted!
*/
export const rsortedHighest = (list, range, includePrerelease = false) => {
const r = parseRange(range, includePrerelease);
if (!r)
return undefined;
for (const v of list) {
const version = parse(v);
if (!version)
continue;
if (!version.satisfies(r))
continue;
return version;
}
};
/**
* Find the lowest-precedence match for a range within a list of versions
*
* Returns `undefined` if no match was found.
*/
export const lowest = (list, range, includePrerelease = false) => {
const r = parseRange(range, includePrerelease);
if (!r)
return undefined;
let min = undefined;
for (const v of list) {
const version = parse(v);
if (!version)
continue;
if (!version.satisfies(r))
continue;
if (!min)
min = version;
else if (version.lessThan(min))
min = version;
}
return min;
};
/**
* Faster form of {@link lowest}, for use when the list is sorted
* in precedence order (lower-precedence versions first).
*
* Note: This stops at the first match, and will produce incorrect results
* when the list is not properly sorted!
*/
export const sortedLowest = (list, range, includePrerelease = false) => rsortedHighest(list, range, includePrerelease);
/**
* Faster form of {@link lowest}, for use when the list is sorted
* in reverse precedence order (higher-precedence versions first).
*
* Note: This stops at the first match, and will produce incorrect results
* when the list is not properly sorted!
*/
export const rsortedLowest = (list, range, includePrerelease = false) => sortedHighest(list, range, includePrerelease);
/**
* Same as {@link sortMethod}, but throws if either version is not valid.
* 1 if versionA is higher precedence than versionB
* -1 if versionA is lower precedence than versionB
* 0 if they have equal precedence
*/
export const compare = (versionA, versionB) => {
const a = parse(versionA);
if (!a) {
throw syntaxError('invalid version', { found: versionA });
}
const b = parse(versionB);
if (!b) {
throw syntaxError('invalid version', { found: versionB });
}
return a.compare(b);
};
/**
* Inverse of {@link compare}
*
* Same as {@link rsortMethod}, but throws if either version is not valid.
*
* -1 if versionA is higher precedence than versionB
* 1 if versionA is lower precedence than versionB
* 0 if they have equal precedence
*/
export const rcompare = (versionA, versionB) => compare(versionB, versionA);
/** true if versionA is > versionB. throws on invalid values */
export const gt = (versionA, versionB) => compare(versionA, versionB) > 0;
/** true if versionA is >= versionB. throws on invalid values */
export const gte = (versionA, versionB) => compare(versionA, versionB) >= 0;
/** true if versionA is < versionB. throws on invalid values */
export const lt = (versionA, versionB) => compare(versionA, versionB) < 0;
/** true if versionA is &lt;= versionB. throws on invalid values */
export const lte = (versionA, versionB) => compare(versionA, versionB) <= 0;
/** true if versionA is not equal to versionB. throws on invalid values */
export const neq = (versionA, versionB) => compare(versionA, versionB) !== 0;
/** true if versionA is equal to versionB. throws on invalid values */
export const eq = (versionA, versionB) => compare(versionA, versionB) === 0;
/** extract the major version number, or undefined if invalid */
export const major = (version) => parse(version)?.major;
/** extract the minor version number, or undefined if invalid */
export const minor = (version) => parse(version)?.minor;
/** extract the patch version number, or undefined if invalid */
export const patch = (version) => parse(version)?.patch;
/**
* extract the list of prerelease identifiers, or undefined if the version
* is invalid. If no prerelease identifiers are present, returns `[]`.
*/
export const prerelease = (version) => {
const p = parse(version);
if (!p)
return undefined;
return p.prerelease ?? [];
};
/**
* extract the list of build identifiers, or undefined if the version
* is invalid. If no build identifiers are present, returns `[]`.
*/
export const build = (version) => {
const p = parse(version);
if (!p)
return undefined;
return p.build ?? [];
};
/** return all versions that do not have any prerelease identifiers */
export const stable = (versions) => versions.filter(v => {
const p = parse(v);
if (!p)
return false;
return !p.prerelease?.length;
});
/**
* Return true if the range r1 intersects any of the ranges r2
* r1 and r2 are either Range objects or range strings.
* Returns true if any version would satisfy both ranges.
*/
export const intersects = (r1, r2, includePrerelease) => {
const range1 = typeof r1 === 'string' ? parseRange(r1, includePrerelease) : r1;
const range2 = typeof r2 === 'string' ? parseRange(r2, includePrerelease) : r2;
if (!range1 || !range2)
return false;
// If either range is 'any', they intersect
if (range1.isAny || range2.isAny)
return true;
// Check if any set from range1 intersects with any set from range2
return range1.set.some(set1 => range2.set.some(set2 => intersectComparators(set1, set2)));
};
/**
* Check if two comparators can be satisfied simultaneously
*/
const intersectComparators = (comp1, comp2) => {
// Collect all tuples from both comparators
const tuples1 = comp1.tuples.filter((t) => Array.isArray(t));
const tuples2 = comp2.tuples.filter((t) => Array.isArray(t));
// Check if there's a satisfiable combination
return satisfiableRange(tuples1.concat(tuples2));
};
/**
* Check if a set of operator-version tuples represents a satisfiable range
* This is a simplified implementation that handles the most common cases
*/
const satisfiableRange = (tuples) => {
// Find bounds
let lowerBound = null;
let lowerInclusive = false;
let upperBound = null;
let upperInclusive = false;
let hasExact = null;
for (const [op, ver] of tuples) {
switch (op) {
case '':
// Exact match - if we already have a different exact match, no intersection
if (hasExact && !eq(hasExact, ver))
return false;
hasExact = ver;
break;
case '>=':
/* c8 ignore start */
if (!lowerBound ||
gt(ver, lowerBound) ||
(eq(ver, lowerBound) && !lowerInclusive)) {
lowerBound = ver;
lowerInclusive = true;
}
/* c8 ignore stop */
break;
case '>':
if (!lowerBound || gt(ver, lowerBound)) {
lowerBound = ver;
lowerInclusive = false;
}
break;
case '<=':
if (!upperBound || lt(ver, upperBound)) {
upperBound = ver;
upperInclusive = true;
}
break;
case '<':
/* c8 ignore start */
if (!upperBound ||
lt(ver, upperBound) ||
eq(ver, upperBound)) {
upperBound = ver;
upperInclusive = false;
}
/* c8 ignore stop */
break;
}
}
// If we have an exact match, check if it's within bounds
if (hasExact) {
if (lowerBound) {
if (lowerInclusive ?
lt(hasExact, lowerBound)
: lte(hasExact, lowerBound)) {
return false;
}
}
if (upperBound) {
if (upperInclusive ?
gt(hasExact, upperBound)
: gte(hasExact, upperBound)) {
return false;
}
}
return true;
}
// Check if lower bound is less than or equal to upper bound
if (lowerBound && upperBound) {
if (gt(lowerBound, upperBound))
return false;
if (eq(lowerBound, upperBound) &&
!(lowerInclusive && upperInclusive))
return false;
}
return true;
};
import { Comparator } from './comparator.ts';
import type { Version } from './version.ts';
export declare const isRange: (range: unknown) => range is Range;
/**
* A representation of a semver range, used to test versions.
*
* Includes a set of comparators representing the `||`-separated
* sections of the range string
*/
export declare class Range {
#private;
/** raw string used to create this Range */
raw: string;
/** true if the range is `*` */
isAny: boolean;
/** true if the range is a single semver version */
isSingle: boolean;
/** true if the range cannot match anything */
/**
* set of {@link Comparator} objects representing the `||`-separated sections
* of the range. If at least one of these matches, then the version is a
* match.
*/
set: Comparator[];
/** true if all prerelease versions should be included */
includePrerelease: boolean;
constructor(range: string, includePrerelease?: boolean);
/**
* test a {@link Version} against the range
*/
test(v: Version): boolean;
/** return the simplified canonical form of this range */
toString(): string;
}
import { fastSplit } from '@vltpkg/fast-split';
import { Comparator } from "./comparator.js";
import { asError } from '@vltpkg/types';
export const isRange = (range) => {
return (range instanceof Range ||
(typeof range === 'object' &&
range !== null &&
'raw' in range &&
typeof range.raw === 'string' &&
'set' in range &&
Array.isArray(range.set) &&
range.set.every(c => c instanceof Comparator)));
};
/**
* A representation of a semver range, used to test versions.
*
* Includes a set of comparators representing the `||`-separated
* sections of the range string
*/
export class Range {
/** raw string used to create this Range */
raw;
/** true if the range is `*` */
isAny;
/** true if the range is a single semver version */
isSingle;
/** true if the range cannot match anything */
/**
* set of {@link Comparator} objects representing the `||`-separated sections
* of the range. If at least one of these matches, then the version is a
* match.
*/
set = [];
/** true if all prerelease versions should be included */
includePrerelease;
/** cached toString */
#toString;
constructor(range, includePrerelease = false) {
this.raw = range;
this.includePrerelease = includePrerelease;
this.isAny = false;
let isFirst = true;
this.isSingle = false;
const comparatorErrors = [];
fastSplit(range, '||', -1, part => {
if (this.isAny)
return;
const cmp = this.#maybeComparator(part, this.includePrerelease);
if (cmp instanceof Error) {
comparatorErrors.push(cmp);
return;
}
if (cmp.isAny) {
this.set = [cmp];
this.isAny = true;
return;
}
this.set.push(cmp);
if (!isFirst)
this.isSingle = false;
else if (Array.isArray(cmp.tuples) &&
cmp.tuples.length === 1 &&
Array.isArray(cmp.tuples[0]) &&
cmp.tuples[0][0] === '') {
this.isSingle = true;
}
isFirst = false;
});
if (!this.set.length && comparatorErrors.length) {
if (comparatorErrors.length === 1 && comparatorErrors[0]) {
throw comparatorErrors[0];
}
throw new AggregateError(comparatorErrors);
}
}
#maybeComparator(part, includePrerelease) {
try {
return new Comparator(part, includePrerelease);
}
catch (er) {
return asError(er);
}
}
/**
* test a {@link Version} against the range
*/
test(v) {
return this.set.some(c => c.test(v));
}
/** return the simplified canonical form of this range */
toString() {
if (this.#toString)
return this.#toString;
if (this.isSingle) {
this.#toString = String(this.set[0]);
return this.#toString;
}
this.#toString = this.set.map(c => String(c)).join(' || ');
return this.#toString;
}
}
import type { Range } from './range.ts';
/**
* Values of valid increment types.
*/
export declare const versionIncrements: readonly ["major", "minor", "patch", "pre", "premajor", "preminor", "prepatch", "prerelease"];
/**
* Types of incrementing supported by {@link Version#inc}
*/
export type IncrementType = (typeof versionIncrements)[number];
/**
* A parsed object representation of a SemVer version string
*
* This is a bit less forgiving than node-semver, in that prerelease versions
* MUST start with '-'. Otherwise, the allowed syntax is identical.
*/
export declare class Version {
/** raw string provided to create this Version */
raw: string;
/** major version number */
major: number;
/** minor version number */
minor: number;
/** patch version number */
patch: number;
/**
* List of `'.'`-separated strings and numbers indicating that this
* version is a prerelease.
*
* This is undefined if the version does not have a prerelease section.
*/
prerelease?: (number | string)[];
/**
* List of `'.'`-separated strings in the `build` section.
*
* This is undefined if the version does not have a build.
*/
build?: string[];
/** Canonical strict form of this version */
toString(): string;
/** Generate a `Version` object from a SemVer string */
static parse(version: string): Version;
constructor(version: string, major: number, minor: number, patch: number, prerelease: string | undefined, build: string | undefined);
/**
* Return 1 if this is > the provided version, -1 if we're less, or 0 if
* they are equal.
*
* No special handling for prerelease versions, this is just a precedence
* comparison.
*
* This can be used to sort a list of versions by precedence:
*
* ```ts
* const versions: Version[] = getVersionsSomehow()
* const sorted = versions.sort((a, b) => a.compare(b))
* ```
*/
compare(v: Version): -1 | 0 | 1;
/**
* The inverse of compare, for sorting version lists in reverse order
*/
rcompare(v: Version): number;
/** true if this version is > the argument */
greaterThan(v: Version): boolean;
/** true if this version is >= the argument */
greaterThanEqual(v: Version): boolean;
/** true if this version is < the argument */
lessThan(v: Version): boolean;
/** true if this version is &lt;= the argument */
lessThanEqual(v: Version): boolean;
/** true if these two versions have equal SemVer precedence */
equals(v: Version): boolean;
/** just compare the M.m.p parts of the version */
tupleEquals(v: Version): boolean;
/** true if this version satisfies the range */
satisfies(r: Range): boolean;
/**
* Increment the version in place, in the manner specified.
*
* Part behaviors:
*
* - `'major'` If the version is a `M.0.0-...` version with a prerelease, then
* simply drop the prerelease. Otherwise, set the minor and patch to 0, and
* increment the major. So `1.0.0-beta` becomes `1.0.0`, and `1.2.3` becomes
* `2.0.0`
*
* - `'minor'` If the version is a `M.m.0-...` version with a prerelease, then
* simply drop the prerelease. Otherwise, set the patch to 0, and increment the
* minor. So `1.2.0-beta` becomes `1.2.0`, and `1.2.3` becomes `1.3.0`.
*
* - `'patch'` If the version has a prerelease, then simply drop the
* prerelease. Otherwise, increment the patch value. So `1.2.3-beta` becomes
* `1.2.3` and `1.2.3` becomes `1.2.4`.
*
* - `'premajor'` Set the patch and minor versions to `0`, increment the major
* version, and add a prerelease, using the optional identifier.
*
* - `'preminor'` Set the patch version to `0`, increment the minor version,
* and add a prerelease, using the optional identifier.
*
* - `'prepatch'` If a prerelease is already present, increment the patch
* version, otherwise leave it untouched, and add a prerelease, using the
* optional identifier.
*
* - `'prerelease'` If a prerelease version is present, then behave the same as
* `'prepatch'`. Otherwise, add a prerelease, using the optional identifier.
*
* - `'pre'` This is mostly for use by the other prerelease incrementers.
*
* - If a prerelease identifier is provided:
*
* Update that named portion of the prerelease. For example,
* `inc('1.2.3-beta.4', 'pre', 'beta')` would result in `1.2.3-beta.5`.
*
* If there is no prerelease identifier by that name, then replace the
* prerelease with `[name]`. So `inc('1.2.3-alpha.4', 'pre', 'beta')`
* would result in `1.2.3-beta`.
*
* If the prerelease identifer is present, but has no numeric value
* following it, then add `0`. So `inc('1.2.3-beta', 'pre', 'beta')`
* would result in `1.2.3-beta.0`.
*
* - If no prerelease identifier is provided:
*
* If there is no current prerelease, then set the prerelease to `0`. So,
* `inc('1.2.3', 'pre')` becomes `1.2.3-0`.
*
* If the last item in the prerelease is numeric, then increment it. So,
* `inc('1.2.3-beta.3', 'pre')` becomes `1.2.3-beta.4`.
*/
inc(part: IncrementType, prereleaseIdentifier?: string): this;
}
import { syntaxError, typeError } from '@vltpkg/error-cause';
import { fastSplit } from '@vltpkg/fast-split';
const maybeNumber = (s) => {
if (!/^[0-9]+$/.test(s))
return s;
const n = Number(s);
return n <= Number.MAX_SAFE_INTEGER ? n : s;
};
const safeNumber = (s, version, field) => {
const n = Number(s);
if (n > Number.MAX_SAFE_INTEGER) {
throw invalidVersion(version, `invalid ${field}, must be <= ${Number.MAX_SAFE_INTEGER}`);
}
return n;
};
const re = {
prefix: /^[ v=]+/,
main: /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)/,
prerelease: /-([0-9a-zA-Z_.-]+)(?:$|\+)/,
build: /\+([0-9a-zA-Z_.-]+)$/,
full: /^[ v=]*(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9a-zA-Z_.-]+))?(?:\+([0-9a-zA-Z_.-]+))?$/,
};
const invalidVersion = (version, message) => {
const er = syntaxError(`invalid version: ${message}`, { version }, Version);
return er;
};
/**
* Values of valid increment types.
*/
export const versionIncrements = [
'major',
'minor',
'patch',
'pre',
'premajor',
'preminor',
'prepatch',
'prerelease',
];
/**
* A parsed object representation of a SemVer version string
*
* This is a bit less forgiving than node-semver, in that prerelease versions
* MUST start with '-'. Otherwise, the allowed syntax is identical.
*/
export class Version {
/** raw string provided to create this Version */
raw;
/** major version number */
major;
/** minor version number */
minor;
/** patch version number */
patch;
/**
* List of `'.'`-separated strings and numbers indicating that this
* version is a prerelease.
*
* This is undefined if the version does not have a prerelease section.
*/
prerelease;
/**
* List of `'.'`-separated strings in the `build` section.
*
* This is undefined if the version does not have a build.
*/
build;
/** Canonical strict form of this version */
toString() {
return `${this.major}.${this.minor}.${this.patch}${this.prerelease ? '-' + this.prerelease.join('.') : ''}${this.build ? '+' + this.build.join('.') : ''}`;
}
/** Generate a `Version` object from a SemVer string */
static parse(version) {
version = version.replace(re.prefix, '').trim();
if (version.length > 256) {
throw invalidVersion(version, 'must be less than 256 characters');
}
const parsed = re.full.exec(version);
if (!parsed) {
const main = re.main.exec(version);
if (!main) {
throw invalidVersion(version, 'no Major.minor.patch tuple present');
}
else {
throw invalidVersion(version, 'invalid build or patch section');
}
}
const [_, major_, minor_, patch_, prerelease, build] = parsed;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const major = safeNumber(major_, version, 'major');
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const minor = safeNumber(minor_, version, 'minor');
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const patch = safeNumber(patch_, version, 'patch');
return new Version(version, major, minor, patch, prerelease, build);
}
constructor(version, major, minor, patch, prerelease, build) {
this.raw = version;
this.major = major;
this.minor = minor;
this.patch = patch;
// has prerelease and/or build
if (prerelease) {
this.prerelease = fastSplit(prerelease, '.', -1, c => {
if (!c) {
throw invalidVersion(version, 'invalid prerelease, empty identifiers not allowed');
}
return maybeNumber(c);
});
}
if (build) {
this.build = fastSplit(build, '.', -1, c => {
if (!c) {
throw invalidVersion(version, 'invalid build metadata, empty identifiers not allowed');
}
});
}
}
/**
* Return 1 if this is > the provided version, -1 if we're less, or 0 if
* they are equal.
*
* No special handling for prerelease versions, this is just a precedence
* comparison.
*
* This can be used to sort a list of versions by precedence:
*
* ```ts
* const versions: Version[] = getVersionsSomehow()
* const sorted = versions.sort((a, b) => a.compare(b))
* ```
*/
compare(v) {
if (this.major > v.major)
return 1;
if (this.major < v.major)
return -1;
if (this.minor > v.minor)
return 1;
if (this.minor < v.minor)
return -1;
if (this.patch > v.patch)
return 1;
if (this.patch < v.patch)
return -1;
// main tuple is equal now
// if the version has no pr, we're definitely less than or equal to
if (!v.prerelease?.length)
return !this.prerelease?.length ? 0 : -1;
// v has a pr. if we don't, we're > it
if (!this.prerelease?.length)
return 1;
// we both have prereleases
const len = Math.max(this.prerelease.length, v.prerelease.length);
const me = this.prerelease;
const thee = v.prerelease;
for (let i = 0; i < len; i++) {
const m = me[i];
const t = thee[i];
if (m === t)
continue;
// having a field is > not having it
if (t === undefined)
return 1;
if (m === undefined)
return -1;
// string parts are higher precedence than
if (typeof m !== typeof t) {
return typeof m === 'string' ? 1 : -1;
}
return m > t ? 1 : -1;
}
return 0;
}
/**
* The inverse of compare, for sorting version lists in reverse order
*/
rcompare(v) {
return -1 * this.compare(v);
}
/** true if this version is > the argument */
greaterThan(v) {
return this.compare(v) === 1;
}
/** true if this version is >= the argument */
greaterThanEqual(v) {
return this.compare(v) > -1;
}
/** true if this version is < the argument */
lessThan(v) {
return this.compare(v) === -1;
}
/** true if this version is &lt;= the argument */
lessThanEqual(v) {
return this.compare(v) < 1;
}
/** true if these two versions have equal SemVer precedence */
equals(v) {
return this.compare(v) === 0;
}
/** just compare the M.m.p parts of the version */
tupleEquals(v) {
return (this.major === v.major &&
this.minor === v.minor &&
this.patch === v.patch);
}
/** true if this version satisfies the range */
satisfies(r) {
return r.test(this);
}
/**
* Increment the version in place, in the manner specified.
*
* Part behaviors:
*
* - `'major'` If the version is a `M.0.0-...` version with a prerelease, then
* simply drop the prerelease. Otherwise, set the minor and patch to 0, and
* increment the major. So `1.0.0-beta` becomes `1.0.0`, and `1.2.3` becomes
* `2.0.0`
*
* - `'minor'` If the version is a `M.m.0-...` version with a prerelease, then
* simply drop the prerelease. Otherwise, set the patch to 0, and increment the
* minor. So `1.2.0-beta` becomes `1.2.0`, and `1.2.3` becomes `1.3.0`.
*
* - `'patch'` If the version has a prerelease, then simply drop the
* prerelease. Otherwise, increment the patch value. So `1.2.3-beta` becomes
* `1.2.3` and `1.2.3` becomes `1.2.4`.
*
* - `'premajor'` Set the patch and minor versions to `0`, increment the major
* version, and add a prerelease, using the optional identifier.
*
* - `'preminor'` Set the patch version to `0`, increment the minor version,
* and add a prerelease, using the optional identifier.
*
* - `'prepatch'` If a prerelease is already present, increment the patch
* version, otherwise leave it untouched, and add a prerelease, using the
* optional identifier.
*
* - `'prerelease'` If a prerelease version is present, then behave the same as
* `'prepatch'`. Otherwise, add a prerelease, using the optional identifier.
*
* - `'pre'` This is mostly for use by the other prerelease incrementers.
*
* - If a prerelease identifier is provided:
*
* Update that named portion of the prerelease. For example,
* `inc('1.2.3-beta.4', 'pre', 'beta')` would result in `1.2.3-beta.5`.
*
* If there is no prerelease identifier by that name, then replace the
* prerelease with `[name]`. So `inc('1.2.3-alpha.4', 'pre', 'beta')`
* would result in `1.2.3-beta`.
*
* If the prerelease identifer is present, but has no numeric value
* following it, then add `0`. So `inc('1.2.3-beta', 'pre', 'beta')`
* would result in `1.2.3-beta.0`.
*
* - If no prerelease identifier is provided:
*
* If there is no current prerelease, then set the prerelease to `0`. So,
* `inc('1.2.3', 'pre')` becomes `1.2.3-0`.
*
* If the last item in the prerelease is numeric, then increment it. So,
* `inc('1.2.3-beta.3', 'pre')` becomes `1.2.3-beta.4`.
*/
inc(part, prereleaseIdentifier) {
switch (part) {
case 'premajor':
this.prerelease = undefined;
this.patch = 0;
this.minor = 0;
this.major++;
this.inc('pre', prereleaseIdentifier);
break;
case 'preminor':
this.prerelease = undefined;
this.patch = 0;
this.minor++;
this.inc('pre', prereleaseIdentifier);
break;
case 'prepatch':
this.prerelease = undefined;
this.inc('patch');
this.inc('pre', prereleaseIdentifier);
break;
case 'prerelease':
if (!this.prerelease?.length)
this.inc('patch', prereleaseIdentifier);
this.inc('pre', prereleaseIdentifier);
break;
case 'pre': {
// this is a bit different than node-semver's logic, but simpler
// always do zero-based incrementing, and either bump the existing
// numeric pr value, or add a `.0` after the identifier.
if (!prereleaseIdentifier) {
if (!this.prerelease?.length) {
this.prerelease = [0];
break;
}
const last = this.prerelease[this.prerelease.length - 1];
if (typeof last === 'number') {
this.prerelease[this.prerelease.length - 1] = last + 1;
}
else {
this.prerelease.push(0);
}
break;
}
if (!this.prerelease?.length) {
this.prerelease = [prereleaseIdentifier];
break;
}
const i = this.prerelease.indexOf(maybeNumber(prereleaseIdentifier));
if (i === -1) {
this.prerelease = [prereleaseIdentifier];
break;
}
const baseValue = this.prerelease[i + 1];
if (typeof baseValue === 'number') {
this.prerelease[i + 1] = baseValue + 1;
break;
}
if (i === this.prerelease.length - 1) {
this.prerelease.push(0);
break;
}
this.prerelease.splice(i + 1, 0, 0);
break;
}
case 'major':
if (!this.prerelease?.length || this.minor || this.patch)
this.major++;
this.prerelease = undefined;
this.patch = 0;
this.minor = 0;
break;
case 'minor':
if (!this.prerelease?.length || this.patch)
this.minor++;
this.prerelease = undefined;
this.patch = 0;
break;
case 'patch':
if (!this.prerelease?.length)
this.patch++;
this.prerelease = undefined;
break;
default:
throw typeError('Invalid increment identifier', {
version: this,
found: part,
validOptions: [
'major',
'minor',
'patch',
'premajor',
'preminor',
'prepatch',
'prerelease',
'pre',
],
}, this.inc);
}
this.raw = this.toString();
return this;
}
}
+4
-4
{
"name": "@vltpkg/semver",
"description": "The semantic version parser used by vlt",
"version": "1.0.0-rc.23",
"version": "1.0.0-rc.24",
"repository": {

@@ -15,5 +15,5 @@ "type": "git",

"dependencies": {
"@vltpkg/error-cause": "1.0.0-rc.23",
"@vltpkg/fast-split": "1.0.0-rc.23",
"@vltpkg/types": "1.0.0-rc.23"
"@vltpkg/error-cause": "1.0.0-rc.24",
"@vltpkg/fast-split": "1.0.0-rc.24",
"@vltpkg/types": "1.0.0-rc.24"
},

@@ -20,0 +20,0 @@ "devDependencies": {