|
| 1 | +# Release Branch Protocols |
| 2 | + |
| 3 | +This document captures the rules that govern **which packages must be released together** |
| 4 | +when a release branch is prepared in `connect-monorepo`. These rules apply on top of the |
| 5 | +mechanical workflow described in [Releasing changes](./contributing.md#releasing-changes). |
| 6 | + |
| 7 | +Most of these rules are enforced automatically by |
| 8 | +[`@metamask/create-release-branch`](https://github.com/MetaMask/create-release-branch) (see |
| 9 | +[Tooling enforcement](#tooling-enforcement)), but understanding them is essential for |
| 10 | +review (see [Reviewing Release PRs](./reviewing-release-prs.md)) and for cases where the |
| 11 | +tool requires a judgement call from the release author. |
| 12 | + |
| 13 | +## Table of contents |
| 14 | + |
| 15 | +- [Why these rules exist](#why-these-rules-exist) |
| 16 | +- [Definitions](#definitions) |
| 17 | +- [Core protocol](#core-protocol) |
| 18 | +- [Canonical scenarios](#canonical-scenarios) |
| 19 | +- [Operational checklist for release authors](#operational-checklist-for-release-authors) |
| 20 | +- [Tooling enforcement](#tooling-enforcement) |
| 21 | +- [Open gaps and future work](#open-gaps-and-future-work) |
| 22 | + |
| 23 | +## Why these rules exist |
| 24 | + |
| 25 | +Several public packages in this monorepo (`@metamask/connect-evm`, |
| 26 | +`@metamask/connect-solana`, ...) declare `@metamask/connect-multichain` as a **peer |
| 27 | +dependency**. From [`packages/connect-evm/package.json`](../packages/connect-evm/package.json) |
| 28 | +and [`packages/connect-solana/package.json`](../packages/connect-solana/package.json): |
| 29 | + |
| 30 | +```json |
| 31 | +"peerDependencies": { |
| 32 | + "@metamask/connect-multichain": "workspace:^" |
| 33 | +} |
| 34 | +``` |
| 35 | + |
| 36 | +At publish time, Yarn rewrites `workspace:^` to `^<connect-multichain-version-at-publish>` |
| 37 | +in the published manifest. So if `connect-evm@1.4.0` is published while |
| 38 | +`connect-multichain` is at `1.0.0`, the published `connect-evm@1.4.0` manifest pins |
| 39 | +`@metamask/connect-multichain: ^1.0.0`. |
| 40 | + |
| 41 | +This has two consequences a release must respect: |
| 42 | + |
| 43 | +1. **Co-installability.** When a consumer installs `@metamask/connect-evm` and |
| 44 | + `@metamask/connect-solana` together (a supported configuration — see the |
| 45 | + [architecture diagram](./architecture.md#package-topology)), `npm` / `yarn` must be |
| 46 | + able to satisfy _both_ packages' peer dependency ranges with a _single_ resolved |
| 47 | + version of `@metamask/connect-multichain`. If one adapter ships with `^1.0.0` and the |
| 48 | + other ships with `^2.0.0`, the install fails (or, worse, silently picks one and warns). |
| 49 | +2. **Minimum-version honesty.** A new `connect-evm` published _immediately after_ a |
| 50 | + `connect-multichain` minor bump captures the new minor as its peer floor (because |
| 51 | + `workspace:^` resolves against the current workspace version). That is the right thing |
| 52 | + to do _if_ the new `connect-evm` actually relies on the new `connect-multichain` |
| 53 | + feature, and a no-op otherwise. |
| 54 | + |
| 55 | +These two constraints drive the [core protocol](#core-protocol). |
| 56 | + |
| 57 | +## Definitions |
| 58 | + |
| 59 | +- **Ecosystem client** — a public workspace package that has |
| 60 | + `@metamask/connect-multichain` as a `peerDependency`. Currently: |
| 61 | + `@metamask/connect-evm`, `@metamask/connect-solana`. |
| 62 | +- **Relies on** — has source changes (since its previous published tag) that depend on |
| 63 | + the new behaviour or API surface of a new `@metamask/connect-multichain` release. |
| 64 | + Bumping just to refresh the peer range without consuming any new behaviour does **not** |
| 65 | + count as "relies on". |
| 66 | +- **Breaking change to a peer-dep'd package** — a `major` bump (per |
| 67 | + [SemVer](https://semver.org/)). This is the only kind of bump that invalidates an |
| 68 | + existing `^X.Y.Z` peer range on downstream packages. |
| 69 | + |
| 70 | +## Core protocol |
| 71 | + |
| 72 | +For each release branch: |
| 73 | + |
| 74 | +1. **An ecosystem client MUST be released alongside `@metamask/connect-multichain` if and |
| 75 | + only if either:** |
| 76 | + - **(a)** it relies on the new `@metamask/connect-multichain` behaviour, or |
| 77 | + - **(b)** the `@metamask/connect-multichain` release is a `major` bump. |
| 78 | + |
| 79 | + Case (a) is enforced by the changelog/review pass — if the adapter has source-level |
| 80 | + changes that depend on the new multichain release, the adapter has its own changelog |
| 81 | + entries and is included by `hasChangesSinceLatestRelease`. |
| 82 | + |
| 83 | + Case (b) is enforced by `create-release-branch` (see |
| 84 | + [Tooling enforcement](#tooling-enforcement)). |
| 85 | + |
| 86 | +2. **An ecosystem client MUST NOT be released solely because its peer dep was bumped |
| 87 | + non-breakingly.** A no-op release that re-tightens the `^1.0.0` floor to `^1.1.0` |
| 88 | + without any consumer-visible change is noise in `npm` and in changelogs. Skip it. |
| 89 | + |
| 90 | +3. **All ecosystem clients that peer-depend on a package being released with a `major` |
| 91 | + bump MUST be released in the same release branch.** Otherwise, the unreleased |
| 92 | + ecosystem client will keep the previous peer range, which by definition no longer |
| 93 | + satisfies the new major, and co-installing the released ecosystem client with the |
| 94 | + unreleased one becomes impossible. |
| 95 | + |
| 96 | +4. **The `intentionally-skip` directive is a last resort.** `create-release-branch` |
| 97 | + accepts `intentionally-skip` as a per-package value in the release spec to bypass a |
| 98 | + "missing peer dependent" error. Use it only when _both_ hold: |
| 99 | + - The skipped package genuinely does not need to be published (e.g. it has been |
| 100 | + deprecated or is unused), **and** |
| 101 | + - You have a follow-up plan to keep it consistent (e.g. retire the package, or |
| 102 | + publish it on a subsequent release). |
| 103 | + |
| 104 | + Skipping for convenience (e.g. "I'll publish it later") creates the co-installability |
| 105 | + failure described above. If in doubt, do not skip. |
| 106 | + |
| 107 | +## Canonical scenarios |
| 108 | + |
| 109 | +The starting state for all three scenarios: |
| 110 | + |
| 111 | +```text |
| 112 | +@metamask/connect-evm@1.0.0 |
| 113 | + peerDependencies: { @metamask/connect-multichain: ^1.0.0 } |
| 114 | +
|
| 115 | +@metamask/connect-solana@1.0.0 |
| 116 | + peerDependencies: { @metamask/connect-multichain: ^1.0.0 } |
| 117 | +
|
| 118 | +@metamask/connect-multichain@1.0.0 |
| 119 | +``` |
| 120 | + |
| 121 | +### Scenario 1 — minor bump of the peer dep, nobody consumes it |
| 122 | + |
| 123 | +`@metamask/connect-multichain@1.1.0` is released. No ecosystem client uses the new |
| 124 | +behaviour. |
| 125 | + |
| 126 | +**Release set:** `@metamask/connect-multichain@1.1.0` only. |
| 127 | + |
| 128 | +Why: |
| 129 | + |
| 130 | +- `^1.0.0` already satisfies `1.1.0`, so the existing published `connect-evm@1.0.0` and |
| 131 | + `connect-solana@1.0.0` remain co-installable with `connect-multichain@1.1.0`. |
| 132 | +- Neither adapter relies on the new behaviour, so rule (a) does not apply. |
| 133 | +- Per rule 2, do **not** ship a no-op release of either adapter. |
| 134 | + |
| 135 | +### Scenario 2 — minor bump of the peer dep, one adapter consumes it |
| 136 | + |
| 137 | +`@metamask/connect-multichain@1.1.0` is released. `@metamask/connect-evm` is updated to |
| 138 | +use the new behaviour. |
| 139 | + |
| 140 | +**Release set:** `@metamask/connect-multichain@1.1.0` **and** |
| 141 | +`@metamask/connect-evm@<new>`. |
| 142 | + |
| 143 | +Why: |
| 144 | + |
| 145 | +- `connect-evm` "relies on" the change (rule 1a), so it must ship. Because the published |
| 146 | + `connect-evm` resolves `workspace:^` against the new multichain version, the new |
| 147 | + `connect-evm` will ship with `peerDependencies.connect-multichain: ^1.1.0` — i.e. it |
| 148 | + correctly advertises the new minimum it needs. |
| 149 | +- `connect-solana` is unchanged and `^1.0.0` still satisfies `1.1.0`. Co-installing |
| 150 | + `connect-evm@<new>` (peer `^1.1.0`) with the still-published |
| 151 | + `connect-solana@1.0.0` (peer `^1.0.0`) resolves cleanly to `connect-multichain@1.1.0`. |
| 152 | + Per rule 2, do **not** release a no-op `connect-solana`. |
| 153 | + |
| 154 | +### Scenario 3 — major (breaking) bump of the peer dep, one adapter consumes it |
| 155 | + |
| 156 | +`@metamask/connect-multichain@2.0.0` is released. `@metamask/connect-evm` is updated to |
| 157 | +adopt the breaking change. |
| 158 | + |
| 159 | +**Release set:** `@metamask/connect-multichain@2.0.0`, `@metamask/connect-evm@<new>`, |
| 160 | +**and** `@metamask/connect-solana@<new>`. |
| 161 | + |
| 162 | +Why: |
| 163 | + |
| 164 | +- `connect-evm` is required by rule 1a (relies on the change). The new `connect-evm` |
| 165 | + publishes with `peerDependencies.connect-multichain: ^2.0.0`. |
| 166 | +- `connect-solana` is required by **rule 3 / rule 1b**: even though it has no source |
| 167 | + changes, its currently-published peer range `^1.0.0` is incompatible with `2.0.0`. A |
| 168 | + consumer using both adapters together would otherwise get a peer-dependency conflict. |
| 169 | + Releasing a `connect-solana` whose only diff is the regenerated `^2.0.0` peer range |
| 170 | + restores co-installability. |
| 171 | + |
| 172 | +This is the case `create-release-branch` raises as a hard error if you forget; see |
| 173 | +[Tooling enforcement](#tooling-enforcement). |
| 174 | + |
| 175 | +## Operational checklist for release authors |
| 176 | + |
| 177 | +When you run `yarn prepare-release`, work through this checklist before clicking |
| 178 | +"Confirm": |
| 179 | + |
| 180 | +1. **Identify the set of public packages being released.** Filter to the ones with a |
| 181 | + version bump in the release spec. |
| 182 | +2. **For each package being released that other workspace packages list as a |
| 183 | + `peerDependency`:** |
| 184 | + - Is the bump a `major`? If yes, **every** workspace package that lists it as a peer |
| 185 | + dependency must also be in the release set. (`create-release-branch` enforces this; |
| 186 | + do not bypass with `intentionally-skip` casually — see rule 4.) |
| 187 | + - Is the bump `minor` or `patch`? Then ecosystem clients are included **only if** they |
| 188 | + have source changes that rely on the new behaviour. Adapters with no source-level |
| 189 | + dependence stay on their current published version. |
| 190 | +3. **For each package being released that lists a workspace package as a `dependency` or |
| 191 | + `peerDependency`:** the tool prompts you if the dependency itself has unreleased |
| 192 | + changes. Decide whether the depending package actually needs the new dependency |
| 193 | + version, and include / skip accordingly. |
| 194 | +4. **Sanity check co-installability.** After the tool finishes, the released ecosystem |
| 195 | + clients should all advertise compatible peer ranges. With `workspace:^`, this is |
| 196 | + automatic — but verify by inspecting the diffs to `packages/*/package.json` in the |
| 197 | + release branch. |
| 198 | + |
| 199 | +## Tooling enforcement |
| 200 | + |
| 201 | +`yarn prepare-release` (= `yarn create-release-branch -i` + |
| 202 | +`yarn bump-playground-versions`) enforces most of the above through |
| 203 | +`@metamask/create-release-branch` v4.1.3. Concretely: |
| 204 | + |
| 205 | +- **Auto-population.** Packages with source changes since their last release |
| 206 | + (`hasChangesSinceLatestRelease`) are pre-listed in the release spec. The UI also adds |
| 207 | + peer dependents of any package the user marks as a `major` bump, so the user can pick |
| 208 | + a version for them directly. (File-based flow does not — see |
| 209 | + [Open gaps](#open-gaps-and-future-work).) |
| 210 | +- **Major-bump peer-dependent enforcement.** Implemented by |
| 211 | + `findMissingUnreleasedDependentsForBreakingChanges` (renamed to |
| 212 | + `findCandidateDependentsOfTypeForMajorBump` on upstream `main`). If a package is |
| 213 | + bumped `major` (either via the `major` directive or an explicit `X+1.0.0` version) and |
| 214 | + any workspace package that peer-depends on it is missing from the release spec, the |
| 215 | + tool refuses to proceed and prints the missing dependents. |
| 216 | +- **Missing-dependency warnings.** Implemented by `findMissingUnreleasedDependencies`. |
| 217 | + If a package is being released and one of its workspace `dependencies` or |
| 218 | + `peerDependencies` has unreleased changes but is not in the release spec, the tool |
| 219 | + raises an error. This is the safeguard for rule 1a. |
| 220 | +- **Peer-range regeneration.** Yarn's `workspace:^` protocol is what makes |
| 221 | + `peerDependencies.connect-multichain` track the freshly-bumped multichain version at |
| 222 | + publish time. The `yarn.config.cjs` constraints (see |
| 223 | + `expectUpToDateWorkspacePeerDependencies`) keep the source manifests using |
| 224 | + `workspace:^` instead of pinned ranges. Combined, these guarantee that any newly |
| 225 | + published ecosystem client correctly captures the current multichain version as its |
| 226 | + peer floor. |
| 227 | +- **Non-major bumps do not require dependents.** This is intentional — see Scenarios 1 |
| 228 | + and 2. A `minor` / `patch` bump of `connect-multichain` does **not** force |
| 229 | + `connect-evm` or `connect-solana` into the release spec. |
| 230 | + |
| 231 | +The only escape hatch is the per-package `intentionally-skip` directive in the release |
| 232 | +spec. See [rule 4](#core-protocol) for when it is appropriate. |
| 233 | + |
| 234 | +## Open gaps and future work |
| 235 | + |
| 236 | +These are known limitations of the current tooling that the release author has to cover |
| 237 | +manually. Some are good candidates for upstream contributions to |
| 238 | +`@metamask/create-release-branch`. |
| 239 | + |
| 240 | +1. **File-based template does not include "required" peer dependents until you bump |
| 241 | + manually.** When you run `yarn create-release-branch` without `-i` (so the YAML |
| 242 | + editor flow rather than the UI), the template only lists packages with source |
| 243 | + changes. If `connect-multichain` has source changes but `connect-evm` / |
| 244 | + `connect-solana` do not, you have to add them to the YAML _and_ bump |
| 245 | + `connect-multichain` to `major` before the validator can tell you they were required. |
| 246 | + The interactive UI already does the right thing here (`requiredDependents` in |
| 247 | + `dist/ui.js`), so the fix would be to mirror that behaviour in |
| 248 | + `generateReleaseSpecificationTemplateForMonorepo` once a `major` selection is known. |
| 249 | + Practical mitigation: prefer `yarn prepare-release` (the interactive flow). |
| 250 | +2. **No automatic enforcement of rule 1a.** "`connect-evm` relies on the change" is a |
| 251 | + judgement call. The tool can detect that `connect-multichain` is in the release spec |
| 252 | + and that `connect-evm` has source changes (in which case |
| 253 | + `findMissingUnreleasedDependencies` raises if you forgot to include |
| 254 | + `connect-multichain`), but it cannot tell whether a `connect-evm` _without_ source |
| 255 | + changes silently relies on a new multichain feature. This must be caught in code |
| 256 | + review of the source diff before opening the release PR. |
| 257 | +3. **No automatic enforcement of "no no-op releases".** The tool does not refuse a |
| 258 | + release that has no consumer-visible changes. If a `connect-solana` ends up in the |
| 259 | + release set whose only diff is a manifest-level peer range refresh that isn't |
| 260 | + strictly required (i.e. a minor bump scenario where the old `^X.0.0` still satisfies |
| 261 | + the new minor), the validator does not flag it. Review-time vigilance is the current |
| 262 | + mitigation, captured in [reviewing-release-prs.md](./reviewing-release-prs.md). |
| 263 | +4. **`intentionally-skip` has no policy enforcement.** A user can suppress any of the |
| 264 | + above errors by adding `intentionally-skip` per package. There is currently no |
| 265 | + automated check that flags a release spec containing `intentionally-skip` for review. |
| 266 | + Until there is, treat any `intentionally-skip` entry in a release PR as something |
| 267 | + that must be explicitly justified in the PR description. |
0 commit comments