@deeeed/metamask-harness 0.79.0 → 0.80.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.
Files changed (210) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.md +1 -1
  3. package/adapters/extension/ensure-browser.sh +2 -2
  4. package/adapters/extension/inject.mjs +0 -1
  5. package/adapters/extension/launch-browser.cjs +39 -613
  6. package/adapters/extension/readiness.mjs +2 -5
  7. package/adapters/extension/reattach.sh +2 -2
  8. package/adapters/extension/stamp-runtime-title.cjs +1 -1
  9. package/adapters/manifest.json +2 -34
  10. package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +57 -1358
  11. package/adapters/mobile/bridge-runtime/teaching.cjs +20 -0
  12. package/adapters/mobile/inject.sh +16 -0
  13. package/adapters/mobile/launch-console-forwarder.cjs +1 -2
  14. package/adapters/mobile/open-device.sh +2 -2
  15. package/adapters/mobile/reload-app.mjs +4 -4
  16. package/adapters/mobile/reset-app-data.sh +1 -1
  17. package/adapters/mobile/start-console-forwarder.sh +3 -2
  18. package/adapters/mobile/start-metro.sh +17 -12
  19. package/adapters/mobile/wait-for-bridge.cjs +2 -2
  20. package/adapters/shared/harness-source-fingerprint.mjs +13 -8
  21. package/adapters/shared/open-debug.mjs +2 -2
  22. package/adapters/shared/sync-wallet-fixture.sh +8 -5
  23. package/adapters/terminal/cleanup.mjs +2 -1
  24. package/adapters/terminal/inject.mjs +2 -1
  25. package/bin/mm-harness +9 -28
  26. package/dist/adapters/checkout-files.js +16 -0
  27. package/dist/adapters/console-capture.js +14 -1
  28. package/dist/adapters/core/actions.js +4 -3
  29. package/dist/adapters/core/overlay.js +1 -1
  30. package/dist/adapters/core/readiness.js +28 -1
  31. package/dist/adapters/core/run.js +2 -2
  32. package/dist/adapters/core/surface.js +16 -1
  33. package/dist/adapters/extension/browser-pid.js +56 -0
  34. package/dist/adapters/extension/console-capture.js +1 -1
  35. package/dist/adapters/extension/doctor-checks.js +1 -1
  36. package/dist/adapters/extension/ensure-ready.js +5 -4
  37. package/dist/adapters/extension/extension-id.js +1 -1
  38. package/dist/adapters/extension/feature-flags.js +1 -1
  39. package/dist/adapters/extension/fixtures.js +1 -2
  40. package/dist/adapters/extension/launch.js +4 -5
  41. package/dist/adapters/extension/launched-browser.js +1 -2
  42. package/dist/adapters/extension/network-observer.js +1 -1
  43. package/dist/adapters/extension/overlay.js +1 -1
  44. package/dist/adapters/extension/product-config.js +1 -1
  45. package/dist/adapters/extension/readiness.js +1 -1
  46. package/dist/adapters/extension/reload.js +1 -1
  47. package/dist/adapters/extension/run.js +24 -10
  48. package/dist/adapters/extension/runtime-decision.js +2 -1
  49. package/dist/adapters/extension/runtime.js +9 -5
  50. package/dist/adapters/extension/surface.js +15 -2
  51. package/dist/adapters/extension/verify.js +2 -2
  52. package/dist/adapters/harness-freshness.js +2 -1
  53. package/dist/adapters/mobile/android-gfxinfo-performance.js +1 -3
  54. package/dist/adapters/mobile/app-identity.js +10 -0
  55. package/dist/adapters/mobile/doctor-checks.js +1 -1
  56. package/dist/adapters/mobile/fixtures.js +1 -1
  57. package/dist/adapters/mobile/launch.js +27 -28
  58. package/dist/adapters/mobile/lifecycle.js +1 -1
  59. package/dist/adapters/mobile/metro-env.js +9 -59
  60. package/dist/adapters/mobile/performance-observer.js +1 -1
  61. package/dist/adapters/mobile/prepare-target.js +1 -1
  62. package/dist/adapters/mobile/prepare.js +3 -4
  63. package/dist/adapters/mobile/provision.js +1 -1
  64. package/dist/adapters/mobile/readiness.js +1 -1
  65. package/dist/adapters/mobile/recording-target.js +15 -0
  66. package/dist/adapters/mobile/release-artifact-state.js +1 -1
  67. package/dist/adapters/mobile/reload.js +1 -1
  68. package/dist/adapters/mobile/run.js +8 -7
  69. package/dist/adapters/mobile/runtime-decision.js +2 -4
  70. package/dist/adapters/mobile/source-freshness.js +22 -126
  71. package/dist/adapters/mobile/surface.js +26 -3
  72. package/dist/adapters/mobile/ui.js +16 -5
  73. package/dist/adapters/mobile/verify.js +2 -3
  74. package/dist/adapters/performance/cdp-trace.js +1 -1
  75. package/dist/adapters/performance/device-platform.js +1 -1
  76. package/dist/adapters/slot-ports.js +1 -1
  77. package/dist/adapters/surface.js +1 -0
  78. package/dist/adapters/terminal/console-capture.js +1 -1
  79. package/dist/adapters/terminal/launch.js +1 -1
  80. package/dist/adapters/terminal/run.js +25 -2
  81. package/dist/adapters/terminal/surface.js +7 -1
  82. package/dist/adapters.js +8 -4
  83. package/dist/advisor-policy.js +1 -1
  84. package/dist/checkout-view.js +1 -2
  85. package/dist/cli.js +28 -15
  86. package/dist/command-contract.js +1 -26
  87. package/dist/commands/call.js +10 -24
  88. package/dist/commands/check.js +3 -4
  89. package/dist/commands/checklist.js +1 -1
  90. package/dist/commands/completion-candidates.js +2 -10
  91. package/dist/commands/config.js +2 -2
  92. package/dist/commands/core-readiness.js +12 -7
  93. package/dist/commands/debug.js +3 -3
  94. package/dist/commands/device-target.js +5 -5
  95. package/dist/commands/doctor.js +2 -14
  96. package/dist/commands/domain.js +2 -2
  97. package/dist/commands/ensure-ready.js +1 -7
  98. package/dist/commands/fixtures.js +5 -6
  99. package/dist/commands/help.js +3 -4
  100. package/dist/commands/launch/device.js +10 -17
  101. package/dist/commands/launch/extension.js +2 -8
  102. package/dist/commands/launch/mobile.js +1 -1
  103. package/dist/commands/list-executables.js +1 -3
  104. package/dist/commands/logs.js +3 -3
  105. package/dist/commands/manifest.js +1 -12
  106. package/dist/commands/mobile-device-view.js +4 -2
  107. package/dist/commands/overlay.js +110 -0
  108. package/dist/commands/pin.js +1 -1
  109. package/dist/commands/pr-body.js +2 -2
  110. package/dist/commands/prepare.js +4 -9
  111. package/dist/commands/provision.js +3 -12
  112. package/dist/commands/recipe-advice.js +1 -2
  113. package/dist/commands/recipe-quality.js +2 -3
  114. package/dist/commands/resolve-extension.js +1 -7
  115. package/dist/commands/review.js +4 -4
  116. package/dist/commands/run-engine.js +12 -51
  117. package/dist/commands/run.js +4 -25
  118. package/dist/commands/runtime-decision.js +1 -6
  119. package/dist/commands/runtime-health.js +1 -7
  120. package/dist/commands/runtime-launch.js +1 -13
  121. package/dist/commands/self-test.js +2 -6
  122. package/dist/commands/status-watch.js +1 -2
  123. package/dist/commands/status.js +2 -5
  124. package/dist/commands/task-init.js +2 -3
  125. package/dist/commands/tutorial.js +2 -2
  126. package/dist/commands/update.js +1 -1
  127. package/dist/completions-cache.js +1 -1
  128. package/dist/console-classifier.js +19 -0
  129. package/dist/doctor.js +2 -3
  130. package/dist/funding-execution-context.js +15 -1
  131. package/dist/host.js +32 -0
  132. package/dist/index.js +1 -6
  133. package/dist/live-tsx.js +49 -0
  134. package/dist/mm-harness-cli.js +2 -4
  135. package/dist/network-observation.js +4 -4
  136. package/dist/paths.js +34 -64
  137. package/dist/performance-observation.js +2 -2
  138. package/dist/progress.js +1 -1
  139. package/dist/recipe-assistance-context.js +1 -3
  140. package/dist/recipe-security.js +2 -59
  141. package/dist/review/knowledge.js +2 -2
  142. package/dist/runner.js +5 -3
  143. package/dist/runtime-context.js +1 -1
  144. package/dist/task-view.js +2 -3
  145. package/docs/CONTRIBUTING.md +1 -1
  146. package/docs/cli-contract.md +2 -2
  147. package/docs/feature-guard.md +90 -0
  148. package/library/README.md +3 -1
  149. package/library/actions/core/perps/_controller.mjs +153 -130
  150. package/library/actions/core/perps/quote_fees.mjs +58 -0
  151. package/library/actions/core/wallet/_fixture-accounts.mjs +69 -0
  152. package/library/actions/core/wallet/list_accounts.mjs +14 -57
  153. package/library/actions/extension/feature_flags/state.mjs +1 -1
  154. package/library/actions/extension/platform/cdp.mjs +2 -8
  155. package/library/actions/harness-exports.mjs +1 -6
  156. package/library/actions/mobile/app/network-control.mjs +6 -8
  157. package/library/actions/mobile/perps/prepare_local_snapshot_endpoint.mjs +1 -1
  158. package/library/actions/mobile/platform/bridge.mjs +8 -8
  159. package/library/actions/mobile/platform/native-session-name.mjs +21 -1
  160. package/library/actions/mobile/platform/native-session.mjs +3 -4
  161. package/library/actions/mobile/platform/observe-ui.mjs +12 -6
  162. package/library/actions/mobile/ui/native-navigation.mjs +4 -3
  163. package/library/actions/mobile/wallet/native-ui.mjs +3 -2
  164. package/library/actions/shared/analytics/_adapter.mjs +1 -1
  165. package/library/actions/shared/console-findings.mjs +1 -1
  166. package/library/actions/terminal/platform/runtime.mjs +2 -1
  167. package/library/manifests/core.action-manifest.json +83 -13
  168. package/package.json +8 -9
  169. package/adapters/extension/lib/slot-title.cjs +0 -292
  170. package/adapters/mobile/bridge-runtime/console-forwarder.cjs +0 -472
  171. package/adapters/mobile/bridge-runtime/lib/bridge-errors.cjs +0 -125
  172. package/adapters/mobile/bridge-runtime/lib/cdp-broker.cjs +0 -1055
  173. package/adapters/mobile/bridge-runtime/lib/cdp-eval.cjs +0 -110
  174. package/adapters/mobile/bridge-runtime/lib/config.cjs +0 -61
  175. package/adapters/mobile/bridge-runtime/lib/console-format.cjs +0 -53
  176. package/adapters/mobile/bridge-runtime/lib/devtools-proxy.cjs +0 -177
  177. package/adapters/mobile/bridge-runtime/lib/issue-capture.cjs +0 -446
  178. package/adapters/mobile/bridge-runtime/lib/match-bridge-target.cjs +0 -84
  179. package/adapters/mobile/bridge-runtime/lib/target-discovery.cjs +0 -403
  180. package/adapters/mobile/bridge-runtime/lib/ws-client.cjs +0 -116
  181. package/adapters/mobile/coalesce-metro-log.cjs +0 -24
  182. package/adapters/mobile/launch-metro.cjs +0 -77
  183. package/adapters/mobile/metro-config.cjs +0 -98
  184. package/adapters/mobile/metro-log-generation.cjs +0 -106
  185. package/dist/adapters/mobile/frame-metrics.js +0 -118
  186. package/dist/adapters/mobile/video-recorder.js +0 -289
  187. package/dist/artifact-files.js +0 -92
  188. package/dist/checkout-lock.js +0 -149
  189. package/dist/cli-color.js +0 -98
  190. package/dist/command-journal.js +0 -308
  191. package/dist/commands/last.js +0 -60
  192. package/dist/commands/launch/index.js +0 -109
  193. package/dist/commands/parse-args.js +0 -279
  194. package/dist/commands/reload.js +0 -41
  195. package/dist/commands/run-report.js +0 -241
  196. package/dist/commands/shared.js +0 -323
  197. package/dist/commands/stop.js +0 -60
  198. package/dist/devices.js +0 -100
  199. package/dist/execution-provenance.js +0 -332
  200. package/dist/harness.js +0 -451
  201. package/dist/heal-bounds.js +0 -137
  202. package/dist/json-stream.js +0 -60
  203. package/dist/leaf-invoke.js +0 -19
  204. package/dist/library-provenance.js +0 -33
  205. package/dist/live-adapter-contract.js +0 -380
  206. package/dist/recording-target.js +0 -120
  207. package/dist/run-diagnostics.js +0 -364
  208. package/dist/run-recording.js +0 -481
  209. package/dist/runtime-proof.js +0 -90
  210. package/library/actions/mobile/platform/tool-paths.mjs +0 -123
@@ -1,10 +1,9 @@
1
+ import { recipeRuntimeDir } from "@farmslot/recipe-cli/harness";
1
2
  import { createRequire } from "node:module";
2
3
  import path from "node:path";
3
- import { recipeRuntimeDir, runnerDir } from "../../paths.js";
4
4
  import { resolveExtensionId } from "./extension-id.js";
5
- const requireFromHarness = createRequire(import.meta.url);
6
- const { stampHomeTabsViaCdp } = requireFromHarness(
7
- path.join(runnerDir, "adapters/extension/lib/slot-title.cjs")
5
+ const { stampHomeTabsViaCdp } = createRequire(import.meta.url)(
6
+ "@farmslot/adapter-web/slot-title"
8
7
  );
9
8
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
10
9
  async function jsonList(port) {
@@ -200,6 +199,8 @@ async function ensureExtensionReady(target, options) {
200
199
  target: resolved,
201
200
  cdpPort,
202
201
  extensionId,
202
+ homePage: "home.html",
203
+ defaultTitle: "MetaMask",
203
204
  runtimeDir: recipeRuntimeDir()
204
205
  });
205
206
  if (stamp.slotId || stamp.stamped > 0 || stamp.skipped > 0) {
@@ -1,7 +1,7 @@
1
1
  import crypto from "node:crypto";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
- import { recipeRuntimeDir } from "../../paths.js";
4
+ import { recipeRuntimeDir } from "@farmslot/recipe-cli/harness";
5
5
  import { releaseArtifactState, runtimeDistCheck } from "./runtime-decision.js";
6
6
  const DIST_MANIFEST = "dist/chrome/manifest.json";
7
7
  function extensionIdFromKey(keyBase64) {
@@ -1,7 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
+ import { recipeRuntimeDir } from "@farmslot/recipe-cli/harness";
3
4
  import { normalizeFeatureFlagRecord } from "../../feature-flags.js";
4
- import { recipeRuntimeDir } from "../../paths.js";
5
5
  import { releaseArtifactState } from "./runtime-decision.js";
6
6
  function extensionRuntimeManifestPath(target) {
7
7
  return path.join(
@@ -1,8 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
- import { colorHumanMessage } from "../../cli-color.js";
3
+ import { colorHumanMessage, EXIT, spawnScriptStreaming } from "@farmslot/recipe-cli/harness";
4
4
  import { FIXTURE_STREAM_TIMEOUT_MS } from "../../commands/fixtures.js";
5
- import { EXIT, spawnScriptStreaming } from "../../commands/shared.js";
6
5
  import { sameFeatureFlags } from "../../feature-flags.js";
7
6
  import { runnerDir } from "../../paths.js";
8
7
  import { readExtensionPinnedFlags } from "./feature-flags.js";
@@ -1,5 +1,5 @@
1
1
  import path from "node:path";
2
- import { colorHumanMessage } from "../../cli-color.js";
2
+ import { colorHumanMessage, EXIT, spawnScript } from "@farmslot/recipe-cli/harness";
3
3
  import {
4
4
  extensionDepsBlock,
5
5
  extensionRuntimeReusable,
@@ -7,18 +7,17 @@ import {
7
7
  launchExtension
8
8
  } from "../../commands/launch/extension.js";
9
9
  import { shellQuote } from "../../commands/launch/quote.js";
10
- import { EXIT, spawnScript } from "../../commands/shared.js";
11
10
  import {
12
11
  fixtureFeatureFlags,
13
12
  sameFeatureFlags,
14
13
  toRemoteFlagSpec
15
14
  } from "../../feature-flags.js";
16
- import { RECOVERY_CODE } from "../../heal-bounds.js";
17
15
  import { runnerDir, walletFixturePath } from "../../paths.js";
18
16
  import { ensureExtensionConsoleCapture } from "./console-capture.js";
19
17
  import { readExtensionPinnedFlags } from "./feature-flags.js";
20
18
  import { extensionProductConfigBlock } from "./product-config.js";
21
19
  import { releaseArtifactState, runtimeDistCheck } from "./runtime-decision.js";
20
+ const CHROME_REOPENED = "chrome.reopened";
22
21
  const DEFAULT_EXTENSION_DAPP_URL = "https://metamask.github.io/test-dapp/";
23
22
  function verifyExtensionPin(target, requested) {
24
23
  const pinned = readExtensionPinnedFlags(target);
@@ -223,7 +222,7 @@ const extensionLaunchProfile = {
223
222
  originalError: rebuildAttempt.output.trim() || void 0
224
223
  });
225
224
  },
226
- recovery: () => ({ code: RECOVERY_CODE.extension, restartApp: false }),
225
+ recovery: () => ({ code: CHROME_REOPENED, restartApp: false }),
227
226
  // --watch only restarts the webpack watcher; the snapshot, and its pin, are
228
227
  // untouched, so the live record is reported rather than verified against a
229
228
  // request the watcher could never apply (--remote-flag is refused with it;
@@ -307,7 +306,7 @@ const extensionLaunchProfile = {
307
306
  return void 0;
308
307
  },
309
308
  verifyArgs: () => process.env.CDP_PORT ? ["--cdp-port", process.env.CDP_PORT] : [],
310
- platformLabel: ({ mobileTarget, displayMode }) => mobileTarget ?? displayMode,
309
+ platformLabel: ({ platformTarget, displayMode }) => platformTarget ?? displayMode,
311
310
  deviceLabel: ({ displayMode }) => displayMode
312
311
  };
313
312
  export {
@@ -1,8 +1,7 @@
1
1
  import fs from "node:fs";
2
+ import { recipeRuntimeDir, isRecord } from "@farmslot/recipe-cli/harness";
2
3
  import { createRequire } from "node:module";
3
4
  import path from "node:path";
4
- import { isRecord } from "../../commands/parse-args.js";
5
- import { recipeRuntimeDir } from "../../paths.js";
6
5
  function readRecord(file) {
7
6
  try {
8
7
  const recorded = JSON.parse(fs.readFileSync(file, "utf8"));
@@ -1,5 +1,5 @@
1
1
  import path from "node:path";
2
- import brokerModule from "../../../adapters/mobile/bridge-runtime/lib/cdp-broker.cjs";
2
+ import brokerModule from "@farmslot/recipe-runner/cdp-broker";
3
3
  import {
4
4
  asBrowserCdpTarget,
5
5
  createBrowserCdpClient,
@@ -1,5 +1,5 @@
1
+ import { readRuntimeContextField, resolveRuntimeContextPath } from "@farmslot/recipe-cli/harness";
1
2
  import fs from "node:fs";
2
- import { readRuntimeContextField, resolveRuntimeContextPath } from "../../harness.js";
3
3
  import { resolveExtensionSlotPorts } from "../slot-ports.js";
4
4
  function hasArg(args, needle) {
5
5
  return args.some((arg) => arg === needle || arg.startsWith(`${needle}=`));
@@ -1,8 +1,8 @@
1
+ import { shellQuote } from "@farmslot/recipe-cli/harness";
1
2
  import { createHash } from "node:crypto";
2
3
  import fs from "node:fs";
3
4
  import path from "node:path";
4
5
  import productConfig from "../../../adapters/extension/lib/product-config.cjs";
5
- import { shellQuote } from "../../commands/parse-args.js";
6
6
  const EXTENSION_PRODUCT_CONFIG_FINGERPRINT_FILENAME = "extension-product-config.sha256";
7
7
  function extensionProductConfigFingerprint(target, environment = process.env) {
8
8
  const resolution = productConfig.resolveExtensionInfuraProjectId(target, environment);
@@ -1,4 +1,4 @@
1
- import { optionFlag, optionString, parsePort } from "../../commands/parse-args.js";
1
+ import { optionFlag, optionString, parsePort } from "@farmslot/recipe-cli/harness";
2
2
  import { ensureExtensionPerpsBuildFlag, extensionBrowserDoctorCheck, extensionBrowserResolutionDoctorCheck } from "./doctor-checks.js";
3
3
  import { readExtensionPinnedFlags } from "./feature-flags.js";
4
4
  import { extensionProductConfigBlock } from "./product-config.js";
@@ -1,5 +1,5 @@
1
+ import { EXIT, spawnScript, usageOut } from "@farmslot/recipe-cli/harness";
1
2
  import path from "node:path";
2
- import { EXIT, spawnScript, usageOut } from "../../commands/shared.js";
3
3
  import { runnerDir } from "../../paths.js";
4
4
  function reloadExtension(target, json) {
5
5
  const adapter = "extension";
@@ -1,5 +1,6 @@
1
- import { optionString } from "../../commands/parse-args.js";
2
- import { resolveExtensionBrowserPid } from "../../recording-target.js";
1
+ import { optionFlag, optionString } from "@farmslot/recipe-cli/harness";
2
+ import { verifyOwnedCollectorControl } from "../console-capture.js";
3
+ import { resolveExtensionBrowserPid } from "./browser-pid.js";
3
4
  import {
4
5
  ensureExtensionConsoleCapture,
5
6
  extensionCaptureFiles,
@@ -7,9 +8,15 @@ import {
7
8
  verifyExtensionConsoleControl
8
9
  } from "./console-capture.js";
9
10
  import { launchedBrowser } from "./launched-browser.js";
11
+ const AUTOLAUNCH_ENV = "METAMASK_RECIPE_EXTENSION_AUTOLAUNCH";
10
12
  const extensionRun = {
13
+ platformOptions: (options) => ({ launchExistingDist: optionFlag(options, "launchExistingDist") }),
14
+ activateEnv(_projectRoot, options) {
15
+ if (options.platform?.launchExistingDist) process.env[AUTOLAUNCH_ENV] = "1";
16
+ },
17
+ envKeys: [AUTOLAUNCH_ENV],
11
18
  async prepareRuntime(projectRoot, runtimeOptions) {
12
- if (runtimeOptions.skipExtensionRuntimePrepare === true) return;
19
+ if (runtimeOptions.platform?.skipExtensionRuntimePrepare === true) return;
13
20
  const { releaseArtifactState, runtimeDistCheck } = await import("./runtime-decision.js");
14
21
  const artifact = releaseArtifactState(projectRoot);
15
22
  const runtimeSnapshot = runtimeDistCheck(projectRoot, artifact);
@@ -24,7 +31,7 @@ const extensionRun = {
24
31
  projectRoot,
25
32
  cdpPort: runtimeOptions.cdpPort,
26
33
  slot: runtimeOptions.slot,
27
- launchExistingDist: runtimeOptions.launchExistingDist === true,
34
+ launchExistingDist: runtimeOptions.platform?.launchExistingDist === true,
28
35
  validationRuntimeDir: runtimeOptions.validationRuntimeDir,
29
36
  pageMode: "home-or-sidepanel"
30
37
  });
@@ -33,12 +40,11 @@ const extensionRun = {
33
40
  // the slot's for the check, and the slot's come back afterwards.
34
41
  runtimeCheck({ target, options, heal, state, json, onRecovery }) {
35
42
  return async () => {
36
- const cliOptions = options;
37
43
  const previousCdpPort = process.env.CDP_PORT;
38
44
  const previousRecipeCdpPort = process.env.RECIPE_CDP_PORT;
39
45
  const previousWatcherPort = process.env.WATCHER_PORT;
40
- const explicitCdpPort = optionString(cliOptions, "cdpPort");
41
- const explicitWatcherPort = optionString(cliOptions, "watcherPort") ?? optionString(cliOptions, "metroPort");
46
+ const explicitCdpPort = optionString(options, "cdpPort");
47
+ const explicitWatcherPort = optionString(options, "watcherPort") ?? optionString(options, "metroPort");
42
48
  if (explicitCdpPort) {
43
49
  process.env.CDP_PORT = explicitCdpPort;
44
50
  process.env.RECIPE_CDP_PORT = explicitCdpPort;
@@ -54,12 +60,20 @@ const extensionRun = {
54
60
  }
55
61
  };
56
62
  },
57
- launchedBrowser: (target, artifactsDir, cdpPort) => launchedBrowser(target, artifactsDir, cdpPort)
63
+ launchedBrowser: (target, artifactsDir, cdpPort) => launchedBrowser(target, artifactsDir, cdpPort),
64
+ browserProvenance: (browser) => ({
65
+ browser: browser.browser,
66
+ browser_version: browser.version,
67
+ browser_source: browser.source,
68
+ extension_loading: browser.extensionLoading
69
+ })
58
70
  };
59
71
  const extensionRecording = {
60
72
  target: (context) => resolveExtensionRecordingTarget(context),
61
73
  framed: {
62
- browserPid: (projectRoot, artifactsDir, cdpPort) => resolveExtensionBrowserPid(projectRoot, artifactsDir, cdpPort)
74
+ browserPid: (projectRoot, artifactsDir, cdpPort) => resolveExtensionBrowserPid(projectRoot, artifactsDir, cdpPort),
75
+ // cdp.mjs snapshots the framed recording of this pid.
76
+ activePidEnv: "METAMASK_RECIPE_EXTENSION_ACTIVE_RECORDING_PID"
63
77
  }
64
78
  };
65
79
  async function resolveExtensionRecordingTarget(context) {
@@ -80,7 +94,7 @@ const extensionDiagnostics = {
80
94
  start: (projectRoot) => ensureExtensionConsoleCapture(projectRoot),
81
95
  files: (projectRoot) => extensionCaptureFiles(projectRoot),
82
96
  cdpPort: () => extensionCdpPort(),
83
- verifyControl: (capture) => verifyExtensionConsoleControl(capture)
97
+ verifyControl: (capture) => verifyOwnedCollectorControl(capture, () => verifyExtensionConsoleControl(capture))
84
98
  }
85
99
  };
86
100
  const extensionObservation = {
@@ -1,3 +1,4 @@
1
+ import { recipeHarnessPath, recipeRuntimeDir } from "@farmslot/recipe-cli/harness";
1
2
  import { execFileSync } from "node:child_process";
2
3
  import crypto from "node:crypto";
3
4
  import fs from "node:fs";
@@ -7,7 +8,7 @@ import {
7
8
  depsCheck,
8
9
  INSTALL_MARKERS
9
10
  } from "@farmslot/recipe-runner/runtime/deps-readiness";
10
- import { recipeHarnessPath, recipeRuntimeDir, recipeWatchLogCandidates } from "../../paths.js";
11
+ import { recipeWatchLogCandidates } from "../../paths.js";
11
12
  const WEBPACK_DIRECT_INPUTS = ["package.json", "yarn.lock", ".yarnrc.yml", ".tool-versions"];
12
13
  const WEBPACK_RECURSIVE_INPUTS = ["development/webpack"];
13
14
  const WEBPACK_CACHE_DIR = "node_modules/.cache/webpack";
@@ -1,8 +1,9 @@
1
+ import { recipeHarnessPath, recipeRuntimeDir } from "@farmslot/recipe-cli/harness";
1
2
  import { spawn } from "node:child_process";
2
3
  import fs from "node:fs";
3
4
  import { createRequire } from "node:module";
4
5
  import path from "node:path";
5
- import { importRecipeHarnessRuntimeBrowserExtension, importRecipeHarnessRuntimeCdp, recipeHarnessPath, recipeRuntimeDir, resolveLocalProtocolRoot, runnerDir } from "../../paths.js";
6
+ import { importRecipeHarnessRuntimeBrowserExtension, importRecipeHarnessRuntimeCdp, resolveFarmslotPoolDir, runnerDir } from "../../paths.js";
6
7
  import { poolFilesForHost } from "../resolve-slot-ports.js";
7
8
  import { profileProcessPids } from "./validation-process-ownership.js";
8
9
  const require2 = createRequire(import.meta.url);
@@ -537,12 +538,15 @@ function resolveCdpPort(rawPort, slot) {
537
538
  return port;
538
539
  }
539
540
  function resolveExtensionSlot(projectRoot, requestedSlot) {
540
- const protocolRoot = resolveLocalProtocolRoot();
541
- if (!protocolRoot) {
542
- if (requestedSlot) throw new Error("Extension slot lookup requires a local host checkout when --slot is provided.");
541
+ const poolsDir = resolveFarmslotPoolDir();
542
+ if (!poolsDir) {
543
+ if (requestedSlot) {
544
+ throw new Error(
545
+ "Extension slot lookup needs the farm's pool/*.json when --slot is provided. Run it from the Farmslot checkout that holds pool/ or from a directory beside that checkout."
546
+ );
547
+ }
543
548
  return null;
544
549
  }
545
- const poolsDir = path.join(protocolRoot, "pool");
546
550
  if (!fs.existsSync(poolsDir)) return null;
547
551
  const pools = poolFilesForHost(poolsDir);
548
552
  for (const { data } of [...pools.local, ...pools.other]) {
@@ -1,8 +1,8 @@
1
+ import { recipeRuntimePath, shellQuote } from "@farmslot/recipe-cli/harness";
1
2
  import { spawnSync } from "node:child_process";
2
3
  import path from "node:path";
3
4
  import { defineAdapter } from "@farmslot/adapter-sdk";
4
- import { recipeRuntimePath, runnerDir } from "../../paths.js";
5
- import { shellQuote } from "../../commands/parse-args.js";
5
+ import { runnerDir } from "../../paths.js";
6
6
  import {
7
7
  isExtensionWatcherLive,
8
8
  resolveExtensionSlotPorts,
@@ -15,6 +15,7 @@ import { extensionActions } from "./actions.js";
15
15
  import { extensionHarness, extensionPrepare, extensionRuntimeContext } from "./overlay.js";
16
16
  import { extensionReadiness, extensionRuntimeDecision } from "./readiness.js";
17
17
  import { extensionDiagnostics, extensionObservation, extensionRecording, extensionRun } from "./run.js";
18
+ import { checkoutHas, checkoutHasDir } from "../checkout-files.js";
18
19
  function watcherStatus(buildLog) {
19
20
  if (buildLog === "ok") return "up";
20
21
  if (buildLog === "no-watch") return "down";
@@ -102,6 +103,18 @@ const extensionSurface = defineAdapter({
102
103
  appLogSource(target) {
103
104
  return { label: "extension", path: recipeRuntimePath(target, "extension-console.log") };
104
105
  },
106
+ detect: {
107
+ remote: (url) => url.includes("metamask-extension"),
108
+ files: (target) => checkoutHas(target, "development/skills-sync.ts") || checkoutHasDir(target, "ui") && checkoutHasDir(target, "app/scripts")
109
+ },
110
+ flags: {
111
+ launch: ["build", "watch", "verify", "sidepanel", "fullscreen"],
112
+ commands: ["launchExistingDist", "startWatch", "buildLavamoat"]
113
+ },
114
+ failurePatterns: {
115
+ walletState: /fixture|keyring|not seeded|\bsrp\b|password|onboard/iu,
116
+ transport: /cdp|chrome|port\b|econnrefused|not reachable|watcher|websocket/iu
117
+ },
105
118
  hints: {
106
119
  launch: "mm-harness launch",
107
120
  relaunch: "mm-harness launch --build",
@@ -1,10 +1,10 @@
1
+ import { recipeHarnessPath, recipeHarnessRoot, recipeRuntimeDir, readRuntimeContextField, resolveRuntimeContextPath } from "@farmslot/recipe-cli/harness";
1
2
  import { execFileSync, spawnSync } from "node:child_process";
2
3
  import crypto from "node:crypto";
3
4
  import fs from "node:fs";
4
5
  import http from "node:http";
5
6
  import path from "node:path";
6
- import { readRuntimeContextField, resolveRuntimeContextPath } from "../../harness.js";
7
- import { recipeHarnessPath, recipeHarnessRoot, recipeRuntimeDir, runnerDir } from "../../paths.js";
7
+ import { runnerDir } from "../../paths.js";
8
8
  import { resolveExtensionSlotPorts } from "../slot-ports.js";
9
9
  const USAGE = "Usage: mm-harness verify --adapter extension -- [--target <metamask-extension>] [--out <recipes-dir>] [--cdp-port <port>] [--static-only] [--artifacts-dir <dir>]";
10
10
  const EXTENSION_ID_PATTERN = /^[a-p]{32}$/u;
@@ -1,8 +1,9 @@
1
+ import { recipeHarnessPath } from "@farmslot/recipe-cli/harness";
1
2
  import { execFileSync } from "node:child_process";
2
3
  import fs from "node:fs";
3
4
  import path from "node:path";
4
5
  import { pathToFileURL } from "node:url";
5
- import { recipeHarnessPath, runnerDir } from "../paths.js";
6
+ import { runnerDir } from "../paths.js";
6
7
  import { getAdapterSurface } from "./surface.js";
7
8
  async function ensureHarnessFresh(target, adapter) {
8
9
  if (!fs.existsSync(path.join(target, "package.json")) || !hasCheckoutHead(target)) return;
@@ -1,8 +1,6 @@
1
1
  import { execFile } from "node:child_process";
2
2
  import { promisify } from "node:util";
3
- import {
4
- summarizeFrames
5
- } from "./frame-metrics.js";
3
+ import { summarizeFrames } from "@farmslot/adapter-rn";
6
4
  const execFileAsync = promisify(execFile);
7
5
  const POLL_INTERVAL_MS = 750;
8
6
  const MAX_FRAMES = 1e5;
@@ -0,0 +1,10 @@
1
+ function mobileAppIdentityEnv(env = {}) {
2
+ return {
3
+ ANDROID_PACKAGE_ID: env.ANDROID_PACKAGE_ID ?? process.env.ANDROID_PACKAGE_ID ?? "io.metamask",
4
+ IOS_BUNDLE_ID: env.IOS_BUNDLE_ID ?? process.env.IOS_BUNDLE_ID ?? "io.metamask.MetaMask",
5
+ METAMASK_RECIPE_MOBILE_APP_LABEL: env.METAMASK_RECIPE_MOBILE_APP_LABEL ?? process.env.METAMASK_RECIPE_MOBILE_APP_LABEL ?? "MetaMask"
6
+ };
7
+ }
8
+ export {
9
+ mobileAppIdentityEnv
10
+ };
@@ -3,7 +3,7 @@ import path from "node:path";
3
3
  import {
4
4
  mobileToolRecovery,
5
5
  resolveMobileToolPath
6
- } from "../../../library/actions/mobile/platform/tool-paths.mjs";
6
+ } from "@farmslot/adapter-rn";
7
7
  import { runnerDir } from "../../paths.js";
8
8
  function mobileToolDoctorChecks(platform) {
9
9
  const specs = platform === "android" ? [{ tool: "adb", platform: "Android" }] : platform === "ios" ? [{ tool: "idb", platform: "iOS" }] : [
@@ -1,5 +1,5 @@
1
+ import { EXIT, spawnScriptStreaming } from "@farmslot/recipe-cli/harness";
1
2
  import path from "node:path";
2
- import { EXIT, spawnScriptStreaming } from "../../commands/shared.js";
3
3
  import { runnerDir } from "../../paths.js";
4
4
  import { FIXTURE_STREAM_TIMEOUT_MS } from "../../commands/fixtures.js";
5
5
  const FIXTURE_BRIDGE_READY_TIMEOUT_MS = 18e4;
@@ -1,10 +1,9 @@
1
1
  import fs from "node:fs";
2
+ import { recipeRuntimePath, EXIT, str, writeInteractiveProgress } from "@farmslot/recipe-cli/harness";
2
3
  import { applyDeviceTargeting } from "../../commands/device-target.js";
3
4
  import { launchMobile } from "../../commands/launch/mobile.js";
4
5
  import { shellQuote } from "../../commands/launch/quote.js";
5
- import { EXIT, str, writeInteractiveProgress } from "../../commands/shared.js";
6
- import { RECOVERY_CODE } from "../../heal-bounds.js";
7
- import { recipeRuntimePath } from "../../paths.js";
6
+ const METRO_RESTARTED = "metro.restarted";
8
7
  function mobileProvisioningBlocked(output) {
9
8
  return /open-device: configured iOS simulator '.+' does not exist|open-device: no MetaMask bundle found|fast mode requires an installed (?:iOS dev client|Android dev client)/u.test(output);
10
9
  }
@@ -17,8 +16,8 @@ function ambiguousConfiguredIosSimulator(output) {
17
16
  function mobileBridgeTargetMissing(output) {
18
17
  return /no bridge target matched|wait-for-bridge: readiness deadline expired/iu.test(output);
19
18
  }
20
- function mobileProvisionCommand(target, mobileTarget) {
21
- const platform = mobileTarget === "android" ? "android" : "ios";
19
+ function mobileProvisionCommand(target, platformTarget) {
20
+ const platform = platformTarget === "android" ? "android" : "ios";
22
21
  const device = platform === "ios" ? process.env.SIM_UDID || process.env.IOS_SIMULATOR : process.env.ADB_SERIAL || process.env.ANDROID_SERIAL || process.env.ANDROID_DEVICE;
23
22
  const parts = ["mm-harness", "provision", "runway", platform, "--adapter", "mobile", "--target", shellQuote(target)];
24
23
  if (device) parts.push("--device", shellQuote(device));
@@ -27,11 +26,11 @@ function mobileProvisionCommand(target, mobileTarget) {
27
26
  function isIosSimulatorUdid(value) {
28
27
  return /^[0-9A-Fa-f-]{36}$/u.test(value);
29
28
  }
30
- function launchDeviceLabel(mobileTarget) {
31
- if (mobileTarget === "android") {
29
+ function launchDeviceLabel(platformTarget) {
30
+ if (platformTarget === "android") {
32
31
  return process.env.ANDROID_TARGET_DEVICE_NAME || process.env.ADB_SERIAL || process.env.ANDROID_SERIAL || process.env.ANDROID_DEVICE || "android device";
33
32
  }
34
- if (mobileTarget === "ios") {
33
+ if (platformTarget === "ios") {
35
34
  return process.env.IOS_SIMULATOR || process.env.SIM_UDID || "iOS simulator";
36
35
  }
37
36
  return process.env.IOS_SIMULATOR || process.env.ADB_SERIAL || "booted device";
@@ -51,22 +50,22 @@ function metroLaunchEvidence(target) {
51
50
  }
52
51
  }
53
52
  const mobileLaunchProfile = {
54
- refuseFlags({ mobileTarget }) {
55
- if (!mobileTarget) {
53
+ refuseFlags({ platformTarget }) {
54
+ if (!platformTarget) {
56
55
  return { message: "target is required for mobile.", userAction: "mm-harness launch ios or mm-harness launch android" };
57
56
  }
58
57
  return void 0;
59
58
  },
60
- applyEnv({ adapter, options, mobileTarget, target }) {
59
+ applyEnv({ adapter, options, platformTarget, target }) {
61
60
  const device = str(options, "device");
62
- if (!device && mobileTarget !== "ios" && mobileTarget !== "android") return void 0;
61
+ if (!device && platformTarget !== "ios" && platformTarget !== "android") return void 0;
63
62
  const targetingOptions = {
64
63
  ...options,
65
- ...mobileTarget ? { platform: mobileTarget } : {}
64
+ ...platformTarget ? { platform: platformTarget } : {}
66
65
  };
67
66
  const result = applyDeviceTargeting("launch", adapter, targetingOptions, { gate: false, rerun: "" });
68
67
  if ("code" in result) {
69
- if (mobileTarget === "ios" && result.code === "DEVICE_NOT_FOUND") {
68
+ if (platformTarget === "ios" && result.code === "DEVICE_NOT_FOUND") {
70
69
  process.env.IOS_SIMULATOR = device;
71
70
  process.env.SIM_UDID = isIosSimulatorUdid(device) ? device : "";
72
71
  process.env.ADB_SERIAL = "";
@@ -77,7 +76,7 @@ const mobileLaunchProfile = {
77
76
  return result;
78
77
  }
79
78
  }
80
- if (mobileTarget === "android" && device && !process.env.ADB_SERIAL && !process.env.ANDROID_SERIAL) {
79
+ if (platformTarget === "android" && device && !process.env.ADB_SERIAL && !process.env.ANDROID_SERIAL) {
81
80
  return {
82
81
  ok: false,
83
82
  code: "DEVICE_WRONG_PLATFORM",
@@ -85,7 +84,7 @@ const mobileLaunchProfile = {
85
84
  userAction: `mm-harness status --target ${shellQuote(target)} --all-devices --json`
86
85
  };
87
86
  }
88
- if (mobileTarget === "ios" && device && !process.env.IOS_SIMULATOR) {
87
+ if (platformTarget === "ios" && device && !process.env.IOS_SIMULATOR) {
89
88
  return {
90
89
  ok: false,
91
90
  code: "DEVICE_WRONG_PLATFORM",
@@ -95,17 +94,17 @@ const mobileLaunchProfile = {
95
94
  }
96
95
  return void 0;
97
96
  },
98
- start({ options, mobileTarget, machine, tier, wantClearMetro }) {
99
- const device = str(options, "device") ?? (mobileTarget === "android" ? process.env.ADB_SERIAL ?? process.env.ANDROID_SERIAL : process.env.IOS_SIMULATOR ?? process.env.SIM_UDID) ?? "configured device";
97
+ start({ options, platformTarget, machine, tier, wantClearMetro }) {
98
+ const device = str(options, "device") ?? (platformTarget === "android" ? process.env.ADB_SERIAL ?? process.env.ANDROID_SERIAL : process.env.IOS_SIMULATOR ?? process.env.SIM_UDID) ?? "configured device";
100
99
  const watcherPort = str(options, "watcherPort") ?? process.env.WATCHER_PORT ?? "auto";
101
100
  writeInteractiveProgress(
102
101
  machine,
103
- `\u2192 mobile launch \u2014 ${mobileTarget} \xB7 ${tier === "build" ? "native build" : "quick readiness"} \xB7 ${device} \xB7 Metro ${watcherPort === "auto" ? "auto" : `:${watcherPort}`}${wantClearMetro ? " \xB7 clear cache" : ""}`
102
+ `\u2192 mobile launch \u2014 ${platformTarget} \xB7 ${tier === "build" ? "native build" : "quick readiness"} \xB7 ${device} \xB7 Metro ${watcherPort === "auto" ? "auto" : `:${watcherPort}`}${wantClearMetro ? " \xB7 clear cache" : ""}`
104
103
  );
105
104
  },
106
105
  nativeInputs: ["ios", "android", "package.json", "yarn.lock"],
107
- rebuildCommand: ({ mobileTarget }) => `mm-harness launch ${mobileTarget} --build`,
108
- compose: ({ target, mobileTarget, machine, remoteFlagRequest }, tier, restartApp, clearMetro) => launchMobile(target, mobileTarget, tier, machine, restartApp, clearMetro, remoteFlagRequest?.flags),
106
+ rebuildCommand: ({ platformTarget }) => `mm-harness launch ${platformTarget} --build`,
107
+ compose: ({ target, platformTarget, machine, remoteFlagRequest }, tier, restartApp, clearMetro) => launchMobile(target, platformTarget, tier, machine, restartApp, clearMetro, remoteFlagRequest?.flags),
109
108
  classify(_launch, output) {
110
109
  if (output.includes("open-device: Android device is locked.")) {
111
110
  return {
@@ -130,7 +129,7 @@ const mobileLaunchProfile = {
130
129
  return void 0;
131
130
  },
132
131
  async beforeHeal(launch, attempt, state) {
133
- const { target, mobileTarget, tier } = launch;
132
+ const { target, platformTarget, tier } = launch;
134
133
  const output = attempt.output;
135
134
  const missingIosSimulator = missingConfiguredIosSimulator(output);
136
135
  const ambiguousIosSimulator = ambiguousConfiguredIosSimulator(output);
@@ -157,9 +156,9 @@ const mobileLaunchProfile = {
157
156
  if (mobileProvisioningBlocked(output)) {
158
157
  return launch.fail(state, {
159
158
  code: "MOBILE_PROVISION_REQUIRED",
160
- message: `mobile ${mobileTarget ?? "runtime"} is not provisioned for this slot.`,
159
+ message: `mobile ${platformTarget ?? "runtime"} is not provisioned for this slot.`,
161
160
  recoverable: false,
162
- userAction: mobileProvisionCommand(target, mobileTarget),
161
+ userAction: mobileProvisionCommand(target, platformTarget),
163
162
  exitCode: EXIT.runtime,
164
163
  originalError: output.trim() || void 0
165
164
  });
@@ -177,14 +176,14 @@ const mobileLaunchProfile = {
177
176
  },
178
177
  recovery(_launch, output) {
179
178
  const restartApp = mobileBridgeTargetMissing(output);
180
- return { code: restartApp ? "mobile.app-restarted" : RECOVERY_CODE.mobile, restartApp };
179
+ return { code: restartApp ? "mobile.app-restarted" : METRO_RESTARTED, restartApp };
181
180
  },
182
181
  // Mobile proved its set inside launchMobile (null: nothing requested and no
183
182
  // runtime answered).
184
183
  provenFlags: (_launch, requested, proven) => ({ flags: proven === void 0 ? requested : proven }),
185
- verifyArgs: ({ mobileTarget }) => mobileTarget ? ["--platform", mobileTarget] : [],
186
- platformLabel: ({ mobileTarget }) => mobileTarget ?? null,
187
- deviceLabel: ({ mobileTarget }) => launchDeviceLabel(mobileTarget),
184
+ verifyArgs: ({ platformTarget }) => platformTarget ? ["--platform", platformTarget] : [],
185
+ platformLabel: ({ platformTarget }) => platformTarget ?? null,
186
+ deviceLabel: ({ platformTarget }) => launchDeviceLabel(platformTarget),
188
187
  evidence: metroLaunchEvidence
189
188
  };
190
189
  export {
@@ -3,7 +3,7 @@ import {
3
3
  bridgeCommand,
4
4
  MOBILE_BRIDGE_ERROR_CODES
5
5
  } from "../../../library/actions/mobile/platform/bridge.mjs";
6
- import { resolveMobileToolPath } from "../../../library/actions/mobile/platform/tool-paths.mjs";
6
+ import { resolveMobileToolPath } from "@farmslot/adapter-rn";
7
7
  import {
8
8
  MOBILE_LIFECYCLE_DEADLINE_FIELD,
9
9
  MOBILE_LIFECYCLE_PREPARED
@@ -1,68 +1,18 @@
1
- import { createHash } from "node:crypto";
2
- import fs from "node:fs";
3
- import path from "node:path";
4
- import { recipeRuntimePath } from "../../paths.js";
5
- const ENV_INPUTS = [".js.env", ".env", ".env.local"];
6
- const PROCESS_ENV_INPUTS = [
7
- "SEGMENT_PROXY_URL",
8
- "SEGMENT_WRITE_KEY",
9
- "SEGMENT_FLUSH_INTERVAL",
10
- "SEGMENT_FLUSH_EVENT_LIMIT"
11
- ];
1
+ import { metroEnvCheck, metroEnvFingerprint, recordMetroEnvBaseline } from "@farmslot/adapter-rn";
2
+ import { recipeRuntimePath } from "@farmslot/recipe-cli/harness";
3
+ const MOBILE_METRO_ENV_INPUTS = {
4
+ files: [".js.env", ".env", ".env.local"],
5
+ env: ["SEGMENT_PROXY_URL", "SEGMENT_WRITE_KEY", "SEGMENT_FLUSH_INTERVAL", "SEGMENT_FLUSH_EVENT_LIMIT"]
6
+ };
12
7
  const BASELINE_FILE = "metro-env-baseline.json";
13
8
  function mobileMetroEnvFingerprint(target) {
14
- const hash = createHash("sha256");
15
- for (const relative of ENV_INPUTS) {
16
- const absolute = path.join(target, relative);
17
- hash.update(`${relative}\0`);
18
- if (fs.existsSync(absolute)) hash.update(fs.readFileSync(absolute));
19
- else hash.update("<absent>");
20
- hash.update("\0");
21
- }
22
- for (const name of PROCESS_ENV_INPUTS) {
23
- hash.update(`${name}\0${process.env[name] ?? "<absent>"}\0`);
24
- }
25
- return hash.digest("hex");
9
+ return metroEnvFingerprint(target, MOBILE_METRO_ENV_INPUTS);
26
10
  }
27
11
  function mobileMetroEnvCheck(target) {
28
- const fingerprint = mobileMetroEnvFingerprint(target);
29
- const marker = readBaseline(target);
30
- return {
31
- fingerprint,
32
- status: marker === null ? "missing" : marker.fingerprint === fingerprint ? "current" : "changed"
33
- };
12
+ return metroEnvCheck(target, MOBILE_METRO_ENV_INPUTS, recipeRuntimePath(target, BASELINE_FILE));
34
13
  }
35
14
  function recordMobileMetroEnvBaseline(target, expectedFingerprint) {
36
- if (mobileMetroEnvFingerprint(target) !== expectedFingerprint) return false;
37
- const marker = recipeRuntimePath(target, BASELINE_FILE);
38
- const temporary = `${marker}.${process.pid}.${Date.now()}.tmp`;
39
- const baseline = {
40
- schemaVersion: 1,
41
- fingerprint: expectedFingerprint,
42
- recordedAt: (/* @__PURE__ */ new Date()).toISOString()
43
- };
44
- fs.mkdirSync(path.dirname(marker), { recursive: true });
45
- try {
46
- fs.writeFileSync(temporary, `${JSON.stringify(baseline, null, 2)}
47
- `, { mode: 384 });
48
- if (mobileMetroEnvFingerprint(target) !== expectedFingerprint) return false;
49
- fs.renameSync(temporary, marker);
50
- if (mobileMetroEnvFingerprint(target) !== expectedFingerprint) {
51
- if (readBaseline(target)?.fingerprint === expectedFingerprint) fs.rmSync(marker, { force: true });
52
- return false;
53
- }
54
- } finally {
55
- fs.rmSync(temporary, { force: true });
56
- }
57
- return true;
58
- }
59
- function readBaseline(target) {
60
- try {
61
- const parsed = JSON.parse(fs.readFileSync(recipeRuntimePath(target, BASELINE_FILE), "utf8"));
62
- return parsed.schemaVersion === 1 && typeof parsed.fingerprint === "string" ? parsed : null;
63
- } catch {
64
- return null;
65
- }
15
+ return recordMetroEnvBaseline(target, MOBILE_METRO_ENV_INPUTS, recipeRuntimePath(target, BASELINE_FILE), expectedFingerprint);
66
16
  }
67
17
  export {
68
18
  mobileMetroEnvCheck,
@@ -37,7 +37,7 @@ async function markRuntime(client, name) {
37
37
  }
38
38
  function resolvePlatform(env) {
39
39
  const explicit = String(
40
- env.MM_HARNESS_EXPLICIT_PLATFORM ?? env.RECIPE_HARNESS_PLATFORM ?? ""
40
+ env.RECIPE_RN_EXPLICIT_PLATFORM ?? env.RECIPE_HARNESS_PLATFORM ?? ""
41
41
  ).toLowerCase();
42
42
  if (explicit === "android" || explicit === "ios") return explicit;
43
43
  return env.ADB_SERIAL || env.ANDROID_SERIAL || env.ANDROID_DEVICE ? "android" : "ios";