@deeeed/metamask-harness 0.14.7 → 0.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 +87 -6
- package/adapters/core/cleanup.sh +0 -0
- package/adapters/core/inject.sh +0 -0
- package/adapters/extension/cleanup.mjs +0 -0
- package/adapters/extension/ensure-browser.sh +4 -2
- package/adapters/extension/inject.mjs +0 -0
- package/adapters/extension/launch-browser.cjs +73 -34
- package/adapters/extension/launch.sh +0 -0
- package/adapters/extension/live.sh +21 -10
- package/adapters/extension/readiness.mjs +0 -0
- package/adapters/extension/reattach.sh +3 -3
- package/adapters/extension/refresh-build.sh +0 -0
- package/adapters/extension/seed-fixture.sh +0 -0
- package/adapters/extension/sidepanel-toggle.sh +10 -4
- package/adapters/extension/snapshot-dist.sh +0 -0
- package/adapters/extension/start-watch.sh +2 -2
- package/adapters/extension/stop-viewers.sh +0 -0
- package/adapters/extension/verify.sh +19 -2
- package/adapters/extension/wallet-fixture-state.cjs +14 -1
- package/adapters/manifest.json +19 -3
- package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +0 -0
- package/adapters/mobile/bridge-runtime/setup-wallet.sh +6 -4
- package/adapters/mobile/cleanup.sh +0 -0
- package/adapters/mobile/inject.sh +0 -0
- package/adapters/mobile/launch-metro.cjs +74 -0
- package/adapters/mobile/lib/metro-listener.sh +0 -0
- package/adapters/mobile/lib/tmux-viewer.sh +4 -4
- package/adapters/mobile/open-device.sh +3 -1
- package/adapters/mobile/prewarm-bundle.sh +0 -0
- package/adapters/mobile/start-metro.sh +12 -18
- package/adapters/mobile/stop-metro.sh +1 -0
- package/adapters/mobile/verify.sh +5 -5
- package/adapters/mobile/wait-for-bridge.sh +0 -0
- package/adapters/mobile/yarn-setup.sh +0 -0
- package/adapters/shared/activate-repo-node.sh +0 -0
- package/adapters/shared/activate-repo-ruby.sh +0 -0
- package/adapters/shared/cli-ux.sh +0 -0
- package/adapters/shared/ensure-runner-deps.sh +0 -0
- package/adapters/shared/harness-path.sh +0 -0
- package/adapters/shared/hash-helpers.sh +0 -0
- package/adapters/shared/install-repo-deps.sh +29 -0
- package/adapters/shared/json-field.sh +0 -0
- package/adapters/shared/open-debug.mjs +35 -4
- package/adapters/shared/open-log-window.sh +1 -1
- package/adapters/shared/reap-checkout-metros.sh +0 -0
- package/adapters/shared/resolve-farmslot-ports.mjs +0 -0
- package/adapters/shared/resolve-farmslot-ports.sh +0 -0
- package/adapters/shared/resolve-slot-ports-core.mjs +72 -1
- package/adapters/shared/resolve-slot-ports.mjs +0 -0
- package/adapters/shared/resolve-slot-ports.sh +21 -17
- package/adapters/shared/sync-wallet-fixture.sh +0 -0
- package/adapters/shared/tmux-session.sh +0 -5
- package/adapters/shared/tmux-viewer.sh +7 -2
- package/bin/mm-harness +7 -0
- package/dist/adapters/core/surface.js +3 -0
- package/dist/adapters/extension/ensure-ready.js +0 -7
- package/dist/adapters/extension/runtime-decision.js +93 -3
- package/dist/adapters/extension/surface.js +17 -6
- package/dist/adapters/mobile/metro-env.js +67 -0
- package/dist/adapters/mobile/perps-env.js +101 -0
- package/dist/adapters/mobile/prepare.js +32 -14
- package/dist/adapters/mobile/runtime-decision.js +21 -2
- package/dist/adapters/mobile/source-freshness.js +116 -0
- package/dist/adapters/mobile/surface.js +3 -0
- package/dist/adapters/resolve-slot-ports.js +4 -0
- package/dist/adapters/slot-ports.js +131 -49
- package/dist/adapters.js +7 -2
- package/dist/checkout-lock.js +72 -0
- package/dist/cli.js +2 -0
- package/dist/commands/call.js +91 -54
- package/dist/commands/check.js +266 -46
- package/dist/commands/checklist.js +136 -0
- package/dist/commands/debug.js +21 -2
- package/dist/commands/doctor.js +196 -13
- package/dist/commands/fixtures.js +177 -42
- package/dist/commands/launch/extension.js +6 -1
- package/dist/commands/launch/index.js +54 -22
- package/dist/commands/logs.js +24 -4
- package/dist/commands/run-engine.js +223 -7
- package/dist/commands/run.js +67 -50
- package/dist/commands/shared.js +86 -5
- package/dist/commands/stop.js +1 -2
- package/dist/doctor.js +39 -17
- package/dist/harness.js +5 -2
- package/dist/heal-bounds.js +1 -1
- package/dist/mm-harness-cli.js +37 -9
- package/dist/runner.js +35 -5
- package/dist/runtime-context.js +228 -0
- package/docs/CLI-SPEC.md +15 -11
- package/docs/MENTAL-MODEL.md +6 -6
- package/library/actions/extension/perps/perps.mjs +29 -8
- package/library/actions/extension/platform/cdp.mjs +128 -28
- package/library/actions/extension/ui/navigate.mjs +1 -1
- package/library/actions/mobile/perps/perps.mjs +13 -1
- package/library/actions/mobile/platform/bridge.mjs +302 -29
- package/library/actions/mobile/wallet/ensure_unlocked.mjs +7 -2
- package/library/manifests/extension.action-manifest.json +32 -12
- package/library/manifests/mobile.action-manifest.json +25 -3
- package/package.json +5 -5
- package/scripts/completions.sh +7 -4
- package/scripts/install-completions.sh +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { readRuntimeContextField, resolveRuntimeContextPath } from "../harness.js";
|
|
5
4
|
import { recipeRuntimeDir } from "../paths.js";
|
|
6
5
|
import {
|
|
7
6
|
resolveDefaultExtensionPorts,
|
|
8
|
-
|
|
7
|
+
resolveExtensionRuntimeContext,
|
|
9
8
|
resolveMobileRuntimeContext,
|
|
9
|
+
resolveSlotPortsByRepo,
|
|
10
10
|
resolveMobileSlotDefaults
|
|
11
11
|
} from "./resolve-slot-ports.js";
|
|
12
12
|
function applyKVLines(output, overwrite) {
|
|
@@ -54,51 +54,34 @@ function resolveMobileSlotPorts(target) {
|
|
|
54
54
|
if (defOut?.trim()) applyKVLines(defOut, false);
|
|
55
55
|
}
|
|
56
56
|
function resolveExtensionSlotPorts(target) {
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
if (
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if (dev) {
|
|
65
|
-
process.env["WATCHER_PORT"] = dev;
|
|
66
|
-
process.env["RECIPE_WATCHER_PORT"] = dev;
|
|
67
|
-
}
|
|
68
|
-
if (!process.env["CDP_PORT"] || !process.env["WATCHER_PORT"]) {
|
|
69
|
-
const poolOut = resolveSlotPortsByRepo(target);
|
|
70
|
-
if (poolOut?.trim()) applyKVLines(poolOut, false);
|
|
71
|
-
const defOut = resolveDefaultExtensionPorts(target);
|
|
72
|
-
if (defOut?.trim()) applyKVLines(defOut, false);
|
|
73
|
-
}
|
|
57
|
+
const resolved = path.resolve(target);
|
|
58
|
+
const ctxOut = resolveExtensionRuntimeContext(resolved);
|
|
59
|
+
if (ctxOut?.trim()) applyKVLines(ctxOut, true);
|
|
60
|
+
const poolOut = resolveSlotPortsByRepo(resolved);
|
|
61
|
+
if (poolOut?.trim()) applyKVLines(poolOut, false);
|
|
62
|
+
const defOut = resolveDefaultExtensionPorts(resolved);
|
|
63
|
+
if (defOut?.trim()) applyKVLines(defOut, false);
|
|
74
64
|
}
|
|
75
|
-
function
|
|
76
|
-
const
|
|
65
|
+
function extensionWatcherPids(target) {
|
|
66
|
+
const resolved = path.resolve(target);
|
|
67
|
+
const runtimeAbs = path.join(resolved, recipeRuntimeDir());
|
|
77
68
|
const webpackPidFile = path.join(runtimeAbs, "recipe-harness-webpack.pid");
|
|
78
|
-
|
|
69
|
+
const ownedPids = /* @__PURE__ */ new Set();
|
|
79
70
|
try {
|
|
80
71
|
const pid = fs.readFileSync(webpackPidFile, "utf8").trim();
|
|
81
|
-
if (/^\d+$/u.test(pid))
|
|
82
|
-
try {
|
|
83
|
-
process.kill(Number(pid), "SIGTERM");
|
|
84
|
-
signalled += 1;
|
|
85
|
-
} catch {
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
fs.rmSync(webpackPidFile, { force: true });
|
|
72
|
+
if (/^\d+$/u.test(pid) && processAlive(Number(pid))) ownedPids.add(Number(pid));
|
|
89
73
|
} catch {
|
|
90
74
|
}
|
|
91
75
|
try {
|
|
92
76
|
const psOut = execFileSync("ps", ["-axo", "pid=,command="], { encoding: "utf8" });
|
|
93
|
-
const orphanPids = [];
|
|
94
77
|
for (const line of psOut.split("\n")) {
|
|
95
78
|
const match = /^\s*(\d+)\s+(.*)$/u.exec(line);
|
|
96
79
|
if (!match) continue;
|
|
97
80
|
const [, pidStr, cmd] = match;
|
|
98
81
|
const isWatcher = cmd.includes("yarn start") || cmd.includes("webpack --watch") || cmd.includes("development/webpack/launch.ts --watch");
|
|
99
82
|
if (!isWatcher) continue;
|
|
100
|
-
if (cmd.includes(
|
|
101
|
-
|
|
83
|
+
if (cmd.includes(resolved)) {
|
|
84
|
+
ownedPids.add(Number(pidStr));
|
|
102
85
|
continue;
|
|
103
86
|
}
|
|
104
87
|
try {
|
|
@@ -106,35 +89,134 @@ function stopExtensionWatcher(target) {
|
|
|
106
89
|
encoding: "utf8",
|
|
107
90
|
timeout: 2e3
|
|
108
91
|
});
|
|
109
|
-
if (cwd.split("\n").some((l) => l.startsWith("n") && l.slice(1) ===
|
|
110
|
-
|
|
92
|
+
if (cwd.split("\n").some((l) => l.startsWith("n") && l.slice(1) === resolved)) {
|
|
93
|
+
ownedPids.add(Number(pidStr));
|
|
111
94
|
}
|
|
112
95
|
} catch {
|
|
113
96
|
}
|
|
114
97
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
98
|
+
} catch {
|
|
99
|
+
}
|
|
100
|
+
return [...ownedPids];
|
|
101
|
+
}
|
|
102
|
+
function isExtensionWatcherLive(target) {
|
|
103
|
+
return extensionWatcherPids(target).length > 0;
|
|
104
|
+
}
|
|
105
|
+
function stopExtensionWatcher(target) {
|
|
106
|
+
const runtimeAbs = path.join(path.resolve(target), recipeRuntimeDir());
|
|
107
|
+
const webpackPidFile = path.join(runtimeAbs, "recipe-harness-webpack.pid");
|
|
108
|
+
const ownedPids = extensionWatcherPids(target);
|
|
109
|
+
for (const pid of ownedPids) {
|
|
110
|
+
try {
|
|
111
|
+
process.kill(pid, "SIGTERM");
|
|
112
|
+
} catch {
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
fs.rmSync(webpackPidFile, { force: true });
|
|
116
|
+
if (ownedPids.length > 0) {
|
|
117
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 2e3);
|
|
118
|
+
for (const pid of ownedPids) {
|
|
119
|
+
try {
|
|
120
|
+
process.kill(pid, "SIGKILL");
|
|
121
|
+
} catch {
|
|
121
122
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return ownedPids.length;
|
|
126
|
+
}
|
|
127
|
+
function stopExtensionRuntime(target) {
|
|
128
|
+
const resolved = path.resolve(target);
|
|
129
|
+
const runtimeAbs = path.join(resolved, recipeRuntimeDir());
|
|
130
|
+
const profile = path.resolve(
|
|
131
|
+
process.env.CHROME_USER_DATA_DIR || path.join(runtimeAbs, process.env.RECIPE_CHROME_PROFILE_NAME || "chrome-profile")
|
|
132
|
+
);
|
|
133
|
+
let signalled = stopExtensionWatcher(resolved);
|
|
134
|
+
const ownedBrowserPids = /* @__PURE__ */ new Set();
|
|
135
|
+
try {
|
|
136
|
+
const psOut = execFileSync("ps", ["-axo", "pid=,command="], { encoding: "utf8" });
|
|
137
|
+
for (const line of psOut.split("\n")) {
|
|
138
|
+
const match = /^\s*(\d+)\s+(.*)$/u.exec(line);
|
|
139
|
+
if (!match) continue;
|
|
140
|
+
const pid = Number(match[1]);
|
|
141
|
+
if (pid !== process.pid && commandHasExactArg(match[2], "--user-data-dir", profile)) {
|
|
142
|
+
ownedBrowserPids.add(pid);
|
|
128
143
|
}
|
|
129
|
-
signalled += orphanPids.length;
|
|
130
144
|
}
|
|
131
145
|
} catch {
|
|
132
146
|
}
|
|
133
|
-
|
|
147
|
+
for (const pid of ownedBrowserPids) {
|
|
148
|
+
try {
|
|
149
|
+
process.kill(pid, "SIGTERM");
|
|
150
|
+
signalled += 1;
|
|
151
|
+
} catch {
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (ownedBrowserPids.size > 0) {
|
|
155
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 2e3);
|
|
156
|
+
for (const pid of ownedBrowserPids) {
|
|
157
|
+
try {
|
|
158
|
+
process.kill(pid, 0);
|
|
159
|
+
process.kill(pid, "SIGKILL");
|
|
160
|
+
} catch {
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
let removedMarkers = 0;
|
|
165
|
+
for (const name of [
|
|
166
|
+
"recipe-harness-webpack.pid",
|
|
167
|
+
"webpack.pid",
|
|
168
|
+
"browser.pid",
|
|
169
|
+
"chromium.pid",
|
|
170
|
+
"chrome.pid",
|
|
171
|
+
"launcher.pid",
|
|
172
|
+
"sandbox.lock",
|
|
173
|
+
"recipe.lock"
|
|
174
|
+
]) {
|
|
175
|
+
const marker = path.join(runtimeAbs, name);
|
|
176
|
+
if (!fs.existsSync(marker)) continue;
|
|
177
|
+
const pid = markerPid(marker);
|
|
178
|
+
if (pid !== null && processAlive(pid) && !ownedBrowserPids.has(pid)) continue;
|
|
179
|
+
fs.rmSync(marker, { force: true });
|
|
180
|
+
removedMarkers += 1;
|
|
181
|
+
}
|
|
182
|
+
return { signalled, browserProcesses: ownedBrowserPids.size, removedMarkers };
|
|
183
|
+
}
|
|
184
|
+
function markerPid(file) {
|
|
185
|
+
try {
|
|
186
|
+
const raw = fs.readFileSync(file, "utf8").trim();
|
|
187
|
+
const value = raw.startsWith("{") ? JSON.parse(raw).pid : raw;
|
|
188
|
+
const pid = Number(value);
|
|
189
|
+
return Number.isInteger(pid) && pid > 0 ? pid : null;
|
|
190
|
+
} catch {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function processAlive(pid) {
|
|
195
|
+
try {
|
|
196
|
+
process.kill(pid, 0);
|
|
197
|
+
return true;
|
|
198
|
+
} catch {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
function commandHasExactArg(command, flag, value) {
|
|
203
|
+
const escapedFlag = escapeRegex(flag);
|
|
204
|
+
const escapedValue = escapeRegex(value);
|
|
205
|
+
return [
|
|
206
|
+
new RegExp(`(?:^|\\s)${escapedFlag}=${escapedValue}(?=\\s|$)`, "u"),
|
|
207
|
+
new RegExp(`(?:^|\\s)${escapedFlag}="${escapedValue}"(?=\\s|$)`, "u"),
|
|
208
|
+
new RegExp(`(?:^|\\s)${escapedFlag}='${escapedValue}'(?=\\s|$)`, "u"),
|
|
209
|
+
new RegExp(`(?:^|\\s)${escapedFlag}\\s+${escapedValue}(?=\\s|$)`, "u")
|
|
210
|
+
].some((pattern) => pattern.test(command));
|
|
211
|
+
}
|
|
212
|
+
function escapeRegex(value) {
|
|
213
|
+
return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
134
214
|
}
|
|
135
215
|
export {
|
|
136
216
|
applyKVLines,
|
|
217
|
+
isExtensionWatcherLive,
|
|
137
218
|
resolveExtensionSlotPorts,
|
|
138
219
|
resolveMobileSlotPorts,
|
|
220
|
+
stopExtensionRuntime,
|
|
139
221
|
stopExtensionWatcher
|
|
140
222
|
};
|
package/dist/adapters.js
CHANGED
|
@@ -465,10 +465,14 @@ function createMetaMaskUiTransport(platform, harness) {
|
|
|
465
465
|
if (live) return live.output;
|
|
466
466
|
throw new Error(`ui.navigate requires library/actions/${platform}/ui/navigate.mjs.`);
|
|
467
467
|
}
|
|
468
|
-
return base.execute(action, node, context);
|
|
468
|
+
return base.execute(action, action === "ui.wait_for" ? normalizeUiWaitNode(node, platform) : node, context);
|
|
469
469
|
}
|
|
470
470
|
};
|
|
471
471
|
}
|
|
472
|
+
function normalizeUiWaitNode(node, adapter) {
|
|
473
|
+
if (adapter !== "extension" || node.expected !== void 0 || typeof node.visible !== "boolean") return node;
|
|
474
|
+
return { ...node, expected: node.visible ? "visible" : "absent" };
|
|
475
|
+
}
|
|
472
476
|
function createMetaMaskAdapters(adapter) {
|
|
473
477
|
const platform = adapter;
|
|
474
478
|
const adapters = [
|
|
@@ -534,5 +538,6 @@ export {
|
|
|
534
538
|
createMetaMaskAdapters,
|
|
535
539
|
createMetaMaskUiTransport,
|
|
536
540
|
hideMobileHudOnTeardown,
|
|
537
|
-
isMobileHudLifecycleSkip
|
|
541
|
+
isMobileHudLifecycleSkip,
|
|
542
|
+
normalizeUiWaitNode
|
|
538
543
|
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { recipeRuntimeDir } from "./paths.js";
|
|
5
|
+
function acquireCheckoutLock(target, operation) {
|
|
6
|
+
const repoRoot = fs.realpathSync(target);
|
|
7
|
+
const lockPath = path.join(repoRoot, recipeRuntimeDir(), "sandbox.lock");
|
|
8
|
+
fs.mkdirSync(path.dirname(lockPath), { recursive: true });
|
|
9
|
+
const inheritedToken = process.env.MM_HARNESS_CHECKOUT_LOCK_TOKEN;
|
|
10
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
11
|
+
try {
|
|
12
|
+
const fd = fs.openSync(lockPath, "wx", 384);
|
|
13
|
+
const token = inheritedToken || randomUUID();
|
|
14
|
+
const owner = {
|
|
15
|
+
schemaVersion: 1,
|
|
16
|
+
pid: process.pid,
|
|
17
|
+
operation,
|
|
18
|
+
repoRoot,
|
|
19
|
+
token,
|
|
20
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
21
|
+
};
|
|
22
|
+
fs.writeFileSync(fd, `${JSON.stringify(owner, null, 2)}
|
|
23
|
+
`);
|
|
24
|
+
fs.closeSync(fd);
|
|
25
|
+
if (!inheritedToken) process.env.MM_HARNESS_CHECKOUT_LOCK_TOKEN = token;
|
|
26
|
+
return {
|
|
27
|
+
path: lockPath,
|
|
28
|
+
release: () => {
|
|
29
|
+
const current = readOwner(lockPath);
|
|
30
|
+
if (current?.pid === process.pid && current.token === token) fs.rmSync(lockPath, { force: true });
|
|
31
|
+
if (!inheritedToken && process.env.MM_HARNESS_CHECKOUT_LOCK_TOKEN === token) {
|
|
32
|
+
delete process.env.MM_HARNESS_CHECKOUT_LOCK_TOKEN;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
} catch (error) {
|
|
37
|
+
if (error.code !== "EEXIST") throw error;
|
|
38
|
+
const owner = readOwner(lockPath);
|
|
39
|
+
if (inheritedToken && owner?.token === inheritedToken && owner.repoRoot === repoRoot && typeof owner.pid === "number" && processAlive(owner.pid)) {
|
|
40
|
+
return { path: lockPath, release: () => void 0 };
|
|
41
|
+
}
|
|
42
|
+
if (owner && typeof owner.pid === "number" && processAlive(owner.pid)) {
|
|
43
|
+
return {
|
|
44
|
+
path: lockPath,
|
|
45
|
+
owner,
|
|
46
|
+
message: `checkout sandbox is busy with ${String(owner.operation ?? "another operation")} (pid ${owner.pid}).`
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
fs.rmSync(lockPath, { force: true });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return { path: lockPath, owner: readOwner(lockPath), message: "checkout sandbox lock could not be acquired." };
|
|
53
|
+
}
|
|
54
|
+
function readOwner(lockPath) {
|
|
55
|
+
try {
|
|
56
|
+
const value = JSON.parse(fs.readFileSync(lockPath, "utf8"));
|
|
57
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
58
|
+
} catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function processAlive(pid) {
|
|
63
|
+
try {
|
|
64
|
+
process.kill(pid, 0);
|
|
65
|
+
return true;
|
|
66
|
+
} catch (error) {
|
|
67
|
+
return error.code === "EPERM";
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
export {
|
|
71
|
+
acquireCheckoutLock
|
|
72
|
+
};
|
package/dist/cli.js
CHANGED
|
@@ -20,6 +20,7 @@ import { handleFixtures } from "./commands/fixtures.js";
|
|
|
20
20
|
import { handleRecipeQuality } from "./commands/recipe-quality.js";
|
|
21
21
|
import { handleStatus } from "./commands/status.js";
|
|
22
22
|
import { handleCheck } from "./commands/check.js";
|
|
23
|
+
import { handleChecklist } from "./commands/checklist.js";
|
|
23
24
|
import { parseArgs, targetPath } from "./commands/parse-args.js";
|
|
24
25
|
import { runOneNode } from "./commands/run-engine.js";
|
|
25
26
|
const COMMANDS = {
|
|
@@ -131,6 +132,7 @@ async function main(argv) {
|
|
|
131
132
|
if (command === "fixtures") return handleFixtures(argv.slice(1), { runOneNode });
|
|
132
133
|
if (command === "recipe-quality") return handleRecipeQuality(argv.slice(1));
|
|
133
134
|
if (command === "check") return handleCheck(argv.slice(1));
|
|
135
|
+
if (command === "checklist") return handleChecklist(argv.slice(1));
|
|
134
136
|
const handler = COMMANDS[command];
|
|
135
137
|
if (!handler) throw new Error(`Unknown command: ${command}`);
|
|
136
138
|
return handler(parseArgs(argv.slice(1), command));
|
package/dist/commands/call.js
CHANGED
|
@@ -6,7 +6,7 @@ import { loadActionManifest } from "../manifest.js";
|
|
|
6
6
|
import { importRecipeProtocol } from "../paths.js";
|
|
7
7
|
import { recipeRunning } from "../heal-bounds.js";
|
|
8
8
|
import { color } from "../cli-color.js";
|
|
9
|
-
import { EXIT } from "./shared.js";
|
|
9
|
+
import { checkoutBusyOut, EXIT, usageOut } from "./shared.js";
|
|
10
10
|
import { describeManifestActions, fuzzyResolveActions } from "./manifest.js";
|
|
11
11
|
import {
|
|
12
12
|
parseArgs,
|
|
@@ -25,11 +25,13 @@ import {
|
|
|
25
25
|
emitHealViolation,
|
|
26
26
|
executeWithHealBounds,
|
|
27
27
|
prepareHeal,
|
|
28
|
+
recoverRunInfra,
|
|
28
29
|
resolveMetaMaskLibrarySources,
|
|
29
30
|
runRecipe,
|
|
30
31
|
synthesizeOneNodeRecipe,
|
|
31
32
|
validateRecipeAdapterAware
|
|
32
33
|
} from "./run-engine.js";
|
|
34
|
+
import { acquireCheckoutLock } from "../checkout-lock.js";
|
|
33
35
|
async function handleCall(argv) {
|
|
34
36
|
if (argv.includes("--list")) {
|
|
35
37
|
const { options: options2 } = parseArgs(argv, "call");
|
|
@@ -63,6 +65,14 @@ async function handleCall(argv) {
|
|
|
63
65
|
return EXIT.usage;
|
|
64
66
|
}
|
|
65
67
|
const { adapter, target } = resolveAdapter(options);
|
|
68
|
+
if (!fs.existsSync(target)) {
|
|
69
|
+
return usageOut(
|
|
70
|
+
json,
|
|
71
|
+
"call",
|
|
72
|
+
`target does not exist: ${target}`,
|
|
73
|
+
"pass --target <metamask-checkout> pointing to an existing checkout"
|
|
74
|
+
);
|
|
75
|
+
}
|
|
66
76
|
getAdapterSurface(adapter).resolveSlotPorts(target);
|
|
67
77
|
const dtResult = applyDeviceTargeting("call", adapter, options, { gate: true, rerun: "" });
|
|
68
78
|
if ("code" in dtResult) {
|
|
@@ -119,65 +129,76 @@ async function handleCall(argv) {
|
|
|
119
129
|
}
|
|
120
130
|
return EXIT.validation;
|
|
121
131
|
}
|
|
122
|
-
const
|
|
123
|
-
if (
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
132
|
+
const lock = acquireCheckoutLock(target, "call");
|
|
133
|
+
if ("message" in lock) {
|
|
134
|
+
return checkoutBusyOut(json, "call", lock.message, lock.path);
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
const prepared = await prepareHeal(adapter, target, options, json, {
|
|
138
|
+
skipMobileSourceFreshness: adapter === "mobile" && resolvedAction === "app.lifecycle"
|
|
139
|
+
});
|
|
140
|
+
if (typeof prepared === "number") return prepared;
|
|
141
|
+
const { state, heal } = prepared;
|
|
142
|
+
const scratch = await mkdtemp(path.join(os.tmpdir(), "mm-harness-call-"));
|
|
143
|
+
const recipeFile = path.join(scratch, "call.recipe.json");
|
|
144
|
+
fs.writeFileSync(recipeFile, `${JSON.stringify(recipe, null, 2)}
|
|
128
145
|
`);
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if (violation !== null) return emitHealViolation(json, "call", result, violation, state, adapter);
|
|
144
|
-
const callOutput = readCallOutput(result.tracePath);
|
|
145
|
-
if (json) {
|
|
146
|
-
console.log(
|
|
147
|
-
JSON.stringify(
|
|
148
|
-
{
|
|
149
|
-
schemaVersion: 1,
|
|
150
|
-
command: "call",
|
|
151
|
-
adapter,
|
|
152
|
-
action: shortName,
|
|
153
|
-
resolvedAction,
|
|
154
|
-
args,
|
|
155
|
-
status: result.status,
|
|
156
|
-
summaryPath: result.summaryPath,
|
|
157
|
-
tracePath: result.tracePath,
|
|
158
|
-
artifactManifestPath: result.artifactManifestPath,
|
|
159
|
-
...callOutput !== void 0 ? { output: callOutput } : {},
|
|
160
|
-
recovered: state.recovered,
|
|
161
|
-
mutations: state.mutations,
|
|
162
|
-
exitCode: result.status === "pass" ? EXIT.ok : EXIT.runtime
|
|
163
|
-
},
|
|
164
|
-
null,
|
|
165
|
-
2
|
|
166
|
-
)
|
|
146
|
+
const artifactsDir = optionString(options, "artifactsDir") ?? path.join(scratch, "artifacts");
|
|
147
|
+
const librarySources = await resolveMetaMaskLibrarySources(optionString(options, "library"));
|
|
148
|
+
const callRuntimeOptions = {
|
|
149
|
+
...runtimeOptionsFromCli(options),
|
|
150
|
+
...librarySources ? { librarySources } : {},
|
|
151
|
+
stdoutIsMachineContract: json
|
|
152
|
+
};
|
|
153
|
+
const { result, violation } = await executeWithHealBounds(
|
|
154
|
+
() => runRecipe(adapter, recipeFile, artifactsDir, target, actionManifestOverride, callRuntimeOptions),
|
|
155
|
+
adapter,
|
|
156
|
+
target,
|
|
157
|
+
heal,
|
|
158
|
+
state,
|
|
159
|
+
() => recoverRunInfra(adapter, target, json)
|
|
167
160
|
);
|
|
168
|
-
|
|
169
|
-
const
|
|
161
|
+
if (violation !== null) return emitHealViolation(json, "call", result, violation, state, adapter);
|
|
162
|
+
const callOutput = readCallOutput(result.tracePath);
|
|
163
|
+
if (json) {
|
|
164
|
+
console.log(
|
|
165
|
+
JSON.stringify(
|
|
166
|
+
{
|
|
167
|
+
schemaVersion: 1,
|
|
168
|
+
command: "call",
|
|
169
|
+
adapter,
|
|
170
|
+
action: shortName,
|
|
171
|
+
resolvedAction,
|
|
172
|
+
args,
|
|
173
|
+
status: result.status,
|
|
174
|
+
summaryPath: result.summaryPath,
|
|
175
|
+
tracePath: result.tracePath,
|
|
176
|
+
artifactManifestPath: result.artifactManifestPath,
|
|
177
|
+
...callOutput !== void 0 ? { output: callOutput } : {},
|
|
178
|
+
recovered: state.recovered,
|
|
179
|
+
mutations: state.mutations,
|
|
180
|
+
exitCode: result.status === "pass" ? EXIT.ok : EXIT.runtime
|
|
181
|
+
},
|
|
182
|
+
null,
|
|
183
|
+
2
|
|
184
|
+
)
|
|
185
|
+
);
|
|
186
|
+
} else {
|
|
187
|
+
const rendered = callOutput !== void 0 ? `
|
|
170
188
|
Result:
|
|
171
189
|
${formatCallOutput(callOutput)}` : "";
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
190
|
+
const out = (style, text) => color(style, text, { stream: process.stdout });
|
|
191
|
+
console.log(
|
|
192
|
+
`${out("label", "call")} ${out("cmd", resolvedAction)}: ${out(result.status === "pass" ? "ok" : "err", result.status)}${rendered ? `
|
|
175
193
|
${out("label", "Result:")}
|
|
176
194
|
${formatCallOutput(callOutput)}` : ""}
|
|
177
195
|
${out("label", "Artifacts:")} ${out("path", result.artifactManifestPath)}`
|
|
178
|
-
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
return result.status === "pass" ? EXIT.ok : EXIT.runtime;
|
|
199
|
+
} finally {
|
|
200
|
+
lock.release();
|
|
179
201
|
}
|
|
180
|
-
return result.status === "pass" ? EXIT.ok : EXIT.runtime;
|
|
181
202
|
}
|
|
182
203
|
function readCallOutput(tracePath) {
|
|
183
204
|
try {
|
|
@@ -281,7 +302,7 @@ function parseCallArgs(argv) {
|
|
|
281
302
|
if (pair === void 0) throw usageError("--arg requires k=v.");
|
|
282
303
|
const eq = pair.indexOf("=");
|
|
283
304
|
if (eq === -1) throw usageError(`--arg must be k=v: ${pair}`);
|
|
284
|
-
args[pair.slice(0, eq)] = pair.slice(eq + 1);
|
|
305
|
+
args[pair.slice(0, eq)] = parseCallValue(pair.slice(eq + 1));
|
|
285
306
|
continue;
|
|
286
307
|
}
|
|
287
308
|
if (!arg.startsWith("--") && action === void 0) {
|
|
@@ -290,13 +311,29 @@ function parseCallArgs(argv) {
|
|
|
290
311
|
}
|
|
291
312
|
if (action !== void 0 && !isForwardedOptionValue(argv, i) && isArgPair(arg)) {
|
|
292
313
|
const eq = arg.indexOf("=");
|
|
293
|
-
args[arg.slice(0, eq)] = arg.slice(eq + 1);
|
|
314
|
+
args[arg.slice(0, eq)] = parseCallValue(arg.slice(eq + 1));
|
|
294
315
|
continue;
|
|
295
316
|
}
|
|
296
317
|
rest.push(arg);
|
|
297
318
|
}
|
|
298
319
|
return { action, args, rest };
|
|
299
320
|
}
|
|
321
|
+
function parseCallValue(value) {
|
|
322
|
+
if (value === "true") return true;
|
|
323
|
+
if (value === "false") return false;
|
|
324
|
+
if (value === "null") return null;
|
|
325
|
+
if (/^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$/u.test(value)) {
|
|
326
|
+
const number = Number(value);
|
|
327
|
+
if (Number.isFinite(number)) return number;
|
|
328
|
+
}
|
|
329
|
+
if (value.startsWith("[") && value.endsWith("]") || value.startsWith("{") && value.endsWith("}") || value.startsWith('"') && value.endsWith('"')) {
|
|
330
|
+
try {
|
|
331
|
+
return JSON.parse(value);
|
|
332
|
+
} catch {
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return value;
|
|
336
|
+
}
|
|
300
337
|
function isArgPair(value) {
|
|
301
338
|
const eq = value.indexOf("=");
|
|
302
339
|
return eq > 0;
|