@deeeed/metamask-harness 0.17.0 → 0.17.2
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 +34 -0
- package/README.md +90 -140
- package/adapters/core/cleanup.sh +0 -0
- package/adapters/core/inject.sh +0 -0
- package/adapters/extension/cleanup.mjs +0 -0
- package/adapters/extension/console-tail.mjs +51 -24
- package/adapters/extension/ensure-browser.sh +0 -0
- package/adapters/extension/inject.mjs +5 -9
- package/adapters/extension/launch-browser.cjs +0 -0
- package/adapters/extension/launch-webpack.cjs +83 -0
- package/adapters/extension/launch.sh +8 -11
- package/adapters/extension/live.sh +2 -0
- package/adapters/extension/readiness.mjs +0 -0
- package/adapters/extension/reattach.sh +1 -0
- package/adapters/extension/refresh-build.sh +0 -0
- package/adapters/extension/seed-fixture.sh +4 -12
- package/adapters/extension/sidepanel-toggle.sh +4 -1
- package/adapters/extension/snapshot-dist.sh +0 -0
- package/adapters/extension/stamp-runtime-title.cjs +49 -0
- package/adapters/extension/start-watch.sh +16 -4
- package/adapters/extension/stop-viewers.sh +1 -1
- package/adapters/extension/sync-webpack-dist.cjs +105 -0
- package/adapters/extension/verify.sh +1 -1
- package/adapters/extension/wallet-fixture-state.cjs +0 -0
- package/adapters/manifest.json +46 -38
- package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +0 -0
- package/adapters/mobile/bridge-runtime/console-forwarder.cjs +1 -1
- package/adapters/mobile/bridge-runtime/lib/target-discovery.cjs +2 -3
- package/adapters/mobile/bridge-runtime/setup-wallet.sh +0 -0
- package/adapters/mobile/cleanup.sh +16 -176
- package/adapters/mobile/inject.sh +88 -671
- package/adapters/mobile/launch-console-forwarder.cjs +39 -0
- package/adapters/mobile/lib/metro-listener.sh +0 -0
- package/adapters/mobile/lib/tmux-viewer.sh +0 -0
- package/adapters/mobile/open-device.sh +0 -0
- package/adapters/mobile/prewarm-bundle.sh +0 -0
- package/adapters/mobile/start-console-forwarder.sh +70 -0
- package/adapters/mobile/start-metro.sh +0 -41
- package/adapters/mobile/stop-metro.sh +0 -0
- package/adapters/mobile/verify.sh +4 -61
- package/adapters/mobile/wait-for-bridge.sh +0 -0
- package/adapters/mobile/yarn-setup.sh +0 -0
- package/adapters/shared/activate-repo-node.sh +0 -0
- package/adapters/shared/activate-repo-ruby.sh +0 -0
- package/adapters/shared/cli-ux.sh +0 -0
- package/adapters/shared/ensure-runner-deps.sh +0 -0
- package/adapters/shared/harness-path.sh +0 -0
- package/adapters/shared/hash-helpers.sh +0 -0
- package/adapters/shared/install-repo-deps.sh +0 -0
- package/adapters/shared/json-field.sh +0 -0
- package/adapters/shared/open-debug.mjs +90 -36
- package/adapters/shared/open-log-window.sh +0 -0
- package/adapters/shared/reap-checkout-metros.sh +0 -0
- package/adapters/shared/resolve-slot-ports-core.mjs +0 -6
- package/adapters/shared/resolve-slot-ports.mjs +0 -0
- package/adapters/shared/resolve-slot-ports.sh +0 -5
- package/adapters/shared/sync-wallet-fixture.sh +0 -0
- package/adapters/shared/tmux-session.sh +0 -0
- package/adapters/shared/tmux-viewer.sh +0 -0
- package/adapters/shared/update-check-worker.mjs +37 -0
- package/dist/adapters/extension/console-capture.js +84 -0
- package/dist/adapters/extension/runtime-decision.js +14 -2
- package/dist/adapters/extension/surface.js +3 -2
- package/dist/adapters/mobile/prepare.js +22 -7
- package/dist/adapters/mobile/surface.js +1 -1
- package/dist/adapters/resolve-slot-ports.js +0 -2
- package/dist/adapters/slot-ports.js +8 -0
- package/dist/cli.js +3 -11
- package/dist/commands/call.js +9 -4
- package/dist/commands/debug.js +38 -31
- package/dist/commands/fixtures.js +18 -10
- package/dist/commands/launch/index.js +10 -0
- package/dist/commands/logs.js +8 -6
- package/dist/commands/manifest.js +5 -17
- package/dist/commands/run-engine.js +1 -32
- package/dist/commands/run-report.js +1 -1
- package/dist/commands/run.js +50 -3
- package/dist/commands/status-probe.js +1 -1
- package/dist/commands/update.js +15 -28
- package/dist/doctor.js +1 -4
- package/dist/harness.js +5 -123
- package/dist/live-adapter-contract.js +6 -20
- package/dist/mm-harness-cli.js +6 -43
- package/dist/paths.js +2 -5
- package/dist/run-diagnostics.js +35 -40
- package/dist/runtime-context.js +26 -8
- package/docs/CHEATSHEET.md +39 -52
- package/docs/CLI-ERGONOMICS-HUMAN-QA.md +48 -104
- package/docs/CODE-MAP.md +1 -1
- package/docs/VIDEO-DEMO-VALIDATION.md +62 -0
- package/docs/architecture.md +65 -375
- package/docs/live-adapter-contract.md +3 -1
- package/library/actions/core/perps/_controller.mjs +10 -55
- package/library/manifests/mobile.action-manifest.json +1 -1
- package/package.json +2 -2
- package/scripts/completions.sh +0 -0
- package/scripts/install-completions.sh +0 -0
- package/adapters/mobile/overlay/app/dev-tools/AgenticService/AgentStepHud.tsx.patch +0 -185
- package/adapters/mobile/overlay/app/dev-tools/AgenticService/AgenticService.ts.patch +0 -1662
- package/adapters/mobile/overlay/compat/README.md +0 -28
- package/adapters/mobile/overlay/compat/rn81-message-event-source.patch +0 -42
- package/adapters/shared/resolve-farmslot-ports-core.mjs +0 -3
- package/adapters/shared/resolve-farmslot-ports.mjs +0 -5
- package/adapters/shared/resolve-farmslot-ports.sh +0 -7
- package/docs/CLI-SPEC.md +0 -1142
- package/docs/MENTAL-MODEL.md +0 -295
package/dist/commands/update.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
1
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
@@ -6,7 +6,7 @@ import { runnerDir } from "../paths.js";
|
|
|
6
6
|
import { EXIT, flag, parseFlags } from "./shared.js";
|
|
7
7
|
const PACKAGE_NAME = "@deeeed/metamask-harness";
|
|
8
8
|
const NUDGE_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
9
|
-
const NUDGE_FETCH_TIMEOUT_MS =
|
|
9
|
+
const NUDGE_FETCH_TIMEOUT_MS = 2e3;
|
|
10
10
|
function currentVersion() {
|
|
11
11
|
try {
|
|
12
12
|
const pkg = JSON.parse(fs.readFileSync(path.join(runnerDir, "package.json"), "utf8"));
|
|
@@ -56,24 +56,6 @@ function fetchLatest(timeoutMs) {
|
|
|
56
56
|
if (!latest) return { error: { kind: "unreachable", message: "npm returned no dist-tag for latest" } };
|
|
57
57
|
return { latest };
|
|
58
58
|
}
|
|
59
|
-
async function fetchLatestAsync(timeoutMs) {
|
|
60
|
-
const controller = new AbortController();
|
|
61
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
62
|
-
try {
|
|
63
|
-
const url = `https://registry.npmjs.org/-/package/${encodeURIComponent(PACKAGE_NAME)}/dist-tags`;
|
|
64
|
-
const res = await fetch(url, { signal: controller.signal });
|
|
65
|
-
if (!res.ok) return { error: { kind: "unreachable", message: `registry returned HTTP ${res.status}` } };
|
|
66
|
-
const data = await res.json();
|
|
67
|
-
const latest = data["latest"];
|
|
68
|
-
if (!latest) return { error: { kind: "unreachable", message: "npm registry returned no latest dist-tag" } };
|
|
69
|
-
return { latest };
|
|
70
|
-
} catch (err) {
|
|
71
|
-
const msg = err.name === "AbortError" ? `registry fetch timed out after ${timeoutMs}ms` : err.message;
|
|
72
|
-
return { error: { kind: "unreachable", message: msg } };
|
|
73
|
-
} finally {
|
|
74
|
-
clearTimeout(timer);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
59
|
function teachFailure(json, current, code, message, userAction) {
|
|
78
60
|
if (json) {
|
|
79
61
|
console.log(
|
|
@@ -211,23 +193,28 @@ function nudgeDisabled() {
|
|
|
211
193
|
function nudgeLine(current, latest) {
|
|
212
194
|
return isNewer(latest, current) ? `mm-harness ${current} \u2192 ${latest} available \xB7 run: mm-harness update` : null;
|
|
213
195
|
}
|
|
214
|
-
|
|
196
|
+
function maybeNudge(now = Date.now()) {
|
|
215
197
|
if (nudgeDisabled()) return;
|
|
216
198
|
const file = cacheFile();
|
|
217
199
|
const cache = readCache(file);
|
|
218
200
|
const current = currentVersion();
|
|
219
|
-
let latest = cache?.latest ?? "";
|
|
220
201
|
process.once("exit", () => {
|
|
221
|
-
const line = nudgeLine(current, latest);
|
|
202
|
+
const line = nudgeLine(current, cache?.latest ?? "");
|
|
222
203
|
if (line) process.stderr.write(`${line}
|
|
223
204
|
`);
|
|
224
205
|
});
|
|
225
206
|
if (!cache || now - cache.lastCheck >= NUDGE_INTERVAL_MS) {
|
|
226
|
-
writeCache(file, { lastCheck: now, latest });
|
|
227
|
-
const
|
|
228
|
-
if (
|
|
229
|
-
|
|
230
|
-
|
|
207
|
+
writeCache(file, { lastCheck: now, latest: cache?.latest ?? "" });
|
|
208
|
+
const worker = path.join(runnerDir, "adapters", "shared", "update-check-worker.mjs");
|
|
209
|
+
if (!fs.existsSync(worker)) return;
|
|
210
|
+
try {
|
|
211
|
+
const child = spawn(
|
|
212
|
+
process.execPath,
|
|
213
|
+
[worker, file, String(NUDGE_FETCH_TIMEOUT_MS)],
|
|
214
|
+
{ detached: true, stdio: "ignore" }
|
|
215
|
+
);
|
|
216
|
+
child.unref();
|
|
217
|
+
} catch {
|
|
231
218
|
}
|
|
232
219
|
}
|
|
233
220
|
}
|
package/dist/doctor.js
CHANGED
|
@@ -93,10 +93,7 @@ function fixtureFileSummary(file, adapter, reportedPath = file) {
|
|
|
93
93
|
const RUNTIME_CONTEXT_FIELDS = [
|
|
94
94
|
{ key: "slotId", envVars: ["RECIPE_SLOT_ID"], envVar: "RECIPE_SLOT_ID", customize: "doctor --fix creates local identity; Farmslot may override", adapters: ["mobile", "extension", "core"] },
|
|
95
95
|
{ key: "extensionId", envVars: ["RECIPE_HARNESS_EXTENSION_ID"], envVar: "RECIPE_HARNESS_EXTENSION_ID", customize: "resolved from the built extension", adapters: ["extension"] },
|
|
96
|
-
{ key: "cdpPort", envVars: ["RECIPE_CDP_PORT", "CDP_PORT"], envVar: "CDP_PORT", customize: "doctor --fix claims one; --cdp-port overrides", adapters: ["extension"] }
|
|
97
|
-
{ key: "runtimeStart.approved", envVars: ["RECIPE_RUNTIME_START_APPROVED"], envVar: "RECIPE_RUNTIME_START_APPROVED", customize: "optional orchestrator-provided legacy live context", adapters: ["mobile", "extension"] },
|
|
98
|
-
{ key: "runtimeStart.command", envVars: [], envVar: null, customize: "optional orchestrator-provided legacy live context", adapters: ["mobile", "extension"] },
|
|
99
|
-
{ key: "runtimeStart.readyUrl", envVars: ["RECIPE_RUNTIME_READY_URL"], envVar: "RECIPE_RUNTIME_READY_URL", customize: "optional orchestrator-provided legacy live context", adapters: ["mobile", "extension"] }
|
|
96
|
+
{ key: "cdpPort", envVars: ["RECIPE_CDP_PORT", "CDP_PORT"], envVar: "CDP_PORT", customize: "doctor --fix claims one; --cdp-port overrides", adapters: ["extension"] }
|
|
100
97
|
];
|
|
101
98
|
function runtimeContextSummary(target, adapter) {
|
|
102
99
|
const contextPath = resolveRuntimeContextPath(target);
|
package/dist/harness.js
CHANGED
|
@@ -4,13 +4,8 @@ import path from "node:path";
|
|
|
4
4
|
import { resolveLeafInvoke, shellLeafMissing, missingShellLeafMessage } from "./leaf-invoke.js";
|
|
5
5
|
import { resolveExtensionSlotPorts } from "./adapters/slot-ports.js";
|
|
6
6
|
import { recipeHarnessPath, recipeRuntimeDir, runnerDir } from "./paths.js";
|
|
7
|
-
|
|
8
|
-
const HARNESS_ACTIONS = ["install", "verify", "cleanup", "live"];
|
|
7
|
+
const HARNESS_ACTIONS = ["install", "verify", "cleanup"];
|
|
9
8
|
const ADAPTERS = ["mobile", "extension", "core"];
|
|
10
|
-
function isValidAdapterAction(adapter, action) {
|
|
11
|
-
if (adapter === "core") return action === "install" || action === "verify" || action === "cleanup";
|
|
12
|
-
return true;
|
|
13
|
-
}
|
|
14
9
|
function harnessUsage() {
|
|
15
10
|
console.error(`mm-harness \u2014 install and validate the MetaMask recipe runtime in a checkout.
|
|
16
11
|
|
|
@@ -167,7 +162,7 @@ function readRuntimeContextField(contextPath, field) {
|
|
|
167
162
|
if (node === void 0 || node === null || node === "") return void 0;
|
|
168
163
|
return String(node);
|
|
169
164
|
}
|
|
170
|
-
function applyExtensionRuntimeEnv(target,
|
|
165
|
+
function applyExtensionRuntimeEnv(target, args) {
|
|
171
166
|
const contextPath = resolveRuntimeContextPath(target);
|
|
172
167
|
const contextExists = fs.existsSync(contextPath);
|
|
173
168
|
if (contextExists) {
|
|
@@ -180,19 +175,6 @@ function applyExtensionRuntimeEnv(target, action, args) {
|
|
|
180
175
|
const extensionId = readRuntimeContextField(contextPath, "extensionId");
|
|
181
176
|
if (extensionId) process.env.RECIPE_HARNESS_EXTENSION_ID = extensionId;
|
|
182
177
|
}
|
|
183
|
-
if (process.env.RECIPE_RUNTIME_START_APPROVED === void 0) {
|
|
184
|
-
const approved = readRuntimeContextField(contextPath, "runtimeStart.approved");
|
|
185
|
-
if (approved === "true" || approved === "True" || approved === "1") process.env.RECIPE_RUNTIME_START_APPROVED = "1";
|
|
186
|
-
else if (approved === "false" || approved === "False" || approved === "0") process.env.RECIPE_RUNTIME_START_APPROVED = "0";
|
|
187
|
-
}
|
|
188
|
-
if (!process.env.RECIPE_RUNTIME_START_CMD) {
|
|
189
|
-
const command = readRuntimeContextField(contextPath, "runtimeStart.command");
|
|
190
|
-
if (command) process.env.RECIPE_RUNTIME_START_CMD = command;
|
|
191
|
-
}
|
|
192
|
-
if (!process.env.RECIPE_RUNTIME_READY_URL) {
|
|
193
|
-
const readyUrl = readRuntimeContextField(contextPath, "runtimeStart.readyUrl");
|
|
194
|
-
if (readyUrl) process.env.RECIPE_RUNTIME_READY_URL = readyUrl;
|
|
195
|
-
}
|
|
196
178
|
} else {
|
|
197
179
|
delete process.env.RECIPE_RUNTIME_CONTEXT;
|
|
198
180
|
}
|
|
@@ -206,11 +188,6 @@ function applyExtensionRuntimeEnv(target, action, args) {
|
|
|
206
188
|
result = [...result, "--cdp-port", cdpPort];
|
|
207
189
|
}
|
|
208
190
|
}
|
|
209
|
-
if (action === "live" && !hasArg(result, "--prepare-cmd")) {
|
|
210
|
-
if (process.env.RECIPE_RUNTIME_START_APPROVED === "1" && process.env.RECIPE_RUNTIME_START_CMD) {
|
|
211
|
-
result = [...result, "--prepare-cmd", process.env.RECIPE_RUNTIME_START_CMD];
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
191
|
return result;
|
|
215
192
|
}
|
|
216
193
|
function resolveEntry(base, candidates, mode) {
|
|
@@ -287,82 +264,6 @@ Next: mm-harness install --platform core --target ${target}`
|
|
|
287
264
|
);
|
|
288
265
|
return { command, prefixArgs: [] };
|
|
289
266
|
}
|
|
290
|
-
async function handleMobileLive(target, forwardArgs, json, autoDetected) {
|
|
291
|
-
const platform = argValue(forwardArgs, "--platform") ?? "ios";
|
|
292
|
-
const watcherPortStr = argValue(forwardArgs, "--watcher-port") ?? process.env.WATCHER_PORT;
|
|
293
|
-
const watcherPort = watcherPortStr ? parseInt(watcherPortStr, 10) : void 0;
|
|
294
|
-
const start = Date.now();
|
|
295
|
-
if (!json) {
|
|
296
|
-
const detected = autoDetected ? ", auto-detected" : "";
|
|
297
|
-
console.error(`\u2192 live (mobile${detected}) \u2014 target: ${target}`);
|
|
298
|
-
}
|
|
299
|
-
const prepResult = await prepareMobile(target, { platform, json, watcherPort });
|
|
300
|
-
if (prepResult.status !== 0) {
|
|
301
|
-
const elapsed2 = ((Date.now() - start) / 1e3).toFixed(1);
|
|
302
|
-
if (json) {
|
|
303
|
-
console.log(
|
|
304
|
-
harnessSummary("live", "mobile", target, "fail", prepResult.status, autoDetected, {
|
|
305
|
-
code: "MOBILE_PREPARE_FAILED",
|
|
306
|
-
message: `mobile prepare failed (exit ${prepResult.status})`,
|
|
307
|
-
userAction: failureHint("mobile", "live")
|
|
308
|
-
})
|
|
309
|
-
);
|
|
310
|
-
} else {
|
|
311
|
-
console.error(
|
|
312
|
-
`\u2717 live mobile failed (exit ${prepResult.status}, ${elapsed2}s)
|
|
313
|
-
${failureHint("mobile", "live")}`
|
|
314
|
-
);
|
|
315
|
-
}
|
|
316
|
-
return prepResult.status;
|
|
317
|
-
}
|
|
318
|
-
const installedVerify = path.join(recipeHarnessPath(target, "mobile"), "scripts", "verify.sh");
|
|
319
|
-
const verifySh = fs.existsSync(installedVerify) ? installedVerify : resolveEntry(runnerDir, ["adapters/mobile/verify.sh", "scripts/mobile/verify.sh"], "file");
|
|
320
|
-
const verifyArgs = hasArg(forwardArgs, "--no-auto-start") ? [...forwardArgs] : ["--no-auto-start", ...forwardArgs];
|
|
321
|
-
if (shellLeafMissing(verifySh)) {
|
|
322
|
-
const message = missingShellLeafMessage(verifySh);
|
|
323
|
-
if (json) {
|
|
324
|
-
console.log(
|
|
325
|
-
harnessSummary("live", "mobile", target, "fail", 1, autoDetected, {
|
|
326
|
-
code: "HARNESS_SPAWN_FAILED",
|
|
327
|
-
message,
|
|
328
|
-
userAction: failureHint("mobile", "live")
|
|
329
|
-
})
|
|
330
|
-
);
|
|
331
|
-
} else {
|
|
332
|
-
console.error(`\u2717 ${message}`);
|
|
333
|
-
}
|
|
334
|
-
return 1;
|
|
335
|
-
}
|
|
336
|
-
const { bin: verifyBin, args: verifySpawnArgs } = resolveLeafInvoke(verifySh, verifyArgs);
|
|
337
|
-
const result = spawnSync(verifyBin, verifySpawnArgs, {
|
|
338
|
-
stdio: json ? ["inherit", 2, "inherit"] : "inherit",
|
|
339
|
-
env: process.env
|
|
340
|
-
});
|
|
341
|
-
const elapsed = ((Date.now() - start) / 1e3).toFixed(1);
|
|
342
|
-
const exitCode = result.status ?? 1;
|
|
343
|
-
if (json) {
|
|
344
|
-
console.log(
|
|
345
|
-
harnessSummary(
|
|
346
|
-
"live",
|
|
347
|
-
"mobile",
|
|
348
|
-
target,
|
|
349
|
-
exitCode === 0 ? "pass" : "fail",
|
|
350
|
-
exitCode,
|
|
351
|
-
autoDetected,
|
|
352
|
-
exitCode === 0 ? void 0 : { code: "MOBILE_VERIFY_FAILED", message: `mobile live verify failed (exit ${exitCode})`, userAction: failureHint("mobile", "live") }
|
|
353
|
-
)
|
|
354
|
-
);
|
|
355
|
-
} else if (exitCode === 0) {
|
|
356
|
-
console.error(`\u2713 live mobile passed (${elapsed}s)
|
|
357
|
-
Next: ${successNext("mobile", "live", target)}`);
|
|
358
|
-
} else {
|
|
359
|
-
console.error(
|
|
360
|
-
`\u2717 live mobile failed (exit ${exitCode}, ${elapsed}s)
|
|
361
|
-
${failureHint("mobile", "live")}`
|
|
362
|
-
);
|
|
363
|
-
}
|
|
364
|
-
return exitCode;
|
|
365
|
-
}
|
|
366
267
|
async function handleHarness(argv) {
|
|
367
268
|
const action = argv[0];
|
|
368
269
|
if (!action || action === "-h" || action === "--help") {
|
|
@@ -394,32 +295,13 @@ async function handleHarness(argv) {
|
|
|
394
295
|
}
|
|
395
296
|
return 2;
|
|
396
297
|
}
|
|
397
|
-
if (!isValidAdapterAction(adapter, harnessAction)) {
|
|
398
|
-
if (json) {
|
|
399
|
-
console.log(
|
|
400
|
-
harnessSummary(harnessAction, adapter, target, "fail", 2, parsedAdapter === void 0, {
|
|
401
|
-
code: "UNSUPPORTED_ACTION",
|
|
402
|
-
message: `${harnessAction} is not supported for ${adapter} (core supports install, verify, cleanup).`,
|
|
403
|
-
userAction: "run mm-harness doctor to see which actions are supported for this adapter"
|
|
404
|
-
})
|
|
405
|
-
);
|
|
406
|
-
} else {
|
|
407
|
-
console.error(
|
|
408
|
-
`\u2717 ${harnessAction} is not supported for ${adapter} (core supports install, verify, cleanup).`
|
|
409
|
-
);
|
|
410
|
-
}
|
|
411
|
-
return 2;
|
|
412
|
-
}
|
|
413
298
|
const autoDetected = parsedAdapter === void 0;
|
|
414
299
|
let forwardArgs = hasArg(forward, "--target") ? [...forward] : ["--target", target, ...forward];
|
|
415
300
|
if (harnessAction === "install" && hasArg(forward, "--runway")) {
|
|
416
301
|
return handleRunwayInstall(adapter, target, forward, json);
|
|
417
302
|
}
|
|
418
|
-
if (adapter === "extension" &&
|
|
419
|
-
forwardArgs = applyExtensionRuntimeEnv(target,
|
|
420
|
-
}
|
|
421
|
-
if (adapter === "mobile" && harnessAction === "live") {
|
|
422
|
-
return handleMobileLive(target, forwardArgs, json, autoDetected);
|
|
303
|
+
if (adapter === "extension" && harnessAction === "verify") {
|
|
304
|
+
forwardArgs = applyExtensionRuntimeEnv(target, forwardArgs);
|
|
423
305
|
}
|
|
424
306
|
const dispatch = resolveHarnessDispatch(adapter, harnessAction, target);
|
|
425
307
|
if ("error" in dispatch) {
|
|
@@ -587,7 +469,7 @@ function harnessSummary(action, adapter, target, status, exitCode, autoDetected,
|
|
|
587
469
|
status,
|
|
588
470
|
exitCode,
|
|
589
471
|
// Error contract: every --json failure carries a stable machine code + human
|
|
590
|
-
// message
|
|
472
|
+
// message. userAction is included when present so callers
|
|
591
473
|
// can surface the reachable escape without parsing the human message.
|
|
592
474
|
...status === "fail" && error ? { error } : {},
|
|
593
475
|
...status === "pass" && adapter ? { next: successNext(adapter, action, target) } : {}
|
|
@@ -46,29 +46,16 @@ function candidatePaths(platform, action) {
|
|
|
46
46
|
}
|
|
47
47
|
pushDomainDispatcherFiles(files, root, platform, family);
|
|
48
48
|
}
|
|
49
|
-
for (const candidateStem of stems) {
|
|
50
|
-
pushFlatCandidateFiles(files, root, platform, candidateStem);
|
|
51
|
-
}
|
|
52
49
|
}
|
|
53
50
|
return files;
|
|
54
51
|
}
|
|
55
52
|
function pushCandidateFiles(files, root, platform, family, stem) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
files.push(path.join(root, "shared", family, `${stem}.${extension}`));
|
|
59
|
-
}
|
|
53
|
+
files.push(path.join(root, platform, family, `${stem}.mjs`));
|
|
54
|
+
files.push(path.join(root, "shared", family, `${stem}.mjs`));
|
|
60
55
|
}
|
|
61
56
|
function pushDomainDispatcherFiles(files, root, platform, family) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
files.push(path.join(root, "shared", family, `${family}.${extension}`));
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
function pushFlatCandidateFiles(files, root, platform, stem) {
|
|
68
|
-
for (const extension of ["mjs", "js", "sh"]) {
|
|
69
|
-
files.push(path.join(root, platform, `${stem}.${extension}`));
|
|
70
|
-
files.push(path.join(root, "shared", `${stem}.${extension}`));
|
|
71
|
-
}
|
|
57
|
+
files.push(path.join(root, platform, family, `${family}.mjs`));
|
|
58
|
+
files.push(path.join(root, "shared", family, `${family}.mjs`));
|
|
72
59
|
}
|
|
73
60
|
async function firstExecutablePath(paths) {
|
|
74
61
|
for (const file of paths) {
|
|
@@ -120,9 +107,8 @@ function runProcess(command, args, options) {
|
|
|
120
107
|
});
|
|
121
108
|
}
|
|
122
109
|
function commandFor(file, projectRoot, platform) {
|
|
123
|
-
if (file.endsWith(".sh")) return { command: "bash", args: [file] };
|
|
124
110
|
const needsTsx = platform === "core" || importsSourceTypescript(file);
|
|
125
|
-
if (!needsTsx
|
|
111
|
+
if (!needsTsx) {
|
|
126
112
|
return { command: process.execPath, args: [file] };
|
|
127
113
|
}
|
|
128
114
|
const tsxBin = resolveTsxBin(projectRoot);
|
|
@@ -165,7 +151,7 @@ function importsSourceTypescriptFrom(file, visited) {
|
|
|
165
151
|
const specifier = match[1] ?? "";
|
|
166
152
|
if (specifier.endsWith(".ts")) return true;
|
|
167
153
|
if (!specifier.startsWith(".")) continue;
|
|
168
|
-
if (!specifier.endsWith(".mjs")
|
|
154
|
+
if (!specifier.endsWith(".mjs")) continue;
|
|
169
155
|
if (importsSourceTypescriptFrom(path.resolve(path.dirname(absolute), specifier), visited)) {
|
|
170
156
|
return true;
|
|
171
157
|
}
|
package/dist/mm-harness-cli.js
CHANGED
|
@@ -424,8 +424,8 @@ Example:
|
|
|
424
424
|
--full Raw log tail (default = compact) (env: RECIPE_LOG_UI)
|
|
425
425
|
--window (Re)open the read-only tmux tail window for the resolved dev-server port (leaves the process untouched)
|
|
426
426
|
--events <n> Compact event count (default 10) (env: RECIPE_LOG_EVENTS)
|
|
427
|
-
--source <label> Log source per adapter \u2014 mobile: metro|app
|
|
428
|
-
extension: webpack|watcher|rebuild
|
|
427
|
+
--source <label> Log source per adapter \u2014 mobile: metro|app;
|
|
428
|
+
extension: extension|dapp|webpack|watcher|rebuild.
|
|
429
429
|
Core is headless (teaching error).
|
|
430
430
|
--adapter <mobile|extension|core> Target adapter (auto-detected inside a checkout)
|
|
431
431
|
--target <path> Checkout path (default: cwd)
|
|
@@ -541,21 +541,6 @@ function assertPublicContractMatchesSurface() {
|
|
|
541
541
|
}
|
|
542
542
|
}
|
|
543
543
|
assertPublicContractMatchesSurface();
|
|
544
|
-
const RETIRED = [
|
|
545
|
-
{
|
|
546
|
-
name: "live",
|
|
547
|
-
message: `mm-harness live is retired (exit 2).
|
|
548
|
-
|
|
549
|
-
Replacement: mm-harness launch --verify (install overlay \u2192 launch \u2192 CDP/bridge poll \u2192 smoke verify).`
|
|
550
|
-
},
|
|
551
|
-
{
|
|
552
|
-
name: "manifest",
|
|
553
|
-
message: `mm-harness manifest is retired (exit 2).
|
|
554
|
-
|
|
555
|
-
Replacement: mm-harness actions --raw (works now \u2014 dumps the raw action registry JSON,
|
|
556
|
-
identical to the old \`manifest --json\`). Manifest validation moved into doctor / run --plan.`
|
|
557
|
-
}
|
|
558
|
-
];
|
|
559
544
|
const HELP_GROUPS = [
|
|
560
545
|
{
|
|
561
546
|
title: "DAILY LOOP",
|
|
@@ -658,7 +643,7 @@ function groupedHelp() {
|
|
|
658
643
|
lines.push("DEV/PROD \u2014 the global npm install is prod; set MM_HARNESS_BIN to a dev checkout to override:");
|
|
659
644
|
lines.push(" MM_HARNESS_BIN=/path/to/checkout/bin/mm-harness # unset = the installed/global bin");
|
|
660
645
|
lines.push("");
|
|
661
|
-
lines.push("See
|
|
646
|
+
lines.push("See README.md for the workflow and docs/CHEATSHEET.md for examples.");
|
|
662
647
|
return `${lines.join("\n")}
|
|
663
648
|
`;
|
|
664
649
|
}
|
|
@@ -670,11 +655,6 @@ async function delegate(argv) {
|
|
|
670
655
|
return error !== null && typeof error === "object" && "exitCode" in error && typeof error.exitCode === "number" ? error.exitCode : 1;
|
|
671
656
|
}
|
|
672
657
|
}
|
|
673
|
-
function translateActionsRaw(argv) {
|
|
674
|
-
const rest = argv.slice(1).filter((arg) => arg !== "--raw");
|
|
675
|
-
const withJson = rest.includes("--json") ? rest : [...rest, "--json"];
|
|
676
|
-
return ["manifest", ...withJson];
|
|
677
|
-
}
|
|
678
658
|
const pkgVersion = (() => {
|
|
679
659
|
try {
|
|
680
660
|
return JSON.parse(fs.readFileSync(path.join(packageRoot, "package.json"), "utf8")).version ?? "unknown";
|
|
@@ -692,8 +672,7 @@ for (const command of REAL) {
|
|
|
692
672
|
if (command.name === "update") {
|
|
693
673
|
process.exit(await handleUpdate(rawArgv.slice(1)));
|
|
694
674
|
}
|
|
695
|
-
|
|
696
|
-
process.exit(await withCommandJournal(command.name, rawArgv, () => delegate(argv)));
|
|
675
|
+
process.exit(await withCommandJournal(command.name, rawArgv, () => delegate(rawArgv)));
|
|
697
676
|
});
|
|
698
677
|
}
|
|
699
678
|
const HIDDEN = [
|
|
@@ -715,24 +694,9 @@ program.command("completions").description("Install/print bundled shell tab-comp
|
|
|
715
694
|
const result = spawnSync("bash", [script, ...rawArgv.slice(1)], { stdio: "inherit" });
|
|
716
695
|
process.exit(result.status ?? 1);
|
|
717
696
|
});
|
|
718
|
-
for (const retired of RETIRED) {
|
|
719
|
-
program.command(retired.name).description(`${retired.name} is retired (see teaching error)`).allowUnknownOption().helpOption(false).argument("[args...]").action(async () => {
|
|
720
|
-
if (retired.name === "live") {
|
|
721
|
-
const adapterIdx = rawArgv.indexOf("--adapter");
|
|
722
|
-
const aliasIdx = rawArgv.indexOf("-a");
|
|
723
|
-
const adapterVal = adapterIdx !== -1 ? rawArgv[adapterIdx + 1] : aliasIdx !== -1 ? rawArgv[aliasIdx + 1] : void 0;
|
|
724
|
-
if (adapterVal === "mobile" || adapterVal === "android") {
|
|
725
|
-
process.exit(await delegate(rawArgv));
|
|
726
|
-
return;
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
console.error(retired.message);
|
|
730
|
-
process.exit(2);
|
|
731
|
-
});
|
|
732
|
-
}
|
|
733
697
|
const NUDGE_SKIP = ["update", "completions", "completion-candidates"];
|
|
734
698
|
if (rawArgv.length > 0 && !NUDGE_SKIP.includes(rawArgv[0])) {
|
|
735
|
-
|
|
699
|
+
maybeNudge();
|
|
736
700
|
}
|
|
737
701
|
function hasPassthroughHelp(argv) {
|
|
738
702
|
const divider = argv.indexOf("--");
|
|
@@ -794,8 +758,7 @@ if (rawArgv.length === 0) {
|
|
|
794
758
|
}
|
|
795
759
|
const preflightBypass = /* @__PURE__ */ new Set([
|
|
796
760
|
...HIDDEN,
|
|
797
|
-
"completions"
|
|
798
|
-
...RETIRED.map((command) => command.name)
|
|
761
|
+
"completions"
|
|
799
762
|
]);
|
|
800
763
|
if (!preflightBypass.has(rawArgv[0] ?? "")) {
|
|
801
764
|
const usageError = validatePublicInvocation(rawArgv, PUBLIC_COMMAND_EXAMPLES);
|
package/dist/paths.js
CHANGED
|
@@ -47,10 +47,7 @@ function recipeWatchLogCandidates() {
|
|
|
47
47
|
}
|
|
48
48
|
function resolveLocalProtocolRoot() {
|
|
49
49
|
const candidates = [
|
|
50
|
-
// Orchestrator-neutral name is primary; FARMSLOT_ROOT stays a back-compat alias
|
|
51
|
-
// for one release (the installer injects METAMASK_RUNNER_PROTOCOL_ROOT).
|
|
52
50
|
process.env.METAMASK_RUNNER_PROTOCOL_ROOT,
|
|
53
|
-
process.env.FARMSLOT_ROOT,
|
|
54
51
|
readConfiguredProtocolRoot(),
|
|
55
52
|
findProtocolRoot(runnerDir),
|
|
56
53
|
findProtocolRoot(process.cwd())
|
|
@@ -62,7 +59,7 @@ function resolveRequiredLocalProtocolRoot(reason) {
|
|
|
62
59
|
const root = resolveLocalProtocolRoot();
|
|
63
60
|
if (!root) {
|
|
64
61
|
throw new Error(
|
|
65
|
-
`${reason} requires a local protocol/runtime checkout. Set METAMASK_RUNNER_PROTOCOL_ROOT
|
|
62
|
+
`${reason} requires a local protocol/runtime checkout. Set METAMASK_RUNNER_PROTOCOL_ROOT or create .farmslot-root for this dev-only path.`
|
|
66
63
|
);
|
|
67
64
|
}
|
|
68
65
|
return root;
|
|
@@ -152,7 +149,7 @@ async function importProtocolPackage(packageName, localSourceEntry) {
|
|
|
152
149
|
const root = resolveLocalProtocolRoot();
|
|
153
150
|
if (!root) {
|
|
154
151
|
throw new Error(
|
|
155
|
-
`${packageName} is not installed. Install @farmslot/* packages normally, or set
|
|
152
|
+
`${packageName} is not installed. Install @farmslot/* packages normally, or set METAMASK_RUNNER_PROTOCOL_ROOT/use npm run dev:link-farmslot while co-developing protocol packages locally.`
|
|
156
153
|
);
|
|
157
154
|
}
|
|
158
155
|
return import(pathToFileURL(path.join(root, localSourceEntry)).href);
|
package/dist/run-diagnostics.js
CHANGED
|
@@ -1,26 +1,54 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { createHash } from "node:crypto";
|
|
4
|
-
import {
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
5
|
import { getAdapterSurface } from "./adapters/surface.js";
|
|
6
6
|
import { runnerDir } from "./paths.js";
|
|
7
|
+
import { ensureExtensionConsoleCapture } from "./adapters/extension/console-capture.js";
|
|
7
8
|
const MAX_CAPTURE_BYTES = 512 * 1024;
|
|
8
9
|
const MAX_FINDINGS = 20;
|
|
9
10
|
const MAX_PREVIEW_CHARS = 320;
|
|
11
|
+
function readRunDiagnosticsDocument(diagnosticsPath) {
|
|
12
|
+
if (typeof diagnosticsPath !== "string" || diagnosticsPath.length === 0) return null;
|
|
13
|
+
try {
|
|
14
|
+
const document = JSON.parse(fs.readFileSync(diagnosticsPath, "utf8"));
|
|
15
|
+
return isRunDiagnosticsDocument(document) ? document : null;
|
|
16
|
+
} catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function formatRunDiagnosticsForHuman(diagnostics, adapter) {
|
|
21
|
+
if (!diagnostics) {
|
|
22
|
+
return [adapter === "core" ? "N/A \u2014 Core is headless and has no application log source." : "UNAVAILABLE \u2014 application diagnostics could not be collected for this run."];
|
|
23
|
+
}
|
|
24
|
+
const status = diagnostics.status === "clean" ? "CLEAN" : diagnostics.status === "review" ? "REVIEW" : "UNAVAILABLE";
|
|
25
|
+
const lines = [`${status} \u2014 ${diagnostics.note}`];
|
|
26
|
+
for (const finding of diagnostics.findings) {
|
|
27
|
+
const count = finding.count > 1 ? ` \xD7${finding.count}` : "";
|
|
28
|
+
lines.push(`${finding.level.toUpperCase()}${count} \u2014 ${finding.preview}`);
|
|
29
|
+
}
|
|
30
|
+
return lines;
|
|
31
|
+
}
|
|
10
32
|
async function beginRunDiagnostics(adapter, projectRoot) {
|
|
11
33
|
const source = getAdapterSurface(adapter).appLogSource(projectRoot);
|
|
12
34
|
if (!source) return null;
|
|
13
35
|
const mobileIssueBuffer = adapter === "mobile" && armMobileIssueBuffer(projectRoot) ? { projectRoot } : void 0;
|
|
14
|
-
|
|
36
|
+
if (adapter === "extension") {
|
|
37
|
+
await ensureExtensionConsoleCapture(projectRoot).catch(() => void 0);
|
|
38
|
+
}
|
|
15
39
|
const stat = safeStat(source.path);
|
|
16
40
|
return {
|
|
17
41
|
source,
|
|
18
42
|
offset: stat?.size ?? 0,
|
|
19
43
|
...stat ? { inode: stat.ino } : {},
|
|
20
|
-
...mobileIssueBuffer ? { mobileIssueBuffer } : {}
|
|
21
|
-
...extensionConsoleCapture ? { extensionConsoleCapture } : {}
|
|
44
|
+
...mobileIssueBuffer ? { mobileIssueBuffer } : {}
|
|
22
45
|
};
|
|
23
46
|
}
|
|
47
|
+
function isRunDiagnosticsDocument(value) {
|
|
48
|
+
if (!isRecord(value) || value.schemaVersion !== 1 || value.scope !== "recipe-run-application") return false;
|
|
49
|
+
if (value.status !== "clean" && value.status !== "review" && value.status !== "unavailable") return false;
|
|
50
|
+
return typeof value.note === "string" && Array.isArray(value.findings);
|
|
51
|
+
}
|
|
24
52
|
function finishRunDiagnostics(baseline, result) {
|
|
25
53
|
if (!baseline) return result;
|
|
26
54
|
try {
|
|
@@ -56,9 +84,7 @@ function finishRunDiagnostics(baseline, result) {
|
|
|
56
84
|
}
|
|
57
85
|
}
|
|
58
86
|
function stopRunDiagnostics(baseline) {
|
|
59
|
-
|
|
60
|
-
if (!capture || capture.exitCode !== null || capture.signalCode !== null) return;
|
|
61
|
-
capture.kill("SIGTERM");
|
|
87
|
+
void baseline;
|
|
62
88
|
}
|
|
63
89
|
function collectRunDiagnostics(baseline, bufferedIssues) {
|
|
64
90
|
const stat = safeStat(baseline.source.path);
|
|
@@ -157,39 +183,6 @@ function runMobileIssueCommand(projectRoot, command) {
|
|
|
157
183
|
return null;
|
|
158
184
|
}
|
|
159
185
|
}
|
|
160
|
-
async function startExtensionConsoleCapture(source, projectRoot) {
|
|
161
|
-
const cdpPort = process.env.CDP_PORT ?? process.env.RECIPE_CDP_PORT;
|
|
162
|
-
if (!cdpPort) return void 0;
|
|
163
|
-
fs.mkdirSync(path.dirname(source.path), { recursive: true });
|
|
164
|
-
const before = safeStat(source.path)?.size ?? 0;
|
|
165
|
-
const script = path.join(runnerDir, "adapters", "extension", "console-tail.mjs");
|
|
166
|
-
const child = spawn(process.execPath, [script, "--cdp-port", cdpPort, "--log", source.path], {
|
|
167
|
-
cwd: projectRoot,
|
|
168
|
-
stdio: "ignore"
|
|
169
|
-
});
|
|
170
|
-
const attached = await waitForExtensionAttachment(source.path, before, child, 3e3);
|
|
171
|
-
if (attached) {
|
|
172
|
-
await delay(50);
|
|
173
|
-
return child;
|
|
174
|
-
}
|
|
175
|
-
if (child.exitCode === null && child.signalCode === null) child.kill("SIGTERM");
|
|
176
|
-
return void 0;
|
|
177
|
-
}
|
|
178
|
-
async function waitForExtensionAttachment(logPath, offset, child, timeoutMs) {
|
|
179
|
-
const deadline = Date.now() + timeoutMs;
|
|
180
|
-
while (Date.now() < deadline && child.exitCode === null && child.signalCode === null) {
|
|
181
|
-
const stat = safeStat(logPath);
|
|
182
|
-
if (stat && stat.size > offset) {
|
|
183
|
-
const appended = readSlice(logPath, offset, Math.min(stat.size - offset, 64 * 1024));
|
|
184
|
-
if (appended.includes("[attached]")) return true;
|
|
185
|
-
}
|
|
186
|
-
await delay(50);
|
|
187
|
-
}
|
|
188
|
-
return false;
|
|
189
|
-
}
|
|
190
|
-
function delay(ms) {
|
|
191
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
192
|
-
}
|
|
193
186
|
function redactPreview(value) {
|
|
194
187
|
return value.replace(/\b(Bearer)\s+\S+/giu, "$1 [REDACTED]").replace(/\b(password|passphrase|mnemonic|seed(?:Phrase)?|privateKey|secret|token|authorization)\b\s*[:=]\s*(?:"[^"]*"|'[^']*'|\S+)/giu, "$1=[REDACTED]").replace(/\b(?:0x)?[a-f0-9]{64,}\b/giu, "[REDACTED_HEX]").replace(/(https?:\/\/[^\s?]+)\?\S+/giu, "$1?[REDACTED_QUERY]").slice(0, MAX_PREVIEW_CHARS);
|
|
195
188
|
}
|
|
@@ -267,5 +260,7 @@ export {
|
|
|
267
260
|
beginRunDiagnostics,
|
|
268
261
|
collectRunDiagnostics,
|
|
269
262
|
finishRunDiagnostics,
|
|
263
|
+
formatRunDiagnosticsForHuman,
|
|
264
|
+
readRunDiagnosticsDocument,
|
|
270
265
|
stopRunDiagnostics
|
|
271
266
|
};
|
package/dist/runtime-context.js
CHANGED
|
@@ -53,6 +53,32 @@ function readExistingContext(contextPath, repoRoot, runtimeDir, adapter) {
|
|
|
53
53
|
return { context, existed: true };
|
|
54
54
|
}
|
|
55
55
|
function validExistingContext(context, repoRoot, runtimeDir, adapter) {
|
|
56
|
+
const allowedFields = /* @__PURE__ */ new Set([
|
|
57
|
+
"schemaVersion",
|
|
58
|
+
"repoRoot",
|
|
59
|
+
"project",
|
|
60
|
+
"slotId",
|
|
61
|
+
"machine",
|
|
62
|
+
"runtimeOwner",
|
|
63
|
+
"strict",
|
|
64
|
+
"platform",
|
|
65
|
+
"runtimeDir",
|
|
66
|
+
"gitDirty",
|
|
67
|
+
"simulator",
|
|
68
|
+
"adbSerial",
|
|
69
|
+
"distDir",
|
|
70
|
+
"extensionId",
|
|
71
|
+
"gitHead",
|
|
72
|
+
"gitBranch",
|
|
73
|
+
"gitCommit",
|
|
74
|
+
"createdAt",
|
|
75
|
+
"session",
|
|
76
|
+
"cdpPort",
|
|
77
|
+
"watcherPort",
|
|
78
|
+
"metroPort",
|
|
79
|
+
"devServerPort"
|
|
80
|
+
]);
|
|
81
|
+
if (Object.keys(context).some((field) => !allowedFields.has(field))) return false;
|
|
56
82
|
if (context.schemaVersion !== void 0 && context.schemaVersion !== 1) return false;
|
|
57
83
|
if (context.repoRoot !== void 0 && (typeof context.repoRoot !== "string" || path.resolve(context.repoRoot) !== repoRoot)) return false;
|
|
58
84
|
if (context.platform !== void 0 && context.platform !== adapter) return false;
|
|
@@ -82,14 +108,6 @@ function validExistingContext(context, repoRoot, runtimeDir, adapter) {
|
|
|
82
108
|
const value = context[field];
|
|
83
109
|
if (value !== void 0 && (!Number.isInteger(value) || Number(value) < 1 || Number(value) > 65535)) return false;
|
|
84
110
|
}
|
|
85
|
-
if (context.runtimeStart !== void 0) {
|
|
86
|
-
if (!context.runtimeStart || typeof context.runtimeStart !== "object" || Array.isArray(context.runtimeStart)) return false;
|
|
87
|
-
const runtimeStart = context.runtimeStart;
|
|
88
|
-
if (runtimeStart.approved !== void 0 && typeof runtimeStart.approved !== "boolean") return false;
|
|
89
|
-
for (const field of ["command", "readyUrl"]) {
|
|
90
|
-
if (runtimeStart[field] !== void 0 && (typeof runtimeStart[field] !== "string" || runtimeStart[field].length === 0)) return false;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
111
|
const forbiddenResources = adapter === "core" ? ["cdpPort", "watcherPort", "metroPort", "devServerPort", "simulator", "adbSerial", "extensionId"] : adapter === "extension" ? ["metroPort", "simulator", "adbSerial"] : ["cdpPort", "extensionId"];
|
|
94
112
|
if (forbiddenResources.some((field) => context[field] !== void 0)) return false;
|
|
95
113
|
return true;
|