@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,1181 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
usage() {
|
|
5
|
+
cat >&2 <<'EOF'
|
|
6
|
+
Usage:
|
|
7
|
+
mme-recipe home status screen (default)
|
|
8
|
+
mme-recipe status [--json] readiness summary + suggested next commands
|
|
9
|
+
mme-recipe ports [--json] slot ports, runtime paths, tmux hint
|
|
10
|
+
mme-recipe up [--json] [--dry-run] decide + run minimum work to reach ready
|
|
11
|
+
mme-recipe sync [--json] refresh harness scripts + canonicalize wallet fixture
|
|
12
|
+
mme-recipe completion [install] install shell tab completion (bash + zsh)
|
|
13
|
+
mme-recipe prepare install harness and optionally validate CDP health
|
|
14
|
+
--target <repo> --cdp-port <port> --runtime-dir <dir> [--validate]
|
|
15
|
+
mme-recipe runtime-status [--json] structured decision/health status
|
|
16
|
+
mme-recipe decision runtime-decision (what next?)
|
|
17
|
+
mme-recipe update perform the recommended runtime action
|
|
18
|
+
mme-recipe ready ensure one healthy extension home tab
|
|
19
|
+
mme-recipe watch start/reuse one webpack watcher for fast refresh
|
|
20
|
+
mme-recipe stop stop this checkout's watcher
|
|
21
|
+
mme-recipe logs [--full] tail last build events (default) or full webpack log
|
|
22
|
+
mme-recipe rebuild clean webpack + browser launch + verify (2-8 min)
|
|
23
|
+
mme-recipe reset alias for rebuild
|
|
24
|
+
mme-recipe reload alias for rebuild (browser refresh semantics)
|
|
25
|
+
mme-recipe reopen|launch aliases for rebuild
|
|
26
|
+
mme-recipe refresh fast refresh using an active watcher only
|
|
27
|
+
mme-recipe build rebuild when no watcher; refresh when watcher is active
|
|
28
|
+
mme-recipe refresh:once one-shot refresh-build helper, then ready
|
|
29
|
+
mme-recipe sidepanel [cycle|open] use harness sidepanel helper
|
|
30
|
+
mme-recipe debug [--json] [worker] open Chrome DevTools for extension UI or worker
|
|
31
|
+
mme-recipe interactive interactive menu
|
|
32
|
+
mme-recipe actions [--json]
|
|
33
|
+
mme-recipe doctor [--json]
|
|
34
|
+
mme-recipe run <recipe.json> [args] run recipe with detected CDP/env
|
|
35
|
+
mme-recipe <recipe.json> [args] shortcut for run
|
|
36
|
+
|
|
37
|
+
Detects Extension root, recipe runtime context, CDP_PORT, WATCHER_PORT.
|
|
38
|
+
EOF
|
|
39
|
+
recipe_speed_ladder
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
|
|
43
|
+
runner="$script_dir/metamask-recipe"
|
|
44
|
+
# This script lives at <root>/orchestration/porcelain/; the runner root is two up.
|
|
45
|
+
runner_root="$(cd "$script_dir/../.." && pwd -P)"
|
|
46
|
+
# shellcheck disable=SC1091
|
|
47
|
+
. "$runner_root/orchestration/lib/harness-path.sh"
|
|
48
|
+
. "$runner_root/orchestration/lib/cli-ux.sh"
|
|
49
|
+
# shellcheck disable=SC1091
|
|
50
|
+
. "$runner_root/orchestration/lib/resolve-farmslot-ports.sh"
|
|
51
|
+
RECIPE_RUNNER_ROOT="$runner_root"
|
|
52
|
+
install_script="$runner_root/orchestration/extension/inject.mjs"
|
|
53
|
+
open_debug_script="$runner_root/orchestration/lib/open-debug.mjs"
|
|
54
|
+
|
|
55
|
+
find_extension_root() {
|
|
56
|
+
local dir="$(pwd -P)"
|
|
57
|
+
while [ "$dir" != "/" ]; do
|
|
58
|
+
if [ -f "$dir/package.json" ] && { grep -q 'metamask-extension' "$dir/package.json" 2>/dev/null || [[ "$(basename "$dir")" == metamask-extension* ]]; }; then
|
|
59
|
+
printf '%s\n' "$dir"
|
|
60
|
+
return 0
|
|
61
|
+
fi
|
|
62
|
+
dir="$(dirname "$dir")"
|
|
63
|
+
done
|
|
64
|
+
return 1
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
json_field() {
|
|
68
|
+
local file="$1" field="$2"
|
|
69
|
+
[ -f "$file" ] || return 1
|
|
70
|
+
node - "$file" "$field" <<'NODE'
|
|
71
|
+
const fs = require('fs');
|
|
72
|
+
const [file, field] = process.argv.slice(-2);
|
|
73
|
+
try {
|
|
74
|
+
const data = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
75
|
+
const value = field.split('.').reduce((node, key) => node?.[key], data);
|
|
76
|
+
if (value !== undefined && value !== null && value !== '') process.stdout.write(String(value));
|
|
77
|
+
} catch { process.exit(1); }
|
|
78
|
+
NODE
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
load_env_file() {
|
|
82
|
+
local file="$1"
|
|
83
|
+
[ -f "$file" ] || return 0
|
|
84
|
+
while IFS= read -r line || [ -n "$line" ]; do
|
|
85
|
+
line="${line#export }"
|
|
86
|
+
case "$line" in
|
|
87
|
+
CDP_PORT=*|RECIPE_CDP_PORT=*|WATCHER_PORT=*|RECIPE_WATCHER_PORT=*)
|
|
88
|
+
key="${line%%=*}"
|
|
89
|
+
val="${line#*=}"
|
|
90
|
+
val="${val%%#*}"
|
|
91
|
+
val="$(printf '%s' "$val" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' -e 's/^"//' -e 's/"$//' -e "s/^'//" -e "s/'$//")"
|
|
92
|
+
[ -n "${!key:-}" ] || export "$key=$val"
|
|
93
|
+
;;
|
|
94
|
+
esac
|
|
95
|
+
done < "$file"
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
infer_slot_number() {
|
|
99
|
+
local root="$1" base
|
|
100
|
+
base="$(basename "$root")"
|
|
101
|
+
[[ "$base" =~ -([0-9]+)$ ]] && printf '%s\n' "${BASH_REMATCH[1]}"
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
ensure_extension_node() {
|
|
105
|
+
local arn
|
|
106
|
+
for arn in \
|
|
107
|
+
"$runner_root/orchestration/lib/activate-repo-node.sh" \
|
|
108
|
+
"$root/$harness_root/extension/scripts/lib/activate-repo-node.sh"; do
|
|
109
|
+
[ -f "$arn" ] || continue
|
|
110
|
+
# shellcheck disable=SC1090
|
|
111
|
+
. "$arn"
|
|
112
|
+
break
|
|
113
|
+
done
|
|
114
|
+
if command -v require_repo_node >/dev/null 2>&1; then
|
|
115
|
+
require_repo_node "$root"
|
|
116
|
+
return $?
|
|
117
|
+
fi
|
|
118
|
+
echo "mme-recipe: activate-repo-node.sh missing; run: recipe sync" >&2
|
|
119
|
+
return 1
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
write_extension_runtime_context() {
|
|
123
|
+
local farmslot="${FARMSLOT_ROOT:-$HOME/dev/farmslot}"
|
|
124
|
+
local slot_id="${RECIPE_SLOT_ID:-}"
|
|
125
|
+
[ -n "$slot_id" ] || slot_id="$(json_field "$runtime_json" slotId 2>/dev/null || true)"
|
|
126
|
+
[ -n "$slot_id" ] || { [ -n "${slot:-}" ] && slot_id="local-extension-${slot}"; }
|
|
127
|
+
apply_resolved_extension_ports "$root"
|
|
128
|
+
[ -n "${CDP_PORT:-}" ] || return 0
|
|
129
|
+
[ -n "${WATCHER_PORT:-}" ] || return 0
|
|
130
|
+
if [ -x "$farmslot/scripts/write-runtime-context.sh" ]; then
|
|
131
|
+
bash "$farmslot/scripts/write-runtime-context.sh" \
|
|
132
|
+
--repo "$root" \
|
|
133
|
+
--slot-id "${slot_id:-local-extension-${slot:-1}}" \
|
|
134
|
+
--runtime-dir "$runtime_dir" \
|
|
135
|
+
--platform chrome-extension \
|
|
136
|
+
--cdp-port "$CDP_PORT" \
|
|
137
|
+
--watcher-port "$WATCHER_PORT" \
|
|
138
|
+
--strict false >/dev/null
|
|
139
|
+
return 0
|
|
140
|
+
fi
|
|
141
|
+
mkdir -p "$root/$runtime_dir"
|
|
142
|
+
cat > "$root/$runtime_dir/agentic-runtime.json" <<JSON
|
|
143
|
+
{
|
|
144
|
+
"slotId": "${slot_id:-local-extension-${slot:-1}}",
|
|
145
|
+
"cdpPort": ${CDP_PORT},
|
|
146
|
+
"watcherPort": ${WATCHER_PORT}
|
|
147
|
+
}
|
|
148
|
+
JSON
|
|
149
|
+
cat > "$root/$runtime_dir/agentic-runtime.env" <<ENV
|
|
150
|
+
# Generated by mme-recipe sync
|
|
151
|
+
export CDP_PORT='${CDP_PORT}'
|
|
152
|
+
export RECIPE_CDP_PORT='${CDP_PORT}'
|
|
153
|
+
export WATCHER_PORT='${WATCHER_PORT}'
|
|
154
|
+
export RECIPE_WATCHER_PORT='${WATCHER_PORT}'
|
|
155
|
+
ENV
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
init_context() {
|
|
159
|
+
root="$(find_extension_root)" || { echo "mme-recipe: run from a metamask-extension checkout" >&2; exit 2; }
|
|
160
|
+
runtime_dir="$(recipe_runtime_dir)"
|
|
161
|
+
harness_root="$(harness_root)"
|
|
162
|
+
export RECIPE_RUNTIME_DIR="$runtime_dir" RECIPE_HARNESS_ROOT="$harness_root"
|
|
163
|
+
runtime_json="$root/$runtime_dir/recipe-runtime.json"
|
|
164
|
+
[ -f "$runtime_json" ] || runtime_json="$root/$runtime_dir/agentic-runtime.json"
|
|
165
|
+
load_env_file "$root/$runtime_dir/recipe-runtime.env"
|
|
166
|
+
load_env_file "$root/$runtime_dir/agentic-runtime.env"
|
|
167
|
+
load_env_file "$root/.js.env"
|
|
168
|
+
load_env_file "$root/.env"
|
|
169
|
+
load_env_file "$root/.env.local"
|
|
170
|
+
[ -n "${CDP_PORT:-}" ] || CDP_PORT="$(json_field "$runtime_json" cdpPort || true)"
|
|
171
|
+
[ -n "${WATCHER_PORT:-}" ] || WATCHER_PORT="$(json_field "$runtime_json" watcherPort || json_field "$runtime_json" devServerPort || true)"
|
|
172
|
+
slot="$(infer_slot_number "$root" || true)"
|
|
173
|
+
apply_resolved_extension_ports "$root"
|
|
174
|
+
if [ -n "$slot" ]; then
|
|
175
|
+
[ -n "${CDP_PORT:-}" ] || CDP_PORT="$((6660 + slot))"
|
|
176
|
+
[ -n "${WATCHER_PORT:-}" ] || WATCHER_PORT="$((9010 + slot))"
|
|
177
|
+
fi
|
|
178
|
+
export CDP_PORT RECIPE_CDP_PORT="${CDP_PORT:-}" WATCHER_PORT RECIPE_WATCHER_PORT="${WATCHER_PORT:-}"
|
|
179
|
+
mkdir -p "$root/temp/recipe-artifacts"
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
pretty_json() {
|
|
184
|
+
node -e '
|
|
185
|
+
let d="";
|
|
186
|
+
process.stdin.on("data", c => d += c).on("end", () => {
|
|
187
|
+
const color = process.env.RECIPE_COLOR === "1";
|
|
188
|
+
const c = (code, s) => color ? `\x1b[${code}m${s}\x1b[0m` : s;
|
|
189
|
+
try {
|
|
190
|
+
const v = JSON.parse(d);
|
|
191
|
+
const printDecision = (value) => {
|
|
192
|
+
const decision = value && typeof value === "object" ? value.decision : value;
|
|
193
|
+
if (!decision) return false;
|
|
194
|
+
const reasonCode = value && typeof value === "object" ? value.reasonCode : undefined;
|
|
195
|
+
const reasons = value && typeof value === "object" ? value.reasons : undefined;
|
|
196
|
+
console.log(`${c("1;33", "Decision")}: ${c("1;32", decision)}${reasonCode ? ` ${c("2", "-")} ${c("0;37", reasonCode)}` : ""}`);
|
|
197
|
+
if (Array.isArray(reasons)) for (const r of reasons) console.log(` ${c("2", "-")} ${r}`);
|
|
198
|
+
return true;
|
|
199
|
+
};
|
|
200
|
+
if (v.schemaVersion === 1 && v.status && v.cdpPort !== undefined) {
|
|
201
|
+
const status = String(v.status).toUpperCase();
|
|
202
|
+
const code = status === "PASS" ? "1;32" : "1;31";
|
|
203
|
+
console.log(`${c(code, status)} ${c("1;36", "runtime-launch")} ${c("2", `cdp=${v.cdpPort}`)}`);
|
|
204
|
+
if (v.reason) console.log(` ${c("2", "reason")}: ${c("0;37", v.reason)}`);
|
|
205
|
+
if (v.fix) console.log(` ${c("1;33", "fix")}: ${v.fix}`);
|
|
206
|
+
if (v.artifactsDir) console.log(` ${c("2", "artifacts")}: ${v.artifactsDir}`);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if (v.schemaVersion === 1 && v.adapter && typeof v.ready === "boolean") {
|
|
210
|
+
const label = v.ready ? "READY" : "NOT READY";
|
|
211
|
+
const code = v.ready ? "1;32" : "1;33";
|
|
212
|
+
console.log(`${c(code, label)} ${c("1;36", v.adapter)}${v.reason ? ` ${c("2", "-")} ${c("0;37", v.reason)}` : ""}`);
|
|
213
|
+
if (v.cdp && v.cdp.port) console.log(`${c("1;36", "CDP")}: ${v.cdp.port}${v.cdp.health && v.cdp.health.status ? ` ${c("2", `(${v.cdp.health.status})`)}` : ""}`);
|
|
214
|
+
if (v.watcher && v.watcher.port) console.log(`${c("1;36", "Watcher")}: ${v.watcher.port}`);
|
|
215
|
+
if (v.fixture) console.log(`${c("1;36", "Fixture")}: ${v.fixture.present ? "present" : "missing"}${v.fixture.path ? ` ${c("2", v.fixture.path)}` : ""}`);
|
|
216
|
+
if (v.decision) printDecision(v.decision);
|
|
217
|
+
if (Array.isArray(v.errors) && v.errors.length) for (const e of v.errors) console.log(`${c("1;31", "Error")}: ${e}`);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (v.decision) {
|
|
221
|
+
printDecision(v);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
if (v.adapter && Array.isArray(v.actions) && v.actions.every(a => a && a.name)) {
|
|
225
|
+
console.log(`${c("1;36", "Actions")} ${c("2", `(${v.adapter})`)}:`);
|
|
226
|
+
for (const a of v.actions) {
|
|
227
|
+
console.log(` ${c("1;32", a.name)}${a.description ? ` ${c("2", "-")} ${c("0;37", a.description)}` : ""}`);
|
|
228
|
+
}
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
if (v.status && v.adapter) {
|
|
232
|
+
const status = String(v.status).toUpperCase();
|
|
233
|
+
const code = status === "PASS" || status === "OK" ? "1;32" : status === "FAIL" || status === "ERROR" ? "1;31" : "1;33";
|
|
234
|
+
console.log(`${c(code, status)} ${c("1;36", v.adapter)}${v.compatibilityMode ? ` ${c("2", "-")} ${v.compatibilityMode}` : ""}`);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
if (v.account || v.route || v.deviceName) {
|
|
238
|
+
console.log(`${c("1;36", "Device")}: ${v.deviceName || "?"}${v.platform ? ` ${c("2", `(${v.platform})`)}` : ""}`);
|
|
239
|
+
console.log(`${c("1;36", "Route")}: ${v.route && v.route.name ? v.route.name : JSON.stringify(v.route || null)}`);
|
|
240
|
+
if (v.account) console.log(`${c("1;36", "Account")}: ${v.account.name || "?"} ${c("2", v.account.address || "")}`);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
console.log(JSON.stringify(v, null, 2));
|
|
244
|
+
} catch {
|
|
245
|
+
process.stdout.write(d);
|
|
246
|
+
}
|
|
247
|
+
})'
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
json_requested() {
|
|
251
|
+
for arg in "$@"; do [ "$arg" = "--json" ] && return 0; done
|
|
252
|
+
return 1
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
full_logs_requested() {
|
|
256
|
+
for arg in "$@"; do
|
|
257
|
+
case "$arg" in
|
|
258
|
+
--full|-f) return 0 ;;
|
|
259
|
+
esac
|
|
260
|
+
done
|
|
261
|
+
return 1
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
dry_run_requested() {
|
|
265
|
+
for arg in "$@"; do
|
|
266
|
+
case "$arg" in
|
|
267
|
+
--dry-run|--explain|-n) return 0 ;;
|
|
268
|
+
esac
|
|
269
|
+
done
|
|
270
|
+
return 1
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
color_requested() {
|
|
274
|
+
[ "${RECIPE_NO_COLOR:-}" != "1" ] && { [ "${RECIPE_COLOR:-}" = "1" ] || [ "${FORCE_COLOR:-}" = "1" ] || [ -t 1 ]; }
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
pipe_pretty() {
|
|
278
|
+
if color_requested; then (export RECIPE_COLOR=1; pretty_json); else pretty_json; fi
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
run_pretty() {
|
|
282
|
+
if json_requested "$@"; then "$@"; else "$@" | pipe_pretty; fi
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
run_json_pretty() {
|
|
286
|
+
if json_requested "$@"; then "$@"; else "$@" --json | pipe_pretty; fi
|
|
287
|
+
}
|
|
288
|
+
log() { printf 'mme-recipe: %s\n' "$*" >&2; }
|
|
289
|
+
need_cdp() { [ -n "${CDP_PORT:-}" ] || { echo "mme-recipe: CDP_PORT not found" >&2; exit 2; }; }
|
|
290
|
+
|
|
291
|
+
report_open_debug() {
|
|
292
|
+
local payload="$1" want_json="$2"
|
|
293
|
+
if [ "$want_json" = true ]; then
|
|
294
|
+
printf '%s\n' "$payload"
|
|
295
|
+
return 0
|
|
296
|
+
fi
|
|
297
|
+
node - "$payload" <<'NODE'
|
|
298
|
+
const [text] = process.argv.slice(2);
|
|
299
|
+
try {
|
|
300
|
+
const v = JSON.parse(text);
|
|
301
|
+
if (v.ok) {
|
|
302
|
+
const detail = v.method ? ` via ${v.method}` : '';
|
|
303
|
+
const where = v.cdpPort ? ` (cdp ${v.cdpPort})` : '';
|
|
304
|
+
const kind = v.targetKind === 'worker' ? 'service worker' : 'extension page';
|
|
305
|
+
console.log(`mme-recipe: opened ${kind} DevTools${detail}${where}`);
|
|
306
|
+
if (v.target?.url) console.log(`mme-recipe: target ${v.target.url}`);
|
|
307
|
+
if (v.configureHint) console.log(`mme-recipe: chrome://inspect target ${v.configureHint}`);
|
|
308
|
+
if (v.hint) console.log(`mme-recipe: ${v.hint}`);
|
|
309
|
+
process.exit(0);
|
|
310
|
+
}
|
|
311
|
+
console.error(`mme-recipe: ${v.error || 'failed to open debugger'}`);
|
|
312
|
+
if (v.hint) console.error(`mme-recipe: ${v.hint}`);
|
|
313
|
+
process.exit(1);
|
|
314
|
+
} catch (error) {
|
|
315
|
+
console.error(`mme-recipe: ${error.message}`);
|
|
316
|
+
process.exit(1);
|
|
317
|
+
}
|
|
318
|
+
NODE
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
open_debug_ui() {
|
|
322
|
+
init_context
|
|
323
|
+
need_cdp
|
|
324
|
+
local target_kind="page" want_json=false
|
|
325
|
+
while [ "$#" -gt 0 ]; do
|
|
326
|
+
case "$1" in
|
|
327
|
+
--json) want_json=true; shift ;;
|
|
328
|
+
--target)
|
|
329
|
+
target_kind="$2"
|
|
330
|
+
shift 2
|
|
331
|
+
;;
|
|
332
|
+
worker|service-worker|background)
|
|
333
|
+
target_kind="worker"
|
|
334
|
+
shift
|
|
335
|
+
;;
|
|
336
|
+
page|ui)
|
|
337
|
+
target_kind="page"
|
|
338
|
+
shift
|
|
339
|
+
;;
|
|
340
|
+
-h|--help)
|
|
341
|
+
echo "Usage: mme-recipe debug [--json] [worker|page]"
|
|
342
|
+
return 0
|
|
343
|
+
;;
|
|
344
|
+
*) echo "mme-recipe debug: unknown arg: $1" >&2; return 2 ;;
|
|
345
|
+
esac
|
|
346
|
+
done
|
|
347
|
+
local payload exit_code=0
|
|
348
|
+
payload="$(node "$open_debug_script" --adapter extension --cdp-port "$CDP_PORT" --target "$target_kind" --json 2>/dev/null)" || exit_code=$?
|
|
349
|
+
if [ "$exit_code" -eq 0 ]; then
|
|
350
|
+
report_open_debug "$payload" "$want_json"
|
|
351
|
+
return 0
|
|
352
|
+
fi
|
|
353
|
+
report_open_debug "$payload" "$want_json" || return 1
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
do_completion() {
|
|
357
|
+
local install_script="$runner_root/scripts/install-completions.sh"
|
|
358
|
+
bash "$install_script" "${1:-install}" "${@:2}"
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
do_sync() {
|
|
362
|
+
init_context
|
|
363
|
+
local start=$SECONDS farmslot_slot="" seed_script="" fixture_ready=false summary_file=""
|
|
364
|
+
RECIPE_CLI_PREFIX=mme-recipe
|
|
365
|
+
recipe_progress sync start "refreshing harness + fixture context"
|
|
366
|
+
farmslot_slot="$(json_field "$runtime_json" slotId 2>/dev/null || true)"
|
|
367
|
+
[ -n "$farmslot_slot" ] || { [ -n "${slot:-}" ] && farmslot_slot="local-extension-${slot}"; }
|
|
368
|
+
seed_script="$root/$harness_root/extension/scripts/seed-fixture.sh"
|
|
369
|
+
log "sync: refreshing harness scripts"
|
|
370
|
+
if ! RECIPE_RUNTIME_DIR="$runtime_dir" "$install_script" --target "$root"; then
|
|
371
|
+
recipe_done sync fail "harness inject failed" $((SECONDS - start))
|
|
372
|
+
return 1
|
|
373
|
+
fi
|
|
374
|
+
log "sync: canonicalizing wallet fixture"
|
|
375
|
+
if recipe_sync_wallet_fixture "$root" "$runtime_dir" "${CDP_PORT:-}" "$seed_script" "$farmslot_slot"; then
|
|
376
|
+
fixture_ready=true
|
|
377
|
+
log "sync: fixture ready at ${runtime_dir}/wallet-fixture.json"
|
|
378
|
+
else
|
|
379
|
+
log "sync: fixture missing - create ${runtime_dir}/wallet-fixture.json or set RECIPE_WALLET_FIXTURE"
|
|
380
|
+
fi
|
|
381
|
+
if recipe_check_harness_stale extension "$root" "$harness_root" "$runner_root"; then
|
|
382
|
+
log "sync: warning - harness still reports stale after inject"
|
|
383
|
+
else
|
|
384
|
+
log "sync: harness up to date"
|
|
385
|
+
fi
|
|
386
|
+
log "sync: writing runtime context (cdp=${CDP_PORT:-?} watcher=${WATCHER_PORT:-?})"
|
|
387
|
+
write_extension_runtime_context
|
|
388
|
+
summary_file="$root/$runtime_dir/sync-summary.json"
|
|
389
|
+
mkdir -p "$(dirname "$summary_file")"
|
|
390
|
+
node - "$summary_file" "$fixture_ready" <<'NODE'
|
|
391
|
+
const fs = require('fs');
|
|
392
|
+
const [file, fixtureReady] = process.argv.slice(2);
|
|
393
|
+
fs.writeFileSync(file, `${JSON.stringify({
|
|
394
|
+
schemaVersion: 1,
|
|
395
|
+
adapter: 'extension',
|
|
396
|
+
status: 'pass',
|
|
397
|
+
harnessRefreshed: true,
|
|
398
|
+
fixtureReady: fixtureReady === 'true',
|
|
399
|
+
generatedAt: new Date().toISOString(),
|
|
400
|
+
}, null, 2)}\n`);
|
|
401
|
+
NODE
|
|
402
|
+
if json_requested "$@"; then
|
|
403
|
+
cat "$summary_file"
|
|
404
|
+
fi
|
|
405
|
+
if $fixture_ready; then
|
|
406
|
+
recipe_done sync pass "harness refreshed; fixture ready" $((SECONDS - start))
|
|
407
|
+
else
|
|
408
|
+
recipe_done sync pass "harness refreshed; fixture missing" $((SECONDS - start))
|
|
409
|
+
fi
|
|
410
|
+
return 0
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
prepare_extension() {
|
|
414
|
+
local target="" cdp_port="${CDP_PORT:-${RECIPE_CDP_PORT:-}}" runtime_dir="$(recipe_runtime_dir)" validate=false
|
|
415
|
+
while [ "$#" -gt 0 ]; do
|
|
416
|
+
case "$1" in
|
|
417
|
+
--target) target="$2"; shift 2 ;;
|
|
418
|
+
--cdp-port) cdp_port="$2"; validate=true; shift 2 ;;
|
|
419
|
+
--runtime-dir) runtime_dir="$2"; shift 2 ;;
|
|
420
|
+
--validate) validate=true; shift ;;
|
|
421
|
+
-h|--help)
|
|
422
|
+
echo "Usage: mme-recipe prepare [--target <repo>] [--cdp-port <port>] [--runtime-dir <dir>] [--validate]"
|
|
423
|
+
return 0
|
|
424
|
+
;;
|
|
425
|
+
*) echo "mme-recipe prepare: unknown arg: $1" >&2; return 2 ;;
|
|
426
|
+
esac
|
|
427
|
+
done
|
|
428
|
+
if [ -n "$target" ]; then
|
|
429
|
+
target="$(cd "$target" && pwd -P)"
|
|
430
|
+
(cd "$target" && RECIPE_RUNTIME_DIR="$runtime_dir" "$install_script" --target "$target")
|
|
431
|
+
else
|
|
432
|
+
init_context
|
|
433
|
+
target="$root"
|
|
434
|
+
RECIPE_RUNTIME_DIR="$runtime_dir" "$install_script" --target "$target"
|
|
435
|
+
fi
|
|
436
|
+
if $validate; then
|
|
437
|
+
[ -n "$cdp_port" ] || { echo "mme-recipe prepare: --validate requires --cdp-port or CDP_PORT" >&2; return 2; }
|
|
438
|
+
run_json_pretty "$runner" runtime-health --adapter extension --target "$target" --cdp-port "$cdp_port"
|
|
439
|
+
fi
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
harness_helper() {
|
|
443
|
+
local field="$1" fallback="$2" rel script manifest
|
|
444
|
+
manifest="$root/$harness_root/extension/manifest.json"
|
|
445
|
+
rel="$(json_field "$manifest" "runtimeHelpers.$field" || true)"
|
|
446
|
+
if [ -n "$rel" ]; then
|
|
447
|
+
case "$rel" in
|
|
448
|
+
/*) script="$rel" ;;
|
|
449
|
+
*) script="$root/$rel" ;;
|
|
450
|
+
esac
|
|
451
|
+
else
|
|
452
|
+
script="$root/$fallback"
|
|
453
|
+
fi
|
|
454
|
+
[ -f "$script" ] || { echo "mme-recipe: missing $field helper. Run: recipe sync" >&2; exit 1; }
|
|
455
|
+
printf '%s\n' "$script"
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
status_home() {
|
|
459
|
+
init_context
|
|
460
|
+
if ! json_requested "$@"; then
|
|
461
|
+
recipe_instant_hint "checking extension runtime..."
|
|
462
|
+
recipe_progress status start "probing CDP, watcher, and readiness"
|
|
463
|
+
fi
|
|
464
|
+
local rs_file="$root/$runtime_dir/runtime-status.json"
|
|
465
|
+
local dec_file="$root/$runtime_dir/.recipe-decision.json"
|
|
466
|
+
local slot_id harness_stale=false
|
|
467
|
+
mkdir -p "$root/$runtime_dir"
|
|
468
|
+
recipe_warn_harness_stale extension "$root" "$harness_root" "$runner_root" && harness_stale=false || harness_stale=true
|
|
469
|
+
runtime_status --json > "$rs_file"
|
|
470
|
+
"$runner" runtime-decision --adapter extension --target "$root" \
|
|
471
|
+
${CDP_PORT:+--cdp-port "$CDP_PORT"} \
|
|
472
|
+
${WATCHER_PORT:+--watch-log "$root/$runtime_dir/webpack.log"} \
|
|
473
|
+
--json > "$dec_file" 2>/dev/null || : > "$dec_file"
|
|
474
|
+
slot_id="$(json_field "$runtime_json" slotId || true)"
|
|
475
|
+
local home_args=(
|
|
476
|
+
--adapter extension --target "$root" --runtime-dir "$runtime_dir"
|
|
477
|
+
--cdp-port "${CDP_PORT:-}" --watcher-port "${WATCHER_PORT:-}"
|
|
478
|
+
--runtime-status-file "$rs_file" --decision-file "$dec_file"
|
|
479
|
+
)
|
|
480
|
+
[ "$harness_stale" = true ] && home_args+=(--harness-stale)
|
|
481
|
+
if json_requested "$@"; then
|
|
482
|
+
node "$(recipe_home_script)" "${home_args[@]}" --json
|
|
483
|
+
else
|
|
484
|
+
node "$(recipe_home_script)" "${home_args[@]}"
|
|
485
|
+
fi
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
show_ports() {
|
|
489
|
+
init_context
|
|
490
|
+
local slot_id
|
|
491
|
+
slot_id="$(json_field "$runtime_json" slotId || true)"
|
|
492
|
+
local args=(
|
|
493
|
+
--mode ports --adapter extension --target "$root" --runtime-dir "$runtime_dir"
|
|
494
|
+
--cdp-port "${CDP_PORT:-}" --watcher-port "${WATCHER_PORT:-}" --slot-id "${slot_id:-}"
|
|
495
|
+
)
|
|
496
|
+
if json_requested "$@"; then
|
|
497
|
+
node "$(recipe_home_script)" "${args[@]}" --json
|
|
498
|
+
else
|
|
499
|
+
node "$(recipe_home_script)" "${args[@]}"
|
|
500
|
+
fi
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
status() { status_home "$@"; }
|
|
504
|
+
decision() { init_context; run_json_pretty "$runner" runtime-decision --adapter extension --target "$root" ${CDP_PORT:+--cdp-port "$CDP_PORT"} ${WATCHER_PORT:+--watch-log "$root/$runtime_dir/webpack.log"} "$@"; }
|
|
505
|
+
ready() { init_context; need_cdp; exec "$runner" ensure-ready --adapter extension --target "$root" --cdp-port "$CDP_PORT" "$@"; }
|
|
506
|
+
|
|
507
|
+
decision_field() {
|
|
508
|
+
node - "$1" "$2" <<'NODE'
|
|
509
|
+
const [text, field] = process.argv.slice(2);
|
|
510
|
+
try {
|
|
511
|
+
const value = JSON.parse(text);
|
|
512
|
+
const result = field.split('.').reduce((node, key) => node?.[key], value);
|
|
513
|
+
if (result !== undefined && result !== null) process.stdout.write(String(result));
|
|
514
|
+
} catch {
|
|
515
|
+
process.exit(1);
|
|
516
|
+
}
|
|
517
|
+
NODE
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
runtime_status() {
|
|
521
|
+
local target="" cdp_port="" runtime_dir_arg=""
|
|
522
|
+
while [ "$#" -gt 0 ]; do
|
|
523
|
+
case "$1" in
|
|
524
|
+
--target) target="$2"; shift 2 ;;
|
|
525
|
+
--cdp-port) cdp_port="$2"; shift 2 ;;
|
|
526
|
+
--runtime-dir) runtime_dir_arg="$2"; shift 2 ;;
|
|
527
|
+
--json) shift ;;
|
|
528
|
+
-h|--help)
|
|
529
|
+
echo "Usage: mme-recipe runtime-status [--target <repo>] [--cdp-port <port>] [--runtime-dir <dir>] [--json]"
|
|
530
|
+
return 0
|
|
531
|
+
;;
|
|
532
|
+
*) echo "mme-recipe runtime-status: unknown arg: $1" >&2; return 2 ;;
|
|
533
|
+
esac
|
|
534
|
+
done
|
|
535
|
+
[ -n "$target" ] && cd "$target"
|
|
536
|
+
[ -n "$runtime_dir_arg" ] && export RECIPE_RUNTIME_DIR="$runtime_dir_arg"
|
|
537
|
+
[ -n "$cdp_port" ] && export CDP_PORT="$cdp_port" RECIPE_CDP_PORT="$cdp_port"
|
|
538
|
+
init_context
|
|
539
|
+
local runtime_abs="$root/$runtime_dir"
|
|
540
|
+
local status_file="$runtime_abs/runtime-status.json"
|
|
541
|
+
mkdir -p "$runtime_abs"
|
|
542
|
+
local decision_json health_json health_exit=0
|
|
543
|
+
decision_json="$("$runner" runtime-decision --adapter extension --target "$root" ${CDP_PORT:+--cdp-port "$CDP_PORT"} ${WATCHER_PORT:+--watch-log "$root/$runtime_dir/webpack.log"} --json)"
|
|
544
|
+
if [ -n "${CDP_PORT:-}" ]; then
|
|
545
|
+
health_json="$("$runner" runtime-health --adapter extension --target "$root" --cdp-port "$CDP_PORT" --json 2>/dev/null)" || health_exit=$?
|
|
546
|
+
else
|
|
547
|
+
health_json=""
|
|
548
|
+
fi
|
|
549
|
+
node - "$root" "$runtime_dir" "${CDP_PORT:-}" "${WATCHER_PORT:-}" "$status_file" "$decision_json" "$health_json" "$health_exit" <<'NODE'
|
|
550
|
+
const fs = require('fs');
|
|
551
|
+
const path = require('path');
|
|
552
|
+
const [target, runtimeDir, cdpPort, watcherPort, statusFile, decisionText, healthText, healthExit] = process.argv.slice(2);
|
|
553
|
+
const parse = (label, text) => {
|
|
554
|
+
if (!text) return { value: null, error: null };
|
|
555
|
+
try {
|
|
556
|
+
return { value: JSON.parse(text), error: null };
|
|
557
|
+
} catch (error) {
|
|
558
|
+
return { value: null, error: `${label} JSON parse failed: ${error.message}` };
|
|
559
|
+
}
|
|
560
|
+
};
|
|
561
|
+
const decisionResult = parse('runtime-decision', decisionText);
|
|
562
|
+
const healthResult = parse('runtime-health', healthText);
|
|
563
|
+
const decision = decisionResult.value;
|
|
564
|
+
const health = healthResult.value;
|
|
565
|
+
const ready = decision?.decision === 'ready' && (!health || health.status === 'PASS');
|
|
566
|
+
function fixtureStatus() {
|
|
567
|
+
const candidates = [
|
|
568
|
+
process.env.RECIPE_WALLET_FIXTURE,
|
|
569
|
+
path.join(target, runtimeDir, 'wallet-fixture.json'),
|
|
570
|
+
path.join(target, 'temp/runtime/wallet-fixture.json'),
|
|
571
|
+
cdpPort ? path.join(target, `temp/.recipe-validation-${cdpPort}/wallet-fixture.json`) : null,
|
|
572
|
+
path.join(target, 'temp/.agent-validation/wallet-fixture.json'),
|
|
573
|
+
].filter(Boolean);
|
|
574
|
+
const fixturePath = candidates.find((candidate) => fs.existsSync(candidate)) || null;
|
|
575
|
+
return {
|
|
576
|
+
present: Boolean(fixturePath),
|
|
577
|
+
path: fixturePath,
|
|
578
|
+
candidates,
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
const status = {
|
|
582
|
+
schemaVersion: 1,
|
|
583
|
+
adapter: 'extension',
|
|
584
|
+
target,
|
|
585
|
+
runtimeDir,
|
|
586
|
+
updatedAt: new Date().toISOString(),
|
|
587
|
+
ready,
|
|
588
|
+
reason: ready ? 'ready' : decision?.reasonCode || health?.findings?.[0] || 'not-ready',
|
|
589
|
+
cdp: { port: cdpPort ? Number(cdpPort) : null, healthExit: Number(healthExit || 0), health },
|
|
590
|
+
watcher: { port: watcherPort ? Number(watcherPort) : null },
|
|
591
|
+
fixture: fixtureStatus(),
|
|
592
|
+
decision,
|
|
593
|
+
errors: [decisionResult.error, healthResult.error].filter(Boolean),
|
|
594
|
+
};
|
|
595
|
+
fs.writeFileSync(statusFile, `${JSON.stringify(status, null, 2)}\n`);
|
|
596
|
+
console.log(JSON.stringify(status, null, 2));
|
|
597
|
+
NODE
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
do_reopen() {
|
|
601
|
+
init_context; need_cdp
|
|
602
|
+
local pids launch_args start=$SECONDS exit_code=0 rebuild_log watch_pid=""
|
|
603
|
+
recipe_progress rebuild start "preparing clean runtime rebuild"
|
|
604
|
+
pids="$(watcher_pids || true)"
|
|
605
|
+
if [ -n "$pids" ]; then
|
|
606
|
+
log "stopping existing watcher before rebuild ($(pids_inline "$pids"))"
|
|
607
|
+
printf '%s\n' "$pids" | xargs kill 2>/dev/null || true
|
|
608
|
+
sleep 2
|
|
609
|
+
pids="$(watcher_pids || true)"
|
|
610
|
+
if [ -n "$pids" ]; then
|
|
611
|
+
log "force stopping existing watcher ($(pids_inline "$pids"))"
|
|
612
|
+
printf '%s\n' "$pids" | xargs kill -9 2>/dev/null || true
|
|
613
|
+
fi
|
|
614
|
+
fi
|
|
615
|
+
rm -f "$root/$runtime_dir/webpack.pid" "$root/$runtime_dir/recipe-harness-webpack.pid"
|
|
616
|
+
rebuild_log="$root/$runtime_dir/rebuild.log"
|
|
617
|
+
mkdir -p "$(dirname "$rebuild_log")"
|
|
618
|
+
: > "$rebuild_log"
|
|
619
|
+
log "rebuild cdp=$CDP_PORT watcher=${WATCHER_PORT:-?}"
|
|
620
|
+
log "phases: (1) clean webpack build (2) dist snapshot (3) browser+fixture (4) live verify"
|
|
621
|
+
log "typical duration 2-8 min; compact events below (full log: ${runtime_dir}/rebuild.log, recipe logs --full)"
|
|
622
|
+
recipe_progress rebuild webpack-compile "clean webpack build starting"
|
|
623
|
+
if [ "$(recipe_log_ui_mode)" != "quiet" ] && ! full_logs_requested "$@"; then
|
|
624
|
+
recipe_watch_rebuild_log "$rebuild_log" &
|
|
625
|
+
watch_pid=$!
|
|
626
|
+
fi
|
|
627
|
+
launch_args=(runtime-launch --adapter extension --target "$root" --cdp-port "$CDP_PORT" --start-watch)
|
|
628
|
+
if json_requested "$@"; then
|
|
629
|
+
env RECIPE_LOG_TUI_PARENT=1 "$runner" "${launch_args[@]}" --json "$@" 2>&1 | tee "$rebuild_log" | pipe_pretty || exit_code=$?
|
|
630
|
+
else
|
|
631
|
+
env RECIPE_LOG_TUI_PARENT=1 "$runner" "${launch_args[@]}" "$@" 2>&1 | tee "$rebuild_log" || exit_code=$?
|
|
632
|
+
fi
|
|
633
|
+
[ -n "$watch_pid" ] && kill "$watch_pid" 2>/dev/null && wait "$watch_pid" 2>/dev/null || true
|
|
634
|
+
if [ "$exit_code" -eq 0 ]; then
|
|
635
|
+
# The console viewer is created by launch.sh inside runtime-launch; no
|
|
636
|
+
# second create here (avoids killing/truncating the just-opened tab).
|
|
637
|
+
recipe_done rebuild pass "runtime rebuild complete" $((SECONDS - start))
|
|
638
|
+
else
|
|
639
|
+
recipe_done rebuild fail "runtime rebuild failed" $((SECONDS - start))
|
|
640
|
+
node "$(recipe_log_tui_script)" events --log "$rebuild_log" --events "$(recipe_log_events_limit)" >&2 || true
|
|
641
|
+
recipe_failure_playbook rebuild "runtime-launch did not pass" \
|
|
642
|
+
--prefix mme-recipe \
|
|
643
|
+
--log "${runtime_dir}/rebuild.log" \
|
|
644
|
+
--artifact "temp/recipe/harness/extension/runtime-launch/"
|
|
645
|
+
fi
|
|
646
|
+
return "$exit_code"
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
# Live extension-console viewer tab: tail the running extension's service worker
|
|
650
|
+
# + UI-page console in a tmux window, mirroring the webpack tab. Guarded so a
|
|
651
|
+
# missing tmux/session/CDP is a silent no-op (never affects the bringup result).
|
|
652
|
+
start_console_viewer_window() {
|
|
653
|
+
local cdp_port="${1:-}" session window console_tail log_file hr
|
|
654
|
+
# Numeric-validate before interpolating into the tmux shell command.
|
|
655
|
+
case "$cdp_port" in ''|*[!0-9]*) return 0 ;; esac
|
|
656
|
+
command -v tmux >/dev/null 2>&1 || return 0
|
|
657
|
+
hr="$(harness_root 2>/dev/null || true)"
|
|
658
|
+
[ -n "$hr" ] || return 0
|
|
659
|
+
console_tail="$root/$hr/extension/scripts/console-tail.mjs"
|
|
660
|
+
[ -f "$console_tail" ] || return 0
|
|
661
|
+
session="${RECIPE_TMUX_SESSION:-}"
|
|
662
|
+
if [ -z "$session" ] && [ -n "${slot:-}" ]; then session="mme-$slot"; fi
|
|
663
|
+
[ -n "$session" ] || return 0
|
|
664
|
+
tmux has-session -t "$session" 2>/dev/null || return 0
|
|
665
|
+
window="console-${cdp_port}"
|
|
666
|
+
log_file="$root/$runtime_dir/extension-console.log"
|
|
667
|
+
mkdir -p "$(dirname "$log_file")"
|
|
668
|
+
: > "$log_file"
|
|
669
|
+
tmux kill-window -t "${session}:${window}" >/dev/null 2>&1 || true
|
|
670
|
+
tmux new-window -d -t "$session" -n "$window" "exec node $(printf '%q' "$console_tail") --cdp-port ${cdp_port} --log $(printf '%q' "$log_file")"
|
|
671
|
+
log "extension console viewer in tmux window ${session}:${window}"
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
# Close the webpack `tail -F` viewer window paired with the watcher do_stop kills.
|
|
675
|
+
# Guarded no-op when there is no tmux/session/port. The console viewer is NOT
|
|
676
|
+
# closed here: it tails the running browser, which `recipe stop` leaves up.
|
|
677
|
+
close_webpack_viewer_window() {
|
|
678
|
+
command -v tmux >/dev/null 2>&1 || return 0
|
|
679
|
+
local session="${RECIPE_TMUX_SESSION:-}"
|
|
680
|
+
if [ -z "$session" ] && [ -n "${slot:-}" ]; then session="mme-$slot"; fi
|
|
681
|
+
[ -n "$session" ] || return 0
|
|
682
|
+
# Match the open-side name (do_watch/start-watch use the same :-default fallback).
|
|
683
|
+
tmux kill-window -t "${session}:webpack-${WATCHER_PORT:-default}" >/dev/null 2>&1 || true
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
do_up() {
|
|
687
|
+
init_context
|
|
688
|
+
local start=$SECONDS report action exit_code=0
|
|
689
|
+
recipe_progress up start "evaluating extension runtime"
|
|
690
|
+
report="$("$runner" runtime-decision --adapter extension --target "$root" \
|
|
691
|
+
${CDP_PORT:+--cdp-port "$CDP_PORT"} \
|
|
692
|
+
${WATCHER_PORT:+--watch-log "$root/$runtime_dir/webpack.log"} --json)"
|
|
693
|
+
action="$(decision_field "$report" decision || true)"
|
|
694
|
+
if dry_run_requested "$@"; then
|
|
695
|
+
if json_requested "$@"; then
|
|
696
|
+
printf '%s\n' "$report"
|
|
697
|
+
else
|
|
698
|
+
printf '%s\n' "$report" | pipe_pretty
|
|
699
|
+
fi
|
|
700
|
+
case "$action" in
|
|
701
|
+
ready) log "dry-run: would run ensure-ready (seconds)" ;;
|
|
702
|
+
relaunch|build) log "dry-run: would run recipe rebuild (2-8 min)" ;;
|
|
703
|
+
install) log "dry-run: would run yarn install --immutable, then re-check" ;;
|
|
704
|
+
blocked|unknown|'') log "dry-run: blocked — inspect recipe status" ;;
|
|
705
|
+
*) log "dry-run: unsupported decision ($action)" ;;
|
|
706
|
+
esac
|
|
707
|
+
return 0
|
|
708
|
+
fi
|
|
709
|
+
if json_requested "$@"; then
|
|
710
|
+
printf '%s\n' "$report"
|
|
711
|
+
else
|
|
712
|
+
printf '%s\n' "$report" | pipe_pretty
|
|
713
|
+
fi
|
|
714
|
+
case "$action" in
|
|
715
|
+
ready)
|
|
716
|
+
if [ -n "${CDP_PORT:-}" ]; then
|
|
717
|
+
recipe_progress up ready "ensuring healthy extension tab"
|
|
718
|
+
if json_requested "$@"; then
|
|
719
|
+
"$runner" ensure-ready --adapter extension --target "$root" --cdp-port "$CDP_PORT" --json || exit_code=$?
|
|
720
|
+
else
|
|
721
|
+
"$runner" ensure-ready --adapter extension --target "$root" --cdp-port "$CDP_PORT" || exit_code=$?
|
|
722
|
+
fi
|
|
723
|
+
fi
|
|
724
|
+
if [ "$exit_code" -eq 0 ]; then
|
|
725
|
+
start_console_viewer_window "${CDP_PORT:-}"
|
|
726
|
+
recipe_done up pass "runtime ready" $((SECONDS - start))
|
|
727
|
+
else
|
|
728
|
+
recipe_done up fail "ensure-ready failed" $((SECONDS - start))
|
|
729
|
+
recipe_failure_playbook up "ensure-ready failed" --prefix mme-recipe --log "${runtime_dir}/webpack.log"
|
|
730
|
+
fi
|
|
731
|
+
return "$exit_code"
|
|
732
|
+
;;
|
|
733
|
+
relaunch|build)
|
|
734
|
+
recipe_progress up "$action" "running rebuild path"
|
|
735
|
+
do_reopen "$@" || exit_code=$?
|
|
736
|
+
if [ "$exit_code" -eq 0 ]; then
|
|
737
|
+
recipe_done up pass "rebuild finished" $((SECONDS - start))
|
|
738
|
+
fi
|
|
739
|
+
return "$exit_code"
|
|
740
|
+
;;
|
|
741
|
+
install)
|
|
742
|
+
if [ "${RECIPE_UP_INSTALL_ATTEMPTED:-0}" = "1" ]; then
|
|
743
|
+
local reason_code=""
|
|
744
|
+
report="$("$runner" runtime-decision --adapter extension --target "$root" \
|
|
745
|
+
${CDP_PORT:+--cdp-port "$CDP_PORT"} \
|
|
746
|
+
${WATCHER_PORT:+--watch-log "$root/$runtime_dir/webpack.log"} --json)"
|
|
747
|
+
reason_code="$(decision_field "$report" reasonCode || true)"
|
|
748
|
+
recipe_done up fail "install did not resolve runtime (${reason_code:-install})" $((SECONDS - start))
|
|
749
|
+
if json_requested "$@"; then
|
|
750
|
+
printf '%s\n' "$report"
|
|
751
|
+
else
|
|
752
|
+
printf '%s\n' "$report" | pipe_pretty
|
|
753
|
+
fi
|
|
754
|
+
recipe_failure_playbook up "yarn install completed but runtime-decision is still install" \
|
|
755
|
+
--prefix mme-recipe
|
|
756
|
+
return 2
|
|
757
|
+
fi
|
|
758
|
+
recipe_progress up install "installing dependencies"
|
|
759
|
+
export RECIPE_UP_INSTALL_ATTEMPTED=1
|
|
760
|
+
if ! (cd "$root" && yarn install --immutable); then
|
|
761
|
+
recipe_done up fail "yarn install failed" $((SECONDS - start))
|
|
762
|
+
return 1
|
|
763
|
+
fi
|
|
764
|
+
"$runner" runtime-decision --adapter extension --target "$root" \
|
|
765
|
+
${CDP_PORT:+--cdp-port "$CDP_PORT"} \
|
|
766
|
+
${WATCHER_PORT:+--watch-log "$root/$runtime_dir/webpack.log"} \
|
|
767
|
+
--record-baseline --json >/dev/null
|
|
768
|
+
RECIPE_UP_INSTALL_ATTEMPTED=1 do_up "$@"
|
|
769
|
+
;;
|
|
770
|
+
blocked|unknown|'')
|
|
771
|
+
recipe_done up fail "blocked (${action:-unknown})" $((SECONDS - start))
|
|
772
|
+
recipe_failure_playbook up "blocked (${action:-unknown})" --prefix mme-recipe
|
|
773
|
+
return 2
|
|
774
|
+
;;
|
|
775
|
+
*)
|
|
776
|
+
recipe_done up fail "unsupported decision: $action" $((SECONDS - start))
|
|
777
|
+
recipe_failure_playbook up "unsupported decision: $action" --prefix mme-recipe
|
|
778
|
+
return 2
|
|
779
|
+
;;
|
|
780
|
+
esac
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
do_update() {
|
|
784
|
+
init_context
|
|
785
|
+
local report action
|
|
786
|
+
report="$("$runner" runtime-decision --adapter extension --target "$root" ${CDP_PORT:+--cdp-port "$CDP_PORT"} ${WATCHER_PORT:+--watch-log "$root/$runtime_dir/webpack.log"} --json)"
|
|
787
|
+
printf '%s\n' "$report" | pipe_pretty
|
|
788
|
+
action="$(decision_field "$report" decision || true)"
|
|
789
|
+
case "$action" in
|
|
790
|
+
ready)
|
|
791
|
+
if [ -n "${CDP_PORT:-}" ]; then
|
|
792
|
+
"$runner" ensure-ready --adapter extension --target "$root" --cdp-port "$CDP_PORT" --json | pipe_pretty
|
|
793
|
+
else
|
|
794
|
+
log "runtime decision is ready; CDP_PORT is not set, so no browser action was needed"
|
|
795
|
+
fi
|
|
796
|
+
;;
|
|
797
|
+
relaunch)
|
|
798
|
+
do_reopen "$@"
|
|
799
|
+
;;
|
|
800
|
+
build)
|
|
801
|
+
do_reopen "$@"
|
|
802
|
+
;;
|
|
803
|
+
install)
|
|
804
|
+
log "installing dependencies before runtime update"
|
|
805
|
+
(cd "$root" && yarn install --immutable)
|
|
806
|
+
do_update "$@"
|
|
807
|
+
;;
|
|
808
|
+
blocked|unknown|'')
|
|
809
|
+
echo "mme-recipe: runtime decision is ${action:-unknown}; inspect with: recipe decision --json" >&2
|
|
810
|
+
return 2
|
|
811
|
+
;;
|
|
812
|
+
*)
|
|
813
|
+
echo "mme-recipe: unsupported runtime decision: $action" >&2
|
|
814
|
+
return 2
|
|
815
|
+
;;
|
|
816
|
+
esac
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
watcher_pids() {
|
|
820
|
+
local pids pid_file pid current_pid command
|
|
821
|
+
pids="$(
|
|
822
|
+
ps -axo pid=,command= | while read -r current_pid command; do
|
|
823
|
+
[ -n "${current_pid:-}" ] || continue
|
|
824
|
+
case "$command" in
|
|
825
|
+
*"yarn start"*|*"webpack --watch"*|*"development/webpack/launch.ts --watch"*) ;;
|
|
826
|
+
*) continue ;;
|
|
827
|
+
esac
|
|
828
|
+
if printf '%s' "$command" | grep -Fq "$root"; then
|
|
829
|
+
printf '%s\n' "$current_pid"
|
|
830
|
+
continue
|
|
831
|
+
fi
|
|
832
|
+
if command -v lsof >/dev/null 2>&1; then
|
|
833
|
+
if lsof -a -p "$current_pid" -d cwd -Fn 2>/dev/null | sed -n 's/^n//p' | grep -Fxq "$root"; then
|
|
834
|
+
printf '%s\n' "$current_pid"
|
|
835
|
+
fi
|
|
836
|
+
fi
|
|
837
|
+
done
|
|
838
|
+
true
|
|
839
|
+
)"
|
|
840
|
+
pid_file="$root/$runtime_dir/webpack.pid"
|
|
841
|
+
pid="$(cat "$pid_file" 2>/dev/null || true)"
|
|
842
|
+
if [ -n "$pid" ] && kill -0 "$pid" >/dev/null 2>&1; then
|
|
843
|
+
if ! printf '%s\n' "$pids" | grep -qx "$pid"; then
|
|
844
|
+
pids="$(printf '%s\n%s\n' "$pids" "$pid" | sed '/^$/d')"
|
|
845
|
+
fi
|
|
846
|
+
fi
|
|
847
|
+
printf '%s\n' "$pids" | sed '/^$/d'
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
do_watch() {
|
|
851
|
+
init_context
|
|
852
|
+
local pids log_file pid_file session window log_q compiled elapsed
|
|
853
|
+
pids="$(watcher_pids || true)"
|
|
854
|
+
if [ -n "$pids" ]; then
|
|
855
|
+
log "watcher already running ($(pids_inline "$pids"))"
|
|
856
|
+
return 0
|
|
857
|
+
fi
|
|
858
|
+
log_file="$root/$runtime_dir/webpack.log"
|
|
859
|
+
pid_file="$root/$runtime_dir/webpack.pid"
|
|
860
|
+
mkdir -p "$(dirname "$log_file")"
|
|
861
|
+
rm -f "$pid_file"
|
|
862
|
+
: > "$log_file"
|
|
863
|
+
rm -rf "$root/node_modules/.cache/webpack"
|
|
864
|
+
log "starting watcher port=${WATCHER_PORT:-default} log=${log_file#$root/}"
|
|
865
|
+
ensure_extension_node
|
|
866
|
+
local path_prefix="${REPO_NODE_PATH_PREFIX:-}"
|
|
867
|
+
# Launch the watcher detached writing to the log; the tmux window is a read-only tail viewer.
|
|
868
|
+
(cd "$root" && nohup bash -lc "exec env -u BUNDLED_DEBUGPY_PATH ${path_prefix:+PATH=\"${path_prefix}\$PATH\"} ${WATCHER_PORT:+PORT=\"$WATCHER_PORT\"} yarn start" > "$log_file" 2>&1 & echo $! > "$pid_file")
|
|
869
|
+
if command -v tmux >/dev/null 2>&1; then
|
|
870
|
+
session="${RECIPE_TMUX_SESSION:-}"
|
|
871
|
+
if [ -z "$session" ] && [ -n "${slot:-}" ]; then session="mme-$slot"; fi
|
|
872
|
+
if [ -n "$session" ] && tmux has-session -t "$session" 2>/dev/null; then
|
|
873
|
+
window="webpack-${WATCHER_PORT:-default}"
|
|
874
|
+
log_q="$(printf '%q' "$log_file")"
|
|
875
|
+
tmux kill-window -t "${session}:${window}" >/dev/null 2>&1 || true
|
|
876
|
+
tmux new-window -d -t "$session" -n "$window" "exec tail -n +1 -F $log_q"
|
|
877
|
+
fi
|
|
878
|
+
fi
|
|
879
|
+
compiled=false
|
|
880
|
+
local tail_pid=""
|
|
881
|
+
if full_logs_requested "$@"; then
|
|
882
|
+
tail -f "$log_file" &
|
|
883
|
+
tail_pid=$!
|
|
884
|
+
elif [ "$(recipe_log_ui_mode)" != "quiet" ]; then
|
|
885
|
+
node "$(recipe_log_tui_script)" watch \
|
|
886
|
+
--log "$log_file" \
|
|
887
|
+
--events "$(recipe_log_events_limit)" \
|
|
888
|
+
--mode "$(recipe_log_ui_mode)" \
|
|
889
|
+
--label "webpack watcher" \
|
|
890
|
+
--success-pattern 'compiled successfully|compiled with [0-9]+ warning|MetaMask .* compiled|Bundle end:' &
|
|
891
|
+
tail_pid=$!
|
|
892
|
+
fi
|
|
893
|
+
for elapsed in $(seq 1 240); do
|
|
894
|
+
pids="$(watcher_pids || true)"
|
|
895
|
+
if [ -z "$pids" ]; then
|
|
896
|
+
if [ "$elapsed" -le 10 ]; then
|
|
897
|
+
sleep 2
|
|
898
|
+
continue
|
|
899
|
+
fi
|
|
900
|
+
[ -n "$tail_pid" ] && kill "$tail_pid" >/dev/null 2>&1 || true
|
|
901
|
+
wait "$tail_pid" 2>/dev/null || true
|
|
902
|
+
recipe_failure_playbook watch "watcher exited before compile completed" \
|
|
903
|
+
--prefix mme-recipe --log "${log_file#$root/}"
|
|
904
|
+
node "$(recipe_log_tui_script)" events --log "$log_file" --events 15 >&2 || true
|
|
905
|
+
rm -f "$pid_file"
|
|
906
|
+
return 1
|
|
907
|
+
fi
|
|
908
|
+
printf '%s\n' "$pids" | head -1 > "$pid_file"
|
|
909
|
+
if grep -Eq 'Module build failed|^ERROR in |compiled with [1-9][0-9]* error' "$log_file" 2>/dev/null; then
|
|
910
|
+
[ -n "$tail_pid" ] && kill "$tail_pid" >/dev/null 2>&1 || true
|
|
911
|
+
wait "$tail_pid" 2>/dev/null || true
|
|
912
|
+
recipe_failure_playbook watch "webpack build failed" \
|
|
913
|
+
--prefix mme-recipe --log "${log_file#$root/}"
|
|
914
|
+
node "$(recipe_log_tui_script)" events --log "$log_file" --events 15 >&2 || true
|
|
915
|
+
return 1
|
|
916
|
+
fi
|
|
917
|
+
if grep -Eq 'compiled successfully|compiled with [0-9]+ warning|MetaMask .* compiled|Bundle end: service worker|Bundle end:.*app-init' "$log_file" 2>/dev/null; then
|
|
918
|
+
compiled=true
|
|
919
|
+
[ -n "$tail_pid" ] && kill "$tail_pid" >/dev/null 2>&1 || true
|
|
920
|
+
wait "$tail_pid" 2>/dev/null || true
|
|
921
|
+
break
|
|
922
|
+
fi
|
|
923
|
+
sleep 2
|
|
924
|
+
done
|
|
925
|
+
if [ "$compiled" != true ]; then
|
|
926
|
+
[ -n "$tail_pid" ] && kill "$tail_pid" >/dev/null 2>&1 || true
|
|
927
|
+
wait "$tail_pid" 2>/dev/null || true
|
|
928
|
+
recipe_failure_playbook watch "compile timeout (8 min)" \
|
|
929
|
+
--prefix mme-recipe --log "${log_file#$root/}"
|
|
930
|
+
node "$(recipe_log_tui_script)" events --log "$log_file" --events 15 >&2 || true
|
|
931
|
+
return 1
|
|
932
|
+
fi
|
|
933
|
+
pids="$(watcher_pids || true)"
|
|
934
|
+
printf '%s\n' "$pids" | head -1 > "$pid_file"
|
|
935
|
+
log "watcher compiled ($(pids_inline "$pids")); use: recipe refresh"
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
pids_inline() {
|
|
939
|
+
printf '%s' "$1" | tr '\n' ' '
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
latest_source_stamp() {
|
|
943
|
+
node - "$root" <<'NODE'
|
|
944
|
+
const { execFileSync } = require('child_process');
|
|
945
|
+
const fs = require('fs');
|
|
946
|
+
const path = require('path');
|
|
947
|
+
const root = process.argv[2];
|
|
948
|
+
function lines(args) {
|
|
949
|
+
try { return execFileSync('git', ['-C', root, ...args], { encoding: 'utf8' }).split('\n').filter(Boolean); }
|
|
950
|
+
catch { return []; }
|
|
951
|
+
}
|
|
952
|
+
const files = new Set([
|
|
953
|
+
...lines(['diff', '--name-only', 'HEAD']),
|
|
954
|
+
...lines(['ls-files', '--others', '--exclude-standard']),
|
|
955
|
+
]);
|
|
956
|
+
let best = { mtime: 0, file: '' };
|
|
957
|
+
for (const rel of files) {
|
|
958
|
+
if (!/\.(tsx?|jsx?|mjs|cjs|json|scss|css)$/u.test(rel)) continue;
|
|
959
|
+
const abs = path.join(root, rel);
|
|
960
|
+
if (!fs.existsSync(abs) || !fs.statSync(abs).isFile()) continue;
|
|
961
|
+
const mtime = fs.statSync(abs).mtimeMs;
|
|
962
|
+
if (mtime > best.mtime) best = { mtime, file: rel };
|
|
963
|
+
}
|
|
964
|
+
if (best.mtime) console.log(`${Math.floor(best.mtime)}\t${best.file}`);
|
|
965
|
+
NODE
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
latest_dist_mtime_ms() {
|
|
969
|
+
node - "$root" <<'NODE'
|
|
970
|
+
const fs = require('fs');
|
|
971
|
+
const path = require('path');
|
|
972
|
+
const root = process.argv[2];
|
|
973
|
+
const dir = path.join(root, 'dist/chrome');
|
|
974
|
+
let best = 0;
|
|
975
|
+
function walk(p) {
|
|
976
|
+
if (!fs.existsSync(p)) return;
|
|
977
|
+
for (const name of fs.readdirSync(p)) {
|
|
978
|
+
const abs = path.join(p, name);
|
|
979
|
+
const st = fs.statSync(abs);
|
|
980
|
+
if (st.isDirectory()) walk(abs);
|
|
981
|
+
else if (st.isFile()) best = Math.max(best, st.mtimeMs);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
walk(dir);
|
|
985
|
+
console.log(Math.floor(best));
|
|
986
|
+
NODE
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
do_stop() {
|
|
990
|
+
init_context
|
|
991
|
+
local pids
|
|
992
|
+
pids="$(watcher_pids || true)"
|
|
993
|
+
if [ -z "$pids" ]; then
|
|
994
|
+
log "no watcher running for this checkout"
|
|
995
|
+
rm -f "$root/$runtime_dir/webpack.pid"
|
|
996
|
+
close_webpack_viewer_window
|
|
997
|
+
return 0
|
|
998
|
+
fi
|
|
999
|
+
log "stopping watcher ($(pids_inline "$pids"))"
|
|
1000
|
+
printf '%s
|
|
1001
|
+
' "$pids" | xargs kill 2>/dev/null || true
|
|
1002
|
+
sleep 2
|
|
1003
|
+
pids="$(watcher_pids || true)"
|
|
1004
|
+
if [ -n "$pids" ]; then
|
|
1005
|
+
log "force stopping watcher ($(pids_inline "$pids"))"
|
|
1006
|
+
printf '%s
|
|
1007
|
+
' "$pids" | xargs kill -9 2>/dev/null || true
|
|
1008
|
+
fi
|
|
1009
|
+
rm -f "$root/$runtime_dir/webpack.pid"
|
|
1010
|
+
close_webpack_viewer_window
|
|
1011
|
+
log "next: recipe watch | recipe rebuild"
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
tail_logs() {
|
|
1015
|
+
init_context
|
|
1016
|
+
local log_file="$root/$runtime_dir/webpack.log"
|
|
1017
|
+
[ -f "$log_file" ] || { echo "mme-recipe: webpack log missing: $runtime_dir/webpack.log. Start with: recipe watch" >&2; return 1; }
|
|
1018
|
+
if full_logs_requested "$@"; then
|
|
1019
|
+
log "full log: $runtime_dir/webpack.log (Ctrl-C to stop)"
|
|
1020
|
+
tail -f "$log_file"
|
|
1021
|
+
return 0
|
|
1022
|
+
fi
|
|
1023
|
+
log "compact tail — full stream: recipe logs --full"
|
|
1024
|
+
exec node "$(recipe_log_tui_script)" tail \
|
|
1025
|
+
--log "$log_file" \
|
|
1026
|
+
--events "$(recipe_log_events_limit)" \
|
|
1027
|
+
--follow \
|
|
1028
|
+
--mode compact
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
do_refresh_once() {
|
|
1032
|
+
init_context
|
|
1033
|
+
local s
|
|
1034
|
+
s="$(harness_helper refreshBuild "$runtime_dir/refresh-build.sh")"
|
|
1035
|
+
log "one-shot refresh watcher=${WATCHER_PORT:-?}"
|
|
1036
|
+
bash "$s" --repo "$root" ${WATCHER_PORT:+--watcher-port "$WATCHER_PORT"} "$@"
|
|
1037
|
+
if [ -n "${CDP_PORT:-}" ]; then
|
|
1038
|
+
"$runner" ensure-ready --adapter extension --target "$root" --cdp-port "$CDP_PORT" --json || true
|
|
1039
|
+
fi
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
do_build() {
|
|
1043
|
+
init_context
|
|
1044
|
+
local pids
|
|
1045
|
+
pids="$(watcher_pids || true)"
|
|
1046
|
+
if [ -n "$pids" ]; then
|
|
1047
|
+
do_refresh "$@"
|
|
1048
|
+
return $?
|
|
1049
|
+
fi
|
|
1050
|
+
log "build: no active watcher — running full rebuild path"
|
|
1051
|
+
do_reopen "$@"
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
do_refresh() {
|
|
1055
|
+
init_context
|
|
1056
|
+
local pids stamp src_mtime src_file dist_mtime timeout elapsed
|
|
1057
|
+
pids="$(watcher_pids || true)"
|
|
1058
|
+
if [ -z "$pids" ]; then
|
|
1059
|
+
recipe_failure_playbook refresh "no active watcher" --prefix mme-recipe
|
|
1060
|
+
return 2
|
|
1061
|
+
fi
|
|
1062
|
+
|
|
1063
|
+
stamp="$(latest_source_stamp || true)"
|
|
1064
|
+
if [ -z "$stamp" ]; then
|
|
1065
|
+
log "watcher active ($(pids_inline "$pids")); no changed source files"
|
|
1066
|
+
[ -n "${CDP_PORT:-}" ] && "$runner" ensure-ready --adapter extension --target "$root" --cdp-port "$CDP_PORT" --json || true
|
|
1067
|
+
return
|
|
1068
|
+
fi
|
|
1069
|
+
src_mtime="${stamp%%$'\t'*}"
|
|
1070
|
+
src_file="${stamp#*$'\t'}"
|
|
1071
|
+
timeout="${RECIPE_REFRESH_TIMEOUT:-90}"
|
|
1072
|
+
log "watcher active ($(pids_inline "$pids")); waiting for incremental build of $src_file"
|
|
1073
|
+
|
|
1074
|
+
for elapsed in $(seq 0 "$timeout"); do
|
|
1075
|
+
dist_mtime="$(latest_dist_mtime_ms)"
|
|
1076
|
+
if [ "$dist_mtime" -ge "$src_mtime" ]; then
|
|
1077
|
+
log "dist/chrome timestamp is current (${elapsed}s); checking runtime"
|
|
1078
|
+
if [ -n "${CDP_PORT:-}" ]; then
|
|
1079
|
+
local ready_json
|
|
1080
|
+
ready_json="$($runner ensure-ready --adapter extension --target "$root" --cdp-port "$CDP_PORT" --json 2>/dev/null || true)"
|
|
1081
|
+
printf '%s
|
|
1082
|
+
' "$ready_json"
|
|
1083
|
+
if printf '%s' "$ready_json" | grep -q '"ready"[[:space:]]*:[[:space:]]*true'; then
|
|
1084
|
+
log "runtime ready; run: recipe reopen only if the visible page is stale"
|
|
1085
|
+
return
|
|
1086
|
+
fi
|
|
1087
|
+
recipe_failure_playbook refresh "dist is current but runtime is unhealthy" --prefix mme-recipe
|
|
1088
|
+
return 2
|
|
1089
|
+
fi
|
|
1090
|
+
log "dist/chrome timestamp is current; run: recipe reopen"
|
|
1091
|
+
return
|
|
1092
|
+
fi
|
|
1093
|
+
if [ $elapsed -gt 0 ] && [ $((elapsed % 10)) -eq 0 ]; then
|
|
1094
|
+
log "still waiting for watcher... (${elapsed}s)"
|
|
1095
|
+
fi
|
|
1096
|
+
sleep 1
|
|
1097
|
+
done
|
|
1098
|
+
|
|
1099
|
+
recipe_failure_playbook refresh "watcher did not update dist/chrome within ${timeout}s" \
|
|
1100
|
+
--prefix mme-recipe --log "${runtime_dir}/webpack.log"
|
|
1101
|
+
return 2
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
sidepanel() {
|
|
1105
|
+
init_context; need_cdp
|
|
1106
|
+
local s
|
|
1107
|
+
s="$(harness_helper sidepanelToggle "$runtime_dir/sidepanel-toggle.sh")"
|
|
1108
|
+
bash "$s" "${1:-cycle}" --cdp-port "$CDP_PORT" --agent-dir "$root/$runtime_dir"
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
run_recipe() {
|
|
1112
|
+
init_context; need_cdp
|
|
1113
|
+
local recipe="$1"; shift
|
|
1114
|
+
[ -f "$recipe" ] || { echo "mme-recipe: recipe not found: $recipe" >&2; exit 2; }
|
|
1115
|
+
local out="$root/temp/recipe-artifacts/$(basename "$recipe" .json)-$(date -u +%Y%m%dT%H%M%SZ)"
|
|
1116
|
+
log "root=$root cdp=$CDP_PORT watcher=${WATCHER_PORT:-?} out=$out"
|
|
1117
|
+
exec "$runner" run "$recipe" --adapter extension --project-root "$root" --cdp-port "$CDP_PORT" --artifacts-dir "$out" "$@"
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
interactive() {
|
|
1121
|
+
init_context
|
|
1122
|
+
echo "mme-recipe (${root}, cdp=${CDP_PORT:-?}, watcher=${WATCHER_PORT:-?})"
|
|
1123
|
+
PS3='choose> '
|
|
1124
|
+
select choice in status ports up runtime-status decision update ready rebuild refresh refresh-once watch stop logs debug sidepanel actions doctor quit; do
|
|
1125
|
+
case "$choice" in
|
|
1126
|
+
status) status_home ;;
|
|
1127
|
+
ports) show_ports ;;
|
|
1128
|
+
up) do_up ;;
|
|
1129
|
+
runtime-status) runtime_status | pipe_pretty ;;
|
|
1130
|
+
decision) run_json_pretty "$runner" runtime-decision --adapter extension --target "$root" ${CDP_PORT:+--cdp-port "$CDP_PORT"} ;;
|
|
1131
|
+
update) do_update ;;
|
|
1132
|
+
ready) "$runner" ensure-ready --adapter extension --target "$root" --cdp-port "$CDP_PORT" ;;
|
|
1133
|
+
watch) do_watch ;;
|
|
1134
|
+
stop) do_stop ;;
|
|
1135
|
+
rebuild) do_reopen ;;
|
|
1136
|
+
refresh) do_refresh ;;
|
|
1137
|
+
refresh-once) do_refresh_once ;;
|
|
1138
|
+
logs) tail_logs ;;
|
|
1139
|
+
debug) open_debug_ui ;;
|
|
1140
|
+
sidepanel) sidepanel cycle ;;
|
|
1141
|
+
actions) run_json_pretty "$runner" actions --adapter extension ;;
|
|
1142
|
+
doctor) run_json_pretty "$runner" doctor --adapter extension --target "$root" ;;
|
|
1143
|
+
quit|'') break ;;
|
|
1144
|
+
esac
|
|
1145
|
+
echo
|
|
1146
|
+
done
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
cmd="${1:-status}"
|
|
1150
|
+
[ "$#" -gt 0 ] && shift
|
|
1151
|
+
case "$cmd" in
|
|
1152
|
+
-h|--help) usage; exit 0 ;;
|
|
1153
|
+
interactive|menu) interactive ;;
|
|
1154
|
+
sync) do_sync "$@" ;;
|
|
1155
|
+
completion|complete) do_completion "$@" ;;
|
|
1156
|
+
prepare) prepare_extension "$@" ;;
|
|
1157
|
+
status|health|home) status_home "$@" ;;
|
|
1158
|
+
ports) show_ports "$@" ;;
|
|
1159
|
+
up) do_up "$@" ;;
|
|
1160
|
+
runtime-status) if json_requested "$@"; then runtime_status "$@"; else runtime_status "$@" | pipe_pretty; fi ;;
|
|
1161
|
+
decision|decide) decision "$@" ;;
|
|
1162
|
+
update|sync-runtime) do_update "$@" ;;
|
|
1163
|
+
ready|ensure-ready) ready "$@" ;;
|
|
1164
|
+
watch|start-watch|watcher) do_watch "$@" ;;
|
|
1165
|
+
stop|stop-watch|stop-watcher) do_stop "$@" ;;
|
|
1166
|
+
logs|tail|tail-webpack) tail_logs "$@" ;;
|
|
1167
|
+
rebuild|reset|reopen|reload|browser|launch|full-launch|runtime-launch)
|
|
1168
|
+
[ "$cmd" = "reload" ] && log "reload runs a full rebuild (not a tab refresh); for fast updates use: recipe refresh"
|
|
1169
|
+
do_reopen "$@"
|
|
1170
|
+
;;
|
|
1171
|
+
refresh) do_refresh "$@" ;;
|
|
1172
|
+
build) do_build "$@" ;;
|
|
1173
|
+
refresh:once|refresh-once|build:once|build-once) do_refresh_once "$@" ;;
|
|
1174
|
+
debug|devtools|inspect) open_debug_ui "$@" ;;
|
|
1175
|
+
sidepanel) sidepanel "$@" ;;
|
|
1176
|
+
actions) init_context; run_json_pretty "$runner" actions --adapter extension "$@" ;;
|
|
1177
|
+
doctor) init_context; run_json_pretty "$runner" doctor --adapter extension --target "$root" "$@" ;;
|
|
1178
|
+
run) recipe="${1:-}"; [ -n "$recipe" ] || { usage; exit 2; }; shift; run_recipe "$recipe" "$@" ;;
|
|
1179
|
+
*.json) run_recipe "$cmd" "$@" ;;
|
|
1180
|
+
*) echo "mme-recipe: unknown command or missing recipe: $cmd" >&2; usage; exit 2 ;;
|
|
1181
|
+
esac
|