@currentjs/gen 0.3.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/CHANGELOG.md +8 -289
  2. package/README.md +623 -427
  3. package/dist/cli.js +2 -1
  4. package/dist/commands/commit.js +25 -42
  5. package/dist/commands/createApp.js +1 -0
  6. package/dist/commands/createModule.js +151 -45
  7. package/dist/commands/diff.js +27 -40
  8. package/dist/commands/generateAll.js +141 -291
  9. package/dist/commands/migrateCommit.js +6 -18
  10. package/dist/commands/migratePush.d.ts +1 -0
  11. package/dist/commands/migratePush.js +135 -0
  12. package/dist/commands/migrateUpdate.d.ts +1 -0
  13. package/dist/commands/migrateUpdate.js +147 -0
  14. package/dist/commands/newGenerateAll.d.ts +4 -0
  15. package/dist/commands/newGenerateAll.js +336 -0
  16. package/dist/generators/controllerGenerator.d.ts +43 -19
  17. package/dist/generators/controllerGenerator.js +547 -329
  18. package/dist/generators/domainLayerGenerator.d.ts +21 -0
  19. package/dist/generators/domainLayerGenerator.js +276 -0
  20. package/dist/generators/dtoGenerator.d.ts +21 -0
  21. package/dist/generators/dtoGenerator.js +518 -0
  22. package/dist/generators/newControllerGenerator.d.ts +55 -0
  23. package/dist/generators/newControllerGenerator.js +644 -0
  24. package/dist/generators/newServiceGenerator.d.ts +19 -0
  25. package/dist/generators/newServiceGenerator.js +266 -0
  26. package/dist/generators/newStoreGenerator.d.ts +39 -0
  27. package/dist/generators/newStoreGenerator.js +408 -0
  28. package/dist/generators/newTemplateGenerator.d.ts +29 -0
  29. package/dist/generators/newTemplateGenerator.js +510 -0
  30. package/dist/generators/serviceGenerator.d.ts +16 -51
  31. package/dist/generators/serviceGenerator.js +167 -586
  32. package/dist/generators/storeGenerator.d.ts +35 -32
  33. package/dist/generators/storeGenerator.js +291 -238
  34. package/dist/generators/storeGeneratorV2.d.ts +31 -0
  35. package/dist/generators/storeGeneratorV2.js +190 -0
  36. package/dist/generators/templateGenerator.d.ts +21 -21
  37. package/dist/generators/templateGenerator.js +393 -268
  38. package/dist/generators/templates/appTemplates.d.ts +3 -1
  39. package/dist/generators/templates/appTemplates.js +15 -10
  40. package/dist/generators/templates/data/appYamlTemplate +5 -2
  41. package/dist/generators/templates/data/cursorRulesTemplate +315 -221
  42. package/dist/generators/templates/data/frontendScriptTemplate +76 -47
  43. package/dist/generators/templates/data/mainViewTemplate +1 -1
  44. package/dist/generators/templates/data/systemTsTemplate +5 -0
  45. package/dist/generators/templates/index.d.ts +0 -3
  46. package/dist/generators/templates/index.js +0 -3
  47. package/dist/generators/templates/newStoreTemplates.d.ts +5 -0
  48. package/dist/generators/templates/newStoreTemplates.js +141 -0
  49. package/dist/generators/templates/storeTemplates.d.ts +1 -5
  50. package/dist/generators/templates/storeTemplates.js +102 -219
  51. package/dist/generators/templates/viewTemplates.js +1 -1
  52. package/dist/generators/useCaseGenerator.d.ts +13 -0
  53. package/dist/generators/useCaseGenerator.js +188 -0
  54. package/dist/types/configTypes.d.ts +148 -0
  55. package/dist/types/configTypes.js +10 -0
  56. package/dist/utils/childEntityUtils.d.ts +18 -0
  57. package/dist/utils/childEntityUtils.js +78 -0
  58. package/dist/utils/commandUtils.d.ts +43 -0
  59. package/dist/utils/commandUtils.js +124 -0
  60. package/dist/utils/commitUtils.d.ts +4 -1
  61. package/dist/utils/constants.d.ts +10 -0
  62. package/dist/utils/constants.js +13 -1
  63. package/dist/utils/diResolver.d.ts +32 -0
  64. package/dist/utils/diResolver.js +204 -0
  65. package/dist/utils/new_parts_of_migrationUtils.d.ts +0 -0
  66. package/dist/utils/new_parts_of_migrationUtils.js +164 -0
  67. package/dist/utils/typeUtils.d.ts +19 -0
  68. package/dist/utils/typeUtils.js +70 -0
  69. package/package.json +7 -3
@@ -38,89 +38,64 @@ const fs = __importStar(require("fs"));
38
38
  const path = __importStar(require("path"));
39
39
  const cliUtils_1 = require("../utils/cliUtils");
40
40
  const yaml_1 = require("yaml");
41
- const domainModelGenerator_1 = require("../generators/domainModelGenerator");
42
- const validationGenerator_1 = require("../generators/validationGenerator");
43
- const serviceGenerator_1 = require("../generators/serviceGenerator");
44
- const controllerGenerator_1 = require("../generators/controllerGenerator");
45
- const storeGenerator_1 = require("../generators/storeGenerator");
46
- const templateGenerator_1 = require("../generators/templateGenerator");
47
41
  const generationRegistry_1 = require("../utils/generationRegistry");
48
42
  const colors_1 = require("../utils/colors");
49
43
  const constants_1 = require("../utils/constants");
44
+ const configTypes_1 = require("../types/configTypes");
45
+ const commandUtils_1 = require("../utils/commandUtils");
46
+ const diResolver_1 = require("../utils/diResolver");
47
+ const appTemplates_1 = require("../generators/templates/appTemplates");
50
48
  async function handleGenerateAll(yamlPathArg, _outArg, moduleName, opts) {
51
- var _a, _b;
49
+ var _a, _b, _c, _d;
52
50
  const appYamlPath = (0, cliUtils_1.resolveYamlPath)(yamlPathArg);
53
51
  (0, generationRegistry_1.initGenerationRegistry)(process.cwd());
54
- const raw = fs.readFileSync(appYamlPath, 'utf8');
55
- const appConfig = (0, yaml_1.parse)(raw);
56
- const modulesList = ((_a = appConfig === null || appConfig === void 0 ? void 0 : appConfig.modules) !== null && _a !== void 0 ? _a : []).map(m => (typeof m === 'string' ? m : m.module));
57
- const providersConfig = appConfig === null || appConfig === void 0 ? void 0 : appConfig.providers;
58
- const databaseProviderName = appConfig === null || appConfig === void 0 ? void 0 : appConfig.database;
59
- const shouldIncludeModule = (moduleYamlRel) => {
60
- if (!moduleName || moduleName === '*')
61
- return true;
62
- const moduleNameLc = moduleName.toLowerCase();
63
- const relNormalized = moduleYamlRel.replace(/\\/g, '/').toLowerCase();
64
- if (relNormalized.endsWith(`/${moduleNameLc}.yaml`))
65
- return true;
66
- const moduleYamlPath = path.isAbsolute(moduleYamlRel)
67
- ? moduleYamlRel
68
- : path.resolve(process.cwd(), moduleYamlRel);
69
- const dirName = path.basename(path.dirname(moduleYamlPath)).toLowerCase();
70
- if (dirName === moduleNameLc)
71
- return true;
72
- // Allow passing a path fragment
73
- if (relNormalized.includes(`/${moduleNameLc}/`) || relNormalized.endsWith(`/${moduleNameLc}`))
74
- return true;
75
- return false;
76
- };
77
- const filteredModules = modulesList.filter(shouldIncludeModule);
78
- if (filteredModules.length === 0) {
52
+ const appConfig = (0, commandUtils_1.loadAppConfig)(appYamlPath);
53
+ const moduleEntries = (0, commandUtils_1.getModuleEntries)(appConfig);
54
+ const providersConfig = appConfig.providers;
55
+ const defaultDatabaseKey = (_a = appConfig.config) === null || _a === void 0 ? void 0 : _a.database;
56
+ const filteredEntries = moduleEntries.filter(entry => (0, commandUtils_1.shouldIncludeModule)(entry.path, moduleName) || (moduleName && entry.name === moduleName));
57
+ if (filteredEntries.length === 0) {
79
58
  // eslint-disable-next-line no-console
80
59
  console.warn(colors_1.colors.yellow(`No modules matched: ${moduleName}`));
81
60
  return;
82
61
  }
83
- const domainGen = new domainModelGenerator_1.DomainModelGenerator();
84
- const valGen = new validationGenerator_1.ValidationGenerator();
85
- const svcGen = new serviceGenerator_1.ServiceGenerator();
86
- const ctrlGen = new controllerGenerator_1.ControllerGenerator();
87
- const storeGen = new storeGenerator_1.StoreGenerator();
88
- const tplGen = new templateGenerator_1.TemplateGenerator();
89
- const initsBySrcDir = new Map();
90
- const moduleConfigByFolder = new Map(); // Cache module configs by folder name
91
- // Run modules sequentially to avoid overlapping interactive prompts
92
- for (const moduleYamlRel of filteredModules) {
93
- const moduleYamlPath = path.isAbsolute(moduleYamlRel)
94
- ? moduleYamlRel
95
- : path.resolve(process.cwd(), moduleYamlRel);
62
+ const { domainGen, dtoGen, useCaseGen, serviceGen, controllerGen, templateGen, storeGen } = (0, commandUtils_1.createGenerators)();
63
+ const moduleScansBySrcDir = new Map();
64
+ // Process each module: generate files and collect module dirs for DI scanning
65
+ for (const entry of filteredEntries) {
66
+ const moduleYamlPath = path.isAbsolute(entry.path)
67
+ ? entry.path
68
+ : path.resolve(process.cwd(), entry.path);
96
69
  if (!fs.existsSync(moduleYamlPath)) {
97
70
  // eslint-disable-next-line no-console
98
71
  console.warn(colors_1.colors.yellow(`Module YAML not found: ${moduleYamlPath}`));
99
72
  continue;
100
73
  }
101
- const moduleDir = path.dirname(moduleYamlPath);
102
- const moduleFolderName = path.basename(moduleDir);
103
- // Parse and cache module config for dependency detection
104
74
  const moduleYamlContent = fs.readFileSync(moduleYamlPath, 'utf8');
105
75
  const moduleConfig = (0, yaml_1.parse)(moduleYamlContent);
106
- moduleConfigByFolder.set(moduleFolderName, moduleConfig);
107
- // Output folders inside module structure
108
- const domainOut = path.join(moduleDir, 'domain', 'entities');
109
- const appOut = path.join(moduleDir, 'application');
110
- const infraOut = path.join(moduleDir, 'infrastructure');
111
- fs.mkdirSync(domainOut, { recursive: true });
112
- fs.mkdirSync(appOut, { recursive: true });
113
- fs.mkdirSync(infraOut, { recursive: true });
114
- // Domain entities
76
+ if (!(0, configTypes_1.isValidModuleConfig)(moduleConfig)) {
77
+ // eslint-disable-next-line no-console
78
+ console.warn(colors_1.colors.yellow(`Skipping ${moduleYamlPath}: not in expected format (missing domain/useCases)`));
79
+ continue;
80
+ }
81
+ const moduleDir = path.dirname(moduleYamlPath);
82
+ // eslint-disable-next-line no-console
83
+ console.log(colors_1.colors.blue(`\nGenerating module: ${path.basename(moduleDir)}`));
84
+ // eslint-disable-next-line no-await-in-loop
85
+ await domainGen.generateAndSaveFiles(moduleYamlPath, moduleDir, opts);
86
+ // eslint-disable-next-line no-await-in-loop
87
+ await dtoGen.generateAndSaveFiles(moduleYamlPath, moduleDir, opts);
88
+ // eslint-disable-next-line no-await-in-loop
89
+ await useCaseGen.generateAndSaveFiles(moduleYamlPath, moduleDir, opts);
90
+ // eslint-disable-next-line no-await-in-loop
91
+ await serviceGen.generateAndSaveFiles(moduleYamlPath, moduleDir, opts);
92
+ // eslint-disable-next-line no-await-in-loop
93
+ await storeGen.generateAndSaveFiles(moduleYamlPath, moduleDir, opts);
115
94
  // eslint-disable-next-line no-await-in-loop
116
- await domainGen.generateAndSaveFiles(moduleYamlPath, domainOut, { force: !!(opts === null || opts === void 0 ? void 0 : opts.force), skipOnConflict: !!(opts === null || opts === void 0 ? void 0 : opts.skip) });
117
- // Generate and save via per-generator write logic
95
+ await controllerGen.generateAndSaveFiles(moduleYamlPath, moduleDir, opts);
118
96
  // eslint-disable-next-line no-await-in-loop
119
- await valGen.generateAndSaveFiles(moduleYamlPath, appOut, { force: !!(opts === null || opts === void 0 ? void 0 : opts.force), skipOnConflict: !!(opts === null || opts === void 0 ? void 0 : opts.skip) });
120
- await svcGen.generateAndSaveFiles(moduleYamlPath, appOut, { force: !!(opts === null || opts === void 0 ? void 0 : opts.force), skipOnConflict: !!(opts === null || opts === void 0 ? void 0 : opts.skip) });
121
- const generatedControllers = await ctrlGen.generateAndSaveFiles(moduleYamlPath, infraOut, { force: !!(opts === null || opts === void 0 ? void 0 : opts.force), skipOnConflict: !!(opts === null || opts === void 0 ? void 0 : opts.skip) });
122
- await tplGen.generateAndSaveFiles(moduleYamlPath, undefined, { force: !!(opts === null || opts === void 0 ? void 0 : opts.force), skipOnConflict: !!(opts === null || opts === void 0 ? void 0 : opts.skip) });
123
- // Find nearest ancestor containing src/app.ts and collect controller inits for a single write later
97
+ await templateGen.generateAndSaveFiles(moduleYamlPath, moduleDir, opts);
98
+ // Find srcDir by probing upward for app.ts
124
99
  let probeDir = moduleDir;
125
100
  let srcDir = null;
126
101
  for (let i = 0; i < 6; i += 1) {
@@ -136,149 +111,46 @@ async function handleGenerateAll(yamlPathArg, _outArg, moduleName, opts) {
136
111
  }
137
112
  if (!srcDir)
138
113
  continue;
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
184
- for (const filePath of generatedControllers) {
185
- const rel = path
186
- .relative(srcDir, filePath)
187
- .replace(/\\/g, '/')
188
- .replace(/\.ts$/, '');
189
- const ctrlName = path.basename(rel);
190
- const importPath = rel.startsWith('.') ? rel : `./${rel}`;
191
- const baseEntityName = ctrlName.endsWith('ApiController')
192
- ? ctrlName.slice(0, -'ApiController'.length)
193
- : ctrlName.endsWith('WebController')
194
- ? ctrlName.slice(0, -'WebController'.length)
195
- : ctrlName.replace('Controller', '');
196
- const entityName = baseEntityName;
197
- const entityVar = entityName.charAt(0).toLowerCase() + entityName.slice(1);
198
- const m = rel.match(/modules\/([^/]+)\/infrastructure\/controllers\//);
199
- const moduleFolder = m ? m[1] : undefined;
200
- if (!moduleFolder)
201
- continue;
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}`;
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}`;
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
- }
250
- }
251
- initsBySrcDir.set(srcDir, list);
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) });
114
+ const scans = (_b = moduleScansBySrcDir.get(srcDir)) !== null && _b !== void 0 ? _b : [];
115
+ scans.push({ moduleDir, databaseKey: entry.database });
116
+ moduleScansBySrcDir.set(srcDir, scans);
253
117
  }
254
- // Single write per app: inject imports and rewrite block between markers
255
- let isIProviderImported = false;
256
- for (const [srcDir, controllerInits] of initsBySrcDir.entries()) {
118
+ // Update app.ts files with DI-based wiring
119
+ for (const [srcDir, moduleScans] of moduleScansBySrcDir.entries()) {
257
120
  try {
258
121
  const appTsPath = path.join(srcDir, constants_1.COMMON_FILES.APP_TS);
259
122
  if (!fs.existsSync(appTsPath))
260
123
  continue;
124
+ // Ensure system.ts exists (for existing apps created before this feature)
125
+ const systemTsPath = path.join(srcDir, 'system.ts');
126
+ if (!fs.existsSync(systemTsPath)) {
127
+ fs.writeFileSync(systemTsPath, appTemplates_1.systemTsTemplate, 'utf8');
128
+ // eslint-disable-next-line no-console
129
+ console.log(colors_1.colors.green(`Created ${systemTsPath}`));
130
+ }
261
131
  let appTs = fs.readFileSync(appTsPath, 'utf8');
262
- // Build providers import and initialization from app.yaml providers section
132
+ // --- Scan all modules for @Injectable and @Controller classes ---
133
+ const allClasses = [];
134
+ for (const scan of moduleScans) {
135
+ const classes = (0, diResolver_1.scanModuleClasses)(scan.moduleDir);
136
+ allClasses.push(...classes);
137
+ }
138
+ // --- Build provider info ---
263
139
  const importLines = [];
264
140
  const providerInitLines = [];
265
141
  const providersArrayEntries = [];
142
+ let isIProviderImported = false;
266
143
  if (providersConfig && Object.keys(providersConfig).length > 0) {
267
144
  for (const [provName, mod] of Object.entries(providersConfig)) {
268
145
  if (!mod)
269
146
  continue;
270
- // Assume default import name from module spec after last '/'
271
- const baseName = mod.split('/').pop() || 'provider';
272
- const className = baseName
273
- .replace(/^[^a-zA-Z_]*/g, '')
274
- .split(/[-_]/)
275
- .map(s => s.charAt(0).toUpperCase() + s.slice(1))
276
- .join('');
277
- importLines.push(`import { ${className}${!isIProviderImported ? ', IProvider, ISqlProvider' : ''} } from '${mod}';`);
147
+ const resolved = (0, diResolver_1.resolveProviderImport)(mod, srcDir);
148
+ if (!appTs.includes(`from '${resolved.importPath}'`)) {
149
+ importLines.push(`import { ${resolved.className}${!isIProviderImported ? ', IProvider, ISqlProvider' : ''} } from '${resolved.importPath}';`);
150
+ }
278
151
  if (!isIProviderImported)
279
152
  isIProviderImported = true;
280
- // Read provider configuration from env by name, parse JSON if possible, pass as is
281
- providerInitLines.push(` ${provName}: new ${className}((() => {
153
+ providerInitLines.push(` ${provName}: new ${resolved.className}((() => {
282
154
  const raw = process.env.${provName.toUpperCase()} || '';
283
155
  try { return raw ? JSON.parse(raw) : undefined; } catch { return raw; }
284
156
  })())`);
@@ -286,42 +158,62 @@ async function handleGenerateAll(yamlPathArg, _outArg, moduleName, opts) {
286
158
  }
287
159
  }
288
160
  else {
289
- // Fallback to MySQL provider if no providers configured
290
- importLines.push(`import { ProviderMysql, ISqlProvider } from '@currentjs/provider-mysql';`);
161
+ if (!appTs.includes("from '@currentjs/provider-mysql'")) {
162
+ importLines.push(`import { ProviderMysql, IProvider, ISqlProvider } from '@currentjs/provider-mysql';`);
163
+ }
291
164
  providerInitLines.push(` mysql: new ProviderMysql((() => {
292
165
  const raw = process.env.MYSQL || '';
293
166
  try { return raw ? JSON.parse(raw) : undefined; } catch { return raw; }
294
167
  })())`);
295
168
  providersArrayEntries.push('mysql');
296
169
  }
297
- const ensureDbLine = `const providers: Record<string, IProvider> = {\n${providerInitLines.join(',\n')}\n};\nconst db = providers['${databaseProviderName || providersArrayEntries[0]}'] as ISqlProvider;`;
298
- // Ensure router import for server (app template already imports templating)
299
- if (!appTs.includes("from '@currentjs/router'")) {
300
- importLines.push(`import { createWebServer, createStaticServer } from '@currentjs/router';`);
301
- }
302
- for (const il of importLines) {
303
- if (!appTs.includes(il)) {
304
- appTs = il + '\n' + appTs;
170
+ // Resolve database keys to provider variable names
171
+ const providerKeysSet = new Set(providersArrayEntries);
172
+ const allDatabaseKeys = [...new Set(moduleScans.map(s => s.databaseKey))];
173
+ const dbVarByKey = {};
174
+ const dbLines = [`const providers: Record<string, IProvider> = {\n${providerInitLines.join(',\n')}\n};`];
175
+ const emittedProviderKeys = new Set();
176
+ for (const key of allDatabaseKeys) {
177
+ const resolvedKey = providerKeysSet.has(key)
178
+ ? key
179
+ : (defaultDatabaseKey && providerKeysSet.has(defaultDatabaseKey) ? defaultDatabaseKey : providersArrayEntries[0]);
180
+ if (!providerKeysSet.has(key)) {
181
+ // eslint-disable-next-line no-console
182
+ console.warn(colors_1.colors.yellow(`Module uses database '${key}' which is not in providers; using '${resolvedKey}'`));
305
183
  }
306
- }
307
- // Ensure MySQL provider import exists
308
- if (!appTs.includes("from '@currentjs/provider-mysql'")) {
309
- importLines.push(`import { ProviderMysql } from '@currentjs/provider-mysql';`);
310
- }
311
- for (const init of controllerInits) {
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);
184
+ const varName = 'db' + (resolvedKey.charAt(0).toUpperCase() + resolvedKey.slice(1));
185
+ dbVarByKey[key] = varName;
186
+ if (!emittedProviderKeys.has(resolvedKey)) {
187
+ emittedProviderKeys.add(resolvedKey);
188
+ dbLines.push(`const ${varName} = providers['${resolvedKey}'] as ISqlProvider;`);
316
189
  }
317
- if (init.importAuth)
318
- maybe.push(init.importAuth);
319
- for (const line of maybe) {
320
- if (line && line.trim() !== '' && !appTs.includes(line) && !importLines.includes(line)) {
321
- importLines.push(line);
190
+ }
191
+ // --- Build DI instantiation order ---
192
+ const providerVarByType = new Map();
193
+ providerVarByType.set('ISqlProvider', (_c = dbVarByKey[allDatabaseKeys[0]]) !== null && _c !== void 0 ? _c : 'dbMysql');
194
+ // Per-class provider var: map each class to its module's database variable
195
+ const classProviderVar = new Map();
196
+ for (const scan of moduleScans) {
197
+ const dbVar = (_d = dbVarByKey[scan.databaseKey]) !== null && _d !== void 0 ? _d : dbVarByKey[Object.keys(dbVarByKey)[0]];
198
+ const classes = allClasses.filter(c => c.filePath.startsWith(scan.moduleDir + path.sep));
199
+ for (const cls of classes) {
200
+ const hasProviderParam = cls.constructorParams.some(p => providerVarByType.has(p.type));
201
+ if (hasProviderParam) {
202
+ classProviderVar.set(cls.className, dbVar);
322
203
  }
323
204
  }
324
205
  }
206
+ const steps = (0, diResolver_1.buildInstantiationOrder)(allClasses, providerVarByType, classProviderVar, srcDir);
207
+ // --- Generate import lines for all discovered classes ---
208
+ for (const step of steps) {
209
+ const importLine = `import { ${step.className} } from '${step.importPath}';`;
210
+ if (!appTs.includes(importLine) && !importLines.includes(importLine)) {
211
+ importLines.push(importLine);
212
+ }
213
+ }
214
+ if (!appTs.includes("from '@currentjs/router'")) {
215
+ importLines.push(`import { createWebServer } from '@currentjs/router';`);
216
+ }
325
217
  if (importLines.length) {
326
218
  const existingImports = new Set();
327
219
  const importRegex = /^import[^;]+;$/gm;
@@ -331,63 +223,27 @@ async function handleGenerateAll(yamlPathArg, _outArg, moduleName, opts) {
331
223
  if (toAdd.length)
332
224
  appTs = toAdd.join('\n') + '\n' + appTs;
333
225
  }
334
- // Compose fresh block content with dependency-aware ordering
226
+ // --- Build wiring block ---
335
227
  const wiringLines = [];
336
- // DB placeholder once
337
- wiringLines.push(ensureDbLine);
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
- }
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(', ')});`);
228
+ wiringLines.push(dbLines.join('\n'));
229
+ const nonControllers = steps.filter(s => !s.isController);
230
+ const controllers = steps.filter(s => s.isController);
231
+ for (const step of nonControllers) {
232
+ const args = step.constructorArgs.length > 0 ? step.constructorArgs.join(', ') : '';
233
+ wiringLines.push(`const ${step.varName} = new ${step.className}(${args});`);
381
234
  }
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);
386
235
  wiringLines.push('const controllers = [');
387
- if (registrations.length > 0) {
388
- wiringLines.push(` ${registrations.join(',\n ')}`);
236
+ if (controllers.length > 0) {
237
+ const registrations = controllers
238
+ .map(s => {
239
+ const args = s.constructorArgs.length > 0 ? s.constructorArgs.join(', ') : '';
240
+ return ` new ${s.className}(${args}),`;
241
+ })
242
+ .join('\n');
243
+ wiringLines.push(registrations);
389
244
  }
390
245
  wiringLines.push('];');
246
+ // Replace content between markers
391
247
  const startMarker = constants_1.GENERATOR_MARKERS.CONTROLLERS_START;
392
248
  const endMarker = constants_1.GENERATOR_MARKERS.CONTROLLERS_END;
393
249
  const startIdx = appTs.indexOf(startMarker);
@@ -398,35 +254,29 @@ async function handleGenerateAll(yamlPathArg, _outArg, moduleName, opts) {
398
254
  const block = '\n' + wiringLines.join('\n') + '\n';
399
255
  appTs = before + block + after;
400
256
  }
401
- // Ensure the createWebServer call includes renderer in options
402
- // Case 1: createWebServer(controllers, { ... })
403
- const withOptionsRegex = /createWebServer\(\s*controllers\s*,\s*\{([\s\S]*?)\}\s*\)/m;
404
- if (withOptionsRegex.test(appTs)) {
405
- appTs = appTs.replace(withOptionsRegex, (full, inner) => {
406
- if (/\brenderer\b\s*:/.test(inner))
407
- return full; // already present
408
- const trimmed = inner.trim();
409
- const prefix = trimmed.length ? inner.replace(trimmed, '') : '';
410
- const suffix = inner.endsWith(trimmed) ? '' : inner.slice(inner.indexOf(trimmed) + trimmed.length);
411
- const sep = trimmed.length ? ', ' : '';
412
- return full.replace(inner, `${prefix}${trimmed}${sep}renderer` + `: renderer${suffix}`);
413
- });
414
- }
415
- else {
416
- // Case 2: createWebServer(controllers)
417
- const noOptionsRegex = /createWebServer\(\s*controllers\s*\)/m;
418
- if (noOptionsRegex.test(appTs)) {
419
- appTs = appTs.replace(noOptionsRegex, 'createWebServer(controllers, { renderer })');
257
+ // Deduplicate import lines across the entire file
258
+ const lines = appTs.split('\n');
259
+ const seenImports = new Set();
260
+ const deduped = lines.filter(line => {
261
+ const trimmed = line.trim();
262
+ if (/^import\s+/.test(trimmed) && trimmed.endsWith(';')) {
263
+ if (seenImports.has(trimmed))
264
+ return false;
265
+ seenImports.add(trimmed);
420
266
  }
421
- }
267
+ return true;
268
+ });
269
+ appTs = deduped.join('\n');
422
270
  fs.writeFileSync(appTsPath, appTs, 'utf8');
271
+ // eslint-disable-next-line no-console
272
+ console.log(colors_1.colors.green(`Updated ${appTsPath}`));
423
273
  }
424
274
  catch (e) {
425
275
  // eslint-disable-next-line no-console
426
- console.warn(colors_1.colors.yellow(`Could not update app.ts with controllers: ${e instanceof Error ? e.message : String(e)}`));
276
+ console.warn(colors_1.colors.yellow(`Could not update app.ts: ${e instanceof Error ? e.message : String(e)}`));
427
277
  }
428
278
  }
429
- // Run npm run build
279
+ // Run build
430
280
  (0, cliUtils_1.runCommand)('npm run build', {
431
281
  infoMessage: '\nBuilding...',
432
282
  successMessage: '[v] Build completed successfully',
@@ -51,12 +51,13 @@ function collectModelsFromYaml(yamlPath) {
51
51
  allModels.push(...config.models);
52
52
  sources.push(`app.yaml (${config.models.length} model(s))`);
53
53
  }
54
- // Check if it's an app YAML (has modules as array of paths)
55
- if (config.modules && Array.isArray(config.modules)) {
54
+ // App YAML: modules as Record<string, { path }> resolve path and read module YAML for .models
55
+ if (config.modules && typeof config.modules === 'object' && !Array.isArray(config.modules)) {
56
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;
57
+ for (const entry of Object.values(config.modules)) {
58
+ const modulePath = entry.path;
59
+ if (!modulePath)
60
+ continue;
60
61
  const moduleYamlPath = path.isAbsolute(modulePath)
61
62
  ? modulePath
62
63
  : path.resolve(projectRoot, modulePath);
@@ -68,20 +69,7 @@ function collectModelsFromYaml(yamlPath) {
68
69
  moduleCount++;
69
70
  }
70
71
  }
71
- });
72
- if (moduleCount > 0) {
73
- sources.push(`app.yaml modules section (${moduleCount} module(s))`);
74
72
  }
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
73
  if (moduleCount > 0) {
86
74
  sources.push(`app.yaml modules section (${moduleCount} module(s))`);
87
75
  }
@@ -0,0 +1 @@
1
+ export declare function handleMigratePush(yamlPath?: string): Promise<void>;