@deeeed/metamask-harness 0.6.3 → 0.7.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 +72 -0
- package/adapters/extension/inject.mjs +5 -0
- package/adapters/extension/start-watch.sh +11 -17
- package/adapters/manifest.json +57 -9
- package/adapters/mobile/lib/tmux-viewer.sh +31 -10
- package/adapters/mobile/open-device.sh +14 -1
- package/adapters/mobile/stop-metro.sh +1 -1
- package/adapters/mobile/yarn-setup.sh +15 -1
- package/adapters/shared/activate-repo-ruby.sh +124 -0
- package/adapters/shared/open-log-window.sh +55 -0
- package/adapters/shared/resolve-farmslot-ports-core.mjs +3 -205
- package/adapters/shared/resolve-farmslot-ports.mjs +4 -19
- package/adapters/shared/resolve-farmslot-ports.sh +6 -104
- package/adapters/shared/resolve-slot-ports-core.mjs +213 -0
- package/adapters/shared/resolve-slot-ports.mjs +20 -0
- package/adapters/shared/resolve-slot-ports.sh +110 -0
- package/adapters/shared/tmux-session.sh +35 -0
- package/dist/adapters/mobile/provision.js +25 -2
- package/dist/adapters/{resolve-farmslot-ports.js → resolve-slot-ports.js} +4 -2
- package/dist/adapters/slot-ports.js +8 -10
- package/dist/cli-commands.js +1 -1
- package/dist/commands/call.js +5 -0
- package/dist/commands/doctor.js +21 -1
- package/dist/commands/fixtures.js +5 -3
- package/dist/commands/launch/extension.js +18 -0
- package/dist/commands/launch/index.js +10 -1
- package/dist/commands/list-executables.js +48 -0
- package/dist/commands/logs.js +25 -2
- package/dist/commands/manifest.js +27 -7
- package/dist/commands/parse-args.js +1 -0
- package/dist/commands/run.js +3 -4
- package/dist/live-adapter-contract.js +30 -9
- package/dist/paths.js +4 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,77 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.7.0
|
|
4
|
+
|
|
5
|
+
The "version people use": every remaining identified fix/improvement batched on the
|
|
6
|
+
0.6.x stability foundation. Built on top of the extension quick-reuse regression fix
|
|
7
|
+
(0.6.3, below). One themed commit per item; full contract suite green (macOS).
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- **`call --list` / `run --list`** — one scope-aware listing of everything invocable
|
|
11
|
+
for the detected adapter: the fuzzy short-name action universe (each action's full
|
|
12
|
+
name plus its unambiguous short name — what `call` accepts) and the runnable
|
|
13
|
+
library flows (what `run <flow>` accepts). No `<action>`/`<recipe>` required; same
|
|
14
|
+
shape under `--json`.
|
|
15
|
+
- **`logs --window`** — (re)open the read-only tmux tail window for the resolved
|
|
16
|
+
dev-server port without touching the process (recovers a window closed while
|
|
17
|
+
Metro/webpack keeps running); teaches when there is no run-owned session or log.
|
|
18
|
+
- **`doctor` surfaces Screen-Recording / capture readiness** (macOS) — shells
|
|
19
|
+
`capture-helper doctor` and reports pass/WARN with the grant step; capture is
|
|
20
|
+
optional (absent binary → no row, never a doctor failure). Seam: `CAPTURE_HELPER_PATH`.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
- **`fixtures set` is never silent** — an upfront intent line the moment work begins,
|
|
24
|
+
plus the mobile setup-wallet leaf's own progress streamed live (heartbeat) during
|
|
25
|
+
the 20-40s wallet build; stdout stays a clean `--json` envelope.
|
|
26
|
+
- **`actions --action <name>` fuzzy-resolves like `call`** (exact full name → exact
|
|
27
|
+
final segment → substring) and teaches the vocabulary on an unknown name (exit 2,
|
|
28
|
+
`--json` error envelope) instead of throwing.
|
|
29
|
+
- **`run` auto-detects the adapter** from the target/cwd when `--adapter` is absent
|
|
30
|
+
(parity with call/doctor); teaches when the repo type cannot be detected.
|
|
31
|
+
- **Log-tail windows follow a run-owned session ladder** — `RECIPE_TMUX_SESSION` →
|
|
32
|
+
the checkout's `agentic-runtime.json` `session` → the current tmux client — with no
|
|
33
|
+
slot-number derivation (the orchestrator names the session; the harness only
|
|
34
|
+
populates windows). Stale `metro-<port>` windows whose port has no live listener
|
|
35
|
+
are swept on Metro start.
|
|
36
|
+
- **Extension slot ports fill independently** — the resolver no longer early-returns
|
|
37
|
+
once CDP is known, so a context that pins only `cdpPort` still gets `WATCHER_PORT`
|
|
38
|
+
from the pool/formula.
|
|
39
|
+
- Orchestrator-neutral protocol-root env: `METAMASK_RUNNER_PROTOCOL_ROOT` is primary,
|
|
40
|
+
`FARMSLOT_ROOT` a back-compat alias for one release.
|
|
41
|
+
|
|
42
|
+
### Fixed
|
|
43
|
+
- **Extension `launch` teaches `yarn install --immutable` on missing/stale deps** —
|
|
44
|
+
a pre-watcher deps-freshness probe fails fast (teaching only, never auto-installs)
|
|
45
|
+
instead of letting webpack crash deep on stale `node_modules`; gated so a reusable
|
|
46
|
+
live runtime (reload-in-place) is never blocked.
|
|
47
|
+
- **Core actions run under the target checkout's own tsx** — a core perps action
|
|
48
|
+
dynamically imports the checkout's controller TypeScript, so it now runs under tsx
|
|
49
|
+
(resolved from the target first — the published harness ships none; a thin-installed
|
|
50
|
+
slot has it) rather than bare node throwing `ERR_MODULE_NOT_FOUND`; teaches
|
|
51
|
+
`yarn install` / `yarn build` when no tsx is found.
|
|
52
|
+
- **Repo-pinned Ruby for iOS pod/gem work** — `yarn setup` / the iOS native build
|
|
53
|
+
activate the checkout's `.ruby-version` (asdf → rbenv → mise → PATH) so a cold
|
|
54
|
+
checkout does not build native gems against the system Ruby; teaches the install
|
|
55
|
+
when the pinned Ruby is unresolved.
|
|
56
|
+
- **iOS deep-link dialog pre-approval at provisioning** — the dev-client scheme
|
|
57
|
+
approval is persisted right after app install, so the first headless `openurl` does
|
|
58
|
+
not pause on the "Open in <App>?" dialog (the launch-time write can race).
|
|
59
|
+
- **Extension quick-tier webpack tail window** is regression-guarded (Metro parity).
|
|
60
|
+
|
|
61
|
+
### Internal
|
|
62
|
+
- Dependabot: no high/critical advisories remain (existing `resolutions` cover them).
|
|
63
|
+
- Boring-library swaps (pidtree, ajv) declined per the "delete hand-rolled code with a
|
|
64
|
+
bug history" bar: the ps/lsof process detection is contract-tested and working, and
|
|
65
|
+
manifest validation is already schema-driven via `@farmslot/protocol`.
|
|
66
|
+
- Orchestrator-neutral port leaf: `resolve-farmslot-ports{,-core}.mjs` / `.sh` and
|
|
67
|
+
`src/adapters/resolve-farmslot-ports.ts` renamed to `resolve-slot-ports*`; the
|
|
68
|
+
function `resolveFarmslotPortsByRepo`, the bash function / cli key
|
|
69
|
+
`resolve_farmslot_ports_by_repo`, and the JS export renamed to the `*slot*`
|
|
70
|
+
equivalents. The old filenames, bash function, cli key, and JS export are kept as
|
|
71
|
+
delegating shims/aliases for ONE RELEASE (deprecation window) — the next release
|
|
72
|
+
removes them. The pool-path constants the leaf reads are left as-is (orchestrator
|
|
73
|
+
data location, not a callable surface).
|
|
74
|
+
|
|
3
75
|
## 0.6.3
|
|
4
76
|
|
|
5
77
|
### Fixed
|
|
@@ -109,11 +109,16 @@ copyFile(path.join(runnerDir, 'adapters/extension/refresh-build.sh'), path.join(
|
|
|
109
109
|
copyFile(path.join(runnerDir, 'adapters/extension/ensure-browser.sh'), path.join(harnessDir, 'scripts/reopen-browser.sh'));
|
|
110
110
|
copyFile(path.join(runnerDir, 'adapters/shared/harness-path.sh'), path.join(harnessDir, 'scripts/lib/harness-path.sh'));
|
|
111
111
|
copyFile(path.join(runnerDir, 'adapters/shared/activate-repo-node.sh'), path.join(harnessDir, 'scripts/lib/activate-repo-node.sh'));
|
|
112
|
+
copyFile(path.join(runnerDir, 'adapters/shared/resolve-slot-ports.sh'), path.join(harnessDir, 'scripts/lib/resolve-slot-ports.sh'));
|
|
113
|
+
copyFile(path.join(runnerDir, 'adapters/shared/resolve-slot-ports.mjs'), path.join(harnessDir, 'scripts/lib/resolve-slot-ports.mjs'));
|
|
114
|
+
copyFile(path.join(runnerDir, 'adapters/shared/resolve-slot-ports-core.mjs'), path.join(harnessDir, 'scripts/lib/resolve-slot-ports-core.mjs'));
|
|
115
|
+
// Back-compat shims (one release) so an overlay consumer using the old leaf names still resolves.
|
|
112
116
|
copyFile(path.join(runnerDir, 'adapters/shared/resolve-farmslot-ports.sh'), path.join(harnessDir, 'scripts/lib/resolve-farmslot-ports.sh'));
|
|
113
117
|
copyFile(path.join(runnerDir, 'adapters/shared/resolve-farmslot-ports.mjs'), path.join(harnessDir, 'scripts/lib/resolve-farmslot-ports.mjs'));
|
|
114
118
|
copyFile(path.join(runnerDir, 'adapters/shared/resolve-farmslot-ports-core.mjs'), path.join(harnessDir, 'scripts/lib/resolve-farmslot-ports-core.mjs'));
|
|
115
119
|
copyFile(path.join(runnerDir, 'adapters/shared/path-defaults.json'), path.join(harnessDir, 'scripts/lib/path-defaults.json'));
|
|
116
120
|
copyFile(path.join(runnerDir, 'adapters/shared/json-field.sh'), path.join(harnessDir, 'scripts/lib/json-field.sh'));
|
|
121
|
+
copyFile(path.join(runnerDir, 'adapters/shared/tmux-session.sh'), path.join(harnessDir, 'scripts/lib/tmux-session.sh'));
|
|
117
122
|
copyFile(path.join(runnerDir, 'adapters/shared/log-tui.mjs'), path.join(harnessDir, 'scripts/lib/log-tui.mjs'));
|
|
118
123
|
makeExecutableTree(path.join(harnessDir, 'scripts'));
|
|
119
124
|
fs.writeFileSync(path.join(harnessDir, 'installed-scripts.sha256'), `${dirContentHash(path.join(harnessDir, 'scripts'))}\n`);
|
|
@@ -174,12 +174,6 @@ write_canonical_watch_pid() {
|
|
|
174
174
|
printf '%s\n' "$live_pid" > "$slot_watch_pid_file"
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
-
slot_number() {
|
|
178
|
-
local base
|
|
179
|
-
base="$(basename "$PWD")"
|
|
180
|
-
[[ "$base" =~ -([0-9]+)$ ]] && printf '%s\n' "${BASH_REMATCH[1]}"
|
|
181
|
-
}
|
|
182
|
-
|
|
183
177
|
# Explicit clean-build path: clear the target-scoped webpack cache before
|
|
184
178
|
# starting the harness-owned watcher. Faster incremental refresh stays on
|
|
185
179
|
# `mm-harness refresh`; this script is for proving a clean build works.
|
|
@@ -240,17 +234,17 @@ path_prefix="${REPO_NODE_PATH_PREFIX:-}"
|
|
|
240
234
|
nohup env -u BUNDLED_DEBUGPY_PATH ${path_prefix:+PATH="${path_prefix}${PATH}"} ${WATCHER_PORT:+PORT="$WATCHER_PORT"} yarn start > "$watch_log" 2>&1 &
|
|
241
235
|
started_pid=$!
|
|
242
236
|
printf '%s\n' "$started_pid" > "$watch_pid_file"
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
if
|
|
253
|
-
tmux_session="$(
|
|
237
|
+
# The orchestrator names the session; the harness only populates windows. Resolve
|
|
238
|
+
# via the run-owned ladder (RECIPE_TMUX_SESSION → agentic-runtime.json session →
|
|
239
|
+
# current tmux client) — no slot-number derivation.
|
|
240
|
+
tmux_session=""
|
|
241
|
+
for _tv_src in "$SCRIPT_DIR/lib/tmux-session.sh" "$SCRIPT_DIR/../shared/tmux-session.sh"; do
|
|
242
|
+
# shellcheck disable=SC1091
|
|
243
|
+
[ -f "$_tv_src" ] && { . "$_tv_src"; break; }
|
|
244
|
+
done
|
|
245
|
+
unset _tv_src
|
|
246
|
+
if command -v resolve_run_tmux_session >/dev/null 2>&1; then
|
|
247
|
+
tmux_session="$(resolve_run_tmux_session "$RUNTIME_DIR")"
|
|
254
248
|
fi
|
|
255
249
|
if [ -n "$tmux_session" ] && command -v tmux >/dev/null 2>&1 && tmux has-session -t "$tmux_session" 2>/dev/null; then
|
|
256
250
|
tmux_window="webpack-${WATCHER_PORT:-default}"
|
package/adapters/manifest.json
CHANGED
|
@@ -298,6 +298,14 @@
|
|
|
298
298
|
"inputs": "json path + dotted field",
|
|
299
299
|
"outputs": "field value on stdout"
|
|
300
300
|
},
|
|
301
|
+
{
|
|
302
|
+
"id": "lib/tmux-session",
|
|
303
|
+
"entry": "adapters/shared/tmux-session.sh",
|
|
304
|
+
"kind": "lib",
|
|
305
|
+
"purpose": "resolve_run_tmux_session: the run-owned tmux session a log-tail window belongs in (RECIPE_TMUX_SESSION -> agentic-runtime.json session -> current client; no slot-number derivation).",
|
|
306
|
+
"inputs": "resolve_run_tmux_session [runtime-dir]; env RECIPE_TMUX_SESSION, RECIPE_RUNTIME_CONTEXT, TMUX",
|
|
307
|
+
"outputs": "resolved session name on stdout (empty when none)"
|
|
308
|
+
},
|
|
301
309
|
{
|
|
302
310
|
"id": "lib/activate-repo-node",
|
|
303
311
|
"entry": "adapters/shared/activate-repo-node.sh",
|
|
@@ -306,6 +314,14 @@
|
|
|
306
314
|
"inputs": "activate_repo_node [target-dir]; tries asdf, fnm, mise, nvm, then matching node on PATH",
|
|
307
315
|
"outputs": "REPO_NODE_BIN, REPO_NODE_PATH_PREFIX; prepends bin dir to PATH; exit 0/1/2"
|
|
308
316
|
},
|
|
317
|
+
{
|
|
318
|
+
"id": "lib/activate-repo-ruby",
|
|
319
|
+
"entry": "adapters/shared/activate-repo-ruby.sh",
|
|
320
|
+
"kind": "lib",
|
|
321
|
+
"purpose": "Manager-agnostic Ruby pin from .ruby-version / .tool-versions for iOS pod/gem work in non-interactive spawns; best-effort variant warns + teaches when unresolved.",
|
|
322
|
+
"inputs": "activate_repo_ruby [target-dir] (0/1/2); activate_repo_ruby_best_effort [target-dir]; tries asdf, rbenv, mise, then matching ruby on PATH",
|
|
323
|
+
"outputs": "REPO_RUBY_BIN, REPO_RUBY_PATH_PREFIX; prepends bin dir to PATH"
|
|
324
|
+
},
|
|
309
325
|
{
|
|
310
326
|
"id": "lib/ensure-runner-deps",
|
|
311
327
|
"entry": "adapters/shared/ensure-runner-deps.sh",
|
|
@@ -314,21 +330,37 @@
|
|
|
314
330
|
"inputs": "ensure-runner-deps.sh [RUNNER_DIR]; env FARMSLOT_ROOT / METAMASK_RUNNER_PROTOCOL_ROOT optional local link target",
|
|
315
331
|
"outputs": "installed node_modules in RUNNER_DIR; exit 0 when deps ready"
|
|
316
332
|
},
|
|
333
|
+
{
|
|
334
|
+
"id": "lib/resolve-slot-ports",
|
|
335
|
+
"entry": "adapters/shared/resolve-slot-ports.mjs",
|
|
336
|
+
"kind": "node",
|
|
337
|
+
"purpose": "Resolve CDP + watcher ports from the orchestrator's slot-pool JSON by repo path, else 6660+N / 9010+N.",
|
|
338
|
+
"inputs": "resolve-slot-ports.mjs resolve_extension_runtime_ports <repo>",
|
|
339
|
+
"outputs": "CDP_PORT / WATCHER_PORT / SLOT_ID lines on stdout"
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
"id": "lib/resolve-slot-ports-core",
|
|
343
|
+
"entry": "adapters/shared/resolve-slot-ports-core.mjs",
|
|
344
|
+
"kind": "lib",
|
|
345
|
+
"purpose": "Self-contained pool/context/formula port resolution imported by the node leaf and TS surface.",
|
|
346
|
+
"inputs": "ESM import from resolve-slot-ports.mjs or resolve-slot-ports.ts",
|
|
347
|
+
"outputs": "resolveSlotPortsByRepo and related functions"
|
|
348
|
+
},
|
|
317
349
|
{
|
|
318
350
|
"id": "lib/resolve-farmslot-ports",
|
|
319
351
|
"entry": "adapters/shared/resolve-farmslot-ports.mjs",
|
|
320
352
|
"kind": "node",
|
|
321
|
-
"purpose": "
|
|
322
|
-
"inputs": "resolve-farmslot-ports.mjs
|
|
323
|
-
"outputs": "
|
|
353
|
+
"purpose": "Back-compat shim (one release): delegates to resolve-slot-ports.mjs.",
|
|
354
|
+
"inputs": "resolve-farmslot-ports.mjs <fn> <repo>",
|
|
355
|
+
"outputs": "same as resolve-slot-ports.mjs"
|
|
324
356
|
},
|
|
325
357
|
{
|
|
326
358
|
"id": "lib/resolve-farmslot-ports-core",
|
|
327
359
|
"entry": "adapters/shared/resolve-farmslot-ports-core.mjs",
|
|
328
360
|
"kind": "lib",
|
|
329
|
-
"purpose": "
|
|
330
|
-
"inputs": "ESM import
|
|
331
|
-
"outputs": "
|
|
361
|
+
"purpose": "Back-compat shim (one release): re-exports resolve-slot-ports-core.mjs.",
|
|
362
|
+
"inputs": "ESM import of the old path",
|
|
363
|
+
"outputs": "re-exports resolveSlotPortsByRepo (and the resolveFarmslotPortsByRepo alias)"
|
|
332
364
|
},
|
|
333
365
|
{
|
|
334
366
|
"id": "lib/recipe-harness-root",
|
|
@@ -339,13 +371,21 @@
|
|
|
339
371
|
"outputs": "recipeHarnessRoot() relative path string"
|
|
340
372
|
},
|
|
341
373
|
{
|
|
342
|
-
"id": "lib/resolve-
|
|
343
|
-
"entry": "adapters/shared/resolve-
|
|
374
|
+
"id": "lib/resolve-slot-ports-sh",
|
|
375
|
+
"entry": "adapters/shared/resolve-slot-ports.sh",
|
|
344
376
|
"kind": "lib",
|
|
345
|
-
"purpose": "Bash-compat sourced wrapper over resolve-
|
|
377
|
+
"purpose": "Bash-compat sourced wrapper over resolve-slot-ports.mjs for stop-metro and legacy callers.",
|
|
346
378
|
"inputs": "source + resolve_* / apply_resolved_* function calls",
|
|
347
379
|
"outputs": "KEY=VALUE lines on stdout; env vars via apply_resolved_*"
|
|
348
380
|
},
|
|
381
|
+
{
|
|
382
|
+
"id": "lib/resolve-farmslot-ports-sh",
|
|
383
|
+
"entry": "adapters/shared/resolve-farmslot-ports.sh",
|
|
384
|
+
"kind": "lib",
|
|
385
|
+
"purpose": "Back-compat shim (one release): sources resolve-slot-ports.sh (keeps the resolve_farmslot_ports_by_repo alias).",
|
|
386
|
+
"inputs": "source",
|
|
387
|
+
"outputs": "same sourced functions as resolve-slot-ports.sh"
|
|
388
|
+
},
|
|
349
389
|
{
|
|
350
390
|
"id": "lib/reap-checkout-metros",
|
|
351
391
|
"entry": "adapters/shared/reap-checkout-metros.sh",
|
|
@@ -359,6 +399,14 @@
|
|
|
359
399
|
"inputs": "--target <checkout> --slot-id <id> --cdp-port <port>",
|
|
360
400
|
"outputs": "wallet-fixture.json in runtime dir; exit 0 always (non-zero = informational, no source found)"
|
|
361
401
|
},
|
|
402
|
+
{
|
|
403
|
+
"id": "lib/open-log-window",
|
|
404
|
+
"entry": "adapters/shared/open-log-window.sh",
|
|
405
|
+
"kind": "bash",
|
|
406
|
+
"purpose": "(Re)open a read-only tmux tail window for a dev-server log in the run-owned session without touching the process (mm-harness logs --window).",
|
|
407
|
+
"inputs": "--window <name> --log <path> [--runtime-dir <dir>]",
|
|
408
|
+
"outputs": "tmux tail window in the resolved session; exit 0 opened / 1 no session or log / 2 bad args"
|
|
409
|
+
},
|
|
362
410
|
{
|
|
363
411
|
"id": "lib/path-defaults",
|
|
364
412
|
"entry": "adapters/shared/path-defaults.json",
|
|
@@ -13,24 +13,45 @@
|
|
|
13
13
|
# Inputs (env/args): $1 = metro log path; PORT, LOG_DIR, and optional `slot` from
|
|
14
14
|
# the caller; TMUX indicates we are inside a tmux client.
|
|
15
15
|
|
|
16
|
+
# Session resolution (RECIPE_TMUX_SESSION → agentic-runtime.json session → current
|
|
17
|
+
# tmux client). Sourced relative to this lib; harness-path fallback for the
|
|
18
|
+
# installed layout where shared libs live beside this file.
|
|
19
|
+
_tv_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
20
|
+
# shellcheck disable=SC1091
|
|
21
|
+
for _tv_src in "$_tv_dir/tmux-session.sh" "$_tv_dir/../../shared/tmux-session.sh"; do
|
|
22
|
+
[ -f "$_tv_src" ] && { . "$_tv_src"; break; }
|
|
23
|
+
done
|
|
24
|
+
unset _tv_src _tv_dir
|
|
25
|
+
|
|
16
26
|
# shellcheck disable=SC2329 # sourced by start-metro.sh; also sourced by tests.
|
|
17
27
|
start_viewer_window() {
|
|
18
28
|
local metro_log="$1"
|
|
19
29
|
command -v tmux >/dev/null 2>&1 || return 0
|
|
20
30
|
local window="metro-${PORT}"
|
|
21
31
|
local metro_tmux="$LOG_DIR/metro.tmux"
|
|
22
|
-
|
|
23
|
-
#
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if [ -n "${slot:-}" ] && tmux has-session -t "=mm-${slot}" 2>/dev/null; then
|
|
27
|
-
session="mm-${slot}"
|
|
28
|
-
# (2) the current session, but ONLY when we are actually inside a tmux client.
|
|
29
|
-
elif [ -n "${TMUX:-}" ]; then
|
|
30
|
-
session="$(tmux display-message -p '#S' 2>/dev/null || true)"
|
|
31
|
-
fi
|
|
32
|
+
# The orchestrator names the session; the harness only populates windows. No
|
|
33
|
+
# slot-number derivation — the name comes from the run-owned session ladder.
|
|
34
|
+
local session
|
|
35
|
+
session="$(resolve_run_tmux_session "$LOG_DIR")"
|
|
32
36
|
# No run-owned session → skip the viewer; no orphaned tail-window is left behind.
|
|
33
37
|
{ [ -n "$session" ] && tmux has-session -t "=$session" 2>/dev/null; } || return 0
|
|
38
|
+
# Hygiene: close stale metro-<port> windows in this session whose port has no
|
|
39
|
+
# live listener — a previous run's window left tailing a dead log. The current
|
|
40
|
+
# window is (re)created below, so skip it here.
|
|
41
|
+
if command -v lsof >/dev/null 2>&1; then
|
|
42
|
+
tmux list-windows -t "$session" -F '#{window_name}' 2>/dev/null | while IFS= read -r _win; do
|
|
43
|
+
[ "$_win" = "$window" ] && continue
|
|
44
|
+
case "$_win" in
|
|
45
|
+
metro-*) : ;;
|
|
46
|
+
*) continue ;;
|
|
47
|
+
esac
|
|
48
|
+
_p="${_win#metro-}"
|
|
49
|
+
case "$_p" in ''|*[!0-9]*) continue ;; esac
|
|
50
|
+
if ! lsof -nP -iTCP:"$_p" -sTCP:LISTEN -t >/dev/null 2>&1; then
|
|
51
|
+
tmux kill-window -t "${session}:${_win}" >/dev/null 2>&1 || true
|
|
52
|
+
fi
|
|
53
|
+
done
|
|
54
|
+
fi
|
|
34
55
|
tmux kill-window -t "${session}:${window}" >/dev/null 2>&1 || true
|
|
35
56
|
tmux new-window -d -t "$session" -n "$window" "exec tail -n +1 -F $(printf '%q' "$metro_log")"
|
|
36
57
|
printf '%s:%s\n' "$session" "$window" > "$metro_tmux"
|
|
@@ -51,6 +51,15 @@ esac
|
|
|
51
51
|
|
|
52
52
|
TARGET="$(cd "$TARGET" && pwd)"
|
|
53
53
|
|
|
54
|
+
# Pin the repo's Ruby for the iOS native build (pod/gem work): a cold checkout
|
|
55
|
+
# under nohup/tmux otherwise uses the system Ruby and fails native gem builds.
|
|
56
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
57
|
+
# shellcheck disable=SC1091
|
|
58
|
+
for _rb in "$SCRIPT_DIR/../shared/activate-repo-ruby.sh" "$SCRIPT_DIR/lib/activate-repo-ruby.sh"; do
|
|
59
|
+
[ -f "$_rb" ] && { . "$_rb"; break; }
|
|
60
|
+
done
|
|
61
|
+
unset _rb
|
|
62
|
+
|
|
54
63
|
# --- iOS helpers --------------------------------------------------------------
|
|
55
64
|
|
|
56
65
|
ios_app_installed() {
|
|
@@ -61,7 +70,11 @@ ios_app_installed() {
|
|
|
61
70
|
run_ios_native_build() {
|
|
62
71
|
local sim_target="$1" reason="$2"
|
|
63
72
|
printf '%s; installing with yarn start:ios (WATCHER_PORT=%s)\n' "$reason" "$PORT" >&2
|
|
64
|
-
(
|
|
73
|
+
(
|
|
74
|
+
cd "$TARGET"
|
|
75
|
+
command -v activate_repo_ruby_best_effort >/dev/null 2>&1 && activate_repo_ruby_best_effort "$TARGET"
|
|
76
|
+
WATCHER_PORT="$PORT" METRO_PORT="$PORT" IOS_SIMULATOR="$sim_target" EXPO_NO_TYPESCRIPT_SETUP=1 yarn start:ios 2>&1
|
|
77
|
+
)
|
|
65
78
|
}
|
|
66
79
|
|
|
67
80
|
ensure_ios_app_for_mode() {
|
|
@@ -35,7 +35,7 @@ fi
|
|
|
35
35
|
# pool/formula fallbacks) so we stop THIS slot's Metro, not the 8081 default.
|
|
36
36
|
if [ -z "$PORT" ]; then
|
|
37
37
|
# shellcheck disable=SC1091
|
|
38
|
-
. "$SCRIPT_DIR/../shared/resolve-
|
|
38
|
+
. "$SCRIPT_DIR/../shared/resolve-slot-ports.sh"
|
|
39
39
|
resolved_port="$(resolve_mobile_runtime_ports "$TARGET" 2>/dev/null | sed -n 's/^WATCHER_PORT=//p' | head -1 || true)"
|
|
40
40
|
PORT="${resolved_port:-8081}"
|
|
41
41
|
fi
|
|
@@ -12,6 +12,9 @@
|
|
|
12
12
|
# no native). The Metro-only remedy: native builds come from open-device.
|
|
13
13
|
set -euo pipefail
|
|
14
14
|
|
|
15
|
+
# Resolve SCRIPT_DIR before any cd (the script may be invoked by a relative path).
|
|
16
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
17
|
+
|
|
15
18
|
TARGET="$PWD"
|
|
16
19
|
MODE="full"
|
|
17
20
|
while [ "$#" -gt 0 ]; do
|
|
@@ -25,8 +28,19 @@ done
|
|
|
25
28
|
|
|
26
29
|
TARGET="$(cd "$TARGET" && pwd)"
|
|
27
30
|
cd "$TARGET"
|
|
31
|
+
|
|
32
|
+
# full mode runs native pods/jetify → pin the repo's Ruby so a cold checkout does
|
|
33
|
+
# not build iOS gems against the system Ruby. Best-effort (never blocks the setup).
|
|
34
|
+
# shellcheck disable=SC1091
|
|
35
|
+
for _rb in "$SCRIPT_DIR/../shared/activate-repo-ruby.sh" "$SCRIPT_DIR/lib/activate-repo-ruby.sh"; do
|
|
36
|
+
[ -f "$_rb" ] && { . "$_rb"; break; }
|
|
37
|
+
done
|
|
38
|
+
unset _rb
|
|
39
|
+
|
|
28
40
|
case "$MODE" in
|
|
29
|
-
full)
|
|
41
|
+
full)
|
|
42
|
+
command -v activate_repo_ruby_best_effort >/dev/null 2>&1 && activate_repo_ruby_best_effort "$TARGET"
|
|
43
|
+
exec yarn setup ;;
|
|
30
44
|
expo) exec yarn setup:expo --no-build-ios --no-build-android ;;
|
|
31
45
|
*) printf 'yarn-setup: unknown --mode: %s (want full|expo)\n' "$MODE" >&2; exit 2 ;;
|
|
32
46
|
esac
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# activate-repo-ruby.sh — pin the target repo's declared Ruby for non-interactive spawns
|
|
2
|
+
#
|
|
3
|
+
# iOS pod/gem work (yarn setup, native build) reads Ruby from PATH. Under tmux /
|
|
4
|
+
# nohup / a cold checkout the PATH is the system Ruby, which does not match the
|
|
5
|
+
# repo's .ruby-version and fails `bundle install` / `pod install` with native gem
|
|
6
|
+
# build errors. Sourced by the leaves that trigger pods so they run the pinned
|
|
7
|
+
# Ruby, mirroring activate-repo-node.sh for Node.
|
|
8
|
+
#
|
|
9
|
+
# On success sets REPO_RUBY_VERSION / REPO_RUBY_BIN / REPO_RUBY_BIN_DIR /
|
|
10
|
+
# REPO_RUBY_PATH_PREFIX and prepends the bin dir to PATH.
|
|
11
|
+
#
|
|
12
|
+
# Usage:
|
|
13
|
+
# . /path/to/activate-repo-ruby.sh
|
|
14
|
+
# activate_repo_ruby [target-dir] # 0 pinned; 1 none declared; 2 declared but unresolved
|
|
15
|
+
# activate_repo_ruby_best_effort [dir] # never fails the caller; warns + teaches on 2
|
|
16
|
+
|
|
17
|
+
_repo_ruby_matches_req() {
|
|
18
|
+
local req="$1" actual="${2:-}"
|
|
19
|
+
[ -n "$actual" ] || actual="$("$REPO_RUBY_BIN" -e 'print RUBY_VERSION' 2>/dev/null || true)"
|
|
20
|
+
[ -n "$actual" ] || return 1
|
|
21
|
+
[ "$actual" = "$req" ] && return 0
|
|
22
|
+
case "$req" in
|
|
23
|
+
*.*.*) return 1 ;;
|
|
24
|
+
*.*) [[ "$actual" == "$req."* ]] ;;
|
|
25
|
+
*) [[ "$actual" == "$req"* ]] ;;
|
|
26
|
+
esac
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
_repo_ruby_set_from_bin() {
|
|
30
|
+
local candidate="$1"
|
|
31
|
+
[ -n "$candidate" ] && [ -x "$candidate" ] || return 1
|
|
32
|
+
REPO_RUBY_BIN="$candidate"
|
|
33
|
+
REPO_RUBY_BIN_DIR="$(cd "$(dirname "$REPO_RUBY_BIN")" && pwd)"
|
|
34
|
+
REPO_RUBY_PATH_PREFIX="${REPO_RUBY_BIN_DIR}:"
|
|
35
|
+
export PATH="${REPO_RUBY_BIN_DIR}:${PATH}"
|
|
36
|
+
return 0
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
_repo_ruby_try_req() {
|
|
40
|
+
local target="$1" req="$2"
|
|
41
|
+
[ -n "$req" ] || return 1
|
|
42
|
+
REPO_RUBY_VERSION="$req"
|
|
43
|
+
|
|
44
|
+
if command -v asdf >/dev/null 2>&1; then
|
|
45
|
+
local asdf_root
|
|
46
|
+
asdf_root="$(asdf where ruby "$req" 2>/dev/null || true)"
|
|
47
|
+
if [ -n "$asdf_root" ] && _repo_ruby_set_from_bin "$asdf_root/bin/ruby" && _repo_ruby_matches_req "$req"; then
|
|
48
|
+
echo "[env] ruby $("$REPO_RUBY_BIN" -e 'print RUBY_VERSION') from .ruby-version/.tool-versions via asdf ($req)" >&2
|
|
49
|
+
return 0
|
|
50
|
+
fi
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
if command -v rbenv >/dev/null 2>&1; then
|
|
54
|
+
local rbenv_root
|
|
55
|
+
rbenv_root="$(rbenv root 2>/dev/null || true)"
|
|
56
|
+
if [ -n "$rbenv_root" ] && _repo_ruby_set_from_bin "$rbenv_root/versions/$req/bin/ruby" && _repo_ruby_matches_req "$req"; then
|
|
57
|
+
echo "[env] ruby $("$REPO_RUBY_BIN" -e 'print RUBY_VERSION') from .ruby-version via rbenv ($req)" >&2
|
|
58
|
+
return 0
|
|
59
|
+
fi
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
if command -v mise >/dev/null 2>&1; then
|
|
63
|
+
local mise_bin
|
|
64
|
+
mise_bin="$(mise x -C "$target" -- which ruby 2>/dev/null || true)"
|
|
65
|
+
if _repo_ruby_set_from_bin "$mise_bin" && _repo_ruby_matches_req "$req"; then
|
|
66
|
+
echo "[env] ruby $("$REPO_RUBY_BIN" -e 'print RUBY_VERSION') from .ruby-version via mise ($req)" >&2
|
|
67
|
+
return 0
|
|
68
|
+
fi
|
|
69
|
+
fi
|
|
70
|
+
return 1
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
activate_repo_ruby() {
|
|
74
|
+
local target="${1:-.}"
|
|
75
|
+
REPO_RUBY_VERSION=""
|
|
76
|
+
REPO_RUBY_BIN=""
|
|
77
|
+
REPO_RUBY_BIN_DIR=""
|
|
78
|
+
REPO_RUBY_PATH_PREFIX=""
|
|
79
|
+
|
|
80
|
+
local declared=false req=""
|
|
81
|
+
if [ -f "$target/.ruby-version" ]; then
|
|
82
|
+
declared=true
|
|
83
|
+
req="$(tr -d ' \t\r\n' < "$target/.ruby-version")"
|
|
84
|
+
req="${req#ruby-}"
|
|
85
|
+
_repo_ruby_try_req "$target" "$req" && return 0
|
|
86
|
+
fi
|
|
87
|
+
if [ -z "$req" ] && [ -f "$target/.tool-versions" ]; then
|
|
88
|
+
declared=true
|
|
89
|
+
req="$(awk '/^ruby / {print $2; exit}' "$target/.tool-versions")"
|
|
90
|
+
_repo_ruby_try_req "$target" "$req" && return 0
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
if [ "$declared" != true ]; then
|
|
94
|
+
return 1
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
# Declared already-on-PATH match (e.g. chruby/system already correct).
|
|
98
|
+
local path_ruby
|
|
99
|
+
path_ruby="$(command -v ruby 2>/dev/null || true)"
|
|
100
|
+
if [ -n "$req" ] && _repo_ruby_set_from_bin "$path_ruby" && _repo_ruby_matches_req "$req"; then
|
|
101
|
+
echo "[env] ruby $("$REPO_RUBY_BIN" -e 'print RUBY_VERSION') already on PATH ($req)" >&2
|
|
102
|
+
return 0
|
|
103
|
+
fi
|
|
104
|
+
|
|
105
|
+
REPO_RUBY_BIN=""
|
|
106
|
+
REPO_RUBY_BIN_DIR=""
|
|
107
|
+
REPO_RUBY_PATH_PREFIX=""
|
|
108
|
+
return 2
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
# Best-effort: never fails the caller. On a declared-but-unresolved Ruby, warn with
|
|
112
|
+
# a teaching hint so a subsequent pod/gem failure is diagnosable (system Ruby).
|
|
113
|
+
activate_repo_ruby_best_effort() {
|
|
114
|
+
local target="${1:-.}"
|
|
115
|
+
# Guard the call so a non-zero return (no version / unresolved) never trips the
|
|
116
|
+
# caller's set -e before we can inspect the code.
|
|
117
|
+
local rc=0
|
|
118
|
+
activate_repo_ruby "$target" || rc=$?
|
|
119
|
+
if [ "$rc" -eq 2 ]; then
|
|
120
|
+
echo "[env] repo pins ruby ${REPO_RUBY_VERSION:-?} (.ruby-version/.tool-versions) but no installed manager provided it; using system ruby." >&2
|
|
121
|
+
echo " Next: install it — asdf install ruby ${REPO_RUBY_VERSION:-<version>} (or rbenv install) — then re-run, so iOS gems build against the pinned Ruby" >&2
|
|
122
|
+
fi
|
|
123
|
+
return 0
|
|
124
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# open-log-window.sh — (re)open a read-only tmux tail window for a dev-server log
|
|
3
|
+
# in the run-owned session, WITHOUT touching the dev-server process. Idempotent:
|
|
4
|
+
# an existing same-name window is replaced with a fresh tail. Used by
|
|
5
|
+
# `mm-harness logs --window` to recover a window that was closed while Metro/webpack
|
|
6
|
+
# keeps running.
|
|
7
|
+
#
|
|
8
|
+
# Args: --window <name> --log <path> [--runtime-dir <dir>]
|
|
9
|
+
# Exit: 0 opened; 1 no run-owned session / no tmux / log missing (teaches); 2 bad args.
|
|
10
|
+
set -euo pipefail
|
|
11
|
+
|
|
12
|
+
WINDOW=""
|
|
13
|
+
LOG=""
|
|
14
|
+
RUNTIME_DIR=""
|
|
15
|
+
require_value() { [ "$#" -ge 2 ] || { echo "Missing value for $1" >&2; exit 2; }; }
|
|
16
|
+
while [ "$#" -gt 0 ]; do
|
|
17
|
+
case "$1" in
|
|
18
|
+
--window) require_value "$@"; WINDOW="$2"; shift 2 ;;
|
|
19
|
+
--log) require_value "$@"; LOG="$2"; shift 2 ;;
|
|
20
|
+
--runtime-dir) require_value "$@"; RUNTIME_DIR="$2"; shift 2 ;;
|
|
21
|
+
-h|--help) echo "Usage: open-log-window.sh --window <name> --log <path> [--runtime-dir <dir>]"; exit 0 ;;
|
|
22
|
+
*) echo "Unknown arg: $1" >&2; exit 2 ;;
|
|
23
|
+
esac
|
|
24
|
+
done
|
|
25
|
+
[ -n "$WINDOW" ] && [ -n "$LOG" ] || { echo "open-log-window: --window and --log are required" >&2; exit 2; }
|
|
26
|
+
|
|
27
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
28
|
+
# shellcheck disable=SC1091
|
|
29
|
+
for _src in "$SCRIPT_DIR/tmux-session.sh" "$SCRIPT_DIR/lib/tmux-session.sh"; do
|
|
30
|
+
[ -f "$_src" ] && { . "$_src"; break; }
|
|
31
|
+
done
|
|
32
|
+
unset _src
|
|
33
|
+
|
|
34
|
+
if ! command -v tmux >/dev/null 2>&1; then
|
|
35
|
+
echo "open-log-window: tmux is not installed; cannot open a log window." >&2
|
|
36
|
+
echo " Next: tail the log directly — mm-harness logs --full" >&2
|
|
37
|
+
exit 1
|
|
38
|
+
fi
|
|
39
|
+
if [ ! -f "$LOG" ]; then
|
|
40
|
+
echo "open-log-window: no log at $LOG (dev server not started for this checkout)." >&2
|
|
41
|
+
echo " Next: start it — mm-harness launch" >&2
|
|
42
|
+
exit 1
|
|
43
|
+
fi
|
|
44
|
+
session=""
|
|
45
|
+
command -v resolve_run_tmux_session >/dev/null 2>&1 && session="$(resolve_run_tmux_session "$RUNTIME_DIR")"
|
|
46
|
+
if [ -z "$session" ] || ! tmux has-session -t "=$session" 2>/dev/null; then
|
|
47
|
+
echo "open-log-window: no run-owned tmux session to place the window in." >&2
|
|
48
|
+
echo " Next: run inside your slot's tmux session, or set RECIPE_TMUX_SESSION=<session>, then retry" >&2
|
|
49
|
+
exit 1
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
tmux kill-window -t "${session}:${WINDOW}" 2>/dev/null || true
|
|
53
|
+
log_q="$(printf '%q' "$LOG")"
|
|
54
|
+
tmux new-window -d -t "$session" -n "$WINDOW" "exec tail -n +1 -F $log_q"
|
|
55
|
+
echo "log window → tmux ${session}:${WINDOW}" >&2
|