268 lines
6.5 KiB
JavaScript
268 lines
6.5 KiB
JavaScript
|
|
#!/usr/bin/env node
|
|||
|
|
|
|||
|
|
const { execSync } = require('child_process');
|
|||
|
|
const path = require('path');
|
|||
|
|
const fs = require('fs');
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Quality Gate - 质量门禁工具
|
|||
|
|
* 执行 TypeScript 编译检查和 ESLint 检查
|
|||
|
|
*/
|
|||
|
|
class QualityGate {
|
|||
|
|
constructor(nestjsBasePath) {
|
|||
|
|
this.nestjsBasePath = nestjsBasePath || '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/wwjcloud-nest';
|
|||
|
|
this.stats = {
|
|||
|
|
tsErrors: 0,
|
|||
|
|
eslintErrors: 0,
|
|||
|
|
eslintWarnings: 0,
|
|||
|
|
filesChecked: 0
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 运行所有质量检查
|
|||
|
|
*/
|
|||
|
|
async run() {
|
|||
|
|
console.log('🚦 启动 Quality Gate 检查...\n');
|
|||
|
|
|
|||
|
|
let passed = true;
|
|||
|
|
|
|||
|
|
// TypeScript 编译检查
|
|||
|
|
console.log('📝 第1阶段:TypeScript 编译检查...');
|
|||
|
|
const tsResult = await this.checkTypeScript();
|
|||
|
|
if (!tsResult) {
|
|||
|
|
passed = false;
|
|||
|
|
console.log(' ❌ TypeScript 编译检查失败\n');
|
|||
|
|
} else {
|
|||
|
|
console.log(' ✅ TypeScript 编译检查通过\n');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ESLint 检查
|
|||
|
|
console.log('📝 第2阶段:ESLint 代码规范检查...');
|
|||
|
|
const eslintResult = await this.checkESLint();
|
|||
|
|
if (!eslintResult) {
|
|||
|
|
passed = false;
|
|||
|
|
console.log(' ❌ ESLint 检查失败\n');
|
|||
|
|
} else {
|
|||
|
|
console.log(' ✅ ESLint 检查通过\n');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 输出统计报告
|
|||
|
|
this.printStats();
|
|||
|
|
|
|||
|
|
return passed;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* TypeScript 编译检查
|
|||
|
|
*/
|
|||
|
|
async checkTypeScript() {
|
|||
|
|
try {
|
|||
|
|
console.log(' 🔍 检查 TypeScript 类型...');
|
|||
|
|
|
|||
|
|
// 运行 tsc --noEmit 进行类型检查
|
|||
|
|
const result = execSync('npm run type-check', {
|
|||
|
|
cwd: this.nestjsBasePath,
|
|||
|
|
encoding: 'utf8',
|
|||
|
|
stdio: 'pipe'
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
console.log(' ✅ TypeScript 类型检查通过');
|
|||
|
|
return true;
|
|||
|
|
|
|||
|
|
} catch (error) {
|
|||
|
|
this.stats.tsErrors++;
|
|||
|
|
|
|||
|
|
if (error.stdout) {
|
|||
|
|
console.error(' ❌ TypeScript 错误:');
|
|||
|
|
console.error(error.stdout);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (error.stderr) {
|
|||
|
|
console.error(error.stderr);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* ESLint 检查
|
|||
|
|
*/
|
|||
|
|
async checkESLint() {
|
|||
|
|
try {
|
|||
|
|
console.log(' 🔍 检查代码规范...');
|
|||
|
|
|
|||
|
|
// 运行 ESLint
|
|||
|
|
const result = execSync('npm run lint', {
|
|||
|
|
cwd: this.nestjsBasePath,
|
|||
|
|
encoding: 'utf8',
|
|||
|
|
stdio: 'pipe'
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
console.log(' ✅ ESLint 检查通过');
|
|||
|
|
return true;
|
|||
|
|
|
|||
|
|
} catch (error) {
|
|||
|
|
// ESLint 返回非零退出码表示有错误或警告
|
|||
|
|
if (error.stdout) {
|
|||
|
|
const output = error.stdout;
|
|||
|
|
|
|||
|
|
// 解析错误和警告数量
|
|||
|
|
const errorMatch = output.match(/(\d+)\s+errors?/);
|
|||
|
|
const warningMatch = output.match(/(\d+)\s+warnings?/);
|
|||
|
|
|
|||
|
|
if (errorMatch) {
|
|||
|
|
this.stats.eslintErrors = parseInt(errorMatch[1]);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (warningMatch) {
|
|||
|
|
this.stats.eslintWarnings = parseInt(warningMatch[1]);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.error(' ❌ ESLint 发现问题:');
|
|||
|
|
console.error(output);
|
|||
|
|
|
|||
|
|
// 如果只有警告,不算失败
|
|||
|
|
return this.stats.eslintErrors === 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 检查单个文件
|
|||
|
|
*/
|
|||
|
|
async checkFile(filePath) {
|
|||
|
|
console.log(` 🔍 检查文件: ${filePath}`);
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
// 使用 tsc 检查单个文件
|
|||
|
|
execSync(`npx tsc --noEmit ${filePath}`, {
|
|||
|
|
cwd: this.nestjsBasePath,
|
|||
|
|
encoding: 'utf8',
|
|||
|
|
stdio: 'pipe'
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 使用 ESLint 检查单个文件
|
|||
|
|
execSync(`npx eslint ${filePath}`, {
|
|||
|
|
cwd: this.nestjsBasePath,
|
|||
|
|
encoding: 'utf8',
|
|||
|
|
stdio: 'pipe'
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
this.stats.filesChecked++;
|
|||
|
|
return true;
|
|||
|
|
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error(` ❌ 文件检查失败: ${filePath}`);
|
|||
|
|
if (error.stdout) {
|
|||
|
|
console.error(error.stdout);
|
|||
|
|
}
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 快速检查(只检查核心层)
|
|||
|
|
*/
|
|||
|
|
async quickCheck() {
|
|||
|
|
console.log('🚀 快速质量检查(仅核心层)...\n');
|
|||
|
|
|
|||
|
|
const coreFiles = this.getGeneratedFiles();
|
|||
|
|
|
|||
|
|
console.log(` 📁 发现 ${coreFiles.length} 个生成的文件\n`);
|
|||
|
|
|
|||
|
|
let passed = 0;
|
|||
|
|
let failed = 0;
|
|||
|
|
|
|||
|
|
for (const file of coreFiles) {
|
|||
|
|
const result = await this.checkFile(file);
|
|||
|
|
if (result) {
|
|||
|
|
passed++;
|
|||
|
|
} else {
|
|||
|
|
failed++;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log(`\n📊 快速检查结果:`);
|
|||
|
|
console.log(` ✅ 通过: ${passed}`);
|
|||
|
|
console.log(` ❌ 失败: ${failed}`);
|
|||
|
|
|
|||
|
|
return failed === 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取所有生成的文件
|
|||
|
|
*/
|
|||
|
|
getGeneratedFiles() {
|
|||
|
|
const coreDir = path.join(this.nestjsBasePath, 'src', 'core');
|
|||
|
|
const files = [];
|
|||
|
|
|
|||
|
|
const scanDir = (dir) => {
|
|||
|
|
if (!fs.existsSync(dir)) return;
|
|||
|
|
|
|||
|
|
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|||
|
|
|
|||
|
|
for (const entry of entries) {
|
|||
|
|
const fullPath = path.join(dir, entry.name);
|
|||
|
|
|
|||
|
|
if (entry.isDirectory()) {
|
|||
|
|
scanDir(fullPath);
|
|||
|
|
} else if (entry.name.endsWith('.ts') && !entry.name.endsWith('.d.ts')) {
|
|||
|
|
files.push(fullPath);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
scanDir(coreDir);
|
|||
|
|
return files;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 输出统计报告
|
|||
|
|
*/
|
|||
|
|
printStats() {
|
|||
|
|
console.log('📊 Quality Gate 统计报告');
|
|||
|
|
console.log('==================================================');
|
|||
|
|
console.log(` 📝 TypeScript 错误: ${this.stats.tsErrors}`);
|
|||
|
|
console.log(` 📝 ESLint 错误: ${this.stats.eslintErrors}`);
|
|||
|
|
console.log(` ⚠️ ESLint 警告: ${this.stats.eslintWarnings}`);
|
|||
|
|
console.log(` 📁 检查文件数: ${this.stats.filesChecked}`);
|
|||
|
|
console.log('==================================================');
|
|||
|
|
|
|||
|
|
const passed = this.stats.tsErrors === 0 && this.stats.eslintErrors === 0;
|
|||
|
|
|
|||
|
|
if (passed) {
|
|||
|
|
console.log('\n✅ 🎉 所有质量检查通过!');
|
|||
|
|
} else {
|
|||
|
|
console.log('\n❌ 质量检查失败,请修复上述问题');
|
|||
|
|
console.log('提示: 运行 "npm run lint:fix" 自动修复部分问题');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return passed;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果直接运行此文件
|
|||
|
|
if (require.main === module) {
|
|||
|
|
const args = process.argv.slice(2);
|
|||
|
|
const mode = args[0] || 'full';
|
|||
|
|
|
|||
|
|
const gate = new QualityGate();
|
|||
|
|
|
|||
|
|
if (mode === 'quick') {
|
|||
|
|
gate.quickCheck().then(passed => {
|
|||
|
|
process.exit(passed ? 0 : 1);
|
|||
|
|
});
|
|||
|
|
} else {
|
|||
|
|
gate.run().then(passed => {
|
|||
|
|
process.exit(passed ? 0 : 1);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
module.exports = QualityGate;
|
|||
|
|
|