stackkit 0.1.3 → 0.1.5

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 (43) hide show
  1. package/README.md +5 -3
  2. package/dist/cli/add.d.ts +2 -1
  3. package/dist/cli/add.js +325 -102
  4. package/dist/cli/create.d.ts +2 -2
  5. package/dist/cli/create.js +96 -30
  6. package/dist/cli/doctor.js +25 -17
  7. package/dist/cli/list.js +14 -2
  8. package/dist/index.js +22 -3
  9. package/dist/lib/conversion/js-conversion.js +2 -2
  10. package/dist/lib/discovery/module-discovery.d.ts +0 -1
  11. package/dist/lib/discovery/module-discovery.js +35 -35
  12. package/dist/lib/env/env-editor.js +1 -1
  13. package/dist/lib/framework/framework-utils.d.ts +1 -1
  14. package/dist/lib/framework/framework-utils.js +3 -3
  15. package/dist/lib/generation/code-generator.d.ts +18 -4
  16. package/dist/lib/generation/code-generator.js +212 -147
  17. package/dist/lib/generation/generator-utils.d.ts +11 -0
  18. package/dist/lib/generation/generator-utils.js +124 -0
  19. package/dist/lib/git-utils.js +20 -0
  20. package/dist/lib/project/detect.js +2 -4
  21. package/dist/lib/utils/package-root.js +2 -2
  22. package/dist/types/index.d.ts +0 -10
  23. package/modules/auth/authjs/generator.json +10 -0
  24. package/modules/auth/authjs/module.json +0 -9
  25. package/modules/auth/better-auth/files/lib/auth.ts +38 -31
  26. package/modules/auth/better-auth/files/prisma/schema.prisma +3 -3
  27. package/modules/auth/better-auth/generator.json +58 -28
  28. package/modules/auth/better-auth/module.json +0 -24
  29. package/modules/database/mongoose/files/models/health.ts +34 -0
  30. package/modules/database/mongoose/generator.json +27 -8
  31. package/modules/database/mongoose/module.json +1 -2
  32. package/modules/database/prisma/files/lib/prisma.ts +27 -21
  33. package/modules/database/prisma/files/prisma.config.ts +17 -0
  34. package/modules/database/prisma/generator.json +79 -15
  35. package/modules/database/prisma/module.json +1 -4
  36. package/package.json +1 -1
  37. package/templates/express/src/server.ts +9 -3
  38. package/templates/express/tsconfig.json +2 -23
  39. package/templates/nextjs/lib/env.ts +1 -1
  40. package/dist/lib/database/database-config.d.ts +0 -6
  41. package/dist/lib/database/database-config.js +0 -9
  42. package/modules/database/mongoose/files/models/User.ts +0 -34
  43. /package/modules/database/mongoose/files/lib/{db.ts → mongoose.ts} +0 -0
@@ -37,6 +37,7 @@ exports.AdvancedCodeGenerator = void 0;
37
37
  const fs = __importStar(require("fs-extra"));
38
38
  const path = __importStar(require("path"));
39
39
  const package_root_1 = require("../utils/package-root");
40
+ const generator_utils_1 = require("./generator-utils");
40
41
  class AdvancedCodeGenerator {
41
42
  constructor(frameworkConfig) {
42
43
  this.generators = new Map();
@@ -44,28 +45,18 @@ class AdvancedCodeGenerator {
44
45
  this.frameworkConfig = frameworkConfig;
45
46
  }
46
47
  async loadGenerators(modulesPath) {
47
- const moduleTypes = ['auth', 'database'];
48
+ const moduleTypes = ["auth", "database"];
48
49
  for (const type of moduleTypes) {
49
50
  const typePath = path.join(modulesPath, type);
50
51
  if (await fs.pathExists(typePath)) {
51
52
  const modules = await fs.readdir(typePath);
52
53
  for (const moduleName of modules) {
53
- const generatorPath = path.join(typePath, moduleName, 'generator.json');
54
+ const generatorPath = path.join(typePath, moduleName, "generator.json");
54
55
  if (await fs.pathExists(generatorPath)) {
55
56
  try {
56
57
  const config = await fs.readJson(generatorPath);
57
- const modulePath = path.join(typePath, moduleName, 'module.json');
58
- if (await fs.pathExists(modulePath)) {
59
- try {
60
- const moduleConfig = await fs.readJson(modulePath);
61
- if (moduleConfig.postInstall && Array.isArray(moduleConfig.postInstall)) {
62
- config.postInstall = moduleConfig.postInstall;
63
- }
64
- }
65
- catch {
66
- // Silently skip if module.json is invalid
67
- }
68
- }
58
+ const modulePath = path.join(typePath, moduleName);
59
+ await (0, generator_utils_1.mergeModuleIntoGeneratorConfig)(config, modulePath);
69
60
  this.generators.set(`${type}:${moduleName}`, config);
70
61
  }
71
62
  catch {
@@ -80,10 +71,10 @@ class AdvancedCodeGenerator {
80
71
  if (!condition)
81
72
  return true;
82
73
  for (const [key, value] of Object.entries(condition)) {
83
- if (key === 'features') {
74
+ if (key === "features") {
84
75
  const requiredFeatures = value;
85
76
  const contextFeatures = context.features || [];
86
- if (!requiredFeatures.every(feature => contextFeatures.includes(feature))) {
77
+ if (!requiredFeatures.every((feature) => contextFeatures.includes(feature))) {
87
78
  return false;
88
79
  }
89
80
  }
@@ -110,19 +101,19 @@ class AdvancedCodeGenerator {
110
101
  // Process the rest of the template with the extended context
111
102
  content = this.processTemplateRecursive(content, templateContext);
112
103
  // Remove leading newlines that might be left from {{#var}} removal
113
- content = content.replace(/^\n+/, '');
104
+ content = content.replace(/^\n+/, "");
114
105
  // Reduce multiple consecutive newlines to maximum 2
115
- content = content.replace(/\n{3,}/g, '\n\n');
106
+ content = content.replace(/\n{3,}/g, "\n\n");
116
107
  return content;
117
108
  }
118
109
  processVariableDefinitions(content, context) {
119
110
  let result = content;
120
111
  let index = 0;
121
112
  while (true) {
122
- const varStart = result.indexOf('{{#var ', index);
113
+ const varStart = result.indexOf("{{#var ", index);
123
114
  if (varStart === -1)
124
115
  break;
125
- const equalsIndex = result.indexOf('=', varStart);
116
+ const equalsIndex = result.indexOf("=", varStart);
126
117
  if (equalsIndex === -1)
127
118
  break;
128
119
  const varNameMatch = result.substring(varStart + 7, equalsIndex).trim();
@@ -134,11 +125,11 @@ class AdvancedCodeGenerator {
134
125
  const valueStart = equalsIndex + 1;
135
126
  let valueEnd = valueStart;
136
127
  for (let i = valueStart; i < result.length; i++) {
137
- if (result[i] === '{' && result[i + 1] === '{') {
128
+ if (result[i] === "{" && result[i + 1] === "{") {
138
129
  braceCount++;
139
130
  i++; // Skip next character
140
131
  }
141
- else if (result[i] === '}' && result[i + 1] === '}') {
132
+ else if (result[i] === "}" && result[i + 1] === "}") {
142
133
  braceCount--;
143
134
  if (braceCount === 0) {
144
135
  valueEnd = i;
@@ -155,71 +146,74 @@ class AdvancedCodeGenerator {
155
146
  const processedValue = this.processTemplateRecursive(varValue, context);
156
147
  context[varNameMatch] = processedValue;
157
148
  // Remove the variable definition
158
- result = result.replace(fullMatch, '');
149
+ result = result.replace(fullMatch, "");
159
150
  index = varStart;
160
151
  }
161
152
  return result;
162
153
  }
163
154
  processTemplateRecursive(content, context) {
164
- content = content.replace(/\{\{#if\s+([^}\s]+)\s+([^}\s]+)\s+([^}]+)\}\}([\s\S]*?)\{\{\/if\}\}/g, (match, varName, operator, expectedValue, blockContent) => {
155
+ content = content.replace(/\{\{#if\s+([^}\s]+)\s+([^}\s]+)\s+([^}]+)\}\}([\s\S]*?)(?:\{\{else\}\}([\s\S]*?))?\{\{\/if\}\}/g, (match, varName, operator, expectedValue, blockContent, elseContent) => {
165
156
  const actualVal = context[varName.trim()];
166
- const cleanExpectedVal = expectedValue.trim().replace(/['"]/g, '');
157
+ const cleanExpectedVal = expectedValue.trim().replace(/['"]/g, "");
167
158
  let conditionMet = false;
168
159
  switch (operator) {
169
- case '==':
170
- case '===':
160
+ case "==":
161
+ case "===":
171
162
  conditionMet = actualVal === cleanExpectedVal;
172
163
  break;
173
- case '!=':
174
- case '!==':
164
+ case "!=":
165
+ case "!==":
175
166
  conditionMet = actualVal !== cleanExpectedVal;
176
167
  break;
177
- case 'includes':
168
+ case "includes":
178
169
  conditionMet = Array.isArray(actualVal) && actualVal.includes(cleanExpectedVal);
179
170
  break;
180
- case 'startsWith':
181
- conditionMet = typeof actualVal === 'string' && actualVal.startsWith(cleanExpectedVal);
171
+ case "startsWith":
172
+ conditionMet = typeof actualVal === "string" && actualVal.startsWith(cleanExpectedVal);
182
173
  break;
183
- case 'endsWith':
184
- conditionMet = typeof actualVal === 'string' && actualVal.endsWith(cleanExpectedVal);
174
+ case "endsWith":
175
+ conditionMet = typeof actualVal === "string" && actualVal.endsWith(cleanExpectedVal);
185
176
  break;
186
177
  }
187
- return conditionMet ? this.processTemplateRecursive(blockContent, context).replace(/^\n+/, '').replace(/\n+$/, '') : '';
178
+ const contentToProcess = conditionMet ? blockContent : (elseContent || "");
179
+ return this.processTemplateRecursive(contentToProcess, context)
180
+ .replace(/^\n+/, "")
181
+ .replace(/\n+$/, "");
188
182
  });
189
183
  // Handle simple conditional blocks {{#if condition}}...{{/if}} (backward compatibility)
190
- content = content.replace(/\{\{#if\s+([^}]+)\}\}([\s\S]*?)\{\{\/if\}\}/g, (match, condition, blockContent) => {
191
- const conditionParts = condition.split('==');
184
+ content = content.replace(/\{\{#if\s+([^}]+)\}\}([\s\S]*?)(?:\{\{else\}\}([\s\S]*?))?\{\{\/if\}\}/g, (match, condition, blockContent, elseContent) => {
185
+ const conditionParts = condition.split("==");
192
186
  if (conditionParts.length === 2) {
193
- const [varName, expectedValue] = conditionParts.map((s) => s.trim().replace(/['"]/g, ''));
194
- if (context[varName] === expectedValue) {
195
- return this.processTemplateRecursive(blockContent, context).replace(/^\n+/, '').replace(/\n+$/, '');
196
- }
197
- return '';
187
+ const [varName, expectedValue] = conditionParts.map((s) => s.trim().replace(/['"]/g, ""));
188
+ const contentToProcess = context[varName] === expectedValue ? blockContent : (elseContent || "");
189
+ return this.processTemplateRecursive(contentToProcess, context)
190
+ .replace(/^\n+/, "")
191
+ .replace(/\n+$/, "");
198
192
  }
199
- const conditionFunc = condition.split('.');
200
- if (conditionFunc.length === 2 && conditionFunc[1] === 'includes') {
201
- const [arrayName, item] = conditionFunc[0].split('(');
202
- const itemValue = item.replace(')', '').replace(/['"]/g, '');
193
+ const conditionFunc = condition.split(".");
194
+ if (conditionFunc.length === 2 && conditionFunc[1] === "includes") {
195
+ const [arrayName, item] = conditionFunc[0].split("(");
196
+ const itemValue = item.replace(")", "").replace(/['"]/g, "");
203
197
  const array = context[arrayName] || [];
204
- if (Array.isArray(array) && array.includes(itemValue)) {
205
- return this.processTemplateRecursive(blockContent, context).replace(/^\n+/, '').replace(/\n+$/, '');
206
- }
207
- return '';
198
+ const contentToProcess = Array.isArray(array) && array.includes(itemValue) ? blockContent : (elseContent || "");
199
+ return this.processTemplateRecursive(contentToProcess, context)
200
+ .replace(/^\n+/, "")
201
+ .replace(/\n+$/, "");
208
202
  }
209
- return '';
203
+ return "";
210
204
  });
211
205
  // Handle switch statements {{#switch variable}}...{{/switch}}
212
206
  content = content.replace(/\{\{#switch\s+([^}]+)\}\}([\s\S]*?)\{\{\/switch\}\}/g, (match, varName, switchContent) => {
213
207
  const actualVal = context[varName.trim()];
214
208
  // Parse cases
215
- const caseRegex = /\{\{#case\s+([^}]+)\}\}([\s\S]*?)(?=\{\{#case|\{\{\/switch\})/g;
216
- let result = '';
217
- let defaultCase = '';
209
+ const caseRegex = /\{\{#case\s+([^}]+)\}\}([\s\S]*?)(?=\{\{#case|\{\{\/case\}|\{\{\/switch\})/g;
210
+ let result = "";
211
+ let defaultCase = "";
218
212
  let caseMatch;
219
213
  while ((caseMatch = caseRegex.exec(switchContent)) !== null) {
220
214
  const [, caseValue, caseContent] = caseMatch;
221
- const cleanCaseValue = caseValue.trim().replace(/['"]/g, '');
222
- if (cleanCaseValue === 'default') {
215
+ const cleanCaseValue = caseValue.trim().replace(/['"]/g, "");
216
+ if (cleanCaseValue === "default") {
223
217
  defaultCase = caseContent;
224
218
  }
225
219
  else if (actualVal === cleanCaseValue) {
@@ -227,7 +221,7 @@ class AdvancedCodeGenerator {
227
221
  break;
228
222
  }
229
223
  }
230
- return result || defaultCase || '';
224
+ return result || defaultCase || "";
231
225
  });
232
226
  // Handle variable replacement with advanced expressions
233
227
  content = content.replace(/\{\{([^}]+)\}\}/g, (match, varExpr) => {
@@ -240,10 +234,10 @@ class AdvancedCodeGenerator {
240
234
  if (conditionMatch) {
241
235
  const [, varName, expectedVal] = conditionMatch;
242
236
  const cleanVarName = varName.trim();
243
- const cleanExpectedVal = expectedVal.trim().replace(/['"]/g, '');
237
+ const cleanExpectedVal = expectedVal.trim().replace(/['"]/g, "");
244
238
  const actualVal = context[cleanVarName];
245
239
  const result = actualVal === cleanExpectedVal ? trueVal.trim() : falseVal.trim();
246
- return result.replace(/['"]/g, ''); // Remove quotes from result
240
+ return result.replace(/['"]/g, ""); // Remove quotes from result
247
241
  }
248
242
  }
249
243
  // Handle switch expressions {{switch variable case1: value1, case2: value2, default: defaultValue}}
@@ -251,21 +245,21 @@ class AdvancedCodeGenerator {
251
245
  if (switchMatch) {
252
246
  const [, varName, casesStr] = switchMatch;
253
247
  const actualVal = context[varName.trim()];
254
- const cases = casesStr.split(',').map((c) => c.trim());
248
+ const cases = casesStr.split(",").map((c) => c.trim());
255
249
  for (const caseStr of cases) {
256
- const [caseVal, result] = caseStr.split(':').map((s) => s.trim());
257
- const cleanCaseVal = caseVal.replace(/['"]/g, '');
258
- if (cleanCaseVal === actualVal || cleanCaseVal === 'default') {
259
- return result.replace(/['"]/g, '');
250
+ const [caseVal, result] = caseStr.split(":").map((s) => s.trim());
251
+ const cleanCaseVal = caseVal.replace(/['"]/g, "");
252
+ if (cleanCaseVal === actualVal || cleanCaseVal === "default") {
253
+ return result.replace(/['"]/g, "");
260
254
  }
261
255
  }
262
- return '';
256
+ return "";
263
257
  }
264
258
  // Handle feature flags {{feature:name}}
265
- if (trimmedExpr.startsWith('feature:')) {
259
+ if (trimmedExpr.startsWith("feature:")) {
266
260
  const featureName = trimmedExpr.substring(8);
267
261
  const features = context.features || [];
268
- return features.includes(featureName) ? 'true' : 'false';
262
+ return features.includes(featureName) ? "true" : "false";
269
263
  }
270
264
  // Handle conditional expressions {{if condition then:value else:value}}
271
265
  const conditionalMatch = trimmedExpr.match(/^if\s+(.+?)\s+then:([^,]+),\s*else:(.+)$/);
@@ -275,10 +269,10 @@ class AdvancedCodeGenerator {
275
269
  if (conditionMatch2) {
276
270
  const [, varName, expectedVal] = conditionMatch2;
277
271
  const cleanVarName = varName.trim();
278
- const cleanExpectedVal = expectedVal.trim().replace(/['"]/g, '');
272
+ const cleanExpectedVal = expectedVal.trim().replace(/['"]/g, "");
279
273
  const actualVal = context[cleanVarName];
280
274
  const result = actualVal === cleanExpectedVal ? thenVal.trim() : elseVal.trim();
281
- return result.replace(/['"]/g, '');
275
+ return result.replace(/['"]/g, "");
282
276
  }
283
277
  }
284
278
  // Simple variable replacement
@@ -295,21 +289,21 @@ class AdvancedCodeGenerator {
295
289
  features,
296
290
  };
297
291
  // Set default prismaProvider if database is prisma but no provider specified
298
- if (selectedModules.database === 'prisma' && !context.prismaProvider) {
299
- context.prismaProvider = 'postgresql';
292
+ if (selectedModules.database === "prisma" && !context.prismaProvider) {
293
+ context.prismaProvider = "postgresql";
300
294
  }
301
295
  // Collect all applicable operations
302
296
  const applicableOperations = [];
303
297
  for (const [key, generator] of this.generators) {
304
- const [genType, name] = key.split(':');
298
+ const [genType, name] = key.split(":");
305
299
  // Check if this generator is selected
306
- if (genType === 'framework' && name === selectedModules.framework) {
300
+ if (genType === "framework" && name === selectedModules.framework) {
307
301
  // Framework is always included
308
302
  }
309
- else if (genType === 'database' && name === selectedModules.database) {
303
+ else if (genType === "database" && name === selectedModules.database) {
310
304
  // Database is selected
311
305
  }
312
- else if (genType === 'auth' && name === selectedModules.auth) {
306
+ else if (genType === "auth" && name === selectedModules.auth) {
313
307
  // Auth is selected
314
308
  }
315
309
  else {
@@ -350,22 +344,22 @@ class AdvancedCodeGenerator {
350
344
  // Process templates in operation content
351
345
  const processedOperation = this.processOperationTemplates(operation, context);
352
346
  switch (processedOperation.type) {
353
- case 'create-file':
347
+ case "create-file":
354
348
  await this.executeCreateFile(processedOperation, context, outputPath);
355
349
  break;
356
- case 'patch-file':
350
+ case "patch-file":
357
351
  await this.executePatchFile(processedOperation, context, outputPath);
358
352
  break;
359
- case 'add-dependency':
353
+ case "add-dependency":
360
354
  await this.executeAddDependency(processedOperation, context, outputPath);
361
355
  break;
362
- case 'add-script':
356
+ case "add-script":
363
357
  await this.executeAddScript(processedOperation, context, outputPath);
364
358
  break;
365
- case 'add-env':
359
+ case "add-env":
366
360
  await this.executeAddEnv(processedOperation, context, outputPath);
367
361
  break;
368
- case 'run-command':
362
+ case "run-command":
369
363
  this.executeRunCommand(processedOperation, context);
370
364
  break;
371
365
  default:
@@ -374,15 +368,15 @@ class AdvancedCodeGenerator {
374
368
  }
375
369
  async copyTemplate(frameworkName, outputPath) {
376
370
  const packageRoot = (0, package_root_1.getPackageRoot)();
377
- const templatePath = path.join(packageRoot, 'templates', frameworkName);
371
+ const templatePath = path.join(packageRoot, "templates", frameworkName);
378
372
  if (await fs.pathExists(templatePath)) {
379
373
  await fs.copy(templatePath, outputPath, {
380
374
  filter: (src) => {
381
375
  const relativePath = path.relative(templatePath, src);
382
- return relativePath !== 'template.json' &&
383
- relativePath !== 'node_modules' &&
384
- !relativePath.startsWith('node_modules/');
385
- }
376
+ return (relativePath !== "template.json" &&
377
+ relativePath !== "node_modules" &&
378
+ !relativePath.startsWith("node_modules/"));
379
+ },
386
380
  });
387
381
  }
388
382
  }
@@ -403,10 +397,10 @@ class AdvancedCodeGenerator {
403
397
  }
404
398
  // Process templates in patch operations
405
399
  if (processed.operations) {
406
- processed.operations = processed.operations.map(op => {
400
+ processed.operations = processed.operations.map((op) => {
407
401
  const processedOp = { ...op };
408
402
  if (processedOp.imports) {
409
- processedOp.imports = processedOp.imports.map(imp => this.processTemplate(imp, context));
403
+ processedOp.imports = processedOp.imports.map((imp) => this.processTemplate(imp, context));
410
404
  }
411
405
  if (processedOp.code) {
412
406
  processedOp.code = this.processTemplate(processedOp.code, context);
@@ -443,19 +437,9 @@ class AdvancedCodeGenerator {
443
437
  content = this.processTemplate(operation.content, context);
444
438
  }
445
439
  else if (operation.source) {
446
- // Find the source file path relative to the module/template directory
447
- const packageRoot = (0, package_root_1.getPackageRoot)();
448
- const modulesPath = path.join(packageRoot, 'modules');
449
- const templatesPath = path.join(packageRoot, 'templates');
450
- const moduleBasePath = operation.generatorType === 'framework'
451
- ? path.join(templatesPath, operation.generator)
452
- : path.join(modulesPath, operation.generatorType, operation.generator);
453
- const sourcePath = path.join(moduleBasePath, 'files', operation.source);
454
- // Check if source file exists
455
- if (await fs.pathExists(sourcePath)) {
456
- // Read source file
457
- content = await fs.readFile(sourcePath, 'utf-8');
458
- // Process template content
440
+ const sourcePath = (0, generator_utils_1.locateOperationSource)(operation.generatorType, operation.generator, operation.source || "");
441
+ if (sourcePath && (await fs.pathExists(sourcePath))) {
442
+ content = await fs.readFile(sourcePath, "utf-8");
459
443
  content = this.processTemplate(content, context);
460
444
  }
461
445
  else {
@@ -466,14 +450,14 @@ class AdvancedCodeGenerator {
466
450
  throw new Error(`Create file operation must have either 'content' or 'source' field`);
467
451
  }
468
452
  // Write destination file
469
- await fs.writeFile(destinationPath, content, 'utf-8');
453
+ await fs.writeFile(destinationPath, content, "utf-8");
470
454
  }
471
455
  async executePatchFile(operation, context, outputPath) {
472
456
  if (!operation.destination)
473
457
  return;
474
458
  const filePath = path.join(outputPath, this.processTemplate(operation.destination, context));
475
459
  // Read existing file
476
- let content = await fs.readFile(filePath, 'utf-8');
460
+ let content = await fs.readFile(filePath, "utf-8");
477
461
  if (operation.content) {
478
462
  content += this.processTemplate(operation.content, context).trim();
479
463
  }
@@ -483,14 +467,16 @@ class AdvancedCodeGenerator {
483
467
  if (!this.evaluateCondition(patchOp.condition, context))
484
468
  continue;
485
469
  switch (patchOp.type) {
486
- case 'add-import':
470
+ case "add-import":
487
471
  if (patchOp.imports) {
488
- const imports = patchOp.imports.map(imp => this.processTemplate(imp, context)).join('\n');
472
+ const imports = patchOp.imports
473
+ .map((imp) => this.processTemplate(imp, context))
474
+ .join("\n");
489
475
  // Add imports at the top, after existing imports
490
- const lines = content.split('\n');
476
+ const lines = content.split("\n");
491
477
  let insertIndex = 0;
492
478
  for (let i = 0; i < lines.length; i++) {
493
- if (lines[i].trim().startsWith('import') || lines[i].trim() === '') {
479
+ if (lines[i].trim().startsWith("import") || lines[i].trim() === "") {
494
480
  insertIndex = i + 1;
495
481
  }
496
482
  else {
@@ -498,59 +484,62 @@ class AdvancedCodeGenerator {
498
484
  }
499
485
  }
500
486
  lines.splice(insertIndex, 0, imports);
501
- content = lines.join('\n');
487
+ content = lines.join("\n");
502
488
  }
503
489
  break;
504
- case 'add-code':
490
+ case "add-code":
505
491
  if (patchOp.code && patchOp.after) {
506
492
  const processedCode = this.processTemplate(patchOp.code, context);
507
493
  const afterPattern = this.processTemplate(patchOp.after, context);
508
494
  const index = content.indexOf(afterPattern);
509
495
  if (index !== -1) {
510
- content = content.slice(0, index + afterPattern.length) + processedCode + content.slice(index + afterPattern.length);
496
+ content =
497
+ content.slice(0, index + afterPattern.length) +
498
+ processedCode +
499
+ content.slice(index + afterPattern.length);
511
500
  }
512
501
  }
513
502
  break;
514
- case 'replace-code':
503
+ case "replace-code":
515
504
  if (patchOp.code && patchOp.replace) {
516
505
  const processedCode = this.processTemplate(patchOp.code, context);
517
506
  const replacePattern = this.processTemplate(patchOp.replace, context);
518
507
  content = content.replace(replacePattern, processedCode);
519
508
  }
520
509
  break;
521
- case 'add-to-top': {
522
- let processedContentTop = '';
510
+ case "add-to-top": {
511
+ let processedContentTop = "";
523
512
  if (patchOp.content) {
524
513
  processedContentTop = this.processTemplate(patchOp.content, context).trim();
525
514
  }
526
515
  else if (patchOp.source) {
527
- const modulesPath = path.join((0, package_root_1.getPackageRoot)(), 'modules');
528
- const sourcePath = path.join(modulesPath, operation.generatorType, operation.generator, 'files', patchOp.source);
516
+ const modulesPath = path.join((0, package_root_1.getPackageRoot)(), "modules");
517
+ const sourcePath = path.join(modulesPath, operation.generatorType, operation.generator, "files", patchOp.source);
529
518
  if (await fs.pathExists(sourcePath)) {
530
- processedContentTop = await fs.readFile(sourcePath, 'utf-8');
519
+ processedContentTop = await fs.readFile(sourcePath, "utf-8");
531
520
  processedContentTop = this.processTemplate(processedContentTop, context).trim();
532
521
  }
533
522
  }
534
523
  if (processedContentTop) {
535
- content = processedContentTop + '\n' + content;
524
+ content = processedContentTop + "\n" + content;
536
525
  }
537
526
  break;
538
527
  }
539
- case 'add-to-bottom': {
540
- let processedContentBottom = '';
528
+ case "add-to-bottom": {
529
+ let processedContentBottom = "";
541
530
  if (patchOp.content) {
542
531
  processedContentBottom = this.processTemplate(patchOp.content, context).trim();
543
532
  }
544
533
  else if (patchOp.source) {
545
- const modulesPath = path.join((0, package_root_1.getPackageRoot)(), 'modules');
546
- const sourcePath = path.join(modulesPath, operation.generatorType, operation.generator, 'files', patchOp.source);
534
+ const modulesPath = path.join((0, package_root_1.getPackageRoot)(), "modules");
535
+ const sourcePath = path.join(modulesPath, operation.generatorType, operation.generator, "files", patchOp.source);
547
536
  if (await fs.pathExists(sourcePath)) {
548
- processedContentBottom = await fs.readFile(sourcePath, 'utf-8');
537
+ processedContentBottom = await fs.readFile(sourcePath, "utf-8");
549
538
  processedContentBottom = this.processTemplate(processedContentBottom, context).trim();
550
539
  }
551
540
  }
552
541
  if (processedContentBottom) {
553
- content = content + '\n' + processedContentBottom;
542
+ content = content + "\n" + processedContentBottom;
554
543
  }
555
544
  break;
556
545
  }
@@ -558,44 +547,56 @@ class AdvancedCodeGenerator {
558
547
  }
559
548
  }
560
549
  // Write back the modified content
561
- await fs.writeFile(filePath, content, 'utf-8');
550
+ await fs.writeFile(filePath, content, "utf-8");
562
551
  }
563
552
  async executeAddDependency(operation, context, outputPath) {
564
- const packageJsonPath = path.join(outputPath, 'package.json');
553
+ const packageJsonPath = path.join(outputPath, "package.json");
565
554
  let packageJson = {};
566
555
  if (await fs.pathExists(packageJsonPath)) {
567
556
  packageJson = await fs.readJson(packageJsonPath);
568
557
  }
569
558
  if (operation.dependencies) {
570
- packageJson.dependencies = { ...(packageJson.dependencies || {}), ...operation.dependencies };
559
+ packageJson.dependencies = {
560
+ ...(packageJson.dependencies || {}),
561
+ ...operation.dependencies,
562
+ };
571
563
  }
572
564
  if (operation.devDependencies) {
573
- packageJson.devDependencies = { ...(packageJson.devDependencies || {}), ...operation.devDependencies };
565
+ packageJson.devDependencies = {
566
+ ...(packageJson.devDependencies || {}),
567
+ ...operation.devDependencies,
568
+ };
574
569
  }
575
570
  await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
576
571
  }
577
572
  async executeAddScript(operation, context, outputPath) {
578
- const packageJsonPath = path.join(outputPath, 'package.json');
573
+ const packageJsonPath = path.join(outputPath, "package.json");
579
574
  let packageJson = {};
580
575
  if (await fs.pathExists(packageJsonPath)) {
581
576
  packageJson = await fs.readJson(packageJsonPath);
582
577
  }
583
578
  if (operation.scripts) {
584
- packageJson.scripts = { ...(packageJson.scripts || {}), ...operation.scripts };
579
+ packageJson.scripts = {
580
+ ...(packageJson.scripts || {}),
581
+ ...operation.scripts,
582
+ };
585
583
  }
586
584
  await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
587
585
  }
588
586
  async executeAddEnv(operation, context, outputPath) {
589
- const envPath = path.join(outputPath, '.env');
590
- let envContent = '';
587
+ const envPath = path.join(outputPath, ".env");
588
+ let envContent = "";
591
589
  if (await fs.pathExists(envPath)) {
592
- envContent = await fs.readFile(envPath, 'utf-8');
590
+ envContent = await fs.readFile(envPath, "utf-8");
593
591
  }
594
592
  if (operation.envVars) {
595
- const envLines = Object.entries(operation.envVars).map(([key, value]) => `${key}=${value}`);
596
- envContent += '\n' + envLines.join('\n');
593
+ const envLines = Object.entries(operation.envVars).map(([key, value]) => {
594
+ const processedValue = this.processTemplate(value, context);
595
+ return `${key}=${processedValue}`;
596
+ });
597
+ envContent += "\n" + envLines.join("\n");
597
598
  }
598
- await fs.writeFile(envPath, envContent.trim(), 'utf-8');
599
+ await fs.writeFile(envPath, envContent.trim(), "utf-8");
599
600
  }
600
601
  executeRunCommand(operation, context) {
601
602
  if (operation.command) {
@@ -605,7 +606,7 @@ class AdvancedCodeGenerator {
605
606
  }
606
607
  }
607
608
  async generatePackageJson(selectedModules, features, outputPath) {
608
- const packageJsonPath = path.join(outputPath, 'package.json');
609
+ const packageJsonPath = path.join(outputPath, "package.json");
609
610
  let packageJson = {};
610
611
  if (await fs.pathExists(packageJsonPath)) {
611
612
  packageJson = await fs.readJson(packageJsonPath);
@@ -615,40 +616,104 @@ class AdvancedCodeGenerator {
615
616
  const allDevDeps = {};
616
617
  const allScripts = {};
617
618
  for (const [key, generator] of this.generators) {
618
- const [type, name] = key.split(':');
619
- if ((type === 'framework' && name === selectedModules.framework) ||
620
- (type === 'database' && name === selectedModules.database) ||
621
- (type === 'auth' && name === selectedModules.auth)) {
619
+ const [type, name] = key.split(":");
620
+ if ((type === "framework" && name === selectedModules.framework) ||
621
+ (type === "database" && name === selectedModules.database) ||
622
+ (type === "auth" && name === selectedModules.auth)) {
622
623
  Object.assign(allDeps, generator.dependencies);
623
624
  Object.assign(allDevDeps, generator.devDependencies);
624
625
  Object.assign(allScripts, generator.scripts);
625
626
  }
626
627
  }
627
628
  // Update package.json
628
- packageJson.dependencies = { ...(packageJson.dependencies || {}), ...allDeps };
629
- packageJson.devDependencies = { ...(packageJson.devDependencies || {}), ...allDevDeps };
630
- packageJson.scripts = { ...(packageJson.scripts || {}), ...allScripts };
629
+ packageJson.dependencies = {
630
+ ...(packageJson.dependencies || {}),
631
+ ...allDeps,
632
+ };
633
+ packageJson.devDependencies = {
634
+ ...(packageJson.devDependencies || {}),
635
+ ...allDevDeps,
636
+ };
637
+ packageJson.scripts = {
638
+ ...(packageJson.scripts || {}),
639
+ ...allScripts,
640
+ };
631
641
  await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
632
642
  }
643
+ /**
644
+ * Apply generators to an existing project directory instead of creating a new template.
645
+ * This executes applicable operations and updates package.json in-place.
646
+ */
647
+ async applyToProject(selectedModules, features, projectPath) {
648
+ const context = {
649
+ ...selectedModules,
650
+ features,
651
+ };
652
+ if (selectedModules.database === "prisma" && !context.prismaProvider) {
653
+ context.prismaProvider = "postgresql";
654
+ }
655
+ const applicableOperations = [];
656
+ for (const [key, generator] of this.generators) {
657
+ const [genType, name] = key.split(":");
658
+ if (genType === "framework" && name === selectedModules.framework) {
659
+ // framework
660
+ }
661
+ else if (genType === "database" && name === selectedModules.database) {
662
+ // database
663
+ }
664
+ else if (genType === "auth" && name === selectedModules.auth) {
665
+ // auth
666
+ }
667
+ else {
668
+ continue;
669
+ }
670
+ if (generator.postInstall && Array.isArray(generator.postInstall)) {
671
+ this.postInstallCommands.push(...generator.postInstall);
672
+ }
673
+ const items = generator.operations || [];
674
+ for (const item of items) {
675
+ if (this.evaluateCondition(item.condition, context)) {
676
+ applicableOperations.push({
677
+ ...item,
678
+ generator: name,
679
+ generatorType: genType,
680
+ priority: generator.priority,
681
+ });
682
+ }
683
+ }
684
+ }
685
+ applicableOperations.sort((a, b) => (a.priority || 0) - (b.priority || 0));
686
+ for (const operation of applicableOperations) {
687
+ await this.executeOperation(operation, context, projectPath);
688
+ }
689
+ await this.generatePackageJson(selectedModules, features, projectPath);
690
+ return this.postInstallCommands;
691
+ }
633
692
  getAvailableGenerators() {
634
693
  const frameworks = [];
635
694
  const databases = [];
636
695
  const auths = [];
637
696
  for (const [key] of this.generators) {
638
- const [type, name] = key.split(':');
697
+ const [type, name] = key.split(":");
639
698
  switch (type) {
640
- case 'framework':
699
+ case "framework":
641
700
  frameworks.push(name);
642
701
  break;
643
- case 'database':
702
+ case "database":
644
703
  databases.push(name);
645
704
  break;
646
- case 'auth':
705
+ case "auth":
647
706
  auths.push(name);
648
707
  break;
649
708
  }
650
709
  }
651
710
  return { frameworks, databases, auths };
652
711
  }
712
+ /**
713
+ * Register a generator config dynamically (used for modules that only provide module.json patches).
714
+ */
715
+ registerGenerator(type, name, config) {
716
+ this.generators.set(`${type}:${name}`, config);
717
+ }
653
718
  }
654
719
  exports.AdvancedCodeGenerator = AdvancedCodeGenerator;