|
79 | 79 | env: |
80 | 80 | NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTOMATION_TOKEN_FROM_KOTAPI }} |
81 | 81 | run: npm publish --access public --provenance |
| 82 | + |
| 83 | + - name: Announce on Discord (with changelog + README summary) |
| 84 | + if: steps.exists.outputs.exists == 'false' |
| 85 | + env: |
| 86 | + DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} |
| 87 | + PKG_NAME: ${{ steps.pkg.outputs.name }} |
| 88 | + PKG_VERSION: ${{ steps.pkg.outputs.version }} |
| 89 | + REPO: ${{ github.repository }} |
| 90 | + run: | |
| 91 | + node - <<'NODE' |
| 92 | + const fs = require('fs'); |
| 93 | + const https = require('https'); |
| 94 | +
|
| 95 | + const webhook = process.env.DISCORD_WEBHOOK_URL; |
| 96 | + const name = process.env.PKG_NAME; |
| 97 | + const version = process.env.PKG_VERSION; |
| 98 | + const repo = process.env.REPO; |
| 99 | +
|
| 100 | + const npmUrl = `https://www.npmjs.com/package/${encodeURIComponent(name)}/v/${version}`; |
| 101 | + const ghUrl = `https://github.com/${repo}`; |
| 102 | +
|
| 103 | + const read = (p) => { try { return fs.readFileSync(p, 'utf8'); } catch { return ''; } }; |
| 104 | + const squash = (s='') => s |
| 105 | + .replace(/<!--[\s\S]*?-->/g, '') |
| 106 | + .replace(/\r/g, '') |
| 107 | + .replace(/\n{3,}/g, '\n\n') |
| 108 | + .trim(); |
| 109 | + const trunc = (s, n) => (s.length > n ? s.slice(0, n - 1) + '…' : s); |
| 110 | +
|
| 111 | + // README summary (first paragraph after title/badges) |
| 112 | + let readmeSummary = ''; |
| 113 | + const readme = read('README.md'); |
| 114 | + if (readme) { |
| 115 | + const lines = readme.split('\n'); |
| 116 | + let i = 0; |
| 117 | + while (i < lines.length && (/^\s*#\s/.test(lines[i]) || /!\[.*\]\(.*\)|<img/i.test(lines[i]) || /^\s*$/.test(lines[i]))) i++; |
| 118 | + const para = []; |
| 119 | + for (; i < lines.length && !/^\s*$/.test(lines[i]); i++) para.push(lines[i]); |
| 120 | + readmeSummary = trunc(squash(para.join('\n')), 900); // field limit safe |
| 121 | + } |
| 122 | +
|
| 123 | + // Changelog section for this version |
| 124 | + let changelog = ''; |
| 125 | + const raw = read('CHANGELOG.md'); |
| 126 | + if (raw) { |
| 127 | + const esc = (v) => v.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); |
| 128 | + const pats = [ |
| 129 | + new RegExp(`^##\\s*\\[?v?${esc(version)}\\]?\\s*$`, 'mi'), |
| 130 | + new RegExp(`^##\\s*.*\\s+v?${esc(version)}\\s*$`, 'mi'), |
| 131 | + ]; |
| 132 | + let start = -1; |
| 133 | + for (const p of pats) { const m = raw.match(p); if (m) { start = m.index; break; } } |
| 134 | + if (start >= 0) { |
| 135 | + const tail = raw.slice(start); |
| 136 | + const next = tail.search(/^##\s+/m); |
| 137 | + const section = (next > 0 ? tail.slice(0, next) : tail).replace(/^##.*$/m, ''); |
| 138 | + changelog = trunc(squash(section), 3600); // embed desc limit |
| 139 | + } else { |
| 140 | + // fallback: top-most section |
| 141 | + const parts = raw.split(/^##\s+/m); |
| 142 | + if (parts[1]) changelog = trunc(squash(parts[1]), 3600); |
| 143 | + } |
| 144 | + } |
| 145 | +
|
| 146 | + const body = { |
| 147 | + username: "Rad UI Release Bot", |
| 148 | + // avatar_url: "https://your.cdn/rad-ui-logo.png", // optional |
| 149 | + embeds: [{ |
| 150 | + title: `Released ${name} ${version} 🎉`, |
| 151 | + url: npmUrl, |
| 152 | + description: changelog || "Changelog is available in the repo.", |
| 153 | + fields: [ |
| 154 | + ...(readmeSummary ? [{ name: "Summary", value: readmeSummary, inline: false }] : []), |
| 155 | + { name: "npm", value: `[${name}@${version}](${npmUrl})`, inline: true }, |
| 156 | + { name: "GitHub", value: ghUrl, inline: true } |
| 157 | + ], |
| 158 | + timestamp: new Date().toISOString() |
| 159 | + }] |
| 160 | + }; |
| 161 | +
|
| 162 | + const data = JSON.stringify(body); |
| 163 | + const u = new URL(webhook); |
| 164 | + const req = https.request( |
| 165 | + { hostname: u.hostname, path: u.pathname + u.search, method: 'POST', |
| 166 | + headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(data) } }, |
| 167 | + res => { res.resume(); res.on('end', () => process.exit(res.statusCode < 300 ? 0 : 1)); } |
| 168 | + ); |
| 169 | + req.on('error', err => { console.error(err); process.exit(1); }); |
| 170 | + req.write(data); req.end(); |
| 171 | + NODE |
| 172 | +
|
0 commit comments