ic-mops 2.14.1 → 2.15.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 +14 -0
- package/bundle/cli.tgz +0 -0
- package/cli.ts +143 -3
- package/commands/build.ts +30 -77
- package/commands/check-stable.ts +4 -0
- package/commands/check.ts +26 -22
- package/commands/deployed.ts +162 -0
- package/commands/generate.ts +201 -0
- package/commands/lint.ts +6 -3
- package/dist/cli.js +75 -3
- package/dist/commands/build.d.ts +6 -0
- package/dist/commands/build.js +26 -52
- package/dist/commands/check-stable.d.ts +2 -0
- package/dist/commands/check-stable.js +2 -2
- package/dist/commands/check.d.ts +2 -0
- package/dist/commands/check.js +20 -21
- package/dist/commands/deployed.d.ts +10 -0
- package/dist/commands/deployed.js +97 -0
- package/dist/commands/generate.d.ts +6 -0
- package/dist/commands/generate.js +124 -0
- package/dist/commands/lint.d.ts +2 -0
- package/dist/commands/lint.js +1 -1
- package/dist/helpers/autofix-motoko.d.ts +2 -0
- package/dist/helpers/autofix-motoko.js +52 -49
- package/dist/helpers/migrations.d.ts +1 -1
- package/dist/helpers/migrations.js +8 -4
- package/dist/helpers/moc-args.d.ts +25 -0
- package/dist/helpers/moc-args.js +58 -0
- package/dist/package.json +6 -6
- package/dist/tests/check-fix-utf8.test.d.ts +1 -0
- package/dist/tests/check-fix-utf8.test.js +38 -0
- package/dist/tests/check.test.js +21 -6
- package/dist/tests/deployed.test.d.ts +1 -0
- package/dist/tests/deployed.test.js +173 -0
- package/dist/tests/generate.test.d.ts +1 -0
- package/dist/tests/generate.test.js +107 -0
- package/dist/tests/helpers.d.ts +8 -0
- package/dist/tests/helpers.js +28 -3
- package/dist/tests/migrate.test.js +5 -19
- package/dist/types.d.ts +3 -0
- package/helpers/autofix-motoko.ts +73 -64
- package/helpers/migrations.ts +8 -3
- package/helpers/moc-args.ts +123 -0
- package/package.json +6 -6
- package/tests/__snapshots__/check-fix-utf8.test.ts.snap +27 -0
- package/tests/__snapshots__/check.test.ts.snap +4 -17
- package/tests/__snapshots__/deployed.test.ts.snap +10 -0
- package/tests/__snapshots__/generate.test.ts.snap +39 -0
- package/tests/check/fix-utf8/mops.toml +5 -0
- package/tests/check/fix-utf8/multibyte.mo +15 -0
- package/tests/check-fix-utf8.test.ts +51 -0
- package/tests/check.test.ts +24 -11
- package/tests/deployed/basic/main.mo +4 -0
- package/tests/deployed/basic/mops.toml +8 -0
- package/tests/deployed/multi/bar.mo +1 -0
- package/tests/deployed/multi/foo.mo +1 -0
- package/tests/deployed/multi/mops.toml +11 -0
- package/tests/deployed.test.ts +249 -0
- package/tests/generate/basic/candid/bar.did +4 -0
- package/tests/generate/basic/mops.toml +12 -0
- package/tests/generate/basic/src/Bar.mo +5 -0
- package/tests/generate/basic/src/Foo.mo +5 -0
- package/tests/generate/error/mops.toml +8 -0
- package/tests/generate/error/src/Broken.mo +5 -0
- package/tests/generate.test.ts +140 -0
- package/tests/helpers.ts +31 -3
- package/tests/migrate.test.ts +7 -22
- package/types.ts +3 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { execa } from "execa";
|
|
3
|
+
import { mkdir } from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { cliError } from "../error.js";
|
|
6
|
+
import {
|
|
7
|
+
filterCanisters,
|
|
8
|
+
resolveCanisterConfigs,
|
|
9
|
+
} from "../helpers/resolve-canisters.js";
|
|
10
|
+
import {
|
|
11
|
+
GENERATE_CANDID_MANAGED_FLAGS,
|
|
12
|
+
prepareMocArgs,
|
|
13
|
+
} from "../helpers/moc-args.js";
|
|
14
|
+
import {
|
|
15
|
+
getRootDir,
|
|
16
|
+
readConfig,
|
|
17
|
+
resolveConfigPath,
|
|
18
|
+
writeConfig,
|
|
19
|
+
} from "../mops.js";
|
|
20
|
+
import { CanisterConfig } from "../types.js";
|
|
21
|
+
import { toolchain } from "./toolchain/index.js";
|
|
22
|
+
|
|
23
|
+
export interface GenerateCandidOptions {
|
|
24
|
+
output?: string;
|
|
25
|
+
verbose?: boolean;
|
|
26
|
+
extraArgs?: string[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function generateCandid(
|
|
30
|
+
canisterNames: string[] | undefined,
|
|
31
|
+
options: GenerateCandidOptions,
|
|
32
|
+
): Promise<void> {
|
|
33
|
+
if (canisterNames?.length === 0) {
|
|
34
|
+
cliError("No canisters specified");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const config = readConfig();
|
|
38
|
+
const canisters = resolveCanisterConfigs(config);
|
|
39
|
+
if (!Object.keys(canisters).length) {
|
|
40
|
+
cliError("No Motoko canisters found in mops.toml configuration");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const filtered = filterCanisters(canisters, canisterNames);
|
|
44
|
+
const filteredEntries = Object.entries(filtered);
|
|
45
|
+
|
|
46
|
+
if (options.output && filteredEntries.length > 1) {
|
|
47
|
+
cliError(
|
|
48
|
+
"--output / -o is only supported when generating for a single canister",
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const mocPath = await toolchain.bin("moc", { fallback: true });
|
|
53
|
+
const rootDir = getRootDir();
|
|
54
|
+
|
|
55
|
+
let configChanged = false;
|
|
56
|
+
|
|
57
|
+
for (const [canisterName, canister] of filteredEntries) {
|
|
58
|
+
const dest = resolveDestination(
|
|
59
|
+
canisterName,
|
|
60
|
+
canister,
|
|
61
|
+
canisters,
|
|
62
|
+
options.output,
|
|
63
|
+
rootDir,
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
console.log(
|
|
67
|
+
chalk.blue("generate candid"),
|
|
68
|
+
chalk.bold(canisterName),
|
|
69
|
+
chalk.gray(`→ ${dest.fsPath}`),
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const prepared = await prepareMocArgs(config, canister, canisterName, {
|
|
73
|
+
mode: "build",
|
|
74
|
+
managedFlags: GENERATE_CANDID_MANAGED_FLAGS,
|
|
75
|
+
commandName: "mops generate candid",
|
|
76
|
+
verbose: options.verbose,
|
|
77
|
+
extraArgs: options.extraArgs,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
await mkdir(path.dirname(dest.fsPath), { recursive: true });
|
|
82
|
+
const args = [
|
|
83
|
+
"--idl",
|
|
84
|
+
"-o",
|
|
85
|
+
dest.fsPath,
|
|
86
|
+
prepared.motokoPath,
|
|
87
|
+
...prepared.args,
|
|
88
|
+
];
|
|
89
|
+
if (options.verbose) {
|
|
90
|
+
console.log(chalk.gray(mocPath, JSON.stringify(args)));
|
|
91
|
+
}
|
|
92
|
+
const result = await execa(mocPath, args, {
|
|
93
|
+
stdio: options.verbose ? "inherit" : "pipe",
|
|
94
|
+
reject: false,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
if (result.exitCode !== 0) {
|
|
98
|
+
if (!options.verbose) {
|
|
99
|
+
if (result.stderr) {
|
|
100
|
+
console.error(chalk.red(result.stderr));
|
|
101
|
+
}
|
|
102
|
+
if (result.stdout?.trim()) {
|
|
103
|
+
console.error(chalk.yellow("Output:"));
|
|
104
|
+
console.error(result.stdout);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
cliError(
|
|
108
|
+
`Failed to generate Candid for canister ${canisterName} (exit code: ${result.exitCode})`,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (dest.configPath !== null) {
|
|
113
|
+
const c = (config.canisters ??= {});
|
|
114
|
+
const existing = c[canisterName];
|
|
115
|
+
const obj: CanisterConfig =
|
|
116
|
+
typeof existing === "string"
|
|
117
|
+
? { main: existing }
|
|
118
|
+
: { ...(existing ?? {}) };
|
|
119
|
+
obj.candid = dest.configPath;
|
|
120
|
+
c[canisterName] = obj;
|
|
121
|
+
configChanged = true;
|
|
122
|
+
}
|
|
123
|
+
} finally {
|
|
124
|
+
await prepared.cleanup();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (configChanged) {
|
|
129
|
+
writeConfig(config);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
console.log(
|
|
133
|
+
chalk.green(
|
|
134
|
+
`\n✓ Generated Candid for ${filteredEntries.length} canister${filteredEntries.length === 1 ? "" : "s"}`,
|
|
135
|
+
),
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
interface Destination {
|
|
140
|
+
/** Path used for filesystem operations and passed to moc (cwd-relative or absolute). */
|
|
141
|
+
fsPath: string;
|
|
142
|
+
/** Value to write into `[canisters.<name>].candid` (project-root-relative). `null` skips the config update. */
|
|
143
|
+
configPath: string | null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function resolveDestination(
|
|
147
|
+
canisterName: string,
|
|
148
|
+
canister: CanisterConfig,
|
|
149
|
+
allCanisters: Record<string, CanisterConfig>,
|
|
150
|
+
outputFlag: string | undefined,
|
|
151
|
+
rootDir: string,
|
|
152
|
+
): Destination {
|
|
153
|
+
if (!canister.main) {
|
|
154
|
+
cliError(`No main file is specified for canister ${canisterName}`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
let fsPath: string;
|
|
158
|
+
let configPath: string | null;
|
|
159
|
+
|
|
160
|
+
if (outputFlag) {
|
|
161
|
+
fsPath = outputFlag;
|
|
162
|
+
configPath = null;
|
|
163
|
+
|
|
164
|
+
const outAbs = path.resolve(fsPath);
|
|
165
|
+
for (const [otherName, other] of Object.entries(allCanisters)) {
|
|
166
|
+
if (otherName === canisterName || !other.candid) {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (path.resolve(rootDir, other.candid) === outAbs) {
|
|
170
|
+
console.warn(
|
|
171
|
+
chalk.yellow(
|
|
172
|
+
`Warning: --output path collides with [canisters.${otherName}].candid (${other.candid}). Sharing a .did between canisters is almost always a mistake.`,
|
|
173
|
+
),
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
} else if (canister.candid) {
|
|
178
|
+
fsPath = resolveConfigPath(canister.candid);
|
|
179
|
+
configPath = null;
|
|
180
|
+
} else {
|
|
181
|
+
// Default: <dirname(main)>/<canisterName>.did, forward slashes for the toml value
|
|
182
|
+
const mainDir = path.dirname(canister.main).replace(/\\/g, "/");
|
|
183
|
+
const projectRel =
|
|
184
|
+
mainDir === "." || mainDir === ""
|
|
185
|
+
? `${canisterName}.did`
|
|
186
|
+
: `${mainDir}/${canisterName}.did`;
|
|
187
|
+
fsPath = resolveConfigPath(projectRel);
|
|
188
|
+
configPath = projectRel;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const absPath = path.resolve(fsPath);
|
|
192
|
+
const dotMopsDir = path.resolve(rootDir, ".mops");
|
|
193
|
+
if (absPath === dotMopsDir || absPath.startsWith(dotMopsDir + path.sep)) {
|
|
194
|
+
cliError(
|
|
195
|
+
`Refusing to write Candid file inside .mops/ (private build cache): ${fsPath}\n` +
|
|
196
|
+
"Choose a path outside .mops/ — it should be committable and readable by downstream tooling.",
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return { fsPath, configPath };
|
|
201
|
+
}
|
package/commands/lint.ts
CHANGED
|
@@ -103,6 +103,8 @@ export interface LintOptions {
|
|
|
103
103
|
rules?: string[];
|
|
104
104
|
files?: string[];
|
|
105
105
|
extraArgs: string[];
|
|
106
|
+
/** Commander `--no-check-limit`: lint the full migration chain. */
|
|
107
|
+
noCheckLimit?: boolean;
|
|
106
108
|
}
|
|
107
109
|
|
|
108
110
|
function buildCommonArgs(
|
|
@@ -193,9 +195,10 @@ async function lintImpl(
|
|
|
193
195
|
: "lintoko";
|
|
194
196
|
|
|
195
197
|
const isExplicit = !!filter || !!(options.files && options.files.length > 0);
|
|
196
|
-
const trimmedMigrations =
|
|
197
|
-
|
|
198
|
-
|
|
198
|
+
const trimmedMigrations =
|
|
199
|
+
isExplicit || options.noCheckLimit
|
|
200
|
+
? new Set<string>()
|
|
201
|
+
: getTrimmedMigrationFiles(config);
|
|
199
202
|
|
|
200
203
|
let filesToLint: string[];
|
|
201
204
|
if (options.files && options.files.length > 0) {
|
package/dist/cli.js
CHANGED
|
@@ -12,9 +12,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
14
|
import { checkStable } from "./commands/check-stable.js";
|
|
15
|
+
import { deployed, deployedInit } from "./commands/deployed.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";
|
|
19
|
+
import { generateCandid } from "./commands/generate.js";
|
|
18
20
|
import { info } from "./commands/info.js";
|
|
19
21
|
import { init } from "./commands/init.js";
|
|
20
22
|
import { lint } from "./commands/lint.js";
|
|
@@ -60,6 +62,16 @@ function parseExtraArgs(variadicArgs) {
|
|
|
60
62
|
: [];
|
|
61
63
|
return { extraArgs, args };
|
|
62
64
|
}
|
|
65
|
+
// Shared `--help` section describing the enhanced migration `check-limit`
|
|
66
|
+
// trimming and its override flag, so the limit behaviour is discoverable
|
|
67
|
+
// from `--help`. `withFix` appends the `--fix` hint for commands that support it.
|
|
68
|
+
function enhancedMigrationHelp(withFix) {
|
|
69
|
+
const example = withFix ? " (e.g. to --fix older migrations)" : "";
|
|
70
|
+
return ("\nEnhanced migration ([canisters.<name>.migrations]):\n" +
|
|
71
|
+
" The canister is checked against its migration chain. [migrations].check-limit\n" +
|
|
72
|
+
" trims it to the last N migrations (older ones are skipped). Pass --no-check-limit\n" +
|
|
73
|
+
` to use the full chain${example}.`);
|
|
74
|
+
}
|
|
63
75
|
program.name("mops");
|
|
64
76
|
// --version
|
|
65
77
|
program.version(`CLI ${version()}\nAPI ${apiVersion}`, "-v --version");
|
|
@@ -76,7 +88,7 @@ program
|
|
|
76
88
|
.command("add <pkg>")
|
|
77
89
|
.description("Install the package and save it to mops.toml")
|
|
78
90
|
.option("--dev", "Add to [dev-dependencies] section")
|
|
79
|
-
.option("--verbose")
|
|
91
|
+
.option("--verbose", "Show more information")
|
|
80
92
|
.addOption(new Option("--lock <action>", "Lockfile action").choices([
|
|
81
93
|
"update",
|
|
82
94
|
"ignore",
|
|
@@ -111,7 +123,7 @@ program
|
|
|
111
123
|
.alias("i")
|
|
112
124
|
.description("Install all dependencies specified in mops.toml")
|
|
113
125
|
.option("--no-toolchain", "Do not install toolchain")
|
|
114
|
-
.option("--verbose")
|
|
126
|
+
.option("--verbose", "Show more information")
|
|
115
127
|
.addOption(new Option("--lock <action>", "Lockfile action").choices([
|
|
116
128
|
"check",
|
|
117
129
|
"update",
|
|
@@ -145,7 +157,7 @@ program
|
|
|
145
157
|
.option("--no-docs", "Do not generate docs")
|
|
146
158
|
.option("--no-test", "Do not run tests")
|
|
147
159
|
.option("--no-bench", "Do not run benchmarks")
|
|
148
|
-
.option("--verbose")
|
|
160
|
+
.option("--verbose", "Show more information")
|
|
149
161
|
.action(async (options) => {
|
|
150
162
|
if (!checkConfigFile()) {
|
|
151
163
|
process.exit(1);
|
|
@@ -247,6 +259,12 @@ program
|
|
|
247
259
|
.description("Build a canister")
|
|
248
260
|
.addOption(new Option("--verbose", "Verbose console output"))
|
|
249
261
|
.addOption(new Option("--output, -o <output>", "Output directory"))
|
|
262
|
+
.addHelpText("after", "\nArguments after -- are forwarded directly to moc, e.g.:\n $ mops build -- -Werror")
|
|
263
|
+
.addHelpText("after", "\nEnhanced migration ([canisters.<name>.migrations]):\n" +
|
|
264
|
+
" The canister is built against its full migration chain (every migration is\n" +
|
|
265
|
+
" compiled into the wasm). If mops check passes but mops build fails while\n" +
|
|
266
|
+
" [migrations].check-limit is set, re-run with mops check --no-check-limit to\n" +
|
|
267
|
+
" surface the issue (check trims the chain; build compiles all of it).")
|
|
250
268
|
.allowUnknownOption(true) // TODO: restrict unknown before "--"
|
|
251
269
|
.action(async (canisters, options) => {
|
|
252
270
|
checkConfigFile(true);
|
|
@@ -268,6 +286,9 @@ program
|
|
|
268
286
|
.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]")
|
|
269
287
|
.option("--verbose", "Verbose console output")
|
|
270
288
|
.addOption(new Option("--fix", "Apply autofixes to all files, including transitively imported ones"))
|
|
289
|
+
.addOption(new Option("--no-check-limit", "Check the full migration chain, ignoring [migrations].check-limit"))
|
|
290
|
+
.addHelpText("after", "\nArguments after -- are forwarded directly to moc, e.g.:\n $ mops check -- -Werror")
|
|
291
|
+
.addHelpText("after", enhancedMigrationHelp(true))
|
|
271
292
|
.allowUnknownOption(true)
|
|
272
293
|
.action(async (args, options) => {
|
|
273
294
|
checkConfigFile(true);
|
|
@@ -300,6 +321,9 @@ program
|
|
|
300
321
|
.command("check-stable [args...]")
|
|
301
322
|
.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")
|
|
302
323
|
.option("--verbose", "Verbose console output")
|
|
324
|
+
.addOption(new Option("--no-check-limit", "Check the full migration chain, ignoring [migrations].check-limit"))
|
|
325
|
+
.addHelpText("after", "\nArguments after -- are forwarded directly to moc, e.g.:\n $ mops check-stable -- -Werror")
|
|
326
|
+
.addHelpText("after", enhancedMigrationHelp(false))
|
|
303
327
|
.allowUnknownOption(true)
|
|
304
328
|
.action(async (args, options) => {
|
|
305
329
|
checkConfigFile(true);
|
|
@@ -314,6 +338,25 @@ program
|
|
|
314
338
|
extraArgs,
|
|
315
339
|
});
|
|
316
340
|
});
|
|
341
|
+
// deployed
|
|
342
|
+
const deployedCommand = new Command("deployed")
|
|
343
|
+
.description("Post-deploy hook: promote .most stable-types files into the deployed directory so `mops check-stable` compares against the just-deployed version. Pass canister names to scope; with no arguments, all canisters in mops.toml are promoted")
|
|
344
|
+
.argument("[canisters...]")
|
|
345
|
+
.addOption(new Option("--build-dir <dir>", "Directory to read built .most files from (default: [build].outputDir or .mops/.build)"))
|
|
346
|
+
.addOption(new Option("--dir <dir>", "Destination directory (default: [deployed].dir or deployed)"))
|
|
347
|
+
.action(async (canisters, options) => {
|
|
348
|
+
checkConfigFile(true);
|
|
349
|
+
await deployed(canisters.length ? canisters : undefined, options);
|
|
350
|
+
});
|
|
351
|
+
deployedCommand
|
|
352
|
+
.command("init [canisters...]")
|
|
353
|
+
.description("Pre-first-deploy bootstrap: create an empty-actor .most baseline in the deployed directory and wire [canisters.<name>.check-stable].path to it. Idempotent")
|
|
354
|
+
.addOption(new Option("--dir <dir>", "Destination directory (default: [deployed].dir or deployed)"))
|
|
355
|
+
.action(async (canisters, options) => {
|
|
356
|
+
checkConfigFile(true);
|
|
357
|
+
await deployedInit(canisters.length ? canisters : undefined, options);
|
|
358
|
+
});
|
|
359
|
+
program.addCommand(deployedCommand);
|
|
317
360
|
// test
|
|
318
361
|
program
|
|
319
362
|
.command("test [filter]")
|
|
@@ -574,6 +617,31 @@ migrateCommand
|
|
|
574
617
|
await migrateFreeze(canister);
|
|
575
618
|
});
|
|
576
619
|
program.addCommand(migrateCommand);
|
|
620
|
+
// generate
|
|
621
|
+
const generateCommand = new Command("generate")
|
|
622
|
+
.description("Generate source-derived artifacts (Candid, ...)")
|
|
623
|
+
.showHelpAfterError();
|
|
624
|
+
generateCommand
|
|
625
|
+
.command("candid [canisters...]")
|
|
626
|
+
.description("(Re)generate the curated `.did` file for one or more canisters from current Motoko source. With no canister names, generates for all canisters in mops.toml. When [canisters.<name>].candid is set, overwrites that file; otherwise writes <name>.did next to `main` and sets the field.")
|
|
627
|
+
.addOption(new Option("--output, -o <output>", "Write the generated .did to <output> (single-canister only; does not touch mops.toml)"))
|
|
628
|
+
.addOption(new Option("--verbose", "Verbose console output"))
|
|
629
|
+
.addHelpText("after", "\nArguments after -- are forwarded directly to moc, e.g.:\n $ mops generate candid -- -Werror")
|
|
630
|
+
.allowUnknownOption(true)
|
|
631
|
+
.action(async (canisters, options) => {
|
|
632
|
+
checkConfigFile(true);
|
|
633
|
+
const { extraArgs, args } = parseExtraArgs(canisters);
|
|
634
|
+
await installAll({
|
|
635
|
+
silent: true,
|
|
636
|
+
lock: "ignore",
|
|
637
|
+
installFromLockFile: true,
|
|
638
|
+
});
|
|
639
|
+
await generateCandid(args.length ? args : undefined, {
|
|
640
|
+
...options,
|
|
641
|
+
extraArgs,
|
|
642
|
+
});
|
|
643
|
+
});
|
|
644
|
+
program.addCommand(generateCommand);
|
|
577
645
|
// self
|
|
578
646
|
const selfCommand = new Command("self").description("Mops CLI management");
|
|
579
647
|
selfCommand
|
|
@@ -623,6 +691,9 @@ program
|
|
|
623
691
|
.addOption(new Option("--verbose", "Verbose output"))
|
|
624
692
|
.addOption(new Option("--fix", "Apply fixes"))
|
|
625
693
|
.addOption(new Option("-r, --rules <directory...>", "Directories containing rules (can be used multiple times)"))
|
|
694
|
+
.addOption(new Option("--no-check-limit", "Lint the full migration chain, ignoring [migrations].check-limit"))
|
|
695
|
+
.addHelpText("after", "\nArguments after -- are forwarded directly to lintoko, e.g.:\n $ mops lint -- --severity warning")
|
|
696
|
+
.addHelpText("after", enhancedMigrationHelp(true))
|
|
626
697
|
.allowUnknownOption(true)
|
|
627
698
|
.action(async (filter, options) => {
|
|
628
699
|
checkConfigFile(true);
|
|
@@ -630,6 +701,7 @@ program
|
|
|
630
701
|
await lint(filter, {
|
|
631
702
|
...options,
|
|
632
703
|
extraArgs,
|
|
704
|
+
noCheckLimit: options.checkLimit === false,
|
|
633
705
|
});
|
|
634
706
|
});
|
|
635
707
|
// docs
|
package/dist/commands/build.d.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
|
+
import { Config } from "../types.js";
|
|
1
2
|
export interface BuildOptions {
|
|
2
3
|
outputDir: string;
|
|
3
4
|
verbose: boolean;
|
|
4
5
|
extraArgs: string[];
|
|
5
6
|
}
|
|
6
7
|
export declare const DEFAULT_BUILD_OUTPUT_DIR = ".mops/.build";
|
|
8
|
+
/**
|
|
9
|
+
* Resolve the build output directory: CLI override → `[build].outputDir`
|
|
10
|
+
* (project-root-relative, resolved via `resolveConfigPath`) → default.
|
|
11
|
+
*/
|
|
12
|
+
export declare function resolveBuildOutputDir(config: Config, override?: string): string;
|
|
7
13
|
export declare function build(canisterNames: string[] | undefined, options: Partial<BuildOptions>): Promise<void>;
|
package/dist/commands/build.js
CHANGED
|
@@ -6,22 +6,30 @@ 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 { filterCanisters, resolveCanisterConfigs,
|
|
10
|
-
import {
|
|
9
|
+
import { filterCanisters, resolveCanisterConfigs, } from "../helpers/resolve-canisters.js";
|
|
10
|
+
import { BUILD_MANAGED_FLAGS, prepareMocArgs } from "../helpers/moc-args.js";
|
|
11
11
|
import { getWasmBindings } from "../wasm.js";
|
|
12
|
-
import {
|
|
13
|
-
import { sourcesArgs } from "./sources.js";
|
|
12
|
+
import { readConfig, resolveConfigPath } from "../mops.js";
|
|
14
13
|
import { toolchain } from "./toolchain/index.js";
|
|
15
14
|
export const DEFAULT_BUILD_OUTPUT_DIR = ".mops/.build";
|
|
15
|
+
/**
|
|
16
|
+
* Resolve the build output directory: CLI override → `[build].outputDir`
|
|
17
|
+
* (project-root-relative, resolved via `resolveConfigPath`) → default.
|
|
18
|
+
*/
|
|
19
|
+
export function resolveBuildOutputDir(config, override) {
|
|
20
|
+
if (override) {
|
|
21
|
+
return override;
|
|
22
|
+
}
|
|
23
|
+
return config.build?.outputDir
|
|
24
|
+
? resolveConfigPath(config.build.outputDir)
|
|
25
|
+
: DEFAULT_BUILD_OUTPUT_DIR;
|
|
26
|
+
}
|
|
16
27
|
export async function build(canisterNames, options) {
|
|
17
28
|
if (canisterNames?.length === 0) {
|
|
18
29
|
cliError("No canisters specified to build");
|
|
19
30
|
}
|
|
20
31
|
let config = readConfig();
|
|
21
|
-
let
|
|
22
|
-
? resolveConfigPath(config.build.outputDir)
|
|
23
|
-
: undefined;
|
|
24
|
-
let outputDir = options.outputDir ?? configOutputDir ?? DEFAULT_BUILD_OUTPUT_DIR;
|
|
32
|
+
let outputDir = resolveBuildOutputDir(config, options.outputDir);
|
|
25
33
|
let mocPath = await toolchain.bin("moc", { fallback: true });
|
|
26
34
|
let canisters = resolveCanisterConfigs(config);
|
|
27
35
|
if (!Object.keys(canisters).length) {
|
|
@@ -33,11 +41,6 @@ export async function build(canisterNames, options) {
|
|
|
33
41
|
const filteredCanisters = filterCanisters(canisters, canisterNames);
|
|
34
42
|
for (let [canisterName, canister] of Object.entries(filteredCanisters)) {
|
|
35
43
|
console.log(chalk.blue("build canister"), chalk.bold(canisterName));
|
|
36
|
-
let motokoPath = canister.main;
|
|
37
|
-
if (!motokoPath) {
|
|
38
|
-
cliError(`No main file is specified for canister ${canisterName}`);
|
|
39
|
-
}
|
|
40
|
-
motokoPath = resolveConfigPath(motokoPath);
|
|
41
44
|
const wasmPath = join(outputDir, `${canisterName}.wasm`);
|
|
42
45
|
const mostPath = join(outputDir, `${canisterName}.most`);
|
|
43
46
|
// per-canister lock to prevent parallel builds of the same canister from clobbering output files
|
|
@@ -63,7 +66,13 @@ export async function build(canisterNames, options) {
|
|
|
63
66
|
catch { }
|
|
64
67
|
};
|
|
65
68
|
process.on("exit", exitCleanup);
|
|
66
|
-
const
|
|
69
|
+
const prepared = await prepareMocArgs(config, canister, canisterName, {
|
|
70
|
+
mode: "build",
|
|
71
|
+
managedFlags: BUILD_MANAGED_FLAGS,
|
|
72
|
+
commandName: "mops build",
|
|
73
|
+
verbose: options.verbose,
|
|
74
|
+
extraArgs: options.extraArgs,
|
|
75
|
+
});
|
|
67
76
|
try {
|
|
68
77
|
let args = [
|
|
69
78
|
"-c",
|
|
@@ -71,12 +80,9 @@ export async function build(canisterNames, options) {
|
|
|
71
80
|
"--stable-types",
|
|
72
81
|
"-o",
|
|
73
82
|
wasmPath,
|
|
74
|
-
motokoPath,
|
|
75
|
-
...
|
|
76
|
-
...getGlobalMocArgs(config),
|
|
77
|
-
...migration.migrationArgs,
|
|
83
|
+
prepared.motokoPath,
|
|
84
|
+
...prepared.args,
|
|
78
85
|
];
|
|
79
|
-
args.push(...collectExtraArgs(config, canister, canisterName, options.extraArgs));
|
|
80
86
|
const isPublicCandid = true; // always true for now to reduce corner cases
|
|
81
87
|
const candidVisibility = isPublicCandid ? "icp:public" : "icp:private";
|
|
82
88
|
if (isPublicCandid) {
|
|
@@ -151,7 +157,7 @@ export async function build(canisterNames, options) {
|
|
|
151
157
|
}
|
|
152
158
|
}
|
|
153
159
|
finally {
|
|
154
|
-
await
|
|
160
|
+
await prepared.cleanup();
|
|
155
161
|
process.removeListener("exit", exitCleanup);
|
|
156
162
|
try {
|
|
157
163
|
await release?.();
|
|
@@ -161,35 +167,3 @@ export async function build(canisterNames, options) {
|
|
|
161
167
|
}
|
|
162
168
|
console.log(chalk.green(`\n✓ Built ${Object.keys(filteredCanisters).length} canister${Object.keys(filteredCanisters).length === 1 ? "" : "s"} successfully`));
|
|
163
169
|
}
|
|
164
|
-
const managedFlags = {
|
|
165
|
-
"-o": "use [build].outputDir in mops.toml or --output flag instead",
|
|
166
|
-
"-c": "this flag is always set by mops build",
|
|
167
|
-
"--idl": "this flag is always set by mops build",
|
|
168
|
-
"--stable-types": "this flag is always set by mops build",
|
|
169
|
-
"--public-metadata": "this flag is managed by mops build",
|
|
170
|
-
};
|
|
171
|
-
function collectExtraArgs(config, canister, canisterName, extraArgs) {
|
|
172
|
-
const args = [];
|
|
173
|
-
if (config.build?.args) {
|
|
174
|
-
if (typeof config.build.args === "string") {
|
|
175
|
-
cliError(`[build] config 'args' should be an array of strings in mops.toml config file`);
|
|
176
|
-
}
|
|
177
|
-
args.push(...config.build.args);
|
|
178
|
-
}
|
|
179
|
-
if (canister.args) {
|
|
180
|
-
validateCanisterArgs(canister, canisterName, config);
|
|
181
|
-
args.push(...canister.args);
|
|
182
|
-
}
|
|
183
|
-
if (extraArgs) {
|
|
184
|
-
args.push(...extraArgs);
|
|
185
|
-
}
|
|
186
|
-
const warned = new Set();
|
|
187
|
-
for (const arg of args) {
|
|
188
|
-
const hint = managedFlags[arg];
|
|
189
|
-
if (hint && !warned.has(arg)) {
|
|
190
|
-
warned.add(arg);
|
|
191
|
-
console.warn(chalk.yellow(`Warning: '${arg}' in args for canister ${canisterName} may conflict with mops build — ${hint}`));
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
return args;
|
|
195
|
-
}
|
|
@@ -2,6 +2,8 @@ import { CanisterConfig } from "../types.js";
|
|
|
2
2
|
export interface CheckStableOptions {
|
|
3
3
|
verbose: boolean;
|
|
4
4
|
extraArgs: string[];
|
|
5
|
+
/** Commander `--no-check-limit`: false ignores [migrations].check-limit. */
|
|
6
|
+
checkLimit: boolean;
|
|
5
7
|
}
|
|
6
8
|
export declare function resolveStablePath(canister: CanisterConfig, canisterName: string, options?: {
|
|
7
9
|
required?: boolean;
|
|
@@ -52,7 +52,7 @@ export async function checkStable(args, options = {}) {
|
|
|
52
52
|
cliError(`No main file specified for canister '${name}' in mops.toml`);
|
|
53
53
|
}
|
|
54
54
|
validateCanisterArgs(canister, name, config);
|
|
55
|
-
const migration = await prepareMigrationArgs(canister.migrations, name, "check", options.verbose);
|
|
55
|
+
const migration = await prepareMigrationArgs(canister.migrations, name, "check", options.verbose, options.checkLimit === false);
|
|
56
56
|
try {
|
|
57
57
|
await runStableCheck({
|
|
58
58
|
oldFile,
|
|
@@ -85,7 +85,7 @@ export async function checkStable(args, options = {}) {
|
|
|
85
85
|
if (!stablePath) {
|
|
86
86
|
continue;
|
|
87
87
|
}
|
|
88
|
-
const migration = await prepareMigrationArgs(canister.migrations, name, "check", options.verbose);
|
|
88
|
+
const migration = await prepareMigrationArgs(canister.migrations, name, "check", options.verbose, options.checkLimit === false);
|
|
89
89
|
try {
|
|
90
90
|
await runStableCheck({
|
|
91
91
|
oldFile: stablePath,
|
package/dist/commands/check.d.ts
CHANGED
|
@@ -2,5 +2,7 @@ export interface CheckOptions {
|
|
|
2
2
|
verbose: boolean;
|
|
3
3
|
fix: boolean;
|
|
4
4
|
extraArgs: string[];
|
|
5
|
+
/** Commander `--no-check-limit`: false ignores [migrations].check-limit. */
|
|
6
|
+
checkLimit: boolean;
|
|
5
7
|
}
|
|
6
8
|
export declare function check(args: string[], options?: Partial<CheckOptions>): Promise<void>;
|
package/dist/commands/check.js
CHANGED
|
@@ -83,6 +83,7 @@ async function checkImpl(args, options = {}) {
|
|
|
83
83
|
fix: options.fix,
|
|
84
84
|
rules: lintRules,
|
|
85
85
|
files: lintFiles,
|
|
86
|
+
noCheckLimit: options.checkLimit === false,
|
|
86
87
|
});
|
|
87
88
|
}
|
|
88
89
|
}
|
|
@@ -97,7 +98,7 @@ async function checkCanisters(config, canisters, options) {
|
|
|
97
98
|
}
|
|
98
99
|
validateCanisterArgs(canister, canisterName, config);
|
|
99
100
|
const motokoPath = resolveConfigPath(canister.main);
|
|
100
|
-
const migration = await prepareMigrationArgs(canister.migrations, canisterName, "check", options.verbose);
|
|
101
|
+
const migration = await prepareMigrationArgs(canister.migrations, canisterName, "check", options.verbose, options.checkLimit === false);
|
|
101
102
|
try {
|
|
102
103
|
const mocArgs = [
|
|
103
104
|
"--check",
|
|
@@ -171,26 +172,24 @@ async function checkFiles(config, files, options) {
|
|
|
171
172
|
const fixResult = await autofixMotoko(mocPath, files, mocArgs);
|
|
172
173
|
logAutofixResult(fixResult, options.verbose);
|
|
173
174
|
}
|
|
174
|
-
// Check all files in a single moc invocation so shared transitive imports
|
|
175
|
-
// are chased and type-checked once, not re-checked for every file.
|
|
176
|
-
const args = [...files, ...mocArgs];
|
|
177
|
-
if (options.verbose) {
|
|
178
|
-
console.log(chalk.blue("check"), chalk.gray("Running moc:"));
|
|
179
|
-
console.log(chalk.gray(mocPath, JSON.stringify(args)));
|
|
180
|
-
}
|
|
181
|
-
try {
|
|
182
|
-
const result = await execa(mocPath, args, {
|
|
183
|
-
stdio: "inherit",
|
|
184
|
-
reject: false,
|
|
185
|
-
});
|
|
186
|
-
if (result.exitCode !== 0) {
|
|
187
|
-
cliError(`✗ Check failed (exit code: ${result.exitCode})`);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
catch (err) {
|
|
191
|
-
cliError(`Error while checking files${err?.message ? `\n${err.message}` : ""}`);
|
|
192
|
-
}
|
|
193
175
|
for (const file of files) {
|
|
194
|
-
|
|
176
|
+
try {
|
|
177
|
+
const args = [file, ...mocArgs];
|
|
178
|
+
if (options.verbose) {
|
|
179
|
+
console.log(chalk.blue("check"), chalk.gray("Running moc:"));
|
|
180
|
+
console.log(chalk.gray(mocPath, JSON.stringify(args)));
|
|
181
|
+
}
|
|
182
|
+
const result = await execa(mocPath, args, {
|
|
183
|
+
stdio: "inherit",
|
|
184
|
+
reject: false,
|
|
185
|
+
});
|
|
186
|
+
if (result.exitCode !== 0) {
|
|
187
|
+
cliError(`✗ Check failed for file ${file} (exit code: ${result.exitCode})`);
|
|
188
|
+
}
|
|
189
|
+
console.log(chalk.green(`✓ ${file}`));
|
|
190
|
+
}
|
|
191
|
+
catch (err) {
|
|
192
|
+
cliError(`Error while checking ${file}${err?.message ? `\n${err.message}` : ""}`);
|
|
193
|
+
}
|
|
195
194
|
}
|
|
196
195
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const DEFAULT_DEPLOYED_DIR = "deployed";
|
|
2
|
+
export interface DeployedOptions {
|
|
3
|
+
buildDir?: string;
|
|
4
|
+
dir?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface DeployedInitOptions {
|
|
7
|
+
dir?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function deployed(canisterNames: string[] | undefined, options?: DeployedOptions): Promise<void>;
|
|
10
|
+
export declare function deployedInit(canisterNames: string[] | undefined, options?: DeployedInitOptions): Promise<void>;
|