145 lines · 3.5 KB · Shell
RawDownload
#!/usr/bin/env bash# Keeps `npm run dev` alive — repairs corrupted .next and restarts after crashes,# including Turbopack cache rot that leaves Next running but serving 500s.set -uo pipefail ROOT="$(cd "$(dirname "$0")/.." && pwd)"PORT="${LOCAL_STUDIO_DEV_PORT:-3000}"LOG="${ROOT}/.local-studio-dev.log"RUN_LOG="${ROOT}/.local-studio-dev.run.log"PIDFILE="${ROOT}/.local-studio-dev.pid"DEV_PID=""READY=0BAD=0 stop_port() {  local pids  pids="$(lsof -ti:"$PORT" 2>/dev/null || true)"  if [ -n "$pids" ]; then    echo "[dev-watch] stopping process(es) on :$PORT: $pids"    kill $pids 2>/dev/null || true    sleep 1    kill -9 $pids 2>/dev/null || true  fi} stop_dev() {  if [ -n "${DEV_PID}" ] && kill -0 "$DEV_PID" 2>/dev/null; then    local pgid    pgid="$(ps -o pgid= -p "$DEV_PID" 2>/dev/null | tr -d ' ' || true)"    kill "$DEV_PID" 2>/dev/null || true    if [ -n "$pgid" ] && [ "$pgid" != "1" ]; then      kill -- "-$pgid" 2>/dev/null || true    fi    sleep 1    kill -9 "$DEV_PID" 2>/dev/null || true    if [ -n "$pgid" ] && [ "$pgid" != "1" ]; then      kill -9 -- "-$pgid" 2>/dev/null || true    fi    wait "$DEV_PID" 2>/dev/null || true  fi  DEV_PID=""  stop_port} repair_cache() {  echo "[dev-watch] clearing frontend/.next (Turbopack cache repair)"  rm -rf "$ROOT/frontend/.next"} log_is_corrupt() {  [ -f "$RUN_LOG" ] || return 1  tail -n 240 "$RUN_LOG" 2>/dev/null | grep -E -q \    'TurbopackInternalError|Failed to restore task data|ENOENT:.*app-paths-manifest|ENOENT:.*routes-manifest|ENOENT:.*\[turbopack\]_runtime|Cannot find module .*\[turbopack\]'} http_code() {  curl -sS -o /dev/null -w '%{http_code}' --max-time 3 "http://127.0.0.1:${PORT}/" 2>/dev/null || echo 000} http_is_down() {  local code  code="$(http_code)"  case "$code" in    200|204|301|302|303|307|308)      READY=1      BAD=0      return 1      ;;    500|502)      if [ "$READY" -eq 1 ]; then        BAD=$((BAD + 1))      fi      ;;    *)      BAD=0      ;;  esac  [ "$READY" -eq 1 ] && [ "$BAD" -ge 3 ]} if [ "${1:-}" = "stop" ]; then  if [ -f "$PIDFILE" ]; then    watcher_pid="$(cat "$PIDFILE")"    if kill -0 "$watcher_pid" 2>/dev/null; then      echo "[dev-watch] stopping watcher (pid $watcher_pid)"      kill "$watcher_pid" 2>/dev/null || true      sleep 1      kill -9 "$watcher_pid" 2>/dev/null || true    fi    rm -f "$PIDFILE"  fi  stop_port  echo "[dev-watch] stopped"  exit 0fi if [ -f "$PIDFILE" ]; then  old_pid="$(cat "$PIDFILE")"  if kill -0 "$old_pid" 2>/dev/null; then    echo "[dev-watch] already running (pid $old_pid). Use: npm run dev:stop"    exit 0  fi  rm -f "$PIDFILE"fi trap '' HUPecho "[dev-watch] log → $LOG"echo $$ >"$PIDFILE"trap 'stop_dev; rm -f "$PIDFILE"' EXIT INT TERM stop_portcd "$ROOT" while true; do  echo "[dev-watch] starting dev @ $(date -u +%Y-%m-%dT%H:%M:%SZ)" | tee -a "$LOG"  READY=0  BAD=0  : >"$RUN_LOG"  set +e  npm run dev > >(tee -a "$LOG" "$RUN_LOG") 2>&1 &  DEV_PID=$!  live_repair=0  while kill -0 "$DEV_PID" 2>/dev/null; do    sleep 4    if log_is_corrupt || http_is_down; then      echo "[dev-watch] Next is unhealthy on :$PORT — repairing cache and restarting" | tee -a "$LOG"      live_repair=1      stop_dev      break    fi  done  if [ "$live_repair" -eq 0 ]; then    wait "$DEV_PID"    code="$?"  else    code=75  fi  DEV_PID=""  set -e   echo "[dev-watch] dev exited ($code) — repairing cache, restarting in 3s…" | tee -a "$LOG"  stop_port  repair_cache  sleep 3done