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.
- package/README.md +321 -1
- package/{scripts/generate-module.ts → dist/generate-module.js} +24 -81
- package/dist/index.js +118 -17
- package/dist/scripts/generate-app.js +73 -55
- package/dist/scripts/generate-locales.js +22 -26
- package/dist/scripts/generate-middleware.js +69 -67
- package/dist/scripts/generate-module.js +140 -78
- package/dist/scripts/generate-root-files.js +199 -63
- package/dist/scripts/generate-utils.js +811 -46
- package/dist/scripts/setup-prisma.js +14 -13
- package/package.json +10 -5
- package/index.ts +0 -29
- package/scripts/generate-app.ts +0 -104
- package/scripts/generate-locales.ts +0 -36
- package/scripts/generate-middleware.ts +0 -176
- package/scripts/generate-root-files.ts +0 -147
- package/scripts/generate-utils.ts +0 -208
- package/scripts/setup-prisma.ts +0 -26
- package/tsconfig.json +0 -10
|
@@ -1,59 +1,22 @@
|
|
|
1
1
|
// generate-module.ts
|
|
2
2
|
import * as fs from 'fs';
|
|
3
3
|
import * as path from 'path';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
console.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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 {
|
|
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:
|
|
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([
|
|
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([
|
|
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([
|
|
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([
|
|
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([
|
|
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 = `
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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:
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
|
|
57
|
-
|
|
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(
|
|
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
|
|
109
|
+
exec: "ts-node server.ts",
|
|
67
110
|
};
|
|
68
|
-
fs.writeFileSync(path.join(
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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(
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
+
}
|