#!/usr/bin/env bash set -Eeuo pipefail echo -e "\033[32m正在启动 Digital Embryo 开发环境...\033[0m" # 获取当前脚本所在目录 SCRIPT_DIR="$(cd -- "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" # 前后端目录 FRONTEND_DIR="${SCRIPT_DIR}/embryo-frontend" BACKEND_DIR="${SCRIPT_DIR}/embryo-backend" # 颜色输出 yellow() { echo -e "\033[33m$*\033[0m"; } green() { echo -e "\033[32m$*\033[0m"; } cyan() { echo -e "\033[36m$*\033[0m"; } red() { echo -e "\033[31m$*\033[0m"; } # PATH 修正助手 prepend_path_if_dir_exists() { local dir="$1" if [[ -d "$dir" ]] && [[ ":$PATH:" != *":${dir}:"* ]]; then export PATH="${dir}:${PATH}" fi } # 确保 brew 可用(不强制安装,避免交互) has_brew() { command -v brew >/dev/null 2>&1; } # 安装 npm(优先 brew,其次 nvm) ensure_npm() { if command -v npm >/dev/null 2>&1; then return 0 fi yellow "未检测到 npm,尝试自动安装..." if has_brew; then yellow "使用 Homebrew 安装 Node.js(包含 npm)..." if brew install node; then green "已通过 Homebrew 安装 Node.js/npm" return 0 else red "通过 Homebrew 安装 Node.js 失败,回退到 nvm 安装方式" fi fi # 回退:安装 nvm 并安装 Node LTS yellow "安装 nvm..." export NVM_DIR="$HOME/.nvm" mkdir -p "$NVM_DIR" if curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash; then # 当前会话加载 nvm if [[ -s "$NVM_DIR/nvm.sh" ]]; then # shellcheck disable=SC1090 . "$NVM_DIR/nvm.sh" fi if [[ -s "$NVM_DIR/bash_completion" ]]; then # shellcheck disable=SC1090 . "$NVM_DIR/bash_completion" fi yellow "使用 nvm 安装 Node.js LTS..." nvm install --lts nvm use --lts nvm alias default 'lts/*' || true else red "安装 nvm 失败" fi if ! command -v npm >/dev/null 2>&1; then red "自动安装 npm 失败,请手动安装 Node.js/npm 后重试。" exit 1 fi green "npm 安装完成" } # 安装 uv(优先 brew,其次官方脚本) ensure_uv() { if command -v uv >/dev/null 2>&1; then return 0 fi yellow "未检测到 uv,尝试自动安装..." if has_brew; then yellow "使用 Homebrew 安装 uv..." if brew install uv; then green "已通过 Homebrew 安装 uv" return 0 else red "通过 Homebrew 安装 uv 失败,回退到官方安装脚本" fi fi # 回退:官方安装脚本(将二进制置于 ~/.local/bin 或 ~/.cargo/bin) if curl -fsSL https://astral.sh/uv/install.sh | sh; then # 尝试将常见安装目录加入 PATH(仅当前会话) prepend_path_if_dir_exists "$HOME/.local/bin" prepend_path_if_dir_exists "$HOME/.cargo/bin" else red "运行 uv 官方安装脚本失败" fi if ! command -v uv >/dev/null 2>&1; then red "自动安装 uv 失败,请手动安装 uv 后重试(可用 brew install uv)。" exit 1 fi green "uv 安装完成" } # 基础校验 if [[ ! -d "${FRONTEND_DIR}" ]]; then echo "前端目录不存在: ${FRONTEND_DIR}" >&2 exit 1 fi if [[ ! -d "${BACKEND_DIR}" ]]; then echo "后端目录不存在: ${BACKEND_DIR}" >&2 exit 1 fi PACKAGE_JSON_PATH="${FRONTEND_DIR}/package.json" APP_PY_PATH="${BACKEND_DIR}/app.py" if [[ ! -f "${PACKAGE_JSON_PATH}" ]]; then echo "package.json 文件不存在: ${PACKAGE_JSON_PATH}" >&2 exit 1 fi if [[ ! -f "${APP_PY_PATH}" ]]; then echo "app.py 文件不存在: ${APP_PY_PATH}" >&2 exit 1 fi # 依赖校验与自动安装 ensure_npm ensure_uv echo -e "\033[33m启动前端开发服务器...\033[0m" pushd "${FRONTEND_DIR}" >/dev/null npm run dev & FRONTEND_PID=$! popd >/dev/null echo -e "\033[33m启动后端API服务器...\033[0m" pushd "${SCRIPT_DIR}" >/dev/null uv run "${BACKEND_DIR}/app.py" & BACKEND_PID=$! popd >/dev/null echo -e "\033[32m两个服务都已启动!\033[0m" echo -e "\033[36m前端开发服务器通常运行在: http://localhost:5173\033[0m" echo -e "\033[36m后端API服务器通常运行在: http://localhost:5000\033[0m" echo echo -e "\033[31m按 Ctrl+C 停止所有服务\033[0m" cleanup() { echo -e "\033[33m正在停止所有服务...\033[0m" if ps -p ${FRONTEND_PID} >/dev/null 2>&1; then kill ${FRONTEND_PID} 2>/dev/null || true wait ${FRONTEND_PID} 2>/dev/null || true fi if ps -p ${BACKEND_PID} >/dev/null 2>&1; then kill ${BACKEND_PID} 2>/dev/null || true wait ${BACKEND_PID} 2>/dev/null || true fi echo -e "\033[32m所有服务已停止\033[0m" } trap cleanup INT TERM # 监控:若任一服务退出则结束脚本并清理 while true; do if ! ps -p ${FRONTEND_PID} >/dev/null 2>&1; then echo "前端服务已退出" break fi if ! ps -p ${BACKEND_PID} >/dev/null 2>&1; then echo "后端服务已退出" break fi sleep 1 done cleanup exit 0