@currentjs/gen 0.2.1 → 0.3.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.
Files changed (41) hide show
  1. package/CHANGELOG.md +120 -0
  2. package/README.md +256 -8
  3. package/dist/cli.js +26 -0
  4. package/dist/commands/commit.js +4 -0
  5. package/dist/commands/createApp.js +7 -0
  6. package/dist/commands/diff.js +4 -0
  7. package/dist/commands/generateAll.js +159 -29
  8. package/dist/commands/migrateCommit.d.ts +1 -0
  9. package/dist/commands/migrateCommit.js +201 -0
  10. package/dist/generators/controllerGenerator.d.ts +7 -0
  11. package/dist/generators/controllerGenerator.js +60 -29
  12. package/dist/generators/domainModelGenerator.d.ts +8 -0
  13. package/dist/generators/domainModelGenerator.js +71 -4
  14. package/dist/generators/serviceGenerator.d.ts +17 -1
  15. package/dist/generators/serviceGenerator.js +139 -12
  16. package/dist/generators/storeGenerator.d.ts +9 -0
  17. package/dist/generators/storeGenerator.js +148 -8
  18. package/dist/generators/templateGenerator.d.ts +19 -0
  19. package/dist/generators/templateGenerator.js +216 -11
  20. package/dist/generators/templates/appTemplates.d.ts +8 -7
  21. package/dist/generators/templates/appTemplates.js +11 -1572
  22. package/dist/generators/templates/data/appTsTemplate +39 -0
  23. package/dist/generators/templates/data/appYamlTemplate +4 -0
  24. package/dist/generators/templates/data/cursorRulesTemplate +671 -0
  25. package/dist/generators/templates/data/errorTemplate +28 -0
  26. package/dist/generators/templates/data/frontendScriptTemplate +739 -0
  27. package/dist/generators/templates/data/mainViewTemplate +16 -0
  28. package/dist/generators/templates/data/translationsTemplate +68 -0
  29. package/dist/generators/templates/data/tsConfigTemplate +19 -0
  30. package/dist/generators/templates/viewTemplates.d.ts +10 -1
  31. package/dist/generators/templates/viewTemplates.js +138 -6
  32. package/dist/generators/validationGenerator.d.ts +5 -0
  33. package/dist/generators/validationGenerator.js +51 -0
  34. package/dist/utils/cliUtils.d.ts +6 -0
  35. package/dist/utils/cliUtils.js +24 -0
  36. package/dist/utils/constants.d.ts +3 -0
  37. package/dist/utils/constants.js +5 -2
  38. package/dist/utils/migrationUtils.d.ts +49 -0
  39. package/dist/utils/migrationUtils.js +291 -0
  40. package/howto.md +158 -66
  41. package/package.json +3 -2
@@ -0,0 +1,16 @@
1
+ <!-- @template name="main_view" -->
2
+ <!doctype html>
3
+ <html lang="en">
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Your App</title>
8
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
9
+ <script src="/app.js"></script>
10
+ </head>
11
+ <body>
12
+ <div class="container-fluid">
13
+ <div id="main">{{ content }}</div>
14
+ </div>
15
+ </body>
16
+ </html>
@@ -0,0 +1,68 @@
1
+ {
2
+ "ru": {
3
+ "error": "ошибка",
4
+ "success": "успешно",
5
+ "Reloading page...": "Перезагрузка страницы...",
6
+ "Request failed. Please try again.": "Запрос не выполнен. Попробуйте еще раз.",
7
+ "This field is required": "Это поле обязательно",
8
+ "An error occurred": "Произошла ошибка",
9
+ "saved successfully": "успешно сохранено",
10
+ "updated successfully": "успешно обновлено",
11
+ "deleted successfully": "успешно удалено"
12
+ },
13
+ "pl": {
14
+ "error": "błąd",
15
+ "success": "sukces",
16
+ "Reloading page...": "Przeładowywanie strony...",
17
+ "Request failed. Please try again.": "Żądanie nie powiodło się. Spróbuj ponownie.",
18
+ "This field is required": "To pole jest wymagane",
19
+ "An error occurred": "Wystąpił błąd",
20
+ "saved successfully": "zapisano pomyślnie",
21
+ "updated successfully": "zaktualizowano pomyślnie",
22
+ "deleted successfully": "usunięto pomyślnie"
23
+ },
24
+ "es": {
25
+ "error": "error",
26
+ "success": "éxito",
27
+ "Reloading page...": "Recargando página...",
28
+ "Request failed. Please try again.": "La solicitud falló. Por favor, inténtelo de nuevo.",
29
+ "This field is required": "Este campo es obligatorio",
30
+ "An error occurred": "Ha ocurrido un error",
31
+ "saved successfully": "guardado exitosamente",
32
+ "updated successfully": "actualizado exitosamente",
33
+ "deleted successfully": "eliminado exitosamente"
34
+ },
35
+ "de": {
36
+ "error": "Fehler",
37
+ "success": "Erfolg",
38
+ "Reloading page...": "Seite wird neu geladen...",
39
+ "Request failed. Please try again.": "Anfrage fehlgeschlagen. Bitte versuchen Sie es erneut.",
40
+ "This field is required": "Dieses Feld ist erforderlich",
41
+ "An error occurred": "Ein Fehler ist aufgetreten",
42
+ "saved successfully": "erfolgreich gespeichert",
43
+ "updated successfully": "erfolgreich aktualisiert",
44
+ "deleted successfully": "erfolgreich gelöscht"
45
+ },
46
+ "pt": {
47
+ "error": "erro",
48
+ "success": "sucesso",
49
+ "Reloading page...": "Recarregando página...",
50
+ "Request failed. Please try again.": "A solicitação falhou. Por favor, tente novamente.",
51
+ "This field is required": "Este campo é obrigatório",
52
+ "An error occurred": "Ocorreu um erro",
53
+ "saved successfully": "salvo com sucesso",
54
+ "updated successfully": "atualizado com sucesso",
55
+ "deleted successfully": "excluído com sucesso"
56
+ },
57
+ "zh": {
58
+ "error": "错误",
59
+ "success": "成功",
60
+ "Reloading page...": "正在重新加载页面...",
61
+ "Request failed. Please try again.": "请求失败。请再试一次。",
62
+ "This field is required": "此字段为必填项",
63
+ "An error occurred": "发生错误",
64
+ "saved successfully": "保存成功",
65
+ "updated successfully": "更新成功",
66
+ "deleted successfully": "删除成功"
67
+ }
68
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "module": "es2020",
4
+ "target": "es2020",
5
+ "sourceMap": false,
6
+ "experimentalDecorators": true,
7
+ "emitDecoratorMetadata": true,
8
+ "baseUrl": "./src",
9
+ "outDir": "./build",
10
+ "types": ["node"],
11
+ "moduleResolution": "Node",
12
+ "declaration": true,
13
+ "declarationMap": false
14
+ },
15
+ "exclude": [
16
+ "node_modules"
17
+ ],
18
+ "type": "module"
19
+ }
@@ -5,12 +5,21 @@ type FieldConfig = {
5
5
  auto?: boolean;
6
6
  unique?: boolean;
7
7
  enum?: string[];
8
+ displayFields?: string[];
8
9
  };
10
+ type RelationshipContext = {
11
+ routePaths: Map<string, string>;
12
+ apiPaths: Map<string, string>;
13
+ };
14
+ declare function setAvailableModels(models: string[]): void;
15
+ declare function setRelationshipContext(context: RelationshipContext): void;
16
+ declare function isRelationshipField(field: FieldConfig): boolean;
17
+ declare function getForeignKeyFieldName(field: FieldConfig): string;
9
18
  export declare function toFileNameFromTemplateName(name: string): string;
19
+ export { setAvailableModels, setRelationshipContext, isRelationshipField, getForeignKeyFieldName };
10
20
  export declare function renderListTemplate(entityName: string, templateName: string, basePath: string, fields: FieldConfig[], apiBase?: string): string;
11
21
  export declare function renderDetailTemplate(entityName: string, templateName: string, fields: FieldConfig[]): string;
12
22
  export declare function renderCreateTemplate(entityName: string, templateName: string, apiBase: string, fields: FieldConfig[], strategy?: string[], basePath?: string): string;
13
23
  export declare function renderUpdateTemplate(entityName: string, templateName: string, apiBase: string, fields: FieldConfig[], strategy?: string[], basePath?: string): string;
14
24
  export declare function renderDeleteTemplate(entityName: string, templateName: string, apiBase: string, strategy?: string[], basePath?: string): string;
15
25
  export declare function renderLayoutTemplate(layoutName: string): string;
16
- export {};
@@ -1,12 +1,35 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.toFileNameFromTemplateName = toFileNameFromTemplateName;
4
+ exports.setAvailableModels = setAvailableModels;
5
+ exports.setRelationshipContext = setRelationshipContext;
6
+ exports.isRelationshipField = isRelationshipField;
7
+ exports.getForeignKeyFieldName = getForeignKeyFieldName;
4
8
  exports.renderListTemplate = renderListTemplate;
5
9
  exports.renderDetailTemplate = renderDetailTemplate;
6
10
  exports.renderCreateTemplate = renderCreateTemplate;
7
11
  exports.renderUpdateTemplate = renderUpdateTemplate;
8
12
  exports.renderDeleteTemplate = renderDeleteTemplate;
9
13
  exports.renderLayoutTemplate = renderLayoutTemplate;
14
+ // Helper to check if a field type is a known model (relationship)
15
+ let availableModels = new Set();
16
+ let relationshipContext = {
17
+ routePaths: new Map(),
18
+ apiPaths: new Map()
19
+ };
20
+ function setAvailableModels(models) {
21
+ availableModels = new Set(models);
22
+ }
23
+ function setRelationshipContext(context) {
24
+ relationshipContext = context;
25
+ }
26
+ function isRelationshipField(field) {
27
+ return availableModels.has(field.type);
28
+ }
29
+ function getForeignKeyFieldName(field) {
30
+ // Convention: fieldName + 'Id' (e.g., owner -> ownerId)
31
+ return field.name + 'Id';
32
+ }
10
33
  function toFileNameFromTemplateName(name) {
11
34
  const last = name.split('/').pop() || 'template';
12
35
  let base = last
@@ -23,6 +46,51 @@ function generateFormInput(field) {
23
46
  if (field.auto) {
24
47
  return '';
25
48
  }
49
+ // Handle relationship fields with a select dropdown
50
+ if (isRelationshipField(field)) {
51
+ const foreignKeyName = getForeignKeyFieldName(field);
52
+ const relatedModel = field.type;
53
+ const relatedModelLower = relatedModel.toLowerCase();
54
+ const displayField = (field.displayFields && field.displayFields.length > 0) ? field.displayFields[0] : 'name';
55
+ // Get actual paths from context, or fallback to defaults
56
+ const routePath = relationshipContext.routePaths.get(relatedModel) || `/${relatedModelLower}/create`;
57
+ const apiPath = relationshipContext.apiPaths.get(relatedModel) || `/api/${relatedModelLower}`;
58
+ return ` <div class="mb-3">
59
+ <label for="${foreignKeyName}" class="form-label">${fieldName}</label>
60
+ <div class="input-group">
61
+ <select id="${foreignKeyName}" name="${foreignKeyName}" class="form-select"${requiredAttr} data-relationship="${relatedModel}">
62
+ <option value="">-- Select ${fieldName} --</option>
63
+ <!-- Options will be populated via JavaScript from ${apiPath} -->
64
+ </select>
65
+ <button type="button" class="btn btn-outline-secondary" onclick="window.open('${routePath}', '${relatedModel}Create', 'width=600,height=400')">
66
+ <i class="bi bi-plus-circle"></i> New
67
+ </button>
68
+ </div>
69
+ <small class="form-text text-muted">Select an existing ${fieldName} or create a new one</small>
70
+ </div>
71
+ <script>
72
+ // Load ${relatedModel} options
73
+ (async () => {
74
+ try {
75
+ const response = await fetch('${apiPath}', {
76
+ headers: App.auth.buildAuthHeaders()
77
+ });
78
+ const data = await response.json();
79
+ const select = document.getElementById('${foreignKeyName}');
80
+ if (Array.isArray(data)) {
81
+ data.forEach(item => {
82
+ const option = document.createElement('option');
83
+ option.value = item.id;
84
+ option.textContent = item.${displayField} || item.id;
85
+ select.appendChild(option);
86
+ });
87
+ }
88
+ } catch (error) {
89
+ console.error('Failed to load ${relatedModel} options:', error);
90
+ }
91
+ })();
92
+ </script>`;
93
+ }
26
94
  switch (field.type.toLowerCase()) {
27
95
  case 'number':
28
96
  case 'int':
@@ -79,6 +147,55 @@ function generateUpdateFormInput(field) {
79
147
  if (field.auto) {
80
148
  return '';
81
149
  }
150
+ // Handle relationship fields with a select dropdown
151
+ if (isRelationshipField(field)) {
152
+ const foreignKeyName = getForeignKeyFieldName(field);
153
+ const relatedModel = field.type;
154
+ const relatedModelLower = relatedModel.toLowerCase();
155
+ const displayField = (field.displayFields && field.displayFields.length > 0) ? field.displayFields[0] : 'name';
156
+ // Get actual paths from context, or fallback to defaults
157
+ const routePath = relationshipContext.routePaths.get(relatedModel) || `/${relatedModelLower}/create`;
158
+ const apiPath = relationshipContext.apiPaths.get(relatedModel) || `/api/${relatedModelLower}`;
159
+ return ` <div class="mb-3">
160
+ <label for="${foreignKeyName}" class="form-label">${fieldName}</label>
161
+ <div class="input-group">
162
+ <select id="${foreignKeyName}" name="${foreignKeyName}" class="form-select"${requiredAttr} data-relationship="${relatedModel}" data-current-value="{{ $root.${foreignKeyName} }}">
163
+ <option value="">-- Select ${fieldName} --</option>
164
+ <!-- Options will be populated via JavaScript from ${apiPath} -->
165
+ </select>
166
+ <button type="button" class="btn btn-outline-secondary" onclick="window.open('${routePath}', '${relatedModel}Create', 'width=600,height=400')">
167
+ <i class="bi bi-plus-circle"></i> New
168
+ </button>
169
+ </div>
170
+ <small class="form-text text-muted">Select an existing ${fieldName} or create a new one</small>
171
+ </div>
172
+ <script>
173
+ // Load ${relatedModel} options
174
+ (async () => {
175
+ try {
176
+ const response = await fetch('${apiPath}', {
177
+ headers: App.auth.buildAuthHeaders()
178
+ });
179
+ const data = await response.json();
180
+ const select = document.getElementById('${foreignKeyName}');
181
+ const currentValue = select.getAttribute('data-current-value');
182
+ if (Array.isArray(data)) {
183
+ data.forEach(item => {
184
+ const option = document.createElement('option');
185
+ option.value = item.id;
186
+ option.textContent = item.${displayField} || item.id;
187
+ if (currentValue && item.id == currentValue) {
188
+ option.selected = true;
189
+ }
190
+ select.appendChild(option);
191
+ });
192
+ }
193
+ } catch (error) {
194
+ console.error('Failed to load ${relatedModel} options:', error);
195
+ }
196
+ })();
197
+ </script>`;
198
+ }
82
199
  switch (field.type.toLowerCase()) {
83
200
  case 'number':
84
201
  case 'int':
@@ -127,11 +244,19 @@ ${options}
127
244
  }
128
245
  }
129
246
  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('');
247
+ const safeFields = fields.length > 0 ? fields : [{ name: 'id', type: 'number' }, { name: 'name', type: 'string' }];
248
+ const headers = ['ID', ...safeFields.filter(f => f.name !== 'id').map(f => f.name.charAt(0).toUpperCase() + f.name.slice(1).replace(/_/g, ' '))].map(f => `<th scope="col">${f}</th>`).join('');
249
+ // Generate cells with relationship field handling
132
250
  const cells = [
133
251
  '<td>{{ row.id }}</td>',
134
- ...safeFields.filter(f => f !== 'id').map(f => `<td>{{ row.${f} }}</td>`)
252
+ ...safeFields.filter(f => f.name !== 'id').map(f => {
253
+ // For relationship fields, show a specific field from the related object
254
+ if (isRelationshipField(f)) {
255
+ const displayField = (f.displayFields && f.displayFields.length > 0) ? f.displayFields[0] : 'name';
256
+ return `<td>{{ row.${f.name}.${displayField} }}</td>`;
257
+ }
258
+ return `<td>{{ row.${f.name} }}</td>`;
259
+ })
135
260
  ].join('\n ');
136
261
  const deleteAction = apiBase ? `${apiBase}/{{ row.id }}` : `${basePath}/api/{{ row.id }}`;
137
262
  return `<!-- @template name="${templateName}" -->
@@ -187,10 +312,17 @@ function renderListTemplate(entityName, templateName, basePath, fields, apiBase)
187
312
  `;
188
313
  }
189
314
  function renderDetailTemplate(entityName, templateName, fields) {
190
- const safeFields = fields.length > 0 ? fields.map(f => f.name) : ['id', 'name'];
315
+ const safeFields = fields.length > 0 ? fields : [{ name: 'id', type: 'number' }, { name: 'name', type: 'string' }];
191
316
  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>`;
317
+ const fieldName = f.name.charAt(0).toUpperCase() + f.name.slice(1).replace(/_/g, ' ');
318
+ // For relationship fields, show specific fields from the related object
319
+ if (isRelationshipField(f)) {
320
+ const displayFields = (f.displayFields && f.displayFields.length > 0) ? f.displayFields : ['name'];
321
+ // Show all displayFields separated by comma
322
+ const fieldAccess = displayFields.map(df => `{{ $root.${f.name}.${df} }}`).join(', ');
323
+ return ` <tr><th scope="row" class="w-25">${fieldName}</th><td>${fieldAccess}</td></tr>`;
324
+ }
325
+ return ` <tr><th scope="row" class="w-25">${fieldName}</th><td>{{ $root.${f.name} }}</td></tr>`;
194
326
  }).join('\n');
195
327
  return `<!-- @template name="${templateName}" -->
196
328
  <div class="container-fluid py-4">
@@ -4,8 +4,13 @@ interface FieldConfig {
4
4
  required?: boolean;
5
5
  auto?: boolean;
6
6
  unique?: boolean;
7
+ displayFields?: string[];
7
8
  }
8
9
  export declare class ValidationGenerator {
10
+ private availableModels;
11
+ private setAvailableModels;
12
+ private isRelationshipField;
13
+ private getForeignKeyFieldName;
9
14
  private replaceTemplateVars;
10
15
  private getTypeScriptType;
11
16
  private generateInterfaceField;
@@ -42,6 +42,22 @@ const generationRegistry_1 = require("../utils/generationRegistry");
42
42
  const colors_1 = require("../utils/colors");
43
43
  const constants_1 = require("../utils/constants");
44
44
  class ValidationGenerator {
45
+ constructor() {
46
+ this.availableModels = new Set();
47
+ }
48
+ setAvailableModels(models) {
49
+ this.availableModels.clear();
50
+ models.forEach(model => {
51
+ this.availableModels.add(model.name);
52
+ });
53
+ }
54
+ isRelationshipField(field) {
55
+ return this.availableModels.has(field.type);
56
+ }
57
+ getForeignKeyFieldName(field) {
58
+ // Convention: fieldName + 'Id' (e.g., owner -> ownerId)
59
+ return field.name + 'Id';
60
+ }
45
61
  replaceTemplateVars(template, variables) {
46
62
  let result = template;
47
63
  Object.entries(variables).forEach(([key, value]) => {
@@ -54,6 +70,18 @@ class ValidationGenerator {
54
70
  return validationTemplates_1.typeMapping[yamlType] || 'any';
55
71
  }
56
72
  generateInterfaceField(field, isCreate) {
73
+ // For relationship fields, use the foreign key field instead
74
+ if (this.isRelationshipField(field)) {
75
+ const foreignKeyName = this.getForeignKeyFieldName(field);
76
+ const tsType = 'number'; // Foreign keys are always numbers
77
+ if (isCreate) {
78
+ const optional = !field.required ? '?' : '';
79
+ return ` ${foreignKeyName}${optional}: ${tsType};`;
80
+ }
81
+ else {
82
+ return ` ${foreignKeyName}?: ${tsType};`;
83
+ }
84
+ }
57
85
  const tsType = this.getTypeScriptType(field.type);
58
86
  if (isCreate) {
59
87
  if (field.auto || field.name === 'id') {
@@ -70,6 +98,13 @@ class ValidationGenerator {
70
98
  }
71
99
  }
72
100
  generateValidationLogic(field, isCreate) {
101
+ // For relationship fields, validate the foreign key
102
+ if (this.isRelationshipField(field)) {
103
+ const foreignKeyName = this.getForeignKeyFieldName(field);
104
+ const isRequired = isCreate && field.required;
105
+ const template = isRequired ? validationTemplates_1.validationTemplates.requiredNumberValidation : validationTemplates_1.validationTemplates.optionalNumberValidation;
106
+ return this.replaceTemplateVars(template, { FIELD_NAME: foreignKeyName });
107
+ }
73
108
  const fieldName = field.name;
74
109
  if (field.auto || field.name === 'id') {
75
110
  return '';
@@ -96,6 +131,18 @@ class ValidationGenerator {
96
131
  return this.replaceTemplateVars(template, { FIELD_NAME: fieldName });
97
132
  }
98
133
  generateDtoField(field, isCreate) {
134
+ // For relationship fields, use the foreign key field instead
135
+ if (this.isRelationshipField(field)) {
136
+ const foreignKeyName = this.getForeignKeyFieldName(field);
137
+ const tsType = 'number'; // Foreign keys are always numbers
138
+ if (isCreate) {
139
+ const optional = !field.required ? '?' : '';
140
+ return ` ${foreignKeyName}${optional}: ${tsType};`;
141
+ }
142
+ else {
143
+ return ` ${foreignKeyName}?: ${tsType};`;
144
+ }
145
+ }
99
146
  const tsType = this.getTypeScriptType(field.type);
100
147
  if (isCreate) {
101
148
  if (field.auto || field.name === 'id') {
@@ -163,6 +210,8 @@ class ValidationGenerator {
163
210
  if (config.modules) {
164
211
  Object.values(config.modules).forEach(moduleConfig => {
165
212
  if (moduleConfig.models && moduleConfig.models.length > 0) {
213
+ // Set available models for relationship detection
214
+ this.setAvailableModels(moduleConfig.models);
166
215
  moduleConfig.models.forEach(model => {
167
216
  const validationCode = this.generateValidation(model.name, model.fields);
168
217
  validations[model.name] = validationCode;
@@ -173,6 +222,8 @@ class ValidationGenerator {
173
222
  else if (config.models) {
174
223
  const module = config;
175
224
  if (module.models) {
225
+ // Set available models for relationship detection
226
+ this.setAvailableModels(module.models);
176
227
  module.models.forEach(model => {
177
228
  const validationCode = this.generateValidation(model.name, model.fields);
178
229
  validations[model.name] = validationCode;
@@ -4,3 +4,9 @@ export declare function ensureDir(dirPath: string): void;
4
4
  export declare function writeFileIfMissing(targetPath: string, contents: string): void;
5
5
  export declare function fileExists(targetPath: string): boolean;
6
6
  export declare function toAbsolute(targetPath: string): string;
7
+ export declare function runCommand(command: string, options?: {
8
+ cwd?: string;
9
+ successMessage?: string;
10
+ errorMessage?: string;
11
+ infoMessage?: string;
12
+ }): boolean;
@@ -39,9 +39,12 @@ exports.ensureDir = ensureDir;
39
39
  exports.writeFileIfMissing = writeFileIfMissing;
40
40
  exports.fileExists = fileExists;
41
41
  exports.toAbsolute = toAbsolute;
42
+ exports.runCommand = runCommand;
42
43
  const fs = __importStar(require("fs"));
43
44
  const path = __importStar(require("path"));
45
+ const child_process_1 = require("child_process");
44
46
  const constants_1 = require("./constants");
47
+ const colors_1 = require("./colors");
45
48
  function resolveYamlPath(provided) {
46
49
  const candidate = provided !== null && provided !== void 0 ? provided : constants_1.COMMON_FILES.APP_YAML;
47
50
  const abs = path.isAbsolute(candidate) ? candidate : path.resolve(process.cwd(), candidate);
@@ -69,3 +72,24 @@ function fileExists(targetPath) {
69
72
  function toAbsolute(targetPath) {
70
73
  return path.isAbsolute(targetPath) ? targetPath : path.resolve(process.cwd(), targetPath);
71
74
  }
75
+ function runCommand(command, options = {}) {
76
+ const { cwd = process.cwd(), successMessage, errorMessage, infoMessage } = options;
77
+ if (infoMessage) {
78
+ // eslint-disable-next-line no-console
79
+ console.log(colors_1.colors.cyan(infoMessage));
80
+ }
81
+ try {
82
+ (0, child_process_1.execSync)(command, { cwd, stdio: 'inherit' });
83
+ if (successMessage) {
84
+ // eslint-disable-next-line no-console
85
+ console.log(colors_1.colors.green(successMessage));
86
+ }
87
+ return true;
88
+ }
89
+ catch (error) {
90
+ const errMsg = errorMessage || `Command failed: ${command}`;
91
+ // eslint-disable-next-line no-console
92
+ console.error(colors_1.colors.red(errMsg), error instanceof Error ? error.message : String(error));
93
+ return false;
94
+ }
95
+ }
@@ -9,6 +9,8 @@ export declare const COMMON_FILES: {
9
9
  readonly APP_TS: "app.ts";
10
10
  readonly REGISTRY_JSON: "registry.json";
11
11
  readonly STORE_INTERFACE: "StoreInterface.ts";
12
+ readonly SCHEMA_STATE: "schema_state.yaml";
13
+ readonly MIGRATION_LOG: "migration_log.json";
12
14
  };
13
15
  export declare const GENERATOR_MARKERS: {
14
16
  readonly CONTROLLERS_START: "// currentjs:controllers:start";
@@ -26,6 +28,7 @@ export declare const PATH_PATTERNS: {
26
28
  readonly STORES: "stores";
27
29
  readonly SERVICES: "services";
28
30
  readonly CONTROLLERS: "controllers";
31
+ readonly MIGRATIONS: "migrations";
29
32
  };
30
33
  export declare const DEFAULTS: {
31
34
  readonly SERVER_PORT: 3000;
@@ -14,7 +14,9 @@ exports.COMMON_FILES = {
14
14
  APP_YAML: 'app.yaml',
15
15
  APP_TS: 'app.ts',
16
16
  REGISTRY_JSON: 'registry.json',
17
- STORE_INTERFACE: 'StoreInterface.ts'
17
+ STORE_INTERFACE: 'StoreInterface.ts',
18
+ SCHEMA_STATE: 'schema_state.yaml',
19
+ MIGRATION_LOG: 'migration_log.json'
18
20
  };
19
21
  // Generator markers
20
22
  exports.GENERATOR_MARKERS = {
@@ -33,7 +35,8 @@ exports.PATH_PATTERNS = {
33
35
  ENTITIES: 'entities',
34
36
  STORES: 'stores',
35
37
  SERVICES: 'services',
36
- CONTROLLERS: 'controllers'
38
+ CONTROLLERS: 'controllers',
39
+ MIGRATIONS: 'migrations'
37
40
  };
38
41
  // Default values
39
42
  exports.DEFAULTS = {
@@ -0,0 +1,49 @@
1
+ export interface FieldConfig {
2
+ name: string;
3
+ type: string;
4
+ required?: boolean;
5
+ unique?: boolean;
6
+ auto?: boolean;
7
+ }
8
+ export interface ModelConfig {
9
+ name: string;
10
+ fields: FieldConfig[];
11
+ }
12
+ export interface SchemaState {
13
+ models: ModelConfig[];
14
+ version: string;
15
+ timestamp: string;
16
+ }
17
+ export interface MigrationLog {
18
+ appliedMigrations: string[];
19
+ }
20
+ export interface ColumnInfo {
21
+ Field: string;
22
+ Type: string;
23
+ Null: string;
24
+ Key: string;
25
+ Default: string | null;
26
+ Extra: string;
27
+ }
28
+ export interface ForeignKeyInfo {
29
+ CONSTRAINT_NAME: string;
30
+ COLUMN_NAME: string;
31
+ REFERENCED_TABLE_NAME: string;
32
+ REFERENCED_COLUMN_NAME: string;
33
+ }
34
+ export declare function mapYamlTypeToSql(yamlType: string, availableModels: Set<string>): string;
35
+ export declare function getTableName(modelName: string): string;
36
+ export declare function getForeignKeyFieldName(fieldName: string): string;
37
+ export declare function isRelationshipField(fieldType: string, availableModels: Set<string>): boolean;
38
+ export declare function generateCreateTableSQL(model: ModelConfig, availableModels: Set<string>): string;
39
+ export declare function generateDropTableSQL(tableName: string): string;
40
+ export declare function generateAddColumnSQL(tableName: string, field: FieldConfig, availableModels: Set<string>): string;
41
+ export declare function generateDropColumnSQL(tableName: string, columnName: string): string;
42
+ export declare function generateModifyColumnSQL(tableName: string, field: FieldConfig, availableModels: Set<string>): string;
43
+ export declare function loadSchemaState(stateFilePath: string): SchemaState | null;
44
+ export declare function saveSchemaState(stateFilePath: string, state: SchemaState): void;
45
+ export declare function loadMigrationLog(logFilePath: string): MigrationLog;
46
+ export declare function saveMigrationLog(logFilePath: string, log: MigrationLog): void;
47
+ export declare function compareSchemas(oldState: SchemaState | null, newModels: ModelConfig[]): string[];
48
+ export declare function generateTimestamp(): string;
49
+ export declare function getMigrationFileName(timestamp: string): string;