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..122fe081c1 100644 --- a/scripts/release.ts +++ b/scripts/release.ts @@ -54,6 +54,40 @@ 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 ""; + } + // 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 ""; + } + 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 +145,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`; }