Skip to content

Commit 2d0cde0

Browse files
committed
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.
1 parent 119fb7b commit 2d0cde0

1 file changed

Lines changed: 58 additions & 30 deletions

File tree

docs/prereleases.md

Lines changed: 58 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -225,28 +225,57 @@ After the hotfix lands on `main`, rebase or merge `main` → `next` to pick it u
225225

226226
---
227227

228-
## Dependency propagation in prerelease channels
228+
## Dependency handling in prerelease channels
229229

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.
231231

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
233233

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:
235235

236-
```jsonc
237-
{
238-
"channels": {
239-
"next": {
240-
"branch": "next",
241-
"preid": "rc",
242-
"tag": "next",
243-
"propagation": "stable", // "suppress" (default) | "stable"
244-
},
245-
},
246-
}
247-
```
236+
> 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`)
261+
- `@org/plugin`'s package.json: `"@org/core": "^1.0.0"`
262+
- You author one bump file: `@org/core` → major
263+
- 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:
268+
269+
| Declaration | When to use |
270+
| ----------------------------------------------- | ------------------------------------------------------- |
271+
| **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.
248277

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.
250279

251280
---
252281

@@ -303,7 +332,6 @@ This matches user intuition (the counter resets when the underlying target moves
303332
"branch": "next", // required — branch that triggers this channel
304333
"preid": "rc", // version suffix, e.g. -rc.0
305334
"tag": "next", // npm dist-tag for publish
306-
"propagation": "suppress", // optional: "suppress" (default) | "stable"
307335
"versionPr": {
308336
// optional — override the channel's version PR
309337
"title": "🐸 Versioned prerelease (next)",
@@ -327,18 +355,18 @@ The directory used to hold shipped bump files matches the channel name: `.bumpy/
327355

328356
## Comparison with changesets pre mode
329357

330-
| | changesets pre mode | bumpy channels |
331-
| ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
332-
| Entering | `changeset pre enter beta` writes `.changeset/pre.json` | Push to the channel branch |
333-
| Exiting | `changeset pre exit` + `version` + `publish` + delete `pre.json` | Merge channel branch → main; bumpy strips suffix and consolidates |
334-
| 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 |
358+
| | changesets pre mode | bumpy channels |
359+
| ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
360+
| Entering | `changeset pre enter beta` writes `.changeset/pre.json` | Push to the channel branch |
361+
| Exiting | `changeset pre exit` + `version` + `publish` + delete `pre.json` | Merge channel branch → main; bumpy strips suffix and consolidates |
362+
| 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 |
342370

343371
---
344372

0 commit comments

Comments
 (0)