stackkit 0.1.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/README.md +41 -0
- package/bin/stackkit.js +4 -0
- package/dist/cli/add.d.ts +8 -0
- package/dist/cli/add.js +313 -0
- package/dist/cli/create.d.ts +22 -0
- package/dist/cli/create.js +336 -0
- package/dist/cli/doctor.d.ts +7 -0
- package/dist/cli/doctor.js +569 -0
- package/dist/cli/list.d.ts +6 -0
- package/dist/cli/list.js +123 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +91 -0
- package/dist/lib/conversion/js-conversion.d.ts +1 -0
- package/dist/lib/conversion/js-conversion.js +244 -0
- package/dist/lib/database/database-config.d.ts +6 -0
- package/dist/lib/database/database-config.js +9 -0
- package/dist/lib/discovery/module-discovery.d.ts +62 -0
- package/dist/lib/discovery/module-discovery.js +188 -0
- package/dist/lib/env/env-editor.d.ts +9 -0
- package/dist/lib/env/env-editor.js +116 -0
- package/dist/lib/framework/framework-utils.d.ts +22 -0
- package/dist/lib/framework/framework-utils.js +74 -0
- package/dist/lib/fs/files.d.ts +14 -0
- package/dist/lib/fs/files.js +101 -0
- package/dist/lib/generation/code-generator.d.ts +83 -0
- package/dist/lib/generation/code-generator.js +681 -0
- package/dist/lib/git-utils.d.ts +1 -0
- package/dist/lib/git-utils.js +9 -0
- package/dist/lib/pm/package-manager.d.ts +5 -0
- package/dist/lib/pm/package-manager.js +69 -0
- package/dist/lib/project/detect.d.ts +4 -0
- package/dist/lib/project/detect.js +121 -0
- package/dist/lib/ui/logger.d.ts +16 -0
- package/dist/lib/ui/logger.js +59 -0
- package/dist/types/index.d.ts +92 -0
- package/dist/types/index.js +2 -0
- package/modules/auth/authjs/files/api/auth/[...nextauth]/route.ts +6 -0
- package/modules/auth/authjs/files/lib/auth-client.ts +11 -0
- package/modules/auth/authjs/files/lib/auth.ts +36 -0
- package/modules/auth/authjs/files/schemas/prisma-schema.prisma +45 -0
- package/modules/auth/authjs/module.json +22 -0
- package/modules/auth/better-auth/files/api/auth/[...all]/route.ts +4 -0
- package/modules/auth/better-auth/files/lib/auth-client.ts +7 -0
- package/modules/auth/better-auth/files/lib/auth.ts +83 -0
- package/modules/auth/better-auth/files/lib/email-service.ts +34 -0
- package/modules/auth/better-auth/files/lib/email-templates.ts +89 -0
- package/modules/auth/better-auth/files/prisma/schema.prisma +63 -0
- package/modules/auth/better-auth/generator.json +78 -0
- package/modules/auth/better-auth/module.json +37 -0
- package/modules/database/mongoose/files/lib/db.ts +63 -0
- package/modules/database/mongoose/files/models/User.ts +34 -0
- package/modules/database/mongoose/generator.json +24 -0
- package/modules/database/mongoose/module.json +15 -0
- package/modules/database/prisma/files/lib/prisma.ts +45 -0
- package/modules/database/prisma/files/prisma/schema.prisma +8 -0
- package/modules/database/prisma/files/prisma.config.ts +12 -0
- package/modules/database/prisma/generator.json +43 -0
- package/modules/database/prisma/module.json +17 -0
- package/package.json +83 -0
- package/templates/express/.env.example +2 -0
- package/templates/express/eslint.config.cjs +42 -0
- package/templates/express/package.json +33 -0
- package/templates/express/src/app.ts +51 -0
- package/templates/express/src/config/env.ts +12 -0
- package/templates/express/src/features/health/health.controller.ts +18 -0
- package/templates/express/src/features/health/health.route.ts +9 -0
- package/templates/express/src/features/health/health.service.ts +6 -0
- package/templates/express/src/middlewares/error.middleware.ts +18 -0
- package/templates/express/src/server.ts +8 -0
- package/templates/express/template.json +27 -0
- package/templates/express/tsconfig.json +30 -0
- package/templates/nextjs/README.md +52 -0
- package/templates/nextjs/app/favicon.ico +0 -0
- package/templates/nextjs/app/globals.css +26 -0
- package/templates/nextjs/app/layout.tsx +30 -0
- package/templates/nextjs/app/page.tsx +57 -0
- package/templates/nextjs/eslint.config.mjs +18 -0
- package/templates/nextjs/lib/env.ts +8 -0
- package/templates/nextjs/next.config.ts +7 -0
- package/templates/nextjs/package.json +27 -0
- package/templates/nextjs/postcss.config.mjs +7 -0
- package/templates/nextjs/public/file.svg +1 -0
- package/templates/nextjs/public/globe.svg +1 -0
- package/templates/nextjs/public/next.svg +1 -0
- package/templates/nextjs/public/vercel.svg +1 -0
- package/templates/nextjs/public/window.svg +1 -0
- package/templates/nextjs/template.json +34 -0
- package/templates/nextjs/tsconfig.json +34 -0
- package/templates/react/.env.example +1 -0
- package/templates/react/.prettierignore +4 -0
- package/templates/react/.prettierrc +9 -0
- package/templates/react/README.md +56 -0
- package/templates/react/eslint.config.js +23 -0
- package/templates/react/index.html +14 -0
- package/templates/react/package.json +44 -0
- package/templates/react/public/vite.svg +1 -0
- package/templates/react/src/api/client.ts +47 -0
- package/templates/react/src/assets/react.svg +1 -0
- package/templates/react/src/components/ErrorBoundary.tsx +51 -0
- package/templates/react/src/components/Layout.tsx +13 -0
- package/templates/react/src/components/Loading.tsx +8 -0
- package/templates/react/src/components/SEO.tsx +49 -0
- package/templates/react/src/config/constants.ts +5 -0
- package/templates/react/src/hooks/index.ts +64 -0
- package/templates/react/src/index.css +1 -0
- package/templates/react/src/lib/queryClient.ts +12 -0
- package/templates/react/src/main.tsx +22 -0
- package/templates/react/src/pages/About.tsx +78 -0
- package/templates/react/src/pages/Home.tsx +49 -0
- package/templates/react/src/pages/NotFound.tsx +24 -0
- package/templates/react/src/router.tsx +21 -0
- package/templates/react/src/types/api.d.ts +20 -0
- package/templates/react/src/utils/helpers.ts +51 -0
- package/templates/react/src/utils/storage.ts +35 -0
- package/templates/react/src/vite-env.d.ts +11 -0
- package/templates/react/template.json +38 -0
- package/templates/react/tsconfig.app.json +28 -0
- package/templates/react/tsconfig.json +4 -0
- package/templates/react/tsconfig.node.json +26 -0
- package/templates/react/vite.config.ts +7 -0
- package/templates/react-vite/README.md +56 -0
|
@@ -0,0 +1,681 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.AdvancedCodeGenerator = void 0;
|
|
37
|
+
const fs = __importStar(require("fs-extra"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
class AdvancedCodeGenerator {
|
|
40
|
+
constructor(frameworkConfig) {
|
|
41
|
+
this.generators = new Map();
|
|
42
|
+
this.postInstallCommands = [];
|
|
43
|
+
this.frameworkConfig = frameworkConfig;
|
|
44
|
+
}
|
|
45
|
+
async loadGenerators(modulesPath) {
|
|
46
|
+
const moduleTypes = ['auth', 'database'];
|
|
47
|
+
// Load module generators
|
|
48
|
+
for (const type of moduleTypes) {
|
|
49
|
+
const typePath = path.join(modulesPath, type);
|
|
50
|
+
if (await fs.pathExists(typePath)) {
|
|
51
|
+
const modules = await fs.readdir(typePath);
|
|
52
|
+
for (const moduleName of modules) {
|
|
53
|
+
const generatorPath = path.join(typePath, moduleName, 'generator.json');
|
|
54
|
+
if (await fs.pathExists(generatorPath)) {
|
|
55
|
+
try {
|
|
56
|
+
const config = await fs.readJson(generatorPath);
|
|
57
|
+
// Also load module.json for additional metadata like postInstall
|
|
58
|
+
const modulePath = path.join(typePath, moduleName, 'module.json');
|
|
59
|
+
if (await fs.pathExists(modulePath)) {
|
|
60
|
+
try {
|
|
61
|
+
const moduleConfig = await fs.readJson(modulePath);
|
|
62
|
+
if (moduleConfig.postInstall && Array.isArray(moduleConfig.postInstall)) {
|
|
63
|
+
// Store postInstall commands with the generator for later use
|
|
64
|
+
config.postInstall = moduleConfig.postInstall;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// Silently skip if module.json is invalid
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
this.generators.set(`${type}:${moduleName}`, config);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// Silently skip invalid generator files
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
evaluateCondition(condition, context) {
|
|
82
|
+
if (!condition)
|
|
83
|
+
return true;
|
|
84
|
+
for (const [key, value] of Object.entries(condition)) {
|
|
85
|
+
if (key === 'features') {
|
|
86
|
+
const requiredFeatures = value;
|
|
87
|
+
const contextFeatures = context.features || [];
|
|
88
|
+
if (!requiredFeatures.every(feature => contextFeatures.includes(feature))) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
if (Array.isArray(value)) {
|
|
94
|
+
if (!value.includes(context[key])) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
if (context[key] !== value) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
processTemplate(content, context) {
|
|
108
|
+
// Create a copy of context for template variables
|
|
109
|
+
const templateContext = { ...context };
|
|
110
|
+
// Handle variable definitions {{#var name = value}} at the top of the file
|
|
111
|
+
content = this.processVariableDefinitions(content, templateContext);
|
|
112
|
+
// Process the rest of the template with the extended context
|
|
113
|
+
content = this.processTemplateRecursive(content, templateContext);
|
|
114
|
+
// Remove leading newlines that might be left from {{#var}} removal
|
|
115
|
+
content = content.replace(/^\n+/, '');
|
|
116
|
+
// Reduce multiple consecutive newlines to maximum 2
|
|
117
|
+
content = content.replace(/\n{3,}/g, '\n\n');
|
|
118
|
+
return content;
|
|
119
|
+
}
|
|
120
|
+
processVariableDefinitions(content, context) {
|
|
121
|
+
let result = content;
|
|
122
|
+
let index = 0;
|
|
123
|
+
while (true) {
|
|
124
|
+
const varStart = result.indexOf('{{#var ', index);
|
|
125
|
+
if (varStart === -1)
|
|
126
|
+
break;
|
|
127
|
+
const equalsIndex = result.indexOf('=', varStart);
|
|
128
|
+
if (equalsIndex === -1)
|
|
129
|
+
break;
|
|
130
|
+
const varNameMatch = result.substring(varStart + 7, equalsIndex).trim();
|
|
131
|
+
if (!varNameMatch)
|
|
132
|
+
break;
|
|
133
|
+
// Find the end of the variable value by counting braces
|
|
134
|
+
// Start with braceCount = 1 because {{#var is already open
|
|
135
|
+
let braceCount = 1;
|
|
136
|
+
const valueStart = equalsIndex + 1;
|
|
137
|
+
let valueEnd = valueStart;
|
|
138
|
+
for (let i = valueStart; i < result.length; i++) {
|
|
139
|
+
if (result[i] === '{' && result[i + 1] === '{') {
|
|
140
|
+
braceCount++;
|
|
141
|
+
i++; // Skip next character
|
|
142
|
+
}
|
|
143
|
+
else if (result[i] === '}' && result[i + 1] === '}') {
|
|
144
|
+
braceCount--;
|
|
145
|
+
if (braceCount === 0) {
|
|
146
|
+
valueEnd = i;
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
i++; // Skip next character
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (valueEnd === valueStart)
|
|
153
|
+
break;
|
|
154
|
+
const varValue = result.substring(valueStart, valueEnd).trim();
|
|
155
|
+
const fullMatch = result.substring(varStart, valueEnd + 2);
|
|
156
|
+
// Process the variable value with current context (allowing nested variables/conditionals)
|
|
157
|
+
const processedValue = this.processTemplateRecursive(varValue, context);
|
|
158
|
+
context[varNameMatch] = processedValue;
|
|
159
|
+
// Remove the variable definition
|
|
160
|
+
result = result.replace(fullMatch, '');
|
|
161
|
+
index = varStart;
|
|
162
|
+
}
|
|
163
|
+
return result;
|
|
164
|
+
}
|
|
165
|
+
processTemplateRecursive(content, context) {
|
|
166
|
+
content = content.replace(/\{\{#if\s+([^}\s]+)\s+([^}\s]+)\s+([^}]+)\}\}([\s\S]*?)\{\{\/if\}\}/g, (match, varName, operator, expectedValue, blockContent) => {
|
|
167
|
+
const actualVal = context[varName.trim()];
|
|
168
|
+
const cleanExpectedVal = expectedValue.trim().replace(/['"]/g, '');
|
|
169
|
+
let conditionMet = false;
|
|
170
|
+
switch (operator) {
|
|
171
|
+
case '==':
|
|
172
|
+
case '===':
|
|
173
|
+
conditionMet = actualVal === cleanExpectedVal;
|
|
174
|
+
break;
|
|
175
|
+
case '!=':
|
|
176
|
+
case '!==':
|
|
177
|
+
conditionMet = actualVal !== cleanExpectedVal;
|
|
178
|
+
break;
|
|
179
|
+
case 'includes':
|
|
180
|
+
conditionMet = Array.isArray(actualVal) && actualVal.includes(cleanExpectedVal);
|
|
181
|
+
break;
|
|
182
|
+
case 'startsWith':
|
|
183
|
+
conditionMet = typeof actualVal === 'string' && actualVal.startsWith(cleanExpectedVal);
|
|
184
|
+
break;
|
|
185
|
+
case 'endsWith':
|
|
186
|
+
conditionMet = typeof actualVal === 'string' && actualVal.endsWith(cleanExpectedVal);
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
return conditionMet ? this.processTemplateRecursive(blockContent, context).replace(/^\n+/, '').replace(/\n+$/, '') : '';
|
|
190
|
+
});
|
|
191
|
+
// Handle simple conditional blocks {{#if condition}}...{{/if}} (backward compatibility)
|
|
192
|
+
content = content.replace(/\{\{#if\s+([^}]+)\}\}([\s\S]*?)\{\{\/if\}\}/g, (match, condition, blockContent) => {
|
|
193
|
+
const conditionParts = condition.split('==');
|
|
194
|
+
if (conditionParts.length === 2) {
|
|
195
|
+
const [varName, expectedValue] = conditionParts.map((s) => s.trim().replace(/['"]/g, ''));
|
|
196
|
+
if (context[varName] === expectedValue) {
|
|
197
|
+
return this.processTemplateRecursive(blockContent, context).replace(/^\n+/, '').replace(/\n+$/, '');
|
|
198
|
+
}
|
|
199
|
+
return '';
|
|
200
|
+
}
|
|
201
|
+
const conditionFunc = condition.split('.');
|
|
202
|
+
if (conditionFunc.length === 2 && conditionFunc[1] === 'includes') {
|
|
203
|
+
const [arrayName, item] = conditionFunc[0].split('(');
|
|
204
|
+
const itemValue = item.replace(')', '').replace(/['"]/g, '');
|
|
205
|
+
const array = context[arrayName] || [];
|
|
206
|
+
if (Array.isArray(array) && array.includes(itemValue)) {
|
|
207
|
+
return this.processTemplateRecursive(blockContent, context).replace(/^\n+/, '').replace(/\n+$/, '');
|
|
208
|
+
}
|
|
209
|
+
return '';
|
|
210
|
+
}
|
|
211
|
+
return '';
|
|
212
|
+
});
|
|
213
|
+
// Handle switch statements {{#switch variable}}...{{/switch}}
|
|
214
|
+
content = content.replace(/\{\{#switch\s+([^}]+)\}\}([\s\S]*?)\{\{\/switch\}\}/g, (match, varName, switchContent) => {
|
|
215
|
+
const actualVal = context[varName.trim()];
|
|
216
|
+
// Parse cases
|
|
217
|
+
const caseRegex = /\{\{#case\s+([^}]+)\}\}([\s\S]*?)(?=\{\{#case|\{\{\/switch\})/g;
|
|
218
|
+
let result = '';
|
|
219
|
+
let defaultCase = '';
|
|
220
|
+
let caseMatch;
|
|
221
|
+
while ((caseMatch = caseRegex.exec(switchContent)) !== null) {
|
|
222
|
+
const [, caseValue, caseContent] = caseMatch;
|
|
223
|
+
const cleanCaseValue = caseValue.trim().replace(/['"]/g, '');
|
|
224
|
+
if (cleanCaseValue === 'default') {
|
|
225
|
+
defaultCase = caseContent;
|
|
226
|
+
}
|
|
227
|
+
else if (actualVal === cleanCaseValue) {
|
|
228
|
+
result = caseContent;
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return result || defaultCase || '';
|
|
233
|
+
});
|
|
234
|
+
// Handle variable replacement with advanced expressions
|
|
235
|
+
content = content.replace(/\{\{([^}]+)\}\}/g, (match, varExpr) => {
|
|
236
|
+
const trimmedExpr = varExpr.trim();
|
|
237
|
+
// Handle ternary expressions like framework=='nextjs' ? '@/lib' : '.'
|
|
238
|
+
const ternaryMatch = trimmedExpr.match(/^(.+?)\s*\?\s*(.+?)\s*:\s*(.+?)$/);
|
|
239
|
+
if (ternaryMatch) {
|
|
240
|
+
const [, condition, trueVal, falseVal] = ternaryMatch;
|
|
241
|
+
const conditionMatch = condition.match(/^(.+?)==(.+)$/);
|
|
242
|
+
if (conditionMatch) {
|
|
243
|
+
const [, varName, expectedVal] = conditionMatch;
|
|
244
|
+
const cleanVarName = varName.trim();
|
|
245
|
+
const cleanExpectedVal = expectedVal.trim().replace(/['"]/g, '');
|
|
246
|
+
const actualVal = context[cleanVarName];
|
|
247
|
+
const result = actualVal === cleanExpectedVal ? trueVal.trim() : falseVal.trim();
|
|
248
|
+
return result.replace(/['"]/g, ''); // Remove quotes from result
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
// Handle switch expressions {{switch variable case1: value1, case2: value2, default: defaultValue}}
|
|
252
|
+
const switchMatch = trimmedExpr.match(/^switch\s+([^}\s]+)\s+(.+)$/);
|
|
253
|
+
if (switchMatch) {
|
|
254
|
+
const [, varName, casesStr] = switchMatch;
|
|
255
|
+
const actualVal = context[varName.trim()];
|
|
256
|
+
const cases = casesStr.split(',').map((c) => c.trim());
|
|
257
|
+
for (const caseStr of cases) {
|
|
258
|
+
const [caseVal, result] = caseStr.split(':').map((s) => s.trim());
|
|
259
|
+
const cleanCaseVal = caseVal.replace(/['"]/g, '');
|
|
260
|
+
if (cleanCaseVal === actualVal || cleanCaseVal === 'default') {
|
|
261
|
+
return result.replace(/['"]/g, '');
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return '';
|
|
265
|
+
}
|
|
266
|
+
// Handle feature flags {{feature:name}}
|
|
267
|
+
if (trimmedExpr.startsWith('feature:')) {
|
|
268
|
+
const featureName = trimmedExpr.substring(8);
|
|
269
|
+
const features = context.features || [];
|
|
270
|
+
return features.includes(featureName) ? 'true' : 'false';
|
|
271
|
+
}
|
|
272
|
+
// Handle conditional expressions {{if condition then:value else:value}}
|
|
273
|
+
const conditionalMatch = trimmedExpr.match(/^if\s+(.+?)\s+then:([^,]+),\s*else:(.+)$/);
|
|
274
|
+
if (conditionalMatch) {
|
|
275
|
+
const [, condition, thenVal, elseVal] = conditionalMatch;
|
|
276
|
+
const conditionMatch2 = condition.match(/^(.+?)==(.+)$/);
|
|
277
|
+
if (conditionMatch2) {
|
|
278
|
+
const [, varName, expectedVal] = conditionMatch2;
|
|
279
|
+
const cleanVarName = varName.trim();
|
|
280
|
+
const cleanExpectedVal = expectedVal.trim().replace(/['"]/g, '');
|
|
281
|
+
const actualVal = context[cleanVarName];
|
|
282
|
+
const result = actualVal === cleanExpectedVal ? thenVal.trim() : elseVal.trim();
|
|
283
|
+
return result.replace(/['"]/g, '');
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
// Simple variable replacement
|
|
287
|
+
const value = context[trimmedExpr];
|
|
288
|
+
return value !== undefined ? String(value) : match;
|
|
289
|
+
});
|
|
290
|
+
return content;
|
|
291
|
+
}
|
|
292
|
+
async generate(selectedModules, features, outputPath) {
|
|
293
|
+
// First, copy the base template
|
|
294
|
+
await this.copyTemplate(selectedModules.framework, outputPath);
|
|
295
|
+
const context = {
|
|
296
|
+
...selectedModules,
|
|
297
|
+
features,
|
|
298
|
+
};
|
|
299
|
+
// Set default prismaProvider if database is prisma but no provider specified
|
|
300
|
+
if (selectedModules.database === 'prisma' && !context.prismaProvider) {
|
|
301
|
+
context.prismaProvider = 'postgresql';
|
|
302
|
+
}
|
|
303
|
+
// Collect all applicable operations
|
|
304
|
+
const applicableOperations = [];
|
|
305
|
+
for (const [key, generator] of this.generators) {
|
|
306
|
+
const [genType, name] = key.split(':');
|
|
307
|
+
// Check if this generator is selected
|
|
308
|
+
if (genType === 'framework' && name === selectedModules.framework) {
|
|
309
|
+
// Framework is always included
|
|
310
|
+
}
|
|
311
|
+
else if (genType === 'database' && name === selectedModules.database) {
|
|
312
|
+
// Database is selected
|
|
313
|
+
}
|
|
314
|
+
else if (genType === 'auth' && name === selectedModules.auth) {
|
|
315
|
+
// Auth is selected
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
continue; // Skip unselected generators
|
|
319
|
+
}
|
|
320
|
+
// Collect postInstall commands from selected generators
|
|
321
|
+
if (generator.postInstall && Array.isArray(generator.postInstall)) {
|
|
322
|
+
this.postInstallCommands.push(...generator.postInstall);
|
|
323
|
+
}
|
|
324
|
+
// Handle operations
|
|
325
|
+
const items = generator.operations || [];
|
|
326
|
+
for (const item of items) {
|
|
327
|
+
if (this.evaluateCondition(item.condition, context)) {
|
|
328
|
+
applicableOperations.push({
|
|
329
|
+
...item,
|
|
330
|
+
generator: name,
|
|
331
|
+
generatorType: genType,
|
|
332
|
+
priority: generator.priority,
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
// Sort operations by priority
|
|
338
|
+
applicableOperations.sort((a, b) => {
|
|
339
|
+
const priorityA = a.priority || 0;
|
|
340
|
+
const priorityB = b.priority || 0;
|
|
341
|
+
return priorityA - priorityB;
|
|
342
|
+
});
|
|
343
|
+
// Execute operations
|
|
344
|
+
for (const operation of applicableOperations) {
|
|
345
|
+
await this.executeOperation(operation, context, outputPath);
|
|
346
|
+
}
|
|
347
|
+
// Generate package.json updates
|
|
348
|
+
await this.generatePackageJson(selectedModules, features, outputPath);
|
|
349
|
+
return this.postInstallCommands;
|
|
350
|
+
}
|
|
351
|
+
async executeOperation(operation, context, outputPath) {
|
|
352
|
+
// Process templates in operation content
|
|
353
|
+
const processedOperation = this.processOperationTemplates(operation, context);
|
|
354
|
+
switch (processedOperation.type) {
|
|
355
|
+
case 'create-file':
|
|
356
|
+
await this.executeCreateFile(processedOperation, context, outputPath);
|
|
357
|
+
break;
|
|
358
|
+
case 'patch-file':
|
|
359
|
+
await this.executePatchFile(processedOperation, context, outputPath);
|
|
360
|
+
break;
|
|
361
|
+
case 'add-dependency':
|
|
362
|
+
await this.executeAddDependency(processedOperation, context, outputPath);
|
|
363
|
+
break;
|
|
364
|
+
case 'add-script':
|
|
365
|
+
await this.executeAddScript(processedOperation, context, outputPath);
|
|
366
|
+
break;
|
|
367
|
+
case 'add-env':
|
|
368
|
+
await this.executeAddEnv(processedOperation, context, outputPath);
|
|
369
|
+
break;
|
|
370
|
+
case 'run-command':
|
|
371
|
+
this.executeRunCommand(processedOperation, context);
|
|
372
|
+
break;
|
|
373
|
+
default:
|
|
374
|
+
// Unknown operation type - skip silently
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
async copyTemplate(frameworkName, outputPath) {
|
|
378
|
+
const candidates = [
|
|
379
|
+
path.resolve(__dirname, '..', '..', 'templates'), // dist/templates (when bundled)
|
|
380
|
+
path.resolve(__dirname, '..', '..', '..', 'templates'), // package root templates
|
|
381
|
+
];
|
|
382
|
+
let templateBase;
|
|
383
|
+
for (const c of candidates) {
|
|
384
|
+
const p = path.join(c, frameworkName);
|
|
385
|
+
if (await fs.pathExists(p)) {
|
|
386
|
+
templateBase = p;
|
|
387
|
+
break;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
if (!templateBase)
|
|
391
|
+
return;
|
|
392
|
+
await fs.copy(templateBase, outputPath, {
|
|
393
|
+
filter: (src) => {
|
|
394
|
+
const relativePath = path.relative(templateBase, src);
|
|
395
|
+
return relativePath !== 'template.json' &&
|
|
396
|
+
relativePath !== 'node_modules' &&
|
|
397
|
+
!relativePath.startsWith('node_modules/');
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
processOperationTemplates(operation, context) {
|
|
402
|
+
const processed = { ...operation };
|
|
403
|
+
// Process templates in string fields
|
|
404
|
+
if (processed.source) {
|
|
405
|
+
processed.source = this.processTemplate(processed.source, context);
|
|
406
|
+
}
|
|
407
|
+
if (processed.destination) {
|
|
408
|
+
processed.destination = this.processTemplate(processed.destination, context);
|
|
409
|
+
}
|
|
410
|
+
if (processed.content) {
|
|
411
|
+
processed.content = this.processTemplate(processed.content, context);
|
|
412
|
+
}
|
|
413
|
+
if (processed.destination) {
|
|
414
|
+
processed.destination = this.processTemplate(processed.destination, context);
|
|
415
|
+
}
|
|
416
|
+
// Process templates in patch operations
|
|
417
|
+
if (processed.operations) {
|
|
418
|
+
processed.operations = processed.operations.map(op => {
|
|
419
|
+
const processedOp = { ...op };
|
|
420
|
+
if (processedOp.imports) {
|
|
421
|
+
processedOp.imports = processedOp.imports.map(imp => this.processTemplate(imp, context));
|
|
422
|
+
}
|
|
423
|
+
if (processedOp.code) {
|
|
424
|
+
processedOp.code = this.processTemplate(processedOp.code, context);
|
|
425
|
+
}
|
|
426
|
+
if (processedOp.after) {
|
|
427
|
+
processedOp.after = this.processTemplate(processedOp.after, context);
|
|
428
|
+
}
|
|
429
|
+
if (processedOp.before) {
|
|
430
|
+
processedOp.before = this.processTemplate(processedOp.before, context);
|
|
431
|
+
}
|
|
432
|
+
if (processedOp.replace) {
|
|
433
|
+
processedOp.replace = this.processTemplate(processedOp.replace, context);
|
|
434
|
+
}
|
|
435
|
+
if (processedOp.content) {
|
|
436
|
+
processedOp.content = this.processTemplate(processedOp.content, context);
|
|
437
|
+
}
|
|
438
|
+
if (processedOp.source) {
|
|
439
|
+
processedOp.source = this.processTemplate(processedOp.source, context);
|
|
440
|
+
}
|
|
441
|
+
return processedOp;
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
return processed;
|
|
445
|
+
}
|
|
446
|
+
async executeCreateFile(operation, context, outputPath) {
|
|
447
|
+
if (!operation.destination)
|
|
448
|
+
return;
|
|
449
|
+
const destinationPath = path.join(outputPath, this.processTemplate(operation.destination, context));
|
|
450
|
+
// Ensure directory exists
|
|
451
|
+
await fs.ensureDir(path.dirname(destinationPath));
|
|
452
|
+
let content;
|
|
453
|
+
if (operation.content) {
|
|
454
|
+
// Use content directly
|
|
455
|
+
content = this.processTemplate(operation.content, context);
|
|
456
|
+
}
|
|
457
|
+
else if (operation.source) {
|
|
458
|
+
// Find the source file path relative to the module/template directory
|
|
459
|
+
// Resolve modules/templates base paths (try both dist and package root)
|
|
460
|
+
const modulesCandidates = [
|
|
461
|
+
path.resolve(__dirname, '..', '..', 'modules'),
|
|
462
|
+
path.resolve(__dirname, '..', '..', '..', 'modules'),
|
|
463
|
+
];
|
|
464
|
+
const templatesCandidates = [
|
|
465
|
+
path.resolve(__dirname, '..', '..', 'templates'),
|
|
466
|
+
path.resolve(__dirname, '..', '..', '..', 'templates'),
|
|
467
|
+
];
|
|
468
|
+
const resolveExisting = async (cands) => {
|
|
469
|
+
for (const c of cands) {
|
|
470
|
+
if (await fs.pathExists(c))
|
|
471
|
+
return c;
|
|
472
|
+
}
|
|
473
|
+
return undefined;
|
|
474
|
+
};
|
|
475
|
+
const modulesPathResolved = await resolveExisting(modulesCandidates);
|
|
476
|
+
const templatesPathResolved = await resolveExisting(templatesCandidates);
|
|
477
|
+
const moduleBasePath = operation.generatorType === 'framework'
|
|
478
|
+
? path.join(templatesPathResolved || templatesCandidates[0], operation.generator)
|
|
479
|
+
: path.join(modulesPathResolved || modulesCandidates[0], operation.generatorType, operation.generator);
|
|
480
|
+
const sourcePath = path.join(moduleBasePath, 'files', operation.source);
|
|
481
|
+
// Check if source file exists
|
|
482
|
+
if (await fs.pathExists(sourcePath)) {
|
|
483
|
+
// Read source file
|
|
484
|
+
content = await fs.readFile(sourcePath, 'utf-8');
|
|
485
|
+
// Process template content
|
|
486
|
+
content = this.processTemplate(content, context);
|
|
487
|
+
}
|
|
488
|
+
else {
|
|
489
|
+
throw new Error(`Source file not found: ${sourcePath}`);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
else {
|
|
493
|
+
throw new Error(`Create file operation must have either 'content' or 'source' field`);
|
|
494
|
+
}
|
|
495
|
+
// Write destination file
|
|
496
|
+
await fs.writeFile(destinationPath, content, 'utf-8');
|
|
497
|
+
}
|
|
498
|
+
async executePatchFile(operation, context, outputPath) {
|
|
499
|
+
if (!operation.destination)
|
|
500
|
+
return;
|
|
501
|
+
const filePath = path.join(outputPath, this.processTemplate(operation.destination, context));
|
|
502
|
+
// Read existing file
|
|
503
|
+
let content = await fs.readFile(filePath, 'utf-8');
|
|
504
|
+
if (operation.content) {
|
|
505
|
+
content += this.processTemplate(operation.content, context).trim();
|
|
506
|
+
}
|
|
507
|
+
else if (operation.operations) {
|
|
508
|
+
// Execute patch operations
|
|
509
|
+
for (const patchOp of operation.operations) {
|
|
510
|
+
if (!this.evaluateCondition(patchOp.condition, context))
|
|
511
|
+
continue;
|
|
512
|
+
switch (patchOp.type) {
|
|
513
|
+
case 'add-import':
|
|
514
|
+
if (patchOp.imports) {
|
|
515
|
+
const imports = patchOp.imports.map(imp => this.processTemplate(imp, context)).join('\n');
|
|
516
|
+
// Add imports at the top, after existing imports
|
|
517
|
+
const lines = content.split('\n');
|
|
518
|
+
let insertIndex = 0;
|
|
519
|
+
for (let i = 0; i < lines.length; i++) {
|
|
520
|
+
if (lines[i].trim().startsWith('import') || lines[i].trim() === '') {
|
|
521
|
+
insertIndex = i + 1;
|
|
522
|
+
}
|
|
523
|
+
else {
|
|
524
|
+
break;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
lines.splice(insertIndex, 0, imports);
|
|
528
|
+
content = lines.join('\n');
|
|
529
|
+
}
|
|
530
|
+
break;
|
|
531
|
+
case 'add-code':
|
|
532
|
+
if (patchOp.code && patchOp.after) {
|
|
533
|
+
const processedCode = this.processTemplate(patchOp.code, context);
|
|
534
|
+
const afterPattern = this.processTemplate(patchOp.after, context);
|
|
535
|
+
const index = content.indexOf(afterPattern);
|
|
536
|
+
if (index !== -1) {
|
|
537
|
+
content = content.slice(0, index + afterPattern.length) + processedCode + content.slice(index + afterPattern.length);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
break;
|
|
541
|
+
case 'replace-code':
|
|
542
|
+
if (patchOp.code && patchOp.replace) {
|
|
543
|
+
const processedCode = this.processTemplate(patchOp.code, context);
|
|
544
|
+
const replacePattern = this.processTemplate(patchOp.replace, context);
|
|
545
|
+
content = content.replace(replacePattern, processedCode);
|
|
546
|
+
}
|
|
547
|
+
break;
|
|
548
|
+
case 'add-to-top': {
|
|
549
|
+
let processedContentTop = '';
|
|
550
|
+
if (patchOp.content) {
|
|
551
|
+
processedContentTop = this.processTemplate(patchOp.content, context).trim();
|
|
552
|
+
}
|
|
553
|
+
else if (patchOp.source) {
|
|
554
|
+
const modulesPath = path.join(__dirname, '..', '..', 'modules');
|
|
555
|
+
const sourcePath = path.join(modulesPath, operation.generatorType, operation.generator, 'files', patchOp.source);
|
|
556
|
+
if (await fs.pathExists(sourcePath)) {
|
|
557
|
+
processedContentTop = await fs.readFile(sourcePath, 'utf-8');
|
|
558
|
+
processedContentTop = this.processTemplate(processedContentTop, context).trim();
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
if (processedContentTop) {
|
|
562
|
+
content = processedContentTop + '\n' + content;
|
|
563
|
+
}
|
|
564
|
+
break;
|
|
565
|
+
}
|
|
566
|
+
case 'add-to-bottom': {
|
|
567
|
+
let processedContentBottom = '';
|
|
568
|
+
if (patchOp.content) {
|
|
569
|
+
processedContentBottom = this.processTemplate(patchOp.content, context).trim();
|
|
570
|
+
}
|
|
571
|
+
else if (patchOp.source) {
|
|
572
|
+
const modulesPath = path.join(__dirname, '..', '..', 'modules');
|
|
573
|
+
const sourcePath = path.join(modulesPath, operation.generatorType, operation.generator, 'files', patchOp.source);
|
|
574
|
+
if (await fs.pathExists(sourcePath)) {
|
|
575
|
+
processedContentBottom = await fs.readFile(sourcePath, 'utf-8');
|
|
576
|
+
processedContentBottom = this.processTemplate(processedContentBottom, context).trim();
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
if (processedContentBottom) {
|
|
580
|
+
content = content + '\n' + processedContentBottom;
|
|
581
|
+
}
|
|
582
|
+
break;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
// Write back the modified content
|
|
588
|
+
await fs.writeFile(filePath, content, 'utf-8');
|
|
589
|
+
}
|
|
590
|
+
async executeAddDependency(operation, context, outputPath) {
|
|
591
|
+
const packageJsonPath = path.join(outputPath, 'package.json');
|
|
592
|
+
let packageJson = {};
|
|
593
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
594
|
+
packageJson = await fs.readJson(packageJsonPath);
|
|
595
|
+
}
|
|
596
|
+
if (operation.dependencies) {
|
|
597
|
+
packageJson.dependencies = { ...(packageJson.dependencies || {}), ...operation.dependencies };
|
|
598
|
+
}
|
|
599
|
+
if (operation.devDependencies) {
|
|
600
|
+
packageJson.devDependencies = { ...(packageJson.devDependencies || {}), ...operation.devDependencies };
|
|
601
|
+
}
|
|
602
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
603
|
+
}
|
|
604
|
+
async executeAddScript(operation, context, outputPath) {
|
|
605
|
+
const packageJsonPath = path.join(outputPath, 'package.json');
|
|
606
|
+
let packageJson = {};
|
|
607
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
608
|
+
packageJson = await fs.readJson(packageJsonPath);
|
|
609
|
+
}
|
|
610
|
+
if (operation.scripts) {
|
|
611
|
+
packageJson.scripts = { ...(packageJson.scripts || {}), ...operation.scripts };
|
|
612
|
+
}
|
|
613
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
614
|
+
}
|
|
615
|
+
async executeAddEnv(operation, context, outputPath) {
|
|
616
|
+
const envPath = path.join(outputPath, '.env');
|
|
617
|
+
let envContent = '';
|
|
618
|
+
if (await fs.pathExists(envPath)) {
|
|
619
|
+
envContent = await fs.readFile(envPath, 'utf-8');
|
|
620
|
+
}
|
|
621
|
+
if (operation.envVars) {
|
|
622
|
+
const envLines = Object.entries(operation.envVars).map(([key, value]) => `${key}=${value}`);
|
|
623
|
+
envContent += '\n' + envLines.join('\n');
|
|
624
|
+
}
|
|
625
|
+
await fs.writeFile(envPath, envContent.trim(), 'utf-8');
|
|
626
|
+
}
|
|
627
|
+
executeRunCommand(operation, context) {
|
|
628
|
+
if (operation.command) {
|
|
629
|
+
// Process template variables in the command
|
|
630
|
+
const processedCommand = this.processTemplate(operation.command, context);
|
|
631
|
+
this.postInstallCommands.push(processedCommand);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
async generatePackageJson(selectedModules, features, outputPath) {
|
|
635
|
+
const packageJsonPath = path.join(outputPath, 'package.json');
|
|
636
|
+
let packageJson = {};
|
|
637
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
638
|
+
packageJson = await fs.readJson(packageJsonPath);
|
|
639
|
+
}
|
|
640
|
+
// Collect dependencies from selected generators
|
|
641
|
+
const allDeps = {};
|
|
642
|
+
const allDevDeps = {};
|
|
643
|
+
const allScripts = {};
|
|
644
|
+
for (const [key, generator] of this.generators) {
|
|
645
|
+
const [type, name] = key.split(':');
|
|
646
|
+
if ((type === 'framework' && name === selectedModules.framework) ||
|
|
647
|
+
(type === 'database' && name === selectedModules.database) ||
|
|
648
|
+
(type === 'auth' && name === selectedModules.auth)) {
|
|
649
|
+
Object.assign(allDeps, generator.dependencies);
|
|
650
|
+
Object.assign(allDevDeps, generator.devDependencies);
|
|
651
|
+
Object.assign(allScripts, generator.scripts);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
// Update package.json
|
|
655
|
+
packageJson.dependencies = { ...(packageJson.dependencies || {}), ...allDeps };
|
|
656
|
+
packageJson.devDependencies = { ...(packageJson.devDependencies || {}), ...allDevDeps };
|
|
657
|
+
packageJson.scripts = { ...(packageJson.scripts || {}), ...allScripts };
|
|
658
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
659
|
+
}
|
|
660
|
+
getAvailableGenerators() {
|
|
661
|
+
const frameworks = [];
|
|
662
|
+
const databases = [];
|
|
663
|
+
const auths = [];
|
|
664
|
+
for (const [key] of this.generators) {
|
|
665
|
+
const [type, name] = key.split(':');
|
|
666
|
+
switch (type) {
|
|
667
|
+
case 'framework':
|
|
668
|
+
frameworks.push(name);
|
|
669
|
+
break;
|
|
670
|
+
case 'database':
|
|
671
|
+
databases.push(name);
|
|
672
|
+
break;
|
|
673
|
+
case 'auth':
|
|
674
|
+
auths.push(name);
|
|
675
|
+
break;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
return { frameworks, databases, auths };
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
exports.AdvancedCodeGenerator = AdvancedCodeGenerator;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function initGit(cwd: string): Promise<void>;
|