ic-mops 2.9.0 → 2.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/bundle/cli.tgz +0 -0
- package/cli.ts +10 -10
- package/commands/build.ts +7 -22
- package/commands/check-stable.ts +110 -17
- package/commands/check.ts +183 -102
- package/dist/cli.js +10 -10
- package/dist/commands/build.js +3 -13
- package/dist/commands/check-stable.d.ts +7 -1
- package/dist/commands/check-stable.js +83 -19
- package/dist/commands/check.d.ts +1 -1
- package/dist/commands/check.js +127 -78
- package/dist/helpers/resolve-canisters.d.ts +3 -1
- package/dist/helpers/resolve-canisters.js +20 -5
- package/dist/package.json +1 -1
- package/dist/tests/check-stable.test.js +18 -0
- package/dist/tests/check.test.js +23 -5
- package/helpers/resolve-canisters.ts +36 -5
- package/package.json +1 -1
- package/tests/__snapshots__/check.test.ts.snap +2 -2
- package/tests/check/canisters-canister-args/Warning.mo +5 -0
- package/tests/check/canisters-canister-args/mops.toml +9 -0
- package/tests/check-stable/canister-args/migrations/20250101_000000_Init.mo +8 -0
- package/tests/check-stable/canister-args/migrations/20250201_000000_AddField.mo +9 -0
- package/tests/check-stable/canister-args/mops.toml +9 -0
- package/tests/check-stable/canister-args/old.most +8 -0
- package/tests/check-stable/canister-args/src/main.mo +11 -0
- package/tests/check-stable.test.ts +21 -0
- package/tests/check.test.ts +26 -5
package/dist/cli.js
CHANGED
|
@@ -263,20 +263,20 @@ program
|
|
|
263
263
|
});
|
|
264
264
|
// check
|
|
265
265
|
program
|
|
266
|
-
.command("check [
|
|
267
|
-
.description("Check Motoko files for syntax errors and type issues. If no
|
|
266
|
+
.command("check [args...]")
|
|
267
|
+
.description("Check Motoko canisters or files for syntax errors and type issues. Arguments can be canister names or file paths. If no arguments are given, checks all canisters from mops.toml. Also runs stable compatibility checks for canisters with [check-stable] configured, and runs linting if lintoko is configured in [toolchain]")
|
|
268
268
|
.option("--verbose", "Verbose console output")
|
|
269
269
|
.addOption(new Option("--fix", "Apply autofixes to all files, including transitively imported ones"))
|
|
270
270
|
.allowUnknownOption(true)
|
|
271
|
-
.action(async (
|
|
271
|
+
.action(async (args, options) => {
|
|
272
272
|
checkConfigFile(true);
|
|
273
|
-
const { extraArgs, args:
|
|
273
|
+
const { extraArgs, args: argList } = parseExtraArgs(args);
|
|
274
274
|
await installAll({
|
|
275
275
|
silent: true,
|
|
276
276
|
lock: "ignore",
|
|
277
277
|
installFromLockFile: true,
|
|
278
278
|
});
|
|
279
|
-
await check(
|
|
279
|
+
await check(argList, {
|
|
280
280
|
...options,
|
|
281
281
|
extraArgs,
|
|
282
282
|
});
|
|
@@ -296,19 +296,19 @@ program
|
|
|
296
296
|
});
|
|
297
297
|
// check-stable
|
|
298
298
|
program
|
|
299
|
-
.command("check-stable
|
|
300
|
-
.description("Check stable variable compatibility
|
|
299
|
+
.command("check-stable [args...]")
|
|
300
|
+
.description("Check stable variable compatibility. With no arguments, checks all canisters with [check-stable] configured. Arguments can be canister names or an old file path followed by an optional canister name")
|
|
301
301
|
.option("--verbose", "Verbose console output")
|
|
302
302
|
.allowUnknownOption(true)
|
|
303
|
-
.action(async (
|
|
303
|
+
.action(async (args, options) => {
|
|
304
304
|
checkConfigFile(true);
|
|
305
|
-
const { extraArgs } = parseExtraArgs();
|
|
305
|
+
const { extraArgs, args: argList } = parseExtraArgs(args);
|
|
306
306
|
await installAll({
|
|
307
307
|
silent: true,
|
|
308
308
|
lock: "ignore",
|
|
309
309
|
installFromLockFile: true,
|
|
310
310
|
});
|
|
311
|
-
await checkStable(
|
|
311
|
+
await checkStable(argList, {
|
|
312
312
|
...options,
|
|
313
313
|
extraArgs,
|
|
314
314
|
});
|
package/dist/commands/build.js
CHANGED
|
@@ -6,7 +6,7 @@ import { join } from "node:path";
|
|
|
6
6
|
import { lock, unlockSync } from "proper-lockfile";
|
|
7
7
|
import { cliError } from "../error.js";
|
|
8
8
|
import { isCandidCompatible } from "../helpers/is-candid-compatible.js";
|
|
9
|
-
import { resolveCanisterConfigs } from "../helpers/resolve-canisters.js";
|
|
9
|
+
import { filterCanisters, resolveCanisterConfigs, validateCanisterArgs, } from "../helpers/resolve-canisters.js";
|
|
10
10
|
import { getWasmBindings } from "../wasm.js";
|
|
11
11
|
import { getGlobalMocArgs, readConfig, resolveConfigPath } from "../mops.js";
|
|
12
12
|
import { sourcesArgs } from "./sources.js";
|
|
@@ -26,18 +26,10 @@ export async function build(canisterNames, options) {
|
|
|
26
26
|
if (!Object.keys(canisters).length) {
|
|
27
27
|
cliError(`No Motoko canisters found in mops.toml configuration`);
|
|
28
28
|
}
|
|
29
|
-
if (canisterNames) {
|
|
30
|
-
let invalidNames = canisterNames.filter((name) => !(name in canisters));
|
|
31
|
-
if (invalidNames.length) {
|
|
32
|
-
cliError(`Motoko canister(s) not found in mops.toml configuration: ${invalidNames.join(", ")}`);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
29
|
if (!(await exists(outputDir))) {
|
|
36
30
|
await mkdir(outputDir, { recursive: true });
|
|
37
31
|
}
|
|
38
|
-
const filteredCanisters = canisterNames
|
|
39
|
-
? Object.fromEntries(Object.entries(canisters).filter(([name]) => canisterNames.includes(name)))
|
|
40
|
-
: canisters;
|
|
32
|
+
const filteredCanisters = filterCanisters(canisters, canisterNames);
|
|
41
33
|
for (let [canisterName, canister] of Object.entries(filteredCanisters)) {
|
|
42
34
|
console.log(chalk.blue("build canister"), chalk.bold(canisterName));
|
|
43
35
|
let motokoPath = canister.main;
|
|
@@ -181,9 +173,7 @@ function collectExtraArgs(config, canister, canisterName, extraArgs) {
|
|
|
181
173
|
args.push(...config.build.args);
|
|
182
174
|
}
|
|
183
175
|
if (canister.args) {
|
|
184
|
-
|
|
185
|
-
cliError(`Canister config 'args' should be an array of strings for canister ${canisterName}`);
|
|
186
|
-
}
|
|
176
|
+
validateCanisterArgs(canister, canisterName);
|
|
187
177
|
args.push(...canister.args);
|
|
188
178
|
}
|
|
189
179
|
if (extraArgs) {
|
|
@@ -1,14 +1,20 @@
|
|
|
1
|
+
import { CanisterConfig } from "../types.js";
|
|
1
2
|
export interface CheckStableOptions {
|
|
2
3
|
verbose: boolean;
|
|
3
4
|
extraArgs: string[];
|
|
4
5
|
}
|
|
5
|
-
export declare function
|
|
6
|
+
export declare function resolveStablePath(canister: CanisterConfig, canisterName: string, options?: {
|
|
7
|
+
required?: boolean;
|
|
8
|
+
}): string | null;
|
|
9
|
+
export declare function checkStable(args: string[], options?: Partial<CheckStableOptions>): Promise<void>;
|
|
6
10
|
export interface RunStableCheckParams {
|
|
7
11
|
oldFile: string;
|
|
8
12
|
canisterMain: string;
|
|
9
13
|
canisterName: string;
|
|
10
14
|
mocPath: string;
|
|
11
15
|
globalMocArgs: string[];
|
|
16
|
+
canisterArgs: string[];
|
|
17
|
+
sources?: string[];
|
|
12
18
|
options?: Partial<CheckStableOptions>;
|
|
13
19
|
}
|
|
14
20
|
export declare function runStableCheck(params: RunStableCheckParams): Promise<void>;
|
|
@@ -5,30 +5,93 @@ import chalk from "chalk";
|
|
|
5
5
|
import { execa } from "execa";
|
|
6
6
|
import { cliError } from "../error.js";
|
|
7
7
|
import { getGlobalMocArgs, readConfig, resolveConfigPath } from "../mops.js";
|
|
8
|
-
import { resolveSingleCanister } from "../helpers/resolve-canisters.js";
|
|
8
|
+
import { filterCanisters, looksLikeFile, resolveCanisterConfigs, resolveSingleCanister, validateCanisterArgs, } from "../helpers/resolve-canisters.js";
|
|
9
9
|
import { sourcesArgs } from "./sources.js";
|
|
10
10
|
import { toolchain } from "./toolchain/index.js";
|
|
11
11
|
const CHECK_STABLE_DIR = ".mops/.check-stable";
|
|
12
|
-
export
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
export function resolveStablePath(canister, canisterName, options) {
|
|
13
|
+
const stableConfig = canister["check-stable"];
|
|
14
|
+
if (!stableConfig) {
|
|
15
|
+
if (options?.required) {
|
|
16
|
+
cliError(`Canister '${canisterName}' has no [canisters.${canisterName}.check-stable] configuration in mops.toml`);
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
17
19
|
}
|
|
20
|
+
const stablePath = resolveConfigPath(stableConfig.path);
|
|
21
|
+
if (!existsSync(stablePath)) {
|
|
22
|
+
if (stableConfig.skipIfMissing) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
cliError(`Deployed file not found: ${stablePath} (canister '${canisterName}')\n` +
|
|
26
|
+
"Set skipIfMissing = true in [canisters." +
|
|
27
|
+
canisterName +
|
|
28
|
+
".check-stable] to skip this check when the file is missing.");
|
|
29
|
+
}
|
|
30
|
+
return stablePath;
|
|
31
|
+
}
|
|
32
|
+
export async function checkStable(args, options = {}) {
|
|
33
|
+
const config = readConfig();
|
|
18
34
|
const mocPath = await toolchain.bin("moc", { fallback: true });
|
|
19
35
|
const globalMocArgs = getGlobalMocArgs(config);
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
canisterName
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
36
|
+
const firstArg = args[0];
|
|
37
|
+
if (firstArg && looksLikeFile(firstArg)) {
|
|
38
|
+
const oldFile = firstArg;
|
|
39
|
+
const canisterName = args[1];
|
|
40
|
+
const { name, canister } = resolveSingleCanister(config, canisterName);
|
|
41
|
+
if (!canister.main) {
|
|
42
|
+
cliError(`No main file specified for canister '${name}' in mops.toml`);
|
|
43
|
+
}
|
|
44
|
+
validateCanisterArgs(canister, name);
|
|
45
|
+
await runStableCheck({
|
|
46
|
+
oldFile,
|
|
47
|
+
canisterMain: resolveConfigPath(canister.main),
|
|
48
|
+
canisterName: name,
|
|
49
|
+
mocPath,
|
|
50
|
+
globalMocArgs,
|
|
51
|
+
canisterArgs: canister.args ?? [],
|
|
52
|
+
options,
|
|
53
|
+
});
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const canisters = resolveCanisterConfigs(config);
|
|
57
|
+
const canisterNames = args.length > 0 ? args : undefined;
|
|
58
|
+
const filteredCanisters = filterCanisters(canisters, canisterNames);
|
|
59
|
+
const sources = (await sourcesArgs()).flat();
|
|
60
|
+
let checked = 0;
|
|
61
|
+
for (const [name, canister] of Object.entries(filteredCanisters)) {
|
|
62
|
+
if (!canister.main) {
|
|
63
|
+
cliError(`No main file specified for canister '${name}' in mops.toml`);
|
|
64
|
+
}
|
|
65
|
+
validateCanisterArgs(canister, name);
|
|
66
|
+
const stablePath = resolveStablePath(canister, name, {
|
|
67
|
+
required: !!canisterNames,
|
|
68
|
+
});
|
|
69
|
+
if (!stablePath) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
await runStableCheck({
|
|
73
|
+
oldFile: stablePath,
|
|
74
|
+
canisterMain: resolveConfigPath(canister.main),
|
|
75
|
+
canisterName: name,
|
|
76
|
+
mocPath,
|
|
77
|
+
globalMocArgs,
|
|
78
|
+
canisterArgs: canister.args ?? [],
|
|
79
|
+
sources,
|
|
80
|
+
options,
|
|
81
|
+
});
|
|
82
|
+
checked++;
|
|
83
|
+
}
|
|
84
|
+
if (checked === 0 && !canisterNames) {
|
|
85
|
+
cliError("No canisters with [check-stable] configuration found in mops.toml.\n" +
|
|
86
|
+
"Either pass an old file: mops check-stable <old-file> [canister]\n" +
|
|
87
|
+
"Or configure check-stable for a canister:\n\n" +
|
|
88
|
+
" [canisters.backend.check-stable]\n" +
|
|
89
|
+
' path = "deployed.mo"');
|
|
90
|
+
}
|
|
28
91
|
}
|
|
29
92
|
export async function runStableCheck(params) {
|
|
30
|
-
const { oldFile, canisterMain, canisterName, mocPath, globalMocArgs, options = {}, } = params;
|
|
31
|
-
const sources = (await sourcesArgs()).flat();
|
|
93
|
+
const { oldFile, canisterMain, canisterName, mocPath, globalMocArgs, canisterArgs, options = {}, } = params;
|
|
94
|
+
const sources = params.sources ?? (await sourcesArgs()).flat();
|
|
32
95
|
const isOldMostFile = oldFile.endsWith(".most");
|
|
33
96
|
if (!existsSync(oldFile)) {
|
|
34
97
|
cliError(`File not found: ${oldFile}`);
|
|
@@ -38,8 +101,8 @@ export async function runStableCheck(params) {
|
|
|
38
101
|
try {
|
|
39
102
|
const oldMostPath = isOldMostFile
|
|
40
103
|
? oldFile
|
|
41
|
-
: await generateStableTypes(mocPath, oldFile, join(CHECK_STABLE_DIR, "old.most"), sources, globalMocArgs, options);
|
|
42
|
-
const newMostPath = await generateStableTypes(mocPath, canisterMain, join(CHECK_STABLE_DIR, "new.most"), sources, globalMocArgs, options);
|
|
104
|
+
: await generateStableTypes(mocPath, oldFile, join(CHECK_STABLE_DIR, "old.most"), sources, globalMocArgs, canisterArgs, options);
|
|
105
|
+
const newMostPath = await generateStableTypes(mocPath, canisterMain, join(CHECK_STABLE_DIR, "new.most"), sources, globalMocArgs, canisterArgs, options);
|
|
43
106
|
if (options.verbose) {
|
|
44
107
|
console.log(chalk.blue("check-stable"), chalk.gray(`Comparing ${oldMostPath} ↔ ${newMostPath}`));
|
|
45
108
|
}
|
|
@@ -63,7 +126,7 @@ export async function runStableCheck(params) {
|
|
|
63
126
|
await rm(CHECK_STABLE_DIR, { recursive: true, force: true });
|
|
64
127
|
}
|
|
65
128
|
}
|
|
66
|
-
async function generateStableTypes(mocPath, moFile, outputPath, sources, globalMocArgs, options) {
|
|
129
|
+
async function generateStableTypes(mocPath, moFile, outputPath, sources, globalMocArgs, canisterArgs, options) {
|
|
67
130
|
const base = basename(outputPath, ".most");
|
|
68
131
|
const wasmPath = join(CHECK_STABLE_DIR, base + ".wasm");
|
|
69
132
|
const args = [
|
|
@@ -73,6 +136,7 @@ async function generateStableTypes(mocPath, moFile, outputPath, sources, globalM
|
|
|
73
136
|
moFile,
|
|
74
137
|
...sources,
|
|
75
138
|
...globalMocArgs,
|
|
139
|
+
...canisterArgs,
|
|
76
140
|
...(options.extraArgs ?? []),
|
|
77
141
|
];
|
|
78
142
|
if (options.verbose) {
|
package/dist/commands/check.d.ts
CHANGED
|
@@ -3,4 +3,4 @@ export interface CheckOptions {
|
|
|
3
3
|
fix: boolean;
|
|
4
4
|
extraArgs: string[];
|
|
5
5
|
}
|
|
6
|
-
export declare function check(
|
|
6
|
+
export declare function check(args: string[], options?: Partial<CheckOptions>): Promise<void>;
|
package/dist/commands/check.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import { existsSync } from "node:fs";
|
|
3
2
|
import chalk from "chalk";
|
|
4
3
|
import { execa } from "execa";
|
|
5
4
|
import { cliError } from "../error.js";
|
|
6
5
|
import { getGlobalMocArgs, getRootDir, readConfig, resolveConfigPath, } from "../mops.js";
|
|
7
6
|
import { autofixMotoko } from "../helpers/autofix-motoko.js";
|
|
8
7
|
import { getMocSemVer } from "../helpers/get-moc-version.js";
|
|
9
|
-
import { resolveCanisterConfigs,
|
|
10
|
-
import { runStableCheck } from "./check-stable.js";
|
|
8
|
+
import { filterCanisters, looksLikeFile, resolveCanisterConfigs, validateCanisterArgs, } from "../helpers/resolve-canisters.js";
|
|
9
|
+
import { resolveStablePath, runStableCheck } from "./check-stable.js";
|
|
11
10
|
import { sourcesArgs } from "./sources.js";
|
|
12
11
|
import { toolchain } from "./toolchain/index.js";
|
|
13
12
|
import { collectLintRules, lint } from "./lint.js";
|
|
@@ -16,35 +15,137 @@ function supportsAllLibsFlag() {
|
|
|
16
15
|
const version = getMocSemVer();
|
|
17
16
|
return version ? version.compare(MOC_ALL_LIBS_MIN_VERSION) >= 0 : false;
|
|
18
17
|
}
|
|
19
|
-
|
|
20
|
-
const explicitFiles = Array.isArray(files) ? files : files ? [files] : [];
|
|
21
|
-
let fileList = [...explicitFiles];
|
|
22
|
-
const config = readConfig();
|
|
23
|
-
if (fileList.length === 0) {
|
|
24
|
-
fileList = resolveCanisterEntrypoints(config).map(resolveConfigPath);
|
|
25
|
-
}
|
|
26
|
-
if (fileList.length === 0) {
|
|
27
|
-
cliError("No Motoko files specified and no canisters defined in mops.toml.\n" +
|
|
28
|
-
"Either pass files: mops check <files...>\n" +
|
|
29
|
-
"Or define canisters in mops.toml:\n\n" +
|
|
30
|
-
" [canisters.backend]\n" +
|
|
31
|
-
' main = "src/main.mo"');
|
|
32
|
-
}
|
|
33
|
-
const mocPath = await toolchain.bin("moc", { fallback: true });
|
|
34
|
-
const sources = await sourcesArgs();
|
|
35
|
-
const globalMocArgs = getGlobalMocArgs(config);
|
|
36
|
-
// --all-libs enables richer diagnostics with edit suggestions from moc (requires moc >= 1.3.0)
|
|
18
|
+
function checkAllLibsSupport(verbose) {
|
|
37
19
|
const allLibs = supportsAllLibsFlag();
|
|
38
20
|
if (!allLibs) {
|
|
39
21
|
console.log(chalk.yellow(`moc < ${MOC_ALL_LIBS_MIN_VERSION}: some diagnostic hints may be missing`));
|
|
40
22
|
}
|
|
41
|
-
else if (
|
|
23
|
+
else if (verbose) {
|
|
42
24
|
console.log(chalk.blue("check"), chalk.gray("Using --all-libs for richer diagnostics"));
|
|
43
25
|
}
|
|
26
|
+
return allLibs;
|
|
27
|
+
}
|
|
28
|
+
function logAutofixResult(fixResult, verbose) {
|
|
29
|
+
if (fixResult) {
|
|
30
|
+
for (const [file, codes] of fixResult.fixedFiles) {
|
|
31
|
+
const unique = [...new Set(codes)].sort();
|
|
32
|
+
const n = codes.length;
|
|
33
|
+
const rel = path.relative(process.cwd(), file);
|
|
34
|
+
console.log(chalk.green(`Fixed ${rel} (${n} ${n === 1 ? "fix" : "fixes"}: ${unique.join(", ")})`));
|
|
35
|
+
}
|
|
36
|
+
const fileCount = fixResult.fixedFiles.size;
|
|
37
|
+
console.log(chalk.green(`\n✓ ${fixResult.totalFixCount} ${fixResult.totalFixCount === 1 ? "fix" : "fixes"} applied to ${fileCount} ${fileCount === 1 ? "file" : "files"}`));
|
|
38
|
+
}
|
|
39
|
+
else if (verbose) {
|
|
40
|
+
console.log(chalk.yellow("No fixes were needed"));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export async function check(args, options = {}) {
|
|
44
|
+
const config = readConfig();
|
|
45
|
+
const canisters = resolveCanisterConfigs(config);
|
|
46
|
+
const hasCanisters = Object.keys(canisters).length > 0;
|
|
47
|
+
const fileArgs = args.filter(looksLikeFile);
|
|
48
|
+
const nonFileArgs = args.filter((a) => !looksLikeFile(a));
|
|
49
|
+
const isFileMode = fileArgs.length > 0;
|
|
50
|
+
if (isFileMode && nonFileArgs.length > 0) {
|
|
51
|
+
cliError(`Cannot mix file paths and canister names: ${args.join(", ")}\n` +
|
|
52
|
+
"Pass either file paths (e.g. mops check src/main.mo) or canister names (e.g. mops check backend)");
|
|
53
|
+
}
|
|
54
|
+
if (isFileMode) {
|
|
55
|
+
await checkFiles(config, fileArgs, options);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
if (!hasCanisters) {
|
|
59
|
+
cliError("No canisters defined in mops.toml.\n" +
|
|
60
|
+
"Either pass files: mops check <files...>\n" +
|
|
61
|
+
"Or define canisters in mops.toml:\n\n" +
|
|
62
|
+
" [canisters.backend]\n" +
|
|
63
|
+
' main = "src/main.mo"');
|
|
64
|
+
}
|
|
65
|
+
const canisterNames = args.length > 0 ? args : undefined;
|
|
66
|
+
const filtered = filterCanisters(canisters, canisterNames);
|
|
67
|
+
await checkCanisters(config, filtered, options);
|
|
68
|
+
}
|
|
69
|
+
if (config.toolchain?.lintoko) {
|
|
70
|
+
const rootDir = getRootDir();
|
|
71
|
+
const lintRules = await collectLintRules(config, rootDir);
|
|
72
|
+
const lintFiles = isFileMode ? fileArgs : undefined;
|
|
73
|
+
await lint(undefined, {
|
|
74
|
+
verbose: options.verbose,
|
|
75
|
+
fix: options.fix,
|
|
76
|
+
rules: lintRules,
|
|
77
|
+
files: lintFiles,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async function checkCanisters(config, canisters, options) {
|
|
82
|
+
const mocPath = await toolchain.bin("moc", { fallback: true });
|
|
83
|
+
const sources = (await sourcesArgs()).flat();
|
|
84
|
+
const globalMocArgs = getGlobalMocArgs(config);
|
|
85
|
+
const allLibs = checkAllLibsSupport(options.verbose);
|
|
86
|
+
for (const [canisterName, canister] of Object.entries(canisters)) {
|
|
87
|
+
if (!canister.main) {
|
|
88
|
+
cliError(`No main file specified for canister '${canisterName}' in mops.toml`);
|
|
89
|
+
}
|
|
90
|
+
validateCanisterArgs(canister, canisterName);
|
|
91
|
+
const motokoPath = resolveConfigPath(canister.main);
|
|
92
|
+
const mocArgs = [
|
|
93
|
+
"--check",
|
|
94
|
+
...(allLibs ? ["--all-libs"] : []),
|
|
95
|
+
...sources,
|
|
96
|
+
...globalMocArgs,
|
|
97
|
+
...(canister.args ?? []),
|
|
98
|
+
...(options.extraArgs ?? []),
|
|
99
|
+
];
|
|
100
|
+
if (options.fix) {
|
|
101
|
+
if (options.verbose) {
|
|
102
|
+
console.log(chalk.blue("check"), chalk.gray(`Attempting to fix ${canisterName}`));
|
|
103
|
+
}
|
|
104
|
+
const fixResult = await autofixMotoko(mocPath, [motokoPath], mocArgs);
|
|
105
|
+
logAutofixResult(fixResult, options.verbose);
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
const args = [motokoPath, ...mocArgs];
|
|
109
|
+
if (options.verbose) {
|
|
110
|
+
console.log(chalk.blue("check"), chalk.gray(`Checking canister ${canisterName}:`));
|
|
111
|
+
console.log(chalk.gray(mocPath, JSON.stringify(args)));
|
|
112
|
+
}
|
|
113
|
+
const result = await execa(mocPath, args, {
|
|
114
|
+
stdio: "inherit",
|
|
115
|
+
reject: false,
|
|
116
|
+
});
|
|
117
|
+
if (result.exitCode !== 0) {
|
|
118
|
+
cliError(`✗ Check failed for canister ${canisterName} (exit code: ${result.exitCode})`);
|
|
119
|
+
}
|
|
120
|
+
console.log(chalk.green(`✓ ${canisterName}`));
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
cliError(`Error while checking canister ${canisterName}${err?.message ? `\n${err.message}` : ""}`);
|
|
124
|
+
}
|
|
125
|
+
const stablePath = resolveStablePath(canister, canisterName);
|
|
126
|
+
if (stablePath) {
|
|
127
|
+
await runStableCheck({
|
|
128
|
+
oldFile: stablePath,
|
|
129
|
+
canisterMain: motokoPath,
|
|
130
|
+
canisterName,
|
|
131
|
+
mocPath,
|
|
132
|
+
globalMocArgs,
|
|
133
|
+
canisterArgs: canister.args ?? [],
|
|
134
|
+
sources,
|
|
135
|
+
options: { verbose: options.verbose, extraArgs: options.extraArgs },
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async function checkFiles(config, files, options) {
|
|
141
|
+
const mocPath = await toolchain.bin("moc", { fallback: true });
|
|
142
|
+
const sources = (await sourcesArgs()).flat();
|
|
143
|
+
const globalMocArgs = getGlobalMocArgs(config);
|
|
144
|
+
const allLibs = checkAllLibsSupport(options.verbose);
|
|
44
145
|
const mocArgs = [
|
|
45
146
|
"--check",
|
|
46
147
|
...(allLibs ? ["--all-libs"] : []),
|
|
47
|
-
...sources
|
|
148
|
+
...sources,
|
|
48
149
|
...globalMocArgs,
|
|
49
150
|
...(options.extraArgs ?? []),
|
|
50
151
|
];
|
|
@@ -52,24 +153,10 @@ export async function check(files, options = {}) {
|
|
|
52
153
|
if (options.verbose) {
|
|
53
154
|
console.log(chalk.blue("check"), chalk.gray("Attempting to fix files"));
|
|
54
155
|
}
|
|
55
|
-
const fixResult = await autofixMotoko(mocPath,
|
|
56
|
-
|
|
57
|
-
for (const [file, codes] of fixResult.fixedFiles) {
|
|
58
|
-
const unique = [...new Set(codes)].sort();
|
|
59
|
-
const n = codes.length;
|
|
60
|
-
const rel = path.relative(process.cwd(), file);
|
|
61
|
-
console.log(chalk.green(`Fixed ${rel} (${n} ${n === 1 ? "fix" : "fixes"}: ${unique.join(", ")})`));
|
|
62
|
-
}
|
|
63
|
-
const fileCount = fixResult.fixedFiles.size;
|
|
64
|
-
console.log(chalk.green(`\n✓ ${fixResult.totalFixCount} ${fixResult.totalFixCount === 1 ? "fix" : "fixes"} applied to ${fileCount} ${fileCount === 1 ? "file" : "files"}`));
|
|
65
|
-
}
|
|
66
|
-
else {
|
|
67
|
-
if (options.verbose) {
|
|
68
|
-
console.log(chalk.yellow("No fixes were needed"));
|
|
69
|
-
}
|
|
70
|
-
}
|
|
156
|
+
const fixResult = await autofixMotoko(mocPath, files, mocArgs);
|
|
157
|
+
logAutofixResult(fixResult, options.verbose);
|
|
71
158
|
}
|
|
72
|
-
for (const file of
|
|
159
|
+
for (const file of files) {
|
|
73
160
|
try {
|
|
74
161
|
const args = [file, ...mocArgs];
|
|
75
162
|
if (options.verbose) {
|
|
@@ -89,42 +176,4 @@ export async function check(files, options = {}) {
|
|
|
89
176
|
cliError(`Error while checking ${file}${err?.message ? `\n${err.message}` : ""}`);
|
|
90
177
|
}
|
|
91
178
|
}
|
|
92
|
-
const canisters = resolveCanisterConfigs(config);
|
|
93
|
-
for (const [name, canister] of Object.entries(canisters)) {
|
|
94
|
-
const stableConfig = canister["check-stable"];
|
|
95
|
-
if (!stableConfig) {
|
|
96
|
-
continue;
|
|
97
|
-
}
|
|
98
|
-
if (!canister.main) {
|
|
99
|
-
cliError(`No main file specified for canister '${name}' in mops.toml`);
|
|
100
|
-
}
|
|
101
|
-
const stablePath = resolveConfigPath(stableConfig.path);
|
|
102
|
-
if (!existsSync(stablePath)) {
|
|
103
|
-
if (stableConfig.skipIfMissing) {
|
|
104
|
-
continue;
|
|
105
|
-
}
|
|
106
|
-
cliError(`Deployed file not found: ${stablePath} (canister '${name}')\n` +
|
|
107
|
-
"Set skipIfMissing = true in [canisters." +
|
|
108
|
-
name +
|
|
109
|
-
".check-stable] to skip this check when the file is missing.");
|
|
110
|
-
}
|
|
111
|
-
await runStableCheck({
|
|
112
|
-
oldFile: stablePath,
|
|
113
|
-
canisterMain: resolveConfigPath(canister.main),
|
|
114
|
-
canisterName: name,
|
|
115
|
-
mocPath,
|
|
116
|
-
globalMocArgs,
|
|
117
|
-
options: { verbose: options.verbose, extraArgs: options.extraArgs },
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
if (config.toolchain?.lintoko) {
|
|
121
|
-
const rootDir = getRootDir();
|
|
122
|
-
const lintRules = await collectLintRules(config, rootDir);
|
|
123
|
-
await lint(undefined, {
|
|
124
|
-
verbose: options.verbose,
|
|
125
|
-
fix: options.fix,
|
|
126
|
-
rules: lintRules,
|
|
127
|
-
files: explicitFiles.length > 0 ? explicitFiles : undefined,
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
179
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { CanisterConfig, Config } from "../types.js";
|
|
2
2
|
export declare function resolveCanisterConfigs(config: Config): Record<string, CanisterConfig>;
|
|
3
|
-
export declare function
|
|
3
|
+
export declare function filterCanisters(canisters: Record<string, CanisterConfig>, names?: string[]): Record<string, CanisterConfig>;
|
|
4
4
|
export declare function resolveSingleCanister(config: Config, canisterName?: string): {
|
|
5
5
|
name: string;
|
|
6
6
|
canister: CanisterConfig;
|
|
7
7
|
};
|
|
8
|
+
export declare function looksLikeFile(arg: string): boolean;
|
|
9
|
+
export declare function validateCanisterArgs(canister: CanisterConfig, canisterName: string): void;
|
|
@@ -5,11 +5,15 @@ export function resolveCanisterConfigs(config) {
|
|
|
5
5
|
}
|
|
6
6
|
return Object.fromEntries(Object.entries(config.canisters).map(([name, c]) => typeof c === "string" ? [name, { main: c }] : [name, c]));
|
|
7
7
|
}
|
|
8
|
-
export function
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
export function filterCanisters(canisters, names) {
|
|
9
|
+
if (!names) {
|
|
10
|
+
return canisters;
|
|
11
|
+
}
|
|
12
|
+
const invalidNames = names.filter((name) => !(name in canisters));
|
|
13
|
+
if (invalidNames.length) {
|
|
14
|
+
cliError(`Canister(s) not found in mops.toml: ${invalidNames.join(", ")}. Available: ${Object.keys(canisters).join(", ")}`);
|
|
15
|
+
}
|
|
16
|
+
return Object.fromEntries(Object.entries(canisters).filter(([name]) => names.includes(name)));
|
|
13
17
|
}
|
|
14
18
|
export function resolveSingleCanister(config, canisterName) {
|
|
15
19
|
const canisters = resolveCanisterConfigs(config);
|
|
@@ -29,3 +33,14 @@ export function resolveSingleCanister(config, canisterName) {
|
|
|
29
33
|
}
|
|
30
34
|
return { name: names[0], canister: canisters[names[0]] };
|
|
31
35
|
}
|
|
36
|
+
export function looksLikeFile(arg) {
|
|
37
|
+
return (arg.endsWith(".mo") ||
|
|
38
|
+
arg.endsWith(".most") ||
|
|
39
|
+
arg.includes("/") ||
|
|
40
|
+
arg.includes("\\"));
|
|
41
|
+
}
|
|
42
|
+
export function validateCanisterArgs(canister, canisterName) {
|
|
43
|
+
if (canister.args && typeof canister.args === "string") {
|
|
44
|
+
cliError(`Canister config 'args' should be an array of strings for canister ${canisterName}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
package/dist/package.json
CHANGED
|
@@ -53,6 +53,24 @@ describe("check-stable", () => {
|
|
|
53
53
|
expect(existsSync(path.join(cwd, "new.most"))).toBe(false);
|
|
54
54
|
expect(existsSync(path.join(cwd, "new.wasm"))).toBe(false);
|
|
55
55
|
});
|
|
56
|
+
test("[canisters.X].args are passed to moc (enhanced migration)", async () => {
|
|
57
|
+
const cwd = path.join(import.meta.dirname, "check-stable/canister-args");
|
|
58
|
+
const result = await cli(["check-stable", "old.most"], { cwd });
|
|
59
|
+
expect(result.exitCode).toBe(0);
|
|
60
|
+
expect(result.stdout).toMatch(/Stable compatibility check passed/);
|
|
61
|
+
});
|
|
62
|
+
test("no args checks all canisters with [check-stable] config", async () => {
|
|
63
|
+
const cwd = path.join(import.meta.dirname, "check/deployed-compatible");
|
|
64
|
+
const result = await cli(["check-stable"], { cwd });
|
|
65
|
+
expect(result.exitCode).toBe(0);
|
|
66
|
+
expect(result.stdout).toMatch(/Stable compatibility check passed/);
|
|
67
|
+
});
|
|
68
|
+
test("canister name filters to specific canister", async () => {
|
|
69
|
+
const cwd = path.join(import.meta.dirname, "check/deployed-compatible");
|
|
70
|
+
const result = await cli(["check-stable", "backend"], { cwd });
|
|
71
|
+
expect(result.exitCode).toBe(0);
|
|
72
|
+
expect(result.stdout).toMatch(/Stable compatibility check passed/);
|
|
73
|
+
});
|
|
56
74
|
test("errors when old file does not exist", async () => {
|
|
57
75
|
const cwd = path.join(import.meta.dirname, "check-stable/compatible");
|
|
58
76
|
const result = await cli(["check-stable", "nonexistent.mo"], { cwd });
|
package/dist/tests/check.test.js
CHANGED
|
@@ -34,30 +34,48 @@ 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
|
|
37
|
+
test("no args checks all canisters", async () => {
|
|
38
38
|
const cwd = path.join(import.meta.dirname, "check/canisters");
|
|
39
39
|
await cliSnapshot(["check"], { cwd }, 0);
|
|
40
40
|
});
|
|
41
|
-
test("canister
|
|
41
|
+
test("canister name filters to specific canister", async () => {
|
|
42
|
+
const cwd = path.join(import.meta.dirname, "check/canisters");
|
|
43
|
+
const result = await cli(["check", "backend"], { cwd });
|
|
44
|
+
expect(result.exitCode).toBe(0);
|
|
45
|
+
expect(result.stdout).toMatch(/✓ backend/);
|
|
46
|
+
});
|
|
47
|
+
test("canister resolved relative to config root when run from subdirectory", async () => {
|
|
42
48
|
const fixtureRoot = path.join(import.meta.dirname, "check/canisters-subdir");
|
|
43
49
|
const subdir = path.join(fixtureRoot, "src/backend");
|
|
44
50
|
const result = await cli(["check"], { cwd: subdir });
|
|
45
51
|
expect(result.exitCode).toBe(0);
|
|
46
52
|
expect(result.stdout).toMatch(/✓/);
|
|
47
53
|
});
|
|
48
|
-
test("[moc] args applied
|
|
54
|
+
test("[moc] args applied to canister check", async () => {
|
|
49
55
|
const cwd = path.join(import.meta.dirname, "check/canisters-moc-args");
|
|
50
56
|
const result = await cli(["check"], { cwd });
|
|
51
57
|
expect(result.exitCode).toBe(1);
|
|
52
58
|
expect(result.stderr).toMatch(/warning \[M0194\]/);
|
|
53
59
|
});
|
|
54
|
-
test("
|
|
60
|
+
test("[canisters.X].args applied to canister check", async () => {
|
|
61
|
+
const cwd = path.join(import.meta.dirname, "check/canisters-canister-args");
|
|
62
|
+
const result = await cli(["check"], { cwd });
|
|
63
|
+
expect(result.exitCode).toBe(1);
|
|
64
|
+
expect(result.stderr).toMatch(/warning \[M0194\]/);
|
|
65
|
+
});
|
|
66
|
+
test("canister with errors", async () => {
|
|
55
67
|
const cwd = path.join(import.meta.dirname, "check/canisters-error");
|
|
56
68
|
const result = await cli(["check"], { cwd });
|
|
57
69
|
expect(result.exitCode).toBe(1);
|
|
58
70
|
expect(result.stderr).toMatch(/error/i);
|
|
59
71
|
});
|
|
60
|
-
test("
|
|
72
|
+
test("invalid canister name errors", async () => {
|
|
73
|
+
const cwd = path.join(import.meta.dirname, "check/canisters");
|
|
74
|
+
const result = await cli(["check", "nonexistent"], { cwd });
|
|
75
|
+
expect(result.exitCode).toBe(1);
|
|
76
|
+
expect(result.stderr).toMatch(/not found in mops\.toml/);
|
|
77
|
+
});
|
|
78
|
+
test("--fix with canister", async () => {
|
|
61
79
|
const cwd = path.join(import.meta.dirname, "check/canisters");
|
|
62
80
|
const result = await cli(["check", "--fix"], { cwd });
|
|
63
81
|
expect(result.exitCode).toBe(0);
|