@deeeed/metamask-harness 0.74.0 → 0.76.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -1
- package/README.md +1 -1
- package/adapters/shared/ensure-runner-deps.sh +15 -3
- package/adapters/shared/reap-checkout-metros.sh +9 -0
- package/dist/action-values.js +15 -0
- package/dist/adapters/core/actions.js +20 -0
- package/dist/adapters/core/live-script-env.js +36 -0
- package/dist/adapters/core/overlay.js +34 -0
- package/dist/adapters/core/readiness.js +8 -0
- package/dist/adapters/core/run.js +54 -0
- package/dist/adapters/core/surface.js +12 -1
- package/dist/adapters/extension/action-inputs.js +206 -0
- package/dist/adapters/extension/actions.js +51 -0
- package/dist/adapters/extension/console-capture.js +13 -1
- package/dist/adapters/extension/doctor-checks.js +135 -0
- package/dist/adapters/extension/fixtures.js +103 -0
- package/dist/adapters/extension/launch.js +315 -0
- package/dist/adapters/extension/launched-browser.js +51 -0
- package/dist/adapters/extension/overlay.js +55 -0
- package/dist/adapters/extension/readiness.js +32 -0
- package/dist/adapters/extension/reload.js +40 -0
- package/dist/adapters/extension/run.js +105 -0
- package/dist/adapters/extension/surface.js +31 -3
- package/dist/adapters/harness-freshness.js +4 -2
- package/dist/adapters/mobile/actions.js +54 -0
- package/dist/adapters/mobile/devices.js +21 -0
- package/dist/adapters/mobile/doctor-checks.js +40 -0
- package/dist/adapters/mobile/fixtures.js +141 -0
- package/dist/adapters/mobile/launch.js +192 -0
- package/dist/adapters/mobile/lifecycle.js +300 -0
- package/dist/adapters/mobile/overlay.js +27 -0
- package/dist/adapters/mobile/prepare-target.js +38 -0
- package/dist/adapters/mobile/readiness.js +39 -0
- package/dist/adapters/mobile/reload.js +15 -0
- package/dist/adapters/mobile/run.js +156 -0
- package/dist/adapters/mobile/runtime-identity.js +34 -0
- package/dist/adapters/mobile/surface.js +39 -3
- package/dist/adapters/mobile/ui.js +701 -0
- package/dist/adapters/performance/cdp-trace.js +24 -6
- package/dist/adapters/performance/device-platform.js +10 -0
- package/dist/adapters/registry.js +1 -1
- package/dist/adapters/terminal/actions.js +43 -0
- package/dist/adapters/terminal/console-capture.js +14 -1
- package/dist/adapters/terminal/doctor-checks.js +22 -0
- package/dist/adapters/terminal/overlay.js +18 -0
- package/dist/adapters/terminal/readiness.js +11 -0
- package/dist/adapters/terminal/run.js +30 -0
- package/dist/adapters/terminal/surface.js +21 -3
- package/dist/adapters.js +32 -824
- package/dist/cli-commands.js +1 -0
- package/dist/cli.js +3 -1
- package/dist/command-contract.js +3 -0
- package/dist/commands/call.js +2 -5
- package/dist/commands/debug.js +6 -25
- package/dist/commands/device-target.js +2 -1
- package/dist/commands/doctor.js +26 -137
- package/dist/commands/fixtures.js +13 -217
- package/dist/commands/launch/device.js +2 -6
- package/dist/commands/launch/extension.js +3 -303
- package/dist/commands/launch/index.js +1 -1
- package/dist/commands/launch/mobile.js +2 -190
- package/dist/commands/launch/quote.js +7 -0
- package/dist/commands/logs.js +1 -1
- package/dist/commands/parse-args.js +9 -4
- package/dist/commands/pin.js +226 -0
- package/dist/commands/prepare.js +23 -53
- package/dist/commands/provision.js +3 -7
- package/dist/commands/reload.js +8 -47
- package/dist/commands/run-engine.js +38 -138
- package/dist/commands/run-report.js +4 -48
- package/dist/commands/run.js +6 -48
- package/dist/commands/runtime-decision.js +5 -27
- package/dist/commands/self-test.js +1 -3
- package/dist/commands/status.js +20 -28
- package/dist/commands/stop.js +1 -1
- package/dist/doctor.js +12 -120
- package/dist/execution-provenance.js +3 -2
- package/dist/harness-pin.js +10 -0
- package/dist/harness.js +31 -100
- package/dist/heal-bounds.js +3 -2
- package/dist/live-adapter-contract.js +8 -34
- package/dist/manifest.js +3 -5
- package/dist/metamask-action-validation.js +5 -196
- package/dist/mm-harness-cli.js +30 -4
- package/dist/network-observation.js +10 -3
- package/dist/paths.js +8 -2
- package/dist/performance-observation.js +15 -22
- package/dist/pr-body/render.js +3 -1
- package/dist/recording-target.js +4 -39
- package/dist/run-diagnostics.js +21 -60
- package/dist/run-recording.js +5 -4
- package/dist/runner.js +20 -368
- package/dist/runtime-context.js +12 -17
- package/docs/CONTRIBUTING.md +29 -7
- package/package.json +2 -1
- package/scripts/completions.sh +2 -1
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { detectAdapter } from "../harness.js";
|
|
2
2
|
import { assertAdapter } from "../paths.js";
|
|
3
3
|
import { getAdapterSurface } from "../adapters/surface.js";
|
|
4
|
-
import {
|
|
5
|
-
import { ADAPTER_DETECT_NEXT, usageOut, writeInteractiveProgress } from "./shared.js";
|
|
4
|
+
import { ADAPTER_DETECT_NEXT, usageOut } from "./shared.js";
|
|
6
5
|
import {
|
|
7
6
|
applyWatcherPortOption,
|
|
8
7
|
optionFlag,
|
|
@@ -27,10 +26,7 @@ async function handleProvision({ positional, options, rawArgv }) {
|
|
|
27
26
|
const surface = getAdapterSurface(adapter);
|
|
28
27
|
const rerunCommand = provisionRerunCommand(rawArgv, options, adapter, target);
|
|
29
28
|
const platform = optionString(options, "platform") ?? optionString(options, "devicePlatform") ?? (positional[0] === "runway" ? positional[1] : positional[0]) ?? "ios";
|
|
30
|
-
|
|
31
|
-
writeInteractiveProgress(json, `\u2192 provision mobile ${platform} \u2014 resolving simulator and Runway artifact`);
|
|
32
|
-
if (!optionFlag(options, "resolveOnly")) clearMobileReleaseArtifactState(target);
|
|
33
|
-
}
|
|
29
|
+
surface.runwayProvision.before?.(target, { json, platform, resolveOnly: optionFlag(options, "resolveOnly") });
|
|
34
30
|
const result = await surface.runwayProvision.run(target, {
|
|
35
31
|
json,
|
|
36
32
|
platform,
|
|
@@ -50,7 +46,7 @@ async function handleProvision({ positional, options, rawArgv }) {
|
|
|
50
46
|
});
|
|
51
47
|
const provisionedSimulator = typeof result.simulator === "object" && result.simulator ? result.simulator : void 0;
|
|
52
48
|
const provisionedDevice = typeof provisionedSimulator?.udid === "string" ? provisionedSimulator.udid : typeof provisionedSimulator?.name === "string" ? provisionedSimulator.name : void 0;
|
|
53
|
-
const next = result.status === "pass" &&
|
|
49
|
+
const next = result.status === "pass" && surface.runwayProvision.launchNext && result.resolveOnly !== true ? `mm-harness launch ${String(result.platform ?? platform)} --adapter ${adapter} --target ${shellQuote(target)}` + (provisionedDevice ? ` --device ${shellQuoteArg(provisionedDevice)}` : "") : void 0;
|
|
54
50
|
const output = next ? { ...result, next } : result;
|
|
55
51
|
if (json) {
|
|
56
52
|
console.log(JSON.stringify(output, null, 2));
|
package/dist/commands/reload.js
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import { runnerDir } from "../paths.js";
|
|
3
1
|
import { getAdapterSurface } from "../adapters/surface.js";
|
|
4
2
|
import {
|
|
5
3
|
ADAPTER_DETECT_NEXT,
|
|
6
|
-
EXIT,
|
|
7
4
|
flag,
|
|
8
5
|
parseFlags,
|
|
9
6
|
resolveAdapter,
|
|
10
|
-
spawnScript,
|
|
11
7
|
targetOf,
|
|
12
8
|
usageOut
|
|
13
9
|
} from "./shared.js";
|
|
@@ -25,55 +21,20 @@ async function handleReload(argv) {
|
|
|
25
21
|
ADAPTER_DETECT_NEXT
|
|
26
22
|
);
|
|
27
23
|
}
|
|
28
|
-
|
|
24
|
+
const surface = getAdapterSurface(adapter);
|
|
25
|
+
if (surface.headless) {
|
|
29
26
|
return usageOut(
|
|
30
27
|
json,
|
|
31
28
|
"reload",
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
`${adapter} is headless; there is no app runtime to reload.`,
|
|
30
|
+
`mm-harness run <${adapter}-recipe>`
|
|
34
31
|
);
|
|
35
32
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (!process.env.CDP_PORT) {
|
|
39
|
-
return usageOut(
|
|
40
|
-
json,
|
|
41
|
-
"reload",
|
|
42
|
-
"the Extension CDP port could not be resolved.",
|
|
43
|
-
"mm-harness launch"
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
const script2 = path.join(runnerDir, "adapters/extension/reattach.sh");
|
|
47
|
-
const args2 = ["--target", target, "--cdp-port", process.env.CDP_PORT];
|
|
48
|
-
if (process.env.WATCHER_PORT) {
|
|
49
|
-
args2.push("--watcher-port", process.env.WATCHER_PORT);
|
|
50
|
-
}
|
|
51
|
-
const result2 = spawnScript(script2, args2, target, json);
|
|
52
|
-
if (json) {
|
|
53
|
-
console.log(
|
|
54
|
-
JSON.stringify(
|
|
55
|
-
{
|
|
56
|
-
ok: result2.status === 0,
|
|
57
|
-
adapter,
|
|
58
|
-
method: "cdp-reattach",
|
|
59
|
-
command: "reload",
|
|
60
|
-
cdpPort: Number(process.env.CDP_PORT),
|
|
61
|
-
...result2.status === 0 ? {} : { error: result2.output.slice(-4e3) || "Extension reload failed." }
|
|
62
|
-
},
|
|
63
|
-
null,
|
|
64
|
-
2
|
|
65
|
-
)
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
return result2.status === 0 ? EXIT.ok : EXIT.runtime;
|
|
33
|
+
if (!surface.reload) {
|
|
34
|
+
return usageOut(json, "reload", `reload does not support the ${adapter} adapter.`, surface.hints.launch);
|
|
69
35
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (process.env.WATCHER_PORT) args.push("--port", process.env.WATCHER_PORT);
|
|
73
|
-
if (json) args.push("--json");
|
|
74
|
-
const result = spawnScript(process.execPath, [script, ...args], target, json);
|
|
75
|
-
if (json) process.stdout.write(result.output);
|
|
76
|
-
return result.status === 0 ? EXIT.ok : EXIT.runtime;
|
|
36
|
+
surface.resolveSlotPorts(target);
|
|
37
|
+
return surface.reload(target, json);
|
|
77
38
|
}
|
|
78
39
|
export {
|
|
79
40
|
handleReload
|
|
@@ -14,9 +14,6 @@ import {
|
|
|
14
14
|
import { resolveActionManifest, validateManifest } from "../manifest.js";
|
|
15
15
|
import { getAdapterSurface } from "../adapters/surface.js";
|
|
16
16
|
import { extensionProductConfigBlock } from "../adapters/extension/product-config.js";
|
|
17
|
-
import {
|
|
18
|
-
requireCurrentMobileReleaseArtifactState
|
|
19
|
-
} from "../adapters/mobile/release-artifact-state.js";
|
|
20
17
|
import {
|
|
21
18
|
importRecipeHarness,
|
|
22
19
|
importRecipeProtocol,
|
|
@@ -121,13 +118,8 @@ async function runRecipe(adapter, recipe, artifactsDir, projectRoot, actionManif
|
|
|
121
118
|
);
|
|
122
119
|
}
|
|
123
120
|
} finally {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
await hideMobileHudOnTeardown(
|
|
127
|
-
projectRoot,
|
|
128
|
-
recipeRunEnv(adapter, runtimeOptions)
|
|
129
|
-
);
|
|
130
|
-
}
|
|
121
|
+
const teardown = getAdapterSurface(adapter).run?.teardown;
|
|
122
|
+
if (teardown) await teardown(projectRoot, recipeRunEnv(adapter, runtimeOptions));
|
|
131
123
|
}
|
|
132
124
|
let recordingError;
|
|
133
125
|
if (executionError === void 0 && result) {
|
|
@@ -240,11 +232,12 @@ async function resolveRecipeExecution(adapter, recipe, artifactsDir, projectRoot
|
|
|
240
232
|
projectRoot,
|
|
241
233
|
rootRecipe: recipeDocument
|
|
242
234
|
});
|
|
243
|
-
const
|
|
235
|
+
const surface = getAdapterSurface(adapter);
|
|
236
|
+
const suppressAutoHud = surface.run?.autoHud?.() === false;
|
|
244
237
|
const runnerOptions = {
|
|
245
238
|
quietStdout: runtimeOptions.stdoutIsMachineContract === true,
|
|
246
239
|
suppressLibraryResolutionLogs: runtimeOptions.suppressLibraryResolutionLogs,
|
|
247
|
-
autoHud:
|
|
240
|
+
autoHud: suppressAutoHud ? false : trust.source && trust.source.trust !== "trusted" ? false : runtimeOptions.autoHud,
|
|
248
241
|
onActionEvent: runtimeOptions.onActionEvent,
|
|
249
242
|
actionSources,
|
|
250
243
|
// A direct engineer invocation is the explicit trust boundary for a
|
|
@@ -256,7 +249,7 @@ async function resolveRecipeExecution(adapter, recipe, artifactsDir, projectRoot
|
|
|
256
249
|
...runnerOptions,
|
|
257
250
|
trustedMutation
|
|
258
251
|
});
|
|
259
|
-
const useFramedExtensionRecording =
|
|
252
|
+
const useFramedExtensionRecording = surface.recording?.framed !== void 0 && recordVideo === "full-run" && trust.source?.trust !== "untrusted" && trust.source?.trust !== "unknown" && captureHelperSupportsRecordSessionSnapshots(projectRoot);
|
|
260
253
|
const runRequest = {
|
|
261
254
|
...recipeDocument !== void 0 ? { recipeDocument } : absoluteRecipePath ? { recipePath: absoluteRecipePath } : { recipeDocument: recipe },
|
|
262
255
|
artifactsDir: absoluteArtifactsDir,
|
|
@@ -447,31 +440,22 @@ function executionLibrarySources(sources, invocationTrust) {
|
|
|
447
440
|
};
|
|
448
441
|
});
|
|
449
442
|
}
|
|
443
|
+
const RUN_ENV_KEYS = [
|
|
444
|
+
"CDP_PORT",
|
|
445
|
+
"RECIPE_CDP_PORT",
|
|
446
|
+
"WATCHER_PORT",
|
|
447
|
+
"METRO_PORT",
|
|
448
|
+
"METAMASK_RECIPE_EXTENSION_AUTOLAUNCH"
|
|
449
|
+
];
|
|
450
450
|
function activateRecipeRuntimeEnvironment(adapter, projectRoot, runtimeOptions) {
|
|
451
|
-
const
|
|
452
|
-
const
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
const
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
const previousAndroidSerial = process.env.ANDROID_SERIAL;
|
|
460
|
-
const explicitMobileDeviceEnv = adapter === "mobile" && process.env.MM_HARNESS_EXPLICIT_PLATFORM ? Object.fromEntries(
|
|
461
|
-
[
|
|
462
|
-
"PLATFORM",
|
|
463
|
-
"MM_HARNESS_EXPLICIT_PLATFORM",
|
|
464
|
-
"IOS_SIMULATOR",
|
|
465
|
-
"SIM_UDID",
|
|
466
|
-
"ADB_SERIAL",
|
|
467
|
-
"ANDROID_SERIAL",
|
|
468
|
-
"ANDROID_DEVICE",
|
|
469
|
-
"ANDROID_TARGET_DEVICE_NAME"
|
|
470
|
-
].map((key) => [key, process.env[key]])
|
|
471
|
-
) : void 0;
|
|
472
|
-
getAdapterSurface(adapter).resolveSlotPorts(projectRoot);
|
|
473
|
-
if (explicitMobileDeviceEnv) {
|
|
474
|
-
for (const [key, value] of Object.entries(explicitMobileDeviceEnv)) {
|
|
451
|
+
const surface = getAdapterSurface(adapter);
|
|
452
|
+
const previousEnv = Object.fromEntries(
|
|
453
|
+
[...RUN_ENV_KEYS, ...surface.run?.envKeys ?? []].map((key) => [key, process.env[key]])
|
|
454
|
+
);
|
|
455
|
+
const pinnedEnv = surface.run?.pinnedEnv?.();
|
|
456
|
+
surface.resolveSlotPorts(projectRoot);
|
|
457
|
+
if (pinnedEnv) {
|
|
458
|
+
for (const [key, value] of Object.entries(pinnedEnv)) {
|
|
475
459
|
restoreEnv(key, value);
|
|
476
460
|
}
|
|
477
461
|
}
|
|
@@ -486,37 +470,9 @@ function activateRecipeRuntimeEnvironment(adapter, projectRoot, runtimeOptions)
|
|
|
486
470
|
if (runtimeOptions.launchExistingDist) {
|
|
487
471
|
process.env.METAMASK_RECIPE_EXTENSION_AUTOLAUNCH = "1";
|
|
488
472
|
}
|
|
489
|
-
|
|
490
|
-
const state = requireCurrentMobileReleaseArtifactState(projectRoot);
|
|
491
|
-
if (state) {
|
|
492
|
-
const selected = process.env.ADB_SERIAL ?? process.env.ANDROID_SERIAL;
|
|
493
|
-
if (selected && selected !== state.deviceSerial) {
|
|
494
|
-
throw new Error(
|
|
495
|
-
`The Mobile release artifact is installed on ${state.deviceSerial}, not ${selected}. Next: rerun runtime-launch for ${selected}.`
|
|
496
|
-
);
|
|
497
|
-
}
|
|
498
|
-
process.env.METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME = "1";
|
|
499
|
-
process.env.ANDROID_PACKAGE_ID = state.packageId;
|
|
500
|
-
process.env.ADB_SERIAL = state.deviceSerial;
|
|
501
|
-
process.env.ANDROID_SERIAL = state.deviceSerial;
|
|
502
|
-
}
|
|
503
|
-
}
|
|
473
|
+
surface.run?.activateEnv?.(projectRoot);
|
|
504
474
|
return () => {
|
|
505
|
-
restoreEnv(
|
|
506
|
-
restoreEnv("RECIPE_CDP_PORT", previousRecipeCdpPort);
|
|
507
|
-
restoreEnv("WATCHER_PORT", previousWatcherPort);
|
|
508
|
-
restoreEnv("METRO_PORT", previousMetroPort);
|
|
509
|
-
restoreEnv(
|
|
510
|
-
"METAMASK_RECIPE_EXTENSION_AUTOLAUNCH",
|
|
511
|
-
previousExtensionAutolaunch
|
|
512
|
-
);
|
|
513
|
-
restoreEnv(
|
|
514
|
-
"METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME",
|
|
515
|
-
previousMobileOpaqueRuntime
|
|
516
|
-
);
|
|
517
|
-
restoreEnv("ANDROID_PACKAGE_ID", previousAndroidPackageId);
|
|
518
|
-
restoreEnv("ADB_SERIAL", previousAdbSerial);
|
|
519
|
-
restoreEnv("ANDROID_SERIAL", previousAndroidSerial);
|
|
475
|
+
for (const [key, value] of Object.entries(previousEnv)) restoreEnv(key, value);
|
|
520
476
|
};
|
|
521
477
|
}
|
|
522
478
|
function actionSourceMap(sources) {
|
|
@@ -539,42 +495,11 @@ function recipeRunEnv(adapter, runtimeOptions = {}) {
|
|
|
539
495
|
PLATFORM: process.env.PLATFORM,
|
|
540
496
|
MM_HARNESS_EXPLICIT_PLATFORM: process.env.MM_HARNESS_EXPLICIT_PLATFORM
|
|
541
497
|
};
|
|
542
|
-
|
|
543
|
-
return
|
|
544
|
-
...base,
|
|
545
|
-
WATCHER_PORT: process.env.WATCHER_PORT ?? base.CDP_PORT ?? base.RECIPE_CDP_PORT,
|
|
546
|
-
METRO_PORT: process.env.METRO_PORT ?? process.env.WATCHER_PORT ?? base.CDP_PORT ?? base.RECIPE_CDP_PORT,
|
|
547
|
-
IOS_SIMULATOR: process.env.IOS_SIMULATOR,
|
|
548
|
-
SIM_UDID: process.env.SIM_UDID,
|
|
549
|
-
ANDROID_DEVICE: process.env.ANDROID_DEVICE,
|
|
550
|
-
ANDROID_TARGET_DEVICE_NAME: process.env.ANDROID_TARGET_DEVICE_NAME,
|
|
551
|
-
ADB_SERIAL: process.env.ADB_SERIAL,
|
|
552
|
-
ANDROID_SERIAL: process.env.ANDROID_SERIAL,
|
|
553
|
-
ANDROID_PACKAGE_ID: process.env.ANDROID_PACKAGE_ID,
|
|
554
|
-
METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME: process.env.METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME
|
|
555
|
-
};
|
|
498
|
+
const childEnv = getAdapterSurface(adapter).run?.childEnv;
|
|
499
|
+
return childEnv ? childEnv(base) : base;
|
|
556
500
|
}
|
|
557
501
|
async function prepareRuntimeIfNeeded(adapter, projectRoot, runtimeOptions) {
|
|
558
|
-
|
|
559
|
-
return;
|
|
560
|
-
const { releaseArtifactState, runtimeDistCheck } = await import("../adapters/extension/runtime-decision.js");
|
|
561
|
-
const artifact = releaseArtifactState(projectRoot);
|
|
562
|
-
const runtimeSnapshot = runtimeDistCheck(projectRoot, artifact);
|
|
563
|
-
if (artifact.status !== "none") {
|
|
564
|
-
if (artifact.status === "valid" && runtimeSnapshot.status === "fresh") return;
|
|
565
|
-
throw new Error(
|
|
566
|
-
"The loaded release artifact identity is invalid or stale. Next: rerun runtime-launch with the same artifact source."
|
|
567
|
-
);
|
|
568
|
-
}
|
|
569
|
-
const { prepareExtensionRuntime } = await import("../adapters/extension/runtime.js");
|
|
570
|
-
await prepareExtensionRuntime({
|
|
571
|
-
projectRoot,
|
|
572
|
-
cdpPort: runtimeOptions.cdpPort,
|
|
573
|
-
slot: runtimeOptions.slot,
|
|
574
|
-
launchExistingDist: runtimeOptions.launchExistingDist === true,
|
|
575
|
-
validationRuntimeDir: runtimeOptions.validationRuntimeDir,
|
|
576
|
-
pageMode: "home-or-sidepanel"
|
|
577
|
-
});
|
|
502
|
+
await getAdapterSurface(adapter).run?.prepareRuntime?.(projectRoot, runtimeOptions);
|
|
578
503
|
}
|
|
579
504
|
function restoreEnv(key, value) {
|
|
580
505
|
if (value === void 0) delete process.env[key];
|
|
@@ -1051,7 +976,15 @@ async function prepareHeal(adapter, target, options, json, prepareOptions = {})
|
|
|
1051
976
|
return EXIT.usage;
|
|
1052
977
|
}
|
|
1053
978
|
const state = newHealState();
|
|
1054
|
-
const
|
|
979
|
+
const runtimeCheck = getAdapterSurface(adapter).run?.runtimeCheck?.({
|
|
980
|
+
target,
|
|
981
|
+
options,
|
|
982
|
+
heal,
|
|
983
|
+
state,
|
|
984
|
+
json,
|
|
985
|
+
appRestartAuthored: prepareOptions.appRestartAuthored,
|
|
986
|
+
onRecovery: (code) => prepareOptions.onPhase?.("recover", { code })
|
|
987
|
+
});
|
|
1055
988
|
prepareOptions.onPhase?.("install");
|
|
1056
989
|
const ensured = await ensureOverlay(
|
|
1057
990
|
adapter,
|
|
@@ -1089,41 +1022,8 @@ async function prepareHeal(adapter, target, options, json, prepareOptions = {})
|
|
|
1089
1022
|
return EXIT.infra;
|
|
1090
1023
|
}
|
|
1091
1024
|
prepareOptions.onPhase?.("healthcheck");
|
|
1092
|
-
if (
|
|
1093
|
-
const
|
|
1094
|
-
const previousRecipeCdpPort = process.env.RECIPE_CDP_PORT;
|
|
1095
|
-
const previousWatcherPort = process.env.WATCHER_PORT;
|
|
1096
|
-
const explicitCdpPort = optionString(options, "cdpPort");
|
|
1097
|
-
const explicitWatcherPort = optionString(options, "watcherPort") ?? optionString(options, "metroPort");
|
|
1098
|
-
if (explicitCdpPort) {
|
|
1099
|
-
process.env.CDP_PORT = explicitCdpPort;
|
|
1100
|
-
process.env.RECIPE_CDP_PORT = explicitCdpPort;
|
|
1101
|
-
}
|
|
1102
|
-
if (explicitWatcherPort) process.env.WATCHER_PORT = explicitWatcherPort;
|
|
1103
|
-
try {
|
|
1104
|
-
const current = await ensureExtensionProofRuntime(
|
|
1105
|
-
target,
|
|
1106
|
-
heal,
|
|
1107
|
-
state,
|
|
1108
|
-
json,
|
|
1109
|
-
{
|
|
1110
|
-
onRecovery: (code) => prepareOptions.onPhase?.("recover", { code })
|
|
1111
|
-
}
|
|
1112
|
-
);
|
|
1113
|
-
if (current !== null) return current;
|
|
1114
|
-
} finally {
|
|
1115
|
-
restoreEnv("CDP_PORT", previousCdpPort);
|
|
1116
|
-
restoreEnv("RECIPE_CDP_PORT", previousRecipeCdpPort);
|
|
1117
|
-
restoreEnv("WATCHER_PORT", previousWatcherPort);
|
|
1118
|
-
}
|
|
1119
|
-
}
|
|
1120
|
-
if (adapter === "mobile" && mobileArtifact === null && prepareOptions.skipMobileSourceFreshness !== true) {
|
|
1121
|
-
const current = await ensureMobileProofRuntime(
|
|
1122
|
-
target,
|
|
1123
|
-
heal,
|
|
1124
|
-
state,
|
|
1125
|
-
json
|
|
1126
|
-
);
|
|
1025
|
+
if (runtimeCheck) {
|
|
1026
|
+
const current = await runtimeCheck();
|
|
1127
1027
|
if (current !== null) return current;
|
|
1128
1028
|
}
|
|
1129
1029
|
return { state, heal };
|
|
@@ -1372,7 +1272,7 @@ async function executeWithHealBounds(exec, target, state) {
|
|
|
1372
1272
|
};
|
|
1373
1273
|
}
|
|
1374
1274
|
function emitHealViolation(json, command, result, violation, state, adapter) {
|
|
1375
|
-
const userAction = adapter
|
|
1275
|
+
const userAction = (adapter ? getAdapterSurface(adapter).run?.violationUserAction?.(violation) : void 0) ?? violation.userAction ?? `inspect ${shellQuoteArg(result.summaryPath)} and ${shellQuoteArg(result.tracePath)}; fix the application or recipe failure before retrying`;
|
|
1376
1276
|
if (json) {
|
|
1377
1277
|
console.log(
|
|
1378
1278
|
JSON.stringify(
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
-
import { createRequire } from "node:module";
|
|
3
2
|
import path from "node:path";
|
|
4
3
|
import { execFileSync } from "node:child_process";
|
|
5
|
-
import {
|
|
4
|
+
import { getAdapterSurface } from "../adapters/surface.js";
|
|
6
5
|
import { isRecord } from "./parse-args.js";
|
|
7
6
|
function writeRunReport(result) {
|
|
8
7
|
const summaryPath = String(result.summaryPath);
|
|
@@ -95,33 +94,6 @@ function indexProductProvenanceArtifact(artifactManifestPath, target, adapter, b
|
|
|
95
94
|
`
|
|
96
95
|
);
|
|
97
96
|
}
|
|
98
|
-
function readRecord(file) {
|
|
99
|
-
try {
|
|
100
|
-
const recorded = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
101
|
-
return isRecord(recorded) ? recorded : null;
|
|
102
|
-
} catch {
|
|
103
|
-
return null;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
const browserCdp = createRequire(import.meta.url)("../../adapters/shared/browser-cdp.cjs");
|
|
107
|
-
function verifyLaunch(record, cdpPort) {
|
|
108
|
-
const launch = isRecord(record.launch) ? record.launch : {};
|
|
109
|
-
const pid = Number(launch.pid);
|
|
110
|
-
const port = Number(launch.cdpPort);
|
|
111
|
-
if (!Number.isInteger(pid) || !Number.isInteger(port) || typeof launch.profile !== "string" || typeof launch.processStartedAt !== "string") {
|
|
112
|
-
return "the browser record has no process identity (pid, start time, profile, CDP port)";
|
|
113
|
-
}
|
|
114
|
-
if (cdpPort && port !== Number(cdpPort)) return `the browser record is for CDP port ${port}, not ${cdpPort}`;
|
|
115
|
-
try {
|
|
116
|
-
const owner = browserCdp.cdpOwner(port, launch.profile);
|
|
117
|
-
if (owner.pid !== pid || owner.startedAt !== launch.processStartedAt) {
|
|
118
|
-
return `CDP port ${port} is now held by another browser process than the recorded pid ${pid}`;
|
|
119
|
-
}
|
|
120
|
-
} catch (error) {
|
|
121
|
-
return error instanceof Error ? error.message : String(error);
|
|
122
|
-
}
|
|
123
|
-
return null;
|
|
124
|
-
}
|
|
125
97
|
function resolveTemplatedPort(value, params, recipe) {
|
|
126
98
|
let resolved = value;
|
|
127
99
|
const template = /^\{\{\s*params\.([\w-]+)\s*\}\}$/u.exec(String(value).trim());
|
|
@@ -155,26 +127,11 @@ function recipeCdpPorts(request) {
|
|
|
155
127
|
return [...ports];
|
|
156
128
|
}
|
|
157
129
|
function executedBrowser(adapter, target, artifactManifestPath, ports) {
|
|
158
|
-
|
|
130
|
+
const launched = getAdapterSurface(adapter).run?.launchedBrowser;
|
|
131
|
+
if (!launched) return null;
|
|
159
132
|
if (ports.length === 0) return { boundTo: "none", reason: "the run had no known CDP port" };
|
|
160
133
|
if (ports.length > 1) return { boundTo: "none", reason: `the run drove several CDP ports (${ports.join(", ")})` };
|
|
161
|
-
return
|
|
162
|
-
}
|
|
163
|
-
function launchedBrowser(target, artifactsDir, cdpPort) {
|
|
164
|
-
const runtime = readRecord(path.join(artifactsDir, "extension-runtime", "runtime.json"));
|
|
165
|
-
const autolaunched = runtime && isRecord(runtime.browser) ? runtime.browser : null;
|
|
166
|
-
const candidates = [];
|
|
167
|
-
if (autolaunched) candidates.push([autolaunched, "run-autolaunch"]);
|
|
168
|
-
const slot = readRecord(path.join(path.resolve(target), recipeRuntimeDir(), "browser-resolution.json"));
|
|
169
|
-
if (slot) candidates.push([slot, "slot-runtime"]);
|
|
170
|
-
if (candidates.length === 0) return null;
|
|
171
|
-
const reasons = [];
|
|
172
|
-
for (const [record, boundTo] of candidates) {
|
|
173
|
-
const problem = verifyLaunch(record, cdpPort);
|
|
174
|
-
if (problem === null) return { ...record, boundTo };
|
|
175
|
-
reasons.push(`${boundTo}: ${problem}`);
|
|
176
|
-
}
|
|
177
|
-
return { boundTo: "none", reason: reasons.join("; ") };
|
|
134
|
+
return launched(target, path.dirname(artifactManifestPath), ports[0]);
|
|
178
135
|
}
|
|
179
136
|
function renderRunReport(summary, entries) {
|
|
180
137
|
const lines = [
|
|
@@ -279,7 +236,6 @@ function indexRunReportArtifact(artifactManifestPath) {
|
|
|
279
236
|
export {
|
|
280
237
|
executedBrowser,
|
|
281
238
|
indexProductProvenanceArtifact,
|
|
282
|
-
launchedBrowser,
|
|
283
239
|
recipeCdpPorts,
|
|
284
240
|
writeRunReport
|
|
285
241
|
};
|
package/dist/commands/run.js
CHANGED
|
@@ -28,8 +28,6 @@ import {
|
|
|
28
28
|
import { handleDescribeRecipe, handleListExecutables } from "./list-executables.js";
|
|
29
29
|
import { applyDeviceTargeting } from "./device-target.js";
|
|
30
30
|
import { getAdapterSurface } from "../adapters/surface.js";
|
|
31
|
-
import { coreDependencyBlock } from "./core-readiness.js";
|
|
32
|
-
import { readMobileReleaseArtifactState } from "../adapters/mobile/release-artifact-state.js";
|
|
33
31
|
import {
|
|
34
32
|
indexProductProvenanceArtifact,
|
|
35
33
|
writeRunReport
|
|
@@ -244,7 +242,10 @@ async function handleRunInner({ positional, options }, stream) {
|
|
|
244
242
|
actionLibraryContextArgs(validated.librarySources)
|
|
245
243
|
);
|
|
246
244
|
}
|
|
247
|
-
const depsBlock =
|
|
245
|
+
const depsBlock = await getAdapterSurface(adapter).run?.dependencyBlock?.(target, {
|
|
246
|
+
recipe: validated.recipe,
|
|
247
|
+
librarySources: validated.librarySources
|
|
248
|
+
}) ?? null;
|
|
248
249
|
if (depsBlock) {
|
|
249
250
|
return emitRunUsageError(jsonOutput, stream, adapter, validated.recipeFile, depsBlock.code, depsBlock.message, depsBlock.userAction);
|
|
250
251
|
}
|
|
@@ -295,8 +296,7 @@ async function handleRunInner({ positional, options }, stream) {
|
|
|
295
296
|
}
|
|
296
297
|
try {
|
|
297
298
|
const prepared = await prepareHeal(adapter, target, options, machine, {
|
|
298
|
-
onPhase: (phase, fields) => stream.phase(phase, fields)
|
|
299
|
-
skipMobileSourceFreshness: adapter === "mobile" && readMobileReleaseArtifactState(target) !== null
|
|
299
|
+
onPhase: (phase, fields) => stream.phase(phase, fields)
|
|
300
300
|
});
|
|
301
301
|
if (typeof prepared === "number") {
|
|
302
302
|
stream.error({
|
|
@@ -511,48 +511,6 @@ function formatPreviewLine(line, out) {
|
|
|
511
511
|
const rest = match[2];
|
|
512
512
|
return `${out(status === "PASS" ? "ok" : "err", status)} ${rest}`;
|
|
513
513
|
}
|
|
514
|
-
async function recipeUsesCoreController(recipe, librarySources) {
|
|
515
|
-
if (objectUsesCoreController(recipe)) return true;
|
|
516
|
-
const refs = collectCallRefs(recipe);
|
|
517
|
-
if (refs.length === 0) return false;
|
|
518
|
-
const sources = librarySources ?? [];
|
|
519
|
-
if (sources.length === 0) return false;
|
|
520
|
-
const { loadRecipeLibraries } = await import("@farmslot/recipe-harness");
|
|
521
|
-
const recipes = (await loadRecipeLibraries(sources, { adapter: "core" })).recipes;
|
|
522
|
-
const visited = /* @__PURE__ */ new Set();
|
|
523
|
-
const visit = (ref) => {
|
|
524
|
-
if (visited.has(ref)) return false;
|
|
525
|
-
visited.add(ref);
|
|
526
|
-
const dependency = recipes.get(ref);
|
|
527
|
-
if (!dependency) return false;
|
|
528
|
-
if (objectUsesCoreController(dependency.document)) return true;
|
|
529
|
-
return collectCallRefs(dependency.document).some(visit);
|
|
530
|
-
};
|
|
531
|
-
return refs.some(visit);
|
|
532
|
-
}
|
|
533
|
-
function objectUsesCoreController(value) {
|
|
534
|
-
if (Array.isArray(value)) return value.some(objectUsesCoreController);
|
|
535
|
-
if (!isRecord(value)) return false;
|
|
536
|
-
if (actionUsesCoreController(value.action)) return true;
|
|
537
|
-
return Object.values(value).some(objectUsesCoreController);
|
|
538
|
-
}
|
|
539
|
-
function collectCallRefs(value) {
|
|
540
|
-
const refs = [];
|
|
541
|
-
const visit = (node) => {
|
|
542
|
-
if (Array.isArray(node)) {
|
|
543
|
-
for (const item of node) visit(item);
|
|
544
|
-
return;
|
|
545
|
-
}
|
|
546
|
-
if (!isRecord(node)) return;
|
|
547
|
-
if (node.action === "call" && typeof node.ref === "string" && node.ref) refs.push(node.ref);
|
|
548
|
-
for (const child of Object.values(node)) visit(child);
|
|
549
|
-
};
|
|
550
|
-
visit(value);
|
|
551
|
-
return [...new Set(refs)];
|
|
552
|
-
}
|
|
553
|
-
function actionUsesCoreController(action) {
|
|
554
|
-
return typeof action === "string" && action.startsWith("metamask.perps.");
|
|
555
|
-
}
|
|
556
514
|
async function handleRunPlan(recipeArg, params, options, stream) {
|
|
557
515
|
const json = optionFlag(options, "json");
|
|
558
516
|
const jsonOutput = json && !stream.enabled;
|
|
@@ -635,7 +593,7 @@ async function handleRunPlan(recipeArg, params, options, stream) {
|
|
|
635
593
|
status: "planned",
|
|
636
594
|
detail: "would auto-ensure the runtime overlay if missing (install phase)"
|
|
637
595
|
},
|
|
638
|
-
...adapter
|
|
596
|
+
...getAdapterSurface(adapter).headless ? [] : [
|
|
639
597
|
{
|
|
640
598
|
step: "launch.app",
|
|
641
599
|
confidence: "conditional",
|
|
@@ -1,32 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { decideMobileReadiness } from "../adapters/mobile/runtime-decision.js";
|
|
1
|
+
import { getAdapterSurface } from "../adapters/surface.js";
|
|
3
2
|
import {
|
|
4
3
|
adapterOption,
|
|
5
4
|
applyRuntimeDirOption,
|
|
6
5
|
optionFlag,
|
|
7
|
-
optionString,
|
|
8
|
-
parsePort,
|
|
9
6
|
targetPath
|
|
10
7
|
} from "./parse-args.js";
|
|
11
8
|
async function handleRuntimeDecision({ options }) {
|
|
12
9
|
applyRuntimeDirOption(options);
|
|
13
10
|
const adapter = adapterOption(options);
|
|
14
11
|
const target = targetPath(options);
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const watcherPort = watcherPortRaw === void 0 ? void 0 : parsePort(watcherPortRaw, "runtime-decision --watcher-port must be a port.");
|
|
18
|
-
const report2 = await decideMobileReadiness(target, {
|
|
19
|
-
watcherPort,
|
|
20
|
-
metroLog: optionString(options, "metroLog"),
|
|
21
|
-
platform: optionString(options, "platform") ?? process.env.PLATFORM ?? process.env.RECIPE_HARNESS_PLATFORM,
|
|
22
|
-
record: optionFlag(options, "record"),
|
|
23
|
-
preflightMode: optionString(options, "preflightMode")
|
|
24
|
-
});
|
|
25
|
-
if (optionFlag(options, "json")) console.log(JSON.stringify(report2, null, 2));
|
|
26
|
-
else console.log(`${report2.decision} ${report2.reasonCode} \u2014 ${report2.reasons[0] ?? ""}`);
|
|
27
|
-
return 0;
|
|
28
|
-
}
|
|
29
|
-
if (adapter !== "extension") {
|
|
12
|
+
const decide = getAdapterSurface(adapter).runtimeDecision;
|
|
13
|
+
if (!decide) {
|
|
30
14
|
const report2 = {
|
|
31
15
|
schemaVersion: 1,
|
|
32
16
|
adapter,
|
|
@@ -42,15 +26,9 @@ async function handleRuntimeDecision({ options }) {
|
|
|
42
26
|
else console.log(`unknown adapter-unsupported \u2014 ${report2.reasons[0]}`);
|
|
43
27
|
return 0;
|
|
44
28
|
}
|
|
45
|
-
const
|
|
46
|
-
const cdpPort = cdpPortRaw === void 0 ? void 0 : parsePort(cdpPortRaw, "runtime-decision --cdp-port must be a port.");
|
|
47
|
-
const report = await decideExtensionReadiness(target, {
|
|
48
|
-
cdpPort,
|
|
49
|
-
watchLog: optionString(options, "watchLog"),
|
|
50
|
-
record: optionFlag(options, "record")
|
|
51
|
-
});
|
|
29
|
+
const { report, line } = await decide(target, options);
|
|
52
30
|
if (optionFlag(options, "json")) console.log(JSON.stringify(report, null, 2));
|
|
53
|
-
else console.log(
|
|
31
|
+
else console.log(line);
|
|
54
32
|
return 0;
|
|
55
33
|
}
|
|
56
34
|
export {
|
|
@@ -25,9 +25,7 @@ async function runSelfTest(options) {
|
|
|
25
25
|
for (const adapter of ["mobile", "extension"]) {
|
|
26
26
|
const manifest = loadActionManifest(adapter);
|
|
27
27
|
const manifestValidation = await validateManifest(manifest);
|
|
28
|
-
const smokeRecipe = recipePath(
|
|
29
|
-
adapter === "mobile" ? "mobile/runner/smoke.recipe.json" : "extension/runner/smoke.recipe.json"
|
|
30
|
-
);
|
|
28
|
+
const smokeRecipe = recipePath(`${adapter}/runner/smoke.recipe.json`);
|
|
31
29
|
const artifactsDir = path.join(root, adapter);
|
|
32
30
|
const result = await runRecipe(adapter, smokeRecipe, artifactsDir, runnerDir, void 0, {
|
|
33
31
|
skipExtensionRuntimePrepare: true
|