#!/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);