import { execSync } from 'child_process'; import fs from 'fs-extra'; import path from 'path'; import { logStep, logSuccess, logError, logInfo } from '../utils/logger'; /** Invokes `openapi-generator-cli` to generate DTOs into a temporary directory. */ export function generateCode(swaggerFile: string, templatesDir: string): string { logStep('Generating code from OpenAPI spec...'); const tempDir = path.join(process.cwd(), '.temp-generated'); if (fs.existsSync(tempDir)) { fs.removeSync(tempDir); } try { const command = `openapi-generator-cli generate \ -i "${swaggerFile}" \ -g typescript-angular \ --global-property models \ -t "${templatesDir}" \ -o "${tempDir}" \ --additional-properties=ngVersion=17.0.0,modelFileSuffix=.dto,modelNameSuffix=Dto`; execSync(command, { stdio: 'inherit' }); logSuccess('Code generated successfully'); return tempDir; } catch (_error) { logError('Error generating code'); if (fs.existsSync(tempDir)) { fs.removeSync(tempDir); } process.exit(1); } } /** Copies the generated DTOs from the temporary directory to the output directory. */ export function organizeFiles(tempDir: string, outputDir: string): void { logStep('Organising generated DTO files...'); const sourceDir = path.join(tempDir, 'model'); const destDir = path.join(outputDir, 'data/dtos'); let filesMoved = 0; if (fs.existsSync(sourceDir)) { fs.ensureDirSync(destDir); const files = fs.readdirSync(sourceDir).filter((file) => file.endsWith('.dto.ts')); files.forEach((file) => { const sourcePath = path.join(sourceDir, file); const destPath = path.join(destDir, file); fs.copySync(sourcePath, destPath); filesMoved++; logInfo(` ${file} → ${path.relative(process.cwd(), destPath)}`); }); } logSuccess(`${filesMoved} DTOs moved successfully`); } /** Post-processes the generated DTOs: adds cross-DTO imports and normalises Array → T[]. */ export function addDtoImports(outputDir: string): void { logStep('Post-processing generated DTOs...'); const dtosDir = path.join(outputDir, 'data/dtos'); if (!fs.existsSync(dtosDir)) return; const files = fs.readdirSync(dtosDir).filter((f) => f.endsWith('.dto.ts')); // Build a map of DTO classname → file base name (without .ts) const dtoMap: Record = {}; files.forEach((file) => { const content = fs.readFileSync(path.join(dtosDir, file), 'utf8'); const match = content.match(/export interface (\w+)/); if (match) { dtoMap[match[1]] = file.replace('.ts', ''); } }); let filesProcessed = 0; files.forEach((file) => { const filePath = path.join(dtosDir, file); const originalContent = fs.readFileSync(filePath, 'utf8'); let content = originalContent; const selfMatch = content.match(/export interface (\w+)/); const selfName = selfMatch ? selfMatch[1] : ''; // Normalize Array → T[] (openapi-generator-cli always outputs Array) content = content.replace(/Array<(\w+)>/g, '$1[]'); // Find all Dto type references in the file body (excluding the interface name itself) const references = new Set(); const typeRegex = /\b(\w+Dto)\b/g; let match; while ((match = typeRegex.exec(content)) !== null) { if (match[1] !== selfName) { references.add(match[1]); } } // Build import lines for each referenced type that exists in the dtoMap const imports: string[] = []; references.forEach((ref) => { if (dtoMap[ref]) { imports.push(`import { ${ref} } from './${dtoMap[ref]}';`); } }); if (imports.length > 0) { content = imports.join('\n') + '\n' + content; } if (content !== originalContent) { fs.writeFileSync(filePath, content); filesProcessed++; logInfo(` Procesado ${file}`); } }); logSuccess(`${filesProcessed} DTOs post-processed`); }