Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .agents/skills/mops-cli/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Opinionated guide for Motoko projects. Covers project config, dependency managem

## Key Principles

1. **No dfx** — always pin `moc` in `[toolchain]`. Use the newest `moc` version.
1. **No dfx** — always pin `moc` in `[toolchain]`. Use the newest `moc` version. Pin `pocket-ic` too if you have replica tests or benchmarks (otherwise `mops test --mode replica`, `mops bench`, and `mops watch` fall back to the deprecated dfx replica and print a warning).
2. **No `mo:base`** — it is deprecated. Always use `mo:core` (`import Array "mo:core/Array"`).
3. **All config in `mops.toml`** — canisters, moc flags, toolchain versions, build settings.
4. **Canister-centric workflow** — define all canisters in `[canisters]`; never pass file paths to `mops check`. Exception: library packages (no `[canisters]`) use file paths directly: `mops check src/**/*.mo`.
Expand All @@ -22,6 +22,7 @@ Opinionated guide for Motoko projects. Covers project config, dependency managem
[toolchain]
moc = "1.7.0"
lintoko = "0.10.0"
pocket-ic = "12.0.0" # only if you have replica tests / benchmarks

[dependencies]
core = "2.5.0"
Expand Down Expand Up @@ -122,6 +123,7 @@ Produces `.wasm`, `.did`, and `.most` files in `[build].outputDir` (default `.mo
mops toolchain use moc 1.7.0 # pin specific version
mops toolchain use moc latest # pin latest version (non-interactive)
mops toolchain use lintoko 0.10.0 # pin specific version
mops toolchain use pocket-ic 12.0.0 # pin for replica tests / benchmarks (pin a specific version; `latest` may resolve to one the bundled pic-js client doesn't support)
mops toolchain update moc # update to latest (requires existing [toolchain] entry)
mops toolchain update # update all tools to latest
mops toolchain bin moc # print path to binary
Expand Down Expand Up @@ -168,6 +170,8 @@ mops test --reporter verbose # show Debug.print output
mops test --watch # re-run on file changes
```

Replica tests (actor files or `// @testmode replica`) use `pocket-ic` from `[toolchain]`. With no pin they fall back to the deprecated `dfx` replica (warning printed) — pin `pocket-ic` in `[toolchain]` to silence it. Same applies to `mops bench` and `mops watch`.

### `mops lint`

Runs lintoko (also runs automatically as part of `mops check` when lintoko is in toolchain):
Expand Down
16 changes: 16 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,22 @@ This file provides guidance to AI coding agents when working with code in this r
- **Pre-commit hook** runs `lint-staged + npm run check` via husky — fix TypeScript/lint errors before committing.
- **Snapshot testing strategy**: Use Jest snapshots (`cliSnapshot` / `toMatchSnapshot`) for the main use cases so the full CLI output is committed and reviewable. Corner-case and error-path tests should use targeted assertions (`toMatch`, `toBe`) without snapshots to avoid cluttering the snapshot file.

## Interactive commands (caution for agents)

Some `mops` commands prompt for input and hang in non-TTY environments (CI, agent loops). Always pass values up front:

| Interactive | Non-interactive form |
|---|---|
| `mops init` | `mops init --yes` |
| `mops bump` | `mops bump <major\|minor\|patch>` |
| `mops template` | `mops template <name>` (see `mops template --help` for names) |
| `mops toolchain use <tool>` | `mops toolchain use <tool> <version>` (e.g. `pocket-ic 12.0.0`). `latest` works but may resolve to a version incompatible with the shipped client. |
| `mops owner add\|rm <principal>` | `mops owner add\|rm <principal> --yes` |
| `mops maintainer add\|rm <principal>` | `mops maintainer add\|rm <principal> --yes` |
| `mops publish` (missing recommended `[package]` field, `CI` env unset) | Fill the field in `[package]`, or run with `CI=1` |

When adding a new command or option, prefer non-interactive (accept the value as an argument or flag). Reserve prompts for purely human-facing flows like `mops init`, and at any deprecation/missing-arg site recommend the non-interactive command verbatim (e.g. ``mops toolchain use pocket-ic 12.0.0``, not ``mops toolchain use pocket-ic``).

## What this repo is

Mops is a package manager for Motoko (the Internet Computer smart contract language). It has three main components:
Expand Down
1 change: 1 addition & 0 deletions NEXT-MAJOR.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ Close the gap between update path and resolve path.

### Drop `dfx` coupling
- Remove `dfx`-bundled `moc` fallback in `toolchain bin --fallback`, `test`, `bench`, `bench-replica`, `docs`. (`cli/helpers/get-dfx-version.ts`, `cli/commands/toolchain/index.ts:359,387`, `cli/commands/docs.ts:44-54`)
- Remove the `dfx` and `dfx-pocket-ic` replica paths from `mops bench`, `mops test --mode replica`, and `mops watch`. Drop the `dfx` choice from `--replica`; drop the implicit `dfx`/`dfx-pocket-ic` fallback when `[toolchain.pocket-ic]` is unset. Flip the default so an unpinned `pocket-ic` auto-resolves to a mops-controlled `DEFAULT_POCKET_IC_VERSION` (download-on-demand via `toolchain.download("pocket-ic", ...)`), so users never need to know dfx exists. Document the version bump policy. Deprecated with warnings in 2.x via `cli/helpers/deprecate-dfx-replica.ts` (PR #555). **User-visible break**: implicit-dfx benchmark baselines drift on first run because PocketIC and dfx-replica report different instruction/heap counts; call out in release notes and recommend re-recording with `--save`.
- `mops init` stops fetching "default packages for dfx" — mops manages its own toolchain. (LIN: Doctor overhaul)
- Drop `mops toolchain init` requirement; env-var setup becomes a hint when `dfx.json` is present. (LIN)
- Reject `dfx` field in `[package]` (deprecated since 2.7).
Expand Down
2 changes: 2 additions & 0 deletions cli/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Mops CLI Changelog

## Next
- Deprecate the `dfx` replica in `mops bench`, `mops test --mode replica`, and `mops watch`. Behavior is unchanged — `--replica dfx`, the implicit `dfx` fallback when no `[toolchain.pocket-ic]` is set, and the dfx-bundled PocketIC fallback all still work — but each now prints a warning. Run `mops toolchain use pocket-ic <version>` to silence it. The `dfx` paths will be removed and the default flipped to PocketIC in mops v3 — `dfx` is being deprecated upstream and PocketIC is a better fit for benchmarks and replica tests (deterministic, in-process, no background daemon).

- `mops toolchain --help` now lists the tools mops manages (`moc`, `wasmtime`, `pocket-ic`, `lintoko`) in the top-level description instead of only mentioning them under `bin`, and `mops toolchain use` / `update` / `bin` print the available tools (via the auto-generated help) when invoked with a missing or invalid `<tool>` argument.

- Add `--patch` to `mops update` and `mops outdated` to restrict updates to patch versions only (e.g. `1.2.3 -> 1.2.4`, never `1.2.3 -> 1.3.0`). Mutually exclusive with `--major`. For pre-1.0 packages this matches the default — caret already restricts `0.x.y` to patch updates. Useful for risk-averse upgrades on packages that have hit 1.0+.
Expand Down
4 changes: 2 additions & 2 deletions cli/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,7 @@ program
.addOption(
new Option(
"--replica <replica>",
"Which replica to use to run tests in replica mode",
"Which replica to use to run tests in replica mode (`dfx` is deprecated; prefer `pocket-ic`)",
).choices(["dfx", "pocket-ic"]),
)
.option("-w, --watch", "Enable watch mode")
Expand All @@ -435,7 +435,7 @@ program
.addOption(
new Option(
"--replica <replica>",
"Which replica to use to run benchmarks",
"Which replica to use to run benchmarks (`dfx` is deprecated; prefer `pocket-ic`)",
).choices(["dfx", "pocket-ic"]),
)
.addOption(
Expand Down
3 changes: 3 additions & 0 deletions cli/commands/bench.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { parallel } from "../parallel.js";
import { absToRel } from "./test/utils.js";
import { getMocVersion } from "../helpers/get-moc-version.js";
import { getDfxVersion } from "../helpers/get-dfx-version.js";
import { warnIfDfxReplica } from "../helpers/deprecate-dfx-replica.js";
import { getMocPath } from "../helpers/get-moc-path.js";
import { sources } from "./sources.js";
import { MOTOKO_GLOB_CONFIG } from "../constants.js";
Expand Down Expand Up @@ -98,6 +99,8 @@ export async function bench(
options.replicaVersion = config.toolchain?.["pocket-ic"] || "";
}

warnIfDfxReplica(replicaType, optionsArg.replica === "dfx");

options.verbose && console.log(options);

let replica = new BenchReplica(replicaType, options.verbose);
Expand Down
1 change: 0 additions & 1 deletion cli/commands/publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,6 @@ export async function publish(
console.log("Running benchmarks...");
try {
benchmarks = await bench("", {
replica: config.toolchain?.["pocket-ic"] ? "pocket-ic" : "dfx",
gc: "copying",
forceGc: true,
silent: true,
Expand Down
14 changes: 14 additions & 0 deletions cli/commands/test/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { toolchain } from "../toolchain/index.js";
import { Replica } from "../replica.js";
import { TestMode } from "../../types.js";
import { getDfxVersion } from "../../helpers/get-dfx-version.js";
import { warnIfDfxReplica } from "../../helpers/deprecate-dfx-replica.js";
import { MOTOKO_GLOB_CONFIG, MOTOKO_IGNORE_PATTERNS } from "../../constants.js";

type ReporterName = "verbose" | "files" | "compact" | "silent";
Expand Down Expand Up @@ -79,6 +80,8 @@ export async function test(filter = "", options: Partial<TestOptions> = {}) {
}
}

let explicitReplica = options.replica === "dfx";

replica.type = replicaType;
replica.verbose = !!options.verbose;

Expand Down Expand Up @@ -124,6 +127,7 @@ export async function test(filter = "", options: Partial<TestOptions> = {}) {
replicaType,
true,
controller.signal,
explicitReplica,
);
await curRun;

Expand All @@ -150,6 +154,9 @@ export async function test(filter = "", options: Partial<TestOptions> = {}) {
filter,
options.mode,
replicaType,
false,
undefined,
explicitReplica,
);
if (!passed) {
process.exit(1);
Expand All @@ -167,6 +174,7 @@ async function runAll(
replicaType: ReplicaName,
watch = false,
signal?: AbortSignal,
explicitReplica = false,
): Promise<boolean> {
let done = await testWithReporter(
reporterName,
Expand All @@ -175,6 +183,7 @@ async function runAll(
replicaType,
watch,
signal,
explicitReplica,
);
return done;
}
Expand All @@ -186,6 +195,7 @@ export async function testWithReporter(
replicaType: ReplicaName,
watch = false,
signal?: AbortSignal,
explicitReplica = false,
): Promise<boolean> {
let rootDir = getRootDir();
let files: string[] = [];
Expand Down Expand Up @@ -261,6 +271,10 @@ export async function testWithReporter(
let hasWasiTests = filesWithMode.some(({ mode }) => mode === "wasi");
let hasReplicaTests = filesWithMode.some(({ mode }) => mode === "replica");

if (hasReplicaTests) {
warnIfDfxReplica(replicaType, explicitReplica);
}

// prepare wasmtime path
if (hasWasiTests && !wasmtimePath) {
// ensure wasmtime is installed or specified in config
Expand Down
6 changes: 5 additions & 1 deletion cli/commands/watch/tester.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { readConfig } from "../../mops.js";
import { ErrorChecker } from "./error-checker.js";
import { testWithReporter } from "../test/test.js";
import { SilentReporter } from "../test/reporters/silent-reporter.js";
import { type ReplicaName } from "../../helpers/deprecate-dfx-replica.js";

export class Tester {
verbose = false;
Expand Down Expand Up @@ -53,12 +54,15 @@ export class Tester {
this.controller = new AbortController();

let config = readConfig();
let replicaType: ReplicaName = config.toolchain?.["pocket-ic"]
? "pocket-ic"
: "dfx";

this.currentRun = testWithReporter(
this.reporter,
"",
"interpreter",
config.toolchain?.["pocket-ic"] ? "pocket-ic" : "dfx",
replicaType,
true,
this.controller.signal,
);
Expand Down
32 changes: 32 additions & 0 deletions cli/helpers/deprecate-dfx-replica.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import chalk from "chalk";

export type ReplicaName = "dfx" | "pocket-ic" | "dfx-pocket-ic";

let alreadyWarned = false;

// Prints a deprecation warning (once per process) when `mops bench`/`mops test`/
// `mops watch` is about to use a dfx-backed replica. Removal is tracked in
// NEXT-MAJOR.md under "Drop dfx coupling".
export function warnIfDfxReplica(
replicaType: ReplicaName,
explicit: boolean,
): void {
if (alreadyWarned) {
return;
}
if (replicaType !== "dfx" && replicaType !== "dfx-pocket-ic") {
return;
}
alreadyWarned = true;
let lead =
explicit && replicaType === "dfx"
? "`--replica dfx` is deprecated and will be removed in a future release."
: replicaType === "dfx-pocket-ic"
? "Falling back to dfx-bundled PocketIC because no `pocket-ic` version is set in `[toolchain]`. This fallback is deprecated and will be removed in a future release."
: "Using `dfx` replica because no `pocket-ic` version is set in `[toolchain]`. The `dfx` replica is deprecated and will be removed in a future release.";
console.log(
chalk.yellow(
`${lead}\nRun \`mops toolchain use pocket-ic 12.0.0\` to pin a PocketIC version and silence this warning.`,
),
);
}
8 changes: 4 additions & 4 deletions docs/docs/cli/4-dev/01-mops-test.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,14 @@ You can also specify `wasi` mode for a specific test file by adding the line bel

Which replica to use to run actor tests.

Default `pocket-ic` if `pocket-ic` is specified in `mops.toml` in `[toolchain]` section, otherwise `dfx`.
Default `pocket-ic` if `pocket-ic` is specified in `mops.toml` in `[toolchain]` section, otherwise `dfx` (deprecated, see below).

Possible values:
- `dfx` - use `dfx` local replica
- `pocket-ic` - use [PocketIC](https://pypi.org/project/pocket-ic/) light replica via [pic.js](https://www.npmjs.com/package/@hadronous/pic) wrapper
- `pocket-ic` - use [PocketIC](https://github.com/dfinity/pocketic) light replica via [pic.js](https://github.com/dfinity/pic-js). Recommended.
- `dfx` - **deprecated**. Uses `dfx` local replica. Will be removed in a future release. Run `mops toolchain use pocket-ic 12.0.0` to pin a PocketIC version and `mops test` will use it directly.

:::info
If you run `mops test --replica pocket-ic` AND `pocket-ic` is not specified in `mops.toml` in `[toolchain]` section, Mops will use pocket-ic replica that comes with dfx (`dfx start --pocketic`).
If you run `mops test --replica pocket-ic` AND `pocket-ic` is not specified in `mops.toml` in `[toolchain]` section, Mops will use pocket-ic replica that comes with dfx (`dfx start --pocketic`). This fallback path is also deprecated.
:::

### `--verbose`
Expand Down
4 changes: 4 additions & 0 deletions docs/docs/cli/4-dev/01-mops-watch.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ Run Motoko tests.
mops watch --test
```

:::info
Replica tests use `pocket-ic` if it's pinned in `mops.toml` under `[toolchain]`, otherwise they fall back to the `dfx` replica, which is **deprecated** and will be removed in a future release. Run `mops toolchain use pocket-ic 12.0.0` to pin a PocketIC version and silence the warning.
:::

### `--generate`

Generate declarations for Motoko canisters from `dfx.json` that have `declarations` field.
Expand Down
6 changes: 3 additions & 3 deletions docs/docs/cli/4-dev/02-mops-bench.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ Under the hood, Mops will:

Which replica to use.

Default `pocket-ic` if `pocket-ic` is specified in `mops.toml` in `[toolchain]` section, otherwise `dfx`.
Default `pocket-ic` if `pocket-ic` is specified in `mops.toml` in `[toolchain]` section, otherwise `dfx` (deprecated, see below).

Possible values:
- `dfx` - use `dfx` local replica
- `pocket-ic` - use [PocketIC](https://pypi.org/project/pocket-ic/) light replica via [pic.js](https://www.npmjs.com/package/@hadronous/pic) wrapper
- `pocket-ic` - use [PocketIC](https://github.com/dfinity/pocketic) light replica via [pic.js](https://github.com/dfinity/pic-js). Recommended.
- `dfx` - **deprecated**. Uses `dfx` local replica. Will be removed in a future release. Run `mops toolchain use pocket-ic 12.0.0` to pin a PocketIC version and `mops bench` will use it directly.

### `--gc`

Expand Down
Loading