@deeeed/metamask-harness 0.3.2 → 0.3.3
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 -0
- package/adapters/mobile/bridge-runtime/setup-wallet.sh +11 -2
- package/adapters/mobile/lib/metro-listener.sh +75 -0
- package/adapters/mobile/prewarm-bundle.sh +9 -1
- package/adapters/mobile/start-metro.sh +16 -57
- package/adapters/mobile/wait-for-bridge.sh +9 -1
- package/package.json +1 -1
- package/src/adapters/mobile/runtime-decision.ts +8 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.3 - 2026-07-04
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- **IMP-23: per-run mobile runtime dir (`RECIPE_RUNTIME_DIR`)** — every on-disk mobile runtime file (`metro.log`, `metro.pid`, `metro.tmux`, `bridge-status.log`, `wallet-fixture.json`) now resolves from the shared runtime-dir resolver that honors `RECIPE_RUNTIME_DIR` instead of the hard-coded `<target>/temp/recipe/runtime`. `RECIPE_RUNTIME_DIR` must be a non-empty relative path under the target checkout — absolute values are rejected at validation. Shell (`recipe_runtime_dir` in `harness-path.sh`, used by `start-metro.sh`, `wait-for-bridge.sh`, `prewarm-bundle.sh`, and `bridge-runtime/setup-wallet.sh`) and TS (`recipeRuntimePath` in `runtime-decision.ts`) resolve the same location, so a run pointed at an isolated subdir (e.g. `temp/recipe/runtime-8081`) writes and reads nothing under the default. Two harness jobs sharing one checkout each set `RECIPE_RUNTIME_DIR` to a distinct relative subdir for full runtime isolation.
|
|
7
|
+
- **IMP-23: port-scoped Metro guard** — the Metro detect/kill/restart helpers are extracted into `adapters/mobile/lib/metro-listener.sh` with an explicit invariant: discovery, inspection, and signalling key off the managed watcher-port only (`lsof -iTCP:<port> -sTCP:LISTEN`). A Metro listening on any other port is never selected, never has its cmdline read, and is never signalled, so a run managing one port cannot detect or kill a concurrent Metro on another port in the same checkout.
|
|
8
|
+
|
|
3
9
|
## 0.3.2 - 2026-07-04
|
|
4
10
|
|
|
5
11
|
### Fixed
|
|
@@ -27,6 +27,15 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
|
27
27
|
APP_ROOT="${APP_ROOT:-$PWD}"
|
|
28
28
|
cd "$APP_ROOT"
|
|
29
29
|
|
|
30
|
+
# Runtime-dir resolver: wallet-fixture.json lands under the same per-run dir as
|
|
31
|
+
# other mobile runtime files, controlled by RECIPE_RUNTIME_DIR (relative, validated).
|
|
32
|
+
# shellcheck disable=SC1091
|
|
33
|
+
. "$SCRIPT_DIR/../../shared/harness-path.sh"
|
|
34
|
+
if ! command -v recipe_runtime_dir >/dev/null 2>&1; then
|
|
35
|
+
echo "setup-wallet: shared lib adapters/shared/harness-path.sh not found; reinstall the runner." >&2
|
|
36
|
+
exit 1
|
|
37
|
+
fi
|
|
38
|
+
|
|
30
39
|
PORT="${WATCHER_PORT:-8081}"
|
|
31
40
|
[[ "$PORT" =~ ^[0-9]+$ ]] || { echo "ERROR: WATCHER_PORT must be numeric (got: $PORT)" >&2; exit 1; }
|
|
32
41
|
SCRIPTS="$SCRIPT_DIR"
|
|
@@ -67,11 +76,11 @@ while [[ $# -gt 0 ]]; do
|
|
|
67
76
|
done
|
|
68
77
|
|
|
69
78
|
# -- Resolve + validate fixture --
|
|
70
|
-
[ -z "$FIXTURE_PATH" ] && FIXTURE_PATH="${WALLET_FIXTURE:-$
|
|
79
|
+
[ -z "$FIXTURE_PATH" ] && FIXTURE_PATH="${WALLET_FIXTURE:-$(recipe_runtime_dir)/wallet-fixture.json}"
|
|
71
80
|
|
|
72
81
|
if [ ! -f "$FIXTURE_PATH" ]; then
|
|
73
82
|
echo "ERROR: Fixture not found: $FIXTURE_PATH"
|
|
74
|
-
echo " create $
|
|
83
|
+
echo " create $(recipe_runtime_dir)/wallet-fixture.json"
|
|
75
84
|
exit 1
|
|
76
85
|
fi
|
|
77
86
|
echo "Reading fixture: $FIXTURE_PATH"
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# metro-listener.sh — port-scoped Metro process helpers.
|
|
3
|
+
#
|
|
4
|
+
# Safety boundary: every function keys off the managed Metro port ($PORT) ONLY.
|
|
5
|
+
# Discovery is `lsof -iTCP:$PORT -sTCP:LISTEN`, so a Metro (or anything) listening
|
|
6
|
+
# on any OTHER port is invisible here — its pid is never in the returned set, its
|
|
7
|
+
# cmdline is never read, and it is never signalled. This is what lets one checkout
|
|
8
|
+
# host concurrent Metro instances on different ports (e.g. 8081 and 8092) while a
|
|
9
|
+
# run that manages 8081 provably cannot detect, restart, or kill the 8092 process.
|
|
10
|
+
#
|
|
11
|
+
# Sourced (not executed) by start-metro.sh and by the contract test. Callers set
|
|
12
|
+
# $PORT (managed watcher-port) and, for metro_listener_valid, $TARGET before use.
|
|
13
|
+
|
|
14
|
+
# metro_ready — true when the managed port serves a running Metro packager status.
|
|
15
|
+
metro_ready() {
|
|
16
|
+
curl -sf --max-time 2 "http://localhost:${PORT}/status" 2>/dev/null | grep -q 'packager-status:running'
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
# metro_listener_pids — pids LISTENing on the managed port (and no other port).
|
|
20
|
+
metro_listener_pids() {
|
|
21
|
+
lsof -nP -iTCP:"${PORT}" -sTCP:LISTEN -t 2>/dev/null | tr '\n' ' ' || true
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
# metro_listener_valid — 0 when the managed-port listener is this checkout's Metro
|
|
25
|
+
# with the required env; 1 when absent, foreign, or missing env (→ restart). Only
|
|
26
|
+
# ever inspects pids listening on $PORT.
|
|
27
|
+
metro_listener_valid() {
|
|
28
|
+
local pids pid command rv
|
|
29
|
+
pids="$(metro_listener_pids)"
|
|
30
|
+
[ -n "$(printf '%s' "$pids" | tr -d ' ')" ] || return 1
|
|
31
|
+
rv=0
|
|
32
|
+
for pid in $pids; do
|
|
33
|
+
command="$(ps eww -p "$pid" -o command= 2>/dev/null || true)"
|
|
34
|
+
case "$command" in
|
|
35
|
+
*"$TARGET"*) ;;
|
|
36
|
+
*)
|
|
37
|
+
printf 'Metro on port %s not owned by this checkout (pid %s); restarting\n' "$PORT" "$pid" >&2
|
|
38
|
+
rv=1; break
|
|
39
|
+
;;
|
|
40
|
+
esac
|
|
41
|
+
for required_var in ${MOBILE_METRO_REQUIRED_ENV:-MM_INFURA_PROJECT_ID}; do
|
|
42
|
+
if ! printf '%s\n' "$command" | grep -Eq "(^|[[:space:]])${required_var}="; then
|
|
43
|
+
printf 'Metro on port %s missing required env %s (pid %s); restarting\n' "$PORT" "$required_var" "$pid" >&2
|
|
44
|
+
rv=1; break 2
|
|
45
|
+
fi
|
|
46
|
+
done
|
|
47
|
+
done
|
|
48
|
+
return "$rv"
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
# stop_metro_listener — TERM then KILL only the pids LISTENing on the managed port.
|
|
52
|
+
# A process on any other port is never signalled. 0 when the port is free.
|
|
53
|
+
stop_metro_listener() {
|
|
54
|
+
local pids i
|
|
55
|
+
pids="$(metro_listener_pids)"
|
|
56
|
+
[ -n "$(printf '%s' "$pids" | tr -d ' ')" ] || return 0
|
|
57
|
+
printf 'Stopping Metro listener on port %s: %s\n' "$PORT" "$pids" >&2
|
|
58
|
+
# shellcheck disable=SC2086
|
|
59
|
+
kill $pids 2>/dev/null || true
|
|
60
|
+
for i in $(seq 1 20); do
|
|
61
|
+
pids="$(metro_listener_pids)"
|
|
62
|
+
[ -z "$(printf '%s' "$pids" | tr -d ' ')" ] && return 0
|
|
63
|
+
sleep 0.25
|
|
64
|
+
done
|
|
65
|
+
printf 'Metro listener still held; force-killing: %s\n' "$pids" >&2
|
|
66
|
+
# shellcheck disable=SC2086
|
|
67
|
+
kill -KILL $pids 2>/dev/null || true
|
|
68
|
+
for i in $(seq 1 20); do
|
|
69
|
+
pids="$(metro_listener_pids)"
|
|
70
|
+
[ -z "$(printf '%s' "$pids" | tr -d ' ')" ] && return 0
|
|
71
|
+
sleep 0.25
|
|
72
|
+
done
|
|
73
|
+
printf 'start-metro: port %s still occupied after force-kill: %s\n' "$PORT" "$pids" >&2
|
|
74
|
+
return 1
|
|
75
|
+
}
|
|
@@ -39,7 +39,15 @@ case "$PLATFORM" in
|
|
|
39
39
|
esac
|
|
40
40
|
|
|
41
41
|
TARGET="$(cd "$TARGET" && pwd)"
|
|
42
|
-
|
|
42
|
+
|
|
43
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
44
|
+
# shellcheck disable=SC1091
|
|
45
|
+
. "$SCRIPT_DIR/../shared/harness-path.sh"
|
|
46
|
+
if ! command -v recipe_runtime_dir >/dev/null 2>&1; then
|
|
47
|
+
echo "prewarm-bundle: shared lib adapters/shared/harness-path.sh not found; reinstall the runner." >&2
|
|
48
|
+
exit 1
|
|
49
|
+
fi
|
|
50
|
+
LOG_DIR="$TARGET/$(recipe_runtime_dir)" || exit 1
|
|
43
51
|
mkdir -p "$LOG_DIR"
|
|
44
52
|
METRO_LOG="$LOG_DIR/metro.log"
|
|
45
53
|
|
|
@@ -37,69 +37,28 @@ while [ "$#" -gt 0 ]; do
|
|
|
37
37
|
done
|
|
38
38
|
|
|
39
39
|
TARGET="$(cd "$TARGET" && pwd)"
|
|
40
|
-
|
|
40
|
+
|
|
41
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
42
|
+
# shellcheck disable=SC1091
|
|
43
|
+
. "$SCRIPT_DIR/../shared/harness-path.sh"
|
|
44
|
+
if ! command -v recipe_runtime_dir >/dev/null 2>&1; then
|
|
45
|
+
echo "start-metro: shared lib adapters/shared/harness-path.sh not found; reinstall the runner." >&2
|
|
46
|
+
exit 1
|
|
47
|
+
fi
|
|
48
|
+
# Port-scoped Metro helpers (metro_ready / *_listener_* / stop_metro_listener)
|
|
49
|
+
# only ever inspect or signal a Metro listening on $PORT.
|
|
50
|
+
# shellcheck disable=SC1091
|
|
51
|
+
. "$SCRIPT_DIR/lib/metro-listener.sh"
|
|
52
|
+
|
|
53
|
+
# RECIPE_RUNTIME_DIR (relative, validated) makes runtime state per-run so jobs
|
|
54
|
+
# sharing one checkout do not collide on metro.log / metro.pid / metro.tmux.
|
|
55
|
+
LOG_DIR="$TARGET/$(recipe_runtime_dir)" || exit 1
|
|
41
56
|
mkdir -p "$LOG_DIR"
|
|
42
57
|
LOG_FILE="$LOG_DIR/metro.log"
|
|
43
58
|
PID_FILE="$LOG_DIR/metro.pid"
|
|
44
59
|
|
|
45
60
|
# --- helpers ------------------------------------------------------------------
|
|
46
61
|
|
|
47
|
-
metro_ready() {
|
|
48
|
-
curl -sf --max-time 2 "http://localhost:${PORT}/status" 2>/dev/null | grep -q 'packager-status:running'
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
metro_listener_pids() {
|
|
52
|
-
lsof -nP -iTCP:"${PORT}" -sTCP:LISTEN -t 2>/dev/null | tr '\n' ' ' || true
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
metro_listener_valid() {
|
|
56
|
-
local pids pid command rv
|
|
57
|
-
pids="$(metro_listener_pids)"
|
|
58
|
-
[ -n "$(printf '%s' "$pids" | tr -d ' ')" ] || return 1
|
|
59
|
-
rv=0
|
|
60
|
-
for pid in $pids; do
|
|
61
|
-
command="$(ps eww -p "$pid" -o command= 2>/dev/null || true)"
|
|
62
|
-
case "$command" in
|
|
63
|
-
*"$TARGET"*) ;;
|
|
64
|
-
*)
|
|
65
|
-
printf 'Metro on port %s not owned by this checkout (pid %s); restarting\n' "$PORT" "$pid" >&2
|
|
66
|
-
rv=1; break
|
|
67
|
-
;;
|
|
68
|
-
esac
|
|
69
|
-
for required_var in ${MOBILE_METRO_REQUIRED_ENV:-MM_INFURA_PROJECT_ID}; do
|
|
70
|
-
if ! printf '%s\n' "$command" | grep -Eq "(^|[[:space:]])${required_var}="; then
|
|
71
|
-
printf 'Metro on port %s missing required env %s (pid %s); restarting\n' "$PORT" "$required_var" "$pid" >&2
|
|
72
|
-
rv=1; break 2
|
|
73
|
-
fi
|
|
74
|
-
done
|
|
75
|
-
done
|
|
76
|
-
return "$rv"
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
stop_metro_listener() {
|
|
80
|
-
local pids i
|
|
81
|
-
pids="$(metro_listener_pids)"
|
|
82
|
-
[ -n "$(printf '%s' "$pids" | tr -d ' ')" ] || return 0
|
|
83
|
-
printf 'Stopping Metro listener on port %s: %s\n' "$PORT" "$pids" >&2
|
|
84
|
-
# shellcheck disable=SC2086
|
|
85
|
-
kill $pids 2>/dev/null || true
|
|
86
|
-
for i in $(seq 1 20); do
|
|
87
|
-
pids="$(metro_listener_pids)"
|
|
88
|
-
[ -z "$(printf '%s' "$pids" | tr -d ' ')" ] && return 0
|
|
89
|
-
sleep 0.25
|
|
90
|
-
done
|
|
91
|
-
printf 'Metro listener still held; force-killing: %s\n' "$pids" >&2
|
|
92
|
-
# shellcheck disable=SC2086
|
|
93
|
-
kill -KILL $pids 2>/dev/null || true
|
|
94
|
-
for i in $(seq 1 20); do
|
|
95
|
-
pids="$(metro_listener_pids)"
|
|
96
|
-
[ -z "$(printf '%s' "$pids" | tr -d ' ')" ] && return 0
|
|
97
|
-
sleep 0.25
|
|
98
|
-
done
|
|
99
|
-
printf 'start-metro: port %s still occupied after force-kill: %s\n' "$PORT" "$pids" >&2
|
|
100
|
-
return 1
|
|
101
|
-
}
|
|
102
|
-
|
|
103
62
|
default_metro_workers() {
|
|
104
63
|
local cores=""
|
|
105
64
|
if command -v sysctl >/dev/null 2>&1; then
|
|
@@ -33,7 +33,15 @@ while [ "$#" -gt 0 ]; do
|
|
|
33
33
|
done
|
|
34
34
|
|
|
35
35
|
TARGET="$(cd "$TARGET" && pwd)"
|
|
36
|
-
|
|
36
|
+
|
|
37
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
38
|
+
# shellcheck disable=SC1091
|
|
39
|
+
. "$SCRIPT_DIR/../shared/harness-path.sh"
|
|
40
|
+
if ! command -v recipe_runtime_dir >/dev/null 2>&1; then
|
|
41
|
+
echo "wait-for-bridge: shared lib adapters/shared/harness-path.sh not found; reinstall the runner." >&2
|
|
42
|
+
exit 1
|
|
43
|
+
fi
|
|
44
|
+
LOG_DIR="$TARGET/$(recipe_runtime_dir)" || exit 1
|
|
37
45
|
mkdir -p "$LOG_DIR"
|
|
38
46
|
|
|
39
47
|
BRIDGE_CJS="$(dirname "$0")/bridge-runtime/cdp-bridge.cjs"
|
package/package.json
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
} from '@farmslot/recipe-harness/runtime/log-analysis';
|
|
16
16
|
import { probeMetroPackager, type MetroReachabilityCheck } from '@farmslot/recipe-harness/runtime/metro-probe';
|
|
17
17
|
|
|
18
|
+
import { recipeRuntimePath } from '../../paths.ts';
|
|
18
19
|
import { mobileProductMarkers } from './deps-markers.ts';
|
|
19
20
|
|
|
20
21
|
/**
|
|
@@ -62,9 +63,14 @@ const BUNDLE_OK = /Bundled \d+ms|iOS Bundled|Android Bundled|Finished bundling/u
|
|
|
62
63
|
const NATIVE_MODULE_STALE =
|
|
63
64
|
/\[runtime not ready\].*HybridObject "([^"]+)" - It has not yet been registered in the Nitro Modules HybridObjectRegistry/u;
|
|
64
65
|
|
|
66
|
+
// The metro.log location honors RECIPE_RUNTIME_DIR via the shared recipeRuntimePath
|
|
67
|
+
// resolver (relative, validated), so the decision engine reads metro.log from the
|
|
68
|
+
// same per-run dir the mobile leaf scripts write — two jobs sharing one checkout
|
|
69
|
+
// stay isolated. An explicit metroLog override (a --metro-log flag) still wins.
|
|
65
70
|
function resolveMetroLog(target: string, metroLog?: string): string | null {
|
|
66
|
-
const
|
|
67
|
-
|
|
71
|
+
const abs = metroLog
|
|
72
|
+
? (path.isAbsolute(metroLog) ? metroLog : path.join(target, metroLog))
|
|
73
|
+
: recipeRuntimePath(target, 'metro.log');
|
|
68
74
|
return fs.existsSync(abs) ? abs : null;
|
|
69
75
|
}
|
|
70
76
|
|