Make local ERP previews one double-click away

Constraint: Finder launch must supervise Java and ngrok in one visible Terminal
Rejected: LaunchAgent | obscures ownership and makes safe cleanup harder
Confidence: high
Scope-risk: narrow
Directive: Never kill listeners not recorded as launcher-owned PIDs
Tested: Shell unit suite, repeat-start reuse, cold start, local/public login and protected API smoke
Not-tested: Finder Gatekeeper behavior on a different Mac
This commit is contained in:
Qiufeng
2026-07-15 16:29:05 +08:00
parent 672569f199
commit 2524a37a07
3 changed files with 897 additions and 0 deletions
+121
View File
@@ -0,0 +1,121 @@
#!/bin/bash
set -uo pipefail
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
SCRIPT="$PROJECT_ROOT/run.command"
PASS=0
FAIL=0
run_test() {
local name="$1"
shift
if ("$@"); then
printf 'PASS %s\n' "$name"
PASS=$((PASS + 1))
else
printf 'FAIL %s\n' "$name"
FAIL=$((FAIL + 1))
fi
}
test_resolves_root_when_sourced_elsewhere() (
[ -r "$SCRIPT" ] || return 1
cd /tmp || return 1
source "$SCRIPT"
[ "$ROOT_DIR" = "$PROJECT_ROOT" ]
[ -z "$BACKEND_PID" ]
[ -z "$NGROK_PID" ]
)
test_reuses_healthy_backend() (
source "$SCRIPT"
declare -F ensure_backend >/dev/null || return 1
backend_is_healthy() { return 0; }
port_listener_pid() { printf '4242\n'; }
start_backend() { return 77; }
ensure_backend
[ -z "$BACKEND_PID" ]
)
test_rejects_unhealthy_port_without_killing_owner() (
source "$SCRIPT"
declare -F ensure_backend >/dev/null || return 1
sleep 30 &
local external_pid=$!
trap "kill '$external_pid' 2>/dev/null || true" EXIT
backend_is_healthy() { return 1; }
port_listener_pid() { printf '%s\n' "$external_pid"; }
if ensure_backend; then return 1; fi
kill -0 "$external_pid" 2>/dev/null || return 1
kill "$external_pid" 2>/dev/null || true
wait "$external_pid" 2>/dev/null || true
trap - EXIT
)
test_cleanup_stops_owned_pid_only() (
source "$SCRIPT"
declare -F cleanup >/dev/null || return 1
local ready="${TMPDIR:-/tmp}/run-command-owned-ready-$$"
rm -f "$ready"
python3 -c 'import signal,sys,time; signal.signal(signal.SIGTERM, lambda *_: sys.exit(0)); open(sys.argv[1], "w").close(); time.sleep(30)' "$ready" &
BACKEND_PID=$!
local attempt=0
while [ ! -e "$ready" ] && [ "$attempt" -lt 50 ]; do
sleep 0.02
attempt=$((attempt + 1))
done
[ -e "$ready" ] || return 1
sleep 30 &
local external_pid=$!
cleanup
if kill -0 "$BACKEND_PID" 2>/dev/null; then return 1; fi
kill -0 "$external_pid" 2>/dev/null || return 1
kill "$external_pid" 2>/dev/null || true
wait "$external_pid" 2>/dev/null || true
rm -f "$ready"
)
test_reuses_healthy_ngrok() (
source "$SCRIPT"
declare -F ensure_ngrok >/dev/null || return 1
ngrok_is_healthy() { return 0; }
port_listener_pid() { printf '5252\n'; }
start_ngrok() { return 78; }
ensure_ngrok
[ -z "$NGROK_PID" ]
)
test_no_open_mode_skips_browser() (
source "$SCRIPT"
declare -F maybe_open_browser >/dev/null || return 1
local marker="${TMPDIR:-/tmp}/run-command-open-$$"
rm -f "$marker"
open() { : > "$marker"; }
ERP_RUN_NO_OPEN=1
maybe_open_browser
[ ! -e "$marker" ]
)
test_main_runs_steps_in_order() (
source "$SCRIPT"
local events="${TMPDIR:-/tmp}/run-command-events-$$"
: > "$events"
preflight() { printf 'preflight\n' >> "$events"; }
ensure_backend() { printf 'backend\n' >> "$events"; }
ensure_ngrok() { printf 'ngrok\n' >> "$events"; }
wait_for_public() { printf 'public\n' >> "$events"; }
maybe_open_browser() { printf 'open\n' >> "$events"; }
monitor_services() { printf 'monitor\n' >> "$events"; }
main
[ "$(tr '\n' ' ' < "$events")" = 'preflight backend ngrok public open monitor ' ]
)
run_test 'resolves project root when sourced elsewhere' test_resolves_root_when_sourced_elsewhere
run_test 'reuses a healthy backend' test_reuses_healthy_backend
run_test 'rejects a foreign 8091 listener without killing it' test_rejects_unhealthy_port_without_killing_owner
run_test 'cleanup stops owned PID only' test_cleanup_stops_owned_pid_only
run_test 'reuses a healthy ngrok tunnel' test_reuses_healthy_ngrok
run_test 'no-open mode skips browser launch' test_no_open_mode_skips_browser
run_test 'main orchestrates steps in order' test_main_runs_steps_in_order
printf 'RESULT pass=%s fail=%s\n' "$PASS" "$FAIL"
[ "$FAIL" -eq 0 ]