175 lines
6.7 KiB
JavaScript
Executable File
175 lines
6.7 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
|
|
import fs from 'fs-extra';
|
|
import mustache from 'mustache';
|
|
import path from 'path';
|
|
import { program } from 'commander';
|
|
|
|
import {
|
|
log,
|
|
logSuccess,
|
|
logWarning,
|
|
logError,
|
|
logDetail,
|
|
initGenerationLog,
|
|
colors
|
|
} from './src/utils/logger';
|
|
import { checkOpenApiGenerator, installOpenApiGenerator } from './src/utils/openapi-generator';
|
|
import { createDirectoryStructure, cleanup } from './src/utils/filesystem';
|
|
import { analyzeSwagger } from './src/swagger/analyzer';
|
|
import { generateCode, organizeFiles, addDtoImports } from './src/generators/dto.generator';
|
|
import {
|
|
generateCleanArchitecture,
|
|
extractTagsWithOperations
|
|
} from './src/generators/clean-arch.generator';
|
|
import { generateReport } from './src/generators/report.generator';
|
|
import { lintGeneratedFiles } from './src/generators/lint.generator';
|
|
import { findEnvironmentFile, parseApiKeys } from './src/utils/environment-finder';
|
|
import { askApiKeysForTags, askSelectionFilter } from './src/utils/prompt';
|
|
import type { SelectionFilter, LintResult } from './src/types';
|
|
import type { CliOptions } from './src/types';
|
|
import packageJson from './package.json';
|
|
|
|
// Disable HTML escaping so that < and > produce valid TypeScript generic types.
|
|
(mustache as { escape: (text: string) => string }).escape = function (text: string): string {
|
|
return text;
|
|
};
|
|
|
|
// ── CLI CONFIGURATION ────────────────────────────────────────────────────────
|
|
|
|
program
|
|
.name('generate-clean-arch')
|
|
.description('Angular Clean Architecture code generator from OpenAPI/Swagger')
|
|
.version(packageJson.version)
|
|
.option('-i, --input <file>', 'OpenAPI/Swagger file (yaml or json)', 'swagger.yaml')
|
|
.option('-o, --output <dir>', 'Output directory', './src/app')
|
|
.option('-t, --templates <dir>', 'Custom templates directory', path.join(__dirname, 'templates'))
|
|
.option('--skip-install', 'Skip dependency installation')
|
|
.option('--dry-run', 'Simulate without generating files')
|
|
.option('--skip-lint', 'Skip post-generation linting and formatting')
|
|
.option('-s, --select-endpoints', 'Interactively select which tags and endpoints to generate')
|
|
.parse(process.argv);
|
|
|
|
const options = program.opts<CliOptions>();
|
|
|
|
// ── MAIN ORCHESTRATOR ────────────────────────────────────────────────────────
|
|
|
|
async function main(): Promise<void> {
|
|
console.log('\n' + '='.repeat(60));
|
|
log(' OpenAPI Clean Architecture Generator', 'bright');
|
|
log(' Angular + Clean Architecture Code Generator', 'cyan');
|
|
console.log('='.repeat(60) + '\n');
|
|
|
|
const logPath = path.join(process.cwd(), 'generation.log');
|
|
initGenerationLog(logPath);
|
|
logDetail('config', `Input: ${options.input}`);
|
|
logDetail('config', `Output: ${options.output}`);
|
|
logDetail('config', `Templates: ${options.templates}`);
|
|
|
|
if (!fs.existsSync(options.input)) {
|
|
logError(`File not found: ${options.input}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
if (options.dryRun) {
|
|
logWarning('DRY RUN mode — no files will be generated');
|
|
}
|
|
|
|
if (!checkOpenApiGenerator()) {
|
|
logWarning('OpenAPI Generator CLI not found');
|
|
if (!options.skipInstall) {
|
|
installOpenApiGenerator();
|
|
} else {
|
|
logError(
|
|
'Install openapi-generator-cli with: npm install -g @openapitools/openapi-generator-cli'
|
|
);
|
|
process.exit(1);
|
|
}
|
|
} else {
|
|
logSuccess('OpenAPI Generator CLI found');
|
|
}
|
|
|
|
const analysis = analyzeSwagger(options.input);
|
|
|
|
if (options.dryRun) {
|
|
logWarning('Finishing in DRY RUN mode');
|
|
return;
|
|
}
|
|
|
|
createDirectoryStructure(options.output);
|
|
|
|
// ── SELECTION: tags and endpoints ─────────────────────────────────────────
|
|
const tagSummaries = extractTagsWithOperations(analysis);
|
|
let selectionFilter: SelectionFilter = {};
|
|
|
|
if (options.selectEndpoints) {
|
|
selectionFilter = await askSelectionFilter(tagSummaries);
|
|
}
|
|
|
|
const selectedTags = options.selectEndpoints
|
|
? Object.keys(selectionFilter)
|
|
: tagSummaries.map((t) => t.tag);
|
|
|
|
// ── ENVIRONMENT API KEY SELECTION ──────────────────────────────────────────
|
|
const envFile = findEnvironmentFile(process.cwd());
|
|
let apiKeys: ReturnType<typeof parseApiKeys> = [];
|
|
|
|
if (envFile) {
|
|
const envContent = fs.readFileSync(envFile, 'utf8');
|
|
apiKeys = parseApiKeys(envContent);
|
|
logSuccess(
|
|
`environment.ts found: ${colors.cyan}${path.relative(process.cwd(), envFile)}${colors.reset}`
|
|
);
|
|
if (apiKeys.length === 0) {
|
|
logWarning('No keys containing "api" found in environment.ts. Will be requested manually.');
|
|
}
|
|
} else {
|
|
logWarning('No environment.ts found. The key will be requested manually per repository.');
|
|
}
|
|
|
|
const tagApiKeyMap = await askApiKeysForTags(selectedTags, apiKeys);
|
|
Object.entries(tagApiKeyMap).forEach(([tag, key]) => {
|
|
logDetail('config', `API key for "${tag}": environment.${key}.url`);
|
|
});
|
|
// ──────────────────────────────────────────────────────────────────────────
|
|
|
|
const tempDir = generateCode(options.input, options.templates);
|
|
organizeFiles(tempDir, options.output);
|
|
addDtoImports(options.output);
|
|
generateCleanArchitecture(
|
|
analysis,
|
|
options.output,
|
|
options.templates,
|
|
tagApiKeyMap,
|
|
selectionFilter
|
|
);
|
|
cleanup(tempDir);
|
|
|
|
const noLintResult: LintResult = {
|
|
prettier: { ran: false, filesFormatted: 0 },
|
|
eslint: { ran: false, filesFixed: 0 }
|
|
};
|
|
const lintResult = options.skipLint ? noLintResult : lintGeneratedFiles(options.output);
|
|
|
|
const report = generateReport(options.output, analysis, lintResult);
|
|
|
|
console.log('\n' + '='.repeat(60));
|
|
log(' ✨ Generation completed successfully', 'green');
|
|
console.log('='.repeat(60));
|
|
console.log(`\n📊 Summary:`);
|
|
console.log(` - DTOs generated: ${report.structure.dtos}`);
|
|
console.log(` - Repositories: ${report.structure.repositories}`);
|
|
console.log(` - Mappers: ${report.structure.mappers}`);
|
|
console.log(` - Use Cases: ${report.structure.useCases}`);
|
|
console.log(` - Providers: ${report.structure.providers}`);
|
|
console.log(` - Mocks: ${report.structure.mocks}`);
|
|
console.log(`\n📁 Files generated in: ${colors.cyan}${options.output}${colors.reset}\n`);
|
|
}
|
|
|
|
main().catch((error: unknown) => {
|
|
const err = error as Error;
|
|
logError(`Fatal error: ${err.message}`);
|
|
console.error(error);
|
|
process.exit(1);
|
|
});
|