chore: merge main into v3 (mops build --check-wasm / --check-deploy) - #703
Merged
Conversation
Pushing to `v3` ran no checks at all. `ci.yml`, `cli-bundle.yml` and `mops-test.yml` trigger on pushes to `main`/`master` and on `pull_request`, so a merge into the integration branch was validated only by whichever PR happened to be based on it next — and when something breaks that way, the failure surfaces on an unrelated PR and is awkward to attribute to the merge that caused it. Adding `v3` to all three workflows behind the required checks rather than to `ci.yml` alone: `ci-ok`, `ci-ok-cli` and `ci-ok-mops` come from three separate files, so touching one would leave two of the three required checks unrun on a `v3` push. `code-quality.yml` is included for parity — it already runs on `main` pushes and is cheap. This matters more now that admin bypass is disabled on both branches: everything reaches `v3` through a PR, and the post-merge state of the branch should be verified in its own right rather than inferred. No behavior change for PRs — those already ran on `pull_request` regardless of target branch.
When 3.0.0 reaches the release canister, `/tags/latest` flips and every 2.x user running `mops self update` absorbs 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: ``` Current version: 2.20.0 Version 3.0.0 is a new major release with breaking changes: https://github.com/caffeinelabs/mops/releases/tag/cli-v3.0.0 ? Update to 3.0.0? › (y/N) ``` Non-interactively (CI, scripts) it skips the update with a notice and **exits 0**: ``` Skipping the major update. Run mops self update --major to update. ``` Exit 0 is the load-bearing choice: erroring would itself have been the breaking change — a script running `mops self update` succeeds 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/latest` body (an error page, an empty response) was previously handed straight to `npm add -g` as 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 `v3` in a sibling PR, which covers the 3.x→4.x jump permanently. ## Scope Deliberately no `releases.json` diffing or npm consultation — `/tags/latest` plus 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.1` prompts; `3.0.0-beta.1 → 3.0.0` does not). The jest `moduleNameMapper` addition is the standard ESM `.js`→`.ts` mapping; the full suite (25 suites, 200 tests, 73 snapshots) passes with it. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
`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>
Every cold replica start asks `api.github.com` for the newest `icp-cli-network-launcher` release — `npm run replica` wipes `.icp/cache/networks`, so in CI that's every run. Two problems, one root cause: 1. **The request is anonymous and gets 403 rate-limited on shared runners.** Observed failing [#698's test job](https://github.com/caffeinelabs/mops/actions/runs/31384954148/job/93443187571) with `Error: failed to fetch latest network launcher version from GitHub`. 2. **The launcher — the PocketIC under every replica test — was unpinned.** Everything else icp is pinned (icp-cli in `ci.yml`, recipes by sha256), and `AGENTS.md` warns against `icp network update` for exactly this reason; the warning was guarding a pin that didn't exist. `icp.yaml`'s managed network accepts a `version`. Pinned to what `latest` resolves to today (`15.0.0-2026-07-31-04-23`), so nothing moves — this changes drift, not behavior. The pin also eliminates the rate-limit exposure, not just hides it: a pinned version downloads straight from `github.com/…/releases/download/<tag>/…` and never touches the rate-limited API. Proven by pinning a bogus version and reading the URL in the resulting 404: ``` HTTP status client error (404 Not Found) for url (https://github.com/dfinity/icp-cli-network-launcher/releases/download/v99.99.99-bogus/…) ``` That bogus-pin failure is also the proof the field is honored rather than silently ignored. Verified with the pin: cold `npm run replica` + `npm run deploy-local` pass and `main` resolves on the local network. To move launcher versions later: bump the `version` in `icp.yaml` alongside the icp-cli/recipe pins and re-run the local pipeline — `AGENTS.md`'s pinning bullet now says so. The `v3` branch has the same replica script and inherits this on the next main→v3 merge. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
## Summary - add independent `mops build --check-wasm` and `[build].check-wasm = true` controls for fast Walrus complexity analysis, with `--no-check-wasm` as a one-build override - add `mops build --check-deploy` and `[build].check-deploy = true` to install final Wasm artifacts on fresh PocketIC canisters, with `--no-check-deploy` as a one-build override - keep static Walrus analysis and dynamic PocketIC validation separately opt-in so either or both can run - use the shared upstream `@dfinity/pic` 0.23.0 client without adding another runtime dependency or binary download - encode configured Candid init arguments, support per-canister Wasm memory limits, and report PocketIC errors as provided by the client - ask the configured `moc --stable-compatible` whether each generated `.most` is reachable from a temporary empty-actor baseline, report incompatible canisters as `MOPS-CHECK-DEPLOY-SKIPPED` with the compiler diagnostic, and continue checking eligible siblings - collect installation errors across checked canisters before failing the command - update CLI help, `mops.toml` configuration, documentation, changelog, skill guidance, snapshots, and integration coverage ## Test plan - TypeScript checks and ESLint - CLI distribution build and `mops build --help` verification - focused build integration tests for independent static and dynamic controls, configuration precedence, collected sibling failures, IC0505, IC0539, ordinary trap, invalid Candid, and PocketIC startup outcomes - moc compatibility integration coverage for first-entry legacy state, later missing state, and empty inputs expressed through a type alias - Rust Wasm analyzer tests - 31-canister deployment benchmark: 31/31 correctly classified, including 14 IC0505 and 10 IC0539 failures --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The feature was written against the 2.x dual-client world and is ported to v3's single upstream client: - The `client: "versioned" | "dfinity"` selector, `PocketIcResult` union and `assertDfinityClientSupportsPocketIc` are not carried. v3 guarantees what they asserted: the 9.0.0 floor is enforced globally at binary resolution, and an unpinned pocket-ic resolves to the compiled-in default, so `--check-deploy` needs no pin at all. - `createClientOrStopServer` is carried into pocket-ic-client.ts — a server whose client never came up is an orphaned process on any client, with its unit tests. - build.ts drops the pin-required error and the pre-build assert; the moc path stays fallback-free. - The two version-gating tests now assert v3 semantics: unpinned --check-deploy succeeds on the default version; a 4.0.0 pin fails with the migration message at pocket-ic resolution (after the build, so the fixture gained buildable source it never needed on 2.x). - Docs, skill and changelog entries reworded where they claimed a 9.0.0+ pin is required. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
Cursor AI review👍 APPROVE — looks safe to merge
VerdictDecision: APPROVE Generated for commit ca3ff72 |
The merge re-added cli/vessel.ts: #702's rename detection had paired main's vessel.ts with v3's install-from-github.ts, so this second merge saw main's copy as a new file. v3 deleted vessel support in #675 and nothing imports the file — it was dead code duplicating the live GitHub install path, compiled into dist. Verified it is the only file this merge resurrected. docs/docs/09-mops.toml.md's new check-deploy row still carried 2.x's "requires pocket-ic 9.0.0 or newer in [toolchain]" — on v3 no pin is needed (the default version is used) and sub-9 pins are rejected globally. Now worded like the other pages this PR already fixed. Both flagged by the AI review. Also normalizes formatting main's global.d.ts and jest.config.js carried in. 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 ca3ff72 is APPROVE. See the "Cursor AI review" comment for details.
Kamirus
added a commit
that referenced
this pull request
Aug 10, 2026
Brings the 2.21.0 release and [#706](#706) (released 2.x line at the docs root) onto `v3`. ## Conflicts Sixteen files conflicted. Thirteen are squash-merge artifacts — `main` commits already applied to `v3` by #702/#703 re-presented because squash merges leave the originals unreachable — and were resolved to `v3`'s side unchanged. The three real ones: **`cli/CHANGELOG.md`** — `main` rolled `## Next` into `## 2.21.0`; `v3`'s five `## Next` entries are exactly those, now released. Took `main`'s file and reinserted `v3`'s `## 3.0.0 (unreleased)` section between `## Next` and `## 2.21.0`. Also dropped the `mops self update` major-guard bullet from the 3.0.0 section — it shipped in 2.21.0, and no other 2.x-released entry is repeated under 3.0.0. Resulting heading order: `Next` (empty) → `3.0.0 (unreleased)` → `2.21.0` → `2.20.0`. **`docs/docusaurus.config.js`** — values identical on both sides. Took `main`'s comment: `v3`'s said "`main` carries `'current'`", which #706 made false. **`cli/vessel.ts`** — resurrected again by rename detection pairing it with `install-from-github.ts`. Deleted; nothing imports it. Confirmed it was the only re-added file via `git diff --diff-filter=A origin/v3 HEAD`. ## Lockfile `cli/package-lock.json` carries `v3`'s dependency graph with the version field aligned to `package.json`'s `2.21.0`. Edited by hand rather than regenerated: `npm install --package-lock-only` on macOS prunes the cross-platform `@esbuild/*` optional entries and breaks Linux `npm ci`. `cli/package.json` differs from `v3` only in that version field, so `v3`'s graph is already correct. ## Verification - `npm ci` from scratch in `cli/` — clean, 0 vulnerabilities - `npm run check` (`tsc --noEmit`) — passes - No files resurrected by rename detection 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Michael Morandi <michael.morandi@caffeine.ai> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: caffeine-ci-bot[bot] <249119985+caffeine-ci-bot[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Kamirus
added a commit
that referenced
this pull request
Aug 13, 2026
Brings `v3` up to `origin/main`. Three of main's fifteen commits carry anything new: the 2.22.0 release and its `cli-releases` artifacts, and the changelog-rollup fix in `prepare-cli-release.yml`. The other twelve were already ported to `v3` in adapted form — the lock `graph`, hash carry-over and cache resilience via [#717](#717), `--check-deploy` via [#703](#703), the `decompress` removal and launcher pin via [#702](#702), the self-update major guard via [#697](#697), the 2.21.0 release via [#707](#707). So every source conflict resolves to v3's version, and the net diff is ten files. ## Why 28 files conflicted for a 15-commit sync Every previous sync (`24793648`, `9d086b9b`, `eb5c1232`) was squash-merged, which discards the ancestry link. Git's merge base is consequently still `0e91af70` (#550), so each sync re-resolves everything both branches have touched since — not what actually changed since the last sync. **Merging this with a merge commit rather than a squash would stop that.** The next sync would then only have to resolve real divergence. ## Conflicts that needed a decision Each main-only hunk was checked against v3 rather than blanket-resolved: - `sync-local-cache.ts` — main's "restore a package missing from the global cache" fix is already at `sync-local-cache.ts:55`. - `build.test.ts` — main's `--check-deploy` / `--check-wasm` tests moved to `build-check-deploy.test.ts` and `build-check-wasm.test.ts` when [#731](#731) split the file. - `build.ts` — main gained a guard requiring an explicit `[toolchain] pocket-ic` pin plus a dfinity-client compatibility assert. Neither applies here: v3 dropped the legacy client and defaults `pocket-ic` to 14.0.0. - `10-mops.lock.md` — main's two new `graph` paragraphs exist on v3, adapted to a model with no `--lock update`. - `CHANGELOG.md` — main's `2.22.0` section is spliced in above `2.21.0`; v3's `Next` and `3.0.0 (unreleased)` blocks already document the same work and are untouched. - Auto-merge quietly added two obsolete `check-deploy` snapshots to `build.test.ts.snap`, where v3 no longer runs those tests. Reverted. `vessel.ts` stays deleted, and `moc-wrapper`, `dhall-to-json-cli`, `pic-ic`, the dfx fallback and `--lock <mode>` stay gone. ## One behavior-adjacent change `docs/docusaurus.config.js` claimed the docs canister is shared and "whichever branch deployed most recently decides the layout". [#709](#709) stopped `main` deploying docs, so `v3` is now the sole deployer: ```js // This branch is the only one that deploys the docs canister, so // this config decides what docs.mops.one serves. `main` keeps a // matching copy, but there it only shapes local previews. ``` The `release.yml` docs-deploy step is kept on this branch for the same reason — main's replacement comment explaining its absence does not apply here. ## What is unchanged No CLI behavior changes. The version bump to 2.22.0 is not asserted anywhere in the tests, and `cli-releases` artifacts are 2.x release history: `releases.json`, `tags/latest`, and `2.tgz` / `latest.tgz` all agree with `2.22.0.tgz` and its recorded sha256. `npm run check` is clean and the CLI suite passes 494 tests across 59 suites. `npm run lint` fails at the repo root only because `cli-releases/frontend` has no `node_modules` in a fresh worktree, so eslint cannot resolve `eslint-plugin-svelte` — pre-existing and unrelated; `npx eslint cli` is clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Michael Morandi <michael.morandi@caffeine.ai> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: caffeine-ci-bot[bot] <249119985+caffeine-ci-bot[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[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.
Syncs #644 into v3. The feature merges mostly clean; the interesting part is that it was written against 2.x's dual-PocketIC-client world, which v3 deleted — so this is a port, not just a conflict resolution.
What is deliberately not carried
#644routed--check-deploythrough aclient: "dfinity"selector plusassertDfinityClientSupportsPocketIc, because on 2.x the default client path could pick the legacypic-ic(< 9.0.0), which cannot drive deployment checks. On v3 the upstream client is the only client and both halves of that guarantee already exist globally: pins below 9.0.0 are rejected at binary resolution with the migration message, and no pin resolves to the compiled-in default — so--check-deployon v3 needs no pin at all, and requiring one (as #644's error did) would contradict v3's own changelog entry. The selector, thePocketIcResultunion and the assert are gone;pocket-ic-startup.tsis not carried.What is carried from that module:
createClientOrStopServer— a server whose client never came up is an orphaned process on any client — now inpocket-ic-client.tswith its unit tests.Behavior differences from #644 on main, both intentional
--check-deploywith nopocket-icpin: error, "requires `pocket-ic` in `[toolchain]`"--check-deploywith a< 9.0.0pin: rejected before building by the assertThe two version-gating tests were rewritten to assert exactly that, and the
check-deploy-legacyfixture gained buildable source — it never needed any on 2.x because the assert fired before compilation ever ran. Docs (03-mops-build.md), the skill and the changelog entry are reworded where they claimed a 9.0.0+ pin is required.Changelog resolution
Same convention as #702: #644's entries land in v3's
## Next(rolled into the version heading at release), the pin-requirement sentence adjusted; the self-update and moc-wrapper duplicates are dropped (already present on v3).cli/package-lock.jsonregenerated by restoring v3's lock and applying the delta with a real install — the platform-optionals lesson from #702.Full suite on the merged tree: 302/302, 77 snapshots, eslint and prettier clean. That count includes #644's check-deploy integration tests running against real PocketIC on v3's client for the first time.
🤖 Generated with Claude Code