From f00acfd3908ad77b2e76c756e42dff34b438ecc8 Mon Sep 17 00:00:00 2001 From: Olivier Biot Date: Tue, 20 Oct 2026 08:56:44 +0800 Subject: [PATCH 1/2] Release 20.10.0 Dates the 20.10.0 changelog section. Also takes the GitHub release notes from the changelog. They used to be the commit subjects between two tags, which read as history rather than as a release: they opened with the release and changelog housekeeping, and the written entries never left the file. `release.ts` now leads the notes with the version's changelog section (falling back to the commit subjects when there is none), keeps the contributor sections, and ends with the compare link. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01SeWCkETjW35ZbXDRswV1hx --- packages/melonjs/CHANGELOG.md | 2 +- scripts/release.ts | 47 +++++++++++++++++++++++++++++++---- 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/packages/melonjs/CHANGELOG.md b/packages/melonjs/CHANGELOG.md index 7fb0a4febe..94d12c6859 100644 --- a/packages/melonjs/CHANGELOG.md +++ b/packages/melonjs/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [20.10.0] (melonJS 2) - _unreleased_ +## [20.10.0] (melonJS 2) - _2026-10-20_ **Highlights:** a game no longer stutters the first time an effect, a blend mode or a new kind of object appears. Shaders, pipelines and render targets are now prepared when the object is added, or during `loader.preload()`, rather than in the frame that first draws it, where a single first draw was measured at up to 140ms. Dropping a batch of meshes no longer stalls the frame either. diff --git a/scripts/release.ts b/scripts/release.ts index 4a9847f283..160afe4b2a 100644 --- a/scripts/release.ts +++ b/scripts/release.ts @@ -54,6 +54,38 @@ function getCommits(previousTag: string, currentTag: string): Commit[] { }); } +/** + * The CHANGELOG entries for a version, which ARE the release notes. + * + * The notes used to be the commit subjects between two tags. That reads as + * history rather than as a release: it opens with whatever landed last, which + * is the release and changelog housekeeping, and the written entries, the ones + * that tell somebody whether to upgrade, never left the file. The compare link + * at the end still carries the full commit list for anyone who wants it. + * @param version - the released version, which is also the tag + * @returns the section body without its heading, or "" if there is no entry + */ +function changelogSection(version: string): string { + let text: string; + try { + text = readFileSync(resolve("packages/melonjs/CHANGELOG.md"), "utf8"); + } catch { + return ""; + } + const escaped = version.replace(/\./g, "\\."); + const start = text.search(new RegExp(`^## \\[${escaped}\\]`, "m")); + if (start === -1) { + return ""; + } + const rest = text.slice(start); + // the next version heading, searched past the first character so the + // section's own heading does not match + const next = rest.slice(1).search(/^## \[/m); + const body = next === -1 ? rest : rest.slice(0, next + 1); + // the release is already titled with the version, so drop the heading + return body.replace(/^##[^\n]*\n/, "").trim(); +} + function buildCategorizedNotes( previousTag: string, currentTag: string, @@ -111,11 +143,16 @@ function buildCategorizedNotes( } } - // Build notes - let notes = "## What's Changed\n\n"; - - if (teamCommits.length > 0) { - notes += "### melonJS Team\n\n"; + // Build notes, led by what the changelog says changed. The team's commits + // are deliberately not listed one by one: they are the changelog entries + // above, said once and said better, and the compare link covers the rest. + let notes = ""; + const changelog = changelogSection(currentTag); + if (changelog) { + notes += `${changelog}\n\n`; + } else if (teamCommits.length > 0) { + // no entry for this version, so fall back to the commit subjects + notes += "## What's Changed\n\n"; for (const c of teamCommits) { notes += `- ${c.message} (${c.hash})\n`; } From 916321876db412f2697d9928e212068ff1d94711 Mon Sep 17 00:00:00 2001 From: Olivier Biot Date: Tue, 20 Oct 2026 09:02:53 +0800 Subject: [PATCH 2/2] Release: find the changelog heading without building a regex CodeQL flagged the pattern built from the version as incomplete escaping: only the dots were escaped. Searching for the heading as plain text at the start of a line needs no escaping at all, and finds the same section. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01SeWCkETjW35ZbXDRswV1hx --- scripts/release.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/release.ts b/scripts/release.ts index 160afe4b2a..122fe081c1 100644 --- a/scripts/release.ts +++ b/scripts/release.ts @@ -72,8 +72,10 @@ function changelogSection(version: string): string { } catch { return ""; } - const escaped = version.replace(/\./g, "\\."); - const start = text.search(new RegExp(`^## \\[${escaped}\\]`, "m")); + // a plain search for the heading at the start of a line, rather than a + // pattern built from the version: nothing in it then needs escaping. The + // leading newline makes the file's first line count as a line start. + const start = `\n${text}`.indexOf(`\n## [${version}]`); if (start === -1) { return ""; }