create-flex-stack 1.0.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 +57 -0
- package/dist/index.js +330 -0
- package/dist/templates/cleanTemplate.js +697 -0
- package/dist/templates/frontendTemplate.js +855 -0
- package/dist/templates/hexagonalTemplate.js +855 -0
- package/dist/templates/layeredTemplate.js +745 -0
- package/dist/templates/modularTemplate.js +691 -0
- package/dist/templates/sharedTemplate.js +654 -0
- package/dist/templates/vmcTemplate.js +26 -0
- package/dist/types.js +1 -0
- package/dist/utils/generator.js +1120 -0
- package/package.json +46 -0
|
@@ -0,0 +1,654 @@
|
|
|
1
|
+
function toPackageName(name) {
|
|
2
|
+
const normalized = name
|
|
3
|
+
.toLowerCase()
|
|
4
|
+
.replace(/[^a-z0-9._-]+/g, '-')
|
|
5
|
+
.replace(/^[._-]+/, '')
|
|
6
|
+
.replace(/[._-]+$/, '');
|
|
7
|
+
return normalized || 'app';
|
|
8
|
+
}
|
|
9
|
+
export function getTsConfig(options) {
|
|
10
|
+
const decorators = options.orm === 'typeorm'
|
|
11
|
+
? `,\n "experimentalDecorators": true,\n "emitDecoratorMetadata": true,\n "strictPropertyInitialization": false`
|
|
12
|
+
: '';
|
|
13
|
+
return `{
|
|
14
|
+
"compilerOptions": {
|
|
15
|
+
"target": "es2022",
|
|
16
|
+
"module": "NodeNext",
|
|
17
|
+
"moduleResolution": "NodeNext",
|
|
18
|
+
"outDir": "./dist",
|
|
19
|
+
"rootDir": "./src",
|
|
20
|
+
"strict": true,
|
|
21
|
+
"isolatedModules": true,
|
|
22
|
+
"esModuleInterop": true,
|
|
23
|
+
"skipLibCheck": true,
|
|
24
|
+
"forceConsistentCasingInFileNames": true,
|
|
25
|
+
"resolveJsonModule": true${decorators}
|
|
26
|
+
},
|
|
27
|
+
"include": ["src/**/*"]
|
|
28
|
+
}`;
|
|
29
|
+
}
|
|
30
|
+
export function getGitIgnore() {
|
|
31
|
+
return `# Logs
|
|
32
|
+
logs
|
|
33
|
+
*.log
|
|
34
|
+
npm-debug.log*
|
|
35
|
+
yarn-debug.log*
|
|
36
|
+
yarn-error.log*
|
|
37
|
+
lerna-debug.log*
|
|
38
|
+
|
|
39
|
+
# Diagnostic reports
|
|
40
|
+
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
|
41
|
+
|
|
42
|
+
# Dependency directories
|
|
43
|
+
node_modules/
|
|
44
|
+
jspm_packages/
|
|
45
|
+
|
|
46
|
+
# Dist / Build
|
|
47
|
+
dist/
|
|
48
|
+
build/
|
|
49
|
+
.tsbuildinfo
|
|
50
|
+
|
|
51
|
+
# Environment variables
|
|
52
|
+
.env
|
|
53
|
+
.env.local
|
|
54
|
+
.env.development.local
|
|
55
|
+
.env.test.local
|
|
56
|
+
.env.production.local
|
|
57
|
+
|
|
58
|
+
# IDEs and editors
|
|
59
|
+
.idea/
|
|
60
|
+
.vscode/
|
|
61
|
+
*.suo
|
|
62
|
+
*.ntvs*
|
|
63
|
+
*.njsproj
|
|
64
|
+
*.sln
|
|
65
|
+
*.sw?
|
|
66
|
+
|
|
67
|
+
# OS files
|
|
68
|
+
.DS_Store
|
|
69
|
+
Thumbs.db
|
|
70
|
+
|
|
71
|
+
# Uploads
|
|
72
|
+
uploads/
|
|
73
|
+
`;
|
|
74
|
+
}
|
|
75
|
+
export function getDockerFiles(options) {
|
|
76
|
+
const isPrisma = options.orm === 'prisma';
|
|
77
|
+
const composeDatabaseUrl = options.database === 'postgres'
|
|
78
|
+
? 'postgresql://postgres:password@db:5432/${PROJECT_NAME}?schema=public'
|
|
79
|
+
: options.database === 'mysql'
|
|
80
|
+
? 'mysql://user:password@db:3306/${PROJECT_NAME}'
|
|
81
|
+
: options.database === 'mongodb'
|
|
82
|
+
? 'mongodb://db:27017/${PROJECT_NAME}'
|
|
83
|
+
: '${DATABASE_URL}';
|
|
84
|
+
const dbService = options.database === 'postgres'
|
|
85
|
+
? ` db:
|
|
86
|
+
image: postgres:15-alpine
|
|
87
|
+
container_name: \${PROJECT_NAME}-db
|
|
88
|
+
restart: always
|
|
89
|
+
environment:
|
|
90
|
+
POSTGRES_USER: postgres
|
|
91
|
+
POSTGRES_PASSWORD: password
|
|
92
|
+
POSTGRES_DB: \${PROJECT_NAME}
|
|
93
|
+
ports:
|
|
94
|
+
- "5432:5432"
|
|
95
|
+
volumes:
|
|
96
|
+
- pgdata:/var/lib/postgresql/data`
|
|
97
|
+
: options.database === 'mysql'
|
|
98
|
+
? ` db:
|
|
99
|
+
image: mysql:8
|
|
100
|
+
container_name: \${PROJECT_NAME}-db
|
|
101
|
+
restart: always
|
|
102
|
+
environment:
|
|
103
|
+
MYSQL_ROOT_PASSWORD: password
|
|
104
|
+
MYSQL_DATABASE: \${PROJECT_NAME}
|
|
105
|
+
MYSQL_USER: user
|
|
106
|
+
MYSQL_PASSWORD: password
|
|
107
|
+
ports:
|
|
108
|
+
- "3306:3306"
|
|
109
|
+
volumes:
|
|
110
|
+
- mysqldata:/var/lib/mysql`
|
|
111
|
+
: options.database === 'mongodb'
|
|
112
|
+
? ` db:
|
|
113
|
+
image: mongo:6
|
|
114
|
+
container_name: \${PROJECT_NAME}-db
|
|
115
|
+
restart: always
|
|
116
|
+
ports:
|
|
117
|
+
- "27017:27017"
|
|
118
|
+
volumes:
|
|
119
|
+
- mongodata:/data/db`
|
|
120
|
+
: ''; // No SQLite service needed in docker-compose
|
|
121
|
+
const redisService = options.caching
|
|
122
|
+
? `\n redis:
|
|
123
|
+
image: redis:7-alpine
|
|
124
|
+
container_name: \${PROJECT_NAME}-redis
|
|
125
|
+
restart: always
|
|
126
|
+
ports:
|
|
127
|
+
- "6379:6379"`
|
|
128
|
+
: '';
|
|
129
|
+
let volumes = '';
|
|
130
|
+
if (options.database === 'postgres') {
|
|
131
|
+
volumes = `volumes:\n pgdata:`;
|
|
132
|
+
}
|
|
133
|
+
else if (options.database === 'mysql') {
|
|
134
|
+
volumes = `volumes:\n mysqldata:`;
|
|
135
|
+
}
|
|
136
|
+
else if (options.database === 'mongodb') {
|
|
137
|
+
volumes = `volumes:\n mongodata:`;
|
|
138
|
+
}
|
|
139
|
+
const dockerfile = isPrisma
|
|
140
|
+
? `FROM node:20-slim AS builder
|
|
141
|
+
WORKDIR /usr/src/app
|
|
142
|
+
|
|
143
|
+
RUN apt-get update -y && apt-get install -y openssl ca-certificates && rm -rf /var/lib/apt/lists/*
|
|
144
|
+
|
|
145
|
+
COPY package*.json ./
|
|
146
|
+
RUN npm ci
|
|
147
|
+
COPY . .
|
|
148
|
+
RUN npx prisma generate
|
|
149
|
+
RUN npm run build
|
|
150
|
+
|
|
151
|
+
FROM node:20-slim
|
|
152
|
+
WORKDIR /usr/src/app
|
|
153
|
+
|
|
154
|
+
RUN apt-get update -y && apt-get install -y openssl ca-certificates && rm -rf /var/lib/apt/lists/*
|
|
155
|
+
|
|
156
|
+
COPY package*.json ./
|
|
157
|
+
RUN npm ci --omit=dev
|
|
158
|
+
COPY --from=builder /usr/src/app/dist ./dist
|
|
159
|
+
COPY --from=builder /usr/src/app/prisma ./prisma
|
|
160
|
+
COPY --from=builder /usr/src/app/node_modules/.prisma ./node_modules/.prisma
|
|
161
|
+
|
|
162
|
+
EXPOSE 3000
|
|
163
|
+
CMD ["node", "dist/server.js"]
|
|
164
|
+
`
|
|
165
|
+
: `FROM node:20-slim AS builder
|
|
166
|
+
WORKDIR /usr/src/app
|
|
167
|
+
|
|
168
|
+
COPY package*.json ./
|
|
169
|
+
RUN npm ci
|
|
170
|
+
COPY . .
|
|
171
|
+
RUN npm run build
|
|
172
|
+
|
|
173
|
+
FROM node:20-slim
|
|
174
|
+
WORKDIR /usr/src/app
|
|
175
|
+
|
|
176
|
+
COPY package*.json ./
|
|
177
|
+
RUN npm ci --omit=dev
|
|
178
|
+
COPY --from=builder /usr/src/app/dist ./dist
|
|
179
|
+
|
|
180
|
+
EXPOSE 3000
|
|
181
|
+
CMD ["node", "dist/server.js"]
|
|
182
|
+
`;
|
|
183
|
+
const dockerCompose = `version: '3.8'
|
|
184
|
+
|
|
185
|
+
services:
|
|
186
|
+
app:
|
|
187
|
+
build: .
|
|
188
|
+
container_name: \${PROJECT_NAME}-app
|
|
189
|
+
ports:
|
|
190
|
+
- "\${PORT:-3000}:\${PORT:-3000}"
|
|
191
|
+
env_file:
|
|
192
|
+
- .env
|
|
193
|
+
environment:
|
|
194
|
+
- NODE_ENV=production
|
|
195
|
+
- PORT=\${PORT:-3000}
|
|
196
|
+
- DATABASE_URL=${composeDatabaseUrl}
|
|
197
|
+
${options.auth ? `- JWT_SECRET=\${JWT_SECRET}
|
|
198
|
+
- JWT_REFRESH_SECRET=\${JWT_REFRESH_SECRET}` : ''}
|
|
199
|
+
${options.caching ? '- REDIS_URL=redis://redis:6379' : ''}
|
|
200
|
+
depends_on:
|
|
201
|
+
${dbService ? '- db' : ''}
|
|
202
|
+
${options.caching ? '- redis' : ''}
|
|
203
|
+
|
|
204
|
+
${dbService}${redisService}
|
|
205
|
+
|
|
206
|
+
${volumes}
|
|
207
|
+
`;
|
|
208
|
+
return { dockerfile, dockerCompose };
|
|
209
|
+
}
|
|
210
|
+
export function getDotEnv(options) {
|
|
211
|
+
let dbUrl = '';
|
|
212
|
+
if (options.database === 'postgres') {
|
|
213
|
+
dbUrl = 'postgresql://postgres:password@localhost:5432/' + options.projectName + '?schema=public';
|
|
214
|
+
}
|
|
215
|
+
else if (options.database === 'mysql') {
|
|
216
|
+
dbUrl = 'mysql://user:password@localhost:3306/' + options.projectName;
|
|
217
|
+
}
|
|
218
|
+
else if (options.database === 'sqlite') {
|
|
219
|
+
dbUrl = 'file:./dev.db';
|
|
220
|
+
}
|
|
221
|
+
else if (options.database === 'mongodb') {
|
|
222
|
+
dbUrl = 'mongodb://localhost:27017/' + options.projectName;
|
|
223
|
+
}
|
|
224
|
+
let env = `PROJECT_NAME="${toPackageName(options.projectName)}"
|
|
225
|
+
PORT=3000
|
|
226
|
+
NODE_ENV=development
|
|
227
|
+
DATABASE_URL="${dbUrl}"
|
|
228
|
+
`;
|
|
229
|
+
if (options.auth) {
|
|
230
|
+
env += `JWT_SECRET="super-secret-key-change-me-in-production"
|
|
231
|
+
JWT_REFRESH_SECRET="super-secret-refresh-key-change-me-in-production"
|
|
232
|
+
JWT_EXPIRES_IN="15m"
|
|
233
|
+
JWT_REFRESH_EXPIRES_IN="7d"
|
|
234
|
+
`;
|
|
235
|
+
}
|
|
236
|
+
if (options.caching) {
|
|
237
|
+
env += `REDIS_URL="redis://localhost:6379"
|
|
238
|
+
`;
|
|
239
|
+
}
|
|
240
|
+
return env;
|
|
241
|
+
}
|
|
242
|
+
export function getReadme(options) {
|
|
243
|
+
const architectureName = options.architecture === 'clean'
|
|
244
|
+
? 'Clean Architecture'
|
|
245
|
+
: options.architecture === 'hexagonal'
|
|
246
|
+
? 'Hexagonal Architecture (Ports & Adapters)'
|
|
247
|
+
: options.architecture === 'modular'
|
|
248
|
+
? 'Modular Feature-based Architecture'
|
|
249
|
+
: options.architecture === 'vmc'
|
|
250
|
+
? 'VMC Architecture (View, Model, Controller)'
|
|
251
|
+
: 'Layered Architecture (MVC)';
|
|
252
|
+
const prismaSection = options.orm === 'prisma'
|
|
253
|
+
? `## Prisma
|
|
254
|
+
|
|
255
|
+
Prisma needs a generated client before the app can import \`@prisma/client\`.
|
|
256
|
+
|
|
257
|
+
Development:
|
|
258
|
+
\`\`\`bash
|
|
259
|
+
npm run db:generate
|
|
260
|
+
npm run db:push
|
|
261
|
+
\`\`\`
|
|
262
|
+
|
|
263
|
+
Production or Docker release flow:
|
|
264
|
+
\`\`\`bash
|
|
265
|
+
npm run db:generate
|
|
266
|
+
npm run db:migrate:deploy
|
|
267
|
+
\`\`\`
|
|
268
|
+
|
|
269
|
+
Equivalent direct Prisma commands:
|
|
270
|
+
\`\`\`bash
|
|
271
|
+
npx prisma generate
|
|
272
|
+
npx prisma migrate deploy
|
|
273
|
+
\`\`\`
|
|
274
|
+
|
|
275
|
+
Useful local tools:
|
|
276
|
+
\`\`\`bash
|
|
277
|
+
npx prisma studio
|
|
278
|
+
\`\`\`
|
|
279
|
+
`
|
|
280
|
+
: '';
|
|
281
|
+
const dockerSection = options.docker
|
|
282
|
+
? `## Docker
|
|
283
|
+
|
|
284
|
+
The generated \`Dockerfile\` uses \`node:20-slim\` and installs \`openssl\` plus CA certificates so Prisma engines work reliably in containers.
|
|
285
|
+
|
|
286
|
+
\`\`\`bash
|
|
287
|
+
docker compose up --build
|
|
288
|
+
\`\`\`
|
|
289
|
+
|
|
290
|
+
Make sure \`.env\` contains \`PROJECT_NAME\`, \`PORT\`, and \`DATABASE_URL\`. The compose file reads \`.env\` via \`env_file\`.
|
|
291
|
+
`
|
|
292
|
+
: '';
|
|
293
|
+
const swaggerSection = options.swagger
|
|
294
|
+
? `## API Documentation
|
|
295
|
+
|
|
296
|
+
Swagger UI is mounted at:
|
|
297
|
+
|
|
298
|
+
\`\`\`text
|
|
299
|
+
http://localhost:3000/docs
|
|
300
|
+
\`\`\`
|
|
301
|
+
|
|
302
|
+
The OpenAPI document includes the user endpoints generated for this project.
|
|
303
|
+
`
|
|
304
|
+
: '';
|
|
305
|
+
const testingSection = options.testing
|
|
306
|
+
? `## Tests
|
|
307
|
+
|
|
308
|
+
Tests live in \`tests/\`.
|
|
309
|
+
|
|
310
|
+
\`\`\`bash
|
|
311
|
+
npm test
|
|
312
|
+
\`\`\`
|
|
313
|
+
`
|
|
314
|
+
: '';
|
|
315
|
+
return `# ${options.projectName}
|
|
316
|
+
|
|
317
|
+
Created with create-flex-stack. This project uses **${architectureName}** built with **ExpressJS + TypeScript**${options.frontend ? ' and a React frontend client' : ''}.
|
|
318
|
+
|
|
319
|
+
## Technologies
|
|
320
|
+
- **Backend Framework**: ExpressJS
|
|
321
|
+
- **Database**: ${options.database}
|
|
322
|
+
- **ORM / ODM**: ${options.orm}
|
|
323
|
+
- **Validation**: ${options.validation}
|
|
324
|
+
${options.auth ? '- **Auth**: JWT Authentication & Refresh Tokens' : ''}
|
|
325
|
+
${options.fileUpload ? '- **File Upload**: Configured with Multer' : ''}
|
|
326
|
+
${options.docker ? '- **Docker**: Dockerfile & docker-compose configuration' : ''}
|
|
327
|
+
${options.rateLimiting ? '- **Rate Limiting**: express-rate-limit' : ''}
|
|
328
|
+
${options.swagger ? '- **Swagger**: API Documentation' : ''}
|
|
329
|
+
${options.caching ? '- **Caching**: Redis Cache Layer' : ''}
|
|
330
|
+
${options.rbac ? '- **RBAC**: Role-Based Access Control' : ''}
|
|
331
|
+
${options.testing ? '- **Testing**: Jest unit & integration testing' : ''}
|
|
332
|
+
|
|
333
|
+
## Project Structure
|
|
334
|
+
|
|
335
|
+
${options.frontend ? `- \`backend/\`: Express API
|
|
336
|
+
- \`frontend/\`: React client
|
|
337
|
+
- \`docker-compose.yml\`: local container orchestration` : `- \`src/\`: application source code
|
|
338
|
+
- \`prisma/\`: Prisma schema and migrations when Prisma is selected
|
|
339
|
+
- \`tests/\`: Jest tests when testing is enabled`}
|
|
340
|
+
|
|
341
|
+
## Getting Started
|
|
342
|
+
|
|
343
|
+
### Installation
|
|
344
|
+
Install dependencies:
|
|
345
|
+
\`\`\`bash
|
|
346
|
+
npm install
|
|
347
|
+
\`\`\`
|
|
348
|
+
|
|
349
|
+
Configure environment variables in \`.env\`.
|
|
350
|
+
|
|
351
|
+
${options.orm !== 'prisma' ? 'Ensure your selected database is running before starting the API.' : ''}
|
|
352
|
+
|
|
353
|
+
### Development
|
|
354
|
+
\`\`\`bash
|
|
355
|
+
npm run dev
|
|
356
|
+
\`\`\`
|
|
357
|
+
|
|
358
|
+
### Build
|
|
359
|
+
\`\`\`bash
|
|
360
|
+
npm run build
|
|
361
|
+
\`\`\`
|
|
362
|
+
|
|
363
|
+
### Production Start
|
|
364
|
+
\`\`\`bash
|
|
365
|
+
npm start
|
|
366
|
+
\`\`\`
|
|
367
|
+
|
|
368
|
+
${prismaSection}
|
|
369
|
+
${dockerSection}
|
|
370
|
+
${swaggerSection}
|
|
371
|
+
${testingSection}
|
|
372
|
+
`;
|
|
373
|
+
}
|
|
374
|
+
export function getRootPackageJson(projectName) {
|
|
375
|
+
const packageName = toPackageName(projectName);
|
|
376
|
+
const pkg = {
|
|
377
|
+
name: packageName,
|
|
378
|
+
version: "1.0.0",
|
|
379
|
+
private: true,
|
|
380
|
+
workspaces: [
|
|
381
|
+
"backend",
|
|
382
|
+
"frontend"
|
|
383
|
+
],
|
|
384
|
+
scripts: {
|
|
385
|
+
"dev": "concurrently \"npm run dev --workspace=backend\" \"npm run dev --workspace=frontend\"",
|
|
386
|
+
"build": "npm run build --workspace=backend && npm run build --workspace=frontend",
|
|
387
|
+
"start": "npm start --workspace=backend",
|
|
388
|
+
"test": "npm test --workspace=backend"
|
|
389
|
+
},
|
|
390
|
+
dependencies: {
|
|
391
|
+
"concurrently": "^8.2.2"
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
return JSON.stringify(pkg, null, 2);
|
|
395
|
+
}
|
|
396
|
+
export function getPackageJson(options) {
|
|
397
|
+
const packageName = toPackageName(options.projectName);
|
|
398
|
+
const dependencies = {
|
|
399
|
+
"express": "^4.19.2",
|
|
400
|
+
"cors": "^2.8.5",
|
|
401
|
+
"dotenv": "^16.4.5",
|
|
402
|
+
"helmet": "^7.1.0"
|
|
403
|
+
};
|
|
404
|
+
const devDependencies = {
|
|
405
|
+
"typescript": "^5.4.5",
|
|
406
|
+
"@types/node": "^20.12.7",
|
|
407
|
+
"@types/express": "^4.17.21",
|
|
408
|
+
"@types/cors": "^2.8.17",
|
|
409
|
+
"tsx": "^4.7.2",
|
|
410
|
+
"nodemon": "^3.1.0"
|
|
411
|
+
};
|
|
412
|
+
// ORM Specifics
|
|
413
|
+
if (options.orm === 'prisma') {
|
|
414
|
+
dependencies["@prisma/client"] = "^5.12.1";
|
|
415
|
+
devDependencies["prisma"] = "^5.12.1";
|
|
416
|
+
}
|
|
417
|
+
else if (options.orm === 'mongoose') {
|
|
418
|
+
dependencies["mongoose"] = "^8.3.1";
|
|
419
|
+
}
|
|
420
|
+
else if (options.orm === 'typeorm') {
|
|
421
|
+
dependencies["typeorm"] = "^0.3.20";
|
|
422
|
+
dependencies["reflect-metadata"] = "^0.2.2";
|
|
423
|
+
if (options.database === 'postgres') {
|
|
424
|
+
dependencies["pg"] = "^8.11.5";
|
|
425
|
+
devDependencies["@types/pg"] = "^8.11.5";
|
|
426
|
+
}
|
|
427
|
+
else if (options.database === 'mysql') {
|
|
428
|
+
dependencies["mysql2"] = "^3.9.7";
|
|
429
|
+
}
|
|
430
|
+
else if (options.database === 'sqlite') {
|
|
431
|
+
dependencies["sqlite3"] = "^5.1.7";
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
// Auth Specifics
|
|
435
|
+
if (options.auth) {
|
|
436
|
+
dependencies["jsonwebtoken"] = "^9.0.2";
|
|
437
|
+
dependencies["bcryptjs"] = "^2.4.3";
|
|
438
|
+
devDependencies["@types/jsonwebtoken"] = "^9.0.6";
|
|
439
|
+
devDependencies["@types/bcryptjs"] = "^2.4.6";
|
|
440
|
+
}
|
|
441
|
+
// Validation
|
|
442
|
+
if (options.validation === 'zod') {
|
|
443
|
+
dependencies["zod"] = "^3.22.4";
|
|
444
|
+
}
|
|
445
|
+
else if (options.validation === 'joi') {
|
|
446
|
+
dependencies["joi"] = "^17.12.3";
|
|
447
|
+
devDependencies["@types/joi"] = "^17.2.3";
|
|
448
|
+
}
|
|
449
|
+
// Optional modules
|
|
450
|
+
if (options.rateLimiting) {
|
|
451
|
+
dependencies["express-rate-limit"] = "^7.2.0";
|
|
452
|
+
}
|
|
453
|
+
if (options.swagger) {
|
|
454
|
+
dependencies["swagger-ui-express"] = "^5.0.0";
|
|
455
|
+
devDependencies["@types/swagger-ui-express"] = "^4.1.6";
|
|
456
|
+
}
|
|
457
|
+
if (options.caching) {
|
|
458
|
+
dependencies["redis"] = "^4.6.13";
|
|
459
|
+
}
|
|
460
|
+
if (options.fileUpload) {
|
|
461
|
+
dependencies["multer"] = "^2.2.0";
|
|
462
|
+
devDependencies["@types/multer"] = "^1.4.11";
|
|
463
|
+
}
|
|
464
|
+
// Testing setup (Jest)
|
|
465
|
+
if (options.testing) {
|
|
466
|
+
devDependencies["jest"] = "^29.7.0";
|
|
467
|
+
devDependencies["ts-jest"] = "^29.1.2";
|
|
468
|
+
devDependencies["supertest"] = "^6.3.4";
|
|
469
|
+
devDependencies["@types/jest"] = "^29.5.12";
|
|
470
|
+
devDependencies["@types/supertest"] = "^6.0.2";
|
|
471
|
+
}
|
|
472
|
+
const packageJson = {
|
|
473
|
+
name: `${packageName}-backend`,
|
|
474
|
+
version: "1.0.0",
|
|
475
|
+
description: `Express backend built with ${options.architecture} architecture`,
|
|
476
|
+
main: "dist/server.js",
|
|
477
|
+
type: "module",
|
|
478
|
+
scripts: {
|
|
479
|
+
"build": "tsc",
|
|
480
|
+
"start": "node dist/server.js",
|
|
481
|
+
"dev": "nodemon --watch src --ext ts --exec tsx src/server.ts",
|
|
482
|
+
"db:generate": options.orm === 'prisma' ? "npx prisma generate" : "echo 'No db:generate script needed'",
|
|
483
|
+
"db:push": options.orm === 'prisma' ? "npx prisma db push" : "echo 'No db:push script needed'",
|
|
484
|
+
"db:migrate:deploy": options.orm === 'prisma' ? "npx prisma migrate deploy" : "echo 'No db:migrate:deploy script needed'",
|
|
485
|
+
"test": options.testing ? "node --experimental-vm-modules node_modules/jest/bin/jest.js --passWithNoTests" : "echo 'No tests configured'"
|
|
486
|
+
},
|
|
487
|
+
dependencies,
|
|
488
|
+
devDependencies
|
|
489
|
+
};
|
|
490
|
+
return JSON.stringify(packageJson, null, 2);
|
|
491
|
+
}
|
|
492
|
+
export function getPrismaSchema(options) {
|
|
493
|
+
let provider = 'postgresql';
|
|
494
|
+
if (options.database === 'mysql')
|
|
495
|
+
provider = 'mysql';
|
|
496
|
+
if (options.database === 'sqlite')
|
|
497
|
+
provider = 'sqlite';
|
|
498
|
+
if (options.database === 'mongodb')
|
|
499
|
+
provider = 'mongodb';
|
|
500
|
+
let schema = `datasource db {
|
|
501
|
+
provider = "${provider}"
|
|
502
|
+
url = env("DATABASE_URL")
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
generator client {
|
|
506
|
+
provider = "prisma-client-js"
|
|
507
|
+
}
|
|
508
|
+
`;
|
|
509
|
+
schema += `\nmodel User {
|
|
510
|
+
id String @id @default(${provider === 'mongodb' ? 'auto()' : 'uuid()'}) ${provider === 'mongodb' ? '@map("_id") @db.ObjectId' : ''}
|
|
511
|
+
email String @unique
|
|
512
|
+
name String?
|
|
513
|
+
${options.auth ? ` password String
|
|
514
|
+
role String @default("user")` : ` role String?`}
|
|
515
|
+
createdAt DateTime @default(now())
|
|
516
|
+
updatedAt DateTime @updatedAt
|
|
517
|
+
${options.auth ? ` refreshToken String?` : ''}
|
|
518
|
+
}
|
|
519
|
+
`;
|
|
520
|
+
return schema;
|
|
521
|
+
}
|
|
522
|
+
export function getAuthenticatedPrismaUserModel(options) {
|
|
523
|
+
let provider = 'postgresql';
|
|
524
|
+
if (options.database === 'mysql')
|
|
525
|
+
provider = 'mysql';
|
|
526
|
+
if (options.database === 'sqlite')
|
|
527
|
+
provider = 'sqlite';
|
|
528
|
+
if (options.database === 'mongodb')
|
|
529
|
+
provider = 'mongodb';
|
|
530
|
+
return `model User {
|
|
531
|
+
id String @id @default(${provider === 'mongodb' ? 'auto()' : 'uuid()'}) ${provider === 'mongodb' ? '@map("_id") @db.ObjectId' : ''}
|
|
532
|
+
email String @unique
|
|
533
|
+
name String?
|
|
534
|
+
password String
|
|
535
|
+
role String @default("user")
|
|
536
|
+
createdAt DateTime @default(now())
|
|
537
|
+
updatedAt DateTime @updatedAt
|
|
538
|
+
refreshToken String?
|
|
539
|
+
}
|
|
540
|
+
`;
|
|
541
|
+
}
|
|
542
|
+
export function getMulterMiddleware() {
|
|
543
|
+
return `import multer from 'multer';
|
|
544
|
+
import path from 'path';
|
|
545
|
+
import fs from 'fs';
|
|
546
|
+
import { AppError } from './errorHandler.js'; // Fallback path
|
|
547
|
+
|
|
548
|
+
const uploadDir = path.resolve(process.cwd(), 'uploads');
|
|
549
|
+
if (!fs.existsSync(uploadDir)) {
|
|
550
|
+
fs.mkdirSync(uploadDir, { recursive: true });
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
const storage = multer.diskStorage({
|
|
554
|
+
destination: (req, file, cb) => {
|
|
555
|
+
cb(null, uploadDir);
|
|
556
|
+
},
|
|
557
|
+
filename: (req, file, cb) => {
|
|
558
|
+
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9);
|
|
559
|
+
cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname));
|
|
560
|
+
}
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
const fileFilter = (req: any, file: any, cb: any) => {
|
|
564
|
+
// Allow images and documents
|
|
565
|
+
const allowedTypes = ['.png', '.jpg', '.jpeg', '.pdf', '.docx', '.txt'];
|
|
566
|
+
const ext = path.extname(file.originalname).toLowerCase();
|
|
567
|
+
if (allowedTypes.includes(ext)) {
|
|
568
|
+
cb(null, true);
|
|
569
|
+
} else {
|
|
570
|
+
cb(new AppError(400, 'Invalid file type. Only PNG, JPG, PDF, DOCX, TXT allowed.'));
|
|
571
|
+
}
|
|
572
|
+
};
|
|
573
|
+
|
|
574
|
+
export const upload = multer({
|
|
575
|
+
storage,
|
|
576
|
+
fileFilter,
|
|
577
|
+
limits: {
|
|
578
|
+
fileSize: 5 * 1024 * 1024 // 5 MB limit
|
|
579
|
+
}
|
|
580
|
+
});
|
|
581
|
+
`;
|
|
582
|
+
}
|
|
583
|
+
export function getTypeOrmConfig(options) {
|
|
584
|
+
let type = 'postgres';
|
|
585
|
+
if (options.database === 'mysql')
|
|
586
|
+
type = 'mysql';
|
|
587
|
+
if (options.database === 'sqlite')
|
|
588
|
+
type = 'sqlite';
|
|
589
|
+
const databaseLine = type === 'sqlite' ? ' database: "dev.db",' : ' url: process.env.DATABASE_URL,';
|
|
590
|
+
return `import "reflect-metadata";
|
|
591
|
+
import { DataSource } from "typeorm";
|
|
592
|
+
import { UserEntity } from "../entities/user.entity.js"; // Fallback path
|
|
593
|
+
|
|
594
|
+
export const AppDataSource = new DataSource({
|
|
595
|
+
type: "${type}",
|
|
596
|
+
${databaseLine}
|
|
597
|
+
synchronize: true, // Auto-create tables in development
|
|
598
|
+
logging: false,
|
|
599
|
+
entities: [UserEntity],
|
|
600
|
+
migrations: [],
|
|
601
|
+
subscribers: [],
|
|
602
|
+
});
|
|
603
|
+
`;
|
|
604
|
+
}
|
|
605
|
+
export function getTypeOrmEntity() {
|
|
606
|
+
return `import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from "typeorm";
|
|
607
|
+
|
|
608
|
+
@Entity({ name: "users" })
|
|
609
|
+
export class UserEntity {
|
|
610
|
+
@PrimaryGeneratedColumn("uuid")
|
|
611
|
+
id!: string;
|
|
612
|
+
|
|
613
|
+
@Column({ unique: true })
|
|
614
|
+
email!: string;
|
|
615
|
+
|
|
616
|
+
@Column({ nullable: true })
|
|
617
|
+
name?: string;
|
|
618
|
+
|
|
619
|
+
@Column()
|
|
620
|
+
password!: string;
|
|
621
|
+
|
|
622
|
+
@Column({ default: "user" })
|
|
623
|
+
role!: string;
|
|
624
|
+
|
|
625
|
+
@Column({ nullable: true })
|
|
626
|
+
refreshToken?: string;
|
|
627
|
+
|
|
628
|
+
@CreateDateColumn()
|
|
629
|
+
createdAt!: Date;
|
|
630
|
+
|
|
631
|
+
@UpdateDateColumn()
|
|
632
|
+
updatedAt!: Date;
|
|
633
|
+
}
|
|
634
|
+
`;
|
|
635
|
+
}
|
|
636
|
+
export function getJestConfig() {
|
|
637
|
+
return `{
|
|
638
|
+
"preset": "ts-jest",
|
|
639
|
+
"testEnvironment": "node",
|
|
640
|
+
"testPathIgnorePatterns": ["/node_modules/", "/dist/"],
|
|
641
|
+
"extensionsToTreatAsEsm": [".ts"],
|
|
642
|
+
"moduleNameMapper": {
|
|
643
|
+
"^(\\\\.{1,2}/.*)\\\\.js$": "$1"
|
|
644
|
+
},
|
|
645
|
+
"transform": {
|
|
646
|
+
"^.+\\\\.tsx?$": [
|
|
647
|
+
"ts-jest",
|
|
648
|
+
{
|
|
649
|
+
"useESM": true
|
|
650
|
+
}
|
|
651
|
+
]
|
|
652
|
+
}
|
|
653
|
+
}`;
|
|
654
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { getLayeredFiles } from './layeredTemplate.js';
|
|
2
|
+
export function getVmcFiles(options) {
|
|
3
|
+
const layeredFiles = getLayeredFiles(options);
|
|
4
|
+
const files = {};
|
|
5
|
+
for (const [filePath, originalContent] of Object.entries(layeredFiles)) {
|
|
6
|
+
let targetPath = filePath
|
|
7
|
+
.replace('src/schemas/user.schema.ts', 'src/view/user.view.ts')
|
|
8
|
+
.replace('src/repositories/user.repository.ts', 'src/model/user.repository.ts')
|
|
9
|
+
.replace('src/models/user.model.ts', 'src/model/user.model.ts')
|
|
10
|
+
.replace('src/services/user.service.ts', 'src/model/user.model-service.ts')
|
|
11
|
+
.replace('src/controllers/user.controller.ts', 'src/controller/user.controller.ts');
|
|
12
|
+
let content = originalContent
|
|
13
|
+
.replaceAll('../repositories/user.repository.js', '../model/user.repository.js')
|
|
14
|
+
.replaceAll('../models/user.model.js', './user.model.js')
|
|
15
|
+
.replaceAll('../services/user.service.js', '../model/user.model-service.js')
|
|
16
|
+
.replaceAll('../controllers/user.controller.js', '../controller/user.controller.js')
|
|
17
|
+
.replaceAll('../schemas/user.schema.js', '../view/user.view.js')
|
|
18
|
+
.replaceAll('Traditional Layered', 'VMC')
|
|
19
|
+
.replaceAll('Layered Architecture', 'VMC Architecture');
|
|
20
|
+
if (targetPath === 'src/model/user.repository.ts') {
|
|
21
|
+
content = content.replaceAll('../models/user.model.js', './user.model.js');
|
|
22
|
+
}
|
|
23
|
+
files[targetPath] = content;
|
|
24
|
+
}
|
|
25
|
+
return files;
|
|
26
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|