Skip to content

Commit 98c0121

Browse files
authored
ci: let the release pull request merge itself on a cadence switch (#180)
## Related Issue No issue — this came out of asking why `1.2.0` went straight to `2.0.0`. ## Problem Changesets keeps one `ci: release packages` pull request open on `main` and rewrites it as changesets land. Merging it cuts exactly one release, so **how often it is merged is what decides the version sequence** — and nothing said how often that should be. Left to accumulate, it collapses a backlog into one bump. The current release pull request carries **19 changesets, all from a single squashed pull request**: one `major`, four `minor`, fourteen `patch`. They become one version, `2.0.0`, and every number in between never exists. The changelog cannot attribute anything either, because every entry cites the same commit. ## What changed A repository variable, `AUTO_MERGE_RELEASE_PR`, picks the cadence: - `true` — the release pull request merges itself once its required checks pass. One merge to `main` is one release, and the version tracks each change: `2.1.2`, `2.1.3`, `2.1.4`, `2.2.0`. - unset or `false` — today's behaviour, unchanged. A maintainer merges it when a release is wanted. This is deliberately **not** a blanket auto-merge: - it only ever targets the changesets-authored `changeset-release/main` branch, - the repository's ten required status checks still gate the merge (`strict: true`, so the branch must be current), - and a `major` is gated separately, on the pull request that introduces the changeset — so an unattended release can never rename the major version on its own. The step is `continue-on-error`. A version pull request left open costs a manual merge; a failure here would block npm, the Marketplace and the CDN behind it. `CONTRIBUTING.md` now states the bump levels and both cadences, so the version sequence is a documented choice rather than a side effect of merge timing. ### Not included Tagging is already correct and needed no change — releases are tagged `@pymodel/pythinker-code@<version>` with a matching GitHub Release, and the desktop app cuts `desktop-v*` separately. [skip changeset] — release tooling and contributor docs only; nothing here reaches the published package. ## Checklist - [x] I have read the [CONTRIBUTING](https://github.com/PyModel/pythinker-code/blob/main/CONTRIBUTING.md) document. - [ ] I have linked a related issue (external PRs: the issue must have a maintainer's `/approve`). - [ ] I have added tests that prove my feature works. - [x] Ran `gen-changesets` skill, or this PR needs no changeset. - [x] Ran `gen-docs` skill, or this PR needs no doc update. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added optional automatic squash-merging for release pull requests when enabled. * Improved release workflows by reliably identifying the correct release pull request for review and merging. * Release workflow lookup issues remain non-blocking when a matching pull request is unavailable. * **Documentation** * Clarified one-changeset-per-PR guidance and approval requirements for major changes. * Documented that pinned installations remain functional until consumers upgrade. * Explained how changesets added during release checks are included in the pending release. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 2ecbe34 commit 98c0121

2 files changed

Lines changed: 114 additions & 4 deletions

File tree

.github/workflows/release.yml

Lines changed: 91 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -180,15 +180,102 @@ jobs:
180180
fi
181181
echo "Cut ${tag} at ${GITHUB_SHA} (desktop ${prev} -> ${curr})."
182182
183-
- name: Request CodeRabbit review on version PR
183+
# Release cadence switch. With this on, the version PR merges itself once
184+
# every required check passes, so one merge to main becomes one release
185+
# and the version tracks each change instead of collapsing a backlog of
186+
# changesets into a single jump. Turn it off to go back to releasing by
187+
# hand: `gh variable set AUTO_MERGE_RELEASE_PR --body false`.
188+
#
189+
# This is deliberately not a blanket auto-merge. It only ever targets the
190+
# changesets-authored branch, the repository requires its status checks
191+
# before any merge, and a major bump is gated separately on the pull
192+
# request that introduces the changeset — so an unattended release can
193+
# still never rename the major version on its own.
194+
#
195+
# Never fail the release over this: a version PR that stays open costs a
196+
# manual merge, while a failure here would block npm, the Marketplace and
197+
# the CDN behind it.
198+
# Resolve the version PR once, for every step that acts on it.
199+
#
200+
# The changesets action reports the pull request it just created or
201+
# updated, which is the only unambiguous identifier. The fallback covers
202+
# the run where changesets had nothing to change, and it is scoped to a
203+
# head branch in THIS repository by full `owner/name`: `--head` matches a
204+
# branch name only, so a head-name match on its own can select a fork's
205+
# branch of the same name, and matching the owner alone would still accept
206+
# a different repository belonging to that owner.
207+
- name: Resolve the version PR
208+
id: version-pr
184209
if: steps.changesets.outputs.published != 'true'
185210
env:
186211
GH_TOKEN: ${{ steps.release-bot.outputs.token }}
212+
CHANGESETS_PR: ${{ steps.changesets.outputs.pullRequestNumber }}
187213
run: |
188-
pr=$(gh pr list --head changeset-release/main --state open --json number --jq '.[0].number' || true)
189-
if [ -n "$pr" ] && [ "$pr" != "null" ]; then
190-
gh pr comment "$pr" --body '@coderabbitai review'
214+
set -uo pipefail
215+
if [ -n "${CHANGESETS_PR}" ] && [ "${CHANGESETS_PR}" != "null" ]; then
216+
echo "number=${CHANGESETS_PR}" >> "${GITHUB_OUTPUT}"
217+
echo "Version PR #${CHANGESETS_PR}, as reported by the changesets action."
218+
exit 0
219+
fi
220+
if ! open_prs=$(gh pr list \
221+
--repo "${GITHUB_REPOSITORY}" \
222+
--head changeset-release/main \
223+
--base main \
224+
--state open \
225+
--json number,headRepository); then
226+
echo "::warning::Could not look up the version PR. Nothing downstream will run; check it by hand."
227+
exit 0
191228
fi
229+
number=$(printf '%s' "${open_prs}" \
230+
| jq -r --arg repo "${GITHUB_REPOSITORY}" \
231+
'[.[] | select(.headRepository.nameWithOwner == $repo)][0].number // ""')
232+
if [ -z "${number}" ]; then
233+
echo "::notice::No open version PR in this repository."
234+
exit 0
235+
fi
236+
echo "number=${number}" >> "${GITHUB_OUTPUT}"
237+
echo "Version PR #${number}."
238+
239+
# Release cadence switch. With this on, the version PR merges itself once
240+
# every required check passes, so one merge to main becomes one release
241+
# and the version tracks each change instead of collapsing a backlog of
242+
# changesets into a single jump. Turn it off to go back to releasing by
243+
# hand: `gh variable set AUTO_MERGE_RELEASE_PR --body false`.
244+
#
245+
# This is deliberately not a blanket auto-merge. It only ever targets the
246+
# changesets-authored branch in this repository, the repository requires
247+
# its status checks before any merge, and a major bump is gated separately
248+
# on the pull request that introduces the changeset — so an unattended
249+
# release can still never rename the major version on its own.
250+
#
251+
# A changeset that lands while this pull request is waiting on its checks
252+
# joins the same release rather than starting the next one. That window is
253+
# how changesets works, not something this step can close; keeping one
254+
# changeset per pull request keeps it small.
255+
#
256+
# Never fail the release over this: a version PR that stays open costs a
257+
# manual merge, while a failure here would block npm, the Marketplace and
258+
# the CDN behind it.
259+
- name: Enable auto-merge on the version PR
260+
if: steps.version-pr.outputs.number != '' && vars.AUTO_MERGE_RELEASE_PR == 'true'
261+
continue-on-error: true
262+
env:
263+
GH_TOKEN: ${{ steps.release-bot.outputs.token }}
264+
PR: ${{ steps.version-pr.outputs.number }}
265+
run: |
266+
set -uo pipefail
267+
if gh pr merge "${PR}" --squash --auto; then
268+
echo "Auto-merge armed on #${PR}; it lands when its required checks pass."
269+
else
270+
echo "::warning::Could not arm auto-merge on #${PR}. Merge it by hand to cut the release."
271+
fi
272+
273+
- name: Request CodeRabbit review on version PR
274+
if: steps.version-pr.outputs.number != ''
275+
env:
276+
GH_TOKEN: ${{ steps.release-bot.outputs.token }}
277+
PR: ${{ steps.version-pr.outputs.number }}
278+
run: gh pr comment "${PR}" --body '@coderabbitai review'
192279

193280
- name: Resolve Pythinker Code native release
194281
if: steps.changesets.outputs.published == 'true'

CONTRIBUTING.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,29 @@ This repo uses [changesets](https://github.com/changesets/changesets) to manage
8080
- Generate one with `pnpm changeset` and follow the prompts (which packages are touched, which bump level).
8181
- For repo-specific conventions on package selection and bump levels, see `.changeset/README.md`. When working in this repo with coding agents, use the `gen-changesets` skill.
8282

83+
### Bump levels
84+
85+
| Level | Use for | Example |
86+
| --- | --- | --- |
87+
| `patch` | A fix, or a small addition to something that already exists | `2.1.2``2.1.3` |
88+
| `minor` | A capability a user could not reach before | `2.1.3``2.2.0` |
89+
| `major` | A break: something that worked stops working, or works differently | `2.2.0``3.0.0` |
90+
91+
Prefer one changeset per pull request. A pull request that needs several is usually carrying several separate releases, and once they are versioned together the changelog can no longer say which change each entry came from.
92+
93+
A `major` needs a maintainer's sign-off: the `changeset-policy` workflow fails a pull request that adds a major changeset, or edits an existing one up to `major`, unless it carries the `breaking-change-approved` label. A pinned install keeps working, but every consumer who upgrades has to deal with the break, and an npm publish cannot be taken back — so it is a decision, never a side effect of a large branch.
94+
95+
### Release cadence
96+
97+
Changesets keeps a `ci: release packages` pull request open on `main` and rewrites it as changesets land. Merging it cuts exactly one release, so how often it is merged is what decides the version sequence:
98+
99+
- Merged per change, versions follow each change: `2.1.2`, `2.1.3`, `2.1.4`, `2.2.0`.
100+
- Left to accumulate, a backlog collapses into one bump and the numbers in between never exist.
101+
102+
The repository variable `AUTO_MERGE_RELEASE_PR` chooses between the two. Set to `true`, the release pull request merges itself once its required checks pass, giving one release per change. Unset or `false`, a maintainer merges it when a release is wanted.
103+
104+
Either way there is a window: a changeset that lands while the release pull request is waiting on its checks joins that release instead of starting the next one. That is how changesets works, so a release can still carry more than one change — one changeset per pull request keeps the window small.
105+
83106
## Pull Requests
84107

85108
Every PR opens with the [PR template](.github/pull_request_template.md). PR titles must follow [Conventional Commits](#commit-convention); CI runs `pnpm lint`, `pnpm typecheck`, and `pnpm test` on every PR. Update user-facing docs in `docs/` when behavior changes — use the `gen-docs` skill when working with coding agents.

0 commit comments

Comments
 (0)