You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs: drop auto-cascade in prerelease channels; require explicit coordination
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.
Copy file name to clipboardExpand all lines: docs/prereleases.md
+58-30Lines changed: 58 additions & 30 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -225,28 +225,57 @@ After the hotfix lands on `main`, rebase or merge `main` → `next` to pick it u
225
225
226
226
---
227
227
228
-
## Dependency propagation in prerelease channels
228
+
## Dependency handling in prerelease channels
229
229
230
-
By default, **dependency cascade is suppressed** on prerelease channels.
230
+
Bumpy never automatically cascades a prerelease through your dependency graph. You decide which packages belong in each cycle through explicit declarations — bump files, `linked` / `fixed` groups, or `cascadeTo` rules. Whatever ends up in the cycle is then exact-pinned together.
231
231
232
-
Background: prerelease versions like `1.2.0-rc.0` don't satisfy semver ranges like `^1.1.0`, so naive propagation would force-bump every dependent in your monorepo on every prerelease — see [changesets#960](https://github.com/changesets/changesets/issues/960). Bumpy avoids this by default. Dependent packages keep their stable versions in the prerelease workspace; the cascade applies normally when you promote to stable.
232
+
### The exact-pin rule
233
233
234
-
If you genuinely want prerelease propagation (e.g., you're shipping prereleases of an entire dependency tree together), opt in per-channel:
234
+
Within a prerelease cycle, any inter-cycle dependency is **exact-pinned** at publish time:
> If `@org/plugin@1.1.0-rc.0` is in the same cycle as `@org/core@2.0.0-rc.0`, the published `@org/plugin@1.1.0-rc.0` has `"@org/core": "2.0.0-rc.0"` — not `"^2.0.0-rc.0"`.
237
+
238
+
This guarantees that any package from the cycle, installed via `@next`, works with the other packages it was published against. Channel-internal consistency is built into the artifact, not relied on at install time.
239
+
240
+
Dependencies pointing **outside** the cycle keep their stable ranges. Their existing `@latest` install continues to work; nothing in their published `package.json` points at a prerelease.
241
+
242
+
### `workspace:` protocol resolution
243
+
244
+
`workspace:^`/ `workspace:*` on an in-cycle dep resolves to the exact prerelease version. On an out-of-cycle dep, it resolves normally (the stable range bumpy would produce on `main`).
245
+
246
+
### Why no automatic cascade
247
+
248
+
Changesets force-bumps every dependent whose range gets broken by a prerelease — including dependents that didn't otherwise need to change. This is the source of many of its prerelease pain points ([#960](https://github.com/changesets/changesets/issues/960), [#1228](https://github.com/changesets/changesets/issues/1228), [#1287](https://github.com/changesets/changesets/issues/1287)).
249
+
250
+
Bumpy already takes the "explicit propagation" stance for stable releases (`updateInternalDependencies: "out-of-range"` is the default). Channels apply the same principle: bumpy does what you asked it to do, nothing more. The trade-off — that you can ship a stranded prerelease if you forget to coordinate — is addressed by the next section.
251
+
252
+
---
253
+
254
+
## Coordinating multi-package prereleases
255
+
256
+
If you prerelease an upstream package without bringing its dependents along, those dependents won't be able to consume the prerelease.
257
+
258
+
Concrete failure mode:
259
+
260
+
- `@org/core@1.0.0` and `@org/plugin@1.0.0` (both on `@latest`)
- Cycle ships `@org/core@2.0.0-rc.0` to `@next`. `@org/plugin` stays at `1.0.0`.
264
+
265
+
A tester running `npm install @org/core@next @org/plugin` hits a peer dep mismatch (or npm hoists two copies of core). The prerelease is "stranded" — usable on its own but not in combination with the rest of the ecosystem.
266
+
267
+
To make the prerelease usable, declare the relationship so `@org/plugin` joins the cycle. Pick whichever fits your situation:
| **Multi-package bump file** | Ad-hoc — this specific PR ships both packages together |
272
+
| **`linked` group** in config | Two packages should always share the highest bump level |
273
+
| **`fixed` group** in config | Two packages should always share an exact version |
274
+
| **`cascadeTo: ["@org/plugin"]`** on `@org/core` | Any change to core should always bump plugin |
275
+
276
+
If you genuinely want a stranded prerelease (the package has no in-monorepo dependents that need it), no declaration is needed — bumpy will ship it as written.
248
277
249
-
`fixed`and `linked` groups still bump together as they normally would — group cohesion is preserved across channels.
278
+
> See [docs/version-propagation.md](./version-propagation.md) for the full propagation model and how `linked` / `fixed` / `cascadeTo` interact.
250
279
251
280
---
252
281
@@ -303,7 +332,6 @@ This matches user intuition (the counter resets when the underlying target moves
303
332
"branch": "next", // required — branch that triggers this channel
| State file | `.changeset/pre.json` committed to repo | None — file location in `.bumpy/` is the state |
335
-
| Wrong-branch hazard | Merging while pre mode is active accidentally turns stable releases into prereleases ([#239](https://github.com/changesets/changesets/issues/239)) | Impossible — channel state lives in the branch and the file layout, not in a global mode |
336
-
| Dist-tag control | Locked to mode tag, `--tag` is rejected ([#786](https://github.com/changesets/changesets/issues/786)) | Per-channel `tag` config, independent of suffix |
337
-
| Dependent force-bumping | Always on, can't be disabled ([#960](https://github.com/changesets/changesets/issues/960)) | Suppressed by default, opt-in via `propagation: "stable"` |
338
-
| Counter | Requires committed `package.json` increments ([#381](https://github.com/changesets/changesets/issues/381)) | Derived from current state; resets cleanly when target moves |
339
-
| Exit re-bumps everything | Yes ([#729](https://github.com/changesets/changesets/issues/729)) | No — promotion strips suffixes and consumes pre-shipped bump files into one consolidated entry |
340
-
| First publish during pre mode | Silently goes to `@latest` | Always goes to channel's dist-tag |
341
-
| Stable changelog after prereleases | Lossy — only the `pre exit` step's diff | Lossless — consolidated entry built from every bump file in the cycle |
| State file | `.changeset/pre.json` committed to repo | None — file location in `.bumpy/` is the state |
363
+
| Wrong-branch hazard | Merging while pre mode is active accidentally turns stable releases into prereleases ([#239](https://github.com/changesets/changesets/issues/239)) | Impossible — channel state lives in the branch and the file layout, not in a global mode |
364
+
| Dist-tag control | Locked to mode tag, `--tag` is rejected ([#786](https://github.com/changesets/changesets/issues/786)) | Per-channel `tag` config, independent of suffix |
365
+
| Dependent force-bumping | Always on, can't be disabled ([#960](https://github.com/changesets/changesets/issues/960)) | Never automatic — cycle membership is declared (bump files, `linked`/`fixed`, `cascadeTo`); inter-cycle deps are exact-pinned at publish |
366
+
| Counter | Requires committed `package.json` increments ([#381](https://github.com/changesets/changesets/issues/381)) | Derived from current state; resets cleanly when target moves |
367
+
| Exit re-bumps everything | Yes ([#729](https://github.com/changesets/changesets/issues/729)) | No — promotion strips suffixes and consumes pre-shipped bump files into one consolidated entry |
368
+
| First publish during pre mode | Silently goes to `@latest` | Always goes to channel's dist-tag |
369
+
| Stable changelog after prereleases | Lossy — only the `pre exit` step's diff | Lossless — consolidated entry built from every bump file in the cycle |
0 commit comments