crypt-express-app 1.0.0 → 1.3.1

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.

Potentially problematic release.


This version of crypt-express-app might be problematic. Click here for more details.

@@ -1,59 +1,22 @@
1
1
  // generate-module.ts
2
2
  import * as fs from 'fs';
3
3
  import * as path from 'path';
4
- // get module name from command line
5
- const args = process.argv.slice(2);
6
- // Check that exactly **one argument** is provided
7
- if (args.length !== 1) {
8
- console.error('Invalid module name: provide exactly one module name without spaces.');
9
- console.error('Example: npm run generate-module clientRole');
10
- process.exit(1);
11
- }
12
- const moduleInput = args[0];
13
- // Reject if input contains spaces
14
- if (/\s/.test(moduleInput)) {
15
- console.error('Invalid module name: spaces are not allowed. Use camelCase, kebab-case or snake_case.');
16
- process.exit(1);
17
- }
18
- if (!moduleInput) {
19
- console.error('Please provide a module name: npm run generate-module <module-name>');
20
- process.exit(1);
21
- }
22
- // Convert any input to camelCase
23
- const moduleName = moduleInput.includes('-') || moduleInput.includes('_') || moduleInput.includes(' ')
24
- ? moduleInput
25
- .toLowerCase()
26
- .replace(/[-_\s]+(.)/g, (_, c) => c.toUpperCase())
27
- : moduleInput; // preserve existing camelCase
28
- console.log("moduleName: ", moduleName);
29
- // Convert any input (camelCase, PascalCase, snake_case, spaces) to kebab-case
30
- const fileName = moduleName
31
- .replace(/([a-z])([A-Z])/g, '$1-$2') // add dash before uppercase letters
32
- .toLowerCase(); // lowercase everything
33
- console.log("fileName: ", fileName);
34
- // const basePath = path.join(__dirname, '..', 'src', 'modules', fileName);
35
- // const basePath = path.join(__dirname, '..', 'src', 'modules', fileName);
36
- const projectRoot = process.cwd();
37
- const modulesRoot = path.join(projectRoot, "src", "modules");
38
- const basePath = path.join(modulesRoot, fileName);
39
- // ensure modules directory exists
40
- if (!fs.existsSync(modulesRoot)) {
41
- fs.mkdirSync(modulesRoot, { recursive: true });
42
- }
43
- // prevent overwrite
44
- if (fs.existsSync(basePath)) {
45
- console.error(`❌ Module '${fileName}' already exists.`);
46
- process.exit(1);
47
- }
48
- fs.mkdirSync(basePath, { recursive: true });
49
- // Create folder
50
- if (!fs.existsSync(basePath)) {
51
- fs.mkdirSync(basePath, { recursive: true });
52
- }
53
- // Capitalize first letter
54
- const ModuleClassName = moduleName.charAt(0).toUpperCase() + moduleName.slice(1);
55
- // Service
56
- const serviceContent = `import { Create${ModuleClassName}Dto, Update${ModuleClassName}Dto, Create${ModuleClassName}Response, Update${ModuleClassName}Response, Get${ModuleClassName}Response, List${ModuleClassName}Response } from "./${fileName}.dto";
4
+ export function generateModule(projectDir, moduleName) {
5
+ // Convert any input (camelCase, PascalCase, snake_case, spaces) to kebab-case
6
+ const fileName = moduleName
7
+ .replace(/([a-z])([A-Z])/g, '$1-$2') // add dash before uppercase letters
8
+ .toLowerCase(); // lowercase everything
9
+ console.log("fileName: ", fileName);
10
+ const basePath = path.join(projectDir, 'src', 'modules', fileName);
11
+ // Create folder
12
+ if (!fs.existsSync(basePath)) {
13
+ fs.mkdirSync(basePath, { recursive: true });
14
+ }
15
+ // Capitalize first letter
16
+ const ModuleClassName = moduleName.charAt(0).toUpperCase() + moduleName.slice(1);
17
+ // Service
18
+ const serviceContent = `
19
+ import { Create${ModuleClassName}Dto, Update${ModuleClassName}Dto, Create${ModuleClassName}Response, Update${ModuleClassName}Response, Get${ModuleClassName}Response, List${ModuleClassName}Response } from "./${fileName}.dto";
57
20
  import { Prisma } from "@prisma/client";
58
21
  import prisma from '../../prisma/client';
59
22
  import { PaginationRequestDto } from "../common/common.dto";
@@ -140,9 +103,9 @@ export class ${ModuleClassName}Service {
140
103
 
141
104
  export default new ${ModuleClassName}Service()
142
105
  `;
143
- fs.writeFileSync(path.join(basePath, `${fileName}.service.ts`), serviceContent);
144
- // Controller
145
- const controllerContent = `
106
+ fs.writeFileSync(path.join(basePath, `${fileName}.service.ts`), serviceContent);
107
+ // Controller
108
+ const controllerContent = `
146
109
  import { Request, Response } from 'express';
147
110
  import { ${ModuleClassName}Service } from './${fileName}.service';
148
111
  import {
@@ -257,21 +220,21 @@ export class ${ModuleClassName}Controller {
257
220
  }
258
221
  }
259
222
  `;
260
- fs.writeFileSync(path.join(basePath, `${fileName}.controller.ts`), controllerContent);
261
- // Routes
262
- const routesContent = `
223
+ fs.writeFileSync(path.join(basePath, `${fileName}.controller.ts`), controllerContent);
224
+ // Routes
225
+ const routesContent = `
263
226
  import express, { Router } from 'express';
264
227
  import { ${ModuleClassName}Controller } from './${fileName}.controller';
265
228
  import { ${moduleName}Middleware } from './${fileName}.middlewares';
266
229
  import { authMiddleware } from '../../middlewares/auth.middleware';
267
230
  import ${moduleName}Service from './${fileName}.service';
268
- import { ActionsType, Resource, ResourceType } from '../../utils/consts';
231
+ import { TypeAction, Resource, TypeResource } from '../../utils/consts';
269
232
  import { authorizationMiddleware } from '../../middlewares/authorization.middleware';
270
233
 
271
234
  const router: Router = express.Router();
272
235
  const controller = new ${ModuleClassName}Controller(${moduleName}Service);
273
236
  router.use(authMiddleware);
274
- const guard = (actions: ActionsType[]) => authorizationMiddleware({ resource: Resource.COMMON, actions, resourceType: ResourceType.API_ENDPOINT });
237
+ const guard = (actions: TypeAction[]) => authorizationMiddleware({ resource: Resource.COMMON, actions, resourceType: TypeResource.API_ENDPOINT });
275
238
 
276
239
  /**
277
240
  * @openapi
@@ -300,7 +263,7 @@ const guard = (actions: ActionsType[]) => authorizationMiddleware({ resource: Re
300
263
  * schema:
301
264
  * $ref: '#/components/schemas/Create${ModuleClassName}ResponseDto'
302
265
  */
303
- router.post('/', ${moduleName}Middleware, guard([ActionsType.CREATE]), controller.create.bind(controller));
266
+ router.post('/', ${moduleName}Middleware, guard([TypeAction.CREATE]), controller.create.bind(controller));
304
267
 
305
268
  /**
306
269
  * @openapi
@@ -322,7 +285,7 @@ router.post('/', ${moduleName}Middleware, guard([ActionsType.CREATE]), controlle
322
285
  * schema:
323
286
  * $ref: '#/components/schemas/Get${ModuleClassName}ResponseDto'
324
287
  */
325
- router.get('/:${moduleName}Id', ${moduleName}Middleware, guard([ActionsType.READ]), controller.getById.bind(controller));
288
+ router.get('/:${moduleName}Id', ${moduleName}Middleware, guard([TypeAction.READ]), controller.getById.bind(controller));
326
289
 
327
290
  /**
328
291
  * @openapi
@@ -350,7 +313,7 @@ router.get('/:${moduleName}Id', ${moduleName}Middleware, guard([ActionsType.READ
350
313
  * schema:
351
314
  * $ref: '#/components/schemas/Update${ModuleClassName}ResponseDto'
352
315
  */
353
- router.put('/:${moduleName}Id', ${moduleName}Middleware, guard([ActionsType.UPDATE]), controller.update.bind(controller));
316
+ router.put('/:${moduleName}Id', ${moduleName}Middleware, guard([TypeAction.UPDATE]), controller.update.bind(controller));
354
317
 
355
318
  /**
356
319
  * @openapi
@@ -379,7 +342,7 @@ router.put('/:${moduleName}Id', ${moduleName}Middleware, guard([ActionsType.UPDA
379
342
  * message:
380
343
  * type: string
381
344
  */
382
- router.delete('/:${moduleName}Id', ${moduleName}Middleware, guard([ActionsType.DELETE]), controller.delete.bind(controller));
345
+ router.delete('/:${moduleName}Id', ${moduleName}Middleware, guard([TypeAction.DELETE]), controller.delete.bind(controller));
383
346
 
384
347
  /**
385
348
  * @openapi
@@ -422,7 +385,7 @@ router.delete('/:${moduleName}Id', ${moduleName}Middleware, guard([ActionsType.D
422
385
  * schema:
423
386
  * $ref: '#/components/schemas/List${ModuleClassName}ResponseDto'
424
387
  */
425
- router.get('/', ${moduleName}Middleware, guard([ActionsType.READ_ALL]), controller.list.bind(controller));
388
+ router.get('/', ${moduleName}Middleware, guard([TypeAction.READ_ALL]), controller.list.bind(controller));
426
389
 
427
390
  /**
428
391
  * ======================================================
@@ -439,9 +402,8 @@ router.get('/health/check', (_req, res) => {
439
402
 
440
403
  export default router;
441
404
  `;
442
- fs.writeFileSync(path.join(basePath, `${fileName}.routes.ts`), routesContent);
443
- const dtoContent = `import { ApiResponse, PaginatedData } from "../common/common.dto";
444
-
405
+ fs.writeFileSync(path.join(basePath, `${fileName}.routes.ts`), routesContent);
406
+ const dtoContent = `
445
407
  /**
446
408
  * Main ${ModuleClassName} entity
447
409
  *
@@ -614,10 +576,114 @@ export interface Get${ModuleClassName}ResponseDto
614
576
  */
615
577
  export interface List${ModuleClassName}ResponseDto
616
578
  extends ApiResponse<PaginatedData<Partial<${ModuleClassName}>>> {}
579
+
580
+ /**
581
+ * @openapi
582
+ * components:
583
+ * schemas:
584
+ * PaginationRequestDto:
585
+ * type: object
586
+ * properties:
587
+ * offset:
588
+ * type: integer
589
+ * example: 0
590
+ * minimum: 0
591
+ * description: Number of records to skip
592
+ * limit:
593
+ * type: integer
594
+ * example: 10
595
+ * minimum: 1
596
+ * maximum: 100
597
+ * description: Number of records to return
598
+ * search:
599
+ * type: string
600
+ * example: laptop
601
+ * description: Search keyword (optional)
602
+ * sortBy:
603
+ * type: string
604
+ * example: createdAt
605
+ * description: Field to sort by
606
+ * sortOrder:
607
+ * type: string
608
+ * enum: [asc, desc]
609
+ * example: desc
610
+ * description: Sorting order
611
+ */
612
+ export interface PaginationRequestDto {
613
+ offset?: number;
614
+ limit?: number;
615
+ search?: string;
616
+ sortBy?: string;
617
+ sortOrder?: "asc" | "desc";
618
+ }
619
+
620
+ /**
621
+ * @openapi
622
+ * components:
623
+ * schemas:
624
+ * ApiResponse:
625
+ * type: object
626
+ * properties:
627
+ * success:
628
+ * type: boolean
629
+ * example: true
630
+ * description: Operation success status
631
+ * status:
632
+ * type: integer
633
+ * example: 200
634
+ * description: HTTP status code
635
+ * message:
636
+ * type: string
637
+ * example: Request successful
638
+ * description: Response message
639
+ * data:
640
+ * type: object
641
+ * nullable: true
642
+ * description: Response data (optional, can be any type)
643
+ */
644
+ export interface ApiResponse<T = any> {
645
+ success: boolean;
646
+ status: number;
647
+ message: string;
648
+ data?: T;
649
+ }
650
+
651
+ /**
652
+ * @openapi
653
+ * components:
654
+ * schemas:
655
+ * PaginatedData:
656
+ * type: object
657
+ * properties:
658
+ * items:
659
+ * type: array
660
+ * description: List of paginated items
661
+ * items:
662
+ * type: object
663
+ * total:
664
+ * type: integer
665
+ * example: 120
666
+ * description: Total number of records
667
+ * offset:
668
+ * type: integer
669
+ * example: 0
670
+ * description: Starting index of the current page
671
+ * limit:
672
+ * type: integer
673
+ * example: 10
674
+ * description: Number of items per page
675
+ */
676
+ export interface PaginatedData<T> {
677
+ items: T[];
678
+ total: number;
679
+ offset: number;
680
+ limit: number;
681
+ }
682
+
617
683
  `;
618
- fs.writeFileSync(path.join(basePath, `${fileName}.dto.ts`), dtoContent);
619
- // Middleware
620
- const middlewareContent = `
684
+ fs.writeFileSync(path.join(basePath, `${fileName}.dto.ts`), dtoContent);
685
+ // Middleware
686
+ const middlewareContent = `
621
687
  import { Request, Response, NextFunction } from "express";
622
688
  import { cacheService } from "../../utils/redis";
623
689
  import { CachedUserProfilePermissionDto } from "../../utils/redis.dto";
@@ -668,10 +734,6 @@ export async function ${moduleName}Middleware(req: Request, res: Response, next:
668
734
  }
669
735
  }
670
736
  `;
671
- fs.writeFileSync(path.join(basePath, `${fileName}.middlewares.ts`), middlewareContent);
672
- const routerIndexPath = path.join(projectRoot, "src", "routes.ts");
673
- if (fs.existsSync(routerIndexPath)) {
674
- fs.appendFileSync(routerIndexPath, `\nimport ${moduleName}Routes from "./modules/${fileName}/${fileName}.routes";`);
675
- fs.appendFileSync(routerIndexPath, `\nrouter.use("/${moduleName}", ${moduleName}Routes);\n`);
737
+ fs.writeFileSync(path.join(basePath, `${fileName}.middlewares.ts`), middlewareContent);
738
+ console.log(`Module '${moduleName}' generated successfully at ${basePath}`);
676
739
  }
677
- console.log(`Module '${moduleName}' generated successfully at ${basePath}`);
@@ -1,13 +1,6 @@
1
- // scripts/generate-root-files.ts
2
1
  import * as fs from "fs";
3
2
  import * as path from "path";
4
- import * as readline from "readline";
5
- const rl = readline.createInterface({
6
- input: process.stdin,
7
- output: process.stdout,
8
- });
9
- rl.question("Enter project name: ", (projectName) => {
10
- /* ================= tsconfig.json ================= */
3
+ export function generateRootFiles(projectDir, projectName) {
11
4
  const tsconfig = {
12
5
  compilerOptions: {
13
6
  module: "nodenext",
@@ -35,89 +28,232 @@ rl.question("Enter project name: ", (projectName) => {
35
28
  },
36
29
  include: ["src", "server.ts", "prisma.config.ts"],
37
30
  exclude: ["node_modules"],
38
- assets: ["src/locales"],
39
31
  };
40
- fs.writeFileSync(path.join(process.cwd(), "tsconfig.json"), JSON.stringify(tsconfig, null, 2));
41
- /* ================= package.json ================= */
32
+ fs.writeFileSync(path.join(projectDir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2));
42
33
  const packageJson = {
43
34
  name: projectName.toLowerCase().replace(/\s+/g, "-"),
44
- version: "1.0.0",
45
- description: `${projectName} backend.`,
46
- license: "ISC",
47
- author: "abir-hosen",
48
- main: "server.ts",
49
- scripts: {
50
- "copy-locales": 'cpx "src/locales/**/*" dist/src/locales',
51
- dev: "nodemon",
52
- build: "tsc && npm run copy-locales",
53
- start: "node dist/server.js",
54
- "generate-module": "ts-node scripts/generate-module.ts",
55
- "generate-middlewares": "ts-node scripts/middleware.ts",
56
- "generate-locales": "ts-node scripts/generate-locales.ts",
57
- "generate-utils": "ts-node scripts/generate-utils.ts"
35
+ "version": "1.0.0",
36
+ "description": "Cryptdox generated expressJS backend.",
37
+ "keywords": [
38
+ "ts"
39
+ ],
40
+ "homepage": "https://github.com/..#readme",
41
+ "bugs": {
42
+ "url": "https://github.com/../issues"
43
+ },
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "git+https://github.com/...git"
47
+ },
48
+ "license": "ISC",
49
+ "author": "abir-hosen",
50
+ "main": "server.ts",
51
+ "scripts": {
52
+ "copy-locales": "cpx \"src/locales/**/*\" dist/src/locales",
53
+ "dev": "nodemon --watch 'src/**/*.ts' --exec 'ts-node' server.ts",
54
+ "build": "tsc && npm run copy-locales",
55
+ "start": "node dist/server.js",
56
+ "test": "echo \"Error: no test specified\" && exit 1",
57
+ "generate-module": "ts-node scripts/generate-module.ts"
58
58
  },
59
+ "dependencies": {
60
+ "@prisma/adapter-pg": "^7.3.0",
61
+ "@prisma/client": "^7.3.0",
62
+ "acorn": "^8.15.0",
63
+ "acorn-walk": "^8.3.4",
64
+ "arg": "^4.1.3",
65
+ "bcrypt": "^6.0.0",
66
+ "cors": "^2.8.6",
67
+ "create-require": "^1.1.1",
68
+ "diff": "^4.0.4",
69
+ "dotenv": "^17.2.3",
70
+ "express": "^5.2.1",
71
+ "i18next": "^25.8.4",
72
+ "i18next-fs-backend": "^2.6.1",
73
+ "i18next-http-middleware": "^3.9.2",
74
+ "jsonwebtoken": "^9.0.3",
75
+ "make-error": "^1.3.6",
76
+ "morgan": "^1.10.1",
77
+ "pg": "^8.18.0",
78
+ "redis": "^5.11.0",
79
+ "swagger-jsdoc": "^6.2.8",
80
+ "swagger-ui-express": "^5.0.1",
81
+ "undici-types": "^7.16.0",
82
+ "v8-compile-cache-lib": "^3.0.1",
83
+ "winston": "^3.19.0",
84
+ "yn": "^3.1.1",
85
+ "zod": "^4.3.6"
86
+ },
87
+ "devDependencies": {
88
+ "@types/bcrypt": "^6.0.0",
89
+ "@types/cors": "^2.8.19",
90
+ "@types/express": "^5.0.6",
91
+ "@types/i18next": "^12.1.0",
92
+ "@types/jsonwebtoken": "^9.0.10",
93
+ "@types/morgan": "^1.9.10",
94
+ "@types/node": "^25.1.0",
95
+ "@types/swagger-jsdoc": "^6.0.4",
96
+ "@types/swagger-ui-express": "^4.1.8",
97
+ "cpx": "^1.5.0",
98
+ "nodemon": "^3.1.11",
99
+ "prisma": "^7.3.0",
100
+ "ts-node": "^10.9.2",
101
+ "typescript": "^5.9.3"
102
+ }
59
103
  };
60
- fs.writeFileSync(path.join(process.cwd(), "package.json"), JSON.stringify(packageJson, null, 2));
61
- /* ================= nodemon.json ================= */
104
+ fs.writeFileSync(path.join(projectDir, "package.json"), JSON.stringify(packageJson, null, 2));
62
105
  const nodemon = {
63
106
  watch: ["src"],
64
107
  ext: "ts",
65
108
  ignore: ["dist"],
66
- exec: "ts-node src/server.ts",
109
+ exec: "ts-node server.ts",
67
110
  };
68
- fs.writeFileSync(path.join(process.cwd(), "nodemon.json"), JSON.stringify(nodemon, null, 2));
69
- /* ================= .gitignore ================= */
111
+ fs.writeFileSync(path.join(projectDir, "nodemon.json"), JSON.stringify(nodemon, null, 2));
70
112
  const gitignore = `
71
113
  node_modules
72
- /src/generated/prisma
73
114
  .env
74
- package-lock.json
75
115
  dist
116
+ package-lock.json
76
117
  pnpm-lock.yaml
77
- /generated/prisma
78
- `;
79
- fs.writeFileSync(path.join(process.cwd(), ".gitignore"), gitignore.trim());
80
- /* ================= .prisma ================= */
81
- const prisma = `
118
+ `;
119
+ fs.writeFileSync(path.join(projectDir, ".gitignore"), gitignore.trim());
120
+ generatePrisma(projectDir);
121
+ generatePrismaConfig(projectDir);
122
+ generateEnvironmentAndDocker(projectDir);
123
+ console.log(`✅ root config files generated for project: ${projectName}`);
124
+ }
125
+ /* ================= PRISMA ================= */
126
+ function generatePrisma(projectDir) {
127
+ const prismaDir = path.join(projectDir, "prisma");
128
+ if (!fs.existsSync(prismaDir))
129
+ fs.mkdirSync(prismaDir, { recursive: true });
130
+ const schema = `
82
131
  // This is your Prisma schema file,
83
132
  // learn more about it in the docs: https://pris.ly/d/prisma-schema
84
133
 
85
134
  // Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
86
135
  // Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
87
136
 
88
- generator client {
89
- provider = "prisma-client-js"
90
- }
91
-
92
- datasource db {
93
- provider = "postgresql"
94
- }
95
-
96
- model Common {
97
- commonId String @id @default(uuid())
98
- name String @unique
99
- }
137
+ generator client {
138
+ provider = "prisma-client-js"
139
+ }
140
+
141
+ datasource db {
142
+ provider = "postgresql"
143
+ }
144
+
145
+ model Common {
146
+ commonId String @id @default(uuid())
147
+ name String @unique
148
+ }
100
149
  `;
101
- fs.writeFileSync(path.join(process.cwd(), "prisma", "schema.prisma"), prisma.trim());
102
- /* ================= .prisma ================= */
103
- const prismaConfig = `
150
+ fs.writeFileSync(path.join(prismaDir, "schema.prisma"), schema.trim());
151
+ }
152
+ /* ================= PRISMA CONFIG ================= */
153
+ function generatePrismaConfig(projectDir) {
154
+ const config = `
104
155
  // This file was generated by Prisma, and assumes you have installed the following:
105
156
  // npm install --save-dev prisma dotenv
157
+
106
158
  import "dotenv/config";
107
159
  import { defineConfig } from "prisma/config";
108
160
 
109
161
  export default defineConfig({
110
162
  schema: "prisma/schema.prisma",
111
- migrations: {
112
- path: "prisma/migrations",
113
- },
114
- datasource: {
115
- url: process.env["DATABASE_URL"],
116
- },
163
+ migrations: { path: "prisma/migrations" },
164
+ datasource: { url: process.env["DATABASE_URL"] },
117
165
  });
166
+ `;
167
+ fs.writeFileSync(path.join(projectDir, "prisma.config.ts"), config.trim());
168
+ }
169
+ /* ================= ENV & DOCKER CONFIG ================= */
170
+ function generateEnvironmentAndDocker(projectDir) {
171
+ /* ================= ENV FILES ================= */
172
+ const baseEnv = `
173
+ PORT=3000
174
+ DATABASE_URL=postgresql://postgres:postgres@localhost:5432/app_db
175
+ REDIS_URL=redis://localhost:6379
176
+ `.trim();
177
+ const envDev = `
178
+ PORT=3000
179
+ DATABASE_URL=postgresql://postgres:postgres@postgres:5432/app_db
180
+ REDIS_URL=redis://redis:6379
181
+ `.trim();
182
+ const envProd = `
183
+ PORT=3000
184
+ DATABASE_URL=postgresql://postgres:strongpassword@postgres:5432/app_db
185
+ REDIS_URL=redis://redis:6379
186
+ `.trim();
187
+ const exampleEnv = `
188
+ PORT=
189
+ DATABASE_URL=
190
+ REDIS_URL=
191
+ `.trim();
192
+ fs.writeFileSync(path.join(projectDir, ".env"), baseEnv);
193
+ fs.writeFileSync(path.join(projectDir, ".env.local"), baseEnv);
194
+ fs.writeFileSync(path.join(projectDir, ".env.dev"), envDev);
195
+ fs.writeFileSync(path.join(projectDir, ".env.prod"), envProd);
196
+ fs.writeFileSync(path.join(projectDir, "example.env"), exampleEnv);
197
+ /* ================= DOCKERFILE ================= */
198
+ const dockerfile = `
199
+ FROM node:20-alpine
118
200
 
119
- `;
120
- fs.writeFileSync(path.join(process.cwd(), "prisma.config.ts"), prismaConfig.trim());
121
- console.log(`✅ Root config files generated for project: ${projectName}`);
122
- rl.close();
123
- });
201
+ WORKDIR /app
202
+
203
+ COPY package.json pnpm-lock.yaml* ./
204
+
205
+ RUN npm install -g pnpm
206
+ RUN pnpm install
207
+
208
+ COPY . .
209
+
210
+ RUN pnpm build
211
+
212
+ EXPOSE 3000
213
+
214
+ CMD ["node", "dist/server.js"]
215
+ `.trim();
216
+ fs.writeFileSync(path.join(projectDir, "Dockerfile"), dockerfile);
217
+ /* ================= DOCKER COMPOSE ================= */
218
+ const dockerCompose = `
219
+ version: "3.9"
220
+
221
+ services:
222
+ app:
223
+ build: .
224
+ container_name: express_app
225
+ ports:
226
+ - "3000:3000"
227
+ env_file:
228
+ - .env.dev
229
+ depends_on:
230
+ - postgres
231
+ - redis
232
+ restart: always
233
+
234
+ postgres:
235
+ image: postgres:16
236
+ container_name: postgres_db
237
+ environment:
238
+ POSTGRES_USER: postgres
239
+ POSTGRES_PASSWORD: postgres
240
+ POSTGRES_DB: app_db
241
+ ports:
242
+ - "5432:5432"
243
+ volumes:
244
+ - pgdata:/var/lib/postgresql/data
245
+ restart: always
246
+
247
+ redis:
248
+ image: redis:7
249
+ container_name: redis_cache
250
+ ports:
251
+ - "6379:6379"
252
+ restart: always
253
+
254
+ volumes:
255
+ pgdata:
256
+ `.trim();
257
+ fs.writeFileSync(path.join(projectDir, "docker-compose.yml"), dockerCompose);
258
+ console.log("✅ ENV and Docker files generated");
259
+ }