stackkit 0.2.9 → 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/bin/stackkit.js +8 -5
- package/dist/cli/add.js +4 -9
- package/dist/cli/create.js +4 -9
- package/dist/cli/doctor.js +11 -32
- package/dist/lib/constants.js +3 -4
- package/dist/lib/conversion/js-conversion.js +20 -16
- package/dist/lib/discovery/installed-detection.js +28 -38
- package/dist/lib/discovery/module-discovery.d.ts +0 -15
- package/dist/lib/discovery/module-discovery.js +15 -50
- package/dist/lib/framework/framework-utils.d.ts +4 -5
- package/dist/lib/framework/framework-utils.js +38 -49
- package/dist/lib/generation/code-generator.d.ts +11 -12
- package/dist/lib/generation/code-generator.js +54 -134
- package/dist/lib/generation/generator-utils.js +3 -15
- package/dist/lib/project/detect.js +11 -19
- package/modules/auth/better-auth/files/express/middlewares/authorize.ts +66 -8
- package/modules/auth/better-auth/files/express/modules/auth/auth.controller.ts +7 -0
- package/modules/auth/better-auth/files/express/modules/auth/auth.route.ts +5 -0
- package/modules/auth/better-auth/files/express/modules/auth/auth.service.ts +145 -11
- package/modules/auth/better-auth/files/express/modules/auth/{auth.interface.ts → auth.type.ts} +16 -6
- package/modules/auth/better-auth/files/shared/config/env.ts +8 -4
- package/modules/auth/better-auth/files/shared/lib/auth.ts +24 -25
- package/modules/auth/better-auth/files/shared/mongoose/auth/constants.ts +11 -0
- package/modules/auth/better-auth/files/shared/mongoose/auth/helper.ts +51 -0
- package/modules/auth/better-auth/files/shared/utils/email.ts +0 -1
- package/modules/auth/better-auth/generator.json +24 -20
- package/modules/database/mongoose/files/lib/mongoose.ts +28 -3
- package/modules/database/mongoose/generator.json +1 -1
- package/package.json +2 -2
- package/templates/express/env.example +1 -0
- package/templates/express/src/config/env.ts +4 -2
|
@@ -46,6 +46,25 @@ class AdvancedCodeGenerator {
|
|
|
46
46
|
this.createdFiles = [];
|
|
47
47
|
this.frameworkConfig = frameworkConfig;
|
|
48
48
|
}
|
|
49
|
+
initializeContext(selectedModules, features) {
|
|
50
|
+
const context = {
|
|
51
|
+
...selectedModules,
|
|
52
|
+
features,
|
|
53
|
+
combo: `${selectedModules.database || ""}:${selectedModules.framework || ""}`,
|
|
54
|
+
};
|
|
55
|
+
if (selectedModules.database === "prisma" && !context.prismaProvider) {
|
|
56
|
+
const providers = (0, shared_1.getPrismaProvidersFromGenerator)((0, package_root_1.getPackageRoot)());
|
|
57
|
+
if (providers.length > 0) {
|
|
58
|
+
context.prismaProvider = providers[0];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return context;
|
|
62
|
+
}
|
|
63
|
+
isGeneratorSelected(genType, name, selectedModules) {
|
|
64
|
+
return ((genType === "framework" && name === selectedModules.framework) ||
|
|
65
|
+
(genType === "database" && name === selectedModules.database) ||
|
|
66
|
+
(genType === "auth" && name === selectedModules.auth));
|
|
67
|
+
}
|
|
49
68
|
async loadGenerators(modulesPath) {
|
|
50
69
|
const moduleTypes = ["auth", "database"];
|
|
51
70
|
for (const type of moduleTypes) {
|
|
@@ -62,7 +81,7 @@ class AdvancedCodeGenerator {
|
|
|
62
81
|
this.generators.set(`${type}:${moduleName}`, config);
|
|
63
82
|
}
|
|
64
83
|
catch {
|
|
65
|
-
|
|
84
|
+
continue;
|
|
66
85
|
}
|
|
67
86
|
}
|
|
68
87
|
}
|
|
@@ -96,15 +115,10 @@ class AdvancedCodeGenerator {
|
|
|
96
115
|
return true;
|
|
97
116
|
}
|
|
98
117
|
processTemplate(content, context) {
|
|
99
|
-
// Create a copy of context for template variables
|
|
100
118
|
const templateContext = { ...context };
|
|
101
|
-
// Handle variable definitions {{#var name = value}} at the top of the file
|
|
102
119
|
content = this.processVariableDefinitions(content, templateContext);
|
|
103
|
-
// Process the rest of the template with the extended context
|
|
104
120
|
content = this.processTemplateRecursive(content, templateContext);
|
|
105
|
-
// Remove leading newlines that might be left from {{#var}} removal
|
|
106
121
|
content = content.replace(/^\n+/, "");
|
|
107
|
-
// Reduce multiple consecutive newlines to maximum 2
|
|
108
122
|
content = content.replace(/\n{3,}/g, "\n\n");
|
|
109
123
|
return content;
|
|
110
124
|
}
|
|
@@ -128,14 +142,13 @@ class AdvancedCodeGenerator {
|
|
|
128
142
|
const varNameMatch = result.substring(varStart + 7, equalsIndex).trim();
|
|
129
143
|
if (!varNameMatch)
|
|
130
144
|
break;
|
|
131
|
-
// Find the closing }} for the variable definition, accounting for nested {{#var}}...{{/var}}
|
|
132
145
|
let braceCount = 1;
|
|
133
146
|
const valueStart = equalsIndex + 1;
|
|
134
147
|
let valueEnd = valueStart;
|
|
135
148
|
for (let i = valueStart; i < result.length; i++) {
|
|
136
149
|
if (result[i] === "{" && result[i + 1] === "{") {
|
|
137
150
|
braceCount++;
|
|
138
|
-
i++;
|
|
151
|
+
i++;
|
|
139
152
|
}
|
|
140
153
|
else if (result[i] === "}" && result[i + 1] === "}") {
|
|
141
154
|
braceCount--;
|
|
@@ -143,17 +156,15 @@ class AdvancedCodeGenerator {
|
|
|
143
156
|
valueEnd = i;
|
|
144
157
|
break;
|
|
145
158
|
}
|
|
146
|
-
i++;
|
|
159
|
+
i++;
|
|
147
160
|
}
|
|
148
161
|
}
|
|
149
162
|
if (valueEnd === valueStart)
|
|
150
163
|
break;
|
|
151
164
|
const varValue = result.substring(valueStart, valueEnd).trim();
|
|
152
165
|
const fullMatch = result.substring(varStart, valueEnd + 2);
|
|
153
|
-
// Process the variable value with current context (allowing nested variables/conditionals)
|
|
154
166
|
const processedValue = this.processTemplateRecursive(varValue, context);
|
|
155
167
|
context[varNameMatch] = processedValue;
|
|
156
|
-
// Remove the variable definition
|
|
157
168
|
result = result.replace(fullMatch, "");
|
|
158
169
|
index = varStart;
|
|
159
170
|
}
|
|
@@ -188,7 +199,6 @@ class AdvancedCodeGenerator {
|
|
|
188
199
|
.replace(/^\n+/, "")
|
|
189
200
|
.replace(/\n+$/, "");
|
|
190
201
|
});
|
|
191
|
-
// Handle simple conditional blocks {{#if condition}}...{{/if}} (backward compatibility)
|
|
192
202
|
content = content.replace(/\{\{#if\s+([^}]+)\}\}([\s\S]*?)(?:\{\{else\}\}([\s\S]*?))?\{\{\/if\}\}/g, (match, condition, blockContent, elseContent) => {
|
|
193
203
|
const conditionParts = condition.split("==");
|
|
194
204
|
if (conditionParts.length === 2) {
|
|
@@ -210,10 +220,8 @@ class AdvancedCodeGenerator {
|
|
|
210
220
|
}
|
|
211
221
|
return "";
|
|
212
222
|
});
|
|
213
|
-
// Handle switch statements {{#switch variable}}...{{/switch}}
|
|
214
223
|
content = content.replace(/\{\{#switch\s+([^}]+)\}\}([\s\S]*?)\{\{\/switch\}\}/g, (match, varName, switchContent) => {
|
|
215
224
|
const actualVal = context[varName.trim()];
|
|
216
|
-
// Parse cases
|
|
217
225
|
const caseRegex = /\{\{#case\s+([^}]+)\}\}([\s\S]*?)(?=\{\{#case|\{\{\/case\}|\{\{\/switch\})/g;
|
|
218
226
|
let result = "";
|
|
219
227
|
let defaultCase = "";
|
|
@@ -232,10 +240,8 @@ class AdvancedCodeGenerator {
|
|
|
232
240
|
const chosen = (result || defaultCase || "").trim();
|
|
233
241
|
return this.processTemplateRecursive(chosen, context);
|
|
234
242
|
});
|
|
235
|
-
// Handle variable replacement with advanced expressions
|
|
236
243
|
content = content.replace(/\{\{([^}]+)\}\}/g, (match, varExpr) => {
|
|
237
244
|
const trimmedExpr = varExpr.trim();
|
|
238
|
-
// Handle ternary expressions like framework=='nextjs' ? '@/lib' : '.'
|
|
239
245
|
const ternaryMatch = trimmedExpr.match(/^(.+?)\s*\?\s*(.+?)\s*:\s*(.+?)$/);
|
|
240
246
|
if (ternaryMatch) {
|
|
241
247
|
const [, condition, trueVal, falseVal] = ternaryMatch;
|
|
@@ -246,10 +252,9 @@ class AdvancedCodeGenerator {
|
|
|
246
252
|
const cleanExpectedVal = expectedVal.trim().replace(/['"]/g, "");
|
|
247
253
|
const actualVal = context[cleanVarName];
|
|
248
254
|
const result = actualVal === cleanExpectedVal ? trueVal.trim() : falseVal.trim();
|
|
249
|
-
return result.replace(/['"]/g, "");
|
|
255
|
+
return result.replace(/['"]/g, "");
|
|
250
256
|
}
|
|
251
257
|
}
|
|
252
|
-
// Handle switch expressions {{switch variable case1: value1, case2: value2, default: defaultValue}}
|
|
253
258
|
const switchMatch = trimmedExpr.match(/^switch\s+([^}\s]+)\s+(.+)$/);
|
|
254
259
|
if (switchMatch) {
|
|
255
260
|
const [, varName, casesStr] = switchMatch;
|
|
@@ -264,17 +269,14 @@ class AdvancedCodeGenerator {
|
|
|
264
269
|
}
|
|
265
270
|
return "";
|
|
266
271
|
}
|
|
267
|
-
// Handle heading helper {{heading:depthVar}} -> '#', '##', ... up to '######'
|
|
268
272
|
if (trimmedExpr.startsWith("heading:")) {
|
|
269
273
|
return this.renderHeadingFromExpr(trimmedExpr, context);
|
|
270
274
|
}
|
|
271
|
-
// Handle feature flags {{feature:name}}
|
|
272
275
|
if (trimmedExpr.startsWith("feature:")) {
|
|
273
276
|
const featureName = trimmedExpr.substring(8).trim();
|
|
274
277
|
const features = Array.isArray(context.features) ? context.features : [];
|
|
275
278
|
return features.includes(featureName) ? "true" : "false";
|
|
276
279
|
}
|
|
277
|
-
// Handle conditional expressions {{if condition then:value else:value}}
|
|
278
280
|
const conditionalMatch = trimmedExpr.match(/^if\s+(.+?)\s+then:([^,]+),\s*else:(.+)$/);
|
|
279
281
|
if (conditionalMatch) {
|
|
280
282
|
const [, condition, thenVal, elseVal] = conditionalMatch;
|
|
@@ -288,50 +290,23 @@ class AdvancedCodeGenerator {
|
|
|
288
290
|
return result.replace(/['"]/g, "");
|
|
289
291
|
}
|
|
290
292
|
}
|
|
291
|
-
// Simple variable replacement
|
|
292
293
|
const value = context[trimmedExpr];
|
|
293
294
|
return value !== undefined ? String(value) : match;
|
|
294
295
|
});
|
|
295
296
|
return content;
|
|
296
297
|
}
|
|
297
298
|
async generate(selectedModules, features, outputPath) {
|
|
298
|
-
// First, copy the base template
|
|
299
299
|
await this.copyTemplate(selectedModules.framework, outputPath);
|
|
300
|
-
const context =
|
|
301
|
-
...selectedModules,
|
|
302
|
-
features,
|
|
303
|
-
};
|
|
304
|
-
// Derived combined key to simplify template conditionals (e.g. "prisma:express")
|
|
305
|
-
context.combo = `${context.database || ""}:${context.framework || ""}`;
|
|
306
|
-
// Set default prismaProvider if database is prisma but no provider specified
|
|
307
|
-
if (selectedModules.database === "prisma" && !context.prismaProvider) {
|
|
308
|
-
const providers = (0, shared_1.getPrismaProvidersFromGenerator)((0, package_root_1.getPackageRoot)());
|
|
309
|
-
if (providers && providers.length > 0) {
|
|
310
|
-
context.prismaProvider = providers[0];
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
// Collect all applicable operations
|
|
300
|
+
const context = this.initializeContext(selectedModules, features);
|
|
314
301
|
const applicableOperations = [];
|
|
315
302
|
for (const [key, generator] of this.generators) {
|
|
316
303
|
const [genType, name] = key.split(":");
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
// Framework is always included
|
|
320
|
-
}
|
|
321
|
-
else if (genType === "database" && name === selectedModules.database) {
|
|
322
|
-
// Database is selected
|
|
323
|
-
}
|
|
324
|
-
else if (genType === "auth" && name === selectedModules.auth) {
|
|
325
|
-
// Auth is selected
|
|
326
|
-
}
|
|
327
|
-
else {
|
|
328
|
-
continue; // Skip unselected generators
|
|
304
|
+
if (!this.isGeneratorSelected(genType, name, selectedModules)) {
|
|
305
|
+
continue;
|
|
329
306
|
}
|
|
330
|
-
// Collect postInstall commands from selected generators
|
|
331
307
|
if (generator.postInstall && Array.isArray(generator.postInstall)) {
|
|
332
308
|
this.postInstallCommands.push(...generator.postInstall);
|
|
333
309
|
}
|
|
334
|
-
// Handle operations
|
|
335
310
|
const items = generator.operations || [];
|
|
336
311
|
for (const item of items) {
|
|
337
312
|
if (this.evaluateCondition(item.condition, context)) {
|
|
@@ -344,25 +319,21 @@ class AdvancedCodeGenerator {
|
|
|
344
319
|
}
|
|
345
320
|
}
|
|
346
321
|
}
|
|
347
|
-
// Sort operations by priority
|
|
348
322
|
applicableOperations.sort((a, b) => {
|
|
349
323
|
const priorityA = a.priority || 0;
|
|
350
324
|
const priorityB = b.priority || 0;
|
|
351
325
|
return priorityA - priorityB;
|
|
352
326
|
});
|
|
353
|
-
// Execute operations
|
|
354
327
|
for (const operation of applicableOperations) {
|
|
355
328
|
await this.executeOperation(operation, context, outputPath);
|
|
356
329
|
}
|
|
357
|
-
|
|
358
|
-
await this.generatePackageJson(selectedModules, features, outputPath);
|
|
330
|
+
await this.generatePackageJson(selectedModules, outputPath);
|
|
359
331
|
return this.postInstallCommands;
|
|
360
332
|
}
|
|
361
333
|
getCreatedFiles() {
|
|
362
334
|
return this.createdFiles.slice();
|
|
363
335
|
}
|
|
364
336
|
async executeOperation(operation, context, outputPath) {
|
|
365
|
-
// Process templates in operation content
|
|
366
337
|
const processedOperation = this.processOperationTemplates(operation, context);
|
|
367
338
|
switch (processedOperation.type) {
|
|
368
339
|
case "create-file":
|
|
@@ -372,10 +343,10 @@ class AdvancedCodeGenerator {
|
|
|
372
343
|
await this.executePatchFile(processedOperation, context, outputPath);
|
|
373
344
|
break;
|
|
374
345
|
case "add-dependency":
|
|
375
|
-
await this.executeAddDependency(processedOperation,
|
|
346
|
+
await this.executeAddDependency(processedOperation, outputPath);
|
|
376
347
|
break;
|
|
377
348
|
case "add-script":
|
|
378
|
-
await this.executeAddScript(processedOperation,
|
|
349
|
+
await this.executeAddScript(processedOperation, outputPath);
|
|
379
350
|
break;
|
|
380
351
|
case "add-env":
|
|
381
352
|
await this.executeAddEnv(processedOperation, context, outputPath);
|
|
@@ -384,7 +355,7 @@ class AdvancedCodeGenerator {
|
|
|
384
355
|
this.executeRunCommand(processedOperation, context);
|
|
385
356
|
break;
|
|
386
357
|
default:
|
|
387
|
-
|
|
358
|
+
return;
|
|
388
359
|
}
|
|
389
360
|
}
|
|
390
361
|
async copyTemplate(frameworkName, outputPath) {
|
|
@@ -412,8 +383,8 @@ class AdvancedCodeGenerator {
|
|
|
412
383
|
}
|
|
413
384
|
}
|
|
414
385
|
}
|
|
415
|
-
catch {
|
|
416
|
-
|
|
386
|
+
catch (error) {
|
|
387
|
+
void error;
|
|
417
388
|
}
|
|
418
389
|
try {
|
|
419
390
|
const templateJsonPath = path.join(templatePath, "template.json");
|
|
@@ -424,8 +395,7 @@ class AdvancedCodeGenerator {
|
|
|
424
395
|
if (typeof f === "string" && f.startsWith(".")) {
|
|
425
396
|
const targetDest = path.join(outputPath, f);
|
|
426
397
|
if (await fs.pathExists(targetDest))
|
|
427
|
-
continue;
|
|
428
|
-
// Special-case: allow creating .gitignore from non-dot fallbacks
|
|
398
|
+
continue;
|
|
429
399
|
if (f === ".gitignore") {
|
|
430
400
|
const nameWithoutDot = f.slice(1);
|
|
431
401
|
const candidates = [f, nameWithoutDot, `_${nameWithoutDot}`];
|
|
@@ -438,7 +408,6 @@ class AdvancedCodeGenerator {
|
|
|
438
408
|
}
|
|
439
409
|
continue;
|
|
440
410
|
}
|
|
441
|
-
// For other dotfiles, only copy if the exact dotfile exists in the template to avoid unintended fallbacks
|
|
442
411
|
const srcDot = path.join(templatePath, f);
|
|
443
412
|
if (await fs.pathExists(srcDot)) {
|
|
444
413
|
await fs.copy(srcDot, targetDest);
|
|
@@ -448,8 +417,8 @@ class AdvancedCodeGenerator {
|
|
|
448
417
|
}
|
|
449
418
|
}
|
|
450
419
|
}
|
|
451
|
-
catch {
|
|
452
|
-
|
|
420
|
+
catch (error) {
|
|
421
|
+
void error;
|
|
453
422
|
}
|
|
454
423
|
try {
|
|
455
424
|
const templateJsonPath2 = path.join(templatePath, "template.json");
|
|
@@ -462,7 +431,6 @@ class AdvancedCodeGenerator {
|
|
|
462
431
|
const nameWithoutDot = f.slice(1);
|
|
463
432
|
const nonDot = path.join(outputPath, nameWithoutDot);
|
|
464
433
|
const underscore = path.join(outputPath, `_${nameWithoutDot}`);
|
|
465
|
-
// If dot already exists, remove non-dot fallbacks
|
|
466
434
|
if (await fs.pathExists(dotDest)) {
|
|
467
435
|
if (await fs.pathExists(nonDot)) {
|
|
468
436
|
await fs.remove(nonDot);
|
|
@@ -472,7 +440,6 @@ class AdvancedCodeGenerator {
|
|
|
472
440
|
}
|
|
473
441
|
continue;
|
|
474
442
|
}
|
|
475
|
-
// If dot doesn't exist but a non-dot fallback was copied, rename it
|
|
476
443
|
if (await fs.pathExists(nonDot)) {
|
|
477
444
|
await fs.move(nonDot, dotDest, { overwrite: true });
|
|
478
445
|
}
|
|
@@ -484,10 +451,9 @@ class AdvancedCodeGenerator {
|
|
|
484
451
|
}
|
|
485
452
|
}
|
|
486
453
|
}
|
|
487
|
-
catch {
|
|
488
|
-
|
|
454
|
+
catch (error) {
|
|
455
|
+
void error;
|
|
489
456
|
}
|
|
490
|
-
// Handle .env.example -> .env copying if .env doesn't already exist
|
|
491
457
|
try {
|
|
492
458
|
const envExampleDest = path.join(outputPath, ".env.example");
|
|
493
459
|
const envDest = path.join(outputPath, ".env");
|
|
@@ -495,14 +461,13 @@ class AdvancedCodeGenerator {
|
|
|
495
461
|
await fs.copy(envExampleDest, envDest);
|
|
496
462
|
}
|
|
497
463
|
}
|
|
498
|
-
catch {
|
|
499
|
-
|
|
464
|
+
catch (error) {
|
|
465
|
+
void error;
|
|
500
466
|
}
|
|
501
467
|
}
|
|
502
468
|
}
|
|
503
469
|
processOperationTemplates(operation, context) {
|
|
504
470
|
const processed = { ...operation };
|
|
505
|
-
// Process templates in string fields
|
|
506
471
|
if (processed.source) {
|
|
507
472
|
processed.source = this.processTemplate(processed.source, context);
|
|
508
473
|
}
|
|
@@ -512,7 +477,6 @@ class AdvancedCodeGenerator {
|
|
|
512
477
|
if (processed.content) {
|
|
513
478
|
processed.content = this.processTemplate(processed.content, context);
|
|
514
479
|
}
|
|
515
|
-
// Process templates in patch operations
|
|
516
480
|
if (processed.operations) {
|
|
517
481
|
processed.operations = processed.operations.map((op) => {
|
|
518
482
|
const processedOp = { ...op };
|
|
@@ -564,8 +528,8 @@ class AdvancedCodeGenerator {
|
|
|
564
528
|
if (rel && !this.createdFiles.includes(rel))
|
|
565
529
|
this.createdFiles.push(rel);
|
|
566
530
|
}
|
|
567
|
-
catch {
|
|
568
|
-
|
|
531
|
+
catch (error) {
|
|
532
|
+
void error;
|
|
569
533
|
}
|
|
570
534
|
return;
|
|
571
535
|
}
|
|
@@ -622,8 +586,8 @@ class AdvancedCodeGenerator {
|
|
|
622
586
|
if (rel && !this.createdFiles.includes(rel))
|
|
623
587
|
this.createdFiles.push(rel);
|
|
624
588
|
}
|
|
625
|
-
catch {
|
|
626
|
-
|
|
589
|
+
catch (error) {
|
|
590
|
+
void error;
|
|
627
591
|
}
|
|
628
592
|
}
|
|
629
593
|
return;
|
|
@@ -640,8 +604,8 @@ class AdvancedCodeGenerator {
|
|
|
640
604
|
if (rel && !this.createdFiles.includes(rel))
|
|
641
605
|
this.createdFiles.push(rel);
|
|
642
606
|
}
|
|
643
|
-
catch {
|
|
644
|
-
|
|
607
|
+
catch (error) {
|
|
608
|
+
void error;
|
|
645
609
|
}
|
|
646
610
|
}
|
|
647
611
|
parsePathPattern(inputPath) {
|
|
@@ -673,13 +637,11 @@ class AdvancedCodeGenerator {
|
|
|
673
637
|
if (!operation.destination)
|
|
674
638
|
return;
|
|
675
639
|
const filePath = path.join(outputPath, this.processTemplate(operation.destination, context));
|
|
676
|
-
// Read existing file
|
|
677
640
|
let content = await fs.readFile(filePath, "utf-8");
|
|
678
641
|
if (operation.content) {
|
|
679
642
|
content += this.processTemplate(operation.content, context).trim();
|
|
680
643
|
}
|
|
681
644
|
else if (operation.operations) {
|
|
682
|
-
// Execute patch operations
|
|
683
645
|
for (const patchOp of operation.operations) {
|
|
684
646
|
if (!this.evaluateCondition(patchOp.condition, context))
|
|
685
647
|
continue;
|
|
@@ -691,31 +653,25 @@ class AdvancedCodeGenerator {
|
|
|
691
653
|
.join("\n")
|
|
692
654
|
.replace(/^\n+/, "")
|
|
693
655
|
.replace(/\n+$/, "");
|
|
694
|
-
// Split content into lines for easier manipulation
|
|
695
656
|
const lines = content.split("\n");
|
|
696
|
-
// Find the last import line index
|
|
697
657
|
let lastImportIndex = -1;
|
|
698
658
|
for (let i = 0; i < lines.length; i++) {
|
|
699
659
|
if (lines[i].trim().startsWith("import"))
|
|
700
660
|
lastImportIndex = i;
|
|
701
661
|
}
|
|
702
|
-
// Determine where to insert new imports: after the last existing import, or at the top if no imports exist
|
|
703
662
|
const insertIndex = lastImportIndex === -1 ? 0 : lastImportIndex + 1;
|
|
704
|
-
// Only add imports that don't already exist in the file
|
|
705
663
|
const importLines = imports
|
|
706
664
|
.split("\n")
|
|
707
665
|
.map((l) => l.trim())
|
|
708
666
|
.filter(Boolean);
|
|
709
667
|
const newImportLines = importLines.filter((imp) => !lines.some((ln) => ln.trim() === imp));
|
|
710
668
|
if (newImportLines.length > 0) {
|
|
711
|
-
// Insert imports
|
|
712
669
|
if (insertIndex < lines.length && lines[insertIndex].trim() === "") {
|
|
713
670
|
lines.splice(insertIndex, 1, ...newImportLines);
|
|
714
671
|
}
|
|
715
672
|
else {
|
|
716
673
|
lines.splice(insertIndex, 0, ...newImportLines);
|
|
717
674
|
}
|
|
718
|
-
// After adding imports, ensure there is exactly one blank line after the last import statement (if there are any imports)
|
|
719
675
|
let lastIdx = -1;
|
|
720
676
|
for (let i = 0; i < lines.length; i++) {
|
|
721
677
|
if (lines[i].trim().startsWith("import"))
|
|
@@ -727,7 +683,6 @@ class AdvancedCodeGenerator {
|
|
|
727
683
|
while (j < lines.length && lines[j].trim() === "") {
|
|
728
684
|
j++;
|
|
729
685
|
}
|
|
730
|
-
// Ensure exactly one blank line after imports unless imports end at EOF
|
|
731
686
|
if (nextIdx < lines.length) {
|
|
732
687
|
lines.splice(nextIdx, j - nextIdx, "");
|
|
733
688
|
}
|
|
@@ -742,21 +697,17 @@ class AdvancedCodeGenerator {
|
|
|
742
697
|
if (Array.isArray(codeValue))
|
|
743
698
|
codeValue = codeValue.join("\n");
|
|
744
699
|
const processedCode = this.processTemplate(codeValue, context);
|
|
745
|
-
// Skip insertion if the exact code already exists in the file
|
|
746
700
|
const codeTrimmed = processedCode.trim();
|
|
747
701
|
if (codeTrimmed && content.includes(codeTrimmed)) {
|
|
748
702
|
break;
|
|
749
703
|
}
|
|
750
|
-
// Insert after pattern if provided
|
|
751
704
|
if (patchOp.after) {
|
|
752
705
|
const afterPattern = this.processTemplate(patchOp.after, context);
|
|
753
706
|
const index = content.indexOf(afterPattern);
|
|
754
707
|
if (index !== -1) {
|
|
755
708
|
const left = content.slice(0, index + afterPattern.length);
|
|
756
709
|
const right = content.slice(index + afterPattern.length);
|
|
757
|
-
// Normalize code: trim surrounding newlines and ensure single trailing newline
|
|
758
710
|
let codeNormalized = processedCode.replace(/^\n+|\n+$/g, "") + "\n";
|
|
759
|
-
// If right already starts with a newline, avoid double-blank by
|
|
760
711
|
const rightStartsWithNewline = right.startsWith("\n");
|
|
761
712
|
if (rightStartsWithNewline && codeNormalized.endsWith("\n")) {
|
|
762
713
|
codeNormalized = codeNormalized.replace(/\n+$/, "");
|
|
@@ -765,16 +716,13 @@ class AdvancedCodeGenerator {
|
|
|
765
716
|
content = left + (leftNeedsNewline ? "\n" : "") + codeNormalized + right;
|
|
766
717
|
}
|
|
767
718
|
}
|
|
768
|
-
// Insert before pattern if provided
|
|
769
719
|
if (patchOp.before) {
|
|
770
720
|
const beforePattern = this.processTemplate(patchOp.before, context);
|
|
771
721
|
const index = content.indexOf(beforePattern);
|
|
772
722
|
if (index !== -1) {
|
|
773
723
|
const left = content.slice(0, index);
|
|
774
724
|
const right = content.slice(index);
|
|
775
|
-
// Normalize code: trim surrounding newlines and ensure single trailing newline
|
|
776
725
|
let codeNormalized = processedCode.replace(/^\n+|\n+$/g, "") + "\n";
|
|
777
|
-
// If right already starts with a newline, avoid double-blank by
|
|
778
726
|
const rightStartsWithNewline = right.startsWith("\n");
|
|
779
727
|
if (rightStartsWithNewline && codeNormalized.endsWith("\n")) {
|
|
780
728
|
codeNormalized = codeNormalized.replace(/\n+$/, "");
|
|
@@ -834,12 +782,10 @@ class AdvancedCodeGenerator {
|
|
|
834
782
|
}
|
|
835
783
|
}
|
|
836
784
|
}
|
|
837
|
-
// Normalize excessive blank lines introduced during patching
|
|
838
785
|
content = content.replace(/\n{3,}/g, "\n\n");
|
|
839
|
-
// Write back the modified content
|
|
840
786
|
await fs.writeFile(filePath, content, "utf-8");
|
|
841
787
|
}
|
|
842
|
-
async executeAddDependency(operation,
|
|
788
|
+
async executeAddDependency(operation, outputPath) {
|
|
843
789
|
const packageJsonPath = path.join(outputPath, "package.json");
|
|
844
790
|
let packageJson = {};
|
|
845
791
|
if (await fs.pathExists(packageJsonPath)) {
|
|
@@ -859,7 +805,7 @@ class AdvancedCodeGenerator {
|
|
|
859
805
|
}
|
|
860
806
|
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
861
807
|
}
|
|
862
|
-
async executeAddScript(operation,
|
|
808
|
+
async executeAddScript(operation, outputPath) {
|
|
863
809
|
const packageJsonPath = path.join(outputPath, "package.json");
|
|
864
810
|
let packageJson = {};
|
|
865
811
|
if (await fs.pathExists(packageJsonPath)) {
|
|
@@ -890,31 +836,27 @@ class AdvancedCodeGenerator {
|
|
|
890
836
|
}
|
|
891
837
|
executeRunCommand(operation, context) {
|
|
892
838
|
if (operation.command) {
|
|
893
|
-
// Process template variables in the command
|
|
894
839
|
const processedCommand = this.processTemplate(operation.command, context);
|
|
895
840
|
this.postInstallCommands.push(processedCommand);
|
|
896
841
|
}
|
|
897
842
|
}
|
|
898
|
-
async generatePackageJson(selectedModules,
|
|
843
|
+
async generatePackageJson(selectedModules, outputPath) {
|
|
899
844
|
const packageJsonPath = path.join(outputPath, "package.json");
|
|
900
845
|
let packageJson = {};
|
|
901
846
|
if (await fs.pathExists(packageJsonPath)) {
|
|
902
847
|
packageJson = await fs.readJson(packageJsonPath);
|
|
903
848
|
}
|
|
904
|
-
// Collect dependencies from selected generators
|
|
905
849
|
const allDeps = {};
|
|
906
850
|
const allDevDeps = {};
|
|
907
851
|
const allScripts = {};
|
|
908
852
|
for (const [key, generator] of this.generators) {
|
|
909
853
|
const [type, name] = key.split(":");
|
|
910
|
-
if ((type
|
|
911
|
-
(
|
|
912
|
-
(
|
|
913
|
-
// Merge dependencies, devDependencies, and scripts from the generator into the cumulative objects
|
|
854
|
+
if (this.isGeneratorSelected(type, name, selectedModules)) {
|
|
855
|
+
Object.assign(allDeps, generator.dependencies);
|
|
856
|
+
Object.assign(allDevDeps, generator.devDependencies);
|
|
914
857
|
Object.assign(allScripts, generator.scripts);
|
|
915
858
|
}
|
|
916
859
|
}
|
|
917
|
-
// Update package.json
|
|
918
860
|
packageJson.dependencies = {
|
|
919
861
|
...(packageJson.dependencies || {}),
|
|
920
862
|
...allDeps,
|
|
@@ -929,33 +871,12 @@ class AdvancedCodeGenerator {
|
|
|
929
871
|
};
|
|
930
872
|
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
931
873
|
}
|
|
932
|
-
// Public method to apply generators to an existing project (used for "Add to existing project" flow)
|
|
933
874
|
async applyToProject(selectedModules, features, projectPath) {
|
|
934
|
-
const context =
|
|
935
|
-
...selectedModules,
|
|
936
|
-
features,
|
|
937
|
-
};
|
|
938
|
-
// Derived combined key to simplify template conditionals (e.g. "prisma:express")
|
|
939
|
-
context.combo = `${context.database || ""}:${context.framework || ""}`;
|
|
940
|
-
if (selectedModules.database === "prisma" && !context.prismaProvider) {
|
|
941
|
-
const providers = (0, shared_1.getPrismaProvidersFromGenerator)((0, package_root_1.getPackageRoot)());
|
|
942
|
-
if (providers && providers.length > 0) {
|
|
943
|
-
context.prismaProvider = providers[0];
|
|
944
|
-
}
|
|
945
|
-
}
|
|
875
|
+
const context = this.initializeContext(selectedModules, features);
|
|
946
876
|
const applicableOperations = [];
|
|
947
877
|
for (const [key, generator] of this.generators) {
|
|
948
878
|
const [genType, name] = key.split(":");
|
|
949
|
-
if (genType
|
|
950
|
-
// framework
|
|
951
|
-
}
|
|
952
|
-
else if (genType === "database" && name === selectedModules.database) {
|
|
953
|
-
// database
|
|
954
|
-
}
|
|
955
|
-
else if (genType === "auth" && name === selectedModules.auth) {
|
|
956
|
-
// auth
|
|
957
|
-
}
|
|
958
|
-
else {
|
|
879
|
+
if (!this.isGeneratorSelected(genType, name, selectedModules)) {
|
|
959
880
|
continue;
|
|
960
881
|
}
|
|
961
882
|
if (generator.postInstall && Array.isArray(generator.postInstall)) {
|
|
@@ -977,7 +898,7 @@ class AdvancedCodeGenerator {
|
|
|
977
898
|
for (const operation of applicableOperations) {
|
|
978
899
|
await this.executeOperation(operation, context, projectPath);
|
|
979
900
|
}
|
|
980
|
-
await this.generatePackageJson(selectedModules,
|
|
901
|
+
await this.generatePackageJson(selectedModules, projectPath);
|
|
981
902
|
return this.postInstallCommands;
|
|
982
903
|
}
|
|
983
904
|
getAvailableGenerators() {
|
|
@@ -1000,7 +921,6 @@ class AdvancedCodeGenerator {
|
|
|
1000
921
|
}
|
|
1001
922
|
return { frameworks, databases, auths };
|
|
1002
923
|
}
|
|
1003
|
-
// Method to register a generator configuration
|
|
1004
924
|
registerGenerator(type, name, config) {
|
|
1005
925
|
this.generators.set(`${type}:${name}`, config);
|
|
1006
926
|
}
|
|
@@ -64,7 +64,7 @@ async function mergeModuleIntoGeneratorConfig(config, modulePath) {
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
catch {
|
|
67
|
-
|
|
67
|
+
return config;
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
return config;
|
|
@@ -74,7 +74,6 @@ async function mergeGeneratorIntoModuleMetadata(metadata, modulePath) {
|
|
|
74
74
|
if (await fs.pathExists(generatorPath)) {
|
|
75
75
|
try {
|
|
76
76
|
const generator = await fs.readJson(generatorPath);
|
|
77
|
-
// Process add-env operations to extract envVars
|
|
78
77
|
if (generator.operations && Array.isArray(generator.operations)) {
|
|
79
78
|
for (const operation of generator.operations) {
|
|
80
79
|
if (operation.type === "add-env" && operation.envVars) {
|
|
@@ -90,11 +89,6 @@ async function mergeGeneratorIntoModuleMetadata(metadata, modulePath) {
|
|
|
90
89
|
});
|
|
91
90
|
}
|
|
92
91
|
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
// Collect dependencies/devDependencies from add-dependency operations
|
|
96
|
-
if (generator.operations && Array.isArray(generator.operations)) {
|
|
97
|
-
for (const operation of generator.operations) {
|
|
98
92
|
if (operation.type === "add-dependency") {
|
|
99
93
|
if (operation.dependencies) {
|
|
100
94
|
metadata.dependencies = {
|
|
@@ -117,7 +111,7 @@ async function mergeGeneratorIntoModuleMetadata(metadata, modulePath) {
|
|
|
117
111
|
}
|
|
118
112
|
}
|
|
119
113
|
catch {
|
|
120
|
-
|
|
114
|
+
return metadata;
|
|
121
115
|
}
|
|
122
116
|
}
|
|
123
117
|
return metadata;
|
|
@@ -129,13 +123,7 @@ function locateOperationSource(generatorType, generatorName, sourceRel) {
|
|
|
129
123
|
const moduleBasePath = generatorType === "framework"
|
|
130
124
|
? path.join(templatesPath, generatorName)
|
|
131
125
|
: path.join(modulesPath, generatorType, generatorName);
|
|
132
|
-
|
|
133
|
-
try {
|
|
134
|
-
return sourcePath;
|
|
135
|
-
}
|
|
136
|
-
catch {
|
|
137
|
-
return null;
|
|
138
|
-
}
|
|
126
|
+
return path.join(moduleBasePath, "files", sourceRel);
|
|
139
127
|
}
|
|
140
128
|
exports.default = {
|
|
141
129
|
mergeModuleIntoGeneratorConfig,
|