Files
prop-ai-hr/scripts/dev.sh
T

276 lines
5.8 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
source "$ROOT_DIR/scripts/lib/process-tree.sh"
PIDS=()
usage() {
cat <<'EOF'
Usage: ./scripts/dev.sh [start|stop|restart|status|--reset]
Starts MySQL, Redis, MinIO, Qdrant, backend, admin frontend and current mobile-uni frontend.
start start local demo services
stop stop backend and frontend dev processes
restart stop backend and frontend, then start again
status show local demo process status
--reset rebuild local demo database before starting services
EOF
}
cleanup() {
trap - INT TERM EXIT
for pid in "${PIDS[@]}"; do
stop_process_tree "$pid"
done
}
ensure_docker() {
if docker info >/dev/null 2>&1; then
return
fi
if [[ "$(uname -s)" == "Darwin" ]] && [[ -d /Applications/Docker.app ]]; then
echo "Docker daemon is not running. Opening Docker Desktop..."
open -a Docker
for _ in {1..90}; do
if docker info >/dev/null 2>&1; then
echo "Docker daemon is ready."
return
fi
sleep 2
done
fi
echo "Docker daemon is not running. Start Docker Desktop, then run ./scripts/dev.sh again." >&2
exit 1
}
ensure_port_free() {
local port="$1"
if lsof -nP -iTCP:"$port" -sTCP:LISTEN >/dev/null 2>&1; then
echo "Port $port is already in use:" >&2
lsof -nP -iTCP:"$port" -sTCP:LISTEN >&2
echo "Stop the existing process, then run ./scripts/dev.sh again." >&2
exit 1
fi
}
stop_port() {
local port="$1"
local pids
pids="$(lsof -tiTCP:"$port" -sTCP:LISTEN || true)"
if [[ -z "$pids" ]]; then
echo "Port $port is not in use."
return
fi
echo "Stopping port $port: $pids"
kill $pids >/dev/null 2>&1 || true
for _ in {1..20}; do
if ! lsof -nP -iTCP:"$port" -sTCP:LISTEN >/dev/null 2>&1; then
return
fi
sleep 0.2
done
kill -9 $pids >/dev/null 2>&1 || true
}
stop_portless_route() {
local name="$1"
local pids
if ! command -v portless >/dev/null 2>&1; then
return
fi
pids="$(portless list 2>/dev/null | sed -nE "s#.*https://${name}[.]localhost.*[(]pid ([0-9]+)[)].*#\\1#p" || true)"
if [[ -z "$pids" ]]; then
if portless list 2>/dev/null | grep -q "https://${name}[.]localhost"; then
echo "Removing portless alias $name"
portless alias --remove "$name" >/dev/null 2>&1 || true
return
fi
echo "Portless route $name is not running."
return
fi
echo "Stopping portless route $name: $pids"
for pid in $pids; do
stop_process_tree "$pid"
done
for _ in {1..20}; do
if ! portless list 2>/dev/null | grep -q "https://${name}[.]localhost"; then
return
fi
sleep 0.2
done
}
stop_launchctl_job() {
local label="$1"
local domain="gui/$(id -u)/$label"
if ! command -v launchctl >/dev/null 2>&1; then
return
fi
if ! launchctl print "$domain" >/dev/null 2>&1; then
return
fi
echo "Stopping launchctl job $label"
launchctl bootout "$domain" >/dev/null 2>&1 || true
}
using_portless() {
[[ "${PORTLESS:-1}" != "0" ]] && command -v portless >/dev/null 2>&1
}
using_portless_backend() {
using_portless
}
using_portless_admin() {
using_portless
}
using_portless_mobile() {
using_portless
}
backend_url() {
if using_portless_backend; then
echo "https://wygj-api.localhost/"
else
echo "http://127.0.0.1:8080/"
fi
}
admin_url() {
if using_portless_admin; then
echo "https://wygj-admin.localhost/"
else
echo "http://127.0.0.1:5173/"
fi
}
mobile_url() {
if using_portless_mobile; then
echo "https://wygj-mobile-uni.localhost/h5/"
else
echo "http://127.0.0.1:5174/h5/user"
fi
}
stop_dev() {
stop_launchctl_job local.wygj.backend.dev
stop_launchctl_job local.wygj.admin.dev
stop_portless_route wygj-api
stop_port 8080
stop_portless_route wygj-admin
stop_port 5173
stop_portless_route wygj-mobile-uni
stop_port 5174
}
status() {
docker compose -f docker-compose.dev.yml ps
lsof -nP -iTCP:8080 -sTCP:LISTEN || true
lsof -nP -iTCP:5173 -sTCP:LISTEN || true
lsof -nP -iTCP:5174 -sTCP:LISTEN || true
if command -v portless >/dev/null 2>&1; then
portless list || true
fi
}
if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then
usage
exit 0
fi
COMMAND="${1:-start}"
RESET=false
case "$COMMAND" in
start)
;;
--reset)
COMMAND="start"
RESET=true
;;
stop)
cd "$ROOT_DIR"
stop_dev
exit 0
;;
restart)
cd "$ROOT_DIR"
stop_dev
COMMAND="start"
;;
status)
cd "$ROOT_DIR"
status
exit 0
;;
*)
usage
exit 1
;;
esac
cd "$ROOT_DIR"
ensure_docker
if ! using_portless_backend; then
ensure_port_free 8080
fi
if ! using_portless_admin; then
ensure_port_free 5173
fi
if ! using_portless_mobile; then
ensure_port_free 5174
fi
if [[ "$RESET" == true ]]; then
./scripts/reset-dev-db.sh
else
docker compose -f docker-compose.dev.yml up -d mysql redis qdrant minio minio-init
fi
trap cleanup INT TERM EXIT
if using_portless_backend; then
portless alias --remove wygj-api >/dev/null 2>&1 || true
portless run --name wygj-api --force ./scripts/dev-backend.sh &
else
./scripts/dev-backend.sh &
fi
PIDS+=("$!")
./scripts/dev-frontend.sh &
PIDS+=("$!")
if using_portless_mobile; then
portless alias --remove wygj-mobile-uni >/dev/null 2>&1 || true
portless run --name wygj-mobile-uni --force npm --prefix mobile-uni run dev &
else
./scripts/dev-mobile.sh &
fi
PIDS+=("$!")
echo "Backend: $(backend_url)"
echo "Admin: $(admin_url)"
echo "Mobile: $(mobile_url)"
if using_portless_mobile; then
echo "Legacy mobile fallback: PORTLESS=0 ./scripts/dev.sh"
fi
echo "Qdrant: http://127.0.0.1:6333/"
echo "Press Ctrl+C to stop backend, admin frontend and mobile frontend."
while :; do
for pid in "${PIDS[@]}"; do
if ! kill -0 "$pid" >/dev/null 2>&1; then
wait "$pid"
exit $?
fi
done
sleep 2
done