@deeeed/metamask-harness 0.37.0 → 0.38.1
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 +33 -0
- package/adapters/extension/artifact-runtime-state.cjs +13 -3
- package/adapters/extension/inject.mjs +1 -0
- package/adapters/extension/launch-browser.cjs +2 -0
- package/adapters/extension/live.sh +1 -1
- package/adapters/extension/readiness.mjs +1 -1
- package/adapters/manifest.json +8 -0
- package/dist/adapters/extension/runtime-decision.js +4 -1
- package/dist/adapters/extension/runtime.js +42 -3
- package/dist/adapters/mobile/provision.js +38 -3
- package/dist/adapters.js +68 -3
- package/dist/app-lifecycle.js +4 -3
- package/dist/commands/call.js +4 -1
- package/dist/commands/launch/extension.js +21 -5
- package/dist/commands/launch/index.js +3 -0
- package/dist/commands/run-engine.js +40 -29
- package/dist/commands/run.js +1 -1
- package/dist/recipe-security.js +40 -0
- package/dist/runner.js +3 -0
- package/docs/RELEASE-QA-CAPABILITY-MAP.md +116 -141
- 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 +53 -0
- package/library/actions/extension/perps/open_position_action.mjs +80 -0
- package/library/actions/extension/perps/perps.mjs +194 -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_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 +72 -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 +104 -1
- 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/lock.mjs +35 -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 +5 -3
- 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 +45 -5
- package/library/actions/mobile/ui/navigate.mjs +3 -1
- package/library/actions/mobile/wallet/ensure_unlocked.mjs +17 -0
- package/library/actions/mobile/wallet/native-ui.mjs +21 -2
- 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/manifests/extension.action-manifest.json +1255 -189
- package/library/manifests/mobile.action-manifest.json +219 -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/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/package.json +6 -5
- package/scripts/validate-release-report.mjs +333 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,39 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.38.1 - 2026-08-14
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Read release evidence through verified regular-file descriptors, escape generated Markdown safely, and keep UI waits out of dynamically constructed page expressions.
|
|
10
|
+
|
|
11
|
+
## 0.38.0 - 2026-08-14
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- Add visible Perps release actions and recipes for market details, search, filters, watchlists, order entry, position management, funds confirmations, and activity on untouched Extension builds, plus native Android market-state evidence without a development bridge.
|
|
16
|
+
- Add shared Assets release actions and recipes for token lists, details, sorting, import, visibility, persistence, send review, custom gas, and network scope on Extension, plus native Android holdings evidence.
|
|
17
|
+
- Add shared Swap and Bridge actions and recipes for exact asset selection, quote amounts, Max, slippage, and guarded submission on Extension, with native Android selection and quote evidence.
|
|
18
|
+
- Add Extension custom-network lifecycle and visible-state actions plus bounded balance-stability validation, and native Android multi-network holdings evidence.
|
|
19
|
+
- Add a canonical release-report gate that rejects missing case IDs, incomplete evidence, non-terminal verdicts, and unfinished case inventories before writing report metadata and quality output.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- Recover Runway provisioning from an unresponsive iOS simulator by restarting the selected device once, waiting for a confirmed boot, and retrying the bounded installed-app probe.
|
|
24
|
+
- Consume Protocol 0.21.0, Recipe Harness 0.15.0, Agent Runtime 0.8.1, and Expo Recipe 0.8.1 as one compatible QA runtime set.
|
|
25
|
+
- Stop every Chrome process using an Extension validation profile before replacing its runtime snapshot, preventing stale pages from loading chunks from a different build.
|
|
26
|
+
- Prefer an unlocked Extension Home page over stale onboarding and Side Panel pages when multiple CDP targets remain open.
|
|
27
|
+
- Fixed opaque Android navigation completing the same native session twice after reaching its destination.
|
|
28
|
+
- Keep Android release actions on one device-and-app-scoped accessibility session so sequential recipe nodes do not conflict over stale leases.
|
|
29
|
+
- Navigate Android Swap through semantic accessibility identifiers, dismiss the keyboard before selecting exact chain-qualified assets, and scroll the token list until the requested row is visible.
|
|
30
|
+
- Unlock an opaque Android release with the wallet password instead of entering biometric authentication, and omit Metro launch data from native release lifecycle actions.
|
|
31
|
+
- Bind send and limit-order evidence to the newly submitted operation, reject selectors that native UI cannot disambiguate, and fail when Perps navigation or partial TP/SL reconciliation is not proven.
|
|
32
|
+
- Classify live Swap submission as an external mutation, reject ID-less release-report cases, and probe competing Extension tabs before selecting the runtime health target.
|
|
33
|
+
- Require explicit approval for live Perps submissions and reject release evidence that escapes its declared root or lacks regular trace leaves.
|
|
34
|
+
- Bind custom-token reuse to its chain and contract, reject vacuous asset sorting proof, and report capped Perps observations as truncated.
|
|
35
|
+
- Bind Swap confirmation to a newly created transaction, reconcile exact TP/SL values, assert disabled zero-withdraw submission, and require structured run evidence.
|
|
36
|
+
- Dismiss the first-run Perps tutorial through its visible Skip control before release recipes act on the Perps home screen.
|
|
37
|
+
|
|
5
38
|
## 0.37.0 - 2026-08-13
|
|
6
39
|
|
|
7
40
|
### Added
|
|
@@ -5,7 +5,7 @@ const fs = require('node:fs');
|
|
|
5
5
|
const path = require('node:path');
|
|
6
6
|
|
|
7
7
|
if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
|
8
|
-
console.log('Usage: artifact-runtime-state.cjs <set|clear> --target <absolute-dir> --runtime-dir <relative-dir> [--source-dir <absolute-dir> --runtime-dist <absolute-dir> --provenance <absolute-file>]');
|
|
8
|
+
console.log('Usage: artifact-runtime-state.cjs <set|clear> --target <absolute-dir> --runtime-dir <relative-dir> [--source-dir <absolute-dir> --runtime-dist <absolute-dir> --provenance <absolute-file> --chrome-user-data-dir <absolute-dir>]');
|
|
9
9
|
process.exit(0);
|
|
10
10
|
}
|
|
11
11
|
|
|
@@ -29,6 +29,16 @@ if (action !== 'set') throw new Error('Usage: artifact-runtime-state.cjs <set|cl
|
|
|
29
29
|
const sourceDir = requiredPath(args, 'source-dir');
|
|
30
30
|
const runtimeDist = requiredPath(args, 'runtime-dist');
|
|
31
31
|
const provenancePath = requiredPath(args, 'provenance');
|
|
32
|
+
const chromeUserDataDir = requiredPath(args, 'chrome-user-data-dir');
|
|
33
|
+
const directory = safeRuntimeDirectory(target, runtimeDir, true);
|
|
34
|
+
const runtimeRoot = path.resolve(target, runtimeDir);
|
|
35
|
+
if (!chromeUserDataDir.startsWith(`${runtimeRoot}${path.sep}`)) {
|
|
36
|
+
throw new Error('Release artifact Chrome profile must be a dedicated child of the runtime directory.');
|
|
37
|
+
}
|
|
38
|
+
const profileStat = fs.lstatSync(chromeUserDataDir, { throwIfNoEntry: false });
|
|
39
|
+
if (!profileStat?.isDirectory() || profileStat.isSymbolicLink()) {
|
|
40
|
+
throw new Error('Release artifact Chrome profile must be an existing non-symlink directory.');
|
|
41
|
+
}
|
|
32
42
|
const provenance = readRegularJson(provenancePath);
|
|
33
43
|
const relativeExtensionDir = provenance.extensionDir;
|
|
34
44
|
if (
|
|
@@ -48,17 +58,17 @@ if (!fs.existsSync(path.join(sourceDir, 'manifest.json'))) throw new Error('Rele
|
|
|
48
58
|
if (!fs.existsSync(path.join(runtimeDist, 'manifest.json'))) throw new Error('Release artifact runtime manifest is missing.');
|
|
49
59
|
|
|
50
60
|
const state = {
|
|
51
|
-
schemaVersion:
|
|
61
|
+
schemaVersion: 2,
|
|
52
62
|
sourceDir,
|
|
53
63
|
runtimeDist,
|
|
54
64
|
provenancePath,
|
|
65
|
+
chromeUserDataDir,
|
|
55
66
|
sha256: provenance.sha256,
|
|
56
67
|
treeSha256: provenance.treeSha256,
|
|
57
68
|
expectedVersion: provenance.expectedVersion,
|
|
58
69
|
manifestVersion: provenance.manifestVersion,
|
|
59
70
|
recordedAt: new Date().toISOString(),
|
|
60
71
|
};
|
|
61
|
-
const directory = safeRuntimeDirectory(target, runtimeDir, true);
|
|
62
72
|
const statePath = path.join(directory, 'extension-release-artifact.json');
|
|
63
73
|
const existing = fs.lstatSync(statePath, { throwIfNoEntry: false });
|
|
64
74
|
if (existing && (!existing.isFile() || existing.isSymbolicLink())) throw invalidRuntimeState(statePath);
|
|
@@ -132,6 +132,7 @@ copyFile(path.join(runnerDir, 'adapters/extension/lib/slot-title.cjs'), path.joi
|
|
|
132
132
|
copyFile(path.join(runnerDir, 'adapters/extension/lib/chrome-args.cjs'), path.join(harnessDir, 'scripts/lib/chrome-args.cjs'));
|
|
133
133
|
copyFile(path.join(runnerDir, 'adapters/extension/lib/macos-focus.cjs'), path.join(harnessDir, 'scripts/lib/macos-focus.cjs'));
|
|
134
134
|
copyFile(path.join(runnerDir, 'adapters/extension/lib/product-config.cjs'), path.join(harnessDir, 'scripts/lib/product-config.cjs'));
|
|
135
|
+
copyFile(path.join(runnerDir, 'adapters/extension/lib/validation-process-ownership.cjs'), path.join(harnessDir, 'scripts/lib/validation-process-ownership.cjs'));
|
|
135
136
|
makeExecutableTree(path.join(harnessDir, 'scripts'));
|
|
136
137
|
fs.writeFileSync(path.join(harnessDir, 'installed-scripts.sha256'), `${dirContentHash(path.join(harnessDir, 'scripts'))}\n`);
|
|
137
138
|
|
|
@@ -24,6 +24,7 @@ const os = require('node:os');
|
|
|
24
24
|
const path = require('node:path');
|
|
25
25
|
const { execFileSync, spawn, spawnSync } = require('node:child_process');
|
|
26
26
|
const { extensionIdFromExtensionDir } = require('./lib/extension-id.cjs');
|
|
27
|
+
const { profileProcessPids } = require('./lib/validation-process-ownership.cjs');
|
|
27
28
|
const {
|
|
28
29
|
automationRuntimeArgs,
|
|
29
30
|
clearDetachedLaunchUnproven,
|
|
@@ -91,6 +92,7 @@ if (foreignPids.length > 0) {
|
|
|
91
92
|
);
|
|
92
93
|
}
|
|
93
94
|
const ownedPids = new Set(listenerPids);
|
|
95
|
+
for (const pid of profileProcessPids(args.profile)) ownedPids.add(pid);
|
|
94
96
|
const previousPid = readPidFile(args['chrome-pid']);
|
|
95
97
|
if (previousPid !== null && processIsOwnedHarnessBrowser(previousPid, args.profile, args['extension-dir'])) ownedPids.add(previousPid);
|
|
96
98
|
try {
|
|
@@ -324,7 +324,7 @@ NODE
|
|
|
324
324
|
prepare_parts+=("bash ${quoted_snapshot_dist} --dist ${quoted_dist} --target ${quoted_target} --runtime-root ${quoted_runtime_root} --runtime-dist ${quoted_runtime_dist}")
|
|
325
325
|
fi
|
|
326
326
|
if $EXTERNAL_ARTIFACT; then
|
|
327
|
-
prepare_parts+=("node ${quoted_artifact_state} set --target ${quoted_target} --runtime-dir ${quoted_runtime_dir} --source-dir ${quoted_dist} --runtime-dist ${quoted_runtime_dist} --provenance ${quoted_artifact_provenance}")
|
|
327
|
+
prepare_parts+=("node ${quoted_artifact_state} set --target ${quoted_target} --runtime-dir ${quoted_runtime_dir} --source-dir ${quoted_dist} --runtime-dist ${quoted_runtime_dist} --provenance ${quoted_artifact_provenance} --chrome-user-data-dir ${quoted_profile}")
|
|
328
328
|
fi
|
|
329
329
|
if ! $EXTERNAL_ARTIFACT; then
|
|
330
330
|
prepare_parts+=("node ${quoted_configure_manifest} --target ${quoted_target} --manifest ${quoted_runtime_dist}/manifest.json")
|
|
@@ -75,7 +75,7 @@ function extensionBuildRoot(target) {
|
|
|
75
75
|
}
|
|
76
76
|
const state = JSON.parse(fs.readFileSync(statePath, 'utf8'));
|
|
77
77
|
const expected = path.join(runtimeRoot, process.env.RECIPE_RUNTIME_DIST_DIR || 'runtime-dist');
|
|
78
|
-
if (state.schemaVersion ===
|
|
78
|
+
if (state.schemaVersion === 2 && path.resolve(state.runtimeDist || '') === expected) return expected;
|
|
79
79
|
} catch (error) {
|
|
80
80
|
throw new Error(`Loaded release artifact identity is invalid: ${error instanceof Error ? error.message : String(error)}`);
|
|
81
81
|
}
|
package/adapters/manifest.json
CHANGED
|
@@ -234,6 +234,14 @@
|
|
|
234
234
|
"inputs": "--cdp-port --chrome-bin --profile --extension-dir --chrome-log --chrome-pid",
|
|
235
235
|
"outputs": "pid file + append-log; exit 0 / non-zero"
|
|
236
236
|
},
|
|
237
|
+
{
|
|
238
|
+
"id": "extension/validation-process-ownership",
|
|
239
|
+
"entry": "adapters/extension/lib/validation-process-ownership.cjs",
|
|
240
|
+
"kind": "lib",
|
|
241
|
+
"purpose": "Resolve browser processes owned by one isolated Extension validation profile.",
|
|
242
|
+
"inputs": "profile path",
|
|
243
|
+
"outputs": "owned process identifiers"
|
|
244
|
+
},
|
|
237
245
|
{
|
|
238
246
|
"id": "extension/refresh-build",
|
|
239
247
|
"entry": "adapters/extension/refresh-build.sh",
|
|
@@ -165,11 +165,13 @@ function releaseArtifactState(target) {
|
|
|
165
165
|
const sourceDir = typeof state.sourceDir === "string" ? path.resolve(state.sourceDir) : "";
|
|
166
166
|
const runtimeDist = typeof state.runtimeDist === "string" ? path.resolve(state.runtimeDist) : "";
|
|
167
167
|
const provenancePath = typeof state.provenancePath === "string" ? path.resolve(state.provenancePath) : "";
|
|
168
|
+
const chromeUserDataDir = typeof state.chromeUserDataDir === "string" ? path.resolve(state.chromeUserDataDir) : "";
|
|
168
169
|
const expectedRuntimeDist = path.join(
|
|
169
170
|
runtimeRoot,
|
|
170
171
|
process.env.RECIPE_RUNTIME_DIST_DIR || "runtime-dist"
|
|
171
172
|
);
|
|
172
|
-
|
|
173
|
+
const profileStat = chromeUserDataDir ? fs.lstatSync(chromeUserDataDir, { throwIfNoEntry: false }) : void 0;
|
|
174
|
+
if (state.schemaVersion !== 2 || !sourceDir || runtimeDist !== expectedRuntimeDist || !provenancePath || !chromeUserDataDir.startsWith(`${runtimeRoot}${path.sep}`) || !profileStat?.isDirectory() || profileStat.isSymbolicLink()) {
|
|
173
175
|
return { status: "invalid", reason: "artifact runtime identity has invalid paths or schema" };
|
|
174
176
|
}
|
|
175
177
|
const provenanceStat = fs.lstatSync(provenancePath);
|
|
@@ -199,6 +201,7 @@ function releaseArtifactState(target) {
|
|
|
199
201
|
sourceDir,
|
|
200
202
|
runtimeDist,
|
|
201
203
|
provenancePath,
|
|
204
|
+
chromeUserDataDir,
|
|
202
205
|
sha256,
|
|
203
206
|
treeSha256,
|
|
204
207
|
expectedVersion,
|
|
@@ -83,11 +83,15 @@ async function checkExtensionRuntimeHealth(projectRoot, cdpPort, options = {}) {
|
|
|
83
83
|
(target2) => target2.type === "page" && String(target2.url ?? "").startsWith("chrome-extension://") && String(target2.url ?? "").includes("/sidepanel.html") && Boolean(target2.webSocketDebuggerUrl)
|
|
84
84
|
);
|
|
85
85
|
const extensionTargets = pageMode === "home-or-sidepanel" && homeTargets.length === 0 ? sidepanelTargets : homeTargets;
|
|
86
|
-
if (extensionTargets.length
|
|
87
|
-
const expected = pageMode === "home-or-sidepanel" ? "
|
|
86
|
+
if (extensionTargets.length === 0) {
|
|
87
|
+
const expected = pageMode === "home-or-sidepanel" ? "a MetaMask extension home page target, or one sidepanel target when home is intentionally closed" : "a MetaMask extension home page target";
|
|
88
88
|
findings.push(`Expected ${expected}, found ${extensionTargets.length}.`);
|
|
89
89
|
}
|
|
90
|
-
const
|
|
90
|
+
const targetSelection = await selectResponsiveExtensionTarget(extensionTargets, cdpCallTimeoutMs);
|
|
91
|
+
const target = targetSelection.target;
|
|
92
|
+
if (extensionTargets.length > 1) {
|
|
93
|
+
warnings.push(`Found ${extensionTargets.length} MetaMask Extension page targets; health selected ${target?.url ?? "none"} after probing each candidate.`);
|
|
94
|
+
}
|
|
91
95
|
if (!target?.webSocketDebuggerUrl) {
|
|
92
96
|
return failReport(cdpPort, findings, {
|
|
93
97
|
targetUrls: targets.map((entry) => entry.url).filter(Boolean)
|
|
@@ -205,6 +209,41 @@ async function checkExtensionRuntimeHealth(projectRoot, cdpPort, options = {}) {
|
|
|
205
209
|
session?.close();
|
|
206
210
|
}
|
|
207
211
|
}
|
|
212
|
+
async function selectResponsiveExtensionTarget(targets, timeoutMs) {
|
|
213
|
+
if (targets.length <= 1) return { target: targets[0] };
|
|
214
|
+
const probeTimeoutMs = Math.min(1e3, timeoutMs);
|
|
215
|
+
const ranked = [];
|
|
216
|
+
for (const [index, target] of targets.entries()) {
|
|
217
|
+
let session;
|
|
218
|
+
try {
|
|
219
|
+
session = await CdpSession.connect(target.webSocketDebuggerUrl, { timeoutMs: probeTimeoutMs });
|
|
220
|
+
await boundedCdpCall(session, "Runtime.enable", {}, probeTimeoutMs);
|
|
221
|
+
await boundedCdpCall(session, "Page.enable", {}, probeTimeoutMs);
|
|
222
|
+
const compositor = await probeCdpCompositorInteractivity(session, probeTimeoutMs);
|
|
223
|
+
const result = await boundedCdpCall(session, "Runtime.evaluate", {
|
|
224
|
+
expression: `(() => ({
|
|
225
|
+
readyState: String(document.readyState || ''),
|
|
226
|
+
href: String(location.href || ''),
|
|
227
|
+
text: String(document.body?.innerText || '').slice(0, 5000),
|
|
228
|
+
}))()`,
|
|
229
|
+
returnByValue: true
|
|
230
|
+
}, probeTimeoutMs);
|
|
231
|
+
const value = result.result?.value ?? {};
|
|
232
|
+
const text = String(value.text ?? "");
|
|
233
|
+
const href = String(value.href ?? target.url ?? "");
|
|
234
|
+
const blocked = text.includes("Background connection unresponsive") || text.includes("MetaMask had trouble starting") || text.includes("Unable to connect to Ethereum");
|
|
235
|
+
const onboarding = href.includes("/onboarding/");
|
|
236
|
+
const score = (compositor.status === "ready" ? 100 : compositor.status === "not-interactive" ? 10 : 0) + (value.readyState === "complete" ? 10 : 0) + (blocked ? 0 : 5) + (onboarding ? 0 : 2);
|
|
237
|
+
ranked.push({ target, score, index });
|
|
238
|
+
} catch {
|
|
239
|
+
ranked.push({ target, score: -1, index });
|
|
240
|
+
} finally {
|
|
241
|
+
session?.close();
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
ranked.sort((left, right) => right.score - left.score || left.index - right.index);
|
|
245
|
+
return { target: ranked[0]?.target };
|
|
246
|
+
}
|
|
208
247
|
function failReport(cdpPort, findings, details, extensionPageTargets = 0) {
|
|
209
248
|
return {
|
|
210
249
|
status: "FAIL",
|
|
@@ -14,6 +14,8 @@ const RUNWAY_IOS_METADATA = {
|
|
|
14
14
|
};
|
|
15
15
|
class SimulatorTargetError extends Error {
|
|
16
16
|
}
|
|
17
|
+
class SimulatorUnresponsiveError extends Error {
|
|
18
|
+
}
|
|
17
19
|
function resolvePoolIdentity(target) {
|
|
18
20
|
const out = resolveSlotPortsByRepo(target);
|
|
19
21
|
const id = {};
|
|
@@ -75,7 +77,7 @@ async function provisionRunwayMobile(target, options) {
|
|
|
75
77
|
const sim = ensureSimulator(simulator, runtime, deviceType);
|
|
76
78
|
let resolved;
|
|
77
79
|
let reason = options.force ? "forced" : "fresh-install";
|
|
78
|
-
if (!options.force &&
|
|
80
|
+
if (!options.force && appInstalledWithRecovery(sim.udid ?? sim.name, options)) {
|
|
79
81
|
const installed = readInstalledBaselineArtifact(resolvedTarget, sim, options.runtimeDir);
|
|
80
82
|
let requestedRunId = options.run;
|
|
81
83
|
if (installed && !requestedRunId) {
|
|
@@ -445,6 +447,24 @@ function bootSimulator(device, options) {
|
|
|
445
447
|
log(options, `runway: bootstatus wait for ${device} did not confirm; continuing to install`);
|
|
446
448
|
}
|
|
447
449
|
}
|
|
450
|
+
function restartSimulator(device, options) {
|
|
451
|
+
log(options, `runway: simulator ${device} did not answer the app probe; restarting it once`);
|
|
452
|
+
execFileSync("xcrun", ["simctl", "shutdown", device], {
|
|
453
|
+
stdio: ["ignore", "ignore", "ignore"],
|
|
454
|
+
timeout: 3e4
|
|
455
|
+
});
|
|
456
|
+
try {
|
|
457
|
+
execFileSync("xcrun", ["simctl", "boot", device], {
|
|
458
|
+
stdio: ["ignore", "ignore", "ignore"],
|
|
459
|
+
timeout: 3e4
|
|
460
|
+
});
|
|
461
|
+
} catch {
|
|
462
|
+
}
|
|
463
|
+
execFileSync("xcrun", ["simctl", "bootstatus", device, "-b"], {
|
|
464
|
+
stdio: ["ignore", "ignore", "ignore"],
|
|
465
|
+
timeout: 12e4
|
|
466
|
+
});
|
|
467
|
+
}
|
|
448
468
|
function preapproveDeepLinkScheme(device, bundleId, options) {
|
|
449
469
|
const scheme = process.env.IOS_DEV_CLIENT_SCHEME ?? "expo-metamask";
|
|
450
470
|
try {
|
|
@@ -572,12 +592,27 @@ function readInstalledBaselineArtifact(target, sim, runtimeDir) {
|
|
|
572
592
|
}
|
|
573
593
|
function appInstalled(device) {
|
|
574
594
|
try {
|
|
575
|
-
execFileSync("xcrun", ["simctl", "get_app_container", device, RUNWAY_IOS_METADATA.bundleId, "app"], {
|
|
595
|
+
execFileSync("xcrun", ["simctl", "get_app_container", device, RUNWAY_IOS_METADATA.bundleId, "app"], {
|
|
596
|
+
stdio: ["ignore", "ignore", "ignore"],
|
|
597
|
+
timeout: 15e3
|
|
598
|
+
});
|
|
576
599
|
return true;
|
|
577
|
-
} catch {
|
|
600
|
+
} catch (error) {
|
|
601
|
+
if (error instanceof Error && "code" in error && error.code === "ETIMEDOUT") {
|
|
602
|
+
throw new SimulatorUnresponsiveError(`simulator ${device} timed out while checking the installed app`);
|
|
603
|
+
}
|
|
578
604
|
return false;
|
|
579
605
|
}
|
|
580
606
|
}
|
|
607
|
+
function appInstalledWithRecovery(device, options) {
|
|
608
|
+
try {
|
|
609
|
+
return appInstalled(device);
|
|
610
|
+
} catch (error) {
|
|
611
|
+
if (!(error instanceof SimulatorUnresponsiveError)) throw error;
|
|
612
|
+
restartSimulator(device, options);
|
|
613
|
+
return appInstalled(device);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
581
616
|
function writeRunwayBaseline(target, slot, platform, resolved, artifact, simulator, alreadyInstalled, runtimeDir, preservedArtifact) {
|
|
582
617
|
const file = runwayBaselinePath(target, runtimeDir);
|
|
583
618
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
package/dist/adapters.js
CHANGED
|
@@ -13,6 +13,7 @@ import { resolveWalletImportCredentials, validateWalletImportOptions } from "../
|
|
|
13
13
|
const execFileAsync = promisify(execFile);
|
|
14
14
|
const NATIVE_PROVIDER_UI_ACTIONS = /* @__PURE__ */ new Set([
|
|
15
15
|
"ui.press",
|
|
16
|
+
"ui.key_press",
|
|
16
17
|
"ui.set_input",
|
|
17
18
|
"ui.scroll",
|
|
18
19
|
"ui.swipe",
|
|
@@ -34,7 +35,41 @@ function simpleAdapter(action, executor) {
|
|
|
34
35
|
}
|
|
35
36
|
};
|
|
36
37
|
}
|
|
37
|
-
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",
|
|
38
73
|
"metamask.perps.read_snapshot",
|
|
39
74
|
"metamask.perps.read_positions",
|
|
40
75
|
"metamask.perps.ensure_positions",
|
|
@@ -66,7 +101,7 @@ const LIVE_ONLY_WALLET_ACTIONS = /* @__PURE__ */ new Set([
|
|
|
66
101
|
]);
|
|
67
102
|
const LIVE_ONLY_APP_ACTIONS = /* @__PURE__ */ new Set(["ui.navigate"]);
|
|
68
103
|
function requiresLiveAdapter(platform, action) {
|
|
69
|
-
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);
|
|
70
105
|
}
|
|
71
106
|
function liveRuntimeConfigured(platform, node) {
|
|
72
107
|
if (platform === "mobile" || platform === "core") return true;
|
|
@@ -214,6 +249,36 @@ function createMetaMaskSemanticAdapters(platform, declaredCustomActions = [], pr
|
|
|
214
249
|
];
|
|
215
250
|
const bundledActions = [
|
|
216
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"] : [],
|
|
217
282
|
"metamask.perps.read_positions",
|
|
218
283
|
"metamask.perps.ensure_positions",
|
|
219
284
|
"metamask.perps.assert_positions",
|
|
@@ -810,7 +875,7 @@ function createMetaMaskUiTransport(platform, harness, preparedLiveAdapters) {
|
|
|
810
875
|
if (live) return live.output;
|
|
811
876
|
throw new Error(`ui.navigate requires library/actions/${platform}/ui/navigate.mjs.`);
|
|
812
877
|
}
|
|
813
|
-
if (platform === "mobile" && NATIVE_PROVIDER_UI_ACTIONS.has(action) && (context.env
|
|
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))) {
|
|
814
879
|
return executeNativeProviderAction(action, node, context, harness.createNativeUiTransport);
|
|
815
880
|
}
|
|
816
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/commands/call.js
CHANGED
|
@@ -290,7 +290,10 @@ async function handleCall(argv) {
|
|
|
290
290
|
target,
|
|
291
291
|
heal,
|
|
292
292
|
state,
|
|
293
|
-
() => recoverRunInfra(adapter, target, json
|
|
293
|
+
() => recoverRunInfra(adapter, target, json, {
|
|
294
|
+
cdpPort: optionString(options, "cdpPort"),
|
|
295
|
+
watcherPort: optionString(options, "watcherPort") ?? optionString(options, "metroPort")
|
|
296
|
+
})
|
|
294
297
|
);
|
|
295
298
|
if (violation !== null) {
|
|
296
299
|
const conciseFailure = violation.originalError ? conciseFailureForHuman(violation.originalError) : "";
|
|
@@ -56,6 +56,12 @@ async function installExtensionDeps(target) {
|
|
|
56
56
|
return result;
|
|
57
57
|
}
|
|
58
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
|
+
};
|
|
59
65
|
if (process.env.CHROME_USER_DATA_DIR) {
|
|
60
66
|
process.env.CHROME_USER_DATA_DIR = path.resolve(process.env.CHROME_USER_DATA_DIR);
|
|
61
67
|
}
|
|
@@ -63,6 +69,7 @@ async function launchExtension(target, tier, wantWatch, displayMode = "fullscree
|
|
|
63
69
|
const artifactSnapshot = runtimeDistCheck(target, artifact);
|
|
64
70
|
const artifactLaunchable = !wantWatch && tier !== "build" && artifact.status === "valid" && artifactSnapshot.status === "fresh";
|
|
65
71
|
const reusable = !wantWatch && tier !== "build" && await extensionRuntimeReusable(target, artifact);
|
|
72
|
+
restoreRequestedPorts();
|
|
66
73
|
if (artifact.status !== "none" && !reusable && !artifactLaunchable) {
|
|
67
74
|
const detail = artifact.status === "invalid" ? `identity is invalid: ${artifact.reason ?? "unknown reason"}` : "runtime is not healthy and reusable";
|
|
68
75
|
return {
|
|
@@ -82,6 +89,7 @@ Next: ${block.userAction}`
|
|
|
82
89
|
}
|
|
83
90
|
}
|
|
84
91
|
await ensureHarnessFresh(target, "extension");
|
|
92
|
+
restoreRequestedPorts();
|
|
85
93
|
if (wantWatch) {
|
|
86
94
|
const startWatchSh = path.join(runnerDir, "adapters/extension/start-watch.sh");
|
|
87
95
|
const watchArgs = ["--target", target];
|
|
@@ -93,6 +101,12 @@ Next: ${block.userAction}`
|
|
|
93
101
|
return extensionRebuild(target);
|
|
94
102
|
}
|
|
95
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
|
+
}
|
|
96
110
|
return extensionReattach(target, displayMode);
|
|
97
111
|
}
|
|
98
112
|
if (artifactLaunchable) {
|
|
@@ -115,7 +129,7 @@ async function extensionRuntimeReusable(target, artifact = releaseArtifactState(
|
|
|
115
129
|
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
116
130
|
const reachable = await cdpVersionReachable(cdpPort);
|
|
117
131
|
const nonceOwned = reachable && await cdpOwnedByRuntimeNonce(cdpPort, target);
|
|
118
|
-
const owned = nonceOwned || reachable && cdpOwnedByExpectedRuntime(cdpPort, target);
|
|
132
|
+
const owned = nonceOwned || reachable && cdpOwnedByExpectedRuntime(cdpPort, target, releaseArtifact ? artifact.chromeUserDataDir : void 0);
|
|
119
133
|
const targetPresent = owned && await cdpHasExpectedExtensionTarget(cdpPort, target);
|
|
120
134
|
const healthy = targetPresent && await cdpRuntimeHealthy(cdpPort, target);
|
|
121
135
|
if (healthy) return true;
|
|
@@ -142,9 +156,9 @@ function commandHasExactFlagValue(command, flag, value) {
|
|
|
142
156
|
}
|
|
143
157
|
return false;
|
|
144
158
|
}
|
|
145
|
-
function cdpOwnedByExpectedRuntime(port, target) {
|
|
159
|
+
function cdpOwnedByExpectedRuntime(port, target, profileOverride) {
|
|
146
160
|
const pids = cdpListenerPids(port);
|
|
147
|
-
const profile = expectedChromeProfile(target);
|
|
161
|
+
const profile = profileOverride ?? expectedChromeProfile(target);
|
|
148
162
|
const runtimeDist = expectedRuntimeDist(target);
|
|
149
163
|
for (const pid of pids) {
|
|
150
164
|
try {
|
|
@@ -369,7 +383,7 @@ async function extensionLaunchDevelopment(target) {
|
|
|
369
383
|
return spawnScriptStreaming(liveScript, liveArgs, target);
|
|
370
384
|
}
|
|
371
385
|
async function extensionLaunchReleaseArtifact(target, artifact) {
|
|
372
|
-
if (!artifact.sourceDir || !artifact.provenancePath) {
|
|
386
|
+
if (!artifact.sourceDir || !artifact.provenancePath || !artifact.chromeUserDataDir) {
|
|
373
387
|
return {
|
|
374
388
|
status: EXIT.infra,
|
|
375
389
|
output: "RELEASE_ARTIFACT_NOT_REUSABLE: The loaded release artifact identity is incomplete.\nNext: rerun runtime-launch with the same artifact source."
|
|
@@ -384,7 +398,9 @@ async function extensionLaunchReleaseArtifact(target, artifact) {
|
|
|
384
398
|
artifact.sourceDir,
|
|
385
399
|
"--artifact-provenance",
|
|
386
400
|
artifact.provenancePath,
|
|
387
|
-
"--preserve-profile"
|
|
401
|
+
"--preserve-profile",
|
|
402
|
+
"--chrome-user-data-dir",
|
|
403
|
+
artifact.chromeUserDataDir
|
|
388
404
|
];
|
|
389
405
|
if (process.env.CDP_PORT) liveArgs.push("--cdp-port", process.env.CDP_PORT);
|
|
390
406
|
if (process.env.EXTENSION_START_URL) liveArgs.push("--start-url", process.env.EXTENSION_START_URL);
|
|
@@ -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) {
|
|
@@ -1197,43 +1197,54 @@ const RUN_RECOVERY_CODE = {
|
|
|
1197
1197
|
extension: "extension.runtime_recovered",
|
|
1198
1198
|
core: "run.retried"
|
|
1199
1199
|
};
|
|
1200
|
-
async function recoverRunInfra(adapter, target, json) {
|
|
1200
|
+
async function recoverRunInfra(adapter, target, json, runtimeOptions = {}) {
|
|
1201
1201
|
getAdapterSurface(adapter).resolveSlotPorts(target);
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
status: 1,
|
|
1208
|
-
output: "Release artifact recovery cannot switch to a checkout build.\nNext: rerun runtime-launch with the same artifact source."
|
|
1209
|
-
};
|
|
1210
|
-
}
|
|
1211
|
-
const { launchExtension } = await import("./launch/extension.js");
|
|
1212
|
-
return launchExtension(target, "quick", false);
|
|
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;
|
|
1213
1207
|
}
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
const
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
await relaunchAndroidReleaseArtifactRuntime(artifact);
|
|
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") {
|
|
1220
1213
|
return {
|
|
1221
|
-
status:
|
|
1222
|
-
output:
|
|
1214
|
+
status: 1,
|
|
1215
|
+
output: "Release artifact recovery cannot trust the recorded runtime identity.\nNext: rerun runtime-launch with the same artifact source."
|
|
1223
1216
|
};
|
|
1224
1217
|
}
|
|
1225
|
-
|
|
1226
|
-
return
|
|
1227
|
-
|
|
1228
|
-
|
|
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)}
|
|
1229
1236
|
Next: rerun runtime-launch with the same artifact source.`
|
|
1230
|
-
|
|
1237
|
+
};
|
|
1238
|
+
}
|
|
1239
|
+
const { launchMobile } = await import("./launch/mobile.js");
|
|
1240
|
+
const platform = resolveMobileRecoveryPlatform();
|
|
1241
|
+
return launchMobile(target, platform, "quick", json);
|
|
1231
1242
|
}
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1243
|
+
return { status: 0, output: "headless run retry" };
|
|
1244
|
+
} finally {
|
|
1245
|
+
restoreEnv("CDP_PORT", previousCdpPort);
|
|
1246
|
+
restoreEnv("RECIPE_CDP_PORT", previousRecipeCdpPort);
|
|
1235
1247
|
}
|
|
1236
|
-
return { status: 0, output: "headless run retry" };
|
|
1237
1248
|
}
|
|
1238
1249
|
function resolveMobileRecoveryPlatform(env = process.env) {
|
|
1239
1250
|
if (env.PLATFORM === "android" || env.PLATFORM === "ios") return env.PLATFORM;
|
package/dist/commands/run.js
CHANGED
|
@@ -284,7 +284,7 @@ async function handleRunInner({ positional, options }, stream) {
|
|
|
284
284
|
target,
|
|
285
285
|
heal,
|
|
286
286
|
state,
|
|
287
|
-
() => recoverRunInfra(adapter, target, machine),
|
|
287
|
+
() => recoverRunInfra(adapter, target, machine, runtimeOptions),
|
|
288
288
|
(code) => stream.phase("recover", { code })
|
|
289
289
|
);
|
|
290
290
|
for (const mutation of state.mutations) stream.mutation(mutation);
|