Files
2026-10-20 12:31:48 -05:00

126 lines
3.3 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
VITE_PORT="${VITE_DEV_PORT:-5173}"
BACKEND_CONFIG="${BACKEND_CONFIG:-$ROOT/backend/test_config.yaml}"
# Browser talks to the Go server (which reverse-proxies /__vite/). Keep Vite's
# server.origin + HMR clientPort in sync with that listen port so modules/HMR
# stay same-origin instead of defaulting to :8080 while Air uses test_config.
backend_http_port() {
local config=$1
local port=""
if [[ -f "$config" ]]; then
port="$(
awk '
/^[[:space:]]*http:[[:space:]]*(#.*)?$/ { in_http=1; next }
in_http && /^[^[:space:]#]/ { in_http=0 }
in_http && /^[[:space:]]*port:[[:space:]]*[0-9]+/ {
gsub(/[^0-9]/, "", $2)
print $2
exit
}
' "$config"
)"
fi
if [[ -z "$port" ]]; then
port=8080
fi
printf '%s' "$port"
}
BACKEND_PORT="$(backend_http_port "$BACKEND_CONFIG")"
VITE_ORIGIN="${VITE_DEV_ORIGIN:-http://localhost:${BACKEND_PORT}}"
VITE_CLIENT_PORT="${VITE_DEV_CLIENT_PORT:-${BACKEND_PORT}}"
READY_URL="http://127.0.0.1:${VITE_PORT}/__vite/@vite/client"
WAIT_SECONDS="${VITE_READY_TIMEOUT:-60}"
stop_listeners_on_port() {
local port=$1
if command -v fuser >/dev/null 2>&1; then
fuser -k "${port}/tcp" 2>/dev/null || true
elif command -v lsof >/dev/null 2>&1; then
local pids
pids="$(lsof -ti :"${port}" 2>/dev/null || true)"
if [[ -n "$pids" ]]; then
kill -TERM $pids 2>/dev/null || true
fi
elif command -v ss >/dev/null 2>&1; then
local pid
pid="$(ss -tlnp 2>/dev/null | sed -n "s/.*:${port} .*pid=\\([0-9]*\\).*/\\1/p" | head -n1)"
if [[ -n "$pid" ]]; then
kill -TERM "$pid" 2>/dev/null || true
fi
fi
sleep 0.3
}
cleanup() {
local status=$?
trap - EXIT INT TERM
if [[ -n "${AIR_PID:-}" ]] && kill -0 "$AIR_PID" 2>/dev/null; then
kill -TERM "$AIR_PID" 2>/dev/null || true
fi
if [[ -n "${VITE_PID:-}" ]] && kill -0 "$VITE_PID" 2>/dev/null; then
kill -TERM "$VITE_PID" 2>/dev/null || true
fi
if [[ -n "${AIR_PID:-}" ]]; then
wait "$AIR_PID" 2>/dev/null || true
fi
wait "$VITE_PID" 2>/dev/null || true
exit "$status"
}
wait_for_vite() {
local elapsed=0
while (( elapsed < WAIT_SECONDS )); do
if curl -fsS -o /dev/null "$READY_URL" 2>/dev/null; then
return 0
fi
if ! kill -0 "$VITE_PID" 2>/dev/null; then
echo "Vite dev server exited before becoming ready." >&2
return 1
fi
sleep 1
elapsed=$((elapsed + 1))
done
echo "Timed out waiting for Vite at ${READY_URL}" >&2
return 1
}
trap cleanup EXIT INT TERM
if curl -fsS -o /dev/null "$READY_URL" 2>/dev/null || ss -tln 2>/dev/null | grep -q ":${VITE_PORT} "; then
echo "Clearing stale listener on port ${VITE_PORT}..."
stop_listeners_on_port "$VITE_PORT"
fi
echo "Starting Vite dev server on 127.0.0.1:${VITE_PORT} (browser origin ${VITE_ORIGIN}, HMR clientPort ${VITE_CLIENT_PORT})..."
(
cd "$ROOT/frontend"
export VITE_DEV_ORIGIN="$VITE_ORIGIN"
export VITE_DEV_PORT="$VITE_PORT"
export VITE_DEV_CLIENT_PORT="$VITE_CLIENT_PORT"
exec npm run dev
) &
VITE_PID=$!
wait_for_vite
echo "Vite is ready. Starting Air..."
(
cd "$ROOT/backend"
export FILEBROWSER_DEVMODE=true
AIR_BIN="$(cd build && go tool -n air)"
exec "$AIR_BIN"
) &
AIR_PID=$!
# wait -n needs Bash 4.3+ (macOS /bin/bash is 3.2)
while kill -0 "$VITE_PID" 2>/dev/null && kill -0 "$AIR_PID" 2>/dev/null; do
sleep 1
done
echo "A dev process exited; stopping the other..." >&2
exit 1