@deeeed/metamask-harness 0.17.1 → 0.17.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 +36 -10
- package/README.md +12 -6
- 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 +1 -0
- package/adapters/extension/stop-viewers.sh +0 -0
- package/adapters/extension/sync-webpack-dist.cjs +1 -1
- package/adapters/extension/verify.sh +0 -0
- package/adapters/extension/wallet-fixture-state.cjs +0 -0
- package/adapters/manifest.json +9 -1
- package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +0 -0
- package/adapters/mobile/bridge-runtime/lib/match-bridge-target.cjs +3 -1
- package/adapters/mobile/bridge-runtime/setup-wallet.sh +0 -0
- package/adapters/mobile/cleanup.sh +0 -0
- package/adapters/mobile/inject.sh +13 -11
- package/adapters/mobile/launch-console-forwarder.cjs +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-console-forwarder.sh +3 -3
- package/adapters/mobile/start-metro.sh +0 -0
- package/adapters/mobile/stop-metro.sh +0 -0
- package/adapters/mobile/verify.sh +0 -0
- package/adapters/mobile/wait-for-bridge.sh +22 -8
- 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/install-repo-deps.sh +0 -0
- package/adapters/shared/json-field.sh +0 -0
- package/adapters/shared/open-debug.mjs +7 -1
- package/adapters/shared/open-log-window.sh +0 -0
- package/adapters/shared/reap-checkout-metros.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/adapters/shared/tmux-viewer.sh +0 -0
- package/adapters/shared/update-check-worker.mjs +35 -0
- package/dist/adapters/extension/console-capture.js +10 -1
- package/dist/adapters/mobile/prepare.js +5 -5
- package/dist/adapters/slot-ports.js +8 -0
- package/dist/cli.js +1 -1
- package/dist/commands/doctor.js +33 -8
- package/dist/commands/run-engine.js +2 -4
- package/dist/commands/update.js +16 -28
- package/dist/harness.js +24 -9
- package/dist/mm-harness-cli.js +8 -7
- package/dist/run-diagnostics.js +1 -6
- package/docs/ADAPTER-SURFACE.md +19 -112
- package/docs/CLI-ERGONOMICS-HUMAN-QA.md +16 -0
- package/docs/UX-PRINCIPLES.md +1 -1
- package/docs/VIDEO-DEMO-VALIDATION.md +56 -44
- package/docs/recipe-libraries.md +45 -275
- package/library/README.md +4 -0
- package/library/manifests/core.action-manifest.json +7 -7
- package/library/recipes/runner/smoke.core.recipe.json +27 -0
- package/library/recipes/wallet/smoke.extension.recipe.json +42 -0
- package/library/recipes/wallet/smoke.mobile.recipe.json +42 -0
- package/package.json +4 -2
- package/scripts/completions.sh +0 -0
- package/scripts/install-completions.sh +0 -0
- package/scripts/validate-human-outcomes.mjs +169 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,37 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.17.3 - 2026-07-15
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added a real-checkout human-outcome gate covering live readiness, runtime verification, debugger target resolution, smoke evidence, and unchanged tracked product source across Extension, Mobile, and Core.
|
|
10
|
+
- Added generic Core runner smoke and fixture-backed wallet smoke recipes so the reusable examples are not tied to Perps.
|
|
11
|
+
- Added nine-slot isolation coverage for platform ports, runtime files, logs, and headless Core behavior.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- `doctor` now reports CDP or simulator/ADB screenshot fallback when `capture-helper` is absent or unhealthy, while identifying video as unavailable separately.
|
|
16
|
+
- Action, flow, and recipe discovery/documentation now use one concise scope model; the default recipe-library and adapter-surface references were reduced to current behavior.
|
|
17
|
+
- Fixture discovery now uses one canonical checkout path instead of legacy fallback locations.
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- Mobile launch now waits for two consecutive platform-matched agentic bridge snapshots, preventing a transient startup route from being reported ready before an immediate verify.
|
|
22
|
+
- `verify --json` keeps stdout to one machine-readable JSON document even when the platform harness must first be installed.
|
|
23
|
+
- Mobile install metadata safely preserves checkout paths containing shell-significant characters.
|
|
24
|
+
- Cleanup discovery no longer advertises a removed compatibility flag, and Core recovery guidance now points to the canonical wallet fixture.
|
|
25
|
+
- Mobile console forwarding now uses canonical checkout paths, readiness rejects impossible one-poll stability checks, and Extension debug handles malformed WebSocket frames without crashing.
|
|
26
|
+
- Extension watch refreshes remove stale runtime files, delay file replacement until transfer completion, allow slower console attachment, and stop the detached webpack process group.
|
|
27
|
+
|
|
28
|
+
## 0.17.2 - 2026-07-15
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
|
|
32
|
+
- Passive update discovery now runs in a detached, once-daily registry probe, so fast commands populate the cache without adding latency or contaminating machine output.
|
|
33
|
+
|
|
34
|
+
## 0.17.1 - 2026-07-15
|
|
35
|
+
|
|
5
36
|
### Fixed
|
|
6
37
|
|
|
7
38
|
- Extension `debug` now uses Chrome's native DevTools command for the active slot target and opens directly on Console; `debug --worker` targets the service worker without creating a frontend browser tab.
|
|
@@ -9,6 +40,11 @@
|
|
|
9
40
|
- Successful Extension watch rebuilds now update the isolated loaded runtime in place, so a normal refresh of the current fullscreen or sidepanel UI shows source edits without another harness build or relaunch.
|
|
10
41
|
- Extension slot titles are stamped into the isolated runtime after snapshots and successful rebuilds, preserving the slot identifier across an operator's normal browser refresh without changing product source.
|
|
11
42
|
- Extension sidepanel opening no longer waits for the harness-owned user-gesture button to pass visual actionability checks; occluded wallet loading surfaces cannot block the trusted click.
|
|
43
|
+
- Human recipe results now print an absolute-path artifact inventory and expand run-scoped application diagnostics inline; single-action calls show the same `CLEAN`/`REVIEW` diagnostics instead of requiring another query.
|
|
44
|
+
- Extension console collection now starts after every successful public launch path, including quick reattach, and persists as one checkout-scoped process until `stop`. MetaMask UI/service-worker events and dapp-page events are routed to separate `extension-console.log` and `dapp-console.log` files; bare `logs` defaults to MetaMask while `--source dapp` and `--source webpack` remain explicit.
|
|
45
|
+
- Mobile CDP-forwarded app events now write to `app-console.log`, so `logs --source app` and run diagnostics are no longer mixed with Metro bundler output; warm and cold launches start a process-group-detached collector only after bridge readiness so capture remains live after the launch command exits.
|
|
46
|
+
- Extension `debug` now opens Chrome's local DevTools frontend inside the exact slot-owned profile, targets the MetaMask extension page rather than the dapp, and verifies the debugging connection before reporting success.
|
|
47
|
+
- Mobile fixture application now fails immediately when the checkout fixture is absent, rejects the misplaced `fixtures set --dev` flag with the correct initialization command, and never prints a success message after failure.
|
|
12
48
|
|
|
13
49
|
### Changed
|
|
14
50
|
|
|
@@ -20,16 +56,6 @@
|
|
|
20
56
|
- Removed compatibility-only runtime-start fields, hidden `live` execution, removed-command handlers, undomained recipe aliases, flat/non-ESM action lookup, old port-resolver filenames, duplicate Extension launcher naming, obsolete fixture search locations, and the alternate Mobile product-source bridge. Mobile install now requires the current product-owned bridge and writes metadata only.
|
|
21
57
|
- Removed the historical CLI migration/spec documents; current behavior is documented in the README, cheatsheet, architecture, and human QA checklist.
|
|
22
58
|
|
|
23
|
-
## 0.17.1 - 2026-07-14
|
|
24
|
-
|
|
25
|
-
### Fixed
|
|
26
|
-
|
|
27
|
-
- Human recipe results now print an absolute-path artifact inventory and expand run-scoped application diagnostics inline; single-action calls show the same `CLEAN`/`REVIEW` diagnostics instead of requiring another query.
|
|
28
|
-
- Extension console collection now starts after every successful public launch path, including quick reattach, and persists as one checkout-scoped process until `stop`. MetaMask UI/service-worker events and dapp-page events are routed to separate `extension-console.log` and `dapp-console.log` files; bare `logs` defaults to MetaMask while `--source dapp` and `--source webpack` remain explicit.
|
|
29
|
-
- Mobile CDP-forwarded app events now write to `app-console.log`, so `logs --source app` and run diagnostics are no longer mixed with Metro bundler output; warm and cold launches start a process-group-detached collector only after bridge readiness so capture remains live after the launch command exits.
|
|
30
|
-
- Extension `debug` now opens Chrome's local DevTools frontend inside the exact slot-owned profile, targets the MetaMask extension page rather than the dapp, and verifies the debugging connection before reporting success.
|
|
31
|
-
- Mobile fixture application now fails immediately when the checkout fixture is absent, rejects the misplaced `fixtures set --dev` flag with the correct initialization command, and never prints a success message after failure.
|
|
32
|
-
|
|
33
59
|
## 0.17.0 - 2026-07-14
|
|
34
60
|
|
|
35
61
|
### Added
|
package/README.md
CHANGED
|
@@ -12,6 +12,12 @@ It has two jobs:
|
|
|
12
12
|
The generic graph engine lives in `@farmslot/recipe-harness`. This package owns
|
|
13
13
|
only MetaMask-specific actions and runtime control.
|
|
14
14
|
|
|
15
|
+
One mental model:
|
|
16
|
+
|
|
17
|
+
- **Action** — one typed operation, such as reading wallet state.
|
|
18
|
+
- **Flow** — a reusable, parameterized sequence of actions.
|
|
19
|
+
- **Recipe** — the complete executable proof for a task or regression.
|
|
20
|
+
|
|
15
21
|
## Install
|
|
16
22
|
|
|
17
23
|
```bash
|
|
@@ -70,11 +76,11 @@ mm-harness flows
|
|
|
70
76
|
|
|
71
77
|
mm-harness run path/to/recipe.json --plan
|
|
72
78
|
mm-harness run path/to/recipe.json --artifacts-dir temp/recipe-proof
|
|
79
|
+
mm-harness run wallet.smoke --artifacts-dir temp/wallet-proof
|
|
73
80
|
```
|
|
74
81
|
|
|
75
|
-
`
|
|
76
|
-
|
|
77
|
-
libraries. A recipe connects those capabilities into a deterministic proof.
|
|
82
|
+
`call` exercises one action. A recipe composes actions and flows into a
|
|
83
|
+
deterministic proof.
|
|
78
84
|
|
|
79
85
|
A run prints its findings, diagnostics, and absolute evidence paths. The artifact
|
|
80
86
|
directory contains the executed recipe, summary, trace, diagnostics when
|
|
@@ -126,6 +132,6 @@ Validate changes with:
|
|
|
126
132
|
yarn check
|
|
127
133
|
```
|
|
128
134
|
|
|
129
|
-
Current references: [cheatsheet](docs/CHEATSHEET.md), [
|
|
130
|
-
|
|
131
|
-
and
|
|
135
|
+
Current references: [cheatsheet](docs/CHEATSHEET.md), [recipe libraries](docs/recipe-libraries.md),
|
|
136
|
+
and [human QA](docs/CLI-ERGONOMICS-HUMAN-QA.md). Maintainers also have the
|
|
137
|
+
[architecture](docs/architecture.md) and [video gate](docs/VIDEO-DEMO-VALIDATION.md).
|
package/adapters/core/cleanup.sh
CHANGED
|
File without changes
|
package/adapters/core/inject.sh
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -182,6 +182,7 @@ rm -rf node_modules/.cache/webpack
|
|
|
182
182
|
killed_harness_pid=""
|
|
183
183
|
if [ -f "$RUNTIME_DIR/recipe-harness-webpack.pid" ]; then
|
|
184
184
|
killed_harness_pid="$(cat "$RUNTIME_DIR/recipe-harness-webpack.pid" 2>/dev/null || true)"
|
|
185
|
+
[ -z "$killed_harness_pid" ] || kill -- "-$killed_harness_pid" >/dev/null 2>&1 || true
|
|
185
186
|
[ -z "$killed_harness_pid" ] || kill "$killed_harness_pid" >/dev/null 2>&1 || true
|
|
186
187
|
rm -f "$RUNTIME_DIR/recipe-harness-webpack.pid"
|
|
187
188
|
fi
|
|
File without changes
|
|
@@ -51,7 +51,7 @@ function removeOwnPidFile() {
|
|
|
51
51
|
|
|
52
52
|
function syncRuntimeDist() {
|
|
53
53
|
if (!fs.existsSync(path.join(dist, 'manifest.json')) || !fs.existsSync(runtimeDist)) return;
|
|
54
|
-
const result = spawnSync('rsync', ['-a', '--exclude', '_metadata', `${dist}${path.sep}`, `${runtimeDist}${path.sep}`], {
|
|
54
|
+
const result = spawnSync('rsync', ['-a', '--delete', '--delay-updates', '--exclude', '_metadata', `${dist}${path.sep}`, `${runtimeDist}${path.sep}`], {
|
|
55
55
|
encoding: 'utf8',
|
|
56
56
|
});
|
|
57
57
|
if (result.status !== 0) {
|
|
File without changes
|
|
File without changes
|
package/adapters/manifest.json
CHANGED
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"entry": "adapters/mobile/cleanup.sh",
|
|
88
88
|
"kind": "bash",
|
|
89
89
|
"purpose": "Remove Mobile harness metadata and recorded git excludes without touching product source.",
|
|
90
|
-
"inputs": "--target
|
|
90
|
+
"inputs": "--target; env RECIPE_HARNESS_ROOT",
|
|
91
91
|
"outputs": "removes <harness>/; exit 0/1/2"
|
|
92
92
|
},
|
|
93
93
|
{
|
|
@@ -486,6 +486,14 @@
|
|
|
486
486
|
"purpose": "Open human-facing debugger UIs for extension CDP or mobile Metro runtimes.",
|
|
487
487
|
"inputs": "--adapter extension|mobile [--cdp-port|--port] [--target page|worker] [--json] [--no-open]",
|
|
488
488
|
"outputs": "debug URL/report on stdout; exit 0/1"
|
|
489
|
+
},
|
|
490
|
+
{
|
|
491
|
+
"id": "lib/update-check-worker",
|
|
492
|
+
"entry": "adapters/shared/update-check-worker.mjs",
|
|
493
|
+
"kind": "node",
|
|
494
|
+
"purpose": "Detached, silent registry probe used by the daily passive update notice.",
|
|
495
|
+
"inputs": "<cache-file> <timeout-ms> [registry-endpoint]",
|
|
496
|
+
"outputs": "atomic update-cache refresh; always silent and best-effort"
|
|
489
497
|
}
|
|
490
498
|
]
|
|
491
499
|
}
|
|
File without changes
|
|
@@ -50,7 +50,9 @@ function matchesBridgeTarget(target, env) {
|
|
|
50
50
|
// (an in-app agentic bridge is live), not merely a registered debug target.
|
|
51
51
|
function hasMatchingRoute(value, env) {
|
|
52
52
|
const targets = Array.isArray(value) ? value : [value];
|
|
53
|
-
return targets.some(
|
|
53
|
+
return targets.some(
|
|
54
|
+
(t) => matchesBridgeTarget(t, env) && t && t.agenticPresent === true && Boolean(t.route),
|
|
55
|
+
);
|
|
54
56
|
}
|
|
55
57
|
|
|
56
58
|
// Human summary of the targets that answered (platform / deviceName), for teaching on
|
|
File without changes
|
|
File without changes
|
|
@@ -98,23 +98,25 @@ fi
|
|
|
98
98
|
RUNNER_REV="$(git -C "$RUNNER_DIR" rev-parse HEAD 2>/dev/null || echo unknown)"
|
|
99
99
|
CLEANUP_Q="$(printf '%q' "$SCRIPT_DIR/cleanup.sh")"
|
|
100
100
|
TARGET_CLEANUP_Q="$(printf '%q' "$TARGET")"
|
|
101
|
-
|
|
101
|
+
CLEANUP_COMMAND="RECIPE_HARNESS_ROOT=$(printf '%q' "$HARNESS_ROOT") $CLEANUP_Q --target $TARGET_CLEANUP_Q"
|
|
102
|
+
node - "$HARNESS_DIR/manifest.json" "$TARGET_REV" "$RUNNER_DIR" "$RUNNER_REV" "$TARGET" "$HARNESS_REL" "$HARNESS_ROOT" "$CLEANUP_COMMAND" <<'NODE'
|
|
102
103
|
const fs = require('node:fs');
|
|
103
|
-
|
|
104
|
+
const [manifestPath, targetRevision, runnerDir, runnerRevision, target, harnessRel, harnessRoot, cleanupCommand] = process.argv.slice(2);
|
|
105
|
+
fs.writeFileSync(manifestPath, JSON.stringify({
|
|
104
106
|
adapter: 'mobile',
|
|
105
107
|
installMode: 'product-owned',
|
|
106
|
-
targetRevision
|
|
108
|
+
targetRevision,
|
|
107
109
|
installedAt: new Date().toISOString(),
|
|
108
|
-
source: { runnerDir
|
|
109
|
-
target
|
|
110
|
+
source: { runnerDir, runnerRevision },
|
|
111
|
+
target,
|
|
110
112
|
protocolVersion: 'v1',
|
|
111
|
-
actionManifestPath:
|
|
112
|
-
runnerEntrypoint:
|
|
113
|
-
runtimeHelpers: { verify:
|
|
114
|
-
harnessInstalledPaths: [
|
|
113
|
+
actionManifestPath: `${harnessRel}/action-manifest.json`,
|
|
114
|
+
runnerEntrypoint: `${harnessRel}/runner/bin/mm-harness`,
|
|
115
|
+
runtimeHelpers: { verify: `${harnessRel}/scripts/verify.sh` },
|
|
116
|
+
harnessInstalledPaths: [`${harnessRel}/runner`, `${harnessRel}/scripts`, `${harnessRel}/action-manifest.json`],
|
|
115
117
|
productOwnedPaths: ['app/dev-tools/AgenticService', 'app/core/NavigationService/NavigationService.ts'],
|
|
116
|
-
cleanupCommand
|
|
117
|
-
productDiffExcludes: [
|
|
118
|
+
cleanupCommand,
|
|
119
|
+
productDiffExcludes: [`:(exclude)${harnessRoot}`],
|
|
118
120
|
}, null, 2) + '\n');
|
|
119
121
|
NODE
|
|
120
122
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -8,8 +8,8 @@ QUIET=false
|
|
|
8
8
|
|
|
9
9
|
while [ "$#" -gt 0 ]; do
|
|
10
10
|
case "$1" in
|
|
11
|
-
--target) TARGET="$2"; shift 2 ;;
|
|
12
|
-
--port) PORT="$2"; shift 2 ;;
|
|
11
|
+
--target) [ "$#" -ge 2 ] || { echo "Missing value for --target" >&2; exit 2; }; TARGET="$2"; shift 2 ;;
|
|
12
|
+
--port) [ "$#" -ge 2 ] || { echo "Missing value for --port" >&2; exit 2; }; PORT="$2"; shift 2 ;;
|
|
13
13
|
--quiet) QUIET=true; shift ;;
|
|
14
14
|
-h|--help)
|
|
15
15
|
printf 'Usage: start-console-forwarder.sh [--target <dir>] [--port <port>] [--quiet]\n'
|
|
@@ -20,7 +20,7 @@ while [ "$#" -gt 0 ]; do
|
|
|
20
20
|
done
|
|
21
21
|
|
|
22
22
|
[ "${METAMASK_RECIPE_CONSOLE_FORWARD:-1}" != "0" ] || exit 0
|
|
23
|
-
TARGET="$(cd "$TARGET" && pwd)"
|
|
23
|
+
TARGET="$(cd "$TARGET" && pwd -P)"
|
|
24
24
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
25
25
|
# shellcheck disable=SC1091
|
|
26
26
|
. "$SCRIPT_DIR/../shared/harness-path.sh"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -24,10 +24,10 @@ REQUIRE_PLATFORM=""
|
|
|
24
24
|
|
|
25
25
|
while [ "$#" -gt 0 ]; do
|
|
26
26
|
case "$1" in
|
|
27
|
-
--target) TARGET="$2"; shift 2 ;;
|
|
28
|
-
--port) PORT="$2"; shift 2 ;;
|
|
29
|
-
--max-polls) MAX_POLLS="$2"; shift 2 ;;
|
|
30
|
-
--platform) REQUIRE_PLATFORM="$2"; shift 2 ;;
|
|
27
|
+
--target) [ "$#" -ge 2 ] || { echo "Missing value for --target" >&2; exit 2; }; TARGET="$2"; shift 2 ;;
|
|
28
|
+
--port) [ "$#" -ge 2 ] || { echo "Missing value for --port" >&2; exit 2; }; PORT="$2"; shift 2 ;;
|
|
29
|
+
--max-polls) [ "$#" -ge 2 ] || { echo "Missing value for --max-polls" >&2; exit 2; }; MAX_POLLS="$2"; shift 2 ;;
|
|
30
|
+
--platform) [ "$#" -ge 2 ] || { echo "Missing value for --platform" >&2; exit 2; }; REQUIRE_PLATFORM="$2"; shift 2 ;;
|
|
31
31
|
-h|--help)
|
|
32
32
|
printf 'Usage: wait-for-bridge.sh [--target <dir>] [--port <n>] [--max-polls <n>] [--platform ios|android]\n'
|
|
33
33
|
exit 0
|
|
@@ -36,7 +36,12 @@ while [ "$#" -gt 0 ]; do
|
|
|
36
36
|
esac
|
|
37
37
|
done
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
case "$MAX_POLLS" in
|
|
40
|
+
''|*[!0-9]*) echo "wait-for-bridge: --max-polls must be an integer of at least 2." >&2; exit 2 ;;
|
|
41
|
+
esac
|
|
42
|
+
[ "$MAX_POLLS" -ge 2 ] || { echo "wait-for-bridge: --max-polls must be at least 2 for stable readiness." >&2; exit 2; }
|
|
43
|
+
|
|
44
|
+
TARGET="$(cd "$TARGET" && pwd -P)"
|
|
40
45
|
|
|
41
46
|
# The matcher reads the required platform from the environment so the value survives
|
|
42
47
|
# into the node subprocess that runs the shared matcher against the status output.
|
|
@@ -128,15 +133,24 @@ try {
|
|
|
128
133
|
' "$MATCH_LIB" "$STATUS_LOG" 2>/dev/null || printf 'none'
|
|
129
134
|
}
|
|
130
135
|
|
|
136
|
+
READY_STREAK=0
|
|
131
137
|
for ATTEMPT in $(seq 1 "$MAX_POLLS"); do
|
|
132
138
|
if bridge_has_route; then
|
|
133
|
-
|
|
134
|
-
|
|
139
|
+
READY_STREAK=$((READY_STREAK + 1))
|
|
140
|
+
# The bridge can appear briefly while React Native is still completing its
|
|
141
|
+
# startup navigation/reload. Require two consecutive healthy snapshots so a
|
|
142
|
+
# following command never inherits that transient gap.
|
|
143
|
+
if [ "$READY_STREAK" -ge 2 ]; then
|
|
144
|
+
printf 'Mobile bridge ready\n' >&2
|
|
145
|
+
exit 0
|
|
146
|
+
fi
|
|
147
|
+
else
|
|
148
|
+
READY_STREAK=0
|
|
135
149
|
fi
|
|
136
150
|
if [ "$ATTEMPT" = "1" ] || [ $((ATTEMPT % 5)) -eq 0 ]; then
|
|
137
151
|
printf 'Waiting for Mobile bridge (%s/%s): %s\n' "$ATTEMPT" "$MAX_POLLS" "$(bridge_wait_reason)" >&2
|
|
138
152
|
fi
|
|
139
|
-
sleep 2
|
|
153
|
+
[ "$ATTEMPT" -ge "$MAX_POLLS" ] || sleep 2
|
|
140
154
|
done
|
|
141
155
|
|
|
142
156
|
cat "$STATUS_LOG" >&2 || true
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -123,7 +123,13 @@ function sendBrowserCommand(webSocketDebuggerUrl, method, params) {
|
|
|
123
123
|
}));
|
|
124
124
|
});
|
|
125
125
|
socket.on('message', (raw) => {
|
|
126
|
-
|
|
126
|
+
let message;
|
|
127
|
+
try {
|
|
128
|
+
message = JSON.parse(String(raw));
|
|
129
|
+
} catch {
|
|
130
|
+
finish({ error: { message: `${method} returned a non-JSON WebSocket frame` } });
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
127
133
|
if (message.id !== 1) return;
|
|
128
134
|
finish(message);
|
|
129
135
|
});
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
const [cacheFile, timeoutText, endpoint] = process.argv.slice(2);
|
|
6
|
+
const timeoutMs = Number.parseInt(timeoutText ?? '', 10);
|
|
7
|
+
|
|
8
|
+
async function main() {
|
|
9
|
+
if (!cacheFile || !endpoint || !Number.isInteger(timeoutMs) || timeoutMs < 1) return;
|
|
10
|
+
const controller = new AbortController();
|
|
11
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
12
|
+
try {
|
|
13
|
+
const response = await fetch(endpoint, { signal: controller.signal });
|
|
14
|
+
if (!response.ok) return;
|
|
15
|
+
const data = await response.json();
|
|
16
|
+
if (!data || typeof data.latest !== 'string' || data.latest.length === 0) return;
|
|
17
|
+
let lastCheck = Date.now();
|
|
18
|
+
try {
|
|
19
|
+
const cached = JSON.parse(fs.readFileSync(cacheFile, 'utf8'));
|
|
20
|
+
if (typeof cached.lastCheck === 'number') lastCheck = cached.lastCheck;
|
|
21
|
+
} catch {
|
|
22
|
+
// The parent may have exited before creating the cache; recreate it below.
|
|
23
|
+
}
|
|
24
|
+
fs.mkdirSync(path.dirname(cacheFile), { recursive: true });
|
|
25
|
+
const temporary = `${cacheFile}.${process.pid}.tmp`;
|
|
26
|
+
fs.writeFileSync(temporary, JSON.stringify({ lastCheck, latest: data.latest }));
|
|
27
|
+
fs.renameSync(temporary, cacheFile);
|
|
28
|
+
} catch {
|
|
29
|
+
// Passive checks are intentionally silent; the explicit update command teaches failures.
|
|
30
|
+
} finally {
|
|
31
|
+
clearTimeout(timer);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
await main();
|
|
@@ -47,12 +47,21 @@ async function ensureExtensionConsoleCapture(target) {
|
|
|
47
47
|
child.unref();
|
|
48
48
|
fs.writeFileSync(pidFile, `${child.pid}
|
|
49
49
|
`);
|
|
50
|
-
for (let attempt = 0; attempt <
|
|
50
|
+
for (let attempt = 0; attempt < 100; attempt += 1) {
|
|
51
|
+
if (!processAlive(child.pid)) break;
|
|
51
52
|
if (collectorOwnsLog(child.pid, extensionLog) && containsAttachment(extensionLog, dappLog)) return;
|
|
52
53
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
53
54
|
}
|
|
54
55
|
throw new Error(`Extension console collector ${child.pid} did not attach to CDP ${cdpPort}.`);
|
|
55
56
|
}
|
|
57
|
+
function processAlive(pid) {
|
|
58
|
+
try {
|
|
59
|
+
process.kill(pid, 0);
|
|
60
|
+
return true;
|
|
61
|
+
} catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
56
65
|
function readPid(pidFile) {
|
|
57
66
|
try {
|
|
58
67
|
const pid = Number.parseInt(fs.readFileSync(pidFile, "utf8").trim(), 10);
|
|
@@ -56,7 +56,7 @@ async function prepareMobile(target, opts = {}) {
|
|
|
56
56
|
json,
|
|
57
57
|
preflightMode
|
|
58
58
|
);
|
|
59
|
-
if (confirm.status === 0) return startMobileConsoleForwarder(target, json);
|
|
59
|
+
if (confirm.status === 0) return startMobileConsoleForwarder(target, platform, json);
|
|
60
60
|
if (!json) {
|
|
61
61
|
process.stderr.write(
|
|
62
62
|
`launch: runtime looked ready but no ${platform} bridge target answered; launching the app.
|
|
@@ -70,7 +70,7 @@ async function prepareMobile(target, opts = {}) {
|
|
|
70
70
|
json,
|
|
71
71
|
preflightMode
|
|
72
72
|
);
|
|
73
|
-
return launch.status === 0 ? startMobileConsoleForwarder(target, json) : launch;
|
|
73
|
+
return launch.status === 0 ? startMobileConsoleForwarder(target, platform, json) : launch;
|
|
74
74
|
}
|
|
75
75
|
if (report.decision === "unknown") {
|
|
76
76
|
const msg = "mobile prepare: runtime state unknown\n Next: run mm-harness verify --adapter mobile --target <checkout>";
|
|
@@ -118,13 +118,13 @@ async function prepareMobile(target, opts = {}) {
|
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
|
-
return startMobileConsoleForwarder(target, json);
|
|
121
|
+
return startMobileConsoleForwarder(target, platform, json);
|
|
122
122
|
}
|
|
123
|
-
function startMobileConsoleForwarder(target, json) {
|
|
123
|
+
function startMobileConsoleForwarder(target, platform, json) {
|
|
124
124
|
return dispatchAction(
|
|
125
125
|
{ id: "start-console-forwarder", cwd: target, argv: json ? ["--quiet"] : [] },
|
|
126
126
|
target,
|
|
127
|
-
|
|
127
|
+
platform,
|
|
128
128
|
json
|
|
129
129
|
);
|
|
130
130
|
}
|
|
@@ -113,6 +113,10 @@ function stopExtensionWatcher(target) {
|
|
|
113
113
|
} catch {
|
|
114
114
|
}
|
|
115
115
|
for (const pid of ownedPids) {
|
|
116
|
+
try {
|
|
117
|
+
process.kill(-pid, "SIGTERM");
|
|
118
|
+
} catch {
|
|
119
|
+
}
|
|
116
120
|
try {
|
|
117
121
|
process.kill(pid, "SIGTERM");
|
|
118
122
|
} catch {
|
|
@@ -123,6 +127,10 @@ function stopExtensionWatcher(target) {
|
|
|
123
127
|
if (ownedPids.length > 0) {
|
|
124
128
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 2e3);
|
|
125
129
|
for (const pid of ownedPids) {
|
|
130
|
+
try {
|
|
131
|
+
process.kill(-pid, "SIGKILL");
|
|
132
|
+
} catch {
|
|
133
|
+
}
|
|
126
134
|
try {
|
|
127
135
|
process.kill(pid, "SIGKILL");
|
|
128
136
|
} catch {
|
package/dist/cli.js
CHANGED
|
@@ -104,7 +104,7 @@ async function main(argv) {
|
|
|
104
104
|
const target = targetPath(options);
|
|
105
105
|
const adapterRaw = (typeof options["adapter"] === "string" ? options["adapter"] : void 0) ?? (typeof options["platform"] === "string" ? options["platform"] : void 0) ?? detectAdapter(target);
|
|
106
106
|
if (adapterRaw === "mobile" || adapterRaw === "extension" || adapterRaw === "core") {
|
|
107
|
-
await handleHarness(["install", "--platform", adapterRaw, "--target", target]);
|
|
107
|
+
await handleHarness(["install", "--platform", adapterRaw, "--target", target, "--quiet"]);
|
|
108
108
|
}
|
|
109
109
|
} catch {
|
|
110
110
|
}
|
package/dist/commands/doctor.js
CHANGED
|
@@ -140,7 +140,7 @@ async function handleDoctor({ options }) {
|
|
|
140
140
|
requiredChecks
|
|
141
141
|
};
|
|
142
142
|
const orphanMetros = adapter === "mobile" ? detectOrphanMetros(target, process.env.WATCHER_PORT) : [];
|
|
143
|
-
const capture =
|
|
143
|
+
const capture = captureHealth(adapter);
|
|
144
144
|
const deviceView = adapter === "mobile" ? mobileDeviceView(allDevices) : null;
|
|
145
145
|
const liveView = deviceView ? await mobileDeviceLiveView(target, deviceView) : null;
|
|
146
146
|
const devices = liveView?.devicesWithLive ?? deviceView?.devices ?? [];
|
|
@@ -192,13 +192,7 @@ async function handleDoctor({ options }) {
|
|
|
192
192
|
);
|
|
193
193
|
console.log(` ${out("dim", "Next: mm-harness stop # reaps every bundler this checkout leaked")}`);
|
|
194
194
|
}
|
|
195
|
-
if (capture)
|
|
196
|
-
console.log(`${out("label", "capture:")} ${out(capture.status === "pass" ? "ok" : "warn", capture.status)} ${out("dim", "(capture-helper: screenshots + --record video)")}`);
|
|
197
|
-
if (capture.status !== "pass") {
|
|
198
|
-
if (capture.failing.length > 0) console.log(` ${out("dim", `failing: ${capture.failing.join(", ")}`)}`);
|
|
199
|
-
console.log(` ${out("dim", "Next: grant Screen Recording (System Settings \u2192 Privacy & Security \u2192 Screen Recording), or run: capture-helper doctor --open-permissions")}`);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
195
|
+
if (capture) renderCaptureHealth(capture, out);
|
|
202
196
|
if (adapter === "mobile") {
|
|
203
197
|
if (deviceView) renderMobileDeviceList(deviceView, out);
|
|
204
198
|
if (additionalReachableDevices.length > 0) renderAdditionalReachableDevices(additionalReachableDevices, out);
|
|
@@ -280,6 +274,25 @@ function emitExpectLive(adapter, target, runtime, result, orphanMetros, capture,
|
|
|
280
274
|
emitExpectLiveVerdict(adapter, target, runtime, "not-live");
|
|
281
275
|
return EXIT.runtime;
|
|
282
276
|
}
|
|
277
|
+
function captureHealth(adapter) {
|
|
278
|
+
if (adapter === "core") return null;
|
|
279
|
+
const fallback = adapter === "extension" ? ["cdp"] : ["simctl", "adb"];
|
|
280
|
+
const helper = captureHelperHealth();
|
|
281
|
+
if (!helper) {
|
|
282
|
+
return {
|
|
283
|
+
status: "fallback",
|
|
284
|
+
failing: [],
|
|
285
|
+
screenshots: { available: true, providers: fallback },
|
|
286
|
+
recording: { available: false, provider: null }
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
return {
|
|
290
|
+
status: helper.status,
|
|
291
|
+
failing: helper.failing,
|
|
292
|
+
screenshots: { available: true, providers: ["capture-helper", ...fallback] },
|
|
293
|
+
recording: { available: helper.status === "pass", provider: helper.status === "pass" ? "capture-helper" : null }
|
|
294
|
+
};
|
|
295
|
+
}
|
|
283
296
|
function captureHelperHealth() {
|
|
284
297
|
if (process.platform !== "darwin") return null;
|
|
285
298
|
const bin = process.env.CAPTURE_HELPER_PATH || "capture-helper";
|
|
@@ -292,6 +305,18 @@ function captureHelperHealth() {
|
|
|
292
305
|
return null;
|
|
293
306
|
}
|
|
294
307
|
}
|
|
308
|
+
function renderCaptureHealth(capture, out) {
|
|
309
|
+
const video = capture.recording.available ? "capture-helper" : "unavailable";
|
|
310
|
+
console.log(
|
|
311
|
+
`${out("label", "capture:")} ${out(capture.status === "pass" ? "ok" : "warn", capture.status)} ${out("dim", `(screenshots: ${capture.screenshots.providers.join(" \u2192 ")}; video: ${video})`)}`
|
|
312
|
+
);
|
|
313
|
+
if (capture.status === "warn") {
|
|
314
|
+
if (capture.failing.length > 0) console.log(` ${out("dim", `failing: ${capture.failing.join(", ")}`)}`);
|
|
315
|
+
console.log(` ${out("dim", "Next: grant Screen Recording (System Settings \u2192 Privacy & Security \u2192 Screen Recording), or run: capture-helper doctor --open-permissions")}`);
|
|
316
|
+
} else if (capture.status === "fallback") {
|
|
317
|
+
console.log(` ${out("dim", "Next: screenshots work without capture-helper; install/configure capture-helper only when recipe video is required")}`);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
295
320
|
function detectOrphanMetros(target, livePort) {
|
|
296
321
|
try {
|
|
297
322
|
const script = path.join(runnerDir, "adapters/shared/reap-checkout-metros.sh");
|
|
@@ -22,8 +22,7 @@ import { listRecipeFiles } from "../recipe-files.js";
|
|
|
22
22
|
import { startRecipeRecording, stopRecipeRecording } from "../run-recording.js";
|
|
23
23
|
import {
|
|
24
24
|
beginRunDiagnostics,
|
|
25
|
-
finishRunDiagnostics
|
|
26
|
-
stopRunDiagnostics
|
|
25
|
+
finishRunDiagnostics
|
|
27
26
|
} from "../run-diagnostics.js";
|
|
28
27
|
import { EXIT } from "./shared.js";
|
|
29
28
|
import {
|
|
@@ -90,7 +89,6 @@ async function runRecipe(adapter, recipe, artifactsDir, projectRoot, actionManif
|
|
|
90
89
|
await stopRecipeRecording(recording, result);
|
|
91
90
|
return finishRunDiagnostics(diagnosticBaseline, result);
|
|
92
91
|
} finally {
|
|
93
|
-
stopRunDiagnostics(diagnosticBaseline);
|
|
94
92
|
await stopRecipeRecording(recording);
|
|
95
93
|
}
|
|
96
94
|
} finally {
|
|
@@ -645,7 +643,7 @@ async function executeWithHealBounds(exec, adapter, target, heal, state, recover
|
|
|
645
643
|
}
|
|
646
644
|
}
|
|
647
645
|
function emitHealViolation(json, command, result, violation, state, adapter) {
|
|
648
|
-
const userAction = adapter === "core" && violation.code === "WALLET_STATE_REQUIRED" ? '
|
|
646
|
+
const userAction = adapter === "core" && violation.code === "WALLET_STATE_REQUIRED" ? 'create temp/recipe/runtime/wallet-fixture.json, or add "account": "<0x\u2026>" to the node block in the recipe' : violation.userAction ?? `inspect ${shellQuoteArg(result.summaryPath)} and ${shellQuoteArg(result.tracePath)}; fix the application or recipe failure before retrying`;
|
|
649
647
|
if (json) {
|
|
650
648
|
console.log(
|
|
651
649
|
JSON.stringify(
|