UNPKG

renovate

Version:

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

435 lines • 18.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.generateBranchConfig = generateBranchConfig; const tslib_1 = require("tslib"); const is_1 = tslib_1.__importDefault(require("@sindresorhus/is")); const luxon_1 = require("luxon"); const markdown_table_1 = require("markdown-table"); const semver_1 = tslib_1.__importDefault(require("semver")); const config_1 = require("../../../config"); const error_messages_1 = require("../../../constants/error-messages"); const logger_1 = require("../../../logger"); const regex_1 = require("../../../util/regex"); const sanitize_1 = require("../../../util/sanitize"); const stringify_1 = require("../../../util/stringify"); const template = tslib_1.__importStar(require("../../../util/template")); const uniq_1 = require("../../../util/uniq"); const commit_message_1 = require("../model/commit-message"); function prettifyVersion(version) { if ((0, regex_1.regEx)(/^\d/).test(version)) { return `v${version}`; } return version; } function isTypesGroup(branchUpgrades) { return (branchUpgrades.some(({ depName }) => depName?.startsWith('@types/')) && new Set(branchUpgrades.map(({ depName }) => depName?.replace(/^@types\//, ''))).size === 1); } function sortTypesGroup(upgrades) { const isTypesUpgrade = ({ depName }) => !!depName?.startsWith('@types/'); const regularUpgrades = upgrades.filter((upgrade) => !isTypesUpgrade(upgrade)); const typesUpgrades = upgrades.filter(isTypesUpgrade); upgrades.splice(0, upgrades.length); upgrades.push(...regularUpgrades, ...typesUpgrades); } function getTableValues(upgrade) { if (!upgrade.commitBodyTable) { return null; } const { datasource, packageName, depName, currentVersion, newVersion } = upgrade; const name = packageName ?? depName; if (datasource && name && currentVersion && newVersion) { return [datasource, name, currentVersion, newVersion]; } logger_1.logger.trace({ datasource, packageName, depName, currentVersion, newVersion, }, 'Cannot determine table values'); return null; } function compileCommitMessage(upgrade) { if (upgrade.semanticCommits === 'enabled' && !upgrade.commitMessagePrefix) { logger_1.logger.trace('Upgrade has semantic commits enabled'); let semanticPrefix = upgrade.semanticCommitType; if (upgrade.semanticCommitScope) { semanticPrefix += `(${template.compile(upgrade.semanticCommitScope, upgrade)})`; } upgrade.commitMessagePrefix = commit_message_1.CommitMessage.formatPrefix(semanticPrefix); upgrade.toLowerCase = (0, regex_1.regEx)(/[A-Z]/).exec(upgrade.semanticCommitType) === null && !upgrade.semanticCommitType.startsWith(':'); } // Compile a few times in case there are nested templates upgrade.commitMessage = template.compile(upgrade.commitMessage ?? '', upgrade); upgrade.commitMessage = template.compile(upgrade.commitMessage, upgrade); upgrade.commitMessage = template.compile(upgrade.commitMessage, upgrade); // istanbul ignore if if (upgrade.commitMessage !== (0, sanitize_1.sanitize)(upgrade.commitMessage)) { logger_1.logger.debug({ branchName: upgrade.branchName }, 'Secrets exposed in commit message'); throw new Error(error_messages_1.CONFIG_SECRETS_EXPOSED); } upgrade.commitMessage = upgrade.commitMessage.trim(); // Trim exterior whitespace upgrade.commitMessage = upgrade.commitMessage.replace((0, regex_1.regEx)(/\s+/g), ' '); // Trim extra whitespace inside string upgrade.commitMessage = upgrade.commitMessage.replace((0, regex_1.regEx)(/to vv(\d)/), 'to v$1'); if (upgrade.toLowerCase && upgrade.commitMessageLowerCase !== 'never') { // We only need to lowercase the first line const splitMessage = upgrade.commitMessage.split(regex_1.newlineRegex); splitMessage[0] = splitMessage[0].toLowerCase(); upgrade.commitMessage = splitMessage.join('\n'); } logger_1.logger.trace(`commitMessage: ` + JSON.stringify(upgrade.commitMessage)); return upgrade.commitMessage; } function compilePrTitle(upgrade, commitMessage) { if (upgrade.prTitle) { upgrade.prTitle = template.compile(upgrade.prTitle, upgrade); upgrade.prTitle = template.compile(upgrade.prTitle, upgrade); upgrade.prTitle = template .compile(upgrade.prTitle, upgrade) .trim() .replace((0, regex_1.regEx)(/\s+/g), ' '); // istanbul ignore if if (upgrade.prTitle !== (0, sanitize_1.sanitize)(upgrade.prTitle)) { logger_1.logger.debug({ branchName: upgrade.branchName }, 'Secrets were exposed in PR title'); throw new Error(error_messages_1.CONFIG_SECRETS_EXPOSED); } if (upgrade.toLowerCase && upgrade.commitMessageLowerCase !== 'never') { upgrade.prTitle = upgrade.prTitle.toLowerCase(); } } else { [upgrade.prTitle] = commitMessage.split(regex_1.newlineRegex); } if (!upgrade.prTitleStrict) { upgrade.prTitle += upgrade.hasBaseBranches ? ' ({{baseBranch}})' : ''; if (upgrade.isGroup) { upgrade.prTitle += upgrade.updateType === 'major' && upgrade.separateMajorMinor ? ' (major)' : ''; upgrade.prTitle += upgrade.updateType === 'minor' && upgrade.separateMinorPatch ? ' (minor)' : ''; upgrade.prTitle += upgrade.updateType === 'patch' && upgrade.separateMinorPatch ? ' (patch)' : ''; } } // Compile again to allow for nested templates upgrade.prTitle = template.compile(upgrade.prTitle, upgrade); logger_1.logger.trace(`prTitle: ` + JSON.stringify(upgrade.prTitle)); } function getMinimumGroupSize(upgrades) { let minimumGroupSize = 1; const groupSizes = new Set(); for (const upg of upgrades) { if (upg.minimumGroupSize) { groupSizes.add(upg.minimumGroupSize); if (minimumGroupSize < upg.minimumGroupSize) { minimumGroupSize = upg.minimumGroupSize; } } } if (groupSizes.size > 1) { logger_1.logger.debug('Multiple minimumGroupSize values found for this branch, using highest.'); } return minimumGroupSize; } // Sorted by priority, from low to high const semanticCommitTypeByPriority = ['chore', 'ci', 'build', 'fix', 'feat']; function generateBranchConfig(upgrades) { let branchUpgrades = upgrades; if (!branchUpgrades.every((upgrade) => upgrade.pendingChecks)) { // If the branch isn't pending, then remove any upgrades within which *are* branchUpgrades = branchUpgrades.filter((upgrade) => !upgrade.pendingChecks); } logger_1.logger.trace({ config: branchUpgrades }, 'generateBranchConfig'); let config = { upgrades: [], }; const hasGroupName = branchUpgrades[0].groupName !== null; logger_1.logger.trace(`hasGroupName: ${hasGroupName}`); // Use group settings only if multiple upgrades or lazy grouping is disabled const depNames = []; const newValue = []; const toVersions = []; const toValues = new Set(); const depTypes = new Set(); for (const upg of branchUpgrades) { upg.recreateClosed = upg.recreateWhen === 'always'; if (upg.currentDigest) { upg.currentDigestShort = upg.currentDigestShort ?? upg.currentDigest.replace('sha256:', '').substring(0, 7); } if (upg.newDigest) { upg.newDigestShort = upg.newDigestShort ?? upg.newDigest.replace('sha256:', '').substring(0, 7); } if (upg.isDigest || upg.isPinDigest) { upg.displayFrom = upg.currentDigestShort; upg.displayTo = upg.newDigestShort; } else if (upg.isLockfileUpdate) { upg.displayFrom = upg.currentVersion; upg.displayTo = upg.newVersion; } else if (!upg.isLockFileMaintenance) { upg.displayFrom = upg.currentValue; upg.displayTo = upg.newValue; } if (upg.isLockFileMaintenance) { upg.recreateClosed = upg.recreateWhen !== 'never'; } upg.displayFrom ??= ''; upg.displayTo ??= ''; if (!depNames.includes(upg.depName)) { depNames.push(upg.depName); } if (!toVersions.includes(upg.newVersion)) { toVersions.push(upg.newVersion); } toValues.add(upg.newValue); if (upg.depType) { depTypes.add(upg.depType); } // prettify newVersion and newMajor for printing if (upg.newVersion) { upg.prettyNewVersion = prettifyVersion(upg.newVersion); } if (upg.newMajor) { upg.prettyNewMajor = `v${upg.newMajor}`; } if (upg.commitMessageExtra) { const extra = template.compile(upg.commitMessageExtra, upg); if (!newValue.includes(extra)) { newValue.push(extra); } } } const groupEligible = depNames.length > 1 || toVersions.length > 1 || (!toVersions[0] && newValue.length > 1); const typesGroup = depNames.length > 1 && !hasGroupName && isTypesGroup(branchUpgrades); logger_1.logger.trace(`groupEligible: ${groupEligible}`); const useGroupSettings = hasGroupName && groupEligible; logger_1.logger.trace(`useGroupSettings: ${useGroupSettings}`); let releaseTimestamp; if (depTypes.size) { config.depTypes = Array.from(depTypes).sort(); } for (const branchUpgrade of branchUpgrades) { let upgrade = { ...branchUpgrade }; upgrade.depTypes = config.depTypes; // needs to be done for each upgrade, as we reorder them below if (newValue.length > 1 && !groupEligible) { upgrade.commitMessageExtra = `to v${toVersions[0]}`; } const pendingVersionsLength = upgrade.pendingVersions?.length; if (pendingVersionsLength) { upgrade.displayPending = `\`${upgrade .pendingVersions.slice(-1) .pop()}\``; if (pendingVersionsLength > 1) { upgrade.displayPending += ` (+${pendingVersionsLength - 1})`; } } else { upgrade.displayPending = ''; } upgrade.prettyDepType = upgrade.prettyDepType ?? upgrade.depType ?? 'dependency'; if (useGroupSettings) { // Now overwrite original config with group config upgrade = (0, config_1.mergeChildConfig)(upgrade, upgrade.group); upgrade.isGroup = true; } else { delete upgrade.groupName; } // Delete group config regardless of whether it was applied delete upgrade.group; if (toVersions.length > 1 && toValues.size > 1 && newValue.length > 1 && !typesGroup) { logger_1.logger.trace({ toVersions }); logger_1.logger.trace({ toValues }); delete upgrade.commitMessageExtra; upgrade.recreateClosed = upgrade.recreateWhen !== 'never'; } else if (newValue.length > 1 && (upgrade.isDigest || upgrade.isPinDigest)) { logger_1.logger.trace({ newValue }); delete upgrade.commitMessageExtra; upgrade.recreateClosed = upgrade.recreateWhen !== 'never'; } else if (semver_1.default.valid(toVersions[0])) { upgrade.isRange = false; } config.upgrades.push(upgrade); if (upgrade.releaseTimestamp) { if (releaseTimestamp) { const existingStamp = luxon_1.DateTime.fromISO(releaseTimestamp); const upgradeStamp = luxon_1.DateTime.fromISO(upgrade.releaseTimestamp); if (upgradeStamp > existingStamp) { releaseTimestamp = upgrade.releaseTimestamp; } } else { releaseTimestamp = upgrade.releaseTimestamp; } } } if (typesGroup) { if (config.upgrades[0].depName?.startsWith('@types/')) { logger_1.logger.debug('Found @types - reversing upgrades to use depName in PR'); sortTypesGroup(config.upgrades); config.upgrades[0].recreateClosed = false; config.hasTypes = true; } } else { config.upgrades.sort((a, b) => { if (a.fileReplacePosition && b.fileReplacePosition) { // This is because we need to replace from the bottom of the file up return a.fileReplacePosition > b.fileReplacePosition ? -1 : 1; } // make sure that ordering is consistent : // items without position will be first in the list. if (a.fileReplacePosition) { return 1; } if (b.fileReplacePosition) { return -1; } if (a.depName < b.depName) { return -1; } if (a.depName > b.depName) { return 1; } return 0; }); } // Now assign first upgrade's config as branch config config = { ...config, ...config.upgrades[0], releaseTimestamp: releaseTimestamp, }; // TODO: fixme (#9666) // Enable `semanticCommits` if one of the branches has it enabled if (config.upgrades.some((upgrade) => upgrade.semanticCommits === 'enabled')) { config.semanticCommits = 'enabled'; // Calculate the highest priority `semanticCommitType` let highestIndex = -1; for (const upgrade of config.upgrades) { if (upgrade.semanticCommits === 'enabled' && upgrade.semanticCommitType) { const priorityIndex = semanticCommitTypeByPriority.indexOf(upgrade.semanticCommitType); if (priorityIndex > highestIndex) { highestIndex = priorityIndex; } } } if (highestIndex > -1) { config.semanticCommitType = semanticCommitTypeByPriority[highestIndex]; } } // Use templates to generate strings const commitMessage = compileCommitMessage(config); compilePrTitle(config, commitMessage); config.dependencyDashboardApproval = config.upgrades.some((upgrade) => upgrade.dependencyDashboardApproval); config.dependencyDashboardPrApproval = config.upgrades.some((upgrade) => upgrade.prCreation === 'approval'); config.prBodyColumns = [ ...new Set(config.upgrades.reduce((existing, upgrade) => existing.concat(upgrade.prBodyColumns), [])), ].filter(is_1.default.nonEmptyString); // combine excludeCommitPaths for multiple manager experience const hasExcludeCommitPaths = config.upgrades.some((u) => u.excludeCommitPaths && u.excludeCommitPaths.length > 0); if (hasExcludeCommitPaths) { config.excludeCommitPaths = Object.keys(config.upgrades.reduce((acc, upgrade) => { if (upgrade.excludeCommitPaths) { upgrade.excludeCommitPaths.forEach((p) => { acc[p] = true; }); } return acc; }, {})); } config.automerge = config.upgrades.every((upgrade) => upgrade.automerge); // combine all labels config.labels = [ ...new Set(config.upgrades .map((upgrade) => upgrade.labels ?? []) .reduce((a, b) => a.concat(b), [])), ]; config.addLabels = [ ...new Set(config.upgrades .map((upgrade) => upgrade.addLabels ?? []) .reduce((a, b) => a.concat(b), [])), ]; if (config.upgrades.some((upgrade) => upgrade.updateType === 'major')) { config.updateType = 'major'; } config.isBreaking = config.upgrades.some((upgrade) => upgrade.isBreaking); // explicit set `isLockFileMaintenance` for the branch for groups if (config.upgrades.some((upgrade) => upgrade.isLockFileMaintenance)) { config.isLockFileMaintenance = true; // istanbul ignore if: not worth testing if (config.upgrades.some((upgrade) => !upgrade.isLockFileMaintenance)) { // TODO: warn? logger_1.logger.debug('Grouping lockfile maintenance with other update types is not supported'); } } config.constraints = {}; for (const upgrade of config.upgrades) { if (upgrade.constraints) { config.constraints = { ...config.constraints, ...upgrade.constraints }; } } config.minimumGroupSize = getMinimumGroupSize(config.upgrades); // Set skipInstalls to false if any upgrade in the branch has it false config.skipInstalls = config.upgrades.every((upgrade) => upgrade.skipInstalls !== false); // Artifact updating will only be skipped if every upgrade wants to skip it. config.skipArtifactsUpdate = config.upgrades.every((upgrade) => upgrade.skipArtifactsUpdate); if (!config.skipArtifactsUpdate && config.upgrades.some((upgrade) => upgrade.skipArtifactsUpdate)) { logger_1.logger.debug({ upgrades: config.upgrades.map((upgrade) => ({ depName: upgrade.depName, skipArtifactsUpdate: upgrade.skipArtifactsUpdate, })), }, 'Mixed `skipArtifactsUpdate` values in upgrades. Artifacts will be updated.'); } const tableRows = config.upgrades .map(getTableValues) .filter((x) => is_1.default.array(x, is_1.default.string)); if (tableRows.length) { const table = []; table.push(['datasource', 'package', 'from', 'to']); const seenRows = new Set(); for (const row of tableRows) { const key = (0, stringify_1.safeStringify)(row); if (seenRows.has(key)) { continue; } seenRows.add(key); table.push(row); } config.commitMessage += '\n\n' + (0, markdown_table_1.markdownTable)(table) + '\n'; } const additionalReviewers = (0, uniq_1.uniq)(config.upgrades .map((upgrade) => upgrade.additionalReviewers) .flat() .filter(is_1.default.nonEmptyString)); if (additionalReviewers.length > 0) { config.additionalReviewers = additionalReviewers; } return config; } //# sourceMappingURL=generate.js.map