@deeeed/metamask-harness 0.17.3 → 0.17.5
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 +6 -96
- package/README.md +136 -96
- package/adapters/extension/console-tail.mjs +24 -51
- package/adapters/extension/ensure-browser.sh +7 -1
- package/adapters/extension/inject.mjs +15 -5
- package/adapters/extension/launch-browser.cjs +4 -3
- package/adapters/extension/launch.sh +11 -8
- package/adapters/extension/lib/chrome-args.cjs +24 -0
- package/adapters/extension/live.sh +1 -2
- package/adapters/extension/reattach.sh +0 -1
- package/adapters/extension/seed-fixture.sh +12 -4
- package/adapters/extension/sidepanel-toggle.sh +1 -4
- package/adapters/extension/start-watch.sh +4 -17
- package/adapters/extension/stop-viewers.sh +1 -1
- package/adapters/extension/verify.sh +1 -1
- package/adapters/manifest.json +45 -53
- package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +0 -16
- package/adapters/mobile/bridge-runtime/console-forwarder.cjs +1 -1
- package/adapters/mobile/bridge-runtime/lib/match-bridge-target.cjs +1 -3
- package/adapters/mobile/bridge-runtime/lib/target-discovery.cjs +3 -2
- package/adapters/mobile/cleanup.sh +176 -16
- package/adapters/mobile/inject.sh +671 -90
- package/adapters/mobile/overlay/app/dev-tools/AgenticService/AgentStepHud.tsx.patch +185 -0
- package/adapters/mobile/overlay/app/dev-tools/AgenticService/AgenticService.ts.patch +1662 -0
- package/adapters/mobile/overlay/compat/README.md +28 -0
- package/adapters/mobile/overlay/compat/rn81-message-event-source.patch +42 -0
- package/adapters/mobile/start-metro.sh +41 -0
- package/adapters/mobile/verify.sh +61 -4
- package/adapters/mobile/wait-for-bridge.sh +8 -22
- package/adapters/shared/harness-source-fingerprint.mjs +49 -0
- package/adapters/shared/install-repo-deps.sh +1 -5
- package/adapters/shared/open-debug.mjs +184 -103
- package/adapters/shared/resolve-farmslot-ports-core.mjs +3 -0
- package/adapters/shared/resolve-farmslot-ports.mjs +5 -0
- package/adapters/shared/resolve-farmslot-ports.sh +7 -0
- package/adapters/shared/resolve-slot-ports-core.mjs +6 -0
- package/adapters/shared/resolve-slot-ports.sh +7 -2
- package/bin/mm-harness +0 -7
- package/dist/adapters/extension/harness-freshness.js +39 -0
- package/dist/adapters/extension/runtime-decision.js +4 -73
- package/dist/adapters/extension/surface.js +2 -3
- package/dist/adapters/mobile/perps-env.js +3 -43
- package/dist/adapters/mobile/prepare.js +7 -22
- package/dist/adapters/mobile/source-freshness.js +26 -41
- package/dist/adapters/mobile/surface.js +1 -1
- package/dist/adapters/resolve-slot-ports.js +2 -0
- package/dist/adapters/slot-ports.js +0 -16
- package/dist/adapters.js +5 -12
- package/dist/cli-commands.js +1 -1
- package/dist/cli.js +12 -6
- package/dist/commands/call.js +22 -56
- package/dist/commands/check.js +3 -9
- package/dist/commands/checklist.js +4 -1
- package/dist/commands/debug.js +31 -38
- package/dist/commands/device-target.js +12 -27
- package/dist/commands/doctor.js +23 -77
- package/dist/commands/fixtures.js +17 -27
- package/dist/commands/launch/extension.js +2 -0
- package/dist/commands/launch/index.js +59 -167
- package/dist/commands/logs.js +6 -8
- package/dist/commands/manifest.js +25 -151
- package/dist/commands/parse-args.js +0 -2
- package/dist/commands/provision.js +3 -10
- package/dist/commands/run-engine.js +69 -71
- package/dist/commands/run-report.js +3 -12
- package/dist/commands/run.js +35 -245
- package/dist/commands/shared.js +3 -55
- package/dist/commands/status-probe.js +1 -1
- package/dist/commands/status.js +1 -1
- package/dist/commands/stop.js +2 -7
- package/dist/commands/update.js +28 -16
- package/dist/doctor.js +4 -1
- package/dist/harness.js +134 -43
- package/dist/heal-bounds.js +1 -1
- package/dist/live-adapter-contract.js +20 -6
- package/dist/mm-harness-cli.js +48 -123
- package/dist/paths.js +5 -2
- package/dist/runner.js +2 -37
- package/dist/runtime-context.js +8 -26
- package/docs/ADAPTER-SURFACE.md +112 -19
- package/docs/CHEATSHEET.md +52 -39
- package/docs/CLI-SPEC.md +1098 -0
- package/docs/CODE-MAP.md +1 -1
- package/docs/DEBUG-HANDOVER.md +36 -0
- package/docs/MENTAL-MODEL.md +295 -0
- package/docs/UX-PRINCIPLES.md +1 -3
- package/docs/architecture.md +375 -65
- package/docs/live-adapter-contract.md +2 -4
- package/docs/recipe-libraries.md +275 -45
- package/library/README.md +0 -4
- package/library/actions/core/perps/_controller.mjs +55 -10
- package/library/actions/mobile/platform/bridge.mjs +1 -5
- package/library/manifests/core.action-manifest.json +7 -7
- package/library/manifests/mobile.action-manifest.json +3 -28
- package/package.json +1 -4
- package/adapters/extension/launch-webpack.cjs +0 -83
- package/adapters/extension/stamp-runtime-title.cjs +0 -49
- package/adapters/extension/sync-webpack-dist.cjs +0 -105
- package/adapters/mobile/launch-console-forwarder.cjs +0 -39
- package/adapters/mobile/start-console-forwarder.sh +0 -70
- package/adapters/shared/update-check-worker.mjs +0 -35
- package/dist/adapters/extension/console-capture.js +0 -93
- package/dist/command-contract.js +0 -441
- package/dist/command-journal.js +0 -225
- package/dist/commands/last.js +0 -52
- package/dist/json-stream.js +0 -57
- package/dist/run-diagnostics.js +0 -261
- package/docs/CLI-ERGONOMICS-AUDIT.md +0 -32
- package/docs/CLI-ERGONOMICS-HUMAN-QA.md +0 -64
- package/docs/VIDEO-DEMO-VALIDATION.md +0 -74
- package/library/recipes/runner/smoke.core.recipe.json +0 -27
- package/library/recipes/wallet/smoke.extension.recipe.json +0 -42
- package/library/recipes/wallet/smoke.mobile.recipe.json +0 -42
- package/scripts/validate-human-outcomes.mjs +0 -169
|
@@ -7,7 +7,6 @@ import { recipeRuntimePath, runnerDir } from "../../paths.js";
|
|
|
7
7
|
import { getAdapterSurface } from "../../adapters/surface.js";
|
|
8
8
|
import {
|
|
9
9
|
ADAPTER_DETECT_NEXT,
|
|
10
|
-
checkoutBusyOut,
|
|
11
10
|
EXIT,
|
|
12
11
|
flag,
|
|
13
12
|
parseFlags,
|
|
@@ -15,8 +14,7 @@ import {
|
|
|
15
14
|
spawnScript,
|
|
16
15
|
str,
|
|
17
16
|
targetOf,
|
|
18
|
-
usageOut
|
|
19
|
-
writeInteractiveProgress
|
|
17
|
+
usageOut
|
|
20
18
|
} from "../shared.js";
|
|
21
19
|
import {
|
|
22
20
|
RECOVERY_CODE,
|
|
@@ -34,8 +32,6 @@ import {
|
|
|
34
32
|
import { launchMobile } from "./mobile.js";
|
|
35
33
|
import { applyDeviceTargeting } from "../device-target.js";
|
|
36
34
|
import { acquireCheckoutLock } from "../../checkout-lock.js";
|
|
37
|
-
import { JsonStreamWriter } from "../../json-stream.js";
|
|
38
|
-
import { ensureExtensionConsoleCapture } from "../../adapters/extension/console-capture.js";
|
|
39
35
|
const LAUNCH_BOOLEANS = /* @__PURE__ */ new Set([
|
|
40
36
|
"build",
|
|
41
37
|
"watch",
|
|
@@ -43,58 +39,30 @@ const LAUNCH_BOOLEANS = /* @__PURE__ */ new Set([
|
|
|
43
39
|
"sidepanel",
|
|
44
40
|
"fullscreen",
|
|
45
41
|
"runway",
|
|
46
|
-
"json"
|
|
47
|
-
"jsonStream"
|
|
42
|
+
"json"
|
|
48
43
|
]);
|
|
49
44
|
const DEFAULT_EXTENSION_DAPP_URL = "https://metamask.github.io/test-dapp/";
|
|
50
45
|
async function handleLaunch(argv) {
|
|
51
46
|
const { options } = parseFlags(argv, LAUNCH_BOOLEANS);
|
|
52
47
|
const json = flag(options, "json");
|
|
53
|
-
const stream = new JsonStreamWriter("launch", flag(options, "jsonStream"));
|
|
54
|
-
const jsonOutput = json && !stream.enabled;
|
|
55
|
-
const restoreStdout = stream.isolateStdout();
|
|
56
48
|
const target = targetOf(options);
|
|
49
|
+
if (!fs.existsSync(target)) return handleLaunchLocked(argv);
|
|
50
|
+
const lock = acquireCheckoutLock(target, "launch");
|
|
51
|
+
if ("message" in lock) {
|
|
52
|
+
return usageOut(json, "launch", lock.message, `wait for the current owner, or inspect ${lock.path} if its process has exited`);
|
|
53
|
+
}
|
|
57
54
|
try {
|
|
58
|
-
|
|
59
|
-
if (!fs.existsSync(target)) {
|
|
60
|
-
exitCode = await handleLaunchLocked(argv, stream);
|
|
61
|
-
} else {
|
|
62
|
-
const lock = acquireCheckoutLock(target, "launch");
|
|
63
|
-
if ("message" in lock) {
|
|
64
|
-
const userAction = `wait for the current owner, or inspect ${lock.path} if its process has exited`;
|
|
65
|
-
stream.error({ code: "SANDBOX_BUSY", message: lock.message, userAction });
|
|
66
|
-
exitCode = checkoutBusyOut(jsonOutput, "launch", lock.message, lock.path);
|
|
67
|
-
} else {
|
|
68
|
-
try {
|
|
69
|
-
exitCode = await handleLaunchLocked(argv, stream);
|
|
70
|
-
} finally {
|
|
71
|
-
lock.release();
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
stream.complete(exitCode === EXIT.ok ? "pass" : "fail", exitCode);
|
|
76
|
-
return exitCode;
|
|
77
|
-
} catch (error) {
|
|
78
|
-
const exitCode = error !== null && typeof error === "object" && "exitCode" in error && typeof error.exitCode === "number" ? error.exitCode : EXIT.runtime;
|
|
79
|
-
stream.error({
|
|
80
|
-
code: exitCode === EXIT.usage ? "CLI_USAGE_ERROR" : "LAUNCH_FAILED",
|
|
81
|
-
message: error instanceof Error ? error.message : String(error),
|
|
82
|
-
userAction: `mm-harness doctor --target ${shellQuote(target)} --json`
|
|
83
|
-
});
|
|
84
|
-
stream.complete("fail", exitCode);
|
|
85
|
-
throw error;
|
|
55
|
+
return await handleLaunchLocked(argv);
|
|
86
56
|
} finally {
|
|
87
|
-
|
|
57
|
+
lock.release();
|
|
88
58
|
}
|
|
89
59
|
}
|
|
90
|
-
async function handleLaunchLocked(argv
|
|
60
|
+
async function handleLaunchLocked(argv) {
|
|
91
61
|
const { positional, options } = parseFlags(argv, LAUNCH_BOOLEANS);
|
|
92
62
|
const json = flag(options, "json");
|
|
93
|
-
const jsonOutput = json && !stream.enabled;
|
|
94
|
-
const machine = json || stream.enabled;
|
|
95
63
|
const target = targetOf(options);
|
|
96
64
|
const heal = parseHeal(options, "auto");
|
|
97
|
-
if (typeof heal !== "string") return
|
|
65
|
+
if (typeof heal !== "string") return usageOut(json, "launch", heal.error, "use --heal off|infra-only|auto");
|
|
98
66
|
const posToken = positional[0];
|
|
99
67
|
const mobileTargetToken = posToken === "ios" || posToken === "android" ? posToken : void 0;
|
|
100
68
|
const platformFlag = str(options, "platform");
|
|
@@ -103,16 +71,15 @@ async function handleLaunchLocked(argv, stream) {
|
|
|
103
71
|
const adapterHint = mobileTarget ? "mobile" : void 0;
|
|
104
72
|
const adapter = resolveAdapter(options, target, adapterHint);
|
|
105
73
|
if (!adapter) {
|
|
106
|
-
return
|
|
74
|
+
return usageOut(json, "launch", `could not detect the MetaMask repo type for ${target}`, ADAPTER_DETECT_NEXT);
|
|
107
75
|
}
|
|
108
|
-
stream.phase("resolve", { target, adapter, platform: mobileTarget ?? null });
|
|
109
76
|
if (adapter === "core") {
|
|
110
|
-
return
|
|
77
|
+
return usageOut(json, "launch", "core is headless; there is nothing to launch.", "mm-harness verify");
|
|
111
78
|
}
|
|
112
79
|
if (adapter === "mobile" && !mobileTarget) {
|
|
113
|
-
return
|
|
114
|
-
|
|
115
|
-
|
|
80
|
+
return usageOut(
|
|
81
|
+
json,
|
|
82
|
+
"launch",
|
|
116
83
|
"target is required for mobile.",
|
|
117
84
|
"mm-harness launch ios or mm-harness launch android"
|
|
118
85
|
);
|
|
@@ -122,15 +89,15 @@ async function handleLaunchLocked(argv, stream) {
|
|
|
122
89
|
const wantWatch = flag(options, "watch");
|
|
123
90
|
const wantRunway = flag(options, "runway");
|
|
124
91
|
if (wantRunway && adapter !== "mobile") {
|
|
125
|
-
return
|
|
92
|
+
return usageOut(json, "launch", "runway is mobile-only.", "drop --runway for the extension");
|
|
126
93
|
}
|
|
127
94
|
const displayMode = flag(options, "sidepanel") && !flag(options, "fullscreen") ? "sidepanel" : "fullscreen";
|
|
128
95
|
for (const portFlag of ["cdpPort", "watcherPort"]) {
|
|
129
96
|
const value = str(options, portFlag);
|
|
130
97
|
if (value !== void 0 && !/^\d+$/u.test(value)) {
|
|
131
|
-
return
|
|
132
|
-
|
|
133
|
-
|
|
98
|
+
return usageOut(
|
|
99
|
+
json,
|
|
100
|
+
"launch",
|
|
134
101
|
`--${portFlag === "cdpPort" ? "cdp-port" : "watcher-port"} must be numeric (got: ${value}).`,
|
|
135
102
|
`pass a numeric port, e.g. --${portFlag === "cdpPort" ? "cdp-port 6663" : "watcher-port 8081"}`
|
|
136
103
|
);
|
|
@@ -138,7 +105,7 @@ async function handleLaunchLocked(argv, stream) {
|
|
|
138
105
|
}
|
|
139
106
|
const envResult = applyLaunchEnvOverrides(options, adapter, mobileTarget, target);
|
|
140
107
|
if (envResult && "code" in envResult) {
|
|
141
|
-
return
|
|
108
|
+
return usageOut(json, "launch", envResult.message, "mm-harness launch android --device <adb-serial|device-name>");
|
|
142
109
|
}
|
|
143
110
|
const tier = wantBuild ? "build" : "quick";
|
|
144
111
|
if (adapter === "extension") {
|
|
@@ -147,55 +114,44 @@ async function handleLaunchLocked(argv, stream) {
|
|
|
147
114
|
process.env.EXTENSION_START_URL = requestedUrl || DEFAULT_EXTENSION_DAPP_URL;
|
|
148
115
|
}
|
|
149
116
|
}
|
|
150
|
-
if (!
|
|
117
|
+
if (!json && adapter === "extension") {
|
|
151
118
|
const modeNote = displayMode === "sidepanel" ? `sidepanel \xB7 dapp ${process.env.EXTENSION_START_URL ?? DEFAULT_EXTENSION_DAPP_URL}` : "fullscreen";
|
|
152
119
|
const workNote = wantWatch ? "watch only" : tier === "build" ? "clean build" : "quick reuse probe";
|
|
153
120
|
console.error(
|
|
154
121
|
`\u2192 extension launch \u2014 ${modeNote} \xB7 CDP :${process.env.CDP_PORT ?? "default"} \xB7 ${workNote}`
|
|
155
122
|
);
|
|
156
123
|
}
|
|
157
|
-
if (adapter === "mobile") {
|
|
158
|
-
const device = str(options, "device") ?? (mobileTarget === "android" ? process.env.ADB_SERIAL ?? process.env.ANDROID_SERIAL : process.env.IOS_SIMULATOR ?? process.env.SIM_UDID) ?? "configured device";
|
|
159
|
-
const watcherPort = str(options, "watcherPort") ?? process.env.WATCHER_PORT ?? "auto";
|
|
160
|
-
writeInteractiveProgress(
|
|
161
|
-
machine,
|
|
162
|
-
`\u2192 mobile launch \u2014 ${mobileTarget} \xB7 ${tier === "build" ? "native build" : "quick readiness"} \xB7 ${device} \xB7 Metro ${watcherPort === "auto" ? "auto" : `:${watcherPort}`}`
|
|
163
|
-
);
|
|
164
|
-
}
|
|
165
124
|
const state = newHealState();
|
|
166
125
|
if (tier === "quick" && nativeInputsChanged(target, adapter)) {
|
|
167
|
-
return
|
|
168
|
-
|
|
169
|
-
|
|
126
|
+
return usageOut(
|
|
127
|
+
json,
|
|
128
|
+
"launch",
|
|
170
129
|
"native build inputs changed since last build.",
|
|
171
130
|
`mm-harness launch ${adapter === "mobile" ? `${mobileTarget} ` : ""}--build`
|
|
172
131
|
);
|
|
173
132
|
}
|
|
174
|
-
|
|
175
|
-
const ensured = await ensureOverlay(adapter, target, heal, state, machine);
|
|
133
|
+
const ensured = await ensureOverlay(adapter, target, heal, state, json);
|
|
176
134
|
if (!ensured.ok) {
|
|
177
|
-
return launchFail(
|
|
135
|
+
return launchFail(json, adapter, mobileTarget, tier, state, {
|
|
178
136
|
code: "OVERLAY_INSTALL_FAILED",
|
|
179
137
|
message: ensured.error ?? "overlay install failed",
|
|
180
138
|
recoverable: false,
|
|
181
139
|
exitCode: EXIT.infra
|
|
182
140
|
});
|
|
183
141
|
}
|
|
184
|
-
stream.phase("healthcheck");
|
|
185
142
|
if (adapter === "extension") {
|
|
186
143
|
const willBuild = wantWatch || tier === "build" || !await extensionRuntimeReusable(target);
|
|
187
144
|
if (willBuild) {
|
|
188
145
|
const depsBlock = extensionDepsBlock(target);
|
|
189
146
|
if (depsBlock) {
|
|
190
147
|
if (heal === "off") {
|
|
191
|
-
return
|
|
148
|
+
return usageOut(json, "launch", depsBlock.message, depsBlock.userAction);
|
|
192
149
|
}
|
|
193
150
|
const recoveryCode2 = "deps.installed";
|
|
194
151
|
state.attemptedRecoveries.push(recoveryCode2);
|
|
195
|
-
stream.phase("recover");
|
|
196
152
|
const repaired = await installExtensionDeps(target);
|
|
197
153
|
if (repaired.status !== 0 || extensionDepsBlock(target)) {
|
|
198
|
-
return launchFail(
|
|
154
|
+
return launchFail(json, adapter, mobileTarget, tier, state, {
|
|
199
155
|
code: "DEPENDENCY_INSTALL_FAILED",
|
|
200
156
|
message: depsBlock.message,
|
|
201
157
|
recoverable: false,
|
|
@@ -209,13 +165,12 @@ async function handleLaunchLocked(argv, stream) {
|
|
|
209
165
|
}
|
|
210
166
|
}
|
|
211
167
|
}
|
|
212
|
-
|
|
213
|
-
let attempt = await executeComposition(adapter, mobileTarget, tier, wantWatch, target, machine, displayMode);
|
|
168
|
+
let attempt = await executeComposition(adapter, mobileTarget, tier, wantWatch, target, json, displayMode);
|
|
214
169
|
if (attempt.status === 0) {
|
|
215
|
-
return await finishLaunch(
|
|
170
|
+
return await finishLaunch(json, adapter, mobileTarget, tier, displayMode, target, state, wantWatch, wantVerify);
|
|
216
171
|
}
|
|
217
172
|
if (heal === "off") {
|
|
218
|
-
return launchFail(
|
|
173
|
+
return launchFail(json, adapter, mobileTarget, tier, state, {
|
|
219
174
|
code: "LAUNCH_FAILED",
|
|
220
175
|
message: `launch ${adapter}${mobileTarget ? ` ${mobileTarget}` : ""} failed (healing off)`,
|
|
221
176
|
recoverable: false,
|
|
@@ -225,13 +180,12 @@ async function handleLaunchLocked(argv, stream) {
|
|
|
225
180
|
if (adapter === "extension" && tier === "quick" && extensionQuickReattachFailed(attempt.output)) {
|
|
226
181
|
const recoveryCode2 = "chrome.relaunched";
|
|
227
182
|
state.attemptedRecoveries.push(recoveryCode2);
|
|
228
|
-
|
|
229
|
-
const rebuildAttempt = await executeComposition(adapter, mobileTarget, "build", wantWatch, target, machine, displayMode);
|
|
183
|
+
const rebuildAttempt = await executeComposition(adapter, mobileTarget, "build", wantWatch, target, json, displayMode);
|
|
230
184
|
if (rebuildAttempt.status === 0) {
|
|
231
185
|
state.recovered.push(recoveryCode2);
|
|
232
|
-
return await finishLaunch(
|
|
186
|
+
return await finishLaunch(json, adapter, mobileTarget, tier, displayMode, target, state, wantWatch, wantVerify);
|
|
233
187
|
}
|
|
234
|
-
return launchFail(
|
|
188
|
+
return launchFail(json, adapter, mobileTarget, tier, state, {
|
|
235
189
|
code: "EXTENSION_RELAUNCH_FAILED",
|
|
236
190
|
message: "quick reattach failed, and clean relaunch failed too.",
|
|
237
191
|
recoverable: false,
|
|
@@ -240,7 +194,7 @@ async function handleLaunchLocked(argv, stream) {
|
|
|
240
194
|
});
|
|
241
195
|
}
|
|
242
196
|
if (adapter === "mobile" && mobileProvisioningBlocked(attempt.output)) {
|
|
243
|
-
return launchFail(
|
|
197
|
+
return launchFail(json, adapter, mobileTarget, tier, state, {
|
|
244
198
|
code: "MOBILE_PROVISION_REQUIRED",
|
|
245
199
|
message: `mobile ${mobileTarget ?? "runtime"} is not provisioned for this slot.`,
|
|
246
200
|
recoverable: false,
|
|
@@ -251,7 +205,7 @@ async function handleLaunchLocked(argv, stream) {
|
|
|
251
205
|
}
|
|
252
206
|
const bound = checkHealBounds(target, attempt.output, state);
|
|
253
207
|
if (bound !== null) {
|
|
254
|
-
return launchFail(
|
|
208
|
+
return launchFail(json, adapter, mobileTarget, tier, state, {
|
|
255
209
|
code: bound.code,
|
|
256
210
|
message: bound.message,
|
|
257
211
|
recoverable: false,
|
|
@@ -262,13 +216,12 @@ async function handleLaunchLocked(argv, stream) {
|
|
|
262
216
|
}
|
|
263
217
|
const recoveryCode = RECOVERY_CODE[adapter];
|
|
264
218
|
state.attemptedRecoveries.push(recoveryCode);
|
|
265
|
-
|
|
266
|
-
attempt = await executeComposition(adapter, mobileTarget, tier, wantWatch, target, machine, displayMode);
|
|
219
|
+
attempt = await executeComposition(adapter, mobileTarget, tier, wantWatch, target, json, displayMode);
|
|
267
220
|
if (attempt.status === 0) {
|
|
268
221
|
state.recovered.push(recoveryCode);
|
|
269
|
-
return await finishLaunch(
|
|
222
|
+
return await finishLaunch(json, adapter, mobileTarget, tier, displayMode, target, state, wantWatch, wantVerify);
|
|
270
223
|
}
|
|
271
|
-
return launchFail(
|
|
224
|
+
return launchFail(json, adapter, mobileTarget, tier, state, {
|
|
272
225
|
code: "SAME_RECOVERY_TWICE",
|
|
273
226
|
message: `launch ${adapter}${mobileTarget ? ` ${mobileTarget}` : ""} failed again after ${recoveryCode} recovery \u2014 refusing to loop.`,
|
|
274
227
|
recoverable: false,
|
|
@@ -321,16 +274,14 @@ function applyLaunchEnvOverrides(options, adapter, mobileTarget, target) {
|
|
|
321
274
|
return {
|
|
322
275
|
ok: false,
|
|
323
276
|
code: "DEVICE_WRONG_PLATFORM",
|
|
324
|
-
message: `--device ${device} did not resolve to an Android device for launch android
|
|
325
|
-
userAction: `mm-harness status --target ${shellQuote(target)} --all-devices --json`
|
|
277
|
+
message: `--device ${device} did not resolve to an Android device for launch android.`
|
|
326
278
|
};
|
|
327
279
|
}
|
|
328
280
|
if (mobileTarget === "ios" && device && !process.env.IOS_SIMULATOR) {
|
|
329
281
|
return {
|
|
330
282
|
ok: false,
|
|
331
283
|
code: "DEVICE_WRONG_PLATFORM",
|
|
332
|
-
message: `--device ${device} did not resolve to an iOS simulator for launch ios
|
|
333
|
-
userAction: `mm-harness status --target ${shellQuote(target)} --all-devices --json`
|
|
284
|
+
message: `--device ${device} did not resolve to an iOS simulator for launch ios.`
|
|
334
285
|
};
|
|
335
286
|
}
|
|
336
287
|
}
|
|
@@ -376,29 +327,26 @@ async function executeComposition(adapter, mobileTarget, tier, wantWatch, target
|
|
|
376
327
|
}
|
|
377
328
|
return launchExtension(target, tier, wantWatch, displayMode);
|
|
378
329
|
}
|
|
379
|
-
async function finishLaunch(
|
|
330
|
+
async function finishLaunch(json, adapter, mobileTarget, tier, displayMode, target, state, wantWatch, wantVerify, sidepanelRecoveryAllowed = true) {
|
|
380
331
|
if (adapter === "extension" && displayMode === "sidepanel") {
|
|
381
332
|
const sidepanelSh = path.join(runnerDir, "adapters/extension/sidepanel-toggle.sh");
|
|
382
333
|
const sidepanelArgs = ["open"];
|
|
383
334
|
if (process.env.CDP_PORT) sidepanelArgs.push("--cdp-port", process.env.CDP_PORT);
|
|
384
|
-
if (!
|
|
335
|
+
if (!json) {
|
|
385
336
|
console.error(
|
|
386
337
|
`[sidepanel] opening panel beside ${process.env.EXTENSION_START_URL ?? DEFAULT_EXTENSION_DAPP_URL}`
|
|
387
338
|
);
|
|
388
339
|
}
|
|
389
|
-
const sidepanel = spawnScript(sidepanelSh, sidepanelArgs, target,
|
|
340
|
+
const sidepanel = spawnScript(sidepanelSh, sidepanelArgs, target, json, { REPO: target });
|
|
390
341
|
if (sidepanel.status !== 0) {
|
|
391
342
|
const recoveryCode = "chrome.relaunched";
|
|
392
343
|
if (sidepanelRecoveryAllowed && tier === "quick" && extensionRuntimeBlocked(sidepanel.output) && !state.attemptedRecoveries.includes(recoveryCode)) {
|
|
393
344
|
state.attemptedRecoveries.push(recoveryCode);
|
|
394
|
-
|
|
395
|
-
const rebuildAttempt = await executeComposition(adapter, mobileTarget, "build", wantWatch, target, machine, displayMode);
|
|
345
|
+
const rebuildAttempt = await executeComposition(adapter, mobileTarget, "build", wantWatch, target, json, displayMode);
|
|
396
346
|
if (rebuildAttempt.status === 0) {
|
|
397
347
|
state.recovered.push(recoveryCode);
|
|
398
348
|
return await finishLaunch(
|
|
399
|
-
|
|
400
|
-
machine,
|
|
401
|
-
stream,
|
|
349
|
+
json,
|
|
402
350
|
adapter,
|
|
403
351
|
mobileTarget,
|
|
404
352
|
tier,
|
|
@@ -410,7 +358,7 @@ async function finishLaunch(jsonOutput, machine, stream, adapter, mobileTarget,
|
|
|
410
358
|
false
|
|
411
359
|
);
|
|
412
360
|
}
|
|
413
|
-
return launchFail(
|
|
361
|
+
return launchFail(json, adapter, mobileTarget, tier, state, {
|
|
414
362
|
code: "EXTENSION_RELAUNCH_FAILED",
|
|
415
363
|
message: "sidepanel open found a blocked extension runtime, and clean relaunch failed too.",
|
|
416
364
|
recoverable: false,
|
|
@@ -418,7 +366,7 @@ async function finishLaunch(jsonOutput, machine, stream, adapter, mobileTarget,
|
|
|
418
366
|
originalError: rebuildAttempt.output.trim() || void 0
|
|
419
367
|
});
|
|
420
368
|
}
|
|
421
|
-
return launchFail(
|
|
369
|
+
return launchFail(json, adapter, mobileTarget, tier, state, {
|
|
422
370
|
code: "SIDEPANEL_OPEN_FAILED",
|
|
423
371
|
message: "app launched but opening the side panel failed.",
|
|
424
372
|
recoverable: false,
|
|
@@ -428,48 +376,16 @@ async function finishLaunch(jsonOutput, machine, stream, adapter, mobileTarget,
|
|
|
428
376
|
});
|
|
429
377
|
}
|
|
430
378
|
}
|
|
431
|
-
if (adapter === "extension") {
|
|
432
|
-
try {
|
|
433
|
-
await ensureExtensionConsoleCapture(target);
|
|
434
|
-
} catch (error) {
|
|
435
|
-
if (!machine) {
|
|
436
|
-
console.error(`WARN: Extension console capture unavailable: ${error instanceof Error ? error.message : String(error)}`);
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
379
|
if (wantVerify) {
|
|
441
|
-
stream.phase("verify");
|
|
442
380
|
const verifyArgs = ["verify", "--adapter", adapter, "--target", target];
|
|
443
381
|
if (adapter === "mobile" && mobileTarget) verifyArgs.push("--platform", mobileTarget);
|
|
444
|
-
if (
|
|
445
|
-
|
|
446
|
-
if (stream.enabled) {
|
|
447
|
-
for (const mutation of state.mutations) stream.mutation(mutation);
|
|
448
|
-
for (const recovery of state.recovered) stream.recovery(recovery);
|
|
449
|
-
stream.complete(exitCode === EXIT.ok ? "pass" : "fail", exitCode, {
|
|
450
|
-
adapter,
|
|
451
|
-
platform: mobileTarget ?? (adapter === "extension" ? displayMode : null),
|
|
452
|
-
tier,
|
|
453
|
-
recovered: state.recovered,
|
|
454
|
-
mutations: state.mutations
|
|
455
|
-
});
|
|
456
|
-
}
|
|
457
|
-
return exitCode;
|
|
382
|
+
if (json) verifyArgs.push("--json");
|
|
383
|
+
return handleHarness(verifyArgs);
|
|
458
384
|
}
|
|
459
|
-
return launchPass(
|
|
385
|
+
return launchPass(json, adapter, mobileTarget, tier, displayMode, state);
|
|
460
386
|
}
|
|
461
|
-
function launchPass(json,
|
|
462
|
-
if (
|
|
463
|
-
for (const mutation of state.mutations) stream.mutation(mutation);
|
|
464
|
-
for (const recovery of state.recovered) stream.recovery(recovery);
|
|
465
|
-
stream.complete("pass", EXIT.ok, {
|
|
466
|
-
adapter,
|
|
467
|
-
platform: mobileTarget ?? (adapter === "extension" ? displayMode : null),
|
|
468
|
-
tier,
|
|
469
|
-
recovered: state.recovered,
|
|
470
|
-
mutations: state.mutations
|
|
471
|
-
});
|
|
472
|
-
} else if (json) {
|
|
387
|
+
function launchPass(json, adapter, mobileTarget, tier, displayMode, state) {
|
|
388
|
+
if (json) {
|
|
473
389
|
console.log(
|
|
474
390
|
JSON.stringify(
|
|
475
391
|
{
|
|
@@ -507,32 +423,8 @@ function launchDeviceLabel(mobileTarget) {
|
|
|
507
423
|
}
|
|
508
424
|
return process.env.IOS_SIMULATOR || process.env.ADB_SERIAL || "booted device";
|
|
509
425
|
}
|
|
510
|
-
function
|
|
511
|
-
|
|
512
|
-
return usageOut(json, "launch", message, userAction);
|
|
513
|
-
}
|
|
514
|
-
function launchFail(json, stream, adapter, mobileTarget, tier, state, target, failure) {
|
|
515
|
-
const userAction = failure.userAction ?? `mm-harness doctor --fix --adapter ${adapter} --target ${shellQuote(target)} --json`;
|
|
516
|
-
if (stream.enabled) {
|
|
517
|
-
for (const mutation of state.mutations) stream.mutation(mutation);
|
|
518
|
-
for (const recovery of state.recovered) stream.recovery(recovery);
|
|
519
|
-
stream.error({
|
|
520
|
-
code: failure.code,
|
|
521
|
-
message: failure.message,
|
|
522
|
-
retryable: failure.recoverable,
|
|
523
|
-
userAction,
|
|
524
|
-
originalError: failure.originalError ?? null
|
|
525
|
-
});
|
|
526
|
-
stream.complete("fail", failure.exitCode, {
|
|
527
|
-
adapter,
|
|
528
|
-
platform: mobileTarget ?? null,
|
|
529
|
-
tier,
|
|
530
|
-
recovered: state.recovered,
|
|
531
|
-
mutations: state.mutations,
|
|
532
|
-
recoverable: failure.recoverable,
|
|
533
|
-
attemptedRecoveries: state.attemptedRecoveries
|
|
534
|
-
});
|
|
535
|
-
} else if (json) {
|
|
426
|
+
function launchFail(json, adapter, mobileTarget, tier, state, failure) {
|
|
427
|
+
if (json) {
|
|
536
428
|
console.log(
|
|
537
429
|
JSON.stringify(
|
|
538
430
|
{
|
|
@@ -552,7 +444,7 @@ function launchFail(json, stream, adapter, mobileTarget, tier, state, target, fa
|
|
|
552
444
|
code: failure.code,
|
|
553
445
|
message: failure.message,
|
|
554
446
|
retryable: failure.recoverable,
|
|
555
|
-
userAction,
|
|
447
|
+
userAction: failure.userAction ?? null,
|
|
556
448
|
originalError: failure.originalError ?? null
|
|
557
449
|
}
|
|
558
450
|
},
|
|
@@ -565,8 +457,8 @@ function launchFail(json, stream, adapter, mobileTarget, tier, state, target, fa
|
|
|
565
457
|
`\u2717 launch ${adapter}${mobileTarget ? ` ${mobileTarget}` : ""} failed
|
|
566
458
|
${failure.message}` + (failure.originalError ? `
|
|
567
459
|
--- original failure ---
|
|
568
|
-
${failure.originalError}` : "") + `
|
|
569
|
-
Next: ${userAction}`
|
|
460
|
+
${failure.originalError}` : "") + (failure.userAction ? `
|
|
461
|
+
Next: ${failure.userAction}` : "")
|
|
570
462
|
);
|
|
571
463
|
}
|
|
572
464
|
return failure.exitCode;
|
package/dist/commands/logs.js
CHANGED
|
@@ -19,10 +19,9 @@ async function handleLogs(argv) {
|
|
|
19
19
|
const logSources = surface.logSources(target);
|
|
20
20
|
const appLogSource = surface.appLogSource(target);
|
|
21
21
|
const sourceLabels = logSources.map((entry) => entry.label);
|
|
22
|
-
const
|
|
23
|
-
const defaultSource = appLogSource && appSourceLabel !== "app" ? appSourceLabel : sourceLabels[0];
|
|
22
|
+
const defaultSource = sourceLabels[0];
|
|
24
23
|
const source = str(options, "source") ?? defaultSource;
|
|
25
|
-
const validSources = [...sourceLabels, ...appLogSource ? [
|
|
24
|
+
const validSources = [...sourceLabels, ...appLogSource ? ["app"] : []];
|
|
26
25
|
if (!validSources.includes(source)) {
|
|
27
26
|
return usageOut(json, "logs", `--source must be one of: ${validSources.join(", ")}.`, `mm-harness logs --source ${defaultSource}`);
|
|
28
27
|
}
|
|
@@ -33,12 +32,11 @@ async function handleLogs(argv) {
|
|
|
33
32
|
}
|
|
34
33
|
process.env.RECIPE_LOG_EVENTS = events;
|
|
35
34
|
}
|
|
36
|
-
const
|
|
37
|
-
const
|
|
38
|
-
const ordered = isAppSource ? requested ? [requested] : [] : requested ? [requested] : logSources;
|
|
35
|
+
const requested = source === "app" ? appLogSource : logSources.find((entry) => entry.label === source);
|
|
36
|
+
const ordered = source === "app" ? requested ? [requested] : [] : requested ? [requested, ...logSources.filter((entry) => entry !== requested)] : logSources;
|
|
39
37
|
const logFile = ordered.find((entry) => fs.existsSync(entry.path))?.path;
|
|
40
38
|
if (!logFile) {
|
|
41
|
-
if (
|
|
39
|
+
if (source === "app" && appLogSource) {
|
|
42
40
|
const message = `application console unavailable for this checkout (${path.basename(appLogSource.path)} is absent).`;
|
|
43
41
|
const userAction = surface.hints.launch;
|
|
44
42
|
if (json) {
|
|
@@ -57,7 +55,7 @@ async function handleLogs(argv) {
|
|
|
57
55
|
}
|
|
58
56
|
return EXIT.runtime;
|
|
59
57
|
}
|
|
60
|
-
const names =
|
|
58
|
+
const names = source === "app" && appLogSource ? path.basename(appLogSource.path) : logSources.map((entry) => path.basename(entry.path)).join(" / ");
|
|
61
59
|
return usageOut(
|
|
62
60
|
json,
|
|
63
61
|
"logs",
|