ic-mops 2.7.0 → 2.8.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 +9 -0
- package/bundle/cli.tgz +0 -0
- package/commands/build.ts +6 -0
- package/commands/check-stable.ts +9 -10
- package/dist/commands/build.js +5 -0
- package/dist/commands/check-stable.js +9 -10
- package/dist/integrity.js +7 -6
- package/dist/package.json +1 -1
- package/dist/tests/build.test.js +4 -1
- package/dist/tests/check-stable.test.js +11 -0
- package/dist/tests/cli.test.js +61 -1
- package/dist/tests/helpers.d.ts +2 -1
- package/dist/tests/helpers.js +2 -2
- package/integrity.ts +7 -9
- package/package.json +1 -1
- package/tests/__snapshots__/build.test.ts.snap +7 -3
- package/tests/build.test.ts +4 -1
- package/tests/check-stable/actor-idl/mops.toml +8 -0
- package/tests/check-stable/actor-idl/new.mo +6 -0
- package/tests/check-stable/actor-idl/old.mo +5 -0
- package/tests/check-stable/actor-idl/system-idl/aaaaa-aa.did +3 -0
- package/tests/check-stable.test.ts +12 -0
- package/tests/cli.test.ts +63 -1
- package/tests/helpers.ts +3 -2
- package/tests/install/success/mops.toml +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## Next
|
|
4
4
|
|
|
5
|
+
## 2.8.1
|
|
6
|
+
|
|
7
|
+
- Fix `mops check-stable` failing when `[moc] args` contains flags with relative paths (e.g. `--actor-idl=system-idl`)
|
|
8
|
+
|
|
9
|
+
## 2.8.0
|
|
10
|
+
|
|
11
|
+
- `mops build` now generates a `.most` (Motoko stable types) file alongside `.wasm` and `.did` for each canister; the `.most` file can be passed directly to `mops check-stable` to verify upgrade compatibility
|
|
12
|
+
- `mops.lock` is now created automatically the first time dependencies are installed — no need to run `mops i --lock update` once to opt in. Triggered by `mops install`, `mops add`, `mops remove`, `mops update`, `mops sync`, and `mops init` (when it installs dependencies). Applications should commit `mops.lock`; library authors should add it to `.gitignore`.
|
|
13
|
+
|
|
5
14
|
## 2.7.0
|
|
6
15
|
|
|
7
16
|
- `mops publish` no longer requires a `repository` field — it is now optional metadata (used by the registry UI for source links)
|
package/bundle/cli.tgz
CHANGED
|
Binary file
|
package/commands/build.ts
CHANGED
|
@@ -69,9 +69,11 @@ export async function build(
|
|
|
69
69
|
}
|
|
70
70
|
motokoPath = resolveConfigPath(motokoPath);
|
|
71
71
|
const wasmPath = join(outputDir, `${canisterName}.wasm`);
|
|
72
|
+
const mostPath = join(outputDir, `${canisterName}.most`);
|
|
72
73
|
let args = [
|
|
73
74
|
"-c",
|
|
74
75
|
"--idl",
|
|
76
|
+
"--stable-types",
|
|
75
77
|
"-o",
|
|
76
78
|
wasmPath,
|
|
77
79
|
motokoPath,
|
|
@@ -116,6 +118,9 @@ export async function build(
|
|
|
116
118
|
console.log(result.stdout);
|
|
117
119
|
}
|
|
118
120
|
|
|
121
|
+
options.verbose &&
|
|
122
|
+
console.log(chalk.gray(`Stable types written to ${mostPath}`));
|
|
123
|
+
|
|
119
124
|
const generatedDidPath = join(outputDir, `${canisterName}.did`);
|
|
120
125
|
const resolvedCandidPath = canister.candid
|
|
121
126
|
? resolveConfigPath(canister.candid)
|
|
@@ -188,6 +193,7 @@ const managedFlags: Record<string, string> = {
|
|
|
188
193
|
"-o": "use [build].outputDir in mops.toml or --output flag instead",
|
|
189
194
|
"-c": "this flag is always set by mops build",
|
|
190
195
|
"--idl": "this flag is always set by mops build",
|
|
196
|
+
"--stable-types": "this flag is always set by mops build",
|
|
191
197
|
"--public-metadata": "this flag is managed by mops build",
|
|
192
198
|
};
|
|
193
199
|
|
package/commands/check-stable.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { basename, join
|
|
1
|
+
import { basename, join } from "node:path";
|
|
2
2
|
import { existsSync, mkdirSync } from "node:fs";
|
|
3
|
-
import {
|
|
3
|
+
import { rm } from "node:fs/promises";
|
|
4
4
|
import chalk from "chalk";
|
|
5
5
|
import { execa } from "execa";
|
|
6
6
|
import { cliError } from "../error.js";
|
|
@@ -62,8 +62,7 @@ export async function runStableCheck(
|
|
|
62
62
|
options = {},
|
|
63
63
|
} = params;
|
|
64
64
|
|
|
65
|
-
const
|
|
66
|
-
const sources = (await sourcesArgs({ cwd: checkStableDir })).flat();
|
|
65
|
+
const sources = (await sourcesArgs()).flat();
|
|
67
66
|
const isOldMostFile = oldFile.endsWith(".most");
|
|
68
67
|
|
|
69
68
|
if (!existsSync(oldFile)) {
|
|
@@ -137,10 +136,13 @@ async function generateStableTypes(
|
|
|
137
136
|
globalMocArgs: string[],
|
|
138
137
|
options: Partial<CheckStableOptions>,
|
|
139
138
|
): Promise<string> {
|
|
140
|
-
const
|
|
139
|
+
const base = basename(outputPath, ".most");
|
|
140
|
+
const wasmPath = join(CHECK_STABLE_DIR, base + ".wasm");
|
|
141
141
|
const args = [
|
|
142
142
|
"--stable-types",
|
|
143
|
-
|
|
143
|
+
"-o",
|
|
144
|
+
wasmPath,
|
|
145
|
+
moFile,
|
|
144
146
|
...sources,
|
|
145
147
|
...globalMocArgs,
|
|
146
148
|
...(options.extraArgs ?? []),
|
|
@@ -155,7 +157,6 @@ async function generateStableTypes(
|
|
|
155
157
|
}
|
|
156
158
|
|
|
157
159
|
const result = await execa(mocPath, args, {
|
|
158
|
-
cwd: CHECK_STABLE_DIR,
|
|
159
160
|
stdio: "pipe",
|
|
160
161
|
reject: false,
|
|
161
162
|
});
|
|
@@ -169,9 +170,7 @@ async function generateStableTypes(
|
|
|
169
170
|
);
|
|
170
171
|
}
|
|
171
172
|
|
|
172
|
-
|
|
173
|
-
await rename(join(CHECK_STABLE_DIR, base + ".most"), outputPath);
|
|
174
|
-
await rm(join(CHECK_STABLE_DIR, base + ".wasm"), { force: true });
|
|
173
|
+
await rm(wasmPath, { force: true });
|
|
175
174
|
|
|
176
175
|
return outputPath;
|
|
177
176
|
}
|
package/dist/commands/build.js
CHANGED
|
@@ -45,9 +45,11 @@ export async function build(canisterNames, options) {
|
|
|
45
45
|
}
|
|
46
46
|
motokoPath = resolveConfigPath(motokoPath);
|
|
47
47
|
const wasmPath = join(outputDir, `${canisterName}.wasm`);
|
|
48
|
+
const mostPath = join(outputDir, `${canisterName}.most`);
|
|
48
49
|
let args = [
|
|
49
50
|
"-c",
|
|
50
51
|
"--idl",
|
|
52
|
+
"--stable-types",
|
|
51
53
|
"-o",
|
|
52
54
|
wasmPath,
|
|
53
55
|
motokoPath,
|
|
@@ -84,6 +86,8 @@ export async function build(canisterNames, options) {
|
|
|
84
86
|
if (options.verbose && result.stdout && result.stdout.trim()) {
|
|
85
87
|
console.log(result.stdout);
|
|
86
88
|
}
|
|
89
|
+
options.verbose &&
|
|
90
|
+
console.log(chalk.gray(`Stable types written to ${mostPath}`));
|
|
87
91
|
const generatedDidPath = join(outputDir, `${canisterName}.did`);
|
|
88
92
|
const resolvedCandidPath = canister.candid
|
|
89
93
|
? resolveConfigPath(canister.candid)
|
|
@@ -132,6 +136,7 @@ const managedFlags = {
|
|
|
132
136
|
"-o": "use [build].outputDir in mops.toml or --output flag instead",
|
|
133
137
|
"-c": "this flag is always set by mops build",
|
|
134
138
|
"--idl": "this flag is always set by mops build",
|
|
139
|
+
"--stable-types": "this flag is always set by mops build",
|
|
135
140
|
"--public-metadata": "this flag is managed by mops build",
|
|
136
141
|
};
|
|
137
142
|
function collectExtraArgs(config, canister, canisterName, extraArgs) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { basename, join
|
|
1
|
+
import { basename, join } from "node:path";
|
|
2
2
|
import { existsSync, mkdirSync } from "node:fs";
|
|
3
|
-
import {
|
|
3
|
+
import { rm } from "node:fs/promises";
|
|
4
4
|
import chalk from "chalk";
|
|
5
5
|
import { execa } from "execa";
|
|
6
6
|
import { cliError } from "../error.js";
|
|
@@ -28,8 +28,7 @@ export async function checkStable(oldFile, canisterName, options = {}) {
|
|
|
28
28
|
}
|
|
29
29
|
export async function runStableCheck(params) {
|
|
30
30
|
const { oldFile, canisterMain, canisterName, mocPath, globalMocArgs, options = {}, } = params;
|
|
31
|
-
const
|
|
32
|
-
const sources = (await sourcesArgs({ cwd: checkStableDir })).flat();
|
|
31
|
+
const sources = (await sourcesArgs()).flat();
|
|
33
32
|
const isOldMostFile = oldFile.endsWith(".most");
|
|
34
33
|
if (!existsSync(oldFile)) {
|
|
35
34
|
cliError(`File not found: ${oldFile}`);
|
|
@@ -65,10 +64,13 @@ export async function runStableCheck(params) {
|
|
|
65
64
|
}
|
|
66
65
|
}
|
|
67
66
|
async function generateStableTypes(mocPath, moFile, outputPath, sources, globalMocArgs, options) {
|
|
68
|
-
const
|
|
67
|
+
const base = basename(outputPath, ".most");
|
|
68
|
+
const wasmPath = join(CHECK_STABLE_DIR, base + ".wasm");
|
|
69
69
|
const args = [
|
|
70
70
|
"--stable-types",
|
|
71
|
-
|
|
71
|
+
"-o",
|
|
72
|
+
wasmPath,
|
|
73
|
+
moFile,
|
|
72
74
|
...sources,
|
|
73
75
|
...globalMocArgs,
|
|
74
76
|
...(options.extraArgs ?? []),
|
|
@@ -78,7 +80,6 @@ async function generateStableTypes(mocPath, moFile, outputPath, sources, globalM
|
|
|
78
80
|
console.log(chalk.gray(mocPath, JSON.stringify(args)));
|
|
79
81
|
}
|
|
80
82
|
const result = await execa(mocPath, args, {
|
|
81
|
-
cwd: CHECK_STABLE_DIR,
|
|
82
83
|
stdio: "pipe",
|
|
83
84
|
reject: false,
|
|
84
85
|
});
|
|
@@ -88,8 +89,6 @@ async function generateStableTypes(mocPath, moFile, outputPath, sources, globalM
|
|
|
88
89
|
}
|
|
89
90
|
cliError(`Failed to generate stable types for ${moFile} (exit code: ${result.exitCode})`);
|
|
90
91
|
}
|
|
91
|
-
|
|
92
|
-
await rename(join(CHECK_STABLE_DIR, base + ".most"), outputPath);
|
|
93
|
-
await rm(join(CHECK_STABLE_DIR, base + ".wasm"), { force: true });
|
|
92
|
+
await rm(wasmPath, { force: true });
|
|
94
93
|
return outputPath;
|
|
95
94
|
}
|
package/dist/integrity.js
CHANGED
|
@@ -9,13 +9,8 @@ import { resolvePackages } from "./resolve-packages.js";
|
|
|
9
9
|
import { getPackageId } from "./helpers/get-package-id.js";
|
|
10
10
|
export async function checkIntegrity(lock) {
|
|
11
11
|
let force = !!lock;
|
|
12
|
-
if (!lock &&
|
|
13
|
-
!process.env["CI"] &&
|
|
14
|
-
fs.existsSync(path.join(getRootDir(), "mops.lock"))) {
|
|
15
|
-
lock = "update";
|
|
16
|
-
}
|
|
17
12
|
if (!lock) {
|
|
18
|
-
lock = process.env["CI"] ? "check" : "
|
|
13
|
+
lock = process.env["CI"] ? "check" : "update";
|
|
19
14
|
}
|
|
20
15
|
if (lock === "update") {
|
|
21
16
|
await updateLockFile();
|
|
@@ -133,7 +128,13 @@ export async function updateLockFile() {
|
|
|
133
128
|
};
|
|
134
129
|
let rootDir = getRootDir();
|
|
135
130
|
let lockFile = path.join(rootDir, "mops.lock");
|
|
131
|
+
let isNew = !fs.existsSync(lockFile);
|
|
136
132
|
fs.writeFileSync(lockFile, JSON.stringify(lockFileJson, null, 2));
|
|
133
|
+
if (isNew) {
|
|
134
|
+
console.log("mops.lock created.");
|
|
135
|
+
console.log(" Applications: commit this file.");
|
|
136
|
+
console.log(" Libraries: add mops.lock to .gitignore.");
|
|
137
|
+
}
|
|
137
138
|
}
|
|
138
139
|
// compare hashes of local files with hashes from the lock file
|
|
139
140
|
export async function checkLockFile(force = false) {
|
package/dist/package.json
CHANGED
package/dist/tests/build.test.js
CHANGED
|
@@ -42,12 +42,14 @@ describe("build", () => {
|
|
|
42
42
|
const customOut = path.join(cwd, "custom-out");
|
|
43
43
|
const customWasm = path.join(customOut, "main.wasm");
|
|
44
44
|
const customDid = path.join(customOut, "main.did");
|
|
45
|
+
const customMost = path.join(customOut, "main.most");
|
|
45
46
|
const defaultDid = path.join(cwd, ".mops/.build/main.did");
|
|
46
47
|
try {
|
|
47
48
|
const result = await cli(["build"], { cwd });
|
|
48
49
|
expect(result.exitCode).toBe(0);
|
|
49
50
|
expect(existsSync(customWasm)).toBe(true);
|
|
50
51
|
expect(existsSync(customDid)).toBe(true);
|
|
52
|
+
expect(existsSync(customMost)).toBe(true);
|
|
51
53
|
expect(existsSync(defaultDid)).toBe(false);
|
|
52
54
|
}
|
|
53
55
|
finally {
|
|
@@ -66,6 +68,7 @@ describe("build", () => {
|
|
|
66
68
|
expect(result.exitCode).toBe(0);
|
|
67
69
|
expect(existsSync(path.join(outputDir, "foo.wasm"))).toBe(true);
|
|
68
70
|
expect(existsSync(path.join(outputDir, "foo.did"))).toBe(true);
|
|
71
|
+
expect(existsSync(path.join(outputDir, "foo.most"))).toBe(true);
|
|
69
72
|
}
|
|
70
73
|
finally {
|
|
71
74
|
cleanFixture(cwd, outputDir);
|
|
@@ -76,7 +79,7 @@ describe("build", () => {
|
|
|
76
79
|
const artifact = path.join(cwd, "x");
|
|
77
80
|
const artifactDid = path.join(cwd, "x.did");
|
|
78
81
|
try {
|
|
79
|
-
await cliSnapshot(["build", "foo", "--", "-o", "x", "-c", "--idl"], { cwd }, 1);
|
|
82
|
+
await cliSnapshot(["build", "foo", "--", "-o", "x", "-c", "--idl", "--stable-types"], { cwd }, 1);
|
|
80
83
|
}
|
|
81
84
|
finally {
|
|
82
85
|
cleanFixture(cwd, artifact, artifactDid);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describe, expect, test } from "@jest/globals";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
2
3
|
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
3
4
|
import { tmpdir } from "node:os";
|
|
4
5
|
import path from "path";
|
|
@@ -42,6 +43,16 @@ describe("check-stable", () => {
|
|
|
42
43
|
await rm(tempDir, { recursive: true, force: true });
|
|
43
44
|
}
|
|
44
45
|
});
|
|
46
|
+
test("works with relative paths in moc args (e.g. --actor-idl)", async () => {
|
|
47
|
+
const cwd = path.join(import.meta.dirname, "check-stable/actor-idl");
|
|
48
|
+
const result = await cli(["check-stable", "old.mo"], { cwd });
|
|
49
|
+
expect(result.exitCode).toBe(0);
|
|
50
|
+
expect(result.stdout).toMatch(/Stable compatibility check passed/);
|
|
51
|
+
expect(existsSync(path.join(cwd, "old.most"))).toBe(false);
|
|
52
|
+
expect(existsSync(path.join(cwd, "old.wasm"))).toBe(false);
|
|
53
|
+
expect(existsSync(path.join(cwd, "new.most"))).toBe(false);
|
|
54
|
+
expect(existsSync(path.join(cwd, "new.wasm"))).toBe(false);
|
|
55
|
+
});
|
|
45
56
|
test("errors when old file does not exist", async () => {
|
|
46
57
|
const cwd = path.join(import.meta.dirname, "check-stable/compatible");
|
|
47
58
|
const result = await cli(["check-stable", "nonexistent.mo"], { cwd });
|
package/dist/tests/cli.test.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { describe, expect, test } from "@jest/globals";
|
|
1
|
+
import { describe, expect, jest, test } from "@jest/globals";
|
|
2
|
+
import { existsSync, rmSync } from "node:fs";
|
|
3
|
+
import path from "path";
|
|
2
4
|
import { cli } from "./helpers";
|
|
3
5
|
describe("cli", () => {
|
|
4
6
|
test("--version", async () => {
|
|
@@ -8,3 +10,61 @@ describe("cli", () => {
|
|
|
8
10
|
expect((await cli(["--help"])).stdout).toMatch(/^Usage: mops/m);
|
|
9
11
|
});
|
|
10
12
|
});
|
|
13
|
+
describe("install", () => {
|
|
14
|
+
jest.setTimeout(120_000);
|
|
15
|
+
test("creates mops.lock automatically on first install", async () => {
|
|
16
|
+
const cwd = path.join(import.meta.dirname, "install/success");
|
|
17
|
+
const lockFile = path.join(cwd, "mops.lock");
|
|
18
|
+
rmSync(lockFile, { force: true });
|
|
19
|
+
try {
|
|
20
|
+
// Unset CI so checkIntegrity uses the local default ("update")
|
|
21
|
+
const result = await cli(["install"], { cwd, env: { CI: undefined } });
|
|
22
|
+
expect(result.exitCode).toBe(0);
|
|
23
|
+
expect(existsSync(lockFile)).toBe(true);
|
|
24
|
+
expect(result.stdout).toMatch(/mops\.lock created/);
|
|
25
|
+
}
|
|
26
|
+
finally {
|
|
27
|
+
rmSync(lockFile, { force: true });
|
|
28
|
+
rmSync(path.join(cwd, ".mops"), { recursive: true, force: true });
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
test("does not print 'mops.lock created' on subsequent installs", async () => {
|
|
32
|
+
const cwd = path.join(import.meta.dirname, "install/success");
|
|
33
|
+
const lockFile = path.join(cwd, "mops.lock");
|
|
34
|
+
rmSync(lockFile, { force: true });
|
|
35
|
+
try {
|
|
36
|
+
// Unset CI so checkIntegrity uses the local default ("update")
|
|
37
|
+
const first = await cli(["install"], { cwd, env: { CI: undefined } });
|
|
38
|
+
expect(first.exitCode).toBe(0);
|
|
39
|
+
expect(first.stdout).toMatch(/mops\.lock created/);
|
|
40
|
+
const result = await cli(["install"], { cwd, env: { CI: undefined } });
|
|
41
|
+
expect(result.exitCode).toBe(0);
|
|
42
|
+
expect(existsSync(lockFile)).toBe(true);
|
|
43
|
+
expect(result.stdout).not.toMatch(/mops\.lock created/);
|
|
44
|
+
}
|
|
45
|
+
finally {
|
|
46
|
+
rmSync(lockFile, { force: true });
|
|
47
|
+
rmSync(path.join(cwd, ".mops"), { recursive: true, force: true });
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
test("does not create mops.lock when --lock ignore is passed", async () => {
|
|
51
|
+
const cwd = path.join(import.meta.dirname, "install/success");
|
|
52
|
+
const lockFile = path.join(cwd, "mops.lock");
|
|
53
|
+
rmSync(lockFile, { force: true });
|
|
54
|
+
try {
|
|
55
|
+
// Unset CI for consistency; --lock ignore bypasses auto-detection regardless
|
|
56
|
+
const result = await cli(["install", "--lock", "ignore"], {
|
|
57
|
+
cwd,
|
|
58
|
+
env: { CI: undefined },
|
|
59
|
+
});
|
|
60
|
+
expect(result.exitCode).toBe(0);
|
|
61
|
+
expect(existsSync(lockFile)).toBe(false);
|
|
62
|
+
}
|
|
63
|
+
finally {
|
|
64
|
+
rmSync(lockFile, { force: true });
|
|
65
|
+
rmSync(path.join(cwd, ".mops"), { recursive: true, force: true });
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
// mops add/remove/update/sync are not separately tested here because they
|
|
69
|
+
// all route through the same checkIntegrity code path tested above.
|
|
70
|
+
});
|
package/dist/tests/helpers.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
export interface CliOptions {
|
|
2
2
|
cwd?: string;
|
|
3
|
+
env?: Record<string, string | undefined>;
|
|
3
4
|
}
|
|
4
|
-
export declare const cli: (args: string[], { cwd }?: CliOptions) => Promise<import("execa").Result<{
|
|
5
|
+
export declare const cli: (args: string[], { cwd, env }?: CliOptions) => Promise<import("execa").Result<{
|
|
5
6
|
stdio: "pipe";
|
|
6
7
|
reject: false;
|
|
7
8
|
cwd?: string | undefined;
|
package/dist/tests/helpers.js
CHANGED
|
@@ -6,12 +6,12 @@ import { fileURLToPath } from "url";
|
|
|
6
6
|
// directly rather than the npm script. This exercises the real global-install
|
|
7
7
|
// code path where the binary lives outside the project tree.
|
|
8
8
|
const useGlobalBinary = Boolean(process.env.MOPS_TEST_GLOBAL);
|
|
9
|
-
export const cli = async (args, { cwd } = {}) => {
|
|
9
|
+
export const cli = async (args, { cwd, env } = {}) => {
|
|
10
10
|
const [cmd, cmdArgs] = useGlobalBinary
|
|
11
11
|
? ["mops", args]
|
|
12
12
|
: ["npm", ["run", "--silent", "mops", "--", ...args]];
|
|
13
13
|
return await execa(cmd, cmdArgs, {
|
|
14
|
-
env: { ...process.env, ...(cwd != null && { MOPS_CWD: cwd }) },
|
|
14
|
+
env: { ...process.env, ...(cwd != null && { MOPS_CWD: cwd }), ...env },
|
|
15
15
|
...(cwd != null && { cwd }),
|
|
16
16
|
stdio: "pipe",
|
|
17
17
|
reject: false,
|
package/integrity.ts
CHANGED
|
@@ -36,16 +36,8 @@ type LockFile = LockFileV1 | LockFileV2 | LockFileV3;
|
|
|
36
36
|
export async function checkIntegrity(lock?: "check" | "update" | "ignore") {
|
|
37
37
|
let force = !!lock;
|
|
38
38
|
|
|
39
|
-
if (
|
|
40
|
-
!lock &&
|
|
41
|
-
!process.env["CI"] &&
|
|
42
|
-
fs.existsSync(path.join(getRootDir(), "mops.lock"))
|
|
43
|
-
) {
|
|
44
|
-
lock = "update";
|
|
45
|
-
}
|
|
46
|
-
|
|
47
39
|
if (!lock) {
|
|
48
|
-
lock = process.env["CI"] ? "check" : "
|
|
40
|
+
lock = process.env["CI"] ? "check" : "update";
|
|
49
41
|
}
|
|
50
42
|
|
|
51
43
|
if (lock === "update") {
|
|
@@ -197,7 +189,13 @@ export async function updateLockFile() {
|
|
|
197
189
|
|
|
198
190
|
let rootDir = getRootDir();
|
|
199
191
|
let lockFile = path.join(rootDir, "mops.lock");
|
|
192
|
+
let isNew = !fs.existsSync(lockFile);
|
|
200
193
|
fs.writeFileSync(lockFile, JSON.stringify(lockFileJson, null, 2));
|
|
194
|
+
if (isNew) {
|
|
195
|
+
console.log("mops.lock created.");
|
|
196
|
+
console.log(" Applications: commit this file.");
|
|
197
|
+
console.log(" Libraries: add mops.lock to .gitignore.");
|
|
198
|
+
}
|
|
201
199
|
}
|
|
202
200
|
|
|
203
201
|
// compare hashes of local files with hashes from the lock file
|
package/package.json
CHANGED
|
@@ -5,7 +5,8 @@ exports[`build error 1`] = `
|
|
|
5
5
|
"exitCode": 0,
|
|
6
6
|
"stderr": "",
|
|
7
7
|
"stdout": "build canister foo
|
|
8
|
-
moc-wrapper ["-c","--idl","-o",".mops/.build/foo.wasm","src/Foo.mo","--package","core",".mops/core@1.0.0/src","--release","--public-metadata","candid:service","--public-metadata","candid:args"]
|
|
8
|
+
moc-wrapper ["-c","--idl","--stable-types","-o",".mops/.build/foo.wasm","src/Foo.mo","--package","core",".mops/core@1.0.0/src","--release","--public-metadata","candid:service","--public-metadata","candid:args"]
|
|
9
|
+
Stable types written to .mops/.build/foo.most
|
|
9
10
|
Adding metadata to .mops/.build/foo.wasm
|
|
10
11
|
|
|
11
12
|
✓ Built 1 canister successfully",
|
|
@@ -34,10 +35,12 @@ exports[`build ok 1`] = `
|
|
|
34
35
|
"exitCode": 0,
|
|
35
36
|
"stderr": "",
|
|
36
37
|
"stdout": "build canister foo
|
|
37
|
-
moc-wrapper ["-c","--idl","-o",".mops/.build/foo.wasm","src/Foo.mo","--package","core",".mops/core@1.0.0/src","--release","--public-metadata","candid:service","--public-metadata","candid:args"]
|
|
38
|
+
moc-wrapper ["-c","--idl","--stable-types","-o",".mops/.build/foo.wasm","src/Foo.mo","--package","core",".mops/core@1.0.0/src","--release","--public-metadata","candid:service","--public-metadata","candid:args"]
|
|
39
|
+
Stable types written to .mops/.build/foo.most
|
|
38
40
|
Adding metadata to .mops/.build/foo.wasm
|
|
39
41
|
build canister bar
|
|
40
|
-
moc-wrapper ["-c","--idl","-o",".mops/.build/bar.wasm","src/Bar.mo","--package","core",".mops/core@1.0.0/src","--release","--incremental-gc","--public-metadata","candid:service","--public-metadata","candid:args"]
|
|
42
|
+
moc-wrapper ["-c","--idl","--stable-types","-o",".mops/.build/bar.wasm","src/Bar.mo","--package","core",".mops/core@1.0.0/src","--release","--incremental-gc","--public-metadata","candid:service","--public-metadata","candid:args"]
|
|
43
|
+
Stable types written to .mops/.build/bar.most
|
|
41
44
|
Candid compatibility check passed for canister bar
|
|
42
45
|
Adding metadata to .mops/.build/bar.wasm
|
|
43
46
|
|
|
@@ -82,6 +85,7 @@ exports[`build warns when args contain managed flags 1`] = `
|
|
|
82
85
|
"stderr": "Warning: '-o' in args for canister foo may conflict with mops build — use [build].outputDir in mops.toml or --output flag instead
|
|
83
86
|
Warning: '-c' in args for canister foo may conflict with mops build — this flag is always set by mops build
|
|
84
87
|
Warning: '--idl' in args for canister foo may conflict with mops build — this flag is always set by mops build
|
|
88
|
+
Warning: '--stable-types' in args for canister foo may conflict with mops build — this flag is always set by mops build
|
|
85
89
|
Error while compiling canister foo
|
|
86
90
|
ENOENT: no such file or directory, open '.mops/.build/foo.did'",
|
|
87
91
|
"stdout": "build canister foo",
|
package/tests/build.test.ts
CHANGED
|
@@ -50,6 +50,7 @@ describe("build", () => {
|
|
|
50
50
|
const customOut = path.join(cwd, "custom-out");
|
|
51
51
|
const customWasm = path.join(customOut, "main.wasm");
|
|
52
52
|
const customDid = path.join(customOut, "main.did");
|
|
53
|
+
const customMost = path.join(customOut, "main.most");
|
|
53
54
|
const defaultDid = path.join(cwd, ".mops/.build/main.did");
|
|
54
55
|
|
|
55
56
|
try {
|
|
@@ -57,6 +58,7 @@ describe("build", () => {
|
|
|
57
58
|
expect(result.exitCode).toBe(0);
|
|
58
59
|
expect(existsSync(customWasm)).toBe(true);
|
|
59
60
|
expect(existsSync(customDid)).toBe(true);
|
|
61
|
+
expect(existsSync(customMost)).toBe(true);
|
|
60
62
|
expect(existsSync(defaultDid)).toBe(false);
|
|
61
63
|
} finally {
|
|
62
64
|
cleanFixture(cwd, customOut);
|
|
@@ -76,6 +78,7 @@ describe("build", () => {
|
|
|
76
78
|
expect(result.exitCode).toBe(0);
|
|
77
79
|
expect(existsSync(path.join(outputDir, "foo.wasm"))).toBe(true);
|
|
78
80
|
expect(existsSync(path.join(outputDir, "foo.did"))).toBe(true);
|
|
81
|
+
expect(existsSync(path.join(outputDir, "foo.most"))).toBe(true);
|
|
79
82
|
} finally {
|
|
80
83
|
cleanFixture(cwd, outputDir);
|
|
81
84
|
}
|
|
@@ -88,7 +91,7 @@ describe("build", () => {
|
|
|
88
91
|
|
|
89
92
|
try {
|
|
90
93
|
await cliSnapshot(
|
|
91
|
-
["build", "foo", "--", "-o", "x", "-c", "--idl"],
|
|
94
|
+
["build", "foo", "--", "-o", "x", "-c", "--idl", "--stable-types"],
|
|
92
95
|
{ cwd },
|
|
93
96
|
1,
|
|
94
97
|
);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describe, expect, test } from "@jest/globals";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
2
3
|
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
3
4
|
import { tmpdir } from "node:os";
|
|
4
5
|
import path from "path";
|
|
@@ -47,6 +48,17 @@ describe("check-stable", () => {
|
|
|
47
48
|
}
|
|
48
49
|
});
|
|
49
50
|
|
|
51
|
+
test("works with relative paths in moc args (e.g. --actor-idl)", async () => {
|
|
52
|
+
const cwd = path.join(import.meta.dirname, "check-stable/actor-idl");
|
|
53
|
+
const result = await cli(["check-stable", "old.mo"], { cwd });
|
|
54
|
+
expect(result.exitCode).toBe(0);
|
|
55
|
+
expect(result.stdout).toMatch(/Stable compatibility check passed/);
|
|
56
|
+
expect(existsSync(path.join(cwd, "old.most"))).toBe(false);
|
|
57
|
+
expect(existsSync(path.join(cwd, "old.wasm"))).toBe(false);
|
|
58
|
+
expect(existsSync(path.join(cwd, "new.most"))).toBe(false);
|
|
59
|
+
expect(existsSync(path.join(cwd, "new.wasm"))).toBe(false);
|
|
60
|
+
});
|
|
61
|
+
|
|
50
62
|
test("errors when old file does not exist", async () => {
|
|
51
63
|
const cwd = path.join(import.meta.dirname, "check-stable/compatible");
|
|
52
64
|
const result = await cli(["check-stable", "nonexistent.mo"], { cwd });
|
package/tests/cli.test.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { describe, expect, test } from "@jest/globals";
|
|
1
|
+
import { describe, expect, jest, test } from "@jest/globals";
|
|
2
|
+
import { existsSync, rmSync } from "node:fs";
|
|
3
|
+
import path from "path";
|
|
2
4
|
import { cli } from "./helpers";
|
|
3
5
|
|
|
4
6
|
describe("cli", () => {
|
|
@@ -10,3 +12,63 @@ describe("cli", () => {
|
|
|
10
12
|
expect((await cli(["--help"])).stdout).toMatch(/^Usage: mops/m);
|
|
11
13
|
});
|
|
12
14
|
});
|
|
15
|
+
|
|
16
|
+
describe("install", () => {
|
|
17
|
+
jest.setTimeout(120_000);
|
|
18
|
+
|
|
19
|
+
test("creates mops.lock automatically on first install", async () => {
|
|
20
|
+
const cwd = path.join(import.meta.dirname, "install/success");
|
|
21
|
+
const lockFile = path.join(cwd, "mops.lock");
|
|
22
|
+
rmSync(lockFile, { force: true });
|
|
23
|
+
try {
|
|
24
|
+
// Unset CI so checkIntegrity uses the local default ("update")
|
|
25
|
+
const result = await cli(["install"], { cwd, env: { CI: undefined } });
|
|
26
|
+
expect(result.exitCode).toBe(0);
|
|
27
|
+
expect(existsSync(lockFile)).toBe(true);
|
|
28
|
+
expect(result.stdout).toMatch(/mops\.lock created/);
|
|
29
|
+
} finally {
|
|
30
|
+
rmSync(lockFile, { force: true });
|
|
31
|
+
rmSync(path.join(cwd, ".mops"), { recursive: true, force: true });
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("does not print 'mops.lock created' on subsequent installs", async () => {
|
|
36
|
+
const cwd = path.join(import.meta.dirname, "install/success");
|
|
37
|
+
const lockFile = path.join(cwd, "mops.lock");
|
|
38
|
+
rmSync(lockFile, { force: true });
|
|
39
|
+
try {
|
|
40
|
+
// Unset CI so checkIntegrity uses the local default ("update")
|
|
41
|
+
const first = await cli(["install"], { cwd, env: { CI: undefined } });
|
|
42
|
+
expect(first.exitCode).toBe(0);
|
|
43
|
+
expect(first.stdout).toMatch(/mops\.lock created/);
|
|
44
|
+
const result = await cli(["install"], { cwd, env: { CI: undefined } });
|
|
45
|
+
expect(result.exitCode).toBe(0);
|
|
46
|
+
expect(existsSync(lockFile)).toBe(true);
|
|
47
|
+
expect(result.stdout).not.toMatch(/mops\.lock created/);
|
|
48
|
+
} finally {
|
|
49
|
+
rmSync(lockFile, { force: true });
|
|
50
|
+
rmSync(path.join(cwd, ".mops"), { recursive: true, force: true });
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("does not create mops.lock when --lock ignore is passed", async () => {
|
|
55
|
+
const cwd = path.join(import.meta.dirname, "install/success");
|
|
56
|
+
const lockFile = path.join(cwd, "mops.lock");
|
|
57
|
+
rmSync(lockFile, { force: true });
|
|
58
|
+
try {
|
|
59
|
+
// Unset CI for consistency; --lock ignore bypasses auto-detection regardless
|
|
60
|
+
const result = await cli(["install", "--lock", "ignore"], {
|
|
61
|
+
cwd,
|
|
62
|
+
env: { CI: undefined },
|
|
63
|
+
});
|
|
64
|
+
expect(result.exitCode).toBe(0);
|
|
65
|
+
expect(existsSync(lockFile)).toBe(false);
|
|
66
|
+
} finally {
|
|
67
|
+
rmSync(lockFile, { force: true });
|
|
68
|
+
rmSync(path.join(cwd, ".mops"), { recursive: true, force: true });
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// mops add/remove/update/sync are not separately tested here because they
|
|
73
|
+
// all route through the same checkIntegrity code path tested above.
|
|
74
|
+
});
|
package/tests/helpers.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { fileURLToPath } from "url";
|
|
|
5
5
|
|
|
6
6
|
export interface CliOptions {
|
|
7
7
|
cwd?: string;
|
|
8
|
+
env?: Record<string, string | undefined>;
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
// When MOPS_TEST_GLOBAL is set, invoke the globally-installed `mops` binary
|
|
@@ -12,12 +13,12 @@ export interface CliOptions {
|
|
|
12
13
|
// code path where the binary lives outside the project tree.
|
|
13
14
|
const useGlobalBinary = Boolean(process.env.MOPS_TEST_GLOBAL);
|
|
14
15
|
|
|
15
|
-
export const cli = async (args: string[], { cwd }: CliOptions = {}) => {
|
|
16
|
+
export const cli = async (args: string[], { cwd, env }: CliOptions = {}) => {
|
|
16
17
|
const [cmd, cmdArgs] = useGlobalBinary
|
|
17
18
|
? ["mops", args]
|
|
18
19
|
: ["npm", ["run", "--silent", "mops", "--", ...args]];
|
|
19
20
|
return await execa(cmd, cmdArgs, {
|
|
20
|
-
env: { ...process.env, ...(cwd != null && { MOPS_CWD: cwd }) },
|
|
21
|
+
env: { ...process.env, ...(cwd != null && { MOPS_CWD: cwd }), ...env },
|
|
21
22
|
...(cwd != null && { cwd }),
|
|
22
23
|
stdio: "pipe",
|
|
23
24
|
reject: false,
|