ic-mops 2.2.0 → 2.3.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.
- package/CHANGELOG.md +13 -0
- package/RELEASE.md +9 -1
- package/bundle/cli.tgz +0 -0
- package/cli.ts +25 -2
- package/commands/build.ts +2 -10
- package/commands/check-stable.ts +177 -0
- package/commands/check.ts +53 -6
- package/commands/toolchain/index.ts +4 -0
- package/dist/cli.js +22 -2
- package/dist/commands/build.js +2 -5
- package/dist/commands/check-stable.d.ts +14 -0
- package/dist/commands/check-stable.js +95 -0
- package/dist/commands/check.js +41 -6
- package/dist/commands/toolchain/index.js +3 -0
- package/dist/helpers/resolve-canisters.d.ts +7 -0
- package/dist/helpers/resolve-canisters.js +31 -0
- package/dist/mops.js +0 -4
- package/dist/package.json +6 -7
- package/dist/tests/check-fix.test.js +17 -0
- package/dist/tests/check-stable.test.d.ts +1 -0
- package/dist/tests/check-stable.test.js +51 -0
- package/dist/tests/check.test.js +54 -1
- package/dist/tests/moc-args.test.js +3 -3
- package/dist/tests/toolchain.test.js +11 -0
- package/dist/types.d.ts +5 -1
- package/helpers/resolve-canisters.ts +52 -0
- package/mops.ts +0 -6
- package/package.json +8 -9
- package/tests/README.md +16 -0
- package/tests/__snapshots__/check-fix.test.ts.snap +16 -10
- package/tests/__snapshots__/check-stable.test.ts.snap +29 -0
- package/tests/__snapshots__/check.test.ts.snap +26 -16
- package/tests/build/no-dfx/mops.toml +3 -0
- package/tests/build/no-dfx/src/Main.mo +1 -1
- package/tests/check/canisters/Ok.mo +5 -0
- package/tests/check/canisters/mops.toml +8 -0
- package/tests/check/canisters-error/Error.mo +7 -0
- package/tests/check/canisters-error/mops.toml +8 -0
- package/tests/check/canisters-moc-args/Warning.mo +5 -0
- package/tests/check/canisters-moc-args/mops.toml +8 -0
- package/tests/check/deployed-compatible/main.mo +4 -0
- package/tests/check/deployed-compatible/mops.toml +11 -0
- package/tests/check/deployed-compatible/old.mo +3 -0
- package/tests/check/deployed-compile-error/Error.mo +7 -0
- package/tests/check/deployed-compile-error/mops.toml +11 -0
- package/tests/check/deployed-compile-error/old.mo +3 -0
- package/tests/check/deployed-missing-error/Ok.mo +5 -0
- package/tests/check/deployed-missing-error/mops.toml +11 -0
- package/tests/check/deployed-missing-skip/Ok.mo +5 -0
- package/tests/check/deployed-missing-skip/mops.toml +12 -0
- package/tests/check/error/Error.mo +1 -1
- package/tests/check/error/mops.toml +5 -2
- package/tests/check/fix/M0223.mo +1 -1
- package/tests/check/fix/M0236.mo +1 -1
- package/tests/check/fix/M0237.mo +1 -1
- package/tests/check/fix/Ok.mo +1 -1
- package/tests/check/fix/fix-with-error.mo +9 -0
- package/tests/check/fix/fix-with-warning.mo +8 -0
- package/tests/check/fix/mops.toml +3 -0
- package/tests/check/fix/transitive-main.mo +1 -1
- package/tests/check/moc-args/Warning.mo +1 -1
- package/tests/check/moc-args/mops.toml +4 -1
- package/tests/check/success/Ok.mo +1 -1
- package/tests/check/success/Warning.mo +1 -1
- package/tests/check/success/mops.toml +5 -2
- package/tests/check-fix.test.ts +25 -0
- package/tests/check-stable/compatible/mops.toml +8 -0
- package/tests/check-stable/compatible/new.mo +4 -0
- package/tests/check-stable/compatible/old.mo +3 -0
- package/tests/check-stable/incompatible/mops.toml +8 -0
- package/tests/check-stable/incompatible/new.mo +3 -0
- package/tests/check-stable/incompatible/old.mo +4 -0
- package/tests/check-stable/subdirectory/.old/src/main.mo +3 -0
- package/tests/check-stable/subdirectory/mops.toml +8 -0
- package/tests/check-stable/subdirectory/src/main.mo +4 -0
- package/tests/check-stable.test.ts +56 -0
- package/tests/check.test.ts +63 -1
- package/tests/moc-args.test.ts +3 -3
- package/tests/toolchain-local-subpath/bin/moc +2 -0
- package/tests/toolchain-local-subpath/mops.toml +2 -0
- package/tests/toolchain.test.ts +13 -0
- package/types.ts +5 -1
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { cliError } from "../error.js";
|
|
2
|
+
export function resolveCanisterConfigs(config) {
|
|
3
|
+
if (!config.canisters) {
|
|
4
|
+
return {};
|
|
5
|
+
}
|
|
6
|
+
return Object.fromEntries(Object.entries(config.canisters).map(([name, c]) => typeof c === "string" ? [name, { main: c }] : [name, c]));
|
|
7
|
+
}
|
|
8
|
+
export function resolveCanisterEntrypoints(config) {
|
|
9
|
+
const canisters = resolveCanisterConfigs(config);
|
|
10
|
+
return Object.values(canisters)
|
|
11
|
+
.map((c) => c.main)
|
|
12
|
+
.filter((main) => Boolean(main));
|
|
13
|
+
}
|
|
14
|
+
export function resolveSingleCanister(config, canisterName) {
|
|
15
|
+
const canisters = resolveCanisterConfigs(config);
|
|
16
|
+
const names = Object.keys(canisters);
|
|
17
|
+
if (names.length === 0) {
|
|
18
|
+
cliError("No canisters defined in mops.toml [canisters] section");
|
|
19
|
+
}
|
|
20
|
+
if (canisterName) {
|
|
21
|
+
const canister = canisters[canisterName];
|
|
22
|
+
if (!canister) {
|
|
23
|
+
cliError(`Canister '${canisterName}' not found in mops.toml. Available: ${names.join(", ")}`);
|
|
24
|
+
}
|
|
25
|
+
return { name: canisterName, canister };
|
|
26
|
+
}
|
|
27
|
+
if (names.length > 1) {
|
|
28
|
+
cliError(`Multiple canisters defined in mops.toml. Please specify one: ${names.join(", ")}`);
|
|
29
|
+
}
|
|
30
|
+
return { name: names[0], canister: canisters[names[0]] };
|
|
31
|
+
}
|
package/dist/mops.js
CHANGED
|
@@ -4,7 +4,6 @@ import fs from "node:fs";
|
|
|
4
4
|
import TOML from "@iarna/toml";
|
|
5
5
|
import chalk from "chalk";
|
|
6
6
|
import prompts from "prompts";
|
|
7
|
-
import fetch from "node-fetch";
|
|
8
7
|
import { decodeFile } from "./pem.js";
|
|
9
8
|
import { cliError } from "./error.js";
|
|
10
9
|
import { mainActor, storageActor } from "./api/actors.js";
|
|
@@ -12,9 +11,6 @@ import { getNetwork } from "./api/network.js";
|
|
|
12
11
|
import { getHighestVersion } from "./api/getHighestVersion.js";
|
|
13
12
|
import { getPackageId } from "./helpers/get-package-id.js";
|
|
14
13
|
import { FILE_PATH_REGEX } from "./constants.js";
|
|
15
|
-
if (!globalThis.fetch) {
|
|
16
|
-
globalThis.fetch = fetch;
|
|
17
|
-
}
|
|
18
14
|
// (!) make changes in pair with backend
|
|
19
15
|
export let apiVersion = "1.3";
|
|
20
16
|
export let globalConfigDir = "";
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ic-mops",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"mops": "bin/mops.js",
|
|
@@ -48,29 +48,28 @@
|
|
|
48
48
|
"filesize": "10.1.6",
|
|
49
49
|
"fs-extra": "11.2.0",
|
|
50
50
|
"get-folder-size": "5.0.0",
|
|
51
|
-
"glob": "
|
|
51
|
+
"glob": "13.0.6",
|
|
52
52
|
"globby": "14.0.2",
|
|
53
53
|
"got": "14.4.6",
|
|
54
|
-
"jsdom": "
|
|
54
|
+
"jsdom": "28.1.0",
|
|
55
55
|
"log-update": "6.1.0",
|
|
56
56
|
"markdown-table": "3.0.4",
|
|
57
57
|
"mdast-util-from-markdown": "2.0.2",
|
|
58
58
|
"mdast-util-to-markdown": "2.1.2",
|
|
59
59
|
"minimatch": "10.0.1",
|
|
60
60
|
"ncp": "2.0.0",
|
|
61
|
-
"node-fetch": "3.3.2",
|
|
62
61
|
"octokit": "3.1.2",
|
|
63
62
|
"pem-file": "1.0.1",
|
|
64
63
|
"pic-ic": "0.5.4",
|
|
65
64
|
"pic-js-mops": "0.14.8",
|
|
66
65
|
"prettier": "3.5.3",
|
|
67
|
-
"prettier-plugin-motoko": "0.
|
|
66
|
+
"prettier-plugin-motoko": "0.13.0",
|
|
68
67
|
"promisify-child-process": "4.1.2",
|
|
69
68
|
"prompts": "2.4.2",
|
|
70
69
|
"semver": "7.7.1",
|
|
71
70
|
"stream-to-promise": "3.0.0",
|
|
72
71
|
"string-width": "7.2.0",
|
|
73
|
-
"tar": "7.5.
|
|
72
|
+
"tar": "7.5.9",
|
|
74
73
|
"terminal-size": "4.0.0",
|
|
75
74
|
"vscode-languageserver-textdocument": "1.0.12"
|
|
76
75
|
},
|
|
@@ -80,7 +79,7 @@
|
|
|
80
79
|
"@types/decompress": "4.2.7",
|
|
81
80
|
"@types/fs-extra": "11.0.4",
|
|
82
81
|
"@types/glob": "8.1.0",
|
|
83
|
-
"@types/jsdom": "
|
|
82
|
+
"@types/jsdom": "28.0.0",
|
|
84
83
|
"@types/ncp": "2.0.8",
|
|
85
84
|
"@types/node": "24.0.3",
|
|
86
85
|
"@types/prompts": "2.4.9",
|
|
@@ -86,4 +86,21 @@ describe("check --fix", () => {
|
|
|
86
86
|
expect(result.stdout).toContain("Attempting to fix files");
|
|
87
87
|
expect(result.stdout).toContain("No fixes were needed");
|
|
88
88
|
});
|
|
89
|
+
test("fix with remaining warnings", async () => {
|
|
90
|
+
const runFilePath = copyFixture("fix-with-warning.mo");
|
|
91
|
+
const result = await cli(["check", runFilePath, "--fix", "--", warningFlags], { cwd: fixDir });
|
|
92
|
+
expect(result.exitCode).toBe(0);
|
|
93
|
+
expect(result.stdout).toContain("1 fix applied");
|
|
94
|
+
expect(result.stdout).toMatch(/✓/);
|
|
95
|
+
expect(result.stderr).toMatch(/warning \[M0194\]/);
|
|
96
|
+
expect(result.stderr).toMatch(/unused identifier/);
|
|
97
|
+
});
|
|
98
|
+
test("fix with remaining errors", async () => {
|
|
99
|
+
const runFilePath = copyFixture("fix-with-error.mo");
|
|
100
|
+
const result = await cli(["check", runFilePath, "--fix", "--", warningFlags], { cwd: fixDir });
|
|
101
|
+
expect(result.exitCode).toBe(1);
|
|
102
|
+
expect(result.stdout).toContain("1 fix applied");
|
|
103
|
+
expect(result.stderr).toMatch(/error/i);
|
|
104
|
+
expect(result.stdout).not.toMatch(/✓ run/);
|
|
105
|
+
});
|
|
89
106
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { describe, expect, test } from "@jest/globals";
|
|
2
|
+
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { cli, cliSnapshot } from "./helpers";
|
|
6
|
+
describe("check-stable", () => {
|
|
7
|
+
test("compatible upgrade from .mo file", async () => {
|
|
8
|
+
const cwd = path.join(import.meta.dirname, "check-stable/compatible");
|
|
9
|
+
await cliSnapshot(["check-stable", "old.mo"], { cwd }, 0);
|
|
10
|
+
});
|
|
11
|
+
test("incompatible upgrade from .mo file", async () => {
|
|
12
|
+
const cwd = path.join(import.meta.dirname, "check-stable/incompatible");
|
|
13
|
+
const result = await cliSnapshot(["check-stable", "old.mo"], { cwd }, 1);
|
|
14
|
+
expect(result.stderr).toMatch(/compatibility/i);
|
|
15
|
+
});
|
|
16
|
+
test("compatible upgrade with verbose", async () => {
|
|
17
|
+
const cwd = path.join(import.meta.dirname, "check-stable/compatible");
|
|
18
|
+
const result = await cli(["check-stable", "old.mo", "--verbose"], { cwd });
|
|
19
|
+
expect(result.exitCode).toBe(0);
|
|
20
|
+
expect(result.stdout).toMatch(/Generating stable types for old\.mo/);
|
|
21
|
+
expect(result.stdout).toMatch(/Generating stable types for new\.mo/);
|
|
22
|
+
expect(result.stdout).toMatch(/--stable-compatible/);
|
|
23
|
+
expect(result.stdout).toMatch(/Stable compatibility check passed/);
|
|
24
|
+
});
|
|
25
|
+
test("old file in subdirectory (.old/src/ pattern)", async () => {
|
|
26
|
+
const cwd = path.join(import.meta.dirname, "check-stable/subdirectory");
|
|
27
|
+
const result = await cli(["check-stable", ".old/src/main.mo"], { cwd });
|
|
28
|
+
expect(result.exitCode).toBe(0);
|
|
29
|
+
expect(result.stdout).toMatch(/Stable compatibility check passed/);
|
|
30
|
+
});
|
|
31
|
+
test("compatible upgrade from .most file", async () => {
|
|
32
|
+
const cwd = path.join(import.meta.dirname, "check-stable/compatible");
|
|
33
|
+
const tempDir = await mkdtemp(path.join(tmpdir(), "mops-test-most-"));
|
|
34
|
+
try {
|
|
35
|
+
const mostPath = path.join(tempDir, "old.most");
|
|
36
|
+
await writeFile(mostPath, "actor {\n stable var counter : Nat\n};\n");
|
|
37
|
+
const result = await cli(["check-stable", mostPath], { cwd });
|
|
38
|
+
expect(result.exitCode).toBe(0);
|
|
39
|
+
expect(result.stdout).toMatch(/Stable compatibility check passed/);
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
test("errors when old file does not exist", async () => {
|
|
46
|
+
const cwd = path.join(import.meta.dirname, "check-stable/compatible");
|
|
47
|
+
const result = await cli(["check-stable", "nonexistent.mo"], { cwd });
|
|
48
|
+
expect(result.exitCode).toBe(1);
|
|
49
|
+
expect(result.stderr).toMatch(/File not found/);
|
|
50
|
+
});
|
|
51
|
+
});
|
package/dist/tests/check.test.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, expect, test } from "@jest/globals";
|
|
2
2
|
import path from "path";
|
|
3
|
-
import { cliSnapshot } from "./helpers";
|
|
3
|
+
import { cli, cliSnapshot } from "./helpers";
|
|
4
4
|
describe("check", () => {
|
|
5
5
|
test("ok", async () => {
|
|
6
6
|
const cwd = path.join(import.meta.dirname, "check/success");
|
|
@@ -34,4 +34,57 @@ describe("check", () => {
|
|
|
34
34
|
const cwd = path.join(import.meta.dirname, "check/moc-args");
|
|
35
35
|
await cliSnapshot(["check", "Warning.mo"], { cwd }, 1);
|
|
36
36
|
});
|
|
37
|
+
test("no args falls back to [canisters] entrypoints", async () => {
|
|
38
|
+
const cwd = path.join(import.meta.dirname, "check/canisters");
|
|
39
|
+
await cliSnapshot(["check"], { cwd }, 0);
|
|
40
|
+
});
|
|
41
|
+
test("[moc] args applied when using canister fallback", async () => {
|
|
42
|
+
const cwd = path.join(import.meta.dirname, "check/canisters-moc-args");
|
|
43
|
+
const result = await cli(["check"], { cwd });
|
|
44
|
+
expect(result.exitCode).toBe(1);
|
|
45
|
+
expect(result.stderr).toMatch(/warning \[M0194\]/);
|
|
46
|
+
});
|
|
47
|
+
test("canister entrypoint with errors", async () => {
|
|
48
|
+
const cwd = path.join(import.meta.dirname, "check/canisters-error");
|
|
49
|
+
const result = await cli(["check"], { cwd });
|
|
50
|
+
expect(result.exitCode).toBe(1);
|
|
51
|
+
expect(result.stderr).toMatch(/error/i);
|
|
52
|
+
});
|
|
53
|
+
test("--fix with canister fallback", async () => {
|
|
54
|
+
const cwd = path.join(import.meta.dirname, "check/canisters");
|
|
55
|
+
const result = await cli(["check", "--fix"], { cwd });
|
|
56
|
+
expect(result.exitCode).toBe(0);
|
|
57
|
+
});
|
|
58
|
+
test("deployed: runs stable check when deployed file exists", async () => {
|
|
59
|
+
const cwd = path.join(import.meta.dirname, "check/deployed-compatible");
|
|
60
|
+
const result = await cli(["check"], { cwd });
|
|
61
|
+
expect(result.exitCode).toBe(0);
|
|
62
|
+
expect(result.stdout).toMatch(/Stable compatibility check passed/);
|
|
63
|
+
});
|
|
64
|
+
test("deployed: silently skips when file missing and skipIfMissing", async () => {
|
|
65
|
+
const cwd = path.join(import.meta.dirname, "check/deployed-missing-skip");
|
|
66
|
+
const result = await cli(["check"], { cwd });
|
|
67
|
+
expect(result.exitCode).toBe(0);
|
|
68
|
+
expect(result.stdout).not.toMatch(/stable/i);
|
|
69
|
+
});
|
|
70
|
+
test("deployed: errors when file missing without deployedSkipIfFileMissing", async () => {
|
|
71
|
+
const cwd = path.join(import.meta.dirname, "check/deployed-missing-error");
|
|
72
|
+
const result = await cli(["check"], { cwd });
|
|
73
|
+
expect(result.exitCode).toBe(1);
|
|
74
|
+
expect(result.stderr).toMatch(/Deployed file not found/);
|
|
75
|
+
expect(result.stderr).toMatch(/skipIfMissing/);
|
|
76
|
+
});
|
|
77
|
+
test("--fix runs stable check after fixing", async () => {
|
|
78
|
+
const cwd = path.join(import.meta.dirname, "check/deployed-compatible");
|
|
79
|
+
const result = await cli(["check", "--fix"], { cwd });
|
|
80
|
+
expect(result.exitCode).toBe(0);
|
|
81
|
+
expect(result.stdout).toMatch(/Stable compatibility check passed/);
|
|
82
|
+
});
|
|
83
|
+
test("stable check is skipped when type-checking fails", async () => {
|
|
84
|
+
const cwd = path.join(import.meta.dirname, "check/deployed-compile-error");
|
|
85
|
+
const result = await cli(["check"], { cwd });
|
|
86
|
+
expect(result.exitCode).toBe(1);
|
|
87
|
+
expect(result.stderr).toMatch(/error/i);
|
|
88
|
+
expect(result.stdout).not.toMatch(/Stable compatibility/);
|
|
89
|
+
});
|
|
37
90
|
});
|
|
@@ -6,12 +6,12 @@ describe("moc-args", () => {
|
|
|
6
6
|
const cwd = path.join(import.meta.dirname, "check/moc-args");
|
|
7
7
|
const result = await cli(["moc-args"], { cwd });
|
|
8
8
|
expect(result.exitCode).toBe(0);
|
|
9
|
-
expect(result.stdout).toBe("-Werror");
|
|
9
|
+
expect(result.stdout).toBe("--default-persistent-actors\n-Werror");
|
|
10
10
|
});
|
|
11
|
-
test("prints
|
|
11
|
+
test("prints only global args when no extra [moc] args", async () => {
|
|
12
12
|
const cwd = path.join(import.meta.dirname, "check/success");
|
|
13
13
|
const result = await cli(["moc-args"], { cwd });
|
|
14
14
|
expect(result.exitCode).toBe(0);
|
|
15
|
-
expect(result.stdout).toBe("");
|
|
15
|
+
expect(result.stdout).toBe("--default-persistent-actors");
|
|
16
16
|
});
|
|
17
17
|
});
|
|
@@ -8,4 +8,15 @@ describe("toolchain", () => {
|
|
|
8
8
|
expect(result.exitCode).toBe(0);
|
|
9
9
|
expect(result.stdout.trim()).toBe("./mock");
|
|
10
10
|
});
|
|
11
|
+
test("file URI with subdirectory path", async () => {
|
|
12
|
+
const cwd = path.join(import.meta.dirname, "toolchain-local-subpath");
|
|
13
|
+
const result = await cli(["toolchain", "bin", "moc"], { cwd });
|
|
14
|
+
expect(result.exitCode).toBe(0);
|
|
15
|
+
expect(result.stdout.trim()).toBe("./bin/moc");
|
|
16
|
+
});
|
|
17
|
+
test("file URI does not trigger download during install", async () => {
|
|
18
|
+
const cwd = path.join(import.meta.dirname, "toolchain-local-subpath");
|
|
19
|
+
const result = await cli(["install"], { cwd });
|
|
20
|
+
expect(result.stderr).not.toContain("Invalid Version");
|
|
21
|
+
});
|
|
11
22
|
});
|
package/dist/types.d.ts
CHANGED
|
@@ -32,10 +32,14 @@ export type Config = {
|
|
|
32
32
|
};
|
|
33
33
|
};
|
|
34
34
|
export type CanisterConfig = {
|
|
35
|
-
main
|
|
35
|
+
main?: string;
|
|
36
36
|
args?: string[];
|
|
37
37
|
candid?: string;
|
|
38
38
|
initArg?: string;
|
|
39
|
+
"check-stable"?: {
|
|
40
|
+
path: string;
|
|
41
|
+
skipIfMissing?: boolean;
|
|
42
|
+
};
|
|
39
43
|
};
|
|
40
44
|
export type Dependencies = Record<string, Dependency>;
|
|
41
45
|
export type Dependency = {
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { CanisterConfig, Config } from "../types.js";
|
|
2
|
+
import { cliError } from "../error.js";
|
|
3
|
+
|
|
4
|
+
export function resolveCanisterConfigs(
|
|
5
|
+
config: Config,
|
|
6
|
+
): Record<string, CanisterConfig> {
|
|
7
|
+
if (!config.canisters) {
|
|
8
|
+
return {};
|
|
9
|
+
}
|
|
10
|
+
return Object.fromEntries(
|
|
11
|
+
Object.entries(config.canisters).map(([name, c]) =>
|
|
12
|
+
typeof c === "string" ? [name, { main: c }] : [name, c],
|
|
13
|
+
),
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function resolveCanisterEntrypoints(config: Config): string[] {
|
|
18
|
+
const canisters = resolveCanisterConfigs(config);
|
|
19
|
+
return Object.values(canisters)
|
|
20
|
+
.map((c) => c.main)
|
|
21
|
+
.filter((main): main is string => Boolean(main));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function resolveSingleCanister(
|
|
25
|
+
config: Config,
|
|
26
|
+
canisterName?: string,
|
|
27
|
+
): { name: string; canister: CanisterConfig } {
|
|
28
|
+
const canisters = resolveCanisterConfigs(config);
|
|
29
|
+
const names = Object.keys(canisters);
|
|
30
|
+
|
|
31
|
+
if (names.length === 0) {
|
|
32
|
+
cliError("No canisters defined in mops.toml [canisters] section");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (canisterName) {
|
|
36
|
+
const canister = canisters[canisterName];
|
|
37
|
+
if (!canister) {
|
|
38
|
+
cliError(
|
|
39
|
+
`Canister '${canisterName}' not found in mops.toml. Available: ${names.join(", ")}`,
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
return { name: canisterName, canister };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (names.length > 1) {
|
|
46
|
+
cliError(
|
|
47
|
+
`Multiple canisters defined in mops.toml. Please specify one: ${names.join(", ")}`,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return { name: names[0]!, canister: canisters[names[0]!]! };
|
|
52
|
+
}
|
package/mops.ts
CHANGED
|
@@ -5,8 +5,6 @@ import { Identity } from "@icp-sdk/core/agent";
|
|
|
5
5
|
import TOML from "@iarna/toml";
|
|
6
6
|
import chalk from "chalk";
|
|
7
7
|
import prompts from "prompts";
|
|
8
|
-
import fetch from "node-fetch";
|
|
9
|
-
|
|
10
8
|
import { decodeFile } from "./pem.js";
|
|
11
9
|
import { cliError } from "./error.js";
|
|
12
10
|
import { Config, Dependency } from "./types.js";
|
|
@@ -16,10 +14,6 @@ import { getHighestVersion } from "./api/getHighestVersion.js";
|
|
|
16
14
|
import { getPackageId } from "./helpers/get-package-id.js";
|
|
17
15
|
import { FILE_PATH_REGEX } from "./constants.js";
|
|
18
16
|
|
|
19
|
-
if (!globalThis.fetch) {
|
|
20
|
-
globalThis.fetch = fetch as any;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
17
|
// (!) make changes in pair with backend
|
|
24
18
|
export let apiVersion = "1.3";
|
|
25
19
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ic-mops",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"mops": "dist/bin/mops.js",
|
|
@@ -36,8 +36,8 @@
|
|
|
36
36
|
"build:wasm:web": "wasm-pack build wasm --target web --out-dir pkg/web && rm wasm/pkg/web/.gitignore",
|
|
37
37
|
"dist": "npm run build:wasm && tsc && mkdir -p dist/wasm && cp -r wasm/pkg dist/wasm",
|
|
38
38
|
"bundle": "rm -rf ./bundle && bun build ./environments/web/cli.ts --outdir ./bundle --target node --minify --external @napi-rs/lzma --external fsevents --format esm --define '__dirname=import.meta.dirname' && run-s bundle:fix bundle:copy bundle:package-json bundle:tar",
|
|
39
|
-
"bundle:fix": "rexreplace 'new URL\\(\"\\.\\./templates' 'new URL(\"./templates' bundle/cli.js && rexreplace 'resolve\\(\".*?/xhr-sync-worker\\.js\"\\)' 'resolve(\"./xhr-sync-worker.js\")' bundle/cli.js && rexreplace '\"import.meta.dirname\",\"wasm_bg.wasm\"' 'import.meta.dirname || new URL(\".\", import.meta.url).pathname,\"wasm_bg.wasm\"' bundle/cli.js",
|
|
40
|
-
"bundle:copy": "cp -r commands/bench bundle && cp -r bin declarations templates package.json bundle && cp -r node_modules/prettier-plugin-motoko/wasm/pkg/nodejs/wasm_bg.wasm node_modules/jsdom/lib/jsdom/living/xhr/xhr-sync-worker.js bundle",
|
|
39
|
+
"bundle:fix": "rexreplace 'new URL\\(\"\\.\\./templates' 'new URL(\"./templates' bundle/cli.js && rexreplace 'resolve\\(\".*?/xhr-sync-worker\\.js\"\\)' 'resolve(\"./xhr-sync-worker.js\")' bundle/cli.js && rexreplace '\"import.meta.dirname\",\"wasm_bg.wasm\"' 'import.meta.dirname || new URL(\".\", import.meta.url).pathname,\"wasm_bg.wasm\"' bundle/cli.js && rexreplace 'resolve\\(\"import.meta.dirname\",\".*?/default-stylesheet\\.css\"\\)' 'resolve(import.meta.dirname,\"./default-stylesheet.css\")' bundle/cli.js",
|
|
40
|
+
"bundle:copy": "cp -r commands/bench bundle && cp -r bin declarations templates package.json bundle && cp -r node_modules/prettier-plugin-motoko/wasm/pkg/nodejs/wasm_bg.wasm node_modules/jsdom/lib/jsdom/living/xhr/xhr-sync-worker.js node_modules/jsdom/lib/jsdom/browser/default-stylesheet.css bundle",
|
|
41
41
|
"bundle:package-json": "tsx bundle-package-json.ts",
|
|
42
42
|
"bundle:tar": "rm -f bundle/cli.tgz && touch -t 200101010101 bundle/cli.tgz && find bundle -exec touch -d '1970-01-01 00:00:00' {} + && tar --sort name --exclude bundle/cli.tgz -cvf - bundle | gzip -n > bundle/cli.tgz",
|
|
43
43
|
"copy": "cp -r commands/bench dist/commands && cp -r declarations templates package.json bin dist | true",
|
|
@@ -69,29 +69,28 @@
|
|
|
69
69
|
"filesize": "10.1.6",
|
|
70
70
|
"fs-extra": "11.2.0",
|
|
71
71
|
"get-folder-size": "5.0.0",
|
|
72
|
-
"glob": "
|
|
72
|
+
"glob": "13.0.6",
|
|
73
73
|
"globby": "14.0.2",
|
|
74
74
|
"got": "14.4.6",
|
|
75
|
-
"jsdom": "
|
|
75
|
+
"jsdom": "28.1.0",
|
|
76
76
|
"log-update": "6.1.0",
|
|
77
77
|
"markdown-table": "3.0.4",
|
|
78
78
|
"mdast-util-from-markdown": "2.0.2",
|
|
79
79
|
"mdast-util-to-markdown": "2.1.2",
|
|
80
80
|
"minimatch": "10.0.1",
|
|
81
81
|
"ncp": "2.0.0",
|
|
82
|
-
"node-fetch": "3.3.2",
|
|
83
82
|
"octokit": "3.1.2",
|
|
84
83
|
"pem-file": "1.0.1",
|
|
85
84
|
"pic-ic": "0.5.4",
|
|
86
85
|
"pic-js-mops": "0.14.8",
|
|
87
86
|
"prettier": "3.5.3",
|
|
88
|
-
"prettier-plugin-motoko": "0.
|
|
87
|
+
"prettier-plugin-motoko": "0.13.0",
|
|
89
88
|
"promisify-child-process": "4.1.2",
|
|
90
89
|
"prompts": "2.4.2",
|
|
91
90
|
"semver": "7.7.1",
|
|
92
91
|
"stream-to-promise": "3.0.0",
|
|
93
92
|
"string-width": "7.2.0",
|
|
94
|
-
"tar": "7.5.
|
|
93
|
+
"tar": "7.5.9",
|
|
95
94
|
"terminal-size": "4.0.0",
|
|
96
95
|
"vscode-languageserver-textdocument": "1.0.12"
|
|
97
96
|
},
|
|
@@ -101,7 +100,7 @@
|
|
|
101
100
|
"@types/decompress": "4.2.7",
|
|
102
101
|
"@types/fs-extra": "11.0.4",
|
|
103
102
|
"@types/glob": "8.1.0",
|
|
104
|
-
"@types/jsdom": "
|
|
103
|
+
"@types/jsdom": "28.0.0",
|
|
105
104
|
"@types/ncp": "2.0.8",
|
|
106
105
|
"@types/node": "24.0.3",
|
|
107
106
|
"@types/prompts": "2.4.9",
|
package/tests/README.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# CLI Test Fixtures
|
|
2
|
+
|
|
3
|
+
Each subdirectory under `check/`, `check-stable/`, etc. is a self-contained test fixture with its own `mops.toml`.
|
|
4
|
+
|
|
5
|
+
## Adding a new fixture
|
|
6
|
+
|
|
7
|
+
1. Create a directory with a `mops.toml` and the `.mo` files your test needs.
|
|
8
|
+
2. Only declare `[dependencies]` if your `.mo` files actually import from them. Unused dependencies cause `moc` to receive `--package` flags pointing to directories that may not exist on CI.
|
|
9
|
+
3. If your fixture declares `[dependencies]`, add a `mops install` step for it in `.github/workflows/ci.yml` under the "Pre-cache" step. Test fixtures' `.mops/` directories are gitignored and don't exist on CI unless explicitly installed.
|
|
10
|
+
4. If your fixture uses a `[toolchain]` moc version that isn't already pre-cached in CI, add a download step for it in the same CI pre-cache block.
|
|
11
|
+
|
|
12
|
+
## Why this matters
|
|
13
|
+
|
|
14
|
+
Jest runs test suites in parallel. Without pre-installation:
|
|
15
|
+
- Multiple workers race to download the same `moc` binary, corrupting the cache.
|
|
16
|
+
- `moc` fails when `--package` flags point to missing `.mops/` directories.
|
|
@@ -4,7 +4,7 @@ exports[`check --fix M0223 1`] = `
|
|
|
4
4
|
"// M0223: Redundant type instantiation
|
|
5
5
|
// The type annotation is not needed when it can be inferred
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
actor {
|
|
8
8
|
public func testM0223() : async () {
|
|
9
9
|
func identity<T>(x : T) : T = x;
|
|
10
10
|
let varArray : [var Nat] = [var 1];
|
|
@@ -18,7 +18,8 @@ persistent actor {
|
|
|
18
18
|
exports[`check --fix M0223: fix output 1`] = `
|
|
19
19
|
"Fixed run/M0223.mo (1 fix: M0223)
|
|
20
20
|
|
|
21
|
-
✓ 1 fix applied to 1 file
|
|
21
|
+
✓ 1 fix applied to 1 file
|
|
22
|
+
✓ <TEST_DIR>/check/fix/run/M0223.mo"
|
|
22
23
|
`;
|
|
23
24
|
|
|
24
25
|
exports[`check --fix M0236 1`] = `
|
|
@@ -27,7 +28,7 @@ exports[`check --fix M0236 1`] = `
|
|
|
27
28
|
import List "mo:core/List";
|
|
28
29
|
import Nat "mo:core/Nat";
|
|
29
30
|
|
|
30
|
-
|
|
31
|
+
actor {
|
|
31
32
|
public func testM0236() : async () {
|
|
32
33
|
let list = List.fromArray<Nat>([1, 2, 3]);
|
|
33
34
|
list.sortInPlace();
|
|
@@ -39,7 +40,8 @@ persistent actor {
|
|
|
39
40
|
exports[`check --fix M0236: fix output 1`] = `
|
|
40
41
|
"Fixed run/M0236.mo (1 fix: M0236)
|
|
41
42
|
|
|
42
|
-
✓ 1 fix applied to 1 file
|
|
43
|
+
✓ 1 fix applied to 1 file
|
|
44
|
+
✓ <TEST_DIR>/check/fix/run/M0236.mo"
|
|
43
45
|
`;
|
|
44
46
|
|
|
45
47
|
exports[`check --fix M0237 1`] = `
|
|
@@ -48,7 +50,7 @@ exports[`check --fix M0237 1`] = `
|
|
|
48
50
|
import List "mo:core/List";
|
|
49
51
|
import Nat "mo:core/Nat";
|
|
50
52
|
|
|
51
|
-
|
|
53
|
+
actor {
|
|
52
54
|
public func testM0237() : async () {
|
|
53
55
|
let list = List.fromArray<Nat>([3, 2, 1]);
|
|
54
56
|
list.sortInPlace();
|
|
@@ -60,7 +62,8 @@ persistent actor {
|
|
|
60
62
|
exports[`check --fix M0237: fix output 1`] = `
|
|
61
63
|
"Fixed run/M0237.mo (1 fix: M0237)
|
|
62
64
|
|
|
63
|
-
✓ 1 fix applied to 1 file
|
|
65
|
+
✓ 1 fix applied to 1 file
|
|
66
|
+
✓ <TEST_DIR>/check/fix/run/M0237.mo"
|
|
64
67
|
`;
|
|
65
68
|
|
|
66
69
|
exports[`check --fix edit-suggestions 1`] = `
|
|
@@ -205,7 +208,8 @@ do {
|
|
|
205
208
|
exports[`check --fix edit-suggestions: fix output 1`] = `
|
|
206
209
|
"Fixed run/edit-suggestions.mo (30 fixes: M0223, M0236, M0237)
|
|
207
210
|
|
|
208
|
-
✓ 30 fixes applied to 1 file
|
|
211
|
+
✓ 30 fixes applied to 1 file
|
|
212
|
+
✓ <TEST_DIR>/check/fix/run/edit-suggestions.mo"
|
|
209
213
|
`;
|
|
210
214
|
|
|
211
215
|
exports[`check --fix overlapping edits 1`] = `
|
|
@@ -224,14 +228,16 @@ do {
|
|
|
224
228
|
exports[`check --fix overlapping edits: fix output 1`] = `
|
|
225
229
|
"Fixed run/overlapping.mo (4 fixes: M0223, M0236)
|
|
226
230
|
|
|
227
|
-
✓ 4 fixes applied to 1 file
|
|
231
|
+
✓ 4 fixes applied to 1 file
|
|
232
|
+
✓ <TEST_DIR>/check/fix/run/overlapping.mo"
|
|
228
233
|
`;
|
|
229
234
|
|
|
230
235
|
exports[`check --fix transitive imports: fix output 1`] = `
|
|
231
236
|
"Fixed run/transitive-lib.mo (1 fix: M0236)
|
|
232
237
|
Fixed run/transitive-main.mo (1 fix: M0223)
|
|
233
238
|
|
|
234
|
-
✓ 2 fixes applied to 2 files
|
|
239
|
+
✓ 2 fixes applied to 2 files
|
|
240
|
+
✓ <TEST_DIR>/check/fix/run/transitive-main.mo"
|
|
235
241
|
`;
|
|
236
242
|
|
|
237
243
|
exports[`check --fix transitive imports: lib file 1`] = `
|
|
@@ -250,7 +256,7 @@ module {
|
|
|
250
256
|
exports[`check --fix transitive imports: main file 1`] = `
|
|
251
257
|
"import Lib "./transitive-lib";
|
|
252
258
|
|
|
253
|
-
|
|
259
|
+
actor {
|
|
254
260
|
public func run() : async () {
|
|
255
261
|
func identity<T>(x : T) : T = x;
|
|
256
262
|
let _ = identity(1);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
|
2
|
+
|
|
3
|
+
exports[`check-stable compatible upgrade from .mo file 1`] = `
|
|
4
|
+
{
|
|
5
|
+
"exitCode": 0,
|
|
6
|
+
"stderr": "",
|
|
7
|
+
"stdout": "✓ Stable compatibility check passed for canister 'backend'",
|
|
8
|
+
}
|
|
9
|
+
`;
|
|
10
|
+
|
|
11
|
+
exports[`check-stable incompatible upgrade from .mo file 1`] = `
|
|
12
|
+
{
|
|
13
|
+
"exitCode": 1,
|
|
14
|
+
"stderr": "(unknown location): Compatibility error [M0170], the new type of stable variable \`counter\` is not compatible with the previous version.
|
|
15
|
+
The previous type
|
|
16
|
+
var Nat
|
|
17
|
+
is not a subtype of
|
|
18
|
+
var Text
|
|
19
|
+
because the type
|
|
20
|
+
Nat
|
|
21
|
+
is not compatible with type
|
|
22
|
+
Text
|
|
23
|
+
in \`counter\`.
|
|
24
|
+
Write an explicit migration function, please see https://internetcomputer.org/docs/motoko/fundamentals/actors/compatibility#explicit-migration-using-a-migration-function.
|
|
25
|
+
(unknown location): Compatibility error [M0169], the stable variable \`name\` of the previous version cannot be implicitly discarded. The variable can only be dropped by an explicit migration function, please see https://internetcomputer.org/docs/motoko/fundamentals/actors/compatibility#explicit-migration-using-a-migration-function
|
|
26
|
+
✗ Stable compatibility check failed for canister 'backend'",
|
|
27
|
+
"stdout": "",
|
|
28
|
+
}
|
|
29
|
+
`;
|