Files
opendance-web/scripts/package-release.sh
T
zijunchiang caca7672cb fix(release): 修复发布包缺失 storage 模块,并加入产物完整性校验
用户实测:用发布包部署后启动报 MODULE_NOT_FOUND
  at dist/models/models.module.js:13  require('../storage/storage.module')

根因:package-release.sh 的 rsync 用了 --exclude 'storage'。该模式匹配
**任意层级**的同名目录,把源码目录 src/storage/ 与编译产物 dist/storage/
一并排除——发布包里完全没有 storage 模块,服务必然起不来。
这与第一阶段 .gitignore 里 'storage/' 误伤 src/storage/ 是同一类错误,
本项目第二次踩到。

修复:
- 排除模式全部锚定到 web-server 根:'/storage/'、'/data/'、'/tmp/'、
  '/.env'、'/admin/',不再匹配嵌套同名目录
- 新增产物完整性校验(打包后自动执行,失败即删除压缩包):
  src 下每个 .ts 必须有对应 dist/*.js;关键文件清单齐全;
  不得混入 node_modules / 真实 .env / 运行时存储 / keystore
- ctl.sh 补上缺失的 warn 函数(line 120: warn: command not found)

实测:
- 对照实验:排除模式改回 'storage' 后校验准确报出缺少
  dist/storage/storage.module.js 并删除压缩包,退出码 1
- 新包端到端:解压 → npm ci --omit=dev(213 包)→ 启动 → /health 200、
  MariaDB 已连接、迁移 0、catalog/songs/models/motions 全部 200
2026-09-15 00:54:22 +08:00

156 lines
6.0 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# ============================================================
# 打包发布产物:opendance-web-v0.1.0.tar.gz
#
# 产物结构(Linux 原生站点部署,不使用 Docker):
# web-server/ NestJS 源码 + 构建产物
# admin/ React 管理端构建产物
# deploy/ install.sh / uninstall.sh / .env.example / nginx / systemd
# contracts/ 当前版本契约快照
# THIRD_PARTY.md 开源项目声明
# README.txt 使用说明
#
# 用法:./scripts/package-release.sh [版本号]
# ============================================================
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$REPO_ROOT"
VERSION="${1:-0.1.0}"
NAME="opendance-web-v${VERSION}"
OUT_DIR="$REPO_ROOT/dist-release"
STAGE="$OUT_DIR/$NAME"
log() { printf '\033[1;34m[package]\033[0m %s\n' "$*"; }
die() { printf '\033[1;31m[error]\033[0m %s\n' "$*" >&2; exit 1; }
# ---------- 1. 契约一致性 ----------
log "校验契约版本一致性..."
( cd web-server && node scripts/check-contracts.mjs )
# ---------- 2. 后端构建 ----------
log "构建后端..."
( cd web-server && npm run build )
# ---------- 3. 管理端构建 ----------
if [ -d web-server/admin/node_modules ]; then
log "构建管理端..."
( cd web-server/admin && npm run build )
else
log "警告:web-server/admin/node_modules 不存在,跳过管理端构建(产物将缺少 admin/)"
fi
# ---------- 4. 组织产物目录 ----------
log "整理产物目录 $STAGE ..."
rm -rf "$STAGE"
mkdir -p "$STAGE"
# 后端源码 + 构建产物(排除依赖、测试、存储、密钥)
# 排除模式**必须锚定到 web-server 根目录**(前导 /)。
# 否则 rsync 的 'storage' 会匹配任意层级的同名目录,把源码目录
# src/storage/ 与编译产物 dist/storage/ 一并删掉——发布包会缺少整个
# storage 模块,服务启动即 MODULE_NOT_FOUND。这是本项目第二次踩到
# 「storage 误伤源码目录」这个坑(第一次是 .gitignore)。
rsync -a --delete \
--exclude 'node_modules' \
--exclude '/storage/' \
--exclude '/data/' \
--exclude '/tmp/' \
--exclude '/.env' \
--exclude '/.env.local' \
--exclude '/admin/' \
web-server/ "$STAGE/web-server/"
# 管理端构建产物(文档要求 admin/ 与 web-server/ 平级)
if [ -d web-server/admin/dist ]; then
mkdir -p "$STAGE/admin"
cp -r web-server/admin/dist/. "$STAGE/admin/"
cp web-server/admin/nginx.conf "$STAGE/admin/" 2>/dev/null || true
else
log "警告:未找到 web-server/admin/dist,产物不含 admin/"
fi
cp -r deploy "$STAGE/deploy"
# 部署配置里绝不能带真实 .env(含数据库口令)
rm -f "$STAGE/deploy/.env"
rm -rf "$STAGE/deploy"/systemd/*.bak "$STAGE/deploy"/nginx/*.bak
cp -r contracts "$STAGE/contracts"
cp THIRD_PARTY.md README.txt project.yaml DSH.md "$STAGE/"
cp -r tasks "$STAGE/tasks"
# ---------- 5. 打包 ----------
log "生成压缩包..."
( cd "$OUT_DIR" && tar -czf "$NAME.tar.gz" "$NAME" )
# ---------- 6. 产物完整性校验 ----------
# 打包脚本本身的错误(如排除模式写错)会静默产出残缺的发布包,
# 直到目标机启动时才暴露。这里做一次硬校验:src 下每个 .ts 都必须在
# 包内有对应的 dist/*.js,且关键文件齐全。
log "校验产物完整性…"
MANIFEST_TMP=$(tar -tzf "$OUT_DIR/$NAME.tar.gz")
fail=0
# 6.1 src → dist 一一对应(能抓到整目录被误排除这类问题)
# 注意 nest build 的 sourceRoot 是 src,产物会**去掉 src/ 前缀**:
# web-server/src/storage/storage.service.ts → web-server/dist/storage/storage.service.js
while IFS= read -r tsfile; do
rel="${tsfile#src/}"
jsfile="opendance-web-v${VERSION}/web-server/dist/${rel%.ts}.js"
if ! grep -qx "$jsfile" <<< "$MANIFEST_TMP"; then
echo " ✗ 缺少编译产物:$jsfile(源:$tsfile)" >&2
fail=1
fi
done < <(cd web-server && find src -name '*.ts' ! -name '*.spec.ts')
[ "$fail" -eq 0 ] && echo " ✓ src 下所有 .ts 均有对应 dist/*.js"
# 6.2 关键文件
for required in \
"opendance-web-v${VERSION}/web-server/dist/main.js" \
"opendance-web-v${VERSION}/web-server/src/storage/storage.service.ts" \
"opendance-web-v${VERSION}/admin/index.html" \
"opendance-web-v${VERSION}/deploy/install.sh" \
"opendance-web-v${VERSION}/deploy/ctl.sh" \
"opendance-web-v${VERSION}/deploy/nginx/opendance.conf" \
"opendance-web-v${VERSION}/deploy/systemd/opendance-api.service" \
"opendance-web-v${VERSION}/contracts/VERSION" \
"opendance-web-v${VERSION}/THIRD_PARTY.md" \
"opendance-web-v${VERSION}/README.txt" ; do
if ! grep -qx "$required" <<< "$MANIFEST_TMP"; then
echo " ✗ 缺少关键文件:$required" >&2
fail=1
fi
done
# 6.3 不得包含的东西(模式必须精确,否则会误伤 src/storage、.env.example)
PREFIX="opendance-web-v${VERSION}"
check_forbidden() {
local label="$1" pattern="$2"
if grep -qE "$pattern" <<< "$MANIFEST_TMP"; then
echo " ✗ 产物中混入了不应包含的内容($label)" >&2
grep -E "$pattern" <<< "$MANIFEST_TMP" | head -3 | sed 's/^/ /' >&2
fail=1
fi
}
check_forbidden "node_modules" "node_modules"
check_forbidden "真实 .env" "^${PREFIX}/(deploy|web-server)/\.env$"
check_forbidden "运行时存储目录" "^${PREFIX}/web-server/storage/"
check_forbidden "keystore" "\.jks$|keystore\.properties$"
[ "$fail" -eq 0 ] && echo " ✓ 无 node_modules / 真实 .env / 运行时存储 / keystore"
if [ "$fail" -ne 0 ]; then
rm -f "$OUT_DIR/$NAME.tar.gz"
die "产物完整性校验失败,已删除有问题的压缩包"
fi
log "完成:$OUT_DIR/$NAME.tar.gz"
echo
echo "产物内容:"
# 先把清单读入变量,避免 tar 因管道提前关闭(SIGPIPE)而误报 "write error"
MANIFEST=$(tar -tzf "$OUT_DIR/$NAME.tar.gz")
echo "$MANIFEST" | sed -n '1,20p'
echo " ...(共 $(echo "$MANIFEST" | wc -l) 项,$(du -h "$OUT_DIR/$NAME.tar.gz" | cut -f1))"
echo
echo "部署方式:解压后进入 deploy/,复制 .env.example 为 .env 并填写数据库信息,执行 ./install.sh"