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
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>;
|
|
@@ -0,0 +1,97 @@
|
|
|
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 { filterCanisters, resolveCanisterConfigs, } from "../helpers/resolve-canisters.js";
|
|
7
|
+
import { getRootDir, readConfig, resolveConfigPath, writeConfig, } from "../mops.js";
|
|
8
|
+
import { resolveBuildOutputDir } from "./build.js";
|
|
9
|
+
export const DEFAULT_DEPLOYED_DIR = "deployed";
|
|
10
|
+
const EMPTY_ACTOR_MOST = "// Version: 1.0.0\nactor { };\n";
|
|
11
|
+
// `[deployed].dir` and CLI overrides are tracked in two forms:
|
|
12
|
+
// - `config`: relative to mops.toml, used for display + writing into mops.toml
|
|
13
|
+
// - `resolved`: relative to cwd, used for fs operations
|
|
14
|
+
function resolveDeployedDir(config, override) {
|
|
15
|
+
if (override) {
|
|
16
|
+
const configRel = path.relative(getRootDir(), path.resolve(override)) || ".";
|
|
17
|
+
return { config: configRel, resolved: override };
|
|
18
|
+
}
|
|
19
|
+
const configured = config.deployed?.dir ?? DEFAULT_DEPLOYED_DIR;
|
|
20
|
+
return { config: configured, resolved: resolveConfigPath(configured) };
|
|
21
|
+
}
|
|
22
|
+
function selectCanisters(canisterNames, config) {
|
|
23
|
+
if (canisterNames?.length === 0) {
|
|
24
|
+
cliError("No canisters specified");
|
|
25
|
+
}
|
|
26
|
+
const canisters = resolveCanisterConfigs(config);
|
|
27
|
+
if (!Object.keys(canisters).length) {
|
|
28
|
+
cliError(`No Motoko canisters found in mops.toml configuration`);
|
|
29
|
+
}
|
|
30
|
+
return filterCanisters(canisters, canisterNames);
|
|
31
|
+
}
|
|
32
|
+
function warnIfStablePathDiverges(name, stablePath, destMostRel) {
|
|
33
|
+
if (path.resolve(resolveConfigPath(stablePath)) ===
|
|
34
|
+
path.resolve(resolveConfigPath(destMostRel))) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
console.warn(chalk.yellow(`WARN: [canisters.${name}.check-stable].path = "${stablePath}" ` +
|
|
38
|
+
`does not match where \`mops deployed\` writes ("${destMostRel}"). ` +
|
|
39
|
+
`\`mops check-stable\` won't see updates from \`mops deployed\`.`));
|
|
40
|
+
}
|
|
41
|
+
export async function deployed(canisterNames, options = {}) {
|
|
42
|
+
const config = readConfig();
|
|
43
|
+
const filtered = selectCanisters(canisterNames, config);
|
|
44
|
+
const buildDir = resolveBuildOutputDir(config, options.buildDir);
|
|
45
|
+
const deployedDir = resolveDeployedDir(config, options.dir);
|
|
46
|
+
mkdirSync(deployedDir.resolved, { recursive: true });
|
|
47
|
+
for (const [name, canister] of Object.entries(filtered)) {
|
|
48
|
+
const sourceMost = path.join(buildDir, `${name}.most`);
|
|
49
|
+
const destMostRel = path.join(deployedDir.config, `${name}.most`);
|
|
50
|
+
const destMost = path.join(deployedDir.resolved, `${name}.most`);
|
|
51
|
+
if (!existsSync(sourceMost)) {
|
|
52
|
+
cliError(`No built .most at ${sourceMost}. Run \`mops build ${name}\` first.`);
|
|
53
|
+
}
|
|
54
|
+
await copyFile(sourceMost, destMost);
|
|
55
|
+
console.log(chalk.green(`✓ ${sourceMost} → ${destMost}`));
|
|
56
|
+
const stablePath = canister["check-stable"]?.path;
|
|
57
|
+
if (stablePath) {
|
|
58
|
+
warnIfStablePathDiverges(name, stablePath, destMostRel);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
export async function deployedInit(canisterNames, options = {}) {
|
|
63
|
+
const config = readConfig();
|
|
64
|
+
const filtered = selectCanisters(canisterNames, config);
|
|
65
|
+
const deployedDir = resolveDeployedDir(config, options.dir);
|
|
66
|
+
mkdirSync(deployedDir.resolved, { recursive: true });
|
|
67
|
+
let configChanged = false;
|
|
68
|
+
for (const [name, canister] of Object.entries(filtered)) {
|
|
69
|
+
const destMostRel = path.join(deployedDir.config, `${name}.most`);
|
|
70
|
+
const destMost = path.join(deployedDir.resolved, `${name}.most`);
|
|
71
|
+
if (!existsSync(destMost)) {
|
|
72
|
+
await writeFile(destMost, EMPTY_ACTOR_MOST);
|
|
73
|
+
console.log(chalk.green(`✓ Created baseline ${destMost}`));
|
|
74
|
+
}
|
|
75
|
+
const stablePath = canister["check-stable"]?.path;
|
|
76
|
+
if (!stablePath) {
|
|
77
|
+
const entry = config.canisters?.[name];
|
|
78
|
+
if (typeof entry === "string") {
|
|
79
|
+
config.canisters[name] = {
|
|
80
|
+
main: entry,
|
|
81
|
+
"check-stable": { path: destMostRel },
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
else if (entry) {
|
|
85
|
+
entry["check-stable"] = { path: destMostRel };
|
|
86
|
+
}
|
|
87
|
+
configChanged = true;
|
|
88
|
+
console.log(chalk.green(`✓ Set [canisters.${name}.check-stable].path = "${destMostRel}"`));
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
warnIfStablePathDiverges(name, stablePath, destMostRel);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (configChanged) {
|
|
95
|
+
writeConfig(config);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
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 { filterCanisters, resolveCanisterConfigs, } from "../helpers/resolve-canisters.js";
|
|
7
|
+
import { GENERATE_CANDID_MANAGED_FLAGS, prepareMocArgs, } from "../helpers/moc-args.js";
|
|
8
|
+
import { getRootDir, readConfig, resolveConfigPath, writeConfig, } from "../mops.js";
|
|
9
|
+
import { toolchain } from "./toolchain/index.js";
|
|
10
|
+
export async function generateCandid(canisterNames, options) {
|
|
11
|
+
if (canisterNames?.length === 0) {
|
|
12
|
+
cliError("No canisters specified");
|
|
13
|
+
}
|
|
14
|
+
const config = readConfig();
|
|
15
|
+
const canisters = resolveCanisterConfigs(config);
|
|
16
|
+
if (!Object.keys(canisters).length) {
|
|
17
|
+
cliError("No Motoko canisters found in mops.toml configuration");
|
|
18
|
+
}
|
|
19
|
+
const filtered = filterCanisters(canisters, canisterNames);
|
|
20
|
+
const filteredEntries = Object.entries(filtered);
|
|
21
|
+
if (options.output && filteredEntries.length > 1) {
|
|
22
|
+
cliError("--output / -o is only supported when generating for a single canister");
|
|
23
|
+
}
|
|
24
|
+
const mocPath = await toolchain.bin("moc", { fallback: true });
|
|
25
|
+
const rootDir = getRootDir();
|
|
26
|
+
let configChanged = false;
|
|
27
|
+
for (const [canisterName, canister] of filteredEntries) {
|
|
28
|
+
const dest = resolveDestination(canisterName, canister, canisters, options.output, rootDir);
|
|
29
|
+
console.log(chalk.blue("generate candid"), chalk.bold(canisterName), chalk.gray(`→ ${dest.fsPath}`));
|
|
30
|
+
const prepared = await prepareMocArgs(config, canister, canisterName, {
|
|
31
|
+
mode: "build",
|
|
32
|
+
managedFlags: GENERATE_CANDID_MANAGED_FLAGS,
|
|
33
|
+
commandName: "mops generate candid",
|
|
34
|
+
verbose: options.verbose,
|
|
35
|
+
extraArgs: options.extraArgs,
|
|
36
|
+
});
|
|
37
|
+
try {
|
|
38
|
+
await mkdir(path.dirname(dest.fsPath), { recursive: true });
|
|
39
|
+
const args = [
|
|
40
|
+
"--idl",
|
|
41
|
+
"-o",
|
|
42
|
+
dest.fsPath,
|
|
43
|
+
prepared.motokoPath,
|
|
44
|
+
...prepared.args,
|
|
45
|
+
];
|
|
46
|
+
if (options.verbose) {
|
|
47
|
+
console.log(chalk.gray(mocPath, JSON.stringify(args)));
|
|
48
|
+
}
|
|
49
|
+
const result = await execa(mocPath, args, {
|
|
50
|
+
stdio: options.verbose ? "inherit" : "pipe",
|
|
51
|
+
reject: false,
|
|
52
|
+
});
|
|
53
|
+
if (result.exitCode !== 0) {
|
|
54
|
+
if (!options.verbose) {
|
|
55
|
+
if (result.stderr) {
|
|
56
|
+
console.error(chalk.red(result.stderr));
|
|
57
|
+
}
|
|
58
|
+
if (result.stdout?.trim()) {
|
|
59
|
+
console.error(chalk.yellow("Output:"));
|
|
60
|
+
console.error(result.stdout);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
cliError(`Failed to generate Candid for canister ${canisterName} (exit code: ${result.exitCode})`);
|
|
64
|
+
}
|
|
65
|
+
if (dest.configPath !== null) {
|
|
66
|
+
const c = (config.canisters ??= {});
|
|
67
|
+
const existing = c[canisterName];
|
|
68
|
+
const obj = typeof existing === "string"
|
|
69
|
+
? { main: existing }
|
|
70
|
+
: { ...(existing ?? {}) };
|
|
71
|
+
obj.candid = dest.configPath;
|
|
72
|
+
c[canisterName] = obj;
|
|
73
|
+
configChanged = true;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
finally {
|
|
77
|
+
await prepared.cleanup();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (configChanged) {
|
|
81
|
+
writeConfig(config);
|
|
82
|
+
}
|
|
83
|
+
console.log(chalk.green(`\n✓ Generated Candid for ${filteredEntries.length} canister${filteredEntries.length === 1 ? "" : "s"}`));
|
|
84
|
+
}
|
|
85
|
+
function resolveDestination(canisterName, canister, allCanisters, outputFlag, rootDir) {
|
|
86
|
+
if (!canister.main) {
|
|
87
|
+
cliError(`No main file is specified for canister ${canisterName}`);
|
|
88
|
+
}
|
|
89
|
+
let fsPath;
|
|
90
|
+
let configPath;
|
|
91
|
+
if (outputFlag) {
|
|
92
|
+
fsPath = outputFlag;
|
|
93
|
+
configPath = null;
|
|
94
|
+
const outAbs = path.resolve(fsPath);
|
|
95
|
+
for (const [otherName, other] of Object.entries(allCanisters)) {
|
|
96
|
+
if (otherName === canisterName || !other.candid) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (path.resolve(rootDir, other.candid) === outAbs) {
|
|
100
|
+
console.warn(chalk.yellow(`Warning: --output path collides with [canisters.${otherName}].candid (${other.candid}). Sharing a .did between canisters is almost always a mistake.`));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
else if (canister.candid) {
|
|
105
|
+
fsPath = resolveConfigPath(canister.candid);
|
|
106
|
+
configPath = null;
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
// Default: <dirname(main)>/<canisterName>.did, forward slashes for the toml value
|
|
110
|
+
const mainDir = path.dirname(canister.main).replace(/\\/g, "/");
|
|
111
|
+
const projectRel = mainDir === "." || mainDir === ""
|
|
112
|
+
? `${canisterName}.did`
|
|
113
|
+
: `${mainDir}/${canisterName}.did`;
|
|
114
|
+
fsPath = resolveConfigPath(projectRel);
|
|
115
|
+
configPath = projectRel;
|
|
116
|
+
}
|
|
117
|
+
const absPath = path.resolve(fsPath);
|
|
118
|
+
const dotMopsDir = path.resolve(rootDir, ".mops");
|
|
119
|
+
if (absPath === dotMopsDir || absPath.startsWith(dotMopsDir + path.sep)) {
|
|
120
|
+
cliError(`Refusing to write Candid file inside .mops/ (private build cache): ${fsPath}\n` +
|
|
121
|
+
"Choose a path outside .mops/ — it should be committable and readable by downstream tooling.");
|
|
122
|
+
}
|
|
123
|
+
return { fsPath, configPath };
|
|
124
|
+
}
|
package/dist/commands/lint.d.ts
CHANGED
|
@@ -6,5 +6,7 @@ export interface LintOptions {
|
|
|
6
6
|
rules?: string[];
|
|
7
7
|
files?: string[];
|
|
8
8
|
extraArgs: string[];
|
|
9
|
+
/** Commander `--no-check-limit`: lint the full migration chain. */
|
|
10
|
+
noCheckLimit?: boolean;
|
|
9
11
|
}
|
|
10
12
|
export declare function lint(filter: string | undefined, options: Partial<LintOptions>): Promise<void>;
|
package/dist/commands/lint.js
CHANGED
|
@@ -122,7 +122,7 @@ async function lintImpl(filter, options) {
|
|
|
122
122
|
? await toolchain.bin("lintoko")
|
|
123
123
|
: "lintoko";
|
|
124
124
|
const isExplicit = !!filter || !!(options.files && options.files.length > 0);
|
|
125
|
-
const trimmedMigrations = isExplicit
|
|
125
|
+
const trimmedMigrations = isExplicit || options.noCheckLimit
|
|
126
126
|
? new Set()
|
|
127
127
|
: getTrimmedMigrationFiles(config);
|
|
128
128
|
let filesToLint;
|