@deeeed/metamask-harness 0.81.0 → 0.82.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 +17 -0
- package/adapters/manifest.json +51 -51
- package/adapters/{terminal → web-dapp}/cleanup.mjs +9 -9
- package/adapters/{terminal → web-dapp}/inject.mjs +14 -14
- package/adapters/{terminal → web-dapp}/launch.mjs +23 -24
- package/adapters/web-dapp/lib/blocked-hosts.mjs +24 -0
- package/adapters/{terminal → web-dapp}/lib/cdp-client.mjs +1 -1
- package/adapters/{terminal → web-dapp}/lib/display.mjs +1 -1
- package/adapters/{terminal → web-dapp}/lib/hud.mjs +1 -1
- package/adapters/web-dapp/lib/policy.mjs +79 -0
- package/adapters/{terminal → web-dapp}/lib/processes.mjs +8 -8
- package/adapters/{terminal → web-dapp}/lib/readiness.mjs +15 -25
- package/adapters/{terminal → web-dapp}/stop.mjs +5 -4
- package/adapters/{terminal → web-dapp}/verify.mjs +11 -15
- package/adapters/{terminal → web-dapp}/wallet-host.mjs +10 -11
- package/dist/adapters/extension/run.js +5 -28
- package/dist/adapters/plugin-surface.js +98 -0
- package/dist/adapters/surface.js +1 -1
- package/dist/adapters/{terminal → web-dapp}/actions.js +7 -7
- package/dist/adapters/web-dapp/console-capture.js +63 -0
- package/dist/adapters/web-dapp/doctor-checks.js +33 -0
- package/dist/adapters/{terminal → web-dapp}/launch.js +21 -22
- package/dist/adapters/web-dapp/overlay.js +21 -0
- package/dist/adapters/web-dapp/readiness.js +10 -0
- package/dist/adapters/{terminal → web-dapp}/run.js +15 -15
- package/dist/adapters/{terminal → web-dapp}/stop.js +5 -4
- package/dist/adapters/web-dapp/surface.js +149 -0
- package/dist/cli.js +3 -0
- package/dist/command-contract.js +29 -7
- package/dist/commands/check.js +2 -2
- package/dist/commands/doctor.js +19 -2
- package/dist/commands/farmslot-ready.js +1 -0
- package/dist/commands/help.js +1 -1
- package/dist/commands/launch/device.js +5 -5
- package/dist/commands/self-test.js +9 -5
- package/dist/engine.js +6 -16
- package/dist/manifest.js +48 -24
- package/dist/mm-harness-cli.js +17 -0
- package/dist/performance-observation.js +7 -13
- package/dist/runner.js +26 -7
- package/docs/cli-contract.md +5 -3
- package/library/actions/shared/console-collector.mjs +1 -1
- package/library/actions/{terminal → web-dapp}/app/assert_no_console_errors.mjs +4 -4
- package/library/actions/{terminal → web-dapp}/app/launch.mjs +7 -7
- package/library/actions/{terminal → web-dapp}/platform/hud.mjs +5 -5
- package/library/actions/{terminal → web-dapp}/platform/page.mjs +11 -11
- package/library/actions/{terminal → web-dapp}/platform/runtime.mjs +16 -12
- package/library/actions/web-dapp/ui/navigate.mjs +24 -0
- package/library/actions/web-dapp/wallet/_signature-log.mjs +32 -0
- package/library/actions/{terminal → web-dapp}/wallet/assert_signatures.mjs +4 -4
- package/library/actions/{terminal → web-dapp}/wallet/list_accounts.mjs +2 -2
- package/library/actions/{terminal → web-dapp}/wallet/read_signatures.mjs +6 -6
- package/library/manifests/{terminal.action-manifest.json → web-dapp.action-manifest.json} +18 -702
- package/package.json +5 -5
- package/adapters/terminal/lib/mainnet-guard.mjs +0 -186
- package/adapters/terminal/policy.mjs +0 -35
- package/dist/adapters/terminal/console-capture.js +0 -59
- package/dist/adapters/terminal/doctor-checks.js +0 -24
- package/dist/adapters/terminal/overlay.js +0 -25
- package/dist/adapters/terminal/readiness.js +0 -11
- package/dist/adapters/terminal/surface.js +0 -135
- package/library/actions/terminal/perps/_venue.mjs +0 -242
- package/library/actions/terminal/perps/assert_orders.mjs +0 -54
- package/library/actions/terminal/perps/assert_positions.mjs +0 -40
- package/library/actions/terminal/perps/read_orders.mjs +0 -26
- package/library/actions/terminal/perps/read_positions.mjs +0 -25
- package/library/actions/terminal/perps/read_snapshot.mjs +0 -40
- package/library/actions/terminal/perps/revoke_agent.mjs +0 -58
- package/library/actions/terminal/perps/teardown_state.mjs +0 -104
- package/library/actions/terminal/ui/navigate.mjs +0 -31
- package/library/actions/terminal/wallet/_signature-log.mjs +0 -56
package/dist/manifest.js
CHANGED
|
@@ -2,7 +2,7 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { OFFICIAL_RECIPE_ACTIONS } from "@farmslot/protocol";
|
|
4
4
|
import { getAdapterSurface } from "./adapters/surface.js";
|
|
5
|
-
import { manifestPath, readJson, importRecipeProtocol } from "./paths.js";
|
|
5
|
+
import { manifestPath, readJson, importRecipeProtocol, runnerDir } from "./paths.js";
|
|
6
6
|
function loadMetaMaskMobileActionManifest() {
|
|
7
7
|
return asActionManifest(readJson(manifestPath("mobile")));
|
|
8
8
|
}
|
|
@@ -12,14 +12,26 @@ function loadMetaMaskExtensionActionManifest() {
|
|
|
12
12
|
function loadMetaMaskCoreActionManifest() {
|
|
13
13
|
return asActionManifest(readJson(manifestPath("core")));
|
|
14
14
|
}
|
|
15
|
-
function loadMetaMaskTerminalActionManifest() {
|
|
16
|
-
return asActionManifest(readJson(manifestPath("terminal")));
|
|
17
|
-
}
|
|
18
15
|
function loadActionManifest(adapter, overridePath) {
|
|
19
16
|
if (overridePath) return asActionManifest(readJson(path.resolve(overridePath)));
|
|
20
17
|
return asActionManifest(readJson(getAdapterSurface(adapter).actions.manifestPath()));
|
|
21
18
|
}
|
|
19
|
+
class ActionManifestError extends Error {
|
|
20
|
+
code = "ACTION_MANIFEST_INVALID";
|
|
21
|
+
userAction;
|
|
22
|
+
constructor(adapter, cause) {
|
|
23
|
+
super(cause instanceof Error ? cause.message : String(cause));
|
|
24
|
+
this.userAction = `fix the ${adapter} action manifest the message names, then rerun`;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
22
27
|
async function resolveActionManifest(adapter, overridePath, librarySources, taskActionRoot) {
|
|
28
|
+
try {
|
|
29
|
+
return await resolveActionManifestSources(adapter, overridePath, librarySources, taskActionRoot);
|
|
30
|
+
} catch (error) {
|
|
31
|
+
throw error instanceof ActionManifestError ? error : new ActionManifestError(adapter, error);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async function resolveActionManifestSources(adapter, overridePath, librarySources, taskActionRoot) {
|
|
23
35
|
if (overridePath) {
|
|
24
36
|
const manifestPath2 = path.resolve(overridePath);
|
|
25
37
|
const implementationRoot = path.resolve(
|
|
@@ -38,9 +50,12 @@ async function resolveActionManifest(adapter, overridePath, librarySources, task
|
|
|
38
50
|
})
|
|
39
51
|
};
|
|
40
52
|
}
|
|
41
|
-
const
|
|
53
|
+
const surfaceActions = getAdapterSurface(adapter).actions;
|
|
54
|
+
const canonicalPath = path.resolve(surfaceActions.manifestPath());
|
|
42
55
|
const canonicalRoot = path.dirname(path.dirname(canonicalPath));
|
|
43
|
-
const
|
|
56
|
+
const bundledRoot = path.resolve(runnerDir, "library");
|
|
57
|
+
const canonicalName = canonicalRoot === bundledRoot ? "metamask" : path.basename(canonicalRoot);
|
|
58
|
+
const sources = librarySources?.length ? librarySources : [{ name: canonicalName, root: canonicalRoot }];
|
|
44
59
|
const manifests = [];
|
|
45
60
|
for (const source of sources) {
|
|
46
61
|
const file = path.resolve(source.root, "manifests", `${adapter}.action-manifest.json`);
|
|
@@ -49,31 +64,39 @@ async function resolveActionManifest(adapter, overridePath, librarySources, task
|
|
|
49
64
|
await validateManifest(manifest);
|
|
50
65
|
manifests.push({ source, file, manifest });
|
|
51
66
|
}
|
|
67
|
+
for (const declared of surfaceActions.manifestPaths?.() ?? []) {
|
|
68
|
+
const file = path.resolve(declared);
|
|
69
|
+
if (file === canonicalPath || manifests.some((entry) => entry.file === file)) continue;
|
|
70
|
+
const manifest = asActionManifest(readJson(file));
|
|
71
|
+
await validateManifest(manifest);
|
|
72
|
+
const root = path.dirname(path.dirname(file));
|
|
73
|
+
const source = sources.find((candidate) => path.resolve(candidate.root) === root) ?? { name: path.basename(root), root };
|
|
74
|
+
manifests.push({ source, file, manifest });
|
|
75
|
+
}
|
|
52
76
|
if (!manifests.some((entry) => entry.file === canonicalPath)) {
|
|
53
77
|
const manifest = loadActionManifest(adapter);
|
|
54
78
|
await validateManifest(manifest);
|
|
55
79
|
manifests.push({
|
|
56
|
-
source: { name:
|
|
80
|
+
source: { name: canonicalName, root: canonicalRoot },
|
|
57
81
|
file: canonicalPath,
|
|
58
82
|
manifest
|
|
59
83
|
});
|
|
60
84
|
}
|
|
61
85
|
const canonical = manifests.find((entry) => entry.file === canonicalPath);
|
|
62
86
|
if (!canonical) throw new Error(`No ${adapter} action manifest could be resolved.`);
|
|
87
|
+
const isBundled = (entry) => path.resolve(entry.source.root) === bundledRoot;
|
|
88
|
+
const bundledParents = manifests.filter((entry) => entry !== canonical && isBundled(entry));
|
|
63
89
|
const official = new Set(OFFICIAL_RECIPE_ACTIONS);
|
|
64
|
-
const canonicalActions = actionsOf(canonical.manifest);
|
|
65
90
|
const mergedActions = {};
|
|
66
|
-
for (const [name, entry] of Object.entries(canonicalActions)) {
|
|
67
|
-
if (official.has(name)) mergedActions[name] = entry;
|
|
68
|
-
}
|
|
69
91
|
const actionSources = /* @__PURE__ */ new Map();
|
|
70
|
-
for (const
|
|
71
|
-
|
|
72
|
-
name
|
|
73
|
-
|
|
74
|
-
manifestPath:
|
|
75
|
-
}
|
|
92
|
+
for (const entry of [canonical, ...bundledParents]) {
|
|
93
|
+
for (const [name, action] of Object.entries(actionsOf(entry.manifest))) {
|
|
94
|
+
if (!official.has(name) || name in mergedActions) continue;
|
|
95
|
+
mergedActions[name] = action;
|
|
96
|
+
actionSources.set(name, { name: "official", tier: "official", manifestPath: entry.file });
|
|
97
|
+
}
|
|
76
98
|
}
|
|
99
|
+
const observers = bundledParents.length ? [canonical, ...bundledParents].flatMap((entry) => entry.manifest.observers ?? []).filter((observer, index, all) => all.findIndex((other) => other.ref === observer.ref) === index) : canonical.manifest.observers;
|
|
77
100
|
const custom = /* @__PURE__ */ new Set();
|
|
78
101
|
const ordered = [
|
|
79
102
|
...manifests.filter((entry) => entry.file !== canonicalPath),
|
|
@@ -81,24 +104,25 @@ async function resolveActionManifest(adapter, overridePath, librarySources, task
|
|
|
81
104
|
];
|
|
82
105
|
for (const entry of ordered) {
|
|
83
106
|
const isCanonical = entry.file === canonicalPath;
|
|
84
|
-
|
|
107
|
+
const bundled = isBundled(entry);
|
|
108
|
+
if (!isCanonical && !bundled && entry.manifest.observers?.length) {
|
|
85
109
|
throw new Error(
|
|
86
110
|
`Library manifest ${entry.file} declares observers, but external library observers are not executable; remove observers or use an explicit task action manifest.`
|
|
87
111
|
);
|
|
88
112
|
}
|
|
89
|
-
const sourceName =
|
|
90
|
-
const tier =
|
|
113
|
+
const sourceName = bundled ? "metamask" : entry.source.name ?? path.basename(entry.source.root);
|
|
114
|
+
const tier = bundled ? "canonical" : sourceName === "personal" ? "personal" : "team";
|
|
91
115
|
const implementationRoot = path.join(entry.source.root, "actions");
|
|
92
116
|
const sourceInfo = {
|
|
93
117
|
name: sourceName,
|
|
94
118
|
tier,
|
|
95
119
|
manifestPath: entry.file,
|
|
96
120
|
implementationRoot,
|
|
97
|
-
trust: entry.source.provenance?.trust ?? (
|
|
121
|
+
trust: entry.source.provenance?.trust ?? (bundled ? "trusted" : "unknown")
|
|
98
122
|
};
|
|
99
123
|
for (const [name, action] of Object.entries(actionsOf(entry.manifest))) {
|
|
100
124
|
if (official.has(name)) {
|
|
101
|
-
if (!isCanonical) {
|
|
125
|
+
if (!isCanonical && !bundled) {
|
|
102
126
|
throw new Error(
|
|
103
127
|
`Library action ${name} from ${entry.file} conflicts with an official action; team libraries may declare namespaced custom actions only.`
|
|
104
128
|
);
|
|
@@ -121,7 +145,7 @@ async function resolveActionManifest(adapter, overridePath, librarySources, task
|
|
|
121
145
|
manifest: {
|
|
122
146
|
$schema: canonical.manifest.$schema,
|
|
123
147
|
actions: mergedActions,
|
|
124
|
-
...
|
|
148
|
+
...observers ? { observers } : {}
|
|
125
149
|
},
|
|
126
150
|
actionSources
|
|
127
151
|
};
|
|
@@ -163,11 +187,11 @@ function asActionManifest(value) {
|
|
|
163
187
|
return value;
|
|
164
188
|
}
|
|
165
189
|
export {
|
|
190
|
+
ActionManifestError,
|
|
166
191
|
loadActionManifest,
|
|
167
192
|
loadMetaMaskCoreActionManifest,
|
|
168
193
|
loadMetaMaskExtensionActionManifest,
|
|
169
194
|
loadMetaMaskMobileActionManifest,
|
|
170
|
-
loadMetaMaskTerminalActionManifest,
|
|
171
195
|
resolveActionManifest,
|
|
172
196
|
validateManifest
|
|
173
197
|
};
|
package/dist/mm-harness-cli.js
CHANGED
|
@@ -11,7 +11,9 @@ import { metaMaskEngine } from "./engine.js";
|
|
|
11
11
|
import { handleHelp } from "./commands/help.js";
|
|
12
12
|
import { handleTutorial } from "./commands/tutorial.js";
|
|
13
13
|
import { handleSetupBase } from "./commands/setup-base.js";
|
|
14
|
+
import { loadSelectedAdapter } from "./adapters/plugin-surface.js";
|
|
14
15
|
import { getAdapterSurface } from "./adapters/surface.js";
|
|
16
|
+
import { ActionManifestError } from "./manifest.js";
|
|
15
17
|
import { configPath, hydrateLibraryPathEnv, readConfig } from "./review/knowledge.js";
|
|
16
18
|
import {
|
|
17
19
|
PUBLIC_COMMAND_CONTRACTS,
|
|
@@ -1088,6 +1090,17 @@ async function delegate(argv) {
|
|
|
1088
1090
|
try {
|
|
1089
1091
|
return await recipeMain(argv);
|
|
1090
1092
|
} catch (error) {
|
|
1093
|
+
const own = argv.includes("--") ? argv.slice(0, argv.indexOf("--")) : argv;
|
|
1094
|
+
if (error instanceof ActionManifestError && own.includes("--json")) {
|
|
1095
|
+
console.log(JSON.stringify({
|
|
1096
|
+
schemaVersion: 1,
|
|
1097
|
+
command: argv[0],
|
|
1098
|
+
status: "fail",
|
|
1099
|
+
exitCode: 1,
|
|
1100
|
+
error: { code: error.code, message: error.message, userAction: error.userAction }
|
|
1101
|
+
}, null, 2));
|
|
1102
|
+
return 1;
|
|
1103
|
+
}
|
|
1091
1104
|
console.error(error instanceof Error ? error.message : String(error));
|
|
1092
1105
|
return error !== null && typeof error === "object" && "exitCode" in error && typeof error.exitCode === "number" ? error.exitCode : 1;
|
|
1093
1106
|
}
|
|
@@ -1226,6 +1239,10 @@ if (!preflightBypass.has(rawArgv[0] ?? "")) {
|
|
|
1226
1239
|
}
|
|
1227
1240
|
}
|
|
1228
1241
|
await hydrateLibraryPathEnv({ target: targetFromArgv(rawArgv), config: readConfig(configPath()) });
|
|
1242
|
+
{
|
|
1243
|
+
const refused = await loadSelectedAdapter(rawArgv[0] ?? "", rawArgv.slice(1));
|
|
1244
|
+
if (refused !== void 0) process.exit(refused);
|
|
1245
|
+
}
|
|
1229
1246
|
if (hasPassthroughHelp(rawArgv)) {
|
|
1230
1247
|
process.exit(await delegate(rawArgv));
|
|
1231
1248
|
}
|
|
@@ -15,17 +15,13 @@ const sessions = /* @__PURE__ */ new Map();
|
|
|
15
15
|
const DEFAULT_MAX_DURATION_MS = 5 * 60 * 1e3;
|
|
16
16
|
const MAX_DURATION_MS = 60 * 60 * 1e3;
|
|
17
17
|
const gzipAsync = promisify(gzip);
|
|
18
|
-
function startRunPerformanceObservation(observer, { target, artifactsDir, env
|
|
18
|
+
function startRunPerformanceObservation(observer, { target, artifactsDir, env }) {
|
|
19
19
|
const key = path.resolve(artifactsDir);
|
|
20
20
|
const session = {
|
|
21
21
|
observer,
|
|
22
22
|
artifactsDir: key,
|
|
23
|
-
env
|
|
24
|
-
...env,
|
|
25
|
-
...ports.watcherPort ? { WATCHER_PORT: ports.watcherPort } : {}
|
|
26
|
-
},
|
|
23
|
+
env,
|
|
27
24
|
nodeEvents: [],
|
|
28
|
-
ports,
|
|
29
25
|
target,
|
|
30
26
|
windows: /* @__PURE__ */ new Map()
|
|
31
27
|
};
|
|
@@ -152,7 +148,7 @@ async function performanceBackend(session) {
|
|
|
152
148
|
if (session.backend) return session.backend;
|
|
153
149
|
if (session.backendError) throw new Error(session.backendError);
|
|
154
150
|
try {
|
|
155
|
-
session.backend = await session.observer.backend(session.target, session.env
|
|
151
|
+
session.backend = await session.observer.backend(session.target, session.env);
|
|
156
152
|
return session.backend;
|
|
157
153
|
} catch (error) {
|
|
158
154
|
session.backendError = errorMessage(error);
|
|
@@ -515,13 +511,11 @@ function unavailableCapture(session, window, reason) {
|
|
|
515
511
|
session.nodeEvents
|
|
516
512
|
);
|
|
517
513
|
}
|
|
518
|
-
async function createExtensionPerformanceObserver(target, env
|
|
519
|
-
return createExtensionPerformanceBackend(extensionCdpPort(
|
|
514
|
+
async function createExtensionPerformanceObserver(target, env) {
|
|
515
|
+
return createExtensionPerformanceBackend(extensionCdpPort(env), target);
|
|
520
516
|
}
|
|
521
|
-
function extensionCdpPort(
|
|
522
|
-
const port = Number(
|
|
523
|
-
ports.cdpPort ?? env.CDP_PORT ?? env.RECIPE_CDP_PORT
|
|
524
|
-
);
|
|
517
|
+
function extensionCdpPort(env) {
|
|
518
|
+
const port = Number(env.CDP_PORT ?? env.RECIPE_CDP_PORT);
|
|
525
519
|
if (!Number.isInteger(port) || port <= 0) {
|
|
526
520
|
throw new Error("Extension performance observation requires CDP_PORT.");
|
|
527
521
|
}
|
package/dist/runner.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { execSync } from "node:child_process";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
2
3
|
import { OFFICIAL_RECIPE_ACTIONS } from "@farmslot/protocol";
|
|
3
4
|
import { createMetaMaskAdapters, createMetaMaskUiTransport } from "./adapters.js";
|
|
4
5
|
import { getAdapterSurface } from "./adapters/surface.js";
|
|
@@ -8,6 +9,7 @@ import {
|
|
|
8
9
|
resolveMetaMaskMobileLifecycleTarget
|
|
9
10
|
} from "./app-lifecycle.js";
|
|
10
11
|
import { loadMetaMaskExtensionActionManifest, loadMetaMaskMobileActionManifest } from "./manifest.js";
|
|
12
|
+
import { adapterPlugin } from "@farmslot/recipe-cli/harness";
|
|
11
13
|
import { actionSourceIntegrity, metaMaskActionExecutionCapabilities } from "./recipe-security.js";
|
|
12
14
|
import {
|
|
13
15
|
createRecordingTargetProvider,
|
|
@@ -50,7 +52,12 @@ async function createMetaMaskRunner(adapter, actionManifest, options = {}) {
|
|
|
50
52
|
const { createReactNativeBridgeUiTransport } = await importRecipeHarnessRuntimeReactNativeBridge();
|
|
51
53
|
const { createAppLifecycleAdapters } = await importRecipeHarnessAppLifecycle();
|
|
52
54
|
const actions = Object.keys(actionManifest.actions);
|
|
53
|
-
const
|
|
55
|
+
const surface = getAdapterSurface(adapter);
|
|
56
|
+
const coded = await surface.actions.adapters?.() ?? [];
|
|
57
|
+
const codedActions = new Set(coded.map((entry) => entry.action));
|
|
58
|
+
const customActions = actions.filter(
|
|
59
|
+
(action) => !OFFICIAL_ACTIONS.has(action) && !codedActions.has(action)
|
|
60
|
+
);
|
|
54
61
|
const actionSources = options.actionSources ? new Map([...options.actionSources].map(([action, source]) => [action, { ...source }])) : void 0;
|
|
55
62
|
const preparedLiveAdapters = await prepareLiveAdapterImplementations(
|
|
56
63
|
adapter,
|
|
@@ -58,6 +65,7 @@ async function createMetaMaskRunner(adapter, actionManifest, options = {}) {
|
|
|
58
65
|
actionSources,
|
|
59
66
|
options.trustTaskActions
|
|
60
67
|
);
|
|
68
|
+
bindPluginDigest(actionSources, codedActions, adapterPlugin(adapter)?.digest);
|
|
61
69
|
const declaredActions = new Set(actions);
|
|
62
70
|
const core = createStandardCoreAdapters({ actions });
|
|
63
71
|
const projectOwnedOfficialActions = /* @__PURE__ */ new Set(["app.status", "app.lifecycle"]);
|
|
@@ -73,7 +81,6 @@ async function createMetaMaskRunner(adapter, actionManifest, options = {}) {
|
|
|
73
81
|
}, preparedLiveAdapters, { hudPolicy: options.autoHud === true ? "show" : "auto" })
|
|
74
82
|
});
|
|
75
83
|
const existing = new Set([...core, ...ui].map((entry) => entry.action));
|
|
76
|
-
const surface = getAdapterSurface(adapter);
|
|
77
84
|
const lifecycleAdapters = bindMetaMaskMobileLifecycleDeadline(
|
|
78
85
|
createAppLifecycleAdapters({
|
|
79
86
|
actions,
|
|
@@ -82,11 +89,12 @@ async function createMetaMaskRunner(adapter, actionManifest, options = {}) {
|
|
|
82
89
|
})
|
|
83
90
|
);
|
|
84
91
|
const lifecycle = surface.actions.lifecycle ? await surface.actions.lifecycle(lifecycleAdapters) : lifecycleAdapters;
|
|
85
|
-
const custom =
|
|
86
|
-
|
|
87
|
-
customActions,
|
|
88
|
-
|
|
89
|
-
|
|
92
|
+
const custom = [
|
|
93
|
+
...coded,
|
|
94
|
+
...createMetaMaskAdapters(adapter, customActions, preparedLiveAdapters).filter(
|
|
95
|
+
(entry) => !codedActions.has(entry.action)
|
|
96
|
+
)
|
|
97
|
+
].filter(
|
|
90
98
|
(entry) => declaredActions.has(entry.action) && !existing.has(entry.action) && !lifecycle.some((lifecycleEntry) => lifecycleEntry.action === entry.action)
|
|
91
99
|
);
|
|
92
100
|
const autoHudDisabled = options.autoHud === false || options.autoHud === void 0 && (process.env.METAMASK_RECIPE_AUTO_HUD === "0" || process.env.METAMASK_RECIPE_AUTO_HUD === "false");
|
|
@@ -197,6 +205,17 @@ function deduplicateLibraryResolutionLogs(logger, suppress = false) {
|
|
|
197
205
|
}
|
|
198
206
|
};
|
|
199
207
|
}
|
|
208
|
+
function bindPluginDigest(sources, actions, pluginDigest) {
|
|
209
|
+
if (!sources || !pluginDigest) return;
|
|
210
|
+
const bind = (digest) => `sha256:${createHash("sha256").update(`${digest}\0${pluginDigest}`).digest("hex")}`;
|
|
211
|
+
for (const action of actions) {
|
|
212
|
+
const source = sources.get(action);
|
|
213
|
+
if (!source) continue;
|
|
214
|
+
const { digest, resolveDigest } = source;
|
|
215
|
+
source.digest = bind(digest ?? "");
|
|
216
|
+
source.resolveDigest = resolveDigest ? async () => bind(await resolveDigest()) : void 0;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
200
219
|
async function prepareLiveAdapterImplementations(platform, actions, sources, trustTaskActions = false) {
|
|
201
220
|
const prepared = /* @__PURE__ */ new Map();
|
|
202
221
|
if (!sources) return prepared;
|
package/docs/cli-contract.md
CHANGED
|
@@ -92,9 +92,11 @@ These are real farm bugs. The goldens freeze the current rejections, so fixing e
|
|
|
92
92
|
|
|
93
93
|
## Terminal adapter
|
|
94
94
|
|
|
95
|
-
`goldens-terminal` freezes the `va-mmcx-terminal-farm` hooks with their exact argv (`recipe_action_manifest`, `recipe_doctor`, `recipe_harness_{install,verify,cleanup}`, `recipe_run`, the browser `boot`/`shutdown` resources and `teardown`), against a fresh fixture slot: no slot dev server, no Extension dist, and the farm's wallet fixture outside the slot, so `install` copies it in. `launch` is frozen on its cold path: it refuses at the missing dist before any browser starts. The slot's CDP port is the one port no port holder reserves: a farm slot with no browser has it free, and the Terminal's port checks probe it.
|
|
95
|
+
`goldens-terminal` freezes the `va-mmcx-terminal-farm` hooks with their exact argv (`recipe_action_manifest`, `recipe_doctor`, `recipe_harness_{install,verify,cleanup}`, `recipe_run`, the browser `boot`/`shutdown` resources and `teardown`), against a fresh fixture slot: no slot dev server, no Extension dist, and the farm's wallet fixture outside the slot, so `install` copies it in. `launch` is frozen on its cold path: it refuses at the missing dist before any browser starts. The slot's CDP port is the one port no port holder reserves: a farm slot with no browser has it free, and the Terminal's port checks probe it.
|
|
96
96
|
|
|
97
|
-
|
|
97
|
+
`terminal` is the plugin the `experimental-metamask-recipe-terminal` library declares (`adapters.terminal`, extends the built-in `web-dapp`), so the family loads it: set `GOLDENS_TERMINAL_LIBRARY` to a checkout of that library, which the family copies into its sandbox and puts on `RECIPE_LIBRARY_PATH`. CI has no access to the private library and skips the family (`CI` set); local runs need the variable, and without it outside CI the family fails. A release gate must set it too (proposed for the release checklist). `goldens-discovery` covers the built-in `web-dapp` in its per-adapter loop.
|
|
98
|
+
|
|
99
|
+
Outside this boundary: a successful `launch` and the `temp/recipe/runtime/terminal/` layout (`browser.pid`), the library's recipes, `run terminal.perps.<name> --plan`, and the recipe-terminal `console-guard.mjs` import paths.
|
|
98
100
|
|
|
99
101
|
## Running the goldens
|
|
100
102
|
|
|
@@ -142,7 +144,7 @@ The goldens need the harness to be a git checkout: `execution-provenance.json` `
|
|
|
142
144
|
2. UUIDs become `<UUID>`. Git object ids (40 hex, or a short hex value under a `*head`/`*gitRef`/`*commit` key) become `<GIT_REF>`. ISO-8601 timestamps become `<TIMESTAMP>`, and compact stamps in names `<STAMP>`. Path-derived slot ids `local-<adapter>-<8 hex>` become `local-<adapter>-<HASH>`.
|
|
143
145
|
3. The package version becomes `<HARNESS_VERSION>`. Any other `x.y.z` under a key ending in `version` becomes `<VERSION>`.
|
|
144
146
|
4. Numbers under volatile keys: `*pid` becomes `<PID>`, `inode` `<INODE>`, `*port` `<PORT>`, `*At`/`*Time`/`*timestamp`/`mtime*` `<TIMESTAMP>`, and `*Ms`/`duration*`/`elapsed*` `<DURATION>`. Values inside `schema` and `examples` subtrees (static manifest content) are never rewritten.
|
|
145
|
-
5. `invocationDigest` becomes `<DIGEST>`: it hashes `recipe-invocation.json`, which carries sandbox paths and timestamps and is frozen itself in normalised form. `runner.sourceFingerprint` becomes `<FINGERPRINT>`: it hashes the harness source tree, so any harness edit changes it. A `status` holding `git status --porcelain` output (provenance `runner.status` and `libraries[].status`, empty when clean) becomes `<GIT_STATUS>`. Content-only digests (recipe and dependency digests) stay frozen.
|
|
147
|
+
5. `invocationDigest` becomes `<DIGEST>`: it hashes `recipe-invocation.json`, which carries sandbox paths and timestamps and is frozen itself in normalised form. `runner.sourceFingerprint` becomes `<FINGERPRINT>`: it hashes the harness source tree, so any harness edit changes it. A `status` holding `git status --porcelain` output (provenance `runner.status` and `libraries[].status`, empty when clean) becomes `<GIT_STATUS>`. A loaded adapter plugin's code digest in doctor's `adapter-plugin:<id>` message becomes `sha256:<PLUGIN_DIGEST>`: it hashes the recipe library's plugin files, so any library edit changes it. Content-only digests (recipe and dependency digests) stay frozen.
|
|
146
148
|
6. Durations in text (`0.3s`, `123ms`) become `<DURATION>`.
|
|
147
149
|
|
|
148
150
|
In tree listings, files whose names differ only by a volatile token collapse into one row: a count and the union of their key shapes, or the sorted list of their values in a value tree.
|
|
@@ -15,7 +15,7 @@ export const COLLECTOR_SCRIPT_NAME = 'console-tail.mjs';
|
|
|
15
15
|
export const CAPTURE_CONTROL_MARK = '[mm-harness-capture-control]';
|
|
16
16
|
|
|
17
17
|
// Same derivation as ownerMarker('console-collector', …) in
|
|
18
|
-
// adapters/
|
|
18
|
+
// adapters/web-dapp/lib/processes.mjs, so the web-dapp stop path finds it.
|
|
19
19
|
export function collectorOwnerMarker(runtimeDir) {
|
|
20
20
|
const id = createHash('sha256').update(path.resolve(runtimeDir)).digest('hex').slice(0, 32);
|
|
21
21
|
return `--mm-harness-owner=console-collector-${id}`;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Fail the recipe when the
|
|
1
|
+
// Fail the recipe when the app page logged an error or uncaught exception
|
|
2
2
|
// that the recipe libraries' console allowlist does not mark as known
|
|
3
3
|
// third-party noise. Reads the slot console capture (app-console.log, and
|
|
4
4
|
// extension-console.log when asked) from where the current slot browser's
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
loadConsoleAllowlist,
|
|
21
21
|
} from '../../shared/console-findings.mjs';
|
|
22
22
|
import { collectorOwnerMarker, collectorStatus, verifyCapture } from '../../shared/console-collector.mjs';
|
|
23
|
-
import { isDirectRun, readBrowserState, runAdapter,
|
|
23
|
+
import { isDirectRun, readBrowserState, runAdapter, webDappPolicy, webDappRuntimeDir } from '../platform/runtime.mjs';
|
|
24
24
|
|
|
25
25
|
const LOGS = Object.freeze({ page: 'app-console.log', extension: 'extension-console.log' });
|
|
26
26
|
|
|
@@ -55,7 +55,7 @@ export async function assertNoConsoleErrors(input, env = process.env, deps = {})
|
|
|
55
55
|
const projectRoot = input.context?.projectRoot;
|
|
56
56
|
const sources = Array.isArray(node.sources) && node.sources.length ? node.sources.map(String) : ['page', 'browser'];
|
|
57
57
|
const levels = new Set(Array.isArray(node.levels) && node.levels.length ? node.levels.map(String) : ['error', 'exception']);
|
|
58
|
-
const runtime =
|
|
58
|
+
const runtime = webDappRuntimeDir(projectRoot);
|
|
59
59
|
const state = await readBrowserState(projectRoot);
|
|
60
60
|
if (!state?.cdpPort || !state.appOrigin) throw unavailable('no slot browser state (browser.json)');
|
|
61
61
|
const capture = (deps.collectorStatus ?? collectorStatus)({
|
|
@@ -107,7 +107,7 @@ export async function assertNoConsoleErrors(input, env = process.env, deps = {})
|
|
|
107
107
|
// The recorded launch offset was past the end of a replaced capture
|
|
108
108
|
// file; that file was read from its start.
|
|
109
109
|
offsetResets,
|
|
110
|
-
proofPath:
|
|
110
|
+
proofPath: `${webDappPolicy().adapterId}-console-capture`,
|
|
111
111
|
};
|
|
112
112
|
if (failing.size > 0) {
|
|
113
113
|
const list = [...failing.values()].map((item) => `${item.level} [${item.source}] ${item.example} ×${item.count}`).join('\n ');
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
// Explicit lifecycle step: start (or reuse) the
|
|
1
|
+
// Explicit lifecycle step: start (or reuse) the web-dapp slot browser with the
|
|
2
2
|
// requested signer (MetaMask extension or injected strict wallet) and fixture
|
|
3
3
|
// account, and the wallet host that records wallet requests. A fresh_profile
|
|
4
4
|
// start forgets the app's stored agent key and the extension's dapp permission.
|
|
5
5
|
|
|
6
|
-
import { appPort, cdpPort, isDirectRun, resolveSigner, runAdapter } from '../platform/runtime.mjs';
|
|
7
|
-
import {
|
|
8
|
-
import { policy } from '../../../../adapters/terminal/policy.mjs';
|
|
6
|
+
import { appPort, cdpPort, isDirectRun, resolveSigner, runAdapter, webDappPolicy } from '../platform/runtime.mjs';
|
|
7
|
+
import { launchWebDappBrowser } from '../../../../adapters/web-dapp/launch.mjs';
|
|
9
8
|
|
|
10
9
|
export async function launch(input) {
|
|
11
10
|
const node = input.node ?? {};
|
|
12
|
-
const
|
|
11
|
+
const policy = webDappPolicy();
|
|
12
|
+
const state = await launchWebDappBrowser({
|
|
13
13
|
target: input.context?.projectRoot,
|
|
14
14
|
'cdp-port': cdpPort(input),
|
|
15
15
|
'app-port': appPort(input),
|
|
@@ -24,7 +24,7 @@ export async function launch(input) {
|
|
|
24
24
|
network: node.network,
|
|
25
25
|
'mainnet-confirmation': node.mainnet_confirmation,
|
|
26
26
|
'start-chain': node.start_chain ?? policy.startChain,
|
|
27
|
-
}, process.env
|
|
27
|
+
}, process.env);
|
|
28
28
|
return {
|
|
29
29
|
action: input.action,
|
|
30
30
|
status: 'ready',
|
|
@@ -39,7 +39,7 @@ export async function launch(input) {
|
|
|
39
39
|
slowMoMs: state.slowMoMs ?? 0,
|
|
40
40
|
extension: state.extension ? { id: state.extension.id, version: state.extension.version } : null,
|
|
41
41
|
freshProfile: state.freshProfile,
|
|
42
|
-
proofPath:
|
|
42
|
+
proofPath: `${policy.adapterId}-browser-launch`,
|
|
43
43
|
};
|
|
44
44
|
}
|
|
45
45
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
// app.hud for
|
|
1
|
+
// app.hud for web-dapp: fold the runner's progress update into
|
|
2
2
|
// <runtime>/hud.json and draw it on the app tab now. The wallet host redraws
|
|
3
3
|
// the same state after every navigation. The HUD is advisory: a missing
|
|
4
4
|
// browser or a busy tab skips the update instead of failing the recipe.
|
|
5
5
|
|
|
6
6
|
import { isAppUrl } from '@farmslot/adapter-web/origin';
|
|
7
7
|
|
|
8
|
-
import { clearHudState, hudRenderExpression, nextHudState, readHudState, writeHudState } from '../../../../adapters/
|
|
9
|
-
import { appOrigin, cdpPort, readBrowserState,
|
|
8
|
+
import { clearHudState, hudRenderExpression, nextHudState, readHudState, writeHudState } from '../../../../adapters/web-dapp/lib/hud.mjs';
|
|
9
|
+
import { appOrigin, cdpPort, readBrowserState, webDappRuntimeDir } from './runtime.mjs';
|
|
10
10
|
import { importRecipeHarnessRuntimeCdp } from '../../harness-exports.mjs';
|
|
11
11
|
|
|
12
12
|
// --hud show draws in any mode; --hud auto (the default) only for a visible
|
|
@@ -47,14 +47,14 @@ async function drawOnAppTabs(input, expression, timeoutMs = 3000) {
|
|
|
47
47
|
return results;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
export async function
|
|
50
|
+
export async function showAppHud(input, { hudPolicy = 'auto' } = {}) {
|
|
51
51
|
const node = input.node ?? {};
|
|
52
52
|
const projectRoot = input.context?.projectRoot;
|
|
53
53
|
const nodeId = input.context?.nodeId ?? '';
|
|
54
54
|
const browserState = projectRoot ? await readBrowserState(projectRoot) : null;
|
|
55
55
|
if (!browserState) return { hud: false, skipped: true, reason: 'no slot browser yet' };
|
|
56
56
|
if (!hudVisible(hudPolicy, browserState)) return { hud: false, skipped: true, reason: 'headless browser (--hud show draws anyway)' };
|
|
57
|
-
const runtimeDir =
|
|
57
|
+
const runtimeDir = webDappRuntimeDir(projectRoot);
|
|
58
58
|
let state = null;
|
|
59
59
|
if (node.clear === true) {
|
|
60
60
|
clearHudState(runtimeDir);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Select the CDP page a
|
|
1
|
+
// Select the CDP page a web-dapp UI action drives: the app tab (default) or the
|
|
2
2
|
// MetaMask wallet surface (side panel / notification / popup) that shows the
|
|
3
3
|
// pending dapp request. Pages are never brought to front unless
|
|
4
4
|
// FARMSLOT_FOCUS_BROWSER=1, as in the Extension adapter: Page.bringToFront
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
|
|
9
9
|
import { isAppUrl } from '@farmslot/adapter-web/origin';
|
|
10
10
|
|
|
11
|
-
import { resolveSlowMo } from '../../../../adapters/
|
|
12
|
-
import { HUD_ELEMENT } from '../../../../adapters/
|
|
13
|
-
import { cdpListenerPids } from '../../../../adapters/
|
|
11
|
+
import { resolveSlowMo } from '../../../../adapters/web-dapp/lib/display.mjs';
|
|
12
|
+
import { HUD_ELEMENT } from '../../../../adapters/web-dapp/lib/hud.mjs';
|
|
13
|
+
import { cdpListenerPids } from '../../../../adapters/web-dapp/lib/processes.mjs';
|
|
14
14
|
import { appOrigin, cdpPort, readBrowserState } from './runtime.mjs';
|
|
15
15
|
import { importRecipeHarnessRuntimeCdp } from '../../harness-exports.mjs';
|
|
16
16
|
|
|
@@ -41,7 +41,7 @@ export function pickAppTarget(targets, origin) {
|
|
|
41
41
|
return pages.find((target) => isAppUrl(target.url, origin)) ?? pages.find((target) => target.url === 'about:blank') ?? null;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
export async function
|
|
44
|
+
export async function selectAppTarget(input) {
|
|
45
45
|
const node = input.node ?? {};
|
|
46
46
|
const port = cdpPort(input);
|
|
47
47
|
const surface = resolveSurface(node);
|
|
@@ -75,7 +75,7 @@ export async function selectTerminalTarget(input) {
|
|
|
75
75
|
// the DOM instead.
|
|
76
76
|
class WalletSurfacePage extends CdpWebPage {
|
|
77
77
|
evaluate(expression) {
|
|
78
|
-
return this.evaluateInIsolatedWorld(expression, '
|
|
78
|
+
return this.evaluateInIsolatedWorld(expression, 'web-dapp-wallet-surface');
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
|
|
@@ -89,7 +89,7 @@ export function hudVisibilityExpression(visible) {
|
|
|
89
89
|
return `(() => { const host = document.documentElement.querySelector(':scope > ${HUD_ELEMENT}'); if (!host) return false; host.style.visibility = ${JSON.stringify(visible ? '' : 'hidden')}; return true; })()`;
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
class
|
|
92
|
+
class AppPage extends CdpWebPage {
|
|
93
93
|
async screenshot(context, relPath, metadata = {}) {
|
|
94
94
|
const hidden = await this.evaluate(hudVisibilityExpression(false)).catch(() => false);
|
|
95
95
|
try {
|
|
@@ -144,7 +144,7 @@ class TerminalAppPage extends CdpWebPage {
|
|
|
144
144
|
}
|
|
145
145
|
const top = document.elementFromPoint(rect.left + rect.width / 2, rect.top + rect.height / 2);
|
|
146
146
|
return { error: 'Target is fully covered by ' + (top ? top.tagName.toLowerCase() : 'nothing') + ': ' + ${JSON.stringify(selector)} };
|
|
147
|
-
})()`, '
|
|
147
|
+
})()`, 'web-dapp-click');
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
async setInput(selector, value) {
|
|
@@ -217,14 +217,14 @@ export function assertAppListenersUnchanged(state, current) {
|
|
|
217
217
|
|
|
218
218
|
const PAGE_DEPS = Object.freeze({
|
|
219
219
|
listeners: (port) => cdpListenerPids(port),
|
|
220
|
-
select: (input) =>
|
|
220
|
+
select: (input) => selectAppTarget(input),
|
|
221
221
|
connect: (url) => CdpSession.connect(url),
|
|
222
222
|
browserState: (projectRoot) => readBrowserState(projectRoot),
|
|
223
223
|
sleep: (ms) => sleep(ms),
|
|
224
224
|
env: process.env,
|
|
225
225
|
});
|
|
226
226
|
|
|
227
|
-
export async function
|
|
227
|
+
export async function withAppPage(input, callback, deps = PAGE_DEPS) {
|
|
228
228
|
const state = input.context?.projectRoot ? await deps.browserState(input.context.projectRoot) : null;
|
|
229
229
|
const delay = slowMoFor(state, deps.env);
|
|
230
230
|
if (delay > 0) await deps.sleep(delay);
|
|
@@ -237,7 +237,7 @@ export async function withTerminalPage(input, callback, deps = PAGE_DEPS) {
|
|
|
237
237
|
await session.call('Page.enable');
|
|
238
238
|
await session.call('Runtime.enable');
|
|
239
239
|
if (focusBrowserRequested(deps.env)) await session.call('Page.bringToFront');
|
|
240
|
-
const page = surface === 'wallet' ? new WalletSurfacePage(session) : new
|
|
240
|
+
const page = surface === 'wallet' ? new WalletSurfacePage(session) : new AppPage(session);
|
|
241
241
|
const result = await callback(page);
|
|
242
242
|
return result && typeof result === 'object' && !Array.isArray(result)
|
|
243
243
|
? { ...result, surface, cdpPort: port, targetUrl: target.url.replace(/0x[0-9a-fA-F]{40}/gu, '<address>') }
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
// Resolve the
|
|
2
|
-
// wallet-fixture accounts. Key material is only ever
|
|
3
|
-
// object; callers must never log or persist it.
|
|
1
|
+
// Resolve the web-dapp slot runtime: the venue policy, ports, runtime paths,
|
|
2
|
+
// browser state and wallet-fixture accounts. Key material is only ever
|
|
3
|
+
// returned as a viem account object; callers must never log or persist it.
|
|
4
4
|
|
|
5
5
|
import { realpathSync } from 'node:fs';
|
|
6
6
|
import { readFile, writeFile } from 'node:fs/promises';
|
|
@@ -11,6 +11,9 @@ import { mnemonicToAccount, privateKeyToAccount } from 'viem/accounts';
|
|
|
11
11
|
|
|
12
12
|
import { recipeRuntimeDir } from '@farmslot/recipe-cli/harness';
|
|
13
13
|
import { walletFixturePath } from '../../harness-exports.mjs';
|
|
14
|
+
import { POLICY_ENV, webDappAdapterId, webDappPolicy } from '../../../../adapters/web-dapp/lib/policy.mjs';
|
|
15
|
+
|
|
16
|
+
export { POLICY_ENV, webDappAdapterId, webDappPolicy };
|
|
14
17
|
|
|
15
18
|
export const SIGNER_MODES = Object.freeze(['extension', 'injected']);
|
|
16
19
|
export const DEFAULT_ACCOUNT = 'dev1';
|
|
@@ -26,12 +29,13 @@ export function canonicalPath(target) {
|
|
|
26
29
|
}
|
|
27
30
|
}
|
|
28
31
|
|
|
29
|
-
|
|
30
|
-
|
|
32
|
+
// <checkout>/<runtime>/<adapter id>, e.g. temp/recipe/runtime/terminal.
|
|
33
|
+
export function webDappRuntimeDir(projectRoot) {
|
|
34
|
+
return path.join(canonicalPath(projectRoot), recipeRuntimeDir(), webDappPolicy().adapterId);
|
|
31
35
|
}
|
|
32
36
|
|
|
33
|
-
export function
|
|
34
|
-
return path.join(
|
|
37
|
+
export function webDappRuntimePath(projectRoot, ...segments) {
|
|
38
|
+
return path.join(webDappRuntimeDir(projectRoot), ...segments);
|
|
35
39
|
}
|
|
36
40
|
|
|
37
41
|
function portFrom(raw, label) {
|
|
@@ -53,7 +57,7 @@ export function appPort(input = {}, env = process.env) {
|
|
|
53
57
|
portFrom(env.WATCHER_PORT, 'WATCHER_PORT');
|
|
54
58
|
if (!port) {
|
|
55
59
|
throw new Error(
|
|
56
|
-
|
|
60
|
+
`${webDappPolicy().adapterId} adapter requires the slot dev-server port.\n` +
|
|
57
61
|
'Next: pass --watcher-port <port> (Farmslot: {{port}}) or set TERMINAL_APP_PORT.',
|
|
58
62
|
);
|
|
59
63
|
}
|
|
@@ -71,7 +75,7 @@ export function cdpPort(input = {}, env = process.env) {
|
|
|
71
75
|
portFrom(env.CDP_PORT, 'CDP_PORT');
|
|
72
76
|
if (!port) {
|
|
73
77
|
throw new Error(
|
|
74
|
-
|
|
78
|
+
`${webDappPolicy().adapterId} adapter requires a per-slot browser CDP port.\n` +
|
|
75
79
|
'Next: pass --cdp-port <port> (Farmslot: {{cdp_port}}).',
|
|
76
80
|
);
|
|
77
81
|
}
|
|
@@ -92,7 +96,7 @@ export async function writePrivateJson(file, value) {
|
|
|
92
96
|
}
|
|
93
97
|
|
|
94
98
|
export async function readBrowserState(projectRoot) {
|
|
95
|
-
return readJsonFile(
|
|
99
|
+
return readJsonFile(webDappRuntimePath(projectRoot, 'browser.json'));
|
|
96
100
|
}
|
|
97
101
|
|
|
98
102
|
export function resolveSigner(raw) {
|
|
@@ -123,7 +127,7 @@ export async function loadWalletFixture(projectRoot, env = process.env) {
|
|
|
123
127
|
if (!fixture) {
|
|
124
128
|
throw new Error(
|
|
125
129
|
`wallet fixture not found at ${file}.\n` +
|
|
126
|
-
|
|
130
|
+
`Next: mm-harness install --adapter ${webDappPolicy().adapterId} --target <checkout> with RECIPE_WALLET_FIXTURE set, or copy the fixture there (0600).`,
|
|
127
131
|
);
|
|
128
132
|
}
|
|
129
133
|
if (!Array.isArray(fixture.accounts) || fixture.accounts.length === 0) {
|
|
@@ -195,7 +199,7 @@ export async function runAdapter(callback) {
|
|
|
195
199
|
const output = await callback(input);
|
|
196
200
|
await writeFile(input.outputPath, `${JSON.stringify(output, null, 2)}\n`);
|
|
197
201
|
} catch (error) {
|
|
198
|
-
process.stderr.write(`[
|
|
202
|
+
process.stderr.write(`[${webDappAdapterId()}] ${input.action} failed: ${error?.stack ?? error}\n`);
|
|
199
203
|
process.exitCode = 1;
|
|
200
204
|
}
|
|
201
205
|
}
|