import { regEx } from "../../util/regex.js";
import { GlobalConfig } from "../../config/global.js";
import { coerceString } from "../../util/string.js";
import { logger } from "../../logger/index.js";
import { coerceArray } from "../../util/array.js";
import { compile } from "../../util/template/index.js";
import { emojify } from "../../util/emoji.js";
import { platform } from "../../modules/platform/index.js";
import { extractRepoProblems } from "./common.js";
import { getDepWarningsDashboard } from "./errors-warnings.js";
import { PackageFiles } from "./package-files.js";
import { Vulnerabilities } from "./process/vulnerabilities.js";
import { isNonEmptyArray, isNonEmptyObject, isNonEmptyString, isNullOrUndefined, isTruthy } from "@sindresorhus/is";
import { DateTime } from "luxon";
//#region lib/workers/repository/dependency-dashboard.ts
const rateLimitedRe = regEx(` - \\[ \\] ${getMarkdownComment("unlimit-branch=([^\\s]+)")}`, "g");
const pendingApprovalRe = regEx(` - \\[ \\] ${getMarkdownComment("approve-branch=([^\\s]+)")}`, "g");
const awaitingScheduleRe = regEx(` - \\[ \\] ${getMarkdownComment("unschedule-branch=([^\\s]+)")}`, "g");
const generalBranchRe = regEx(` ${getMarkdownComment("([a-zA-Z]+)-branch=([^\\s]+)")}`);
const markedBranchesRe = regEx(` - \\[x\\] ${getMarkdownComment("([a-zA-Z]+)-branch=([^\\s]+)")}`, "g");
const approveAllPendingPrs = "approve-all-pending-prs";
const createAllRateLimitedPrs = "create-all-rate-limited-prs";
const createAllAwaitingSchedulePrs = "create-all-awaiting-schedule-prs";
const createConfigMigrationPr = "create-config-migration-pr";
const configMigrationPrInfo = "config-migration-pr-info";
const rebaseAllOpenPrs = "rebase-all-open-prs";
function getMarkdownComment(comment) {
	return `<!-- ${comment} -->`;
}
function isBoxChecked(issueBody, type) {
	return issueBody.includes(getCheckbox(type, true));
}
function isBoxUnchecked(issueBody, type) {
	return issueBody.includes(getCheckbox(type));
}
function getCheckbox(type, checked = false) {
	return ` - [${checked ? "x" : " "}] ${getMarkdownComment(type)}`;
}
function checkOpenAllRateLimitedPR(issueBody) {
	return isBoxChecked(issueBody, createAllRateLimitedPrs);
}
function checkOpenAllAwaitingSchedulePR(issueBody) {
	return isBoxChecked(issueBody, createAllAwaitingSchedulePrs);
}
function checkApproveAllPendingPR(issueBody) {
	return isBoxChecked(issueBody, approveAllPendingPrs);
}
function checkRebaseAll(issueBody) {
	return isBoxChecked(issueBody, rebaseAllOpenPrs);
}
function getConfigMigrationCheckboxState(issueBody) {
	if (issueBody.includes(getMarkdownComment(configMigrationPrInfo))) return "migration-pr-exists";
	if (isBoxChecked(issueBody, createConfigMigrationPr)) return "checked";
	if (isBoxUnchecked(issueBody, createConfigMigrationPr)) return "unchecked";
	return "no-checkbox";
}
function selectAllRelevantBranches(issueBody) {
	const checkedBranches = [];
	if (checkOpenAllRateLimitedPR(issueBody)) for (const match of issueBody.matchAll(rateLimitedRe)) checkedBranches.push(match[0]);
	if (checkOpenAllAwaitingSchedulePR(issueBody)) for (const match of issueBody.matchAll(awaitingScheduleRe)) checkedBranches.push(match[0]);
	if (checkApproveAllPendingPR(issueBody)) for (const match of issueBody.matchAll(pendingApprovalRe)) checkedBranches.push(match[0]);
	return checkedBranches;
}
function getAllSelectedBranches(issueBody, dependencyDashboardChecks) {
	const allRelevantBranches = selectAllRelevantBranches(issueBody);
	for (const branch of allRelevantBranches) {
		const [, type, branchName] = generalBranchRe.exec(branch);
		dependencyDashboardChecks[branchName] = type;
	}
	return dependencyDashboardChecks;
}
function getCheckedBranches(issueBody) {
	let dependencyDashboardChecks = {};
	for (const [, type, branchName] of issueBody.matchAll(markedBranchesRe)) dependencyDashboardChecks[branchName] = type;
	dependencyDashboardChecks = getAllSelectedBranches(issueBody, dependencyDashboardChecks);
	return dependencyDashboardChecks;
}
function parseDashboardIssue(issueBody) {
	const dependencyDashboardChecks = getCheckedBranches(issueBody);
	const dependencyDashboardRebaseAllOpen = checkRebaseAll(issueBody);
	const dependencyDashboardAllAwaitingSchedule = checkOpenAllAwaitingSchedulePR(issueBody);
	const dependencyDashboardAllPending = checkApproveAllPendingPR(issueBody);
	const dependencyDashboardAllRateLimited = checkOpenAllRateLimitedPR(issueBody);
	dependencyDashboardChecks.configMigrationCheckboxState = getConfigMigrationCheckboxState(issueBody);
	return {
		dependencyDashboardChecks,
		dependencyDashboardRebaseAllOpen,
		dependencyDashboardAllAwaitingSchedule,
		dependencyDashboardAllPending,
		dependencyDashboardAllRateLimited
	};
}
async function readDashboardBody(config) {
	let dashboardChecks = {
		dependencyDashboardChecks: {},
		dependencyDashboardRebaseAllOpen: false,
		dependencyDashboardAllAwaitingSchedule: false,
		dependencyDashboardAllPending: false,
		dependencyDashboardAllRateLimited: false
	};
	const stringifiedConfig = JSON.stringify(config);
	if (config.dependencyDashboard === true || 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;
			dashboardChecks = parseDashboardIssue(issue.body ?? "");
		}
	}
	if (config.checkedBranches) {
		const checkedBranchesRec = Object.fromEntries(config.checkedBranches.map((branchName) => [branchName, "global-config"]));
		dashboardChecks.dependencyDashboardChecks = {
			...dashboardChecks.dependencyDashboardChecks,
			...checkedBranchesRec
		};
	}
	Object.assign(config, dashboardChecks);
}
function formatAsMarkdownLink(name, url) {
	return url ? `[${name}](${url})` : `\`${name}\``;
}
function getListItem(branch, type) {
	let item = getCheckbox(`${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 splitBranchesByCategory(filteredBranches) {
	const categories = {};
	const uncategorized = [];
	let hasCategorized = false;
	let hasUncategorized = false;
	for (const branch of filteredBranches) {
		if (branch.dependencyDashboardCategory) {
			categories[branch.dependencyDashboardCategory] ??= [];
			categories[branch.dependencyDashboardCategory].push(branch);
			hasCategorized = true;
			continue;
		}
		uncategorized.push(branch);
		hasUncategorized = true;
	}
	return {
		categories,
		uncategorized,
		hasCategorized,
		hasUncategorized
	};
}
function getBranchList(branches, listItemType) {
	return branches.map((branch) => getListItem(branch, listItemType)).join("");
}
function getBranchesListMd(branches, predicate, title, description, listItemType = "approvePr", bulkComment, bulkMessage, bulkIcon) {
	const filteredBranches = branches.filter(predicate);
	if (filteredBranches.length === 0) return "";
	let result = `## ${title}\n\n${description}\n\n`;
	const { categories, uncategorized, hasCategorized, hasUncategorized } = splitBranchesByCategory(filteredBranches);
	if (hasCategorized) {
		for (const [category, branches] of Object.entries(categories).sort(([keyA], [keyB]) => keyA.localeCompare(keyB, void 0, { numeric: true }))) {
			result = result.trimEnd() + "\n\n";
			result += `### ${category}\n\n`;
			result += getBranchList(branches, listItemType);
		}
		if (hasUncategorized) {
			result = result.trimEnd() + "\n\n";
			result += `### Others`;
		}
	}
	result = result.trimEnd() + "\n\n";
	result += getBranchList(uncategorized, listItemType);
	if (bulkComment && bulkMessage && filteredBranches.length > 1) {
		if (hasCategorized) {
			result = result.trimEnd() + "\n\n";
			result += "### All\n\n";
		}
		result += getCheckbox(bulkComment);
		result += `${bulkIcon ? bulkIcon + " " : ""}**${bulkMessage}**${bulkIcon ? " " + bulkIcon : ""}`;
	}
	return result.trimEnd() + "\n\n";
}
function appendRepoProblems(config, issueBody) {
	let newIssueBody = issueBody;
	const repoProblems = extractRepoProblems(config.repository);
	if (repoProblems.size) {
		newIssueBody += "## Repository Problems\n\n";
		const repoProblemsHeader = config.customizeDashboard?.repoProblemsHeader ?? "Renovate tried to run on this repository, but found these problems.";
		newIssueBody += compile(repoProblemsHeader, config) + "\n\n";
		for (const repoProblem of repoProblems) newIssueBody += ` - ${repoProblem}\n`;
		newIssueBody += "\n";
	}
	return newIssueBody;
}
async function ensureDependencyDashboard(config, allBranches, packageFiles = {}, configMigrationRes) {
	logger.debug("ensureDependencyDashboard()");
	if (config.mode === "silent") {
		logger.debug("Dependency Dashboard issue is not created, updated or closed when mode=silent");
		return;
	}
	const reuseTitle = "Update Dependencies (Renovate Bot)";
	const branches = allBranches.filter((branch) => branch.result !== "automerged" && !branch.upgrades?.every((upgrade) => upgrade.remediationNotPossible));
	if (!(config.dependencyDashboard === true || config.dependencyDashboardApproval === true || config.packageRules?.some((rule) => rule.dependencyDashboardApproval) === true || 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");
	let hasDeprecationsOrReplacements = false;
	const deprecatedPackages = {};
	logger.debug("Checking packageFiles for deprecated or replacement packages");
	if (isNonEmptyObject(packageFiles)) for (const [manager, fileNames] of Object.entries(packageFiles)) for (const fileName of fileNames) for (const dep of fileName.deps) {
		const name = dep.packageName ?? dep.depName;
		const hasReplacement = !!dep.updates?.find((updates) => updates.updateType === "replacement");
		if (name && (dep.deprecationMessage ?? hasReplacement)) {
			hasDeprecationsOrReplacements = true;
			deprecatedPackages[manager] ??= {};
			deprecatedPackages[manager][name] ??= {
				hasReplacement,
				sourceUrl: dep.sourceUrl
			};
		}
	}
	const hasBranches = isNonEmptyArray(branches);
	if (config.dependencyDashboardAutoclose && !hasBranches && !hasDeprecationsOrReplacements) {
		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 += compile(config.dependencyDashboardHeader, config) + "\n\n";
	if (configMigrationRes.result === "pr-exists") issueBody += "## Config Migration Needed\n\n" + getMarkdownComment(configMigrationPrInfo) + ` See Config Migration PR: #${configMigrationRes.prNumber}.\n\n`;
	else if (configMigrationRes?.result === "pr-modified") issueBody += "## Config Migration Needed (Blocked)\n\n" + getMarkdownComment(configMigrationPrInfo) + ` The Config Migration branch exists but has been modified by another user. Renovate will not push to this branch unless it is first deleted. \n\n See Config Migration PR: #${configMigrationRes.prNumber}.\n\n`;
	else if (configMigrationRes?.result === "add-checkbox") issueBody += "## Config Migration Needed\n\n" + getCheckbox(createConfigMigrationPr) + " Select this checkbox to let Renovate create an automated Config Migration PR.\n\n";
	issueBody = appendRepoProblems(config, issueBody);
	if (hasDeprecationsOrReplacements) {
		issueBody += "## Deprecations / Replacements\n";
		issueBody += emojify("> :warning: **Warning**\n> \n");
		issueBody += "These dependencies are either deprecated or have replacements available:\n\n";
		issueBody += "| Datasource | Package | Replacement PR? |\n";
		issueBody += "|------------|------|--------------|\n";
		for (const manager of Object.keys(deprecatedPackages).sort()) {
			const deps = deprecatedPackages[manager];
			for (const depName of Object.keys(deps).sort()) {
				const { hasReplacement, sourceUrl } = deps[depName];
				const packageName = formatAsMarkdownLink(depName, sourceUrl);
				issueBody += `| ${manager} | ${packageName} | ${hasReplacement ? "![Available](https://img.shields.io/badge/available-green?style=flat-square)" : "![Unavailable](https://img.shields.io/badge/unavailable-orange?style=flat-square)"} |\n`;
			}
		}
		issueBody += "\n";
	}
	if (config.dependencyDashboardReportAbandonment) issueBody += getAbandonedPackagesMd(packageFiles);
	issueBody += getBranchesListMd(branches, (branch) => branch.result === "needs-approval", "Pending Approval", "The following branches are pending approval. To create them, click on a checkbox below.", "approve", approveAllPendingPrs, "Create all pending approval PRs at once", "🔐");
	issueBody += getBranchesListMd(branches, (branch) => branch.result === "minimum-group-size-not-met", "Group Size Not Met", "The following branches have not met their minimum group size. To create them, click on a checkbox below.", "approveGroup");
	issueBody += getBranchesListMd(branches, (branch) => branch.result === "not-scheduled", "Awaiting Schedule", "The following updates are awaiting their schedule. To get an update now, click on a checkbox below.", "unschedule", createAllAwaitingSchedulePrs, "Create all awaiting schedule PRs at once", "🔐");
	issueBody += getBranchesListMd(branches, (branch) => branch.result === "branch-limit-reached" || branch.result === "pr-limit-reached" || branch.result === "commit-per-run-limit-reached" || branch.result === "commit-hourly-limit-reached", "Rate-Limited", "The following updates are currently rate-limited. To force their creation now, click on a checkbox below.", "unlimit", createAllRateLimitedPrs, "Create all rate-limited PRs at once", "🔐");
	issueBody += getBranchesListMd(branches, (branch) => branch.result === "error", "Errored", "The following updates encountered an error and will be retried. To force a retry now, click on a checkbox below.", "retry");
	issueBody += getBranchesListMd(branches, (branch) => branch.result === "needs-pr-approval", "PR Creation Approval Required", "The following branches exist but PR creation requires approval. To approve PR creation, click on a checkbox below.");
	issueBody += getBranchesListMd(branches, (branch) => branch.result === "pr-edited", "PR Edited (Blocked)", "The following updates have been manually edited so Renovate will no longer make changes. To discard all commits and start over, click on a checkbox below.", "rebase");
	issueBody += getBranchesListMd(branches, (branch) => branch.result === "pending", "Pending Status Checks", "The following updates await pending status checks. To force their creation now, click on a checkbox below.");
	issueBody += getBranchesListMd(branches, (branch) => branch.prBlockedBy === "BranchAutomerge", "Pending Branch Automerge", "The following updates await pending status checks before automerging. To abort the branch automerge and create a PR instead, click on a checkbox below.");
	const warn = getDepWarningsDashboard(packageFiles, config);
	if (warn) {
		issueBody += warn;
		issueBody += "\n";
	}
	const otherRes = [
		"pending",
		"needs-approval",
		"needs-pr-approval",
		"not-scheduled",
		"pr-limit-reached",
		"commit-per-run-limit-reached",
		"commit-hourly-limit-reached",
		"branch-limit-reached",
		"already-existed",
		"error",
		"automerged",
		"pr-edited",
		"minimum-group-size-not-met"
	];
	const inProgress = branches.filter((branch) => !otherRes.includes(branch.result) && branch.prBlockedBy !== "BranchAutomerge");
	issueBody += getBranchesListMd(inProgress, (branch) => !!branch.prBlockedBy || !branch.prNo, "Other Branches", "The following updates are pending. To force the creation of a PR, click on a checkbox below.", "other");
	issueBody += getBranchesListMd(inProgress, (branch) => branch.prNo && !branch.prBlockedBy, "Open", "The following updates have all been created. To force a retry/rebase of any, click on a checkbox below.", "rebase", rebaseAllOpenPrs, "Click on this checkbox to rebase all open PRs at once");
	issueBody += getBranchesListMd(branches, (branch) => branch.result === "already-existed", "PR Closed (Blocked)", "The following updates are blocked by an existing closed PR. To recreate the PR, click on a checkbox below.", "recreate");
	if (!hasBranches) issueBody += "This repository currently has no open or pending branches.\n\n";
	issueBody += await getDashboardMarkdownVulnerabilities(config, packageFiles);
	const footer = getFooter(config);
	issueBody += PackageFiles.getDashboardMarkdown(platform.maxBodyLength() - issueBody.length - footer.length);
	issueBody += footer;
	if (config.dependencyDashboardIssue) {
		if ((await platform.getIssue?.(config.dependencyDashboardIssue))?.body === issueBody) {
			logger.debug("No changes to dependency dashboard issue needed");
			return;
		}
		const updatedIssue = await platform.getIssue?.(config.dependencyDashboardIssue, false);
		if (updatedIssue) {
			const { dependencyDashboardChecks } = parseDashboardIssue(coerceString(updatedIssue.body));
			for (const branchName of Object.keys(config.dependencyDashboardChecks)) delete dependencyDashboardChecks[branchName];
			for (const branchName of Object.keys(dependencyDashboardChecks)) {
				const checkText = getCheckbox(`${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: platform.massageMarkdown(issueBody, config.rebaseLabel),
		labels: config.dependencyDashboardLabels,
		confidential: config.confidential
	});
}
function getAbandonedPackagesMd(packageFiles) {
	const abandonedPackages = {};
	let abandonedCount = 0;
	for (const [manager, managerPackageFiles] of Object.entries(packageFiles)) for (const packageFile of managerPackageFiles) for (const dep of coerceArray(packageFile.deps)) if (dep.depName && dep.isAbandoned) {
		abandonedCount++;
		abandonedPackages[manager] = abandonedPackages[manager] || {};
		abandonedPackages[manager][dep.depName] = {
			mostRecentTimestamp: dep.mostRecentTimestamp,
			sourceUrl: dep.sourceUrl
		};
	}
	if (abandonedCount === 0) return "";
	let abandonedMd = emojify("## Abandoned Dependencies\n\n> :information_source: **Note**\n> \n");
	abandonedMd += "Packages are marked as abandoned when they exceed the [`abandonmentThreshold`](https://docs.renovatebot.com/configuration-options/#abandonmentthreshold) since their last release. ";
	abandonedMd += "Unlike deprecated packages with official notices, abandonment is detected by release inactivity.\n> \n";
	abandonedMd += "> These dependencies have not received updates for an extended period and may be unmaintained:\n\n";
	abandonedMd += "<details>\n";
	abandonedMd += `<summary>View abandoned dependencies (${abandonedCount})</summary>\n\n`;
	abandonedMd += "| Datasource | Package | Last Updated |\n";
	abandonedMd += "|------------|------|-------------|\n";
	for (const manager of Object.keys(abandonedPackages).sort()) {
		const deps = abandonedPackages[manager];
		for (const depName of Object.keys(deps).sort()) {
			const { mostRecentTimestamp, sourceUrl } = deps[depName];
			const formattedDate = mostRecentTimestamp ? DateTime.fromISO(mostRecentTimestamp).toFormat("yyyy-MM-dd") : "unknown";
			const packageName = formatAsMarkdownLink(depName, sourceUrl);
			abandonedMd += `| ${manager} | ${packageName} | \`${formattedDate}\` |\n`;
		}
	}
	abandonedMd += "\n</details>\n\n\n";
	return abandonedMd;
}
function getFooter(config) {
	let footer = "";
	if (config.dependencyDashboardFooter?.length) footer += "---\n" + compile(config.dependencyDashboardFooter, config) + "\n";
	return footer;
}
async function getDashboardMarkdownVulnerabilities(config, packageFiles) {
	let result = "";
	if (isNullOrUndefined(config.dependencyDashboardOSVVulnerabilitySummary) || config.dependencyDashboardOSVVulnerabilitySummary === "none") return result;
	result += "## Vulnerabilities\n\n";
	const vulnerabilities = await (await Vulnerabilities.create()).fetchVulnerabilities(config, packageFiles);
	if (vulnerabilities.length === 0) {
		result += "Renovate has not found any CVEs on [osv.dev](https://osv.dev).\n\n";
		return result;
	}
	const unresolvedVulnerabilities = vulnerabilities.filter((value) => isNullOrUndefined(value.fixedVersion));
	const resolvedVulnerabilitiesLength = vulnerabilities.length - unresolvedVulnerabilities.length;
	result += emojify("> :exclamation: **Important**\n> \n");
	result += `> \`${resolvedVulnerabilitiesLength}\`/\`${vulnerabilities.length}\``;
	if (isTruthy(config.osvVulnerabilityAlerts)) result += " CVEs have Renovate fixes.\n\n";
	else result += " CVEs have possible Renovate fixes.\n> See [`osvVulnerabilityAlerts`](https://docs.renovatebot.com/configuration-options/#osvvulnerabilityalerts) to allow Renovate to supply fixes.\n\n";
	let renderedVulnerabilities;
	switch (config.dependencyDashboardOSVVulnerabilitySummary) {
		case "unresolved":
			renderedVulnerabilities = unresolvedVulnerabilities;
			break;
		default: renderedVulnerabilities = vulnerabilities;
	}
	const managerRecords = {};
	for (const vulnerability of renderedVulnerabilities) {
		const { manager, packageFile } = vulnerability.packageFileConfig;
		if (isNullOrUndefined(managerRecords[manager])) managerRecords[manager] = {};
		if (isNullOrUndefined(managerRecords[manager][packageFile])) managerRecords[manager][packageFile] = {};
		if (isNullOrUndefined(managerRecords[manager][packageFile][vulnerability.packageName])) managerRecords[manager][packageFile][vulnerability.packageName] = [];
		managerRecords[manager][packageFile][vulnerability.packageName].push(vulnerability);
	}
	for (const [manager, packageFileRecords] of Object.entries(managerRecords)) {
		result += `<details><summary>${manager}</summary>\n<blockquote>\n\n`;
		for (const [packageFile, packageNameRecords] of Object.entries(packageFileRecords)) {
			result += `<details><summary>${packageFile}</summary>\n<blockquote>\n\n`;
			for (const [packageName, cves] of Object.entries(packageNameRecords)) {
				result += `<details><summary>${packageName}</summary>\n<blockquote>\n\n`;
				for (const vul of cves) {
					const id = vul.vulnerability.id;
					const suffix = isNonEmptyString(vul.fixedVersion) ? ` (fixed in ${vul.fixedVersion})` : "";
					result += `- [${id}](https://osv.dev/vulnerability/${id})${suffix}\n`;
				}
				result += `</blockquote>\n</details>\n\n`;
			}
			result += `</blockquote>\n</details>\n\n`;
		}
		result += `</blockquote>\n</details>\n\n`;
	}
	return result;
}
//#endregion
export { ensureDependencyDashboard, readDashboardBody };

//# sourceMappingURL=dependency-dashboard.js.map