const fs = require('fs'); const path = require('path'); /** * NestJS模块生成器 * 为每个模块创建对应的.module.ts文件并正确引用所有组件 */ class ModuleGenerator { constructor() { this.config = { nestjsBasePath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/wwjcloud-nest/src/core', discoveryResultPath: './php-discovery-result.json', whitelistModules: [], // 空数组=全部业务模块,结合黑名单过滤 blacklistModules: ['job','queue','workerman','lang','menu','system'], includeTypeOrmFeature: true }; this.discoveryData = null; this.stats = { createdModules: 0, updatedModules: 0, errors: 0 }; } /** * 运行模块生成 */ async run() { try { console.log('🚀 启动NestJS模块生成器...'); console.log('目标:为每个模块创建.module.ts文件并正确引用所有组件\n'); // 第1阶段:加载PHP文件发现结果 console.log('📊 第1阶段:加载PHP文件发现结果...'); await this.loadDiscoveryData(); console.log(' ✅ 成功加载PHP文件发现结果'); // 第2阶段:扫描现有文件结构 console.log('\n📊 第2阶段:扫描现有文件结构...'); const moduleStructure = await this.scanModuleStructure(); console.log(` ✅ 扫描了 ${Object.keys(moduleStructure).length} 个模块`); // 第3阶段:生成模块文件 console.log('\n📊 第3阶段:生成模块文件...'); await this.generateModules(moduleStructure); console.log(` ✅ 生成了 ${this.stats.createdModules} 个模块文件`); // 第4阶段:生成统计报告 console.log('\n📊 第4阶段:生成统计报告...'); this.generateStatsReport(); } catch (error) { console.error('❌ 生成过程中发生错误:', error.message); this.stats.errors++; throw error; } } /** * 加载PHP文件发现结果 */ async loadDiscoveryData() { try { const data = fs.readFileSync(this.config.discoveryResultPath, 'utf8'); this.discoveryData = JSON.parse(data); } catch (error) { console.log(` ⚠️ 未找到发现结果文件,跳过加载: ${error.message}`); this.discoveryData = {}; } } /** * 扫描模块结构 */ async scanModuleStructure() { const moduleStructure = {}; const commonPath = this.config.nestjsBasePath; if (!fs.existsSync(commonPath)) { console.log(' ⚠️ common目录不存在'); return moduleStructure; } const modules = fs.readdirSync(commonPath, { withFileTypes: true }) .filter(dirent => dirent.isDirectory()) .map(dirent => dirent.name); for (const moduleName of modules) { if (this.shouldSkipModule(moduleName)) { console.log(` ⏭️ 跳过非业务模块: ${moduleName}`); continue; } const modulePath = path.join(commonPath, moduleName); moduleStructure[moduleName] = { controllers: this.scanControllers(modulePath), services: this.scanServices(modulePath), entities: this.scanEntities(modulePath), validators: this.scanValidators(modulePath), middlewares: this.scanMiddlewares(modulePath), jobs: this.scanJobs(modulePath), listeners: this.scanListeners(modulePath), commands: this.scanCommands(modulePath), dicts: this.scanDicts(modulePath) }; } return moduleStructure; } /** * 读取实际文件中的类名 */ getActualClassName(filePath) { try { if (!fs.existsSync(filePath)) { return null; } const content = fs.readFileSync(filePath, 'utf8'); const match = content.match(/export\s+(?:class|interface|enum)\s+(\w+)/); return match ? match[1] : null; } catch (error) { console.error(`读取文件 ${filePath} 时出错:`, error.message); return null; } } /** * 扫描控制器 */ scanControllers(modulePath) { const controllers = []; const controllersPath = path.join(modulePath, 'controllers'); if (fs.existsSync(controllersPath)) { const layers = ['adminapi', 'api']; for (const layer of layers) { const layerPath = path.join(controllersPath, layer); if (fs.existsSync(layerPath)) { const allFiles = fs.readdirSync(layerPath); const controllerFiles = allFiles.filter(file => file.endsWith('.controller.ts')); if (controllerFiles.length > 0) { console.log(` 发现 ${layer} 层控制器: ${controllerFiles.join(', ')}`); } const files = controllerFiles.map(file => { const filePath = path.join(layerPath, file); const actualClassName = this.getActualClassName(filePath); return { name: actualClassName || this.guessControllerClassName(file), path: `./controllers/${layer}/${file}`, layer: layer }; }); controllers.push(...files); } } } return controllers; } /** * 扫描服务 */ scanServices(modulePath) { const services = []; const servicesPath = path.join(modulePath, 'services'); if (fs.existsSync(servicesPath)) { const layers = ['admin', 'api', 'core']; for (const layer of layers) { const layerPath = path.join(servicesPath, layer); if (fs.existsSync(layerPath)) { const files = fs.readdirSync(layerPath) .filter(file => file.endsWith('.service.ts')) .map(file => { const filePath = path.join(layerPath, file); const actualClassName = this.getActualClassName(filePath); return { name: actualClassName || this.guessServiceClassName(file, layer), path: `./services/${layer}/${file}`, layer: layer }; }); services.push(...files); } } } return services; } /** * 扫描实体 */ scanEntities(modulePath) { const entities = []; const entitiesPath = path.join(modulePath, 'entity'); if (fs.existsSync(entitiesPath)) { const files = fs.readdirSync(entitiesPath) .filter(file => file.endsWith('.entity.ts')) .map(file => ({ name: this.getActualClassName(path.join(entitiesPath, file)) || this.guessEntityClassName(file), path: `./entity/${file}` })); entities.push(...files); } return entities; } /** * 扫描验证器 */ scanValidators(modulePath) { const validators = []; const validatorsPath = path.join(modulePath, 'dto'); if (fs.existsSync(validatorsPath)) { const files = fs.readdirSync(validatorsPath, { recursive: true }) .filter(file => file.endsWith('.ts') && !file.endsWith('.d.ts')) .map(file => ({ name: file.replace('.ts', ''), path: `./dto/${file}` })); validators.push(...files); } return validators; } /** * 扫描中间件 */ scanMiddlewares(modulePath) { const middlewares = []; const middlewaresPath = path.join(modulePath, 'guards'); if (fs.existsSync(middlewaresPath)) { const files = fs.readdirSync(middlewaresPath) .filter(file => file.endsWith('.ts') && !file.endsWith('.d.ts')) .map(file => ({ name: file.replace('.ts', ''), path: `./guards/${file}` })); middlewares.push(...files); } return middlewares; } /** * 扫描任务 */ scanJobs(modulePath) { const jobs = []; const jobsPath = path.join(modulePath, 'jobs'); if (fs.existsSync(jobsPath)) { const files = fs.readdirSync(jobsPath) .filter(file => file.endsWith('.ts') && !file.endsWith('.d.ts')) .map(file => ({ name: file.replace('.ts', ''), path: `./jobs/${file}` })); jobs.push(...files); } return jobs; } /** * 扫描监听器 */ scanListeners(modulePath) { const listeners = []; const listenersPath = path.join(modulePath, 'listeners'); if (fs.existsSync(listenersPath)) { const files = fs.readdirSync(listenersPath) .filter(file => file.endsWith('.ts') && !file.endsWith('.d.ts')) .map(file => ({ name: file.replace('.ts', ''), path: `./listeners/${file}` })); listeners.push(...files); } return listeners; } /** * 扫描命令 */ scanCommands(modulePath) { const commands = []; const commandsPath = path.join(modulePath, 'commands'); if (fs.existsSync(commandsPath)) { const files = fs.readdirSync(commandsPath) .filter(file => file.endsWith('.ts') && !file.endsWith('.d.ts')) .map(file => ({ name: file.replace('.ts', ''), path: `./commands/${file}` })); commands.push(...files); } return commands; } /** * 扫描字典 */ scanDicts(modulePath) { const dicts = []; const dictsPath = path.join(modulePath, 'dicts'); if (fs.existsSync(dictsPath)) { const files = fs.readdirSync(dictsPath) .filter(file => file.endsWith('.ts') && !file.endsWith('.d.ts')) .map(file => ({ name: file.replace('.ts', ''), path: `./dicts/${file}` })); dicts.push(...files); } return dicts; } /** * 生成模块文件 */ async generateModules(moduleStructure) { console.log(' 🔨 生成模块文件...'); for (const [moduleName, components] of Object.entries(moduleStructure)) { try { await this.generateModuleFile(moduleName, components); this.stats.createdModules++; } catch (error) { console.error(` ❌ 生成模块 ${moduleName} 失败:`, error.message); this.stats.errors++; } } } /** * 生成单个模块文件 */ async generateModuleFile(moduleName, components) { const modulePath = path.join(this.config.nestjsBasePath, moduleName, `${moduleName}.module.ts`); // 生成模块内容 const moduleContent = this.generateModuleContent(moduleName, components); // 确保目录存在 this.ensureDir(path.dirname(modulePath)); // 写入文件 fs.writeFileSync(modulePath, moduleContent); console.log(` ✅ 创建模块: ${moduleName}/${moduleName}.module.ts`); } /** * 生成模块内容 */ generateModuleContent(moduleName, components) { const className = this.toPascalCase(moduleName) + 'Module'; let imports = []; let controllers = []; let providers = []; let exports = []; let importSet = new Set(); // 用于去重 // TypeORM feature (可选) const entityClassNames = components.entities.map(e => e.name).filter(Boolean); if (this.config.includeTypeOrmFeature && entityClassNames.length > 0) { importSet.add(`import { TypeOrmModule } from '@nestjs/typeorm';`); imports.push(`TypeOrmModule.forFeature([${entityClassNames.join(', ')}])`); } // 导入控制器并注册 for (const controller of components.controllers) { importSet.add(`import { ${controller.name} } from '${controller.path}';`); controllers.push(controller.name); } // 导入服务并注册 for (const service of components.services) { if (!importSet.has(`import { ${service.name} } from '${service.path}';`)) { importSet.add(`import { ${service.name} } from '${service.path}';`); providers.push(`${service.name}`); } } // 导入实体(如果需要) for (const entity of components.entities) { if (!importSet.has(`import { ${entity.name} } from '${entity.path}';`)) { importSet.add(`import { ${entity.name} } from '${entity.path}';`); } } // 组合最终内容 const moduleContent = `${Array.from(importSet).join('\n')} import { Module } from '@nestjs/common'; @Module({ imports: [${imports.join(', ')}], controllers: [${controllers.join(', ')}], providers: [${providers.join(', ')}], exports: [${exports.join(', ')}], }) export class ${className} {} `; return moduleContent; } /** * 从文件内容获取导出的类名 */ getActualClassName(filePath) { try { if (!fs.existsSync(filePath)) return null; const content = fs.readFileSync(filePath, 'utf8'); const match = content.match(/export\s+class\s+(\w+)/); return match ? match[1] : null; } catch (error) { return null; } } /** * 由 kebab-case 实体文件名推测类名 * 例如: member.entity.ts -> MemberEntity */ guessEntityClassName(fileName) { const base = fileName.replace(/\.entity\.ts$/i, ''); return this.kebabToPascal(base) + 'Entity'; } /** * 由 kebab-case 控制器文件名推测类名 * 例如: member-level.controller.ts -> MemberLevelController */ guessControllerClassName(fileName) { const base = fileName.replace(/\.controller\.ts$/i, ''); return this.kebabToPascal(base) + 'Controller'; } /** * 由 kebab-case 服务文件名推测类名 * 例如: member-level.service.ts -> MemberLevelService */ guessServiceClassName(fileName, layer) { const base = fileName.replace(/\.service\.ts$/i, ''); return this.kebabToPascal(base) + 'Service'; } /** * 确保目录存在 */ ensureDir(dirPath) { if (!fs.existsSync(dirPath)) { fs.mkdirSync(dirPath, { recursive: true }); } } /** * 转换为PascalCase */ toPascalCase(str) { return str.replace(/(^|_)([a-z])/g, (match, p1, p2) => p2.toUpperCase()); } /** * kebab-case 转 PascalCase */ kebabToPascal(str) { return str .split('-') .filter(Boolean) .map(s => s.charAt(0).toUpperCase() + s.slice(1)) .join(''); } shouldSkipModule(moduleName) { if (this.config.whitelistModules && this.config.whitelistModules.length > 0) { if (!this.config.whitelistModules.includes(moduleName)) return true; } if (this.config.blacklistModules && this.config.blacklistModules.includes(moduleName)) { return true; } return false; } /** * 获取层前缀 */ getLayerPrefix(layer, serviceName) { // 如果服务名已经包含Core前缀,则不需要再添加 if (layer === 'core' && serviceName.toLowerCase().startsWith('core')) { return ''; } const layerMap = { 'admin': 'Admin', 'api': 'Api', 'core': 'Core' }; return layerMap[layer] || ''; } /** * 检查是否需要别名 */ needsAlias(layer, serviceName) { // 如果服务名已经包含层前缀,则不需要别名 if (layer === 'core' && serviceName.toLowerCase().startsWith('core')) { return false; } return true; } /** * 生成统计报告 */ generateStatsReport() { console.log('\n📊 NestJS模块生成统计报告:'); console.log('============================================================'); console.log(` 📁 创建模块: ${this.stats.createdModules} 个`); console.log(` 🔄 更新模块: ${this.stats.updatedModules} 个`); console.log(` ❌ 错误数量: ${this.stats.errors} 个`); console.log('============================================================'); console.log('\n✅ 🎉 NestJS模块生成完成!'); } } // 运行模块生成器 if (require.main === module) { const generator = new ModuleGenerator(); generator.run().catch(console.error); } module.exports = ModuleGenerator;