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.
- package/README.md +5 -3
- package/dist/cli/add.d.ts +2 -1
- package/dist/cli/add.js +325 -102
- package/dist/cli/create.d.ts +2 -2
- package/dist/cli/create.js +96 -30
- package/dist/cli/doctor.js +25 -17
- package/dist/cli/list.js +14 -2
- package/dist/index.js +22 -3
- package/dist/lib/conversion/js-conversion.js +2 -2
- package/dist/lib/discovery/module-discovery.d.ts +0 -1
- package/dist/lib/discovery/module-discovery.js +35 -35
- package/dist/lib/env/env-editor.js +1 -1
- package/dist/lib/framework/framework-utils.d.ts +1 -1
- package/dist/lib/framework/framework-utils.js +3 -3
- package/dist/lib/generation/code-generator.d.ts +18 -4
- package/dist/lib/generation/code-generator.js +212 -147
- package/dist/lib/generation/generator-utils.d.ts +11 -0
- package/dist/lib/generation/generator-utils.js +124 -0
- package/dist/lib/git-utils.js +20 -0
- package/dist/lib/project/detect.js +2 -4
- package/dist/lib/utils/package-root.js +2 -2
- package/dist/types/index.d.ts +0 -10
- package/modules/auth/authjs/generator.json +10 -0
- package/modules/auth/authjs/module.json +0 -9
- package/modules/auth/better-auth/files/lib/auth.ts +38 -31
- package/modules/auth/better-auth/files/prisma/schema.prisma +3 -3
- package/modules/auth/better-auth/generator.json +58 -28
- package/modules/auth/better-auth/module.json +0 -24
- package/modules/database/mongoose/files/models/health.ts +34 -0
- package/modules/database/mongoose/generator.json +27 -8
- package/modules/database/mongoose/module.json +1 -2
- package/modules/database/prisma/files/lib/prisma.ts +27 -21
- package/modules/database/prisma/files/prisma.config.ts +17 -0
- package/modules/database/prisma/generator.json +79 -15
- package/modules/database/prisma/module.json +1 -4
- package/package.json +1 -1
- package/templates/express/src/server.ts +9 -3
- package/templates/express/tsconfig.json +2 -23
- package/templates/nextjs/lib/env.ts +1 -1
- package/dist/lib/database/database-config.d.ts +0 -6
- package/dist/lib/database/database-config.js +0 -9
- package/modules/database/mongoose/files/models/User.ts +0 -34
- /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 = [
|
|
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,
|
|
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
|
|
58
|
-
|
|
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 ===
|
|
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,
|
|
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(
|
|
113
|
+
const varStart = result.indexOf("{{#var ", index);
|
|
123
114
|
if (varStart === -1)
|
|
124
115
|
break;
|
|
125
|
-
const equalsIndex = result.indexOf(
|
|
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] ===
|
|
128
|
+
if (result[i] === "{" && result[i + 1] === "{") {
|
|
138
129
|
braceCount++;
|
|
139
130
|
i++; // Skip next character
|
|
140
131
|
}
|
|
141
|
-
else if (result[i] ===
|
|
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
|
|
168
|
+
case "includes":
|
|
178
169
|
conditionMet = Array.isArray(actualVal) && actualVal.includes(cleanExpectedVal);
|
|
179
170
|
break;
|
|
180
|
-
case
|
|
181
|
-
conditionMet = typeof actualVal ===
|
|
171
|
+
case "startsWith":
|
|
172
|
+
conditionMet = typeof actualVal === "string" && actualVal.startsWith(cleanExpectedVal);
|
|
182
173
|
break;
|
|
183
|
-
case
|
|
184
|
-
conditionMet = typeof actualVal ===
|
|
174
|
+
case "endsWith":
|
|
175
|
+
conditionMet = typeof actualVal === "string" && actualVal.endsWith(cleanExpectedVal);
|
|
185
176
|
break;
|
|
186
177
|
}
|
|
187
|
-
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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] ===
|
|
201
|
-
const [arrayName, item] = conditionFunc[0].split(
|
|
202
|
-
const itemValue = item.replace(
|
|
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
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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 ===
|
|
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,
|
|
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(
|
|
248
|
+
const cases = casesStr.split(",").map((c) => c.trim());
|
|
255
249
|
for (const caseStr of cases) {
|
|
256
|
-
const [caseVal, result] = caseStr.split(
|
|
257
|
-
const cleanCaseVal = caseVal.replace(/['"]/g,
|
|
258
|
-
if (cleanCaseVal === actualVal || cleanCaseVal ===
|
|
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(
|
|
259
|
+
if (trimmedExpr.startsWith("feature:")) {
|
|
266
260
|
const featureName = trimmedExpr.substring(8);
|
|
267
261
|
const features = context.features || [];
|
|
268
|
-
return features.includes(featureName) ?
|
|
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 ===
|
|
299
|
-
context.prismaProvider =
|
|
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 ===
|
|
300
|
+
if (genType === "framework" && name === selectedModules.framework) {
|
|
307
301
|
// Framework is always included
|
|
308
302
|
}
|
|
309
|
-
else if (genType ===
|
|
303
|
+
else if (genType === "database" && name === selectedModules.database) {
|
|
310
304
|
// Database is selected
|
|
311
305
|
}
|
|
312
|
-
else if (genType ===
|
|
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
|
|
347
|
+
case "create-file":
|
|
354
348
|
await this.executeCreateFile(processedOperation, context, outputPath);
|
|
355
349
|
break;
|
|
356
|
-
case
|
|
350
|
+
case "patch-file":
|
|
357
351
|
await this.executePatchFile(processedOperation, context, outputPath);
|
|
358
352
|
break;
|
|
359
|
-
case
|
|
353
|
+
case "add-dependency":
|
|
360
354
|
await this.executeAddDependency(processedOperation, context, outputPath);
|
|
361
355
|
break;
|
|
362
|
-
case
|
|
356
|
+
case "add-script":
|
|
363
357
|
await this.executeAddScript(processedOperation, context, outputPath);
|
|
364
358
|
break;
|
|
365
|
-
case
|
|
359
|
+
case "add-env":
|
|
366
360
|
await this.executeAddEnv(processedOperation, context, outputPath);
|
|
367
361
|
break;
|
|
368
|
-
case
|
|
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,
|
|
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 !==
|
|
383
|
-
relativePath !==
|
|
384
|
-
!relativePath.startsWith(
|
|
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
|
-
|
|
447
|
-
|
|
448
|
-
|
|
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,
|
|
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,
|
|
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
|
|
470
|
+
case "add-import":
|
|
487
471
|
if (patchOp.imports) {
|
|
488
|
-
const imports = patchOp.imports
|
|
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(
|
|
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(
|
|
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(
|
|
487
|
+
content = lines.join("\n");
|
|
502
488
|
}
|
|
503
489
|
break;
|
|
504
|
-
case
|
|
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 =
|
|
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
|
|
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
|
|
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)(),
|
|
528
|
-
const sourcePath = path.join(modulesPath, operation.generatorType, operation.generator,
|
|
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,
|
|
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 +
|
|
524
|
+
content = processedContentTop + "\n" + content;
|
|
536
525
|
}
|
|
537
526
|
break;
|
|
538
527
|
}
|
|
539
|
-
case
|
|
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)(),
|
|
546
|
-
const sourcePath = path.join(modulesPath, operation.generatorType, operation.generator,
|
|
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,
|
|
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 +
|
|
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,
|
|
550
|
+
await fs.writeFile(filePath, content, "utf-8");
|
|
562
551
|
}
|
|
563
552
|
async executeAddDependency(operation, context, outputPath) {
|
|
564
|
-
const packageJsonPath = path.join(outputPath,
|
|
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 = {
|
|
559
|
+
packageJson.dependencies = {
|
|
560
|
+
...(packageJson.dependencies || {}),
|
|
561
|
+
...operation.dependencies,
|
|
562
|
+
};
|
|
571
563
|
}
|
|
572
564
|
if (operation.devDependencies) {
|
|
573
|
-
packageJson.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,
|
|
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 = {
|
|
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,
|
|
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,
|
|
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]) =>
|
|
596
|
-
|
|
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(),
|
|
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,
|
|
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 ===
|
|
620
|
-
(type ===
|
|
621
|
-
(type ===
|
|
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 = {
|
|
629
|
-
|
|
630
|
-
|
|
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
|
|
699
|
+
case "framework":
|
|
641
700
|
frameworks.push(name);
|
|
642
701
|
break;
|
|
643
|
-
case
|
|
702
|
+
case "database":
|
|
644
703
|
databases.push(name);
|
|
645
704
|
break;
|
|
646
|
-
case
|
|
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;
|