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
@@ -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",
@@ -171,26 +172,24 @@ async function checkFiles(config, files, options) {
171
172
  const fixResult = await autofixMotoko(mocPath, files, mocArgs);
172
173
  logAutofixResult(fixResult, options.verbose);
173
174
  }
174
- // Check all files in a single moc invocation so shared transitive imports
175
- // are chased and type-checked once, not re-checked for every file.
176
- const args = [...files, ...mocArgs];
177
- if (options.verbose) {
178
- console.log(chalk.blue("check"), chalk.gray("Running moc:"));
179
- console.log(chalk.gray(mocPath, JSON.stringify(args)));
180
- }
181
- try {
182
- const result = await execa(mocPath, args, {
183
- stdio: "inherit",
184
- reject: false,
185
- });
186
- if (result.exitCode !== 0) {
187
- cliError(`✗ Check failed (exit code: ${result.exitCode})`);
188
- }
189
- }
190
- catch (err) {
191
- cliError(`Error while checking files${err?.message ? `\n${err.message}` : ""}`);
192
- }
193
175
  for (const file of files) {
194
- console.log(chalk.green(`✓ ${file}`));
176
+ try {
177
+ const args = [file, ...mocArgs];
178
+ if (options.verbose) {
179
+ console.log(chalk.blue("check"), chalk.gray("Running moc:"));
180
+ console.log(chalk.gray(mocPath, JSON.stringify(args)));
181
+ }
182
+ const result = await execa(mocPath, args, {
183
+ stdio: "inherit",
184
+ reject: false,
185
+ });
186
+ if (result.exitCode !== 0) {
187
+ cliError(`✗ Check failed for file ${file} (exit code: ${result.exitCode})`);
188
+ }
189
+ console.log(chalk.green(`✓ ${file}`));
190
+ }
191
+ catch (err) {
192
+ cliError(`Error while checking ${file}${err?.message ? `\n${err.message}` : ""}`);
193
+ }
195
194
  }
196
195
  }
@@ -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>;
@@ -0,0 +1,97 @@
1
+ import { existsSync, mkdirSync } from "node:fs";
2
+ import { copyFile, writeFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import chalk from "chalk";
5
+ import { cliError } from "../error.js";
6
+ import { filterCanisters, resolveCanisterConfigs, } from "../helpers/resolve-canisters.js";
7
+ import { getRootDir, readConfig, resolveConfigPath, writeConfig, } from "../mops.js";
8
+ import { resolveBuildOutputDir } from "./build.js";
9
+ export const DEFAULT_DEPLOYED_DIR = "deployed";
10
+ const EMPTY_ACTOR_MOST = "// Version: 1.0.0\nactor { };\n";
11
+ // `[deployed].dir` and CLI overrides are tracked in two forms:
12
+ // - `config`: relative to mops.toml, used for display + writing into mops.toml
13
+ // - `resolved`: relative to cwd, used for fs operations
14
+ function resolveDeployedDir(config, override) {
15
+ if (override) {
16
+ const configRel = path.relative(getRootDir(), path.resolve(override)) || ".";
17
+ return { config: configRel, resolved: override };
18
+ }
19
+ const configured = config.deployed?.dir ?? DEFAULT_DEPLOYED_DIR;
20
+ return { config: configured, resolved: resolveConfigPath(configured) };
21
+ }
22
+ function selectCanisters(canisterNames, config) {
23
+ if (canisterNames?.length === 0) {
24
+ cliError("No canisters specified");
25
+ }
26
+ const canisters = resolveCanisterConfigs(config);
27
+ if (!Object.keys(canisters).length) {
28
+ cliError(`No Motoko canisters found in mops.toml configuration`);
29
+ }
30
+ return filterCanisters(canisters, canisterNames);
31
+ }
32
+ function warnIfStablePathDiverges(name, stablePath, destMostRel) {
33
+ if (path.resolve(resolveConfigPath(stablePath)) ===
34
+ path.resolve(resolveConfigPath(destMostRel))) {
35
+ return;
36
+ }
37
+ console.warn(chalk.yellow(`WARN: [canisters.${name}.check-stable].path = "${stablePath}" ` +
38
+ `does not match where \`mops deployed\` writes ("${destMostRel}"). ` +
39
+ `\`mops check-stable\` won't see updates from \`mops deployed\`.`));
40
+ }
41
+ export async function deployed(canisterNames, options = {}) {
42
+ const config = readConfig();
43
+ const filtered = selectCanisters(canisterNames, config);
44
+ const buildDir = resolveBuildOutputDir(config, options.buildDir);
45
+ const deployedDir = resolveDeployedDir(config, options.dir);
46
+ mkdirSync(deployedDir.resolved, { recursive: true });
47
+ for (const [name, canister] of Object.entries(filtered)) {
48
+ const sourceMost = path.join(buildDir, `${name}.most`);
49
+ const destMostRel = path.join(deployedDir.config, `${name}.most`);
50
+ const destMost = path.join(deployedDir.resolved, `${name}.most`);
51
+ if (!existsSync(sourceMost)) {
52
+ cliError(`No built .most at ${sourceMost}. Run \`mops build ${name}\` first.`);
53
+ }
54
+ await copyFile(sourceMost, destMost);
55
+ console.log(chalk.green(`✓ ${sourceMost} → ${destMost}`));
56
+ const stablePath = canister["check-stable"]?.path;
57
+ if (stablePath) {
58
+ warnIfStablePathDiverges(name, stablePath, destMostRel);
59
+ }
60
+ }
61
+ }
62
+ export async function deployedInit(canisterNames, options = {}) {
63
+ const config = readConfig();
64
+ const filtered = selectCanisters(canisterNames, config);
65
+ const deployedDir = resolveDeployedDir(config, options.dir);
66
+ mkdirSync(deployedDir.resolved, { recursive: true });
67
+ let configChanged = false;
68
+ for (const [name, canister] of Object.entries(filtered)) {
69
+ const destMostRel = path.join(deployedDir.config, `${name}.most`);
70
+ const destMost = path.join(deployedDir.resolved, `${name}.most`);
71
+ if (!existsSync(destMost)) {
72
+ await writeFile(destMost, EMPTY_ACTOR_MOST);
73
+ console.log(chalk.green(`✓ Created baseline ${destMost}`));
74
+ }
75
+ const stablePath = canister["check-stable"]?.path;
76
+ if (!stablePath) {
77
+ const entry = config.canisters?.[name];
78
+ if (typeof entry === "string") {
79
+ config.canisters[name] = {
80
+ main: entry,
81
+ "check-stable": { path: destMostRel },
82
+ };
83
+ }
84
+ else if (entry) {
85
+ entry["check-stable"] = { path: destMostRel };
86
+ }
87
+ configChanged = true;
88
+ console.log(chalk.green(`✓ Set [canisters.${name}.check-stable].path = "${destMostRel}"`));
89
+ }
90
+ else {
91
+ warnIfStablePathDiverges(name, stablePath, destMostRel);
92
+ }
93
+ }
94
+ if (configChanged) {
95
+ writeConfig(config);
96
+ }
97
+ }
@@ -0,0 +1,6 @@
1
+ export interface GenerateCandidOptions {
2
+ output?: string;
3
+ verbose?: boolean;
4
+ extraArgs?: string[];
5
+ }
6
+ export declare function generateCandid(canisterNames: string[] | undefined, options: GenerateCandidOptions): Promise<void>;
@@ -0,0 +1,124 @@
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 { filterCanisters, resolveCanisterConfigs, } from "../helpers/resolve-canisters.js";
7
+ import { GENERATE_CANDID_MANAGED_FLAGS, prepareMocArgs, } from "../helpers/moc-args.js";
8
+ import { getRootDir, readConfig, resolveConfigPath, writeConfig, } from "../mops.js";
9
+ import { toolchain } from "./toolchain/index.js";
10
+ export async function generateCandid(canisterNames, options) {
11
+ if (canisterNames?.length === 0) {
12
+ cliError("No canisters specified");
13
+ }
14
+ const config = readConfig();
15
+ const canisters = resolveCanisterConfigs(config);
16
+ if (!Object.keys(canisters).length) {
17
+ cliError("No Motoko canisters found in mops.toml configuration");
18
+ }
19
+ const filtered = filterCanisters(canisters, canisterNames);
20
+ const filteredEntries = Object.entries(filtered);
21
+ if (options.output && filteredEntries.length > 1) {
22
+ cliError("--output / -o is only supported when generating for a single canister");
23
+ }
24
+ const mocPath = await toolchain.bin("moc", { fallback: true });
25
+ const rootDir = getRootDir();
26
+ let configChanged = false;
27
+ for (const [canisterName, canister] of filteredEntries) {
28
+ const dest = resolveDestination(canisterName, canister, canisters, options.output, rootDir);
29
+ console.log(chalk.blue("generate candid"), chalk.bold(canisterName), chalk.gray(`→ ${dest.fsPath}`));
30
+ const prepared = await prepareMocArgs(config, canister, canisterName, {
31
+ mode: "build",
32
+ managedFlags: GENERATE_CANDID_MANAGED_FLAGS,
33
+ commandName: "mops generate candid",
34
+ verbose: options.verbose,
35
+ extraArgs: options.extraArgs,
36
+ });
37
+ try {
38
+ await mkdir(path.dirname(dest.fsPath), { recursive: true });
39
+ const args = [
40
+ "--idl",
41
+ "-o",
42
+ dest.fsPath,
43
+ prepared.motokoPath,
44
+ ...prepared.args,
45
+ ];
46
+ if (options.verbose) {
47
+ console.log(chalk.gray(mocPath, JSON.stringify(args)));
48
+ }
49
+ const result = await execa(mocPath, args, {
50
+ stdio: options.verbose ? "inherit" : "pipe",
51
+ reject: false,
52
+ });
53
+ if (result.exitCode !== 0) {
54
+ if (!options.verbose) {
55
+ if (result.stderr) {
56
+ console.error(chalk.red(result.stderr));
57
+ }
58
+ if (result.stdout?.trim()) {
59
+ console.error(chalk.yellow("Output:"));
60
+ console.error(result.stdout);
61
+ }
62
+ }
63
+ cliError(`Failed to generate Candid for canister ${canisterName} (exit code: ${result.exitCode})`);
64
+ }
65
+ if (dest.configPath !== null) {
66
+ const c = (config.canisters ??= {});
67
+ const existing = c[canisterName];
68
+ const obj = typeof existing === "string"
69
+ ? { main: existing }
70
+ : { ...(existing ?? {}) };
71
+ obj.candid = dest.configPath;
72
+ c[canisterName] = obj;
73
+ configChanged = true;
74
+ }
75
+ }
76
+ finally {
77
+ await prepared.cleanup();
78
+ }
79
+ }
80
+ if (configChanged) {
81
+ writeConfig(config);
82
+ }
83
+ console.log(chalk.green(`\n✓ Generated Candid for ${filteredEntries.length} canister${filteredEntries.length === 1 ? "" : "s"}`));
84
+ }
85
+ function resolveDestination(canisterName, canister, allCanisters, outputFlag, rootDir) {
86
+ if (!canister.main) {
87
+ cliError(`No main file is specified for canister ${canisterName}`);
88
+ }
89
+ let fsPath;
90
+ let configPath;
91
+ if (outputFlag) {
92
+ fsPath = outputFlag;
93
+ configPath = null;
94
+ const outAbs = path.resolve(fsPath);
95
+ for (const [otherName, other] of Object.entries(allCanisters)) {
96
+ if (otherName === canisterName || !other.candid) {
97
+ continue;
98
+ }
99
+ if (path.resolve(rootDir, other.candid) === outAbs) {
100
+ console.warn(chalk.yellow(`Warning: --output path collides with [canisters.${otherName}].candid (${other.candid}). Sharing a .did between canisters is almost always a mistake.`));
101
+ }
102
+ }
103
+ }
104
+ else if (canister.candid) {
105
+ fsPath = resolveConfigPath(canister.candid);
106
+ configPath = null;
107
+ }
108
+ else {
109
+ // Default: <dirname(main)>/<canisterName>.did, forward slashes for the toml value
110
+ const mainDir = path.dirname(canister.main).replace(/\\/g, "/");
111
+ const projectRel = mainDir === "." || mainDir === ""
112
+ ? `${canisterName}.did`
113
+ : `${mainDir}/${canisterName}.did`;
114
+ fsPath = resolveConfigPath(projectRel);
115
+ configPath = projectRel;
116
+ }
117
+ const absPath = path.resolve(fsPath);
118
+ const dotMopsDir = path.resolve(rootDir, ".mops");
119
+ if (absPath === dotMopsDir || absPath.startsWith(dotMopsDir + path.sep)) {
120
+ cliError(`Refusing to write Candid file inside .mops/ (private build cache): ${fsPath}\n` +
121
+ "Choose a path outside .mops/ — it should be committable and readable by downstream tooling.");
122
+ }
123
+ return { fsPath, configPath };
124
+ }
@@ -6,5 +6,7 @@ export interface LintOptions {
6
6
  rules?: string[];
7
7
  files?: string[];
8
8
  extraArgs: string[];
9
+ /** Commander `--no-check-limit`: lint the full migration chain. */
10
+ noCheckLimit?: boolean;
9
11
  }
10
12
  export declare function lint(filter: string | undefined, options: Partial<LintOptions>): Promise<void>;
@@ -122,7 +122,7 @@ async function lintImpl(filter, options) {
122
122
  ? await toolchain.bin("lintoko")
123
123
  : "lintoko";
124
124
  const isExplicit = !!filter || !!(options.files && options.files.length > 0);
125
- const trimmedMigrations = isExplicit
125
+ const trimmedMigrations = isExplicit || options.noCheckLimit
126
126
  ? new Set()
127
127
  : getTrimmedMigrationFiles(config);
128
128
  let filesToLint;
@@ -1,5 +1,7 @@
1
1
  interface MocSpan {
2
2
  file: string;
3
+ byte_start?: number | null;
4
+ byte_end?: number | null;
3
5
  line_start: number;
4
6
  column_start: number;
5
7
  line_end: number;