@deeeed/metamask-harness 0.17.0 → 0.17.2
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 +34 -0
- package/README.md +90 -140
- package/adapters/core/cleanup.sh +0 -0
- package/adapters/core/inject.sh +0 -0
- package/adapters/extension/cleanup.mjs +0 -0
- package/adapters/extension/console-tail.mjs +51 -24
- package/adapters/extension/ensure-browser.sh +0 -0
- package/adapters/extension/inject.mjs +5 -9
- package/adapters/extension/launch-browser.cjs +0 -0
- package/adapters/extension/launch-webpack.cjs +83 -0
- package/adapters/extension/launch.sh +8 -11
- package/adapters/extension/live.sh +2 -0
- package/adapters/extension/readiness.mjs +0 -0
- package/adapters/extension/reattach.sh +1 -0
- package/adapters/extension/refresh-build.sh +0 -0
- package/adapters/extension/seed-fixture.sh +4 -12
- package/adapters/extension/sidepanel-toggle.sh +4 -1
- package/adapters/extension/snapshot-dist.sh +0 -0
- package/adapters/extension/stamp-runtime-title.cjs +49 -0
- package/adapters/extension/start-watch.sh +16 -4
- package/adapters/extension/stop-viewers.sh +1 -1
- package/adapters/extension/sync-webpack-dist.cjs +105 -0
- package/adapters/extension/verify.sh +1 -1
- package/adapters/extension/wallet-fixture-state.cjs +0 -0
- package/adapters/manifest.json +46 -38
- package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +0 -0
- package/adapters/mobile/bridge-runtime/console-forwarder.cjs +1 -1
- package/adapters/mobile/bridge-runtime/lib/target-discovery.cjs +2 -3
- package/adapters/mobile/bridge-runtime/setup-wallet.sh +0 -0
- package/adapters/mobile/cleanup.sh +16 -176
- package/adapters/mobile/inject.sh +88 -671
- package/adapters/mobile/launch-console-forwarder.cjs +39 -0
- package/adapters/mobile/lib/metro-listener.sh +0 -0
- package/adapters/mobile/lib/tmux-viewer.sh +0 -0
- package/adapters/mobile/open-device.sh +0 -0
- package/adapters/mobile/prewarm-bundle.sh +0 -0
- package/adapters/mobile/start-console-forwarder.sh +70 -0
- package/adapters/mobile/start-metro.sh +0 -41
- package/adapters/mobile/stop-metro.sh +0 -0
- package/adapters/mobile/verify.sh +4 -61
- package/adapters/mobile/wait-for-bridge.sh +0 -0
- package/adapters/mobile/yarn-setup.sh +0 -0
- package/adapters/shared/activate-repo-node.sh +0 -0
- package/adapters/shared/activate-repo-ruby.sh +0 -0
- package/adapters/shared/cli-ux.sh +0 -0
- package/adapters/shared/ensure-runner-deps.sh +0 -0
- package/adapters/shared/harness-path.sh +0 -0
- package/adapters/shared/hash-helpers.sh +0 -0
- package/adapters/shared/install-repo-deps.sh +0 -0
- package/adapters/shared/json-field.sh +0 -0
- package/adapters/shared/open-debug.mjs +90 -36
- package/adapters/shared/open-log-window.sh +0 -0
- package/adapters/shared/reap-checkout-metros.sh +0 -0
- package/adapters/shared/resolve-slot-ports-core.mjs +0 -6
- package/adapters/shared/resolve-slot-ports.mjs +0 -0
- package/adapters/shared/resolve-slot-ports.sh +0 -5
- package/adapters/shared/sync-wallet-fixture.sh +0 -0
- package/adapters/shared/tmux-session.sh +0 -0
- package/adapters/shared/tmux-viewer.sh +0 -0
- package/adapters/shared/update-check-worker.mjs +37 -0
- package/dist/adapters/extension/console-capture.js +84 -0
- package/dist/adapters/extension/runtime-decision.js +14 -2
- package/dist/adapters/extension/surface.js +3 -2
- package/dist/adapters/mobile/prepare.js +22 -7
- package/dist/adapters/mobile/surface.js +1 -1
- package/dist/adapters/resolve-slot-ports.js +0 -2
- package/dist/adapters/slot-ports.js +8 -0
- package/dist/cli.js +3 -11
- package/dist/commands/call.js +9 -4
- package/dist/commands/debug.js +38 -31
- package/dist/commands/fixtures.js +18 -10
- package/dist/commands/launch/index.js +10 -0
- package/dist/commands/logs.js +8 -6
- package/dist/commands/manifest.js +5 -17
- package/dist/commands/run-engine.js +1 -32
- package/dist/commands/run-report.js +1 -1
- package/dist/commands/run.js +50 -3
- package/dist/commands/status-probe.js +1 -1
- package/dist/commands/update.js +15 -28
- package/dist/doctor.js +1 -4
- package/dist/harness.js +5 -123
- package/dist/live-adapter-contract.js +6 -20
- package/dist/mm-harness-cli.js +6 -43
- package/dist/paths.js +2 -5
- package/dist/run-diagnostics.js +35 -40
- package/dist/runtime-context.js +26 -8
- package/docs/CHEATSHEET.md +39 -52
- package/docs/CLI-ERGONOMICS-HUMAN-QA.md +48 -104
- package/docs/CODE-MAP.md +1 -1
- package/docs/VIDEO-DEMO-VALIDATION.md +62 -0
- package/docs/architecture.md +65 -375
- package/docs/live-adapter-contract.md +3 -1
- package/library/actions/core/perps/_controller.mjs +10 -55
- package/library/manifests/mobile.action-manifest.json +1 -1
- package/package.json +2 -2
- package/scripts/completions.sh +0 -0
- package/scripts/install-completions.sh +0 -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/CLI-SPEC.md +0 -1142
- package/docs/MENTAL-MODEL.md +0 -295
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,40 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.17.2 - 2026-07-15
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Passive update discovery now runs in a detached, once-daily registry probe, so fast commands populate the cache without adding latency or contaminating machine output.
|
|
10
|
+
|
|
11
|
+
## 0.17.1 - 2026-07-15
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- Extension `debug` now uses Chrome's native DevTools command for the active slot target and opens directly on Console; `debug --worker` targets the service worker without creating a frontend browser tab.
|
|
16
|
+
- Extension webpack watchers now launch in their own process group, so they remain alive for incremental rebuilds after the initial `launch --build` command exits.
|
|
17
|
+
- Successful Extension watch rebuilds now update the isolated loaded runtime in place, so a normal refresh of the current fullscreen or sidepanel UI shows source edits without another harness build or relaunch.
|
|
18
|
+
- Extension slot titles are stamped into the isolated runtime after snapshots and successful rebuilds, preserving the slot identifier across an operator's normal browser refresh without changing product source.
|
|
19
|
+
- Extension sidepanel opening no longer waits for the harness-owned user-gesture button to pass visual actionability checks; occluded wallet loading surfaces cannot block the trusted click.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- Current documentation now teaches the auto-detected workflow and verified human outcomes only: launch, incremental refresh, target-specific debugging/logs, fixture setup, capability discovery, and proof evidence.
|
|
24
|
+
- Mobile fixture setup now requires the current multichain account-tree path, and Core signing requires the canonical checkout wallet fixture.
|
|
25
|
+
|
|
26
|
+
### Removed
|
|
27
|
+
|
|
28
|
+
- Removed compatibility-only runtime-start fields, hidden `live` execution, removed-command handlers, undomained recipe aliases, flat/non-ESM action lookup, old port-resolver filenames, duplicate Extension launcher naming, obsolete fixture search locations, and the alternate Mobile product-source bridge. Mobile install now requires the current product-owned bridge and writes metadata only.
|
|
29
|
+
- Removed the historical CLI migration/spec documents; current behavior is documented in the README, cheatsheet, architecture, and human QA checklist.
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
|
|
33
|
+
- Human recipe results now print an absolute-path artifact inventory and expand run-scoped application diagnostics inline; single-action calls show the same `CLEAN`/`REVIEW` diagnostics instead of requiring another query.
|
|
34
|
+
- Extension console collection now starts after every successful public launch path, including quick reattach, and persists as one checkout-scoped process until `stop`. MetaMask UI/service-worker events and dapp-page events are routed to separate `extension-console.log` and `dapp-console.log` files; bare `logs` defaults to MetaMask while `--source dapp` and `--source webpack` remain explicit.
|
|
35
|
+
- Mobile CDP-forwarded app events now write to `app-console.log`, so `logs --source app` and run diagnostics are no longer mixed with Metro bundler output; warm and cold launches start a process-group-detached collector only after bridge readiness so capture remains live after the launch command exits.
|
|
36
|
+
- Extension `debug` now opens Chrome's local DevTools frontend inside the exact slot-owned profile, targets the MetaMask extension page rather than the dapp, and verifies the debugging connection before reporting success.
|
|
37
|
+
- Mobile fixture application now fails immediately when the checkout fixture is absent, rejects the misplaced `fixtures set --dev` flag with the correct initialization command, and never prints a success message after failure.
|
|
38
|
+
|
|
5
39
|
## 0.17.0 - 2026-07-14
|
|
6
40
|
|
|
7
41
|
### Added
|
package/README.md
CHANGED
|
@@ -1,181 +1,131 @@
|
|
|
1
|
-
#
|
|
1
|
+
# mm-harness
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`mm-harness` gives people and agents one CLI for operating MetaMask Mobile,
|
|
4
|
+
Extension, and Core. Run it inside a checkout; the product is auto-detected.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
understand.** It is the MetaMask recipe harness: one front door to launch the app,
|
|
7
|
-
prove behavior with recipes, and manage the per-checkout runtime overlay. It is a
|
|
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.
|
|
6
|
+
It has two jobs:
|
|
14
7
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
PROVE run · doctor run recipes, check readiness
|
|
19
|
-
RUNTIME OVERLAY install · verify · cleanup manage the per-checkout runtime overlay
|
|
20
|
-
```
|
|
8
|
+
- operate the app with the same controls a developer needs: launch, logs, debug,
|
|
9
|
+
fixtures, actions, and reusable flows;
|
|
10
|
+
- execute deterministic recipes and save reviewable proof.
|
|
21
11
|
|
|
22
|
-
|
|
23
|
-
|
|
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.
|
|
12
|
+
The generic graph engine lives in `@farmslot/recipe-harness`. This package owns
|
|
13
|
+
only MetaMask-specific actions and runtime control.
|
|
27
14
|
|
|
28
|
-
|
|
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.
|
|
32
|
-
Release candidates have a short [human QA checklist](docs/CLI-ERGONOMICS-HUMAN-QA.md).
|
|
15
|
+
## Install
|
|
33
16
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
-
|
|
37
|
-
-
|
|
17
|
+
```bash
|
|
18
|
+
npm install -g @deeeed/metamask-harness@latest
|
|
19
|
+
mm-harness --version
|
|
20
|
+
mm-harness doctor
|
|
21
|
+
```
|
|
38
22
|
|
|
39
|
-
|
|
40
|
-
logic.
|
|
23
|
+
Use `mm-harness update` to install the latest published version.
|
|
41
24
|
|
|
42
|
-
##
|
|
25
|
+
## Daily workflow
|
|
43
26
|
|
|
44
|
-
|
|
27
|
+
From a MetaMask checkout:
|
|
45
28
|
|
|
46
29
|
```bash
|
|
47
|
-
#
|
|
48
|
-
mm-harness launch
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
30
|
+
# Extension
|
|
31
|
+
mm-harness launch # starts/reuses Chrome and the incremental watcher
|
|
32
|
+
mm-harness launch --sidepanel # open the sidepanel surface
|
|
33
|
+
|
|
34
|
+
# Mobile
|
|
35
|
+
mm-harness launch ios
|
|
36
|
+
mm-harness launch android
|
|
37
|
+
|
|
38
|
+
# Any product
|
|
39
|
+
mm-harness status
|
|
40
|
+
mm-harness logs
|
|
41
|
+
mm-harness debug
|
|
42
|
+
mm-harness fixtures set
|
|
52
43
|
```
|
|
53
44
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
adjacent bugs without claiming the recipe or current change caused them. Core
|
|
59
|
-
is headless, so this is N/A there.
|
|
45
|
+
`launch` installs the checkout-local runtime overlay when needed. On Extension,
|
|
46
|
+
the normal launch keeps `yarn start` running after the command exits. Source
|
|
47
|
+
edits compile incrementally into the isolated runtime; refresh the active page or
|
|
48
|
+
sidepanel to see them. Use `launch --build` only when a clean rebuild is needed.
|
|
60
49
|
|
|
61
|
-
|
|
62
|
-
|
|
50
|
+
`debug` opens DevTools for the active Extension target in that checkout's Chrome
|
|
51
|
+
profile. Mobile opens the React Native developer surface.
|
|
63
52
|
|
|
64
|
-
|
|
53
|
+
Filter logs when needed:
|
|
65
54
|
|
|
66
|
-
```
|
|
67
|
-
mm-harness
|
|
68
|
-
mm-harness
|
|
55
|
+
```bash
|
|
56
|
+
mm-harness logs --source extension # Extension console
|
|
57
|
+
mm-harness logs --source dapp # active dapp console
|
|
58
|
+
mm-harness logs --source webpack # Extension compiler
|
|
59
|
+
mm-harness logs --source app # Mobile app events
|
|
60
|
+
mm-harness logs --source metro # Mobile bundler
|
|
69
61
|
```
|
|
70
62
|
|
|
71
|
-
|
|
72
|
-
manifests — the content). src/ = what EXECUTES them (TypeScript CLI/engine glue,
|
|
73
|
-
typed adapters). adapters/ = what CONTROLS the app and its instances (start,
|
|
74
|
-
windows, wallet state, health, ports, parallel).
|
|
75
|
-
|
|
76
|
-
## Useful commands
|
|
63
|
+
## Build and run proof
|
|
77
64
|
|
|
78
65
|
```bash
|
|
79
|
-
|
|
80
|
-
mm-harness actions
|
|
81
|
-
mm-harness
|
|
82
|
-
mm-harness
|
|
83
|
-
mm-harness call unlock --adapter extension # run one action via the real engine path
|
|
84
|
-
mm-harness last --json # resume from the last significant command
|
|
85
|
-
mm-harness flows --json # reusable library flows, with provenance
|
|
86
|
-
|
|
87
|
-
# Readiness + proof
|
|
88
|
-
mm-harness doctor # read-only readiness check (no launch)
|
|
89
|
-
mm-harness doctor --fix # heal the overlay without launching
|
|
90
|
-
mm-harness run recipe.json --plan --adapter mobile # validate + plan, touch nothing
|
|
91
|
-
mm-harness run recipe.json --json-stream # line-flushed JSONL progress for agents
|
|
92
|
-
|
|
93
|
-
# Daily loop (run from inside a checkout)
|
|
94
|
-
mm-harness launch ios # launch (mobile: ios | android)
|
|
95
|
-
mm-harness logs # tail Metro/webpack + app logs
|
|
96
|
-
mm-harness debug # open the debug console
|
|
97
|
-
mm-harness fixtures sync # sync fixtures; `fixtures set` seeds the wallet
|
|
98
|
-
|
|
99
|
-
# Keep the install current
|
|
100
|
-
mm-harness update # upgrade to the published latest (prints old → new)
|
|
101
|
-
mm-harness update --check --json # report only — { current, latest, updateAvailable }
|
|
102
|
-
```
|
|
66
|
+
mm-harness actions positions
|
|
67
|
+
mm-harness actions --action metamask.wallet.ensure_unlocked
|
|
68
|
+
mm-harness call metamask.wallet.ensure_unlocked
|
|
69
|
+
mm-harness flows
|
|
103
70
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
never blocks and never auto-updates. Silence it with `MM_HARNESS_NO_UPDATE_CHECK=1`
|
|
107
|
-
(auto-off in CI).
|
|
108
|
-
|
|
109
|
-
Set `RECIPE_LOG_UI=compact|full|quiet` (default `compact`) and `RECIPE_LOG_EVENTS=10`
|
|
110
|
-
to tune the compact log view during launch and Metro/webpack startup.
|
|
111
|
-
|
|
112
|
-
## Layout
|
|
113
|
-
|
|
114
|
-
```text
|
|
115
|
-
bin/ the mm-harness entrypoint
|
|
116
|
-
src/ what EXECUTES recipes: TypeScript CLI/engine glue +
|
|
117
|
-
typed per-adapter logic (src/adapters/)
|
|
118
|
-
adapters/ what CONTROLS the app and its instances: launch/live/
|
|
119
|
-
watch/windows/wallet state/health/inject/cleanup per
|
|
120
|
-
platform, shared glue, porcelain, overlay payload +
|
|
121
|
-
manifest.json
|
|
122
|
-
library/ what recipes ARE: recipes/ (recipe JSONs), actions/
|
|
123
|
-
(per-platform implementations), manifests/ (capability)
|
|
124
|
-
scripts/ dev tooling (yarn check, local farmslot link, e2e
|
|
125
|
-
validation, adapter-surface-doctor)
|
|
126
|
-
docs/ details when this README is not enough
|
|
71
|
+
mm-harness run path/to/recipe.json --plan
|
|
72
|
+
mm-harness run path/to/recipe.json --artifacts-dir temp/recipe-proof
|
|
127
73
|
```
|
|
128
74
|
|
|
129
|
-
|
|
130
|
-
|
|
75
|
+
`actions` is the executable vocabulary. `call` exercises one action. `flows`
|
|
76
|
+
lists reusable parameterized behavior from personal, team, and bundled recipe
|
|
77
|
+
libraries. A recipe connects those capabilities into a deterministic proof.
|
|
78
|
+
|
|
79
|
+
A run prints its findings, diagnostics, and absolute evidence paths. The artifact
|
|
80
|
+
directory contains the executed recipe, summary, trace, diagnostics when
|
|
81
|
+
applicable, screenshots/logs, and `artifact-manifest.json`. Core is headless, so
|
|
82
|
+
browser/device diagnostics do not apply.
|
|
131
83
|
|
|
132
|
-
|
|
84
|
+
Use `mm-harness last --json` to resume after interruption without repeating a
|
|
85
|
+
side-effecting command.
|
|
133
86
|
|
|
134
|
-
|
|
87
|
+
## Team recipe libraries
|
|
135
88
|
|
|
136
|
-
|
|
137
|
-
- `@farmslot/recipe-harness/runtime/log-analysis` — bundle-log boundaries, unresolved-module scoping
|
|
138
|
-
- `@farmslot/recipe-harness/runtime/metro-probe` — Metro `/status` reachability
|
|
89
|
+
Add a named shared directory with `RECIPE_LIBRARY_PATH`:
|
|
139
90
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
`@farmslot/recipe-harness` before bumping the dependency here.
|
|
91
|
+
```bash
|
|
92
|
+
export RECIPE_LIBRARY_PATH="team=$HOME/shared-library/team-recipes"
|
|
93
|
+
mm-harness flows
|
|
94
|
+
```
|
|
145
95
|
|
|
146
|
-
|
|
96
|
+
Team flows can wrap repeated product behavior while each checklist or task keeps
|
|
97
|
+
its own acceptance criteria. See [recipe libraries](docs/recipe-libraries.md).
|
|
147
98
|
|
|
148
|
-
|
|
149
|
-
harness. To run a **dev** checkout with active pre-release changes alongside it,
|
|
150
|
-
set `MM_HARNESS_BIN` to that checkout's `bin/mm-harness`:
|
|
99
|
+
## Health and recovery
|
|
151
100
|
|
|
152
101
|
```bash
|
|
153
|
-
|
|
154
|
-
|
|
102
|
+
mm-harness doctor # inspect; does not launch
|
|
103
|
+
mm-harness doctor --fix # repair local harness setup; does not launch
|
|
104
|
+
mm-harness verify # verify the installed runtime overlay
|
|
105
|
+
mm-harness cleanup # remove the runtime overlay
|
|
155
106
|
```
|
|
156
107
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
no `PATH` collision — dev is reached only through `MM_HARNESS_BIN` (or a shell
|
|
161
|
-
alias). A per-farm environment can export it so the dev farm dispatches through the
|
|
162
|
-
dev harness while the prod farm leaves it unset.
|
|
108
|
+
Human output gives a next command. `--json` gives stable machine-readable errors
|
|
109
|
+
and keeps stdout free of decoration. `--json-stream` emits line-delimited progress
|
|
110
|
+
for long recipe runs.
|
|
163
111
|
|
|
164
|
-
##
|
|
112
|
+
## Development
|
|
113
|
+
|
|
114
|
+
Use a source checkout without replacing the global install:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
export MM_HARNESS_BIN=/path/to/metamask-harness/bin/mm-harness
|
|
118
|
+
mm-harness --version
|
|
119
|
+
```
|
|
165
120
|
|
|
166
|
-
|
|
167
|
-
falls back to detached `nohup` where possible.
|
|
168
|
-
- Mobile may inject a local development bridge/HUD into older checkouts. Do not
|
|
169
|
-
commit those product patches.
|
|
170
|
-
- Extension does not patch product source; it drives `dist/chrome` through Chrome
|
|
171
|
-
CDP.
|
|
121
|
+
Unset `MM_HARNESS_BIN` to return to the published install.
|
|
172
122
|
|
|
173
|
-
|
|
123
|
+
Validate changes with:
|
|
174
124
|
|
|
175
125
|
```bash
|
|
176
126
|
yarn check
|
|
177
|
-
bash -n bin/mm-harness adapters/porcelain/{metamask-recipe,mm-recipe,mme-recipe} adapters/{mobile,extension,core}/*.sh
|
|
178
|
-
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
|
|
179
127
|
```
|
|
180
128
|
|
|
181
|
-
|
|
129
|
+
Current references: [cheatsheet](docs/CHEATSHEET.md), [architecture](docs/architecture.md),
|
|
130
|
+
[recipe libraries](docs/recipe-libraries.md), [human QA](docs/CLI-ERGONOMICS-HUMAN-QA.md),
|
|
131
|
+
and the checked [video demo validation](docs/VIDEO-DEMO-VALIDATION.md).
|
package/adapters/core/cleanup.sh
CHANGED
|
File without changes
|
package/adapters/core/inject.sh
CHANGED
|
File without changes
|
|
File without changes
|
|
@@ -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,9 @@ 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) appendFileSync(destination, `${line}\n`);
|
|
71
79
|
}
|
|
72
80
|
|
|
73
81
|
function stamp() {
|
|
@@ -95,6 +103,22 @@ function isExtensionTarget(target) {
|
|
|
95
103
|
return String(target.url || '').startsWith('chrome-extension://');
|
|
96
104
|
}
|
|
97
105
|
|
|
106
|
+
function isDappTarget(target) {
|
|
107
|
+
if (!target || !target.webSocketDebuggerUrl || target.type !== 'page') return false;
|
|
108
|
+
try {
|
|
109
|
+
const url = new URL(String(target?.url || ''));
|
|
110
|
+
return url.protocol === 'http:' || url.protocol === 'https:';
|
|
111
|
+
} catch {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function targetStream(target) {
|
|
117
|
+
if (isExtensionTarget(target)) return { kind: 'extension', log: args.extensionLog };
|
|
118
|
+
if (isDappTarget(target)) return { kind: 'dapp', log: args.dappLog };
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
|
|
98
122
|
function previewArg(arg) {
|
|
99
123
|
if (!arg || typeof arg !== 'object') return String(arg);
|
|
100
124
|
if ('value' in arg && arg.value !== undefined) {
|
|
@@ -108,29 +132,29 @@ function previewArg(arg) {
|
|
|
108
132
|
return arg.subtype || arg.type || '?';
|
|
109
133
|
}
|
|
110
134
|
|
|
111
|
-
function emitConsole(label, params) {
|
|
135
|
+
function emitConsole(label, params, destination) {
|
|
112
136
|
const level = String(params.type || 'log').toUpperCase();
|
|
113
137
|
const text = (params.args || []).map(previewArg).join(' ');
|
|
114
|
-
out(`${stamp()} [${label}] ${level} ${text}
|
|
138
|
+
out(`${stamp()} [${label}] ${level} ${text}`, destination);
|
|
115
139
|
}
|
|
116
140
|
|
|
117
|
-
function emitException(label, params) {
|
|
141
|
+
function emitException(label, params, destination) {
|
|
118
142
|
const d = params.exceptionDetails || {};
|
|
119
143
|
const text =
|
|
120
144
|
d.exception?.description ||
|
|
121
145
|
d.exception?.value ||
|
|
122
146
|
d.text ||
|
|
123
147
|
'uncaught exception';
|
|
124
|
-
out(`${stamp()} [${label}] EXCEPTION ${text}
|
|
148
|
+
out(`${stamp()} [${label}] EXCEPTION ${text}`, destination);
|
|
125
149
|
}
|
|
126
150
|
|
|
127
|
-
function emitLogEntry(label, params) {
|
|
151
|
+
function emitLogEntry(label, params, destination) {
|
|
128
152
|
const e = params.entry || {};
|
|
129
153
|
const level = String(e.level || 'log').toUpperCase();
|
|
130
|
-
out(`${stamp()} [${label}] ${level} ${e.text || ''}
|
|
154
|
+
out(`${stamp()} [${label}] ${level} ${e.text || ''}`, destination);
|
|
131
155
|
}
|
|
132
156
|
|
|
133
|
-
function handleMessage(label, data) {
|
|
157
|
+
function handleMessage(label, destination, data) {
|
|
134
158
|
let message;
|
|
135
159
|
try {
|
|
136
160
|
message = JSON.parse(data);
|
|
@@ -141,13 +165,13 @@ function handleMessage(label, data) {
|
|
|
141
165
|
if (message.id != null) return; // command ack, not an event
|
|
142
166
|
switch (message.method) {
|
|
143
167
|
case 'Runtime.consoleAPICalled':
|
|
144
|
-
emitConsole(label, message.params || {});
|
|
168
|
+
emitConsole(label, message.params || {}, destination);
|
|
145
169
|
break;
|
|
146
170
|
case 'Runtime.exceptionThrown':
|
|
147
|
-
emitException(label, message.params || {});
|
|
171
|
+
emitException(label, message.params || {}, destination);
|
|
148
172
|
break;
|
|
149
173
|
case 'Log.entryAdded':
|
|
150
|
-
emitLogEntry(label, message.params || {});
|
|
174
|
+
emitLogEntry(label, message.params || {}, destination);
|
|
151
175
|
break;
|
|
152
176
|
default:
|
|
153
177
|
break;
|
|
@@ -155,9 +179,11 @@ function handleMessage(label, data) {
|
|
|
155
179
|
}
|
|
156
180
|
|
|
157
181
|
function attach(target) {
|
|
158
|
-
const
|
|
182
|
+
const stream = targetStream(target);
|
|
183
|
+
if (!stream) return;
|
|
184
|
+
const label = `${stream.kind}:${shortLabel(target)}`;
|
|
159
185
|
const ws = new WebSocket(target.webSocketDebuggerUrl);
|
|
160
|
-
const entry = { ws, label, opened: false };
|
|
186
|
+
const entry = { ws, label, destination: stream.log, opened: false };
|
|
161
187
|
attached.set(target.id, entry);
|
|
162
188
|
|
|
163
189
|
ws.addEventListener('open', () => {
|
|
@@ -166,16 +192,16 @@ function attach(target) {
|
|
|
166
192
|
for (const method of ['Runtime.enable', 'Log.enable']) {
|
|
167
193
|
ws.send(JSON.stringify({ id: ++id, method, params: {} }));
|
|
168
194
|
}
|
|
169
|
-
out(`${stamp()} [${label}] [attached]
|
|
195
|
+
out(`${stamp()} [${label}] [attached]`, stream.log);
|
|
170
196
|
});
|
|
171
197
|
ws.addEventListener('message', (event) => {
|
|
172
|
-
handleMessage(label, typeof event.data === 'string' ? event.data : '');
|
|
198
|
+
handleMessage(label, stream.log, typeof event.data === 'string' ? event.data : '');
|
|
173
199
|
});
|
|
174
200
|
const drop = () => {
|
|
175
201
|
if (attached.get(target.id) !== entry) return;
|
|
176
202
|
attached.delete(target.id);
|
|
177
203
|
if (entry.opened) {
|
|
178
|
-
out(`${stamp()} [${label}] [reconnect] target detached; will re-attach
|
|
204
|
+
out(`${stamp()} [${label}] [reconnect] target detached; will re-attach`, stream.log);
|
|
179
205
|
} else {
|
|
180
206
|
// Never connected (target gone / not debuggable yet): back off so a dead
|
|
181
207
|
// target is not retried — and logged — on every 2s poll.
|
|
@@ -196,8 +222,9 @@ async function poll() {
|
|
|
196
222
|
// CDP not reachable this tick (browser starting/restarting); retry next poll.
|
|
197
223
|
return;
|
|
198
224
|
}
|
|
199
|
-
|
|
200
|
-
|
|
225
|
+
const targetList = Array.isArray(targets) ? targets : [];
|
|
226
|
+
for (const target of targetList) {
|
|
227
|
+
if (!targetStream(target)) continue;
|
|
201
228
|
if (attached.has(target.id)) continue;
|
|
202
229
|
const until = cooldown.get(target.id);
|
|
203
230
|
if (until !== undefined) {
|
|
@@ -226,6 +253,6 @@ function shutdown() {
|
|
|
226
253
|
process.on('SIGINT', shutdown);
|
|
227
254
|
process.on('SIGTERM', shutdown);
|
|
228
255
|
|
|
229
|
-
out(`${stamp()} [console-tail] streaming extension
|
|
256
|
+
out(`${stamp()} [console-tail] streaming extension and dapp consoles via cdp ${cdpPort}`);
|
|
230
257
|
await poll();
|
|
231
258
|
setInterval(poll, 2000);
|
|
File without changes
|
|
@@ -94,16 +94,16 @@ copyDir(path.join(runnerDir, 'library/recipes'), path.join(harnessDir, 'runner/r
|
|
|
94
94
|
copyFile(path.join(runnerDir, 'library/library.json'), path.join(harnessDir, 'runner/library.json'));
|
|
95
95
|
fs.mkdirSync(path.join(harnessDir, 'runner/flows'), { recursive: true });
|
|
96
96
|
copyDir(path.join(runnerDir, 'library/flows'), path.join(harnessDir, 'runner/flows'));
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
// consumers are unaffected by runner-repo layout changes.
|
|
97
|
+
// The installed overlay has a flat scripts/ runtime API independent of the
|
|
98
|
+
// package's source layout.
|
|
100
99
|
copyFile(path.join(runnerDir, 'adapters/extension/launch-browser.cjs'), path.join(harnessDir, 'scripts/launch-browser.cjs'));
|
|
101
|
-
// installed back-compat alias: callers of the pre-rename installed name
|
|
102
|
-
copyFile(path.join(runnerDir, 'adapters/extension/launch-browser.cjs'), path.join(harnessDir, 'scripts/launch-chrome-detached.cjs'));
|
|
103
100
|
copyFile(path.join(runnerDir, 'adapters/extension/console-tail.mjs'), path.join(harnessDir, 'scripts/console-tail.mjs'));
|
|
104
101
|
copyFile(path.join(runnerDir, 'adapters/extension/launch.sh'), path.join(harnessDir, 'scripts/launch.sh'));
|
|
105
102
|
copyFile(path.join(runnerDir, 'adapters/extension/live.sh'), path.join(harnessDir, 'scripts/live.sh'));
|
|
106
103
|
copyFile(path.join(runnerDir, 'adapters/extension/start-watch.sh'), path.join(harnessDir, 'scripts/start-watch.sh'));
|
|
104
|
+
copyFile(path.join(runnerDir, 'adapters/extension/launch-webpack.cjs'), path.join(harnessDir, 'scripts/launch-webpack.cjs'));
|
|
105
|
+
copyFile(path.join(runnerDir, 'adapters/extension/sync-webpack-dist.cjs'), path.join(harnessDir, 'scripts/sync-webpack-dist.cjs'));
|
|
106
|
+
copyFile(path.join(runnerDir, 'adapters/extension/stamp-runtime-title.cjs'), path.join(harnessDir, 'scripts/stamp-runtime-title.cjs'));
|
|
107
107
|
copyFile(path.join(runnerDir, 'adapters/extension/stop-viewers.sh'), path.join(harnessDir, 'scripts/stop-viewers.sh'));
|
|
108
108
|
copyFile(path.join(runnerDir, 'adapters/extension/snapshot-dist.sh'), path.join(harnessDir, 'scripts/snapshot-dist.sh'));
|
|
109
109
|
copyFile(path.join(runnerDir, 'adapters/extension/pin-remote-flags.cjs'), path.join(harnessDir, 'scripts/pin-remote-flags.cjs'));
|
|
@@ -120,10 +120,6 @@ copyFile(path.join(runnerDir, 'adapters/shared/activate-repo-node.sh'), path.joi
|
|
|
120
120
|
copyFile(path.join(runnerDir, 'adapters/shared/resolve-slot-ports.sh'), path.join(harnessDir, 'scripts/lib/resolve-slot-ports.sh'));
|
|
121
121
|
copyFile(path.join(runnerDir, 'adapters/shared/resolve-slot-ports.mjs'), path.join(harnessDir, 'scripts/lib/resolve-slot-ports.mjs'));
|
|
122
122
|
copyFile(path.join(runnerDir, 'adapters/shared/resolve-slot-ports-core.mjs'), path.join(harnessDir, 'scripts/lib/resolve-slot-ports-core.mjs'));
|
|
123
|
-
// Back-compat shims (one release) so an overlay consumer using the old leaf names still resolves.
|
|
124
|
-
copyFile(path.join(runnerDir, 'adapters/shared/resolve-farmslot-ports.sh'), path.join(harnessDir, 'scripts/lib/resolve-farmslot-ports.sh'));
|
|
125
|
-
copyFile(path.join(runnerDir, 'adapters/shared/resolve-farmslot-ports.mjs'), path.join(harnessDir, 'scripts/lib/resolve-farmslot-ports.mjs'));
|
|
126
|
-
copyFile(path.join(runnerDir, 'adapters/shared/resolve-farmslot-ports-core.mjs'), path.join(harnessDir, 'scripts/lib/resolve-farmslot-ports-core.mjs'));
|
|
127
123
|
copyFile(path.join(runnerDir, 'adapters/shared/path-defaults.json'), path.join(harnessDir, 'scripts/lib/path-defaults.json'));
|
|
128
124
|
copyFile(path.join(runnerDir, 'adapters/shared/json-field.sh'), path.join(harnessDir, 'scripts/lib/json-field.sh'));
|
|
129
125
|
copyFile(path.join(runnerDir, 'adapters/shared/tmux-session.sh'), path.join(harnessDir, 'scripts/lib/tmux-session.sh'));
|
|
File without changes
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const { spawn } = require('node:child_process');
|
|
5
|
+
const fs = require('node:fs');
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
|
|
8
|
+
const args = {};
|
|
9
|
+
for (let index = 2; index < process.argv.length; index += 1) {
|
|
10
|
+
const key = process.argv[index];
|
|
11
|
+
if (key === '--help' || key === '-h') {
|
|
12
|
+
process.stdout.write('Usage: launch-webpack.cjs --target <path> --log <path> --pid-file <path> --sync-script <path> --sync-pid-file <path> --sync-log <path> --dist <path> --runtime-dist <path> [--port <port>] [--path-prefix <path>]\n');
|
|
13
|
+
process.exit(0);
|
|
14
|
+
}
|
|
15
|
+
if (!key.startsWith('--') || index + 1 >= process.argv.length) throw new Error(`invalid argument: ${key}`);
|
|
16
|
+
args[key.slice(2)] = process.argv[++index];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function main() {
|
|
20
|
+
for (const required of ['target', 'log', 'pid-file', 'sync-script', 'sync-pid-file', 'sync-log', 'dist', 'runtime-dist']) {
|
|
21
|
+
if (!args[required]) throw new Error(`--${required} is required`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const target = path.resolve(args.target);
|
|
25
|
+
const log = path.resolve(args.log);
|
|
26
|
+
const pidFile = path.resolve(args['pid-file']);
|
|
27
|
+
const syncScript = path.resolve(args['sync-script']);
|
|
28
|
+
const syncPidFile = path.resolve(args['sync-pid-file']);
|
|
29
|
+
const syncLog = path.resolve(args['sync-log']);
|
|
30
|
+
fs.mkdirSync(path.dirname(log), { recursive: true });
|
|
31
|
+
fs.mkdirSync(path.dirname(pidFile), { recursive: true });
|
|
32
|
+
fs.mkdirSync(path.dirname(syncLog), { recursive: true });
|
|
33
|
+
if (!fs.existsSync(syncScript)) throw new Error(`sync script not found: ${syncScript}`);
|
|
34
|
+
const logFd = fs.openSync(log, 'a');
|
|
35
|
+
const syncLogFd = fs.openSync(syncLog, 'a');
|
|
36
|
+
const env = { ...process.env };
|
|
37
|
+
if (args.port) env.PORT = String(args.port);
|
|
38
|
+
if (args['path-prefix']) env.PATH = `${args['path-prefix']}${env.PATH || ''}`;
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const child = spawn('yarn', ['start'], {
|
|
42
|
+
cwd: target,
|
|
43
|
+
detached: true,
|
|
44
|
+
env,
|
|
45
|
+
stdio: ['ignore', logFd, logFd],
|
|
46
|
+
});
|
|
47
|
+
await new Promise((resolve, reject) => {
|
|
48
|
+
child.once('spawn', resolve);
|
|
49
|
+
child.once('error', reject);
|
|
50
|
+
});
|
|
51
|
+
fs.writeFileSync(pidFile, `${child.pid}\n`);
|
|
52
|
+
const syncChild = spawn(process.execPath, [
|
|
53
|
+
syncScript,
|
|
54
|
+
'--watcher-pid', String(child.pid),
|
|
55
|
+
'--watch-log', log,
|
|
56
|
+
'--target', target,
|
|
57
|
+
'--dist', path.resolve(args.dist),
|
|
58
|
+
'--runtime-dist', path.resolve(args['runtime-dist']),
|
|
59
|
+
'--pid-file', syncPidFile,
|
|
60
|
+
], {
|
|
61
|
+
cwd: target,
|
|
62
|
+
detached: true,
|
|
63
|
+
env,
|
|
64
|
+
stdio: ['ignore', syncLogFd, syncLogFd],
|
|
65
|
+
});
|
|
66
|
+
await new Promise((resolve, reject) => {
|
|
67
|
+
syncChild.once('spawn', resolve);
|
|
68
|
+
syncChild.once('error', reject);
|
|
69
|
+
});
|
|
70
|
+
fs.writeFileSync(syncPidFile, `${syncChild.pid}\n`);
|
|
71
|
+
process.stdout.write(`${child.pid}\n`);
|
|
72
|
+
syncChild.unref();
|
|
73
|
+
child.unref();
|
|
74
|
+
} finally {
|
|
75
|
+
fs.closeSync(logFd);
|
|
76
|
+
fs.closeSync(syncLogFd);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
main().catch((error) => {
|
|
81
|
+
process.stderr.write(`launch-webpack: ${error.message}\n`);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
});
|