ic-mops 2.8.1 → 2.10.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 (73) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/bundle/cli.tgz +0 -0
  3. package/cli.ts +21 -11
  4. package/commands/build.ts +134 -114
  5. package/commands/check-stable.ts +110 -17
  6. package/commands/check.ts +183 -102
  7. package/commands/info.ts +103 -0
  8. package/commands/lint.ts +132 -42
  9. package/commands/toolchain/moc.ts +5 -5
  10. package/dist/cli.js +19 -10
  11. package/dist/commands/build.js +105 -82
  12. package/dist/commands/check-stable.d.ts +7 -1
  13. package/dist/commands/check-stable.js +83 -19
  14. package/dist/commands/check.d.ts +1 -1
  15. package/dist/commands/check.js +127 -78
  16. package/dist/commands/info.d.ts +4 -0
  17. package/dist/commands/info.js +75 -0
  18. package/dist/commands/lint.js +84 -37
  19. package/dist/commands/toolchain/moc.js +5 -5
  20. package/dist/helpers/resolve-canisters.d.ts +3 -1
  21. package/dist/helpers/resolve-canisters.js +20 -5
  22. package/dist/package.json +3 -1
  23. package/dist/templates/mops-publish.yml +1 -1
  24. package/dist/templates/mops-test.yml +1 -1
  25. package/dist/tests/build.test.js +17 -0
  26. package/dist/tests/check-stable.test.js +18 -0
  27. package/dist/tests/check.test.js +23 -5
  28. package/dist/tests/lint.test.js +33 -0
  29. package/dist/types.d.ts +1 -0
  30. package/dist/wasm/pkg/nodejs/wasm_bg.wasm +0 -0
  31. package/dist/wasm/pkg/web/wasm_bg.wasm +0 -0
  32. package/helpers/resolve-canisters.ts +36 -5
  33. package/package.json +3 -1
  34. package/templates/mops-publish.yml +1 -1
  35. package/templates/mops-test.yml +1 -1
  36. package/tests/__snapshots__/check.test.ts.snap +2 -2
  37. package/tests/__snapshots__/lint.test.ts.snap +163 -5
  38. package/tests/build.test.ts +17 -0
  39. package/tests/check/canisters-canister-args/Warning.mo +5 -0
  40. package/tests/check/canisters-canister-args/mops.toml +9 -0
  41. package/tests/check-stable/canister-args/migrations/20250101_000000_Init.mo +8 -0
  42. package/tests/check-stable/canister-args/migrations/20250201_000000_AddField.mo +9 -0
  43. package/tests/check-stable/canister-args/mops.toml +9 -0
  44. package/tests/check-stable/canister-args/old.most +8 -0
  45. package/tests/check-stable/canister-args/src/main.mo +11 -0
  46. package/tests/check-stable.test.ts +21 -0
  47. package/tests/check.test.ts +26 -5
  48. package/tests/lint-extra/mops.toml +5 -0
  49. package/tests/lint-extra/src/Ok.mo +5 -0
  50. package/tests/lint-extra/src/restricted/B.mo +8 -0
  51. package/tests/lint-extra/src/restricted/Restricted.mo +8 -0
  52. package/tests/lint-extra-edge-cases/mops.toml +8 -0
  53. package/tests/lint-extra-edge-cases/src/Clean.mo +5 -0
  54. package/tests/lint-extra-example-rules/lint/migration-only/migration-only.toml +9 -0
  55. package/tests/lint-extra-example-rules/lint/no-types/no-types.toml +5 -0
  56. package/tests/lint-extra-example-rules/lint/types-only/types-only.toml +6 -0
  57. package/tests/lint-extra-example-rules/mops.toml +7 -0
  58. package/tests/lint-extra-example-rules/src/Main.mo +10 -0
  59. package/tests/lint-extra-example-rules/src/Migration.mo +9 -0
  60. package/tests/lint-extra-example-rules/src/Types.mo +10 -0
  61. package/tests/lint-extra-with-base/mops.toml +8 -0
  62. package/tests/lint-extra-with-base/src/BadBase.mo +8 -0
  63. package/tests/lint-extra-with-base/src/Ok.mo +5 -0
  64. package/tests/lint-extra-with-base/src/Restricted.mo +5 -0
  65. package/tests/lint-extra-with-cli-rules/empty-rules/.gitkeep +0 -0
  66. package/tests/lint-extra-with-cli-rules/mops.toml +5 -0
  67. package/tests/lint-extra-with-cli-rules/rules-b/no-bool-switch-2.toml +9 -0
  68. package/tests/lint-extra-with-cli-rules/src/Ok.mo +5 -0
  69. package/tests/lint-extra-with-cli-rules/src/Restricted.mo +8 -0
  70. package/tests/lint.test.ts +42 -0
  71. package/types.ts +1 -0
  72. package/wasm/pkg/nodejs/wasm_bg.wasm +0 -0
  73. package/wasm/pkg/web/wasm_bg.wasm +0 -0
package/commands/check.ts CHANGED
@@ -1,5 +1,4 @@
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";
@@ -9,13 +8,16 @@ import {
9
8
  readConfig,
10
9
  resolveConfigPath,
11
10
  } from "../mops.js";
12
- import { autofixMotoko } from "../helpers/autofix-motoko.js";
11
+ import { AutofixResult, autofixMotoko } from "../helpers/autofix-motoko.js";
13
12
  import { getMocSemVer } from "../helpers/get-moc-version.js";
14
13
  import {
14
+ filterCanisters,
15
+ looksLikeFile,
15
16
  resolveCanisterConfigs,
16
- resolveCanisterEntrypoints,
17
+ validateCanisterArgs,
17
18
  } from "../helpers/resolve-canisters.js";
18
- import { runStableCheck } from "./check-stable.js";
19
+ import { CanisterConfig, Config } from "../types.js";
20
+ import { resolveStablePath, runStableCheck } from "./check-stable.js";
19
21
  import { sourcesArgs } from "./sources.js";
20
22
  import { toolchain } from "./toolchain/index.js";
21
23
  import { collectLintRules, lint } from "./lint.js";
@@ -33,52 +35,197 @@ export interface CheckOptions {
33
35
  extraArgs: string[];
34
36
  }
35
37
 
38
+ function checkAllLibsSupport(verbose?: boolean): boolean {
39
+ const allLibs = supportsAllLibsFlag();
40
+ if (!allLibs) {
41
+ console.log(
42
+ chalk.yellow(
43
+ `moc < ${MOC_ALL_LIBS_MIN_VERSION}: some diagnostic hints may be missing`,
44
+ ),
45
+ );
46
+ } else if (verbose) {
47
+ console.log(
48
+ chalk.blue("check"),
49
+ chalk.gray("Using --all-libs for richer diagnostics"),
50
+ );
51
+ }
52
+ return allLibs;
53
+ }
54
+
55
+ function logAutofixResult(
56
+ fixResult: AutofixResult | null,
57
+ verbose?: boolean,
58
+ ): void {
59
+ if (fixResult) {
60
+ for (const [file, codes] of fixResult.fixedFiles) {
61
+ const unique = [...new Set(codes)].sort();
62
+ const n = codes.length;
63
+ const rel = path.relative(process.cwd(), file);
64
+ console.log(
65
+ chalk.green(
66
+ `Fixed ${rel} (${n} ${n === 1 ? "fix" : "fixes"}: ${unique.join(", ")})`,
67
+ ),
68
+ );
69
+ }
70
+ const fileCount = fixResult.fixedFiles.size;
71
+ console.log(
72
+ chalk.green(
73
+ `\n✓ ${fixResult.totalFixCount} ${fixResult.totalFixCount === 1 ? "fix" : "fixes"} applied to ${fileCount} ${fileCount === 1 ? "file" : "files"}`,
74
+ ),
75
+ );
76
+ } else if (verbose) {
77
+ console.log(chalk.yellow("No fixes were needed"));
78
+ }
79
+ }
80
+
36
81
  export async function check(
37
- files: string | string[],
82
+ args: string[],
38
83
  options: Partial<CheckOptions> = {},
39
84
  ): Promise<void> {
40
- const explicitFiles = Array.isArray(files) ? files : files ? [files] : [];
41
- let fileList = [...explicitFiles];
42
-
43
85
  const config = readConfig();
86
+ const canisters = resolveCanisterConfigs(config);
87
+ const hasCanisters = Object.keys(canisters).length > 0;
88
+ const fileArgs = args.filter(looksLikeFile);
89
+ const nonFileArgs = args.filter((a) => !looksLikeFile(a));
90
+ const isFileMode = fileArgs.length > 0;
44
91
 
45
- if (fileList.length === 0) {
46
- fileList = resolveCanisterEntrypoints(config).map(resolveConfigPath);
47
- }
48
-
49
- if (fileList.length === 0) {
92
+ if (isFileMode && nonFileArgs.length > 0) {
50
93
  cliError(
51
- "No Motoko files specified and no canisters defined in mops.toml.\n" +
52
- "Either pass files: mops check <files...>\n" +
53
- "Or define canisters in mops.toml:\n\n" +
54
- " [canisters.backend]\n" +
55
- ' main = "src/main.mo"',
94
+ `Cannot mix file paths and canister names: ${args.join(", ")}\n` +
95
+ "Pass either file paths (e.g. mops check src/main.mo) or canister names (e.g. mops check backend)",
56
96
  );
57
97
  }
98
+
99
+ if (isFileMode) {
100
+ await checkFiles(config, fileArgs, options);
101
+ } else {
102
+ if (!hasCanisters) {
103
+ cliError(
104
+ "No canisters defined in mops.toml.\n" +
105
+ "Either pass files: mops check <files...>\n" +
106
+ "Or define canisters in mops.toml:\n\n" +
107
+ " [canisters.backend]\n" +
108
+ ' main = "src/main.mo"',
109
+ );
110
+ }
111
+
112
+ const canisterNames = args.length > 0 ? args : undefined;
113
+ const filtered = filterCanisters(canisters, canisterNames);
114
+ await checkCanisters(config, filtered, options);
115
+ }
116
+
117
+ if (config.toolchain?.lintoko) {
118
+ const rootDir = getRootDir();
119
+ const lintRules = await collectLintRules(config, rootDir);
120
+ const lintFiles = isFileMode ? fileArgs : undefined;
121
+ await lint(undefined, {
122
+ verbose: options.verbose,
123
+ fix: options.fix,
124
+ rules: lintRules,
125
+ files: lintFiles,
126
+ });
127
+ }
128
+ }
129
+
130
+ async function checkCanisters(
131
+ config: Config,
132
+ canisters: Record<string, CanisterConfig>,
133
+ options: Partial<CheckOptions>,
134
+ ): Promise<void> {
58
135
  const mocPath = await toolchain.bin("moc", { fallback: true });
59
- const sources = await sourcesArgs();
136
+ const sources = (await sourcesArgs()).flat();
60
137
  const globalMocArgs = getGlobalMocArgs(config);
138
+ const allLibs = checkAllLibsSupport(options.verbose);
61
139
 
62
- // --all-libs enables richer diagnostics with edit suggestions from moc (requires moc >= 1.3.0)
63
- const allLibs = supportsAllLibsFlag();
140
+ for (const [canisterName, canister] of Object.entries(canisters)) {
141
+ if (!canister.main) {
142
+ cliError(
143
+ `No main file specified for canister '${canisterName}' in mops.toml`,
144
+ );
145
+ }
64
146
 
65
- if (!allLibs) {
66
- console.log(
67
- chalk.yellow(
68
- `moc < ${MOC_ALL_LIBS_MIN_VERSION}: some diagnostic hints may be missing`,
69
- ),
70
- );
71
- } else if (options.verbose) {
72
- console.log(
73
- chalk.blue("check"),
74
- chalk.gray("Using --all-libs for richer diagnostics"),
75
- );
147
+ validateCanisterArgs(canister, canisterName);
148
+ const motokoPath = resolveConfigPath(canister.main);
149
+
150
+ const mocArgs = [
151
+ "--check",
152
+ ...(allLibs ? ["--all-libs"] : []),
153
+ ...sources,
154
+ ...globalMocArgs,
155
+ ...(canister.args ?? []),
156
+ ...(options.extraArgs ?? []),
157
+ ];
158
+
159
+ if (options.fix) {
160
+ if (options.verbose) {
161
+ console.log(
162
+ chalk.blue("check"),
163
+ chalk.gray(`Attempting to fix ${canisterName}`),
164
+ );
165
+ }
166
+
167
+ const fixResult = await autofixMotoko(mocPath, [motokoPath], mocArgs);
168
+ logAutofixResult(fixResult, options.verbose);
169
+ }
170
+
171
+ try {
172
+ const args = [motokoPath, ...mocArgs];
173
+ if (options.verbose) {
174
+ console.log(
175
+ chalk.blue("check"),
176
+ chalk.gray(`Checking canister ${canisterName}:`),
177
+ );
178
+ console.log(chalk.gray(mocPath, JSON.stringify(args)));
179
+ }
180
+
181
+ const result = await execa(mocPath, args, {
182
+ stdio: "inherit",
183
+ reject: false,
184
+ });
185
+
186
+ if (result.exitCode !== 0) {
187
+ cliError(
188
+ `✗ Check failed for canister ${canisterName} (exit code: ${result.exitCode})`,
189
+ );
190
+ }
191
+
192
+ console.log(chalk.green(`✓ ${canisterName}`));
193
+ } catch (err: any) {
194
+ cliError(
195
+ `Error while checking canister ${canisterName}${err?.message ? `\n${err.message}` : ""}`,
196
+ );
197
+ }
198
+
199
+ const stablePath = resolveStablePath(canister, canisterName);
200
+ if (stablePath) {
201
+ await runStableCheck({
202
+ oldFile: stablePath,
203
+ canisterMain: motokoPath,
204
+ canisterName,
205
+ mocPath,
206
+ globalMocArgs,
207
+ canisterArgs: canister.args ?? [],
208
+ sources,
209
+ options: { verbose: options.verbose, extraArgs: options.extraArgs },
210
+ });
211
+ }
76
212
  }
213
+ }
214
+
215
+ async function checkFiles(
216
+ config: Config,
217
+ files: string[],
218
+ options: Partial<CheckOptions>,
219
+ ): Promise<void> {
220
+ const mocPath = await toolchain.bin("moc", { fallback: true });
221
+ const sources = (await sourcesArgs()).flat();
222
+ const globalMocArgs = getGlobalMocArgs(config);
223
+ const allLibs = checkAllLibsSupport(options.verbose);
77
224
 
78
225
  const mocArgs = [
79
226
  "--check",
80
227
  ...(allLibs ? ["--all-libs"] : []),
81
- ...sources.flat(),
228
+ ...sources,
82
229
  ...globalMocArgs,
83
230
  ...(options.extraArgs ?? []),
84
231
  ];
@@ -88,32 +235,11 @@ export async function check(
88
235
  console.log(chalk.blue("check"), chalk.gray("Attempting to fix files"));
89
236
  }
90
237
 
91
- const fixResult = await autofixMotoko(mocPath, fileList, mocArgs);
92
- if (fixResult) {
93
- for (const [file, codes] of fixResult.fixedFiles) {
94
- const unique = [...new Set(codes)].sort();
95
- const n = codes.length;
96
- const rel = path.relative(process.cwd(), file);
97
- console.log(
98
- chalk.green(
99
- `Fixed ${rel} (${n} ${n === 1 ? "fix" : "fixes"}: ${unique.join(", ")})`,
100
- ),
101
- );
102
- }
103
- const fileCount = fixResult.fixedFiles.size;
104
- console.log(
105
- chalk.green(
106
- `\n✓ ${fixResult.totalFixCount} ${fixResult.totalFixCount === 1 ? "fix" : "fixes"} applied to ${fileCount} ${fileCount === 1 ? "file" : "files"}`,
107
- ),
108
- );
109
- } else {
110
- if (options.verbose) {
111
- console.log(chalk.yellow("No fixes were needed"));
112
- }
113
- }
238
+ const fixResult = await autofixMotoko(mocPath, files, mocArgs);
239
+ logAutofixResult(fixResult, options.verbose);
114
240
  }
115
241
 
116
- for (const file of fileList) {
242
+ for (const file of files) {
117
243
  try {
118
244
  const args = [file, ...mocArgs];
119
245
  if (options.verbose) {
@@ -139,49 +265,4 @@ export async function check(
139
265
  );
140
266
  }
141
267
  }
142
-
143
- const canisters = resolveCanisterConfigs(config);
144
- for (const [name, canister] of Object.entries(canisters)) {
145
- const stableConfig = canister["check-stable"];
146
- if (!stableConfig) {
147
- continue;
148
- }
149
-
150
- if (!canister.main) {
151
- cliError(`No main file specified for canister '${name}' in mops.toml`);
152
- }
153
-
154
- const stablePath = resolveConfigPath(stableConfig.path);
155
- if (!existsSync(stablePath)) {
156
- if (stableConfig.skipIfMissing) {
157
- continue;
158
- }
159
- cliError(
160
- `Deployed file not found: ${stablePath} (canister '${name}')\n` +
161
- "Set skipIfMissing = true in [canisters." +
162
- name +
163
- ".check-stable] to skip this check when the file is missing.",
164
- );
165
- }
166
-
167
- await runStableCheck({
168
- oldFile: stablePath,
169
- canisterMain: resolveConfigPath(canister.main),
170
- canisterName: name,
171
- mocPath,
172
- globalMocArgs,
173
- options: { verbose: options.verbose, extraArgs: options.extraArgs },
174
- });
175
- }
176
-
177
- if (config.toolchain?.lintoko) {
178
- const rootDir = getRootDir();
179
- const lintRules = await collectLintRules(config, rootDir);
180
- await lint(undefined, {
181
- verbose: options.verbose,
182
- fix: options.fix,
183
- rules: lintRules,
184
- files: explicitFiles.length > 0 ? explicitFiles : undefined,
185
- });
186
- }
187
268
  }
@@ -0,0 +1,103 @@
1
+ import process from "node:process";
2
+ import chalk from "chalk";
3
+ import { mainActor } from "../api/actors.js";
4
+ import { resolveVersion } from "../api/resolveVersion.js";
5
+ import type { PackageDetails } from "../declarations/main/main.did.js";
6
+
7
+ function label(text: string): string {
8
+ return chalk.bold(text.padEnd(16));
9
+ }
10
+
11
+ export interface InfoOptions {
12
+ versions?: boolean;
13
+ }
14
+
15
+ export async function info(pkgArg: string, options: InfoOptions = {}) {
16
+ let [name, versionArg] = pkgArg.split("@") as [string, string | undefined];
17
+ let actor = await mainActor();
18
+
19
+ let version: string;
20
+ try {
21
+ version = await resolveVersion(name, versionArg ?? "");
22
+ } catch (err) {
23
+ let message = err instanceof Error ? err.message : String(err);
24
+ console.error(chalk.red("Error: ") + message);
25
+ process.exit(1);
26
+ }
27
+
28
+ let res = await actor.getPackageDetails(name, version);
29
+ if ("err" in res) {
30
+ console.error(chalk.red("Error: ") + res.err);
31
+ process.exit(1);
32
+ }
33
+
34
+ let d: PackageDetails = res.ok;
35
+ let c = d.config;
36
+
37
+ // d.versions is in ascending order (oldest first)
38
+ if (options.versions) {
39
+ for (let ver of d.versions) {
40
+ console.log(ver);
41
+ }
42
+ return;
43
+ }
44
+
45
+ console.log("");
46
+ console.log(
47
+ `${chalk.green.bold(c.name)}${chalk.gray("@")}${chalk.yellow(c.version)}`,
48
+ );
49
+
50
+ if (c.description) {
51
+ console.log(chalk.dim(c.description));
52
+ }
53
+
54
+ if (c.version !== d.highestVersion) {
55
+ console.log(chalk.yellow(`latest: ${d.highestVersion}`));
56
+ }
57
+
58
+ console.log("");
59
+
60
+ if (c.license) {
61
+ console.log(`${label("license")}${c.license}`);
62
+ }
63
+ if (c.repository) {
64
+ console.log(`${label("repository")}${chalk.cyan(c.repository)}`);
65
+ }
66
+ if (c.homepage) {
67
+ console.log(`${label("homepage")}${chalk.cyan(c.homepage)}`);
68
+ }
69
+ if (c.documentation) {
70
+ console.log(`${label("documentation")}${chalk.cyan(c.documentation)}`);
71
+ }
72
+
73
+ if (c.dependencies.length > 0) {
74
+ console.log("");
75
+ console.log(
76
+ `${label("dependencies")}${c.dependencies.map((dep) => `${dep.name}${chalk.gray("@")}${dep.version || dep.repo}`).join(", ")}`,
77
+ );
78
+ }
79
+ if (c.devDependencies.length > 0) {
80
+ console.log(
81
+ `${label("dev-deps")}${c.devDependencies.map((dep) => `${dep.name}${chalk.gray("@")}${dep.version || dep.repo}`).join(", ")}`,
82
+ );
83
+ }
84
+
85
+ if (c.keywords.length > 0) {
86
+ console.log("");
87
+ console.log(
88
+ `${label("keywords")}${c.keywords.map((k) => chalk.yellow(k)).join(", ")}`,
89
+ );
90
+ }
91
+
92
+ if (d.versions.length > 0) {
93
+ let versionsDisplay = d.versions.slice(-10).reverse().join(", ");
94
+ let extra =
95
+ d.versions.length > 10
96
+ ? ` ${chalk.gray(`(+${d.versions.length - 10} more)`)}`
97
+ : "";
98
+ console.log("");
99
+ console.log(`${label("versions")}${versionsDisplay}${extra}`);
100
+ }
101
+
102
+ console.log("");
103
+ }
package/commands/lint.ts CHANGED
@@ -103,6 +103,62 @@ export interface LintOptions {
103
103
  extraArgs: string[];
104
104
  }
105
105
 
106
+ function buildCommonArgs(
107
+ options: Partial<LintOptions>,
108
+ config: Config,
109
+ ): string[] {
110
+ const args: string[] = [];
111
+ if (options.verbose) {
112
+ args.push("--verbose");
113
+ }
114
+ if (options.fix) {
115
+ args.push("--fix");
116
+ }
117
+ if (config.lint?.args) {
118
+ if (typeof config.lint.args === "string") {
119
+ cliError(
120
+ `[lint] config 'args' should be an array of strings in mops.toml config file`,
121
+ );
122
+ }
123
+ args.push(...config.lint.args);
124
+ }
125
+ if (options.extraArgs && options.extraArgs.length > 0) {
126
+ args.push(...options.extraArgs);
127
+ }
128
+ return args;
129
+ }
130
+
131
+ async function runLintoko(
132
+ lintokoBinPath: string,
133
+ rootDir: string,
134
+ args: string[],
135
+ options: Partial<LintOptions>,
136
+ label: string,
137
+ ): Promise<boolean> {
138
+ try {
139
+ if (options.verbose) {
140
+ console.log(
141
+ chalk.blue("lint"),
142
+ chalk.gray(`Running lintoko (${label}):`),
143
+ );
144
+ console.log(chalk.gray(lintokoBinPath));
145
+ console.log(chalk.gray(JSON.stringify(args)));
146
+ }
147
+
148
+ const result = await execa(lintokoBinPath, args, {
149
+ cwd: rootDir,
150
+ stdio: "inherit",
151
+ reject: false,
152
+ });
153
+
154
+ return result.exitCode === 0;
155
+ } catch (err: any) {
156
+ cliError(
157
+ `Error while running lintoko${err?.message ? `\n${err.message}` : ""}`,
158
+ );
159
+ }
160
+ }
161
+
106
162
  export async function lint(
107
163
  filter: string | undefined,
108
164
  options: Partial<LintOptions>,
@@ -131,59 +187,93 @@ export async function lint(
131
187
  }
132
188
  }
133
189
 
134
- let args: string[] = [];
135
- if (options.verbose) {
136
- args.push("--verbose");
137
- }
138
- if (options.fix) {
139
- args.push("--fix");
140
- }
190
+ const commonArgs = buildCommonArgs(options, config);
191
+
192
+ // --- base run ---
193
+ const baseArgs: string[] = [...commonArgs];
141
194
  const rules =
142
195
  options.rules !== undefined
143
196
  ? options.rules
144
197
  : await collectLintRules(config, rootDir);
145
- rules.forEach((rule) => args.push("--rules", rule));
198
+ rules.forEach((rule) => baseArgs.push("--rules", rule));
199
+ baseArgs.push(...filesToLint);
146
200
 
147
- if (config.lint?.args) {
148
- if (typeof config.lint.args === "string") {
149
- cliError(
150
- `[lint] config 'args' should be an array of strings in mops.toml config file`,
151
- );
152
- }
153
- args.push(...config.lint.args);
154
- }
201
+ let failed = !(await runLintoko(
202
+ lintokoBinPath,
203
+ rootDir,
204
+ baseArgs,
205
+ options,
206
+ "base",
207
+ ));
155
208
 
156
- if (options.extraArgs && options.extraArgs.length > 0) {
157
- args.push(...options.extraArgs);
158
- }
209
+ // --- extra runs ---
210
+ const extraEntries = config.lint?.extra;
211
+ if (extraEntries) {
212
+ const isFiltered = filter || (options.files && options.files.length > 0);
213
+ const baseFileSet = isFiltered
214
+ ? new Set(filesToLint.map((f) => path.resolve(rootDir, f)))
215
+ : undefined;
159
216
 
160
- args.push(...filesToLint);
217
+ for (const [globPattern, ruleDirs] of Object.entries(extraEntries)) {
218
+ if (!Array.isArray(ruleDirs) || ruleDirs.length === 0) {
219
+ console.warn(
220
+ chalk.yellow(
221
+ `[lint.extra] skipping '${globPattern}': value must be a non-empty array of rule directories`,
222
+ ),
223
+ );
224
+ continue;
225
+ }
161
226
 
162
- try {
163
- if (options.verbose) {
164
- console.log(chalk.blue("lint"), chalk.gray("Running lintoko:"));
165
- console.log(chalk.gray(lintokoBinPath));
166
- console.log(chalk.gray(JSON.stringify(args)));
167
- }
227
+ for (const dir of ruleDirs) {
228
+ if (!existsSync(path.join(rootDir, dir))) {
229
+ cliError(
230
+ `[lint.extra] rule directory '${dir}' not found (referenced by glob '${globPattern}')`,
231
+ );
232
+ }
233
+ }
168
234
 
169
- const result = await execa(lintokoBinPath, args, {
170
- cwd: rootDir,
171
- stdio: "inherit",
172
- reject: false,
173
- });
235
+ let matchedFiles = globSync(path.join(rootDir, globPattern), {
236
+ ...MOTOKO_GLOB_CONFIG,
237
+ cwd: rootDir,
238
+ });
174
239
 
175
- if (result.exitCode !== 0) {
176
- cliError(`Lint failed with exit code ${result.exitCode}`);
177
- }
240
+ if (baseFileSet) {
241
+ matchedFiles = matchedFiles.filter((f) =>
242
+ baseFileSet.has(path.resolve(rootDir, f)),
243
+ );
244
+ }
178
245
 
179
- if (options.fix) {
180
- console.log(chalk.green("✓ Lint fixes applied"));
181
- } else {
182
- console.log(chalk.green("✓ Lint succeeded"));
246
+ if (matchedFiles.length === 0) {
247
+ console.warn(
248
+ chalk.yellow(
249
+ `[lint.extra] no files matched glob '${globPattern}', skipping`,
250
+ ),
251
+ );
252
+ continue;
253
+ }
254
+
255
+ const extraArgs: string[] = [...commonArgs];
256
+ for (const dir of ruleDirs) {
257
+ extraArgs.push("--rules", dir);
258
+ }
259
+ extraArgs.push(...matchedFiles);
260
+
261
+ const passed = await runLintoko(
262
+ lintokoBinPath,
263
+ rootDir,
264
+ extraArgs,
265
+ options,
266
+ `extra: ${globPattern}`,
267
+ );
268
+ failed ||= !passed;
183
269
  }
184
- } catch (err: any) {
185
- cliError(
186
- `Error while running lintoko${err?.message ? `\n${err.message}` : ""}`,
187
- );
270
+ }
271
+
272
+ if (failed) {
273
+ cliError("Lint failed");
274
+ } else if (options.fix) {
275
+ console.log(chalk.green("✓ Lint fixes applied"));
276
+ } else {
277
+ console.log(chalk.green("✓ Lint succeeded"));
188
278
  }
189
279
  }
@@ -8,7 +8,7 @@ import * as toolchainUtils from "./toolchain-utils.js";
8
8
 
9
9
  let cacheDir = path.join(globalCacheDir, "moc");
10
10
 
11
- export let repo = "dfinity/motoko";
11
+ export let repo = "caffeinelabs/motoko";
12
12
 
13
13
  export let getLatestReleaseTag = async () => {
14
14
  return toolchainUtils.getLatestReleaseTag(repo);
@@ -44,7 +44,7 @@ export let download = async (
44
44
  }
45
45
  } else {
46
46
  // Download the .js artifact
47
- const jsUrl = `https://github.com/dfinity/motoko/releases/download/${version}/moc-${version}.js`;
47
+ const jsUrl = `https://github.com/caffeinelabs/motoko/releases/download/${version}/moc-${version}.js`;
48
48
  const jsDestPath = path.join(destDir, "moc.js");
49
49
 
50
50
  if (verbose && !silent) {
@@ -75,14 +75,14 @@ export let download = async (
75
75
  ? "arm64"
76
76
  : "aarch64"
77
77
  : "x86_64";
78
- url = `https://github.com/dfinity/motoko/releases/download/${version}/motoko-${platfrom}-${arch}-${version}.tar.gz`;
78
+ url = `https://github.com/caffeinelabs/motoko/releases/download/${version}/motoko-${platfrom}-${arch}-${version}.tar.gz`;
79
79
  } else if (new SemVer(version).compare(new SemVer("0.9.5")) >= 0) {
80
80
  let platfrom = process.platform == "darwin" ? "Darwin" : "Linux";
81
81
  let arch = "x86_64";
82
- url = `https://github.com/dfinity/motoko/releases/download/${version}/motoko-${platfrom}-${arch}-${version}.tar.gz`;
82
+ url = `https://github.com/caffeinelabs/motoko/releases/download/${version}/motoko-${platfrom}-${arch}-${version}.tar.gz`;
83
83
  } else {
84
84
  let platfrom = process.platform == "darwin" ? "macos" : "linux64";
85
- url = `https://github.com/dfinity/motoko/releases/download/${version}/motoko-${platfrom}-${version}.tar.gz`;
85
+ url = `https://github.com/caffeinelabs/motoko/releases/download/${version}/motoko-${platfrom}-${version}.tar.gz`;
86
86
  }
87
87
 
88
88
  if (verbose && !silent) {