@deeeed/metamask-harness 0.17.5 → 0.18.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 +872 -0
- package/README.md +94 -140
- package/adapters/extension/console-tail.mjs +55 -24
- package/adapters/extension/ensure-browser.sh +6 -1
- package/adapters/extension/inject.mjs +6 -9
- package/adapters/extension/launch-browser.cjs +7 -1
- package/adapters/extension/launch-webpack.cjs +83 -0
- package/adapters/extension/launch.sh +8 -11
- package/adapters/extension/lib/chrome-args.cjs +8 -1
- package/adapters/extension/lib/macos-focus.cjs +32 -0
- package/adapters/extension/live.sh +12 -21
- package/adapters/extension/reattach.sh +1 -0
- package/adapters/extension/seed-fixture.sh +4 -12
- package/adapters/extension/sidepanel-toggle.sh +4 -1
- package/adapters/extension/stamp-runtime-title.cjs +58 -0
- package/adapters/extension/start-watch.sh +17 -4
- package/adapters/extension/stop-viewers.sh +1 -1
- package/adapters/extension/sync-webpack-dist.cjs +107 -0
- package/adapters/extension/verify.sh +1 -1
- package/adapters/manifest.json +53 -37
- package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +16 -0
- package/adapters/mobile/bridge-runtime/console-forwarder.cjs +1 -1
- package/adapters/mobile/bridge-runtime/lib/match-bridge-target.cjs +3 -1
- package/adapters/mobile/bridge-runtime/lib/target-discovery.cjs +2 -3
- package/adapters/mobile/cleanup.sh +16 -176
- package/adapters/mobile/inject.sh +90 -671
- package/adapters/mobile/launch-console-forwarder.cjs +39 -0
- package/adapters/mobile/open-device.sh +45 -7
- package/adapters/mobile/start-console-forwarder.sh +70 -0
- package/adapters/mobile/start-metro.sh +0 -41
- package/adapters/mobile/verify.sh +19 -64
- package/adapters/mobile/wait-for-bridge.sh +22 -8
- package/adapters/shared/resolve-slot-ports-core.mjs +17 -6
- package/adapters/shared/resolve-slot-ports.sh +17 -5
- package/adapters/shared/update-check-worker.mjs +43 -0
- package/bin/mm-harness +21 -2
- package/dist/adapters/core/surface.js +4 -1
- package/dist/adapters/extension/console-capture.js +95 -0
- package/dist/adapters/extension/product-config.js +110 -0
- package/dist/adapters/extension/runtime-decision.js +20 -2
- package/dist/adapters/extension/surface.js +22 -3
- package/dist/adapters/mobile/perps-env.js +43 -3
- package/dist/adapters/mobile/prepare.js +39 -7
- package/dist/adapters/mobile/surface.js +5 -2
- package/dist/adapters/resolve-slot-ports.js +2 -2
- package/dist/adapters/slot-ports.js +13 -16
- package/dist/adapters.js +56 -16
- package/dist/checkout-lock.js +27 -2
- package/dist/cli-color.js +19 -0
- package/dist/cli-commands.js +1 -1
- package/dist/cli.js +7 -14
- package/dist/command-contract.js +451 -0
- package/dist/command-journal.js +225 -0
- package/dist/commands/call.js +170 -50
- package/dist/commands/check.js +9 -3
- package/dist/commands/completion-candidates.js +20 -13
- package/dist/commands/device-target.js +27 -12
- package/dist/commands/doctor.js +106 -25
- package/dist/commands/fixtures.js +92 -34
- package/dist/commands/flows.js +39 -10
- package/dist/commands/last.js +52 -0
- package/dist/commands/launch/extension.js +38 -15
- package/dist/commands/launch/index.js +207 -63
- package/dist/commands/list-executables.js +151 -29
- package/dist/commands/logs.js +8 -6
- package/dist/commands/manifest.js +270 -35
- package/dist/commands/parse-args.js +13 -1
- package/dist/commands/provision.js +10 -3
- package/dist/commands/run-engine.js +435 -105
- package/dist/commands/run-report.js +12 -3
- package/dist/commands/run.js +355 -50
- package/dist/commands/shared.js +75 -3
- package/dist/commands/status-probe.js +4 -1
- package/dist/commands/status.js +2 -1
- package/dist/commands/stop.js +7 -2
- package/dist/commands/update.js +16 -28
- package/dist/completions-cache.js +1 -1
- package/dist/doctor.js +57 -10
- package/dist/harness.js +47 -137
- package/dist/heal-bounds.js +2 -2
- package/dist/json-stream.js +57 -0
- package/dist/live-adapter-contract.js +138 -32
- package/dist/manifest.js +161 -1
- package/dist/mm-harness-cli.js +135 -54
- package/dist/paths.js +2 -5
- package/dist/recipe-security.js +178 -0
- package/dist/run-diagnostics.js +261 -0
- package/dist/runner.js +117 -8
- package/docs/CONTRIBUTING.md +137 -0
- package/docs/QA.md +185 -0
- package/docs/RECIPES.md +161 -0
- package/docs/SECURITY.md +88 -0
- package/library/README.md +4 -0
- package/library/actions/core/perps/_controller.mjs +10 -55
- package/library/actions/core/perps/read_account.mjs +2 -2
- package/library/actions/core/perps/read_orders.mjs +2 -1
- package/library/actions/core/perps/read_positions.mjs +2 -1
- package/library/actions/core/wallet/list_accounts.mjs +95 -0
- package/library/actions/extension/platform/cdp.mjs +1 -0
- package/library/actions/extension/wallet/list_accounts.mjs +41 -0
- package/library/actions/mobile/wallet/list_accounts.mjs +37 -0
- package/library/manifests/core.action-manifest.json +68 -7
- package/library/manifests/extension.action-manifest.json +53 -0
- package/library/manifests/mobile.action-manifest.json +81 -3
- package/library/recipes/runner/action-validation.extension.recipe.json +8 -1
- package/library/recipes/runner/action-validation.mobile.recipe.json +8 -1
- package/library/recipes/runner/smoke.core.recipe.json +27 -0
- package/library/recipes/wallet/smoke.extension.recipe.json +42 -0
- package/library/recipes/wallet/smoke.mobile.recipe.json +42 -0
- package/package.json +12 -5
- package/scripts/completions.sh +7 -7
- package/scripts/validate-human-outcomes.mjs +169 -0
- package/adapters/mobile/overlay/app/dev-tools/AgenticService/AgentStepHud.tsx.patch +0 -185
- package/adapters/mobile/overlay/app/dev-tools/AgenticService/AgenticService.ts.patch +0 -1662
- package/adapters/mobile/overlay/compat/README.md +0 -28
- package/adapters/mobile/overlay/compat/rn81-message-event-source.patch +0 -42
- package/adapters/shared/resolve-farmslot-ports-core.mjs +0 -3
- package/adapters/shared/resolve-farmslot-ports.mjs +0 -5
- package/adapters/shared/resolve-farmslot-ports.sh +0 -7
- package/docs/ADAPTER-SURFACE.md +0 -119
- package/docs/CHEATSHEET.md +0 -61
- package/docs/CLI-SPEC.md +0 -1098
- package/docs/CODE-MAP.md +0 -62
- package/docs/DEBUG-HANDOVER.md +0 -36
- package/docs/MENTAL-MODEL.md +0 -295
- package/docs/UX-PRINCIPLES.md +0 -64
- package/docs/architecture.md +0 -398
- package/docs/live-adapter-contract.md +0 -188
- package/docs/package-boundaries.md +0 -47
- package/docs/perps-flow-catalog.md +0 -235
- package/docs/recipe-libraries.md +0 -301
- package/docs/runtime-file-conventions.md +0 -36
package/README.md
CHANGED
|
@@ -1,177 +1,131 @@
|
|
|
1
|
-
#
|
|
1
|
+
# mm-harness
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
One CLI for operating MetaMask Extension, Mobile, and Core and producing
|
|
4
|
+
reviewable recipe evidence. Run it inside a checkout; the product, slot, ports,
|
|
5
|
+
and runtime paths are detected automatically.
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
thin MetaMask adapter over the generic `@farmslot/recipe-harness` engine — the
|
|
9
|
-
runner never re-implements graph execution, it binds to it. The agent gets full
|
|
10
|
-
depth (explicit flags, `--json`, ports, build tiers); the human gets short
|
|
11
|
-
commands, positional targets, good defaults, completions, and teaching
|
|
12
|
-
help/errors. Run it from inside a MetaMask Mobile/Extension/Core checkout and the
|
|
13
|
-
platform is auto-detected.
|
|
7
|
+
- **Action:** one typed operation.
|
|
8
|
+
- **Flow:** a reusable, parameterized sequence of actions.
|
|
9
|
+
- **Recipe:** the complete proof for a task or regression.
|
|
14
10
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
DISCOVER actions · call · flows compose recipes from the vocabulary + flow library
|
|
18
|
-
PROVE run · doctor run recipes, check readiness
|
|
19
|
-
RUNTIME OVERLAY install · verify · cleanup manage the per-checkout runtime overlay
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
**One bin, no per-platform binaries.** `mm-harness` is the only command. Grammar:
|
|
23
|
-
`mm-harness <command> [target] [flags]` — the human types the bare command
|
|
24
|
-
(platform auto-detected) or a positional target (`mm-harness launch ios`); depth
|
|
25
|
-
lives in FLAGS with good defaults, never in extra command names. It ships shell
|
|
26
|
-
completions (zsh + bash) for commands, targets, and flags.
|
|
27
|
-
|
|
28
|
-
Naming: **"harness" is the tool** (`mm-harness`) and the generic engine package
|
|
29
|
-
(`@farmslot/recipe-harness`); the files it installs into a checkout are the
|
|
30
|
-
**runtime overlay**. Read [MENTAL-MODEL.md](docs/MENTAL-MODEL.md) first (the
|
|
31
|
-
before→after map), then [CLI-SPEC.md](docs/CLI-SPEC.md) for the full contract.
|
|
11
|
+
The generic graph engine and evidence schemas live in Farmslot packages.
|
|
12
|
+
`mm-harness` owns MetaMask runtime control and domain capabilities.
|
|
32
13
|
|
|
33
|
-
|
|
14
|
+
## Install
|
|
34
15
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
## Quick start
|
|
16
|
+
```bash
|
|
17
|
+
npm install -g @deeeed/metamask-harness@latest
|
|
18
|
+
mm-harness --version
|
|
19
|
+
mm-harness doctor
|
|
20
|
+
```
|
|
42
21
|
|
|
43
|
-
|
|
22
|
+
`doctor` is read-only. `doctor --fix` repairs harness-owned runtime state but
|
|
23
|
+
does not launch an app, invent credentials, or choose a wallet fixture.
|
|
44
24
|
|
|
45
25
|
```bash
|
|
46
|
-
|
|
47
|
-
mm-harness
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
mm-harness run recipe.json --adapter mobile --artifacts-dir /tmp/recipe-artifacts --json
|
|
26
|
+
mm-harness doctor --fix
|
|
27
|
+
mm-harness fixtures init --from /secure/path/wallet-fixture.json
|
|
28
|
+
# Disposable public testing only; never fund this wallet:
|
|
29
|
+
mm-harness fixtures init --dev
|
|
51
30
|
```
|
|
52
31
|
|
|
53
|
-
|
|
54
|
-
manifest.
|
|
32
|
+
Use `mm-harness update` to update a published installation.
|
|
55
33
|
|
|
56
|
-
##
|
|
34
|
+
## Operate
|
|
57
35
|
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
mm-harness
|
|
36
|
+
```bash
|
|
37
|
+
# Extension
|
|
38
|
+
mm-harness launch
|
|
39
|
+
mm-harness launch --sidepanel
|
|
40
|
+
|
|
41
|
+
# Mobile
|
|
42
|
+
mm-harness launch ios
|
|
43
|
+
mm-harness launch android
|
|
44
|
+
|
|
45
|
+
# Any checkout
|
|
46
|
+
mm-harness status
|
|
47
|
+
mm-harness stop
|
|
48
|
+
mm-harness logs
|
|
49
|
+
mm-harness debug
|
|
50
|
+
mm-harness fixtures set
|
|
61
51
|
```
|
|
62
52
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
typed adapters). adapters/ = what CONTROLS the app and its instances (start,
|
|
66
|
-
windows, wallet state, health, ports, parallel).
|
|
53
|
+
Extension `launch` keeps its incremental watcher running. Refresh the active
|
|
54
|
+
page after a successful rebuild; use `launch --build` only for a clean rebuild.
|
|
67
55
|
|
|
68
|
-
|
|
56
|
+
Log sources stay separate:
|
|
69
57
|
|
|
70
58
|
```bash
|
|
71
|
-
|
|
72
|
-
mm-harness
|
|
73
|
-
mm-harness
|
|
74
|
-
mm-harness
|
|
75
|
-
mm-harness
|
|
76
|
-
|
|
77
|
-
#
|
|
78
|
-
mm-harness doctor # read-only readiness check (no launch)
|
|
79
|
-
mm-harness doctor --fix # heal the overlay without launching
|
|
80
|
-
mm-harness run recipe.json --plan --adapter mobile # validate + plan, touch nothing
|
|
81
|
-
|
|
82
|
-
# Daily loop (run from inside a checkout)
|
|
83
|
-
mm-harness launch ios # launch (mobile: ios | android)
|
|
84
|
-
mm-harness logs # tail Metro/webpack + app logs
|
|
85
|
-
mm-harness debug # open the debug console
|
|
86
|
-
mm-harness fixtures sync # sync fixtures; `fixtures set` seeds the wallet
|
|
87
|
-
|
|
88
|
-
# Keep the install current
|
|
89
|
-
mm-harness update # upgrade to the published latest (prints old → new)
|
|
90
|
-
mm-harness update --check --json # report only — { current, latest, updateAvailable }
|
|
59
|
+
mm-harness logs --source extension # Extension page
|
|
60
|
+
mm-harness logs --source dapp # active dapp
|
|
61
|
+
mm-harness logs --source webpack # compiler
|
|
62
|
+
mm-harness logs --source watcher # watcher lifecycle
|
|
63
|
+
mm-harness logs --source rebuild # incremental rebuilds
|
|
64
|
+
mm-harness logs --source app # Mobile app
|
|
65
|
+
mm-harness logs --source metro # Mobile bundler
|
|
91
66
|
```
|
|
92
67
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
An exit code or `--json` result is not sufficient for a UI/debug command. After
|
|
96
|
-
`launch --verify`, confirm the debugger is visibly connected to the current
|
|
97
|
-
slot's app/profile. Repeat this check on every slot/platform that will be used
|
|
98
|
-
before release.
|
|
99
|
-
|
|
100
|
-
A passive once-a-day nudge prints one `stderr` line when a newer version is
|
|
101
|
-
published (`mm-harness <cur> → <latest> available · run: mm-harness update`). It
|
|
102
|
-
never blocks and never auto-updates. Silence it with `MM_HARNESS_NO_UPDATE_CHECK=1`
|
|
103
|
-
(auto-off in CI).
|
|
104
|
-
|
|
105
|
-
Set `RECIPE_LOG_UI=compact|full|quiet` (default `compact`) and `RECIPE_LOG_EVENTS=10`
|
|
106
|
-
to tune the compact log view during launch and Metro/webpack startup.
|
|
107
|
-
|
|
108
|
-
## Layout
|
|
109
|
-
|
|
110
|
-
```text
|
|
111
|
-
bin/ the mm-harness entrypoint
|
|
112
|
-
src/ what EXECUTES recipes: TypeScript CLI/engine glue +
|
|
113
|
-
typed per-adapter logic (src/adapters/)
|
|
114
|
-
adapters/ what CONTROLS the app and its instances: launch/live/
|
|
115
|
-
watch/windows/wallet state/health/inject/cleanup per
|
|
116
|
-
platform, shared glue, porcelain, overlay payload +
|
|
117
|
-
manifest.json
|
|
118
|
-
library/ what recipes ARE: recipes/ (recipe JSONs), actions/
|
|
119
|
-
(per-platform implementations), manifests/ (capability)
|
|
120
|
-
scripts/ dev tooling (yarn check, local farmslot link, e2e
|
|
121
|
-
validation, adapter-surface-doctor)
|
|
122
|
-
docs/ details when this README is not enough
|
|
123
|
-
```
|
|
68
|
+
## Discover and prove
|
|
124
69
|
|
|
125
|
-
|
|
126
|
-
|
|
70
|
+
```bash
|
|
71
|
+
mm-harness actions positions
|
|
72
|
+
mm-harness actions --action metamask.wallet.ensure_unlocked
|
|
73
|
+
mm-harness call metamask.wallet.ensure_unlocked
|
|
74
|
+
mm-harness flows
|
|
75
|
+
mm-harness flows describe perps.clean_market_testnet
|
|
76
|
+
|
|
77
|
+
mm-harness run --list
|
|
78
|
+
mm-harness run wallet.smoke --describe
|
|
79
|
+
mm-harness run path/to/recipe.json --plan
|
|
80
|
+
mm-harness run path/to/recipe.json
|
|
81
|
+
```
|
|
127
82
|
|
|
128
|
-
|
|
83
|
+
`run` selects a checkout-local artifact directory unless
|
|
84
|
+
`--artifacts-dir <dir>` overrides it. Human output prints diagnostics and
|
|
85
|
+
absolute paths to the report, trace, executed recipe, and artifact manifest.
|
|
86
|
+
Use `mm-harness last --json` to resume without repeating the last operation.
|
|
129
87
|
|
|
130
|
-
|
|
88
|
+
For automation, `--json` emits one stable document. `run --json-stream` emits
|
|
89
|
+
line-buffered JSONL progress and a terminal event.
|
|
131
90
|
|
|
132
|
-
|
|
133
|
-
- `@farmslot/recipe-harness/runtime/log-analysis` — bundle-log boundaries, unresolved-module scoping
|
|
134
|
-
- `@farmslot/recipe-harness/runtime/metro-probe` — Metro `/status` reachability
|
|
91
|
+
## Team libraries
|
|
135
92
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
93
|
+
```bash
|
|
94
|
+
export RECIPE_LIBRARY_PATH="team=$HOME/shared-library/team-recipes"
|
|
95
|
+
mm-harness flows
|
|
96
|
+
mm-harness run --list
|
|
97
|
+
```
|
|
141
98
|
|
|
142
|
-
|
|
99
|
+
Shared libraries hold durable actions, flows, and recipes. Task acceptance
|
|
100
|
+
criteria remain task-local. See [Recipes](docs/RECIPES.md).
|
|
143
101
|
|
|
144
|
-
|
|
145
|
-
harness. To run a **dev** checkout with active pre-release changes alongside it,
|
|
146
|
-
set `MM_HARNESS_BIN` to that checkout's `bin/mm-harness`:
|
|
102
|
+
## Recover
|
|
147
103
|
|
|
148
104
|
```bash
|
|
149
|
-
|
|
150
|
-
|
|
105
|
+
mm-harness doctor
|
|
106
|
+
mm-harness doctor --fix
|
|
107
|
+
mm-harness verify
|
|
108
|
+
mm-harness cleanup
|
|
151
109
|
```
|
|
152
110
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
dev harness while the prod farm leaves it unset.
|
|
111
|
+
Failures name one exact next action. Core is headless; browser, device, logs,
|
|
112
|
+
and debugger capabilities are reported as unavailable instead of fabricated.
|
|
113
|
+
Stable exit codes are `1` runtime/action failure, `2` invalid CLI usage, `3`
|
|
114
|
+
infrastructure failure, `4` bounded recovery refusal, and `5` validation/trust
|
|
115
|
+
failure.
|
|
159
116
|
|
|
160
|
-
##
|
|
117
|
+
## Reference
|
|
161
118
|
|
|
162
|
-
-
|
|
163
|
-
|
|
164
|
-
-
|
|
165
|
-
|
|
166
|
-
- Extension does not patch product source; it drives `dist/chrome` through Chrome
|
|
167
|
-
CDP.
|
|
119
|
+
- [Recipes](docs/RECIPES.md) — discover, compose, author, and share proof.
|
|
120
|
+
- [Security](docs/SECURITY.md) — trust, approval, fixtures, and evidence safety.
|
|
121
|
+
- [QA](docs/QA.md) — clean-machine and human release checks.
|
|
122
|
+
- [Contributing](docs/CONTRIBUTING.md) — ownership, layout, and change gates.
|
|
168
123
|
|
|
169
|
-
|
|
124
|
+
For development, point the installed command at a source checkout:
|
|
170
125
|
|
|
171
126
|
```bash
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
node --check adapters/extension/inject.mjs adapters/extension/cleanup.mjs adapters/extension/readiness.mjs adapters/extension/launch-browser.cjs adapters/shared/open-debug.mjs adapters/shared/cli-home.mjs adapters/shared/log-tui.mjs adapters/shared/progress.mjs adapters/shared/recipe-paths.mjs scripts/check.mjs
|
|
127
|
+
export MM_HARNESS_BIN=/path/to/metamask-harness/bin/mm-harness
|
|
128
|
+
mm-harness --version
|
|
175
129
|
```
|
|
176
130
|
|
|
177
|
-
|
|
131
|
+
Unset `MM_HARNESS_BIN` to return to the published installation.
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// the tab shows live extension logs (the equivalent of the Metro/webpack tabs).
|
|
9
9
|
//
|
|
10
10
|
// Usage:
|
|
11
|
-
// console-tail.mjs --cdp-port <port> [--log <file>]
|
|
11
|
+
// console-tail.mjs --cdp-port <port> [--extension-log <file>] [--dapp-log <file>]
|
|
12
12
|
//
|
|
13
13
|
// Resilience: MV3 service workers cycle on idle and UI pages open/close, so we
|
|
14
14
|
// re-poll /json/list every ~2s and (re)attach to any extension target that is
|
|
@@ -19,9 +19,10 @@
|
|
|
19
19
|
'use strict';
|
|
20
20
|
|
|
21
21
|
import { appendFileSync } from 'node:fs';
|
|
22
|
+
import path from 'node:path';
|
|
22
23
|
|
|
23
24
|
function parseArgs(argv) {
|
|
24
|
-
const args = { cdpPort: null,
|
|
25
|
+
const args = { cdpPort: null, extensionLog: null, dappLog: null };
|
|
25
26
|
for (let i = 0; i < argv.length; i += 1) {
|
|
26
27
|
const arg = argv[i];
|
|
27
28
|
switch (arg) {
|
|
@@ -29,12 +30,16 @@ function parseArgs(argv) {
|
|
|
29
30
|
args.cdpPort = argv[++i];
|
|
30
31
|
break;
|
|
31
32
|
case '--log':
|
|
32
|
-
|
|
33
|
+
case '--extension-log':
|
|
34
|
+
args.extensionLog = argv[++i];
|
|
35
|
+
break;
|
|
36
|
+
case '--dapp-log':
|
|
37
|
+
args.dappLog = argv[++i];
|
|
33
38
|
break;
|
|
34
39
|
case '-h':
|
|
35
40
|
case '--help':
|
|
36
41
|
process.stdout.write(
|
|
37
|
-
'Usage: console-tail.mjs --cdp-port <port> [--log <file>]\n',
|
|
42
|
+
'Usage: console-tail.mjs --cdp-port <port> [--extension-log <file>] [--dapp-log <file>]\n',
|
|
38
43
|
);
|
|
39
44
|
process.exit(0);
|
|
40
45
|
break;
|
|
@@ -51,6 +56,9 @@ if (!Number.isInteger(cdpPort) || cdpPort <= 0) {
|
|
|
51
56
|
throw new Error('console-tail requires a numeric --cdp-port.');
|
|
52
57
|
}
|
|
53
58
|
const HOST = `127.0.0.1:${cdpPort}`;
|
|
59
|
+
if (args.extensionLog && !args.dappLog) {
|
|
60
|
+
args.dappLog = path.join(path.dirname(path.resolve(args.extensionLog)), 'dapp-console.log');
|
|
61
|
+
}
|
|
54
62
|
|
|
55
63
|
if (typeof WebSocket === 'undefined') {
|
|
56
64
|
throw new Error('console-tail requires Node 22+ with a global WebSocket.');
|
|
@@ -65,9 +73,13 @@ const cooldown = new Map();
|
|
|
65
73
|
// Writes formatted console-event strings (never raw HTTP response bodies) to
|
|
66
74
|
// stdout and optionally to a caller-supplied log file. The source is the local
|
|
67
75
|
// Chrome CDP WebSocket (127.0.0.1:<cdpPort>) — a trusted-local endpoint.
|
|
68
|
-
function out(line) {
|
|
76
|
+
function out(line, destination = null) {
|
|
69
77
|
process.stdout.write(`${line}\n`);
|
|
70
|
-
if (
|
|
78
|
+
if (destination) {
|
|
79
|
+
// This file is explicitly an untrusted application-console transcript; it is never executed or parsed as configuration.
|
|
80
|
+
// codeql[js/http-to-file-access]
|
|
81
|
+
appendFileSync(destination, `${line}\n`);
|
|
82
|
+
}
|
|
71
83
|
}
|
|
72
84
|
|
|
73
85
|
function stamp() {
|
|
@@ -95,6 +107,22 @@ function isExtensionTarget(target) {
|
|
|
95
107
|
return String(target.url || '').startsWith('chrome-extension://');
|
|
96
108
|
}
|
|
97
109
|
|
|
110
|
+
function isDappTarget(target) {
|
|
111
|
+
if (!target || !target.webSocketDebuggerUrl || target.type !== 'page') return false;
|
|
112
|
+
try {
|
|
113
|
+
const url = new URL(String(target?.url || ''));
|
|
114
|
+
return url.protocol === 'http:' || url.protocol === 'https:';
|
|
115
|
+
} catch {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function targetStream(target) {
|
|
121
|
+
if (isExtensionTarget(target)) return { kind: 'extension', log: args.extensionLog };
|
|
122
|
+
if (isDappTarget(target)) return { kind: 'dapp', log: args.dappLog };
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
|
|
98
126
|
function previewArg(arg) {
|
|
99
127
|
if (!arg || typeof arg !== 'object') return String(arg);
|
|
100
128
|
if ('value' in arg && arg.value !== undefined) {
|
|
@@ -108,29 +136,29 @@ function previewArg(arg) {
|
|
|
108
136
|
return arg.subtype || arg.type || '?';
|
|
109
137
|
}
|
|
110
138
|
|
|
111
|
-
function emitConsole(label, params) {
|
|
139
|
+
function emitConsole(label, params, destination) {
|
|
112
140
|
const level = String(params.type || 'log').toUpperCase();
|
|
113
141
|
const text = (params.args || []).map(previewArg).join(' ');
|
|
114
|
-
out(`${stamp()} [${label}] ${level} ${text}
|
|
142
|
+
out(`${stamp()} [${label}] ${level} ${text}`, destination);
|
|
115
143
|
}
|
|
116
144
|
|
|
117
|
-
function emitException(label, params) {
|
|
145
|
+
function emitException(label, params, destination) {
|
|
118
146
|
const d = params.exceptionDetails || {};
|
|
119
147
|
const text =
|
|
120
148
|
d.exception?.description ||
|
|
121
149
|
d.exception?.value ||
|
|
122
150
|
d.text ||
|
|
123
151
|
'uncaught exception';
|
|
124
|
-
out(`${stamp()} [${label}] EXCEPTION ${text}
|
|
152
|
+
out(`${stamp()} [${label}] EXCEPTION ${text}`, destination);
|
|
125
153
|
}
|
|
126
154
|
|
|
127
|
-
function emitLogEntry(label, params) {
|
|
155
|
+
function emitLogEntry(label, params, destination) {
|
|
128
156
|
const e = params.entry || {};
|
|
129
157
|
const level = String(e.level || 'log').toUpperCase();
|
|
130
|
-
out(`${stamp()} [${label}] ${level} ${e.text || ''}
|
|
158
|
+
out(`${stamp()} [${label}] ${level} ${e.text || ''}`, destination);
|
|
131
159
|
}
|
|
132
160
|
|
|
133
|
-
function handleMessage(label, data) {
|
|
161
|
+
function handleMessage(label, destination, data) {
|
|
134
162
|
let message;
|
|
135
163
|
try {
|
|
136
164
|
message = JSON.parse(data);
|
|
@@ -141,13 +169,13 @@ function handleMessage(label, data) {
|
|
|
141
169
|
if (message.id != null) return; // command ack, not an event
|
|
142
170
|
switch (message.method) {
|
|
143
171
|
case 'Runtime.consoleAPICalled':
|
|
144
|
-
emitConsole(label, message.params || {});
|
|
172
|
+
emitConsole(label, message.params || {}, destination);
|
|
145
173
|
break;
|
|
146
174
|
case 'Runtime.exceptionThrown':
|
|
147
|
-
emitException(label, message.params || {});
|
|
175
|
+
emitException(label, message.params || {}, destination);
|
|
148
176
|
break;
|
|
149
177
|
case 'Log.entryAdded':
|
|
150
|
-
emitLogEntry(label, message.params || {});
|
|
178
|
+
emitLogEntry(label, message.params || {}, destination);
|
|
151
179
|
break;
|
|
152
180
|
default:
|
|
153
181
|
break;
|
|
@@ -155,9 +183,11 @@ function handleMessage(label, data) {
|
|
|
155
183
|
}
|
|
156
184
|
|
|
157
185
|
function attach(target) {
|
|
158
|
-
const
|
|
186
|
+
const stream = targetStream(target);
|
|
187
|
+
if (!stream) return;
|
|
188
|
+
const label = `${stream.kind}:${shortLabel(target)}`;
|
|
159
189
|
const ws = new WebSocket(target.webSocketDebuggerUrl);
|
|
160
|
-
const entry = { ws, label, opened: false };
|
|
190
|
+
const entry = { ws, label, destination: stream.log, opened: false };
|
|
161
191
|
attached.set(target.id, entry);
|
|
162
192
|
|
|
163
193
|
ws.addEventListener('open', () => {
|
|
@@ -166,16 +196,16 @@ function attach(target) {
|
|
|
166
196
|
for (const method of ['Runtime.enable', 'Log.enable']) {
|
|
167
197
|
ws.send(JSON.stringify({ id: ++id, method, params: {} }));
|
|
168
198
|
}
|
|
169
|
-
out(`${stamp()} [${label}] [attached]
|
|
199
|
+
out(`${stamp()} [${label}] [attached]`, stream.log);
|
|
170
200
|
});
|
|
171
201
|
ws.addEventListener('message', (event) => {
|
|
172
|
-
handleMessage(label, typeof event.data === 'string' ? event.data : '');
|
|
202
|
+
handleMessage(label, stream.log, typeof event.data === 'string' ? event.data : '');
|
|
173
203
|
});
|
|
174
204
|
const drop = () => {
|
|
175
205
|
if (attached.get(target.id) !== entry) return;
|
|
176
206
|
attached.delete(target.id);
|
|
177
207
|
if (entry.opened) {
|
|
178
|
-
out(`${stamp()} [${label}] [reconnect] target detached; will re-attach
|
|
208
|
+
out(`${stamp()} [${label}] [reconnect] target detached; will re-attach`, stream.log);
|
|
179
209
|
} else {
|
|
180
210
|
// Never connected (target gone / not debuggable yet): back off so a dead
|
|
181
211
|
// target is not retried — and logged — on every 2s poll.
|
|
@@ -196,8 +226,9 @@ async function poll() {
|
|
|
196
226
|
// CDP not reachable this tick (browser starting/restarting); retry next poll.
|
|
197
227
|
return;
|
|
198
228
|
}
|
|
199
|
-
|
|
200
|
-
|
|
229
|
+
const targetList = Array.isArray(targets) ? targets : [];
|
|
230
|
+
for (const target of targetList) {
|
|
231
|
+
if (!targetStream(target)) continue;
|
|
201
232
|
if (attached.has(target.id)) continue;
|
|
202
233
|
const until = cooldown.get(target.id);
|
|
203
234
|
if (until !== undefined) {
|
|
@@ -226,6 +257,6 @@ function shutdown() {
|
|
|
226
257
|
process.on('SIGINT', shutdown);
|
|
227
258
|
process.on('SIGTERM', shutdown);
|
|
228
259
|
|
|
229
|
-
out(`${stamp()} [console-tail] streaming extension
|
|
260
|
+
out(`${stamp()} [console-tail] streaming extension and dapp consoles via cdp ${cdpPort}`);
|
|
230
261
|
await poll();
|
|
231
262
|
setInterval(poll, 2000);
|
|
@@ -277,7 +277,8 @@ const fs = require('fs');
|
|
|
277
277
|
const http = require('http');
|
|
278
278
|
const { execSync, execFileSync } = require('child_process');
|
|
279
279
|
let chromium; try { chromium = require('@playwright/test').chromium; } catch { chromium = require('playwright').chromium; }
|
|
280
|
-
const { remoteDebuggingArgs } = require(path.join('${SCRIPT_DIR}', 'lib/chrome-args.cjs'));
|
|
280
|
+
const { isolatedProfileArgs, remoteDebuggingArgs } = require(path.join('${SCRIPT_DIR}', 'lib/chrome-args.cjs'));
|
|
281
|
+
const { captureMacFrontmostProcess, restoreMacFrontmostProcess } = require(path.join('${SCRIPT_DIR}', 'lib/macos-focus.cjs'));
|
|
281
282
|
|
|
282
283
|
const SLOT_ID = '${SLOT_ID}';
|
|
283
284
|
const AGENT_DIR = '${AGENT_DIR}';
|
|
@@ -308,6 +309,7 @@ const resumeWebpack = () => {
|
|
|
308
309
|
'--disable-renderer-backgrounding',
|
|
309
310
|
'--no-first-run',
|
|
310
311
|
'--no-default-browser-check',
|
|
312
|
+
...isolatedProfileArgs(),
|
|
311
313
|
'--window-size=420,800',
|
|
312
314
|
];
|
|
313
315
|
// Debug-port trio (address + port + scoped allow-origins) from the shared module
|
|
@@ -318,6 +320,7 @@ const resumeWebpack = () => {
|
|
|
318
320
|
}
|
|
319
321
|
|
|
320
322
|
const chromiumApp = path.dirname(path.dirname(path.dirname(chromium.executablePath())));
|
|
323
|
+
const previousFrontmostPid = captureMacFrontmostProcess();
|
|
321
324
|
execFileSync('open', ['-g', '-n', '-a', chromiumApp, '--args', ...args], { stdio: 'ignore' });
|
|
322
325
|
for (let i = 0; i < 60; i++) {
|
|
323
326
|
try {
|
|
@@ -420,6 +423,8 @@ const resumeWebpack = () => {
|
|
|
420
423
|
}
|
|
421
424
|
if (process.env.MM_HARNESS_FOCUS_BROWSER === '1') {
|
|
422
425
|
await page.bringToFront().catch(() => {});
|
|
426
|
+
} else {
|
|
427
|
+
restoreMacFrontmostProcess(previousFrontmostPid);
|
|
423
428
|
}
|
|
424
429
|
|
|
425
430
|
fs.writeFileSync(path.join(AGENT_DIR, 'extension.id'), extId);
|
|
@@ -95,16 +95,16 @@ copyDir(path.join(runnerDir, 'library/recipes'), path.join(harnessDir, 'runner/r
|
|
|
95
95
|
copyFile(path.join(runnerDir, 'library/library.json'), path.join(harnessDir, 'runner/library.json'));
|
|
96
96
|
fs.mkdirSync(path.join(harnessDir, 'runner/flows'), { recursive: true });
|
|
97
97
|
copyDir(path.join(runnerDir, 'library/flows'), path.join(harnessDir, 'runner/flows'));
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
// consumers are unaffected by runner-repo layout changes.
|
|
98
|
+
// The installed overlay has a flat scripts/ runtime API independent of the
|
|
99
|
+
// package's source layout.
|
|
101
100
|
copyFile(path.join(runnerDir, 'adapters/extension/launch-browser.cjs'), path.join(harnessDir, 'scripts/launch-browser.cjs'));
|
|
102
|
-
// installed back-compat alias: callers of the pre-rename installed name
|
|
103
|
-
copyFile(path.join(runnerDir, 'adapters/extension/launch-browser.cjs'), path.join(harnessDir, 'scripts/launch-chrome-detached.cjs'));
|
|
104
101
|
copyFile(path.join(runnerDir, 'adapters/extension/console-tail.mjs'), path.join(harnessDir, 'scripts/console-tail.mjs'));
|
|
105
102
|
copyFile(path.join(runnerDir, 'adapters/extension/launch.sh'), path.join(harnessDir, 'scripts/launch.sh'));
|
|
106
103
|
copyFile(path.join(runnerDir, 'adapters/extension/live.sh'), path.join(harnessDir, 'scripts/live.sh'));
|
|
107
104
|
copyFile(path.join(runnerDir, 'adapters/extension/start-watch.sh'), path.join(harnessDir, 'scripts/start-watch.sh'));
|
|
105
|
+
copyFile(path.join(runnerDir, 'adapters/extension/launch-webpack.cjs'), path.join(harnessDir, 'scripts/launch-webpack.cjs'));
|
|
106
|
+
copyFile(path.join(runnerDir, 'adapters/extension/sync-webpack-dist.cjs'), path.join(harnessDir, 'scripts/sync-webpack-dist.cjs'));
|
|
107
|
+
copyFile(path.join(runnerDir, 'adapters/extension/stamp-runtime-title.cjs'), path.join(harnessDir, 'scripts/stamp-runtime-title.cjs'));
|
|
108
108
|
copyFile(path.join(runnerDir, 'adapters/extension/stop-viewers.sh'), path.join(harnessDir, 'scripts/stop-viewers.sh'));
|
|
109
109
|
copyFile(path.join(runnerDir, 'adapters/extension/snapshot-dist.sh'), path.join(harnessDir, 'scripts/snapshot-dist.sh'));
|
|
110
110
|
copyFile(path.join(runnerDir, 'adapters/extension/pin-remote-flags.cjs'), path.join(harnessDir, 'scripts/pin-remote-flags.cjs'));
|
|
@@ -121,10 +121,6 @@ copyFile(path.join(runnerDir, 'adapters/shared/activate-repo-node.sh'), path.joi
|
|
|
121
121
|
copyFile(path.join(runnerDir, 'adapters/shared/resolve-slot-ports.sh'), path.join(harnessDir, 'scripts/lib/resolve-slot-ports.sh'));
|
|
122
122
|
copyFile(path.join(runnerDir, 'adapters/shared/resolve-slot-ports.mjs'), path.join(harnessDir, 'scripts/lib/resolve-slot-ports.mjs'));
|
|
123
123
|
copyFile(path.join(runnerDir, 'adapters/shared/resolve-slot-ports-core.mjs'), path.join(harnessDir, 'scripts/lib/resolve-slot-ports-core.mjs'));
|
|
124
|
-
// Back-compat shims (one release) so an overlay consumer using the old leaf names still resolves.
|
|
125
|
-
copyFile(path.join(runnerDir, 'adapters/shared/resolve-farmslot-ports.sh'), path.join(harnessDir, 'scripts/lib/resolve-farmslot-ports.sh'));
|
|
126
|
-
copyFile(path.join(runnerDir, 'adapters/shared/resolve-farmslot-ports.mjs'), path.join(harnessDir, 'scripts/lib/resolve-farmslot-ports.mjs'));
|
|
127
|
-
copyFile(path.join(runnerDir, 'adapters/shared/resolve-farmslot-ports-core.mjs'), path.join(harnessDir, 'scripts/lib/resolve-farmslot-ports-core.mjs'));
|
|
128
124
|
copyFile(path.join(runnerDir, 'adapters/shared/path-defaults.json'), path.join(harnessDir, 'scripts/lib/path-defaults.json'));
|
|
129
125
|
copyFile(path.join(runnerDir, 'adapters/shared/json-field.sh'), path.join(harnessDir, 'scripts/lib/json-field.sh'));
|
|
130
126
|
copyFile(path.join(runnerDir, 'adapters/shared/tmux-session.sh'), path.join(harnessDir, 'scripts/lib/tmux-session.sh'));
|
|
@@ -133,6 +129,7 @@ copyFile(path.join(runnerDir, 'adapters/shared/log-tui.mjs'), path.join(harnessD
|
|
|
133
129
|
copyFile(path.join(runnerDir, 'adapters/extension/lib/extension-id.cjs'), path.join(harnessDir, 'scripts/lib/extension-id.cjs'));
|
|
134
130
|
copyFile(path.join(runnerDir, 'adapters/extension/lib/slot-title.cjs'), path.join(harnessDir, 'scripts/lib/slot-title.cjs'));
|
|
135
131
|
copyFile(path.join(runnerDir, 'adapters/extension/lib/chrome-args.cjs'), path.join(harnessDir, 'scripts/lib/chrome-args.cjs'));
|
|
132
|
+
copyFile(path.join(runnerDir, 'adapters/extension/lib/macos-focus.cjs'), path.join(harnessDir, 'scripts/lib/macos-focus.cjs'));
|
|
136
133
|
makeExecutableTree(path.join(harnessDir, 'scripts'));
|
|
137
134
|
fs.writeFileSync(path.join(harnessDir, 'installed-scripts.sha256'), `${dirContentHash(path.join(harnessDir, 'scripts'))}\n`);
|
|
138
135
|
|
|
@@ -24,7 +24,8 @@ const os = require('node:os');
|
|
|
24
24
|
const path = require('node:path');
|
|
25
25
|
const { execFileSync, spawn, spawnSync } = require('node:child_process');
|
|
26
26
|
const { extensionIdFromExtensionDir } = require('./lib/extension-id.cjs');
|
|
27
|
-
const { remoteDebuggingArgs } = require('./lib/chrome-args.cjs');
|
|
27
|
+
const { isolatedProfileArgs, remoteDebuggingArgs } = require('./lib/chrome-args.cjs');
|
|
28
|
+
const { captureMacFrontmostProcess, restoreMacFrontmostProcess } = require('./lib/macos-focus.cjs');
|
|
28
29
|
|
|
29
30
|
if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
|
30
31
|
console.log(
|
|
@@ -98,6 +99,7 @@ const chromeArgs = [
|
|
|
98
99
|
...remoteDebuggingArgs(cdpPort),
|
|
99
100
|
'--no-first-run',
|
|
100
101
|
'--disable-first-run-ui',
|
|
102
|
+
...isolatedProfileArgs(),
|
|
101
103
|
'--disable-default-apps',
|
|
102
104
|
'--disable-popup-blocking',
|
|
103
105
|
'--disable-extensions-file-access-check',
|
|
@@ -111,6 +113,7 @@ const chromeArgs = [
|
|
|
111
113
|
initialUrl,
|
|
112
114
|
];
|
|
113
115
|
const logFd = fs.openSync(args['chrome-log'], 'a');
|
|
116
|
+
const previousFrontmostPid = captureMacFrontmostProcess();
|
|
114
117
|
let browserPid;
|
|
115
118
|
try {
|
|
116
119
|
const application = process.platform === 'darwin' ? macApplicationForExecutable(args['chrome-bin']) : null;
|
|
@@ -136,6 +139,9 @@ try {
|
|
|
136
139
|
fs.closeSync(logFd);
|
|
137
140
|
}
|
|
138
141
|
fs.writeFileSync(args['chrome-pid'], `${browserPid}\n`);
|
|
142
|
+
if (process.env.MM_HARNESS_FOCUS_BROWSER !== '1') {
|
|
143
|
+
restoreMacFrontmostProcess(previousFrontmostPid);
|
|
144
|
+
}
|
|
139
145
|
|
|
140
146
|
function sanitizedChildEnv() {
|
|
141
147
|
return {
|