Files
opendance-web/scripts/package-release.sh
T
opendance 9eae6f3e43 feat(models): 骨骼重绑定——修复 MMD 模型 humanoid 恒为 0/22(契约 skeleton-topology 0.2.0)
根因:normalizeBoneName() 只保留 a-z0-9,**中日文字符被整个丢掉**:
'上半身'/'左ひじ'/'首' 归一化后都是空串,MMD 模型的骨名必然失配,
humanoid 覆盖率恒为 0/22 -> 不可驱动 -> 只能回退交付 GLB 并标记不可用。

修复:
1. 归一化保留 CJK/平假名/片假名/长音符/々,并把全角 ASCII(足IK)折成半角;
2. 候选表补齐四类命名(22 根 x 8-13 个候选):VRoid / Mixamo /
   MMD 标准骨架(下半身/上半身/上半身2/首/頭/左肩/左腕/左ひじ/左手首/
   左足/左ひざ/左足首/左つま先)/ 常见中文名(臀部/头/脖子/左大臂/左大腿…);
   精确匹配保证「左足IK」不会抢占「左足」;
3. Windows 客户端 humanoid_map.gd 的 CANDIDATES 由服务端表**生成写入**,
   normalize() 同步改造,parity 测试断言键/候选顺序完全一致;
4. 契约 skeleton-topology -> 0.2.0:新增 §6「内部动作标准与模型骨骼重绑定」
   (三层标准、22/16 根骨、归一化与匹配规则、实测记录);
5. 新增 4 项测试;规模 17 文件 / 229 项。

实测:奥黛塔 PMX 0/22 -> 21/22、初音未来 PMX 0/22 -> 19/22,均 drivable=true 且产出 VRM。
版本 0.4.0。
2026-09-17 00:10:07 +08:00

181 lines
7.8 KiB
Bash
Executable File
Raw Permalink 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.4.0.tar.gz
#
# 产物结构(Linux 业务服务部署,不使用 Docker、不部署反向代理):
# web-server/ NestJS 源码 + 构建产物
# admin/ React 管理端构建产物
# deploy/ install.sh / ctl.sh / uninstall.sh / .env.example
# env-lib.sh (.env 语法预检) / assimp-lib.sh (Assimp 定位安装)
# init-env.sh (.env 引导)
# systemd|openrc|initd / proxy(反代说明文档,仅文档不部署)
# contracts/ 当前版本契约快照
# THIRD_PARTY.md 开源项目声明
# README.md 使用说明(Markdown, 仓库首页展示的就是它)
# README.txt 使用说明(纯文本同内容版本)
#
# 用法:./scripts/package-release.sh [版本号]
# ============================================================
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$REPO_ROOT"
VERSION="${1:-0.4.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/"
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"/openrc/*.bak "$STAGE/deploy"/initd/*.bak
cp -r contracts "$STAGE/contracts"
cp THIRD_PARTY.md README.txt README.md 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/check-access.sh" \
"opendance-web-v${VERSION}/web-server/dist/preprocess/vmd-parser.js" \
"opendance-web-v${VERSION}/web-server/dist/preprocess/mmd-pose.js" \
"opendance-web-v${VERSION}/web-server/dist/preprocess/mmd-skeleton.js" \
"opendance-web-v${VERSION}/web-server/scripts/inspect-vmd.mjs" \
"opendance-web-v${VERSION}/web-server/scripts/import-default-model.mjs" \
"opendance-web-v${VERSION}/web-server/scripts/reanalyze-models.mjs" \
"opendance-web-v${VERSION}/web-server/dist/common/zip-reader.js" \
"opendance-web-v${VERSION}/web-server/dist/models/model-convert.js" \
"opendance-web-v${VERSION}/web-server/dist/models/glb-textures.js" \
"opendance-web-v${VERSION}/web-server/dist/migrations/1789450000000-AddModelConversionFields.js" \
"opendance-web-v${VERSION}/web-server/dist/migrations/1789460000000-AddModelStats.js" \
"opendance-web-v${VERSION}/web-server/dist/models/model-inspector.js" \
"opendance-web-v${VERSION}/web-server/dist/models/model-thumbnail.js" \
"opendance-web-v${VERSION}/web-server/dist/models/vrm-writer.js" \
"opendance-web-v${VERSION}/web-server/dist/models/gltf-packer.js" \
"opendance-web-v${VERSION}/deploy/init-env.sh" \
"opendance-web-v${VERSION}/deploy/env-lib.sh" \
"opendance-web-v${VERSION}/deploy/assimp-lib.sh" \
"opendance-web-v${VERSION}/deploy/proxy/README.md" \
"opendance-web-v${VERSION}/deploy/proxy/nginx.conf.example" \
"opendance-web-v${VERSION}/deploy/proxy/web.config.example" \
"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" \
"opendance-web-v${VERSION}/README.md" ; 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"