@deeeed/metamask-harness 0.5.0 → 0.6.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 (102) hide show
  1. package/CHANGELOG.md +51 -20
  2. package/adapters/core/inject.sh +1 -0
  3. package/adapters/extension/cleanup.mjs +1 -1
  4. package/adapters/extension/inject.mjs +4 -1
  5. package/adapters/extension/launch-browser.cjs +18 -4
  6. package/adapters/extension/live.sh +14 -2
  7. package/adapters/extension/refresh-build.sh +2 -2
  8. package/adapters/extension/sidepanel-toggle.sh +33 -12
  9. package/adapters/extension/start-watch.sh +2 -2
  10. package/adapters/extension/wallet-fixture-state.cjs +38 -24
  11. package/adapters/manifest.json +32 -3
  12. package/adapters/mobile/inject.sh +1 -0
  13. package/adapters/mobile/start-metro.sh +1 -1
  14. package/adapters/mobile/stop-metro.sh +20 -0
  15. package/adapters/mobile/verify.sh +1 -1
  16. package/adapters/shared/activate-repo-node.sh +1 -1
  17. package/adapters/shared/cli-ux.sh +44 -25
  18. package/adapters/shared/ensure-runner-deps.sh +30 -0
  19. package/adapters/shared/log-tui.mjs +4 -4
  20. package/adapters/shared/open-debug.mjs +1 -1
  21. package/adapters/shared/reap-checkout-metros.sh +53 -0
  22. package/adapters/shared/recipe-harness-root.mjs +23 -0
  23. package/adapters/shared/resolve-farmslot-ports-core.mjs +205 -0
  24. package/adapters/shared/resolve-farmslot-ports.mjs +20 -0
  25. package/adapters/shared/resolve-farmslot-ports.sh +19 -126
  26. package/bin/mm-harness +45 -4
  27. package/dist/adapters/extension/runtime.js +3 -1
  28. package/dist/adapters/mobile/provision.js +34 -1
  29. package/dist/adapters/mobile/runtime-decision.js +3 -1
  30. package/dist/adapters/resolve-farmslot-ports.js +22 -0
  31. package/dist/adapters/slot-ports.js +18 -29
  32. package/dist/cli.js +32 -1463
  33. package/dist/commands/call.js +183 -0
  34. package/dist/commands/completion-candidates.js +58 -0
  35. package/dist/commands/doctor.js +101 -0
  36. package/dist/commands/ensure-ready.js +24 -0
  37. package/dist/commands/flows.js +62 -0
  38. package/dist/commands/launch/extension.js +40 -0
  39. package/dist/commands/{launch.js → launch/index.js} +15 -47
  40. package/dist/commands/launch/mobile.js +10 -0
  41. package/dist/commands/manifest.js +72 -0
  42. package/dist/commands/parse-args.js +189 -0
  43. package/dist/commands/provision.js +136 -0
  44. package/dist/commands/resolve-extension.js +23 -0
  45. package/dist/commands/run-engine.js +341 -0
  46. package/dist/commands/run.js +217 -0
  47. package/dist/commands/runtime-decision.js +58 -0
  48. package/dist/commands/runtime-health.js +25 -0
  49. package/dist/commands/runtime-launch.js +139 -0
  50. package/dist/commands/self-test.js +52 -0
  51. package/dist/commands/stop.js +52 -0
  52. package/dist/harness.js +8 -48
  53. package/dist/mm-harness-cli.js +13 -8
  54. package/docs/CLI-SPEC.md +1 -1
  55. package/docs/CODE-MAP.md +62 -0
  56. package/library/README.md +14 -0
  57. package/library/actions/core/perps/_controller.mjs +1 -1
  58. package/library/actions/extension/platform/cdp.mjs +2 -2
  59. package/library/actions/extension/wallet/ensure_unlocked.mjs +7 -1
  60. package/library/actions/harness-exports.mjs +27 -0
  61. package/library/actions/mobile/wallet/ensure_unlocked.mjs +14 -2
  62. package/library/actions/mobile/wallet/setup.mjs +1 -1
  63. package/package.json +6 -7
  64. package/src/adapters/core/surface.ts +0 -71
  65. package/src/adapters/extension/ensure-ready.ts +0 -185
  66. package/src/adapters/extension/extension-id.ts +0 -107
  67. package/src/adapters/extension/runtime-decision.ts +0 -445
  68. package/src/adapters/extension/runtime.ts +0 -407
  69. package/src/adapters/extension/surface.ts +0 -88
  70. package/src/adapters/mobile/deps-markers.ts +0 -21
  71. package/src/adapters/mobile/prepare.ts +0 -246
  72. package/src/adapters/mobile/provision.ts +0 -594
  73. package/src/adapters/mobile/runtime-decision.ts +0 -459
  74. package/src/adapters/mobile/surface.ts +0 -71
  75. package/src/adapters/slot-ports.ts +0 -165
  76. package/src/adapters/surface.ts +0 -117
  77. package/src/adapters.ts +0 -601
  78. package/src/cli-color.ts +0 -92
  79. package/src/cli-commands.ts +0 -250
  80. package/src/cli-version.ts +0 -141
  81. package/src/cli.ts +0 -2091
  82. package/src/commands/debug.ts +0 -65
  83. package/src/commands/fixtures.ts +0 -198
  84. package/src/commands/launch.ts +0 -470
  85. package/src/commands/logs.ts +0 -99
  86. package/src/commands/shared.ts +0 -235
  87. package/src/commands/update.ts +0 -316
  88. package/src/completions-cache.ts +0 -86
  89. package/src/doctor.ts +0 -215
  90. package/src/harness.ts +0 -797
  91. package/src/heal-bounds.ts +0 -198
  92. package/src/index.ts +0 -15
  93. package/src/leaf-invoke.ts +0 -28
  94. package/src/live-adapter-contract.ts +0 -274
  95. package/src/manifest.ts +0 -47
  96. package/src/mm-harness-cli.ts +0 -655
  97. package/src/paths.ts +0 -198
  98. package/src/progress.ts +0 -117
  99. package/src/recording-target.ts +0 -147
  100. package/src/run-recording.ts +0 -329
  101. package/src/runner.ts +0 -108
  102. package/src/types.ts +0 -57
@@ -1,407 +0,0 @@
1
- // runtime.ts — extension runtime prepare/health over CDP.
2
- // App/instance control.
3
- import { spawn } from 'node:child_process';
4
- import fs from 'node:fs';
5
- import path from 'node:path';
6
-
7
- import { importRecipeHarnessRuntimeBrowserExtension, importRecipeHarnessRuntimeCdp, recipeRuntimeDir, resolveLocalProtocolRoot, resolveRequiredLocalProtocolRoot, runnerDir } from '../../paths.ts';
8
-
9
- // Runtime health uses package dependencies. Launching a host-managed validation
10
- // browser remains a dev-only path because it needs pool/project scripts from a
11
- // checkout.
12
- const { CdpSession, jsonGet, sleep } = await importRecipeHarnessRuntimeCdp();
13
- const { extensionIdFromTarget } = await importRecipeHarnessRuntimeBrowserExtension();
14
-
15
- export interface ExtensionRuntimeOptions {
16
- projectRoot: string;
17
- cdpPort?: string | number;
18
- slot?: string;
19
- launchExistingDist?: boolean;
20
- validationRuntimeDir?: string;
21
- healthTimeoutMs?: number;
22
- }
23
-
24
- export interface ExtensionRuntimeLaunchResult {
25
- launched: boolean;
26
- slotId?: string;
27
- cdpPort: number;
28
- runtimeDir?: string;
29
- stdout?: string;
30
- }
31
-
32
- export interface ExtensionRuntimeHealthReport {
33
- status: 'PASS' | 'FAIL';
34
- cdpPort: number;
35
- targetUrl?: string;
36
- extensionId?: string;
37
- extensionPageTargets: number;
38
- findings: string[];
39
- details: Record<string, unknown>;
40
- }
41
-
42
- interface CdpTarget {
43
- id?: string;
44
- type?: string;
45
- title?: string;
46
- url?: string;
47
- webSocketDebuggerUrl?: string;
48
- }
49
-
50
- export async function prepareExtensionRuntime(
51
- options: ExtensionRuntimeOptions,
52
- ): Promise<{ launch: ExtensionRuntimeLaunchResult | null; health: ExtensionRuntimeHealthReport }> {
53
- const projectRoot = path.resolve(options.projectRoot);
54
- const slot = resolveExtensionSlot(projectRoot, options.slot);
55
- const cdpPort = resolveCdpPort(options.cdpPort, slot);
56
- let launch: ExtensionRuntimeLaunchResult | null = null;
57
- if (options.launchExistingDist) {
58
- if (!slot) {
59
- throw new Error(
60
- `Cannot launch host-managed Extension runtime for ${projectRoot}: no configured slot maps to this repo. ` +
61
- 'Pass --slot <slot-id> or add the checkout to pool/*.json.',
62
- );
63
- }
64
- launch = await launchHostValidationBrowser({
65
- projectRoot,
66
- cdpPort,
67
- slotId: slot.id,
68
- validationRuntimeDir:
69
- options.validationRuntimeDir ?? `${recipeRuntimeDir()}/validation-${cdpPort}`,
70
- });
71
- }
72
- const health = await assertHealthyExtensionRuntime({
73
- projectRoot,
74
- cdpPort,
75
- timeoutMs: options.healthTimeoutMs,
76
- });
77
- return { launch, health };
78
- }
79
-
80
- export async function assertHealthyExtensionRuntime(options: {
81
- projectRoot: string;
82
- cdpPort: number;
83
- timeoutMs?: number;
84
- }): Promise<ExtensionRuntimeHealthReport> {
85
- const timeoutMs = options.timeoutMs ?? 30_000;
86
- const deadline = Date.now() + timeoutMs;
87
- let lastReport: ExtensionRuntimeHealthReport | null = null;
88
- while (Date.now() <= deadline) {
89
- lastReport = await checkExtensionRuntimeHealth(options.projectRoot, options.cdpPort);
90
- if (lastReport.status === 'PASS') return lastReport;
91
- await sleep(500);
92
- }
93
- const report = lastReport ?? failReport(options.cdpPort, ['CDP runtime was not probed.'], {});
94
- throw new Error(formatHealthFailure(report, options.projectRoot));
95
- }
96
-
97
- export async function checkExtensionRuntimeHealth(
98
- projectRoot: string,
99
- cdpPort: number,
100
- ): Promise<ExtensionRuntimeHealthReport> {
101
- const findings: string[] = [];
102
- let targets: CdpTarget[] = [];
103
- try {
104
- await jsonGet(`http://127.0.0.1:${cdpPort}/json/version`);
105
- const rawTargets = await jsonGet(`http://127.0.0.1:${cdpPort}/json/list`);
106
- targets = Array.isArray(rawTargets) ? rawTargets as CdpTarget[] : [];
107
- } catch (error) {
108
- return failReport(cdpPort, [`CDP is not reachable on port ${cdpPort}: ${messageOf(error)}`], {});
109
- }
110
-
111
- const extensionTargets = targets.filter((target) =>
112
- target.type === 'page' &&
113
- String(target.url ?? '').startsWith('chrome-extension://') &&
114
- String(target.url ?? '').includes('/home.html') &&
115
- Boolean(target.webSocketDebuggerUrl),
116
- );
117
- if (extensionTargets.length !== 1) {
118
- findings.push(`Expected exactly one MetaMask extension home page target, found ${extensionTargets.length}.`);
119
- }
120
- const target = extensionTargets[0];
121
- if (!target?.webSocketDebuggerUrl) {
122
- return failReport(cdpPort, findings, {
123
- targetUrls: targets.map((entry) => entry.url).filter(Boolean),
124
- });
125
- }
126
- if (String(target.url ?? '').startsWith('chrome-error://')) {
127
- findings.push(`Extension target resolved to chrome-error page: ${target.url}`);
128
- }
129
-
130
- const session = await CdpSession.connect(target.webSocketDebuggerUrl);
131
- try {
132
- await session.call('Runtime.enable');
133
- await session.call('Page.enable');
134
- const runtime = await evaluateHealth(session);
135
- if (runtime.href && !String(runtime.href).startsWith('chrome-extension://')) {
136
- findings.push(`Extension page href is not an extension URL: ${runtime.href}`);
137
- }
138
- if (runtime.backgroundUnresponsive === true) {
139
- findings.push('Extension UI reports background connection unresponsive.');
140
- }
141
- if (runtime.hasSubmitRequest !== true) {
142
- findings.push('stateHooks.submitRequestToBackground is unavailable.');
143
- }
144
- if (runtime.hasStore !== true) {
145
- findings.push('stateHooks.store is unavailable.');
146
- }
147
- if (runtime.hasPerpsStreamManager !== true) {
148
- findings.push('stateHooks.getPerpsStreamManager is unavailable.');
149
- }
150
- if (runtime.backgroundProbeOk !== true) {
151
- findings.push(`Perps background read probe failed: ${runtime.backgroundProbeError ?? 'unknown error'}.`);
152
- }
153
- const extensionId = safeExtensionId(target);
154
- return {
155
- status: findings.length === 0 ? 'PASS' : 'FAIL',
156
- cdpPort,
157
- targetUrl: target.url,
158
- extensionId,
159
- extensionPageTargets: extensionTargets.length,
160
- findings,
161
- details: {
162
- projectRoot,
163
- targetUrls: targets.map((entry) => entry.url).filter(Boolean),
164
- runtime,
165
- },
166
- };
167
- } finally {
168
- session.close();
169
- }
170
- }
171
-
172
- function failReport(
173
- cdpPort: number,
174
- findings: string[],
175
- details: Record<string, unknown>,
176
- ): ExtensionRuntimeHealthReport {
177
- return {
178
- status: 'FAIL',
179
- cdpPort,
180
- extensionPageTargets: 0,
181
- findings,
182
- details,
183
- };
184
- }
185
-
186
- async function evaluateHealth(session: any): Promise<Record<string, unknown>> {
187
- const result = await session.call('Runtime.evaluate', {
188
- expression: `(() => {
189
- const hooks = globalThis.stateHooks || {};
190
- const bodyText = document.body?.innerText || '';
191
- const storeState = hooks.store?.getState?.() || {};
192
- const manager = hooks.getPerpsStreamManager?.();
193
- const accountCache = manager?.account?.cache;
194
- return Promise.race([
195
- (async () => {
196
- let backgroundProbeOk = false;
197
- let backgroundProbeError = null;
198
- if (typeof hooks.submitRequestToBackground === 'function') {
199
- try {
200
- const accountState = await hooks.submitRequestToBackground('perpsGetAccountState', []);
201
- backgroundProbeOk = Boolean(accountState && typeof accountState === 'object');
202
- if (!backgroundProbeOk) backgroundProbeError = 'perpsGetAccountState returned an empty result';
203
- } catch (error) {
204
- backgroundProbeError = String(error?.message || error);
205
- }
206
- } else {
207
- backgroundProbeError = 'submitRequestToBackground is not a function';
208
- }
209
- return {
210
- href: location.href,
211
- title: document.title,
212
- hookKeys: Object.keys(hooks),
213
- hasSubmitRequest: typeof hooks.submitRequestToBackground === 'function',
214
- hasStore: Boolean(hooks.store),
215
- hasPerpsStreamManager: typeof hooks.getPerpsStreamManager === 'function',
216
- backgroundUnresponsive: bodyText.includes('Background connection unresponsive') || bodyText.includes('MetaMask had trouble starting'),
217
- activeProvider: storeState.metamask?.activeProvider || null,
218
- isTestnet: Boolean(storeState.metamask?.isTestnet),
219
- perpsManagerInitialized: Boolean(manager?.isInitialized?.()),
220
- positionsCacheIsArray: Array.isArray(manager?.positions?.cache),
221
- ordersCacheIsArray: Array.isArray(manager?.orders?.cache),
222
- accountCachePresent: Boolean(accountCache && typeof accountCache === 'object'),
223
- backgroundProbeOk,
224
- backgroundProbeError,
225
- };
226
- })(),
227
- new Promise((resolve) => setTimeout(() => resolve({
228
- href: location.href,
229
- title: document.title,
230
- hookKeys: Object.keys(hooks),
231
- hasSubmitRequest: typeof hooks.submitRequestToBackground === 'function',
232
- hasStore: Boolean(hooks.store),
233
- hasPerpsStreamManager: typeof hooks.getPerpsStreamManager === 'function',
234
- backgroundUnresponsive: bodyText.includes('Background connection unresponsive') || bodyText.includes('MetaMask had trouble starting'),
235
- backgroundProbeOk: false,
236
- backgroundProbeError: 'perpsGetAccountState timed out after 5000ms',
237
- }), 5000)),
238
- ]);
239
- })()`,
240
- awaitPromise: true,
241
- returnByValue: true,
242
- });
243
- if (result.exceptionDetails) {
244
- throw new Error(
245
- result.exceptionDetails.exception?.description ??
246
- result.exceptionDetails.text ??
247
- 'Extension runtime health evaluation failed.',
248
- );
249
- }
250
- return result.result?.value ?? {};
251
- }
252
-
253
- export function formatHealthFailure(report: ExtensionRuntimeHealthReport, projectRoot: string): string {
254
- return [
255
- `Extension runtime health check failed for ${projectRoot} on CDP port ${report.cdpPort}.`,
256
- ...report.findings.map((finding) => `- ${finding}`),
257
- 'Use the host-managed validation launcher, for example:',
258
- ` mm-harness run <recipe.json> --adapter extension --target ${projectRoot} --slot <slot-id> --cdp-port ${report.cdpPort} --launch-existing-dist --artifacts-dir <artifacts-dir>`,
259
- `Health details: ${JSON.stringify(report.details)}`,
260
- ].join('\n');
261
- }
262
-
263
- function resolveCdpPort(rawPort: string | number | undefined, slot: ResolvedSlot | null): number {
264
- const raw = rawPort ?? process.env.CDP_PORT ?? process.env.RECIPE_CDP_PORT ?? slot?.cdpPort;
265
- const port = Number(raw);
266
- if (!Number.isInteger(port) || port <= 0) {
267
- throw new Error('Extension runtime requires --cdp-port, CDP_PORT, RECIPE_CDP_PORT, or a configured slot with resources.browser.cdp_port.');
268
- }
269
- return port;
270
- }
271
-
272
- interface ResolvedSlot {
273
- id: string;
274
- repo: string;
275
- cdpPort?: number;
276
- }
277
-
278
- function resolveExtensionSlot(projectRoot: string, requestedSlot?: string): ResolvedSlot | null {
279
- const protocolRoot = resolveLocalProtocolRoot();
280
- if (!protocolRoot) {
281
- if (requestedSlot) throw new Error('Extension slot lookup requires a local host checkout when --slot is provided.');
282
- return null;
283
- }
284
- const poolsDir = path.join(protocolRoot, 'pool');
285
- if (!fs.existsSync(poolsDir)) return null;
286
- for (const file of fs.readdirSync(poolsDir)) {
287
- // Only canonical pool files: skip backups (`*.json.bak.*`) and any non-.json,
288
- // so a stale backup can't resolve a slot the live pool no longer defines.
289
- if (!file.endsWith('.json') || file.includes('.bak')) continue;
290
- const pool = JSON.parse(fs.readFileSync(path.join(poolsDir, file), 'utf8')) as { slots?: unknown[] };
291
- for (const rawSlot of pool.slots ?? []) {
292
- if (!rawSlot || typeof rawSlot !== 'object') continue;
293
- const slot = rawSlot as Record<string, any>;
294
- if (requestedSlot && slot.id !== requestedSlot) continue;
295
- if (!requestedSlot) {
296
- // Auto-match by repo requires an ABSOLUTE repo path. A relative entry —
297
- // e.g. demo pools' "repo": "." — resolves to the runner's CWD and would
298
- // spuriously shadow the real slot for whatever repo it runs in.
299
- const repo = String(slot.repo ?? '');
300
- if (!path.isAbsolute(repo) || path.resolve(repo) !== projectRoot) continue;
301
- }
302
- return {
303
- id: String(slot.id),
304
- repo: path.resolve(String(slot.repo)),
305
- cdpPort: Number(slot.resources?.browser?.cdp_port || slot.resources?.browser?.cdpPort || undefined) || undefined,
306
- };
307
- }
308
- }
309
- return null;
310
- }
311
-
312
- async function launchHostValidationBrowser(options: {
313
- projectRoot: string;
314
- cdpPort: number;
315
- slotId: string;
316
- validationRuntimeDir: string;
317
- }): Promise<ExtensionRuntimeLaunchResult> {
318
- const protocolRoot = resolveRequiredLocalProtocolRoot('Extension validation browser launch');
319
- const script = path.join(protocolRoot, 'projects/metamask-extension-farm/setup/launch-validation-browser.sh');
320
- if (!fs.existsSync(script)) {
321
- throw new Error(`Extension validation launcher not found: ${script}`);
322
- }
323
- await killCdpPort(options.cdpPort);
324
- const result = await runProcess('bash', [
325
- script,
326
- options.slotId,
327
- '--cdp-port',
328
- String(options.cdpPort),
329
- '--runtime-dir',
330
- options.validationRuntimeDir,
331
- '--no-landing',
332
- ], {
333
- cwd: protocolRoot,
334
- env: process.env,
335
- timeoutMs: 180_000,
336
- });
337
- if (result.timedOut) {
338
- throw new Error(`Extension validation launcher timed out after 180000ms.\n${result.stdout}\n${result.stderr}`);
339
- }
340
- if (result.exitCode !== 0) {
341
- throw new Error(`Extension validation launcher failed with exit ${result.exitCode}.\n${result.stdout}\n${result.stderr}`);
342
- }
343
- return {
344
- launched: true,
345
- slotId: options.slotId,
346
- cdpPort: options.cdpPort,
347
- runtimeDir: options.validationRuntimeDir,
348
- stdout: result.stdout,
349
- };
350
- }
351
-
352
- function runProcess(command: string, args: string[], options: { cwd: string; env: NodeJS.ProcessEnv; timeoutMs?: number }): Promise<{ exitCode: number | null; stdout: string; stderr: string; timedOut: boolean }> {
353
- return new Promise((resolve, reject) => {
354
- const child = spawn(command, args, { cwd: options.cwd, env: options.env, stdio: ['ignore', 'pipe', 'pipe'] });
355
- let stdout = '';
356
- let stderr = '';
357
- let settled = false;
358
- const timeout = options.timeoutMs
359
- ? setTimeout(() => {
360
- if (settled) return;
361
- settled = true;
362
- child.kill('SIGTERM');
363
- setTimeout(() => {
364
- if (!child.killed) child.kill('SIGKILL');
365
- }, 1000);
366
- resolve({ exitCode: null, stdout, stderr, timedOut: true });
367
- }, options.timeoutMs)
368
- : undefined;
369
- child.stdout.on('data', (chunk) => { stdout += chunk; });
370
- child.stderr.on('data', (chunk) => { stderr += chunk; });
371
- child.on('error', (error) => {
372
- if (settled) return;
373
- settled = true;
374
- if (timeout) clearTimeout(timeout);
375
- reject(error);
376
- });
377
- child.on('close', (exitCode) => {
378
- if (settled) return;
379
- settled = true;
380
- if (timeout) clearTimeout(timeout);
381
- resolve({ exitCode, stdout, stderr, timedOut: false });
382
- });
383
- });
384
- }
385
-
386
- async function killCdpPort(cdpPort: number): Promise<void> {
387
- if (process.platform !== 'darwin' && process.platform !== 'linux') return;
388
- await runProcess('bash', ['-lc', `lsof -ti tcp:${cdpPort} -sTCP:LISTEN | xargs -r kill -TERM || true; sleep 1; lsof -ti tcp:${cdpPort} -sTCP:LISTEN | xargs -r kill -KILL || true`], {
389
- cwd: runnerDir,
390
- env: process.env,
391
- timeoutMs: 10_000,
392
- });
393
- }
394
-
395
- function safeExtensionId(target: CdpTarget): string | undefined {
396
- try {
397
- return extensionIdFromTarget(target);
398
- } catch (_error) {
399
- // Non-extension CDP targets are expected while scanning all browser tabs.
400
- // Treat them as unmatched targets instead of failing runtime discovery.
401
- return undefined;
402
- }
403
- }
404
-
405
- function messageOf(error: unknown): string {
406
- return error instanceof Error ? error.message : String(error);
407
- }
@@ -1,88 +0,0 @@
1
- // Extension surface: delegates to the existing extension readiness/port plumbing
2
- // and the webpack watcher stop.
3
- import { spawnSync } from 'node:child_process';
4
- import path from 'node:path';
5
-
6
- import { recipeRuntimePath } from '../../paths.ts';
7
- import { resolveExtensionSlotPorts, stopExtensionWatcher } from '../slot-ports.ts';
8
- import { decideExtensionReadiness } from './runtime-decision.ts';
9
- import type {
10
- AdapterDevServerStop,
11
- AdapterLogSource,
12
- AdapterRuntimeStatus,
13
- AdapterRunwayProvisionResult,
14
- AdapterSurface,
15
- } from '../surface.ts';
16
-
17
- // Map the webpack watch-log health onto a dev-server up/down/building/errors
18
- // status doctor renders the same way as mobile's Metro line.
19
- function watcherStatus(buildLog: string): string {
20
- if (buildLog === 'ok') return 'up';
21
- if (buildLog === 'no-watch') return 'down';
22
- return buildLog; // 'building' | 'errors'
23
- }
24
-
25
- export const extensionSurface: AdapterSurface = {
26
- adapter: 'extension',
27
- headless: false,
28
-
29
- resolveSlotPorts(target: string): void {
30
- resolveExtensionSlotPorts(target);
31
- },
32
-
33
- async runtimeStatus(target: string): Promise<AdapterRuntimeStatus> {
34
- const cdpPort = process.env.CDP_PORT ? parseInt(process.env.CDP_PORT, 10) : undefined;
35
- const report = await decideExtensionReadiness(target, { cdpPort });
36
- return {
37
- decision: report.decision,
38
- reasonCode: report.reasonCode,
39
- reasons: report.reasons,
40
- deps: report.checks.deps.status,
41
- devServer: { label: 'webpack', status: watcherStatus(report.checks.buildLog.status) },
42
- };
43
- },
44
-
45
- runwayProvision: {
46
- async run(target, options): Promise<AdapterRunwayProvisionResult> {
47
- return {
48
- schemaVersion: 1,
49
- command: 'provision',
50
- adapter: 'extension',
51
- target: path.resolve(target),
52
- status: 'fail',
53
- exitCode: 2,
54
- error: {
55
- code: 'UNSUPPORTED_ADAPTER',
56
- message: 'extension uses a browser runtime; Runway mobile provisioning is not supported.',
57
- userAction: options.rerunCommand || 'mm-harness provision --adapter extension',
58
- },
59
- };
60
- },
61
- },
62
-
63
- devServer: {
64
- describe: () => 'webpack watcher',
65
- stop(target: string): AdapterDevServerStop {
66
- const signalled = stopExtensionWatcher(target);
67
- const port = process.env.WATCHER_PORT ?? 'default';
68
- spawnSync('tmux', ['kill-window', '-t', `webpack-${port}`], { stdio: 'ignore', timeout: 2000 });
69
- const summary = signalled > 0
70
- ? `stopped webpack watcher (${signalled} process${signalled === 1 ? '' : 'es'}) for ${target}`
71
- : `webpack watcher not running for ${target} — nothing to stop`;
72
- return { kind: 'stopped', status: 0, summary, signalled };
73
- },
74
- },
75
-
76
- logSources(target: string): AdapterLogSource[] {
77
- return [
78
- { label: 'webpack', path: recipeRuntimePath(target, 'webpack.log') },
79
- { label: 'watcher', path: recipeRuntimePath(target, 'recipe-harness-webpack.log') },
80
- { label: 'rebuild', path: recipeRuntimePath(target, 'rebuild.log') },
81
- ];
82
- },
83
-
84
- hints: {
85
- launch: 'mm-harness launch',
86
- relaunch: 'mm-harness launch --build',
87
- },
88
- };
@@ -1,21 +0,0 @@
1
- /** MetaMask Mobile product markers for deps-readiness partial checks. */
2
- export const MOBILE_PRODUCT_MARKERS = [
3
- 'node_modules/.yarn-state.yml',
4
- 'app/core/InpageBridgeWeb3.js',
5
- 'docs/assets/termsOfUse.html',
6
- 'app/util/termsOfUse/termsOfUseContent.ts',
7
- 'node_modules/.bin/anvil',
8
- ];
9
-
10
- export const MOBILE_IOS_NATIVE_MARKERS = ['ios/Podfile.lock'];
11
- export const MOBILE_ANDROID_NATIVE_MARKERS = ['android/gradle.properties'];
12
-
13
- export function mobileProductMarkers(platform?: string): string[] {
14
- const markers = [...MOBILE_PRODUCT_MARKERS];
15
- const resolved = (platform ?? process.env.PLATFORM ?? process.env.RECIPE_HARNESS_PLATFORM ?? '')
16
- .trim()
17
- .toLowerCase();
18
- if (resolved === 'ios') markers.push(...MOBILE_IOS_NATIVE_MARKERS);
19
- else if (resolved === 'android') markers.push(...MOBILE_ANDROID_NATIVE_MARKERS);
20
- return markers;
21
- }