create-stackkit-app 0.4.5 → 0.4.6
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/dist/index.js +1 -2
- package/dist/lib/code-generator.d.ts +80 -0
- package/dist/lib/code-generator.js +541 -0
- package/dist/lib/create-project.js +57 -84
- package/dist/lib/framework-utils.d.ts +22 -0
- package/dist/lib/framework-utils.js +74 -0
- package/dist/lib/utils/module-discovery.d.ts +62 -0
- package/dist/lib/utils/module-discovery.js +180 -0
- package/modules/auth/authjs/files/lib/auth.ts +0 -5
- package/modules/auth/authjs/module.json +6 -79
- package/modules/auth/better-auth/files/lib/auth.ts +17 -3
- package/modules/auth/better-auth/files/schemas/prisma-schema.prisma +6 -6
- package/modules/auth/better-auth/generator.json +83 -0
- package/modules/auth/better-auth/module.json +6 -160
- package/modules/database/mongoose/files/lib/db.ts +2 -7
- package/modules/database/mongoose/generator.json +33 -0
- package/modules/database/mongoose/module.json +6 -46
- package/modules/database/prisma/files/lib/prisma.ts +9 -4
- package/modules/database/prisma/generator.json +46 -0
- package/modules/database/prisma/module.json +6 -114
- package/package.json +1 -1
- package/templates/express/template.json +6 -19
- package/templates/nextjs/template.json +5 -1
- package/templates/react-vite/template.json +6 -14
- package/dist/lib/utils/config-utils.d.ts +0 -2
- package/dist/lib/utils/config-utils.js +0 -88
- package/dist/lib/utils/file-utils.d.ts +0 -8
- package/dist/lib/utils/file-utils.js +0 -75
- package/dist/lib/utils/module-utils.d.ts +0 -2
- package/dist/lib/utils/module-utils.js +0 -461
package/dist/index.js
CHANGED
|
@@ -47,7 +47,7 @@ Usage:
|
|
|
47
47
|
|
|
48
48
|
Options:
|
|
49
49
|
-f, --framework <framework> Framework: nextjs, express, react-vite
|
|
50
|
-
-d, --database <database> Database: prisma
|
|
50
|
+
-d, --database <database> Database: prisma, mongoose, none
|
|
51
51
|
-a, --auth <auth> Auth: better-auth, authjs, none
|
|
52
52
|
-l, --language <language> Language: typescript, javascript
|
|
53
53
|
-p, --package-manager <pm> Package manager: pnpm, npm, yarn, bun
|
|
@@ -58,7 +58,6 @@ Options:
|
|
|
58
58
|
|
|
59
59
|
Examples:
|
|
60
60
|
create-stackkit-app my-app --framework nextjs --database prisma-postgresql --auth better-auth
|
|
61
|
-
create-stackkit-app --help
|
|
62
61
|
`);
|
|
63
62
|
}
|
|
64
63
|
async function main() {
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { FrameworkConfig } from './framework-utils';
|
|
2
|
+
export interface GenerationContext {
|
|
3
|
+
framework: string;
|
|
4
|
+
database?: string;
|
|
5
|
+
auth?: string;
|
|
6
|
+
features?: string[];
|
|
7
|
+
[key: string]: unknown;
|
|
8
|
+
}
|
|
9
|
+
export interface TemplateCondition {
|
|
10
|
+
framework?: string;
|
|
11
|
+
database?: string;
|
|
12
|
+
auth?: string;
|
|
13
|
+
features?: string[];
|
|
14
|
+
}
|
|
15
|
+
export interface Operation {
|
|
16
|
+
type: 'create-file' | 'patch-file' | 'add-dependency' | 'add-script' | 'add-env' | 'run-command';
|
|
17
|
+
description?: string;
|
|
18
|
+
condition?: TemplateCondition;
|
|
19
|
+
priority?: number;
|
|
20
|
+
source?: string;
|
|
21
|
+
destination?: string;
|
|
22
|
+
content?: string;
|
|
23
|
+
file?: string;
|
|
24
|
+
operations?: PatchOperation[];
|
|
25
|
+
dependencies?: Record<string, string>;
|
|
26
|
+
devDependencies?: Record<string, string>;
|
|
27
|
+
scripts?: Record<string, string>;
|
|
28
|
+
envVars?: Record<string, string>;
|
|
29
|
+
command?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface PatchOperation {
|
|
32
|
+
type: 'add-import' | 'add-code' | 'replace-code' | 'add-to-top' | 'add-to-bottom';
|
|
33
|
+
condition?: TemplateCondition;
|
|
34
|
+
imports?: string[];
|
|
35
|
+
code?: string;
|
|
36
|
+
after?: string;
|
|
37
|
+
before?: string;
|
|
38
|
+
replace?: string;
|
|
39
|
+
content?: string;
|
|
40
|
+
}
|
|
41
|
+
export interface GeneratorConfig {
|
|
42
|
+
name: string;
|
|
43
|
+
type: 'framework' | 'database' | 'auth';
|
|
44
|
+
priority: number;
|
|
45
|
+
operations?: Operation[];
|
|
46
|
+
dependencies?: Record<string, string>;
|
|
47
|
+
devDependencies?: Record<string, string>;
|
|
48
|
+
scripts?: Record<string, string>;
|
|
49
|
+
envVars?: Record<string, string>;
|
|
50
|
+
}
|
|
51
|
+
export declare class AdvancedCodeGenerator {
|
|
52
|
+
private generators;
|
|
53
|
+
private frameworkConfig;
|
|
54
|
+
private postInstallCommands;
|
|
55
|
+
constructor(frameworkConfig: FrameworkConfig);
|
|
56
|
+
loadGenerators(modulesPath: string): Promise<void>;
|
|
57
|
+
private evaluateCondition;
|
|
58
|
+
private processTemplate;
|
|
59
|
+
generate(selectedModules: {
|
|
60
|
+
framework: string;
|
|
61
|
+
database?: string;
|
|
62
|
+
auth?: string;
|
|
63
|
+
prismaProvider?: string;
|
|
64
|
+
}, features: string[], outputPath: string): Promise<string[]>;
|
|
65
|
+
private executeOperation;
|
|
66
|
+
private copyTemplate;
|
|
67
|
+
private processOperationTemplates;
|
|
68
|
+
private executeCreateFile;
|
|
69
|
+
private executePatchFile;
|
|
70
|
+
private executeAddDependency;
|
|
71
|
+
private executeAddScript;
|
|
72
|
+
private executeAddEnv;
|
|
73
|
+
private executeRunCommand;
|
|
74
|
+
private generatePackageJson;
|
|
75
|
+
getAvailableGenerators(): {
|
|
76
|
+
frameworks: string[];
|
|
77
|
+
databases: string[];
|
|
78
|
+
auths: string[];
|
|
79
|
+
};
|
|
80
|
+
}
|
|
@@ -0,0 +1,541 @@
|
|
|
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
|
+
this.generators.set(`${type}:${moduleName}`, config);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// Silently skip invalid generator files
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
evaluateCondition(condition, context) {
|
|
68
|
+
if (!condition)
|
|
69
|
+
return true;
|
|
70
|
+
for (const [key, value] of Object.entries(condition)) {
|
|
71
|
+
if (key === 'features') {
|
|
72
|
+
const requiredFeatures = value;
|
|
73
|
+
const contextFeatures = context.features || [];
|
|
74
|
+
if (!requiredFeatures.every(feature => contextFeatures.includes(feature))) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
if (context[key] !== value) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
processTemplate(content, context) {
|
|
87
|
+
// Handle advanced conditional blocks {{#if condition operator value}}...{{/if}}
|
|
88
|
+
content = content.replace(/\{\{#if\s+([^}\s]+)\s+([^}\s]+)\s+([^}]+)\}\}([\s\S]*?)\{\{\/if\}\}/g, (match, varName, operator, expectedValue, blockContent) => {
|
|
89
|
+
const actualVal = context[varName.trim()];
|
|
90
|
+
const cleanExpectedVal = expectedValue.trim().replace(/['"]/g, '');
|
|
91
|
+
let conditionMet = false;
|
|
92
|
+
switch (operator) {
|
|
93
|
+
case '==':
|
|
94
|
+
case '===':
|
|
95
|
+
conditionMet = actualVal === cleanExpectedVal;
|
|
96
|
+
break;
|
|
97
|
+
case '!=':
|
|
98
|
+
case '!==':
|
|
99
|
+
conditionMet = actualVal !== cleanExpectedVal;
|
|
100
|
+
break;
|
|
101
|
+
case 'includes':
|
|
102
|
+
conditionMet = Array.isArray(actualVal) && actualVal.includes(cleanExpectedVal);
|
|
103
|
+
break;
|
|
104
|
+
case 'startsWith':
|
|
105
|
+
conditionMet = typeof actualVal === 'string' && actualVal.startsWith(cleanExpectedVal);
|
|
106
|
+
break;
|
|
107
|
+
case 'endsWith':
|
|
108
|
+
conditionMet = typeof actualVal === 'string' && actualVal.endsWith(cleanExpectedVal);
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
return conditionMet ? this.processTemplate(blockContent, context) : '';
|
|
112
|
+
});
|
|
113
|
+
// Handle simple conditional blocks {{#if condition}}...{{/if}} (backward compatibility)
|
|
114
|
+
content = content.replace(/\{\{#if\s+([^}]+)\}\}([\s\S]*?)\{\{\/if\}\}/g, (match, condition, blockContent) => {
|
|
115
|
+
const conditionParts = condition.split('==');
|
|
116
|
+
if (conditionParts.length === 2) {
|
|
117
|
+
const [varName, expectedValue] = conditionParts.map((s) => s.trim().replace(/['"]/g, ''));
|
|
118
|
+
if (context[varName] === expectedValue) {
|
|
119
|
+
return this.processTemplate(blockContent, context);
|
|
120
|
+
}
|
|
121
|
+
return '';
|
|
122
|
+
}
|
|
123
|
+
const conditionFunc = condition.split('.');
|
|
124
|
+
if (conditionFunc.length === 2 && conditionFunc[1] === 'includes') {
|
|
125
|
+
const [arrayName, item] = conditionFunc[0].split('(');
|
|
126
|
+
const itemValue = item.replace(')', '').replace(/['"]/g, '');
|
|
127
|
+
const array = context[arrayName] || [];
|
|
128
|
+
if (Array.isArray(array) && array.includes(itemValue)) {
|
|
129
|
+
return this.processTemplate(blockContent, context);
|
|
130
|
+
}
|
|
131
|
+
return '';
|
|
132
|
+
}
|
|
133
|
+
return '';
|
|
134
|
+
});
|
|
135
|
+
// Handle switch statements {{#switch variable}}...{{/switch}}
|
|
136
|
+
content = content.replace(/\{\{#switch\s+([^}]+)\}\}([\s\S]*?)\{\{\/switch\}\}/g, (match, varName, switchContent) => {
|
|
137
|
+
const actualVal = context[varName.trim()];
|
|
138
|
+
// Parse cases
|
|
139
|
+
const caseRegex = /\{\{#case\s+([^}]+)\}\}([\s\S]*?)(?=\{\{#case|\{\{\/switch\})/g;
|
|
140
|
+
let result = '';
|
|
141
|
+
let defaultCase = '';
|
|
142
|
+
let caseMatch;
|
|
143
|
+
while ((caseMatch = caseRegex.exec(switchContent)) !== null) {
|
|
144
|
+
const [, caseValue, caseContent] = caseMatch;
|
|
145
|
+
const cleanCaseValue = caseValue.trim().replace(/['"]/g, '');
|
|
146
|
+
if (cleanCaseValue === 'default') {
|
|
147
|
+
defaultCase = caseContent;
|
|
148
|
+
}
|
|
149
|
+
else if (actualVal === cleanCaseValue) {
|
|
150
|
+
result = caseContent;
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return result || defaultCase || '';
|
|
155
|
+
});
|
|
156
|
+
// Handle variable replacement with advanced expressions
|
|
157
|
+
content = content.replace(/\{\{([^}]+)\}\}/g, (match, varExpr) => {
|
|
158
|
+
const trimmedExpr = varExpr.trim();
|
|
159
|
+
// Handle ternary expressions like framework=='nextjs' ? '@/lib' : '.'
|
|
160
|
+
const ternaryMatch = trimmedExpr.match(/^(.+?)\s*\?\s*(.+?)\s*:\s*(.+?)$/);
|
|
161
|
+
if (ternaryMatch) {
|
|
162
|
+
const [, condition, trueVal, falseVal] = ternaryMatch;
|
|
163
|
+
const conditionMatch = condition.match(/^(.+?)==(.+)$/);
|
|
164
|
+
if (conditionMatch) {
|
|
165
|
+
const [, varName, expectedVal] = conditionMatch;
|
|
166
|
+
const cleanVarName = varName.trim();
|
|
167
|
+
const cleanExpectedVal = expectedVal.trim().replace(/['"]/g, '');
|
|
168
|
+
const actualVal = context[cleanVarName];
|
|
169
|
+
const result = actualVal === cleanExpectedVal ? trueVal.trim() : falseVal.trim();
|
|
170
|
+
return result.replace(/['"]/g, ''); // Remove quotes from result
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
// Handle switch expressions {{switch variable case1: value1, case2: value2, default: defaultValue}}
|
|
174
|
+
const switchMatch = trimmedExpr.match(/^switch\s+([^}\s]+)\s+(.+)$/);
|
|
175
|
+
if (switchMatch) {
|
|
176
|
+
const [, varName, casesStr] = switchMatch;
|
|
177
|
+
const actualVal = context[varName.trim()];
|
|
178
|
+
const cases = casesStr.split(',').map((c) => c.trim());
|
|
179
|
+
for (const caseStr of cases) {
|
|
180
|
+
const [caseVal, result] = caseStr.split(':').map((s) => s.trim());
|
|
181
|
+
const cleanCaseVal = caseVal.replace(/['"]/g, '');
|
|
182
|
+
if (cleanCaseVal === actualVal || cleanCaseVal === 'default') {
|
|
183
|
+
return result.replace(/['"]/g, '');
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return '';
|
|
187
|
+
}
|
|
188
|
+
// Handle feature flags {{feature:name}}
|
|
189
|
+
if (trimmedExpr.startsWith('feature:')) {
|
|
190
|
+
const featureName = trimmedExpr.substring(8);
|
|
191
|
+
const features = context.features || [];
|
|
192
|
+
return features.includes(featureName) ? 'true' : 'false';
|
|
193
|
+
}
|
|
194
|
+
// Handle conditional expressions {{if condition then:value else:value}}
|
|
195
|
+
const conditionalMatch = trimmedExpr.match(/^if\s+(.+?)\s+then:([^,]+),\s*else:(.+)$/);
|
|
196
|
+
if (conditionalMatch) {
|
|
197
|
+
const [, condition, thenVal, elseVal] = conditionalMatch;
|
|
198
|
+
const conditionMatch2 = condition.match(/^(.+?)==(.+)$/);
|
|
199
|
+
if (conditionMatch2) {
|
|
200
|
+
const [, varName, expectedVal] = conditionMatch2;
|
|
201
|
+
const cleanVarName = varName.trim();
|
|
202
|
+
const cleanExpectedVal = expectedVal.trim().replace(/['"]/g, '');
|
|
203
|
+
const actualVal = context[cleanVarName];
|
|
204
|
+
const result = actualVal === cleanExpectedVal ? thenVal.trim() : elseVal.trim();
|
|
205
|
+
return result.replace(/['"]/g, '');
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
// Simple variable replacement
|
|
209
|
+
const value = context[trimmedExpr];
|
|
210
|
+
return value !== undefined ? String(value) : match;
|
|
211
|
+
});
|
|
212
|
+
return content;
|
|
213
|
+
}
|
|
214
|
+
async generate(selectedModules, features, outputPath) {
|
|
215
|
+
// First, copy the base template
|
|
216
|
+
await this.copyTemplate(selectedModules.framework, outputPath);
|
|
217
|
+
const context = {
|
|
218
|
+
...selectedModules,
|
|
219
|
+
features,
|
|
220
|
+
};
|
|
221
|
+
// Add prismaProvider if prismaProvider is specified
|
|
222
|
+
if (selectedModules.prismaProvider) {
|
|
223
|
+
context.prismaProvider = selectedModules.prismaProvider;
|
|
224
|
+
}
|
|
225
|
+
// Collect all applicable operations
|
|
226
|
+
const applicableOperations = [];
|
|
227
|
+
for (const [key, generator] of this.generators) {
|
|
228
|
+
const [genType, name] = key.split(':');
|
|
229
|
+
// Check if this generator is selected
|
|
230
|
+
if (genType === 'framework' && name === selectedModules.framework) {
|
|
231
|
+
// Framework is always included
|
|
232
|
+
}
|
|
233
|
+
else if (genType === 'database' && name === selectedModules.database) {
|
|
234
|
+
// Database is selected
|
|
235
|
+
}
|
|
236
|
+
else if (genType === 'auth' && name === selectedModules.auth) {
|
|
237
|
+
// Auth is selected
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
continue; // Skip unselected generators
|
|
241
|
+
}
|
|
242
|
+
// Handle operations
|
|
243
|
+
const items = generator.operations || [];
|
|
244
|
+
for (const item of items) {
|
|
245
|
+
if (this.evaluateCondition(item.condition, context)) {
|
|
246
|
+
applicableOperations.push({
|
|
247
|
+
...item,
|
|
248
|
+
generator: name,
|
|
249
|
+
generatorType: genType,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
// Sort operations by priority
|
|
255
|
+
applicableOperations.sort((a, b) => {
|
|
256
|
+
const priorityA = a.priority || 0;
|
|
257
|
+
const priorityB = b.priority || 0;
|
|
258
|
+
return priorityA - priorityB;
|
|
259
|
+
});
|
|
260
|
+
// Execute operations
|
|
261
|
+
for (const operation of applicableOperations) {
|
|
262
|
+
await this.executeOperation(operation, context, outputPath);
|
|
263
|
+
}
|
|
264
|
+
// Generate package.json updates
|
|
265
|
+
await this.generatePackageJson(selectedModules, features, outputPath);
|
|
266
|
+
return this.postInstallCommands;
|
|
267
|
+
}
|
|
268
|
+
async executeOperation(operation, context, outputPath) {
|
|
269
|
+
// Process templates in operation content
|
|
270
|
+
const processedOperation = this.processOperationTemplates(operation, context);
|
|
271
|
+
switch (processedOperation.type) {
|
|
272
|
+
case 'create-file':
|
|
273
|
+
await this.executeCreateFile(processedOperation, context, outputPath);
|
|
274
|
+
break;
|
|
275
|
+
case 'patch-file':
|
|
276
|
+
await this.executePatchFile(processedOperation, context, outputPath);
|
|
277
|
+
break;
|
|
278
|
+
case 'add-dependency':
|
|
279
|
+
await this.executeAddDependency(processedOperation, context, outputPath);
|
|
280
|
+
break;
|
|
281
|
+
case 'add-script':
|
|
282
|
+
await this.executeAddScript(processedOperation, context, outputPath);
|
|
283
|
+
break;
|
|
284
|
+
case 'add-env':
|
|
285
|
+
await this.executeAddEnv(processedOperation, context, outputPath);
|
|
286
|
+
break;
|
|
287
|
+
case 'run-command':
|
|
288
|
+
this.executeRunCommand(processedOperation, context);
|
|
289
|
+
break;
|
|
290
|
+
default:
|
|
291
|
+
// Unknown operation type - skip silently
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
async copyTemplate(frameworkName, outputPath) {
|
|
295
|
+
const templatesPath = path.resolve(__dirname, '..', '..', 'templates');
|
|
296
|
+
const templatePath = path.join(templatesPath, frameworkName);
|
|
297
|
+
if (await fs.pathExists(templatePath)) {
|
|
298
|
+
// Copy all files except template.json and node_modules
|
|
299
|
+
await fs.copy(templatePath, outputPath, {
|
|
300
|
+
filter: (src) => {
|
|
301
|
+
const relativePath = path.relative(templatePath, src);
|
|
302
|
+
return relativePath !== 'template.json' &&
|
|
303
|
+
relativePath !== 'node_modules' &&
|
|
304
|
+
!relativePath.startsWith('node_modules/');
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
processOperationTemplates(operation, context) {
|
|
310
|
+
const processed = { ...operation };
|
|
311
|
+
// Process templates in string fields
|
|
312
|
+
if (processed.source) {
|
|
313
|
+
processed.source = this.processTemplate(processed.source, context);
|
|
314
|
+
}
|
|
315
|
+
if (processed.destination) {
|
|
316
|
+
processed.destination = this.processTemplate(processed.destination, context);
|
|
317
|
+
}
|
|
318
|
+
if (processed.content) {
|
|
319
|
+
processed.content = this.processTemplate(processed.content, context);
|
|
320
|
+
}
|
|
321
|
+
if (processed.file) {
|
|
322
|
+
processed.file = this.processTemplate(processed.file, context);
|
|
323
|
+
}
|
|
324
|
+
// Process templates in patch operations
|
|
325
|
+
if (processed.operations) {
|
|
326
|
+
processed.operations = processed.operations.map(op => {
|
|
327
|
+
const processedOp = { ...op };
|
|
328
|
+
if (processedOp.imports) {
|
|
329
|
+
processedOp.imports = processedOp.imports.map(imp => this.processTemplate(imp, context));
|
|
330
|
+
}
|
|
331
|
+
if (processedOp.code) {
|
|
332
|
+
processedOp.code = this.processTemplate(processedOp.code, context);
|
|
333
|
+
}
|
|
334
|
+
if (processedOp.after) {
|
|
335
|
+
processedOp.after = this.processTemplate(processedOp.after, context);
|
|
336
|
+
}
|
|
337
|
+
if (processedOp.before) {
|
|
338
|
+
processedOp.before = this.processTemplate(processedOp.before, context);
|
|
339
|
+
}
|
|
340
|
+
if (processedOp.replace) {
|
|
341
|
+
processedOp.replace = this.processTemplate(processedOp.replace, context);
|
|
342
|
+
}
|
|
343
|
+
if (processedOp.content) {
|
|
344
|
+
processedOp.content = this.processTemplate(processedOp.content, context);
|
|
345
|
+
}
|
|
346
|
+
return processedOp;
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
return processed;
|
|
350
|
+
}
|
|
351
|
+
async executeCreateFile(operation, context, outputPath) {
|
|
352
|
+
if (!operation.destination)
|
|
353
|
+
return;
|
|
354
|
+
const destinationPath = path.join(outputPath, this.processTemplate(operation.destination, context));
|
|
355
|
+
// Ensure directory exists
|
|
356
|
+
await fs.ensureDir(path.dirname(destinationPath));
|
|
357
|
+
let content;
|
|
358
|
+
if (operation.content) {
|
|
359
|
+
// Use content directly
|
|
360
|
+
content = this.processTemplate(operation.content, context);
|
|
361
|
+
}
|
|
362
|
+
else if (operation.source) {
|
|
363
|
+
// Find the source file path relative to the module/template directory
|
|
364
|
+
const modulesPath = path.join(__dirname, '..', '..', 'modules');
|
|
365
|
+
const templatesPath = path.join(__dirname, '..', '..', 'templates');
|
|
366
|
+
const moduleBasePath = operation.generatorType === 'framework'
|
|
367
|
+
? path.join(templatesPath, operation.generator)
|
|
368
|
+
: path.join(modulesPath, operation.generatorType, operation.generator);
|
|
369
|
+
const sourcePath = path.join(moduleBasePath, 'files', operation.source);
|
|
370
|
+
// Check if source file exists
|
|
371
|
+
if (await fs.pathExists(sourcePath)) {
|
|
372
|
+
// Read source file
|
|
373
|
+
content = await fs.readFile(sourcePath, 'utf-8');
|
|
374
|
+
// Process template content
|
|
375
|
+
content = this.processTemplate(content, context);
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
throw new Error(`Source file not found: ${sourcePath}`);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
throw new Error(`Create file operation must have either 'content' or 'source' field`);
|
|
383
|
+
}
|
|
384
|
+
// Write destination file
|
|
385
|
+
await fs.writeFile(destinationPath, content, 'utf-8');
|
|
386
|
+
}
|
|
387
|
+
async executePatchFile(operation, context, outputPath) {
|
|
388
|
+
if (!operation.file || !operation.operations)
|
|
389
|
+
return;
|
|
390
|
+
const filePath = path.join(outputPath, this.processTemplate(operation.file, context));
|
|
391
|
+
// Read existing file
|
|
392
|
+
let content = await fs.readFile(filePath, 'utf-8');
|
|
393
|
+
// Execute patch operations
|
|
394
|
+
for (const patchOp of operation.operations) {
|
|
395
|
+
if (!this.evaluateCondition(patchOp.condition, context))
|
|
396
|
+
continue;
|
|
397
|
+
switch (patchOp.type) {
|
|
398
|
+
case 'add-import':
|
|
399
|
+
if (patchOp.imports) {
|
|
400
|
+
const imports = patchOp.imports.map(imp => this.processTemplate(imp, context)).join('\n');
|
|
401
|
+
// Add imports at the top, after existing imports
|
|
402
|
+
const lines = content.split('\n');
|
|
403
|
+
let insertIndex = 0;
|
|
404
|
+
for (let i = 0; i < lines.length; i++) {
|
|
405
|
+
if (lines[i].trim().startsWith('import') || lines[i].trim() === '') {
|
|
406
|
+
insertIndex = i + 1;
|
|
407
|
+
}
|
|
408
|
+
else {
|
|
409
|
+
break;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
lines.splice(insertIndex, 0, imports);
|
|
413
|
+
content = lines.join('\n');
|
|
414
|
+
}
|
|
415
|
+
break;
|
|
416
|
+
case 'add-code':
|
|
417
|
+
if (patchOp.code && patchOp.after) {
|
|
418
|
+
const processedCode = this.processTemplate(patchOp.code, context);
|
|
419
|
+
const afterPattern = this.processTemplate(patchOp.after, context);
|
|
420
|
+
const index = content.indexOf(afterPattern);
|
|
421
|
+
if (index !== -1) {
|
|
422
|
+
content = content.slice(0, index + afterPattern.length) + processedCode + content.slice(index + afterPattern.length);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
break;
|
|
426
|
+
case 'replace-code':
|
|
427
|
+
if (patchOp.code && patchOp.replace) {
|
|
428
|
+
const processedCode = this.processTemplate(patchOp.code, context);
|
|
429
|
+
const replacePattern = this.processTemplate(patchOp.replace, context);
|
|
430
|
+
content = content.replace(replacePattern, processedCode);
|
|
431
|
+
}
|
|
432
|
+
break;
|
|
433
|
+
case 'add-to-top':
|
|
434
|
+
if (patchOp.content) {
|
|
435
|
+
const processedContent = this.processTemplate(patchOp.content, context);
|
|
436
|
+
content = processedContent + '\n' + content;
|
|
437
|
+
}
|
|
438
|
+
break;
|
|
439
|
+
case 'add-to-bottom':
|
|
440
|
+
if (patchOp.content) {
|
|
441
|
+
const processedContent = this.processTemplate(patchOp.content, context);
|
|
442
|
+
content = content + '\n' + processedContent;
|
|
443
|
+
}
|
|
444
|
+
break;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
// Write back the modified content
|
|
448
|
+
await fs.writeFile(filePath, content, 'utf-8');
|
|
449
|
+
}
|
|
450
|
+
async executeAddDependency(operation, context, outputPath) {
|
|
451
|
+
const packageJsonPath = path.join(outputPath, 'package.json');
|
|
452
|
+
let packageJson = {};
|
|
453
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
454
|
+
packageJson = await fs.readJson(packageJsonPath);
|
|
455
|
+
}
|
|
456
|
+
if (operation.dependencies) {
|
|
457
|
+
packageJson.dependencies = { ...(packageJson.dependencies || {}), ...operation.dependencies };
|
|
458
|
+
}
|
|
459
|
+
if (operation.devDependencies) {
|
|
460
|
+
packageJson.devDependencies = { ...(packageJson.devDependencies || {}), ...operation.devDependencies };
|
|
461
|
+
}
|
|
462
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
463
|
+
}
|
|
464
|
+
async executeAddScript(operation, context, outputPath) {
|
|
465
|
+
const packageJsonPath = path.join(outputPath, 'package.json');
|
|
466
|
+
let packageJson = {};
|
|
467
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
468
|
+
packageJson = await fs.readJson(packageJsonPath);
|
|
469
|
+
}
|
|
470
|
+
if (operation.scripts) {
|
|
471
|
+
packageJson.scripts = { ...(packageJson.scripts || {}), ...operation.scripts };
|
|
472
|
+
}
|
|
473
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
474
|
+
}
|
|
475
|
+
async executeAddEnv(operation, context, outputPath) {
|
|
476
|
+
const envPath = path.join(outputPath, '.env');
|
|
477
|
+
let envContent = '';
|
|
478
|
+
if (await fs.pathExists(envPath)) {
|
|
479
|
+
envContent = await fs.readFile(envPath, 'utf-8');
|
|
480
|
+
}
|
|
481
|
+
if (operation.envVars) {
|
|
482
|
+
const envLines = Object.entries(operation.envVars).map(([key, value]) => `${key}=${value}`);
|
|
483
|
+
envContent += '\n' + envLines.join('\n');
|
|
484
|
+
}
|
|
485
|
+
await fs.writeFile(envPath, envContent.trim(), 'utf-8');
|
|
486
|
+
}
|
|
487
|
+
executeRunCommand(operation, context) {
|
|
488
|
+
if (operation.command) {
|
|
489
|
+
// Process template variables in the command
|
|
490
|
+
const processedCommand = this.processTemplate(operation.command, context);
|
|
491
|
+
this.postInstallCommands.push(processedCommand);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
async generatePackageJson(selectedModules, features, outputPath) {
|
|
495
|
+
const packageJsonPath = path.join(outputPath, 'package.json');
|
|
496
|
+
let packageJson = {};
|
|
497
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
498
|
+
packageJson = await fs.readJson(packageJsonPath);
|
|
499
|
+
}
|
|
500
|
+
// Collect dependencies from selected generators
|
|
501
|
+
const allDeps = {};
|
|
502
|
+
const allDevDeps = {};
|
|
503
|
+
const allScripts = {};
|
|
504
|
+
for (const [key, generator] of this.generators) {
|
|
505
|
+
const [type, name] = key.split(':');
|
|
506
|
+
if ((type === 'framework' && name === selectedModules.framework) ||
|
|
507
|
+
(type === 'database' && name === selectedModules.database) ||
|
|
508
|
+
(type === 'auth' && name === selectedModules.auth)) {
|
|
509
|
+
Object.assign(allDeps, generator.dependencies);
|
|
510
|
+
Object.assign(allDevDeps, generator.devDependencies);
|
|
511
|
+
Object.assign(allScripts, generator.scripts);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
// Update package.json
|
|
515
|
+
packageJson.dependencies = { ...(packageJson.dependencies || {}), ...allDeps };
|
|
516
|
+
packageJson.devDependencies = { ...(packageJson.devDependencies || {}), ...allDevDeps };
|
|
517
|
+
packageJson.scripts = { ...(packageJson.scripts || {}), ...allScripts };
|
|
518
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
519
|
+
}
|
|
520
|
+
getAvailableGenerators() {
|
|
521
|
+
const frameworks = [];
|
|
522
|
+
const databases = [];
|
|
523
|
+
const auths = [];
|
|
524
|
+
for (const [key] of this.generators) {
|
|
525
|
+
const [type, name] = key.split(':');
|
|
526
|
+
switch (type) {
|
|
527
|
+
case 'framework':
|
|
528
|
+
frameworks.push(name);
|
|
529
|
+
break;
|
|
530
|
+
case 'database':
|
|
531
|
+
databases.push(name);
|
|
532
|
+
break;
|
|
533
|
+
case 'auth':
|
|
534
|
+
auths.push(name);
|
|
535
|
+
break;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
return { frameworks, databases, auths };
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
exports.AdvancedCodeGenerator = AdvancedCodeGenerator;
|