renovate
Version:
Automated dependency updates. Flexible so you don't need to be.
440 lines • 16.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getNewValue = getNewValue;
exports.isLessThanRange = isLessThanRange;
exports.checkRangeAndRemoveUnnecessaryRangeLimit = checkRangeAndRemoveUnnecessaryRangeLimit;
const pep440_1 = require("@renovatebot/pep440");
const specifier_js_1 = require("@renovatebot/pep440/lib/specifier.js");
const version_js_1 = require("@renovatebot/pep440/lib/version.js");
const logger_1 = require("../../../logger");
const array_1 = require("../../../util/array");
const regex_1 = require("../../../util/regex");
const UserPolicyPrecisionMap = {
Major: 0,
Minor: 1,
Micro: 2,
Bug: 3,
None: Infinity,
};
const PrecisionUserPolicyMap = {
0: 'Major',
1: 'Minor',
2: 'Micro',
3: 'Bug',
};
/**
* Calculate current update range precision.
* @param ranges A {@link Range} consists of current range
* @returns A {@link UserPolicy}
*/
function getRangePrecision(ranges) {
const bound = (0, array_1.coerceArray)((0, version_js_1.parse)((ranges[1] || ranges[0]).version)?.release);
let rangePrecision = -1;
// range is defined by a single bound.
// ie. <1.2.2.3,
// >=7
if (ranges.length === 1) {
rangePrecision = bound.length - 1;
}
// Range is defined by both upper and lower bounds.
if (ranges.length === 2) {
const lowerBound = (0, array_1.coerceArray)((0, version_js_1.parse)(ranges[0].version)?.release);
rangePrecision = bound.findIndex((el, index) => el > lowerBound[index]);
}
// Tune down Major precision if followed by a zero
if (rangePrecision === UserPolicyPrecisionMap.Major &&
rangePrecision + 1 < bound.length &&
bound[rangePrecision + 1] === 0) {
rangePrecision++;
}
// Could not calculate user precision
// Default to the smallest possible
// istanbul ignore next
if (rangePrecision === -1) {
rangePrecision = bound.length - 1;
}
const key = PrecisionUserPolicyMap[rangePrecision];
return UserPolicyPrecisionMap[key];
}
/**
* @param policy Required range precision
* @param newVersion The newly accepted version
* @param baseVersion Optional Current upper bound
* @returns A string represents a future version upper bound.
*/
function getFutureVersion(policy, newVersion, baseVersion) {
const toRelease = (0, array_1.coerceArray)((0, version_js_1.parse)(newVersion)?.release);
const baseRelease = (0, array_1.coerceArray)((0, version_js_1.parse)(baseVersion ?? newVersion)?.release);
return baseRelease.map((_, index) => {
const toPart = toRelease[index] ?? 0;
if (index < policy) {
return toPart;
}
if (index === policy) {
return toPart + (baseVersion === undefined ? 0 : 1);
}
return 0;
});
}
function getNewValue({ currentValue, rangeStrategy, currentVersion, newVersion, isReplacement, }) {
let ranges;
let updatedRange;
if (rangeStrategy === 'pin' && !isReplacement) {
return '==' + newVersion;
}
// no symbol: accept only that specific version specified
if (currentValue === currentVersion || isReplacement) {
return newVersion;
}
try {
ranges = parseCurrentRange(currentValue);
if (!ranges.length) {
// an empty string is an allowed value for PEP440 range
// it means get any version
logger_1.logger.warn({ currentValue }, 'Empty currentValue');
return currentValue;
}
}
catch (err) {
logger_1.logger.warn({ currentValue, err }, 'Unexpected range error');
return null;
}
// return early if newVersion is excluded from range
if (ranges.some((range) => range.operator === '!=' && range.version === newVersion)) {
logger_1.logger.debug(`Cannot calculate new value as the newVersion:\`${newVersion}\` is excluded from range: \`${currentValue}\``);
return null;
}
switch (rangeStrategy) {
case 'auto':
case 'replace':
updatedRange = handleReplaceStrategy({
currentValue,
rangeStrategy,
currentVersion,
newVersion,
}, ranges);
break;
case 'widen':
updatedRange = handleWidenStrategy({
currentValue,
rangeStrategy,
currentVersion,
newVersion,
}, ranges);
break;
case 'bump':
updatedRange = handleBumpStrategy({
currentValue,
rangeStrategy,
currentVersion,
newVersion,
}, ranges);
break;
default:
// Unsupported rangeStrategy
// Valid rangeStrategy values are: bump, extend, pin, replace.
// https://docs.renovatebot.com/modules/versioning/#pep440-versioning
logger_1.logger.debug('Unsupported rangeStrategy: ' +
rangeStrategy +
'. Using "replace" instead.');
return getNewValue({
currentValue,
rangeStrategy: 'replace',
currentVersion,
newVersion,
});
}
let result = updatedRange.filter(Boolean).join(', ');
if (result.includes(', ') && !currentValue.includes(', ')) {
result = result.replace((0, regex_1.regEx)(/, /g), ',');
}
const checkedResult = checkRangeAndRemoveUnnecessaryRangeLimit(result, newVersion);
// istanbul ignore if
if (!(0, pep440_1.satisfies)(newVersion, checkedResult)) {
// we failed at creating the range
logger_1.logger.warn({ result, newVersion, currentValue }, 'pep440: failed to calculate newValue');
return null;
}
return checkedResult;
}
function isLessThanRange(input, range) {
try {
let invertResult = true;
const results = range
.split(',')
.map((x) => x
.replace((0, regex_1.regEx)(/\s*/g), '')
.split((0, regex_1.regEx)(/(~=|==|!=|<=|>=|<|>|===)/))
.slice(1))
.map(([op, version]) => {
if (['!=', '<=', '<'].includes(op)) {
return true;
}
invertResult = false;
if (['~=', '==', '>=', '==='].includes(op)) {
return (0, pep440_1.lt)(input, version);
}
if (op === '>') {
return (0, pep440_1.lte)(input, version);
}
// istanbul ignore next
return false;
});
const result = results.every((res) => res === true);
return invertResult ? !result : result;
}
catch /* istanbul ignore next */ {
return false;
}
}
function parseCurrentRange(currentValue) {
const ranges = (0, specifier_js_1.parse)(currentValue);
if (!ranges) {
throw new TypeError('Invalid pep440 currentValue');
}
if (ranges.some((range) => range.operator === '===')) {
// the operator "===" is used for legacy non PEP440 versions
throw new TypeError('PEP440 arbitrary equality (===) not supported');
}
return ranges;
}
function handleLowerBound(range, newVersion) {
// used to mark minimum supported version
// lower the bound if the new version is lower than current range
if (['>', '>='].includes(range.operator)) {
if ((0, pep440_1.lte)(newVersion, range.version)) {
// this looks like a rollback
return '>=' + newVersion;
}
// otherwise, treat it same as exclude
return range.operator + range.version;
}
// istanbul ignore next
return null;
}
function handleUpperBound(range, newVersion) {
// this is used to exclude future versions
if (range.operator === '<') {
// if newVersion is that future version
if ((0, pep440_1.gte)(newVersion, range.version)) {
// now here things get tricky
// we calculate the new future version
const precision = getRangePrecision([range]);
const futureVersion = getFutureVersion(precision, newVersion, range.version);
return range.operator + futureVersion.join('.');
}
// newVersion is in range, for other than "replace" strategies
return range.operator + range.version;
}
// istanbul ignore next
return null;
}
function updateRangeValue({ currentValue, rangeStrategy, currentVersion, newVersion }, range) {
// used to exclude versions,
// we assume that's for a good reason
if (range.operator === '!=') {
return range.operator + range.version;
}
// keep the .* suffix
if (range.prefix) {
const futureVersion = getFutureVersion(UserPolicyPrecisionMap.None, newVersion, range.version).join('.');
return range.operator + futureVersion + '.*';
}
if (range.operator === '~=') {
const baseVersion = (0, array_1.coerceArray)((0, version_js_1.parse)(range.version)?.release);
const futureVersion = (0, array_1.coerceArray)((0, version_js_1.parse)(newVersion)?.release);
const baseLen = baseVersion.length;
const newVerLen = futureVersion.length;
// trim redundant trailing version specifiers
if (baseLen < newVerLen) {
return (range.operator + futureVersion.slice(0, baseVersion.length).join('.'));
}
// pad with specifiers
if (baseLen > newVerLen) {
for (let i = baseLen - newVerLen - 1; i >= 0; i--) {
futureVersion.push(0);
}
return range.operator + futureVersion.join('.');
}
return range.operator + newVersion;
}
if (['==', '<='].includes(range.operator)) {
if ((0, pep440_1.lte)(newVersion, range.version)) {
return range.operator + range.version;
}
return range.operator + newVersion;
}
let output = handleUpperBound(range, newVersion);
if (output) {
// manged to update upperbound
// no need to try anything else
return output;
}
output = handleLowerBound(range, newVersion);
if (output) {
return output;
}
// unless PEP440 changes, this won't happen
// istanbul ignore next
logger_1.logger.error({ newVersion, currentValue, range }, 'pep440: failed to process range');
// istanbul ignore next
return null;
}
/**
* Checks for zero specifiers.
* returns true if one of the bounds' length is < 3.
* @param ranges A {@link Range} array.
* @returns A boolean value
* Used mainly for cosmetics for the rez versioning syntax.
*/
function hasZeroSpecifier(ranges) {
return ranges.some((range) => {
const release = (0, version_js_1.parse)(range.version)?.release;
return release && release.length < 3;
});
}
function trimTrailingZeros(numbers) {
let i = numbers.length - 1;
while (numbers[i] === 0) {
i--;
}
return numbers.slice(0, i + 1);
}
function divideCompatibleReleaseRange(currentRange) {
const currentVersionUpperBound = currentRange.version
.split('.')
.map((num) => parseInt(num));
if (currentVersionUpperBound.length > 1) {
currentVersionUpperBound.splice(-1);
}
currentVersionUpperBound[currentVersionUpperBound.length - 1] += 1;
return [
{ operator: '>=', version: currentRange.version },
{
operator: '<',
version: currentVersionUpperBound.join('.'),
},
];
}
function handleWidenStrategy({ currentValue, rangeStrategy, currentVersion, newVersion }, ranges) {
// newVersion is within range
if ((0, pep440_1.satisfies)(newVersion, currentValue)) {
return [currentValue];
}
let rangePrecision = getRangePrecision(ranges);
const trimZeros = hasZeroSpecifier(ranges);
let newRanges = [];
if (ranges.length === 1 && ranges[0].operator === '~=') {
// If range operator is '~=', divide the range into its logical equivalent.
// Example: ~=1.0 --> >=1.0,<2
newRanges = divideCompatibleReleaseRange(ranges[0]);
}
else {
newRanges = ranges;
}
return newRanges.map((range) => {
// newVersion is over the upper bound
if (range.operator === '<' && (0, pep440_1.gte)(newVersion, range.version)) {
const upperBound = (0, array_1.coerceArray)((0, version_js_1.parse)(range.version)?.release);
const len = upperBound.length;
// Match the precision of the smallest specifier if other than 0
if (upperBound[len - 1] !== 0) {
const key = PrecisionUserPolicyMap[(len - 1)];
rangePrecision =
UserPolicyPrecisionMap[key];
}
let futureVersion = getFutureVersion(rangePrecision, newVersion, range.version);
if (trimZeros) {
futureVersion = trimTrailingZeros(futureVersion);
}
return range.operator + futureVersion.join('.');
}
// default
return updateRangeValue({
currentValue,
rangeStrategy,
currentVersion,
newVersion,
}, range);
});
}
function handleReplaceStrategy({ currentValue, rangeStrategy, currentVersion, newVersion }, ranges) {
// newVersion is within range
if ((0, pep440_1.satisfies)(newVersion, currentValue)) {
return [currentValue];
}
const trimZeros = hasZeroSpecifier(ranges);
return ranges.map((range) => {
// newVersion is over the upper bound
if (range.operator === '<' && (0, pep440_1.gte)(newVersion, range.version)) {
const rangePrecision = getRangePrecision(ranges);
let futureVersion = getFutureVersion(rangePrecision, newVersion, range.version);
if (trimZeros) {
futureVersion = trimTrailingZeros(futureVersion);
}
return range.operator + futureVersion.join('.');
}
// handle lower bound
if (['>', '>='].includes(range.operator)) {
if ((0, pep440_1.lte)(newVersion, range.version)) {
// this looks like a rollback
return '>=' + newVersion;
}
// update the lower bound to reflect the accepted new version
const lowerBound = (0, array_1.coerceArray)((0, version_js_1.parse)(range.version)?.release);
const rangePrecision = lowerBound.length - 1;
let newBase = getFutureVersion(rangePrecision, newVersion);
if (trimZeros) {
newBase = trimTrailingZeros(newBase);
}
// trim last element of the newBase when new accepted version is out of range.
// example: let new bound be >8.2.5 & newVersion be 8.2.5
// return value will be: >8.2
if (range.operator === '>') {
if (newVersion === newBase.join('.') && newBase.length > 1) {
newBase.pop();
}
}
return range.operator + newBase.join('.');
}
// default
return updateRangeValue({
currentValue,
rangeStrategy,
currentVersion,
newVersion,
}, range);
});
}
function handleBumpStrategy({ currentValue, rangeStrategy, currentVersion, newVersion }, ranges) {
return ranges.map((range) => {
// bump lower bound to current new version
if (range.operator === '>=') {
return range.operator + newVersion;
}
return updateRangeValue({
currentValue,
rangeStrategy,
currentVersion,
newVersion,
}, range);
});
}
function checkRangeAndRemoveUnnecessaryRangeLimit(rangeInput, newVersion) {
let newRange = rangeInput;
if (rangeInput.includes(',')) {
const newRes = rangeInput.split(',');
if (newRes[0].includes('.*') &&
newRes[0].includes('==') &&
newRes[1].includes('>=')) {
if ((0, pep440_1.satisfies)(newVersion, newRes[0])) {
newRange = newRes[0];
}
}
}
else {
return rangeInput;
}
return newRange;
}
//# sourceMappingURL=range.js.map