#!/usr/bin/env bash # tts-like 单端口启动脚本 # # 用法: ./start.sh # 或: bash start.sh # # 行为: # 1. 加载 nvm + 启用 pnpm # 2. node_modules 缺失时自动 pnpm install # 3. 以 PORT=5173 启动 boardgame.io relay server(Koa) # —— Koa 单端口同时 serve 前端页面(apps/web/dist) + /api REST + /socket.io WS, # 浏览器直连 :5173 即可,无需 Vite 代理(见 CODEBUDDY.md「统一端口」一节)。 # —— 前置要求:apps/web/dist 已存在(即跑过 pnpm --filter @tts-like/web build)。 # 若缺失,本脚本会自动先 build 一次。 # # 启动后打开 http://localhost:5173 # 该模式适合用 frp / Cloudflare Tunnel 直接映射 5173 端口对外(客户端零配置)。 # # Ctrl-C 干净退出(连带 kill tsx 派生进程,避免残留占端口)。 set -euo pipefail # ---------- 1. 加载 nvm + pnpm ---------- export NVM_DIR="$HOME/.nvm" if [ -s "$NVM_DIR/nvm.sh" ]; then # shellcheck disable=SC1091 . "$NVM_DIR/nvm.sh" fi if ! command -v node >/dev/null 2>&1; then echo "✗ 未找到 node。请先装 nvm + Node 22:" echo " curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash" echo " nvm install 22" exit 1 fi echo "✓ node $(node -v)" if ! command -v pnpm >/dev/null 2>&1; then echo "→ 启用 pnpm (corepack)..." corepack enable >/dev/null 2>&1 || true fi echo "✓ pnpm $(pnpm --version)" # 切到项目根目录(脚本所在目录) cd "$(dirname "$0")" # ---------- 2. 依赖检查 ---------- if [ ! -d node_modules ]; then echo "→ 首次运行,安装依赖(可能要 1-2 分钟)..." pnpm install fi echo "✓ 依赖就绪" # ---------- 3. 确保前端已构建(单端口 serve 需要 dist) ---------- if [ ! -f apps/web/dist/index.html ]; then echo "→ apps/web/dist 不存在,先 build 前端(生产静态产物)..." pnpm --filter @tts-like/web build fi echo "✓ 前端产物就绪 (apps/web/dist)" # ---------- 4. 清理可能残留的旧进程(5173 / 8000) ---------- for port in 5173 8000; do if command -v ss >/dev/null 2>&1 && ss -ltn "sport = :$port" 2>/dev/null | grep -q ":$port"; then echo "⚠ 端口 $port 已被占用,尝试清理..." (command -v lsof >/dev/null 2>&1 && lsof -ti:"$port" | xargs -r kill) 2>/dev/null || true fi done # ---------- 5. 以 PORT=5173 启动 server(单端口,serve 页面 + API + WS) ---------- echo "" echo "→ 启动 server(PORT=5173,单端口模式)..." export PORT=5173 setsid pnpm --filter @tts-like/server dev & SERVER_PID=$! PIDS=("$SERVER_PID") cleanup() { echo "" echo "→ 退出,清理子进程..." for pid in "${PIDS[@]:-}"; do [ -n "$pid" ] || continue kill -TERM -"$pid" 2>/dev/null || kill "$pid" 2>/dev/null || true done for pid in "${PIDS[@]:-}"; do [ -n "$pid" ] && kill -9 "$pid" 2>/dev/null || true done wait 2>/dev/null || true echo "✓ 已退出" } trap cleanup EXIT INT TERM # 等 server 起来(看到 listening 才继续) echo "→ 等待 server 就绪(:5173/healthz)..." for i in $(seq 1 30); do if curl -sf http://localhost:5173/healthz >/dev/null 2>&1; then echo "✓ server 就绪" break fi if ! kill -0 "$SERVER_PID" 2>/dev/null; then echo "✗ server 进程已退出,启动失败。检查上方日志(常见于 :5173 端口被占用)。" exit 1 fi sleep 0.5 done echo "" echo "════════════════════════════════════════════════════════════════" echo " tts-like 已启动(单端口模式)" echo " 单端口: http://localhost:5173 (页面 + /api + /socket.io)" echo " 健康: http://localhost:5173/healthz" echo "" echo " 局域网/公网映射: 直接映射 5173 端口即可(frp / Cloudflare Tunnel)" echo " Ctrl-C 退出" echo "════════════════════════════════════════════════════════════════" echo "" wait "$SERVER_PID" 2>/dev/null || true