@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
@@ -0,0 +1,156 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import path from "node:path";
3
+ import { isRecord } from "../../commands/parse-args.js";
4
+ import { runnerDir } from "../../paths.js";
5
+ import { devicePlatformFromEnv } from "../performance/device-platform.js";
6
+ import {
7
+ IOS_SIMULATOR_RECORDING_TARGET_PREFIX,
8
+ resolveAndroidDeviceSerial,
9
+ resolveMobileIosSimulatorName
10
+ } from "../../recording-target.js";
11
+ import { requireCurrentMobileReleaseArtifactState } from "./release-artifact-state.js";
12
+ const EXPLICIT_DEVICE_KEYS = [
13
+ "PLATFORM",
14
+ "MM_HARNESS_EXPLICIT_PLATFORM",
15
+ "IOS_SIMULATOR",
16
+ "SIM_UDID",
17
+ "ADB_SERIAL",
18
+ "ANDROID_SERIAL",
19
+ "ANDROID_DEVICE",
20
+ "ANDROID_TARGET_DEVICE_NAME"
21
+ ];
22
+ const mobileRun = {
23
+ pinnedEnv() {
24
+ if (!process.env.MM_HARNESS_EXPLICIT_PLATFORM) return void 0;
25
+ return Object.fromEntries(EXPLICIT_DEVICE_KEYS.map((key) => [key, process.env[key]]));
26
+ },
27
+ envKeys: ["METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME", "ANDROID_PACKAGE_ID", "ADB_SERIAL", "ANDROID_SERIAL"],
28
+ activateEnv(projectRoot) {
29
+ const state = requireCurrentMobileReleaseArtifactState(projectRoot);
30
+ if (!state) return;
31
+ const selected = process.env.ADB_SERIAL ?? process.env.ANDROID_SERIAL;
32
+ if (selected && selected !== state.deviceSerial) {
33
+ throw new Error(
34
+ `The Mobile release artifact is installed on ${state.deviceSerial}, not ${selected}. Next: rerun runtime-launch for ${selected}.`
35
+ );
36
+ }
37
+ process.env.METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME = "1";
38
+ process.env.ANDROID_PACKAGE_ID = state.packageId;
39
+ process.env.ADB_SERIAL = state.deviceSerial;
40
+ process.env.ANDROID_SERIAL = state.deviceSerial;
41
+ },
42
+ childEnv: (base) => ({
43
+ ...base,
44
+ WATCHER_PORT: process.env.WATCHER_PORT ?? base.CDP_PORT ?? base.RECIPE_CDP_PORT,
45
+ METRO_PORT: process.env.METRO_PORT ?? process.env.WATCHER_PORT ?? base.CDP_PORT ?? base.RECIPE_CDP_PORT,
46
+ IOS_SIMULATOR: process.env.IOS_SIMULATOR,
47
+ SIM_UDID: process.env.SIM_UDID,
48
+ ANDROID_DEVICE: process.env.ANDROID_DEVICE,
49
+ ANDROID_TARGET_DEVICE_NAME: process.env.ANDROID_TARGET_DEVICE_NAME,
50
+ ADB_SERIAL: process.env.ADB_SERIAL,
51
+ ANDROID_SERIAL: process.env.ANDROID_SERIAL,
52
+ ANDROID_PACKAGE_ID: process.env.ANDROID_PACKAGE_ID,
53
+ METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME: process.env.METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME
54
+ }),
55
+ // An installed release artifact is opaque: no harness HUD inside it.
56
+ autoHud: () => process.env.METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME !== "1",
57
+ // Always clear a HUD step the engine left on-device (a failed run strands
58
+ // the FAIL banner). Best-effort — never masks the run's real outcome.
59
+ async teardown(projectRoot, env) {
60
+ const { hideMobileHudOnTeardown } = await import("./ui.js");
61
+ await hideMobileHudOnTeardown(projectRoot, env);
62
+ },
63
+ // A release artifact is checked before the overlay install; the loaded
64
+ // source only matters for a development build the caller did not restart.
65
+ runtimeCheck({ target, heal, state, json, appRestartAuthored }) {
66
+ if (requireCurrentMobileReleaseArtifactState(target) !== null || appRestartAuthored) return void 0;
67
+ return async () => {
68
+ const { ensureMobileProofRuntime } = await import("../../commands/run-engine.js");
69
+ return ensureMobileProofRuntime(target, heal, state, json);
70
+ };
71
+ }
72
+ };
73
+ const mobileRecording = {
74
+ target: (context) => resolveMobileRecordingTarget(context),
75
+ // An Android device records through the mirror, with screenrecord as the
76
+ // fallback; otherwise the selected iOS simulator records.
77
+ async videoRecorder() {
78
+ const androidSerial = resolveAndroidDeviceSerial();
79
+ const iosSimulator = androidSerial ? void 0 : resolveMobileIosSimulatorName();
80
+ if (!androidSerial && !iosSimulator) return void 0;
81
+ const { createAndroidVideoRecorder, createIosSimulatorVideoRecorder } = await import("./video-recorder.js");
82
+ if (androidSerial) {
83
+ const { createAndroidMirrorVideoRecorder } = await import("@farmslot/recipe-harness");
84
+ return createAndroidMirrorVideoRecorder({ serial: androidSerial, fallback: createAndroidVideoRecorder(androidSerial) });
85
+ }
86
+ return createIosSimulatorVideoRecorder(iosSimulator);
87
+ }
88
+ };
89
+ async function resolveMobileRecordingTarget(context) {
90
+ const platform = (context.env.PLATFORM ?? process.env.PLATFORM ?? "ios").trim().toLowerCase();
91
+ const androidSerial = resolveAndroidDeviceSerial(context.env);
92
+ if (platform === "android" || androidSerial) {
93
+ if (!androidSerial) {
94
+ throw new Error(
95
+ "Could not resolve an Android device for --record-video. Set ADB_SERIAL or pass --device to mm-harness run."
96
+ );
97
+ }
98
+ return {
99
+ kind: "android-device",
100
+ serial: androidSerial
101
+ };
102
+ }
103
+ const simulator = resolveMobileIosSimulatorName(context.env);
104
+ if (!simulator) {
105
+ throw new Error(
106
+ "Could not resolve an iOS simulator name for --record-video. Set IOS_SIMULATOR or pass --simulator to the recipe hook."
107
+ );
108
+ }
109
+ return {
110
+ kind: "window-id",
111
+ windowId: `${IOS_SIMULATOR_RECORDING_TARGET_PREFIX}${simulator}`
112
+ };
113
+ }
114
+ const mobileDiagnostics = {
115
+ issueBuffer: {
116
+ arm(projectRoot) {
117
+ if (process.env.METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME === "1") return false;
118
+ if (!runMobileIssueCommand(projectRoot, "issues-arm")) return false;
119
+ return runMobileIssueCommand(projectRoot, "issues-collect") !== null;
120
+ },
121
+ collect(projectRoot) {
122
+ const result = runMobileIssueCommand(projectRoot, "issues-collect");
123
+ return isRecord(result) && Array.isArray(result.entries) ? result.entries : null;
124
+ }
125
+ }
126
+ };
127
+ const mobileObservation = {
128
+ network: {
129
+ backend: async (target, env) => (await import("../../network-observation.js")).createMobileNetworkBackend(target, env)
130
+ },
131
+ performance: {
132
+ backend: async (target, env) => (await import("./performance-observer.js")).createMobilePerformanceBackend(target, env),
133
+ platform: (env) => devicePlatformFromEnv(env)
134
+ }
135
+ };
136
+ function runMobileIssueCommand(projectRoot, command) {
137
+ const bridge = path.join(runnerDir, "adapters", "mobile", "bridge-runtime", "cdp-bridge.cjs");
138
+ const result = spawnSync(process.execPath, [bridge, command], {
139
+ cwd: projectRoot,
140
+ env: { ...process.env, APP_ROOT: projectRoot, CDP_TIMEOUT: "5000" },
141
+ encoding: "utf8",
142
+ timeout: 1e4
143
+ });
144
+ if (result.status !== 0) return null;
145
+ try {
146
+ return JSON.parse(result.stdout.trim());
147
+ } catch {
148
+ return null;
149
+ }
150
+ }
151
+ export {
152
+ mobileDiagnostics,
153
+ mobileObservation,
154
+ mobileRecording,
155
+ mobileRun
156
+ };
@@ -0,0 +1,34 @@
1
+ import { readAndroidNativeRuntimeIdentity } from "./release-artifact.js";
2
+ import { readMobileReleaseArtifactState } from "./release-artifact-state.js";
3
+ function withMobileNativeRuntimeIdentity(adapters, identity = {
4
+ state: readMobileReleaseArtifactState,
5
+ read: readAndroidNativeRuntimeIdentity
6
+ }) {
7
+ return adapters.map((entry) => ({
8
+ ...entry,
9
+ async execute(node, context) {
10
+ const state = identity.state(context.projectRoot);
11
+ if (!state) {
12
+ if (context.env.METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME === "1") {
13
+ throw new Error("Mobile release-artifact actions require current trusted runtime state. Next: rerun runtime-launch with the exact artifact.");
14
+ }
15
+ return entry.execute(node, context);
16
+ }
17
+ identity.read(state);
18
+ const result = await entry.execute(node, context);
19
+ const nativeRuntimeIdentity = identity.read(state);
20
+ const output = result.output && typeof result.output === "object" && !Array.isArray(result.output) ? result.output : { result: result.output };
21
+ return {
22
+ ...result,
23
+ output: {
24
+ ...output,
25
+ nativeRuntimeIdentity,
26
+ runtimeSessionId: nativeRuntimeIdentity.runtimeSessionId
27
+ }
28
+ };
29
+ }
30
+ }));
31
+ }
32
+ export {
33
+ withMobileNativeRuntimeIdentity
34
+ };
@@ -6,6 +6,13 @@ import { resolveMobileSlotPorts } from "../slot-ports.js";
6
6
  import { mobileRuntimeStatus } from "./prepare.js";
7
7
  import { hasRunwayProvisionBaseline, provisionRunwayMobile } from "./provision.js";
8
8
  import { adapterRegistry } from "../registry.js";
9
+ import { writeInteractiveProgress } from "../../commands/shared.js";
10
+ import { mobileActions } from "./actions.js";
11
+ import { mobileHarness, mobilePrepare, mobileRuntimeContext } from "./overlay.js";
12
+ import { mobileReadiness, mobileRuntimeDecision } from "./readiness.js";
13
+ import { clearMobileReleaseArtifactState } from "./release-artifact-state.js";
14
+ import { mobileDiagnostics, mobileObservation, mobileRecording, mobileRun } from "./run.js";
15
+ import { mobileSourceFingerprint } from "./source-freshness.js";
9
16
  function mobileRuntimeNextAction(reasonCode, target, platform) {
10
17
  const resolved = shellQuote(path.resolve(target));
11
18
  return reasonCode === "deps-missing" || reasonCode === "deps-partial" || reasonCode === "deps-stale" ? `cd ${resolved} && yarn setup` : `mm-harness launch ${platform} --adapter mobile --target ${resolved}`;
@@ -34,7 +41,12 @@ const mobileSurface = {
34
41
  runwayProvision: {
35
42
  async run(target, options) {
36
43
  return provisionRunwayMobile(target, options);
37
- }
44
+ },
45
+ before(target, { json, platform, resolveOnly }) {
46
+ writeInteractiveProgress(json, `\u2192 provision mobile ${platform} \u2014 resolving simulator and Runway artifact`);
47
+ if (!resolveOnly) clearMobileReleaseArtifactState(target);
48
+ },
49
+ launchNext: true
38
50
  },
39
51
  devServer: {
40
52
  label: "metro",
@@ -64,10 +76,34 @@ const mobileSurface = {
64
76
  launch: async (context) => {
65
77
  const [{ runDeviceLaunch }, { mobileLaunchProfile }] = await Promise.all([
66
78
  import("../../commands/launch/device.js"),
67
- import("../../commands/launch/mobile.js")
79
+ import("./launch.js")
68
80
  ]);
69
81
  return runDeviceLaunch(context, mobileLaunchProfile);
70
- }
82
+ },
83
+ actions: mobileActions,
84
+ harness: mobileHarness,
85
+ runtimeContext: mobileRuntimeContext,
86
+ readiness: mobileReadiness,
87
+ fixtures: {
88
+ operations: ["init", "sync", "set", "reset"],
89
+ apply: async (context) => (await import("./fixtures.js")).applyMobileFixture(context)
90
+ },
91
+ prepare: mobilePrepare,
92
+ debug: {
93
+ refuse: ({ worker }) => worker ? { message: "--worker is extension-only (service-worker DevTools). Use --dev-menu on mobile.", userAction: "mm-harness debug --dev-menu" } : void 0,
94
+ args: ({ devMenu }) => [
95
+ ...process.env.WATCHER_PORT ? ["--port", process.env.WATCHER_PORT] : [],
96
+ "--action",
97
+ devMenu ? "dev-menu" : "debug"
98
+ ]
99
+ },
100
+ reload: async (target, json) => (await import("./reload.js")).reloadMobile(target, json),
101
+ runtimeDecision: mobileRuntimeDecision,
102
+ run: mobileRun,
103
+ recording: mobileRecording,
104
+ diagnostics: mobileDiagnostics,
105
+ observation: mobileObservation,
106
+ sourceFingerprint: (root) => mobileSourceFingerprint(root)
71
107
  };
72
108
  adapterRegistry.register(mobileSurface);
73
109
  export {