185 lines
4.3 KiB
JavaScript
185 lines
4.3 KiB
JavaScript
|
|
#!/usr/bin/env node
|
|||
|
|
|
|||
|
|
const fs = require('fs');
|
|||
|
|
const path = require('path');
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 基础生成器类
|
|||
|
|
* 提供通用的 dry-run、文件操作、日志等功能
|
|||
|
|
*/
|
|||
|
|
class BaseGenerator {
|
|||
|
|
constructor(generatorName = 'Generator') {
|
|||
|
|
this.generatorName = generatorName;
|
|||
|
|
|
|||
|
|
// 从环境变量或参数读取配置
|
|||
|
|
this.dryRun = process.env.DRY_RUN === 'true' || process.argv.includes('--dry-run');
|
|||
|
|
this.verbose = process.env.VERBOSE === 'true' || process.argv.includes('--verbose');
|
|||
|
|
|
|||
|
|
this.stats = {
|
|||
|
|
filesCreated: 0,
|
|||
|
|
filesUpdated: 0,
|
|||
|
|
filesSkipped: 0,
|
|||
|
|
errors: 0
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 安全写入文件(支持 dry-run)
|
|||
|
|
*/
|
|||
|
|
writeFile(filePath, content, description = '') {
|
|||
|
|
try {
|
|||
|
|
if (this.dryRun) {
|
|||
|
|
console.log(` [DRY-RUN] Would create/update: ${filePath}`);
|
|||
|
|
if (this.verbose && description) {
|
|||
|
|
console.log(` Description: ${description}`);
|
|||
|
|
}
|
|||
|
|
this.stats.filesCreated++;
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 确保目录存在
|
|||
|
|
this.ensureDir(path.dirname(filePath));
|
|||
|
|
|
|||
|
|
// 写入文件
|
|||
|
|
fs.writeFileSync(filePath, content, 'utf8');
|
|||
|
|
|
|||
|
|
const action = fs.existsSync(filePath) ? 'Updated' : 'Created';
|
|||
|
|
console.log(` ✅ ${action}: ${filePath}`);
|
|||
|
|
|
|||
|
|
if (action === 'Created') {
|
|||
|
|
this.stats.filesCreated++;
|
|||
|
|
} else {
|
|||
|
|
this.stats.filesUpdated++;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error(` ❌ Failed to write ${filePath}:`, error.message);
|
|||
|
|
this.stats.errors++;
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 确保目录存在
|
|||
|
|
*/
|
|||
|
|
ensureDir(dirPath) {
|
|||
|
|
if (this.dryRun) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!fs.existsSync(dirPath)) {
|
|||
|
|
fs.mkdirSync(dirPath, { recursive: true });
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 读取文件(安全)
|
|||
|
|
*/
|
|||
|
|
readFile(filePath) {
|
|||
|
|
try {
|
|||
|
|
if (!fs.existsSync(filePath)) {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
return fs.readFileSync(filePath, 'utf8');
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error(` ❌ Failed to read ${filePath}:`, error.message);
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 检查文件是否存在
|
|||
|
|
*/
|
|||
|
|
fileExists(filePath) {
|
|||
|
|
return fs.existsSync(filePath);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 日志输出
|
|||
|
|
*/
|
|||
|
|
log(message, level = 'info') {
|
|||
|
|
const prefix = {
|
|||
|
|
'info': ' ℹ️ ',
|
|||
|
|
'success': ' ✅',
|
|||
|
|
'warning': ' ⚠️ ',
|
|||
|
|
'error': ' ❌',
|
|||
|
|
'debug': ' 🔍'
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
if (level === 'debug' && !this.verbose) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log(`${prefix[level] || ' '}${message}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 输出统计信息
|
|||
|
|
*/
|
|||
|
|
printStats(additionalStats = {}) {
|
|||
|
|
console.log('\n📊 Generation Statistics');
|
|||
|
|
console.log('==================================================');
|
|||
|
|
|
|||
|
|
if (this.dryRun) {
|
|||
|
|
console.log(' 🔍 DRY-RUN MODE - No files were actually modified');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log(` 📁 Files Created: ${this.stats.filesCreated}`);
|
|||
|
|
console.log(` 🔄 Files Updated: ${this.stats.filesUpdated}`);
|
|||
|
|
console.log(` ⏭️ Files Skipped: ${this.stats.filesSkipped}`);
|
|||
|
|
console.log(` ❌ Errors: ${this.stats.errors}`);
|
|||
|
|
|
|||
|
|
// 输出额外的统计信息
|
|||
|
|
for (const [key, value] of Object.entries(additionalStats)) {
|
|||
|
|
console.log(` 📈 ${key}: ${value}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const total = this.stats.filesCreated + this.stats.filesUpdated;
|
|||
|
|
const successRate = total > 0
|
|||
|
|
? ((total / (total + this.stats.errors)) * 100).toFixed(2)
|
|||
|
|
: '0.00';
|
|||
|
|
|
|||
|
|
console.log(` 📊 Success Rate: ${successRate}%`);
|
|||
|
|
console.log('==================================================');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* kebab-case 转换
|
|||
|
|
*/
|
|||
|
|
toKebabCase(str) {
|
|||
|
|
return String(str)
|
|||
|
|
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|||
|
|
.replace(/_/g, '-')
|
|||
|
|
.toLowerCase();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* PascalCase 转换
|
|||
|
|
*/
|
|||
|
|
toPascalCase(str) {
|
|||
|
|
return String(str)
|
|||
|
|
.split(/[-_]/)
|
|||
|
|
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|||
|
|
.join('');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* camelCase 转换
|
|||
|
|
*/
|
|||
|
|
toCamelCase(str) {
|
|||
|
|
const pascal = this.toPascalCase(str);
|
|||
|
|
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* snake_case 转换
|
|||
|
|
*/
|
|||
|
|
toSnakeCase(str) {
|
|||
|
|
return str.replace(/([A-Z])/g, '_$1').toLowerCase().replace(/^_/, '');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
module.exports = BaseGenerator;
|
|||
|
|
|