Skip to content

Commit e7daea1

Browse files
chore(release): migrate the release toolchain to @changesets/cli v3 (#9560)
* chore(release): bump @changesets/cli to v3 and migrate .changeset/config.json - privatePackages: {version:true, tag:false} (U4) - drop ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH (U2) - $schema -> @changesets/config@4.0.0 (the version v3 resolves) * chore(release): model v3's pre-mode semantics in the gates, workflows 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 --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 65d4fff commit e7daea1

8 files changed

Lines changed: 444 additions & 621 deletions

File tree

.changeset/config.json

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"$schema": "https://unpkg.com/@changesets/config@3.1.2/schema.json",
2+
"$schema": "https://unpkg.com/@changesets/config@4.0.0/schema.json",
33
"changelog": [
44
"@changesets/cli/changelog",
55
{
@@ -84,7 +84,5 @@
8484
"baseBranch": "main",
8585
"updateInternalDependencies": "patch",
8686
"ignore": [],
87-
"___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
88-
"onlyUpdatePeerDependentsWhenOutOfRange": true
89-
}
87+
"privatePackages": { "version": true, "tag": false }
9088
}

.github/workflows/cut-rc.yml

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -486,17 +486,39 @@ jobs:
486486
# cut: the pin bump and the version output are one atomic unit, and there is
487487
# never a main commit carrying a bumped pin but an unversioned tree.
488488
#
489-
# WHAT MAY BE IN IT — re-measured on the 17.0.0 -> 18.0.0-rc.0 train (a full
490-
# local `pnpm run version` in a throwaway clone over 199 pending changesets),
491-
# not assumed. 160 paths:
492-
# 77 modified package.json, 76 modified CHANGELOG.md,
493-
# .changeset/pre.json, the 3 major-boundary paths below, and the 3 doc
494-
# surfaces below. Zero deletions. `.changeset/pre.json` is UNTRACKED on the
495-
# first cut of a train (`changeset pre enter` just created it) and modified
496-
# on later ones; `git add -A` covers both. In PRE mode `changeset version`
497-
# does NOT delete the consumed changesets — it records them in pre.json and
498-
# they are removed at `changeset pre exit` — so a cut never races a lane PR
499-
# over a `.changeset/*.md` file it wants to keep.
489+
# WHAT MAY BE IN IT — re-measured under @changesets/cli v3 on the
490+
# 17.0.0 -> 17.1.0-rc.0 train (a full local `pnpm run version` in a throwaway
491+
# clone over 209 pending changesets), not assumed. 365 staged paths:
492+
# 76 modified package.json, 76 modified CHANGELOG.md, 209 changesets MOVED
493+
# into `.changeset/pre/`, `.changeset/pre.json`, and the 3 doc surfaces
494+
# below. `.changeset/pre.json` is UNTRACKED on the first cut of a train
495+
# (`changeset pre enter` just created it) and modified on later ones;
496+
# `git add -A` covers both.
497+
# The 3 major-boundary paths below did NOT appear on this train and are
498+
# allowed for the train that does move the major — as is the blank
499+
# template's own package.json, which is why the v2 measurement this block
500+
# used to carry counted 77 package.json against this one's 76: that train
501+
# was 17.0.0 -> 18.0.0-rc.0, so `sync-template-versions.mjs` had to rewrite
502+
# the template's `^17.0.0` pins. On a minor train it logs "already pins" and
503+
# writes nothing. The counts move with the SHAPE of the train; the
504+
# allowlist below is what does not have to.
505+
#
506+
# THE CONSUMED CHANGESETS MOVE — new in v3 (changesets#2190), and the one
507+
# thing about a cut that this file's prose used to get wrong. v2 left every
508+
# consumed `.changeset/*.md` on disk and recorded it in `pre.json`; v3 moves
509+
# it to `.changeset/pre/NAME.md` VERBATIM (measured: a moved file diffs
510+
# identical to its original, and git reports all 209 as `R100` renames), and
511+
# `pre.json` is `{"mode","tag"}` only — `changeset version` never rewrites
512+
# it. So in the worktree a cut is 209 deletions plus a new untracked
513+
# `.changeset/pre/` directory, and the claim this block used to make — "a
514+
# cut never races a lane PR over a `.changeset/*.md` file it wants to keep"
515+
# — is FALSE under v3. The cut moves tracked changeset paths, so a lane PR
516+
# that edits or deletes one of them conflicts; the rebase guard further down
517+
# is what catches it, and it already names `.changeset` in its conflict
518+
# surface. What does NOT change is this allowlist: `^\.changeset/` covers
519+
# `.changeset/pre/…` and `git add -A -- … .changeset …` stages the deletions
520+
# and the new directory together (measured: all 365 staged paths accepted,
521+
# nothing tracked left unstaged).
500522
# Three more paths can appear at a major boundary and are allowed for that
501523
# reason, all written by `sync-protocol-version.mjs` /
502524
# `sync-template-versions.mjs`: packages/spec/src/kernel/protocol-version.ts
@@ -848,7 +870,7 @@ jobs:
848870
echo "### Two things that are expected, not defects"
849871
echo
850872
echo "1. **The standing \`chore: version packages (rc)\` PR (#6208) may look stale.**"
851-
echo " Its changesets were consumed by this cut and are recorded in \`.changeset/pre.json\`."
873+
echo " Its changesets were consumed by this cut and moved to \`.changeset/pre/\` (v3)."
852874
echo " Whether it refreshes now depends on the push credential: pushes made with the"
853875
echo " Actions \`GITHUB_TOKEN\` trigger no workflow runs (GitHub's recursion guard), so"
854876
echo " \`version-pr\` does not fire until some later push to main; with a"

.github/workflows/pr-automation.yml

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -383,12 +383,25 @@ jobs:
383383
fi
384384
# Count changesets THIS PR adds (diff against the base commit), NOT
385385
# the whole .changeset directory. A global `find | wc -l` is unsound:
386-
# in pre-release (RC) mode `changeset version` RETAINS every consumed
387-
# .md file, so the directory is permanently non-empty and the gate can
388-
# never go red. #3373 merged a real spec/api-surface fix with no
389-
# changeset while this step happily reported "Found 104 changeset(s)".
390-
# Diffing against the merge base ignores that residue and sees only
391-
# what the PR itself introduced. It has to be the MERGE BASE and not
386+
# it counts main's pending stock, which has nothing to do with what this
387+
# PR wrote. #3373 merged a real spec/api-surface fix with no changeset
388+
# while this step happily reported "Found 104 changeset(s)".
389+
# Diffing against the merge base ignores that stock and sees only what
390+
# the PR itself introduced.
391+
#
392+
# That argument used to be stated in terms of pre-mode RESIDUE — under
393+
# @changesets/cli v2, `changeset version` in pre mode retained every
394+
# consumed .md file, so `.changeset/` was permanently non-empty for the
395+
# whole RC window. v3 moves consumed changesets into `.changeset/pre/`
396+
# instead (changesets#2190), so the root directory is no longer
397+
# permanently non-empty. The unsoundness is unchanged and so is this
398+
# step: a stock count is the wrong question in EVERY repo phase, not just
399+
# a polluted one. What the migration did have to be checked against is
400+
# the counter itself, because a cut now MOVES 209 tracked changeset
401+
# paths: measured on a real v3 cut commit, git reports them as `R100`
402+
# renames, so `--diff-filter=A` credits this PR with 0 of them (still 0
403+
# with `diff.renameLimit=1` forced; only `--no-renames` turns them into
404+
# 209 `A` rows, and nothing here passes it). It has to be the MERGE BASE and not
392405
# the payload's frozen `base.sha` -- see the base-resolution step above
393406
# (#6129); with the frozen sha this count silently included every
394407
# changeset main gained while the PR was open.
@@ -827,12 +840,19 @@ jobs:
827840
# same #6129 reason one defect along (#7005). This script used to read
828841
# the whole `.changeset` directory with no branch point, so its verdict
829842
# was a function of what main carried rather than of what the author
830-
# wrote. That was invisible while pre-mode held and would have become
831-
# visible all at once at `changeset pre exit`: 171 consumed-but-undeleted
832-
# major changesets sit on main until the post-exit `changeset version`
833-
# removes them, so every unlabelled PR open in that window would have
834-
# gone red listing files it never touched, with `allow-major` -- a label
835-
# meaning "a whole-stack major is intended HERE" -- as its only way out.
843+
# wrote. Under @changesets/cli v2 that was invisible while pre-mode held
844+
# and would have become visible all at once at `changeset pre exit`: 171
845+
# consumed-but-undeleted major changesets sat on main until the post-exit
846+
# `changeset version` removed them, so every unlabelled PR open in that
847+
# window would have gone red listing files it never touched, with
848+
# `allow-major` -- a label meaning "a whole-stack major is intended HERE"
849+
# -- as its only way out. v3 retires that particular detonation: each cut
850+
# moves its consumed changesets into `.changeset/pre/`, which the gate's
851+
# root-only reader does not enumerate, so the root stock stays bounded to
852+
# the unconsumed residue. It retires no line of the fix. `--base` is
853+
# correct because a verdict about what a PR INTRODUCED cannot be computed
854+
# without the fork -- #6129's argument, which never depended on how big
855+
# the stock was.
836856
if: >-
837857
steps.labels.outputs.skip != 'true'
838858
&& steps.labels_settled.outputs.skip != 'true'

docs/releases-maintenance.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -303,11 +303,14 @@ rediscovered at 2am:
303303
by itself.
304304

305305
**A stale #6208 after an rc cut is EXPECTED AND HARMLESS — do not "fix" it by hand.**
306-
Its changesets were consumed by the cut and are recorded in `.changeset/pre.json`; the
307-
PR is bookkeeping, it carries no publish capability by construction (`release.yml`
308-
passes the changesets action no `publish:` script), and it regenerates correctly at
309-
the next push to `main` or the next GA cut. Editing or force-refreshing it manually
310-
only risks putting a version commit somewhere the publish lane can reach.
306+
Its changesets were consumed by the cut and MOVED into `.changeset/pre/` — under
307+
`@changesets/cli` v3 that move is what "consumed" looks like on disk, and
308+
`.changeset/pre.json` carries `{"mode","tag"}` and nothing else, so it is not a
309+
record of what was consumed (it was, under v2). The PR is bookkeeping, it carries no
310+
publish capability by construction (`release.yml` passes the changesets action no
311+
`publish:` script), and it regenerates correctly at the next push to `main` or the
312+
next GA cut. Editing or force-refreshing it manually only risks putting a version
313+
commit somewhere the publish lane can reach.
311314

312315
**The runtime image is not built here.** `release.yml`'s `release-integrity` lane runs
313316
on every push to `main` and requests the image once the version is on npm, so it

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@
117117
"author": "ObjectStack",
118118
"license": "Apache-2.0",
119119
"devDependencies": {
120-
"@changesets/cli": "^2.31.1",
120+
"@changesets/cli": "^3.0.0",
121121
"@types/node": "^26.1.2",
122122
"@typescript-eslint/parser": "^8.65.0",
123123
"eslint": "^10.8.0",

0 commit comments

Comments
 (0)