@deeeed/metamask-harness 0.51.7 → 0.51.8

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
+ ## Unreleased
4
+
5
+ ## 0.51.8 - 2026-09-15
6
+
7
+ ### Fixed
8
+
9
+ - Mobile launch replaces a Metro process that Metro itself reported unusable (delta-graph corruption after a commit failed mid-way, or its "Detected a change in babel.config.js. Restart the server" advisory) by stopping it before `start-metro`, keeping the transform cache. Previously every "restart Metro" verdict and the `metro.restarted` recovery reused the live listener, so a Metro that outlived a bundler-config change failed every bundle until someone killed it by hand. Plain bundle errors still never restart Metro, and `--clear` stays explicit. `start-metro` now records the content hashes of the bundler config files so an advisory raised by an mtime-only touch is ignored; a Metro started before this release has no record, so its first advisory restarts it once.
10
+
3
11
  ## 0.51.7 - 2026-09-14
4
12
 
5
13
  ### Added
@@ -257,6 +257,27 @@ printf '(Full log: %s — mm-harness logs | mm-harness logs --full)\n' "$LOG_FIL
257
257
  ROTATION_REASON="metro-start"
258
258
  [ "$CLEAR" = true ] && ROTATION_REASON="clear-cache-restart"
259
259
  node "$METRO_LOG_GENERATION" "$LOG_DIR" "$PORT" "$ROTATION_REASON" >/dev/null || exit 1
260
+ # Record what this process loads from the files Expo's FileNotifier watches, so
261
+ # the readiness decision can tell a real config change from an mtime-only touch
262
+ # when Metro later prints its "Detected a change in <file>" advisory.
263
+ record_metro_config_hashes() {
264
+ local file hash hashes=""
265
+ for file in babel.config.js metro.config.js metro.transform.js app.json app.config.js; do
266
+ [ -f "$TARGET/$file" ] || continue
267
+ if command -v shasum >/dev/null 2>&1; then
268
+ hash="$(shasum -a 256 "$TARGET/$file" | cut -d' ' -f1)"
269
+ else
270
+ hash="$(sha256sum "$TARGET/$file" | cut -d' ' -f1)"
271
+ fi
272
+ # An entry without a hash could never match and would force a restart on
273
+ # every advisory; leave the file out so the decision fails closed to
274
+ # "changed" for that file only, visibly.
275
+ [ -n "$hash" ] || continue
276
+ hashes+="$hash $file"$'\n'
277
+ done
278
+ printf '%s' "$hashes" > "$LOG_DIR/metro-config.sha256"
279
+ }
280
+ record_metro_config_hashes
260
281
 
261
282
  write_metro_build_env || {
262
283
  printf 'start-metro: could not stage the scoped Metro environment\n' >&2
@@ -6,7 +6,8 @@
6
6
  set -euo pipefail
7
7
 
8
8
  TARGET="."
9
- PORT="${WATCHER_PORT:-}"
9
+ # Same fallback order as start-metro so stop and start always agree on the port.
10
+ PORT="${WATCHER_PORT:-${METRO_PORT:-}}"
10
11
  while [ $# -gt 0 ]; do
11
12
  case "$1" in
12
13
  --target) TARGET="$2"; shift 2 ;;
@@ -14,7 +15,7 @@ while [ $# -gt 0 ]; do
14
15
  -h|--help)
15
16
  printf 'Usage: stop-metro.sh [--target <dir>] [--port <port>]\n'
16
17
  printf ' --target MetaMask Mobile checkout directory\n'
17
- printf ' --port Metro port (default: WATCHER_PORT env, else the slot context, else 8081)\n'
18
+ printf ' --port Metro port (default: WATCHER_PORT env, else METRO_PORT, else the slot context, else 8081)\n'
18
19
  exit 0
19
20
  ;;
20
21
  *) printf 'stop-metro: unknown arg: %s\n' "$1" >&2; exit 2 ;;
@@ -63,6 +64,7 @@ set +e
63
64
  rm -f "$PID_FILE"
64
65
  rm -f "$LOG_DIR/metro-launch.json"
65
66
  rm -f "$LOG_DIR/metro-build-env.sh"
67
+ rm -f "$LOG_DIR/metro-config.sha256"
66
68
 
67
69
  # start-metro runs a per-checkout console-forwarder holding the device debugger
68
70
  # slot; Metro going down must take it too, or the orphan keeps polling and later
@@ -386,6 +386,11 @@ async function dispatchAction(action, target, platform, json, preflightMode = "f
386
386
  const leaf = path.join(runnerDir, "adapters/mobile/yarn-setup.sh");
387
387
  return spawnScriptStreaming(leaf, ["--target", cwd], target, POD_PROBE_ENV);
388
388
  }
389
+ case "stop-metro": {
390
+ const leaf = path.join(runnerDir, "adapters/mobile/stop-metro.sh");
391
+ const port = watcherPort ?? Number(process.env.WATCHER_PORT || process.env.METRO_PORT || 8081);
392
+ return spawnScriptStreaming(leaf, ["--target", cwd, "--port", String(port)], target);
393
+ }
389
394
  case "start-metro": {
390
395
  const leaf = path.join(runnerDir, "adapters/mobile/start-metro.sh");
391
396
  const extra = action.argv ?? [];
@@ -1,4 +1,5 @@
1
1
  import { execFileSync } from "node:child_process";
2
+ import { createHash } from "node:crypto";
2
3
  import fs from "node:fs";
3
4
  import path from "node:path";
4
5
  import {
@@ -16,6 +17,12 @@ import { mobileProductMarkers } from "./deps-markers.js";
16
17
  import { mobileMetroEnvCheck } from "./metro-env.js";
17
18
  const BUNDLE_ERR = /Bundling failed|Unable to resolve /u;
18
19
  const BUNDLE_OK = /Bundled \d+ms|iOS Bundled|Android Bundled|Finished bundling/u;
20
+ const METRO_GRAPH_ROLLBACK = /attempted to roll back a graph commit but there were still changes/u;
21
+ const METRO_GRAPH_COMMIT_THROW = /Got unexpected undefined/u;
22
+ const METRO_GRAPH_FRAME = /DeltaBundler\/Graph\.js/u;
23
+ const METRO_RESTART_ADVISORY = /Detected a change in ([\w.-]+)\. Restart the server/u;
24
+ const METRO_REASON_MAX = 200;
25
+ const METRO_CONFIG_HASHES_FILE = "metro-config.sha256";
19
26
  const NATIVE_MODULE_STALE = /\[runtime not ready\].*HybridObject "([^"]+)" - It has not yet been registered in the Nitro Modules HybridObjectRegistry/u;
20
27
  function resolveMetroLog(target, metroLog) {
21
28
  const abs = metroLog ? path.isAbsolute(metroLog) ? metroLog : path.join(target, metroLog) : recipeRuntimePath(target, "metro.log");
@@ -87,6 +94,46 @@ function appRunningOnDevice(platform) {
87
94
  return false;
88
95
  }
89
96
  }
97
+ function metroConfigUnchangedSinceStart(target, relative) {
98
+ let recorded;
99
+ try {
100
+ recorded = fs.readFileSync(recipeRuntimePath(target, METRO_CONFIG_HASHES_FILE), "utf8");
101
+ } catch {
102
+ return false;
103
+ }
104
+ const entry = recorded.split("\n").find((line) => line.endsWith(` ${relative}`));
105
+ if (!entry) return false;
106
+ try {
107
+ const current = createHash("sha256").update(fs.readFileSync(path.join(target, relative))).digest("hex");
108
+ return entry.startsWith(current);
109
+ } catch {
110
+ return false;
111
+ }
112
+ }
113
+ function metroProcessUnusable(target, logText) {
114
+ const lines = logText.split("\n");
115
+ let lastOk = -1;
116
+ for (let i = lines.length - 1; i >= 0; i -= 1) {
117
+ if (BUNDLE_OK.test(lines[i])) {
118
+ lastOk = i;
119
+ break;
120
+ }
121
+ }
122
+ for (let i = lines.length - 1; i > lastOk; i -= 1) {
123
+ if (METRO_GRAPH_ROLLBACK.test(lines[i])) return lines[i].trim().slice(0, METRO_REASON_MAX);
124
+ if (METRO_GRAPH_COMMIT_THROW.test(lines[i]) && lines.slice(i + 1, i + 4).some((frame) => METRO_GRAPH_FRAME.test(frame))) {
125
+ return lines[i].trim().slice(0, METRO_REASON_MAX);
126
+ }
127
+ }
128
+ const advised = /* @__PURE__ */ new Set();
129
+ for (const line of lines) {
130
+ const advisory = METRO_RESTART_ADVISORY.exec(line);
131
+ if (!advisory || advised.has(advisory[1])) continue;
132
+ advised.add(advisory[1]);
133
+ if (!metroConfigUnchangedSinceStart(target, advisory[1])) return advisory[0];
134
+ }
135
+ return null;
136
+ }
90
137
  const launchActions = (target, clearMetro = false) => {
91
138
  const actions = [];
92
139
  actions.push({
@@ -218,6 +265,21 @@ async function computeMobileReadiness(resolved, options, fast) {
218
265
  ]
219
266
  };
220
267
  }
268
+ const unusable = metro.status !== "down" && metroLog.reason !== "unresolved-module" ? metroProcessUnusable(resolved, metroLogText) : null;
269
+ if (unusable) {
270
+ return {
271
+ schemaVersion: 1,
272
+ adapter: "mobile",
273
+ target: resolved,
274
+ decision: "launch",
275
+ reasonCode: "metro-restart-required",
276
+ reasons: [
277
+ `Metro reported its process unusable ("${unusable}"); restarting Metro and keeping its transform cache. Pass --clear-metro only if the same failure returns in the new process.`
278
+ ],
279
+ checks,
280
+ actions: [{ id: "stop-metro", cwd: resolved }, ...launchActions(resolved)]
281
+ };
282
+ }
221
283
  if (metroLog.status === "errors") {
222
284
  const depsSatisfied = deps.status === "current";
223
285
  if (depsSatisfied && (metroLog.reason === "stale-bundle-error" || metroLog.reason === "bundle-error")) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deeeed/metamask-harness",
3
- "version": "0.51.7",
3
+ "version": "0.51.8",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mm-harness": "bin/mm-harness"