#!/usr/bin/env bash set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" RUNTIME_DIR="${RUNTIME_DIR:-${ROOT_DIR}/.local}" LOG_DIR="${RUNTIME_DIR}/logs" LOG_FILE="${LOG_FILE:-${LOG_DIR}/frontend.log}" PID_FILE="${PID_FILE:-${RUNTIME_DIR}/frontend.pid}" BACKEND_URL="${LOCAL_BACKEND_URL:-http://127.0.0.1:9091}" FRONTEND_PORT="${PORT:-8000}" NPM_CMD="${NPM_CMD:-npm}" MAX_CMD="${ROOT_DIR}/node_modules/.bin/max" info() { printf '\033[1;32m%s\033[0m\n' "$*" } error() { printf '\033[1;31m%s\033[0m\n' "$*" >&2 } usage() { cat <<'EOF' 用法:./local.sh <命令> [参数] 命令: install 使用 package-lock.json 安装依赖 start [--skip-install] 检查本地后端并在后台启动前端 stop 停止本脚本启动的前端进程 restart [--skip-install] 重启前端 status 查看前端和本地后端状态 logs [行数] 查看并持续跟踪日志,默认显示最后 200 行 help 显示帮助 可选环境变量: LOCAL_BACKEND_URL 本地后端地址,默认 http://127.0.0.1:9091 PORT 前端端口,默认 8000 NPM_CMD npm 命令,默认 npm EOF } prepare_runtime() { mkdir -p "${LOG_DIR}" } read_pid() { if [[ -f "${PID_FILE}" ]]; then tr -d '[:space:]' < "${PID_FILE}" fi } is_running() { local pid pid="$(read_pid)" [[ -n "${pid}" ]] && kill -0 "${pid}" 2>/dev/null } install_dependencies() { command -v "${NPM_CMD}" >/dev/null 2>&1 || { error "未找到 npm 命令:${NPM_CMD}" exit 1 } info "正在根据 package-lock.json 安装依赖..." ( cd "${ROOT_DIR}" "${NPM_CMD}" ci ) } check_backend() { local status command -v curl >/dev/null 2>&1 || { error "未找到 curl,无法检查本地后端" exit 1 } status="$(curl -s -o /dev/null --max-time 3 -w '%{http_code}' "${BACKEND_URL}/" || true)" if [[ -z "${status}" || "${status}" == "000" ]]; then error "本地后端不可访问:${BACKEND_URL}" error "请先在 shz-backend 中执行:./local.sh start" exit 1 fi info "本地后端已连接:${BACKEND_URL}(HTTP ${status})" } start_frontend() { local skip_install="${1:-}" prepare_runtime if is_running; then info "前端已在运行,PID:$(read_pid)" return 0 fi rm -f "${PID_FILE}" check_backend if [[ ! -x "${MAX_CMD}" ]]; then if [[ "${skip_install}" == "--skip-install" ]]; then error "依赖尚未安装,不能跳过安装:${MAX_CMD} 不存在" exit 1 fi install_dependencies elif [[ -n "${skip_install}" && "${skip_install}" != "--skip-install" ]]; then error "未知参数:${skip_install}" exit 2 fi export REACT_APP_ENV=dev export MOCK=none export UMI_ENV=dev export LOCAL_BACKEND_URL="${BACKEND_URL}" export PORT="${FRONTEND_PORT}" info "正在启动前端:http://localhost:${FRONTEND_PORT}" info "后端代理目标:${BACKEND_URL}" ( cd "${ROOT_DIR}" nohup "${MAX_CMD}" dev >> "${LOG_FILE}" 2>&1 & printf '%s\n' "$!" > "${PID_FILE}" ) local frontend_status="000" for _ in {1..60}; do if ! is_running; then break fi frontend_status="$(curl -s -o /dev/null --max-time 1 -w '%{http_code}' \ "http://127.0.0.1:${FRONTEND_PORT}/" || true)" if [[ -n "${frontend_status}" && "${frontend_status}" != "000" ]]; then info "前端已启动,PID:$(read_pid),HTTP ${frontend_status}" info "日志命令:./local.sh logs" return 0 fi sleep 1 done rm -f "${PID_FILE}" error "前端未能在 60 秒内启动,最近日志如下:" tail -n 80 "${LOG_FILE}" >&2 || true exit 1 } stop_frontend() { local pid pid="$(read_pid)" if ! is_running; then rm -f "${PID_FILE}" info "前端未运行" return 0 fi info "正在停止前端,PID:${pid}" kill "${pid}" for _ in {1..15}; do if ! kill -0 "${pid}" 2>/dev/null; then rm -f "${PID_FILE}" info "前端已停止" return 0 fi sleep 1 done error "前端未在 15 秒内停止,请检查 PID:${pid}" exit 1 } show_status() { local backend_status if is_running; then info "前端正在运行,PID:$(read_pid),地址:http://localhost:${FRONTEND_PORT}" else rm -f "${PID_FILE}" info "前端未运行" fi backend_status="$(curl -s -o /dev/null --max-time 2 -w '%{http_code}' "${BACKEND_URL}/" || true)" if [[ -n "${backend_status}" && "${backend_status}" != "000" ]]; then info "本地后端可访问:${BACKEND_URL}(HTTP ${backend_status})" else error "本地后端不可访问:${BACKEND_URL}" fi info "日志文件:${LOG_FILE}" } show_logs() { local lines="${1:-200}" if [[ ! "${lines}" =~ ^[0-9]+$ ]]; then error "日志行数必须是非负整数:${lines}" exit 2 fi prepare_runtime touch "${LOG_FILE}" info "正在跟踪日志:${LOG_FILE}(Ctrl+C 退出)" tail -n "${lines}" -f "${LOG_FILE}" } command="${1:-help}" case "${command}" in install) install_dependencies ;; start) start_frontend "${2:-}" ;; stop) stop_frontend ;; restart) stop_frontend start_frontend "${2:-}" ;; status) show_status ;; logs) show_logs "${2:-200}" ;; help|-h|--help) usage ;; *) error "未知命令:${command}" usage >&2 exit 2 ;; esac