ic-mops 2.2.1 → 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 +10 -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/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/helpers/resolve-canisters.d.ts +7 -0
- package/dist/helpers/resolve-canisters.js +31 -0
- package/dist/package.json +2 -2
- 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/types.d.ts +5 -1
- package/dist/wasm/pkg/nodejs/wasm_bg.wasm +0 -0
- package/dist/wasm/pkg/web/wasm_bg.wasm +0 -0
- package/helpers/resolve-canisters.ts +52 -0
- package/package.json +2 -2
- 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/types.ts +5 -1
- package/wasm/Cargo.lock +101 -54
- package/wasm/pkg/nodejs/wasm_bg.wasm +0 -0
- package/wasm/pkg/web/wasm_bg.wasm +0 -0
- package/.DS_Store +0 -0
- package/bundle/bench/bench-canister.mo +0 -130
- package/bundle/bench/user-bench.mo +0 -10
- package/bundle/bin/moc-wrapper.sh +0 -40
- package/bundle/bin/mops.js +0 -3
- package/bundle/cli.js +0 -1569
- package/bundle/declarations/bench/bench.did +0 -30
- package/bundle/declarations/bench/bench.did.d.ts +0 -33
- package/bundle/declarations/bench/bench.did.js +0 -30
- package/bundle/declarations/bench/index.d.ts +0 -50
- package/bundle/declarations/bench/index.js +0 -40
- package/bundle/declarations/main/index.d.ts +0 -50
- package/bundle/declarations/main/index.js +0 -40
- package/bundle/declarations/main/main.did +0 -428
- package/bundle/declarations/main/main.did.d.ts +0 -348
- package/bundle/declarations/main/main.did.js +0 -406
- package/bundle/declarations/storage/index.d.ts +0 -50
- package/bundle/declarations/storage/index.js +0 -30
- package/bundle/declarations/storage/storage.did +0 -46
- package/bundle/declarations/storage/storage.did.d.ts +0 -40
- package/bundle/declarations/storage/storage.did.js +0 -38
- package/bundle/default-stylesheet.css +0 -415
- package/bundle/package.json +0 -36
- package/bundle/templates/README.md +0 -13
- package/bundle/templates/licenses/Apache-2.0 +0 -202
- package/bundle/templates/licenses/Apache-2.0-NOTICE +0 -13
- package/bundle/templates/licenses/MIT +0 -21
- package/bundle/templates/mops-publish.yml +0 -17
- package/bundle/templates/mops-test.yml +0 -24
- package/bundle/templates/src/lib.mo +0 -15
- package/bundle/templates/test/lib.test.mo +0 -4
- package/bundle/wasm_bg.wasm +0 -0
- package/bundle/xhr-sync-worker.js +0 -51
- package/dist/wasm/pkg/bundler/package.json +0 -20
- package/dist/wasm/pkg/bundler/wasm.d.ts +0 -3
- package/dist/wasm/pkg/bundler/wasm.js +0 -5
- package/dist/wasm/pkg/bundler/wasm_bg.js +0 -93
- package/dist/wasm/pkg/bundler/wasm_bg.wasm +0 -0
- package/dist/wasm/pkg/bundler/wasm_bg.wasm.d.ts +0 -8
- package/tests/build/success/.dfx/local/canister_ids.json +0 -17
- package/tests/build/success/.dfx/local/canisters/bar/bar.did +0 -3
- package/tests/build/success/.dfx/local/canisters/bar/bar.most +0 -4
- package/tests/build/success/.dfx/local/canisters/bar/bar.wasm +0 -0
- package/tests/build/success/.dfx/local/canisters/bar/constructor.did +0 -3
- package/tests/build/success/.dfx/local/canisters/bar/index.js +0 -42
- package/tests/build/success/.dfx/local/canisters/bar/init_args.txt +0 -1
- package/tests/build/success/.dfx/local/canisters/bar/service.did +0 -3
- package/tests/build/success/.dfx/local/canisters/bar/service.did.d.ts +0 -7
- package/tests/build/success/.dfx/local/canisters/bar/service.did.js +0 -4
- package/tests/build/success/.dfx/local/canisters/foo/constructor.did +0 -3
- package/tests/build/success/.dfx/local/canisters/foo/foo.did +0 -3
- package/tests/build/success/.dfx/local/canisters/foo/foo.most +0 -4
- package/tests/build/success/.dfx/local/canisters/foo/foo.wasm +0 -0
- package/tests/build/success/.dfx/local/canisters/foo/index.js +0 -42
- package/tests/build/success/.dfx/local/canisters/foo/init_args.txt +0 -1
- package/tests/build/success/.dfx/local/canisters/foo/service.did +0 -3
- package/tests/build/success/.dfx/local/canisters/foo/service.did.d.ts +0 -7
- package/tests/build/success/.dfx/local/canisters/foo/service.did.js +0 -4
- package/tests/build/success/.dfx/local/lsp/ucwa4-rx777-77774-qaada-cai.did +0 -3
- package/tests/build/success/.dfx/local/lsp/ulvla-h7777-77774-qaacq-cai.did +0 -3
- package/tests/build/success/.dfx/local/network-id +0 -4
- package/wasm/pkg/bundler/package.json +0 -20
- package/wasm/pkg/bundler/wasm.d.ts +0 -3
- package/wasm/pkg/bundler/wasm.js +0 -5
- package/wasm/pkg/bundler/wasm_bg.js +0 -93
- package/wasm/pkg/bundler/wasm_bg.wasm +0 -0
- package/wasm/pkg/bundler/wasm_bg.wasm.d.ts +0 -8
package/CHANGELOG.md
CHANGED
|
@@ -2,8 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
## Next
|
|
4
4
|
|
|
5
|
+
## 2.3.0
|
|
6
|
+
- Add `mops check-stable` command for stable variable compatibility checking
|
|
7
|
+
- `mops check` now falls back to canister entrypoints from `mops.toml` when no files are specified
|
|
8
|
+
- `mops check` automatically runs stable compatibility when `[canisters.<name>.check-stable]` is configured
|
|
9
|
+
- `mops check --fix` now behaves like fix + `mops check` — reports changed files, then type-checks and runs stable compatibility if configured
|
|
10
|
+
- `skipIfMissing` in `[canisters.<name>.check-stable]` silently skips when the file doesn't exist
|
|
11
|
+
- Add docs for `mops lint`, `mops moc-args`, `[canisters]`, `[build]`, and `[lint]` config sections
|
|
12
|
+
- Add docs canister deployment step to release process
|
|
13
|
+
|
|
5
14
|
## 2.2.1
|
|
6
15
|
- Fix `mops toolchain` when toolchain version is a local file path with subdirectories.
|
|
16
|
+
- Update Motoko formatter (`prettier-plugin-motoko`).
|
|
7
17
|
|
|
8
18
|
## 2.2.0
|
|
9
19
|
- Add `[moc]` config section for global `moc` compiler flags (applied to `check`, `build`, `test`, `bench`, `watch`)
|
package/RELEASE.md
CHANGED
|
@@ -130,7 +130,15 @@ dfx deploy --network ic --no-wallet cli --identity mops
|
|
|
130
130
|
|
|
131
131
|
This deploys the `cli-releases` canister (serving `cli.mops.one`) to the Internet Computer mainnet.
|
|
132
132
|
|
|
133
|
-
### 10.
|
|
133
|
+
### 10. Deploy the docs canister
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
dfx deploy --network ic --no-wallet docs --identity mops
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
This builds the Docusaurus site (`docs/`) and deploys the `docs` assets canister (serving `docs.mops.one`). Docs are not auto-deployed, so this step ensures any documentation changes from the release are published.
|
|
140
|
+
|
|
141
|
+
### 11. Commit and push release artifacts
|
|
134
142
|
|
|
135
143
|
Step 8 generates files in `cli-releases/` that must be committed and pushed:
|
|
136
144
|
|
package/bundle/cli.tgz
CHANGED
|
Binary file
|
package/cli.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { build, DEFAULT_BUILD_OUTPUT_DIR } from "./commands/build.js";
|
|
|
12
12
|
import { bump } from "./commands/bump.js";
|
|
13
13
|
import { check } from "./commands/check.js";
|
|
14
14
|
import { checkCandid } from "./commands/check-candid.js";
|
|
15
|
+
import { checkStable } from "./commands/check-stable.js";
|
|
15
16
|
import { docsCoverage } from "./commands/docs-coverage.js";
|
|
16
17
|
import { docs } from "./commands/docs.js";
|
|
17
18
|
import { format } from "./commands/format.js";
|
|
@@ -322,9 +323,9 @@ program
|
|
|
322
323
|
|
|
323
324
|
// check
|
|
324
325
|
program
|
|
325
|
-
.command("check
|
|
326
|
+
.command("check [files...]")
|
|
326
327
|
.description(
|
|
327
|
-
"Check Motoko
|
|
328
|
+
"Check Motoko files for syntax errors and type issues. If no files are specified, checks all canister entrypoints from mops.toml. Also runs stable compatibility checks for canisters with [check-stable] configured",
|
|
328
329
|
)
|
|
329
330
|
.option("--verbose", "Verbose console output")
|
|
330
331
|
.addOption(
|
|
@@ -362,6 +363,28 @@ program
|
|
|
362
363
|
await checkCandid(newCandid, originalCandid);
|
|
363
364
|
});
|
|
364
365
|
|
|
366
|
+
// check-stable
|
|
367
|
+
program
|
|
368
|
+
.command("check-stable <old-file> [canister]")
|
|
369
|
+
.description(
|
|
370
|
+
"Check stable variable compatibility between an old version (.mo or .most file) and the current canister entrypoint",
|
|
371
|
+
)
|
|
372
|
+
.option("--verbose", "Verbose console output")
|
|
373
|
+
.allowUnknownOption(true)
|
|
374
|
+
.action(async (oldFile, canister, options) => {
|
|
375
|
+
checkConfigFile(true);
|
|
376
|
+
const { extraArgs } = parseExtraArgs();
|
|
377
|
+
await installAll({
|
|
378
|
+
silent: true,
|
|
379
|
+
lock: "ignore",
|
|
380
|
+
installFromLockFile: true,
|
|
381
|
+
});
|
|
382
|
+
await checkStable(oldFile, canister, {
|
|
383
|
+
...options,
|
|
384
|
+
extraArgs,
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
|
|
365
388
|
// test
|
|
366
389
|
program
|
|
367
390
|
.command("test [filter]")
|
package/commands/build.ts
CHANGED
|
@@ -5,9 +5,9 @@ import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
import { cliError } from "../error.js";
|
|
7
7
|
import { isCandidCompatible } from "../helpers/is-candid-compatible.js";
|
|
8
|
+
import { resolveCanisterConfigs } from "../helpers/resolve-canisters.js";
|
|
8
9
|
import { CustomSection, getWasmBindings } from "../wasm.js";
|
|
9
10
|
import { getGlobalMocArgs, readConfig } from "../mops.js";
|
|
10
|
-
import { CanisterConfig } from "../types.js";
|
|
11
11
|
import { sourcesArgs } from "./sources.js";
|
|
12
12
|
import { toolchain } from "./toolchain/index.js";
|
|
13
13
|
|
|
@@ -29,16 +29,8 @@ export async function build(
|
|
|
29
29
|
|
|
30
30
|
let outputDir = options.outputDir ?? DEFAULT_BUILD_OUTPUT_DIR;
|
|
31
31
|
let mocPath = await toolchain.bin("moc", { fallback: true });
|
|
32
|
-
let canisters: Record<string, CanisterConfig> = {};
|
|
33
32
|
let config = readConfig();
|
|
34
|
-
|
|
35
|
-
canisters =
|
|
36
|
-
Object.fromEntries(
|
|
37
|
-
Object.entries(config.canisters).map(([name, c]) =>
|
|
38
|
-
typeof c === "string" ? [name, { main: c }] : [name, c],
|
|
39
|
-
),
|
|
40
|
-
) ?? {};
|
|
41
|
-
}
|
|
33
|
+
let canisters = resolveCanisterConfigs(config);
|
|
42
34
|
if (!Object.keys(canisters).length) {
|
|
43
35
|
cliError(`No Motoko canisters found in mops.toml configuration`);
|
|
44
36
|
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { basename, join, relative, resolve } from "node:path";
|
|
2
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
3
|
+
import { rename, rm } from "node:fs/promises";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import { execa } from "execa";
|
|
6
|
+
import { cliError } from "../error.js";
|
|
7
|
+
import { getGlobalMocArgs, readConfig } from "../mops.js";
|
|
8
|
+
import { resolveSingleCanister } from "../helpers/resolve-canisters.js";
|
|
9
|
+
import { sourcesArgs } from "./sources.js";
|
|
10
|
+
import { toolchain } from "./toolchain/index.js";
|
|
11
|
+
|
|
12
|
+
const CHECK_STABLE_DIR = ".mops/.check-stable";
|
|
13
|
+
|
|
14
|
+
export interface CheckStableOptions {
|
|
15
|
+
verbose: boolean;
|
|
16
|
+
extraArgs: string[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function checkStable(
|
|
20
|
+
oldFile: string,
|
|
21
|
+
canisterName: string | undefined,
|
|
22
|
+
options: Partial<CheckStableOptions> = {},
|
|
23
|
+
): Promise<void> {
|
|
24
|
+
const config = readConfig();
|
|
25
|
+
const { name, canister } = resolveSingleCanister(config, canisterName);
|
|
26
|
+
|
|
27
|
+
if (!canister.main) {
|
|
28
|
+
cliError(`No main file specified for canister '${name}' in mops.toml`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const mocPath = await toolchain.bin("moc", { fallback: true });
|
|
32
|
+
const globalMocArgs = getGlobalMocArgs(config);
|
|
33
|
+
|
|
34
|
+
await runStableCheck({
|
|
35
|
+
oldFile,
|
|
36
|
+
canisterMain: canister.main,
|
|
37
|
+
canisterName: name,
|
|
38
|
+
mocPath,
|
|
39
|
+
globalMocArgs,
|
|
40
|
+
options,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface RunStableCheckParams {
|
|
45
|
+
oldFile: string;
|
|
46
|
+
canisterMain: string;
|
|
47
|
+
canisterName: string;
|
|
48
|
+
mocPath: string;
|
|
49
|
+
globalMocArgs: string[];
|
|
50
|
+
options?: Partial<CheckStableOptions>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function runStableCheck(
|
|
54
|
+
params: RunStableCheckParams,
|
|
55
|
+
): Promise<void> {
|
|
56
|
+
const {
|
|
57
|
+
oldFile,
|
|
58
|
+
canisterMain,
|
|
59
|
+
canisterName,
|
|
60
|
+
mocPath,
|
|
61
|
+
globalMocArgs,
|
|
62
|
+
options = {},
|
|
63
|
+
} = params;
|
|
64
|
+
|
|
65
|
+
const checkStableDir = resolve(CHECK_STABLE_DIR);
|
|
66
|
+
const sources = (await sourcesArgs({ cwd: checkStableDir })).flat();
|
|
67
|
+
const isOldMostFile = oldFile.endsWith(".most");
|
|
68
|
+
|
|
69
|
+
if (!existsSync(oldFile)) {
|
|
70
|
+
cliError(`File not found: ${oldFile}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
await rm(CHECK_STABLE_DIR, { recursive: true, force: true });
|
|
74
|
+
mkdirSync(CHECK_STABLE_DIR, { recursive: true });
|
|
75
|
+
try {
|
|
76
|
+
const oldMostPath = isOldMostFile
|
|
77
|
+
? oldFile
|
|
78
|
+
: await generateStableTypes(
|
|
79
|
+
mocPath,
|
|
80
|
+
oldFile,
|
|
81
|
+
join(CHECK_STABLE_DIR, "old.most"),
|
|
82
|
+
sources,
|
|
83
|
+
globalMocArgs,
|
|
84
|
+
options,
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const newMostPath = await generateStableTypes(
|
|
88
|
+
mocPath,
|
|
89
|
+
canisterMain,
|
|
90
|
+
join(CHECK_STABLE_DIR, "new.most"),
|
|
91
|
+
sources,
|
|
92
|
+
globalMocArgs,
|
|
93
|
+
options,
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
if (options.verbose) {
|
|
97
|
+
console.log(
|
|
98
|
+
chalk.blue("check-stable"),
|
|
99
|
+
chalk.gray(`Comparing ${oldMostPath} ↔ ${newMostPath}`),
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const args = ["--stable-compatible", oldMostPath, newMostPath];
|
|
104
|
+
if (options.verbose) {
|
|
105
|
+
console.log(chalk.gray(mocPath, JSON.stringify(args)));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const result = await execa(mocPath, args, {
|
|
109
|
+
stdio: "pipe",
|
|
110
|
+
reject: false,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
if (result.exitCode !== 0) {
|
|
114
|
+
if (result.stderr) {
|
|
115
|
+
console.error(result.stderr);
|
|
116
|
+
}
|
|
117
|
+
cliError(
|
|
118
|
+
`✗ Stable compatibility check failed for canister '${canisterName}'`,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
console.log(
|
|
123
|
+
chalk.green(
|
|
124
|
+
`✓ Stable compatibility check passed for canister '${canisterName}'`,
|
|
125
|
+
),
|
|
126
|
+
);
|
|
127
|
+
} finally {
|
|
128
|
+
await rm(CHECK_STABLE_DIR, { recursive: true, force: true });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function generateStableTypes(
|
|
133
|
+
mocPath: string,
|
|
134
|
+
moFile: string,
|
|
135
|
+
outputPath: string,
|
|
136
|
+
sources: string[],
|
|
137
|
+
globalMocArgs: string[],
|
|
138
|
+
options: Partial<CheckStableOptions>,
|
|
139
|
+
): Promise<string> {
|
|
140
|
+
const relFile = relative(resolve(CHECK_STABLE_DIR), resolve(moFile));
|
|
141
|
+
const args = [
|
|
142
|
+
"--stable-types",
|
|
143
|
+
relFile,
|
|
144
|
+
...sources,
|
|
145
|
+
...globalMocArgs,
|
|
146
|
+
...(options.extraArgs ?? []),
|
|
147
|
+
];
|
|
148
|
+
|
|
149
|
+
if (options.verbose) {
|
|
150
|
+
console.log(
|
|
151
|
+
chalk.blue("check-stable"),
|
|
152
|
+
chalk.gray(`Generating stable types for ${moFile}`),
|
|
153
|
+
);
|
|
154
|
+
console.log(chalk.gray(mocPath, JSON.stringify(args)));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const result = await execa(mocPath, args, {
|
|
158
|
+
cwd: CHECK_STABLE_DIR,
|
|
159
|
+
stdio: "pipe",
|
|
160
|
+
reject: false,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
if (result.exitCode !== 0) {
|
|
164
|
+
if (result.stderr) {
|
|
165
|
+
console.error(result.stderr);
|
|
166
|
+
}
|
|
167
|
+
cliError(
|
|
168
|
+
`Failed to generate stable types for ${moFile} (exit code: ${result.exitCode})`,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const base = basename(moFile, ".mo");
|
|
173
|
+
await rename(join(CHECK_STABLE_DIR, base + ".most"), outputPath);
|
|
174
|
+
await rm(join(CHECK_STABLE_DIR, base + ".wasm"), { force: true });
|
|
175
|
+
|
|
176
|
+
return outputPath;
|
|
177
|
+
}
|
package/commands/check.ts
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { relative } from "node:path";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
2
3
|
import chalk from "chalk";
|
|
3
4
|
import { execa } from "execa";
|
|
4
5
|
import { cliError } from "../error.js";
|
|
5
6
|
import { getGlobalMocArgs, readConfig } from "../mops.js";
|
|
6
7
|
import { autofixMotoko } from "../helpers/autofix-motoko.js";
|
|
7
8
|
import { getMocSemVer } from "../helpers/get-moc-version.js";
|
|
9
|
+
import {
|
|
10
|
+
resolveCanisterConfigs,
|
|
11
|
+
resolveCanisterEntrypoints,
|
|
12
|
+
} from "../helpers/resolve-canisters.js";
|
|
13
|
+
import { runStableCheck } from "./check-stable.js";
|
|
8
14
|
import { sourcesArgs } from "./sources.js";
|
|
9
15
|
import { toolchain } from "./toolchain/index.js";
|
|
10
16
|
|
|
@@ -25,13 +31,23 @@ export async function check(
|
|
|
25
31
|
files: string | string[],
|
|
26
32
|
options: Partial<CheckOptions> = {},
|
|
27
33
|
): Promise<void> {
|
|
28
|
-
|
|
34
|
+
let fileList = Array.isArray(files) ? files : files ? [files] : [];
|
|
35
|
+
|
|
36
|
+
const config = readConfig();
|
|
29
37
|
|
|
30
38
|
if (fileList.length === 0) {
|
|
31
|
-
|
|
39
|
+
fileList = resolveCanisterEntrypoints(config);
|
|
32
40
|
}
|
|
33
41
|
|
|
34
|
-
|
|
42
|
+
if (fileList.length === 0) {
|
|
43
|
+
cliError(
|
|
44
|
+
"No Motoko files specified and no canisters defined in mops.toml.\n" +
|
|
45
|
+
"Either pass files: mops check <files...>\n" +
|
|
46
|
+
"Or define canisters in mops.toml:\n\n" +
|
|
47
|
+
" [canisters.backend]\n" +
|
|
48
|
+
' main = "src/main.mo"',
|
|
49
|
+
);
|
|
50
|
+
}
|
|
35
51
|
const mocPath = await toolchain.bin("moc", { fallback: true });
|
|
36
52
|
const sources = await sourcesArgs();
|
|
37
53
|
const globalMocArgs = getGlobalMocArgs(config);
|
|
@@ -109,13 +125,44 @@ export async function check(
|
|
|
109
125
|
);
|
|
110
126
|
}
|
|
111
127
|
|
|
112
|
-
|
|
113
|
-
console.log(chalk.green(`✓ ${file}`));
|
|
114
|
-
}
|
|
128
|
+
console.log(chalk.green(`✓ ${file}`));
|
|
115
129
|
} catch (err: any) {
|
|
116
130
|
cliError(
|
|
117
131
|
`Error while checking ${file}${err?.message ? `\n${err.message}` : ""}`,
|
|
118
132
|
);
|
|
119
133
|
}
|
|
120
134
|
}
|
|
135
|
+
|
|
136
|
+
const canisters = resolveCanisterConfigs(config);
|
|
137
|
+
for (const [name, canister] of Object.entries(canisters)) {
|
|
138
|
+
const stableConfig = canister["check-stable"];
|
|
139
|
+
if (!stableConfig) {
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!canister.main) {
|
|
144
|
+
cliError(`No main file specified for canister '${name}' in mops.toml`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (!existsSync(stableConfig.path)) {
|
|
148
|
+
if (stableConfig.skipIfMissing) {
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
cliError(
|
|
152
|
+
`Deployed file not found: ${stableConfig.path} (canister '${name}')\n` +
|
|
153
|
+
"Set skipIfMissing = true in [canisters." +
|
|
154
|
+
name +
|
|
155
|
+
".check-stable] to skip this check when the file is missing.",
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
await runStableCheck({
|
|
160
|
+
oldFile: stableConfig.path,
|
|
161
|
+
canisterMain: canister.main,
|
|
162
|
+
canisterName: name,
|
|
163
|
+
mocPath,
|
|
164
|
+
globalMocArgs,
|
|
165
|
+
options: { verbose: options.verbose, extraArgs: options.extraArgs },
|
|
166
|
+
});
|
|
167
|
+
}
|
|
121
168
|
}
|
package/dist/cli.js
CHANGED
|
@@ -11,6 +11,7 @@ import { build, DEFAULT_BUILD_OUTPUT_DIR } from "./commands/build.js";
|
|
|
11
11
|
import { bump } from "./commands/bump.js";
|
|
12
12
|
import { check } from "./commands/check.js";
|
|
13
13
|
import { checkCandid } from "./commands/check-candid.js";
|
|
14
|
+
import { checkStable } from "./commands/check-stable.js";
|
|
14
15
|
import { docsCoverage } from "./commands/docs-coverage.js";
|
|
15
16
|
import { docs } from "./commands/docs.js";
|
|
16
17
|
import { format } from "./commands/format.js";
|
|
@@ -252,8 +253,8 @@ program
|
|
|
252
253
|
});
|
|
253
254
|
// check
|
|
254
255
|
program
|
|
255
|
-
.command("check
|
|
256
|
-
.description("Check Motoko
|
|
256
|
+
.command("check [files...]")
|
|
257
|
+
.description("Check Motoko files for syntax errors and type issues. If no files are specified, checks all canister entrypoints from mops.toml. Also runs stable compatibility checks for canisters with [check-stable] configured")
|
|
257
258
|
.option("--verbose", "Verbose console output")
|
|
258
259
|
.addOption(new Option("--fix", "Apply autofixes to all files, including transitively imported ones"))
|
|
259
260
|
.allowUnknownOption(true)
|
|
@@ -283,6 +284,25 @@ program
|
|
|
283
284
|
});
|
|
284
285
|
await checkCandid(newCandid, originalCandid);
|
|
285
286
|
});
|
|
287
|
+
// check-stable
|
|
288
|
+
program
|
|
289
|
+
.command("check-stable <old-file> [canister]")
|
|
290
|
+
.description("Check stable variable compatibility between an old version (.mo or .most file) and the current canister entrypoint")
|
|
291
|
+
.option("--verbose", "Verbose console output")
|
|
292
|
+
.allowUnknownOption(true)
|
|
293
|
+
.action(async (oldFile, canister, options) => {
|
|
294
|
+
checkConfigFile(true);
|
|
295
|
+
const { extraArgs } = parseExtraArgs();
|
|
296
|
+
await installAll({
|
|
297
|
+
silent: true,
|
|
298
|
+
lock: "ignore",
|
|
299
|
+
installFromLockFile: true,
|
|
300
|
+
});
|
|
301
|
+
await checkStable(oldFile, canister, {
|
|
302
|
+
...options,
|
|
303
|
+
extraArgs,
|
|
304
|
+
});
|
|
305
|
+
});
|
|
286
306
|
// test
|
|
287
307
|
program
|
|
288
308
|
.command("test [filter]")
|
package/dist/commands/build.js
CHANGED
|
@@ -5,6 +5,7 @@ import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
import { cliError } from "../error.js";
|
|
7
7
|
import { isCandidCompatible } from "../helpers/is-candid-compatible.js";
|
|
8
|
+
import { resolveCanisterConfigs } from "../helpers/resolve-canisters.js";
|
|
8
9
|
import { getWasmBindings } from "../wasm.js";
|
|
9
10
|
import { getGlobalMocArgs, readConfig } from "../mops.js";
|
|
10
11
|
import { sourcesArgs } from "./sources.js";
|
|
@@ -16,12 +17,8 @@ export async function build(canisterNames, options) {
|
|
|
16
17
|
}
|
|
17
18
|
let outputDir = options.outputDir ?? DEFAULT_BUILD_OUTPUT_DIR;
|
|
18
19
|
let mocPath = await toolchain.bin("moc", { fallback: true });
|
|
19
|
-
let canisters = {};
|
|
20
20
|
let config = readConfig();
|
|
21
|
-
|
|
22
|
-
canisters =
|
|
23
|
-
Object.fromEntries(Object.entries(config.canisters).map(([name, c]) => typeof c === "string" ? [name, { main: c }] : [name, c])) ?? {};
|
|
24
|
-
}
|
|
21
|
+
let canisters = resolveCanisterConfigs(config);
|
|
25
22
|
if (!Object.keys(canisters).length) {
|
|
26
23
|
cliError(`No Motoko canisters found in mops.toml configuration`);
|
|
27
24
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface CheckStableOptions {
|
|
2
|
+
verbose: boolean;
|
|
3
|
+
extraArgs: string[];
|
|
4
|
+
}
|
|
5
|
+
export declare function checkStable(oldFile: string, canisterName: string | undefined, options?: Partial<CheckStableOptions>): Promise<void>;
|
|
6
|
+
export interface RunStableCheckParams {
|
|
7
|
+
oldFile: string;
|
|
8
|
+
canisterMain: string;
|
|
9
|
+
canisterName: string;
|
|
10
|
+
mocPath: string;
|
|
11
|
+
globalMocArgs: string[];
|
|
12
|
+
options?: Partial<CheckStableOptions>;
|
|
13
|
+
}
|
|
14
|
+
export declare function runStableCheck(params: RunStableCheckParams): Promise<void>;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { basename, join, relative, resolve } from "node:path";
|
|
2
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
3
|
+
import { rename, rm } from "node:fs/promises";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import { execa } from "execa";
|
|
6
|
+
import { cliError } from "../error.js";
|
|
7
|
+
import { getGlobalMocArgs, readConfig } from "../mops.js";
|
|
8
|
+
import { resolveSingleCanister } from "../helpers/resolve-canisters.js";
|
|
9
|
+
import { sourcesArgs } from "./sources.js";
|
|
10
|
+
import { toolchain } from "./toolchain/index.js";
|
|
11
|
+
const CHECK_STABLE_DIR = ".mops/.check-stable";
|
|
12
|
+
export async function checkStable(oldFile, canisterName, options = {}) {
|
|
13
|
+
const config = readConfig();
|
|
14
|
+
const { name, canister } = resolveSingleCanister(config, canisterName);
|
|
15
|
+
if (!canister.main) {
|
|
16
|
+
cliError(`No main file specified for canister '${name}' in mops.toml`);
|
|
17
|
+
}
|
|
18
|
+
const mocPath = await toolchain.bin("moc", { fallback: true });
|
|
19
|
+
const globalMocArgs = getGlobalMocArgs(config);
|
|
20
|
+
await runStableCheck({
|
|
21
|
+
oldFile,
|
|
22
|
+
canisterMain: canister.main,
|
|
23
|
+
canisterName: name,
|
|
24
|
+
mocPath,
|
|
25
|
+
globalMocArgs,
|
|
26
|
+
options,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
export async function runStableCheck(params) {
|
|
30
|
+
const { oldFile, canisterMain, canisterName, mocPath, globalMocArgs, options = {}, } = params;
|
|
31
|
+
const checkStableDir = resolve(CHECK_STABLE_DIR);
|
|
32
|
+
const sources = (await sourcesArgs({ cwd: checkStableDir })).flat();
|
|
33
|
+
const isOldMostFile = oldFile.endsWith(".most");
|
|
34
|
+
if (!existsSync(oldFile)) {
|
|
35
|
+
cliError(`File not found: ${oldFile}`);
|
|
36
|
+
}
|
|
37
|
+
await rm(CHECK_STABLE_DIR, { recursive: true, force: true });
|
|
38
|
+
mkdirSync(CHECK_STABLE_DIR, { recursive: true });
|
|
39
|
+
try {
|
|
40
|
+
const oldMostPath = isOldMostFile
|
|
41
|
+
? oldFile
|
|
42
|
+
: await generateStableTypes(mocPath, oldFile, join(CHECK_STABLE_DIR, "old.most"), sources, globalMocArgs, options);
|
|
43
|
+
const newMostPath = await generateStableTypes(mocPath, canisterMain, join(CHECK_STABLE_DIR, "new.most"), sources, globalMocArgs, options);
|
|
44
|
+
if (options.verbose) {
|
|
45
|
+
console.log(chalk.blue("check-stable"), chalk.gray(`Comparing ${oldMostPath} ↔ ${newMostPath}`));
|
|
46
|
+
}
|
|
47
|
+
const args = ["--stable-compatible", oldMostPath, newMostPath];
|
|
48
|
+
if (options.verbose) {
|
|
49
|
+
console.log(chalk.gray(mocPath, JSON.stringify(args)));
|
|
50
|
+
}
|
|
51
|
+
const result = await execa(mocPath, args, {
|
|
52
|
+
stdio: "pipe",
|
|
53
|
+
reject: false,
|
|
54
|
+
});
|
|
55
|
+
if (result.exitCode !== 0) {
|
|
56
|
+
if (result.stderr) {
|
|
57
|
+
console.error(result.stderr);
|
|
58
|
+
}
|
|
59
|
+
cliError(`✗ Stable compatibility check failed for canister '${canisterName}'`);
|
|
60
|
+
}
|
|
61
|
+
console.log(chalk.green(`✓ Stable compatibility check passed for canister '${canisterName}'`));
|
|
62
|
+
}
|
|
63
|
+
finally {
|
|
64
|
+
await rm(CHECK_STABLE_DIR, { recursive: true, force: true });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async function generateStableTypes(mocPath, moFile, outputPath, sources, globalMocArgs, options) {
|
|
68
|
+
const relFile = relative(resolve(CHECK_STABLE_DIR), resolve(moFile));
|
|
69
|
+
const args = [
|
|
70
|
+
"--stable-types",
|
|
71
|
+
relFile,
|
|
72
|
+
...sources,
|
|
73
|
+
...globalMocArgs,
|
|
74
|
+
...(options.extraArgs ?? []),
|
|
75
|
+
];
|
|
76
|
+
if (options.verbose) {
|
|
77
|
+
console.log(chalk.blue("check-stable"), chalk.gray(`Generating stable types for ${moFile}`));
|
|
78
|
+
console.log(chalk.gray(mocPath, JSON.stringify(args)));
|
|
79
|
+
}
|
|
80
|
+
const result = await execa(mocPath, args, {
|
|
81
|
+
cwd: CHECK_STABLE_DIR,
|
|
82
|
+
stdio: "pipe",
|
|
83
|
+
reject: false,
|
|
84
|
+
});
|
|
85
|
+
if (result.exitCode !== 0) {
|
|
86
|
+
if (result.stderr) {
|
|
87
|
+
console.error(result.stderr);
|
|
88
|
+
}
|
|
89
|
+
cliError(`Failed to generate stable types for ${moFile} (exit code: ${result.exitCode})`);
|
|
90
|
+
}
|
|
91
|
+
const base = basename(moFile, ".mo");
|
|
92
|
+
await rename(join(CHECK_STABLE_DIR, base + ".most"), outputPath);
|
|
93
|
+
await rm(join(CHECK_STABLE_DIR, base + ".wasm"), { force: true });
|
|
94
|
+
return outputPath;
|
|
95
|
+
}
|
package/dist/commands/check.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { relative } from "node:path";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
2
3
|
import chalk from "chalk";
|
|
3
4
|
import { execa } from "execa";
|
|
4
5
|
import { cliError } from "../error.js";
|
|
5
6
|
import { getGlobalMocArgs, readConfig } from "../mops.js";
|
|
6
7
|
import { autofixMotoko } from "../helpers/autofix-motoko.js";
|
|
7
8
|
import { getMocSemVer } from "../helpers/get-moc-version.js";
|
|
9
|
+
import { resolveCanisterConfigs, resolveCanisterEntrypoints, } from "../helpers/resolve-canisters.js";
|
|
10
|
+
import { runStableCheck } from "./check-stable.js";
|
|
8
11
|
import { sourcesArgs } from "./sources.js";
|
|
9
12
|
import { toolchain } from "./toolchain/index.js";
|
|
10
13
|
const MOC_ALL_LIBS_MIN_VERSION = "1.3.0";
|
|
@@ -13,11 +16,18 @@ function supportsAllLibsFlag() {
|
|
|
13
16
|
return version ? version.compare(MOC_ALL_LIBS_MIN_VERSION) >= 0 : false;
|
|
14
17
|
}
|
|
15
18
|
export async function check(files, options = {}) {
|
|
16
|
-
|
|
19
|
+
let fileList = Array.isArray(files) ? files : files ? [files] : [];
|
|
20
|
+
const config = readConfig();
|
|
17
21
|
if (fileList.length === 0) {
|
|
18
|
-
|
|
22
|
+
fileList = resolveCanisterEntrypoints(config);
|
|
23
|
+
}
|
|
24
|
+
if (fileList.length === 0) {
|
|
25
|
+
cliError("No Motoko files specified and no canisters defined in mops.toml.\n" +
|
|
26
|
+
"Either pass files: mops check <files...>\n" +
|
|
27
|
+
"Or define canisters in mops.toml:\n\n" +
|
|
28
|
+
" [canisters.backend]\n" +
|
|
29
|
+
' main = "src/main.mo"');
|
|
19
30
|
}
|
|
20
|
-
const config = readConfig();
|
|
21
31
|
const mocPath = await toolchain.bin("moc", { fallback: true });
|
|
22
32
|
const sources = await sourcesArgs();
|
|
23
33
|
const globalMocArgs = getGlobalMocArgs(config);
|
|
@@ -71,12 +81,37 @@ export async function check(files, options = {}) {
|
|
|
71
81
|
if (result.exitCode !== 0) {
|
|
72
82
|
cliError(`✗ Check failed for file ${file} (exit code: ${result.exitCode})`);
|
|
73
83
|
}
|
|
74
|
-
|
|
75
|
-
console.log(chalk.green(`✓ ${file}`));
|
|
76
|
-
}
|
|
84
|
+
console.log(chalk.green(`✓ ${file}`));
|
|
77
85
|
}
|
|
78
86
|
catch (err) {
|
|
79
87
|
cliError(`Error while checking ${file}${err?.message ? `\n${err.message}` : ""}`);
|
|
80
88
|
}
|
|
81
89
|
}
|
|
90
|
+
const canisters = resolveCanisterConfigs(config);
|
|
91
|
+
for (const [name, canister] of Object.entries(canisters)) {
|
|
92
|
+
const stableConfig = canister["check-stable"];
|
|
93
|
+
if (!stableConfig) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (!canister.main) {
|
|
97
|
+
cliError(`No main file specified for canister '${name}' in mops.toml`);
|
|
98
|
+
}
|
|
99
|
+
if (!existsSync(stableConfig.path)) {
|
|
100
|
+
if (stableConfig.skipIfMissing) {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
cliError(`Deployed file not found: ${stableConfig.path} (canister '${name}')\n` +
|
|
104
|
+
"Set skipIfMissing = true in [canisters." +
|
|
105
|
+
name +
|
|
106
|
+
".check-stable] to skip this check when the file is missing.");
|
|
107
|
+
}
|
|
108
|
+
await runStableCheck({
|
|
109
|
+
oldFile: stableConfig.path,
|
|
110
|
+
canisterMain: canister.main,
|
|
111
|
+
canisterName: name,
|
|
112
|
+
mocPath,
|
|
113
|
+
globalMocArgs,
|
|
114
|
+
options: { verbose: options.verbose, extraArgs: options.extraArgs },
|
|
115
|
+
});
|
|
116
|
+
}
|
|
82
117
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { CanisterConfig, Config } from "../types.js";
|
|
2
|
+
export declare function resolveCanisterConfigs(config: Config): Record<string, CanisterConfig>;
|
|
3
|
+
export declare function resolveCanisterEntrypoints(config: Config): string[];
|
|
4
|
+
export declare function resolveSingleCanister(config: Config, canisterName?: string): {
|
|
5
|
+
name: string;
|
|
6
|
+
canister: CanisterConfig;
|
|
7
|
+
};
|
|
@@ -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
|
+
}
|