@deeeed/metamask-harness 0.81.0 → 0.82.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 +17 -0
- package/adapters/manifest.json +51 -51
- package/adapters/{terminal → web-dapp}/cleanup.mjs +9 -9
- package/adapters/{terminal → web-dapp}/inject.mjs +14 -14
- package/adapters/{terminal → web-dapp}/launch.mjs +23 -24
- package/adapters/web-dapp/lib/blocked-hosts.mjs +24 -0
- package/adapters/{terminal → web-dapp}/lib/cdp-client.mjs +1 -1
- package/adapters/{terminal → web-dapp}/lib/display.mjs +1 -1
- package/adapters/{terminal → web-dapp}/lib/hud.mjs +1 -1
- package/adapters/web-dapp/lib/policy.mjs +79 -0
- package/adapters/{terminal → web-dapp}/lib/processes.mjs +8 -8
- package/adapters/{terminal → web-dapp}/lib/readiness.mjs +15 -25
- package/adapters/{terminal → web-dapp}/stop.mjs +5 -4
- package/adapters/{terminal → web-dapp}/verify.mjs +11 -15
- package/adapters/{terminal → web-dapp}/wallet-host.mjs +10 -11
- package/dist/adapters/extension/run.js +5 -28
- package/dist/adapters/plugin-surface.js +98 -0
- package/dist/adapters/surface.js +1 -1
- package/dist/adapters/{terminal → web-dapp}/actions.js +7 -7
- package/dist/adapters/web-dapp/console-capture.js +63 -0
- package/dist/adapters/web-dapp/doctor-checks.js +33 -0
- package/dist/adapters/{terminal → web-dapp}/launch.js +21 -22
- package/dist/adapters/web-dapp/overlay.js +21 -0
- package/dist/adapters/web-dapp/readiness.js +10 -0
- package/dist/adapters/{terminal → web-dapp}/run.js +15 -15
- package/dist/adapters/{terminal → web-dapp}/stop.js +5 -4
- package/dist/adapters/web-dapp/surface.js +149 -0
- package/dist/cli.js +3 -0
- package/dist/command-contract.js +29 -7
- package/dist/commands/check.js +2 -2
- package/dist/commands/doctor.js +19 -2
- package/dist/commands/farmslot-ready.js +1 -0
- package/dist/commands/help.js +1 -1
- package/dist/commands/launch/device.js +5 -5
- package/dist/commands/self-test.js +9 -5
- package/dist/engine.js +6 -16
- package/dist/manifest.js +48 -24
- package/dist/mm-harness-cli.js +17 -0
- package/dist/performance-observation.js +7 -13
- package/dist/runner.js +26 -7
- package/docs/cli-contract.md +5 -3
- package/library/actions/shared/console-collector.mjs +1 -1
- package/library/actions/{terminal → web-dapp}/app/assert_no_console_errors.mjs +4 -4
- package/library/actions/{terminal → web-dapp}/app/launch.mjs +7 -7
- package/library/actions/{terminal → web-dapp}/platform/hud.mjs +5 -5
- package/library/actions/{terminal → web-dapp}/platform/page.mjs +11 -11
- package/library/actions/{terminal → web-dapp}/platform/runtime.mjs +16 -12
- package/library/actions/web-dapp/ui/navigate.mjs +24 -0
- package/library/actions/web-dapp/wallet/_signature-log.mjs +32 -0
- package/library/actions/{terminal → web-dapp}/wallet/assert_signatures.mjs +4 -4
- package/library/actions/{terminal → web-dapp}/wallet/list_accounts.mjs +2 -2
- package/library/actions/{terminal → web-dapp}/wallet/read_signatures.mjs +6 -6
- package/library/manifests/{terminal.action-manifest.json → web-dapp.action-manifest.json} +18 -702
- package/package.json +5 -5
- package/adapters/terminal/lib/mainnet-guard.mjs +0 -186
- package/adapters/terminal/policy.mjs +0 -35
- package/dist/adapters/terminal/console-capture.js +0 -59
- package/dist/adapters/terminal/doctor-checks.js +0 -24
- package/dist/adapters/terminal/overlay.js +0 -25
- package/dist/adapters/terminal/readiness.js +0 -11
- package/dist/adapters/terminal/surface.js +0 -135
- package/library/actions/terminal/perps/_venue.mjs +0 -242
- package/library/actions/terminal/perps/assert_orders.mjs +0 -54
- package/library/actions/terminal/perps/assert_positions.mjs +0 -40
- package/library/actions/terminal/perps/read_orders.mjs +0 -26
- package/library/actions/terminal/perps/read_positions.mjs +0 -25
- package/library/actions/terminal/perps/read_snapshot.mjs +0 -40
- package/library/actions/terminal/perps/revoke_agent.mjs +0 -58
- package/library/actions/terminal/perps/teardown_state.mjs +0 -104
- package/library/actions/terminal/ui/navigate.mjs +0 -31
- package/library/actions/terminal/wallet/_signature-log.mjs +0 -56
|
@@ -2,7 +2,8 @@ import { flag, str } from "@farmslot/recipe-cli/harness";
|
|
|
2
2
|
import { execFileSync } from "node:child_process";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { runnerDir } from "../../paths.js";
|
|
5
|
-
|
|
5
|
+
import { webDappAdapterId } from "../../../adapters/web-dapp/lib/policy.mjs";
|
|
6
|
+
const WEB_DAPP_ONLY_LAUNCH_OPTIONS = Object.freeze({
|
|
6
7
|
signer: "--signer",
|
|
7
8
|
account: "--account",
|
|
8
9
|
freshProfile: "--fresh-profile",
|
|
@@ -12,12 +13,12 @@ const TERMINAL_ONLY_LAUNCH_OPTIONS = Object.freeze({
|
|
|
12
13
|
network: "--network",
|
|
13
14
|
mainnetConfirmation: "--mainnet-confirmation"
|
|
14
15
|
});
|
|
15
|
-
function
|
|
16
|
-
return Object.entries(
|
|
16
|
+
function webDappOnlyLaunchFlags(options) {
|
|
17
|
+
return Object.entries(WEB_DAPP_ONLY_LAUNCH_OPTIONS).filter(([key]) => options[key] !== void 0 && options[key] !== false).map(([, name]) => name);
|
|
17
18
|
}
|
|
18
|
-
function
|
|
19
|
+
function parseWebDappLauncherOutput(output) {
|
|
19
20
|
const text = output.trim();
|
|
20
|
-
if (!text) return { status: "fail", error:
|
|
21
|
+
if (!text) return { status: "fail", error: `the ${webDappAdapterId()} launcher printed no summary` };
|
|
21
22
|
try {
|
|
22
23
|
return JSON.parse(text);
|
|
23
24
|
} catch {
|
|
@@ -25,30 +26,28 @@ function parseTerminalLauncherOutput(output) {
|
|
|
25
26
|
try {
|
|
26
27
|
return JSON.parse(last);
|
|
27
28
|
} catch {
|
|
28
|
-
return { status: "fail", error: `the
|
|
29
|
+
return { status: "fail", error: `the ${webDappAdapterId()} launcher printed no JSON summary: ${last.slice(0, 200)}` };
|
|
29
30
|
}
|
|
30
31
|
}
|
|
31
32
|
}
|
|
32
|
-
async function
|
|
33
|
+
async function launchWebDapp({ options, target, jsonOutput, stream, usage }) {
|
|
34
|
+
const id = webDappAdapterId();
|
|
33
35
|
const cdpPort = str(options, "cdpPort") ?? process.env.RECIPE_CDP_PORT ?? process.env.CDP_PORT;
|
|
34
36
|
const appPort = str(options, "watcherPort") ?? process.env.TERMINAL_APP_PORT ?? process.env.RECIPE_WATCHER_PORT ?? process.env.WATCHER_PORT;
|
|
35
37
|
if (!cdpPort || !appPort) {
|
|
36
38
|
return usage(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
`${id} launch needs the slot browser CDP port and the slot dev-server port.`,
|
|
40
|
+
`mm-harness launch --adapter ${id} --cdp-port <port> --watcher-port <app port>`
|
|
39
41
|
);
|
|
40
42
|
}
|
|
41
43
|
const args = [
|
|
42
|
-
path.join(runnerDir, "adapters", "
|
|
44
|
+
path.join(runnerDir, "adapters", "web-dapp", "launch.mjs"),
|
|
43
45
|
"--target",
|
|
44
46
|
target,
|
|
45
47
|
"--cdp-port",
|
|
46
48
|
cdpPort,
|
|
47
49
|
"--app-port",
|
|
48
50
|
appPort,
|
|
49
|
-
// The Terminal's venue policy (Hyperliquid hosts, refusal, start page).
|
|
50
|
-
"--policy-module",
|
|
51
|
-
path.join(runnerDir, "adapters", "terminal", "policy.mjs"),
|
|
52
51
|
"--signer",
|
|
53
52
|
str(options, "signer") ?? process.env.TERMINAL_SIGNER ?? "extension",
|
|
54
53
|
"--account",
|
|
@@ -64,7 +63,7 @@ async function launchTerminal({ options, target, jsonOutput, stream, usage }) {
|
|
|
64
63
|
if (network) args.push("--network", network);
|
|
65
64
|
const mainnetConfirmation = str(options, "mainnetConfirmation");
|
|
66
65
|
if (mainnetConfirmation) args.push("--mainnet-confirmation", mainnetConfirmation);
|
|
67
|
-
stream.phase("launch", { adapter:
|
|
66
|
+
stream.phase("launch", { adapter: id, cdpPort, appPort });
|
|
68
67
|
let output = "";
|
|
69
68
|
let status = 0;
|
|
70
69
|
try {
|
|
@@ -73,16 +72,16 @@ async function launchTerminal({ options, target, jsonOutput, stream, usage }) {
|
|
|
73
72
|
status = 1;
|
|
74
73
|
output = String(error.stdout ?? "");
|
|
75
74
|
}
|
|
76
|
-
const result =
|
|
77
|
-
if (jsonOutput) console.log(JSON.stringify({ schemaVersion: 1, command: "launch", adapter:
|
|
78
|
-
else if (result.status === "pass") console.log(`\u2713
|
|
79
|
-
else console.error(`\u2717
|
|
75
|
+
const result = parseWebDappLauncherOutput(output);
|
|
76
|
+
if (jsonOutput) console.log(JSON.stringify({ schemaVersion: 1, command: "launch", adapter: id, target, ...result }, null, 2));
|
|
77
|
+
else if (result.status === "pass") console.log(`\u2713 ${id} browser ${result.reused ? "reused" : "started"} on CDP ${result.cdpPort} (${result.signer}, ${result.account?.name})`);
|
|
78
|
+
else console.error(`\u2717 ${id} launch failed: ${result.error ?? "unknown error"}`);
|
|
80
79
|
stream.complete(result.status === "pass" ? "pass" : "fail", status);
|
|
81
80
|
return result.status === "pass" ? 0 : 1;
|
|
82
81
|
}
|
|
83
82
|
export {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
83
|
+
WEB_DAPP_ONLY_LAUNCH_OPTIONS,
|
|
84
|
+
launchWebDapp,
|
|
85
|
+
parseWebDappLauncherOutput,
|
|
86
|
+
webDappOnlyLaunchFlags
|
|
88
87
|
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { runnerDir } from "../../paths.js";
|
|
3
|
+
const webDappHarness = {
|
|
4
|
+
install: { entry: "adapters/web-dapp/inject.mjs", fallback: "adapters/web-dapp/inject.mjs", node: true },
|
|
5
|
+
cleanup: { entry: "adapters/web-dapp/cleanup.mjs", fallback: "adapters/web-dapp/cleanup.mjs", node: true },
|
|
6
|
+
verify: () => ({
|
|
7
|
+
command: process.execPath,
|
|
8
|
+
prefixArgs: [path.join(runnerDir, "adapters", "web-dapp", "verify.mjs")]
|
|
9
|
+
}),
|
|
10
|
+
restart: "mm-harness launch"
|
|
11
|
+
};
|
|
12
|
+
const webDappRuntimeContext = {
|
|
13
|
+
forbiddenFields: ["metroPort", "simulator", "simulatorUdid", "adbSerial", "extensionId"],
|
|
14
|
+
resources: ({ existing, defaults, envPort }) => ({
|
|
15
|
+
cdpPort: envPort("RECIPE_CDP_PORT", "CDP_PORT") ?? existing.cdpPort ?? defaults.cdpPort
|
|
16
|
+
})
|
|
17
|
+
};
|
|
18
|
+
export {
|
|
19
|
+
webDappHarness,
|
|
20
|
+
webDappRuntimeContext
|
|
21
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { POLICY_ENV, webDappPolicy } from "../../../adapters/web-dapp/lib/policy.mjs";
|
|
2
|
+
const webDappReadiness = {
|
|
3
|
+
compatibilityMode: (target) => process.env[POLICY_ENV] && webDappPolicy().checkout.matches(target) ? "slot browser over CDP (website; no product bridge)" : "unsupported/no bridge",
|
|
4
|
+
liveChecks: async (target) => (await import("./doctor-checks.js")).webDappDoctorChecks(target),
|
|
5
|
+
// Screenshots come from the slot browser over CDP.
|
|
6
|
+
captureProviders: ["cdp"]
|
|
7
|
+
};
|
|
8
|
+
export {
|
|
9
|
+
webDappReadiness
|
|
10
|
+
};
|
|
@@ -2,21 +2,21 @@ import { isRecord } from "@farmslot/recipe-cli/harness";
|
|
|
2
2
|
import { verifyOwnedCollectorControl } from "../console-capture.js";
|
|
3
3
|
import { devicePlatformFromEnv } from "../performance/device-platform.js";
|
|
4
4
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
ensureWebDappConsoleCapture,
|
|
6
|
+
webDappCaptureFiles,
|
|
7
|
+
webDappCdpPort,
|
|
8
|
+
webDappRuntimeFile,
|
|
9
|
+
verifyWebDappConsoleControl
|
|
10
10
|
} from "./console-capture.js";
|
|
11
|
-
const
|
|
11
|
+
const webDappDiagnostics = {
|
|
12
12
|
console: {
|
|
13
|
-
start: (projectRoot) =>
|
|
14
|
-
files: (projectRoot) =>
|
|
15
|
-
cdpPort: () =>
|
|
16
|
-
verifyControl: (capture) => verifyOwnedCollectorControl(capture, () =>
|
|
13
|
+
start: (projectRoot) => ensureWebDappConsoleCapture(projectRoot),
|
|
14
|
+
files: (projectRoot) => webDappCaptureFiles(projectRoot),
|
|
15
|
+
cdpPort: () => webDappCdpPort(),
|
|
16
|
+
verifyControl: (capture) => verifyOwnedCollectorControl(capture, () => verifyWebDappConsoleControl(capture))
|
|
17
17
|
},
|
|
18
18
|
requestLog: {
|
|
19
|
-
path: (projectRoot) =>
|
|
19
|
+
path: (projectRoot) => webDappRuntimeFile(projectRoot, "wallet-requests.jsonl"),
|
|
20
20
|
findings: (lines) => walletRequestFindings(lines)
|
|
21
21
|
}
|
|
22
22
|
};
|
|
@@ -38,7 +38,7 @@ function walletRequestFindings(lines) {
|
|
|
38
38
|
}
|
|
39
39
|
return findings;
|
|
40
40
|
}
|
|
41
|
-
const
|
|
41
|
+
const webDappObservation = {
|
|
42
42
|
network: {
|
|
43
43
|
backend: async (_target, env, artifactsDir) => (await import("../extension/network-observer.js")).createExtensionNetworkBackend(env, artifactsDir)
|
|
44
44
|
},
|
|
@@ -46,13 +46,13 @@ const terminalObservation = {
|
|
|
46
46
|
start: async (context) => {
|
|
47
47
|
const observation = await import("../../performance-observation.js");
|
|
48
48
|
return observation.startRunPerformanceObservation({
|
|
49
|
-
backend: (target, env
|
|
49
|
+
backend: (target, env) => observation.createExtensionPerformanceObserver(target, env),
|
|
50
50
|
platform: (env) => devicePlatformFromEnv(env)
|
|
51
51
|
}, context);
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
};
|
|
55
55
|
export {
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
webDappDiagnostics,
|
|
57
|
+
webDappObservation
|
|
58
58
|
};
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { runnerDir } from "../../paths.js";
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
import { webDappAdapterId } from "../../../adapters/web-dapp/lib/policy.mjs";
|
|
5
|
+
function stopWebDappBrowserSync(target) {
|
|
6
|
+
const args = [path.join(runnerDir, "adapters", "web-dapp", "stop.mjs"), "--target", path.resolve(target)];
|
|
6
7
|
const cdpPort = process.env.RECIPE_CDP_PORT ?? process.env.CDP_PORT;
|
|
7
8
|
if (cdpPort) args.push("--cdp-port", cdpPort);
|
|
8
9
|
const result = spawnSync(process.execPath, args, { encoding: "utf8" });
|
|
@@ -10,10 +11,10 @@ function stopTerminalBrowserSync(target) {
|
|
|
10
11
|
const status = result.status ?? 1;
|
|
11
12
|
return {
|
|
12
13
|
status,
|
|
13
|
-
summary: status === 0 ?
|
|
14
|
+
summary: status === 0 ? `${webDappAdapterId()} browser and wallet host stopped` : `${webDappAdapterId()} browser stop failed`,
|
|
14
15
|
output
|
|
15
16
|
};
|
|
16
17
|
}
|
|
17
18
|
export {
|
|
18
|
-
|
|
19
|
+
stopWebDappBrowserSync
|
|
19
20
|
};
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { shellQuote } from "@farmslot/recipe-cli/harness";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { defineAdapter } from "@farmslot/adapter-sdk";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import { POLICY_ENV, webDappAdapterId, webDappPolicy } from "../../../adapters/web-dapp/lib/policy.mjs";
|
|
6
|
+
import { WEB_DAPP_CONSOLE_FILES, webDappRuntimeFile } from "./console-capture.js";
|
|
7
|
+
import { stopWebDappBrowserSync } from "./stop.js";
|
|
8
|
+
import { adapterRegistry } from "../registry.js";
|
|
9
|
+
import { webDappActions } from "./actions.js";
|
|
10
|
+
import { webDappHarness, webDappRuntimeContext } from "./overlay.js";
|
|
11
|
+
import { webDappReadiness } from "./readiness.js";
|
|
12
|
+
import { webDappDiagnostics, webDappObservation } from "./run.js";
|
|
13
|
+
function portFromEnv(...names) {
|
|
14
|
+
for (const name of names) {
|
|
15
|
+
const raw = process.env[name];
|
|
16
|
+
if (raw && /^\d+$/u.test(raw)) return Number(raw);
|
|
17
|
+
}
|
|
18
|
+
return void 0;
|
|
19
|
+
}
|
|
20
|
+
function webDappAppPort() {
|
|
21
|
+
return portFromEnv("TERMINAL_APP_PORT", "RECIPE_WATCHER_PORT", "WATCHER_PORT");
|
|
22
|
+
}
|
|
23
|
+
function webDappCdpPort() {
|
|
24
|
+
return portFromEnv("RECIPE_CDP_PORT", "CDP_PORT");
|
|
25
|
+
}
|
|
26
|
+
function webDappSignerFor(target) {
|
|
27
|
+
try {
|
|
28
|
+
const state = JSON.parse(fs.readFileSync(webDappRuntimeFile(target, "browser.json"), "utf8"));
|
|
29
|
+
if (typeof state.signer === "string") return state.signer;
|
|
30
|
+
} catch {
|
|
31
|
+
}
|
|
32
|
+
return process.env.TERMINAL_SIGNER || "extension";
|
|
33
|
+
}
|
|
34
|
+
function bindWebDappPolicy(adapter) {
|
|
35
|
+
if (adapter.extends === "web-dapp" && typeof adapter.policy?.module === "string") {
|
|
36
|
+
process.env[POLICY_ENV] = adapter.policy.module;
|
|
37
|
+
}
|
|
38
|
+
return adapter;
|
|
39
|
+
}
|
|
40
|
+
const webDappSurface = defineAdapter({
|
|
41
|
+
id: "web-dapp",
|
|
42
|
+
sdkVersion: 1,
|
|
43
|
+
headless: false,
|
|
44
|
+
resolveSlotPorts() {
|
|
45
|
+
},
|
|
46
|
+
async runtimeStatus(target) {
|
|
47
|
+
if (!process.env[POLICY_ENV]) {
|
|
48
|
+
return {
|
|
49
|
+
decision: "blocked",
|
|
50
|
+
reasonCode: "web-dapp-venue-policy",
|
|
51
|
+
reasons: ["web-dapp needs a venue policy: select an adapter that extends it"],
|
|
52
|
+
nextAction: "pass --adapter <the adapter that extends web-dapp>, e.g. --adapter terminal, with its recipe library on RECIPE_LIBRARY_PATH",
|
|
53
|
+
deps: "missing",
|
|
54
|
+
devServer: { label: "next", status: "down" }
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
const policy = webDappPolicy();
|
|
58
|
+
const { webDappReadiness: webDappReadiness2 } = await import("../../../adapters/web-dapp/lib/readiness.mjs");
|
|
59
|
+
const report = await webDappReadiness2({
|
|
60
|
+
policy,
|
|
61
|
+
target: path.resolve(target),
|
|
62
|
+
appPort: webDappAppPort(),
|
|
63
|
+
cdpPort: webDappCdpPort(),
|
|
64
|
+
signer: process.env.TERMINAL_SIGNER || "extension"
|
|
65
|
+
});
|
|
66
|
+
const blocking = report.checks.filter((check) => check.required && check.status === "fail");
|
|
67
|
+
const devServer = report.checks.find((check) => check.id === "app-dev-server");
|
|
68
|
+
return {
|
|
69
|
+
decision: blocking.length === 0 ? "ready" : "blocked",
|
|
70
|
+
reasonCode: blocking[0]?.id,
|
|
71
|
+
reasons: (blocking.length ? blocking : report.checks).map((check) => `${check.id}: ${check.detail}`),
|
|
72
|
+
nextAction: blocking.length === 0 ? void 0 : blocking[0].id === "app-dev-server" || blocking[0].id === "testnet-forced" ? "start the slot dev server with testnet forced (Farmslot preflight), then retry" : `mm-harness doctor --adapter ${policy.adapterId} --target ${shellQuote(path.resolve(target))} --json`,
|
|
73
|
+
deps: report.checks.find((check) => check.id === "dependencies")?.status === "pass" ? "current" : "missing",
|
|
74
|
+
devServer: {
|
|
75
|
+
label: "next",
|
|
76
|
+
status: devServer?.status === "pass" ? "up" : "down"
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
},
|
|
80
|
+
runwayProvision: {
|
|
81
|
+
async run(target, options) {
|
|
82
|
+
return {
|
|
83
|
+
schemaVersion: 1,
|
|
84
|
+
command: "provision",
|
|
85
|
+
adapter: webDappAdapterId(),
|
|
86
|
+
target: path.resolve(target),
|
|
87
|
+
status: "fail",
|
|
88
|
+
exitCode: 2,
|
|
89
|
+
error: {
|
|
90
|
+
code: "UNSUPPORTED_ADAPTER",
|
|
91
|
+
message: `the ${process.env[POLICY_ENV] ? webDappPolicy().checkout.name : "web dapp"} is a website; there is no simulator or build to provision.`,
|
|
92
|
+
userAction: options.rerunCommand || `mm-harness launch --adapter ${webDappAdapterId()}`
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
devServer: {
|
|
98
|
+
label: "browser",
|
|
99
|
+
describe: () => `${webDappAdapterId()} slot browser and wallet host (the Next.js dev server belongs to the slot)`,
|
|
100
|
+
stop(target) {
|
|
101
|
+
const result = stopWebDappBrowserSync(target);
|
|
102
|
+
return { kind: "stopped", status: result.status, summary: result.summary, output: result.output };
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
logSources(target) {
|
|
106
|
+
return [
|
|
107
|
+
{ label: "app-console", path: webDappRuntimeFile(target, WEB_DAPP_CONSOLE_FILES.app) },
|
|
108
|
+
...webDappSignerFor(target) === "extension" ? [{ label: "extension-console", path: webDappRuntimeFile(target, WEB_DAPP_CONSOLE_FILES.extension) }] : [],
|
|
109
|
+
{ label: "wallet-requests", path: webDappRuntimeFile(target, "wallet-requests.jsonl") },
|
|
110
|
+
{ label: "wallet-host", path: webDappRuntimeFile(target, "wallet-host.log") },
|
|
111
|
+
{ label: "browser", path: webDappRuntimeFile(target, "browser.log") },
|
|
112
|
+
{ label: "next", path: path.join(target, "temp/farmslot/next-dev.log") }
|
|
113
|
+
];
|
|
114
|
+
},
|
|
115
|
+
// The app page's console and uncaught exceptions, as the Extension's
|
|
116
|
+
// diagnostics read the extension console: errors make a run REVIEW.
|
|
117
|
+
appLogSource(target) {
|
|
118
|
+
return { label: "app-console", path: webDappRuntimeFile(target, WEB_DAPP_CONSOLE_FILES.app) };
|
|
119
|
+
},
|
|
120
|
+
flags: { launch: ["freshProfile", "headless", "headful"] },
|
|
121
|
+
hints: {
|
|
122
|
+
// Bare web-dapp has no venue: every hint names the adapter that extends it.
|
|
123
|
+
launch: "pass --adapter <the adapter that extends web-dapp>, e.g. --adapter terminal, with its recipe library on RECIPE_LIBRARY_PATH",
|
|
124
|
+
relaunch: "pass --adapter <the adapter that extends web-dapp>, e.g. --adapter terminal, with --fresh-profile",
|
|
125
|
+
runtimeProbeRecovery: () => "pass --adapter <the adapter that extends web-dapp>, e.g. --adapter terminal, with its recipe library on RECIPE_LIBRARY_PATH"
|
|
126
|
+
},
|
|
127
|
+
launch: async (context) => (await import("./launch.js")).launchWebDapp(context),
|
|
128
|
+
actions: webDappActions,
|
|
129
|
+
harness: webDappHarness,
|
|
130
|
+
runtimeContext: webDappRuntimeContext,
|
|
131
|
+
readiness: webDappReadiness,
|
|
132
|
+
fixtures: { operations: ["init", "sync", "set"] },
|
|
133
|
+
debug: {
|
|
134
|
+
args: ({ devMenu }) => [
|
|
135
|
+
...process.env.WATCHER_PORT ? ["--port", process.env.WATCHER_PORT] : [],
|
|
136
|
+
"--action",
|
|
137
|
+
devMenu ? "dev-menu" : "debug"
|
|
138
|
+
]
|
|
139
|
+
},
|
|
140
|
+
diagnostics: webDappDiagnostics,
|
|
141
|
+
observation: webDappObservation
|
|
142
|
+
});
|
|
143
|
+
adapterRegistry.register(webDappSurface);
|
|
144
|
+
export {
|
|
145
|
+
bindWebDappPolicy,
|
|
146
|
+
webDappAppPort,
|
|
147
|
+
webDappCdpPort,
|
|
148
|
+
webDappSurface
|
|
149
|
+
};
|
package/dist/cli.js
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
targetPath
|
|
16
16
|
} from "@farmslot/recipe-cli/harness";
|
|
17
17
|
import { stopProjectCollector } from "../library/actions/shared/analytics/collector.mjs";
|
|
18
|
+
import { loadSelectedAdapter } from "./adapters/plugin-surface.js";
|
|
18
19
|
import { adapterRegistry } from "./adapters/registry.js";
|
|
19
20
|
import { applyDeviceTargeting } from "./commands/device-target.js";
|
|
20
21
|
import { metaMaskEngine } from "./engine.js";
|
|
@@ -135,6 +136,8 @@ async function main(argv) {
|
|
|
135
136
|
usage();
|
|
136
137
|
return command ? 0 : 2;
|
|
137
138
|
}
|
|
139
|
+
const refused = await loadSelectedAdapter(command, argv.slice(1));
|
|
140
|
+
if (refused !== void 0) return refused;
|
|
138
141
|
if (command === "status" || command === "health" || command === "home") {
|
|
139
142
|
return handleStatus(parseArgs(argv.slice(1)));
|
|
140
143
|
}
|
package/dist/command-contract.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { closest } from "@farmslot/recipe-cli/harness";
|
|
1
|
+
import { adapterChoices, closest } from "@farmslot/recipe-cli/harness";
|
|
2
|
+
import { configuredLibrarySources, operatorLibraryEnv } from "./adapters/plugin-surface.js";
|
|
2
3
|
const bool = () => ({ kind: "boolean" });
|
|
3
4
|
const value = (choices) => ({ kind: "value", ...choices ? { choices } : {} });
|
|
4
5
|
const optionalValue = (choices) => ({ kind: "optional-value", ...choices ? { choices } : {} });
|
|
@@ -8,9 +9,18 @@ const JSON = { "--json": bool() };
|
|
|
8
9
|
const NO_OPEN = { "--no-open": bool() };
|
|
9
10
|
const JSON_STREAM = { "--json-stream": bool() };
|
|
10
11
|
const TARGET = { "--target": value() };
|
|
11
|
-
const
|
|
12
|
+
const adapterIds = (tokens) => adapterChoices(optionValues(tokens, "--library"), {
|
|
13
|
+
configured: configuredLibrarySources(),
|
|
14
|
+
env: operatorLibraryEnv()
|
|
15
|
+
});
|
|
16
|
+
const ADAPTER = { "--adapter": { kind: "value", choices: adapterIds } };
|
|
12
17
|
const MOBILE_PLATFORM = { "--platform": value(["ios", "android"]) };
|
|
13
|
-
const ADAPTER_OR_MOBILE_PLATFORM = {
|
|
18
|
+
const ADAPTER_OR_MOBILE_PLATFORM = {
|
|
19
|
+
"--platform": {
|
|
20
|
+
kind: "value",
|
|
21
|
+
choices: (tokens) => [...adapterIds(tokens), "ios", "android"]
|
|
22
|
+
}
|
|
23
|
+
};
|
|
14
24
|
const DEVICE = { "--device": value() };
|
|
15
25
|
const RUNTIME_PORTS = {
|
|
16
26
|
"--cdp-port": value(),
|
|
@@ -152,7 +162,7 @@ const PUBLIC_COMMAND_CONTRACTS = {
|
|
|
152
162
|
prepare: {
|
|
153
163
|
usage: "mm-harness prepare [--target <checkout>] [--platform mobile|extension|core] [--mobile-platform ios|android] [--clear-metro] [--artifacts-dir <dir>] [--json]",
|
|
154
164
|
options: options(HELP, JSON, TARGET, {
|
|
155
|
-
"--platform":
|
|
165
|
+
"--platform": { kind: "value", choices: adapterIds },
|
|
156
166
|
"--mobile-platform": value(["ios", "android"]),
|
|
157
167
|
"--clear-metro": bool(),
|
|
158
168
|
"--artifacts-dir": value(),
|
|
@@ -420,6 +430,16 @@ const PUBLIC_COMMAND_CONTRACTS = {
|
|
|
420
430
|
minimumPositionals: 0
|
|
421
431
|
}
|
|
422
432
|
};
|
|
433
|
+
function optionValues(args, name) {
|
|
434
|
+
const values = [];
|
|
435
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
436
|
+
const arg = args[index];
|
|
437
|
+
if (arg === "--") break;
|
|
438
|
+
if (arg === name && args[index + 1] !== void 0) values.push(args[index += 1]);
|
|
439
|
+
else if (arg.startsWith(`${name}=`)) values.push(arg.slice(name.length + 1));
|
|
440
|
+
}
|
|
441
|
+
return values;
|
|
442
|
+
}
|
|
423
443
|
function validatePublicInvocation(argv, examples = {}) {
|
|
424
444
|
const token = argv[0];
|
|
425
445
|
if (!token || token === "--help" || token === "-h" || token === "--version" || token === "-v") return null;
|
|
@@ -487,12 +507,13 @@ function validatePublicInvocation(argv, examples = {}) {
|
|
|
487
507
|
return usageFailure("CLI_MISSING_OPTION_VALUE", name, `${optionName} requires a value.`, contract, examples);
|
|
488
508
|
}
|
|
489
509
|
if (inlineValue === void 0) index += 1;
|
|
490
|
-
|
|
491
|
-
|
|
510
|
+
const choices = typeof spec.choices === "function" ? spec.choices(tokens) : spec.choices;
|
|
511
|
+
if (choices && !choices.includes(optionValue)) {
|
|
512
|
+
const suggestion = closest(optionValue, choices);
|
|
492
513
|
return {
|
|
493
514
|
code: "CLI_INVALID_OPTION_VALUE",
|
|
494
515
|
command: name,
|
|
495
|
-
message: `${optionName} must be ${formatChoiceList(
|
|
516
|
+
message: `${optionName} must be ${formatChoiceList(choices)}; received '${optionValue}'.`,
|
|
496
517
|
userAction: actionFor(optionValue, suggestion, exampleFor(name, examples))
|
|
497
518
|
};
|
|
498
519
|
}
|
|
@@ -701,6 +722,7 @@ function exampleFor(command, examples) {
|
|
|
701
722
|
}
|
|
702
723
|
export {
|
|
703
724
|
PUBLIC_COMMAND_CONTRACTS,
|
|
725
|
+
optionValues,
|
|
704
726
|
publicCommandNames,
|
|
705
727
|
publicCommandTokens,
|
|
706
728
|
validatePublicInvocation
|
package/dist/commands/check.js
CHANGED
|
@@ -3,7 +3,7 @@ import { spawnSync } from "node:child_process";
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import "../adapters/surface.js";
|
|
6
|
-
const USAGE = "mm-harness check diff [--fix] [--target <repo>] [--adapter
|
|
6
|
+
const USAGE = "mm-harness check diff [--fix] [--target <repo>] [--adapter <adapter>] [--base <ref>] [--profile fast|full] [--artifacts-dir <dir>] [--json]";
|
|
7
7
|
const JS_TS_EXT_RE = /\.(?:c|m)?jsx?$|\.tsx?$/u;
|
|
8
8
|
const FORMAT_EXT_RE = /\.(?:c|m)?jsx?$|\.tsx?$|\.json$|\.md$|\.css$|\.scss$|\.ya?ml$/u;
|
|
9
9
|
const TEST_FILE_RE = /(?:^|[./_-])(?:test|spec)\.(?:c|m)?jsx?$|(?:^|[./_-])(?:test|spec)\.tsx?$/u;
|
|
@@ -21,7 +21,7 @@ async function handleCheck(argv) {
|
|
|
21
21
|
const target = path.resolve(optionString(options, "target") ?? process.cwd());
|
|
22
22
|
const explicitAdapter = optionString(options, "adapter") ?? optionString(options, "platform");
|
|
23
23
|
const detected = explicitAdapter ?? detectAdapter(target);
|
|
24
|
-
if (detected
|
|
24
|
+
if (detected === "ios" || detected === "android") {
|
|
25
25
|
return usageOut(json, "check", `unsupported adapter '${detected}'`, USAGE);
|
|
26
26
|
}
|
|
27
27
|
const adapter = detected;
|
package/dist/commands/doctor.js
CHANGED
|
@@ -2,7 +2,7 @@ import { execFileSync } from "node:child_process";
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { featureFlagReport } from "../feature-flags.js";
|
|
5
|
-
import { color, acquireCheckoutLock, detectAdapter, resolveRuntimeContextPath, ensureOverlay, newHealState, recipeRunning, adapterDetectNext, checkoutBusyOut, EXIT, usageOut, actionManifestPathOption, applyRuntimeDirOption, optionFlag, optionString, shellQuote, targetPath } from "@farmslot/recipe-cli/harness";
|
|
5
|
+
import { adapterPluginChecks, color, acquireCheckoutLock, detectAdapter, resolveRuntimeContextPath, ensureOverlay, newHealState, recipeRunning, adapterDetectNext, checkoutBusyOut, EXIT, usageOut, actionManifestPathOption, applyRuntimeDirOption, optionFlag, optionString, shellQuote, targetPath } from "@farmslot/recipe-cli/harness";
|
|
6
6
|
import { collectCheckoutView, renderCheckoutView } from "../checkout-view.js";
|
|
7
7
|
import { createDoctorReport, fixtureSummary, requiredDoctorCheckSummary } from "../doctor.js";
|
|
8
8
|
import { assertAdapter, runnerDir } from "../paths.js";
|
|
@@ -158,7 +158,10 @@ async function handleDoctor({ options }) {
|
|
|
158
158
|
const checks = [
|
|
159
159
|
...result.checks,
|
|
160
160
|
runtimeCheck,
|
|
161
|
-
...await readiness.liveChecks?.(target) ?? []
|
|
161
|
+
...await readiness.liveChecks?.(target) ?? [],
|
|
162
|
+
...await platformDoctorChecks(adapter, target),
|
|
163
|
+
// Which library plugin code this process loaded: library, module and digest.
|
|
164
|
+
...adapterPluginChecks()
|
|
162
165
|
];
|
|
163
166
|
const requiredChecks = requiredDoctorCheckSummary(checks);
|
|
164
167
|
const doctorResult = {
|
|
@@ -244,6 +247,20 @@ async function handleDoctor({ options }) {
|
|
|
244
247
|
}
|
|
245
248
|
return doctorResult.status === "pass" ? 0 : 1;
|
|
246
249
|
}
|
|
250
|
+
async function platformDoctorChecks(adapter, target) {
|
|
251
|
+
try {
|
|
252
|
+
return await getAdapterSurface(adapter).doctor?.(target) ?? [];
|
|
253
|
+
} catch (error) {
|
|
254
|
+
return [{
|
|
255
|
+
id: `adapter-plugin:${adapter}:doctor`,
|
|
256
|
+
status: "fail",
|
|
257
|
+
required: true,
|
|
258
|
+
message: `${adapter}'s doctor checks failed to run.`,
|
|
259
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
260
|
+
userAction: `fix the doctor() of the ${adapter} adapter, then rerun mm-harness doctor --adapter ${adapter}`
|
|
261
|
+
}];
|
|
262
|
+
}
|
|
263
|
+
}
|
|
247
264
|
function doctorRuntimeCheck(runtime, error, expectLive) {
|
|
248
265
|
if (error) {
|
|
249
266
|
return {
|
package/dist/commands/help.js
CHANGED
|
@@ -5,7 +5,7 @@ import { handleHelpReview } from "./review.js";
|
|
|
5
5
|
import "../adapters/surface.js";
|
|
6
6
|
const UNSCOPED_NEXT = "cd into a MetaMask checkout or pass --adapter <mobile|extension|core|terminal>";
|
|
7
7
|
function validHelpLine(value) {
|
|
8
|
-
return typeof value === "string" || Boolean(value && typeof value.text === "string" && Array.isArray(value.adapters) && value.adapters.length > 0 && value.adapters.every((adapter) => ["mobile", "extension", "core", "
|
|
8
|
+
return typeof value === "string" || Boolean(value && typeof value.text === "string" && Array.isArray(value.adapters) && value.adapters.length > 0 && value.adapters.every((adapter) => ["mobile", "extension", "core", "web-dapp"].includes(adapter)));
|
|
9
9
|
}
|
|
10
10
|
function scopedSections(topic, adapter) {
|
|
11
11
|
const select = (lines) => lines.flatMap((line) => typeof line === "string" ? [line] : line.adapters.includes(adapter) ? [line.text] : []);
|
|
@@ -9,15 +9,15 @@ import {
|
|
|
9
9
|
parseRemoteFlagSpec,
|
|
10
10
|
remoteFlagRequestFor
|
|
11
11
|
} from "../../feature-flags.js";
|
|
12
|
-
import {
|
|
12
|
+
import { webDappOnlyLaunchFlags } from "../../adapters/web-dapp/launch.js";
|
|
13
13
|
import { shellQuote } from "./quote.js";
|
|
14
14
|
async function runDeviceLaunch(context, profile) {
|
|
15
15
|
const { adapter, options, target, heal, jsonOutput, machine, stream, usage } = context;
|
|
16
|
-
const
|
|
17
|
-
if (
|
|
16
|
+
const webDappOnly = webDappOnlyLaunchFlags(options);
|
|
17
|
+
if (webDappOnly.length > 0) {
|
|
18
18
|
return usage(
|
|
19
|
-
`${
|
|
20
|
-
"drop
|
|
19
|
+
`${webDappOnly.join(", ")} apply only to adapters that extend web-dapp (e.g. --adapter terminal); ${adapter} does not read them.`,
|
|
20
|
+
"drop those flags, or pass --adapter terminal"
|
|
21
21
|
);
|
|
22
22
|
}
|
|
23
23
|
const wantBuild = flag(options, "build");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { optionFlag, optionString, requiredOption, runRecipe } from "@farmslot/recipe-cli/harness";
|
|
1
|
+
import { activateRecipeRuntimeEnvironment, optionFlag, optionString, requiredOption, runRecipe } from "@farmslot/recipe-cli/harness";
|
|
2
2
|
import { mkdtemp } from "node:fs/promises";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
@@ -23,10 +23,14 @@ async function runSelfTest(options) {
|
|
|
23
23
|
const manifestValidation = await validateManifest(manifest);
|
|
24
24
|
const smokeRecipe = recipePath(`${adapter}/runner/smoke.recipe.json`);
|
|
25
25
|
const artifactsDir = path.join(root, adapter);
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
const runtimeOptions = { platform: { skipExtensionRuntimePrepare: true } };
|
|
27
|
+
const restoreRuntimeEnvironment = activateRecipeRuntimeEnvironment(adapter, runnerDir, runtimeOptions);
|
|
28
|
+
let result;
|
|
29
|
+
try {
|
|
30
|
+
result = await runRecipe(metaMaskEngine, adapter, smokeRecipe, artifactsDir, runnerDir, void 0, runtimeOptions);
|
|
31
|
+
} finally {
|
|
32
|
+
restoreRuntimeEnvironment();
|
|
33
|
+
}
|
|
30
34
|
runs.push({ adapter, manifestValidation: manifestValidation.summary, artifactsDir, result });
|
|
31
35
|
}
|
|
32
36
|
} finally {
|
package/dist/engine.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import { resolveAdapter } from "@farmslot/recipe-cli/harness";
|
|
3
2
|
import { getAdapterSurface } from "./adapters/surface.js";
|
|
4
3
|
import { consoleClassifier } from "./console-classifier.js";
|
|
5
4
|
import {
|
|
@@ -21,20 +20,21 @@ const metaMaskEngine = {
|
|
|
21
20
|
resolveActionManifest,
|
|
22
21
|
validateManifest,
|
|
23
22
|
actionCapabilities: metaMaskActionExecutionCapabilities,
|
|
24
|
-
createRunner: async (adapter, manifest,
|
|
23
|
+
createRunner: async (adapter, manifest, options) => {
|
|
25
24
|
const { createMetaMaskRunner } = await import("./runner.js");
|
|
26
|
-
return
|
|
25
|
+
return createMetaMaskRunner(adapter, manifest, options);
|
|
27
26
|
},
|
|
28
27
|
// Funding files from the run's command line, bound to the run's plan. Without
|
|
29
28
|
// them, a platform whose actions take a trusted mutation (the Extension) runs a
|
|
30
29
|
// plan with external mutations in the fixture-policy lane, where each action
|
|
31
30
|
// applies the shared order policy; any other plan runs without one.
|
|
32
31
|
trustedMutation: {
|
|
33
|
-
load: async ({ cli, ...run }) => await loadTrustedMutationBase({ ...fundingOptionsFromCli(cli), ...run }) ?? (
|
|
32
|
+
load: async ({ cli, adapter, ...run }) => await loadTrustedMutationBase({ ...fundingOptionsFromCli(cli), ...run }) ?? (getAdapterSurface(adapter).actions.trustedMutation === true ? fixturePolicyMutationBase({ artifactsDir: run.artifactsDir, rootRecipe: run.rootRecipe }) : void 0),
|
|
33
|
+
// Nothing to bind (undefined): recipe-cli keeps the runner it planned with.
|
|
34
34
|
authorize: async (base, plan) => {
|
|
35
|
-
if (!base
|
|
35
|
+
if (!base) return void 0;
|
|
36
36
|
if (isFixturePolicyBase(base) && !plan.nodes.some((node) => node.capabilities.includes("external-mutation"))) {
|
|
37
|
-
return
|
|
37
|
+
return void 0;
|
|
38
38
|
}
|
|
39
39
|
return authorizeTrustedMutationPlan(base, plan);
|
|
40
40
|
}
|
|
@@ -42,16 +42,6 @@ const metaMaskEngine = {
|
|
|
42
42
|
console: consoleClassifier,
|
|
43
43
|
runnerIncludes: ["src", "adapters", "library", "bin", "dist", "package.json"]
|
|
44
44
|
};
|
|
45
|
-
const NO_PLAN_MUTATION = /* @__PURE__ */ Symbol("no plan mutation");
|
|
46
|
-
function takesTrustedMutation(cli) {
|
|
47
|
-
let adapter;
|
|
48
|
-
try {
|
|
49
|
-
({ adapter } = resolveAdapter(cli));
|
|
50
|
-
} catch {
|
|
51
|
-
return false;
|
|
52
|
-
}
|
|
53
|
-
return getAdapterSurface(adapter).actions.trustedMutation === true;
|
|
54
|
-
}
|
|
55
45
|
export {
|
|
56
46
|
metaMaskEngine
|
|
57
47
|
};
|