@deeeed/metamask-harness 0.78.0 → 0.80.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 (215) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/README.md +1 -1
  3. package/adapters/extension/ensure-browser.sh +2 -2
  4. package/adapters/extension/inject.mjs +0 -1
  5. package/adapters/extension/launch-browser.cjs +39 -613
  6. package/adapters/extension/readiness.mjs +2 -5
  7. package/adapters/extension/reattach.sh +2 -2
  8. package/adapters/extension/stamp-runtime-title.cjs +1 -1
  9. package/adapters/manifest.json +2 -34
  10. package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +57 -1358
  11. package/adapters/mobile/bridge-runtime/teaching.cjs +20 -0
  12. package/adapters/mobile/inject.sh +16 -0
  13. package/adapters/mobile/launch-console-forwarder.cjs +1 -2
  14. package/adapters/mobile/open-device.sh +2 -2
  15. package/adapters/mobile/reload-app.mjs +4 -4
  16. package/adapters/mobile/reset-app-data.sh +1 -1
  17. package/adapters/mobile/start-console-forwarder.sh +3 -2
  18. package/adapters/mobile/start-metro.sh +17 -12
  19. package/adapters/mobile/wait-for-bridge.cjs +2 -2
  20. package/adapters/shared/ensure-runner-deps.sh +2 -2
  21. package/adapters/shared/harness-source-fingerprint.mjs +13 -8
  22. package/adapters/shared/open-debug.mjs +2 -2
  23. package/adapters/shared/runner-deps-ready.mjs +1 -1
  24. package/adapters/shared/sync-wallet-fixture.sh +8 -5
  25. package/adapters/terminal/cleanup.mjs +2 -1
  26. package/adapters/terminal/inject.mjs +2 -1
  27. package/adapters/terminal/lib/cdp-client.mjs +1 -1
  28. package/bin/mm-harness +9 -28
  29. package/dist/adapters/checkout-files.js +16 -0
  30. package/dist/adapters/console-capture.js +14 -1
  31. package/dist/adapters/core/actions.js +4 -3
  32. package/dist/adapters/core/overlay.js +1 -1
  33. package/dist/adapters/core/readiness.js +28 -1
  34. package/dist/adapters/core/run.js +3 -3
  35. package/dist/adapters/core/surface.js +22 -5
  36. package/dist/adapters/extension/browser-pid.js +56 -0
  37. package/dist/adapters/extension/console-capture.js +1 -1
  38. package/dist/adapters/extension/doctor-checks.js +1 -1
  39. package/dist/adapters/extension/ensure-ready.js +5 -4
  40. package/dist/adapters/extension/extension-id.js +1 -1
  41. package/dist/adapters/extension/feature-flags.js +1 -1
  42. package/dist/adapters/extension/fixtures.js +1 -2
  43. package/dist/adapters/extension/launch.js +4 -5
  44. package/dist/adapters/extension/launched-browser.js +1 -2
  45. package/dist/adapters/extension/network-observer.js +1 -1
  46. package/dist/adapters/extension/overlay.js +1 -1
  47. package/dist/adapters/extension/product-config.js +1 -1
  48. package/dist/adapters/extension/readiness.js +1 -1
  49. package/dist/adapters/extension/reload.js +1 -1
  50. package/dist/adapters/extension/run.js +24 -10
  51. package/dist/adapters/extension/runtime-decision.js +3 -2
  52. package/dist/adapters/extension/runtime.js +9 -5
  53. package/dist/adapters/extension/surface.js +20 -5
  54. package/dist/adapters/extension/verify.js +2 -2
  55. package/dist/adapters/harness-freshness.js +2 -1
  56. package/dist/adapters/mobile/android-gfxinfo-performance.js +1 -3
  57. package/dist/adapters/mobile/app-identity.js +10 -0
  58. package/dist/adapters/mobile/doctor-checks.js +1 -1
  59. package/dist/adapters/mobile/fixtures.js +1 -1
  60. package/dist/adapters/mobile/launch.js +27 -28
  61. package/dist/adapters/mobile/lifecycle.js +1 -1
  62. package/dist/adapters/mobile/metro-env.js +9 -59
  63. package/dist/adapters/mobile/performance-observer.js +1 -1
  64. package/dist/adapters/mobile/prepare-target.js +1 -1
  65. package/dist/adapters/mobile/prepare.js +4 -5
  66. package/dist/adapters/mobile/provision.js +1 -1
  67. package/dist/adapters/mobile/readiness.js +1 -1
  68. package/dist/adapters/mobile/recording-target.js +15 -0
  69. package/dist/adapters/mobile/release-artifact-state.js +1 -1
  70. package/dist/adapters/mobile/reload.js +1 -1
  71. package/dist/adapters/mobile/run.js +9 -8
  72. package/dist/adapters/mobile/runtime-decision.js +5 -7
  73. package/dist/adapters/mobile/source-freshness.js +22 -126
  74. package/dist/adapters/mobile/surface.js +31 -6
  75. package/dist/adapters/mobile/ui.js +16 -5
  76. package/dist/adapters/mobile/verify.js +2 -3
  77. package/dist/adapters/performance/cdp-trace.js +1 -1
  78. package/dist/adapters/performance/device-platform.js +1 -1
  79. package/dist/adapters/registry.js +2 -14
  80. package/dist/adapters/slot-ports.js +1 -1
  81. package/dist/adapters/surface.js +1 -0
  82. package/dist/adapters/terminal/console-capture.js +1 -1
  83. package/dist/adapters/terminal/launch.js +1 -1
  84. package/dist/adapters/terminal/run.js +25 -2
  85. package/dist/adapters/terminal/surface.js +12 -4
  86. package/dist/adapters.js +8 -4
  87. package/dist/advisor-policy.js +1 -1
  88. package/dist/checkout-view.js +1 -2
  89. package/dist/cli.js +28 -15
  90. package/dist/command-contract.js +1 -26
  91. package/dist/commands/call.js +10 -24
  92. package/dist/commands/check.js +3 -4
  93. package/dist/commands/checklist.js +1 -1
  94. package/dist/commands/completion-candidates.js +2 -10
  95. package/dist/commands/config.js +2 -2
  96. package/dist/commands/core-readiness.js +12 -7
  97. package/dist/commands/debug.js +3 -3
  98. package/dist/commands/device-target.js +5 -5
  99. package/dist/commands/doctor.js +2 -14
  100. package/dist/commands/domain.js +2 -2
  101. package/dist/commands/ensure-ready.js +1 -7
  102. package/dist/commands/fixtures.js +5 -6
  103. package/dist/commands/help.js +3 -4
  104. package/dist/commands/launch/device.js +10 -17
  105. package/dist/commands/launch/extension.js +3 -9
  106. package/dist/commands/launch/mobile.js +1 -1
  107. package/dist/commands/list-executables.js +1 -3
  108. package/dist/commands/logs.js +3 -3
  109. package/dist/commands/manifest.js +1 -12
  110. package/dist/commands/mobile-device-view.js +4 -2
  111. package/dist/commands/overlay.js +110 -0
  112. package/dist/commands/pin.js +1 -1
  113. package/dist/commands/pr-body.js +2 -2
  114. package/dist/commands/prepare.js +6 -11
  115. package/dist/commands/provision.js +3 -12
  116. package/dist/commands/recipe-advice.js +1 -2
  117. package/dist/commands/recipe-quality.js +2 -3
  118. package/dist/commands/resolve-extension.js +1 -7
  119. package/dist/commands/review.js +4 -4
  120. package/dist/commands/run-engine.js +12 -51
  121. package/dist/commands/run.js +4 -25
  122. package/dist/commands/runtime-decision.js +1 -6
  123. package/dist/commands/runtime-health.js +1 -7
  124. package/dist/commands/runtime-launch.js +1 -13
  125. package/dist/commands/self-test.js +2 -6
  126. package/dist/commands/status-watch.js +1 -2
  127. package/dist/commands/status.js +2 -5
  128. package/dist/commands/task-init.js +2 -3
  129. package/dist/commands/tutorial.js +2 -2
  130. package/dist/commands/update.js +1 -1
  131. package/dist/completions-cache.js +1 -1
  132. package/dist/console-classifier.js +19 -0
  133. package/dist/discovery.js +1 -1
  134. package/dist/doctor.js +3 -4
  135. package/dist/funding-execution-context.js +15 -1
  136. package/dist/host.js +32 -0
  137. package/dist/index.js +1 -6
  138. package/dist/live-tsx.js +49 -0
  139. package/dist/mm-harness-cli.js +2 -4
  140. package/dist/network-observation.js +4 -4
  141. package/dist/paths.js +48 -78
  142. package/dist/performance-observation.js +2 -2
  143. package/dist/progress.js +1 -1
  144. package/dist/recipe-assistance-context.js +2 -4
  145. package/dist/recipe-security.js +2 -59
  146. package/dist/review/knowledge.js +3 -3
  147. package/dist/runner.js +5 -3
  148. package/dist/runtime-context.js +1 -1
  149. package/dist/task-view.js +3 -4
  150. package/docs/CONTRIBUTING.md +3 -3
  151. package/docs/cli-contract.md +2 -2
  152. package/docs/feature-guard.md +90 -0
  153. package/library/README.md +3 -1
  154. package/library/actions/core/perps/_controller.mjs +153 -130
  155. package/library/actions/core/perps/quote_fees.mjs +58 -0
  156. package/library/actions/core/wallet/_fixture-accounts.mjs +69 -0
  157. package/library/actions/core/wallet/list_accounts.mjs +14 -57
  158. package/library/actions/extension/feature_flags/state.mjs +1 -1
  159. package/library/actions/extension/platform/cdp.mjs +2 -8
  160. package/library/actions/harness-exports.mjs +1 -6
  161. package/library/actions/mobile/app/network-control.mjs +6 -8
  162. package/library/actions/mobile/perps/prepare_local_snapshot_endpoint.mjs +1 -1
  163. package/library/actions/mobile/platform/bridge.mjs +8 -8
  164. package/library/actions/mobile/platform/native-session-name.mjs +21 -1
  165. package/library/actions/mobile/platform/native-session.mjs +3 -4
  166. package/library/actions/mobile/platform/observe-ui.mjs +12 -6
  167. package/library/actions/mobile/ui/native-navigation.mjs +4 -3
  168. package/library/actions/mobile/wallet/native-ui.mjs +3 -2
  169. package/library/actions/shared/analytics/_adapter.mjs +1 -1
  170. package/library/actions/shared/console-findings.mjs +1 -1
  171. package/library/actions/terminal/platform/runtime.mjs +2 -1
  172. package/library/manifests/core.action-manifest.json +83 -13
  173. package/package.json +8 -8
  174. package/adapters/extension/lib/slot-title.cjs +0 -292
  175. package/adapters/mobile/bridge-runtime/console-forwarder.cjs +0 -472
  176. package/adapters/mobile/bridge-runtime/lib/bridge-errors.cjs +0 -125
  177. package/adapters/mobile/bridge-runtime/lib/cdp-broker.cjs +0 -1055
  178. package/adapters/mobile/bridge-runtime/lib/cdp-eval.cjs +0 -110
  179. package/adapters/mobile/bridge-runtime/lib/config.cjs +0 -61
  180. package/adapters/mobile/bridge-runtime/lib/console-format.cjs +0 -53
  181. package/adapters/mobile/bridge-runtime/lib/devtools-proxy.cjs +0 -177
  182. package/adapters/mobile/bridge-runtime/lib/issue-capture.cjs +0 -446
  183. package/adapters/mobile/bridge-runtime/lib/match-bridge-target.cjs +0 -84
  184. package/adapters/mobile/bridge-runtime/lib/target-discovery.cjs +0 -403
  185. package/adapters/mobile/bridge-runtime/lib/ws-client.cjs +0 -116
  186. package/adapters/mobile/coalesce-metro-log.cjs +0 -24
  187. package/adapters/mobile/launch-metro.cjs +0 -77
  188. package/adapters/mobile/metro-config.cjs +0 -98
  189. package/adapters/mobile/metro-log-generation.cjs +0 -106
  190. package/dist/adapters/mobile/frame-metrics.js +0 -118
  191. package/dist/adapters/mobile/video-recorder.js +0 -289
  192. package/dist/artifact-files.js +0 -92
  193. package/dist/checkout-lock.js +0 -149
  194. package/dist/cli-color.js +0 -98
  195. package/dist/command-journal.js +0 -308
  196. package/dist/commands/last.js +0 -60
  197. package/dist/commands/launch/index.js +0 -109
  198. package/dist/commands/parse-args.js +0 -279
  199. package/dist/commands/reload.js +0 -41
  200. package/dist/commands/run-report.js +0 -241
  201. package/dist/commands/shared.js +0 -323
  202. package/dist/commands/stop.js +0 -60
  203. package/dist/devices.js +0 -100
  204. package/dist/execution-provenance.js +0 -332
  205. package/dist/harness.js +0 -451
  206. package/dist/heal-bounds.js +0 -137
  207. package/dist/json-stream.js +0 -60
  208. package/dist/leaf-invoke.js +0 -19
  209. package/dist/library-provenance.js +0 -33
  210. package/dist/live-adapter-contract.js +0 -380
  211. package/dist/recording-target.js +0 -120
  212. package/dist/run-diagnostics.js +0 -364
  213. package/dist/run-recording.js +0 -481
  214. package/dist/runtime-proof.js +0 -90
  215. package/library/actions/mobile/platform/tool-paths.mjs +0 -123
@@ -1,241 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import { execFileSync } from "node:child_process";
4
- import { getAdapterSurface } from "../adapters/surface.js";
5
- import { isRecord } from "./parse-args.js";
6
- function writeRunReport(result) {
7
- const summaryPath = String(result.summaryPath);
8
- const tracePath = String(result.tracePath);
9
- const artifactManifestPath = String(result.artifactManifestPath);
10
- const artifactsDir = path.dirname(summaryPath);
11
- const summary = JSON.parse(fs.readFileSync(summaryPath, "utf8"));
12
- const trace = JSON.parse(fs.readFileSync(tracePath, "utf8"));
13
- const entries = Array.isArray(trace.entries) ? trace.entries : [];
14
- const lines = renderRunReport(summary, entries);
15
- const reportPath = path.join(artifactsDir, "report.md");
16
- fs.writeFileSync(reportPath, `${lines.join("\n")}
17
- `);
18
- indexRunReportArtifact(artifactManifestPath);
19
- return {
20
- path: reportPath,
21
- preview: lines.filter((line) => line.startsWith("- ")).slice(0, 6).map((line) => line.slice(2))
22
- };
23
- }
24
- function indexProductProvenanceArtifact(artifactManifestPath, target, adapter, browser = null) {
25
- const artifactsDir = path.dirname(artifactManifestPath);
26
- let gitRef = "unknown";
27
- let branch = "unknown";
28
- let dirty = true;
29
- try {
30
- gitRef = execFileSync("git", ["rev-parse", "HEAD"], {
31
- cwd: target,
32
- encoding: "utf8",
33
- stdio: ["ignore", "pipe", "ignore"]
34
- }).trim();
35
- branch = execFileSync("git", ["branch", "--show-current"], {
36
- cwd: target,
37
- encoding: "utf8",
38
- stdio: ["ignore", "pipe", "ignore"]
39
- }).trim() || "detached";
40
- dirty = execFileSync(
41
- "git",
42
- ["status", "--porcelain", "--untracked-files=normal"],
43
- {
44
- cwd: target,
45
- encoding: "utf8",
46
- stdio: ["ignore", "pipe", "ignore"]
47
- }
48
- ).trim().length > 0;
49
- } catch {
50
- }
51
- const provenance = {
52
- schemaVersion: 1,
53
- adapter,
54
- repository: path.basename(path.resolve(target)),
55
- git_ref: gitRef,
56
- branch,
57
- dirty,
58
- ...browser ? { browser } : {}
59
- };
60
- fs.writeFileSync(
61
- path.join(artifactsDir, "product-provenance.json"),
62
- `${JSON.stringify(provenance, null, 2)}
63
- `
64
- );
65
- let manifest;
66
- try {
67
- manifest = JSON.parse(fs.readFileSync(artifactManifestPath, "utf8"));
68
- } catch {
69
- return;
70
- }
71
- if (!isRecord(manifest)) return;
72
- const artifacts = Array.isArray(manifest.artifacts) ? manifest.artifacts : [];
73
- manifest.artifacts = [
74
- ...artifacts.filter(
75
- (artifact) => !isRecord(artifact) || artifact.path !== "product-provenance.json"
76
- ),
77
- {
78
- path: "product-provenance.json",
79
- type: "json",
80
- label: "Product checkout provenance",
81
- category: "system",
82
- metadata: {
83
- adapter,
84
- git_ref: gitRef,
85
- branch,
86
- dirty,
87
- ...browser?.boundTo && browser.boundTo !== "none" ? { browser: browser.browser, browser_version: browser.version, browser_source: browser.source, extension_loading: browser.extensionLoading } : {}
88
- }
89
- }
90
- ];
91
- fs.writeFileSync(
92
- artifactManifestPath,
93
- `${JSON.stringify(manifest, null, 2)}
94
- `
95
- );
96
- }
97
- function resolveTemplatedPort(value, params, recipe) {
98
- let resolved = value;
99
- const template = /^\{\{\s*params\.([\w-]+)\s*\}\}$/u.exec(String(value).trim());
100
- if (template) {
101
- const name = template[1];
102
- const schema = isRecord(recipe) && isRecord(recipe.paramsSchema) && isRecord(recipe.paramsSchema.properties) ? recipe.paramsSchema.properties[name] : void 0;
103
- resolved = isRecord(params) && params[name] !== void 0 ? params[name] : isRecord(schema) ? schema.default : void 0;
104
- }
105
- const text = String(resolved ?? "").trim();
106
- return /^\d+$/u.test(text) ? text : null;
107
- }
108
- function recipeCdpPorts(request) {
109
- const ports = /* @__PURE__ */ new Set();
110
- const envPort = request.env?.CDP_PORT || request.env?.RECIPE_CDP_PORT;
111
- if (envPort) ports.add(String(envPort));
112
- let recipe = request.recipeDocument;
113
- if (recipe === void 0 && request.recipePath) {
114
- try {
115
- recipe = JSON.parse(fs.readFileSync(request.recipePath, "utf8"));
116
- } catch {
117
- recipe = void 0;
118
- }
119
- }
120
- const workflow = isRecord(recipe) && isRecord(recipe.workflow) ? recipe.workflow : null;
121
- const nodes = workflow && isRecord(workflow.nodes) ? Object.values(workflow.nodes) : [];
122
- for (const node of nodes) {
123
- if (!isRecord(node) || node.cdp_port === void 0 || node.cdp_port === null) continue;
124
- const port = resolveTemplatedPort(node.cdp_port, request.params, recipe);
125
- ports.add(port ?? `unresolved ${String(node.cdp_port)}`);
126
- }
127
- return [...ports];
128
- }
129
- function executedBrowser(adapter, target, artifactManifestPath, ports) {
130
- const launched = getAdapterSurface(adapter).run?.launchedBrowser;
131
- if (!launched) return null;
132
- if (ports.length === 0) return { boundTo: "none", reason: "the run had no known CDP port" };
133
- if (ports.length > 1) return { boundTo: "none", reason: `the run drove several CDP ports (${ports.join(", ")})` };
134
- return launched(target, path.dirname(artifactManifestPath), ports[0]);
135
- }
136
- function renderRunReport(summary, entries) {
137
- const lines = [
138
- "# MetaMask Recipe Run",
139
- "",
140
- `Status: ${String(summary.status ?? "unknown")}`,
141
- `Duration: ${formatDuration(Number(summary.durationMs ?? 0))}`,
142
- `Nodes: ${Number(summary.passed ?? 0)}/${Number(summary.total ?? entries.length)} passed`
143
- ];
144
- const sideFindings = isRecord(summary.sideFindings) ? summary.sideFindings : void 0;
145
- const sideFindingCounts = sideFindings && isRecord(sideFindings.counts) ? sideFindings.counts : void 0;
146
- const sideFindingTotal = Number(sideFindingCounts?.total ?? 0);
147
- if (sideFindingTotal > 0) {
148
- lines.push(
149
- "",
150
- "## Side findings",
151
- `- REVIEW ${sideFindingTotal} distinct application warning/error event(s) (non-blocking; expanded below and stored in diagnostics.json)`
152
- );
153
- }
154
- lines.push("", "## Steps");
155
- for (const entry of entries) {
156
- if (!isRecord(entry)) continue;
157
- const mark = entry.ok === false ? "FAIL" : "PASS";
158
- const nodeId = String(entry.nodeId ?? "(node)");
159
- const action = String(entry.action ?? "(action)");
160
- const duration = formatDuration(Number(entry.durationMs ?? 0));
161
- const detail = summarizeNodeOutput(entry.output);
162
- lines.push(`- ${mark} ${nodeId} (${action}, ${duration})${detail ? `: ${detail}` : ""}`);
163
- }
164
- return lines;
165
- }
166
- function summarizeNodeOutput(output) {
167
- if (!isRecord(output)) return "";
168
- const parts = [];
169
- const preferredKeys = [
170
- "platform",
171
- "screen",
172
- "route",
173
- "page",
174
- "network",
175
- "account",
176
- "count",
177
- "matchingCount",
178
- "proofPath",
179
- "screenshot",
180
- "path"
181
- ];
182
- for (const key of preferredKeys) addPart(parts, labelFor(key), summarizeValue(output[key]));
183
- const accountState = isRecord(output.accountState) ? output.accountState : void 0;
184
- if (accountState) {
185
- addPart(parts, "totalBalance", accountState.totalBalance);
186
- addPart(parts, "spendable", accountState.spendableBalance);
187
- addPart(parts, "marginUsed", accountState.marginUsed);
188
- addPart(parts, "unrealizedPnl", accountState.unrealizedPnl);
189
- }
190
- if (parts.length === 0) {
191
- for (const [key, value] of Object.entries(output)) {
192
- if (parts.length >= 5) break;
193
- if (key === "liveAdapter" || key === "artifacts") continue;
194
- addPart(parts, key, summarizeValue(value));
195
- }
196
- }
197
- return parts.join(", ");
198
- }
199
- function labelFor(key) {
200
- return key === "matchingCount" ? "matching" : key === "proofPath" ? "proof" : key;
201
- }
202
- function summarizeValue(value) {
203
- if (typeof value === "string") return shortenAddress(value);
204
- if (typeof value === "number" || typeof value === "boolean") return value;
205
- if (Array.isArray(value)) return `${value.length} item${value.length === 1 ? "" : "s"}`;
206
- return void 0;
207
- }
208
- function addPart(parts, label, value) {
209
- if (value === void 0 || value === null || value === "") return;
210
- parts.push(`${label}=${String(value)}`);
211
- }
212
- function shortenAddress(value) {
213
- return /^0x[a-fA-F0-9]{40}$/u.test(value) ? `${value.slice(0, 6)}...${value.slice(-4)}` : value;
214
- }
215
- function formatDuration(ms) {
216
- if (!Number.isFinite(ms) || ms <= 0) return "0ms";
217
- if (ms < 1e3) return `${Math.round(ms)}ms`;
218
- return `${(ms / 1e3).toFixed(ms < 1e4 ? 1 : 0)}s`;
219
- }
220
- function indexRunReportArtifact(artifactManifestPath) {
221
- let manifest;
222
- try {
223
- manifest = JSON.parse(fs.readFileSync(artifactManifestPath, "utf8"));
224
- } catch {
225
- return;
226
- }
227
- if (!isRecord(manifest)) return;
228
- const artifacts = Array.isArray(manifest.artifacts) ? manifest.artifacts : [];
229
- manifest.artifacts = [
230
- ...artifacts.filter((artifact) => !isRecord(artifact) || artifact.path !== "report.md"),
231
- { path: "report.md", type: "report", label: "Human run report", category: "system" }
232
- ];
233
- fs.writeFileSync(artifactManifestPath, `${JSON.stringify(manifest, null, 2)}
234
- `);
235
- }
236
- export {
237
- executedBrowser,
238
- indexProductProvenanceArtifact,
239
- recipeCdpPorts,
240
- writeRunReport
241
- };
@@ -1,323 +0,0 @@
1
- import { trackCheckoutChild } from "../checkout-lock.js";
2
- import { recordCommandOutput, recordCommandStage } from "../command-journal.js";
3
- import { spawn, spawnSync } from "node:child_process";
4
- import { StringDecoder } from "node:string_decoder";
5
- import { OperationOutputTail } from "@farmslot/recipe-harness/runtime/operation";
6
- import path from "node:path";
7
- import { detectAdapter } from "../harness.js";
8
- import { colorHumanMessage } from "../cli-color.js";
9
- import { resolveLeafInvoke, shellLeafMissing } from "../leaf-invoke.js";
10
- import { adapterRegistry } from "../adapters/registry.js";
11
- const EXIT = { ok: 0, runtime: 1, usage: 2, infra: 3, bounded: 4, validation: 5 };
12
- function writeInteractiveProgress(json, message, {
13
- stdoutIsTTY = Boolean(process.stdout.isTTY),
14
- stream = process.stderr
15
- } = {}) {
16
- if (json || !stdoutIsTTY) return false;
17
- stream.write(`${colorHumanMessage(message, { stream })}
18
- `);
19
- return true;
20
- }
21
- function parseFlags(argv, booleans) {
22
- const positional = [];
23
- const options = {};
24
- for (let i = 0; i < argv.length; i += 1) {
25
- const arg = argv[i];
26
- if (!arg.startsWith("--")) {
27
- positional.push(arg);
28
- continue;
29
- }
30
- const body = arg.slice(2);
31
- const eq = body.indexOf("=");
32
- const rawKey = eq === -1 ? body : body.slice(0, eq);
33
- const inline = eq === -1 ? void 0 : body.slice(eq + 1);
34
- const key = rawKey.replace(/-([a-z])/gu, (_, c) => c.toUpperCase());
35
- if (booleans.has(key)) {
36
- options[key] = inline === void 0 ? true : inline !== "false";
37
- continue;
38
- }
39
- if (inline !== void 0) {
40
- options[key] = inline;
41
- continue;
42
- }
43
- const next = argv[i + 1];
44
- if (next === void 0 || next.startsWith("--")) {
45
- options[key] = true;
46
- continue;
47
- }
48
- options[key] = next;
49
- i += 1;
50
- }
51
- return { positional, options };
52
- }
53
- function str(options, key) {
54
- const value = options[key];
55
- return typeof value === "string" ? value : void 0;
56
- }
57
- function flag(options, key) {
58
- return options[key] === true;
59
- }
60
- function targetOf(options) {
61
- return path.resolve(str(options, "target") ?? process.cwd());
62
- }
63
- function resolveAdapter(options, target, hint) {
64
- const platform = str(options, "platform");
65
- const explicit = str(options, "adapter") ?? (platform === "ios" || platform === "android" ? "mobile" : platform);
66
- if (explicit && adapterRegistry.has(explicit)) return explicit;
67
- if (hint) return hint;
68
- return detectAdapter(target);
69
- }
70
- function scriptOverride(scriptPath) {
71
- const stem = path.basename(scriptPath).replace(/[^A-Za-z0-9]/gu, "_").toUpperCase();
72
- return process.env[`MM_HARNESS_SCRIPT_BIN_${stem}`];
73
- }
74
- function spawnScript(script, args, cwd, json, env) {
75
- const isNodeScript = script === process.execPath && args.length > 0;
76
- const stem = isNodeScript ? path.basename(args[0]).replace(/[^A-Za-z0-9]/gu, "_").toUpperCase() : path.basename(script).replace(/[^A-Za-z0-9]/gu, "_").toUpperCase();
77
- const override = process.env[`MM_HARNESS_SCRIPT_BIN_${stem}`];
78
- const bin = override ?? script;
79
- const directArgs = override !== void 0 && isNodeScript ? args.slice(1) : args;
80
- if (shellLeafMissing(bin)) {
81
- const message = `leaf could not start: ${path.basename(bin)} (ENOENT)
82
- Next: reinstall mm-harness (npm i -g @deeeed/metamask-harness) \u2014 the shell leaf is missing or not executable`;
83
- process.stderr.write(`${message}
84
- `);
85
- return { status: 1, output: message };
86
- }
87
- const { bin: invokeBin, args: spawnArgs } = resolveLeafInvoke(bin, directArgs);
88
- recordCommandStage(path.basename(isNodeScript ? args[0] : script));
89
- const result = spawnSync(invokeBin, spawnArgs, {
90
- cwd,
91
- encoding: "utf8",
92
- env: env ? { ...process.env, ...env } : process.env,
93
- maxBuffer: 64 * 1024 * 1024
94
- });
95
- if (result.error) {
96
- const leaf = isNodeScript ? path.basename(args[0]) : path.basename(script);
97
- const code = result.error.code ?? "ESPAWN";
98
- const message = `leaf could not start: ${leaf} (${code})
99
- Next: reinstall mm-harness (npm i -g @deeeed/metamask-harness) \u2014 the shell leaf is missing or not executable`;
100
- process.stderr.write(`${message}
101
- `);
102
- return { status: 1, output: message };
103
- }
104
- const output = `${result.stdout ?? ""}${result.stderr ?? ""}`;
105
- recordCommandOutput(output);
106
- if (!json && output) process.stderr.write(output);
107
- return { status: result.status ?? 1, output };
108
- }
109
- function spawnInherit(script, args, cwd) {
110
- const isNodeScript = script === process.execPath && args.length > 0;
111
- const stem = (isNodeScript ? path.basename(args[0]) : path.basename(script)).replace(/[^A-Za-z0-9]/gu, "_").toUpperCase();
112
- const override = process.env[`MM_HARNESS_SCRIPT_BIN_${stem}`];
113
- const bin = override ?? script;
114
- const directArgs = override !== void 0 && isNodeScript ? args.slice(1) : args;
115
- const { bin: invokeBin, args: spawnArgs } = resolveLeafInvoke(bin, directArgs);
116
- const result = spawnSync(invokeBin, spawnArgs, { cwd, stdio: "inherit", env: process.env });
117
- if (result.error) {
118
- const leaf = isNodeScript ? path.basename(args[0]) : path.basename(script);
119
- const code = result.error.code ?? "ESPAWN";
120
- process.stderr.write(
121
- `leaf could not start: ${leaf} (${code})
122
- Next: reinstall mm-harness (npm i -g @deeeed/metamask-harness) \u2014 the leaf is missing or not executable
123
- `
124
- );
125
- return 1;
126
- }
127
- if (result.status !== null) return result.status;
128
- return result.signal === "SIGINT" || result.signal === "SIGTERM" ? 0 : 1;
129
- }
130
- function spawnScriptStreaming(script, args, cwd, options) {
131
- const spawnOptions = options && ("env" in options || "timeoutMs" in options || "forward" in options || "capture" in options || "stdin" in options || "stage" in options) ? options : { env: options };
132
- const isNodeScript = script === process.execPath && args.length > 0;
133
- const stem = isNodeScript ? path.basename(args[0]).replace(/[^A-Za-z0-9]/gu, "_").toUpperCase() : path.basename(script).replace(/[^A-Za-z0-9]/gu, "_").toUpperCase();
134
- const override = process.env[`MM_HARNESS_SCRIPT_BIN_${stem}`];
135
- const bin = override ?? script;
136
- const directArgs = override !== void 0 && isNodeScript ? args.slice(1) : args;
137
- if (shellLeafMissing(bin)) {
138
- const message = `leaf could not start: ${path.basename(bin)} (ENOENT)
139
- Next: reinstall mm-harness (npm i -g @deeeed/metamask-harness) \u2014 the shell leaf is missing or not executable`;
140
- if (spawnOptions.forward !== "none") process.stderr.write(`${message}
141
- `);
142
- return Promise.resolve({ status: 1, output: message });
143
- }
144
- const { bin: invokeBin, args: spawnArgs } = resolveLeafInvoke(bin, directArgs);
145
- recordCommandStage(spawnOptions.stage ?? path.basename(isNodeScript ? args[0] : script));
146
- return new Promise((resolve, reject) => {
147
- const ownsProcessGroup = process.platform !== "win32";
148
- const child = spawn(invokeBin, spawnArgs, {
149
- cwd,
150
- env: spawnOptions.env ? { ...process.env, ...spawnOptions.env } : process.env,
151
- stdio: [spawnOptions.stdin ?? "ignore", "pipe", "pipe"],
152
- detached: ownsProcessGroup
153
- });
154
- let untrack;
155
- try {
156
- untrack = child.pid ? trackCheckoutChild(cwd, child.pid, ownsProcessGroup) : void 0;
157
- } catch (error) {
158
- child.once("error", reject);
159
- child.once("close", () => reject(error));
160
- try {
161
- if (ownsProcessGroup && child.pid) process.kill(-child.pid, "SIGKILL");
162
- else child.kill("SIGKILL");
163
- } catch (killError) {
164
- if (killError.code !== "ESRCH") reject(killError);
165
- }
166
- return;
167
- }
168
- const captured = new OperationOutputTail();
169
- let settled = false;
170
- let didTimeout = false;
171
- let escalation;
172
- let forwardedParentSignal;
173
- let parentSignalFallback;
174
- const parentSignals = ["SIGINT", "SIGTERM", "SIGHUP", "SIGQUIT"];
175
- const removeParentSignalHandlers = () => {
176
- for (const signal of parentSignals) process.removeListener(signal, parentSignalHandlers[signal]);
177
- };
178
- const finish = (result) => {
179
- if (settled) return;
180
- settled = true;
181
- if (timer) clearTimeout(timer);
182
- if (escalation) clearTimeout(escalation);
183
- if (parentSignalFallback) clearTimeout(parentSignalFallback);
184
- removeParentSignalHandlers();
185
- resolve(result);
186
- };
187
- const signalChildTree = (signal) => {
188
- try {
189
- if (ownsProcessGroup && child.pid) process.kill(-child.pid, signal);
190
- else child.kill(signal);
191
- } catch {
192
- }
193
- };
194
- const exitFromForwardedSignal = () => {
195
- if (!forwardedParentSignal) return;
196
- const signal = forwardedParentSignal;
197
- forwardedParentSignal = void 0;
198
- if (parentSignalFallback) clearTimeout(parentSignalFallback);
199
- process.kill(process.pid, signal);
200
- };
201
- const parentSignalHandlers = Object.fromEntries(parentSignals.map((signal) => [signal, () => {
202
- if (forwardedParentSignal) return;
203
- forwardedParentSignal = signal;
204
- signalChildTree(signal);
205
- removeParentSignalHandlers();
206
- if (Number(spawnOptions.timeoutMs) > 0) parentSignalFallback = setTimeout(() => {
207
- signalChildTree("SIGKILL");
208
- exitFromForwardedSignal();
209
- }, 1e3);
210
- }]));
211
- if (ownsProcessGroup) {
212
- for (const signal of parentSignals) process.once(signal, parentSignalHandlers[signal]);
213
- }
214
- const timeoutMs = spawnOptions.timeoutMs;
215
- const timer = Number.isFinite(timeoutMs) && Number(timeoutMs) > 0 ? setTimeout(() => {
216
- didTimeout = true;
217
- const message = `leaf timed out after ${String(timeoutMs)}ms: ${path.basename(script)}`;
218
- captured.append(`
219
- ${message}
220
- `);
221
- if (spawnOptions.forward !== "none") process.stderr.write(`${message}
222
- `);
223
- signalChildTree("SIGTERM");
224
- escalation = setTimeout(() => {
225
- signalChildTree("SIGKILL");
226
- finish({ status: 1, output: captured.toString(), timedOut: true, timeoutMs: Number(timeoutMs) });
227
- }, 1e3);
228
- }, Number(timeoutMs)) : void 0;
229
- const decoders = [new StringDecoder("utf8"), new StringDecoder("utf8")];
230
- const tee = (chunk, stdout = false) => {
231
- if (spawnOptions.capture !== false) captured.append(decoders[stdout ? 0 : 1].write(chunk));
232
- recordCommandOutput(chunk);
233
- if (spawnOptions.forward !== "none") {
234
- (stdout && spawnOptions.forward === "original" ? process.stdout : process.stderr).write(chunk);
235
- }
236
- };
237
- child.stdout?.on("data", (chunk) => tee(chunk, true));
238
- child.stderr?.on("data", tee);
239
- child.on("error", (error) => {
240
- const leaf = isNodeScript ? path.basename(args[0]) : path.basename(script);
241
- const code = error.code ?? "ESPAWN";
242
- const message = `leaf could not start: ${leaf} (${code})
243
- Next: reinstall mm-harness (npm i -g @deeeed/metamask-harness) \u2014 the shell leaf is missing or not executable`;
244
- if (spawnOptions.forward !== "none") process.stderr.write(`${message}
245
- `);
246
- finish({ status: 1, output: message, error });
247
- });
248
- child.on("exit", () => {
249
- if (forwardedParentSignal) {
250
- exitFromForwardedSignal();
251
- return;
252
- }
253
- if (!didTimeout) return;
254
- signalChildTree("SIGKILL");
255
- finish({ status: 1, output: captured.toString(), timedOut: true, timeoutMs: Number(timeoutMs) });
256
- });
257
- child.on("close", (status) => {
258
- if (status !== null) untrack?.();
259
- if (spawnOptions.capture !== false) captured.append(decoders.map((decoder) => decoder.end()).join(""));
260
- if (didTimeout) signalChildTree("SIGKILL");
261
- finish({
262
- status: didTimeout ? 1 : status ?? 1,
263
- output: captured.toString(),
264
- ...didTimeout ? { timedOut: true, timeoutMs: Number(timeoutMs) } : {}
265
- });
266
- });
267
- });
268
- }
269
- import { ADAPTER_DETECT_NEXT } from "../harness.js";
270
- function usageOut(json, command, message, userAction) {
271
- if (json) {
272
- console.log(
273
- JSON.stringify(
274
- { schemaVersion: 1, command, status: "fail", exitCode: EXIT.usage, error: { code: "USAGE", message, userAction } },
275
- null,
276
- 2
277
- )
278
- );
279
- } else {
280
- console.error(`\u2717 mm-harness ${command}: ${message}
281
- Next: ${userAction}`);
282
- }
283
- return EXIT.usage;
284
- }
285
- function checkoutBusyOut(json, command, message, lockPath) {
286
- const userAction = `wait for the current owner, or inspect ${lockPath} if its process has exited`;
287
- if (json) {
288
- console.log(
289
- JSON.stringify(
290
- {
291
- schemaVersion: 1,
292
- command,
293
- status: "fail",
294
- exitCode: EXIT.bounded,
295
- recoverable: false,
296
- error: { code: "SANDBOX_BUSY", message, userAction }
297
- },
298
- null,
299
- 2
300
- )
301
- );
302
- } else {
303
- console.error(`\u2717 mm-harness ${command}: ${message}
304
- Next: ${userAction}`);
305
- }
306
- return EXIT.bounded;
307
- }
308
- export {
309
- ADAPTER_DETECT_NEXT,
310
- EXIT,
311
- checkoutBusyOut,
312
- flag,
313
- parseFlags,
314
- resolveAdapter,
315
- scriptOverride,
316
- spawnInherit,
317
- spawnScript,
318
- spawnScriptStreaming,
319
- str,
320
- targetOf,
321
- usageOut,
322
- writeInteractiveProgress
323
- };
@@ -1,60 +0,0 @@
1
- import { color } from "../cli-color.js";
2
- import { getAdapterSurface } from "../adapters/surface.js";
3
- import { usageOut } from "./shared.js";
4
- import { stopProjectCollector } from "../../library/actions/shared/analytics/collector.mjs";
5
- import {
6
- parseArgs,
7
- optionFlag,
8
- optionString,
9
- resolveAdapter,
10
- shellQuote
11
- } from "./parse-args.js";
12
- async function handleStop(argv) {
13
- const { options } = parseArgs(argv, "stop");
14
- const json = optionFlag(options, "json");
15
- const { adapter, target } = resolveAdapter(options);
16
- const surface = getAdapterSurface(adapter);
17
- surface.resolveSlotPorts(target);
18
- const explicitPort = optionString(options, "port") ?? optionString(options, "watcherPort");
19
- if (explicitPort) {
20
- process.env.WATCHER_PORT = explicitPort;
21
- process.env.METRO_PORT = explicitPort;
22
- }
23
- const stop = surface.devServer.stop(target);
24
- if (stop.kind === "headless") {
25
- return usageOut(json, "stop", stop.message, stop.userAction);
26
- }
27
- const collector = await stopProjectCollector(target);
28
- const signalled = (stop.signalled ?? 0) + (collector.stopped ? 1 : 0);
29
- const summary = collector.stopped ? `${stop.summary}; stopped analytics collector ${collector.pid}` : stop.summary;
30
- const next = surface.devServer.afterStop?.(target);
31
- const userAction = `mm-harness status --target ${shellQuote(target)} --json`;
32
- if (json) {
33
- console.log(
34
- JSON.stringify(
35
- {
36
- schemaVersion: 1,
37
- command: "stop",
38
- adapter,
39
- target,
40
- status: stop.status === 0 ? "pass" : "fail",
41
- ...stop.signalled !== void 0 || collector.stopped ? { signalled } : {},
42
- exitCode: stop.status,
43
- ...stop.output ? { output: stop.output } : {},
44
- ...stop.status === 0 ? next ? { next } : {} : { error: { code: "DEV_SERVER_STOP_FAILED", message: summary, userAction } }
45
- },
46
- null,
47
- 2
48
- )
49
- );
50
- } else {
51
- if (stop.output) process.stderr.write(`${stop.output}
52
- `);
53
- console.error(`${color(stop.status === 0 ? "ok" : "err", stop.status === 0 ? "\u2713" : "\u2717")} ${summary}`);
54
- if (stop.status !== 0 || next) console.error(` Next: ${stop.status === 0 ? next : userAction}`);
55
- }
56
- return stop.status;
57
- }
58
- export {
59
- handleStop
60
- };