@deeeed/metamask-harness 0.17.5 → 0.18.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 +872 -0
- package/README.md +94 -140
- package/adapters/extension/console-tail.mjs +55 -24
- package/adapters/extension/ensure-browser.sh +6 -1
- package/adapters/extension/inject.mjs +6 -9
- package/adapters/extension/launch-browser.cjs +7 -1
- package/adapters/extension/launch-webpack.cjs +83 -0
- package/adapters/extension/launch.sh +8 -11
- package/adapters/extension/lib/chrome-args.cjs +8 -1
- package/adapters/extension/lib/macos-focus.cjs +32 -0
- package/adapters/extension/live.sh +12 -21
- package/adapters/extension/reattach.sh +1 -0
- package/adapters/extension/seed-fixture.sh +4 -12
- package/adapters/extension/sidepanel-toggle.sh +4 -1
- package/adapters/extension/stamp-runtime-title.cjs +58 -0
- package/adapters/extension/start-watch.sh +17 -4
- package/adapters/extension/stop-viewers.sh +1 -1
- package/adapters/extension/sync-webpack-dist.cjs +107 -0
- package/adapters/extension/verify.sh +1 -1
- package/adapters/manifest.json +53 -37
- package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +16 -0
- package/adapters/mobile/bridge-runtime/console-forwarder.cjs +1 -1
- package/adapters/mobile/bridge-runtime/lib/match-bridge-target.cjs +3 -1
- package/adapters/mobile/bridge-runtime/lib/target-discovery.cjs +2 -3
- package/adapters/mobile/cleanup.sh +16 -176
- package/adapters/mobile/inject.sh +90 -671
- package/adapters/mobile/launch-console-forwarder.cjs +39 -0
- package/adapters/mobile/open-device.sh +45 -7
- package/adapters/mobile/start-console-forwarder.sh +70 -0
- package/adapters/mobile/start-metro.sh +0 -41
- package/adapters/mobile/verify.sh +19 -64
- package/adapters/mobile/wait-for-bridge.sh +22 -8
- package/adapters/shared/resolve-slot-ports-core.mjs +17 -6
- package/adapters/shared/resolve-slot-ports.sh +17 -5
- package/adapters/shared/update-check-worker.mjs +43 -0
- package/bin/mm-harness +21 -2
- package/dist/adapters/core/surface.js +4 -1
- package/dist/adapters/extension/console-capture.js +95 -0
- package/dist/adapters/extension/product-config.js +110 -0
- package/dist/adapters/extension/runtime-decision.js +20 -2
- package/dist/adapters/extension/surface.js +22 -3
- package/dist/adapters/mobile/perps-env.js +43 -3
- package/dist/adapters/mobile/prepare.js +39 -7
- package/dist/adapters/mobile/surface.js +5 -2
- package/dist/adapters/resolve-slot-ports.js +2 -2
- package/dist/adapters/slot-ports.js +13 -16
- package/dist/adapters.js +56 -16
- package/dist/checkout-lock.js +27 -2
- package/dist/cli-color.js +19 -0
- package/dist/cli-commands.js +1 -1
- package/dist/cli.js +7 -14
- package/dist/command-contract.js +451 -0
- package/dist/command-journal.js +225 -0
- package/dist/commands/call.js +170 -50
- package/dist/commands/check.js +9 -3
- package/dist/commands/completion-candidates.js +20 -13
- package/dist/commands/device-target.js +27 -12
- package/dist/commands/doctor.js +106 -25
- package/dist/commands/fixtures.js +92 -34
- package/dist/commands/flows.js +39 -10
- package/dist/commands/last.js +52 -0
- package/dist/commands/launch/extension.js +38 -15
- package/dist/commands/launch/index.js +207 -63
- package/dist/commands/list-executables.js +151 -29
- package/dist/commands/logs.js +8 -6
- package/dist/commands/manifest.js +270 -35
- package/dist/commands/parse-args.js +13 -1
- package/dist/commands/provision.js +10 -3
- package/dist/commands/run-engine.js +435 -105
- package/dist/commands/run-report.js +12 -3
- package/dist/commands/run.js +355 -50
- package/dist/commands/shared.js +75 -3
- package/dist/commands/status-probe.js +4 -1
- package/dist/commands/status.js +2 -1
- package/dist/commands/stop.js +7 -2
- package/dist/commands/update.js +16 -28
- package/dist/completions-cache.js +1 -1
- package/dist/doctor.js +57 -10
- package/dist/harness.js +47 -137
- package/dist/heal-bounds.js +2 -2
- package/dist/json-stream.js +57 -0
- package/dist/live-adapter-contract.js +138 -32
- package/dist/manifest.js +161 -1
- package/dist/mm-harness-cli.js +135 -54
- package/dist/paths.js +2 -5
- package/dist/recipe-security.js +178 -0
- package/dist/run-diagnostics.js +261 -0
- package/dist/runner.js +117 -8
- package/docs/CONTRIBUTING.md +137 -0
- package/docs/QA.md +185 -0
- package/docs/RECIPES.md +161 -0
- package/docs/SECURITY.md +88 -0
- package/library/README.md +4 -0
- package/library/actions/core/perps/_controller.mjs +10 -55
- package/library/actions/core/perps/read_account.mjs +2 -2
- package/library/actions/core/perps/read_orders.mjs +2 -1
- package/library/actions/core/perps/read_positions.mjs +2 -1
- package/library/actions/core/wallet/list_accounts.mjs +95 -0
- package/library/actions/extension/platform/cdp.mjs +1 -0
- package/library/actions/extension/wallet/list_accounts.mjs +41 -0
- package/library/actions/mobile/wallet/list_accounts.mjs +37 -0
- package/library/manifests/core.action-manifest.json +68 -7
- package/library/manifests/extension.action-manifest.json +53 -0
- package/library/manifests/mobile.action-manifest.json +81 -3
- package/library/recipes/runner/action-validation.extension.recipe.json +8 -1
- package/library/recipes/runner/action-validation.mobile.recipe.json +8 -1
- package/library/recipes/runner/smoke.core.recipe.json +27 -0
- package/library/recipes/wallet/smoke.extension.recipe.json +42 -0
- package/library/recipes/wallet/smoke.mobile.recipe.json +42 -0
- package/package.json +12 -5
- package/scripts/completions.sh +7 -7
- package/scripts/validate-human-outcomes.mjs +169 -0
- package/adapters/mobile/overlay/app/dev-tools/AgenticService/AgentStepHud.tsx.patch +0 -185
- package/adapters/mobile/overlay/app/dev-tools/AgenticService/AgenticService.ts.patch +0 -1662
- package/adapters/mobile/overlay/compat/README.md +0 -28
- package/adapters/mobile/overlay/compat/rn81-message-event-source.patch +0 -42
- package/adapters/shared/resolve-farmslot-ports-core.mjs +0 -3
- package/adapters/shared/resolve-farmslot-ports.mjs +0 -5
- package/adapters/shared/resolve-farmslot-ports.sh +0 -7
- package/docs/ADAPTER-SURFACE.md +0 -119
- package/docs/CHEATSHEET.md +0 -61
- package/docs/CLI-SPEC.md +0 -1098
- package/docs/CODE-MAP.md +0 -62
- package/docs/DEBUG-HANDOVER.md +0 -36
- package/docs/MENTAL-MODEL.md +0 -295
- package/docs/UX-PRINCIPLES.md +0 -64
- package/docs/architecture.md +0 -398
- package/docs/live-adapter-contract.md +0 -188
- package/docs/package-boundaries.md +0 -47
- package/docs/perps-flow-catalog.md +0 -235
- package/docs/recipe-libraries.md +0 -301
- package/docs/runtime-file-conventions.md +0 -36
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const { spawn } = require('node:child_process');
|
|
5
|
+
const fs = require('node:fs');
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
|
|
8
|
+
const args = {};
|
|
9
|
+
for (let index = 2; index < process.argv.length; index += 1) {
|
|
10
|
+
const key = process.argv[index];
|
|
11
|
+
if (key === '--help' || key === '-h') {
|
|
12
|
+
process.stdout.write('Usage: launch-webpack.cjs --target <path> --log <path> --pid-file <path> --sync-script <path> --sync-pid-file <path> --sync-log <path> --dist <path> --runtime-dist <path> [--port <port>] [--path-prefix <path>]\n');
|
|
13
|
+
process.exit(0);
|
|
14
|
+
}
|
|
15
|
+
if (!key.startsWith('--') || index + 1 >= process.argv.length) throw new Error(`invalid argument: ${key}`);
|
|
16
|
+
args[key.slice(2)] = process.argv[++index];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function main() {
|
|
20
|
+
for (const required of ['target', 'log', 'pid-file', 'sync-script', 'sync-pid-file', 'sync-log', 'dist', 'runtime-dist']) {
|
|
21
|
+
if (!args[required]) throw new Error(`--${required} is required`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const target = path.resolve(args.target);
|
|
25
|
+
const log = path.resolve(args.log);
|
|
26
|
+
const pidFile = path.resolve(args['pid-file']);
|
|
27
|
+
const syncScript = path.resolve(args['sync-script']);
|
|
28
|
+
const syncPidFile = path.resolve(args['sync-pid-file']);
|
|
29
|
+
const syncLog = path.resolve(args['sync-log']);
|
|
30
|
+
fs.mkdirSync(path.dirname(log), { recursive: true });
|
|
31
|
+
fs.mkdirSync(path.dirname(pidFile), { recursive: true });
|
|
32
|
+
fs.mkdirSync(path.dirname(syncLog), { recursive: true });
|
|
33
|
+
if (!fs.existsSync(syncScript)) throw new Error(`sync script not found: ${syncScript}`);
|
|
34
|
+
const logFd = fs.openSync(log, 'a');
|
|
35
|
+
const syncLogFd = fs.openSync(syncLog, 'a');
|
|
36
|
+
const env = { ...process.env };
|
|
37
|
+
if (args.port) env.PORT = String(args.port);
|
|
38
|
+
if (args['path-prefix']) env.PATH = `${args['path-prefix']}${env.PATH || ''}`;
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const child = spawn('yarn', ['start'], {
|
|
42
|
+
cwd: target,
|
|
43
|
+
detached: true,
|
|
44
|
+
env,
|
|
45
|
+
stdio: ['ignore', logFd, logFd],
|
|
46
|
+
});
|
|
47
|
+
await new Promise((resolve, reject) => {
|
|
48
|
+
child.once('spawn', resolve);
|
|
49
|
+
child.once('error', reject);
|
|
50
|
+
});
|
|
51
|
+
fs.writeFileSync(pidFile, `${child.pid}\n`);
|
|
52
|
+
const syncChild = spawn(process.execPath, [
|
|
53
|
+
syncScript,
|
|
54
|
+
'--watcher-pid', String(child.pid),
|
|
55
|
+
'--watch-log', log,
|
|
56
|
+
'--target', target,
|
|
57
|
+
'--dist', path.resolve(args.dist),
|
|
58
|
+
'--runtime-dist', path.resolve(args['runtime-dist']),
|
|
59
|
+
'--pid-file', syncPidFile,
|
|
60
|
+
], {
|
|
61
|
+
cwd: target,
|
|
62
|
+
detached: true,
|
|
63
|
+
env,
|
|
64
|
+
stdio: ['ignore', syncLogFd, syncLogFd],
|
|
65
|
+
});
|
|
66
|
+
await new Promise((resolve, reject) => {
|
|
67
|
+
syncChild.once('spawn', resolve);
|
|
68
|
+
syncChild.once('error', reject);
|
|
69
|
+
});
|
|
70
|
+
fs.writeFileSync(syncPidFile, `${syncChild.pid}\n`);
|
|
71
|
+
process.stdout.write(`${child.pid}\n`);
|
|
72
|
+
syncChild.unref();
|
|
73
|
+
child.unref();
|
|
74
|
+
} finally {
|
|
75
|
+
fs.closeSync(logFd);
|
|
76
|
+
fs.closeSync(syncLogFd);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
main().catch((error) => {
|
|
81
|
+
process.stderr.write(`launch-webpack: ${error.message}\n`);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
});
|
|
@@ -114,24 +114,21 @@ else
|
|
|
114
114
|
status="fail"
|
|
115
115
|
fi
|
|
116
116
|
|
|
117
|
-
#
|
|
118
|
-
#
|
|
119
|
-
#
|
|
120
|
-
# it never affects the launch result.
|
|
117
|
+
# The public CLI owns console collection after every successful launch path.
|
|
118
|
+
# This leaf only adds an optional viewer when an explicitly run-owned tmux
|
|
119
|
+
# session exists; the viewer tails the file and never owns the collector.
|
|
121
120
|
if [ "$status" = "pass" ] && [ -n "$CDP_PORT" ] && command -v tmux >/dev/null 2>&1; then
|
|
121
|
+
console_runtime="$TARGET/$( (cd "$TARGET" && recipe_runtime_dir) 2>/dev/null || echo temp/recipe/runtime)"
|
|
122
|
+
mkdir -p "$console_runtime"
|
|
123
|
+
console_log="$console_runtime/extension-console.log"
|
|
122
124
|
for _tv_src in "$SCRIPT_DIR/lib/tmux-viewer.sh" "$SCRIPT_DIR/../shared/tmux-viewer.sh"; do
|
|
123
125
|
# shellcheck disable=SC1091
|
|
124
126
|
[ -f "$_tv_src" ] && { . "$_tv_src"; break; }
|
|
125
127
|
done
|
|
126
128
|
unset _tv_src
|
|
127
|
-
|
|
128
|
-
if command -v tmux_viewer_open >/dev/null 2>&1 && [ -f "$console_tail" ]; then
|
|
129
|
-
console_runtime="$TARGET/$( (cd "$TARGET" && recipe_runtime_dir) 2>/dev/null || echo temp/recipe/runtime)"
|
|
130
|
-
mkdir -p "$console_runtime"
|
|
131
|
-
console_log="$console_runtime/extension-console.log"
|
|
132
|
-
: > "$console_log"
|
|
129
|
+
if command -v tmux_viewer_open >/dev/null 2>&1; then
|
|
133
130
|
tmux_viewer_open "console" "$CDP_PORT" "$console_log" "$console_runtime" "$console_runtime/console.tmux" \
|
|
134
|
-
"exec
|
|
131
|
+
"exec tail -n +1 -F $(printf '%q' "$console_log")"
|
|
135
132
|
fi
|
|
136
133
|
fi
|
|
137
134
|
|
|
@@ -21,4 +21,11 @@ function remoteDebuggingArgs(cdpPort) {
|
|
|
21
21
|
];
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
// An isolated automation profile must never ask for or write credentials in the
|
|
25
|
+
// operator's macOS login keychain. Chromium's mock keychain keeps profile-local
|
|
26
|
+
// encryption deterministic and prevents modal prompts from blocking startup.
|
|
27
|
+
function isolatedProfileArgs(platform = process.platform) {
|
|
28
|
+
return platform === 'darwin' ? ['--use-mock-keychain'] : [];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = { isolatedProfileArgs, remoteDebuggingArgs };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { execFileSync } = require('node:child_process');
|
|
4
|
+
|
|
5
|
+
function captureMacFrontmostProcess() {
|
|
6
|
+
if (process.platform !== 'darwin') return null;
|
|
7
|
+
try {
|
|
8
|
+
const value = execFileSync('osascript', [
|
|
9
|
+
'-e',
|
|
10
|
+
'tell application "System Events" to get unix id of first application process whose frontmost is true',
|
|
11
|
+
], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
12
|
+
const pid = Number.parseInt(value, 10);
|
|
13
|
+
return Number.isInteger(pid) && pid > 0 ? pid : null;
|
|
14
|
+
} catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function restoreMacFrontmostProcess(pid) {
|
|
20
|
+
if (process.platform !== 'darwin' || !Number.isInteger(pid) || pid <= 0) return false;
|
|
21
|
+
try {
|
|
22
|
+
execFileSync('osascript', [
|
|
23
|
+
'-e',
|
|
24
|
+
`tell application "System Events" to set frontmost of first application process whose unix id is ${pid} to true`,
|
|
25
|
+
], { stdio: 'ignore' });
|
|
26
|
+
return true;
|
|
27
|
+
} catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports = { captureMacFrontmostProcess, restoreMacFrontmostProcess };
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
set -euo pipefail
|
|
33
33
|
|
|
34
34
|
TARGET="$PWD"
|
|
35
|
+
INVOCATION_DIR="$PWD"
|
|
35
36
|
CDP_PORT=""
|
|
36
37
|
ARTIFACTS=""
|
|
37
38
|
OUT=""
|
|
@@ -39,7 +40,7 @@ PREPARE_CMD="${RECIPE_HARNESS_EXTENSION_LAUNCH_CMD:-}"
|
|
|
39
40
|
LAUNCH_EXISTING_DIST=false
|
|
40
41
|
START_WATCH=false
|
|
41
42
|
DIST_DIR="dist/chrome"
|
|
42
|
-
CHROME_USER_DATA_DIR=""
|
|
43
|
+
CHROME_USER_DATA_DIR="${CHROME_USER_DATA_DIR:-}"
|
|
43
44
|
REMOTE_FLAGS=""
|
|
44
45
|
REMOTE_FLAGS_APPLIED=false
|
|
45
46
|
while [ "$#" -gt 0 ]; do
|
|
@@ -60,6 +61,11 @@ while [ "$#" -gt 0 ]; do
|
|
|
60
61
|
esac
|
|
61
62
|
done
|
|
62
63
|
|
|
64
|
+
case "$CHROME_USER_DATA_DIR" in
|
|
65
|
+
""|/*) ;;
|
|
66
|
+
*) CHROME_USER_DATA_DIR="$INVOCATION_DIR/$CHROME_USER_DATA_DIR" ;;
|
|
67
|
+
esac
|
|
68
|
+
|
|
63
69
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
64
70
|
# shellcheck disable=SC1091
|
|
65
71
|
for _hp in "$SCRIPT_DIR/lib/harness-path.sh" "$SCRIPT_DIR/../shared/harness-path.sh"; do
|
|
@@ -100,7 +106,6 @@ if [ -z "$VERIFY_SH" ]; then
|
|
|
100
106
|
echo "mm-harness: extension verify.sh not found next to $SCRIPT_DIR; reinstall the runner." >&2
|
|
101
107
|
exit 1
|
|
102
108
|
fi
|
|
103
|
-
TARGET="$(cd "$TARGET" && pwd)"
|
|
104
109
|
RUNTIME_DIR="$(recipe_runtime_dir)"
|
|
105
110
|
RUNTIME_DIST_DIR="${RECIPE_RUNTIME_DIST_DIR:-runtime-dist}"
|
|
106
111
|
CHROME_PROFILE_NAME="${RECIPE_CHROME_PROFILE_NAME:-chrome-profile}"
|
|
@@ -163,6 +168,7 @@ NODE
|
|
|
163
168
|
quoted_seed_fixture="$(printf '%q' "$SCRIPT_DIR/seed-fixture.sh")"
|
|
164
169
|
quoted_start_watch="$(printf '%q' "$SCRIPT_DIR/start-watch.sh")"
|
|
165
170
|
quoted_snapshot_dist="$(printf '%q' "$SCRIPT_DIR/snapshot-dist.sh")"
|
|
171
|
+
quoted_stamp_title="$(printf '%q' "$SCRIPT_DIR/stamp-runtime-title.cjs")"
|
|
166
172
|
quoted_chrome_launcher="$(printf '%q' "$SCRIPT_DIR/launch-browser.cjs")"
|
|
167
173
|
quoted_fixture_state="$(printf '%q' "$FIXTURE_STATE_ABS")"
|
|
168
174
|
quoted_fixture_validation="$(printf '%q' "$FIXTURE_VALIDATION_ABS")"
|
|
@@ -211,11 +217,11 @@ try {
|
|
|
211
217
|
executable = chromium.executablePath();
|
|
212
218
|
} catch (error) {
|
|
213
219
|
const message = error && error.message ? error.message : String(error);
|
|
214
|
-
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
|
|
220
|
+
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.`);
|
|
215
221
|
process.exit(1);
|
|
216
222
|
}
|
|
217
223
|
if (!fs.existsSync(executable)) {
|
|
218
|
-
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
|
|
224
|
+
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`);
|
|
219
225
|
console.error('[recipe-harness] To use a browser that is already installed, set RECIPE_HARNESS_CHROME_BIN=/path/to/chrome explicitly.');
|
|
220
226
|
process.exit(1);
|
|
221
227
|
}
|
|
@@ -227,25 +233,9 @@ NODE
|
|
|
227
233
|
rm -f "$_chrome_probe"
|
|
228
234
|
fi
|
|
229
235
|
|
|
230
|
-
if [ -z "${CHROME_BIN:-}" ]; then
|
|
231
|
-
for candidate in \
|
|
232
|
-
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
|
|
233
|
-
"/Applications/Chromium.app/Contents/MacOS/Chromium" \
|
|
234
|
-
"/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary" \
|
|
235
|
-
"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge"; do
|
|
236
|
-
if [ -x "$candidate" ]; then
|
|
237
|
-
CHROME_BIN="$candidate"
|
|
238
|
-
break
|
|
239
|
-
fi
|
|
240
|
-
done
|
|
241
|
-
if [ -n "${CHROME_BIN:-}" ]; then
|
|
242
|
-
echo "[recipe-harness] WARN: Playwright Chromium unavailable; falling back to system browser: $CHROME_BIN" >&2
|
|
243
|
-
fi
|
|
244
|
-
fi
|
|
245
|
-
|
|
246
236
|
if [ -z "${CHROME_BIN:-}" ]; then
|
|
247
237
|
[ -n "${CHROME_PROBE_LOG:-}" ] && cat "$CHROME_PROBE_LOG" >&2 2>/dev/null || true
|
|
248
|
-
echo "[recipe-harness] No
|
|
238
|
+
echo "[recipe-harness] No compatible Chromium binary selected; stopping before build or live Extension launch." >&2
|
|
249
239
|
exit 1
|
|
250
240
|
fi
|
|
251
241
|
quoted_chrome="$(printf '%q' "$CHROME_BIN")"
|
|
@@ -257,6 +247,7 @@ NODE
|
|
|
257
247
|
fi
|
|
258
248
|
prepare_parts+=("node ${quoted_chrome_launcher} --stop-only 1 --reset-profile 1 --chrome-bin ${quoted_chrome} --profile ${quoted_profile} --cdp-port ${CDP_PORT} --extension-dir ${quoted_runtime_dist} --chrome-log ${quoted_chrome_log} --chrome-pid ${quoted_chrome_pid}")
|
|
259
249
|
prepare_parts+=("bash ${quoted_snapshot_dist} --dist ${quoted_dist} --runtime-dist ${quoted_runtime_dist}")
|
|
250
|
+
prepare_parts+=("node ${quoted_stamp_title} --target ${quoted_target} --runtime-dist ${quoted_runtime_dist} --runtime-dir ${quoted_runtime_dir}")
|
|
260
251
|
# Optional A/B feature-flag pinning: patch the ephemeral snapshot manifest so
|
|
261
252
|
# manifest._flags wins over the fetched ClientConfigApi value. No-op unless
|
|
262
253
|
# --remote-flag was passed; the snapshot is re-created each run, so no revert.
|
|
@@ -103,6 +103,7 @@ echo "[reattach] reusing loaded runtime-dist: $RUNTIME_DIST_ABS" >&2
|
|
|
103
103
|
if [ -d "$DIST_ABS" ] && [ -d "$RUNTIME_DIST_ABS" ]; then
|
|
104
104
|
echo "[reattach] refreshing loaded runtime-dist in place" >&2
|
|
105
105
|
rsync -a --exclude _metadata "$DIST_ABS/" "$RUNTIME_DIST_ABS/" >&2
|
|
106
|
+
node "$SCRIPT_DIR/stamp-runtime-title.cjs" --target "$TARGET" --runtime-dist "$RUNTIME_DIST_ABS" --runtime-dir "$RUNTIME_DIR" >&2
|
|
106
107
|
fi
|
|
107
108
|
|
|
108
109
|
# Resolve the extension id: explicit flag, then the recorded id file, else CDP.
|
|
@@ -4,15 +4,13 @@
|
|
|
4
4
|
#
|
|
5
5
|
# Purpose:
|
|
6
6
|
# Resolves which wallet fixture a slot run should use (env override then
|
|
7
|
-
# runtime
|
|
7
|
+
# the checkout's canonical runtime fixture) and drives the recipe-tree
|
|
8
8
|
# wallet-fixture-state.cjs phases around the browser launch.
|
|
9
9
|
#
|
|
10
10
|
# Subcommands (flags):
|
|
11
11
|
# resolve --target <repo> --cdp-port <port> [--source-out <file>]
|
|
12
12
|
# Prints the fixture path (empty if none). Chain:
|
|
13
|
-
# RECIPE_WALLET_FIXTURE env > <runtime-dir>/wallet-fixture.json
|
|
14
|
-
# temp/runtime/ > temp/.recipe-validation-<port>/ >
|
|
15
|
-
# temp/.agent-validation/.
|
|
13
|
+
# RECIPE_WALLET_FIXTURE env > <runtime-dir>/wallet-fixture.json.
|
|
16
14
|
# prefill --fixture <json> --target <repo> --state <file> --profile <dir>
|
|
17
15
|
# --extension-dir <dist> --extension-id-file <file>
|
|
18
16
|
# Runs fixture-state generate then prefill-profile (pre-launch).
|
|
@@ -131,14 +129,8 @@ if [ "$SUBCOMMAND" = resolve ]; then
|
|
|
131
129
|
fi
|
|
132
130
|
resolved="$RECIPE_WALLET_FIXTURE"
|
|
133
131
|
else
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
"$TARGET/temp/runtime/wallet-fixture.json" \
|
|
137
|
-
${CDP_PORT:+"$TARGET/temp/.recipe-validation-${CDP_PORT}/wallet-fixture.json"} \
|
|
138
|
-
"$TARGET/temp/.agent-validation/wallet-fixture.json"
|
|
139
|
-
do
|
|
140
|
-
[ -f "$candidate" ] && { resolved="$candidate"; break; }
|
|
141
|
-
done
|
|
132
|
+
candidate="${RUNTIME_DIR:+$TARGET/$RUNTIME_DIR/wallet-fixture.json}"
|
|
133
|
+
[ -n "$candidate" ] && [ -f "$candidate" ] && resolved="$candidate"
|
|
142
134
|
fi
|
|
143
135
|
[ -n "$resolved" ] || resolve_status=missing
|
|
144
136
|
if [ -n "$SOURCE_OUT" ]; then
|
|
@@ -289,7 +289,10 @@ const { chromium } = require('playwright');
|
|
|
289
289
|
return { windowId: currentWindow.id };
|
|
290
290
|
});
|
|
291
291
|
|
|
292
|
-
|
|
292
|
+
// This harness-owned button exists only to provide Chrome's trusted user
|
|
293
|
+
// gesture. Its visual layout is irrelevant and can be occluded by the app's
|
|
294
|
+
// full-screen loading surface, so do not wait on Playwright actionability.
|
|
295
|
+
await page.locator('#__recipe_open_sidepanel__').click({ timeout: 5000, force: true });
|
|
293
296
|
const clickResult = await page
|
|
294
297
|
.locator('#__recipe_open_sidepanel__')
|
|
295
298
|
.evaluate((button) => button.dataset.result || '');
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
|
|
7
|
+
const { readSlotId } = require('./lib/slot-title.cjs');
|
|
8
|
+
|
|
9
|
+
function stampRuntimeTitles({ target, runtimeDist, runtimeDir = process.env.RECIPE_RUNTIME_DIR || 'temp/recipe/runtime' }) {
|
|
10
|
+
const slotId = readSlotId(target, runtimeDir);
|
|
11
|
+
if (!slotId) return { slotId: '', stamped: [] };
|
|
12
|
+
const stamped = [];
|
|
13
|
+
for (const name of ['home.html', 'sidepanel.html']) {
|
|
14
|
+
const file = path.join(runtimeDist, name);
|
|
15
|
+
let fd;
|
|
16
|
+
try {
|
|
17
|
+
fd = fs.openSync(file, 'r+');
|
|
18
|
+
const source = fs.readFileSync(fd, 'utf8');
|
|
19
|
+
const next = source.replace(/<title>([^<]*)<\/title>/iu, (_match, title) => {
|
|
20
|
+
const base = String(title || 'MetaMask').replace(/^.+?\s+[—–-]\s+/u, '') || 'MetaMask';
|
|
21
|
+
return `<title>${slotId} — ${base}</title>`;
|
|
22
|
+
});
|
|
23
|
+
if (next === source) continue;
|
|
24
|
+
const bytes = Buffer.from(next, 'utf8');
|
|
25
|
+
fs.writeSync(fd, bytes, 0, bytes.length, 0);
|
|
26
|
+
fs.ftruncateSync(fd, bytes.length);
|
|
27
|
+
stamped.push(name);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
if (error?.code !== 'ENOENT') throw error;
|
|
30
|
+
} finally {
|
|
31
|
+
if (fd !== undefined) fs.closeSync(fd);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return { slotId, stamped };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (require.main === module) {
|
|
38
|
+
const args = {};
|
|
39
|
+
for (let index = 2; index < process.argv.length; index += 1) {
|
|
40
|
+
const key = process.argv[index];
|
|
41
|
+
if (key === '--help' || key === '-h') {
|
|
42
|
+
process.stdout.write('Usage: stamp-runtime-title.cjs --target <path> --runtime-dist <path> [--runtime-dir <path>]\n');
|
|
43
|
+
process.exit(0);
|
|
44
|
+
}
|
|
45
|
+
if (!key.startsWith('--') || index + 1 >= process.argv.length) throw new Error(`invalid argument: ${key}`);
|
|
46
|
+
args[key.slice(2)] = process.argv[++index];
|
|
47
|
+
}
|
|
48
|
+
if (!args.target) throw new Error('--target is required');
|
|
49
|
+
if (!args['runtime-dist']) throw new Error('--runtime-dist is required');
|
|
50
|
+
const result = stampRuntimeTitles({
|
|
51
|
+
target: path.resolve(args.target),
|
|
52
|
+
runtimeDist: path.resolve(args['runtime-dist']),
|
|
53
|
+
runtimeDir: args['runtime-dir'],
|
|
54
|
+
});
|
|
55
|
+
if (result.stamped.length > 0) process.stdout.write(`slot title ${result.slotId}: ${result.stamped.join(', ')}\n`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = { stampRuntimeTitles };
|
|
@@ -182,9 +182,15 @@ rm -rf node_modules/.cache/webpack
|
|
|
182
182
|
killed_harness_pid=""
|
|
183
183
|
if [ -f "$RUNTIME_DIR/recipe-harness-webpack.pid" ]; then
|
|
184
184
|
killed_harness_pid="$(cat "$RUNTIME_DIR/recipe-harness-webpack.pid" 2>/dev/null || true)"
|
|
185
|
+
[ -z "$killed_harness_pid" ] || kill -- "-$killed_harness_pid" >/dev/null 2>&1 || true
|
|
185
186
|
[ -z "$killed_harness_pid" ] || kill "$killed_harness_pid" >/dev/null 2>&1 || true
|
|
186
187
|
rm -f "$RUNTIME_DIR/recipe-harness-webpack.pid"
|
|
187
188
|
fi
|
|
189
|
+
if [ -f "$RUNTIME_DIR/recipe-harness-webpack-sync.pid" ]; then
|
|
190
|
+
sync_pid="$(cat "$RUNTIME_DIR/recipe-harness-webpack-sync.pid" 2>/dev/null || true)"
|
|
191
|
+
[ -z "$sync_pid" ] || kill "$sync_pid" >/dev/null 2>&1 || true
|
|
192
|
+
rm -f "$RUNTIME_DIR/recipe-harness-webpack-sync.pid"
|
|
193
|
+
fi
|
|
188
194
|
|
|
189
195
|
# Do not start a second writer for dist/chrome. Slot-owned watches use
|
|
190
196
|
# webpack.pid; harness-owned watches use recipe-harness-webpack.pid.
|
|
@@ -219,6 +225,8 @@ fi
|
|
|
219
225
|
# unrelated repo and leave this target validating stale dist/chrome output.
|
|
220
226
|
watch_pid_file="$RUNTIME_DIR/recipe-harness-webpack.pid"
|
|
221
227
|
watch_log="$RUNTIME_DIR/recipe-harness-webpack.log"
|
|
228
|
+
sync_pid_file="$RUNTIME_DIR/recipe-harness-webpack-sync.pid"
|
|
229
|
+
sync_log="$RUNTIME_DIR/recipe-harness-webpack-sync.log"
|
|
222
230
|
rm -f "$watch_pid_file"
|
|
223
231
|
: > "$watch_log"
|
|
224
232
|
LIB_DIR=""
|
|
@@ -229,11 +237,16 @@ unset _lib
|
|
|
229
237
|
echo "[recipe-harness] Starting yarn start (watcher-port=${WATCHER_PORT:-default})"
|
|
230
238
|
echo "[recipe-harness] compact build view - full log: $watch_log (mm-harness logs --full)"
|
|
231
239
|
started_pid=""
|
|
232
|
-
# Launch the watcher detached writing to the log; the tmux window is a read-only tail viewer.
|
|
233
240
|
path_prefix="${REPO_NODE_PATH_PREFIX:-}"
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
241
|
+
launcher="$SCRIPT_DIR/launch-webpack.cjs"
|
|
242
|
+
[ -f "$launcher" ] || { echo "launch-webpack.cjs missing from harness; run: mm-harness install" >&2; exit 1; }
|
|
243
|
+
sync_script="$SCRIPT_DIR/sync-webpack-dist.cjs"
|
|
244
|
+
[ -f "$sync_script" ] || { echo "sync-webpack-dist.cjs missing from harness; run: mm-harness install" >&2; exit 1; }
|
|
245
|
+
launch_args=(--target "$PWD" --log "$watch_log" --pid-file "$watch_pid_file" --sync-script "$sync_script" --sync-pid-file "$sync_pid_file" --sync-log "$sync_log" --dist "$PWD/dist/chrome" --runtime-dist "$PWD/$RUNTIME_DIR/${RECIPE_RUNTIME_DIST_DIR:-runtime-dist}")
|
|
246
|
+
[ -z "$WATCHER_PORT" ] || launch_args+=(--port "$WATCHER_PORT")
|
|
247
|
+
[ -z "$path_prefix" ] || launch_args+=(--path-prefix "$path_prefix")
|
|
248
|
+
env -u BUNDLED_DEBUGPY_PATH node "$launcher" "${launch_args[@]}" >/dev/null
|
|
249
|
+
started_pid="$(cat "$watch_pid_file")"
|
|
237
250
|
# The orchestrator names the session; the harness only populates windows. Resolve
|
|
238
251
|
# via the run-owned ladder (RECIPE_TMUX_SESSION → agentic-runtime.json session) —
|
|
239
252
|
# no current-session fallback or slot-number derivation.
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const { spawnSync } = require('node:child_process');
|
|
5
|
+
const fs = require('node:fs');
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
const { stampRuntimeTitles } = require('./stamp-runtime-title.cjs');
|
|
8
|
+
|
|
9
|
+
const args = {};
|
|
10
|
+
for (let index = 2; index < process.argv.length; index += 1) {
|
|
11
|
+
const key = process.argv[index];
|
|
12
|
+
if (key === '--help' || key === '-h') {
|
|
13
|
+
process.stdout.write('Usage: sync-webpack-dist.cjs --watcher-pid <pid> --watch-log <path> --target <path> --dist <path> --runtime-dist <path> --pid-file <path>\n');
|
|
14
|
+
process.exit(0);
|
|
15
|
+
}
|
|
16
|
+
if (!key.startsWith('--') || index + 1 >= process.argv.length) throw new Error(`invalid argument: ${key}`);
|
|
17
|
+
args[key.slice(2)] = process.argv[++index];
|
|
18
|
+
}
|
|
19
|
+
for (const required of ['watcher-pid', 'watch-log', 'target', 'dist', 'runtime-dist', 'pid-file']) {
|
|
20
|
+
if (!args[required]) throw new Error(`--${required} is required`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const watcherPid = Number(args['watcher-pid']);
|
|
24
|
+
if (!Number.isInteger(watcherPid) || watcherPid <= 0) throw new Error('--watcher-pid must be a positive integer');
|
|
25
|
+
|
|
26
|
+
const watchLog = path.resolve(args['watch-log']);
|
|
27
|
+
const dist = path.resolve(args.dist);
|
|
28
|
+
const runtimeDist = path.resolve(args['runtime-dist']);
|
|
29
|
+
const pidFile = path.resolve(args['pid-file']);
|
|
30
|
+
const compilePattern = /compiled successfully|compiled with \d+ warning|MetaMask .* compiled|Bundle end: service worker|Bundle end:.*app-init/u;
|
|
31
|
+
let offset = 0;
|
|
32
|
+
let carry = '';
|
|
33
|
+
let timer;
|
|
34
|
+
|
|
35
|
+
function processAlive(pid) {
|
|
36
|
+
try {
|
|
37
|
+
process.kill(pid, 0);
|
|
38
|
+
return true;
|
|
39
|
+
} catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function removeOwnPidFile() {
|
|
45
|
+
try {
|
|
46
|
+
if (fs.readFileSync(pidFile, 'utf8').trim() === String(process.pid)) fs.rmSync(pidFile, { force: true });
|
|
47
|
+
} catch {
|
|
48
|
+
// The launcher may have already removed or replaced it.
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function syncRuntimeDist() {
|
|
53
|
+
if (!fs.existsSync(path.join(dist, 'manifest.json')) || !fs.existsSync(runtimeDist)) return;
|
|
54
|
+
const result = spawnSync('rsync', ['-a', '--delete', '--delay-updates', '--exclude', '_metadata', `${dist}${path.sep}`, `${runtimeDist}${path.sep}`], {
|
|
55
|
+
encoding: 'utf8',
|
|
56
|
+
});
|
|
57
|
+
if (result.status !== 0) {
|
|
58
|
+
process.stderr.write(`runtime-dist sync failed: ${(result.stderr || result.stdout || `rsync exited ${result.status}`).trim()}\n`);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
stampRuntimeTitles({ target: path.resolve(args.target), runtimeDist });
|
|
62
|
+
process.stdout.write(`runtime-dist updated after webpack compile (${new Date().toISOString()})\n`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function poll() {
|
|
66
|
+
if (!processAlive(watcherPid)) {
|
|
67
|
+
removeOwnPidFile();
|
|
68
|
+
process.exit(0);
|
|
69
|
+
}
|
|
70
|
+
let fd;
|
|
71
|
+
try {
|
|
72
|
+
fd = fs.openSync(watchLog, 'r');
|
|
73
|
+
const size = fs.fstatSync(fd).size;
|
|
74
|
+
if (size < offset) {
|
|
75
|
+
offset = 0;
|
|
76
|
+
carry = '';
|
|
77
|
+
}
|
|
78
|
+
if (size > offset) {
|
|
79
|
+
const length = size - offset;
|
|
80
|
+
const chunk = Buffer.alloc(length);
|
|
81
|
+
const bytesRead = fs.readSync(fd, chunk, 0, length, offset);
|
|
82
|
+
offset += bytesRead;
|
|
83
|
+
const text = carry + chunk.subarray(0, bytesRead).toString('utf8');
|
|
84
|
+
const lines = text.split(/\r?\n/u);
|
|
85
|
+
carry = lines.pop() || '';
|
|
86
|
+
if (lines.some((line) => compilePattern.test(line))) syncRuntimeDist();
|
|
87
|
+
}
|
|
88
|
+
} catch (error) {
|
|
89
|
+
if (error?.code !== 'ENOENT') process.stderr.write(`runtime-dist sync monitor: ${error.message}\n`);
|
|
90
|
+
} finally {
|
|
91
|
+
if (fd !== undefined) fs.closeSync(fd);
|
|
92
|
+
}
|
|
93
|
+
timer = setTimeout(poll, 250);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
fs.mkdirSync(path.dirname(pidFile), { recursive: true });
|
|
97
|
+
process.on('SIGTERM', () => {
|
|
98
|
+
clearTimeout(timer);
|
|
99
|
+
removeOwnPidFile();
|
|
100
|
+
process.exit(0);
|
|
101
|
+
});
|
|
102
|
+
process.on('SIGINT', () => {
|
|
103
|
+
clearTimeout(timer);
|
|
104
|
+
removeOwnPidFile();
|
|
105
|
+
process.exit(0);
|
|
106
|
+
});
|
|
107
|
+
poll();
|