mcpField
M
mops-cli

mops-cli

github.com/dfinity开发39161 次安装今天更新
代码仓库

关于 mops-cli

mops-cli 是面向 Motoko 开发者的项目工作流指南,涵盖配置、依赖、检查、构建与 lint。它强调以 canister 为中心的配置,并可检查稳定变量兼容性。

主要功能

  1. 01在 mops.toml 配置 canister 和工具链
  2. 02管理依赖与锁文件
  3. 03类型检查、lint 和稳定性兼容检查
  4. 04构建 canister 并生成 Candid 接口

适用场景

  1. 01初始化 Motoko 项目并固定编译器版本
  2. 02部署前验证 canister 变更
  3. 03在 CI 中保持依赖锁文件一致

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/dfinity/icskills --skill mops-cli

安装后在 Claude Code 中输入/mops-cli确认已加载。

详细介绍

Mops CLI

面向 Motoko 项目的实用指南。涵盖项目配置、依赖管理、类型检查、构建和 lint。

核心原则

  1. 不使用 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 服务时,无需固定版本;如果已固定版本,则会被忽略并显示警告——不要为了消除警告而添加固定版本;请改为取消设置该变量。
  2. 不使用 mo:base — 它已弃用。始终使用 mo:core(import Array "mo:core/Array")。
  3. 所有配置都放在 mops.toml 中 — canister、moc 标志、工具链版本和构建设置。
  4. 以 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 参数分层

标志按以下顺序应用(后者会覆盖前者):

  1. [moc].args — 全局,适用于所有命令(check、build、test、bench 等)
  2. [build].args — 仅用于构建(例如 --release)
  3. [canisters.<name>.migrations] — 自动注入 --enhanced-migration(由 mops 管理)
  4. [canisters.<name>].args — 针对单个 canister
  5. 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(更新所有工具)。

评论

支持 Markdown