@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
@@ -1,8 +1,7 @@
1
- import { detectAdapter, EXIT, usageOut, optionFlag, optionString, parseArgs, shellQuote } from "@farmslot/recipe-cli/harness";
1
+ import { EXIT, contextAdapter, usageOut, optionFlag, optionString, parseArgs, shellQuote } from "@farmslot/recipe-cli/harness";
2
2
  import { spawnSync } from "node:child_process";
3
3
  import fs from "node:fs";
4
4
  import path from "node:path";
5
- import "../adapters/surface.js";
6
5
  const USAGE = "mm-harness check diff [--fix] [--target <repo>] [--adapter <adapter>] [--base <ref>] [--profile fast|full] [--artifacts-dir <dir>] [--json]";
7
6
  const JS_TS_EXT_RE = /\.(?:c|m)?jsx?$|\.tsx?$/u;
8
7
  const FORMAT_EXT_RE = /\.(?:c|m)?jsx?$|\.tsx?$|\.json$|\.md$|\.css$|\.scss$|\.ya?ml$/u;
@@ -20,12 +19,11 @@ async function handleCheck(argv) {
20
19
  return usageOut(json, "check", message, USAGE);
21
20
  }
22
21
  const target = path.resolve(optionString(options, "target") ?? process.cwd());
23
- const explicitAdapter = optionString(options, "adapter") ?? optionString(options, "platform");
24
- const detected = explicitAdapter ?? detectAdapter(target);
25
- if (detected === "ios" || detected === "android") {
26
- return usageOut(json, "check", `unsupported adapter '${detected}'`, USAGE);
22
+ const platform = optionString(options, "adapter") ? void 0 : optionString(options, "platform");
23
+ if (platform === "ios" || platform === "android") {
24
+ return usageOut(json, "check", `unsupported adapter '${platform}'`, USAGE);
27
25
  }
28
- const adapter = detected;
26
+ const adapter = contextAdapter(target);
29
27
  const profileInput = optionString(options, "profile");
30
28
  if (profileInput && profileInput !== "fast" && profileInput !== "full") {
31
29
  return usageOut(json, "check", "--profile must be fast or full", USAGE);
@@ -1,4 +1,4 @@
1
- import { adapterDetectNext, EXIT, flag, parseFlags, resolveFlagsAdapter, spawnScript, targetOf, usageOut } from "@farmslot/recipe-cli/harness";
1
+ import { adapterDetectNext, EXIT, flag, parseFlags, contextAdapter, spawnScript, targetOf, usageOut } from "@farmslot/recipe-cli/harness";
2
2
  import path from "node:path";
3
3
  import { runnerDir } from "../paths.js";
4
4
  import { getAdapterSurface } from "../adapters/surface.js";
@@ -7,7 +7,7 @@ async function handleDebug(argv) {
7
7
  const { options } = parseFlags(argv, DEBUG_BOOLEANS);
8
8
  const json = flag(options, "json");
9
9
  const target = targetOf(options);
10
- const adapter = resolveFlagsAdapter(options, target);
10
+ const adapter = contextAdapter(target);
11
11
  if (!adapter) return usageOut(json, "debug", `could not detect the MetaMask repo type for ${target}`, adapterDetectNext());
12
12
  const surface = getAdapterSurface(adapter);
13
13
  if (surface.headless) return usageOut(json, "debug", `${adapter} is headless; there is no debug console.`, surface.hints.relaunch);
@@ -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 { adapterPluginChecks, 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, contextAdapter, 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";
@@ -18,7 +18,7 @@ function applyDoctorRuntimePorts(options) {
18
18
  process.env.CDP_PORT = cdpPort;
19
19
  process.env.RECIPE_CDP_PORT = cdpPort;
20
20
  }
21
- const watcherPort = optionString(options, "watcherPort") ?? optionString(options, "port") ?? optionString(options, "metroPort");
21
+ const watcherPort = optionString(options, "watcherPort") ?? optionString(options, "port");
22
22
  if (watcherPort) {
23
23
  process.env.WATCHER_PORT = watcherPort;
24
24
  process.env.METRO_PORT = watcherPort;
@@ -33,8 +33,7 @@ async function handleDoctor({ options }) {
33
33
  const expectLive = optionFlag(options, "expectLive") || printReady;
34
34
  const allDevices = optionFlag(options, "allDevices");
35
35
  const platformOption = optionString(options, "platform");
36
- const explicitAdapter = optionString(options, "adapter") ?? (platformOption === "ios" || platformOption === "android" ? "mobile" : platformOption);
37
- const adapter = explicitAdapter ?? detectAdapter(target);
36
+ const adapter = contextAdapter(target);
38
37
  if (!adapter) {
39
38
  return usageOut(json, "doctor", `could not detect the MetaMask repo type for ${target}`, adapterDetectNext());
40
39
  }
@@ -4,7 +4,8 @@ import {
4
4
  classifyDomain,
5
5
  configPath,
6
6
  discoverLibraries,
7
- readConfig
7
+ readConfig,
8
+ REVIEW_ADAPTERS
8
9
  } from "../review/knowledge.js";
9
10
  import { adapterFromOptions } from "./review.js";
10
11
  async function handleDomain(argv) {
@@ -12,14 +13,14 @@ async function handleDomain(argv) {
12
13
  const target = targetPath(parsed.options);
13
14
  const json = optionFlag(parsed.options, "json");
14
15
  const declared = optionString(parsed.options, "domain") ?? process.env.MM_HARNESS_DOMAIN;
15
- const detected = adapterFromOptions(parsed.options, target);
16
+ const detected = adapterFromOptions();
16
17
  const adapter = detected === "unscoped" ? void 0 : detected;
17
18
  if (declared) {
18
19
  emit(json, { domain: declared, source: "declared", adapter: adapter ?? null, target });
19
20
  return 0;
20
21
  }
21
22
  if (!adapter) {
22
- throw usageError("No MetaMask checkout detected: pass --adapter <mobile|extension|core|terminal> or --domain <name>.");
23
+ throw usageError(`No MetaMask checkout detected: pass --adapter <${REVIEW_ADAPTERS.join("|")}> or --domain <name>.`);
23
24
  }
24
25
  const config = readConfig(configPath());
25
26
  const libraries = discoverLibraries({ target, config });
@@ -4,7 +4,7 @@ import path from "node:path";
4
4
  import { runnerDir, walletFixturePath } from "../paths.js";
5
5
  import { getAdapterSurface } from "../adapters/surface.js";
6
6
  import { fixtureFileSummary, fixtureSummary } from "../doctor.js";
7
- import { acquireCheckoutLock, adapterDetectNext, checkoutBusyOut, EXIT, flag, parseFlags, resolveFlagsAdapter, scriptOverride, spawnScript, spawnScriptStreaming, str, targetOf, usageOut } from "@farmslot/recipe-cli/harness";
7
+ import { acquireCheckoutLock, adapterDetectNext, checkoutBusyOut, EXIT, flag, parseFlags, contextAdapter, scriptOverride, spawnScript, spawnScriptStreaming, str, targetOf, usageOut } from "@farmslot/recipe-cli/harness";
8
8
  import { applyDeviceTargeting } from "./device-target.js";
9
9
  import { FeatureFlagSpecError, fixtureFeatureFlags, parseRemoteFlagSpec } from "../feature-flags.js";
10
10
  const FIXTURES_BOOLEANS = /* @__PURE__ */ new Set(["dev", "force", "json"]);
@@ -38,7 +38,7 @@ function fixturesStatus(options) {
38
38
  );
39
39
  }
40
40
  const target = targetOf(options);
41
- const adapter = resolveFlagsAdapter(options, target);
41
+ const adapter = contextAdapter(target);
42
42
  if (!adapter) {
43
43
  return usageOut(json, "fixtures", `could not detect the MetaMask repo type for ${target}`, adapterDetectNext());
44
44
  }
@@ -114,7 +114,7 @@ async function handleFixturesLocked(argv) {
114
114
  );
115
115
  }
116
116
  const target = targetOf(options);
117
- const adapter = resolveFlagsAdapter(options, target);
117
+ const adapter = contextAdapter(target);
118
118
  if (!adapter) {
119
119
  return usageOut(json, "fixtures", `could not detect the MetaMask repo type for ${target}`, adapterDetectNext());
120
120
  }
@@ -1,9 +1,10 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
- import { color, detectAdapter, optionString, parseArgs, targetPath } from "@farmslot/recipe-cli/harness";
3
+ import { color, contextAdapter, parseArgs, targetPath } from "@farmslot/recipe-cli/harness";
4
+ import { selectableAdapters } from "../command-contract.js";
4
5
  import { handleHelpReview } from "./review.js";
5
6
  import "../adapters/surface.js";
6
- const UNSCOPED_NEXT = "cd into a MetaMask checkout or pass --adapter <mobile|extension|core|terminal>";
7
+ const unscopedNext = () => `cd into a MetaMask checkout or pass --adapter <${selectableAdapters().join("|")}>`;
7
8
  function validHelpLine(value) {
8
9
  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
10
  }
@@ -35,7 +36,7 @@ function recipeHelpPayload(topic, harnessVersion, adapter, target) {
35
36
  harnessVersion,
36
37
  recipeProtocolVersion: topic.recipeProtocolVersion,
37
38
  adapter,
38
- ...adapter === "unscoped" ? { next: UNSCOPED_NEXT } : {},
39
+ ...adapter === "unscoped" ? { next: unscopedNext() } : {},
39
40
  target,
40
41
  topic: topic.topic,
41
42
  title: topic.title,
@@ -58,7 +59,7 @@ function renderRecipeHelp(topic, harnessVersion, adapter) {
58
59
  paint("warn", topic.scopeNotice),
59
60
  `Visual tutorial: ${paint("cmd", "mm-harness tutorial")}`
60
61
  ];
61
- if (adapter === "unscoped") lines.push(paint("warn", `No MetaMask checkout detected. Next: ${UNSCOPED_NEXT}.`));
62
+ if (adapter === "unscoped") lines.push(paint("warn", `No MetaMask checkout detected. Next: ${unscopedNext()}.`));
62
63
  for (const [index, section] of scopedSections(topic, adapter).entries()) {
63
64
  lines.push("", paint("label", `${index + 1}. ${section.title}`), ` ${section.instruction}`);
64
65
  for (const detail of section.details) lines.push(` - ${detail}`);
@@ -72,7 +73,7 @@ function handleHelp(argv, context) {
72
73
  const parsed = parseArgs(argv);
73
74
  if (parsed.positional[0] === "review") return handleHelpReview(argv, context.harnessVersion);
74
75
  const target = targetPath(parsed.options);
75
- const adapter = optionString(parsed.options, "adapter") ?? detectAdapter(target) ?? "unscoped";
76
+ const adapter = contextAdapter(target) ?? "unscoped";
76
77
  const topic = loadRecipeHelp(context.packageRoot);
77
78
  const payload = recipeHelpPayload(topic, context.harnessVersion, adapter, target);
78
79
  if (parsed.options.json === true) {
@@ -9,7 +9,7 @@ import {
9
9
  parseRemoteFlagSpec,
10
10
  remoteFlagRequestFor
11
11
  } from "../../feature-flags.js";
12
- import { webDappOnlyLaunchFlags } from "../../adapters/web-dapp/launch.js";
12
+ import { webDappOnlyLaunchFlags } from "@farmslot/adapter-web/web-dapp";
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;
@@ -24,6 +24,7 @@ import {
24
24
  import { checkExtensionRuntimeHealth } from "../../adapters/extension/runtime.js";
25
25
  import { ensureHarnessFresh } from "../../adapters/harness-freshness.js";
26
26
  import { isExtensionWatcherLive, stopExtensionWatcher } from "../../adapters/slot-ports.js";
27
+ import { recordExtensionConsoleLaunch } from "../../adapters/extension/console-capture.js";
27
28
  import { readExtensionPinnedFlags } from "../../adapters/extension/feature-flags.js";
28
29
  import { sameFeatureFlags } from "../../feature-flags.js";
29
30
  const { CdpSession } = await importRecipeHarnessRuntimeCdp();
@@ -108,6 +109,14 @@ Next: ${block.userAction}`
108
109
  }
109
110
  await ensureHarnessFresh(target, "extension");
110
111
  restoreRequestedPorts();
112
+ if (!reusable && artifactLaunchable && remoteFlags) {
113
+ return {
114
+ status: EXIT.infra,
115
+ output: `REMOTE_FLAG_NOT_PINNABLE: the remote feature-flag pin '${remoteFlags.spec}' (from --remote-flag and the wallet fixture's remoteFeatureFlags) cannot be applied to a verified release artifact; the artifact's manifest is signed evidence and is never rewritten.
116
+ Next: drop --remote-flag and remove remoteFeatureFlags from the wallet fixture to validate the artifact as shipped, or validate the variant on a source checkout launched without the artifact.`
117
+ };
118
+ }
119
+ recordExtensionConsoleLaunch(target);
111
120
  if (wantWatch) {
112
121
  const startWatchSh = path.join(runnerDir, "adapters/extension/start-watch.sh");
113
122
  const watchArgs = ["--target", target, "--runner-bin", path.join(runnerDir, "bin/mm-harness")];
@@ -128,13 +137,6 @@ Next: ${block.userAction}`
128
137
  return extensionReattach(target, displayMode);
129
138
  }
130
139
  if (artifactLaunchable) {
131
- if (remoteFlags) {
132
- return {
133
- status: EXIT.infra,
134
- output: `REMOTE_FLAG_NOT_PINNABLE: the remote feature-flag pin '${remoteFlags.spec}' (from --remote-flag and the wallet fixture's remoteFeatureFlags) cannot be applied to a verified release artifact; the artifact's manifest is signed evidence and is never rewritten.
135
- Next: drop --remote-flag and remove remoteFeatureFlags from the wallet fixture to validate the artifact as shipped, or validate the variant on a source checkout launched without the artifact.`
136
- };
137
- }
138
140
  return extensionLaunchReleaseArtifact(target, artifact);
139
141
  }
140
142
  return extensionLaunchDevelopment(target, remoteFlags);
@@ -1,4 +1,4 @@
1
- import { adapterDetectNext, EXIT, flag, parseFlags, resolveFlagsAdapter, spawnInherit, spawnScript, str, targetOf, usageOut } from "@farmslot/recipe-cli/harness";
1
+ import { adapterDetectNext, EXIT, flag, parseFlags, contextAdapter, spawnInherit, spawnScript, str, targetOf, usageOut } from "@farmslot/recipe-cli/harness";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import { runnerDir } from "../paths.js";
@@ -8,7 +8,7 @@ async function handleLogs(argv) {
8
8
  const { options } = parseFlags(argv, LOGS_BOOLEANS);
9
9
  const json = flag(options, "json");
10
10
  const target = targetOf(options);
11
- const adapter = resolveFlagsAdapter(options, target);
11
+ const adapter = contextAdapter(target);
12
12
  if (!adapter) {
13
13
  return usageOut(json, "logs", `could not detect the MetaMask repo type for ${target}`, adapterDetectNext());
14
14
  }
@@ -1,4 +1,4 @@
1
- import { acquireCheckoutLock, trackCheckoutChild, recordCommandStage, color, stripAnsi, harnessExecutable, recipeRuntimeDir, detectAdapter, optionFlag, optionString, parseArgs, targetPath, usageError, EXIT } from "@farmslot/recipe-cli/harness";
1
+ import { acquireCheckoutLock, trackCheckoutChild, recordCommandStage, color, stripAnsi, harnessExecutable, recipeRuntimeDir, contextAdapter, optionFlag, optionString, parseArgs, targetPath, usageError, EXIT } from "@farmslot/recipe-cli/harness";
2
2
  import { spawn } from "node:child_process";
3
3
  import { OperationOutputTail } from "@farmslot/recipe-runner/runtime/operation";
4
4
  import fs from "node:fs";
@@ -265,8 +265,8 @@ function mobilePlatformOption(value) {
265
265
  Next: ${USAGE}`
266
266
  );
267
267
  }
268
- function resolvePlatform(requested, target) {
269
- const platform = requested ?? detectAdapter(target);
268
+ function resolvePlatform(target) {
269
+ const platform = contextAdapter(target);
270
270
  const surface = platform && adapterRegistry.has(platform) ? getAdapterSurface(platform) : void 0;
271
271
  if (surface?.prepare) {
272
272
  return surface;
@@ -296,7 +296,7 @@ async function handlePrepareLocked(argv) {
296
296
  const { options } = parseArgs(argv);
297
297
  const json = optionFlag(options, "json");
298
298
  const target = targetPath(options);
299
- const surface = resolvePlatform(optionString(options, "platform"), target);
299
+ const surface = resolvePlatform(target);
300
300
  const platform = surface.id;
301
301
  const artifactsDir = path.resolve(
302
302
  optionString(options, "artifactsDir") ?? path.join(target, recipeRuntimeDir())
@@ -1,11 +1,11 @@
1
- import { detectAdapter, adapterDetectNext, usageOut, applyWatcherPortOption, optionFlag, optionString, shellQuote, shellQuoteArg, targetPath } from "@farmslot/recipe-cli/harness";
1
+ import { contextAdapter, adapterDetectNext, usageOut, applyWatcherPortOption, optionFlag, optionString, shellQuote, shellQuoteArg, targetPath } from "@farmslot/recipe-cli/harness";
2
2
  import { assertAdapter } from "../paths.js";
3
3
  import { getAdapterSurface } from "../adapters/surface.js";
4
4
  async function handleProvision({ positional, options, rawArgv }) {
5
5
  applyWatcherPortOption(options);
6
6
  const json = optionFlag(options, "json");
7
7
  const target = targetPath(options);
8
- const adapter = optionString(options, "adapter") ?? detectAdapter(target);
8
+ const adapter = contextAdapter(target);
9
9
  if (!adapter) {
10
10
  return usageOut(json, "provision", `could not detect the MetaMask repo type for ${target}`, adapterDetectNext());
11
11
  }
@@ -1,4 +1,4 @@
1
- import { detectAdapter, optionFlag, optionString, parseArgs, targetPath, usageError } from "@farmslot/recipe-cli/harness";
1
+ import { harnessContext, optionFlag, optionString, parseArgs, targetPath, usageError } from "@farmslot/recipe-cli/harness";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import { runnerDir } from "../paths.js";
@@ -15,7 +15,6 @@ import {
15
15
  resolveReference,
16
16
  defaultBaseRef
17
17
  } from "../review/knowledge.js";
18
- import "../adapters/surface.js";
19
18
  const EXIT_OK = 0;
20
19
  let cachedHarnessVersion;
21
20
  function harnessVersion() {
@@ -25,18 +24,17 @@ function harnessVersion() {
25
24
  }
26
25
  return cachedHarnessVersion;
27
26
  }
28
- function adapterFromOptions(options, target) {
29
- const raw = optionString(options, "adapter") ?? optionString(options, "platform");
30
- if (raw !== void 0) {
31
- if (raw !== "mobile" && raw !== "extension" && raw !== "core" && raw !== "terminal") throw usageError(`--adapter must be mobile, extension, core, or terminal (got ${raw}).`);
32
- return raw;
27
+ function adapterFromOptions() {
28
+ const adapter = harnessContext()?.adapter;
29
+ const scope = REVIEW_ADAPTERS.find((id) => id === adapter?.value);
30
+ if (adapter?.source === "flag" && !scope) {
31
+ throw usageError(`--adapter must be one of ${REVIEW_ADAPTERS.join(", ")} (got ${adapter.value}).`);
33
32
  }
34
- const detected = detectAdapter(target);
35
- return REVIEW_ADAPTERS.find((adapter) => adapter === detected) ?? "unscoped";
33
+ return scope ?? "unscoped";
36
34
  }
37
35
  function resolveReviewContext(options, { fetch = true } = {}) {
38
36
  const target = targetPath(options);
39
- const adapter = adapterFromOptions(options, target);
37
+ const adapter = adapterFromOptions();
40
38
  const domain = optionString(options, "domain") ?? process.env.MM_HARNESS_DOMAIN;
41
39
  const config = readConfig(configPath());
42
40
  const context = { target, adapter, ...domain ? { domain } : {} };
@@ -10,6 +10,7 @@ import {
10
10
  writeMobileReleaseArtifactState
11
11
  } from "../adapters/mobile/release-artifact-state.js";
12
12
  import { extensionDepsBlock } from "./launch/extension.js";
13
+ import { recordExtensionConsoleLaunch } from "../adapters/extension/console-capture.js";
13
14
  import { isExtensionDistStale } from "../adapters/extension/runtime-decision.js";
14
15
  import { FeatureFlagSpecError, parseRemoteFlagSpec } from "../feature-flags.js";
15
16
  async function handleRuntimeLaunch(args) {
@@ -186,6 +187,7 @@ async function handleRuntimeLaunchLocked({ options }) {
186
187
  cdpPort
187
188
  }));
188
189
  }
190
+ recordExtensionConsoleLaunch(target);
189
191
  const result = await spawnScriptStreaming(command[0], command.slice(1), target, {
190
192
  forward: jsonMode ? "stderr" : "original",
191
193
  stdin: jsonMode ? "ignore" : "inherit",
@@ -1,7 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { featureFlagReport } from "../feature-flags.js";
4
- import { color, detectAdapter, adapterDetectNext, EXIT, usageOut, optionFlag, optionString, targetPath } from "@farmslot/recipe-cli/harness";
4
+ import { color, contextAdapter, harnessContextField, adapterDetectNext, EXIT, usageOut, optionFlag, optionString, targetPath } from "@farmslot/recipe-cli/harness";
5
5
  import { collectCheckoutView, renderCheckoutView } from "../checkout-view.js";
6
6
  import { assertAdapter } from "../paths.js";
7
7
  import { getAdapterSurface } from "../adapters/surface.js";
@@ -24,7 +24,7 @@ async function handleStatus({ options }) {
24
24
  }
25
25
  const fast = optionFlag(options, "fast");
26
26
  const allDevices = optionFlag(options, "allDevices");
27
- const adapter = detectAdapter(target);
27
+ const adapter = contextAdapter(target);
28
28
  if (!adapter) {
29
29
  return usageOut(json, "status", `could not detect the MetaMask repo type for ${target}`, adapterDetectNext());
30
30
  }
@@ -58,6 +58,7 @@ async function handleStatus({ options }) {
58
58
  command: "status",
59
59
  adapter,
60
60
  target,
61
+ ...harnessContextField(),
61
62
  view,
62
63
  devices: liveView.devicesWithLive,
63
64
  featureFlags: featureFlagReport(liveView.featureFlagOverrides),
@@ -79,6 +80,7 @@ async function handleStatus({ options }) {
79
80
  command: "status",
80
81
  adapter,
81
82
  target,
83
+ ...harnessContextField(),
82
84
  view,
83
85
  devices,
84
86
  featureFlags: staticFeatureFlags,
@@ -1,7 +1,6 @@
1
- import { harnessExecutable, detectAdapter, EXIT } from "@farmslot/recipe-cli/harness";
1
+ import { harnessExecutable, contextAdapter, EXIT } from "@farmslot/recipe-cli/harness";
2
2
  import { spawnSync } from "node:child_process";
3
3
  import { createRequire } from "node:module";
4
- import "../adapters/surface.js";
5
4
  const require2 = createRequire(import.meta.url);
6
5
  const USAGE = "mm-harness task init <task-dir> --flow f --template id --title t [options]";
7
6
  function hasOption(argv, name) {
@@ -14,7 +13,7 @@ function metamaskDefaults(argv) {
14
13
  defaults.push("--mark-command", `${harnessExecutable()} checklist mark`);
15
14
  }
16
15
  if (!hasOption(argv, "--platform")) {
17
- const adapter = detectAdapter(process.cwd());
16
+ const adapter = contextAdapter(process.cwd());
18
17
  if (adapter) defaults.push("--platform", adapter);
19
18
  }
20
19
  return defaults;
@@ -4,13 +4,14 @@ import {
4
4
  consoleRecords,
5
5
  libraryRootsFromEnv,
6
6
  loadConsoleAllowlist,
7
- messageSignature
7
+ messageSignature,
8
+ withBundledLibraryRoot
8
9
  } from "../library/actions/shared/console-findings.mjs";
9
10
  const consoleClassifier = {
10
11
  records: (text) => consoleRecords(text),
11
12
  classify: (record) => classifyConsoleRecord(record),
12
13
  signature: (message) => messageSignature(message),
13
- loadAllowlist: (libraryRoots) => loadConsoleAllowlist([...libraryRoots]),
14
+ loadAllowlist: (libraryRoots) => loadConsoleAllowlist(withBundledLibraryRoot([...libraryRoots])),
14
15
  match: (event, allowlist) => allowlistMatch(event, allowlist),
15
16
  libraryRoots: () => libraryRootsFromEnv()
16
17
  };
package/dist/doctor.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
- import { color, harnessExecutable, recipeHarnessRoot, recipeRuntimeDir, readRuntimeContextField, resolveRuntimeContextPath } from "@farmslot/recipe-cli/harness";
3
+ import { RECIPE_CLI_VERSION } from "@farmslot/recipe-cli";
4
+ import { color, harnessContext, harnessContextField, harnessExecutable, recipeHarnessRoot, recipeRuntimeDir, readRuntimeContextField, resolveRuntimeContextPath } from "@farmslot/recipe-cli/harness";
4
5
  import { normalizeFeatureFlagRecord } from "./feature-flags.js";
5
6
  import { getAdapterSurface } from "./adapters/surface.js";
6
7
  import { isPin } from "./harness-pin.js";
@@ -92,7 +93,7 @@ const DEV_SERVER_PORT_ENV = ["WATCHER_PORT", "METRO_PORT", "RECIPE_WATCHER_PORT"
92
93
  const RUNTIME_CONTEXT_FIELDS = [
93
94
  { key: "slotId", envVars: ["RECIPE_SLOT_ID"], envVar: "RECIPE_SLOT_ID", customize: "doctor --fix creates local identity; Farmslot may override", adapters: ["mobile", "extension", "core"] },
94
95
  { key: "watcherPort", envVars: DEV_SERVER_PORT_ENV, envVar: "WATCHER_PORT", customize: "doctor --fix claims one; --watcher-port overrides", adapters: ["mobile"] },
95
- { key: "metroPort", envVars: DEV_SERVER_PORT_ENV, envVar: "WATCHER_PORT", customize: "mirrors watcherPort; --metro-port is its alias", adapters: ["mobile"] },
96
+ { key: "metroPort", envVars: DEV_SERVER_PORT_ENV, envVar: "WATCHER_PORT", customize: "mirrors watcherPort", adapters: ["mobile"] },
96
97
  { key: "devServerPort", envVars: DEV_SERVER_PORT_ENV, envVar: "WATCHER_PORT", customize: "mirrors watcherPort for the adapter dev server", adapters: ["mobile", "extension"] },
97
98
  { key: "simulator", envVars: ["IOS_SIMULATOR"], envVar: "IOS_SIMULATOR", customize: "--device pins the matched simulator into the context", adapters: ["mobile"] },
98
99
  { key: "simulatorUdid", envVars: ["SIM_UDID"], envVar: "SIM_UDID", customize: "--device pins the matched udid into the context", adapters: ["mobile"] },
@@ -106,10 +107,13 @@ function runtimeContextSummary(target, adapter) {
106
107
  const file = envOverride ?? path.relative(target, contextPath);
107
108
  const fields = {};
108
109
  const specs = adapter ? RUNTIME_CONTEXT_FIELDS.filter((spec) => spec.adapters.includes(adapter)) : RUNTIME_CONTEXT_FIELDS;
110
+ const filled = new Set(
111
+ Object.values(harnessContext()?.ports ?? {}).flatMap((port) => port?.filled ? port.names ?? [] : [])
112
+ );
109
113
  for (const spec of specs) {
110
114
  const envName = spec.envVars.find((name) => {
111
115
  const value = process.env[name];
112
- return value !== void 0 && value !== "";
116
+ return value !== void 0 && value !== "" && !filled.has(name);
113
117
  });
114
118
  if (envName !== void 0) {
115
119
  fields[spec.key] = { value: process.env[envName], source: "env", envVar: envName, customize: spec.customize };
@@ -164,6 +168,7 @@ function createDoctorReport(adapter, target, manifestValidation, actionManifestP
164
168
  requiredChecks,
165
169
  adapter,
166
170
  target,
171
+ ...harnessContextField(),
167
172
  runner: runnerProvenance(actionManifestPath),
168
173
  compatibilityMode: mode,
169
174
  shape: repoShape(target),
@@ -223,7 +228,9 @@ function runnerProvenance(actionManifestPath) {
223
228
  invokedPath,
224
229
  executablePath,
225
230
  actionManifestPath,
226
- harnessPackage: "@farmslot/recipe-runner"
231
+ harnessPackage: "@farmslot/recipe-runner",
232
+ // The @farmslot/recipe-cli this harness runs on (`--version --verbose` prints it too).
233
+ recipeCliVersion: RECIPE_CLI_VERSION
227
234
  };
228
235
  }
229
236
  function readJsonObject(file) {