feat: prerelease channels (branch-based, no committed prerelease versions)#104
Merged
Conversation
Draft design doc for branch-based prerelease support. Models channels as long-lived branches (next, beta, etc.) with bump files tracked by directory location — pending at .bumpy/*, shipped at .bumpy/<channel>/, consolidated on promotion to main. Documents the comparison with changesets' pre mode and what's deliberately out of scope. Not yet implemented — sharing for user feedback before building.
Channels are for long-lived release lines (next/beta/rc), not per-PR previews. Add a "when to use this" section and point users at pkg.pr.new for ephemeral PR packages.
…dination Rewrite the dependency section around exact-pin-within-cycle, with cycle membership coming from explicit declarations (bump files, linked/fixed, cascadeTo) rather than automatic propagation. Drop the propagation config knob — matches bumpy's existing "explicit > implicit" stance for stable releases. Add a "Coordinating multi-package prereleases" section with the stranded-prerelease failure mode and the four ways to fix it. Update the changesets comparison table to reflect the new approach.
Re-thinking: semver only resolves a prerelease against a range when major.minor.patch matches exactly. That means every prerelease of an upstream breaks every dependent's range — not occasionally, but always. "Suppressing" cascade would just produce prereleases that consumers can't install together without manual overrides. So the right model is: Phase A/B/C run unchanged on channels, producing a wide cascade by nature of how prerelease semver matches. The user complaints behind changesets #960 are about bump-level policy (peer deps jumping to major), not about whether to cascade — and bumpy's proportional rules already address that. Drop the "Coordinating multi-package prereleases" stranded-failure section (no longer applies). Replace with a "Limiting cascade scope" note pointing at the existing ignore/include/managed controls. Update the changesets comparison row to reflect the actual difference.
After reviewing realistic prerelease workflows: between bumpy channels
(managed long-running release lines) and pkg.pr.new (ephemeral previews,
canaries, branch snapshots), real-world use cases are covered. No need
for an in-bumpy snapshot mechanism that would compete with the
recommended tool.
Expand the rule-of-thumb table to cover per-commit canaries and ad-hoc
branch snapshots. Reinforce the pkg.pr.new pointer in the day-to-day
workflow section so readers see it at the natural moment ("I want to
install this PR before merging").
…les, and tags Major revision of the channels design around one principle: git carries inputs (bump files) and stable outputs only. Prerelease versions exist solely in the registry and git tags. - package.json stays at the last stable version on channel branches - release PR becomes a pure file-move PR; computed versions go in the PR title and merge commit message (advisory; registry wins at publish) - counters derived from registry floor (max published + 1) — immune to abandoned cycles, force-resets, and re-runs - every publish recomputes the full cycle; lockstep republish + exact pins make the @next set coherent by construction - promotion is now just a merge; no suffix stripping, no rc versions ever on main, no version conflicts on main <-> channel syncs - generalized pending rule enables channel graduation (alpha -> beta) - no CHANGELOG.md on channels; GitHub releases + render-on-demand via bumpy status; consolidated stable entry written once at promotion - publish mechanics specified: trigger via channel-dir diff since last tag, tag-on-SHA idempotency, gitHead-based partial-failure resume - document build-time version baking caveat (rewrite before build) - reserve schema room for future stable/maintenance channels
Branch-based prerelease lines where prerelease versions are never committed to git — derived at publish time from bump files (targets), the registry (counters), and git tags/gitHead (idempotency). Core: - channels config block (+ JSON schema): branch, preid, tag, versionPr with automerge; names validated as .bumpy/ subdirectory-safe - bump file location is the only channel state: pending at .bumpy/ root (or other channels' dirs), shipped in .bumpy/<channel>/; the general pending rule gives alpha -> beta graduation for free - release plan gains a prerelease mode: range satisfaction is checked against <target>-<preid>.0, so every dependent joins the cycle at proportional bump levels (prereleases never satisfy stable ranges) - prerelease versions: registry-floor counters (max published + 1), per-package gitHead skip for re-runs/resume, exact-pinned in-cycle deps written transiently into the working tree and restored after Commands: - version: on a channel, only moves pending files into .bumpy/<channel>/ - publish: channel flow computes + rewrites + publishes to the channel dist-tag; stable flow refuses suffixed versions when channels exist - ci release: channel branches get publish-on-move-detection (push range diff) + file-move release PRs with computed versions in the title/commit message; unknown branches are refused - ci plan/check, status, check: channel-aware (status shows the cycle; check skips channel branches and gains --base) - promotion needs no special mode: main consumes channel dirs as pending, bumps stable-to-stable, writes the consolidated changelog - GitHub releases for prerelease versions are marked --prerelease Docs updated to match (banner removed, trigger/notes/check rows aligned); changesets comparison moved to Implemented.
|
The changes in this PR will be included in the next version bump.
|
- add to Features and Documentation sections - add to changesets pain-points comparison - remove from Roadmap (now implemented)
- add channels.next (preid rc, @next dist-tag) to .bumpy/_config.json - add next to the release workflow push triggers - make the release concurrency group per-branch
When a PR targets a prerelease channel branch, the bumpy ci check comment now says so explicitly instead of implying a stable release: - headline: 'This PR targets the <name> prerelease channel — merging ships these as a prerelease to @<tag>, not a stable release' - versions display the derived '-<preid>.?' suffix (counter comes from the registry at publish time) - a note with the dist-tag install hint and how to promote to stable - plan uses the prerelease preid so the wider channel cascade is shown Export formatReleasePlanComment and cover both stable and channel comment shapes with tests.
Split the PR check into two mutually-exclusive jobs: - check-published: fork PRs run published @latest (never executes untrusted code with the pull_request_target write token) - check-local: non-fork PRs build + run this repo's local bumpy, so unreleased behavior (e.g. channel-aware comments) is dogfooded on our own PRs before it ships to @latest Lets PR #104 (internal, targets the next channel) show the real channel-aware check comment now instead of waiting for promotion.
theoephraim
added a commit
that referenced
this pull request
Jun 12, 2026
…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.
fd20b8f to
573780a
Compare
theoephraim
added a commit
that referenced
this pull request
Jun 13, 2026
Promotes the `next` prerelease 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 `@next` dist-tag has shipped `1.14.0-rc.0` and `1.14.0-rc.1` through this cycle. What's in the cycle: - **Prerelease channels** ([#104](#104)) — branch-based prerelease lines; versions derived at publish time, never committed. - **Deterministic channel release PR titles** ([#107](#107)) — wildcard `rc.x` counters in PR titles/bodies/commits so they can't drift from the registry; package count for multi-package cycles. Validated live on [#109](#109). - **Docs** ([#106](#106)) — environment deployment-branch allowances for channel branches with trusted publishing. --------- Co-authored-by: bumpy 🐸 <bumpy.bot@varlock.dev> Co-authored-by: bumpy-bot <276066384+bumpy-bot@users.noreply.github.com>
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.


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:
next/beta/ etc. branch in.bumpy/_config.json. Pushing to it produces-rc.N/-beta.Nversions on the matching dist-tag, using the sameci releaseflow asmain.package.jsonon a channel branch keeps the last stable version, identical tomain. Targets are derived from bump files, counters from the registry (max published -preid.N + 1), idempotency from npm'sgitHeadmetadata. No suffix to strip at promotion, no counter state to corrupt, no version conflicts onmain↔ channel syncs, and abandoned cycles can't cause registry collisions..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.CHANGELOG.mdentry. Zero special promotion code.Why not just match changesets' pre mode?
Changesets' own docs 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 surveys the recurring complaints (#239, #381, #729, #786, #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 (prefersGITHUB_REF_NAMEfor detached CI checkouts),--channeloverride.src/core/prerelease.ts— registry-floor counters, per-package published-from-HEADskip (gitHeadon 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 afinally.src/core/release-plan.ts— newprereleasePreidmode: Phase A checks ranges against<target>-<preid>.0, producing the required wide-but-proportional cascade (channel-only; stable plans are unchanged).versionon a channel only moves files;publishderives + rewrites + publishes to the channel dist-tag (and the stable path refuses suffixed versions when channels are configured);ci releasepublishes when the triggering push moved files into.bumpy/<channel>/and maintains the file-move release PR (withversionPr.automergesupport);statusshows the cycle with registry-derived counters;checkskips channel branches and gains--base; unknown branches makeci releaseerror instead of guessing. GitHub releases for prereleases are marked--prerelease.config-schema.json) updated;preidis optional in the schema to leave room for future stable/maintenance channels.Out of scope (deliberately)
1.xbranches) — future; config shape already accommodates.channel:frontmatter — not planned; channels stay branch-derived.Test plan
workspace:*opt-out and transitivity)tsc --noEmit, oxlint, oxfmt cleanversion(file move only, no version writes) →status(correctrc.0counters) →publish --dry-run(correct versions,--tag next, exact pins) → merge tomain→ stableversion(channel dir consumed, consolidated changelog, no spurious cascade on main)bumpy ci releaserun on anextpush)