@deeeed/metamask-harness 0.80.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 +43 -0
- package/adapters/manifest.json +48 -64
- 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 +27 -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 +32 -34
- package/adapters/{terminal → web-dapp}/stop.mjs +5 -4
- package/adapters/{terminal → web-dapp}/verify.mjs +9 -6
- package/adapters/{terminal → web-dapp}/wallet-host.mjs +44 -227
- package/dist/adapters/core/actions.js +4 -1
- package/dist/adapters/extension/actions.js +6 -2
- package/dist/adapters/extension/network-observer.js +13 -0
- package/dist/adapters/extension/run.js +115 -31
- package/dist/adapters/mobile/actions.js +4 -1
- package/dist/adapters/mobile/network-observer.js +68 -0
- package/dist/adapters/mobile/performance-observer.js +1 -3
- package/dist/adapters/mobile/run.js +32 -9
- package/dist/adapters/perps-order-inputs.js +36 -0
- package/dist/adapters/plugin-surface.js +98 -0
- package/dist/adapters/proof-failure.js +11 -0
- package/dist/adapters/surface.js +1 -1
- package/dist/adapters/{terminal → web-dapp}/actions.js +11 -8
- 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 -19
- 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 +22 -17
- package/dist/adapters/{terminal → web-dapp}/stop.js +5 -4
- package/dist/adapters/web-dapp/surface.js +149 -0
- package/dist/adapters.js +20 -4
- package/dist/cli.js +34 -6
- package/dist/command-contract.js +29 -7
- package/dist/commands/check.js +32 -8
- package/dist/commands/completion-candidates.js +5 -5
- 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 +10 -5
- package/dist/engine.js +47 -0
- package/dist/funding-execution-context.js +81 -0
- package/dist/manifest.js +48 -24
- package/dist/mm-harness-cli.js +20 -3
- package/dist/performance-observation.js +7 -15
- package/dist/recipe-assistance-context.js +6 -7
- package/dist/runner.js +28 -8
- package/docs/CONTRIBUTING.md +4 -2
- package/docs/RECIPES.md +31 -3
- package/docs/cli-contract.md +5 -3
- package/library/actions/core/perps/_controller.mjs +24 -28
- package/library/actions/core/perps/_rewards.mjs +186 -0
- package/library/actions/core/perps/close_positions.mjs +1 -1
- package/library/actions/core/perps/place_order.mjs +167 -45
- package/library/actions/core/perps/quote_fees.mjs +7 -4
- package/library/actions/core/wallet/list_accounts.mjs +1 -1
- package/library/actions/extension/perps/close_visible_position.mjs +5 -0
- package/library/actions/extension/perps/edit_margin.mjs +4 -0
- package/library/actions/extension/perps/mutation-receipt.mjs +95 -3
- package/library/actions/extension/perps/perps.mjs +320 -3
- package/library/actions/extension/perps/place_order.mjs +3 -1
- package/library/actions/extension/perps/update_position_tpsl.mjs +4 -0
- package/library/actions/extension/platform/cdp.mjs +71 -3
- package/library/actions/mobile/perps/perps.mjs +216 -11
- package/library/actions/mobile/perps/place_order.mjs +3 -1
- package/library/actions/shared/console-collector.mjs +1 -1
- package/library/actions/shared/perps/order-policy.mjs +177 -0
- package/library/actions/{core/wallet/_fixture-accounts.mjs → shared/wallet/fixture-accounts.mjs} +18 -3
- package/library/actions/{terminal → web-dapp}/app/assert_no_console_errors.mjs +4 -4
- package/library/actions/{terminal → web-dapp}/app/launch.mjs +8 -7
- package/library/actions/{terminal → web-dapp}/platform/hud.mjs +7 -6
- package/library/actions/{terminal → web-dapp}/platform/page.mjs +13 -12
- 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 +8 -14
- package/library/actions/{terminal → web-dapp}/wallet/list_accounts.mjs +2 -2
- package/library/actions/web-dapp/wallet/read_signatures.mjs +38 -0
- package/library/manifests/core.action-manifest.json +6 -2
- package/library/manifests/extension.action-manifest.json +4 -0
- package/library/manifests/mobile.action-manifest.json +12 -0
- package/library/manifests/{terminal.action-manifest.json → web-dapp.action-manifest.json} +18 -702
- package/package.json +6 -6
- package/site/assets/help-recipes.json +1 -0
- package/adapters/terminal/lib/mainnet-guard.mjs +0 -177
- package/adapters/terminal/lib/origin.mjs +0 -30
- package/adapters/terminal/lib/page-script.mjs +0 -246
- package/adapters/terminal/lib/strict-wallet.mjs +0 -138
- package/dist/adapters/terminal/console-capture.js +0 -59
- package/dist/adapters/terminal/doctor-checks.js +0 -22
- package/dist/adapters/terminal/overlay.js +0 -18
- package/dist/adapters/terminal/readiness.js +0 -11
- package/dist/adapters/terminal/surface.js +0 -133
- package/dist/commands/call.js +0 -671
- package/dist/commands/list-executables.js +0 -245
- package/dist/commands/manifest.js +0 -477
- package/dist/commands/run-engine.js +0 -1301
- package/dist/commands/run.js +0 -730
- package/dist/discovery.js +0 -15
- package/dist/metamask-action-validation.js +0 -68
- package/dist/network-observation.js +0 -290
- 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 -177
- package/library/actions/terminal/wallet/read_signatures.mjs +0 -37
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// The venue policy of the adapter that extends web-dapp (e.g. the Terminal
|
|
2
|
+
// plugin's plugins/terminal/policy.mjs): what web-dapp's launch, wallet host,
|
|
3
|
+
// readiness and actions know about the app's venue. mm-harness sets its module
|
|
4
|
+
// path in RECIPE_WEB_DAPP_POLICY when it binds such an adapter, so every
|
|
5
|
+
// web-dapp leaf process and live action it starts reads the same policy.
|
|
6
|
+
|
|
7
|
+
import { createRequire } from 'node:module';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
|
|
10
|
+
export const POLICY_ENV = 'RECIPE_WEB_DAPP_POLICY';
|
|
11
|
+
// The policy contract this web-dapp reads; a policy declares the version it was written for.
|
|
12
|
+
export const POLICY_VERSION = 1;
|
|
13
|
+
let loaded;
|
|
14
|
+
|
|
15
|
+
// Every member web-dapp reads, checked once when the policy loads: a missing or mistyped
|
|
16
|
+
// member fails here, naming the module, instead of deep in a launch or an assertion (a
|
|
17
|
+
// missing signatureLog would silently drop the venue's L1 and mainnet rules).
|
|
18
|
+
const MEMBERS = {
|
|
19
|
+
adapterId: 'string',
|
|
20
|
+
checkout: 'object',
|
|
21
|
+
venueHosts: 'function',
|
|
22
|
+
linkHosts: 'array',
|
|
23
|
+
probe: 'object',
|
|
24
|
+
startChain: 'number',
|
|
25
|
+
refuseTypedData: 'object',
|
|
26
|
+
signatureLog: 'object',
|
|
27
|
+
startPath: 'string',
|
|
28
|
+
pagePath: 'function',
|
|
29
|
+
dependencies: 'array',
|
|
30
|
+
testnetVariable: 'string',
|
|
31
|
+
module: 'string',
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function checkPolicy(policy, file) {
|
|
35
|
+
const problems = [];
|
|
36
|
+
if (!policy || typeof policy !== 'object') problems.push('exports no policy object');
|
|
37
|
+
else {
|
|
38
|
+
if (policy.policyVersion !== POLICY_VERSION) problems.push(`policyVersion is ${policy.policyVersion ?? 'missing'}, this web-dapp reads ${POLICY_VERSION}`);
|
|
39
|
+
for (const [name, type] of Object.entries(MEMBERS)) {
|
|
40
|
+
const value = policy[name];
|
|
41
|
+
const ok = type === 'array' ? Array.isArray(value) : type === 'object' ? value !== null && typeof value === 'object' : typeof value === type;
|
|
42
|
+
if (!ok) problems.push(`${name} must be ${type === 'array' || type === 'object' ? `an ${type}` : `a ${type}`}`);
|
|
43
|
+
}
|
|
44
|
+
const { checkout, refuseTypedData, signatureLog } = policy;
|
|
45
|
+
if (checkout && typeof checkout.matches !== 'function') problems.push('checkout.matches must be a function');
|
|
46
|
+
for (const name of ['name', 'label', 'needs']) if (checkout && typeof checkout[name] !== 'string') problems.push(`checkout.${name} must be a string`);
|
|
47
|
+
if (refuseTypedData && typeof refuseTypedData.reason !== 'function') problems.push('refuseTypedData.reason must be a function');
|
|
48
|
+
for (const name of ['kind', 'message']) if (refuseTypedData && typeof refuseTypedData[name] !== 'string') problems.push(`refuseTypedData.${name} must be a string`);
|
|
49
|
+
// The venue's request-log rules: both maps are required (empty only on purpose), so a
|
|
50
|
+
// policy cannot drop its L1 or mainnet rules by omission.
|
|
51
|
+
for (const name of ['typedDataClasses', 'forbiddenEntries']) {
|
|
52
|
+
if (signatureLog && (signatureLog[name] === null || typeof signatureLog[name] !== 'object')) problems.push(`signatureLog.${name} must be an object`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (problems.length) {
|
|
56
|
+
throw new Error(`web-dapp venue policy ${file}: ${problems.join('; ')}.\nNext: update the library that declares the adapter to a revision written for this mm-harness.`);
|
|
57
|
+
}
|
|
58
|
+
return policy;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function webDappPolicy(env = process.env) {
|
|
62
|
+
const file = env[POLICY_ENV];
|
|
63
|
+
if (!file) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
'web-dapp needs a venue policy: select an adapter that extends it.\n' +
|
|
66
|
+
'Next: put the library that declares it on RECIPE_LIBRARY_PATH and pass its id, e.g. --adapter terminal.',
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
if (loaded?.file !== file) {
|
|
70
|
+
// The policy module uses node built-ins only and has no top-level await, so it loads synchronously.
|
|
71
|
+
loaded = { file, policy: checkPolicy(createRequire(import.meta.url)(path.resolve(file)).policy, file) };
|
|
72
|
+
}
|
|
73
|
+
return loaded.policy;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// The bound adapter's id, for names and paths; web-dapp's own when none is bound.
|
|
77
|
+
export function webDappAdapterId(env = process.env) {
|
|
78
|
+
return env[POLICY_ENV] ? webDappPolicy(env).adapterId : 'web-dapp';
|
|
79
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Stop the
|
|
1
|
+
// Stop the web-dapp slot browser and wallet host by their pid files and prove
|
|
2
2
|
// the slot CDP port is released. A pid is signalled only when its command line
|
|
3
3
|
// carries this slot's owner marker for that process kind. A pid file naming a
|
|
4
4
|
// dead process is removed; one naming a live process without the marker is
|
|
@@ -10,7 +10,7 @@ import { existsSync, readFileSync, rmSync } from 'node:fs';
|
|
|
10
10
|
import net from 'node:net';
|
|
11
11
|
import path from 'node:path';
|
|
12
12
|
|
|
13
|
-
import {
|
|
13
|
+
import { webDappPolicy, webDappRuntimeDir } from '../../../library/actions/web-dapp/platform/runtime.mjs';
|
|
14
14
|
|
|
15
15
|
export function pidAlive(pid) {
|
|
16
16
|
if (!Number.isInteger(Number(pid)) || Number(pid) <= 0) return false;
|
|
@@ -58,7 +58,7 @@ export const OWNER_FLAG = '--mm-harness-owner';
|
|
|
58
58
|
export const OWNER_KINDS = Object.freeze(['browser', 'wallet-host', 'console-collector']);
|
|
59
59
|
|
|
60
60
|
export function ownerMarker(kind, runtimeDir) {
|
|
61
|
-
if (!OWNER_KINDS.includes(kind)) throw new Error(`unknown
|
|
61
|
+
if (!OWNER_KINDS.includes(kind)) throw new Error(`unknown web-dapp process kind ${kind}`);
|
|
62
62
|
const id = createHash('sha256').update(path.resolve(runtimeDir)).digest('hex').slice(0, 32);
|
|
63
63
|
return `${OWNER_FLAG}=${kind}-${id}`;
|
|
64
64
|
}
|
|
@@ -104,10 +104,10 @@ export async function terminate(pid, graceMs = 8000) {
|
|
|
104
104
|
return true;
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
// The console collector a run started (src/adapters/
|
|
107
|
+
// The console collector a run started (src/adapters/web-dapp/console-capture.ts)
|
|
108
108
|
// outlives browser relaunches inside the run; stop and cleanup end it.
|
|
109
109
|
export async function stopConsoleCollector(target, { log = () => {}, runtimeDir } = {}) {
|
|
110
|
-
const runtime = runtimeDir ??
|
|
110
|
+
const runtime = runtimeDir ?? webDappRuntimeDir(target);
|
|
111
111
|
const pidFile = path.join(runtime, 'console-tail.pid');
|
|
112
112
|
const pid = readPid(pidFile);
|
|
113
113
|
if (!pid) return { stopped: null };
|
|
@@ -149,8 +149,8 @@ export function cdpListenerPids(port) {
|
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
-
export async function
|
|
153
|
-
const runtime = runtimeDir ??
|
|
152
|
+
export async function stopWebDappBrowser(target, { cdpPort, log = () => {}, runtimeDir } = {}) {
|
|
153
|
+
const runtime = runtimeDir ?? webDappRuntimeDir(target);
|
|
154
154
|
const stopped = [];
|
|
155
155
|
const stale = [];
|
|
156
156
|
const foreign = [];
|
|
@@ -181,7 +181,7 @@ export async function stopTerminalBrowser(target, { cdpPort, log = () => {}, run
|
|
|
181
181
|
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
182
182
|
free = await portFree(cdpPort);
|
|
183
183
|
}
|
|
184
|
-
if (!free) throw new Error(`CDP port ${cdpPort} is still in use after stopping the
|
|
184
|
+
if (!free) throw new Error(`CDP port ${cdpPort} is still in use after stopping the ${webDappPolicy().adapterId} browser; another process owns it.`);
|
|
185
185
|
}
|
|
186
186
|
return { stopped, stale, foreign, cdpPortFree: free };
|
|
187
187
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Read-only readiness of a
|
|
1
|
+
// Read-only readiness of a web-dapp slot for recipe runs: checkout shape,
|
|
2
2
|
// dependencies, the slot dev server, forced testnet, wallet fixture, browser,
|
|
3
3
|
// MetaMask extension copy and the per-slot CDP port. Never launches anything
|
|
4
4
|
// except the optional short-lived browser start probe.
|
|
@@ -7,23 +7,13 @@ import { execFileSync } from 'node:child_process';
|
|
|
7
7
|
import { existsSync, readFileSync } from 'node:fs';
|
|
8
8
|
import path from 'node:path';
|
|
9
9
|
|
|
10
|
-
import { canonicalPath, fixtureEntry, loadWalletFixture, readBrowserState,
|
|
10
|
+
import { canonicalPath, fixtureEntry, loadWalletFixture, readBrowserState, viemAccountFromEntry, webDappPolicy, webDappRuntimeDir } from '../../../library/actions/web-dapp/platform/runtime.mjs';
|
|
11
11
|
import { ownsProcess, pidAlive, portFree } from './processes.mjs';
|
|
12
12
|
|
|
13
13
|
function check(id, ok, detail, { required = true, status } = {}) {
|
|
14
14
|
return { id, status: status ?? (ok ? 'pass' : 'fail'), required, detail };
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
export function isTerminalCheckout(target) {
|
|
18
|
-
try {
|
|
19
|
-
const pkg = JSON.parse(readFileSync(path.join(target, 'package.json'), 'utf8'));
|
|
20
|
-
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
21
|
-
return Boolean(deps.next) && existsSync(path.join(target, 'src/features/perpetuals'));
|
|
22
|
-
} catch {
|
|
23
|
-
return false;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
17
|
async function httpStatus(url, timeoutMs = 20000) {
|
|
28
18
|
try {
|
|
29
19
|
const response = await fetch(url, { signal: AbortSignal.timeout(timeoutMs), redirect: 'manual' });
|
|
@@ -34,18 +24,18 @@ async function httpStatus(url, timeoutMs = 20000) {
|
|
|
34
24
|
}
|
|
35
25
|
|
|
36
26
|
// Diagnostic only. Whether the slot's dev server looks configured for testnet:
|
|
37
|
-
// the farm starts `next dev` (temp/farmslot/next-dev.pid) with
|
|
38
|
-
//
|
|
27
|
+
// the farm starts `next dev` (temp/farmslot/next-dev.pid) with the policy's
|
|
28
|
+
// testnet variable set to true (the Terminal's
|
|
29
|
+
// NEXT_PUBLIC_HYPERLIQUID_FORCE_TESTNET). Process inspection cannot prove
|
|
39
30
|
// what the browser talks to (macOS reports a process's initial environment,
|
|
40
31
|
// and lsof cannot see other users' sockets), so this only warns; the browser
|
|
41
|
-
// is held to testnet by the network and wallet layers (
|
|
42
|
-
// wallet host). Each listener on the app port must descend
|
|
43
|
-
// process and run from the checkout (its cwd); every process on the way from
|
|
32
|
+
// is held to testnet by the network and wallet layers (the venue policy's
|
|
33
|
+
// blocked hosts, the wallet host). Each listener on the app port must descend
|
|
34
|
+
// from the pid-file process and run from the checkout (its cwd); every process on the way from
|
|
44
35
|
// a listener up to the pid-file process is read exactly (argv and environment
|
|
45
36
|
// entries from sysctl KERN_PROCARGS2) and must carry the testnet variable. An
|
|
46
37
|
// unreadable process makes the result unknown.
|
|
47
38
|
export const DEV_SERVER_PID_FILE = 'temp/farmslot/next-dev.pid';
|
|
48
|
-
const FORCE_VARIABLE = 'NEXT_PUBLIC_HYPERLIQUID_FORCE_TESTNET';
|
|
49
39
|
|
|
50
40
|
function run(command, args) {
|
|
51
41
|
try {
|
|
@@ -142,7 +132,7 @@ function chainTo(listener, root, parents) {
|
|
|
142
132
|
return null;
|
|
143
133
|
}
|
|
144
134
|
|
|
145
|
-
export function devServerTestnetDiagnostic(target, { appPort, probe = systemProbe } = {}) {
|
|
135
|
+
export function devServerTestnetDiagnostic(target, { appPort, variable, probe = systemProbe } = {}) {
|
|
146
136
|
const checkout = canonicalPath(target);
|
|
147
137
|
const unknown = (detail) => ({ known: false, forced: false, detail });
|
|
148
138
|
const pidFile = path.join(checkout, DEV_SERVER_PID_FILE);
|
|
@@ -170,32 +160,40 @@ export function devServerTestnetDiagnostic(target, { appPort, probe = systemProb
|
|
|
170
160
|
const processMode = nextServerMode(args.argv);
|
|
171
161
|
if (processMode === 'start') return { known: true, forced: false, detail: `pid ${pid} runs next start; its environment cannot prove what the build inlined` };
|
|
172
162
|
if (processMode === 'dev') mode = 'dev';
|
|
173
|
-
const value = environmentValue(args.env,
|
|
163
|
+
const value = environmentValue(args.env, variable);
|
|
174
164
|
if (value !== 'true') {
|
|
175
|
-
return { known: true, forced: false, detail: `pid ${pid} (between app port ${appPort} and next dev pid ${root}) has ${
|
|
165
|
+
return { known: true, forced: false, detail: `pid ${pid} (between app port ${appPort} and next dev pid ${root}) has ${variable}=${value ?? '<unset>'}` };
|
|
176
166
|
}
|
|
177
167
|
}
|
|
178
168
|
}
|
|
179
169
|
if (mode !== 'dev') return { known: true, forced: false, detail: `no process serving app port ${appPort} runs next dev` };
|
|
180
|
-
return { known: true, forced: true, detail: `every process from app port ${appPort}'s listeners (cwd ${checkout}) up to next dev pid ${root} has ${
|
|
170
|
+
return { known: true, forced: true, detail: `every process from app port ${appPort}'s listeners (cwd ${checkout}) up to next dev pid ${root} has ${variable}=true` };
|
|
181
171
|
}
|
|
182
172
|
|
|
183
173
|
export const MAINNET_CONFIRMATION = 'REAL_FUNDS';
|
|
184
174
|
|
|
185
175
|
// The launch only checks the requested network: mainnet needs the real-funds
|
|
186
|
-
// confirmation. Testnet is enforced inside the browser (
|
|
176
|
+
// confirmation. Testnet is enforced inside the browser (the venue policy's blocked hosts).
|
|
187
177
|
export function assertLaunchNetwork(_target, { network, mainnetConfirmation } = {}) {
|
|
188
178
|
const requested = network == null || network === '' ? 'testnet' : String(network);
|
|
189
179
|
if (requested !== 'testnet' && requested !== 'mainnet') {
|
|
190
180
|
throw new Error(`network must be testnet or mainnet, got ${JSON.stringify(network)}.`);
|
|
191
181
|
}
|
|
192
182
|
if (requested === 'mainnet' && mainnetConfirmation !== MAINNET_CONFIRMATION) {
|
|
193
|
-
throw new Error(`network=mainnet needs mainnet_confirmation=${MAINNET_CONFIRMATION}; refusing to launch the
|
|
183
|
+
throw new Error(`network=mainnet needs mainnet_confirmation=${MAINNET_CONFIRMATION}; refusing to launch the ${webDappPolicy().adapterId} browser.`);
|
|
194
184
|
}
|
|
195
185
|
return requested;
|
|
196
186
|
}
|
|
197
187
|
|
|
198
|
-
|
|
188
|
+
// "a, b and c".
|
|
189
|
+
function listing(names) {
|
|
190
|
+
return names.length > 1 ? `${names.slice(0, -1).join(', ')} and ${names.at(-1)}` : names.join('');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// `policy`: the venue policy (checkout, dependencies, startPath, testnetVariable);
|
|
194
|
+
// by default the one RECIPE_WEB_DAPP_POLICY names.
|
|
195
|
+
export async function webDappReadiness({
|
|
196
|
+
policy = webDappPolicy(),
|
|
199
197
|
target,
|
|
200
198
|
appPort,
|
|
201
199
|
cdpPort,
|
|
@@ -205,19 +203,19 @@ export async function terminalReadiness({
|
|
|
205
203
|
probeBrowser = undefined,
|
|
206
204
|
}) {
|
|
207
205
|
const checks = [];
|
|
208
|
-
const shape =
|
|
209
|
-
checks.push(check('checkout', shape, shape ?
|
|
210
|
-
const deps =
|
|
211
|
-
checks.push(check('dependencies', deps.length === 0, deps.length === 0 ?
|
|
206
|
+
const shape = policy.checkout.matches(target);
|
|
207
|
+
checks.push(check('checkout', shape, shape ? policy.checkout.label : `${target} is not a ${policy.checkout.name} checkout`));
|
|
208
|
+
const deps = policy.dependencies.filter((name) => !existsSync(path.join(target, 'node_modules', name, 'package.json')));
|
|
209
|
+
checks.push(check('dependencies', deps.length === 0, deps.length === 0 ? `${listing(policy.dependencies)} installed` : `missing: ${deps.join(', ')} (run npm ci on Node 22)`));
|
|
212
210
|
|
|
213
211
|
if (appPort) {
|
|
214
|
-
const status = await httpStatus(`http://127.0.0.1:${appPort}
|
|
215
|
-
checks.push(check('app-dev-server', status === 200, `GET http://localhost:${appPort}
|
|
212
|
+
const status = await httpStatus(`http://127.0.0.1:${appPort}${policy.startPath}`);
|
|
213
|
+
checks.push(check('app-dev-server', status === 200, `GET http://localhost:${appPort}${policy.startPath} -> ${status}`));
|
|
216
214
|
} else {
|
|
217
215
|
checks.push(check('app-dev-server', false, 'no dev-server port; pass --watcher-port <port> or set TERMINAL_APP_PORT'));
|
|
218
216
|
}
|
|
219
|
-
// Advisory: the browser enforces testnet itself (
|
|
220
|
-
const testnet = devServerTestnetDiagnostic(target, { appPort });
|
|
217
|
+
// Advisory: the browser enforces testnet itself (the venue policy's blocked hosts).
|
|
218
|
+
const testnet = devServerTestnetDiagnostic(target, { appPort, variable: policy.testnetVariable });
|
|
221
219
|
checks.push(check('testnet-forced', testnet.known && testnet.forced, testnet.detail, {
|
|
222
220
|
required: false,
|
|
223
221
|
status: testnet.known && testnet.forced ? 'pass' : 'warn',
|
|
@@ -256,7 +254,7 @@ export async function terminalReadiness({
|
|
|
256
254
|
: 'TERMINAL_EXTENSION_CHECKOUT unset or not installed (needed to import the wallet into the extension)', { required: extensionRequired }));
|
|
257
255
|
|
|
258
256
|
const state = await readBrowserState(target);
|
|
259
|
-
const runtime =
|
|
257
|
+
const runtime = webDappRuntimeDir(target);
|
|
260
258
|
const live = Boolean(state && ownsProcess('browser', state.browserPid, runtime) && ownsProcess('wallet-host', state.hostPid, runtime));
|
|
261
259
|
if (cdpPort) {
|
|
262
260
|
const free = await portFree(cdpPort);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// stop.mjs — stop the
|
|
2
|
+
// stop.mjs — stop the web-dapp slot browser, wallet host and console collector, verify the CDP port
|
|
3
3
|
// is free. Idempotent: nothing running is success.
|
|
4
4
|
//
|
|
5
5
|
// Inputs: --target <checkout> [--cdp-port <port>] [--json]
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
import path from 'node:path';
|
|
10
10
|
import { fileURLToPath } from 'node:url';
|
|
11
11
|
|
|
12
|
-
import {
|
|
12
|
+
import { webDappPolicy } from '../../library/actions/web-dapp/platform/runtime.mjs';
|
|
13
|
+
import { stopConsoleCollector, stopWebDappBrowser } from './lib/processes.mjs';
|
|
13
14
|
|
|
14
15
|
const usage = 'Usage: stop.mjs --target <checkout> [--cdp-port <port>] [--json]';
|
|
15
16
|
|
|
@@ -26,8 +27,8 @@ if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.me
|
|
|
26
27
|
const target = value('--target') ?? process.cwd();
|
|
27
28
|
const rawPort = value('--cdp-port') ?? process.env.RECIPE_CDP_PORT ?? process.env.CDP_PORT;
|
|
28
29
|
try {
|
|
29
|
-
const log = (message) => process.stderr.write(`[
|
|
30
|
-
const result = await
|
|
30
|
+
const log = (message) => process.stderr.write(`[${webDappPolicy().adapterId}/stop] ${message}\n`);
|
|
31
|
+
const result = await stopWebDappBrowser(target, { cdpPort: rawPort ? Number(rawPort) : undefined, log });
|
|
31
32
|
const collector = await stopConsoleCollector(target, { log });
|
|
32
33
|
process.stdout.write(`${JSON.stringify({ status: 'pass', ...result, consoleCollector: collector })}\n`);
|
|
33
34
|
} catch (error) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// verify.mjs — `mm-harness verify --adapter
|
|
3
|
-
//
|
|
2
|
+
// verify.mjs — `mm-harness verify --adapter <web-dapp adapter>`: read-only readiness of a
|
|
3
|
+
// web-dapp slot (checkout, deps, dev server, forced testnet, fixture,
|
|
4
4
|
// browser start probe, MetaMask extension copy, CDP port).
|
|
5
5
|
//
|
|
6
6
|
// Inputs: --target <checkout> [--cdp-port <port>] [--watcher-port <app port>]
|
|
@@ -12,7 +12,8 @@ import path from 'node:path';
|
|
|
12
12
|
import { fileURLToPath } from 'node:url';
|
|
13
13
|
|
|
14
14
|
import { resolveBrowser } from './launch.mjs';
|
|
15
|
-
import {
|
|
15
|
+
import { webDappPolicy } from '../../library/actions/web-dapp/platform/runtime.mjs';
|
|
16
|
+
import { webDappReadiness } from './lib/readiness.mjs';
|
|
16
17
|
|
|
17
18
|
const usage = 'Usage: verify.mjs --target <checkout> [--cdp-port <port>] [--watcher-port <app port>] [--signer extension|injected] [--account <name>] [--json]';
|
|
18
19
|
|
|
@@ -27,7 +28,9 @@ if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.me
|
|
|
27
28
|
return index >= 0 ? argv[index + 1] : undefined;
|
|
28
29
|
};
|
|
29
30
|
const num = (raw) => (raw && /^\d+$/u.test(raw) ? Number(raw) : undefined);
|
|
30
|
-
const
|
|
31
|
+
const policy = webDappPolicy();
|
|
32
|
+
const report = await webDappReadiness({
|
|
33
|
+
policy,
|
|
31
34
|
target: path.resolve(value('--target') ?? process.cwd()),
|
|
32
35
|
appPort: num(value('--watcher-port') ?? process.env.TERMINAL_APP_PORT ?? process.env.WATCHER_PORT),
|
|
33
36
|
cdpPort: num(value('--cdp-port') ?? process.env.RECIPE_CDP_PORT ?? process.env.CDP_PORT),
|
|
@@ -36,13 +39,13 @@ if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.me
|
|
|
36
39
|
probeBrowser: resolveBrowser,
|
|
37
40
|
});
|
|
38
41
|
if (argv.includes('--json')) {
|
|
39
|
-
process.stdout.write(`${JSON.stringify({ schemaVersion: 1, command: 'verify', adapter:
|
|
42
|
+
process.stdout.write(`${JSON.stringify({ schemaVersion: 1, command: 'verify', adapter: policy.adapterId, ...report }, null, 2)}\n`);
|
|
40
43
|
} else {
|
|
41
44
|
for (const check of report.checks) {
|
|
42
45
|
const mark = check.status === 'pass' ? '✓' : check.status === 'warn' || !check.required ? '·' : '✗';
|
|
43
46
|
process.stdout.write(`${mark} ${check.id}: ${check.detail}\n`);
|
|
44
47
|
}
|
|
45
|
-
process.stdout.write(report.status === 'pass' ?
|
|
48
|
+
process.stdout.write(report.status === 'pass' ? `${policy.adapterId} slot ready\n` : `not ready: ${report.failed.join(', ')}\n`);
|
|
46
49
|
}
|
|
47
50
|
process.exit(report.status === 'pass' ? 0 : 1);
|
|
48
51
|
}
|