@deeeed/metamask-harness 0.36.0 → 0.38.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 +51 -0
- package/adapters/extension/artifact-runtime-state.cjs +138 -0
- package/adapters/extension/inject.mjs +2 -0
- package/adapters/extension/launch-browser.cjs +24 -0
- package/adapters/extension/live.sh +82 -12
- package/adapters/extension/readiness.mjs +77 -36
- package/adapters/extension/snapshot-dist.sh +88 -3
- package/adapters/extension/verify.sh +6 -2
- package/adapters/manifest.json +17 -1
- package/adapters/shared/log-tui.mjs +1 -1
- package/dist/adapters/extension/artifact-integrity.js +38 -0
- package/dist/adapters/extension/extension-id.js +23 -4
- package/dist/adapters/extension/release-artifact.js +386 -0
- package/dist/adapters/extension/runtime-decision.js +164 -20
- package/dist/adapters/extension/runtime.js +169 -3
- package/dist/adapters/mobile/provision.js +38 -3
- package/dist/adapters/mobile/release-artifact-state.js +124 -0
- package/dist/adapters/mobile/release-artifact.js +295 -0
- package/dist/adapters.js +78 -6
- package/dist/app-lifecycle.js +4 -3
- package/dist/command-contract.js +16 -0
- package/dist/commands/call.js +6 -2
- package/dist/commands/launch/extension.js +74 -9
- package/dist/commands/launch/index.js +3 -0
- package/dist/commands/launch/mobile.js +2 -0
- package/dist/commands/provision.js +2 -0
- package/dist/commands/run-engine.js +109 -14
- package/dist/commands/run.js +4 -2
- package/dist/commands/runtime-launch.js +178 -10
- package/dist/heal-bounds.js +1 -1
- package/dist/live-adapter-contract.js +3 -1
- package/dist/metamask-action-validation.js +47 -1
- package/dist/mm-harness-cli.js +31 -1
- package/dist/recipe-security.js +40 -0
- package/dist/run-diagnostics.js +1 -1
- package/dist/runner.js +3 -0
- package/docs/RELEASE-QA-CAPABILITY-MAP.md +125 -0
- package/library/actions/extension/assets/finish_send.mjs +128 -0
- package/library/actions/extension/assets/import_custom_token.mjs +227 -0
- package/library/actions/extension/assets/prepare_send.mjs +53 -0
- package/library/actions/extension/assets/read_details.mjs +57 -0
- package/library/actions/extension/assets/read_visible_state.mjs +50 -0
- package/library/actions/extension/assets/review_send.mjs +23 -0
- package/library/actions/extension/assets/select_network_scope.mjs +30 -0
- package/library/actions/extension/assets/send.mjs +104 -0
- package/library/actions/extension/assets/set_custom_gas.mjs +56 -0
- package/library/actions/extension/assets/set_token_visibility.mjs +129 -0
- package/library/actions/extension/assets/verify_sorting.mjs +89 -0
- package/library/actions/extension/networks/add_chainlist.mjs +94 -0
- package/library/actions/extension/networks/add_custom.mjs +123 -0
- package/library/actions/extension/networks/custom_network.mjs +103 -0
- package/library/actions/extension/networks/read_visible_state.mjs +50 -0
- package/library/actions/extension/networks/remove_custom.mjs +68 -0
- package/library/actions/extension/perps/close_orders.mjs +1 -0
- package/library/actions/extension/perps/close_visible_position.mjs +133 -0
- package/library/actions/extension/perps/compare_provider_market.mjs +109 -0
- package/library/actions/extension/perps/edit_margin.mjs +82 -0
- package/library/actions/extension/perps/open_balance_action.mjs +58 -0
- package/library/actions/extension/perps/open_position_action.mjs +80 -0
- package/library/actions/extension/perps/perps.mjs +196 -2
- package/library/actions/extension/perps/place_order.mjs +5 -2
- package/library/actions/extension/perps/read_eligibility.mjs +34 -0
- package/library/actions/extension/perps/read_funds_confirmation.mjs +69 -0
- package/library/actions/extension/perps/read_snapshot.mjs +470 -0
- package/library/actions/extension/perps/read_visible_state.mjs +98 -0
- package/library/actions/extension/perps/select_activity_filter.mjs +44 -0
- package/library/actions/extension/perps/select_market_filter.mjs +71 -0
- package/library/actions/extension/perps/set_market_favorite.mjs +47 -0
- package/library/actions/extension/perps/update_position_tpsl.mjs +106 -0
- package/library/actions/extension/platform/cdp.mjs +109 -3
- package/library/actions/extension/settings/set_basic_functionality.mjs +58 -0
- package/library/actions/extension/swap_bridge/read_visible_state.mjs +71 -0
- package/library/actions/extension/swap_bridge/select_assets.mjs +103 -0
- package/library/actions/extension/swap_bridge/set_amount.mjs +36 -0
- package/library/actions/extension/swap_bridge/set_max_amount.mjs +58 -0
- package/library/actions/extension/swap_bridge/set_slippage.mjs +121 -0
- package/library/actions/extension/swap_bridge/submit_transaction.mjs +363 -0
- package/library/actions/extension/ui/activate_and_observe.mjs +96 -0
- package/library/actions/extension/ui/navigate.mjs +264 -8
- package/library/actions/extension/wallet/ensure_unlocked.mjs +2 -6
- package/library/actions/extension/wallet/import.mjs +13 -46
- package/library/actions/extension/wallet/lock.mjs +35 -0
- package/library/actions/extension/wallet/secret-input.mjs +98 -0
- package/library/actions/extension/wallet/select_account.mjs +71 -7
- package/library/actions/mobile/assets/read_visible_state.mjs +37 -0
- package/library/actions/mobile/perps/read_visible_state.mjs +31 -0
- package/library/actions/mobile/platform/native-session-name.mjs +5 -0
- package/library/actions/mobile/platform/observe-ui.mjs +86 -2
- package/library/actions/mobile/swap_bridge/native-session.mjs +45 -0
- package/library/actions/mobile/swap_bridge/read_visible_state.mjs +31 -0
- package/library/actions/mobile/swap_bridge/select_assets.mjs +94 -0
- package/library/actions/mobile/swap_bridge/set_amount.mjs +73 -0
- package/library/actions/mobile/ui/native-navigation.mjs +265 -0
- package/library/actions/mobile/ui/navigate.mjs +10 -1
- package/library/actions/mobile/wallet/ensure_unlocked.mjs +17 -0
- package/library/actions/mobile/wallet/import.mjs +71 -2
- package/library/actions/mobile/wallet/native-ui.mjs +512 -0
- package/library/actions/mobile/wallet/read_state.mjs +16 -0
- package/library/actions/mobile/wallet/reset.mjs +17 -4
- package/library/actions/shared/assets/visible-state.mjs +176 -0
- package/library/actions/shared/networks/visible-state.mjs +70 -0
- package/library/actions/shared/perps/visible-state.mjs +354 -0
- package/library/actions/shared/swap-bridge/transaction.mjs +54 -0
- package/library/actions/shared/swap-bridge/visible-state.mjs +97 -0
- package/library/actions/shared/ui/locators.mjs +7 -0
- package/library/manifests/extension.action-manifest.json +1366 -184
- package/library/manifests/mobile.action-manifest.json +235 -1
- package/library/recipes/assets/release-custom-gas-send.recipe.json +88 -0
- package/library/recipes/assets/release-custom-network-token-persistence.recipe.json +106 -0
- package/library/recipes/assets/release-native-max-cancel.recipe.json +70 -0
- package/library/recipes/assets/release-native-max-send.recipe.json +78 -0
- package/library/recipes/assets/release-own-account-send.recipe.json +80 -0
- package/library/recipes/assets/release-token-address-import.recipe.json +47 -0
- package/library/recipes/assets/release-token-details.recipe.json +48 -0
- package/library/recipes/assets/release-token-hide-readd.recipe.json +55 -0
- package/library/recipes/assets/release-token-list.recipe.json +75 -0
- package/library/recipes/assets/release-token-persistence.recipe.json +73 -0
- package/library/recipes/assets/release-token-search-import.recipe.json +40 -0
- package/library/recipes/assets/release-token-sorting.recipe.json +29 -0
- package/library/recipes/extension/networks/release-balance-stability.recipe.json +51 -0
- package/library/recipes/extension/perps/release-live-limit-order.recipe.json +104 -0
- package/library/recipes/extension/perps/release-order-entry.recipe.json +155 -0
- package/library/recipes/extension/perps/release-order-validation.recipe.json +220 -0
- package/library/recipes/extension/runner/action-validation.recipe.json +12 -1
- package/library/recipes/perps/release-activity-filters.recipe.json +51 -0
- package/library/recipes/perps/release-funds-flows.recipe.json +76 -0
- package/library/recipes/perps/release-live-market-order.recipe.json +122 -0
- package/library/recipes/perps/release-market-details.recipe.json +83 -0
- package/library/recipes/perps/release-market-search.recipe.json +79 -0
- package/library/recipes/perps/release-watchlist.recipe.json +99 -0
- package/library/recipes/swap-bridge/release-custom-slippage.recipe.json +90 -0
- package/library/recipes/swap-bridge/release-live-max-swap.recipe.json +99 -0
- package/library/recipes/swap-bridge/release-live-swap.recipe.json +101 -0
- package/library/recipes/swap-bridge/release-max-quote.recipe.json +72 -0
- package/library/recipes/swap-bridge/release-quote.recipe.json +72 -0
- package/library/recipes/swap-bridge/release-surface.recipe.json +43 -0
- package/library/recipes/wallet/import.recipe.json +20 -1
- package/library/recipes/wallet/reset-import.recipe.json +20 -1
- package/package.json +6 -5
- package/scripts/validate-release-report.mjs +319 -0
package/dist/adapters.js
CHANGED
|
@@ -12,10 +12,16 @@ import { resolveMobileToolPath } from "../library/actions/mobile/platform/tool-p
|
|
|
12
12
|
import { resolveWalletImportCredentials, validateWalletImportOptions } from "../library/actions/shared/wallet/import-source.mjs";
|
|
13
13
|
const execFileAsync = promisify(execFile);
|
|
14
14
|
const NATIVE_PROVIDER_UI_ACTIONS = /* @__PURE__ */ new Set([
|
|
15
|
+
"ui.press",
|
|
16
|
+
"ui.key_press",
|
|
17
|
+
"ui.set_input",
|
|
18
|
+
"ui.scroll",
|
|
15
19
|
"ui.swipe",
|
|
16
20
|
"ui.pan",
|
|
17
21
|
"ui.drag",
|
|
18
22
|
"ui.long_press",
|
|
23
|
+
"ui.wait_for",
|
|
24
|
+
"ui.screenshot",
|
|
19
25
|
"ui.capture_surface"
|
|
20
26
|
]);
|
|
21
27
|
function sleep(ms) {
|
|
@@ -29,7 +35,42 @@ function simpleAdapter(action, executor) {
|
|
|
29
35
|
}
|
|
30
36
|
};
|
|
31
37
|
}
|
|
32
|
-
const
|
|
38
|
+
const LIVE_ONLY_ACTIONS = /* @__PURE__ */ new Set([
|
|
39
|
+
"metamask.assets.read_visible_state",
|
|
40
|
+
"metamask.assets.read_details",
|
|
41
|
+
"metamask.assets.verify_sorting",
|
|
42
|
+
"metamask.assets.set_token_visibility",
|
|
43
|
+
"metamask.assets.import_custom_token",
|
|
44
|
+
"metamask.assets.prepare_send",
|
|
45
|
+
"metamask.assets.review_send",
|
|
46
|
+
"metamask.assets.finish_send",
|
|
47
|
+
"metamask.assets.set_custom_gas",
|
|
48
|
+
"metamask.assets.select_network_scope",
|
|
49
|
+
"metamask.wallet.lock",
|
|
50
|
+
"metamask.networks.read_visible_state",
|
|
51
|
+
"metamask.networks.add_custom",
|
|
52
|
+
"metamask.networks.add_chainlist",
|
|
53
|
+
"metamask.networks.remove_custom",
|
|
54
|
+
"metamask.perps.read_visible_state",
|
|
55
|
+
"metamask.perps.compare_provider_market",
|
|
56
|
+
"metamask.perps.read_eligibility",
|
|
57
|
+
"metamask.perps.select_market_filter",
|
|
58
|
+
"metamask.perps.close_visible_position",
|
|
59
|
+
"metamask.perps.set_market_favorite",
|
|
60
|
+
"metamask.perps.select_activity_filter",
|
|
61
|
+
"metamask.perps.open_balance_action",
|
|
62
|
+
"metamask.perps.open_position_action",
|
|
63
|
+
"metamask.perps.edit_margin",
|
|
64
|
+
"metamask.perps.update_position_tpsl",
|
|
65
|
+
"metamask.perps.read_funds_confirmation",
|
|
66
|
+
"metamask.swap_bridge.read_visible_state",
|
|
67
|
+
"metamask.swap_bridge.select_assets",
|
|
68
|
+
"metamask.swap_bridge.set_amount",
|
|
69
|
+
"metamask.swap_bridge.set_max_amount",
|
|
70
|
+
"metamask.swap_bridge.set_slippage",
|
|
71
|
+
"metamask.swap_bridge.submit_transaction",
|
|
72
|
+
"metamask.settings.set_basic_functionality",
|
|
73
|
+
"metamask.perps.read_snapshot",
|
|
33
74
|
"metamask.perps.read_positions",
|
|
34
75
|
"metamask.perps.ensure_positions",
|
|
35
76
|
"metamask.perps.assert_positions",
|
|
@@ -47,8 +88,7 @@ const LIVE_ONLY_PERPS_ACTIONS = /* @__PURE__ */ new Set([
|
|
|
47
88
|
"metamask.perps.capture_performance"
|
|
48
89
|
]);
|
|
49
90
|
const CORE_ONLY_PERPS_ACTIONS = /* @__PURE__ */ new Set([
|
|
50
|
-
"metamask.perps.read_account"
|
|
51
|
-
"metamask.perps.read_snapshot"
|
|
91
|
+
"metamask.perps.read_account"
|
|
52
92
|
]);
|
|
53
93
|
const LIVE_ONLY_WALLET_ACTIONS = /* @__PURE__ */ new Set([
|
|
54
94
|
"metamask.wallet.setup",
|
|
@@ -61,7 +101,7 @@ const LIVE_ONLY_WALLET_ACTIONS = /* @__PURE__ */ new Set([
|
|
|
61
101
|
]);
|
|
62
102
|
const LIVE_ONLY_APP_ACTIONS = /* @__PURE__ */ new Set(["ui.navigate"]);
|
|
63
103
|
function requiresLiveAdapter(platform, action) {
|
|
64
|
-
return
|
|
104
|
+
return LIVE_ONLY_ACTIONS.has(action) || platform === "core" && CORE_ONLY_PERPS_ACTIONS.has(action) || LIVE_ONLY_WALLET_ACTIONS.has(action) || LIVE_ONLY_APP_ACTIONS.has(action);
|
|
65
105
|
}
|
|
66
106
|
function liveRuntimeConfigured(platform, node) {
|
|
67
107
|
if (platform === "mobile" || platform === "core") return true;
|
|
@@ -209,6 +249,36 @@ function createMetaMaskSemanticAdapters(platform, declaredCustomActions = [], pr
|
|
|
209
249
|
];
|
|
210
250
|
const bundledActions = [
|
|
211
251
|
...walletActions,
|
|
252
|
+
"metamask.assets.read_visible_state",
|
|
253
|
+
...platform === "extension" ? ["metamask.assets.read_details"] : [],
|
|
254
|
+
...platform === "extension" ? ["metamask.assets.verify_sorting"] : [],
|
|
255
|
+
...platform === "extension" ? ["metamask.assets.set_token_visibility"] : [],
|
|
256
|
+
...platform === "extension" ? ["metamask.assets.import_custom_token"] : [],
|
|
257
|
+
...platform === "extension" ? [
|
|
258
|
+
"metamask.assets.prepare_send",
|
|
259
|
+
"metamask.assets.review_send",
|
|
260
|
+
"metamask.assets.finish_send",
|
|
261
|
+
"metamask.assets.set_custom_gas",
|
|
262
|
+
"metamask.assets.select_network_scope"
|
|
263
|
+
] : [],
|
|
264
|
+
...platform === "extension" ? ["metamask.wallet.lock"] : [],
|
|
265
|
+
...platform === "extension" ? ["metamask.networks.read_visible_state"] : [],
|
|
266
|
+
...platform === "extension" ? ["metamask.networks.add_custom", "metamask.networks.add_chainlist", "metamask.networks.remove_custom"] : [],
|
|
267
|
+
"metamask.perps.read_visible_state",
|
|
268
|
+
...platform === "extension" ? ["metamask.perps.compare_provider_market"] : [],
|
|
269
|
+
...platform === "extension" ? ["metamask.perps.read_eligibility"] : [],
|
|
270
|
+
...platform === "extension" ? [
|
|
271
|
+
"metamask.perps.open_position_action",
|
|
272
|
+
"metamask.perps.edit_margin",
|
|
273
|
+
"metamask.perps.update_position_tpsl"
|
|
274
|
+
] : [],
|
|
275
|
+
"metamask.swap_bridge.read_visible_state",
|
|
276
|
+
"metamask.swap_bridge.select_assets",
|
|
277
|
+
"metamask.swap_bridge.set_amount",
|
|
278
|
+
...platform === "extension" ? ["metamask.swap_bridge.set_max_amount"] : [],
|
|
279
|
+
...platform === "extension" ? ["metamask.swap_bridge.set_slippage"] : [],
|
|
280
|
+
...platform === "extension" ? ["metamask.swap_bridge.submit_transaction"] : [],
|
|
281
|
+
...platform === "extension" ? ["metamask.settings.set_basic_functionality"] : [],
|
|
212
282
|
"metamask.perps.read_positions",
|
|
213
283
|
"metamask.perps.ensure_positions",
|
|
214
284
|
"metamask.perps.assert_positions",
|
|
@@ -221,13 +291,14 @@ function createMetaMaskSemanticAdapters(platform, declaredCustomActions = [], pr
|
|
|
221
291
|
"metamask.perps.start_state",
|
|
222
292
|
"metamask.perps.teardown_state",
|
|
223
293
|
"metamask.perps.capture_performance",
|
|
294
|
+
...platform === "extension" || platform === "core" ? ["metamask.perps.read_snapshot"] : [],
|
|
224
295
|
...platform === "mobile" ? [
|
|
225
296
|
"metamask.perps.clear_performance_caches",
|
|
226
297
|
"metamask.perps.measure_homepage_visible",
|
|
227
298
|
"metamask.perps.prepare_local_snapshot_endpoint"
|
|
228
299
|
] : [],
|
|
229
300
|
// read_account is core-only: only the headless core adapter implements it.
|
|
230
|
-
...platform === "core" ? ["metamask.perps.read_account"
|
|
301
|
+
...platform === "core" ? ["metamask.perps.read_account"] : []
|
|
231
302
|
];
|
|
232
303
|
const bundled = new Set(bundledActions);
|
|
233
304
|
const actions = [.../* @__PURE__ */ new Set([...bundledActions, ...declaredCustomActions])];
|
|
@@ -662,6 +733,7 @@ async function handleMobileHud(payload, context) {
|
|
|
662
733
|
}
|
|
663
734
|
}
|
|
664
735
|
async function hideMobileHudOnTeardown(projectRoot, env = {}) {
|
|
736
|
+
if (env.METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME === "1" || process.env.METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME === "1") return;
|
|
665
737
|
if (process.env.METAMASK_RECIPE_AUTO_HUD === "0" || process.env.METAMASK_RECIPE_AUTO_HUD === "false") return;
|
|
666
738
|
const input = {
|
|
667
739
|
node: { bridge_timeout_ms: 8e3, cdp_timeout_ms: 5e3 },
|
|
@@ -803,7 +875,7 @@ function createMetaMaskUiTransport(platform, harness, preparedLiveAdapters) {
|
|
|
803
875
|
if (live) return live.output;
|
|
804
876
|
throw new Error(`ui.navigate requires library/actions/${platform}/ui/navigate.mjs.`);
|
|
805
877
|
}
|
|
806
|
-
if (platform === "mobile" && NATIVE_PROVIDER_UI_ACTIONS.has(action)) {
|
|
878
|
+
if (platform === "mobile" && NATIVE_PROVIDER_UI_ACTIONS.has(action) && (context.env?.METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME === "1" || process.env.METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME === "1" || ["ui.swipe", "ui.pan", "ui.drag", "ui.long_press", "ui.capture_surface"].includes(action))) {
|
|
807
879
|
return executeNativeProviderAction(action, node, context, harness.createNativeUiTransport);
|
|
808
880
|
}
|
|
809
881
|
return base.execute(action, action === "ui.wait_for" ? normalizeUiWaitNode(node) : node, context);
|
package/dist/app-lifecycle.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
function resolveMetaMaskMobileLifecycleTarget(node, rawContext) {
|
|
2
2
|
const context = rawContext;
|
|
3
3
|
const platform = resolveMobilePlatform(node, context);
|
|
4
|
+
const opaqueRuntime = context.env.METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME === "1" || process.env.METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME === "1";
|
|
4
5
|
const port = scalar(node.watcher_port ?? node.metro_port, "app.lifecycle.metro_port") ?? context.env.WATCHER_PORT ?? context.env.METRO_PORT ?? process.env.WATCHER_PORT ?? process.env.METRO_PORT ?? "8081";
|
|
5
6
|
const launchUrl = scalar(node.launch_url ?? node.launchUrl ?? node.url, "app.lifecycle.launch_url") ?? expoDevClientUrl(port, platform);
|
|
6
7
|
if (platform === "android") {
|
|
@@ -8,9 +9,9 @@ function resolveMetaMaskMobileLifecycleTarget(node, rawContext) {
|
|
|
8
9
|
platform,
|
|
9
10
|
deviceId: scalar(node.adb_serial ?? node.android_device ?? node.device, "app.lifecycle.device") ?? context.env.ADB_SERIAL ?? context.env.ANDROID_SERIAL ?? process.env.ADB_SERIAL ?? process.env.ANDROID_SERIAL,
|
|
10
11
|
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",
|
|
11
|
-
launchUrl,
|
|
12
|
-
metroPort: port,
|
|
13
|
-
prelaunchCalls: expoBundlePrewarmCalls(port, "android")
|
|
12
|
+
launchUrl: opaqueRuntime ? void 0 : launchUrl,
|
|
13
|
+
metroPort: opaqueRuntime ? void 0 : port,
|
|
14
|
+
prelaunchCalls: opaqueRuntime ? [] : expoBundlePrewarmCalls(port, "android")
|
|
14
15
|
};
|
|
15
16
|
}
|
|
16
17
|
return {
|
package/dist/command-contract.js
CHANGED
|
@@ -239,6 +239,22 @@ const PUBLIC_COMMAND_CONTRACTS = {
|
|
|
239
239
|
}),
|
|
240
240
|
positionals: [{ label: "platform", choices: ["ios", "android"] }]
|
|
241
241
|
},
|
|
242
|
+
"runtime-launch": {
|
|
243
|
+
options: options(HELP, JSON, TARGET, ADAPTER, DEVICE, MOBILE_PLATFORM, {
|
|
244
|
+
"--cdp-port": value(),
|
|
245
|
+
"--chrome-user-data-dir": value(),
|
|
246
|
+
"--artifacts-dir": value(),
|
|
247
|
+
"--start-watch": bool(),
|
|
248
|
+
"--build-lavamoat": bool(),
|
|
249
|
+
"--remote-flag": value(),
|
|
250
|
+
"--artifact-file": value(),
|
|
251
|
+
"--artifact-version": value(),
|
|
252
|
+
"--artifact-build-id": value(),
|
|
253
|
+
"--artifact-package-id": value(),
|
|
254
|
+
"--artifact-sha256": value(),
|
|
255
|
+
"--artifact-cache": value()
|
|
256
|
+
})
|
|
257
|
+
},
|
|
242
258
|
logs: {
|
|
243
259
|
options: options(HELP, JSON, TARGET, ADAPTER, ADAPTER_OR_MOBILE_PLATFORM, {
|
|
244
260
|
"--full": bool(),
|
package/dist/commands/call.js
CHANGED
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
synthesizeOneNodeRecipe,
|
|
41
41
|
validateRecipeAdapterAware
|
|
42
42
|
} from "./run-engine.js";
|
|
43
|
+
import { readMobileReleaseArtifactState } from "../adapters/mobile/release-artifact-state.js";
|
|
43
44
|
import { acquireCheckoutLock } from "../checkout-lock.js";
|
|
44
45
|
import { formatRunDiagnosticsForHuman, readRunDiagnosticsDocument } from "../run-diagnostics.js";
|
|
45
46
|
import { recipeTrustFailure } from "../recipe-security.js";
|
|
@@ -267,7 +268,7 @@ async function handleCall(argv) {
|
|
|
267
268
|
}
|
|
268
269
|
try {
|
|
269
270
|
const prepared = await prepareHeal(adapter, target, options, json, {
|
|
270
|
-
skipMobileSourceFreshness: adapter === "mobile" && resolvedAction === "app.lifecycle"
|
|
271
|
+
skipMobileSourceFreshness: adapter === "mobile" && (resolvedAction === "app.lifecycle" || readMobileReleaseArtifactState(target) !== null)
|
|
271
272
|
});
|
|
272
273
|
if (typeof prepared === "number") return prepared;
|
|
273
274
|
const { state, heal } = prepared;
|
|
@@ -289,7 +290,10 @@ async function handleCall(argv) {
|
|
|
289
290
|
target,
|
|
290
291
|
heal,
|
|
291
292
|
state,
|
|
292
|
-
() => recoverRunInfra(adapter, target, json
|
|
293
|
+
() => recoverRunInfra(adapter, target, json, {
|
|
294
|
+
cdpPort: optionString(options, "cdpPort"),
|
|
295
|
+
watcherPort: optionString(options, "watcherPort") ?? optionString(options, "metroPort")
|
|
296
|
+
})
|
|
293
297
|
);
|
|
294
298
|
if (violation !== null) {
|
|
295
299
|
const conciseFailure = violation.originalError ? conciseFailureForHuman(violation.originalError) : "";
|
|
@@ -21,7 +21,11 @@ import {
|
|
|
21
21
|
extensionProductConfigBlock,
|
|
22
22
|
extensionProductConfigFingerprint
|
|
23
23
|
} from "../../adapters/extension/product-config.js";
|
|
24
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
isExtensionDistStale,
|
|
26
|
+
releaseArtifactState,
|
|
27
|
+
runtimeDistCheck
|
|
28
|
+
} from "../../adapters/extension/runtime-decision.js";
|
|
25
29
|
import { checkExtensionRuntimeHealth } from "../../adapters/extension/runtime.js";
|
|
26
30
|
import { ensureHarnessFresh } from "../../adapters/harness-freshness.js";
|
|
27
31
|
import { isExtensionWatcherLive, stopExtensionWatcher } from "../../adapters/slot-ports.js";
|
|
@@ -52,10 +56,28 @@ async function installExtensionDeps(target) {
|
|
|
52
56
|
return result;
|
|
53
57
|
}
|
|
54
58
|
async function launchExtension(target, tier, wantWatch, displayMode = "fullscreen") {
|
|
59
|
+
const requestedCdpPort = process.env.CDP_PORT;
|
|
60
|
+
const requestedRecipeCdpPort = process.env.RECIPE_CDP_PORT;
|
|
61
|
+
const restoreRequestedPorts = () => {
|
|
62
|
+
if (requestedCdpPort) process.env.CDP_PORT = requestedCdpPort;
|
|
63
|
+
if (requestedRecipeCdpPort) process.env.RECIPE_CDP_PORT = requestedRecipeCdpPort;
|
|
64
|
+
};
|
|
55
65
|
if (process.env.CHROME_USER_DATA_DIR) {
|
|
56
66
|
process.env.CHROME_USER_DATA_DIR = path.resolve(process.env.CHROME_USER_DATA_DIR);
|
|
57
67
|
}
|
|
58
|
-
const
|
|
68
|
+
const artifact = releaseArtifactState(target);
|
|
69
|
+
const artifactSnapshot = runtimeDistCheck(target, artifact);
|
|
70
|
+
const artifactLaunchable = !wantWatch && tier !== "build" && artifact.status === "valid" && artifactSnapshot.status === "fresh";
|
|
71
|
+
const reusable = !wantWatch && tier !== "build" && await extensionRuntimeReusable(target, artifact);
|
|
72
|
+
restoreRequestedPorts();
|
|
73
|
+
if (artifact.status !== "none" && !reusable && !artifactLaunchable) {
|
|
74
|
+
const detail = artifact.status === "invalid" ? `identity is invalid: ${artifact.reason ?? "unknown reason"}` : "runtime is not healthy and reusable";
|
|
75
|
+
return {
|
|
76
|
+
status: EXIT.infra,
|
|
77
|
+
output: `RELEASE_ARTIFACT_NOT_REUSABLE: The loaded release artifact ${detail}.
|
|
78
|
+
Next: rerun runtime-launch with the same artifact source.`
|
|
79
|
+
};
|
|
80
|
+
}
|
|
59
81
|
if (!reusable) {
|
|
60
82
|
const block = extensionProductConfigBlock(target);
|
|
61
83
|
if (block) {
|
|
@@ -67,6 +89,7 @@ Next: ${block.userAction}`
|
|
|
67
89
|
}
|
|
68
90
|
}
|
|
69
91
|
await ensureHarnessFresh(target, "extension");
|
|
92
|
+
restoreRequestedPorts();
|
|
70
93
|
if (wantWatch) {
|
|
71
94
|
const startWatchSh = path.join(runnerDir, "adapters/extension/start-watch.sh");
|
|
72
95
|
const watchArgs = ["--target", target];
|
|
@@ -78,20 +101,35 @@ Next: ${block.userAction}`
|
|
|
78
101
|
return extensionRebuild(target);
|
|
79
102
|
}
|
|
80
103
|
if (reusable) {
|
|
104
|
+
if (artifact.status === "valid") {
|
|
105
|
+
console.error(colorHumanMessage(
|
|
106
|
+
`\u2192 extension release reuse \u2014 verified artifact ${artifact.manifestVersion ?? artifact.expectedVersion ?? ""} \xB7 CDP :${process.env.CDP_PORT ?? "default"} (no snapshot mutation)`
|
|
107
|
+
));
|
|
108
|
+
return { status: EXIT.ok, output: "Verified release artifact runtime is already healthy." };
|
|
109
|
+
}
|
|
81
110
|
return extensionReattach(target, displayMode);
|
|
82
111
|
}
|
|
112
|
+
if (artifactLaunchable) {
|
|
113
|
+
return extensionLaunchReleaseArtifact(target, artifact);
|
|
114
|
+
}
|
|
83
115
|
return extensionLaunchDevelopment(target);
|
|
84
116
|
}
|
|
85
|
-
async function extensionRuntimeReusable(target) {
|
|
117
|
+
async function extensionRuntimeReusable(target, artifact = releaseArtifactState(target)) {
|
|
86
118
|
const cdpPort = process.env.CDP_PORT;
|
|
87
119
|
if (!cdpPort) return false;
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (
|
|
120
|
+
const runtimeSnapshot = runtimeDistCheck(target, artifact);
|
|
121
|
+
const releaseArtifact = runtimeSnapshot.source === "release-artifact";
|
|
122
|
+
if (releaseArtifact) {
|
|
123
|
+
if (runtimeSnapshot.status !== "fresh") return false;
|
|
124
|
+
} else {
|
|
125
|
+
if (isExtensionDistStale(target)) return false;
|
|
126
|
+
if (isExtensionWatcherLive(target) && !harnessWatcherMatchesProductConfig(target)) return false;
|
|
127
|
+
if (!extensionCompiledScriptsMatchProductConfig(target, expectedRuntimeDist(target))) return false;
|
|
128
|
+
}
|
|
91
129
|
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
92
130
|
const reachable = await cdpVersionReachable(cdpPort);
|
|
93
131
|
const nonceOwned = reachable && await cdpOwnedByRuntimeNonce(cdpPort, target);
|
|
94
|
-
const owned = nonceOwned || reachable && cdpOwnedByExpectedRuntime(cdpPort, target);
|
|
132
|
+
const owned = nonceOwned || reachable && cdpOwnedByExpectedRuntime(cdpPort, target, releaseArtifact ? artifact.chromeUserDataDir : void 0);
|
|
95
133
|
const targetPresent = owned && await cdpHasExpectedExtensionTarget(cdpPort, target);
|
|
96
134
|
const healthy = targetPresent && await cdpRuntimeHealthy(cdpPort, target);
|
|
97
135
|
if (healthy) return true;
|
|
@@ -118,9 +156,9 @@ function commandHasExactFlagValue(command, flag, value) {
|
|
|
118
156
|
}
|
|
119
157
|
return false;
|
|
120
158
|
}
|
|
121
|
-
function cdpOwnedByExpectedRuntime(port, target) {
|
|
159
|
+
function cdpOwnedByExpectedRuntime(port, target, profileOverride) {
|
|
122
160
|
const pids = cdpListenerPids(port);
|
|
123
|
-
const profile = expectedChromeProfile(target);
|
|
161
|
+
const profile = profileOverride ?? expectedChromeProfile(target);
|
|
124
162
|
const runtimeDist = expectedRuntimeDist(target);
|
|
125
163
|
for (const pid of pids) {
|
|
126
164
|
try {
|
|
@@ -344,6 +382,33 @@ async function extensionLaunchDevelopment(target) {
|
|
|
344
382
|
));
|
|
345
383
|
return spawnScriptStreaming(liveScript, liveArgs, target);
|
|
346
384
|
}
|
|
385
|
+
async function extensionLaunchReleaseArtifact(target, artifact) {
|
|
386
|
+
if (!artifact.sourceDir || !artifact.provenancePath || !artifact.chromeUserDataDir) {
|
|
387
|
+
return {
|
|
388
|
+
status: EXIT.infra,
|
|
389
|
+
output: "RELEASE_ARTIFACT_NOT_REUSABLE: The loaded release artifact identity is incomplete.\nNext: rerun runtime-launch with the same artifact source."
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
const liveScript = recipeHarnessPath(target, "extension", "scripts", "live.sh");
|
|
393
|
+
const liveArgs = [
|
|
394
|
+
"--target",
|
|
395
|
+
target,
|
|
396
|
+
"--external-artifact",
|
|
397
|
+
"--dist-dir",
|
|
398
|
+
artifact.sourceDir,
|
|
399
|
+
"--artifact-provenance",
|
|
400
|
+
artifact.provenancePath,
|
|
401
|
+
"--preserve-profile",
|
|
402
|
+
"--chrome-user-data-dir",
|
|
403
|
+
artifact.chromeUserDataDir
|
|
404
|
+
];
|
|
405
|
+
if (process.env.CDP_PORT) liveArgs.push("--cdp-port", process.env.CDP_PORT);
|
|
406
|
+
if (process.env.EXTENSION_START_URL) liveArgs.push("--start-url", process.env.EXTENSION_START_URL);
|
|
407
|
+
console.error(colorHumanMessage(
|
|
408
|
+
`\u2192 extension release relaunch \u2014 verified artifact ${artifact.manifestVersion ?? artifact.expectedVersion ?? ""} \xB7 CDP :${process.env.CDP_PORT ?? "default"}`
|
|
409
|
+
));
|
|
410
|
+
return spawnScriptStreaming(liveScript, liveArgs, target);
|
|
411
|
+
}
|
|
347
412
|
function harnessWatcherMatchesProductConfig(target) {
|
|
348
413
|
const runtimeDir = path.join(target, recipeRuntimeDir());
|
|
349
414
|
const harnessPidFile = path.join(runtimeDir, "recipe-harness-webpack.pid");
|
|
@@ -562,6 +562,9 @@ async function finishLaunch(jsonOutput, machine, stream, adapter, mobileTarget,
|
|
|
562
562
|
stream.phase("verify");
|
|
563
563
|
const verifyArgs = ["verify", "--adapter", adapter, "--target", target];
|
|
564
564
|
if (adapter === "mobile" && mobileTarget) verifyArgs.push("--platform", mobileTarget);
|
|
565
|
+
if (adapter === "extension" && process.env.CDP_PORT) {
|
|
566
|
+
verifyArgs.push("--cdp-port", process.env.CDP_PORT);
|
|
567
|
+
}
|
|
565
568
|
if (machine) verifyArgs.push("--json");
|
|
566
569
|
const exitCode = await handleHarness(verifyArgs);
|
|
567
570
|
if (stream.enabled) {
|
|
@@ -4,8 +4,10 @@ import {
|
|
|
4
4
|
recordMobileSourceBaseline
|
|
5
5
|
} from "../../adapters/mobile/source-freshness.js";
|
|
6
6
|
import { ensureHarnessFresh } from "../../adapters/harness-freshness.js";
|
|
7
|
+
import { clearMobileReleaseArtifactState } from "../../adapters/mobile/release-artifact-state.js";
|
|
7
8
|
import { EXIT } from "../shared.js";
|
|
8
9
|
async function launchMobile(target, mobileTarget, tier, json, restartApp = false, clearMetro = false) {
|
|
10
|
+
clearMobileReleaseArtifactState(target);
|
|
9
11
|
await ensureHarnessFresh(target, "mobile");
|
|
10
12
|
const platform = mobileTarget ?? "ios";
|
|
11
13
|
const watcherPort = process.env.WATCHER_PORT ? parseInt(process.env.WATCHER_PORT, 10) : void 0;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { detectAdapter } from "../harness.js";
|
|
2
2
|
import { assertAdapter } from "../paths.js";
|
|
3
3
|
import { getAdapterSurface } from "../adapters/surface.js";
|
|
4
|
+
import { clearMobileReleaseArtifactState } from "../adapters/mobile/release-artifact-state.js";
|
|
4
5
|
import { ADAPTER_DETECT_NEXT, usageOut, writeInteractiveProgress } from "./shared.js";
|
|
5
6
|
import {
|
|
6
7
|
applyWatcherPortOption,
|
|
@@ -28,6 +29,7 @@ async function handleProvision({ positional, options, rawArgv }) {
|
|
|
28
29
|
const platform = optionString(options, "platform") ?? optionString(options, "devicePlatform") ?? (positional[0] === "runway" ? positional[1] : positional[0]) ?? "ios";
|
|
29
30
|
if (adapter === "mobile") {
|
|
30
31
|
writeInteractiveProgress(json, `\u2192 provision mobile ${platform} \u2014 resolving simulator and Runway artifact`);
|
|
32
|
+
if (!optionFlag(options, "resolveOnly")) clearMobileReleaseArtifactState(target);
|
|
31
33
|
}
|
|
32
34
|
const result = await surface.runwayProvision.run(target, {
|
|
33
35
|
json,
|
|
@@ -13,6 +13,9 @@ import {
|
|
|
13
13
|
import { resolveActionManifest, validateManifest } from "../manifest.js";
|
|
14
14
|
import { getAdapterSurface } from "../adapters/surface.js";
|
|
15
15
|
import { extensionProductConfigBlock } from "../adapters/extension/product-config.js";
|
|
16
|
+
import {
|
|
17
|
+
requireCurrentMobileReleaseArtifactState
|
|
18
|
+
} from "../adapters/mobile/release-artifact-state.js";
|
|
16
19
|
import {
|
|
17
20
|
importRecipeHarness,
|
|
18
21
|
importRecipeProtocol,
|
|
@@ -137,10 +140,11 @@ async function resolveRecipeExecution(adapter, recipe, artifactsDir, projectRoot
|
|
|
137
140
|
const { createMetaMaskRunner } = await import("../runner.js");
|
|
138
141
|
const recipeDocument = runtimeRecipeDocument(recipe, absoluteRecipePath);
|
|
139
142
|
const suppressHudForLifecycleTiming = adapter === "mobile" && mobileRecipeRequiresUninterruptedLifecycleTiming(recipeDocument);
|
|
143
|
+
const suppressHudForOpaqueMobile = adapter === "mobile" && process.env.METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME === "1";
|
|
140
144
|
const runner = await createMetaMaskRunner(adapter, manifest, {
|
|
141
145
|
quietStdout: runtimeOptions.stdoutIsMachineContract === true,
|
|
142
146
|
suppressLibraryResolutionLogs: runtimeOptions.suppressLibraryResolutionLogs,
|
|
143
|
-
autoHud: suppressHudForLifecycleTiming ? false : trust.source && trust.source.trust !== "trusted" ? false : runtimeOptions.autoHud,
|
|
147
|
+
autoHud: suppressHudForLifecycleTiming || suppressHudForOpaqueMobile ? false : trust.source && trust.source.trust !== "trusted" ? false : runtimeOptions.autoHud,
|
|
144
148
|
onActionEvent: runtimeOptions.onActionEvent,
|
|
145
149
|
actionSources,
|
|
146
150
|
// A direct engineer invocation is the explicit trust boundary for a
|
|
@@ -216,6 +220,10 @@ function activateRecipeRuntimeEnvironment(adapter, projectRoot, runtimeOptions)
|
|
|
216
220
|
const previousWatcherPort = process.env.WATCHER_PORT;
|
|
217
221
|
const previousMetroPort = process.env.METRO_PORT;
|
|
218
222
|
const previousExtensionAutolaunch = process.env.METAMASK_RECIPE_EXTENSION_AUTOLAUNCH;
|
|
223
|
+
const previousMobileOpaqueRuntime = process.env.METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME;
|
|
224
|
+
const previousAndroidPackageId = process.env.ANDROID_PACKAGE_ID;
|
|
225
|
+
const previousAdbSerial = process.env.ADB_SERIAL;
|
|
226
|
+
const previousAndroidSerial = process.env.ANDROID_SERIAL;
|
|
219
227
|
getAdapterSurface(adapter).resolveSlotPorts(projectRoot);
|
|
220
228
|
if (runtimeOptions.cdpPort) {
|
|
221
229
|
process.env.CDP_PORT = runtimeOptions.cdpPort;
|
|
@@ -228,6 +236,21 @@ function activateRecipeRuntimeEnvironment(adapter, projectRoot, runtimeOptions)
|
|
|
228
236
|
if (runtimeOptions.launchExistingDist) {
|
|
229
237
|
process.env.METAMASK_RECIPE_EXTENSION_AUTOLAUNCH = "1";
|
|
230
238
|
}
|
|
239
|
+
if (adapter === "mobile") {
|
|
240
|
+
const state = requireCurrentMobileReleaseArtifactState(projectRoot);
|
|
241
|
+
if (state) {
|
|
242
|
+
const selected = process.env.ADB_SERIAL ?? process.env.ANDROID_SERIAL;
|
|
243
|
+
if (selected && selected !== state.deviceSerial) {
|
|
244
|
+
throw new Error(
|
|
245
|
+
`The Mobile release artifact is installed on ${state.deviceSerial}, not ${selected}. Next: rerun runtime-launch for ${selected}.`
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
process.env.METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME = "1";
|
|
249
|
+
process.env.ANDROID_PACKAGE_ID = state.packageId;
|
|
250
|
+
process.env.ADB_SERIAL = state.deviceSerial;
|
|
251
|
+
process.env.ANDROID_SERIAL = state.deviceSerial;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
231
254
|
return () => {
|
|
232
255
|
restoreEnv("CDP_PORT", previousCdpPort);
|
|
233
256
|
restoreEnv("RECIPE_CDP_PORT", previousRecipeCdpPort);
|
|
@@ -237,6 +260,13 @@ function activateRecipeRuntimeEnvironment(adapter, projectRoot, runtimeOptions)
|
|
|
237
260
|
"METAMASK_RECIPE_EXTENSION_AUTOLAUNCH",
|
|
238
261
|
previousExtensionAutolaunch
|
|
239
262
|
);
|
|
263
|
+
restoreEnv(
|
|
264
|
+
"METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME",
|
|
265
|
+
previousMobileOpaqueRuntime
|
|
266
|
+
);
|
|
267
|
+
restoreEnv("ANDROID_PACKAGE_ID", previousAndroidPackageId);
|
|
268
|
+
restoreEnv("ADB_SERIAL", previousAdbSerial);
|
|
269
|
+
restoreEnv("ANDROID_SERIAL", previousAndroidSerial);
|
|
240
270
|
};
|
|
241
271
|
}
|
|
242
272
|
function actionSourceMap(sources) {
|
|
@@ -267,12 +297,23 @@ function recipeRunEnv(adapter, runtimeOptions = {}) {
|
|
|
267
297
|
ANDROID_DEVICE: process.env.ANDROID_DEVICE,
|
|
268
298
|
ANDROID_TARGET_DEVICE_NAME: process.env.ANDROID_TARGET_DEVICE_NAME,
|
|
269
299
|
ADB_SERIAL: process.env.ADB_SERIAL,
|
|
270
|
-
ANDROID_SERIAL: process.env.ANDROID_SERIAL
|
|
300
|
+
ANDROID_SERIAL: process.env.ANDROID_SERIAL,
|
|
301
|
+
ANDROID_PACKAGE_ID: process.env.ANDROID_PACKAGE_ID,
|
|
302
|
+
METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME: process.env.METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME
|
|
271
303
|
};
|
|
272
304
|
}
|
|
273
305
|
async function prepareRuntimeIfNeeded(adapter, projectRoot, runtimeOptions) {
|
|
274
306
|
if (adapter !== "extension" || runtimeOptions.skipExtensionRuntimePrepare === true)
|
|
275
307
|
return;
|
|
308
|
+
const { releaseArtifactState, runtimeDistCheck } = await import("../adapters/extension/runtime-decision.js");
|
|
309
|
+
const artifact = releaseArtifactState(projectRoot);
|
|
310
|
+
const runtimeSnapshot = runtimeDistCheck(projectRoot, artifact);
|
|
311
|
+
if (artifact.status !== "none") {
|
|
312
|
+
if (artifact.status === "valid" && runtimeSnapshot.status === "fresh") return;
|
|
313
|
+
throw new Error(
|
|
314
|
+
"The loaded release artifact identity is invalid or stale. Next: rerun runtime-launch with the same artifact source."
|
|
315
|
+
);
|
|
316
|
+
}
|
|
276
317
|
const { prepareExtensionRuntime } = await import("../adapters/extension/runtime.js");
|
|
277
318
|
await prepareExtensionRuntime({
|
|
278
319
|
projectRoot,
|
|
@@ -306,7 +347,7 @@ async function validateRecipeAdapterAware(adapter, recipe, manifest, librarySour
|
|
|
306
347
|
);
|
|
307
348
|
const findings = [
|
|
308
349
|
...withManifest.findings,
|
|
309
|
-
...validateMetaMaskActionInputs(recipe)
|
|
350
|
+
...validateMetaMaskActionInputs(recipe, adapter)
|
|
310
351
|
];
|
|
311
352
|
if (withManifest.status === "valid" && isRecord(recipe) && libraryResolution) {
|
|
312
353
|
try {
|
|
@@ -336,7 +377,7 @@ async function validateRecipeAdapterAware(adapter, recipe, manifest, librarySour
|
|
|
336
377
|
manifest,
|
|
337
378
|
validationOptions
|
|
338
379
|
).findings,
|
|
339
|
-
...validateMetaMaskActionInputs(dependency.document)
|
|
380
|
+
...validateMetaMaskActionInputs(dependency.document, adapter)
|
|
340
381
|
);
|
|
341
382
|
}
|
|
342
383
|
} catch (error) {
|
|
@@ -782,6 +823,7 @@ async function prepareHeal(adapter, target, options, json, prepareOptions = {})
|
|
|
782
823
|
return EXIT.usage;
|
|
783
824
|
}
|
|
784
825
|
const state = newHealState();
|
|
826
|
+
const mobileArtifact = adapter === "mobile" ? requireCurrentMobileReleaseArtifactState(target) : null;
|
|
785
827
|
prepareOptions.onPhase?.("install");
|
|
786
828
|
const ensured = await ensureOverlay(
|
|
787
829
|
adapter,
|
|
@@ -847,7 +889,7 @@ async function prepareHeal(adapter, target, options, json, prepareOptions = {})
|
|
|
847
889
|
restoreEnv("WATCHER_PORT", previousWatcherPort);
|
|
848
890
|
}
|
|
849
891
|
}
|
|
850
|
-
if (adapter === "mobile" && prepareOptions.skipMobileSourceFreshness !== true) {
|
|
892
|
+
if (adapter === "mobile" && mobileArtifact === null && prepareOptions.skipMobileSourceFreshness !== true) {
|
|
851
893
|
const current = await ensureMobileProofRuntime(
|
|
852
894
|
target,
|
|
853
895
|
heal,
|
|
@@ -994,6 +1036,22 @@ async function ensureExtensionProofRuntime(target, heal, state, json, deps = {})
|
|
|
994
1036
|
});
|
|
995
1037
|
const initial = await decide();
|
|
996
1038
|
if (initial.decision === "ready") return null;
|
|
1039
|
+
if (initial.reasonCode === "release-artifact-identity-invalid" || initial.reasonCode === "release-artifact-runtime-missing" || initial.reasonCode === "release-artifact-runtime-stale") {
|
|
1040
|
+
const message = initial.reasons[0] ?? "The loaded release artifact identity is invalid.";
|
|
1041
|
+
const userAction2 = "rerun runtime-launch with the same artifact source";
|
|
1042
|
+
if (json) {
|
|
1043
|
+
console.log(JSON.stringify({
|
|
1044
|
+
schemaVersion: 1,
|
|
1045
|
+
status: "fail",
|
|
1046
|
+
recoverable: false,
|
|
1047
|
+
error: { code: "RELEASE_ARTIFACT_NOT_CURRENT", message, userAction: userAction2 }
|
|
1048
|
+
}, null, 2));
|
|
1049
|
+
} else {
|
|
1050
|
+
console.error(`\u2717 ${message}`);
|
|
1051
|
+
console.error(` Next: ${userAction2}`);
|
|
1052
|
+
}
|
|
1053
|
+
return EXIT.infra;
|
|
1054
|
+
}
|
|
997
1055
|
if (initial.reasonCode === "runtime-dist-check-failed") {
|
|
998
1056
|
const message = `Extension proof runtime freshness could not be checked: ${initial.reasons[0] ?? initial.decision}`;
|
|
999
1057
|
const userAction2 = `mm-harness doctor --adapter extension --target ${JSON.stringify(path.resolve(target))}`;
|
|
@@ -1139,18 +1197,54 @@ const RUN_RECOVERY_CODE = {
|
|
|
1139
1197
|
extension: "extension.runtime_recovered",
|
|
1140
1198
|
core: "run.retried"
|
|
1141
1199
|
};
|
|
1142
|
-
async function recoverRunInfra(adapter, target, json) {
|
|
1200
|
+
async function recoverRunInfra(adapter, target, json, runtimeOptions = {}) {
|
|
1143
1201
|
getAdapterSurface(adapter).resolveSlotPorts(target);
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1202
|
+
const previousCdpPort = process.env.CDP_PORT;
|
|
1203
|
+
const previousRecipeCdpPort = process.env.RECIPE_CDP_PORT;
|
|
1204
|
+
if (runtimeOptions.cdpPort) {
|
|
1205
|
+
process.env.CDP_PORT = runtimeOptions.cdpPort;
|
|
1206
|
+
process.env.RECIPE_CDP_PORT = runtimeOptions.cdpPort;
|
|
1147
1207
|
}
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1208
|
+
try {
|
|
1209
|
+
if (adapter === "extension") {
|
|
1210
|
+
const { releaseArtifactState } = await import("../adapters/extension/runtime-decision.js");
|
|
1211
|
+
const artifact = releaseArtifactState(target);
|
|
1212
|
+
if (artifact.status !== "none" && artifact.status !== "valid") {
|
|
1213
|
+
return {
|
|
1214
|
+
status: 1,
|
|
1215
|
+
output: "Release artifact recovery cannot trust the recorded runtime identity.\nNext: rerun runtime-launch with the same artifact source."
|
|
1216
|
+
};
|
|
1217
|
+
}
|
|
1218
|
+
const { launchExtension } = await import("./launch/extension.js");
|
|
1219
|
+
return launchExtension(target, "quick", false);
|
|
1220
|
+
}
|
|
1221
|
+
if (adapter === "mobile") {
|
|
1222
|
+
try {
|
|
1223
|
+
const artifact = requireCurrentMobileReleaseArtifactState(target);
|
|
1224
|
+
if (artifact) {
|
|
1225
|
+
const { relaunchAndroidReleaseArtifactRuntime } = await import("../adapters/mobile/release-artifact.js");
|
|
1226
|
+
await relaunchAndroidReleaseArtifactRuntime(artifact);
|
|
1227
|
+
return {
|
|
1228
|
+
status: 0,
|
|
1229
|
+
output: `Relaunched ${artifact.packageId} ${artifact.versionName} (${artifact.versionCode}) on ${artifact.deviceSerial}.`
|
|
1230
|
+
};
|
|
1231
|
+
}
|
|
1232
|
+
} catch (error) {
|
|
1233
|
+
return {
|
|
1234
|
+
status: 1,
|
|
1235
|
+
output: `Release artifact recovery could not revalidate and relaunch the pinned Android package: ${error instanceof Error ? error.message : String(error)}
|
|
1236
|
+
Next: rerun runtime-launch with the same artifact source.`
|
|
1237
|
+
};
|
|
1238
|
+
}
|
|
1239
|
+
const { launchMobile } = await import("./launch/mobile.js");
|
|
1240
|
+
const platform = resolveMobileRecoveryPlatform();
|
|
1241
|
+
return launchMobile(target, platform, "quick", json);
|
|
1242
|
+
}
|
|
1243
|
+
return { status: 0, output: "headless run retry" };
|
|
1244
|
+
} finally {
|
|
1245
|
+
restoreEnv("CDP_PORT", previousCdpPort);
|
|
1246
|
+
restoreEnv("RECIPE_CDP_PORT", previousRecipeCdpPort);
|
|
1152
1247
|
}
|
|
1153
|
-
return { status: 0, output: "headless run retry" };
|
|
1154
1248
|
}
|
|
1155
1249
|
function resolveMobileRecoveryPlatform(env = process.env) {
|
|
1156
1250
|
if (env.PLATFORM === "android" || env.PLATFORM === "ios") return env.PLATFORM;
|
|
@@ -1251,6 +1345,7 @@ export {
|
|
|
1251
1345
|
mobileRecipeRequiresUninterruptedLifecycleTiming,
|
|
1252
1346
|
preflightRecipe,
|
|
1253
1347
|
prepareHeal,
|
|
1348
|
+
prepareRuntimeIfNeeded,
|
|
1254
1349
|
recoverRunInfra,
|
|
1255
1350
|
resolveMetaMaskLibrarySources,
|
|
1256
1351
|
resolveMobileRecoveryPlatform,
|
package/dist/commands/run.js
CHANGED
|
@@ -29,6 +29,7 @@ import { handleDescribeRecipe, handleListExecutables } from "./list-executables.
|
|
|
29
29
|
import { applyDeviceTargeting } from "./device-target.js";
|
|
30
30
|
import { getAdapterSurface } from "../adapters/surface.js";
|
|
31
31
|
import { coreDependencyBlock } from "./core-readiness.js";
|
|
32
|
+
import { readMobileReleaseArtifactState } from "../adapters/mobile/release-artifact-state.js";
|
|
32
33
|
import { writeRunReport } from "./run-report.js";
|
|
33
34
|
import { acquireCheckoutLock } from "../checkout-lock.js";
|
|
34
35
|
import { JsonStreamWriter } from "../json-stream.js";
|
|
@@ -250,7 +251,8 @@ async function handleRunInner({ positional, options }, stream) {
|
|
|
250
251
|
}
|
|
251
252
|
try {
|
|
252
253
|
const prepared = await prepareHeal(adapter, target, options, machine, {
|
|
253
|
-
onPhase: (phase, fields) => stream.phase(phase, fields)
|
|
254
|
+
onPhase: (phase, fields) => stream.phase(phase, fields),
|
|
255
|
+
skipMobileSourceFreshness: adapter === "mobile" && readMobileReleaseArtifactState(target) !== null
|
|
254
256
|
});
|
|
255
257
|
if (typeof prepared === "number") {
|
|
256
258
|
stream.error({
|
|
@@ -282,7 +284,7 @@ async function handleRunInner({ positional, options }, stream) {
|
|
|
282
284
|
target,
|
|
283
285
|
heal,
|
|
284
286
|
state,
|
|
285
|
-
() => recoverRunInfra(adapter, target, machine),
|
|
287
|
+
() => recoverRunInfra(adapter, target, machine, runtimeOptions),
|
|
286
288
|
(code) => stream.phase("recover", { code })
|
|
287
289
|
);
|
|
288
290
|
for (const mutation of state.mutations) stream.mutation(mutation);
|