feat(cli): confirm major-version jumps in mops self update - #696
Merged
Conversation
mops self update installed whatever /tags/latest served, so the day a new major is released, every user running it absorbs breaking changes with no signal beyond the version number. A major jump now prints the release-notes link and asks; in non-interactive environments it refuses and names the fix verbatim: mops self update --major. Same-major updates are unchanged. An unparseable /tags/latest (an error page, an empty body) is now an error instead of being passed to npm as an install spec. The decision table lives in helpers/self-update-kind.ts so it can be unit-tested without dragging in the network fetch and global npm install around it; the jest moduleNameMapper addition is the standard recipe for resolving ESM .js source imports back to .ts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
Cursor AI review👍 APPROVE — looks safe to merge
VerdictDecision: APPROVE Generated for commit 917c11f |
automation-sa-sre
previously approved these changes
Aug 10, 2026
automation-sa-sre
left a comment
There was a problem hiding this comment.
Automated approval: the AI review verdict for 4b0d777 is APPROVE. See the "Cursor AI review" comment for details.
Exiting 1 would itself have been the breaking change: a script running mops self update succeeds today and would turn permanently red the day a new major ships. It now prints the release-notes link, says it is skipping, names mops self update --major, and exits 0 — the script keeps working and stays on its major. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
automation-sa-sre
approved these changes
Aug 10, 2026
automation-sa-sre
left a comment
There was a problem hiding this comment.
Automated approval: the AI review verdict for 917c11f is APPROVE. See the "Cursor AI review" comment for details.
Kamirus
added a commit
that referenced
this pull request
Aug 10, 2026
Same guard as [#696](#696), on the v3 line: a major jump in `mops self update` prints the release-notes link and asks for confirmation; non-interactive environments skip the update with a notice and exit 0 (erroring would break scripted updates the day a major ships), naming `mops self update --major`; an unparseable `/tags/latest` body is an error instead of an npm install spec. Same-major updates are unchanged. #696 is what partially protects the 2.x→3.0 jump (client-side check, so only users who update into it get it). This one covers 3.x→4.x permanently. Decision table in `helpers/self-update-kind.ts` with unit tests for the prerelease edges — `2.20.0 → 3.0.0-beta.1` prompts, `3.0.0-beta.1 → 3.0.0` does not, which matters while previews ship from this branch. Full v3 suite passes (26 suites, 265 tests, 75 snapshots). 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Kamirus
added a commit
that referenced
this pull request
Aug 10, 2026
`npm audit` on the CLI reports two criticals, both against `decompress`, both unfixed upstream ([GHSA-mp2f-45pm-3cg9](GHSA-mp2f-45pm-3cg9), [GHSA-h39j-r5qq-r9mm](GHSA-h39j-r5qq-r9mm)): a malicious archive can write files outside the extraction directory, via `..` entries or symlinks. The one remaining call site is `downloadFromGithub`, which extracts zips from **arbitrary user-specified repos** (`repo = "..."` deps) — exactly that threat model. The toolchain extractor moved off `decompress` in 2.20.0 (#667); this finishes the job and removes the dependency. Extraction now uses [`fflate`](https://www.npmjs.com/package/fflate) (zero-dependency, actively maintained) with explicit containment in `helpers/extract-github-zip.ts`: - an entry with a `..` segment or an absolute name fails the **whole** extraction, before anything is written; - symlink entries are written as plain files holding the link target, so a link pointing outside the install directory cannot be followed later — this is the one behavior change, and it affects only repos that ship symlinks; - the `<repo>-<ref>/` root GitHub wraps archives in is stripped, as `decompress`'s `strip: 1` did. Unit tests build malicious zips with `fflate` itself (zip-slip via `..`, absolute names) and assert refusal with nothing written, plus the happy-path strip. Verified end to end by installing a real GitHub dep (`dfinity/motoko-base#moc-0.14.14`) through the CLI: files land at the same paths as before and the lockfile integrity check passes. Full suite: 197/197, `npm audit` clean. Ships in 2.x deliberately — it's non-breaking and the exposure exists today ([TODO.md](TODO.md) tracked it as "should not wait for v3"). The v3 branch has the same call site in `cli/commands/install/install-from-github.ts`; it inherits this on the next main→v3 merge (the file moved, but the extraction code is identical, so if the merge conflicts it resolves to this version). The jest `moduleNameMapper` addition is the standard ESM `.js`→`.ts` mapping, needed to unit-test the helper; [#696](#696) adds the identical lines. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Kamirus
added a commit
that referenced
this pull request
Aug 10, 2026
…ncher pin) (#702) Brings v3 level with main. Three changes flow in, one needing a by-hand carry: - **[#699](#699) — decompress removal.** The call site moved on v3 (`cli/vessel.ts` → `cli/commands/install/install-from-github.ts`), so the extractor swap was carried by hand: main's `extractGithubZip` with v3's import depths. Verified on v3 by installing a real GitHub dep (`dfinity/motoko-base#moc-0.14.14`) — extracted at the right paths, lock integrity passes. `npm audit` clean on the merged tree. - **[#696](#696) — self-update guard.** Already on v3 via [#697](#697); the merge resolves to one `prompts` import and otherwise identical code. - **[#700](#700) — network-launcher pin.** `icp.yaml` merged clean; AGENTS.md's icp-cli bullet takes main's reworded version on top of v3's dfx bullets. **Changelog resolution**: v3's `## Next` gains the decompress entry (the release workflow rolls `Next` into the version heading at release time, so it lands in 3.0.0 automatically) and keeps the 2.x-only moc-wrapper note; main's self-update entry is dropped from `Next` because #697 already placed it in the `3.0.0 (unreleased)` section. The 3.0.0 section itself is untouched, so this does not conflict with [#701](#701). `cli/package-lock.json` regenerated from the merged `package.json` rather than resolved textually — the ~500-line shrink is `decompress`'s transitive tree leaving. Full merged suite: 269/269, 75 snapshots. (Two initial failures were a stale local `dist/` predating the merge, green after rebuild — same artifact-staleness mode as in #699's development, not a code issue.) 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Kamirus
added a commit
that referenced
this pull request
Aug 14, 2026
Readers of the 2.x docs at the docs.mops.one site root never saw the features shipped in CLI 2.21.0–2.23.0: `grep -rn "check-deploy" docs/versioned_docs/version-2.x/` had zero hits even though `mops build --check-deploy` shipped in 2.21.0. The versioned tree was treated as a frozen snapshot while every feature doc landed only in `docs/docs/` — per the amended "Keep docs in sync" rule, features shipping in a 2.x release belong in both trees. This replays the `docs/docs/` diffs of each 2.21.0–2.23.0 feature into `docs/versioned_docs/version-2.x/`, wording identical: - [#696](#696) — `mops self update` major-version confirmation and `--major` - [#644](#644) — `mops build --check-wasm` / `--check-deploy`, `[build].check-wasm` / `check-deploy`, and `wasmMemoryLimit` in the config reference - [#713](#713) / [#714](#714) — `mops.lock` `graph` section and hash carry-over - [#624](#624) — check/check-stable/migrate diagnostics on moc 1.12.0+ - [#754](#754) — `pocket-ic 15.0.0` recommendation (6 pages) After the replay, `diff -r docs/docs docs/versioned_docs/version-2.x` is empty, so the sync state is trivially checkable. `docusaurus build` passes (links and anchors resolve in both trees). ## What is unchanged - `MOPS_POCKET_IC_URL` on the environment-variables page — already mirrored by [#761](#761). - 2.21.0–2.23.0 changelog entries with no `docs/docs/` counterpart (the unzipper security fix, atomic lock writes, the ENOENT cache-state fixes, the executable-bit and moc-wrapper fixes) — nothing to port; if any deserve docs, that's a change to make in `docs/docs/` first. ## Follow-up worth flagging The docs site deploys from the `v3` branch only, so this has no effect on the live site until it is synced there. `v3`'s `docs/versioned_docs/version-2.x/` is currently identical to pre-backfill `main`, so the sync is exactly this patch — follow-up PR to `v3` incoming. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5 <noreply@anthropic.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.
When 3.0.0 reaches the release canister,
/tags/latestflips and every 2.x user runningmops self updateabsorbs a major with breaking changes — the only signal is the version number scrolling by. This is the last 2.x window to change that: the guard has to be in the installed CLI before the flip.A major jump now stops and asks:
Non-interactively (CI, scripts) it skips the update with a notice and exits 0:
Exit 0 is the load-bearing choice: erroring would itself have been the breaking change — a script running
mops self updatesucceeds today and would turn permanently red the day 3.0.0 ships. With the skip, the script keeps working and simply stays on its major. Same-major updates (minor/patch) never prompt and are byte-identical to today.Also fixed in passing: an unparseable
/tags/latestbody (an error page, an empty response) was previously handed straight tonpm add -gas an install spec; it is now an error naming the URL.Coverage is honest-but-partial
Only users who self-update to a release carrying this guard get it — someone sitting on 2.20.0 still jumps straight to 3.0.0. That is unavoidable (the check runs client-side), which is why it should ship in a 2.x release soon rather than ride along with something else. The GA release notes remain the safety net for everyone else.
The same guard lands on
v3in a sibling PR, which covers the 3.x→4.x jump permanently.Scope
Deliberately no
releases.jsondiffing or npm consultation —/tags/latestplus a major comparison is the whole check. The decision table is a pure helper (helpers/self-update-kind.ts) with unit tests covering the prerelease edges (2.20.0 → 3.0.0-beta.1prompts;3.0.0-beta.1 → 3.0.0does not). The jestmoduleNameMapperaddition is the standard ESM.js→.tsmapping; the full suite (25 suites, 200 tests, 73 snapshots) passes with it.🤖 Generated with Claude Code