@deeeed/metamask-harness 0.9.0 → 0.9.1

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 CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.9.1 - 2026-07-07
4
+
5
+ ### Fixed
6
+ - **Metro survives the launching shell.** `start-metro.sh` spawned Metro without SIGHUP protection, so closing the launching tmux window/shell killed it, leaving a stale `metro.pid` and hanging every subsequent bridge/fixtures call. Metro now starts under `nohup` (argv spawn, no `bash -c` string interpolation), and a stale `metro.pid` naming a dead process is detected and cleaned before start.
7
+ - **`--device <adb serial>` reaches CDP target selection.** The runner's ambiguity gate resolved the serial, but target discovery compared it against Metro's `deviceName` ("Pixel 6 - 16 - API 36") — never a serial — so with an iOS simulator also attached, a recipe pinned to the physical Android device could silently drive the simulator. The serial is now mapped to the Metro identity via `adb -s <serial> shell getprop ro.product.model` with model-prefix matching (scoped to non-simulator targets); an unmatchable or ambiguous pin (two same-model devices) fails fast listing every Metro `/json/list` candidate instead of silently picking one. `--device <serial>` remains the only thing users pass.
8
+ - **Bridge commands always emit valid JSON.** `get-route` printed the literal string `undefined` when the route was transiently unavailable mid-navigation, so `bridgeCommand()` threw on parse and `waitForRoute()` aborted instead of polling. `get-route`/`navigate`/`go-back` now normalise a missing route to `null`, `bridgeCommand()` treats `''`/`undefined` stdout as not-settled-yet only for transient-legitimate commands, and `waitForRoute()` polls through `null` until timeout — the timeout error carries the expected route, last parsed route, last bridge reply and the device pin.
9
+ - **Wallet setup env propagation.** `bridgeEnv()` became async with the serial mapping; the wallet setup action now awaits it instead of spreading a Promise, which would have handed `setup-wallet.sh` an almost-empty environment.
10
+
3
11
  ## 0.9.0 - 2026-07-07
4
12
 
5
13
  ### Added
@@ -104,7 +104,17 @@ const COMMANDS = {
104
104
  client,
105
105
  'globalThis.__AGENTIC__?.getRoute()',
106
106
  );
107
- return { navigated: routeName, params, previousRoute, currentRoute, deviceName, platform };
107
+ // Normalize route values: cdpEval returns undefined when the optional-chain
108
+ // short-circuits (mid-navigation transient or bridge not yet installed).
109
+ // null is valid JSON; undefined produces the literal string "undefined".
110
+ return {
111
+ navigated: routeName,
112
+ params,
113
+ previousRoute: previousRoute ?? null,
114
+ currentRoute: currentRoute ?? null,
115
+ deviceName,
116
+ platform,
117
+ };
108
118
  },
109
119
 
110
120
  async 'get-route'(client) {
@@ -112,7 +122,10 @@ const COMMANDS = {
112
122
  client,
113
123
  'globalThis.__AGENTIC__?.getRoute()',
114
124
  );
115
- return route;
125
+ // cdpEval returns undefined when the optional-chain short-circuits (bridge not
126
+ // yet installed, mid-navigation, or route state transiently missing). Return null
127
+ // so JSON.stringify produces valid JSON ("null") instead of literal "undefined".
128
+ return route ?? null;
116
129
  },
117
130
 
118
131
  async 'get-state'(client, args) {
@@ -168,7 +181,9 @@ const COMMANDS = {
168
181
  client,
169
182
  'globalThis.__AGENTIC__?.getRoute()',
170
183
  );
171
- return { currentRoute: route, deviceName, platform };
184
+ // Same normalization as navigate/get-route: a transiently-missing route must
185
+ // serialize as "currentRoute": null, not have the key silently omitted.
186
+ return { currentRoute: route ?? null, deviceName, platform };
172
187
  },
173
188
 
174
189
  async status(client, _args, { deviceName, platform } = {}) {
@@ -30,10 +30,22 @@ function loadSimulatorName() {
30
30
  return loadEnvValue('IOS_SIMULATOR') || '';
31
31
  }
32
32
 
33
- /** Read ANDROID_DEVICE serial from .js.env or env (default: none — accept any device) */
33
+ /** Read ANDROID_DEVICE from .js.env or env (default: none — accept any device) */
34
34
  function loadAndroidDevice() {
35
35
  if ('ANDROID_DEVICE' in process.env) return process.env.ANDROID_DEVICE;
36
36
  return loadEnvValue('ANDROID_DEVICE') || '';
37
37
  }
38
38
 
39
- module.exports = { loadEnvValue, loadPort, loadSimulatorName, loadAndroidDevice };
39
+ /**
40
+ * Read ANDROID_TARGET_DEVICE_NAME from env — the Metro-compatible model prefix
41
+ * resolved from an adb serial by bridge.mjs. Used by target-discovery to select
42
+ * the correct Metro CDP target when multiple devices are connected.
43
+ *
44
+ * Set automatically by the bridge when --device <serial> is used. Users never
45
+ * need to set this; pass --device <adb-serial> and the harness maps it internally.
46
+ */
47
+ function loadAndroidTargetDeviceName() {
48
+ return process.env.ANDROID_TARGET_DEVICE_NAME || '';
49
+ }
50
+
51
+ module.exports = { loadEnvValue, loadPort, loadSimulatorName, loadAndroidDevice, loadAndroidTargetDeviceName };
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const http = require('node:http');
4
- const { loadSimulatorName, loadAndroidDevice } = require('./config.cjs');
4
+ const { loadSimulatorName, loadAndroidDevice, loadAndroidTargetDeviceName } = require('./config.cjs');
5
5
  const { createWSClient } = require('./ws-client.cjs');
6
6
 
7
7
  const FETCH_TIMEOUT_MS = Number.parseInt(process.env.CDP_TIMEOUT || '30000', 10);
@@ -125,14 +125,76 @@ async function discoverTarget(port) {
125
125
  }
126
126
  }
127
127
 
128
- // Filter by device name if ANDROID_DEVICE is set
128
+ // Android device filtering: support two identity layers.
129
+ // ANDROID_TARGET_DEVICE_NAME — Metro-compatible model prefix resolved by bridge.mjs
130
+ // from the adb serial via `adb -s <serial> shell getprop ro.product.model`.
131
+ // Example: "Pixel 6" matches Metro deviceName "Pixel 6 - 16 - API 36".
132
+ // ANDROID_DEVICE — exact Metro deviceName (backward compat / user-specified).
133
+ //
134
+ // When a device is pinned and multiple candidates remain, we MUST NOT silently fall
135
+ // back to another target (e.g. an iOS simulator). If the pin cannot be matched,
136
+ // fail fast with diagnostics so the operator can diagnose the mismatch.
137
+ const androidTargetName = loadAndroidTargetDeviceName();
129
138
  const androidDevice = loadAndroidDevice();
130
- if (androidDevice && candidates.length > 1) {
131
- const deviceFiltered = candidates.filter(
132
- (t) => t.deviceName === androidDevice,
133
- );
134
- if (deviceFiltered.length > 0) {
135
- candidates = deviceFiltered;
139
+ const adbSerial = process.env.ADB_SERIAL || process.env.ANDROID_SERIAL || '';
140
+
141
+ if (candidates.length > 1) {
142
+ let androidFiltered = [];
143
+
144
+ if (androidTargetName) {
145
+ // Model prefix match: "Pixel 6" matches "Pixel 6 - 16 - API 36". Metro's
146
+ // /json/list has no explicit platform field, so scope the ANDROID pin by
147
+ // excluding the one iOS identity we do know (the pinned simulator name) —
148
+ // a simulator named to shadow a model string must not satisfy an android pin.
149
+ androidFiltered = candidates.filter(
150
+ (t) => t.deviceName !== simName &&
151
+ (t.deviceName === androidTargetName ||
152
+ (t.deviceName != null && t.deviceName.startsWith(androidTargetName))),
153
+ );
154
+ if (androidFiltered.length > 1) {
155
+ // Two same-model devices produce identical Metro model prefixes — the pin
156
+ // is genuinely ambiguous; picking one silently is exactly the bug this
157
+ // path exists to prevent.
158
+ const ambiguousList = androidFiltered
159
+ .map((t) => ` deviceName=${JSON.stringify(t.deviceName || '')} ws=${t.webSocketDebuggerUrl || ''}`)
160
+ .join('\n');
161
+ throw new Error(
162
+ `Pinned Android device is ambiguous: model '${androidTargetName}' matches ${androidFiltered.length} Metro targets.\n` +
163
+ ` Requested --device (ADB_SERIAL): ${adbSerial || '(not set)'}\n` +
164
+ ` Matching Metro targets:\n${ambiguousList}\n` +
165
+ ` Set ANDROID_DEVICE to the exact Metro deviceName to disambiguate.`,
166
+ );
167
+ }
168
+ if (androidFiltered.length === 0) {
169
+ // Pinned device could not be matched — never silently pick another target.
170
+ const candidateList = targets
171
+ .map((t) => ` deviceName=${JSON.stringify(t.deviceName || '')} ws=${t.webSocketDebuggerUrl || ''}`)
172
+ .join('\n');
173
+ throw new Error(
174
+ `Pinned Android device did not match any Metro target.\n` +
175
+ ` Requested --device (ADB_SERIAL): ${adbSerial || '(not set)'}\n` +
176
+ ` Resolved model (ANDROID_TARGET_DEVICE_NAME): ${androidTargetName}\n` +
177
+ ` ANDROID_DEVICE: ${androidDevice || '(not set)'}\n` +
178
+ ` Metro /json/list candidates:\n${candidateList}`,
179
+ );
180
+ }
181
+ candidates = androidFiltered;
182
+ } else if (androidDevice) {
183
+ // Exact Metro deviceName match (user set ANDROID_DEVICE to the Metro name, or
184
+ // legacy path where ANDROID_DEVICE was not resolved from serial).
185
+ androidFiltered = candidates.filter((t) => t.deviceName === androidDevice);
186
+ if (androidFiltered.length === 0) {
187
+ // Pinned by exact Metro name but no candidate matched — fail fast.
188
+ const candidateList = targets
189
+ .map((t) => ` deviceName=${JSON.stringify(t.deviceName || '')} ws=${t.webSocketDebuggerUrl || ''}`)
190
+ .join('\n');
191
+ throw new Error(
192
+ `Pinned Android device (ANDROID_DEVICE='${androidDevice}') did not match any Metro target.\n` +
193
+ ` ADB_SERIAL: ${adbSerial || '(not set)'}\n` +
194
+ ` Metro /json/list candidates:\n${candidateList}`,
195
+ );
196
+ }
197
+ candidates = androidFiltered;
136
198
  }
137
199
  }
138
200
 
@@ -79,6 +79,18 @@ default_metro_workers() {
79
79
 
80
80
  # --- main ---------------------------------------------------------------------
81
81
 
82
+ # Detect stale metro.pid: if the pid file names a dead process, clean it up.
83
+ # A stale file survives when Metro is killed externally (session close, SIGKILL)
84
+ # and the normal stop path never ran. Cleaning it here keeps metro_ready and the
85
+ # log from attributing a new start to the old process id.
86
+ if [ -f "$PID_FILE" ]; then
87
+ stale_pid="$(cat "$PID_FILE" 2>/dev/null || true)"
88
+ if [ -n "$stale_pid" ] && ! kill -0 "$stale_pid" 2>/dev/null; then
89
+ printf 'start-metro: stale metro.pid (pid %s no longer alive); cleaning up\n' "$stale_pid" >&2
90
+ rm -f "$PID_FILE"
91
+ fi
92
+ fi
93
+
82
94
  # Decide whether to (re)start or skip.
83
95
  if metro_ready; then
84
96
  if [ "$CLEAR" = true ]; then
@@ -97,11 +109,11 @@ fi
97
109
  stop_metro_listener || exit 1
98
110
 
99
111
  METRO_WORKERS="${METRO_MAX_WORKERS:-$(default_metro_workers)}"
100
- CLEAR_SUFFIX=""
101
- [ "$CLEAR" = true ] && CLEAR_SUFFIX=" --clear"
112
+ CLEAR_LABEL=""
113
+ [ "$CLEAR" = true ] && CLEAR_LABEL=", clear"
102
114
 
103
115
  printf 'Starting Metro on port %s (workers=%s%s)\n' \
104
- "$PORT" "$METRO_WORKERS" "${CLEAR:+, clear}" >&2
116
+ "$PORT" "$METRO_WORKERS" "$CLEAR_LABEL" >&2
105
117
  printf '(Full log: %s — mm-harness logs | mm-harness logs --full)\n' "$LOG_FILE" >&2
106
118
 
107
119
  # Metro runs detached, writing to the log. The tmux window is a read-only tail.
@@ -127,12 +139,17 @@ printf '(Full log: %s — mm-harness logs | mm-harness logs --full)\n' "$LOG_FIL
127
139
  export WATCHER_PORT="${PORT}" METRO_PORT="${PORT}"
128
140
  export METRO_MAX_WORKERS="${METRO_WORKERS}"
129
141
  # shellcheck disable=SC2094
130
- bash -c "exec yarn expo start --port ${PORT}${CLEAR_SUFFIX}" \
142
+ # nohup sets SIGHUP to ignored (inherited across exec), so Metro survives the
143
+ # launching shell or tmux window closing; when this subshell exits the process
144
+ # is reparented to init. That alone is the detach guarantee — Metro was never
145
+ # in the parent shell's job table, so there is nothing to disown.
146
+ # nohup.out is suppressed (stdout/stderr both go to $LOG_FILE via >>).
147
+ metro_args=(expo start --port "$PORT")
148
+ [ "$CLEAR" = true ] && metro_args+=(--clear)
149
+ nohup yarn "${metro_args[@]}" \
131
150
  </dev/null >> "$LOG_FILE" 2>&1 &
132
151
  echo "$!" > "$PID_FILE"
133
152
  )
134
- # Disown the background process so it outlives this script.
135
- disown "$(cat "$PID_FILE" 2>/dev/null || true)" 2>/dev/null || true
136
153
 
137
154
  start_viewer_window "$LOG_FILE"
138
155
 
package/docs/CLI-SPEC.md CHANGED
@@ -439,7 +439,9 @@ Readiness check for a checkout without launching the app. Doctor is the single p
439
439
 
440
440
  ## `--device <id>` — first-class mobile device targeting (REAL)
441
441
 
442
- `--device` is the uniform mobile device selector on `run`, `call`, and `doctor`. It sets the **same env `launch` does** before the engine reads `process.env`, so the engine needs no changes: an Android serial `ADB_SERIAL` + `ANDROID_SERIAL` + `ANDROID_DEVICE`; an iOS UDID/simulator name `IOS_SIMULATOR`. The android-vs-ios decision comes from matching the id against the connected-device lists (`adb devices -l` + booted `xcrun simctl` simulators); an id that matches neither list is a teaching usage error that lists the connected devices.
442
+ `--device` is the uniform mobile device selector on `run`, `call`, and `doctor`. Pass the **adb serial** for Android (from `adb devices`) or the **UDID / simulator name** for iOS. The harness resolves the adb serial to the Metro CDP target identity internally users never need to know or set `ANDROID_DEVICE='Pixel 6 - 16 - API 36'`.
443
+
444
+ **Internal Android identity mapping**: `ADB_SERIAL` / `ANDROID_SERIAL` carry the raw adb serial. The bridge resolves the device model via `adb -s <serial> shell getprop ro.product.model` and propagates it as `ANDROID_TARGET_DEVICE_NAME`. Target-discovery uses `ANDROID_TARGET_DEVICE_NAME` for Metro `deviceName` prefix matching (e.g. `"Pixel 6"` matches `"Pixel 6 - 16 - API 36"`). When the pinned model cannot be matched to any Metro `/json/list` candidate and multiple candidates exist, the bridge fails fast with a diagnostic listing every candidate's `deviceName` — it never silently selects the wrong device. iOS UDID/simulator name → `IOS_SIMULATOR` (unchanged).
443
445
 
444
446
  | Verb | `--device` given | `--device` omitted (mobile) |
445
447
  |---|---|---|
@@ -1,8 +1,11 @@
1
1
  import { mkdir, readFile, writeFile } from 'node:fs/promises';
2
- import { spawn } from 'node:child_process';
2
+ import { execFile, spawn } from 'node:child_process';
3
+ import { promisify } from 'node:util';
3
4
  import path from 'node:path';
4
5
  import { fileURLToPath } from 'node:url';
5
6
 
7
+ const execFileAsync = promisify(execFile);
8
+
6
9
  function sleep(ms) {
7
10
  return new Promise((resolve) => setTimeout(resolve, ms));
8
11
  }
@@ -34,7 +37,30 @@ function runtimeDir() {
34
37
  return fileURLToPath(new URL('../../../../adapters/mobile/bridge-runtime', import.meta.url));
35
38
  }
36
39
 
37
- export function bridgeEnv(input) {
40
+ /**
41
+ * Resolve the Android model name from an adb serial.
42
+ * Returns the trimmed ro.product.model value, or null when adb is unavailable
43
+ * or the serial does not respond. Uses execFile (never shell interpolation).
44
+ */
45
+ async function resolveAndroidModel(adbSerial) {
46
+ try {
47
+ const { stdout } = await execFileAsync(
48
+ 'adb',
49
+ ['-s', adbSerial, 'shell', 'getprop', 'ro.product.model'],
50
+ { timeout: 5000, encoding: 'utf8' },
51
+ );
52
+ const model = stdout.trim();
53
+ return model || null;
54
+ } catch {
55
+ // Recovery is correct: model resolution for Metro target selection is advisory.
56
+ // If adb is unavailable or the serial is unreachable, discovery falls through to
57
+ // the exact ANDROID_DEVICE match or probe-and-pick. The error will surface from
58
+ // the CDP connection attempt if the wrong target is selected.
59
+ return null;
60
+ }
61
+ }
62
+
63
+ export async function bridgeEnv(input) {
38
64
  /** @type {NodeJS.ProcessEnv} */
39
65
  const env = {
40
66
  ...process.env,
@@ -53,6 +79,20 @@ export function bridgeEnv(input) {
53
79
  env.ADB_SERIAL = String(adbSerial);
54
80
  env.ANDROID_SERIAL = String(adbSerial);
55
81
  }
82
+ // When --device passes an adb serial, device-target.ts sets both ANDROID_DEVICE and
83
+ // ADB_SERIAL to the same value. ANDROID_DEVICE must be the Metro deviceName for
84
+ // target-discovery to select the right CDP target; it cannot be a raw adb serial.
85
+ // Resolve the device model via adb getprop and propagate it as
86
+ // ANDROID_TARGET_DEVICE_NAME so target-discovery can match by model prefix
87
+ // (e.g. "Pixel 6" matches Metro deviceName "Pixel 6 - 16 - API 36").
88
+ const serialStr = adbSerial != null ? String(adbSerial) : '';
89
+ const deviceStr = androidDevice != null ? String(androidDevice) : '';
90
+ if (serialStr && deviceStr === serialStr) {
91
+ const model = await resolveAndroidModel(serialStr);
92
+ if (model) {
93
+ env.ANDROID_TARGET_DEVICE_NAME = model;
94
+ }
95
+ }
56
96
  return env;
57
97
  }
58
98
 
@@ -65,13 +105,20 @@ function resolveMobileTarget(input) {
65
105
  return { watcherPort, iosSimulator, androidDevice, adbSerial };
66
106
  }
67
107
 
108
+ // Commands where a transient undefined/empty stdout means "not yet settled", not failure.
109
+ // get-route returns undefined mid-navigation when the route state is momentarily unavailable;
110
+ // waitForRoute polls through these nulls rather than aborting on a parse error.
111
+ const TRANSIENT_NULL_COMMANDS = new Set(['get-route']);
112
+
68
113
  export async function bridgeCommand(input, args) {
69
114
  const script = bridgeScript(input);
115
+ // bridgeEnv is async: it may call `adb getprop` to resolve the Metro device name.
116
+ const env = await bridgeEnv(input);
70
117
  const result = await new Promise((resolve, reject) => {
71
118
  const timeoutMs = Number(input.node?.bridge_timeout_ms ?? input.node?.cdp_timeout_ms ?? process.env.CDP_TIMEOUT ?? 30000);
72
119
  const child = spawn(process.execPath, [script, ...args], {
73
120
  cwd: input.context.projectRoot,
74
- env: { ...bridgeEnv(input), APP_ROOT: input.context.projectRoot },
121
+ env: { ...env, APP_ROOT: input.context.projectRoot },
75
122
  stdio: ['ignore', 'pipe', 'pipe'],
76
123
  });
77
124
  let stdout = '';
@@ -111,6 +158,14 @@ export async function bridgeCommand(input, args) {
111
158
  const command = ['node', path.relative(input.context.projectRoot, script), ...redactBridgeArgs(args)].join(' ');
112
159
  throw new Error(`Mobile CDP bridge command failed: ${command}\n${redactBridgeOutput(result.stderr || result.stdout, sensitiveBridgeArgs(args))}`);
113
160
  }
161
+ // For transient commands, stdout of '' or 'undefined' means the state is not yet
162
+ // settled (e.g. mid-navigation). Return null so callers like waitForRoute can poll
163
+ // rather than aborting on a JSON parse error.
164
+ const command = String(args[0] ?? '');
165
+ const trimmedStdout = result.stdout.trim();
166
+ if ((trimmedStdout === '' || trimmedStdout === 'undefined') && TRANSIENT_NULL_COMMANDS.has(command)) {
167
+ return null;
168
+ }
114
169
  try {
115
170
  const parsed = JSON.parse(result.stdout);
116
171
  if (parsed && typeof parsed === 'object' && !Array.isArray(parsed) && parsed.ok === false) {
@@ -195,13 +250,35 @@ function routeName(route) {
195
250
  export async function waitForRoute(input, expectedRoute, timeoutMs = 15000) {
196
251
  const expected = String(expectedRoute);
197
252
  const deadline = Date.now() + timeoutMs;
253
+ // lastRoute is null when bridgeCommand returns null (transient: route not yet
254
+ // settled mid-navigation). Null means "not ready yet" — keep polling.
198
255
  let lastRoute = null;
256
+ let pollCount = 0;
199
257
  while (Date.now() < deadline) {
200
258
  lastRoute = await bridgeCommand(input, ['get-route']);
201
259
  if (routeName(lastRoute) === expected) return lastRoute;
260
+ pollCount += 1;
202
261
  await sleep(250);
203
262
  }
204
- throw new Error(`Timed out waiting for Mobile route ${expected}; last route was ${JSON.stringify(lastRoute)}`);
263
+ const target = resolveMobileTarget(input);
264
+ const deviceHint = [
265
+ target.adbSerial && `ADB_SERIAL=${target.adbSerial}`,
266
+ target.androidDevice && target.androidDevice !== target.adbSerial && `ANDROID_DEVICE=${target.androidDevice}`,
267
+ target.iosSimulator && `IOS_SIMULATOR=${target.iosSimulator}`,
268
+ ].filter(Boolean).join(', ') || 'no device pin';
269
+ // bridgeCommand already parsed (or null-normalized) the reply, so raw stdout is
270
+ // not available here — describe the reply honestly instead of relabeling the
271
+ // parsed value as "raw".
272
+ const lastReply = lastRoute === null
273
+ ? 'empty/undefined (route transiently unavailable — bridge not yet settled)'
274
+ : JSON.stringify(lastRoute);
275
+ throw new Error(
276
+ `Timed out waiting for Mobile route '${expected}' after ${timeoutMs}ms (${pollCount} polls).\n` +
277
+ ` Expected route: ${expected}\n` +
278
+ ` Last parsed route: ${JSON.stringify(lastRoute)}\n` +
279
+ ` Last bridge reply: ${lastReply}\n` +
280
+ ` Device: ${deviceHint}`,
281
+ );
205
282
  }
206
283
 
207
284
  export async function simulatorScreenshot(input, relPath) {
@@ -112,7 +112,10 @@ function setupWalletScript() {
112
112
  return fileURLToPath(new URL('../../../../adapters/mobile/bridge-runtime/setup-wallet.sh', import.meta.url));
113
113
  }
114
114
 
115
- function runSetupWallet(input, fixture) {
115
+ async function runSetupWallet(input, fixture) {
116
+ // bridgeEnv is async (it may shell adb to resolve the Metro device name);
117
+ // spreading its un-awaited Promise would hand the child an almost-empty env.
118
+ const env = await bridgeEnv(input);
116
119
  return new Promise((resolve, reject) => {
117
120
  const timeoutMs = Number(
118
121
  input.node?.setup_timeout_ms ?? input.node?.timeout_ms ?? 120000,
@@ -122,7 +125,7 @@ function runSetupWallet(input, fixture) {
122
125
  [setupWalletScript(), '--fixture', fixture.absolutePath],
123
126
  {
124
127
  cwd: input.context.projectRoot,
125
- env: { ...bridgeEnv(input), CDP_TIMEOUT: String(timeoutMs), APP_ROOT: input.context.projectRoot },
128
+ env: { ...env, CDP_TIMEOUT: String(timeoutMs), APP_ROOT: input.context.projectRoot },
126
129
  stdio: ['ignore', 'pipe', 'pipe'],
127
130
  },
128
131
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deeeed/metamask-harness",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mm-harness": "bin/mm-harness"