feat: announce each release once with a toast generated from the changelog

This commit is contained in:
Devvyn committed 2026-10-15 04:22:26 +08:00
1 parent a8a8bb71a3
commit d18deed51d
8 files changed
+181 -1

No files matched your search

+18
View File
@@ -0,0 +1,18 @@
#!/usr/bin/env node
// Writes src/web/public/whats-new.json for the version in package.json from CHANGELOG.md.
// Runs as part of `npm run version-packages`, so every release carries its own notes and the
// UI can announce them once per version without anyone remembering to.
import { readFileSync, writeFileSync } from 'node:fs';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { buildWhatsNew } from './lib/whats-new.mjs';
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..');
const { version } = JSON.parse(readFileSync(resolve(root, 'package.json'), 'utf8'));
const changelog = readFileSync(resolve(root, 'CHANGELOG.md'), 'utf8');
const notes = buildWhatsNew(changelog, version);
if (notes.highlights.length === 0) {
console.warn(`whats-new: no bold highlights found for ${version}; the in-app announcement will be skipped`);
}
writeFileSync(resolve(root, 'src/web/public/whats-new.json'), JSON.stringify(notes, null, 2) + '\n');
console.log(`whats-new: ${version} -> ${notes.highlights.length} highlight(s)`);
+35
View File
@@ -0,0 +1,35 @@
/**
* Pulls the headline of each change out of one version's section of CHANGELOG.md.
*
* Release notes here lead each change with a bold phrase ("**Tab layouts (#538, optional).** ..."),
* so a highlight is that phrase. The Thanks block is credit, not a feature.
*/
const MAX_HIGHLIGHTS = 8;
/** The text between `## <version>` and the next `## ` heading, or '' when absent. */
export function versionSection(changelog, version) {
const lines = changelog.split(/\r?\n/);
const start = lines.findIndex((l) => l.trim() === `## ${version}`);
if (start === -1) return '';
let end = lines.findIndex((l, i) => i > start && /^## /.test(l));
if (end === -1) end = lines.length;
return lines.slice(start + 1, end).join('\n');
}
/** Bold lead-ins at the start of a paragraph or bullet, cleaned for display. */
export function extractHighlights(section) {
const out = [];
for (const line of section.split('\n')) {
const m = /^\s*(?:[-*]\s+)?\*\*([^*]{3,120})\*\*/.exec(line);
if (!m) continue;
const text = m[1].replace(/[\s.:]+$/, '').trim();
if (!text || /^thanks\b/i.test(text)) continue;
if (!out.includes(text)) out.push(text);
}
return out.slice(0, MAX_HIGHLIGHTS);
}
export function buildWhatsNew(changelog, version) {
return { version, highlights: extractHighlights(versionSection(changelog, version)) };
}