@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
|
-
|
|
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")) {
|