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