@deeeed/metamask-harness 0.47.4 → 0.48.0
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/README.md +3 -2
- package/adapters/extension/lib/macos-focus.cjs +2 -2
- package/adapters/extension/live.sh +6 -8
- package/adapters/mobile/bridge-runtime/lib/bridge-errors.cjs +1 -0
- package/adapters/shared/ensure-runner-deps.sh +6 -0
- package/dist/adapters/extension/runtime-decision.js +18 -1
- package/dist/adapters/extension/runtime.js +5 -5
- package/dist/adapters/extension/surface.js +1 -0
- package/dist/app-lifecycle.js +3 -1
- package/dist/command-contract.js +2 -0
- package/dist/commands/call.js +1 -8
- package/dist/commands/device-target.js +12 -3
- package/dist/commands/doctor.js +7 -5
- package/dist/commands/fixtures.js +1 -1
- package/dist/commands/launch/index.js +10 -0
- package/dist/commands/manifest.js +3 -1
- package/dist/commands/mobile-device-view.js +13 -3
- package/dist/commands/parse-args.js +2 -0
- package/dist/commands/run-engine.js +29 -4
- package/dist/commands/run.js +1 -1
- package/dist/commands/runtime-launch.js +10 -1
- package/dist/commands/status.js +8 -4
- package/dist/devices.js +31 -9
- package/dist/execution-provenance.js +8 -7
- package/dist/funding-execution-context.js +43 -13
- package/dist/heal-bounds.js +3 -3
- package/dist/live-adapter-contract.js +4 -0
- package/dist/manifest.js +2 -13
- package/dist/mm-harness-cli.js +2 -1
- package/dist/runner.js +14 -3
- package/docs/RECIPES.md +133 -22
- package/docs/RELEASE-QA-CAPABILITY-MAP.md +3 -2
- package/library/actions/core/perps/_controller.mjs +10 -0
- package/library/actions/core/perps/close_orders.mjs +13 -17
- package/library/actions/extension/assets/home-token-identity.mjs +30 -0
- package/library/actions/extension/assets/import_custom_token.mjs +61 -79
- package/library/actions/extension/assets/open_details.mjs +18 -7
- package/library/actions/extension/assets/read_details.mjs +61 -29
- package/library/actions/extension/assets/read_visible_state.mjs +4 -1
- package/library/actions/extension/assets/select_network_scope.mjs +16 -9
- package/library/actions/extension/assets/set_token_visibility.mjs +84 -17
- package/library/actions/extension/assets/verify_sorting.mjs +62 -14
- package/library/actions/extension/networks/add_custom.mjs +19 -17
- package/library/actions/extension/networks/custom_network.mjs +8 -3
- package/library/actions/extension/networks/read_visible_state.mjs +1 -0
- package/library/actions/extension/performance/_navigation-memory.mjs +35 -6
- package/library/actions/extension/performance/compare_idle_navigation_memory.mjs +3 -2
- package/library/actions/extension/performance/measure_detached_dom.mjs +3 -2
- package/library/actions/extension/performance/measure_navigation_memory.mjs +3 -2
- package/library/actions/extension/perps/assert_visible_consistency.mjs +9 -4
- package/library/actions/extension/perps/close_visible_position.mjs +3 -3
- package/library/actions/extension/perps/edit_margin.mjs +1 -1
- package/library/actions/extension/perps/open_balance_action.mjs +16 -7
- package/library/actions/extension/perps/perps.mjs +150 -45
- package/library/actions/extension/perps/read_funds_confirmation.mjs +8 -0
- package/library/actions/extension/perps/read_snapshot.mjs +2 -1
- package/library/actions/extension/perps/read_visible_state.mjs +10 -6
- package/library/actions/extension/perps/search_markets.mjs +7 -2
- package/library/actions/extension/perps/select_activity_filter.mjs +1 -1
- package/library/actions/extension/perps/select_market_filter.mjs +74 -45
- package/library/actions/extension/perps/set_market_favorite.mjs +6 -3
- package/library/actions/extension/perps/state.mjs +1 -0
- package/library/actions/extension/perps/update_position_tpsl.mjs +1 -1
- package/library/actions/extension/perps/visible-mutation-identity.mjs +4 -2
- package/library/actions/extension/platform/cdp.mjs +23 -142
- package/library/actions/extension/swap_bridge/quote-state.mjs +54 -0
- package/library/actions/extension/swap_bridge/read_visible_state.mjs +4 -1
- package/library/actions/extension/swap_bridge/select_assets.mjs +11 -1
- package/library/actions/extension/swap_bridge/set_amount.mjs +10 -8
- package/library/actions/extension/swap_bridge/set_max_amount.mjs +17 -18
- package/library/actions/extension/swap_bridge/set_slippage.mjs +0 -9
- package/library/actions/extension/ui/navigate.mjs +58 -14
- package/library/actions/extension/wallet/lock.mjs +2 -1
- package/library/actions/mobile/assets/import_custom_token.mjs +1 -0
- package/library/actions/mobile/assets/read_visible_state.mjs +3 -1
- package/library/actions/mobile/assets/set_token_visibility.mjs +13 -9
- package/library/actions/mobile/networks/network-management.mjs +22 -15
- package/library/actions/mobile/networks/remove_custom.mjs +6 -2
- package/library/actions/mobile/perps/perps.mjs +3 -0
- package/library/actions/mobile/perps/read-visible-state-loop.mjs +1 -0
- package/library/actions/mobile/perps/set_market_favorite.mjs +15 -19
- package/library/actions/mobile/platform/native-session.mjs +26 -17
- package/library/actions/mobile/platform/observe-ui.mjs +2 -2
- package/library/actions/mobile/ui/navigate.mjs +1 -1
- package/library/actions/shared/perps/visible-state.mjs +14 -3
- package/library/manifests/core.action-manifest.json +14 -2
- package/library/manifests/extension.action-manifest.json +49 -16
- package/library/manifests/mobile.action-manifest.json +8 -7
- package/library/recipes/core/perps/smoke.recipe.json +23 -0
- package/library/recipes/core/perps/snapshot.recipe.json +26 -1
- package/library/recipes/extension/assets/release-custom-network-token-persistence.recipe.json +25 -8
- package/library/recipes/extension/performance/navigation-memory-suite.recipe.json +219 -0
- package/library/recipes/extension/performance/navigation-memory.recipe.json +61 -156
- package/library/recipes/extension/wallet/smoke.recipe.json +17 -8
- package/library/recipes/mobile/app/lifecycle.android-smoke.recipe.json +23 -4
- package/library/recipes/mobile/networks/release-custom-network-persistence.recipe.json +31 -6
- package/library/recipes/mobile/networks/release-multichain-visibility.recipe.json +5 -4
- package/library/recipes/mobile/wallet/smoke.recipe.json +17 -8
- package/library/recipes/{assets → shared/assets}/release-token-address-import.recipe.json +7 -0
- package/library/recipes/{assets → shared/assets}/release-token-details.recipe.json +2 -2
- package/library/recipes/shared/assets/release-token-hide-readd.recipe.json +91 -0
- package/library/recipes/{assets → shared/assets}/release-token-persistence.recipe.json +3 -0
- package/library/recipes/{assets → shared/assets}/release-token-search-import.recipe.json +4 -1
- package/library/recipes/{assets → shared/assets}/release-token-sorting.recipe.json +13 -2
- package/library/recipes/{swap-bridge → shared/swap-bridge}/release-quote.recipe.json +29 -3
- package/package.json +3 -3
- package/site/assets/help-recipes.json +63 -23
- package/library/recipes/assets/release-token-hide-readd.recipe.json +0 -57
- package/library/recipes/core/perps/clean-market-testnet.recipe.json +0 -44
- package/library/recipes/core/perps/order-lifecycle.recipe.json +0 -82
- package/library/recipes/core/perps/read-markets.recipe.json +0 -36
- package/library/recipes/core/perps/trading-lifecycle.recipe.json +0 -80
- package/library/recipes/extension/perps/release-activity-filters.recipe.json +0 -51
- package/library/recipes/extension/perps/release-funds-flows.recipe.json +0 -76
- package/library/recipes/extension/perps/release-live-limit-order.recipe.json +0 -109
- package/library/recipes/extension/perps/release-market-filters.recipe.json +0 -97
- package/library/recipes/extension/perps/release-order-entry.recipe.json +0 -158
- package/library/recipes/extension/perps/release-order-validation.recipe.json +0 -259
- package/library/recipes/extension/perps/release-visible-consistency.recipe.json +0 -47
- package/library/recipes/extension/perps/source-dev-snapshot-consistency.recipe.json +0 -76
- package/library/recipes/mobile/perps/chase-assert-running.recipe.json +0 -110
- package/library/recipes/mobile/perps/chase-place.recipe.json +0 -145
- package/library/recipes/mobile/perps/chase-terminate.recipe.json +0 -98
- package/library/recipes/mobile/perps/performance.recipe.json +0 -722
- package/library/recipes/mobile/perps/pro-order-setup.recipe.json +0 -111
- package/library/recipes/mobile/perps/pro-order-start-state.recipe.json +0 -53
- package/library/recipes/mobile/perps/scale-assert-orders.recipe.json +0 -126
- package/library/recipes/mobile/perps/scale-place.recipe.json +0 -186
- package/library/recipes/mobile/perps/twap-assert-active.recipe.json +0 -97
- package/library/recipes/mobile/perps/twap-place.recipe.json +0 -146
- package/library/recipes/mobile/runner/action-validation.recipe.json +0 -336
- package/library/recipes/perps/clean-market-testnet.recipe.json +0 -49
- package/library/recipes/perps/lifecycle.recipe.json +0 -144
- package/library/recipes/perps/release-live-market-order.recipe.json +0 -139
- package/library/recipes/perps/release-watchlist.recipe.json +0 -111
- package/library/recipes/swap-bridge/release-max-quote.recipe.json +0 -72
- /package/library/recipes/{assets → shared/assets}/release-token-list.recipe.json +0 -0
- /package/library/recipes/{perps → shared/perps}/release-market-details.recipe.json +0 -0
- /package/library/recipes/{perps → shared/perps}/release-market-search.recipe.json +0 -0
- /package/library/recipes/{swap-bridge → shared/swap-bridge}/release-custom-slippage.recipe.json +0 -0
- /package/library/recipes/{swap-bridge → shared/swap-bridge}/release-live-max-swap.recipe.json +0 -0
- /package/library/recipes/{swap-bridge → shared/swap-bridge}/release-live-swap.recipe.json +0 -0
- /package/library/recipes/{swap-bridge → shared/swap-bridge}/release-surface.recipe.json +0 -0
- /package/library/recipes/{wallet → shared/wallet}/import.recipe.json +0 -0
- /package/library/recipes/{wallet → shared/wallet}/reset-import.recipe.json +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.48.0 - 2026-09-07
|
|
6
|
+
|
|
7
|
+
- Consolidate parameterized recipes, move specialized Perps journeys into the team library, and separate short performance checks from full coverage suites.
|
|
8
|
+
- Correct wallet, token, quote and Perps validation; honor network-addition opt-in and preserve identity-bound cleanup and failure evidence. Bound macOS focus calls, avoid duplicate Android preparation and native-session ownership, and refuse dependency repairs through shared directory links.
|
|
9
|
+
- Add compact, searchable tutorials and version-matched recipe guidance for branching, composition, performance, and diagnosing drift.
|
|
10
|
+
|
|
5
11
|
## 0.47.4 - 2026-09-05
|
|
6
12
|
|
|
7
13
|
### Fixed
|
package/README.md
CHANGED
|
@@ -129,7 +129,7 @@ mm-harness actions --action metamask.wallet.ensure_unlocked
|
|
|
129
129
|
mm-harness call metamask.wallet.ensure_unlocked
|
|
130
130
|
|
|
131
131
|
mm-harness run --list
|
|
132
|
-
mm-harness run perps.
|
|
132
|
+
mm-harness run perps.smoke --describe
|
|
133
133
|
mm-harness run wallet.smoke --describe
|
|
134
134
|
mm-harness run wallet.import method=auto
|
|
135
135
|
|
|
@@ -145,7 +145,8 @@ absolute paths to the report, trace, executed recipe, and artifact manifest.
|
|
|
145
145
|
Use `mm-harness last --json` to resume without repeating the last operation.
|
|
146
146
|
|
|
147
147
|
For automation, `--json` emits one stable document. `run --json-stream` emits
|
|
148
|
-
line-buffered JSONL progress and a terminal event.
|
|
148
|
+
line-buffered JSONL progress and a terminal event. Its status matches the recipe's
|
|
149
|
+
`pass`, `fail`, or `unknown` verdict; `unknown` still exits nonzero.
|
|
149
150
|
|
|
150
151
|
## Team libraries
|
|
151
152
|
|
|
@@ -8,7 +8,7 @@ function captureMacFrontmostProcess() {
|
|
|
8
8
|
const value = execFileSync('osascript', [
|
|
9
9
|
'-e',
|
|
10
10
|
'tell application "System Events" to get unix id of first application process whose frontmost is true',
|
|
11
|
-
], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
11
|
+
], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], timeout: 1_000 }).trim();
|
|
12
12
|
const pid = Number.parseInt(value, 10);
|
|
13
13
|
return Number.isInteger(pid) && pid > 0 ? pid : null;
|
|
14
14
|
} catch {
|
|
@@ -22,7 +22,7 @@ function restoreMacFrontmostProcess(pid) {
|
|
|
22
22
|
execFileSync('osascript', [
|
|
23
23
|
'-e',
|
|
24
24
|
`tell application "System Events" to set frontmost of first application process whose unix id is ${pid} to true`,
|
|
25
|
-
], { stdio: 'ignore' });
|
|
25
|
+
], { stdio: 'ignore', timeout: 1_000 });
|
|
26
26
|
return true;
|
|
27
27
|
} catch {
|
|
28
28
|
return false;
|
|
@@ -45,7 +45,7 @@ START_WATCH=false
|
|
|
45
45
|
BUILD_LAVAMOAT=false
|
|
46
46
|
EXTERNAL_ARTIFACT=false
|
|
47
47
|
ARTIFACT_PROVENANCE=""
|
|
48
|
-
RESET_PROFILE=
|
|
48
|
+
RESET_PROFILE=false
|
|
49
49
|
DIST_DIR="dist/chrome"
|
|
50
50
|
CHROME_USER_DATA_DIR="${CHROME_USER_DATA_DIR:-}"
|
|
51
51
|
REMOTE_FLAGS=""
|
|
@@ -62,6 +62,7 @@ while [ "$#" -gt 0 ]; do
|
|
|
62
62
|
--external-artifact) EXTERNAL_ARTIFACT=true; LAUNCH_EXISTING_DIST=true; shift ;;
|
|
63
63
|
--artifact-provenance) [ "$#" -ge 2 ] || { echo "Missing value for $1" >&2; exit 2; }; ARTIFACT_PROVENANCE="$2"; shift 2 ;;
|
|
64
64
|
--preserve-profile) RESET_PROFILE=false; shift ;;
|
|
65
|
+
--reset-profile) RESET_PROFILE=true; shift ;;
|
|
65
66
|
--start-url) [ "$#" -ge 2 ] || { echo "Missing value for $1" >&2; exit 2; }; START_URL="$2"; shift 2 ;;
|
|
66
67
|
--start-watch|--start-test-watch) START_WATCH=true; LAUNCH_EXISTING_DIST=true; shift ;;
|
|
67
68
|
--build-lavamoat) BUILD_LAVAMOAT=true; LAUNCH_EXISTING_DIST=true; shift ;;
|
|
@@ -69,7 +70,7 @@ while [ "$#" -gt 0 ]; do
|
|
|
69
70
|
--dist-dir) [ "$#" -ge 2 ] || { echo "Missing value for $1" >&2; exit 2; }; DIST_DIR="$2"; shift 2 ;;
|
|
70
71
|
--chrome-user-data-dir) [ "$#" -ge 2 ] || { echo "Missing value for $1" >&2; exit 2; }; CHROME_USER_DATA_DIR="$2"; shift 2 ;;
|
|
71
72
|
--remote-flag) [ "$#" -ge 2 ] || { echo "Missing value for $1" >&2; exit 2; }; REMOTE_FLAGS="$2"; shift 2 ;;
|
|
72
|
-
-h|--help) echo "Usage: live.sh [--target <metamask-extension>] [--out <recipes-dir>] [--cdp-port <port>] [--launch-existing-dist|--start-watch|--build-lavamoat|--external-artifact --dist-dir <absolute-dir> --artifact-provenance <absolute-file> [--preserve-profile
|
|
73
|
+
-h|--help) echo "Usage: live.sh [--target <metamask-extension>] [--out <recipes-dir>] [--cdp-port <port>] [--launch-existing-dist|--start-watch|--build-lavamoat|--external-artifact --dist-dir <absolute-dir> --artifact-provenance <absolute-file>|--prepare-cmd <cmd>] [--preserve-profile|--reset-profile] [--launch-only] [--dist-dir dist/chrome] [--remote-flag KEY=VARIANT] [--artifacts-dir <dir>]"; exit 0 ;;
|
|
73
74
|
*) echo "Unknown arg: $1" >&2; exit 2 ;;
|
|
74
75
|
esac
|
|
75
76
|
done
|
|
@@ -86,10 +87,6 @@ if $EXTERNAL_ARTIFACT && [ ! -f "$ARTIFACT_PROVENANCE" ]; then
|
|
|
86
87
|
echo "--external-artifact requires --artifact-provenance <file>" >&2
|
|
87
88
|
exit 2
|
|
88
89
|
fi
|
|
89
|
-
if ! $RESET_PROFILE && ! $EXTERNAL_ARTIFACT; then
|
|
90
|
-
echo "--preserve-profile is only valid for a verified external artifact" >&2
|
|
91
|
-
exit 2
|
|
92
|
-
fi
|
|
93
90
|
if $EXTERNAL_ARTIFACT; then
|
|
94
91
|
case "$ARTIFACT_PROVENANCE" in
|
|
95
92
|
/*) ;;
|
|
@@ -200,11 +197,11 @@ if $LAUNCH_EXISTING_DIST && [ -z "$PREPARE_CMD" ]; then
|
|
|
200
197
|
FIXTURE_STATE_ABS="$ARTIFACTS/fixture-state.json"
|
|
201
198
|
FIXTURE_VALIDATION_ABS="$ARTIFACTS/logs/fixture-account-parity.json"
|
|
202
199
|
WALLET_FIXTURE_ABS=""
|
|
203
|
-
if ! $EXTERNAL_ARTIFACT; then
|
|
200
|
+
if $RESET_PROFILE && ! $EXTERNAL_ARTIFACT; then
|
|
204
201
|
# Wallet fixture resolution chain + provenance live in seed-fixture.sh.
|
|
205
202
|
WALLET_FIXTURE_ABS="$(bash "$SCRIPT_DIR/seed-fixture.sh" resolve --target "$TARGET" --cdp-port "$CDP_PORT" --source-out "$ARTIFACTS/logs/fixture-source.json")"
|
|
206
203
|
fi
|
|
207
|
-
if ! $EXTERNAL_ARTIFACT && [ ! -f "$ARTIFACTS/logs/fixture-source.json" ]; then
|
|
204
|
+
if $RESET_PROFILE && ! $EXTERNAL_ARTIFACT && [ ! -f "$ARTIFACTS/logs/fixture-source.json" ]; then
|
|
208
205
|
# resolve failed before writing provenance (invalid RECIPE_WALLET_FIXTURE);
|
|
209
206
|
# keep the documented missing-provenance artifact.
|
|
210
207
|
node - "$ARTIFACTS/logs/fixture-source.json" "" <<'NODE'
|
|
@@ -370,6 +367,7 @@ display_args=(mm-harness runtime-launch --adapter extension --target "$TARGET" -
|
|
|
370
367
|
$START_WATCH && display_args+=(--start-watch)
|
|
371
368
|
$BUILD_LAVAMOAT && display_args+=(--build-lavamoat)
|
|
372
369
|
$EXTERNAL_ARTIFACT && display_args+=(--external-artifact --dist-dir "$DIST_DIR" --artifact-provenance "$ARTIFACT_PROVENANCE")
|
|
370
|
+
$RESET_PROFILE && display_args+=(--reset-profile)
|
|
373
371
|
$RESET_PROFILE || display_args+=(--preserve-profile)
|
|
374
372
|
[ -n "$REMOTE_FLAGS" ] && display_args+=(--remote-flag "$REMOTE_FLAGS")
|
|
375
373
|
[ -n "$CHROME_USER_DATA_DIR" ] && display_args+=(--chrome-user-data-dir "$CHROME_USER_DATA_DIR")
|
|
@@ -64,6 +64,7 @@ const NEEDLE_CODES = [
|
|
|
64
64
|
['pinned android device', BRIDGE_ERROR_CODES.NO_TARGET],
|
|
65
65
|
['no react native bridge target', BRIDGE_ERROR_CODES.NO_TARGET],
|
|
66
66
|
['cdp broker target unavailable', BRIDGE_ERROR_CODES.NO_TARGET],
|
|
67
|
+
['cdp broker never observed the requested target', BRIDGE_ERROR_CODES.NO_TARGET],
|
|
67
68
|
['websocket closed', BRIDGE_ERROR_CODES.WS_CLOSED],
|
|
68
69
|
['websocket error', BRIDGE_ERROR_CODES.WS_CLOSED],
|
|
69
70
|
['cdp connection timeout', BRIDGE_ERROR_CODES.CDP_TIMEOUT],
|
|
@@ -60,6 +60,12 @@ if [ ! -f "$RUNNER_DIR/package.json" ]; then
|
|
|
60
60
|
exit 1
|
|
61
61
|
fi
|
|
62
62
|
|
|
63
|
+
if [ -L "$RUNNER_DIR/node_modules" ]; then
|
|
64
|
+
echo "mm-harness: cannot repair dependencies through a shared node_modules symlink." >&2
|
|
65
|
+
echo " Next: readlink '$RUNNER_DIR/node_modules' # inspect the shared target, then prepare a private dependency directory" >&2
|
|
66
|
+
exit 1
|
|
67
|
+
fi
|
|
68
|
+
|
|
63
69
|
if [ -n "$PROTOCOL_ROOT" ] && [ -f "$RUNNER_DIR/scripts/link-local-farmslot.mjs" ]; then
|
|
64
70
|
if [ -f "$PROTOCOL_ROOT/packages/recipe-harness/package.json" ]; then
|
|
65
71
|
FARMSLOT_ROOT="$(cd "$PROTOCOL_ROOT" && pwd -P)" \
|
|
@@ -356,7 +356,7 @@ async function cdpCheck(target, cdpPort, pageMode) {
|
|
|
356
356
|
try {
|
|
357
357
|
const { checkExtensionRuntimeHealth } = await import("./runtime.js");
|
|
358
358
|
const report = await checkExtensionRuntimeHealth(target, cdpPort, { pageMode });
|
|
359
|
-
return report.status === "PASS" ? { status: "pass" } : { status: "fail", findings: report.findings };
|
|
359
|
+
return report.status === "PASS" ? { status: "pass" } : { status: "fail", findings: report.findings, errorCode: report.errorCode };
|
|
360
360
|
} catch (error) {
|
|
361
361
|
return { status: "fail", findings: [error instanceof Error ? error.message : String(error)] };
|
|
362
362
|
}
|
|
@@ -441,6 +441,16 @@ async function decideExtensionReadiness(target, options = {}) {
|
|
|
441
441
|
reasons: ["Build is fresh and the extension runtime is healthy over CDP."],
|
|
442
442
|
actions: []
|
|
443
443
|
},
|
|
444
|
+
{
|
|
445
|
+
when: cdp.errorCode === "EVM_RPC_EXECUTION_FAILED",
|
|
446
|
+
decision: "blocked",
|
|
447
|
+
reasonCode: "rpc-execution-failed",
|
|
448
|
+
reasons: [
|
|
449
|
+
"Background RPC fetch failed before an HTTP response; inspect its JavaScript stack before relaunching.",
|
|
450
|
+
...cdp.findings?.slice(0, 3) ?? []
|
|
451
|
+
],
|
|
452
|
+
actions: []
|
|
453
|
+
},
|
|
444
454
|
{
|
|
445
455
|
when: cdp.status === "fail",
|
|
446
456
|
decision: "relaunch",
|
|
@@ -482,6 +492,13 @@ async function decideExtensionReadiness(target, options = {}) {
|
|
|
482
492
|
reasons: [`Release artifact ${releaseArtifact.expectedVersion} is intact and healthy over CDP.`],
|
|
483
493
|
actions: []
|
|
484
494
|
},
|
|
495
|
+
{
|
|
496
|
+
when: cdp.errorCode === "EVM_RPC_EXECUTION_FAILED",
|
|
497
|
+
decision: "blocked",
|
|
498
|
+
reasonCode: "rpc-execution-failed",
|
|
499
|
+
reasons: ["Background RPC fetch failed before an HTTP response; inspect its JavaScript stack before relaunching.", ...cdp.findings?.slice(0, 3) ?? []],
|
|
500
|
+
actions: []
|
|
501
|
+
},
|
|
485
502
|
{
|
|
486
503
|
when: cdp.status === "fail",
|
|
487
504
|
decision: "relaunch",
|
|
@@ -166,10 +166,11 @@ async function checkExtensionRuntimeHealth(projectRoot, cdpPort, options = {}) {
|
|
|
166
166
|
if (runtime.ethereumConnectionUnavailable === true) {
|
|
167
167
|
findings.push('Extension UI reports "Unable to connect to Ethereum".');
|
|
168
168
|
}
|
|
169
|
+
const rpcExecutionFailed = runtime.evmRpcProbeOk !== true && /Failed to execute 'fetch' on 'WorkerGlobalScope': Illegal invocation/u.test(String(runtime.evmRpcProbeError));
|
|
169
170
|
if (runtime.evmRpcProbeOk !== true) {
|
|
170
171
|
const probeError = String(runtime.evmRpcProbeError ?? "unknown error").replace(/[.\s]+$/u, "");
|
|
171
172
|
findings.push(
|
|
172
|
-
`EVM RPC readiness probe failed: ${probeError}. Infura configured \u2260 RPC reachable.`
|
|
173
|
+
rpcExecutionFailed ? `EVM_RPC_EXECUTION_FAILED: background RPC fetch threw before an HTTP response: ${probeError}.` : `EVM RPC readiness probe failed: ${probeError}. Infura configured \u2260 RPC reachable.`
|
|
173
174
|
);
|
|
174
175
|
}
|
|
175
176
|
if (runtime.hasSubmitRequest === true && runtime.backgroundProbeOk !== true) {
|
|
@@ -180,8 +181,8 @@ async function checkExtensionRuntimeHealth(projectRoot, cdpPort, options = {}) {
|
|
|
180
181
|
return {
|
|
181
182
|
status: findings.length === 0 ? "PASS" : "FAIL",
|
|
182
183
|
...evmRpcUnreachable ? {
|
|
183
|
-
errorCode: "EVM_RPC_UNREACHABLE",
|
|
184
|
-
userAction: evmRpcRecoveryAction(projectRoot)
|
|
184
|
+
errorCode: rpcExecutionFailed ? "EVM_RPC_EXECUTION_FAILED" : "EVM_RPC_UNREACHABLE",
|
|
185
|
+
userAction: rpcExecutionFailed ? `mm-harness logs --target ${shellQuote(projectRoot)} --full # inspect the background fetch stack; credential resync is not indicated by this error` : evmRpcRecoveryAction(projectRoot)
|
|
185
186
|
} : {},
|
|
186
187
|
warnings,
|
|
187
188
|
cdpPort,
|
|
@@ -497,8 +498,7 @@ function formatHealthFailure(report, projectRoot) {
|
|
|
497
498
|
].join("\n");
|
|
498
499
|
}
|
|
499
500
|
function compositorRecoveryAction(projectRoot) {
|
|
500
|
-
|
|
501
|
-
return process.platform === "darwin" ? `Unlock the macOS session, then rerun: ${recovery}` : `Restore an active display compositor, then rerun: ${recovery}`;
|
|
501
|
+
return `Make the MetaMask tab visible in an unlocked desktop session, then rerun: mm-harness verify --adapter extension --target ${shellQuote(projectRoot)}`;
|
|
502
502
|
}
|
|
503
503
|
function evmRpcRecoveryAction(projectRoot) {
|
|
504
504
|
return `Confirm ${shellQuote(path.join(projectRoot, ".metamaskrc"))} has a real INFURA_PROJECT_ID (not 00000000000), then rerun: mm-harness launch --verify --target ${shellQuote(projectRoot)}`;
|
|
@@ -16,6 +16,7 @@ function watcherStatus(buildLog) {
|
|
|
16
16
|
}
|
|
17
17
|
function extensionRuntimeNextAction(reasonCode, target) {
|
|
18
18
|
const resolved = shellQuote(path.resolve(target));
|
|
19
|
+
if (reasonCode === "rpc-execution-failed") return `mm-harness logs --target ${resolved} --full`;
|
|
19
20
|
return reasonCode === "deps-missing" || reasonCode === "deps-stale" ? `cd ${resolved} && yarn install --immutable` : `mm-harness launch --adapter extension --target ${resolved}`;
|
|
20
21
|
}
|
|
21
22
|
const extensionSurface = {
|
package/dist/app-lifecycle.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
2
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
3
|
import { promisify } from "node:util";
|
|
4
|
+
const MOBILE_LIFECYCLE_PREPARED = /* @__PURE__ */ Symbol("mobileLifecyclePrepared");
|
|
4
5
|
const MOBILE_LIFECYCLE_DEADLINE_FIELD = "_mobile_lifecycle_deadline_epoch_ms";
|
|
5
6
|
const lifecycleDeadline = new AsyncLocalStorage();
|
|
6
7
|
const execFileAsync = promisify(execFile);
|
|
@@ -64,7 +65,7 @@ function resolveMetaMaskMobileLifecycleTarget(node, rawContext) {
|
|
|
64
65
|
appId: scalar(node.package_id ?? node.packageName ?? node.app_id, "app.lifecycle.package_id") ?? context.env.ANDROID_PACKAGE_ID ?? process.env.ANDROID_PACKAGE_ID ?? "io.metamask",
|
|
65
66
|
launchUrl: opaqueRuntime ? void 0 : launchUrl,
|
|
66
67
|
metroPort: opaqueRuntime ? void 0 : port,
|
|
67
|
-
prelaunchCalls: opaqueRuntime ? [] : expoBundlePrewarmCalls(port, "android")
|
|
68
|
+
prelaunchCalls: opaqueRuntime || node[MOBILE_LIFECYCLE_PREPARED] ? [] : expoBundlePrewarmCalls(port, "android")
|
|
68
69
|
};
|
|
69
70
|
}
|
|
70
71
|
return {
|
|
@@ -123,6 +124,7 @@ function scalar(value, label) {
|
|
|
123
124
|
}
|
|
124
125
|
export {
|
|
125
126
|
MOBILE_LIFECYCLE_DEADLINE_FIELD,
|
|
127
|
+
MOBILE_LIFECYCLE_PREPARED,
|
|
126
128
|
bindMetaMaskMobileLifecycleDeadline,
|
|
127
129
|
createMetaMaskMobileLifecycleCommandRunner,
|
|
128
130
|
resolveMetaMaskMobileLifecycleTarget
|
package/dist/command-contract.js
CHANGED
|
@@ -258,6 +258,8 @@ const PUBLIC_COMMAND_CONTRACTS = {
|
|
|
258
258
|
options: options(HELP, JSON, TARGET, ADAPTER, DEVICE, MOBILE_PLATFORM, {
|
|
259
259
|
"--cdp-port": value(),
|
|
260
260
|
"--chrome-user-data-dir": value(),
|
|
261
|
+
"--reset-profile": bool(),
|
|
262
|
+
"--preserve-profile": bool(),
|
|
261
263
|
"--artifacts-dir": value(),
|
|
262
264
|
"--start-watch": bool(),
|
|
263
265
|
"--build-lavamoat": bool(),
|
package/dist/commands/call.js
CHANGED
|
@@ -377,14 +377,7 @@ async function handleCall(argv) {
|
|
|
377
377
|
persistRunEffects(result.summaryPath, result.artifactManifestPath, state);
|
|
378
378
|
if (violation !== null) {
|
|
379
379
|
const conciseFailure = violation.originalError ? conciseFailureForHuman(violation.originalError) : "";
|
|
380
|
-
const
|
|
381
|
-
describedAction,
|
|
382
|
-
adapter,
|
|
383
|
-
target,
|
|
384
|
-
process.env.MM_HARNESS_INVOKED_AS ?? process.env.MM_HARNESS_EXECUTABLE ?? "mm-harness",
|
|
385
|
-
args
|
|
386
|
-
) : void 0;
|
|
387
|
-
const taughtViolation = violation.code === "APP_LOGIC_FAILURE" && conciseFailure.includes(" requires ") && example ? { ...violation, userAction: example } : violation;
|
|
380
|
+
const taughtViolation = violation.code === "APP_LOGIC_FAILURE" && conciseFailure.includes(" requires ") && describedAction ? { ...violation, userAction: `mm-harness actions ${resolvedAction} --adapter ${adapter} --json` } : violation;
|
|
388
381
|
return emitHealViolation(json, "call", result, taughtViolation, state, adapter);
|
|
389
382
|
}
|
|
390
383
|
const callOutput = readCallOutput(result.tracePath);
|
|
@@ -109,8 +109,13 @@ function applyDeviceTargeting(command, adapter, options, opts) {
|
|
|
109
109
|
}
|
|
110
110
|
return { ok: true };
|
|
111
111
|
}
|
|
112
|
+
const discoveryErrors = [];
|
|
113
|
+
const discoveryFailure = () => {
|
|
114
|
+
const error = discoveryErrors.find((error2) => !platformPreference || error2.platform === platformPreference);
|
|
115
|
+
return error ? { ok: false, code: "DEVICE_DISCOVERY_FAILED", message: error.message, userAction: error.userAction } : void 0;
|
|
116
|
+
};
|
|
112
117
|
if (device !== void 0) {
|
|
113
|
-
const connected2 = listConnectedDevices();
|
|
118
|
+
const connected2 = listConnectedDevices(void 0, discoveryErrors);
|
|
114
119
|
const setResolvedDevice = (matched) => {
|
|
115
120
|
if (platformPreference && matched.platform !== platformPreference) {
|
|
116
121
|
return {
|
|
@@ -130,10 +135,12 @@ function applyDeviceTargeting(command, adapter, options, opts) {
|
|
|
130
135
|
if (androidById) {
|
|
131
136
|
return setResolvedDevice(androidById);
|
|
132
137
|
}
|
|
133
|
-
const iosById = connected2.find((d) => d.platform === "ios" && d.id === device) ?? (command === "launch" && platformPreference === "ios" ? findAvailableIosSimulatorById(device) : void 0);
|
|
138
|
+
const iosById = connected2.find((d) => d.platform === "ios" && d.id === device) ?? (command === "launch" && platformPreference === "ios" ? findAvailableIosSimulatorById(device, discoveryErrors) : void 0);
|
|
134
139
|
if (iosById) {
|
|
135
140
|
return setResolvedDevice(iosById);
|
|
136
141
|
}
|
|
142
|
+
const probeFailure2 = discoveryFailure();
|
|
143
|
+
if (probeFailure2) return probeFailure2;
|
|
137
144
|
const normalizedDevice = normalizeDeviceName(device);
|
|
138
145
|
const byName = connected2.filter((d) => {
|
|
139
146
|
const name = d.name ?? "";
|
|
@@ -161,7 +168,9 @@ ${formatConnectedDevices(connected2)}` : ` No devices are currently connected (
|
|
|
161
168
|
userAction: deviceRecovery(options, opts.rerun)
|
|
162
169
|
};
|
|
163
170
|
}
|
|
164
|
-
const connected = listConnectedDevices();
|
|
171
|
+
const connected = listConnectedDevices(void 0, discoveryErrors);
|
|
172
|
+
const probeFailure = discoveryFailure();
|
|
173
|
+
if (probeFailure) return opts.gate || command === "launch" ? probeFailure : { ok: true };
|
|
165
174
|
const targetable = connected.filter(isTargetable);
|
|
166
175
|
if (platformPreference) {
|
|
167
176
|
const selectedTargetable2 = targetable.filter(deviceSelected);
|
package/dist/commands/doctor.js
CHANGED
|
@@ -188,6 +188,7 @@ async function handleDoctor({ options }) {
|
|
|
188
188
|
const liveView = deviceView ? await mobileDeviceLiveView(target, deviceView) : null;
|
|
189
189
|
const devices = liveView?.devicesWithLive ?? deviceView?.devices ?? [];
|
|
190
190
|
const additionalReachableDevices = liveView?.additionalReachableDevices ?? [];
|
|
191
|
+
const discovery = deviceView?.deviceDiscoveryErrors.length ? { deviceDiscoveryErrors: deviceView.deviceDiscoveryErrors } : {};
|
|
191
192
|
if (expectLive) {
|
|
192
193
|
return emitExpectLive(
|
|
193
194
|
adapter,
|
|
@@ -199,15 +200,16 @@ async function handleDoctor({ options }) {
|
|
|
199
200
|
devices,
|
|
200
201
|
additionalReachableDevices,
|
|
201
202
|
json,
|
|
202
|
-
printReady
|
|
203
|
+
printReady,
|
|
204
|
+
discovery
|
|
203
205
|
);
|
|
204
206
|
}
|
|
205
207
|
const doctorUserAction = doctorResult.checks.find(
|
|
206
208
|
(check) => check.required && check.status === "fail" && check.userAction
|
|
207
209
|
)?.userAction ?? `mm-harness doctor --fix --adapter ${adapter} --target ${shellQuote(target)} --json`;
|
|
208
210
|
const doctorError = doctorResult.status === "fail" ? { code: "DOCTOR_CHECKS_FAILED", message: "one or more required doctor checks failed", userAction: doctorUserAction } : void 0;
|
|
209
|
-
const next = doctorError?.userAction ?? (runtime?.decision === "ready" ? void 0 : runtime?.nextAction);
|
|
210
|
-
if (json) console.log(JSON.stringify({ ...doctorResult, ready: runtime?.decision === "ready", runtime, orphanMetros, capture, devices, additionalReachableDevices, ...next ? { next } : {}, ...doctorError ? { error: doctorError } : {} }, null, 2));
|
|
211
|
+
const next = doctorError?.userAction ?? discovery.deviceDiscoveryErrors?.[0]?.userAction ?? (runtime?.decision === "ready" ? void 0 : runtime?.nextAction);
|
|
212
|
+
if (json) console.log(JSON.stringify({ ...doctorResult, ready: runtime?.decision === "ready", runtime, orphanMetros, capture, devices, additionalReachableDevices, ...discovery, ...next ? { next } : {}, ...doctorError ? { error: doctorError } : {} }, null, 2));
|
|
211
213
|
else {
|
|
212
214
|
const out = (style, text) => color(style, text, { stream: process.stdout });
|
|
213
215
|
const stateStyle = (value, good) => value === good ? "ok" : "warn";
|
|
@@ -298,7 +300,7 @@ function emitExpectLiveVerdict(adapter, target, runtime, verdict) {
|
|
|
298
300
|
for (const reason of runtime?.reasons ?? []) console.error(` ${out("dim", reason)}`);
|
|
299
301
|
console.error(` Next: ${runtime?.nextAction ?? getAdapterSurface(adapter).hints.runtimeProbeRecovery(target)}`);
|
|
300
302
|
}
|
|
301
|
-
function emitExpectLive(adapter, target, runtime, result, orphanMetros, capture, devices, additionalReachableDevices, json, printReady) {
|
|
303
|
+
function emitExpectLive(adapter, target, runtime, result, orphanMetros, capture, devices, additionalReachableDevices, json, printReady, discovery) {
|
|
302
304
|
const live = runtime?.decision === "ready";
|
|
303
305
|
if (printReady) {
|
|
304
306
|
if (live) {
|
|
@@ -316,7 +318,7 @@ function emitExpectLive(adapter, target, runtime, result, orphanMetros, capture,
|
|
|
316
318
|
if (json) {
|
|
317
319
|
const userAction = runtime?.nextAction ?? getAdapterSurface(adapter).hints.runtimeProbeRecovery(target);
|
|
318
320
|
const error = live ? void 0 : { code: "RUNTIME_NOT_LIVE", message: `${adapter} runtime is not live`, userAction };
|
|
319
|
-
console.log(JSON.stringify({ ...result, ready: live, runtime, orphanMetros, capture, devices, additionalReachableDevices, ...error ? { error } : {} }, null, 2));
|
|
321
|
+
console.log(JSON.stringify({ ...result, ready: live, runtime, orphanMetros, capture, devices, additionalReachableDevices, ...discovery, ...error ? { error } : {} }, null, 2));
|
|
320
322
|
return live ? EXIT.ok : EXIT.runtime;
|
|
321
323
|
}
|
|
322
324
|
const out = (style, text) => color(style, text, { stream: process.stdout });
|
|
@@ -328,7 +328,7 @@ async function applyExtensionFixture(target, fixturePath, canonicalFixture, json
|
|
|
328
328
|
process.stderr.write(`${colorHumanMessage(`\u2192 fixtures ${action} extension \u2014 resetting and seeding the slot-owned profile from the existing build\u2026`)}
|
|
329
329
|
`);
|
|
330
330
|
const liveScript = path.join(runnerDir, "adapters/extension/live.sh");
|
|
331
|
-
const args = ["--target", target, "--launch-existing-dist", "--launch-only"];
|
|
331
|
+
const args = ["--target", target, "--launch-existing-dist", "--launch-only", "--reset-profile"];
|
|
332
332
|
if (process.env.CDP_PORT) args.push("--cdp-port", process.env.CDP_PORT);
|
|
333
333
|
const result = await spawnScriptStreaming(liveScript, args, target, {
|
|
334
334
|
env: { RECIPE_WALLET_FIXTURE: fixturePath },
|
|
@@ -310,6 +310,16 @@ async function handleLaunchLocked(argv, stream) {
|
|
|
310
310
|
exitCode: EXIT.infra
|
|
311
311
|
});
|
|
312
312
|
}
|
|
313
|
+
if (adapter === "extension" && attempt.output.includes("EVM_RPC_EXECUTION_FAILED:")) {
|
|
314
|
+
return launchFail(jsonOutput, stream, adapter, mobileTarget, tier, state, target, {
|
|
315
|
+
code: "EVM_RPC_EXECUTION_FAILED",
|
|
316
|
+
message: "Background RPC fetch failed during JavaScript execution; this does not establish an Infura authentication or connectivity failure.",
|
|
317
|
+
recoverable: false,
|
|
318
|
+
userAction: `mm-harness logs --target ${shellQuote(target)} --full`,
|
|
319
|
+
exitCode: EXIT.runtime,
|
|
320
|
+
originalError: attempt.output.trim() || void 0
|
|
321
|
+
});
|
|
322
|
+
}
|
|
313
323
|
if (adapter === "extension" && extensionEvmRpcUnreachable(attempt.output)) {
|
|
314
324
|
return launchFail(jsonOutput, stream, adapter, mobileTarget, tier, state, target, {
|
|
315
325
|
code: "EVM_RPC_UNREACHABLE",
|
|
@@ -358,10 +358,11 @@ function renderHumanActionCatalog(actions, options) {
|
|
|
358
358
|
for (const entry of entries) {
|
|
359
359
|
const fieldNames = humanFieldNames(entry);
|
|
360
360
|
const fields = fieldNames.length ? ` ${out("dim", `fields=${fieldNames.join(",")}`)}` : "";
|
|
361
|
+
const cases = entry.result_cases?.length ? ` ${out("dim", `cases=${entry.result_cases.join(",")}`)}` : "";
|
|
361
362
|
const description = entry.description ? ` ${out("comment", `\u2014 ${entry.description}`)}` : "";
|
|
362
363
|
const source = ` ${out("dim", `[${entry.source}]`)}`;
|
|
363
364
|
const risk = entry.capabilities.length ? ` ${out("dim", `risk=${entry.capabilities.join(",")}`)}` : "";
|
|
364
|
-
lines.push(` ${out("cmd", options.displayName?.(entry) ?? entry.name)}${source}${fields}${risk}${description}`);
|
|
365
|
+
lines.push(` ${out("cmd", options.displayName?.(entry) ?? entry.name)}${source}${fields}${cases}${risk}${description}`);
|
|
365
366
|
}
|
|
366
367
|
}
|
|
367
368
|
return lines.join("\n");
|
|
@@ -519,6 +520,7 @@ function describeManifestAction(name, kind, metadata, source) {
|
|
|
519
520
|
fields: properties,
|
|
520
521
|
schema,
|
|
521
522
|
examples: record.examples,
|
|
523
|
+
...Array.isArray(record.result_cases) ? { result_cases: record.result_cases.filter((value) => typeof value === "string") } : {},
|
|
522
524
|
source: source?.name ?? (kind === "official" ? "official" : "metamask"),
|
|
523
525
|
sourceTier: source?.tier ?? (kind === "official" ? "official" : "canonical"),
|
|
524
526
|
sourceManifest: source?.manifestPath ?? "",
|
|
@@ -4,10 +4,12 @@ import { listConnectedDevices } from "../devices.js";
|
|
|
4
4
|
import { renderDeviceList, scopedDevices } from "./device-target.js";
|
|
5
5
|
import { probeMobileLiveState } from "./status-probe.js";
|
|
6
6
|
function mobileDeviceView(allDevices) {
|
|
7
|
-
const
|
|
7
|
+
const deviceDiscoveryErrors = [];
|
|
8
|
+
const allConnectedDevices = listConnectedDevices(void 0, deviceDiscoveryErrors);
|
|
8
9
|
return {
|
|
9
10
|
allConnectedDevices,
|
|
10
|
-
devices: scopedDevices(allConnectedDevices, allDevices)
|
|
11
|
+
devices: scopedDevices(allConnectedDevices, allDevices),
|
|
12
|
+
deviceDiscoveryErrors
|
|
11
13
|
};
|
|
12
14
|
}
|
|
13
15
|
async function mobileDeviceLiveView(target, view) {
|
|
@@ -39,7 +41,15 @@ function detectAdditionalReachableDevices(scoped, allConnected, liveMap) {
|
|
|
39
41
|
}).filter((device) => device !== null);
|
|
40
42
|
}
|
|
41
43
|
function renderMobileDeviceList(view, out) {
|
|
42
|
-
|
|
44
|
+
if (view.devices.length === 0 && view.deviceDiscoveryErrors.length > 0) {
|
|
45
|
+
console.log(`${out("label", "devices:")} ${out("warn", "discovery incomplete")}`);
|
|
46
|
+
} else {
|
|
47
|
+
renderDeviceList(view.devices, out);
|
|
48
|
+
}
|
|
49
|
+
for (const error of view.deviceDiscoveryErrors) {
|
|
50
|
+
console.log(`${out("warn", error.message)}
|
|
51
|
+
Next: ${error.userAction}`);
|
|
52
|
+
}
|
|
43
53
|
}
|
|
44
54
|
function renderAdditionalReachableDevices(devices, out) {
|
|
45
55
|
console.log(`${out("warn", "additional reachable target:")} ${devices.length} device(s) answered on this Metro but are not the active target for this command`);
|
|
@@ -582,6 +582,11 @@ async function validateRecipeAdapterAware(adapter, recipe, manifest, librarySour
|
|
|
582
582
|
);
|
|
583
583
|
const findings = [
|
|
584
584
|
...withManifest.findings,
|
|
585
|
+
...withManifest.status === "valid" && params ? protocol.validateRecipeWithManifest(
|
|
586
|
+
resolveMetaMaskParameterValue(recipe, params),
|
|
587
|
+
manifest,
|
|
588
|
+
validationOptions
|
|
589
|
+
).findings : [],
|
|
585
590
|
...validateMetaMaskActionInputs(recipe, adapter, params)
|
|
586
591
|
];
|
|
587
592
|
if (withManifest.status === "valid" && isRecord(recipe) && libraryResolution) {
|
|
@@ -626,6 +631,11 @@ async function validateRecipeAdapterAware(adapter, recipe, manifest, librarySour
|
|
|
626
631
|
supplied,
|
|
627
632
|
isRecord(dependency.document) ? dependency.document.paramsSchema : void 0
|
|
628
633
|
);
|
|
634
|
+
findings.push(...protocol.validateRecipeWithManifest(
|
|
635
|
+
resolveMetaMaskParameterValue(dependency.document, childParams),
|
|
636
|
+
manifest,
|
|
637
|
+
validationOptions
|
|
638
|
+
).findings);
|
|
629
639
|
findings.push(...validateMetaMaskActionInputs(dependency.document, adapter, childParams));
|
|
630
640
|
validateDependencyInstances(dependency.document, childParams);
|
|
631
641
|
}
|
|
@@ -690,9 +700,24 @@ async function listRunnableRecipes(adapter, librarySources) {
|
|
|
690
700
|
const protocol = await importRecipeProtocol();
|
|
691
701
|
const { manifest } = await resolveActionManifest(adapter, void 0, sources);
|
|
692
702
|
const externalRecipeIds = new Set(resolution.recipes.keys());
|
|
693
|
-
|
|
703
|
+
const validRefs = new Set([...resolution.recipes.values()].filter((recipe) => protocol.validateRecipeWithManifest(recipe.document, manifest, {
|
|
694
704
|
externalRecipeIds
|
|
695
|
-
}).status === "valid").map(
|
|
705
|
+
}).status === "valid").map((recipe) => recipe.ref));
|
|
706
|
+
return [...resolution.recipes.values()].filter((recipe) => {
|
|
707
|
+
if (!validRefs.has(recipe.ref)) return false;
|
|
708
|
+
try {
|
|
709
|
+
const dependencies = harness.resolveRecipeDependencies({
|
|
710
|
+
rootRef: recipe.ref,
|
|
711
|
+
root: recipe.document,
|
|
712
|
+
rootSource: recipe.provenance,
|
|
713
|
+
recipes: resolution.recipes
|
|
714
|
+
});
|
|
715
|
+
return [...dependencies.recipes.keys()].every((ref) => validRefs.has(ref));
|
|
716
|
+
} catch (error) {
|
|
717
|
+
if (error instanceof harness.RecipeResolutionError) return false;
|
|
718
|
+
throw error;
|
|
719
|
+
}
|
|
720
|
+
}).map(
|
|
696
721
|
(recipe) => recipeSummary(recipe.ref, adapter, recipe.document, recipe)
|
|
697
722
|
).sort((left, right) => left.name.localeCompare(right.name));
|
|
698
723
|
}
|
|
@@ -1371,14 +1396,14 @@ function readRunFailureText(result) {
|
|
|
1371
1396
|
}
|
|
1372
1397
|
async function executeWithHealBounds(exec, target, state) {
|
|
1373
1398
|
const result = await exec();
|
|
1374
|
-
if (result.status === "pass") return { result, violation: null };
|
|
1399
|
+
if (result.status === "pass" || result.status === "unknown") return { result, violation: null };
|
|
1375
1400
|
return {
|
|
1376
1401
|
result,
|
|
1377
1402
|
violation: checkHealBounds(target, readRunFailureText(result), state)
|
|
1378
1403
|
};
|
|
1379
1404
|
}
|
|
1380
1405
|
function emitHealViolation(json, command, result, violation, state, adapter) {
|
|
1381
|
-
const userAction = adapter === "core" && violation.code === "WALLET_STATE_REQUIRED" ?
|
|
1406
|
+
const userAction = adapter === "core" && violation.code === "WALLET_STATE_REQUIRED" ? "for signing, configure a reserved wallet-fixture.json account and pass its non-empty name; an explicit 0x address is supported only by read-only actions" : violation.userAction ?? `inspect ${shellQuoteArg(result.summaryPath)} and ${shellQuoteArg(result.tracePath)}; fix the application or recipe failure before retrying`;
|
|
1382
1407
|
if (json) {
|
|
1383
1408
|
console.log(
|
|
1384
1409
|
JSON.stringify(
|
package/dist/commands/run.js
CHANGED
|
@@ -396,7 +396,7 @@ async function handleRunInner({ positional, options }, stream) {
|
|
|
396
396
|
userAction: failureUserAction
|
|
397
397
|
});
|
|
398
398
|
}
|
|
399
|
-
stream.complete(result.status
|
|
399
|
+
stream.complete(result.status, exitCode, {
|
|
400
400
|
adapter,
|
|
401
401
|
reportPath: report.path,
|
|
402
402
|
summaryPath: result.summaryPath,
|
|
@@ -34,15 +34,20 @@ async function handleRuntimeLaunch({ options }) {
|
|
|
34
34
|
--device <serial> Explicit Android device serial
|
|
35
35
|
--cdp-port <port> Extension CDP port
|
|
36
36
|
--chrome-user-data-dir Isolated Chrome profile path
|
|
37
|
+
--reset-profile Explicitly clear the selected Extension profile
|
|
37
38
|
--artifacts-dir <dir> Validation evidence directory
|
|
38
39
|
--json Machine-readable terminal report
|
|
39
40
|
|
|
40
41
|
The harness verifies and runs a local artifact but never downloads one. Extension
|
|
41
|
-
launch
|
|
42
|
+
launch preserves the selected profile unless --reset-profile is requested.
|
|
43
|
+
Android install requires an explicit serial.`);
|
|
42
44
|
return 0;
|
|
43
45
|
}
|
|
44
46
|
const adapter = adapterOption(options);
|
|
45
47
|
const target = targetPath(options);
|
|
48
|
+
if (adapter !== "extension" && (optionFlag(options, "resetProfile") || optionFlag(options, "preserveProfile"))) {
|
|
49
|
+
throw usageError("Profile options apply only to Extension.\nNext: mm-harness runtime-launch --help");
|
|
50
|
+
}
|
|
46
51
|
const artifactFile = optionString(options, "artifactFile");
|
|
47
52
|
const artifactVersion = optionString(options, "artifactVersion");
|
|
48
53
|
const artifactSha256 = optionString(options, "artifactSha256");
|
|
@@ -68,6 +73,9 @@ async function handleRuntimeLaunch({ options }) {
|
|
|
68
73
|
});
|
|
69
74
|
}
|
|
70
75
|
if (adapter !== "extension") throw new Error("runtime-launch currently applies to the extension adapter.");
|
|
76
|
+
if (optionFlag(options, "resetProfile") && optionFlag(options, "preserveProfile")) {
|
|
77
|
+
throw usageError("Choose either --reset-profile or --preserve-profile, not both.\nNext: mm-harness runtime-launch --help");
|
|
78
|
+
}
|
|
71
79
|
if (artifactBuildId || artifactPackageId || optionString(options, "platform") || optionString(options, "device")) {
|
|
72
80
|
throw usageError("Extension runtime-launch does not accept Android artifact options.");
|
|
73
81
|
}
|
|
@@ -119,6 +127,7 @@ async function handleRuntimeLaunch({ options }) {
|
|
|
119
127
|
);
|
|
120
128
|
}
|
|
121
129
|
if (chromeUserDataDir) command.push("--chrome-user-data-dir", chromeUserDataDir);
|
|
130
|
+
command.push(optionFlag(options, "resetProfile") ? "--reset-profile" : "--preserve-profile");
|
|
122
131
|
const remoteFlag = optionString(options, "remoteFlag");
|
|
123
132
|
if (remoteFlag) command.push("--remote-flag", remoteFlag);
|
|
124
133
|
if (!fs.existsSync(liveScript)) {
|
package/dist/commands/status.js
CHANGED
|
@@ -37,6 +37,8 @@ async function handleStatus({ options }) {
|
|
|
37
37
|
const next = surface.hints.relaunch;
|
|
38
38
|
const deviceView = adapter === "mobile" ? mobileDeviceView(allDevices) : null;
|
|
39
39
|
const devices = deviceView?.devices ?? [];
|
|
40
|
+
const discovery = deviceView?.deviceDiscoveryErrors.length ? { deviceDiscoveryErrors: deviceView.deviceDiscoveryErrors } : {};
|
|
41
|
+
const discoveryNext = discovery.deviceDiscoveryErrors?.[0]?.userAction;
|
|
40
42
|
if (json) {
|
|
41
43
|
const doProbe = !fast && adapter === "mobile" && deviceView !== null && deviceView.allConnectedDevices.length > 0;
|
|
42
44
|
if (doProbe) {
|
|
@@ -49,9 +51,10 @@ async function handleStatus({ options }) {
|
|
|
49
51
|
adapter,
|
|
50
52
|
target,
|
|
51
53
|
devices: liveView.devicesWithLive,
|
|
54
|
+
...discovery,
|
|
52
55
|
...liveView.additionalReachableDevices.length > 0 ? { additionalReachableDevices: liveView.additionalReachableDevices } : {},
|
|
53
56
|
...liveView.androidPortHints.length > 0 ? { androidPortHints: liveView.androidPortHints } : {},
|
|
54
|
-
next: nextForLive(next, liveView.liveMap)
|
|
57
|
+
next: discoveryNext ?? nextForLive(next, liveView.liveMap)
|
|
55
58
|
},
|
|
56
59
|
null,
|
|
57
60
|
2
|
|
@@ -67,8 +70,9 @@ async function handleStatus({ options }) {
|
|
|
67
70
|
adapter,
|
|
68
71
|
target,
|
|
69
72
|
devices,
|
|
73
|
+
...discovery,
|
|
70
74
|
...runtime ? { runtime } : {},
|
|
71
|
-
next: runtime ? nextForRuntime(surface.hints.launch, surface.hints.relaunch, runtime) : next
|
|
75
|
+
next: discoveryNext ?? (runtime ? nextForRuntime(surface.hints.launch, surface.hints.relaunch, runtime) : next)
|
|
72
76
|
},
|
|
73
77
|
null,
|
|
74
78
|
2
|
|
@@ -85,13 +89,13 @@ async function handleStatus({ options }) {
|
|
|
85
89
|
if (liveView.additionalReachableDevices.length > 0) renderAdditionalReachableDevices(liveView.additionalReachableDevices, out);
|
|
86
90
|
renderMobileLiveBlock(devices, liveView.liveMap, out);
|
|
87
91
|
renderAndroidPortHints(liveView.androidPortHints, out);
|
|
88
|
-
console.log(`${out("label", "Next:")} ${out("cmd", nextForLive(next, liveView.liveMap))}`);
|
|
92
|
+
console.log(`${out("label", "Next:")} ${out("cmd", discoveryNext ?? nextForLive(next, liveView.liveMap))}`);
|
|
89
93
|
} else if (!fast && adapter === "extension") {
|
|
90
94
|
const runtime = await surface.runtimeStatus(target);
|
|
91
95
|
renderRuntimeStatus(runtime, out);
|
|
92
96
|
console.log(`${out("label", "Next:")} ${out("cmd", nextForRuntime(surface.hints.launch, surface.hints.relaunch, runtime))}`);
|
|
93
97
|
} else {
|
|
94
|
-
console.log(`${out("label", "Next:")} ${out("cmd", next)}`);
|
|
98
|
+
console.log(`${out("label", "Next:")} ${out("cmd", discoveryNext ?? next)}`);
|
|
95
99
|
}
|
|
96
100
|
return EXIT.ok;
|
|
97
101
|
}
|