From 520139f5cfe603155573a628cc033e55b04a734d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 11 Jun 2026 14:04:02 +0400 Subject: [PATCH 1/2] feat(tools): include enriched dep changes in Discord release announcement --- tools/announce-release.ts | 25 +++++++++++++------------ tools/changelog.ts | 17 +++++++++++++++-- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/tools/announce-release.ts b/tools/announce-release.ts index df6280f..2d8fde4 100644 --- a/tools/announce-release.ts +++ b/tools/announce-release.ts @@ -60,23 +60,24 @@ for (const { name, version } of published) { const file = Bun.file(path); if (!(await file.exists())) continue; const changelog = await file.text(); - for (const bullet of bulletsInRange(changelog, "", version)) { + for (const bullet of bulletsInRange(changelog, "", version, { includeNested: true })) { + const nested = /^\s/.test(bullet); const key = dedupeKey(bullet); if (seen.has(key)) continue; seen.add(key); - notes.push(formatNote(stripViaSuffix(bullet))); + notes.push(formatNote(stripViaSuffix(bullet.trim()), nested)); } } // Turn a changeset-github bullet into a compact Discord line: message first, -// PR link trailing, dropping the commit hash and "Thanks @user!". -function formatNote(bullet: string): string { - const m = bullet.match( - /^-\s*(\[#\d+\]\([^)]+\))?\s*(?:\[`[0-9a-f]+`\]\([^)]+\))?\s*(?:Thanks[^!]*!)?\s*-?\s*(.*)$/s, - ); +// PR link trailing, dropping the commit hash and "Thanks @user!". Nested bullets +// (the enriched underlying dep changes) are indented under their parent note — +// non-breaking spaces so Discord doesn't collapse the indent. +function formatNote(bullet: string, nested = false): string { + const m = bullet.match(/^-\s*(\[#\d+\]\([^)]+\))?\s*(?:\[`[0-9a-f]+`\]\([^)]+\))?\s*(?:Thanks[^!]*!)?\s*-?\s*(.*)$/s); const pr = m?.[1]; const msg = (m?.[2] || bullet.replace(/^-\s*/, "")).trim().replace(/\s+/g, " "); - return `• ${msg}${pr ? ` (${pr})` : ""}`; + return `${nested ? "   ↳ " : "• "}${msg}${pr ? ` (${pr})` : ""}`; } // Build the description, truncating to Discord's limit with an overflow link. @@ -111,14 +112,14 @@ function buildPackageField(): string { const embed = { author: { name: RELEASE_LABEL }, - title: `📦 ${RELEASE_LABEL} — Release`, color: EMBED_COLOR, description: buildDescription(), fields: [{ name: `Packages (${published.length})`, value: buildPackageField() }], + title: `📦 ${RELEASE_LABEL} — Release`, ...(RUN_URL ? { url: RUN_URL } : {}), }; -const payload = { username: "SwapKit Releases", embeds: [embed] }; +const payload = { embeds: [embed], username: "SwapKit Releases" }; if (DRY_RUN || !WEBHOOK) { if (!WEBHOOK && !DRY_RUN) console.info("DISCORD_RELEASE_WEBHOOK unset — skipping Discord post."); @@ -127,9 +128,9 @@ if (DRY_RUN || !WEBHOOK) { } const res = await fetch(WEBHOOK, { - method: "POST", - headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), + headers: { "Content-Type": "application/json" }, + method: "POST", }); if (!res.ok) { diff --git a/tools/changelog.ts b/tools/changelog.ts index 2eea8a3..23b84f9 100644 --- a/tools/changelog.ts +++ b/tools/changelog.ts @@ -37,7 +37,15 @@ export function stripViaSuffix(bullet: string): string { // Real (non-"Updated dependencies") bullets of every changelog section whose // version is in (oldVersion, newVersion]. If oldVersion is empty, take only newVersion. -export function bulletsInRange(changelog: string, oldVersion: string, newVersion: string): string[] { +// With includeNested, sub-bullets of a real bullet (how enrich-dep-changelogs inlines +// the underlying dep changes) are returned too, keeping a two-space indent marker so +// callers can tell parent from child. +export function bulletsInRange( + changelog: string, + oldVersion: string, + newVersion: string, + { includeNested = false } = {}, +): string[] { const bullets: string[] = []; let take = false; let inDepBlock = false; @@ -60,7 +68,12 @@ export function bulletsInRange(changelog: string, oldVersion: string, newVersion inDepBlock = true; continue; } - if (inDepBlock && /^\s+- /.test(line)) continue; // nested dep ref + if (/^\s+- /.test(line)) { + if (includeNested && !inDepBlock && bullets.length > 0) { + bullets.push(` ${line.trim()}`); + } + continue; + } if (/^- /.test(line)) { inDepBlock = false; bullets.push(line.trim()); From 07a9bfd05436604ad48a402d7cfea093ffe411f4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 11 Jun 2026 14:06:42 +0400 Subject: [PATCH 2/2] feat(tools): group release announcement by bumped package with its changes --- tools/announce-release.ts | 58 +++++++++++++++++++++++++-------------- 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/tools/announce-release.ts b/tools/announce-release.ts index 2d8fde4..6ce1fa3 100644 --- a/tools/announce-release.ts +++ b/tools/announce-release.ts @@ -6,8 +6,10 @@ // Packages" PR or a failed publish. // // For each published package it slices that version's section from the package's -// CHANGELOG.md, aggregates + dedupes the notes (shared changelog helpers), and -// sends one embed listing the changes and the package@versions. +// CHANGELOG.md (its changes since the last released version) and sends one embed +// listing every bumped package with its notes. Packages whose sections carry the +// same changes (the usual case for a dep-bump release) are grouped under one +// heading instead of repeating the notes per package. // // Env: // DISCORD_RELEASE_WEBHOOK Discord channel webhook URL (no-op if unset) @@ -51,22 +53,29 @@ for await (const f of new Glob("packages/*/package.json").scan(".")) { if (name) changelogByName.set(name, f.replace(/package\.json$/, "CHANGELOG.md")); } -// Aggregate + dedupe the released version's notes across all published packages. -const seen = new Set(); -const notes: string[] = []; -for (const { name, version } of published) { - const path = changelogByName.get(name); - if (!path) continue; - const file = Bun.file(path); - if (!(await file.exists())) continue; - const changelog = await file.text(); - for (const bullet of bulletsInRange(changelog, "", version, { includeNested: true })) { +// Per-package notes from the released version's changelog section, then group +// packages whose sections carry the exact same changes (keyed by the notes' +// dedupe identities) so shared dep-bump notes appear once. +const groups = new Map(); +for (const pkg of published) { + const path = changelogByName.get(pkg.name); + const file = path ? Bun.file(path) : null; + const changelog = file && (await file.exists()) ? await file.text() : ""; + + const seen = new Set(); + const notes: string[] = []; + for (const bullet of bulletsInRange(changelog, "", pkg.version, { includeNested: true })) { const nested = /^\s/.test(bullet); - const key = dedupeKey(bullet); + const key = `${nested ? ">" : ""}${dedupeKey(bullet)}`; if (seen.has(key)) continue; seen.add(key); notes.push(formatNote(stripViaSuffix(bullet.trim()), nested)); } + + const signature = [...seen].join("|"); + const group = groups.get(signature); + if (group) group.pkgs.push(pkg); + else groups.set(signature, { notes, pkgs: [pkg] }); } // Turn a changeset-github bullet into a compact Discord line: message first, @@ -80,22 +89,29 @@ function formatNote(bullet: string, nested = false): string { return `${nested ? "   ↳ " : "• "}${msg}${pr ? ` (${pr})` : ""}`; } -// Build the description, truncating to Discord's limit with an overflow link. +// Build the description: one section per group — the bumped package@versions as +// a heading, their changes underneath — truncated to Discord's limit with an +// overflow link. function buildDescription(): string { - if (notes.length === 0) return "_No notable changes recorded._"; + const lines: string[] = []; + for (const { pkgs, notes } of groups.values()) { + if (lines.length > 0) lines.push(""); + lines.push(pkgs.map((p) => `**\`${p.name}@${p.version}\`**`).join(" · ")); + lines.push(...(notes.length > 0 ? notes : ["_Dependency updates only._"])); + } const kept: string[] = []; let len = 0; - for (let i = 0; i < notes.length; i++) { - const line = notes[i]; + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; if (len + line.length + 1 > DESC_LIMIT) { - const more = notes.length - i; - kept.push(RUN_URL ? `…and ${more} more — [release run](${RUN_URL})` : `…and ${more} more`); + const more = lines.length - i; + kept.push(RUN_URL ? `…and ${more} more lines — [release run](${RUN_URL})` : `…and ${more} more lines`); break; } kept.push(line); len += line.length + 1; } - return kept.join("\n"); + return kept.join("\n") || "_No notable changes recorded._"; } function buildPackageField(): string { @@ -137,4 +153,4 @@ if (!res.ok) { console.error(`Discord webhook failed: ${res.status} ${await res.text().catch(() => "")}`); process.exit(1); } -console.info(`📣 announced ${published.length} packages, ${notes.length} change notes to Discord.`); +console.info(`📣 announced ${published.length} packages in ${groups.size} groups to Discord.`);