#!/bin/bash # ========================================== # Web 部署公共函数库(Next.js 16 + pnpm 版本,**默认**部署模型) # 原始 Dart: scripts/deploy/_deploy_lib.sh # 上一轮(rsync standalone)→ 本轮:标准 .next/ + public/ + next start # # 主要差异: # - 构建命令:flutter build web → pnpm install + pnpm build # - 构建输出:./build/web/ → ./.next/ + ./public/ # - 版本号源:pubspec.yaml → package.json # - 删除 compress_main_js(Next.js 无单 JS 入口) # - 删除 standalone 三段 rsync(standalone + static + public) # ========================================== # 标准 Next.js 构建产物 LOCAL_WEB_DIR="./.next" LOCAL_PUBLIC_DIR="./public" # 设置环境(Next.js 按 NODE_ENV 自动加载 .env.{development,test,production}, # 但保留 cp 到 .env 的行为以兼容历史流程) setup_env() { local env_file="$1" if [ -z "$env_file" ]; then echo "未设置环境配置文件,跳过" return 0 fi if [ -f "$env_file" ]; then echo "复制 $env_file 到项目根 .env" cp "$env_file" .env else echo "警告: $env_file 不存在" fi } # 检查本地构建目录是否存在 check_local_build_dir() { if [ ! -d "$LOCAL_WEB_DIR" ]; then echo "错误: 本地构建目录 $LOCAL_WEB_DIR 不存在" echo "请先运行: pnpm build" return 1 fi return 0 } # 构建函数:执行 pnpm install + pnpm build build() { echo "==========================================" echo "开始构建 Next.js 制品..." echo "==========================================" # 安装依赖(frozen-lockfile 保证 lockfile 一致) pnpm install --frozen-lockfile if [ $? -ne 0 ]; then echo "==========================================" echo "pnpm install 失败!" echo "==========================================" return 1 fi # 构建(默认 Turbopack,输出到 .next/) pnpm build if [ $? -ne 0 ]; then echo "==========================================" echo "构建失败!" echo "==========================================" return 1 fi # 检查本地构建目录 check_local_build_dir || return 1 echo "==========================================" echo "构建完成!" echo "构建目录: $LOCAL_WEB_DIR" echo "==========================================" return 0 } # 执行部署:rsync .next/ + public/ deploy() { echo "==========================================" echo "开始部署 Web 制品到服务器..." echo "服务器: $SERVER_USER@$SERVER_HOST" echo "目标路径: $SERVER_PATH" echo "==========================================" # 1. 同步 .next/(含 build manifest / chunks / 路由表 / 静态预渲染) sshpass -p "$SERVER_PASSWORD" rsync -avz --delete \ -e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" \ "$LOCAL_WEB_DIR/" \ "$SERVER_USER@$SERVER_HOST:$SERVER_PATH/" if [ $? -ne 0 ]; then echo "==========================================" echo "部署 .next/ 失败!" echo "==========================================" return 1 fi # 2. 同步 public/(favicon / manifest / 静态图片) sshpass -p "$SERVER_PASSWORD" rsync -avz \ -e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" \ "$LOCAL_PUBLIC_DIR/" \ "$SERVER_USER@$SERVER_HOST:$SERVER_PATH/public/" if [ $? -eq 0 ]; then echo "==========================================" echo "部署成功!" echo "服务器启动:cd $SERVER_PATH && next start -H 0.0.0.0 -p 3000" echo "==========================================" else echo "==========================================" echo "部署失败!请检查错误信息" echo "==========================================" return 1 fi return 0 } # 归档函数:复制构建产物到 archives/{版本号}/ archive() { local version="$1" if [ -z "$version" ]; then echo "错误: 未指定版本号" return 1 fi local archive_dir="./archives/$version" echo "==========================================" echo "开始归档 Web 制品..." echo "版本号: $version" echo "归档目录: $archive_dir" echo "==========================================" # 创建归档目录 mkdir -p "$archive_dir" # 复制 .next/ if [ -d "$LOCAL_WEB_DIR" ]; then cp -r "$LOCAL_WEB_DIR/"* "$archive_dir/" fi # 复制 public/ if [ -d "$LOCAL_PUBLIC_DIR" ]; then mkdir -p "$archive_dir/public" cp -r "$LOCAL_PUBLIC_DIR/"* "$archive_dir/public/" fi if [ $? -eq 0 ]; then echo "==========================================" echo "归档成功!" echo "归档路径: $archive_dir" echo "==========================================" else echo "==========================================" echo "归档失败!请检查错误信息" echo "==========================================" return 1 fi return 0 } # 读取当前版本号(package.json) get_version() { grep '"version":' package.json | head -1 | sed -E 's/.*"version":\s*"([^"]+)".*/\1/' } # 递增 build number 并更新 package.json # 返回新的版本号(仅 stdout 输出版本号,日志输出到 stderr) bump_version() { local current=$(get_version) if [ -z "$current" ]; then echo "错误: 无法读取当前版本号" >&2 return 1 fi # 解析版本号(格式: x.y.z+build) local build_num=$(echo "$current" | cut -d'+' -f2) local new_build_num=$((build_num + 1)) local new_version="${current%%+*}+${new_build_num}" # 日志输出到 stderr echo "==========================================" >&2 echo "更新版本号..." >&2 echo "当前版本: $current" >&2 echo "新版本: $new_version" >&2 echo "==========================================" >&2 # 更新 package.json(JSON 格式) sed -i.bak "s/\"version\":\s*\"$current\"/\"version\": \"$new_version\"/" package.json rm -f package.json.bak if [ $? -eq 0 ]; then echo "版本号更新成功: $new_version" >&2 else echo "版本号更新失败!" >&2 return 1 fi # 只输出版本号到 stdout echo "$new_version" } # 清除 Cloudflare CDN 缓存 purge_cdn_cache() { if [ -z "$ZONE_ID" ] || [ -z "$CF_API_TOKEN" ]; then echo "错误: Cloudflare 配置不完整,跳过缓存清除" return 1 fi echo "==========================================" echo "开始清除 Cloudflare CDN 缓存..." echo "Zone ID: $ZONE_ID" echo "==========================================" local response=$(curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/purge_cache" \ -H "Authorization: Bearer $CF_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"purge_everything":true}') if echo "$response" | grep -q '"success":true'; then echo "==========================================" echo "CDN 缓存清除成功!" echo "==========================================" return 0 else echo "==========================================" echo "CDN 缓存清除失败!" echo "响应: $response" echo "==========================================" return 1 fi }