diff --git a/deploy.sh b/deploy.sh index 29267d98..210f85f0 100755 --- a/deploy.sh +++ b/deploy.sh @@ -36,6 +36,34 @@ UNIT_FILE="${CMA_DEPLOY_UNIT_FILE:-/etc/systemd/system/cma-backend.service}" ENV_SRC="$REPO_DIR/backend/.env" VENV_SRC="$REPO_DIR/backend/venv" +# ── pnpm 版本断言(2026-10-14 deploy-chain-rootfix;fail-closed)──────────────── +# 背景:corepack 在项目**未声明** `packageManager` 时,按全局 +# /root/.cache/node/corepack/lastKnownGood.json 解析 pnpm 版本 —— 该文件会被**任何** +# 跑过 corepack 的项目改写(本型事故历史 ≥3 次:2026-10-13 12.11.2 ⇒ `Cannot find +# module '…/bin/pnpm.cjs'`;2026-10-14 9.15.9 ⇒ `packages field missing or empty`)。 +# ⇒ 无 pin 的部署树会**静默换工具链**,一路跑到 pnpm build 才炸,且修复只能落在宿主机状态上。 +# 做法:期望版本**只**取自 `frontend/package.json` 的 `packageManager`(单一源),与 +# `pnpm -v` 实读逐字比对(也覆盖 COREPACK_ENABLE_PROJECT_SPEC=0 等让 pin 失效的形态); +# 缺失或不一致即中止。断言先于任何 pnpm 调用与任何生产写([3] 铺站 / [5] 重启)。 +# 用法:在部署树 frontend 目录内调用 `assert_pnpm_version`。 +assert_pnpm_version() { + local expect actual + expect="$(sed -n 's/.*"packageManager"[[:space:]]*:[[:space:]]*"pnpm@\([^"+]*\).*/\1/p' package.json 2>/dev/null | head -1)" + if [ -z "$expect" ]; then + echo "❌ pnpm 版本断言失败:frontend/package.json 未声明 \"packageManager\": \"pnpm@+sha512…\"" + echo " 无 pin ⇒ corepack 按全局 lastKnownGood 解析 ⇒ 工具链不可复现 ⇒ 中止(生产未被触碰)" + exit 1 + fi + actual="$(pnpm -v 2>/dev/null | tail -1 | tr -d '[:space:]' || true)" + if [ "$actual" != "$expect" ]; then + echo "❌ pnpm 版本断言失败:package.json pin=$expect,实读 pnpm -v=${actual:-<空>}" + echo " 多为 corepack lastKnownGood 漂移(/root/.cache/node/corepack/lastKnownGood.json)" + echo " 修法:HOME=/root corepack prepare pnpm@$expect --activate | 中止(生产未被触碰)" + exit 1 + fi + echo " ✓ pnpm 版本断言通过:pnpm $actual == package.json pin $expect" +} + echo "===== 管理会计OS 生产部署 =====" echo " 入口工作区(开发区): $REPO_DIR" echo " 部署树(已提交树) : $DEPLOY_SRC" @@ -138,6 +166,10 @@ mkdir -p "$DEPLOY_SRC/backend/logs" "$DEPLOY_SRC/backend/scripts/backups" if [ -f "$REPO_DIR/frontend/.env" ]; then ln -sfn "$REPO_DIR/frontend/.env" "$DEPLOY_SRC/frontend/.env" fi +# 1.2a pnpm 版本断言(fail-closed;先于**首次** pnpm 调用):坏工具链不得先污染部署树 node_modules +if [ -d "$DEPLOY_SRC/frontend" ]; then + ( cd "$DEPLOY_SRC/frontend" && assert_pnpm_version ) +fi if [ ! -e "$DEPLOY_SRC/frontend/node_modules" ]; then echo " ℹ️ 部署树缺 frontend/node_modules → 尝试 pnpm install --frozen-lockfile --prefer-offline" if ! ( cd "$DEPLOY_SRC/frontend" && pnpm install --frozen-lockfile --prefer-offline ); then @@ -171,6 +203,9 @@ echo " ✓ 鉴权面审计通过(无 B 类缺口)" # 2. 构建前端(在部署树内构建:pnpm/corepack 的生成物落在这里,污染不到开发区) echo "[2/5] 构建前端..." cd "$DEPLOY_SRC/frontend" +# [2] 段前 pnpm 版本断言(fail-closed):与 package.json 的 packageManager pin 不一致即中止, +# 先于 [3] 铺站与 [5] 重启 ⇒ 任何生产写之前失败关闭。 +assert_pnpm_version pnpm build echo " ✓ 构建完成" # 构建工具链(corepack/pnpm、unplugin-auto-import/vue-components)会在部署树里写生成物 —— diff --git a/frontend/package.json b/frontend/package.json index 03a4b6da..2e0e2dc8 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -51,5 +51,6 @@ "unplugin-vue-components": "^32.0.0", "vite": "^5.0.0", "vue-tsc": "^3.2.7" - } + }, + "packageManager": "pnpm@10.33.2+sha512.a90faf6feeab71ad6c6e57f94e0fe1a12f5dcc22cd754db40ae9593eb6a3e0b6b12e3540218bb37ae083404b1f2ce6db2a4121e979829b4aff94b99f49da1cf8" }