Files
wwjcloud-nest-v1/tools-v1/java-tools/incremental-updater.js

774 lines
21 KiB
JavaScript
Raw Normal View History

#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const { execSync } = require('child_process');
/**
* 🔄 增量更新器
* 智能检测Java项目变更实现增量迁移到NestJS (参考Java架构)
*/
class IncrementalUpdater {
constructor() {
this.config = {
javaBasePath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/niucloud-java/niucloud-core/src/main/java',
phpBasePath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/niucloud-php/niucloud', // 仅用于业务逻辑提取
nestjsBasePath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/wwjcloud-nest-v1/libs/wwjcloud-core/src',
stateFilePath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/tools-v1/java-tools/.incremental-state.json',
backupPath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/tools-v1/java-tools/backups',
dryRun: process.env.DRY_RUN === 'true'
};
this.state = {
lastUpdate: null,
fileHashes: {},
migrationHistory: [],
userModifications: {},
conflicts: []
};
this.stats = {
filesChanged: 0,
filesAdded: 0,
filesDeleted: 0,
conflictsDetected: 0,
autoMerged: 0,
manualMergeRequired: 0
};
}
/**
* 🚀 执行增量更新
*/
async run() {
console.log('🔄 启动增量更新器...');
console.log(`📁 Java架构参考: ${this.config.javaBasePath}`);
console.log(`📁 PHP业务逻辑源: ${this.config.phpBasePath}`);
console.log(`📁 NestJS项目: ${this.config.nestjsBasePath}`);
console.log(`🔍 Dry-run模式: ${this.config.dryRun ? '是' : '否'}\n`);
try {
// 1. 加载上次更新状态
await this.loadState();
// 2. 检测Java架构变更参考Java提取PHP业务逻辑
const changes = await this.detectChanges();
if (changes.length === 0) {
console.log('✅ 没有检测到变更,无需更新');
return;
}
console.log(`📊 检测到 ${changes.length} 个变更文件`);
// 3. 分析变更类型
const changeAnalysis = await this.analyzeChanges(changes);
// 4. 检测用户自定义修改
await this.detectUserModifications();
// 5. 执行智能合并
const mergeResults = await this.performSmartMerge(changeAnalysis);
// 6. 生成更新报告
this.generateUpdateReport(mergeResults);
// 7. 保存新状态
if (!this.config.dryRun) {
await this.saveState();
}
} catch (error) {
console.error('❌ 增量更新失败:', error.message);
throw error;
}
}
/**
* 📂 加载上次更新状态
*/
async loadState() {
try {
if (fs.existsSync(this.config.stateFilePath)) {
const data = fs.readFileSync(this.config.stateFilePath, 'utf8');
this.state = { ...this.state, ...JSON.parse(data) };
console.log(`📋 加载状态: 上次更新时间 ${this.state.lastUpdate || '从未更新'}`);
} else {
console.log('📋 首次运行,创建新状态');
}
} catch (error) {
console.log(`⚠️ 加载状态失败,使用默认状态: ${error.message}`);
}
}
/**
* 🔍 检测Java架构变更 (参考Java架构提取PHP业务逻辑)
*/
async detectChanges() {
console.log('🔍 检测Java架构变更 (参考Java架构)...');
const changes = [];
const phpFiles = this.getAllPHPFiles();
for (const filePath of phpFiles) {
const relativePath = path.relative(this.config.phpBasePath, filePath);
const currentHash = this.calculateFileHash(filePath);
const lastHash = this.state.fileHashes[relativePath];
if (!lastHash) {
// 新文件
changes.push({
type: 'added',
path: relativePath,
fullPath: filePath,
hash: currentHash
});
this.stats.filesAdded++;
} else if (currentHash !== lastHash) {
// 修改的文件
changes.push({
type: 'modified',
path: relativePath,
fullPath: filePath,
hash: currentHash,
oldHash: lastHash
});
this.stats.filesChanged++;
}
// 更新哈希
this.state.fileHashes[relativePath] = currentHash;
}
// 检测删除的文件
for (const [relativePath, hash] of Object.entries(this.state.fileHashes)) {
const fullPath = path.join(this.config.phpBasePath, relativePath);
if (!fs.existsSync(fullPath)) {
changes.push({
type: 'deleted',
path: relativePath,
fullPath: fullPath,
hash: hash
});
this.stats.filesDeleted++;
delete this.state.fileHashes[relativePath];
}
}
return changes;
}
/**
* 📊 分析变更类型
*/
async analyzeChanges(changes) {
console.log('📊 分析变更类型...');
const analysis = {
controllers: [],
services: [],
models: [],
validators: [],
others: []
};
for (const change of changes) {
const category = this.categorizeFile(change.path);
analysis[category].push(change);
console.log(` ${this.getChangeIcon(change.type)} ${change.type.toUpperCase()}: ${change.path} (${category})`);
}
return analysis;
}
/**
* 🔍 检测用户自定义修改
*/
async detectUserModifications() {
console.log('🔍 检测用户自定义修改...');
const nestjsFiles = this.getAllNestJSFiles();
for (const filePath of nestjsFiles) {
const relativePath = path.relative(this.config.nestjsBasePath, filePath);
const content = fs.readFileSync(filePath, 'utf8');
// 检测用户自定义标记
const userModifications = this.detectUserCode(content);
if (userModifications.length > 0) {
this.state.userModifications[relativePath] = userModifications;
console.log(` 🔧 检测到用户修改: ${relativePath} (${userModifications.length}处)`);
}
}
}
/**
* 🤖 执行智能合并
*/
async performSmartMerge(changeAnalysis) {
console.log('🤖 执行智能合并...');
const mergeResults = {
autoMerged: [],
conflicts: [],
skipped: []
};
// 创建备份
if (!this.config.dryRun) {
await this.createBackup();
}
// 处理各类变更
for (const [category, changes] of Object.entries(changeAnalysis)) {
if (changes.length === 0) continue;
console.log(`\n📋 处理 ${category} 变更 (${changes.length}个文件):`);
for (const change of changes) {
const result = await this.mergeFile(change, category);
mergeResults[result.status].push(result);
console.log(` ${this.getMergeIcon(result.status)} ${change.path}: ${result.message}`);
}
}
return mergeResults;
}
/**
* 🔀 合并单个文件
*/
async mergeFile(change, category) {
const nestjsPath = this.mapPHPToNestJS(change.path, category);
if (!nestjsPath) {
return {
status: 'skipped',
change: change,
message: '无对应的NestJS文件映射'
};
}
const nestjsFullPath = path.join(this.config.nestjsBasePath, nestjsPath);
// 检查是否存在用户修改
const hasUserModifications = this.state.userModifications[nestjsPath];
if (change.type === 'deleted') {
return await this.handleDeletedFile(change, nestjsFullPath, hasUserModifications);
}
if (change.type === 'added') {
return await this.handleAddedFile(change, nestjsFullPath, category);
}
if (change.type === 'modified') {
return await this.handleModifiedFile(change, nestjsFullPath, hasUserModifications, category);
}
}
/**
* 处理新增文件
*/
async handleAddedFile(change, nestjsPath, category) {
if (fs.existsSync(nestjsPath)) {
return {
status: 'conflicts',
change: change,
message: 'NestJS文件已存在需要手动处理'
};
}
if (this.config.dryRun) {
return {
status: 'autoMerged',
change: change,
message: '[DRY-RUN] 将生成新的NestJS文件'
};
}
// 生成NestJS文件
const success = await this.generateNestJSFile(change.fullPath, nestjsPath, category);
if (success) {
this.stats.autoMerged++;
return {
status: 'autoMerged',
change: change,
message: '成功生成新的NestJS文件'
};
} else {
return {
status: 'conflicts',
change: change,
message: '生成NestJS文件失败'
};
}
}
/**
* 处理修改文件
*/
async handleModifiedFile(change, nestjsPath, hasUserModifications, category) {
if (!fs.existsSync(nestjsPath)) {
// NestJS文件不存在直接生成
return await this.handleAddedFile(change, nestjsPath, category);
}
if (hasUserModifications) {
// 存在用户修改,需要智能合并
return await this.performIntelligentMerge(change, nestjsPath, category);
}
if (this.config.dryRun) {
return {
status: 'autoMerged',
change: change,
message: '[DRY-RUN] 将重新生成NestJS文件'
};
}
// 没有用户修改,直接重新生成
const success = await this.generateNestJSFile(change.fullPath, nestjsPath, category);
if (success) {
this.stats.autoMerged++;
return {
status: 'autoMerged',
change: change,
message: '成功重新生成NestJS文件'
};
} else {
return {
status: 'conflicts',
change: change,
message: '重新生成NestJS文件失败'
};
}
}
/**
* 🗑 处理删除文件
*/
async handleDeletedFile(change, nestjsPath, hasUserModifications) {
if (!fs.existsSync(nestjsPath)) {
return {
status: 'autoMerged',
change: change,
message: 'NestJS文件已不存在'
};
}
if (hasUserModifications) {
return {
status: 'conflicts',
change: change,
message: '文件包含用户修改,需要手动决定是否删除'
};
}
if (this.config.dryRun) {
return {
status: 'autoMerged',
change: change,
message: '[DRY-RUN] 将删除对应的NestJS文件'
};
}
// 删除NestJS文件
fs.unlinkSync(nestjsPath);
this.stats.autoMerged++;
return {
status: 'autoMerged',
change: change,
message: '成功删除对应的NestJS文件'
};
}
/**
* 🧠 执行智能合并
*/
async performIntelligentMerge(change, nestjsPath, category) {
console.log(` 🧠 智能合并: ${change.path}`);
// 读取现有NestJS文件
const existingContent = fs.readFileSync(nestjsPath, 'utf8');
// 生成新的NestJS内容
const newContent = await this.generateNestJSContent(change.fullPath, category);
if (!newContent) {
return {
status: 'conflicts',
change: change,
message: '无法生成新的NestJS内容'
};
}
// 执行三路合并
const mergeResult = this.performThreeWayMerge(existingContent, newContent, change);
if (mergeResult.hasConflicts) {
this.stats.conflictsDetected++;
// 保存冲突文件
const conflictPath = `${nestjsPath}.conflict`;
if (!this.config.dryRun) {
fs.writeFileSync(conflictPath, mergeResult.conflictContent);
}
return {
status: 'conflicts',
change: change,
message: `存在合并冲突,冲突文件保存为: ${conflictPath}`
};
}
if (this.config.dryRun) {
return {
status: 'autoMerged',
change: change,
message: '[DRY-RUN] 将执行智能合并'
};
}
// 保存合并结果
fs.writeFileSync(nestjsPath, mergeResult.mergedContent);
this.stats.autoMerged++;
return {
status: 'autoMerged',
change: change,
message: '成功执行智能合并'
};
}
/**
* 🔀 执行三路合并
*/
performThreeWayMerge(existingContent, newContent, change) {
// 简化的三路合并实现
// 在实际项目中,可以使用更复杂的合并算法
const userSections = this.extractUserSections(existingContent);
const generatedSections = this.extractGeneratedSections(newContent);
let mergedContent = newContent;
let hasConflicts = false;
let conflictContent = '';
// 尝试保留用户自定义部分
for (const userSection of userSections) {
const insertPosition = this.findInsertPosition(mergedContent, userSection);
if (insertPosition !== -1) {
// 可以安全插入
mergedContent = this.insertUserSection(mergedContent, userSection, insertPosition);
} else {
// 存在冲突
hasConflicts = true;
conflictContent += `\n<<<<<<< 用户修改\n${userSection.content}\n=======\n`;
conflictContent += `${this.getConflictingSection(newContent, userSection)}\n>>>>>>> 新生成\n`;
}
}
return {
mergedContent,
hasConflicts,
conflictContent: hasConflicts ? existingContent + '\n\n' + conflictContent : ''
};
}
/**
* 🏷 检测用户代码
*/
detectUserCode(content) {
const userModifications = [];
// 检测用户自定义注释
const userCommentRegex = /\/\*\s*USER_CUSTOM_START\s*\*\/([\s\S]*?)\/\*\s*USER_CUSTOM_END\s*\*\//g;
let match;
while ((match = userCommentRegex.exec(content)) !== null) {
userModifications.push({
type: 'custom_block',
content: match[1].trim(),
start: match.index,
end: match.index + match[0].length
});
}
// 检测手动添加的方法
const methodRegex = /\/\*\s*@user-added\s*\*\/\s*([\s\S]*?)(?=\/\*|$)/g;
while ((match = methodRegex.exec(content)) !== null) {
userModifications.push({
type: 'user_method',
content: match[1].trim(),
start: match.index,
end: match.index + match[0].length
});
}
return userModifications;
}
/**
* 🗂 文件分类
*/
categorizeFile(filePath) {
if (filePath.includes('/controller/')) return 'controllers';
if (filePath.includes('/service/')) return 'services';
if (filePath.includes('/model/')) return 'models';
if (filePath.includes('/validate/')) return 'validators';
return 'others';
}
/**
* 🗺 PHP到NestJS文件映射
*/
mapPHPToNestJS(phpPath, category) {
// 简化的映射逻辑,实际项目中需要更复杂的映射规则
const baseName = path.basename(phpPath, '.php');
const dirName = path.dirname(phpPath);
switch (category) {
case 'controllers':
return `${dirName}/${baseName.toLowerCase()}.controller.ts`;
case 'services':
return `${dirName}/${baseName.toLowerCase()}.service.ts`;
case 'models':
return `${dirName}/entity/${baseName.toLowerCase()}.entity.ts`;
case 'validators':
return `${dirName}/${baseName.toLowerCase()}.validator.ts`;
default:
return null;
}
}
/**
* 📁 获取所有PHP文件
*/
getAllPHPFiles() {
const files = [];
const scanDir = (dir) => {
const items = fs.readdirSync(dir);
for (const item of items) {
const fullPath = path.join(dir, item);
const stat = fs.statSync(fullPath);
if (stat.isDirectory()) {
scanDir(fullPath);
} else if (item.endsWith('.php')) {
files.push(fullPath);
}
}
};
scanDir(this.config.phpBasePath);
return files;
}
/**
* 📁 获取所有NestJS文件
*/
getAllNestJSFiles() {
const files = [];
const scanDir = (dir) => {
if (!fs.existsSync(dir)) return;
const items = fs.readdirSync(dir);
for (const item of items) {
const fullPath = path.join(dir, item);
const stat = fs.statSync(fullPath);
if (stat.isDirectory()) {
scanDir(fullPath);
} else if (item.endsWith('.ts')) {
files.push(fullPath);
}
}
};
scanDir(this.config.nestjsBasePath);
return files;
}
/**
* 🔐 计算文件哈希
*/
calculateFileHash(filePath) {
const content = fs.readFileSync(filePath);
return crypto.createHash('md5').update(content).digest('hex');
}
/**
* 💾 创建备份
*/
async createBackup() {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const backupDir = path.join(this.config.backupPath, timestamp);
if (!fs.existsSync(this.config.backupPath)) {
fs.mkdirSync(this.config.backupPath, { recursive: true });
}
fs.mkdirSync(backupDir, { recursive: true });
// 复制NestJS项目到备份目录
this.copyDirectory(this.config.nestjsBasePath, backupDir);
console.log(`💾 创建备份: ${backupDir}`);
}
/**
* 📋 复制目录
*/
copyDirectory(src, dest) {
if (!fs.existsSync(dest)) {
fs.mkdirSync(dest, { recursive: true });
}
const items = fs.readdirSync(src);
for (const item of items) {
const srcPath = path.join(src, item);
const destPath = path.join(dest, item);
const stat = fs.statSync(srcPath);
if (stat.isDirectory()) {
this.copyDirectory(srcPath, destPath);
} else {
fs.copyFileSync(srcPath, destPath);
}
}
}
/**
* 🏗 生成NestJS文件
*/
async generateNestJSFile(phpPath, nestjsPath, category) {
// 这里应该调用相应的生成器
// 为了简化,这里只是创建一个占位符
const content = await this.generateNestJSContent(phpPath, category);
if (!content) return false;
// 确保目录存在
const dir = path.dirname(nestjsPath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(nestjsPath, content);
return true;
}
/**
* 📝 生成NestJS内容
*/
async generateNestJSContent(phpPath, category) {
// 这里应该调用相应的转换器
// 为了简化,返回一个基本模板
const className = path.basename(phpPath, '.php');
switch (category) {
case 'controllers':
return `import { Controller } from '@nestjs/common';\n\n@Controller()\nexport class ${className}Controller {\n // Generated from ${phpPath}\n}\n`;
case 'services':
return `import { Injectable } from '@nestjs/common';\n\n@Injectable()\nexport class ${className}Service {\n // Generated from ${phpPath}\n}\n`;
case 'models':
return `import { Entity } from 'typeorm';\n\n@Entity()\nexport class ${className} {\n // Generated from ${phpPath}\n}\n`;
default:
return `// Generated from ${phpPath}\nexport class ${className} {\n}\n`;
}
}
/**
* 📊 生成更新报告
*/
generateUpdateReport(mergeResults) {
console.log('\n📊 增量更新报告');
console.log('==================================================');
console.log(`📁 文件变更统计:`);
console.log(` 新增: ${this.stats.filesAdded}`);
console.log(` ✏️ 修改: ${this.stats.filesChanged}`);
console.log(` 🗑️ 删除: ${this.stats.filesDeleted}`);
console.log(`\n🔀 合并结果统计:`);
console.log(` ✅ 自动合并: ${mergeResults.autoMerged.length}`);
console.log(` ⚠️ 冲突需处理: ${mergeResults.conflicts.length}`);
console.log(` ⏭️ 跳过: ${mergeResults.skipped.length}`);
if (mergeResults.conflicts.length > 0) {
console.log(`\n⚠️ 需要手动处理的冲突:`);
for (const conflict of mergeResults.conflicts) {
console.log(` - ${conflict.change.path}: ${conflict.message}`);
}
}
console.log('==================================================');
}
/**
* 💾 保存状态
*/
async saveState() {
this.state.lastUpdate = new Date().toISOString();
this.state.migrationHistory.push({
timestamp: this.state.lastUpdate,
stats: { ...this.stats }
});
fs.writeFileSync(this.config.stateFilePath, JSON.stringify(this.state, null, 2));
console.log(`💾 状态已保存: ${this.config.stateFilePath}`);
}
/**
* 🎨 获取变更图标
*/
getChangeIcon(type) {
const icons = {
added: '',
modified: '✏️',
deleted: '🗑️'
};
return icons[type] || '❓';
}
/**
* 🎨 获取合并图标
*/
getMergeIcon(status) {
const icons = {
autoMerged: '✅',
conflicts: '⚠️',
skipped: '⏭️'
};
return icons[status] || '❓';
}
// 辅助方法(简化实现)
extractUserSections(content) { return []; }
extractGeneratedSections(content) { return []; }
findInsertPosition(content, section) { return -1; }
insertUserSection(content, section, position) { return content; }
getConflictingSection(content, section) { return ''; }
}
// 命令行执行
if (require.main === module) {
const updater = new IncrementalUpdater();
updater.run().catch(console.error);
}
module.exports = IncrementalUpdater;