ic-mops 2.9.0 → 2.11.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 (61) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/bundle/cli.tgz +0 -0
  3. package/cli.ts +34 -10
  4. package/commands/build.ts +16 -22
  5. package/commands/check-stable.ts +141 -17
  6. package/commands/check.ts +195 -101
  7. package/commands/migrate.ts +165 -0
  8. package/declarations/main/main.did +38 -0
  9. package/declarations/main/main.did.d.ts +36 -0
  10. package/declarations/main/main.did.js +36 -0
  11. package/dist/cli.js +28 -10
  12. package/dist/commands/build.js +7 -13
  13. package/dist/commands/check-stable.d.ts +8 -1
  14. package/dist/commands/check-stable.js +101 -19
  15. package/dist/commands/check.d.ts +1 -1
  16. package/dist/commands/check.js +136 -78
  17. package/dist/commands/migrate.d.ts +2 -0
  18. package/dist/commands/migrate.js +104 -0
  19. package/dist/declarations/main/main.did +38 -0
  20. package/dist/declarations/main/main.did.d.ts +36 -0
  21. package/dist/declarations/main/main.did.js +36 -0
  22. package/dist/helpers/migrations.d.ts +10 -0
  23. package/dist/helpers/migrations.js +109 -0
  24. package/dist/helpers/resolve-canisters.d.ts +3 -1
  25. package/dist/helpers/resolve-canisters.js +34 -5
  26. package/dist/package.json +1 -1
  27. package/dist/tests/check-stable.test.js +18 -0
  28. package/dist/tests/check.test.js +23 -5
  29. package/dist/tests/migrate.test.d.ts +1 -0
  30. package/dist/tests/migrate.test.js +160 -0
  31. package/dist/types.d.ts +7 -0
  32. package/helpers/migrations.ts +166 -0
  33. package/helpers/resolve-canisters.ts +53 -5
  34. package/package.json +1 -1
  35. package/tests/__snapshots__/check.test.ts.snap +2 -2
  36. package/tests/__snapshots__/migrate.test.ts.snap +119 -0
  37. package/tests/check/canisters-canister-args/Warning.mo +5 -0
  38. package/tests/check/canisters-canister-args/mops.toml +9 -0
  39. package/tests/check-stable/canister-args/migrations/20250101_000000_Init.mo +8 -0
  40. package/tests/check-stable/canister-args/migrations/20250201_000000_AddField.mo +9 -0
  41. package/tests/check-stable/canister-args/mops.toml +9 -0
  42. package/tests/check-stable/canister-args/old.most +8 -0
  43. package/tests/check-stable/canister-args/src/main.mo +11 -0
  44. package/tests/check-stable.test.ts +21 -0
  45. package/tests/check.test.ts +26 -5
  46. package/tests/migrate/basic/deployed.most +12 -0
  47. package/tests/migrate/basic/migrations/20250101_000000_Init.mo +5 -0
  48. package/tests/migrate/basic/migrations/20250201_000000_AddName.mo +5 -0
  49. package/tests/migrate/basic/migrations/20250301_000000_AddEmail.mo +9 -0
  50. package/tests/migrate/basic/mops.toml +15 -0
  51. package/tests/migrate/basic/next-migration/.gitkeep +0 -0
  52. package/tests/migrate/basic/src/main.mo +11 -0
  53. package/tests/migrate/with-next/deployed.most +12 -0
  54. package/tests/migrate/with-next/migrations/20250101_000000_Init.mo +5 -0
  55. package/tests/migrate/with-next/migrations/20250201_000000_AddName.mo +5 -0
  56. package/tests/migrate/with-next/migrations/20250301_000000_AddEmail.mo +9 -0
  57. package/tests/migrate/with-next/mops.toml +15 -0
  58. package/tests/migrate/with-next/next-migration/20250401_000000_RenameId.mo +9 -0
  59. package/tests/migrate/with-next/src/main.mo +11 -0
  60. package/tests/migrate.test.ts +228 -0
  61. package/types.ts +8 -0
package/dist/cli.js CHANGED
@@ -33,6 +33,7 @@ import { test } from "./commands/test/test.js";
33
33
  import { toolchain } from "./commands/toolchain/index.js";
34
34
  import { update } from "./commands/update.js";
35
35
  import { getPrincipal, getUserProp, importPem, setUserProp, } from "./commands/user.js";
36
+ import { migrateNew, migrateFreeze } from "./commands/migrate.js";
36
37
  import { watch } from "./commands/watch/watch.js";
37
38
  import { apiVersion, checkApiCompatibility, checkConfigFile, getGlobalMocArgs, getNetworkFile, readConfig, setNetwork, version, } from "./mops.js";
38
39
  import { resolvePackages } from "./resolve-packages.js";
@@ -263,20 +264,20 @@ program
263
264
  });
264
265
  // check
265
266
  program
266
- .command("check [files...]")
267
- .description("Check Motoko files for syntax errors and type issues. If no files are specified, checks all canister entrypoints from mops.toml. Also runs stable compatibility checks for canisters with [check-stable] configured, and runs linting if lintoko is configured in [toolchain] and rule directories are present")
267
+ .command("check [args...]")
268
+ .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]")
268
269
  .option("--verbose", "Verbose console output")
269
270
  .addOption(new Option("--fix", "Apply autofixes to all files, including transitively imported ones"))
270
271
  .allowUnknownOption(true)
271
- .action(async (files, options) => {
272
+ .action(async (args, options) => {
272
273
  checkConfigFile(true);
273
- const { extraArgs, args: fileList } = parseExtraArgs(files);
274
+ const { extraArgs, args: argList } = parseExtraArgs(args);
274
275
  await installAll({
275
276
  silent: true,
276
277
  lock: "ignore",
277
278
  installFromLockFile: true,
278
279
  });
279
- await check(fileList, {
280
+ await check(argList, {
280
281
  ...options,
281
282
  extraArgs,
282
283
  });
@@ -296,19 +297,19 @@ program
296
297
  });
297
298
  // check-stable
298
299
  program
299
- .command("check-stable <old-file> [canister]")
300
- .description("Check stable variable compatibility between an old version (.mo or .most file) and the current canister entrypoint")
300
+ .command("check-stable [args...]")
301
+ .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")
301
302
  .option("--verbose", "Verbose console output")
302
303
  .allowUnknownOption(true)
303
- .action(async (oldFile, canister, options) => {
304
+ .action(async (args, options) => {
304
305
  checkConfigFile(true);
305
- const { extraArgs } = parseExtraArgs();
306
+ const { extraArgs, args: argList } = parseExtraArgs(args);
306
307
  await installAll({
307
308
  silent: true,
308
309
  lock: "ignore",
309
310
  installFromLockFile: true,
310
311
  });
311
- await checkStable(oldFile, canister, {
312
+ await checkStable(argList, {
312
313
  ...options,
313
314
  extraArgs,
314
315
  });
@@ -550,6 +551,23 @@ toolchainCommand
550
551
  console.log(bin);
551
552
  });
552
553
  program.addCommand(toolchainCommand);
554
+ // migrate
555
+ const migrateCommand = new Command("migrate").description("Manage enhanced migration chains");
556
+ migrateCommand
557
+ .command("new <name> [canister]")
558
+ .description("Create a new migration file in the next-migration directory")
559
+ .action(async (name, canister) => {
560
+ checkConfigFile(true);
561
+ await migrateNew(name, canister);
562
+ });
563
+ migrateCommand
564
+ .command("freeze [canister]")
565
+ .description("Move the next migration into the frozen chain")
566
+ .action(async (canister) => {
567
+ checkConfigFile(true);
568
+ await migrateFreeze(canister);
569
+ });
570
+ program.addCommand(migrateCommand);
553
571
  // self
554
572
  const selfCommand = new Command("self").description("Mops CLI management");
555
573
  selfCommand
@@ -6,7 +6,8 @@ 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 { resolveCanisterConfigs } from "../helpers/resolve-canisters.js";
9
+ import { filterCanisters, resolveCanisterConfigs, validateCanisterArgs, } from "../helpers/resolve-canisters.js";
10
+ import { prepareMigrationArgs } from "../helpers/migrations.js";
10
11
  import { getWasmBindings } from "../wasm.js";
11
12
  import { getGlobalMocArgs, readConfig, resolveConfigPath } from "../mops.js";
12
13
  import { sourcesArgs } from "./sources.js";
@@ -26,18 +27,10 @@ export async function build(canisterNames, options) {
26
27
  if (!Object.keys(canisters).length) {
27
28
  cliError(`No Motoko canisters found in mops.toml configuration`);
28
29
  }
29
- if (canisterNames) {
30
- let invalidNames = canisterNames.filter((name) => !(name in canisters));
31
- if (invalidNames.length) {
32
- cliError(`Motoko canister(s) not found in mops.toml configuration: ${invalidNames.join(", ")}`);
33
- }
34
- }
35
30
  if (!(await exists(outputDir))) {
36
31
  await mkdir(outputDir, { recursive: true });
37
32
  }
38
- const filteredCanisters = canisterNames
39
- ? Object.fromEntries(Object.entries(canisters).filter(([name]) => canisterNames.includes(name)))
40
- : canisters;
33
+ const filteredCanisters = filterCanisters(canisters, canisterNames);
41
34
  for (let [canisterName, canister] of Object.entries(filteredCanisters)) {
42
35
  console.log(chalk.blue("build canister"), chalk.bold(canisterName));
43
36
  let motokoPath = canister.main;
@@ -70,6 +63,7 @@ export async function build(canisterNames, options) {
70
63
  catch { }
71
64
  };
72
65
  process.on("exit", exitCleanup);
66
+ const migration = await prepareMigrationArgs(canister.migrations, canisterName, "build", options.verbose);
73
67
  try {
74
68
  let args = [
75
69
  "-c",
@@ -80,6 +74,7 @@ export async function build(canisterNames, options) {
80
74
  motokoPath,
81
75
  ...(await sourcesArgs()).flat(),
82
76
  ...getGlobalMocArgs(config),
77
+ ...migration.migrationArgs,
83
78
  ];
84
79
  args.push(...collectExtraArgs(config, canister, canisterName, options.extraArgs));
85
80
  const isPublicCandid = true; // always true for now to reduce corner cases
@@ -156,6 +151,7 @@ export async function build(canisterNames, options) {
156
151
  }
157
152
  }
158
153
  finally {
154
+ await migration.cleanup();
159
155
  process.removeListener("exit", exitCleanup);
160
156
  try {
161
157
  await release?.();
@@ -181,9 +177,7 @@ function collectExtraArgs(config, canister, canisterName, extraArgs) {
181
177
  args.push(...config.build.args);
182
178
  }
183
179
  if (canister.args) {
184
- if (typeof canister.args === "string") {
185
- cliError(`Canister config 'args' should be an array of strings for canister ${canisterName}`);
186
- }
180
+ validateCanisterArgs(canister, canisterName, config);
187
181
  args.push(...canister.args);
188
182
  }
189
183
  if (extraArgs) {
@@ -1,14 +1,21 @@
1
+ import { CanisterConfig } from "../types.js";
1
2
  export interface CheckStableOptions {
2
3
  verbose: boolean;
3
4
  extraArgs: string[];
4
5
  }
5
- export declare function checkStable(oldFile: string, canisterName: string | undefined, options?: Partial<CheckStableOptions>): Promise<void>;
6
+ export declare function resolveStablePath(canister: CanisterConfig, canisterName: string, options?: {
7
+ required?: boolean;
8
+ }): string | null;
9
+ export declare function checkStable(args: string[], options?: Partial<CheckStableOptions>): Promise<void>;
6
10
  export interface RunStableCheckParams {
7
11
  oldFile: string;
8
12
  canisterMain: string;
9
13
  canisterName: string;
10
14
  mocPath: string;
11
15
  globalMocArgs: string[];
16
+ canisterArgs: string[];
17
+ sources?: string[];
12
18
  options?: Partial<CheckStableOptions>;
19
+ hasMigrations?: boolean;
13
20
  }
14
21
  export declare function runStableCheck(params: RunStableCheckParams): Promise<void>;
@@ -4,31 +4,109 @@ import { rm } from "node:fs/promises";
4
4
  import chalk from "chalk";
5
5
  import { execa } from "execa";
6
6
  import { cliError } from "../error.js";
7
+ import { prepareMigrationArgs } from "../helpers/migrations.js";
7
8
  import { getGlobalMocArgs, readConfig, resolveConfigPath } from "../mops.js";
8
- import { resolveSingleCanister } from "../helpers/resolve-canisters.js";
9
+ import { filterCanisters, looksLikeFile, resolveCanisterConfigs, resolveSingleCanister, validateCanisterArgs, } from "../helpers/resolve-canisters.js";
9
10
  import { sourcesArgs } from "./sources.js";
10
11
  import { toolchain } from "./toolchain/index.js";
11
12
  const CHECK_STABLE_DIR = ".mops/.check-stable";
12
- export async function checkStable(oldFile, canisterName, options = {}) {
13
- const config = readConfig();
14
- const { name, canister } = resolveSingleCanister(config, canisterName);
15
- if (!canister.main) {
16
- cliError(`No main file specified for canister '${name}' in mops.toml`);
13
+ export function resolveStablePath(canister, canisterName, options) {
14
+ const stableConfig = canister["check-stable"];
15
+ if (!stableConfig) {
16
+ if (options?.required) {
17
+ cliError(`Canister '${canisterName}' has no [canisters.${canisterName}.check-stable] configuration in mops.toml`);
18
+ }
19
+ return null;
20
+ }
21
+ const stablePath = resolveConfigPath(stableConfig.path);
22
+ if (!existsSync(stablePath)) {
23
+ if (stableConfig.skipIfMissing) {
24
+ return null;
25
+ }
26
+ cliError(`Deployed file not found: ${stablePath} (canister '${canisterName}')\n` +
27
+ "Set skipIfMissing = true in [canisters." +
28
+ canisterName +
29
+ ".check-stable] to skip this check when the file is missing.");
17
30
  }
31
+ return stablePath;
32
+ }
33
+ export async function checkStable(args, options = {}) {
34
+ const config = readConfig();
18
35
  const mocPath = await toolchain.bin("moc", { fallback: true });
19
36
  const globalMocArgs = getGlobalMocArgs(config);
20
- await runStableCheck({
21
- oldFile,
22
- canisterMain: resolveConfigPath(canister.main),
23
- canisterName: name,
24
- mocPath,
25
- globalMocArgs,
26
- options,
27
- });
37
+ const firstArg = args[0];
38
+ if (firstArg && looksLikeFile(firstArg)) {
39
+ const oldFile = firstArg;
40
+ const canisterName = args[1];
41
+ const { name, canister } = resolveSingleCanister(config, canisterName);
42
+ if (!canister.main) {
43
+ cliError(`No main file specified for canister '${name}' in mops.toml`);
44
+ }
45
+ validateCanisterArgs(canister, name, config);
46
+ const migration = await prepareMigrationArgs(canister.migrations, name, "check", options.verbose);
47
+ try {
48
+ await runStableCheck({
49
+ oldFile,
50
+ canisterMain: resolveConfigPath(canister.main),
51
+ canisterName: name,
52
+ mocPath,
53
+ globalMocArgs,
54
+ canisterArgs: [...migration.migrationArgs, ...(canister.args ?? [])],
55
+ options,
56
+ hasMigrations: !!canister.migrations,
57
+ });
58
+ }
59
+ finally {
60
+ await migration.cleanup();
61
+ }
62
+ return;
63
+ }
64
+ const canisters = resolveCanisterConfigs(config);
65
+ const canisterNames = args.length > 0 ? args : undefined;
66
+ const filteredCanisters = filterCanisters(canisters, canisterNames);
67
+ const sources = (await sourcesArgs()).flat();
68
+ let checked = 0;
69
+ for (const [name, canister] of Object.entries(filteredCanisters)) {
70
+ if (!canister.main) {
71
+ cliError(`No main file specified for canister '${name}' in mops.toml`);
72
+ }
73
+ validateCanisterArgs(canister, name, config);
74
+ const stablePath = resolveStablePath(canister, name, {
75
+ required: !!canisterNames,
76
+ });
77
+ if (!stablePath) {
78
+ continue;
79
+ }
80
+ const migration = await prepareMigrationArgs(canister.migrations, name, "check", options.verbose);
81
+ try {
82
+ await runStableCheck({
83
+ oldFile: stablePath,
84
+ canisterMain: resolveConfigPath(canister.main),
85
+ canisterName: name,
86
+ mocPath,
87
+ globalMocArgs,
88
+ canisterArgs: [...migration.migrationArgs, ...(canister.args ?? [])],
89
+ sources,
90
+ options,
91
+ hasMigrations: !!canister.migrations,
92
+ });
93
+ }
94
+ finally {
95
+ await migration.cleanup();
96
+ }
97
+ checked++;
98
+ }
99
+ if (checked === 0 && !canisterNames) {
100
+ cliError("No canisters with [check-stable] configuration found in mops.toml.\n" +
101
+ "Either pass an old file: mops check-stable <old-file> [canister]\n" +
102
+ "Or configure check-stable for a canister:\n\n" +
103
+ " [canisters.backend.check-stable]\n" +
104
+ ' path = "deployed.mo"');
105
+ }
28
106
  }
29
107
  export async function runStableCheck(params) {
30
- const { oldFile, canisterMain, canisterName, mocPath, globalMocArgs, options = {}, } = params;
31
- const sources = (await sourcesArgs()).flat();
108
+ const { oldFile, canisterMain, canisterName, mocPath, globalMocArgs, canisterArgs, options = {}, } = params;
109
+ const sources = params.sources ?? (await sourcesArgs()).flat();
32
110
  const isOldMostFile = oldFile.endsWith(".most");
33
111
  if (!existsSync(oldFile)) {
34
112
  cliError(`File not found: ${oldFile}`);
@@ -38,8 +116,8 @@ export async function runStableCheck(params) {
38
116
  try {
39
117
  const oldMostPath = isOldMostFile
40
118
  ? oldFile
41
- : await generateStableTypes(mocPath, oldFile, join(CHECK_STABLE_DIR, "old.most"), sources, globalMocArgs, options);
42
- const newMostPath = await generateStableTypes(mocPath, canisterMain, join(CHECK_STABLE_DIR, "new.most"), sources, globalMocArgs, options);
119
+ : await generateStableTypes(mocPath, oldFile, join(CHECK_STABLE_DIR, "old.most"), sources, globalMocArgs, canisterArgs, options);
120
+ const newMostPath = await generateStableTypes(mocPath, canisterMain, join(CHECK_STABLE_DIR, "new.most"), sources, globalMocArgs, canisterArgs, options);
43
121
  if (options.verbose) {
44
122
  console.log(chalk.blue("check-stable"), chalk.gray(`Comparing ${oldMostPath} ↔ ${newMostPath}`));
45
123
  }
@@ -55,6 +133,9 @@ export async function runStableCheck(params) {
55
133
  if (result.stderr) {
56
134
  console.error(result.stderr);
57
135
  }
136
+ if (params.hasMigrations) {
137
+ console.error(chalk.yellow("Hint: You may need a migration. Run `mops migrate new <Name>` to create one."));
138
+ }
58
139
  cliError(`✗ Stable compatibility check failed for canister '${canisterName}'`);
59
140
  }
60
141
  console.log(chalk.green(`✓ Stable compatibility check passed for canister '${canisterName}'`));
@@ -63,7 +144,7 @@ export async function runStableCheck(params) {
63
144
  await rm(CHECK_STABLE_DIR, { recursive: true, force: true });
64
145
  }
65
146
  }
66
- async function generateStableTypes(mocPath, moFile, outputPath, sources, globalMocArgs, options) {
147
+ async function generateStableTypes(mocPath, moFile, outputPath, sources, globalMocArgs, canisterArgs, options) {
67
148
  const base = basename(outputPath, ".most");
68
149
  const wasmPath = join(CHECK_STABLE_DIR, base + ".wasm");
69
150
  const args = [
@@ -73,6 +154,7 @@ async function generateStableTypes(mocPath, moFile, outputPath, sources, globalM
73
154
  moFile,
74
155
  ...sources,
75
156
  ...globalMocArgs,
157
+ ...canisterArgs,
76
158
  ...(options.extraArgs ?? []),
77
159
  ];
78
160
  if (options.verbose) {
@@ -3,4 +3,4 @@ export interface CheckOptions {
3
3
  fix: boolean;
4
4
  extraArgs: string[];
5
5
  }
6
- export declare function check(files: string | string[], options?: Partial<CheckOptions>): Promise<void>;
6
+ export declare function check(args: string[], options?: Partial<CheckOptions>): Promise<void>;
@@ -1,13 +1,13 @@
1
1
  import path from "node:path";
2
- import { existsSync } from "node:fs";
3
2
  import chalk from "chalk";
4
3
  import { execa } from "execa";
5
4
  import { cliError } from "../error.js";
6
5
  import { getGlobalMocArgs, getRootDir, readConfig, resolveConfigPath, } from "../mops.js";
7
6
  import { autofixMotoko } from "../helpers/autofix-motoko.js";
8
7
  import { getMocSemVer } from "../helpers/get-moc-version.js";
9
- import { resolveCanisterConfigs, resolveCanisterEntrypoints, } from "../helpers/resolve-canisters.js";
10
- import { runStableCheck } from "./check-stable.js";
8
+ import { filterCanisters, looksLikeFile, resolveCanisterConfigs, validateCanisterArgs, } from "../helpers/resolve-canisters.js";
9
+ import { prepareMigrationArgs } from "../helpers/migrations.js";
10
+ import { resolveStablePath, runStableCheck } from "./check-stable.js";
11
11
  import { sourcesArgs } from "./sources.js";
12
12
  import { toolchain } from "./toolchain/index.js";
13
13
  import { collectLintRules, lint } from "./lint.js";
@@ -16,35 +16,145 @@ function supportsAllLibsFlag() {
16
16
  const version = getMocSemVer();
17
17
  return version ? version.compare(MOC_ALL_LIBS_MIN_VERSION) >= 0 : false;
18
18
  }
19
- export async function check(files, options = {}) {
20
- const explicitFiles = Array.isArray(files) ? files : files ? [files] : [];
21
- let fileList = [...explicitFiles];
22
- const config = readConfig();
23
- if (fileList.length === 0) {
24
- fileList = resolveCanisterEntrypoints(config).map(resolveConfigPath);
25
- }
26
- if (fileList.length === 0) {
27
- cliError("No Motoko files specified and no canisters defined in mops.toml.\n" +
28
- "Either pass files: mops check <files...>\n" +
29
- "Or define canisters in mops.toml:\n\n" +
30
- " [canisters.backend]\n" +
31
- ' main = "src/main.mo"');
32
- }
33
- const mocPath = await toolchain.bin("moc", { fallback: true });
34
- const sources = await sourcesArgs();
35
- const globalMocArgs = getGlobalMocArgs(config);
36
- // --all-libs enables richer diagnostics with edit suggestions from moc (requires moc >= 1.3.0)
19
+ function checkAllLibsSupport(verbose) {
37
20
  const allLibs = supportsAllLibsFlag();
38
21
  if (!allLibs) {
39
22
  console.log(chalk.yellow(`moc < ${MOC_ALL_LIBS_MIN_VERSION}: some diagnostic hints may be missing`));
40
23
  }
41
- else if (options.verbose) {
24
+ else if (verbose) {
42
25
  console.log(chalk.blue("check"), chalk.gray("Using --all-libs for richer diagnostics"));
43
26
  }
27
+ return allLibs;
28
+ }
29
+ function logAutofixResult(fixResult, verbose) {
30
+ if (fixResult) {
31
+ for (const [file, codes] of fixResult.fixedFiles) {
32
+ const unique = [...new Set(codes)].sort();
33
+ const n = codes.length;
34
+ const rel = path.relative(process.cwd(), file);
35
+ console.log(chalk.green(`Fixed ${rel} (${n} ${n === 1 ? "fix" : "fixes"}: ${unique.join(", ")})`));
36
+ }
37
+ const fileCount = fixResult.fixedFiles.size;
38
+ console.log(chalk.green(`\n✓ ${fixResult.totalFixCount} ${fixResult.totalFixCount === 1 ? "fix" : "fixes"} applied to ${fileCount} ${fileCount === 1 ? "file" : "files"}`));
39
+ }
40
+ else if (verbose) {
41
+ console.log(chalk.yellow("No fixes were needed"));
42
+ }
43
+ }
44
+ export async function check(args, options = {}) {
45
+ const config = readConfig();
46
+ const canisters = resolveCanisterConfigs(config);
47
+ const hasCanisters = Object.keys(canisters).length > 0;
48
+ const fileArgs = args.filter(looksLikeFile);
49
+ const nonFileArgs = args.filter((a) => !looksLikeFile(a));
50
+ const isFileMode = fileArgs.length > 0;
51
+ if (isFileMode && nonFileArgs.length > 0) {
52
+ cliError(`Cannot mix file paths and canister names: ${args.join(", ")}\n` +
53
+ "Pass either file paths (e.g. mops check src/main.mo) or canister names (e.g. mops check backend)");
54
+ }
55
+ if (isFileMode) {
56
+ await checkFiles(config, fileArgs, options);
57
+ }
58
+ else {
59
+ if (!hasCanisters) {
60
+ cliError("No canisters defined in mops.toml.\n" +
61
+ "Either pass files: mops check <files...>\n" +
62
+ "Or define canisters in mops.toml:\n\n" +
63
+ " [canisters.backend]\n" +
64
+ ' main = "src/main.mo"');
65
+ }
66
+ const canisterNames = args.length > 0 ? args : undefined;
67
+ const filtered = filterCanisters(canisters, canisterNames);
68
+ await checkCanisters(config, filtered, options);
69
+ }
70
+ if (config.toolchain?.lintoko) {
71
+ const rootDir = getRootDir();
72
+ const lintRules = await collectLintRules(config, rootDir);
73
+ const lintFiles = isFileMode ? fileArgs : undefined;
74
+ await lint(undefined, {
75
+ verbose: options.verbose,
76
+ fix: options.fix,
77
+ rules: lintRules,
78
+ files: lintFiles,
79
+ });
80
+ }
81
+ }
82
+ async function checkCanisters(config, canisters, options) {
83
+ const mocPath = await toolchain.bin("moc", { fallback: true });
84
+ const sources = (await sourcesArgs()).flat();
85
+ const globalMocArgs = getGlobalMocArgs(config);
86
+ const allLibs = checkAllLibsSupport(options.verbose);
87
+ for (const [canisterName, canister] of Object.entries(canisters)) {
88
+ if (!canister.main) {
89
+ cliError(`No main file specified for canister '${canisterName}' in mops.toml`);
90
+ }
91
+ validateCanisterArgs(canister, canisterName, config);
92
+ const motokoPath = resolveConfigPath(canister.main);
93
+ const migration = await prepareMigrationArgs(canister.migrations, canisterName, "check", options.verbose);
94
+ try {
95
+ const mocArgs = [
96
+ "--check",
97
+ ...(allLibs ? ["--all-libs"] : []),
98
+ ...sources,
99
+ ...globalMocArgs,
100
+ ...migration.migrationArgs,
101
+ ...(canister.args ?? []),
102
+ ...(options.extraArgs ?? []),
103
+ ];
104
+ if (options.fix) {
105
+ if (options.verbose) {
106
+ console.log(chalk.blue("check"), chalk.gray(`Attempting to fix ${canisterName}`));
107
+ }
108
+ const fixResult = await autofixMotoko(mocPath, [motokoPath], mocArgs);
109
+ logAutofixResult(fixResult, options.verbose);
110
+ }
111
+ try {
112
+ const args = [motokoPath, ...mocArgs];
113
+ if (options.verbose) {
114
+ console.log(chalk.blue("check"), chalk.gray(`Checking canister ${canisterName}:`));
115
+ console.log(chalk.gray(mocPath, JSON.stringify(args)));
116
+ }
117
+ const result = await execa(mocPath, args, {
118
+ stdio: "inherit",
119
+ reject: false,
120
+ });
121
+ if (result.exitCode !== 0) {
122
+ cliError(`✗ Check failed for canister ${canisterName} (exit code: ${result.exitCode})`);
123
+ }
124
+ console.log(chalk.green(`✓ ${canisterName}`));
125
+ }
126
+ catch (err) {
127
+ cliError(`Error while checking canister ${canisterName}${err?.message ? `\n${err.message}` : ""}`);
128
+ }
129
+ const stablePath = resolveStablePath(canister, canisterName);
130
+ if (stablePath) {
131
+ await runStableCheck({
132
+ oldFile: stablePath,
133
+ canisterMain: motokoPath,
134
+ canisterName,
135
+ mocPath,
136
+ globalMocArgs,
137
+ canisterArgs: [...migration.migrationArgs, ...(canister.args ?? [])],
138
+ sources,
139
+ options: { verbose: options.verbose, extraArgs: options.extraArgs },
140
+ hasMigrations: !!canister.migrations,
141
+ });
142
+ }
143
+ }
144
+ finally {
145
+ await migration.cleanup();
146
+ }
147
+ }
148
+ }
149
+ async function checkFiles(config, files, options) {
150
+ const mocPath = await toolchain.bin("moc", { fallback: true });
151
+ const sources = (await sourcesArgs()).flat();
152
+ const globalMocArgs = getGlobalMocArgs(config);
153
+ const allLibs = checkAllLibsSupport(options.verbose);
44
154
  const mocArgs = [
45
155
  "--check",
46
156
  ...(allLibs ? ["--all-libs"] : []),
47
- ...sources.flat(),
157
+ ...sources,
48
158
  ...globalMocArgs,
49
159
  ...(options.extraArgs ?? []),
50
160
  ];
@@ -52,24 +162,10 @@ export async function check(files, options = {}) {
52
162
  if (options.verbose) {
53
163
  console.log(chalk.blue("check"), chalk.gray("Attempting to fix files"));
54
164
  }
55
- const fixResult = await autofixMotoko(mocPath, fileList, mocArgs);
56
- if (fixResult) {
57
- for (const [file, codes] of fixResult.fixedFiles) {
58
- const unique = [...new Set(codes)].sort();
59
- const n = codes.length;
60
- const rel = path.relative(process.cwd(), file);
61
- console.log(chalk.green(`Fixed ${rel} (${n} ${n === 1 ? "fix" : "fixes"}: ${unique.join(", ")})`));
62
- }
63
- const fileCount = fixResult.fixedFiles.size;
64
- console.log(chalk.green(`\n✓ ${fixResult.totalFixCount} ${fixResult.totalFixCount === 1 ? "fix" : "fixes"} applied to ${fileCount} ${fileCount === 1 ? "file" : "files"}`));
65
- }
66
- else {
67
- if (options.verbose) {
68
- console.log(chalk.yellow("No fixes were needed"));
69
- }
70
- }
165
+ const fixResult = await autofixMotoko(mocPath, files, mocArgs);
166
+ logAutofixResult(fixResult, options.verbose);
71
167
  }
72
- for (const file of fileList) {
168
+ for (const file of files) {
73
169
  try {
74
170
  const args = [file, ...mocArgs];
75
171
  if (options.verbose) {
@@ -89,42 +185,4 @@ export async function check(files, options = {}) {
89
185
  cliError(`Error while checking ${file}${err?.message ? `\n${err.message}` : ""}`);
90
186
  }
91
187
  }
92
- const canisters = resolveCanisterConfigs(config);
93
- for (const [name, canister] of Object.entries(canisters)) {
94
- const stableConfig = canister["check-stable"];
95
- if (!stableConfig) {
96
- continue;
97
- }
98
- if (!canister.main) {
99
- cliError(`No main file specified for canister '${name}' in mops.toml`);
100
- }
101
- const stablePath = resolveConfigPath(stableConfig.path);
102
- if (!existsSync(stablePath)) {
103
- if (stableConfig.skipIfMissing) {
104
- continue;
105
- }
106
- cliError(`Deployed file not found: ${stablePath} (canister '${name}')\n` +
107
- "Set skipIfMissing = true in [canisters." +
108
- name +
109
- ".check-stable] to skip this check when the file is missing.");
110
- }
111
- await runStableCheck({
112
- oldFile: stablePath,
113
- canisterMain: resolveConfigPath(canister.main),
114
- canisterName: name,
115
- mocPath,
116
- globalMocArgs,
117
- options: { verbose: options.verbose, extraArgs: options.extraArgs },
118
- });
119
- }
120
- if (config.toolchain?.lintoko) {
121
- const rootDir = getRootDir();
122
- const lintRules = await collectLintRules(config, rootDir);
123
- await lint(undefined, {
124
- verbose: options.verbose,
125
- fix: options.fix,
126
- rules: lintRules,
127
- files: explicitFiles.length > 0 ? explicitFiles : undefined,
128
- });
129
- }
130
188
  }
@@ -0,0 +1,2 @@
1
+ export declare function migrateNew(name: string, canisterName?: string): Promise<void>;
2
+ export declare function migrateFreeze(canisterName?: string): Promise<void>;