Эх сурвалжийг харах

feat: 添加代码健康度检查脚本

scripts/health-check.ts 汇总项目各维度指标:
代码规模、lint 问题、测试结果、冗余代码、构建状态和产物大小。
新增 health script 一键运行。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
claude-code-best 3 долоо хоног өмнө
parent
commit
173d18bea8
3 өөрчлөгдсөн 166 нэмэгдсэн , 2 устгасан
  1. 1 1
      TODO.md
  2. 2 1
      package.json
  3. 163 0
      scripts/health-check.ts

+ 1 - 1
TODO.md

@@ -20,6 +20,6 @@
 - [x] 代码格式化与校验
 - [x] 冗余代码检查
 - [x] git hook 的配置
-- [ ] 代码健康度检查
+- [x] 代码健康度检查
 - [x] 单元测试基础设施搭建 (test runner 配置)
 - [x] CI/CD 流水线 (GitHub Actions)

+ 2 - 1
package.json

@@ -18,7 +18,8 @@
         "format": "biome format --write src/",
         "prepare": "git config core.hooksPath .githooks",
         "test": "bun test",
-        "check:unused": "knip-bun"
+        "check:unused": "knip-bun",
+        "health": "bun run scripts/health-check.ts"
     },
     "dependencies": {
         "@alcalzone/ansi-tokenize": "^0.3.0",

+ 163 - 0
scripts/health-check.ts

@@ -0,0 +1,163 @@
+#!/usr/bin/env bun
+/**
+ * 代码健康度检查脚本
+ *
+ * 汇总项目各维度指标,输出健康度报告:
+ * - 代码规模(文件数、代码行数)
+ * - Lint 问题数(Biome)
+ * - 测试结果(Bun test)
+ * - 冗余代码(Knip)
+ * - 构建状态
+ */
+
+import { $ } from "bun";
+
+const DIVIDER = "─".repeat(60);
+
+interface Metric {
+	label: string;
+	value: string | number;
+	status: "ok" | "warn" | "error" | "info";
+}
+
+const metrics: Metric[] = [];
+
+function add(label: string, value: string | number, status: Metric["status"] = "info") {
+	metrics.push({ label, value, status });
+}
+
+function icon(status: Metric["status"]): string {
+	switch (status) {
+		case "ok":
+			return "[OK]";
+		case "warn":
+			return "[!!]";
+		case "error":
+			return "[XX]";
+		case "info":
+			return "[--]";
+	}
+}
+
+// ---------------------------------------------------------------------------
+// 1. 代码规模
+// ---------------------------------------------------------------------------
+async function checkCodeSize() {
+	const tsFiles = await $`find src -name '*.ts' -o -name '*.tsx' | grep -v node_modules`.text();
+	const fileCount = tsFiles.trim().split("\n").filter(Boolean).length;
+	add("TypeScript 文件数", fileCount, "info");
+
+	const loc = await $`find src -name '*.ts' -o -name '*.tsx' | grep -v node_modules | xargs wc -l | tail -1`.text();
+	const totalLines = loc.trim().split(/\s+/)[0] ?? "?";
+	add("总代码行数 (src/)", totalLines, "info");
+}
+
+// ---------------------------------------------------------------------------
+// 2. Lint 检查
+// ---------------------------------------------------------------------------
+async function checkLint() {
+	try {
+		const result = await $`bunx biome check src/ 2>&1`.quiet().nothrow().text();
+		const errorMatch = result.match(/Found (\d+) errors?/);
+		const warnMatch = result.match(/Found (\d+) warnings?/);
+		const errors = errorMatch ? Number.parseInt(errorMatch[1]) : 0;
+		const warnings = warnMatch ? Number.parseInt(warnMatch[1]) : 0;
+		add("Lint 错误", errors, errors === 0 ? "ok" : errors < 100 ? "warn" : "info");
+		add("Lint 警告", warnings, warnings === 0 ? "ok" : "info");
+	} catch {
+		add("Lint 检查", "执行失败", "error");
+	}
+}
+
+// ---------------------------------------------------------------------------
+// 3. 测试
+// ---------------------------------------------------------------------------
+async function checkTests() {
+	try {
+		const result = await $`bun test 2>&1`.quiet().nothrow().text();
+		const passMatch = result.match(/(\d+) pass/);
+		const failMatch = result.match(/(\d+) fail/);
+		const pass = passMatch ? Number.parseInt(passMatch[1]) : 0;
+		const fail = failMatch ? Number.parseInt(failMatch[1]) : 0;
+		add("测试通过", pass, pass > 0 ? "ok" : "warn");
+		add("测试失败", fail, fail === 0 ? "ok" : "error");
+	} catch {
+		add("测试", "执行失败", "error");
+	}
+}
+
+// ---------------------------------------------------------------------------
+// 4. 冗余代码
+// ---------------------------------------------------------------------------
+async function checkUnused() {
+	try {
+		const result = await $`bunx knip-bun 2>&1`.quiet().nothrow().text();
+		const unusedFiles = result.match(/Unused files \((\d+)\)/);
+		const unusedExports = result.match(/Unused exports \((\d+)\)/);
+		const unusedDeps = result.match(/Unused dependencies \((\d+)\)/);
+		add("未使用文件", unusedFiles?.[1] ?? "0", "info");
+		add("未使用导出", unusedExports?.[1] ?? "0", "info");
+		add("未使用依赖", unusedDeps?.[1] ?? "0", unusedDeps && Number(unusedDeps[1]) > 0 ? "warn" : "ok");
+	} catch {
+		add("冗余代码检查", "执行失败", "error");
+	}
+}
+
+// ---------------------------------------------------------------------------
+// 5. 构建
+// ---------------------------------------------------------------------------
+async function checkBuild() {
+	try {
+		const result = await $`bun run build 2>&1`.quiet().nothrow();
+		if (result.exitCode === 0) {
+			// 获取产物大小
+			const stat = Bun.file("dist/cli.js");
+			const mb = (stat.size / 1024 / 1024).toFixed(1);
+			const size = `${mb} MB`;
+			add("构建状态", "成功", "ok");
+			add("产物大小 (dist/cli.js)", size, "info");
+		} else {
+			add("构建状态", "失败", "error");
+		}
+	} catch {
+		add("构建", "执行失败", "error");
+	}
+}
+
+// ---------------------------------------------------------------------------
+// Run
+// ---------------------------------------------------------------------------
+console.log("");
+console.log(DIVIDER);
+console.log("  代码健康度检查报告");
+console.log(`  ${new Date().toLocaleString("zh-CN")}`);
+console.log(DIVIDER);
+
+await checkCodeSize();
+await checkLint();
+await checkTests();
+await checkUnused();
+await checkBuild();
+
+console.log("");
+for (const m of metrics) {
+	const tag = icon(m.status);
+	console.log(`  ${tag}  ${m.label.padEnd(20)} ${m.value}`);
+}
+
+const errorCount = metrics.filter((m) => m.status === "error").length;
+const warnCount = metrics.filter((m) => m.status === "warn").length;
+
+console.log("");
+console.log(DIVIDER);
+if (errorCount > 0) {
+	console.log(`  结果: ${errorCount} 个错误, ${warnCount} 个警告`);
+} else if (warnCount > 0) {
+	console.log(`  结果: 无错误, ${warnCount} 个警告`);
+} else {
+	console.log("  结果: 全部通过");
+}
+console.log(DIVIDER);
+console.log("");
+
+process.exit(errorCount > 0 ? 1 : 0);