@0kmpo/openapi-clean-arch-generator 1.3.14 → 1.3.15
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.
- package/README.md +12 -0
- package/dist/main.js +24 -2
- package/dist/package.json +1 -1
- package/dist/src/generators/clean-arch.generator.js +200 -136
- package/dist/src/generators/dto.generator.js +42 -22
- package/dist/src/generators/report.generator.js +6 -0
- package/dist/src/utils/example-validator.js +86 -0
- package/dist/src/utils/mock-value-resolver.js +19 -7
- package/dist/src/utils/name-formatter.js +99 -2
- package/dist/templates/api.repository.contract.mustache +1 -1
- package/dist/templates/api.repository.impl.mock.mustache +2 -2
- package/dist/templates/api.repository.impl.mustache +5 -5
- package/dist/templates/api.repository.impl.spec.mustache +2 -2
- package/dist/templates/api.use-cases.contract.mustache +1 -1
- package/dist/templates/api.use-cases.impl.mustache +2 -2
- package/dist/templates/api.use-cases.impl.spec.mustache +2 -2
- package/dist/templates/api.use-cases.mock.mustache +2 -2
- package/dist/templates/dto.mock.mustache +1 -1
- package/dist/templates/mapper.mustache +2 -2
- package/dist/templates/mapper.spec.mustache +2 -2
- package/dist/templates/model-entity.mustache +1 -1
- package/dist/templates/model-entity.spec.mustache +4 -0
- package/dist/templates/model.mock.mustache +2 -2
- package/dist/templates/repository.provider.mock.mustache +2 -2
- package/dist/templates/repository.provider.mustache +2 -2
- package/dist/templates/use-cases.provider.mock.mustache +2 -2
- package/dist/templates/use-cases.provider.mustache +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -76,7 +76,10 @@ Options:
|
|
|
76
76
|
-o, --output <dir> Output directory [default: ./src/app]
|
|
77
77
|
-t, --templates <dir> Custom templates directory [default: ./templates]
|
|
78
78
|
-s, --select-endpoints Interactively select tags and endpoints to generate
|
|
79
|
+
-c, --config <file> Use a JSON configuration file (skips interactive prompts)
|
|
80
|
+
--init-config [file] Generate a JSON configuration file instead of generating code
|
|
79
81
|
--skip-install Skip dependency installation
|
|
82
|
+
--skip-lint Skip post-generation linting and formatting
|
|
80
83
|
--dry-run Simulate without writing files
|
|
81
84
|
-h, --help Show help
|
|
82
85
|
```
|
|
@@ -96,6 +99,15 @@ generate-clean-arch -i api.yaml -t ./my-templates
|
|
|
96
99
|
# Dry run (no files written)
|
|
97
100
|
generate-clean-arch -i swagger.yaml --dry-run
|
|
98
101
|
|
|
102
|
+
# Skip linting after generation
|
|
103
|
+
generate-clean-arch -i swagger.yaml --skip-lint
|
|
104
|
+
|
|
105
|
+
# Generate a config file to reuse later
|
|
106
|
+
generate-clean-arch --init-config generation-config.json
|
|
107
|
+
|
|
108
|
+
# Run using a config file (no interactive prompts)
|
|
109
|
+
generate-clean-arch -c generation-config.json
|
|
110
|
+
|
|
99
111
|
# Full example with all options
|
|
100
112
|
generate-clean-arch -i ./docs/api.yaml -o ./frontend/src/app -t ./custom-templates
|
|
101
113
|
```
|
package/dist/main.js
CHANGED
|
@@ -17,6 +17,7 @@ const clean_arch_generator_1 = require("./src/generators/clean-arch.generator");
|
|
|
17
17
|
const report_generator_1 = require("./src/generators/report.generator");
|
|
18
18
|
const lint_generator_1 = require("./src/generators/lint.generator");
|
|
19
19
|
const environment_finder_1 = require("./src/utils/environment-finder");
|
|
20
|
+
const example_validator_1 = require("./src/utils/example-validator");
|
|
20
21
|
const prompt_1 = require("./src/utils/prompt");
|
|
21
22
|
const config_1 = require("./src/utils/config");
|
|
22
23
|
const package_json_1 = __importDefault(require("./package.json"));
|
|
@@ -109,6 +110,7 @@ async function main() {
|
|
|
109
110
|
return;
|
|
110
111
|
}
|
|
111
112
|
(0, filesystem_1.createDirectoryStructure)(options.output);
|
|
113
|
+
(0, example_validator_1.clearExampleMismatches)();
|
|
112
114
|
// ── SELECTION: tags and endpoints ─────────────────────────────────────────
|
|
113
115
|
let selectionFilter = {};
|
|
114
116
|
let tagApiKeyMap;
|
|
@@ -150,10 +152,27 @@ async function main() {
|
|
|
150
152
|
}
|
|
151
153
|
// ──────────────────────────────────────────────────────────────────────────
|
|
152
154
|
const tempDir = (0, dto_generator_1.generateCode)(options.input, options.templates);
|
|
153
|
-
|
|
155
|
+
// Compute schema→tag map before organizeFiles so DTOs land in the right subfolder
|
|
156
|
+
const tagsMapForSchema = (0, clean_arch_generator_1.buildTagsMapFromAnalysis)(analysis, selectionFilter);
|
|
157
|
+
const schemaTagMap = (0, clean_arch_generator_1.buildSchemaTagMap)(analysis.swagger.components
|
|
158
|
+
?.schemas || {}, tagsMapForSchema);
|
|
159
|
+
(0, dto_generator_1.organizeFiles)(tempDir, options.output, schemaTagMap);
|
|
154
160
|
(0, dto_generator_1.addDtoImports)(options.output);
|
|
155
|
-
(0, clean_arch_generator_1.generateCleanArchitecture)(analysis, options.output, options.templates, tagApiKeyMap, selectionFilter);
|
|
161
|
+
(0, clean_arch_generator_1.generateCleanArchitecture)(analysis, options.output, options.templates, tagApiKeyMap, selectionFilter, schemaTagMap);
|
|
156
162
|
(0, filesystem_1.cleanup)(tempDir);
|
|
163
|
+
// ── EXAMPLE/TYPE MISMATCH WARNINGS ─────────────────────────────────────────
|
|
164
|
+
const mismatches = (0, example_validator_1.getExampleMismatches)();
|
|
165
|
+
if (mismatches.length > 0) {
|
|
166
|
+
console.log('');
|
|
167
|
+
(0, logger_1.logWarning)(`${mismatches.length} example/type mismatch(es) detected in OpenAPI schemas:`);
|
|
168
|
+
for (const m of mismatches) {
|
|
169
|
+
const action = m.action === 'coerced'
|
|
170
|
+
? `→ coerced to ${JSON.stringify(m.coercedValue)}`
|
|
171
|
+
: '→ example ignored, using type default';
|
|
172
|
+
(0, logger_1.logWarning)(` ${m.schemaName}.${m.propertyName}: type '${m.declaredType}' but example is ${m.exampleJsType} (${JSON.stringify(m.exampleValue)}) ${action}`);
|
|
173
|
+
(0, logger_1.logDetail)('VALIDATE', `${m.schemaName}.${m.propertyName}: declared=${m.declaredType} example=${JSON.stringify(m.exampleValue)} (${m.exampleJsType}) action=${m.action}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
157
176
|
const noLintResult = {
|
|
158
177
|
prettier: { ran: false, filesFormatted: 0 },
|
|
159
178
|
eslint: { ran: false, filesFixed: 0 }
|
|
@@ -170,6 +189,9 @@ async function main() {
|
|
|
170
189
|
console.log(` - Use Cases: ${report.structure.useCases}`);
|
|
171
190
|
console.log(` - Providers: ${report.structure.providers}`);
|
|
172
191
|
console.log(` - Mocks: ${report.structure.mocks}`);
|
|
192
|
+
if (report.warnings.total > 0) {
|
|
193
|
+
console.log(`\n ${logger_1.colors.yellow}⚠️ ${report.warnings.total} example/type mismatch(es) (see above)${logger_1.colors.reset}`);
|
|
194
|
+
}
|
|
173
195
|
console.log(`\n📁 Files generated in: ${logger_1.colors.cyan}${options.output}${logger_1.colors.reset}\n`);
|
|
174
196
|
}
|
|
175
197
|
main().catch((error) => {
|
package/dist/package.json
CHANGED
|
@@ -5,6 +5,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.extractTagsFromAnalysis = extractTagsFromAnalysis;
|
|
7
7
|
exports.extractTagsWithOperations = extractTagsWithOperations;
|
|
8
|
+
exports.buildTagsMapFromAnalysis = buildTagsMapFromAnalysis;
|
|
9
|
+
exports.buildSchemaTagMap = buildSchemaTagMap;
|
|
8
10
|
exports.generateCleanArchitecture = generateCleanArchitecture;
|
|
9
11
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
10
12
|
const path_1 = __importDefault(require("path"));
|
|
@@ -57,8 +59,139 @@ function extractTagsWithOperations(analysis) {
|
|
|
57
59
|
});
|
|
58
60
|
return [...map.values()];
|
|
59
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Builds and returns the tagsMap from the swagger analysis, applying the optional selection filter.
|
|
64
|
+
* Exported so callers (e.g. main.ts) can compute it before organizeFiles runs.
|
|
65
|
+
*/
|
|
66
|
+
function buildTagsMapFromAnalysis(analysis, selectionFilter = {}) {
|
|
67
|
+
const tagsMap = {};
|
|
68
|
+
Object.keys(analysis.paths).forEach((pathKey) => {
|
|
69
|
+
const pathObj = analysis.paths[pathKey];
|
|
70
|
+
Object.keys(pathObj).forEach((method) => {
|
|
71
|
+
const op = pathObj[method];
|
|
72
|
+
if (op.tags && op.tags.length > 0) {
|
|
73
|
+
const tag = op.tags[0];
|
|
74
|
+
if (!tagsMap[tag])
|
|
75
|
+
tagsMap[tag] = [];
|
|
76
|
+
const allParams = (op.parameters || []).map((p) => ({
|
|
77
|
+
paramName: p.name,
|
|
78
|
+
dataType: (0, type_mapper_1.mapSwaggerTypeToTs)(p.schema?.type || ''),
|
|
79
|
+
description: p.description || '',
|
|
80
|
+
required: p.required,
|
|
81
|
+
testValue: resolveTestParamValue((0, type_mapper_1.mapSwaggerTypeToTs)(p.schema?.type || ''))
|
|
82
|
+
}));
|
|
83
|
+
if (op.requestBody) {
|
|
84
|
+
let bodyType = 'unknown';
|
|
85
|
+
const content = op.requestBody.content?.['application/json']?.schema;
|
|
86
|
+
if (content) {
|
|
87
|
+
if (content.$ref)
|
|
88
|
+
bodyType = content.$ref.split('/').pop() || 'unknown';
|
|
89
|
+
else if (content.type)
|
|
90
|
+
bodyType = (0, type_mapper_1.mapSwaggerTypeToTs)(content.type);
|
|
91
|
+
}
|
|
92
|
+
allParams.push({
|
|
93
|
+
paramName: 'body',
|
|
94
|
+
dataType: bodyType,
|
|
95
|
+
description: op.requestBody.description || '',
|
|
96
|
+
required: true,
|
|
97
|
+
testValue: resolveTestParamValue(bodyType)
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
let returnType = 'void';
|
|
101
|
+
let returnBaseType = 'void';
|
|
102
|
+
let isListContainer = false;
|
|
103
|
+
const successCode = ['200', '201', '202', '203'].find((code) => op.responses?.[code]);
|
|
104
|
+
const responseSchema = successCode !== undefined
|
|
105
|
+
? op.responses?.[successCode]?.content?.['application/json']?.schema
|
|
106
|
+
: undefined;
|
|
107
|
+
if (responseSchema) {
|
|
108
|
+
if (responseSchema.$ref) {
|
|
109
|
+
returnType = responseSchema.$ref.split('/').pop() || 'unknown';
|
|
110
|
+
returnBaseType = returnType;
|
|
111
|
+
}
|
|
112
|
+
else if (responseSchema.type === 'array' && responseSchema.items?.$ref) {
|
|
113
|
+
returnBaseType = responseSchema.items.$ref.split('/').pop() || 'unknown';
|
|
114
|
+
returnType = `${returnBaseType}[]`;
|
|
115
|
+
isListContainer = true;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const hasQueryParams = (op.parameters || []).some((p) => p.in === 'query');
|
|
119
|
+
const hasBodyParam = !!op.requestBody;
|
|
120
|
+
// Sort: required params first, optional params last (TypeScript requirement)
|
|
121
|
+
allParams.sort((a, b) => {
|
|
122
|
+
if (a.required === b.required)
|
|
123
|
+
return 0;
|
|
124
|
+
return a.required ? -1 : 1;
|
|
125
|
+
});
|
|
126
|
+
tagsMap[tag].push({
|
|
127
|
+
nickname: op.operationId || `${method}${pathKey.replace(/\//g, '_')}`,
|
|
128
|
+
summary: op.summary || '',
|
|
129
|
+
notes: op.description || '',
|
|
130
|
+
httpMethod: method.toLowerCase(),
|
|
131
|
+
uppercaseHttpMethod: method.toUpperCase(),
|
|
132
|
+
path: pathKey,
|
|
133
|
+
allParams: allParams.map((p, i) => ({
|
|
134
|
+
...p,
|
|
135
|
+
'-last': i === allParams.length - 1
|
|
136
|
+
})),
|
|
137
|
+
hasQueryParams,
|
|
138
|
+
queryParams: (op.parameters || [])
|
|
139
|
+
.filter((p) => p.in === 'query')
|
|
140
|
+
.map((p, i, arr) => ({
|
|
141
|
+
paramName: p.name,
|
|
142
|
+
'-last': i === arr.length - 1
|
|
143
|
+
})),
|
|
144
|
+
hasBodyParam,
|
|
145
|
+
bodyParam: 'body',
|
|
146
|
+
hasOptions: hasQueryParams || hasBodyParam,
|
|
147
|
+
hasBothParamsAndBody: hasQueryParams && hasBodyParam,
|
|
148
|
+
returnType: returnType !== 'void' ? returnType : false,
|
|
149
|
+
returnBaseType: returnBaseType !== 'void' ? returnBaseType : false,
|
|
150
|
+
returnTypeVarName: returnType !== 'void' ? (0, name_formatter_1.toCamelCase)(returnType) : false,
|
|
151
|
+
returnBaseTypeVarName: returnBaseType !== 'void' ? (0, name_formatter_1.toCamelCase)(returnBaseType) : false,
|
|
152
|
+
isListContainer: isListContainer,
|
|
153
|
+
vendorExtensions: {}
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
if (Object.keys(selectionFilter).length > 0) {
|
|
159
|
+
Object.keys(tagsMap).forEach((tag) => {
|
|
160
|
+
if (!selectionFilter[tag]) {
|
|
161
|
+
delete tagsMap[tag];
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
tagsMap[tag] = tagsMap[tag].filter((op) => selectionFilter[tag].includes(op.nickname));
|
|
165
|
+
if (tagsMap[tag].length === 0)
|
|
166
|
+
delete tagsMap[tag];
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
return tagsMap;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Maps each schema basename to the tag subfolder it belongs to.
|
|
174
|
+
* Schemas used by exactly one tag → that tag's camelCase name.
|
|
175
|
+
* Schemas used by 0 or multiple tags → 'shared'.
|
|
176
|
+
*/
|
|
177
|
+
function buildSchemaTagMap(schemas, tagsMap) {
|
|
178
|
+
const result = {};
|
|
179
|
+
Object.keys(schemas).forEach((schemaName) => {
|
|
180
|
+
const baseName = schemaName.replace(/Dto$/, '');
|
|
181
|
+
const tagsUsing = [];
|
|
182
|
+
Object.keys(tagsMap).forEach((tag) => {
|
|
183
|
+
const used = tagsMap[tag].some((op) => op.returnType === baseName ||
|
|
184
|
+
op.returnType === `${baseName}[]` ||
|
|
185
|
+
op.allParams.some((p) => p.dataType === baseName || p.dataType === `${baseName}[]`));
|
|
186
|
+
if (used)
|
|
187
|
+
tagsUsing.push(tag);
|
|
188
|
+
});
|
|
189
|
+
result[baseName] = tagsUsing.length === 1 ? (0, name_formatter_1.toCamelCase)(tagsUsing[0]) : 'shared';
|
|
190
|
+
});
|
|
191
|
+
return result;
|
|
192
|
+
}
|
|
60
193
|
/** Generates all Clean Architecture artefacts (models, mappers, repos, use cases, providers) using Mustache. */
|
|
61
|
-
function generateCleanArchitecture(analysis, outputDir, templatesDir, tagApiKeyMap = {}, selectionFilter = {}) {
|
|
194
|
+
function generateCleanArchitecture(analysis, outputDir, templatesDir, tagApiKeyMap = {}, selectionFilter = {}, precomputedSchemaTagMap = {}) {
|
|
62
195
|
(0, logger_1.logStep)('Generating Clean Architecture artefacts using Mustache...');
|
|
63
196
|
const generatedCount = {
|
|
64
197
|
models: 0,
|
|
@@ -71,14 +204,22 @@ function generateCleanArchitecture(analysis, outputDir, templatesDir, tagApiKeyM
|
|
|
71
204
|
};
|
|
72
205
|
const schemas = analysis.swagger.components
|
|
73
206
|
?.schemas || {};
|
|
207
|
+
// Build tagsMap first — needed to compute schemaTagMap before the schema loop
|
|
208
|
+
const tagsMap = buildTagsMapFromAnalysis(analysis, selectionFilter);
|
|
209
|
+
// Map each schema basename → tag subfolder ('shared' if used by 0 or >1 tags)
|
|
210
|
+
const schemaTagMap = Object.keys(precomputedSchemaTagMap).length > 0
|
|
211
|
+
? precomputedSchemaTagMap
|
|
212
|
+
: buildSchemaTagMap(schemas, tagsMap);
|
|
74
213
|
// 1. Generate Models, Entities and Mappers from Schemas
|
|
75
214
|
Object.keys(schemas).forEach((schemaName) => {
|
|
76
215
|
const baseName = schemaName.replace(/Dto$/, '');
|
|
216
|
+
const tagFilename = schemaTagMap[baseName] || 'shared';
|
|
77
217
|
const schemaObj = schemas[schemaName];
|
|
78
218
|
const rawProperties = schemaObj.properties || {};
|
|
79
219
|
const requiredProps = schemaObj.required || [];
|
|
80
220
|
const varsMap = Object.keys(rawProperties).map((k) => {
|
|
81
221
|
let tsType = (0, type_mapper_1.mapSwaggerTypeToTs)(rawProperties[k].type);
|
|
222
|
+
const isInlineObject = rawProperties[k].type === 'object' && !rawProperties[k].$ref;
|
|
82
223
|
if (rawProperties[k].$ref) {
|
|
83
224
|
tsType = rawProperties[k].$ref.split('/').pop() || 'unknown';
|
|
84
225
|
}
|
|
@@ -86,10 +227,12 @@ function generateCleanArchitecture(analysis, outputDir, templatesDir, tagApiKeyM
|
|
|
86
227
|
tsType = `${rawProperties[k].items.$ref.split('/').pop()}[]`;
|
|
87
228
|
}
|
|
88
229
|
return {
|
|
89
|
-
name: k,
|
|
230
|
+
name: (0, name_formatter_1.safePropertyName)(k),
|
|
231
|
+
originalName: k,
|
|
90
232
|
dataType: tsType,
|
|
91
233
|
description: rawProperties[k].description || '',
|
|
92
|
-
required: requiredProps.includes(k)
|
|
234
|
+
required: requiredProps.includes(k),
|
|
235
|
+
hasMockValue: !isInlineObject
|
|
93
236
|
};
|
|
94
237
|
});
|
|
95
238
|
// Collect imports for types referenced via $ref in properties
|
|
@@ -102,10 +245,13 @@ function generateCleanArchitecture(analysis, outputDir, templatesDir, tagApiKeyM
|
|
|
102
245
|
referencedTypes.add(prop.items.$ref.split('/').pop() || '');
|
|
103
246
|
}
|
|
104
247
|
});
|
|
105
|
-
const modelImports = [...referencedTypes]
|
|
106
|
-
|
|
107
|
-
|
|
248
|
+
const modelImports = [...referencedTypes].filter(Boolean).map((name) => ({
|
|
249
|
+
classname: name,
|
|
250
|
+
classFilename: (0, name_formatter_1.toCamelCase)(name),
|
|
251
|
+
tagFilename: schemaTagMap[name] || 'shared'
|
|
252
|
+
}));
|
|
108
253
|
const modelViewData = {
|
|
254
|
+
tagFilename,
|
|
109
255
|
models: [
|
|
110
256
|
{
|
|
111
257
|
model: {
|
|
@@ -128,7 +274,8 @@ function generateCleanArchitecture(analysis, outputDir, templatesDir, tagApiKeyM
|
|
|
128
274
|
operations: {
|
|
129
275
|
classname: baseName,
|
|
130
276
|
classFilename: (0, name_formatter_1.toCamelCase)(baseName),
|
|
131
|
-
classVarName: (0, name_formatter_1.toCamelCase)(baseName)
|
|
277
|
+
classVarName: (0, name_formatter_1.toCamelCase)(baseName),
|
|
278
|
+
tagFilename
|
|
132
279
|
}
|
|
133
280
|
}
|
|
134
281
|
]
|
|
@@ -139,7 +286,8 @@ function generateCleanArchitecture(analysis, outputDir, templatesDir, tagApiKeyM
|
|
|
139
286
|
if (fs_extra_1.default.existsSync(modelTemplatePath)) {
|
|
140
287
|
const template = fs_extra_1.default.readFileSync(modelTemplatePath, 'utf8');
|
|
141
288
|
const output = mustache_1.default.render(template, modelViewData);
|
|
142
|
-
const destPath = path_1.default.join(outputDir, 'entities/models', `${(0, name_formatter_1.toCamelCase)(baseName)}.model.ts`);
|
|
289
|
+
const destPath = path_1.default.join(outputDir, 'entities/models', tagFilename, `${(0, name_formatter_1.toCamelCase)(baseName)}.model.ts`);
|
|
290
|
+
fs_extra_1.default.ensureDirSync(path_1.default.dirname(destPath));
|
|
143
291
|
fs_extra_1.default.writeFileSync(destPath, output);
|
|
144
292
|
generatedCount.models++;
|
|
145
293
|
(0, logger_1.logDetail)('generate', `model-entity → ${path_1.default.relative(process.cwd(), destPath)}`);
|
|
@@ -149,19 +297,29 @@ function generateCleanArchitecture(analysis, outputDir, templatesDir, tagApiKeyM
|
|
|
149
297
|
if (fs_extra_1.default.existsSync(mapperTemplatePath)) {
|
|
150
298
|
const template = fs_extra_1.default.readFileSync(mapperTemplatePath, 'utf8');
|
|
151
299
|
const output = mustache_1.default.render(template, mapperViewData);
|
|
152
|
-
const destPath = path_1.default.join(outputDir, 'data/mappers', `${(0, name_formatter_1.toCamelCase)(baseName)}.mapper.ts`);
|
|
300
|
+
const destPath = path_1.default.join(outputDir, 'data/mappers', tagFilename, `${(0, name_formatter_1.toCamelCase)(baseName)}.mapper.ts`);
|
|
301
|
+
fs_extra_1.default.ensureDirSync(path_1.default.dirname(destPath));
|
|
153
302
|
fs_extra_1.default.writeFileSync(destPath, output);
|
|
154
303
|
generatedCount.mappers++;
|
|
155
304
|
}
|
|
156
305
|
// DTO mock — values resolved from raw schema (example, format, type)
|
|
157
306
|
const dtoMockVarsMap = Object.keys(rawProperties).map((k) => ({
|
|
158
|
-
name: k,
|
|
159
|
-
mockValue: (0, mock_value_resolver_1.resolveMockValue)(k, rawProperties[k], 'dto')
|
|
307
|
+
name: (0, name_formatter_1.safePropertyName)(k),
|
|
308
|
+
mockValue: (0, mock_value_resolver_1.resolveMockValue)(k, rawProperties[k], 'dto', schemaName)
|
|
160
309
|
}));
|
|
161
|
-
const dtoMockImports = [...referencedTypes]
|
|
162
|
-
|
|
163
|
-
|
|
310
|
+
const dtoMockImports = [...referencedTypes].filter(Boolean).map((name) => {
|
|
311
|
+
const targetTag = schemaTagMap[name] || 'shared';
|
|
312
|
+
const targetFile = `${(0, name_formatter_1.toCamelCase)(name)}.dto.mock`;
|
|
313
|
+
const importPath = targetTag === tagFilename ? `./${targetFile}` : `../${targetTag}/${targetFile}`;
|
|
314
|
+
return {
|
|
315
|
+
classname: name,
|
|
316
|
+
classFilename: (0, name_formatter_1.toCamelCase)(name),
|
|
317
|
+
tagFilename: targetTag,
|
|
318
|
+
importPath
|
|
319
|
+
};
|
|
320
|
+
});
|
|
164
321
|
const dtoMockViewData = {
|
|
322
|
+
tagFilename,
|
|
165
323
|
models: [
|
|
166
324
|
{
|
|
167
325
|
model: {
|
|
@@ -174,120 +332,29 @@ function generateCleanArchitecture(analysis, outputDir, templatesDir, tagApiKeyM
|
|
|
174
332
|
}
|
|
175
333
|
]
|
|
176
334
|
};
|
|
177
|
-
renderTemplate(templatesDir, 'dto.mock.mustache', dtoMockViewData, path_1.default.join(outputDir, 'data/dtos', `${(0, name_formatter_1.toCamelCase)(baseName)}.dto.mock.ts`), generatedCount, 'mocks');
|
|
335
|
+
renderTemplate(templatesDir, 'dto.mock.mustache', dtoMockViewData, path_1.default.join(outputDir, 'data/dtos', tagFilename, `${(0, name_formatter_1.toCamelCase)(baseName)}.dto.mock.ts`), generatedCount, 'mocks');
|
|
178
336
|
// Model mock — delegates to mapper + DTO mock (no property values needed)
|
|
179
|
-
renderTemplate(templatesDir, 'model.mock.mustache', modelViewData, path_1.default.join(outputDir, 'entities/models', `${(0, name_formatter_1.toCamelCase)(baseName)}.model.mock.ts`), generatedCount, 'mocks');
|
|
337
|
+
renderTemplate(templatesDir, 'model.mock.mustache', modelViewData, path_1.default.join(outputDir, 'entities/models', tagFilename, `${(0, name_formatter_1.toCamelCase)(baseName)}.model.mock.ts`), generatedCount, 'mocks');
|
|
180
338
|
// Model spec
|
|
181
|
-
renderTemplate(templatesDir, 'model-entity.spec.mustache', modelViewData, path_1.default.join(outputDir, 'entities/models', `${(0, name_formatter_1.toCamelCase)(baseName)}.model.spec.ts`), generatedCount, 'specs');
|
|
339
|
+
renderTemplate(templatesDir, 'model-entity.spec.mustache', modelViewData, path_1.default.join(outputDir, 'entities/models', tagFilename, `${(0, name_formatter_1.toCamelCase)(baseName)}.model.spec.ts`), generatedCount, 'specs');
|
|
182
340
|
// Mapper spec
|
|
183
|
-
renderTemplate(templatesDir, 'mapper.spec.mustache', mapperViewData, path_1.default.join(outputDir, 'data/mappers', `${(0, name_formatter_1.toCamelCase)(baseName)}.mapper.spec.ts`), generatedCount, 'specs');
|
|
341
|
+
renderTemplate(templatesDir, 'mapper.spec.mustache', mapperViewData, path_1.default.join(outputDir, 'data/mappers', tagFilename, `${(0, name_formatter_1.toCamelCase)(baseName)}.mapper.spec.ts`), generatedCount, 'specs');
|
|
184
342
|
});
|
|
185
343
|
// 2. Generate Use Cases and Repositories from Paths/Tags
|
|
186
|
-
const tagsMap = {};
|
|
187
|
-
Object.keys(analysis.paths).forEach((pathKey) => {
|
|
188
|
-
const pathObj = analysis.paths[pathKey];
|
|
189
|
-
Object.keys(pathObj).forEach((method) => {
|
|
190
|
-
const op = pathObj[method];
|
|
191
|
-
if (op.tags && op.tags.length > 0) {
|
|
192
|
-
const tag = op.tags[0];
|
|
193
|
-
if (!tagsMap[tag])
|
|
194
|
-
tagsMap[tag] = [];
|
|
195
|
-
const allParams = (op.parameters || []).map((p) => ({
|
|
196
|
-
paramName: p.name,
|
|
197
|
-
dataType: (0, type_mapper_1.mapSwaggerTypeToTs)(p.schema?.type || ''),
|
|
198
|
-
description: p.description || '',
|
|
199
|
-
required: p.required,
|
|
200
|
-
testValue: resolveTestParamValue((0, type_mapper_1.mapSwaggerTypeToTs)(p.schema?.type || ''))
|
|
201
|
-
}));
|
|
202
|
-
if (op.requestBody) {
|
|
203
|
-
let bodyType = 'unknown';
|
|
204
|
-
const content = op.requestBody.content?.['application/json']?.schema;
|
|
205
|
-
if (content) {
|
|
206
|
-
if (content.$ref)
|
|
207
|
-
bodyType = content.$ref.split('/').pop() || 'unknown';
|
|
208
|
-
else if (content.type)
|
|
209
|
-
bodyType = (0, type_mapper_1.mapSwaggerTypeToTs)(content.type);
|
|
210
|
-
}
|
|
211
|
-
allParams.push({
|
|
212
|
-
paramName: 'body',
|
|
213
|
-
dataType: bodyType,
|
|
214
|
-
description: op.requestBody.description || '',
|
|
215
|
-
required: true,
|
|
216
|
-
testValue: resolveTestParamValue(bodyType)
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
let returnType = 'void';
|
|
220
|
-
let returnBaseType = 'void';
|
|
221
|
-
let isListContainer = false;
|
|
222
|
-
const successCode = ['200', '201', '202', '203'].find((code) => op.responses?.[code]);
|
|
223
|
-
const responseSchema = successCode !== undefined
|
|
224
|
-
? op.responses?.[successCode]?.content?.['application/json']?.schema
|
|
225
|
-
: undefined;
|
|
226
|
-
if (responseSchema) {
|
|
227
|
-
if (responseSchema.$ref) {
|
|
228
|
-
returnType = responseSchema.$ref.split('/').pop() || 'unknown';
|
|
229
|
-
returnBaseType = returnType;
|
|
230
|
-
}
|
|
231
|
-
else if (responseSchema.type === 'array' && responseSchema.items?.$ref) {
|
|
232
|
-
returnBaseType = responseSchema.items.$ref.split('/').pop() || 'unknown';
|
|
233
|
-
returnType = `${returnBaseType}[]`;
|
|
234
|
-
isListContainer = true;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
const hasQueryParams = (op.parameters || []).some((p) => p.in === 'query');
|
|
238
|
-
const hasBodyParam = !!op.requestBody;
|
|
239
|
-
tagsMap[tag].push({
|
|
240
|
-
nickname: op.operationId || `${method}${pathKey.replace(/\//g, '_')}`,
|
|
241
|
-
summary: op.summary || '',
|
|
242
|
-
notes: op.description || '',
|
|
243
|
-
httpMethod: method.toLowerCase(),
|
|
244
|
-
uppercaseHttpMethod: method.toUpperCase(),
|
|
245
|
-
path: pathKey,
|
|
246
|
-
allParams: allParams.map((p, i) => ({
|
|
247
|
-
...p,
|
|
248
|
-
'-last': i === allParams.length - 1
|
|
249
|
-
})),
|
|
250
|
-
hasQueryParams,
|
|
251
|
-
queryParams: (op.parameters || [])
|
|
252
|
-
.filter((p) => p.in === 'query')
|
|
253
|
-
.map((p, i, arr) => ({
|
|
254
|
-
paramName: p.name,
|
|
255
|
-
'-last': i === arr.length - 1
|
|
256
|
-
})),
|
|
257
|
-
hasBodyParam,
|
|
258
|
-
bodyParam: 'body',
|
|
259
|
-
hasOptions: hasQueryParams || hasBodyParam,
|
|
260
|
-
hasBothParamsAndBody: hasQueryParams && hasBodyParam,
|
|
261
|
-
returnType: returnType !== 'void' ? returnType : false,
|
|
262
|
-
returnBaseType: returnBaseType !== 'void' ? returnBaseType : false,
|
|
263
|
-
returnTypeVarName: returnType !== 'void' ? (0, name_formatter_1.toCamelCase)(returnType) : false,
|
|
264
|
-
returnBaseTypeVarName: returnBaseType !== 'void' ? (0, name_formatter_1.toCamelCase)(returnBaseType) : false,
|
|
265
|
-
isListContainer: isListContainer,
|
|
266
|
-
vendorExtensions: {}
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
});
|
|
270
|
-
});
|
|
271
|
-
if (Object.keys(selectionFilter).length > 0) {
|
|
272
|
-
Object.keys(tagsMap).forEach((tag) => {
|
|
273
|
-
if (!selectionFilter[tag]) {
|
|
274
|
-
delete tagsMap[tag];
|
|
275
|
-
}
|
|
276
|
-
else {
|
|
277
|
-
tagsMap[tag] = tagsMap[tag].filter((op) => selectionFilter[tag].includes(op.nickname));
|
|
278
|
-
if (tagsMap[tag].length === 0)
|
|
279
|
-
delete tagsMap[tag];
|
|
280
|
-
}
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
344
|
// Generate per tag
|
|
284
345
|
Object.keys(tagsMap).forEach((tag) => {
|
|
346
|
+
const tagFilename = (0, name_formatter_1.toCamelCase)(tag);
|
|
285
347
|
const returnImports = [];
|
|
286
348
|
const paramImports = [];
|
|
287
349
|
Object.keys(schemas).forEach((s) => {
|
|
288
350
|
const usedAsReturn = tagsMap[tag].some((op) => op.returnType === s || op.returnType === `${s}[]`);
|
|
289
351
|
const usedAsParam = tagsMap[tag].some((op) => op.allParams.some((p) => p.dataType === s || p.dataType === `${s}[]`));
|
|
290
|
-
const entry = {
|
|
352
|
+
const entry = {
|
|
353
|
+
classname: s,
|
|
354
|
+
classFilename: (0, name_formatter_1.toCamelCase)(s),
|
|
355
|
+
classVarName: (0, name_formatter_1.toCamelCase)(s),
|
|
356
|
+
tagFilename: schemaTagMap[s] || 'shared'
|
|
357
|
+
};
|
|
291
358
|
if (usedAsReturn) {
|
|
292
359
|
returnImports.push(entry);
|
|
293
360
|
}
|
|
@@ -301,39 +368,35 @@ function generateCleanArchitecture(analysis, outputDir, templatesDir, tagApiKeyM
|
|
|
301
368
|
apis: [
|
|
302
369
|
{
|
|
303
370
|
operations: {
|
|
304
|
-
classname: tag,
|
|
305
|
-
classFilename:
|
|
306
|
-
classVarName:
|
|
371
|
+
classname: (0, name_formatter_1.toPascalCase)(tag),
|
|
372
|
+
classFilename: tagFilename,
|
|
373
|
+
classVarName: tagFilename,
|
|
307
374
|
constantName: tag.toUpperCase().replace(/[^A-Z0-9]/g, '_'),
|
|
308
375
|
operation: tagsMap[tag],
|
|
309
|
-
// All entity imports (return + param) — for contracts and use-cases
|
|
310
376
|
imports: [...returnImports, ...paramImports],
|
|
311
|
-
// Return-type-only imports — for repo impl (Dto + Entity + Mapper)
|
|
312
377
|
returnImports,
|
|
313
|
-
// Param-only imports — for repo impl (Entity only, no Dto/Mapper)
|
|
314
378
|
paramImports,
|
|
315
|
-
// Environment API key for the repository base URL (e.g. "aprovalmApi")
|
|
316
379
|
environmentApiKey: tagApiKeyMap[tag] || 'apiUrl'
|
|
317
380
|
}
|
|
318
381
|
}
|
|
319
382
|
]
|
|
320
383
|
}
|
|
321
384
|
};
|
|
322
|
-
renderTemplate(templatesDir, 'api.use-cases.contract.mustache', apiViewData, path_1.default.join(outputDir, 'domain/use-cases', `${
|
|
323
|
-
renderTemplate(templatesDir, 'api.use-cases.impl.mustache', apiViewData, path_1.default.join(outputDir, 'domain/use-cases', `${
|
|
324
|
-
renderTemplate(templatesDir, 'api.repository.contract.mustache', apiViewData, path_1.default.join(outputDir, 'domain/repositories', `${
|
|
325
|
-
renderTemplate(templatesDir, 'api.repository.impl.mustache', apiViewData, path_1.default.join(outputDir, 'data/repositories', `${
|
|
326
|
-
renderTemplate(templatesDir, 'use-cases.provider.mustache', apiViewData, path_1.default.join(outputDir, 'di/use-cases', `${
|
|
327
|
-
renderTemplate(templatesDir, 'repository.provider.mustache', apiViewData, path_1.default.join(outputDir, 'di/repositories', `${
|
|
328
|
-
// Mocks
|
|
329
|
-
renderTemplate(templatesDir, 'api.repository.impl.mock.mustache', apiViewData, path_1.default.join(outputDir, 'data/repositories', `${
|
|
330
|
-
renderTemplate(templatesDir, 'api.use-cases.mock.mustache', apiViewData, path_1.default.join(outputDir, 'domain/use-cases', `${
|
|
331
|
-
renderTemplate(templatesDir, 'repository.provider.mock.mustache', apiViewData, path_1.default.join(outputDir, 'di/repositories', `${
|
|
332
|
-
renderTemplate(templatesDir, 'use-cases.provider.mock.mustache', apiViewData, path_1.default.join(outputDir, 'di/use-cases', `${
|
|
385
|
+
renderTemplate(templatesDir, 'api.use-cases.contract.mustache', apiViewData, path_1.default.join(outputDir, 'domain/use-cases', tagFilename, `${tagFilename}.use-cases.contract.ts`), generatedCount, 'useCases');
|
|
386
|
+
renderTemplate(templatesDir, 'api.use-cases.impl.mustache', apiViewData, path_1.default.join(outputDir, 'domain/use-cases', tagFilename, `${tagFilename}.use-cases.impl.ts`), generatedCount, 'useCases');
|
|
387
|
+
renderTemplate(templatesDir, 'api.repository.contract.mustache', apiViewData, path_1.default.join(outputDir, 'domain/repositories', tagFilename, `${tagFilename}.repository.contract.ts`), generatedCount, 'repositories');
|
|
388
|
+
renderTemplate(templatesDir, 'api.repository.impl.mustache', apiViewData, path_1.default.join(outputDir, 'data/repositories', tagFilename, `${tagFilename}.repository.impl.ts`), generatedCount, 'repositories');
|
|
389
|
+
renderTemplate(templatesDir, 'use-cases.provider.mustache', apiViewData, path_1.default.join(outputDir, 'di/use-cases', tagFilename, `${tagFilename}.use-cases.provider.ts`), generatedCount, 'providers');
|
|
390
|
+
renderTemplate(templatesDir, 'repository.provider.mustache', apiViewData, path_1.default.join(outputDir, 'di/repositories', tagFilename, `${tagFilename}.repository.provider.ts`), generatedCount, 'providers');
|
|
391
|
+
// Mocks
|
|
392
|
+
renderTemplate(templatesDir, 'api.repository.impl.mock.mustache', apiViewData, path_1.default.join(outputDir, 'data/repositories', tagFilename, `${tagFilename}.repository.impl.mock.ts`), generatedCount, 'mocks');
|
|
393
|
+
renderTemplate(templatesDir, 'api.use-cases.mock.mustache', apiViewData, path_1.default.join(outputDir, 'domain/use-cases', tagFilename, `${tagFilename}.use-cases.mock.ts`), generatedCount, 'mocks');
|
|
394
|
+
renderTemplate(templatesDir, 'repository.provider.mock.mustache', apiViewData, path_1.default.join(outputDir, 'di/repositories', tagFilename, `${tagFilename}.repository.provider.mock.ts`), generatedCount, 'mocks');
|
|
395
|
+
renderTemplate(templatesDir, 'use-cases.provider.mock.mustache', apiViewData, path_1.default.join(outputDir, 'di/use-cases', tagFilename, `${tagFilename}.use-cases.provider.mock.ts`), generatedCount, 'mocks');
|
|
333
396
|
// Repository impl spec
|
|
334
|
-
renderTemplate(templatesDir, 'api.repository.impl.spec.mustache', apiViewData, path_1.default.join(outputDir, 'data/repositories', `${
|
|
397
|
+
renderTemplate(templatesDir, 'api.repository.impl.spec.mustache', apiViewData, path_1.default.join(outputDir, 'data/repositories', tagFilename, `${tagFilename}.repository.impl.spec.ts`), generatedCount, 'specs');
|
|
335
398
|
// Use-cases impl spec
|
|
336
|
-
renderTemplate(templatesDir, 'api.use-cases.impl.spec.mustache', apiViewData, path_1.default.join(outputDir, 'domain/use-cases', `${
|
|
399
|
+
renderTemplate(templatesDir, 'api.use-cases.impl.spec.mustache', apiViewData, path_1.default.join(outputDir, 'domain/use-cases', tagFilename, `${tagFilename}.use-cases.impl.spec.ts`), generatedCount, 'specs');
|
|
337
400
|
});
|
|
338
401
|
(0, logger_1.logSuccess)(`${generatedCount.models} Models, ${generatedCount.repositories} Repos, ${generatedCount.useCases} Use Cases, ${generatedCount.mappers} Mappers, ${generatedCount.providers} Providers, ${generatedCount.mocks} Mocks, ${generatedCount.specs} Specs generated`);
|
|
339
402
|
return generatedCount;
|
|
@@ -344,6 +407,7 @@ function renderTemplate(templatesDir, templateName, viewData, destPath, counter,
|
|
|
344
407
|
if (fs_extra_1.default.existsSync(templatePath)) {
|
|
345
408
|
const template = fs_extra_1.default.readFileSync(templatePath, 'utf8');
|
|
346
409
|
const output = mustache_1.default.render(template, viewData);
|
|
410
|
+
fs_extra_1.default.ensureDirSync(path_1.default.dirname(destPath));
|
|
347
411
|
fs_extra_1.default.writeFileSync(destPath, output);
|
|
348
412
|
counter[key]++;
|
|
349
413
|
(0, logger_1.logDetail)('generate', `${templateName.replace('.mustache', '')} → ${path_1.default.relative(process.cwd(), destPath)}`);
|
|
@@ -10,6 +10,7 @@ const child_process_1 = require("child_process");
|
|
|
10
10
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
11
11
|
const path_1 = __importDefault(require("path"));
|
|
12
12
|
const logger_1 = require("../utils/logger");
|
|
13
|
+
const name_formatter_1 = require("../utils/name-formatter");
|
|
13
14
|
/** Invokes `openapi-generator-cli` to generate DTOs into a temporary directory. */
|
|
14
15
|
function generateCode(swaggerFile, templatesDir) {
|
|
15
16
|
(0, logger_1.logStep)('Generating code from OpenAPI spec...');
|
|
@@ -37,8 +38,8 @@ function generateCode(swaggerFile, templatesDir) {
|
|
|
37
38
|
process.exit(1);
|
|
38
39
|
}
|
|
39
40
|
}
|
|
40
|
-
/** Copies the generated DTOs from the temporary directory to the output directory. */
|
|
41
|
-
function organizeFiles(tempDir, outputDir) {
|
|
41
|
+
/** Copies the generated DTOs from the temporary directory to the output directory, organised by tag subfolder. */
|
|
42
|
+
function organizeFiles(tempDir, outputDir, schemaTagMap = {}) {
|
|
42
43
|
(0, logger_1.logStep)('Organising generated DTO files...');
|
|
43
44
|
const sourceDir = path_1.default.join(tempDir, 'model');
|
|
44
45
|
const destDir = path_1.default.join(outputDir, 'data/dtos');
|
|
@@ -47,8 +48,13 @@ function organizeFiles(tempDir, outputDir) {
|
|
|
47
48
|
fs_extra_1.default.ensureDirSync(destDir);
|
|
48
49
|
const files = fs_extra_1.default.readdirSync(sourceDir).filter((file) => file.endsWith('.dto.ts'));
|
|
49
50
|
files.forEach((file) => {
|
|
51
|
+
// file is like "userResponse.dto.ts" → derive PascalCase schema name to look up tag
|
|
52
|
+
const camelName = file.replace('.dto.ts', '');
|
|
53
|
+
const pascalName = (0, name_formatter_1.toPascalCase)(camelName);
|
|
54
|
+
const tagFolder = schemaTagMap[pascalName] || 'shared';
|
|
50
55
|
const sourcePath = path_1.default.join(sourceDir, file);
|
|
51
|
-
const destPath = path_1.default.join(destDir, file);
|
|
56
|
+
const destPath = path_1.default.join(destDir, tagFolder, file);
|
|
57
|
+
fs_extra_1.default.ensureDirSync(path_1.default.dirname(destPath));
|
|
52
58
|
fs_extra_1.default.copySync(sourcePath, destPath);
|
|
53
59
|
filesMoved++;
|
|
54
60
|
(0, logger_1.logDetail)('dto', `${file} → ${path_1.default.relative(process.cwd(), destPath)}`);
|
|
@@ -62,46 +68,60 @@ function addDtoImports(outputDir) {
|
|
|
62
68
|
const dtosDir = path_1.default.join(outputDir, 'data/dtos');
|
|
63
69
|
if (!fs_extra_1.default.existsSync(dtosDir))
|
|
64
70
|
return;
|
|
65
|
-
|
|
66
|
-
|
|
71
|
+
// Collect all .dto.ts files from all subfolders (1 level deep)
|
|
72
|
+
const allFiles = [];
|
|
73
|
+
const entries = fs_extra_1.default.readdirSync(dtosDir);
|
|
74
|
+
entries.forEach((entry) => {
|
|
75
|
+
const entryPath = path_1.default.join(dtosDir, entry);
|
|
76
|
+
if (fs_extra_1.default.statSync(entryPath).isDirectory()) {
|
|
77
|
+
fs_extra_1.default.readdirSync(entryPath)
|
|
78
|
+
.filter((f) => f.endsWith('.dto.ts'))
|
|
79
|
+
.forEach((file) => allFiles.push({ subfolder: entry, file, fullPath: path_1.default.join(entryPath, file) }));
|
|
80
|
+
}
|
|
81
|
+
else if (entry.endsWith('.dto.ts')) {
|
|
82
|
+
allFiles.push({ subfolder: '', file: entry, fullPath: entryPath });
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
// Build map: ClassName → { subfolder, fileBase }
|
|
67
86
|
const dtoMap = {};
|
|
68
|
-
|
|
69
|
-
const content = fs_extra_1.default.readFileSync(
|
|
87
|
+
allFiles.forEach(({ subfolder, file, fullPath }) => {
|
|
88
|
+
const content = fs_extra_1.default.readFileSync(fullPath, 'utf8');
|
|
70
89
|
const match = content.match(/export interface (\w+)/);
|
|
71
|
-
if (match)
|
|
72
|
-
dtoMap[match[1]] = file.replace('.ts', '');
|
|
73
|
-
}
|
|
90
|
+
if (match)
|
|
91
|
+
dtoMap[match[1]] = { subfolder, fileBase: file.replace('.ts', '') };
|
|
74
92
|
});
|
|
75
93
|
let filesProcessed = 0;
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
const originalContent = fs_extra_1.default.readFileSync(filePath, 'utf8');
|
|
94
|
+
allFiles.forEach(({ subfolder, file, fullPath }) => {
|
|
95
|
+
const originalContent = fs_extra_1.default.readFileSync(fullPath, 'utf8');
|
|
79
96
|
let content = originalContent;
|
|
80
97
|
const selfMatch = content.match(/export interface (\w+)/);
|
|
81
98
|
const selfName = selfMatch ? selfMatch[1] : '';
|
|
82
|
-
// Normalize Array<T> → T[] (openapi-generator-cli always outputs Array<T>)
|
|
83
99
|
content = content.replace(/Array<(\w+)>/g, '$1[]');
|
|
84
|
-
// Find all Dto type references in the file body (excluding the interface name itself)
|
|
85
100
|
const references = new Set();
|
|
86
101
|
const typeRegex = /\b(\w+Dto)\b/g;
|
|
87
102
|
let match;
|
|
88
103
|
while ((match = typeRegex.exec(content)) !== null) {
|
|
89
|
-
if (match[1] !== selfName)
|
|
104
|
+
if (match[1] !== selfName)
|
|
90
105
|
references.add(match[1]);
|
|
91
|
-
}
|
|
92
106
|
}
|
|
93
|
-
// Build import lines for each referenced type that exists in the dtoMap
|
|
94
107
|
const imports = [];
|
|
95
108
|
references.forEach((ref) => {
|
|
96
109
|
if (dtoMap[ref]) {
|
|
97
|
-
|
|
110
|
+
const { subfolder: refSubfolder, fileBase: refFileBase } = dtoMap[ref];
|
|
111
|
+
const fromDir = subfolder ? path_1.default.join(dtosDir, subfolder) : dtosDir;
|
|
112
|
+
const toFile = refSubfolder
|
|
113
|
+
? path_1.default.join(dtosDir, refSubfolder, refFileBase)
|
|
114
|
+
: path_1.default.join(dtosDir, refFileBase);
|
|
115
|
+
let relPath = path_1.default.relative(fromDir, toFile).replace(/\\/g, '/');
|
|
116
|
+
if (!relPath.startsWith('.'))
|
|
117
|
+
relPath = './' + relPath;
|
|
118
|
+
imports.push(`import { ${ref} } from '${relPath}';`);
|
|
98
119
|
}
|
|
99
120
|
});
|
|
100
|
-
if (imports.length > 0)
|
|
121
|
+
if (imports.length > 0)
|
|
101
122
|
content = imports.join('\n') + '\n' + content;
|
|
102
|
-
}
|
|
103
123
|
if (content !== originalContent) {
|
|
104
|
-
fs_extra_1.default.writeFileSync(
|
|
124
|
+
fs_extra_1.default.writeFileSync(fullPath, content);
|
|
105
125
|
filesProcessed++;
|
|
106
126
|
(0, logger_1.logDetail)('dto', `Post-processed ${file} (added ${imports.length} import(s))`);
|
|
107
127
|
}
|
|
@@ -7,6 +7,7 @@ exports.generateReport = generateReport;
|
|
|
7
7
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
const logger_1 = require("../utils/logger");
|
|
10
|
+
const example_validator_1 = require("../utils/example-validator");
|
|
10
11
|
/** Counts files ending with `.mock.ts` in a directory (returns 0 if directory does not exist). */
|
|
11
12
|
function countMockFiles(dir) {
|
|
12
13
|
try {
|
|
@@ -37,6 +38,7 @@ function generateReport(outputDir, analysis, lintResult) {
|
|
|
37
38
|
})).length;
|
|
38
39
|
return { name: t.name, description: t.description || '', endpoints: endpointCount };
|
|
39
40
|
});
|
|
41
|
+
const exampleMismatches = (0, example_validator_1.getExampleMismatches)();
|
|
40
42
|
const report = {
|
|
41
43
|
timestamp: new Date().toISOString(),
|
|
42
44
|
tags: analysis.tags.length,
|
|
@@ -44,6 +46,10 @@ function generateReport(outputDir, analysis, lintResult) {
|
|
|
44
46
|
tagDetails,
|
|
45
47
|
outputDirectory: outputDir,
|
|
46
48
|
linting: lintResult,
|
|
49
|
+
warnings: {
|
|
50
|
+
exampleMismatches: exampleMismatches.map((m) => ({ ...m })),
|
|
51
|
+
total: exampleMismatches.length
|
|
52
|
+
},
|
|
47
53
|
structure: {
|
|
48
54
|
dtos: fs_extra_1.default.readdirSync(path_1.default.join(outputDir, 'data/dtos')).length,
|
|
49
55
|
repositories: fs_extra_1.default.readdirSync(path_1.default.join(outputDir, 'data/repositories')).length,
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Validates that OpenAPI `example` values match their declared `type`.
|
|
4
|
+
*
|
|
5
|
+
* YAML parses unquoted values by native type (e.g. `example: 68131` becomes a JS number
|
|
6
|
+
* even when the schema declares `type: string`). This module detects such mismatches,
|
|
7
|
+
* coerces them when possible, and accumulates warnings for the generation report.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.validateExample = validateExample;
|
|
11
|
+
exports.registerMismatch = registerMismatch;
|
|
12
|
+
exports.getExampleMismatches = getExampleMismatches;
|
|
13
|
+
exports.clearExampleMismatches = clearExampleMismatches;
|
|
14
|
+
// Module-level accumulator — reset between runs via `clearExampleMismatches()`.
|
|
15
|
+
let mismatches = [];
|
|
16
|
+
/**
|
|
17
|
+
* Validates an `example` value against a declared OpenAPI `type`.
|
|
18
|
+
*
|
|
19
|
+
* @returns `{ valid: true }` when types already match, or
|
|
20
|
+
* `{ valid: false, coerced: <value> }` when the value was coerced, or
|
|
21
|
+
* `{ valid: false }` when coercion is not possible (caller should ignore the example).
|
|
22
|
+
*/
|
|
23
|
+
function validateExample(declaredType, example) {
|
|
24
|
+
if (declaredType === undefined)
|
|
25
|
+
return { valid: true };
|
|
26
|
+
const jsType = typeof example;
|
|
27
|
+
// ── string declared ──────────────────────────────────────────────────────
|
|
28
|
+
if (declaredType === 'string') {
|
|
29
|
+
if (jsType === 'string')
|
|
30
|
+
return { valid: true };
|
|
31
|
+
// number or boolean → coerce to string
|
|
32
|
+
if (jsType === 'number' || jsType === 'boolean') {
|
|
33
|
+
return { valid: false, coerced: String(example) };
|
|
34
|
+
}
|
|
35
|
+
return { valid: false };
|
|
36
|
+
}
|
|
37
|
+
// ── integer / number declared ────────────────────────────────────────────
|
|
38
|
+
if (declaredType === 'integer' || declaredType === 'number') {
|
|
39
|
+
if (jsType === 'number')
|
|
40
|
+
return { valid: true };
|
|
41
|
+
if (jsType === 'string') {
|
|
42
|
+
const parsed = Number(example);
|
|
43
|
+
if (!Number.isNaN(parsed))
|
|
44
|
+
return { valid: false, coerced: parsed };
|
|
45
|
+
return { valid: false }; // unparseable → ignore
|
|
46
|
+
}
|
|
47
|
+
return { valid: false };
|
|
48
|
+
}
|
|
49
|
+
// ── boolean declared ─────────────────────────────────────────────────────
|
|
50
|
+
if (declaredType === 'boolean') {
|
|
51
|
+
if (jsType === 'boolean')
|
|
52
|
+
return { valid: true };
|
|
53
|
+
if (jsType === 'string') {
|
|
54
|
+
const lower = example.toLowerCase();
|
|
55
|
+
if (lower === 'true')
|
|
56
|
+
return { valid: false, coerced: true };
|
|
57
|
+
if (lower === 'false')
|
|
58
|
+
return { valid: false, coerced: false };
|
|
59
|
+
}
|
|
60
|
+
return { valid: false }; // cannot coerce
|
|
61
|
+
}
|
|
62
|
+
// Other types (object, array, etc.) — no validation
|
|
63
|
+
return { valid: true };
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Records a mismatch so it can be retrieved later for console warnings and the report.
|
|
67
|
+
*/
|
|
68
|
+
function registerMismatch(schemaName, propertyName, declaredType, exampleValue, action, coercedValue) {
|
|
69
|
+
mismatches.push({
|
|
70
|
+
schemaName,
|
|
71
|
+
propertyName,
|
|
72
|
+
declaredType,
|
|
73
|
+
exampleValue,
|
|
74
|
+
exampleJsType: typeof exampleValue,
|
|
75
|
+
action,
|
|
76
|
+
coercedValue
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
/** Returns all recorded mismatches. */
|
|
80
|
+
function getExampleMismatches() {
|
|
81
|
+
return mismatches;
|
|
82
|
+
}
|
|
83
|
+
/** Clears all recorded mismatches (call before each generation run). */
|
|
84
|
+
function clearExampleMismatches() {
|
|
85
|
+
mismatches = [];
|
|
86
|
+
}
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.resolveMockValue = resolveMockValue;
|
|
4
|
+
const example_validator_1 = require("./example-validator");
|
|
4
5
|
/**
|
|
5
6
|
* Resolves a TypeScript literal string to use as a mock value for a single schema property.
|
|
6
7
|
*
|
|
7
8
|
* Priority chain:
|
|
8
9
|
* $ref mock call → array $ref mock call → enum[0] → example → format fallback → type default
|
|
9
10
|
*
|
|
10
|
-
* @param propName
|
|
11
|
-
* @param prop
|
|
12
|
-
* @param context
|
|
11
|
+
* @param propName Property name (used for format heuristics such as "email").
|
|
12
|
+
* @param prop Raw OpenAPI property definition.
|
|
13
|
+
* @param context 'dto' generates `mockFooDto()`, 'model' generates `mockFooModel()`.
|
|
14
|
+
* @param schemaName Parent schema name (used for mismatch reporting).
|
|
13
15
|
*/
|
|
14
|
-
function resolveMockValue(propName, prop, context = 'dto') {
|
|
16
|
+
function resolveMockValue(propName, prop, context = 'dto', schemaName = 'unknown') {
|
|
15
17
|
const suffix = context === 'dto' ? 'Dto' : 'Model';
|
|
16
18
|
// 1. Direct $ref → call the referenced mock factory
|
|
17
19
|
if (prop.$ref) {
|
|
@@ -31,9 +33,19 @@ function resolveMockValue(propName, prop, context = 'dto') {
|
|
|
31
33
|
const first = prop.enum[0];
|
|
32
34
|
return typeof first === 'string' ? `'${first}'` : String(first);
|
|
33
35
|
}
|
|
34
|
-
// 5. Example value
|
|
35
|
-
if (prop.example !== undefined)
|
|
36
|
-
|
|
36
|
+
// 5. Example value — validated and coerced if needed
|
|
37
|
+
if (prop.example !== undefined) {
|
|
38
|
+
const result = (0, example_validator_1.validateExample)(prop.type, prop.example);
|
|
39
|
+
if (result.valid) {
|
|
40
|
+
return formatLiteral(prop.example);
|
|
41
|
+
}
|
|
42
|
+
if (result.coerced !== undefined) {
|
|
43
|
+
(0, example_validator_1.registerMismatch)(schemaName, propName, prop.type, prop.example, 'coerced', result.coerced);
|
|
44
|
+
return formatLiteral(result.coerced);
|
|
45
|
+
}
|
|
46
|
+
// Cannot coerce — register and fall through to defaults
|
|
47
|
+
(0, example_validator_1.registerMismatch)(schemaName, propName, prop.type, prop.example, 'ignored');
|
|
48
|
+
}
|
|
37
49
|
// 6. Format-aware fallbacks (when no example is provided)
|
|
38
50
|
if (prop.format === 'date-time')
|
|
39
51
|
return `'2024-01-01T00:00:00.000Z'`;
|
|
@@ -1,16 +1,113 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.toPascalCase = toPascalCase;
|
|
3
4
|
exports.toCamelCase = toCamelCase;
|
|
5
|
+
exports.isReservedWord = isReservedWord;
|
|
6
|
+
exports.safePropertyName = safePropertyName;
|
|
4
7
|
/**
|
|
5
|
-
* Converts a
|
|
8
|
+
* Converts a string to PascalCase, handling spaces, hyphens and underscores.
|
|
9
|
+
* Used to derive class names from schema/tag names.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* toPascalCase('Product Format') // 'ProductFormat'
|
|
13
|
+
* toPascalCase('user-response') // 'UserResponse'
|
|
14
|
+
* toPascalCase('UserSchema') // 'UserSchema'
|
|
15
|
+
*/
|
|
16
|
+
function toPascalCase(name) {
|
|
17
|
+
if (!name)
|
|
18
|
+
return name;
|
|
19
|
+
return name
|
|
20
|
+
.split(/[\s\-_]+/)
|
|
21
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
22
|
+
.join('');
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Converts a string to camelCase, handling spaces, hyphens and underscores.
|
|
6
26
|
* Used to derive class filenames and variable names from schema/tag names.
|
|
7
27
|
*
|
|
8
28
|
* @example
|
|
29
|
+
* toCamelCase('Product Format') // 'productFormat'
|
|
9
30
|
* toCamelCase('ProductResponse') // 'productResponse'
|
|
10
31
|
* toCamelCase('UserSchema') // 'userSchema'
|
|
11
32
|
*/
|
|
12
33
|
function toCamelCase(name) {
|
|
13
34
|
if (!name)
|
|
14
35
|
return name;
|
|
15
|
-
|
|
36
|
+
const pascal = toPascalCase(name);
|
|
37
|
+
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
38
|
+
}
|
|
39
|
+
const JS_RESERVED_WORDS = new Set([
|
|
40
|
+
'abstract',
|
|
41
|
+
'arguments',
|
|
42
|
+
'await',
|
|
43
|
+
'boolean',
|
|
44
|
+
'break',
|
|
45
|
+
'byte',
|
|
46
|
+
'case',
|
|
47
|
+
'catch',
|
|
48
|
+
'char',
|
|
49
|
+
'class',
|
|
50
|
+
'const',
|
|
51
|
+
'continue',
|
|
52
|
+
'debugger',
|
|
53
|
+
'default',
|
|
54
|
+
'delete',
|
|
55
|
+
'do',
|
|
56
|
+
'double',
|
|
57
|
+
'else',
|
|
58
|
+
'enum',
|
|
59
|
+
'eval',
|
|
60
|
+
'export',
|
|
61
|
+
'extends',
|
|
62
|
+
'false',
|
|
63
|
+
'final',
|
|
64
|
+
'finally',
|
|
65
|
+
'float',
|
|
66
|
+
'for',
|
|
67
|
+
'function',
|
|
68
|
+
'goto',
|
|
69
|
+
'if',
|
|
70
|
+
'implements',
|
|
71
|
+
'import',
|
|
72
|
+
'in',
|
|
73
|
+
'instanceof',
|
|
74
|
+
'int',
|
|
75
|
+
'interface',
|
|
76
|
+
'let',
|
|
77
|
+
'long',
|
|
78
|
+
'native',
|
|
79
|
+
'new',
|
|
80
|
+
'null',
|
|
81
|
+
'package',
|
|
82
|
+
'private',
|
|
83
|
+
'protected',
|
|
84
|
+
'public',
|
|
85
|
+
'return',
|
|
86
|
+
'short',
|
|
87
|
+
'static',
|
|
88
|
+
'super',
|
|
89
|
+
'switch',
|
|
90
|
+
'synchronized',
|
|
91
|
+
'this',
|
|
92
|
+
'throw',
|
|
93
|
+
'throws',
|
|
94
|
+
'transient',
|
|
95
|
+
'true',
|
|
96
|
+
'try',
|
|
97
|
+
'typeof',
|
|
98
|
+
'undefined',
|
|
99
|
+
'var',
|
|
100
|
+
'void',
|
|
101
|
+
'volatile',
|
|
102
|
+
'while',
|
|
103
|
+
'with',
|
|
104
|
+
'yield'
|
|
105
|
+
]);
|
|
106
|
+
/** Returns true if the given name is a JS/TS reserved word. */
|
|
107
|
+
function isReservedWord(name) {
|
|
108
|
+
return JS_RESERVED_WORDS.has(name);
|
|
109
|
+
}
|
|
110
|
+
/** Prefixes reserved words with `_` to produce a safe identifier. */
|
|
111
|
+
function safePropertyName(name) {
|
|
112
|
+
return isReservedWord(name) ? `_${name}` : name;
|
|
16
113
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { InjectionToken } from '@angular/core';
|
|
5
5
|
import { Observable } from 'rxjs';
|
|
6
6
|
{{#imports}}
|
|
7
|
-
import { {{classname}} } from '@/entities/models/{{classFilename}}.model';
|
|
7
|
+
import { {{classname}} } from '@/entities/models/{{tagFilename}}/{{classFilename}}.model';
|
|
8
8
|
{{/imports}}
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
import { MockService } from 'ng-mocks';
|
|
5
5
|
import { of } from 'rxjs';
|
|
6
6
|
|
|
7
|
-
import { {{classname}}RepositoryImpl } from '@/data/repositories/{{classFilename}}.repository.impl';
|
|
7
|
+
import { {{classname}}RepositoryImpl } from '@/data/repositories/{{classFilename}}/{{classFilename}}.repository.impl';
|
|
8
8
|
{{#returnImports}}
|
|
9
|
-
import { mock{{classname}}Model } from '@/entities/models/{{classFilename}}.model.mock';
|
|
9
|
+
import { mock{{classname}}Model } from '@/entities/models/{{tagFilename}}/{{classFilename}}.model.mock';
|
|
10
10
|
{{/returnImports}}
|
|
11
11
|
|
|
12
12
|
export const mock{{classname}}RepositoryImpl = () =>
|
|
@@ -9,14 +9,14 @@ import { environment } from '@environment';
|
|
|
9
9
|
|
|
10
10
|
import { MRepository } from '@mercadona/core/utils/repository';
|
|
11
11
|
|
|
12
|
-
import { {{classname}}Repository } from '@/domain/repositories/{{classFilename}}.repository.contract';
|
|
12
|
+
import { {{classname}}Repository } from '@/domain/repositories/{{classFilename}}/{{classFilename}}.repository.contract';
|
|
13
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';
|
|
14
|
+
import { {{classname}}Dto } from '@/dtos/{{tagFilename}}/{{classFilename}}.dto';
|
|
15
|
+
import { {{classname}} } from '@/entities/models/{{tagFilename}}/{{classFilename}}.model';
|
|
16
|
+
import { {{classVarName}}Mapper } from '@/mappers/{{tagFilename}}/{{classFilename}}.mapper';
|
|
17
17
|
{{/returnImports}}
|
|
18
18
|
{{#paramImports}}
|
|
19
|
-
import { {{classname}} } from '@/entities/models/{{classFilename}}.model';
|
|
19
|
+
import { {{classname}} } from '@/entities/models/{{tagFilename}}/{{classFilename}}.model';
|
|
20
20
|
{{/paramImports}}
|
|
21
21
|
|
|
22
22
|
/**
|
|
@@ -6,8 +6,8 @@ import { TestBed } from '@angular/core/testing';
|
|
|
6
6
|
|
|
7
7
|
import { {{classname}}RepositoryImpl } from './{{classFilename}}.repository.impl';
|
|
8
8
|
{{#returnImports}}
|
|
9
|
-
import { mock{{classname}}Dto } from '@/dtos/{{classFilename}}.dto.mock';
|
|
10
|
-
import { mock{{classname}}Model } from '@/entities/models/{{classFilename}}.model.mock';
|
|
9
|
+
import { mock{{classname}}Dto } from '@/dtos/{{tagFilename}}/{{classFilename}}.dto.mock';
|
|
10
|
+
import { mock{{classname}}Model } from '@/entities/models/{{tagFilename}}/{{classFilename}}.model.mock';
|
|
11
11
|
{{/returnImports}}
|
|
12
12
|
|
|
13
13
|
describe('{{classname}}RepositoryImpl', () => {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { InjectionToken } from '@angular/core';
|
|
5
5
|
import { Observable } from 'rxjs';
|
|
6
6
|
{{#imports}}
|
|
7
|
-
import { {{classname}} } from '@/entities/models/{{classFilename}}.model';
|
|
7
|
+
import { {{classname}} } from '@/entities/models/{{tagFilename}}/{{classFilename}}.model';
|
|
8
8
|
{{/imports}}
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -6,9 +6,9 @@ import { Observable } from 'rxjs';
|
|
|
6
6
|
|
|
7
7
|
import { {{classname}}UseCases } from './{{classFilename}}.use-cases.contract';
|
|
8
8
|
|
|
9
|
-
import { {{constantName}}_REPOSITORY, {{classname}}Repository } from '@/domain/repositories/{{classFilename}}.repository.contract';
|
|
9
|
+
import { {{constantName}}_REPOSITORY, {{classname}}Repository } from '@/domain/repositories/{{classFilename}}/{{classFilename}}.repository.contract';
|
|
10
10
|
{{#imports}}
|
|
11
|
-
import { {{classname}} } from '@/entities/models/{{classFilename}}.model';
|
|
11
|
+
import { {{classname}} } from '@/entities/models/{{tagFilename}}/{{classFilename}}.model';
|
|
12
12
|
{{/imports}}
|
|
13
13
|
|
|
14
14
|
/**
|
|
@@ -6,9 +6,9 @@ import { of } from 'rxjs';
|
|
|
6
6
|
|
|
7
7
|
import { {{classname}}UseCasesImpl } from './{{classFilename}}.use-cases.impl';
|
|
8
8
|
|
|
9
|
-
import { {{constantName}}_REPOSITORY, {{classname}}Repository } from '@/domain/repositories/{{classFilename}}.repository.contract';
|
|
9
|
+
import { {{constantName}}_REPOSITORY, {{classname}}Repository } from '@/domain/repositories/{{classFilename}}/{{classFilename}}.repository.contract';
|
|
10
10
|
{{#returnImports}}
|
|
11
|
-
import { mock{{classname}}Model } from '@/entities/models/{{classFilename}}.model.mock';
|
|
11
|
+
import { mock{{classname}}Model } from '@/entities/models/{{tagFilename}}/{{classFilename}}.model.mock';
|
|
12
12
|
{{/returnImports}}
|
|
13
13
|
|
|
14
14
|
describe('{{classname}}UseCasesImpl', () => {
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
import { MockService } from 'ng-mocks';
|
|
5
5
|
import { of } from 'rxjs';
|
|
6
6
|
|
|
7
|
-
import { {{classname}}UseCasesImpl } from '@/domain/use-cases/{{classFilename}}.use-cases.impl';
|
|
7
|
+
import { {{classname}}UseCasesImpl } from '@/domain/use-cases/{{classFilename}}/{{classFilename}}.use-cases.impl';
|
|
8
8
|
{{#returnImports}}
|
|
9
|
-
import { mock{{classname}}Model } from '@/entities/models/{{classFilename}}.model.mock';
|
|
9
|
+
import { mock{{classname}}Model } from '@/entities/models/{{tagFilename}}/{{classFilename}}.model.mock';
|
|
10
10
|
{{/returnImports}}
|
|
11
11
|
|
|
12
12
|
export const mock{{classname}}UseCasesImpl = () =>
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
import { MapFromFn } from '@mercadona/common/public';
|
|
5
5
|
import { Builder } from '@mercadona/common/utils';
|
|
6
6
|
|
|
7
|
-
import { {{classname}}Dto } from '@/dtos/{{classFilename}}.dto';
|
|
8
|
-
import { {{classname}} } from '@/entities/models/{{classFilename}}.model';
|
|
7
|
+
import { {{classname}}Dto } from '@/dtos/{{tagFilename}}/{{classFilename}}.dto';
|
|
8
|
+
import { {{classname}} } from '@/entities/models/{{tagFilename}}/{{classFilename}}.model';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* {{classname}} Mapper
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
{{#model}}
|
|
3
3
|
import { {{classVarName}}Mapper } from './{{classFilename}}.mapper';
|
|
4
4
|
|
|
5
|
-
import { mock{{classname}}Dto } from '@/dtos/{{classFilename}}.dto.mock';
|
|
6
|
-
import { {{classname}} } from '@/entities/models/{{classFilename}}.model';
|
|
5
|
+
import { mock{{classname}}Dto } from '@/dtos/{{tagFilename}}/{{classFilename}}.dto.mock';
|
|
6
|
+
import { {{classname}} } from '@/entities/models/{{tagFilename}}/{{classFilename}}.model';
|
|
7
7
|
|
|
8
8
|
describe('{{classVarName}}Mapper', () => {
|
|
9
9
|
{{#vars}}
|
|
@@ -11,6 +11,7 @@ describe('{{classname}}', () => {
|
|
|
11
11
|
});
|
|
12
12
|
|
|
13
13
|
{{#vars}}
|
|
14
|
+
{{#hasMockValue}}
|
|
14
15
|
it('should allow setting {{name}}', () => {
|
|
15
16
|
const model = new {{classname}}();
|
|
16
17
|
const expected = mock{{classname}}Model();
|
|
@@ -19,13 +20,16 @@ describe('{{classname}}', () => {
|
|
|
19
20
|
expect(model.{{name}}).toBe(expected.{{name}});
|
|
20
21
|
});
|
|
21
22
|
|
|
23
|
+
{{/hasMockValue}}
|
|
22
24
|
{{/vars}}
|
|
23
25
|
it('should build a valid model from mock', () => {
|
|
24
26
|
const model = mock{{classname}}Model();
|
|
25
27
|
|
|
26
28
|
expect(model).toBeInstanceOf({{classname}});
|
|
27
29
|
{{#vars}}
|
|
30
|
+
{{#hasMockValue}}
|
|
28
31
|
expect(model.{{name}}).toBeDefined();
|
|
32
|
+
{{/hasMockValue}}
|
|
29
33
|
{{/vars}}
|
|
30
34
|
});
|
|
31
35
|
});
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{{#models}}
|
|
2
2
|
{{#model}}
|
|
3
3
|
import { {{classname}} } from './{{classFilename}}.model';
|
|
4
|
-
import { {{classVarName}}Mapper } from '@/mappers/{{classFilename}}.mapper';
|
|
5
|
-
import { mock{{classname}}Dto } from '@/dtos/{{classFilename}}.dto.mock';
|
|
4
|
+
import { {{classVarName}}Mapper } from '@/mappers/{{tagFilename}}/{{classFilename}}.mapper';
|
|
5
|
+
import { mock{{classname}}Dto } from '@/dtos/{{tagFilename}}/{{classFilename}}.dto.mock';
|
|
6
6
|
|
|
7
7
|
export const mock{{classname}}Model = (overrides: Partial<{{classname}}> = {}): {{classname}} =>
|
|
8
8
|
Object.assign(new {{classname}}(), {
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
{{#operations}}
|
|
4
4
|
import { Provider } from '@angular/core';
|
|
5
5
|
|
|
6
|
-
import { {{constantName}}_REPOSITORY } from '@/domain/repositories/{{classFilename}}.repository.contract';
|
|
7
|
-
import { mock{{classname}}RepositoryImpl } from '@/data/repositories/{{classFilename}}.repository.impl.mock';
|
|
6
|
+
import { {{constantName}}_REPOSITORY } from '@/domain/repositories/{{classFilename}}/{{classFilename}}.repository.contract';
|
|
7
|
+
import { mock{{classname}}RepositoryImpl } from '@/data/repositories/{{classFilename}}/{{classFilename}}.repository.impl.mock';
|
|
8
8
|
|
|
9
9
|
export function mock{{classname}}Repository(): Provider[] {
|
|
10
10
|
return [
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
{{#operations}}
|
|
4
4
|
import { EnvironmentProviders, makeEnvironmentProviders } from '@angular/core';
|
|
5
5
|
|
|
6
|
-
import { {{constantName}}_REPOSITORY } from '@/domain/repositories/{{classFilename}}.repository.contract';
|
|
7
|
-
import { {{classname}}RepositoryImpl } from '@/data/repositories/{{classFilename}}.repository.impl';
|
|
6
|
+
import { {{constantName}}_REPOSITORY } from '@/domain/repositories/{{classFilename}}/{{classFilename}}.repository.contract';
|
|
7
|
+
import { {{classname}}RepositoryImpl } from '@/data/repositories/{{classFilename}}/{{classFilename}}.repository.impl';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* {{classname}} Repository Provider
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
{{#operations}}
|
|
4
4
|
import { Provider } from '@angular/core';
|
|
5
5
|
|
|
6
|
-
import { {{constantName}}_USE_CASES } from '@/domain/use-cases/{{classFilename}}.use-cases.contract';
|
|
7
|
-
import { mock{{classname}}UseCasesImpl } from '@/domain/use-cases/{{classFilename}}.use-cases.mock';
|
|
6
|
+
import { {{constantName}}_USE_CASES } from '@/domain/use-cases/{{classFilename}}/{{classFilename}}.use-cases.contract';
|
|
7
|
+
import { mock{{classname}}UseCasesImpl } from '@/domain/use-cases/{{classFilename}}/{{classFilename}}.use-cases.mock';
|
|
8
8
|
|
|
9
9
|
export function mock{{classname}}UseCases(): Provider[] {
|
|
10
10
|
return [
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
{{#operations}}
|
|
4
4
|
import { EnvironmentProviders, makeEnvironmentProviders } from '@angular/core';
|
|
5
5
|
|
|
6
|
-
import { {{constantName}}_USE_CASES } from '@/domain/use-cases/{{classFilename}}.use-cases.contract';
|
|
7
|
-
import { {{classname}}UseCasesImpl } from '@/domain/use-cases/{{classFilename}}.use-cases.impl';
|
|
6
|
+
import { {{constantName}}_USE_CASES } from '@/domain/use-cases/{{classFilename}}/{{classFilename}}.use-cases.contract';
|
|
7
|
+
import { {{classname}}UseCasesImpl } from '@/domain/use-cases/{{classFilename}}/{{classFilename}}.use-cases.impl';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* {{classname}} Use Cases Provider
|