@currentjs/gen 0.2.2 → 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.
- package/CHANGELOG.md +80 -0
- package/README.md +256 -0
- package/dist/cli.js +26 -0
- package/dist/commands/createApp.js +2 -0
- package/dist/commands/generateAll.js +153 -29
- package/dist/commands/migrateCommit.d.ts +1 -0
- package/dist/commands/migrateCommit.js +201 -0
- package/dist/generators/controllerGenerator.d.ts +7 -0
- package/dist/generators/controllerGenerator.js +60 -29
- package/dist/generators/domainModelGenerator.d.ts +7 -0
- package/dist/generators/domainModelGenerator.js +57 -3
- package/dist/generators/serviceGenerator.d.ts +16 -1
- package/dist/generators/serviceGenerator.js +125 -12
- package/dist/generators/storeGenerator.d.ts +8 -0
- package/dist/generators/storeGenerator.js +133 -7
- package/dist/generators/templateGenerator.d.ts +19 -0
- package/dist/generators/templateGenerator.js +216 -11
- package/dist/generators/templates/appTemplates.d.ts +8 -7
- package/dist/generators/templates/appTemplates.js +11 -1572
- package/dist/generators/templates/data/appTsTemplate +39 -0
- package/dist/generators/templates/data/appYamlTemplate +4 -0
- package/dist/generators/templates/data/cursorRulesTemplate +671 -0
- package/dist/generators/templates/data/errorTemplate +28 -0
- package/dist/generators/templates/data/frontendScriptTemplate +739 -0
- package/dist/generators/templates/data/mainViewTemplate +16 -0
- package/dist/generators/templates/data/translationsTemplate +68 -0
- package/dist/generators/templates/data/tsConfigTemplate +19 -0
- package/dist/generators/templates/viewTemplates.d.ts +10 -1
- package/dist/generators/templates/viewTemplates.js +138 -6
- package/dist/generators/validationGenerator.d.ts +5 -0
- package/dist/generators/validationGenerator.js +51 -0
- package/dist/utils/constants.d.ts +3 -0
- package/dist/utils/constants.js +5 -2
- package/dist/utils/migrationUtils.d.ts +49 -0
- package/dist/utils/migrationUtils.js +291 -0
- package/howto.md +157 -65
- package/package.json +3 -2
|
@@ -87,6 +87,7 @@ async function handleGenerateAll(yamlPathArg, _outArg, moduleName, opts) {
|
|
|
87
87
|
const storeGen = new storeGenerator_1.StoreGenerator();
|
|
88
88
|
const tplGen = new templateGenerator_1.TemplateGenerator();
|
|
89
89
|
const initsBySrcDir = new Map();
|
|
90
|
+
const moduleConfigByFolder = new Map(); // Cache module configs by folder name
|
|
90
91
|
// Run modules sequentially to avoid overlapping interactive prompts
|
|
91
92
|
for (const moduleYamlRel of filteredModules) {
|
|
92
93
|
const moduleYamlPath = path.isAbsolute(moduleYamlRel)
|
|
@@ -98,6 +99,11 @@ async function handleGenerateAll(yamlPathArg, _outArg, moduleName, opts) {
|
|
|
98
99
|
continue;
|
|
99
100
|
}
|
|
100
101
|
const moduleDir = path.dirname(moduleYamlPath);
|
|
102
|
+
const moduleFolderName = path.basename(moduleDir);
|
|
103
|
+
// Parse and cache module config for dependency detection
|
|
104
|
+
const moduleYamlContent = fs.readFileSync(moduleYamlPath, 'utf8');
|
|
105
|
+
const moduleConfig = (0, yaml_1.parse)(moduleYamlContent);
|
|
106
|
+
moduleConfigByFolder.set(moduleFolderName, moduleConfig);
|
|
101
107
|
// Output folders inside module structure
|
|
102
108
|
const domainOut = path.join(moduleDir, 'domain', 'entities');
|
|
103
109
|
const appOut = path.join(moduleDir, 'application');
|
|
@@ -131,6 +137,50 @@ async function handleGenerateAll(yamlPathArg, _outArg, moduleName, opts) {
|
|
|
131
137
|
if (!srcDir)
|
|
132
138
|
continue;
|
|
133
139
|
const list = (_b = initsBySrcDir.get(srcDir)) !== null && _b !== void 0 ? _b : [];
|
|
140
|
+
// First, add entries for all models in this module (even without controllers)
|
|
141
|
+
// This ensures dependency stores are initialized
|
|
142
|
+
if (moduleConfig && moduleConfig.models) {
|
|
143
|
+
const m = moduleYamlPath.match(/modules\/([^/]+)\//);
|
|
144
|
+
const moduleFolder = m ? m[1] : path.basename(moduleDir);
|
|
145
|
+
for (const model of moduleConfig.models) {
|
|
146
|
+
const entityName = model.name;
|
|
147
|
+
const entityVar = entityName.charAt(0).toLowerCase() + entityName.slice(1);
|
|
148
|
+
// Check if we already have an entry for this entity
|
|
149
|
+
if (list.find(x => x.entityName === entityName)) {
|
|
150
|
+
continue; // Skip if already added via controller
|
|
151
|
+
}
|
|
152
|
+
const storeImportPath = `${constants_1.PATH_PATTERNS.MODULES_RELATIVE}${moduleFolder}/${constants_1.PATH_PATTERNS.INFRASTRUCTURE}/${constants_1.PATH_PATTERNS.STORES}/${entityName}${constants_1.GENERATOR_SUFFIXES.STORE}`;
|
|
153
|
+
const serviceImportPath = `${constants_1.PATH_PATTERNS.MODULES_RELATIVE}${moduleFolder}/${constants_1.PATH_PATTERNS.APPLICATION}/${constants_1.PATH_PATTERNS.SERVICES}/${entityName}${constants_1.GENERATOR_SUFFIXES.SERVICE}`;
|
|
154
|
+
// Detect dependencies
|
|
155
|
+
const storeDeps = [];
|
|
156
|
+
const serviceDeps = [];
|
|
157
|
+
if (model.fields) {
|
|
158
|
+
model.fields.forEach((field) => {
|
|
159
|
+
const isRelationship = moduleConfig.models.some((m) => m.name === field.type);
|
|
160
|
+
if (isRelationship) {
|
|
161
|
+
storeDeps.push(field.type);
|
|
162
|
+
serviceDeps.push(field.type);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
// Add a minimal init entry (no controller, just for dependency resolution)
|
|
167
|
+
const init = {
|
|
168
|
+
ctrlName: '', // No controller
|
|
169
|
+
entityName,
|
|
170
|
+
entityVar,
|
|
171
|
+
importController: '',
|
|
172
|
+
importStore: `import { ${entityName}Store } from '${storeImportPath}';`,
|
|
173
|
+
importService: `import { ${entityName}Service } from '${serviceImportPath}';`,
|
|
174
|
+
importAuth: undefined,
|
|
175
|
+
wiring: [],
|
|
176
|
+
registration: '',
|
|
177
|
+
storeDependencies: storeDeps,
|
|
178
|
+
serviceDependencies: serviceDeps
|
|
179
|
+
};
|
|
180
|
+
list.push(init);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
// Then process controllers
|
|
134
184
|
for (const filePath of generatedControllers) {
|
|
135
185
|
const rel = path
|
|
136
186
|
.relative(srcDir, filePath)
|
|
@@ -151,25 +201,52 @@ async function handleGenerateAll(yamlPathArg, _outArg, moduleName, opts) {
|
|
|
151
201
|
continue;
|
|
152
202
|
const storeImportPath = `${constants_1.PATH_PATTERNS.MODULES_RELATIVE}${moduleFolder}/${constants_1.PATH_PATTERNS.INFRASTRUCTURE}/${constants_1.PATH_PATTERNS.STORES}/${entityName}${constants_1.GENERATOR_SUFFIXES.STORE}`;
|
|
153
203
|
const serviceImportPath = `${constants_1.PATH_PATTERNS.MODULES_RELATIVE}${moduleFolder}/${constants_1.PATH_PATTERNS.APPLICATION}/${constants_1.PATH_PATTERNS.SERVICES}/${entityName}${constants_1.GENERATOR_SUFFIXES.SERVICE}`;
|
|
154
|
-
//
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
204
|
+
// Look up the correct module config for this controller
|
|
205
|
+
const controllerModuleConfig = moduleConfigByFolder.get(moduleFolder);
|
|
206
|
+
// Detect store and service dependencies from module config
|
|
207
|
+
const storeDeps = [];
|
|
208
|
+
const serviceDeps = [];
|
|
209
|
+
if (controllerModuleConfig && controllerModuleConfig.models) {
|
|
210
|
+
const model = controllerModuleConfig.models.find((m) => m.name === entityName);
|
|
211
|
+
if (model && model.fields) {
|
|
212
|
+
model.fields.forEach((field) => {
|
|
213
|
+
// Check if this field is a relationship (type is another model name)
|
|
214
|
+
const isRelationship = controllerModuleConfig.models.some((m) => m.name === field.type);
|
|
215
|
+
if (isRelationship) {
|
|
216
|
+
storeDeps.push(field.type);
|
|
217
|
+
serviceDeps.push(field.type);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
// Check if we already have a base entry for this entity (from all models)
|
|
223
|
+
const existingEntry = list.find(x => x.entityName === entityName && !x.ctrlName);
|
|
224
|
+
if (existingEntry) {
|
|
225
|
+
// Update existing entry to add controller info
|
|
226
|
+
existingEntry.ctrlName = ctrlName;
|
|
227
|
+
existingEntry.importController = `import { ${ctrlName} } from '${importPath}';`;
|
|
228
|
+
existingEntry.registration = `new ${ctrlName}(${entityVar}Service)`;
|
|
229
|
+
// Keep the dependencies we already detected
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
// Create new entry with controller
|
|
233
|
+
const init = {
|
|
234
|
+
ctrlName,
|
|
235
|
+
entityName,
|
|
236
|
+
entityVar,
|
|
237
|
+
importController: `import { ${ctrlName} } from '${importPath}';`,
|
|
238
|
+
importStore: `import { ${entityName}Store } from '${storeImportPath}';`,
|
|
239
|
+
importService: `import { ${entityName}Service } from '${serviceImportPath}';`,
|
|
240
|
+
importAuth: undefined,
|
|
241
|
+
wiring: [],
|
|
242
|
+
registration: `new ${ctrlName}(${entityVar}Service)`,
|
|
243
|
+
storeDependencies: storeDeps,
|
|
244
|
+
serviceDependencies: serviceDeps
|
|
245
|
+
};
|
|
246
|
+
if (!list.find((x) => x.ctrlName === init.ctrlName)) {
|
|
247
|
+
list.push(init);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
173
250
|
}
|
|
174
251
|
initsBySrcDir.set(srcDir, list);
|
|
175
252
|
await storeGen.generateAndSaveFiles(moduleYamlPath, infraOut, { force: !!(opts === null || opts === void 0 ? void 0 : opts.force), skipOnConflict: !!(opts === null || opts === void 0 ? void 0 : opts.skip) });
|
|
@@ -232,12 +309,17 @@ async function handleGenerateAll(yamlPathArg, _outArg, moduleName, opts) {
|
|
|
232
309
|
importLines.push(`import { ProviderMysql } from '@currentjs/provider-mysql';`);
|
|
233
310
|
}
|
|
234
311
|
for (const init of controllerInits) {
|
|
235
|
-
const maybe = [init.
|
|
312
|
+
const maybe = [init.importStore, init.importService];
|
|
313
|
+
// Only add controller import if entity has a controller
|
|
314
|
+
if (init.importController && init.importController.trim() !== '') {
|
|
315
|
+
maybe.push(init.importController);
|
|
316
|
+
}
|
|
236
317
|
if (init.importAuth)
|
|
237
318
|
maybe.push(init.importAuth);
|
|
238
319
|
for (const line of maybe) {
|
|
239
|
-
if (!appTs.includes(line) && !importLines.includes(line))
|
|
320
|
+
if (line && line.trim() !== '' && !appTs.includes(line) && !importLines.includes(line)) {
|
|
240
321
|
importLines.push(line);
|
|
322
|
+
}
|
|
241
323
|
}
|
|
242
324
|
}
|
|
243
325
|
if (importLines.length) {
|
|
@@ -249,20 +331,62 @@ async function handleGenerateAll(yamlPathArg, _outArg, moduleName, opts) {
|
|
|
249
331
|
if (toAdd.length)
|
|
250
332
|
appTs = toAdd.join('\n') + '\n' + appTs;
|
|
251
333
|
}
|
|
252
|
-
// Compose fresh block content
|
|
334
|
+
// Compose fresh block content with dependency-aware ordering
|
|
253
335
|
const wiringLines = [];
|
|
254
336
|
// DB placeholder once
|
|
255
337
|
wiringLines.push(ensureDbLine);
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
338
|
+
// Deduplicate by entityName
|
|
339
|
+
const uniqueInits = Array.from(new Map(controllerInits.map(init => [init.entityName, init])).values());
|
|
340
|
+
// Sort entities by dependencies (entities with no deps first)
|
|
341
|
+
const sorted = [];
|
|
342
|
+
const processed = new Set();
|
|
343
|
+
const addEntity = (init) => {
|
|
344
|
+
if (processed.has(init.entityName))
|
|
345
|
+
return;
|
|
346
|
+
// Process dependencies first
|
|
347
|
+
if (init.storeDependencies) {
|
|
348
|
+
for (const depName of init.storeDependencies) {
|
|
349
|
+
const depInit = uniqueInits.find(i => i.entityName === depName);
|
|
350
|
+
if (depInit && !processed.has(depName)) {
|
|
351
|
+
addEntity(depInit);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
261
354
|
}
|
|
355
|
+
sorted.push(init);
|
|
356
|
+
processed.add(init.entityName);
|
|
357
|
+
};
|
|
358
|
+
uniqueInits.forEach(init => addEntity(init));
|
|
359
|
+
// Generate wiring for stores and services
|
|
360
|
+
for (const init of sorted) {
|
|
361
|
+
const entityVar = init.entityVar;
|
|
362
|
+
const entityName = init.entityName;
|
|
363
|
+
// Build store constructor parameters
|
|
364
|
+
const storeParams = ['db'];
|
|
365
|
+
if (init.storeDependencies && init.storeDependencies.length > 0) {
|
|
366
|
+
init.storeDependencies.forEach(depName => {
|
|
367
|
+
const depVar = depName.charAt(0).toLowerCase() + depName.slice(1);
|
|
368
|
+
storeParams.push(`${depVar}Store`);
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
// Build service constructor parameters
|
|
372
|
+
const serviceParams = [`${entityVar}Store`];
|
|
373
|
+
if (init.serviceDependencies && init.serviceDependencies.length > 0) {
|
|
374
|
+
init.serviceDependencies.forEach(depName => {
|
|
375
|
+
const depVar = depName.charAt(0).toLowerCase() + depName.slice(1);
|
|
376
|
+
serviceParams.push(`${depVar}Store`);
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
wiringLines.push(`const ${entityVar}Store = new ${entityName}Store(${storeParams.join(', ')});`);
|
|
380
|
+
wiringLines.push(`const ${entityVar}Service = new ${entityName}Service(${serviceParams.join(', ')});`);
|
|
262
381
|
}
|
|
263
|
-
|
|
382
|
+
// Only include registrations for entities with controllers
|
|
383
|
+
const registrations = controllerInits
|
|
384
|
+
.filter(i => i.registration && i.registration.trim() !== '')
|
|
385
|
+
.map(i => i.registration);
|
|
264
386
|
wiringLines.push('const controllers = [');
|
|
265
|
-
|
|
387
|
+
if (registrations.length > 0) {
|
|
388
|
+
wiringLines.push(` ${registrations.join(',\n ')}`);
|
|
389
|
+
}
|
|
266
390
|
wiringLines.push('];');
|
|
267
391
|
const startMarker = constants_1.GENERATOR_MARKERS.CONTROLLERS_START;
|
|
268
392
|
const endMarker = constants_1.GENERATOR_MARKERS.CONTROLLERS_END;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function handleMigrateCommit(yamlPath?: string): void;
|
|
@@ -0,0 +1,201 @@
|
|
|
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.handleMigrateCommit = handleMigrateCommit;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const yaml_1 = require("yaml");
|
|
40
|
+
const colors_1 = require("../utils/colors");
|
|
41
|
+
const cliUtils_1 = require("../utils/cliUtils");
|
|
42
|
+
const migrationUtils_1 = require("../utils/migrationUtils");
|
|
43
|
+
function collectModelsFromYaml(yamlPath) {
|
|
44
|
+
const yamlContent = fs.readFileSync(yamlPath, 'utf8');
|
|
45
|
+
const config = (0, yaml_1.parse)(yamlContent);
|
|
46
|
+
const projectRoot = path.dirname(yamlPath);
|
|
47
|
+
const allModels = [];
|
|
48
|
+
const sources = [];
|
|
49
|
+
// Check if it's a module YAML (has models directly)
|
|
50
|
+
if (config.models) {
|
|
51
|
+
allModels.push(...config.models);
|
|
52
|
+
sources.push(`app.yaml (${config.models.length} model(s))`);
|
|
53
|
+
}
|
|
54
|
+
// Check if it's an app YAML (has modules as array of paths)
|
|
55
|
+
if (config.modules && Array.isArray(config.modules)) {
|
|
56
|
+
let moduleCount = 0;
|
|
57
|
+
config.modules.forEach(moduleRef => {
|
|
58
|
+
// Handle both string and object format
|
|
59
|
+
const modulePath = typeof moduleRef === 'string' ? moduleRef : moduleRef.module;
|
|
60
|
+
const moduleYamlPath = path.isAbsolute(modulePath)
|
|
61
|
+
? modulePath
|
|
62
|
+
: path.resolve(projectRoot, modulePath);
|
|
63
|
+
if (fs.existsSync(moduleYamlPath)) {
|
|
64
|
+
const moduleYamlContent = fs.readFileSync(moduleYamlPath, 'utf8');
|
|
65
|
+
const moduleConfig = (0, yaml_1.parse)(moduleYamlContent);
|
|
66
|
+
if (moduleConfig.models) {
|
|
67
|
+
allModels.push(...moduleConfig.models);
|
|
68
|
+
moduleCount++;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
if (moduleCount > 0) {
|
|
73
|
+
sources.push(`app.yaml modules section (${moduleCount} module(s))`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// Check if modules is an object (legacy format with models embedded)
|
|
77
|
+
else if (config.modules && typeof config.modules === 'object' && !Array.isArray(config.modules)) {
|
|
78
|
+
let moduleCount = 0;
|
|
79
|
+
Object.values(config.modules).forEach(moduleConfig => {
|
|
80
|
+
if (moduleConfig.models) {
|
|
81
|
+
allModels.push(...moduleConfig.models);
|
|
82
|
+
moduleCount++;
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
if (moduleCount > 0) {
|
|
86
|
+
sources.push(`app.yaml modules section (${moduleCount} module(s))`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// Also check for module YAMLs in src/modules/*/module.yaml (as fallback)
|
|
90
|
+
const modulesDir = path.join(projectRoot, 'src', 'modules');
|
|
91
|
+
if (fs.existsSync(modulesDir)) {
|
|
92
|
+
const moduleFolders = fs.readdirSync(modulesDir).filter(f => {
|
|
93
|
+
const stat = fs.statSync(path.join(modulesDir, f));
|
|
94
|
+
return stat.isDirectory();
|
|
95
|
+
});
|
|
96
|
+
let moduleYamlCount = 0;
|
|
97
|
+
for (const moduleFolder of moduleFolders) {
|
|
98
|
+
const moduleYamlPath = path.join(modulesDir, moduleFolder, 'module.yaml');
|
|
99
|
+
if (fs.existsSync(moduleYamlPath)) {
|
|
100
|
+
const moduleYamlContent = fs.readFileSync(moduleYamlPath, 'utf8');
|
|
101
|
+
const moduleConfig = (0, yaml_1.parse)(moduleYamlContent);
|
|
102
|
+
if (moduleConfig.models) {
|
|
103
|
+
allModels.push(...moduleConfig.models);
|
|
104
|
+
moduleYamlCount++;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (moduleYamlCount > 0) {
|
|
109
|
+
sources.push(`src/modules/*/module.yaml (${moduleYamlCount} module(s))`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// Log sources
|
|
113
|
+
if (sources.length > 0) {
|
|
114
|
+
// eslint-disable-next-line no-console
|
|
115
|
+
console.log(colors_1.colors.gray(` Sources: ${sources.join(', ')}`));
|
|
116
|
+
}
|
|
117
|
+
return allModels;
|
|
118
|
+
}
|
|
119
|
+
function handleMigrateCommit(yamlPath) {
|
|
120
|
+
try {
|
|
121
|
+
const resolvedYamlPath = (0, cliUtils_1.resolveYamlPath)(yamlPath);
|
|
122
|
+
const projectRoot = path.dirname(resolvedYamlPath);
|
|
123
|
+
const migrationsDir = path.join(projectRoot, 'migrations');
|
|
124
|
+
const stateFilePath = path.join(migrationsDir, 'schema_state.yaml');
|
|
125
|
+
// eslint-disable-next-line no-console
|
|
126
|
+
console.log(colors_1.colors.cyan('\n🔄 Running migration commit at application level...'));
|
|
127
|
+
// eslint-disable-next-line no-console
|
|
128
|
+
console.log(colors_1.colors.gray(` Project root: ${projectRoot}`));
|
|
129
|
+
// eslint-disable-next-line no-console
|
|
130
|
+
console.log(colors_1.colors.gray(` Migrations dir: ${migrationsDir}`));
|
|
131
|
+
// Ensure migrations directory exists
|
|
132
|
+
if (!fs.existsSync(migrationsDir)) {
|
|
133
|
+
fs.mkdirSync(migrationsDir, { recursive: true });
|
|
134
|
+
// eslint-disable-next-line no-console
|
|
135
|
+
console.log(colors_1.colors.green(` ✓ Created migrations directory`));
|
|
136
|
+
}
|
|
137
|
+
// Collect all models from YAML files
|
|
138
|
+
// eslint-disable-next-line no-console
|
|
139
|
+
console.log(colors_1.colors.cyan('\n📋 Collecting models from all modules...'));
|
|
140
|
+
const currentModels = collectModelsFromYaml(resolvedYamlPath);
|
|
141
|
+
if (currentModels.length === 0) {
|
|
142
|
+
// eslint-disable-next-line no-console
|
|
143
|
+
console.log(colors_1.colors.yellow('⚠️ No models found in YAML configuration.'));
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
// eslint-disable-next-line no-console
|
|
147
|
+
console.log(colors_1.colors.green(`✓ Found ${currentModels.length} model(s): ${currentModels.map(m => m.name).join(', ')}`));
|
|
148
|
+
// Load previous state
|
|
149
|
+
const oldState = (0, migrationUtils_1.loadSchemaState)(stateFilePath);
|
|
150
|
+
if (oldState) {
|
|
151
|
+
// eslint-disable-next-line no-console
|
|
152
|
+
console.log(colors_1.colors.cyan(`📖 Previous schema state loaded (version: ${oldState.version})`));
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
// eslint-disable-next-line no-console
|
|
156
|
+
console.log(colors_1.colors.cyan('📖 No previous schema state found - will generate initial migration'));
|
|
157
|
+
}
|
|
158
|
+
// Compare schemas and generate SQL
|
|
159
|
+
// eslint-disable-next-line no-console
|
|
160
|
+
console.log(colors_1.colors.cyan('\n🔍 Comparing schemas...'));
|
|
161
|
+
const sqlStatements = (0, migrationUtils_1.compareSchemas)(oldState, currentModels);
|
|
162
|
+
if (sqlStatements.length === 0 || sqlStatements.every(s => s.trim() === '' || s.startsWith('--'))) {
|
|
163
|
+
// eslint-disable-next-line no-console
|
|
164
|
+
console.log(colors_1.colors.yellow('⚠️ No changes detected. Schema is up to date.'));
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
// Generate migration file
|
|
168
|
+
const timestamp = (0, migrationUtils_1.generateTimestamp)();
|
|
169
|
+
const migrationFileName = (0, migrationUtils_1.getMigrationFileName)(timestamp);
|
|
170
|
+
const migrationFilePath = path.join(migrationsDir, migrationFileName);
|
|
171
|
+
const migrationHeader = `-- Migration: ${oldState ? 'Schema update' : 'Initial schema'}
|
|
172
|
+
-- Created: ${new Date().toISOString().split('T')[0]}
|
|
173
|
+
-- Timestamp: ${timestamp}
|
|
174
|
+
|
|
175
|
+
`;
|
|
176
|
+
const migrationContent = migrationHeader + sqlStatements.join('\n');
|
|
177
|
+
fs.writeFileSync(migrationFilePath, migrationContent);
|
|
178
|
+
// Update state file
|
|
179
|
+
const newState = {
|
|
180
|
+
models: currentModels,
|
|
181
|
+
version: timestamp,
|
|
182
|
+
timestamp: new Date().toISOString()
|
|
183
|
+
};
|
|
184
|
+
(0, migrationUtils_1.saveSchemaState)(stateFilePath, newState);
|
|
185
|
+
// eslint-disable-next-line no-console
|
|
186
|
+
console.log(colors_1.colors.green('\n✅ Migration created successfully!'));
|
|
187
|
+
// eslint-disable-next-line no-console
|
|
188
|
+
console.log(colors_1.colors.gray(` File: ${migrationFileName}`));
|
|
189
|
+
// eslint-disable-next-line no-console
|
|
190
|
+
console.log(colors_1.colors.gray(` Path: ${migrationFilePath}`));
|
|
191
|
+
// eslint-disable-next-line no-console
|
|
192
|
+
console.log(colors_1.colors.gray(` Location: Application-level migrations directory`));
|
|
193
|
+
// eslint-disable-next-line no-console
|
|
194
|
+
console.log(colors_1.colors.cyan('\n💡 Next step: Run "currentjs migrate push" to apply this migration to the database (not implemented yet).'));
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
// eslint-disable-next-line no-console
|
|
198
|
+
console.error(colors_1.colors.red('❌ Error creating migration:'), error instanceof Error ? error.message : String(error));
|
|
199
|
+
process.exitCode = 1;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
@@ -13,6 +13,13 @@ export declare class ControllerGenerator {
|
|
|
13
13
|
private getWebMethodName;
|
|
14
14
|
private getPathSuffix;
|
|
15
15
|
private getReturnType;
|
|
16
|
+
/**
|
|
17
|
+
* Generate controller for a single config (api or routes)
|
|
18
|
+
*/
|
|
19
|
+
private generateControllerForModelWithConfig;
|
|
20
|
+
/**
|
|
21
|
+
* Generate controller for a model (handles both single config and array) - Option D
|
|
22
|
+
*/
|
|
16
23
|
private generateControllerForModel;
|
|
17
24
|
private generateController;
|
|
18
25
|
generateFromYamlFile(yamlFilePath: string): Record<string, string>;
|
|
@@ -254,17 +254,18 @@ class ControllerGenerator {
|
|
|
254
254
|
return 'any';
|
|
255
255
|
}
|
|
256
256
|
}
|
|
257
|
-
|
|
257
|
+
/**
|
|
258
|
+
* Generate controller for a single config (api or routes)
|
|
259
|
+
*/
|
|
260
|
+
generateControllerForModelWithConfig(model, moduleName, moduleConfig, cfg, hasGlobalPermissions, kind) {
|
|
258
261
|
var _a;
|
|
259
262
|
const isApi = kind === 'api';
|
|
260
|
-
const cfgRaw = isApi ? moduleConfig.api : moduleConfig.routes;
|
|
261
|
-
let cfg = cfgRaw;
|
|
262
263
|
const entityName = model.name;
|
|
263
264
|
const entityLower = entityName.toLowerCase();
|
|
264
265
|
// Determine if we should generate a controller for this model
|
|
265
266
|
const configModel = (cfg === null || cfg === void 0 ? void 0 : cfg.model) || (moduleConfig.models && moduleConfig.models[0] ? moduleConfig.models[0].name : null);
|
|
266
267
|
const topLevelMatches = !configModel || configModel === entityName || configModel.toLowerCase() === entityLower;
|
|
267
|
-
// Also check if any endpoints specifically target this model
|
|
268
|
+
// Also check if any endpoints specifically target this model (Option A)
|
|
268
269
|
const hasEndpointForThisModel = ((_a = cfg === null || cfg === void 0 ? void 0 : cfg.endpoints) === null || _a === void 0 ? void 0 : _a.some(endpoint => {
|
|
269
270
|
const endpointModel = endpoint.model || configModel;
|
|
270
271
|
return endpointModel === entityName || (endpointModel === null || endpointModel === void 0 ? void 0 : endpointModel.toLowerCase()) === entityLower;
|
|
@@ -273,33 +274,10 @@ class ControllerGenerator {
|
|
|
273
274
|
if (!shouldGenerateForThisModel) {
|
|
274
275
|
return '';
|
|
275
276
|
}
|
|
276
|
-
if (!isApi) {
|
|
277
|
-
// Ensure sensible defaults for Web routes: list, detail, create (empty), edit (get)
|
|
278
|
-
if (!cfgRaw || !cfgRaw.endpoints || cfgRaw.endpoints.length === 0) {
|
|
279
|
-
cfg = {
|
|
280
|
-
prefix: `/${entityLower}`,
|
|
281
|
-
endpoints: [
|
|
282
|
-
{ path: '/', action: 'list', method: 'GET', view: `${entityLower}List` },
|
|
283
|
-
{ path: '/:id', action: 'get', method: 'GET', view: `${entityLower}Detail` },
|
|
284
|
-
{ path: '/create', action: 'empty', method: 'GET', view: `${entityLower}Create` },
|
|
285
|
-
{ path: '/:id/edit', action: 'get', method: 'GET', view: `${entityLower}Update` }
|
|
286
|
-
]
|
|
287
|
-
};
|
|
288
|
-
}
|
|
289
|
-
else {
|
|
290
|
-
// Force GET for all web endpoints; forms should submit to API using custom form handling
|
|
291
|
-
cfg = {
|
|
292
|
-
prefix: cfgRaw.prefix || `/${entityLower}`,
|
|
293
|
-
endpoints: (cfgRaw.endpoints || []).map(e => ({ ...e, method: 'GET' }))
|
|
294
|
-
};
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
if (!cfg)
|
|
298
|
-
return '';
|
|
299
277
|
const controllerBase = (cfg.prefix || `/${isApi ? 'api/' : ''}${entityLower}`).replace(/\/$/, '');
|
|
300
278
|
const actionPermissions = this.getActionPermissions(moduleName, moduleConfig);
|
|
301
279
|
const hasPermissions = hasGlobalPermissions && !!(moduleConfig.permissions && moduleConfig.permissions.length > 0);
|
|
302
|
-
// Filter endpoints that apply to this model
|
|
280
|
+
// Filter endpoints that apply to this model (Option A)
|
|
303
281
|
const modelEndpoints = (cfg.endpoints || []).filter(endpoint => {
|
|
304
282
|
const endpointModel = endpoint.model || (cfg === null || cfg === void 0 ? void 0 : cfg.model) || (moduleConfig.models && moduleConfig.models[0] ? moduleConfig.models[0].name : null);
|
|
305
283
|
return endpointModel === entityName || (endpointModel === null || endpointModel === void 0 ? void 0 : endpointModel.toLowerCase()) === entityLower;
|
|
@@ -314,12 +292,65 @@ class ControllerGenerator {
|
|
|
314
292
|
return this.generateControllerMethod(endpoint, entityName, roles, hasPermissions, kind, moduleConfig.actions);
|
|
315
293
|
})
|
|
316
294
|
.join('\n\n');
|
|
295
|
+
return controllerMethods;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Generate controller for a model (handles both single config and array) - Option D
|
|
299
|
+
*/
|
|
300
|
+
generateControllerForModel(model, moduleName, moduleConfig, hasGlobalPermissions, kind) {
|
|
301
|
+
const isApi = kind === 'api';
|
|
302
|
+
const cfgRaw = isApi ? moduleConfig.api : moduleConfig.routes;
|
|
303
|
+
const entityName = model.name;
|
|
304
|
+
const entityLower = entityName.toLowerCase();
|
|
305
|
+
// Support both single config and array (Option D)
|
|
306
|
+
let configs = [];
|
|
307
|
+
if (!cfgRaw) {
|
|
308
|
+
// No config - generate defaults for web routes only
|
|
309
|
+
if (!isApi) {
|
|
310
|
+
configs = [{
|
|
311
|
+
prefix: `/${entityLower}`,
|
|
312
|
+
endpoints: [
|
|
313
|
+
{ path: '/', action: 'list', method: 'GET', view: `${entityLower}List` },
|
|
314
|
+
{ path: '/:id', action: 'get', method: 'GET', view: `${entityLower}Detail` },
|
|
315
|
+
{ path: '/create', action: 'empty', method: 'GET', view: `${entityLower}Create` },
|
|
316
|
+
{ path: '/:id/edit', action: 'get', method: 'GET', view: `${entityLower}Update` }
|
|
317
|
+
]
|
|
318
|
+
}];
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
return '';
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
else if (Array.isArray(cfgRaw)) {
|
|
325
|
+
configs = cfgRaw;
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
configs = [cfgRaw];
|
|
329
|
+
}
|
|
330
|
+
// For web routes, force GET method
|
|
331
|
+
if (!isApi) {
|
|
332
|
+
configs = configs.map(cfg => ({
|
|
333
|
+
...cfg,
|
|
334
|
+
endpoints: (cfg.endpoints || []).map(e => ({ ...e, method: 'GET' }))
|
|
335
|
+
}));
|
|
336
|
+
}
|
|
337
|
+
// Generate methods from all configs
|
|
338
|
+
const allMethods = configs
|
|
339
|
+
.map(cfg => this.generateControllerForModelWithConfig(model, moduleName, moduleConfig, cfg, hasGlobalPermissions, kind))
|
|
340
|
+
.filter(methods => methods.trim() !== '')
|
|
341
|
+
.join('\n\n');
|
|
342
|
+
if (!allMethods.trim()) {
|
|
343
|
+
return '';
|
|
344
|
+
}
|
|
345
|
+
// Use the first config for controller base path (or default)
|
|
346
|
+
const firstConfig = configs[0];
|
|
347
|
+
const controllerBase = ((firstConfig === null || firstConfig === void 0 ? void 0 : firstConfig.prefix) || `/${isApi ? 'api/' : ''}${entityLower}`).replace(/\/$/, '');
|
|
317
348
|
const controllerClass = this.replaceTemplateVars(controllerTemplates_1.controllerTemplates.controllerClass, {
|
|
318
349
|
CONTROLLER_NAME: `${entityName}${isApi ? 'ApiController' : 'WebController'}`,
|
|
319
350
|
ENTITY_NAME: entityName,
|
|
320
351
|
ENTITY_LOWER: entityLower,
|
|
321
352
|
CONTROLLER_BASE: controllerBase,
|
|
322
|
-
CONTROLLER_METHODS:
|
|
353
|
+
CONTROLLER_METHODS: allMethods
|
|
323
354
|
});
|
|
324
355
|
return this.replaceTemplateVars(controllerTemplates_1.controllerFileTemplate, {
|
|
325
356
|
ENTITY_NAME: entityName,
|
|
@@ -4,6 +4,7 @@ interface FieldConfig {
|
|
|
4
4
|
required?: boolean;
|
|
5
5
|
unique?: boolean;
|
|
6
6
|
auto?: boolean;
|
|
7
|
+
displayFields?: string[];
|
|
7
8
|
}
|
|
8
9
|
interface ModelConfig {
|
|
9
10
|
name: string;
|
|
@@ -17,9 +18,15 @@ type AppConfig = {
|
|
|
17
18
|
} | ModuleConfig;
|
|
18
19
|
export declare class DomainModelGenerator {
|
|
19
20
|
private typeMapping;
|
|
21
|
+
private availableModels;
|
|
20
22
|
private getDefaultValue;
|
|
21
23
|
private mapType;
|
|
24
|
+
private setAvailableModels;
|
|
25
|
+
private getRelatedModelImports;
|
|
22
26
|
private generateConstructorParameter;
|
|
27
|
+
private isRelationshipField;
|
|
28
|
+
private getForeignKeyFieldName;
|
|
29
|
+
private generateForeignKeyParameter;
|
|
23
30
|
private generateSetterMethods;
|
|
24
31
|
private sortFieldsByRequired;
|
|
25
32
|
generateModel(modelConfig: ModelConfig): string;
|