@currentjs/gen 0.3.2 → 0.5.0
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/CHANGELOG.md +10 -611
- package/README.md +623 -427
- package/dist/cli.js +2 -1
- package/dist/commands/commit.js +25 -42
- package/dist/commands/createApp.js +1 -0
- package/dist/commands/createModule.js +151 -45
- package/dist/commands/diff.js +27 -40
- package/dist/commands/generateAll.js +141 -291
- package/dist/commands/migrateCommit.js +6 -18
- package/dist/commands/migratePush.d.ts +1 -0
- package/dist/commands/migratePush.js +135 -0
- package/dist/commands/migrateUpdate.d.ts +1 -0
- package/dist/commands/migrateUpdate.js +147 -0
- package/dist/commands/newGenerateAll.d.ts +4 -0
- package/dist/commands/newGenerateAll.js +336 -0
- package/dist/generators/controllerGenerator.d.ts +43 -19
- package/dist/generators/controllerGenerator.js +547 -329
- package/dist/generators/domainLayerGenerator.d.ts +21 -0
- package/dist/generators/domainLayerGenerator.js +276 -0
- package/dist/generators/dtoGenerator.d.ts +21 -0
- package/dist/generators/dtoGenerator.js +518 -0
- package/dist/generators/newControllerGenerator.d.ts +55 -0
- package/dist/generators/newControllerGenerator.js +644 -0
- package/dist/generators/newServiceGenerator.d.ts +19 -0
- package/dist/generators/newServiceGenerator.js +266 -0
- package/dist/generators/newStoreGenerator.d.ts +39 -0
- package/dist/generators/newStoreGenerator.js +408 -0
- package/dist/generators/newTemplateGenerator.d.ts +29 -0
- package/dist/generators/newTemplateGenerator.js +510 -0
- package/dist/generators/serviceGenerator.d.ts +16 -51
- package/dist/generators/serviceGenerator.js +167 -586
- package/dist/generators/storeGenerator.d.ts +35 -32
- package/dist/generators/storeGenerator.js +291 -238
- package/dist/generators/storeGeneratorV2.d.ts +31 -0
- package/dist/generators/storeGeneratorV2.js +190 -0
- package/dist/generators/templateGenerator.d.ts +21 -21
- package/dist/generators/templateGenerator.js +393 -268
- package/dist/generators/templates/appTemplates.d.ts +3 -1
- package/dist/generators/templates/appTemplates.js +15 -10
- package/dist/generators/templates/data/appYamlTemplate +5 -2
- package/dist/generators/templates/data/cursorRulesTemplate +315 -221
- package/dist/generators/templates/data/frontendScriptTemplate +45 -11
- package/dist/generators/templates/data/mainViewTemplate +1 -1
- package/dist/generators/templates/data/systemTsTemplate +5 -0
- package/dist/generators/templates/index.d.ts +0 -3
- package/dist/generators/templates/index.js +0 -3
- package/dist/generators/templates/newStoreTemplates.d.ts +5 -0
- package/dist/generators/templates/newStoreTemplates.js +141 -0
- package/dist/generators/templates/storeTemplates.d.ts +1 -5
- package/dist/generators/templates/storeTemplates.js +102 -219
- package/dist/generators/templates/viewTemplates.js +1 -1
- package/dist/generators/useCaseGenerator.d.ts +13 -0
- package/dist/generators/useCaseGenerator.js +188 -0
- package/dist/types/configTypes.d.ts +148 -0
- package/dist/types/configTypes.js +10 -0
- package/dist/utils/childEntityUtils.d.ts +18 -0
- package/dist/utils/childEntityUtils.js +78 -0
- package/dist/utils/commandUtils.d.ts +43 -0
- package/dist/utils/commandUtils.js +124 -0
- package/dist/utils/commitUtils.d.ts +4 -1
- package/dist/utils/constants.d.ts +10 -0
- package/dist/utils/constants.js +13 -1
- package/dist/utils/diResolver.d.ts +32 -0
- package/dist/utils/diResolver.js +204 -0
- package/dist/utils/new_parts_of_migrationUtils.d.ts +0 -0
- package/dist/utils/new_parts_of_migrationUtils.js +164 -0
- package/dist/utils/typeUtils.d.ts +19 -0
- package/dist/utils/typeUtils.js +70 -0
- package/package.json +7 -3
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.NewTemplateGenerator = void 0;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const yaml_1 = require("yaml");
|
|
40
|
+
const generationRegistry_1 = require("../utils/generationRegistry");
|
|
41
|
+
const colors_1 = require("../utils/colors");
|
|
42
|
+
const configTypes_1 = require("../types/configTypes");
|
|
43
|
+
const childEntityUtils_1 = require("../utils/childEntityUtils");
|
|
44
|
+
class NewTemplateGenerator {
|
|
45
|
+
constructor() {
|
|
46
|
+
this.valueObjects = {};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Convert a route prefix like "/invoice/:invoiceId/items" into a
|
|
50
|
+
* template-ready path like "/invoice/{{ invoiceId }}/items".
|
|
51
|
+
*/
|
|
52
|
+
prefixToTemplatePath(prefix) {
|
|
53
|
+
return prefix.replace(/:([a-zA-Z_]\w*)/g, '{{ $1 }}');
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Replace a single path param in prefix with a template expression (e.g. for list: item.id, for detail: id).
|
|
57
|
+
*/
|
|
58
|
+
prefixWithParam(prefix, paramName, templateExpr) {
|
|
59
|
+
return prefix.replace(new RegExp(':' + paramName + '(?=/|$)'), templateExpr);
|
|
60
|
+
}
|
|
61
|
+
renderListTemplate(modelName, viewName, fields, basePath, withChildChildren) {
|
|
62
|
+
const fieldHeaders = fields
|
|
63
|
+
.filter(([name]) => name !== 'id')
|
|
64
|
+
.slice(0, 5)
|
|
65
|
+
.map(([name]) => ` <th>${this.capitalize(name)}</th>`)
|
|
66
|
+
.join('\n');
|
|
67
|
+
const fieldCells = fields
|
|
68
|
+
.filter(([name]) => name !== 'id')
|
|
69
|
+
.slice(0, 5)
|
|
70
|
+
.map(([name, config]) => {
|
|
71
|
+
const voConfig = this.valueObjects[this.capitalize((config.type || 'string'))];
|
|
72
|
+
if (voConfig) {
|
|
73
|
+
const parts = Object.keys(voConfig.fields)
|
|
74
|
+
.map(sub => `{{ item.${name}.${sub} }}`)
|
|
75
|
+
.join(' ');
|
|
76
|
+
return ` <td>${parts}</td>`;
|
|
77
|
+
}
|
|
78
|
+
return ` <td>{{ item.${name} }}</td>`;
|
|
79
|
+
})
|
|
80
|
+
.join('\n');
|
|
81
|
+
const childLinkHeaders = (withChildChildren || [])
|
|
82
|
+
.map(child => ` <th>${child.childEntityName}</th>`)
|
|
83
|
+
.join('\n');
|
|
84
|
+
const childLinkCells = (withChildChildren || [])
|
|
85
|
+
.map(child => {
|
|
86
|
+
const childPath = child.childWebPrefix
|
|
87
|
+
? this.prefixWithParam(child.childWebPrefix, child.parentIdField, '{{ item.id }}')
|
|
88
|
+
: '#';
|
|
89
|
+
return ` <td><a href="${childPath}" class="btn btn-sm btn-outline-secondary">Items</a></td>`;
|
|
90
|
+
})
|
|
91
|
+
.join('\n');
|
|
92
|
+
const childHeaderBlock = childLinkHeaders ? '\n' + childLinkHeaders : '';
|
|
93
|
+
const childCellBlock = childLinkCells ? '\n' + childLinkCells : '';
|
|
94
|
+
return `<!-- @template name="${viewName}" -->
|
|
95
|
+
<div class="container mt-4">
|
|
96
|
+
<h1>${modelName} List</h1>
|
|
97
|
+
|
|
98
|
+
<div class="mb-3">
|
|
99
|
+
<a href="${basePath}/create" class="btn btn-primary">Create New ${modelName}</a>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<table class="table table-striped">
|
|
103
|
+
<thead>
|
|
104
|
+
<tr>
|
|
105
|
+
${fieldHeaders}
|
|
106
|
+
<th>Actions</th>${childHeaderBlock}
|
|
107
|
+
</tr>
|
|
108
|
+
</thead>
|
|
109
|
+
<tbody x-for="items" x-row="item">
|
|
110
|
+
<tr>
|
|
111
|
+
${fieldCells}
|
|
112
|
+
<td>
|
|
113
|
+
<a href="${basePath}/{{ item.id }}" class="btn btn-sm btn-info">View</a>
|
|
114
|
+
<a href="${basePath}/{{ item.id }}/edit" class="btn btn-sm btn-warning">Edit</a>
|
|
115
|
+
</td>${childCellBlock}
|
|
116
|
+
</tr>
|
|
117
|
+
</tbody>
|
|
118
|
+
</table>
|
|
119
|
+
|
|
120
|
+
<div x-if="total > limit">
|
|
121
|
+
<nav>
|
|
122
|
+
<ul class="pagination">
|
|
123
|
+
<!-- Pagination controls -->
|
|
124
|
+
</ul>
|
|
125
|
+
</nav>
|
|
126
|
+
</div>
|
|
127
|
+
</div>`;
|
|
128
|
+
}
|
|
129
|
+
renderChildTableSection(child, parentIdTemplateExpr) {
|
|
130
|
+
const childVar = child.childEntityName.charAt(0).toLowerCase() + child.childEntityName.slice(1);
|
|
131
|
+
const childItemsKey = `${childVar}Items`;
|
|
132
|
+
const childBasePath = child.childWebPrefix
|
|
133
|
+
? this.prefixWithParam(child.childWebPrefix, child.parentIdField, parentIdTemplateExpr)
|
|
134
|
+
: '';
|
|
135
|
+
const fieldEntries = Object.entries(child.childFields).filter(([name]) => name !== 'id').slice(0, 5);
|
|
136
|
+
const headers = fieldEntries.map(([name]) => ` <th>${this.capitalize(name)}</th>`).join('\n');
|
|
137
|
+
const cells = fieldEntries.map(([name, config]) => {
|
|
138
|
+
const voConfig = this.valueObjects[this.capitalize(((config === null || config === void 0 ? void 0 : config.type) || 'string'))];
|
|
139
|
+
if (voConfig) {
|
|
140
|
+
const parts = Object.keys(voConfig.fields)
|
|
141
|
+
.map(sub => `{{ childItem.${name}.${sub} }}`)
|
|
142
|
+
.join(' ');
|
|
143
|
+
return ` <td>${parts}</td>`;
|
|
144
|
+
}
|
|
145
|
+
return ` <td>{{ childItem.${name} }}</td>`;
|
|
146
|
+
}).join('\n');
|
|
147
|
+
const addLink = childBasePath
|
|
148
|
+
? ` <div class="mb-3">
|
|
149
|
+
<a href="${childBasePath}/create" class="btn btn-primary btn-sm">Add ${child.childEntityName}</a>
|
|
150
|
+
</div>`
|
|
151
|
+
: '';
|
|
152
|
+
const actionLinks = childBasePath
|
|
153
|
+
? ` <td>
|
|
154
|
+
<a href="${childBasePath}/{{ childItem.id }}" class="btn btn-sm btn-info">View</a>
|
|
155
|
+
<a href="${childBasePath}/{{ childItem.id }}/edit" class="btn btn-sm btn-warning">Edit</a>
|
|
156
|
+
</td>`
|
|
157
|
+
: ' <td></td>';
|
|
158
|
+
return `
|
|
159
|
+
<h2 class="mt-4">${child.childEntityName} List</h2>
|
|
160
|
+
${addLink}
|
|
161
|
+
<table class="table table-striped">
|
|
162
|
+
<thead>
|
|
163
|
+
<tr>
|
|
164
|
+
${headers}
|
|
165
|
+
<th>Actions</th>
|
|
166
|
+
</tr>
|
|
167
|
+
</thead>
|
|
168
|
+
<tbody x-for="${childItemsKey}" x-row="childItem">
|
|
169
|
+
<tr>
|
|
170
|
+
${cells}
|
|
171
|
+
${actionLinks}
|
|
172
|
+
</tr>
|
|
173
|
+
</tbody>
|
|
174
|
+
</table>`;
|
|
175
|
+
}
|
|
176
|
+
renderDetailTemplate(modelName, viewName, fields, basePath, withChildChildren) {
|
|
177
|
+
const fieldRows = fields
|
|
178
|
+
.map(([name, config]) => {
|
|
179
|
+
const voConfig = this.valueObjects[this.capitalize((config.type || 'string'))];
|
|
180
|
+
if (voConfig) {
|
|
181
|
+
const parts = Object.keys(voConfig.fields)
|
|
182
|
+
.map(sub => `{{ ${name}.${sub} }}`)
|
|
183
|
+
.join(' ');
|
|
184
|
+
return ` <div class="row mb-2">
|
|
185
|
+
<div class="col-4"><strong>${this.capitalize(name)}:</strong></div>
|
|
186
|
+
<div class="col-8">${parts}</div>
|
|
187
|
+
</div>`;
|
|
188
|
+
}
|
|
189
|
+
return ` <div class="row mb-2">
|
|
190
|
+
<div class="col-4"><strong>${this.capitalize(name)}:</strong></div>
|
|
191
|
+
<div class="col-8">{{ ${name} }}</div>
|
|
192
|
+
</div>`;
|
|
193
|
+
})
|
|
194
|
+
.join('\n');
|
|
195
|
+
const childSections = (withChildChildren || [])
|
|
196
|
+
.map(child => this.renderChildTableSection(child, '{{ id }}'))
|
|
197
|
+
.join('');
|
|
198
|
+
return `<!-- @template name="${viewName}" -->
|
|
199
|
+
<div class="container mt-4">
|
|
200
|
+
<h1>${modelName} Details</h1>
|
|
201
|
+
|
|
202
|
+
<div class="card">
|
|
203
|
+
<div class="card-body">
|
|
204
|
+
${fieldRows}
|
|
205
|
+
</div>
|
|
206
|
+
</div>
|
|
207
|
+
|
|
208
|
+
<div class="mt-3">
|
|
209
|
+
<a href="${basePath}/{{ id }}/edit" class="btn btn-warning">Edit</a>
|
|
210
|
+
<a href="${basePath}" class="btn btn-secondary">Back to List</a>
|
|
211
|
+
</div>${childSections}
|
|
212
|
+
</div>`;
|
|
213
|
+
}
|
|
214
|
+
renderCreateTemplate(modelName, viewName, fields, basePath, onSuccess, onError, enumValuesMap = {}) {
|
|
215
|
+
const safeFields = fields.filter(([name, config]) => name !== 'id' && !config.auto);
|
|
216
|
+
const formFields = safeFields
|
|
217
|
+
.map(([name, config]) => this.renderFormField(name, config, enumValuesMap[name] || []))
|
|
218
|
+
.join('\n');
|
|
219
|
+
const fieldTypesJson = JSON.stringify(safeFields.reduce((acc, [name, config]) => {
|
|
220
|
+
const capitalizedType = this.capitalize(config.type || 'string');
|
|
221
|
+
const voConfig = this.valueObjects[capitalizedType];
|
|
222
|
+
if (voConfig) {
|
|
223
|
+
for (const [subName, subConfig] of Object.entries(voConfig.fields)) {
|
|
224
|
+
if (typeof subConfig === 'object' && 'values' in subConfig) {
|
|
225
|
+
acc[`${name}.${subName}`] = 'enum';
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
acc[`${name}.${subName}`] = subConfig.type || 'string';
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
acc[name] = config.type || 'string';
|
|
234
|
+
}
|
|
235
|
+
return acc;
|
|
236
|
+
}, {}));
|
|
237
|
+
// Build strategy array from onSuccess/onError
|
|
238
|
+
const strategies = [];
|
|
239
|
+
if (onSuccess === null || onSuccess === void 0 ? void 0 : onSuccess.toast)
|
|
240
|
+
strategies.push('toast');
|
|
241
|
+
if (onSuccess === null || onSuccess === void 0 ? void 0 : onSuccess.back)
|
|
242
|
+
strategies.push('back');
|
|
243
|
+
if (onSuccess === null || onSuccess === void 0 ? void 0 : onSuccess.redirect)
|
|
244
|
+
strategies.push('redirect');
|
|
245
|
+
const strategyAttr = strategies.length > 0
|
|
246
|
+
? `data-strategy='${JSON.stringify(strategies)}'`
|
|
247
|
+
: '';
|
|
248
|
+
const redirectAttr = (onSuccess === null || onSuccess === void 0 ? void 0 : onSuccess.redirect)
|
|
249
|
+
? `data-redirect="${this.prefixToTemplatePath(onSuccess.redirect)}"`
|
|
250
|
+
: '';
|
|
251
|
+
return `<!-- @template name="${viewName}" -->
|
|
252
|
+
<div class="container mt-4">
|
|
253
|
+
<h1>Create ${modelName}</h1>
|
|
254
|
+
|
|
255
|
+
<form method="POST" action="${basePath}/create" ${strategyAttr} ${redirectAttr} data-entity-name="${modelName}" data-field-types='${fieldTypesJson}'>
|
|
256
|
+
${formFields}
|
|
257
|
+
|
|
258
|
+
<div class="d-flex gap-2">
|
|
259
|
+
<button type="submit" class="btn btn-primary">Create</button>
|
|
260
|
+
<a href="${basePath}" class="btn btn-secondary">Cancel</a>
|
|
261
|
+
</div>
|
|
262
|
+
</form>
|
|
263
|
+
</div>`;
|
|
264
|
+
}
|
|
265
|
+
renderEditTemplate(modelName, viewName, fields, basePath, onSuccess, onError, enumValuesMap = {}) {
|
|
266
|
+
const safeFields = fields.filter(([name, config]) => name !== 'id' && !config.auto);
|
|
267
|
+
const formFields = safeFields
|
|
268
|
+
.map(([name, config]) => this.renderFormField(name, config, enumValuesMap[name] || [], true))
|
|
269
|
+
.join('\n');
|
|
270
|
+
const fieldTypesJson = JSON.stringify(safeFields.reduce((acc, [name, config]) => {
|
|
271
|
+
const capitalizedType = this.capitalize(config.type || 'string');
|
|
272
|
+
const voConfig = this.valueObjects[capitalizedType];
|
|
273
|
+
if (voConfig) {
|
|
274
|
+
for (const [subName, subConfig] of Object.entries(voConfig.fields)) {
|
|
275
|
+
if (typeof subConfig === 'object' && 'values' in subConfig) {
|
|
276
|
+
acc[`${name}.${subName}`] = 'enum';
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
acc[`${name}.${subName}`] = subConfig.type || 'string';
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
acc[name] = config.type || 'string';
|
|
285
|
+
}
|
|
286
|
+
return acc;
|
|
287
|
+
}, {}));
|
|
288
|
+
// Build strategy array from onSuccess/onError
|
|
289
|
+
const strategies = [];
|
|
290
|
+
if (onSuccess === null || onSuccess === void 0 ? void 0 : onSuccess.toast)
|
|
291
|
+
strategies.push('toast');
|
|
292
|
+
if (onSuccess === null || onSuccess === void 0 ? void 0 : onSuccess.back)
|
|
293
|
+
strategies.push('back');
|
|
294
|
+
const strategyAttr = strategies.length > 0
|
|
295
|
+
? `data-strategy='${JSON.stringify(strategies)}'`
|
|
296
|
+
: '';
|
|
297
|
+
return `<!-- @template name="${viewName}" -->
|
|
298
|
+
<div class="container mt-4">
|
|
299
|
+
<h1>Edit ${modelName}</h1>
|
|
300
|
+
|
|
301
|
+
<form method="POST" action="${basePath}/{{ id }}/edit" ${strategyAttr} data-entity-name="${modelName}" data-field-types='${fieldTypesJson}'>
|
|
302
|
+
${formFields}
|
|
303
|
+
|
|
304
|
+
<div class="d-flex gap-2">
|
|
305
|
+
<button type="submit" class="btn btn-primary">Update</button>
|
|
306
|
+
<a href="${basePath}/{{ id }}" class="btn btn-secondary">Cancel</a>
|
|
307
|
+
</div>
|
|
308
|
+
</form>
|
|
309
|
+
</div>`;
|
|
310
|
+
}
|
|
311
|
+
getInputType(fieldType) {
|
|
312
|
+
switch (fieldType) {
|
|
313
|
+
case 'string': return 'text';
|
|
314
|
+
case 'number':
|
|
315
|
+
case 'integer':
|
|
316
|
+
case 'float':
|
|
317
|
+
case 'decimal':
|
|
318
|
+
case 'money':
|
|
319
|
+
case 'id': return 'number';
|
|
320
|
+
case 'datetime':
|
|
321
|
+
case 'date': return 'datetime-local';
|
|
322
|
+
default: return 'text';
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
renderValueObjectField(name, label, voConfig, required, isEdit) {
|
|
326
|
+
const subFields = Object.entries(voConfig.fields);
|
|
327
|
+
const columns = subFields.map(([subName, subConfig]) => {
|
|
328
|
+
const fullName = `${name}.${subName}`;
|
|
329
|
+
const subLabel = this.capitalize(subName);
|
|
330
|
+
if (typeof subConfig === 'object' && 'values' in subConfig) {
|
|
331
|
+
const uniqueValues = [...new Set(subConfig.values)];
|
|
332
|
+
const options = uniqueValues.map(v => {
|
|
333
|
+
const sel = isEdit ? ` {{ ${name}.${subName} === '${v}' ? 'selected' : '' }}` : '';
|
|
334
|
+
return ` <option value="${v}"${sel}>${v}</option>`;
|
|
335
|
+
}).join('\n');
|
|
336
|
+
return ` <div class="col-auto">
|
|
337
|
+
<select class="form-select" id="${fullName}" name="${fullName}" ${required}>
|
|
338
|
+
<option value="">-- ${subLabel} --</option>
|
|
339
|
+
${options}
|
|
340
|
+
</select>
|
|
341
|
+
</div>`;
|
|
342
|
+
}
|
|
343
|
+
else {
|
|
344
|
+
const type = this.getInputType(subConfig.type);
|
|
345
|
+
const value = isEdit ? ` value="{{ ${name}.${subName} || '' }}"` : '';
|
|
346
|
+
return ` <div class="col">
|
|
347
|
+
<input type="${type}" class="form-control" id="${fullName}" name="${fullName}" placeholder="${subLabel}"${value} ${required}>
|
|
348
|
+
</div>`;
|
|
349
|
+
}
|
|
350
|
+
}).join('\n');
|
|
351
|
+
return ` <div class="mb-3">
|
|
352
|
+
<label class="form-label">${label}</label>
|
|
353
|
+
<div class="row g-2">
|
|
354
|
+
${columns}
|
|
355
|
+
</div>
|
|
356
|
+
</div>`;
|
|
357
|
+
}
|
|
358
|
+
renderFormField(name, config, enumValues = [], isEdit = false) {
|
|
359
|
+
const required = config.required ? 'required' : '';
|
|
360
|
+
const label = this.capitalize(name);
|
|
361
|
+
const fieldType = (config.type || 'string').toLowerCase();
|
|
362
|
+
const capitalizedType = this.capitalize(fieldType);
|
|
363
|
+
const voConfig = this.valueObjects[capitalizedType];
|
|
364
|
+
if (voConfig) {
|
|
365
|
+
return this.renderValueObjectField(name, label, voConfig, required, isEdit);
|
|
366
|
+
}
|
|
367
|
+
switch (fieldType) {
|
|
368
|
+
case 'boolean':
|
|
369
|
+
case 'bool': {
|
|
370
|
+
const checked = isEdit ? ` {{ ${name} ? 'checked' : '' }}` : '';
|
|
371
|
+
return ` <div class="mb-3">
|
|
372
|
+
<div class="form-check">
|
|
373
|
+
<input type="checkbox" class="form-check-input" id="${name}" name="${name}" value="true"${checked} ${required}>
|
|
374
|
+
<label for="${name}" class="form-check-label">${label}</label>
|
|
375
|
+
</div>
|
|
376
|
+
</div>`;
|
|
377
|
+
}
|
|
378
|
+
case 'enum': {
|
|
379
|
+
if (enumValues.length > 0) {
|
|
380
|
+
const options = enumValues.map(v => {
|
|
381
|
+
const sel = isEdit ? ` {{ ${name} === '${v}' ? 'selected' : '' }}` : '';
|
|
382
|
+
return ` <option value="${v}"${sel}>${this.capitalize(v)}</option>`;
|
|
383
|
+
}).join('\n');
|
|
384
|
+
return ` <div class="mb-3">
|
|
385
|
+
<label for="${name}" class="form-label">${label}</label>
|
|
386
|
+
<select class="form-select" id="${name}" name="${name}" ${required}>
|
|
387
|
+
<option value="">-- Select ${label} --</option>
|
|
388
|
+
${options}
|
|
389
|
+
</select>
|
|
390
|
+
</div>`;
|
|
391
|
+
}
|
|
392
|
+
const value = isEdit ? ` value="{{ ${name} || '' }}"` : '';
|
|
393
|
+
return ` <div class="mb-3">
|
|
394
|
+
<label for="${name}" class="form-label">${label}</label>
|
|
395
|
+
<input type="text" class="form-control" id="${name}" name="${name}"${value} ${required}>
|
|
396
|
+
</div>`;
|
|
397
|
+
}
|
|
398
|
+
default: {
|
|
399
|
+
const type = this.getInputType(config.type);
|
|
400
|
+
const value = isEdit ? ` value="{{ ${name} || '' }}"` : '';
|
|
401
|
+
return ` <div class="mb-3">
|
|
402
|
+
<label for="${name}" class="form-label">${label}</label>
|
|
403
|
+
<input type="${type}" class="form-control" id="${name}" name="${name}"${value} ${required}>
|
|
404
|
+
</div>`;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
getEnumValuesMap(config, resourceName) {
|
|
409
|
+
var _a;
|
|
410
|
+
const enumMap = {};
|
|
411
|
+
const aggregate = config.domain.aggregates[resourceName];
|
|
412
|
+
if (!aggregate)
|
|
413
|
+
return enumMap;
|
|
414
|
+
for (const [fieldName, fieldConfig] of Object.entries(aggregate.fields)) {
|
|
415
|
+
if (fieldConfig.type === 'enum' && fieldConfig.values) {
|
|
416
|
+
enumMap[fieldName] = fieldConfig.values;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
const modelUseCases = config.useCases[resourceName];
|
|
420
|
+
if (modelUseCases) {
|
|
421
|
+
for (const useCase of Object.values(modelUseCases)) {
|
|
422
|
+
if ((_a = useCase.input) === null || _a === void 0 ? void 0 : _a.filters) {
|
|
423
|
+
for (const [filterName, filterConfig] of Object.entries(useCase.input.filters)) {
|
|
424
|
+
if (filterConfig.enum && !enumMap[filterName]) {
|
|
425
|
+
enumMap[filterName] = filterConfig.enum;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
return enumMap;
|
|
432
|
+
}
|
|
433
|
+
capitalize(str) {
|
|
434
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
435
|
+
}
|
|
436
|
+
generateFromConfig(config) {
|
|
437
|
+
const result = {};
|
|
438
|
+
this.valueObjects = config.domain.valueObjects || {};
|
|
439
|
+
if (!config.web) {
|
|
440
|
+
return result;
|
|
441
|
+
}
|
|
442
|
+
Object.entries(config.web).forEach(([resourceName, resourceConfig]) => {
|
|
443
|
+
const aggregate = config.domain.aggregates[resourceName];
|
|
444
|
+
if (!aggregate) {
|
|
445
|
+
console.warn(`Warning: No aggregate found for resource ${resourceName}`);
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
const fields = Object.entries(aggregate.fields);
|
|
449
|
+
const enumValuesMap = this.getEnumValuesMap(config, resourceName);
|
|
450
|
+
const basePath = this.prefixToTemplatePath(resourceConfig.prefix);
|
|
451
|
+
const withChildChildren = (0, childEntityUtils_1.getChildrenOfParent)(config, resourceName);
|
|
452
|
+
resourceConfig.pages.forEach(page => {
|
|
453
|
+
var _a;
|
|
454
|
+
if (!page.view)
|
|
455
|
+
return;
|
|
456
|
+
// Determine template type from path and method
|
|
457
|
+
if (page.method === 'POST') {
|
|
458
|
+
// Skip POST endpoints - they don't need templates
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
const useCaseWithChild = (() => {
|
|
462
|
+
var _a, _b;
|
|
463
|
+
if (!page.useCase)
|
|
464
|
+
return false;
|
|
465
|
+
const [model, action] = page.useCase.split(':');
|
|
466
|
+
return ((_b = (_a = config.useCases[model]) === null || _a === void 0 ? void 0 : _a[action]) === null || _b === void 0 ? void 0 : _b.withChild) === true;
|
|
467
|
+
})();
|
|
468
|
+
const childrenForTemplate = useCaseWithChild && withChildChildren.length > 0 ? withChildChildren : undefined;
|
|
469
|
+
if (page.path === '/' && ((_a = page.useCase) === null || _a === void 0 ? void 0 : _a.endsWith(':list'))) {
|
|
470
|
+
result[page.view] = this.renderListTemplate(resourceName, page.view, fields, basePath, childrenForTemplate);
|
|
471
|
+
}
|
|
472
|
+
else if (page.path.includes(':id') && !page.path.includes('edit')) {
|
|
473
|
+
result[page.view] = this.renderDetailTemplate(resourceName, page.view, fields, basePath, childrenForTemplate);
|
|
474
|
+
}
|
|
475
|
+
else if (page.path.includes('/create')) {
|
|
476
|
+
// Find corresponding POST endpoint for onSuccess/onError
|
|
477
|
+
const postEndpoint = resourceConfig.pages.find(p => p.path === page.path && p.method === 'POST');
|
|
478
|
+
result[page.view] = this.renderCreateTemplate(resourceName, page.view, fields, basePath, postEndpoint === null || postEndpoint === void 0 ? void 0 : postEndpoint.onSuccess, postEndpoint === null || postEndpoint === void 0 ? void 0 : postEndpoint.onError, enumValuesMap);
|
|
479
|
+
}
|
|
480
|
+
else if (page.path.includes('edit')) {
|
|
481
|
+
// Find corresponding POST endpoint for onSuccess/onError
|
|
482
|
+
const postEndpoint = resourceConfig.pages.find(p => p.path === page.path && p.method === 'POST');
|
|
483
|
+
result[page.view] = this.renderEditTemplate(resourceName, page.view, fields, basePath, postEndpoint === null || postEndpoint === void 0 ? void 0 : postEndpoint.onSuccess, postEndpoint === null || postEndpoint === void 0 ? void 0 : postEndpoint.onError, enumValuesMap);
|
|
484
|
+
}
|
|
485
|
+
});
|
|
486
|
+
});
|
|
487
|
+
return result;
|
|
488
|
+
}
|
|
489
|
+
generateFromYamlFile(yamlFilePath) {
|
|
490
|
+
const yamlContent = fs.readFileSync(yamlFilePath, 'utf8');
|
|
491
|
+
const config = (0, yaml_1.parse)(yamlContent);
|
|
492
|
+
if (!(0, configTypes_1.isNewModuleConfig)(config)) {
|
|
493
|
+
throw new Error('Configuration does not match new module format. Expected domain/useCases/web structure.');
|
|
494
|
+
}
|
|
495
|
+
return this.generateFromConfig(config);
|
|
496
|
+
}
|
|
497
|
+
async generateAndSaveFiles(yamlFilePath, moduleDir, opts) {
|
|
498
|
+
const templatesByName = this.generateFromYamlFile(yamlFilePath);
|
|
499
|
+
const viewsDir = path.join(moduleDir, 'views');
|
|
500
|
+
fs.mkdirSync(viewsDir, { recursive: true });
|
|
501
|
+
for (const [name, content] of Object.entries(templatesByName)) {
|
|
502
|
+
const filePath = path.join(viewsDir, `${name}.html`);
|
|
503
|
+
// eslint-disable-next-line no-await-in-loop
|
|
504
|
+
await (0, generationRegistry_1.writeGeneratedFile)(filePath, content, { force: !!(opts === null || opts === void 0 ? void 0 : opts.force), skipOnConflict: !!(opts === null || opts === void 0 ? void 0 : opts.skipOnConflict) });
|
|
505
|
+
}
|
|
506
|
+
// eslint-disable-next-line no-console
|
|
507
|
+
console.log('\n' + colors_1.colors.green('Template files generated successfully!') + '\n');
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
exports.NewTemplateGenerator = NewTemplateGenerator;
|
|
@@ -1,57 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
name: string;
|
|
3
|
-
type: string;
|
|
4
|
-
required?: boolean;
|
|
5
|
-
unique?: boolean;
|
|
6
|
-
auto?: boolean;
|
|
7
|
-
displayFields?: string[];
|
|
8
|
-
}
|
|
9
|
-
interface ActionConfig {
|
|
10
|
-
handlers: string[];
|
|
11
|
-
}
|
|
12
|
-
interface PermissionConfig {
|
|
13
|
-
role: string;
|
|
14
|
-
actions: string[];
|
|
15
|
-
}
|
|
16
|
-
interface ModelConfig {
|
|
17
|
-
name: string;
|
|
18
|
-
fields: FieldConfig[];
|
|
19
|
-
}
|
|
20
|
-
type ModuleConfig = {
|
|
21
|
-
models?: ModelConfig[];
|
|
22
|
-
actions?: Record<string, ActionConfig>;
|
|
23
|
-
permissions?: PermissionConfig[];
|
|
24
|
-
};
|
|
1
|
+
import { ModuleConfig } from '../types/configTypes';
|
|
25
2
|
export declare class ServiceGenerator {
|
|
26
|
-
private
|
|
27
|
-
private
|
|
28
|
-
private
|
|
29
|
-
private
|
|
30
|
-
private
|
|
31
|
-
private
|
|
32
|
-
private
|
|
33
|
-
private
|
|
34
|
-
private
|
|
35
|
-
private
|
|
36
|
-
private
|
|
37
|
-
private
|
|
38
|
-
private
|
|
39
|
-
|
|
40
|
-
private generateConstructorArgs;
|
|
41
|
-
private generateRelationshipLoading;
|
|
42
|
-
private generateUpdateSetterCalls;
|
|
43
|
-
private replaceTemplateVars;
|
|
44
|
-
private getServiceMethodName;
|
|
45
|
-
private generateHandlerMethod;
|
|
46
|
-
generateServiceForModel(model: ModelConfig, moduleName: string, moduleConfig: ModuleConfig, hasGlobalPermissions: boolean): string;
|
|
47
|
-
generateService(moduleName: string, moduleConfig: ModuleConfig, hasGlobalPermissions: boolean): string;
|
|
48
|
-
private generateForeignStoreImports;
|
|
49
|
-
private generateForeignStoreConstructorParams;
|
|
50
|
-
private generateCustomImports;
|
|
3
|
+
private availableAggregates;
|
|
4
|
+
private mapType;
|
|
5
|
+
private generateListHandler;
|
|
6
|
+
private generateGetHandler;
|
|
7
|
+
private generateCreateHandler;
|
|
8
|
+
private generateUpdateHandler;
|
|
9
|
+
private generateDeleteHandler;
|
|
10
|
+
private generateDefaultHandlerMethod;
|
|
11
|
+
private generateCustomHandlerMethod;
|
|
12
|
+
private collectHandlers;
|
|
13
|
+
private generateListByParentMethod;
|
|
14
|
+
private generateGetResourceOwnerMethod;
|
|
15
|
+
private generateService;
|
|
16
|
+
generateFromConfig(config: ModuleConfig): Record<string, string>;
|
|
51
17
|
generateFromYamlFile(yamlFilePath: string): Record<string, string>;
|
|
52
|
-
generateAndSaveFiles(yamlFilePath
|
|
18
|
+
generateAndSaveFiles(yamlFilePath: string, moduleDir: string, opts?: {
|
|
53
19
|
force?: boolean;
|
|
54
20
|
skipOnConflict?: boolean;
|
|
55
21
|
}): Promise<void>;
|
|
56
22
|
}
|
|
57
|
-
export {};
|