# pre-commit hook 不再用 npx：显式指向本地 ts-node binary

> **日期**: 2026-05-09
> **场景**: `.githooks/pre-commit` 第 3 次因 `npx ts-node` 在 worktree 缺依赖时偷拉 TS 7 preview，触发 TS5107 炸 commit
> **关联**: [ERR-20260418-001](ERRORS/ERR-20260418-001.md) / [ERR-20260429-002](ERRORS/ERR-20260429-002.md) / 工单 #265

## 问题

worktree 多分支并行开发时，常开"只改 docs / scripts / learnings 的 PR"——这种 worktree 没装 backend 依赖。但 `.githooks/pre-commit` 在 staged 命中 schema / service 文件时调用 `npx ts-node` 跑校验脚本：

```bash
(cd "${ROOT_DIR}/backend" && npx ts-node --transpile-only --project ... data-scope-fields-check.ts --staged)
```

`npx` 在 `./node_modules/.bin/ts-node` 缺失时**不**报错，而是从 npm 拉最新 ts-node（绑了 TS 7 preview）。TS 7 把 `moduleResolution=node10` 从警告升级为 hard error TS5107，commit 直接炸。`--transpile-only` 跳过的是文件类型检查，但 tsconfig 解析阶段的 deprecation 仍被新版 TS 当 hard error，flag 救不了。

## 元教训：绕行 ≠ 修复

前两次 ERR 给了**临时绕行**："请用 `backend/node_modules/.bin/ts-node`"——但 `.githooks/pre-commit` 自己仍写 `npx ts-node`。绕行只解决当前那次踩坑，**没改根，下个人下次还踩**。第 3 次复现就是必然。

→ **凡是 ERR 给出"正确做法"，配套修复必须落到调用点（hook / script / CI）的代码里**，不能只停留在 learning 文档。

## 项目级修复（详见关联工单 #265 PR）

修了 `.githooks/pre-commit` 和 `.githooks/pre-push` 两处同款 `npx ts-node`：

### 1. 顶部统一定义 `TS_NODE` + `require_ts_node` helper

```bash
TS_NODE="${ROOT_DIR}/backend/node_modules/.bin/ts-node"

require_ts_node() {
  if [ ! -x "$TS_NODE" ]; then
    echo "pre-commit: 未找到 backend/node_modules/.bin/ts-node。" >&2
    echo "  - worktree 也需先 'cd backend && npm install' 才能 commit（hook 校验脚本依赖）。" >&2
    echo "  - 即使 PR 只改 docs/scripts，校验仍需本地 ts-node；不再 fallback 到 npx 拉全网新版。" >&2
    exit 1
  fi
}
```

### 2. 两处 `npx ts-node` 替换为 `"$TS_NODE"`，前置 `require_ts_node`

```bash
require_ts_node
(cd "${ROOT_DIR}/backend" && "$TS_NODE" --transpile-only --project ... data-scope-fields-check.ts --staged)
```

### 3. 副带优化：`--diff-filter=AM` 过滤删除/重命名

`git diff --cached --name-only -- 'backend/src/**/*.service.ts'` 默认含 D（删除）。删除一个 service 文件去跑 access-check 没意义（文件已不存在）→ 加 `--diff-filter=AM` 让"只删 service"的 PR 直接跳过 ts-node 调用，少一个无谓触发点。

### 4. pre-push 同款修复（contract-check）

`.githooks/pre-push:16` 用 `cd testing && npx ts-node scripts/contract-check.ts`，testing 没装 deps 时同样偷拉 TS 7。改成 `${ROOT_DIR}/testing/node_modules/.bin/ts-node`，缺失时 fail-fast 提示 `cd testing && npm install`。

## 可推广策略

**任何 hook / git script / CI 调用 npm/yarn 装的工具时**：

| ❌ 反模式 | ✅ 正模式 |
|---|---|
| `npx <tool>` | 显式 `${ROOT}/<package>/node_modules/.bin/<tool>` |
| 缺依赖 silent fallback 到全网 | 缺依赖立刻 fail-fast + 给可执行的修复提示 |
| 只在 ERR doc 写"正确做法" | 把正确做法 enforce 到调用点代码 |

适用范围：`.githooks/`、`scripts/dev/`、`scripts/ops/`、`.gitea/workflows/`（CI 一般有 NPM cache 不会撞，但同款显式更可靠）。

## 验证

slot worktree 实测 4 case：
- 无 ts-node + stage schema A/M → exit 1，友好中文提示
- 无 ts-node + stage service A/M → exit 1，友好中文提示
- 无 ts-node + 仅删 service (D) → exit 0，跳过 ts-node 调用
- 有 ts-node + stage service A/M → 走本地 ts-node v10.9.2，无 TS5107

## 顺手发现的隐患（未在本 PR 修）

`backend/src/**/*.service.ts` 的 git pathspec 默认 fnmatch **不匹配**顶层 `backend/src/foo.service.ts`（`**` 至少要一层子目录）。如果未来有 service 直接放 `backend/src/` 顶层，hook 会**漏检** access-check。

当前所有 service 文件都在子目录下，未实际触发——但属于隐性约定，建议后续加 pathspec magic 或显式 `:(glob)` 修正。已在 PR 描述提 follow-up。
