@deeeed/metamask-harness 0.2.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 +161 -0
- package/README.md +140 -0
- package/bin/mm-harness +99 -0
- package/docs/CHEATSHEET.md +61 -0
- package/docs/CLI-SPEC.md +915 -0
- package/docs/MENTAL-MODEL.md +295 -0
- package/docs/architecture.md +367 -0
- package/docs/extension-runtime-commands.md +60 -0
- package/docs/harness-cli.md +43 -0
- package/docs/live-adapter-contract.md +188 -0
- package/docs/package-boundaries.md +47 -0
- package/docs/perps-flow-catalog.md +235 -0
- package/docs/recipe-libraries.md +95 -0
- package/docs/runtime-file-conventions.md +36 -0
- package/library/actions/core/perps/_controller.mjs +727 -0
- package/library/actions/core/perps/assert_orders.mjs +53 -0
- package/library/actions/core/perps/assert_positions.mjs +52 -0
- package/library/actions/core/perps/close_orders.mjs +97 -0
- package/library/actions/core/perps/close_positions.mjs +118 -0
- package/library/actions/core/perps/ensure_orders.mjs +40 -0
- package/library/actions/core/perps/ensure_positions.mjs +37 -0
- package/library/actions/core/perps/place_order.mjs +201 -0
- package/library/actions/core/perps/read_account.mjs +30 -0
- package/library/actions/core/perps/read_orders.mjs +27 -0
- package/library/actions/core/perps/read_positions.mjs +27 -0
- package/library/actions/core/perps/start_state.mjs +92 -0
- package/library/actions/core/perps/teardown_state.mjs +86 -0
- package/library/actions/extension/perps/assert_orders.mjs +11 -0
- package/library/actions/extension/perps/assert_positions.mjs +11 -0
- package/library/actions/extension/perps/close_orders.mjs +8 -0
- package/library/actions/extension/perps/close_positions.mjs +8 -0
- package/library/actions/extension/perps/ensure_orders.mjs +4 -0
- package/library/actions/extension/perps/ensure_positions.mjs +4 -0
- package/library/actions/extension/perps/perps.mjs +730 -0
- package/library/actions/extension/perps/place_order.mjs +7 -0
- package/library/actions/extension/perps/read_orders.mjs +4 -0
- package/library/actions/extension/perps/read_positions.mjs +3 -0
- package/library/actions/extension/platform/cdp.mjs +541 -0
- package/library/actions/extension/ui/navigate.mjs +44 -0
- package/library/actions/extension/wallet/ensure_unlocked.mjs +36 -0
- package/library/actions/extension/wallet/read_state.mjs +27 -0
- package/library/actions/extension/wallet/select_account.mjs +48 -0
- package/library/actions/extension/wallet/setup.mjs +35 -0
- package/library/actions/mobile/app-overlay/app/dev-tools/AgenticService/AgentStepHud.tsx.patch +185 -0
- package/library/actions/mobile/app-overlay/app/dev-tools/AgenticService/AgenticService.ts.patch +1662 -0
- package/library/actions/mobile/bridge-runtime/cdp-bridge.cjs +686 -0
- package/library/actions/mobile/bridge-runtime/lib/cdp-eval.cjs +110 -0
- package/library/actions/mobile/bridge-runtime/lib/config.cjs +39 -0
- package/library/actions/mobile/bridge-runtime/lib/issue-capture.cjs +446 -0
- package/library/actions/mobile/bridge-runtime/lib/target-discovery.cjs +204 -0
- package/library/actions/mobile/bridge-runtime/lib/ws-client.cjs +108 -0
- package/library/actions/mobile/bridge-runtime/setup-wallet.sh +442 -0
- package/library/actions/mobile/perps/assert_orders.mjs +11 -0
- package/library/actions/mobile/perps/assert_positions.mjs +11 -0
- package/library/actions/mobile/perps/close_orders.mjs +8 -0
- package/library/actions/mobile/perps/close_positions.mjs +8 -0
- package/library/actions/mobile/perps/ensure_orders.mjs +4 -0
- package/library/actions/mobile/perps/ensure_positions.mjs +4 -0
- package/library/actions/mobile/perps/perps.mjs +709 -0
- package/library/actions/mobile/perps/place_order.mjs +7 -0
- package/library/actions/mobile/perps/read_orders.mjs +4 -0
- package/library/actions/mobile/perps/read_positions.mjs +3 -0
- package/library/actions/mobile/platform/bridge.mjs +283 -0
- package/library/actions/mobile/ui/navigate.mjs +38 -0
- package/library/actions/mobile/wallet/ensure_unlocked.mjs +107 -0
- package/library/actions/mobile/wallet/home.mjs +35 -0
- package/library/actions/mobile/wallet/read_state.mjs +40 -0
- package/library/actions/mobile/wallet/select_account.mjs +48 -0
- package/library/actions/mobile/wallet/setup.mjs +220 -0
- package/library/flows/perps.flows.json +64 -0
- package/library/library.json +7 -0
- package/library/manifests/core.action-manifest.json +1282 -0
- package/library/manifests/extension.action-manifest.json +1749 -0
- package/library/manifests/mobile.action-manifest.json +1753 -0
- package/library/recipes/action-validation.extension.recipe.json +417 -0
- package/library/recipes/action-validation.mobile.recipe.json +422 -0
- package/library/recipes/order-lifecycle.core.recipe.json +78 -0
- package/library/recipes/perps-lifecycle.recipe.json +194 -0
- package/library/recipes/read-markets.core.recipe.json +38 -0
- package/library/recipes/smoke.extension.recipe.json +31 -0
- package/library/recipes/smoke.mobile.recipe.json +31 -0
- package/library/recipes/trading-lifecycle.core.recipe.json +76 -0
- package/orchestration/compat-overlays/README.md +19 -0
- package/orchestration/compat-overlays/mobile/README.md +13 -0
- package/orchestration/compat-overlays/mobile/rn81-message-event-source.patch +42 -0
- package/orchestration/core/cleanup.sh +37 -0
- package/orchestration/core/inject.sh +154 -0
- package/orchestration/doctor.mjs +72 -0
- package/orchestration/extension/cleanup.mjs +60 -0
- package/orchestration/extension/console-tail.mjs +228 -0
- package/orchestration/extension/ensure-browser.sh +416 -0
- package/orchestration/extension/ensure-ready.ts +185 -0
- package/orchestration/extension/extension-id.ts +107 -0
- package/orchestration/extension/inject.mjs +266 -0
- package/orchestration/extension/launch-browser.cjs +216 -0
- package/orchestration/extension/launch.sh +175 -0
- package/orchestration/extension/live.sh +320 -0
- package/orchestration/extension/pin-remote-flags.cjs +45 -0
- package/orchestration/extension/readiness.mjs +414 -0
- package/orchestration/extension/refresh-build.sh +190 -0
- package/orchestration/extension/runtime-decision.ts +445 -0
- package/orchestration/extension/runtime.ts +407 -0
- package/orchestration/extension/seed-fixture.sh +177 -0
- package/orchestration/extension/sidepanel-toggle.sh +291 -0
- package/orchestration/extension/snapshot-dist.sh +84 -0
- package/orchestration/extension/start-watch.sh +339 -0
- package/orchestration/extension/wallet-fixture-state.cjs +1086 -0
- package/orchestration/lib/activate-repo-node.sh +144 -0
- package/orchestration/lib/cli-color.mjs +84 -0
- package/orchestration/lib/cli-commands.mjs +243 -0
- package/orchestration/lib/cli-home.mjs +354 -0
- package/orchestration/lib/cli-ux.sh +252 -0
- package/orchestration/lib/cli-version.mjs +123 -0
- package/orchestration/lib/ensure-runner-deps.sh +56 -0
- package/orchestration/lib/harness-path.sh +55 -0
- package/orchestration/lib/hash-helpers.sh +44 -0
- package/orchestration/lib/json-field.sh +23 -0
- package/orchestration/lib/log-tui.mjs +304 -0
- package/orchestration/lib/open-debug.mjs +317 -0
- package/orchestration/lib/path-defaults.json +4 -0
- package/orchestration/lib/progress.mjs +107 -0
- package/orchestration/lib/recipe-paths.mjs +26 -0
- package/orchestration/lib/resolve-farmslot-ports.sh +144 -0
- package/orchestration/manifest.json +358 -0
- package/orchestration/mobile/cleanup.sh +192 -0
- package/orchestration/mobile/deps-markers.ts +21 -0
- package/orchestration/mobile/inject.sh +681 -0
- package/orchestration/mobile/launch.sh +137 -0
- package/orchestration/mobile/live.sh +125 -0
- package/orchestration/mobile/runtime-decision.ts +292 -0
- package/orchestration/porcelain/metamask-recipe +99 -0
- package/orchestration/porcelain/mm-recipe +1591 -0
- package/orchestration/porcelain/mme-recipe +1181 -0
- package/package.json +59 -0
- package/runner/extension/verify.sh +511 -0
- package/runner/mobile/verify.sh +501 -0
- package/runner/src/adapters.ts +601 -0
- package/runner/src/cli.ts +1820 -0
- package/runner/src/commands/debug.ts +44 -0
- package/runner/src/commands/fixtures.ts +99 -0
- package/runner/src/commands/launch.ts +397 -0
- package/runner/src/commands/logs.ts +60 -0
- package/runner/src/commands/shared.ts +138 -0
- package/runner/src/completions-cache.ts +86 -0
- package/runner/src/doctor.ts +203 -0
- package/runner/src/harness.ts +516 -0
- package/runner/src/heal-bounds.ts +179 -0
- package/runner/src/index.ts +6 -0
- package/runner/src/live-adapter-contract.ts +274 -0
- package/runner/src/manifest.ts +47 -0
- package/runner/src/mm-harness-cli.ts +488 -0
- package/runner/src/paths.ts +198 -0
- package/runner/src/recording-target.ts +147 -0
- package/runner/src/run-recording.ts +329 -0
- package/runner/src/runner.ts +108 -0
- package/runner/src/types.ts +57 -0
- package/scripts/completions.sh +125 -0
- package/scripts/install-completions.sh +62 -0
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# sidepanel-toggle.sh — window-mode control for the extension instance.
|
|
3
|
+
#
|
|
4
|
+
# Purpose:
|
|
5
|
+
# App control: drives chrome.sidePanel for one extension instance's CDP
|
|
6
|
+
# browser (status/open/close/toggle/cycle) without global shortcuts.
|
|
7
|
+
#
|
|
8
|
+
# Inputs: subcommand (status|open|close|toggle|cycle, default status);
|
|
9
|
+
# --cdp-port <port> (required), --ext-id <id>, --agent-dir <dir>,
|
|
10
|
+
# --settle-ms <ms>; env REPO, CDP_PORT, EXT_ID, SETTLE_MS, AGENT_DIR.
|
|
11
|
+
# Outputs: status/progress lines on stdout.
|
|
12
|
+
# Exit 0 — action done; 1 — repo/args missing or action failed;
|
|
13
|
+
# 2 — non-numeric port / CDP unreachable.
|
|
14
|
+
# Never touches: other instances' browsers (single CDP port scope); the
|
|
15
|
+
# dist build (read-only).
|
|
16
|
+
#
|
|
17
|
+
# Usage:
|
|
18
|
+
# bash sidepanel-toggle.sh status --cdp-port 6663
|
|
19
|
+
# bash sidepanel-toggle.sh open --cdp-port 6663 [--ext-id <extension id>]
|
|
20
|
+
# bash sidepanel-toggle.sh close --cdp-port 6663
|
|
21
|
+
# bash sidepanel-toggle.sh toggle --cdp-port 6663
|
|
22
|
+
# bash sidepanel-toggle.sh cycle --cdp-port 6663 [--settle-ms 4000]
|
|
23
|
+
#
|
|
24
|
+
# CDP can inspect, activate, and close sidepanel targets. Opening the side panel
|
|
25
|
+
# is done from a clicked extension-page button so Chrome treats
|
|
26
|
+
# `chrome.sidePanel.open()` as a user gesture while the action remains scoped to
|
|
27
|
+
# this slot's CDP browser. This avoids global Alt+Shift+M shortcut ambiguity
|
|
28
|
+
# when several Chromium profiles are running.
|
|
29
|
+
set -euo pipefail
|
|
30
|
+
|
|
31
|
+
ACTION="${1:-status}"
|
|
32
|
+
if [ "$ACTION" = "-h" ] || [ "$ACTION" = "--help" ]; then
|
|
33
|
+
echo "Usage: sidepanel-toggle.sh <status|open|close|toggle|cycle> --cdp-port <port> [--ext-id <id>] [--agent-dir <dir>] [--settle-ms <ms>]"
|
|
34
|
+
exit 0
|
|
35
|
+
fi
|
|
36
|
+
shift || true
|
|
37
|
+
|
|
38
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
39
|
+
REPO="${REPO:-}"
|
|
40
|
+
if [ -z "$REPO" ]; then
|
|
41
|
+
d="$SCRIPT_DIR"
|
|
42
|
+
while [ "$d" != "/" ]; do
|
|
43
|
+
if [ -f "$d/package.json" ] && [ -d "$d/dist/chrome" ]; then
|
|
44
|
+
REPO="$d"
|
|
45
|
+
break
|
|
46
|
+
fi
|
|
47
|
+
d="$(dirname "$d")"
|
|
48
|
+
done
|
|
49
|
+
fi
|
|
50
|
+
if [ -z "$REPO" ]; then
|
|
51
|
+
echo "FAIL: could not resolve repo root from ${SCRIPT_DIR}; pass REPO=/path/to/metamask-extension" >&2
|
|
52
|
+
exit 1
|
|
53
|
+
fi
|
|
54
|
+
cd "$REPO"
|
|
55
|
+
|
|
56
|
+
CDP_PORT="${CDP_PORT:-}"
|
|
57
|
+
EXT_ID="${EXT_ID:-}"
|
|
58
|
+
SETTLE_MS="${SETTLE_MS:-4000}"
|
|
59
|
+
AGENT_DIR="${AGENT_DIR:-$SCRIPT_DIR}"
|
|
60
|
+
|
|
61
|
+
while [[ $# -gt 0 ]]; do
|
|
62
|
+
case "$1" in
|
|
63
|
+
--cdp-port) CDP_PORT="$2"; shift 2 ;;
|
|
64
|
+
--ext-id) EXT_ID="$2"; shift 2 ;;
|
|
65
|
+
--agent-dir) AGENT_DIR="$2"; shift 2 ;;
|
|
66
|
+
--settle-ms) SETTLE_MS="$2"; shift 2 ;;
|
|
67
|
+
-h|--help)
|
|
68
|
+
echo "Usage: sidepanel-toggle.sh <status|open|close|toggle|cycle> --cdp-port <port> [--ext-id <id>] [--agent-dir <dir>] [--settle-ms <ms>]"
|
|
69
|
+
exit 0
|
|
70
|
+
;;
|
|
71
|
+
*) echo "unknown arg: $1" >&2; exit 1 ;;
|
|
72
|
+
esac
|
|
73
|
+
done
|
|
74
|
+
|
|
75
|
+
if [ -z "$CDP_PORT" ]; then
|
|
76
|
+
echo "FAIL: --cdp-port is required" >&2
|
|
77
|
+
exit 1
|
|
78
|
+
fi
|
|
79
|
+
if ! [[ "$CDP_PORT" =~ ^[0-9]+$ ]]; then
|
|
80
|
+
echo "FAIL: CDP port \"$CDP_PORT\" must be numeric" >&2
|
|
81
|
+
exit 2
|
|
82
|
+
fi
|
|
83
|
+
if ! curl -s -m 3 "http://127.0.0.1:${CDP_PORT}/json/version" >/dev/null 2>&1; then
|
|
84
|
+
echo "FAIL: CDP not reachable on port ${CDP_PORT}" >&2
|
|
85
|
+
exit 2
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
json_list() {
|
|
89
|
+
curl -s "http://127.0.0.1:${CDP_PORT}/json/list"
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
find_sidepanel_id() {
|
|
93
|
+
json_list | python3 -c "import json,sys; d=json.load(sys.stdin); ids=[t.get('id','') for t in d if t.get('type')=='page' and 'sidepanel.html' in t.get('url','')]; print(ids[0] if ids else '')"
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
resolve_ext_id() {
|
|
97
|
+
if [ -n "$EXT_ID" ]; then
|
|
98
|
+
printf '%s\n' "$EXT_ID"
|
|
99
|
+
return
|
|
100
|
+
fi
|
|
101
|
+
if [ -f "$AGENT_DIR/extension.id" ]; then
|
|
102
|
+
tr -d '[:space:]' < "$AGENT_DIR/extension.id"
|
|
103
|
+
return
|
|
104
|
+
fi
|
|
105
|
+
json_list | python3 -c "import json,re,sys; d=json.load(sys.stdin); ids=[]; [ids.extend(re.findall(r'^chrome-extension://([^/]+)/', t.get('url',''))) for t in d]; print(ids[0] if ids else '')"
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
activate_sandbox_page() {
|
|
109
|
+
local ext_id target_id
|
|
110
|
+
ext_id="$(resolve_ext_id)"
|
|
111
|
+
target_id="$(
|
|
112
|
+
EXT_ID="$ext_id" json_list | python3 -c "import json,os,sys; d=json.load(sys.stdin); ext=os.environ.get('EXT_ID',''); pages=[t for t in d if t.get('type')=='page']; target=next((t for t in pages if ext and t.get('url','').startswith('chrome-extension://'+ext+'/') and 'sidepanel.html' not in t.get('url','')), None) or next((t for t in pages if t.get('url','').startswith('http://') or t.get('url','').startswith('https://')), None) or (pages[0] if pages else None); print(target.get('id','') if target else '')"
|
|
113
|
+
)"
|
|
114
|
+
if [ -n "$target_id" ]; then
|
|
115
|
+
curl -s "http://127.0.0.1:${CDP_PORT}/json/activate/${target_id}" >/dev/null || true
|
|
116
|
+
fi
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
now_ms() {
|
|
120
|
+
python3 -c "import time; print(int(time.time() * 1000))"
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
wait_for_sidepanel() {
|
|
124
|
+
local start deadline now
|
|
125
|
+
start="$(now_ms)"
|
|
126
|
+
deadline=$((start + SETTLE_MS))
|
|
127
|
+
while :; do
|
|
128
|
+
if [ -n "$(find_sidepanel_id)" ]; then
|
|
129
|
+
return 0
|
|
130
|
+
fi
|
|
131
|
+
now="$(now_ms)"
|
|
132
|
+
if [ "$now" -ge "$deadline" ]; then
|
|
133
|
+
return 1
|
|
134
|
+
fi
|
|
135
|
+
sleep 0.2
|
|
136
|
+
done
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
print_targets() {
|
|
140
|
+
json_list | python3 -c "import json,sys; d=json.load(sys.stdin); [print(f' {t.get(\"type\",\"?\")[:18]:18s} {t.get(\"url\",\"\")[:100]}') for t in d]"
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
status_sidepanel() {
|
|
144
|
+
local sp
|
|
145
|
+
sp="$(find_sidepanel_id)"
|
|
146
|
+
if [ -n "$sp" ]; then
|
|
147
|
+
echo "[sidepanel] open target ${sp}"
|
|
148
|
+
else
|
|
149
|
+
echo "[sidepanel] closed"
|
|
150
|
+
fi
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
close_sidepanel() {
|
|
154
|
+
local sp
|
|
155
|
+
sp="$(find_sidepanel_id)"
|
|
156
|
+
if [ -z "$sp" ]; then
|
|
157
|
+
echo "[sidepanel] already closed"
|
|
158
|
+
return 0
|
|
159
|
+
fi
|
|
160
|
+
curl -s "http://127.0.0.1:${CDP_PORT}/json/close/${sp}" >/dev/null
|
|
161
|
+
echo "[sidepanel] closed target ${sp}"
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
open_sidepanel() {
|
|
165
|
+
if [ -n "$(find_sidepanel_id)" ]; then
|
|
166
|
+
echo "[sidepanel] already open"
|
|
167
|
+
return 0
|
|
168
|
+
fi
|
|
169
|
+
|
|
170
|
+
activate_sandbox_page
|
|
171
|
+
|
|
172
|
+
local ext_id
|
|
173
|
+
ext_id="$(resolve_ext_id)"
|
|
174
|
+
if [ -z "$ext_id" ]; then
|
|
175
|
+
echo "FAIL: could not resolve extension id for CDP ${CDP_PORT}" >&2
|
|
176
|
+
exit 4
|
|
177
|
+
fi
|
|
178
|
+
|
|
179
|
+
CDP_PORT="$CDP_PORT" EXT_ID="$ext_id" node <<'NODE'
|
|
180
|
+
const { chromium } = require('playwright');
|
|
181
|
+
|
|
182
|
+
(async () => {
|
|
183
|
+
const port = process.env.CDP_PORT;
|
|
184
|
+
const extId = process.env.EXT_ID;
|
|
185
|
+
const browser = await chromium.connectOverCDP(`http://127.0.0.1:${port}`);
|
|
186
|
+
const context = browser.contexts()[0];
|
|
187
|
+
let createdPage = false;
|
|
188
|
+
let page = context
|
|
189
|
+
.pages()
|
|
190
|
+
.find(
|
|
191
|
+
(candidate) =>
|
|
192
|
+
candidate.url().startsWith(`chrome-extension://${extId}/`) &&
|
|
193
|
+
!candidate.url().includes('/sidepanel.html'),
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
if (!page) {
|
|
197
|
+
page = await context.newPage();
|
|
198
|
+
createdPage = true;
|
|
199
|
+
await page.goto(`chrome-extension://${extId}/home.html`, {
|
|
200
|
+
waitUntil: 'domcontentloaded',
|
|
201
|
+
timeout: 15000,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
await page.bringToFront();
|
|
206
|
+
const result = await page.evaluate(async () => {
|
|
207
|
+
const currentWindow = await chrome.windows.getCurrent();
|
|
208
|
+
const id = '__recipe_open_sidepanel__';
|
|
209
|
+
document.getElementById(id)?.remove();
|
|
210
|
+
const button = document.createElement('button');
|
|
211
|
+
button.id = id;
|
|
212
|
+
button.textContent = 'open sidepanel';
|
|
213
|
+
button.style.cssText =
|
|
214
|
+
'position:fixed;left:8px;top:8px;z-index:2147483647';
|
|
215
|
+
button.onclick = async () => {
|
|
216
|
+
try {
|
|
217
|
+
await chrome.sidePanel.open({ windowId: currentWindow.id });
|
|
218
|
+
button.dataset.result = `ok:${currentWindow.id}`;
|
|
219
|
+
} catch (error) {
|
|
220
|
+
button.dataset.result = `error:${
|
|
221
|
+
error && error.message ? error.message : String(error)
|
|
222
|
+
}`;
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
document.documentElement.appendChild(button);
|
|
226
|
+
return { windowId: currentWindow.id };
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
await page.locator('#__recipe_open_sidepanel__').click({ timeout: 5000 });
|
|
230
|
+
const clickResult = await page
|
|
231
|
+
.locator('#__recipe_open_sidepanel__')
|
|
232
|
+
.evaluate((button) => button.dataset.result || '');
|
|
233
|
+
if (!clickResult.startsWith('ok:')) {
|
|
234
|
+
throw new Error(
|
|
235
|
+
`chrome.sidePanel.open failed for window ${result.windowId}: ${clickResult}`,
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
await page.evaluate(() => {
|
|
239
|
+
document.getElementById('__recipe_open_sidepanel__')?.remove();
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
if (createdPage) {
|
|
243
|
+
try {
|
|
244
|
+
await page.close();
|
|
245
|
+
} catch (error) {
|
|
246
|
+
console.warn(
|
|
247
|
+
`[sidepanel] helper page close failed after successful open: ${
|
|
248
|
+
error && error.message ? error.message : error
|
|
249
|
+
}`,
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
await browser.close();
|
|
254
|
+
})().catch((error) => {
|
|
255
|
+
console.error(`FAIL: ${error.message || error}`);
|
|
256
|
+
process.exit(4);
|
|
257
|
+
});
|
|
258
|
+
NODE
|
|
259
|
+
|
|
260
|
+
if wait_for_sidepanel; then
|
|
261
|
+
echo "[sidepanel] opened"
|
|
262
|
+
return 0
|
|
263
|
+
fi
|
|
264
|
+
|
|
265
|
+
echo "FAIL: sidepanel did not appear within ${SETTLE_MS}ms" >&2
|
|
266
|
+
echo " Current CDP targets:" >&2
|
|
267
|
+
print_targets >&2
|
|
268
|
+
exit 3
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
case "$ACTION" in
|
|
272
|
+
status) status_sidepanel ;;
|
|
273
|
+
open) open_sidepanel ;;
|
|
274
|
+
close) close_sidepanel ;;
|
|
275
|
+
toggle)
|
|
276
|
+
if [ -n "$(find_sidepanel_id)" ]; then
|
|
277
|
+
close_sidepanel
|
|
278
|
+
else
|
|
279
|
+
open_sidepanel
|
|
280
|
+
fi
|
|
281
|
+
;;
|
|
282
|
+
cycle)
|
|
283
|
+
close_sidepanel
|
|
284
|
+
sleep 1
|
|
285
|
+
open_sidepanel
|
|
286
|
+
;;
|
|
287
|
+
*)
|
|
288
|
+
echo "Usage: bash sidepanel-toggle.sh {status|open|close|toggle|cycle} --cdp-port PORT [--ext-id ID] [--agent-dir DIR] [--settle-ms N]" >&2
|
|
289
|
+
exit 1
|
|
290
|
+
;;
|
|
291
|
+
esac
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# snapshot-dist.sh — runtime-dist snapshot with git-id freshness guard
|
|
3
|
+
# mega-string in scripts/extension/live.sh)
|
|
4
|
+
#
|
|
5
|
+
# Purpose:
|
|
6
|
+
# Snapshots dist/chrome into a per-run runtime-dist so Chrome's loaded
|
|
7
|
+
# extension can't be ripped out mid-rebuild, then verifies the snapshot's
|
|
8
|
+
# "from git id:" matches the source dist (catches mid-rebuild copies).
|
|
9
|
+
#
|
|
10
|
+
# Inputs (flags):
|
|
11
|
+
# --dist <dir> source dist (required, e.g. <repo>/dist/chrome)
|
|
12
|
+
# --runtime-dist <dir> snapshot destination (required, recreated)
|
|
13
|
+
# --wait-iterations <n> manifest wait loop length, 2s each (default 180)
|
|
14
|
+
# --summary <file> optional standard summary.json
|
|
15
|
+
#
|
|
16
|
+
# Outputs:
|
|
17
|
+
# <runtime-dist>/ snapshot (excludes _metadata); optional --summary file
|
|
18
|
+
# {feature,status,inputs,outputs,generatedAt}.
|
|
19
|
+
# Exit 0 — snapshot fresh; 1 — manifest never appeared, rsync failed, or
|
|
20
|
+
# git-id mismatch (mid-rebuild); 2 — bad args.
|
|
21
|
+
#
|
|
22
|
+
# Never touches: the source dist (read-only); anything outside
|
|
23
|
+
# --runtime-dist and --summary.
|
|
24
|
+
set -euo pipefail
|
|
25
|
+
|
|
26
|
+
DIST=""
|
|
27
|
+
RUNTIME_DIST=""
|
|
28
|
+
WAIT_ITERATIONS=180
|
|
29
|
+
SUMMARY=""
|
|
30
|
+
require_value() { [ "$#" -ge 2 ] || { echo "Missing value for $1" >&2; exit 2; }; }
|
|
31
|
+
while [ "$#" -gt 0 ]; do
|
|
32
|
+
case "$1" in
|
|
33
|
+
--dist) require_value "$@"; DIST="$2"; shift 2 ;;
|
|
34
|
+
--runtime-dist) require_value "$@"; RUNTIME_DIST="$2"; shift 2 ;;
|
|
35
|
+
--wait-iterations) require_value "$@"; WAIT_ITERATIONS="$2"; shift 2 ;;
|
|
36
|
+
--summary) require_value "$@"; SUMMARY="$2"; shift 2 ;;
|
|
37
|
+
-h|--help)
|
|
38
|
+
echo "Usage: snapshot-dist.sh --dist <dir> --runtime-dist <dir> [--wait-iterations <n>] [--summary <file>]"
|
|
39
|
+
exit 0
|
|
40
|
+
;;
|
|
41
|
+
*) echo "Unknown arg: $1" >&2; exit 2 ;;
|
|
42
|
+
esac
|
|
43
|
+
done
|
|
44
|
+
[ -n "$DIST" ] || { echo "Missing --dist" >&2; exit 2; }
|
|
45
|
+
[ -n "$RUNTIME_DIST" ] || { echo "Missing --runtime-dist" >&2; exit 2; }
|
|
46
|
+
case "$WAIT_ITERATIONS" in ''|*[!0-9]*) echo "Invalid --wait-iterations (must be numeric): $WAIT_ITERATIONS" >&2; exit 2 ;; esac
|
|
47
|
+
|
|
48
|
+
status=fail
|
|
49
|
+
finish() {
|
|
50
|
+
if [ -n "$SUMMARY" ]; then
|
|
51
|
+
mkdir -p "$(dirname "$SUMMARY")"
|
|
52
|
+
STATUS_FOR_SUMMARY="$status" DIST_FOR_SUMMARY="$DIST" RUNTIME_DIST_FOR_SUMMARY="$RUNTIME_DIST" SUMMARY_PATH="$SUMMARY" node <<'NODE' || true
|
|
53
|
+
const fs = require('fs');
|
|
54
|
+
fs.writeFileSync(process.env.SUMMARY_PATH, `${JSON.stringify({
|
|
55
|
+
feature: 'extension/snapshot-dist',
|
|
56
|
+
status: process.env.STATUS_FOR_SUMMARY,
|
|
57
|
+
inputs: { dist: process.env.DIST_FOR_SUMMARY },
|
|
58
|
+
outputs: { runtimeDist: process.env.RUNTIME_DIST_FOR_SUMMARY },
|
|
59
|
+
generatedAt: new Date().toISOString(),
|
|
60
|
+
}, null, 2)}\n`);
|
|
61
|
+
NODE
|
|
62
|
+
fi
|
|
63
|
+
}
|
|
64
|
+
trap finish EXIT
|
|
65
|
+
|
|
66
|
+
for i in $(seq 1 "$WAIT_ITERATIONS"); do
|
|
67
|
+
[ -f "$DIST/manifest.json" ] && break
|
|
68
|
+
if [ $((i % 15)) -eq 0 ]; then
|
|
69
|
+
echo "[recipe-harness] waiting for dist manifest (~$((i * 2))s): $DIST/manifest.json" >&2
|
|
70
|
+
fi
|
|
71
|
+
sleep 2
|
|
72
|
+
done
|
|
73
|
+
test -f "$DIST/manifest.json" || { echo "snapshot-dist: no manifest at $DIST/manifest.json" >&2; exit 1; }
|
|
74
|
+
|
|
75
|
+
echo "[recipe-harness] snapshotting dist -> runtime-dist: $RUNTIME_DIST" >&2
|
|
76
|
+
rm -rf "$RUNTIME_DIST" && mkdir -p "$RUNTIME_DIST" \
|
|
77
|
+
&& rsync -a --delete --exclude _metadata "$DIST/" "$RUNTIME_DIST/" || exit 1
|
|
78
|
+
echo "[recipe-harness] runtime-dist snapshot complete" >&2
|
|
79
|
+
|
|
80
|
+
# Freshness guard: the loaded runtime-dist must match dist/chrome's git id. A
|
|
81
|
+
# mismatch means the rsync caught a mid-rebuild dist; abort rather than load
|
|
82
|
+
# an inconsistent bundle (the "Element type is invalid: undefined" class of crash).
|
|
83
|
+
node -e 'const fs=require("fs");const id=p=>{try{return (JSON.parse(fs.readFileSync(p,"utf8")).description||"").match(/from git id: *([0-9a-f]+)/i)?.[1]||""}catch{return""}};const [distManifest,runtimeManifest]=process.argv.slice(-2);const d=id(distManifest),r=id(runtimeManifest);if(d&&d!==r){console.error("runtime-dist git id "+r+" != dist "+d+" (mid-rebuild?); aborting");process.exit(1)}' "$DIST/manifest.json" "$RUNTIME_DIST/manifest.json" || exit 1
|
|
84
|
+
status=pass
|