@deeeed/metamask-harness 0.5.1 → 0.6.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 +37 -20
- package/adapters/core/inject.sh +1 -0
- package/adapters/extension/cleanup.mjs +1 -1
- package/adapters/extension/inject.mjs +2 -1
- package/adapters/extension/launch-browser.cjs +18 -4
- package/adapters/extension/live.sh +14 -2
- package/adapters/extension/sidepanel-toggle.sh +33 -12
- package/adapters/extension/wallet-fixture-state.cjs +38 -24
- package/adapters/manifest.json +13 -0
- package/adapters/mobile/inject.sh +1 -0
- package/adapters/mobile/stop-metro.sh +20 -0
- package/adapters/shared/cli-ux.sh +20 -2
- package/adapters/shared/ensure-runner-deps.sh +30 -0
- package/adapters/shared/reap-checkout-metros.sh +53 -0
- package/adapters/shared/recipe-harness-root.mjs +23 -0
- package/adapters/shared/resolve-farmslot-ports-core.mjs +15 -6
- package/bin/mm-harness +45 -4
- package/dist/adapters/extension/runtime.js +3 -1
- package/dist/adapters/mobile/provision.js +34 -1
- package/dist/adapters/slot-ports.js +3 -8
- package/dist/cli.js +32 -1463
- package/dist/commands/call.js +183 -0
- package/dist/commands/completion-candidates.js +58 -0
- package/dist/commands/doctor.js +101 -0
- package/dist/commands/ensure-ready.js +24 -0
- package/dist/commands/flows.js +62 -0
- package/dist/commands/launch/extension.js +40 -0
- package/dist/commands/{launch.js → launch/index.js} +15 -47
- package/dist/commands/launch/mobile.js +10 -0
- package/dist/commands/manifest.js +72 -0
- package/dist/commands/parse-args.js +189 -0
- package/dist/commands/provision.js +136 -0
- package/dist/commands/resolve-extension.js +23 -0
- package/dist/commands/run-engine.js +341 -0
- package/dist/commands/run.js +217 -0
- package/dist/commands/runtime-decision.js +58 -0
- package/dist/commands/runtime-health.js +25 -0
- package/dist/commands/runtime-launch.js +139 -0
- package/dist/commands/self-test.js +52 -0
- package/dist/commands/stop.js +52 -0
- package/dist/harness.js +8 -48
- package/dist/mm-harness-cli.js +13 -8
- package/docs/CLI-SPEC.md +1 -1
- package/docs/CODE-MAP.md +62 -0
- package/library/README.md +14 -0
- package/library/actions/extension/platform/cdp.mjs +1 -1
- package/library/actions/extension/wallet/ensure_unlocked.mjs +6 -0
- package/library/actions/mobile/wallet/ensure_unlocked.mjs +13 -1
- package/package.json +6 -7
- package/src/adapters/core/surface.ts +0 -71
- package/src/adapters/extension/ensure-ready.ts +0 -185
- package/src/adapters/extension/extension-id.ts +0 -107
- package/src/adapters/extension/runtime-decision.ts +0 -445
- package/src/adapters/extension/runtime.ts +0 -407
- package/src/adapters/extension/surface.ts +0 -88
- package/src/adapters/mobile/deps-markers.ts +0 -21
- package/src/adapters/mobile/prepare.ts +0 -246
- package/src/adapters/mobile/provision.ts +0 -594
- package/src/adapters/mobile/runtime-decision.ts +0 -466
- package/src/adapters/mobile/surface.ts +0 -71
- package/src/adapters/resolve-farmslot-ports.ts +0 -13
- package/src/adapters/slot-ports.ts +0 -158
- package/src/adapters/surface.ts +0 -117
- package/src/adapters.ts +0 -601
- package/src/cli-color.ts +0 -92
- package/src/cli-commands.ts +0 -250
- package/src/cli-version.ts +0 -141
- package/src/cli.ts +0 -2091
- package/src/commands/debug.ts +0 -65
- package/src/commands/fixtures.ts +0 -198
- package/src/commands/launch.ts +0 -470
- package/src/commands/logs.ts +0 -99
- package/src/commands/shared.ts +0 -235
- package/src/commands/update.ts +0 -316
- package/src/completions-cache.ts +0 -86
- package/src/doctor.ts +0 -215
- package/src/harness.ts +0 -797
- package/src/heal-bounds.ts +0 -198
- package/src/index.ts +0 -15
- package/src/leaf-invoke.ts +0 -28
- package/src/live-adapter-contract.ts +0 -274
- package/src/manifest.ts +0 -47
- package/src/mm-harness-cli.ts +0 -655
- package/src/paths.ts +0 -198
- package/src/progress.ts +0 -117
- package/src/recording-target.ts +0 -147
- package/src/run-recording.ts +0 -329
- package/src/runner.ts +0 -108
- package/src/types.ts +0 -57
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.6.0 - 2026-07-06
|
|
4
|
+
|
|
5
|
+
Minor release: CLI hardening (decomposed, single-parser, dist-only) plus a
|
|
6
|
+
fresh-slot validation wave — every fix proven live on untouched -2 slots and
|
|
7
|
+
independently cross-reviewed.
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
- **CLI decomposition + dist-only publish** — `src/cli.ts` is parse+dispatch only;
|
|
11
|
+
handlers under `src/commands/<verb>.ts`; one commander parser; the npm tarball
|
|
12
|
+
ships compiled `dist/` without `src/` and `tsx` is a devDependency (plain-node,
|
|
13
|
+
smaller, faster). Source checkouts keep the tsx dev path (`MM_HARNESS_BIN`).
|
|
14
|
+
- `verify` auto-ensures its own overlay (no refusal on a fresh checkout); every
|
|
15
|
+
engine execution resolves the slot's ports/device first (call/run/fixtures get
|
|
16
|
+
the same slot isolation launch has).
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- **`stop` reaps leaked Metro bundlers by checkout** (port-agnostic, path-
|
|
20
|
+
boundary matched), `doctor` surfaces any orphan and teaches the reap.
|
|
21
|
+
- **`launch --url <dapp>`** (extension) opens the dapp in the main tab beside the
|
|
22
|
+
MetaMask sidepanel; `--stop-only` profile release before seed; stable per-checkout
|
|
23
|
+
Chrome profile + runtime-dist; post-open tab cleanup.
|
|
24
|
+
- DX: run-mode + stale-`dist` warning in `bin`; overlay-delegation notice; docs/CODE-MAP.md.
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
- `launch` targets the slot's own simulator (never the `booted` alias) and boots it;
|
|
28
|
+
slot-port resolution layers rungs so a partial rung never nulls the port; provision
|
|
29
|
+
resolves the simulator from the pool and boots before install.
|
|
30
|
+
- `call` works from published installs (dist bridge, no type-strip crash);
|
|
31
|
+
`ensure_unlocked` fails teaching `fixtures set` on an un-onboarded wallet instead
|
|
32
|
+
of vacuously passing (mobile + extension); `provision --json` usage errors emit the
|
|
33
|
+
standard envelope; deps-not-ready teaches a command that works on a bare checkout.
|
|
3
34
|
## 0.5.1 - 2026-07-05
|
|
4
35
|
|
|
5
36
|
Fresh-install hotfixes found in live validation of 0.5.0.
|
|
@@ -11,7 +42,12 @@ Fresh-install hotfixes found in live validation of 0.5.0.
|
|
|
11
42
|
|
|
12
43
|
## Unreleased
|
|
13
44
|
|
|
45
|
+
### Fixed
|
|
46
|
+
- **`provision --json` adapter-detection failures emit the standard error envelope** — usage errors now match `doctor`/`launch` (envelope on stdout with `error.userAction`; stderr stays human-only). Previously `--json` callers got empty stdout.
|
|
47
|
+
|
|
14
48
|
### Changed
|
|
49
|
+
- **CLI decomposition** — `src/cli.ts` is parse+dispatch only; handlers live under `src/commands/<verb>.ts`; `provision` routes through `src/commands/provision.ts` (not overlay harness dispatch); launch composition splits under `src/commands/launch/`; commander owns per-command `--help` (no hand-rolled interception).
|
|
50
|
+
- **Published tarball is dist-only** — `files` no longer ships `src/`; `tsx` is a devDependency; `bin/mm-harness` and overlay leaves resolve `dist/*.js` (teaching error if a published install lacks `dist/`). Library actions keep importing `library/actions/harness-exports.mjs` (dist-preferring narrow bridge from 0.5.1); package exports remain for external consumers (`dist/index.js` published; `src/index.ts` when `--conditions=development` and dist is absent).
|
|
15
51
|
- **resolve-farmslot-ports is TypeScript-first** — pool/context/formula port resolution lives in self-contained `adapters/shared/resolve-farmslot-ports-core.mjs` (works from runner and injected overlay copies); `src/adapters/resolve-farmslot-ports.ts` re-exports it for `slot-ports.ts`. `resolve-farmslot-ports.mjs` is the node leaf; `resolve-farmslot-ports.sh` remains a thin bash-compat wrapper for sourced callers.
|
|
16
52
|
|
|
17
53
|
## 0.5.0 - 2026-07-05
|
|
@@ -54,20 +90,7 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
|
|
|
54
90
|
- **Colored grouped help with DEV OVERRIDE banner and SLOT line**; `mmdev-harness` completions; outcome-stating launch summaries with a `[dev]` marker.
|
|
55
91
|
- **docs/UX-PRINCIPLES.md** — the CLI interaction contract (human surface rules; `--json` envelope purity is exempt and inviolable).
|
|
56
92
|
|
|
57
|
-
## Unreleased
|
|
58
|
-
|
|
59
|
-
### Added
|
|
60
|
-
- Add `mm-harness provision` for thin mobile Runway artifact provisioning with a shared integrity-checked cache and simulator install path.
|
|
61
|
-
- Add `mm-harness provision runway ios --resolve-only` to return Runway run/revision/artifact metadata without touching simulator, cache, download, install, or baseline state.
|
|
62
|
-
- **Dist publish path** — `npm run build` compiles `src/` to `dist/`; `npm pack` / global install runs the compiled CLI entry (`dist/mm-harness-cli.js`) while a source checkout without `dist/` keeps the tsx+src dev path. Contracts: `packaging-dist-install`, `packaging-dev-checkout`.
|
|
63
|
-
|
|
64
|
-
### Changed
|
|
65
|
-
- **Leaf stderr hints use mm-harness verbs** — Metro/webpack compact-log prefixes, log-tail hints, and speed-ladder copy now point at `mm-harness logs`, `mm-harness doctor`, `mm-harness launch`, and `mm-harness install` instead of retired `recipe *` commands. Contract: `leaf-branding-retired`.
|
|
66
|
-
|
|
67
93
|
### Fixed
|
|
68
|
-
- `mm-harness provision runway ios` now routes through the same Runway provisioning surface as `install --runway`, so overlay dispatch accepts the compatibility command instead of rejecting it as an unsupported harness action.
|
|
69
|
-
- Runway provisioning now accepts farm context flags (`--slot`, `--watcher-port`, `--runtime-dir`), records slot/watcher/runtime-dir baselines correctly, ignores stale provision baselines unless the app is still installed on the simulator, and emits rerunnable `Next:` commands from the actual invocation.
|
|
70
|
-
- Runway iOS artifact extraction now handles the live nested zip layout and app bundle names other than `MetaMask.app` while keeping the cached app directory integrity hash/size contract.
|
|
71
94
|
- **Mobile preflight no longer re-runs `yarn setup` on every launch** — the deps readiness decision escalated an mtime hint to `stale` and ran the FULL `yarn setup` (submodules + inpage bridge + jetify + pods) inside the launch phase on every invocation in orchestrated (farmslot) contexts. `depsCheck` reports `stale` for a no-baseline checkout whenever a manifest is newer than the install markers; the orchestrator's git phase refreshes tracked-file mtimes on every sync while its deps phase leaves `node_modules` untouched, so "manifest newer than markers" is normal and not proof of drift — yet it duplicated the orchestrator's deps work and turned a fast preflight into 15+ minutes (the behavior that also looked like a hang before 0.3.8's streaming). **Presence is now authoritative:** without a recorded baseline an mtime-only `stale` is trusted as `current` (with a one-line stderr warning), so a churned-but-present checkout launches with zero `yarn` in both tiers. Genuine drift is still caught by the recorded-baseline fingerprint. `mm-harness launch` (quick tier / no `--build`) also dropped `preflightMode` before deciding, so the fast contract never took effect; it is threaded through now.
|
|
72
95
|
- **Fast tier never installs; genuine gaps teach the orchestrator** — in the fast tier (quick launch / orchestrated), deps that genuinely need work (absent markers, an absent required package, a fingerprint drift, or Metro unable to resolve a module) return a teaching block naming the orchestrator deps/prepare phase (plus the standalone `yarn setup:expo` / `launch --build` remedies) instead of an implicit setup — deps are the orchestrator's contract. The standalone/full tier still installs.
|
|
73
96
|
- **Deps baseline recorded the instant a setup leaf succeeds** — `prepareMobile` records the deps fingerprint baseline immediately after any successful `yarn-setup`, unconditionally (previously only a later `record: true` re-decide did, gated on the original decision being `install` and on `RECIPE_UP_INSTALL_ATTEMPTED`). Genuine-drift detection is fingerprint-based and self-sustaining after one good install.
|
|
@@ -153,7 +176,7 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
|
|
|
153
176
|
- Core run teaches the correct next step when no account is set.
|
|
154
177
|
- Resolved CodeQL alerts (insecure temp dir, file-access-to-http).
|
|
155
178
|
|
|
156
|
-
##
|
|
179
|
+
## 0.2.0 - 2026-07-03
|
|
157
180
|
|
|
158
181
|
### Added
|
|
159
182
|
|
|
@@ -164,12 +187,6 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
|
|
|
164
187
|
### Changed
|
|
165
188
|
|
|
166
189
|
- **Teaching errors are type-enforced**: `usageOut` now requires a `userAction` escape argument, so a teaching error without a reachable next step no longer compiles. The repo-type detection failure teaches BOTH escapes (`cd into a checkout / pass --target <path>` and `--adapter <mobile|extension|core>`) consistently across `doctor`, `launch`, `verify`, and the overlay path.
|
|
167
|
-
- **Repository reorganized into `src/`, `adapters/`, `library/` domains**: all TypeScript now lives under `src/` (shared CLI/core plus typed per-adapter logic in `src/adapters/<adapter>/`); everything that executes against or is injected into the app runtime lives under `adapters/` (`adapters/<adapter>/` per-app glue, `adapters/shared/` cross-adapter OS-glue, `adapters/porcelain/` composed entrypoints, `adapters/*/overlay/` the only injected payload); `library/` holds composable vocabulary only. The `runner/` and `orchestration/` trees no longer exist. The surface doctor moved to `scripts/adapter-surface-doctor.mjs` with its index at `adapters/manifest.json`. Placement rules (R1–R11) are documented in `AGENTS.md`. This is a path-only reorganization; runtime behavior is unchanged.
|
|
168
|
-
|
|
169
|
-
## 0.2.0 - 2026-07-03
|
|
170
|
-
|
|
171
|
-
### Changed
|
|
172
|
-
|
|
173
190
|
- **Docs realigned to the shipped single-bin surface**: `README.md`, `docs/harness-cli.md` (reduced to a pointer at `CLI-SPEC.md`), `docs/recipe-libraries.md`, `docs/extension-runtime-commands.md`, `docs/package-boundaries.md`, and `docs/live-adapter-contract.md` no longer teach the retired `metamask-recipe`/`mm-recipe`/`mme-recipe` names or "planned/stub" daily-loop claims — every command example is `mm-harness`. (`docs/MENTAL-MODEL.md` and `docs/CLI-SPEC.md` retain the old names by design: they are the before→after migration contract.)
|
|
174
191
|
- **Published package trimmed to runtime needs**: the `files` field drops dev-only helpers (`scripts/check.mjs`, `scripts/quality/`, `scripts/link-local-farmslot.mjs`, `scripts/validate-action-e2e-artifacts.mjs`) and repo-dev docs (`AGENTS.md`, `CLAUDE.md`), keeping `bin`, `runner`, `orchestration`, `library`, the runtime completion scripts, `docs`, `README.md`, and `CHANGELOG.md`. The `files` field now also negates `scripts/README.md` (dev-only) so the published tarball no longer ships it alongside the two runtime completion scripts.
|
|
175
192
|
- **Comment-hygiene guard extended**: `scripts/quality/comment-hygiene.mjs` now also fails on presentation vocabulary (`verb`/`daily`) in comments and object-property keys (previously only filenames and declarations), enforced with word boundaries so `verbatim`/`verbose` and user-facing help titles like `DAILY LOOP` stay exempt. Source comments and the `verb: 'rebuild'` progress field were reworded/renamed to `command`.
|
package/adapters/core/inject.sh
CHANGED
|
@@ -88,6 +88,7 @@ install_v1_runner_assets() {
|
|
|
88
88
|
if [ -n "$METAMASK_RUNNER_PROTOCOL_ROOT" ]; then
|
|
89
89
|
printf 'export FARMSLOT_ROOT=${FARMSLOT_ROOT:-%s}\n' "$runner_protocol_root_q"
|
|
90
90
|
fi
|
|
91
|
+
printf 'if [ -n "${MM_HARNESS_BIN:-}" ] && [ "$MM_HARNESS_BIN" != %s ]; then echo "mm-harness overlay: %s: %s (reinstall to repoint: mm-harness install)" >&2; fi\n' "$runner_exec_q" 'delegating to the runner pinned at install time' "$runner_exec_q"
|
|
91
92
|
printf 'exec %s "$@"\n' "$runner_exec_q"
|
|
92
93
|
} > "$HARNESS_DIR/runner/bin/mm-harness"
|
|
93
94
|
chmod +x "$HARNESS_DIR/runner/bin/mm-harness"
|
|
@@ -18,7 +18,7 @@ import fs from 'node:fs';
|
|
|
18
18
|
import path from 'node:path';
|
|
19
19
|
import { spawnSync } from 'node:child_process';
|
|
20
20
|
import process from 'node:process';
|
|
21
|
-
import { recipeHarnessRoot } from '
|
|
21
|
+
import { recipeHarnessRoot } from '../shared/recipe-harness-root.mjs';
|
|
22
22
|
|
|
23
23
|
function usage() { console.error('Usage: cleanup-extension-harness.mjs [--target <metamask-extension>]'); }
|
|
24
24
|
let target = process.cwd();
|
|
@@ -23,7 +23,7 @@ import fs from 'node:fs';
|
|
|
23
23
|
import path from 'node:path';
|
|
24
24
|
import { spawnSync } from 'node:child_process';
|
|
25
25
|
import process from 'node:process';
|
|
26
|
-
import { recipeHarnessRoot } from '
|
|
26
|
+
import { recipeHarnessRoot } from '../shared/recipe-harness-root.mjs';
|
|
27
27
|
|
|
28
28
|
function usage() {
|
|
29
29
|
console.error('Usage: inject-extension-harness.mjs [--target <metamask-extension>] [--no-git-exclude]');
|
|
@@ -72,6 +72,7 @@ const delegate = [
|
|
|
72
72
|
'#!/usr/bin/env bash',
|
|
73
73
|
'set -euo pipefail',
|
|
74
74
|
protocolRoot ? `export FARMSLOT_ROOT=\${FARMSLOT_ROOT:-${shellQuote(protocolRoot)}}` : null,
|
|
75
|
+
`if [ -n "\${MM_HARNESS_BIN:-}" ] && [ "\$MM_HARNESS_BIN" != ${shellQuote(path.join(runnerDir, 'bin/mm-harness'))} ]; then echo "mm-harness overlay: delegating to the runner pinned at install time: ${shellQuote(path.join(runnerDir, 'bin/mm-harness'))} (reinstall to repoint: mm-harness install)" >&2; fi`,
|
|
75
76
|
`exec ${shellQuote(path.join(runnerDir, 'bin/mm-harness'))} "$@"`,
|
|
76
77
|
].filter(Boolean).join('\n') + '\n';
|
|
77
78
|
fs.writeFileSync(path.join(harnessDir, 'runner/bin/mm-harness'), delegate, { mode: 0o755 });
|
|
@@ -39,9 +39,13 @@ if (!Number.isInteger(cdpPort) || cdpPort <= 0) {
|
|
|
39
39
|
for (const key of ['chrome-bin', 'profile', 'extension-dir', 'chrome-log', 'chrome-pid']) {
|
|
40
40
|
if (!args[key]) throw new Error(`Missing --${key}`);
|
|
41
41
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
// --stop-only releases the profile before the dist snapshot exists on a first
|
|
43
|
+
// run, so launch-input validation applies only to a real launch.
|
|
44
|
+
if (args['stop-only'] === undefined) {
|
|
45
|
+
if (!fs.existsSync(args['chrome-bin'])) throw new Error(`Chrome binary not found: ${args['chrome-bin']}`);
|
|
46
|
+
if (!fs.existsSync(path.join(args['extension-dir'], 'manifest.json'))) {
|
|
47
|
+
throw new Error(`Extension dist manifest not found: ${path.join(args['extension-dir'], 'manifest.json')}`);
|
|
48
|
+
}
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
// Isolation guard: a standalone launch must use a per-checkout profile, never a
|
|
@@ -72,6 +76,14 @@ if (previousPid !== null && processLoadsProfile(previousPid, args.profile)) owne
|
|
|
72
76
|
terminatePids([...ownedPids]);
|
|
73
77
|
removeProfileSingletonLocks(args.profile);
|
|
74
78
|
|
|
79
|
+
// --stop-only: release the profile (owned Chrome terminated, singleton locks
|
|
80
|
+
// cleared) without launching. The prefill step needs the profile's LevelDB
|
|
81
|
+
// unlocked BEFORE it writes; a prior run's Chrome would hold the lock and the
|
|
82
|
+
// write fails with "Database is not open".
|
|
83
|
+
if (args['stop-only'] !== undefined) {
|
|
84
|
+
process.exit(0);
|
|
85
|
+
}
|
|
86
|
+
|
|
75
87
|
const logFd = fs.openSync(args['chrome-log'], 'a');
|
|
76
88
|
let child;
|
|
77
89
|
try {
|
|
@@ -88,7 +100,9 @@ try {
|
|
|
88
100
|
'--disable-features=ExtensionContentVerification,DisableLoadExtensionCommandLineSwitch',
|
|
89
101
|
`--disable-extensions-except=${args['extension-dir']}`,
|
|
90
102
|
`--load-extension=${args['extension-dir']}`,
|
|
91
|
-
'
|
|
103
|
+
// Initial tab: the caller's dapp URL when provided (sidepanel demos pair the
|
|
104
|
+
// extension sidepanel with a dapp in the main tab), else the extensions page.
|
|
105
|
+
args['start-url'] || 'chrome://extensions/',
|
|
92
106
|
], {
|
|
93
107
|
detached: true,
|
|
94
108
|
env: {
|
|
@@ -50,6 +50,7 @@ while [ "$#" -gt 0 ]; do
|
|
|
50
50
|
--artifacts-dir) [ "$#" -ge 2 ] || { echo "Missing value for $1" >&2; exit 2; }; ARTIFACTS="$2"; shift 2 ;;
|
|
51
51
|
--prepare-cmd) [ "$#" -ge 2 ] || { echo "Missing value for $1" >&2; exit 2; }; PREPARE_CMD="$2"; shift 2 ;;
|
|
52
52
|
--launch-existing-dist) LAUNCH_EXISTING_DIST=true; shift ;;
|
|
53
|
+
--start-url) [ "$#" -ge 2 ] || { echo "Missing value for $1" >&2; exit 2; }; START_URL="$2"; shift 2 ;;
|
|
53
54
|
--start-watch|--start-test-watch) START_WATCH=true; LAUNCH_EXISTING_DIST=true; shift ;;
|
|
54
55
|
--dist-dir) [ "$#" -ge 2 ] || { echo "Missing value for $1" >&2; exit 2; }; DIST_DIR="$2"; shift 2 ;;
|
|
55
56
|
--chrome-user-data-dir) [ "$#" -ge 2 ] || { echo "Missing value for $1" >&2; exit 2; }; CHROME_USER_DATA_DIR="$2"; shift 2 ;;
|
|
@@ -114,8 +115,15 @@ fi
|
|
|
114
115
|
|
|
115
116
|
if $LAUNCH_EXISTING_DIST && [ -z "$PREPARE_CMD" ]; then
|
|
116
117
|
DIST_ABS="$TARGET/$DIST_DIR"
|
|
117
|
-
|
|
118
|
-
|
|
118
|
+
# Stable beside the profile: the reused profile registers the unpacked
|
|
119
|
+
# extension by PATH; a per-run snapshot path gets pruned and the next launch
|
|
120
|
+
# loads nothing ("No EIP-6963 Provider Detected").
|
|
121
|
+
RUNTIME_DIST_ABS="$TARGET/temp/recipe/runtime/runtime-dist"
|
|
122
|
+
# Stable per-checkout profile: successive runs recognize their own Chrome
|
|
123
|
+
# (ownership guard matches on --user-data-dir), take it over cleanly, and the
|
|
124
|
+
# seeded wallet persists across relaunches. A per-run profile made every
|
|
125
|
+
# relaunch "foreign" and let live/ pruning delete profiles still in use.
|
|
126
|
+
PROFILE_ABS="${CHROME_USER_DATA_DIR:-$TARGET/temp/recipe/runtime/chrome-profile}"
|
|
119
127
|
FIXTURE_STATE_ABS="$ARTIFACTS/fixture-state.json"
|
|
120
128
|
FIXTURE_VALIDATION_ABS="$ARTIFACTS/logs/fixture-account-parity.json"
|
|
121
129
|
# Wallet fixture resolution chain + provenance live in seed-fixture.sh.
|
|
@@ -233,6 +241,7 @@ NODE
|
|
|
233
241
|
if $START_WATCH; then
|
|
234
242
|
prepare_parts+=("bash ${quoted_start_watch} --runtime-dir ${quoted_runtime_dir} --runner-bin ${quoted_runner}")
|
|
235
243
|
fi
|
|
244
|
+
prepare_parts+=("node ${quoted_chrome_launcher} --stop-only 1 --chrome-bin ${quoted_chrome} --profile ${quoted_profile} --cdp-port ${CDP_PORT} --extension-dir ${quoted_runtime_dist} --chrome-log ${quoted_chrome_log} --chrome-pid ${quoted_chrome_pid}")
|
|
236
245
|
prepare_parts+=("bash ${quoted_snapshot_dist} --dist ${quoted_dist} --runtime-dist ${quoted_runtime_dist}")
|
|
237
246
|
# Optional A/B feature-flag pinning: patch the ephemeral snapshot manifest so
|
|
238
247
|
# manifest._flags wins over the fetched ClientConfigApi value. No-op unless
|
|
@@ -249,6 +258,9 @@ NODE
|
|
|
249
258
|
prepare_parts+=("bash ${quoted_seed_fixture} prefill --target ${quoted_target} --fixture ${quoted_wallet_fixture} --state ${quoted_fixture_state} --profile ${quoted_profile} --extension-dir ${quoted_runtime_dist} --extension-id-file ${quoted_extension_id_file}")
|
|
250
259
|
fi
|
|
251
260
|
chrome_launch_cmd="node ${quoted_chrome_launcher} --chrome-bin ${quoted_chrome} --profile ${quoted_profile} --cdp-port ${CDP_PORT} --extension-dir ${quoted_runtime_dist} --chrome-log ${quoted_chrome_log} --chrome-pid ${quoted_chrome_pid}"
|
|
261
|
+
if [ -n "${START_URL:-}" ]; then
|
|
262
|
+
chrome_launch_cmd="$chrome_launch_cmd --start-url $(printf '%q' "$START_URL")"
|
|
263
|
+
fi
|
|
252
264
|
prepare_parts+=("$chrome_launch_cmd")
|
|
253
265
|
prepare_parts+=("for i in {1..60}; do curl -fsS --max-time 1 http://127.0.0.1:${CDP_PORT}/json/version >/dev/null 2>&1 && break; sleep 1; done; curl -fsS --max-time 1 http://127.0.0.1:${CDP_PORT}/json/version >/dev/null")
|
|
254
266
|
if [ -n "$WALLET_FIXTURE_ABS" ]; then
|
|
@@ -98,10 +98,12 @@ resolve_ext_id() {
|
|
|
98
98
|
printf '%s\n' "$EXT_ID"
|
|
99
99
|
return
|
|
100
100
|
fi
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
101
|
+
for idf in "$AGENT_DIR/extension.id" "$REPO/temp/recipe/runtime/extension.id"; do
|
|
102
|
+
if [ -f "$idf" ]; then
|
|
103
|
+
tr -d '[:space:]' < "$idf"
|
|
104
|
+
return
|
|
105
|
+
fi
|
|
106
|
+
done
|
|
105
107
|
json_list | python3 -c "import json,re,sys; d=json.load(sys.stdin); ids=[]; [ids.extend(re.findall(r'^chrome-extension://([^/]+)/', t.get('url',''))) for t in d]; print(ids[0] if ids else '')"
|
|
106
108
|
}
|
|
107
109
|
|
|
@@ -239,15 +241,34 @@ const { chromium } = require('playwright');
|
|
|
239
241
|
document.getElementById('__recipe_open_sidepanel__')?.remove();
|
|
240
242
|
});
|
|
241
243
|
|
|
242
|
-
|
|
244
|
+
// The wallet now lives in the sidepanel: leftover extension home tabs would
|
|
245
|
+
// show it twice and steal the foreground from the dapp tab. Close every
|
|
246
|
+
// non-sidepanel extension page (not just one this helper created), then
|
|
247
|
+
// bring the first http(s) tab back to front.
|
|
248
|
+
for (const candidate of context.pages()) {
|
|
249
|
+
if (
|
|
250
|
+
candidate.url().startsWith(`chrome-extension://${extId}/`) &&
|
|
251
|
+
!candidate.url().includes('/sidepanel.html')
|
|
252
|
+
) {
|
|
253
|
+
try {
|
|
254
|
+
await candidate.close();
|
|
255
|
+
} catch (error) {
|
|
256
|
+
console.warn(
|
|
257
|
+
`[sidepanel] extension page close failed after successful open: ${
|
|
258
|
+
error && error.message ? error.message : error
|
|
259
|
+
}`,
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
const dappPage = context
|
|
265
|
+
.pages()
|
|
266
|
+
.find((candidate) => candidate.url().startsWith('http://') || candidate.url().startsWith('https://'));
|
|
267
|
+
if (dappPage) {
|
|
243
268
|
try {
|
|
244
|
-
await
|
|
245
|
-
} catch
|
|
246
|
-
|
|
247
|
-
`[sidepanel] helper page close failed after successful open: ${
|
|
248
|
-
error && error.message ? error.message : error
|
|
249
|
-
}`,
|
|
250
|
-
);
|
|
269
|
+
await dappPage.bringToFront();
|
|
270
|
+
} catch {
|
|
271
|
+
// focus is cosmetic; the panel is already open
|
|
251
272
|
}
|
|
252
273
|
}
|
|
253
274
|
await browser.close();
|
|
@@ -668,8 +668,8 @@ async function detectExtension(context, extensionDir, extensionIdFile) {
|
|
|
668
668
|
const page = await context.newPage();
|
|
669
669
|
try {
|
|
670
670
|
await page.goto(`chrome-extension://${candidate.id}/home.html`, {
|
|
671
|
-
waitUntil: '
|
|
672
|
-
timeout:
|
|
671
|
+
waitUntil: 'domcontentloaded',
|
|
672
|
+
timeout: 30000,
|
|
673
673
|
});
|
|
674
674
|
if (page.url().startsWith('chrome-error://')) {
|
|
675
675
|
throw new Error('candidate resolved to chrome-error page');
|
|
@@ -726,34 +726,48 @@ async function waitForWalletScreen(page) {
|
|
|
726
726
|
return { state: 'unknown', selector: null };
|
|
727
727
|
}
|
|
728
728
|
|
|
729
|
+
async function attemptUnlock(page, password) {
|
|
730
|
+
// Selector ladder: current testids first, then the generic selectors the CDP
|
|
731
|
+
// unlock action uses (proven against the same build). A freshly seeded vault
|
|
732
|
+
// can also still be initializing, so a single attempt is not conclusive —
|
|
733
|
+
// the caller retries.
|
|
734
|
+
const filled = await page.evaluate((pw) => {
|
|
735
|
+
const input =
|
|
736
|
+
document.querySelector('[data-testid="unlock-password"]') ??
|
|
737
|
+
document.querySelector('input[type="password"]');
|
|
738
|
+
if (!input) return false;
|
|
739
|
+
const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
|
|
740
|
+
setter.call(input, pw);
|
|
741
|
+
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
742
|
+
input.dispatchEvent(new Event('change', { bubbles: true }));
|
|
743
|
+
return true;
|
|
744
|
+
}, password);
|
|
745
|
+
if (!filled) return false;
|
|
746
|
+
return page.evaluate(() => {
|
|
747
|
+
const button =
|
|
748
|
+
document.querySelector('[data-testid="unlock-submit"]') ??
|
|
749
|
+
document.querySelector('button[type="submit"]') ??
|
|
750
|
+
Array.from(document.querySelectorAll('button')).find((candidate) => /unlock/iu.test(candidate.innerText || candidate.textContent || ''));
|
|
751
|
+
if (!button) return false;
|
|
752
|
+
button.click();
|
|
753
|
+
return true;
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
|
|
729
757
|
async function unlockIfNeeded(page, password) {
|
|
730
758
|
let state = await waitForWalletScreen(page);
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
await page
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
if (!message.includes('Timeout')) throw error;
|
|
738
|
-
const clicked = await page.evaluate(() => {
|
|
739
|
-
const button = document.querySelector('[data-testid="unlock-submit"]');
|
|
740
|
-
if (!button) return false;
|
|
741
|
-
button.click();
|
|
742
|
-
return true;
|
|
743
|
-
});
|
|
744
|
-
if (!clicked) throw new Error(`Unlock submit timed out and DOM fallback could not find the button: ${message}`);
|
|
745
|
-
}
|
|
746
|
-
const deadline = Date.now() + 45000;
|
|
747
|
-
while (Date.now() < deadline) {
|
|
748
|
-
state = await waitForWalletScreen(page);
|
|
749
|
-
if (state.state === 'unlocked') {
|
|
750
|
-
break;
|
|
751
|
-
}
|
|
759
|
+
const deadline = Date.now() + 90000;
|
|
760
|
+
while (state.state !== 'unlocked' && state.state !== 'onboarding' && Date.now() < deadline) {
|
|
761
|
+
if (state.state === 'locked') {
|
|
762
|
+
await attemptUnlock(page, password);
|
|
763
|
+
await page.waitForTimeout(1500);
|
|
764
|
+
} else {
|
|
752
765
|
await page.waitForTimeout(750);
|
|
753
766
|
}
|
|
767
|
+
state = await waitForWalletScreen(page);
|
|
754
768
|
}
|
|
755
769
|
if (state.state !== 'unlocked') {
|
|
756
|
-
throw new Error(`Wallet did not reach unlocked home screen after fixture seeding (state=${state.state})`);
|
|
770
|
+
throw new Error(`Wallet did not reach unlocked home screen after fixture seeding (state=${state.state}). Next: mm-harness call ensure_unlocked # then relaunch`);
|
|
757
771
|
}
|
|
758
772
|
return state;
|
|
759
773
|
}
|
package/adapters/manifest.json
CHANGED
|
@@ -322,6 +322,14 @@
|
|
|
322
322
|
"inputs": "ESM import from resolve-farmslot-ports.mjs or resolve-farmslot-ports.ts",
|
|
323
323
|
"outputs": "resolveFarmslotPortsByRepo and related functions"
|
|
324
324
|
},
|
|
325
|
+
{
|
|
326
|
+
"id": "lib/recipe-harness-root",
|
|
327
|
+
"entry": "adapters/shared/recipe-harness-root.mjs",
|
|
328
|
+
"kind": "lib",
|
|
329
|
+
"purpose": "Resolve RECIPE_HARNESS_ROOT for overlay node leaves without importing src/paths.ts.",
|
|
330
|
+
"inputs": "ESM import; env RECIPE_HARNESS_ROOT optional override",
|
|
331
|
+
"outputs": "recipeHarnessRoot() relative path string"
|
|
332
|
+
},
|
|
325
333
|
{
|
|
326
334
|
"id": "lib/resolve-farmslot-ports-sh",
|
|
327
335
|
"entry": "adapters/shared/resolve-farmslot-ports.sh",
|
|
@@ -330,6 +338,11 @@
|
|
|
330
338
|
"inputs": "source + resolve_* / apply_resolved_* function calls",
|
|
331
339
|
"outputs": "KEY=VALUE lines on stdout; env vars via apply_resolved_*"
|
|
332
340
|
},
|
|
341
|
+
{
|
|
342
|
+
"id": "lib/reap-checkout-metros",
|
|
343
|
+
"entry": "adapters/shared/reap-checkout-metros.sh",
|
|
344
|
+
"kind": "lib"
|
|
345
|
+
},
|
|
333
346
|
{
|
|
334
347
|
"id": "lib/sync-wallet-fixture",
|
|
335
348
|
"entry": "adapters/shared/sync-wallet-fixture.sh",
|
|
@@ -187,6 +187,7 @@ install_v1_runner_assets() {
|
|
|
187
187
|
if [ -n "$METAMASK_RUNNER_PROTOCOL_ROOT" ]; then
|
|
188
188
|
printf 'export FARMSLOT_ROOT=${FARMSLOT_ROOT:-%s}\n' "$runner_protocol_root_q"
|
|
189
189
|
fi
|
|
190
|
+
printf 'if [ -n "${MM_HARNESS_BIN:-}" ] && [ "$MM_HARNESS_BIN" != %s ]; then echo "mm-harness overlay: %s: %s (reinstall to repoint: mm-harness install)" >&2; fi\n' "$runner_exec_q" 'delegating to the runner pinned at install time' "$runner_exec_q"
|
|
190
191
|
printf 'exec %s "$@"\n' "$runner_exec_q"
|
|
191
192
|
} > "$HARNESS_DIR/runner/bin/mm-harness"
|
|
192
193
|
chmod +x "$HARNESS_DIR/runner/bin/mm-harness"
|
|
@@ -54,8 +54,23 @@ if [ -n "$pids" ]; then
|
|
|
54
54
|
else
|
|
55
55
|
printf 'Metro not running on port %s — nothing to stop\n' "$PORT" >&2
|
|
56
56
|
fi
|
|
57
|
+
|
|
58
|
+
# The stop decision is made; everything below is best-effort cleanup (pid file,
|
|
59
|
+
# leaked-bundler sweep, tmux window). None of it may fail the command — the
|
|
60
|
+
# idempotent-stop contract is already satisfied. Drop set -e for the tail so an
|
|
61
|
+
# incidental non-zero (platform-dependent) can never turn success into exit 1.
|
|
62
|
+
set +e
|
|
57
63
|
rm -f "$PID_FILE"
|
|
58
64
|
|
|
65
|
+
# Port-scoped stop above misses bundlers a prior launch left on a DIFFERENT port
|
|
66
|
+
# (port drift / missing pid file). Sweep every Metro bound to this checkout so
|
|
67
|
+
# stop fully cleans up — the leak that stacked bundlers across relaunches.
|
|
68
|
+
# Guarded: the reap lib may be absent in a minimal install; skip rather than abort.
|
|
69
|
+
if [ -f "$SCRIPT_DIR/../shared/reap-checkout-metros.sh" ]; then
|
|
70
|
+
. "$SCRIPT_DIR/../shared/reap-checkout-metros.sh"
|
|
71
|
+
reap_checkout_metros "$TARGET" || true
|
|
72
|
+
fi
|
|
73
|
+
|
|
59
74
|
# Close the read-only log-tail window start-metro opened, if it is still there.
|
|
60
75
|
if [ -f "$TMUX_FILE" ] && command -v tmux >/dev/null 2>&1; then
|
|
61
76
|
win="$(cat "$TMUX_FILE" 2>/dev/null || true)"
|
|
@@ -64,3 +79,8 @@ if [ -f "$TMUX_FILE" ] && command -v tmux >/dev/null 2>&1; then
|
|
|
64
79
|
fi
|
|
65
80
|
fi
|
|
66
81
|
rm -f "$TMUX_FILE"
|
|
82
|
+
|
|
83
|
+
# Reaching here = cleanup done (nothing needed stopping, or it stopped and
|
|
84
|
+
# windows/pids were cleared). The idempotent-stop contract is success; do not
|
|
85
|
+
# leak an incidental non-zero from the last command on any platform.
|
|
86
|
+
exit 0
|
|
@@ -26,7 +26,16 @@ recipe_color() {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
recipe_progress_script() {
|
|
29
|
-
|
|
29
|
+
if [ -n "${RECIPE_PROGRESS_SCRIPT:-}" ]; then
|
|
30
|
+
printf '%s' "$RECIPE_PROGRESS_SCRIPT"
|
|
31
|
+
return 0
|
|
32
|
+
fi
|
|
33
|
+
local root="${RECIPE_RUNNER_ROOT:?}"
|
|
34
|
+
if [ -f "$root/dist/progress.js" ]; then
|
|
35
|
+
printf '%s' "$root/dist/progress.js"
|
|
36
|
+
else
|
|
37
|
+
printf '%s' "$root/src/progress.ts"
|
|
38
|
+
fi
|
|
30
39
|
}
|
|
31
40
|
|
|
32
41
|
recipe_log_tui_script() {
|
|
@@ -34,7 +43,16 @@ recipe_log_tui_script() {
|
|
|
34
43
|
}
|
|
35
44
|
|
|
36
45
|
recipe_version_script() {
|
|
37
|
-
|
|
46
|
+
if [ -n "${RECIPE_VERSION_SCRIPT:-}" ]; then
|
|
47
|
+
printf '%s' "$RECIPE_VERSION_SCRIPT"
|
|
48
|
+
return 0
|
|
49
|
+
fi
|
|
50
|
+
local root="${RECIPE_RUNNER_ROOT:?}"
|
|
51
|
+
if [ -f "$root/dist/cli-version.js" ]; then
|
|
52
|
+
printf '%s' "$root/dist/cli-version.js"
|
|
53
|
+
else
|
|
54
|
+
printf '%s' "$root/src/cli-version.ts"
|
|
55
|
+
fi
|
|
38
56
|
}
|
|
39
57
|
|
|
40
58
|
recipe_log_ui_mode() {
|
|
@@ -21,7 +21,32 @@ deps_ready() {
|
|
|
21
21
|
[ -f "$RUNNER_DIR/node_modules/@farmslot/recipe-harness/package.json" ]
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
# Library actions import @deeeed/metamask-harness by package name; a source checkout
|
|
25
|
+
# is not npm-installed into its own node_modules, so symlink self for resolution.
|
|
26
|
+
ensure_self_package_link() {
|
|
27
|
+
local link="$RUNNER_DIR/node_modules/@deeeed/metamask-harness"
|
|
28
|
+
/bin/mkdir -p "$RUNNER_DIR/node_modules/@deeeed"
|
|
29
|
+
if [ ! -e "$link" ]; then
|
|
30
|
+
ln -sf "$RUNNER_DIR" "$link"
|
|
31
|
+
fi
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
ensure_dev_package_condition() {
|
|
35
|
+
if [ -f "$RUNNER_DIR/dist/index.js" ]; then
|
|
36
|
+
return 0
|
|
37
|
+
fi
|
|
38
|
+
if [ ! -f "$RUNNER_DIR/src/index.ts" ]; then
|
|
39
|
+
return 0
|
|
40
|
+
fi
|
|
41
|
+
case " ${NODE_OPTIONS:-} " in
|
|
42
|
+
*" --conditions=development "*) return 0 ;;
|
|
43
|
+
esac
|
|
44
|
+
export NODE_OPTIONS="${NODE_OPTIONS:+$NODE_OPTIONS }--conditions=development"
|
|
45
|
+
}
|
|
46
|
+
|
|
24
47
|
if deps_ready; then
|
|
48
|
+
ensure_self_package_link
|
|
49
|
+
ensure_dev_package_condition
|
|
25
50
|
exit 0
|
|
26
51
|
fi
|
|
27
52
|
|
|
@@ -39,6 +64,8 @@ if [ -n "$PROTOCOL_ROOT" ] && [ -f "$RUNNER_DIR/scripts/link-local-farmslot.mjs"
|
|
|
39
64
|
FARMSLOT_ROOT="$(cd "$PROTOCOL_ROOT" && pwd -P)" \
|
|
40
65
|
node "$RUNNER_DIR/scripts/link-local-farmslot.mjs" >&2 || true
|
|
41
66
|
if deps_ready; then
|
|
67
|
+
ensure_self_package_link
|
|
68
|
+
ensure_dev_package_condition
|
|
42
69
|
exit 0
|
|
43
70
|
fi
|
|
44
71
|
fi
|
|
@@ -54,3 +81,6 @@ if ! deps_ready; then
|
|
|
54
81
|
echo "mm-harness: runner dependencies still missing after npm install: $RUNNER_DIR" >&2
|
|
55
82
|
exit 1
|
|
56
83
|
fi
|
|
84
|
+
|
|
85
|
+
ensure_self_package_link
|
|
86
|
+
ensure_dev_package_condition
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# reap-checkout-metros.sh — kill EVERY Metro/Expo bundler bound to a given
|
|
3
|
+
# checkout, regardless of port. Port-scoped stop leaks a bundler whenever a
|
|
4
|
+
# launch drifts ports or fails to record its pid; this sweeps them by checkout
|
|
5
|
+
# path so `stop` fully cleans up and `launch` starts from zero.
|
|
6
|
+
#
|
|
7
|
+
# reap_checkout_metros <checkout-abs-path> [keep-pid]
|
|
8
|
+
# keep-pid (optional): a pid to spare (the bundler just started/handled).
|
|
9
|
+
reap_checkout_metros() {
|
|
10
|
+
local repo="$1" keep="${2:-}" reaped=""
|
|
11
|
+
[ -n "$repo" ] || return 0
|
|
12
|
+
# Match the bundler's argv: `expo start` / metro under this checkout. ps argv
|
|
13
|
+
# embeds the cwd-launched command; expo/metro run with the repo on the path.
|
|
14
|
+
local pid args
|
|
15
|
+
while IFS= read -r pid; do
|
|
16
|
+
[ -n "$pid" ] || continue
|
|
17
|
+
[ "$pid" = "$keep" ] && continue
|
|
18
|
+
args="$(ps -o args= -p "$pid" 2>/dev/null || true)"
|
|
19
|
+
# Anchor to a path boundary so /repos/mm-1 does not match /repos/mm-10.
|
|
20
|
+
case "$args" in
|
|
21
|
+
*"$repo/"*|*"$repo "*) : ;; # argv references this checkout
|
|
22
|
+
*) continue ;;
|
|
23
|
+
esac
|
|
24
|
+
case "$args" in
|
|
25
|
+
*"expo start"*|*"metro"*|*"react-native/cli"*|*"@react-native-community/cli"*)
|
|
26
|
+
kill "$pid" 2>/dev/null && reaped="$reaped $pid" ;;
|
|
27
|
+
esac
|
|
28
|
+
done <<LIST
|
|
29
|
+
$(pgrep -f "expo start|metro" 2>/dev/null)
|
|
30
|
+
LIST
|
|
31
|
+
if [ -n "$reaped" ]; then
|
|
32
|
+
printf 'Reaped leaked Metro bundler(s) for this checkout:%s\n' "$reaped" >&2
|
|
33
|
+
fi
|
|
34
|
+
return 0
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
# detect_checkout_metros <checkout-abs-path> — print pids of every metro/expo
|
|
38
|
+
# bundler bound to the checkout, one per line, WITHOUT killing. Powers doctor's
|
|
39
|
+
# orphan visibility so leaked bundlers are surfaced without a launch.
|
|
40
|
+
detect_checkout_metros() {
|
|
41
|
+
local repo="$1" pid args
|
|
42
|
+
[ -n "$repo" ] || return 0
|
|
43
|
+
while IFS= read -r pid; do
|
|
44
|
+
[ -n "$pid" ] || continue
|
|
45
|
+
args="$(ps -o args= -p "$pid" 2>/dev/null || true)"
|
|
46
|
+
case "$args" in *"$repo/"*|*"$repo "*) : ;; *) continue ;; esac
|
|
47
|
+
case "$args" in
|
|
48
|
+
*"expo start"*|*"metro"*|*"react-native/cli"*|*"@react-native-community/cli"*) printf '%s\n' "$pid" ;;
|
|
49
|
+
esac
|
|
50
|
+
done <<LIST
|
|
51
|
+
$(pgrep -f "expo start|metro" 2>/dev/null)
|
|
52
|
+
LIST
|
|
53
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// recipe-harness-root.mjs — overlay harness root path for node leaves (no src/ import).
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
|
|
6
|
+
const runnerDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
|
|
7
|
+
|
|
8
|
+
function validateRelative(name, value) {
|
|
9
|
+
if (!value || path.isAbsolute(value)) {
|
|
10
|
+
throw new Error(`${name} must be a non-empty relative path: ${value}`);
|
|
11
|
+
}
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function readDefaults() {
|
|
16
|
+
const defaultsPath = path.join(runnerDir, 'adapters/shared/path-defaults.json');
|
|
17
|
+
const parsed = JSON.parse(fs.readFileSync(defaultsPath, 'utf8'));
|
|
18
|
+
return validateRelative('recipeHarnessRoot', parsed.recipeHarnessRoot || 'temp/recipe/harness');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function recipeHarnessRoot() {
|
|
22
|
+
return validateRelative('RECIPE_HARNESS_ROOT', process.env.RECIPE_HARNESS_ROOT || readDefaults());
|
|
23
|
+
}
|