ic-mops 2.0.0 → 2.1.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 (197) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/RELEASE.md +179 -0
  3. package/bundle/cli.tgz +0 -0
  4. package/check-requirements.ts +3 -8
  5. package/cli.ts +79 -11
  6. package/commands/bench/bench-canister.mo +17 -6
  7. package/commands/bench.ts +2 -13
  8. package/commands/build.ts +42 -17
  9. package/commands/check.ts +117 -0
  10. package/commands/format.ts +3 -18
  11. package/commands/lint.ts +92 -0
  12. package/commands/sync.ts +2 -8
  13. package/commands/test/test.ts +7 -19
  14. package/commands/toolchain/index.ts +21 -8
  15. package/commands/toolchain/lintoko.ts +54 -0
  16. package/commands/toolchain/toolchain-utils.ts +2 -0
  17. package/constants.ts +23 -0
  18. package/dist/check-requirements.js +3 -8
  19. package/dist/cli.js +60 -10
  20. package/dist/commands/bench/bench-canister.mo +17 -6
  21. package/dist/commands/bench.js +2 -11
  22. package/dist/commands/build.js +38 -16
  23. package/dist/commands/check.d.ts +6 -0
  24. package/dist/commands/check.js +78 -0
  25. package/dist/commands/format.js +3 -16
  26. package/dist/commands/lint.d.ts +7 -0
  27. package/dist/commands/lint.js +69 -0
  28. package/dist/commands/sync.js +2 -7
  29. package/dist/commands/test/test.js +7 -17
  30. package/dist/commands/toolchain/index.d.ts +2 -2
  31. package/dist/commands/toolchain/index.js +18 -7
  32. package/dist/commands/toolchain/lintoko.d.ts +8 -0
  33. package/dist/commands/toolchain/lintoko.js +36 -0
  34. package/dist/commands/toolchain/toolchain-utils.d.ts +1 -0
  35. package/dist/commands/toolchain/toolchain-utils.js +1 -0
  36. package/dist/constants.d.ts +15 -0
  37. package/dist/constants.js +21 -0
  38. package/dist/environments/nodejs/cli.js +6 -1
  39. package/dist/helpers/autofix-motoko.d.ts +26 -0
  40. package/dist/helpers/autofix-motoko.js +105 -0
  41. package/dist/helpers/get-moc-version.d.ts +2 -0
  42. package/dist/helpers/get-moc-version.js +10 -1
  43. package/dist/mops.js +2 -1
  44. package/dist/package.json +4 -3
  45. package/dist/tests/build-no-dfx.test.d.ts +1 -0
  46. package/dist/tests/build-no-dfx.test.js +9 -0
  47. package/dist/tests/build.test.d.ts +1 -0
  48. package/dist/tests/build.test.js +18 -0
  49. package/dist/tests/check-candid.test.d.ts +1 -0
  50. package/dist/tests/check-candid.test.js +20 -0
  51. package/dist/tests/check-fix.test.d.ts +1 -0
  52. package/dist/tests/check-fix.test.js +73 -0
  53. package/dist/tests/check.test.d.ts +1 -0
  54. package/dist/tests/check.test.js +33 -0
  55. package/dist/tests/cli.test.js +4 -57
  56. package/dist/tests/helpers.d.ts +22 -0
  57. package/dist/tests/helpers.js +43 -0
  58. package/dist/tests/lint.test.d.ts +1 -0
  59. package/dist/tests/lint.test.js +15 -0
  60. package/dist/tests/toolchain.test.d.ts +1 -0
  61. package/dist/tests/toolchain.test.js +11 -0
  62. package/dist/types.d.ts +6 -1
  63. package/dist/wasm/pkg/nodejs/wasm.d.ts +3 -0
  64. package/dist/wasm/pkg/nodejs/wasm.js +323 -17
  65. package/dist/wasm/pkg/nodejs/wasm_bg.wasm +0 -0
  66. package/dist/wasm/pkg/nodejs/wasm_bg.wasm.d.ts +6 -1
  67. package/dist/wasm/pkg/web/wasm.d.ts +10 -1
  68. package/dist/wasm/pkg/web/wasm.js +300 -21
  69. package/dist/wasm/pkg/web/wasm_bg.wasm +0 -0
  70. package/dist/wasm/pkg/web/wasm_bg.wasm.d.ts +6 -1
  71. package/dist/wasm.d.ts +6 -1
  72. package/environments/nodejs/cli.ts +7 -1
  73. package/helpers/autofix-motoko.ts +170 -0
  74. package/helpers/get-moc-version.ts +12 -1
  75. package/mops.ts +2 -1
  76. package/package.json +4 -3
  77. package/tests/__snapshots__/build-no-dfx.test.ts.snap +11 -0
  78. package/tests/__snapshots__/build.test.ts.snap +77 -0
  79. package/tests/__snapshots__/check-candid.test.ts.snap +73 -0
  80. package/tests/__snapshots__/check-fix.test.ts.snap +242 -0
  81. package/tests/__snapshots__/check.test.ts.snap +72 -0
  82. package/tests/__snapshots__/lint.test.ts.snap +78 -0
  83. package/tests/build/error/src/Bar.mo +2 -2
  84. package/tests/build/no-dfx/mops.toml +5 -0
  85. package/tests/build/no-dfx/src/Main.mo +5 -0
  86. package/tests/build/success/candid/bar.did +1 -0
  87. package/tests/build/success/mops.toml +8 -3
  88. package/tests/build-no-dfx.test.ts +10 -0
  89. package/tests/build.test.ts +24 -0
  90. package/tests/check/error/Error.mo +7 -0
  91. package/tests/check/error/mops.toml +2 -0
  92. package/tests/check/fix/M0223.mo +11 -0
  93. package/tests/check/fix/M0236.mo +11 -0
  94. package/tests/check/fix/M0237.mo +11 -0
  95. package/tests/check/fix/Ok.mo +7 -0
  96. package/tests/check/fix/edit-suggestions.mo +143 -0
  97. package/tests/check/fix/mops.toml +5 -0
  98. package/tests/check/fix/transitive-lib.mo +9 -0
  99. package/tests/check/fix/transitive-main.mo +9 -0
  100. package/tests/check/success/Ok.mo +5 -0
  101. package/tests/check/success/Warning.mo +5 -0
  102. package/tests/check/success/mops.toml +2 -0
  103. package/tests/check-candid.test.ts +22 -0
  104. package/tests/check-fix.test.ts +111 -0
  105. package/tests/check.test.ts +46 -0
  106. package/tests/cli.test.ts +4 -74
  107. package/tests/helpers.ts +58 -0
  108. package/tests/lint/lints/no-bool-switch.toml +9 -0
  109. package/tests/lint/mops.toml +4 -0
  110. package/tests/lint/src/NoBoolSwitch.mo +8 -0
  111. package/tests/lint/src/Ok.mo +5 -0
  112. package/tests/lint.test.ts +17 -0
  113. package/tests/toolchain/mock +2 -0
  114. package/tests/toolchain/mops.toml +2 -0
  115. package/tests/toolchain.test.ts +12 -0
  116. package/types.ts +6 -1
  117. package/wasm/Cargo.lock +101 -54
  118. package/wasm/Cargo.toml +2 -5
  119. package/wasm/pkg/nodejs/wasm.d.ts +3 -0
  120. package/wasm/pkg/nodejs/wasm.js +323 -17
  121. package/wasm/pkg/nodejs/wasm_bg.wasm +0 -0
  122. package/wasm/pkg/nodejs/wasm_bg.wasm.d.ts +6 -1
  123. package/wasm/pkg/web/wasm.d.ts +10 -1
  124. package/wasm/pkg/web/wasm.js +300 -21
  125. package/wasm/pkg/web/wasm_bg.wasm +0 -0
  126. package/wasm/pkg/web/wasm_bg.wasm.d.ts +6 -1
  127. package/wasm/src/lib.rs +10 -5
  128. package/wasm/src/utils.rs +15 -0
  129. package/wasm/src/wasm_utils.rs +79 -0
  130. package/wasm.ts +10 -1
  131. package/.DS_Store +0 -0
  132. package/bundle/bench/bench-canister.mo +0 -121
  133. package/bundle/bench/user-bench.mo +0 -10
  134. package/bundle/bin/moc-wrapper.sh +0 -40
  135. package/bundle/bin/mops.js +0 -3
  136. package/bundle/cli.js +0 -2144
  137. package/bundle/declarations/bench/bench.did +0 -30
  138. package/bundle/declarations/bench/bench.did.d.ts +0 -33
  139. package/bundle/declarations/bench/bench.did.js +0 -30
  140. package/bundle/declarations/bench/index.d.ts +0 -50
  141. package/bundle/declarations/bench/index.js +0 -40
  142. package/bundle/declarations/main/index.d.ts +0 -50
  143. package/bundle/declarations/main/index.js +0 -40
  144. package/bundle/declarations/main/main.did +0 -428
  145. package/bundle/declarations/main/main.did.d.ts +0 -348
  146. package/bundle/declarations/main/main.did.js +0 -406
  147. package/bundle/declarations/storage/index.d.ts +0 -50
  148. package/bundle/declarations/storage/index.js +0 -30
  149. package/bundle/declarations/storage/storage.did +0 -46
  150. package/bundle/declarations/storage/storage.did.d.ts +0 -40
  151. package/bundle/declarations/storage/storage.did.js +0 -38
  152. package/bundle/package.json +0 -36
  153. package/bundle/templates/README.md +0 -13
  154. package/bundle/templates/licenses/Apache-2.0 +0 -202
  155. package/bundle/templates/licenses/Apache-2.0-NOTICE +0 -13
  156. package/bundle/templates/licenses/MIT +0 -21
  157. package/bundle/templates/mops-publish.yml +0 -17
  158. package/bundle/templates/mops-test.yml +0 -24
  159. package/bundle/templates/src/lib.mo +0 -15
  160. package/bundle/templates/test/lib.test.mo +0 -4
  161. package/bundle/wasm_bg.wasm +0 -0
  162. package/bundle/xhr-sync-worker.js +0 -59
  163. package/dist/wasm/pkg/bundler/package.json +0 -20
  164. package/dist/wasm/pkg/bundler/wasm.d.ts +0 -3
  165. package/dist/wasm/pkg/bundler/wasm.js +0 -5
  166. package/dist/wasm/pkg/bundler/wasm_bg.js +0 -93
  167. package/dist/wasm/pkg/bundler/wasm_bg.wasm +0 -0
  168. package/dist/wasm/pkg/bundler/wasm_bg.wasm.d.ts +0 -8
  169. package/tests/__snapshots__/cli.test.ts.snap +0 -202
  170. package/tests/build/success/.dfx/local/canister_ids.json +0 -17
  171. package/tests/build/success/.dfx/local/canisters/bar/bar.did +0 -3
  172. package/tests/build/success/.dfx/local/canisters/bar/bar.most +0 -4
  173. package/tests/build/success/.dfx/local/canisters/bar/bar.wasm +0 -0
  174. package/tests/build/success/.dfx/local/canisters/bar/constructor.did +0 -3
  175. package/tests/build/success/.dfx/local/canisters/bar/index.js +0 -42
  176. package/tests/build/success/.dfx/local/canisters/bar/init_args.txt +0 -1
  177. package/tests/build/success/.dfx/local/canisters/bar/service.did +0 -3
  178. package/tests/build/success/.dfx/local/canisters/bar/service.did.d.ts +0 -7
  179. package/tests/build/success/.dfx/local/canisters/bar/service.did.js +0 -4
  180. package/tests/build/success/.dfx/local/canisters/foo/constructor.did +0 -3
  181. package/tests/build/success/.dfx/local/canisters/foo/foo.did +0 -3
  182. package/tests/build/success/.dfx/local/canisters/foo/foo.most +0 -4
  183. package/tests/build/success/.dfx/local/canisters/foo/foo.wasm +0 -0
  184. package/tests/build/success/.dfx/local/canisters/foo/index.js +0 -42
  185. package/tests/build/success/.dfx/local/canisters/foo/init_args.txt +0 -1
  186. package/tests/build/success/.dfx/local/canisters/foo/service.did +0 -3
  187. package/tests/build/success/.dfx/local/canisters/foo/service.did.d.ts +0 -7
  188. package/tests/build/success/.dfx/local/canisters/foo/service.did.js +0 -4
  189. package/tests/build/success/.dfx/local/lsp/ucwa4-rx777-77774-qaada-cai.did +0 -3
  190. package/tests/build/success/.dfx/local/lsp/ulvla-h7777-77774-qaacq-cai.did +0 -3
  191. package/tests/build/success/.dfx/local/network-id +0 -4
  192. package/wasm/pkg/bundler/package.json +0 -20
  193. package/wasm/pkg/bundler/wasm.d.ts +0 -3
  194. package/wasm/pkg/bundler/wasm.js +0 -5
  195. package/wasm/pkg/bundler/wasm_bg.js +0 -93
  196. package/wasm/pkg/bundler/wasm_bg.wasm +0 -0
  197. package/wasm/pkg/bundler/wasm_bg.wasm.d.ts +0 -8
@@ -2,14 +2,25 @@ import Nat64 "mo:core/Nat64";
2
2
  import Nat "mo:core/Nat";
3
3
  import Runtime "mo:core/Runtime";
4
4
  import InternetComputer "mo:core/InternetComputer";
5
- import Int64 "mo:core/Int64";
6
5
  import Region "mo:core/Region";
7
6
  import Prim "mo:prim";
8
- import Bench "mo:bench";
9
7
 
10
8
  import UserBench "./user-bench"; // file path will be replaced with the *.bench.mo file path
11
9
 
12
10
  persistent actor class () {
11
+ type BenchSchema = {
12
+ name : Text;
13
+ description : Text;
14
+ rows : [Text];
15
+ cols : [Text];
16
+ };
17
+
18
+ type Bench = {
19
+ getVersion : () -> Nat;
20
+ getSchema : () -> BenchSchema;
21
+ runCell : (Nat, Nat) -> ();
22
+ };
23
+
13
24
  type BenchResult = {
14
25
  instructions : Int;
15
26
  rts_mutator_instructions : Int;
@@ -23,16 +34,16 @@ persistent actor class () {
23
34
  rts_reclaimed : Int;
24
35
  };
25
36
 
26
- transient var benchOpt : ?Bench.Bench = null;
37
+ transient var benchOpt : ?Bench = null;
27
38
 
28
- public func init() : async Bench.BenchSchema {
39
+ public func init() : async BenchSchema {
29
40
  let bench = UserBench.init();
30
41
  benchOpt := ?bench;
31
42
  ignore Region.grow(Region.new(), 1);
32
43
  bench.getSchema();
33
44
  };
34
45
 
35
- public query func getSchema() : async Bench.BenchSchema {
46
+ public query func getSchema() : async BenchSchema {
36
47
  let ?bench = benchOpt else Runtime.trap("bench not initialized");
37
48
  bench.getSchema();
38
49
  };
@@ -41,7 +52,7 @@ persistent actor class () {
41
52
  {
42
53
  instructions = 0;
43
54
  rts_heap_size = Prim.rts_heap_size();
44
- stable_memory_size = Int64.toInt(Int64.fromNat64(Prim.stableMemorySize())) * 65536;
55
+ stable_memory_size = Prim.rts_stable_memory_size() * 65536;
45
56
  rts_stable_memory_size = Prim.rts_stable_memory_size();
46
57
  rts_logical_stable_memory_size = Prim.rts_logical_stable_memory_size();
47
58
  rts_memory_size = Prim.rts_memory_size();
@@ -18,17 +18,8 @@ import { getMocVersion } from "../helpers/get-moc-version.js";
18
18
  import { getDfxVersion } from "../helpers/get-dfx-version.js";
19
19
  import { getMocPath } from "../helpers/get-moc-path.js";
20
20
  import { sources } from "./sources.js";
21
+ import { MOTOKO_GLOB_CONFIG } from "../constants.js";
21
22
  import { BenchReplica } from "./bench-replica.js";
22
- let ignore = [
23
- "**/node_modules/**",
24
- "**/.mops/**",
25
- "**/.vessel/**",
26
- "**/.git/**",
27
- ];
28
- let globConfig = {
29
- nocase: true,
30
- ignore: ignore,
31
- };
32
23
  export async function bench(filter = "", optionsArg = {}) {
33
24
  let config = readConfig();
34
25
  let dfxJson = readDfxJson();
@@ -75,7 +66,7 @@ export async function bench(filter = "", optionsArg = {}) {
75
66
  if (filter) {
76
67
  globStr = `**/bench?(mark)/**/*${filter}*.mo`;
77
68
  }
78
- let files = globSync(path.join(rootDir, globStr), globConfig);
69
+ let files = globSync(path.join(rootDir, globStr), MOTOKO_GLOB_CONFIG);
79
70
  if (!files.length) {
80
71
  if (filter) {
81
72
  options.silent ||
@@ -1,20 +1,21 @@
1
1
  import chalk from "chalk";
2
2
  import { execa } from "execa";
3
3
  import { exists } from "fs-extra";
4
- import { mkdir } from "node:fs/promises";
4
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
5
5
  import { join } from "node:path";
6
- import { getMocPath } from "../helpers/get-moc-path.js";
6
+ import { cliError } from "../error.js";
7
+ import { isCandidCompatible } from "../helpers/is-candid-compatible.js";
8
+ import { getWasmBindings } from "../wasm.js";
7
9
  import { readConfig } from "../mops.js";
8
10
  import { sourcesArgs } from "./sources.js";
9
- import { isCandidCompatible } from "../helpers/is-candid-compatible.js";
10
- import { cliError } from "../error.js";
11
+ import { toolchain } from "./toolchain/index.js";
11
12
  export const DEFAULT_BUILD_OUTPUT_DIR = ".mops/.build";
12
13
  export async function build(canisterNames, options) {
13
14
  if (canisterNames?.length == 0) {
14
15
  cliError("No canisters specified to build");
15
16
  }
16
17
  let outputDir = options.outputDir ?? DEFAULT_BUILD_OUTPUT_DIR;
17
- let mocPath = getMocPath();
18
+ let mocPath = await toolchain.bin("moc", { fallback: true });
18
19
  let canisters = {};
19
20
  let config = readConfig();
20
21
  if (config.canisters) {
@@ -42,17 +43,17 @@ export async function build(canisterNames, options) {
42
43
  ? Object.fromEntries(Object.entries(canisters).filter(([name]) => canisterNames.includes(name)))
43
44
  : canisters;
44
45
  for (let [canisterName, canister] of Object.entries(filteredCanisters)) {
45
- options.verbose && console.time(`build canister ${canisterName}`);
46
46
  console.log(chalk.blue("build canister"), chalk.bold(canisterName));
47
47
  let motokoPath = canister.main;
48
48
  if (!motokoPath) {
49
49
  cliError(`No main file is specified for canister ${canisterName}`);
50
50
  }
51
+ const wasmPath = join(outputDir, `${canisterName}.wasm`);
51
52
  let args = [
52
53
  "-c",
53
54
  "--idl",
54
55
  "-o",
55
- join(outputDir, `${canisterName}.wasm`),
56
+ wasmPath,
56
57
  motokoPath,
57
58
  ...(options.extraArgs ?? []),
58
59
  ...(await sourcesArgs()).flat(),
@@ -69,6 +70,12 @@ export async function build(canisterNames, options) {
69
70
  }
70
71
  args.push(...canister.args);
71
72
  }
73
+ const isPublicCandid = true; // always true for now to reduce corner cases
74
+ const candidVisibility = isPublicCandid ? "icp:public" : "icp:private";
75
+ if (isPublicCandid) {
76
+ args.push("--public-metadata", "candid:service");
77
+ args.push("--public-metadata", "candid:args");
78
+ }
72
79
  try {
73
80
  if (options.verbose) {
74
81
  console.log(chalk.gray(mocPath, JSON.stringify(args)));
@@ -92,8 +99,8 @@ export async function build(canisterNames, options) {
92
99
  if (options.verbose && result.stdout && result.stdout.trim()) {
93
100
  console.log(result.stdout);
94
101
  }
102
+ const generatedDidPath = join(outputDir, `${canisterName}.did`);
95
103
  if (canister.candid) {
96
- const generatedDidPath = join(outputDir, `${canisterName}.did`);
97
104
  const originalCandidPath = canister.candid;
98
105
  try {
99
106
  const compatible = await isCandidCompatible(generatedDidPath, originalCandidPath);
@@ -101,21 +108,36 @@ export async function build(canisterNames, options) {
101
108
  cliError(`Candid compatibility check failed for canister ${canisterName}`);
102
109
  }
103
110
  if (options.verbose) {
104
- console.log(chalk.green(`Candid compatibility check passed for canister ${canisterName}`));
111
+ console.log(chalk.gray(`Candid compatibility check passed for canister ${canisterName}`));
105
112
  }
106
113
  }
107
- catch (candidError) {
108
- cliError(`Error during Candid compatibility check for canister ${canisterName}`, candidError);
114
+ catch (err) {
115
+ cliError(`Error during Candid compatibility check for canister ${canisterName}${err?.message ? `\n${err.message}` : ""}`);
109
116
  }
110
117
  }
118
+ options.verbose &&
119
+ console.log(chalk.gray(`Adding metadata to ${wasmPath}`));
120
+ const candidPath = canister.candid ?? generatedDidPath;
121
+ const candidText = await readFile(candidPath, "utf-8");
122
+ const customSections = [
123
+ { name: `${candidVisibility} candid:service`, data: candidText },
124
+ ];
125
+ if (canister.initArg) {
126
+ customSections.push({
127
+ name: `${candidVisibility} candid:args`,
128
+ data: canister.initArg,
129
+ });
130
+ }
131
+ const wasmBytes = await readFile(wasmPath);
132
+ const newWasm = getWasmBindings().add_custom_sections(wasmBytes, customSections);
133
+ await writeFile(wasmPath, newWasm);
111
134
  }
112
- catch (cliError) {
113
- if (cliError.message?.includes("Build failed for canister")) {
114
- throw cliError;
135
+ catch (err) {
136
+ if (err.message?.includes("Build failed for canister")) {
137
+ throw err;
115
138
  }
116
- cliError(`Error while compiling canister ${canisterName}${cliError?.message ? `\n${cliError.message}` : ""}`);
139
+ cliError(`Error while compiling canister ${canisterName}${err?.message ? `\n${err.message}` : ""}`);
117
140
  }
118
- options.verbose && console.timeEnd(`build canister ${canisterName}`);
119
141
  }
120
142
  console.log(chalk.green(`\n✓ Built ${Object.keys(filteredCanisters).length} canister${Object.keys(filteredCanisters).length == 1 ? "" : "s"} successfully`));
121
143
  }
@@ -0,0 +1,6 @@
1
+ export interface CheckOptions {
2
+ verbose: boolean;
3
+ fix: boolean;
4
+ extraArgs: string[];
5
+ }
6
+ export declare function check(files: string | string[], options?: Partial<CheckOptions>): Promise<void>;
@@ -0,0 +1,78 @@
1
+ import { relative } from "node:path";
2
+ import chalk from "chalk";
3
+ import { execa } from "execa";
4
+ import { cliError } from "../error.js";
5
+ import { autofixMotoko } from "../helpers/autofix-motoko.js";
6
+ import { getMocSemVer } from "../helpers/get-moc-version.js";
7
+ import { sourcesArgs } from "./sources.js";
8
+ import { toolchain } from "./toolchain/index.js";
9
+ const MOC_ALL_LIBS_MIN_VERSION = "1.3.0";
10
+ function supportsAllLibsFlag() {
11
+ const version = getMocSemVer();
12
+ return version ? version.compare(MOC_ALL_LIBS_MIN_VERSION) >= 0 : false;
13
+ }
14
+ export async function check(files, options = {}) {
15
+ const fileList = Array.isArray(files) ? files : [files];
16
+ if (fileList.length === 0) {
17
+ cliError("No Motoko files specified for checking");
18
+ }
19
+ const mocPath = await toolchain.bin("moc", { fallback: true });
20
+ const sources = await sourcesArgs();
21
+ // --all-libs enables richer diagnostics with edit suggestions from moc (requires moc >= 1.3.0)
22
+ const allLibs = supportsAllLibsFlag();
23
+ if (!allLibs) {
24
+ console.log(chalk.yellow(`moc < ${MOC_ALL_LIBS_MIN_VERSION}: some diagnostic hints may be missing`));
25
+ }
26
+ else if (options.verbose) {
27
+ console.log(chalk.blue("check"), chalk.gray("Using --all-libs for richer diagnostics"));
28
+ }
29
+ const mocArgs = [
30
+ "--check",
31
+ ...(allLibs ? ["--all-libs"] : []),
32
+ ...sources.flat(),
33
+ ...(options.extraArgs ?? []),
34
+ ];
35
+ if (options.fix) {
36
+ if (options.verbose) {
37
+ console.log(chalk.blue("check"), chalk.gray("Attempting to fix files"));
38
+ }
39
+ const fixResult = await autofixMotoko(mocPath, fileList, mocArgs);
40
+ if (fixResult) {
41
+ for (const [file, codes] of fixResult.fixedFiles) {
42
+ const unique = [...new Set(codes)].sort();
43
+ const n = codes.length;
44
+ const rel = relative(process.cwd(), file);
45
+ console.log(chalk.green(`Fixed ${rel} (${n} ${n === 1 ? "fix" : "fixes"}: ${unique.join(", ")})`));
46
+ }
47
+ const fileCount = fixResult.fixedFiles.size;
48
+ console.log(chalk.green(`\n✓ ${fixResult.totalFixCount} ${fixResult.totalFixCount === 1 ? "fix" : "fixes"} applied to ${fileCount} ${fileCount === 1 ? "file" : "files"}`));
49
+ }
50
+ else {
51
+ if (options.verbose) {
52
+ console.log(chalk.yellow("No fixes were needed"));
53
+ }
54
+ }
55
+ }
56
+ for (const file of fileList) {
57
+ try {
58
+ const args = [file, ...mocArgs];
59
+ if (options.verbose) {
60
+ console.log(chalk.blue("check"), chalk.gray("Running moc:"));
61
+ console.log(chalk.gray(mocPath, JSON.stringify(args)));
62
+ }
63
+ const result = await execa(mocPath, args, {
64
+ stdio: "inherit",
65
+ reject: false,
66
+ });
67
+ if (result.exitCode !== 0) {
68
+ cliError(`✗ Check failed for file ${file} (exit code: ${result.exitCode})`);
69
+ }
70
+ if (!options.fix) {
71
+ console.log(chalk.green(`✓ ${file}`));
72
+ }
73
+ }
74
+ catch (err) {
75
+ cliError(`Error while checking ${file}${err?.message ? `\n${err.message}` : ""}`);
76
+ }
77
+ }
78
+ }
@@ -7,26 +7,13 @@ import motokoPlugin from "prettier-plugin-motoko";
7
7
  import { getRootDir } from "../mops.js";
8
8
  import { absToRel } from "./test/utils.js";
9
9
  import { parallel } from "../parallel.js";
10
- let ignore = [
11
- "**/node_modules/**",
12
- "**/.mops/**",
13
- "**/.vessel/**",
14
- "**/.git/**",
15
- "**/dist/**",
16
- ];
17
- let globConfig = {
18
- nocase: true,
19
- ignore: ignore,
20
- };
10
+ import { MOTOKO_GLOB_CONFIG } from "../constants.js";
21
11
  export async function format(filter, options = {}, signal, onProgress) {
22
12
  let startTime = Date.now();
23
13
  let rootDir = getRootDir();
24
- let globStr = "**/*.mo";
25
- if (filter) {
26
- globStr = `**/*${filter}*.mo`;
27
- }
14
+ let globStr = filter ? `**/*${filter}*.mo` : "**/*.mo";
28
15
  let files = globSync(path.join(rootDir, globStr), {
29
- ...globConfig,
16
+ ...MOTOKO_GLOB_CONFIG,
30
17
  cwd: rootDir,
31
18
  });
32
19
  let invalidFiles = 0;
@@ -0,0 +1,7 @@
1
+ export interface LintOptions {
2
+ verbose: boolean;
3
+ fix: boolean;
4
+ rules?: string[];
5
+ extraArgs: string[];
6
+ }
7
+ export declare function lint(filter: string | undefined, options: Partial<LintOptions>): Promise<void>;
@@ -0,0 +1,69 @@
1
+ import chalk from "chalk";
2
+ import { execa } from "execa";
3
+ import { globSync } from "glob";
4
+ import path from "node:path";
5
+ import { cliError } from "../error.js";
6
+ import { getRootDir, readConfig } from "../mops.js";
7
+ import { toolchain } from "./toolchain/index.js";
8
+ import { MOTOKO_GLOB_CONFIG } from "../constants.js";
9
+ import { existsSync } from "node:fs";
10
+ export async function lint(filter, options) {
11
+ let config = readConfig();
12
+ let rootDir = getRootDir();
13
+ let lintokoBinPath = config.toolchain?.lintoko
14
+ ? await toolchain.bin("lintoko")
15
+ : "lintoko";
16
+ let globStr = filter ? `**/*${filter}*.mo` : "**/*.mo";
17
+ let filesToLint = globSync(path.join(rootDir, globStr), {
18
+ ...MOTOKO_GLOB_CONFIG,
19
+ cwd: rootDir,
20
+ });
21
+ if (filesToLint.length === 0) {
22
+ cliError(`No files found for filter '${filter}'`);
23
+ }
24
+ let args = [];
25
+ if (options.verbose) {
26
+ args.push("--verbose");
27
+ }
28
+ if (options.fix) {
29
+ args.push("--fix");
30
+ }
31
+ const rules = options.rules && options.rules.length > 0
32
+ ? options.rules
33
+ : ["lint", "lints"].filter((d) => existsSync(path.join(rootDir, d)));
34
+ rules.forEach((rule) => args.push("--rules", rule));
35
+ if (config.lint?.args) {
36
+ if (typeof config.lint.args === "string") {
37
+ cliError(`[lint] config 'args' should be an array of strings in mops.toml config file`);
38
+ }
39
+ args.push(...config.lint.args);
40
+ }
41
+ if (options.extraArgs && options.extraArgs.length > 0) {
42
+ args.push(...options.extraArgs);
43
+ }
44
+ args.push(...filesToLint);
45
+ try {
46
+ if (options.verbose) {
47
+ console.log(chalk.blue("lint"), chalk.gray("Running lintoko:"));
48
+ console.log(chalk.gray(lintokoBinPath));
49
+ console.log(chalk.gray(JSON.stringify(args)));
50
+ }
51
+ const result = await execa(lintokoBinPath, args, {
52
+ cwd: rootDir,
53
+ stdio: "inherit",
54
+ reject: false,
55
+ });
56
+ if (result.exitCode !== 0) {
57
+ cliError(`Lint failed with exit code ${result.exitCode}`);
58
+ }
59
+ if (options.fix) {
60
+ console.log(chalk.green("✓ Lint fixes applied"));
61
+ }
62
+ else {
63
+ console.log(chalk.green("✓ Lint succeeded"));
64
+ }
65
+ }
66
+ catch (err) {
67
+ cliError(`Error while running lintoko${err?.message ? `\n${err.message}` : ""}`);
68
+ }
69
+ }
@@ -7,6 +7,7 @@ import { add } from "./add.js";
7
7
  import { remove } from "./remove.js";
8
8
  import { checkIntegrity } from "../integrity.js";
9
9
  import { getMocPath } from "../helpers/get-moc-path.js";
10
+ import { MOTOKO_IGNORE_PATTERNS } from "../constants.js";
10
11
  export async function sync({ lock } = {}) {
11
12
  if (!checkConfigFile()) {
12
13
  return;
@@ -31,19 +32,13 @@ export async function sync({ lock } = {}) {
31
32
  }
32
33
  await checkIntegrity(lock);
33
34
  }
34
- let ignore = [
35
- "**/node_modules/**",
36
- "**/.vessel/**",
37
- "**/.git/**",
38
- "**/.mops/**",
39
- ];
40
35
  async function getUsedPackages() {
41
36
  let rootDir = getRootDir();
42
37
  let mocPath = getMocPath();
43
38
  let files = globSync("**/*.mo", {
44
39
  cwd: rootDir,
45
40
  nocase: true,
46
- ignore: ignore,
41
+ ignore: MOTOKO_IGNORE_PATTERNS,
47
42
  });
48
43
  let packages = new Set();
49
44
  for (let file of files) {
@@ -21,16 +21,7 @@ import { SilentReporter } from "./reporters/silent-reporter.js";
21
21
  import { toolchain } from "../toolchain/index.js";
22
22
  import { Replica } from "../replica.js";
23
23
  import { getDfxVersion } from "../../helpers/get-dfx-version.js";
24
- let ignore = [
25
- "**/node_modules/**",
26
- "**/.mops/**",
27
- "**/.vessel/**",
28
- "**/.git/**",
29
- ];
30
- let globConfig = {
31
- nocase: true,
32
- ignore: ignore,
33
- };
24
+ import { MOTOKO_GLOB_CONFIG, MOTOKO_IGNORE_PATTERNS } from "../../constants.js";
34
25
  let replica = new Replica();
35
26
  let replicaStartPromise;
36
27
  async function startReplicaOnce(replica, type) {
@@ -94,7 +85,7 @@ export async function test(filter = "", options = {}) {
94
85
  console.log(chalk.gray(`Press ${chalk.gray("Ctrl+C")} to exit.`));
95
86
  }, 200);
96
87
  let watcher = chokidar.watch([path.join(rootDir, "**/*.mo"), path.join(rootDir, "mops.toml")], {
97
- ignored: ignore,
88
+ ignored: MOTOKO_IGNORE_PATTERNS,
98
89
  ignoreInitial: true,
99
90
  });
100
91
  watcher.on("all", () => {
@@ -118,16 +109,15 @@ async function runAll(reporterName, filter = "", mode = "interpreter", replicaTy
118
109
  export async function testWithReporter(reporterName, filter = "", defaultMode = "interpreter", replicaType, watch = false, signal) {
119
110
  let rootDir = getRootDir();
120
111
  let files = [];
121
- let libFiles = globSync("**/test?(s)/lib.mo", globConfig);
112
+ let libFiles = globSync("**/test?(s)/lib.mo", MOTOKO_GLOB_CONFIG);
122
113
  if (libFiles[0]) {
123
114
  files = [libFiles[0]];
124
115
  }
125
116
  else {
126
- let globStr = "**/test?(s)/**/*.test.mo";
127
- if (filter) {
128
- globStr = `**/test?(s)/**/*${filter}*.mo`;
129
- }
130
- files = globSync(path.join(rootDir, globStr), globConfig);
117
+ let globStr = filter
118
+ ? `**/test?(s)/**/*${filter}*.mo`
119
+ : "**/test?(s)/**/*.test.mo";
120
+ files = globSync(path.join(rootDir, globStr), MOTOKO_GLOB_CONFIG);
131
121
  }
132
122
  if (!files.length) {
133
123
  if (filter) {
@@ -1,5 +1,5 @@
1
1
  import { Tool } from "../../types.js";
2
- declare function ensureToolchainInited({ strict }?: {
2
+ declare function checkToolchainInited({ strict }?: {
3
3
  strict?: boolean | undefined;
4
4
  }): Promise<boolean>;
5
5
  declare function init({ reset, silent }?: {
@@ -21,6 +21,6 @@ export declare let toolchain: {
21
21
  update: typeof update;
22
22
  bin: typeof bin;
23
23
  installAll: typeof installAll;
24
- ensureToolchainInited: typeof ensureToolchainInited;
24
+ checkToolchainInited: typeof checkToolchainInited;
25
25
  };
26
26
  export {};
@@ -11,6 +11,8 @@ import { checkRequirements } from "../../check-requirements.js";
11
11
  import * as moc from "./moc.js";
12
12
  import * as pocketIc from "./pocket-ic.js";
13
13
  import * as wasmtime from "./wasmtime.js";
14
+ import * as lintoko from "./lintoko.js";
15
+ import { FILE_PATH_REGEX } from "../../constants.js";
14
16
  function getToolUtils(tool) {
15
17
  if (tool === "moc") {
16
18
  return moc;
@@ -21,12 +23,15 @@ function getToolUtils(tool) {
21
23
  else if (tool === "wasmtime") {
22
24
  return wasmtime;
23
25
  }
26
+ else if (tool === "lintoko") {
27
+ return lintoko;
28
+ }
24
29
  else {
25
30
  console.error(`Unknown tool '${tool}'`);
26
31
  process.exit(1);
27
32
  }
28
33
  }
29
- async function ensureToolchainInited({ strict = true } = {}) {
34
+ async function checkToolchainInited({ strict = false } = {}) {
30
35
  // auto init in CI
31
36
  if (process.env.CI) {
32
37
  await init({ silent: true });
@@ -47,8 +52,8 @@ async function ensureToolchainInited({ strict = true } = {}) {
47
52
  }
48
53
  }
49
54
  catch { }
50
- console.error('Toolchain management is not initialized. Run "mops toolchain init"');
51
- process.exit(1);
55
+ process.stderr.write(`${chalk.yellow('Toolchain management is not initialized. Run "mops toolchain init" to use with dfx.')}\n`);
56
+ return false;
52
57
  }
53
58
  // update shell config files to set DFX_MOC_PATH to moc-wrapper
54
59
  async function init({ reset = false, silent = false } = {}) {
@@ -172,6 +177,9 @@ async function installAll({ silent = false, verbose = false } = {}) {
172
177
  verbose,
173
178
  });
174
179
  }
180
+ if (config.toolchain?.lintoko) {
181
+ await download("lintoko", config.toolchain.lintoko, { silent, verbose });
182
+ }
175
183
  if (!silent) {
176
184
  logUpdate.clear();
177
185
  console.log(chalk.green("Toolchain installed"));
@@ -204,7 +212,7 @@ async function promptVersion(tool) {
204
212
  // download binary and set version in mops.toml
205
213
  async function use(tool, version) {
206
214
  if (tool === "moc") {
207
- await ensureToolchainInited();
215
+ await checkToolchainInited();
208
216
  }
209
217
  if (!version) {
210
218
  version = await promptVersion(tool);
@@ -232,7 +240,7 @@ async function use(tool, version) {
232
240
  // download latest binary and set version in mops.toml
233
241
  async function update(tool) {
234
242
  if (tool === "moc") {
235
- await ensureToolchainInited();
243
+ await checkToolchainInited();
236
244
  }
237
245
  let config = readConfig();
238
246
  config.toolchain = config.toolchain || {};
@@ -273,8 +281,11 @@ async function bin(tool, { fallback = false } = {}) {
273
281
  let config = readConfig();
274
282
  let version = config.toolchain?.[tool];
275
283
  if (version) {
284
+ if (version.match(FILE_PATH_REGEX)) {
285
+ return version;
286
+ }
276
287
  if (tool === "moc") {
277
- await ensureToolchainInited();
288
+ await checkToolchainInited();
278
289
  }
279
290
  await download(tool, version, { silent: true });
280
291
  if (tool === "moc") {
@@ -300,5 +311,5 @@ export let toolchain = {
300
311
  update,
301
312
  bin,
302
313
  installAll,
303
- ensureToolchainInited,
314
+ checkToolchainInited,
304
315
  };
@@ -0,0 +1,8 @@
1
+ export declare let repo: string;
2
+ export declare let getLatestReleaseTag: () => Promise<string>;
3
+ export declare let getReleases: () => Promise<any>;
4
+ export declare let isCached: (version: string) => boolean;
5
+ export declare let download: (version: string, { silent, verbose }?: {
6
+ silent?: boolean | undefined;
7
+ verbose?: boolean | undefined;
8
+ }) => Promise<void>;
@@ -0,0 +1,36 @@
1
+ import process from "node:process";
2
+ import path from "node:path";
3
+ import fs from "node:fs";
4
+ import { globalCacheDir } from "../../mops.js";
5
+ import * as toolchainUtils from "./toolchain-utils.js";
6
+ let cacheDir = path.join(globalCacheDir, "lintoko");
7
+ export let repo = "caffeinelabs/lintoko";
8
+ export let getLatestReleaseTag = async () => {
9
+ return toolchainUtils.getLatestReleaseTag(repo);
10
+ };
11
+ export let getReleases = async () => {
12
+ return toolchainUtils.getReleases(repo);
13
+ };
14
+ export let isCached = (version) => {
15
+ let dir = path.join(cacheDir, version);
16
+ return fs.existsSync(dir) && fs.existsSync(path.join(dir, "lintoko"));
17
+ };
18
+ export let download = async (version, { silent = false, verbose = false } = {}) => {
19
+ if (!version) {
20
+ console.error("version is not defined");
21
+ process.exit(1);
22
+ }
23
+ if (isCached(version)) {
24
+ if (verbose) {
25
+ console.log(`lintoko ${version} is already installed`);
26
+ }
27
+ return;
28
+ }
29
+ let platform = process.platform == "darwin" ? "apple-darwin" : "unknown-linux-gnu";
30
+ let arch = process.arch.startsWith("arm") ? "aarch64" : "x86_64";
31
+ let url = `https://github.com/caffeinelabs/lintoko/releases/download/v${version}/lintoko-${arch}-${platform}.tar.xz`;
32
+ if (verbose && !silent) {
33
+ console.log(`Downloading ${url}`);
34
+ }
35
+ await toolchainUtils.downloadAndExtract(url, path.join(cacheDir, version), "lintoko");
36
+ };
@@ -1,4 +1,5 @@
1
1
  import { Buffer } from "node:buffer";
2
+ export declare const TOOLCHAINS: string[];
2
3
  export declare let tryDownloadFile: (url: string) => Promise<Buffer | null>;
3
4
  export declare let downloadAndExtract: (url: string, destDir: string, destFileName?: string) => Promise<void>;
4
5
  export declare let getLatestReleaseTag: (repo: string) => Promise<string>;
@@ -10,6 +10,7 @@ import { deleteSync } from "del";
10
10
  import { Octokit } from "octokit";
11
11
  import { extract as extractTar } from "tar";
12
12
  import { getRootDir } from "../../mops.js";
13
+ export const TOOLCHAINS = ["moc", "wasmtime", "pocket-ic", "lintoko"];
13
14
  export let tryDownloadFile = async (url) => {
14
15
  let res = await fetch(url);
15
16
  if (!res.ok) {
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Common ignore patterns for glob operations across Motoko files.
3
+ */
4
+ export declare const MOTOKO_IGNORE_PATTERNS: string[];
5
+ /**
6
+ * Common glob configuration for Motoko file operations
7
+ */
8
+ export declare const MOTOKO_GLOB_CONFIG: {
9
+ nocase: boolean;
10
+ ignore: string[];
11
+ };
12
+ /**
13
+ * Regex to match a file path for dependency and toolchain versions
14
+ */
15
+ export declare const FILE_PATH_REGEX: RegExp;