@deeeed/metamask-harness 0.17.0 → 0.17.2
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 +34 -0
- package/README.md +90 -140
- package/adapters/core/cleanup.sh +0 -0
- package/adapters/core/inject.sh +0 -0
- package/adapters/extension/cleanup.mjs +0 -0
- package/adapters/extension/console-tail.mjs +51 -24
- package/adapters/extension/ensure-browser.sh +0 -0
- package/adapters/extension/inject.mjs +5 -9
- package/adapters/extension/launch-browser.cjs +0 -0
- package/adapters/extension/launch-webpack.cjs +83 -0
- package/adapters/extension/launch.sh +8 -11
- package/adapters/extension/live.sh +2 -0
- package/adapters/extension/readiness.mjs +0 -0
- package/adapters/extension/reattach.sh +1 -0
- package/adapters/extension/refresh-build.sh +0 -0
- package/adapters/extension/seed-fixture.sh +4 -12
- package/adapters/extension/sidepanel-toggle.sh +4 -1
- package/adapters/extension/snapshot-dist.sh +0 -0
- package/adapters/extension/stamp-runtime-title.cjs +49 -0
- package/adapters/extension/start-watch.sh +16 -4
- package/adapters/extension/stop-viewers.sh +1 -1
- package/adapters/extension/sync-webpack-dist.cjs +105 -0
- package/adapters/extension/verify.sh +1 -1
- package/adapters/extension/wallet-fixture-state.cjs +0 -0
- package/adapters/manifest.json +46 -38
- package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +0 -0
- package/adapters/mobile/bridge-runtime/console-forwarder.cjs +1 -1
- package/adapters/mobile/bridge-runtime/lib/target-discovery.cjs +2 -3
- package/adapters/mobile/bridge-runtime/setup-wallet.sh +0 -0
- package/adapters/mobile/cleanup.sh +16 -176
- package/adapters/mobile/inject.sh +88 -671
- package/adapters/mobile/launch-console-forwarder.cjs +39 -0
- package/adapters/mobile/lib/metro-listener.sh +0 -0
- package/adapters/mobile/lib/tmux-viewer.sh +0 -0
- package/adapters/mobile/open-device.sh +0 -0
- package/adapters/mobile/prewarm-bundle.sh +0 -0
- package/adapters/mobile/start-console-forwarder.sh +70 -0
- package/adapters/mobile/start-metro.sh +0 -41
- package/adapters/mobile/stop-metro.sh +0 -0
- package/adapters/mobile/verify.sh +4 -61
- 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 +0 -0
- package/adapters/shared/json-field.sh +0 -0
- package/adapters/shared/open-debug.mjs +90 -36
- package/adapters/shared/open-log-window.sh +0 -0
- package/adapters/shared/reap-checkout-metros.sh +0 -0
- package/adapters/shared/resolve-slot-ports-core.mjs +0 -6
- package/adapters/shared/resolve-slot-ports.mjs +0 -0
- package/adapters/shared/resolve-slot-ports.sh +0 -5
- package/adapters/shared/sync-wallet-fixture.sh +0 -0
- package/adapters/shared/tmux-session.sh +0 -0
- package/adapters/shared/tmux-viewer.sh +0 -0
- package/adapters/shared/update-check-worker.mjs +37 -0
- package/dist/adapters/extension/console-capture.js +84 -0
- package/dist/adapters/extension/runtime-decision.js +14 -2
- package/dist/adapters/extension/surface.js +3 -2
- package/dist/adapters/mobile/prepare.js +22 -7
- package/dist/adapters/mobile/surface.js +1 -1
- package/dist/adapters/resolve-slot-ports.js +0 -2
- package/dist/adapters/slot-ports.js +8 -0
- package/dist/cli.js +3 -11
- package/dist/commands/call.js +9 -4
- package/dist/commands/debug.js +38 -31
- package/dist/commands/fixtures.js +18 -10
- package/dist/commands/launch/index.js +10 -0
- package/dist/commands/logs.js +8 -6
- package/dist/commands/manifest.js +5 -17
- package/dist/commands/run-engine.js +1 -32
- package/dist/commands/run-report.js +1 -1
- package/dist/commands/run.js +50 -3
- package/dist/commands/status-probe.js +1 -1
- package/dist/commands/update.js +15 -28
- package/dist/doctor.js +1 -4
- package/dist/harness.js +5 -123
- package/dist/live-adapter-contract.js +6 -20
- package/dist/mm-harness-cli.js +6 -43
- package/dist/paths.js +2 -5
- package/dist/run-diagnostics.js +35 -40
- package/dist/runtime-context.js +26 -8
- package/docs/CHEATSHEET.md +39 -52
- package/docs/CLI-ERGONOMICS-HUMAN-QA.md +48 -104
- package/docs/CODE-MAP.md +1 -1
- package/docs/VIDEO-DEMO-VALIDATION.md +62 -0
- package/docs/architecture.md +65 -375
- package/docs/live-adapter-contract.md +3 -1
- package/library/actions/core/perps/_controller.mjs +10 -55
- package/library/manifests/mobile.action-manifest.json +1 -1
- package/package.json +2 -2
- package/scripts/completions.sh +0 -0
- package/scripts/install-completions.sh +0 -0
- package/adapters/mobile/overlay/app/dev-tools/AgenticService/AgentStepHud.tsx.patch +0 -185
- package/adapters/mobile/overlay/app/dev-tools/AgenticService/AgenticService.ts.patch +0 -1662
- package/adapters/mobile/overlay/compat/README.md +0 -28
- package/adapters/mobile/overlay/compat/rn81-message-event-source.patch +0 -42
- package/adapters/shared/resolve-farmslot-ports-core.mjs +0 -3
- package/adapters/shared/resolve-farmslot-ports.mjs +0 -5
- package/adapters/shared/resolve-farmslot-ports.sh +0 -7
- package/docs/CLI-SPEC.md +0 -1142
- package/docs/MENTAL-MODEL.md +0 -295
|
@@ -173,7 +173,7 @@ function runtimeDistCheck(target) {
|
|
|
173
173
|
} catch (error) {
|
|
174
174
|
if (error.code === "ENOENT") {
|
|
175
175
|
try {
|
|
176
|
-
const modified2 = filesystemSnapshotDiff(dist, runtimeDist).slice(0, 10);
|
|
176
|
+
const modified2 = filesystemSnapshotDiff(dist, runtimeDist).filter((relative) => !slotTitleOnlyDifference(dist, runtimeDist, relative)).slice(0, 10);
|
|
177
177
|
return modified2.length > 0 ? { status: "stale", comparison: "filesystem-quick", modified: modified2 } : { status: "fresh", comparison: "filesystem-quick" };
|
|
178
178
|
} catch (fallbackError) {
|
|
179
179
|
return {
|
|
@@ -188,9 +188,21 @@ function runtimeDistCheck(target) {
|
|
|
188
188
|
error: `rsync snapshot comparison failed${typeof status === "number" ? ` with exit ${status}` : ""}`
|
|
189
189
|
};
|
|
190
190
|
}
|
|
191
|
-
const modified = output.split("\n").map((line) => line.trim()).filter(Boolean).slice(0, 10);
|
|
191
|
+
const modified = output.split("\n").map((line) => line.trim()).filter(Boolean).filter((relative) => !slotTitleOnlyDifference(dist, runtimeDist, relative)).slice(0, 10);
|
|
192
192
|
return modified.length > 0 ? { status: "stale", comparison: "rsync-quick", modified } : { status: "fresh", comparison: "rsync-quick" };
|
|
193
193
|
}
|
|
194
|
+
function slotTitleOnlyDifference(dist, runtimeDist, relative) {
|
|
195
|
+
if (relative !== "home.html" && relative !== "sidepanel.html") return false;
|
|
196
|
+
try {
|
|
197
|
+
const normalize = (source) => source.replace(
|
|
198
|
+
/<title>([^<]*)<\/title>/iu,
|
|
199
|
+
(_match, title) => `<title>${String(title || "MetaMask").replace(/^.+?\s+[—–-]\s+/u, "") || "MetaMask"}</title>`
|
|
200
|
+
);
|
|
201
|
+
return normalize(fs.readFileSync(path.join(dist, relative), "utf8")) === normalize(fs.readFileSync(path.join(runtimeDist, relative), "utf8"));
|
|
202
|
+
} catch {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
194
206
|
function filesystemSnapshotDiff(source, snapshot) {
|
|
195
207
|
const sourceEntries = snapshotEntries(source);
|
|
196
208
|
const snapshotEntriesByPath = snapshotEntries(snapshot);
|
|
@@ -65,11 +65,12 @@ const extensionSurface = {
|
|
|
65
65
|
return [
|
|
66
66
|
{ label: "webpack", path: recipeRuntimePath(target, "webpack.log") },
|
|
67
67
|
{ label: "watcher", path: recipeRuntimePath(target, "recipe-harness-webpack.log") },
|
|
68
|
-
{ label: "rebuild", path: recipeRuntimePath(target, "rebuild.log") }
|
|
68
|
+
{ label: "rebuild", path: recipeRuntimePath(target, "rebuild.log") },
|
|
69
|
+
{ label: "dapp", path: recipeRuntimePath(target, "dapp-console.log") }
|
|
69
70
|
];
|
|
70
71
|
},
|
|
71
72
|
appLogSource(target) {
|
|
72
|
-
return { label: "
|
|
73
|
+
return { label: "extension", path: recipeRuntimePath(target, "extension-console.log") };
|
|
73
74
|
},
|
|
74
75
|
hints: {
|
|
75
76
|
launch: "mm-harness launch",
|
|
@@ -56,14 +56,21 @@ async function prepareMobile(target, opts = {}) {
|
|
|
56
56
|
json,
|
|
57
57
|
preflightMode
|
|
58
58
|
);
|
|
59
|
-
if (confirm.status === 0) return
|
|
59
|
+
if (confirm.status === 0) return startMobileConsoleForwarder(target, json);
|
|
60
60
|
if (!json) {
|
|
61
61
|
process.stderr.write(
|
|
62
62
|
`launch: runtime looked ready but no ${platform} bridge target answered; launching the app.
|
|
63
63
|
`
|
|
64
64
|
);
|
|
65
65
|
}
|
|
66
|
-
|
|
66
|
+
const launch = await dispatchActionSequence(
|
|
67
|
+
launchActions(path.resolve(target)),
|
|
68
|
+
target,
|
|
69
|
+
platform,
|
|
70
|
+
json,
|
|
71
|
+
preflightMode
|
|
72
|
+
);
|
|
73
|
+
return launch.status === 0 ? startMobileConsoleForwarder(target, json) : launch;
|
|
67
74
|
}
|
|
68
75
|
if (report.decision === "unknown") {
|
|
69
76
|
const msg = "mobile prepare: runtime state unknown\n Next: run mm-harness verify --adapter mobile --target <checkout>";
|
|
@@ -111,7 +118,15 @@ async function prepareMobile(target, opts = {}) {
|
|
|
111
118
|
}
|
|
112
119
|
}
|
|
113
120
|
}
|
|
114
|
-
return
|
|
121
|
+
return startMobileConsoleForwarder(target, json);
|
|
122
|
+
}
|
|
123
|
+
function startMobileConsoleForwarder(target, json) {
|
|
124
|
+
return dispatchAction(
|
|
125
|
+
{ id: "start-console-forwarder", cwd: target, argv: json ? ["--quiet"] : [] },
|
|
126
|
+
target,
|
|
127
|
+
"ios",
|
|
128
|
+
json
|
|
129
|
+
);
|
|
115
130
|
}
|
|
116
131
|
async function dispatchActionSequence(requestedActions, target, platform, json, preflightMode) {
|
|
117
132
|
const resolved = path.resolve(target);
|
|
@@ -147,6 +162,10 @@ async function dispatchAction(action, target, platform, json, preflightMode = "f
|
|
|
147
162
|
const extra = action.argv ?? [];
|
|
148
163
|
return spawnScriptStreaming(leaf, ["--target", cwd, ...extra], target);
|
|
149
164
|
}
|
|
165
|
+
case "start-console-forwarder": {
|
|
166
|
+
const leaf = path.join(runnerDir, "adapters/mobile/start-console-forwarder.sh");
|
|
167
|
+
return spawnScriptStreaming(leaf, ["--target", cwd, ...action.argv ?? []], target);
|
|
168
|
+
}
|
|
150
169
|
case "prewarm-bundle": {
|
|
151
170
|
const leaf = path.join(runnerDir, "adapters/mobile/prewarm-bundle.sh");
|
|
152
171
|
return spawnScriptStreaming(leaf, ["--platform", platform, "--target", cwd], target);
|
|
@@ -156,10 +175,6 @@ async function dispatchAction(action, target, platform, json, preflightMode = "f
|
|
|
156
175
|
const extra = action.argv ?? [];
|
|
157
176
|
return spawnScriptStreaming(leaf, ["--target", cwd, "--platform", platform, ...extra], target);
|
|
158
177
|
}
|
|
159
|
-
case "clear-metro-cache": {
|
|
160
|
-
const leaf = path.join(runnerDir, "adapters/mobile/start-metro.sh");
|
|
161
|
-
return spawnScriptStreaming(leaf, ["--target", cwd, "--clear"], target);
|
|
162
|
-
}
|
|
163
178
|
case "launch-mobile-runtime": {
|
|
164
179
|
const leaf = path.join(runnerDir, "adapters/mobile/open-device.sh");
|
|
165
180
|
return spawnScriptStreaming(
|
|
@@ -47,7 +47,7 @@ const mobileSurface = {
|
|
|
47
47
|
return [{ label: "metro", path: recipeRuntimePath(target, "metro.log") }];
|
|
48
48
|
},
|
|
49
49
|
appLogSource(target) {
|
|
50
|
-
return { label: "app", path: recipeRuntimePath(target, "
|
|
50
|
+
return { label: "app", path: recipeRuntimePath(target, "app-console.log") };
|
|
51
51
|
},
|
|
52
52
|
hints: {
|
|
53
53
|
launch: "mm-harness launch ios",
|
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
resolveExtensionRuntimeContext,
|
|
8
8
|
resolveExtensionRuntimePorts,
|
|
9
9
|
resolveSlotPortsByRepo,
|
|
10
|
-
resolveFarmslotPortsByRepo,
|
|
11
10
|
resolveMobileRuntimeContext,
|
|
12
11
|
resolveMobileRuntimePorts,
|
|
13
12
|
resolveMobileSlotDefaults
|
|
@@ -20,7 +19,6 @@ export {
|
|
|
20
19
|
resolveDefaultExtensionPorts,
|
|
21
20
|
resolveExtensionRuntimeContext,
|
|
22
21
|
resolveExtensionRuntimePorts,
|
|
23
|
-
resolveFarmslotPortsByRepo,
|
|
24
22
|
resolveMobileRuntimeContext,
|
|
25
23
|
resolveMobileRuntimePorts,
|
|
26
24
|
resolveMobileSlotDefaults,
|
|
@@ -105,7 +105,13 @@ function isExtensionWatcherLive(target) {
|
|
|
105
105
|
function stopExtensionWatcher(target) {
|
|
106
106
|
const runtimeAbs = path.join(path.resolve(target), recipeRuntimeDir());
|
|
107
107
|
const webpackPidFile = path.join(runtimeAbs, "recipe-harness-webpack.pid");
|
|
108
|
+
const syncPidFile = path.join(runtimeAbs, "recipe-harness-webpack-sync.pid");
|
|
108
109
|
const ownedPids = extensionWatcherPids(target);
|
|
110
|
+
try {
|
|
111
|
+
const syncPid = fs.readFileSync(syncPidFile, "utf8").trim();
|
|
112
|
+
if (/^\d+$/u.test(syncPid) && processAlive(Number(syncPid))) ownedPids.push(Number(syncPid));
|
|
113
|
+
} catch {
|
|
114
|
+
}
|
|
109
115
|
for (const pid of ownedPids) {
|
|
110
116
|
try {
|
|
111
117
|
process.kill(pid, "SIGTERM");
|
|
@@ -113,6 +119,7 @@ function stopExtensionWatcher(target) {
|
|
|
113
119
|
}
|
|
114
120
|
}
|
|
115
121
|
fs.rmSync(webpackPidFile, { force: true });
|
|
122
|
+
fs.rmSync(syncPidFile, { force: true });
|
|
116
123
|
if (ownedPids.length > 0) {
|
|
117
124
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 2e3);
|
|
118
125
|
for (const pid of ownedPids) {
|
|
@@ -164,6 +171,7 @@ function stopExtensionRuntime(target) {
|
|
|
164
171
|
let removedMarkers = 0;
|
|
165
172
|
for (const name of [
|
|
166
173
|
"recipe-harness-webpack.pid",
|
|
174
|
+
"recipe-harness-webpack-sync.pid",
|
|
167
175
|
"webpack.pid",
|
|
168
176
|
"browser.pid",
|
|
169
177
|
"chromium.pid",
|
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { detectAdapter, handleHarness } from "./harness.js";
|
|
2
|
-
import { handleActions
|
|
2
|
+
import { handleActions } from "./commands/manifest.js";
|
|
3
3
|
import { handleDoctor } from "./commands/doctor.js";
|
|
4
4
|
import { handleProvision } from "./commands/provision.js";
|
|
5
5
|
import { handleRuntimeHealth } from "./commands/runtime-health.js";
|
|
@@ -25,7 +25,6 @@ import { handleLast } from "./commands/last.js";
|
|
|
25
25
|
import { parseArgs, targetPath } from "./commands/parse-args.js";
|
|
26
26
|
import { runOneNode } from "./commands/run-engine.js";
|
|
27
27
|
const COMMANDS = {
|
|
28
|
-
manifest: handleManifest,
|
|
29
28
|
actions: handleActions,
|
|
30
29
|
doctor: handleDoctor,
|
|
31
30
|
"runtime-health": handleRuntimeHealth,
|
|
@@ -36,7 +35,7 @@ const COMMANDS = {
|
|
|
36
35
|
run: handleRun,
|
|
37
36
|
"self-test": handleSelfTest
|
|
38
37
|
};
|
|
39
|
-
const OVERLAY_COMMANDS = ["install", "verify", "cleanup"
|
|
38
|
+
const OVERLAY_COMMANDS = ["install", "verify", "cleanup"];
|
|
40
39
|
function usage() {
|
|
41
40
|
console.error(`mm-harness \u2014 the MetaMask recipe harness: launch the app, prove behavior, manage the runtime overlay.
|
|
42
41
|
Run it from inside a MetaMask checkout; the platform (mobile | extension | core) is auto-detected.
|
|
@@ -63,9 +62,6 @@ PROVE \u2014 run recipes and inspect capabilities:
|
|
|
63
62
|
mm-harness check diff --profile fast --artifacts-dir artifacts/validation
|
|
64
63
|
actions Describe the actions a manifest declares.
|
|
65
64
|
mm-harness actions --adapter mobile
|
|
66
|
-
manifest Print/validate the action manifest for an adapter.
|
|
67
|
-
mm-harness manifest --adapter extension --json
|
|
68
|
-
|
|
69
65
|
RUNTIME OVERLAY \u2014 install/verify/clean the per-checkout runtime overlay:
|
|
70
66
|
provision Install the cached Runway mobile dev client (artifact cache + sim install).
|
|
71
67
|
mm-harness provision runway ios --adapter mobile
|
|
@@ -73,12 +69,8 @@ RUNTIME OVERLAY \u2014 install/verify/clean the per-checkout runtime overlay:
|
|
|
73
69
|
mm-harness install # inside a checkout, auto-detected
|
|
74
70
|
verify Check the overlay/runtime is present and healthy (no launch).
|
|
75
71
|
mm-harness verify
|
|
76
|
-
live Launch/reuse the app, then verify live control end-to-end.
|
|
77
|
-
mm-harness live
|
|
78
72
|
cleanup Remove the installed overlay and restore the checkout.
|
|
79
73
|
mm-harness cleanup
|
|
80
|
-
# Open sub-question (Arthur): keep these top-level (mm-harness install) or group
|
|
81
|
-
# them under an \`overlay\` command (mm-harness overlay install)? Top-level for now.
|
|
82
74
|
|
|
83
75
|
ADVANCED \u2014 internal runtime probes (rarely typed by hand):
|
|
84
76
|
runtime-health runtime-decision runtime-launch
|
|
@@ -89,7 +81,7 @@ ONE bin: mm-harness is the only command. No per-platform binaries \u2014 platfor
|
|
|
89
81
|
auto-detected, the positional target forces it (mm-harness launch ios), and
|
|
90
82
|
platform-specific needs are FLAGS on the same command (e.g. --sidebar, --full-build).
|
|
91
83
|
Human happy path = the bare command; agents add depth via flags (--json, --target, ports).
|
|
92
|
-
See
|
|
84
|
+
See README.md for the current workflow and docs/CHEATSHEET.md for examples.
|
|
93
85
|
`);
|
|
94
86
|
}
|
|
95
87
|
async function main(argv) {
|
package/dist/commands/call.js
CHANGED
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
validateRecipeAdapterAware
|
|
34
34
|
} from "./run-engine.js";
|
|
35
35
|
import { acquireCheckoutLock } from "../checkout-lock.js";
|
|
36
|
+
import { formatRunDiagnosticsForHuman, readRunDiagnosticsDocument } from "../run-diagnostics.js";
|
|
36
37
|
async function handleCall(argv) {
|
|
37
38
|
if (argv.includes("--list")) {
|
|
38
39
|
const { options: options2 } = parseArgs(argv, "call");
|
|
@@ -206,15 +207,19 @@ async function handleCall(argv) {
|
|
|
206
207
|
const rendered = callOutput !== void 0 ? `
|
|
207
208
|
Result:
|
|
208
209
|
${formatCallOutput(callOutput)}` : "";
|
|
209
|
-
const sideFindingTotal = result.sideFindings?.counts.total ?? 0;
|
|
210
210
|
const out = (style, text) => color(style, text, { stream: process.stdout });
|
|
211
|
-
const
|
|
212
|
-
|
|
211
|
+
const diagnostics = formatRunDiagnosticsForHuman(
|
|
212
|
+
readRunDiagnosticsDocument(result.diagnosticsPath),
|
|
213
|
+
adapter
|
|
214
|
+
);
|
|
215
|
+
const renderedDiagnostics = `${out("label", "Diagnostics:")}
|
|
216
|
+
${diagnostics.map((line) => ` ${line}`).join("\n")}
|
|
217
|
+
`;
|
|
213
218
|
console.log(
|
|
214
219
|
`${out("label", "call")} ${out("cmd", resolvedAction)}: ${out(result.status === "pass" ? "ok" : "err", result.status)}${rendered ? `
|
|
215
220
|
${out("label", "Result:")}
|
|
216
221
|
${formatCallOutput(callOutput)}` : ""}
|
|
217
|
-
` +
|
|
222
|
+
` + renderedDiagnostics + `${out("label", "Artifacts:")} ${out("path", result.artifactManifestPath)}` + (result.status === "fail" ? `
|
|
218
223
|
Next: ${failureUserAction}` : "")
|
|
219
224
|
);
|
|
220
225
|
}
|
package/dist/commands/debug.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { runnerDir } from "../paths.js";
|
|
3
|
+
import { acquireCheckoutLock } from "../checkout-lock.js";
|
|
3
4
|
import { getAdapterSurface } from "../adapters/surface.js";
|
|
4
|
-
import { ADAPTER_DETECT_NEXT, EXIT, flag, parseFlags, resolveAdapter, spawnScript, targetOf, usageOut } from "./shared.js";
|
|
5
|
+
import { ADAPTER_DETECT_NEXT, checkoutBusyOut, EXIT, flag, parseFlags, resolveAdapter, spawnScript, targetOf, usageOut } from "./shared.js";
|
|
5
6
|
const DEBUG_BOOLEANS = /* @__PURE__ */ new Set(["worker", "devMenu", "json", "noOpen"]);
|
|
6
7
|
async function handleDebug(argv) {
|
|
7
8
|
const { options } = parseFlags(argv, DEBUG_BOOLEANS);
|
|
@@ -31,37 +32,43 @@ async function handleDebug(argv) {
|
|
|
31
32
|
"mm-harness debug --worker"
|
|
32
33
|
);
|
|
33
34
|
}
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
debugArgs
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
35
|
+
const lock = acquireCheckoutLock(target, "debug");
|
|
36
|
+
if ("message" in lock) return checkoutBusyOut(json, "debug", lock.message, lock.path);
|
|
37
|
+
try {
|
|
38
|
+
const openDebug = path.join(runnerDir, "adapters/shared/open-debug.mjs");
|
|
39
|
+
const debugArgs = ["--adapter", adapter];
|
|
40
|
+
if (adapter === "extension") {
|
|
41
|
+
if (process.env.CDP_PORT) debugArgs.push("--cdp-port", process.env.CDP_PORT);
|
|
42
|
+
debugArgs.push("--target", worker ? "worker" : "page");
|
|
43
|
+
} else {
|
|
44
|
+
if (process.env.WATCHER_PORT) debugArgs.push("--port", process.env.WATCHER_PORT);
|
|
45
|
+
debugArgs.push("--action", devMenu ? "dev-menu" : "debug");
|
|
46
|
+
}
|
|
47
|
+
if (json) debugArgs.push("--json");
|
|
48
|
+
if (noOpen) debugArgs.push("--no-open");
|
|
49
|
+
const result = spawnScript(process.execPath, [openDebug, ...debugArgs], target, json);
|
|
50
|
+
if (json) {
|
|
51
|
+
const leaf = parseDebugLeaf(result.output);
|
|
52
|
+
console.log(
|
|
53
|
+
JSON.stringify(
|
|
54
|
+
{
|
|
55
|
+
...leaf,
|
|
56
|
+
schemaVersion: 1,
|
|
57
|
+
command: "debug",
|
|
58
|
+
adapter,
|
|
59
|
+
mode: worker ? "worker" : devMenu ? "dev-menu" : "default",
|
|
60
|
+
opened: leaf.opened === true,
|
|
61
|
+
exitCode: result.status === 0 ? EXIT.ok : EXIT.runtime
|
|
62
|
+
},
|
|
63
|
+
null,
|
|
64
|
+
2
|
|
65
|
+
)
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
return result.status === 0 ? EXIT.ok : EXIT.runtime;
|
|
69
|
+
} finally {
|
|
70
|
+
lock.release();
|
|
63
71
|
}
|
|
64
|
-
return result.status === 0 ? EXIT.ok : EXIT.runtime;
|
|
65
72
|
}
|
|
66
73
|
function parseDebugLeaf(output) {
|
|
67
74
|
try {
|
|
@@ -56,6 +56,14 @@ async function handleFixturesLocked(argv, deps) {
|
|
|
56
56
|
if (sub !== "init" && sub !== "sync" && sub !== "set" && sub !== "generate" && sub !== "finalize") {
|
|
57
57
|
return usageOut(json, "fixtures", "fixtures requires a subcommand: mm-harness fixtures <init|sync|set|generate|finalize>", "mm-harness fixtures init --from <path> or mm-harness fixtures init --dev");
|
|
58
58
|
}
|
|
59
|
+
if (sub !== "init" && (flag(options, "dev") || flag(options, "force") || str(options, "from"))) {
|
|
60
|
+
return usageOut(
|
|
61
|
+
json,
|
|
62
|
+
"fixtures",
|
|
63
|
+
"--dev, --from, and --force belong to fixtures init; they do not apply a fixture.",
|
|
64
|
+
"mm-harness fixtures init --dev && mm-harness fixtures set"
|
|
65
|
+
);
|
|
66
|
+
}
|
|
59
67
|
const target = targetOf(options);
|
|
60
68
|
const adapter = resolveAdapter(options, target);
|
|
61
69
|
if (!adapter) {
|
|
@@ -96,6 +104,14 @@ async function handleFixturesLocked(argv, deps) {
|
|
|
96
104
|
return exitCode;
|
|
97
105
|
}
|
|
98
106
|
const fixturePath = path.resolve(str(options, "fixture") ?? process.env.RECIPE_WALLET_FIXTURE ?? canonicalFixture);
|
|
107
|
+
if (!fs.existsSync(fixturePath)) {
|
|
108
|
+
return usageOut(
|
|
109
|
+
json,
|
|
110
|
+
"fixtures",
|
|
111
|
+
`no wallet fixture at ${fixturePath}.`,
|
|
112
|
+
"mm-harness fixtures init --dev && mm-harness fixtures set"
|
|
113
|
+
);
|
|
114
|
+
}
|
|
99
115
|
process.stderr.write(`\u2192 fixtures set ${adapter} \u2014 connecting bridge + applying wallet fixture (can take ~30s)\u2026
|
|
100
116
|
`);
|
|
101
117
|
let status;
|
|
@@ -129,14 +145,6 @@ async function handleFixturesLocked(argv, deps) {
|
|
|
129
145
|
console.error(` Next: ${retryHint}`);
|
|
130
146
|
}
|
|
131
147
|
} else {
|
|
132
|
-
if (!fs.existsSync(fixturePath)) {
|
|
133
|
-
return usageOut(
|
|
134
|
-
json,
|
|
135
|
-
"fixtures",
|
|
136
|
-
`no wallet fixture at ${fixturePath}.`,
|
|
137
|
-
"create it (or pass --fixture <path>), then re-run: mm-harness fixtures set"
|
|
138
|
-
);
|
|
139
|
-
}
|
|
140
148
|
const previousFixtureEnv = process.env.RECIPE_WALLET_FIXTURE;
|
|
141
149
|
process.env.RECIPE_WALLET_FIXTURE = fixturePath;
|
|
142
150
|
try {
|
|
@@ -150,7 +158,7 @@ async function handleFixturesLocked(argv, deps) {
|
|
|
150
158
|
console.error(` Next: ${retryHint}`);
|
|
151
159
|
}
|
|
152
160
|
}
|
|
153
|
-
const teaching = `Wallet fixture applied. Want different accounts? Edit: ${canonicalFixture}
|
|
161
|
+
const teaching = status === "pass" ? `Wallet fixture applied. Want different accounts? Edit: ${canonicalFixture}` : "Wallet fixture was not applied.";
|
|
154
162
|
if (json) {
|
|
155
163
|
console.log(
|
|
156
164
|
JSON.stringify(
|
|
@@ -174,7 +182,7 @@ async function handleFixturesLocked(argv, deps) {
|
|
|
174
182
|
2
|
|
175
183
|
)
|
|
176
184
|
);
|
|
177
|
-
} else {
|
|
185
|
+
} else if (status === "pass") {
|
|
178
186
|
console.error(teaching);
|
|
179
187
|
}
|
|
180
188
|
return status === "pass" ? EXIT.ok : EXIT.runtime;
|
|
@@ -35,6 +35,7 @@ import { launchMobile } from "./mobile.js";
|
|
|
35
35
|
import { applyDeviceTargeting } from "../device-target.js";
|
|
36
36
|
import { acquireCheckoutLock } from "../../checkout-lock.js";
|
|
37
37
|
import { JsonStreamWriter } from "../../json-stream.js";
|
|
38
|
+
import { ensureExtensionConsoleCapture } from "../../adapters/extension/console-capture.js";
|
|
38
39
|
const LAUNCH_BOOLEANS = /* @__PURE__ */ new Set([
|
|
39
40
|
"build",
|
|
40
41
|
"watch",
|
|
@@ -427,6 +428,15 @@ async function finishLaunch(jsonOutput, machine, stream, adapter, mobileTarget,
|
|
|
427
428
|
});
|
|
428
429
|
}
|
|
429
430
|
}
|
|
431
|
+
if (adapter === "extension") {
|
|
432
|
+
try {
|
|
433
|
+
await ensureExtensionConsoleCapture(target);
|
|
434
|
+
} catch (error) {
|
|
435
|
+
if (!machine) {
|
|
436
|
+
console.error(`WARN: Extension console capture unavailable: ${error instanceof Error ? error.message : String(error)}`);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
430
440
|
if (wantVerify) {
|
|
431
441
|
stream.phase("verify");
|
|
432
442
|
const verifyArgs = ["verify", "--adapter", adapter, "--target", target];
|
package/dist/commands/logs.js
CHANGED
|
@@ -19,9 +19,10 @@ async function handleLogs(argv) {
|
|
|
19
19
|
const logSources = surface.logSources(target);
|
|
20
20
|
const appLogSource = surface.appLogSource(target);
|
|
21
21
|
const sourceLabels = logSources.map((entry) => entry.label);
|
|
22
|
-
const
|
|
22
|
+
const appSourceLabel = appLogSource?.label ?? "app";
|
|
23
|
+
const defaultSource = appLogSource && appSourceLabel !== "app" ? appSourceLabel : sourceLabels[0];
|
|
23
24
|
const source = str(options, "source") ?? defaultSource;
|
|
24
|
-
const validSources = [...sourceLabels, ...appLogSource ? ["app"] : []];
|
|
25
|
+
const validSources = [...sourceLabels, ...appLogSource ? [appSourceLabel] : [], ...appLogSource && appSourceLabel !== "app" ? ["app"] : []];
|
|
25
26
|
if (!validSources.includes(source)) {
|
|
26
27
|
return usageOut(json, "logs", `--source must be one of: ${validSources.join(", ")}.`, `mm-harness logs --source ${defaultSource}`);
|
|
27
28
|
}
|
|
@@ -32,11 +33,12 @@ async function handleLogs(argv) {
|
|
|
32
33
|
}
|
|
33
34
|
process.env.RECIPE_LOG_EVENTS = events;
|
|
34
35
|
}
|
|
35
|
-
const
|
|
36
|
-
const
|
|
36
|
+
const isAppSource = source === "app" || source === appSourceLabel;
|
|
37
|
+
const requested = isAppSource ? appLogSource : logSources.find((entry) => entry.label === source);
|
|
38
|
+
const ordered = isAppSource ? requested ? [requested] : [] : requested ? [requested] : logSources;
|
|
37
39
|
const logFile = ordered.find((entry) => fs.existsSync(entry.path))?.path;
|
|
38
40
|
if (!logFile) {
|
|
39
|
-
if (
|
|
41
|
+
if (isAppSource && appLogSource) {
|
|
40
42
|
const message = `application console unavailable for this checkout (${path.basename(appLogSource.path)} is absent).`;
|
|
41
43
|
const userAction = surface.hints.launch;
|
|
42
44
|
if (json) {
|
|
@@ -55,7 +57,7 @@ async function handleLogs(argv) {
|
|
|
55
57
|
}
|
|
56
58
|
return EXIT.runtime;
|
|
57
59
|
}
|
|
58
|
-
const names =
|
|
60
|
+
const names = isAppSource && appLogSource ? path.basename(appLogSource.path) : logSources.map((entry) => path.basename(entry.path)).join(" / ");
|
|
59
61
|
return usageOut(
|
|
60
62
|
json,
|
|
61
63
|
"logs",
|
|
@@ -1,25 +1,14 @@
|
|
|
1
1
|
import { loadActionManifest, validateManifest } from "../manifest.js";
|
|
2
2
|
import { EXIT } from "./shared.js";
|
|
3
|
-
import {
|
|
4
|
-
actionManifestPathOption,
|
|
5
|
-
isRecord,
|
|
6
|
-
optionFlag,
|
|
7
|
-
optionString,
|
|
8
|
-
resolveAdapter
|
|
9
|
-
} from "./parse-args.js";
|
|
10
|
-
async function handleManifest({ options }) {
|
|
11
|
-
const { adapter } = resolveAdapter(options);
|
|
12
|
-
const actionManifestPath = actionManifestPathOption(options, adapter);
|
|
13
|
-
const manifest = loadActionManifest(adapter, optionString(options, "actionManifest"));
|
|
14
|
-
await validateManifest(manifest);
|
|
15
|
-
if (optionFlag(options, "json")) console.log(JSON.stringify(manifest, null, 2));
|
|
16
|
-
else console.log(actionManifestPath);
|
|
17
|
-
return 0;
|
|
18
|
-
}
|
|
3
|
+
import { isRecord, optionFlag, optionString, resolveAdapter } from "./parse-args.js";
|
|
19
4
|
async function handleActions({ options, positional }) {
|
|
20
5
|
const { adapter } = resolveAdapter(options);
|
|
21
6
|
const manifest = loadActionManifest(adapter, optionString(options, "actionManifest"));
|
|
22
7
|
await validateManifest(manifest);
|
|
8
|
+
if (optionFlag(options, "raw")) {
|
|
9
|
+
console.log(JSON.stringify(manifest, null, 2));
|
|
10
|
+
return EXIT.ok;
|
|
11
|
+
}
|
|
23
12
|
const json = optionFlag(options, "json");
|
|
24
13
|
const action = optionString(options, "action");
|
|
25
14
|
const query = positional[0]?.trim();
|
|
@@ -226,7 +215,6 @@ export {
|
|
|
226
215
|
findRelatedActions,
|
|
227
216
|
fuzzyResolveActions,
|
|
228
217
|
handleActions,
|
|
229
|
-
handleManifest,
|
|
230
218
|
searchActions,
|
|
231
219
|
summarizeActionCategories
|
|
232
220
|
};
|
|
@@ -200,12 +200,7 @@ function libraryRecipeNames(adapter) {
|
|
|
200
200
|
return [...new Set(names)].sort();
|
|
201
201
|
}
|
|
202
202
|
function recipeNameCandidates(recipeArg, adapter) {
|
|
203
|
-
|
|
204
|
-
const candidates = [];
|
|
205
|
-
for (const name of names) {
|
|
206
|
-
candidates.push(...singleRecipeNameCandidates(name, adapter));
|
|
207
|
-
}
|
|
208
|
-
return [...new Set(candidates)];
|
|
203
|
+
return singleRecipeNameCandidates(recipeArg, adapter);
|
|
209
204
|
}
|
|
210
205
|
function singleRecipeNameCandidates(name, adapter) {
|
|
211
206
|
if (name.endsWith(".recipe.json")) {
|
|
@@ -231,32 +226,6 @@ function domainPathForRecipeName(name) {
|
|
|
231
226
|
function uniqueStrings(values) {
|
|
232
227
|
return [...new Set(values)];
|
|
233
228
|
}
|
|
234
|
-
const LEGACY_RECIPE_ALIASES = {
|
|
235
|
-
"action-validation": ["runner.action-validation"],
|
|
236
|
-
"action-validation.extension.recipe.json": ["runner.action-validation.extension.recipe.json"],
|
|
237
|
-
"action-validation.mobile.recipe.json": ["runner.action-validation.mobile.recipe.json"],
|
|
238
|
-
"app-lifecycle-android-smoke": ["app.lifecycle.android-smoke"],
|
|
239
|
-
"app-lifecycle-android-smoke.mobile.recipe.json": ["app.lifecycle.android-smoke.mobile.recipe.json"],
|
|
240
|
-
"order-lifecycle": ["perps.order-lifecycle"],
|
|
241
|
-
"order-lifecycle.core.recipe.json": ["perps.order-lifecycle.core.recipe.json"],
|
|
242
|
-
"perps-lifecycle": ["perps.lifecycle"],
|
|
243
|
-
"perps-lifecycle.recipe.json": ["perps.lifecycle.recipe.json"],
|
|
244
|
-
"perps-performance": ["perps.performance"],
|
|
245
|
-
"perps-performance-background-resume": ["perps.performance.background-resume"],
|
|
246
|
-
"perps-performance-background-resume.mobile.recipe.json": ["perps.performance.background-resume.mobile.recipe.json"],
|
|
247
|
-
"perps-performance-cold-start": ["perps.performance.cold-start"],
|
|
248
|
-
"perps-performance-cold-start.mobile.recipe.json": ["perps.performance.cold-start.mobile.recipe.json"],
|
|
249
|
-
"perps-performance-warm-start": ["perps.performance.warm-start"],
|
|
250
|
-
"perps-performance-warm-start.mobile.recipe.json": ["perps.performance.warm-start.mobile.recipe.json"],
|
|
251
|
-
"perps-performance.mobile.recipe.json": ["perps.performance.mobile.recipe.json"],
|
|
252
|
-
"read-markets": ["perps.read-markets"],
|
|
253
|
-
"read-markets.core.recipe.json": ["perps.read-markets.core.recipe.json"],
|
|
254
|
-
"smoke": ["runner.smoke"],
|
|
255
|
-
"smoke.extension.recipe.json": ["runner.smoke.extension.recipe.json"],
|
|
256
|
-
"smoke.mobile.recipe.json": ["runner.smoke.mobile.recipe.json"],
|
|
257
|
-
"trading-lifecycle": ["perps.trading-lifecycle"],
|
|
258
|
-
"trading-lifecycle.core.recipe.json": ["perps.trading-lifecycle.core.recipe.json"]
|
|
259
|
-
};
|
|
260
229
|
async function validateRunRecipeStatic(recipeArg, adapter, options) {
|
|
261
230
|
const librarySources = await resolveMetaMaskLibrarySources(optionString(options, "library"));
|
|
262
231
|
const resolved = resolveRunRecipeArg(recipeArg, adapter, librarySources);
|
|
@@ -34,7 +34,7 @@ function renderRunReport(summary, entries) {
|
|
|
34
34
|
lines.push(
|
|
35
35
|
"",
|
|
36
36
|
"## Side findings",
|
|
37
|
-
`- REVIEW ${sideFindingTotal} distinct application warning/error event(s);
|
|
37
|
+
`- REVIEW ${sideFindingTotal} distinct application warning/error event(s) (non-blocking; expanded below and stored in diagnostics.json)`
|
|
38
38
|
);
|
|
39
39
|
}
|
|
40
40
|
lines.push("", "## Steps");
|
package/dist/commands/run.js
CHANGED
|
@@ -31,6 +31,7 @@ import { coreDependencyBlock } from "./core-readiness.js";
|
|
|
31
31
|
import { writeRunReport } from "./run-report.js";
|
|
32
32
|
import { acquireCheckoutLock } from "../checkout-lock.js";
|
|
33
33
|
import { JsonStreamWriter } from "../json-stream.js";
|
|
34
|
+
import { formatRunDiagnosticsForHuman, readRunDiagnosticsDocument } from "../run-diagnostics.js";
|
|
34
35
|
async function handleRun(parsed) {
|
|
35
36
|
const stream = new JsonStreamWriter("run", optionFlag(parsed.options, "jsonStream"));
|
|
36
37
|
const restoreStdout = stream.isolateStdout();
|
|
@@ -211,15 +212,61 @@ async function handleRunInner({ positional, options }, stream) {
|
|
|
211
212
|
console.log(out("label", "summary:"));
|
|
212
213
|
for (const line of report.preview) console.log(` ${formatPreviewLine(line, out)}`);
|
|
213
214
|
}
|
|
214
|
-
|
|
215
|
-
console.log(
|
|
216
|
-
|
|
215
|
+
const artifacts = runArtifactInventory(result.artifactManifestPath);
|
|
216
|
+
console.log(out("label", `artifacts (${artifacts.length}):`));
|
|
217
|
+
for (const artifact of artifacts) {
|
|
218
|
+
console.log(` ${out("dim", `${artifact.label}:`)} ${out("path", artifact.absolutePath)}`);
|
|
219
|
+
}
|
|
220
|
+
if (result.status === "fail") {
|
|
221
|
+
console.error(` Next: ${failureUserAction}`);
|
|
222
|
+
}
|
|
223
|
+
console.log(out("label", "diagnostics:"));
|
|
224
|
+
const diagnostics = readRunDiagnosticsDocument(result.diagnosticsPath);
|
|
225
|
+
for (const line of formatRunDiagnosticsForHuman(diagnostics, adapter)) {
|
|
226
|
+
console.log(` ${formatDiagnosticLine(line, out)}`);
|
|
227
|
+
}
|
|
217
228
|
}
|
|
218
229
|
return exitCode;
|
|
219
230
|
} finally {
|
|
220
231
|
lock.release();
|
|
221
232
|
}
|
|
222
233
|
}
|
|
234
|
+
function formatDiagnosticLine(line, out) {
|
|
235
|
+
const match = /^(CLEAN|REVIEW|UNAVAILABLE|N\/A|WARNING|ERROR|EXCEPTION)(.*)$/u.exec(line);
|
|
236
|
+
if (!match) return line;
|
|
237
|
+
const status = match[1];
|
|
238
|
+
const style = status === "CLEAN" ? "ok" : status === "ERROR" || status === "EXCEPTION" ? "err" : "warn";
|
|
239
|
+
return `${out(style, status)}${match[2]}`;
|
|
240
|
+
}
|
|
241
|
+
function runArtifactInventory(manifestPathValue) {
|
|
242
|
+
const manifestPath = path.resolve(String(manifestPathValue));
|
|
243
|
+
const root = path.dirname(manifestPath);
|
|
244
|
+
let manifest;
|
|
245
|
+
try {
|
|
246
|
+
manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
|
247
|
+
} catch {
|
|
248
|
+
return [{ absolutePath: manifestPath, basename: path.basename(manifestPath), label: "Artifact manifest" }];
|
|
249
|
+
}
|
|
250
|
+
const entries = isRecord(manifest) && Array.isArray(manifest.artifacts) ? manifest.artifacts : [];
|
|
251
|
+
const artifacts = [];
|
|
252
|
+
for (const entry of entries) {
|
|
253
|
+
if (!isRecord(entry) || typeof entry.path !== "string" || entry.path.length === 0) continue;
|
|
254
|
+
const absolutePath = path.resolve(root, entry.path);
|
|
255
|
+
const basename = path.basename(absolutePath);
|
|
256
|
+
const label = typeof entry.label === "string" && entry.label.length > 0 ? entry.label : basename;
|
|
257
|
+
artifacts.push({ absolutePath, basename, label });
|
|
258
|
+
}
|
|
259
|
+
artifacts.push({ absolutePath: manifestPath, basename: path.basename(manifestPath), label: "Artifact manifest" });
|
|
260
|
+
const priority = /* @__PURE__ */ new Map([
|
|
261
|
+
["diagnostics.json", 0],
|
|
262
|
+
["report.md", 1],
|
|
263
|
+
["summary.json", 2],
|
|
264
|
+
["trace.json", 3],
|
|
265
|
+
["recipe.json", 4],
|
|
266
|
+
["artifact-manifest.json", 100]
|
|
267
|
+
]);
|
|
268
|
+
return artifacts.filter((artifact, index, all) => all.findIndex((candidate) => candidate.absolutePath === artifact.absolutePath) === index).sort((left, right) => (priority.get(left.basename) ?? 50) - (priority.get(right.basename) ?? 50));
|
|
269
|
+
}
|
|
223
270
|
function formatPreviewLine(line, out) {
|
|
224
271
|
const match = /^(PASS|FAIL)\s+(.+)$/u.exec(line);
|
|
225
272
|
if (!match) return line;
|
|
@@ -34,7 +34,7 @@ function parseBridgeEntries(raw) {
|
|
|
34
34
|
deviceName: typeof e.deviceName === "string" ? e.deviceName : "",
|
|
35
35
|
platform: typeof e.platform === "string" ? e.platform : "",
|
|
36
36
|
// agenticPresent is only set when the bridge explicitly sends it (false = absent).
|
|
37
|
-
//
|
|
37
|
+
// An omitted value remains unknown.
|
|
38
38
|
...typeof e.agenticPresent === "boolean" ? { agenticPresent: e.agenticPresent } : {}
|
|
39
39
|
}));
|
|
40
40
|
}
|