@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
|
@@ -1,205 +1,3 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
3
|
-
|
|
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
|
-
};
|
|
1
|
+
// Back-compat shim: the port-resolution core is now resolve-slot-ports-core.mjs.
|
|
2
|
+
// Re-exports it verbatim for one release so importers of the old path keep resolving.
|
|
3
|
+
export * from './resolve-slot-ports-core.mjs';
|
|
@@ -1,20 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
|
|
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`);
|
|
2
|
+
// Back-compat shim: this leaf is now resolve-slot-ports.mjs. Delegates to it for one
|
|
3
|
+
// release so `node resolve-farmslot-ports.mjs <fn> <repo>` keeps working (the new CLI
|
|
4
|
+
// reads the same argv positions on import).
|
|
5
|
+
import './resolve-slot-ports.mjs';
|
|
@@ -1,105 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
|
|
6
|
-
#
|
|
7
|
-
|
|
8
|
-
# . resolve-farmslot-ports.sh
|
|
9
|
-
# resolve_extension_runtime_ports /path/to/metamask-extension-N
|
|
10
|
-
# -> prints CDP_PORT=... WATCHER_PORT=... SLOT_ID=... lines
|
|
11
|
-
|
|
12
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
13
|
-
_RESOLVE_PORTS_MJS="$SCRIPT_DIR/resolve-farmslot-ports.mjs"
|
|
14
|
-
|
|
15
|
-
_resolve_ports_cli() {
|
|
16
|
-
node "$_RESOLVE_PORTS_MJS" "$1" "${2:-.}"
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
resolve_farmslot_ports_by_repo() {
|
|
20
|
-
_resolve_ports_cli resolve_farmslot_ports_by_repo "$1"
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
infer_extension_slot_suffix() {
|
|
24
|
-
local repo="$1" base
|
|
25
|
-
base="$(basename "$repo")"
|
|
26
|
-
if [[ "$base" =~ -([0-9]+)$ ]]; then
|
|
27
|
-
printf '%s' "${BASH_REMATCH[1]}"
|
|
28
|
-
return 0
|
|
29
|
-
fi
|
|
30
|
-
return 1
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
resolve_default_extension_ports() {
|
|
34
|
-
_resolve_ports_cli resolve_default_extension_ports "$1"
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
resolve_extension_runtime_ports() {
|
|
38
|
-
_resolve_ports_cli resolve_extension_runtime_ports "${1:-.}"
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
apply_resolved_extension_ports() {
|
|
42
|
-
local repo="${1:-.}" line key val from_pool=false resolved=""
|
|
43
|
-
if resolved="$(_resolve_ports_cli resolve_farmslot_ports_by_repo "$repo" 2>/dev/null)"; then
|
|
44
|
-
from_pool=true
|
|
45
|
-
elif resolved="$(_resolve_ports_cli resolve_default_extension_ports "$repo" 2>/dev/null)"; then
|
|
46
|
-
from_pool=false
|
|
47
|
-
else
|
|
48
|
-
return 0
|
|
49
|
-
fi
|
|
50
|
-
while IFS= read -r line; do
|
|
51
|
-
[ -n "$line" ] || continue
|
|
52
|
-
key="${line%%=*}"
|
|
53
|
-
val="${line#*=}"
|
|
54
|
-
case "$key" in
|
|
55
|
-
CDP_PORT)
|
|
56
|
-
if [ "$from_pool" = true ] || [ -z "${CDP_PORT:-}" ]; then CDP_PORT="$val"; fi
|
|
57
|
-
;;
|
|
58
|
-
WATCHER_PORT)
|
|
59
|
-
if [ "$from_pool" = true ] || [ -z "${WATCHER_PORT:-}" ]; then WATCHER_PORT="$val"; fi
|
|
60
|
-
;;
|
|
61
|
-
SLOT_ID)
|
|
62
|
-
if [ "$from_pool" = true ] || [ -z "${RECIPE_SLOT_ID:-}" ]; then RECIPE_SLOT_ID="$val"; fi
|
|
63
|
-
;;
|
|
64
|
-
esac
|
|
65
|
-
done <<< "$resolved"
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
resolve_mobile_slot_defaults() {
|
|
69
|
-
_resolve_ports_cli resolve_mobile_slot_defaults "$1"
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
resolve_mobile_runtime_context() {
|
|
73
|
-
_resolve_ports_cli resolve_mobile_runtime_context "$1"
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
resolve_mobile_runtime_ports() {
|
|
77
|
-
_resolve_ports_cli resolve_mobile_runtime_ports "${1:-.}"
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
apply_resolved_mobile_ports() {
|
|
81
|
-
local repo="${1:-.}" line key val from_pool=false resolved=""
|
|
82
|
-
if resolved="$(_resolve_ports_cli resolve_farmslot_ports_by_repo "$repo" 2>/dev/null)"; then
|
|
83
|
-
from_pool=true
|
|
84
|
-
elif resolved="$(_resolve_ports_cli resolve_mobile_slot_defaults "$repo" 2>/dev/null)"; then
|
|
85
|
-
from_pool=false
|
|
86
|
-
else
|
|
87
|
-
return 0
|
|
88
|
-
fi
|
|
89
|
-
while IFS= read -r line; do
|
|
90
|
-
[ -n "$line" ] || continue
|
|
91
|
-
key="${line%%=*}"
|
|
92
|
-
val="${line#*=}"
|
|
93
|
-
case "$key" in
|
|
94
|
-
WATCHER_PORT)
|
|
95
|
-
if [ "$from_pool" = true ] || [ -z "${WATCHER_PORT:-}" ]; then WATCHER_PORT="$val"; fi
|
|
96
|
-
;;
|
|
97
|
-
IOS_SIMULATOR)
|
|
98
|
-
if [ "$from_pool" = true ] || [ -z "${IOS_SIMULATOR:-}" ]; then IOS_SIMULATOR="$val"; fi
|
|
99
|
-
;;
|
|
100
|
-
SLOT_ID)
|
|
101
|
-
if [ "$from_pool" = true ] || [ -z "${RECIPE_SLOT_ID:-}" ]; then RECIPE_SLOT_ID="$val"; fi
|
|
102
|
-
;;
|
|
103
|
-
esac
|
|
104
|
-
done <<< "$resolved"
|
|
105
|
-
}
|
|
2
|
+
# Back-compat shim: this wrapper is now resolve-slot-ports.sh. Sources it for one
|
|
3
|
+
# release so old callers keep the same sourced function names (including the
|
|
4
|
+
# resolve_farmslot_ports_by_repo alias defined there).
|
|
5
|
+
_SHIM_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
|
+
# shellcheck disable=SC1091
|
|
7
|
+
. "$_SHIM_DIR/resolve-slot-ports.sh"
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
// Slot-pool + slot-suffix port resolution — pure JS for runner and overlay leaves.
|
|
2
|
+
// Reads the orchestrator's pool JSON (the on-disk `pool/` directory) when a slot
|
|
3
|
+
// repo matches, else the local-extension-N suffix formula.
|
|
4
|
+
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import os from 'node:os';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
|
|
10
|
+
const coreDir = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
|
|
12
|
+
function pathDefault(key) {
|
|
13
|
+
for (const candidate of [
|
|
14
|
+
path.join(coreDir, 'path-defaults.json'),
|
|
15
|
+
path.join(coreDir, '../../adapters/shared/path-defaults.json'),
|
|
16
|
+
]) {
|
|
17
|
+
if (!fs.existsSync(candidate)) continue;
|
|
18
|
+
const value = JSON.parse(fs.readFileSync(candidate, 'utf8'))[key];
|
|
19
|
+
if (value) return value;
|
|
20
|
+
}
|
|
21
|
+
throw new Error(`Missing path default: ${key}`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function recipeRuntimeDir() {
|
|
25
|
+
return process.env.RECIPE_RUNTIME_DIR || pathDefault('recipeRuntimeDir');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function formatKvLines(kv) {
|
|
29
|
+
const lines = [];
|
|
30
|
+
if (kv.CDP_PORT !== undefined) lines.push(`CDP_PORT=${kv.CDP_PORT}`);
|
|
31
|
+
if (kv.WATCHER_PORT !== undefined) lines.push(`WATCHER_PORT=${kv.WATCHER_PORT}`);
|
|
32
|
+
if (kv.SLOT_ID) lines.push(`SLOT_ID=${kv.SLOT_ID}`);
|
|
33
|
+
if (kv.IOS_SIMULATOR) lines.push(`IOS_SIMULATOR=${kv.IOS_SIMULATOR}`);
|
|
34
|
+
return lines.length ? `${lines.join('\n')}\n` : '';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function realRepoPath(repo) {
|
|
38
|
+
if (!repo) return null;
|
|
39
|
+
try {
|
|
40
|
+
return fs.realpathSync(repo);
|
|
41
|
+
} catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function inferSlotSuffix(repo) {
|
|
47
|
+
const base = path.basename(repo);
|
|
48
|
+
const m = /-(\d+)$/u.exec(base);
|
|
49
|
+
return m ? m[1] : null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function scoreSlot(slot, slotSuffix) {
|
|
53
|
+
const resources = slot.resources ?? {};
|
|
54
|
+
const cdp = resources.browser?.cdp_port;
|
|
55
|
+
const port = resources['dev-server']?.port;
|
|
56
|
+
const simulator = resources['ios-sim']?.simulator;
|
|
57
|
+
const session = slot.session ?? '';
|
|
58
|
+
const slotId = slot.id ?? '';
|
|
59
|
+
let score = 0;
|
|
60
|
+
if (cdp !== undefined) score += 100;
|
|
61
|
+
if (port !== undefined) score += 10;
|
|
62
|
+
if (slotSuffix) {
|
|
63
|
+
if (session === `mme-${slotSuffix}`) score += 50;
|
|
64
|
+
if (slotId.endsWith(`mme-${slotSuffix}`) || slotId.includes(`-mme-${slotSuffix}`)) score += 40;
|
|
65
|
+
}
|
|
66
|
+
if (!slotId.toLowerCase().includes('demo')) score += 5;
|
|
67
|
+
return [score, cdp, port, slotId, simulator];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function resolveSlotPortsByRepo(repo) {
|
|
71
|
+
const realRepo = realRepoPath(repo);
|
|
72
|
+
if (!realRepo) return null;
|
|
73
|
+
|
|
74
|
+
const slotSuffix = inferSlotSuffix(realRepo);
|
|
75
|
+
const roots = [];
|
|
76
|
+
// A slot checkout lives at <workspace>/repos/<slot>; that workspace's own
|
|
77
|
+
// pool is the authoritative one for this repo — machine-global fallbacks
|
|
78
|
+
// (env root, dev checkout) apply only when the repo is not workspace-shaped.
|
|
79
|
+
if (path.basename(path.dirname(realRepo)) === 'repos') {
|
|
80
|
+
roots.push(path.join(path.dirname(path.dirname(realRepo)), 'farmslot'));
|
|
81
|
+
}
|
|
82
|
+
if (process.env.FARMSLOT_ROOT) roots.push(process.env.FARMSLOT_ROOT);
|
|
83
|
+
roots.push(path.join(os.homedir(), 'dev', 'farmslot'));
|
|
84
|
+
|
|
85
|
+
for (const root of roots) {
|
|
86
|
+
if (!root || !fs.existsSync(path.join(root, 'pool'))) continue;
|
|
87
|
+
const poolDir = path.join(root, 'pool');
|
|
88
|
+
let best = null;
|
|
89
|
+
|
|
90
|
+
for (const name of fs.readdirSync(poolDir).sort()) {
|
|
91
|
+
if (!name.endsWith('.json') || name.includes('.bak.')) continue;
|
|
92
|
+
let data;
|
|
93
|
+
try {
|
|
94
|
+
data = JSON.parse(fs.readFileSync(path.join(poolDir, name), 'utf8'));
|
|
95
|
+
} catch {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
for (const slot of data.slots ?? []) {
|
|
99
|
+
const slotRepo = slot.repo ?? '';
|
|
100
|
+
if (!slotRepo) continue;
|
|
101
|
+
let slotReal;
|
|
102
|
+
try {
|
|
103
|
+
slotReal = fs.realpathSync(slotRepo);
|
|
104
|
+
} catch {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (slotReal !== realRepo) continue;
|
|
108
|
+
const scored = scoreSlot(slot, slotSuffix);
|
|
109
|
+
if (!best || scored[0] > best[0]) best = scored;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (best) {
|
|
114
|
+
const [, cdp, port, slotId, simulator] = best;
|
|
115
|
+
return formatKvLines({
|
|
116
|
+
...(cdp !== undefined ? { CDP_PORT: cdp } : {}),
|
|
117
|
+
...(port !== undefined ? { WATCHER_PORT: port } : {}),
|
|
118
|
+
...(slotId ? { SLOT_ID: slotId } : {}),
|
|
119
|
+
...(simulator ? { IOS_SIMULATOR: simulator } : {}),
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function resolveDefaultExtensionPorts(repo) {
|
|
127
|
+
const n = inferSlotSuffix(repo);
|
|
128
|
+
if (!n) return null;
|
|
129
|
+
return formatKvLines({
|
|
130
|
+
CDP_PORT: 6660 + Number(n),
|
|
131
|
+
WATCHER_PORT: 9010 + Number(n),
|
|
132
|
+
SLOT_ID: `local-extension-${n}`,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function resolveExtensionRuntimePorts(repo) {
|
|
137
|
+
return resolveSlotPortsByRepo(repo) ?? resolveDefaultExtensionPorts(repo) ?? '';
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function resolveMobileSlotDefaults(repo) {
|
|
141
|
+
const n = inferSlotSuffix(repo);
|
|
142
|
+
if (!n) return null;
|
|
143
|
+
return formatKvLines({
|
|
144
|
+
WATCHER_PORT: 8060 + Number(n),
|
|
145
|
+
IOS_SIMULATOR: `mm-${n}`,
|
|
146
|
+
SLOT_ID: `local-mobile-${n}`,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function resolveMobileRuntimeContext(repo) {
|
|
151
|
+
const ctxPath = path.join(repo, recipeRuntimeDir(), 'agentic-runtime.json');
|
|
152
|
+
if (fs.existsSync(ctxPath)) {
|
|
153
|
+
try {
|
|
154
|
+
const c = JSON.parse(fs.readFileSync(ctxPath, 'utf8'));
|
|
155
|
+
if (c.simulator || (c.metroPort != null && c.metroPort !== '')) {
|
|
156
|
+
return formatKvLines({
|
|
157
|
+
...(c.metroPort != null && c.metroPort !== '' ? { WATCHER_PORT: c.metroPort } : {}),
|
|
158
|
+
...(c.simulator ? { IOS_SIMULATOR: c.simulator } : {}),
|
|
159
|
+
...(c.slotId ? { SLOT_ID: c.slotId } : {}),
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
} catch {
|
|
163
|
+
/* unreadable context falls through to the provision baseline */
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return resolveMobileProvisionBaseline(repo);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// A provisioned-but-unprepared slot has no agentic-runtime.json yet, but the
|
|
170
|
+
// provision baseline records the same authoritative simulator/port identity —
|
|
171
|
+
// without it, a fresh slot's first launch degrades to the simctl `booted`
|
|
172
|
+
// alias and misses the installed dev client entirely.
|
|
173
|
+
function resolveMobileProvisionBaseline(repo) {
|
|
174
|
+
const basePath = path.join(repo, recipeRuntimeDir(), 'runway-provision.json');
|
|
175
|
+
if (!fs.existsSync(basePath)) return null;
|
|
176
|
+
try {
|
|
177
|
+
const c = JSON.parse(fs.readFileSync(basePath, 'utf8'));
|
|
178
|
+
const simulator = c.simulator?.name || c.simulator?.udid;
|
|
179
|
+
const watcherPort = c.watcherPort != null && c.watcherPort !== '' ? c.watcherPort : undefined;
|
|
180
|
+
if (!simulator && watcherPort === undefined) return null;
|
|
181
|
+
return formatKvLines({
|
|
182
|
+
...(watcherPort !== undefined ? { WATCHER_PORT: watcherPort } : {}),
|
|
183
|
+
...(simulator ? { IOS_SIMULATOR: simulator } : {}),
|
|
184
|
+
...(c.slotId ? { SLOT_ID: c.slotId } : {}),
|
|
185
|
+
});
|
|
186
|
+
} catch {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function resolveMobileRuntimePorts(repo) {
|
|
192
|
+
return (
|
|
193
|
+
resolveMobileRuntimeContext(repo)
|
|
194
|
+
?? resolveSlotPortsByRepo(repo)
|
|
195
|
+
?? resolveMobileSlotDefaults(repo)
|
|
196
|
+
?? ''
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Back-compat: the previous name for resolveSlotPortsByRepo, kept as an alias for
|
|
201
|
+
// one release so existing importers keep resolving.
|
|
202
|
+
export const resolveFarmslotPortsByRepo = resolveSlotPortsByRepo;
|
|
203
|
+
|
|
204
|
+
export const cliFns = {
|
|
205
|
+
resolve_slot_ports_by_repo: resolveSlotPortsByRepo,
|
|
206
|
+
// Back-compat cli key for one release.
|
|
207
|
+
resolve_farmslot_ports_by_repo: resolveSlotPortsByRepo,
|
|
208
|
+
resolve_default_extension_ports: resolveDefaultExtensionPorts,
|
|
209
|
+
resolve_extension_runtime_ports: (repo) => resolveExtensionRuntimePorts(repo) || null,
|
|
210
|
+
resolve_mobile_runtime_context: resolveMobileRuntimeContext,
|
|
211
|
+
resolve_mobile_slot_defaults: resolveMobileSlotDefaults,
|
|
212
|
+
resolve_mobile_runtime_ports: (repo) => resolveMobileRuntimePorts(repo) || null,
|
|
213
|
+
};
|
|
@@ -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-slot-ports-core.mjs';
|
|
5
|
+
|
|
6
|
+
const [, , fn, repo] = process.argv;
|
|
7
|
+
if (!fn || fn === '-h' || fn === '--help') {
|
|
8
|
+
process.stderr.write('Usage: resolve-slot-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-slot-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`);
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# resolve-slot-ports.sh — bash-compat wrapper over resolve-slot-ports.mjs
|
|
3
|
+
#
|
|
4
|
+
# The orchestrator's pool JSON is authoritative when a slot repo matches; otherwise
|
|
5
|
+
# fall back to the local-extension-N formula (6660+N / 9010+N).
|
|
6
|
+
#
|
|
7
|
+
# Usage:
|
|
8
|
+
# . resolve-slot-ports.sh
|
|
9
|
+
# resolve_extension_runtime_ports /path/to/metamask-extension-N
|
|
10
|
+
# -> prints CDP_PORT=... WATCHER_PORT=... SLOT_ID=... lines
|
|
11
|
+
|
|
12
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
13
|
+
_RESOLVE_PORTS_MJS="$SCRIPT_DIR/resolve-slot-ports.mjs"
|
|
14
|
+
|
|
15
|
+
_resolve_ports_cli() {
|
|
16
|
+
node "$_RESOLVE_PORTS_MJS" "$1" "${2:-.}"
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
resolve_slot_ports_by_repo() {
|
|
20
|
+
_resolve_ports_cli resolve_slot_ports_by_repo "$1"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
# Back-compat alias for the previous function name (one release).
|
|
24
|
+
resolve_farmslot_ports_by_repo() {
|
|
25
|
+
resolve_slot_ports_by_repo "$1"
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
infer_extension_slot_suffix() {
|
|
29
|
+
local repo="$1" base
|
|
30
|
+
base="$(basename "$repo")"
|
|
31
|
+
if [[ "$base" =~ -([0-9]+)$ ]]; then
|
|
32
|
+
printf '%s' "${BASH_REMATCH[1]}"
|
|
33
|
+
return 0
|
|
34
|
+
fi
|
|
35
|
+
return 1
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
resolve_default_extension_ports() {
|
|
39
|
+
_resolve_ports_cli resolve_default_extension_ports "$1"
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
resolve_extension_runtime_ports() {
|
|
43
|
+
_resolve_ports_cli resolve_extension_runtime_ports "${1:-.}"
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
apply_resolved_extension_ports() {
|
|
47
|
+
local repo="${1:-.}" line key val from_pool=false resolved=""
|
|
48
|
+
if resolved="$(_resolve_ports_cli resolve_slot_ports_by_repo "$repo" 2>/dev/null)"; then
|
|
49
|
+
from_pool=true
|
|
50
|
+
elif resolved="$(_resolve_ports_cli resolve_default_extension_ports "$repo" 2>/dev/null)"; then
|
|
51
|
+
from_pool=false
|
|
52
|
+
else
|
|
53
|
+
return 0
|
|
54
|
+
fi
|
|
55
|
+
while IFS= read -r line; do
|
|
56
|
+
[ -n "$line" ] || continue
|
|
57
|
+
key="${line%%=*}"
|
|
58
|
+
val="${line#*=}"
|
|
59
|
+
case "$key" in
|
|
60
|
+
CDP_PORT)
|
|
61
|
+
if [ "$from_pool" = true ] || [ -z "${CDP_PORT:-}" ]; then CDP_PORT="$val"; fi
|
|
62
|
+
;;
|
|
63
|
+
WATCHER_PORT)
|
|
64
|
+
if [ "$from_pool" = true ] || [ -z "${WATCHER_PORT:-}" ]; then WATCHER_PORT="$val"; fi
|
|
65
|
+
;;
|
|
66
|
+
SLOT_ID)
|
|
67
|
+
if [ "$from_pool" = true ] || [ -z "${RECIPE_SLOT_ID:-}" ]; then RECIPE_SLOT_ID="$val"; fi
|
|
68
|
+
;;
|
|
69
|
+
esac
|
|
70
|
+
done <<< "$resolved"
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
resolve_mobile_slot_defaults() {
|
|
74
|
+
_resolve_ports_cli resolve_mobile_slot_defaults "$1"
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
resolve_mobile_runtime_context() {
|
|
78
|
+
_resolve_ports_cli resolve_mobile_runtime_context "$1"
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
resolve_mobile_runtime_ports() {
|
|
82
|
+
_resolve_ports_cli resolve_mobile_runtime_ports "${1:-.}"
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
apply_resolved_mobile_ports() {
|
|
86
|
+
local repo="${1:-.}" line key val from_pool=false resolved=""
|
|
87
|
+
if resolved="$(_resolve_ports_cli resolve_slot_ports_by_repo "$repo" 2>/dev/null)"; then
|
|
88
|
+
from_pool=true
|
|
89
|
+
elif resolved="$(_resolve_ports_cli resolve_mobile_slot_defaults "$repo" 2>/dev/null)"; then
|
|
90
|
+
from_pool=false
|
|
91
|
+
else
|
|
92
|
+
return 0
|
|
93
|
+
fi
|
|
94
|
+
while IFS= read -r line; do
|
|
95
|
+
[ -n "$line" ] || continue
|
|
96
|
+
key="${line%%=*}"
|
|
97
|
+
val="${line#*=}"
|
|
98
|
+
case "$key" in
|
|
99
|
+
WATCHER_PORT)
|
|
100
|
+
if [ "$from_pool" = true ] || [ -z "${WATCHER_PORT:-}" ]; then WATCHER_PORT="$val"; fi
|
|
101
|
+
;;
|
|
102
|
+
IOS_SIMULATOR)
|
|
103
|
+
if [ "$from_pool" = true ] || [ -z "${IOS_SIMULATOR:-}" ]; then IOS_SIMULATOR="$val"; fi
|
|
104
|
+
;;
|
|
105
|
+
SLOT_ID)
|
|
106
|
+
if [ "$from_pool" = true ] || [ -z "${RECIPE_SLOT_ID:-}" ]; then RECIPE_SLOT_ID="$val"; fi
|
|
107
|
+
;;
|
|
108
|
+
esac
|
|
109
|
+
done <<< "$resolved"
|
|
110
|
+
}
|