feat(release-notes): terse bilingual notes as data files — new/fixed only, the dialog lists the latest ten (E28)
This commit is contained in:
+14
-23
@@ -1,11 +1,9 @@
|
||||
// Generates CHANGELOG.md from frontend/src/lib/releaseNotes.ts — the same entries the app shows
|
||||
// under Release Notes, so a self-hoster deciding whether to upgrade reads the same text an
|
||||
// Generates CHANGELOG.md from frontend/src/content/release-notes/en.json — the same entries the app
|
||||
// shows under Release Notes, so a self-hoster deciding whether to upgrade reads the same text an
|
||||
// in-app user does, without having to install the app first (DX-14).
|
||||
//
|
||||
// Why derive rather than hand-write: two lists of "what changed in 0.80.0" WILL drift, and the
|
||||
// in-app one is the one that gets written during a release. This makes the file a build product of
|
||||
// it. RELEASE_NOTES is a plain data literal by design, so it is imported by stripping the single TS
|
||||
// type annotation off the declaration — no transpiler, no dependency.
|
||||
// in-app one is the one that gets written during a release. This makes the file a build product of it.
|
||||
//
|
||||
// node scripts/gen-changelog.mjs # write CHANGELOG.md
|
||||
// node scripts/gen-changelog.mjs --check # exit 1 if CHANGELOG.md is stale (used by `siftlode release`)
|
||||
@@ -15,22 +13,15 @@ import { fileURLToPath } from "node:url";
|
||||
import path from "node:path";
|
||||
|
||||
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const SOURCE = path.join(root, "frontend", "src", "lib", "releaseNotes.ts");
|
||||
const SOURCE = path.join(root, "frontend", "src", "content", "release-notes", "en.json");
|
||||
const TARGET = path.join(root, "CHANGELOG.md");
|
||||
|
||||
async function loadEntries() {
|
||||
const src = await readFile(SOURCE, "utf8");
|
||||
const decl = "export const RELEASE_NOTES";
|
||||
const at = src.indexOf(decl);
|
||||
if (at === -1) {
|
||||
throw new Error(`${SOURCE} no longer declares ${decl} — the generator's assumption broke, fix it rather than hand-writing the changelog.`);
|
||||
const entries = JSON.parse(await readFile(SOURCE, "utf8"));
|
||||
if (!Array.isArray(entries) || entries.length === 0) {
|
||||
throw new Error(`${SOURCE} is not a non-empty array of release notes.`);
|
||||
}
|
||||
const body = src.slice(at).replace(/^export const RELEASE_NOTES\s*:[^=]*=/, "export const RELEASE_NOTES =");
|
||||
const mod = await import(`data:text/javascript,${encodeURIComponent(body)}`);
|
||||
if (!Array.isArray(mod.RELEASE_NOTES) || mod.RELEASE_NOTES.length === 0) {
|
||||
throw new Error("RELEASE_NOTES imported as something other than a non-empty array.");
|
||||
}
|
||||
return mod.RELEASE_NOTES;
|
||||
return entries;
|
||||
}
|
||||
|
||||
function section(title, items) {
|
||||
@@ -44,7 +35,7 @@ function render(entries) {
|
||||
"",
|
||||
"Every released version of Siftlode, newest first.",
|
||||
"",
|
||||
"**Generated from `frontend/src/lib/releaseNotes.ts` by `scripts/gen-changelog.mjs` — do not edit by hand;**",
|
||||
"**Generated from `frontend/src/content/release-notes/en.json` by `scripts/gen-changelog.mjs` — do not edit by hand;**",
|
||||
"edit the release notes and re-run the generator.",
|
||||
"",
|
||||
"Upgrading, pinning a version and rolling back: [docs/self-hosting.md](docs/self-hosting.md#upgrading).",
|
||||
@@ -56,10 +47,10 @@ function render(entries) {
|
||||
.map((e) => {
|
||||
const sha = e.sha ? ` (\`${e.sha}\`)` : "";
|
||||
let out = `## ${e.version} — ${e.date}${sha}\n\n`;
|
||||
if (e.summary) out += `${e.summary}\n\n`;
|
||||
out += section("Features", e.features);
|
||||
out += section("Fixes", e.fixes);
|
||||
out += section("Internal", e.chores);
|
||||
out += section("New", e.new);
|
||||
out += section("Fixed", e.fixed);
|
||||
// A release that carried only internal work (none has since the owner's E28 rule).
|
||||
if (e.new.length === 0 && e.fixed.length === 0) out += "Internal changes only.\n\n";
|
||||
return out;
|
||||
})
|
||||
.join("");
|
||||
@@ -74,7 +65,7 @@ if (process.argv.includes("--check")) {
|
||||
const current = await readFile(TARGET, "utf8").catch(() => null);
|
||||
if (current !== rendered) {
|
||||
console.error(
|
||||
`CHANGELOG.md is out of date with releaseNotes.ts (newest entry: ${entries[0].version}).\n` +
|
||||
`CHANGELOG.md is out of date with the release notes (newest entry: ${entries[0].version}).\n` +
|
||||
"Run: node scripts/gen-changelog.mjs — then commit the result.",
|
||||
);
|
||||
process.exit(1);
|
||||
|
||||
Reference in New Issue
Block a user