ic-mops 2.15.0 → 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 +8 -0
- package/bundle/cli.tgz +0 -0
- package/cli.ts +6 -1
- package/commands/bench-replica.ts +2 -1
- package/commands/bench.ts +32 -9
- package/dist/cli.js +1 -1
- package/dist/commands/bench-replica.js +3 -1
- package/dist/commands/bench.js +21 -4
- package/dist/helpers/autofix-motoko.js +19 -2
- package/dist/helpers/pocket-ic-client.d.ts +2 -2
- package/dist/helpers/pocket-ic-client.js +8 -4
- package/dist/package.json +1 -1
- package/dist/tests/check-fix.test.js +14 -2
- package/helpers/autofix-motoko.ts +23 -2
- package/helpers/pocket-ic-client.ts +11 -5
- package/package.json +1 -1
- package/tests/check-fix.test.ts +26 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
## Next
|
|
4
4
|
|
|
5
|
+
## 2.15.1
|
|
6
|
+
|
|
7
|
+
- `mops check --fix` no longer aborts when a fixable file is read-only (e.g. a frozen migration chain file deliberately `chmod`'d to remove write access). The autofixer now skips such files with a warning and continues fixing the rest, instead of crashing the whole run on `EACCES`/`EPERM`.
|
|
8
|
+
|
|
9
|
+
- Load the PocketIC client lazily, only when a command actually starts a replica. Commands like `mops check`, `mops build`, and `mops install` no longer pay to load it (and its `@icp-sdk/core` dependency) at startup. This also unblocks running the CLI via `tsx` in local dev, where `pic-js-mops` (shipped as ESM without `"type": "module"`) fails to resolve as a static import.
|
|
10
|
+
|
|
11
|
+
- `mops bench --verbose` is now actually verbose. It prints the benchmark pipeline up front — compiler version, replica + version, GC, profile, and whether the wasm is optimized (`dfx` post-optimizes with `optimize: "cycles"` via ic-wasm on deploy; `pocket-ic` runs the raw `moc` output) — logs the full `moc` build command, and streams the compiler and `dfx` output instead of capturing and discarding it. Notably this surfaces dfx's `WARNING: Failed to optimize the Wasm module`, which dfx prints (and then silently deploys the unoptimized module) when `optimize: "cycles"` fails — e.g. on multi-value modules that the bundled ic-wasm can't process. Previously all of this was hidden even with `--verbose`.
|
|
12
|
+
|
|
5
13
|
## 2.15.0
|
|
6
14
|
|
|
7
15
|
- Fix `mops check --fix` corrupting source on lines containing multi-byte UTF-8 characters (e.g. `Char.toNat32('京')` dropping its trailing `)`). The autofixer was feeding moc's UTF-8 byte columns into LSP's UTF-16 position API, mis-applying every edit past the first non-ASCII byte on the line. When moc emits `byte_start`/`byte_end` (1.10.0 and newer) the fixer now applies edits byte-accurately; older moc still falls back to the line+column path (unchanged behavior, still ASCII-only).
|
package/bundle/cli.tgz
CHANGED
|
Binary file
|
package/cli.ts
CHANGED
|
@@ -543,7 +543,12 @@ program
|
|
|
543
543
|
),
|
|
544
544
|
)
|
|
545
545
|
// .addOption(new Option('--force-gc', 'Force GC'))
|
|
546
|
-
.addOption(
|
|
546
|
+
.addOption(
|
|
547
|
+
new Option(
|
|
548
|
+
"--verbose",
|
|
549
|
+
"Print the benchmark pipeline (compiler, replica, GC, optimization) and stream compiler/replica output, including dfx optimization warnings",
|
|
550
|
+
),
|
|
551
|
+
)
|
|
547
552
|
.action(async (filter, options) => {
|
|
548
553
|
checkConfigFile(true);
|
|
549
554
|
await installAll({
|
|
@@ -74,7 +74,8 @@ export class BenchReplica {
|
|
|
74
74
|
if (this.type === "dfx" || this.type === "dfx-pocket-ic") {
|
|
75
75
|
await execaCommand(
|
|
76
76
|
`dfx deploy ${name} --mode reinstall --yes --identity anonymous`,
|
|
77
|
-
|
|
77
|
+
// `inherit` so dfx output is streamed under --verbose (incl. its `Failed to optimize` warning)
|
|
78
|
+
{ cwd, stdio: this.verbose ? "inherit" : ["pipe", "ignore", "pipe"] },
|
|
78
79
|
);
|
|
79
80
|
let canisterId = execSync(`dfx canister id ${name}`, { cwd })
|
|
80
81
|
.toString()
|
package/commands/bench.ts
CHANGED
|
@@ -101,7 +101,28 @@ export async function bench(
|
|
|
101
101
|
|
|
102
102
|
warnIfDfxReplica(replicaType, optionsArg.replica === "dfx");
|
|
103
103
|
|
|
104
|
-
options.verbose
|
|
104
|
+
if (options.verbose) {
|
|
105
|
+
// `dfx` post-optimizes the wasm on deploy (`optimize: "cycles"`, via ic-wasm);
|
|
106
|
+
// `pocket-ic` runs the raw moc output. This changes instruction counts, so surface it.
|
|
107
|
+
let optimize =
|
|
108
|
+
replicaType === "dfx" || replicaType === "dfx-pocket-ic"
|
|
109
|
+
? 'dfx `optimize: "cycles"` (ic-wasm) on deploy'
|
|
110
|
+
: "none (raw moc output)";
|
|
111
|
+
console.log(chalk.gray("Benchmark pipeline:"));
|
|
112
|
+
console.log(chalk.gray(` compiler: moc ${options.compilerVersion}`));
|
|
113
|
+
console.log(
|
|
114
|
+
chalk.gray(
|
|
115
|
+
` replica: ${replicaType}${options.replicaVersion ? ` ${options.replicaVersion}` : ""}`,
|
|
116
|
+
),
|
|
117
|
+
);
|
|
118
|
+
console.log(
|
|
119
|
+
chalk.gray(
|
|
120
|
+
` gc: ${options.gc}${options.forceGc ? " (forced)" : ""}`,
|
|
121
|
+
),
|
|
122
|
+
);
|
|
123
|
+
console.log(chalk.gray(` profile: ${options.profile}`));
|
|
124
|
+
console.log(chalk.gray(` optimize: ${optimize}`));
|
|
125
|
+
}
|
|
105
126
|
|
|
106
127
|
let replica = new BenchReplica(replicaType, options.verbose);
|
|
107
128
|
|
|
@@ -303,14 +324,16 @@ async function deployBenchFile(
|
|
|
303
324
|
// build canister
|
|
304
325
|
let mocPath = getMocPath();
|
|
305
326
|
let mocArgs = getMocArgs(options);
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
{
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
327
|
+
let buildCmd = `${mocPath} -c --idl canister.mo ${globalMocArgs.join(" ")} ${mocArgs} ${(await sources({ cwd: tempDir })).join(" ")}`;
|
|
328
|
+
if (options.verbose) {
|
|
329
|
+
console.log(chalk.gray(`[${canisterName}] ${buildCmd}`));
|
|
330
|
+
console.time(`build ${canisterName}`);
|
|
331
|
+
}
|
|
332
|
+
await execaCommand(buildCmd, {
|
|
333
|
+
cwd: tempDir,
|
|
334
|
+
// `inherit` so the compiler output (warnings/errors) is streamed under --verbose
|
|
335
|
+
stdio: options.verbose ? "inherit" : ["pipe", "ignore", "pipe"],
|
|
336
|
+
});
|
|
314
337
|
options.verbose && console.timeEnd(`build ${canisterName}`);
|
|
315
338
|
|
|
316
339
|
// deploy canister
|
package/dist/cli.js
CHANGED
|
@@ -393,7 +393,7 @@ program
|
|
|
393
393
|
.addOption(new Option("--save", "Save benchmark results to .bench/<filename>.json"))
|
|
394
394
|
.addOption(new Option("--compare", "Run benchmark and compare results with .bench/<filename>.json"))
|
|
395
395
|
// .addOption(new Option('--force-gc', 'Force GC'))
|
|
396
|
-
.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"))
|
|
397
397
|
.action(async (filter, options) => {
|
|
398
398
|
checkConfigFile(true);
|
|
399
399
|
await installAll({
|
|
@@ -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
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFile, writeFile } from "node:fs/promises";
|
|
2
|
-
import { resolve } from "node:path";
|
|
2
|
+
import { relative, resolve } from "node:path";
|
|
3
|
+
import chalk from "chalk";
|
|
3
4
|
import { execa } from "execa";
|
|
4
5
|
import { TextDocument } from "vscode-languageserver-textdocument";
|
|
5
6
|
export function parseDiagnostics(stdout) {
|
|
@@ -110,6 +111,9 @@ function applyDiagnosticFixes(content, fixes) {
|
|
|
110
111
|
const MAX_FIX_ITERATIONS = 10;
|
|
111
112
|
export async function autofixMotoko(mocPath, files, mocArgs) {
|
|
112
113
|
const fixedFilesCodes = new Map();
|
|
114
|
+
// Frozen migration files are often chmod'd read-only; warn once and skip
|
|
115
|
+
// them rather than aborting the whole run on EACCES/EPERM.
|
|
116
|
+
const readOnlySkipped = new Set();
|
|
113
117
|
for (let iteration = 0; iteration < MAX_FIX_ITERATIONS; iteration++) {
|
|
114
118
|
const fixesByFile = new Map();
|
|
115
119
|
for (const file of files) {
|
|
@@ -126,12 +130,25 @@ export async function autofixMotoko(mocPath, files, mocArgs) {
|
|
|
126
130
|
}
|
|
127
131
|
let progress = false;
|
|
128
132
|
for (const [file, fixes] of fixesByFile) {
|
|
133
|
+
if (readOnlySkipped.has(file)) {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
129
136
|
const original = await readFile(file, "utf-8");
|
|
130
137
|
const { text: result, appliedCodes } = applyDiagnosticFixes(original, fixes);
|
|
131
138
|
if (result === original) {
|
|
132
139
|
continue;
|
|
133
140
|
}
|
|
134
|
-
|
|
141
|
+
try {
|
|
142
|
+
await writeFile(file, result, "utf-8");
|
|
143
|
+
}
|
|
144
|
+
catch (err) {
|
|
145
|
+
if (err?.code === "EACCES" || err?.code === "EPERM") {
|
|
146
|
+
readOnlySkipped.add(file);
|
|
147
|
+
console.warn(chalk.yellow(`Skipped read-only file ${relative(process.cwd(), file)} (${appliedCodes.length} fix(es) not applied)`));
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
throw err;
|
|
151
|
+
}
|
|
135
152
|
progress = true;
|
|
136
153
|
const existing = fixedFilesCodes.get(file) ?? [];
|
|
137
154
|
existing.push(...appliedCodes);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { PocketIc, PocketIcServer } from "pic-ic";
|
|
2
|
-
import { PocketIc as PocketIcModern, PocketIcServer as PocketIcServerModern,
|
|
1
|
+
import type { PocketIc, PocketIcServer } from "pic-ic";
|
|
2
|
+
import type { PocketIc as PocketIcModern, PocketIcServer as PocketIcServerModern, StartServerOptions } from "pic-js-mops";
|
|
3
3
|
export type AnyPocketIcServer = PocketIcServer | PocketIcServerModern;
|
|
4
4
|
export type AnyPocketIc = PocketIc | PocketIcModern;
|
|
5
5
|
export type AnySetupCanister = PocketIc["setupCanister"] & PocketIcModern["setupCanister"];
|
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
import semver from "semver";
|
|
2
|
-
import { PocketIc, PocketIcServer } from "pic-ic";
|
|
3
|
-
import { PocketIc as PocketIcModern, PocketIcServer as PocketIcServerModern, } from "pic-js-mops";
|
|
4
2
|
import { readConfig } from "../mops.js";
|
|
5
3
|
function isLegacy() {
|
|
6
4
|
let version = readConfig().toolchain?.["pocket-ic"];
|
|
7
5
|
return !!version && !!semver.valid(version) && semver.lt(version, "9.0.0");
|
|
8
6
|
}
|
|
9
7
|
export async function startPocketIc(options) {
|
|
8
|
+
// Imported lazily so commands that never start a replica don't load the
|
|
9
|
+
// PocketIC client. `pic-js-mops` ships ESM without `type: module`, which a
|
|
10
|
+
// static import fails to resolve under tsx (local dev); a dynamic import
|
|
11
|
+
// resolves it on every platform.
|
|
10
12
|
if (isLegacy()) {
|
|
13
|
+
const { PocketIc, PocketIcServer } = await import("pic-ic");
|
|
11
14
|
let server = await PocketIcServer.start(options);
|
|
12
15
|
let client = await PocketIc.create(server.getUrl());
|
|
13
16
|
return { server, client };
|
|
14
17
|
}
|
|
15
|
-
|
|
16
|
-
let
|
|
18
|
+
const { PocketIc, PocketIcServer } = await import("pic-js-mops");
|
|
19
|
+
let server = await PocketIcServer.start(options);
|
|
20
|
+
let client = await PocketIc.create(server.getUrl());
|
|
17
21
|
return { server, client };
|
|
18
22
|
}
|
package/dist/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { beforeAll, describe, expect, test } from "@jest/globals";
|
|
2
|
-
import { cpSync, readdirSync, readFileSync, unlinkSync } from "node:fs";
|
|
2
|
+
import { chmodSync, cpSync, readdirSync, readFileSync, unlinkSync, } from "node:fs";
|
|
3
3
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
4
4
|
import path from "path";
|
|
5
5
|
import { lock } from "proper-lockfile";
|
|
@@ -19,7 +19,9 @@ describe("check --fix", () => {
|
|
|
19
19
|
const diagnosticFlags = [warningFlags, "--error-format=json"];
|
|
20
20
|
beforeAll(() => {
|
|
21
21
|
for (const file of readdirSync(runDir).filter((f) => f.endsWith(".mo"))) {
|
|
22
|
-
|
|
22
|
+
const p = path.join(runDir, file);
|
|
23
|
+
chmodSync(p, 0o644);
|
|
24
|
+
unlinkSync(p);
|
|
23
25
|
}
|
|
24
26
|
});
|
|
25
27
|
function copyFixture(file) {
|
|
@@ -86,6 +88,16 @@ describe("check --fix", () => {
|
|
|
86
88
|
expect(fixResult.stdout).toContain("1 fix applied");
|
|
87
89
|
expect(readFileSync(runFilePath, "utf-8")).not.toContain("<Nat>");
|
|
88
90
|
});
|
|
91
|
+
test("read-only file is skipped, not crashed", async () => {
|
|
92
|
+
const runFilePath = copyFixture("M0223.mo");
|
|
93
|
+
const before = readFileSync(runFilePath, "utf-8");
|
|
94
|
+
chmodSync(runFilePath, 0o444);
|
|
95
|
+
const result = await cli(["check", runFilePath, "--fix", "--", warningFlags], { cwd: fixDir });
|
|
96
|
+
expect(result.exitCode).toBe(0);
|
|
97
|
+
expect(result.stderr).toMatch(/Skipped read-only file/);
|
|
98
|
+
// File left untouched since the fix couldn't be written.
|
|
99
|
+
expect(readFileSync(runFilePath, "utf-8")).toBe(before);
|
|
100
|
+
});
|
|
89
101
|
test("verbose", async () => {
|
|
90
102
|
const result = await cli(["check", "Ok.mo", "--fix", "--verbose"], {
|
|
91
103
|
cwd: fixDir,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFile, writeFile } from "node:fs/promises";
|
|
2
|
-
import { resolve } from "node:path";
|
|
2
|
+
import { relative, resolve } from "node:path";
|
|
3
|
+
import chalk from "chalk";
|
|
3
4
|
import { execa } from "execa";
|
|
4
5
|
import { TextDocument } from "vscode-languageserver-textdocument";
|
|
5
6
|
|
|
@@ -186,6 +187,9 @@ export async function autofixMotoko(
|
|
|
186
187
|
mocArgs: string[],
|
|
187
188
|
): Promise<AutofixResult | null> {
|
|
188
189
|
const fixedFilesCodes = new Map<string, string[]>();
|
|
190
|
+
// Frozen migration files are often chmod'd read-only; warn once and skip
|
|
191
|
+
// them rather than aborting the whole run on EACCES/EPERM.
|
|
192
|
+
const readOnlySkipped = new Set<string>();
|
|
189
193
|
|
|
190
194
|
for (let iteration = 0; iteration < MAX_FIX_ITERATIONS; iteration++) {
|
|
191
195
|
const fixesByFile = new Map<string, DiagnosticFix[]>();
|
|
@@ -212,6 +216,10 @@ export async function autofixMotoko(
|
|
|
212
216
|
let progress = false;
|
|
213
217
|
|
|
214
218
|
for (const [file, fixes] of fixesByFile) {
|
|
219
|
+
if (readOnlySkipped.has(file)) {
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
|
|
215
223
|
const original = await readFile(file, "utf-8");
|
|
216
224
|
const { text: result, appliedCodes } = applyDiagnosticFixes(
|
|
217
225
|
original,
|
|
@@ -222,7 +230,20 @@ export async function autofixMotoko(
|
|
|
222
230
|
continue;
|
|
223
231
|
}
|
|
224
232
|
|
|
225
|
-
|
|
233
|
+
try {
|
|
234
|
+
await writeFile(file, result, "utf-8");
|
|
235
|
+
} catch (err: any) {
|
|
236
|
+
if (err?.code === "EACCES" || err?.code === "EPERM") {
|
|
237
|
+
readOnlySkipped.add(file);
|
|
238
|
+
console.warn(
|
|
239
|
+
chalk.yellow(
|
|
240
|
+
`Skipped read-only file ${relative(process.cwd(), file)} (${appliedCodes.length} fix(es) not applied)`,
|
|
241
|
+
),
|
|
242
|
+
);
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
throw err;
|
|
246
|
+
}
|
|
226
247
|
progress = true;
|
|
227
248
|
|
|
228
249
|
const existing = fixedFilesCodes.get(file) ?? [];
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import semver from "semver";
|
|
2
|
-
import { PocketIc, PocketIcServer } from "pic-ic";
|
|
3
|
-
import {
|
|
2
|
+
import type { PocketIc, PocketIcServer } from "pic-ic";
|
|
3
|
+
import type {
|
|
4
4
|
PocketIc as PocketIcModern,
|
|
5
5
|
PocketIcServer as PocketIcServerModern,
|
|
6
|
-
|
|
6
|
+
StartServerOptions,
|
|
7
7
|
} from "pic-js-mops";
|
|
8
8
|
import { readConfig } from "../mops.js";
|
|
9
9
|
|
|
@@ -20,13 +20,19 @@ function isLegacy(): boolean {
|
|
|
20
20
|
export async function startPocketIc(
|
|
21
21
|
options: StartServerOptions,
|
|
22
22
|
): Promise<{ server: AnyPocketIcServer; client: AnyPocketIc }> {
|
|
23
|
+
// Imported lazily so commands that never start a replica don't load the
|
|
24
|
+
// PocketIC client. `pic-js-mops` ships ESM without `type: module`, which a
|
|
25
|
+
// static import fails to resolve under tsx (local dev); a dynamic import
|
|
26
|
+
// resolves it on every platform.
|
|
23
27
|
if (isLegacy()) {
|
|
28
|
+
const { PocketIc, PocketIcServer } = await import("pic-ic");
|
|
24
29
|
let server = await PocketIcServer.start(options);
|
|
25
30
|
let client = await PocketIc.create(server.getUrl());
|
|
26
31
|
return { server, client };
|
|
27
32
|
}
|
|
28
33
|
|
|
29
|
-
|
|
30
|
-
let
|
|
34
|
+
const { PocketIc, PocketIcServer } = await import("pic-js-mops");
|
|
35
|
+
let server = await PocketIcServer.start(options);
|
|
36
|
+
let client = await PocketIc.create(server.getUrl());
|
|
31
37
|
return { server, client };
|
|
32
38
|
}
|
package/package.json
CHANGED
package/tests/check-fix.test.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { beforeAll, describe, expect, test } from "@jest/globals";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
chmodSync,
|
|
4
|
+
cpSync,
|
|
5
|
+
readdirSync,
|
|
6
|
+
readFileSync,
|
|
7
|
+
unlinkSync,
|
|
8
|
+
} from "node:fs";
|
|
3
9
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
4
10
|
import path from "path";
|
|
5
11
|
import { lock } from "proper-lockfile";
|
|
@@ -22,7 +28,9 @@ describe("check --fix", () => {
|
|
|
22
28
|
|
|
23
29
|
beforeAll(() => {
|
|
24
30
|
for (const file of readdirSync(runDir).filter((f) => f.endsWith(".mo"))) {
|
|
25
|
-
|
|
31
|
+
const p = path.join(runDir, file);
|
|
32
|
+
chmodSync(p, 0o644);
|
|
33
|
+
unlinkSync(p);
|
|
26
34
|
}
|
|
27
35
|
});
|
|
28
36
|
|
|
@@ -132,6 +140,22 @@ describe("check --fix", () => {
|
|
|
132
140
|
expect(readFileSync(runFilePath, "utf-8")).not.toContain("<Nat>");
|
|
133
141
|
});
|
|
134
142
|
|
|
143
|
+
test("read-only file is skipped, not crashed", async () => {
|
|
144
|
+
const runFilePath = copyFixture("M0223.mo");
|
|
145
|
+
const before = readFileSync(runFilePath, "utf-8");
|
|
146
|
+
chmodSync(runFilePath, 0o444);
|
|
147
|
+
|
|
148
|
+
const result = await cli(
|
|
149
|
+
["check", runFilePath, "--fix", "--", warningFlags],
|
|
150
|
+
{ cwd: fixDir },
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
expect(result.exitCode).toBe(0);
|
|
154
|
+
expect(result.stderr).toMatch(/Skipped read-only file/);
|
|
155
|
+
// File left untouched since the fix couldn't be written.
|
|
156
|
+
expect(readFileSync(runFilePath, "utf-8")).toBe(before);
|
|
157
|
+
});
|
|
158
|
+
|
|
135
159
|
test("verbose", async () => {
|
|
136
160
|
const result = await cli(["check", "Ok.mo", "--fix", "--verbose"], {
|
|
137
161
|
cwd: fixDir,
|