@deeeed/metamask-harness 0.5.0 → 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 +51 -20
- package/adapters/core/inject.sh +1 -0
- package/adapters/extension/cleanup.mjs +1 -1
- package/adapters/extension/inject.mjs +4 -1
- package/adapters/extension/launch-browser.cjs +18 -4
- package/adapters/extension/live.sh +14 -2
- package/adapters/extension/refresh-build.sh +2 -2
- package/adapters/extension/sidepanel-toggle.sh +33 -12
- package/adapters/extension/start-watch.sh +2 -2
- package/adapters/extension/wallet-fixture-state.cjs +38 -24
- package/adapters/manifest.json +32 -3
- package/adapters/mobile/inject.sh +1 -0
- package/adapters/mobile/start-metro.sh +1 -1
- package/adapters/mobile/stop-metro.sh +20 -0
- package/adapters/mobile/verify.sh +1 -1
- package/adapters/shared/activate-repo-node.sh +1 -1
- package/adapters/shared/cli-ux.sh +44 -25
- package/adapters/shared/ensure-runner-deps.sh +30 -0
- package/adapters/shared/log-tui.mjs +4 -4
- package/adapters/shared/open-debug.mjs +1 -1
- 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 +205 -0
- package/adapters/shared/resolve-farmslot-ports.mjs +20 -0
- package/adapters/shared/resolve-farmslot-ports.sh +19 -126
- 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/mobile/runtime-decision.js +3 -1
- package/dist/adapters/resolve-farmslot-ports.js +22 -0
- package/dist/adapters/slot-ports.js +18 -29
- 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/core/perps/_controller.mjs +1 -1
- package/library/actions/extension/platform/cdp.mjs +2 -2
- package/library/actions/extension/wallet/ensure_unlocked.mjs +7 -1
- package/library/actions/harness-exports.mjs +27 -0
- package/library/actions/mobile/wallet/ensure_unlocked.mjs +14 -2
- package/library/actions/mobile/wallet/setup.mjs +1 -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 -459
- package/src/adapters/mobile/surface.ts +0 -71
- package/src/adapters/slot-ports.ts +0 -165
- 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
|
@@ -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() {
|
|
@@ -67,7 +85,7 @@ recipe_instant_hint() {
|
|
|
67
85
|
local message="$1"
|
|
68
86
|
[ "${RECIPE_PROGRESS_JSON:-}" = "1" ] && return 0
|
|
69
87
|
[ -t 2 ] || return 0
|
|
70
|
-
local prefix="${RECIPE_CLI_PREFIX:-
|
|
88
|
+
local prefix="${RECIPE_CLI_PREFIX:-mm-harness}"
|
|
71
89
|
printf '%s: %s\n' \
|
|
72
90
|
"$(recipe_color label "$prefix")" \
|
|
73
91
|
"$(recipe_color dim "$message")" >&2
|
|
@@ -92,7 +110,7 @@ recipe_done() {
|
|
|
92
110
|
recipe_failure_playbook() {
|
|
93
111
|
local verb="$1" reason="$2"
|
|
94
112
|
shift 2
|
|
95
|
-
local log_path="" artifact="" prefix="${RECIPE_CLI_PREFIX:-
|
|
113
|
+
local log_path="" artifact="" prefix="${RECIPE_CLI_PREFIX:-mm-harness}"
|
|
96
114
|
while [ "$#" -gt 0 ]; do
|
|
97
115
|
case "$1" in
|
|
98
116
|
--log) log_path="$2"; shift 2 ;;
|
|
@@ -106,31 +124,32 @@ recipe_failure_playbook() {
|
|
|
106
124
|
"$(recipe_color cmd "$verb")" \
|
|
107
125
|
"$(recipe_color err failed)" \
|
|
108
126
|
"$(recipe_color warn "$reason")" >&2
|
|
109
|
-
printf '%s\n' "$(recipe_color label next:)" >&2
|
|
127
|
+
printf '%s\n' "$(recipe_color label "next:")" >&2
|
|
110
128
|
case "$verb" in
|
|
111
129
|
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 "
|
|
130
|
+
printf ' %s\n' "$(recipe_color cmd "mm-harness logs")" >&2
|
|
131
|
+
printf ' %s\n' "$(recipe_color cmd "mm-harness logs --full")" >&2
|
|
132
|
+
printf ' %s\n' "$(recipe_color cmd "mm-harness doctor")" >&2
|
|
133
|
+
printf ' %s\n' "$(recipe_color cmd "mm-harness install")" >&2
|
|
116
134
|
;;
|
|
117
135
|
refresh)
|
|
118
|
-
printf ' %s\n' "$(recipe_color cmd "
|
|
119
|
-
printf ' %s\n' "$(recipe_color cmd "
|
|
120
|
-
printf ' %s\n' "$(recipe_color cmd "
|
|
136
|
+
printf ' %s\n' "$(recipe_color cmd "mm-harness launch")" >&2
|
|
137
|
+
printf ' %s\n' "$(recipe_color cmd "mm-harness logs")" >&2
|
|
138
|
+
printf ' %s\n' "$(recipe_color cmd "mm-harness launch --build")" >&2
|
|
121
139
|
;;
|
|
122
140
|
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 "
|
|
141
|
+
printf ' %s\n' "$(recipe_color cmd "mm-harness logs")" >&2
|
|
142
|
+
printf ' %s\n' "$(recipe_color cmd "mm-harness logs --full")" >&2
|
|
143
|
+
printf ' %s\n' "$(recipe_color cmd "mm-harness launch")" >&2
|
|
126
144
|
;;
|
|
127
145
|
*)
|
|
128
|
-
printf ' %s\n' "$(recipe_color cmd "
|
|
129
|
-
printf ' %s\n' "$(recipe_color cmd "
|
|
146
|
+
printf ' %s\n' "$(recipe_color cmd "mm-harness doctor")" >&2
|
|
147
|
+
printf ' %s\n' "$(recipe_color cmd "mm-harness logs")" >&2
|
|
130
148
|
;;
|
|
131
149
|
esac
|
|
132
150
|
[ -n "$log_path" ] && printf '%s\n' "$(recipe_color path "log: $log_path")" >&2
|
|
133
151
|
[ -n "$artifact" ] && printf '%s\n' "$(recipe_color path "artifact: $artifact")" >&2
|
|
152
|
+
return 0
|
|
134
153
|
}
|
|
135
154
|
|
|
136
155
|
recipe_check_harness_stale() {
|
|
@@ -152,12 +171,12 @@ recipe_check_harness_stale() {
|
|
|
152
171
|
|
|
153
172
|
recipe_warn_harness_stale() {
|
|
154
173
|
local adapter="$1" root="$2" harness_root="$3" runner_root="$4"
|
|
155
|
-
local prefix="${RECIPE_CLI_PREFIX:-
|
|
174
|
+
local prefix="${RECIPE_CLI_PREFIX:-mm-harness}"
|
|
156
175
|
if recipe_check_harness_stale "$adapter" "$root" "$harness_root" "$runner_root"; then
|
|
157
176
|
printf '%s: %s - run: %s\n' \
|
|
158
177
|
"$(recipe_color label "$prefix")" \
|
|
159
178
|
"$(recipe_color warn 'harness scripts are stale')" \
|
|
160
|
-
"$(recipe_color cmd "
|
|
179
|
+
"$(recipe_color cmd "mm-harness install")" >&2
|
|
161
180
|
return 1
|
|
162
181
|
fi
|
|
163
182
|
return 0
|
|
@@ -190,7 +209,7 @@ recipe_try_farmslot_fixture_sync() {
|
|
|
190
209
|
recipe_sync_wallet_fixture() {
|
|
191
210
|
local root="$1" runtime_dir="$2" cdp_port="${3:-}" seed_script="${4:-}" farmslot_slot="${5:-}"
|
|
192
211
|
local canonical="$root/$runtime_dir/wallet-fixture.json"
|
|
193
|
-
local candidate resolved prefix="${RECIPE_CLI_PREFIX:-
|
|
212
|
+
local candidate resolved prefix="${RECIPE_CLI_PREFIX:-mm-harness}"
|
|
194
213
|
|
|
195
214
|
mkdir -p "$root/$runtime_dir"
|
|
196
215
|
if [ -f "$canonical" ]; then
|
|
@@ -239,10 +258,10 @@ recipe_sync_wallet_fixture() {
|
|
|
239
258
|
recipe_speed_ladder() {
|
|
240
259
|
cat >&2 <<'EOF'
|
|
241
260
|
Speed ladder:
|
|
242
|
-
FAST (seconds):
|
|
243
|
-
SLOW (minutes):
|
|
244
|
-
INSPECT:
|
|
245
|
-
PROOF:
|
|
246
|
-
SYNC CONTEXT:
|
|
261
|
+
FAST (seconds): mm-harness launch (extension quick relaunch)
|
|
262
|
+
SLOW (minutes): mm-harness launch --build
|
|
263
|
+
INSPECT: mm-harness doctor | mm-harness logs | mm-harness logs --full | mm-harness debug
|
|
264
|
+
PROOF: mm-harness run <recipe.json>
|
|
265
|
+
SYNC CONTEXT: mm-harness fixtures sync
|
|
247
266
|
EOF
|
|
248
267
|
}
|
|
@@ -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
|
|
@@ -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,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
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
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 simulator = resources['ios-sim']?.simulator;
|
|
55
|
+
const session = slot.session ?? '';
|
|
56
|
+
const slotId = slot.id ?? '';
|
|
57
|
+
let score = 0;
|
|
58
|
+
if (cdp !== undefined) score += 100;
|
|
59
|
+
if (port !== undefined) score += 10;
|
|
60
|
+
if (slotSuffix) {
|
|
61
|
+
if (session === `mme-${slotSuffix}`) score += 50;
|
|
62
|
+
if (slotId.endsWith(`mme-${slotSuffix}`) || slotId.includes(`-mme-${slotSuffix}`)) score += 40;
|
|
63
|
+
}
|
|
64
|
+
if (!slotId.toLowerCase().includes('demo')) score += 5;
|
|
65
|
+
return [score, cdp, port, slotId, simulator];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function resolveFarmslotPortsByRepo(repo) {
|
|
69
|
+
const realRepo = realRepoPath(repo);
|
|
70
|
+
if (!realRepo) return null;
|
|
71
|
+
|
|
72
|
+
const slotSuffix = inferSlotSuffix(realRepo);
|
|
73
|
+
const roots = [];
|
|
74
|
+
// A slot checkout lives at <workspace>/repos/<slot>; that workspace's own
|
|
75
|
+
// pool is the authoritative one for this repo — machine-global fallbacks
|
|
76
|
+
// (env root, dev checkout) apply only when the repo is not workspace-shaped.
|
|
77
|
+
if (path.basename(path.dirname(realRepo)) === 'repos') {
|
|
78
|
+
roots.push(path.join(path.dirname(path.dirname(realRepo)), 'farmslot'));
|
|
79
|
+
}
|
|
80
|
+
if (process.env.FARMSLOT_ROOT) roots.push(process.env.FARMSLOT_ROOT);
|
|
81
|
+
roots.push(path.join(os.homedir(), 'dev', 'farmslot'));
|
|
82
|
+
|
|
83
|
+
for (const root of roots) {
|
|
84
|
+
if (!root || !fs.existsSync(path.join(root, 'pool'))) continue;
|
|
85
|
+
const poolDir = path.join(root, 'pool');
|
|
86
|
+
let best = null;
|
|
87
|
+
|
|
88
|
+
for (const name of fs.readdirSync(poolDir).sort()) {
|
|
89
|
+
if (!name.endsWith('.json') || name.includes('.bak.')) continue;
|
|
90
|
+
let data;
|
|
91
|
+
try {
|
|
92
|
+
data = JSON.parse(fs.readFileSync(path.join(poolDir, name), 'utf8'));
|
|
93
|
+
} catch {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
for (const slot of data.slots ?? []) {
|
|
97
|
+
const slotRepo = slot.repo ?? '';
|
|
98
|
+
if (!slotRepo) continue;
|
|
99
|
+
let slotReal;
|
|
100
|
+
try {
|
|
101
|
+
slotReal = fs.realpathSync(slotRepo);
|
|
102
|
+
} catch {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (slotReal !== realRepo) continue;
|
|
106
|
+
const scored = scoreSlot(slot, slotSuffix);
|
|
107
|
+
if (!best || scored[0] > best[0]) best = scored;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (best) {
|
|
112
|
+
const [, cdp, port, slotId, simulator] = best;
|
|
113
|
+
return formatKvLines({
|
|
114
|
+
...(cdp !== undefined ? { CDP_PORT: cdp } : {}),
|
|
115
|
+
...(port !== undefined ? { WATCHER_PORT: port } : {}),
|
|
116
|
+
...(slotId ? { SLOT_ID: slotId } : {}),
|
|
117
|
+
...(simulator ? { IOS_SIMULATOR: simulator } : {}),
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function resolveDefaultExtensionPorts(repo) {
|
|
125
|
+
const n = inferSlotSuffix(repo);
|
|
126
|
+
if (!n) return null;
|
|
127
|
+
return formatKvLines({
|
|
128
|
+
CDP_PORT: 6660 + Number(n),
|
|
129
|
+
WATCHER_PORT: 9010 + Number(n),
|
|
130
|
+
SLOT_ID: `local-extension-${n}`,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function resolveExtensionRuntimePorts(repo) {
|
|
135
|
+
return resolveFarmslotPortsByRepo(repo) ?? resolveDefaultExtensionPorts(repo) ?? '';
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function resolveMobileSlotDefaults(repo) {
|
|
139
|
+
const n = inferSlotSuffix(repo);
|
|
140
|
+
if (!n) return null;
|
|
141
|
+
return formatKvLines({
|
|
142
|
+
WATCHER_PORT: 8060 + Number(n),
|
|
143
|
+
IOS_SIMULATOR: `mm-${n}`,
|
|
144
|
+
SLOT_ID: `local-mobile-${n}`,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function resolveMobileRuntimeContext(repo) {
|
|
149
|
+
const ctxPath = path.join(repo, recipeRuntimeDir(), 'agentic-runtime.json');
|
|
150
|
+
if (fs.existsSync(ctxPath)) {
|
|
151
|
+
try {
|
|
152
|
+
const c = JSON.parse(fs.readFileSync(ctxPath, 'utf8'));
|
|
153
|
+
if (c.simulator || (c.metroPort != null && c.metroPort !== '')) {
|
|
154
|
+
return formatKvLines({
|
|
155
|
+
...(c.metroPort != null && c.metroPort !== '' ? { WATCHER_PORT: c.metroPort } : {}),
|
|
156
|
+
...(c.simulator ? { IOS_SIMULATOR: c.simulator } : {}),
|
|
157
|
+
...(c.slotId ? { SLOT_ID: c.slotId } : {}),
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
} catch {
|
|
161
|
+
/* unreadable context falls through to the provision baseline */
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return resolveMobileProvisionBaseline(repo);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// A provisioned-but-unprepared slot has no agentic-runtime.json yet, but the
|
|
168
|
+
// provision baseline records the same authoritative simulator/port identity —
|
|
169
|
+
// without it, a fresh slot's first launch degrades to the simctl `booted`
|
|
170
|
+
// alias and misses the installed dev client entirely.
|
|
171
|
+
function resolveMobileProvisionBaseline(repo) {
|
|
172
|
+
const basePath = path.join(repo, recipeRuntimeDir(), 'runway-provision.json');
|
|
173
|
+
if (!fs.existsSync(basePath)) return null;
|
|
174
|
+
try {
|
|
175
|
+
const c = JSON.parse(fs.readFileSync(basePath, 'utf8'));
|
|
176
|
+
const simulator = c.simulator?.name || c.simulator?.udid;
|
|
177
|
+
const watcherPort = c.watcherPort != null && c.watcherPort !== '' ? c.watcherPort : undefined;
|
|
178
|
+
if (!simulator && watcherPort === undefined) return null;
|
|
179
|
+
return formatKvLines({
|
|
180
|
+
...(watcherPort !== undefined ? { WATCHER_PORT: watcherPort } : {}),
|
|
181
|
+
...(simulator ? { IOS_SIMULATOR: simulator } : {}),
|
|
182
|
+
...(c.slotId ? { SLOT_ID: c.slotId } : {}),
|
|
183
|
+
});
|
|
184
|
+
} catch {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function resolveMobileRuntimePorts(repo) {
|
|
190
|
+
return (
|
|
191
|
+
resolveMobileRuntimeContext(repo)
|
|
192
|
+
?? resolveFarmslotPortsByRepo(repo)
|
|
193
|
+
?? resolveMobileSlotDefaults(repo)
|
|
194
|
+
?? ''
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export const cliFns = {
|
|
199
|
+
resolve_farmslot_ports_by_repo: resolveFarmslotPortsByRepo,
|
|
200
|
+
resolve_default_extension_ports: resolveDefaultExtensionPorts,
|
|
201
|
+
resolve_extension_runtime_ports: (repo) => resolveExtensionRuntimePorts(repo) || null,
|
|
202
|
+
resolve_mobile_runtime_context: resolveMobileRuntimeContext,
|
|
203
|
+
resolve_mobile_slot_defaults: resolveMobileSlotDefaults,
|
|
204
|
+
resolve_mobile_runtime_ports: (repo) => resolveMobileRuntimePorts(repo) || null,
|
|
205
|
+
};
|
|
@@ -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`);
|