ic-mops 2.15.1 → 2.16.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 (55) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/bundle/cli.tgz +0 -0
  3. package/check-requirements.ts +53 -31
  4. package/cli.ts +37 -10
  5. package/commands/bench.ts +23 -5
  6. package/commands/check-stable.ts +20 -3
  7. package/commands/check.ts +6 -1
  8. package/commands/toolchain/index.ts +1 -3
  9. package/dist/check-requirements.js +31 -24
  10. package/dist/cli.js +19 -9
  11. package/dist/commands/bench.d.ts +2 -0
  12. package/dist/commands/bench.js +16 -3
  13. package/dist/commands/check-stable.d.ts +2 -1
  14. package/dist/commands/check-stable.js +8 -2
  15. package/dist/commands/check.js +6 -1
  16. package/dist/commands/toolchain/index.js +1 -3
  17. package/dist/helpers/get-lintoko-version.d.ts +3 -0
  18. package/dist/helpers/get-lintoko-version.js +30 -0
  19. package/dist/helpers/migrations.d.ts +16 -0
  20. package/dist/helpers/migrations.js +71 -1
  21. package/dist/helpers/parse-most.d.ts +5 -0
  22. package/dist/helpers/parse-most.js +28 -0
  23. package/dist/mops.js +2 -2
  24. package/dist/package.json +1 -1
  25. package/dist/tests/check-stable.test.js +15 -0
  26. package/dist/tests/check.test.js +5 -0
  27. package/dist/tests/lint.test.js +1 -1
  28. package/dist/tests/migrations-most.test.d.ts +1 -0
  29. package/dist/tests/migrations-most.test.js +47 -0
  30. package/dist/tests/requirements.test.d.ts +1 -0
  31. package/dist/tests/requirements.test.js +46 -0
  32. package/dist/types.d.ts +1 -0
  33. package/helpers/get-lintoko-version.ts +32 -0
  34. package/helpers/migrations.ts +117 -0
  35. package/helpers/parse-most.ts +32 -0
  36. package/mops.ts +2 -2
  37. package/package.json +1 -1
  38. package/tests/__snapshots__/check-stable.test.ts.snap +11 -0
  39. package/tests/__snapshots__/lint.test.ts.snap +29 -1
  40. package/tests/check-stable/check-limit-warning/deployed.most +8 -0
  41. package/tests/check-stable/check-limit-warning/migrations/20250101_000000_Init.mo +5 -0
  42. package/tests/check-stable/check-limit-warning/migrations/20250201_000000_AddField.mo +9 -0
  43. package/tests/check-stable/check-limit-warning/migrations/20250301_000000_AddD.mo +10 -0
  44. package/tests/check-stable/check-limit-warning/mops.toml +15 -0
  45. package/tests/check-stable/check-limit-warning/src/main.mo +12 -0
  46. package/tests/check-stable.test.ts +24 -0
  47. package/tests/check.test.ts +9 -0
  48. package/tests/lint-extra-example-rules/lint/migration-self-contained/migration-self-contained.toml +7 -0
  49. package/tests/lint-extra-example-rules/migrations/20250101_000000_Init.mo +11 -0
  50. package/tests/lint-extra-example-rules/mops.toml +1 -0
  51. package/tests/lint.test.ts +1 -1
  52. package/tests/migrations-most.test.ts +64 -0
  53. package/tests/requirements-lintoko/mops.toml +5 -0
  54. package/tests/requirements.test.ts +66 -0
  55. package/types.ts +1 -0
@@ -0,0 +1,10 @@
1
+ module {
2
+ public func migration(old : { a : Nat; b : Text; c : Bool }) : {
3
+ a : Nat;
4
+ b : Text;
5
+ c : Bool;
6
+ d : Int;
7
+ } {
8
+ { old with d = 0 };
9
+ };
10
+ };
@@ -0,0 +1,15 @@
1
+ [toolchain]
2
+ moc = "1.5.0"
3
+
4
+ [moc]
5
+ args = ["--default-persistent-actors"]
6
+
7
+ [canisters.backend]
8
+ main = "src/main.mo"
9
+
10
+ [canisters.backend.migrations]
11
+ chain = "migrations"
12
+ check-limit = 1
13
+
14
+ [canisters.backend.check-stable]
15
+ path = "deployed.most"
@@ -0,0 +1,12 @@
1
+ import Prim "mo:prim";
2
+
3
+ actor {
4
+ let a : Nat;
5
+ let b : Text;
6
+ let c : Bool;
7
+ let d : Int;
8
+
9
+ public func check() : async () {
10
+ Prim.debugPrint(debug_show { a; b; c; d });
11
+ };
12
+ };
@@ -106,4 +106,28 @@ describe("check-stable", () => {
106
106
  expect(result.stdout).toMatch(/Stable compatibility check passed/);
107
107
  }
108
108
  }, 60_000);
109
+
110
+ test("fails with check-limit diagnostic when pending migrations exceed check-limit", async () => {
111
+ const cwd = path.join(
112
+ import.meta.dirname,
113
+ "check-stable/check-limit-warning",
114
+ );
115
+ await cliSnapshot(["check-stable"], { cwd }, 1);
116
+ });
117
+
118
+ test("does not warn when deployed baseline matches the chain", async () => {
119
+ const cwd = path.join(import.meta.dirname, "check-stable/migrations-chain");
120
+ const result = await cli(["check-stable"], { cwd });
121
+ expect(result.exitCode).toBe(0);
122
+ expect(result.stderr).not.toMatch(/pending migration\(s\) but check-limit/);
123
+ });
124
+
125
+ test("--no-check-limit suppresses pending migration warning", async () => {
126
+ const cwd = path.join(
127
+ import.meta.dirname,
128
+ "check-stable/check-limit-warning",
129
+ );
130
+ const result = await cli(["check-stable", "--no-check-limit"], { cwd });
131
+ expect(result.stderr).not.toMatch(/pending migration\(s\) but check-limit/);
132
+ });
109
133
  });
@@ -195,4 +195,13 @@ describe("check", () => {
195
195
  expect(result.stdout).not.toMatch(/\.migrations-/);
196
196
  expect(result.stdout).not.toMatch(/-A=M0254/);
197
197
  });
198
+
199
+ test("--no-check-limit suppresses pending migration warning in check", async () => {
200
+ const cwd = path.join(
201
+ import.meta.dirname,
202
+ "check-stable/check-limit-warning",
203
+ );
204
+ const result = await cli(["check", "--no-check-limit"], { cwd });
205
+ expect(result.stderr).not.toMatch(/pending migration\(s\) but check-limit/);
206
+ });
198
207
  });
@@ -0,0 +1,7 @@
1
+ name = "migration-self-contained"
2
+ description = "Migration files must not import local modules (relative paths). Got: @path"
3
+ includes = ["migrations/*.mo"]
4
+ query = '''
5
+ (import (text_literal) @path @error
6
+ (#match? @path "^\"[^:]*\"$"))
7
+ '''
@@ -0,0 +1,11 @@
1
+ import Map "mo:map/Map";
2
+ import Backend "canister:backend";
3
+ import Types "Types";
4
+ import TypesDot "./Types";
5
+ import TypesParent "../src/Types";
6
+
7
+ module {
8
+ public func migration(_ : {}) : { count : Nat } {
9
+ { count = 0 };
10
+ };
11
+ };
@@ -5,3 +5,4 @@ lintoko = "0.7.0"
5
5
  "src/Main.mo" = ["lint/no-types"]
6
6
  "src/Types.mo" = ["lint/types-only"]
7
7
  "src/Migration.mo" = ["lint/migration-only"]
8
+ "migrations/*.mo" = ["lint/migration-self-contained"]
@@ -83,7 +83,7 @@ describe("lint", () => {
83
83
  );
84
84
  });
85
85
 
86
- test("example rules: no-types, types-only, migration-only", async () => {
86
+ test("example rules: no-types, types-only, migration-only, migration-self-contained", async () => {
87
87
  const cwd = path.join(import.meta.dirname, "lint-extra-example-rules");
88
88
  await cliSnapshot(["lint"], { cwd }, 1);
89
89
  });
@@ -0,0 +1,64 @@
1
+ import { describe, expect, test } from "@jest/globals";
2
+ import {
3
+ latestAppliedMigrationName,
4
+ parseMostAppliedMigrationNames,
5
+ } from "../helpers/parse-most";
6
+
7
+ describe("parseMostAppliedMigrationNames", () => {
8
+ test("parses Version 4.0.0 enhanced-migration chain", () => {
9
+ const content = `// Version: 4.0.0
10
+ {
11
+ "20250101_000000_Init" : {} -> {a : Nat; b : Text};
12
+ "20250201_000000_AddField" : (old : {a : Nat; b : Text}) -> {a : Nat; b : Text; c : Bool};
13
+ }
14
+ actor {
15
+ stable a : Nat;
16
+ stable b : Text;
17
+ stable c : Bool
18
+ };
19
+ `;
20
+ expect(parseMostAppliedMigrationNames(content)).toEqual([
21
+ "20250101_000000_Init",
22
+ "20250201_000000_AddField",
23
+ ]);
24
+ });
25
+
26
+ test("returns empty array for Version 1.0.0 (no EM chain)", () => {
27
+ expect(
28
+ parseMostAppliedMigrationNames("// Version: 1.0.0\nactor { };\n"),
29
+ ).toEqual([]);
30
+ });
31
+
32
+ test("returns empty array for Version 3.0.0 (legacy migration)", () => {
33
+ const content = `// Version: 3.0.0
34
+ actor ({
35
+ }, {
36
+ stable var field1 : Nat;
37
+ stable var field2 : Text
38
+ });
39
+ `;
40
+ expect(parseMostAppliedMigrationNames(content)).toEqual([]);
41
+ });
42
+
43
+ test("returns null when version header is missing", () => {
44
+ expect(parseMostAppliedMigrationNames("actor { };\n")).toBeNull();
45
+ });
46
+
47
+ test("returns null when Version 4.0.0 has no actor block", () => {
48
+ expect(
49
+ parseMostAppliedMigrationNames(
50
+ '// Version: 4.0.0\n{ "Init" : {} -> {} }',
51
+ ),
52
+ ).toBeNull();
53
+ });
54
+
55
+ test("latestAppliedMigrationName picks lexicographic max", () => {
56
+ expect(
57
+ latestAppliedMigrationName([
58
+ "20250101_000000_Init",
59
+ "20250301_000000_AddD",
60
+ "20250201_000000_AddField",
61
+ ]),
62
+ ).toBe("20250301_000000_AddD");
63
+ });
64
+ });
@@ -0,0 +1,5 @@
1
+ [dependencies]
2
+ my-pkg = "0.1.0"
3
+
4
+ [toolchain]
5
+ lintoko = "0.7.0"
@@ -0,0 +1,66 @@
1
+ import { describe, expect, test, beforeEach, afterEach } from "@jest/globals";
2
+ import { cp, mkdir, rm, writeFile } from "node:fs/promises";
3
+ import path from "path";
4
+ import { bytesToHex } from "@noble/hashes/utils";
5
+ import { sha256 } from "@noble/hashes/sha256";
6
+ import { cli } from "./helpers";
7
+
8
+ describe("requirements", () => {
9
+ const fixtureDir = path.join(import.meta.dirname, "requirements-lintoko");
10
+ let tempDir: string;
11
+
12
+ beforeEach(async () => {
13
+ tempDir = path.join(
14
+ import.meta.dirname,
15
+ `_tmp_requirements_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
16
+ );
17
+ await cp(fixtureDir, tempDir, { recursive: true });
18
+
19
+ const depDir = path.join(tempDir, ".mops", "my-pkg@0.1.0");
20
+ await mkdir(depDir, { recursive: true });
21
+ await writeFile(
22
+ path.join(depDir, "mops.toml"),
23
+ `[package]
24
+ name = "my-pkg"
25
+ version = "0.1.0"
26
+
27
+ [requirements]
28
+ lintoko = "0.10.0"
29
+ `,
30
+ );
31
+
32
+ const mopsTomlDepsHash = bytesToHex(
33
+ sha256(JSON.stringify({ "my-pkg": "0.1.0" })),
34
+ );
35
+ await writeFile(
36
+ path.join(tempDir, "mops.lock"),
37
+ JSON.stringify({
38
+ version: 3,
39
+ mopsTomlDepsHash,
40
+ deps: { "my-pkg": "0.1.0" },
41
+ hashes: {
42
+ "my-pkg@0.1.0": {
43
+ "mops.toml":
44
+ "0000000000000000000000000000000000000000000000000000000000000000",
45
+ },
46
+ },
47
+ }),
48
+ );
49
+ });
50
+
51
+ afterEach(async () => {
52
+ await rm(tempDir, { recursive: true, force: true });
53
+ });
54
+
55
+ test("lintoko requirement warns when installed version is too old", async () => {
56
+ const result = await cli(["toolchain", "use", "lintoko", "0.7.0"], {
57
+ cwd: tempDir,
58
+ });
59
+ expect(result.exitCode).toBe(0);
60
+ expect(result.stdout).toMatch(
61
+ /lintoko version does not meet the requirements of my-pkg@0\.1\.0/,
62
+ );
63
+ expect(result.stdout).toMatch(/Required: >= 0\.10\.0/);
64
+ expect(result.stdout).toMatch(/Installed:\s+0\.7\.0/);
65
+ });
66
+ });
package/types.ts CHANGED
@@ -78,6 +78,7 @@ export type Tool = "moc" | "wasmtime" | "pocket-ic" | "lintoko";
78
78
 
79
79
  export type Requirements = {
80
80
  moc?: string;
81
+ lintoko?: string;
81
82
  };
82
83
 
83
84
  // export type Format = {