mirror of
https://github.com/renovatebot/renovate.git
synced 2025-01-12 23:16:26 +00:00
89d7fdf781
This reverts commit 39471b57ad
.
362 lines
12 KiB
TypeScript
362 lines
12 KiB
TypeScript
import is from '@sindresorhus/is';
|
|
import { nameFromLevel } from 'bunyan';
|
|
import { GlobalConfig } from '../../config/global';
|
|
import type { RenovateConfig } from '../../config/types';
|
|
import { getProblems, logger } from '../../logger';
|
|
import { platform } from '../../modules/platform';
|
|
import { regEx } from '../../util/regex';
|
|
import * as template from '../../util/template';
|
|
import { BranchConfig, BranchResult } from '../types';
|
|
|
|
interface DependencyDashboard {
|
|
dependencyDashboardChecks: Record<string, string>;
|
|
dependencyDashboardRebaseAllOpen: boolean;
|
|
}
|
|
|
|
function parseDashboardIssue(issueBody: string): DependencyDashboard {
|
|
const checkMatch = ' - \\[x\\] <!-- ([a-zA-Z]+)-branch=([^\\s]+) -->';
|
|
const checked = issueBody.match(regEx(checkMatch, 'g'));
|
|
const dependencyDashboardChecks: Record<string, string> = {};
|
|
if (checked?.length) {
|
|
const re = regEx(checkMatch);
|
|
checked.forEach((check) => {
|
|
const [, type, branchName] = re.exec(check);
|
|
dependencyDashboardChecks[branchName] = type;
|
|
});
|
|
}
|
|
const checkedRebaseAll = issueBody.includes(
|
|
' - [x] <!-- rebase-all-open-prs -->'
|
|
);
|
|
let dependencyDashboardRebaseAllOpen = false;
|
|
if (checkedRebaseAll) {
|
|
dependencyDashboardRebaseAllOpen = true;
|
|
}
|
|
return { dependencyDashboardChecks, dependencyDashboardRebaseAllOpen };
|
|
}
|
|
|
|
export async function readDashboardBody(config: RenovateConfig): Promise<void> {
|
|
config.dependencyDashboardChecks = {};
|
|
const stringifiedConfig = JSON.stringify(config);
|
|
if (
|
|
config.dependencyDashboard ||
|
|
stringifiedConfig.includes('"dependencyDashboardApproval":true') ||
|
|
stringifiedConfig.includes('"prCreation":"approval"')
|
|
) {
|
|
config.dependencyDashboardTitle =
|
|
config.dependencyDashboardTitle || `Dependency Dashboard`;
|
|
const issue = await platform.findIssue(config.dependencyDashboardTitle);
|
|
if (issue) {
|
|
config.dependencyDashboardIssue = issue.number;
|
|
Object.assign(config, parseDashboardIssue(issue.body));
|
|
}
|
|
}
|
|
}
|
|
|
|
function getListItem(branch: BranchConfig, type: string): string {
|
|
let item = ' - [ ] ';
|
|
item += `<!-- ${type}-branch=${branch.branchName} -->`;
|
|
if (branch.prNo) {
|
|
item += `[${branch.prTitle}](../pull/${branch.prNo})`;
|
|
} else {
|
|
item += branch.prTitle;
|
|
}
|
|
const uniquePackages = [
|
|
...new Set(branch.upgrades.map((upgrade) => '`' + upgrade.depName + '`')),
|
|
];
|
|
if (uniquePackages.length < 2) {
|
|
return item + '\n';
|
|
}
|
|
return item + ' (' + uniquePackages.join(', ') + ')\n';
|
|
}
|
|
|
|
function appendRepoProblems(config: RenovateConfig, issueBody: string): string {
|
|
let newIssueBody = issueBody;
|
|
const repoProblems = new Set(
|
|
getProblems()
|
|
.filter(
|
|
(problem) =>
|
|
problem.repository === config.repository && !problem.artifactErrors
|
|
)
|
|
.map(
|
|
(problem) =>
|
|
`${nameFromLevel[problem.level].toUpperCase()}: ${problem.msg}`
|
|
)
|
|
);
|
|
if (repoProblems.size) {
|
|
newIssueBody += '## Repository problems\n\n';
|
|
newIssueBody +=
|
|
'These problems occurred while renovating this repository.\n\n';
|
|
for (const repoProblem of repoProblems) {
|
|
newIssueBody += ` - ${repoProblem}\n`;
|
|
}
|
|
newIssueBody += '\n';
|
|
}
|
|
return newIssueBody;
|
|
}
|
|
|
|
export async function ensureDependencyDashboard(
|
|
config: RenovateConfig,
|
|
allBranches: BranchConfig[]
|
|
): Promise<void> {
|
|
// legacy/migrated issue
|
|
const reuseTitle = 'Update Dependencies (Renovate Bot)';
|
|
const branches = allBranches.filter(
|
|
(branch) =>
|
|
branch.result !== BranchResult.Automerged &&
|
|
!branch.upgrades?.every((upgrade) => upgrade.remediationNotPossible)
|
|
);
|
|
if (
|
|
!(
|
|
config.dependencyDashboard ||
|
|
config.dependencyDashboardApproval ||
|
|
config.packageRules?.some((rule) => rule.dependencyDashboardApproval) ||
|
|
branches.some(
|
|
(branch) =>
|
|
branch.dependencyDashboardApproval ||
|
|
branch.dependencyDashboardPrApproval
|
|
)
|
|
)
|
|
) {
|
|
if (GlobalConfig.get('dryRun')) {
|
|
logger.info(
|
|
{ title: config.dependencyDashboardTitle },
|
|
'DRY-RUN: Would close Dependency Dashboard'
|
|
);
|
|
} else {
|
|
logger.debug('Closing Dependency Dashboard');
|
|
await platform.ensureIssueClosing(config.dependencyDashboardTitle);
|
|
}
|
|
return;
|
|
}
|
|
// istanbul ignore if
|
|
if (config.repoIsOnboarded === false) {
|
|
logger.debug('Repo is onboarding - skipping dependency dashboard');
|
|
return;
|
|
}
|
|
logger.debug('Ensuring Dependency Dashboard');
|
|
const hasBranches = is.nonEmptyArray(branches);
|
|
if (config.dependencyDashboardAutoclose && !hasBranches) {
|
|
if (GlobalConfig.get('dryRun')) {
|
|
logger.info(
|
|
{ title: config.dependencyDashboardTitle },
|
|
'DRY-RUN: Would close Dependency Dashboard'
|
|
);
|
|
} else {
|
|
logger.debug('Closing Dependency Dashboard');
|
|
await platform.ensureIssueClosing(config.dependencyDashboardTitle);
|
|
}
|
|
return;
|
|
}
|
|
let issueBody = '';
|
|
if (config.dependencyDashboardHeader?.length) {
|
|
issueBody +=
|
|
template.compile(config.dependencyDashboardHeader, config) + '\n\n';
|
|
}
|
|
|
|
issueBody = appendRepoProblems(config, issueBody);
|
|
|
|
const pendingApprovals = branches.filter(
|
|
(branch) => branch.result === BranchResult.NeedsApproval
|
|
);
|
|
if (pendingApprovals.length) {
|
|
issueBody += '## Pending Approval\n\n';
|
|
issueBody += `These branches will be created by Renovate only once you click their checkbox below.\n\n`;
|
|
for (const branch of pendingApprovals) {
|
|
issueBody += getListItem(branch, 'approve');
|
|
}
|
|
issueBody += '\n';
|
|
}
|
|
const awaitingSchedule = branches.filter(
|
|
(branch) => branch.result === BranchResult.NotScheduled
|
|
);
|
|
if (awaitingSchedule.length) {
|
|
issueBody += '## Awaiting Schedule\n\n';
|
|
issueBody +=
|
|
'These updates are awaiting their schedule. Click on a checkbox to get an update now.\n\n';
|
|
for (const branch of awaitingSchedule) {
|
|
issueBody += getListItem(branch, 'unschedule');
|
|
}
|
|
issueBody += '\n';
|
|
}
|
|
const rateLimited = branches.filter(
|
|
(branch) =>
|
|
branch.result === BranchResult.BranchLimitReached ||
|
|
branch.result === BranchResult.PrLimitReached ||
|
|
branch.result === BranchResult.CommitLimitReached
|
|
);
|
|
if (rateLimited.length) {
|
|
issueBody += '## Rate Limited\n\n';
|
|
issueBody +=
|
|
'These updates are currently rate limited. Click on a checkbox below to force their creation now.\n\n';
|
|
for (const branch of rateLimited) {
|
|
issueBody += getListItem(branch, 'unlimit');
|
|
}
|
|
issueBody += '\n';
|
|
}
|
|
const errorList = branches.filter(
|
|
(branch) => branch.result === BranchResult.Error
|
|
);
|
|
if (errorList.length) {
|
|
issueBody += '## Errored\n\n';
|
|
issueBody +=
|
|
'These updates encountered an error and will be retried. Click on a checkbox below to force a retry now.\n\n';
|
|
for (const branch of errorList) {
|
|
issueBody += getListItem(branch, 'retry');
|
|
}
|
|
issueBody += '\n';
|
|
}
|
|
const awaitingPr = branches.filter(
|
|
(branch) => branch.result === BranchResult.NeedsPrApproval
|
|
);
|
|
if (awaitingPr.length) {
|
|
issueBody += '## PR Creation Approval Required\n\n';
|
|
issueBody +=
|
|
"These branches exist but PRs won't be created until you approve them by clicking on a checkbox.\n\n";
|
|
for (const branch of awaitingPr) {
|
|
issueBody += getListItem(branch, 'approvePr');
|
|
}
|
|
issueBody += '\n';
|
|
}
|
|
const prEdited = branches.filter(
|
|
(branch) => branch.result === BranchResult.PrEdited
|
|
);
|
|
if (prEdited.length) {
|
|
issueBody += '## Edited/Blocked\n\n';
|
|
issueBody += `These updates have been manually edited so Renovate will no longer make changes. To discard all commits and start over, click on a checkbox.\n\n`;
|
|
for (const branch of prEdited) {
|
|
issueBody += getListItem(branch, 'rebase');
|
|
}
|
|
issueBody += '\n';
|
|
}
|
|
const prPending = branches.filter(
|
|
(branch) => branch.result === BranchResult.Pending
|
|
);
|
|
if (prPending.length) {
|
|
issueBody += '## Pending Status Checks\n\n';
|
|
issueBody += `These updates await pending status checks. To force their creation now, click the checkbox below.\n\n`;
|
|
for (const branch of prPending) {
|
|
issueBody += getListItem(branch, 'approvePr');
|
|
}
|
|
issueBody += '\n';
|
|
}
|
|
const prPendingBranchAutomerge = branches.filter(
|
|
(branch) => branch.prBlockedBy === 'BranchAutomerge'
|
|
);
|
|
if (prPendingBranchAutomerge.length) {
|
|
issueBody += '## Pending Branch Automerge\n\n';
|
|
issueBody += `These updates await pending status checks before automerging. Click on a checkbox to abort the branch automerge, and create a PR instead.\n\n`;
|
|
for (const branch of prPendingBranchAutomerge) {
|
|
issueBody += getListItem(branch, 'approvePr');
|
|
}
|
|
issueBody += '\n';
|
|
}
|
|
const otherRes = [
|
|
BranchResult.Pending,
|
|
BranchResult.NeedsApproval,
|
|
BranchResult.NeedsPrApproval,
|
|
BranchResult.NotScheduled,
|
|
BranchResult.PrLimitReached,
|
|
BranchResult.CommitLimitReached,
|
|
BranchResult.BranchLimitReached,
|
|
BranchResult.AlreadyExisted,
|
|
BranchResult.Error,
|
|
BranchResult.Automerged,
|
|
BranchResult.PrEdited,
|
|
];
|
|
let inProgress = branches.filter(
|
|
(branch) =>
|
|
!otherRes.includes(branch.result) &&
|
|
branch.prBlockedBy !== 'BranchAutomerge'
|
|
);
|
|
const otherBranches = inProgress.filter(
|
|
(branch) => branch.prBlockedBy || !branch.prNo
|
|
);
|
|
// istanbul ignore if
|
|
if (otherBranches.length) {
|
|
issueBody += '## Other Branches\n\n';
|
|
issueBody += `These updates are pending. To force PRs open, click the checkbox below.\n\n`;
|
|
for (const branch of otherBranches) {
|
|
issueBody += getListItem(branch, 'other');
|
|
}
|
|
issueBody += '\n';
|
|
}
|
|
inProgress = inProgress.filter(
|
|
(branch) => branch.prNo && !branch.prBlockedBy
|
|
);
|
|
if (inProgress.length) {
|
|
issueBody += '## Open\n\n';
|
|
issueBody +=
|
|
'These updates have all been created already. Click a checkbox below to force a retry/rebase of any.\n\n';
|
|
for (const branch of inProgress) {
|
|
issueBody += getListItem(branch, 'rebase');
|
|
}
|
|
if (inProgress.length > 2) {
|
|
issueBody += ' - [ ] ';
|
|
issueBody += '<!-- rebase-all-open-prs -->';
|
|
issueBody += '**Click on this checkbox to rebase all open PRs at once**';
|
|
issueBody += '\n';
|
|
}
|
|
issueBody += '\n';
|
|
}
|
|
const alreadyExisted = branches.filter(
|
|
(branch) => branch.result === BranchResult.AlreadyExisted
|
|
);
|
|
if (alreadyExisted.length) {
|
|
issueBody += '## Ignored or Blocked\n\n';
|
|
issueBody +=
|
|
'These are blocked by an existing closed PR and will not be recreated unless you click a checkbox below.\n\n';
|
|
for (const branch of alreadyExisted) {
|
|
issueBody += getListItem(branch, 'recreate');
|
|
}
|
|
issueBody += '\n';
|
|
}
|
|
|
|
if (!hasBranches) {
|
|
issueBody +=
|
|
'This repository currently has no open or pending branches.\n\n';
|
|
}
|
|
|
|
if (config.dependencyDashboardFooter?.length) {
|
|
issueBody +=
|
|
'---\n' +
|
|
template.compile(config.dependencyDashboardFooter, config) +
|
|
'\n';
|
|
}
|
|
|
|
if (config.dependencyDashboardIssue) {
|
|
const updatedIssue = await platform.getIssue?.(
|
|
config.dependencyDashboardIssue,
|
|
false
|
|
);
|
|
if (updatedIssue) {
|
|
const { dependencyDashboardChecks } = parseDashboardIssue(
|
|
updatedIssue.body
|
|
);
|
|
for (const branchName of Object.keys(config.dependencyDashboardChecks)) {
|
|
delete dependencyDashboardChecks[branchName];
|
|
}
|
|
for (const branchName of Object.keys(dependencyDashboardChecks)) {
|
|
const checkText = `- [ ] <!-- ${dependencyDashboardChecks[branchName]}-branch=${branchName} -->`;
|
|
issueBody = issueBody.replace(
|
|
checkText,
|
|
checkText.replace('[ ]', '[x]')
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (GlobalConfig.get('dryRun')) {
|
|
logger.info(
|
|
{ title: config.dependencyDashboardTitle },
|
|
'DRY-RUN: Would ensure Dependency Dashboard'
|
|
);
|
|
} else {
|
|
await platform.ensureIssue({
|
|
title: config.dependencyDashboardTitle,
|
|
reuseTitle,
|
|
body: issueBody,
|
|
labels: config.dependencyDashboardLabels,
|
|
confidential: config.confidential,
|
|
});
|
|
}
|
|
}
|