From 32f368a846c2859a4fb8f6e87266cad3c9e48561 Mon Sep 17 00:00:00 2001 From: wecom-fullstack Date: Wed, 14 Oct 2026 13:37:42 +0800 Subject: [PATCH] =?UTF-8?q?fix(infra):=20pnpm=20=E5=B7=A5=E5=85=B7?= =?UTF-8?q?=E9=93=BE=E5=8E=BB=E8=84=86=E5=BC=B1=E5=8C=96=20=E2=80=94?= =?UTF-8?q?=E2=80=94=20frontend/package.json=20=E8=90=BD=20packageManager?= =?UTF-8?q?=20pin=20+=20deploy.sh=20pnpm=20=E7=89=88=E6=9C=AC=E6=96=AD?= =?UTF-8?q?=E8=A8=80=EF=BC=88fail-closed=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ① frontend/package.json 补 "packageManager": "pnpm@10.33.2+sha512.a90faf6f…": corepack 在项目未声明该字段时按全局 lastKnownGood.json 解析版本,而该文件会被任何跑过 corepack 的项目改写(2026-10-13 → 12.11.2 ⇒ MODULE_NOT_FOUND;2026-10-14 → 9.15.9 ⇒ packages field missing)。pin 落库后 corepack 只认 pin,宿主机状态漂移不再影响部署树。 选 10.33.2 的依据:pnpm-workspace.yaml 的 allowBuilds 仅 pnpm 10 识别;部署树 node_modules .modules.yaml packageManager=pnpm@10.33.2(store/v11/v10);lockfileVersion 9.0;2026-10-13 22:53 该版本下部署成功。 ② deploy.sh 新增 assert_pnpm_version()(fail-closed,两处调用:1.2a 首次 pnpm 调用前、[2] 构建前): 期望版本只取自 package.json 的 packageManager(单一源),与 pnpm -v 实读逐字比对,缺失/不一致 即中止;两次调用都先于 [3] 铺站与 [5] 重启 ⇒ 任何生产写之前失败关闭。 零生产写:候选仅落本 worktree 分支,未 push、未动 /srv/hermes/shared/cma-deploy 与 dev 树。 --- deploy.sh | 35 +++++++++++++++++++++++++++++++++++ frontend/package.json | 3 ++- 2 files changed, 37 insertions(+), 1 deletion(-) 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" }