FN-7875: split archived pre-0.50 release notes out of root CHANGELOG.md
Adds a deterministic changelog-archive split so scripts/release.mjs stops regenerating one ever-growing root CHANGELOG.md and instead keeps only current release notes at the root while durably archiving pre-0.50.0 history. - Add scripts/lib/changelog-archive.mjs with partitionVersionsByCutoff (splits a version-ordered list at the 0.50.0 cutoff, preserving order and treating non-parseable keys as archived) and archivePointerLine (renders the "older releases" pointer appended to the current changelog). - Rework scripts/release.mjs's syncRootChangelog to build CHANGELOG.md (current versions + archive pointer) and a new CHANGELOG-archive.md (versions before 0.50.0) via a shared buildRootChangelogLines/normalizeChangelogLines helper instead of one monolithic file. - Add scripts/__tests__/changelog-archive.test.mjs covering cutoff partitioning, boundary/patch handling, non-parseable keys, custom cutoffs, and the archive pointer text. - Regenerate CHANGELOG.md (now only 0.50.0+) and add CHANGELOG-archive.md containing the pre-0.50.0 history moved out of the root file. Files changed: CHANGELOG-archive.md | 10882 +++++++++++++++++++++++ CHANGELOG.md | 11717 ++----------------------- scripts/__tests__/changelog-archive.test.mjs | 58 + scripts/lib/changelog-archive.mjs | 56 + scripts/release.mjs | 49 +- 5 files changed, 11710 insertions(+), 11052 deletions(-) Fusion-Task-Id: FN-7875 Fusion-Task-Lineage: 220e6aa1-54fb-4800-a86e-6d8d21f6bf18 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
10882
CHANGELOG-archive.md
Normal file
10882
CHANGELOG-archive.md
Normal file
File diff suppressed because it is too large
Load Diff
11703
CHANGELOG.md
11703
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
58
scripts/__tests__/changelog-archive.test.mjs
Normal file
58
scripts/__tests__/changelog-archive.test.mjs
Normal file
@@ -0,0 +1,58 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import {
|
||||
archivePointerLine,
|
||||
CHANGELOG_ARCHIVE_CUTOFF,
|
||||
CHANGELOG_ARCHIVE_FILE,
|
||||
partitionVersionsByCutoff,
|
||||
} from "../lib/changelog-archive.mjs";
|
||||
|
||||
test("partitions versions at the pre-0.50 archive cutoff", () => {
|
||||
assert.deepEqual(
|
||||
partitionVersionsByCutoff(["0.58.0", "0.50.0", "0.49.0", "0.11.1"]),
|
||||
{
|
||||
current: ["0.58.0", "0.50.0"],
|
||||
archived: ["0.49.0", "0.11.1"],
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test("keeps the 0.50.0 boundary and newer patch versions current", () => {
|
||||
const partitioned = partitionVersionsByCutoff(["0.53.1", "0.50.0", "0.49.0"]);
|
||||
|
||||
assert.deepEqual(partitioned.current, ["0.53.1", "0.50.0"]);
|
||||
assert.deepEqual(partitioned.archived, ["0.49.0"]);
|
||||
});
|
||||
|
||||
test("preserves input order and archives non-parseable version keys", () => {
|
||||
const partitioned = partitionVersionsByCutoff([
|
||||
"0.51.0",
|
||||
"legacy notes",
|
||||
"0.50.0 (pre-release, unpublished)",
|
||||
"0.49.9",
|
||||
"0.53.0",
|
||||
]);
|
||||
|
||||
assert.deepEqual(partitioned.current, [
|
||||
"0.51.0",
|
||||
"0.50.0 (pre-release, unpublished)",
|
||||
"0.53.0",
|
||||
]);
|
||||
assert.deepEqual(partitioned.archived, ["legacy notes", "0.49.9"]);
|
||||
});
|
||||
|
||||
test("supports a custom cutoff with release.mjs semver-ish parsing", () => {
|
||||
assert.deepEqual(partitionVersionsByCutoff(["1.2.0", "1.1.9", "not semver"], "1.2.0"), {
|
||||
current: ["1.2.0"],
|
||||
archived: ["1.1.9", "not semver"],
|
||||
});
|
||||
});
|
||||
|
||||
test("archive pointer references the archive file and cutoff", () => {
|
||||
const pointer = archivePointerLine();
|
||||
|
||||
assert.ok(pointer.includes(CHANGELOG_ARCHIVE_FILE));
|
||||
assert.ok(pointer.includes(CHANGELOG_ARCHIVE_CUTOFF));
|
||||
assert.match(pointer, /\.\/CHANGELOG-archive\.md/);
|
||||
});
|
||||
56
scripts/lib/changelog-archive.mjs
Normal file
56
scripts/lib/changelog-archive.mjs
Normal file
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* FNXC:ReleaseChangelog 2026-07-12-00:00:
|
||||
* The root CHANGELOG.md is regenerated by scripts/release.mjs on every release, so pruning old release notes by hand would be reverted.
|
||||
* Keep the 0.50.0 cutoff in generator-owned pure logic so current release notes stay in CHANGELOG.md and older notes are durably split into CHANGELOG-archive.md.
|
||||
*/
|
||||
export const CHANGELOG_ARCHIVE_CUTOFF = "0.50.0";
|
||||
export const CHANGELOG_ARCHIVE_FILE = "CHANGELOG-archive.md";
|
||||
|
||||
/**
|
||||
* Partition version keys while preserving the source changelog order inside each bucket.
|
||||
* Versions greater than or equal to the cutoff remain in the current changelog; versions below it, including non-parseable keys treated as 0.0.0, move to the archive.
|
||||
*
|
||||
* @param {string[]} versionOrder
|
||||
* @param {string} cutoff
|
||||
* @returns {{ current: string[], archived: string[] }}
|
||||
*/
|
||||
export function partitionVersionsByCutoff(versionOrder, cutoff = CHANGELOG_ARCHIVE_CUTOFF) {
|
||||
const current = [];
|
||||
const archived = [];
|
||||
|
||||
for (const version of versionOrder) {
|
||||
if (compareSemver(version, cutoff) >= 0) {
|
||||
current.push(version);
|
||||
} else {
|
||||
archived.push(version);
|
||||
}
|
||||
}
|
||||
|
||||
return { current, archived };
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the stable archive pointer appended to the current root changelog.
|
||||
*
|
||||
* @param {string} archiveFile
|
||||
* @returns {string}
|
||||
*/
|
||||
export function archivePointerLine(archiveFile = CHANGELOG_ARCHIVE_FILE) {
|
||||
return `> Older releases (before ${CHANGELOG_ARCHIVE_CUTOFF}) are archived in [\`${archiveFile}\`](./${archiveFile}).`;
|
||||
}
|
||||
|
||||
/** Compare two semver-ish version strings ("0.2.5", "0.4.0 (pre-release)"). */
|
||||
function compareSemver(a, b) {
|
||||
const pa = parseVersionKey(a);
|
||||
const pb = parseVersionKey(b);
|
||||
for (let i = 0; i < 3; i++) {
|
||||
if (pa[i] !== pb[i]) return pa[i] - pb[i];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function parseVersionKey(key) {
|
||||
const m = key.match(/^(\d+)\.(\d+)\.(\d+)/);
|
||||
if (!m) return [0, 0, 0];
|
||||
return [Number(m[1]), Number(m[2]), Number(m[3])];
|
||||
}
|
||||
@@ -37,6 +37,12 @@ import { extractVersionNotes, replaceVersionSection } from "./lib/extract-versio
|
||||
import { parseChangesetFile } from "./lib/changeset-schema.mjs";
|
||||
import { distillDeterministic } from "./lib/distill-release-notes.mjs";
|
||||
import { shouldPromptForVersion } from "./lib/release-prompt-gate.mjs";
|
||||
import {
|
||||
archivePointerLine,
|
||||
CHANGELOG_ARCHIVE_CUTOFF,
|
||||
CHANGELOG_ARCHIVE_FILE,
|
||||
partitionVersionsByCutoff,
|
||||
} from "./lib/changelog-archive.mjs";
|
||||
|
||||
const args = new Set(process.argv.slice(2));
|
||||
/*
|
||||
@@ -68,7 +74,7 @@ function run(cmd, { capture = false, allowFail = false, cwd } = {}) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrite the repo-root CHANGELOG.md by aggregating every
|
||||
* Rewrite the repo-root changelogs by aggregating every
|
||||
* `packages/*\/CHANGELOG.md` into a single per-version view.
|
||||
*
|
||||
* For each version that appears in any package, we emit a top-level
|
||||
@@ -80,6 +86,10 @@ function run(cmd, { capture = false, allowFail = false, cwd } = {}) {
|
||||
* release (the one whose top version is highest by semver). Any extra
|
||||
* versions found only in other packages are appended in semver-descending
|
||||
* order at the end.
|
||||
*
|
||||
* FNXC:ReleaseChangelog 2026-07-12-00:00:
|
||||
* The root CHANGELOG.md is regenerated during every release, so the pre-0.50 prune must happen in this generator instead of as a manual docs edit.
|
||||
* Keep versions greater than or equal to the 0.50.0 cutoff in CHANGELOG.md, and write older versions to CHANGELOG-archive.md so the split survives the next release sync.
|
||||
*/
|
||||
function syncRootChangelog() {
|
||||
const pkgsDir = "packages";
|
||||
@@ -113,12 +123,31 @@ function syncRootChangelog() {
|
||||
}
|
||||
}
|
||||
|
||||
const lines = [
|
||||
"# Fusion changelog",
|
||||
"",
|
||||
"User-facing release notes aggregated across all packages. This file is auto-synced from each `packages/*/CHANGELOG.md` by `scripts/release.mjs` — do not edit by hand.",
|
||||
"",
|
||||
];
|
||||
const { current, archived } = partitionVersionsByCutoff(versionOrder);
|
||||
const currentLines = buildRootChangelogLines({
|
||||
title: "# Fusion changelog",
|
||||
banner: "User-facing release notes aggregated across all packages. This file is auto-synced from each `packages/*/CHANGELOG.md` by `scripts/release.mjs` — do not edit by hand.",
|
||||
parsed,
|
||||
versionOrder: current,
|
||||
});
|
||||
|
||||
if (archived.length > 0) {
|
||||
currentLines.push(archivePointerLine(), "");
|
||||
}
|
||||
|
||||
const archiveLines = buildRootChangelogLines({
|
||||
title: "# Fusion changelog archive",
|
||||
banner: `Archived release notes before ${CHANGELOG_ARCHIVE_CUTOFF}. This file is auto-synced from each \`packages/*/CHANGELOG.md\` by \`scripts/release.mjs\` — do not edit by hand.`,
|
||||
parsed,
|
||||
versionOrder: archived,
|
||||
});
|
||||
|
||||
writeFileSync("CHANGELOG.md", normalizeChangelogLines(currentLines));
|
||||
writeFileSync(CHANGELOG_ARCHIVE_FILE, normalizeChangelogLines(archiveLines));
|
||||
}
|
||||
|
||||
function buildRootChangelogLines({ title, banner, parsed, versionOrder }) {
|
||||
const lines = [title, "", banner, ""];
|
||||
|
||||
for (const version of versionOrder) {
|
||||
lines.push(`## ${version}`, "");
|
||||
@@ -136,7 +165,11 @@ function syncRootChangelog() {
|
||||
}
|
||||
}
|
||||
|
||||
writeFileSync("CHANGELOG.md", lines.join("\n").replace(/\n{3,}/g, "\n\n"));
|
||||
return lines;
|
||||
}
|
||||
|
||||
function normalizeChangelogLines(lines) {
|
||||
return lines.join("\n").replace(/\n{3,}/g, "\n\n");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user