@deeeed/metamask-harness 0.17.5 → 0.19.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 +1059 -0
- package/README.md +91 -140
- package/adapters/core/inject.sh +1 -6
- package/adapters/extension/console-tail.mjs +55 -24
- package/adapters/extension/ensure-browser.sh +6 -1
- package/adapters/extension/inject.mjs +7 -13
- package/adapters/extension/launch-browser.cjs +7 -1
- package/adapters/extension/launch-webpack.cjs +83 -0
- package/adapters/extension/launch.sh +8 -11
- package/adapters/extension/lib/chrome-args.cjs +8 -1
- package/adapters/extension/lib/macos-focus.cjs +32 -0
- package/adapters/extension/live.sh +12 -21
- package/adapters/extension/reattach.sh +1 -0
- package/adapters/extension/seed-fixture.sh +4 -12
- package/adapters/extension/sidepanel-toggle.sh +4 -1
- package/adapters/extension/stamp-runtime-title.cjs +58 -0
- package/adapters/extension/start-watch.sh +17 -4
- package/adapters/extension/stop-viewers.sh +1 -1
- package/adapters/extension/sync-webpack-dist.cjs +107 -0
- package/adapters/extension/verify.sh +2 -2
- package/adapters/manifest.json +53 -37
- package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +16 -0
- package/adapters/mobile/bridge-runtime/console-forwarder.cjs +1 -1
- package/adapters/mobile/bridge-runtime/lib/match-bridge-target.cjs +3 -1
- package/adapters/mobile/bridge-runtime/lib/target-discovery.cjs +2 -3
- package/adapters/mobile/cleanup.sh +16 -176
- package/adapters/mobile/inject.sh +91 -671
- package/adapters/mobile/launch-console-forwarder.cjs +39 -0
- package/adapters/mobile/open-device.sh +45 -7
- package/adapters/mobile/start-console-forwarder.sh +70 -0
- package/adapters/mobile/start-metro.sh +0 -41
- package/adapters/mobile/verify.sh +34 -83
- package/adapters/mobile/wait-for-bridge.sh +22 -8
- package/adapters/shared/harness-source-fingerprint.mjs +14 -12
- package/adapters/shared/resolve-slot-ports-core.mjs +17 -6
- package/adapters/shared/resolve-slot-ports.sh +17 -5
- package/adapters/shared/update-check-worker.mjs +43 -0
- package/bin/mm-harness +21 -2
- package/dist/adapters/core/surface.js +4 -1
- package/dist/adapters/extension/console-capture.js +95 -0
- package/dist/adapters/extension/product-config.js +110 -0
- package/dist/adapters/extension/runtime-decision.js +20 -2
- package/dist/adapters/extension/surface.js +22 -3
- package/dist/adapters/harness-freshness.js +49 -0
- package/dist/adapters/mobile/perps-env.js +43 -3
- package/dist/adapters/mobile/prepare.js +39 -7
- package/dist/adapters/mobile/surface.js +5 -2
- package/dist/adapters/resolve-slot-ports.js +2 -2
- package/dist/adapters/slot-ports.js +13 -16
- package/dist/adapters.js +56 -16
- package/dist/checkout-lock.js +27 -2
- package/dist/cli-color.js +19 -0
- package/dist/cli-commands.js +1 -1
- package/dist/cli.js +7 -18
- package/dist/command-contract.js +462 -0
- package/dist/command-journal.js +263 -0
- package/dist/commands/call.js +252 -57
- package/dist/commands/check.js +10 -4
- package/dist/commands/completion-candidates.js +17 -22
- package/dist/commands/device-target.js +27 -12
- package/dist/commands/doctor.js +106 -25
- package/dist/commands/fixtures.js +142 -35
- package/dist/commands/last.js +60 -0
- package/dist/commands/launch/extension.js +40 -17
- package/dist/commands/launch/index.js +207 -63
- package/dist/commands/launch/mobile.js +2 -0
- package/dist/commands/list-executables.js +187 -29
- package/dist/commands/logs.js +8 -6
- package/dist/commands/manifest.js +286 -35
- package/dist/commands/parse-args.js +68 -4
- package/dist/commands/provision.js +10 -4
- package/dist/commands/run-engine.js +772 -264
- package/dist/commands/run-report.js +12 -3
- package/dist/commands/run.js +378 -85
- package/dist/commands/shared.js +76 -4
- package/dist/commands/status-probe.js +4 -1
- package/dist/commands/status.js +2 -1
- package/dist/commands/stop.js +7 -2
- package/dist/commands/update.js +16 -28
- package/dist/completions-cache.js +1 -1
- package/dist/doctor.js +57 -10
- package/dist/harness.js +47 -137
- package/dist/heal-bounds.js +7 -2
- package/dist/json-stream.js +57 -0
- package/dist/live-adapter-contract.js +138 -32
- package/dist/manifest.js +161 -1
- package/dist/mm-harness-cli.js +146 -73
- package/dist/paths.js +2 -5
- package/dist/recipe-security.js +178 -0
- package/dist/run-diagnostics.js +261 -0
- package/dist/run-recording.js +1 -1
- package/dist/runner.js +140 -9
- package/docs/CONTRIBUTING.md +136 -0
- package/docs/QA.md +184 -0
- package/docs/RECIPES.md +122 -0
- package/docs/SECURITY.md +88 -0
- package/library/README.md +7 -3
- package/library/actions/core/perps/_controller.mjs +43 -56
- package/library/actions/core/perps/assert_orders.mjs +6 -7
- package/library/actions/core/perps/assert_positions.mjs +6 -7
- package/library/actions/core/perps/close_orders.mjs +2 -0
- package/library/actions/core/perps/close_positions.mjs +2 -0
- package/library/actions/core/perps/ensure_orders.mjs +4 -2
- package/library/actions/core/perps/ensure_positions.mjs +4 -2
- package/library/actions/core/perps/place_order.mjs +7 -3
- package/library/actions/core/perps/read_account.mjs +2 -2
- package/library/actions/core/perps/read_orders.mjs +2 -1
- package/library/actions/core/perps/read_positions.mjs +2 -1
- package/library/actions/core/wallet/list_accounts.mjs +95 -0
- package/library/actions/extension/perps/assert_orders.mjs +2 -1
- package/library/actions/extension/perps/assert_positions.mjs +2 -1
- package/library/actions/extension/perps/perps.mjs +43 -14
- package/library/actions/extension/platform/cdp.mjs +1 -0
- package/library/actions/extension/wallet/list_accounts.mjs +41 -0
- package/library/actions/mobile/perps/assert_orders.mjs +2 -1
- package/library/actions/mobile/perps/assert_positions.mjs +2 -1
- package/library/actions/mobile/perps/perps.mjs +40 -12
- package/library/actions/mobile/wallet/list_accounts.mjs +37 -0
- package/library/library.json +1 -1
- package/library/manifests/core.action-manifest.json +1214 -396
- package/library/manifests/extension.action-manifest.json +1635 -728
- package/library/manifests/mobile.action-manifest.json +1740 -727
- package/library/recipes/app/lifecycle.android-smoke.mobile.recipe.json +63 -81
- package/library/recipes/perps/clean-market-testnet.core.recipe.json +44 -0
- package/library/recipes/perps/clean-market-testnet.recipe.json +49 -0
- package/library/recipes/perps/lifecycle.recipe.json +136 -180
- package/library/recipes/perps/order-lifecycle.core.recipe.json +71 -67
- package/library/recipes/perps/performance.background-resume.mobile.recipe.json +51 -67
- package/library/recipes/perps/performance.cold-start.mobile.recipe.json +51 -67
- package/library/recipes/perps/performance.mobile.recipe.json +37 -51
- package/library/recipes/perps/performance.warm-start.mobile.recipe.json +44 -59
- package/library/recipes/perps/read-markets.core.recipe.json +29 -31
- package/library/recipes/perps/smoke.core.recipe.json +29 -32
- package/library/recipes/perps/smoke.extension.recipe.json +41 -44
- package/library/recipes/perps/smoke.mobile.recipe.json +42 -44
- package/library/recipes/perps/trading-lifecycle.core.recipe.json +69 -65
- package/library/recipes/runner/action-validation.extension.recipe.json +312 -398
- package/library/recipes/runner/action-validation.mobile.recipe.json +316 -402
- package/library/recipes/runner/smoke.core.recipe.json +25 -0
- package/library/recipes/runner/smoke.extension.recipe.json +23 -24
- package/library/recipes/runner/smoke.mobile.recipe.json +23 -24
- package/library/recipes/wallet/smoke.extension.recipe.json +40 -0
- package/library/recipes/wallet/smoke.mobile.recipe.json +40 -0
- package/package.json +12 -5
- package/scripts/completions.sh +8 -11
- package/scripts/validate-human-outcomes.mjs +169 -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/dist/adapters/extension/harness-freshness.js +0 -39
- package/dist/commands/flows.js +0 -62
- package/docs/ADAPTER-SURFACE.md +0 -119
- package/docs/CHEATSHEET.md +0 -61
- package/docs/CLI-SPEC.md +0 -1098
- package/docs/CODE-MAP.md +0 -62
- package/docs/DEBUG-HANDOVER.md +0 -36
- package/docs/MENTAL-MODEL.md +0 -295
- package/docs/UX-PRINCIPLES.md +0 -64
- package/docs/architecture.md +0 -398
- package/docs/live-adapter-contract.md +0 -188
- package/docs/package-boundaries.md +0 -47
- package/docs/perps-flow-catalog.md +0 -235
- package/docs/recipe-libraries.md +0 -301
- package/docs/runtime-file-conventions.md +0 -36
- package/library/flows/perps.flows.json +0 -64
package/dist/harness.js
CHANGED
|
@@ -4,13 +4,9 @@ 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
|
-
import {
|
|
8
|
-
const HARNESS_ACTIONS = ["install", "verify", "cleanup"
|
|
7
|
+
import { colorHumanMessage } from "./cli-color.js";
|
|
8
|
+
const HARNESS_ACTIONS = ["install", "verify", "cleanup"];
|
|
9
9
|
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
10
|
function harnessUsage() {
|
|
15
11
|
console.error(`mm-harness \u2014 install and validate the MetaMask recipe runtime in a checkout.
|
|
16
12
|
|
|
@@ -50,6 +46,12 @@ function failureHint(adapter, action) {
|
|
|
50
46
|
const helper = adapter === "mobile" ? "mm-harness launch ios # or launch android" : "mm-harness launch";
|
|
51
47
|
return `Read the error above for the specific cause. To (re)start the runtime, run: ${helper}`;
|
|
52
48
|
}
|
|
49
|
+
function successNext(adapter, action, target) {
|
|
50
|
+
const base = `--adapter ${adapter} --target ${shellQuote(target)}`;
|
|
51
|
+
if (action === "install") return `mm-harness verify ${base}`;
|
|
52
|
+
if (action === "cleanup") return `mm-harness install ${base}`;
|
|
53
|
+
return `mm-harness status --target ${shellQuote(target)} --json`;
|
|
54
|
+
}
|
|
53
55
|
function hasArg(args, needle) {
|
|
54
56
|
return args.some((arg) => arg === needle || arg.startsWith(`${needle}=`));
|
|
55
57
|
}
|
|
@@ -70,6 +72,7 @@ function parseHarnessArgs(args) {
|
|
|
70
72
|
const forward = [];
|
|
71
73
|
let adapter;
|
|
72
74
|
let json = false;
|
|
75
|
+
let quiet = false;
|
|
73
76
|
let separator = false;
|
|
74
77
|
for (let i = 0; i < args.length; i += 1) {
|
|
75
78
|
const arg = args[i];
|
|
@@ -85,6 +88,10 @@ function parseHarnessArgs(args) {
|
|
|
85
88
|
json = true;
|
|
86
89
|
continue;
|
|
87
90
|
}
|
|
91
|
+
if (arg === "--quiet") {
|
|
92
|
+
quiet = true;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
88
95
|
if (arg === "--adapter" || arg === "--platform") {
|
|
89
96
|
const value = args[i + 1];
|
|
90
97
|
if (arg === "--adapter") {
|
|
@@ -115,7 +122,7 @@ function parseHarnessArgs(args) {
|
|
|
115
122
|
}
|
|
116
123
|
forward.push(arg);
|
|
117
124
|
}
|
|
118
|
-
return { adapter, json, forward };
|
|
125
|
+
return { adapter, json, quiet, forward };
|
|
119
126
|
}
|
|
120
127
|
const ADAPTER_DETECT_NEXT = "cd into a MetaMask checkout or pass --target <path>, or force it with --adapter <mobile|extension|core>";
|
|
121
128
|
function detectAdapter(target) {
|
|
@@ -161,7 +168,7 @@ function readRuntimeContextField(contextPath, field) {
|
|
|
161
168
|
if (node === void 0 || node === null || node === "") return void 0;
|
|
162
169
|
return String(node);
|
|
163
170
|
}
|
|
164
|
-
function applyExtensionRuntimeEnv(target,
|
|
171
|
+
function applyExtensionRuntimeEnv(target, args) {
|
|
165
172
|
const contextPath = resolveRuntimeContextPath(target);
|
|
166
173
|
const contextExists = fs.existsSync(contextPath);
|
|
167
174
|
if (contextExists) {
|
|
@@ -174,19 +181,6 @@ function applyExtensionRuntimeEnv(target, action, args) {
|
|
|
174
181
|
const extensionId = readRuntimeContextField(contextPath, "extensionId");
|
|
175
182
|
if (extensionId) process.env.RECIPE_HARNESS_EXTENSION_ID = extensionId;
|
|
176
183
|
}
|
|
177
|
-
if (process.env.RECIPE_RUNTIME_START_APPROVED === void 0) {
|
|
178
|
-
const approved = readRuntimeContextField(contextPath, "runtimeStart.approved");
|
|
179
|
-
if (approved === "true" || approved === "True" || approved === "1") process.env.RECIPE_RUNTIME_START_APPROVED = "1";
|
|
180
|
-
else if (approved === "false" || approved === "False" || approved === "0") process.env.RECIPE_RUNTIME_START_APPROVED = "0";
|
|
181
|
-
}
|
|
182
|
-
if (!process.env.RECIPE_RUNTIME_START_CMD) {
|
|
183
|
-
const command = readRuntimeContextField(contextPath, "runtimeStart.command");
|
|
184
|
-
if (command) process.env.RECIPE_RUNTIME_START_CMD = command;
|
|
185
|
-
}
|
|
186
|
-
if (!process.env.RECIPE_RUNTIME_READY_URL) {
|
|
187
|
-
const readyUrl = readRuntimeContextField(contextPath, "runtimeStart.readyUrl");
|
|
188
|
-
if (readyUrl) process.env.RECIPE_RUNTIME_READY_URL = readyUrl;
|
|
189
|
-
}
|
|
190
184
|
} else {
|
|
191
185
|
delete process.env.RECIPE_RUNTIME_CONTEXT;
|
|
192
186
|
}
|
|
@@ -200,11 +194,6 @@ function applyExtensionRuntimeEnv(target, action, args) {
|
|
|
200
194
|
result = [...result, "--cdp-port", cdpPort];
|
|
201
195
|
}
|
|
202
196
|
}
|
|
203
|
-
if (action === "live" && !hasArg(result, "--prepare-cmd")) {
|
|
204
|
-
if (process.env.RECIPE_RUNTIME_START_APPROVED === "1" && process.env.RECIPE_RUNTIME_START_CMD) {
|
|
205
|
-
result = [...result, "--prepare-cmd", process.env.RECIPE_RUNTIME_START_CMD];
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
197
|
return result;
|
|
209
198
|
}
|
|
210
199
|
function resolveEntry(base, candidates, mode) {
|
|
@@ -281,81 +270,6 @@ Next: mm-harness install --platform core --target ${target}`
|
|
|
281
270
|
);
|
|
282
271
|
return { command, prefixArgs: [] };
|
|
283
272
|
}
|
|
284
|
-
async function handleMobileLive(target, forwardArgs, json, autoDetected) {
|
|
285
|
-
const platform = argValue(forwardArgs, "--platform") ?? "ios";
|
|
286
|
-
const watcherPortStr = argValue(forwardArgs, "--watcher-port") ?? process.env.WATCHER_PORT;
|
|
287
|
-
const watcherPort = watcherPortStr ? parseInt(watcherPortStr, 10) : void 0;
|
|
288
|
-
const start = Date.now();
|
|
289
|
-
if (!json) {
|
|
290
|
-
const detected = autoDetected ? ", auto-detected" : "";
|
|
291
|
-
console.error(`\u2192 live (mobile${detected}) \u2014 target: ${target}`);
|
|
292
|
-
}
|
|
293
|
-
const prepResult = await prepareMobile(target, { platform, json, watcherPort });
|
|
294
|
-
if (prepResult.status !== 0) {
|
|
295
|
-
const elapsed2 = ((Date.now() - start) / 1e3).toFixed(1);
|
|
296
|
-
if (json) {
|
|
297
|
-
console.log(
|
|
298
|
-
harnessSummary("live", "mobile", target, "fail", prepResult.status, autoDetected, {
|
|
299
|
-
code: "MOBILE_PREPARE_FAILED",
|
|
300
|
-
message: `mobile prepare failed (exit ${prepResult.status})`,
|
|
301
|
-
userAction: failureHint("mobile", "live")
|
|
302
|
-
})
|
|
303
|
-
);
|
|
304
|
-
} else {
|
|
305
|
-
console.error(
|
|
306
|
-
`\u2717 live mobile failed (exit ${prepResult.status}, ${elapsed2}s)
|
|
307
|
-
${failureHint("mobile", "live")}`
|
|
308
|
-
);
|
|
309
|
-
}
|
|
310
|
-
return prepResult.status;
|
|
311
|
-
}
|
|
312
|
-
const installedVerify = path.join(recipeHarnessPath(target, "mobile"), "scripts", "verify.sh");
|
|
313
|
-
const verifySh = fs.existsSync(installedVerify) ? installedVerify : resolveEntry(runnerDir, ["adapters/mobile/verify.sh", "scripts/mobile/verify.sh"], "file");
|
|
314
|
-
const verifyArgs = hasArg(forwardArgs, "--no-auto-start") ? [...forwardArgs] : ["--no-auto-start", ...forwardArgs];
|
|
315
|
-
if (shellLeafMissing(verifySh)) {
|
|
316
|
-
const message = missingShellLeafMessage(verifySh);
|
|
317
|
-
if (json) {
|
|
318
|
-
console.log(
|
|
319
|
-
harnessSummary("live", "mobile", target, "fail", 1, autoDetected, {
|
|
320
|
-
code: "HARNESS_SPAWN_FAILED",
|
|
321
|
-
message,
|
|
322
|
-
userAction: failureHint("mobile", "live")
|
|
323
|
-
})
|
|
324
|
-
);
|
|
325
|
-
} else {
|
|
326
|
-
console.error(`\u2717 ${message}`);
|
|
327
|
-
}
|
|
328
|
-
return 1;
|
|
329
|
-
}
|
|
330
|
-
const { bin: verifyBin, args: verifySpawnArgs } = resolveLeafInvoke(verifySh, verifyArgs);
|
|
331
|
-
const result = spawnSync(verifyBin, verifySpawnArgs, {
|
|
332
|
-
stdio: json ? ["inherit", 2, "inherit"] : "inherit",
|
|
333
|
-
env: process.env
|
|
334
|
-
});
|
|
335
|
-
const elapsed = ((Date.now() - start) / 1e3).toFixed(1);
|
|
336
|
-
const exitCode = result.status ?? 1;
|
|
337
|
-
if (json) {
|
|
338
|
-
console.log(
|
|
339
|
-
harnessSummary(
|
|
340
|
-
"live",
|
|
341
|
-
"mobile",
|
|
342
|
-
target,
|
|
343
|
-
exitCode === 0 ? "pass" : "fail",
|
|
344
|
-
exitCode,
|
|
345
|
-
autoDetected,
|
|
346
|
-
exitCode === 0 ? void 0 : { code: "MOBILE_VERIFY_FAILED", message: `mobile live verify failed (exit ${exitCode})`, userAction: failureHint("mobile", "live") }
|
|
347
|
-
)
|
|
348
|
-
);
|
|
349
|
-
} else if (exitCode === 0) {
|
|
350
|
-
console.error(`\u2713 live mobile passed (${elapsed}s)`);
|
|
351
|
-
} else {
|
|
352
|
-
console.error(
|
|
353
|
-
`\u2717 live mobile failed (exit ${exitCode}, ${elapsed}s)
|
|
354
|
-
${failureHint("mobile", "live")}`
|
|
355
|
-
);
|
|
356
|
-
}
|
|
357
|
-
return exitCode;
|
|
358
|
-
}
|
|
359
273
|
async function handleHarness(argv) {
|
|
360
274
|
const action = argv[0];
|
|
361
275
|
if (!action || action === "-h" || action === "--help") {
|
|
@@ -368,12 +282,14 @@ async function handleHarness(argv) {
|
|
|
368
282
|
return 2;
|
|
369
283
|
}
|
|
370
284
|
const harnessAction = action;
|
|
371
|
-
const { adapter: parsedAdapter, json, forward } = parseHarnessArgs(argv.slice(1));
|
|
285
|
+
const { adapter: parsedAdapter, json, quiet, forward } = parseHarnessArgs(argv.slice(1));
|
|
372
286
|
const rawTarget = argValue(forward, "--target");
|
|
373
287
|
const target = path.resolve(rawTarget ?? process.cwd());
|
|
374
288
|
const adapter = parsedAdapter ?? detectAdapter(target);
|
|
375
289
|
if (!adapter || !isAdapter(adapter)) {
|
|
376
|
-
if (
|
|
290
|
+
if (quiet) {
|
|
291
|
+
return 2;
|
|
292
|
+
} else if (json) {
|
|
377
293
|
const detectError = adapter ? { code: "UNSUPPORTED_PLATFORM", message: `unsupported platform: ${adapter}`, userAction: "pass --adapter <mobile|extension|core> to specify a supported adapter" } : { code: "ADAPTER_DETECTION_FAILED", message: `could not detect the MetaMask repo type for ${target}`, userAction: ADAPTER_DETECT_NEXT };
|
|
378
294
|
console.log(harnessSummary(harnessAction, void 0, target, "fail", 2, false, detectError));
|
|
379
295
|
} else {
|
|
@@ -387,36 +303,19 @@ async function handleHarness(argv) {
|
|
|
387
303
|
}
|
|
388
304
|
return 2;
|
|
389
305
|
}
|
|
390
|
-
if (!isValidAdapterAction(adapter, harnessAction)) {
|
|
391
|
-
if (json) {
|
|
392
|
-
console.log(
|
|
393
|
-
harnessSummary(harnessAction, adapter, target, "fail", 2, parsedAdapter === void 0, {
|
|
394
|
-
code: "UNSUPPORTED_ACTION",
|
|
395
|
-
message: `${harnessAction} is not supported for ${adapter} (core supports install, verify, cleanup).`,
|
|
396
|
-
userAction: "run mm-harness doctor to see which actions are supported for this adapter"
|
|
397
|
-
})
|
|
398
|
-
);
|
|
399
|
-
} else {
|
|
400
|
-
console.error(
|
|
401
|
-
`\u2717 ${harnessAction} is not supported for ${adapter} (core supports install, verify, cleanup).`
|
|
402
|
-
);
|
|
403
|
-
}
|
|
404
|
-
return 2;
|
|
405
|
-
}
|
|
406
306
|
const autoDetected = parsedAdapter === void 0;
|
|
407
307
|
let forwardArgs = hasArg(forward, "--target") ? [...forward] : ["--target", target, ...forward];
|
|
408
308
|
if (harnessAction === "install" && hasArg(forward, "--runway")) {
|
|
409
309
|
return handleRunwayInstall(adapter, target, forward, json);
|
|
410
310
|
}
|
|
411
|
-
if (adapter === "extension" &&
|
|
412
|
-
forwardArgs = applyExtensionRuntimeEnv(target,
|
|
413
|
-
}
|
|
414
|
-
if (adapter === "mobile" && harnessAction === "live") {
|
|
415
|
-
return handleMobileLive(target, forwardArgs, json, autoDetected);
|
|
311
|
+
if (adapter === "extension" && harnessAction === "verify") {
|
|
312
|
+
forwardArgs = applyExtensionRuntimeEnv(target, forwardArgs);
|
|
416
313
|
}
|
|
417
314
|
const dispatch = resolveHarnessDispatch(adapter, harnessAction, target);
|
|
418
315
|
if ("error" in dispatch) {
|
|
419
|
-
if (
|
|
316
|
+
if (quiet) {
|
|
317
|
+
return 1;
|
|
318
|
+
} else if (json) {
|
|
420
319
|
console.log(
|
|
421
320
|
harnessSummary(harnessAction, adapter, target, "fail", 1, autoDetected, {
|
|
422
321
|
code: "DISPATCH_UNAVAILABLE",
|
|
@@ -432,14 +331,16 @@ async function handleHarness(argv) {
|
|
|
432
331
|
}
|
|
433
332
|
return 1;
|
|
434
333
|
}
|
|
435
|
-
if (!json) {
|
|
334
|
+
if (!json && !quiet) {
|
|
436
335
|
const detected = autoDetected ? ", auto-detected" : "";
|
|
437
|
-
console.error(`\u2192 ${harnessAction} (${adapter}${detected}) \u2014 target: ${target}`);
|
|
336
|
+
console.error(colorHumanMessage(`\u2192 ${harnessAction} (${adapter}${detected}) \u2014 target: ${target}`));
|
|
438
337
|
}
|
|
439
338
|
const start = Date.now();
|
|
440
339
|
if (shellLeafMissing(dispatch.command)) {
|
|
441
340
|
const message = missingShellLeafMessage(dispatch.command);
|
|
442
|
-
if (
|
|
341
|
+
if (quiet) {
|
|
342
|
+
return 1;
|
|
343
|
+
} else if (json) {
|
|
443
344
|
console.log(
|
|
444
345
|
harnessSummary(harnessAction, adapter, target, "fail", 1, autoDetected, {
|
|
445
346
|
code: "HARNESS_SPAWN_FAILED",
|
|
@@ -459,12 +360,14 @@ async function handleHarness(argv) {
|
|
|
459
360
|
const result = spawnSync(dispatchBin, dispatchArgs, {
|
|
460
361
|
// Default: stream child output verbatim (byte-identical to the skill path).
|
|
461
362
|
// --json: route child output to our stderr so stdout carries only the summary.
|
|
462
|
-
stdio: json ? ["inherit", 2, "inherit"] : "inherit",
|
|
363
|
+
stdio: quiet ? "ignore" : json ? ["inherit", 2, "inherit"] : "inherit",
|
|
463
364
|
env: process.env
|
|
464
365
|
});
|
|
465
366
|
const seconds = ((Date.now() - start) / 1e3).toFixed(1);
|
|
466
367
|
if (result.error) {
|
|
467
|
-
if (
|
|
368
|
+
if (quiet) {
|
|
369
|
+
return 1;
|
|
370
|
+
} else if (json) {
|
|
468
371
|
console.log(
|
|
469
372
|
harnessSummary(harnessAction, adapter, target, "fail", 1, autoDetected, {
|
|
470
373
|
code: "HARNESS_SPAWN_FAILED",
|
|
@@ -479,7 +382,9 @@ async function handleHarness(argv) {
|
|
|
479
382
|
return 1;
|
|
480
383
|
}
|
|
481
384
|
const exitCode = result.status ?? 1;
|
|
482
|
-
if (
|
|
385
|
+
if (quiet) {
|
|
386
|
+
return exitCode;
|
|
387
|
+
} else if (json) {
|
|
483
388
|
console.log(
|
|
484
389
|
harnessSummary(
|
|
485
390
|
harnessAction,
|
|
@@ -492,10 +397,11 @@ async function handleHarness(argv) {
|
|
|
492
397
|
)
|
|
493
398
|
);
|
|
494
399
|
} else if (exitCode === 0) {
|
|
495
|
-
console.error(`\u2713 ${harnessAction} ${adapter} passed (${seconds}s)
|
|
400
|
+
console.error(colorHumanMessage(`\u2713 ${harnessAction} ${adapter} passed (${seconds}s)
|
|
401
|
+
Next: ${successNext(adapter, harnessAction, target)}`));
|
|
496
402
|
} else {
|
|
497
|
-
console.error(`\u2717 ${harnessAction} ${adapter} failed (exit ${exitCode}, ${seconds}s)
|
|
498
|
-
${failureHint(adapter, harnessAction)}`);
|
|
403
|
+
console.error(colorHumanMessage(`\u2717 ${harnessAction} ${adapter} failed (exit ${exitCode}, ${seconds}s)
|
|
404
|
+
${failureHint(adapter, harnessAction)}`));
|
|
499
405
|
}
|
|
500
406
|
return exitCode;
|
|
501
407
|
}
|
|
@@ -522,8 +428,10 @@ async function handleRunwayInstall(adapter, target, forward, json) {
|
|
|
522
428
|
resolveOnly: hasArg(forward, "--resolve-only"),
|
|
523
429
|
rerunCommand
|
|
524
430
|
});
|
|
431
|
+
const next = result.status === "pass" && adapter === "mobile" && result.resolveOnly !== true ? `mm-harness launch ${String(result.platform ?? "ios")} --adapter mobile --target ${shellQuote(target)}` : void 0;
|
|
432
|
+
const output = next ? { ...result, next } : result;
|
|
525
433
|
if (json) {
|
|
526
|
-
console.log(JSON.stringify(
|
|
434
|
+
console.log(JSON.stringify(output, null, 2));
|
|
527
435
|
} else if (result.status === "pass") {
|
|
528
436
|
const cache = typeof result.cache === "object" && result.cache ? result.cache : void 0;
|
|
529
437
|
const simulator = typeof result.simulator === "object" && result.simulator ? result.simulator : void 0;
|
|
@@ -534,6 +442,7 @@ async function handleRunwayInstall(adapter, target, forward, json) {
|
|
|
534
442
|
const action = result.skipped ? "already provisioned" : "installed Runway app";
|
|
535
443
|
console.error(`\u2713 ${action} for ${adapter} ${result.platform ?? ""} simulator=${simulator?.name ?? "unknown"} cache=${cache?.status ?? "skip"}`);
|
|
536
444
|
}
|
|
445
|
+
if (next) console.error(` Next: ${next}`);
|
|
537
446
|
} else {
|
|
538
447
|
console.error(`\u2717 mm-harness install --runway: ${result.error?.message ?? "runway install failed"}
|
|
539
448
|
Next: ${result.error?.userAction ?? rerunCommand}`);
|
|
@@ -576,9 +485,10 @@ function harnessSummary(action, adapter, target, status, exitCode, autoDetected,
|
|
|
576
485
|
status,
|
|
577
486
|
exitCode,
|
|
578
487
|
// Error contract: every --json failure carries a stable machine code + human
|
|
579
|
-
// message
|
|
488
|
+
// message. userAction is included when present so callers
|
|
580
489
|
// can surface the reachable escape without parsing the human message.
|
|
581
|
-
...status === "fail" && error ? { error } : {}
|
|
490
|
+
...status === "fail" && error ? { error } : {},
|
|
491
|
+
...status === "pass" && adapter ? { next: successNext(adapter, action, target) } : {}
|
|
582
492
|
});
|
|
583
493
|
}
|
|
584
494
|
export {
|
package/dist/heal-bounds.js
CHANGED
|
@@ -7,6 +7,10 @@ import { EXIT } from "./commands/shared.js";
|
|
|
7
7
|
function newHealState() {
|
|
8
8
|
return { recovered: [], mutations: [], attemptedRecoveries: [] };
|
|
9
9
|
}
|
|
10
|
+
function conciseFailureForHuman(output) {
|
|
11
|
+
const errors = output.split(/\r?\n/u).map((line) => /^(?:[A-Za-z]*Error):\s*(.+)$/u.exec(line.trim())?.[1]).filter((line) => Boolean(line));
|
|
12
|
+
return errors.at(-1) ?? output.trim();
|
|
13
|
+
}
|
|
10
14
|
function parseHeal(options, fallback) {
|
|
11
15
|
const value = options.heal;
|
|
12
16
|
if (value === void 0) return fallback;
|
|
@@ -45,7 +49,7 @@ async function ensureOverlay(adapter, target, heal, state, json) {
|
|
|
45
49
|
});
|
|
46
50
|
code = result.status ?? 1;
|
|
47
51
|
} else {
|
|
48
|
-
code = await handleHarness(["install", "--platform", adapter, "--target", target,
|
|
52
|
+
code = await handleHarness(["install", "--platform", adapter, "--target", target, "--quiet"]);
|
|
49
53
|
}
|
|
50
54
|
if (code !== 0 || !overlayPresent(target, adapter)) {
|
|
51
55
|
return { ok: false, error: `runtime overlay install failed (exit ${code})` };
|
|
@@ -103,7 +107,7 @@ function checkHealBounds(target, output, state) {
|
|
|
103
107
|
return {
|
|
104
108
|
code: "SAME_RECOVERY_TWICE",
|
|
105
109
|
exitCode: EXIT.bounded,
|
|
106
|
-
message: "
|
|
110
|
+
message: state.recovered.length > 0 ? "one recovery already succeeded this invocation \u2014 bounded policy refuses a second recovery." : "one recovery was already attempted this invocation \u2014 refusing to loop.",
|
|
107
111
|
originalError
|
|
108
112
|
};
|
|
109
113
|
}
|
|
@@ -113,6 +117,7 @@ export {
|
|
|
113
117
|
RECOVERY_CODE,
|
|
114
118
|
checkHealBounds,
|
|
115
119
|
classifyFailure,
|
|
120
|
+
conciseFailureForHuman,
|
|
116
121
|
ensureOverlay,
|
|
117
122
|
newHealState,
|
|
118
123
|
parseHeal,
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
class JsonStreamWriter {
|
|
2
|
+
enabled;
|
|
3
|
+
command;
|
|
4
|
+
completed = false;
|
|
5
|
+
writeLine;
|
|
6
|
+
constructor(command, enabled, output = process.stdout) {
|
|
7
|
+
this.command = command;
|
|
8
|
+
this.enabled = enabled;
|
|
9
|
+
const write = output.write.bind(output);
|
|
10
|
+
this.writeLine = (line) => {
|
|
11
|
+
write(`${line}
|
|
12
|
+
`);
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
isolateStdout() {
|
|
16
|
+
if (!this.enabled) return () => void 0;
|
|
17
|
+
const original = process.stdout.write;
|
|
18
|
+
const redirect = ((chunk, encoding, callback) => process.stderr.write(chunk, encoding, callback));
|
|
19
|
+
process.stdout.write = redirect;
|
|
20
|
+
return () => {
|
|
21
|
+
if (process.stdout.write === redirect) process.stdout.write = original;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
emit(event, fields = {}) {
|
|
25
|
+
if (!this.enabled || this.completed) return;
|
|
26
|
+
this.writeLine(JSON.stringify({
|
|
27
|
+
schemaVersion: 1,
|
|
28
|
+
command: this.command,
|
|
29
|
+
event,
|
|
30
|
+
...fields,
|
|
31
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
32
|
+
}));
|
|
33
|
+
}
|
|
34
|
+
phase(phase, fields = {}) {
|
|
35
|
+
this.emit("phase", { phase, ...fields });
|
|
36
|
+
}
|
|
37
|
+
node(nodeId, action, status) {
|
|
38
|
+
this.emit("node", { nodeId, action, status });
|
|
39
|
+
}
|
|
40
|
+
mutation(mutation) {
|
|
41
|
+
this.emit("mutation", { mutation });
|
|
42
|
+
}
|
|
43
|
+
recovery(code) {
|
|
44
|
+
this.emit("recovery", { code });
|
|
45
|
+
}
|
|
46
|
+
error(error) {
|
|
47
|
+
this.emit("error", { error });
|
|
48
|
+
}
|
|
49
|
+
complete(status, exitCode, fields = {}) {
|
|
50
|
+
if (!this.enabled || this.completed) return;
|
|
51
|
+
this.emit("complete", { status, exitCode, ...fields });
|
|
52
|
+
this.completed = true;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export {
|
|
56
|
+
JsonStreamWriter
|
|
57
|
+
};
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { access, mkdtemp, readFile, realpath, rm, writeFile } from "node:fs/promises";
|
|
2
3
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
4
|
import os from "node:os";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
import { spawn } from "node:child_process";
|
|
7
|
+
import { pathToFileURL } from "node:url";
|
|
8
|
+
import { build } from "esbuild";
|
|
9
|
+
import { init, parse } from "es-module-lexer";
|
|
6
10
|
import { resolveRequiredLocalProtocolRoot, runnerDir } from "./paths.js";
|
|
7
11
|
import { corePnpNodeOptions } from "./commands/core-readiness.js";
|
|
8
12
|
function actionFileStem(action) {
|
|
@@ -31,13 +35,17 @@ function candidateFamilies(action) {
|
|
|
31
35
|
const families = [family];
|
|
32
36
|
return [...new Set(families)];
|
|
33
37
|
}
|
|
34
|
-
function candidatePaths(platform, action) {
|
|
38
|
+
function candidatePaths(platform, action, runtimeEnv = process.env) {
|
|
35
39
|
const stems = candidateStems(action);
|
|
36
40
|
const families = candidateFamilies(action);
|
|
37
|
-
const
|
|
38
|
-
process.env.
|
|
41
|
+
const declaredRoot = declaredActionRoot(
|
|
42
|
+
runtimeEnv.METAMASK_RECIPE_ACTION_SOURCE_MAP ?? process.env.METAMASK_RECIPE_ACTION_SOURCE_MAP,
|
|
43
|
+
action
|
|
44
|
+
);
|
|
45
|
+
const roots = [...new Set((declaredRoot ? [declaredRoot] : [
|
|
46
|
+
runtimeEnv.METAMASK_RECIPE_LIVE_ADAPTER_DIR ?? process.env.METAMASK_RECIPE_LIVE_ADAPTER_DIR,
|
|
39
47
|
path.join(runnerDir, "library/actions")
|
|
40
|
-
].filter(Boolean);
|
|
48
|
+
]).filter((entry) => Boolean(entry)))];
|
|
41
49
|
const files = [];
|
|
42
50
|
for (const root of roots) {
|
|
43
51
|
for (const family of families) {
|
|
@@ -46,29 +54,26 @@ function candidatePaths(platform, action) {
|
|
|
46
54
|
}
|
|
47
55
|
pushDomainDispatcherFiles(files, root, platform, family);
|
|
48
56
|
}
|
|
49
|
-
for (const candidateStem of stems) {
|
|
50
|
-
pushFlatCandidateFiles(files, root, platform, candidateStem);
|
|
51
|
-
}
|
|
52
57
|
}
|
|
53
58
|
return files;
|
|
54
59
|
}
|
|
55
|
-
function
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
60
|
+
function declaredActionRoot(raw, action) {
|
|
61
|
+
if (!raw) return void 0;
|
|
62
|
+
try {
|
|
63
|
+
const parsed = JSON.parse(raw);
|
|
64
|
+
const root = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed[action] : void 0;
|
|
65
|
+
return typeof root === "string" && root.length > 0 ? root : void 0;
|
|
66
|
+
} catch {
|
|
67
|
+
return void 0;
|
|
59
68
|
}
|
|
60
69
|
}
|
|
61
|
-
function
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
files.push(path.join(root, "shared", family, `${family}.${extension}`));
|
|
65
|
-
}
|
|
70
|
+
function pushCandidateFiles(files, root, platform, family, stem) {
|
|
71
|
+
files.push(path.join(root, platform, family, `${stem}.mjs`));
|
|
72
|
+
files.push(path.join(root, "shared", family, `${stem}.mjs`));
|
|
66
73
|
}
|
|
67
|
-
function
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
files.push(path.join(root, "shared", `${stem}.${extension}`));
|
|
71
|
-
}
|
|
74
|
+
function pushDomainDispatcherFiles(files, root, platform, family) {
|
|
75
|
+
files.push(path.join(root, platform, family, `${family}.mjs`));
|
|
76
|
+
files.push(path.join(root, "shared", family, `${family}.mjs`));
|
|
72
77
|
}
|
|
73
78
|
async function firstExecutablePath(paths) {
|
|
74
79
|
for (const file of paths) {
|
|
@@ -87,8 +92,16 @@ function runProcess(command, args, options) {
|
|
|
87
92
|
const child = spawn(command, args, {
|
|
88
93
|
cwd: options.cwd,
|
|
89
94
|
env: options.env,
|
|
90
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
95
|
+
stdio: [options.stdin == null ? "ignore" : "pipe", "pipe", "pipe"]
|
|
91
96
|
});
|
|
97
|
+
if (options.stdin != null) {
|
|
98
|
+
child.stdin.on("error", (error) => {
|
|
99
|
+
if (error.code === "EPIPE" || settled) return;
|
|
100
|
+
settled = true;
|
|
101
|
+
reject(error);
|
|
102
|
+
});
|
|
103
|
+
child.stdin.end(options.stdin);
|
|
104
|
+
}
|
|
92
105
|
let stdout = "";
|
|
93
106
|
let stderr = "";
|
|
94
107
|
child.stdout.on("data", (chunk) => {
|
|
@@ -119,10 +132,82 @@ function runProcess(command, args, options) {
|
|
|
119
132
|
});
|
|
120
133
|
});
|
|
121
134
|
}
|
|
135
|
+
async function prepareLiveAdapterScript({
|
|
136
|
+
platform,
|
|
137
|
+
action,
|
|
138
|
+
implementationRoot,
|
|
139
|
+
allowRuntimeImports = false
|
|
140
|
+
}) {
|
|
141
|
+
const entryPath = await firstExecutablePath(
|
|
142
|
+
candidatePaths(platform, action, {
|
|
143
|
+
METAMASK_RECIPE_ACTION_SOURCE_MAP: JSON.stringify({ [action]: implementationRoot })
|
|
144
|
+
})
|
|
145
|
+
);
|
|
146
|
+
if (!entryPath) return null;
|
|
147
|
+
const rootReal = await realpath(implementationRoot);
|
|
148
|
+
const entryReal = await realpath(entryPath);
|
|
149
|
+
if (!isPathWithin(rootReal, entryReal)) {
|
|
150
|
+
throw new Error(`Live adapter ${entryPath} resolves outside ${implementationRoot}.`);
|
|
151
|
+
}
|
|
152
|
+
const result = await build({
|
|
153
|
+
entryPoints: [entryReal],
|
|
154
|
+
bundle: true,
|
|
155
|
+
write: false,
|
|
156
|
+
platform: "node",
|
|
157
|
+
format: "esm",
|
|
158
|
+
target: `node${process.versions.node.split(".")[0]}`,
|
|
159
|
+
legalComments: "none",
|
|
160
|
+
sourcemap: false,
|
|
161
|
+
banner: { js: `process.argv[1] = ${JSON.stringify(entryReal)};` },
|
|
162
|
+
plugins: [bindImportMetaUrls()],
|
|
163
|
+
logLevel: "silent"
|
|
164
|
+
});
|
|
165
|
+
const output = result.outputFiles[0];
|
|
166
|
+
if (!output) throw new Error(`Bundling live adapter ${entryReal} produced no executable output.`);
|
|
167
|
+
const sourceText = output.text;
|
|
168
|
+
await init;
|
|
169
|
+
const [imports] = parse(sourceText);
|
|
170
|
+
if (!allowRuntimeImports && imports.some((entry) => entry.d >= 0)) {
|
|
171
|
+
const error = new Error(
|
|
172
|
+
`Live adapter ${entryReal} contains a runtime dynamic import that cannot be bound to the approved bundle. Replace it with a static import so its implementation bytes are included in the plan digest.`
|
|
173
|
+
);
|
|
174
|
+
Object.assign(error, {
|
|
175
|
+
code: "RECIPE_TRUST_UNBOUND_IMPORT",
|
|
176
|
+
userAction: "replace the computed import with a static import, or promote the adapter into an explicitly trusted engineer-owned library"
|
|
177
|
+
});
|
|
178
|
+
throw error;
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
action,
|
|
182
|
+
platform,
|
|
183
|
+
entryPath: entryReal,
|
|
184
|
+
sourceText,
|
|
185
|
+
sourceDigest: `sha256:${createHash("sha256").update(sourceText).digest("hex")}`
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function bindImportMetaUrls() {
|
|
189
|
+
return {
|
|
190
|
+
name: "bind-import-meta-urls",
|
|
191
|
+
setup(buildContext) {
|
|
192
|
+
buildContext.onLoad({ filter: /\.[cm]?[jt]sx?$/ }, async (args) => {
|
|
193
|
+
const contents = await readFile(args.path, "utf8");
|
|
194
|
+
if (!contents.includes("import.meta.url")) return void 0;
|
|
195
|
+
const extension = path.extname(args.path);
|
|
196
|
+
const loader = extension === ".ts" || extension === ".mts" || extension === ".cts" ? "ts" : extension === ".tsx" ? "tsx" : extension === ".jsx" ? "jsx" : "js";
|
|
197
|
+
return {
|
|
198
|
+
contents: contents.replaceAll(
|
|
199
|
+
"import.meta.url",
|
|
200
|
+
JSON.stringify(pathToFileURL(args.path).href)
|
|
201
|
+
),
|
|
202
|
+
loader
|
|
203
|
+
};
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
}
|
|
122
208
|
function commandFor(file, projectRoot, platform) {
|
|
123
|
-
if (file.endsWith(".sh")) return { command: "bash", args: [file] };
|
|
124
209
|
const needsTsx = platform === "core" || importsSourceTypescript(file);
|
|
125
|
-
if (!needsTsx
|
|
210
|
+
if (!needsTsx) {
|
|
126
211
|
return { command: process.execPath, args: [file] };
|
|
127
212
|
}
|
|
128
213
|
const tsxBin = resolveTsxBin(projectRoot);
|
|
@@ -165,15 +250,15 @@ function importsSourceTypescriptFrom(file, visited) {
|
|
|
165
250
|
const specifier = match[1] ?? "";
|
|
166
251
|
if (specifier.endsWith(".ts")) return true;
|
|
167
252
|
if (!specifier.startsWith(".")) continue;
|
|
168
|
-
if (!specifier.endsWith(".mjs")
|
|
253
|
+
if (!specifier.endsWith(".mjs")) continue;
|
|
169
254
|
if (importsSourceTypescriptFrom(path.resolve(path.dirname(absolute), specifier), visited)) {
|
|
170
255
|
return true;
|
|
171
256
|
}
|
|
172
257
|
}
|
|
173
258
|
return false;
|
|
174
259
|
}
|
|
175
|
-
async function resolveLiveAdapter(platform, action) {
|
|
176
|
-
return firstExecutablePath(candidatePaths(platform, action));
|
|
260
|
+
async function resolveLiveAdapter(platform, action, runtimeEnv) {
|
|
261
|
+
return firstExecutablePath(candidatePaths(platform, action, runtimeEnv));
|
|
177
262
|
}
|
|
178
263
|
const CORE_WORKSPACE_PACKAGES = [
|
|
179
264
|
"@metamask/base-controller",
|
|
@@ -205,8 +290,8 @@ async function platformAdapterEnv(platform, projectRoot, tempDir) {
|
|
|
205
290
|
NODE_OPTIONS: corePnpNodeOptions(projectRoot, process.env.NODE_OPTIONS)
|
|
206
291
|
};
|
|
207
292
|
}
|
|
208
|
-
async function runLiveAdapterScript({ platform, action, node, context }) {
|
|
209
|
-
const script = await resolveLiveAdapter(platform, action);
|
|
293
|
+
async function runLiveAdapterScript({ platform, action, node, context, prepared }) {
|
|
294
|
+
const script = prepared?.entryPath ?? await resolveLiveAdapter(platform, action, context?.env);
|
|
210
295
|
if (!script) return null;
|
|
211
296
|
const tempDir = await mkdtemp(path.join(os.tmpdir(), "mm-harness-live-adapter-"));
|
|
212
297
|
const inputPath = path.join(tempDir, "input.json");
|
|
@@ -225,7 +310,7 @@ async function runLiveAdapterScript({ platform, action, node, context }) {
|
|
|
225
310
|
};
|
|
226
311
|
await writeFile(inputPath, `${JSON.stringify(input, null, 2)}
|
|
227
312
|
`);
|
|
228
|
-
const command = commandFor(script, context.projectRoot, platform);
|
|
313
|
+
const command = prepared ? commandForPrepared(context.projectRoot, platform) : commandFor(script, context.projectRoot, platform);
|
|
229
314
|
const platformEnv = await platformAdapterEnv(platform, context.projectRoot, tempDir);
|
|
230
315
|
const result = await runProcess(command.command, [...command.args, inputPath], {
|
|
231
316
|
cwd: context.projectRoot,
|
|
@@ -236,7 +321,8 @@ async function runLiveAdapterScript({ platform, action, node, context }) {
|
|
|
236
321
|
METAMASK_RECIPE_ADAPTER_INPUT: inputPath,
|
|
237
322
|
METAMASK_RECIPE_ADAPTER_OUTPUT: outputPath
|
|
238
323
|
},
|
|
239
|
-
timeoutMs: Number(node.live_adapter_timeout_ms ?? node.timeout_ms ?? 6e4)
|
|
324
|
+
timeoutMs: Number(node.live_adapter_timeout_ms ?? node.timeout_ms ?? 6e4),
|
|
325
|
+
...prepared ? { stdin: prepared.sourceText } : {}
|
|
240
326
|
});
|
|
241
327
|
try {
|
|
242
328
|
if (result.timedOut) {
|
|
@@ -258,7 +344,27 @@ async function runLiveAdapterScript({ platform, action, node, context }) {
|
|
|
258
344
|
await rm(tempDir, { recursive: true, force: true });
|
|
259
345
|
}
|
|
260
346
|
}
|
|
347
|
+
function commandForPrepared(projectRoot, platform) {
|
|
348
|
+
if (platform !== "core") {
|
|
349
|
+
return { command: process.execPath, args: ["--input-type=module", "-"] };
|
|
350
|
+
}
|
|
351
|
+
const tsxBin = resolveTsxBin(projectRoot);
|
|
352
|
+
if (!tsxBin) {
|
|
353
|
+
const error = new Error(
|
|
354
|
+
`this action imports TypeScript from the checkout but no tsx runtime was found.
|
|
355
|
+
Next: run 'yarn install' in ${projectRoot}`
|
|
356
|
+
);
|
|
357
|
+
error.exitCode = 2;
|
|
358
|
+
throw error;
|
|
359
|
+
}
|
|
360
|
+
return { command: tsxBin, args: ["--input-type=module", "-"] };
|
|
361
|
+
}
|
|
362
|
+
function isPathWithin(root, candidate) {
|
|
363
|
+
const relative = path.relative(root, candidate);
|
|
364
|
+
return relative === "" || relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
|
|
365
|
+
}
|
|
261
366
|
export {
|
|
367
|
+
prepareLiveAdapterScript,
|
|
262
368
|
resolveLiveAdapter,
|
|
263
369
|
runLiveAdapterScript
|
|
264
370
|
};
|