@deeeed/metamask-harness 0.83.0 → 0.85.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 +62 -0
- package/README.md +8 -1
- package/adapters/extension/console-tail.mjs +11 -7
- package/adapters/extension/ensure-browser.sh +17 -18
- package/adapters/manifest.json +14 -110
- package/adapters/mobile/start-console-forwarder.sh +8 -0
- package/adapters/shared/resolve-slot-ports-core.mjs +48 -6
- package/adapters/shared/session-lock.mjs +43 -0
- package/adapters/web-dapp/metamask-signer.mjs +212 -0
- package/dist/adapters/extension/actions.js +5 -0
- package/dist/adapters/extension/console-capture.js +18 -0
- package/dist/adapters/extension/fixtures.js +2 -0
- package/dist/adapters/extension/overlay.js +5 -4
- package/dist/adapters/extension/run.js +4 -2
- package/dist/adapters/extension/runtime.js +73 -18
- package/dist/adapters/mobile/devices.js +5 -1
- package/dist/adapters/mobile/launch.js +19 -2
- package/dist/adapters/mobile/provision.js +4 -2
- package/dist/adapters/mobile/run.js +3 -2
- package/dist/adapters/mobile/surface.js +1 -2
- package/dist/adapters/mobile/verify.js +15 -7
- package/dist/adapters/plugin-surface.js +14 -42
- package/dist/adapters/session-lock.js +23 -0
- package/dist/adapters/web-dapp/actions.js +7 -5
- package/dist/adapters/web-dapp/console-capture.js +2 -20
- package/dist/adapters/web-dapp/run.js +25 -24
- package/dist/adapters/web-dapp/surface.js +32 -108
- package/dist/adapters.js +5 -3
- package/dist/command-contract.js +1351 -643
- package/dist/commands/check.js +5 -7
- package/dist/commands/debug.js +2 -2
- package/dist/commands/doctor.js +3 -4
- package/dist/commands/domain.js +4 -3
- package/dist/commands/fixtures.js +3 -3
- package/dist/commands/help.js +6 -5
- package/dist/commands/launch/device.js +1 -1
- package/dist/commands/launch/extension.js +9 -7
- package/dist/commands/logs.js +2 -2
- package/dist/commands/prepare.js +4 -4
- package/dist/commands/provision.js +2 -2
- package/dist/commands/review.js +8 -10
- package/dist/commands/runtime-launch.js +2 -0
- package/dist/commands/status.js +4 -2
- package/dist/commands/task-init.js +2 -3
- package/dist/console-classifier.js +3 -2
- package/dist/doctor.js +11 -4
- package/dist/mm-harness-cli.js +241 -1210
- package/dist/paths.js +9 -4
- package/dist/recipe-security.js +1 -1
- package/dist/review/knowledge.js +10 -2
- package/dist/runner.js +18 -3
- package/docs/CONTRIBUTING.md +14 -4
- package/docs/RECIPES.md +14 -1
- package/docs/cli-contract.md +8 -7
- package/docs/feature-guard.md +1 -1
- package/library/actions/extension/app/assert_no_console_errors.mjs +95 -0
- package/library/actions/extension/platform/cdp.mjs +26 -0
- package/library/actions/mobile/app/assert_no_console_errors.mjs +129 -0
- package/library/actions/shared/console-assert.mjs +174 -0
- package/library/actions/shared/console-collector.mjs +11 -4
- package/library/actions/shared/console-findings.mjs +108 -7
- package/library/actions/web-dapp/app/assert_no_console_errors.mjs +10 -54
- package/library/actions/web-dapp/app/launch.mjs +1 -1
- package/library/actions/web-dapp/platform/adapter-web-lib.mjs +18 -0
- package/library/actions/web-dapp/platform/hud.mjs +3 -10
- package/library/actions/web-dapp/platform/page.mjs +63 -18
- package/library/actions/web-dapp/platform/runtime.mjs +30 -179
- package/library/diagnostics/console-allowlist.json +13 -0
- package/library/manifests/extension.action-manifest.json +36 -0
- package/library/manifests/mobile.action-manifest.json +36 -0
- package/library/manifests/web-dapp.action-manifest.json +1 -1
- package/library/recipes/core/health/check.recipe.json +40 -0
- package/library/recipes/extension/health/check.recipe.json +71 -0
- package/library/recipes/mobile/health/check.recipe.json +71 -0
- package/package.json +9 -9
- package/scripts/completions.sh +1 -1
- package/site/assets/help-recipes.json +2 -1
- package/adapters/web-dapp/cleanup.mjs +0 -69
- package/adapters/web-dapp/inject.mjs +0 -75
- package/adapters/web-dapp/launch.mjs +0 -631
- package/adapters/web-dapp/lib/blocked-hosts.mjs +0 -24
- package/adapters/web-dapp/lib/cdp-client.mjs +0 -84
- package/adapters/web-dapp/lib/display.mjs +0 -115
- package/adapters/web-dapp/lib/hud.mjs +0 -148
- package/adapters/web-dapp/lib/policy-fence.mjs +0 -119
- package/adapters/web-dapp/lib/policy.mjs +0 -84
- package/adapters/web-dapp/lib/processes.mjs +0 -187
- package/adapters/web-dapp/lib/readiness.mjs +0 -272
- package/adapters/web-dapp/stop.mjs +0 -38
- package/adapters/web-dapp/verify.mjs +0 -51
- package/adapters/web-dapp/wallet-host.mjs +0 -520
- package/dist/adapters/web-dapp/doctor-checks.js +0 -33
- package/dist/adapters/web-dapp/launch.js +0 -87
- package/dist/adapters/web-dapp/overlay.js +0 -21
- package/dist/adapters/web-dapp/readiness.js +0 -10
- package/dist/adapters/web-dapp/stop.js +0 -20
- package/dist/cli.js +0 -202
|
@@ -4,7 +4,7 @@ import crypto from "node:crypto";
|
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { runnerDir } from "../../paths.js";
|
|
7
|
-
const USAGE = "Usage: mm-harness verify --adapter mobile -- [--target <metamask-mobile>] [--artifacts-dir <dir>] [--static-only] [--platform ios|android] [--preflight-mode fast|auto|default|rebuild-native|clean] [--no-auto-start]";
|
|
7
|
+
const USAGE = "Usage: mm-harness verify --adapter mobile -- [--target <metamask-mobile>] [--artifacts-dir <dir>] [--static-only] [--platform ios|android] [--watcher-port <port>] [--preflight-mode fast|auto|default|rebuild-native|clean] [--no-auto-start]";
|
|
8
8
|
const ENV_FILES = [".js.env", ".env", ".env.local"];
|
|
9
9
|
const PREFLIGHT_MODES = ["fast", "auto", "default", "rebuild-native", "clean"];
|
|
10
10
|
const AUTO_START_TRUE = ["1", "true", "TRUE", "True", "yes", "YES", "Yes", "on", "ON", "On"];
|
|
@@ -36,6 +36,7 @@ function parseMobileVerifyArgs(argv, env = process.env) {
|
|
|
36
36
|
let platform = env.RECIPE_HARNESS_PLATFORM || env.PLATFORM || "ios";
|
|
37
37
|
let preflightMode = env.RECIPE_HARNESS_MOBILE_PREFLIGHT_MODE || "fast";
|
|
38
38
|
let autoStart = env.RECIPE_HARNESS_MOBILE_AUTO_START ?? "false";
|
|
39
|
+
let watcherPort;
|
|
39
40
|
for (let i = 0; i < argv.length; i += 1) {
|
|
40
41
|
const arg = argv[i];
|
|
41
42
|
const needsValue = () => {
|
|
@@ -65,6 +66,12 @@ function parseMobileVerifyArgs(argv, env = process.env) {
|
|
|
65
66
|
platform = value;
|
|
66
67
|
break;
|
|
67
68
|
}
|
|
69
|
+
case "--watcher-port": {
|
|
70
|
+
const value = needsValue();
|
|
71
|
+
if (value === void 0) return { kind: "error", message: "Missing value for --watcher-port", exitCode: 2 };
|
|
72
|
+
watcherPort = value;
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
68
75
|
case "--preflight-mode": {
|
|
69
76
|
const value = needsValue();
|
|
70
77
|
if (value === void 0) return { kind: "error", message: "Missing value for --preflight-mode", exitCode: 2 };
|
|
@@ -101,7 +108,8 @@ function parseMobileVerifyArgs(argv, env = process.env) {
|
|
|
101
108
|
staticOnly,
|
|
102
109
|
platform,
|
|
103
110
|
preflightMode,
|
|
104
|
-
autoStart: AUTO_START_TRUE.includes(autoStart)
|
|
111
|
+
autoStart: AUTO_START_TRUE.includes(autoStart),
|
|
112
|
+
...watcherPort === void 0 ? {} : { watcherPort }
|
|
105
113
|
}
|
|
106
114
|
};
|
|
107
115
|
}
|
|
@@ -141,8 +149,8 @@ function buildMobileVerifySummary(input) {
|
|
|
141
149
|
generatedAt: input.generatedAt
|
|
142
150
|
};
|
|
143
151
|
}
|
|
144
|
-
function resolveWatcherPort(target, env = process.env) {
|
|
145
|
-
const explicit = env.WATCHER_PORT || "";
|
|
152
|
+
function resolveWatcherPort(target, env = process.env, flag) {
|
|
153
|
+
const explicit = flag || env.RECIPE_WATCHER_PORT || env.WATCHER_PORT || "";
|
|
146
154
|
if (explicit) return explicit;
|
|
147
155
|
for (const file of ENV_FILES) {
|
|
148
156
|
const full = path.join(target, file);
|
|
@@ -299,7 +307,7 @@ async function runMobileVerify(argv, io = defaultIo) {
|
|
|
299
307
|
io.err(parsed.message);
|
|
300
308
|
return parsed.exitCode;
|
|
301
309
|
}
|
|
302
|
-
const { staticOnly, platform, preflightMode, autoStart } = parsed.options;
|
|
310
|
+
const { staticOnly, platform, preflightMode, autoStart, watcherPort } = parsed.options;
|
|
303
311
|
const target = path.resolve(parsed.options.target);
|
|
304
312
|
if (!fs.existsSync(target)) {
|
|
305
313
|
io.err(`Refusing mobile verify: target checkout does not exist: ${target}`);
|
|
@@ -344,9 +352,9 @@ async function runMobileVerify(argv, io = defaultIo) {
|
|
|
344
352
|
let fixtureStatus = null;
|
|
345
353
|
let portHolder = null;
|
|
346
354
|
if (!staticOnly) {
|
|
347
|
-
const port = resolveWatcherPort(target);
|
|
355
|
+
const port = resolveWatcherPort(target, process.env, watcherPort);
|
|
348
356
|
if (!isNumericWatcherPort(port)) {
|
|
349
|
-
io.err(`Refusing mobile verify: resolved watcher port is not numeric: '${port}' (check WATCHER_PORT)`);
|
|
357
|
+
io.err(`Refusing mobile verify: resolved watcher port is not numeric: '${port}' (check --watcher-port, RECIPE_WATCHER_PORT or WATCHER_PORT)`);
|
|
350
358
|
return 2;
|
|
351
359
|
}
|
|
352
360
|
let deviceTarget = mobileDeviceTarget(target, platform, process.env);
|
|
@@ -1,16 +1,9 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
adapterSelectionFailureOut,
|
|
6
|
-
ensureAdapterLoaded,
|
|
7
|
-
selectedAdapterId
|
|
8
|
-
} from "@farmslot/recipe-cli/harness";
|
|
3
|
+
import { fencePolicy, POLICY_DIGEST_ENV, POLICY_ENV, PolicyFenceError } from "@farmslot/adapter-web/web-dapp";
|
|
4
|
+
import { adapterPlugin, AdapterPluginError } from "@farmslot/recipe-cli/harness";
|
|
9
5
|
import { libraryAdapterFiles } from "@farmslot/recipe-runner";
|
|
10
|
-
import { POLICY_ENV } from "../../adapters/web-dapp/lib/policy.mjs";
|
|
11
|
-
import { POLICY_DIGEST_ENV, PolicyFenceError, fencePolicy } from "../../adapters/web-dapp/lib/policy-fence.mjs";
|
|
12
6
|
import { configPath, declaredLibraryPathEnv, readConfig } from "../review/knowledge.js";
|
|
13
|
-
import { adapterRegistry } from "./registry.js";
|
|
14
7
|
import { bindWebDappPolicy } from "./web-dapp/surface.js";
|
|
15
8
|
function configuredLibrarySources() {
|
|
16
9
|
let libraries;
|
|
@@ -29,38 +22,6 @@ function operatorLibraryEnv() {
|
|
|
29
22
|
else env.RECIPE_LIBRARY_PATH = declared.value;
|
|
30
23
|
return env;
|
|
31
24
|
}
|
|
32
|
-
async function loadSelectedAdapter(command, args) {
|
|
33
|
-
const passthrough = args.indexOf("--");
|
|
34
|
-
const own = passthrough < 0 ? args : args.slice(0, passthrough);
|
|
35
|
-
const libraries = [];
|
|
36
|
-
own.forEach((arg, index) => {
|
|
37
|
-
if (arg === "--library" && own[index + 1] !== void 0) libraries.push(own[index + 1]);
|
|
38
|
-
else if (arg.startsWith("--library=")) libraries.push(arg.slice("--library=".length));
|
|
39
|
-
});
|
|
40
|
-
const selected = selectedAdapterId(args);
|
|
41
|
-
const builtin = selected !== void 0 && adapterRegistry.has(selected);
|
|
42
|
-
try {
|
|
43
|
-
await ensureAdapterLoaded(selected, {
|
|
44
|
-
libraries,
|
|
45
|
-
configured: configuredLibrarySources(),
|
|
46
|
-
env: operatorLibraryEnv(),
|
|
47
|
-
adopt: adoptPluginAdapter
|
|
48
|
-
});
|
|
49
|
-
await fenceBoundPolicy();
|
|
50
|
-
return void 0;
|
|
51
|
-
} catch (error) {
|
|
52
|
-
const refusal = error;
|
|
53
|
-
if (typeof refusal.code !== "string" || typeof refusal.userAction !== "string") throw error;
|
|
54
|
-
if (builtin && refusal.code !== "ADAPTER_ID_CONFLICT") {
|
|
55
|
-
return void 0;
|
|
56
|
-
}
|
|
57
|
-
return adapterSelectionFailureOut(own.includes("--json"), command, {
|
|
58
|
-
code: refusal.code,
|
|
59
|
-
message: String(refusal.message),
|
|
60
|
-
userAction: refusal.userAction
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
25
|
const ADAPTER_PLUGIN_ENV = "METAMASK_RECIPE_ADAPTER_PLUGIN";
|
|
65
26
|
let boundPolicy;
|
|
66
27
|
async function fenceBoundPolicy() {
|
|
@@ -80,6 +41,16 @@ async function fenceBoundPolicy() {
|
|
|
80
41
|
throw error;
|
|
81
42
|
}
|
|
82
43
|
}
|
|
44
|
+
async function afterAdapterLoad() {
|
|
45
|
+
try {
|
|
46
|
+
await fenceBoundPolicy();
|
|
47
|
+
} catch (error) {
|
|
48
|
+
if (error instanceof PolicyFenceError) {
|
|
49
|
+
throw new AdapterPluginError("ADAPTER_PLUGIN_INVALID", error.message, error.userAction);
|
|
50
|
+
}
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
83
54
|
function adoptPluginAdapter(adapter) {
|
|
84
55
|
const surface = bindWebDappPolicy(adapter);
|
|
85
56
|
const policy = adapter;
|
|
@@ -120,7 +91,8 @@ function adoptPluginAdapter(adapter) {
|
|
|
120
91
|
export {
|
|
121
92
|
ADAPTER_PLUGIN_ENV,
|
|
122
93
|
adoptPluginAdapter,
|
|
94
|
+
afterAdapterLoad,
|
|
123
95
|
configuredLibrarySources,
|
|
124
|
-
|
|
96
|
+
fenceBoundPolicy,
|
|
125
97
|
operatorLibraryEnv
|
|
126
98
|
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Writable } from "node:stream";
|
|
2
|
+
import { EXIT, optionFlag } from "@farmslot/recipe-cli/harness";
|
|
3
|
+
import {
|
|
4
|
+
macosSessionLocked,
|
|
5
|
+
SESSION_LOCKED_MESSAGE,
|
|
6
|
+
SESSION_LOCKED_USER_ACTION
|
|
7
|
+
} from "../../adapters/shared/session-lock.mjs";
|
|
8
|
+
import { emitProofFailure } from "./proof-failure.js";
|
|
9
|
+
function refuseLockedSession(json, options = {}) {
|
|
10
|
+
if (macosSessionLocked() !== true) return null;
|
|
11
|
+
const error = { code: "SESSION_LOCKED", message: SESSION_LOCKED_MESSAGE, userAction: SESSION_LOCKED_USER_ACTION };
|
|
12
|
+
if (optionFlag(options, "jsonStream")) emitRunStreamError(error);
|
|
13
|
+
emitProofFailure(json, { recoverable: false, error });
|
|
14
|
+
return EXIT.infra;
|
|
15
|
+
}
|
|
16
|
+
function emitRunStreamError(error) {
|
|
17
|
+
const line = JSON.stringify({ schemaVersion: 1, command: "run", event: "error", error, ts: (/* @__PURE__ */ new Date()).toISOString() });
|
|
18
|
+
Writable.prototype.write.call(process.stdout, `${line}
|
|
19
|
+
`);
|
|
20
|
+
}
|
|
21
|
+
export {
|
|
22
|
+
refuseLockedSession
|
|
23
|
+
};
|
|
@@ -9,7 +9,7 @@ const webDappActions = {
|
|
|
9
9
|
"app.performance_assert"
|
|
10
10
|
],
|
|
11
11
|
cdpTarget: { transport: "chrome-cdp", probePath: "json/version" },
|
|
12
|
-
ui: (harness
|
|
12
|
+
ui: (harness) => ({
|
|
13
13
|
base: harness.createCdpWebUiTransport({
|
|
14
14
|
async withPage(input, callback) {
|
|
15
15
|
const { withAppPage } = await import("../../../library/actions/web-dapp/platform/page.mjs");
|
|
@@ -30,14 +30,16 @@ const webDappActions = {
|
|
|
30
30
|
async execute(action, node, context, standard) {
|
|
31
31
|
if (action === "app.hud") {
|
|
32
32
|
const { showAppHud } = await import("../../../library/actions/web-dapp/platform/hud.mjs");
|
|
33
|
-
return showAppHud(
|
|
34
|
-
{ action, node, context: { nodeId: context.nodeId, projectRoot: context.projectRoot } },
|
|
35
|
-
{ hudPolicy }
|
|
36
|
-
);
|
|
33
|
+
return showAppHud({ action, node, context: { nodeId: context.nodeId, projectRoot: context.projectRoot } });
|
|
37
34
|
}
|
|
38
35
|
return standard();
|
|
39
36
|
}
|
|
40
37
|
}),
|
|
38
|
+
// Deletes hud.json (the wallet host redraws from it) and the HUD on the app tabs.
|
|
39
|
+
async clearHud(projectRoot) {
|
|
40
|
+
const { showAppHud } = await import("../../../library/actions/web-dapp/platform/hud.mjs");
|
|
41
|
+
await showAppHud({ node: { clear: true }, context: { nodeId: "hud-clear", projectRoot } });
|
|
42
|
+
},
|
|
41
43
|
// ensure_positions and ensure_orders with an open or present state place an order.
|
|
42
44
|
inputFindings: (nodeId, node) => perpsOrderInputFindings(nodeId, node, { ensureChecksOnly: false })
|
|
43
45
|
};
|
|
@@ -1,23 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
-
import
|
|
3
|
-
import { recipeRuntimePath } from "@farmslot/recipe-cli/harness";
|
|
2
|
+
import { WEB_DAPP_CONSOLE_FILES, webDappAdapterId, webDappRuntimeFile } from "@farmslot/adapter-web/web-dapp";
|
|
4
3
|
import { verifyCapture } from "../../../library/actions/shared/console-collector.mjs";
|
|
5
4
|
import { ensureConsoleCollector } from "../console-capture.js";
|
|
6
|
-
import { webDappAdapterId } from "../../../adapters/web-dapp/lib/policy.mjs";
|
|
7
|
-
const WEB_DAPP_CONSOLE_FILES = Object.freeze({
|
|
8
|
-
app: "app-console.log",
|
|
9
|
-
extension: "extension-console.log",
|
|
10
|
-
collectorLog: "console-tail.log",
|
|
11
|
-
pidFile: "console-tail.pid"
|
|
12
|
-
});
|
|
13
|
-
function webDappRuntimeFile(target, name) {
|
|
14
|
-
let root = path.resolve(target);
|
|
15
|
-
try {
|
|
16
|
-
root = fs.realpathSync(root);
|
|
17
|
-
} catch {
|
|
18
|
-
}
|
|
19
|
-
return recipeRuntimePath(root, webDappAdapterId(), name);
|
|
20
|
-
}
|
|
21
5
|
function webDappCaptureFiles(target) {
|
|
22
6
|
return {
|
|
23
7
|
extensionLog: webDappRuntimeFile(target, WEB_DAPP_CONSOLE_FILES.extension),
|
|
@@ -54,10 +38,8 @@ async function verifyWebDappConsoleControl(capture) {
|
|
|
54
38
|
return verifyCapture({ cdpPort: capture.cdpPort, log: capture.pageLog, target: "page", appOrigin: browser.appOrigin });
|
|
55
39
|
}
|
|
56
40
|
export {
|
|
57
|
-
WEB_DAPP_CONSOLE_FILES,
|
|
58
41
|
ensureWebDappConsoleCapture,
|
|
59
42
|
verifyWebDappConsoleControl,
|
|
60
43
|
webDappCaptureFiles,
|
|
61
|
-
webDappCdpPort
|
|
62
|
-
webDappRuntimeFile
|
|
44
|
+
webDappCdpPort
|
|
63
45
|
};
|
|
@@ -1,44 +1,43 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
1
2
|
import path from "node:path";
|
|
3
|
+
import { ownsProcess, webDappRuntimeFile } from "@farmslot/adapter-web/web-dapp";
|
|
2
4
|
import { isRecord } from "@farmslot/recipe-cli/harness";
|
|
3
5
|
import { verifyOwnedCollectorControl } from "../console-capture.js";
|
|
6
|
+
import { refuseLockedSession } from "../session-lock.js";
|
|
4
7
|
import { devicePlatformFromEnv } from "../performance/device-platform.js";
|
|
5
8
|
import {
|
|
6
9
|
ensureWebDappConsoleCapture,
|
|
7
10
|
webDappCaptureFiles,
|
|
8
11
|
webDappCdpPort,
|
|
9
|
-
webDappRuntimeFile,
|
|
10
12
|
verifyWebDappConsoleControl
|
|
11
13
|
} from "./console-capture.js";
|
|
14
|
+
async function webDappRunHeadless(target) {
|
|
15
|
+
const stateFile = webDappRuntimeFile(target, "browser.json");
|
|
16
|
+
try {
|
|
17
|
+
const state = JSON.parse(fs.readFileSync(stateFile, "utf8"));
|
|
18
|
+
if (isRecord(state) && isRecord(state.browser) && typeof state.browser.headless === "boolean") {
|
|
19
|
+
if (ownsProcess("browser", state.browserPid, path.dirname(stateFile))) return state.browser.headless;
|
|
20
|
+
}
|
|
21
|
+
} catch {
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
const { resolveHeadless } = await import("../../../library/actions/web-dapp/platform/adapter-web-lib.mjs");
|
|
25
|
+
return resolveHeadless();
|
|
26
|
+
} catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const webDappRun = {
|
|
31
|
+
runtimeCheck: ({ target, options, json }) => async () => await webDappRunHeadless(target) === false ? refuseLockedSession(json, options) : null
|
|
32
|
+
};
|
|
12
33
|
const webDappDiagnostics = {
|
|
13
34
|
console: {
|
|
14
35
|
start: (projectRoot) => ensureWebDappConsoleCapture(projectRoot),
|
|
15
36
|
files: (projectRoot) => webDappCaptureFiles(projectRoot),
|
|
16
37
|
cdpPort: () => webDappCdpPort(),
|
|
17
38
|
verifyControl: (capture) => verifyOwnedCollectorControl(capture, () => verifyWebDappConsoleControl(capture))
|
|
18
|
-
},
|
|
19
|
-
requestLog: {
|
|
20
|
-
path: (projectRoot) => webDappRuntimeFile(projectRoot, "wallet-requests.jsonl"),
|
|
21
|
-
findings: (lines) => walletRequestFindings(lines)
|
|
22
39
|
}
|
|
23
40
|
};
|
|
24
|
-
function walletRequestFindings(lines) {
|
|
25
|
-
const findings = [];
|
|
26
|
-
for (const line of lines) {
|
|
27
|
-
let entry;
|
|
28
|
-
try {
|
|
29
|
-
entry = JSON.parse(line);
|
|
30
|
-
} catch {
|
|
31
|
-
continue;
|
|
32
|
-
}
|
|
33
|
-
if (!isRecord(entry)) continue;
|
|
34
|
-
let text = null;
|
|
35
|
-
if (entry.kind === "blocked-mainnet" && entry.probe !== true) text = `blocked-mainnet: ${String(entry.transport ?? "request")} to ${String(entry.url ?? "?")}`;
|
|
36
|
-
else if (entry.kind === "refused-mainnet") text = `refused-mainnet: ${String(entry.method ?? "?")} (${String(entry.reason ?? "?")})`;
|
|
37
|
-
else if (entry.kind === "unattributed") text = `unattributed wallet ${entry.binding === "request" ? "request" : "log entry"}${typeof entry.method === "string" ? ` (${entry.method})` : ""}`;
|
|
38
|
-
if (text) findings.push({ level: "error", source: "wallet", text });
|
|
39
|
-
}
|
|
40
|
-
return findings;
|
|
41
|
-
}
|
|
42
41
|
const webDappObservation = {
|
|
43
42
|
network: {
|
|
44
43
|
backend: async (target, env, artifactsDir) => (await import("../extension/network-observer.js")).createLoadedExtensionNetworkBackend(
|
|
@@ -59,5 +58,7 @@ const webDappObservation = {
|
|
|
59
58
|
};
|
|
60
59
|
export {
|
|
61
60
|
webDappDiagnostics,
|
|
62
|
-
webDappObservation
|
|
61
|
+
webDappObservation,
|
|
62
|
+
webDappRun,
|
|
63
|
+
webDappRunHeadless
|
|
63
64
|
};
|
|
@@ -1,82 +1,38 @@
|
|
|
1
|
-
import { shellQuote } from "@farmslot/recipe-cli/harness";
|
|
2
1
|
import path from "node:path";
|
|
3
2
|
import { defineAdapter } from "@farmslot/adapter-sdk";
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
import {
|
|
4
|
+
bindWebDappPolicy as bindPolicy,
|
|
5
|
+
createWebDappAdapter,
|
|
6
|
+
POLICY_ENV,
|
|
7
|
+
SIGNER_MODULE_ENV,
|
|
8
|
+
webDappAdapterId,
|
|
9
|
+
webDappPolicy
|
|
10
|
+
} from "@farmslot/adapter-web/web-dapp";
|
|
11
|
+
import { runnerDir } from "../../paths.js";
|
|
8
12
|
import { adapterRegistry } from "../registry.js";
|
|
9
13
|
import { webDappActions } from "./actions.js";
|
|
10
|
-
import {
|
|
11
|
-
|
|
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
|
-
}
|
|
14
|
+
import { webDappDiagnostics, webDappObservation, webDappRun } from "./run.js";
|
|
15
|
+
const METAMASK_SIGNER_MODULE = path.join(runnerDir, "adapters", "web-dapp", "metamask-signer.mjs");
|
|
34
16
|
function bindWebDappPolicy(adapter) {
|
|
17
|
+
bindPolicy(adapter);
|
|
35
18
|
if (adapter.extends === "web-dapp" && typeof adapter.policy?.module === "string") {
|
|
36
|
-
process.env[
|
|
19
|
+
process.env[SIGNER_MODULE_ENV] = METAMASK_SIGNER_MODULE;
|
|
37
20
|
}
|
|
38
21
|
return adapter;
|
|
39
22
|
}
|
|
40
|
-
const
|
|
23
|
+
const generic = createWebDappAdapter({
|
|
41
24
|
id: "web-dapp",
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
},
|
|
25
|
+
signerModule: METAMASK_SIGNER_MODULE,
|
|
26
|
+
cli: "mm-harness",
|
|
27
|
+
hooks: {
|
|
28
|
+
actions: webDappActions,
|
|
29
|
+
run: webDappRun,
|
|
30
|
+
diagnostics: webDappDiagnostics,
|
|
31
|
+
observation: webDappObservation
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
const webDappSurface = defineAdapter({
|
|
35
|
+
...generic,
|
|
80
36
|
runwayProvision: {
|
|
81
37
|
async run(target, options) {
|
|
82
38
|
return {
|
|
@@ -94,41 +50,12 @@ const webDappSurface = defineAdapter({
|
|
|
94
50
|
};
|
|
95
51
|
}
|
|
96
52
|
},
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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"
|
|
53
|
+
// adapter-web's slot readiness (with MetaMask's own checks) and capture providers.
|
|
54
|
+
readiness: {
|
|
55
|
+
liveChecks: generic.readiness.liveChecks,
|
|
56
|
+
captureProviders: generic.readiness.captureProviders,
|
|
57
|
+
compatibilityMode: (target) => process.env[POLICY_ENV] && webDappPolicy().checkout.matches(target) ? "slot browser over CDP (website; no product bridge)" : "unsupported/no bridge"
|
|
126
58
|
},
|
|
127
|
-
launch: async (context) => (await import("./launch.js")).launchWebDapp(context),
|
|
128
|
-
actions: webDappActions,
|
|
129
|
-
harness: webDappHarness,
|
|
130
|
-
runtimeContext: webDappRuntimeContext,
|
|
131
|
-
readiness: webDappReadiness,
|
|
132
59
|
fixtures: { operations: ["init", "sync", "set"] },
|
|
133
60
|
debug: {
|
|
134
61
|
args: ({ devMenu }) => [
|
|
@@ -136,14 +63,11 @@ const webDappSurface = defineAdapter({
|
|
|
136
63
|
"--action",
|
|
137
64
|
devMenu ? "dev-menu" : "debug"
|
|
138
65
|
]
|
|
139
|
-
}
|
|
140
|
-
diagnostics: webDappDiagnostics,
|
|
141
|
-
observation: webDappObservation
|
|
66
|
+
}
|
|
142
67
|
});
|
|
143
68
|
adapterRegistry.register(webDappSurface);
|
|
144
69
|
export {
|
|
70
|
+
METAMASK_SIGNER_MODULE,
|
|
145
71
|
bindWebDappPolicy,
|
|
146
|
-
webDappAppPort,
|
|
147
|
-
webDappCdpPort,
|
|
148
72
|
webDappSurface
|
|
149
73
|
};
|
package/dist/adapters.js
CHANGED
|
@@ -90,7 +90,9 @@ const LIVE_ONLY_APP_ACTIONS = /* @__PURE__ */ new Set([
|
|
|
90
90
|
"app.network_capture",
|
|
91
91
|
"app.network_assert",
|
|
92
92
|
"app.performance_capture",
|
|
93
|
-
"app.performance_assert"
|
|
93
|
+
"app.performance_assert",
|
|
94
|
+
// Proves a capture of the running app; a static result would pass on no evidence.
|
|
95
|
+
"metamask.app.assert_no_console_errors"
|
|
94
96
|
]);
|
|
95
97
|
function requiresLiveAdapter(platform, action) {
|
|
96
98
|
return LIVE_ONLY_ACTIONS.has(action) || Boolean(getAdapterSurface(platform).actions.liveOnly?.includes(action)) || LIVE_ONLY_WALLET_ACTIONS.has(action) || LIVE_ONLY_FEATURE_FLAG_ACTIONS.has(action) || LIVE_ONLY_APP_ACTIONS.has(action);
|
|
@@ -290,7 +292,7 @@ function createMetaMaskSemanticAdapters(platform, declaredCustomActions = [], pr
|
|
|
290
292
|
)
|
|
291
293
|
);
|
|
292
294
|
}
|
|
293
|
-
function createMetaMaskUiTransport(platform, harness, preparedLiveAdapters
|
|
295
|
+
function createMetaMaskUiTransport(platform, harness, preparedLiveAdapters) {
|
|
294
296
|
const ui = getAdapterSurface(platform).actions.ui;
|
|
295
297
|
if (!ui) {
|
|
296
298
|
return {
|
|
@@ -301,7 +303,7 @@ function createMetaMaskUiTransport(platform, harness, preparedLiveAdapters, { hu
|
|
|
301
303
|
}
|
|
302
304
|
};
|
|
303
305
|
}
|
|
304
|
-
const { base, execute } = ui(harness, { hudPolicy });
|
|
306
|
+
const { base, execute } = ui(harness, { hudPolicy: "show" });
|
|
305
307
|
async function standard(action, node, context) {
|
|
306
308
|
if (action === "ui.navigate") {
|
|
307
309
|
const live = await runLiveFirst(
|