@deeeed/metamask-harness 0.17.5 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1059 -0
- package/README.md +91 -140
- package/adapters/core/inject.sh +1 -6
- package/adapters/extension/console-tail.mjs +55 -24
- package/adapters/extension/ensure-browser.sh +6 -1
- package/adapters/extension/inject.mjs +7 -13
- package/adapters/extension/launch-browser.cjs +7 -1
- package/adapters/extension/launch-webpack.cjs +83 -0
- package/adapters/extension/launch.sh +8 -11
- package/adapters/extension/lib/chrome-args.cjs +8 -1
- package/adapters/extension/lib/macos-focus.cjs +32 -0
- package/adapters/extension/live.sh +12 -21
- package/adapters/extension/reattach.sh +1 -0
- package/adapters/extension/seed-fixture.sh +4 -12
- package/adapters/extension/sidepanel-toggle.sh +4 -1
- package/adapters/extension/stamp-runtime-title.cjs +58 -0
- package/adapters/extension/start-watch.sh +17 -4
- package/adapters/extension/stop-viewers.sh +1 -1
- package/adapters/extension/sync-webpack-dist.cjs +107 -0
- package/adapters/extension/verify.sh +2 -2
- package/adapters/manifest.json +53 -37
- package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +16 -0
- package/adapters/mobile/bridge-runtime/console-forwarder.cjs +1 -1
- package/adapters/mobile/bridge-runtime/lib/match-bridge-target.cjs +3 -1
- package/adapters/mobile/bridge-runtime/lib/target-discovery.cjs +2 -3
- package/adapters/mobile/cleanup.sh +16 -176
- package/adapters/mobile/inject.sh +91 -671
- package/adapters/mobile/launch-console-forwarder.cjs +39 -0
- package/adapters/mobile/open-device.sh +45 -7
- package/adapters/mobile/start-console-forwarder.sh +70 -0
- package/adapters/mobile/start-metro.sh +0 -41
- package/adapters/mobile/verify.sh +34 -83
- package/adapters/mobile/wait-for-bridge.sh +22 -8
- package/adapters/shared/harness-source-fingerprint.mjs +14 -12
- package/adapters/shared/resolve-slot-ports-core.mjs +17 -6
- package/adapters/shared/resolve-slot-ports.sh +17 -5
- package/adapters/shared/update-check-worker.mjs +43 -0
- package/bin/mm-harness +21 -2
- package/dist/adapters/core/surface.js +4 -1
- package/dist/adapters/extension/console-capture.js +95 -0
- package/dist/adapters/extension/product-config.js +110 -0
- package/dist/adapters/extension/runtime-decision.js +20 -2
- package/dist/adapters/extension/surface.js +22 -3
- package/dist/adapters/harness-freshness.js +49 -0
- package/dist/adapters/mobile/perps-env.js +43 -3
- package/dist/adapters/mobile/prepare.js +39 -7
- package/dist/adapters/mobile/surface.js +5 -2
- package/dist/adapters/resolve-slot-ports.js +2 -2
- package/dist/adapters/slot-ports.js +13 -16
- package/dist/adapters.js +56 -16
- package/dist/checkout-lock.js +27 -2
- package/dist/cli-color.js +19 -0
- package/dist/cli-commands.js +1 -1
- package/dist/cli.js +7 -18
- package/dist/command-contract.js +462 -0
- package/dist/command-journal.js +263 -0
- package/dist/commands/call.js +252 -57
- package/dist/commands/check.js +10 -4
- package/dist/commands/completion-candidates.js +17 -22
- package/dist/commands/device-target.js +27 -12
- package/dist/commands/doctor.js +106 -25
- package/dist/commands/fixtures.js +142 -35
- package/dist/commands/last.js +60 -0
- package/dist/commands/launch/extension.js +40 -17
- package/dist/commands/launch/index.js +207 -63
- package/dist/commands/launch/mobile.js +2 -0
- package/dist/commands/list-executables.js +187 -29
- package/dist/commands/logs.js +8 -6
- package/dist/commands/manifest.js +286 -35
- package/dist/commands/parse-args.js +68 -4
- package/dist/commands/provision.js +10 -4
- package/dist/commands/run-engine.js +772 -264
- package/dist/commands/run-report.js +12 -3
- package/dist/commands/run.js +378 -85
- package/dist/commands/shared.js +76 -4
- package/dist/commands/status-probe.js +4 -1
- package/dist/commands/status.js +2 -1
- package/dist/commands/stop.js +7 -2
- package/dist/commands/update.js +16 -28
- package/dist/completions-cache.js +1 -1
- package/dist/doctor.js +57 -10
- package/dist/harness.js +47 -137
- package/dist/heal-bounds.js +7 -2
- package/dist/json-stream.js +57 -0
- package/dist/live-adapter-contract.js +138 -32
- package/dist/manifest.js +161 -1
- package/dist/mm-harness-cli.js +146 -73
- package/dist/paths.js +2 -5
- package/dist/recipe-security.js +178 -0
- package/dist/run-diagnostics.js +261 -0
- package/dist/run-recording.js +1 -1
- package/dist/runner.js +140 -9
- package/docs/CONTRIBUTING.md +136 -0
- package/docs/QA.md +184 -0
- package/docs/RECIPES.md +122 -0
- package/docs/SECURITY.md +88 -0
- package/library/README.md +7 -3
- package/library/actions/core/perps/_controller.mjs +43 -56
- package/library/actions/core/perps/assert_orders.mjs +6 -7
- package/library/actions/core/perps/assert_positions.mjs +6 -7
- package/library/actions/core/perps/close_orders.mjs +2 -0
- package/library/actions/core/perps/close_positions.mjs +2 -0
- package/library/actions/core/perps/ensure_orders.mjs +4 -2
- package/library/actions/core/perps/ensure_positions.mjs +4 -2
- package/library/actions/core/perps/place_order.mjs +7 -3
- package/library/actions/core/perps/read_account.mjs +2 -2
- package/library/actions/core/perps/read_orders.mjs +2 -1
- package/library/actions/core/perps/read_positions.mjs +2 -1
- package/library/actions/core/wallet/list_accounts.mjs +95 -0
- package/library/actions/extension/perps/assert_orders.mjs +2 -1
- package/library/actions/extension/perps/assert_positions.mjs +2 -1
- package/library/actions/extension/perps/perps.mjs +43 -14
- package/library/actions/extension/platform/cdp.mjs +1 -0
- package/library/actions/extension/wallet/list_accounts.mjs +41 -0
- package/library/actions/mobile/perps/assert_orders.mjs +2 -1
- package/library/actions/mobile/perps/assert_positions.mjs +2 -1
- package/library/actions/mobile/perps/perps.mjs +40 -12
- package/library/actions/mobile/wallet/list_accounts.mjs +37 -0
- package/library/library.json +1 -1
- package/library/manifests/core.action-manifest.json +1214 -396
- package/library/manifests/extension.action-manifest.json +1635 -728
- package/library/manifests/mobile.action-manifest.json +1740 -727
- package/library/recipes/app/lifecycle.android-smoke.mobile.recipe.json +63 -81
- package/library/recipes/perps/clean-market-testnet.core.recipe.json +44 -0
- package/library/recipes/perps/clean-market-testnet.recipe.json +49 -0
- package/library/recipes/perps/lifecycle.recipe.json +136 -180
- package/library/recipes/perps/order-lifecycle.core.recipe.json +71 -67
- package/library/recipes/perps/performance.background-resume.mobile.recipe.json +51 -67
- package/library/recipes/perps/performance.cold-start.mobile.recipe.json +51 -67
- package/library/recipes/perps/performance.mobile.recipe.json +37 -51
- package/library/recipes/perps/performance.warm-start.mobile.recipe.json +44 -59
- package/library/recipes/perps/read-markets.core.recipe.json +29 -31
- package/library/recipes/perps/smoke.core.recipe.json +29 -32
- package/library/recipes/perps/smoke.extension.recipe.json +41 -44
- package/library/recipes/perps/smoke.mobile.recipe.json +42 -44
- package/library/recipes/perps/trading-lifecycle.core.recipe.json +69 -65
- package/library/recipes/runner/action-validation.extension.recipe.json +312 -398
- package/library/recipes/runner/action-validation.mobile.recipe.json +316 -402
- package/library/recipes/runner/smoke.core.recipe.json +25 -0
- package/library/recipes/runner/smoke.extension.recipe.json +23 -24
- package/library/recipes/runner/smoke.mobile.recipe.json +23 -24
- package/library/recipes/wallet/smoke.extension.recipe.json +40 -0
- package/library/recipes/wallet/smoke.mobile.recipe.json +40 -0
- package/package.json +12 -5
- package/scripts/completions.sh +8 -11
- package/scripts/validate-human-outcomes.mjs +169 -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/dist/adapters/extension/harness-freshness.js +0 -39
- package/dist/commands/flows.js +0 -62
- package/docs/ADAPTER-SURFACE.md +0 -119
- package/docs/CHEATSHEET.md +0 -61
- package/docs/CLI-SPEC.md +0 -1098
- package/docs/CODE-MAP.md +0 -62
- package/docs/DEBUG-HANDOVER.md +0 -36
- package/docs/MENTAL-MODEL.md +0 -295
- package/docs/UX-PRINCIPLES.md +0 -64
- package/docs/architecture.md +0 -398
- package/docs/live-adapter-contract.md +0 -188
- package/docs/package-boundaries.md +0 -47
- package/docs/perps-flow-catalog.md +0 -235
- package/docs/recipe-libraries.md +0 -301
- package/docs/runtime-file-conventions.md +0 -36
- package/library/flows/perps.flows.json +0 -64
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
2
|
import { listConnectedDevices } from "../devices.js";
|
|
3
|
-
import { optionString } from "./parse-args.js";
|
|
3
|
+
import { optionString, shellQuoteArg, targetPath } from "./parse-args.js";
|
|
4
4
|
function normalizeDeviceName(value) {
|
|
5
5
|
return value.replace(/_/gu, " ").trim();
|
|
6
6
|
}
|
|
@@ -75,6 +75,12 @@ 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
|
+
}
|
|
78
84
|
function applyDeviceTargeting(command, adapter, options, opts) {
|
|
79
85
|
const device = optionString(options, "device");
|
|
80
86
|
const platformPreference = mobilePlatformPreference(options);
|
|
@@ -84,7 +90,8 @@ function applyDeviceTargeting(command, adapter, options, opts) {
|
|
|
84
90
|
ok: false,
|
|
85
91
|
code: "DEVICE_WRONG_ADAPTER",
|
|
86
92
|
message: `--device is only supported on the mobile adapter (the ${adapter} adapter has no device to target).
|
|
87
|
-
Drop --device, or run this ${command} inside a metamask-mobile checkout
|
|
93
|
+
Drop --device, or run this ${command} inside a metamask-mobile checkout.`,
|
|
94
|
+
userAction: `remove --device; ${adapter} has no device target`
|
|
88
95
|
};
|
|
89
96
|
}
|
|
90
97
|
return { ok: true };
|
|
@@ -97,7 +104,8 @@ function applyDeviceTargeting(command, adapter, options, opts) {
|
|
|
97
104
|
ok: false,
|
|
98
105
|
code: "DEVICE_PLATFORM_CONFLICT",
|
|
99
106
|
message: `--device ${device} resolves to ${matched.platform}, but --platform ${platformPreference} was requested.
|
|
100
|
-
To use this device intentionally, drop --platform ${platformPreference} or choose a ${platformPreference} device
|
|
107
|
+
To use this device intentionally, drop --platform ${platformPreference} or choose a ${platformPreference} device.`,
|
|
108
|
+
userAction: deviceRecovery(options, opts.rerun)
|
|
101
109
|
};
|
|
102
110
|
}
|
|
103
111
|
if (matched.platform === "android") setAndroidDeviceEnv(matched.id, matched.name);
|
|
@@ -126,7 +134,8 @@ function applyDeviceTargeting(command, adapter, options, opts) {
|
|
|
126
134
|
ok: false,
|
|
127
135
|
code: "DEVICE_NAME_AMBIGUOUS",
|
|
128
136
|
message: `device name '${device}' is ambiguous; use the id:
|
|
129
|
-
` + byName.map((d) => ` - ${d.id}${d.name ? ` (${d.name})` : ""} [${d.state}] ${d.platform}`).join("\n")
|
|
137
|
+
` + byName.map((d) => ` - ${d.id}${d.name ? ` (${d.name})` : ""} [${d.state}] ${d.platform}`).join("\n"),
|
|
138
|
+
userAction: deviceRecovery(options, opts.rerun)
|
|
130
139
|
};
|
|
131
140
|
}
|
|
132
141
|
return {
|
|
@@ -134,7 +143,8 @@ function applyDeviceTargeting(command, adapter, options, opts) {
|
|
|
134
143
|
code: "DEVICE_NOT_FOUND",
|
|
135
144
|
message: `--device ${device} did not match any connected device.
|
|
136
145
|
` + (connected2.length > 0 ? ` Connected devices:
|
|
137
|
-
${formatConnectedDevices(connected2)}` : ` No devices are currently connected (adb devices / booted iOS simulators).`)
|
|
146
|
+
${formatConnectedDevices(connected2)}` : ` No devices are currently connected (adb devices / booted iOS simulators).`),
|
|
147
|
+
userAction: deviceRecovery(options, opts.rerun)
|
|
138
148
|
};
|
|
139
149
|
}
|
|
140
150
|
const connected = listConnectedDevices();
|
|
@@ -158,7 +168,8 @@ ${formatConnectedDevices(connected2)}` : ` No devices are currently connected (
|
|
|
158
168
|
message: `--platform ${platformPreference} conflicts with the current slot-selected device.
|
|
159
169
|
Selected devices:
|
|
160
170
|
${formatConnectedDevices(selectedTargetable2)}
|
|
161
|
-
To use a different connected device intentionally, pass --device <id
|
|
171
|
+
To use a different connected device intentionally, pass --device <id>.`,
|
|
172
|
+
userAction: deviceRecovery(options, opts.rerun)
|
|
162
173
|
};
|
|
163
174
|
}
|
|
164
175
|
if (!configuredPin && oppositePin) {
|
|
@@ -167,7 +178,8 @@ ${formatConnectedDevices(selectedTargetable2)}
|
|
|
167
178
|
code: "DEVICE_PLATFORM_CONFLICT",
|
|
168
179
|
message: `--platform ${platformPreference} conflicts with the configured slot target.
|
|
169
180
|
Configured ${oppositePlatform(platformPreference)} target: ${oppositePin}
|
|
170
|
-
To use a different connected device intentionally, pass --device <id
|
|
181
|
+
To use a different connected device intentionally, pass --device <id>.`,
|
|
182
|
+
userAction: deviceRecovery(options, opts.rerun)
|
|
171
183
|
};
|
|
172
184
|
}
|
|
173
185
|
if (configuredPin) {
|
|
@@ -178,8 +190,8 @@ ${formatConnectedDevices(selectedTargetable2)}
|
|
|
178
190
|
message: `--platform ${platformPreference} matches the configured slot target, but that target is not ready.
|
|
179
191
|
Configured target: ${configuredPin}
|
|
180
192
|
Connected devices:
|
|
181
|
-
${connected.length > 0 ? formatConnectedDevices(connected) : " - none"}
|
|
182
|
-
|
|
193
|
+
${connected.length > 0 ? formatConnectedDevices(connected) : " - none"}`,
|
|
194
|
+
userAction: `mm-harness launch ${platformPreference} --target ${shellQuoteArg(targetPath(options))}`
|
|
183
195
|
};
|
|
184
196
|
}
|
|
185
197
|
if (platformTargetable.length === 1) {
|
|
@@ -195,7 +207,8 @@ ${connected.length > 0 ? formatConnectedDevices(connected) : " - none"}
|
|
|
195
207
|
code: "DEVICE_PLATFORM_NOT_FOUND",
|
|
196
208
|
message: `--platform ${platformPreference} did not match any ready connected device.
|
|
197
209
|
` + (connected.length > 0 ? ` Connected devices:
|
|
198
|
-
${formatConnectedDevices(connected)}` : ` No devices are currently connected (adb devices / booted iOS simulators).`)
|
|
210
|
+
${formatConnectedDevices(connected)}` : ` No devices are currently connected (adb devices / booted iOS simulators).`),
|
|
211
|
+
userAction: deviceRecovery(options, opts.rerun)
|
|
199
212
|
};
|
|
200
213
|
}
|
|
201
214
|
return {
|
|
@@ -205,7 +218,8 @@ ${formatConnectedDevices(connected)}` : ` No devices are currently connected (a
|
|
|
205
218
|
Connected devices:
|
|
206
219
|
${formatConnectedDevices(connected)}
|
|
207
220
|
Add --device <id> to disambiguate, e.g.:${platformTargetable.map((d) => `
|
|
208
|
-
--device ${d.id}`).join("")}
|
|
221
|
+
--device ${d.id}`).join("")}`,
|
|
222
|
+
userAction: deviceRecovery(options, opts.rerun)
|
|
209
223
|
};
|
|
210
224
|
}
|
|
211
225
|
const selectedTargetable = targetable.filter(deviceSelected);
|
|
@@ -224,7 +238,8 @@ ${formatConnectedDevices(connected)}
|
|
|
224
238
|
Connected devices:
|
|
225
239
|
${formatConnectedDevices(connected)}
|
|
226
240
|
` + (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) => `
|
|
227
|
-
--device ${d.id}`).join("")}`)
|
|
241
|
+
--device ${d.id}`).join("")}`),
|
|
242
|
+
userAction: deviceRecovery(options, opts.rerun)
|
|
228
243
|
};
|
|
229
244
|
}
|
|
230
245
|
return { ok: true };
|
package/dist/commands/doctor.js
CHANGED
|
@@ -4,9 +4,10 @@ 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 } from "../harness.js";
|
|
7
|
+
import { detectAdapter, resolveRuntimeContextPath } from "../harness.js";
|
|
8
8
|
import { assertAdapter, runnerDir } from "../paths.js";
|
|
9
9
|
import { getAdapterSurface } from "../adapters/surface.js";
|
|
10
|
+
import { extensionProductConfigBlock } from "../adapters/extension/product-config.js";
|
|
10
11
|
import { ensureMobilePerpsEnvironment, mobilePerpsEnvironment } from "../adapters/mobile/perps-env.js";
|
|
11
12
|
import { loadActionManifest, validateManifest } from "../manifest.js";
|
|
12
13
|
import { ensureOverlay, newHealState, recipeRunning } from "../heal-bounds.js";
|
|
@@ -21,12 +22,13 @@ import {
|
|
|
21
22
|
import { farmslotReadyIndicator } from "./farmslot-ready.js";
|
|
22
23
|
import { ensureRuntimeContext } from "../runtime-context.js";
|
|
23
24
|
import { acquireCheckoutLock } from "../checkout-lock.js";
|
|
24
|
-
import { ADAPTER_DETECT_NEXT, EXIT, usageOut } from "./shared.js";
|
|
25
|
+
import { ADAPTER_DETECT_NEXT, checkoutBusyOut, EXIT, usageOut } from "./shared.js";
|
|
25
26
|
import {
|
|
26
27
|
actionManifestPathOption,
|
|
27
28
|
applyRuntimeDirOption,
|
|
28
29
|
optionFlag,
|
|
29
30
|
optionString,
|
|
31
|
+
shellQuote,
|
|
30
32
|
targetPath
|
|
31
33
|
} from "./parse-args.js";
|
|
32
34
|
function applyDoctorRuntimePorts(options) {
|
|
@@ -56,6 +58,14 @@ async function handleDoctor({ options }) {
|
|
|
56
58
|
return usageOut(json, "doctor", `could not detect the MetaMask repo type for ${target}`, ADAPTER_DETECT_NEXT);
|
|
57
59
|
}
|
|
58
60
|
assertAdapter(adapter);
|
|
61
|
+
if (!fs.existsSync(target)) {
|
|
62
|
+
return usageOut(
|
|
63
|
+
json,
|
|
64
|
+
"doctor",
|
|
65
|
+
`target does not exist: ${target}`,
|
|
66
|
+
"pass --target <metamask-checkout> pointing to an existing checkout"
|
|
67
|
+
);
|
|
68
|
+
}
|
|
59
69
|
if (printReady && json) {
|
|
60
70
|
return usageOut(
|
|
61
71
|
json,
|
|
@@ -72,12 +82,12 @@ async function handleDoctor({ options }) {
|
|
|
72
82
|
surface.resolveSlotPorts(target);
|
|
73
83
|
const dtResult = applyDeviceTargeting("doctor", adapter, options, { gate: false, rerun: "" });
|
|
74
84
|
if ("code" in dtResult) {
|
|
75
|
-
return usageOut(json, "doctor", dtResult.message,
|
|
85
|
+
return usageOut(json, "doctor", dtResult.message, dtResult.userAction);
|
|
76
86
|
}
|
|
77
87
|
applyDoctorRuntimePorts(options);
|
|
78
88
|
const lock = acquireCheckoutLock(target, "doctor-fix");
|
|
79
89
|
if ("message" in lock) {
|
|
80
|
-
return
|
|
90
|
+
return checkoutBusyOut(json, "doctor", lock.message, lock.path);
|
|
81
91
|
}
|
|
82
92
|
let fixed;
|
|
83
93
|
let failed;
|
|
@@ -87,11 +97,36 @@ async function handleDoctor({ options }) {
|
|
|
87
97
|
lock.release();
|
|
88
98
|
}
|
|
89
99
|
const result2 = createDoctorReport(adapter, target, manifestValidation, actionManifestPath);
|
|
90
|
-
|
|
100
|
+
let runtime2;
|
|
101
|
+
let runtimeProbeError2;
|
|
102
|
+
try {
|
|
103
|
+
runtime2 = await surface.runtimeStatus(target);
|
|
104
|
+
} catch (error2) {
|
|
105
|
+
runtimeProbeError2 = error2 instanceof Error ? error2.message : String(error2);
|
|
106
|
+
}
|
|
107
|
+
const ready = runtime2?.decision === "ready";
|
|
108
|
+
if (adapter === "extension" && runtime2?.reasonCode === "product-config-required") {
|
|
109
|
+
failed.push("extension-product-config");
|
|
110
|
+
}
|
|
111
|
+
const nextActions = doctorFixNextActions(failed, result2.fixture, adapter, target);
|
|
112
|
+
if (failed.length === 0 && !ready && runtime2?.nextAction) nextActions.push(runtime2.nextAction);
|
|
91
113
|
const status = result2.status === "pass" && failed.length === 0 ? "pass" : "fail";
|
|
92
|
-
|
|
114
|
+
const retryDoctor = `mm-harness doctor --fix --adapter ${adapter} --target ${shellQuote(target)} --json`;
|
|
115
|
+
const fixUserAction = failed.includes("wallet-fixture") ? `choose one wallet fixture source from nextActions, run it, then retry: ${retryDoctor}` : nextActions[0] ?? retryDoctor;
|
|
116
|
+
const error = status === "fail" ? {
|
|
117
|
+
code: "DOCTOR_FIX_INCOMPLETE",
|
|
118
|
+
message: "doctor --fix could not make every required check pass",
|
|
119
|
+
userAction: fixUserAction
|
|
120
|
+
} : void 0;
|
|
121
|
+
if (json) console.log(JSON.stringify({ ...result2, status, fixed, failed, ready, runtime: runtime2, ...runtimeProbeError2 ? { runtimeProbeError: runtimeProbeError2 } : {}, nextActions, ...error ? { error } : {} }, null, 2));
|
|
93
122
|
else {
|
|
94
|
-
console.log(`${status} ${adapter} ${result2.compatibilityMode} manifest=${actionManifestPath} fixed=[${fixed.join(",")}] failed=[${failed.join(",")}]`);
|
|
123
|
+
console.log(`${status} ${adapter} ${result2.compatibilityMode} ready=${ready} manifest=${actionManifestPath} fixed=[${fixed.join(",")}] failed=[${failed.join(",")}]`);
|
|
124
|
+
if (runtime2) {
|
|
125
|
+
console.log(`runtime: decision=${runtime2.decision}${runtime2.reasonCode ? ` (${runtime2.reasonCode})` : ""}`);
|
|
126
|
+
for (const reason of runtime2.reasons) console.log(` ${reason}`);
|
|
127
|
+
} else if (runtimeProbeError2) {
|
|
128
|
+
console.log(`runtime probe failed: ${runtimeProbeError2}`);
|
|
129
|
+
}
|
|
95
130
|
for (const action of nextActions) console.error(` Next: ${action}`);
|
|
96
131
|
}
|
|
97
132
|
return status === "pass" ? 0 : 1;
|
|
@@ -104,8 +139,9 @@ async function handleDoctor({ options }) {
|
|
|
104
139
|
surface.resolveSlotPorts(target);
|
|
105
140
|
const dtResult = applyDeviceTargeting("doctor", adapter, options, { gate: false, rerun: "" });
|
|
106
141
|
if ("code" in dtResult) {
|
|
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}
|
|
142
|
+
if (json) console.log(JSON.stringify({ schemaVersion: 1, command: "doctor", adapter, target, error: { code: dtResult.code, message: dtResult.message, userAction: dtResult.userAction } }, null, 2));
|
|
143
|
+
else console.error(`\u2717 doctor: ${dtResult.message}
|
|
144
|
+
Next: ${dtResult.userAction}`);
|
|
109
145
|
return EXIT.usage;
|
|
110
146
|
}
|
|
111
147
|
applyDoctorRuntimePorts(options);
|
|
@@ -123,7 +159,7 @@ async function handleDoctor({ options }) {
|
|
|
123
159
|
requiredChecks
|
|
124
160
|
};
|
|
125
161
|
const orphanMetros = adapter === "mobile" ? detectOrphanMetros(target, process.env.WATCHER_PORT) : [];
|
|
126
|
-
const capture =
|
|
162
|
+
const capture = captureHealth(adapter);
|
|
127
163
|
const deviceView = adapter === "mobile" ? mobileDeviceView(allDevices) : null;
|
|
128
164
|
const liveView = deviceView ? await mobileDeviceLiveView(target, deviceView) : null;
|
|
129
165
|
const devices = liveView?.devicesWithLive ?? deviceView?.devices ?? [];
|
|
@@ -142,11 +178,17 @@ async function handleDoctor({ options }) {
|
|
|
142
178
|
printReady
|
|
143
179
|
);
|
|
144
180
|
}
|
|
145
|
-
|
|
181
|
+
const doctorUserAction = `mm-harness doctor --fix --adapter ${adapter} --target ${shellQuote(target)} --json`;
|
|
182
|
+
const doctorError = doctorResult.status === "fail" ? { code: "DOCTOR_CHECKS_FAILED", message: "one or more required doctor checks failed", userAction: doctorUserAction } : void 0;
|
|
183
|
+
const next = doctorError?.userAction ?? (runtime?.decision === "ready" ? void 0 : runtime?.nextAction);
|
|
184
|
+
if (json) console.log(JSON.stringify({ ...doctorResult, ready: runtime?.decision === "ready", runtime, orphanMetros, capture, devices, additionalReachableDevices, ...next ? { next } : {}, ...doctorError ? { error: doctorError } : {} }, null, 2));
|
|
146
185
|
else {
|
|
147
186
|
const out = (style, text) => color(style, text, { stream: process.stdout });
|
|
148
187
|
const stateStyle = (value, good) => value === good ? "ok" : "warn";
|
|
149
188
|
console.log(`${out(doctorResult.status === "pass" ? "ok" : "err", doctorResult.status)} ${out("bold", adapter)} ${doctorResult.compatibilityMode} ${out("dim", `manifest=${actionManifestPath}`)}`);
|
|
189
|
+
console.log(
|
|
190
|
+
`${out("label", "harness:")} ${doctorResult.runner.packageName}@${doctorResult.runner.version} ${out("accent", doctorResult.runner.installKind)} ${out("dim", `executable=${doctorResult.runner.executablePath}`)}`
|
|
191
|
+
);
|
|
150
192
|
if (runtime) {
|
|
151
193
|
const provisionedDepsPending = runtime.reasonCode === "app-installed-deps-pending";
|
|
152
194
|
const decisionStyle = provisionedDepsPending ? "info" : runtime.decision === "ready" ? "ok" : runtime.decision === "blocked" ? "err" : "warn";
|
|
@@ -173,18 +215,13 @@ async function handleDoctor({ options }) {
|
|
|
173
215
|
);
|
|
174
216
|
console.log(` ${out("dim", "Next: mm-harness stop # reaps every bundler this checkout leaked")}`);
|
|
175
217
|
}
|
|
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
|
-
}
|
|
218
|
+
if (capture) renderCaptureHealth(capture, out);
|
|
183
219
|
if (adapter === "mobile") {
|
|
184
220
|
if (deviceView) renderMobileDeviceList(deviceView, out);
|
|
185
221
|
if (additionalReachableDevices.length > 0) renderAdditionalReachableDevices(additionalReachableDevices, out);
|
|
186
222
|
if (liveView) renderMobileLiveBlock(deviceView?.devices ?? [], liveView.liveMap, out);
|
|
187
223
|
}
|
|
224
|
+
if (next) console.log(` ${out("dim", `Next: ${next}`)}`);
|
|
188
225
|
}
|
|
189
226
|
return doctorResult.status === "pass" ? 0 : 1;
|
|
190
227
|
}
|
|
@@ -207,15 +244,25 @@ function doctorRuntimeCheck(runtime, error, expectLive) {
|
|
|
207
244
|
detail: runtime?.reasons.join(" | ") || "Runtime readiness probe returned no result."
|
|
208
245
|
};
|
|
209
246
|
}
|
|
210
|
-
function doctorFixNextActions(failed, fixture) {
|
|
247
|
+
function doctorFixNextActions(failed, fixture, adapter, target) {
|
|
211
248
|
const actions = [];
|
|
249
|
+
const executable = process.env.MM_HARNESS_EXECUTABLE ? shellQuote(process.env.MM_HARNESS_EXECUTABLE) : "mm-harness";
|
|
250
|
+
const scope = `--adapter ${adapter} --target ${shellQuote(target)}`;
|
|
212
251
|
if (failed.includes("wallet-fixture")) {
|
|
213
252
|
const force = fixture.status === "invalid" || fixture.status === "incomplete" ? " --force" : "";
|
|
214
|
-
actions.push(`1)
|
|
215
|
-
actions.push(`2)
|
|
253
|
+
actions.push(`1) ${executable} fixtures init --from <path>${force} ${scope} # existing team/test fixture`);
|
|
254
|
+
actions.push(`2) ${executable} fixtures init --dev${force} ${scope} # disposable public test wallet; never real funds`);
|
|
255
|
+
}
|
|
256
|
+
if (failed.includes("runtime-context")) {
|
|
257
|
+
actions.push(
|
|
258
|
+
`inspect ${shellQuote(resolveRuntimeContextPath(target))}, then retry ${executable} doctor --fix ${scope}`
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
if (failed.includes("overlay")) actions.push(`${executable} install ${scope}`);
|
|
262
|
+
if (adapter === "extension" && failed.includes("extension-product-config")) {
|
|
263
|
+
const block = extensionProductConfigBlock(target);
|
|
264
|
+
if (block) actions.push(block.userAction);
|
|
216
265
|
}
|
|
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 .");
|
|
219
266
|
return actions;
|
|
220
267
|
}
|
|
221
268
|
function emitExpectLiveVerdict(adapter, target, runtime, verdict) {
|
|
@@ -223,7 +270,7 @@ function emitExpectLiveVerdict(adapter, target, runtime, verdict) {
|
|
|
223
270
|
const detail = runtime ? `${runtime.decision}${runtime.reasonCode ? ` (${runtime.reasonCode})` : ""}` : "runtime probe unavailable";
|
|
224
271
|
console.error(`${out("err", verdict)} ${out("bold", adapter)} ${detail} ${out("dim", target)}`);
|
|
225
272
|
for (const reason of runtime?.reasons ?? []) console.error(` ${out("dim", reason)}`);
|
|
226
|
-
console.error(` Next: ${getAdapterSurface(adapter).hints.
|
|
273
|
+
console.error(` Next: ${runtime?.nextAction ?? getAdapterSurface(adapter).hints.runtimeProbeRecovery(target)}`);
|
|
227
274
|
}
|
|
228
275
|
function emitExpectLive(adapter, target, runtime, result, orphanMetros, capture, devices, additionalReachableDevices, json, printReady) {
|
|
229
276
|
const live = runtime?.decision === "ready";
|
|
@@ -241,7 +288,9 @@ function emitExpectLive(adapter, target, runtime, result, orphanMetros, capture,
|
|
|
241
288
|
return EXIT.runtime;
|
|
242
289
|
}
|
|
243
290
|
if (json) {
|
|
244
|
-
|
|
291
|
+
const userAction = runtime?.nextAction ?? getAdapterSurface(adapter).hints.runtimeProbeRecovery(target);
|
|
292
|
+
const error = live ? void 0 : { code: "RUNTIME_NOT_LIVE", message: `${adapter} runtime is not live`, userAction };
|
|
293
|
+
console.log(JSON.stringify({ ...result, ready: live, runtime, orphanMetros, capture, devices, additionalReachableDevices, ...error ? { error } : {} }, null, 2));
|
|
245
294
|
return live ? EXIT.ok : EXIT.runtime;
|
|
246
295
|
}
|
|
247
296
|
const out = (style, text) => color(style, text, { stream: process.stdout });
|
|
@@ -252,6 +301,25 @@ function emitExpectLive(adapter, target, runtime, result, orphanMetros, capture,
|
|
|
252
301
|
emitExpectLiveVerdict(adapter, target, runtime, "not-live");
|
|
253
302
|
return EXIT.runtime;
|
|
254
303
|
}
|
|
304
|
+
function captureHealth(adapter) {
|
|
305
|
+
if (adapter === "core") return null;
|
|
306
|
+
const fallback = adapter === "extension" ? ["cdp"] : ["simctl", "adb"];
|
|
307
|
+
const helper = captureHelperHealth();
|
|
308
|
+
if (!helper) {
|
|
309
|
+
return {
|
|
310
|
+
status: "fallback",
|
|
311
|
+
failing: [],
|
|
312
|
+
screenshots: { available: true, providers: fallback },
|
|
313
|
+
recording: { available: false, provider: null }
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
return {
|
|
317
|
+
status: helper.status,
|
|
318
|
+
failing: helper.failing,
|
|
319
|
+
screenshots: { available: true, providers: ["capture-helper", ...fallback] },
|
|
320
|
+
recording: { available: helper.status === "pass", provider: helper.status === "pass" ? "capture-helper" : null }
|
|
321
|
+
};
|
|
322
|
+
}
|
|
255
323
|
function captureHelperHealth() {
|
|
256
324
|
if (process.platform !== "darwin") return null;
|
|
257
325
|
const bin = process.env.CAPTURE_HELPER_PATH || "capture-helper";
|
|
@@ -264,6 +332,18 @@ function captureHelperHealth() {
|
|
|
264
332
|
return null;
|
|
265
333
|
}
|
|
266
334
|
}
|
|
335
|
+
function renderCaptureHealth(capture, out) {
|
|
336
|
+
const video = capture.recording.available ? "capture-helper" : "unavailable";
|
|
337
|
+
console.log(
|
|
338
|
+
`${out("label", "capture:")} ${out(capture.status === "pass" ? "ok" : "warn", capture.status)} ${out("dim", `(screenshots: ${capture.screenshots.providers.join(" \u2192 ")}; video: ${video})`)}`
|
|
339
|
+
);
|
|
340
|
+
if (capture.status === "warn") {
|
|
341
|
+
if (capture.failing.length > 0) console.log(` ${out("dim", `failing: ${capture.failing.join(", ")}`)}`);
|
|
342
|
+
console.log(` ${out("dim", "Next: grant Screen Recording (System Settings \u2192 Privacy & Security \u2192 Screen Recording), or run: capture-helper doctor --open-permissions")}`);
|
|
343
|
+
} else if (capture.status === "fallback") {
|
|
344
|
+
console.log(` ${out("dim", "Next: screenshots work without capture-helper; install/configure capture-helper only when recipe video is required")}`);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
267
347
|
function detectOrphanMetros(target, livePort) {
|
|
268
348
|
try {
|
|
269
349
|
const script = path.join(runnerDir, "adapters/shared/reap-checkout-metros.sh");
|
|
@@ -381,6 +461,7 @@ function readTextSnapshot(file) {
|
|
|
381
461
|
}
|
|
382
462
|
}
|
|
383
463
|
export {
|
|
464
|
+
doctorFixNextActions,
|
|
384
465
|
ensureExtensionPerpsBuildFlag,
|
|
385
466
|
handleDoctor
|
|
386
467
|
};
|
|
@@ -5,25 +5,75 @@ import { runnerDir, walletFixturePath } from "../paths.js";
|
|
|
5
5
|
import { getAdapterSurface } from "../adapters/surface.js";
|
|
6
6
|
import { fixtureFileSummary, fixtureSummary } from "../doctor.js";
|
|
7
7
|
import { acquireCheckoutLock } from "../checkout-lock.js";
|
|
8
|
+
import { colorHumanMessage } from "../cli-color.js";
|
|
8
9
|
import { applyDeviceTargeting } from "./device-target.js";
|
|
9
|
-
import { ADAPTER_DETECT_NEXT, EXIT, flag, parseFlags, resolveAdapter, scriptOverride, spawnScript, spawnScriptStreaming, str, targetOf, usageOut } from "./shared.js";
|
|
10
|
+
import { ADAPTER_DETECT_NEXT, checkoutBusyOut, EXIT, flag, parseFlags, resolveAdapter, scriptOverride, spawnScript, spawnScriptStreaming, str, targetOf, usageOut } from "./shared.js";
|
|
10
11
|
const FIXTURES_BOOLEANS = /* @__PURE__ */ new Set(["dev", "force", "json"]);
|
|
11
|
-
const
|
|
12
|
-
async function handleFixtures(argv
|
|
13
|
-
const { options } = parseFlags(argv, FIXTURES_BOOLEANS);
|
|
12
|
+
const FIXTURE_STREAM_TIMEOUT_MS = 12e4;
|
|
13
|
+
async function handleFixtures(argv) {
|
|
14
|
+
const { positional, options } = parseFlags(argv, FIXTURES_BOOLEANS);
|
|
15
|
+
if (!positional[0]) return fixturesStatus(options);
|
|
14
16
|
const json = flag(options, "json");
|
|
15
17
|
const target = targetOf(options);
|
|
16
|
-
if (!fs.existsSync(target)) return handleFixturesLocked(argv
|
|
18
|
+
if (!fs.existsSync(target)) return handleFixturesLocked(argv);
|
|
17
19
|
const lock = acquireCheckoutLock(target, "fixtures");
|
|
18
20
|
if ("message" in lock) {
|
|
19
|
-
return
|
|
21
|
+
return checkoutBusyOut(json, "fixtures", lock.message, lock.path);
|
|
20
22
|
}
|
|
21
23
|
try {
|
|
22
|
-
return await handleFixturesLocked(argv
|
|
24
|
+
return await handleFixturesLocked(argv);
|
|
23
25
|
} finally {
|
|
24
26
|
lock.release();
|
|
25
27
|
}
|
|
26
28
|
}
|
|
29
|
+
function fixturesStatus(options) {
|
|
30
|
+
const json = flag(options, "json");
|
|
31
|
+
const statusOptions = /* @__PURE__ */ new Set(["json", "target", "adapter", "platform", "device"]);
|
|
32
|
+
const actionOption = Object.keys(options).find((key) => !statusOptions.has(key));
|
|
33
|
+
if (actionOption) {
|
|
34
|
+
return usageOut(
|
|
35
|
+
json,
|
|
36
|
+
"fixtures",
|
|
37
|
+
`--${actionOption.replace(/[A-Z]/gu, (letter) => `-${letter.toLowerCase()}`)} requires a fixture action.`,
|
|
38
|
+
"mm-harness fixtures --help"
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
const target = targetOf(options);
|
|
42
|
+
const adapter = resolveAdapter(options, target);
|
|
43
|
+
if (!adapter) {
|
|
44
|
+
return usageOut(json, "fixtures", `could not detect the MetaMask repo type for ${target}`, ADAPTER_DETECT_NEXT);
|
|
45
|
+
}
|
|
46
|
+
const canonical = walletFixturePath(target);
|
|
47
|
+
const summary = fixtureSummary(target, adapter);
|
|
48
|
+
const fixture = { ...summary, path: canonical };
|
|
49
|
+
const ready = summary.status === "ready";
|
|
50
|
+
const nextCommand = ready ? "mm-harness fixtures set" : "mm-harness fixtures init --from <path> # or: mm-harness fixtures init --dev";
|
|
51
|
+
const operations = adapter === "extension" ? ["init", "sync", "set", "generate", "finalize"] : ["init", "sync", "set"];
|
|
52
|
+
if (json) {
|
|
53
|
+
console.log(JSON.stringify({
|
|
54
|
+
schemaVersion: 1,
|
|
55
|
+
command: "fixtures",
|
|
56
|
+
action: "status",
|
|
57
|
+
adapter,
|
|
58
|
+
target,
|
|
59
|
+
status: "pass",
|
|
60
|
+
fixture,
|
|
61
|
+
operations,
|
|
62
|
+
nextCommand,
|
|
63
|
+
exitCode: EXIT.ok
|
|
64
|
+
}, null, 2));
|
|
65
|
+
return EXIT.ok;
|
|
66
|
+
}
|
|
67
|
+
console.log(`wallet fixture (${adapter}): ${String(summary.status).toUpperCase()}`);
|
|
68
|
+
console.log(`path: ${canonical}`);
|
|
69
|
+
if (typeof summary.accountCount === "number") {
|
|
70
|
+
const password = summary.hasPassword === true ? "present" : "missing";
|
|
71
|
+
console.log(`accounts: ${summary.accountCount} \xB7 password: ${password}`);
|
|
72
|
+
}
|
|
73
|
+
console.log(`operations: ${operations.join(", ")}`);
|
|
74
|
+
console.log(`Next: ${nextCommand}`);
|
|
75
|
+
return EXIT.ok;
|
|
76
|
+
}
|
|
27
77
|
function ensureFixturePublicationParent(target) {
|
|
28
78
|
const repoRoot = fs.realpathSync(target);
|
|
29
79
|
const parent = path.dirname(walletFixturePath(repoRoot));
|
|
@@ -49,13 +99,21 @@ function ensureFixturePublicationParent(target) {
|
|
|
49
99
|
}
|
|
50
100
|
return fs.realpathSync(parent) === parent ? null : `wallet fixture runtime path escapes the checkout: ${parent}`;
|
|
51
101
|
}
|
|
52
|
-
async function handleFixturesLocked(argv
|
|
102
|
+
async function handleFixturesLocked(argv) {
|
|
53
103
|
const { positional, options } = parseFlags(argv, FIXTURES_BOOLEANS);
|
|
54
104
|
const json = flag(options, "json");
|
|
55
105
|
const sub = positional[0];
|
|
56
106
|
if (sub !== "init" && sub !== "sync" && sub !== "set" && sub !== "generate" && sub !== "finalize") {
|
|
57
107
|
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
108
|
}
|
|
109
|
+
if (sub !== "init" && (flag(options, "dev") || flag(options, "force") || str(options, "from"))) {
|
|
110
|
+
return usageOut(
|
|
111
|
+
json,
|
|
112
|
+
"fixtures",
|
|
113
|
+
"--dev, --from, and --force belong to fixtures init; they do not apply a fixture.",
|
|
114
|
+
"mm-harness fixtures init --dev && mm-harness fixtures set"
|
|
115
|
+
);
|
|
116
|
+
}
|
|
59
117
|
const target = targetOf(options);
|
|
60
118
|
const adapter = resolveAdapter(options, target);
|
|
61
119
|
if (!adapter) {
|
|
@@ -65,7 +123,7 @@ async function handleFixturesLocked(argv, deps) {
|
|
|
65
123
|
surface.resolveSlotPorts(target);
|
|
66
124
|
const dtResult = applyDeviceTargeting("fixtures", adapter, options, { gate: false, rerun: "" });
|
|
67
125
|
if ("code" in dtResult) {
|
|
68
|
-
return usageOut(json, "fixtures", dtResult.message,
|
|
126
|
+
return usageOut(json, "fixtures", dtResult.message, dtResult.userAction);
|
|
69
127
|
}
|
|
70
128
|
if (sub === "generate") return fixturesGenerate(adapter, target, options, json);
|
|
71
129
|
if (sub === "finalize") return fixturesFinalize(adapter, target, options, json);
|
|
@@ -96,6 +154,17 @@ async function handleFixturesLocked(argv, deps) {
|
|
|
96
154
|
return exitCode;
|
|
97
155
|
}
|
|
98
156
|
const fixturePath = path.resolve(str(options, "fixture") ?? process.env.RECIPE_WALLET_FIXTURE ?? canonicalFixture);
|
|
157
|
+
if (!fs.existsSync(fixturePath)) {
|
|
158
|
+
return usageOut(
|
|
159
|
+
json,
|
|
160
|
+
"fixtures",
|
|
161
|
+
`no wallet fixture at ${fixturePath}.`,
|
|
162
|
+
"mm-harness fixtures init --dev && mm-harness fixtures set"
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
if (adapter === "extension") {
|
|
166
|
+
return applyExtensionFixture(target, fixturePath, canonicalFixture, json);
|
|
167
|
+
}
|
|
99
168
|
process.stderr.write(`\u2192 fixtures set ${adapter} \u2014 connecting bridge + applying wallet fixture (can take ~30s)\u2026
|
|
100
169
|
`);
|
|
101
170
|
let status;
|
|
@@ -105,8 +174,8 @@ async function handleFixturesLocked(argv, deps) {
|
|
|
105
174
|
const previousAppRoot = process.env.APP_ROOT;
|
|
106
175
|
process.env.APP_ROOT = target;
|
|
107
176
|
try {
|
|
108
|
-
const configuredTimeout = Number(process.env.MM_HARNESS_FIXTURES_SET_TIMEOUT_MS ??
|
|
109
|
-
const timeoutMs = Number.isFinite(configuredTimeout) && configuredTimeout > 0 ? configuredTimeout :
|
|
177
|
+
const configuredTimeout = Number(process.env.MM_HARNESS_FIXTURES_SET_TIMEOUT_MS ?? FIXTURE_STREAM_TIMEOUT_MS);
|
|
178
|
+
const timeoutMs = Number.isFinite(configuredTimeout) && configuredTimeout > 0 ? configuredTimeout : FIXTURE_STREAM_TIMEOUT_MS;
|
|
110
179
|
mobileSetupResult = await spawnScriptStreaming(
|
|
111
180
|
setupWalletSh,
|
|
112
181
|
["--fixture", fixturePath],
|
|
@@ -128,29 +197,8 @@ async function handleFixturesLocked(argv, deps) {
|
|
|
128
197
|
if (!json && status === "fail") {
|
|
129
198
|
console.error(` Next: ${retryHint}`);
|
|
130
199
|
}
|
|
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
|
-
}
|
|
140
|
-
const previousFixtureEnv = process.env.RECIPE_WALLET_FIXTURE;
|
|
141
|
-
process.env.RECIPE_WALLET_FIXTURE = fixturePath;
|
|
142
|
-
try {
|
|
143
|
-
const result = await deps.runOneNode("extension", "metamask.wallet.setup", {}, target, str(options, "actionManifest"));
|
|
144
|
-
status = result.status;
|
|
145
|
-
} finally {
|
|
146
|
-
if (previousFixtureEnv === void 0) delete process.env.RECIPE_WALLET_FIXTURE;
|
|
147
|
-
else process.env.RECIPE_WALLET_FIXTURE = previousFixtureEnv;
|
|
148
|
-
}
|
|
149
|
-
if (!json && status === "fail") {
|
|
150
|
-
console.error(` Next: ${retryHint}`);
|
|
151
|
-
}
|
|
152
200
|
}
|
|
153
|
-
const teaching = `Wallet fixture applied. Want different accounts? Edit: ${canonicalFixture}
|
|
201
|
+
const teaching = status === "pass" ? `Wallet fixture applied. Want different accounts? Edit: ${canonicalFixture}` : "Wallet fixture was not applied.";
|
|
154
202
|
if (json) {
|
|
155
203
|
console.log(
|
|
156
204
|
JSON.stringify(
|
|
@@ -174,11 +222,68 @@ async function handleFixturesLocked(argv, deps) {
|
|
|
174
222
|
2
|
|
175
223
|
)
|
|
176
224
|
);
|
|
177
|
-
} else {
|
|
225
|
+
} else if (status === "pass") {
|
|
178
226
|
console.error(teaching);
|
|
179
227
|
}
|
|
180
228
|
return status === "pass" ? EXIT.ok : EXIT.runtime;
|
|
181
229
|
}
|
|
230
|
+
async function applyExtensionFixture(target, fixturePath, canonicalFixture, json) {
|
|
231
|
+
const distManifest = path.join(target, "dist/chrome/manifest.json");
|
|
232
|
+
const userAction = `mm-harness launch --build --verify --target ${JSON.stringify(target)}`;
|
|
233
|
+
if (!fs.existsSync(distManifest)) {
|
|
234
|
+
const message2 = `Extension fixture needs an existing dist/chrome build; no manifest was found at ${distManifest}.`;
|
|
235
|
+
if (json) {
|
|
236
|
+
console.log(JSON.stringify({
|
|
237
|
+
schemaVersion: 1,
|
|
238
|
+
command: "fixtures",
|
|
239
|
+
action: "set",
|
|
240
|
+
adapter: "extension",
|
|
241
|
+
fixture: fixturePath,
|
|
242
|
+
canonicalFixture,
|
|
243
|
+
status: "fail",
|
|
244
|
+
exitCode: EXIT.runtime,
|
|
245
|
+
error: { code: "EXTENSION_FIXTURE_BUILD_REQUIRED", message: message2, userAction }
|
|
246
|
+
}, null, 2));
|
|
247
|
+
} else {
|
|
248
|
+
console.error(`\u2717 mm-harness fixtures set: ${message2}
|
|
249
|
+
Next: ${userAction}`);
|
|
250
|
+
}
|
|
251
|
+
return EXIT.runtime;
|
|
252
|
+
}
|
|
253
|
+
process.stderr.write(`${colorHumanMessage("\u2192 fixtures set extension \u2014 resetting and seeding the slot-owned profile from the existing build\u2026")}
|
|
254
|
+
`);
|
|
255
|
+
const liveScript = path.join(runnerDir, "adapters/extension/live.sh");
|
|
256
|
+
const args = ["--target", target, "--launch-existing-dist"];
|
|
257
|
+
if (process.env.CDP_PORT) args.push("--cdp-port", process.env.CDP_PORT);
|
|
258
|
+
const result = await spawnScriptStreaming(liveScript, args, target, {
|
|
259
|
+
env: { RECIPE_WALLET_FIXTURE: fixturePath },
|
|
260
|
+
timeoutMs: FIXTURE_STREAM_TIMEOUT_MS
|
|
261
|
+
});
|
|
262
|
+
const status = result.status === 0 ? "pass" : "fail";
|
|
263
|
+
const exitCode = status === "pass" ? EXIT.ok : EXIT.runtime;
|
|
264
|
+
const message = status === "pass" ? `Wallet fixture applied and validated. Want different accounts? Edit: ${canonicalFixture}` : "Extension wallet fixture could not be applied and validated.";
|
|
265
|
+
if (json) {
|
|
266
|
+
console.log(JSON.stringify({
|
|
267
|
+
schemaVersion: 1,
|
|
268
|
+
command: "fixtures",
|
|
269
|
+
action: "set",
|
|
270
|
+
adapter: "extension",
|
|
271
|
+
fixture: fixturePath,
|
|
272
|
+
canonicalFixture,
|
|
273
|
+
status,
|
|
274
|
+
exitCode,
|
|
275
|
+
profileReset: status === "pass",
|
|
276
|
+
message,
|
|
277
|
+
error: status === "fail" ? { code: "EXTENSION_FIXTURE_APPLY_FAILED", message, userAction } : null
|
|
278
|
+
}, null, 2));
|
|
279
|
+
} else if (status === "pass") {
|
|
280
|
+
console.error(message);
|
|
281
|
+
} else {
|
|
282
|
+
console.error(`\u2717 mm-harness fixtures set: ${message}
|
|
283
|
+
Next: ${userAction}`);
|
|
284
|
+
}
|
|
285
|
+
return exitCode;
|
|
286
|
+
}
|
|
182
287
|
const EXT_FIXTURE_LEAF = "adapters/extension/wallet-fixture-state.cjs";
|
|
183
288
|
function emitLeafMissing(json, action, fields, leaf) {
|
|
184
289
|
const message = `leaf could not start: ${path.basename(leaf)} (ENOENT).`;
|
|
@@ -298,7 +403,9 @@ async function fixturesFinalize(adapter, target, options, json) {
|
|
|
298
403
|
if (outPath) leafArgs.push("--out", outPath);
|
|
299
404
|
process.stderr.write(`\u2192 fixtures finalize extension \u2014 seeding account labels via CDP on port ${cdpPort} (can take ~30s)\u2026
|
|
300
405
|
`);
|
|
301
|
-
const result = await spawnScriptStreaming(process.execPath, leafArgs, target
|
|
406
|
+
const result = await spawnScriptStreaming(process.execPath, leafArgs, target, {
|
|
407
|
+
timeoutMs: FIXTURE_STREAM_TIMEOUT_MS
|
|
408
|
+
});
|
|
302
409
|
const status = result.status === 0 ? "pass" : "fail";
|
|
303
410
|
const exitCode = result.status === 0 ? EXIT.ok : EXIT.runtime;
|
|
304
411
|
const retry = `mm-harness fixtures finalize --fixture ${fixturePath} --state ${statePath} --cdp-port ${cdpPort} --extension-dir ${extensionDirPath}`;
|