@deeeed/metamask-harness 0.12.0 → 0.14.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 +26 -5
- package/adapters/core/cleanup.sh +0 -0
- package/adapters/core/inject.sh +0 -0
- package/adapters/extension/cleanup.mjs +0 -0
- package/adapters/extension/ensure-browser.sh +0 -0
- package/adapters/extension/inject.mjs +0 -0
- package/adapters/extension/launch-browser.cjs +0 -0
- package/adapters/extension/launch.sh +0 -0
- package/adapters/extension/live.sh +0 -0
- package/adapters/extension/readiness.mjs +0 -0
- package/adapters/extension/reattach.sh +0 -0
- package/adapters/extension/refresh-build.sh +0 -0
- package/adapters/extension/seed-fixture.sh +0 -0
- package/adapters/extension/sidepanel-toggle.sh +0 -0
- package/adapters/extension/snapshot-dist.sh +0 -0
- package/adapters/extension/start-watch.sh +0 -0
- package/adapters/extension/verify.sh +4 -4
- package/adapters/extension/wallet-fixture-state.cjs +0 -0
- package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +16 -0
- package/adapters/mobile/bridge-runtime/console-forwarder.cjs +2 -4
- package/adapters/mobile/bridge-runtime/lib/bridge-errors.cjs +106 -0
- package/adapters/mobile/bridge-runtime/lib/console-format.cjs +53 -0
- package/adapters/mobile/bridge-runtime/lib/match-bridge-target.cjs +82 -0
- package/adapters/mobile/bridge-runtime/lib/target-discovery.cjs +17 -13
- package/adapters/mobile/bridge-runtime/lib/ws-client.cjs +10 -5
- package/adapters/mobile/bridge-runtime/setup-wallet.sh +0 -0
- package/adapters/mobile/cleanup.sh +0 -0
- package/adapters/mobile/inject.sh +0 -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-metro.sh +22 -1
- package/adapters/mobile/stop-metro.sh +0 -0
- package/adapters/mobile/verify.sh +50 -13
- package/adapters/mobile/wait-for-bridge.sh +41 -23
- 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/json-field.sh +0 -0
- package/adapters/shared/log-tui.mjs +7 -3
- package/adapters/shared/open-log-window.sh +0 -0
- package/adapters/shared/reap-checkout-metros.sh +0 -0
- package/adapters/shared/resolve-farmslot-ports.mjs +0 -0
- package/adapters/shared/resolve-farmslot-ports.sh +0 -0
- package/adapters/shared/resolve-slot-ports.mjs +0 -0
- package/adapters/shared/resolve-slot-ports.sh +0 -0
- package/adapters/shared/sync-wallet-fixture.sh +0 -0
- package/adapters/shared/tmux-session.sh +0 -0
- package/dist/adapters/mobile/prepare.js +23 -2
- package/dist/adapters/mobile/runtime-decision.js +2 -1
- package/dist/adapters.js +33 -10
- package/dist/cli-commands.js +5 -4
- package/dist/commands/call.js +138 -3
- package/dist/commands/device-target.js +10 -3
- package/dist/commands/fixtures.js +2 -1
- package/dist/commands/manifest.js +2 -0
- package/dist/commands/run-engine.js +71 -4
- package/dist/commands/run.js +2 -0
- package/dist/commands/self-test.js +1 -1
- package/dist/commands/status.js +19 -4
- package/dist/mm-harness-cli.js +19 -5
- package/dist/recipe-files.js +14 -0
- package/docs/architecture.md +2 -2
- package/docs/live-adapter-contract.md +1 -1
- package/docs/recipe-libraries.md +21 -18
- package/library/actions/mobile/platform/bridge.mjs +35 -4
- package/library/recipes/{app-lifecycle-android-smoke.mobile.recipe.json → app/lifecycle.android-smoke.mobile.recipe.json} +1 -1
- package/library/recipes/{perps-performance.mobile.recipe.json → perps/performance.mobile.recipe.json} +1 -1
- package/library/recipes/perps/smoke.core.recipe.json +39 -0
- package/library/recipes/perps/smoke.extension.recipe.json +51 -0
- package/library/recipes/perps/smoke.mobile.recipe.json +51 -0
- package/library/recipes/{action-validation.extension.recipe.json → runner/action-validation.extension.recipe.json} +7 -7
- package/library/recipes/{action-validation.mobile.recipe.json → runner/action-validation.mobile.recipe.json} +7 -7
- package/package.json +2 -2
- package/scripts/completions.sh +0 -0
- package/scripts/install-completions.sh +0 -0
- /package/library/recipes/{perps-lifecycle.recipe.json → perps/lifecycle.recipe.json} +0 -0
- /package/library/recipes/{order-lifecycle.core.recipe.json → perps/order-lifecycle.core.recipe.json} +0 -0
- /package/library/recipes/{perps-performance-background-resume.mobile.recipe.json → perps/performance.background-resume.mobile.recipe.json} +0 -0
- /package/library/recipes/{perps-performance-cold-start.mobile.recipe.json → perps/performance.cold-start.mobile.recipe.json} +0 -0
- /package/library/recipes/{perps-performance-warm-start.mobile.recipe.json → perps/performance.warm-start.mobile.recipe.json} +0 -0
- /package/library/recipes/{read-markets.core.recipe.json → perps/read-markets.core.recipe.json} +0 -0
- /package/library/recipes/{trading-lifecycle.core.recipe.json → perps/trading-lifecycle.core.recipe.json} +0 -0
- /package/library/recipes/{smoke.extension.recipe.json → runner/smoke.extension.recipe.json} +0 -0
- /package/library/recipes/{smoke.mobile.recipe.json → runner/smoke.mobile.recipe.json} +0 -0
|
@@ -216,6 +216,34 @@ process.stdout.write(value);
|
|
|
216
216
|
NODE
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
+
runtime_context_value() {
|
|
220
|
+
TARGET_FOR_RUNTIME_CONTEXT="$TARGET" RUNTIME_DIR_FOR_RUNTIME_CONTEXT="$RUNTIME_DIR" RUNTIME_CONTEXT_NAME="$1" node <<'NODE'
|
|
221
|
+
const fs = require('fs');
|
|
222
|
+
const path = require('path');
|
|
223
|
+
const target = process.env.TARGET_FOR_RUNTIME_CONTEXT;
|
|
224
|
+
const runtimeDir = process.env.RUNTIME_DIR_FOR_RUNTIME_CONTEXT;
|
|
225
|
+
const names = process.env.RUNTIME_CONTEXT_NAME.split(',');
|
|
226
|
+
const file = path.join(target, runtimeDir, 'agentic-runtime.json');
|
|
227
|
+
let data = {};
|
|
228
|
+
try { data = JSON.parse(fs.readFileSync(file, 'utf8')); } catch {}
|
|
229
|
+
for (const name of names) {
|
|
230
|
+
const value = data && data[name];
|
|
231
|
+
if (value !== undefined && value !== null && value !== '') {
|
|
232
|
+
process.stdout.write(String(value));
|
|
233
|
+
process.exit(0);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
NODE
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
mobile_device_target() {
|
|
240
|
+
if [ "$PLATFORM" = "android" ]; then
|
|
241
|
+
printf '%s' "${ADB_SERIAL:-${ANDROID_SERIAL:-${ANDROID_DEVICE:-$(runtime_context_value adbSerial,androidDevice,device)}}}"
|
|
242
|
+
return
|
|
243
|
+
fi
|
|
244
|
+
printf '%s' "${SIM_UDID:-${IOS_SIMULATOR:-$(runtime_context_value simulator,iosSimulator,device)}}"
|
|
245
|
+
}
|
|
246
|
+
|
|
219
247
|
check_file() {
|
|
220
248
|
local rel="$1"
|
|
221
249
|
if [ -e "$TARGET/$rel" ]; then
|
|
@@ -331,6 +359,7 @@ run_with_timeout() {
|
|
|
331
359
|
|
|
332
360
|
live_status_ok() {
|
|
333
361
|
local log_path="$1"
|
|
362
|
+
local device_target="$2"
|
|
334
363
|
cat > "$ARTIFACTS/mobile-status-smoke.recipe.json" <<'JSON'
|
|
335
364
|
{
|
|
336
365
|
"schema_version": 1,
|
|
@@ -349,12 +378,15 @@ live_status_ok() {
|
|
|
349
378
|
JSON
|
|
350
379
|
(
|
|
351
380
|
cd "$TARGET"
|
|
352
|
-
|
|
381
|
+
device_args=()
|
|
382
|
+
[ -n "$device_target" ] && device_args=(--device "$device_target")
|
|
383
|
+
"$RUNNER_BIN" run "$ARTIFACTS/mobile-status-smoke.recipe.json" --adapter mobile --project-root "$TARGET" "${device_args[@]}" --artifacts-dir "$ARTIFACTS/runner-status-smoke" --json
|
|
353
384
|
) > "$log_path" 2>&1
|
|
354
385
|
}
|
|
355
386
|
|
|
356
387
|
ensure_live_runtime() {
|
|
357
|
-
|
|
388
|
+
local device_target="$1"
|
|
389
|
+
if live_status_ok "$ARTIFACTS/logs/app-status-precheck.log" "$device_target"; then
|
|
358
390
|
return 0
|
|
359
391
|
fi
|
|
360
392
|
if [ "$AUTO_START" = true ]; then
|
|
@@ -366,7 +398,17 @@ EOF
|
|
|
366
398
|
}
|
|
367
399
|
|
|
368
400
|
if [ "$STATIC_ONLY" = false ]; then
|
|
369
|
-
|
|
401
|
+
device_target="$(mobile_device_target)"
|
|
402
|
+
if [ -z "$device_target" ]; then
|
|
403
|
+
if [ "$PLATFORM" = "android" ]; then
|
|
404
|
+
device_target="$(jsenv_value ADB_SERIAL)"
|
|
405
|
+
[ -n "$device_target" ] || device_target="$(jsenv_value ANDROID_DEVICE)"
|
|
406
|
+
else
|
|
407
|
+
device_target="$(jsenv_value IOS_SIMULATOR)"
|
|
408
|
+
fi
|
|
409
|
+
fi
|
|
410
|
+
|
|
411
|
+
if ensure_live_runtime "$device_target"; then
|
|
370
412
|
checks+=('{"name":"mobile runtime controllable precheck","status":"pass"}')
|
|
371
413
|
else
|
|
372
414
|
checks+=('{"name":"mobile runtime controllable precheck","status":"fail","detail":"see logs/app-status-precheck.log"}')
|
|
@@ -394,20 +436,13 @@ if [ "$STATIC_ONLY" = false ]; then
|
|
|
394
436
|
{
|
|
395
437
|
"schema_version": 1,
|
|
396
438
|
"title": "Mobile v1 runner live bridge smoke",
|
|
397
|
-
"description": "Verifies the installed MetaMask runner can
|
|
439
|
+
"description": "Verifies the installed MetaMask runner can read the React Native debug bridge without mutating wallet state.",
|
|
398
440
|
"validate": {
|
|
399
441
|
"workflow": {
|
|
400
442
|
"entry": "status",
|
|
401
443
|
"nodes": {
|
|
402
444
|
"status": { "action": "app.status", "intent": "Read Mobile app status through the v1 runner", "next": "cdp-probe" },
|
|
403
|
-
"cdp-probe": { "action": "cdp.target", "intent": "Verify the React Native debug bridge target is reachable", "required": true, "timeout_ms": 15000, "next": "
|
|
404
|
-
"wallet-setup": { "action": "metamask.wallet.setup", "intent": "Prepare the wallet fixture for the live bridge smoke", "timeout_ms": 45000, "next": "wallet-unlock" },
|
|
405
|
-
"wallet-unlock": { "action": "metamask.wallet.ensure_unlocked", "intent": "Unlock the wallet through the manifest-declared action", "timeout_ms": 45000, "next": "wallet-read" },
|
|
406
|
-
"wallet-read": { "action": "metamask.wallet.read_state", "intent": "Read wallet state before navigating to the wallet view", "timeout_ms": 45000, "next": "navigate-wallet" },
|
|
407
|
-
"navigate-wallet": { "action": "ui.navigate", "intent": "Open the wallet view through the UI navigation action", "route": "WalletView", "timeout_ms": 45000, "next": "wait-wallet" },
|
|
408
|
-
"wait-wallet": { "action": "ui.wait_for", "intent": "Wait until the wallet screen is present", "test_id": "wallet-screen", "expected": "present", "timeout_ms": 45000, "next": "hud-smoke" },
|
|
409
|
-
"hud-smoke": { "action": "app.hud", "status": "running", "intent": "Show the Mobile live bridge smoke HUD", "progress": { "current": 1, "total": 1 }, "timeout_ms": 45000, "next": "screenshot" },
|
|
410
|
-
"screenshot": { "action": "ui.screenshot", "intent": "Capture proof that the wallet screen is visible", "path": "screenshots/mobile-v1-live-smoke.png", "timeout_ms": 45000, "next": "done" },
|
|
445
|
+
"cdp-probe": { "action": "cdp.target", "intent": "Verify the React Native debug bridge target is reachable", "required": true, "timeout_ms": 15000, "next": "done" },
|
|
411
446
|
"done": { "action": "end", "status": "pass" }
|
|
412
447
|
}
|
|
413
448
|
}
|
|
@@ -417,13 +452,15 @@ JSON
|
|
|
417
452
|
|
|
418
453
|
ios_simulator_resolved="$(jsenv_value IOS_SIMULATOR)"
|
|
419
454
|
adb_serial_resolved="$(jsenv_value ADB_SERIAL)"
|
|
455
|
+
device_args=()
|
|
456
|
+
[ -n "$device_target" ] && device_args=(--device "$device_target")
|
|
420
457
|
if (
|
|
421
458
|
cd "$TARGET"
|
|
422
459
|
METAMASK_RECIPE_AUTO_HUD=0 \
|
|
423
460
|
IOS_SIMULATOR="${IOS_SIMULATOR:-$ios_simulator_resolved}" \
|
|
424
461
|
ADB_SERIAL="${ADB_SERIAL:-$adb_serial_resolved}" \
|
|
425
462
|
ANDROID_SERIAL="${ANDROID_SERIAL:-$adb_serial_resolved}" \
|
|
426
|
-
"$RUNNER_BIN" run "$ARTIFACTS/mobile-v1-live-smoke.recipe.json" --adapter mobile --project-root "$TARGET" --artifacts-dir "$ARTIFACTS/runner-live-smoke" --json
|
|
463
|
+
"$RUNNER_BIN" run "$ARTIFACTS/mobile-v1-live-smoke.recipe.json" --adapter mobile --project-root "$TARGET" "${device_args[@]}" --artifacts-dir "$ARTIFACTS/runner-live-smoke" --json
|
|
427
464
|
) > "$ARTIFACTS/logs/runner-live-smoke.log" 2>&1; then
|
|
428
465
|
checks+=("{\"name\":\"runner v1 live bridge smoke\",\"status\":\"pass\"}")
|
|
429
466
|
else
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
# --target <metamask-mobile dir> (default $PWD)
|
|
10
10
|
# --port <number> (default WATCHER_PORT env, else METRO_PORT, else 8081)
|
|
11
11
|
# --max-polls <n> (default MOBILE_BRIDGE_READY_POLLS env, else 90; 2s each = 180s)
|
|
12
|
+
# --platform ios|android bind the required platform: the answering target
|
|
13
|
+
# must belong to it, regardless of ambient device env
|
|
12
14
|
# Outputs:
|
|
13
15
|
# Progress on stderr; exit 0 bridge ready; 1 timeout.
|
|
14
16
|
#
|
|
@@ -18,14 +20,16 @@ set -uo pipefail
|
|
|
18
20
|
TARGET="$PWD"
|
|
19
21
|
PORT="${WATCHER_PORT:-${METRO_PORT:-8081}}"
|
|
20
22
|
MAX_POLLS="${MOBILE_BRIDGE_READY_POLLS:-90}"
|
|
23
|
+
REQUIRE_PLATFORM=""
|
|
21
24
|
|
|
22
25
|
while [ "$#" -gt 0 ]; do
|
|
23
26
|
case "$1" in
|
|
24
27
|
--target) TARGET="$2"; shift 2 ;;
|
|
25
28
|
--port) PORT="$2"; shift 2 ;;
|
|
26
29
|
--max-polls) MAX_POLLS="$2"; shift 2 ;;
|
|
30
|
+
--platform) REQUIRE_PLATFORM="$2"; shift 2 ;;
|
|
27
31
|
-h|--help)
|
|
28
|
-
printf 'Usage: wait-for-bridge.sh [--target <dir>] [--port <n>] [--max-polls <n>]\n'
|
|
32
|
+
printf 'Usage: wait-for-bridge.sh [--target <dir>] [--port <n>] [--max-polls <n>] [--platform ios|android]\n'
|
|
29
33
|
exit 0
|
|
30
34
|
;;
|
|
31
35
|
*) printf 'wait-for-bridge: unknown arg: %s\n' "$1" >&2; exit 2 ;;
|
|
@@ -34,6 +38,10 @@ done
|
|
|
34
38
|
|
|
35
39
|
TARGET="$(cd "$TARGET" && pwd)"
|
|
36
40
|
|
|
41
|
+
# The matcher reads the required platform from the environment so the value survives
|
|
42
|
+
# into the node subprocess that runs the shared matcher against the status output.
|
|
43
|
+
export WAIT_FOR_BRIDGE_PLATFORM="$REQUIRE_PLATFORM"
|
|
44
|
+
|
|
37
45
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
38
46
|
# shellcheck disable=SC1091
|
|
39
47
|
. "$SCRIPT_DIR/../shared/harness-path.sh"
|
|
@@ -45,6 +53,7 @@ LOG_DIR="$TARGET/$(recipe_runtime_dir)" || exit 1
|
|
|
45
53
|
mkdir -p "$LOG_DIR"
|
|
46
54
|
|
|
47
55
|
BRIDGE_CJS="$(dirname "$0")/bridge-runtime/cdp-bridge.cjs"
|
|
56
|
+
MATCH_LIB="$(dirname "$0")/bridge-runtime/lib/match-bridge-target.cjs"
|
|
48
57
|
STATUS_LOG="$LOG_DIR/bridge-status.log"
|
|
49
58
|
METRO_LOG="$LOG_DIR/metro.log"
|
|
50
59
|
|
|
@@ -86,30 +95,37 @@ bridge_wait_reason() {
|
|
|
86
95
|
bundle_progress "$METRO_LOG"
|
|
87
96
|
}
|
|
88
97
|
|
|
98
|
+
# Matcher is the shared boundary-safe module (match-bridge-target.cjs), so the
|
|
99
|
+
# launch ready-path confirm and this poll enforce identical platform/device rules.
|
|
89
100
|
bridge_has_route() {
|
|
90
101
|
(cd "$TARGET" && APP_ROOT="$TARGET" node "$BRIDGE_CJS" status > "$STATUS_LOG" 2>&1) \
|
|
91
|
-
&& node -
|
|
92
|
-
const
|
|
102
|
+
&& node -e '
|
|
103
|
+
const { hasMatchingRoute } = require(process.argv[1]);
|
|
104
|
+
const fs = require("fs");
|
|
93
105
|
try {
|
|
94
|
-
const value = JSON.parse(fs.readFileSync(process.argv[2],
|
|
95
|
-
|
|
96
|
-
const androidName = process.env.ANDROID_TARGET_DEVICE_NAME || process.env.ANDROID_DEVICE || '';
|
|
97
|
-
const adbSerial = process.env.ADB_SERIAL || process.env.ANDROID_SERIAL || '';
|
|
98
|
-
const iosSimulator = process.env.IOS_SIMULATOR || '';
|
|
99
|
-
const matchesTarget = (target) => {
|
|
100
|
-
if (!target || typeof target !== 'object') return false;
|
|
101
|
-
if (adbSerial || androidName) {
|
|
102
|
-
if (target.platform !== 'android') return false;
|
|
103
|
-
if (!androidName) return true;
|
|
104
|
-
const deviceName = String(target.deviceName || '');
|
|
105
|
-
return deviceName === androidName || deviceName.startsWith(`${androidName} -`);
|
|
106
|
-
}
|
|
107
|
-
if (iosSimulator) return target.deviceName === iosSimulator;
|
|
108
|
-
return true;
|
|
109
|
-
};
|
|
110
|
-
if (!targets.some((t) => matchesTarget(t) && t.route)) process.exit(1);
|
|
106
|
+
const value = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
|
107
|
+
process.exit(hasMatchingRoute(value) ? 0 : 1);
|
|
111
108
|
} catch { process.exit(1); }
|
|
112
|
-
|
|
109
|
+
' "$MATCH_LIB" "$STATUS_LOG"
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
# The device/platform this run is pinned to — the bridge target must belong to it.
|
|
113
|
+
requested_target() {
|
|
114
|
+
node -e 'process.stdout.write(require(process.argv[1]).describeRequested())' "$MATCH_LIB" 2>/dev/null \
|
|
115
|
+
|| printf 'any platform'
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
# Summarise the targets that DID answer (platform / deviceName), so a timeout teaches
|
|
119
|
+
# what responded vs what was requested — e.g. an iOS target answering an android launch.
|
|
120
|
+
answered_targets() {
|
|
121
|
+
[ -s "$STATUS_LOG" ] || { printf 'none'; return; }
|
|
122
|
+
node -e '
|
|
123
|
+
const { describeTargets } = require(process.argv[1]);
|
|
124
|
+
const fs = require("fs");
|
|
125
|
+
try {
|
|
126
|
+
process.stdout.write(describeTargets(JSON.parse(fs.readFileSync(process.argv[2], "utf8"))));
|
|
127
|
+
} catch { process.stdout.write("none"); }
|
|
128
|
+
' "$MATCH_LIB" "$STATUS_LOG" 2>/dev/null || printf 'none'
|
|
113
129
|
}
|
|
114
130
|
|
|
115
131
|
for ATTEMPT in $(seq 1 "$MAX_POLLS"); do
|
|
@@ -124,7 +140,9 @@ for ATTEMPT in $(seq 1 "$MAX_POLLS"); do
|
|
|
124
140
|
done
|
|
125
141
|
|
|
126
142
|
cat "$STATUS_LOG" >&2 || true
|
|
127
|
-
printf 'wait-for-bridge: bridge
|
|
128
|
-
"$PORT" "$MAX_POLLS" "$((MAX_POLLS * 2))" >&2
|
|
143
|
+
printf 'wait-for-bridge: no bridge target matched the requested %s on port %s (%s polls × 2s = %ss)\n' \
|
|
144
|
+
"$(requested_target)" "$PORT" "$MAX_POLLS" "$((MAX_POLLS * 2))" >&2
|
|
145
|
+
printf ' requested: %s\n' "$(requested_target)" >&2
|
|
146
|
+
printf ' answered: %s\n' "$(answered_targets)" >&2
|
|
129
147
|
printf ' Next: check %s — or run mm-harness app-status\n' "$STATUS_LOG" >&2
|
|
130
148
|
exit 1
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -222,13 +222,17 @@ async function watchLog(options) {
|
|
|
222
222
|
const chunk = readNewContent(logPath, offset);
|
|
223
223
|
offset = chunk.offset;
|
|
224
224
|
const allText = fs.existsSync(logPath) ? fs.readFileSync(logPath, 'utf8') : '';
|
|
225
|
+
// Match readiness only against bytes read after this watch started. This
|
|
226
|
+
// assumes line-oriented log writes; a marker split across partial writes
|
|
227
|
+
// waits for a later complete marker instead of stale-passing.
|
|
228
|
+
const matchText = chunk.text;
|
|
225
229
|
const events = extractEvents(allText, eventsLimit);
|
|
226
230
|
|
|
227
|
-
if (matchesPattern(
|
|
231
|
+
if (matchesPattern(matchText, 'Module build failed|^ERROR in |compiled with [1-9][0-9]* error|FAIL runtime-launch|Extension harness launch fail')) {
|
|
228
232
|
renderCompact({ label, logPath, events, quiet: mode === 'quiet' });
|
|
229
233
|
return 1;
|
|
230
234
|
}
|
|
231
|
-
if (matchesPattern(
|
|
235
|
+
if (matchesPattern(matchText, successPattern)) {
|
|
232
236
|
renderCompact({ label, logPath, events, quiet: mode === 'quiet' });
|
|
233
237
|
if (mode !== 'quiet') {
|
|
234
238
|
const elapsed = Math.round((Date.now() - started) / 1000);
|
|
@@ -357,4 +361,4 @@ async function main() {
|
|
|
357
361
|
main().catch((error) => {
|
|
358
362
|
console.error(error instanceof Error ? error.message : String(error));
|
|
359
363
|
process.exit(1);
|
|
360
|
-
});
|
|
364
|
+
});
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -3,8 +3,10 @@ import { recordDepsBaseline } from "@farmslot/recipe-harness/runtime/deps-readin
|
|
|
3
3
|
import { EXIT, spawnScriptStreaming } from "../../commands/shared.js";
|
|
4
4
|
import { runnerDir } from "../../paths.js";
|
|
5
5
|
import {
|
|
6
|
-
decideMobileReadiness
|
|
6
|
+
decideMobileReadiness,
|
|
7
|
+
launchActions
|
|
7
8
|
} from "./runtime-decision.js";
|
|
9
|
+
const READY_BRIDGE_CONFIRM_POLLS = 3;
|
|
8
10
|
const POD_PROBE_ENV = {
|
|
9
11
|
FORCE_COLOR: "0",
|
|
10
12
|
NO_COLOR: "1",
|
|
@@ -42,6 +44,24 @@ async function prepareMobile(target, opts = {}) {
|
|
|
42
44
|
return { status: EXIT.runtime, output: msg };
|
|
43
45
|
}
|
|
44
46
|
if (report.decision === "ready") {
|
|
47
|
+
const confirm = await dispatchAction(
|
|
48
|
+
{ id: "wait-for-bridge", cwd: target, argv: ["--max-polls", String(READY_BRIDGE_CONFIRM_POLLS)] },
|
|
49
|
+
target,
|
|
50
|
+
platform,
|
|
51
|
+
json,
|
|
52
|
+
preflightMode
|
|
53
|
+
);
|
|
54
|
+
if (confirm.status === 0) return { status: 0, output: "" };
|
|
55
|
+
if (!json) {
|
|
56
|
+
process.stderr.write(
|
|
57
|
+
`launch: runtime looked ready but no ${platform} bridge target answered; launching the app.
|
|
58
|
+
`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
for (const action of launchActions(path.resolve(target))) {
|
|
62
|
+
const result = await dispatchAction(action, target, platform, json, preflightMode);
|
|
63
|
+
if (result.status !== 0) return result;
|
|
64
|
+
}
|
|
45
65
|
return { status: 0, output: "" };
|
|
46
66
|
}
|
|
47
67
|
if (report.decision === "unknown") {
|
|
@@ -115,7 +135,8 @@ async function dispatchAction(action, target, platform, json, preflightMode = "f
|
|
|
115
135
|
}
|
|
116
136
|
case "wait-for-bridge": {
|
|
117
137
|
const leaf = path.join(runnerDir, "adapters/mobile/wait-for-bridge.sh");
|
|
118
|
-
|
|
138
|
+
const extra = action.argv ?? [];
|
|
139
|
+
return spawnScriptStreaming(leaf, ["--target", cwd, "--platform", platform, ...extra], target);
|
|
119
140
|
}
|
|
120
141
|
case "clear-metro-cache": {
|
|
121
142
|
const leaf = path.join(runnerDir, "adapters/mobile/start-metro.sh");
|
package/dist/adapters.js
CHANGED
|
@@ -2,7 +2,7 @@ import http from "node:http";
|
|
|
2
2
|
import { compatibilityMode, fixtureSummary, repoShape } from "./doctor.js";
|
|
3
3
|
import { runLiveAdapterScript } from "./live-adapter-contract.js";
|
|
4
4
|
import { withExtensionPage } from "../library/actions/extension/platform/cdp.mjs";
|
|
5
|
-
import { bridgeCommand, evalSync, selectBridgeStatusEntry, simulatorScreenshot } from "../library/actions/mobile/platform/bridge.mjs";
|
|
5
|
+
import { bridgeCommand, evalSync, MOBILE_BRIDGE_ERROR_CODES, selectBridgeStatusEntry, simulatorScreenshot } from "../library/actions/mobile/platform/bridge.mjs";
|
|
6
6
|
function sleep(ms) {
|
|
7
7
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
8
8
|
}
|
|
@@ -331,9 +331,21 @@ async function handleMobileHud(payload, context) {
|
|
|
331
331
|
return mobileHudSkippedOrThrow(error, { nodeId: hud.nodeId, status: hud.status });
|
|
332
332
|
}
|
|
333
333
|
}
|
|
334
|
+
async function hideMobileHudOnTeardown(projectRoot, env = {}) {
|
|
335
|
+
if (process.env.METAMASK_RECIPE_AUTO_HUD === "0" || process.env.METAMASK_RECIPE_AUTO_HUD === "false") return;
|
|
336
|
+
const input = {
|
|
337
|
+
node: { bridge_timeout_ms: 8e3, cdp_timeout_ms: 5e3 },
|
|
338
|
+
context: { nodeId: "teardown", projectRoot, artifactsDir: projectRoot, env }
|
|
339
|
+
};
|
|
340
|
+
try {
|
|
341
|
+
await bridgeCommand(input, ["hide-step"]);
|
|
342
|
+
} catch {
|
|
343
|
+
}
|
|
344
|
+
}
|
|
334
345
|
function mobileHudSkippedOrThrow(error, extra = {}) {
|
|
335
346
|
const reason = traceText(error);
|
|
336
|
-
|
|
347
|
+
const code = isRecord(error) && typeof error.code === "string" ? error.code : void 0;
|
|
348
|
+
if (isMobileHudLifecycleSkip(reason, code)) {
|
|
337
349
|
process.stderr.write(`app.hud skipped (bridge target down or transitioning): ${reason}
|
|
338
350
|
`);
|
|
339
351
|
return {
|
|
@@ -346,20 +358,30 @@ function mobileHudSkippedOrThrow(error, extra = {}) {
|
|
|
346
358
|
}
|
|
347
359
|
throw new Error(`app.hud bridge command failed outside a lifecycle/no-target transition: ${reason}`);
|
|
348
360
|
}
|
|
349
|
-
|
|
361
|
+
const HUD_LIFECYCLE_SKIP_CODES = /* @__PURE__ */ new Set([
|
|
362
|
+
MOBILE_BRIDGE_ERROR_CODES.NO_TARGET,
|
|
363
|
+
MOBILE_BRIDGE_ERROR_CODES.CDP_TIMEOUT,
|
|
364
|
+
MOBILE_BRIDGE_ERROR_CODES.WS_CLOSED,
|
|
365
|
+
MOBILE_BRIDGE_ERROR_CODES.METRO_UNREACHABLE
|
|
366
|
+
]);
|
|
367
|
+
function isMobileHudLifecycleSkip(reason, code) {
|
|
368
|
+
if (code && HUD_LIFECYCLE_SKIP_CODES.has(code)) return true;
|
|
369
|
+
const haystack = reason.toLowerCase();
|
|
350
370
|
return [
|
|
351
371
|
"no responding bridge target",
|
|
352
|
-
"
|
|
372
|
+
"pinned android device",
|
|
353
373
|
"bridge probe failed",
|
|
354
|
-
"
|
|
374
|
+
"econnrefused",
|
|
355
375
|
"timed out",
|
|
356
|
-
"
|
|
357
|
-
|
|
376
|
+
"no react native bridge target",
|
|
377
|
+
// Metro down mid-run: HUD cannot paint, so skip rather than abort (the real
|
|
378
|
+
// action fails on its own). Matches the METRO_UNREACHABLE code branch above.
|
|
379
|
+
"cannot reach metro",
|
|
358
380
|
// Dev-client foreground/reload kills the Hermes page mid-flight: pending
|
|
359
381
|
// CDP calls surface as a message timeout or a closed socket.
|
|
360
|
-
"
|
|
361
|
-
"
|
|
362
|
-
].some((needle) =>
|
|
382
|
+
"cdp message timeout",
|
|
383
|
+
"websocket closed"
|
|
384
|
+
].some((needle) => haystack.includes(needle));
|
|
363
385
|
}
|
|
364
386
|
function animatedFlag(payload) {
|
|
365
387
|
return payload.animated === true ? "--animated" : "--no-animated";
|
|
@@ -511,5 +533,6 @@ function targetProbeUrl(platform, targetPort) {
|
|
|
511
533
|
export {
|
|
512
534
|
createMetaMaskAdapters,
|
|
513
535
|
createMetaMaskUiTransport,
|
|
536
|
+
hideMobileHudOnTeardown,
|
|
514
537
|
isMobileHudLifecycleSkip
|
|
515
538
|
};
|
package/dist/cli-commands.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { listRecipeFiles } from "./recipe-files.js";
|
|
4
5
|
const SPEC = {
|
|
5
6
|
shim: [
|
|
6
7
|
{ name: "sync", desc: "Copy fixture, write slot context, install harness", flags: ["--dry-run", "--no-install"] },
|
|
@@ -16,7 +17,7 @@ const SPEC = {
|
|
|
16
17
|
{ name: "fixtures", desc: "Manage the canonical wallet fixture (sync/set/generate)", args: ["sync", "set", "generate"], flags: ["--fixture", "--out", "--adapter", "--target", "--device", "--json"] },
|
|
17
18
|
{ name: "actions", desc: "List runnable recipe actions", flags: ["--json"] },
|
|
18
19
|
{ name: "doctor", desc: "Check harness/orchestration health", flags: ["--json", "--target", "--adapter", "--runtime-dir", "--expect-live", "--cdp-port", "--device"] },
|
|
19
|
-
{ name: "run", desc: "Execute a proof recipe (path or library name, e.g. run smoke)", args: ["recipe.json|name"], flags: ["--list", "--device"] },
|
|
20
|
+
{ name: "run", desc: "Execute a proof recipe (path or library name, e.g. run perps.smoke)", args: ["recipe.json|name"], flags: ["--list", "--device"] },
|
|
20
21
|
{ name: "recipe-quality", desc: "Build the recipe-quality artifact from compact JSON", args: ["build"], flags: ["--input", "--output", "--json"] },
|
|
21
22
|
{ name: "interactive", aliases: ["menu"], desc: "Interactive command menu" },
|
|
22
23
|
{ name: "prepare", desc: "Install harness (+ optional validate)", flags: ["--target", "--runtime-dir", "--json"] },
|
|
@@ -124,9 +125,9 @@ function listRunRecipes(adapter, root, cwd = process.cwd()) {
|
|
|
124
125
|
for (const resolveDir of RUN_RECIPE_DIRS) {
|
|
125
126
|
const dir = resolveDir(adapter, root);
|
|
126
127
|
if (!fs.existsSync(dir)) continue;
|
|
127
|
-
for (const
|
|
128
|
-
if (
|
|
129
|
-
byBase.set(
|
|
128
|
+
for (const rel of listRecipeFiles(dir)) {
|
|
129
|
+
if (byBase.has(rel)) continue;
|
|
130
|
+
byBase.set(rel, path.join(dir, rel));
|
|
130
131
|
}
|
|
131
132
|
}
|
|
132
133
|
return [...byBase.values()].map((abs) => path.relative(cwd, abs)).sort();
|