@deeeed/metamask-harness 0.73.0 → 0.75.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 +22 -0
- package/README.md +1 -1
- package/adapters/shared/ensure-runner-deps.sh +15 -3
- 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/run.js +54 -0
- package/dist/adapters/core/surface.js +8 -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/launch.js +315 -0
- package/dist/adapters/extension/launched-browser.js +51 -0
- package/dist/adapters/extension/run.js +105 -0
- package/dist/adapters/extension/surface.js +17 -1
- package/dist/adapters/mobile/actions.js +54 -0
- package/dist/adapters/mobile/launch.js +192 -0
- package/dist/adapters/mobile/lifecycle.js +300 -0
- package/dist/adapters/mobile/run.js +156 -0
- package/dist/adapters/mobile/runtime-identity.js +34 -0
- package/dist/adapters/mobile/surface.js +19 -1
- 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 +17 -0
- package/dist/adapters/surface.js +6 -11
- package/dist/adapters/terminal/actions.js +43 -0
- package/dist/adapters/terminal/console-capture.js +14 -1
- package/dist/adapters/terminal/launch.js +85 -0
- package/dist/adapters/terminal/run.js +30 -0
- package/dist/adapters/terminal/surface.js +9 -1
- package/dist/adapters.js +32 -824
- package/dist/cli-commands.js +1 -0
- package/dist/command-contract.js +3 -0
- package/dist/commands/call.js +2 -5
- package/dist/commands/launch/device.js +342 -0
- package/dist/commands/launch/index.js +9 -942
- package/dist/commands/launch/quote.js +7 -0
- package/dist/commands/pin.js +226 -0
- package/dist/commands/review.js +3 -1
- 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/shared.js +2 -2
- package/dist/doctor.js +3 -1
- package/dist/execution-provenance.js +3 -2
- package/dist/harness-pin.js +10 -0
- 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 +27 -2
- package/dist/network-observation.js +10 -3
- 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/docs/CONTRIBUTING.md +20 -3
- package/package.json +2 -1
- 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:
|
|
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
|
-
|
|
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 =
|
|
139
|
-
const scope =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const surfaces = /* @__PURE__ */ new Map();
|
|
2
|
+
const adapterRegistry = {
|
|
3
|
+
register(surface) {
|
|
4
|
+
if (surfaces.has(surface.adapter)) throw new Error(`adapter '${surface.adapter}' is already registered`);
|
|
5
|
+
surfaces.set(surface.adapter, surface);
|
|
6
|
+
},
|
|
7
|
+
get(id) {
|
|
8
|
+
const surface = surfaces.get(id);
|
|
9
|
+
if (!surface) throw new Error(`unknown adapter '${id}' (registered: ${adapterRegistry.list().join(", ")})`);
|
|
10
|
+
return surface;
|
|
11
|
+
},
|
|
12
|
+
has: (id) => surfaces.has(id),
|
|
13
|
+
list: () => [...surfaces.keys()]
|
|
14
|
+
};
|
|
15
|
+
export {
|
|
16
|
+
adapterRegistry
|
|
17
|
+
};
|
package/dist/adapters/surface.js
CHANGED
|
@@ -1,15 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
mobile: mobileSurface,
|
|
7
|
-
extension: extensionSurface,
|
|
8
|
-
core: coreSurface,
|
|
9
|
-
terminal: terminalSurface
|
|
10
|
-
};
|
|
1
|
+
import { adapterRegistry } from "./registry.js";
|
|
2
|
+
import "./mobile/surface.js";
|
|
3
|
+
import "./extension/surface.js";
|
|
4
|
+
import "./core/surface.js";
|
|
5
|
+
import "./terminal/surface.js";
|
|
11
6
|
function getAdapterSurface(adapter) {
|
|
12
|
-
return
|
|
7
|
+
return adapterRegistry.get(adapter);
|
|
13
8
|
}
|
|
14
9
|
export {
|
|
15
10
|
getAdapterSurface
|
|
@@ -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,85 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { flag, str } from "../../commands/shared.js";
|
|
4
|
+
import { runnerDir } from "../../paths.js";
|
|
5
|
+
const TERMINAL_ONLY_LAUNCH_OPTIONS = Object.freeze({
|
|
6
|
+
signer: "--signer",
|
|
7
|
+
account: "--account",
|
|
8
|
+
freshProfile: "--fresh-profile",
|
|
9
|
+
headless: "--headless",
|
|
10
|
+
headful: "--headful",
|
|
11
|
+
slowMo: "--slow-mo",
|
|
12
|
+
network: "--network",
|
|
13
|
+
mainnetConfirmation: "--mainnet-confirmation"
|
|
14
|
+
});
|
|
15
|
+
function terminalOnlyLaunchFlags(options) {
|
|
16
|
+
return Object.entries(TERMINAL_ONLY_LAUNCH_OPTIONS).filter(([key]) => options[key] !== void 0 && options[key] !== false).map(([, name]) => name);
|
|
17
|
+
}
|
|
18
|
+
function parseTerminalLauncherOutput(output) {
|
|
19
|
+
const text = output.trim();
|
|
20
|
+
if (!text) return { status: "fail", error: "the terminal launcher printed no summary" };
|
|
21
|
+
try {
|
|
22
|
+
return JSON.parse(text);
|
|
23
|
+
} catch {
|
|
24
|
+
const last = text.split("\n").filter((line) => line.trim()).pop() ?? "";
|
|
25
|
+
try {
|
|
26
|
+
return JSON.parse(last);
|
|
27
|
+
} catch {
|
|
28
|
+
return { status: "fail", error: `the terminal launcher printed no JSON summary: ${last.slice(0, 200)}` };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async function launchTerminal({ options, target, jsonOutput, stream, usage }) {
|
|
33
|
+
const cdpPort = str(options, "cdpPort") ?? process.env.RECIPE_CDP_PORT ?? process.env.CDP_PORT;
|
|
34
|
+
const appPort = str(options, "watcherPort") ?? process.env.TERMINAL_APP_PORT ?? process.env.RECIPE_WATCHER_PORT ?? process.env.WATCHER_PORT;
|
|
35
|
+
if (!cdpPort || !appPort) {
|
|
36
|
+
return usage(
|
|
37
|
+
"terminal launch needs the slot browser CDP port and the slot dev-server port.",
|
|
38
|
+
"mm-harness launch --adapter terminal --cdp-port <port> --watcher-port <app port>"
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
const args = [
|
|
42
|
+
path.join(runnerDir, "adapters", "terminal", "launch.mjs"),
|
|
43
|
+
"--target",
|
|
44
|
+
target,
|
|
45
|
+
"--cdp-port",
|
|
46
|
+
cdpPort,
|
|
47
|
+
"--app-port",
|
|
48
|
+
appPort,
|
|
49
|
+
"--signer",
|
|
50
|
+
str(options, "signer") ?? process.env.TERMINAL_SIGNER ?? "extension",
|
|
51
|
+
"--account",
|
|
52
|
+
str(options, "account") ?? process.env.TERMINAL_ACCOUNT ?? "dev1",
|
|
53
|
+
"--json"
|
|
54
|
+
];
|
|
55
|
+
if (flag(options, "freshProfile")) args.push("--fresh-profile");
|
|
56
|
+
if (flag(options, "headless")) args.push("--headless");
|
|
57
|
+
if (flag(options, "headful")) args.push("--headful");
|
|
58
|
+
const slowMo = str(options, "slowMo");
|
|
59
|
+
if (slowMo) args.push("--slow-mo", slowMo);
|
|
60
|
+
const network = str(options, "network");
|
|
61
|
+
if (network) args.push("--network", network);
|
|
62
|
+
const mainnetConfirmation = str(options, "mainnetConfirmation");
|
|
63
|
+
if (mainnetConfirmation) args.push("--mainnet-confirmation", mainnetConfirmation);
|
|
64
|
+
stream.phase("launch", { adapter: "terminal", cdpPort, appPort });
|
|
65
|
+
let output = "";
|
|
66
|
+
let status = 0;
|
|
67
|
+
try {
|
|
68
|
+
output = execFileSync(process.execPath, args, { encoding: "utf8", stdio: ["ignore", "pipe", "inherit"] });
|
|
69
|
+
} catch (error) {
|
|
70
|
+
status = 1;
|
|
71
|
+
output = String(error.stdout ?? "");
|
|
72
|
+
}
|
|
73
|
+
const result = parseTerminalLauncherOutput(output);
|
|
74
|
+
if (jsonOutput) console.log(JSON.stringify({ schemaVersion: 1, command: "launch", adapter: "terminal", target, ...result }, null, 2));
|
|
75
|
+
else if (result.status === "pass") console.log(`\u2713 terminal browser ${result.reused ? "reused" : "started"} on CDP ${result.cdpPort} (${result.signer}, ${result.account?.name})`);
|
|
76
|
+
else console.error(`\u2717 terminal launch failed: ${result.error ?? "unknown error"}`);
|
|
77
|
+
stream.complete(result.status === "pass" ? "pass" : "fail", status);
|
|
78
|
+
return result.status === "pass" ? 0 : 1;
|
|
79
|
+
}
|
|
80
|
+
export {
|
|
81
|
+
TERMINAL_ONLY_LAUNCH_OPTIONS,
|
|
82
|
+
launchTerminal,
|
|
83
|
+
parseTerminalLauncherOutput,
|
|
84
|
+
terminalOnlyLaunchFlags
|
|
85
|
+
};
|
|
@@ -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
|
+
};
|
|
@@ -3,6 +3,9 @@ import { shellQuote } from "../../commands/parse-args.js";
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import { TERMINAL_CONSOLE_FILES, terminalRuntimeFile } from "./console-capture.js";
|
|
5
5
|
import { stopTerminalBrowserSync } from "./stop.js";
|
|
6
|
+
import { adapterRegistry } from "../registry.js";
|
|
7
|
+
import { terminalActions } from "./actions.js";
|
|
8
|
+
import { terminalDiagnostics, terminalObservation } from "./run.js";
|
|
6
9
|
function portFromEnv(...names) {
|
|
7
10
|
for (const name of names) {
|
|
8
11
|
const raw = process.env[name];
|
|
@@ -95,8 +98,13 @@ const terminalSurface = {
|
|
|
95
98
|
launch: "mm-harness launch --adapter terminal --cdp-port <port> --watcher-port <app port>",
|
|
96
99
|
relaunch: "mm-harness launch --adapter terminal --fresh-profile",
|
|
97
100
|
runtimeProbeRecovery: (target) => `mm-harness doctor --adapter terminal --target ${shellQuote(path.resolve(target))}`
|
|
98
|
-
}
|
|
101
|
+
},
|
|
102
|
+
launch: async (context) => (await import("./launch.js")).launchTerminal(context),
|
|
103
|
+
actions: terminalActions,
|
|
104
|
+
diagnostics: terminalDiagnostics,
|
|
105
|
+
observation: terminalObservation
|
|
99
106
|
};
|
|
107
|
+
adapterRegistry.register(terminalSurface);
|
|
100
108
|
export {
|
|
101
109
|
terminalAppPort,
|
|
102
110
|
terminalCdpPort,
|