@0kmpo/openapi-clean-arch-generator 1.3.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/.gitea/workflows/lint.yaml +41 -0
  2. package/.gitea/workflows/publish.yml +105 -0
  3. package/.openapi-generator-ignore +33 -0
  4. package/.prettierrc +7 -0
  5. package/LICENSE +21 -0
  6. package/README.md +333 -0
  7. package/dist/main.js +180 -0
  8. package/eslint.config.js +33 -0
  9. package/example-swagger.yaml +150 -0
  10. package/generation-config.json +24 -0
  11. package/main.ts +233 -0
  12. package/openapitools.json +23 -0
  13. package/package.json +70 -0
  14. package/src/generators/clean-arch.generator.ts +537 -0
  15. package/src/generators/dto.generator.ts +126 -0
  16. package/src/generators/lint.generator.ts +124 -0
  17. package/src/generators/report.generator.ts +80 -0
  18. package/src/swagger/analyzer.ts +32 -0
  19. package/src/types/cli.types.ts +36 -0
  20. package/src/types/generation.types.ts +50 -0
  21. package/src/types/index.ts +8 -0
  22. package/src/types/openapi.types.ts +126 -0
  23. package/src/types/swagger.types.ts +9 -0
  24. package/src/utils/config.ts +118 -0
  25. package/src/utils/environment-finder.ts +53 -0
  26. package/src/utils/filesystem.ts +31 -0
  27. package/src/utils/logger.ts +60 -0
  28. package/src/utils/mock-value-resolver.ts +70 -0
  29. package/src/utils/name-formatter.ts +12 -0
  30. package/src/utils/openapi-generator.ts +24 -0
  31. package/src/utils/prompt.ts +183 -0
  32. package/src/utils/type-mapper.ts +14 -0
  33. package/templates/api.repository.contract.mustache +34 -0
  34. package/templates/api.repository.impl.mock.mustache +21 -0
  35. package/templates/api.repository.impl.mustache +58 -0
  36. package/templates/api.repository.impl.spec.mustache +97 -0
  37. package/templates/api.use-cases.contract.mustache +34 -0
  38. package/templates/api.use-cases.impl.mustache +32 -0
  39. package/templates/api.use-cases.impl.spec.mustache +94 -0
  40. package/templates/api.use-cases.mock.mustache +21 -0
  41. package/templates/dto.mock.mustache +16 -0
  42. package/templates/mapper.mustache +28 -0
  43. package/templates/mapper.spec.mustache +39 -0
  44. package/templates/model-entity.mustache +24 -0
  45. package/templates/model-entity.spec.mustache +34 -0
  46. package/templates/model.mock.mustache +14 -0
  47. package/templates/model.mustache +20 -0
  48. package/templates/repository.provider.mock.mustache +20 -0
  49. package/templates/repository.provider.mustache +26 -0
  50. package/templates/use-cases.provider.mock.mustache +20 -0
  51. package/templates/use-cases.provider.mustache +26 -0
  52. package/tsconfig.json +17 -0
package/dist/main.js ADDED
@@ -0,0 +1,180 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const fs_extra_1 = __importDefault(require("fs-extra"));
8
+ const mustache_1 = __importDefault(require("mustache"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const commander_1 = require("commander");
11
+ const logger_1 = require("./src/utils/logger");
12
+ const openapi_generator_1 = require("./src/utils/openapi-generator");
13
+ const filesystem_1 = require("./src/utils/filesystem");
14
+ const analyzer_1 = require("./src/swagger/analyzer");
15
+ const dto_generator_1 = require("./src/generators/dto.generator");
16
+ const clean_arch_generator_1 = require("./src/generators/clean-arch.generator");
17
+ const report_generator_1 = require("./src/generators/report.generator");
18
+ const lint_generator_1 = require("./src/generators/lint.generator");
19
+ const environment_finder_1 = require("./src/utils/environment-finder");
20
+ const prompt_1 = require("./src/utils/prompt");
21
+ const config_1 = require("./src/utils/config");
22
+ const package_json_1 = __importDefault(require("./package.json"));
23
+ // Disable HTML escaping so that < and > produce valid TypeScript generic types.
24
+ mustache_1.default.escape = function (text) {
25
+ return text;
26
+ };
27
+ // ── CLI CONFIGURATION ────────────────────────────────────────────────────────
28
+ commander_1.program
29
+ .name('generate-clean-arch')
30
+ .description('Angular Clean Architecture code generator from OpenAPI/Swagger')
31
+ .version(package_json_1.default.version)
32
+ .option('-i, --input <file>', 'OpenAPI/Swagger file (yaml or json)', 'swagger.yaml')
33
+ .option('-o, --output <dir>', 'Output directory', './src/app')
34
+ .option('-t, --templates <dir>', 'Custom templates directory', path_1.default.join(__dirname, 'templates'))
35
+ .option('--skip-install', 'Skip dependency installation')
36
+ .option('--dry-run', 'Simulate without generating files')
37
+ .option('--skip-lint', 'Skip post-generation linting and formatting')
38
+ .option('-s, --select-endpoints', 'Interactively select which tags and endpoints to generate')
39
+ .option('-c, --config <file>', 'Use a JSON configuration file (skips interactive prompts)')
40
+ .option('--init-config [file]', 'Generate a JSON configuration file instead of generating code')
41
+ .parse(process.argv);
42
+ const options = commander_1.program.opts();
43
+ // ── MAIN ORCHESTRATOR ────────────────────────────────────────────────────────
44
+ async function main() {
45
+ console.log('\n' + '='.repeat(60));
46
+ (0, logger_1.log)(' OpenAPI Clean Architecture Generator', 'bright');
47
+ (0, logger_1.log)(' Angular + Clean Architecture Code Generator', 'cyan');
48
+ console.log('='.repeat(60) + '\n');
49
+ const logPath = path_1.default.join(process.cwd(), 'generation.log');
50
+ (0, logger_1.initGenerationLog)(logPath);
51
+ // ── CONFIG FILE: override CLI defaults with config values ─────────────────
52
+ const configFile = options.config;
53
+ const generationConfig = configFile ? (0, config_1.loadConfig)(configFile) : undefined;
54
+ if (generationConfig) {
55
+ if (generationConfig.input)
56
+ options.input = generationConfig.input;
57
+ if (generationConfig.output)
58
+ options.output = generationConfig.output;
59
+ if (generationConfig.templates)
60
+ options.templates = generationConfig.templates;
61
+ if (generationConfig.skipInstall !== undefined)
62
+ options.skipInstall = generationConfig.skipInstall;
63
+ if (generationConfig.skipLint !== undefined)
64
+ options.skipLint = generationConfig.skipLint;
65
+ (0, logger_1.logDetail)('config', `Using configuration file: ${configFile}`);
66
+ }
67
+ (0, logger_1.logDetail)('config', `Input: ${options.input}`);
68
+ (0, logger_1.logDetail)('config', `Output: ${options.output}`);
69
+ (0, logger_1.logDetail)('config', `Templates: ${options.templates}`);
70
+ if (!fs_extra_1.default.existsSync(options.input)) {
71
+ (0, logger_1.logError)(`File not found: ${options.input}`);
72
+ process.exit(1);
73
+ }
74
+ if (options.dryRun) {
75
+ (0, logger_1.logWarning)('DRY RUN mode — no files will be generated');
76
+ }
77
+ if (!(0, openapi_generator_1.checkOpenApiGenerator)()) {
78
+ (0, logger_1.logWarning)('OpenAPI Generator CLI not found');
79
+ if (!options.skipInstall) {
80
+ (0, openapi_generator_1.installOpenApiGenerator)();
81
+ }
82
+ else {
83
+ (0, logger_1.logError)('Install openapi-generator-cli with: npm install -g @openapitools/openapi-generator-cli');
84
+ process.exit(1);
85
+ }
86
+ }
87
+ else {
88
+ (0, logger_1.logSuccess)('OpenAPI Generator CLI found');
89
+ }
90
+ const analysis = (0, analyzer_1.analyzeSwagger)(options.input);
91
+ const tagSummaries = (0, clean_arch_generator_1.extractTagsWithOperations)(analysis);
92
+ // ── INIT CONFIG MODE: generate config file and exit ───────────────────────
93
+ if (options.initConfig !== undefined) {
94
+ const envFile = (0, environment_finder_1.findEnvironmentFile)(process.cwd());
95
+ let apiKeys = [];
96
+ if (envFile) {
97
+ const envContent = fs_extra_1.default.readFileSync(envFile, 'utf8');
98
+ apiKeys = (0, environment_finder_1.parseApiKeys)(envContent);
99
+ }
100
+ const defaultConfig = (0, config_1.generateDefaultConfig)(analysis, tagSummaries, options, apiKeys);
101
+ const outputFile = typeof options.initConfig === 'string' ? options.initConfig : 'generation-config.json';
102
+ (0, config_1.writeConfig)(defaultConfig, outputFile);
103
+ (0, logger_1.logSuccess)(`Configuration file generated: ${outputFile}`);
104
+ (0, logger_1.logDetail)('config', 'Edit the file to customise tags, endpoints and baseUrls, then run with --config');
105
+ return;
106
+ }
107
+ if (options.dryRun) {
108
+ (0, logger_1.logWarning)('Finishing in DRY RUN mode');
109
+ return;
110
+ }
111
+ (0, filesystem_1.createDirectoryStructure)(options.output);
112
+ // ── SELECTION: tags and endpoints ─────────────────────────────────────────
113
+ let selectionFilter = {};
114
+ let tagApiKeyMap;
115
+ if (generationConfig) {
116
+ // Config-driven: derive everything from the JSON file
117
+ selectionFilter = (0, config_1.deriveSelectionFilter)(generationConfig);
118
+ tagApiKeyMap = (0, config_1.deriveTagApiKeyMap)(generationConfig);
119
+ (0, logger_1.logDetail)('config', `Tags from config: ${Object.keys(generationConfig.tags).join(', ')}`);
120
+ Object.entries(tagApiKeyMap).forEach(([tag, key]) => {
121
+ (0, logger_1.logDetail)('config', `API key for "${tag}": environment.${key}.url`);
122
+ });
123
+ }
124
+ else {
125
+ // Interactive mode (original behaviour)
126
+ if (options.selectEndpoints) {
127
+ selectionFilter = await (0, prompt_1.askSelectionFilter)(tagSummaries);
128
+ }
129
+ const selectedTags = options.selectEndpoints
130
+ ? Object.keys(selectionFilter)
131
+ : tagSummaries.map((t) => t.tag);
132
+ // ── ENVIRONMENT API KEY SELECTION ────────────────────────────────────────
133
+ const envFile = (0, environment_finder_1.findEnvironmentFile)(process.cwd());
134
+ let apiKeys = [];
135
+ if (envFile) {
136
+ const envContent = fs_extra_1.default.readFileSync(envFile, 'utf8');
137
+ apiKeys = (0, environment_finder_1.parseApiKeys)(envContent);
138
+ (0, logger_1.logSuccess)(`environment.ts found: ${logger_1.colors.cyan}${path_1.default.relative(process.cwd(), envFile)}${logger_1.colors.reset}`);
139
+ if (apiKeys.length === 0) {
140
+ (0, logger_1.logWarning)('No keys containing "api" found in environment.ts. Will be requested manually.');
141
+ }
142
+ }
143
+ else {
144
+ (0, logger_1.logWarning)('No environment.ts found. The key will be requested manually per repository.');
145
+ }
146
+ tagApiKeyMap = await (0, prompt_1.askApiKeysForTags)(selectedTags, apiKeys);
147
+ Object.entries(tagApiKeyMap).forEach(([tag, key]) => {
148
+ (0, logger_1.logDetail)('config', `API key for "${tag}": environment.${key}.url`);
149
+ });
150
+ }
151
+ // ──────────────────────────────────────────────────────────────────────────
152
+ const tempDir = (0, dto_generator_1.generateCode)(options.input, options.templates);
153
+ (0, dto_generator_1.organizeFiles)(tempDir, options.output);
154
+ (0, dto_generator_1.addDtoImports)(options.output);
155
+ (0, clean_arch_generator_1.generateCleanArchitecture)(analysis, options.output, options.templates, tagApiKeyMap, selectionFilter);
156
+ (0, filesystem_1.cleanup)(tempDir);
157
+ const noLintResult = {
158
+ prettier: { ran: false, filesFormatted: 0 },
159
+ eslint: { ran: false, filesFixed: 0 }
160
+ };
161
+ const lintResult = options.skipLint ? noLintResult : (0, lint_generator_1.lintGeneratedFiles)(options.output);
162
+ const report = (0, report_generator_1.generateReport)(options.output, analysis, lintResult);
163
+ console.log('\n' + '='.repeat(60));
164
+ (0, logger_1.log)(' ✨ Generation completed successfully', 'green');
165
+ console.log('='.repeat(60));
166
+ console.log(`\n📊 Summary:`);
167
+ console.log(` - DTOs generated: ${report.structure.dtos}`);
168
+ console.log(` - Repositories: ${report.structure.repositories}`);
169
+ console.log(` - Mappers: ${report.structure.mappers}`);
170
+ console.log(` - Use Cases: ${report.structure.useCases}`);
171
+ console.log(` - Providers: ${report.structure.providers}`);
172
+ console.log(` - Mocks: ${report.structure.mocks}`);
173
+ console.log(`\n📁 Files generated in: ${logger_1.colors.cyan}${options.output}${logger_1.colors.reset}\n`);
174
+ }
175
+ main().catch((error) => {
176
+ const err = error;
177
+ (0, logger_1.logError)(`Fatal error: ${err.message}`);
178
+ console.error(error);
179
+ process.exit(1);
180
+ });
@@ -0,0 +1,33 @@
1
+ const eslint = require('@eslint/js');
2
+ const tseslint = require('typescript-eslint');
3
+ const eslintPluginPrettierRecommended = require('eslint-plugin-prettier/recommended');
4
+
5
+ module.exports = tseslint.config(
6
+ eslint.configs.recommended,
7
+ ...tseslint.configs.recommendedTypeChecked,
8
+ eslintPluginPrettierRecommended,
9
+ {
10
+ languageOptions: {
11
+ parserOptions: {
12
+ project: ['./tsconfig.json'],
13
+ tsconfigRootDir: __dirname
14
+ }
15
+ },
16
+ rules: {
17
+ '@typescript-eslint/no-explicit-any': 'error',
18
+ '@typescript-eslint/explicit-function-return-type': 'warn',
19
+ '@typescript-eslint/no-unsafe-member-access': 'off',
20
+ '@typescript-eslint/no-unsafe-assignment': 'off',
21
+ '@typescript-eslint/no-unsafe-call': 'off',
22
+ '@typescript-eslint/no-unsafe-argument': 'off',
23
+ '@typescript-eslint/require-await': 'off',
24
+ '@typescript-eslint/no-unused-vars': [
25
+ 'warn',
26
+ { argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_' }
27
+ ]
28
+ }
29
+ },
30
+ {
31
+ ignores: ['dist/', 'node_modules/', 'eslint.config.js']
32
+ }
33
+ );
@@ -0,0 +1,150 @@
1
+ openapi: 3.0.1
2
+ info:
3
+ title: Example API
4
+ description: API de ejemplo para probar el generador
5
+ version: 1.0.0
6
+ tags:
7
+ - name: User
8
+ description: Operaciones de usuario
9
+ - name: Product
10
+ description: Operaciones de productos
11
+ paths:
12
+ /v1/users:
13
+ get:
14
+ tags:
15
+ - User
16
+ summary: Obtener lista de usuarios
17
+ operationId: getUsers
18
+ parameters:
19
+ - name: search
20
+ in: query
21
+ required: false
22
+ schema:
23
+ type: string
24
+ responses:
25
+ '200':
26
+ description: OK
27
+ content:
28
+ application/json:
29
+ schema:
30
+ $ref: '#/components/schemas/UserResponse'
31
+ post:
32
+ tags:
33
+ - User
34
+ summary: Crear usuario
35
+ operationId: createUser
36
+ requestBody:
37
+ required: true
38
+ content:
39
+ application/json:
40
+ schema:
41
+ $ref: '#/components/schemas/CreateUserRequest'
42
+ responses:
43
+ '201':
44
+ description: Created
45
+ content:
46
+ application/json:
47
+ schema:
48
+ $ref: '#/components/schemas/UserSchema'
49
+ /v1/users/{id}:
50
+ get:
51
+ tags:
52
+ - User
53
+ summary: Obtener usuario por ID
54
+ operationId: getUserById
55
+ parameters:
56
+ - name: id
57
+ in: path
58
+ required: true
59
+ schema:
60
+ type: integer
61
+ responses:
62
+ '200':
63
+ description: OK
64
+ content:
65
+ application/json:
66
+ schema:
67
+ $ref: '#/components/schemas/UserSchema'
68
+ delete:
69
+ tags:
70
+ - User
71
+ summary: Eliminar usuario
72
+ operationId: deleteUser
73
+ parameters:
74
+ - name: id
75
+ in: path
76
+ required: true
77
+ schema:
78
+ type: integer
79
+ responses:
80
+ '204':
81
+ description: No Content
82
+ /v1/products:
83
+ get:
84
+ tags:
85
+ - Product
86
+ summary: Obtener lista de productos
87
+ operationId: getProducts
88
+ responses:
89
+ '200':
90
+ description: OK
91
+ content:
92
+ application/json:
93
+ schema:
94
+ $ref: '#/components/schemas/ProductResponse'
95
+ components:
96
+ schemas:
97
+ UserSchema:
98
+ type: object
99
+ properties:
100
+ id:
101
+ type: integer
102
+ example: 1
103
+ name:
104
+ type: string
105
+ example: Juan Pérez
106
+ email:
107
+ type: string
108
+ example: juan@example.com
109
+ active:
110
+ type: boolean
111
+ example: true
112
+ CreateUserRequest:
113
+ type: object
114
+ required:
115
+ - name
116
+ - email
117
+ properties:
118
+ name:
119
+ type: string
120
+ example: Juan Pérez
121
+ email:
122
+ type: string
123
+ example: juan@example.com
124
+ UserResponse:
125
+ type: object
126
+ properties:
127
+ users:
128
+ type: array
129
+ items:
130
+ $ref: '#/components/schemas/UserSchema'
131
+ ProductSchema:
132
+ type: object
133
+ properties:
134
+ id:
135
+ type: integer
136
+ example: 100
137
+ name:
138
+ type: string
139
+ example: Laptop HP
140
+ price:
141
+ type: number
142
+ format: float
143
+ example: 599.99
144
+ ProductResponse:
145
+ type: object
146
+ properties:
147
+ products:
148
+ type: array
149
+ items:
150
+ $ref: '#/components/schemas/ProductSchema'
@@ -0,0 +1,24 @@
1
+ {
2
+ "input": "example-swagger.yaml",
3
+ "output": "./src/app",
4
+ "skipLint": false,
5
+ "skipInstall": false,
6
+ "tags": {
7
+ "User": {
8
+ "baseUrl": "apiUrl",
9
+ "endpoints": [
10
+ "getUsers",
11
+ "createUser",
12
+ "getUserById",
13
+ "deleteUser"
14
+ ]
15
+ },
16
+ "Product": {
17
+ "baseUrl": "apiUrl",
18
+ "endpoints": [
19
+ "getProducts"
20
+ ]
21
+ }
22
+ },
23
+ "templates": "/Users/bsantome/Downloads/openapi-clean-arch-generator/templates"
24
+ }
package/main.ts ADDED
@@ -0,0 +1,233 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'fs-extra';
4
+ import mustache from 'mustache';
5
+ import path from 'path';
6
+ import { program } from 'commander';
7
+
8
+ import {
9
+ log,
10
+ logSuccess,
11
+ logWarning,
12
+ logError,
13
+ logDetail,
14
+ initGenerationLog,
15
+ colors
16
+ } from './src/utils/logger';
17
+ import { checkOpenApiGenerator, installOpenApiGenerator } from './src/utils/openapi-generator';
18
+ import { createDirectoryStructure, cleanup } from './src/utils/filesystem';
19
+ import { analyzeSwagger } from './src/swagger/analyzer';
20
+ import { generateCode, organizeFiles, addDtoImports } from './src/generators/dto.generator';
21
+ import {
22
+ generateCleanArchitecture,
23
+ extractTagsWithOperations
24
+ } from './src/generators/clean-arch.generator';
25
+ import { generateReport } from './src/generators/report.generator';
26
+ import { lintGeneratedFiles } from './src/generators/lint.generator';
27
+ import { findEnvironmentFile, parseApiKeys } from './src/utils/environment-finder';
28
+ import { askApiKeysForTags, askSelectionFilter } from './src/utils/prompt';
29
+ import {
30
+ loadConfig,
31
+ generateDefaultConfig,
32
+ writeConfig,
33
+ deriveSelectionFilter,
34
+ deriveTagApiKeyMap
35
+ } from './src/utils/config';
36
+ import type { SelectionFilter, LintResult } from './src/types';
37
+ import type { CliOptions } from './src/types';
38
+ import packageJson from './package.json';
39
+
40
+ // Disable HTML escaping so that < and > produce valid TypeScript generic types.
41
+ (mustache as { escape: (text: string) => string }).escape = function (text: string): string {
42
+ return text;
43
+ };
44
+
45
+ // ── CLI CONFIGURATION ────────────────────────────────────────────────────────
46
+
47
+ program
48
+ .name('generate-clean-arch')
49
+ .description('Angular Clean Architecture code generator from OpenAPI/Swagger')
50
+ .version(packageJson.version)
51
+ .option('-i, --input <file>', 'OpenAPI/Swagger file (yaml or json)', 'swagger.yaml')
52
+ .option('-o, --output <dir>', 'Output directory', './src/app')
53
+ .option('-t, --templates <dir>', 'Custom templates directory', path.join(__dirname, 'templates'))
54
+ .option('--skip-install', 'Skip dependency installation')
55
+ .option('--dry-run', 'Simulate without generating files')
56
+ .option('--skip-lint', 'Skip post-generation linting and formatting')
57
+ .option('-s, --select-endpoints', 'Interactively select which tags and endpoints to generate')
58
+ .option('-c, --config <file>', 'Use a JSON configuration file (skips interactive prompts)')
59
+ .option('--init-config [file]', 'Generate a JSON configuration file instead of generating code')
60
+ .parse(process.argv);
61
+
62
+ const options = program.opts<CliOptions>();
63
+
64
+ // ── MAIN ORCHESTRATOR ────────────────────────────────────────────────────────
65
+
66
+ async function main(): Promise<void> {
67
+ console.log('\n' + '='.repeat(60));
68
+ log(' OpenAPI Clean Architecture Generator', 'bright');
69
+ log(' Angular + Clean Architecture Code Generator', 'cyan');
70
+ console.log('='.repeat(60) + '\n');
71
+
72
+ const logPath = path.join(process.cwd(), 'generation.log');
73
+ initGenerationLog(logPath);
74
+
75
+ // ── CONFIG FILE: override CLI defaults with config values ─────────────────
76
+ const configFile = options.config;
77
+ const generationConfig = configFile ? loadConfig(configFile) : undefined;
78
+
79
+ if (generationConfig) {
80
+ if (generationConfig.input) options.input = generationConfig.input;
81
+ if (generationConfig.output) options.output = generationConfig.output;
82
+ if (generationConfig.templates) options.templates = generationConfig.templates;
83
+ if (generationConfig.skipInstall !== undefined)
84
+ options.skipInstall = generationConfig.skipInstall;
85
+ if (generationConfig.skipLint !== undefined) options.skipLint = generationConfig.skipLint;
86
+ logDetail('config', `Using configuration file: ${configFile}`);
87
+ }
88
+
89
+ logDetail('config', `Input: ${options.input}`);
90
+ logDetail('config', `Output: ${options.output}`);
91
+ logDetail('config', `Templates: ${options.templates}`);
92
+
93
+ if (!fs.existsSync(options.input)) {
94
+ logError(`File not found: ${options.input}`);
95
+ process.exit(1);
96
+ }
97
+
98
+ if (options.dryRun) {
99
+ logWarning('DRY RUN mode — no files will be generated');
100
+ }
101
+
102
+ if (!checkOpenApiGenerator()) {
103
+ logWarning('OpenAPI Generator CLI not found');
104
+ if (!options.skipInstall) {
105
+ installOpenApiGenerator();
106
+ } else {
107
+ logError(
108
+ 'Install openapi-generator-cli with: npm install -g @openapitools/openapi-generator-cli'
109
+ );
110
+ process.exit(1);
111
+ }
112
+ } else {
113
+ logSuccess('OpenAPI Generator CLI found');
114
+ }
115
+
116
+ const analysis = analyzeSwagger(options.input);
117
+ const tagSummaries = extractTagsWithOperations(analysis);
118
+
119
+ // ── INIT CONFIG MODE: generate config file and exit ───────────────────────
120
+ if (options.initConfig !== undefined) {
121
+ const envFile = findEnvironmentFile(process.cwd());
122
+ let apiKeys: ReturnType<typeof parseApiKeys> = [];
123
+ if (envFile) {
124
+ const envContent = fs.readFileSync(envFile, 'utf8');
125
+ apiKeys = parseApiKeys(envContent);
126
+ }
127
+
128
+ const defaultConfig = generateDefaultConfig(analysis, tagSummaries, options, apiKeys);
129
+ const outputFile =
130
+ typeof options.initConfig === 'string' ? options.initConfig : 'generation-config.json';
131
+
132
+ writeConfig(defaultConfig, outputFile);
133
+ logSuccess(`Configuration file generated: ${outputFile}`);
134
+ logDetail(
135
+ 'config',
136
+ 'Edit the file to customise tags, endpoints and baseUrls, then run with --config'
137
+ );
138
+ return;
139
+ }
140
+
141
+ if (options.dryRun) {
142
+ logWarning('Finishing in DRY RUN mode');
143
+ return;
144
+ }
145
+
146
+ createDirectoryStructure(options.output);
147
+
148
+ // ── SELECTION: tags and endpoints ─────────────────────────────────────────
149
+ let selectionFilter: SelectionFilter = {};
150
+ let tagApiKeyMap: Record<string, string>;
151
+
152
+ if (generationConfig) {
153
+ // Config-driven: derive everything from the JSON file
154
+ selectionFilter = deriveSelectionFilter(generationConfig);
155
+ tagApiKeyMap = deriveTagApiKeyMap(generationConfig);
156
+ logDetail('config', `Tags from config: ${Object.keys(generationConfig.tags).join(', ')}`);
157
+ Object.entries(tagApiKeyMap).forEach(([tag, key]) => {
158
+ logDetail('config', `API key for "${tag}": environment.${key}.url`);
159
+ });
160
+ } else {
161
+ // Interactive mode (original behaviour)
162
+ if (options.selectEndpoints) {
163
+ selectionFilter = await askSelectionFilter(tagSummaries);
164
+ }
165
+
166
+ const selectedTags = options.selectEndpoints
167
+ ? Object.keys(selectionFilter)
168
+ : tagSummaries.map((t) => t.tag);
169
+
170
+ // ── ENVIRONMENT API KEY SELECTION ────────────────────────────────────────
171
+ const envFile = findEnvironmentFile(process.cwd());
172
+ let apiKeys: ReturnType<typeof parseApiKeys> = [];
173
+
174
+ if (envFile) {
175
+ const envContent = fs.readFileSync(envFile, 'utf8');
176
+ apiKeys = parseApiKeys(envContent);
177
+ logSuccess(
178
+ `environment.ts found: ${colors.cyan}${path.relative(process.cwd(), envFile)}${colors.reset}`
179
+ );
180
+ if (apiKeys.length === 0) {
181
+ logWarning('No keys containing "api" found in environment.ts. Will be requested manually.');
182
+ }
183
+ } else {
184
+ logWarning('No environment.ts found. The key will be requested manually per repository.');
185
+ }
186
+
187
+ tagApiKeyMap = await askApiKeysForTags(selectedTags, apiKeys);
188
+ Object.entries(tagApiKeyMap).forEach(([tag, key]) => {
189
+ logDetail('config', `API key for "${tag}": environment.${key}.url`);
190
+ });
191
+ }
192
+
193
+ // ──────────────────────────────────────────────────────────────────────────
194
+
195
+ const tempDir = generateCode(options.input, options.templates);
196
+ organizeFiles(tempDir, options.output);
197
+ addDtoImports(options.output);
198
+ generateCleanArchitecture(
199
+ analysis,
200
+ options.output,
201
+ options.templates,
202
+ tagApiKeyMap,
203
+ selectionFilter
204
+ );
205
+ cleanup(tempDir);
206
+
207
+ const noLintResult: LintResult = {
208
+ prettier: { ran: false, filesFormatted: 0 },
209
+ eslint: { ran: false, filesFixed: 0 }
210
+ };
211
+ const lintResult = options.skipLint ? noLintResult : lintGeneratedFiles(options.output);
212
+
213
+ const report = generateReport(options.output, analysis, lintResult);
214
+
215
+ console.log('\n' + '='.repeat(60));
216
+ log(' ✨ Generation completed successfully', 'green');
217
+ console.log('='.repeat(60));
218
+ console.log(`\n📊 Summary:`);
219
+ console.log(` - DTOs generated: ${report.structure.dtos}`);
220
+ console.log(` - Repositories: ${report.structure.repositories}`);
221
+ console.log(` - Mappers: ${report.structure.mappers}`);
222
+ console.log(` - Use Cases: ${report.structure.useCases}`);
223
+ console.log(` - Providers: ${report.structure.providers}`);
224
+ console.log(` - Mocks: ${report.structure.mocks}`);
225
+ console.log(`\n📁 Files generated in: ${colors.cyan}${options.output}${colors.reset}\n`);
226
+ }
227
+
228
+ main().catch((error: unknown) => {
229
+ const err = error as Error;
230
+ logError(`Fatal error: ${err.message}`);
231
+ console.error(error);
232
+ process.exit(1);
233
+ });
@@ -0,0 +1,23 @@
1
+ {
2
+ "$schema": "node_modules/@openapitools/openapi-generator-cli/config.schema.json",
3
+ "spaces": 2,
4
+ "generator-cli": {
5
+ "version": "7.2.0",
6
+ "generators": {
7
+ "typescript-angular-clean": {
8
+ "generatorName": "typescript-angular",
9
+ "output": "./.temp-generated",
10
+ "glob": "**/*",
11
+ "additionalProperties": {
12
+ "ngVersion": "17.0.0",
13
+ "modelPropertyNaming": "camelCase",
14
+ "supportsES6": true,
15
+ "withInterfaces": true,
16
+ "providedInRoot": false,
17
+ "npmName": "api-client",
18
+ "npmVersion": "1.0.0"
19
+ }
20
+ }
21
+ }
22
+ }
23
+ }