crypt-express-app 1.0.0 β 1.3.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/README.md +321 -1
- package/{scripts/generate-module.ts β dist/generate-module.js} +24 -81
- package/dist/index.js +122 -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
package/README.md
CHANGED
|
@@ -1 +1,321 @@
|
|
|
1
|
-
# crypt-express-app
|
|
1
|
+
# crypt-express-app
|
|
2
|
+
|
|
3
|
+
π A production-ready Express + TypeScript backend starter CLI with Prisma, Redis, i18n, Swagger, Docker, and scalable modular architecture.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## β¨ What is crypt-express-app?
|
|
8
|
+
|
|
9
|
+
`crypt-express-app` is a CLI tool that instantly scaffolds a fully structured, enterprise-grade Express backend application.
|
|
10
|
+
|
|
11
|
+
It generates:
|
|
12
|
+
|
|
13
|
+
- Express + TypeScript setup
|
|
14
|
+
- Prisma ORM configuration
|
|
15
|
+
- PostgreSQL + Redis Docker setup
|
|
16
|
+
- Swagger documentation
|
|
17
|
+
- i18next internationalization
|
|
18
|
+
- Modular folder architecture
|
|
19
|
+
- Logger + Error handling middleware
|
|
20
|
+
- Environment configuration
|
|
21
|
+
- Ready-to-run Docker Compose stack
|
|
22
|
+
|
|
23
|
+
This tool is built for developers who want a clean, scalable backend architecture in seconds.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
# π¦ Installation
|
|
28
|
+
|
|
29
|
+
You donβt install it globally.
|
|
30
|
+
|
|
31
|
+
Use directly with npx:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npx crypt-express-app my-project
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Or inside current directory:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npx crypt-express-app .
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
# π§ What It Generates
|
|
46
|
+
|
|
47
|
+
After running the CLI, your project will include:
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
my-project/
|
|
51
|
+
β
|
|
52
|
+
βββ src/
|
|
53
|
+
β βββ app.ts
|
|
54
|
+
β βββ routes.ts
|
|
55
|
+
β βββ prisma/
|
|
56
|
+
β β βββ client.ts
|
|
57
|
+
β βββ utils/
|
|
58
|
+
β βββ middlewares/
|
|
59
|
+
β βββ locales/
|
|
60
|
+
β βββ modules/
|
|
61
|
+
β
|
|
62
|
+
βββ prisma/
|
|
63
|
+
β βββ schema.prisma
|
|
64
|
+
βββ server.ts
|
|
65
|
+
βββ .env
|
|
66
|
+
βββ .env.dev
|
|
67
|
+
βββ .env.prod
|
|
68
|
+
βββ example.env
|
|
69
|
+
βββ Dockerfile
|
|
70
|
+
βββ docker-compose.yml
|
|
71
|
+
βββ tsconfig.json
|
|
72
|
+
βββ package.json
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
# π₯ Features
|
|
78
|
+
|
|
79
|
+
## β
Express + TypeScript
|
|
80
|
+
- Clean app architecture
|
|
81
|
+
- Modular routing system
|
|
82
|
+
- Centralized error handling
|
|
83
|
+
- Logger middleware
|
|
84
|
+
|
|
85
|
+
## β
Prisma ORM
|
|
86
|
+
- PostgreSQL configuration
|
|
87
|
+
- Auto Prisma client setup
|
|
88
|
+
- prisma.config.ts support
|
|
89
|
+
|
|
90
|
+
## β
Redis Cache
|
|
91
|
+
- Redis service container
|
|
92
|
+
- Cache service wrapper
|
|
93
|
+
- TTL-based caching support
|
|
94
|
+
|
|
95
|
+
## β
Internationalization (i18n)
|
|
96
|
+
- English + Bengali support
|
|
97
|
+
- Language detection
|
|
98
|
+
- JSON-based translations
|
|
99
|
+
|
|
100
|
+
## β
Swagger API Docs
|
|
101
|
+
- OpenAPI 3.0 setup
|
|
102
|
+
- Bearer auth support
|
|
103
|
+
- Persist authorization enabled
|
|
104
|
+
|
|
105
|
+
Access at:
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
http://localhost:3000/api/docs
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## β
Docker Ready
|
|
112
|
+
- Node service container
|
|
113
|
+
- PostgreSQL container
|
|
114
|
+
- Redis container
|
|
115
|
+
- Volume persistence
|
|
116
|
+
- Production-ready configuration
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
# π Quick Start
|
|
121
|
+
|
|
122
|
+
After project generation:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
cd my-project
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Start infrastructure:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
docker compose up -d postgres redis
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Generate Prisma client:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
npx prisma generate
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Run development server:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
pnpm dev
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Or build and run:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
pnpm build
|
|
150
|
+
pnpm start
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
# π Environment Variables
|
|
156
|
+
|
|
157
|
+
Default `.env`:
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
PORT=3000
|
|
161
|
+
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/app_db
|
|
162
|
+
REDIS_URL=redis://localhost:6379
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
For Docker (`.env.dev`):
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
DATABASE_URL=postgresql://postgres:postgres@postgres:5432/app_db
|
|
169
|
+
REDIS_URL=redis://redis:6379
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
# π³ Docker Usage
|
|
175
|
+
|
|
176
|
+
Start everything:
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
docker compose up --build
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Only database + redis:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
docker compose up -d postgres redis
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Stop:
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
docker compose down
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
# π§© Architecture Philosophy
|
|
197
|
+
|
|
198
|
+
crypt-express-app follows a clean separation:
|
|
199
|
+
|
|
200
|
+
- `app.ts` β Express configuration
|
|
201
|
+
- `routes.ts` β Route registration
|
|
202
|
+
- `modules/` β Feature modules
|
|
203
|
+
- `utils/` β Shared services
|
|
204
|
+
- `middlewares/` β Cross-cutting concerns
|
|
205
|
+
- `prisma/` β Database client layer
|
|
206
|
+
|
|
207
|
+
This makes scaling large backend systems easier.
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
# π CLI Behavior
|
|
212
|
+
|
|
213
|
+
If project name contains spaces:
|
|
214
|
+
|
|
215
|
+
```
|
|
216
|
+
"My App Project"
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
It auto converts to:
|
|
220
|
+
|
|
221
|
+
```
|
|
222
|
+
my-app-project
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
# π Scripts
|
|
228
|
+
|
|
229
|
+
Generated project includes:
|
|
230
|
+
|
|
231
|
+
```json
|
|
232
|
+
"dev": "nodemon",
|
|
233
|
+
"build": "tsc && npm run copy-locales",
|
|
234
|
+
"start": "node dist/server.js"
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
# π Requirements
|
|
240
|
+
|
|
241
|
+
- Node.js 18+
|
|
242
|
+
- pnpm or npm
|
|
243
|
+
- Docker (optional but recommended)
|
|
244
|
+
- PostgreSQL
|
|
245
|
+
- Redis
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
# π Production Ready
|
|
250
|
+
|
|
251
|
+
This starter includes:
|
|
252
|
+
|
|
253
|
+
- Structured logging
|
|
254
|
+
- Error middleware
|
|
255
|
+
- Swagger security
|
|
256
|
+
- Environment separation
|
|
257
|
+
- Docker orchestration
|
|
258
|
+
- Prisma adapter configuration
|
|
259
|
+
|
|
260
|
+
You can safely use this as a base for:
|
|
261
|
+
|
|
262
|
+
- SaaS backend
|
|
263
|
+
- Microservices
|
|
264
|
+
- Enterprise REST APIs
|
|
265
|
+
- Auth servers
|
|
266
|
+
- Admin panels
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
# π€ Contributing
|
|
271
|
+
|
|
272
|
+
Pull requests are welcome.
|
|
273
|
+
|
|
274
|
+
1. Fork repository
|
|
275
|
+
2. Create feature branch
|
|
276
|
+
3. Submit PR
|
|
277
|
+
|
|
278
|
+
---
|
|
279
|
+
|
|
280
|
+
# π License
|
|
281
|
+
|
|
282
|
+
ISC
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
# π¨βπ» Author
|
|
287
|
+
|
|
288
|
+
Abir Hosen
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
# β Why crypt-express-app?
|
|
293
|
+
|
|
294
|
+
Because setting up backend boilerplate repeatedly is painful.
|
|
295
|
+
|
|
296
|
+
crypt-express-app removes:
|
|
297
|
+
|
|
298
|
+
- Folder setup time
|
|
299
|
+
- Docker setup time
|
|
300
|
+
- Prisma configuration time
|
|
301
|
+
- Redis setup time
|
|
302
|
+
- Swagger configuration time
|
|
303
|
+
- i18n wiring time
|
|
304
|
+
|
|
305
|
+
Start coding business logic immediately.
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
# π Future Roadmap
|
|
310
|
+
|
|
311
|
+
- Auth module generator
|
|
312
|
+
- Role-based access control
|
|
313
|
+
- GraphQL support
|
|
314
|
+
- Microservice mode
|
|
315
|
+
- CLI flags support
|
|
316
|
+
- Health check system
|
|
317
|
+
- CI/CD template
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
Enjoy building scalable backends π
|
|
@@ -1,75 +1,43 @@
|
|
|
1
1
|
// generate-module.ts
|
|
2
2
|
import * as fs from 'fs';
|
|
3
3
|
import * as path from 'path';
|
|
4
|
-
|
|
5
4
|
// get module name from command line
|
|
6
5
|
const args = process.argv.slice(2);
|
|
7
|
-
|
|
8
6
|
// Check that exactly **one argument** is provided
|
|
9
7
|
if (args.length !== 1) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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);
|
|
13
11
|
}
|
|
14
|
-
|
|
15
12
|
const moduleInput = args[0];
|
|
16
|
-
|
|
17
13
|
// Reject if input contains spaces
|
|
18
14
|
if (/\s/.test(moduleInput)) {
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
console.error('Invalid module name: spaces are not allowed. Use camelCase, kebab-case or snake_case.');
|
|
16
|
+
process.exit(1);
|
|
21
17
|
}
|
|
22
|
-
|
|
23
18
|
if (!moduleInput) {
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
console.error('Please provide a module name: npm run generate-module <module-name>');
|
|
20
|
+
process.exit(1);
|
|
26
21
|
}
|
|
27
|
-
|
|
28
22
|
// Convert any input to camelCase
|
|
29
23
|
const moduleName = moduleInput.includes('-') || moduleInput.includes('_') || moduleInput.includes(' ')
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
console.log("moduleName: ", moduleName)
|
|
36
|
-
|
|
24
|
+
? moduleInput
|
|
25
|
+
.toLowerCase()
|
|
26
|
+
.replace(/[-_\s]+(.)/g, (_, c) => c.toUpperCase())
|
|
27
|
+
: moduleInput; // preserve existing camelCase
|
|
28
|
+
console.log("moduleName: ", moduleName);
|
|
37
29
|
// Convert any input (camelCase, PascalCase, snake_case, spaces) to kebab-case
|
|
38
30
|
const fileName = moduleName
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
// const basePath = path.join(__dirname, '..', 'src', 'modules', fileName);
|
|
46
|
-
// const basePath = path.join(__dirname, '..', 'src', 'modules', fileName);
|
|
47
|
-
const projectRoot = process.cwd();
|
|
48
|
-
const modulesRoot = path.join(projectRoot, "src", "modules");
|
|
49
|
-
const basePath = path.join(modulesRoot, fileName);
|
|
50
|
-
|
|
51
|
-
// ensure modules directory exists
|
|
52
|
-
if (!fs.existsSync(modulesRoot)) {
|
|
53
|
-
fs.mkdirSync(modulesRoot, { recursive: true });
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// prevent overwrite
|
|
57
|
-
if (fs.existsSync(basePath)) {
|
|
58
|
-
console.error(`β Module '${fileName}' already exists.`);
|
|
59
|
-
process.exit(1);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
fs.mkdirSync(basePath, { recursive: true });
|
|
63
|
-
|
|
64
|
-
|
|
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);
|
|
65
35
|
// Create folder
|
|
66
36
|
if (!fs.existsSync(basePath)) {
|
|
67
|
-
|
|
37
|
+
fs.mkdirSync(basePath, { recursive: true });
|
|
68
38
|
}
|
|
69
|
-
|
|
70
39
|
// Capitalize first letter
|
|
71
40
|
const ModuleClassName = moduleName.charAt(0).toUpperCase() + moduleName.slice(1);
|
|
72
|
-
|
|
73
41
|
// Service
|
|
74
42
|
const serviceContent = `import { Create${ModuleClassName}Dto, Update${ModuleClassName}Dto, Create${ModuleClassName}Response, Update${ModuleClassName}Response, Get${ModuleClassName}Response, List${ModuleClassName}Response } from "./${fileName}.dto";
|
|
75
43
|
import { Prisma } from "@prisma/client";
|
|
@@ -158,9 +126,7 @@ export class ${ModuleClassName}Service {
|
|
|
158
126
|
|
|
159
127
|
export default new ${ModuleClassName}Service()
|
|
160
128
|
`;
|
|
161
|
-
|
|
162
129
|
fs.writeFileSync(path.join(basePath, `${fileName}.service.ts`), serviceContent);
|
|
163
|
-
|
|
164
130
|
// Controller
|
|
165
131
|
const controllerContent = `
|
|
166
132
|
import { Request, Response } from 'express';
|
|
@@ -277,9 +243,7 @@ export class ${ModuleClassName}Controller {
|
|
|
277
243
|
}
|
|
278
244
|
}
|
|
279
245
|
`;
|
|
280
|
-
|
|
281
246
|
fs.writeFileSync(path.join(basePath, `${fileName}.controller.ts`), controllerContent);
|
|
282
|
-
|
|
283
247
|
// Routes
|
|
284
248
|
const routesContent = `
|
|
285
249
|
import express, { Router } from 'express';
|
|
@@ -287,13 +251,13 @@ import { ${ModuleClassName}Controller } from './${fileName}.controller';
|
|
|
287
251
|
import { ${moduleName}Middleware } from './${fileName}.middlewares';
|
|
288
252
|
import { authMiddleware } from '../../middlewares/auth.middleware';
|
|
289
253
|
import ${moduleName}Service from './${fileName}.service';
|
|
290
|
-
import {
|
|
254
|
+
import { TypeAction, Resource, TypeResource } from '../../utils/consts';
|
|
291
255
|
import { authorizationMiddleware } from '../../middlewares/authorization.middleware';
|
|
292
256
|
|
|
293
257
|
const router: Router = express.Router();
|
|
294
258
|
const controller = new ${ModuleClassName}Controller(${moduleName}Service);
|
|
295
259
|
router.use(authMiddleware);
|
|
296
|
-
const guard = (actions:
|
|
260
|
+
const guard = (actions: TypeAction[]) => authorizationMiddleware({ resource: Resource.COMMON, actions, resourceType: TypeResource.API_ENDPOINT });
|
|
297
261
|
|
|
298
262
|
/**
|
|
299
263
|
* @openapi
|
|
@@ -322,7 +286,7 @@ const guard = (actions: ActionsType[]) => authorizationMiddleware({ resource: Re
|
|
|
322
286
|
* schema:
|
|
323
287
|
* $ref: '#/components/schemas/Create${ModuleClassName}ResponseDto'
|
|
324
288
|
*/
|
|
325
|
-
router.post('/', ${moduleName}Middleware, guard([
|
|
289
|
+
router.post('/', ${moduleName}Middleware, guard([TypeAction.CREATE]), controller.create.bind(controller));
|
|
326
290
|
|
|
327
291
|
/**
|
|
328
292
|
* @openapi
|
|
@@ -344,7 +308,7 @@ router.post('/', ${moduleName}Middleware, guard([ActionsType.CREATE]), controlle
|
|
|
344
308
|
* schema:
|
|
345
309
|
* $ref: '#/components/schemas/Get${ModuleClassName}ResponseDto'
|
|
346
310
|
*/
|
|
347
|
-
router.get('/:${moduleName}Id', ${moduleName}Middleware, guard([
|
|
311
|
+
router.get('/:${moduleName}Id', ${moduleName}Middleware, guard([TypeAction.READ]), controller.getById.bind(controller));
|
|
348
312
|
|
|
349
313
|
/**
|
|
350
314
|
* @openapi
|
|
@@ -372,7 +336,7 @@ router.get('/:${moduleName}Id', ${moduleName}Middleware, guard([ActionsType.READ
|
|
|
372
336
|
* schema:
|
|
373
337
|
* $ref: '#/components/schemas/Update${ModuleClassName}ResponseDto'
|
|
374
338
|
*/
|
|
375
|
-
router.put('/:${moduleName}Id', ${moduleName}Middleware, guard([
|
|
339
|
+
router.put('/:${moduleName}Id', ${moduleName}Middleware, guard([TypeAction.UPDATE]), controller.update.bind(controller));
|
|
376
340
|
|
|
377
341
|
/**
|
|
378
342
|
* @openapi
|
|
@@ -401,7 +365,7 @@ router.put('/:${moduleName}Id', ${moduleName}Middleware, guard([ActionsType.UPDA
|
|
|
401
365
|
* message:
|
|
402
366
|
* type: string
|
|
403
367
|
*/
|
|
404
|
-
router.delete('/:${moduleName}Id', ${moduleName}Middleware, guard([
|
|
368
|
+
router.delete('/:${moduleName}Id', ${moduleName}Middleware, guard([TypeAction.DELETE]), controller.delete.bind(controller));
|
|
405
369
|
|
|
406
370
|
/**
|
|
407
371
|
* @openapi
|
|
@@ -444,7 +408,7 @@ router.delete('/:${moduleName}Id', ${moduleName}Middleware, guard([ActionsType.D
|
|
|
444
408
|
* schema:
|
|
445
409
|
* $ref: '#/components/schemas/List${ModuleClassName}ResponseDto'
|
|
446
410
|
*/
|
|
447
|
-
router.get('/', ${moduleName}Middleware, guard([
|
|
411
|
+
router.get('/', ${moduleName}Middleware, guard([TypeAction.READ_ALL]), controller.list.bind(controller));
|
|
448
412
|
|
|
449
413
|
/**
|
|
450
414
|
* ======================================================
|
|
@@ -461,8 +425,6 @@ router.get('/health/check', (_req, res) => {
|
|
|
461
425
|
|
|
462
426
|
export default router;
|
|
463
427
|
`;
|
|
464
|
-
|
|
465
|
-
|
|
466
428
|
fs.writeFileSync(path.join(basePath, `${fileName}.routes.ts`), routesContent);
|
|
467
429
|
const dtoContent = `import { ApiResponse, PaginatedData } from "../common/common.dto";
|
|
468
430
|
|
|
@@ -639,10 +601,7 @@ export interface Get${ModuleClassName}ResponseDto
|
|
|
639
601
|
export interface List${ModuleClassName}ResponseDto
|
|
640
602
|
extends ApiResponse<PaginatedData<Partial<${ModuleClassName}>>> {}
|
|
641
603
|
`;
|
|
642
|
-
|
|
643
|
-
|
|
644
604
|
fs.writeFileSync(path.join(basePath, `${fileName}.dto.ts`), dtoContent);
|
|
645
|
-
|
|
646
605
|
// Middleware
|
|
647
606
|
const middlewareContent = `
|
|
648
607
|
import { Request, Response, NextFunction } from "express";
|
|
@@ -695,21 +654,5 @@ export async function ${moduleName}Middleware(req: Request, res: Response, next:
|
|
|
695
654
|
}
|
|
696
655
|
}
|
|
697
656
|
`;
|
|
698
|
-
|
|
699
657
|
fs.writeFileSync(path.join(basePath, `${fileName}.middlewares.ts`), middlewareContent);
|
|
700
|
-
|
|
701
|
-
const routerIndexPath = path.join(projectRoot, "src", "routes.ts");
|
|
702
|
-
|
|
703
|
-
if (fs.existsSync(routerIndexPath)) {
|
|
704
|
-
fs.appendFileSync(
|
|
705
|
-
routerIndexPath,
|
|
706
|
-
`\nimport ${moduleName}Routes from "./modules/${fileName}/${fileName}.routes";`
|
|
707
|
-
);
|
|
708
|
-
|
|
709
|
-
fs.appendFileSync(
|
|
710
|
-
routerIndexPath,
|
|
711
|
-
`\nrouter.use("/${moduleName}", ${moduleName}Routes);\n`
|
|
712
|
-
);
|
|
713
|
-
}
|
|
714
|
-
|
|
715
658
|
console.log(`Module '${moduleName}' generated successfully at ${basePath}`);
|
package/dist/index.js
CHANGED
|
@@ -1,20 +1,125 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { execSync } from "child_process";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
import * as fs from "fs";
|
|
4
|
+
import * as path from "path";
|
|
5
|
+
import * as readline from "readline";
|
|
6
|
+
import { generateApp } from "./scripts/generate-app.js";
|
|
7
|
+
import { generateLocales } from "./scripts/generate-locales.js";
|
|
8
|
+
import { generateMiddlewares } from "./scripts/generate-middleware.js";
|
|
9
|
+
import { generateRootFiles } from "./scripts/generate-root-files.js";
|
|
10
|
+
import { generateModule } from "./scripts/generate-module.js";
|
|
11
|
+
import { generateUtils } from "./scripts/generate-utils.js";
|
|
12
|
+
import { generatePrismaClient } from "./scripts/setup-prisma.js";
|
|
13
|
+
/* ===================== CLI SETUP ===================== */
|
|
14
|
+
const rl = readline.createInterface({
|
|
15
|
+
input: process.stdin,
|
|
16
|
+
output: process.stdout,
|
|
17
|
+
});
|
|
18
|
+
function ask(question) {
|
|
19
|
+
return new Promise((resolve) => rl.question(question, resolve));
|
|
8
20
|
}
|
|
9
|
-
|
|
10
|
-
function
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
/* ===================== MAIN ===================== */
|
|
22
|
+
(async function main() {
|
|
23
|
+
let projectName = process.argv[2];
|
|
24
|
+
if (process.argv.length > 3) {
|
|
25
|
+
console.error("β only one project name allowed");
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
if (!projectName) {
|
|
29
|
+
projectName = await ask("Enter project name: ");
|
|
30
|
+
}
|
|
31
|
+
projectName = projectName.trim();
|
|
32
|
+
if (!projectName) {
|
|
33
|
+
console.error("β Project name required");
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
// allow spaces β convert to kebab-case
|
|
37
|
+
const normalizedName = projectName
|
|
38
|
+
.toLowerCase()
|
|
39
|
+
.trim()
|
|
40
|
+
.replace(/\s+/g, "-") // space β dash
|
|
41
|
+
.replace(/[^a-z0-9-]/g, ""); // remove invalid chars (optional)
|
|
42
|
+
const useCurrentDir = projectName === ".";
|
|
43
|
+
const projectDir = useCurrentDir
|
|
44
|
+
? process.cwd()
|
|
45
|
+
: path.join(process.cwd(), projectName);
|
|
46
|
+
if (!useCurrentDir) {
|
|
47
|
+
if (/\s/.test(projectName)) {
|
|
48
|
+
console.error("β Name cannot contain spaces");
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
if (fs.existsSync(projectDir)) {
|
|
52
|
+
console.error("β Directory already exists");
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
fs.mkdirSync(projectDir, { recursive: true });
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
const files = fs.readdirSync(projectDir).filter((f) => !f.startsWith("."));
|
|
59
|
+
if (files.length > 0) {
|
|
60
|
+
console.error("β Current directory must be empty");
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
rl.close();
|
|
65
|
+
const finalProjectName = useCurrentDir ? path.basename(process.cwd())
|
|
66
|
+
.toLowerCase()
|
|
67
|
+
.trim()
|
|
68
|
+
.replace(/\s+/g, "-") // space β dash
|
|
69
|
+
.replace(/[^a-z0-9-]/g, "") // remove invalid chars (optional)
|
|
70
|
+
: normalizedName;
|
|
71
|
+
process.env.PROJECT_NAME = finalProjectName;
|
|
72
|
+
console.log(`π¦ Creating project: ${process.env.PROJECT_NAME}`);
|
|
73
|
+
generateRootFiles(projectDir, finalProjectName);
|
|
74
|
+
generateApp(projectDir);
|
|
75
|
+
generateMiddlewares(projectDir);
|
|
76
|
+
generateLocales(projectDir);
|
|
77
|
+
generateModule(projectDir, "common");
|
|
78
|
+
generateUtils(projectDir);
|
|
79
|
+
generatePrismaClient(projectDir);
|
|
80
|
+
console.log("π¦ Installing dependencies...");
|
|
81
|
+
try {
|
|
82
|
+
execSync("pnpm install", {
|
|
83
|
+
cwd: projectDir,
|
|
84
|
+
stdio: "inherit",
|
|
85
|
+
});
|
|
86
|
+
console.log("β
Dependencies installed");
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
console.error("β pnpm install failed");
|
|
90
|
+
console.error(err);
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
execSync("npx prisma generate", {
|
|
94
|
+
cwd: projectDir,
|
|
95
|
+
stdio: "inherit",
|
|
96
|
+
});
|
|
97
|
+
console.log("β
Prisma client generated");
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
console.error("β Prisma client generation failed");
|
|
101
|
+
console.error(err);
|
|
102
|
+
}
|
|
103
|
+
// try {
|
|
104
|
+
// execSync("docker compose up -d redis postgres", {
|
|
105
|
+
// cwd: projectDir,
|
|
106
|
+
// stdio: "inherit",
|
|
107
|
+
// });
|
|
108
|
+
// console.log("β
Redis and Postgres started");
|
|
109
|
+
// } catch (err) {
|
|
110
|
+
// console.error("β Failed to start Redis and Postgres");
|
|
111
|
+
// console.error(err);
|
|
112
|
+
// }
|
|
113
|
+
// try {
|
|
114
|
+
// execSync("npx prisma db push", {
|
|
115
|
+
// cwd: projectDir,
|
|
116
|
+
// stdio: "inherit",
|
|
117
|
+
// });
|
|
118
|
+
// console.log("β
Prisma client generated");
|
|
119
|
+
// } catch (err) {
|
|
120
|
+
// console.error("β Prisma client generation failed");
|
|
121
|
+
// console.error(err);
|
|
122
|
+
// }
|
|
123
|
+
console.log("β
Project generated");
|
|
124
|
+
})();
|
|
125
|
+
/* ===================== CONFIG GENERATION ===================== */
|