@deeeed/metamask-harness 0.68.0 → 0.70.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 +21 -0
- package/README.md +97 -0
- package/adapters/extension/ensure-browser.sh +85 -15
- package/adapters/extension/inject.mjs +2 -0
- package/adapters/extension/launch-browser.cjs +140 -18
- package/adapters/extension/lib/macos-focus.cjs +67 -195
- package/adapters/extension/live.sh +29 -48
- package/adapters/manifest.json +20 -4
- package/adapters/shared/browser-cdp.cjs +528 -0
- package/adapters/shared/browser-resolver.cjs +996 -0
- package/adapters/shared/open-debug.mjs +6 -1
- package/dist/adapters/mobile/prepare.js +7 -4
- package/dist/adapters/mobile/runtime-decision.js +8 -10
- package/dist/command-contract.js +7 -0
- package/dist/commands/call.js +3 -0
- package/dist/commands/launch/index.js +19 -0
- package/dist/commands/list-executables.js +62 -11
- package/dist/commands/manifest.js +11 -4
- package/dist/commands/run-engine.js +8 -1
- package/dist/commands/run-report.js +96 -3
- package/dist/commands/run.js +5 -1
- package/dist/doctor.js +48 -1
- package/dist/mm-harness-cli.js +7 -0
- package/docs/QA.md +3 -0
- package/docs/RECIPES.md +10 -3
- package/library/actions/extension/platform/cdp.mjs +87 -46
- package/package.json +1 -1
- package/scripts/completions.sh +2 -2
- package/scripts/site-contrast.mjs +16 -3
|
@@ -1,236 +1,108 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
// macOS focus helpers for launchers that start a headed browser in the
|
|
4
|
+
// background. One owner (the launcher) and one decision: after the launch
|
|
5
|
+
// settles, if the frontmost process is one of OUR browser pids, give the front
|
|
6
|
+
// back to the app that had it, once, by pid. Anything else in front (the
|
|
7
|
+
// operator's own Chrome included, whatever its bundle) is left alone. A restore
|
|
8
|
+
// only activates an already-running app (never `open -a`, which can launch).
|
|
9
|
+
// MM_HARNESS_FOCUS_HOLD=0 makes every function here a no-op (tests).
|
|
5
10
|
|
|
6
|
-
|
|
7
|
-
// spawning far fewer lsappinfo/open subprocesses per second.
|
|
8
|
-
const HOLD_INTERVAL_MS = 80;
|
|
9
|
-
// Upper bound for one Launch Services query or `open -a`. A short bound made a
|
|
10
|
-
// loaded Mac time out the first query, which silently disabled the whole hold.
|
|
11
|
-
const LS_TIMEOUT_MS = 2000;
|
|
12
|
-
// Hard cap on a hold whose parent is alive but never released it.
|
|
13
|
-
const HOLD_MAX_MS = 120_000;
|
|
14
|
-
const toolEnv = () => ({ ...process.env });
|
|
11
|
+
const { execFileSync } = require('node:child_process');
|
|
15
12
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
} catch {
|
|
33
|
-
// Best effort: lsappinfo missing, timed out, or the app exited; no focus to preserve.
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
13
|
+
// Upper bound for one Launch Services query or activation.
|
|
14
|
+
const LS_TIMEOUT_MS = 2000;
|
|
15
|
+
// Activates a running app by pid; prints "missing" for a pid with no app.
|
|
16
|
+
const ACTIVATE_BY_PID = [
|
|
17
|
+
'function run(argv) {',
|
|
18
|
+
" ObjC.import('AppKit');",
|
|
19
|
+
' const app = $.NSRunningApplication.runningApplicationWithProcessIdentifier(Number(argv[0]));',
|
|
20
|
+
" if (!app || app.isNil()) return 'missing';",
|
|
21
|
+
" return app.activateWithOptions($.NSApplicationActivateIgnoringOtherApps) ? 'ok' : 'failed';",
|
|
22
|
+
'}',
|
|
23
|
+
].join('\n');
|
|
24
|
+
|
|
25
|
+
function macFocusDisabled() {
|
|
26
|
+
return process.platform !== 'darwin' ||
|
|
27
|
+
process.env.MM_HARNESS_FOCUS_HOLD === '0' ||
|
|
28
|
+
process.env.MM_HARNESS_FOCUS_BROWSER === '1';
|
|
36
29
|
}
|
|
37
30
|
|
|
38
|
-
|
|
39
|
-
|
|
31
|
+
// At most one restore per launcher process.
|
|
32
|
+
let restored = false;
|
|
33
|
+
|
|
34
|
+
function lsappinfo(args) {
|
|
35
|
+
return execFileSync('lsappinfo', args, {
|
|
36
|
+
encoding: 'utf8',
|
|
37
|
+
env: { ...process.env },
|
|
38
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
39
|
+
timeout: LS_TIMEOUT_MS,
|
|
40
|
+
});
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
function parseLsappinfo(info) {
|
|
43
44
|
const pid = Number.parseInt((info.match(/\bpid\s*=\s*(\d+)/u) || info.match(/"pid"=(\d+)/u) || [])[1], 10);
|
|
44
45
|
const bundlePath = (info.match(/bundle path="([^"]+)"/u) || info.match(/"LSBundlePath"="([^"]+)"/u) || [])[1];
|
|
45
46
|
const name = (info.match(/^"([^"]+)"/mu) || info.match(/"LSDisplayName"="([^"]+)"/u) || [])[1];
|
|
46
|
-
if (!Number.isInteger(pid) || pid <= 0
|
|
47
|
-
return { pid, bundlePath, name: name || bundlePath };
|
|
47
|
+
if (!Number.isInteger(pid) || pid <= 0) return null;
|
|
48
|
+
return { pid, bundlePath: bundlePath || null, name: name || bundlePath || `pid ${pid}` };
|
|
48
49
|
}
|
|
49
50
|
|
|
50
|
-
|
|
51
|
+
// The frontmost app as { pid, bundlePath, name }, or null when unknown.
|
|
52
|
+
function captureMacFrontmost() {
|
|
53
|
+
if (macFocusDisabled()) return null;
|
|
51
54
|
try {
|
|
52
|
-
const asn =
|
|
53
|
-
|
|
54
|
-
env: toolEnv(),
|
|
55
|
-
stdio: ['ignore', 'pipe', 'ignore'],
|
|
56
|
-
timeout: LS_TIMEOUT_MS,
|
|
57
|
-
}).trim();
|
|
58
|
-
if (!asn) return null;
|
|
59
|
-
const value = execFileSync('lsappinfo', ['info', '-only', 'pid', asn], {
|
|
60
|
-
encoding: 'utf8',
|
|
61
|
-
env: toolEnv(),
|
|
62
|
-
stdio: ['ignore', 'pipe', 'ignore'],
|
|
63
|
-
timeout: LS_TIMEOUT_MS,
|
|
64
|
-
}).trim();
|
|
65
|
-
const pid = Number.parseInt((value.match(/"pid"=(\d+)/u) || [])[1], 10);
|
|
66
|
-
return Number.isInteger(pid) && pid > 0 ? pid : null;
|
|
55
|
+
const asn = lsappinfo(['front']).trim();
|
|
56
|
+
return asn ? parseLsappinfo(lsappinfo(['info', asn])) : null;
|
|
67
57
|
} catch {
|
|
68
|
-
// Best effort: lsappinfo
|
|
58
|
+
// Best effort: lsappinfo missing, timed out, or the app exited.
|
|
69
59
|
return null;
|
|
70
60
|
}
|
|
71
61
|
}
|
|
72
62
|
|
|
73
|
-
function
|
|
74
|
-
|
|
75
|
-
try {
|
|
76
|
-
execFileSync('open', ['-a', target.bundlePath], {
|
|
77
|
-
env: toolEnv(),
|
|
78
|
-
stdio: 'ignore',
|
|
79
|
-
timeout: LS_TIMEOUT_MS,
|
|
80
|
-
});
|
|
81
|
-
return true;
|
|
82
|
-
} catch {
|
|
83
|
-
// Best effort: `open -a` failed or timed out; the operator keeps whatever is in front.
|
|
84
|
-
return false;
|
|
85
|
-
}
|
|
63
|
+
function macFrontmostPid() {
|
|
64
|
+
return captureMacFrontmost()?.pid ?? null;
|
|
86
65
|
}
|
|
87
66
|
|
|
88
|
-
|
|
89
|
-
|
|
67
|
+
// Bring an already-running app to the front by pid. Never launches anything.
|
|
68
|
+
function activateMacAppByPid(pid) {
|
|
69
|
+
if (macFocusDisabled() || !Number.isInteger(pid) || pid <= 0) return false;
|
|
90
70
|
try {
|
|
91
|
-
|
|
71
|
+
return execFileSync('osascript', ['-l', 'JavaScript', '-e', ACTIVATE_BY_PID, String(pid)], {
|
|
92
72
|
encoding: 'utf8',
|
|
93
|
-
env:
|
|
73
|
+
env: { ...process.env },
|
|
94
74
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
95
75
|
timeout: LS_TIMEOUT_MS,
|
|
96
|
-
}).trim();
|
|
97
|
-
if (!asn) return false;
|
|
98
|
-
return restoreMacFrontmost(parseLsappinfo(execFileSync('lsappinfo', ['info', asn], {
|
|
99
|
-
encoding: 'utf8',
|
|
100
|
-
env: toolEnv(),
|
|
101
|
-
stdio: ['ignore', 'pipe', 'ignore'],
|
|
102
|
-
timeout: LS_TIMEOUT_MS,
|
|
103
|
-
})));
|
|
76
|
+
}).trim() === 'ok';
|
|
104
77
|
} catch {
|
|
105
|
-
// Best effort:
|
|
78
|
+
// Best effort: osascript failed or timed out; the operator keeps whatever is in front.
|
|
106
79
|
return false;
|
|
107
80
|
}
|
|
108
81
|
}
|
|
109
82
|
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
if (front
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
function preserveMacFrontmostProcess(pid) {
|
|
124
|
-
if (process.env.MM_HARNESS_FOCUS_BROWSER === '1') return false;
|
|
125
|
-
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
126
|
-
const current = currentFrontmostPid();
|
|
127
|
-
if (current === pid) return true;
|
|
128
|
-
return restoreMacFrontmostProcess(pid);
|
|
83
|
+
// One check, at most one restore: 'restored' when our browser had the front
|
|
84
|
+
// and `previous` got it back, 'kept' when anything else is in front (left as
|
|
85
|
+
// is), 'off' when focus handling is disabled or there is nothing to restore.
|
|
86
|
+
// A click on the slot window right after launch can therefore be undone once.
|
|
87
|
+
function restoreFrontmostIfOurs(previous, ourPids) {
|
|
88
|
+
if (macFocusDisabled() || restored || !Number.isInteger(previous?.pid) || previous.pid <= 0) return 'off';
|
|
89
|
+
const ours = new Set((ourPids || []).filter((pid) => Number.isInteger(pid) && pid > 0));
|
|
90
|
+
if (ours.size === 0 || ours.has(previous.pid)) return 'off';
|
|
91
|
+
const front = macFrontmostPid();
|
|
92
|
+
if (front === null || !ours.has(front)) return 'kept';
|
|
93
|
+
restored = true;
|
|
94
|
+
return activateMacAppByPid(previous.pid) ? 'restored' : 'kept';
|
|
129
95
|
}
|
|
130
96
|
|
|
131
97
|
function macBackgroundOpenArgs(application, chromeArgs) {
|
|
132
98
|
return ['-g', '-n', '-a', application, '--args', ...chromeArgs];
|
|
133
99
|
}
|
|
134
100
|
|
|
135
|
-
// Keep the previously focused app in front while `launchedBundlePath` starts.
|
|
136
|
-
// The hold only counters the launched browser: the moment any other app is
|
|
137
|
-
// brought to the front, the operator switched on purpose and the hold ends.
|
|
138
|
-
function startMacFocusHold(targetOrPid, launchedBundlePath) {
|
|
139
|
-
if (process.env.MM_HARNESS_FOCUS_BROWSER === '1') return null;
|
|
140
|
-
if (process.platform !== 'darwin') return null;
|
|
141
|
-
const target = typeof targetOrPid === 'number'
|
|
142
|
-
? { pid: targetOrPid, bundlePath: bundlePathForPid(targetOrPid) }
|
|
143
|
-
: targetOrPid;
|
|
144
|
-
if (!target?.bundlePath || !Number.isInteger(target.pid) || target.pid <= 0) return null;
|
|
145
|
-
return spawn(process.execPath, [
|
|
146
|
-
__filename,
|
|
147
|
-
'--hold',
|
|
148
|
-
String(target.pid),
|
|
149
|
-
target.bundlePath,
|
|
150
|
-
String(process.pid),
|
|
151
|
-
launchedBundlePath || '',
|
|
152
|
-
], { stdio: 'ignore', env: toolEnv() });
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function sameBundle(left, right) {
|
|
156
|
-
if (!left || !right) return false;
|
|
157
|
-
const real = (value) => {
|
|
158
|
-
try {
|
|
159
|
-
return fs.realpathSync(value);
|
|
160
|
-
} catch {
|
|
161
|
-
// A path that cannot be resolved is compared as given.
|
|
162
|
-
return value;
|
|
163
|
-
}
|
|
164
|
-
};
|
|
165
|
-
return real(left) === real(right);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function bundlePathForPid(pid) {
|
|
169
|
-
try {
|
|
170
|
-
const asn = execFileSync('lsappinfo', ['find', `pid=${pid}`], {
|
|
171
|
-
encoding: 'utf8',
|
|
172
|
-
env: toolEnv(),
|
|
173
|
-
stdio: ['ignore', 'pipe', 'ignore'],
|
|
174
|
-
timeout: LS_TIMEOUT_MS,
|
|
175
|
-
}).trim();
|
|
176
|
-
return parseLsappinfo(execFileSync('lsappinfo', ['info', asn], {
|
|
177
|
-
encoding: 'utf8',
|
|
178
|
-
env: toolEnv(),
|
|
179
|
-
stdio: ['ignore', 'pipe', 'ignore'],
|
|
180
|
-
timeout: LS_TIMEOUT_MS,
|
|
181
|
-
}))?.bundlePath ?? null;
|
|
182
|
-
} catch {
|
|
183
|
-
// Best effort: lsappinfo unavailable, timed out, or the pid has no Launch Services entry; the hold is skipped.
|
|
184
|
-
return null;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function stopMacFocusHold(child) {
|
|
189
|
-
if (!child || !Number.isInteger(child.pid) || child.pid <= 0) return;
|
|
190
|
-
try {
|
|
191
|
-
process.kill(child.pid, 'SIGTERM');
|
|
192
|
-
} catch (error) {
|
|
193
|
-
if (error.code !== 'ESRCH') throw error;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
function parentAlive(pid) {
|
|
198
|
-
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
199
|
-
try {
|
|
200
|
-
process.kill(pid, 0);
|
|
201
|
-
return true;
|
|
202
|
-
} catch {
|
|
203
|
-
// ESRCH or EPERM: the parent is gone or not ours; either way the hold must end.
|
|
204
|
-
return false;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
if (require.main === module && process.argv[2] === '--hold') {
|
|
209
|
-
const pid = Number.parseInt(process.argv[3], 10);
|
|
210
|
-
const bundlePath = process.argv[4];
|
|
211
|
-
const parentPid = Number.parseInt(process.argv[5], 10);
|
|
212
|
-
const launchedBundlePath = process.argv[6] || '';
|
|
213
|
-
const target = { pid, bundlePath };
|
|
214
|
-
const hold = () => {
|
|
215
|
-
if (!parentAlive(parentPid)) process.exit(0);
|
|
216
|
-
const front = captureMacFrontmost();
|
|
217
|
-
if (front && front.pid === target.pid) return;
|
|
218
|
-
if (front && launchedBundlePath && !sameBundle(front.bundlePath, launchedBundlePath)) process.exit(0);
|
|
219
|
-
preserveMacFrontmost(target, launchedBundlePath);
|
|
220
|
-
};
|
|
221
|
-
hold();
|
|
222
|
-
setInterval(hold, HOLD_INTERVAL_MS);
|
|
223
|
-
setTimeout(() => process.exit(0), HOLD_MAX_MS);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
101
|
module.exports = {
|
|
102
|
+
activateMacAppByPid,
|
|
227
103
|
captureMacFrontmost,
|
|
228
|
-
captureMacFrontmostProcess,
|
|
229
104
|
macBackgroundOpenArgs,
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
restoreMacFrontmostProcess,
|
|
234
|
-
startMacFocusHold,
|
|
235
|
-
stopMacFocusHold,
|
|
105
|
+
macFocusDisabled,
|
|
106
|
+
macFrontmostPid,
|
|
107
|
+
restoreFrontmostIfOurs,
|
|
236
108
|
};
|
|
@@ -185,6 +185,8 @@ if $LAUNCH_EXISTING_DIST && [ -z "$PREPARE_CMD" ]; then
|
|
|
185
185
|
# seeded wallet persists across relaunches. A per-run profile made every
|
|
186
186
|
# relaunch "foreign" and let live/ pruning delete profiles still in use.
|
|
187
187
|
PROFILE_ABS="${CHROME_USER_DATA_DIR:-$TARGET/$RUNTIME_DIR/$CHROME_PROFILE_NAME}"
|
|
188
|
+
# One spelling per profile: ownership checks compare it exactly.
|
|
189
|
+
while [ "${PROFILE_ABS%/}" != "$PROFILE_ABS" ]; do PROFILE_ABS="${PROFILE_ABS%/}"; done
|
|
188
190
|
case "$PROFILE_ABS" in
|
|
189
191
|
"$TARGET/$RUNTIME_DIR/"*) ;;
|
|
190
192
|
*) echo "--chrome-user-data-dir must be a dedicated child of $TARGET/$RUNTIME_DIR" >&2; exit 2 ;;
|
|
@@ -238,53 +240,31 @@ NODE
|
|
|
238
240
|
fi
|
|
239
241
|
fi
|
|
240
242
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
process.exit(1);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
let executable = '';
|
|
270
|
-
try {
|
|
271
|
-
executable = chromium.executablePath();
|
|
272
|
-
} catch (error) {
|
|
273
|
-
const message = error && error.message ? error.message : String(error);
|
|
274
|
-
console.error(`[recipe-harness] Could not resolve Playwright Chromium executable: ${message}. Manual approval required before installing the Playwright Chromium browser cache (no package.json changes); ask the user before running yarn playwright install chromium.`);
|
|
275
|
-
process.exit(1);
|
|
276
|
-
}
|
|
277
|
-
if (!fs.existsSync(executable)) {
|
|
278
|
-
console.error(`[recipe-harness] Playwright Chromium is not installed at ${executable}. Manual approval required before installing the Playwright Chromium browser cache (no package.json changes). Ask the user for approval; if they agree, run: cd ${shellQuote(process.cwd())} && yarn playwright install chromium`);
|
|
279
|
-
console.error('[recipe-harness] To use a browser that is already installed, set RECIPE_HARNESS_CHROME_BIN=/path/to/chrome explicitly.');
|
|
280
|
-
process.exit(1);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
process.stdout.write(executable);
|
|
284
|
-
NODE
|
|
285
|
-
CHROME_PROBE_LOG="$ARTIFACTS/logs/chrome-probe.log"
|
|
286
|
-
CHROME_BIN="$(cd "$TARGET" && node "$_chrome_probe" 2>"$CHROME_PROBE_LOG" || true)"
|
|
287
|
-
rm -f "$_chrome_probe"
|
|
243
|
+
# The shared resolver picks the browser (RECIPE_HARNESS_BROWSER=auto|cft|
|
|
244
|
+
# chrome|<path>; RECIPE_HARNESS_CHROME_BIN wins) and records why, so the
|
|
245
|
+
# launcher knows how to load the extension and the run evidence names it.
|
|
246
|
+
# launch-browser.cjs starts a macOS .app through Launch Services, headful,
|
|
247
|
+
# so the probe does too.
|
|
248
|
+
BROWSER_RESOLVER="$SCRIPT_DIR/lib/browser-resolver.cjs"
|
|
249
|
+
[ -f "$BROWSER_RESOLVER" ] || BROWSER_RESOLVER="$SCRIPT_DIR/../shared/browser-resolver.cjs"
|
|
250
|
+
# A preserved profile keeps the browser recorded for it (auto never flips a
|
|
251
|
+
# profile between builds); the resolver bounds itself (probe + watchdog).
|
|
252
|
+
CHROME_PROBE_LOG="$ARTIFACTS/logs/chrome-probe.log"
|
|
253
|
+
BROWSER_RECORD="$TARGET/$RUNTIME_DIR/browser-resolution.json"
|
|
254
|
+
recorded_args=()
|
|
255
|
+
if ! $RESET_PROFILE && [ -f "$PROFILE_ABS/Local State" ] && [ -f "$BROWSER_RECORD" ]; then
|
|
256
|
+
recorded_args=(--recorded "$BROWSER_RECORD")
|
|
257
|
+
fi
|
|
258
|
+
CHROME_BIN="$(cd "$TARGET" && node "$BROWSER_RESOLVER" resolve --target "$TARGET" \
|
|
259
|
+
--launch-method launch-services --display headful ${recorded_args[@]+"${recorded_args[@]}"} \
|
|
260
|
+
--out "$BROWSER_RECORD" --out "$ARTIFACTS/logs/browser-resolution.json" \
|
|
261
|
+
2>"$CHROME_PROBE_LOG" || true)"
|
|
262
|
+
if [ -n "${CHROME_BIN:-}" ]; then
|
|
263
|
+
cat "$CHROME_PROBE_LOG" >&2 2>/dev/null || true
|
|
264
|
+
elif grep -q 'BROWSER_UNLAUNCHABLE:' "$CHROME_PROBE_LOG" 2>/dev/null; then
|
|
265
|
+
cat "$CHROME_PROBE_LOG" >&2
|
|
266
|
+
echo "[recipe-harness] No launchable browser on this machine; stopping before build or live Extension launch." >&2
|
|
267
|
+
exit 1
|
|
288
268
|
fi
|
|
289
269
|
|
|
290
270
|
if [ -z "${CHROME_BIN:-}" ]; then
|
|
@@ -345,7 +325,8 @@ NODE
|
|
|
345
325
|
quoted_wallet_fixture="$(printf '%q' "$WALLET_FIXTURE_ABS")"
|
|
346
326
|
prepare_parts+=("bash ${quoted_seed_fixture} prefill --target ${quoted_target} --fixture ${quoted_wallet_fixture} --state ${quoted_fixture_state} --profile ${quoted_profile} --extension-dir ${quoted_runtime_dist} --extension-id-file ${quoted_extension_id_file}")
|
|
347
327
|
fi
|
|
348
|
-
|
|
328
|
+
quoted_browser_record="$(printf '%q' "$BROWSER_RECORD")"
|
|
329
|
+
chrome_launch_cmd="node ${quoted_chrome_launcher} --chrome-bin ${quoted_chrome} --browser-resolution ${quoted_browser_record} --profile ${quoted_profile} --cdp-port ${CDP_PORT} --extension-dir ${quoted_runtime_dist} --chrome-log ${quoted_chrome_log} --chrome-pid ${quoted_chrome_pid}"
|
|
349
330
|
if [ -n "${START_URL:-}" ]; then
|
|
350
331
|
chrome_launch_cmd="$chrome_launch_cmd --start-url $(printf '%q' "$START_URL")"
|
|
351
332
|
fi
|
package/adapters/manifest.json
CHANGED
|
@@ -159,7 +159,7 @@
|
|
|
159
159
|
"entry": "adapters/extension/live.sh",
|
|
160
160
|
"kind": "bash",
|
|
161
161
|
"purpose": "Sequencer: builds the prepare command from the named feature scripts, then launch + verify.",
|
|
162
|
-
"inputs": "--target --cdp-port --launch-existing-dist|--start-watch|--build-lavamoat|--prepare-cmd --dist-dir --chrome-user-data-dir --out --artifacts-dir; env RECIPE_HARNESS_CHROME_BIN, RECIPE_WALLET_FIXTURE, RECIPE_HARNESS_LIVE_KEEP",
|
|
162
|
+
"inputs": "--target --cdp-port --launch-existing-dist|--start-watch|--build-lavamoat|--prepare-cmd --dist-dir --chrome-user-data-dir --out --artifacts-dir; env RECIPE_HARNESS_BROWSER, RECIPE_HARNESS_CHROME_BIN, RECIPE_WALLET_FIXTURE, RECIPE_HARNESS_LIVE_KEEP",
|
|
163
163
|
"outputs": "<artifacts>/summary.json + launch/ + verify/ + logs/fixture-source.json; exit 0/1/2"
|
|
164
164
|
},
|
|
165
165
|
{
|
|
@@ -279,8 +279,8 @@
|
|
|
279
279
|
"entry": "adapters/extension/launch-browser.cjs",
|
|
280
280
|
"kind": "node",
|
|
281
281
|
"purpose": "Low-level spawn primitive: detached Chrome with isolated profile, CDP on 127.0.0.1, unpacked dist.",
|
|
282
|
-
"inputs": "--cdp-port --chrome-bin --profile --extension-dir --chrome-log --chrome-pid",
|
|
283
|
-
"outputs": "pid file + append-log; exit 0 / non-zero"
|
|
282
|
+
"inputs": "--cdp-port --chrome-bin --profile --extension-dir --chrome-log --chrome-pid [--browser-resolution]",
|
|
283
|
+
"outputs": "pid file + append-log + <runtime>/browser-resolution.json; exit 0 / non-zero"
|
|
284
284
|
},
|
|
285
285
|
{
|
|
286
286
|
"id": "extension/validation-process-ownership",
|
|
@@ -304,7 +304,7 @@
|
|
|
304
304
|
"kind": "bash",
|
|
305
305
|
"purpose": "Ensure the slot's Chromium is running against the existing dist/profile/port (kills + relaunches).",
|
|
306
306
|
"inputs": "--slot-id --repo --cdp-port --runtime-dir --watcher-port; env CHROME_USER_DATA_DIR, RECIPE_HARNESS_ROOT",
|
|
307
|
-
"outputs": "<runtime-dir>/{browser.pid,chromium.pid,extension.id}; exit 0/1"
|
|
307
|
+
"outputs": "<runtime-dir>/{browser.pid,chromium.pid,extension.id,browser-resolution.json}; exit 0/1"
|
|
308
308
|
},
|
|
309
309
|
{
|
|
310
310
|
"id": "extension/inject",
|
|
@@ -506,6 +506,22 @@
|
|
|
506
506
|
"inputs": "--target <checkout>",
|
|
507
507
|
"outputs": "corepack yarn install output; exit 0/1/2"
|
|
508
508
|
},
|
|
509
|
+
{
|
|
510
|
+
"id": "lib/browser-resolver",
|
|
511
|
+
"entry": "adapters/shared/browser-resolver.cjs",
|
|
512
|
+
"kind": "node",
|
|
513
|
+
"purpose": "Choose the extension browser (RECIPE_HARNESS_BROWSER=auto|cft|chrome|<path>; probe-launch Chrome for Testing, cached per machine and version, branded Chrome fallback) and load an unpacked extension over CDP for branded Chrome.",
|
|
514
|
+
"inputs": "resolve --target <checkout> [--launch-method spawn|launch-services] [--display headless|headful] [--recorded <file>] [--out <file>] [--json] | load-unpacked --port <cdp> --path <dist> --profile <dir> [--expect-id <id>] [--open-url <url>]; env RECIPE_HARNESS_BROWSER, RECIPE_HARNESS_CHROME_BIN, RECIPE_HARNESS_CHROME_CANDIDATES, RECIPE_HARNESS_BROWSER_CACHE",
|
|
515
|
+
"outputs": "executable path or resolution JSON on stdout; probe cache under ~/.cache/mm-harness; exit 0/1"
|
|
516
|
+
},
|
|
517
|
+
{
|
|
518
|
+
"id": "lib/browser-cdp",
|
|
519
|
+
"entry": "adapters/shared/browser-cdp.cjs",
|
|
520
|
+
"kind": "lib",
|
|
521
|
+
"purpose": "Browser-level CDP client with per-command deadlines; prove a CDP port belongs to a slot profile, load an unpacked extension (Extensions.loadUnpacked), audit for other user extensions.",
|
|
522
|
+
"inputs": "CDP port, extension dist, owning profile",
|
|
523
|
+
"outputs": "loaded extension id or an error; never loads into a browser it does not own"
|
|
524
|
+
},
|
|
509
525
|
{
|
|
510
526
|
"id": "lib/resolve-slot-ports",
|
|
511
527
|
"entry": "adapters/shared/resolve-slot-ports.mjs",
|