ic-mops 2.3.1 → 2.4.0
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/RELEASE.md +16 -162
- package/bundle/cli.tgz +0 -0
- package/cli.ts +2 -5
- package/commands/bench-replica.ts +13 -34
- package/commands/build.ts +70 -24
- package/commands/check-stable.ts +2 -2
- package/commands/check.ts +9 -8
- package/commands/replica.ts +17 -42
- package/dist/cli.js +3 -2
- package/dist/commands/bench-replica.d.ts +3 -4
- package/dist/commands/bench-replica.js +5 -23
- package/dist/commands/build.js +47 -20
- package/dist/commands/check-stable.js +2 -2
- package/dist/commands/check.js +9 -8
- package/dist/commands/replica.d.ts +3 -4
- package/dist/commands/replica.js +9 -29
- package/dist/helpers/pocket-ic-client.d.ts +9 -0
- package/dist/helpers/pocket-ic-client.js +18 -0
- package/dist/mops.d.ts +5 -0
- package/dist/mops.js +7 -0
- package/dist/package.json +1 -1
- package/dist/release-cli.js +6 -2
- package/dist/tests/build.test.js +83 -16
- package/dist/tests/check.test.js +7 -0
- package/dist/tests/pocket-ic.test.d.ts +1 -0
- package/dist/tests/pocket-ic.test.js +12 -0
- package/dist/wasm/pkg/nodejs/wasm_bg.wasm +0 -0
- package/dist/wasm/pkg/nodejs/wasm_bg.wasm.d.ts +1 -1
- package/dist/wasm/pkg/web/wasm.d.ts +1 -1
- package/dist/wasm/pkg/web/wasm_bg.wasm +0 -0
- package/dist/wasm/pkg/web/wasm_bg.wasm.d.ts +1 -1
- package/helpers/pocket-ic-client.ts +32 -0
- package/mops.ts +8 -0
- package/package.json +1 -1
- package/release-cli.ts +7 -2
- package/tests/__snapshots__/build.test.ts.snap +12 -0
- package/tests/build/custom-output/dfx.json +8 -0
- package/tests/build/custom-output/mops.toml +8 -0
- package/tests/build/custom-output/src/Main.mo +5 -0
- package/tests/build.test.ts +92 -20
- package/tests/check/canisters-subdir/mops.toml +8 -0
- package/tests/check/canisters-subdir/src/backend/main.mo +5 -0
- package/tests/check.test.ts +11 -0
- package/tests/pocket-ic/mops.toml +3 -0
- package/tests/pocket-ic/test/hello.test.mo +3 -0
- package/tests/pocket-ic.test.ts +19 -0
- package/wasm/pkg/nodejs/wasm_bg.wasm +0 -0
- package/wasm/pkg/nodejs/wasm_bg.wasm.d.ts +1 -1
- package/wasm/pkg/web/wasm.d.ts +1 -1
- package/wasm/pkg/web/wasm_bg.wasm +0 -0
- package/wasm/pkg/web/wasm_bg.wasm.d.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## Next
|
|
4
4
|
|
|
5
|
+
## 2.4.0
|
|
6
|
+
- Support `[build].outputDir` config in `mops.toml` for custom build output directory
|
|
7
|
+
- Fix `mops build --output` CLI option being silently ignored
|
|
8
|
+
- Warn when canister `args` contain flags managed by `mops build` (e.g. `-o`, `-c`, `--idl`)
|
|
9
|
+
- Support pocket-ic versions beyond 9.x.x (fixes #410)
|
|
10
|
+
|
|
11
|
+
## 2.3.2
|
|
12
|
+
- Fix `mops check`, `mops build`, and `mops check-stable` failing to find canister entrypoints when run from a subdirectory
|
|
13
|
+
|
|
5
14
|
## 2.3.1
|
|
6
15
|
- Fix `mops build` and `mops check-candid` failing with "Wasm bindings have not been set" when installed via `npm i -g ic-mops`
|
|
7
16
|
|
package/RELEASE.md
CHANGED
|
@@ -1,37 +1,8 @@
|
|
|
1
1
|
# Mops CLI Release
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 1. Update changelog
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
brew install gnu-tar
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
Add to `~/.zshrc` or `~/.bashrc`:
|
|
12
|
-
```bash
|
|
13
|
-
export PATH="$HOMEBREW_PREFIX/opt/gnu-tar/libexec/gnubin:$PATH"
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
### Docker
|
|
17
|
-
|
|
18
|
-
Docker (or OrbStack) must be installed and running. The CLI build happens inside a Docker container (`zenvoich/mops-builder:1.1.0`) for reproducibility.
|
|
19
|
-
|
|
20
|
-
### dfx
|
|
21
|
-
|
|
22
|
-
`dfx` must be installed with the `mops` identity configured (see [Adding the `mops` identity to dfx](#adding-the-mops-identity-to-dfx)).
|
|
23
|
-
|
|
24
|
-
## Release Steps
|
|
25
|
-
|
|
26
|
-
### 1. Install dependencies
|
|
27
|
-
|
|
28
|
-
```bash
|
|
29
|
-
cd cli && bun install
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
### 2. Update changelog
|
|
33
|
-
|
|
34
|
-
Move items from the `## Next` section in `CHANGELOG.md` into a new version heading:
|
|
5
|
+
Move items from `## Next` in `CHANGELOG.md` into a new version heading:
|
|
35
6
|
|
|
36
7
|
```markdown
|
|
37
8
|
## Next
|
|
@@ -41,166 +12,49 @@ Move items from the `## Next` section in `CHANGELOG.md` into a new version headi
|
|
|
41
12
|
- Change 2
|
|
42
13
|
```
|
|
43
14
|
|
|
44
|
-
The heading must
|
|
45
|
-
|
|
46
|
-
### 3. Create a release branch and PR
|
|
47
|
-
|
|
48
|
-
Direct pushes to `main` are not allowed. Create a branch and PR:
|
|
49
|
-
|
|
50
|
-
```bash
|
|
51
|
-
git checkout -b <username>/release-X.Y.Z
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
### 4. Bump version
|
|
15
|
+
The heading must match the exact version string — the release workflow parses it to extract release notes.
|
|
55
16
|
|
|
56
|
-
|
|
17
|
+
## 2. Bump version
|
|
57
18
|
|
|
58
19
|
```bash
|
|
59
20
|
cd cli
|
|
60
|
-
npm version
|
|
21
|
+
npm version patch --no-git-tag-version # or: minor / major
|
|
61
22
|
```
|
|
62
23
|
|
|
63
|
-
|
|
24
|
+
## 3. Create a release PR
|
|
64
25
|
|
|
65
26
|
```bash
|
|
27
|
+
git checkout -b <username>/release-X.Y.Z
|
|
66
28
|
git add cli/CHANGELOG.md cli/package.json cli/package-lock.json
|
|
67
29
|
git commit -m "release: CLI vX.Y.Z"
|
|
68
30
|
git push -u origin <username>/release-X.Y.Z
|
|
69
31
|
gh pr create --title "release: CLI vX.Y.Z" --body "..."
|
|
70
32
|
```
|
|
71
33
|
|
|
72
|
-
Wait for CI to pass, then merge
|
|
73
|
-
|
|
74
|
-
### 6. Check reproducibility of the build
|
|
75
|
-
|
|
76
|
-
After the PR is merged to `main`, check the SHA256 hash from the latest [build-hash workflow](https://github.com/caffeinelabs/mops/actions/workflows/build-hash.yml) run.
|
|
77
|
-
|
|
78
|
-
**Important**: The CI hash is written to the GitHub Actions **Step Summary** — it is only visible on the **Summary tab** of the workflow run page. It does **not** appear in the downloadable logs (`gh run view --log` will not find it). You must open the run URL in a browser to see it.
|
|
79
|
-
|
|
80
|
-
Build the same version locally:
|
|
81
|
-
```bash
|
|
82
|
-
cd cli
|
|
83
|
-
MOPS_VERSION=0.0.0 ./build.sh
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
`build.sh` does the following:
|
|
87
|
-
1. Reads `COMMIT_HASH` (defaults to `git rev-parse HEAD`) and `MOPS_VERSION`
|
|
88
|
-
2. Runs `docker build` using `cli/Dockerfile` with those as build args
|
|
89
|
-
3. The Dockerfile clones the repo at that commit, runs `bun install`, sets the version, and runs `npm run build`
|
|
90
|
-
4. Prints the SHA256 hash of `cli.tgz`
|
|
91
|
-
5. Copies `cli.tgz` out of the container into `cli/bundle/cli.tgz`
|
|
92
|
-
|
|
93
|
-
Compare the locally printed hash with the one from the CI Step Summary. If they don't match, do not proceed.
|
|
94
|
-
|
|
95
|
-
**Notes on the local build output:**
|
|
96
|
-
- The "Verification failed" message at the end is **expected** — it happens because no `SHASUM` env var is passed for comparison. The important output is the `Actual shasum: <hash>` line.
|
|
34
|
+
Wait for CI to pass, then merge.
|
|
97
35
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
After the PR is merged and the build hash is verified:
|
|
36
|
+
## 4. Tag and push
|
|
101
37
|
|
|
102
38
|
```bash
|
|
103
39
|
git checkout main && git pull
|
|
104
|
-
|
|
105
|
-
|
|
40
|
+
git tag cli-vX.Y.Z
|
|
41
|
+
git push origin cli-vX.Y.Z
|
|
106
42
|
```
|
|
107
43
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
Run from the **repo root** (not `cli/`), with Docker running:
|
|
44
|
+
This triggers the [`release.yml`](../.github/workflows/release.yml) workflow which builds, publishes to npm, creates a GitHub Release, deploys canisters (`cli.mops.one` and `docs.mops.one`), and opens a PR with on-chain release artifacts.
|
|
111
45
|
|
|
112
|
-
|
|
113
|
-
npm run release-cli
|
|
114
|
-
```
|
|
46
|
+
Monitor at [Actions → Release CLI](https://github.com/caffeinelabs/mops/actions/workflows/release.yml).
|
|
115
47
|
|
|
116
|
-
|
|
117
|
-
1. Calls `./build.sh` — full Docker build with the real version from `cli/package.json`
|
|
118
|
-
2. Extracts release notes from `CHANGELOG.md` for that version
|
|
119
|
-
3. Computes SHA256 of `bundle/cli.tgz`
|
|
120
|
-
4. Copies the tarball to `cli-releases/versions/<version>.tgz`
|
|
121
|
-
5. Creates tag copies: `latest.tgz` and `<major>.tgz`
|
|
122
|
-
6. Updates `cli-releases/tags/latest` to the new version
|
|
123
|
-
7. Updates `cli-releases/releases.json` with metadata (timestamp, size, hash, commit hash, download URL, release notes)
|
|
48
|
+
## 5. Merge artifacts PR
|
|
124
49
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
```bash
|
|
128
|
-
dfx deploy --network ic --no-wallet cli --identity mops
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
This deploys the `cli-releases` canister (serving `cli.mops.one`) to the Internet Computer mainnet.
|
|
132
|
-
|
|
133
|
-
### 10. Deploy the docs canister
|
|
134
|
-
|
|
135
|
-
```bash
|
|
136
|
-
dfx deploy --network ic --no-wallet docs --identity mops
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
This builds the Docusaurus site (`docs/`) and deploys the `docs` assets canister (serving `docs.mops.one`). Docs are not auto-deployed, so this step ensures any documentation changes from the release are published.
|
|
140
|
-
|
|
141
|
-
### 11. Commit and push release artifacts
|
|
142
|
-
|
|
143
|
-
Step 8 generates files in `cli-releases/` that must be committed and pushed:
|
|
144
|
-
|
|
145
|
-
```bash
|
|
146
|
-
git add cli-releases/
|
|
147
|
-
git commit -m "cli-releases: v<version> artifacts"
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
Since direct pushes to `main` are not allowed, create a branch and PR:
|
|
151
|
-
|
|
152
|
-
```bash
|
|
153
|
-
git checkout -b <username>/release-X.Y.Z-artifacts
|
|
154
|
-
git push -u origin <username>/release-X.Y.Z-artifacts
|
|
155
|
-
gh pr create --title "cli-releases: vX.Y.Z artifacts" --body "Release artifacts generated by \`npm run release-cli\` for CLI vX.Y.Z."
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
Merge this PR after approval.
|
|
50
|
+
After the workflow completes, merge the `cli-releases: vX.Y.Z artifacts` PR.
|
|
159
51
|
|
|
160
52
|
## Verify build
|
|
161
53
|
|
|
162
|
-
Anyone can verify a released version by rebuilding from source
|
|
54
|
+
Anyone can verify a released version by rebuilding from source. Instructions are included in each [GitHub Release](https://github.com/caffeinelabs/mops/releases).
|
|
163
55
|
|
|
164
56
|
```bash
|
|
165
57
|
cd cli
|
|
166
58
|
docker build . --build-arg COMMIT_HASH=<commit_hash> --build-arg MOPS_VERSION=<mops_version> -t mops
|
|
167
59
|
docker run --rm --env SHASUM=<build_hash> mops
|
|
168
60
|
```
|
|
169
|
-
|
|
170
|
-
## Adding the `mops` identity to dfx
|
|
171
|
-
|
|
172
|
-
Check if the identity already exists:
|
|
173
|
-
```bash
|
|
174
|
-
dfx identity list
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
If `mops` appears, verify it's the correct one:
|
|
178
|
-
```bash
|
|
179
|
-
dfx identity get-principal --identity mops
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
If it doesn't exist, import it from the PEM file stored in Bitwarden (`Mops identity (canisters and packages)`):
|
|
183
|
-
|
|
184
|
-
1. Save the PEM key to a temporary file:
|
|
185
|
-
```bash
|
|
186
|
-
cat > /tmp/mops-identity.pem << 'EOF'
|
|
187
|
-
-----BEGIN EC PRIVATE KEY-----
|
|
188
|
-
<paste key from Bitwarden>
|
|
189
|
-
-----END EC PRIVATE KEY-----
|
|
190
|
-
EOF
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
2. Import into dfx:
|
|
194
|
-
```bash
|
|
195
|
-
dfx identity import mops /tmp/mops-identity.pem
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
3. Delete the temporary file:
|
|
199
|
-
```bash
|
|
200
|
-
rm /tmp/mops-identity.pem
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
4. Verify:
|
|
204
|
-
```bash
|
|
205
|
-
dfx identity get-principal --identity mops
|
|
206
|
-
```
|
package/bundle/cli.tgz
ADDED
|
Binary file
|
package/cli.ts
CHANGED
|
@@ -301,11 +301,7 @@ program
|
|
|
301
301
|
.command("build [canisters...]")
|
|
302
302
|
.description("Build a canister")
|
|
303
303
|
.addOption(new Option("--verbose", "Verbose console output"))
|
|
304
|
-
.addOption(
|
|
305
|
-
new Option("--output, -o <output>", "Output directory").default(
|
|
306
|
-
DEFAULT_BUILD_OUTPUT_DIR,
|
|
307
|
-
),
|
|
308
|
-
)
|
|
304
|
+
.addOption(new Option("--output, -o <output>", "Output directory"))
|
|
309
305
|
.allowUnknownOption(true) // TODO: restrict unknown before "--"
|
|
310
306
|
.action(async (canisters, options) => {
|
|
311
307
|
checkConfigFile(true);
|
|
@@ -317,6 +313,7 @@ program
|
|
|
317
313
|
});
|
|
318
314
|
await build(args.length ? args : undefined, {
|
|
319
315
|
...options,
|
|
316
|
+
outputDir: options.output,
|
|
320
317
|
extraArgs,
|
|
321
318
|
});
|
|
322
319
|
});
|
|
@@ -3,12 +3,13 @@ import { execSync } from "node:child_process";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import { execaCommand } from "execa";
|
|
6
|
-
import {
|
|
6
|
+
import { getRootDir } from "../mops.js";
|
|
7
7
|
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
type AnyPocketIcServer,
|
|
9
|
+
type AnyPocketIc,
|
|
10
|
+
type AnySetupCanister,
|
|
11
|
+
startPocketIc,
|
|
12
|
+
} from "../helpers/pocket-ic-client.js";
|
|
12
13
|
import { createActor, idlFactory } from "../declarations/bench/index.js";
|
|
13
14
|
import { toolchain } from "./toolchain/index.js";
|
|
14
15
|
import { getDfxVersion } from "../helpers/get-dfx-version.js";
|
|
@@ -18,8 +19,8 @@ export class BenchReplica {
|
|
|
18
19
|
verbose = false;
|
|
19
20
|
canisters: Record<string, { cwd: string; canisterId: string; actor: any }> =
|
|
20
21
|
{};
|
|
21
|
-
pocketIcServer?:
|
|
22
|
-
pocketIc?:
|
|
22
|
+
pocketIcServer?: AnyPocketIcServer;
|
|
23
|
+
pocketIc?: AnyPocketIc;
|
|
23
24
|
|
|
24
25
|
constructor(type: "dfx" | "pocket-ic" | "dfx-pocket-ic", verbose = false) {
|
|
25
26
|
this.type = type;
|
|
@@ -49,30 +50,10 @@ export class BenchReplica {
|
|
|
49
50
|
);
|
|
50
51
|
} else {
|
|
51
52
|
let pocketIcBin = await toolchain.bin("pocket-ic");
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
) {
|
|
57
|
-
console.error(
|
|
58
|
-
"Current Mops CLI only supports pocket-ic 9.x.x and 4.0.0",
|
|
59
|
-
);
|
|
60
|
-
process.exit(1);
|
|
61
|
-
}
|
|
62
|
-
// pocket-ic 9.x.x
|
|
63
|
-
if (config.toolchain?.["pocket-ic"]?.startsWith("9.")) {
|
|
64
|
-
this.pocketIcServer = await PocketIcServerMops.start({
|
|
65
|
-
binPath: pocketIcBin,
|
|
66
|
-
});
|
|
67
|
-
this.pocketIc = await PocketIcMops.create(this.pocketIcServer.getUrl());
|
|
68
|
-
}
|
|
69
|
-
// pocket-ic 4.0.0
|
|
70
|
-
else {
|
|
71
|
-
this.pocketIcServer = await PocketIcServer.start({
|
|
72
|
-
binPath: pocketIcBin,
|
|
73
|
-
});
|
|
74
|
-
this.pocketIc = await PocketIc.create(this.pocketIcServer.getUrl());
|
|
75
|
-
}
|
|
53
|
+
|
|
54
|
+
let pic = await startPocketIc({ binPath: pocketIcBin });
|
|
55
|
+
this.pocketIcServer = pic.server;
|
|
56
|
+
this.pocketIc = pic.client;
|
|
76
57
|
}
|
|
77
58
|
}
|
|
78
59
|
|
|
@@ -105,10 +86,8 @@ export class BenchReplica {
|
|
|
105
86
|
});
|
|
106
87
|
this.canisters[name] = { cwd, canisterId, actor };
|
|
107
88
|
} else if (this.pocketIc) {
|
|
108
|
-
type PocketIcSetupCanister = PocketIcMops["setupCanister"] &
|
|
109
|
-
PocketIc["setupCanister"];
|
|
110
89
|
let { canisterId, actor } = await (
|
|
111
|
-
this.pocketIc.setupCanister as
|
|
90
|
+
this.pocketIc.setupCanister as AnySetupCanister
|
|
112
91
|
)({ idlFactory, wasm });
|
|
113
92
|
this.canisters[name] = {
|
|
114
93
|
cwd,
|
package/commands/build.ts
CHANGED
|
@@ -6,8 +6,9 @@ import { join } from "node:path";
|
|
|
6
6
|
import { cliError } from "../error.js";
|
|
7
7
|
import { isCandidCompatible } from "../helpers/is-candid-compatible.js";
|
|
8
8
|
import { resolveCanisterConfigs } from "../helpers/resolve-canisters.js";
|
|
9
|
+
import { CanisterConfig, Config } from "../types.js";
|
|
9
10
|
import { CustomSection, getWasmBindings } from "../wasm.js";
|
|
10
|
-
import { getGlobalMocArgs, readConfig } from "../mops.js";
|
|
11
|
+
import { getGlobalMocArgs, readConfig, resolveConfigPath } from "../mops.js";
|
|
11
12
|
import { sourcesArgs } from "./sources.js";
|
|
12
13
|
import { toolchain } from "./toolchain/index.js";
|
|
13
14
|
|
|
@@ -27,9 +28,13 @@ export async function build(
|
|
|
27
28
|
cliError("No canisters specified to build");
|
|
28
29
|
}
|
|
29
30
|
|
|
30
|
-
let outputDir = options.outputDir ?? DEFAULT_BUILD_OUTPUT_DIR;
|
|
31
|
-
let mocPath = await toolchain.bin("moc", { fallback: true });
|
|
32
31
|
let config = readConfig();
|
|
32
|
+
let configOutputDir = config.build?.outputDir
|
|
33
|
+
? resolveConfigPath(config.build.outputDir)
|
|
34
|
+
: undefined;
|
|
35
|
+
let outputDir =
|
|
36
|
+
options.outputDir ?? configOutputDir ?? DEFAULT_BUILD_OUTPUT_DIR;
|
|
37
|
+
let mocPath = await toolchain.bin("moc", { fallback: true });
|
|
33
38
|
let canisters = resolveCanisterConfigs(config);
|
|
34
39
|
if (!Object.keys(canisters).length) {
|
|
35
40
|
cliError(`No Motoko canisters found in mops.toml configuration`);
|
|
@@ -67,6 +72,7 @@ export async function build(
|
|
|
67
72
|
if (!motokoPath) {
|
|
68
73
|
cliError(`No main file is specified for canister ${canisterName}`);
|
|
69
74
|
}
|
|
75
|
+
motokoPath = resolveConfigPath(motokoPath);
|
|
70
76
|
const wasmPath = join(outputDir, `${canisterName}.wasm`);
|
|
71
77
|
let args = [
|
|
72
78
|
"-c",
|
|
@@ -77,23 +83,10 @@ export async function build(
|
|
|
77
83
|
...(await sourcesArgs()).flat(),
|
|
78
84
|
...getGlobalMocArgs(config),
|
|
79
85
|
];
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
args.push(...config.build.args);
|
|
87
|
-
}
|
|
88
|
-
if (canister.args) {
|
|
89
|
-
if (typeof canister.args === "string") {
|
|
90
|
-
cliError(
|
|
91
|
-
`Canister config 'args' should be an array of strings for canister ${canisterName}`,
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
args.push(...canister.args);
|
|
95
|
-
}
|
|
96
|
-
args.push(...(options.extraArgs ?? []));
|
|
86
|
+
args.push(
|
|
87
|
+
...collectExtraArgs(config, canister, canisterName, options.extraArgs),
|
|
88
|
+
);
|
|
89
|
+
|
|
97
90
|
const isPublicCandid = true; // always true for now to reduce corner cases
|
|
98
91
|
const candidVisibility = isPublicCandid ? "icp:public" : "icp:private";
|
|
99
92
|
if (isPublicCandid) {
|
|
@@ -129,13 +122,15 @@ export async function build(
|
|
|
129
122
|
}
|
|
130
123
|
|
|
131
124
|
const generatedDidPath = join(outputDir, `${canisterName}.did`);
|
|
132
|
-
|
|
133
|
-
|
|
125
|
+
const resolvedCandidPath = canister.candid
|
|
126
|
+
? resolveConfigPath(canister.candid)
|
|
127
|
+
: null;
|
|
134
128
|
|
|
129
|
+
if (resolvedCandidPath) {
|
|
135
130
|
try {
|
|
136
131
|
const compatible = await isCandidCompatible(
|
|
137
132
|
generatedDidPath,
|
|
138
|
-
|
|
133
|
+
resolvedCandidPath,
|
|
139
134
|
);
|
|
140
135
|
|
|
141
136
|
if (!compatible) {
|
|
@@ -160,7 +155,7 @@ export async function build(
|
|
|
160
155
|
|
|
161
156
|
options.verbose &&
|
|
162
157
|
console.log(chalk.gray(`Adding metadata to ${wasmPath}`));
|
|
163
|
-
const candidPath =
|
|
158
|
+
const candidPath = resolvedCandidPath ?? generatedDidPath;
|
|
164
159
|
const candidText = await readFile(candidPath, "utf-8");
|
|
165
160
|
const customSections: CustomSection[] = [
|
|
166
161
|
{ name: `${candidVisibility} candid:service`, data: candidText },
|
|
@@ -193,3 +188,54 @@ export async function build(
|
|
|
193
188
|
),
|
|
194
189
|
);
|
|
195
190
|
}
|
|
191
|
+
|
|
192
|
+
const managedFlags: Record<string, string> = {
|
|
193
|
+
"-o": "use [build].outputDir in mops.toml or --output flag instead",
|
|
194
|
+
"-c": "this flag is always set by mops build",
|
|
195
|
+
"--idl": "this flag is always set by mops build",
|
|
196
|
+
"--public-metadata": "this flag is managed by mops build",
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
function collectExtraArgs(
|
|
200
|
+
config: Config,
|
|
201
|
+
canister: CanisterConfig,
|
|
202
|
+
canisterName: string,
|
|
203
|
+
extraArgs?: string[],
|
|
204
|
+
): string[] {
|
|
205
|
+
const args: string[] = [];
|
|
206
|
+
|
|
207
|
+
if (config.build?.args) {
|
|
208
|
+
if (typeof config.build.args === "string") {
|
|
209
|
+
cliError(
|
|
210
|
+
`[build] config 'args' should be an array of strings in mops.toml config file`,
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
args.push(...config.build.args);
|
|
214
|
+
}
|
|
215
|
+
if (canister.args) {
|
|
216
|
+
if (typeof canister.args === "string") {
|
|
217
|
+
cliError(
|
|
218
|
+
`Canister config 'args' should be an array of strings for canister ${canisterName}`,
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
args.push(...canister.args);
|
|
222
|
+
}
|
|
223
|
+
if (extraArgs) {
|
|
224
|
+
args.push(...extraArgs);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const warned = new Set<string>();
|
|
228
|
+
for (const arg of args) {
|
|
229
|
+
const hint = managedFlags[arg];
|
|
230
|
+
if (hint && !warned.has(arg)) {
|
|
231
|
+
warned.add(arg);
|
|
232
|
+
console.warn(
|
|
233
|
+
chalk.yellow(
|
|
234
|
+
`Warning: '${arg}' in args for canister ${canisterName} may conflict with mops build — ${hint}`,
|
|
235
|
+
),
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return args;
|
|
241
|
+
}
|
package/commands/check-stable.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { rename, rm } from "node:fs/promises";
|
|
|
4
4
|
import chalk from "chalk";
|
|
5
5
|
import { execa } from "execa";
|
|
6
6
|
import { cliError } from "../error.js";
|
|
7
|
-
import { getGlobalMocArgs, readConfig } from "../mops.js";
|
|
7
|
+
import { getGlobalMocArgs, readConfig, resolveConfigPath } from "../mops.js";
|
|
8
8
|
import { resolveSingleCanister } from "../helpers/resolve-canisters.js";
|
|
9
9
|
import { sourcesArgs } from "./sources.js";
|
|
10
10
|
import { toolchain } from "./toolchain/index.js";
|
|
@@ -33,7 +33,7 @@ export async function checkStable(
|
|
|
33
33
|
|
|
34
34
|
await runStableCheck({
|
|
35
35
|
oldFile,
|
|
36
|
-
canisterMain: canister.main,
|
|
36
|
+
canisterMain: resolveConfigPath(canister.main),
|
|
37
37
|
canisterName: name,
|
|
38
38
|
mocPath,
|
|
39
39
|
globalMocArgs,
|
package/commands/check.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import path from "node:path";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import { execa } from "execa";
|
|
5
5
|
import { cliError } from "../error.js";
|
|
6
|
-
import { getGlobalMocArgs, readConfig } from "../mops.js";
|
|
6
|
+
import { getGlobalMocArgs, readConfig, resolveConfigPath } from "../mops.js";
|
|
7
7
|
import { autofixMotoko } from "../helpers/autofix-motoko.js";
|
|
8
8
|
import { getMocSemVer } from "../helpers/get-moc-version.js";
|
|
9
9
|
import {
|
|
@@ -36,7 +36,7 @@ export async function check(
|
|
|
36
36
|
const config = readConfig();
|
|
37
37
|
|
|
38
38
|
if (fileList.length === 0) {
|
|
39
|
-
fileList = resolveCanisterEntrypoints(config);
|
|
39
|
+
fileList = resolveCanisterEntrypoints(config).map(resolveConfigPath);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
if (fileList.length === 0) {
|
|
@@ -86,7 +86,7 @@ export async function check(
|
|
|
86
86
|
for (const [file, codes] of fixResult.fixedFiles) {
|
|
87
87
|
const unique = [...new Set(codes)].sort();
|
|
88
88
|
const n = codes.length;
|
|
89
|
-
const rel = relative(process.cwd(), file);
|
|
89
|
+
const rel = path.relative(process.cwd(), file);
|
|
90
90
|
console.log(
|
|
91
91
|
chalk.green(
|
|
92
92
|
`Fixed ${rel} (${n} ${n === 1 ? "fix" : "fixes"}: ${unique.join(", ")})`,
|
|
@@ -144,12 +144,13 @@ export async function check(
|
|
|
144
144
|
cliError(`No main file specified for canister '${name}' in mops.toml`);
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
|
|
147
|
+
const stablePath = resolveConfigPath(stableConfig.path);
|
|
148
|
+
if (!existsSync(stablePath)) {
|
|
148
149
|
if (stableConfig.skipIfMissing) {
|
|
149
150
|
continue;
|
|
150
151
|
}
|
|
151
152
|
cliError(
|
|
152
|
-
`Deployed file not found: ${
|
|
153
|
+
`Deployed file not found: ${stablePath} (canister '${name}')\n` +
|
|
153
154
|
"Set skipIfMissing = true in [canisters." +
|
|
154
155
|
name +
|
|
155
156
|
".check-stable] to skip this check when the file is missing.",
|
|
@@ -157,8 +158,8 @@ export async function check(
|
|
|
157
158
|
}
|
|
158
159
|
|
|
159
160
|
await runStableCheck({
|
|
160
|
-
oldFile:
|
|
161
|
-
canisterMain: canister.main,
|
|
161
|
+
oldFile: stablePath,
|
|
162
|
+
canisterMain: resolveConfigPath(canister.main),
|
|
162
163
|
canisterName: name,
|
|
163
164
|
mocPath,
|
|
164
165
|
globalMocArgs,
|
package/commands/replica.ts
CHANGED
|
@@ -11,14 +11,14 @@ import { spawn as spawnAsync } from "promisify-child-process";
|
|
|
11
11
|
|
|
12
12
|
import { IDL } from "@icp-sdk/core/candid";
|
|
13
13
|
import { Actor, HttpAgent } from "@icp-sdk/core/agent";
|
|
14
|
-
import { PocketIc, PocketIcServer } from "pic-ic";
|
|
15
|
-
import {
|
|
16
|
-
PocketIc as PocketIcMops,
|
|
17
|
-
PocketIcServer as PocketIcServerMops,
|
|
18
|
-
} from "pic-js-mops";
|
|
19
14
|
import chalk from "chalk";
|
|
20
15
|
|
|
21
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
type AnyPocketIcServer,
|
|
18
|
+
type AnyPocketIc,
|
|
19
|
+
type AnySetupCanister,
|
|
20
|
+
startPocketIc,
|
|
21
|
+
} from "../helpers/pocket-ic-client.js";
|
|
22
22
|
import { toolchain } from "./toolchain/index.js";
|
|
23
23
|
import { getDfxVersion } from "../helpers/get-dfx-version.js";
|
|
24
24
|
|
|
@@ -36,8 +36,8 @@ export class Replica {
|
|
|
36
36
|
string,
|
|
37
37
|
{ cwd: string; canisterId: string; actor: any; stream: PassThrough }
|
|
38
38
|
> = {};
|
|
39
|
-
pocketIcServer?:
|
|
40
|
-
pocketIc?:
|
|
39
|
+
pocketIcServer?: AnyPocketIcServer;
|
|
40
|
+
pocketIc?: AnyPocketIc;
|
|
41
41
|
dfxProcess?: ChildProcessWithoutNullStreams;
|
|
42
42
|
dir: string = ""; // absolute path (/.../.mops/.test/)
|
|
43
43
|
ttl = 60;
|
|
@@ -114,37 +114,14 @@ export class Replica {
|
|
|
114
114
|
} else {
|
|
115
115
|
let pocketIcBin = await toolchain.bin("pocket-ic");
|
|
116
116
|
|
|
117
|
-
let
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
process.exit(1);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// pocket-ic 9.x.x
|
|
129
|
-
if (config.toolchain?.["pocket-ic"]?.startsWith("9.")) {
|
|
130
|
-
this.pocketIcServer = await PocketIcServerMops.start({
|
|
131
|
-
showRuntimeLogs: false,
|
|
132
|
-
showCanisterLogs: false,
|
|
133
|
-
binPath: pocketIcBin,
|
|
134
|
-
ttl: this.ttl,
|
|
135
|
-
});
|
|
136
|
-
this.pocketIc = await PocketIcMops.create(this.pocketIcServer.getUrl());
|
|
137
|
-
}
|
|
138
|
-
// pocket-ic 4.0.0
|
|
139
|
-
else {
|
|
140
|
-
this.pocketIcServer = await PocketIcServer.start({
|
|
141
|
-
showRuntimeLogs: false,
|
|
142
|
-
showCanisterLogs: false,
|
|
143
|
-
binPath: pocketIcBin,
|
|
144
|
-
ttl: this.ttl,
|
|
145
|
-
});
|
|
146
|
-
this.pocketIc = await PocketIc.create(this.pocketIcServer.getUrl());
|
|
147
|
-
}
|
|
117
|
+
let pic = await startPocketIc({
|
|
118
|
+
binPath: pocketIcBin,
|
|
119
|
+
showRuntimeLogs: false,
|
|
120
|
+
showCanisterLogs: false,
|
|
121
|
+
ttl: this.ttl,
|
|
122
|
+
});
|
|
123
|
+
this.pocketIcServer = pic.server;
|
|
124
|
+
this.pocketIc = pic.client;
|
|
148
125
|
|
|
149
126
|
// process canister logs
|
|
150
127
|
this._attachCanisterLogHandler(
|
|
@@ -305,10 +282,8 @@ export class Replica {
|
|
|
305
282
|
stream: new PassThrough(),
|
|
306
283
|
};
|
|
307
284
|
} else if (this.pocketIc) {
|
|
308
|
-
type PocketIcSetupCanister = PocketIcMops["setupCanister"] &
|
|
309
|
-
PocketIc["setupCanister"];
|
|
310
285
|
let { canisterId, actor } = await (
|
|
311
|
-
this.pocketIc.setupCanister as
|
|
286
|
+
this.pocketIc.setupCanister as AnySetupCanister
|
|
312
287
|
)({
|
|
313
288
|
wasm,
|
|
314
289
|
idlFactory,
|
package/dist/cli.js
CHANGED
|
@@ -7,7 +7,7 @@ import { getNetwork } from "./api/network.js";
|
|
|
7
7
|
import { cacheSize, cleanCache, show } from "./cache.js";
|
|
8
8
|
import { add } from "./commands/add.js";
|
|
9
9
|
import { bench } from "./commands/bench.js";
|
|
10
|
-
import { build
|
|
10
|
+
import { build } from "./commands/build.js";
|
|
11
11
|
import { bump } from "./commands/bump.js";
|
|
12
12
|
import { check } from "./commands/check.js";
|
|
13
13
|
import { checkCandid } from "./commands/check-candid.js";
|
|
@@ -236,7 +236,7 @@ program
|
|
|
236
236
|
.command("build [canisters...]")
|
|
237
237
|
.description("Build a canister")
|
|
238
238
|
.addOption(new Option("--verbose", "Verbose console output"))
|
|
239
|
-
.addOption(new Option("--output, -o <output>", "Output directory")
|
|
239
|
+
.addOption(new Option("--output, -o <output>", "Output directory"))
|
|
240
240
|
.allowUnknownOption(true) // TODO: restrict unknown before "--"
|
|
241
241
|
.action(async (canisters, options) => {
|
|
242
242
|
checkConfigFile(true);
|
|
@@ -248,6 +248,7 @@ program
|
|
|
248
248
|
});
|
|
249
249
|
await build(args.length ? args : undefined, {
|
|
250
250
|
...options,
|
|
251
|
+
outputDir: options.output,
|
|
251
252
|
extraArgs,
|
|
252
253
|
});
|
|
253
254
|
});
|