@deeeed/metamask-harness 0.75.0 → 0.76.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/adapters/shared/reap-checkout-metros.sh +9 -0
- package/dist/adapters/core/overlay.js +34 -0
- package/dist/adapters/core/readiness.js +8 -0
- package/dist/adapters/core/surface.js +7 -0
- package/dist/adapters/extension/doctor-checks.js +135 -0
- package/dist/adapters/extension/fixtures.js +103 -0
- package/dist/adapters/extension/overlay.js +55 -0
- package/dist/adapters/extension/readiness.js +32 -0
- package/dist/adapters/extension/reload.js +40 -0
- package/dist/adapters/extension/surface.js +22 -1
- package/dist/adapters/harness-freshness.js +4 -2
- package/dist/adapters/mobile/devices.js +21 -0
- package/dist/adapters/mobile/doctor-checks.js +40 -0
- package/dist/adapters/mobile/fixtures.js +141 -0
- package/dist/adapters/mobile/overlay.js +27 -0
- package/dist/adapters/mobile/prepare-target.js +38 -0
- package/dist/adapters/mobile/readiness.js +39 -0
- package/dist/adapters/mobile/reload.js +15 -0
- package/dist/adapters/mobile/surface.js +28 -1
- package/dist/adapters/terminal/doctor-checks.js +22 -0
- package/dist/adapters/terminal/overlay.js +18 -0
- package/dist/adapters/terminal/readiness.js +11 -0
- package/dist/adapters/terminal/surface.js +15 -2
- package/dist/cli.js +3 -1
- package/dist/commands/debug.js +6 -25
- package/dist/commands/device-target.js +2 -1
- package/dist/commands/doctor.js +26 -137
- package/dist/commands/fixtures.js +13 -217
- package/dist/commands/logs.js +1 -1
- package/dist/commands/parse-args.js +9 -4
- package/dist/commands/prepare.js +23 -53
- package/dist/commands/provision.js +3 -7
- package/dist/commands/reload.js +8 -47
- package/dist/commands/runtime-decision.js +5 -27
- package/dist/commands/self-test.js +1 -3
- package/dist/commands/status.js +20 -28
- package/dist/commands/stop.js +1 -1
- package/dist/doctor.js +9 -119
- package/dist/harness.js +31 -100
- package/dist/mm-harness-cli.js +3 -2
- package/dist/paths.js +8 -2
- package/dist/runtime-context.js +12 -17
- package/docs/CONTRIBUTING.md +27 -17
- package/package.json +1 -1
package/dist/commands/doctor.js
CHANGED
|
@@ -1,28 +1,16 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
|
-
import { randomUUID } from "node:crypto";
|
|
3
2
|
import fs from "node:fs";
|
|
4
3
|
import path from "node:path";
|
|
5
|
-
import { readExtensionPinnedFlags } from "../adapters/extension/feature-flags.js";
|
|
6
4
|
import { featureFlagReport } from "../feature-flags.js";
|
|
7
5
|
import { color } from "../cli-color.js";
|
|
8
6
|
import { collectCheckoutView, renderCheckoutView } from "../checkout-view.js";
|
|
9
7
|
import { createDoctorReport, fixtureSummary, requiredDoctorCheckSummary } from "../doctor.js";
|
|
10
|
-
import { terminalAppPort, terminalCdpPort } from "../adapters/terminal/surface.js";
|
|
11
8
|
import { detectAdapter, resolveRuntimeContextPath } from "../harness.js";
|
|
12
9
|
import { assertAdapter, runnerDir } from "../paths.js";
|
|
13
10
|
import { getAdapterSurface } from "../adapters/surface.js";
|
|
14
|
-
import { extensionProductConfigBlock } from "../adapters/extension/product-config.js";
|
|
15
|
-
import { ensureMobilePerpsEnvironment, mobilePerpsEnvironment } from "../adapters/mobile/perps-env.js";
|
|
16
11
|
import { loadActionManifest, validateManifest } from "../manifest.js";
|
|
17
12
|
import { ensureOverlay, newHealState, recipeRunning } from "../heal-bounds.js";
|
|
18
13
|
import { applyDeviceTargeting } from "./device-target.js";
|
|
19
|
-
import {
|
|
20
|
-
mobileDeviceView,
|
|
21
|
-
mobileDeviceLiveView,
|
|
22
|
-
renderAdditionalReachableDevices,
|
|
23
|
-
renderMobileDeviceList,
|
|
24
|
-
renderMobileLiveBlock
|
|
25
|
-
} from "./mobile-device-view.js";
|
|
26
14
|
import { farmslotReadyIndicator } from "./farmslot-ready.js";
|
|
27
15
|
import { updateStatus } from "./update.js";
|
|
28
16
|
import { ensureRuntimeContext } from "../runtime-context.js";
|
|
@@ -63,6 +51,7 @@ async function handleDoctor({ options }) {
|
|
|
63
51
|
return usageOut(json, "doctor", `could not detect the MetaMask repo type for ${target}`, ADAPTER_DETECT_NEXT);
|
|
64
52
|
}
|
|
65
53
|
assertAdapter(adapter);
|
|
54
|
+
const { readiness } = getAdapterSurface(adapter);
|
|
66
55
|
if (!fs.existsSync(target)) {
|
|
67
56
|
return usageOut(
|
|
68
57
|
json,
|
|
@@ -110,10 +99,9 @@ async function handleDoctor({ options }) {
|
|
|
110
99
|
runtimeProbeError2 = error2 instanceof Error ? error2.message : String(error2);
|
|
111
100
|
}
|
|
112
101
|
const ready = runtime2?.decision === "ready";
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const nextActions = doctorFixNextActions(failed, result2.fixture, adapter, target);
|
|
102
|
+
const block = readiness.runtimeBlock?.(target, runtime2);
|
|
103
|
+
if (block) failed.push(block.id);
|
|
104
|
+
const nextActions = doctorFixNextActions(failed, result2.fixture, adapter, target, block);
|
|
117
105
|
if (failed.length === 0 && !ready && runtime2?.nextAction) nextActions.push(runtime2.nextAction);
|
|
118
106
|
const status = result2.status === "pass" && failed.length === 0 ? "pass" : "fail";
|
|
119
107
|
const retryDoctor = `mm-harness doctor --fix --adapter ${adapter} --target ${shellQuote(target)} --json`;
|
|
@@ -136,13 +124,12 @@ async function handleDoctor({ options }) {
|
|
|
136
124
|
}
|
|
137
125
|
return status === "pass" ? 0 : 1;
|
|
138
126
|
}
|
|
139
|
-
const mobilePlatform = adapter === "mobile" && (platformOption === "ios" || platformOption === "android") ? platformOption : void 0;
|
|
140
127
|
const result = createDoctorReport(
|
|
141
128
|
adapter,
|
|
142
129
|
target,
|
|
143
130
|
manifestValidation,
|
|
144
131
|
actionManifestPath,
|
|
145
|
-
|
|
132
|
+
platformOption
|
|
146
133
|
);
|
|
147
134
|
const view = collectCheckoutView(target, adapter);
|
|
148
135
|
const missingRequiredTool = result.checks.find(
|
|
@@ -183,7 +170,7 @@ async function handleDoctor({ options }) {
|
|
|
183
170
|
const checks = [
|
|
184
171
|
...result.checks,
|
|
185
172
|
runtimeCheck,
|
|
186
|
-
...
|
|
173
|
+
...await readiness.liveChecks?.(target) ?? []
|
|
187
174
|
];
|
|
188
175
|
const requiredChecks = requiredDoctorCheckSummary(checks);
|
|
189
176
|
const doctorResult = {
|
|
@@ -192,10 +179,11 @@ async function handleDoctor({ options }) {
|
|
|
192
179
|
checks,
|
|
193
180
|
requiredChecks
|
|
194
181
|
};
|
|
195
|
-
const orphanMetros =
|
|
196
|
-
const capture = captureHealth(
|
|
197
|
-
const
|
|
198
|
-
const
|
|
182
|
+
const orphanMetros = readiness.orphanDevServers?.(target, process.env.WATCHER_PORT) ?? [];
|
|
183
|
+
const capture = captureHealth(readiness.captureProviders);
|
|
184
|
+
const deviceSource = await readiness.devices?.();
|
|
185
|
+
const deviceView = deviceSource ? deviceSource.view(allDevices) : null;
|
|
186
|
+
const liveView = deviceSource && deviceView ? await deviceSource.live(target, deviceView) : null;
|
|
199
187
|
const devices = liveView?.devicesWithLive ?? deviceView?.devices ?? [];
|
|
200
188
|
const additionalReachableDevices = liveView?.additionalReachableDevices ?? [];
|
|
201
189
|
const discovery = deviceView?.deviceDiscoveryErrors.length ? { deviceDiscoveryErrors: deviceView.deviceDiscoveryErrors } : {};
|
|
@@ -216,8 +204,8 @@ async function handleDoctor({ options }) {
|
|
|
216
204
|
}
|
|
217
205
|
const skills = skillsView(target);
|
|
218
206
|
const update = updateStatus();
|
|
219
|
-
const
|
|
220
|
-
const featureFlags =
|
|
207
|
+
const pinnedFlags = readiness.pinnedFlags?.(target) ?? null;
|
|
208
|
+
const featureFlags = pinnedFlags ? featureFlagReport(pinnedFlags.overrides, pinnedFlags) : featureFlagReport(liveView?.featureFlagOverrides ?? null);
|
|
221
209
|
const doctorUserAction = doctorResult.checks.find(
|
|
222
210
|
(check) => check.required && check.status === "fail" && check.userAction
|
|
223
211
|
)?.userAction ?? `mm-harness doctor --fix --adapter ${adapter} --target ${shellQuote(target)} --json`;
|
|
@@ -249,12 +237,7 @@ async function handleDoctor({ options }) {
|
|
|
249
237
|
featureFlags.error || (featureFlags.overrideCount ?? 0) > 0 ? "warn" : "dim",
|
|
250
238
|
featureFlags.summary.replace(/^feature flags: /u, "")
|
|
251
239
|
)}`);
|
|
252
|
-
|
|
253
|
-
const perps = mobilePerpsEnvironment(target);
|
|
254
|
-
console.log(
|
|
255
|
-
`mobile-perps: ${perps.status} (${perps.file}; MM_PERPS_ENABLED=${perps.perpsEnabled.value ?? "missing"}; OVERRIDE_REMOTE_FEATURE_FLAGS=${perps.remoteFeatureFlagOverride.value ?? "missing"})`
|
|
256
|
-
);
|
|
257
|
-
}
|
|
240
|
+
for (const line of readiness.lines?.(target) ?? []) console.log(line);
|
|
258
241
|
if (orphanMetros.length > 0) {
|
|
259
242
|
console.log(
|
|
260
243
|
`${out("warn", "orphan Metro:")} ${orphanMetros.length} leaked bundler(s) for this checkout (pids ${orphanMetros.join(", ")})`
|
|
@@ -264,33 +247,15 @@ async function handleDoctor({ options }) {
|
|
|
264
247
|
if (capture) renderCaptureHealth(capture, out);
|
|
265
248
|
renderSkillsView(skills, out);
|
|
266
249
|
renderUpdateStatus(update, out);
|
|
267
|
-
if (
|
|
268
|
-
if (deviceView)
|
|
269
|
-
if (additionalReachableDevices.length > 0)
|
|
270
|
-
if (liveView)
|
|
250
|
+
if (deviceSource) {
|
|
251
|
+
if (deviceView) deviceSource.renderList(deviceView, out);
|
|
252
|
+
if (additionalReachableDevices.length > 0) deviceSource.renderAdditional(additionalReachableDevices, out);
|
|
253
|
+
if (liveView) deviceSource.renderLive(deviceView?.devices ?? [], liveView.liveMap, out);
|
|
271
254
|
}
|
|
272
255
|
if (next) console.log(` ${out("dim", `Next: ${next}`)}`);
|
|
273
256
|
}
|
|
274
257
|
return doctorResult.status === "pass" ? 0 : 1;
|
|
275
258
|
}
|
|
276
|
-
async function terminalDoctorChecks(target) {
|
|
277
|
-
const { terminalReadiness } = await import("../../adapters/terminal/lib/readiness.mjs");
|
|
278
|
-
const { resolveBrowser } = await import("../../adapters/terminal/launch.mjs");
|
|
279
|
-
const report = await terminalReadiness({
|
|
280
|
-
target,
|
|
281
|
-
appPort: terminalAppPort(),
|
|
282
|
-
cdpPort: terminalCdpPort(),
|
|
283
|
-
signer: process.env.TERMINAL_SIGNER || "extension",
|
|
284
|
-
account: process.env.TERMINAL_ACCOUNT || "dev1",
|
|
285
|
-
probeBrowser: resolveBrowser
|
|
286
|
-
});
|
|
287
|
-
return report.checks.map((check) => ({
|
|
288
|
-
id: `terminal-${check.id}`,
|
|
289
|
-
status: check.status === "pass" ? "pass" : "fail",
|
|
290
|
-
required: check.required,
|
|
291
|
-
message: check.detail
|
|
292
|
-
}));
|
|
293
|
-
}
|
|
294
259
|
function doctorRuntimeCheck(runtime, error, expectLive) {
|
|
295
260
|
if (error) {
|
|
296
261
|
return {
|
|
@@ -310,7 +275,7 @@ function doctorRuntimeCheck(runtime, error, expectLive) {
|
|
|
310
275
|
detail: runtime?.reasons.join(" | ") || "Runtime readiness probe returned no result."
|
|
311
276
|
};
|
|
312
277
|
}
|
|
313
|
-
function doctorFixNextActions(failed, fixture, adapter, target) {
|
|
278
|
+
function doctorFixNextActions(failed, fixture, adapter, target, block) {
|
|
314
279
|
const actions = [];
|
|
315
280
|
const executable = process.env.MM_HARNESS_EXECUTABLE ? shellQuote(process.env.MM_HARNESS_EXECUTABLE) : "mm-harness";
|
|
316
281
|
const scope = `--adapter ${adapter} --target ${shellQuote(target)}`;
|
|
@@ -325,10 +290,7 @@ function doctorFixNextActions(failed, fixture, adapter, target) {
|
|
|
325
290
|
);
|
|
326
291
|
}
|
|
327
292
|
if (failed.includes("overlay")) actions.push(`${executable} install ${scope}`);
|
|
328
|
-
if (
|
|
329
|
-
const block = extensionProductConfigBlock(target);
|
|
330
|
-
if (block) actions.push(block.userAction);
|
|
331
|
-
}
|
|
293
|
+
if (block?.userAction && failed.includes(block.id)) actions.push(block.userAction);
|
|
332
294
|
return actions;
|
|
333
295
|
}
|
|
334
296
|
function emitExpectLiveVerdict(adapter, target, runtime, verdict) {
|
|
@@ -367,9 +329,9 @@ function emitExpectLive(adapter, target, runtime, result, orphanMetros, capture,
|
|
|
367
329
|
emitExpectLiveVerdict(adapter, target, runtime, "not-live");
|
|
368
330
|
return EXIT.runtime;
|
|
369
331
|
}
|
|
370
|
-
function captureHealth(
|
|
371
|
-
if (
|
|
372
|
-
const fallback =
|
|
332
|
+
function captureHealth(providers) {
|
|
333
|
+
if (!providers) return null;
|
|
334
|
+
const fallback = [...providers];
|
|
373
335
|
const helper = captureHelperHealth();
|
|
374
336
|
if (!helper) {
|
|
375
337
|
return {
|
|
@@ -445,18 +407,6 @@ function renderUpdateStatus(status, out) {
|
|
|
445
407
|
status.updateAvailable ? `${out("label", "update:")} ${out("warn", `${status.current} \u2192 ${status.latest} available`)} ${out("dim", "(mm-harness update)")}` : status.latest && status.latest !== status.current ? `${out("label", "update:")} ${out("ok", status.current)} ${out("dim", `ahead of registry ${status.latest}`)}` : `${out("label", "update:")} ${out("ok", `${status.current} is latest`)}`
|
|
446
408
|
);
|
|
447
409
|
}
|
|
448
|
-
function detectOrphanMetros(target, livePort) {
|
|
449
|
-
try {
|
|
450
|
-
const script = path.join(runnerDir, "adapters/shared/reap-checkout-metros.sh");
|
|
451
|
-
const out = execFileSync("bash", ["-c", `. "$1"; detect_checkout_metros "$2" "$3"`, "mm-harness-doctor", script, target, livePort ?? ""], {
|
|
452
|
-
encoding: "utf8",
|
|
453
|
-
timeout: 5e3
|
|
454
|
-
});
|
|
455
|
-
return out.split("\n").map((line) => line.trim()).filter((line) => /^\d+$/u.test(line));
|
|
456
|
-
} catch {
|
|
457
|
-
return [];
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
410
|
async function runDoctorFix(adapter, target, manifestValidation, json) {
|
|
461
411
|
const fixed = [];
|
|
462
412
|
const failed = [];
|
|
@@ -473,18 +423,11 @@ async function runDoctorFix(adapter, target, manifestValidation, json) {
|
|
|
473
423
|
} catch {
|
|
474
424
|
failed.push("runtime-context");
|
|
475
425
|
}
|
|
476
|
-
|
|
477
|
-
try {
|
|
478
|
-
if (ensureExtensionPerpsBuildFlag(target)) fixed.push("extension-perps-build");
|
|
479
|
-
} catch {
|
|
480
|
-
failed.push("extension-perps-build");
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
if (adapter === "mobile") {
|
|
426
|
+
for (const fix of getAdapterSurface(adapter).readiness.fixes ?? []) {
|
|
484
427
|
try {
|
|
485
|
-
if (
|
|
428
|
+
if (fix.apply(target)) fixed.push(fix.id);
|
|
486
429
|
} catch {
|
|
487
|
-
failed.push(
|
|
430
|
+
failed.push(fix.id);
|
|
488
431
|
}
|
|
489
432
|
}
|
|
490
433
|
const state = newHealState();
|
|
@@ -508,62 +451,8 @@ async function runDoctorFix(adapter, target, manifestValidation, json) {
|
|
|
508
451
|
}
|
|
509
452
|
return { fixed, failed };
|
|
510
453
|
}
|
|
511
|
-
function ensureExtensionPerpsBuildFlag(target) {
|
|
512
|
-
const configPath = path.join(target, ".metamaskrc");
|
|
513
|
-
const templatePath = path.join(target, ".metamaskrc.dist");
|
|
514
|
-
const config = readTextSnapshot(configPath);
|
|
515
|
-
const template = config.kind === "file" ? null : readTextSnapshot(templatePath);
|
|
516
|
-
const source = config.kind === "file" ? config : template?.kind === "file" ? template : config.kind === "symlink" ? { kind: "file", text: "", mode: 438 } : null;
|
|
517
|
-
if (!source) return false;
|
|
518
|
-
const before = source.text;
|
|
519
|
-
if (/^\s*PERPS_ENABLED\s*=\s*(['"]?)true\1\s*$/mu.test(before)) return false;
|
|
520
|
-
const line = "PERPS_ENABLED='true'";
|
|
521
|
-
const after = /^\s*PERPS_ENABLED\s*=.*$/mu.test(before) ? before.replace(/^\s*PERPS_ENABLED\s*=.*$/mu, line) : `${before.replace(/\s*$/u, "")}
|
|
522
|
-
${line}
|
|
523
|
-
`;
|
|
524
|
-
const temporary = `${configPath}.${process.pid}.${randomUUID()}.tmp`;
|
|
525
|
-
let descriptor = null;
|
|
526
|
-
try {
|
|
527
|
-
const mode = config.kind === "file" ? config.mode : 438;
|
|
528
|
-
descriptor = fs.openSync(
|
|
529
|
-
temporary,
|
|
530
|
-
fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL,
|
|
531
|
-
mode
|
|
532
|
-
);
|
|
533
|
-
fs.writeFileSync(descriptor, after);
|
|
534
|
-
if (config.kind === "file") fs.fchmodSync(descriptor, config.mode);
|
|
535
|
-
fs.closeSync(descriptor);
|
|
536
|
-
descriptor = null;
|
|
537
|
-
fs.renameSync(temporary, configPath);
|
|
538
|
-
} finally {
|
|
539
|
-
if (descriptor !== null) fs.closeSync(descriptor);
|
|
540
|
-
fs.rmSync(temporary, { force: true });
|
|
541
|
-
}
|
|
542
|
-
return true;
|
|
543
|
-
}
|
|
544
|
-
function readTextSnapshot(file) {
|
|
545
|
-
let descriptor;
|
|
546
|
-
try {
|
|
547
|
-
descriptor = fs.openSync(file, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
|
|
548
|
-
} catch (error) {
|
|
549
|
-
const code = error.code;
|
|
550
|
-
if (code === "ENOENT") return { kind: "missing" };
|
|
551
|
-
if (code === "ELOOP") return { kind: "symlink" };
|
|
552
|
-
throw error;
|
|
553
|
-
}
|
|
554
|
-
try {
|
|
555
|
-
return {
|
|
556
|
-
kind: "file",
|
|
557
|
-
text: fs.readFileSync(descriptor, "utf8"),
|
|
558
|
-
mode: fs.fstatSync(descriptor).mode & 511
|
|
559
|
-
};
|
|
560
|
-
} finally {
|
|
561
|
-
fs.closeSync(descriptor);
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
454
|
export {
|
|
565
455
|
doctorFixNextActions,
|
|
566
|
-
ensureExtensionPerpsBuildFlag,
|
|
567
456
|
handleDoctor,
|
|
568
457
|
skillsView
|
|
569
458
|
};
|
|
@@ -5,15 +5,11 @@ import { runnerDir, walletFixturePath } from "../paths.js";
|
|
|
5
5
|
import { getAdapterSurface } from "../adapters/surface.js";
|
|
6
6
|
import { fixtureFileSummary, fixtureSummary } from "../doctor.js";
|
|
7
7
|
import { acquireCheckoutLock } from "../checkout-lock.js";
|
|
8
|
-
import { colorHumanMessage } from "../cli-color.js";
|
|
9
8
|
import { applyDeviceTargeting } from "./device-target.js";
|
|
10
9
|
import { ADAPTER_DETECT_NEXT, checkoutBusyOut, EXIT, flag, parseFlags, resolveAdapter, scriptOverride, spawnScript, spawnScriptStreaming, str, targetOf, usageOut } from "./shared.js";
|
|
11
|
-
import { FeatureFlagSpecError, fixtureFeatureFlags, parseRemoteFlagSpec
|
|
12
|
-
import { readExtensionPinnedFlags } from "../adapters/extension/feature-flags.js";
|
|
10
|
+
import { FeatureFlagSpecError, fixtureFeatureFlags, parseRemoteFlagSpec } from "../feature-flags.js";
|
|
13
11
|
const FIXTURES_BOOLEANS = /* @__PURE__ */ new Set(["dev", "force", "json"]);
|
|
14
12
|
const FIXTURE_STREAM_TIMEOUT_MS = 12e4;
|
|
15
|
-
const FIXTURE_BRIDGE_READY_TIMEOUT_MS = 18e4;
|
|
16
|
-
const MOBILE_SETUP_WALLET_TIMEOUT_MS = 36e4;
|
|
17
13
|
async function handleFixtures(argv) {
|
|
18
14
|
const { positional, options } = parseFlags(argv, FIXTURES_BOOLEANS);
|
|
19
15
|
if (!positional[0]) return fixturesStatus(options);
|
|
@@ -52,7 +48,7 @@ function fixturesStatus(options) {
|
|
|
52
48
|
const fixture = { ...summary, path: canonical };
|
|
53
49
|
const ready = summary.status === "ready";
|
|
54
50
|
const nextCommand = ready ? "mm-harness fixtures set" : "mm-harness fixtures init --from <path> # or: mm-harness fixtures init --dev";
|
|
55
|
-
const operations =
|
|
51
|
+
const operations = [...getAdapterSurface(adapter).fixtures.operations];
|
|
56
52
|
if (json) {
|
|
57
53
|
console.log(JSON.stringify({
|
|
58
54
|
schemaVersion: 1,
|
|
@@ -191,217 +187,16 @@ async function handleFixturesLocked(argv) {
|
|
|
191
187
|
if (!(error instanceof FeatureFlagSpecError)) throw error;
|
|
192
188
|
return usageOut(json, "fixtures", error.message, error.userAction);
|
|
193
189
|
}
|
|
194
|
-
const
|
|
195
|
-
if (
|
|
196
|
-
return
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
let status;
|
|
202
|
-
let mobileSetupResult;
|
|
203
|
-
let resetError;
|
|
204
|
-
if (adapter === "mobile") {
|
|
205
|
-
const setupWalletSh = path.join(runnerDir, "adapters/mobile/bridge-runtime/setup-wallet.sh");
|
|
206
|
-
const previousAppRoot = process.env.APP_ROOT;
|
|
207
|
-
process.env.APP_ROOT = target;
|
|
208
|
-
try {
|
|
209
|
-
if (reset) {
|
|
210
|
-
const platform = process.env.PLATFORM === "android" || process.env.ADB_SERIAL || process.env.ANDROID_SERIAL ? "android" : "ios";
|
|
211
|
-
const port = process.env.WATCHER_PORT ?? process.env.METRO_PORT ?? "8081";
|
|
212
|
-
const resetAppData = path.join(runnerDir, "adapters/mobile/reset-app-data.sh");
|
|
213
|
-
const resetArgs = ["--platform", platform, "--target", target];
|
|
214
|
-
if (platform === "ios") {
|
|
215
|
-
resetArgs.push("--simulator", process.env.SIM_UDID || process.env.IOS_SIMULATOR || "booted");
|
|
216
|
-
} else if (process.env.ADB_SERIAL || process.env.ANDROID_SERIAL) {
|
|
217
|
-
resetArgs.push("--adb-serial", process.env.ADB_SERIAL || process.env.ANDROID_SERIAL || "");
|
|
218
|
-
}
|
|
219
|
-
const resetResult = await spawnScriptStreaming(resetAppData, resetArgs, target, {
|
|
220
|
-
timeoutMs: FIXTURE_STREAM_TIMEOUT_MS
|
|
221
|
-
});
|
|
222
|
-
if (resetResult.status !== 0) {
|
|
223
|
-
resetError = new Error("Mobile app data reset failed.");
|
|
224
|
-
} else {
|
|
225
|
-
const prewarmBundle = path.join(runnerDir, "adapters/mobile/prewarm-bundle.sh");
|
|
226
|
-
const prewarmResult = await spawnScriptStreaming(
|
|
227
|
-
prewarmBundle,
|
|
228
|
-
["--platform", platform, "--target", target, "--port", port],
|
|
229
|
-
target,
|
|
230
|
-
{
|
|
231
|
-
timeoutMs: FIXTURE_STREAM_TIMEOUT_MS
|
|
232
|
-
}
|
|
233
|
-
);
|
|
234
|
-
if (prewarmResult.status !== 0) {
|
|
235
|
-
resetError = new Error("Mobile bundle prewarm failed after app data reset.");
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
if (!resetError) {
|
|
239
|
-
const openDevice = path.join(runnerDir, "adapters/mobile/open-device.sh");
|
|
240
|
-
const openArgs = ["--platform", platform, "--target", target, "--port", port, "--preflight-mode", "fast", "--restart"];
|
|
241
|
-
if (platform === "ios") {
|
|
242
|
-
openArgs.push("--simulator", process.env.SIM_UDID || process.env.IOS_SIMULATOR || "booted");
|
|
243
|
-
} else if (process.env.ADB_SERIAL || process.env.ANDROID_SERIAL) {
|
|
244
|
-
openArgs.push("--adb-serial", process.env.ADB_SERIAL || process.env.ANDROID_SERIAL || "");
|
|
245
|
-
}
|
|
246
|
-
const openResult = await spawnScriptStreaming(openDevice, openArgs, target, {
|
|
247
|
-
timeoutMs: FIXTURE_STREAM_TIMEOUT_MS
|
|
248
|
-
});
|
|
249
|
-
if (openResult.status !== 0) {
|
|
250
|
-
resetError = new Error("Mobile app relaunch after data reset failed.");
|
|
251
|
-
} else {
|
|
252
|
-
const waitForBridge = path.join(runnerDir, "adapters/mobile/wait-for-bridge.sh");
|
|
253
|
-
const waitResult = await spawnScriptStreaming(
|
|
254
|
-
waitForBridge,
|
|
255
|
-
["--target", target, "--port", port, "--platform", platform, "--timeout-ms", String(FIXTURE_BRIDGE_READY_TIMEOUT_MS)],
|
|
256
|
-
target,
|
|
257
|
-
{ timeoutMs: FIXTURE_BRIDGE_READY_TIMEOUT_MS + 1e4 }
|
|
258
|
-
);
|
|
259
|
-
if (waitResult.status !== 0) {
|
|
260
|
-
resetError = new Error("Mobile bridge did not return after app data reset.");
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
const configuredTimeout = Number(process.env.MM_HARNESS_FIXTURES_SET_TIMEOUT_MS ?? MOBILE_SETUP_WALLET_TIMEOUT_MS);
|
|
266
|
-
const timeoutMs = Number.isFinite(configuredTimeout) && configuredTimeout > 0 ? configuredTimeout : MOBILE_SETUP_WALLET_TIMEOUT_MS;
|
|
267
|
-
if (!resetError) {
|
|
268
|
-
mobileSetupResult = await spawnScriptStreaming(
|
|
269
|
-
setupWalletSh,
|
|
270
|
-
["--fixture", fixturePath, "--remote-flags-json", fixtureFlagsJson],
|
|
271
|
-
target,
|
|
272
|
-
{
|
|
273
|
-
env: {
|
|
274
|
-
APP_ROOT: target,
|
|
275
|
-
WATCHER_PORT: process.env.WATCHER_PORT ?? "8081",
|
|
276
|
-
METRO_PORT: process.env.METRO_PORT ?? process.env.WATCHER_PORT ?? "8081"
|
|
277
|
-
},
|
|
278
|
-
timeoutMs
|
|
279
|
-
}
|
|
280
|
-
);
|
|
281
|
-
}
|
|
282
|
-
status = !resetError && mobileSetupResult?.status === 0 ? "pass" : "fail";
|
|
283
|
-
} finally {
|
|
284
|
-
if (previousAppRoot === void 0) delete process.env.APP_ROOT;
|
|
285
|
-
else process.env.APP_ROOT = previousAppRoot;
|
|
286
|
-
}
|
|
287
|
-
if (!json && status === "fail") {
|
|
288
|
-
console.error(` Next: ${retryHint}`);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
const teaching = status === "pass" ? `Wallet fixture applied. Want different accounts? Edit: ${canonicalFixture}` : "Wallet fixture was not applied.";
|
|
292
|
-
if (json) {
|
|
293
|
-
console.log(
|
|
294
|
-
JSON.stringify(
|
|
295
|
-
{
|
|
296
|
-
schemaVersion: 1,
|
|
297
|
-
command: "fixtures",
|
|
298
|
-
action: sub,
|
|
299
|
-
adapter,
|
|
300
|
-
fixture: fixturePath,
|
|
301
|
-
canonicalFixture,
|
|
302
|
-
status,
|
|
303
|
-
exitCode: status === "pass" ? EXIT.ok : EXIT.runtime,
|
|
304
|
-
walletReset: reset && status === "pass",
|
|
305
|
-
message: teaching,
|
|
306
|
-
error: status === "fail" ? {
|
|
307
|
-
code: resetError ? "MOBILE_WALLET_RESET_FAILED" : mobileSetupResult?.timedOut ? "SETUP_WALLET_TIMEOUT" : "SETUP_WALLET_FAILED",
|
|
308
|
-
message: resetError ? `wallet reset failed: ${resetError instanceof Error ? resetError.message : String(resetError)}` : mobileSetupResult?.timedOut ? `wallet fixture setup exceeded its ${String(mobileSetupResult.timeoutMs)}ms bound on Metro port ${process.env.WATCHER_PORT ?? "8081"}` : `wallet fixture setup failed on ${process.env.WATCHER_PORT ?? "8081"}`,
|
|
309
|
-
userAction: retryHint
|
|
310
|
-
} : null
|
|
311
|
-
},
|
|
312
|
-
null,
|
|
313
|
-
2
|
|
314
|
-
)
|
|
190
|
+
const apply = surface.fixtures.apply;
|
|
191
|
+
if (!apply) {
|
|
192
|
+
return usageOut(
|
|
193
|
+
json,
|
|
194
|
+
"fixtures",
|
|
195
|
+
`fixtures ${sub} has no wallet to apply on ${adapter}.`,
|
|
196
|
+
surface.hints.launch
|
|
315
197
|
);
|
|
316
|
-
} else if (status === "pass") {
|
|
317
|
-
console.error(teaching);
|
|
318
|
-
}
|
|
319
|
-
return status === "pass" ? EXIT.ok : EXIT.runtime;
|
|
320
|
-
}
|
|
321
|
-
async function applyExtensionFixture(target, fixturePath, canonicalFixture, json, action, fixtureFlags) {
|
|
322
|
-
const distManifest = path.join(target, "dist/chrome/manifest.json");
|
|
323
|
-
const userAction = `mm-harness launch --build --verify --target ${JSON.stringify(target)}`;
|
|
324
|
-
if (!fs.existsSync(distManifest)) {
|
|
325
|
-
const message2 = `Extension fixture needs an existing dist/chrome build; no manifest was found at ${distManifest}.`;
|
|
326
|
-
if (json) {
|
|
327
|
-
console.log(JSON.stringify({
|
|
328
|
-
schemaVersion: 1,
|
|
329
|
-
command: "fixtures",
|
|
330
|
-
action,
|
|
331
|
-
adapter: "extension",
|
|
332
|
-
fixture: fixturePath,
|
|
333
|
-
canonicalFixture,
|
|
334
|
-
status: "fail",
|
|
335
|
-
exitCode: EXIT.runtime,
|
|
336
|
-
error: { code: "EXTENSION_FIXTURE_BUILD_REQUIRED", message: message2, userAction }
|
|
337
|
-
}, null, 2));
|
|
338
|
-
} else {
|
|
339
|
-
console.error(`\u2717 mm-harness fixtures ${action}: ${message2}
|
|
340
|
-
Next: ${userAction}`);
|
|
341
|
-
}
|
|
342
|
-
return EXIT.runtime;
|
|
343
198
|
}
|
|
344
|
-
|
|
345
|
-
`);
|
|
346
|
-
const liveScript = path.join(runnerDir, "adapters/extension/live.sh");
|
|
347
|
-
const args = ["--target", target, "--launch-existing-dist", "--launch-only", "--reset-profile"];
|
|
348
|
-
const flagKeys = Object.keys(fixtureFlags);
|
|
349
|
-
if (flagKeys.length > 0) args.push("--remote-flags-json", JSON.stringify(fixtureFlags));
|
|
350
|
-
if (process.env.CDP_PORT) args.push("--cdp-port", process.env.CDP_PORT);
|
|
351
|
-
const result = await spawnScriptStreaming(liveScript, args, target, {
|
|
352
|
-
env: { RECIPE_WALLET_FIXTURE: fixturePath },
|
|
353
|
-
timeoutMs: FIXTURE_STREAM_TIMEOUT_MS
|
|
354
|
-
});
|
|
355
|
-
const pinned = readExtensionPinnedFlags(target);
|
|
356
|
-
const pinnedFlags = pinned.pinned ?? {};
|
|
357
|
-
const flagMismatch = result.status === 0 && !pinned.error && (pinned.pinned === null || !sameFeatureFlags(pinnedFlags, fixtureFlags)) ? [.../* @__PURE__ */ new Set([...flagKeys, ...Object.keys(pinnedFlags)])].filter(
|
|
358
|
-
(key) => JSON.stringify(pinnedFlags[key]) !== JSON.stringify(fixtureFlags[key])
|
|
359
|
-
) : [];
|
|
360
|
-
if (result.status === 0 && (pinned.error || flagMismatch.length > 0)) {
|
|
361
|
-
const detail = pinned.error ? `the launched snapshot manifest could not be read (${pinned.sourcePath ?? "unknown path"}): ${pinned.error}` : `snapshot pin disagrees with the fixture for: ${flagMismatch.join(", ")}`;
|
|
362
|
-
const flagNext = `mm-harness launch --adapter extension --build --target ${JSON.stringify(target)}`;
|
|
363
|
-
if (json) {
|
|
364
|
-
console.log(JSON.stringify({
|
|
365
|
-
schemaVersion: 1,
|
|
366
|
-
command: "fixtures",
|
|
367
|
-
action,
|
|
368
|
-
adapter: "extension",
|
|
369
|
-
fixture: fixturePath,
|
|
370
|
-
canonicalFixture,
|
|
371
|
-
status: "fail",
|
|
372
|
-
exitCode: EXIT.runtime,
|
|
373
|
-
error: { code: "EXTENSION_FIXTURE_FLAGS_NOT_APPLIED", message: detail, userAction: flagNext }
|
|
374
|
-
}, null, 2));
|
|
375
|
-
} else {
|
|
376
|
-
console.error(`\u2717 mm-harness fixtures ${action}: ${detail}
|
|
377
|
-
Next: ${flagNext}`);
|
|
378
|
-
}
|
|
379
|
-
return EXIT.runtime;
|
|
380
|
-
}
|
|
381
|
-
const status = result.status === 0 ? "pass" : "fail";
|
|
382
|
-
const exitCode = status === "pass" ? EXIT.ok : EXIT.runtime;
|
|
383
|
-
const message = status === "pass" ? `Wallet fixture applied and validated. Want different accounts? Edit: ${canonicalFixture}` : "Extension wallet fixture could not be applied and validated.";
|
|
384
|
-
if (json) {
|
|
385
|
-
console.log(JSON.stringify({
|
|
386
|
-
schemaVersion: 1,
|
|
387
|
-
command: "fixtures",
|
|
388
|
-
action,
|
|
389
|
-
adapter: "extension",
|
|
390
|
-
fixture: fixturePath,
|
|
391
|
-
canonicalFixture,
|
|
392
|
-
status,
|
|
393
|
-
exitCode,
|
|
394
|
-
profileReset: status === "pass",
|
|
395
|
-
message,
|
|
396
|
-
error: status === "fail" ? { code: "EXTENSION_FIXTURE_APPLY_FAILED", message, userAction } : null
|
|
397
|
-
}, null, 2));
|
|
398
|
-
} else if (status === "pass") {
|
|
399
|
-
console.error(message);
|
|
400
|
-
} else {
|
|
401
|
-
console.error(`\u2717 mm-harness fixtures ${action}: ${message}
|
|
402
|
-
Next: ${userAction}`);
|
|
403
|
-
}
|
|
404
|
-
return exitCode;
|
|
199
|
+
return apply({ target, sub, fixturePath, canonicalFixture, fixtureFlags, json, retryHint });
|
|
405
200
|
}
|
|
406
201
|
const EXT_FIXTURE_LEAF = "adapters/extension/wallet-fixture-state.cjs";
|
|
407
202
|
function emitLeafMissing(json, action, fields, leaf) {
|
|
@@ -422,7 +217,7 @@ function emitLeafMissing(json, action, fields, leaf) {
|
|
|
422
217
|
return EXIT.runtime;
|
|
423
218
|
}
|
|
424
219
|
function fixturesGenerate(adapter, target, options, json) {
|
|
425
|
-
if (adapter
|
|
220
|
+
if (!getAdapterSurface(adapter).fixtures.operations.includes("generate")) {
|
|
426
221
|
return usageOut(
|
|
427
222
|
json,
|
|
428
223
|
"fixtures",
|
|
@@ -486,7 +281,7 @@ function fixturesGenerate(adapter, target, options, json) {
|
|
|
486
281
|
return exitCode;
|
|
487
282
|
}
|
|
488
283
|
async function fixturesFinalize(adapter, target, options, json) {
|
|
489
|
-
if (adapter
|
|
284
|
+
if (!getAdapterSurface(adapter).fixtures.operations.includes("finalize")) {
|
|
490
285
|
return usageOut(
|
|
491
286
|
json,
|
|
492
287
|
"fixtures",
|
|
@@ -678,5 +473,6 @@ Wallet fixture: ${canonical}`);
|
|
|
678
473
|
return EXIT.ok;
|
|
679
474
|
}
|
|
680
475
|
export {
|
|
476
|
+
FIXTURE_STREAM_TIMEOUT_MS,
|
|
681
477
|
handleFixtures
|
|
682
478
|
};
|
package/dist/commands/logs.js
CHANGED
|
@@ -68,7 +68,7 @@ async function handleLogs(argv) {
|
|
|
68
68
|
if (flag(options, "window")) {
|
|
69
69
|
surface.resolveSlotPorts(target);
|
|
70
70
|
const port = process.env.WATCHER_PORT ?? process.env.METRO_PORT ?? "default";
|
|
71
|
-
const windowName =
|
|
71
|
+
const windowName = `${surface.devServer.label}-${port}`;
|
|
72
72
|
const devLog = logSources[0].path;
|
|
73
73
|
const leaf = path.join(runnerDir, "adapters/shared/open-log-window.sh");
|
|
74
74
|
const result = spawnScript(
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { detectAdapter } from "../harness.js";
|
|
3
|
-
import {
|
|
3
|
+
import { adapterRegistry } from "../adapters/registry.js";
|
|
4
|
+
import { assertAdapter } from "../paths.js";
|
|
4
5
|
import { explicitRecipeTrustOptions } from "../recipe-security.js";
|
|
5
6
|
import { ADAPTER_DETECT_NEXT } from "./shared.js";
|
|
6
7
|
import { EXIT } from "./shared.js";
|
|
@@ -179,12 +180,16 @@ function requiredOption(options, key, message) {
|
|
|
179
180
|
if (!value) throw usageError(message);
|
|
180
181
|
return value;
|
|
181
182
|
}
|
|
183
|
+
function adapterUsageError(error) {
|
|
184
|
+
if (error instanceof Error && error.message.startsWith("internal:")) return error;
|
|
185
|
+
return usageError(error instanceof Error ? error.message : String(error));
|
|
186
|
+
}
|
|
182
187
|
function adapterOption(options) {
|
|
183
188
|
const adapter = optionString(options, "adapter");
|
|
184
189
|
try {
|
|
185
190
|
assertAdapter(adapter);
|
|
186
191
|
} catch (error) {
|
|
187
|
-
throw
|
|
192
|
+
throw adapterUsageError(error);
|
|
188
193
|
}
|
|
189
194
|
return adapter;
|
|
190
195
|
}
|
|
@@ -193,7 +198,7 @@ function targetPath(options) {
|
|
|
193
198
|
}
|
|
194
199
|
function actionManifestPathOption(options, adapter) {
|
|
195
200
|
const configured = optionString(options, "actionManifest");
|
|
196
|
-
return configured ? path.resolve(configured) :
|
|
201
|
+
return configured ? path.resolve(configured) : adapterRegistry.get(adapter).actions.manifestPath();
|
|
197
202
|
}
|
|
198
203
|
function parsePort(value, errorMessage) {
|
|
199
204
|
const port = Number(value);
|
|
@@ -238,7 +243,7 @@ function resolveAdapter(options) {
|
|
|
238
243
|
try {
|
|
239
244
|
assertAdapter(adapter);
|
|
240
245
|
} catch (error) {
|
|
241
|
-
throw
|
|
246
|
+
throw adapterUsageError(error);
|
|
242
247
|
}
|
|
243
248
|
return { adapter, target };
|
|
244
249
|
}
|