import * as fs from 'fs/promises'; import * as path from 'path'; import chalk from 'chalk'; import { ClassInfo } from './interfaces/class-info'; import { TypeInformation, MethodSignature } from './interfaces/type-information'; import { createRegexPatterns } from './utils/regex-utils'; import { getAllTsFiles, scanProject } from './core/file-scanner'; import { Project } from 'ts-morph'; import { AnalyzerType, createAnalyzer } from './core/analyzer-factory'; function generateInterfaceString(classInfos: ClassInfo[], typeInfo: TypeInformation, returnType: string, project: Project): string { let output = "// Auto-generated by rpc-nats-alvamind\n\n"; // Add imports if (typeInfo.imports.size > 0) { output += Array.from(typeInfo.imports).join("\n") + "\n\n"; } output += "export interface ExposedMethods {\n"; for (const classInfo of classInfos) { const { className, methods } = classInfo; if (methods.length === 0) { output += ` ${className}: {};\n`; continue; } output += ` ${className}: {\n`; const methodSignatures = typeInfo.methodSignatures.get(className); if (methodSignatures) { for (const [methodName, signatures] of methodSignatures) { signatures.forEach(signature => { const typeParams = signature.typeParameters.length > 0 ? `<${signature.typeParameters.map(tp => `${tp.name}${tp.constraint ? ` extends ${tp.constraint}` : ''}` ).join(", ")}>` : ""; const params = signature.parameters .map(p => `${p.name}${p.optional ? "?" : ""}: ${p.type}`) .join(", "); let returnTypeStr = signature.returnType; if (returnType === 'promise' && !returnTypeStr.startsWith('Promise<')) { returnTypeStr = `Promise<${returnTypeStr}>`; } else if (returnType === 'observable') { returnTypeStr = `Observable<${returnTypeStr.startsWith('Promise<') ? returnTypeStr.slice(8, -1) : returnTypeStr}>`; } else if (returnType === 'raw' && returnTypeStr.startsWith('Promise<')) { returnTypeStr = returnTypeStr.slice(8, -1); } output += ` ${signature.name}${typeParams}(${params}): ${returnTypeStr};\n`; }); } } output += " };\n"; } output += "}\n"; return output; } export async function generateExposedMethodsType( options: { scanPath: string, excludeFiles?: string[], returnType?: string, logLevel?: 'silent' | 'info' | 'debug', resolver?: AnalyzerType }, outputPath: string, ) { try { const excludePatterns = createRegexPatterns(options.excludeFiles); const tsFiles = await getAllTsFiles(options.scanPath, excludePatterns); if (options.logLevel === 'info' || options.logLevel === 'debug') { console.log(chalk.yellow(`[NATS] Found ${tsFiles.length} TypeScript files to process`)); } const fileMap = await scanProject(options.scanPath); const analyzerType = options.resolver || 'regex'; const analyzer = createAnalyzer(analyzerType); const classInfos = await analyzer.scanClasses(tsFiles); if (options.logLevel === 'debug') { console.log(chalk.blue(`[DEBUG] Found classes:`), classInfos); } const typeInfo = await analyzer.extractTypeInformation( options.scanPath, tsFiles, outputPath, fileMap, ); if (options.logLevel === 'debug') { console.log(chalk.blue(`[DEBUG] Extracted type information:`), typeInfo); } const project = new Project(); project.addSourceFilesAtPaths(tsFiles); const interfaceString = generateInterfaceString(classInfos, typeInfo, options.returnType || 'raw', project); await fs.mkdir(path.dirname(outputPath), { recursive: true }); await fs.writeFile(outputPath, interfaceString, "utf-8"); if (options.logLevel === 'info' || options.logLevel === 'debug') { console.log(chalk.green(`[NATS] Successfully generated types at ${outputPath}`)); } } catch (error) { console.error(chalk.red.bold('[NATS] Error generating exposed methods types:')); console.error(chalk.red(error)); throw error; } }