@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.
- package/CHANGELOG.md +39 -1
- package/README.md +1 -1
- package/adapters/shared/ensure-runner-deps.sh +15 -3
- package/adapters/shared/reap-checkout-metros.sh +9 -0
- package/dist/action-values.js +15 -0
- package/dist/adapters/core/actions.js +20 -0
- package/dist/adapters/core/live-script-env.js +36 -0
- package/dist/adapters/core/overlay.js +34 -0
- package/dist/adapters/core/readiness.js +8 -0
- package/dist/adapters/core/run.js +54 -0
- package/dist/adapters/core/surface.js +12 -1
- package/dist/adapters/extension/action-inputs.js +206 -0
- package/dist/adapters/extension/actions.js +51 -0
- package/dist/adapters/extension/console-capture.js +13 -1
- package/dist/adapters/extension/doctor-checks.js +135 -0
- package/dist/adapters/extension/fixtures.js +103 -0
- package/dist/adapters/extension/launch.js +315 -0
- package/dist/adapters/extension/launched-browser.js +51 -0
- package/dist/adapters/extension/overlay.js +55 -0
- package/dist/adapters/extension/readiness.js +32 -0
- package/dist/adapters/extension/reload.js +40 -0
- package/dist/adapters/extension/run.js +105 -0
- package/dist/adapters/extension/surface.js +31 -3
- package/dist/adapters/harness-freshness.js +4 -2
- package/dist/adapters/mobile/actions.js +54 -0
- package/dist/adapters/mobile/devices.js +21 -0
- package/dist/adapters/mobile/doctor-checks.js +40 -0
- package/dist/adapters/mobile/fixtures.js +141 -0
- package/dist/adapters/mobile/launch.js +192 -0
- package/dist/adapters/mobile/lifecycle.js +300 -0
- package/dist/adapters/mobile/overlay.js +27 -0
- package/dist/adapters/mobile/prepare-target.js +38 -0
- package/dist/adapters/mobile/readiness.js +39 -0
- package/dist/adapters/mobile/reload.js +15 -0
- package/dist/adapters/mobile/run.js +156 -0
- package/dist/adapters/mobile/runtime-identity.js +34 -0
- package/dist/adapters/mobile/surface.js +39 -3
- package/dist/adapters/mobile/ui.js +701 -0
- package/dist/adapters/performance/cdp-trace.js +24 -6
- package/dist/adapters/performance/device-platform.js +10 -0
- package/dist/adapters/registry.js +1 -1
- package/dist/adapters/terminal/actions.js +43 -0
- package/dist/adapters/terminal/console-capture.js +14 -1
- package/dist/adapters/terminal/doctor-checks.js +22 -0
- package/dist/adapters/terminal/overlay.js +18 -0
- package/dist/adapters/terminal/readiness.js +11 -0
- package/dist/adapters/terminal/run.js +30 -0
- package/dist/adapters/terminal/surface.js +21 -3
- package/dist/adapters.js +32 -824
- package/dist/cli-commands.js +1 -0
- package/dist/cli.js +3 -1
- package/dist/command-contract.js +3 -0
- package/dist/commands/call.js +2 -5
- package/dist/commands/debug.js +6 -25
- package/dist/commands/device-target.js +2 -1
- package/dist/commands/doctor.js +26 -137
- package/dist/commands/fixtures.js +13 -217
- package/dist/commands/launch/device.js +2 -6
- package/dist/commands/launch/extension.js +3 -303
- package/dist/commands/launch/index.js +1 -1
- package/dist/commands/launch/mobile.js +2 -190
- package/dist/commands/launch/quote.js +7 -0
- package/dist/commands/logs.js +1 -1
- package/dist/commands/parse-args.js +9 -4
- package/dist/commands/pin.js +226 -0
- package/dist/commands/prepare.js +23 -53
- package/dist/commands/provision.js +3 -7
- package/dist/commands/reload.js +8 -47
- package/dist/commands/run-engine.js +38 -138
- package/dist/commands/run-report.js +4 -48
- package/dist/commands/run.js +6 -48
- package/dist/commands/runtime-decision.js +5 -27
- package/dist/commands/self-test.js +1 -3
- package/dist/commands/status.js +20 -28
- package/dist/commands/stop.js +1 -1
- package/dist/doctor.js +12 -120
- package/dist/execution-provenance.js +3 -2
- package/dist/harness-pin.js +10 -0
- package/dist/harness.js +31 -100
- package/dist/heal-bounds.js +3 -2
- package/dist/live-adapter-contract.js +8 -34
- package/dist/manifest.js +3 -5
- package/dist/metamask-action-validation.js +5 -196
- package/dist/mm-harness-cli.js +30 -4
- package/dist/network-observation.js +10 -3
- package/dist/paths.js +8 -2
- package/dist/performance-observation.js +15 -22
- package/dist/pr-body/render.js +3 -1
- package/dist/recording-target.js +4 -39
- package/dist/run-diagnostics.js +21 -60
- package/dist/run-recording.js +5 -4
- package/dist/runner.js +20 -368
- package/dist/runtime-context.js +12 -17
- package/docs/CONTRIBUTING.md +29 -7
- package/package.json +2 -1
- 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("
|
|
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 {
|