@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.
Files changed (97) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/README.md +8 -1
  3. package/adapters/extension/console-tail.mjs +11 -7
  4. package/adapters/extension/ensure-browser.sh +17 -18
  5. package/adapters/manifest.json +14 -110
  6. package/adapters/mobile/start-console-forwarder.sh +8 -0
  7. package/adapters/shared/resolve-slot-ports-core.mjs +48 -6
  8. package/adapters/shared/session-lock.mjs +43 -0
  9. package/adapters/web-dapp/metamask-signer.mjs +212 -0
  10. package/dist/adapters/extension/actions.js +5 -0
  11. package/dist/adapters/extension/console-capture.js +18 -0
  12. package/dist/adapters/extension/fixtures.js +2 -0
  13. package/dist/adapters/extension/overlay.js +5 -4
  14. package/dist/adapters/extension/run.js +4 -2
  15. package/dist/adapters/extension/runtime.js +73 -18
  16. package/dist/adapters/mobile/devices.js +5 -1
  17. package/dist/adapters/mobile/launch.js +19 -2
  18. package/dist/adapters/mobile/provision.js +4 -2
  19. package/dist/adapters/mobile/run.js +3 -2
  20. package/dist/adapters/mobile/surface.js +1 -2
  21. package/dist/adapters/mobile/verify.js +15 -7
  22. package/dist/adapters/plugin-surface.js +14 -42
  23. package/dist/adapters/session-lock.js +23 -0
  24. package/dist/adapters/web-dapp/actions.js +7 -5
  25. package/dist/adapters/web-dapp/console-capture.js +2 -20
  26. package/dist/adapters/web-dapp/run.js +25 -24
  27. package/dist/adapters/web-dapp/surface.js +32 -108
  28. package/dist/adapters.js +5 -3
  29. package/dist/command-contract.js +1351 -643
  30. package/dist/commands/check.js +5 -7
  31. package/dist/commands/debug.js +2 -2
  32. package/dist/commands/doctor.js +3 -4
  33. package/dist/commands/domain.js +4 -3
  34. package/dist/commands/fixtures.js +3 -3
  35. package/dist/commands/help.js +6 -5
  36. package/dist/commands/launch/device.js +1 -1
  37. package/dist/commands/launch/extension.js +9 -7
  38. package/dist/commands/logs.js +2 -2
  39. package/dist/commands/prepare.js +4 -4
  40. package/dist/commands/provision.js +2 -2
  41. package/dist/commands/review.js +8 -10
  42. package/dist/commands/runtime-launch.js +2 -0
  43. package/dist/commands/status.js +4 -2
  44. package/dist/commands/task-init.js +2 -3
  45. package/dist/console-classifier.js +3 -2
  46. package/dist/doctor.js +11 -4
  47. package/dist/mm-harness-cli.js +241 -1210
  48. package/dist/paths.js +9 -4
  49. package/dist/recipe-security.js +1 -1
  50. package/dist/review/knowledge.js +10 -2
  51. package/dist/runner.js +18 -3
  52. package/docs/CONTRIBUTING.md +14 -4
  53. package/docs/RECIPES.md +14 -1
  54. package/docs/cli-contract.md +8 -7
  55. package/docs/feature-guard.md +1 -1
  56. package/library/actions/extension/app/assert_no_console_errors.mjs +95 -0
  57. package/library/actions/extension/platform/cdp.mjs +26 -0
  58. package/library/actions/mobile/app/assert_no_console_errors.mjs +129 -0
  59. package/library/actions/shared/console-assert.mjs +174 -0
  60. package/library/actions/shared/console-collector.mjs +11 -4
  61. package/library/actions/shared/console-findings.mjs +108 -7
  62. package/library/actions/web-dapp/app/assert_no_console_errors.mjs +10 -54
  63. package/library/actions/web-dapp/app/launch.mjs +1 -1
  64. package/library/actions/web-dapp/platform/adapter-web-lib.mjs +18 -0
  65. package/library/actions/web-dapp/platform/hud.mjs +3 -10
  66. package/library/actions/web-dapp/platform/page.mjs +63 -18
  67. package/library/actions/web-dapp/platform/runtime.mjs +30 -179
  68. package/library/diagnostics/console-allowlist.json +13 -0
  69. package/library/manifests/extension.action-manifest.json +36 -0
  70. package/library/manifests/mobile.action-manifest.json +36 -0
  71. package/library/manifests/web-dapp.action-manifest.json +1 -1
  72. package/library/recipes/core/health/check.recipe.json +40 -0
  73. package/library/recipes/extension/health/check.recipe.json +71 -0
  74. package/library/recipes/mobile/health/check.recipe.json +71 -0
  75. package/package.json +9 -9
  76. package/scripts/completions.sh +1 -1
  77. package/site/assets/help-recipes.json +2 -1
  78. package/adapters/web-dapp/cleanup.mjs +0 -69
  79. package/adapters/web-dapp/inject.mjs +0 -75
  80. package/adapters/web-dapp/launch.mjs +0 -631
  81. package/adapters/web-dapp/lib/blocked-hosts.mjs +0 -24
  82. package/adapters/web-dapp/lib/cdp-client.mjs +0 -84
  83. package/adapters/web-dapp/lib/display.mjs +0 -115
  84. package/adapters/web-dapp/lib/hud.mjs +0 -148
  85. package/adapters/web-dapp/lib/policy-fence.mjs +0 -119
  86. package/adapters/web-dapp/lib/policy.mjs +0 -84
  87. package/adapters/web-dapp/lib/processes.mjs +0 -187
  88. package/adapters/web-dapp/lib/readiness.mjs +0 -272
  89. package/adapters/web-dapp/stop.mjs +0 -38
  90. package/adapters/web-dapp/verify.mjs +0 -51
  91. package/adapters/web-dapp/wallet-host.mjs +0 -520
  92. package/dist/adapters/web-dapp/doctor-checks.js +0 -33
  93. package/dist/adapters/web-dapp/launch.js +0 -87
  94. package/dist/adapters/web-dapp/overlay.js +0 -21
  95. package/dist/adapters/web-dapp/readiness.js +0 -10
  96. package/dist/adapters/web-dapp/stop.js +0 -20
  97. 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
- adapterPlugin,
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
- loadSelectedAdapter,
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, { hudPolicy }) => ({
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 path from "node:path";
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 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";
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 { 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
- }
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[POLICY_ENV] = adapter.policy.module;
19
+ process.env[SIGNER_MODULE_ENV] = METAMASK_SIGNER_MODULE;
37
20
  }
38
21
  return adapter;
39
22
  }
40
- const webDappSurface = defineAdapter({
23
+ const generic = createWebDappAdapter({
41
24
  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
- },
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
- 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"
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, { hudPolicy = "auto" } = {}) {
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(