Skip to content

Commit 173d18b

Browse files
feat: 添加代码健康度检查脚本
scripts/health-check.ts 汇总项目各维度指标: 代码规模、lint 问题、测试结果、冗余代码、构建状态和产物大小。 新增 health script 一键运行。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent c587a64 commit 173d18b

3 files changed

Lines changed: 166 additions & 2 deletions

File tree

TODO.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,6 @@
2020
- [x] 代码格式化与校验
2121
- [x] 冗余代码检查
2222
- [x] git hook 的配置
23-
- [ ] 代码健康度检查
23+
- [x] 代码健康度检查
2424
- [x] 单元测试基础设施搭建 (test runner 配置)
2525
- [x] CI/CD 流水线 (GitHub Actions)

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
"format": "biome format --write src/",
1919
"prepare": "git config core.hooksPath .githooks",
2020
"test": "bun test",
21-
"check:unused": "knip-bun"
21+
"check:unused": "knip-bun",
22+
"health": "bun run scripts/health-check.ts"
2223
},
2324
"dependencies": {
2425
"@alcalzone/ansi-tokenize": "^0.3.0",

scripts/health-check.ts

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
#!/usr/bin/env bun
2+
/**
3+
* 代码健康度检查脚本
4+
*
5+
* 汇总项目各维度指标,输出健康度报告:
6+
* - 代码规模(文件数、代码行数)
7+
* - Lint 问题数(Biome)
8+
* - 测试结果(Bun test)
9+
* - 冗余代码(Knip)
10+
* - 构建状态
11+
*/
12+
13+
import { $ } from "bun";
14+
15+
const DIVIDER = "─".repeat(60);
16+
17+
interface Metric {
18+
label: string;
19+
value: string | number;
20+
status: "ok" | "warn" | "error" | "info";
21+
}
22+
23+
const metrics: Metric[] = [];
24+
25+
function add(label: string, value: string | number, status: Metric["status"] = "info") {
26+
metrics.push({ label, value, status });
27+
}
28+
29+
function icon(status: Metric["status"]): string {
30+
switch (status) {
31+
case "ok":
32+
return "[OK]";
33+
case "warn":
34+
return "[!!]";
35+
case "error":
36+
return "[XX]";
37+
case "info":
38+
return "[--]";
39+
}
40+
}
41+
42+
// ---------------------------------------------------------------------------
43+
// 1. 代码规模
44+
// ---------------------------------------------------------------------------
45+
async function checkCodeSize() {
46+
const tsFiles = await $`find src -name '*.ts' -o -name '*.tsx' | grep -v node_modules`.text();
47+
const fileCount = tsFiles.trim().split("\n").filter(Boolean).length;
48+
add("TypeScript 文件数", fileCount, "info");
49+
50+
const loc = await $`find src -name '*.ts' -o -name '*.tsx' | grep -v node_modules | xargs wc -l | tail -1`.text();
51+
const totalLines = loc.trim().split(/\s+/)[0] ?? "?";
52+
add("总代码行数 (src/)", totalLines, "info");
53+
}
54+
55+
// ---------------------------------------------------------------------------
56+
// 2. Lint 检查
57+
// ---------------------------------------------------------------------------
58+
async function checkLint() {
59+
try {
60+
const result = await $`bunx biome check src/ 2>&1`.quiet().nothrow().text();
61+
const errorMatch = result.match(/Found (\d+) errors?/);
62+
const warnMatch = result.match(/Found (\d+) warnings?/);
63+
const errors = errorMatch ? Number.parseInt(errorMatch[1]) : 0;
64+
const warnings = warnMatch ? Number.parseInt(warnMatch[1]) : 0;
65+
add("Lint 错误", errors, errors === 0 ? "ok" : errors < 100 ? "warn" : "info");
66+
add("Lint 警告", warnings, warnings === 0 ? "ok" : "info");
67+
} catch {
68+
add("Lint 检查", "执行失败", "error");
69+
}
70+
}
71+
72+
// ---------------------------------------------------------------------------
73+
// 3. 测试
74+
// ---------------------------------------------------------------------------
75+
async function checkTests() {
76+
try {
77+
const result = await $`bun test 2>&1`.quiet().nothrow().text();
78+
const passMatch = result.match(/(\d+) pass/);
79+
const failMatch = result.match(/(\d+) fail/);
80+
const pass = passMatch ? Number.parseInt(passMatch[1]) : 0;
81+
const fail = failMatch ? Number.parseInt(failMatch[1]) : 0;
82+
add("测试通过", pass, pass > 0 ? "ok" : "warn");
83+
add("测试失败", fail, fail === 0 ? "ok" : "error");
84+
} catch {
85+
add("测试", "执行失败", "error");
86+
}
87+
}
88+
89+
// ---------------------------------------------------------------------------
90+
// 4. 冗余代码
91+
// ---------------------------------------------------------------------------
92+
async function checkUnused() {
93+
try {
94+
const result = await $`bunx knip-bun 2>&1`.quiet().nothrow().text();
95+
const unusedFiles = result.match(/Unused files \((\d+)\)/);
96+
const unusedExports = result.match(/Unused exports \((\d+)\)/);
97+
const unusedDeps = result.match(/Unused dependencies \((\d+)\)/);
98+
add("未使用文件", unusedFiles?.[1] ?? "0", "info");
99+
add("未使用导出", unusedExports?.[1] ?? "0", "info");
100+
add("未使用依赖", unusedDeps?.[1] ?? "0", unusedDeps && Number(unusedDeps[1]) > 0 ? "warn" : "ok");
101+
} catch {
102+
add("冗余代码检查", "执行失败", "error");
103+
}
104+
}
105+
106+
// ---------------------------------------------------------------------------
107+
// 5. 构建
108+
// ---------------------------------------------------------------------------
109+
async function checkBuild() {
110+
try {
111+
const result = await $`bun run build 2>&1`.quiet().nothrow();
112+
if (result.exitCode === 0) {
113+
// 获取产物大小
114+
const stat = Bun.file("dist/cli.js");
115+
const mb = (stat.size / 1024 / 1024).toFixed(1);
116+
const size = `${mb} MB`;
117+
add("构建状态", "成功", "ok");
118+
add("产物大小 (dist/cli.js)", size, "info");
119+
} else {
120+
add("构建状态", "失败", "error");
121+
}
122+
} catch {
123+
add("构建", "执行失败", "error");
124+
}
125+
}
126+
127+
// ---------------------------------------------------------------------------
128+
// Run
129+
// ---------------------------------------------------------------------------
130+
console.log("");
131+
console.log(DIVIDER);
132+
console.log(" 代码健康度检查报告");
133+
console.log(` ${new Date().toLocaleString("zh-CN")}`);
134+
console.log(DIVIDER);
135+
136+
await checkCodeSize();
137+
await checkLint();
138+
await checkTests();
139+
await checkUnused();
140+
await checkBuild();
141+
142+
console.log("");
143+
for (const m of metrics) {
144+
const tag = icon(m.status);
145+
console.log(` ${tag} ${m.label.padEnd(20)} ${m.value}`);
146+
}
147+
148+
const errorCount = metrics.filter((m) => m.status === "error").length;
149+
const warnCount = metrics.filter((m) => m.status === "warn").length;
150+
151+
console.log("");
152+
console.log(DIVIDER);
153+
if (errorCount > 0) {
154+
console.log(` 结果: ${errorCount} 个错误, ${warnCount} 个警告`);
155+
} else if (warnCount > 0) {
156+
console.log(` 结果: 无错误, ${warnCount} 个警告`);
157+
} else {
158+
console.log(" 结果: 全部通过");
159+
}
160+
console.log(DIVIDER);
161+
console.log("");
162+
163+
process.exit(errorCount > 0 ? 1 : 0);

0 commit comments

Comments
 (0)