@currentjs/gen 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/LICENSE +56 -0
  3. package/README.md +686 -0
  4. package/dist/cli.d.ts +2 -0
  5. package/dist/cli.js +143 -0
  6. package/dist/commands/commit.d.ts +1 -0
  7. package/dist/commands/commit.js +153 -0
  8. package/dist/commands/createApp.d.ts +1 -0
  9. package/dist/commands/createApp.js +64 -0
  10. package/dist/commands/createModule.d.ts +1 -0
  11. package/dist/commands/createModule.js +121 -0
  12. package/dist/commands/diff.d.ts +1 -0
  13. package/dist/commands/diff.js +164 -0
  14. package/dist/commands/generateAll.d.ts +4 -0
  15. package/dist/commands/generateAll.js +305 -0
  16. package/dist/commands/infer.d.ts +1 -0
  17. package/dist/commands/infer.js +179 -0
  18. package/dist/generators/controllerGenerator.d.ts +20 -0
  19. package/dist/generators/controllerGenerator.js +280 -0
  20. package/dist/generators/domainModelGenerator.d.ts +33 -0
  21. package/dist/generators/domainModelGenerator.js +175 -0
  22. package/dist/generators/serviceGenerator.d.ts +39 -0
  23. package/dist/generators/serviceGenerator.js +379 -0
  24. package/dist/generators/storeGenerator.d.ts +31 -0
  25. package/dist/generators/storeGenerator.js +191 -0
  26. package/dist/generators/templateGenerator.d.ts +11 -0
  27. package/dist/generators/templateGenerator.js +143 -0
  28. package/dist/generators/templates/appTemplates.d.ts +27 -0
  29. package/dist/generators/templates/appTemplates.js +1621 -0
  30. package/dist/generators/templates/controllerTemplates.d.ts +43 -0
  31. package/dist/generators/templates/controllerTemplates.js +82 -0
  32. package/dist/generators/templates/index.d.ts +5 -0
  33. package/dist/generators/templates/index.js +21 -0
  34. package/dist/generators/templates/serviceTemplates.d.ts +15 -0
  35. package/dist/generators/templates/serviceTemplates.js +54 -0
  36. package/dist/generators/templates/storeTemplates.d.ts +9 -0
  37. package/dist/generators/templates/storeTemplates.js +260 -0
  38. package/dist/generators/templates/validationTemplates.d.ts +25 -0
  39. package/dist/generators/templates/validationTemplates.js +66 -0
  40. package/dist/generators/templates/viewTemplates.d.ts +16 -0
  41. package/dist/generators/templates/viewTemplates.js +359 -0
  42. package/dist/generators/validationGenerator.d.ts +24 -0
  43. package/dist/generators/validationGenerator.js +199 -0
  44. package/dist/utils/cliUtils.d.ts +6 -0
  45. package/dist/utils/cliUtils.js +71 -0
  46. package/dist/utils/colors.d.ts +26 -0
  47. package/dist/utils/colors.js +80 -0
  48. package/dist/utils/commitUtils.d.ts +46 -0
  49. package/dist/utils/commitUtils.js +377 -0
  50. package/dist/utils/constants.d.ts +52 -0
  51. package/dist/utils/constants.js +64 -0
  52. package/dist/utils/generationRegistry.d.ts +25 -0
  53. package/dist/utils/generationRegistry.js +192 -0
  54. package/howto.md +556 -0
  55. package/package.json +44 -0
@@ -0,0 +1,359 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toFileNameFromTemplateName = toFileNameFromTemplateName;
4
+ exports.renderListTemplate = renderListTemplate;
5
+ exports.renderDetailTemplate = renderDetailTemplate;
6
+ exports.renderCreateTemplate = renderCreateTemplate;
7
+ exports.renderUpdateTemplate = renderUpdateTemplate;
8
+ exports.renderDeleteTemplate = renderDeleteTemplate;
9
+ exports.renderLayoutTemplate = renderLayoutTemplate;
10
+ function toFileNameFromTemplateName(name) {
11
+ const last = name.split('/').pop() || 'template';
12
+ let base = last
13
+ .replace(/\.tpl\.html$/i, '')
14
+ .replace(/\.(html|htm|tpl)$/i, '');
15
+ base = base.toLowerCase().replace(/[^a-z0-9._-]+/g, '-');
16
+ return `${base}.html`;
17
+ }
18
+ function generateFormInput(field) {
19
+ const requiredAttr = field.required ? ' required' : '';
20
+ const fieldId = field.name;
21
+ const fieldName = field.name.charAt(0).toUpperCase() + field.name.slice(1).replace(/_/g, ' ');
22
+ // Skip auto fields (like auto-incrementing IDs)
23
+ if (field.auto) {
24
+ return '';
25
+ }
26
+ switch (field.type.toLowerCase()) {
27
+ case 'number':
28
+ case 'int':
29
+ case 'integer':
30
+ case 'float':
31
+ case 'decimal':
32
+ return ` <div class="mb-3">
33
+ <label for="${fieldId}" class="form-label">${fieldName}</label>
34
+ <input id="${fieldId}" name="${field.name}" type="number" class="form-control"${requiredAttr} />
35
+ </div>`;
36
+ case 'boolean':
37
+ case 'bool':
38
+ return ` <div class="mb-3">
39
+ <label class="form-label">${fieldName}</label>
40
+ <div class="form-check">
41
+ <input id="${fieldId}_true" name="${field.name}" type="radio" value="true" class="form-check-input"${requiredAttr} />
42
+ <label for="${fieldId}_true" class="form-check-label">Yes</label>
43
+ </div>
44
+ <div class="form-check">
45
+ <input id="${fieldId}_false" name="${field.name}" type="radio" value="false" class="form-check-input" />
46
+ <label for="${fieldId}_false" class="form-check-label">No</label>
47
+ </div>
48
+ </div>`;
49
+ case 'enum':
50
+ if (field.enum && field.enum.length > 0) {
51
+ const options = field.enum.map(opt => ` <option value="${opt}">${opt.charAt(0).toUpperCase() + opt.slice(1)}</option>`).join('\n');
52
+ return ` <div class="mb-3">
53
+ <label for="${fieldId}" class="form-label">${fieldName}</label>
54
+ <select id="${fieldId}" name="${field.name}" class="form-select"${requiredAttr}>
55
+ <option value="">-- Select ${fieldName} --</option>
56
+ ${options}
57
+ </select>
58
+ </div>`;
59
+ }
60
+ // Fallback to text if no enum values
61
+ return ` <div class="mb-3">
62
+ <label for="${fieldId}" class="form-label">${fieldName}</label>
63
+ <input id="${fieldId}" name="${field.name}" type="text" class="form-control"${requiredAttr} />
64
+ </div>`;
65
+ default:
66
+ // Text fields (string, text, etc.)
67
+ return ` <div class="mb-3">
68
+ <label for="${fieldId}" class="form-label">${fieldName}</label>
69
+ <input id="${fieldId}" name="${field.name}" type="text" class="form-control"${requiredAttr} />
70
+ </div>`;
71
+ }
72
+ }
73
+ function generateUpdateFormInput(field) {
74
+ const requiredAttr = field.required ? ' required' : '';
75
+ const fieldId = field.name;
76
+ const fieldName = field.name.charAt(0).toUpperCase() + field.name.slice(1).replace(/_/g, ' ');
77
+ const fieldValue = `{{ $root.${field.name} }}`;
78
+ // Skip auto fields (like auto-incrementing IDs)
79
+ if (field.auto) {
80
+ return '';
81
+ }
82
+ switch (field.type.toLowerCase()) {
83
+ case 'number':
84
+ case 'int':
85
+ case 'integer':
86
+ case 'float':
87
+ case 'decimal':
88
+ return ` <div class="mb-3">
89
+ <label for="${fieldId}" class="form-label">${fieldName}</label>
90
+ <input id="${fieldId}" name="${field.name}" type="number" class="form-control" value="${fieldValue}"${requiredAttr} />
91
+ </div>`;
92
+ case 'boolean':
93
+ case 'bool':
94
+ return ` <div class="mb-3">
95
+ <label class="form-label">${fieldName}</label>
96
+ <div class="form-check">
97
+ <input id="${fieldId}_true" name="${field.name}" type="radio" value="true" class="form-check-input" {{ $root.${field.name} ? 'checked' : '' }}${requiredAttr} />
98
+ <label for="${fieldId}_true" class="form-check-label">Yes</label>
99
+ </div>
100
+ <div class="form-check">
101
+ <input id="${fieldId}_false" name="${field.name}" type="radio" value="false" class="form-check-input" {{ $root.${field.name} ? '' : 'checked' }} />
102
+ <label for="${fieldId}_false" class="form-check-label">No</label>
103
+ </div>
104
+ </div>`;
105
+ case 'enum':
106
+ if (field.enum && field.enum.length > 0) {
107
+ const options = field.enum.map(opt => ` <option value="${opt}" {{ $root.${field.name} === '${opt}' ? 'selected' : '' }}>${opt.charAt(0).toUpperCase() + opt.slice(1)}</option>`).join('\n');
108
+ return ` <div class="mb-3">
109
+ <label for="${fieldId}" class="form-label">${fieldName}</label>
110
+ <select id="${fieldId}" name="${field.name}" class="form-select"${requiredAttr}>
111
+ <option value="">-- Select ${fieldName} --</option>
112
+ ${options}
113
+ </select>
114
+ </div>`;
115
+ }
116
+ // Fallback to text if no enum values
117
+ return ` <div class="mb-3">
118
+ <label for="${fieldId}" class="form-label">${fieldName}</label>
119
+ <input id="${fieldId}" name="${field.name}" type="text" class="form-control" value="${fieldValue}"${requiredAttr} />
120
+ </div>`;
121
+ default:
122
+ // Text fields (string, text, etc.)
123
+ return ` <div class="mb-3">
124
+ <label for="${fieldId}" class="form-label">${fieldName}</label>
125
+ <input id="${fieldId}" name="${field.name}" type="text" class="form-control" value="${fieldValue}"${requiredAttr} />
126
+ </div>`;
127
+ }
128
+ }
129
+ function renderListTemplate(entityName, templateName, basePath, fields, apiBase) {
130
+ const safeFields = fields.length > 0 ? fields.map(f => f.name) : ['id', 'name'];
131
+ const headers = ['ID', ...safeFields.filter(f => f !== 'id').map(f => f.charAt(0).toUpperCase() + f.slice(1).replace(/_/g, ' '))].map(f => `<th scope="col">${f}</th>`).join('');
132
+ const cells = [
133
+ '<td>{{ row.id }}</td>',
134
+ ...safeFields.filter(f => f !== 'id').map(f => `<td>{{ row.${f} }}</td>`)
135
+ ].join('\n ');
136
+ const deleteAction = apiBase ? `${apiBase}/{{ row.id }}` : `${basePath}/api/{{ row.id }}`;
137
+ return `<!-- @template name="${templateName}" -->
138
+ <div class="container-fluid py-4">
139
+ <div class="row">
140
+ <div class="col">
141
+ <div class="d-flex justify-content-between align-items-center mb-4">
142
+ <h1 class="h2">${entityName} List</h1>
143
+ <a href="${basePath}/create" class="btn btn-primary">
144
+ <i class="bi bi-plus-circle me-1"></i>Create New ${entityName}
145
+ </a>
146
+ </div>
147
+
148
+ <div class="card">
149
+ <div class="card-body">
150
+ <div class="table-responsive">
151
+ <table class="table table-hover">
152
+ <thead class="table-light">
153
+ <tr>
154
+ ${headers}
155
+ <th scope="col" class="text-end">Actions</th>
156
+ </tr>
157
+ </thead>
158
+ <tbody x-for="$root" x-row="row">
159
+ <tr id="row-{{ row.id }}">
160
+ ${cells}
161
+ <td class="text-end">
162
+ <div class="btn-group" role="group">
163
+ <a href="${basePath}/{{ row.id }}" class="btn btn-sm btn-outline-primary">View</a>
164
+ <a href="${basePath}/{{ row.id }}/edit" class="btn btn-sm btn-outline-secondary">Edit</a>
165
+ <form style="display: inline;"
166
+ data-action="${deleteAction}"
167
+ data-method="DELETE"
168
+ data-strategy='["toast", "remove"]'
169
+ data-entity-name="${entityName}"
170
+ data-confirm-message="Are you sure you want to delete this ${entityName.toLowerCase()}? This action cannot be undone."
171
+ data-target-selector="#row-{{ row.id }}">
172
+ <button type="submit" class="btn btn-sm btn-outline-danger">
173
+ <i class="bi bi-trash me-1"></i>Delete
174
+ </button>
175
+ </form>
176
+ </div>
177
+ </td>
178
+ </tr>
179
+ </tbody>
180
+ </table>
181
+ </div>
182
+ </div>
183
+ </div>
184
+ </div>
185
+ </div>
186
+ </div>
187
+ `;
188
+ }
189
+ function renderDetailTemplate(entityName, templateName, fields) {
190
+ const safeFields = fields.length > 0 ? fields.map(f => f.name) : ['id', 'name'];
191
+ const rows = safeFields.map(f => {
192
+ const fieldName = f.charAt(0).toUpperCase() + f.slice(1).replace(/_/g, ' ');
193
+ return ` <tr><th scope="row" class="w-25">${fieldName}</th><td>{{ $root.${f} }}</td></tr>`;
194
+ }).join('\n');
195
+ return `<!-- @template name="${templateName}" -->
196
+ <div class="container-fluid py-4">
197
+ <div class="row justify-content-center">
198
+ <div class="col-lg-8">
199
+ <div class="d-flex justify-content-between align-items-center mb-4">
200
+ <h1 class="h2">${entityName} Details</h1>
201
+ <div class="btn-group" role="group">
202
+ <a href="{{ basePath }}/{{ $root.id }}/edit" class="btn btn-outline-primary">Edit</a>
203
+ <button onclick="window.history.back()" class="btn btn-outline-secondary">Back</button>
204
+ </div>
205
+ </div>
206
+
207
+ <div class="card">
208
+ <div class="card-body">
209
+ <div class="table-responsive">
210
+ <table class="table table-borderless">
211
+ <tbody>
212
+ ${rows}
213
+ </tbody>
214
+ </table>
215
+ </div>
216
+ </div>
217
+ </div>
218
+ </div>
219
+ </div>
220
+ </div>
221
+ `;
222
+ }
223
+ function renderCreateTemplate(entityName, templateName, apiBase, fields, strategy = ['back', 'toast'], basePath) {
224
+ const safeFields = fields.filter(f => f.name !== 'id' && !f.auto);
225
+ const inputs = safeFields.map(f => generateFormInput(f)).filter(input => input.trim() !== '').join('\n');
226
+ const tplId = templateName.replace(/[^a-z0-9._-]+/gi, '-').toLowerCase();
227
+ const targetId = `${tplId}-result`;
228
+ const messageId = `${tplId}-message`;
229
+ const modalId = `${tplId}-modal`;
230
+ const errorsId = `${tplId}-errors`;
231
+ const fieldTypes = JSON.stringify(safeFields.reduce((acc, f) => {
232
+ acc[f.name] = f.type;
233
+ return acc;
234
+ }, {}));
235
+ const inlineErrorsBlock = strategy.includes('inline') ? `\n <div id="${errorsId}" class="alert alert-danger d-none"></div>` : '';
236
+ const messageBlock = strategy.includes('message') ? `\n <div id="${messageId}" class="alert d-none" role="status"></div>` : '';
237
+ const modalBlock = strategy.includes('modal')
238
+ ? `\n<div class="modal fade" id="${modalId}" tabindex="-1"><div class="modal-dialog"><div class="modal-content"><div class="modal-body"></div></div></div></div>`
239
+ : '';
240
+ return `<!-- @template name="${templateName}" -->
241
+ <div class="container-fluid py-4">
242
+ <div class="row justify-content-center">
243
+ <div class="col-lg-6">
244
+ <div class="d-flex justify-content-between align-items-center mb-4">
245
+ <h1 class="h2">Create ${entityName}</h1>
246
+ <button onclick="window.history.back()" class="btn btn-outline-secondary">Cancel</button>
247
+ </div>
248
+
249
+ <div class="card">
250
+ <div class="card-body">
251
+ <form data-template="${tplId}" data-action="${apiBase}" data-method="POST" data-strategy='${JSON.stringify(strategy)}' data-base-path="${basePath || ''}" data-entity-name="${entityName}" data-message-id="${messageId}" data-modal-id="${modalId}" data-field-types='${fieldTypes}'>
252
+ ${inputs}
253
+ <div class="d-flex gap-2 justify-content-end">
254
+ <button type="button" onclick="window.history.back()" class="btn btn-outline-secondary">Cancel</button>
255
+ <button type="submit" class="btn btn-primary">Create ${entityName}</button>
256
+ </div>
257
+ </form>
258
+ <div id="${targetId}"></div>${inlineErrorsBlock}${messageBlock}
259
+ </div>
260
+ </div>
261
+ </div>
262
+ </div>
263
+ </div>${modalBlock}
264
+ `;
265
+ }
266
+ function renderUpdateTemplate(entityName, templateName, apiBase, fields, strategy = ['back', 'toast'], basePath) {
267
+ const safeFields = fields.filter(f => f.name !== 'id' && !f.auto);
268
+ const inputs = safeFields.map(f => generateUpdateFormInput(f)).filter(input => input.trim() !== '').join('\n');
269
+ const tplId = templateName.replace(/[^a-z0-9._-]+/gi, '-').toLowerCase();
270
+ const targetId = `${tplId}-result`;
271
+ const messageId = `${tplId}-message`;
272
+ const modalId = `${tplId}-modal`;
273
+ const errorsId = `${tplId}-errors`;
274
+ const fieldTypes = JSON.stringify(safeFields.reduce((acc, f) => {
275
+ acc[f.name] = f.type;
276
+ return acc;
277
+ }, {}));
278
+ const inlineErrorsBlock = strategy.includes('inline') ? `\n <div id="${errorsId}" class="alert alert-danger d-none"></div>` : '';
279
+ const messageBlock = strategy.includes('message') ? `\n <div id="${messageId}" class="alert d-none" role="status"></div>` : '';
280
+ const modalBlock = strategy.includes('modal')
281
+ ? `\n<div class="modal fade" id="${modalId}" tabindex="-1"><div class="modal-dialog"><div class="modal-content"><div class="modal-body"></div></div></div></div>`
282
+ : '';
283
+ return `<!-- @template name="${templateName}" -->
284
+ <div class="container-fluid py-4">
285
+ <div class="row justify-content-center">
286
+ <div class="col-lg-6">
287
+ <div class="d-flex justify-content-between align-items-center mb-4">
288
+ <h1 class="h2">Edit ${entityName}</h1>
289
+ <button onclick="window.history.back()" class="btn btn-outline-secondary">Cancel</button>
290
+ </div>
291
+
292
+ <div class="card">
293
+ <div class="card-body">
294
+ <form data-template="${tplId}" data-action="${apiBase}/{{ $root.id }}" data-method="PUT" data-strategy='${JSON.stringify(strategy)}' data-base-path="${basePath || ''}" data-entity-name="${entityName}" data-message-id="${messageId}" data-modal-id="${modalId}" data-field-types='${fieldTypes}'>
295
+ ${inputs}
296
+ <div class="d-flex gap-2 justify-content-end">
297
+ <button type="button" onclick="window.history.back()" class="btn btn-outline-secondary">Cancel</button>
298
+ <button type="submit" class="btn btn-primary">Save Changes</button>
299
+ </div>
300
+ </form>
301
+ <div id="${targetId}"></div>${inlineErrorsBlock}${messageBlock}
302
+ </div>
303
+ </div>
304
+ </div>
305
+ </div>
306
+ </div>${modalBlock}
307
+ `;
308
+ }
309
+ function renderDeleteTemplate(entityName, templateName, apiBase, strategy = ['back', 'toast'], basePath) {
310
+ const tplId = templateName.replace(/[^a-z0-9._-]+/gi, '-').toLowerCase();
311
+ const messageId = `${tplId}-message`;
312
+ const modalId = `${tplId}-modal`;
313
+ const messageBlock = strategy.includes('message') ? `\n <div id="${messageId}" class="alert d-none" role="status"></div>` : '';
314
+ const modalBlock = strategy.includes('modal')
315
+ ? `\n<div class="modal fade" id="${modalId}" tabindex="-1"><div class="modal-dialog"><div class="modal-content"><div class="modal-body"></div></div></div></div>`
316
+ : '';
317
+ return `<!-- @template name="${templateName}" -->
318
+ <div class="container-fluid py-4">
319
+ <div class="row justify-content-center">
320
+ <div class="col-lg-6">
321
+ <div class="d-flex justify-content-between align-items-center mb-4">
322
+ <h1 class="h2">Delete ${entityName}</h1>
323
+ <button onclick="window.history.back()" class="btn btn-outline-secondary">Cancel</button>
324
+ </div>
325
+
326
+ <div class="card border-danger">
327
+ <div class="card-body">
328
+ <div class="alert alert-warning" role="alert">
329
+ <h5 class="alert-heading">⚠️ Confirm Deletion</h5>
330
+ <p class="mb-0">Are you sure you want to delete this ${entityName.toLowerCase()}? This action cannot be undone.</p>
331
+ </div>
332
+
333
+ <form data-template="${tplId}" data-action="${apiBase}/{{ $root.id }}" data-method="DELETE" data-strategy='${JSON.stringify(strategy)}' data-base-path="${basePath || ''}" data-entity-name="${entityName}" data-message-id="${messageId}" data-modal-id="${modalId}">
334
+ <div class="d-flex gap-2 justify-content-end">
335
+ <button type="button" onclick="window.history.back()" class="btn btn-outline-secondary">Cancel</button>
336
+ <button type="submit" class="btn btn-danger">Delete ${entityName}</button>
337
+ </div>
338
+ </form>${messageBlock}
339
+ </div>
340
+ </div>
341
+ </div>
342
+ </div>
343
+ </div>${modalBlock}
344
+ `;
345
+ }
346
+ function renderLayoutTemplate(layoutName) {
347
+ return `<!-- @template name="${layoutName}" -->
348
+ <!doctype html>
349
+ <html>
350
+ <head>
351
+ <meta charset="utf-8" />
352
+ <title>${layoutName}</title>
353
+ </head>
354
+ <body>
355
+ {{ content }}
356
+ </body>
357
+ </html>
358
+ `;
359
+ }
@@ -0,0 +1,24 @@
1
+ interface FieldConfig {
2
+ name: string;
3
+ type: string;
4
+ required?: boolean;
5
+ auto?: boolean;
6
+ unique?: boolean;
7
+ }
8
+ export declare class ValidationGenerator {
9
+ private replaceTemplateVars;
10
+ private getTypeScriptType;
11
+ private generateInterfaceField;
12
+ private generateValidationLogic;
13
+ private generateDtoField;
14
+ private generateDtoInterface;
15
+ private generateInputInterface;
16
+ private generateValidationFunction;
17
+ generateValidation(entityName: string, fields: FieldConfig[]): string;
18
+ generateFromYamlFile(yamlFilePath: string): Record<string, string>;
19
+ generateAndSaveFiles(yamlFilePath?: string, outputDir?: string, opts?: {
20
+ force?: boolean;
21
+ skipOnConflict?: boolean;
22
+ }): Promise<void>;
23
+ }
24
+ export {};
@@ -0,0 +1,199 @@
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.ValidationGenerator = void 0;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const yaml_1 = require("yaml");
40
+ const validationTemplates_1 = require("./templates/validationTemplates");
41
+ const generationRegistry_1 = require("../utils/generationRegistry");
42
+ const colors_1 = require("../utils/colors");
43
+ const constants_1 = require("../utils/constants");
44
+ class ValidationGenerator {
45
+ replaceTemplateVars(template, variables) {
46
+ let result = template;
47
+ Object.entries(variables).forEach(([key, value]) => {
48
+ const regex = new RegExp(`{{${key}}}`, 'g');
49
+ result = result.replace(regex, value);
50
+ });
51
+ return result;
52
+ }
53
+ getTypeScriptType(yamlType) {
54
+ return validationTemplates_1.typeMapping[yamlType] || 'any';
55
+ }
56
+ generateInterfaceField(field, isCreate) {
57
+ const tsType = this.getTypeScriptType(field.type);
58
+ if (isCreate) {
59
+ if (field.auto || field.name === 'id') {
60
+ return '';
61
+ }
62
+ const optional = !field.required ? '?' : '';
63
+ return ` ${field.name}${optional}: ${tsType};`;
64
+ }
65
+ else {
66
+ if (field.name === 'id' || field.auto) {
67
+ return '';
68
+ }
69
+ return ` ${field.name}?: ${tsType};`;
70
+ }
71
+ }
72
+ generateValidationLogic(field, isCreate) {
73
+ const fieldName = field.name;
74
+ if (field.auto || field.name === 'id') {
75
+ return '';
76
+ }
77
+ let template = '';
78
+ const isRequired = isCreate && field.required;
79
+ switch (field.type) {
80
+ case 'string':
81
+ template = isRequired ? validationTemplates_1.validationTemplates.requiredStringValidation : validationTemplates_1.validationTemplates.optionalStringValidation;
82
+ break;
83
+ case 'number':
84
+ template = isRequired ? validationTemplates_1.validationTemplates.requiredNumberValidation : validationTemplates_1.validationTemplates.optionalNumberValidation;
85
+ break;
86
+ case 'boolean':
87
+ template = isRequired ? validationTemplates_1.validationTemplates.requiredBooleanValidation : validationTemplates_1.validationTemplates.optionalBooleanValidation;
88
+ break;
89
+ case 'datetime':
90
+ template = isRequired ? validationTemplates_1.validationTemplates.requiredDateValidation : validationTemplates_1.validationTemplates.optionalDateValidation;
91
+ break;
92
+ default:
93
+ template = isRequired ? validationTemplates_1.validationTemplates.requiredComplexValidation : validationTemplates_1.validationTemplates.optionalComplexValidation;
94
+ break;
95
+ }
96
+ return this.replaceTemplateVars(template, { FIELD_NAME: fieldName });
97
+ }
98
+ generateDtoField(field, isCreate) {
99
+ const tsType = this.getTypeScriptType(field.type);
100
+ if (isCreate) {
101
+ if (field.auto || field.name === 'id') {
102
+ return '';
103
+ }
104
+ const optional = !field.required ? '?' : '';
105
+ return ` ${field.name}${optional}: ${tsType};`;
106
+ }
107
+ else {
108
+ if (field.name === 'id' || field.auto) {
109
+ return '';
110
+ }
111
+ return ` ${field.name}?: ${tsType};`;
112
+ }
113
+ }
114
+ generateDtoInterface(entityName, fields, isCreate) {
115
+ const dtoName = `${entityName}DTO`;
116
+ const dtoFields = fields
117
+ .map(field => this.generateDtoField(field, isCreate))
118
+ .filter(field => field !== '')
119
+ .join('\n');
120
+ return this.replaceTemplateVars(validationTemplates_1.validationTemplates.dtoInterface, {
121
+ DTO_NAME: dtoName,
122
+ DTO_FIELDS: dtoFields
123
+ });
124
+ }
125
+ generateInputInterface(entityName, fields, isCreate) {
126
+ const interfaceName = `${isCreate ? 'Create' : 'Update'}${entityName}Input`;
127
+ const interfaceFields = fields
128
+ .map(field => this.generateInterfaceField(field, isCreate))
129
+ .filter(field => field !== '')
130
+ .join('\n');
131
+ return this.replaceTemplateVars(validationTemplates_1.validationTemplates.inputInterface, {
132
+ INTERFACE_NAME: interfaceName,
133
+ INTERFACE_FIELDS: interfaceFields
134
+ });
135
+ }
136
+ generateValidationFunction(entityName, fields, isCreate) {
137
+ const functionName = `validate${isCreate ? 'Create' : 'Update'}${entityName}`;
138
+ const dtoParam = `${entityName.toLowerCase()}Data: ${entityName}DTO`;
139
+ const validationLogic = fields
140
+ .map(field => this.generateValidationLogic(field, isCreate))
141
+ .filter(logic => logic !== '')
142
+ .join('\n');
143
+ return this.replaceTemplateVars(validationTemplates_1.validationTemplates.validationFunction, {
144
+ FUNCTION_NAME: functionName,
145
+ VALIDATION_LOGIC: validationLogic
146
+ }).replace('(data: any)', `(${dtoParam})`).replace(/data\./g, `${entityName.toLowerCase()}Data.`);
147
+ }
148
+ generateValidation(entityName, fields) {
149
+ const dtoInterface = this.generateDtoInterface(entityName, fields, true);
150
+ const createValidation = this.generateValidationFunction(entityName, fields, true);
151
+ const updateValidation = this.generateValidationFunction(entityName, fields, false);
152
+ const validationFunctions = `${createValidation}\n\n${updateValidation}`;
153
+ return this.replaceTemplateVars(validationTemplates_1.validationTemplates.validationFileTemplate, {
154
+ ENTITY_NAME: entityName,
155
+ DTO_INTERFACES: dtoInterface,
156
+ VALIDATION_FUNCTIONS: validationFunctions
157
+ });
158
+ }
159
+ generateFromYamlFile(yamlFilePath) {
160
+ const yamlContent = fs.readFileSync(yamlFilePath, 'utf8');
161
+ const config = (0, yaml_1.parse)(yamlContent);
162
+ const validations = {};
163
+ if (config.modules) {
164
+ Object.values(config.modules).forEach(moduleConfig => {
165
+ if (moduleConfig.models && moduleConfig.models.length > 0) {
166
+ moduleConfig.models.forEach(model => {
167
+ const validationCode = this.generateValidation(model.name, model.fields);
168
+ validations[model.name] = validationCode;
169
+ });
170
+ }
171
+ });
172
+ }
173
+ else if (config.models) {
174
+ const module = config;
175
+ if (module.models) {
176
+ module.models.forEach(model => {
177
+ const validationCode = this.generateValidation(model.name, model.fields);
178
+ validations[model.name] = validationCode;
179
+ });
180
+ }
181
+ }
182
+ return validations;
183
+ }
184
+ async generateAndSaveFiles(yamlFilePath = constants_1.COMMON_FILES.APP_YAML, outputDir = 'application', opts) {
185
+ const validations = this.generateFromYamlFile(yamlFilePath);
186
+ const validationDir = path.join(outputDir, 'validation');
187
+ fs.mkdirSync(validationDir, { recursive: true });
188
+ for (const [entityName, validationCode] of Object.entries(validations)) {
189
+ const fileName = `${entityName}Validation.ts`;
190
+ const filePath = path.join(validationDir, fileName);
191
+ // Sequential to avoid multiple prompts overlapping
192
+ // eslint-disable-next-line no-await-in-loop
193
+ await (0, generationRegistry_1.writeGeneratedFile)(filePath, validationCode, { force: !!(opts === null || opts === void 0 ? void 0 : opts.force), skipOnConflict: !!(opts === null || opts === void 0 ? void 0 : opts.skipOnConflict) });
194
+ }
195
+ // eslint-disable-next-line no-console
196
+ console.log('\n' + colors_1.colors.green('Validation files generated successfully!') + '\n');
197
+ }
198
+ }
199
+ exports.ValidationGenerator = ValidationGenerator;
@@ -0,0 +1,6 @@
1
+ export declare function resolveYamlPath(provided?: string): string;
2
+ export declare function resolveOutDir(provided?: string, defaultDir?: string): string;
3
+ export declare function ensureDir(dirPath: string): void;
4
+ export declare function writeFileIfMissing(targetPath: string, contents: string): void;
5
+ export declare function fileExists(targetPath: string): boolean;
6
+ export declare function toAbsolute(targetPath: string): string;
@@ -0,0 +1,71 @@
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.resolveYamlPath = resolveYamlPath;
37
+ exports.resolveOutDir = resolveOutDir;
38
+ exports.ensureDir = ensureDir;
39
+ exports.writeFileIfMissing = writeFileIfMissing;
40
+ exports.fileExists = fileExists;
41
+ exports.toAbsolute = toAbsolute;
42
+ const fs = __importStar(require("fs"));
43
+ const path = __importStar(require("path"));
44
+ const constants_1 = require("./constants");
45
+ function resolveYamlPath(provided) {
46
+ const candidate = provided !== null && provided !== void 0 ? provided : constants_1.COMMON_FILES.APP_YAML;
47
+ const abs = path.isAbsolute(candidate) ? candidate : path.resolve(process.cwd(), candidate);
48
+ if (!fs.existsSync(abs)) {
49
+ throw new Error(`YAML config not found at ${abs}. Run this command from your project root or pass --yaml <path>.`);
50
+ }
51
+ return abs;
52
+ }
53
+ function resolveOutDir(provided, defaultDir = 'src/generated') {
54
+ const dir = provided !== null && provided !== void 0 ? provided : defaultDir;
55
+ return path.isAbsolute(dir) ? dir : path.resolve(process.cwd(), dir);
56
+ }
57
+ function ensureDir(dirPath) {
58
+ fs.mkdirSync(dirPath, { recursive: true });
59
+ }
60
+ function writeFileIfMissing(targetPath, contents) {
61
+ if (fs.existsSync(targetPath))
62
+ return;
63
+ ensureDir(path.dirname(targetPath));
64
+ fs.writeFileSync(targetPath, contents);
65
+ }
66
+ function fileExists(targetPath) {
67
+ return fs.existsSync(targetPath);
68
+ }
69
+ function toAbsolute(targetPath) {
70
+ return path.isAbsolute(targetPath) ? targetPath : path.resolve(process.cwd(), targetPath);
71
+ }