2025-09-27 03:28:46 +08:00
|
|
|
|
const fs = require('fs');
|
|
|
|
|
|
const path = require('path');
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* NestJS模块生成器
|
|
|
|
|
|
* 为每个模块创建对应的.module.ts文件并正确引用所有组件
|
|
|
|
|
|
*/
|
|
|
|
|
|
class ModuleGenerator {
|
|
|
|
|
|
constructor() {
|
|
|
|
|
|
this.config = {
|
2025-10-06 10:56:59 +08:00
|
|
|
|
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
|
2025-09-27 03:28:46 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
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) {
|
2025-10-06 10:56:59 +08:00
|
|
|
|
console.log(` ⚠️ 未找到发现结果文件,跳过加载: ${error.message}`);
|
|
|
|
|
|
this.discoveryData = {};
|
2025-09-27 03:28:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 扫描模块结构
|
|
|
|
|
|
*/
|
|
|
|
|
|
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) {
|
2025-10-06 10:56:59 +08:00
|
|
|
|
if (this.shouldSkipModule(moduleName)) {
|
|
|
|
|
|
console.log(` ⏭️ 跳过非业务模块: ${moduleName}`);
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
2025-09-27 03:28:46 +08:00
|
|
|
|
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);
|
2025-10-06 10:56:59 +08:00
|
|
|
|
const controllerFiles = allFiles.filter(file => file.endsWith('.controller.ts'));
|
2025-09-27 03:28:46 +08:00
|
|
|
|
|
|
|
|
|
|
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 {
|
2025-10-06 10:56:59 +08:00
|
|
|
|
name: actualClassName || this.guessControllerClassName(file),
|
2025-09-27 03:28:46 +08:00
|
|
|
|
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 {
|
2025-10-06 10:56:59 +08:00
|
|
|
|
name: actualClassName || this.guessServiceClassName(file, layer),
|
2025-09-27 03:28:46 +08:00
|
|
|
|
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)
|
2025-10-06 10:56:59 +08:00
|
|
|
|
.filter(file => file.endsWith('.entity.ts'))
|
2025-09-27 03:28:46 +08:00
|
|
|
|
.map(file => ({
|
2025-10-06 10:56:59 +08:00
|
|
|
|
name: this.getActualClassName(path.join(entitiesPath, file)) || this.guessEntityClassName(file),
|
2025-09-27 03:28:46 +08:00
|
|
|
|
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) {
|
2025-10-06 10:56:59 +08:00
|
|
|
|
const className = this.toPascalCase(moduleName) + 'Module';
|
2025-09-27 03:28:46 +08:00
|
|
|
|
|
|
|
|
|
|
let imports = [];
|
|
|
|
|
|
let controllers = [];
|
|
|
|
|
|
let providers = [];
|
|
|
|
|
|
let exports = [];
|
|
|
|
|
|
let importSet = new Set(); // 用于去重
|
|
|
|
|
|
|
2025-10-06 10:56:59 +08:00
|
|
|
|
// 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(', ')}])`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 导入控制器并注册
|
2025-09-27 03:28:46 +08:00
|
|
|
|
for (const controller of components.controllers) {
|
2025-10-06 10:56:59 +08:00
|
|
|
|
importSet.add(`import { ${controller.name} } from '${controller.path}';`);
|
|
|
|
|
|
controllers.push(controller.name);
|
2025-09-27 03:28:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-06 10:56:59 +08:00
|
|
|
|
// 导入服务并注册
|
2025-09-27 03:28:46 +08:00
|
|
|
|
for (const service of components.services) {
|
2025-10-06 10:56:59 +08:00
|
|
|
|
if (!importSet.has(`import { ${service.name} } from '${service.path}';`)) {
|
|
|
|
|
|
importSet.add(`import { ${service.name} } from '${service.path}';`);
|
|
|
|
|
|
providers.push(`${service.name}`);
|
2025-09-27 03:28:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-06 10:56:59 +08:00
|
|
|
|
// 导入实体(如果需要)
|
2025-09-27 03:28:46 +08:00
|
|
|
|
for (const entity of components.entities) {
|
2025-10-06 10:56:59 +08:00
|
|
|
|
if (!importSet.has(`import { ${entity.name} } from '${entity.path}';`)) {
|
|
|
|
|
|
importSet.add(`import { ${entity.name} } from '${entity.path}';`);
|
2025-09-27 03:28:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-06 10:56:59 +08:00
|
|
|
|
// 组合最终内容
|
|
|
|
|
|
const moduleContent = `${Array.from(importSet).join('\n')}
|
2025-09-27 03:28:46 +08:00
|
|
|
|
|
2025-10-06 10:56:59 +08:00
|
|
|
|
import { Module } from '@nestjs/common';
|
2025-09-27 03:28:46 +08:00
|
|
|
|
|
|
|
|
|
|
@Module({
|
2025-10-06 10:56:59 +08:00
|
|
|
|
imports: [${imports.join(', ')}],
|
2025-09-27 03:28:46 +08:00
|
|
|
|
controllers: [${controllers.join(', ')}],
|
|
|
|
|
|
providers: [${providers.join(', ')}],
|
|
|
|
|
|
exports: [${exports.join(', ')}],
|
|
|
|
|
|
})
|
2025-10-06 10:56:59 +08:00
|
|
|
|
export class ${className} {}
|
2025-09-27 03:28:46 +08:00
|
|
|
|
`;
|
|
|
|
|
|
|
2025-10-06 10:56:59 +08:00
|
|
|
|
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';
|
2025-09-27 03:28:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 确保目录存在
|
|
|
|
|
|
*/
|
|
|
|
|
|
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());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-06 10:56:59 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-27 03:28:46 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 获取层前缀
|
|
|
|
|
|
*/
|
|
|
|
|
|
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;
|