159 lines
4.4 KiB
Bash
Executable File
159 lines
4.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# dev.sh — start/stop the frontend (Nuxt) and backend (Express) together.
|
|
#
|
|
# Usage:
|
|
# ./dev.sh Start both in the foreground (Ctrl+C stops both)
|
|
# ./dev.sh start Start both dev servers in the background
|
|
# ./dev.sh stop Stop both dev servers
|
|
# ./dev.sh restart Stop then start
|
|
# ./dev.sh status Show which services are running
|
|
# ./dev.sh logs [service] Follow logs (all, backend, or frontend)
|
|
|
|
set -euo pipefail
|
|
|
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PID_DIR="$ROOT/.pids"
|
|
LOG_DIR="$ROOT/.logs"
|
|
SERVICES=("backend" "frontend")
|
|
|
|
pid_of() {
|
|
local pid_file="$PID_DIR/$1.pid"
|
|
[[ -f "$pid_file" ]] && cat "$pid_file" || true
|
|
}
|
|
|
|
is_running() {
|
|
local pid
|
|
pid="$(pid_of "$1")"
|
|
[[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null
|
|
}
|
|
|
|
start_service() {
|
|
local name="$1"
|
|
if is_running "$name"; then
|
|
echo "$name is already running (PID $(pid_of "$name"))"
|
|
return
|
|
fi
|
|
mkdir -p "$PID_DIR" "$LOG_DIR"
|
|
# setsid puts each service in its own process group so `stop` can kill
|
|
# npm and all of its child processes (node --watch, nuxt, ...) cleanly.
|
|
setsid bash -c 'cd "$1" && exec npm run dev' _ "$ROOT/$name" \
|
|
> "$LOG_DIR/$name.log" 2>&1 &
|
|
local pid=$!
|
|
echo "$pid" > "$PID_DIR/$name.pid"
|
|
echo "started $name (PID $pid) — log: $LOG_DIR/$name.log"
|
|
}
|
|
|
|
stop_service() {
|
|
local name="$1"
|
|
local pid
|
|
pid="$(pid_of "$name")"
|
|
|
|
if ! is_running "$name"; then
|
|
echo "$name is not running"
|
|
rm -f "$PID_DIR/$name.pid"
|
|
return
|
|
fi
|
|
|
|
# Negative PID kills the whole process group (npm + its children).
|
|
kill -TERM -- "-$pid" 2>/dev/null || kill -TERM "$pid" 2>/dev/null || true
|
|
# Wait up to 5s for a graceful shutdown, then force-kill.
|
|
for _ in {1..50}; do
|
|
kill -0 "$pid" 2>/dev/null || break
|
|
sleep 0.1
|
|
done
|
|
if kill -0 "$pid" 2>/dev/null; then
|
|
kill -KILL -- "-$pid" 2>/dev/null || kill -KILL "$pid" 2>/dev/null || true
|
|
fi
|
|
rm -f "$PID_DIR/$name.pid"
|
|
echo "stopped $name (PID $pid)"
|
|
}
|
|
|
|
status() {
|
|
for name in "${SERVICES[@]}"; do
|
|
if is_running "$name"; then
|
|
echo "$name: running (PID $(pid_of "$name"))"
|
|
else
|
|
echo "$name: stopped"
|
|
fi
|
|
done
|
|
}
|
|
|
|
FG_PIDS=()
|
|
SHUTTING=0
|
|
|
|
# Stop all foreground services (and their process groups). A second call
|
|
# (e.g. pressing Ctrl+C again while shutting down) escalates to SIGKILL.
|
|
fg_stop_all() {
|
|
local name pid sig=TERM
|
|
if [[ $SHUTTING -eq 1 ]]; then sig=KILL; else
|
|
SHUTTING=1
|
|
echo
|
|
echo "stopping frontend + backend..."
|
|
fi
|
|
for pid in "${FG_PIDS[@]:-}"; do
|
|
kill -"$sig" -- "-$pid" 2>/dev/null || kill -"$sig" "$pid" 2>/dev/null || true
|
|
done
|
|
wait 2>/dev/null || true
|
|
for name in "${SERVICES[@]}"; do rm -f "$PID_DIR/$name.pid"; done
|
|
exit 0
|
|
}
|
|
|
|
foreground() {
|
|
local name
|
|
for name in "${SERVICES[@]}"; do
|
|
if is_running "$name"; then
|
|
echo "$name is already running (PID $(pid_of "$name")); run '$0 stop' first" >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
mkdir -p "$PID_DIR"
|
|
for name in "${SERVICES[@]}"; do
|
|
# setsid puts each service in its own process group (PID == PGID) so the
|
|
# output prefix pipeline survives the shutdown; sed -u tags every line.
|
|
setsid bash -c 'cd "$1" && exec npm run dev' _ "$ROOT/$name" \
|
|
> >(sed -u "s/^/[$name] /") 2>&1 &
|
|
local pid=$!
|
|
FG_PIDS+=("$pid")
|
|
echo "$pid" > "$PID_DIR/$name.pid"
|
|
done
|
|
trap 'fg_stop_all' INT TERM
|
|
echo "frontend + backend running — press Ctrl+C to stop both"
|
|
# Exit as soon as either service dies, so they always run/stop together.
|
|
wait -n
|
|
echo "a dev server exited — stopping the other one..."
|
|
fg_stop_all
|
|
}
|
|
|
|
case "${1:-dev}" in
|
|
dev)
|
|
foreground
|
|
;;
|
|
start)
|
|
for name in "${SERVICES[@]}"; do start_service "$name"; done
|
|
echo
|
|
echo "backend: http://localhost:5000"
|
|
echo "frontend: http://localhost:3000"
|
|
;;
|
|
stop)
|
|
for name in "${SERVICES[@]}"; do stop_service "$name"; done
|
|
;;
|
|
restart)
|
|
for name in "${SERVICES[@]}"; do stop_service "$name"; done
|
|
for name in "${SERVICES[@]}"; do start_service "$name"; done
|
|
;;
|
|
status)
|
|
status
|
|
;;
|
|
logs)
|
|
case "${2:-all}" in
|
|
all) tail -f "$LOG_DIR"/*.log ;;
|
|
backend|frontend) tail -f "$LOG_DIR/$2.log" ;;
|
|
*) echo "usage: $0 logs [all|backend|frontend]"; exit 1 ;;
|
|
esac
|
|
;;
|
|
*)
|
|
echo "usage: $0 [dev|start|stop|restart|status|logs [all|backend|frontend]] (default: dev)"
|
|
exit 1
|
|
;;
|
|
esac
|