Promote next channel to stable (@varlock/bumpy 1.14.0)#110
Merged
Conversation
…nt on next-targeted PRs) pull_request_target reads the workflow from the base branch, so this must live on next for #104's own check to use it. The check-local job builds the PR head, so next not yet having the feature source is fine.
…ions) (#104) ## Summary Branch-based **prerelease channels** — design doc *and* full implementation. This PR started as an RFC; after a few design revisions (see commit history) it now ships the feature, tests, and docs. The TL;DR of the model: - **Branch = channel.** Nominate a `next` / `beta` / etc. branch in `.bumpy/_config.json`. Pushing to it produces `-rc.N` / `-beta.N` versions on the matching dist-tag, using the same `ci release` flow as `main`. - **Prerelease versions are never committed to git.** Every `package.json` on a channel branch keeps the last stable version, identical to `main`. Targets are derived from bump files, counters from the registry (`max published -preid.N + 1`), idempotency from npm's `gitHead` metadata. No suffix to strip at promotion, no counter state to corrupt, no version conflicts on `main` ↔ channel syncs, and abandoned cycles can't cause registry collisions. - **Bump file location is the only state.** Pending at `.bumpy/*.md`, shipped at `.bumpy/<channel>/*.md`. The "release PR" on a channel is a pure file-move PR — computed versions appear in its title and merge commit message as narrative; the registry wins at publish time. The general pending rule ("pending unless in *this* context's dir") gives alpha → beta graduation for free. - **The cycle moves as one.** Every publish recomputes the full cycle; on channels, range satisfaction is checked against the prerelease version (which never satisfies a stable range), so all dependents join at proportional bump levels and inter-cycle deps are exact-pinned in the published artifacts. The channel dist-tag always points at one coherent set. - **Promotion is just a merge.** Main treats channel-dir files as pending, so the ordinary stable flow consumes them, bumps stable-to-stable, and writes one consolidated `CHANGELOG.md` entry. Zero special promotion code. ## Why not just match changesets' pre mode? Changesets' [own docs](https://github.com/changesets/changesets/blob/main/docs/prereleases.md) describe their prerelease mode as *"very complicated"* with *"mistakes that can lead to repository and publish states that are very hard to fix."* [docs/prereleases.md](https://github.com/dmno-dev/bumpy/blob/docs/prerelease-channels/docs/prereleases.md) surveys the recurring complaints ([#239](changesets/changesets#239), [#381](changesets/changesets#381), [#729](changesets/changesets#729), [#786](changesets/changesets#786), [#960](changesets/changesets#960)) and designs them out rather than re-importing them — see the side-by-side comparison table at the bottom of the doc. ## Implementation - `src/core/channels.ts` — config resolution + validation, branch detection (prefers `GITHUB_REF_NAME` for detached CI checkouts), `--channel` override. - `src/core/prerelease.ts` — registry-floor counters, per-package published-from-`HEAD` skip (`gitHead` on npm; git tags for custom-publish packages), and the transient in-place rewrite: computed versions + exact pins are written to the working tree so pack/build see them, then restored in a `finally`. - `src/core/release-plan.ts` — new `prereleasePreid` mode: Phase A checks ranges against `<target>-<preid>.0`, producing the required wide-but-proportional cascade (channel-only; stable plans are unchanged). - Command flows: `version` on a channel only moves files; `publish` derives + rewrites + publishes to the channel dist-tag (and the stable path refuses suffixed versions when channels are configured); `ci release` publishes when the triggering push moved files into `.bumpy/<channel>/` and maintains the file-move release PR (with `versionPr.automerge` support); `status` shows the cycle with registry-derived counters; `check` skips channel branches and gains `--base`; unknown branches make `ci release` error instead of guessing. GitHub releases for prereleases are marked `--prerelease`. - Config schema (`config-schema.json`) updated; `preid` is optional in the schema to leave room for future stable/maintenance channels. ## Out of scope (deliberately) - Ephemeral per-PR/per-commit previews — [pkg.pr.new](https://pkg.pr.new)'s job; the doc draws the line explicitly. - Workflow-dispatch one-off prereleases — nearly free under this architecture (same compute-and-publish from any SHA); planned as a fast-follow. - Stable/maintenance channels (`1.x` branches) — future; config shape already accommodates. - Per-bump-file `channel:` frontmatter — not planned; channels stay branch-derived. ## Test plan - [x] 285 tests pass (27 new: channel config resolution/validation, registry-floor counter math, exact-pin rewrite + restore, channel-dir bump file reading/moves/duplicate detection, prerelease cascade behavior incl. `workspace:*` opt-out and transitivity) - [x] `tsc --noEmit`, oxlint, oxfmt clean - [x] End-to-end smoke test in a scratch workspace: bump file → channel `version` (file move only, no version writes) → `status` (correct `rc.0` counters) → `publish --dry-run` (correct versions, `--tag next`, exact pins) → merge to `main` → stable `version` (channel dir consumed, consolidated changelog, no spurious cascade on main) - [ ] Dogfood on a real channel branch in this repo once merged (first real `bumpy ci release` run on a `next` push)
<a href="https://bumpy.varlock.dev"><img src="https://raw.githubusercontent.com/dmno-dev/bumpy/main/images/frog-clipboard.png" alt="bumpy-frog" width="60" align="left" style="image-rendering: pixelated;" title="Hi! I'm bumpy!" /></a> This PR was created and will be kept in sync by [bumpy](https://bumpy.varlock.dev) based on your bump files (in `.bumpy/`). Merge it when you are ready to release the packages listed below: <br clear="left" /> > 🔀 **Prerelease channel `next`** — merging this PR publishes the versions below to the `@next` dist-tag. > The diff only moves bump files into `.bumpy/next/` — prerelease versions are derived at publish time and never committed. Version numbers shown here are estimates; the registry wins at publish. ### <a href="https://bumpy.varlock.dev" title="Minor releases"><img src="https://raw.githubusercontent.com/dmno-dev/bumpy/main/images/frog-minor.png" alt="minor" width="52" style="image-rendering: pixelated;" align="right" /></a> Minor releases #### `@varlock/bumpy` 1.13.2 → **1.14.0-rc.0** <sub>[CHANGELOG.md](https://github.com/dmno-dev/bumpy/pull/105/changes#diff-4172fac00a078eb9cef75fbac6f1df8320cd5bf3d45ed733e055f7567d8d0e29)</sub> - Add prerelease channels — branch-based prerelease lines (e.g. `next` → `@next` dist-tag) where prerelease versions are never committed to git. Targets derive from bump files, counters from the registry; shipped bump files are tracked by moving them into `.bumpy/<channel>/`. Includes channel-aware `version` / `publish` / `status` / `ci release` flows, exact-pinned lockstep cycle publishes, and promotion-by-merge to stable. ([bump file](https://github.com/dmno-dev/bumpy/pull/105/changes#diff-cd102124258af740299a22752ba5caa925bab64cdec60ef8d060beddc4687c75)) Co-authored-by: bumpy-bot <276066384+bumpy-bot@users.noreply.github.com>
…106) When the publish job runs in a GitHub Environment with deployment branch restrictions (our recommended hardening restricts it to `main`), prerelease channel branches can't enter the environment — with trusted publishing this means OIDC token requests are rejected and channel publishes fail. Adds reminders in both places users would hit this: - **docs/prereleases.md** — setup step 3 (adding the channel branch to the release workflow) now calls out updating the environment's allowed deployment branches. - **docs/github-actions.md** — the "restrict deployment branches to `main`" hardening bullet now notes channel branches must be added to the allowed list.
Channel release PR titles previously showed registry-derived prerelease counters, which could be out of sync with what actually publishes (the registry is re-queried at publish time and wins). They also degraded poorly in multi-package cycles: an arbitrary alphabetical lead package plus `(+N more)`. Titles, PR bodies, and merge commit messages now only claim what's derivable from committed state: - **Wildcard counter** — versions display as `1.2.0-rc.x`; the target comes from bump files (deterministic), the `.x` is assigned from the registry at publish. The title can no longer drift, by construction. - **Package count for multi-package cycles** — `🐸 Versioned release (next): 4 packages` instead of an arbitrary lead + `(+N more)`. Single-package cycles keep `name@1.2.0-rc.x`. - **No registry call in the version-PR job** — the `forDisplay` fetch (and its offline `-rc.?` fallback) is gone from the release PR path; one less network dependency and failure mode. `status` and `ci plan` keep live registry-derived counters (`.?` when offline) since they're interactive/live output. - **Consistent wildcard** — the PR check comment and channel `version` output now use `.x` too (they never queried the registry; `.?` previously implied a failed lookup rather than "assigned later"). Docs updated where they described the title as "advisory narrative; registry wins" — the new story is simpler: the title only shows deterministic state. All 292 tests pass, including new coverage for `channelDisplayPlan` (wildcard mapping + unpublishable-package filtering).
<a href="https://bumpy.varlock.dev"><img src="https://raw.githubusercontent.com/dmno-dev/bumpy/main/images/frog-clipboard.png" alt="bumpy-frog" width="60" align="left" style="image-rendering: pixelated;" title="Hi! I'm bumpy!" /></a> This PR was created and will be kept in sync by [bumpy](https://bumpy.varlock.dev) based on your bump files (in `.bumpy/`). Merge it when you are ready to release the packages listed below: <br clear="left" /> > 🔀 **Prerelease channel `next`** — merging this PR publishes the versions below to the `@next` dist-tag. > The diff only moves bump files into `.bumpy/next/` — prerelease versions are derived at publish time and never committed. The `.x` counter is assigned from the registry at publish time. ### <a href="https://bumpy.varlock.dev" title="Minor releases"><img src="https://raw.githubusercontent.com/dmno-dev/bumpy/main/images/frog-minor.png" alt="minor" width="52" style="image-rendering: pixelated;" align="right" /></a> Minor releases #### `@varlock/bumpy` 1.13.2 → **1.14.0-rc.x** <sub>[CHANGELOG.md](https://github.com/dmno-dev/bumpy/pull/109/changes#diff-4172fac00a078eb9cef75fbac6f1df8320cd5bf3d45ed733e055f7567d8d0e29)</sub> - Add prerelease channels — branch-based prerelease lines (e.g. `next` → `@next` dist-tag) where prerelease versions are never committed to git. Targets derive from bump files, counters from the registry; shipped bump files are tracked by moving them into `.bumpy/<channel>/`. Includes channel-aware `version` / `publish` / `status` / `ci release` flows, exact-pinned lockstep cycle publishes, and promotion-by-merge to stable. ([bump file](https://github.com/dmno-dev/bumpy/pull/109/changes#diff-cd102124258af740299a22752ba5caa925bab64cdec60ef8d060beddc4687c75)) - Channel release PR titles and bodies now show deterministic versions: targets with a wildcard counter (`1.2.0-rc.x`) derived purely from committed state, instead of registry-derived counters that could drift between PR creation and publish. Multi-package cycles show a package count in the title instead of an arbitrary lead package. The PR check comment and `version` output use the same `.x` wildcard; `status` / `ci plan` still show live registry-derived counters (`.?` when offline). ([bump file](https://github.com/dmno-dev/bumpy/pull/109/changes#diff-66048fddb3a0d7725cd37a4ec20bd6f474c6cbb0e3cfea8d9137e2dd8416e521)) Co-authored-by: bumpy-bot <276066384+bumpy-bot@users.noreply.github.com>
|
This PR promotes the
|
Found via the promotion PR [#110](#110): its check failed and commented "Merging this PR will not cause a version bump" even though the cycle has two pending bump files. **Root cause:** `ciCheckCommand` called `readBumpFiles(rootDir)` without the `channels` option, so files in `.bumpy/next/` were never parsed. On a promotion PR those are exactly the pending files (the diff vs main lists them), so the diff-based filter matched nothing → "no bump files" comment + exit 1. **Fix:** - `ci check` reads channel dirs like every other channel-aware path. This is safe for all PR shapes: feature PRs never have shipped channel files in their diff vs the PR base; promotion (channel → main) and graduation (channel → channel) PRs do, and there they genuinely are pending. - Channel-dir bump files render in the comment as `next/feature.md` with subdir-aware view-diff/edit links (previously they'd have linked to the nonexistent root path). Deliberately **not** changed: the local `bumpy check` and `bumpy add` commands also read bump files without channels, but they always diff against `config.baseBranch` — making them channel-aware without base-branch awareness would wrongly count shipped channel files as branch coverage on feature branches off `next`. Separate issue. Tests: promotion-PR comment rendering + the channel-path/basename id contract that the diff filter relies on. 295 pass. After this merges to next, re-triggering the check on #110 should produce the proper stable-promotion comment (`1.13.2 → 1.14.0`).
Follow-up from [#110](#110 comment looking identical to an ordinary feature PR's. A promotion PR is the highest-stakes merge in the channel flow — it ends the cycle, consolidates the changelog, and ships to `@latest` — so the comment now says so. **Detection:** stable-targeted PR (no channel match on the base) carrying bump files with `channel` set. Covers the canonical `next` → `main` PR and partial promotions from branches cut off a channel. Channel-targeted PRs keep their existing banner. **Headline** becomes: > **This PR promotes the `next` prerelease cycle to a stable release.** The changes below that already shipped to the `@next` dist-tag will be consolidated into the next stable version bump. **File list** annotates shipped files, so mixed PRs (shipped cycle + never-rc'd fixes) read at a glance: > - `next/prerelease-channels.md` _(shipped on `@next`)_ > - `ci-check-channel-bump-files.md` The shipped annotation also appears on graduation PRs (e.g. `.bumpy/alpha/` files on a beta-targeted PR), where it's equally accurate. 297 tests pass. Once merged, #110's comment should update itself with the promotion callout (next moves forward → synchronize → check-local rebuilds).
This was referenced Jun 13, 2026
theoephraim
added a commit
that referenced
this pull request
Jun 13, 2026
Dogfood finding from the first promotion: after merging [#110](#110), the channel release PR [#112](#112) lingered open, offering to publish another rc of a cycle that had already moved to its stable release ([#114](#114)). I closed it manually; this makes bumpy do it. **When:** as part of creating/updating the destination's version PR — - stable version PR on main whose plan includes channel-dir bump files (promotion), and - channel release PR creation that moved files from *other* channels (graduation, e.g. `alpha` → `beta`). **What:** for each promoted source channel, look up an open PR on its `versionPr.branch` head and close it with a comment explaining why, noting a fresh release PR appears if more work lands on the channel. Failures are warn-only — a lingering PR is annoying, not dangerous. The current channel's own PR is explicitly never touched. Targeting `main` (rather than `next`) deliberately: this path executes via the published bumpy in the release workflow, so shipping it in 1.14.0 activates it for the next cycle's promotion — and `next` is due for its post-promotion reset anyway. The bump file will fold into [#114](#114). Docs updated in the promotion section. 297 tests pass. --- **Update — race fix (b13e94d):** the first cut closed the source channel's release PR whenever its bump files were *pending* on the destination, but promoted files stay pending until the destination's version/release PR merges (potentially days). If a user started a fresh `next` cycle in that window, an unrelated merge to `main` would close its legitimate release PR — then the next push to `next` recreates it, and the cycle repeats (close → recreate ping-pong). Now the close is gated on the **triggering push** actually adding files under `.bumpy/<channel>/` (its `before..after` range — the same detection the channel publish trigger uses), so it fires exactly once, on the promotion/graduation merge itself. Moved both call sites to after the checkout-back so the non-CI fallback (`HEAD^..HEAD`) diffs the triggering commit from the right branch. _Thanks to @theoephraim for spotting the restarted-cycle race._
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.


Promotes the
nextprerelease channel to stable. This merge carries the cycle's accumulated bump files (in.bumpy/next/) into main — versions never diverged, so the diff is the feature work plus file moves.On merge, main's release workflow will open the ordinary stable version PR:
@varlock/bumpy→ 1.14.0, with a consolidated changelog entry built from the cycle's bump files. The@nextdist-tag has shipped1.14.0-rc.0and1.14.0-rc.1through this cycle.What's in the cycle:
rc.xcounters in PR titles/bodies/commits so they can't drift from the registry; package count for multi-package cycles. Validated live on #109.