Make next Discord changelog release-only

This commit is contained in:
Solvely-Colin
2026-05-24 17:56:28 -04:00
parent fc4cb75819
commit 7170120325

View File

@@ -1,21 +1,9 @@
name: Discord changelog
on:
push:
branches:
- main
- master
pull_request:
types:
- opened
- reopened
- synchronize
- closed
release:
types:
- published
create:
delete:
workflow_dispatch:
inputs:
release_tag:
@@ -25,14 +13,13 @@ on:
permissions:
contents: read
pull-requests: read
jobs:
discord-changelog:
runs-on: ubuntu-latest
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
if: github.event_name == 'release' || github.event_name == 'workflow_dispatch'
steps:
- name: Post changelog to Discord
- name: Post release changelog to Discord
uses: actions/github-script@v7
env:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_CHANGELOG_WEBHOOK }}
@@ -46,12 +33,40 @@ jobs:
return;
}
let eventName = context.eventName;
let event = context.payload;
const repoFullName = `${context.repo.owner}/${context.repo.repo}`;
const repoUrl = `https://github.com/${repoFullName}`;
if (context.eventName !== "release" && context.eventName !== "workflow_dispatch") {
core.info(`Skipping ${context.eventName}; this workflow only posts releases.`);
return;
}
const release = await getRelease();
const messages = buildReleaseMessages(release);
for (const content of messages) {
const response = await fetch(webhookUrl, {
method: "POST",
headers: {
"content-type": "application/json",
},
body: JSON.stringify({
username: "Repo Changelog",
content,
allowed_mentions: {
parse: [],
},
}),
});
if (!response.ok) {
core.setFailed(`Discord webhook failed with ${response.status}: ${await response.text()}`);
return;
}
}
async function getRelease() {
if (context.eventName === "release") {
return context.payload.release;
}
if (eventName === "workflow_dispatch") {
const releaseTag = process.env.RELEASE_TAG;
const response = releaseTag
? await github.rest.repos.getReleaseByTag({
@@ -64,201 +79,26 @@ jobs:
repo: context.repo.repo,
});
eventName = "release";
event = {
action: "published",
repository: {
full_name: repoFullName,
html_url: repoUrl,
},
sender: {
login: context.actor,
},
release: response.data,
};
return response.data;
}
const discordPayload = buildDiscordPayload(eventName, event);
const discordResponse = await fetch(webhookUrl, {
method: "POST",
headers: {
"content-type": "application/json",
},
body: JSON.stringify(discordPayload),
});
function buildReleaseMessages(release) {
const tag = release.tag_name || release.name || "release";
const title = release.name || tag;
const notes = formatReleaseNotes(release.body || "") || "No release notes provided.";
const fullMessage = `**${title} Released**\n\n${notes}`;
if (!discordResponse.ok) {
core.setFailed(
`Discord webhook failed with ${discordResponse.status}: ${await discordResponse.text()}`,
);
}
function buildDiscordPayload(eventName, event) {
return {
username: "Repo Changelog",
content: buildContent(eventName, event),
embeds: [
{
...buildEmbed(eventName, event),
footer: {
text: `GitHub ${eventName} event`,
},
timestamp: new Date().toISOString(),
},
],
};
}
function buildContent(eventName, event) {
const repo = getRepoName(event);
if (eventName === "release") {
const tag = event.release?.tag_name || event.release?.name || "release";
const action = event.action === "published" ? "published" : event.action || "updated";
return `**${repo}** ${action} **${tag}**`;
}
return `**${repo}** update from ${getActor(event)}`;
}
function buildEmbed(eventName, event) {
if (eventName === "release") {
return buildReleaseEmbed(event);
}
if (eventName === "push") {
return buildPushEmbed(event);
}
if (eventName === "pull_request") {
return buildPullRequestEmbed(event);
}
if (eventName === "create" || eventName === "delete") {
return buildRefEmbed(eventName, event);
}
return {
title: `GitHub ${eventName} event`,
description: `Received a ${eventName} event for ${getRepoName(event)}.`,
url: event.repository?.html_url,
color: 0x99aab5,
};
}
function buildReleaseEmbed(event) {
const release = event.release || {};
const tag = release.tag_name || "release";
const titleTag = release.name || tag;
const author = release.author?.login || event.sender?.login || "unknown";
return {
title: `${titleTag} Released`,
description: formatReleaseNotes(release.body || "") || "No release notes provided.",
url: release.html_url || event.repository?.html_url,
color: 0x57f287,
fields: [
{
name: "Repository",
value: link(getRepoName(event), event.repository?.html_url),
inline: true,
},
{
name: "Tag",
value: tag,
inline: true,
},
{
name: "Published by",
value: author,
inline: true,
},
],
};
}
function buildPushEmbed(event) {
const repo = getRepoName(event);
const branch = trimRef(event.ref);
const commits = Array.isArray(event.commits) ? event.commits : [];
const commitLines = commits.slice(0, 8).map(formatCommit).join("\n");
const overflow = commits.length > 8 ? `\n...and ${commits.length - 8} more.` : "";
const commitWord = commits.length === 1 ? "commit" : "commits";
return {
title: `${commits.length} ${commitWord} pushed to ${branch}`,
description: commitLines
? `${commitLines}${overflow}`
: "No commit list was included in this event.",
url: event.compare || event.repository?.html_url,
color: 0x5865f2,
fields: [
{
name: "Repository",
value: link(repo, event.repository?.html_url),
inline: true,
},
{
name: "Branch",
value: branch,
inline: true,
},
],
};
}
function buildPullRequestEmbed(event) {
const pr = event.pull_request || {};
const action =
event.action === "closed" && pr.merged ? "merged" : event.action || "updated";
const number = pr.number ? `#${pr.number}` : "";
return {
title: `Pull request ${number} ${action}`,
description: link(pr.title || "Pull request", pr.html_url),
url: pr.html_url || event.repository?.html_url,
color: pr.merged ? 0x57f287 : 0xfee75c,
fields: [
{
name: "Author",
value: pr.user?.login || event.sender?.login || "unknown",
inline: true,
},
{
name: "Branch",
value: `${pr.head?.ref || "head"} -> ${pr.base?.ref || "base"}`,
inline: true,
},
],
};
}
function buildRefEmbed(eventName, event) {
const action = eventName === "create" ? "created" : "deleted";
const type = event.ref_type || "ref";
const ref = event.ref || "unknown";
return {
title: `${capitalize(type)} ${action}: ${ref}`,
description: `${getActor(event)} ${action} ${type} \`${ref}\`.`,
url: event.repository?.html_url,
color: eventName === "create" ? 0x57f287 : 0xed4245,
};
}
function formatCommit(commit) {
const shortSha = commit.id ? commit.id.slice(0, 7) : "commit";
const message = truncate(oneLine(commit.message || "No commit message"), 140);
const author = commit.author?.name ? ` - ${commit.author.name}` : "";
return `- ${link(shortSha, commit.url)} ${message}${author}`;
return splitDiscordMessage(fullMessage, 1900);
}
function formatReleaseNotes(value) {
return truncate(
value
.replace(/\r\n/g, "\n")
.replace(/https:\/\/github\.com\/[^/\s]+\/[^/\s]+\/pull\/(\d+)/g, "#$1")
.replace(/https:\/\/github\.com\/[^/\s]+\/[^/\s]+\/issues\/(\d+)/g, "#$1")
.replace(/https:\/\/github\.com\/[^/\s]+\/[^/\s]+\/commit\/([0-9a-f]{7})[0-9a-f]*/gi, "$1")
.replace(/https:\/\/github\.com\/\S+/g, "")
.trim()
.split("\n")
.map((line) => {
@@ -272,30 +112,38 @@ jobs:
);
}
function getRepoName(event) {
return event.repository?.full_name || repoFullName;
}
function splitDiscordMessage(value, maxLength) {
if (value.length <= maxLength) {
return [value];
}
function getActor(event) {
return event.sender?.login || event.pusher?.name || context.actor || "GitHub";
}
const chunks = [];
let remaining = value;
function link(label, url) {
return url ? `[${label}](${url})` : label;
}
while (remaining.length > maxLength) {
let splitAt = remaining.lastIndexOf("\n", maxLength);
function trimRef(ref) {
return (ref || "unknown").replace(/^refs\/heads\//, "").replace(/^refs\/tags\//, "");
}
if (splitAt < 500) {
splitAt = remaining.lastIndexOf(" ", maxLength);
}
function oneLine(value) {
return value.replace(/\s+/g, " ").trim();
if (splitAt < 500) {
splitAt = maxLength;
}
chunks.push(remaining.slice(0, splitAt).trim());
remaining = remaining.slice(splitAt).trim();
}
if (remaining) {
chunks.push(remaining);
}
return chunks.map((chunk, index) =>
index === 0 ? chunk : `**Changelog continued**\n\n${chunk}`,
);
}
function truncate(value, maxLength) {
return value.length <= maxLength ? value : `${value.slice(0, maxLength - 3)}...`;
}
function capitalize(value) {
return `${value.slice(0, 1).toUpperCase()}${value.slice(1)}`;
}