## 缺陷 `server` 的 `test:coverage` 只写了阈值,没有 include/exclude。某些开发机 (IDE / 代理 / 沙箱工具)会在 Node 的 `require` 链上注入 `*.cjs` 钩子,这些 **项目外**文件被覆盖率跟踪器一并统计: ``` # broker-ipc-client.cjs | 24.76 # node-brokered-fs-shim.cjs | 50.13 # node-language-shim.cjs | 82.86 # node-safe-delete-shim.cjs | 41.91 # all files | 76.56 ← 被稀释 10+ 个点 ``` 危害是双向的: ① 换机器/换环境数字就变,**不能作为交付证明**; ② 60% 门禁在拿被稀释的分母判定 —— `server.js` 真掉 20 个点可能被这些 shim 抵消 而不报警。 ## 修法(用 include 白名单,而不是逐个 exclude 环境特有文件名) ```json "test:coverage": "node --test --experimental-test-coverage \ --test-coverage-include='**/server/*.js' \ --test-coverage-exclude='**/node_modules/**' \ --test-coverage-exclude='test/**' \ --test-coverage-lines=60 test/*.test.mjs" ``` `include` 表达的是正确语义:「只统计本项目源码」。逐个 exclude 环境特有的 `*.cjs` 名字换台机器就会再冒出来,不解决问题。 ## 三条验收(逐条实测) 1. **报告内无项目外文件**:覆盖率报告的文件列只有 10 个项目源码 (config / crypto / db / errors / logger / migrate / ratelimit / server / tokens / validate .js),`grep -E "Applications|/Users/|node_modules|\.cjs"` 对报告段落 零命中。 2. **数字可复现**:连跑 3 次,`all files` 均为 `89.29 | 70.85 | 88.65`。 3. **门禁真实会 fail**:阈值临时调到 99 复跑 → `exit=1` 并打印 `Error: 89.29% line coverage does not meet threshold of 99%.` (改回 60 后恢复正常) ## 连带 - `.github/workflows/ci.yml`:把口径写进覆盖率那一步的注释(为什么用 include、 为什么不能靠 exclude 兜底),本地与 CI 从此同一口径。 - `README.md`:覆盖率与测试数全部换成实测值 —— server **89.29%**(原 75.31% 是被污染的数字)、launcher **91.67%**(原 91.06%); server 测试 **52**(原 43)、launcher 测试 **451**(原 390); 并新增「覆盖率口径为何用 include 而不是 exclude」小节,把这次事故写清楚。
119 lines
4.3 KiB
YAML
119 lines
4.3 KiB
YAML
# Only in One - CI(R-P1-23)
|
||
#
|
||
# 门禁顺序刻意「先快后慢、先静态后动态」:
|
||
# 1. 类型检查 + Lint + Prettier 格式校验(秒级失败,最快给出反馈)
|
||
# 2. launcher 单测 + 覆盖率门禁(含核心四模块 ≥70%)
|
||
# 3. server 单测 + 覆盖率门禁(≥60%)
|
||
# 4. 冒烟测试(真实起服务跑全链路)
|
||
# 5. 残留进程检查(防止冒烟测试漏下常驻进程)
|
||
#
|
||
# 注意:本工作流**不引入**任何密钥;server 的 JWT_SECRET 由 smoke.mjs 内部随机生成。
|
||
name: ci
|
||
|
||
on:
|
||
push:
|
||
branches: ['**']
|
||
pull_request:
|
||
|
||
concurrency:
|
||
group: ci-${{ github.ref }}
|
||
cancel-in-progress: true
|
||
|
||
jobs:
|
||
verify:
|
||
name: typecheck + tests + coverage + smoke
|
||
runs-on: ubuntu-latest
|
||
timeout-minutes: 15
|
||
|
||
steps:
|
||
- name: Checkout
|
||
uses: actions/checkout@v4
|
||
|
||
- name: Setup Node
|
||
uses: actions/setup-node@v4
|
||
with:
|
||
node-version: '22'
|
||
|
||
- name: Print toolchain versions
|
||
run: |
|
||
node -v
|
||
npm -v
|
||
|
||
# ---------------- 依赖(两个子项目各自独立)----------------
|
||
- name: Install server deps
|
||
working-directory: server
|
||
run: npm ci
|
||
|
||
- name: Install launcher deps
|
||
working-directory: launcher
|
||
run: npm ci --ignore-scripts
|
||
|
||
# ---------------- 1) 类型检查 + Lint + 格式 ----------------
|
||
# `tsconfig.typecheck.json` 同时覆盖 src 与 test(主 tsconfig.json 只含 src,
|
||
# 属 build 用途),因此测试文件的类型漂移也会在这里被拦住。
|
||
- name: Typecheck launcher (src + test)
|
||
working-directory: launcher
|
||
run: npm run typecheck
|
||
|
||
# lint 首次实测 9.07s、二次 7.28s(< 10s 阈值,故与 typecheck 同列,不拆 lint:ci)。
|
||
# 见 docs/packaging.md「代码规范与静态检查」。
|
||
- name: Lint launcher
|
||
working-directory: launcher
|
||
run: npm run lint
|
||
|
||
- name: Prettier format check
|
||
working-directory: launcher
|
||
run: npm run format:check
|
||
|
||
# ---------------- 2) launcher 单测 + 覆盖率门禁 ----------------
|
||
# thresholds 写在 launcher/vitest.config.ts:
|
||
# · 全局 lines ≥60%
|
||
# · connect / discover / usb / agent / share / platform/* ≥70%
|
||
# 低于阈值时 vitest 以非 0 退出,CI 直接失败。
|
||
- name: Launcher tests + coverage gate
|
||
working-directory: launcher
|
||
run: npm run test:coverage
|
||
|
||
# ---------------- 3) server 单测 + 覆盖率门禁 ----------------
|
||
- name: Server tests
|
||
working-directory: server
|
||
run: npm test
|
||
|
||
# 口径写在 server/package.json 的 test:coverage 里:
|
||
# --test-coverage-include='**/server/*.js' ← 只统计本项目源码
|
||
# --test-coverage-exclude='**/node_modules/**'
|
||
# --test-coverage-exclude='test/**'
|
||
# 为什么不靠 exclude 兜底:本地开发机上运行时的 require 钩子(如 IDE / 代理
|
||
# 工具注入的 *.cjs shim)会被覆盖率跟踪器一起统计,把分母稀释掉 10+ 个点,
|
||
# 于是「server.js 掉 20 个点」可能被 shim 抵消而不报警。用 include 白名单后
|
||
# 数字与机器无关、可复现(本地与 CI 同一口径)。
|
||
- name: Server coverage gate (lines >= 60%)
|
||
working-directory: server
|
||
run: npm run test:coverage
|
||
|
||
# ---------------- 4) 冒烟测试(真实起服务 + 全链路)----------------
|
||
- name: Smoke test
|
||
working-directory: server
|
||
run: npm run smoke
|
||
|
||
# ---------------- 5) 残留进程检查 ----------------
|
||
# smoke.mjs 会在结束时 close server + exit;若有残留说明清理逻辑坏了。
|
||
- name: Assert no leftover processes
|
||
run: |
|
||
if pgrep -f 'smoke\.mjs' >/dev/null 2>&1; then
|
||
echo '::error::smoke.mjs 退出后仍有残留进程'
|
||
pgrep -af 'smoke\.mjs' || true
|
||
exit 1
|
||
fi
|
||
echo 'no leftover process'
|
||
|
||
# 覆盖率报告作为构建产物留存,便于事后排查(不参与判定)
|
||
- name: Upload launcher coverage
|
||
if: always()
|
||
uses: actions/upload-artifact@v4
|
||
with:
|
||
name: launcher-coverage
|
||
path: launcher/coverage/
|
||
if-no-files-found: ignore
|
||
retention-days: 7
|