@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
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { EXIT, spawnScriptStreaming } from "../../commands/shared.js";
|
|
3
|
+
import { runnerDir } from "../../paths.js";
|
|
4
|
+
import { FIXTURE_STREAM_TIMEOUT_MS } from "../../commands/fixtures.js";
|
|
5
|
+
const FIXTURE_BRIDGE_READY_TIMEOUT_MS = 18e4;
|
|
6
|
+
const MOBILE_SETUP_WALLET_TIMEOUT_MS = 36e4;
|
|
7
|
+
async function applyMobileFixture({
|
|
8
|
+
target,
|
|
9
|
+
sub,
|
|
10
|
+
fixturePath,
|
|
11
|
+
canonicalFixture,
|
|
12
|
+
fixtureFlags,
|
|
13
|
+
json,
|
|
14
|
+
retryHint
|
|
15
|
+
}) {
|
|
16
|
+
const adapter = "mobile";
|
|
17
|
+
const fixtureFlagsJson = JSON.stringify(fixtureFlags);
|
|
18
|
+
const reset = sub === "reset";
|
|
19
|
+
process.stderr.write(`\u2192 fixtures ${sub} ${adapter} \u2014 ${reset ? "resetting selected app data, relaunching, then applying" : "connecting bridge + applying"} wallet fixture (can take ~30s)\u2026
|
|
20
|
+
`);
|
|
21
|
+
let status;
|
|
22
|
+
let mobileSetupResult;
|
|
23
|
+
let resetError;
|
|
24
|
+
const setupWalletSh = path.join(runnerDir, "adapters/mobile/bridge-runtime/setup-wallet.sh");
|
|
25
|
+
const previousAppRoot = process.env.APP_ROOT;
|
|
26
|
+
process.env.APP_ROOT = target;
|
|
27
|
+
try {
|
|
28
|
+
if (reset) {
|
|
29
|
+
const platform = process.env.PLATFORM === "android" || process.env.ADB_SERIAL || process.env.ANDROID_SERIAL ? "android" : "ios";
|
|
30
|
+
const port = process.env.WATCHER_PORT ?? process.env.METRO_PORT ?? "8081";
|
|
31
|
+
const resetAppData = path.join(runnerDir, "adapters/mobile/reset-app-data.sh");
|
|
32
|
+
const resetArgs = ["--platform", platform, "--target", target];
|
|
33
|
+
if (platform === "ios") {
|
|
34
|
+
resetArgs.push("--simulator", process.env.SIM_UDID || process.env.IOS_SIMULATOR || "booted");
|
|
35
|
+
} else if (process.env.ADB_SERIAL || process.env.ANDROID_SERIAL) {
|
|
36
|
+
resetArgs.push("--adb-serial", process.env.ADB_SERIAL || process.env.ANDROID_SERIAL || "");
|
|
37
|
+
}
|
|
38
|
+
const resetResult = await spawnScriptStreaming(resetAppData, resetArgs, target, {
|
|
39
|
+
timeoutMs: FIXTURE_STREAM_TIMEOUT_MS
|
|
40
|
+
});
|
|
41
|
+
if (resetResult.status !== 0) {
|
|
42
|
+
resetError = new Error("Mobile app data reset failed.");
|
|
43
|
+
} else {
|
|
44
|
+
const prewarmBundle = path.join(runnerDir, "adapters/mobile/prewarm-bundle.sh");
|
|
45
|
+
const prewarmResult = await spawnScriptStreaming(
|
|
46
|
+
prewarmBundle,
|
|
47
|
+
["--platform", platform, "--target", target, "--port", port],
|
|
48
|
+
target,
|
|
49
|
+
{
|
|
50
|
+
timeoutMs: FIXTURE_STREAM_TIMEOUT_MS
|
|
51
|
+
}
|
|
52
|
+
);
|
|
53
|
+
if (prewarmResult.status !== 0) {
|
|
54
|
+
resetError = new Error("Mobile bundle prewarm failed after app data reset.");
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (!resetError) {
|
|
58
|
+
const openDevice = path.join(runnerDir, "adapters/mobile/open-device.sh");
|
|
59
|
+
const openArgs = ["--platform", platform, "--target", target, "--port", port, "--preflight-mode", "fast", "--restart"];
|
|
60
|
+
if (platform === "ios") {
|
|
61
|
+
openArgs.push("--simulator", process.env.SIM_UDID || process.env.IOS_SIMULATOR || "booted");
|
|
62
|
+
} else if (process.env.ADB_SERIAL || process.env.ANDROID_SERIAL) {
|
|
63
|
+
openArgs.push("--adb-serial", process.env.ADB_SERIAL || process.env.ANDROID_SERIAL || "");
|
|
64
|
+
}
|
|
65
|
+
const openResult = await spawnScriptStreaming(openDevice, openArgs, target, {
|
|
66
|
+
timeoutMs: FIXTURE_STREAM_TIMEOUT_MS
|
|
67
|
+
});
|
|
68
|
+
if (openResult.status !== 0) {
|
|
69
|
+
resetError = new Error("Mobile app relaunch after data reset failed.");
|
|
70
|
+
} else {
|
|
71
|
+
const waitForBridge = path.join(runnerDir, "adapters/mobile/wait-for-bridge.sh");
|
|
72
|
+
const waitResult = await spawnScriptStreaming(
|
|
73
|
+
waitForBridge,
|
|
74
|
+
["--target", target, "--port", port, "--platform", platform, "--timeout-ms", String(FIXTURE_BRIDGE_READY_TIMEOUT_MS)],
|
|
75
|
+
target,
|
|
76
|
+
{ timeoutMs: FIXTURE_BRIDGE_READY_TIMEOUT_MS + 1e4 }
|
|
77
|
+
);
|
|
78
|
+
if (waitResult.status !== 0) {
|
|
79
|
+
resetError = new Error("Mobile bridge did not return after app data reset.");
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const configuredTimeout = Number(process.env.MM_HARNESS_FIXTURES_SET_TIMEOUT_MS ?? MOBILE_SETUP_WALLET_TIMEOUT_MS);
|
|
85
|
+
const timeoutMs = Number.isFinite(configuredTimeout) && configuredTimeout > 0 ? configuredTimeout : MOBILE_SETUP_WALLET_TIMEOUT_MS;
|
|
86
|
+
if (!resetError) {
|
|
87
|
+
mobileSetupResult = await spawnScriptStreaming(
|
|
88
|
+
setupWalletSh,
|
|
89
|
+
["--fixture", fixturePath, "--remote-flags-json", fixtureFlagsJson],
|
|
90
|
+
target,
|
|
91
|
+
{
|
|
92
|
+
env: {
|
|
93
|
+
APP_ROOT: target,
|
|
94
|
+
WATCHER_PORT: process.env.WATCHER_PORT ?? "8081",
|
|
95
|
+
METRO_PORT: process.env.METRO_PORT ?? process.env.WATCHER_PORT ?? "8081"
|
|
96
|
+
},
|
|
97
|
+
timeoutMs
|
|
98
|
+
}
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
status = !resetError && mobileSetupResult?.status === 0 ? "pass" : "fail";
|
|
102
|
+
} finally {
|
|
103
|
+
if (previousAppRoot === void 0) delete process.env.APP_ROOT;
|
|
104
|
+
else process.env.APP_ROOT = previousAppRoot;
|
|
105
|
+
}
|
|
106
|
+
if (!json && status === "fail") {
|
|
107
|
+
console.error(` Next: ${retryHint}`);
|
|
108
|
+
}
|
|
109
|
+
const teaching = status === "pass" ? `Wallet fixture applied. Want different accounts? Edit: ${canonicalFixture}` : "Wallet fixture was not applied.";
|
|
110
|
+
if (json) {
|
|
111
|
+
console.log(
|
|
112
|
+
JSON.stringify(
|
|
113
|
+
{
|
|
114
|
+
schemaVersion: 1,
|
|
115
|
+
command: "fixtures",
|
|
116
|
+
action: sub,
|
|
117
|
+
adapter,
|
|
118
|
+
fixture: fixturePath,
|
|
119
|
+
canonicalFixture,
|
|
120
|
+
status,
|
|
121
|
+
exitCode: status === "pass" ? EXIT.ok : EXIT.runtime,
|
|
122
|
+
walletReset: reset && status === "pass",
|
|
123
|
+
message: teaching,
|
|
124
|
+
error: status === "fail" ? {
|
|
125
|
+
code: resetError ? "MOBILE_WALLET_RESET_FAILED" : mobileSetupResult?.timedOut ? "SETUP_WALLET_TIMEOUT" : "SETUP_WALLET_FAILED",
|
|
126
|
+
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"}`,
|
|
127
|
+
userAction: retryHint
|
|
128
|
+
} : null
|
|
129
|
+
},
|
|
130
|
+
null,
|
|
131
|
+
2
|
|
132
|
+
)
|
|
133
|
+
);
|
|
134
|
+
} else if (status === "pass") {
|
|
135
|
+
console.error(teaching);
|
|
136
|
+
}
|
|
137
|
+
return status === "pass" ? EXIT.ok : EXIT.runtime;
|
|
138
|
+
}
|
|
139
|
+
export {
|
|
140
|
+
applyMobileFixture
|
|
141
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { mobilePlatformFromConfiguredTarget, mobilePlatformFromDevices, readRuntimeContextTarget } from "./prepare-target.js";
|
|
2
|
+
const mobileHarness = {
|
|
3
|
+
install: { entry: "adapters/mobile/inject.sh", fallback: "scripts/inject-mobile-harness.sh" },
|
|
4
|
+
cleanup: { entry: "adapters/mobile/cleanup.sh", fallback: "scripts/cleanup-mobile-harness.sh", fromInstalledRunner: true },
|
|
5
|
+
// Runner-owned typed code, not a shipped leaf: no installed overlay copy can shadow it.
|
|
6
|
+
verify: () => ({ inProcess: async () => (await import("./verify.js")).runMobileVerify }),
|
|
7
|
+
restart: "mm-harness launch ios # or launch android"
|
|
8
|
+
};
|
|
9
|
+
const mobileRuntimeContext = {
|
|
10
|
+
forbiddenFields: ["cdpPort", "extensionId"],
|
|
11
|
+
localPorts: { watcher: 4e4 },
|
|
12
|
+
resources: ({ watcherPort, existing }) => ({
|
|
13
|
+
metroPort: watcherPort,
|
|
14
|
+
simulator: process.env.IOS_SIMULATOR || existing.simulator,
|
|
15
|
+
simulatorUdid: process.env.SIM_UDID || existing.simulatorUdid,
|
|
16
|
+
adbSerial: process.env.ADB_SERIAL || process.env.ANDROID_SERIAL || existing.adbSerial
|
|
17
|
+
})
|
|
18
|
+
};
|
|
19
|
+
const mobilePrepare = {
|
|
20
|
+
clearMetro: true,
|
|
21
|
+
devicePlatform: (target, devices) => mobilePlatformFromConfiguredTarget(process.env, readRuntimeContextTarget(target)) ?? mobilePlatformFromDevices(devices)
|
|
22
|
+
};
|
|
23
|
+
export {
|
|
24
|
+
mobileHarness,
|
|
25
|
+
mobilePrepare,
|
|
26
|
+
mobileRuntimeContext
|
|
27
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { recipeRuntimeDir } from "../../paths.js";
|
|
4
|
+
function readRuntimeContextTarget(target) {
|
|
5
|
+
const file = path.join(target, recipeRuntimeDir(), "agentic-runtime.json");
|
|
6
|
+
let parsed;
|
|
7
|
+
try {
|
|
8
|
+
parsed = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
9
|
+
} catch {
|
|
10
|
+
return {};
|
|
11
|
+
}
|
|
12
|
+
const record = parsed ?? {};
|
|
13
|
+
const str = (key) => typeof record[key] === "string" ? record[key] : void 0;
|
|
14
|
+
return { simulator: str("simulator"), simulatorUdid: str("simulatorUdid"), adbSerial: str("adbSerial") };
|
|
15
|
+
}
|
|
16
|
+
function mobilePlatformFromConfiguredTarget(env, context) {
|
|
17
|
+
const ios = Boolean(env.IOS_SIMULATOR || env.SIM_UDID || context.simulator || context.simulatorUdid);
|
|
18
|
+
const android = Boolean(
|
|
19
|
+
env.ADB_SERIAL || env.ANDROID_SERIAL || env.ANDROID_TARGET_DEVICE_NAME || env.ANDROID_DEVICE || context.adbSerial
|
|
20
|
+
);
|
|
21
|
+
if (ios === android) return void 0;
|
|
22
|
+
return ios ? "ios" : "android";
|
|
23
|
+
}
|
|
24
|
+
function mobilePlatformFromDevices(devices) {
|
|
25
|
+
const selected = devices.filter((device) => device?.selected === true);
|
|
26
|
+
const candidates = selected.length > 0 ? selected : devices;
|
|
27
|
+
const platforms = [
|
|
28
|
+
...new Set(
|
|
29
|
+
candidates.map((device) => device?.platform).filter((value) => value === "ios" || value === "android")
|
|
30
|
+
)
|
|
31
|
+
];
|
|
32
|
+
return platforms.length === 1 ? platforms[0] : void 0;
|
|
33
|
+
}
|
|
34
|
+
export {
|
|
35
|
+
mobilePlatformFromConfiguredTarget,
|
|
36
|
+
mobilePlatformFromDevices,
|
|
37
|
+
readRuntimeContextTarget
|
|
38
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { optionFlag, optionString, parsePort } from "../../commands/parse-args.js";
|
|
2
|
+
import { detectOrphanMetros, mobileToolDoctorChecks } from "./doctor-checks.js";
|
|
3
|
+
import { ensureMobilePerpsEnvironment, mobilePerpsEnvironment } from "./perps-env.js";
|
|
4
|
+
const mobileReadiness = {
|
|
5
|
+
compatibilityMode(_target, shape) {
|
|
6
|
+
if (shape.injectedHarness && shape.agenticService) return "runner bridge with injected app bridge";
|
|
7
|
+
if (shape.agenticService) return "runner bridge with app bridge";
|
|
8
|
+
return "runner bridge available; app bridge not installed";
|
|
9
|
+
},
|
|
10
|
+
checks: (_target, platform) => mobileToolDoctorChecks(platform === "ios" || platform === "android" ? platform : void 0),
|
|
11
|
+
environment: (target) => ({ mobilePerps: mobilePerpsEnvironment(target) }),
|
|
12
|
+
lines(target) {
|
|
13
|
+
const perps = mobilePerpsEnvironment(target);
|
|
14
|
+
return [
|
|
15
|
+
`mobile-perps: ${perps.status} (${perps.file}; MM_PERPS_ENABLED=${perps.perpsEnabled.value ?? "missing"}; OVERRIDE_REMOTE_FEATURE_FLAGS=${perps.remoteFeatureFlagOverride.value ?? "missing"})`
|
|
16
|
+
];
|
|
17
|
+
},
|
|
18
|
+
orphanDevServers: detectOrphanMetros,
|
|
19
|
+
captureProviders: ["simctl", "adb"],
|
|
20
|
+
fixes: [{ id: "mobile-perps-env", apply: (target) => ensureMobilePerpsEnvironment(target).length > 0 }],
|
|
21
|
+
devices: async () => (await import("./devices.js")).mobileDevices
|
|
22
|
+
};
|
|
23
|
+
async function mobileRuntimeDecision(target, options) {
|
|
24
|
+
const watcherPortRaw = optionString(options, "watcherPort") ?? process.env.WATCHER_PORT ?? process.env.METRO_PORT;
|
|
25
|
+
const watcherPort = watcherPortRaw === void 0 ? void 0 : parsePort(watcherPortRaw, "runtime-decision --watcher-port must be a port.");
|
|
26
|
+
const { decideMobileReadiness } = await import("./runtime-decision.js");
|
|
27
|
+
const report = await decideMobileReadiness(target, {
|
|
28
|
+
watcherPort,
|
|
29
|
+
metroLog: optionString(options, "metroLog"),
|
|
30
|
+
platform: optionString(options, "platform") ?? process.env.PLATFORM ?? process.env.RECIPE_HARNESS_PLATFORM,
|
|
31
|
+
record: optionFlag(options, "record"),
|
|
32
|
+
preflightMode: optionString(options, "preflightMode")
|
|
33
|
+
});
|
|
34
|
+
return { report, line: `${report.decision} ${report.reasonCode} \u2014 ${report.reasons[0] ?? ""}` };
|
|
35
|
+
}
|
|
36
|
+
export {
|
|
37
|
+
mobileReadiness,
|
|
38
|
+
mobileRuntimeDecision
|
|
39
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { EXIT, spawnScript } from "../../commands/shared.js";
|
|
3
|
+
import { runnerDir } from "../../paths.js";
|
|
4
|
+
function reloadMobile(target, json) {
|
|
5
|
+
const script = path.join(runnerDir, "adapters/mobile/reload-app.mjs");
|
|
6
|
+
const args = [];
|
|
7
|
+
if (process.env.WATCHER_PORT) args.push("--port", process.env.WATCHER_PORT);
|
|
8
|
+
if (json) args.push("--json");
|
|
9
|
+
const result = spawnScript(process.execPath, [script, ...args], target, json);
|
|
10
|
+
if (json) process.stdout.write(result.output);
|
|
11
|
+
return result.status === 0 ? EXIT.ok : EXIT.runtime;
|
|
12
|
+
}
|
|
13
|
+
export {
|
|
14
|
+
reloadMobile
|
|
15
|
+
};
|
|
@@ -6,7 +6,11 @@ import { resolveMobileSlotPorts } from "../slot-ports.js";
|
|
|
6
6
|
import { mobileRuntimeStatus } from "./prepare.js";
|
|
7
7
|
import { hasRunwayProvisionBaseline, provisionRunwayMobile } from "./provision.js";
|
|
8
8
|
import { adapterRegistry } from "../registry.js";
|
|
9
|
+
import { writeInteractiveProgress } from "../../commands/shared.js";
|
|
9
10
|
import { mobileActions } from "./actions.js";
|
|
11
|
+
import { mobileHarness, mobilePrepare, mobileRuntimeContext } from "./overlay.js";
|
|
12
|
+
import { mobileReadiness, mobileRuntimeDecision } from "./readiness.js";
|
|
13
|
+
import { clearMobileReleaseArtifactState } from "./release-artifact-state.js";
|
|
10
14
|
import { mobileDiagnostics, mobileObservation, mobileRecording, mobileRun } from "./run.js";
|
|
11
15
|
import { mobileSourceFingerprint } from "./source-freshness.js";
|
|
12
16
|
function mobileRuntimeNextAction(reasonCode, target, platform) {
|
|
@@ -37,7 +41,12 @@ const mobileSurface = {
|
|
|
37
41
|
runwayProvision: {
|
|
38
42
|
async run(target, options) {
|
|
39
43
|
return provisionRunwayMobile(target, options);
|
|
40
|
-
}
|
|
44
|
+
},
|
|
45
|
+
before(target, { json, platform, resolveOnly }) {
|
|
46
|
+
writeInteractiveProgress(json, `\u2192 provision mobile ${platform} \u2014 resolving simulator and Runway artifact`);
|
|
47
|
+
if (!resolveOnly) clearMobileReleaseArtifactState(target);
|
|
48
|
+
},
|
|
49
|
+
launchNext: true
|
|
41
50
|
},
|
|
42
51
|
devServer: {
|
|
43
52
|
label: "metro",
|
|
@@ -72,6 +81,24 @@ const mobileSurface = {
|
|
|
72
81
|
return runDeviceLaunch(context, mobileLaunchProfile);
|
|
73
82
|
},
|
|
74
83
|
actions: mobileActions,
|
|
84
|
+
harness: mobileHarness,
|
|
85
|
+
runtimeContext: mobileRuntimeContext,
|
|
86
|
+
readiness: mobileReadiness,
|
|
87
|
+
fixtures: {
|
|
88
|
+
operations: ["init", "sync", "set", "reset"],
|
|
89
|
+
apply: async (context) => (await import("./fixtures.js")).applyMobileFixture(context)
|
|
90
|
+
},
|
|
91
|
+
prepare: mobilePrepare,
|
|
92
|
+
debug: {
|
|
93
|
+
refuse: ({ worker }) => worker ? { message: "--worker is extension-only (service-worker DevTools). Use --dev-menu on mobile.", userAction: "mm-harness debug --dev-menu" } : void 0,
|
|
94
|
+
args: ({ devMenu }) => [
|
|
95
|
+
...process.env.WATCHER_PORT ? ["--port", process.env.WATCHER_PORT] : [],
|
|
96
|
+
"--action",
|
|
97
|
+
devMenu ? "dev-menu" : "debug"
|
|
98
|
+
]
|
|
99
|
+
},
|
|
100
|
+
reload: async (target, json) => (await import("./reload.js")).reloadMobile(target, json),
|
|
101
|
+
runtimeDecision: mobileRuntimeDecision,
|
|
75
102
|
run: mobileRun,
|
|
76
103
|
recording: mobileRecording,
|
|
77
104
|
diagnostics: mobileDiagnostics,
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { terminalAppPort, terminalCdpPort } from "./surface.js";
|
|
2
|
+
async function terminalDoctorChecks(target) {
|
|
3
|
+
const { terminalReadiness } = await import("../../../adapters/terminal/lib/readiness.mjs");
|
|
4
|
+
const { resolveBrowser } = await import("../../../adapters/terminal/launch.mjs");
|
|
5
|
+
const report = await terminalReadiness({
|
|
6
|
+
target,
|
|
7
|
+
appPort: terminalAppPort(),
|
|
8
|
+
cdpPort: terminalCdpPort(),
|
|
9
|
+
signer: process.env.TERMINAL_SIGNER || "extension",
|
|
10
|
+
account: process.env.TERMINAL_ACCOUNT || "dev1",
|
|
11
|
+
probeBrowser: resolveBrowser
|
|
12
|
+
});
|
|
13
|
+
return report.checks.map((check) => ({
|
|
14
|
+
id: `terminal-${check.id}`,
|
|
15
|
+
status: check.status === "pass" ? "pass" : "fail",
|
|
16
|
+
required: check.required,
|
|
17
|
+
message: check.detail
|
|
18
|
+
}));
|
|
19
|
+
}
|
|
20
|
+
export {
|
|
21
|
+
terminalDoctorChecks
|
|
22
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { runnerDir } from "../../paths.js";
|
|
3
|
+
const terminalHarness = {
|
|
4
|
+
install: { entry: "adapters/terminal/inject.mjs", fallback: "adapters/terminal/inject.mjs", node: true },
|
|
5
|
+
cleanup: { entry: "adapters/terminal/cleanup.mjs", fallback: "adapters/terminal/cleanup.mjs", node: true },
|
|
6
|
+
verify: () => ({ command: process.execPath, prefixArgs: [path.join(runnerDir, "adapters", "terminal", "verify.mjs")] }),
|
|
7
|
+
restart: "mm-harness launch"
|
|
8
|
+
};
|
|
9
|
+
const terminalRuntimeContext = {
|
|
10
|
+
forbiddenFields: ["metroPort", "simulator", "simulatorUdid", "adbSerial", "extensionId"],
|
|
11
|
+
resources: ({ existing, defaults, envPort }) => ({
|
|
12
|
+
cdpPort: envPort("RECIPE_CDP_PORT", "CDP_PORT") ?? existing.cdpPort ?? defaults.cdpPort
|
|
13
|
+
})
|
|
14
|
+
};
|
|
15
|
+
export {
|
|
16
|
+
terminalHarness,
|
|
17
|
+
terminalRuntimeContext
|
|
18
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
const terminalReadiness = {
|
|
4
|
+
compatibilityMode: (target) => fs.existsSync(path.join(target, "src/features/perpetuals")) ? "slot browser over CDP (website; no product bridge)" : "unsupported/no bridge",
|
|
5
|
+
liveChecks: async (target) => (await import("./doctor-checks.js")).terminalDoctorChecks(target),
|
|
6
|
+
// Screenshots come from the slot browser over CDP.
|
|
7
|
+
captureProviders: ["cdp"]
|
|
8
|
+
};
|
|
9
|
+
export {
|
|
10
|
+
terminalReadiness
|
|
11
|
+
};
|
|
@@ -5,6 +5,8 @@ import { TERMINAL_CONSOLE_FILES, terminalRuntimeFile } from "./console-capture.j
|
|
|
5
5
|
import { stopTerminalBrowserSync } from "./stop.js";
|
|
6
6
|
import { adapterRegistry } from "../registry.js";
|
|
7
7
|
import { terminalActions } from "./actions.js";
|
|
8
|
+
import { terminalHarness, terminalRuntimeContext } from "./overlay.js";
|
|
9
|
+
import { terminalReadiness } from "./readiness.js";
|
|
8
10
|
import { terminalDiagnostics, terminalObservation } from "./run.js";
|
|
9
11
|
function portFromEnv(...names) {
|
|
10
12
|
for (const name of names) {
|
|
@@ -33,8 +35,8 @@ const terminalSurface = {
|
|
|
33
35
|
resolveSlotPorts() {
|
|
34
36
|
},
|
|
35
37
|
async runtimeStatus(target) {
|
|
36
|
-
const { terminalReadiness } = await import("../../../adapters/terminal/lib/readiness.mjs");
|
|
37
|
-
const report = await
|
|
38
|
+
const { terminalReadiness: terminalReadiness2 } = await import("../../../adapters/terminal/lib/readiness.mjs");
|
|
39
|
+
const report = await terminalReadiness2({
|
|
38
40
|
target: path.resolve(target),
|
|
39
41
|
appPort: terminalAppPort(),
|
|
40
42
|
cdpPort: terminalCdpPort(),
|
|
@@ -101,6 +103,17 @@ const terminalSurface = {
|
|
|
101
103
|
},
|
|
102
104
|
launch: async (context) => (await import("./launch.js")).launchTerminal(context),
|
|
103
105
|
actions: terminalActions,
|
|
106
|
+
harness: terminalHarness,
|
|
107
|
+
runtimeContext: terminalRuntimeContext,
|
|
108
|
+
readiness: terminalReadiness,
|
|
109
|
+
fixtures: { operations: ["init", "sync", "set"] },
|
|
110
|
+
debug: {
|
|
111
|
+
args: ({ devMenu }) => [
|
|
112
|
+
...process.env.WATCHER_PORT ? ["--port", process.env.WATCHER_PORT] : [],
|
|
113
|
+
"--action",
|
|
114
|
+
devMenu ? "dev-menu" : "debug"
|
|
115
|
+
]
|
|
116
|
+
},
|
|
104
117
|
diagnostics: terminalDiagnostics,
|
|
105
118
|
observation: terminalObservation
|
|
106
119
|
};
|
package/dist/cli.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { adapterRegistry } from "./adapters/registry.js";
|
|
1
2
|
import { detectAdapter, handleHarness } from "./harness.js";
|
|
2
3
|
import { handleActions } from "./commands/manifest.js";
|
|
3
4
|
import { handleDoctor } from "./commands/doctor.js";
|
|
@@ -108,7 +109,8 @@ async function main(argv) {
|
|
|
108
109
|
const { options } = parseArgs(argv.slice(1), command);
|
|
109
110
|
const target = targetPath(options);
|
|
110
111
|
const adapterRaw = (typeof options["adapter"] === "string" ? options["adapter"] : void 0) ?? (typeof options["platform"] === "string" ? options["platform"] : void 0) ?? detectAdapter(target);
|
|
111
|
-
|
|
112
|
+
await import("./adapters/surface.js");
|
|
113
|
+
if (adapterRaw && adapterRegistry.has(adapterRaw)) {
|
|
112
114
|
await handleHarness(["install", "--platform", adapterRaw, "--target", target, "--quiet"]);
|
|
113
115
|
}
|
|
114
116
|
} catch {
|
package/dist/commands/debug.js
CHANGED
|
@@ -10,36 +10,17 @@ async function handleDebug(argv) {
|
|
|
10
10
|
const adapter = resolveAdapter(options, target);
|
|
11
11
|
if (!adapter) return usageOut(json, "debug", `could not detect the MetaMask repo type for ${target}`, ADAPTER_DETECT_NEXT);
|
|
12
12
|
const surface = getAdapterSurface(adapter);
|
|
13
|
-
if (surface.headless) return usageOut(json, "debug",
|
|
13
|
+
if (surface.headless) return usageOut(json, "debug", `${adapter} is headless; there is no debug console.`, surface.hints.relaunch);
|
|
14
14
|
surface.resolveSlotPorts(target);
|
|
15
15
|
const worker = flag(options, "worker");
|
|
16
16
|
const devMenu = flag(options, "devMenu");
|
|
17
17
|
const noOpen = flag(options, "noOpen");
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
"--worker is extension-only (service-worker DevTools). Use --dev-menu on mobile.",
|
|
23
|
-
"mm-harness debug --dev-menu"
|
|
24
|
-
);
|
|
25
|
-
}
|
|
26
|
-
if (adapter === "extension" && devMenu) {
|
|
27
|
-
return usageOut(
|
|
28
|
-
json,
|
|
29
|
-
"debug",
|
|
30
|
-
"--dev-menu is mobile-only (RN developer menu). Use --worker on the extension.",
|
|
31
|
-
"mm-harness debug --worker"
|
|
32
|
-
);
|
|
33
|
-
}
|
|
18
|
+
const debug = surface.debug;
|
|
19
|
+
if (!debug) return usageOut(json, "debug", `${adapter} has no debug console.`, surface.hints.relaunch);
|
|
20
|
+
const refused = debug.refuse?.({ worker, devMenu });
|
|
21
|
+
if (refused) return usageOut(json, "debug", refused.message, refused.userAction);
|
|
34
22
|
const openDebug = path.join(runnerDir, "adapters/shared/open-debug.mjs");
|
|
35
|
-
const debugArgs = ["--adapter", adapter];
|
|
36
|
-
if (adapter === "extension") {
|
|
37
|
-
if (process.env.CDP_PORT) debugArgs.push("--cdp-port", process.env.CDP_PORT);
|
|
38
|
-
debugArgs.push("--target", worker ? "worker" : "page");
|
|
39
|
-
} else {
|
|
40
|
-
if (process.env.WATCHER_PORT) debugArgs.push("--port", process.env.WATCHER_PORT);
|
|
41
|
-
debugArgs.push("--action", devMenu ? "dev-menu" : "debug");
|
|
42
|
-
}
|
|
23
|
+
const debugArgs = ["--adapter", adapter, ...debug.args({ worker, devMenu })];
|
|
43
24
|
if (json) debugArgs.push("--json");
|
|
44
25
|
if (noOpen) debugArgs.push("--no-open");
|
|
45
26
|
const result = spawnScript(process.execPath, [openDebug, ...debugArgs], target, json);
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
findAvailableIosSimulatorById,
|
|
5
5
|
listConnectedDevices
|
|
6
6
|
} from "../devices.js";
|
|
7
|
+
import { getAdapterSurface } from "../adapters/surface.js";
|
|
7
8
|
import { persistExplicitDevicePin } from "../runtime-context.js";
|
|
8
9
|
import { optionString, shellQuoteArg, targetPath } from "./parse-args.js";
|
|
9
10
|
const DEVICE_SELECT_HINT = "Pass --device <name|udid|serial> to select one target. Do not shut down other connected devices to force selection.";
|
|
@@ -97,7 +98,7 @@ function deviceRecovery(options, rerun) {
|
|
|
97
98
|
function applyDeviceTargeting(command, adapter, options, opts) {
|
|
98
99
|
const device = optionString(options, "device");
|
|
99
100
|
const platformPreference = mobilePlatformPreference(options);
|
|
100
|
-
if (adapter
|
|
101
|
+
if (!getAdapterSurface(adapter).readiness.devices) {
|
|
101
102
|
if (device !== void 0) {
|
|
102
103
|
return {
|
|
103
104
|
ok: false,
|