@deeeed/metamask-harness 0.74.0 → 0.76.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 (96) hide show
  1. package/CHANGELOG.md +39 -1
  2. package/README.md +1 -1
  3. package/adapters/shared/ensure-runner-deps.sh +15 -3
  4. package/adapters/shared/reap-checkout-metros.sh +9 -0
  5. package/dist/action-values.js +15 -0
  6. package/dist/adapters/core/actions.js +20 -0
  7. package/dist/adapters/core/live-script-env.js +36 -0
  8. package/dist/adapters/core/overlay.js +34 -0
  9. package/dist/adapters/core/readiness.js +8 -0
  10. package/dist/adapters/core/run.js +54 -0
  11. package/dist/adapters/core/surface.js +12 -1
  12. package/dist/adapters/extension/action-inputs.js +206 -0
  13. package/dist/adapters/extension/actions.js +51 -0
  14. package/dist/adapters/extension/console-capture.js +13 -1
  15. package/dist/adapters/extension/doctor-checks.js +135 -0
  16. package/dist/adapters/extension/fixtures.js +103 -0
  17. package/dist/adapters/extension/launch.js +315 -0
  18. package/dist/adapters/extension/launched-browser.js +51 -0
  19. package/dist/adapters/extension/overlay.js +55 -0
  20. package/dist/adapters/extension/readiness.js +32 -0
  21. package/dist/adapters/extension/reload.js +40 -0
  22. package/dist/adapters/extension/run.js +105 -0
  23. package/dist/adapters/extension/surface.js +31 -3
  24. package/dist/adapters/harness-freshness.js +4 -2
  25. package/dist/adapters/mobile/actions.js +54 -0
  26. package/dist/adapters/mobile/devices.js +21 -0
  27. package/dist/adapters/mobile/doctor-checks.js +40 -0
  28. package/dist/adapters/mobile/fixtures.js +141 -0
  29. package/dist/adapters/mobile/launch.js +192 -0
  30. package/dist/adapters/mobile/lifecycle.js +300 -0
  31. package/dist/adapters/mobile/overlay.js +27 -0
  32. package/dist/adapters/mobile/prepare-target.js +38 -0
  33. package/dist/adapters/mobile/readiness.js +39 -0
  34. package/dist/adapters/mobile/reload.js +15 -0
  35. package/dist/adapters/mobile/run.js +156 -0
  36. package/dist/adapters/mobile/runtime-identity.js +34 -0
  37. package/dist/adapters/mobile/surface.js +39 -3
  38. package/dist/adapters/mobile/ui.js +701 -0
  39. package/dist/adapters/performance/cdp-trace.js +24 -6
  40. package/dist/adapters/performance/device-platform.js +10 -0
  41. package/dist/adapters/registry.js +1 -1
  42. package/dist/adapters/terminal/actions.js +43 -0
  43. package/dist/adapters/terminal/console-capture.js +14 -1
  44. package/dist/adapters/terminal/doctor-checks.js +22 -0
  45. package/dist/adapters/terminal/overlay.js +18 -0
  46. package/dist/adapters/terminal/readiness.js +11 -0
  47. package/dist/adapters/terminal/run.js +30 -0
  48. package/dist/adapters/terminal/surface.js +21 -3
  49. package/dist/adapters.js +32 -824
  50. package/dist/cli-commands.js +1 -0
  51. package/dist/cli.js +3 -1
  52. package/dist/command-contract.js +3 -0
  53. package/dist/commands/call.js +2 -5
  54. package/dist/commands/debug.js +6 -25
  55. package/dist/commands/device-target.js +2 -1
  56. package/dist/commands/doctor.js +26 -137
  57. package/dist/commands/fixtures.js +13 -217
  58. package/dist/commands/launch/device.js +2 -6
  59. package/dist/commands/launch/extension.js +3 -303
  60. package/dist/commands/launch/index.js +1 -1
  61. package/dist/commands/launch/mobile.js +2 -190
  62. package/dist/commands/launch/quote.js +7 -0
  63. package/dist/commands/logs.js +1 -1
  64. package/dist/commands/parse-args.js +9 -4
  65. package/dist/commands/pin.js +226 -0
  66. package/dist/commands/prepare.js +23 -53
  67. package/dist/commands/provision.js +3 -7
  68. package/dist/commands/reload.js +8 -47
  69. package/dist/commands/run-engine.js +38 -138
  70. package/dist/commands/run-report.js +4 -48
  71. package/dist/commands/run.js +6 -48
  72. package/dist/commands/runtime-decision.js +5 -27
  73. package/dist/commands/self-test.js +1 -3
  74. package/dist/commands/status.js +20 -28
  75. package/dist/commands/stop.js +1 -1
  76. package/dist/doctor.js +12 -120
  77. package/dist/execution-provenance.js +3 -2
  78. package/dist/harness-pin.js +10 -0
  79. package/dist/harness.js +31 -100
  80. package/dist/heal-bounds.js +3 -2
  81. package/dist/live-adapter-contract.js +8 -34
  82. package/dist/manifest.js +3 -5
  83. package/dist/metamask-action-validation.js +5 -196
  84. package/dist/mm-harness-cli.js +30 -4
  85. package/dist/network-observation.js +10 -3
  86. package/dist/paths.js +8 -2
  87. package/dist/performance-observation.js +15 -22
  88. package/dist/pr-body/render.js +3 -1
  89. package/dist/recording-target.js +4 -39
  90. package/dist/run-diagnostics.js +21 -60
  91. package/dist/run-recording.js +5 -4
  92. package/dist/runner.js +20 -368
  93. package/dist/runtime-context.js +12 -17
  94. package/docs/CONTRIBUTING.md +29 -7
  95. package/package.json +2 -1
  96. package/scripts/completions.sh +2 -1
@@ -18,6 +18,22 @@ const EXTENSION_TRACE_CATEGORIES = [
18
18
  "disabled-by-default-devtools.timeline.frame",
19
19
  "v8.execute"
20
20
  ];
21
+ const MOBILE_TRACE_KIND = {
22
+ categories: MOBILE_TRACE_CATEGORIES,
23
+ rendererScoped: false,
24
+ nativeSource: "react-native-cdp-frame-timings",
25
+ javascriptTasks: false
26
+ };
27
+ const TRACE_KINDS = {
28
+ extension: {
29
+ categories: EXTENSION_TRACE_CATEGORIES,
30
+ rendererScoped: true,
31
+ nativeSource: "chromium-cdp-frame-timings",
32
+ javascriptTasks: true
33
+ },
34
+ ios: MOBILE_TRACE_KIND,
35
+ android: MOBILE_TRACE_KIND
36
+ };
21
37
  function createCdpTraceCollector(client, platform, marker) {
22
38
  let active;
23
39
  const offData = client.on("Tracing.dataCollected", (params) => {
@@ -60,7 +76,7 @@ function createCdpTraceCollector(client, platform, marker) {
60
76
  await client.send(
61
77
  "Tracing.start",
62
78
  {
63
- categories: (platform === "extension" ? EXTENSION_TRACE_CATEGORIES : MOBILE_TRACE_CATEGORIES).join(","),
79
+ categories: TRACE_KINDS[platform].categories.join(","),
64
80
  transferMode: "ReportEvents"
65
81
  },
66
82
  15e3
@@ -126,7 +142,8 @@ function createCdpTraceCollector(client, platform, marker) {
126
142
  }
127
143
  function parseTraceCapture(capture) {
128
144
  const marker = findClockMarker(capture.events, capture.markerName);
129
- if (capture.platform === "extension" && !finite(marker?.pid)) {
145
+ const kind = TRACE_KINDS[capture.platform];
146
+ if (kind.rendererScoped && !finite(marker?.pid)) {
130
147
  const reason = "Extension trace marker did not identify a renderer process.";
131
148
  return {
132
149
  platform: capture.platform,
@@ -135,12 +152,12 @@ function parseTraceCapture(capture) {
135
152
  trace: traceEvidence(capture, "unresolved")
136
153
  };
137
154
  }
138
- const scopedEvents = capture.platform === "extension" && finite(marker?.pid) ? capture.events.filter((event) => event.pid === marker.pid) : capture.events;
139
- const scope = capture.platform !== "extension" ? "mobile-host" : finite(marker?.pid) ? "extension-renderer" : "unresolved";
155
+ const scopedEvents = kind.rendererScoped && finite(marker?.pid) ? capture.events.filter((event) => event.pid === marker.pid) : capture.events;
156
+ const scope = !kind.rendererScoped ? "mobile-host" : finite(marker?.pid) ? "extension-renderer" : "unresolved";
140
157
  const trace = traceEvidence(
141
158
  { ...capture, events: scopedEvents },
142
159
  scope,
143
- capture.platform === "extension" && finite(marker?.pid) ? marker.pid : void 0
160
+ kind.rendererScoped && finite(marker?.pid) ? marker.pid : void 0
144
161
  );
145
162
  if (!finite(marker?.ts) || capture.markerHostEpochMs === void 0 || capture.markerUncertaintyMs === void 0) {
146
163
  const reason = "CDP trace clock marker was unavailable.";
@@ -181,7 +198,7 @@ function parseTraceCapture(capture) {
181
198
  capture.markerUncertaintyMs
182
199
  ),
183
200
  nativeUi: nativeUiSourceResult(
184
- capture.platform === "extension" ? "chromium-cdp-frame-timings" : "react-native-cdp-frame-timings",
201
+ kind.nativeSource,
185
202
  nativeSamples,
186
203
  status,
187
204
  gapReasons,
@@ -383,6 +400,7 @@ function asRecord(value) {
383
400
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
384
401
  }
385
402
  export {
403
+ TRACE_KINDS,
386
404
  createCdpTraceCollector,
387
405
  parseTraceCapture
388
406
  };
@@ -0,0 +1,10 @@
1
+ function devicePlatformFromEnv(env) {
2
+ const explicit = String(
3
+ env.MM_HARNESS_EXPLICIT_PLATFORM ?? env.RECIPE_HARNESS_PLATFORM ?? ""
4
+ ).toLowerCase();
5
+ if (explicit === "android" || explicit === "ios") return explicit;
6
+ return env.ADB_SERIAL || env.ANDROID_SERIAL || env.ANDROID_DEVICE ? "android" : "ios";
7
+ }
8
+ export {
9
+ devicePlatformFromEnv
10
+ };
@@ -6,7 +6,7 @@ const adapterRegistry = {
6
6
  },
7
7
  get(id) {
8
8
  const surface = surfaces.get(id);
9
- if (!surface) throw new Error(`unknown adapter '${id}' (registered: ${[...surfaces.keys()].join(", ")})`);
9
+ if (!surface) throw new Error(`unknown adapter '${id}' (registered: ${adapterRegistry.list().join(", ")})`);
10
10
  return surface;
11
11
  },
12
12
  has: (id) => surfaces.has(id),
@@ -0,0 +1,43 @@
1
+ import { manifestPath } from "../../paths.js";
2
+ const terminalActions = {
3
+ manifestPath: () => manifestPath("terminal"),
4
+ semantic: [
5
+ "app.network_capture",
6
+ "app.network_assert",
7
+ "app.performance_capture",
8
+ "app.performance_assert"
9
+ ],
10
+ cdpTarget: { transport: "chrome-cdp", probePath: "json/version" },
11
+ ui: (harness, { hudPolicy }) => ({
12
+ base: harness.createCdpWebUiTransport({
13
+ async withPage(input, callback) {
14
+ const { withTerminalPage } = await import("../../../library/actions/terminal/platform/page.mjs");
15
+ return withTerminalPage(
16
+ {
17
+ action: input.action,
18
+ node: input.node,
19
+ context: {
20
+ nodeId: input.context.nodeId,
21
+ projectRoot: input.context.projectRoot,
22
+ artifactsDir: input.context.artifactsDir
23
+ }
24
+ },
25
+ callback
26
+ );
27
+ }
28
+ }),
29
+ async execute(action, node, context, standard) {
30
+ if (action === "app.hud") {
31
+ const { showTerminalHud } = await import("../../../library/actions/terminal/platform/hud.mjs");
32
+ return showTerminalHud(
33
+ { action, node, context: { nodeId: context.nodeId, projectRoot: context.projectRoot } },
34
+ { hudPolicy }
35
+ );
36
+ }
37
+ return standard();
38
+ }
39
+ })
40
+ };
41
+ export {
42
+ terminalActions
43
+ };
@@ -1,5 +1,6 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
+ import { verifyCapture } from "../../../library/actions/shared/console-collector.mjs";
3
4
  import { recipeRuntimePath } from "../../paths.js";
4
5
  import { ensureConsoleCollector } from "../console-capture.js";
5
6
  const TERMINAL_CONSOLE_FILES = Object.freeze({
@@ -37,10 +38,22 @@ async function ensureTerminalConsoleCapture(target) {
37
38
  cwd: target
38
39
  });
39
40
  }
41
+ async function verifyTerminalConsoleControl(capture) {
42
+ let browser = null;
43
+ try {
44
+ const value = JSON.parse(fs.readFileSync(terminalRuntimeFile(capture.projectRoot, "browser.json"), "utf8"));
45
+ browser = typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
46
+ } catch {
47
+ browser = null;
48
+ }
49
+ if (typeof browser?.appOrigin !== "string") return { ok: false, detail: "no slot browser state (browser.json) to verify the page console capture against" };
50
+ return verifyCapture({ cdpPort: capture.cdpPort, log: capture.pageLog, target: "page", appOrigin: browser.appOrigin });
51
+ }
40
52
  export {
41
53
  TERMINAL_CONSOLE_FILES,
42
54
  ensureTerminalConsoleCapture,
43
55
  terminalCaptureFiles,
44
56
  terminalCdpPort,
45
- terminalRuntimeFile
57
+ terminalRuntimeFile,
58
+ verifyTerminalConsoleControl
46
59
  };
@@ -0,0 +1,22 @@
1
+ import { terminalAppPort, terminalCdpPort } from "./surface.js";
2
+ async function terminalDoctorChecks(target) {
3
+ const { terminalReadiness } = await import("../../../adapters/terminal/lib/readiness.mjs");
4
+ const { resolveBrowser } = await import("../../../adapters/terminal/launch.mjs");
5
+ const report = await terminalReadiness({
6
+ target,
7
+ appPort: terminalAppPort(),
8
+ cdpPort: terminalCdpPort(),
9
+ signer: process.env.TERMINAL_SIGNER || "extension",
10
+ account: process.env.TERMINAL_ACCOUNT || "dev1",
11
+ probeBrowser: resolveBrowser
12
+ });
13
+ return report.checks.map((check) => ({
14
+ id: `terminal-${check.id}`,
15
+ status: check.status === "pass" ? "pass" : "fail",
16
+ required: check.required,
17
+ message: check.detail
18
+ }));
19
+ }
20
+ export {
21
+ terminalDoctorChecks
22
+ };
@@ -0,0 +1,18 @@
1
+ import path from "node:path";
2
+ import { runnerDir } from "../../paths.js";
3
+ const terminalHarness = {
4
+ install: { entry: "adapters/terminal/inject.mjs", fallback: "adapters/terminal/inject.mjs", node: true },
5
+ cleanup: { entry: "adapters/terminal/cleanup.mjs", fallback: "adapters/terminal/cleanup.mjs", node: true },
6
+ verify: () => ({ command: process.execPath, prefixArgs: [path.join(runnerDir, "adapters", "terminal", "verify.mjs")] }),
7
+ restart: "mm-harness launch"
8
+ };
9
+ const terminalRuntimeContext = {
10
+ forbiddenFields: ["metroPort", "simulator", "simulatorUdid", "adbSerial", "extensionId"],
11
+ resources: ({ existing, defaults, envPort }) => ({
12
+ cdpPort: envPort("RECIPE_CDP_PORT", "CDP_PORT") ?? existing.cdpPort ?? defaults.cdpPort
13
+ })
14
+ };
15
+ export {
16
+ terminalHarness,
17
+ terminalRuntimeContext
18
+ };
@@ -0,0 +1,11 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ const terminalReadiness = {
4
+ compatibilityMode: (target) => fs.existsSync(path.join(target, "src/features/perpetuals")) ? "slot browser over CDP (website; no product bridge)" : "unsupported/no bridge",
5
+ liveChecks: async (target) => (await import("./doctor-checks.js")).terminalDoctorChecks(target),
6
+ // Screenshots come from the slot browser over CDP.
7
+ captureProviders: ["cdp"]
8
+ };
9
+ export {
10
+ terminalReadiness
11
+ };
@@ -0,0 +1,30 @@
1
+ import { devicePlatformFromEnv } from "../performance/device-platform.js";
2
+ import {
3
+ ensureTerminalConsoleCapture,
4
+ terminalCaptureFiles,
5
+ terminalCdpPort,
6
+ terminalRuntimeFile,
7
+ verifyTerminalConsoleControl
8
+ } from "./console-capture.js";
9
+ const terminalDiagnostics = {
10
+ console: {
11
+ start: (projectRoot) => ensureTerminalConsoleCapture(projectRoot),
12
+ files: (projectRoot) => terminalCaptureFiles(projectRoot),
13
+ cdpPort: () => terminalCdpPort(),
14
+ verifyControl: (capture) => verifyTerminalConsoleControl(capture)
15
+ },
16
+ walletLog: (projectRoot) => terminalRuntimeFile(projectRoot, "wallet-requests.jsonl")
17
+ };
18
+ const terminalObservation = {
19
+ network: {
20
+ backend: async (_target, env, artifactsDir) => (await import("../../network-observation.js")).createExtensionNetworkBackend(env, artifactsDir)
21
+ },
22
+ performance: {
23
+ backend: async (target, env, ports) => (await import("../../performance-observation.js")).createExtensionPerformanceObserver(target, env, ports),
24
+ platform: (env) => devicePlatformFromEnv(env)
25
+ }
26
+ };
27
+ export {
28
+ terminalDiagnostics,
29
+ terminalObservation
30
+ };
@@ -4,6 +4,10 @@ import fs from "node:fs";
4
4
  import { TERMINAL_CONSOLE_FILES, terminalRuntimeFile } from "./console-capture.js";
5
5
  import { stopTerminalBrowserSync } from "./stop.js";
6
6
  import { adapterRegistry } from "../registry.js";
7
+ import { terminalActions } from "./actions.js";
8
+ import { terminalHarness, terminalRuntimeContext } from "./overlay.js";
9
+ import { terminalReadiness } from "./readiness.js";
10
+ import { terminalDiagnostics, terminalObservation } from "./run.js";
7
11
  function portFromEnv(...names) {
8
12
  for (const name of names) {
9
13
  const raw = process.env[name];
@@ -31,8 +35,8 @@ const terminalSurface = {
31
35
  resolveSlotPorts() {
32
36
  },
33
37
  async runtimeStatus(target) {
34
- const { terminalReadiness } = await import("../../../adapters/terminal/lib/readiness.mjs");
35
- const report = await terminalReadiness({
38
+ const { terminalReadiness: terminalReadiness2 } = await import("../../../adapters/terminal/lib/readiness.mjs");
39
+ const report = await terminalReadiness2({
36
40
  target: path.resolve(target),
37
41
  appPort: terminalAppPort(),
38
42
  cdpPort: terminalCdpPort(),
@@ -97,7 +101,21 @@ const terminalSurface = {
97
101
  relaunch: "mm-harness launch --adapter terminal --fresh-profile",
98
102
  runtimeProbeRecovery: (target) => `mm-harness doctor --adapter terminal --target ${shellQuote(path.resolve(target))}`
99
103
  },
100
- launch: async (context) => (await import("./launch.js")).launchTerminal(context)
104
+ launch: async (context) => (await import("./launch.js")).launchTerminal(context),
105
+ actions: terminalActions,
106
+ harness: terminalHarness,
107
+ runtimeContext: terminalRuntimeContext,
108
+ readiness: terminalReadiness,
109
+ fixtures: { operations: ["init", "sync", "set"] },
110
+ debug: {
111
+ args: ({ devMenu }) => [
112
+ ...process.env.WATCHER_PORT ? ["--port", process.env.WATCHER_PORT] : [],
113
+ "--action",
114
+ devMenu ? "dev-menu" : "debug"
115
+ ]
116
+ },
117
+ diagnostics: terminalDiagnostics,
118
+ observation: terminalObservation
101
119
  };
102
120
  adapterRegistry.register(terminalSurface);
103
121
  export {