chore(release): migrate the release toolchain to @changesets/cli v3 - #9560
Merged
os-project-manager merged 2 commits intoAug 18, 2026
Merged
Conversation
…fig.json
- privatePackages: {version:true, tag:false} (U4)
- drop ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH (U2)
- $schema -> @changesets/config@4.0.0 (the version v3 resolves)
… and docs - check-adr-0087-registration.mjs: exclude .changeset/pre/ from the audit surfaces (--list / --audit-stock) while the enforcing diff scan keeps judging it; PRE1-PRE3 fixtures - check-changeset-no-major.mjs: header only — v3 numbers, and the stale claim that both siblings pass --diff-filter=AM (they pass AMR since #7045) - cut-rc.yml / pr-automation.yml / docs/releases-maintenance.md: the prose asserting consumed changesets are recorded in .changeset/pre.json
This was referenced Aug 18, 2026
os-project-manager
marked this pull request as ready for review
August 18, 2026 10:30
os-project-manager
enabled auto-merge
August 18, 2026 10:30
os-project-manager
deleted the
claude/issue-9498-changesets-v3-migration
branch
August 18, 2026 11:00
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #9498
@changesets/cli2.31.1 → 3.0.0, with every consequential change in the same PR — epic #9465's ruling 1: both half-states are broken, and each is broken silently until the next release. Sub-issue 2/4.The scope is #9497's rehearsal. Every number below was re-measured on this branch's tree rather than inherited, because the stock has grown from the rehearsal's 165 changesets to 209 — and one of those new changesets turns out to matter (see "Where measurement diverged from the rehearsal").
1. The next train is 17.1.0 — shown, not asserted
Maintainer ruling on #9498, verbatim: 接受 17.1.0. Proof on the migrated tree,
@changesets/cli3.0.0:and in pre mode, which is the number the next cut dispatches:
Driven end to end through a full window in a throwaway clone (
pre enter→ RC1 → author a changeset → RC2 →pre exit→ GA), all on this branch's tree:.changeset/*.md.changeset/pre/packages/cliversionpre enter rcpnpm run version(RC1)pnpm run version(RC2)changeset pre exitpre.jsonmode→"exit", one line, nothing else)pnpm run version(GA)No changeset declares a major (
major releases: NONE), and none was written to preserve 18.0.0 — explicitly rejected in the ruling. v2's 18.0.0 came from one optional peer edge (@objectstack/cli→@objectstack/driver-turso) crossed with "a prerelease never satisfies a non-prerelease range"; that is traced by ablation on #9465, not re-derived here.2. What changed, and which unknown drove it
package.json+pnpm-lock.yaml@changesets/cli^2.31.1→^3.0.0.changeset/config.json"privatePackages": {"version": true, "tag": false}.changeset/config.json___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH.changeset/config.json$schema→@changesets/config@4.0.0(the version v3 resolves).github/workflows/cut-rc.yml.github/workflows/pr-automation.ymldocs/releases-maintenance.mdscripts/check-changeset-no-major.mjs--diff-filter=AMis correctedscripts/check-adr-0087-registration.mjs.changeset/pre/excluded from--list/--audit-stock, plus PRE1–PRE3 fixtures.changeset/config.json— the toggle matrix, measured hereSame 209 changesets, v3,
changeset status:{"minor":69,"patch":7,"none":1}, 77 packages, spec 17.1.0true)falseThe experimental key is measurably inert under v3 (the peer-escalation branch is gone from
@changesets/assemble-release-plan@7), so dropping it is a no-op here — and it may only be dropped in this commit, because under v2 it is load-bearing on the GA path (U2).3. Where measurement diverged from the rehearsal — and the divergence matters
#9497 measured
privatePackagesunset as lossy but survivable: 8 private packages stop being versioned, 2 private-only changesets never get consumed. On today's stock it is fatal:With
privatePackages.version === false, v3 treats private packages as ignored, and a changeset naming one private and one public package becomes a "mixed changeset", which changesets refuses outright. Today's stock has exactly one —.changeset/default-timeout-margin-repair.md(@objectstack/typespatch +@objectstack/dogfoodpatch), landed after the rehearsal's snapshot — plus 4 private-only changesets where the rehearsal saw 2.So U4's recommendation is not a nicety about version numbers: without that key the bump alone takes the release lane from "wrong versions" to "will not run". It is more evidence for ruling 1, arriving from a direction nobody planned.
4. The measured v3 file set vs the enforced allowlist (U3)
git status --porcelainafterchangeset pre enter rc+pnpm run version, v3.0.0, 209 pending changesets — 366 lines:.changeset/pre/account-oauth-tokens-internal.mddiffs identical to the original — the files are moved verbatim.package.jsonCHANGELOG.md.changeset/pre.jsonmodifiedpre.jsonis{"mode","tag"}and stays that way.changeset/pre/, holding the 209.changeset/*.mdfile"The enforcement itself needs no change, replayed verbatim from the workflow against the v3 output:
^\.changeset/already covers.changeset/pre/…, andgit add -A -- … .changeset …stages the deletions and the new directory together. TheSURFACE_LIST/grep -vxFblock from 955ccf2 is untouched — this PR changes prose in that file and oneechostring, nothing else. Ruling 3 ("the allowlist follows the measurement") is satisfied by the allowlist needing no move.5. Gate exposure, measured on a real v3 cut commit
git ls-tree -r --name-only CUT -- .changesetat the rehearsal cut:scripts/check-adr-0087-registration.mjs'schangesetDirAtuses exactly that recursivels-tree, so from the first v3 cut the entire consumed stock of an open window flows into--listand--audit-stock— surfaces whose subject is the pending backlog. Fixed by excluding thepre/prefix inchangesetsAtonly:isChangesetFile, so a breaking changeset added under.changeset/pre/is judged like any other. Filtering the verdict instead of the audit would make.changeset/pre/a hiding place;entriesstays unfiltered, so a tree whose changesets have all moved intopre/is still readable input, not a check:react-declaration-parity 是唯一没接进任何 workflow 的源码审计门禁,且无 MANIFEST 时静默 skip 退出 0 —— 它现在永远不可能红 #4690 refusal (that would undo check-adr-0087-registration is red on every PR after a release cut — its input assertion needs a breaking changeset in the real stock (sibling of #8654) #8658 by a new route).Both halves are pinned and both ablations were run:
Other gates, measured on the same cut commit rather than reasoned about:
pr-automation.yml's changeset counter (--diff-filter=A … '.changeset/*.md'): the 209 moves report asR100, so it credits a PR with 0 — still 0 withdiff.renameLimit=1forced; only--no-renamesturns them into 209Arows, and nothing passes it. No change.AMRscan both gates use returns exactly 209R100rows over the cut. Pure renames compare equal at the old path, so every one is exempt — but only because the filter saysAMR. That is now the ordinary path, not a crafted bypass, and the header says so.readdirSyncfiltered to.md) seepreas a directory and ignore it: after the GA pass,readdirSync('.changeset')isREADME.md, config.json, preand pending stock is 0. The post-exit mass deletion is 211 D, of which 210 are under.changeset/pre/and 0 in the root.6. What did NOT change — measured, not assumed
scripts/check-empty-changeset.mjs— untouched (U1), re-measured here on v3.0.0. Three changesets with empty frontmatter:changeset versionexits 0, deletes all three, bumps nothing — 空 changeset 会静默卡死已 version 的发布:Release run 全绿,但 npm 和 Docker 什么都没发(17.0.0-rc.2 现在就卡着) #4898 exactly, reproduced on v3. Zero changesets: exits 1. v3's exit-1 counts changeset files, not releases, so it covers a disjoint case; there is no coverage to trade. (v3 also reads.changeset/pre/, so inside a window the count is never 0 and the guard is disarmed for the whole window.)scripts/objectui-changeset-digest.mjs— re-read on currentmain(it moved in fix(devx): the objectui pin guard tests walk completeness, not object presence #9448). Its onlypre.jsoncontact isinPreMode()→pre?.mode === 'pre', which survives v3's slimmed file.check:objectui-changesetgreen..github/workflows/release.yml— itspre.jsonreference is a past-tense account of chore(release)!: enter RC pre-mode and anchor the train as the v17 major #3600's mechanism and is still accurate. [v3 migration 3/4] Decide #9208 —changesets/actionv1 → v2 against release.yml's pinned rationale #9499 owns the action question (U6 was not answerable from here).scripts/check-changeset-fixed.mjs— green:fixedgroup in sync with 69 public workspace packages.scripts/check-changeset-no-major.mjs's logic — U5's verdict is "less than hoped": the exemption switch, themode: "exit"re-arm and the merge-base/AMRscoping are all still load-bearing under v3. What v3 retires is the urgency the header was written in, not a line of code. The header now says that explicitly, so nobody reads shrinking stock as licence to drop the scoping.pre.json.changesetsorpre.json.initialVersions— repo-wide, the only references were prose, all corrected here.7. #9450 — decided, not folded in silently
Not carried.
cut-rc.yml:285's "range is walkable" preflight is a different defect in the same file: it is about objectui clone reachability, and nothing in this migration touches it. It is an ungradedfinding, #9497 explicitly did not exercise the objectui pin-range path, and grading it would be fresh adjudication rather than execution against a measured spec. The two blocks are textually disjoint, so nothing is gained by bundling. It stays open and unassigned for its own dispatch.8. Changeset decision —
skip-changesetThis PR releases nothing. The diff is a root devDependency range plus release machinery: two gate scripts, two workflows' comments, an internal maintainer doc, and
.changeset/config.json. No workspace package's published surface, runtime, or types are touched;docs/is notcontent/docs/. Naming a package in a changeset would put a false line in 69 CHANGELOGs. The label is the exemption designed for exactly this, so it is applied rather than an empty changeset written (#5471/#4898).9. Gates — re-derived for this diff and run
node scripts/pm/dispatch-gates.mjsover the 8 changed paths named 13 families; all 13 run green on the final commit —git rev-parse --short HEADat the time of the run:06e7ca712:10. For whoever dispatches the next cut
cut-rc.ymlasserts computed == dispatched. From today's stock the computed number is 17.1.0-rc.0, not 18.0.0-rc.0. Dispatch the new number, or the cut refuses before anything is pushed or published.Rehearsal rig — reproducible, and why it terminates
Per #9555: a local
pnpm run versionnever terminates in this container, because@changesets/git'sgetCommitsThatAddFilesdeepens in a loop against a shallow clone with no reachable remote. The scaffold, inside a throwaway clone with its remote removed:Tree hash verified identical before and after (
7c25e1c0f04822a9fd29f5718b9c1ec32a509fea). A fullpnpm run versionthen takes 7.7s. No release was performed: no publish, no tag, no pre-mode transition onorigin, no workflow dispatch, no Version Packages PR touched..changeset/pre.jsonis absent onmainand stays absent on this branch.Generated by Claude Code