mirror of
https://github.com/renovatebot/renovate.git
synced 2025-01-13 07:26:26 +00:00
321 lines
11 KiB
TypeScript
321 lines
11 KiB
TypeScript
import { DateTime } from 'luxon';
|
|
import mdTable from 'markdown-table';
|
|
import semver from 'semver';
|
|
import { mergeChildConfig } from '../../../config';
|
|
import { CONFIG_SECRETS_EXPOSED } from '../../../constants/error-messages';
|
|
import { logger } from '../../../logger';
|
|
import { sanitize } from '../../../util/sanitize';
|
|
import * as template from '../../../util/template';
|
|
import type { BranchConfig, BranchUpgradeConfig } from '../../types';
|
|
import { formatCommitMessagePrefix } from '../util/commit-message';
|
|
|
|
function isTypesGroup(branchUpgrades: BranchUpgradeConfig[]): boolean {
|
|
return (
|
|
branchUpgrades.some(({ depName }) => depName?.startsWith('@types/')) &&
|
|
new Set(
|
|
branchUpgrades.map(({ depName }) => depName?.replace(/^@types\//, ''))
|
|
).size === 1
|
|
);
|
|
}
|
|
|
|
function sortTypesGroup(upgrades: BranchUpgradeConfig[]): void {
|
|
const isTypesUpgrade = ({ depName }: BranchUpgradeConfig): boolean =>
|
|
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: BranchUpgradeConfig
|
|
): [string, string, string, string] | null {
|
|
if (!upgrade.commitBodyTable) {
|
|
return null;
|
|
}
|
|
const {
|
|
datasource,
|
|
lookupName,
|
|
depName,
|
|
currentVersion,
|
|
newVersion,
|
|
} = upgrade;
|
|
const name = lookupName || depName;
|
|
if (datasource && name && currentVersion && newVersion) {
|
|
return [datasource, name, currentVersion, newVersion];
|
|
}
|
|
logger.debug(
|
|
{
|
|
datasource,
|
|
lookupName,
|
|
depName,
|
|
currentVersion,
|
|
newVersion,
|
|
},
|
|
'Cannot determine table values'
|
|
);
|
|
return null;
|
|
}
|
|
|
|
export function generateBranchConfig(
|
|
branchUpgrades: BranchUpgradeConfig[]
|
|
): BranchConfig {
|
|
logger.trace({ config: branchUpgrades }, 'generateBranchConfig');
|
|
let config: BranchConfig = {
|
|
upgrades: [],
|
|
} as any;
|
|
const hasGroupName = branchUpgrades[0].groupName !== null;
|
|
logger.trace(`hasGroupName: ${hasGroupName}`);
|
|
// Use group settings only if multiple upgrades or lazy grouping is disabled
|
|
const depNames: string[] = [];
|
|
const newValue: string[] = [];
|
|
const toVersions: string[] = [];
|
|
branchUpgrades.forEach((upg) => {
|
|
if (!depNames.includes(upg.depName)) {
|
|
depNames.push(upg.depName);
|
|
}
|
|
if (!toVersions.includes(upg.newVersion)) {
|
|
toVersions.push(upg.newVersion);
|
|
}
|
|
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);
|
|
if (newValue.length > 1 && !groupEligible) {
|
|
// eslint-disable-next-line no-param-reassign
|
|
branchUpgrades[0].commitMessageExtra = `to v${toVersions[0]}`;
|
|
}
|
|
const typesGroup =
|
|
depNames.length > 1 && !hasGroupName && isTypesGroup(branchUpgrades);
|
|
logger.trace(`groupEligible: ${groupEligible}`);
|
|
const useGroupSettings = hasGroupName && groupEligible;
|
|
logger.trace(`useGroupSettings: ${useGroupSettings}`);
|
|
let releaseTimestamp: string;
|
|
for (const branchUpgrade of branchUpgrades) {
|
|
let upgrade: BranchUpgradeConfig = { ...branchUpgrade };
|
|
if (upgrade.currentDigest) {
|
|
upgrade.currentDigestShort =
|
|
upgrade.currentDigestShort ||
|
|
upgrade.currentDigest.replace('sha256:', '').substring(0, 7);
|
|
}
|
|
if (upgrade.newDigest) {
|
|
upgrade.newDigestShort =
|
|
upgrade.newDigestShort ||
|
|
upgrade.newDigest.replace('sha256:', '').substring(0, 7);
|
|
}
|
|
if (upgrade.isDigest) {
|
|
upgrade.displayFrom = upgrade.currentDigestShort;
|
|
upgrade.displayTo = upgrade.newDigestShort;
|
|
} else if (upgrade.isLockfileUpdate) {
|
|
upgrade.displayFrom = upgrade.currentVersion;
|
|
upgrade.displayTo = upgrade.newVersion;
|
|
} else if (!upgrade.isLockFileMaintenance) {
|
|
upgrade.displayFrom = upgrade.currentValue;
|
|
upgrade.displayTo = upgrade.newValue;
|
|
}
|
|
upgrade.displayFrom ??= '';
|
|
upgrade.displayTo ??= '';
|
|
upgrade.prettyDepType =
|
|
upgrade.prettyDepType || upgrade.depType || 'dependency';
|
|
if (useGroupSettings) {
|
|
// Now overwrite original config with group config
|
|
upgrade = mergeChildConfig(upgrade, upgrade.group);
|
|
upgrade.isGroup = true;
|
|
} else {
|
|
delete upgrade.groupName;
|
|
}
|
|
// Delete group config regardless of whether it was applied
|
|
delete upgrade.group;
|
|
|
|
// istanbul ignore else
|
|
if (toVersions.length > 1 && !typesGroup) {
|
|
logger.trace({ toVersions });
|
|
delete upgrade.commitMessageExtra;
|
|
upgrade.recreateClosed = true;
|
|
} else if (newValue.length > 1 && upgrade.isDigest) {
|
|
logger.trace({ newValue });
|
|
delete upgrade.commitMessageExtra;
|
|
upgrade.recreateClosed = true;
|
|
} else if (semver.valid(toVersions[0])) {
|
|
upgrade.isRange = false;
|
|
}
|
|
// Use templates to generate strings
|
|
if (upgrade.semanticCommits === 'enabled' && !upgrade.commitMessagePrefix) {
|
|
logger.trace('Upgrade has semantic commits enabled');
|
|
let semanticPrefix = upgrade.semanticCommitType;
|
|
if (upgrade.semanticCommitScope) {
|
|
semanticPrefix += `(${template.compile(
|
|
upgrade.semanticCommitScope,
|
|
upgrade
|
|
)})`;
|
|
}
|
|
upgrade.commitMessagePrefix = formatCommitMessagePrefix(semanticPrefix);
|
|
upgrade.toLowerCase =
|
|
// eslint-disable-next-line @typescript-eslint/prefer-regexp-exec
|
|
upgrade.semanticCommitType.match(/[A-Z]/) === 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 !== sanitize(upgrade.commitMessage)) {
|
|
throw new Error(CONFIG_SECRETS_EXPOSED);
|
|
}
|
|
upgrade.commitMessage = upgrade.commitMessage.trim(); // Trim exterior whitespace
|
|
upgrade.commitMessage = upgrade.commitMessage.replace(/\s+/g, ' '); // Trim extra whitespace inside string
|
|
upgrade.commitMessage = upgrade.commitMessage.replace(
|
|
/to vv(\d)/,
|
|
'to v$1'
|
|
);
|
|
if (upgrade.toLowerCase) {
|
|
// We only need to lowercase the first line
|
|
const splitMessage = upgrade.commitMessage.split('\n');
|
|
splitMessage[0] = splitMessage[0].toLowerCase();
|
|
upgrade.commitMessage = splitMessage.join('\n');
|
|
}
|
|
if (upgrade.commitBody) {
|
|
upgrade.commitMessage = `${upgrade.commitMessage}\n\n${template.compile(
|
|
upgrade.commitBody,
|
|
upgrade
|
|
)}`;
|
|
}
|
|
logger.trace(`commitMessage: ` + JSON.stringify(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(/\s+/g, ' ');
|
|
// istanbul ignore if
|
|
if (upgrade.prTitle !== sanitize(upgrade.prTitle)) {
|
|
throw new Error(CONFIG_SECRETS_EXPOSED);
|
|
}
|
|
if (upgrade.toLowerCase) {
|
|
upgrade.prTitle = upgrade.prTitle.toLowerCase();
|
|
}
|
|
} else {
|
|
[upgrade.prTitle] = upgrade.commitMessage.split('\n');
|
|
}
|
|
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' ? ' (patch)' : '';
|
|
}
|
|
// Compile again to allow for nested templates
|
|
upgrade.prTitle = template.compile(upgrade.prTitle, upgrade);
|
|
logger.trace(`prTitle: ` + JSON.stringify(upgrade.prTitle));
|
|
config.upgrades.push(upgrade);
|
|
if (upgrade.releaseTimestamp) {
|
|
if (releaseTimestamp) {
|
|
const existingStamp = DateTime.fromISO(releaseTimestamp);
|
|
const upgradeStamp = DateTime.fromISO(upgrade.releaseTimestamp);
|
|
if (upgradeStamp > existingStamp) {
|
|
releaseTimestamp = upgrade.releaseTimestamp; // eslint-disable-line
|
|
}
|
|
} else {
|
|
releaseTimestamp = upgrade.releaseTimestamp; // eslint-disable-line
|
|
}
|
|
}
|
|
}
|
|
|
|
if (typesGroup) {
|
|
if (config.upgrades[0].depName?.startsWith('@types/')) {
|
|
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 }; // TODO: fixme
|
|
config.reuseLockFiles = config.upgrades.every(
|
|
(upgrade) => upgrade.updateType !== 'lockFileMaintenance'
|
|
);
|
|
config.dependencyDashboardApproval = config.upgrades.some(
|
|
(upgrade) => upgrade.dependencyDashboardApproval
|
|
);
|
|
config.dependencyDashboardPrApproval = config.upgrades.some(
|
|
(upgrade) => upgrade.prCreation === 'approval'
|
|
);
|
|
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.constraints = {};
|
|
for (const upgrade of config.upgrades || []) {
|
|
if (upgrade.constraints) {
|
|
config.constraints = { ...config.constraints, ...upgrade.constraints };
|
|
}
|
|
}
|
|
const tableRows = config.upgrades
|
|
.map((upgrade) => getTableValues(upgrade))
|
|
.filter(Boolean);
|
|
if (tableRows.length) {
|
|
let table = [];
|
|
table.push(['datasource', 'package', 'from', 'to']);
|
|
table = table.concat(tableRows);
|
|
config.commitMessage += '\n\n' + mdTable(table) + '\n';
|
|
}
|
|
return config;
|
|
}
|