initkit 1.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/LICENSE +21 -0
- package/README.md +844 -0
- package/bin/index.js +8 -0
- package/package.json +79 -0
- package/src/cli.js +264 -0
- package/src/commands/create.js +264 -0
- package/src/index.js +9 -0
- package/src/prompts/questions.js +358 -0
- package/src/templates/express.js +915 -0
- package/src/templates/fullstack.js +1236 -0
- package/src/templates/nextjs.js +620 -0
- package/src/templates/react.js +586 -0
- package/src/templates/vue.js +545 -0
- package/src/utils/errorHandler.js +275 -0
- package/src/utils/git.js +69 -0
- package/src/utils/packageManager.js +90 -0
- package/src/utils/templateGenerator.js +365 -0
- package/src/utils/validation.js +186 -0
- package/src/utils/versionFetcher.js +128 -0
|
@@ -0,0 +1,915 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { getLatestVersion } from '../utils/versionFetcher.js';
|
|
6
|
+
|
|
7
|
+
async function fetchVersion(packageName, fallback = 'latest') {
|
|
8
|
+
try {
|
|
9
|
+
const version = await getLatestVersion(packageName);
|
|
10
|
+
return `^${version}`;
|
|
11
|
+
} catch {
|
|
12
|
+
return fallback;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Generate Express.js backend API project with organized architecture
|
|
18
|
+
*
|
|
19
|
+
* Creates a production-ready Express.js server with:
|
|
20
|
+
* - RESTful API structure
|
|
21
|
+
* - Multiple architecture patterns (MVC, Clean Architecture, Domain-Driven Design)
|
|
22
|
+
* - Database integration (MongoDB, PostgreSQL, MySQL, SQLite)
|
|
23
|
+
* - TypeScript or JavaScript support
|
|
24
|
+
* - Authentication middleware setup
|
|
25
|
+
* - Error handling and validation
|
|
26
|
+
* - Environment configuration
|
|
27
|
+
*
|
|
28
|
+
* Generated project includes:
|
|
29
|
+
* - Organized folder structure based on pattern
|
|
30
|
+
* - Server entry point with middleware configuration
|
|
31
|
+
* - Route handlers and controllers
|
|
32
|
+
* - Database models and schemas
|
|
33
|
+
* - Package.json with Express and dependencies
|
|
34
|
+
* - .env.example for environment variables
|
|
35
|
+
* - README with API documentation
|
|
36
|
+
*
|
|
37
|
+
* @param {string} projectPath - Absolute path to the project directory
|
|
38
|
+
* @param {Object} config - User configuration object
|
|
39
|
+
* @param {string} config.projectName - Name of the project
|
|
40
|
+
* @param {string} config.language - Programming language ('typescript'|'javascript')
|
|
41
|
+
* @param {string} [config.folderStructure='mvc'] - Architecture pattern
|
|
42
|
+
* - 'mvc': Model-View-Controller (recommended for REST APIs)
|
|
43
|
+
* - 'clean-architecture': Clean Architecture with domain/infrastructure layers
|
|
44
|
+
* - 'ddd': Domain-Driven Design with bounded contexts
|
|
45
|
+
* - 'feature-based': Organize by features/modules
|
|
46
|
+
* @param {string} [config.database='none'] - Database choice ('mongodb'|'postgresql'|'mysql'|'sqlite'|'none')
|
|
47
|
+
* @param {string} config.packageManager - Package manager to use
|
|
48
|
+
*
|
|
49
|
+
* @returns {Promise<void>}
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* // Create Express API with PostgreSQL and MVC pattern
|
|
53
|
+
* await generateExpressTemplate('/path/to/project', {
|
|
54
|
+
* projectName: 'my-express-api',
|
|
55
|
+
* language: 'typescript',
|
|
56
|
+
* folderStructure: 'mvc',
|
|
57
|
+
* database: 'postgresql',
|
|
58
|
+
* packageManager: 'npm'
|
|
59
|
+
* });
|
|
60
|
+
*/
|
|
61
|
+
export async function generateExpressTemplate(projectPath, config) {
|
|
62
|
+
// Create folder structure
|
|
63
|
+
await createExpressFolderStructure(projectPath, config);
|
|
64
|
+
|
|
65
|
+
// Generate configuration files
|
|
66
|
+
await generateExpressConfigFiles(projectPath, config);
|
|
67
|
+
|
|
68
|
+
// Generate package.json
|
|
69
|
+
await generateExpressPackageJson(projectPath, config);
|
|
70
|
+
|
|
71
|
+
// Generate README
|
|
72
|
+
await generateExpressReadme(projectPath, config);
|
|
73
|
+
|
|
74
|
+
// Generate environment file example
|
|
75
|
+
await generateEnvExample(projectPath, config);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function createExpressFolderStructure(projectPath, config) {
|
|
79
|
+
const srcPath = path.join(projectPath, 'src');
|
|
80
|
+
const folderStructure = config.folderStructure || 'mvc';
|
|
81
|
+
const database = config.database || 'none';
|
|
82
|
+
|
|
83
|
+
if (folderStructure === 'mvc') {
|
|
84
|
+
// MVC Pattern
|
|
85
|
+
await fs.ensureDir(path.join(srcPath, 'models'));
|
|
86
|
+
await fs.ensureDir(path.join(srcPath, 'views'));
|
|
87
|
+
await fs.ensureDir(path.join(srcPath, 'controllers'));
|
|
88
|
+
await fs.ensureDir(path.join(srcPath, 'routes'));
|
|
89
|
+
await fs.ensureDir(path.join(srcPath, 'middleware'));
|
|
90
|
+
await fs.ensureDir(path.join(srcPath, 'utils'));
|
|
91
|
+
await fs.ensureDir(path.join(srcPath, 'config'));
|
|
92
|
+
|
|
93
|
+
// Routes index
|
|
94
|
+
await fs.writeFile(path.join(srcPath, 'routes', 'index.ts'), generateRoutesIndex());
|
|
95
|
+
} else if (folderStructure === 'clean-architecture') {
|
|
96
|
+
// Clean Architecture
|
|
97
|
+
await fs.ensureDir(path.join(srcPath, 'domain', 'entities'));
|
|
98
|
+
await fs.ensureDir(path.join(srcPath, 'domain', 'repositories'));
|
|
99
|
+
await fs.ensureDir(path.join(srcPath, 'domain', 'use-cases'));
|
|
100
|
+
|
|
101
|
+
await fs.ensureDir(path.join(srcPath, 'application', 'dto'));
|
|
102
|
+
await fs.ensureDir(path.join(srcPath, 'application', 'services'));
|
|
103
|
+
await fs.ensureDir(path.join(srcPath, 'application', 'interfaces'));
|
|
104
|
+
|
|
105
|
+
await fs.ensureDir(path.join(srcPath, 'infrastructure', 'database'));
|
|
106
|
+
await fs.ensureDir(path.join(srcPath, 'infrastructure', 'repositories'));
|
|
107
|
+
await fs.ensureDir(path.join(srcPath, 'infrastructure', 'external-services'));
|
|
108
|
+
|
|
109
|
+
await fs.ensureDir(path.join(srcPath, 'presentation', 'controllers'));
|
|
110
|
+
await fs.ensureDir(path.join(srcPath, 'presentation', 'middleware'));
|
|
111
|
+
await fs.ensureDir(path.join(srcPath, 'presentation', 'routes'));
|
|
112
|
+
} else if (folderStructure === 'feature-based') {
|
|
113
|
+
// Feature-based structure
|
|
114
|
+
const features = ['auth', 'users', 'posts'];
|
|
115
|
+
|
|
116
|
+
for (const feature of features) {
|
|
117
|
+
await fs.ensureDir(path.join(srcPath, 'features', feature, 'controllers'));
|
|
118
|
+
await fs.ensureDir(path.join(srcPath, 'features', feature, 'services'));
|
|
119
|
+
await fs.ensureDir(path.join(srcPath, 'features', feature, 'routes'));
|
|
120
|
+
await fs.ensureDir(path.join(srcPath, 'features', feature, 'models'));
|
|
121
|
+
await fs.ensureDir(path.join(srcPath, 'features', feature, 'middleware'));
|
|
122
|
+
await fs.ensureDir(path.join(srcPath, 'features', feature, 'validators'));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Shared directory
|
|
126
|
+
await fs.ensureDir(path.join(srcPath, 'shared', 'middleware'));
|
|
127
|
+
await fs.ensureDir(path.join(srcPath, 'shared', 'utils'));
|
|
128
|
+
await fs.ensureDir(path.join(srcPath, 'shared', 'types'));
|
|
129
|
+
await fs.ensureDir(path.join(srcPath, 'shared', 'config'));
|
|
130
|
+
} else if (folderStructure === 'layered') {
|
|
131
|
+
// Layered Architecture
|
|
132
|
+
await fs.ensureDir(path.join(srcPath, 'controllers'));
|
|
133
|
+
await fs.ensureDir(path.join(srcPath, 'services'));
|
|
134
|
+
await fs.ensureDir(path.join(srcPath, 'repositories'));
|
|
135
|
+
await fs.ensureDir(path.join(srcPath, 'models'));
|
|
136
|
+
await fs.ensureDir(path.join(srcPath, 'routes'));
|
|
137
|
+
await fs.ensureDir(path.join(srcPath, 'middleware'));
|
|
138
|
+
await fs.ensureDir(path.join(srcPath, 'validators'));
|
|
139
|
+
await fs.ensureDir(path.join(srcPath, 'utils'));
|
|
140
|
+
await fs.ensureDir(path.join(srcPath, 'config'));
|
|
141
|
+
await fs.ensureDir(path.join(srcPath, 'types'));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Common directories for all structures
|
|
145
|
+
await fs.ensureDir(path.join(srcPath, 'tests'));
|
|
146
|
+
await fs.ensureDir(path.join(projectPath, 'logs'));
|
|
147
|
+
|
|
148
|
+
// Database specific setup
|
|
149
|
+
if (database === 'prisma') {
|
|
150
|
+
await fs.ensureDir(path.join(projectPath, 'prisma'));
|
|
151
|
+
await fs.ensureDir(path.join(projectPath, 'prisma', 'migrations'));
|
|
152
|
+
await fs.writeFile(path.join(projectPath, 'prisma', 'schema.prisma'), generatePrismaSchema());
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async function generateExpressConfigFiles(projectPath, config) {
|
|
157
|
+
const { language, database } = config;
|
|
158
|
+
const srcPath = path.join(projectPath, 'src');
|
|
159
|
+
const isTypeScript = language === 'typescript';
|
|
160
|
+
const folderStructure = config.folderStructure || 'mvc';
|
|
161
|
+
|
|
162
|
+
// Determine the correct paths based on folder structure
|
|
163
|
+
const configPath =
|
|
164
|
+
folderStructure === 'feature-based'
|
|
165
|
+
? path.join(srcPath, 'shared', 'config')
|
|
166
|
+
: path.join(srcPath, 'config');
|
|
167
|
+
|
|
168
|
+
const middlewarePath =
|
|
169
|
+
folderStructure === 'feature-based'
|
|
170
|
+
? path.join(srcPath, 'shared', 'middleware')
|
|
171
|
+
: folderStructure === 'clean-architecture'
|
|
172
|
+
? path.join(srcPath, 'presentation', 'middleware')
|
|
173
|
+
: path.join(srcPath, 'middleware');
|
|
174
|
+
|
|
175
|
+
// Ensure directories exist
|
|
176
|
+
await fs.ensureDir(configPath);
|
|
177
|
+
await fs.ensureDir(middlewarePath);
|
|
178
|
+
|
|
179
|
+
// TypeScript configuration
|
|
180
|
+
if (isTypeScript) {
|
|
181
|
+
await fs.writeFile(path.join(projectPath, 'tsconfig.json'), generateTsConfig());
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ESLint configuration
|
|
185
|
+
await fs.writeFile(
|
|
186
|
+
path.join(projectPath, 'eslint.config.js'),
|
|
187
|
+
generateEslintConfig(isTypeScript)
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
// Nodemon configuration
|
|
191
|
+
await fs.writeFile(path.join(projectPath, 'nodemon.json'), generateNodemonConfig(isTypeScript));
|
|
192
|
+
|
|
193
|
+
// Main app file
|
|
194
|
+
await fs.writeFile(
|
|
195
|
+
path.join(srcPath, isTypeScript ? 'app.ts' : 'app.js'),
|
|
196
|
+
generateAppFile(config)
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
// Server entry point
|
|
200
|
+
await fs.writeFile(
|
|
201
|
+
path.join(srcPath, isTypeScript ? 'server.ts' : 'server.js'),
|
|
202
|
+
generateServerFile(isTypeScript)
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
// Database configuration
|
|
206
|
+
if (database !== 'none') {
|
|
207
|
+
await fs.writeFile(
|
|
208
|
+
path.join(configPath, isTypeScript ? 'database.ts' : 'database.js'),
|
|
209
|
+
generateDatabaseConfig(database, isTypeScript)
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Error handler middleware
|
|
214
|
+
await fs.writeFile(
|
|
215
|
+
path.join(middlewarePath, isTypeScript ? 'errorHandler.ts' : 'errorHandler.js'),
|
|
216
|
+
generateErrorHandler(isTypeScript)
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
// CORS configuration
|
|
220
|
+
await fs.writeFile(
|
|
221
|
+
path.join(configPath, isTypeScript ? 'cors.ts' : 'cors.js'),
|
|
222
|
+
generateCorsConfig(isTypeScript)
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async function generateExpressPackageJson(projectPath, config) {
|
|
227
|
+
const { language, database, projectName } = config;
|
|
228
|
+
const isTypeScript = language === 'typescript';
|
|
229
|
+
|
|
230
|
+
const spinner = ora('Fetching latest package versions...').start();
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
const scripts = {
|
|
234
|
+
dev: isTypeScript ? 'nodemon' : 'nodemon src/server.js',
|
|
235
|
+
build: isTypeScript ? 'tsc' : 'echo "No build step for JavaScript"',
|
|
236
|
+
start: isTypeScript ? 'node dist/server.js' : 'node src/server.js',
|
|
237
|
+
lint: 'eslint .',
|
|
238
|
+
'lint:fix': 'eslint . --fix',
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
if (database === 'prisma') {
|
|
242
|
+
scripts['db:generate'] = 'prisma generate';
|
|
243
|
+
scripts['db:push'] = 'prisma db push';
|
|
244
|
+
scripts['db:migrate'] = 'prisma migrate dev';
|
|
245
|
+
scripts['db:studio'] = 'prisma studio';
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Fetch core dependencies
|
|
249
|
+
const [expressVer, dotenvVer, corsVer, helmetVer, nodemonVer] = await Promise.all([
|
|
250
|
+
fetchVersion('express'),
|
|
251
|
+
fetchVersion('dotenv'),
|
|
252
|
+
fetchVersion('cors'),
|
|
253
|
+
fetchVersion('helmet'),
|
|
254
|
+
fetchVersion('nodemon'),
|
|
255
|
+
]);
|
|
256
|
+
|
|
257
|
+
const dependencies = {
|
|
258
|
+
express: expressVer,
|
|
259
|
+
dotenv: dotenvVer,
|
|
260
|
+
cors: corsVer,
|
|
261
|
+
helmet: helmetVer,
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
const devDependencies = {
|
|
265
|
+
nodemon: nodemonVer,
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
// TypeScript dependencies
|
|
269
|
+
if (isTypeScript) {
|
|
270
|
+
const [tsVer, typesExpressVer, typesNodeVer, typesCorsVer, tsNodeVer] = await Promise.all([
|
|
271
|
+
fetchVersion('typescript'),
|
|
272
|
+
fetchVersion('@types/express'),
|
|
273
|
+
fetchVersion('@types/node'),
|
|
274
|
+
fetchVersion('@types/cors'),
|
|
275
|
+
fetchVersion('ts-node'),
|
|
276
|
+
]);
|
|
277
|
+
devDependencies['typescript'] = tsVer;
|
|
278
|
+
devDependencies['@types/express'] = typesExpressVer;
|
|
279
|
+
devDependencies['@types/node'] = typesNodeVer;
|
|
280
|
+
devDependencies['@types/cors'] = typesCorsVer;
|
|
281
|
+
devDependencies['ts-node'] = tsNodeVer;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Database specific dependencies
|
|
285
|
+
if (database === 'prisma') {
|
|
286
|
+
const [prismaClientVer, prismaVer] = await Promise.all([
|
|
287
|
+
fetchVersion('@prisma/client'),
|
|
288
|
+
fetchVersion('prisma'),
|
|
289
|
+
]);
|
|
290
|
+
dependencies['@prisma/client'] = prismaClientVer;
|
|
291
|
+
devDependencies['prisma'] = prismaVer;
|
|
292
|
+
} else if (database === 'mongodb') {
|
|
293
|
+
dependencies['mongoose'] = await fetchVersion('mongoose');
|
|
294
|
+
} else if (database === 'postgresql' || database === 'mysql') {
|
|
295
|
+
dependencies['pg'] = await fetchVersion('pg');
|
|
296
|
+
if (isTypeScript) {
|
|
297
|
+
devDependencies['@types/pg'] = await fetchVersion('@types/pg');
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
spinner.succeed(chalk.green('Fetched latest versions'));
|
|
302
|
+
|
|
303
|
+
const packageJson = {
|
|
304
|
+
name: projectName || 'express-api',
|
|
305
|
+
version: '1.0.0',
|
|
306
|
+
description: 'Express.js API server',
|
|
307
|
+
main: isTypeScript ? 'dist/server.js' : 'src/server.js',
|
|
308
|
+
type: 'module',
|
|
309
|
+
scripts,
|
|
310
|
+
keywords: ['express', 'api', 'backend'],
|
|
311
|
+
author: '',
|
|
312
|
+
license: 'MIT',
|
|
313
|
+
dependencies,
|
|
314
|
+
devDependencies,
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
await fs.writeFile(
|
|
318
|
+
path.join(projectPath, 'package.json'),
|
|
319
|
+
JSON.stringify(packageJson, null, 2)
|
|
320
|
+
);
|
|
321
|
+
} catch (error) {
|
|
322
|
+
spinner.fail(chalk.yellow('Could not fetch versions, using fallbacks'));
|
|
323
|
+
|
|
324
|
+
// Fallback with latest tag
|
|
325
|
+
const scripts = {
|
|
326
|
+
dev: isTypeScript ? 'nodemon' : 'nodemon src/server.js',
|
|
327
|
+
build: isTypeScript ? 'tsc' : 'echo "No build step for JavaScript"',
|
|
328
|
+
start: isTypeScript ? 'node dist/server.js' : 'node src/server.js',
|
|
329
|
+
lint: 'eslint .',
|
|
330
|
+
'lint:fix': 'eslint . --fix',
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
if (database === 'prisma') {
|
|
334
|
+
scripts['db:generate'] = 'prisma generate';
|
|
335
|
+
scripts['db:push'] = 'prisma db push';
|
|
336
|
+
scripts['db:migrate'] = 'prisma migrate dev';
|
|
337
|
+
scripts['db:studio'] = 'prisma studio';
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const dependencies = { express: 'latest', dotenv: 'latest', cors: 'latest', helmet: 'latest' };
|
|
341
|
+
const devDependencies = {
|
|
342
|
+
nodemon: 'latest',
|
|
343
|
+
...(isTypeScript && {
|
|
344
|
+
typescript: 'latest',
|
|
345
|
+
'@types/express': 'latest',
|
|
346
|
+
'@types/node': 'latest',
|
|
347
|
+
'@types/cors': 'latest',
|
|
348
|
+
'ts-node': 'latest',
|
|
349
|
+
}),
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
if (database === 'prisma') {
|
|
353
|
+
dependencies['@prisma/client'] = 'latest';
|
|
354
|
+
devDependencies['prisma'] = 'latest';
|
|
355
|
+
} else if (database === 'mongodb') {
|
|
356
|
+
dependencies['mongoose'] = 'latest';
|
|
357
|
+
} else if (database === 'postgresql' || database === 'mysql') {
|
|
358
|
+
dependencies['pg'] = 'latest';
|
|
359
|
+
if (isTypeScript) devDependencies['@types/pg'] = 'latest';
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const packageJson = {
|
|
363
|
+
name: projectName || 'express-api',
|
|
364
|
+
version: '1.0.0',
|
|
365
|
+
description: 'Express.js API server',
|
|
366
|
+
main: isTypeScript ? 'dist/server.js' : 'src/server.js',
|
|
367
|
+
type: 'module',
|
|
368
|
+
scripts,
|
|
369
|
+
keywords: ['express', 'api', 'backend'],
|
|
370
|
+
author: '',
|
|
371
|
+
license: 'MIT',
|
|
372
|
+
dependencies,
|
|
373
|
+
devDependencies,
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
await fs.writeFile(
|
|
377
|
+
path.join(projectPath, 'package.json'),
|
|
378
|
+
JSON.stringify(packageJson, null, 2)
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
async function generateExpressReadme(projectPath, config) {
|
|
384
|
+
const { language, database, folderStructure } = config;
|
|
385
|
+
const isTypeScript = language === 'typescript';
|
|
386
|
+
|
|
387
|
+
const readme = `# ${config.projectName || 'Express API'}
|
|
388
|
+
|
|
389
|
+
Express.js backend API with ${language === 'typescript' ? 'TypeScript' : 'JavaScript'}
|
|
390
|
+
|
|
391
|
+
## 📁 Folder Structure
|
|
392
|
+
|
|
393
|
+
This project uses **${folderStructure}** architecture.
|
|
394
|
+
|
|
395
|
+
${getArchitectureDescription(folderStructure)}
|
|
396
|
+
|
|
397
|
+
## 🚀 Getting Started
|
|
398
|
+
|
|
399
|
+
### Prerequisites
|
|
400
|
+
|
|
401
|
+
- Node.js 18+ installed
|
|
402
|
+
${database !== 'none' ? `- ${database} database` : ''}
|
|
403
|
+
|
|
404
|
+
### Installation
|
|
405
|
+
|
|
406
|
+
\`\`\`bash
|
|
407
|
+
# Install dependencies
|
|
408
|
+
npm install
|
|
409
|
+
|
|
410
|
+
${
|
|
411
|
+
database === 'prisma'
|
|
412
|
+
? `# Generate Prisma Client
|
|
413
|
+
npm run db:generate
|
|
414
|
+
|
|
415
|
+
# Run database migrations
|
|
416
|
+
npm run db:migrate
|
|
417
|
+
`
|
|
418
|
+
: ''
|
|
419
|
+
}
|
|
420
|
+
\`\`\`
|
|
421
|
+
|
|
422
|
+
### Environment Variables
|
|
423
|
+
|
|
424
|
+
Copy \`.env.example\` to \`.env\` and fill in your values:
|
|
425
|
+
|
|
426
|
+
\`\`\`bash
|
|
427
|
+
cp .env.example .env
|
|
428
|
+
\`\`\`
|
|
429
|
+
|
|
430
|
+
### Development
|
|
431
|
+
|
|
432
|
+
\`\`\`bash
|
|
433
|
+
# Start development server with hot reload
|
|
434
|
+
npm run dev
|
|
435
|
+
\`\`\`
|
|
436
|
+
|
|
437
|
+
${
|
|
438
|
+
isTypeScript
|
|
439
|
+
? `### Build
|
|
440
|
+
|
|
441
|
+
\`\`\`bash
|
|
442
|
+
# Build TypeScript to JavaScript
|
|
443
|
+
npm run build
|
|
444
|
+
|
|
445
|
+
# Start production server
|
|
446
|
+
npm start
|
|
447
|
+
\`\`\`
|
|
448
|
+
`
|
|
449
|
+
: ''
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
## 🔧 Available Scripts
|
|
453
|
+
|
|
454
|
+
- \`npm run dev\` - Start development server
|
|
455
|
+
- \`npm run build\` - Build for production
|
|
456
|
+
- \`npm start\` - Start production server
|
|
457
|
+
- \`npm run lint\` - Lint code
|
|
458
|
+
- \`npm run lint:fix\` - Fix linting issues
|
|
459
|
+
|
|
460
|
+
${
|
|
461
|
+
database === 'prisma'
|
|
462
|
+
? `
|
|
463
|
+
## 💾 Database Commands
|
|
464
|
+
|
|
465
|
+
- \`npm run db:generate\` - Generate Prisma Client
|
|
466
|
+
- \`npm run db:push\` - Push schema changes
|
|
467
|
+
- \`npm run db:migrate\` - Run migrations
|
|
468
|
+
- \`npm run db:studio\` - Open Prisma Studio
|
|
469
|
+
`
|
|
470
|
+
: ''
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
## 📝 License
|
|
474
|
+
|
|
475
|
+
MIT
|
|
476
|
+
`;
|
|
477
|
+
|
|
478
|
+
await fs.writeFile(path.join(projectPath, 'README.md'), readme);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
async function generateEnvExample(projectPath, config) {
|
|
482
|
+
const { database } = config;
|
|
483
|
+
|
|
484
|
+
let envContent = `# Server Configuration
|
|
485
|
+
NODE_ENV=development
|
|
486
|
+
PORT=3000
|
|
487
|
+
|
|
488
|
+
# CORS Configuration
|
|
489
|
+
CORS_ORIGIN=http://localhost:5173
|
|
490
|
+
|
|
491
|
+
`;
|
|
492
|
+
|
|
493
|
+
if (database === 'prisma') {
|
|
494
|
+
envContent += `# Database Configuration (Prisma)
|
|
495
|
+
DATABASE_URL="postgresql://user:password@localhost:5432/mydb"
|
|
496
|
+
`;
|
|
497
|
+
} else if (database === 'mongodb') {
|
|
498
|
+
envContent += `# MongoDB Configuration
|
|
499
|
+
MONGODB_URI=mongodb://localhost:27017/mydb
|
|
500
|
+
`;
|
|
501
|
+
} else if (database === 'postgresql') {
|
|
502
|
+
envContent += `# PostgreSQL Configuration
|
|
503
|
+
DB_HOST=localhost
|
|
504
|
+
DB_PORT=5432
|
|
505
|
+
DB_NAME=mydb
|
|
506
|
+
DB_USER=postgres
|
|
507
|
+
DB_PASSWORD=password
|
|
508
|
+
`;
|
|
509
|
+
} else if (database === 'mysql') {
|
|
510
|
+
envContent += `# MySQL Configuration
|
|
511
|
+
DB_HOST=localhost
|
|
512
|
+
DB_PORT=3306
|
|
513
|
+
DB_NAME=mydb
|
|
514
|
+
DB_USER=root
|
|
515
|
+
DB_PASSWORD=password
|
|
516
|
+
`;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
await fs.writeFile(path.join(projectPath, '.env.example'), envContent);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// Configuration file generators
|
|
523
|
+
function generatePrismaSchema() {
|
|
524
|
+
return `// This is your Prisma schema file,
|
|
525
|
+
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
|
526
|
+
|
|
527
|
+
generator client {
|
|
528
|
+
provider = "prisma-client-js"
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
datasource db {
|
|
532
|
+
provider = "postgresql"
|
|
533
|
+
url = env("DATABASE_URL")
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// Add your models here
|
|
537
|
+
model User {
|
|
538
|
+
id String @id @default(cuid())
|
|
539
|
+
email String @unique
|
|
540
|
+
name String
|
|
541
|
+
createdAt DateTime @default(now())
|
|
542
|
+
updatedAt DateTime @updatedAt
|
|
543
|
+
|
|
544
|
+
@@map("users")
|
|
545
|
+
}
|
|
546
|
+
`;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function generateTsConfig() {
|
|
550
|
+
return JSON.stringify(
|
|
551
|
+
{
|
|
552
|
+
compilerOptions: {
|
|
553
|
+
target: 'ES2022',
|
|
554
|
+
module: 'ESNext',
|
|
555
|
+
lib: ['ES2022'],
|
|
556
|
+
moduleResolution: 'node',
|
|
557
|
+
rootDir: './src',
|
|
558
|
+
outDir: './dist',
|
|
559
|
+
strict: true,
|
|
560
|
+
esModuleInterop: true,
|
|
561
|
+
skipLibCheck: true,
|
|
562
|
+
forceConsistentCasingInFileNames: true,
|
|
563
|
+
resolveJsonModule: true,
|
|
564
|
+
declaration: true,
|
|
565
|
+
sourceMap: true,
|
|
566
|
+
types: ['node'],
|
|
567
|
+
},
|
|
568
|
+
include: ['src/**/*'],
|
|
569
|
+
exclude: ['node_modules', 'dist'],
|
|
570
|
+
},
|
|
571
|
+
null,
|
|
572
|
+
2
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function generateEslintConfig(isTypeScript) {
|
|
577
|
+
if (isTypeScript) {
|
|
578
|
+
return `import js from '@eslint/js';
|
|
579
|
+
import tsPlugin from '@typescript-eslint/eslint-plugin';
|
|
580
|
+
import tsParser from '@typescript-eslint/parser';
|
|
581
|
+
|
|
582
|
+
export default [
|
|
583
|
+
js.configs.recommended,
|
|
584
|
+
{
|
|
585
|
+
files: ['**/*.ts'],
|
|
586
|
+
languageOptions: {
|
|
587
|
+
parser: tsParser,
|
|
588
|
+
parserOptions: {
|
|
589
|
+
ecmaVersion: 'latest',
|
|
590
|
+
sourceType: 'module',
|
|
591
|
+
},
|
|
592
|
+
},
|
|
593
|
+
plugins: {
|
|
594
|
+
'@typescript-eslint': tsPlugin,
|
|
595
|
+
},
|
|
596
|
+
rules: {
|
|
597
|
+
...tsPlugin.configs.recommended.rules,
|
|
598
|
+
'@typescript-eslint/no-explicit-any': 'warn',
|
|
599
|
+
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
|
|
600
|
+
},
|
|
601
|
+
},
|
|
602
|
+
{
|
|
603
|
+
ignores: ['dist/', 'node_modules/', '*.config.js'],
|
|
604
|
+
},
|
|
605
|
+
];
|
|
606
|
+
`;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
return `import js from '@eslint/js';
|
|
610
|
+
|
|
611
|
+
export default [
|
|
612
|
+
js.configs.recommended,
|
|
613
|
+
{
|
|
614
|
+
rules: {
|
|
615
|
+
'no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
|
|
616
|
+
},
|
|
617
|
+
},
|
|
618
|
+
{
|
|
619
|
+
ignores: ['node_modules/', '*.config.js'],
|
|
620
|
+
},
|
|
621
|
+
];
|
|
622
|
+
`;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
function generateNodemonConfig(isTypeScript) {
|
|
626
|
+
if (isTypeScript) {
|
|
627
|
+
return JSON.stringify(
|
|
628
|
+
{
|
|
629
|
+
watch: ['src'],
|
|
630
|
+
ext: 'ts',
|
|
631
|
+
exec: 'ts-node src/server.ts',
|
|
632
|
+
env: {
|
|
633
|
+
NODE_ENV: 'development',
|
|
634
|
+
},
|
|
635
|
+
},
|
|
636
|
+
null,
|
|
637
|
+
2
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
return JSON.stringify(
|
|
642
|
+
{
|
|
643
|
+
watch: ['src'],
|
|
644
|
+
ext: 'js',
|
|
645
|
+
exec: 'node src/server.js',
|
|
646
|
+
env: {
|
|
647
|
+
NODE_ENV: 'development',
|
|
648
|
+
},
|
|
649
|
+
},
|
|
650
|
+
null,
|
|
651
|
+
2
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
function generateAppFile(_config) {
|
|
656
|
+
return `import express from 'express';
|
|
657
|
+
import cors from 'cors';
|
|
658
|
+
import helmet from 'helmet';
|
|
659
|
+
import { corsOptions } from './config/cors';
|
|
660
|
+
import { errorHandler } from './middleware/errorHandler';
|
|
661
|
+
import routes from './routes';
|
|
662
|
+
|
|
663
|
+
const app = express();
|
|
664
|
+
|
|
665
|
+
// Security Middleware
|
|
666
|
+
app.use(helmet());
|
|
667
|
+
app.use(cors(corsOptions));
|
|
668
|
+
|
|
669
|
+
// Body Parser
|
|
670
|
+
app.use(express.json());
|
|
671
|
+
app.use(express.urlencoded({ extended: true }));
|
|
672
|
+
|
|
673
|
+
// API Routes
|
|
674
|
+
app.use('/api', routes);
|
|
675
|
+
|
|
676
|
+
// Root Route
|
|
677
|
+
app.get('/', (req, res) => {
|
|
678
|
+
res.json({
|
|
679
|
+
message: 'Welcome to the API',
|
|
680
|
+
version: '1.0.0',
|
|
681
|
+
endpoints: {
|
|
682
|
+
health: '/api/health',
|
|
683
|
+
},
|
|
684
|
+
});
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
// 404 Handler
|
|
688
|
+
app.use((req, res) => {
|
|
689
|
+
res.status(404).json({
|
|
690
|
+
success: false,
|
|
691
|
+
message: 'Route not found',
|
|
692
|
+
});
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
// Error Handler (must be last)
|
|
696
|
+
app.use(errorHandler);
|
|
697
|
+
|
|
698
|
+
export default app;
|
|
699
|
+
`;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
function generateServerFile(isTypeScript) {
|
|
703
|
+
return `${isTypeScript ? "import 'dotenv/config';" : "import dotenv from 'dotenv';\ndotenv.config();"}
|
|
704
|
+
import app from './app';
|
|
705
|
+
|
|
706
|
+
const PORT = process.env.PORT || 3000;
|
|
707
|
+
|
|
708
|
+
const server = app.listen(PORT, () => {
|
|
709
|
+
console.log(\`🚀 Server running on http://localhost:\${PORT}\`);
|
|
710
|
+
console.log(\`📝 Environment: \${process.env.NODE_ENV || 'development'}\`);
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
// Graceful shutdown
|
|
714
|
+
process.on('SIGTERM', () => {
|
|
715
|
+
console.log('SIGTERM signal received: closing HTTP server');
|
|
716
|
+
server.close(() => {
|
|
717
|
+
console.log('HTTP server closed');
|
|
718
|
+
});
|
|
719
|
+
});
|
|
720
|
+
`;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
function generateDatabaseConfig(database, isTypeScript) {
|
|
724
|
+
if (database === 'mongodb') {
|
|
725
|
+
return `import mongoose from 'mongoose';
|
|
726
|
+
|
|
727
|
+
export const connectDB = async () => {
|
|
728
|
+
try {
|
|
729
|
+
const conn = await mongoose.connect(process.env.MONGODB_URI${isTypeScript ? ' as string' : ''});
|
|
730
|
+
console.log(\`✅ MongoDB Connected: \${conn.connection.host}\`);
|
|
731
|
+
} catch (error) {
|
|
732
|
+
console.error(\`❌ MongoDB Error: \${error${isTypeScript ? '.message' : ''}}\`);
|
|
733
|
+
process.exit(1);
|
|
734
|
+
}
|
|
735
|
+
};
|
|
736
|
+
|
|
737
|
+
export default connectDB;
|
|
738
|
+
`;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
if (database === 'prisma') {
|
|
742
|
+
return `import { PrismaClient } from '@prisma/client';
|
|
743
|
+
|
|
744
|
+
const prisma = new PrismaClient();
|
|
745
|
+
|
|
746
|
+
export const connectDB = async () => {
|
|
747
|
+
try {
|
|
748
|
+
await prisma.$connect();
|
|
749
|
+
console.log('✅ Database connected successfully');
|
|
750
|
+
} catch (error) {
|
|
751
|
+
console.error('❌ Database connection failed:', error);
|
|
752
|
+
process.exit(1);
|
|
753
|
+
}
|
|
754
|
+
};
|
|
755
|
+
|
|
756
|
+
export const disconnectDB = async () => {
|
|
757
|
+
await prisma.$disconnect();
|
|
758
|
+
};
|
|
759
|
+
|
|
760
|
+
export default prisma;
|
|
761
|
+
`;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
return `// Database configuration
|
|
765
|
+
// TODO: Implement database connection
|
|
766
|
+
|
|
767
|
+
export const connectDB = async () => {
|
|
768
|
+
console.log('✅ Database connected');
|
|
769
|
+
};
|
|
770
|
+
`;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
function generateErrorHandler(isTypeScript) {
|
|
774
|
+
return `import { Request, Response, NextFunction } from 'express';
|
|
775
|
+
|
|
776
|
+
export const errorHandler = (
|
|
777
|
+
err${isTypeScript ? ': any' : ''},
|
|
778
|
+
req${isTypeScript ? ': Request' : ''},
|
|
779
|
+
res${isTypeScript ? ': Response' : ''},
|
|
780
|
+
next${isTypeScript ? ': NextFunction' : ''}
|
|
781
|
+
) => {
|
|
782
|
+
const statusCode = err.statusCode || 500;
|
|
783
|
+
const message = err.message || 'Internal Server Error';
|
|
784
|
+
|
|
785
|
+
res.status(statusCode).json({
|
|
786
|
+
success: false,
|
|
787
|
+
message,
|
|
788
|
+
...(process.env.NODE_ENV === 'development' && {
|
|
789
|
+
stack: err.stack,
|
|
790
|
+
}),
|
|
791
|
+
});
|
|
792
|
+
};
|
|
793
|
+
`;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
function generateCorsConfig(isTypeScript) {
|
|
797
|
+
return `import { CorsOptions } from 'cors';
|
|
798
|
+
|
|
799
|
+
export const corsOptions${isTypeScript ? ': CorsOptions' : ''} = {
|
|
800
|
+
origin: (origin${isTypeScript ? ': string | undefined' : ''}, callback${isTypeScript ? ': (err: Error | null, allow?: boolean) => void' : ''}) => {
|
|
801
|
+
const allowedOrigins = [
|
|
802
|
+
'http://localhost:3000',
|
|
803
|
+
'http://localhost:5173',
|
|
804
|
+
process.env.CORS_ORIGIN,
|
|
805
|
+
].filter(Boolean);
|
|
806
|
+
|
|
807
|
+
if (!origin || allowedOrigins.includes(origin)) {
|
|
808
|
+
callback(null, true);
|
|
809
|
+
} else {
|
|
810
|
+
callback(new Error('Not allowed by CORS'));
|
|
811
|
+
}
|
|
812
|
+
},
|
|
813
|
+
credentials: true,
|
|
814
|
+
optionsSuccessStatus: 200,
|
|
815
|
+
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
|
|
816
|
+
allowedHeaders: ['Content-Type', 'Authorization'],
|
|
817
|
+
};
|
|
818
|
+
`;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
function generateRoutesIndex() {
|
|
822
|
+
return `import { Router } from 'express';
|
|
823
|
+
|
|
824
|
+
const router = Router();
|
|
825
|
+
|
|
826
|
+
// Health check
|
|
827
|
+
router.get('/health', (req, res) => {
|
|
828
|
+
res.json({
|
|
829
|
+
status: 'OK',
|
|
830
|
+
timestamp: new Date().toISOString()
|
|
831
|
+
});
|
|
832
|
+
});
|
|
833
|
+
|
|
834
|
+
// Add your routes here
|
|
835
|
+
// Example: router.use('/users', userRoutes);
|
|
836
|
+
|
|
837
|
+
export default router;
|
|
838
|
+
`;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
function getArchitectureDescription(structure) {
|
|
842
|
+
const descriptions = {
|
|
843
|
+
mvc: `
|
|
844
|
+
**Model-View-Controller (MVC)**
|
|
845
|
+
|
|
846
|
+
- \`models/\` - Data models and database schemas
|
|
847
|
+
- \`views/\` - Template rendering (if needed)
|
|
848
|
+
- \`controllers/\` - Request handlers and business logic
|
|
849
|
+
- \`routes/\` - API route definitions
|
|
850
|
+
- \`middleware/\` - Custom middleware functions
|
|
851
|
+
- \`utils/\` - Utility functions and helpers
|
|
852
|
+
- \`config/\` - Configuration files
|
|
853
|
+
|
|
854
|
+
**Flow:** Route → Controller → Model → Controller → Response
|
|
855
|
+
`,
|
|
856
|
+
'clean-architecture': `
|
|
857
|
+
**Clean Architecture (Onion Architecture)**
|
|
858
|
+
|
|
859
|
+
- \`domain/\` - Business entities and rules (core layer)
|
|
860
|
+
- \`entities/\` - Business objects
|
|
861
|
+
- \`repositories/\` - Repository interfaces
|
|
862
|
+
- \`use-cases/\` - Application business rules
|
|
863
|
+
|
|
864
|
+
- \`application/\` - Application business rules
|
|
865
|
+
- \`dto/\` - Data transfer objects
|
|
866
|
+
- \`services/\` - Application services
|
|
867
|
+
- \`interfaces/\` - Application interfaces
|
|
868
|
+
|
|
869
|
+
- \`infrastructure/\` - External concerns
|
|
870
|
+
- \`database/\` - Database implementations
|
|
871
|
+
- \`repositories/\` - Repository implementations
|
|
872
|
+
- \`external-services/\` - Third-party integrations
|
|
873
|
+
|
|
874
|
+
- \`presentation/\` - UI layer (HTTP)
|
|
875
|
+
- \`controllers/\` - HTTP request handlers
|
|
876
|
+
- \`middleware/\` - HTTP middleware
|
|
877
|
+
- \`routes/\` - Route definitions
|
|
878
|
+
|
|
879
|
+
**Dependencies flow inward:** Presentation → Application → Domain
|
|
880
|
+
`,
|
|
881
|
+
'feature-based': `
|
|
882
|
+
**Feature-based Architecture**
|
|
883
|
+
|
|
884
|
+
Each feature is self-contained with its own:
|
|
885
|
+
- \`controllers/\` - Feature-specific controllers
|
|
886
|
+
- \`services/\` - Feature business logic
|
|
887
|
+
- \`routes/\` - Feature routes
|
|
888
|
+
- \`models/\` - Feature data models
|
|
889
|
+
- \`middleware/\` - Feature-specific middleware
|
|
890
|
+
- \`validators/\` - Input validation
|
|
891
|
+
|
|
892
|
+
**Benefits:** Easy to understand, scale, and maintain. Each feature can be developed independently.
|
|
893
|
+
`,
|
|
894
|
+
layered: `
|
|
895
|
+
**Layered Architecture**
|
|
896
|
+
|
|
897
|
+
- \`controllers/\` - Presentation layer (HTTP handlers)
|
|
898
|
+
- \`services/\` - Business logic layer
|
|
899
|
+
- \`repositories/\` - Data access layer
|
|
900
|
+
- \`models/\` - Data models
|
|
901
|
+
- \`routes/\` - Route definitions
|
|
902
|
+
- \`middleware/\` - Middleware functions
|
|
903
|
+
- \`validators/\` - Input validation
|
|
904
|
+
- \`utils/\` - Utility functions
|
|
905
|
+
- \`config/\` - Configuration files
|
|
906
|
+
|
|
907
|
+
**Flow:** Controller → Service → Repository → Database
|
|
908
|
+
Each layer only communicates with adjacent layers.
|
|
909
|
+
`,
|
|
910
|
+
};
|
|
911
|
+
|
|
912
|
+
return descriptions[structure] || 'Custom architecture';
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
export default generateExpressTemplate;
|