@deeeed/metamask-harness 0.2.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 +161 -0
- package/README.md +140 -0
- package/bin/mm-harness +99 -0
- package/docs/CHEATSHEET.md +61 -0
- package/docs/CLI-SPEC.md +915 -0
- package/docs/MENTAL-MODEL.md +295 -0
- package/docs/architecture.md +367 -0
- package/docs/extension-runtime-commands.md +60 -0
- package/docs/harness-cli.md +43 -0
- package/docs/live-adapter-contract.md +188 -0
- package/docs/package-boundaries.md +47 -0
- package/docs/perps-flow-catalog.md +235 -0
- package/docs/recipe-libraries.md +95 -0
- package/docs/runtime-file-conventions.md +36 -0
- package/library/actions/core/perps/_controller.mjs +727 -0
- package/library/actions/core/perps/assert_orders.mjs +53 -0
- package/library/actions/core/perps/assert_positions.mjs +52 -0
- package/library/actions/core/perps/close_orders.mjs +97 -0
- package/library/actions/core/perps/close_positions.mjs +118 -0
- package/library/actions/core/perps/ensure_orders.mjs +40 -0
- package/library/actions/core/perps/ensure_positions.mjs +37 -0
- package/library/actions/core/perps/place_order.mjs +201 -0
- package/library/actions/core/perps/read_account.mjs +30 -0
- package/library/actions/core/perps/read_orders.mjs +27 -0
- package/library/actions/core/perps/read_positions.mjs +27 -0
- package/library/actions/core/perps/start_state.mjs +92 -0
- package/library/actions/core/perps/teardown_state.mjs +86 -0
- package/library/actions/extension/perps/assert_orders.mjs +11 -0
- package/library/actions/extension/perps/assert_positions.mjs +11 -0
- package/library/actions/extension/perps/close_orders.mjs +8 -0
- package/library/actions/extension/perps/close_positions.mjs +8 -0
- package/library/actions/extension/perps/ensure_orders.mjs +4 -0
- package/library/actions/extension/perps/ensure_positions.mjs +4 -0
- package/library/actions/extension/perps/perps.mjs +730 -0
- package/library/actions/extension/perps/place_order.mjs +7 -0
- package/library/actions/extension/perps/read_orders.mjs +4 -0
- package/library/actions/extension/perps/read_positions.mjs +3 -0
- package/library/actions/extension/platform/cdp.mjs +541 -0
- package/library/actions/extension/ui/navigate.mjs +44 -0
- package/library/actions/extension/wallet/ensure_unlocked.mjs +36 -0
- package/library/actions/extension/wallet/read_state.mjs +27 -0
- package/library/actions/extension/wallet/select_account.mjs +48 -0
- package/library/actions/extension/wallet/setup.mjs +35 -0
- package/library/actions/mobile/app-overlay/app/dev-tools/AgenticService/AgentStepHud.tsx.patch +185 -0
- package/library/actions/mobile/app-overlay/app/dev-tools/AgenticService/AgenticService.ts.patch +1662 -0
- package/library/actions/mobile/bridge-runtime/cdp-bridge.cjs +686 -0
- package/library/actions/mobile/bridge-runtime/lib/cdp-eval.cjs +110 -0
- package/library/actions/mobile/bridge-runtime/lib/config.cjs +39 -0
- package/library/actions/mobile/bridge-runtime/lib/issue-capture.cjs +446 -0
- package/library/actions/mobile/bridge-runtime/lib/target-discovery.cjs +204 -0
- package/library/actions/mobile/bridge-runtime/lib/ws-client.cjs +108 -0
- package/library/actions/mobile/bridge-runtime/setup-wallet.sh +442 -0
- package/library/actions/mobile/perps/assert_orders.mjs +11 -0
- package/library/actions/mobile/perps/assert_positions.mjs +11 -0
- package/library/actions/mobile/perps/close_orders.mjs +8 -0
- package/library/actions/mobile/perps/close_positions.mjs +8 -0
- package/library/actions/mobile/perps/ensure_orders.mjs +4 -0
- package/library/actions/mobile/perps/ensure_positions.mjs +4 -0
- package/library/actions/mobile/perps/perps.mjs +709 -0
- package/library/actions/mobile/perps/place_order.mjs +7 -0
- package/library/actions/mobile/perps/read_orders.mjs +4 -0
- package/library/actions/mobile/perps/read_positions.mjs +3 -0
- package/library/actions/mobile/platform/bridge.mjs +283 -0
- package/library/actions/mobile/ui/navigate.mjs +38 -0
- package/library/actions/mobile/wallet/ensure_unlocked.mjs +107 -0
- package/library/actions/mobile/wallet/home.mjs +35 -0
- package/library/actions/mobile/wallet/read_state.mjs +40 -0
- package/library/actions/mobile/wallet/select_account.mjs +48 -0
- package/library/actions/mobile/wallet/setup.mjs +220 -0
- package/library/flows/perps.flows.json +64 -0
- package/library/library.json +7 -0
- package/library/manifests/core.action-manifest.json +1282 -0
- package/library/manifests/extension.action-manifest.json +1749 -0
- package/library/manifests/mobile.action-manifest.json +1753 -0
- package/library/recipes/action-validation.extension.recipe.json +417 -0
- package/library/recipes/action-validation.mobile.recipe.json +422 -0
- package/library/recipes/order-lifecycle.core.recipe.json +78 -0
- package/library/recipes/perps-lifecycle.recipe.json +194 -0
- package/library/recipes/read-markets.core.recipe.json +38 -0
- package/library/recipes/smoke.extension.recipe.json +31 -0
- package/library/recipes/smoke.mobile.recipe.json +31 -0
- package/library/recipes/trading-lifecycle.core.recipe.json +76 -0
- package/orchestration/compat-overlays/README.md +19 -0
- package/orchestration/compat-overlays/mobile/README.md +13 -0
- package/orchestration/compat-overlays/mobile/rn81-message-event-source.patch +42 -0
- package/orchestration/core/cleanup.sh +37 -0
- package/orchestration/core/inject.sh +154 -0
- package/orchestration/doctor.mjs +72 -0
- package/orchestration/extension/cleanup.mjs +60 -0
- package/orchestration/extension/console-tail.mjs +228 -0
- package/orchestration/extension/ensure-browser.sh +416 -0
- package/orchestration/extension/ensure-ready.ts +185 -0
- package/orchestration/extension/extension-id.ts +107 -0
- package/orchestration/extension/inject.mjs +266 -0
- package/orchestration/extension/launch-browser.cjs +216 -0
- package/orchestration/extension/launch.sh +175 -0
- package/orchestration/extension/live.sh +320 -0
- package/orchestration/extension/pin-remote-flags.cjs +45 -0
- package/orchestration/extension/readiness.mjs +414 -0
- package/orchestration/extension/refresh-build.sh +190 -0
- package/orchestration/extension/runtime-decision.ts +445 -0
- package/orchestration/extension/runtime.ts +407 -0
- package/orchestration/extension/seed-fixture.sh +177 -0
- package/orchestration/extension/sidepanel-toggle.sh +291 -0
- package/orchestration/extension/snapshot-dist.sh +84 -0
- package/orchestration/extension/start-watch.sh +339 -0
- package/orchestration/extension/wallet-fixture-state.cjs +1086 -0
- package/orchestration/lib/activate-repo-node.sh +144 -0
- package/orchestration/lib/cli-color.mjs +84 -0
- package/orchestration/lib/cli-commands.mjs +243 -0
- package/orchestration/lib/cli-home.mjs +354 -0
- package/orchestration/lib/cli-ux.sh +252 -0
- package/orchestration/lib/cli-version.mjs +123 -0
- package/orchestration/lib/ensure-runner-deps.sh +56 -0
- package/orchestration/lib/harness-path.sh +55 -0
- package/orchestration/lib/hash-helpers.sh +44 -0
- package/orchestration/lib/json-field.sh +23 -0
- package/orchestration/lib/log-tui.mjs +304 -0
- package/orchestration/lib/open-debug.mjs +317 -0
- package/orchestration/lib/path-defaults.json +4 -0
- package/orchestration/lib/progress.mjs +107 -0
- package/orchestration/lib/recipe-paths.mjs +26 -0
- package/orchestration/lib/resolve-farmslot-ports.sh +144 -0
- package/orchestration/manifest.json +358 -0
- package/orchestration/mobile/cleanup.sh +192 -0
- package/orchestration/mobile/deps-markers.ts +21 -0
- package/orchestration/mobile/inject.sh +681 -0
- package/orchestration/mobile/launch.sh +137 -0
- package/orchestration/mobile/live.sh +125 -0
- package/orchestration/mobile/runtime-decision.ts +292 -0
- package/orchestration/porcelain/metamask-recipe +99 -0
- package/orchestration/porcelain/mm-recipe +1591 -0
- package/orchestration/porcelain/mme-recipe +1181 -0
- package/package.json +59 -0
- package/runner/extension/verify.sh +511 -0
- package/runner/mobile/verify.sh +501 -0
- package/runner/src/adapters.ts +601 -0
- package/runner/src/cli.ts +1820 -0
- package/runner/src/commands/debug.ts +44 -0
- package/runner/src/commands/fixtures.ts +99 -0
- package/runner/src/commands/launch.ts +397 -0
- package/runner/src/commands/logs.ts +60 -0
- package/runner/src/commands/shared.ts +138 -0
- package/runner/src/completions-cache.ts +86 -0
- package/runner/src/doctor.ts +203 -0
- package/runner/src/harness.ts +516 -0
- package/runner/src/heal-bounds.ts +179 -0
- package/runner/src/index.ts +6 -0
- package/runner/src/live-adapter-contract.ts +274 -0
- package/runner/src/manifest.ts +47 -0
- package/runner/src/mm-harness-cli.ts +488 -0
- package/runner/src/paths.ts +198 -0
- package/runner/src/recording-target.ts +147 -0
- package/runner/src/run-recording.ts +329 -0
- package/runner/src/runner.ts +108 -0
- package/runner/src/types.ts +57 -0
- package/scripts/completions.sh +125 -0
- package/scripts/install-completions.sh +62 -0
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Keep the package entrypoint narrow. CLI internals import path/runtime helpers
|
|
2
|
+
// directly so consumers see only the runner factory, manifest, and doctor API.
|
|
3
|
+
export { createDoctorReport } from './doctor.ts';
|
|
4
|
+
export { loadActionManifest, validateManifest } from './manifest.ts';
|
|
5
|
+
export { createMetaMaskExtensionRunner, createMetaMaskMobileRunner, createMetaMaskRunner } from './runner.ts';
|
|
6
|
+
export type { CreateMetaMaskRunnerOptions, MetaMaskDoctorReport, MetaMaskRecipeAdapter } from './types.ts';
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { access, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { spawn } from 'node:child_process';
|
|
6
|
+
|
|
7
|
+
import { resolveRequiredLocalProtocolRoot, runnerDir } from './paths.ts';
|
|
8
|
+
|
|
9
|
+
function actionFileStem(action: string) {
|
|
10
|
+
return String(action).replace(/[^a-zA-Z0-9._-]/g, '_');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function actionParts(action: string) {
|
|
14
|
+
const parts = String(action).split('.').filter(Boolean);
|
|
15
|
+
if (parts.length >= 3 && parts[0] === 'metamask') {
|
|
16
|
+
return { family: parts[1], localName: parts.slice(2).join('.') };
|
|
17
|
+
}
|
|
18
|
+
if (parts.length >= 2) {
|
|
19
|
+
return { family: parts[0], localName: parts.slice(1).join('.') };
|
|
20
|
+
}
|
|
21
|
+
return { family: 'actions', localName: String(action) };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function candidateStems(action: string) {
|
|
25
|
+
const stem = actionFileStem(action);
|
|
26
|
+
const { localName } = actionParts(action);
|
|
27
|
+
const localStem = actionFileStem(localName);
|
|
28
|
+
const stems = [stem];
|
|
29
|
+
if (localStem && localStem !== stem) stems.push(localStem);
|
|
30
|
+
return stems;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function candidateFamilies(action: string) {
|
|
34
|
+
const { family } = actionParts(action);
|
|
35
|
+
const families = [family];
|
|
36
|
+
return [...new Set(families)];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function candidatePaths(platform: string, action: string) {
|
|
40
|
+
const stems = candidateStems(action);
|
|
41
|
+
const families = candidateFamilies(action);
|
|
42
|
+
const roots = [
|
|
43
|
+
process.env.METAMASK_RECIPE_LIVE_ADAPTER_DIR,
|
|
44
|
+
path.join(runnerDir, 'library/actions'),
|
|
45
|
+
].filter(Boolean);
|
|
46
|
+
const files = [];
|
|
47
|
+
for (const root of roots) {
|
|
48
|
+
for (const family of families) {
|
|
49
|
+
for (const candidateStem of stems) {
|
|
50
|
+
pushCandidateFiles(files, root, platform, family, candidateStem);
|
|
51
|
+
}
|
|
52
|
+
pushDomainDispatcherFiles(files, root, platform, family);
|
|
53
|
+
}
|
|
54
|
+
for (const candidateStem of stems) {
|
|
55
|
+
pushFlatCandidateFiles(files, root, platform, candidateStem);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return files;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function pushCandidateFiles(files: string[], root: string, platform: string, family: string, stem: string) {
|
|
62
|
+
for (const extension of ['mjs', 'js', 'sh']) {
|
|
63
|
+
files.push(path.join(root, platform, family, `${stem}.${extension}`));
|
|
64
|
+
files.push(path.join(root, 'shared', family, `${stem}.${extension}`));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
function pushDomainDispatcherFiles(files: string[], root: string, platform: string, family: string) {
|
|
70
|
+
for (const extension of ['mjs', 'js', 'sh']) {
|
|
71
|
+
files.push(path.join(root, platform, family, `${family}.${extension}`));
|
|
72
|
+
files.push(path.join(root, 'shared', family, `${family}.${extension}`));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function pushFlatCandidateFiles(files: string[], root: string, platform: string, stem: string) {
|
|
77
|
+
for (const extension of ['mjs', 'js', 'sh']) {
|
|
78
|
+
files.push(path.join(root, platform, `${stem}.${extension}`));
|
|
79
|
+
files.push(path.join(root, 'shared', `${stem}.${extension}`));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function firstExecutablePath(paths: string[]) {
|
|
84
|
+
for (const file of paths) {
|
|
85
|
+
try {
|
|
86
|
+
await access(file);
|
|
87
|
+
return file;
|
|
88
|
+
} catch (error) {
|
|
89
|
+
// Try the next candidate path. Missing optional live adapters are reported by the caller.
|
|
90
|
+
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function runProcess(
|
|
97
|
+
command: string,
|
|
98
|
+
args: string[],
|
|
99
|
+
options: { cwd: string; env: NodeJS.ProcessEnv; timeoutMs?: number },
|
|
100
|
+
): Promise<{ exitCode: number | null; stdout: string; stderr: string; timedOut: boolean }> {
|
|
101
|
+
return new Promise((resolve, reject) => {
|
|
102
|
+
let settled = false;
|
|
103
|
+
const child = spawn(command, args, {
|
|
104
|
+
cwd: options.cwd,
|
|
105
|
+
env: options.env,
|
|
106
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
107
|
+
});
|
|
108
|
+
let stdout = '';
|
|
109
|
+
let stderr = '';
|
|
110
|
+
child.stdout.on('data', (chunk) => {
|
|
111
|
+
stdout += chunk;
|
|
112
|
+
});
|
|
113
|
+
child.stderr.on('data', (chunk) => {
|
|
114
|
+
stderr += chunk;
|
|
115
|
+
});
|
|
116
|
+
const timeout = options.timeoutMs
|
|
117
|
+
? setTimeout(() => {
|
|
118
|
+
if (settled) return;
|
|
119
|
+
settled = true;
|
|
120
|
+
child.kill('SIGTERM');
|
|
121
|
+
setTimeout(() => {
|
|
122
|
+
if (!child.killed) child.kill('SIGKILL');
|
|
123
|
+
}, 1000);
|
|
124
|
+
resolve({ exitCode: null, stdout, stderr, timedOut: true });
|
|
125
|
+
}, options.timeoutMs)
|
|
126
|
+
: undefined;
|
|
127
|
+
child.on('error', (error) => {
|
|
128
|
+
if (settled) return;
|
|
129
|
+
settled = true;
|
|
130
|
+
if (timeout) clearTimeout(timeout);
|
|
131
|
+
reject(error);
|
|
132
|
+
});
|
|
133
|
+
child.on('close', (exitCode) => {
|
|
134
|
+
if (settled) return;
|
|
135
|
+
settled = true;
|
|
136
|
+
if (timeout) clearTimeout(timeout);
|
|
137
|
+
resolve({ exitCode, stdout, stderr, timedOut: false });
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function commandFor(file: string) {
|
|
143
|
+
if (file.endsWith('.sh')) return { command: 'bash', args: [file] };
|
|
144
|
+
if (file.endsWith('.mjs') || file.endsWith('.js')) {
|
|
145
|
+
if (!importsSourceTypescript(file)) return { command: process.execPath, args: [file] };
|
|
146
|
+
}
|
|
147
|
+
const localTsx = path.join(runnerDir, 'node_modules/.bin/tsx');
|
|
148
|
+
const tsxBin =
|
|
149
|
+
process.env.TSX_BIN ||
|
|
150
|
+
(existsSync(localTsx)
|
|
151
|
+
? localTsx
|
|
152
|
+
: path.join(
|
|
153
|
+
resolveRequiredLocalProtocolRoot('TypeScript live adapter execution'),
|
|
154
|
+
'node_modules/.bin/tsx',
|
|
155
|
+
));
|
|
156
|
+
return { command: tsxBin, args: [file] };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function importsSourceTypescript(file: string): boolean {
|
|
160
|
+
return importsSourceTypescriptFrom(file, new Set());
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function importsSourceTypescriptFrom(file: string, visited: Set<string>): boolean {
|
|
164
|
+
const absolute = path.resolve(file);
|
|
165
|
+
if (visited.has(absolute)) return false;
|
|
166
|
+
visited.add(absolute);
|
|
167
|
+
const source = readFileSync(file, 'utf8');
|
|
168
|
+
const importPattern = /(?:from\s+|import\(\s*)['"]([^'"]+)['"]/gu;
|
|
169
|
+
for (const match of source.matchAll(importPattern)) {
|
|
170
|
+
const specifier = match[1] ?? '';
|
|
171
|
+
if (specifier.endsWith('.ts')) return true;
|
|
172
|
+
if (!specifier.startsWith('.')) continue;
|
|
173
|
+
if (!specifier.endsWith('.mjs') && !specifier.endsWith('.js')) continue;
|
|
174
|
+
if (importsSourceTypescriptFrom(path.resolve(path.dirname(absolute), specifier), visited)) {
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export async function resolveLiveAdapter(platform: string, action: string) {
|
|
182
|
+
return firstExecutablePath(candidatePaths(platform, action));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Workspace packages the headless core adapter imports as runtime values. In an
|
|
186
|
+
// unbuilt MetaMask/core checkout these have no dist/, so their package "exports"
|
|
187
|
+
// (which point only at dist) cannot resolve. We point tsx at the package src via
|
|
188
|
+
// a generated tsconfig paths map so the adapter can import the controller without
|
|
189
|
+
// building or modifying the core checkout.
|
|
190
|
+
const CORE_WORKSPACE_PACKAGES = [
|
|
191
|
+
'@metamask/base-controller',
|
|
192
|
+
'@metamask/messenger',
|
|
193
|
+
'@metamask/controller-utils',
|
|
194
|
+
'@metamask/keyring-controller',
|
|
195
|
+
];
|
|
196
|
+
|
|
197
|
+
async function platformAdapterEnv(
|
|
198
|
+
platform: string,
|
|
199
|
+
projectRoot: string,
|
|
200
|
+
tempDir: string,
|
|
201
|
+
): Promise<NodeJS.ProcessEnv> {
|
|
202
|
+
if (platform !== 'core') return {};
|
|
203
|
+
const paths: Record<string, string[]> = {};
|
|
204
|
+
for (const pkg of CORE_WORKSPACE_PACKAGES) {
|
|
205
|
+
const packageDir = path.join(projectRoot, 'packages', pkg.replace('@metamask/', ''));
|
|
206
|
+
const distEntry = path.join(packageDir, 'dist/index.cjs');
|
|
207
|
+
if (existsSync(distEntry)) continue; // built package resolves normally
|
|
208
|
+
const srcDir = path.join(packageDir, 'src');
|
|
209
|
+
if (!existsSync(path.join(srcDir, 'index.ts'))) continue;
|
|
210
|
+
paths[pkg] = [path.join(srcDir, 'index.ts')];
|
|
211
|
+
paths[`${pkg}/*`] = [path.join(srcDir, '*')];
|
|
212
|
+
}
|
|
213
|
+
if (Object.keys(paths).length === 0) return {};
|
|
214
|
+
const tsconfigPath = path.join(tempDir, 'core-adapter.tsconfig.json');
|
|
215
|
+
await writeFile(
|
|
216
|
+
tsconfigPath,
|
|
217
|
+
`${JSON.stringify({ compilerOptions: { baseUrl: projectRoot, paths } }, null, 2)}\n`,
|
|
218
|
+
);
|
|
219
|
+
return { TSX_TSCONFIG_PATH: tsconfigPath };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export async function runLiveAdapterScript({ platform, action, node, context }: { platform: string; action: string; node: Record<string, any>; context: any }) {
|
|
223
|
+
const script = await resolveLiveAdapter(platform, action);
|
|
224
|
+
if (!script) return null;
|
|
225
|
+
|
|
226
|
+
const tempDir = await mkdtemp(path.join(os.tmpdir(), 'metamask-recipe-live-adapter-'));
|
|
227
|
+
const inputPath = path.join(tempDir, 'input.json');
|
|
228
|
+
const outputPath = path.join(tempDir, 'output.json');
|
|
229
|
+
const input = {
|
|
230
|
+
schemaVersion: 1,
|
|
231
|
+
platform,
|
|
232
|
+
action,
|
|
233
|
+
node,
|
|
234
|
+
context: {
|
|
235
|
+
nodeId: context.nodeId,
|
|
236
|
+
projectRoot: context.projectRoot,
|
|
237
|
+
artifactsDir: context.artifactsDir,
|
|
238
|
+
},
|
|
239
|
+
outputPath,
|
|
240
|
+
};
|
|
241
|
+
await writeFile(inputPath, `${JSON.stringify(input, null, 2)}\n`);
|
|
242
|
+
const command = commandFor(script);
|
|
243
|
+
const platformEnv = await platformAdapterEnv(platform, context.projectRoot, tempDir);
|
|
244
|
+
const result = await runProcess(command.command, [...command.args, inputPath], {
|
|
245
|
+
cwd: context.projectRoot,
|
|
246
|
+
env: {
|
|
247
|
+
...process.env,
|
|
248
|
+
...context.env,
|
|
249
|
+
...platformEnv,
|
|
250
|
+
METAMASK_RECIPE_ADAPTER_INPUT: inputPath,
|
|
251
|
+
METAMASK_RECIPE_ADAPTER_OUTPUT: outputPath,
|
|
252
|
+
},
|
|
253
|
+
timeoutMs: Number(node.live_adapter_timeout_ms ?? node.timeout_ms ?? 60000),
|
|
254
|
+
});
|
|
255
|
+
try {
|
|
256
|
+
if (result.timedOut) {
|
|
257
|
+
throw new Error(`Live adapter ${script} timed out after ${Number(node.live_adapter_timeout_ms ?? node.timeout_ms ?? 60000)}ms.`);
|
|
258
|
+
}
|
|
259
|
+
if (result.exitCode !== 0) {
|
|
260
|
+
throw new Error(`Live adapter ${script} exited ${result.exitCode}: ${result.stderr || result.stdout}`);
|
|
261
|
+
}
|
|
262
|
+
let parsed = null;
|
|
263
|
+
try {
|
|
264
|
+
parsed = JSON.parse(await readFile(outputPath, 'utf8'));
|
|
265
|
+
} catch (_error) {
|
|
266
|
+
const stdout = result.stdout.trim();
|
|
267
|
+
if (!stdout) throw new Error(`Live adapter ${script} did not write JSON output.`);
|
|
268
|
+
parsed = JSON.parse(stdout);
|
|
269
|
+
}
|
|
270
|
+
return { script, result: parsed };
|
|
271
|
+
} finally {
|
|
272
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
273
|
+
}
|
|
274
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
|
|
3
|
+
import type { RecipeActionManifestDocument, RecipeValidationResult } from '@farmslot/protocol';
|
|
4
|
+
|
|
5
|
+
import { manifestPath, readJson, importRecipeProtocol } from './paths.ts';
|
|
6
|
+
import type { MetaMaskRecipeAdapter } from './types.ts';
|
|
7
|
+
|
|
8
|
+
export function loadMetaMaskMobileActionManifest(): RecipeActionManifestDocument {
|
|
9
|
+
return asActionManifest(readJson(manifestPath('mobile')));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function loadMetaMaskExtensionActionManifest(): RecipeActionManifestDocument {
|
|
13
|
+
return asActionManifest(readJson(manifestPath('extension')));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function loadMetaMaskCoreActionManifest(): RecipeActionManifestDocument {
|
|
17
|
+
return asActionManifest(readJson(manifestPath('core')));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function loadActionManifest(
|
|
21
|
+
adapter: MetaMaskRecipeAdapter,
|
|
22
|
+
overridePath?: string,
|
|
23
|
+
): RecipeActionManifestDocument {
|
|
24
|
+
if (overridePath) return asActionManifest(readJson(path.resolve(overridePath)));
|
|
25
|
+
if (adapter === 'mobile') return loadMetaMaskMobileActionManifest();
|
|
26
|
+
if (adapter === 'core') return loadMetaMaskCoreActionManifest();
|
|
27
|
+
return loadMetaMaskExtensionActionManifest();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function validateManifest(
|
|
31
|
+
manifest: RecipeActionManifestDocument,
|
|
32
|
+
): Promise<RecipeValidationResult> {
|
|
33
|
+
const { validateRecipeActionManifestDocument } = await importRecipeProtocol();
|
|
34
|
+
const result = validateRecipeActionManifestDocument(manifest);
|
|
35
|
+
if (result.status === 'invalid') {
|
|
36
|
+
throw new Error(
|
|
37
|
+
`Manifest invalid: ${result.findings
|
|
38
|
+
.map((finding) => `${finding.code} ${finding.path}`)
|
|
39
|
+
.join(', ')}`,
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function asActionManifest(value: unknown): RecipeActionManifestDocument {
|
|
46
|
+
return value as RecipeActionManifestDocument;
|
|
47
|
+
}
|