Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 59 additions & 80 deletions SyncPRSummary/action.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
name: Sync PR Summary
description: Update the managed PR summary block from workflow outputs and bot comments.
description: Consolidate workflow outputs and bot comments into a single managed bot comment on the PR.

inputs:
docs-content:
description: Documentation preview content to inject into the PR summary.
description: Documentation preview content to inject into the managed bot comment.
required: false
default: ''
perf-content:
description: Performance summary content to inject into the PR summary.
description: Performance summary content to inject into the managed bot comment.
required: false
default: ''

Expand All @@ -22,108 +22,87 @@ runs:
github-token: ${{ github.token }}
script: |
const START_MARKER = "<!-- managed-pr-summary:start -->";
const END_MARKER = "<!-- managed-pr-summary:end -->";
const END_MARKER = "<!-- managed-pr-summary:end -->";

const sectionsConfig = [
const SECTIONS = [
{
title: "Documentation Preview",
fallback: "_Pending. No docs preview comment found yet._",
input: process.env.INPUT_DOCS_CONTENT,
key: "docs",
title: "Documentation Preview",
input: (process.env.INPUT_DOCS_CONTENT ?? "").trim(),
fallback: "_Pending. No docs preview available yet._",
},
{
marker: "<!-- perf-results -->",
title: "Performance",
fallback: "_Pending. No performance comment found yet._",
input: process.env.INPUT_PERF_CONTENT,
key: "perf",
marker: "<!-- perf-results -->",
title: "Performance",
input: (process.env.INPUT_PERF_CONTENT ?? "").trim(),
fallback: "_Pending. No performance results available yet._",
},
];

function extractManagedBlock(body) {
const start = body.indexOf(START_MARKER);
const end = body.indexOf(END_MARKER);
if (start === -1 || end === -1 || end < start) return "";
return body.slice(start + START_MARKER.length, end).trim();
function readSection(body, key) {
const open = `<!-- section:${key} -->`;
const close = `<!-- /section:${key} -->`;
const s = body.indexOf(open);
const e = body.indexOf(close);
if (s === -1 || e === -1 || e <= s) return null;
return body.slice(s + open.length, e).trim();
}

function extractManagedSections(body) {
const managedBlock = extractManagedBlock(body);
if (!managedBlock) return new Map();

const sections = new Map();
const sectionRegex = /^### (.+)$/gm;
const matches = [...managedBlock.matchAll(sectionRegex)];
for (let i = 0; i < matches.length; i += 1) {
const title = matches[i][1];
const contentStart = matches[i].index + matches[i][0].length;
const contentEnd = i + 1 < matches.length ? matches[i + 1].index : managedBlock.length;
const content = managedBlock.slice(contentStart, contentEnd).trim();
if (content) sections.set(title, `### ${title}\n\n${content}`);
}
return sections;
}

function replaceManagedBlock(body, managedBlock) {
const start = body.indexOf(START_MARKER);
const end = body.indexOf(END_MARKER);

if (start === -1 && end === -1) {
if (!body) return managedBlock;
if (body.endsWith("\n\n")) return `${body}${managedBlock}`;
if (body.endsWith("\n")) return `${body}\n${managedBlock}`;
return `${body}\n\n${managedBlock}`;
}
if (start !== -1 && (end === -1 || end < start)) return `${body.slice(0, start)}${managedBlock}`;
if (start === -1 && end !== -1) return `${managedBlock}${body.slice(end + END_MARKER.length)}`;
return `${body.slice(0, start)}${managedBlock}${body.slice(end + END_MARKER.length)}`;
function buildBody(resolvedSections) {
const inner = resolvedSections
.map(({ key, title, content }) =>
`<!-- section:${key} -->\n### ${title}\n\n${content}\n<!-- /section:${key} -->`)
.join("\n\n");
return `${START_MARKER}\n## CI Summary — GitHub Actions\n\n${inner}\n${END_MARKER}`;
}

const { owner, repo } = context.repo;
const prNumber = context.payload.pull_request.number;
const owner = context.repo.owner;
const repo = context.repo.repo;

const pr = await github.rest.pulls.get({ owner, repo, pull_number: prNumber });
const allComments = await github.paginate(github.rest.issues.listComments, {
owner, repo, issue_number: prNumber, per_page: 100,
});

const comments = await github.paginate(github.rest.issues.listComments, {
owner,
repo,
issue_number: prNumber,
per_page: 100,
const managedComment = allComments.find((c) => {
const user = c.user?.login;
const app = c.performed_via_github_app?.slug;
const isBot = user === "github-actions[bot]" || app === "github-actions";
return isBot && (c.body || "").includes(START_MARKER);
});

const currentBody = pr.data.body || "";
const existingSections = extractManagedSections(currentBody);
const latestByMarker = new Map();
const managedBody = managedComment?.body ?? "";

for (const comment of comments) {
const latestByMarker = new Map();
for (const comment of allComments) {
const body = comment.body || "";
const user = comment.user?.login;
const app = comment.performed_via_github_app?.slug;
if (user !== "github-actions[bot]" && app !== "github-actions") continue;
for (const { marker, title } of sectionsConfig) {
const app = comment.performed_via_github_app?.slug;
const isBot = user === "github-actions[bot]" || app === "github-actions";
if (!isBot) continue;

for (const { marker, title } of SECTIONS) {
if (!marker) continue;
if (!body.startsWith(marker)) continue;
const content = body.slice(marker.length).trim();
if (content) latestByMarker.set(marker, `### ${title}\n\n${content}`);
if (content) latestByMarker.set(marker, content);
}
}

const sections = sectionsConfig.map(({ marker, title, fallback, input }) => {
if (input) return `### ${title}\n\n${input.trim()}`;
return latestByMarker.get(marker) || existingSections.get(title) || `### ${title}\n\n${fallback}`;
});
const resolvedSections = SECTIONS.map(({ key, marker, title, input, fallback }) => ({
key,
title,
content: input || latestByMarker.get(marker) || readSection(managedBody, key) || fallback,
}));

const managedBlock = [
START_MARKER,
"## CI Summary — GitHub Actions",
"",
...sections,
END_MARKER,
].join("\n\n");
const newBody = buildBody(resolvedSections);

const updatedBody = replaceManagedBlock(currentBody, managedBlock);
if (updatedBody === currentBody) {
core.info("PR summary already up to date.");
return;
if (managedComment) {
if (managedComment.body !== newBody) {
await github.rest.issues.updateComment({ owner, repo, comment_id: managedComment.id, body: newBody });
} else {
core.info("PR summary already up to date.");
}
} else {
await github.rest.issues.createComment({ owner, repo, issue_number: prNumber, body: newBody });
}

await github.rest.pulls.update({ owner, repo, pull_number: prNumber, body: updatedBody });