diff --git a/start-dev.sh b/start-dev.sh new file mode 100644 index 0000000..640c50b --- /dev/null +++ b/start-dev.sh @@ -0,0 +1,191 @@ +#!/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 + +