ic-mops 2.14.0 → 2.15.0

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 (67) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/bundle/cli.tgz +0 -0
  3. package/cli.ts +143 -3
  4. package/commands/build.ts +30 -77
  5. package/commands/check-stable.ts +4 -0
  6. package/commands/check.ts +4 -0
  7. package/commands/deployed.ts +162 -0
  8. package/commands/generate.ts +201 -0
  9. package/commands/lint.ts +6 -3
  10. package/dist/cli.js +75 -3
  11. package/dist/commands/build.d.ts +6 -0
  12. package/dist/commands/build.js +26 -52
  13. package/dist/commands/check-stable.d.ts +2 -0
  14. package/dist/commands/check-stable.js +2 -2
  15. package/dist/commands/check.d.ts +2 -0
  16. package/dist/commands/check.js +2 -1
  17. package/dist/commands/deployed.d.ts +10 -0
  18. package/dist/commands/deployed.js +97 -0
  19. package/dist/commands/generate.d.ts +6 -0
  20. package/dist/commands/generate.js +124 -0
  21. package/dist/commands/lint.d.ts +2 -0
  22. package/dist/commands/lint.js +1 -1
  23. package/dist/helpers/autofix-motoko.d.ts +2 -0
  24. package/dist/helpers/autofix-motoko.js +44 -42
  25. package/dist/helpers/migrations.d.ts +1 -1
  26. package/dist/helpers/migrations.js +8 -4
  27. package/dist/helpers/moc-args.d.ts +25 -0
  28. package/dist/helpers/moc-args.js +58 -0
  29. package/dist/package.json +6 -6
  30. package/dist/tests/check-fix-utf8.test.d.ts +1 -0
  31. package/dist/tests/check-fix-utf8.test.js +38 -0
  32. package/dist/tests/check.test.js +21 -0
  33. package/dist/tests/deployed.test.d.ts +1 -0
  34. package/dist/tests/deployed.test.js +173 -0
  35. package/dist/tests/generate.test.d.ts +1 -0
  36. package/dist/tests/generate.test.js +107 -0
  37. package/dist/tests/helpers.d.ts +8 -0
  38. package/dist/tests/helpers.js +28 -3
  39. package/dist/tests/migrate.test.js +5 -19
  40. package/dist/types.d.ts +3 -0
  41. package/helpers/autofix-motoko.ts +60 -52
  42. package/helpers/migrations.ts +8 -3
  43. package/helpers/moc-args.ts +123 -0
  44. package/package.json +6 -6
  45. package/tests/__snapshots__/check-fix-utf8.test.ts.snap +27 -0
  46. package/tests/__snapshots__/deployed.test.ts.snap +10 -0
  47. package/tests/__snapshots__/generate.test.ts.snap +39 -0
  48. package/tests/check/fix-utf8/mops.toml +5 -0
  49. package/tests/check/fix-utf8/multibyte.mo +15 -0
  50. package/tests/check-fix-utf8.test.ts +51 -0
  51. package/tests/check.test.ts +24 -0
  52. package/tests/deployed/basic/main.mo +4 -0
  53. package/tests/deployed/basic/mops.toml +8 -0
  54. package/tests/deployed/multi/bar.mo +1 -0
  55. package/tests/deployed/multi/foo.mo +1 -0
  56. package/tests/deployed/multi/mops.toml +11 -0
  57. package/tests/deployed.test.ts +249 -0
  58. package/tests/generate/basic/candid/bar.did +4 -0
  59. package/tests/generate/basic/mops.toml +12 -0
  60. package/tests/generate/basic/src/Bar.mo +5 -0
  61. package/tests/generate/basic/src/Foo.mo +5 -0
  62. package/tests/generate/error/mops.toml +8 -0
  63. package/tests/generate/error/src/Broken.mo +5 -0
  64. package/tests/generate.test.ts +140 -0
  65. package/tests/helpers.ts +31 -3
  66. package/tests/migrate.test.ts +7 -22
  67. package/types.ts +3 -0
@@ -0,0 +1,201 @@
1
+ import chalk from "chalk";
2
+ import { execa } from "execa";
3
+ import { mkdir } from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { cliError } from "../error.js";
6
+ import {
7
+ filterCanisters,
8
+ resolveCanisterConfigs,
9
+ } from "../helpers/resolve-canisters.js";
10
+ import {
11
+ GENERATE_CANDID_MANAGED_FLAGS,
12
+ prepareMocArgs,
13
+ } from "../helpers/moc-args.js";
14
+ import {
15
+ getRootDir,
16
+ readConfig,
17
+ resolveConfigPath,
18
+ writeConfig,
19
+ } from "../mops.js";
20
+ import { CanisterConfig } from "../types.js";
21
+ import { toolchain } from "./toolchain/index.js";
22
+
23
+ export interface GenerateCandidOptions {
24
+ output?: string;
25
+ verbose?: boolean;
26
+ extraArgs?: string[];
27
+ }
28
+
29
+ export async function generateCandid(
30
+ canisterNames: string[] | undefined,
31
+ options: GenerateCandidOptions,
32
+ ): Promise<void> {
33
+ if (canisterNames?.length === 0) {
34
+ cliError("No canisters specified");
35
+ }
36
+
37
+ const config = readConfig();
38
+ const canisters = resolveCanisterConfigs(config);
39
+ if (!Object.keys(canisters).length) {
40
+ cliError("No Motoko canisters found in mops.toml configuration");
41
+ }
42
+
43
+ const filtered = filterCanisters(canisters, canisterNames);
44
+ const filteredEntries = Object.entries(filtered);
45
+
46
+ if (options.output && filteredEntries.length > 1) {
47
+ cliError(
48
+ "--output / -o is only supported when generating for a single canister",
49
+ );
50
+ }
51
+
52
+ const mocPath = await toolchain.bin("moc", { fallback: true });
53
+ const rootDir = getRootDir();
54
+
55
+ let configChanged = false;
56
+
57
+ for (const [canisterName, canister] of filteredEntries) {
58
+ const dest = resolveDestination(
59
+ canisterName,
60
+ canister,
61
+ canisters,
62
+ options.output,
63
+ rootDir,
64
+ );
65
+
66
+ console.log(
67
+ chalk.blue("generate candid"),
68
+ chalk.bold(canisterName),
69
+ chalk.gray(`→ ${dest.fsPath}`),
70
+ );
71
+
72
+ const prepared = await prepareMocArgs(config, canister, canisterName, {
73
+ mode: "build",
74
+ managedFlags: GENERATE_CANDID_MANAGED_FLAGS,
75
+ commandName: "mops generate candid",
76
+ verbose: options.verbose,
77
+ extraArgs: options.extraArgs,
78
+ });
79
+
80
+ try {
81
+ await mkdir(path.dirname(dest.fsPath), { recursive: true });
82
+ const args = [
83
+ "--idl",
84
+ "-o",
85
+ dest.fsPath,
86
+ prepared.motokoPath,
87
+ ...prepared.args,
88
+ ];
89
+ if (options.verbose) {
90
+ console.log(chalk.gray(mocPath, JSON.stringify(args)));
91
+ }
92
+ const result = await execa(mocPath, args, {
93
+ stdio: options.verbose ? "inherit" : "pipe",
94
+ reject: false,
95
+ });
96
+
97
+ if (result.exitCode !== 0) {
98
+ if (!options.verbose) {
99
+ if (result.stderr) {
100
+ console.error(chalk.red(result.stderr));
101
+ }
102
+ if (result.stdout?.trim()) {
103
+ console.error(chalk.yellow("Output:"));
104
+ console.error(result.stdout);
105
+ }
106
+ }
107
+ cliError(
108
+ `Failed to generate Candid for canister ${canisterName} (exit code: ${result.exitCode})`,
109
+ );
110
+ }
111
+
112
+ if (dest.configPath !== null) {
113
+ const c = (config.canisters ??= {});
114
+ const existing = c[canisterName];
115
+ const obj: CanisterConfig =
116
+ typeof existing === "string"
117
+ ? { main: existing }
118
+ : { ...(existing ?? {}) };
119
+ obj.candid = dest.configPath;
120
+ c[canisterName] = obj;
121
+ configChanged = true;
122
+ }
123
+ } finally {
124
+ await prepared.cleanup();
125
+ }
126
+ }
127
+
128
+ if (configChanged) {
129
+ writeConfig(config);
130
+ }
131
+
132
+ console.log(
133
+ chalk.green(
134
+ `\n✓ Generated Candid for ${filteredEntries.length} canister${filteredEntries.length === 1 ? "" : "s"}`,
135
+ ),
136
+ );
137
+ }
138
+
139
+ interface Destination {
140
+ /** Path used for filesystem operations and passed to moc (cwd-relative or absolute). */
141
+ fsPath: string;
142
+ /** Value to write into `[canisters.<name>].candid` (project-root-relative). `null` skips the config update. */
143
+ configPath: string | null;
144
+ }
145
+
146
+ function resolveDestination(
147
+ canisterName: string,
148
+ canister: CanisterConfig,
149
+ allCanisters: Record<string, CanisterConfig>,
150
+ outputFlag: string | undefined,
151
+ rootDir: string,
152
+ ): Destination {
153
+ if (!canister.main) {
154
+ cliError(`No main file is specified for canister ${canisterName}`);
155
+ }
156
+
157
+ let fsPath: string;
158
+ let configPath: string | null;
159
+
160
+ if (outputFlag) {
161
+ fsPath = outputFlag;
162
+ configPath = null;
163
+
164
+ const outAbs = path.resolve(fsPath);
165
+ for (const [otherName, other] of Object.entries(allCanisters)) {
166
+ if (otherName === canisterName || !other.candid) {
167
+ continue;
168
+ }
169
+ if (path.resolve(rootDir, other.candid) === outAbs) {
170
+ console.warn(
171
+ chalk.yellow(
172
+ `Warning: --output path collides with [canisters.${otherName}].candid (${other.candid}). Sharing a .did between canisters is almost always a mistake.`,
173
+ ),
174
+ );
175
+ }
176
+ }
177
+ } else if (canister.candid) {
178
+ fsPath = resolveConfigPath(canister.candid);
179
+ configPath = null;
180
+ } else {
181
+ // Default: <dirname(main)>/<canisterName>.did, forward slashes for the toml value
182
+ const mainDir = path.dirname(canister.main).replace(/\\/g, "/");
183
+ const projectRel =
184
+ mainDir === "." || mainDir === ""
185
+ ? `${canisterName}.did`
186
+ : `${mainDir}/${canisterName}.did`;
187
+ fsPath = resolveConfigPath(projectRel);
188
+ configPath = projectRel;
189
+ }
190
+
191
+ const absPath = path.resolve(fsPath);
192
+ const dotMopsDir = path.resolve(rootDir, ".mops");
193
+ if (absPath === dotMopsDir || absPath.startsWith(dotMopsDir + path.sep)) {
194
+ cliError(
195
+ `Refusing to write Candid file inside .mops/ (private build cache): ${fsPath}\n` +
196
+ "Choose a path outside .mops/ — it should be committable and readable by downstream tooling.",
197
+ );
198
+ }
199
+
200
+ return { fsPath, configPath };
201
+ }
package/commands/lint.ts CHANGED
@@ -103,6 +103,8 @@ export interface LintOptions {
103
103
  rules?: string[];
104
104
  files?: string[];
105
105
  extraArgs: string[];
106
+ /** Commander `--no-check-limit`: lint the full migration chain. */
107
+ noCheckLimit?: boolean;
106
108
  }
107
109
 
108
110
  function buildCommonArgs(
@@ -193,9 +195,10 @@ async function lintImpl(
193
195
  : "lintoko";
194
196
 
195
197
  const isExplicit = !!filter || !!(options.files && options.files.length > 0);
196
- const trimmedMigrations = isExplicit
197
- ? new Set<string>()
198
- : getTrimmedMigrationFiles(config);
198
+ const trimmedMigrations =
199
+ isExplicit || options.noCheckLimit
200
+ ? new Set<string>()
201
+ : getTrimmedMigrationFiles(config);
199
202
 
200
203
  let filesToLint: string[];
201
204
  if (options.files && options.files.length > 0) {
package/dist/cli.js CHANGED
@@ -12,9 +12,11 @@ import { bump } from "./commands/bump.js";
12
12
  import { check } from "./commands/check.js";
13
13
  import { checkCandid } from "./commands/check-candid.js";
14
14
  import { checkStable } from "./commands/check-stable.js";
15
+ import { deployed, deployedInit } from "./commands/deployed.js";
15
16
  import { docsCoverage } from "./commands/docs-coverage.js";
16
17
  import { docs } from "./commands/docs.js";
17
18
  import { format } from "./commands/format.js";
19
+ import { generateCandid } from "./commands/generate.js";
18
20
  import { info } from "./commands/info.js";
19
21
  import { init } from "./commands/init.js";
20
22
  import { lint } from "./commands/lint.js";
@@ -60,6 +62,16 @@ function parseExtraArgs(variadicArgs) {
60
62
  : [];
61
63
  return { extraArgs, args };
62
64
  }
65
+ // Shared `--help` section describing the enhanced migration `check-limit`
66
+ // trimming and its override flag, so the limit behaviour is discoverable
67
+ // from `--help`. `withFix` appends the `--fix` hint for commands that support it.
68
+ function enhancedMigrationHelp(withFix) {
69
+ const example = withFix ? " (e.g. to --fix older migrations)" : "";
70
+ return ("\nEnhanced migration ([canisters.<name>.migrations]):\n" +
71
+ " The canister is checked against its migration chain. [migrations].check-limit\n" +
72
+ " trims it to the last N migrations (older ones are skipped). Pass --no-check-limit\n" +
73
+ ` to use the full chain${example}.`);
74
+ }
63
75
  program.name("mops");
64
76
  // --version
65
77
  program.version(`CLI ${version()}\nAPI ${apiVersion}`, "-v --version");
@@ -76,7 +88,7 @@ program
76
88
  .command("add <pkg>")
77
89
  .description("Install the package and save it to mops.toml")
78
90
  .option("--dev", "Add to [dev-dependencies] section")
79
- .option("--verbose")
91
+ .option("--verbose", "Show more information")
80
92
  .addOption(new Option("--lock <action>", "Lockfile action").choices([
81
93
  "update",
82
94
  "ignore",
@@ -111,7 +123,7 @@ program
111
123
  .alias("i")
112
124
  .description("Install all dependencies specified in mops.toml")
113
125
  .option("--no-toolchain", "Do not install toolchain")
114
- .option("--verbose")
126
+ .option("--verbose", "Show more information")
115
127
  .addOption(new Option("--lock <action>", "Lockfile action").choices([
116
128
  "check",
117
129
  "update",
@@ -145,7 +157,7 @@ program
145
157
  .option("--no-docs", "Do not generate docs")
146
158
  .option("--no-test", "Do not run tests")
147
159
  .option("--no-bench", "Do not run benchmarks")
148
- .option("--verbose")
160
+ .option("--verbose", "Show more information")
149
161
  .action(async (options) => {
150
162
  if (!checkConfigFile()) {
151
163
  process.exit(1);
@@ -247,6 +259,12 @@ program
247
259
  .description("Build a canister")
248
260
  .addOption(new Option("--verbose", "Verbose console output"))
249
261
  .addOption(new Option("--output, -o <output>", "Output directory"))
262
+ .addHelpText("after", "\nArguments after -- are forwarded directly to moc, e.g.:\n $ mops build -- -Werror")
263
+ .addHelpText("after", "\nEnhanced migration ([canisters.<name>.migrations]):\n" +
264
+ " The canister is built against its full migration chain (every migration is\n" +
265
+ " compiled into the wasm). If mops check passes but mops build fails while\n" +
266
+ " [migrations].check-limit is set, re-run with mops check --no-check-limit to\n" +
267
+ " surface the issue (check trims the chain; build compiles all of it).")
250
268
  .allowUnknownOption(true) // TODO: restrict unknown before "--"
251
269
  .action(async (canisters, options) => {
252
270
  checkConfigFile(true);
@@ -268,6 +286,9 @@ program
268
286
  .description("Check Motoko canisters or files for syntax errors and type issues. Arguments can be canister names or file paths. If no arguments are given, checks all canisters from mops.toml. Also runs stable compatibility checks for canisters with [check-stable] configured, and runs linting if lintoko is configured in [toolchain]")
269
287
  .option("--verbose", "Verbose console output")
270
288
  .addOption(new Option("--fix", "Apply autofixes to all files, including transitively imported ones"))
289
+ .addOption(new Option("--no-check-limit", "Check the full migration chain, ignoring [migrations].check-limit"))
290
+ .addHelpText("after", "\nArguments after -- are forwarded directly to moc, e.g.:\n $ mops check -- -Werror")
291
+ .addHelpText("after", enhancedMigrationHelp(true))
271
292
  .allowUnknownOption(true)
272
293
  .action(async (args, options) => {
273
294
  checkConfigFile(true);
@@ -300,6 +321,9 @@ program
300
321
  .command("check-stable [args...]")
301
322
  .description("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")
302
323
  .option("--verbose", "Verbose console output")
324
+ .addOption(new Option("--no-check-limit", "Check the full migration chain, ignoring [migrations].check-limit"))
325
+ .addHelpText("after", "\nArguments after -- are forwarded directly to moc, e.g.:\n $ mops check-stable -- -Werror")
326
+ .addHelpText("after", enhancedMigrationHelp(false))
303
327
  .allowUnknownOption(true)
304
328
  .action(async (args, options) => {
305
329
  checkConfigFile(true);
@@ -314,6 +338,25 @@ program
314
338
  extraArgs,
315
339
  });
316
340
  });
341
+ // deployed
342
+ const deployedCommand = new Command("deployed")
343
+ .description("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")
344
+ .argument("[canisters...]")
345
+ .addOption(new Option("--build-dir <dir>", "Directory to read built .most files from (default: [build].outputDir or .mops/.build)"))
346
+ .addOption(new Option("--dir <dir>", "Destination directory (default: [deployed].dir or deployed)"))
347
+ .action(async (canisters, options) => {
348
+ checkConfigFile(true);
349
+ await deployed(canisters.length ? canisters : undefined, options);
350
+ });
351
+ deployedCommand
352
+ .command("init [canisters...]")
353
+ .description("Pre-first-deploy bootstrap: create an empty-actor .most baseline in the deployed directory and wire [canisters.<name>.check-stable].path to it. Idempotent")
354
+ .addOption(new Option("--dir <dir>", "Destination directory (default: [deployed].dir or deployed)"))
355
+ .action(async (canisters, options) => {
356
+ checkConfigFile(true);
357
+ await deployedInit(canisters.length ? canisters : undefined, options);
358
+ });
359
+ program.addCommand(deployedCommand);
317
360
  // test
318
361
  program
319
362
  .command("test [filter]")
@@ -574,6 +617,31 @@ migrateCommand
574
617
  await migrateFreeze(canister);
575
618
  });
576
619
  program.addCommand(migrateCommand);
620
+ // generate
621
+ const generateCommand = new Command("generate")
622
+ .description("Generate source-derived artifacts (Candid, ...)")
623
+ .showHelpAfterError();
624
+ generateCommand
625
+ .command("candid [canisters...]")
626
+ .description("(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.")
627
+ .addOption(new Option("--output, -o <output>", "Write the generated .did to <output> (single-canister only; does not touch mops.toml)"))
628
+ .addOption(new Option("--verbose", "Verbose console output"))
629
+ .addHelpText("after", "\nArguments after -- are forwarded directly to moc, e.g.:\n $ mops generate candid -- -Werror")
630
+ .allowUnknownOption(true)
631
+ .action(async (canisters, options) => {
632
+ checkConfigFile(true);
633
+ const { extraArgs, args } = parseExtraArgs(canisters);
634
+ await installAll({
635
+ silent: true,
636
+ lock: "ignore",
637
+ installFromLockFile: true,
638
+ });
639
+ await generateCandid(args.length ? args : undefined, {
640
+ ...options,
641
+ extraArgs,
642
+ });
643
+ });
644
+ program.addCommand(generateCommand);
577
645
  // self
578
646
  const selfCommand = new Command("self").description("Mops CLI management");
579
647
  selfCommand
@@ -623,6 +691,9 @@ program
623
691
  .addOption(new Option("--verbose", "Verbose output"))
624
692
  .addOption(new Option("--fix", "Apply fixes"))
625
693
  .addOption(new Option("-r, --rules <directory...>", "Directories containing rules (can be used multiple times)"))
694
+ .addOption(new Option("--no-check-limit", "Lint the full migration chain, ignoring [migrations].check-limit"))
695
+ .addHelpText("after", "\nArguments after -- are forwarded directly to lintoko, e.g.:\n $ mops lint -- --severity warning")
696
+ .addHelpText("after", enhancedMigrationHelp(true))
626
697
  .allowUnknownOption(true)
627
698
  .action(async (filter, options) => {
628
699
  checkConfigFile(true);
@@ -630,6 +701,7 @@ program
630
701
  await lint(filter, {
631
702
  ...options,
632
703
  extraArgs,
704
+ noCheckLimit: options.checkLimit === false,
633
705
  });
634
706
  });
635
707
  // docs
@@ -1,7 +1,13 @@
1
+ import { Config } from "../types.js";
1
2
  export interface BuildOptions {
2
3
  outputDir: string;
3
4
  verbose: boolean;
4
5
  extraArgs: string[];
5
6
  }
6
7
  export declare const DEFAULT_BUILD_OUTPUT_DIR = ".mops/.build";
8
+ /**
9
+ * Resolve the build output directory: CLI override → `[build].outputDir`
10
+ * (project-root-relative, resolved via `resolveConfigPath`) → default.
11
+ */
12
+ export declare function resolveBuildOutputDir(config: Config, override?: string): string;
7
13
  export declare function build(canisterNames: string[] | undefined, options: Partial<BuildOptions>): Promise<void>;
@@ -6,22 +6,30 @@ import { join } from "node:path";
6
6
  import { lock, unlockSync } from "proper-lockfile";
7
7
  import { cliError } from "../error.js";
8
8
  import { isCandidCompatible } from "../helpers/is-candid-compatible.js";
9
- import { filterCanisters, resolveCanisterConfigs, validateCanisterArgs, } from "../helpers/resolve-canisters.js";
10
- import { prepareMigrationArgs } from "../helpers/migrations.js";
9
+ import { filterCanisters, resolveCanisterConfigs, } from "../helpers/resolve-canisters.js";
10
+ import { BUILD_MANAGED_FLAGS, prepareMocArgs } from "../helpers/moc-args.js";
11
11
  import { getWasmBindings } from "../wasm.js";
12
- import { getGlobalMocArgs, readConfig, resolveConfigPath } from "../mops.js";
13
- import { sourcesArgs } from "./sources.js";
12
+ import { readConfig, resolveConfigPath } from "../mops.js";
14
13
  import { toolchain } from "./toolchain/index.js";
15
14
  export const DEFAULT_BUILD_OUTPUT_DIR = ".mops/.build";
15
+ /**
16
+ * Resolve the build output directory: CLI override → `[build].outputDir`
17
+ * (project-root-relative, resolved via `resolveConfigPath`) → default.
18
+ */
19
+ export function resolveBuildOutputDir(config, override) {
20
+ if (override) {
21
+ return override;
22
+ }
23
+ return config.build?.outputDir
24
+ ? resolveConfigPath(config.build.outputDir)
25
+ : DEFAULT_BUILD_OUTPUT_DIR;
26
+ }
16
27
  export async function build(canisterNames, options) {
17
28
  if (canisterNames?.length === 0) {
18
29
  cliError("No canisters specified to build");
19
30
  }
20
31
  let config = readConfig();
21
- let configOutputDir = config.build?.outputDir
22
- ? resolveConfigPath(config.build.outputDir)
23
- : undefined;
24
- let outputDir = options.outputDir ?? configOutputDir ?? DEFAULT_BUILD_OUTPUT_DIR;
32
+ let outputDir = resolveBuildOutputDir(config, options.outputDir);
25
33
  let mocPath = await toolchain.bin("moc", { fallback: true });
26
34
  let canisters = resolveCanisterConfigs(config);
27
35
  if (!Object.keys(canisters).length) {
@@ -33,11 +41,6 @@ export async function build(canisterNames, options) {
33
41
  const filteredCanisters = filterCanisters(canisters, canisterNames);
34
42
  for (let [canisterName, canister] of Object.entries(filteredCanisters)) {
35
43
  console.log(chalk.blue("build canister"), chalk.bold(canisterName));
36
- let motokoPath = canister.main;
37
- if (!motokoPath) {
38
- cliError(`No main file is specified for canister ${canisterName}`);
39
- }
40
- motokoPath = resolveConfigPath(motokoPath);
41
44
  const wasmPath = join(outputDir, `${canisterName}.wasm`);
42
45
  const mostPath = join(outputDir, `${canisterName}.most`);
43
46
  // per-canister lock to prevent parallel builds of the same canister from clobbering output files
@@ -63,7 +66,13 @@ export async function build(canisterNames, options) {
63
66
  catch { }
64
67
  };
65
68
  process.on("exit", exitCleanup);
66
- const migration = await prepareMigrationArgs(canister.migrations, canisterName, "build", options.verbose);
69
+ const prepared = await prepareMocArgs(config, canister, canisterName, {
70
+ mode: "build",
71
+ managedFlags: BUILD_MANAGED_FLAGS,
72
+ commandName: "mops build",
73
+ verbose: options.verbose,
74
+ extraArgs: options.extraArgs,
75
+ });
67
76
  try {
68
77
  let args = [
69
78
  "-c",
@@ -71,12 +80,9 @@ export async function build(canisterNames, options) {
71
80
  "--stable-types",
72
81
  "-o",
73
82
  wasmPath,
74
- motokoPath,
75
- ...(await sourcesArgs()).flat(),
76
- ...getGlobalMocArgs(config),
77
- ...migration.migrationArgs,
83
+ prepared.motokoPath,
84
+ ...prepared.args,
78
85
  ];
79
- args.push(...collectExtraArgs(config, canister, canisterName, options.extraArgs));
80
86
  const isPublicCandid = true; // always true for now to reduce corner cases
81
87
  const candidVisibility = isPublicCandid ? "icp:public" : "icp:private";
82
88
  if (isPublicCandid) {
@@ -151,7 +157,7 @@ export async function build(canisterNames, options) {
151
157
  }
152
158
  }
153
159
  finally {
154
- await migration.cleanup();
160
+ await prepared.cleanup();
155
161
  process.removeListener("exit", exitCleanup);
156
162
  try {
157
163
  await release?.();
@@ -161,35 +167,3 @@ export async function build(canisterNames, options) {
161
167
  }
162
168
  console.log(chalk.green(`\n✓ Built ${Object.keys(filteredCanisters).length} canister${Object.keys(filteredCanisters).length === 1 ? "" : "s"} successfully`));
163
169
  }
164
- const managedFlags = {
165
- "-o": "use [build].outputDir in mops.toml or --output flag instead",
166
- "-c": "this flag is always set by mops build",
167
- "--idl": "this flag is always set by mops build",
168
- "--stable-types": "this flag is always set by mops build",
169
- "--public-metadata": "this flag is managed by mops build",
170
- };
171
- function collectExtraArgs(config, canister, canisterName, extraArgs) {
172
- const args = [];
173
- if (config.build?.args) {
174
- if (typeof config.build.args === "string") {
175
- cliError(`[build] config 'args' should be an array of strings in mops.toml config file`);
176
- }
177
- args.push(...config.build.args);
178
- }
179
- if (canister.args) {
180
- validateCanisterArgs(canister, canisterName, config);
181
- args.push(...canister.args);
182
- }
183
- if (extraArgs) {
184
- args.push(...extraArgs);
185
- }
186
- const warned = new Set();
187
- for (const arg of args) {
188
- const hint = managedFlags[arg];
189
- if (hint && !warned.has(arg)) {
190
- warned.add(arg);
191
- console.warn(chalk.yellow(`Warning: '${arg}' in args for canister ${canisterName} may conflict with mops build — ${hint}`));
192
- }
193
- }
194
- return args;
195
- }
@@ -2,6 +2,8 @@ import { CanisterConfig } from "../types.js";
2
2
  export interface CheckStableOptions {
3
3
  verbose: boolean;
4
4
  extraArgs: string[];
5
+ /** Commander `--no-check-limit`: false ignores [migrations].check-limit. */
6
+ checkLimit: boolean;
5
7
  }
6
8
  export declare function resolveStablePath(canister: CanisterConfig, canisterName: string, options?: {
7
9
  required?: boolean;
@@ -52,7 +52,7 @@ export async function checkStable(args, options = {}) {
52
52
  cliError(`No main file specified for canister '${name}' in mops.toml`);
53
53
  }
54
54
  validateCanisterArgs(canister, name, config);
55
- const migration = await prepareMigrationArgs(canister.migrations, name, "check", options.verbose);
55
+ const migration = await prepareMigrationArgs(canister.migrations, name, "check", options.verbose, options.checkLimit === false);
56
56
  try {
57
57
  await runStableCheck({
58
58
  oldFile,
@@ -85,7 +85,7 @@ export async function checkStable(args, options = {}) {
85
85
  if (!stablePath) {
86
86
  continue;
87
87
  }
88
- const migration = await prepareMigrationArgs(canister.migrations, name, "check", options.verbose);
88
+ const migration = await prepareMigrationArgs(canister.migrations, name, "check", options.verbose, options.checkLimit === false);
89
89
  try {
90
90
  await runStableCheck({
91
91
  oldFile: stablePath,
@@ -2,5 +2,7 @@ export interface CheckOptions {
2
2
  verbose: boolean;
3
3
  fix: boolean;
4
4
  extraArgs: string[];
5
+ /** Commander `--no-check-limit`: false ignores [migrations].check-limit. */
6
+ checkLimit: boolean;
5
7
  }
6
8
  export declare function check(args: string[], options?: Partial<CheckOptions>): Promise<void>;
@@ -83,6 +83,7 @@ async function checkImpl(args, options = {}) {
83
83
  fix: options.fix,
84
84
  rules: lintRules,
85
85
  files: lintFiles,
86
+ noCheckLimit: options.checkLimit === false,
86
87
  });
87
88
  }
88
89
  }
@@ -97,7 +98,7 @@ async function checkCanisters(config, canisters, options) {
97
98
  }
98
99
  validateCanisterArgs(canister, canisterName, config);
99
100
  const motokoPath = resolveConfigPath(canister.main);
100
- const migration = await prepareMigrationArgs(canister.migrations, canisterName, "check", options.verbose);
101
+ const migration = await prepareMigrationArgs(canister.migrations, canisterName, "check", options.verbose, options.checkLimit === false);
101
102
  try {
102
103
  const mocArgs = [
103
104
  "--check",
@@ -0,0 +1,10 @@
1
+ export declare const DEFAULT_DEPLOYED_DIR = "deployed";
2
+ export interface DeployedOptions {
3
+ buildDir?: string;
4
+ dir?: string;
5
+ }
6
+ export interface DeployedInitOptions {
7
+ dir?: string;
8
+ }
9
+ export declare function deployed(canisterNames: string[] | undefined, options?: DeployedOptions): Promise<void>;
10
+ export declare function deployedInit(canisterNames: string[] | undefined, options?: DeployedInitOptions): Promise<void>;