UNPKG

renovate

Version:

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

349 lines • 13.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.api = exports.supportedRangeStrategies = exports.supportsRanges = exports.urls = exports.displayName = exports.id = void 0; exports.isValid = isValid; exports.isVersion = isVersion; const tslib_1 = require("tslib"); const is_1 = tslib_1.__importDefault(require("@sindresorhus/is")); const semver_1 = tslib_1.__importDefault(require("semver")); const semver_utils_1 = require("semver-utils"); const logger_1 = require("../../../logger"); const regex_1 = require("../../../util/regex"); const npm_1 = require("../npm"); exports.id = 'composer'; exports.displayName = 'Composer'; exports.urls = [ 'https://getcomposer.org/doc/articles/versions.md', 'https://packagist.org/packages/composer/semver', 'https://madewithlove.be/tilde-and-caret-constraints/', 'https://semver.mwl.be', ]; exports.supportsRanges = true; exports.supportedRangeStrategies = [ 'bump', 'widen', 'pin', 'replace', 'update-lockfile', ]; function getVersionParts(input) { const versionParts = input.split('-'); if (versionParts.length === 1) { return [input, '']; } return [versionParts[0], '-' + versionParts[1]]; } function padZeroes(input) { const [output, stability] = getVersionParts(input); const sections = output.split('.'); while (sections.length < 3) { sections.push('0'); } return sections.join('.') + stability; } function convertStabilityModifier(input) { // Handle stability modifiers. const versionParts = input.split('@'); if (versionParts.length === 1) { return input; } // 1.0@beta2 to 1.0-beta.2 const stability = versionParts[1].replace((0, regex_1.regEx)(/(?:^|\s)(beta|alpha|rc)([1-9][0-9]*)(?: |$)/gi), '$1.$2'); // If there is a stability part, npm semver expects the version // to be full return padZeroes(versionParts[0]) + '-' + stability; } function normalizeVersion(input) { let output = input; output = output.replace((0, regex_1.regEx)(/(^|>|>=|\^|~)v/i), '$1'); return convertStabilityModifier(output); } /** * @param versions Version list in any format, it recognizes the specific patch format x.x.x-pXX * @param range Range in composer format * @param minMode If true, it will calculate minSatisfyingVersion, if false, it calculates the maxSatisfyingVersion * @returns min or max satisfyingVersion from the input */ function calculateSatisfyingVersionIntenal(versions, range, minMode) { // Because composer -p versions are considered stable, we have to remove the suffix for the npm.XXX functions. const versionsMapped = versions.map((x) => { return { origianl: x, cleaned: removeComposerSpecificPatchPart(x), npmVariant: composer2npm(removeComposerSpecificPatchPart(x)[0]), }; }); const npmVersions = versionsMapped.map((x) => x.npmVariant); const npmVersion = minMode ? npm_1.api.minSatisfyingVersion(npmVersions, composer2npm(range)) : npm_1.api.getSatisfyingVersion(npmVersions, composer2npm(range)); if (!npmVersion) { return null; } // After we find the npm versions, we select from them back in the mapping the possible patches. const candidates = versionsMapped .filter((x) => x.npmVariant === npmVersion) .sort((a, b) => (minMode ? 1 : -1) * sortVersions(a.origianl, b.origianl)); return candidates[0].origianl; } /** * @param intput Version in any format, it recognizes the specific patch format x.x.x-pXX * @returns If input contains the specific patch, it returns the input with removed the patch and true, otherwise it retunrs the same string and false. */ function removeComposerSpecificPatchPart(input) { // the regex is based on the original from composer implementation https://github.com/composer/semver/blob/fa1ec24f0ab1efe642671ec15c51a3ab879f59bf/src/VersionParser.php#L137 const pattern = /^v?\d+(\.\d+(\.\d+(\.\d+)?)?)?(?<suffix>-p[1-9]\d*)$/gi; const match = pattern.exec(input); return match ? [input.replace(match.groups.suffix, ''), true] : [input, false]; } function composer2npm(input) { return input .split((0, regex_1.regEx)(/\s*\|\|?\s*/g)) .map((part) => { const cleanInput = normalizeVersion(part); if (npm_1.api.isVersion(cleanInput)) { return cleanInput; } if (npm_1.api.isVersion(padZeroes(cleanInput))) { return padZeroes(cleanInput); } const [versionId, stability] = getVersionParts(cleanInput); let output = versionId; // ~4 to ^4 and ~4.1 to ^4.1 output = output.replace((0, regex_1.regEx)(/(?:^|\s)~([1-9][0-9]*(?:\.[0-9]*)?)(?: |$)/g), '^$1'); // ~0.4 to >=0.4 <1 output = output.replace((0, regex_1.regEx)(/(?:^|\s)~(0\.[1-9][0-9]*)(?: |$)/g), '>=$1 <1'); // add extra digits to <8-DEV and <8.0-DEV output = output .replace((0, regex_1.regEx)(/^(<\d+(\.\d+)?)$/g), '$1.0') .replace((0, regex_1.regEx)(/^(<\d+(\.\d+)?)$/g), '$1.0'); return output + stability; }) .map((part) => part.replace(/([a-z])([0-9])/gi, '$1.$2')) .join(' || '); } function equals(a, b) { return npm_1.api.equals(composer2npm(a), composer2npm(b)); } function getMajor(version) { const semverVersion = semver_1.default.coerce(composer2npm(version)); return semverVersion ? npm_1.api.getMajor(semverVersion) : null; } function getMinor(version) { const semverVersion = semver_1.default.coerce(composer2npm(version)); return semverVersion ? npm_1.api.getMinor(semverVersion) : null; } function getPatch(version) { const semverVersion = semver_1.default.coerce(composer2npm(version)); // This returns only the numbers without the optional `-pXX` patch version supported by composer. Fixing that would require a bigger // refactoring, because the API supports only numbers. return semverVersion ? npm_1.api.getPatch(semverVersion) : null; } function isGreaterThan(a, b) { return sortVersions(a, b) === 1; } function isLessThanRange(version, range) { return !!npm_1.api.isLessThanRange?.(composer2npm(version), composer2npm(range)); } function isSingleVersion(input) { return !!input && npm_1.api.isSingleVersion(composer2npm(input)); } function isStable(version) { if (version) { // Composer considers patches `-pXX` as stable: https://github.com/composer/semver/blob/fa1ec24f0ab1efe642671ec15c51a3ab879f59bf/src/VersionParser.php#L568 but npm not. // In order to be able to use the standard npm.isStable function, we remove the potential patch version for the check. const [withoutPatch] = removeComposerSpecificPatchPart(version); return npm_1.api.isStable(composer2npm(withoutPatch)); } return false; } function isValid(input) { return !!input && npm_1.api.isValid(composer2npm(input)); } function isVersion(input) { return !!input && npm_1.api.isVersion(composer2npm(input)); } function matches(version, range) { return npm_1.api.matches(composer2npm(version), composer2npm(range)); } function getSatisfyingVersion(versions, range) { return calculateSatisfyingVersionIntenal(versions, range, false); } function minSatisfyingVersion(versions, range) { return calculateSatisfyingVersionIntenal(versions, range, true); } function subset(subRange, superRange) { try { return npm_1.api.subset(composer2npm(subRange), composer2npm(superRange)); } catch (err) { logger_1.logger.trace({ err }, 'composer.subset error'); return false; } } function intersects(subRange, superRange) { try { return npm_1.api.intersects(composer2npm(subRange), composer2npm(superRange)); } catch (err) { logger_1.logger.trace({ err }, 'composer.intersects error'); return false; } } function getNewValue({ currentValue, rangeStrategy, currentVersion, newVersion, }) { if (rangeStrategy === 'pin') { return newVersion; } if (rangeStrategy === 'update-lockfile') { if (matches(newVersion, currentValue)) { return currentValue; } return getNewValue({ currentValue, rangeStrategy: 'replace', currentVersion, newVersion, }); } const currentMajor = currentVersion ? getMajor(currentVersion) : null; const toMajor = getMajor(newVersion); const toMinor = getMinor(newVersion); let newValue = null; if (isVersion(currentValue)) { newValue = newVersion; } else if ((0, regex_1.regEx)(/^[~^](0\.[1-9][0-9]*)$/).test(currentValue)) { const operator = currentValue.substring(0, 1); // handle ~0.4 case first if (toMajor === 0) { // TODO: types (#22198) newValue = `${operator}0.${toMinor}`; } else { // TODO: types (#22198) newValue = `${operator}${toMajor}.0`; } } else if ((0, regex_1.regEx)(/^[~^]([0-9]*)$/).test(currentValue)) { // handle ~4 case const operator = currentValue.substring(0, 1); // TODO: types (#22198) newValue = `${operator}${toMajor}`; } else if (toMajor && (0, regex_1.regEx)(/^[~^]([0-9]*(?:\.[0-9]*)?)$/).test(currentValue)) { const operator = currentValue.substring(0, 1); if (rangeStrategy === 'bump') { newValue = `${operator}${newVersion}`; } else if ((is_1.default.number(currentMajor) && toMajor > currentMajor) || !toMinor) { // handle ~4.1 case newValue = `${operator}${toMajor}.0`; } else { newValue = `${operator}${toMajor}.${toMinor}`; } } else if (currentVersion && npm_1.api.isVersion(padZeroes(normalizeVersion(newVersion))) && npm_1.api.isValid(normalizeVersion(currentValue)) && composer2npm(currentValue) === normalizeVersion(currentValue)) { newValue = npm_1.api.getNewValue({ currentValue: normalizeVersion(currentValue), rangeStrategy, currentVersion: padZeroes(normalizeVersion(currentVersion)), newVersion: padZeroes(normalizeVersion(newVersion)), }); } if (rangeStrategy === 'widen' && matches(newVersion, currentValue)) { newValue = currentValue; } else { const hasOr = currentValue.includes(' || '); if (hasOr || rangeStrategy === 'widen') { const splitValues = currentValue.split('||'); const lastValue = splitValues[splitValues.length - 1]; const replacementValue = getNewValue({ currentValue: lastValue.trim(), rangeStrategy: 'replace', currentVersion, newVersion, }); if (rangeStrategy === 'replace') { newValue = replacementValue; } else if (replacementValue) { const parsedRange = (0, semver_utils_1.parseRange)(replacementValue); const element = parsedRange[parsedRange.length - 1]; if (element.operator?.startsWith('<')) { const splitCurrent = currentValue.split(element.operator); splitCurrent.pop(); newValue = splitCurrent.join(element.operator) + replacementValue; } else { newValue = currentValue + ' || ' + replacementValue; } } } } if (!newValue) { logger_1.logger.warn({ currentValue, rangeStrategy, currentVersion, newVersion }, 'Unsupported composer value'); newValue = newVersion; } if (currentValue.split('.')[0].includes('v')) { newValue = newValue.replace((0, regex_1.regEx)(/([0-9])/), 'v$1'); } // Preserve original min-stability specifier if (currentValue.includes('@')) { newValue += '@' + currentValue.split('@')[1]; } return newValue; } function sortVersions(a, b) { const [aWithoutPatch, aContainsPatch] = removeComposerSpecificPatchPart(a); const [bWithoutPatch, bContainsPatch] = removeComposerSpecificPatchPart(b); if (aContainsPatch === bContainsPatch) { // If both [a and b] contain patch version or both [a and b] do not contain patch version, then npm comparison deliveres correct results return npm_1.api.sortVersions(composer2npm(a), composer2npm(b)); } else if (npm_1.api.equals(composer2npm(aWithoutPatch), composer2npm(bWithoutPatch))) { // If only one [a or b] contains patch version and the parts without patch versions are equal, then the version with patch is greater (this is the case where npm comparison fails) return aContainsPatch ? 1 : -1; } else { // All other cases can be compared correctly by npm return npm_1.api.sortVersions(composer2npm(a), composer2npm(b)); } } function isCompatible(version) { return isVersion(version); } function isBreaking(current, version) { return npm_1.api.isBreaking(composer2npm(current), composer2npm(version)); } exports.api = { equals, getMajor, getMinor, getPatch, isBreaking, isCompatible, isGreaterThan, isLessThanRange, isSingleVersion, isStable, isValid, isVersion, matches, getSatisfyingVersion, minSatisfyingVersion, getNewValue, sortVersions, subset, intersects, }; exports.default = exports.api; //# sourceMappingURL=index.js.map