mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-06-11 02:33:10 +08:00
On a published GitHub release, post the notes to the ECC Discord #announcements channel (via bot), pin it, and cross-post to GitHub Discussions (Announcements category). Release data flows through env vars (no shell interpolation of untrusted input). Secrets: DISCORD_BOT_TOKEN, DISCORD_ANNOUNCE_CHANNEL_ID (repo secrets), GITHUB_TOKEN. Ties the 2.0.0/1.11.0 official release to the community launch. Co-authored-by: ECC Test <ecc@example.test>
107 lines
4.1 KiB
JavaScript
107 lines
4.1 KiB
JavaScript
#!/usr/bin/env node
|
|
// Posts a published GitHub release to the Discord #announcements channel,
|
|
// pins it, and cross-posts to GitHub Discussions (Announcements category).
|
|
// Dependency-free (Node 18+ fetch). Runs from the release-announce workflow.
|
|
'use strict';
|
|
|
|
const {
|
|
DISCORD_BOT_TOKEN,
|
|
DISCORD_ANNOUNCE_CHANNEL_ID,
|
|
RELEASE_NAME,
|
|
RELEASE_TAG,
|
|
RELEASE_URL,
|
|
RELEASE_BODY,
|
|
GITHUB_TOKEN,
|
|
GITHUB_REPOSITORY,
|
|
} = process.env;
|
|
|
|
const sleep = ms => new Promise(r => setTimeout(r, ms));
|
|
|
|
async function discord(method, path, body) {
|
|
const res = await fetch(`https://discord.com/api/v10${path}`, {
|
|
method,
|
|
headers: { Authorization: `Bot ${DISCORD_BOT_TOKEN}`, 'Content-Type': 'application/json' },
|
|
body: body ? JSON.stringify(body) : undefined,
|
|
});
|
|
if (res.status === 429) {
|
|
const j = await res.json().catch(() => ({ retry_after: 1 }));
|
|
await sleep((j.retry_after || 1) * 1000 + 250);
|
|
return discord(method, path, body);
|
|
}
|
|
if (!res.ok) throw new Error(`${method} ${path} -> ${res.status} ${(await res.text()).slice(0, 200)}`);
|
|
return res.status === 204 ? null : res.json();
|
|
}
|
|
|
|
function buildMessage() {
|
|
const title = (RELEASE_NAME && RELEASE_NAME.trim()) || RELEASE_TAG || 'New release';
|
|
const body = (RELEASE_BODY || '').trim();
|
|
// Discord message cap is 2000 chars; leave room for header + link.
|
|
const maxBody = 1600;
|
|
const trimmed = body.length > maxBody ? `${body.slice(0, maxBody)}\n...` : body;
|
|
const parts = [`# ${title} is out`, ''];
|
|
if (trimmed) parts.push(trimmed, '');
|
|
if (RELEASE_URL) parts.push(`full release notes: ${RELEASE_URL}`);
|
|
return parts.join('\n');
|
|
}
|
|
|
|
async function postAndPinToDiscord() {
|
|
if (!DISCORD_BOT_TOKEN || !DISCORD_ANNOUNCE_CHANNEL_ID) {
|
|
console.log('skip discord: missing DISCORD_BOT_TOKEN / DISCORD_ANNOUNCE_CHANNEL_ID');
|
|
return;
|
|
}
|
|
const msg = await discord('POST', `/channels/${DISCORD_ANNOUNCE_CHANNEL_ID}/messages`, { content: buildMessage() });
|
|
console.log('posted release to #announcements:', msg.id);
|
|
try {
|
|
await discord('PUT', `/channels/${DISCORD_ANNOUNCE_CHANNEL_ID}/pins/${msg.id}`);
|
|
console.log('pinned announcement');
|
|
} catch (e) {
|
|
console.log('pin skipped:', e.message);
|
|
}
|
|
}
|
|
|
|
async function graphql(query, variables) {
|
|
const res = await fetch('https://api.github.com/graphql', {
|
|
method: 'POST',
|
|
headers: { Authorization: `Bearer ${GITHUB_TOKEN}`, 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ query, variables }),
|
|
});
|
|
const j = await res.json();
|
|
if (j.errors) throw new Error(JSON.stringify(j.errors).slice(0, 300));
|
|
return j.data;
|
|
}
|
|
|
|
async function crossPostToDiscussions() {
|
|
if (!GITHUB_TOKEN || !GITHUB_REPOSITORY) {
|
|
console.log('skip discussions: missing GITHUB_TOKEN / GITHUB_REPOSITORY');
|
|
return;
|
|
}
|
|
const [owner, name] = GITHUB_REPOSITORY.split('/');
|
|
try {
|
|
const data = await graphql(
|
|
`query($owner:String!,$name:String!){repository(owner:$owner,name:$name){id discussionCategories(first:25){nodes{id name}}}}`,
|
|
{ owner, name }
|
|
);
|
|
const repo = data.repository;
|
|
const cat = repo.discussionCategories.nodes.find(c => /announcement/i.test(c.name))
|
|
|| repo.discussionCategories.nodes[0];
|
|
if (!cat) { console.log('skip discussions: no category found'); return; }
|
|
const title = `${(RELEASE_NAME && RELEASE_NAME.trim()) || RELEASE_TAG} release`;
|
|
const bodyParts = [(RELEASE_BODY || '').trim(), '', RELEASE_URL ? `Release: ${RELEASE_URL}` : ''].filter(Boolean);
|
|
const created = await graphql(
|
|
`mutation($repo:ID!,$cat:ID!,$title:String!,$body:String!){createDiscussion(input:{repositoryId:$repo,categoryId:$cat,title:$title,body:$body}){discussion{url}}}`,
|
|
{ repo: repo.id, cat: cat.id, title, body: bodyParts.join('\n') || title }
|
|
);
|
|
console.log('created discussion:', created.createDiscussion.discussion.url);
|
|
} catch (e) {
|
|
console.log('discussions cross-post skipped:', e.message);
|
|
}
|
|
}
|
|
|
|
async function main() {
|
|
await postAndPinToDiscord();
|
|
await crossPostToDiscussions();
|
|
console.log('release-announce done');
|
|
}
|
|
|
|
main().catch(e => { console.error('release-announce FAILED:', e.message); process.exit(1); });
|