---
date: 2026-05-08
type: error
tags: [nvm, pm2, prisma, deploy, node-upgrade, env-loading]
---

# nvm Node 版本切换的连环坑：pm2 / npm global / prisma generate / .env 全要单独处理

## 现场

UAT 升级 Node 20 → 22 过程中，连续撞到 4 个独立但表现相似的"看似 ESM 修不好"假象。

## 坑 1：nvm 切版本后 pm2 不见了

```bash
nvm install 22.21.1 && nvm use 22.21.1
pm2 list  # → bash: pm2: command not found
```

**根因**：nvm 每个 Node 版本各有独立的 npm global（`~/.nvm/versions/node/v22.21.1/lib/node_modules/`）。Node 20 上装的 pm2 在 Node 22 PATH 里不可见。

**解法**：每个 Node 版本都要独立 `npm install -g pm2`：
```bash
nvm use 22.21.1
npm install -g pm2  # 装到 22.21.1 的 global
```

## 坑 2：PM2 daemon 不会跟着 nvm use 切换

切到 Node 22 后 `pm2 list` 仍显示老进程 — 因为 daemon 还是旧 Node 20 上启动的，pm2 用的是 daemon 的 Node binary，不是当前 shell 的。

**解法**：必须 `pm2 kill` 杀 daemon → 在新 Node 下重新 `pm2 start ecosystem.config.js`。`pm2 restart` / `pm2 reload` 都不会切换 daemon 版本。

完整切换流程：
```bash
nvm use 20.20.2 >/dev/null     # 用旧 PM2 保存当前进程列表
pm2 save                        # 写 ~/.pm2/dump.pm2
pm2 kill                        # 杀 daemon

nvm use 22.21.1 >/dev/null      # 切到新版本
pm2 start /path/ecosystem.config.js  # 新 daemon (Node 22) 起所有进程
pm2 save                        # 持久化
```

## 坑 3：`npm ci` 不会自动 `prisma generate`

UAT `rm -rf node_modules && npm ci` 后 backend 仍 crash，错误：
```
TypeError: Cannot read properties of undefined (reading 'SELF')
  at data-scope.service.ts:53
```

**根因**：`@prisma/client` 包内 `client.js` 是占位文件，必须跑 `prisma generate` 后才会写入实际的 schema-specific 类型。`backend/package.json` 没声明 `postinstall: prisma generate`，所以 `npm ci` 装完 `node_modules` 但 client 类型是空的，所有 `Prisma.DataScopeType.SELF` 等枚举值 → undefined。

**解法**：装完依赖后必须显式跑：
```bash
npm run prisma:generate   # 必须用 npm script（包了 dotenv 读 .env）
```

不要直接 `npx prisma generate` — 见坑 4。

## 坑 4：`npx prisma generate` 直接跑读不到 `DATABASE_URL`

```bash
cd backend && npx prisma generate
# → PrismaConfigEnvError: Missing required environment variable: DATABASE_URL
```

**根因**：项目用 `prisma.config.js` + `dotenv -e .env -- prisma generate` 读取 `.env`（软链到 `.env.uat`）。`npx prisma` 直接走时绕过 dotenv，看不到环境变量。

**解法**：永远用 `npm run prisma:generate`，不要直接 `npx prisma`。

## 坑 5（潜在）：PM2 systemd 开机自启服务也得跟着升级

`pm2 startup` 当前提示输出指向 Node 22 路径。这意味着旧的 systemd 服务（如果当初用 Node 20 注册过）下次系统重启时会用旧路径找 PM2，启动不起来。

**待执行**（需 sudo）：
```bash
sudo env PATH=$PATH:/home/ubuntu/.nvm/versions/node/v22.21.1/bin \
  /home/ubuntu/.nvm/versions/node/v22.21.1/lib/node_modules/pm2/bin/pm2 \
  startup systemd -u ubuntu --hp /home/ubuntu
```

## 整合：nvm Node 升级标准流程

适用任何环境（UAT / dev / 未来生产升级）：

```bash
# 1. 装新 Node
nvm install <new-version>

# 2. 在新 Node 下重装全局工具
nvm use <new-version>
npm install -g pm2     # 必须，不会跨版本

# 3. 用旧 Node 保存进程列表
nvm use <old-version>
pm2 save
pm2 kill

# 4. 切到新 Node 重装应用依赖
nvm use <new-version>
nvm alias default <new-version>
cd <app>/backend  && rm -rf node_modules && npm ci && npm run prisma:generate
cd <app>/frontend && rm -rf node_modules && npm ci

# 5. 在新 Node 下启动 PM2
pm2 start <app>/ecosystem.config.js
pm2 save

# 6. 更新 systemd（sudo，一次性）
sudo env PATH=$PATH:<new-pm2-path> <new-pm2-bin> startup systemd -u <user> --hp <home>
```

## 适用范围

任何"先 nvm 升 Node、再 npm ci 重装"的部署/开发场景。**关键认知**：升级 Node 不是单一操作，是一组连环操作 — 漏掉任何一步都会出现"看似别的 bug"。本次 4 个坑表现都不一样（command not found / 老 daemon / Prisma 类型 undefined / DATABASE_URL 缺失），但都属于同一个根因：版本切换后没把所有派生工具/缓存/客户端全部刷新一遍。

## 关联

- 工单 #242（UAT @otplib ESM 事故）
- PR #243（统一基线 Node 22）
- `.learnings/ERRORS/ERR-20260507-002.md`（事故主线）
- `.learnings/2026-05-08-node-version-drift-across-envs.md`（治理对策）
- `.learnings/ERRORS/ERR-20260429-015.md`（同样的 DataScopeType.SELF undefined，但触发路径不同）
