renovate
Version:
Automated dependency updates. Flexible so you don't need to be.
268 lines (267 loc) • 9.19 kB
JavaScript
import { regEx } from "../../../util/regex.js";
import { isNotNullOrUndefined } from "../../../util/array.js";
import { parseVersion } from "../nuget/parser.js";
import { versionToString } from "../nuget/version.js";
import { compare, sameReleaseParts } from "./version.js";
//#region lib/modules/versioning/paket/range.ts
const constraintOperators = [
"=",
"==",
">",
">=",
"<",
"<=",
"~>"
];
const prereleaseTagRegex = regEx(/^[a-zA-Z][a-zA-Z0-9-]*$/);
/**
* Paket only accepts these operator combinations for two-part constraints.
*/
function isValidPair(first, second) {
if (first === "~>") return second === ">" || second === ">=" || second === "<" || second === "<=";
if (first === ">" || first === ">=") return second === "<" || second === "<=";
return false;
}
function parseRange(input) {
if (!input?.trim()) return null;
let text = input.trim();
let strategy = null;
if (text.startsWith("!") || text.startsWith("@")) {
strategy = text[0];
text = text.slice(1).trim();
}
const tokens = text.split(regEx(/\s+/));
const constraints = [];
let idx = 0;
while (idx < tokens.length && constraints.length < 2 && constraintOperators.includes(tokens[idx])) {
const operator = tokens[idx];
const versionToken = tokens[idx + 1];
const version = versionToken ? parseVersion(versionToken) : null;
if (!version) return null;
constraints.push({
operator,
version
});
idx += 2;
}
if (constraints.length === 0) {
const version = parseVersion(tokens[0]);
if (!version) return null;
constraints.push({
operator: "",
version
});
idx = 1;
}
if (constraints.length === 2 && !isValidPair(constraints[0].operator, constraints[1].operator)) return null;
const prereleaseTags = [];
for (const token of tokens.slice(idx)) {
if (!prereleaseTagRegex.test(token)) return null;
if (!prereleaseTags.includes(token)) prereleaseTags.push(token);
}
return {
strategy,
constraints,
prereleaseTags
};
}
function rangeToString(range) {
const parts = [];
for (const { operator, version } of range.constraints) {
const versionStr = versionToString(version);
parts.push(operator === "" ? versionStr : `${operator} ${versionStr}`);
}
parts.push(...range.prereleaseTags);
return `${range.strategy ?? ""}${parts.join(" ")}`;
}
function releaseParts(version) {
const { major, minor, patch, revision } = version;
return [
major,
minor,
patch,
revision
].filter(isNotNullOrUndefined);
}
function versionFromParts(parts) {
const [major, minor, patch, revision] = parts;
const result = {
type: "nuget-version",
major
};
if (minor !== void 0) result.minor = minor;
if (patch !== void 0) result.patch = patch;
if (revision !== void 0) result.revision = revision;
return result;
}
function adaptToPrecision(version, precision) {
const parts = releaseParts(version).slice(0, precision);
while (parts.length < precision) parts.push(0);
return versionFromParts(parts);
}
function bumpAtPrecision(version, precision) {
const parts = releaseParts(adaptToPrecision(version, precision));
parts[precision - 1] += 1;
return versionFromParts(parts);
}
/**
* Upper bound of the pessimistic operator: chop off the last release part and
* increment the remaining last number, so `~> 1.2.3` allows `< 1.3` and
* `~> 1.2` allows `< 2`.
*/
function twiddle(version) {
return bumpAtPrecision(version, Math.max(releaseParts(version).length - 1, 1));
}
function intervalOfPair(first, second) {
if (first.operator === "~>") {
const cap = twiddle(first.version);
if (second.operator === ">" || second.operator === ">=") return {
kind: "range",
from: second.version,
fromInclusive: second.operator === ">=",
to: cap,
toInclusive: false
};
const to = compare(second.version, cap) < 0 ? second.version : cap;
return {
kind: "range",
from: first.version,
fromInclusive: true,
to,
toInclusive: second.operator === "<="
};
}
return {
kind: "range",
from: first.version,
fromInclusive: first.operator === ">=",
to: second.version,
toInclusive: second.operator === "<="
};
}
const singleOperatorKind = {
"": "specific",
"=": "specific",
"==": "override",
">=": "minimum",
">": "greater-than",
"<=": "maximum",
"<": "less-than"
};
function intervalOf(constraints) {
if (constraints.length === 2) return intervalOfPair(constraints[0], constraints[1]);
const [{ operator, version }] = constraints;
if (operator === "~>") return {
kind: "range",
from: version,
fromInclusive: true,
to: twiddle(version),
toInclusive: false
};
return {
kind: singleOperatorKind[operator],
version
};
}
const numericSegmentRegex = regEx(/^\d+$/);
const channelPrefixRegex = regEx(/^[a-zA-Z]+(?:-[a-zA-Z]+)*/);
function channelFromSegments(segments) {
const [first, second] = segments;
if (first && !numericSegmentRegex.test(first)) return first;
if (second && !numericSegmentRegex.test(second)) return second;
return "";
}
/**
* The channel name of a prerelease suffix, e.g. `alpha` for `alpha001` or
* `beta` for `beta.2`.
*/
function prereleaseChannel(prerelease) {
const segments = prerelease.split(".");
if (segments.length > 1) return channelFromSegments(segments);
const prefix = channelPrefixRegex.exec(prerelease);
if (prefix) return prefix[0];
return channelFromSegments(prerelease.split("-").filter((segment) => segment !== ""));
}
function prereleaseStatusOf(range, interval) {
const { prereleaseTags } = range;
if (prereleaseTags.length === 1 && prereleaseTags[0].toLowerCase() === "prerelease") return "all";
if (prereleaseTags.length > 0) return prereleaseTags;
const bounds = interval.kind === "range" ? [interval.from, interval.to] : [interval.version];
const channels = [];
for (const bound of bounds) {
if (!bound.prerelease) continue;
const channel = prereleaseChannel(bound.prerelease);
if (!channels.includes(channel)) channels.push(channel);
}
return channels.length > 0 ? channels : "none";
}
function isPrereleaseAllowed(version, status) {
if (status === "all") return true;
if (status === "none") return !version.prerelease;
if (!version.prerelease) return true;
return status.includes(prereleaseChannel(version.prerelease));
}
/**
* Paket rule: a prerelease of a stable bound version counts as being in range
* when its channel is allowed, e.g. `1.2.3-alpha1` satisfies `= 1.2.3 alpha`.
*/
function isPrereleaseOfBound(version, bound, status) {
return status !== "none" && !bound.prerelease && sameReleaseParts(version, bound) && isPrereleaseAllowed(version, status);
}
function matches(version, range) {
const interval = intervalOf(range.constraints);
const status = prereleaseStatusOf(range, interval);
switch (interval.kind) {
case "specific": return compare(version, interval.version) === 0 || isPrereleaseOfBound(version, interval.version, status);
case "override": return compare(version, interval.version) === 0;
case "minimum": {
const cmp = compare(version, interval.version);
return cmp === 0 || cmp > 0 && isPrereleaseAllowed(version, status) || isPrereleaseOfBound(version, interval.version, status);
}
case "greater-than": return compare(version, interval.version) > 0 && isPrereleaseAllowed(version, status);
case "maximum": {
const cmp = compare(version, interval.version);
return cmp === 0 || cmp < 0 && isPrereleaseAllowed(version, status);
}
case "less-than": return compare(version, interval.version) < 0 && isPrereleaseAllowed(version, status) && !isPrereleaseOfBound(version, interval.version, status);
case "range": {
const { from, fromInclusive, to, toInclusive } = interval;
const lowerCmp = compare(version, from);
const upperCmp = compare(version, to);
const inLower = fromInclusive ? lowerCmp >= 0 : lowerCmp > 0;
const inUpper = toInclusive ? upperCmp <= 0 : upperCmp < 0 && !isPrereleaseOfBound(version, to, status);
return inLower && inUpper && isPrereleaseAllowed(version, status) || isPrereleaseOfBound(version, from, status);
}
}
}
function isLessThanLowerBound(version, range) {
const interval = intervalOf(range.constraints);
switch (interval.kind) {
case "specific":
case "override":
case "minimum": return compare(version, interval.version) < 0;
case "greater-than": return compare(version, interval.version) <= 0;
case "maximum":
case "less-than": return false;
case "range": return interval.fromInclusive ? compare(version, interval.from) < 0 : compare(version, interval.from) <= 0;
}
}
/**
* A rewritten range admits the new version numerically, but its prerelease
* channel may still be blocked, so allow the channel with an explicit tag.
*/
function ensurePrereleaseMatches(range, version) {
if (!version.prerelease || matches(version, range)) return range;
const channel = prereleaseChannel(version.prerelease);
if (prereleaseTagRegex.test(channel)) return {
...range,
prereleaseTags: [...range.prereleaseTags, channel]
};
return {
...range,
prereleaseTags: ["prerelease"]
};
}
//#endregion
export { adaptToPrecision, bumpAtPrecision, ensurePrereleaseMatches, intervalOf, isLessThanLowerBound, matches, parseRange, prereleaseChannel, rangeToString, releaseParts, twiddle };
//# sourceMappingURL=range.js.map