ic-mops 2.14.1 → 2.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/bundle/cli.tgz +0 -0
  3. package/cli.ts +149 -4
  4. package/commands/bench-replica.ts +2 -1
  5. package/commands/bench.ts +32 -9
  6. package/commands/build.ts +30 -77
  7. package/commands/check-stable.ts +4 -0
  8. package/commands/check.ts +26 -22
  9. package/commands/deployed.ts +162 -0
  10. package/commands/generate.ts +201 -0
  11. package/commands/lint.ts +6 -3
  12. package/dist/cli.js +76 -4
  13. package/dist/commands/bench-replica.js +3 -1
  14. package/dist/commands/bench.js +21 -4
  15. package/dist/commands/build.d.ts +6 -0
  16. package/dist/commands/build.js +26 -52
  17. package/dist/commands/check-stable.d.ts +2 -0
  18. package/dist/commands/check-stable.js +2 -2
  19. package/dist/commands/check.d.ts +2 -0
  20. package/dist/commands/check.js +20 -21
  21. package/dist/commands/deployed.d.ts +10 -0
  22. package/dist/commands/deployed.js +97 -0
  23. package/dist/commands/generate.d.ts +6 -0
  24. package/dist/commands/generate.js +124 -0
  25. package/dist/commands/lint.d.ts +2 -0
  26. package/dist/commands/lint.js +1 -1
  27. package/dist/helpers/autofix-motoko.d.ts +2 -0
  28. package/dist/helpers/autofix-motoko.js +71 -51
  29. package/dist/helpers/migrations.d.ts +1 -1
  30. package/dist/helpers/migrations.js +8 -4
  31. package/dist/helpers/moc-args.d.ts +25 -0
  32. package/dist/helpers/moc-args.js +58 -0
  33. package/dist/helpers/pocket-ic-client.d.ts +2 -2
  34. package/dist/helpers/pocket-ic-client.js +8 -4
  35. package/dist/package.json +6 -6
  36. package/dist/tests/check-fix-utf8.test.d.ts +1 -0
  37. package/dist/tests/check-fix-utf8.test.js +38 -0
  38. package/dist/tests/check-fix.test.js +14 -2
  39. package/dist/tests/check.test.js +21 -6
  40. package/dist/tests/deployed.test.d.ts +1 -0
  41. package/dist/tests/deployed.test.js +173 -0
  42. package/dist/tests/generate.test.d.ts +1 -0
  43. package/dist/tests/generate.test.js +107 -0
  44. package/dist/tests/helpers.d.ts +8 -0
  45. package/dist/tests/helpers.js +28 -3
  46. package/dist/tests/migrate.test.js +5 -19
  47. package/dist/types.d.ts +3 -0
  48. package/helpers/autofix-motoko.ts +96 -66
  49. package/helpers/migrations.ts +8 -3
  50. package/helpers/moc-args.ts +123 -0
  51. package/helpers/pocket-ic-client.ts +11 -5
  52. package/package.json +6 -6
  53. package/tests/__snapshots__/check-fix-utf8.test.ts.snap +27 -0
  54. package/tests/__snapshots__/check.test.ts.snap +4 -17
  55. package/tests/__snapshots__/deployed.test.ts.snap +10 -0
  56. package/tests/__snapshots__/generate.test.ts.snap +39 -0
  57. package/tests/check/fix-utf8/mops.toml +5 -0
  58. package/tests/check/fix-utf8/multibyte.mo +15 -0
  59. package/tests/check-fix-utf8.test.ts +51 -0
  60. package/tests/check-fix.test.ts +26 -2
  61. package/tests/check.test.ts +24 -11
  62. package/tests/deployed/basic/main.mo +4 -0
  63. package/tests/deployed/basic/mops.toml +8 -0
  64. package/tests/deployed/multi/bar.mo +1 -0
  65. package/tests/deployed/multi/foo.mo +1 -0
  66. package/tests/deployed/multi/mops.toml +11 -0
  67. package/tests/deployed.test.ts +249 -0
  68. package/tests/generate/basic/candid/bar.did +4 -0
  69. package/tests/generate/basic/mops.toml +12 -0
  70. package/tests/generate/basic/src/Bar.mo +5 -0
  71. package/tests/generate/basic/src/Foo.mo +5 -0
  72. package/tests/generate/error/mops.toml +8 -0
  73. package/tests/generate/error/src/Broken.mo +5 -0
  74. package/tests/generate.test.ts +140 -0
  75. package/tests/helpers.ts +31 -3
  76. package/tests/migrate.test.ts +7 -22
  77. package/types.ts +3 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,28 @@
2
2
 
3
3
  ## Next
4
4
 
5
+ ## 2.15.1
6
+
7
+ - `mops check --fix` no longer aborts when a fixable file is read-only (e.g. a frozen migration chain file deliberately `chmod`'d to remove write access). The autofixer now skips such files with a warning and continues fixing the rest, instead of crashing the whole run on `EACCES`/`EPERM`.
8
+
9
+ - Load the PocketIC client lazily, only when a command actually starts a replica. Commands like `mops check`, `mops build`, and `mops install` no longer pay to load it (and its `@icp-sdk/core` dependency) at startup. This also unblocks running the CLI via `tsx` in local dev, where `pic-js-mops` (shipped as ESM without `"type": "module"`) fails to resolve as a static import.
10
+
11
+ - `mops bench --verbose` is now actually verbose. It prints the benchmark pipeline up front — compiler version, replica + version, GC, profile, and whether the wasm is optimized (`dfx` post-optimizes with `optimize: "cycles"` via ic-wasm on deploy; `pocket-ic` runs the raw `moc` output) — logs the full `moc` build command, and streams the compiler and `dfx` output instead of capturing and discarding it. Notably this surfaces dfx's `WARNING: Failed to optimize the Wasm module`, which dfx prints (and then silently deploys the unoptimized module) when `optimize: "cycles"` fails — e.g. on multi-value modules that the bundled ic-wasm can't process. Previously all of this was hidden even with `--verbose`.
12
+
13
+ ## 2.15.0
14
+
15
+ - Fix `mops check --fix` corrupting source on lines containing multi-byte UTF-8 characters (e.g. `Char.toNat32('京')` dropping its trailing `)`). The autofixer was feeding moc's UTF-8 byte columns into LSP's UTF-16 position API, mis-applying every edit past the first non-ASCII byte on the line. When moc emits `byte_start`/`byte_end` (1.10.0 and newer) the fixer now applies edits byte-accurately; older moc still falls back to the line+column path (unchanged behavior, still ASCII-only).
16
+
17
+ - Revert "Speed up `mops check <files...>`" (2.14.1). Passing all files to a single `moc --check` invocation accumulates scope across them: checking `A.mo B.mo` makes `A.mo`'s definitions visible while type-checking `B.mo`, so a file that only compiles because a sibling brings something into scope is wrongly reported as passing. `mops check` again checks each file in its own `moc` invocation so every file is validated in isolation.
18
+
19
+ - Add `--no-check-limit` to `mops check`, `mops check-stable`, and `mops lint` to process the full migration chain for a single run, ignoring the configured `[canisters.<name>.migrations].check-limit`. Handy for `mops check --fix --no-check-limit` to autofix issues in older migrations that the limit normally skips
20
+
21
+ - `--help` now lists every option and the `-- <tool flags>` passthrough for each command: `mops build`, `mops check`, `mops check-stable`, and `mops generate candid` document `-- <moc flags>` (e.g. `mops check -- -Werror`), `mops lint` documents `-- <lintoko flags>`, and the `--verbose` flag of `mops add`/`mops install`/`mops publish` now has a description instead of showing blank
22
+
23
+ - Add `mops deployed` (post-deploy hook) and `mops deployed init` (one-time bootstrap). After a successful deploy, `mops deployed [canisters...]` promotes the built `<build-dir>/<name>.most` into `<deployed-dir>/<name>.most` so `mops check-stable` always compares against the just-deployed version. `mops deployed init` creates an empty-actor `.most` baseline and wires `[canisters.<name>.check-stable].path` to it. Configurable via `[deployed].dir` (default `deployed`) and overridable with `--dir`; the build output dir it reads from defaults to `[build].outputDir` (`.mops/.build`) and is overridable with `--build-dir`.
24
+
25
+ - Add `mops generate candid [canisters...]` to (re)generate the curated `.did` file from current Motoko source. With `[canisters.<name>].candid` set, overwrites that file in place; otherwise writes `<name>.did` next to `main` and sets the field in `mops.toml`. `--output, -o <path>` writes to an arbitrary path (single-canister only) without modifying `mops.toml`. `moc` is invoked with the same packages, `[moc].args`, `[build].args`, per-canister `args`, and migration flags as `mops build`, so the generated interface always satisfies `mops build`'s subtype check.
26
+
5
27
  ## 2.14.1
6
28
  - Speed up `mops check <files...>` (e.g. `mops check src/**/*.mo`) on packages with many files. Previously each file was checked in its own `moc` invocation, so every shared transitive import was re-parsed and re-type-checked once per file. All files are now passed to a single `moc --check` call, which loads and type-checks each import only once — on motoko-core (53 files) this drops a full check from ~27s to ~1.6s. The per-file `✓` confirmations now print only when the whole check passes.
7
29
 
package/bundle/cli.tgz CHANGED
Binary file
package/cli.ts CHANGED
@@ -13,9 +13,11 @@ import { bump } from "./commands/bump.js";
13
13
  import { check } from "./commands/check.js";
14
14
  import { checkCandid } from "./commands/check-candid.js";
15
15
  import { checkStable } from "./commands/check-stable.js";
16
+ import { deployed, deployedInit } from "./commands/deployed.js";
16
17
  import { docsCoverage } from "./commands/docs-coverage.js";
17
18
  import { docs } from "./commands/docs.js";
18
19
  import { format } from "./commands/format.js";
20
+ import { generateCandid } from "./commands/generate.js";
19
21
  import { info } from "./commands/info.js";
20
22
  import { init } from "./commands/init.js";
21
23
  import { lint } from "./commands/lint.js";
@@ -97,6 +99,19 @@ function parseExtraArgs(variadicArgs?: string[]): {
97
99
  return { extraArgs, args };
98
100
  }
99
101
 
102
+ // Shared `--help` section describing the enhanced migration `check-limit`
103
+ // trimming and its override flag, so the limit behaviour is discoverable
104
+ // from `--help`. `withFix` appends the `--fix` hint for commands that support it.
105
+ function enhancedMigrationHelp(withFix: boolean): string {
106
+ const example = withFix ? " (e.g. to --fix older migrations)" : "";
107
+ return (
108
+ "\nEnhanced migration ([canisters.<name>.migrations]):\n" +
109
+ " The canister is checked against its migration chain. [migrations].check-limit\n" +
110
+ " trims it to the last N migrations (older ones are skipped). Pass --no-check-limit\n" +
111
+ ` to use the full chain${example}.`
112
+ );
113
+ }
114
+
100
115
  program.name("mops");
101
116
 
102
117
  // --version
@@ -116,7 +131,7 @@ program
116
131
  .command("add <pkg>")
117
132
  .description("Install the package and save it to mops.toml")
118
133
  .option("--dev", "Add to [dev-dependencies] section")
119
- .option("--verbose")
134
+ .option("--verbose", "Show more information")
120
135
  .addOption(
121
136
  new Option("--lock <action>", "Lockfile action").choices([
122
137
  "update",
@@ -157,7 +172,7 @@ program
157
172
  .alias("i")
158
173
  .description("Install all dependencies specified in mops.toml")
159
174
  .option("--no-toolchain", "Do not install toolchain")
160
- .option("--verbose")
175
+ .option("--verbose", "Show more information")
161
176
  .addOption(
162
177
  new Option("--lock <action>", "Lockfile action").choices([
163
178
  "check",
@@ -200,7 +215,7 @@ program
200
215
  .option("--no-docs", "Do not generate docs")
201
216
  .option("--no-test", "Do not run tests")
202
217
  .option("--no-bench", "Do not run benchmarks")
203
- .option("--verbose")
218
+ .option("--verbose", "Show more information")
204
219
  .action(async (options) => {
205
220
  if (!checkConfigFile()) {
206
221
  process.exit(1);
@@ -313,6 +328,18 @@ program
313
328
  .description("Build a canister")
314
329
  .addOption(new Option("--verbose", "Verbose console output"))
315
330
  .addOption(new Option("--output, -o <output>", "Output directory"))
331
+ .addHelpText(
332
+ "after",
333
+ "\nArguments after -- are forwarded directly to moc, e.g.:\n $ mops build -- -Werror",
334
+ )
335
+ .addHelpText(
336
+ "after",
337
+ "\nEnhanced migration ([canisters.<name>.migrations]):\n" +
338
+ " The canister is built against its full migration chain (every migration is\n" +
339
+ " compiled into the wasm). If mops check passes but mops build fails while\n" +
340
+ " [migrations].check-limit is set, re-run with mops check --no-check-limit to\n" +
341
+ " surface the issue (check trims the chain; build compiles all of it).",
342
+ )
316
343
  .allowUnknownOption(true) // TODO: restrict unknown before "--"
317
344
  .action(async (canisters, options) => {
318
345
  checkConfigFile(true);
@@ -342,6 +369,17 @@ program
342
369
  "Apply autofixes to all files, including transitively imported ones",
343
370
  ),
344
371
  )
372
+ .addOption(
373
+ new Option(
374
+ "--no-check-limit",
375
+ "Check the full migration chain, ignoring [migrations].check-limit",
376
+ ),
377
+ )
378
+ .addHelpText(
379
+ "after",
380
+ "\nArguments after -- are forwarded directly to moc, e.g.:\n $ mops check -- -Werror",
381
+ )
382
+ .addHelpText("after", enhancedMigrationHelp(true))
345
383
  .allowUnknownOption(true)
346
384
  .action(async (args, options) => {
347
385
  checkConfigFile(true);
@@ -378,6 +416,17 @@ program
378
416
  "Check stable variable compatibility. With no arguments, checks all canisters with [check-stable] configured. Arguments can be canister names or an old file path followed by an optional canister name",
379
417
  )
380
418
  .option("--verbose", "Verbose console output")
419
+ .addOption(
420
+ new Option(
421
+ "--no-check-limit",
422
+ "Check the full migration chain, ignoring [migrations].check-limit",
423
+ ),
424
+ )
425
+ .addHelpText(
426
+ "after",
427
+ "\nArguments after -- are forwarded directly to moc, e.g.:\n $ mops check-stable -- -Werror",
428
+ )
429
+ .addHelpText("after", enhancedMigrationHelp(false))
381
430
  .allowUnknownOption(true)
382
431
  .action(async (args, options) => {
383
432
  checkConfigFile(true);
@@ -393,6 +442,47 @@ program
393
442
  });
394
443
  });
395
444
 
445
+ // deployed
446
+ const deployedCommand = new Command("deployed")
447
+ .description(
448
+ "Post-deploy hook: promote .most stable-types files into the deployed directory so `mops check-stable` compares against the just-deployed version. Pass canister names to scope; with no arguments, all canisters in mops.toml are promoted",
449
+ )
450
+ .argument("[canisters...]")
451
+ .addOption(
452
+ new Option(
453
+ "--build-dir <dir>",
454
+ "Directory to read built .most files from (default: [build].outputDir or .mops/.build)",
455
+ ),
456
+ )
457
+ .addOption(
458
+ new Option(
459
+ "--dir <dir>",
460
+ "Destination directory (default: [deployed].dir or deployed)",
461
+ ),
462
+ )
463
+ .action(async (canisters: string[], options) => {
464
+ checkConfigFile(true);
465
+ await deployed(canisters.length ? canisters : undefined, options);
466
+ });
467
+
468
+ deployedCommand
469
+ .command("init [canisters...]")
470
+ .description(
471
+ "Pre-first-deploy bootstrap: create an empty-actor .most baseline in the deployed directory and wire [canisters.<name>.check-stable].path to it. Idempotent",
472
+ )
473
+ .addOption(
474
+ new Option(
475
+ "--dir <dir>",
476
+ "Destination directory (default: [deployed].dir or deployed)",
477
+ ),
478
+ )
479
+ .action(async (canisters: string[], options) => {
480
+ checkConfigFile(true);
481
+ await deployedInit(canisters.length ? canisters : undefined, options);
482
+ });
483
+
484
+ program.addCommand(deployedCommand);
485
+
396
486
  // test
397
487
  program
398
488
  .command("test [filter]")
@@ -453,7 +543,12 @@ program
453
543
  ),
454
544
  )
455
545
  // .addOption(new Option('--force-gc', 'Force GC'))
456
- .addOption(new Option("--verbose", "Show more information"))
546
+ .addOption(
547
+ new Option(
548
+ "--verbose",
549
+ "Print the benchmark pipeline (compiler, replica, GC, optimization) and stream compiler/replica output, including dfx optimization warnings",
550
+ ),
551
+ )
457
552
  .action(async (filter, options) => {
458
553
  checkConfigFile(true);
459
554
  await installAll({
@@ -769,6 +864,44 @@ migrateCommand
769
864
 
770
865
  program.addCommand(migrateCommand);
771
866
 
867
+ // generate
868
+ const generateCommand = new Command("generate")
869
+ .description("Generate source-derived artifacts (Candid, ...)")
870
+ .showHelpAfterError();
871
+
872
+ generateCommand
873
+ .command("candid [canisters...]")
874
+ .description(
875
+ "(Re)generate the curated `.did` file for one or more canisters from current Motoko source. With no canister names, generates for all canisters in mops.toml. When [canisters.<name>].candid is set, overwrites that file; otherwise writes <name>.did next to `main` and sets the field.",
876
+ )
877
+ .addOption(
878
+ new Option(
879
+ "--output, -o <output>",
880
+ "Write the generated .did to <output> (single-canister only; does not touch mops.toml)",
881
+ ),
882
+ )
883
+ .addOption(new Option("--verbose", "Verbose console output"))
884
+ .addHelpText(
885
+ "after",
886
+ "\nArguments after -- are forwarded directly to moc, e.g.:\n $ mops generate candid -- -Werror",
887
+ )
888
+ .allowUnknownOption(true)
889
+ .action(async (canisters, options) => {
890
+ checkConfigFile(true);
891
+ const { extraArgs, args } = parseExtraArgs(canisters);
892
+ await installAll({
893
+ silent: true,
894
+ lock: "ignore",
895
+ installFromLockFile: true,
896
+ });
897
+ await generateCandid(args.length ? args : undefined, {
898
+ ...options,
899
+ extraArgs,
900
+ });
901
+ });
902
+
903
+ program.addCommand(generateCommand);
904
+
772
905
  // self
773
906
  const selfCommand = new Command("self").description("Mops CLI management");
774
907
 
@@ -836,6 +969,17 @@ program
836
969
  "Directories containing rules (can be used multiple times)",
837
970
  ),
838
971
  )
972
+ .addOption(
973
+ new Option(
974
+ "--no-check-limit",
975
+ "Lint the full migration chain, ignoring [migrations].check-limit",
976
+ ),
977
+ )
978
+ .addHelpText(
979
+ "after",
980
+ "\nArguments after -- are forwarded directly to lintoko, e.g.:\n $ mops lint -- --severity warning",
981
+ )
982
+ .addHelpText("after", enhancedMigrationHelp(true))
839
983
  .allowUnknownOption(true)
840
984
  .action(async (filter, options) => {
841
985
  checkConfigFile(true);
@@ -843,6 +987,7 @@ program
843
987
  await lint(filter, {
844
988
  ...options,
845
989
  extraArgs,
990
+ noCheckLimit: options.checkLimit === false,
846
991
  });
847
992
  });
848
993
 
@@ -74,7 +74,8 @@ export class BenchReplica {
74
74
  if (this.type === "dfx" || this.type === "dfx-pocket-ic") {
75
75
  await execaCommand(
76
76
  `dfx deploy ${name} --mode reinstall --yes --identity anonymous`,
77
- { cwd, stdio: this.verbose ? "pipe" : ["pipe", "ignore", "pipe"] },
77
+ // `inherit` so dfx output is streamed under --verbose (incl. its `Failed to optimize` warning)
78
+ { cwd, stdio: this.verbose ? "inherit" : ["pipe", "ignore", "pipe"] },
78
79
  );
79
80
  let canisterId = execSync(`dfx canister id ${name}`, { cwd })
80
81
  .toString()
package/commands/bench.ts CHANGED
@@ -101,7 +101,28 @@ export async function bench(
101
101
 
102
102
  warnIfDfxReplica(replicaType, optionsArg.replica === "dfx");
103
103
 
104
- options.verbose && console.log(options);
104
+ if (options.verbose) {
105
+ // `dfx` post-optimizes the wasm on deploy (`optimize: "cycles"`, via ic-wasm);
106
+ // `pocket-ic` runs the raw moc output. This changes instruction counts, so surface it.
107
+ let optimize =
108
+ replicaType === "dfx" || replicaType === "dfx-pocket-ic"
109
+ ? 'dfx `optimize: "cycles"` (ic-wasm) on deploy'
110
+ : "none (raw moc output)";
111
+ console.log(chalk.gray("Benchmark pipeline:"));
112
+ console.log(chalk.gray(` compiler: moc ${options.compilerVersion}`));
113
+ console.log(
114
+ chalk.gray(
115
+ ` replica: ${replicaType}${options.replicaVersion ? ` ${options.replicaVersion}` : ""}`,
116
+ ),
117
+ );
118
+ console.log(
119
+ chalk.gray(
120
+ ` gc: ${options.gc}${options.forceGc ? " (forced)" : ""}`,
121
+ ),
122
+ );
123
+ console.log(chalk.gray(` profile: ${options.profile}`));
124
+ console.log(chalk.gray(` optimize: ${optimize}`));
125
+ }
105
126
 
106
127
  let replica = new BenchReplica(replicaType, options.verbose);
107
128
 
@@ -303,14 +324,16 @@ async function deployBenchFile(
303
324
  // build canister
304
325
  let mocPath = getMocPath();
305
326
  let mocArgs = getMocArgs(options);
306
- options.verbose && console.time(`build ${canisterName}`);
307
- await execaCommand(
308
- `${mocPath} -c --idl canister.mo ${globalMocArgs.join(" ")} ${mocArgs} ${(await sources({ cwd: tempDir })).join(" ")}`,
309
- {
310
- cwd: tempDir,
311
- stdio: options.verbose ? "pipe" : ["pipe", "ignore", "pipe"],
312
- },
313
- );
327
+ let buildCmd = `${mocPath} -c --idl canister.mo ${globalMocArgs.join(" ")} ${mocArgs} ${(await sources({ cwd: tempDir })).join(" ")}`;
328
+ if (options.verbose) {
329
+ console.log(chalk.gray(`[${canisterName}] ${buildCmd}`));
330
+ console.time(`build ${canisterName}`);
331
+ }
332
+ await execaCommand(buildCmd, {
333
+ cwd: tempDir,
334
+ // `inherit` so the compiler output (warnings/errors) is streamed under --verbose
335
+ stdio: options.verbose ? "inherit" : ["pipe", "ignore", "pipe"],
336
+ });
314
337
  options.verbose && console.timeEnd(`build ${canisterName}`);
315
338
 
316
339
  // deploy canister
package/commands/build.ts CHANGED
@@ -9,13 +9,11 @@ import { isCandidCompatible } from "../helpers/is-candid-compatible.js";
9
9
  import {
10
10
  filterCanisters,
11
11
  resolveCanisterConfigs,
12
- validateCanisterArgs,
13
12
  } from "../helpers/resolve-canisters.js";
14
- import { prepareMigrationArgs } from "../helpers/migrations.js";
15
- import { CanisterConfig, Config } from "../types.js";
13
+ import { BUILD_MANAGED_FLAGS, prepareMocArgs } from "../helpers/moc-args.js";
16
14
  import { CustomSection, getWasmBindings } from "../wasm.js";
17
- import { getGlobalMocArgs, readConfig, resolveConfigPath } from "../mops.js";
18
- import { sourcesArgs } from "./sources.js";
15
+ import { readConfig, resolveConfigPath } from "../mops.js";
16
+ import { Config } from "../types.js";
19
17
  import { toolchain } from "./toolchain/index.js";
20
18
 
21
19
  export interface BuildOptions {
@@ -26,6 +24,22 @@ export interface BuildOptions {
26
24
 
27
25
  export const DEFAULT_BUILD_OUTPUT_DIR = ".mops/.build";
28
26
 
27
+ /**
28
+ * Resolve the build output directory: CLI override → `[build].outputDir`
29
+ * (project-root-relative, resolved via `resolveConfigPath`) → default.
30
+ */
31
+ export function resolveBuildOutputDir(
32
+ config: Config,
33
+ override?: string,
34
+ ): string {
35
+ if (override) {
36
+ return override;
37
+ }
38
+ return config.build?.outputDir
39
+ ? resolveConfigPath(config.build.outputDir)
40
+ : DEFAULT_BUILD_OUTPUT_DIR;
41
+ }
42
+
29
43
  export async function build(
30
44
  canisterNames: string[] | undefined,
31
45
  options: Partial<BuildOptions>,
@@ -35,11 +49,7 @@ export async function build(
35
49
  }
36
50
 
37
51
  let config = readConfig();
38
- let configOutputDir = config.build?.outputDir
39
- ? resolveConfigPath(config.build.outputDir)
40
- : undefined;
41
- let outputDir =
42
- options.outputDir ?? configOutputDir ?? DEFAULT_BUILD_OUTPUT_DIR;
52
+ let outputDir = resolveBuildOutputDir(config, options.outputDir);
43
53
  let mocPath = await toolchain.bin("moc", { fallback: true });
44
54
  let canisters = resolveCanisterConfigs(config);
45
55
  if (!Object.keys(canisters).length) {
@@ -54,11 +64,6 @@ export async function build(
54
64
 
55
65
  for (let [canisterName, canister] of Object.entries(filteredCanisters)) {
56
66
  console.log(chalk.blue("build canister"), chalk.bold(canisterName));
57
- let motokoPath = canister.main;
58
- if (!motokoPath) {
59
- cliError(`No main file is specified for canister ${canisterName}`);
60
- }
61
- motokoPath = resolveConfigPath(motokoPath);
62
67
  const wasmPath = join(outputDir, `${canisterName}.wasm`);
63
68
  const mostPath = join(outputDir, `${canisterName}.most`);
64
69
 
@@ -88,12 +93,13 @@ export async function build(
88
93
  };
89
94
  process.on("exit", exitCleanup);
90
95
 
91
- const migration = await prepareMigrationArgs(
92
- canister.migrations,
93
- canisterName,
94
- "build",
95
- options.verbose,
96
- );
96
+ const prepared = await prepareMocArgs(config, canister, canisterName, {
97
+ mode: "build",
98
+ managedFlags: BUILD_MANAGED_FLAGS,
99
+ commandName: "mops build",
100
+ verbose: options.verbose,
101
+ extraArgs: options.extraArgs,
102
+ });
97
103
  try {
98
104
  let args = [
99
105
  "-c",
@@ -101,14 +107,9 @@ export async function build(
101
107
  "--stable-types",
102
108
  "-o",
103
109
  wasmPath,
104
- motokoPath,
105
- ...(await sourcesArgs()).flat(),
106
- ...getGlobalMocArgs(config),
107
- ...migration.migrationArgs,
110
+ prepared.motokoPath,
111
+ ...prepared.args,
108
112
  ];
109
- args.push(
110
- ...collectExtraArgs(config, canister, canisterName, options.extraArgs),
111
- );
112
113
 
113
114
  const isPublicCandid = true; // always true for now to reduce corner cases
114
115
  const candidVisibility = isPublicCandid ? "icp:public" : "icp:private";
@@ -207,7 +208,7 @@ export async function build(
207
208
  );
208
209
  }
209
210
  } finally {
210
- await migration.cleanup();
211
+ await prepared.cleanup();
211
212
  process.removeListener("exit", exitCleanup);
212
213
  try {
213
214
  await release?.();
@@ -221,51 +222,3 @@ export async function build(
221
222
  ),
222
223
  );
223
224
  }
224
-
225
- const managedFlags: Record<string, string> = {
226
- "-o": "use [build].outputDir in mops.toml or --output flag instead",
227
- "-c": "this flag is always set by mops build",
228
- "--idl": "this flag is always set by mops build",
229
- "--stable-types": "this flag is always set by mops build",
230
- "--public-metadata": "this flag is managed by mops build",
231
- };
232
-
233
- function collectExtraArgs(
234
- config: Config,
235
- canister: CanisterConfig,
236
- canisterName: string,
237
- extraArgs?: string[],
238
- ): string[] {
239
- const args: string[] = [];
240
-
241
- if (config.build?.args) {
242
- if (typeof config.build.args === "string") {
243
- cliError(
244
- `[build] config 'args' should be an array of strings in mops.toml config file`,
245
- );
246
- }
247
- args.push(...config.build.args);
248
- }
249
- if (canister.args) {
250
- validateCanisterArgs(canister, canisterName, config);
251
- args.push(...canister.args);
252
- }
253
- if (extraArgs) {
254
- args.push(...extraArgs);
255
- }
256
-
257
- const warned = new Set<string>();
258
- for (const arg of args) {
259
- const hint = managedFlags[arg];
260
- if (hint && !warned.has(arg)) {
261
- warned.add(arg);
262
- console.warn(
263
- chalk.yellow(
264
- `Warning: '${arg}' in args for canister ${canisterName} may conflict with mops build — ${hint}`,
265
- ),
266
- );
267
- }
268
- }
269
-
270
- return args;
271
- }
@@ -25,6 +25,8 @@ const CHECK_STABLE_PREFIX = ".check-stable-";
25
25
  export interface CheckStableOptions {
26
26
  verbose: boolean;
27
27
  extraArgs: string[];
28
+ /** Commander `--no-check-limit`: false ignores [migrations].check-limit. */
29
+ checkLimit: boolean;
28
30
  }
29
31
 
30
32
  export function resolveStablePath(
@@ -91,6 +93,7 @@ export async function checkStable(
91
93
  name,
92
94
  "check",
93
95
  options.verbose,
96
+ options.checkLimit === false,
94
97
  );
95
98
  try {
96
99
  await runStableCheck({
@@ -132,6 +135,7 @@ export async function checkStable(
132
135
  name,
133
136
  "check",
134
137
  options.verbose,
138
+ options.checkLimit === false,
135
139
  );
136
140
  try {
137
141
  await runStableCheck({
package/commands/check.ts CHANGED
@@ -35,6 +35,8 @@ export interface CheckOptions {
35
35
  verbose: boolean;
36
36
  fix: boolean;
37
37
  extraArgs: string[];
38
+ /** Commander `--no-check-limit`: false ignores [migrations].check-limit. */
39
+ checkLimit: boolean;
38
40
  }
39
41
 
40
42
  function checkAllLibsSupport(verbose?: boolean): boolean {
@@ -135,6 +137,7 @@ async function checkImpl(
135
137
  fix: options.fix,
136
138
  rules: lintRules,
137
139
  files: lintFiles,
140
+ noCheckLimit: options.checkLimit === false,
138
141
  });
139
142
  }
140
143
  }
@@ -164,6 +167,7 @@ async function checkCanisters(
164
167
  canisterName,
165
168
  "check",
166
169
  options.verbose,
170
+ options.checkLimit === false,
167
171
  );
168
172
  try {
169
173
  const mocArgs = [
@@ -262,30 +266,30 @@ async function checkFiles(
262
266
  logAutofixResult(fixResult, options.verbose);
263
267
  }
264
268
 
265
- // Check all files in a single moc invocation so shared transitive imports
266
- // are chased and type-checked once, not re-checked for every file.
267
- const args = [...files, ...mocArgs];
268
- if (options.verbose) {
269
- console.log(chalk.blue("check"), chalk.gray("Running moc:"));
270
- console.log(chalk.gray(mocPath, JSON.stringify(args)));
271
- }
269
+ for (const file of files) {
270
+ try {
271
+ const args = [file, ...mocArgs];
272
+ if (options.verbose) {
273
+ console.log(chalk.blue("check"), chalk.gray("Running moc:"));
274
+ console.log(chalk.gray(mocPath, JSON.stringify(args)));
275
+ }
272
276
 
273
- try {
274
- const result = await execa(mocPath, args, {
275
- stdio: "inherit",
276
- reject: false,
277
- });
277
+ const result = await execa(mocPath, args, {
278
+ stdio: "inherit",
279
+ reject: false,
280
+ });
278
281
 
279
- if (result.exitCode !== 0) {
280
- cliError(`✗ Check failed (exit code: ${result.exitCode})`);
281
- }
282
- } catch (err: any) {
283
- cliError(
284
- `Error while checking files${err?.message ? `\n${err.message}` : ""}`,
285
- );
286
- }
282
+ if (result.exitCode !== 0) {
283
+ cliError(
284
+ `✗ Check failed for file ${file} (exit code: ${result.exitCode})`,
285
+ );
286
+ }
287
287
 
288
- for (const file of files) {
289
- console.log(chalk.green(`✓ ${file}`));
288
+ console.log(chalk.green(`✓ ${file}`));
289
+ } catch (err: any) {
290
+ cliError(
291
+ `Error while checking ${file}${err?.message ? `\n${err.message}` : ""}`,
292
+ );
293
+ }
290
294
  }
291
295
  }