ic-mops 2.15.2 → 2.16.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 +11 -0
- package/bundle/cli.tgz +0 -0
- package/check-requirements.ts +53 -31
- package/cli.ts +18 -3
- package/commands/bench.ts +46 -11
- package/commands/build.ts +3 -2
- package/commands/check-stable.ts +5 -3
- package/commands/check.ts +5 -3
- package/commands/generate.ts +3 -2
- package/commands/test/test.ts +12 -4
- package/commands/test/utils.ts +6 -2
- package/commands/toolchain/index.ts +1 -3
- package/dist/check-requirements.js +31 -24
- package/dist/cli.js +5 -3
- package/dist/commands/bench.d.ts +2 -0
- package/dist/commands/bench.js +29 -6
- package/dist/commands/build.js +2 -2
- package/dist/commands/check-stable.js +3 -3
- package/dist/commands/check.js +3 -3
- package/dist/commands/generate.js +2 -2
- package/dist/commands/test/test.js +11 -4
- package/dist/commands/test/utils.d.ts +1 -1
- package/dist/commands/test/utils.js +2 -2
- package/dist/commands/toolchain/index.js +1 -3
- package/dist/error.d.ts +1 -0
- package/dist/error.js +4 -0
- package/dist/helpers/get-lintoko-version.d.ts +3 -0
- package/dist/helpers/get-lintoko-version.js +30 -0
- package/dist/mops.js +2 -2
- package/dist/package.json +1 -1
- package/dist/tests/bench.test.d.ts +1 -0
- package/dist/tests/bench.test.js +21 -0
- package/dist/tests/requirements.test.d.ts +1 -0
- package/dist/tests/requirements.test.js +46 -0
- package/dist/types.d.ts +1 -0
- package/error.ts +5 -0
- package/helpers/get-lintoko-version.ts +32 -0
- package/mops.ts +2 -2
- package/package.json +1 -1
- package/tests/bench/bench/sanity.bench.mo +27 -0
- package/tests/bench/mops.toml +6 -0
- package/tests/bench.test.ts +22 -0
- package/tests/lint-extra-example-rules/lint/migration-self-contained/migration-self-contained.toml +1 -1
- package/tests/lint-extra-example-rules/mops.toml +1 -1
- package/tests/requirements-lintoko/mops.toml +5 -0
- package/tests/requirements.test.ts +66 -0
- package/types.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
## Next
|
|
4
4
|
|
|
5
|
+
## 2.16.1
|
|
6
|
+
|
|
7
|
+
- Fix `mops bench` crashing on moc 0.15+ with the default `--gc copying`: `--copying-gc` is rejected under enhanced orthogonal persistence, which became the default persistence mode in 2.16.0. The default GC is now `incremental` (moc's default and the only collector available under EOP). Selecting a legacy collector (`copying`, `compacting`, `generational`) now implies `--legacy-persistence`, since moc only accepts them there.
|
|
8
|
+
- `mops build`, `mops check`, `mops generate`, and `mops check-stable` now tunnel `moc`'s exit code instead of always exiting `1`. moc exits `2` on a compiler crash (uncaught internal error) versus `1` for normal user errors (type/compile errors, stable-compat mismatch, bad args), so CI and scripts can now distinguish "file a Motoko bug" from "fix your code" via `$?`. `mops test` likewise exits `2` when a `moc` process crashes during a test run, instead of reporting it as an ordinary test failure.
|
|
9
|
+
|
|
10
|
+
## 2.16.0
|
|
11
|
+
|
|
12
|
+
- `mops bench` now compiles benchmark canisters under **enhanced orthogonal persistence** (moc's default) instead of forcing `--legacy-persistence` — measuring the persistence mode real canisters run. Pass `--legacy-persistence` to opt back into legacy persistence.
|
|
13
|
+
- `mops bench --query` measures each cell in a **query** call instead of an update call. Queries run no GC on the IC, so the instruction counts exclude GC work an update would incur — for benchmarking `query`/read-only workloads realistically. Only for synchronous benchmark runners (no inter-canister `await`).
|
|
14
|
+
- `[requirements].lintoko` declares a minimum lintoko version for package consumers. `mops install` (and `mops add`, `mops toolchain use`) warn when the project's lintoko is below a dependency's requirement, same as `moc` (#597).
|
|
15
|
+
|
|
5
16
|
## 2.15.2
|
|
6
17
|
|
|
7
18
|
- `mops check-stable` (and the stable check inside `mops check`) reports when `[canisters.<name>.migrations].check-limit` is set but more migrations are pending than the limit allows. If the compatibility check failed, the check-limit diagnostic replaces the misleading `moc` error; if it passed anyway, a warning is shown. Compares the deployed `.most` baseline against the local chain; use `--no-check-limit` to suppress.
|
package/bundle/cli.tgz
CHANGED
|
Binary file
|
package/check-requirements.ts
CHANGED
|
@@ -5,47 +5,69 @@ import chalk from "chalk";
|
|
|
5
5
|
import { getDependencyType, getRootDir, readConfig } from "./mops.js";
|
|
6
6
|
import { resolvePackages } from "./resolve-packages.js";
|
|
7
7
|
import { getMocSemVer } from "./helpers/get-moc-version.js";
|
|
8
|
+
import { getLintokoSemVer } from "./helpers/get-lintoko-version.js";
|
|
8
9
|
import { getPackageId } from "./helpers/get-package-id.js";
|
|
10
|
+
import type { Requirements } from "./types.js";
|
|
11
|
+
|
|
12
|
+
type ToolRequirement = keyof Requirements;
|
|
13
|
+
|
|
14
|
+
const TOOL_REQUIREMENTS: {
|
|
15
|
+
tool: ToolRequirement;
|
|
16
|
+
getInstalled: () => SemVer | null;
|
|
17
|
+
}[] = [
|
|
18
|
+
{ tool: "moc", getInstalled: getMocSemVer },
|
|
19
|
+
{ tool: "lintoko", getInstalled: getLintokoSemVer },
|
|
20
|
+
];
|
|
9
21
|
|
|
10
22
|
export async function checkRequirements({ verbose = false } = {}) {
|
|
11
|
-
let installedMoc = getMocSemVer();
|
|
12
|
-
if (!installedMoc) {
|
|
13
|
-
return;
|
|
14
|
-
}
|
|
15
|
-
let highestRequiredMoc = new SemVer("0.0.0");
|
|
16
|
-
let highestRequiredMocPkgId = "";
|
|
17
23
|
let rootDir = getRootDir();
|
|
18
|
-
|
|
19
24
|
let resolvedPackages = await resolvePackages();
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
25
|
+
|
|
26
|
+
for (let { tool, getInstalled } of TOOL_REQUIREMENTS) {
|
|
27
|
+
let installed = getInstalled();
|
|
28
|
+
if (!installed) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let highestRequired = new SemVer("0.0.0");
|
|
33
|
+
let highestRequiredPkgId = "";
|
|
34
|
+
|
|
35
|
+
for (let [name, version] of Object.entries(resolvedPackages)) {
|
|
36
|
+
if (getDependencyType(version) === "mops") {
|
|
37
|
+
let pkgId = getPackageId(name, version);
|
|
38
|
+
let depConfig = readConfig(
|
|
39
|
+
path.join(rootDir, ".mops", pkgId, "mops.toml"),
|
|
40
|
+
);
|
|
41
|
+
let required = depConfig.requirements?.[tool];
|
|
42
|
+
|
|
43
|
+
if (required) {
|
|
44
|
+
let requiredVersion = new SemVer(required);
|
|
45
|
+
if (highestRequired.compare(requiredVersion) < 0) {
|
|
46
|
+
highestRequired = requiredVersion;
|
|
47
|
+
highestRequiredPkgId = pkgId;
|
|
48
|
+
}
|
|
49
|
+
verbose && _check(tool, pkgId, installed, requiredVersion);
|
|
33
50
|
}
|
|
34
|
-
verbose && _check(pkgId, installedMoc, requiredMoc);
|
|
35
51
|
}
|
|
36
52
|
}
|
|
37
|
-
}
|
|
38
53
|
|
|
39
|
-
|
|
54
|
+
verbose || _check(tool, highestRequiredPkgId, installed, highestRequired);
|
|
55
|
+
}
|
|
40
56
|
}
|
|
41
57
|
|
|
42
|
-
function _check(
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
58
|
+
function _check(
|
|
59
|
+
tool: ToolRequirement,
|
|
60
|
+
pkgId: string,
|
|
61
|
+
installed: SemVer,
|
|
62
|
+
required: SemVer,
|
|
63
|
+
) {
|
|
64
|
+
if (!pkgId || installed.compare(required) >= 0) {
|
|
65
|
+
return;
|
|
50
66
|
}
|
|
67
|
+
|
|
68
|
+
console.log(
|
|
69
|
+
chalk.yellow(`${tool} version does not meet the requirements of ${pkgId}`),
|
|
70
|
+
);
|
|
71
|
+
console.log(chalk.yellow(` Required: >= ${required.format()}`));
|
|
72
|
+
console.log(chalk.yellow(` Installed: ${installed.format()}`));
|
|
51
73
|
}
|
package/cli.ts
CHANGED
|
@@ -544,9 +544,12 @@ program
|
|
|
544
544
|
).choices(["dfx", "pocket-ic"]),
|
|
545
545
|
)
|
|
546
546
|
.addOption(
|
|
547
|
-
new Option(
|
|
547
|
+
new Option(
|
|
548
|
+
"--gc <gc>",
|
|
549
|
+
"Garbage collector. Under enhanced orthogonal persistence (the default) the GC is fixed to `incremental`; selecting `copying`, `compacting`, or `generational` implies `--legacy-persistence`",
|
|
550
|
+
)
|
|
548
551
|
.choices(["copying", "compacting", "generational", "incremental"])
|
|
549
|
-
.default("
|
|
552
|
+
.default("incremental"),
|
|
550
553
|
)
|
|
551
554
|
.addOption(
|
|
552
555
|
new Option("--save", "Save benchmark results to .bench/<filename>.json"),
|
|
@@ -558,10 +561,22 @@ program
|
|
|
558
561
|
),
|
|
559
562
|
)
|
|
560
563
|
// .addOption(new Option('--force-gc', 'Force GC'))
|
|
564
|
+
.addOption(
|
|
565
|
+
new Option(
|
|
566
|
+
"--query",
|
|
567
|
+
"Measure each cell in a query call (how `query` methods run on the IC: no GC). Only for benchmarks whose runner is synchronous (no inter-canister calls)",
|
|
568
|
+
),
|
|
569
|
+
)
|
|
570
|
+
.addOption(
|
|
571
|
+
new Option(
|
|
572
|
+
"--legacy-persistence",
|
|
573
|
+
"Compile benchmark canisters under legacy persistence instead of enhanced orthogonal persistence (the default)",
|
|
574
|
+
),
|
|
575
|
+
)
|
|
561
576
|
.addOption(
|
|
562
577
|
new Option(
|
|
563
578
|
"--verbose",
|
|
564
|
-
"Print the benchmark pipeline (compiler, replica, GC, optimization) and stream compiler/replica output, including dfx optimization warnings",
|
|
579
|
+
"Print the benchmark pipeline (compiler, replica, GC, context, persistence, profile, optimization) and stream compiler/replica output, including dfx optimization warnings",
|
|
565
580
|
),
|
|
566
581
|
)
|
|
567
582
|
.action(async (filter, options) => {
|
package/commands/bench.ts
CHANGED
|
@@ -40,6 +40,8 @@ type BenchOptions = {
|
|
|
40
40
|
compilerVersion: string;
|
|
41
41
|
gc: "copying" | "compacting" | "generational" | "incremental";
|
|
42
42
|
forceGc: boolean;
|
|
43
|
+
query: boolean;
|
|
44
|
+
legacyPersistence: boolean;
|
|
43
45
|
save: boolean;
|
|
44
46
|
compare: boolean;
|
|
45
47
|
verbose: boolean;
|
|
@@ -59,8 +61,10 @@ export async function bench(
|
|
|
59
61
|
replicaVersion: "",
|
|
60
62
|
compiler: "moc",
|
|
61
63
|
compilerVersion: getMocVersion(true),
|
|
62
|
-
gc: "
|
|
64
|
+
gc: "incremental",
|
|
63
65
|
forceGc: true,
|
|
66
|
+
query: false,
|
|
67
|
+
legacyPersistence: false,
|
|
64
68
|
save: false,
|
|
65
69
|
compare: false,
|
|
66
70
|
verbose: false,
|
|
@@ -108,6 +112,8 @@ export async function bench(
|
|
|
108
112
|
replicaType === "dfx" || replicaType === "dfx-pocket-ic"
|
|
109
113
|
? 'dfx `optimize: "cycles"` (ic-wasm) on deploy'
|
|
110
114
|
: "none (raw moc output)";
|
|
115
|
+
let legacyGc = isLegacyGc(options.gc);
|
|
116
|
+
let effectiveLegacyPersistence = options.legacyPersistence || legacyGc;
|
|
111
117
|
console.log(chalk.gray("Benchmark pipeline:"));
|
|
112
118
|
console.log(chalk.gray(` compiler: moc ${options.compilerVersion}`));
|
|
113
119
|
console.log(
|
|
@@ -120,6 +126,21 @@ export async function bench(
|
|
|
120
126
|
` gc: ${options.gc}${options.forceGc ? " (forced)" : ""}`,
|
|
121
127
|
),
|
|
122
128
|
);
|
|
129
|
+
console.log(
|
|
130
|
+
chalk.gray(` context: ${options.query ? "query" : "update"}`),
|
|
131
|
+
);
|
|
132
|
+
console.log(
|
|
133
|
+
chalk.gray(
|
|
134
|
+
` persistence: ${effectiveLegacyPersistence ? "legacy" : "enhanced"}`,
|
|
135
|
+
),
|
|
136
|
+
);
|
|
137
|
+
if (legacyGc && !options.legacyPersistence) {
|
|
138
|
+
console.log(
|
|
139
|
+
chalk.gray(
|
|
140
|
+
` (gc '${options.gc}' only exists under legacy persistence; enabling --legacy-persistence)`,
|
|
141
|
+
),
|
|
142
|
+
);
|
|
143
|
+
}
|
|
123
144
|
console.log(chalk.gray(` profile: ${options.profile}`));
|
|
124
145
|
console.log(chalk.gray(` optimize: ${optimize}`));
|
|
125
146
|
}
|
|
@@ -267,13 +288,25 @@ function computeDiffAll(
|
|
|
267
288
|
return diff;
|
|
268
289
|
}
|
|
269
290
|
|
|
291
|
+
// Collectors that only exist under legacy persistence. moc 0.15+ fixes the GC to
|
|
292
|
+
// incremental under enhanced orthogonal persistence and rejects these there.
|
|
293
|
+
function isLegacyGc(gc: BenchOptions["gc"]): boolean {
|
|
294
|
+
return gc === "copying" || gc === "compacting" || gc === "generational";
|
|
295
|
+
}
|
|
296
|
+
|
|
270
297
|
function getMocArgs(options: BenchOptions): string {
|
|
271
298
|
let args = "";
|
|
272
299
|
|
|
273
|
-
|
|
274
|
-
options.compilerVersion &&
|
|
275
|
-
new SemVer(options.compilerVersion).compare("0.15.0") >= 0
|
|
276
|
-
|
|
300
|
+
let mocAtLeast015 =
|
|
301
|
+
!!options.compilerVersion &&
|
|
302
|
+
new SemVer(options.compilerVersion).compare("0.15.0") >= 0;
|
|
303
|
+
|
|
304
|
+
// Legacy collectors require legacy persistence; moc < 0.15 is already legacy
|
|
305
|
+
// and has no --legacy-persistence flag.
|
|
306
|
+
let useLegacyPersistence =
|
|
307
|
+
options.legacyPersistence || isLegacyGc(options.gc);
|
|
308
|
+
|
|
309
|
+
if (useLegacyPersistence && mocAtLeast015) {
|
|
277
310
|
args += " --legacy-persistence";
|
|
278
311
|
}
|
|
279
312
|
|
|
@@ -281,7 +314,8 @@ function getMocArgs(options: BenchOptions): string {
|
|
|
281
314
|
args += " --force-gc";
|
|
282
315
|
}
|
|
283
316
|
|
|
284
|
-
|
|
317
|
+
// Under EOP the GC is fixed; only pass a collector flag where it's selectable.
|
|
318
|
+
if (options.gc && (useLegacyPersistence || !mocAtLeast015)) {
|
|
285
319
|
args += ` --${options.gc}-gc`;
|
|
286
320
|
}
|
|
287
321
|
|
|
@@ -533,13 +567,14 @@ async function runBenchFile(
|
|
|
533
567
|
log();
|
|
534
568
|
}
|
|
535
569
|
|
|
536
|
-
// run all cells
|
|
570
|
+
// run all cells. `--query` measures in query context (how `query` methods
|
|
571
|
+
// actually run on the IC: no GC), at the cost of not supporting benches whose
|
|
572
|
+
// runner performs async calls. Otherwise use the update path.
|
|
537
573
|
for (let [rowIndex, row] of schema.rows.entries()) {
|
|
538
574
|
for (let [colIndex, col] of schema.cols.entries()) {
|
|
539
|
-
let res =
|
|
540
|
-
BigInt(rowIndex),
|
|
541
|
-
BigInt(colIndex)
|
|
542
|
-
);
|
|
575
|
+
let res = options.query
|
|
576
|
+
? await actor.runCellQuery(BigInt(rowIndex), BigInt(colIndex))
|
|
577
|
+
: await actor.runCellUpdateAwait(BigInt(rowIndex), BigInt(colIndex));
|
|
543
578
|
results.set(`${row}:${col}`, res);
|
|
544
579
|
|
|
545
580
|
// @ts-ignore
|
package/commands/build.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { exists } from "fs-extra";
|
|
|
4
4
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
import { lock, unlockSync } from "proper-lockfile";
|
|
7
|
-
import { cliError } from "../error.js";
|
|
7
|
+
import { cliError, cliExit } from "../error.js";
|
|
8
8
|
import { isCandidCompatible } from "../helpers/is-candid-compatible.js";
|
|
9
9
|
import {
|
|
10
10
|
filterCanisters,
|
|
@@ -136,7 +136,8 @@ export async function build(
|
|
|
136
136
|
console.error(result.stdout);
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
|
-
|
|
139
|
+
cliExit(
|
|
140
|
+
result.exitCode ?? 1,
|
|
140
141
|
`Build failed for canister ${canisterName} (exit code: ${result.exitCode})`,
|
|
141
142
|
);
|
|
142
143
|
}
|
package/commands/check-stable.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { existsSync, mkdirSync, mkdtempSync } from "node:fs";
|
|
|
3
3
|
import { rm } from "node:fs/promises";
|
|
4
4
|
import chalk from "chalk";
|
|
5
5
|
import { execa } from "execa";
|
|
6
|
-
import { cliError } from "../error.js";
|
|
6
|
+
import { cliError, cliExit } from "../error.js";
|
|
7
7
|
import {
|
|
8
8
|
getCheckLimitPendingIssue,
|
|
9
9
|
prepareMigrationArgs,
|
|
@@ -261,7 +261,8 @@ export async function runStableCheck(
|
|
|
261
261
|
if (result.stderr) {
|
|
262
262
|
console.error(result.stderr);
|
|
263
263
|
}
|
|
264
|
-
|
|
264
|
+
cliExit(
|
|
265
|
+
result.exitCode ?? 1,
|
|
265
266
|
`✗ Stable compatibility check failed for canister '${canisterName}'`,
|
|
266
267
|
);
|
|
267
268
|
}
|
|
@@ -314,7 +315,8 @@ async function generateStableTypes(
|
|
|
314
315
|
if (result.stderr) {
|
|
315
316
|
console.error(result.stderr);
|
|
316
317
|
}
|
|
317
|
-
|
|
318
|
+
cliExit(
|
|
319
|
+
result.exitCode ?? 1,
|
|
318
320
|
`Failed to generate stable types for ${moFile} (exit code: ${result.exitCode})`,
|
|
319
321
|
);
|
|
320
322
|
}
|
package/commands/check.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import chalk from "chalk";
|
|
3
3
|
import { execa } from "execa";
|
|
4
|
-
import { cliError } from "../error.js";
|
|
4
|
+
import { cliError, cliExit } from "../error.js";
|
|
5
5
|
import {
|
|
6
6
|
getGlobalMocArgs,
|
|
7
7
|
getRootDir,
|
|
@@ -208,7 +208,8 @@ async function checkCanisters(
|
|
|
208
208
|
});
|
|
209
209
|
|
|
210
210
|
if (result.exitCode !== 0) {
|
|
211
|
-
|
|
211
|
+
cliExit(
|
|
212
|
+
result.exitCode ?? 1,
|
|
212
213
|
`✗ Check failed for canister ${canisterName} (exit code: ${result.exitCode})`,
|
|
213
214
|
);
|
|
214
215
|
}
|
|
@@ -285,7 +286,8 @@ async function checkFiles(
|
|
|
285
286
|
});
|
|
286
287
|
|
|
287
288
|
if (result.exitCode !== 0) {
|
|
288
|
-
|
|
289
|
+
cliExit(
|
|
290
|
+
result.exitCode ?? 1,
|
|
289
291
|
`✗ Check failed for file ${file} (exit code: ${result.exitCode})`,
|
|
290
292
|
);
|
|
291
293
|
}
|
package/commands/generate.ts
CHANGED
|
@@ -2,7 +2,7 @@ import chalk from "chalk";
|
|
|
2
2
|
import { execa } from "execa";
|
|
3
3
|
import { mkdir } from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
|
-
import { cliError } from "../error.js";
|
|
5
|
+
import { cliError, cliExit } from "../error.js";
|
|
6
6
|
import {
|
|
7
7
|
filterCanisters,
|
|
8
8
|
resolveCanisterConfigs,
|
|
@@ -104,7 +104,8 @@ export async function generateCandid(
|
|
|
104
104
|
console.error(result.stdout);
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
|
-
|
|
107
|
+
cliExit(
|
|
108
|
+
result.exitCode ?? 1,
|
|
108
109
|
`Failed to generate Candid for canister ${canisterName} (exit code: ${result.exitCode})`,
|
|
109
110
|
);
|
|
110
111
|
}
|
package/commands/test/test.ts
CHANGED
|
@@ -159,7 +159,7 @@ export async function test(filter = "", options: Partial<TestOptions> = {}) {
|
|
|
159
159
|
explicitReplica,
|
|
160
160
|
);
|
|
161
161
|
if (!passed) {
|
|
162
|
-
process.exit(1);
|
|
162
|
+
process.exit(maxMocExit >= 2 ? 2 : 1);
|
|
163
163
|
}
|
|
164
164
|
}
|
|
165
165
|
}
|
|
@@ -167,6 +167,13 @@ export async function test(filter = "", options: Partial<TestOptions> = {}) {
|
|
|
167
167
|
let mocPath = "";
|
|
168
168
|
let wasmtimePath = "";
|
|
169
169
|
|
|
170
|
+
let maxMocExit = 0;
|
|
171
|
+
const trackMocExit = (code: number | null) => {
|
|
172
|
+
if (code && code >= 2) {
|
|
173
|
+
maxMocExit = Math.max(maxMocExit, code);
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
|
|
170
177
|
async function runAll(
|
|
171
178
|
reporterName: ReporterName | undefined,
|
|
172
179
|
filter = "",
|
|
@@ -197,6 +204,7 @@ export async function testWithReporter(
|
|
|
197
204
|
signal?: AbortSignal,
|
|
198
205
|
explicitReplica = false,
|
|
199
206
|
): Promise<boolean> {
|
|
207
|
+
maxMocExit = 0;
|
|
200
208
|
let rootDir = getRootDir();
|
|
201
209
|
let files: string[] = [];
|
|
202
210
|
let libFiles = globSync("**/test?(s)/lib.mo", MOTOKO_GLOB_CONFIG);
|
|
@@ -328,7 +336,7 @@ export async function testWithReporter(
|
|
|
328
336
|
}
|
|
329
337
|
throw error;
|
|
330
338
|
});
|
|
331
|
-
pipeMMF(proc, mmf).then(resolve);
|
|
339
|
+
pipeMMF(proc, mmf, trackMocExit).then(resolve);
|
|
332
340
|
}
|
|
333
341
|
// build and run wasm
|
|
334
342
|
else if (mode === "wasi") {
|
|
@@ -346,7 +354,7 @@ export async function testWithReporter(
|
|
|
346
354
|
}
|
|
347
355
|
throw error;
|
|
348
356
|
});
|
|
349
|
-
pipeMMF(buildProc, mmf)
|
|
357
|
+
pipeMMF(buildProc, mmf, trackMocExit)
|
|
350
358
|
.then(async () => {
|
|
351
359
|
if (mmf.failed > 0) {
|
|
352
360
|
return;
|
|
@@ -413,7 +421,7 @@ export async function testWithReporter(
|
|
|
413
421
|
throw error;
|
|
414
422
|
});
|
|
415
423
|
|
|
416
|
-
pipeMMF(buildProc, mmf)
|
|
424
|
+
pipeMMF(buildProc, mmf, trackMocExit)
|
|
417
425
|
.then(async () => {
|
|
418
426
|
if (mmf.failed > 0) {
|
|
419
427
|
return;
|
package/commands/test/utils.ts
CHANGED
|
@@ -68,18 +68,22 @@ export function pipeStderrToMMF(stderr: Readable, mmf: MMF1, dir = "") {
|
|
|
68
68
|
});
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
export function pipeMMF(
|
|
71
|
+
export function pipeMMF(
|
|
72
|
+
proc: ChildProcessWithoutNullStreams,
|
|
73
|
+
mmf: MMF1,
|
|
74
|
+
onClose?: (code: number | null) => void,
|
|
75
|
+
) {
|
|
72
76
|
return new Promise<void>((resolve) => {
|
|
73
77
|
pipeStdoutToMMF(proc.stdout, mmf);
|
|
74
78
|
pipeStderrToMMF(proc.stderr, mmf);
|
|
75
79
|
|
|
76
|
-
// exit
|
|
77
80
|
proc.on("close", (code) => {
|
|
78
81
|
if (code === 0) {
|
|
79
82
|
mmf.strategy !== "print" && mmf.pass();
|
|
80
83
|
} else if (code !== 1) {
|
|
81
84
|
mmf.fail(`unknown exit code: ${code}`);
|
|
82
85
|
}
|
|
86
|
+
onClose?.(code ?? null);
|
|
83
87
|
resolve();
|
|
84
88
|
});
|
|
85
89
|
});
|
|
@@ -337,9 +337,7 @@ async function update(tool?: Tool) {
|
|
|
337
337
|
config.toolchain[tool] = version;
|
|
338
338
|
writeConfig(config);
|
|
339
339
|
|
|
340
|
-
|
|
341
|
-
await checkRequirements();
|
|
342
|
-
}
|
|
340
|
+
await checkRequirements();
|
|
343
341
|
|
|
344
342
|
if (oldVersion === version) {
|
|
345
343
|
console.log(`Latest ${tool} ${version} is already installed`);
|
|
@@ -4,38 +4,45 @@ import chalk from "chalk";
|
|
|
4
4
|
import { getDependencyType, getRootDir, readConfig } from "./mops.js";
|
|
5
5
|
import { resolvePackages } from "./resolve-packages.js";
|
|
6
6
|
import { getMocSemVer } from "./helpers/get-moc-version.js";
|
|
7
|
+
import { getLintokoSemVer } from "./helpers/get-lintoko-version.js";
|
|
7
8
|
import { getPackageId } from "./helpers/get-package-id.js";
|
|
9
|
+
const TOOL_REQUIREMENTS = [
|
|
10
|
+
{ tool: "moc", getInstalled: getMocSemVer },
|
|
11
|
+
{ tool: "lintoko", getInstalled: getLintokoSemVer },
|
|
12
|
+
];
|
|
8
13
|
export async function checkRequirements({ verbose = false } = {}) {
|
|
9
|
-
let installedMoc = getMocSemVer();
|
|
10
|
-
if (!installedMoc) {
|
|
11
|
-
return;
|
|
12
|
-
}
|
|
13
|
-
let highestRequiredMoc = new SemVer("0.0.0");
|
|
14
|
-
let highestRequiredMocPkgId = "";
|
|
15
14
|
let rootDir = getRootDir();
|
|
16
15
|
let resolvedPackages = await resolvePackages();
|
|
17
|
-
for (let
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
16
|
+
for (let { tool, getInstalled } of TOOL_REQUIREMENTS) {
|
|
17
|
+
let installed = getInstalled();
|
|
18
|
+
if (!installed) {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
let highestRequired = new SemVer("0.0.0");
|
|
22
|
+
let highestRequiredPkgId = "";
|
|
23
|
+
for (let [name, version] of Object.entries(resolvedPackages)) {
|
|
24
|
+
if (getDependencyType(version) === "mops") {
|
|
25
|
+
let pkgId = getPackageId(name, version);
|
|
26
|
+
let depConfig = readConfig(path.join(rootDir, ".mops", pkgId, "mops.toml"));
|
|
27
|
+
let required = depConfig.requirements?.[tool];
|
|
28
|
+
if (required) {
|
|
29
|
+
let requiredVersion = new SemVer(required);
|
|
30
|
+
if (highestRequired.compare(requiredVersion) < 0) {
|
|
31
|
+
highestRequired = requiredVersion;
|
|
32
|
+
highestRequiredPkgId = pkgId;
|
|
33
|
+
}
|
|
34
|
+
verbose && _check(tool, pkgId, installed, requiredVersion);
|
|
27
35
|
}
|
|
28
|
-
verbose && _check(pkgId, installedMoc, requiredMoc);
|
|
29
36
|
}
|
|
30
37
|
}
|
|
38
|
+
verbose || _check(tool, highestRequiredPkgId, installed, highestRequired);
|
|
31
39
|
}
|
|
32
|
-
verbose || _check(highestRequiredMocPkgId, installedMoc, highestRequiredMoc);
|
|
33
40
|
}
|
|
34
|
-
function _check(pkgId,
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
console.log(chalk.yellow(`moc version does not meet the requirements of ${pkgId}`));
|
|
38
|
-
console.log(chalk.yellow(` Required: >= ${requiredMoc.format()}`));
|
|
39
|
-
console.log(chalk.yellow(` Installed: ${installedMoc.format()}`));
|
|
41
|
+
function _check(tool, pkgId, installed, required) {
|
|
42
|
+
if (!pkgId || installed.compare(required) >= 0) {
|
|
43
|
+
return;
|
|
40
44
|
}
|
|
45
|
+
console.log(chalk.yellow(`${tool} version does not meet the requirements of ${pkgId}`));
|
|
46
|
+
console.log(chalk.yellow(` Required: >= ${required.format()}`));
|
|
47
|
+
console.log(chalk.yellow(` Installed: ${installed.format()}`));
|
|
41
48
|
}
|
package/dist/cli.js
CHANGED
|
@@ -395,13 +395,15 @@ program
|
|
|
395
395
|
.command("bench [filter]")
|
|
396
396
|
.description("Run benchmarks")
|
|
397
397
|
.addOption(new Option("--replica <replica>", "Which replica to use to run benchmarks (`dfx` is deprecated; prefer `pocket-ic`)").choices(["dfx", "pocket-ic"]))
|
|
398
|
-
.addOption(new Option("--gc <gc>", "Garbage collector")
|
|
398
|
+
.addOption(new Option("--gc <gc>", "Garbage collector. Under enhanced orthogonal persistence (the default) the GC is fixed to `incremental`; selecting `copying`, `compacting`, or `generational` implies `--legacy-persistence`")
|
|
399
399
|
.choices(["copying", "compacting", "generational", "incremental"])
|
|
400
|
-
.default("
|
|
400
|
+
.default("incremental"))
|
|
401
401
|
.addOption(new Option("--save", "Save benchmark results to .bench/<filename>.json"))
|
|
402
402
|
.addOption(new Option("--compare", "Run benchmark and compare results with .bench/<filename>.json"))
|
|
403
403
|
// .addOption(new Option('--force-gc', 'Force GC'))
|
|
404
|
-
.addOption(new Option("--
|
|
404
|
+
.addOption(new Option("--query", "Measure each cell in a query call (how `query` methods run on the IC: no GC). Only for benchmarks whose runner is synchronous (no inter-canister calls)"))
|
|
405
|
+
.addOption(new Option("--legacy-persistence", "Compile benchmark canisters under legacy persistence instead of enhanced orthogonal persistence (the default)"))
|
|
406
|
+
.addOption(new Option("--verbose", "Print the benchmark pipeline (compiler, replica, GC, context, persistence, profile, optimization) and stream compiler/replica output, including dfx optimization warnings"))
|
|
405
407
|
.action(async (filter, options) => {
|
|
406
408
|
checkConfigFile(true);
|
|
407
409
|
await installAll({
|
package/dist/commands/bench.d.ts
CHANGED
package/dist/commands/bench.js
CHANGED
|
@@ -29,8 +29,10 @@ export async function bench(filter = "", optionsArg = {}) {
|
|
|
29
29
|
replicaVersion: "",
|
|
30
30
|
compiler: "moc",
|
|
31
31
|
compilerVersion: getMocVersion(true),
|
|
32
|
-
gc: "
|
|
32
|
+
gc: "incremental",
|
|
33
33
|
forceGc: true,
|
|
34
|
+
query: false,
|
|
35
|
+
legacyPersistence: false,
|
|
34
36
|
save: false,
|
|
35
37
|
compare: false,
|
|
36
38
|
verbose: false,
|
|
@@ -67,10 +69,17 @@ export async function bench(filter = "", optionsArg = {}) {
|
|
|
67
69
|
let optimize = replicaType === "dfx" || replicaType === "dfx-pocket-ic"
|
|
68
70
|
? 'dfx `optimize: "cycles"` (ic-wasm) on deploy'
|
|
69
71
|
: "none (raw moc output)";
|
|
72
|
+
let legacyGc = isLegacyGc(options.gc);
|
|
73
|
+
let effectiveLegacyPersistence = options.legacyPersistence || legacyGc;
|
|
70
74
|
console.log(chalk.gray("Benchmark pipeline:"));
|
|
71
75
|
console.log(chalk.gray(` compiler: moc ${options.compilerVersion}`));
|
|
72
76
|
console.log(chalk.gray(` replica: ${replicaType}${options.replicaVersion ? ` ${options.replicaVersion}` : ""}`));
|
|
73
77
|
console.log(chalk.gray(` gc: ${options.gc}${options.forceGc ? " (forced)" : ""}`));
|
|
78
|
+
console.log(chalk.gray(` context: ${options.query ? "query" : "update"}`));
|
|
79
|
+
console.log(chalk.gray(` persistence: ${effectiveLegacyPersistence ? "legacy" : "enhanced"}`));
|
|
80
|
+
if (legacyGc && !options.legacyPersistence) {
|
|
81
|
+
console.log(chalk.gray(` (gc '${options.gc}' only exists under legacy persistence; enabling --legacy-persistence)`));
|
|
82
|
+
}
|
|
74
83
|
console.log(chalk.gray(` profile: ${options.profile}`));
|
|
75
84
|
console.log(chalk.gray(` optimize: ${optimize}`));
|
|
76
85
|
}
|
|
@@ -186,16 +195,26 @@ function computeDiffAll(currentResults, prevResults, rows, cols) {
|
|
|
186
195
|
}
|
|
187
196
|
return diff;
|
|
188
197
|
}
|
|
198
|
+
// Collectors that only exist under legacy persistence. moc 0.15+ fixes the GC to
|
|
199
|
+
// incremental under enhanced orthogonal persistence and rejects these there.
|
|
200
|
+
function isLegacyGc(gc) {
|
|
201
|
+
return gc === "copying" || gc === "compacting" || gc === "generational";
|
|
202
|
+
}
|
|
189
203
|
function getMocArgs(options) {
|
|
190
204
|
let args = "";
|
|
191
|
-
|
|
192
|
-
new SemVer(options.compilerVersion).compare("0.15.0") >= 0
|
|
205
|
+
let mocAtLeast015 = !!options.compilerVersion &&
|
|
206
|
+
new SemVer(options.compilerVersion).compare("0.15.0") >= 0;
|
|
207
|
+
// Legacy collectors require legacy persistence; moc < 0.15 is already legacy
|
|
208
|
+
// and has no --legacy-persistence flag.
|
|
209
|
+
let useLegacyPersistence = options.legacyPersistence || isLegacyGc(options.gc);
|
|
210
|
+
if (useLegacyPersistence && mocAtLeast015) {
|
|
193
211
|
args += " --legacy-persistence";
|
|
194
212
|
}
|
|
195
213
|
if (options.forceGc) {
|
|
196
214
|
args += " --force-gc";
|
|
197
215
|
}
|
|
198
|
-
|
|
216
|
+
// Under EOP the GC is fixed; only pass a collector flag where it's selectable.
|
|
217
|
+
if (options.gc && (useLegacyPersistence || !mocAtLeast015)) {
|
|
199
218
|
args += ` --${options.gc}-gc`;
|
|
200
219
|
}
|
|
201
220
|
if (options.profile === "Debug") {
|
|
@@ -386,10 +405,14 @@ async function runBenchFile(file, options, replica) {
|
|
|
386
405
|
if (canUpdateLog) {
|
|
387
406
|
log();
|
|
388
407
|
}
|
|
389
|
-
// run all cells
|
|
408
|
+
// run all cells. `--query` measures in query context (how `query` methods
|
|
409
|
+
// actually run on the IC: no GC), at the cost of not supporting benches whose
|
|
410
|
+
// runner performs async calls. Otherwise use the update path.
|
|
390
411
|
for (let [rowIndex, row] of schema.rows.entries()) {
|
|
391
412
|
for (let [colIndex, col] of schema.cols.entries()) {
|
|
392
|
-
let res =
|
|
413
|
+
let res = options.query
|
|
414
|
+
? await actor.runCellQuery(BigInt(rowIndex), BigInt(colIndex))
|
|
415
|
+
: await actor.runCellUpdateAwait(BigInt(rowIndex), BigInt(colIndex));
|
|
393
416
|
results.set(`${row}:${col}`, res);
|
|
394
417
|
// @ts-ignore
|
|
395
418
|
instructionsCells[rowIndex][colIndex] = res.instructions;
|
package/dist/commands/build.js
CHANGED
|
@@ -4,7 +4,7 @@ import { exists } from "fs-extra";
|
|
|
4
4
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
import { lock, unlockSync } from "proper-lockfile";
|
|
7
|
-
import { cliError } from "../error.js";
|
|
7
|
+
import { cliError, cliExit } from "../error.js";
|
|
8
8
|
import { isCandidCompatible } from "../helpers/is-candid-compatible.js";
|
|
9
9
|
import { filterCanisters, resolveCanisterConfigs, } from "../helpers/resolve-canisters.js";
|
|
10
10
|
import { BUILD_MANAGED_FLAGS, prepareMocArgs } from "../helpers/moc-args.js";
|
|
@@ -107,7 +107,7 @@ export async function build(canisterNames, options) {
|
|
|
107
107
|
console.error(result.stdout);
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
|
-
|
|
110
|
+
cliExit(result.exitCode ?? 1, `Build failed for canister ${canisterName} (exit code: ${result.exitCode})`);
|
|
111
111
|
}
|
|
112
112
|
if (options.verbose && result.stdout && result.stdout.trim()) {
|
|
113
113
|
console.log(result.stdout);
|
|
@@ -3,7 +3,7 @@ import { existsSync, mkdirSync, mkdtempSync } from "node:fs";
|
|
|
3
3
|
import { rm } from "node:fs/promises";
|
|
4
4
|
import chalk from "chalk";
|
|
5
5
|
import { execa } from "execa";
|
|
6
|
-
import { cliError } from "../error.js";
|
|
6
|
+
import { cliError, cliExit } from "../error.js";
|
|
7
7
|
import { getCheckLimitPendingIssue, prepareMigrationArgs, reportCheckLimitPendingIssue, } from "../helpers/migrations.js";
|
|
8
8
|
import { getGlobalMocArgs, readConfig, resolveConfigPath } from "../mops.js";
|
|
9
9
|
import { filterCanisters, looksLikeFile, resolveCanisterConfigs, resolveSingleCanister, validateCanisterArgs, } from "../helpers/resolve-canisters.js";
|
|
@@ -146,7 +146,7 @@ export async function runStableCheck(params) {
|
|
|
146
146
|
if (result.stderr) {
|
|
147
147
|
console.error(result.stderr);
|
|
148
148
|
}
|
|
149
|
-
|
|
149
|
+
cliExit(result.exitCode ?? 1, `✗ Stable compatibility check failed for canister '${canisterName}'`);
|
|
150
150
|
}
|
|
151
151
|
console.log(chalk.green(`✓ Stable compatibility check passed for canister '${canisterName}'`));
|
|
152
152
|
}
|
|
@@ -178,7 +178,7 @@ async function generateStableTypes(mocPath, moFile, outputPath, sources, globalM
|
|
|
178
178
|
if (result.stderr) {
|
|
179
179
|
console.error(result.stderr);
|
|
180
180
|
}
|
|
181
|
-
|
|
181
|
+
cliExit(result.exitCode ?? 1, `Failed to generate stable types for ${moFile} (exit code: ${result.exitCode})`);
|
|
182
182
|
}
|
|
183
183
|
await rm(wasmPath, { force: true });
|
|
184
184
|
return outputPath;
|
package/dist/commands/check.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import chalk from "chalk";
|
|
3
3
|
import { execa } from "execa";
|
|
4
|
-
import { cliError } from "../error.js";
|
|
4
|
+
import { cliError, cliExit } from "../error.js";
|
|
5
5
|
import { getGlobalMocArgs, getRootDir, readConfig, resolveConfigPath, } from "../mops.js";
|
|
6
6
|
import { autofixMotoko } from "../helpers/autofix-motoko.js";
|
|
7
7
|
import { withFixLock } from "../helpers/fix-lock.js";
|
|
@@ -127,7 +127,7 @@ async function checkCanisters(config, canisters, options) {
|
|
|
127
127
|
reject: false,
|
|
128
128
|
});
|
|
129
129
|
if (result.exitCode !== 0) {
|
|
130
|
-
|
|
130
|
+
cliExit(result.exitCode ?? 1, `✗ Check failed for canister ${canisterName} (exit code: ${result.exitCode})`);
|
|
131
131
|
}
|
|
132
132
|
console.log(chalk.green(`✓ ${canisterName}`));
|
|
133
133
|
}
|
|
@@ -189,7 +189,7 @@ async function checkFiles(config, files, options) {
|
|
|
189
189
|
reject: false,
|
|
190
190
|
});
|
|
191
191
|
if (result.exitCode !== 0) {
|
|
192
|
-
|
|
192
|
+
cliExit(result.exitCode ?? 1, `✗ Check failed for file ${file} (exit code: ${result.exitCode})`);
|
|
193
193
|
}
|
|
194
194
|
console.log(chalk.green(`✓ ${file}`));
|
|
195
195
|
}
|
|
@@ -2,7 +2,7 @@ import chalk from "chalk";
|
|
|
2
2
|
import { execa } from "execa";
|
|
3
3
|
import { mkdir } from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
|
-
import { cliError } from "../error.js";
|
|
5
|
+
import { cliError, cliExit } from "../error.js";
|
|
6
6
|
import { filterCanisters, resolveCanisterConfigs, } from "../helpers/resolve-canisters.js";
|
|
7
7
|
import { GENERATE_CANDID_MANAGED_FLAGS, prepareMocArgs, } from "../helpers/moc-args.js";
|
|
8
8
|
import { getRootDir, readConfig, resolveConfigPath, writeConfig, } from "../mops.js";
|
|
@@ -60,7 +60,7 @@ export async function generateCandid(canisterNames, options) {
|
|
|
60
60
|
console.error(result.stdout);
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
|
-
|
|
63
|
+
cliExit(result.exitCode ?? 1, `Failed to generate Candid for canister ${canisterName} (exit code: ${result.exitCode})`);
|
|
64
64
|
}
|
|
65
65
|
if (dest.configPath !== null) {
|
|
66
66
|
const c = (config.canisters ??= {});
|
|
@@ -98,17 +98,24 @@ export async function test(filter = "", options = {}) {
|
|
|
98
98
|
else {
|
|
99
99
|
let passed = await runAll(options.reporter, filter, options.mode, replicaType, false, undefined, explicitReplica);
|
|
100
100
|
if (!passed) {
|
|
101
|
-
process.exit(1);
|
|
101
|
+
process.exit(maxMocExit >= 2 ? 2 : 1);
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
105
|
let mocPath = "";
|
|
106
106
|
let wasmtimePath = "";
|
|
107
|
+
let maxMocExit = 0;
|
|
108
|
+
const trackMocExit = (code) => {
|
|
109
|
+
if (code && code >= 2) {
|
|
110
|
+
maxMocExit = Math.max(maxMocExit, code);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
107
113
|
async function runAll(reporterName, filter = "", mode = "interpreter", replicaType, watch = false, signal, explicitReplica = false) {
|
|
108
114
|
let done = await testWithReporter(reporterName, filter, mode, replicaType, watch, signal, explicitReplica);
|
|
109
115
|
return done;
|
|
110
116
|
}
|
|
111
117
|
export async function testWithReporter(reporterName, filter = "", defaultMode = "interpreter", replicaType, watch = false, signal, explicitReplica = false) {
|
|
118
|
+
maxMocExit = 0;
|
|
112
119
|
let rootDir = getRootDir();
|
|
113
120
|
let files = [];
|
|
114
121
|
let libFiles = globSync("**/test?(s)/lib.mo", MOTOKO_GLOB_CONFIG);
|
|
@@ -217,7 +224,7 @@ export async function testWithReporter(reporterName, filter = "", defaultMode =
|
|
|
217
224
|
}
|
|
218
225
|
throw error;
|
|
219
226
|
});
|
|
220
|
-
pipeMMF(proc, mmf).then(resolve);
|
|
227
|
+
pipeMMF(proc, mmf, trackMocExit).then(resolve);
|
|
221
228
|
}
|
|
222
229
|
// build and run wasm
|
|
223
230
|
else if (mode === "wasi") {
|
|
@@ -230,7 +237,7 @@ export async function testWithReporter(reporterName, filter = "", defaultMode =
|
|
|
230
237
|
}
|
|
231
238
|
throw error;
|
|
232
239
|
});
|
|
233
|
-
pipeMMF(buildProc, mmf)
|
|
240
|
+
pipeMMF(buildProc, mmf, trackMocExit)
|
|
234
241
|
.then(async () => {
|
|
235
242
|
if (mmf.failed > 0) {
|
|
236
243
|
return;
|
|
@@ -288,7 +295,7 @@ export async function testWithReporter(reporterName, filter = "", defaultMode =
|
|
|
288
295
|
}
|
|
289
296
|
throw error;
|
|
290
297
|
});
|
|
291
|
-
pipeMMF(buildProc, mmf)
|
|
298
|
+
pipeMMF(buildProc, mmf, trackMocExit)
|
|
292
299
|
.then(async () => {
|
|
293
300
|
if (mmf.failed > 0) {
|
|
294
301
|
return;
|
|
@@ -4,4 +4,4 @@ import { MMF1 } from "./mmf1.js";
|
|
|
4
4
|
export declare function absToRel(p: string): string;
|
|
5
5
|
export declare function pipeStdoutToMMF(stdout: Readable, mmf: MMF1): void;
|
|
6
6
|
export declare function pipeStderrToMMF(stderr: Readable, mmf: MMF1, dir?: string): void;
|
|
7
|
-
export declare function pipeMMF(proc: ChildProcessWithoutNullStreams, mmf: MMF1): Promise<void>;
|
|
7
|
+
export declare function pipeMMF(proc: ChildProcessWithoutNullStreams, mmf: MMF1, onClose?: (code: number | null) => void): Promise<void>;
|
|
@@ -49,11 +49,10 @@ export function pipeStderrToMMF(stderr, mmf, dir = "") {
|
|
|
49
49
|
mmf.fail(text);
|
|
50
50
|
});
|
|
51
51
|
}
|
|
52
|
-
export function pipeMMF(proc, mmf) {
|
|
52
|
+
export function pipeMMF(proc, mmf, onClose) {
|
|
53
53
|
return new Promise((resolve) => {
|
|
54
54
|
pipeStdoutToMMF(proc.stdout, mmf);
|
|
55
55
|
pipeStderrToMMF(proc.stderr, mmf);
|
|
56
|
-
// exit
|
|
57
56
|
proc.on("close", (code) => {
|
|
58
57
|
if (code === 0) {
|
|
59
58
|
mmf.strategy !== "print" && mmf.pass();
|
|
@@ -61,6 +60,7 @@ export function pipeMMF(proc, mmf) {
|
|
|
61
60
|
else if (code !== 1) {
|
|
62
61
|
mmf.fail(`unknown exit code: ${code}`);
|
|
63
62
|
}
|
|
63
|
+
onClose?.(code ?? null);
|
|
64
64
|
resolve();
|
|
65
65
|
});
|
|
66
66
|
});
|
|
@@ -259,9 +259,7 @@ async function update(tool) {
|
|
|
259
259
|
let oldVersion = config.toolchain[tool];
|
|
260
260
|
config.toolchain[tool] = version;
|
|
261
261
|
writeConfig(config);
|
|
262
|
-
|
|
263
|
-
await checkRequirements();
|
|
264
|
-
}
|
|
262
|
+
await checkRequirements();
|
|
265
263
|
if (oldVersion === version) {
|
|
266
264
|
console.log(`Latest ${tool} ${version} is already installed`);
|
|
267
265
|
}
|
package/dist/error.d.ts
CHANGED
package/dist/error.js
CHANGED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { parse } from "semver";
|
|
3
|
+
import { FILE_PATH_REGEX } from "../constants.js";
|
|
4
|
+
import { readConfig } from "../mops.js";
|
|
5
|
+
export function getLintokoSemVer() {
|
|
6
|
+
return parse(getLintokoVersion(false));
|
|
7
|
+
}
|
|
8
|
+
export function getLintokoVersion(throwOnError = false) {
|
|
9
|
+
let configVersion = readConfig().toolchain?.lintoko;
|
|
10
|
+
if (!configVersion) {
|
|
11
|
+
return "";
|
|
12
|
+
}
|
|
13
|
+
if (!configVersion.match(FILE_PATH_REGEX)) {
|
|
14
|
+
return configVersion;
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
let match = execFileSync(configVersion, ["--version"])
|
|
18
|
+
.toString()
|
|
19
|
+
.trim()
|
|
20
|
+
.match(/lintoko ([^\s]+)/);
|
|
21
|
+
return match?.[1] || "";
|
|
22
|
+
}
|
|
23
|
+
catch (e) {
|
|
24
|
+
if (throwOnError) {
|
|
25
|
+
console.error(e);
|
|
26
|
+
throw new Error("lintoko not found");
|
|
27
|
+
}
|
|
28
|
+
return "";
|
|
29
|
+
}
|
|
30
|
+
}
|
package/dist/mops.js
CHANGED
|
@@ -177,9 +177,9 @@ export function readConfig(configFile = getClosestConfigFile()) {
|
|
|
177
177
|
processDeps(toml["dev-dependencies"] || {});
|
|
178
178
|
let config = { ...toml };
|
|
179
179
|
Object.entries(config.requirements || {}).forEach(([name, value]) => {
|
|
180
|
-
if (name === "moc") {
|
|
180
|
+
if (name === "moc" || name === "lintoko") {
|
|
181
181
|
config.requirements = config.requirements || {};
|
|
182
|
-
config.requirements
|
|
182
|
+
config.requirements[name] = value;
|
|
183
183
|
}
|
|
184
184
|
});
|
|
185
185
|
return config;
|
package/dist/package.json
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { describe, expect, jest, test } from "@jest/globals";
|
|
2
|
+
import { rmSync } from "node:fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { cli } from "./helpers";
|
|
5
|
+
// Pin moc 1.3.0 (≥ 0.15) to exercise the EOP path — this repo's own mops.toml
|
|
6
|
+
// uses moc 0.14.14, so the default bench run is never EOP-tested here.
|
|
7
|
+
describe("bench", () => {
|
|
8
|
+
jest.setTimeout(180_000);
|
|
9
|
+
test("runs under EOP with the default gc", async () => {
|
|
10
|
+
const cwd = path.join(import.meta.dirname, "bench");
|
|
11
|
+
try {
|
|
12
|
+
const result = await cli(["bench"], { cwd });
|
|
13
|
+
expect(result.stderr).not.toContain("--copying-gc is not supported");
|
|
14
|
+
expect(result.stderr).not.toContain("Invalid compiler flag combination");
|
|
15
|
+
expect(result.exitCode).toBe(0);
|
|
16
|
+
}
|
|
17
|
+
finally {
|
|
18
|
+
rmSync(path.join(cwd, ".mops"), { recursive: true, force: true });
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { describe, expect, test, beforeEach, afterEach } from "@jest/globals";
|
|
2
|
+
import { cp, mkdir, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { bytesToHex } from "@noble/hashes/utils";
|
|
5
|
+
import { sha256 } from "@noble/hashes/sha256";
|
|
6
|
+
import { cli } from "./helpers";
|
|
7
|
+
describe("requirements", () => {
|
|
8
|
+
const fixtureDir = path.join(import.meta.dirname, "requirements-lintoko");
|
|
9
|
+
let tempDir;
|
|
10
|
+
beforeEach(async () => {
|
|
11
|
+
tempDir = path.join(import.meta.dirname, `_tmp_requirements_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`);
|
|
12
|
+
await cp(fixtureDir, tempDir, { recursive: true });
|
|
13
|
+
const depDir = path.join(tempDir, ".mops", "my-pkg@0.1.0");
|
|
14
|
+
await mkdir(depDir, { recursive: true });
|
|
15
|
+
await writeFile(path.join(depDir, "mops.toml"), `[package]
|
|
16
|
+
name = "my-pkg"
|
|
17
|
+
version = "0.1.0"
|
|
18
|
+
|
|
19
|
+
[requirements]
|
|
20
|
+
lintoko = "0.10.0"
|
|
21
|
+
`);
|
|
22
|
+
const mopsTomlDepsHash = bytesToHex(sha256(JSON.stringify({ "my-pkg": "0.1.0" })));
|
|
23
|
+
await writeFile(path.join(tempDir, "mops.lock"), JSON.stringify({
|
|
24
|
+
version: 3,
|
|
25
|
+
mopsTomlDepsHash,
|
|
26
|
+
deps: { "my-pkg": "0.1.0" },
|
|
27
|
+
hashes: {
|
|
28
|
+
"my-pkg@0.1.0": {
|
|
29
|
+
"mops.toml": "0000000000000000000000000000000000000000000000000000000000000000",
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
}));
|
|
33
|
+
});
|
|
34
|
+
afterEach(async () => {
|
|
35
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
36
|
+
});
|
|
37
|
+
test("lintoko requirement warns when installed version is too old", async () => {
|
|
38
|
+
const result = await cli(["toolchain", "use", "lintoko", "0.7.0"], {
|
|
39
|
+
cwd: tempDir,
|
|
40
|
+
});
|
|
41
|
+
expect(result.exitCode).toBe(0);
|
|
42
|
+
expect(result.stdout).toMatch(/lintoko version does not meet the requirements of my-pkg@0\.1\.0/);
|
|
43
|
+
expect(result.stdout).toMatch(/Required: >= 0\.10\.0/);
|
|
44
|
+
expect(result.stdout).toMatch(/Installed:\s+0\.7\.0/);
|
|
45
|
+
});
|
|
46
|
+
});
|
package/dist/types.d.ts
CHANGED
package/error.ts
CHANGED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { type SemVer, parse } from "semver";
|
|
3
|
+
import { FILE_PATH_REGEX } from "../constants.js";
|
|
4
|
+
import { readConfig } from "../mops.js";
|
|
5
|
+
|
|
6
|
+
export function getLintokoSemVer(): SemVer | null {
|
|
7
|
+
return parse(getLintokoVersion(false));
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function getLintokoVersion(throwOnError = false): string {
|
|
11
|
+
let configVersion = readConfig().toolchain?.lintoko;
|
|
12
|
+
if (!configVersion) {
|
|
13
|
+
return "";
|
|
14
|
+
}
|
|
15
|
+
if (!configVersion.match(FILE_PATH_REGEX)) {
|
|
16
|
+
return configVersion;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
let match = execFileSync(configVersion, ["--version"])
|
|
21
|
+
.toString()
|
|
22
|
+
.trim()
|
|
23
|
+
.match(/lintoko ([^\s]+)/);
|
|
24
|
+
return match?.[1] || "";
|
|
25
|
+
} catch (e) {
|
|
26
|
+
if (throwOnError) {
|
|
27
|
+
console.error(e);
|
|
28
|
+
throw new Error("lintoko not found");
|
|
29
|
+
}
|
|
30
|
+
return "";
|
|
31
|
+
}
|
|
32
|
+
}
|
package/mops.ts
CHANGED
|
@@ -205,9 +205,9 @@ export function readConfig(configFile = getClosestConfigFile()): Config {
|
|
|
205
205
|
let config: Config = { ...toml };
|
|
206
206
|
|
|
207
207
|
Object.entries(config.requirements || {}).forEach(([name, value]) => {
|
|
208
|
-
if (name === "moc") {
|
|
208
|
+
if (name === "moc" || name === "lintoko") {
|
|
209
209
|
config.requirements = config.requirements || {};
|
|
210
|
-
config.requirements
|
|
210
|
+
config.requirements[name] = value;
|
|
211
211
|
}
|
|
212
212
|
});
|
|
213
213
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Exercises the bench pipeline under EOP; the template only needs
|
|
2
|
+
// init() -> { getVersion; getSchema; runCell }, so no mo:bench dep.
|
|
3
|
+
module {
|
|
4
|
+
type Schema = {
|
|
5
|
+
name : Text;
|
|
6
|
+
description : Text;
|
|
7
|
+
rows : [Text];
|
|
8
|
+
cols : [Text];
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
class Bench(schema : Schema, run : (Nat, Nat) -> ()) {
|
|
12
|
+
public func getVersion() : Nat = 1;
|
|
13
|
+
public func getSchema() : Schema = schema;
|
|
14
|
+
public let runCell = run;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
public func init() : Bench {
|
|
18
|
+
let schema : Schema = {
|
|
19
|
+
name = "Sanity";
|
|
20
|
+
description = "Trivial bench to exercise the mops bench pipeline under EOP";
|
|
21
|
+
rows = ["a"];
|
|
22
|
+
cols = ["1"];
|
|
23
|
+
};
|
|
24
|
+
func run(_ri : Nat, _ci : Nat) {};
|
|
25
|
+
Bench(schema, run);
|
|
26
|
+
};
|
|
27
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { describe, expect, jest, test } from "@jest/globals";
|
|
2
|
+
import { rmSync } from "node:fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { cli } from "./helpers";
|
|
5
|
+
|
|
6
|
+
// Pin moc 1.3.0 (≥ 0.15) to exercise the EOP path — this repo's own mops.toml
|
|
7
|
+
// uses moc 0.14.14, so the default bench run is never EOP-tested here.
|
|
8
|
+
describe("bench", () => {
|
|
9
|
+
jest.setTimeout(180_000);
|
|
10
|
+
|
|
11
|
+
test("runs under EOP with the default gc", async () => {
|
|
12
|
+
const cwd = path.join(import.meta.dirname, "bench");
|
|
13
|
+
try {
|
|
14
|
+
const result = await cli(["bench"], { cwd });
|
|
15
|
+
expect(result.stderr).not.toContain("--copying-gc is not supported");
|
|
16
|
+
expect(result.stderr).not.toContain("Invalid compiler flag combination");
|
|
17
|
+
expect(result.exitCode).toBe(0);
|
|
18
|
+
} finally {
|
|
19
|
+
rmSync(path.join(cwd, ".mops"), { recursive: true, force: true });
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
});
|
package/tests/lint-extra-example-rules/lint/migration-self-contained/migration-self-contained.toml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
name = "migration-self-contained"
|
|
2
2
|
description = "Migration files must not import local modules (relative paths). Got: @path"
|
|
3
|
-
includes = ["
|
|
3
|
+
includes = ["migrations/*.mo"]
|
|
4
4
|
query = '''
|
|
5
5
|
(import (text_literal) @path @error
|
|
6
6
|
(#match? @path "^\"[^:]*\"$"))
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { describe, expect, test, beforeEach, afterEach } from "@jest/globals";
|
|
2
|
+
import { cp, mkdir, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { bytesToHex } from "@noble/hashes/utils";
|
|
5
|
+
import { sha256 } from "@noble/hashes/sha256";
|
|
6
|
+
import { cli } from "./helpers";
|
|
7
|
+
|
|
8
|
+
describe("requirements", () => {
|
|
9
|
+
const fixtureDir = path.join(import.meta.dirname, "requirements-lintoko");
|
|
10
|
+
let tempDir: string;
|
|
11
|
+
|
|
12
|
+
beforeEach(async () => {
|
|
13
|
+
tempDir = path.join(
|
|
14
|
+
import.meta.dirname,
|
|
15
|
+
`_tmp_requirements_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
16
|
+
);
|
|
17
|
+
await cp(fixtureDir, tempDir, { recursive: true });
|
|
18
|
+
|
|
19
|
+
const depDir = path.join(tempDir, ".mops", "my-pkg@0.1.0");
|
|
20
|
+
await mkdir(depDir, { recursive: true });
|
|
21
|
+
await writeFile(
|
|
22
|
+
path.join(depDir, "mops.toml"),
|
|
23
|
+
`[package]
|
|
24
|
+
name = "my-pkg"
|
|
25
|
+
version = "0.1.0"
|
|
26
|
+
|
|
27
|
+
[requirements]
|
|
28
|
+
lintoko = "0.10.0"
|
|
29
|
+
`,
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const mopsTomlDepsHash = bytesToHex(
|
|
33
|
+
sha256(JSON.stringify({ "my-pkg": "0.1.0" })),
|
|
34
|
+
);
|
|
35
|
+
await writeFile(
|
|
36
|
+
path.join(tempDir, "mops.lock"),
|
|
37
|
+
JSON.stringify({
|
|
38
|
+
version: 3,
|
|
39
|
+
mopsTomlDepsHash,
|
|
40
|
+
deps: { "my-pkg": "0.1.0" },
|
|
41
|
+
hashes: {
|
|
42
|
+
"my-pkg@0.1.0": {
|
|
43
|
+
"mops.toml":
|
|
44
|
+
"0000000000000000000000000000000000000000000000000000000000000000",
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
}),
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
afterEach(async () => {
|
|
52
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("lintoko requirement warns when installed version is too old", async () => {
|
|
56
|
+
const result = await cli(["toolchain", "use", "lintoko", "0.7.0"], {
|
|
57
|
+
cwd: tempDir,
|
|
58
|
+
});
|
|
59
|
+
expect(result.exitCode).toBe(0);
|
|
60
|
+
expect(result.stdout).toMatch(
|
|
61
|
+
/lintoko version does not meet the requirements of my-pkg@0\.1\.0/,
|
|
62
|
+
);
|
|
63
|
+
expect(result.stdout).toMatch(/Required: >= 0\.10\.0/);
|
|
64
|
+
expect(result.stdout).toMatch(/Installed:\s+0\.7\.0/);
|
|
65
|
+
});
|
|
66
|
+
});
|