@deeeed/metamask-harness 0.5.0 → 0.5.1
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 +17 -3
- package/adapters/extension/inject.mjs +2 -0
- package/adapters/extension/refresh-build.sh +2 -2
- package/adapters/extension/start-watch.sh +2 -2
- package/adapters/manifest.json +19 -3
- package/adapters/mobile/start-metro.sh +1 -1
- package/adapters/mobile/verify.sh +1 -1
- package/adapters/shared/activate-repo-node.sh +1 -1
- package/adapters/shared/cli-ux.sh +24 -23
- package/adapters/shared/log-tui.mjs +4 -4
- package/adapters/shared/open-debug.mjs +1 -1
- package/adapters/shared/resolve-farmslot-ports-core.mjs +196 -0
- package/adapters/shared/resolve-farmslot-ports.mjs +20 -0
- package/adapters/shared/resolve-farmslot-ports.sh +19 -126
- package/dist/adapters/mobile/runtime-decision.js +3 -1
- package/dist/adapters/resolve-farmslot-ports.js +22 -0
- package/dist/adapters/slot-ports.js +17 -23
- package/library/actions/core/perps/_controller.mjs +1 -1
- package/library/actions/extension/platform/cdp.mjs +1 -1
- package/library/actions/extension/wallet/ensure_unlocked.mjs +1 -1
- package/library/actions/harness-exports.mjs +27 -0
- package/library/actions/mobile/wallet/ensure_unlocked.mjs +1 -1
- package/library/actions/mobile/wallet/setup.mjs +1 -1
- package/package.json +1 -1
- package/src/adapters/mobile/runtime-decision.ts +8 -1
- package/src/adapters/resolve-farmslot-ports.ts +13 -0
- package/src/adapters/slot-ports.ts +17 -24
package/CHANGELOG.md
CHANGED
|
@@ -1,13 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.5.
|
|
3
|
+
## 0.5.1 - 2026-07-05
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Fresh-install hotfixes found in live validation of 0.5.0.
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- **First launch on a provisioned slot targets the slot's own simulator** — the mobile resolution ladder now reads the `runway-provision.json` baseline (simulator name/udid, watcher port, slot id) when `agentic-runtime.json` doesn't exist yet, and boots that simulator. Previously a provisioned-but-unprepared slot degraded to the simctl `booted` alias, missed the installed dev client, and guessed the wrong Metro port.
|
|
9
|
+
- **`call` works from published installs** — library actions load harness helpers through a dist-preferring bridge instead of importing `src/index.ts` (node refuses to type-strip `.ts` under `node_modules`). The bridge is deliberately narrow (paths + run-recording) to avoid an import cycle through `adapters.ts`.
|
|
10
|
+
- **deps-not-ready guidance matches the caller's state** — on a checkout without node_modules the taught command now leads with `yarn install --immutable` (yarn cannot run scripts at all on a bare checkout), and always ends with the launch re-run.
|
|
6
11
|
|
|
12
|
+
## Unreleased
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
- **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
|
+
|
|
17
|
+
## 0.5.0 - 2026-07-05
|
|
18
|
+
Minor release: thin Runway provisioning (harness-owned slot provisioning with a shared artifact cache) plus compiled-dist packaging.
|
|
7
19
|
### Added
|
|
8
20
|
- **`mm-harness provision runway`** — provisions a mobile slot with exactly: resolve the latest Runway (expo-dev-build) artifact, download it ONCE into a shared machine cache (`~/.cache/metamask-harness/runway/<runId>/`, sha256+size integrity, corrupt entries re-downloaded, never a slot failure), create the simulator if missing, install the dev client on it. Zero yarn/deps/Metro — those stay dispatch-time (lazy, presence-authoritative readiness unchanged). `install --runway` is the overlay-path equivalent (covered by its own contract test). Baseline recorded so doctor reports provisioned-but-deps-pending as a normal state.
|
|
9
21
|
- **Compiled `dist/` for published installs** — the npm tarball ships esbuild-compiled JS; `npm i -g` installs run the dist entry (~0.21s cold vs ~0.37s tsx). Source checkouts without `dist/` keep the tsx dev path; `bin/mm-harness` prefers dist when present. Packaging contract test (pack → global install → dist entry runs).
|
|
10
|
-
|
|
11
22
|
### Changed
|
|
12
23
|
- Runway provisioning callers (farm installer, mobile-farm `runway` prepare profile) delegate to the harness; the profile no longer runs a deps phase and a contract test enforces that.
|
|
13
24
|
|
|
@@ -50,6 +61,9 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
|
|
|
50
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.
|
|
51
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`.
|
|
52
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
|
+
|
|
53
67
|
### Fixed
|
|
54
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.
|
|
55
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.
|
|
@@ -108,6 +108,8 @@ copyFile(path.join(runnerDir, 'adapters/extension/ensure-browser.sh'), path.join
|
|
|
108
108
|
copyFile(path.join(runnerDir, 'adapters/shared/harness-path.sh'), path.join(harnessDir, 'scripts/lib/harness-path.sh'));
|
|
109
109
|
copyFile(path.join(runnerDir, 'adapters/shared/activate-repo-node.sh'), path.join(harnessDir, 'scripts/lib/activate-repo-node.sh'));
|
|
110
110
|
copyFile(path.join(runnerDir, 'adapters/shared/resolve-farmslot-ports.sh'), path.join(harnessDir, 'scripts/lib/resolve-farmslot-ports.sh'));
|
|
111
|
+
copyFile(path.join(runnerDir, 'adapters/shared/resolve-farmslot-ports.mjs'), path.join(harnessDir, 'scripts/lib/resolve-farmslot-ports.mjs'));
|
|
112
|
+
copyFile(path.join(runnerDir, 'adapters/shared/resolve-farmslot-ports-core.mjs'), path.join(harnessDir, 'scripts/lib/resolve-farmslot-ports-core.mjs'));
|
|
111
113
|
copyFile(path.join(runnerDir, 'adapters/shared/path-defaults.json'), path.join(harnessDir, 'scripts/lib/path-defaults.json'));
|
|
112
114
|
copyFile(path.join(runnerDir, 'adapters/shared/json-field.sh'), path.join(harnessDir, 'scripts/lib/json-field.sh'));
|
|
113
115
|
copyFile(path.join(runnerDir, 'adapters/shared/log-tui.mjs'), path.join(harnessDir, 'scripts/lib/log-tui.mjs'));
|
|
@@ -97,7 +97,7 @@ elif command -v activate_repo_node >/dev/null 2>&1; then
|
|
|
97
97
|
exit 1
|
|
98
98
|
}
|
|
99
99
|
else
|
|
100
|
-
echo "[refresh] activate-repo-node.sh missing from harness; run:
|
|
100
|
+
echo "[refresh] activate-repo-node.sh missing from harness; run: mm-harness install" >&2
|
|
101
101
|
exit 1
|
|
102
102
|
fi
|
|
103
103
|
|
|
@@ -186,5 +186,5 @@ kill "$BUILD_PID" 2>/dev/null || true
|
|
|
186
186
|
wait "$BUILD_PID" 2>/dev/null || true
|
|
187
187
|
trap - EXIT
|
|
188
188
|
|
|
189
|
-
echo "[refresh] Build refreshed and frozen — run:
|
|
189
|
+
echo "[refresh] Build refreshed and frozen — run: mm-harness launch"
|
|
190
190
|
echo "[refresh] Log kept at: $LOG"
|
|
@@ -82,7 +82,7 @@ elif command -v activate_repo_node >/dev/null 2>&1; then
|
|
|
82
82
|
exit 1
|
|
83
83
|
}
|
|
84
84
|
else
|
|
85
|
-
echo "[recipe-harness] activate-repo-node.sh missing from harness; run:
|
|
85
|
+
echo "[recipe-harness] activate-repo-node.sh missing from harness; run: mm-harness install" >&2
|
|
86
86
|
exit 1
|
|
87
87
|
fi
|
|
88
88
|
if [ -z "$WATCHER_PORT" ]; then
|
|
@@ -233,7 +233,7 @@ for _lib in "$SCRIPT_DIR/lib" "$SCRIPT_DIR/../shared"; do
|
|
|
233
233
|
done
|
|
234
234
|
unset _lib
|
|
235
235
|
echo "[recipe-harness] Starting yarn start (watcher-port=${WATCHER_PORT:-default})"
|
|
236
|
-
echo "[recipe-harness] compact build view - full log: $watch_log (
|
|
236
|
+
echo "[recipe-harness] compact build view - full log: $watch_log (mm-harness logs --full)"
|
|
237
237
|
started_pid=""
|
|
238
238
|
# Launch the watcher detached writing to the log; the tmux window is a read-only tail viewer.
|
|
239
239
|
path_prefix="${REPO_NODE_PATH_PREFIX:-}"
|
package/adapters/manifest.json
CHANGED
|
@@ -308,12 +308,28 @@
|
|
|
308
308
|
},
|
|
309
309
|
{
|
|
310
310
|
"id": "lib/resolve-farmslot-ports",
|
|
311
|
-
"entry": "adapters/shared/resolve-farmslot-ports.
|
|
312
|
-
"kind": "
|
|
311
|
+
"entry": "adapters/shared/resolve-farmslot-ports.mjs",
|
|
312
|
+
"kind": "node",
|
|
313
313
|
"purpose": "Resolve CDP + watcher ports from farmslot pool JSON by repo path, else 6660+N / 9010+N.",
|
|
314
|
-
"inputs": "resolve_extension_runtime_ports <repo>",
|
|
314
|
+
"inputs": "resolve-farmslot-ports.mjs resolve_extension_runtime_ports <repo>",
|
|
315
315
|
"outputs": "CDP_PORT / WATCHER_PORT / SLOT_ID lines on stdout"
|
|
316
316
|
},
|
|
317
|
+
{
|
|
318
|
+
"id": "lib/resolve-farmslot-ports-core",
|
|
319
|
+
"entry": "adapters/shared/resolve-farmslot-ports-core.mjs",
|
|
320
|
+
"kind": "lib",
|
|
321
|
+
"purpose": "Self-contained pool/context/formula port resolution imported by the node leaf and TS surface.",
|
|
322
|
+
"inputs": "ESM import from resolve-farmslot-ports.mjs or resolve-farmslot-ports.ts",
|
|
323
|
+
"outputs": "resolveFarmslotPortsByRepo and related functions"
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
"id": "lib/resolve-farmslot-ports-sh",
|
|
327
|
+
"entry": "adapters/shared/resolve-farmslot-ports.sh",
|
|
328
|
+
"kind": "lib",
|
|
329
|
+
"purpose": "Bash-compat sourced wrapper over resolve-farmslot-ports.mjs for stop-metro and legacy callers.",
|
|
330
|
+
"inputs": "source + resolve_* / apply_resolved_* function calls",
|
|
331
|
+
"outputs": "KEY=VALUE lines on stdout; env vars via apply_resolved_*"
|
|
332
|
+
},
|
|
317
333
|
{
|
|
318
334
|
"id": "lib/sync-wallet-fixture",
|
|
319
335
|
"entry": "adapters/shared/sync-wallet-fixture.sh",
|
|
@@ -102,7 +102,7 @@ CLEAR_SUFFIX=""
|
|
|
102
102
|
|
|
103
103
|
printf 'Starting Metro on port %s (workers=%s%s)\n' \
|
|
104
104
|
"$PORT" "$METRO_WORKERS" "${CLEAR:+, clear}" >&2
|
|
105
|
-
printf '(Full log: %s —
|
|
105
|
+
printf '(Full log: %s — mm-harness logs | mm-harness logs --full)\n' "$LOG_FILE" >&2
|
|
106
106
|
|
|
107
107
|
# Metro runs detached, writing to the log. The tmux window is a read-only tail.
|
|
108
108
|
(
|
|
@@ -296,7 +296,7 @@ if (checked === 0) {
|
|
|
296
296
|
const parts = [];
|
|
297
297
|
if (drifted.length) parts.push('behind runner: ' + drifted.join(', '));
|
|
298
298
|
if (missing.length) parts.push('absent in repo: ' + missing.join(', '));
|
|
299
|
-
process.stdout.write(JSON.stringify({ name, status: 'warn', detail: parts.join('; ') + ' — rerun mm-harness install --force-overlay or
|
|
299
|
+
process.stdout.write(JSON.stringify({ name, status: 'warn', detail: parts.join('; ') + ' — rerun mm-harness install --force-overlay or mm-harness fixtures sync to refresh the in-repo HUD/AgenticService from the runner' }));
|
|
300
300
|
} else {
|
|
301
301
|
process.stdout.write(JSON.stringify({ name, status: 'pass' }));
|
|
302
302
|
}
|
|
@@ -134,7 +134,7 @@ activate_repo_node() {
|
|
|
134
134
|
require_repo_node() {
|
|
135
135
|
local target="${1:-.}"
|
|
136
136
|
if ! command -v activate_repo_node >/dev/null 2>&1; then
|
|
137
|
-
echo "[env] activate-repo-node.sh is not sourced; reinstall the harness (
|
|
137
|
+
echo "[env] activate-repo-node.sh is not sourced; reinstall the harness (mm-harness install)" >&2
|
|
138
138
|
return 1
|
|
139
139
|
fi
|
|
140
140
|
activate_repo_node "$target" || {
|
|
@@ -67,7 +67,7 @@ recipe_instant_hint() {
|
|
|
67
67
|
local message="$1"
|
|
68
68
|
[ "${RECIPE_PROGRESS_JSON:-}" = "1" ] && return 0
|
|
69
69
|
[ -t 2 ] || return 0
|
|
70
|
-
local prefix="${RECIPE_CLI_PREFIX:-
|
|
70
|
+
local prefix="${RECIPE_CLI_PREFIX:-mm-harness}"
|
|
71
71
|
printf '%s: %s\n' \
|
|
72
72
|
"$(recipe_color label "$prefix")" \
|
|
73
73
|
"$(recipe_color dim "$message")" >&2
|
|
@@ -92,7 +92,7 @@ recipe_done() {
|
|
|
92
92
|
recipe_failure_playbook() {
|
|
93
93
|
local verb="$1" reason="$2"
|
|
94
94
|
shift 2
|
|
95
|
-
local log_path="" artifact="" prefix="${RECIPE_CLI_PREFIX:-
|
|
95
|
+
local log_path="" artifact="" prefix="${RECIPE_CLI_PREFIX:-mm-harness}"
|
|
96
96
|
while [ "$#" -gt 0 ]; do
|
|
97
97
|
case "$1" in
|
|
98
98
|
--log) log_path="$2"; shift 2 ;;
|
|
@@ -106,31 +106,32 @@ recipe_failure_playbook() {
|
|
|
106
106
|
"$(recipe_color cmd "$verb")" \
|
|
107
107
|
"$(recipe_color err failed)" \
|
|
108
108
|
"$(recipe_color warn "$reason")" >&2
|
|
109
|
-
printf '%s\n' "$(recipe_color label next:)" >&2
|
|
109
|
+
printf '%s\n' "$(recipe_color label "next:")" >&2
|
|
110
110
|
case "$verb" in
|
|
111
111
|
rebuild|up|watch)
|
|
112
|
-
printf ' %s\n' "$(recipe_color cmd "
|
|
113
|
-
printf ' %s\n' "$(recipe_color cmd "
|
|
114
|
-
printf ' %s\n' "$(recipe_color cmd "
|
|
115
|
-
printf ' %s\n' "$(recipe_color cmd "
|
|
112
|
+
printf ' %s\n' "$(recipe_color cmd "mm-harness logs")" >&2
|
|
113
|
+
printf ' %s\n' "$(recipe_color cmd "mm-harness logs --full")" >&2
|
|
114
|
+
printf ' %s\n' "$(recipe_color cmd "mm-harness doctor")" >&2
|
|
115
|
+
printf ' %s\n' "$(recipe_color cmd "mm-harness install")" >&2
|
|
116
116
|
;;
|
|
117
117
|
refresh)
|
|
118
|
-
printf ' %s\n' "$(recipe_color cmd "
|
|
119
|
-
printf ' %s\n' "$(recipe_color cmd "
|
|
120
|
-
printf ' %s\n' "$(recipe_color cmd "
|
|
118
|
+
printf ' %s\n' "$(recipe_color cmd "mm-harness launch")" >&2
|
|
119
|
+
printf ' %s\n' "$(recipe_color cmd "mm-harness logs")" >&2
|
|
120
|
+
printf ' %s\n' "$(recipe_color cmd "mm-harness launch --build")" >&2
|
|
121
121
|
;;
|
|
122
122
|
metro|ios|android|setup-wallet)
|
|
123
|
-
printf ' %s\n' "$(recipe_color cmd "
|
|
124
|
-
printf ' %s\n' "$(recipe_color cmd "
|
|
125
|
-
printf ' %s\n' "$(recipe_color cmd "
|
|
123
|
+
printf ' %s\n' "$(recipe_color cmd "mm-harness logs")" >&2
|
|
124
|
+
printf ' %s\n' "$(recipe_color cmd "mm-harness logs --full")" >&2
|
|
125
|
+
printf ' %s\n' "$(recipe_color cmd "mm-harness launch")" >&2
|
|
126
126
|
;;
|
|
127
127
|
*)
|
|
128
|
-
printf ' %s\n' "$(recipe_color cmd "
|
|
129
|
-
printf ' %s\n' "$(recipe_color cmd "
|
|
128
|
+
printf ' %s\n' "$(recipe_color cmd "mm-harness doctor")" >&2
|
|
129
|
+
printf ' %s\n' "$(recipe_color cmd "mm-harness logs")" >&2
|
|
130
130
|
;;
|
|
131
131
|
esac
|
|
132
132
|
[ -n "$log_path" ] && printf '%s\n' "$(recipe_color path "log: $log_path")" >&2
|
|
133
133
|
[ -n "$artifact" ] && printf '%s\n' "$(recipe_color path "artifact: $artifact")" >&2
|
|
134
|
+
return 0
|
|
134
135
|
}
|
|
135
136
|
|
|
136
137
|
recipe_check_harness_stale() {
|
|
@@ -152,12 +153,12 @@ recipe_check_harness_stale() {
|
|
|
152
153
|
|
|
153
154
|
recipe_warn_harness_stale() {
|
|
154
155
|
local adapter="$1" root="$2" harness_root="$3" runner_root="$4"
|
|
155
|
-
local prefix="${RECIPE_CLI_PREFIX:-
|
|
156
|
+
local prefix="${RECIPE_CLI_PREFIX:-mm-harness}"
|
|
156
157
|
if recipe_check_harness_stale "$adapter" "$root" "$harness_root" "$runner_root"; then
|
|
157
158
|
printf '%s: %s - run: %s\n' \
|
|
158
159
|
"$(recipe_color label "$prefix")" \
|
|
159
160
|
"$(recipe_color warn 'harness scripts are stale')" \
|
|
160
|
-
"$(recipe_color cmd "
|
|
161
|
+
"$(recipe_color cmd "mm-harness install")" >&2
|
|
161
162
|
return 1
|
|
162
163
|
fi
|
|
163
164
|
return 0
|
|
@@ -190,7 +191,7 @@ recipe_try_farmslot_fixture_sync() {
|
|
|
190
191
|
recipe_sync_wallet_fixture() {
|
|
191
192
|
local root="$1" runtime_dir="$2" cdp_port="${3:-}" seed_script="${4:-}" farmslot_slot="${5:-}"
|
|
192
193
|
local canonical="$root/$runtime_dir/wallet-fixture.json"
|
|
193
|
-
local candidate resolved prefix="${RECIPE_CLI_PREFIX:-
|
|
194
|
+
local candidate resolved prefix="${RECIPE_CLI_PREFIX:-mm-harness}"
|
|
194
195
|
|
|
195
196
|
mkdir -p "$root/$runtime_dir"
|
|
196
197
|
if [ -f "$canonical" ]; then
|
|
@@ -239,10 +240,10 @@ recipe_sync_wallet_fixture() {
|
|
|
239
240
|
recipe_speed_ladder() {
|
|
240
241
|
cat >&2 <<'EOF'
|
|
241
242
|
Speed ladder:
|
|
242
|
-
FAST (seconds):
|
|
243
|
-
SLOW (minutes):
|
|
244
|
-
INSPECT:
|
|
245
|
-
PROOF:
|
|
246
|
-
SYNC CONTEXT:
|
|
243
|
+
FAST (seconds): mm-harness launch (extension quick relaunch)
|
|
244
|
+
SLOW (minutes): mm-harness launch --build
|
|
245
|
+
INSPECT: mm-harness doctor | mm-harness logs | mm-harness logs --full | mm-harness debug
|
|
246
|
+
PROOF: mm-harness run <recipe.json>
|
|
247
|
+
SYNC CONTEXT: mm-harness fixtures sync
|
|
247
248
|
EOF
|
|
248
249
|
}
|
|
@@ -153,9 +153,9 @@ function readNewContent(file, offset) {
|
|
|
153
153
|
|
|
154
154
|
function renderHeader(label, logPath, title) {
|
|
155
155
|
return [
|
|
156
|
-
`${color('label', '
|
|
157
|
-
`${color('label', '
|
|
158
|
-
`${color('label', '
|
|
156
|
+
`${color('label', 'mm-harness', { stream: process.stderr })} ${color('dim', '|', { stream: process.stderr })} ${color('bold', title || label, { stream: process.stderr })}`,
|
|
157
|
+
`${color('label', 'mm-harness', { stream: process.stderr })} ${color('dim', '|', { stream: process.stderr })} ${color('path', `full log: ${logPath}`, { stream: process.stderr })}`,
|
|
158
|
+
`${color('label', 'mm-harness', { stream: process.stderr })} ${color('dim', '|', { stream: process.stderr })} ${color('comment', 'full stream: mm-harness logs --full', { stream: process.stderr })}`,
|
|
159
159
|
'',
|
|
160
160
|
];
|
|
161
161
|
}
|
|
@@ -232,7 +232,7 @@ async function watchLog(options) {
|
|
|
232
232
|
renderCompact({ label, logPath, events, quiet: mode === 'quiet' });
|
|
233
233
|
if (mode !== 'quiet') {
|
|
234
234
|
const elapsed = Math.round((Date.now() - started) / 1000);
|
|
235
|
-
process.stderr.write(`${color('label', '
|
|
235
|
+
process.stderr.write(`${color('label', 'mm-harness', { stream: process.stderr })} ${color('dim', '|', { stream: process.stderr })} ${color('ok', `build complete (${elapsed}s)`, { stream: process.stderr })}\n`);
|
|
236
236
|
}
|
|
237
237
|
return 0;
|
|
238
238
|
}
|
|
@@ -221,7 +221,7 @@ async function openExtensionDevtools(cdpPort, targetKind, shouldOpen) {
|
|
|
221
221
|
error: 'cdp-unreachable',
|
|
222
222
|
inspectUrl,
|
|
223
223
|
configureHint,
|
|
224
|
-
hint: `Start the extension runtime first (
|
|
224
|
+
hint: `Start the extension runtime first (mm-harness launch). Then open chrome://inspect and add ${configureHint}.`,
|
|
225
225
|
detail: error instanceof Error ? error.message : String(error),
|
|
226
226
|
};
|
|
227
227
|
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
// Farmslot pool + slot-suffix port resolution — pure JS for runner and overlay leaves.
|
|
2
|
+
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
|
|
8
|
+
const coreDir = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
|
|
10
|
+
function pathDefault(key) {
|
|
11
|
+
for (const candidate of [
|
|
12
|
+
path.join(coreDir, 'path-defaults.json'),
|
|
13
|
+
path.join(coreDir, '../../adapters/shared/path-defaults.json'),
|
|
14
|
+
]) {
|
|
15
|
+
if (!fs.existsSync(candidate)) continue;
|
|
16
|
+
const value = JSON.parse(fs.readFileSync(candidate, 'utf8'))[key];
|
|
17
|
+
if (value) return value;
|
|
18
|
+
}
|
|
19
|
+
throw new Error(`Missing path default: ${key}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function recipeRuntimeDir() {
|
|
23
|
+
return process.env.RECIPE_RUNTIME_DIR || pathDefault('recipeRuntimeDir');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function formatKvLines(kv) {
|
|
27
|
+
const lines = [];
|
|
28
|
+
if (kv.CDP_PORT !== undefined) lines.push(`CDP_PORT=${kv.CDP_PORT}`);
|
|
29
|
+
if (kv.WATCHER_PORT !== undefined) lines.push(`WATCHER_PORT=${kv.WATCHER_PORT}`);
|
|
30
|
+
if (kv.SLOT_ID) lines.push(`SLOT_ID=${kv.SLOT_ID}`);
|
|
31
|
+
if (kv.IOS_SIMULATOR) lines.push(`IOS_SIMULATOR=${kv.IOS_SIMULATOR}`);
|
|
32
|
+
return lines.length ? `${lines.join('\n')}\n` : '';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function realRepoPath(repo) {
|
|
36
|
+
if (!repo) return null;
|
|
37
|
+
try {
|
|
38
|
+
return fs.realpathSync(repo);
|
|
39
|
+
} catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function inferSlotSuffix(repo) {
|
|
45
|
+
const base = path.basename(repo);
|
|
46
|
+
const m = /-(\d+)$/u.exec(base);
|
|
47
|
+
return m ? m[1] : null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function scoreSlot(slot, slotSuffix) {
|
|
51
|
+
const resources = slot.resources ?? {};
|
|
52
|
+
const cdp = resources.browser?.cdp_port;
|
|
53
|
+
const port = resources['dev-server']?.port;
|
|
54
|
+
const session = slot.session ?? '';
|
|
55
|
+
const slotId = slot.id ?? '';
|
|
56
|
+
let score = 0;
|
|
57
|
+
if (cdp !== undefined) score += 100;
|
|
58
|
+
if (port !== undefined) score += 10;
|
|
59
|
+
if (slotSuffix) {
|
|
60
|
+
if (session === `mme-${slotSuffix}`) score += 50;
|
|
61
|
+
if (slotId.endsWith(`mme-${slotSuffix}`) || slotId.includes(`-mme-${slotSuffix}`)) score += 40;
|
|
62
|
+
}
|
|
63
|
+
if (!slotId.toLowerCase().includes('demo')) score += 5;
|
|
64
|
+
return [score, cdp, port, slotId];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function resolveFarmslotPortsByRepo(repo) {
|
|
68
|
+
const realRepo = realRepoPath(repo);
|
|
69
|
+
if (!realRepo) return null;
|
|
70
|
+
|
|
71
|
+
const slotSuffix = inferSlotSuffix(realRepo);
|
|
72
|
+
const roots = [];
|
|
73
|
+
if (process.env.FARMSLOT_ROOT) roots.push(process.env.FARMSLOT_ROOT);
|
|
74
|
+
roots.push(path.join(os.homedir(), 'dev', 'farmslot'));
|
|
75
|
+
|
|
76
|
+
for (const root of roots) {
|
|
77
|
+
if (!root || !fs.existsSync(path.join(root, 'pool'))) continue;
|
|
78
|
+
const poolDir = path.join(root, 'pool');
|
|
79
|
+
let best = null;
|
|
80
|
+
|
|
81
|
+
for (const name of fs.readdirSync(poolDir).sort()) {
|
|
82
|
+
if (!name.endsWith('.json') || name.includes('.bak.')) continue;
|
|
83
|
+
let data;
|
|
84
|
+
try {
|
|
85
|
+
data = JSON.parse(fs.readFileSync(path.join(poolDir, name), 'utf8'));
|
|
86
|
+
} catch {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
for (const slot of data.slots ?? []) {
|
|
90
|
+
const slotRepo = slot.repo ?? '';
|
|
91
|
+
if (!slotRepo) continue;
|
|
92
|
+
let slotReal;
|
|
93
|
+
try {
|
|
94
|
+
slotReal = fs.realpathSync(slotRepo);
|
|
95
|
+
} catch {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (slotReal !== realRepo) continue;
|
|
99
|
+
const scored = scoreSlot(slot, slotSuffix);
|
|
100
|
+
if (!best || scored[0] > best[0]) best = scored;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (best) {
|
|
105
|
+
const [, cdp, port, slotId] = best;
|
|
106
|
+
return formatKvLines({
|
|
107
|
+
...(cdp !== undefined ? { CDP_PORT: cdp } : {}),
|
|
108
|
+
...(port !== undefined ? { WATCHER_PORT: port } : {}),
|
|
109
|
+
...(slotId ? { SLOT_ID: slotId } : {}),
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function resolveDefaultExtensionPorts(repo) {
|
|
117
|
+
const n = inferSlotSuffix(repo);
|
|
118
|
+
if (!n) return null;
|
|
119
|
+
return formatKvLines({
|
|
120
|
+
CDP_PORT: 6660 + Number(n),
|
|
121
|
+
WATCHER_PORT: 9010 + Number(n),
|
|
122
|
+
SLOT_ID: `local-extension-${n}`,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function resolveExtensionRuntimePorts(repo) {
|
|
127
|
+
return resolveFarmslotPortsByRepo(repo) ?? resolveDefaultExtensionPorts(repo) ?? '';
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function resolveMobileSlotDefaults(repo) {
|
|
131
|
+
const n = inferSlotSuffix(repo);
|
|
132
|
+
if (!n) return null;
|
|
133
|
+
return formatKvLines({
|
|
134
|
+
WATCHER_PORT: 8060 + Number(n),
|
|
135
|
+
IOS_SIMULATOR: `mm-${n}`,
|
|
136
|
+
SLOT_ID: `local-mobile-${n}`,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function resolveMobileRuntimeContext(repo) {
|
|
141
|
+
const ctxPath = path.join(repo, recipeRuntimeDir(), 'agentic-runtime.json');
|
|
142
|
+
if (fs.existsSync(ctxPath)) {
|
|
143
|
+
try {
|
|
144
|
+
const c = JSON.parse(fs.readFileSync(ctxPath, 'utf8'));
|
|
145
|
+
if (c.simulator || c.metroPort) {
|
|
146
|
+
return formatKvLines({
|
|
147
|
+
...(c.metroPort !== undefined ? { WATCHER_PORT: c.metroPort } : {}),
|
|
148
|
+
...(c.simulator ? { IOS_SIMULATOR: c.simulator } : {}),
|
|
149
|
+
...(c.slotId ? { SLOT_ID: c.slotId } : {}),
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
} catch {
|
|
153
|
+
/* unreadable context falls through to the provision baseline */
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return resolveMobileProvisionBaseline(repo);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// A provisioned-but-unprepared slot has no agentic-runtime.json yet, but the
|
|
160
|
+
// provision baseline records the same authoritative simulator/port identity —
|
|
161
|
+
// without it, a fresh slot's first launch degrades to the simctl `booted`
|
|
162
|
+
// alias and misses the installed dev client entirely.
|
|
163
|
+
function resolveMobileProvisionBaseline(repo) {
|
|
164
|
+
const basePath = path.join(repo, recipeRuntimeDir(), 'runway-provision.json');
|
|
165
|
+
if (!fs.existsSync(basePath)) return null;
|
|
166
|
+
try {
|
|
167
|
+
const c = JSON.parse(fs.readFileSync(basePath, 'utf8'));
|
|
168
|
+
const simulator = c.simulator?.name || c.simulator?.udid;
|
|
169
|
+
if (!simulator && c.watcherPort === undefined) return null;
|
|
170
|
+
return formatKvLines({
|
|
171
|
+
...(c.watcherPort !== undefined ? { WATCHER_PORT: c.watcherPort } : {}),
|
|
172
|
+
...(simulator ? { IOS_SIMULATOR: simulator } : {}),
|
|
173
|
+
...(c.slotId ? { SLOT_ID: c.slotId } : {}),
|
|
174
|
+
});
|
|
175
|
+
} catch {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function resolveMobileRuntimePorts(repo) {
|
|
181
|
+
return (
|
|
182
|
+
resolveMobileRuntimeContext(repo)
|
|
183
|
+
?? resolveFarmslotPortsByRepo(repo)
|
|
184
|
+
?? resolveMobileSlotDefaults(repo)
|
|
185
|
+
?? ''
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export const cliFns = {
|
|
190
|
+
resolve_farmslot_ports_by_repo: resolveFarmslotPortsByRepo,
|
|
191
|
+
resolve_default_extension_ports: resolveDefaultExtensionPorts,
|
|
192
|
+
resolve_extension_runtime_ports: (repo) => resolveExtensionRuntimePorts(repo) || null,
|
|
193
|
+
resolve_mobile_runtime_context: resolveMobileRuntimeContext,
|
|
194
|
+
resolve_mobile_slot_defaults: resolveMobileSlotDefaults,
|
|
195
|
+
resolve_mobile_runtime_ports: (repo) => resolveMobileRuntimePorts(repo) || null,
|
|
196
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Node leaf for port resolution — self-contained for runner and injected overlay copies.
|
|
3
|
+
|
|
4
|
+
import { cliFns } from './resolve-farmslot-ports-core.mjs';
|
|
5
|
+
|
|
6
|
+
const [, , fn, repo] = process.argv;
|
|
7
|
+
if (!fn || fn === '-h' || fn === '--help') {
|
|
8
|
+
process.stderr.write('Usage: resolve-farmslot-ports.mjs <fn> <repo>\n');
|
|
9
|
+
process.exit(fn ? 0 : 2);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const handler = cliFns[fn];
|
|
13
|
+
if (!handler) {
|
|
14
|
+
process.stderr.write(`resolve-farmslot-ports: unknown fn: ${fn}\n`);
|
|
15
|
+
process.exit(2);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const out = handler(repo ?? '.');
|
|
19
|
+
if (!out?.trim()) process.exit(1);
|
|
20
|
+
process.stdout.write(out.endsWith('\n') ? out : `${out}\n`);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# resolve-farmslot-ports.sh — bash-compat wrapper over resolve-farmslot-ports.mjs
|
|
2
3
|
#
|
|
3
4
|
# Farmslot pool JSON is authoritative when a slot repo matches; otherwise fall back
|
|
4
5
|
# to the local-extension-N formula (6660+N / 9010+N).
|
|
@@ -8,85 +9,15 @@
|
|
|
8
9
|
# resolve_extension_runtime_ports /path/to/metamask-extension-N
|
|
9
10
|
# -> prints CDP_PORT=... WATCHER_PORT=... SLOT_ID=... lines
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
local farmslot_root="${FARMSLOT_ROOT:-}"
|
|
14
|
-
[ -n "$repo" ] || return 1
|
|
15
|
-
repo="$(cd "$repo" && pwd -P)"
|
|
16
|
-
|
|
17
|
-
local root candidate match
|
|
18
|
-
for root in "$farmslot_root" "$HOME/dev/farmslot"; do
|
|
19
|
-
[ -n "$root" ] && [ -d "$root/pool" ] || continue
|
|
20
|
-
match="$(POOL_ROOT="$root" REPO_TARGET="$repo" python3 - <<'PY'
|
|
21
|
-
import glob, json, os, re, sys
|
|
22
|
-
|
|
23
|
-
pool_root = os.environ["POOL_ROOT"]
|
|
24
|
-
repo = os.path.realpath(os.environ["REPO_TARGET"])
|
|
25
|
-
base = os.path.basename(repo)
|
|
26
|
-
slot_suffix = None
|
|
27
|
-
m = re.search(r"-(\d+)$", base)
|
|
28
|
-
if m:
|
|
29
|
-
slot_suffix = m.group(1)
|
|
30
|
-
|
|
31
|
-
def score_slot(slot):
|
|
32
|
-
resources = slot.get("resources") or {}
|
|
33
|
-
cdp = (resources.get("browser") or {}).get("cdp_port")
|
|
34
|
-
port = (resources.get("dev-server") or {}).get("port")
|
|
35
|
-
session = slot.get("session") or ""
|
|
36
|
-
slot_id = slot.get("id") or ""
|
|
37
|
-
score = 0
|
|
38
|
-
if cdp is not None:
|
|
39
|
-
score += 100
|
|
40
|
-
if port is not None:
|
|
41
|
-
score += 10
|
|
42
|
-
if slot_suffix:
|
|
43
|
-
if session == f"mme-{slot_suffix}":
|
|
44
|
-
score += 50
|
|
45
|
-
if slot_id.endswith(f"mme-{slot_suffix}") or f"-mme-{slot_suffix}" in slot_id:
|
|
46
|
-
score += 40
|
|
47
|
-
if "demo" not in slot_id.lower():
|
|
48
|
-
score += 5
|
|
49
|
-
return score, cdp, port, slot_id
|
|
12
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
13
|
+
_RESOLVE_PORTS_MJS="$SCRIPT_DIR/resolve-farmslot-ports.mjs"
|
|
50
14
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
continue
|
|
55
|
-
try:
|
|
56
|
-
data = json.load(open(path, encoding="utf-8"))
|
|
57
|
-
except Exception:
|
|
58
|
-
continue
|
|
59
|
-
for slot in data.get("slots") or []:
|
|
60
|
-
slot_repo = slot.get("repo") or ""
|
|
61
|
-
if not slot_repo:
|
|
62
|
-
continue
|
|
63
|
-
try:
|
|
64
|
-
if os.path.realpath(slot_repo) != repo:
|
|
65
|
-
continue
|
|
66
|
-
except Exception:
|
|
67
|
-
continue
|
|
68
|
-
scored = score_slot(slot)
|
|
69
|
-
if best is None or scored[0] > best[0]:
|
|
70
|
-
best = scored
|
|
71
|
-
|
|
72
|
-
if best is None:
|
|
73
|
-
sys.exit(1)
|
|
15
|
+
_resolve_ports_cli() {
|
|
16
|
+
node "$_RESOLVE_PORTS_MJS" "$1" "${2:-.}"
|
|
17
|
+
}
|
|
74
18
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
print(f"CDP_PORT={cdp}")
|
|
78
|
-
if port is not None:
|
|
79
|
-
print(f"WATCHER_PORT={port}")
|
|
80
|
-
if slot_id:
|
|
81
|
-
print(f"SLOT_ID={slot_id}")
|
|
82
|
-
sys.exit(0)
|
|
83
|
-
PY
|
|
84
|
-
)" || continue
|
|
85
|
-
[ -n "$match" ] || continue
|
|
86
|
-
printf '%s\n' "$match"
|
|
87
|
-
return 0
|
|
88
|
-
done
|
|
89
|
-
return 1
|
|
19
|
+
resolve_farmslot_ports_by_repo() {
|
|
20
|
+
_resolve_ports_cli resolve_farmslot_ports_by_repo "$1"
|
|
90
21
|
}
|
|
91
22
|
|
|
92
23
|
infer_extension_slot_suffix() {
|
|
@@ -100,27 +31,18 @@ infer_extension_slot_suffix() {
|
|
|
100
31
|
}
|
|
101
32
|
|
|
102
33
|
resolve_default_extension_ports() {
|
|
103
|
-
|
|
104
|
-
n="$(infer_extension_slot_suffix "$repo" || true)"
|
|
105
|
-
[ -n "$n" ] || return 1
|
|
106
|
-
printf 'CDP_PORT=%s\n' "$((6660 + n))"
|
|
107
|
-
printf 'WATCHER_PORT=%s\n' "$((9010 + n))"
|
|
108
|
-
printf 'SLOT_ID=local-extension-%s\n' "$n"
|
|
34
|
+
_resolve_ports_cli resolve_default_extension_ports "$1"
|
|
109
35
|
}
|
|
110
36
|
|
|
111
37
|
resolve_extension_runtime_ports() {
|
|
112
|
-
|
|
113
|
-
if resolve_farmslot_ports_by_repo "$repo"; then
|
|
114
|
-
return 0
|
|
115
|
-
fi
|
|
116
|
-
resolve_default_extension_ports "$repo"
|
|
38
|
+
_resolve_ports_cli resolve_extension_runtime_ports "${1:-.}"
|
|
117
39
|
}
|
|
118
40
|
|
|
119
41
|
apply_resolved_extension_ports() {
|
|
120
42
|
local repo="${1:-.}" line key val from_pool=false resolved=""
|
|
121
|
-
if resolved="$(resolve_farmslot_ports_by_repo "$repo" 2>/dev/null)"; then
|
|
43
|
+
if resolved="$(_resolve_ports_cli resolve_farmslot_ports_by_repo "$repo" 2>/dev/null)"; then
|
|
122
44
|
from_pool=true
|
|
123
|
-
elif resolved="$(resolve_default_extension_ports "$repo" 2>/dev/null)"; then
|
|
45
|
+
elif resolved="$(_resolve_ports_cli resolve_default_extension_ports "$repo" 2>/dev/null)"; then
|
|
124
46
|
from_pool=false
|
|
125
47
|
else
|
|
126
48
|
return 0
|
|
@@ -143,52 +65,23 @@ apply_resolved_extension_ports() {
|
|
|
143
65
|
done <<< "$resolved"
|
|
144
66
|
}
|
|
145
67
|
|
|
146
|
-
# Mobile slot defaults: derive Metro port + iOS simulator name from directory suffix.
|
|
147
|
-
# Port formula: 8060 + N (slot 1 → 8061). iOS simulator: mm-N.
|
|
148
|
-
# Mirrors the extension convention (6660+N / 9010+N) for multi-slot mobile farms.
|
|
149
68
|
resolve_mobile_slot_defaults() {
|
|
150
|
-
|
|
151
|
-
n="$(infer_extension_slot_suffix "$repo" || true)"
|
|
152
|
-
[ -n "$n" ] || return 1
|
|
153
|
-
printf 'WATCHER_PORT=%s\n' "$((8060 + n))"
|
|
154
|
-
printf 'IOS_SIMULATOR=mm-%s\n' "$n"
|
|
155
|
-
printf 'SLOT_ID=local-mobile-%s\n' "$n"
|
|
69
|
+
_resolve_ports_cli resolve_mobile_slot_defaults "$1"
|
|
156
70
|
}
|
|
157
71
|
|
|
158
|
-
# Slot context the orchestrator's prepare wrote INTO the checkout — the
|
|
159
|
-
# authoritative device/port mapping for this repo (survives pool renames that
|
|
160
|
-
# break any naming formula). Highest-priority detection source.
|
|
161
72
|
resolve_mobile_runtime_context() {
|
|
162
|
-
|
|
163
|
-
ctx="$repo/${RECIPE_RUNTIME_DIR:-temp/recipe/runtime}/agentic-runtime.json"
|
|
164
|
-
[ -f "$ctx" ] || return 1
|
|
165
|
-
node -e '
|
|
166
|
-
try {
|
|
167
|
-
const c = JSON.parse(require("fs").readFileSync(process.argv[1], "utf8"));
|
|
168
|
-
if (!c.simulator && !c.metroPort) process.exit(1);
|
|
169
|
-
if (c.metroPort) console.log(`WATCHER_PORT=${c.metroPort}`);
|
|
170
|
-
if (c.simulator) console.log(`IOS_SIMULATOR=${c.simulator}`);
|
|
171
|
-
if (c.slotId) console.log(`SLOT_ID=${c.slotId}`);
|
|
172
|
-
} catch { process.exit(1); }
|
|
173
|
-
' "$ctx" 2>/dev/null
|
|
73
|
+
_resolve_ports_cli resolve_mobile_runtime_context "$1"
|
|
174
74
|
}
|
|
175
75
|
|
|
176
76
|
resolve_mobile_runtime_ports() {
|
|
177
|
-
|
|
178
|
-
if resolve_mobile_runtime_context "$repo"; then
|
|
179
|
-
return 0
|
|
180
|
-
fi
|
|
181
|
-
if resolve_farmslot_ports_by_repo "$repo"; then
|
|
182
|
-
return 0
|
|
183
|
-
fi
|
|
184
|
-
resolve_mobile_slot_defaults "$repo"
|
|
77
|
+
_resolve_ports_cli resolve_mobile_runtime_ports "${1:-.}"
|
|
185
78
|
}
|
|
186
79
|
|
|
187
80
|
apply_resolved_mobile_ports() {
|
|
188
81
|
local repo="${1:-.}" line key val from_pool=false resolved=""
|
|
189
|
-
if resolved="$(resolve_farmslot_ports_by_repo "$repo" 2>/dev/null)"; then
|
|
82
|
+
if resolved="$(_resolve_ports_cli resolve_farmslot_ports_by_repo "$repo" 2>/dev/null)"; then
|
|
190
83
|
from_pool=true
|
|
191
|
-
elif resolved="$(resolve_mobile_slot_defaults "$repo" 2>/dev/null)"; then
|
|
84
|
+
elif resolved="$(_resolve_ports_cli resolve_mobile_slot_defaults "$repo" 2>/dev/null)"; then
|
|
192
85
|
from_pool=false
|
|
193
86
|
else
|
|
194
87
|
return 0
|
|
@@ -209,4 +102,4 @@ apply_resolved_mobile_ports() {
|
|
|
209
102
|
;;
|
|
210
103
|
esac
|
|
211
104
|
done <<< "$resolved"
|
|
212
|
-
}
|
|
105
|
+
}
|
|
@@ -103,6 +103,8 @@ async function decideMobileReadiness(target, options = {}) {
|
|
|
103
103
|
const fast = options.preflightMode === "fast";
|
|
104
104
|
const report = await computeMobileReadiness(resolved, options, fast);
|
|
105
105
|
if (fast && report.decision === "install") {
|
|
106
|
+
const hasNodeModules = fs.existsSync(path.join(resolved, "node_modules"));
|
|
107
|
+
const standalone = hasNodeModules ? "`yarn setup:expo --no-build-ios --no-build-android`" : "`yarn install --immutable && yarn setup:expo --no-build-ios --no-build-android`";
|
|
106
108
|
return {
|
|
107
109
|
...report,
|
|
108
110
|
decision: "blocked",
|
|
@@ -111,7 +113,7 @@ async function decideMobileReadiness(target, options = {}) {
|
|
|
111
113
|
"Fast preflight found dependencies not ready and does not install them (the orchestrator deps phase owns installation).",
|
|
112
114
|
...report.reasons
|
|
113
115
|
],
|
|
114
|
-
userAction:
|
|
116
|
+
userAction: `run the slot deps/prepare phase; standalone: ${standalone} in the checkout, then re-run \`mm-harness launch <platform>\``,
|
|
115
117
|
actions: []
|
|
116
118
|
};
|
|
117
119
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {
|
|
2
|
+
formatKvLines,
|
|
3
|
+
inferSlotSuffix,
|
|
4
|
+
realRepoPath,
|
|
5
|
+
resolveDefaultExtensionPorts,
|
|
6
|
+
resolveExtensionRuntimePorts,
|
|
7
|
+
resolveFarmslotPortsByRepo,
|
|
8
|
+
resolveMobileRuntimeContext,
|
|
9
|
+
resolveMobileRuntimePorts,
|
|
10
|
+
resolveMobileSlotDefaults
|
|
11
|
+
} from "../../adapters/shared/resolve-farmslot-ports-core.mjs";
|
|
12
|
+
export {
|
|
13
|
+
formatKvLines,
|
|
14
|
+
inferSlotSuffix,
|
|
15
|
+
realRepoPath,
|
|
16
|
+
resolveDefaultExtensionPorts,
|
|
17
|
+
resolveExtensionRuntimePorts,
|
|
18
|
+
resolveFarmslotPortsByRepo,
|
|
19
|
+
resolveMobileRuntimeContext,
|
|
20
|
+
resolveMobileRuntimePorts,
|
|
21
|
+
resolveMobileSlotDefaults
|
|
22
|
+
};
|
|
@@ -2,7 +2,13 @@ import { execFileSync } from "node:child_process";
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { readRuntimeContextField, resolveRuntimeContextPath } from "../harness.js";
|
|
5
|
-
import { recipeRuntimeDir
|
|
5
|
+
import { recipeRuntimeDir } from "../paths.js";
|
|
6
|
+
import {
|
|
7
|
+
resolveDefaultExtensionPorts,
|
|
8
|
+
resolveFarmslotPortsByRepo,
|
|
9
|
+
resolveMobileRuntimeContext,
|
|
10
|
+
resolveMobileSlotDefaults
|
|
11
|
+
} from "./resolve-farmslot-ports.js";
|
|
6
12
|
function applyKVLines(output, overwrite) {
|
|
7
13
|
for (const line of output.split("\n")) {
|
|
8
14
|
const m = /^([A-Z_]+)=(.+)$/u.exec(line.trim());
|
|
@@ -31,31 +37,19 @@ function applyKVLines(output, overwrite) {
|
|
|
31
37
|
}
|
|
32
38
|
}
|
|
33
39
|
}
|
|
34
|
-
function sourceResolve(fn, target) {
|
|
35
|
-
const resolveScript = path.join(runnerDir, "adapters/shared/resolve-farmslot-ports.sh");
|
|
36
|
-
try {
|
|
37
|
-
return execFileSync("bash", ["-c", `source "${resolveScript}" && ${fn} "${target}"`], {
|
|
38
|
-
encoding: "utf8",
|
|
39
|
-
timeout: 5e3,
|
|
40
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
41
|
-
});
|
|
42
|
-
} catch {
|
|
43
|
-
return "";
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
40
|
function resolveMobileSlotPorts(target) {
|
|
47
|
-
const ctxOut =
|
|
48
|
-
if (ctxOut
|
|
41
|
+
const ctxOut = resolveMobileRuntimeContext(target);
|
|
42
|
+
if (ctxOut?.trim()) {
|
|
49
43
|
applyKVLines(ctxOut, true);
|
|
50
44
|
return;
|
|
51
45
|
}
|
|
52
|
-
const poolOut =
|
|
53
|
-
if (poolOut
|
|
46
|
+
const poolOut = resolveFarmslotPortsByRepo(target);
|
|
47
|
+
if (poolOut?.trim()) {
|
|
54
48
|
applyKVLines(poolOut, true);
|
|
55
49
|
return;
|
|
56
50
|
}
|
|
57
|
-
const defOut =
|
|
58
|
-
if (defOut
|
|
51
|
+
const defOut = resolveMobileSlotDefaults(target);
|
|
52
|
+
if (defOut?.trim()) applyKVLines(defOut, false);
|
|
59
53
|
}
|
|
60
54
|
function resolveExtensionSlotPorts(target) {
|
|
61
55
|
const contextPath = resolveRuntimeContextPath(target);
|
|
@@ -70,13 +64,13 @@ function resolveExtensionSlotPorts(target) {
|
|
|
70
64
|
process.env["RECIPE_WATCHER_PORT"] = dev;
|
|
71
65
|
}
|
|
72
66
|
if (process.env["CDP_PORT"]) return;
|
|
73
|
-
const poolOut =
|
|
74
|
-
if (poolOut
|
|
67
|
+
const poolOut = resolveFarmslotPortsByRepo(target);
|
|
68
|
+
if (poolOut?.trim()) {
|
|
75
69
|
applyKVLines(poolOut, true);
|
|
76
70
|
return;
|
|
77
71
|
}
|
|
78
|
-
const defOut =
|
|
79
|
-
if (defOut
|
|
72
|
+
const defOut = resolveDefaultExtensionPorts(target);
|
|
73
|
+
if (defOut?.trim()) applyKVLines(defOut, false);
|
|
80
74
|
}
|
|
81
75
|
function stopExtensionWatcher(target) {
|
|
82
76
|
const runtimeAbs = path.join(target, recipeRuntimeDir());
|
|
@@ -8,7 +8,7 @@ import { mnemonicToAccount, privateKeyToAccount } from 'viem/accounts';
|
|
|
8
8
|
// this adapter under the bundled tsx (see commandFor/importsSourceTypescript in
|
|
9
9
|
// src/live-adapter-contract.ts). The core adapter dynamic-imports the perps
|
|
10
10
|
// controller TypeScript at runtime, so it MUST run under tsx, not plain node.
|
|
11
|
-
import { walletFixturePath } from '
|
|
11
|
+
import { walletFixturePath } from '../../harness-exports.mjs';
|
|
12
12
|
|
|
13
13
|
// Shared headless instantiation for the MetaMask `core` adapter.
|
|
14
14
|
//
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
recipeHarnessPath,
|
|
12
12
|
walletFixturePath,
|
|
13
13
|
captureActiveRecipeRecordingSnapshot,
|
|
14
|
-
} from '
|
|
14
|
+
} from '../../harness-exports.mjs';
|
|
15
15
|
|
|
16
16
|
// Resolve the Farmslot harness through normal package dependencies by default.
|
|
17
17
|
// Local Farmslot source is only a dev override handled by src/paths.ts.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
2
|
import { runAdapter, withExtensionPage } from '../platform/cdp.mjs';
|
|
3
|
-
import { walletFixturePath } from '
|
|
3
|
+
import { walletFixturePath } from '../../harness-exports.mjs';
|
|
4
4
|
|
|
5
5
|
async function fixturePassword(projectRoot) {
|
|
6
6
|
const candidates = [
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Bridge from library actions to the harness path/recording helpers. Published
|
|
2
|
+
// installs load the compiled dist (node cannot type-strip .ts under
|
|
3
|
+
// node_modules); source checkouts without dist fall back to src. Deliberately
|
|
4
|
+
// narrow — importing the package entry here would close an import cycle
|
|
5
|
+
// through src/adapters.ts (which imports these actions) and deadlock.
|
|
6
|
+
const packageRoot = new URL('../../', import.meta.url);
|
|
7
|
+
|
|
8
|
+
async function load(distRelative, srcRelative) {
|
|
9
|
+
try {
|
|
10
|
+
return await import(new URL(distRelative, packageRoot).href);
|
|
11
|
+
} catch {
|
|
12
|
+
return await import(new URL(srcRelative, packageRoot).href);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const paths = await load('dist/paths.js', 'src/paths.ts');
|
|
17
|
+
const recording = await load('dist/run-recording.js', 'src/run-recording.ts');
|
|
18
|
+
|
|
19
|
+
export const {
|
|
20
|
+
importRecipeHarnessRuntimeBrowserExtension,
|
|
21
|
+
importRecipeHarnessRuntimeCdp,
|
|
22
|
+
extensionIdPath,
|
|
23
|
+
recipeHarnessPath,
|
|
24
|
+
walletFixturePath,
|
|
25
|
+
} = paths;
|
|
26
|
+
|
|
27
|
+
export const { captureActiveRecipeRecordingSnapshot } = recording;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
2
|
import { bridgeCommand, runAdapter } from '../platform/bridge.mjs';
|
|
3
|
-
import { walletFixturePath } from '
|
|
3
|
+
import { walletFixturePath } from '../../harness-exports.mjs';
|
|
4
4
|
|
|
5
5
|
async function fixturePassword(projectRoot) {
|
|
6
6
|
const candidates = [
|
|
@@ -3,7 +3,7 @@ import { spawn } from 'node:child_process';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import { bridgeCommand, bridgeEnv, runAdapter } from '../platform/bridge.mjs';
|
|
6
|
-
import { walletFixturePath } from '
|
|
6
|
+
import { walletFixturePath } from '../../harness-exports.mjs';
|
|
7
7
|
|
|
8
8
|
async function fixtureProfile(projectRoot) {
|
|
9
9
|
const candidates = [
|
package/package.json
CHANGED
|
@@ -201,6 +201,13 @@ export async function decideMobileReadiness(
|
|
|
201
201
|
// mid-launch is inverted authority. Surface a teaching block, not an implicit
|
|
202
202
|
// setup, so a runway preflight never re-does the orchestrator's deps work.
|
|
203
203
|
if (fast && report.decision === 'install') {
|
|
204
|
+
// A checkout without node_modules cannot even run yarn scripts (yarn exits
|
|
205
|
+
// with "Couldn't find the node_modules state file"), so the taught command
|
|
206
|
+
// must lead with the install when that is the caller's actual state.
|
|
207
|
+
const hasNodeModules = fs.existsSync(path.join(resolved, 'node_modules'));
|
|
208
|
+
const standalone = hasNodeModules
|
|
209
|
+
? '`yarn setup:expo --no-build-ios --no-build-android`'
|
|
210
|
+
: '`yarn install --immutable && yarn setup:expo --no-build-ios --no-build-android`';
|
|
204
211
|
return {
|
|
205
212
|
...report,
|
|
206
213
|
decision: 'blocked',
|
|
@@ -210,7 +217,7 @@ export async function decideMobileReadiness(
|
|
|
210
217
|
...report.reasons,
|
|
211
218
|
],
|
|
212
219
|
userAction:
|
|
213
|
-
|
|
220
|
+
`run the slot deps/prepare phase; standalone: ${standalone} in the checkout, then re-run \`mm-harness launch <platform>\``,
|
|
214
221
|
actions: [],
|
|
215
222
|
};
|
|
216
223
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// TypeScript surface for slot-port resolution — delegates to the self-contained core leaf.
|
|
2
|
+
|
|
3
|
+
export {
|
|
4
|
+
formatKvLines,
|
|
5
|
+
inferSlotSuffix,
|
|
6
|
+
realRepoPath,
|
|
7
|
+
resolveDefaultExtensionPorts,
|
|
8
|
+
resolveExtensionRuntimePorts,
|
|
9
|
+
resolveFarmslotPortsByRepo,
|
|
10
|
+
resolveMobileRuntimeContext,
|
|
11
|
+
resolveMobileRuntimePorts,
|
|
12
|
+
resolveMobileSlotDefaults,
|
|
13
|
+
} from '../../adapters/shared/resolve-farmslot-ports-core.mjs';
|
|
@@ -8,7 +8,13 @@ import fs from 'node:fs';
|
|
|
8
8
|
import path from 'node:path';
|
|
9
9
|
|
|
10
10
|
import { readRuntimeContextField, resolveRuntimeContextPath } from '../harness.ts';
|
|
11
|
-
import { recipeRuntimeDir
|
|
11
|
+
import { recipeRuntimeDir } from '../paths.ts';
|
|
12
|
+
import {
|
|
13
|
+
resolveDefaultExtensionPorts,
|
|
14
|
+
resolveFarmslotPortsByRepo,
|
|
15
|
+
resolveMobileRuntimeContext,
|
|
16
|
+
resolveMobileSlotDefaults,
|
|
17
|
+
} from './resolve-farmslot-ports.ts';
|
|
12
18
|
|
|
13
19
|
// Apply KEY=VALUE lines from slot resolution to process.env.
|
|
14
20
|
// overwrite=true → pool/context match, always overrides existing env.
|
|
@@ -42,19 +48,6 @@ export function applyKVLines(output: string, overwrite: boolean): void {
|
|
|
42
48
|
}
|
|
43
49
|
}
|
|
44
50
|
|
|
45
|
-
function sourceResolve(fn: string, target: string): string {
|
|
46
|
-
const resolveScript = path.join(runnerDir, 'adapters/shared/resolve-farmslot-ports.sh');
|
|
47
|
-
try {
|
|
48
|
-
return execFileSync('bash', ['-c', `source "${resolveScript}" && ${fn} "${target}"`], {
|
|
49
|
-
encoding: 'utf8',
|
|
50
|
-
timeout: 5000,
|
|
51
|
-
stdio: ['ignore', 'pipe', 'ignore'],
|
|
52
|
-
});
|
|
53
|
-
} catch {
|
|
54
|
-
return '';
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
51
|
// Resolve mobile slot port/simulator: the slot context the orchestrator wrote
|
|
59
52
|
// into the checkout wins first, then the farmslot pool (both overwrite env),
|
|
60
53
|
// then the slot-suffix formula (only fills unset vars). Called before explicit
|
|
@@ -62,20 +55,20 @@ function sourceResolve(fn: string, target: string): string {
|
|
|
62
55
|
export function resolveMobileSlotPorts(target: string): void {
|
|
63
56
|
// The checkout's own runtime context is authoritative — it names the exact
|
|
64
57
|
// simulator/port this slot was prepared with, surviving pool renames.
|
|
65
|
-
const ctxOut =
|
|
66
|
-
if (ctxOut
|
|
58
|
+
const ctxOut = resolveMobileRuntimeContext(target);
|
|
59
|
+
if (ctxOut?.trim()) {
|
|
67
60
|
applyKVLines(ctxOut, true);
|
|
68
61
|
return;
|
|
69
62
|
}
|
|
70
63
|
// Pool match always wins — overwrite whatever is in the environment.
|
|
71
|
-
const poolOut =
|
|
72
|
-
if (poolOut
|
|
64
|
+
const poolOut = resolveFarmslotPortsByRepo(target);
|
|
65
|
+
if (poolOut?.trim()) {
|
|
73
66
|
applyKVLines(poolOut, true);
|
|
74
67
|
return;
|
|
75
68
|
}
|
|
76
69
|
// Formula match only fills unset vars (never overrides explicit env/pool).
|
|
77
|
-
const defOut =
|
|
78
|
-
if (defOut
|
|
70
|
+
const defOut = resolveMobileSlotDefaults(target);
|
|
71
|
+
if (defOut?.trim()) applyKVLines(defOut, false);
|
|
79
72
|
}
|
|
80
73
|
|
|
81
74
|
// Resolve extension slot ports the same way as mobile: the checkout's runtime
|
|
@@ -97,13 +90,13 @@ export function resolveExtensionSlotPorts(target: string): void {
|
|
|
97
90
|
process.env['RECIPE_WATCHER_PORT'] = dev;
|
|
98
91
|
}
|
|
99
92
|
if (process.env['CDP_PORT']) return;
|
|
100
|
-
const poolOut =
|
|
101
|
-
if (poolOut
|
|
93
|
+
const poolOut = resolveFarmslotPortsByRepo(target);
|
|
94
|
+
if (poolOut?.trim()) {
|
|
102
95
|
applyKVLines(poolOut, true);
|
|
103
96
|
return;
|
|
104
97
|
}
|
|
105
|
-
const defOut =
|
|
106
|
-
if (defOut
|
|
98
|
+
const defOut = resolveDefaultExtensionPorts(target);
|
|
99
|
+
if (defOut?.trim()) applyKVLines(defOut, false);
|
|
107
100
|
}
|
|
108
101
|
|
|
109
102
|
// Kill the harness-owned webpack watcher for this checkout: pid file first,
|