@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
@@ -0,0 +1,118 @@
1
+ import fs from 'fs-extra';
2
+ import { logInfo, logError } from './logger';
3
+ import type { GenerationConfig, TagConfig } from '../types';
4
+ import type { SwaggerAnalysis } from '../types';
5
+ import type { TagSummary } from '../types';
6
+ import type { ApiKeyInfo } from './environment-finder';
7
+
8
+ /**
9
+ * Loads and validates a GenerationConfig from a JSON file.
10
+ */
11
+ export function loadConfig(filePath: string): GenerationConfig {
12
+ if (!fs.existsSync(filePath)) {
13
+ logError(`Configuration file not found: ${filePath}`);
14
+ process.exit(1);
15
+ }
16
+
17
+ const raw = fs.readFileSync(filePath, 'utf8');
18
+ let config: GenerationConfig;
19
+
20
+ try {
21
+ config = JSON.parse(raw) as GenerationConfig;
22
+ } catch {
23
+ logError(`Invalid JSON in configuration file: ${filePath}`);
24
+ process.exit(1);
25
+ }
26
+
27
+ if (!config.tags || typeof config.tags !== 'object') {
28
+ logError('Configuration file must contain a "tags" object');
29
+ process.exit(1);
30
+ }
31
+
32
+ for (const [tag, tagConfig] of Object.entries(config.tags)) {
33
+ if (!tagConfig.baseUrl || typeof tagConfig.baseUrl !== 'string') {
34
+ logError(`Tag "${tag}" must have a "baseUrl" string`);
35
+ process.exit(1);
36
+ }
37
+ if (!Array.isArray(tagConfig.endpoints) || tagConfig.endpoints.length === 0) {
38
+ logError(`Tag "${tag}" must have a non-empty "endpoints" array`);
39
+ process.exit(1);
40
+ }
41
+ }
42
+
43
+ return config;
44
+ }
45
+
46
+ /**
47
+ * Builds a default GenerationConfig from a swagger analysis, including all tags
48
+ * and all endpoints. Useful for --init-config to scaffold a config template.
49
+ */
50
+ export function generateDefaultConfig(
51
+ analysis: SwaggerAnalysis,
52
+ tagSummaries: TagSummary[],
53
+ cliOptions: {
54
+ input: string;
55
+ output: string;
56
+ templates?: string;
57
+ skipLint?: boolean;
58
+ skipInstall?: boolean;
59
+ },
60
+ apiKeys: ApiKeyInfo[]
61
+ ): GenerationConfig {
62
+ const tags: Record<string, TagConfig> = {};
63
+
64
+ for (const summary of tagSummaries) {
65
+ const matchingKey = apiKeys.find((k) =>
66
+ k.key.toLowerCase().includes(summary.tag.toLowerCase())
67
+ );
68
+
69
+ tags[summary.tag] = {
70
+ baseUrl: matchingKey?.key || 'apiUrl',
71
+ endpoints: summary.operations.map((op) => op.nickname)
72
+ };
73
+ }
74
+
75
+ const config: GenerationConfig = {
76
+ input: cliOptions.input,
77
+ output: cliOptions.output,
78
+ skipLint: cliOptions.skipLint ?? false,
79
+ skipInstall: cliOptions.skipInstall ?? false,
80
+ tags
81
+ };
82
+
83
+ if (cliOptions.templates) {
84
+ config.templates = cliOptions.templates;
85
+ }
86
+
87
+ return config;
88
+ }
89
+
90
+ /**
91
+ * Writes a GenerationConfig to a JSON file.
92
+ */
93
+ export function writeConfig(config: GenerationConfig, filePath: string): void {
94
+ fs.writeFileSync(filePath, JSON.stringify(config, null, 2) + '\n', 'utf8');
95
+ logInfo(`Configuration file written to: ${filePath}`);
96
+ }
97
+
98
+ /**
99
+ * Derives the selectionFilter (tag → endpoint nicknames) from a GenerationConfig.
100
+ */
101
+ export function deriveSelectionFilter(config: GenerationConfig): Record<string, string[]> {
102
+ const filter: Record<string, string[]> = {};
103
+ for (const [tag, tagConfig] of Object.entries(config.tags)) {
104
+ filter[tag] = [...tagConfig.endpoints];
105
+ }
106
+ return filter;
107
+ }
108
+
109
+ /**
110
+ * Derives the tagApiKeyMap (tag → baseUrl key) from a GenerationConfig.
111
+ */
112
+ export function deriveTagApiKeyMap(config: GenerationConfig): Record<string, string> {
113
+ const map: Record<string, string> = {};
114
+ for (const [tag, tagConfig] of Object.entries(config.tags)) {
115
+ map[tag] = tagConfig.baseUrl;
116
+ }
117
+ return map;
118
+ }
@@ -0,0 +1,53 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+
4
+ export interface ApiKeyInfo {
5
+ key: string;
6
+ url?: string;
7
+ }
8
+
9
+ const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', '.angular', 'coverage', '.cache']);
10
+
11
+ /**
12
+ * Recursively searches for an `environment.ts` file starting from `dir`,
13
+ * up to `maxDepth` directory levels deep.
14
+ */
15
+ export function findEnvironmentFile(dir: string, maxDepth = 8, currentDepth = 0): string | null {
16
+ if (currentDepth > maxDepth) return null;
17
+ try {
18
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
19
+ for (const entry of entries) {
20
+ if (SKIP_DIRS.has(entry.name)) continue;
21
+ const fullPath = path.join(dir, entry.name);
22
+ if (entry.isFile() && entry.name === 'environment.ts') {
23
+ return fullPath;
24
+ }
25
+ if (entry.isDirectory()) {
26
+ const found = findEnvironmentFile(fullPath, maxDepth, currentDepth + 1);
27
+ if (found) return found;
28
+ }
29
+ }
30
+ } catch {
31
+ //bypass errors
32
+ }
33
+ return null;
34
+ }
35
+
36
+ /**
37
+ * Parses environment.ts content and returns all top-level keys that contain "api"
38
+ * (case-insensitive), along with their `url` value if present.
39
+ */
40
+ export function parseApiKeys(content: string): ApiKeyInfo[] {
41
+ const result: ApiKeyInfo[] = [];
42
+ const keyRegex = /^ {2}(\w*[Aa][Pp][Ii]\w*)\s*:/gm;
43
+ let match: RegExpExecArray | null;
44
+
45
+ while ((match = keyRegex.exec(content)) !== null) {
46
+ const key = match[1];
47
+ const afterKey = content.slice(match.index);
48
+ const urlMatch = afterKey.match(/url:\s*['"`]([^'"`\n]+)['"`]/);
49
+ result.push({ key, url: urlMatch?.[1] });
50
+ }
51
+
52
+ return result;
53
+ }
@@ -0,0 +1,31 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import { logSuccess, logInfo } from './logger';
4
+
5
+ /** Creates the required Clean Architecture directory structure (idempotent). */
6
+ export function createDirectoryStructure(baseDir: string): void {
7
+ const dirs = [
8
+ path.join(baseDir, 'data/dtos'),
9
+ path.join(baseDir, 'data/repositories'),
10
+ path.join(baseDir, 'data/mappers'),
11
+ path.join(baseDir, 'domain/repositories'),
12
+ path.join(baseDir, 'domain/use-cases'),
13
+ path.join(baseDir, 'di/repositories'),
14
+ path.join(baseDir, 'di/use-cases'),
15
+ path.join(baseDir, 'entities/models')
16
+ ];
17
+
18
+ dirs.forEach((dir) => {
19
+ fs.ensureDirSync(dir);
20
+ });
21
+
22
+ logSuccess('Directory structure created');
23
+ }
24
+
25
+ /** Removes a temporary directory and all its contents. */
26
+ export function cleanup(tempDir: string): void {
27
+ if (fs.existsSync(tempDir)) {
28
+ fs.removeSync(tempDir);
29
+ logInfo('Temporary files removed');
30
+ }
31
+ }
@@ -0,0 +1,60 @@
1
+ import fs from 'fs-extra';
2
+
3
+ const colors = {
4
+ reset: '\x1b[0m',
5
+ bright: '\x1b[1m',
6
+ green: '\x1b[32m',
7
+ blue: '\x1b[34m',
8
+ yellow: '\x1b[33m',
9
+ red: '\x1b[31m',
10
+ cyan: '\x1b[36m'
11
+ } as const;
12
+
13
+ type Color = keyof typeof colors;
14
+
15
+ let _logFilePath: string | null = null;
16
+
17
+ /** Initialises the generation log file, overwriting any previous run. */
18
+ export function initGenerationLog(filePath: string): void {
19
+ _logFilePath = filePath;
20
+ fs.writeFileSync(filePath, `Generation log — ${new Date().toISOString()}\n${'='.repeat(60)}\n`);
21
+ }
22
+
23
+ /** Writes a detailed entry to the generation log file (not to console). */
24
+ export function logDetail(category: string, message: string): void {
25
+ if (!_logFilePath) return;
26
+ const line = `[${new Date().toISOString()}] [${category.toUpperCase().padEnd(8)}] ${message}\n`;
27
+ fs.appendFileSync(_logFilePath, line);
28
+ }
29
+
30
+ /** Prints a console message with the given ANSI colour. */
31
+ export function log(message: string, color: Color = 'reset'): void {
32
+ console.log(`${colors[color]}${message}${colors.reset}`);
33
+ }
34
+
35
+ /** Prints a success message (green). */
36
+ export function logSuccess(message: string): void {
37
+ log(`✅ ${message}`, 'green');
38
+ }
39
+
40
+ /** Prints an informational message (blue). */
41
+ export function logInfo(message: string): void {
42
+ log(`ℹ️ ${message}`, 'blue');
43
+ }
44
+
45
+ /** Prints a warning message (yellow). */
46
+ export function logWarning(message: string): void {
47
+ log(`⚠️ ${message}`, 'yellow');
48
+ }
49
+
50
+ /** Prints an error message (red). */
51
+ export function logError(message: string): void {
52
+ log(`❌ ${message}`, 'red');
53
+ }
54
+
55
+ /** Prints a step/stage header (cyan). */
56
+ export function logStep(message: string): void {
57
+ log(`\n🚀 ${message}`, 'cyan');
58
+ }
59
+
60
+ export { colors };
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Resolves a TypeScript literal string to use as a mock value for a single schema property.
3
+ *
4
+ * Priority chain:
5
+ * $ref mock call → array $ref mock call → enum[0] → example → format fallback → type default
6
+ *
7
+ * @param propName Property name (used for format heuristics such as "email").
8
+ * @param prop Raw OpenAPI property definition.
9
+ * @param context 'dto' generates `mockFooDto()`, 'model' generates `mockFooModel()`.
10
+ */
11
+ export function resolveMockValue(
12
+ propName: string,
13
+ prop: {
14
+ type?: string;
15
+ format?: string;
16
+ example?: unknown;
17
+ enum?: unknown[];
18
+ $ref?: string;
19
+ items?: { $ref?: string; type?: string };
20
+ },
21
+ context: 'dto' | 'model' = 'dto'
22
+ ): string {
23
+ const suffix = context === 'dto' ? 'Dto' : 'Model';
24
+
25
+ // 1. Direct $ref → call the referenced mock factory
26
+ if (prop.$ref) {
27
+ const refName = prop.$ref.split('/').pop()!;
28
+ return `mock${refName}${suffix}()`;
29
+ }
30
+
31
+ // 2. Array of $ref → wrap referenced mock in an array
32
+ if (prop.type === 'array' && prop.items?.$ref) {
33
+ const refName = prop.items.$ref.split('/').pop()!;
34
+ return `[mock${refName}${suffix}()]`;
35
+ }
36
+
37
+ // 3. Array of primitives
38
+ if (prop.type === 'array') return '[]';
39
+
40
+ // 4. Enum → first declared value
41
+ if (prop.enum?.length) {
42
+ const first = prop.enum[0];
43
+ return typeof first === 'string' ? `'${first}'` : String(first);
44
+ }
45
+
46
+ // 5. Example value from the swagger spec (highest fidelity)
47
+ if (prop.example !== undefined) return formatLiteral(prop.example);
48
+
49
+ // 6. Format-aware fallbacks (when no example is provided)
50
+ if (prop.format === 'date-time') return `'2024-01-01T00:00:00.000Z'`;
51
+ if (prop.format === 'date') return `'2024-01-01'`;
52
+ if (prop.format === 'uuid') return `'00000000-0000-0000-0000-000000000000'`;
53
+ if (prop.format === 'uri') return `'https://example.com'`;
54
+ if (prop.format === 'email' || propName.toLowerCase().includes('email'))
55
+ return `'user@example.com'`;
56
+
57
+ // 7. Type defaults
58
+ if (prop.type === 'string') return `'value'`;
59
+ if (prop.type === 'integer' || prop.type === 'number') return `0`;
60
+ if (prop.type === 'boolean') return `false`;
61
+
62
+ return 'undefined';
63
+ }
64
+
65
+ function formatLiteral(value: unknown): string {
66
+ if (typeof value === 'string') return `'${value}'`;
67
+ if (typeof value === 'number') return `${value}`;
68
+ if (typeof value === 'boolean') return `${value}`;
69
+ return `'${String(value)}'`;
70
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Converts a PascalCase name to camelCase by lowercasing the first character.
3
+ * Used to derive class filenames and variable names from schema/tag names.
4
+ *
5
+ * @example
6
+ * toCamelCase('ProductResponse') // 'productResponse'
7
+ * toCamelCase('UserSchema') // 'userSchema'
8
+ */
9
+ export function toCamelCase(name: string): string {
10
+ if (!name) return name;
11
+ return name.charAt(0).toLowerCase() + name.slice(1);
12
+ }
@@ -0,0 +1,24 @@
1
+ import { execSync } from 'child_process';
2
+ import { logStep, logSuccess, logError } from './logger';
3
+
4
+ /** Checks whether `openapi-generator-cli` is available on the PATH. */
5
+ export function checkOpenApiGenerator(): boolean {
6
+ try {
7
+ execSync('openapi-generator-cli version', { stdio: 'ignore' });
8
+ return true;
9
+ } catch (_error) {
10
+ return false;
11
+ }
12
+ }
13
+
14
+ /** Installs `@openapitools/openapi-generator-cli` globally via npm. */
15
+ export function installOpenApiGenerator(): void {
16
+ logStep('Installing @openapitools/openapi-generator-cli...');
17
+ try {
18
+ execSync('npm install -g @openapitools/openapi-generator-cli', { stdio: 'inherit' });
19
+ logSuccess('OpenAPI Generator CLI installed successfully');
20
+ } catch (_error) {
21
+ logError('Error installing OpenAPI Generator CLI');
22
+ process.exit(1);
23
+ }
24
+ }
@@ -0,0 +1,183 @@
1
+ import prompts from 'prompts';
2
+ import { ApiKeyInfo } from './environment-finder';
3
+ import { colors } from './logger';
4
+ import type { TagSummary, SelectionFilter } from '../types';
5
+
6
+ function clearScreen(): void {
7
+ process.stdout.write('\x1Bc');
8
+ }
9
+
10
+ function printHeader(current?: number, total?: number): void {
11
+ const stepText =
12
+ current !== undefined && total !== undefined
13
+ ? ` [${colors.cyan}${current}${colors.reset} de ${colors.cyan}${total}${colors.reset}]`
14
+ : '';
15
+ console.log(`\n ${colors.bright}🔑 Configuración de URLs base${colors.reset}${stepText}`);
16
+ console.log(` ${'─'.repeat(54)}\n`);
17
+ }
18
+
19
+ function printSummary(tags: string[], result: Record<string, string>): void {
20
+ clearScreen();
21
+ console.log(`\n ${colors.bright}✅ Configuración completada${colors.reset}`);
22
+ console.log(` ${'─'.repeat(54)}\n`);
23
+ tags.forEach((tag) => {
24
+ console.log(` ${colors.bright}${tag}${colors.reset}`);
25
+ console.log(` ${colors.cyan}environment.${result[tag]}.url${colors.reset}\n`);
26
+ });
27
+ console.log(` ${'─'.repeat(54)}\n`);
28
+ }
29
+
30
+ /**
31
+ * Interactively asks the user which tags and endpoints to generate.
32
+ * Returns a SelectionFilter map of tag → selected operation nicknames.
33
+ */
34
+ export async function askSelectionFilter(tagSummaries: TagSummary[]): Promise<SelectionFilter> {
35
+ if (tagSummaries.length === 0) return {};
36
+
37
+ clearScreen();
38
+ console.log(`\n ${colors.bright}📋 Selección de tags y endpoints${colors.reset}`);
39
+ console.log(` ${'─'.repeat(54)}\n`);
40
+
41
+ // Step 1: select tags
42
+ const tagResponse = await prompts({
43
+ type: 'multiselect',
44
+ name: 'tags',
45
+ message: 'Tags a generar',
46
+ choices: tagSummaries.map((t) => ({
47
+ title: `${colors.bright}${t.tag}${colors.reset} ${colors.cyan}(${t.operations.length} endpoint${t.operations.length !== 1 ? 's' : ''})${colors.reset}`,
48
+ value: t.tag,
49
+ selected: true
50
+ })),
51
+ min: 1,
52
+ hint: 'Espacio para marcar/desmarcar, Enter para confirmar'
53
+ });
54
+
55
+ if (!tagResponse.tags?.length) process.exit(0);
56
+
57
+ const selectedTags: string[] = tagResponse.tags;
58
+ const filter: SelectionFilter = {};
59
+
60
+ // Step 2: for each selected tag, select endpoints
61
+ for (let i = 0; i < selectedTags.length; i++) {
62
+ const tag = selectedTags[i];
63
+ const summary = tagSummaries.find((t) => t.tag === tag)!;
64
+
65
+ clearScreen();
66
+ console.log(
67
+ `\n ${colors.bright}📋 Endpoints a generar${colors.reset} [${colors.cyan}${i + 1}${colors.reset} de ${colors.cyan}${selectedTags.length}${colors.reset}]`
68
+ );
69
+ console.log(` ${'─'.repeat(54)}\n`);
70
+
71
+ const opResponse = await prompts({
72
+ type: 'multiselect',
73
+ name: 'ops',
74
+ message: `Tag ${colors.bright}${tag}${colors.reset}`,
75
+ choices: summary.operations.map((op) => ({
76
+ title:
77
+ `${colors.bright}${op.method.padEnd(6)}${colors.reset} ${op.path}` +
78
+ (op.summary ? ` ${colors.cyan}${op.summary}${colors.reset}` : ''),
79
+ value: op.nickname,
80
+ selected: true
81
+ })),
82
+ min: 1,
83
+ hint: 'Espacio para marcar/desmarcar, Enter para confirmar'
84
+ });
85
+
86
+ if (!opResponse.ops?.length) process.exit(0);
87
+
88
+ filter[tag] = opResponse.ops;
89
+ }
90
+
91
+ return filter;
92
+ }
93
+
94
+ /**
95
+ * Interactively asks the user which environment API key to use for each tag,
96
+ * using arrow-key selection. The last option always allows typing manually.
97
+ * Returns a map of tag → environment key (e.g. { "SupplyingMaintenances": "suppliyingMaintenancesApi" }).
98
+ */
99
+ export async function askApiKeysForTags(
100
+ tags: string[],
101
+ apiKeys: ApiKeyInfo[]
102
+ ): Promise<Record<string, string>> {
103
+ if (tags.length === 0) return {};
104
+
105
+ clearScreen();
106
+ printHeader();
107
+
108
+ const modeResponse = await prompts({
109
+ type: 'select',
110
+ name: 'mode',
111
+ message: 'URL base para los repositorios',
112
+ choices: [
113
+ { title: `${colors.bright}La misma para todos${colors.reset}`, value: 'all' },
114
+ { title: `${colors.bright}Configurar individualmente${colors.reset}`, value: 'individual' }
115
+ ],
116
+ hint: ' '
117
+ });
118
+
119
+ if (modeResponse.mode === undefined) process.exit(0);
120
+
121
+ const result: Record<string, string> = {};
122
+
123
+ if (modeResponse.mode === 'all') {
124
+ clearScreen();
125
+ printHeader();
126
+ const sharedKey = await askApiKeyForTag('todos los repositorios', apiKeys);
127
+ tags.forEach((tag) => (result[tag] = sharedKey));
128
+ } else {
129
+ for (let i = 0; i < tags.length; i++) {
130
+ clearScreen();
131
+ printHeader(i + 1, tags.length);
132
+ result[tags[i]] = await askApiKeyForTag(tags[i], apiKeys);
133
+ }
134
+ }
135
+
136
+ printSummary(tags, result);
137
+ return result;
138
+ }
139
+
140
+ async function askApiKeyForTag(tagName: string, apiKeys: ApiKeyInfo[]): Promise<string> {
141
+ const MANUAL_VALUE = '__manual__';
142
+
143
+ const choices = [
144
+ ...apiKeys.map((k) => ({
145
+ title: k.url
146
+ ? `${colors.bright}${k.key}${colors.reset}\n ${colors.cyan}↳ ${k.url}${colors.reset}`
147
+ : `${colors.bright}${k.key}${colors.reset}`,
148
+ value: k.key
149
+ })),
150
+ {
151
+ title: `${colors.bright}Escribir manualmente${colors.reset}`,
152
+ value: MANUAL_VALUE
153
+ }
154
+ ];
155
+
156
+ const selectResponse = await prompts({
157
+ type: 'select',
158
+ name: 'key',
159
+ message: `Repositorio ${colors.bright}${tagName}${colors.reset}`,
160
+ choices,
161
+ hint: ' '
162
+ });
163
+
164
+ if (selectResponse.key === undefined) process.exit(0);
165
+
166
+ if (selectResponse.key !== MANUAL_VALUE) {
167
+ return selectResponse.key as string;
168
+ }
169
+
170
+ console.log();
171
+
172
+ const textResponse = await prompts({
173
+ type: 'text',
174
+ name: 'key',
175
+ message: `Clave de environment`,
176
+ hint: 'ej: aprovalmApi',
177
+ validate: (v: string) => v.trim().length > 0 || 'La clave no puede estar vacía'
178
+ });
179
+
180
+ if (textResponse.key === undefined) process.exit(0);
181
+
182
+ return (textResponse.key as string).trim();
183
+ }
@@ -0,0 +1,14 @@
1
+ /** Translates a primitive OpenAPI/Swagger type to its TypeScript equivalent. */
2
+ export function mapSwaggerTypeToTs(type?: string): string {
3
+ if (!type) return 'unknown';
4
+
5
+ const typeMap: Record<string, string> = {
6
+ integer: 'number',
7
+ string: 'string',
8
+ boolean: 'boolean',
9
+ number: 'number',
10
+ array: 'unknown[]',
11
+ object: 'unknown'
12
+ };
13
+ return typeMap[type] || 'unknown';
14
+ }
@@ -0,0 +1,34 @@
1
+ {{#apiInfo}}
2
+ {{#apis}}
3
+ {{#operations}}
4
+ import { InjectionToken } from '@angular/core';
5
+ import { Observable } from 'rxjs';
6
+ {{#imports}}
7
+ import { {{classname}} } from '@/entities/models/{{classFilename}}.model';
8
+ {{/imports}}
9
+
10
+ /**
11
+ * {{classname}} Repository Contract
12
+ * Generated from OpenAPI tag: {{classname}}
13
+ */
14
+ export interface {{classname}}Repository {
15
+ {{#operation}}
16
+ /**
17
+ * {{summary}}
18
+ {{#notes}}
19
+ * {{notes}}
20
+ {{/notes}}
21
+ {{#allParams}}
22
+ * @param {{paramName}} {{description}}
23
+ {{/allParams}}
24
+ */
25
+ {{nickname}}({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}{{^-last}}, {{/-last}}{{/allParams}}): Observable<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}}>;
26
+
27
+ {{/operation}}
28
+ }
29
+
30
+ export const {{constantName}}_REPOSITORY = new InjectionToken<{{classname}}Repository>('{{constantName}}_REPOSITORY');
31
+
32
+ {{/operations}}
33
+ {{/apis}}
34
+ {{/apiInfo}}
@@ -0,0 +1,21 @@
1
+ {{#apiInfo}}
2
+ {{#apis}}
3
+ {{#operations}}
4
+ import { MockService } from 'ng-mocks';
5
+ import { of } from 'rxjs';
6
+
7
+ import { {{classname}}RepositoryImpl } from '@/data/repositories/{{classFilename}}.repository.impl';
8
+ {{#returnImports}}
9
+ import { mock{{classname}}Model } from '@/entities/models/{{classFilename}}.model.mock';
10
+ {{/returnImports}}
11
+
12
+ export const mock{{classname}}RepositoryImpl = () =>
13
+ MockService({{classname}}RepositoryImpl, {
14
+ {{#operation}}
15
+ {{nickname}}: () => of({{#isListContainer}}[mock{{returnBaseType}}Model()]{{/isListContainer}}{{^isListContainer}}{{#returnBaseType}}mock{{returnBaseType}}Model(){{/returnBaseType}}{{^returnBaseType}}undefined{{/returnBaseType}}{{/isListContainer}}),
16
+ {{/operation}}
17
+ });
18
+
19
+ {{/operations}}
20
+ {{/apis}}
21
+ {{/apiInfo}}
@@ -0,0 +1,58 @@
1
+ {{#apiInfo}}
2
+ {{#apis}}
3
+ {{#operations}}
4
+ import { Injectable } from '@angular/core';
5
+ import { Observable } from 'rxjs';
6
+ import { map } from 'rxjs/operators';
7
+
8
+ import { environment } from '@environment';
9
+
10
+ import { MRepository } from '@mercadona/core/utils/repository';
11
+
12
+ import { {{classname}}Repository } from '@/domain/repositories/{{classFilename}}.repository.contract';
13
+ {{#returnImports}}
14
+ import { {{classname}}Dto } from '@/dtos/{{classFilename}}.dto';
15
+ import { {{classname}} } from '@/entities/models/{{classFilename}}.model';
16
+ import { {{classVarName}}Mapper } from '@/mappers/{{classFilename}}.mapper';
17
+ {{/returnImports}}
18
+ {{#paramImports}}
19
+ import { {{classname}} } from '@/entities/models/{{classFilename}}.model';
20
+ {{/paramImports}}
21
+
22
+ /**
23
+ * {{classname}} Repository Implementation
24
+ * Generated from OpenAPI tag: {{classname}}
25
+ */
26
+ @Injectable()
27
+ export class {{classname}}RepositoryImpl extends MRepository implements {{classname}}Repository {
28
+ constructor() {
29
+ super(`${environment.{{environmentApiKey}}.url}`);
30
+ }
31
+
32
+ {{#operation}}
33
+ {{nickname}}({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}{{^-last}}, {{/-last}}{{/allParams}}): Observable<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}}> {
34
+ {{#isListContainer}}
35
+ return this.{{httpMethod}}<{{{returnBaseType}}}Dto>('{{path}}'{{#hasOptions}}, { {{#hasQueryParams}}params: { {{#queryParams}}{{paramName}}{{^-last}}, {{/-last}}{{/queryParams}} }{{/hasQueryParams}}{{#hasBothParamsAndBody}}, {{/hasBothParamsAndBody}}{{#hasBodyParam}}body{{/hasBodyParam}} }{{/hasOptions}})
36
+ .pipe(
37
+ map((response) => response.{{#vendorExtensions}}{{x-response-property}}{{/vendorExtensions}}{{^vendorExtensions}}items{{/vendorExtensions}}.map({{{returnBaseTypeVarName}}}Mapper))
38
+ );
39
+ {{/isListContainer}}
40
+ {{^isListContainer}}
41
+ {{#returnType}}
42
+ return this.{{httpMethod}}<{{{returnType}}}Dto>('{{path}}'{{#hasOptions}}, { {{#hasQueryParams}}params: { {{#queryParams}}{{paramName}}{{^-last}}, {{/-last}}{{/queryParams}} }{{/hasQueryParams}}{{#hasBothParamsAndBody}}, {{/hasBothParamsAndBody}}{{#hasBodyParam}}body{{/hasBodyParam}} }{{/hasOptions}})
43
+ .pipe(
44
+ map({{{returnTypeVarName}}}Mapper)
45
+ );
46
+ {{/returnType}}
47
+ {{^returnType}}
48
+ return this.{{httpMethod}}<void>('{{path}}'{{#hasOptions}}, { {{#hasQueryParams}}params: { {{#queryParams}}{{paramName}}{{^-last}}, {{/-last}}{{/queryParams}} }{{/hasQueryParams}}{{#hasBothParamsAndBody}}, {{/hasBothParamsAndBody}}{{#hasBodyParam}}body{{/hasBodyParam}} }{{/hasOptions}});
49
+ {{/returnType}}
50
+ {{/isListContainer}}
51
+ }
52
+
53
+ {{/operation}}
54
+ }
55
+
56
+ {{/operations}}
57
+ {{/apis}}
58
+ {{/apiInfo}}