diff --git a/scripts/notify-slack-release.mjs b/scripts/notify-slack-release.mjs index 1b38a23d..9838f3ee 100644 --- a/scripts/notify-slack-release.mjs +++ b/scripts/notify-slack-release.mjs @@ -35,15 +35,20 @@ const server = process.env.GITHUB_SERVER_URL || 'https://github.com'; const repo = process.env.GITHUB_REPOSITORY || ''; const releaseUrl = process.env.RELEASE_URL || (repo && version ? `${server}/${repo}/releases/tag/v${version}` : ''); +// Slack mrkdwn reserves & < > — a raw commit subject containing them (release notes are raw +// commit subjects) would misrender or be read as a . Escape the note body only; +// NOT the intentional link line below. +const esc = (s) => s.replace(/&/g, '&').replace(//g, '>'); + let notes = ''; try { notes = readFileSync(notesFile, 'utf8').trim(); } catch { /* notes optional */ } // Slack section text caps at 3000 chars; keep well under and never dump a wall. if (notes.length > 2600) { notes = `${notes.slice(0, 2600)}\n…`; } const tag = label ? ` \`${label}\`` : ''; -const header = `:package: *${product}* \`${version || 'release'}\`${tag}`; +const header = `:package: *${esc(product)}* \`${version || 'release'}\`${tag}`; const linkLine = releaseUrl ? `<${releaseUrl}|Download / release page>` : ''; -const body = notes || '_No release notes generated for this build._'; +const body = notes ? esc(notes) : '_No release notes generated for this build._'; const blocks = [ { type: 'section', text: { type: 'mrkdwn', text: header } }, @@ -59,6 +64,7 @@ try { method: 'POST', headers: { 'Content-Type': 'application/json; charset=utf-8' }, body: JSON.stringify({ text: fallbackText, blocks, unfurl_links: false }), + signal: AbortSignal.timeout(10000), }); const t = await res.text().catch(() => ''); if (!res.ok) { warn(`webhook not ok: HTTP ${res.status} ${t}`); process.exit(0); } @@ -68,6 +74,7 @@ try { method: 'POST', headers: { 'Content-Type': 'application/json; charset=utf-8', Authorization: `Bearer ${token}` }, body: JSON.stringify({ channel, text: fallbackText, blocks, unfurl_links: false }), + signal: AbortSignal.timeout(10000), }); const j = await res.json().catch(() => ({})); if (!res.ok || !j.ok) { warn(`chat.postMessage not ok: HTTP ${res.status} ${j.error || ''}`); process.exit(0); }