From 330e4970f07e8664815c66d0b69d2e4385cab3cb Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Fri, 24 Jul 2026 23:01:11 -0700 Subject: [PATCH] refactor(release): move the version-anchor package.json rewrite into the shared lib Makes the re-anchor file mutation unit-testable alongside the anchor decision. --- .../__tests__/release-version-anchor.test.mjs | 22 ++++++++++++++++++ scripts/lib/release-version-anchor.mjs | 23 +++++++++++++++++++ scripts/release.mjs | 19 ++++----------- 3 files changed, 49 insertions(+), 15 deletions(-) diff --git a/scripts/__tests__/release-version-anchor.test.mjs b/scripts/__tests__/release-version-anchor.test.mjs index 4cd6d9ddb2..62318cc693 100644 --- a/scripts/__tests__/release-version-anchor.test.mjs +++ b/scripts/__tests__/release-version-anchor.test.mjs @@ -10,12 +10,16 @@ Regression context: v0.73.0 was cut on `release` while `main` stayed in the import test from "node:test"; import assert from "node:assert/strict"; +import { mkdtempSync, readFileSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; import { compareReleaseVersions, evaluateBetaCycleAnchor, isVersionAheadOfStable, latestStableVersionFromTags, + setPackageJsonVersions, } from "../lib/release-version-anchor.mjs"; test("compareReleaseVersions ranks a prerelease below its stable", () => { @@ -82,3 +86,21 @@ test("isVersionAheadOfStable rejects releases at or below the newest stable", () assert.equal(isVersionAheadOfStable("0.74.0", "0.73.0"), true); assert.equal(isVersionAheadOfStable("0.1.0", null), true); }); + +test("setPackageJsonVersions re-anchors only the files that need it", () => { + const dir = mkdtempSync(join(tmpdir(), "fusion-anchor-test-")); + const root = join(dir, "package.json"); + const cli = join(dir, "cli.package.json"); + const missing = join(dir, "nope.package.json"); + writeFileSync(root, JSON.stringify({ name: "root", version: "0.73.0-beta.6" }, null, 2) + "\n"); + writeFileSync(cli, JSON.stringify({ name: "cli", version: "0.73.0" }, null, 2) + "\n"); + + const rewritten = setPackageJsonVersions([root, cli, missing], "0.73.0"); + + // Already-anchored and absent files are left alone; the stale one is rewritten. + assert.deepEqual(rewritten, [root]); + assert.equal(JSON.parse(readFileSync(root, "utf8")).version, "0.73.0"); + assert.equal(JSON.parse(readFileSync(cli, "utf8")).version, "0.73.0"); + // Unrelated fields survive the rewrite. + assert.equal(JSON.parse(readFileSync(root, "utf8")).name, "root"); +}); diff --git a/scripts/lib/release-version-anchor.mjs b/scripts/lib/release-version-anchor.mjs index f442ec350d..c5b0ec9f69 100644 --- a/scripts/lib/release-version-anchor.mjs +++ b/scripts/lib/release-version-anchor.mjs @@ -14,6 +14,8 @@ and the dev checkout (`pnpm dev`, dashboard version badge) still reported the last beta. */ +import { existsSync, readFileSync, writeFileSync } from "node:fs"; + /** * Prerelease-aware semver comparison: returns <0, 0, >0. * `1.2.3-beta.1` sorts BELOW `1.2.3`; numeric prerelease identifiers compare @@ -91,6 +93,27 @@ export function evaluateBetaCycleAnchor({ cycleBase, latestStable }) { return { stale, anchor: stale ? latestStable : cycleBase }; } +/** + * Point each package.json at `version`, skipping files already there or absent. + * Used to re-anchor the fixed group (plus the workspace root) on the shipped + * stable before `changeset pre enter`, which snapshots these versions into + * pre.json's `initialVersions` and derives every X.Y.Z-beta.N from them. + * + * @returns {string[]} the paths actually rewritten, so a dry-run can restore them. + */ +export function setPackageJsonVersions(paths, version) { + const rewritten = []; + for (const path of paths) { + if (!existsSync(path)) continue; + const pkg = JSON.parse(readFileSync(path, "utf8")); + if (pkg.version === version) continue; + pkg.version = version; + writeFileSync(path, JSON.stringify(pkg, null, 2) + "\n"); + rewritten.push(path); + } + return rewritten; +} + /** * Backstop applied to both channels: a release must be strictly newer than the * newest published stable. Catches hand-edited pre.json, a back-merge resolved diff --git a/scripts/release.mjs b/scripts/release.mjs index a84f4e74fd..1206c1c6da 100755 --- a/scripts/release.mjs +++ b/scripts/release.mjs @@ -52,6 +52,7 @@ import { evaluateBetaCycleAnchor, isVersionAheadOfStable, latestStableVersionFromTags, + setPackageJsonVersions, } from "./lib/release-version-anchor.mjs"; import { archivePointerLine, @@ -418,24 +419,12 @@ function readFixedGroupPackageNames() { * Returns the list of rewritten paths so a dry-run can restore them. */ function rewriteFixedGroupVersions(version) { - const rewritten = []; + const paths = ["package.json"]; for (const name of readFixedGroupPackageNames()) { const dir = findPackageDir(name); - if (!dir) continue; - const pkgPath = join(dir, "package.json"); - const pkg = JSON.parse(readFileSync(pkgPath, "utf8")); - if (pkg.version === version) continue; - pkg.version = version; - writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n"); - rewritten.push(pkgPath); + if (dir) paths.push(join(dir, "package.json")); } - const rootPkg = JSON.parse(readFileSync("package.json", "utf8")); - if (rootPkg.version !== version) { - rootPkg.version = version; - writeFileSync("package.json", JSON.stringify(rootPkg, null, 2) + "\n"); - rewritten.push("package.json"); - } - return rewritten; + return setPackageJsonVersions(paths, version); } /**