@deeeed/metamask-harness 0.17.3 → 0.17.5
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 +6 -96
- package/README.md +136 -96
- package/adapters/extension/console-tail.mjs +24 -51
- package/adapters/extension/ensure-browser.sh +7 -1
- package/adapters/extension/inject.mjs +15 -5
- package/adapters/extension/launch-browser.cjs +4 -3
- package/adapters/extension/launch.sh +11 -8
- package/adapters/extension/lib/chrome-args.cjs +24 -0
- package/adapters/extension/live.sh +1 -2
- package/adapters/extension/reattach.sh +0 -1
- package/adapters/extension/seed-fixture.sh +12 -4
- package/adapters/extension/sidepanel-toggle.sh +1 -4
- package/adapters/extension/start-watch.sh +4 -17
- package/adapters/extension/stop-viewers.sh +1 -1
- package/adapters/extension/verify.sh +1 -1
- package/adapters/manifest.json +45 -53
- package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +0 -16
- package/adapters/mobile/bridge-runtime/console-forwarder.cjs +1 -1
- package/adapters/mobile/bridge-runtime/lib/match-bridge-target.cjs +1 -3
- package/adapters/mobile/bridge-runtime/lib/target-discovery.cjs +3 -2
- package/adapters/mobile/cleanup.sh +176 -16
- package/adapters/mobile/inject.sh +671 -90
- package/adapters/mobile/overlay/app/dev-tools/AgenticService/AgentStepHud.tsx.patch +185 -0
- package/adapters/mobile/overlay/app/dev-tools/AgenticService/AgenticService.ts.patch +1662 -0
- package/adapters/mobile/overlay/compat/README.md +28 -0
- package/adapters/mobile/overlay/compat/rn81-message-event-source.patch +42 -0
- package/adapters/mobile/start-metro.sh +41 -0
- package/adapters/mobile/verify.sh +61 -4
- package/adapters/mobile/wait-for-bridge.sh +8 -22
- package/adapters/shared/harness-source-fingerprint.mjs +49 -0
- package/adapters/shared/install-repo-deps.sh +1 -5
- package/adapters/shared/open-debug.mjs +184 -103
- package/adapters/shared/resolve-farmslot-ports-core.mjs +3 -0
- package/adapters/shared/resolve-farmslot-ports.mjs +5 -0
- package/adapters/shared/resolve-farmslot-ports.sh +7 -0
- package/adapters/shared/resolve-slot-ports-core.mjs +6 -0
- package/adapters/shared/resolve-slot-ports.sh +7 -2
- package/bin/mm-harness +0 -7
- package/dist/adapters/extension/harness-freshness.js +39 -0
- package/dist/adapters/extension/runtime-decision.js +4 -73
- package/dist/adapters/extension/surface.js +2 -3
- package/dist/adapters/mobile/perps-env.js +3 -43
- package/dist/adapters/mobile/prepare.js +7 -22
- package/dist/adapters/mobile/source-freshness.js +26 -41
- package/dist/adapters/mobile/surface.js +1 -1
- package/dist/adapters/resolve-slot-ports.js +2 -0
- package/dist/adapters/slot-ports.js +0 -16
- package/dist/adapters.js +5 -12
- package/dist/cli-commands.js +1 -1
- package/dist/cli.js +12 -6
- package/dist/commands/call.js +22 -56
- package/dist/commands/check.js +3 -9
- package/dist/commands/checklist.js +4 -1
- package/dist/commands/debug.js +31 -38
- package/dist/commands/device-target.js +12 -27
- package/dist/commands/doctor.js +23 -77
- package/dist/commands/fixtures.js +17 -27
- package/dist/commands/launch/extension.js +2 -0
- package/dist/commands/launch/index.js +59 -167
- package/dist/commands/logs.js +6 -8
- package/dist/commands/manifest.js +25 -151
- package/dist/commands/parse-args.js +0 -2
- package/dist/commands/provision.js +3 -10
- package/dist/commands/run-engine.js +69 -71
- package/dist/commands/run-report.js +3 -12
- package/dist/commands/run.js +35 -245
- package/dist/commands/shared.js +3 -55
- package/dist/commands/status-probe.js +1 -1
- package/dist/commands/status.js +1 -1
- package/dist/commands/stop.js +2 -7
- package/dist/commands/update.js +28 -16
- package/dist/doctor.js +4 -1
- package/dist/harness.js +134 -43
- package/dist/heal-bounds.js +1 -1
- package/dist/live-adapter-contract.js +20 -6
- package/dist/mm-harness-cli.js +48 -123
- package/dist/paths.js +5 -2
- package/dist/runner.js +2 -37
- package/dist/runtime-context.js +8 -26
- package/docs/ADAPTER-SURFACE.md +112 -19
- package/docs/CHEATSHEET.md +52 -39
- package/docs/CLI-SPEC.md +1098 -0
- package/docs/CODE-MAP.md +1 -1
- package/docs/DEBUG-HANDOVER.md +36 -0
- package/docs/MENTAL-MODEL.md +295 -0
- package/docs/UX-PRINCIPLES.md +1 -3
- package/docs/architecture.md +375 -65
- package/docs/live-adapter-contract.md +2 -4
- package/docs/recipe-libraries.md +275 -45
- package/library/README.md +0 -4
- package/library/actions/core/perps/_controller.mjs +55 -10
- package/library/actions/mobile/platform/bridge.mjs +1 -5
- package/library/manifests/core.action-manifest.json +7 -7
- package/library/manifests/mobile.action-manifest.json +3 -28
- package/package.json +1 -4
- package/adapters/extension/launch-webpack.cjs +0 -83
- package/adapters/extension/stamp-runtime-title.cjs +0 -49
- package/adapters/extension/sync-webpack-dist.cjs +0 -105
- package/adapters/mobile/launch-console-forwarder.cjs +0 -39
- package/adapters/mobile/start-console-forwarder.sh +0 -70
- package/adapters/shared/update-check-worker.mjs +0 -35
- package/dist/adapters/extension/console-capture.js +0 -93
- package/dist/command-contract.js +0 -441
- package/dist/command-journal.js +0 -225
- package/dist/commands/last.js +0 -52
- package/dist/json-stream.js +0 -57
- package/dist/run-diagnostics.js +0 -261
- package/docs/CLI-ERGONOMICS-AUDIT.md +0 -32
- package/docs/CLI-ERGONOMICS-HUMAN-QA.md +0 -64
- package/docs/VIDEO-DEMO-VALIDATION.md +0 -74
- package/library/recipes/runner/smoke.core.recipe.json +0 -27
- package/library/recipes/wallet/smoke.extension.recipe.json +0 -42
- package/library/recipes/wallet/smoke.mobile.recipe.json +0 -42
- package/scripts/validate-human-outcomes.mjs +0 -169
|
@@ -4,6 +4,9 @@ import { createRequire } from "node:module";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { EXIT } from "./shared.js";
|
|
6
6
|
const require2 = createRequire(import.meta.url);
|
|
7
|
+
const MARK_SCRIPT = require2.resolve(
|
|
8
|
+
"@farmslot/agent-runtime/scripts/mark-checklist-step.cjs"
|
|
9
|
+
);
|
|
7
10
|
const USAGE = "mm-harness checklist mark <task-dir> <step> [options]";
|
|
8
11
|
function terminalSignalStatus(taskDir) {
|
|
9
12
|
try {
|
|
@@ -122,7 +125,7 @@ async function handleChecklist(argv) {
|
|
|
122
125
|
teachRecipeCompletion(resolvedTaskDir, markArgs);
|
|
123
126
|
const result = spawnSync(
|
|
124
127
|
process.execPath,
|
|
125
|
-
[
|
|
128
|
+
[MARK_SCRIPT, resolvedTaskDir, ...markArgs],
|
|
126
129
|
{ stdio: "inherit" }
|
|
127
130
|
);
|
|
128
131
|
if (result.error) {
|
package/dist/commands/debug.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { runnerDir } from "../paths.js";
|
|
3
|
-
import { acquireCheckoutLock } from "../checkout-lock.js";
|
|
4
3
|
import { getAdapterSurface } from "../adapters/surface.js";
|
|
5
|
-
import { ADAPTER_DETECT_NEXT,
|
|
4
|
+
import { ADAPTER_DETECT_NEXT, EXIT, flag, parseFlags, resolveAdapter, spawnScript, targetOf, usageOut } from "./shared.js";
|
|
6
5
|
const DEBUG_BOOLEANS = /* @__PURE__ */ new Set(["worker", "devMenu", "json", "noOpen"]);
|
|
7
6
|
async function handleDebug(argv) {
|
|
8
7
|
const { options } = parseFlags(argv, DEBUG_BOOLEANS);
|
|
@@ -32,43 +31,37 @@ async function handleDebug(argv) {
|
|
|
32
31
|
"mm-harness debug --worker"
|
|
33
32
|
);
|
|
34
33
|
}
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
2
|
|
65
|
-
)
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
return result.status === 0 ? EXIT.ok : EXIT.runtime;
|
|
69
|
-
} finally {
|
|
70
|
-
lock.release();
|
|
34
|
+
const openDebug = path.join(runnerDir, "adapters/shared/open-debug.mjs");
|
|
35
|
+
const debugArgs = ["--adapter", adapter];
|
|
36
|
+
if (adapter === "extension") {
|
|
37
|
+
if (process.env.CDP_PORT) debugArgs.push("--cdp-port", process.env.CDP_PORT);
|
|
38
|
+
debugArgs.push("--target", worker ? "worker" : "page");
|
|
39
|
+
} else {
|
|
40
|
+
if (process.env.WATCHER_PORT) debugArgs.push("--port", process.env.WATCHER_PORT);
|
|
41
|
+
debugArgs.push("--action", devMenu ? "dev-menu" : "debug");
|
|
42
|
+
}
|
|
43
|
+
if (json) debugArgs.push("--json");
|
|
44
|
+
if (noOpen) debugArgs.push("--no-open");
|
|
45
|
+
const result = spawnScript(process.execPath, [openDebug, ...debugArgs], target, json);
|
|
46
|
+
if (json) {
|
|
47
|
+
const leaf = parseDebugLeaf(result.output);
|
|
48
|
+
console.log(
|
|
49
|
+
JSON.stringify(
|
|
50
|
+
{
|
|
51
|
+
...leaf,
|
|
52
|
+
schemaVersion: 1,
|
|
53
|
+
command: "debug",
|
|
54
|
+
adapter,
|
|
55
|
+
mode: worker ? "worker" : devMenu ? "dev-menu" : "default",
|
|
56
|
+
opened: leaf.opened === true,
|
|
57
|
+
exitCode: result.status === 0 ? EXIT.ok : EXIT.runtime
|
|
58
|
+
},
|
|
59
|
+
null,
|
|
60
|
+
2
|
|
61
|
+
)
|
|
62
|
+
);
|
|
71
63
|
}
|
|
64
|
+
return result.status === 0 ? EXIT.ok : EXIT.runtime;
|
|
72
65
|
}
|
|
73
66
|
function parseDebugLeaf(output) {
|
|
74
67
|
try {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
2
|
import { listConnectedDevices } from "../devices.js";
|
|
3
|
-
import { optionString
|
|
3
|
+
import { optionString } from "./parse-args.js";
|
|
4
4
|
function normalizeDeviceName(value) {
|
|
5
5
|
return value.replace(/_/gu, " ").trim();
|
|
6
6
|
}
|
|
@@ -75,12 +75,6 @@ function configuredPinForPlatform(platform) {
|
|
|
75
75
|
function oppositePlatform(platform) {
|
|
76
76
|
return platform === "ios" ? "android" : "ios";
|
|
77
77
|
}
|
|
78
|
-
function deviceProbe(options) {
|
|
79
|
-
return `mm-harness status --target ${shellQuoteArg(targetPath(options))} --all-devices --json`;
|
|
80
|
-
}
|
|
81
|
-
function deviceRecovery(options, rerun) {
|
|
82
|
-
return rerun ? `${deviceProbe(options)}; then retry: ${rerun}` : deviceProbe(options);
|
|
83
|
-
}
|
|
84
78
|
function applyDeviceTargeting(command, adapter, options, opts) {
|
|
85
79
|
const device = optionString(options, "device");
|
|
86
80
|
const platformPreference = mobilePlatformPreference(options);
|
|
@@ -90,8 +84,7 @@ function applyDeviceTargeting(command, adapter, options, opts) {
|
|
|
90
84
|
ok: false,
|
|
91
85
|
code: "DEVICE_WRONG_ADAPTER",
|
|
92
86
|
message: `--device is only supported on the mobile adapter (the ${adapter} adapter has no device to target).
|
|
93
|
-
Drop --device, or run this ${command} inside a metamask-mobile checkout
|
|
94
|
-
userAction: `remove --device; ${adapter} has no device target`
|
|
87
|
+
Drop --device, or run this ${command} inside a metamask-mobile checkout.`
|
|
95
88
|
};
|
|
96
89
|
}
|
|
97
90
|
return { ok: true };
|
|
@@ -104,8 +97,7 @@ function applyDeviceTargeting(command, adapter, options, opts) {
|
|
|
104
97
|
ok: false,
|
|
105
98
|
code: "DEVICE_PLATFORM_CONFLICT",
|
|
106
99
|
message: `--device ${device} resolves to ${matched.platform}, but --platform ${platformPreference} was requested.
|
|
107
|
-
To use this device intentionally, drop --platform ${platformPreference} or choose a ${platformPreference} device
|
|
108
|
-
userAction: deviceRecovery(options, opts.rerun)
|
|
100
|
+
To use this device intentionally, drop --platform ${platformPreference} or choose a ${platformPreference} device.`
|
|
109
101
|
};
|
|
110
102
|
}
|
|
111
103
|
if (matched.platform === "android") setAndroidDeviceEnv(matched.id, matched.name);
|
|
@@ -134,8 +126,7 @@ function applyDeviceTargeting(command, adapter, options, opts) {
|
|
|
134
126
|
ok: false,
|
|
135
127
|
code: "DEVICE_NAME_AMBIGUOUS",
|
|
136
128
|
message: `device name '${device}' is ambiguous; use the id:
|
|
137
|
-
` + byName.map((d) => ` - ${d.id}${d.name ? ` (${d.name})` : ""} [${d.state}] ${d.platform}`).join("\n")
|
|
138
|
-
userAction: deviceRecovery(options, opts.rerun)
|
|
129
|
+
` + byName.map((d) => ` - ${d.id}${d.name ? ` (${d.name})` : ""} [${d.state}] ${d.platform}`).join("\n")
|
|
139
130
|
};
|
|
140
131
|
}
|
|
141
132
|
return {
|
|
@@ -143,8 +134,7 @@ function applyDeviceTargeting(command, adapter, options, opts) {
|
|
|
143
134
|
code: "DEVICE_NOT_FOUND",
|
|
144
135
|
message: `--device ${device} did not match any connected device.
|
|
145
136
|
` + (connected2.length > 0 ? ` Connected devices:
|
|
146
|
-
${formatConnectedDevices(connected2)}` : ` No devices are currently connected (adb devices / booted iOS simulators).`)
|
|
147
|
-
userAction: deviceRecovery(options, opts.rerun)
|
|
137
|
+
${formatConnectedDevices(connected2)}` : ` No devices are currently connected (adb devices / booted iOS simulators).`)
|
|
148
138
|
};
|
|
149
139
|
}
|
|
150
140
|
const connected = listConnectedDevices();
|
|
@@ -168,8 +158,7 @@ ${formatConnectedDevices(connected2)}` : ` No devices are currently connected (
|
|
|
168
158
|
message: `--platform ${platformPreference} conflicts with the current slot-selected device.
|
|
169
159
|
Selected devices:
|
|
170
160
|
${formatConnectedDevices(selectedTargetable2)}
|
|
171
|
-
To use a different connected device intentionally, pass --device <id
|
|
172
|
-
userAction: deviceRecovery(options, opts.rerun)
|
|
161
|
+
To use a different connected device intentionally, pass --device <id>.`
|
|
173
162
|
};
|
|
174
163
|
}
|
|
175
164
|
if (!configuredPin && oppositePin) {
|
|
@@ -178,8 +167,7 @@ ${formatConnectedDevices(selectedTargetable2)}
|
|
|
178
167
|
code: "DEVICE_PLATFORM_CONFLICT",
|
|
179
168
|
message: `--platform ${platformPreference} conflicts with the configured slot target.
|
|
180
169
|
Configured ${oppositePlatform(platformPreference)} target: ${oppositePin}
|
|
181
|
-
To use a different connected device intentionally, pass --device <id
|
|
182
|
-
userAction: deviceRecovery(options, opts.rerun)
|
|
170
|
+
To use a different connected device intentionally, pass --device <id>.`
|
|
183
171
|
};
|
|
184
172
|
}
|
|
185
173
|
if (configuredPin) {
|
|
@@ -190,8 +178,8 @@ ${formatConnectedDevices(selectedTargetable2)}
|
|
|
190
178
|
message: `--platform ${platformPreference} matches the configured slot target, but that target is not ready.
|
|
191
179
|
Configured target: ${configuredPin}
|
|
192
180
|
Connected devices:
|
|
193
|
-
${connected.length > 0 ? formatConnectedDevices(connected) : " - none"}
|
|
194
|
-
|
|
181
|
+
${connected.length > 0 ? formatConnectedDevices(connected) : " - none"}
|
|
182
|
+
Next: run mm-harness launch ${platformPreference}, or pass --device <id> to intentionally use another target.`
|
|
195
183
|
};
|
|
196
184
|
}
|
|
197
185
|
if (platformTargetable.length === 1) {
|
|
@@ -207,8 +195,7 @@ ${connected.length > 0 ? formatConnectedDevices(connected) : " - none"}`,
|
|
|
207
195
|
code: "DEVICE_PLATFORM_NOT_FOUND",
|
|
208
196
|
message: `--platform ${platformPreference} did not match any ready connected device.
|
|
209
197
|
` + (connected.length > 0 ? ` Connected devices:
|
|
210
|
-
${formatConnectedDevices(connected)}` : ` No devices are currently connected (adb devices / booted iOS simulators).`)
|
|
211
|
-
userAction: deviceRecovery(options, opts.rerun)
|
|
198
|
+
${formatConnectedDevices(connected)}` : ` No devices are currently connected (adb devices / booted iOS simulators).`)
|
|
212
199
|
};
|
|
213
200
|
}
|
|
214
201
|
return {
|
|
@@ -218,8 +205,7 @@ ${formatConnectedDevices(connected)}` : ` No devices are currently connected (a
|
|
|
218
205
|
Connected devices:
|
|
219
206
|
${formatConnectedDevices(connected)}
|
|
220
207
|
Add --device <id> to disambiguate, e.g.:${platformTargetable.map((d) => `
|
|
221
|
-
--device ${d.id}`).join("")}
|
|
222
|
-
userAction: deviceRecovery(options, opts.rerun)
|
|
208
|
+
--device ${d.id}`).join("")}`
|
|
223
209
|
};
|
|
224
210
|
}
|
|
225
211
|
const selectedTargetable = targetable.filter(deviceSelected);
|
|
@@ -238,8 +224,7 @@ ${formatConnectedDevices(connected)}
|
|
|
238
224
|
Connected devices:
|
|
239
225
|
${formatConnectedDevices(connected)}
|
|
240
226
|
` + (selectedTargetable.length > 1 ? ` The current slot context selects more than one target; fix the slot device pins or add --device <id>.` : ` Add --device <id> to disambiguate, e.g.:${targetable.map((d) => `
|
|
241
|
-
--device ${d.id}`).join("")}`)
|
|
242
|
-
userAction: deviceRecovery(options, opts.rerun)
|
|
227
|
+
--device ${d.id}`).join("")}`)
|
|
243
228
|
};
|
|
244
229
|
}
|
|
245
230
|
return { ok: true };
|
package/dist/commands/doctor.js
CHANGED
|
@@ -4,7 +4,7 @@ import fs from "node:fs";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { color } from "../cli-color.js";
|
|
6
6
|
import { createDoctorReport, fixtureSummary, renderRuntimeContext, requiredDoctorCheckSummary } from "../doctor.js";
|
|
7
|
-
import { detectAdapter
|
|
7
|
+
import { detectAdapter } from "../harness.js";
|
|
8
8
|
import { assertAdapter, runnerDir } from "../paths.js";
|
|
9
9
|
import { getAdapterSurface } from "../adapters/surface.js";
|
|
10
10
|
import { ensureMobilePerpsEnvironment, mobilePerpsEnvironment } from "../adapters/mobile/perps-env.js";
|
|
@@ -21,13 +21,12 @@ import {
|
|
|
21
21
|
import { farmslotReadyIndicator } from "./farmslot-ready.js";
|
|
22
22
|
import { ensureRuntimeContext } from "../runtime-context.js";
|
|
23
23
|
import { acquireCheckoutLock } from "../checkout-lock.js";
|
|
24
|
-
import { ADAPTER_DETECT_NEXT,
|
|
24
|
+
import { ADAPTER_DETECT_NEXT, EXIT, usageOut } from "./shared.js";
|
|
25
25
|
import {
|
|
26
26
|
actionManifestPathOption,
|
|
27
27
|
applyRuntimeDirOption,
|
|
28
28
|
optionFlag,
|
|
29
29
|
optionString,
|
|
30
|
-
shellQuote,
|
|
31
30
|
targetPath
|
|
32
31
|
} from "./parse-args.js";
|
|
33
32
|
function applyDoctorRuntimePorts(options) {
|
|
@@ -57,14 +56,6 @@ async function handleDoctor({ options }) {
|
|
|
57
56
|
return usageOut(json, "doctor", `could not detect the MetaMask repo type for ${target}`, ADAPTER_DETECT_NEXT);
|
|
58
57
|
}
|
|
59
58
|
assertAdapter(adapter);
|
|
60
|
-
if (!fs.existsSync(target)) {
|
|
61
|
-
return usageOut(
|
|
62
|
-
json,
|
|
63
|
-
"doctor",
|
|
64
|
-
`target does not exist: ${target}`,
|
|
65
|
-
"pass --target <metamask-checkout> pointing to an existing checkout"
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
59
|
if (printReady && json) {
|
|
69
60
|
return usageOut(
|
|
70
61
|
json,
|
|
@@ -81,12 +72,12 @@ async function handleDoctor({ options }) {
|
|
|
81
72
|
surface.resolveSlotPorts(target);
|
|
82
73
|
const dtResult = applyDeviceTargeting("doctor", adapter, options, { gate: false, rerun: "" });
|
|
83
74
|
if ("code" in dtResult) {
|
|
84
|
-
return usageOut(json, "doctor", dtResult.message,
|
|
75
|
+
return usageOut(json, "doctor", dtResult.message, "mm-harness doctor --fix --adapter mobile --device <adb-serial|simulator-udid>");
|
|
85
76
|
}
|
|
86
77
|
applyDoctorRuntimePorts(options);
|
|
87
78
|
const lock = acquireCheckoutLock(target, "doctor-fix");
|
|
88
79
|
if ("message" in lock) {
|
|
89
|
-
return
|
|
80
|
+
return usageOut(json, "doctor", lock.message, `wait for the current owner, or inspect ${lock.path} if its process has exited`);
|
|
90
81
|
}
|
|
91
82
|
let fixed;
|
|
92
83
|
let failed;
|
|
@@ -96,16 +87,9 @@ async function handleDoctor({ options }) {
|
|
|
96
87
|
lock.release();
|
|
97
88
|
}
|
|
98
89
|
const result2 = createDoctorReport(adapter, target, manifestValidation, actionManifestPath);
|
|
99
|
-
const nextActions = doctorFixNextActions(failed, result2.fixture
|
|
90
|
+
const nextActions = doctorFixNextActions(failed, result2.fixture);
|
|
100
91
|
const status = result2.status === "pass" && failed.length === 0 ? "pass" : "fail";
|
|
101
|
-
|
|
102
|
-
const fixUserAction = failed.includes("wallet-fixture") ? `choose one wallet fixture source from nextActions, run it, then retry: ${retryDoctor}` : nextActions[0] ?? retryDoctor;
|
|
103
|
-
const error = status === "fail" ? {
|
|
104
|
-
code: "DOCTOR_FIX_INCOMPLETE",
|
|
105
|
-
message: "doctor --fix could not make every required check pass",
|
|
106
|
-
userAction: fixUserAction
|
|
107
|
-
} : void 0;
|
|
108
|
-
if (json) console.log(JSON.stringify({ ...result2, status, fixed, failed, nextActions, ...error ? { error } : {} }, null, 2));
|
|
92
|
+
if (json) console.log(JSON.stringify({ ...result2, status, fixed, failed, nextActions }, null, 2));
|
|
109
93
|
else {
|
|
110
94
|
console.log(`${status} ${adapter} ${result2.compatibilityMode} manifest=${actionManifestPath} fixed=[${fixed.join(",")}] failed=[${failed.join(",")}]`);
|
|
111
95
|
for (const action of nextActions) console.error(` Next: ${action}`);
|
|
@@ -120,9 +104,8 @@ async function handleDoctor({ options }) {
|
|
|
120
104
|
surface.resolveSlotPorts(target);
|
|
121
105
|
const dtResult = applyDeviceTargeting("doctor", adapter, options, { gate: false, rerun: "" });
|
|
122
106
|
if ("code" in dtResult) {
|
|
123
|
-
if (json) console.log(JSON.stringify({ schemaVersion: 1, command: "doctor", adapter, target, error: { code: dtResult.code, message: dtResult.message
|
|
124
|
-
else console.error(`\u2717 doctor: ${dtResult.message}
|
|
125
|
-
Next: ${dtResult.userAction}`);
|
|
107
|
+
if (json) console.log(JSON.stringify({ schemaVersion: 1, command: "doctor", adapter, target, error: { code: dtResult.code, message: dtResult.message } }, null, 2));
|
|
108
|
+
else console.error(`\u2717 doctor: ${dtResult.message}`);
|
|
126
109
|
return EXIT.usage;
|
|
127
110
|
}
|
|
128
111
|
applyDoctorRuntimePorts(options);
|
|
@@ -140,7 +123,7 @@ async function handleDoctor({ options }) {
|
|
|
140
123
|
requiredChecks
|
|
141
124
|
};
|
|
142
125
|
const orphanMetros = adapter === "mobile" ? detectOrphanMetros(target, process.env.WATCHER_PORT) : [];
|
|
143
|
-
const capture =
|
|
126
|
+
const capture = captureHelperHealth();
|
|
144
127
|
const deviceView = adapter === "mobile" ? mobileDeviceView(allDevices) : null;
|
|
145
128
|
const liveView = deviceView ? await mobileDeviceLiveView(target, deviceView) : null;
|
|
146
129
|
const devices = liveView?.devicesWithLive ?? deviceView?.devices ?? [];
|
|
@@ -159,9 +142,7 @@ async function handleDoctor({ options }) {
|
|
|
159
142
|
printReady
|
|
160
143
|
);
|
|
161
144
|
}
|
|
162
|
-
|
|
163
|
-
const doctorError = doctorResult.status === "fail" ? { code: "DOCTOR_CHECKS_FAILED", message: "one or more required doctor checks failed", userAction: doctorUserAction } : void 0;
|
|
164
|
-
if (json) console.log(JSON.stringify({ ...doctorResult, ready: runtime?.decision === "ready", runtime, orphanMetros, capture, devices, additionalReachableDevices, ...doctorError ? { error: doctorError } : {} }, null, 2));
|
|
145
|
+
if (json) console.log(JSON.stringify({ ...doctorResult, ready: runtime?.decision === "ready", runtime, orphanMetros, capture, devices, additionalReachableDevices }, null, 2));
|
|
165
146
|
else {
|
|
166
147
|
const out = (style, text) => color(style, text, { stream: process.stdout });
|
|
167
148
|
const stateStyle = (value, good) => value === good ? "ok" : "warn";
|
|
@@ -192,13 +173,18 @@ async function handleDoctor({ options }) {
|
|
|
192
173
|
);
|
|
193
174
|
console.log(` ${out("dim", "Next: mm-harness stop # reaps every bundler this checkout leaked")}`);
|
|
194
175
|
}
|
|
195
|
-
if (capture)
|
|
176
|
+
if (capture) {
|
|
177
|
+
console.log(`${out("label", "capture:")} ${out(capture.status === "pass" ? "ok" : "warn", capture.status)} ${out("dim", "(capture-helper: screenshots + --record video)")}`);
|
|
178
|
+
if (capture.status !== "pass") {
|
|
179
|
+
if (capture.failing.length > 0) console.log(` ${out("dim", `failing: ${capture.failing.join(", ")}`)}`);
|
|
180
|
+
console.log(` ${out("dim", "Next: grant Screen Recording (System Settings \u2192 Privacy & Security \u2192 Screen Recording), or run: capture-helper doctor --open-permissions")}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
196
183
|
if (adapter === "mobile") {
|
|
197
184
|
if (deviceView) renderMobileDeviceList(deviceView, out);
|
|
198
185
|
if (additionalReachableDevices.length > 0) renderAdditionalReachableDevices(additionalReachableDevices, out);
|
|
199
186
|
if (liveView) renderMobileLiveBlock(deviceView?.devices ?? [], liveView.liveMap, out);
|
|
200
187
|
}
|
|
201
|
-
if (doctorError) console.log(` ${out("dim", `Next: ${doctorUserAction}`)}`);
|
|
202
188
|
}
|
|
203
189
|
return doctorResult.status === "pass" ? 0 : 1;
|
|
204
190
|
}
|
|
@@ -221,21 +207,15 @@ function doctorRuntimeCheck(runtime, error, expectLive) {
|
|
|
221
207
|
detail: runtime?.reasons.join(" | ") || "Runtime readiness probe returned no result."
|
|
222
208
|
};
|
|
223
209
|
}
|
|
224
|
-
function doctorFixNextActions(failed, fixture
|
|
210
|
+
function doctorFixNextActions(failed, fixture) {
|
|
225
211
|
const actions = [];
|
|
226
|
-
const executable = process.env.MM_HARNESS_EXECUTABLE ? shellQuote(process.env.MM_HARNESS_EXECUTABLE) : "mm-harness";
|
|
227
|
-
const scope = `--adapter ${adapter} --target ${shellQuote(target)}`;
|
|
228
212
|
if (failed.includes("wallet-fixture")) {
|
|
229
213
|
const force = fixture.status === "invalid" || fixture.status === "incomplete" ? " --force" : "";
|
|
230
|
-
actions.push(`1)
|
|
231
|
-
actions.push(`2)
|
|
232
|
-
}
|
|
233
|
-
if (failed.includes("runtime-context")) {
|
|
234
|
-
actions.push(
|
|
235
|
-
`inspect ${shellQuote(resolveRuntimeContextPath(target))}, then retry ${executable} doctor --fix ${scope}`
|
|
236
|
-
);
|
|
214
|
+
actions.push(`1) mm-harness fixtures init --from <path>${force} # existing team/test fixture`);
|
|
215
|
+
actions.push(`2) mm-harness fixtures init --dev${force} # disposable public test wallet; never real funds`);
|
|
237
216
|
}
|
|
238
|
-
if (failed.includes("
|
|
217
|
+
if (failed.includes("runtime-context")) actions.push("inspect temp/recipe/runtime/agentic-runtime.json, then retry mm-harness doctor --fix");
|
|
218
|
+
if (failed.includes("overlay")) actions.push("mm-harness install --target .");
|
|
239
219
|
return actions;
|
|
240
220
|
}
|
|
241
221
|
function emitExpectLiveVerdict(adapter, target, runtime, verdict) {
|
|
@@ -261,9 +241,7 @@ function emitExpectLive(adapter, target, runtime, result, orphanMetros, capture,
|
|
|
261
241
|
return EXIT.runtime;
|
|
262
242
|
}
|
|
263
243
|
if (json) {
|
|
264
|
-
|
|
265
|
-
const error = live ? void 0 : { code: "RUNTIME_NOT_LIVE", message: `${adapter} runtime is not live`, userAction };
|
|
266
|
-
console.log(JSON.stringify({ ...result, ready: live, runtime, orphanMetros, capture, devices, additionalReachableDevices, ...error ? { error } : {} }, null, 2));
|
|
244
|
+
console.log(JSON.stringify({ ...result, ready: live, runtime, orphanMetros, capture, devices, additionalReachableDevices }, null, 2));
|
|
267
245
|
return live ? EXIT.ok : EXIT.runtime;
|
|
268
246
|
}
|
|
269
247
|
const out = (style, text) => color(style, text, { stream: process.stdout });
|
|
@@ -274,25 +252,6 @@ function emitExpectLive(adapter, target, runtime, result, orphanMetros, capture,
|
|
|
274
252
|
emitExpectLiveVerdict(adapter, target, runtime, "not-live");
|
|
275
253
|
return EXIT.runtime;
|
|
276
254
|
}
|
|
277
|
-
function captureHealth(adapter) {
|
|
278
|
-
if (adapter === "core") return null;
|
|
279
|
-
const fallback = adapter === "extension" ? ["cdp"] : ["simctl", "adb"];
|
|
280
|
-
const helper = captureHelperHealth();
|
|
281
|
-
if (!helper) {
|
|
282
|
-
return {
|
|
283
|
-
status: "fallback",
|
|
284
|
-
failing: [],
|
|
285
|
-
screenshots: { available: true, providers: fallback },
|
|
286
|
-
recording: { available: false, provider: null }
|
|
287
|
-
};
|
|
288
|
-
}
|
|
289
|
-
return {
|
|
290
|
-
status: helper.status,
|
|
291
|
-
failing: helper.failing,
|
|
292
|
-
screenshots: { available: true, providers: ["capture-helper", ...fallback] },
|
|
293
|
-
recording: { available: helper.status === "pass", provider: helper.status === "pass" ? "capture-helper" : null }
|
|
294
|
-
};
|
|
295
|
-
}
|
|
296
255
|
function captureHelperHealth() {
|
|
297
256
|
if (process.platform !== "darwin") return null;
|
|
298
257
|
const bin = process.env.CAPTURE_HELPER_PATH || "capture-helper";
|
|
@@ -305,18 +264,6 @@ function captureHelperHealth() {
|
|
|
305
264
|
return null;
|
|
306
265
|
}
|
|
307
266
|
}
|
|
308
|
-
function renderCaptureHealth(capture, out) {
|
|
309
|
-
const video = capture.recording.available ? "capture-helper" : "unavailable";
|
|
310
|
-
console.log(
|
|
311
|
-
`${out("label", "capture:")} ${out(capture.status === "pass" ? "ok" : "warn", capture.status)} ${out("dim", `(screenshots: ${capture.screenshots.providers.join(" \u2192 ")}; video: ${video})`)}`
|
|
312
|
-
);
|
|
313
|
-
if (capture.status === "warn") {
|
|
314
|
-
if (capture.failing.length > 0) console.log(` ${out("dim", `failing: ${capture.failing.join(", ")}`)}`);
|
|
315
|
-
console.log(` ${out("dim", "Next: grant Screen Recording (System Settings \u2192 Privacy & Security \u2192 Screen Recording), or run: capture-helper doctor --open-permissions")}`);
|
|
316
|
-
} else if (capture.status === "fallback") {
|
|
317
|
-
console.log(` ${out("dim", "Next: screenshots work without capture-helper; install/configure capture-helper only when recipe video is required")}`);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
267
|
function detectOrphanMetros(target, livePort) {
|
|
321
268
|
try {
|
|
322
269
|
const script = path.join(runnerDir, "adapters/shared/reap-checkout-metros.sh");
|
|
@@ -434,7 +381,6 @@ function readTextSnapshot(file) {
|
|
|
434
381
|
}
|
|
435
382
|
}
|
|
436
383
|
export {
|
|
437
|
-
doctorFixNextActions,
|
|
438
384
|
ensureExtensionPerpsBuildFlag,
|
|
439
385
|
handleDoctor
|
|
440
386
|
};
|
|
@@ -6,9 +6,9 @@ import { getAdapterSurface } from "../adapters/surface.js";
|
|
|
6
6
|
import { fixtureFileSummary, fixtureSummary } from "../doctor.js";
|
|
7
7
|
import { acquireCheckoutLock } from "../checkout-lock.js";
|
|
8
8
|
import { applyDeviceTargeting } from "./device-target.js";
|
|
9
|
-
import { ADAPTER_DETECT_NEXT,
|
|
9
|
+
import { ADAPTER_DETECT_NEXT, EXIT, flag, parseFlags, resolveAdapter, scriptOverride, spawnScript, spawnScriptStreaming, str, targetOf, usageOut } from "./shared.js";
|
|
10
10
|
const FIXTURES_BOOLEANS = /* @__PURE__ */ new Set(["dev", "force", "json"]);
|
|
11
|
-
const
|
|
11
|
+
const MOBILE_FIXTURE_TIMEOUT_MS = 12e4;
|
|
12
12
|
async function handleFixtures(argv, deps) {
|
|
13
13
|
const { options } = parseFlags(argv, FIXTURES_BOOLEANS);
|
|
14
14
|
const json = flag(options, "json");
|
|
@@ -16,7 +16,7 @@ async function handleFixtures(argv, deps) {
|
|
|
16
16
|
if (!fs.existsSync(target)) return handleFixturesLocked(argv, deps);
|
|
17
17
|
const lock = acquireCheckoutLock(target, "fixtures");
|
|
18
18
|
if ("message" in lock) {
|
|
19
|
-
return
|
|
19
|
+
return usageOut(json, "fixtures", lock.message, `wait for the current owner, or inspect ${lock.path} if its process has exited`);
|
|
20
20
|
}
|
|
21
21
|
try {
|
|
22
22
|
return await handleFixturesLocked(argv, deps);
|
|
@@ -56,14 +56,6 @@ 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
|
-
}
|
|
67
59
|
const target = targetOf(options);
|
|
68
60
|
const adapter = resolveAdapter(options, target);
|
|
69
61
|
if (!adapter) {
|
|
@@ -73,7 +65,7 @@ async function handleFixturesLocked(argv, deps) {
|
|
|
73
65
|
surface.resolveSlotPorts(target);
|
|
74
66
|
const dtResult = applyDeviceTargeting("fixtures", adapter, options, { gate: false, rerun: "" });
|
|
75
67
|
if ("code" in dtResult) {
|
|
76
|
-
return usageOut(json, "fixtures", dtResult.message,
|
|
68
|
+
return usageOut(json, "fixtures", dtResult.message, "mm-harness fixtures set --adapter mobile --device <adb-serial|simulator-udid>");
|
|
77
69
|
}
|
|
78
70
|
if (sub === "generate") return fixturesGenerate(adapter, target, options, json);
|
|
79
71
|
if (sub === "finalize") return fixturesFinalize(adapter, target, options, json);
|
|
@@ -104,14 +96,6 @@ async function handleFixturesLocked(argv, deps) {
|
|
|
104
96
|
return exitCode;
|
|
105
97
|
}
|
|
106
98
|
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
|
-
}
|
|
115
99
|
process.stderr.write(`\u2192 fixtures set ${adapter} \u2014 connecting bridge + applying wallet fixture (can take ~30s)\u2026
|
|
116
100
|
`);
|
|
117
101
|
let status;
|
|
@@ -121,8 +105,8 @@ async function handleFixturesLocked(argv, deps) {
|
|
|
121
105
|
const previousAppRoot = process.env.APP_ROOT;
|
|
122
106
|
process.env.APP_ROOT = target;
|
|
123
107
|
try {
|
|
124
|
-
const configuredTimeout = Number(process.env.MM_HARNESS_FIXTURES_SET_TIMEOUT_MS ??
|
|
125
|
-
const timeoutMs = Number.isFinite(configuredTimeout) && configuredTimeout > 0 ? configuredTimeout :
|
|
108
|
+
const configuredTimeout = Number(process.env.MM_HARNESS_FIXTURES_SET_TIMEOUT_MS ?? MOBILE_FIXTURE_TIMEOUT_MS);
|
|
109
|
+
const timeoutMs = Number.isFinite(configuredTimeout) && configuredTimeout > 0 ? configuredTimeout : MOBILE_FIXTURE_TIMEOUT_MS;
|
|
126
110
|
mobileSetupResult = await spawnScriptStreaming(
|
|
127
111
|
setupWalletSh,
|
|
128
112
|
["--fixture", fixturePath],
|
|
@@ -145,6 +129,14 @@ async function handleFixturesLocked(argv, deps) {
|
|
|
145
129
|
console.error(` Next: ${retryHint}`);
|
|
146
130
|
}
|
|
147
131
|
} 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
|
+
}
|
|
148
140
|
const previousFixtureEnv = process.env.RECIPE_WALLET_FIXTURE;
|
|
149
141
|
process.env.RECIPE_WALLET_FIXTURE = fixturePath;
|
|
150
142
|
try {
|
|
@@ -158,7 +150,7 @@ async function handleFixturesLocked(argv, deps) {
|
|
|
158
150
|
console.error(` Next: ${retryHint}`);
|
|
159
151
|
}
|
|
160
152
|
}
|
|
161
|
-
const teaching =
|
|
153
|
+
const teaching = `Wallet fixture applied. Want different accounts? Edit: ${canonicalFixture}`;
|
|
162
154
|
if (json) {
|
|
163
155
|
console.log(
|
|
164
156
|
JSON.stringify(
|
|
@@ -182,7 +174,7 @@ async function handleFixturesLocked(argv, deps) {
|
|
|
182
174
|
2
|
|
183
175
|
)
|
|
184
176
|
);
|
|
185
|
-
} else
|
|
177
|
+
} else {
|
|
186
178
|
console.error(teaching);
|
|
187
179
|
}
|
|
188
180
|
return status === "pass" ? EXIT.ok : EXIT.runtime;
|
|
@@ -306,9 +298,7 @@ async function fixturesFinalize(adapter, target, options, json) {
|
|
|
306
298
|
if (outPath) leafArgs.push("--out", outPath);
|
|
307
299
|
process.stderr.write(`\u2192 fixtures finalize extension \u2014 seeding account labels via CDP on port ${cdpPort} (can take ~30s)\u2026
|
|
308
300
|
`);
|
|
309
|
-
const result = await spawnScriptStreaming(process.execPath, leafArgs, target
|
|
310
|
-
timeoutMs: FIXTURE_STREAM_TIMEOUT_MS
|
|
311
|
-
});
|
|
301
|
+
const result = await spawnScriptStreaming(process.execPath, leafArgs, target);
|
|
312
302
|
const status = result.status === 0 ? "pass" : "fail";
|
|
313
303
|
const exitCode = result.status === 0 ? EXIT.ok : EXIT.runtime;
|
|
314
304
|
const retry = `mm-harness fixtures finalize --fixture ${fixturePath} --state ${statePath} --cdp-port ${cdpPort} --extension-dir ${extensionDirPath}`;
|
|
@@ -7,6 +7,7 @@ import { recipeHarnessPath, recipeRuntimeDir, runnerDir } from "../../paths.js";
|
|
|
7
7
|
import { extensionIdFromKey } from "../../adapters/extension/extension-id.js";
|
|
8
8
|
import { isExtensionDistStale } from "../../adapters/extension/runtime-decision.js";
|
|
9
9
|
import { checkExtensionRuntimeHealth } from "../../adapters/extension/runtime.js";
|
|
10
|
+
import { ensureExtensionHarnessFresh } from "../../adapters/extension/harness-freshness.js";
|
|
10
11
|
import { stopExtensionWatcher } from "../../adapters/slot-ports.js";
|
|
11
12
|
import { spawnScriptStreaming } from "../shared.js";
|
|
12
13
|
function extensionDepsBlock(target) {
|
|
@@ -29,6 +30,7 @@ function installExtensionDeps(target) {
|
|
|
29
30
|
return spawnScriptStreaming(installScript, ["--target", target], target);
|
|
30
31
|
}
|
|
31
32
|
async function launchExtension(target, tier, wantWatch, displayMode = "fullscreen") {
|
|
33
|
+
await ensureExtensionHarnessFresh(target);
|
|
32
34
|
if (wantWatch) {
|
|
33
35
|
const startWatchSh = path.join(runnerDir, "adapters/extension/start-watch.sh");
|
|
34
36
|
const watchArgs = ["--target", target];
|