初始化
Some checks failed
CI/CD Pipeline / 测试 (18.x) (push) Has been cancelled
CI/CD Pipeline / 测试 (20.x) (push) Has been cancelled
CI/CD Pipeline / 安全检查 (push) Has been cancelled
CI/CD Pipeline / 部署 (push) Has been cancelled
CI/CD Pipeline / 通知 (push) Has been cancelled

This commit is contained in:
2025-11-03 19:47:36 +08:00
parent 7a04b85667
commit f25b0307db
454 changed files with 37064 additions and 4544 deletions

View File

@@ -0,0 +1,443 @@
#!/usr/bin/env node
const fs = require('fs')
const path = require('path')
const { execSync } = require('child_process')
// 创建报告目录
const reportDir = path.resolve(process.cwd(), 'quality-report')
if (!fs.existsSync(reportDir)) {
fs.mkdirSync(reportDir, { recursive: true })
}
// 生成时间戳
const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
const reportFile = path.join(reportDir, `quality-report-${timestamp}.html`)
// 报告模板
const reportTemplate = `
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>代码质量报告</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
line-height: 1.6;
color: #333;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
h1, h2, h3 {
color: #2c3e50;
}
.header {
border-bottom: 1px solid #eee;
padding-bottom: 20px;
margin-bottom: 30px;
}
.section {
margin-bottom: 40px;
}
.metric {
display: flex;
justify-content: space-between;
padding: 10px 0;
border-bottom: 1px solid #f0f0f0;
}
.metric-name {
font-weight: 500;
}
.metric-value {
font-weight: bold;
}
.success {
color: #27ae60;
}
.warning {
color: #f39c12;
}
.error {
color: #e74c3c;
}
.progress-bar {
width: 100%;
height: 20px;
background-color: #f0f0f0;
border-radius: 10px;
overflow: hidden;
margin: 10px 0;
}
.progress-fill {
height: 100%;
transition: width 0.3s ease;
}
.progress-success {
background-color: #27ae60;
}
.progress-warning {
background-color: #f39c12;
}
.progress-error {
background-color: #e74c3c;
}
.code-block {
background-color: #f8f9fa;
border-radius: 4px;
padding: 16px;
overflow-x: auto;
font-family: Consolas, Monaco, 'Andale Mono', monospace;
font-size: 14px;
line-height: 1.45;
}
.summary {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
gap: 20px;
}
.summary-card {
flex: 1;
min-width: 200px;
background-color: #f8f9fa;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.summary-card h3 {
margin-top: 0;
color: #495057;
}
.summary-value {
font-size: 2rem;
font-weight: bold;
margin: 10px 0;
}
.footer {
margin-top: 40px;
padding-top: 20px;
border-top: 1px solid #eee;
font-size: 14px;
color: #6c757d;
text-align: center;
}
</style>
</head>
<body>
<div class="header">
<h1>代码质量报告</h1>
<p>生成时间: ${new Date().toLocaleString('zh-CN')}</p>
</div>
<div class="section">
<h2>概览</h2>
<div class="summary">
<div class="summary-card">
<h3>测试覆盖率</h3>
<div class="summary-value" id="test-coverage">-</div>
<p>代码行覆盖率</p>
</div>
<div class="summary-card">
<h3>ESLint 问题</h3>
<div class="summary-value" id="eslint-issues">-</div>
<p>代码风格问题</p>
</div>
<div class="summary-card">
<h3>类型错误</h3>
<div class="summary-value" id="type-errors">-</div>
<p>TypeScript 类型错误</p>
</div>
<div class="summary-card">
<h3>安全漏洞</h3>
<div class="summary-value" id="security-vulnerabilities">-</div>
<p>npm audit 结果</p>
</div>
</div>
</div>
<div class="section">
<h2>测试覆盖率</h2>
<div id="coverage-details"></div>
</div>
<div class="section">
<h2>代码风格检查</h2>
<div id="eslint-details"></div>
</div>
<div class="section">
<h2>类型检查</h2>
<div id="type-check-details"></div>
</div>
<div class="section">
<h2>安全检查</h2>
<div id="security-details"></div>
</div>
<div class="footer">
<p>此报告由自动化代码质量检查工具生成</p>
</div>
</body>
</html>
`
// 写入报告文件
fs.writeFileSync(reportFile, reportTemplate)
console.log(`✅ 代码质量报告已生成: ${reportFile}`)
// 运行各种检查并更新报告
async function generateQualityReport() {
try {
// 1. 运行测试覆盖率
console.log('🔍 运行测试覆盖率...')
try {
execSync('npm run test:unit:coverage', { stdio: 'pipe' })
const coverageSummary = JSON.parse(fs.readFileSync('coverage/coverage-summary.json', 'utf8'))
updateTestCoverage(coverageSummary)
} catch (error) {
console.error('❌ 测试覆盖率检查失败:', error.message)
updateTestCoverage(null, error.message)
}
// 2. 运行 ESLint
console.log('🔍 运行 ESLint...')
try {
const eslintOutput = execSync('npm run lint -- --format=json', { stdio: 'pipe' }).toString()
const eslintResults = JSON.parse(eslintOutput)
updateESLintResults(eslintResults)
} catch (error) {
console.error('❌ ESLint 检查失败:', error.message)
updateESLintResults(null, error.message)
}
// 3. 运行类型检查
console.log('🔍 运行类型检查...')
try {
execSync('npm run type-check', { stdio: 'pipe' })
updateTypeCheckResults(true)
} catch (error) {
console.error('❌ 类型检查失败:', error.message)
updateTypeCheckResults(false, error.message)
}
// 4. 运行安全审计
console.log('🔍 运行安全审计...')
try {
const auditOutput = execSync('npm audit --json', { stdio: 'pipe' }).toString()
const auditResults = JSON.parse(auditOutput)
updateSecurityResults(auditResults)
} catch (error) {
console.error('❌ 安全审计失败:', error.message)
updateSecurityResults(null, error.message)
}
console.log('✅ 代码质量报告更新完成')
} catch (error) {
console.error('❌ 生成代码质量报告时出错:', error)
}
}
// 更新测试覆盖率信息
function updateTestCoverage(coverageSummary, error) {
let html = ''
if (error) {
html = `
<div class="metric">
<span class="metric-name">状态</span>
<span class="metric-value error">检查失败</span>
</div>
<div class="code-block">${error}</div>
`
} else {
const { lines, functions, branches, statements } = coverageSummary.total
const linesPercent = parseFloat(lines.pct)
html = `
<div class="metric">
<span class="metric-name">行覆盖率</span>
<span class="metric-value ${linesPercent >= 80 ? 'success' : linesPercent >= 60 ? 'warning' : 'error'}">${lines.pct}%</span>
</div>
<div class="progress-bar">
<div class="progress-fill ${linesPercent >= 80 ? 'progress-success' : linesPercent >= 60 ? 'progress-warning' : 'progress-error'}" style="width: ${lines.pct}%"></div>
</div>
<div class="metric">
<span class="metric-name">函数覆盖率</span>
<span class="metric-value">${functions.pct}%</span>
</div>
<div class="metric">
<span class="metric-name">分支覆盖率</span>
<span class="metric-value">${branches.pct}%</span>
</div>
<div class="metric">
<span class="metric-name">语句覆盖率</span>
<span class="metric-value">${statements.pct}%</span>
</div>
`
// 更新概览
updateOverview('test-coverage', lines.pct + '%', linesPercent >= 80 ? 'success' : linesPercent >= 60 ? 'warning' : 'error')
}
updateReportSection('coverage-details', html)
}
// 更新 ESLint 结果
function updateESLintResults(eslintResults, error) {
let html = ''
if (error) {
html = `
<div class="metric">
<span class="metric-name">状态</span>
<span class="metric-value error">检查失败</span>
</div>
<div class="code-block">${error}</div>
`
} else {
let totalErrors = 0
let totalWarnings = 0
eslintResults.forEach(file => {
totalErrors += file.errorCount
totalWarnings += file.warningCount
})
const status = totalErrors === 0 && totalWarnings === 0 ? 'success' : totalErrors === 0 ? 'warning' : 'error'
html = `
<div class="metric">
<span class="metric-name">错误</span>
<span class="metric-value ${totalErrors === 0 ? 'success' : 'error'}">${totalErrors}</span>
</div>
<div class="metric">
<span class="metric-name">警告</span>
<span class="metric-value ${totalWarnings === 0 ? 'success' : 'warning'}">${totalWarnings}</span>
</div>
<div class="metric">
<span class="metric-name">状态</span>
<span class="metric-value ${status}">${totalErrors === 0 && totalWarnings === 0 ? '通过' : totalErrors === 0 ? '警告' : '失败'}</span>
</div>
`
// 更新概览
updateOverview('eslint-issues', totalErrors + totalWarnings, status)
}
updateReportSection('eslint-details', html)
}
// 更新类型检查结果
function updateTypeCheckResults(success, error) {
let html = ''
if (success) {
html = `
<div class="metric">
<span class="metric-name">状态</span>
<span class="metric-value success">通过</span>
</div>
<div class="metric">
<span class="metric-name">类型错误</span>
<span class="metric-value success">0</span>
</div>
`
// 更新概览
updateOverview('type-errors', '0', 'success')
} else {
html = `
<div class="metric">
<span class="metric-name">状态</span>
<span class="metric-value error">失败</span>
</div>
<div class="code-block">${error}</div>
`
// 更新概览
updateOverview('type-errors', '>', 'error')
}
updateReportSection('type-check-details', html)
}
// 更新安全检查结果
function updateSecurityResults(auditResults, error) {
let html = ''
if (error) {
html = `
<div class="metric">
<span class="metric-name">状态</span>
<span class="metric-value error">检查失败</span>
</div>
<div class="code-block">${error}</div>
`
// 更新概览
updateOverview('security-vulnerabilities', '?', 'error')
} else {
const { vulnerabilities } = auditResults.metadata
const { low, moderate, high, critical } = vulnerabilities
const totalVulns = low + moderate + high + critical
const status = critical > 0 || high > 0 ? 'error' : moderate > 0 ? 'warning' : 'success'
html = `
<div class="metric">
<span class="metric-name">严重</span>
<span class="metric-value ${critical === 0 ? 'success' : 'error'}">${critical}</span>
</div>
<div class="metric">
<span class="metric-name">高危</span>
<span class="metric-value ${high === 0 ? 'success' : 'error'}">${high}</span>
</div>
<div class="metric">
<span class="metric-name">中危</span>
<span class="metric-value ${moderate === 0 ? 'success' : 'warning'}">${moderate}</span>
</div>
<div class="metric">
<span class="metric-name">低危</span>
<span class="metric-value ${low === 0 ? 'success' : 'warning'}">${low}</span>
</div>
<div class="metric">
<span class="metric-name">总计</span>
<span class="metric-value ${status}">${totalVulns}</span>
</div>
`
// 更新概览
updateOverview('security-vulnerabilities', totalVulns, status)
}
updateReportSection('security-details', html)
}
// 更新概览
function updateOverview(id, value, status) {
const elementId = id
const className = status === 'success' ? 'success' : status === 'warning' ? 'warning' : 'error'
// 这里需要使用 DOM 操作,但在 Node.js 环境中无法直接操作 HTML
// 实际实现中可以使用 cheerio 或其他 HTML 解析库
console.log(`更新概览 ${id}: ${value} (${status})`)
}
// 更新报告部分
function updateReportSection(id, html) {
// 这里需要使用 DOM 操作,但在 Node.js 环境中无法直接操作 HTML
// 实际实现中可以使用 cheerio 或其他 HTML 解析库
console.log(`更新报告部分 ${id}`)
}
// 运行报告生成
generateQualityReport()
module.exports = {
generateQualityReport
}