Skip to content

CLI v3.0.0

Choose a tag to compare

@github-actions github-actions released this 20 Aug 08:00
· 14 commits to main since this release
89154ce

Migrating from 2.x

Everything that requires action, in one list. Details in the themed sections below.

  • Pin a compiler: mops toolchain use moc <version>, commit mops.toml. Every command that compiles now requires the pin — there is no dfx fallback.
  • If you deploy with dfx: delete the export DFX_MOC_PATH=moc-wrapper line from your shell config (dfx build fails outright while it points at the removed binary), then read dfx support is removed — your dfx builds no longer use the pinned compiler.
  • Drop --replica dfx / --replica pocket-ic from mops test / mops bench — the flag is gone, PocketIC is always used. Re-record benchmark baselines with mops bench --save.
  • Pin pocket-ic if you run replica tests, benches, or --check-deploy: mops toolchain use pocket-ic 15.0.0. There is no silent default.
  • [toolchain] pocket-ic below 9.0.0: run mops toolchain use pocket-ic 15.0.0.
  • If you use [optimize]: pin Binaryen with mops toolchain use wasm-opt <version> and commit it. Builds no longer pin one for you, and a wasm-opt failure now fails the build.
  • check-stable baselines: point [canisters.<name>.check-stable].path at a .most file — a .mo source is rejected. mops build writes one per canister.
  • CI pipelines: replace --lock check and the CI env-var behavior with an explicit --locked flag; it is accepted by mops install and every command that installs implicitly.
  • Replace mops set-network <net> with the MOPS_NETWORK environment variable.
  • mops watch: drop -g / -d (removed); no flags now means error + warning + format, --test is opt-in.
  • Scripts parsing mops info <pkg> --versions: take the first line for the latest version, not the last.
  • Node.js >= 20 is required.
  • Commit mops.lock — libraries too; remove it from .gitignore if the old advice put it there.
  • Publishing: delete the dfx = "..." line from [package] if you still have one.
  • Vessel users: mops init no longer migrates vessel.dhall — copy dependencies into mops.toml by hand.
  • CI gates on mops outdated: it now exits 1 when updates are available (it always exited 0 before), and 2 when the check itself fails.
  • Programmatic users of the ic-mops package: downloadFile and downloadPackageFiles return Uint8Array instead of Array<number>.

dfx support is removed

  • The dfx and dfx-pocket-ic replicas are gone, and with them the --replica flag on mops test and mops bench (deprecated since 2.14). PocketIC is always used.
  • The dfx-bundled moc fallback is gone. mops build, check, check-stable, test, bench, docs, generate, sync and mops watch resolve the compiler only from [toolchain] moc and error naming the fix when it is unset. mops toolchain bin --fallback is removed (the flag, not the command).
  • mops toolchain init and mops toolchain reset are gone, along with the moc-wrapper binary. Their job was exporting DFX_MOC_PATH=moc-wrapper so dfx build compiled with the pinned moc. If you deploy with dfx, this is the entry to read: mops sources still works as a dfx.json packtool, so dfx keeps resolving your mops dependencies — but dfx build now uses its own bundled compiler while mops check / build / test use your pinned one, and a program that passes mops check can build differently, or not at all, under dfx. In GitHub Actions the same applies silently: mops install / mops sources no longer write DFX_MOC_PATH into $GITHUB_ENV, so such a workflow stays green while compiling with a different moc. The supported path is icp, whose Motoko recipe builds by invoking mops build, so the pin propagates. If you stay on dfx, set DFX_MOC_PATH yourself and keep it in step with [toolchain] moc.
  • mops watch --deploy and mops watch --generate are removed, not replaced: mops is not a deployment tool, and dfx generate emits JS/TS bindings mops has never produced. Flag bundles fail loudly — mops watch -tgd errors with unknown option '-gd'; use mops watch -t. To deploy or regenerate declarations on change, run icp deploy / icp-bindgen (or the dfx equivalents) in a second terminal.
  • mops init no longer contacts the registry for a dfx-versioned "default package set" (a fresh mops.toml has no [dependencies]mops add core yourself) and no longer writes defaults.build.packtool into a dfx.json it finds. It touches nothing outside your project's own files.
  • mops bench no longer reads profile from dfx.json; benchmark canisters are always compiled --release. Projects with "profile": "Debug" were silently benchmarking debug builds.
  • Benchmark baselines drift: PocketIC and the dfx replica report different instruction and heap counts, so the first mops bench --compare after upgrading shows a large diff wherever a dfx replica was implicitly in use. Change of measuring instrument, not a regression — re-record with mops bench --save.
  • The dfx field in [package] is rejected at publish with an error naming the field (documentation-deprecated since 2.7, but no runtime warning ever shipped). Delete the line.
  • mops sources is unchanged, byte-for-byte, including its stdout. It prints --package flags and has no opinion about who consumes them.

PocketIC and toolchain

  • Breaking: replica tests, mops bench, --check-deploy, and mops toolchain bin pocket-ic require an explicit [toolchain] pocket-ic pin. An unpinned project errors naming mops toolchain use pocket-ic 15.0.0 — that version is a hint, not a runtime fallback, so it can move without changing anyone's replica. Attaching to an already-running server via MOPS_POCKET_IC_URL (added in 2.24.0) still works without a pin.
  • Breaking: the legacy pic-ic PocketIC client is gone, and with it [toolchain] pocket-ic pins below 9.0.0 (deprecated in 2.20). Migration: mops toolchain use pocket-ic 15.0.0. There is no upper bound — mops keeps no list of blessed pocket-ic versions, same as for moc, wasmtime and lintoko.
  • @icp-sdk/core upgraded from 4.0.2 to 5.4.0 (#652). Only affects MOPS_NETWORK=local / MOPS_REGISTRY_HOST: update calls now use the IC HTTP API v3 endpoint, so the replica you point mops at must serve it — icp and recent dfx do. Nothing changes for the default ic network or staging. dist/vendor/pic.mjs no longer inlines a second copy and shrinks from 1.15 MB to 600 KB.
  • Removed legacy mocv detection: mops docs no longer resolves mo-doc from a mocv-managed DFX_MOC_PATH. Use mops toolchain use moc <version>.
  • Breaking: [optimize] requires a [toolchain] wasm-opt pin. Build commands now fail before compiling and name the fix (mops toolchain use wasm-opt <version>). Previously a project with [optimize] and no pin had its mops.toml rewritten by the next mops build or mops bench, with the version chosen by a "latest release" lookup — so the same commit built different artifacts either side of a Binaryen release. This closes the last runtime "latest" lookup on the build path.
  • Breaking: a wasm-opt failure fails the build instead of warning and keeping the unoptimized module, which left nothing downstream — anything that hashes, certifies or deploys the artifact — able to tell. --no-optimize still skips the pass deliberately, and --verbose still prints full wasm-opt output.

Checks and builds

  • Breaking: the check-stable baseline must be a .most file. [canisters.<name>.check-stable].path and the mops check-stable <baseline> argument no longer accept a .mo source, in mops check and mops check-stable alike; a .mo path is rejected up front. A .mo baseline described whatever that source said at the time of the run rather than what the canister actually holds, so it drifted silently and the check passed against the wrong state. Migration: point path at a .mostmops build already writes one per canister, and how it gets committed depends on who runs the deploy; see mops deployed.
  • On moc 1.12.0+, mops check and mops check-stable check upgrade compatibility faster and report it better for canisters with [migrations]: compatibility errors now point at your source (src/main.mo:3.1-11.2) instead of (unknown location), and a field the initial actor requires that no migration produces now fails as an M0267 error rather than only warning (M0254) — a forgotten migration that previously slipped through as a warning will now fail the check. Older moc pins and canisters without [migrations] are unaffected.

Lockfile and integrity

  • Breaking: --locked replaces --lock <check|update|ignore>, which is removed from mops add, remove, install, sync and update (#516). Two modes, one flag: plain commands are the dev flow, --locked is the CI flow.
    • --lock check--locked. Strictly stronger: it also refuses to write the lockfile, so a CI run can never mutate it. Fails when mops.lock is missing, unparseable, not the current format version, does not pin what mops.toml declares, or records a file hash the registry disagrees with. Accepted by mops install and every implicitly-installing command (build, check, check-candid, check-stable, test, bench, generate candid), so a pipeline can run mops test --locked with no install step. mops sources deliberately has no --locked (machine-parsed mid-build) — put mops install --locked earlier in the pipeline.
    • --lock update → plain mops install, now self-healing: a missing, unparseable, legacy-format or mops.toml-inconsistent lockfile is regenerated instead of erroring, as is one carrying absolute local path entries from a pre-2.19.2 CLI.
    • --lock ignore → no successor; the lockfile is always maintained (cargo model).
    • Note when moving off --lock check: --locked requires the current format (v3), while --lock check accepted v1/v2. A committed v1/v2 lock fails --locked with a message saying to run mops install once and commit the upgrade.
    • 3.0.0-beta.4 and 3.0.0-beta.5 accepted --lock <mode> as a hidden, value-ignoring shim so v2 call sites would keep parsing during the rollout. It is gone; --lock is an unknown option again.
  • Breaking: the CI environment variable no longer switches mops install to check mode (deprecated in 2.18). Pass --locked explicitly. (#516)
  • Breaking: integrity is verified at download time instead of by re-hashing .mops/ on every install (#517). Files are hashed as they arrive and compared against the registry before the package is committed to the cache, so a corrupted download never reaches your project.
    • Guarantee change: editing a file under .mops/ no longer fails your next install/build/test — installs are not a tamper gate for files already on disk. If a pipeline relied on that, run mops verify, which re-hashes every file the lockfile records and checks the lock against mops.toml and the registry. It also audits packages cached by an earlier CLI, which were never download-verified; mops cache clean forces a verified re-download. Neither --locked nor mops verify re-walks the graph, so a transitive change reached through a local path dependency is not detected.
    • The removed re-hash was paid on every install, proportional to the whole tree (~136 ms for an 842-file tree on a warm SSD, worse on cold caches or networked filesystems).
  • Breaking: mops build, check, check-candid, check-stable, test, bench and generate candid no longer silently ignore mops.lock when installing implicitly — they follow the same lock flow as mops install, and a download that fails its integrity check aborts them with exit code 1 (previously they carried on against a partially-populated .mops/).
  • mops install also self-heals what it previously ignored: a structurally-wrong lock (missing/non-object deps or hashes) is regenerated instead of throwing a TypeError; a deps entry that disagrees with mops.toml (previously installed as-is — the wrong version, silently) and a hashes section inconsistent with deps are both detected offline and repaired.
  • Known limitation, by design: hand-edited file hash values in mops.lock are not repaired by mops install — detecting them costs a ~1.2 s registry call that would outweigh the re-hash this release removes. They are consumed only by --locked and mops verify (never by the build), and both report the mismatch with the recovery that works: restore mops.lock from version control, or delete and reinstall.
  • Breaking (guidance): commit mops.lock, for libraries as well as applications. A library's lock has no effect on consumers (they resolve their own graph) and makes the library's own CI reproducible. The old gitignore advice is gone from the mops.lock created. message and the docs.
  • Fixes for incomplete global-cache state: a cache entry counts as cached only if it is complete (empty leftover directories from interrupted runs are deleted and re-downloaded instead of being treated as hits), packages missing from the global cache are re-downloaded when syncing .mops, and the advisory moc/lintoko requirements check falls back to the global cache instead of crashing on a manifest missing from .mops/.
  • mops.lock is now written with all keys sorted — dependencies, packages, per-file hashes, graph entries and GitHub entries — so unrelated installs no longer produce diff churn or spurious merge conflicts. The lockfile format is unchanged: an existing lockfile with unsorted keys stays valid, still passes mops install --locked, and is reordered only the next time something legitimately updates it.
  • mops.lock is now a trust anchor on the install path. When the lockfile already covers a package being downloaded, its bytes are verified against the hashes recorded there — a local, committed record — so no registry call is needed at all. A clean clone with a committed lock and a cold cache therefore makes no consensus call, where previously it made one per package (~1.2–2.5 s each, each blocking the next). This is the model cargo uses with Cargo.lock.
  • When the lockfile cannot answer — no lock, a stale one, a package new to the lock, or a version that lost a conflict — the registry's consensus reply is used, as before, and always before the bytes are staged into the cache. Verification is therefore always against either a committed local record or a subnet-agreed one, at the moment of admission, regardless of which command is installing.
  • Registry file hashes are fetched at most once per process, so a package downloaded during an install costs nothing further when the lockfile is written.
  • A hash mismatch now names its source. If the expectation came from mops.lock, the message says so and points at restoring or regenerating the lockfile, rather than suggesting a retry that cannot succeed.
  • GitHub dependencies are now covered by the lockfile. mops.lock records the resolved commit and a content hash for every repo = "..." dependency, and an install verifies the fetched archive against them before it enters the cache. A ref carrying no commit — a bare #main, or a tag — is resolved once and pinned and then fetched by commit, so a moved tag or a force-push can no longer silently change what you build. mops verify audits them on disk too. Migration: for a project with GitHub dependencies, a lockfile written by an older CLI counts as stale — mops install regenerates it, and --locked fails until the result is committed. They are often transitive, so this can apply to a project whose own mops.toml declares none.
  • Behaviour change: plain mops install now fails when it has to download a package whose hashes disagree with the committed lockfile. It remains true that files already on disk under .mops/ are not re-hashed by an install — mops verify is still the command that audits those.

Dependency resolution

  • Version comparison uses a real semver comparator instead of parseInt on dot-split parts. Semantics are unchanged — bare 1.2.3 is still exact, conflicts still resolve to the root version or the highest — but GitHub refs now compare correctly: prereleases no longer tie with their release or with each other (rc.2 vs rc.10 was a coin flip), 0.16 no longer equals 0.16.1, and #release-v1.2.0 no longer parses as 0.2.0. Registry versions are validated x.y.z at publish, so nothing changes for them.
  • Breaking: cross-major dependency conflicts are reported by default on every resolving command (mops install, build, test, sources, …), on stderr so mops sources stdout stays machine-parseable. It is a warning — resolution still succeeds. The report names every dependent, which version won, and that pinning in your root mops.toml is how you choose otherwise. Minor/patch skew stays silent, and only registry dependencies take part. Since a valid mops.lock skips the graph walk, the report appears on the run that produces or updates the lock. (mops watch cannot surface it yet: its redraw clears the terminal.)
  • mops sources --conflicts ignore silences the report for the whole command — pass it where your build tool invokes mops sources after you have reviewed a conflict and decided to keep it. --conflicts error still exits non-zero. Other commands have no opt-out.

Install engine

  • Parallel package installs. Packages download through a bounded pool instead of one at a time, and branches of the graph requesting the same package share one download. A cold install of 8 root packages plus transitives measured 19.7 s → 13.2 s. New mops install --concurrency <n> and MOPS_CONCURRENCY cap simultaneous registry requests; the default derives from the CPU count and the file-descriptor soft limit (2 × cores, clamped to 4–16), so a many-core machine with a low ulimit -n cannot exhaust its own descriptors. The undocumented heuristic that capped download threads whenever GITHUB_ENV was set is removed — it detected a brand, not a constraint.
  • Installs self-heal on transient network failures. A fetch failed, ECONNRESET or EMFILE retries up to twice with the request concurrency halved, so an environment that cannot sustain the default parallelism degrades to a slower install instead of a broken one. Packages that already downloaded come from the cache; only the failures are re-fetched. Registry answers such as "Package not found" are never retried.
  • Roughly 1.5 s of fixed cost removed from every install, independent of how many packages a project has: registry file-hash lookups are batched into one request that starts before the downloads so the consensus round overlaps them, dependency resolution runs once per command instead of three to five times, the API compatibility check runs alongside the install rather than before it, install telemetry no longer waits for a certified reply, and the agent no longer synchronises time against a ledger canister it never otherwise talks to.
  • mops update is single-pass. Outdated dependencies download in parallel through the same pool mops install uses, mops.toml is rewritten once after the installs instead of once per package, and the registry is notified in one batched call. Failure handling is unchanged: a dependency that fails to update is reported, keeps its old mops.toml entry and exits 2 without blocking the others. Each applied update prints Updated <pkg> <old> -> <new>.
  • mops outdated and mops update no longer make a registry call when a project has no registry dependencies to check.
  • Breaking for programmatic consumers of the ic-mops package: downloadFile and downloadPackageFiles return Uint8Array instead of Array<number>.

Defaults and CLI strictness

  • Breaking: unknown flags before -- are rejected with an error instead of silently swallowed as arguments (a mistyped mops check --nope used to be treated as an ordinary argument). Applies to build, check, check-stable, test, bench, generate candid and lint. The -- <tool flags> passthrough is unaffected: mops check -- -Werror, mops lint -- --severity warning keep working.
  • Breaking: mops watch without flags runs the safe informative set — error check, warning check, formatting — instead of "almost everything"; --test is opt-in. Passing any flag still selects only the named tasks.
  • Breaking: mops test defaults to the verbose reporter for any number of files. Pass --reporter files for the old multi-file output.
  • Breaking: mops info <pkg> --versions lists newest-first, matching mops toolchain info --versions. Scripts that took the last line (| tail -1) should take the first (| head -1).
  • mops check gets --no-lint to skip the automatic lint step for one run when lintoko is pinned. Projects without a pin are unaffected.
  • Fix mops lint -- <lintoko flags> failing with too many arguments (Commander 13 regression). mops lint <filter> -- <lintoko flags> works too.
  • mops update --help and its doc page now state that the command rewrites mops.toml — it is cargo upgrade semantics, not cargo update — and list its exit codes.

Errors and exit codes

  • Error messages now go to stderr. Failures used to be printed with console.log, so mops <cmd> > out.txt swallowed them; every error now reaches the terminal even when stdout is redirected or machine-parsed. Exit codes are unchanged. Cleanup that used to be skipped on failure — build and --fix lock release, temp dirs — now runs reliably.
  • mops update now exits 2 when re-pinning a GitHub dependency fails, and carries on to the remaining dependencies. A failed branch lookup previously exited 0 — an incomplete update looked like success — and a failed download killed the whole update with exit 1.
  • Commands run outside a mops project now exit 1 after printing the missing-mops.toml error. mops bump, mops sync, mops owner * and mops maintainer * used to print it and exit 0. A registry lookup error in mops add — including the add calls mops sync makes — also exits 1 now instead of 0.
  • mops update now exits 2 for an unknown package or a missing mops.toml, matching mops outdated. It previously exited 0 after printing Package "<name>" is not installed!, so a typo in a scripted update looked like success.

Removals

  • Breaking: mops set-network and mops get-network are removed. Use the MOPS_NETWORK environment variable — MOPS_NETWORK=local mops install (or staging); unset means ic. The removed commands stored the choice in a file inside the installed CLI directory: frequently not writable (CI, Docker, root-owned globals), shared across every project on the machine, and wiped by the next npm i -g ic-mops. MOPS_NETWORK has been the documented mechanism since 2.5.1. mops get-network has no replacement — read $MOPS_NETWORK.
  • Remove vessel/dhall support (deprecated since 2.14). mops init no longer migrates vessel.dhall — copy dependencies into mops.toml and delete vessel.dhall / package-set.dhall. GitHub dependencies (repo = "...") are unaffected, but transitives they declare via vessel files are no longer resolved — add what you need to your own mops.toml. .vessel directories are no longer excluded from mops test/watch scans, and the dhall-to-json-cli dependency is gone.

Runtime

  • Breaking: Node.js >= 20 is required (engines bump from >= 18); installs on Node 18 fail with an engines error. (#288)

Fixed

  • A local path dependency's own mops.toml no longer goes unnoticed. Adding or bumping a dependency inside a local package left mops.lock judged fresh, so mops install exited 0, installed nothing, and never passed the new dependency to the compiler — the package then failed to build against a dependency mops had reported as installed. mops.lock now records a hash of the [dependencies] of every path dependency it reaches, transitively, so editing any of them makes the lockfile stale.
  • Changing MOPS_ENV no longer leaves mops.lock pinned to the previous environment. {MOPS_ENV} paths are stored expanded in the lockfile, but the freshness check compared the unexpanded string, so a full mops install under a new environment exited 0 and kept building against the old environment's directories. mops install now re-resolves, mops sources reports the current environment, and mops install --locked fails rather than silently using the wrong paths. Note that a committed lockfile now only satisfies --locked for the MOPS_ENV it was generated under.
  • Breaking: only the winning versions and their own dependencies are installed — the closure of the winners, as cargo and pnpm do it. Previously a package declared only by a version that lost a conflict was still downloaded and passed to moc as --package. A project that was compiling against such a package without declaring it now fails with an unresolved import — add the dependency to mops.toml. Dependency edges for losing versions stay in the lockfile, so regenerating a lock does not need those versions back on disk.
  • mops outdated is now usable as a CI gate. It exited 0 whether or not anything was outdated. It now exits 1 when updates are available and 2 when the check itself could not be completed (no mops.toml, unknown package, registry or GitHub lookup error), so a partial report can never be mistaken for a clean bill of health. 1 for "found something" matches npm outdated and pnpm outdated.
  • mops outdated and mops update no longer disagree. outdated skipped GitHub dependencies while mops update updates them, so it could print "All dependencies are up to date!" for a project where mops update would rewrite a GitHub pin. GitHub dependencies whose branch has moved past the pinned commit are now reported, using the same rule mops update applies.
  • mops update no longer declares a package twice when it is declared in both sections. Given core in [dependencies] and "core@1" in [dev-dependencies], the update was written under the [dependencies] key but into [dev-dependencies], leaving the intended entry stale and the package declared twice. The key and the section now come from the single declaration that matched.
  • A pinned alias no longer claims versions outside its own segment. "map@1" matched 10.x and "map@8" matched 80.x — a version prefix was compared without a segment boundary — so mops update and mops outdated could report an update against the wrong declared version and write it under the wrong key.
  • mops add <pkg> --dev now moves an existing [dependencies] entry into [dev-dependencies] instead of declaring the package twice; a plain mops add <pkg> moves it back.
  • mops remove <pkg> now finds the package in whichever section declares it, so removing a dev-only dependency no longer requires --dev. When both sections declare it, both entries go; --dev still removes only the dev entry.
  • mops add org/repo is accepted as a GitHub shorthand instead of crashing with an unhandled ERR_INVALID_URL. An argument that is neither a package name, a GitHub repo nor a local path now fails with a message naming the accepted forms, and a failed repo lookup prints an error rather than a stack trace.
  • mops add <pkg>@<version> no longer collapses an existing pinned alias such as "map@8.1.0" = "8.1.0" into the bare package key. Replacing a declared version is now reported, along with the pinned alias to add if both versions should be kept.
  • mops add and mops remove now name the affected section in their output.
  • mops sync no longer destroys a pinned alias dependency. Given map = "9.0.1" and "map@8.1.0" = "8.1.0", sync compared imports (map@8.1.0) against alias-stripped manifest keys (map), so it reported the alias as both missing and unused — adding it overwrote map with 8.1.0, and a single run could remove the dependency entirely. Aliases are now matched verbatim and added under their own key.
  • mops sync adds packages imported only from test, tests, bench or benchmark directories to [dev-dependencies] rather than [dependencies]. Already-declared packages are never moved between sections.
  • mops sync removes an unused package from both sections when it is declared in both; previously it was only removed from [dependencies], leaving a dangling entry that the next run reported again.
  • mops sync is roughly twice as fast — it runs moc --print-deps once per file instead of twice.
  • mops remove --dry-run is now side-effect free. It no longer deletes local cache directories or rewrites a stale mops.lock, and it prints Would remove package … instead of reporting the removal as done.
  • mops cache clean works on Windows and on non-ic networks. Its safety guard compared a path.join result against a forward-slash suffix, so it failed with "Invalid cache directory" on every Windows run and for every network-scoped cache directory. The replacement is separator-agnostic and strictly narrower: it also requires the directory to be inside the global cache root, rejecting a traversing MOPS_NETWORK.
  • mops cache clean no longer deletes ./.mops when run outside a project, where an empty root directory made it target the current working directory.
  • mops update and mops outdated now share one rule for deciding when a repo = "..." dependency is out of date, so the two commands cannot disagree about it.
  • mops install no longer crashes with a raw RangeError: Maximum call stack size exceeded when two local path dependencies require each other, or when one requires itself. The install walk now skips a local package it has already visited in the same run, naming neither a cycle nor an error — the packages install normally.
  • Fixed mops remove <pkg> crashing with Invalid dependency value "" when the dependency is a local path dep.
  • mops remove now echoes the dependency value it removed for GitHub and local path deps, instead of an empty version.

Added

  • mops outdated [pkg] accepts a package name, matching mops update [pkg].
  • mops update --verbose, matching mops add, mops remove and mops install.
  • mops sync --dry-run prints what would be added and removed without touching mops.toml, the local cache or mops.lock.
  • mops cache clean --global cleans only the global cache and keeps the project's .mops directory.
  • mops.lock gains an optional localDepsHash field, written only for projects that declare a local path dependency. Those projects have their lockfile regenerated once on the next mops install, and mops install --locked fails until the regenerated lockfile is committed. Projects without path dependencies are unaffected — the field is omitted entirely and existing lockfiles stay valid.

SHA256: 79320180f705ecb98cba81994ddcb76f6fbe6e752c10f5c745ce2053f146a7a1

Verify build:

cd cli
docker build . --build-arg COMMIT_HASH=89154ce076a48fae0c76d60506b1a0e3f44294fb --build-arg MOPS_VERSION=3.0.0 -t mops
docker run --rm --env SHASUM=79320180f705ecb98cba81994ddcb76f6fbe6e752c10f5c745ce2053f146a7a1 mops