ic-mops 2.14.1 → 2.15.1
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 +22 -0
- package/bundle/cli.tgz +0 -0
- package/cli.ts +149 -4
- package/commands/bench-replica.ts +2 -1
- package/commands/bench.ts +32 -9
- 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 +76 -4
- package/dist/commands/bench-replica.js +3 -1
- package/dist/commands/bench.js +21 -4
- 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 +71 -51
- 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/helpers/pocket-ic-client.d.ts +2 -2
- package/dist/helpers/pocket-ic-client.js +8 -4
- 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-fix.test.js +14 -2
- 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 +96 -66
- package/helpers/migrations.ts +8 -3
- package/helpers/moc-args.ts +123 -0
- package/helpers/pocket-ic-client.ts +11 -5
- 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-fix.test.ts +26 -2
- 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,162 @@
|
|
|
1
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { copyFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import { cliError } from "../error.js";
|
|
6
|
+
import {
|
|
7
|
+
filterCanisters,
|
|
8
|
+
resolveCanisterConfigs,
|
|
9
|
+
} from "../helpers/resolve-canisters.js";
|
|
10
|
+
import {
|
|
11
|
+
getRootDir,
|
|
12
|
+
readConfig,
|
|
13
|
+
resolveConfigPath,
|
|
14
|
+
writeConfig,
|
|
15
|
+
} from "../mops.js";
|
|
16
|
+
import { CanisterConfig, Config } from "../types.js";
|
|
17
|
+
import { resolveBuildOutputDir } from "./build.js";
|
|
18
|
+
|
|
19
|
+
export const DEFAULT_DEPLOYED_DIR = "deployed";
|
|
20
|
+
|
|
21
|
+
const EMPTY_ACTOR_MOST = "// Version: 1.0.0\nactor { };\n";
|
|
22
|
+
|
|
23
|
+
export interface DeployedOptions {
|
|
24
|
+
buildDir?: string;
|
|
25
|
+
dir?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface DeployedInitOptions {
|
|
29
|
+
dir?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// `[deployed].dir` and CLI overrides are tracked in two forms:
|
|
33
|
+
// - `config`: relative to mops.toml, used for display + writing into mops.toml
|
|
34
|
+
// - `resolved`: relative to cwd, used for fs operations
|
|
35
|
+
function resolveDeployedDir(
|
|
36
|
+
config: Config,
|
|
37
|
+
override: string | undefined,
|
|
38
|
+
): { config: string; resolved: string } {
|
|
39
|
+
if (override) {
|
|
40
|
+
const configRel =
|
|
41
|
+
path.relative(getRootDir(), path.resolve(override)) || ".";
|
|
42
|
+
return { config: configRel, resolved: override };
|
|
43
|
+
}
|
|
44
|
+
const configured = config.deployed?.dir ?? DEFAULT_DEPLOYED_DIR;
|
|
45
|
+
return { config: configured, resolved: resolveConfigPath(configured) };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function selectCanisters(
|
|
49
|
+
canisterNames: string[] | undefined,
|
|
50
|
+
config: Config,
|
|
51
|
+
): Record<string, CanisterConfig> {
|
|
52
|
+
if (canisterNames?.length === 0) {
|
|
53
|
+
cliError("No canisters specified");
|
|
54
|
+
}
|
|
55
|
+
const canisters = resolveCanisterConfigs(config);
|
|
56
|
+
if (!Object.keys(canisters).length) {
|
|
57
|
+
cliError(`No Motoko canisters found in mops.toml configuration`);
|
|
58
|
+
}
|
|
59
|
+
return filterCanisters(canisters, canisterNames);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function warnIfStablePathDiverges(
|
|
63
|
+
name: string,
|
|
64
|
+
stablePath: string,
|
|
65
|
+
destMostRel: string,
|
|
66
|
+
): void {
|
|
67
|
+
if (
|
|
68
|
+
path.resolve(resolveConfigPath(stablePath)) ===
|
|
69
|
+
path.resolve(resolveConfigPath(destMostRel))
|
|
70
|
+
) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
console.warn(
|
|
74
|
+
chalk.yellow(
|
|
75
|
+
`WARN: [canisters.${name}.check-stable].path = "${stablePath}" ` +
|
|
76
|
+
`does not match where \`mops deployed\` writes ("${destMostRel}"). ` +
|
|
77
|
+
`\`mops check-stable\` won't see updates from \`mops deployed\`.`,
|
|
78
|
+
),
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export async function deployed(
|
|
83
|
+
canisterNames: string[] | undefined,
|
|
84
|
+
options: DeployedOptions = {},
|
|
85
|
+
): Promise<void> {
|
|
86
|
+
const config = readConfig();
|
|
87
|
+
const filtered = selectCanisters(canisterNames, config);
|
|
88
|
+
|
|
89
|
+
const buildDir = resolveBuildOutputDir(config, options.buildDir);
|
|
90
|
+
const deployedDir = resolveDeployedDir(config, options.dir);
|
|
91
|
+
|
|
92
|
+
mkdirSync(deployedDir.resolved, { recursive: true });
|
|
93
|
+
|
|
94
|
+
for (const [name, canister] of Object.entries(filtered)) {
|
|
95
|
+
const sourceMost = path.join(buildDir, `${name}.most`);
|
|
96
|
+
const destMostRel = path.join(deployedDir.config, `${name}.most`);
|
|
97
|
+
const destMost = path.join(deployedDir.resolved, `${name}.most`);
|
|
98
|
+
|
|
99
|
+
if (!existsSync(sourceMost)) {
|
|
100
|
+
cliError(
|
|
101
|
+
`No built .most at ${sourceMost}. Run \`mops build ${name}\` first.`,
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
await copyFile(sourceMost, destMost);
|
|
106
|
+
console.log(chalk.green(`✓ ${sourceMost} → ${destMost}`));
|
|
107
|
+
|
|
108
|
+
const stablePath = canister["check-stable"]?.path;
|
|
109
|
+
if (stablePath) {
|
|
110
|
+
warnIfStablePathDiverges(name, stablePath, destMostRel);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export async function deployedInit(
|
|
116
|
+
canisterNames: string[] | undefined,
|
|
117
|
+
options: DeployedInitOptions = {},
|
|
118
|
+
): Promise<void> {
|
|
119
|
+
const config = readConfig();
|
|
120
|
+
const filtered = selectCanisters(canisterNames, config);
|
|
121
|
+
|
|
122
|
+
const deployedDir = resolveDeployedDir(config, options.dir);
|
|
123
|
+
|
|
124
|
+
mkdirSync(deployedDir.resolved, { recursive: true });
|
|
125
|
+
|
|
126
|
+
let configChanged = false;
|
|
127
|
+
|
|
128
|
+
for (const [name, canister] of Object.entries(filtered)) {
|
|
129
|
+
const destMostRel = path.join(deployedDir.config, `${name}.most`);
|
|
130
|
+
const destMost = path.join(deployedDir.resolved, `${name}.most`);
|
|
131
|
+
|
|
132
|
+
if (!existsSync(destMost)) {
|
|
133
|
+
await writeFile(destMost, EMPTY_ACTOR_MOST);
|
|
134
|
+
console.log(chalk.green(`✓ Created baseline ${destMost}`));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const stablePath = canister["check-stable"]?.path;
|
|
138
|
+
if (!stablePath) {
|
|
139
|
+
const entry = config.canisters?.[name];
|
|
140
|
+
if (typeof entry === "string") {
|
|
141
|
+
config.canisters![name] = {
|
|
142
|
+
main: entry,
|
|
143
|
+
"check-stable": { path: destMostRel },
|
|
144
|
+
};
|
|
145
|
+
} else if (entry) {
|
|
146
|
+
entry["check-stable"] = { path: destMostRel };
|
|
147
|
+
}
|
|
148
|
+
configChanged = true;
|
|
149
|
+
console.log(
|
|
150
|
+
chalk.green(
|
|
151
|
+
`✓ Set [canisters.${name}.check-stable].path = "${destMostRel}"`,
|
|
152
|
+
),
|
|
153
|
+
);
|
|
154
|
+
} else {
|
|
155
|
+
warnIfStablePathDiverges(name, stablePath, destMostRel);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (configChanged) {
|
|
160
|
+
writeConfig(config);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -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]")
|
|
@@ -350,7 +393,7 @@ program
|
|
|
350
393
|
.addOption(new Option("--save", "Save benchmark results to .bench/<filename>.json"))
|
|
351
394
|
.addOption(new Option("--compare", "Run benchmark and compare results with .bench/<filename>.json"))
|
|
352
395
|
// .addOption(new Option('--force-gc', 'Force GC'))
|
|
353
|
-
.addOption(new Option("--verbose", "
|
|
396
|
+
.addOption(new Option("--verbose", "Print the benchmark pipeline (compiler, replica, GC, optimization) and stream compiler/replica output, including dfx optimization warnings"))
|
|
354
397
|
.action(async (filter, options) => {
|
|
355
398
|
checkConfigFile(true);
|
|
356
399
|
await installAll({
|
|
@@ -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
|
|
@@ -55,7 +55,9 @@ export class BenchReplica {
|
|
|
55
55
|
}
|
|
56
56
|
async deploy(name, wasm, cwd = process.cwd()) {
|
|
57
57
|
if (this.type === "dfx" || this.type === "dfx-pocket-ic") {
|
|
58
|
-
await execaCommand(`dfx deploy ${name} --mode reinstall --yes --identity anonymous`,
|
|
58
|
+
await execaCommand(`dfx deploy ${name} --mode reinstall --yes --identity anonymous`,
|
|
59
|
+
// `inherit` so dfx output is streamed under --verbose (incl. its `Failed to optimize` warning)
|
|
60
|
+
{ cwd, stdio: this.verbose ? "inherit" : ["pipe", "ignore", "pipe"] });
|
|
59
61
|
let canisterId = execSync(`dfx canister id ${name}`, { cwd })
|
|
60
62
|
.toString()
|
|
61
63
|
.trim();
|
package/dist/commands/bench.js
CHANGED
|
@@ -61,7 +61,19 @@ export async function bench(filter = "", optionsArg = {}) {
|
|
|
61
61
|
options.replicaVersion = config.toolchain?.["pocket-ic"] || "";
|
|
62
62
|
}
|
|
63
63
|
warnIfDfxReplica(replicaType, optionsArg.replica === "dfx");
|
|
64
|
-
options.verbose
|
|
64
|
+
if (options.verbose) {
|
|
65
|
+
// `dfx` post-optimizes the wasm on deploy (`optimize: "cycles"`, via ic-wasm);
|
|
66
|
+
// `pocket-ic` runs the raw moc output. This changes instruction counts, so surface it.
|
|
67
|
+
let optimize = replicaType === "dfx" || replicaType === "dfx-pocket-ic"
|
|
68
|
+
? 'dfx `optimize: "cycles"` (ic-wasm) on deploy'
|
|
69
|
+
: "none (raw moc output)";
|
|
70
|
+
console.log(chalk.gray("Benchmark pipeline:"));
|
|
71
|
+
console.log(chalk.gray(` compiler: moc ${options.compilerVersion}`));
|
|
72
|
+
console.log(chalk.gray(` replica: ${replicaType}${options.replicaVersion ? ` ${options.replicaVersion}` : ""}`));
|
|
73
|
+
console.log(chalk.gray(` gc: ${options.gc}${options.forceGc ? " (forced)" : ""}`));
|
|
74
|
+
console.log(chalk.gray(` profile: ${options.profile}`));
|
|
75
|
+
console.log(chalk.gray(` optimize: ${optimize}`));
|
|
76
|
+
}
|
|
65
77
|
let replica = new BenchReplica(replicaType, options.verbose);
|
|
66
78
|
let rootDir = getRootDir();
|
|
67
79
|
let globStr = "**/bench?(mark)/**/*.bench.mo";
|
|
@@ -207,10 +219,15 @@ async function deployBenchFile(file, options, replica, globalMocArgs) {
|
|
|
207
219
|
// build canister
|
|
208
220
|
let mocPath = getMocPath();
|
|
209
221
|
let mocArgs = getMocArgs(options);
|
|
210
|
-
|
|
211
|
-
|
|
222
|
+
let buildCmd = `${mocPath} -c --idl canister.mo ${globalMocArgs.join(" ")} ${mocArgs} ${(await sources({ cwd: tempDir })).join(" ")}`;
|
|
223
|
+
if (options.verbose) {
|
|
224
|
+
console.log(chalk.gray(`[${canisterName}] ${buildCmd}`));
|
|
225
|
+
console.time(`build ${canisterName}`);
|
|
226
|
+
}
|
|
227
|
+
await execaCommand(buildCmd, {
|
|
212
228
|
cwd: tempDir,
|
|
213
|
-
|
|
229
|
+
// `inherit` so the compiler output (warnings/errors) is streamed under --verbose
|
|
230
|
+
stdio: options.verbose ? "inherit" : ["pipe", "ignore", "pipe"],
|
|
214
231
|
});
|
|
215
232
|
options.verbose && console.timeEnd(`build ${canisterName}`);
|
|
216
233
|
// deploy canister
|
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>;
|