关于 mops-cli
mops-cli 是面向 Motoko 开发者的项目工作流指南,涵盖配置、依赖、检查、构建与 lint。它强调以 canister 为中心的配置,并可检查稳定变量兼容性。
主要功能
- 01在 mops.toml 配置 canister 和工具链
- 02管理依赖与锁文件
- 03类型检查、lint 和稳定性兼容检查
- 04构建 canister 并生成 Candid 接口
适用场景
- 01初始化 Motoko 项目并固定编译器版本
- 02部署前验证 canister 变更
- 03在 CI 中保持依赖锁文件一致
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ dfinity/ icskills --skill mops-cli安装后在 Claude Code 中输入/mops-cli确认已加载。
详细介绍
Mops CLI
面向 Motoko 项目的实用指南。涵盖项目配置、依赖管理、类型检查、构建和 lint。
核心原则
- 不使用 dfx — mops 既不会调用
dfx,也不支持它。没有mops toolchain init、moc-wrapper或mops watch --generate/--deploy。始终在[toolchain]中固定moc版本;每条需要编译的命令都必须有该版本。使用最新的moc版本。如果有 replica 测试、基准测试或--check-deploy,也要固定pocket-ic版本——如果没有固定版本,这些命令会报错,并提示使用mops toolchain use pocket-ic 15.0.0。当MOPS_POCKET_IC_URL指向正在运行的 PocketIC 服务时,无需固定版本;如果已固定版本,则会被忽略并显示警告——不要为了消除警告而添加固定版本;请改为取消设置该变量。 - 不使用
mo:base— 它已弃用。始终使用mo:core(import Array "mo:core/Array")。 - 所有配置都放在
mops.toml中 — canister、moc 标志、工具链版本和构建设置。 - 以 canister 为中心的工作流 — 在
[canisters]中定义所有 canister;绝不要向mops check传递文件路径。例外:库包(没有[canisters])可直接使用文件路径:mops check src/**/*.mo。
项目设置
最精简的 mops.toml
[toolchain]
moc = "1.15.1"
lintoko = "0.11.0"
pocket-ic = "15.0.0" # required for replica tests / benchmarks / --check-deploy
[dependencies]
core = "2.6.1"
[moc]
args = ["--default-persistent-actors", "-W=M0223,M0236,M0237"]
[canisters.backend]
main = "src/backend/main.mo"
[canisters.backend.migrations]
chain = "src/backend/migrations"
# check-limit = 10 # optional — speeds up `mops check` when the chain gets long
[canisters.backend.check-stable]
path = "deployed/backend.most"
[build]
outputDir = "src/backend/dist"
args = ["--release"]
# check-wasm = true # optional: analyze final Wasm complexity
# check-deploy = true # optional: verify fresh PocketIC installation after build
# Opt-in Wasm optimization (Binaryen wasm-opt) for build + bench
[optimize]
# level = "O3" # default
# keep-names = true # default
# wasm-opt pin: [toolchain] wasm-opt = "131" (required when [optimize] is set)
check-stable 会在本地运行 ICP 的升级时稳定变量兼容性检查,因此不兼容的更改会在 mops check 中报错,而不会等到升级线上 canister 时才被拒绝。它会将当前代码与已部署版本的 .most 进行比较。
初始化该 .most:新项目 → mops deployed init(空 actor 基线);已部署的 canister → 从已部署的提交构建,然后运行 mops deployed。每次部署后,运行 mops deployed,将刚构建的 .most 提升为基准(见下文的 mops deployed)。
canister 可选字段:candid(用于兼容性检查的 .did 文件路径)、initArg(经 Candid 编码的初始化参数)。
警告标志
-W=M0223,M0236,M0237 — 冗余类型实例化(M0223)、建议使用上下文点表示法(M0236)、建议使用冗余的显式参数(M0237)。这些警告默认允许(禁用);-W= 会将它们启用为警告。
Moc 参数分层
标志按以下顺序应用(后者会覆盖前者):
[moc].args— 全局,适用于所有命令(check、build、test、bench 等)[build].args— 仅用于构建(例如--release)[canisters.<name>.migrations]— 自动注入--enhanced-migration(由 mops 管理)[canisters.<name>].args— 针对单个 canister- CLI
-- <flags>— 一次性覆盖;mops check、mops build、mops check-stable、mops generate、mops migrate、mops test和mops bench均支持
核心命令
mops install
mops install # dev flow: keeps mops.lock in sync, self-heals a broken one
mops install --locked # CI flow: fail if mops.lock is missing or would change
克隆项目或手动编辑 mops.toml 后运行。始终维护 mops.lock——无法通过标志选择不生成锁文件,也没有 --lock 标志(已在 v3 中移除)。如果锁文件缺失、无法解析、格式过时或与 mops.toml 不一致,直接运行 mops install 就会重新生成锁文件;这也包括仍带有旧版 CLI 写入的本地绝对路径的锁文件,以及丢失了传递依赖的锁文件(某个锁定的软件包声明了一个依赖,但 deps 中不再列出它——通常是合并操作出错所致)。
--locked 要求锁文件为最新状态,且绝不会写入锁文件。每个会隐式安装依赖的命令都支持它(mops build、mops check、mops check-stable、mops check-candid、mops test、mops bench、mops generate candid),因此 CI 可以直接运行 mops test --locked,无需事先安装。mops sources 不支持 --locked(packtool 调用方会在构建过程中解析它的标准输出),因此应在流水线的前序步骤中运行 mops install --locked。
CI 环境变量不再影响锁文件行为(已在 v3 中移除)。应用和库都应提交 mops.lock。
本地 path 依赖自身的 mops.toml 发生变化时,锁文件也会过期——包括依赖链中更深层的本地 path 依赖。运行 mops install 会重新生成锁文件;在提交重新生成的锁文件之前,--locked 会失败。请注意,升级后首次运行 mops install 时,任何包含 path 依赖的项目都会重新生成锁文件,因此在 CI 中运行 --locked 前,请先提交该文件。没有 path 依赖的项目会保留现有锁文件。
path 依赖中的 {MOPS_ENV} 会展开为 $MOPS_ENV(默认为 local),因此锁文件会因环境而异。切换 MOPS_ENV 会使锁文件过期:运行 mops install 会重新生成锁文件,而 --locked 会报错并指出 MOPS_ENV。每个环境保留一份锁文件,或不使用 --locked——在某个 MOPS_ENV 下生成的锁文件无法满足另一个环境下的 --locked 要求。
完整性会在下载时验证,因此 mops install 不再重新计算 .mops/ 中文件的哈希值:就地编辑依赖不会导致下一次安装失败。需要按需审计磁盘上的文件时,请使用 mops verify。
下载的文件在进入缓存前始终会经过检查——如果 mops.lock 已记录该软件包,则依据锁文件检查;否则依据注册表检查。因此,提交锁文件即可免去验证步骤,这也是全新检出后安装时无需向注册表查询哈希值的原因。
有两点值得注意:损坏或手动编辑过的 mops.lock 现在会导致_下载_失败(错误会指出 mops.lock 可能有问题——请从版本控制中恢复该文件;已缓存的软件包不受影响);如果注册表发布的软件包没有哈希值,该软件包仍会安装,但未经验证,并会显示警告。
软件包通过有上限的线程池并行下载。mops install --concurrency <n> 或 MOPS_CONCURRENCY 环境变量(所有安装命令均适用)可限制同时发出的注册表请求数;默认值根据 CPU 数量和文件描述符软限制计算(4–16)。遇到瞬时网络错误(fetch failed、ECONNRESET、EMFILE)时,会自动将并发数减半后重试,最多两次。只有在重试后安装仍失败时才设置 MOPS_CONCURRENCY=1(例如,出口代理限制了连接数)。
mops verify
mops verify # re-hash .mops/ against mops.lock, and mops.lock against the registry
适用于 GitHub 依赖和注册表依赖。
如果发现问题文件,会以状态码 1 退出,并提供恢复提示。对于过去依赖 .mops/ 被修改时 mops install 会失败的用户,此命令可替代原有做法。
mops add <package>
mops add core # latest version
mops add [email protected] # specific version
mops add --dev test # dev dependency
mops add org/repo # GitHub shorthand (also accepts a full github.com url)
mops add ./pkg # local package directory
更新 mops.toml 和 mops.lock。
如果要添加的软件包已在另一个分区中声明,则会将其移动到目标分区,而不是重复声明。<pkg>@<version> 会替换已声明的版本并报告被替换的内容;它不会修改 "[email protected]" = "1.0.0" 这类固定别名,也没有用于创建此类别名的标志——请手动编写。
mops check
主要正确性检查命令——运行 moc check,然后运行 check-stable(如果已配置),最后运行 lint(如果工具链中包含 lintoko)。
在 moc 1.15.0 及以上版本中,带有 [migrations] 的 canister 会获得更严格的升级诊断:如果初始 actor 要求某个字段,但没有任何迁移生成该字段,则会报错 M0267,而不是仅发出警告(M0254);兼容性错误也会附带源代码位置。较旧的 moc 固定版本,以及不带 [migrations] 的 canister,不受影响。
check-stable 基线始终是 .most 文件——通过 [canisters.<name>.check-stable].path 指定,或作为 mops check-stable <baseline.most> 参数传入。不接受 .mo 源文件。基线的来源请参见 mops deployed——新建项目与已部署的 canister 在这方面有所不同。
mops check # all canisters
mops check backend # single canister
mops check --fix # autofix + check + stable + lint
mops check --no-lint # skip the lint step for one run
mops check --verbose # show moc invocations
mops check -- -Werror # treat warnings as errors
始终使用 canister 名称,而不是文件路径。 mops.toml 中针对各 canister 的参数会自动应用。
--fix 会一次性应用 moc 和 lintoko 提供的机器可应用修复。并发运行 --fix(跨进程)时,会通过 .mops/fix.lock 中的建议锁自动串行执行,因此可在同一项目中安全地由多个 Agent 调用。只读文件(例如已冻结的迁移文件)会跳过并发出警告,不会被修复。
mops build
mops build # all canisters
mops build backend # single canister
mops build --verbose # show compiler commands
mops build --check-wasm # analyze final Wasm complexity without PocketIC
mops build --no-check-wasm # skip configured [build].check-wasm once
mops build --check-deploy # verify fresh installation on PocketIC
mops build --no-check-deploy # skip configured [build].check-deploy once
mops build -- --ai-errors # pass extra moc flags
在 [build].outputDir(默认为 .mops/.build)中生成 .wasm、.did 和 .most 文件。
如果 mops.toml 中配置了 [optimize],则会在生成 candid 元数据后运行 wasm-opt(默认为 -O3 -g)。这需要固定 Binaryen 版本:mops toolchain use wasm-opt 131。Build 命令不会自行写入该版本固定项;若 wasm-opt 失败,Build 也会失败。在 build 或 bench 命令中传入 --no-optimize,即可在单次运行中跳过优化,而无需编辑 mops.toml。
启用 --check-wasm 或 [build].check-wasm = true 时,Mops 会在不启动 PocketIC 的情况下,对最终 Wasm 运行快速 Walrus 分析。单个函数的 IC0505 复杂度低于 750,000 时不会输出提示,750,000 至 899,999 时会发出早期警告,达到 900,000 或以上时会发出严重警告。MOPS-WASM-COMPLEXITY 输出包含可操作的函数指标、复杂度贡献最大的三个因素,以及 Motoko 修正建议。此估算不会导致 Build 失败。使用 --no-check-wasm 可在单次运行中跳过已配置的分析。
mops deployed
部署后钩子——使磁盘上的 .most 基线与实际部署的内容保持同步,供 check-stable 使用。
mops deployed init backend # one-time bootstrap: empty-actor baseline + sets [check-stable].path
mops deployed backend # post-deploy: promotes .mops/.build/backend.most → deployed/backend.most
mops deployed # all canisters
默认目标路径为 deployed/<name>.most;可通过 mops.toml 中的 [deployed].dir 或 --dir 覆盖。此命令会从 [build].outputDir(默认为 .mops/.build)读取已构建的 .most 文件;可通过 --build-dir 覆盖。若源 .most 文件缺失,mops deployed 会报错——它绝不会重新生成该文件。请在部署流程中,于部署成功后立即运行此命令。
mops generate candid
mops generate candid # all canisters
mops generate candid backend # single canister
mops generate candid backend -o <path> # single canister, ad-hoc path
根据当前 Motoko 源代码(重新)生成整理后的 .did 文件。设置了 [canisters.<name>].candid 时,会覆盖该文件。未设置时,会在 main 所在目录写入 <name>.did(例如,main = "src/Backend.mo" → src/backend.did),并在 mops.toml 中设置 [canisters.<name>].candid。每次接口变更后都应运行此命令;同时提交 .did 和 mops.toml。它使用与 mops build 相同的 moc 调用,因此生成结果始终能通过 mops build 的子类型检查。
mops toolchain
mops toolchain use moc 1.15.1 # pin specific version
mops toolchain use moc latest # pin latest version (non-interactive)
mops toolchain use lintoko 0.11.0 # pin specific version
mops toolchain use pocket-ic 15.0.0 # pin for replica tests / benchmarks / --check-deploy
mops toolchain use wasm-opt 131 # Binaryen for [optimize] (or `latest`)
mops toolchain update moc # update to latest (requires existing [toolchain] entry)
mops toolchain update # update all tools to latest
mops toolchain info <tool> # show release info (latest, pinned, history)
mops toolchain info <tool> --versions # list recent releases, newest first
mops toolchain info <tool> --versions --all # full release history (cache warming)
mops toolchain info <tool> --versions --prerelease # include prereleases
mops toolchain bin moc # print path to binary
pocket-ic 版本:可固定为 9.0.0 及以上的任意版本,包括 latest——mops 不维护认可版本列表。低于 9.0.0 的固定版本会报错并提示迁移(这些版本运行于旧版客户端,而 mops 3.0.0 已移除该客户端)。如果没有固定版本,副本测试 / mops bench / --check-deploy / mops toolchain bin pocket-ic 都会报错,并提示使用 mops toolchain use pocket-ic 15.0.0。该版本只是提示,并非回退版本。
Agent 提示:不带版本运行 toolchain use <tool> 会打开交互式选择器——不要在脚本或 Agent 中使用。始终传入版本或 latest。选择器、latest 和 --versions 默认都使用稳定版本;传入 --prerelease 可包含预发布版本。只有当工具已有 [toolchain] 条目时,toolchain update 才有效。即使没有 mops.toml,也可以运行 toolchain info <tool> --versions(默认显示 GitHub 上的第一页;传入 --all 可查看完整历史)。
增强迁移
配置了 [canisters.<name>.migrations] 后,mops check、mops build 和 mops check-stable 会自动注入 --enhanced-migration。不要将 --enhanced-migration 添加到 [canisters.<name>].args 中——mops 会报错。
直接在 chain 目录中创建迁移文件。
运行 mops check --fix(或 mops check <canister>)确认迁移链可以编译后,再运行 mops build 生成 wasm 构件。
使用 mops build --check-deploy,或设置 [build].check-deploy = true 以在每次构建时安装到全新的 PocketIC canister 上,检查模块验证、初始化和安装是否失败。需要 [toolchain] pocket-ic(9.0.0 及以上版本,或本地二进制文件路径)——或者将 MOPS_POCKET_IC_URL 设置为正在运行的 PocketIC 服务(此时会忽略固定版本)。如果没有固定版本且未设置 URL,构建会报错并提示使用 mops toolchain use pocket-ic 15.0.0。使用 --no-check-deploy 可跳过一次已配置的验证。该命令使用每个 canister 的 initArg;如果未指定,则使用 ()。在 canister 上将 wasmMemoryLimit 设置为正整数(字节数),即可在该限制下检查部署。PocketIC 错误会按客户端提供的内容报告,安装失败也会在各个 canister 中汇总。安装前,Mops 会从临时的空 actor .most 运行 moc --stable-compatible,并将其与每个生成的 .most 进行比较。如果 moc 报告不兼容,Mops 会发出 MOPS-CHECK-DEPLOY-SKIPPED,附上编译器诊断信息,并且不会在全新的 PocketIC 上检查该 canister。符合条件的其他 canister 仍会接受检查;请使用有代表性的基线状态验证跳过检查的升级。
check-limit(可选)限制 mops check 和 mops lint 会检查的近期迁移文件数量——当迁移链变得很长、重新检查所有旧迁移会减慢反馈速度时,这会很有用。mops build 不受 check-limit 影响;它会编译完整迁移链,并且在设置了 [canisters.<name>.check-stable].path 时,还会通过 --stable-baseline 合并已部署的基线。因此,如果部署后已应用的迁移被编辑、删除或回溯日期,构建会以 M0268 失败——当 check-limit 将该迁移从合并后的 mops check 中裁掉时,这是防止遗漏的保障措施。达到限制时,mops 会将纳入检查的文件暂存到 chain 目录旁的 .migrations-<canister>/ 中(并自动添加到 .gitignore)。moc 诊断信息可能会显示这些路径——实际文件仍位于 chain 目录中,且文件名相同。
使用 --no-check-limit 可在单次运行中覆盖 check-limit(适用于 mops check、mops check-stable、mops lint)——例如,运行 mops check --fix --no-check-limit 可自动修复较旧且通常会被裁掉的迁移。在 mops check 和 mops check-stable 中,--no-check-limit 也会抑制待处理迁移警告。
设置了 check-limit 时,如果待处理迁移数量超过限制,mops check-stable(以及 mops check 内部的稳定性检查)会报告此情况——如果兼容性检查失败,则作为错误报告(替代容易误导的 moc 消息);否则作为警告报告。
mops remove <package>
mops remove core
从声明该包的部分中移除它;--dev 会将操作范围限制在 [dev-dependencies]。如果某个包在两个部分中都有声明,则会从两处移除。
依赖管理
mops outdated # list outdated deps (caret-bound); exit 1 if any, 2 if the check failed
mops outdated core # check a single package
mops update # rewrite mops.toml versions within caret bound (no major-version crossing)
mops update core # update specific package within caret bound
mops update --major # allow updates that cross major versions
mops update --patch # restrict to patch bumps only (mutually exclusive with --major)
mops update --verbose # verbose output
mops sync # add missing / remove unused packages
mops sync --dry-run # print what would change, write nothing
mops update 会原地重写 mops.toml(类似于 cargo upgrade,而非 cargo update),并将 GitHub 依赖重新固定到其分支头。与 mops outdated 一样,如果无法运行或完成操作,它会以状态码 2 退出——例如,没有 mops.toml、包未声明,或依赖更新失败(其他依赖仍会更新)。
mops sync 需要已固定的 [toolchain] moc——它会使用 moc --print-deps 读取导入项。仅从 test/tests/bench/benchmark 目录导入的包会添加到 [dev-dependencies];已声明的包绝不会在不同部分之间移动。
其他命令
mops self update
仅更新全局安装(npm/pnpm -g),如果 PATH 中的 mops 最终未更新到新版本,则操作失败。如果将 ic-mops 作为项目依赖,请改为运行 npm i -D ic-mops@<version>。
mops publish
mops publish # publish to the registry (runs tests/docs/bench by default)
mops publish --dry-run # same local steps as publish; no registry contact / identity
mops publish --dry-run --no-test --no-docs --no-bench # packaging checks only
mops publish --no-test --no-docs --no-bench
--dry-run 会运行相同的本地发布流程(打包检查、文档、变更日志、测试、基准测试),并打印最终文件列表,然后在身份验证/上传之前停止。--no-* 标志照常生效。它不会运行 canister 配置验证(SPDX/semver/名称规则),也不会验证注册表是否接受发布(已发布、权限、缺失依赖等)。
mops test
测试位于 test/*.test.mo:
mops test # run all tests
mops test my-test # filter by name
mops test --mode wasi # use wasmtime (for to_candid/from_candid)
mops test --reporter files # one line per file (default is verbose)
mops test --watch # re-run on file changes
mops test -- -Werror # pass extra moc flags
Replica 测试(actor 文件或 // @testmode replica)会在 PocketIC 上运行——使用 [toolchain] 中的 pocket-ic 版本,或在设置了 MOPS_POCKET_IC_URL 时使用已运行的服务(无需固定版本;附加模式下不会流式传输 canister 日志输出)。如果未固定版本且未设置 URL,则会报错并提示 mops toolchain use pocket-ic 15.0.0。mops bench 和 mops watch --test 也一样。没有 --replica 标志,也没有 dfx replica。
mops bench
基准测试位于 bench/*.bench.mo:
mops bench # run all benchmarks
mops bench my-bench # filter by name
mops bench --gc incremental # select GC
mops bench --save # save results to .bench/<name>.json
mops bench --compare # compare with saved results
mops bench -- -Werror # pass extra moc flags
mops lint
运行 lintoko(如果 toolchain 中包含 lintoko,mops check 也会自动运行它):
mops lint # lint all .mo files
mops lint --fix # autofix lint issues
mops lint <name> # filter to .mo files matching <name>
设置 [canisters.<name>.migrations].check-limit 后,mops lint 会跳过裁剪后的链式迁移,以匹配 mops check 中 moc 所见的内容。若要按需检查被裁剪的迁移,请传入显式筛选项(例如 mops lint OldMigrationName),或传入 --no-check-limit 以检查完整链。
mops format
mops format # format all .mo files
mops format --check # check formatting without modifying
mops watch
mops watch # errors + warnings + format (the default set)
mops watch --warning # errors + warnings only
mops watch -t # errors + tests
错误检查始终开启。传入任何标志都会只选择指定任务,因此如果希望这些任务与 -t 一起运行,请添加 -w / -f。任务总共只有这四种——没有 --generate 或 --deploy。
常见模式
抑制 canister 的警告
使用每个 canister 的 args(而非全局配置)来抑制警告:
[canisters.backend]
main = "src/backend/main.mo"
args = ["-A=M0198"]
新建项目
mops init -y
mops toolchain use moc latest # pin latest moc (non-interactive)
mops toolchain use lintoko latest # pin latest lintoko
mops add core
然后在 mops.toml 中配置 [moc].args、[canisters] 和 [build]。
稍后如需更新工具:运行 mops toolchain update moc 或 mops toolchain update(更新所有工具)。