240 lines
7.5 KiB
Bash
Executable File
240 lines
7.5 KiB
Bash
Executable File
#!/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
|
||
}
|