UNPKG

renovate

Version:

Automated dependency updates. Flexible so you don't need to be.

440 lines • 16.4 kB
"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