exp-node-server 1.1.7 → 2.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/README.md +82 -20
- package/dist/cli.js +525 -0
- package/package.json +29 -9
- package/templates/mongoose/.dockerignore +10 -0
- package/templates/mongoose/.env.example +56 -7
- package/templates/mongoose/.prettierignore +3 -0
- package/templates/mongoose/.prettierrc +7 -0
- package/templates/mongoose/Dockerfile +33 -0
- package/templates/mongoose/_gitignore +32 -0
- package/templates/mongoose/docker-compose.yml +21 -0
- package/templates/mongoose/eslint.config.js +35 -0
- package/templates/mongoose/package-lock.json +6377 -0
- package/templates/mongoose/package.json +49 -10
- package/templates/mongoose/readme.md +85 -0
- package/templates/mongoose/src/app/configs/index.ts +178 -26
- package/templates/mongoose/src/app/errors/zodError.ts +16 -16
- package/templates/mongoose/src/app/lib/db.ts +18 -0
- package/templates/mongoose/src/app/lib/logger.ts +12 -0
- package/templates/mongoose/src/app/middlewares/auth.ts +48 -44
- package/templates/mongoose/src/app/middlewares/global_error_handler.ts +59 -65
- package/templates/mongoose/src/app/middlewares/not_found_api.ts +7 -7
- package/templates/mongoose/src/app/middlewares/rate_limiter.ts +153 -0
- package/templates/mongoose/src/app/middlewares/request_validator.ts +11 -10
- package/templates/mongoose/src/app/middlewares/uploader.ts +17 -13
- package/templates/mongoose/src/app/modules/account/account.controller.ts +156 -0
- package/templates/mongoose/src/app/modules/account/account.interface.ts +65 -0
- package/templates/mongoose/src/app/modules/account/account.route.ts +101 -0
- package/templates/mongoose/src/app/modules/account/account.schema.ts +97 -0
- package/templates/mongoose/src/app/modules/account/account.service.ts +422 -0
- package/templates/mongoose/src/app/modules/account/account.swagger.ts +278 -0
- package/templates/mongoose/src/app/modules/account/account.validation.ts +63 -0
- package/templates/mongoose/src/app/modules/profile/profile.controller.ts +17 -0
- package/templates/mongoose/src/app/modules/profile/profile.interface.ts +7 -0
- package/templates/mongoose/src/app/modules/profile/profile.route.ts +28 -0
- package/templates/mongoose/src/app/modules/profile/profile.schema.ts +13 -0
- package/templates/mongoose/src/app/modules/profile/profile.service.ts +28 -0
- package/templates/mongoose/src/app/modules/profile/profile.swagger.ts +34 -0
- package/templates/mongoose/src/app/modules/profile/profile.validation.ts +8 -0
- package/templates/mongoose/src/app/passport-js/index.ts +87 -0
- package/templates/mongoose/src/app/passport-js/oauth-state.ts +150 -0
- package/templates/mongoose/src/app/passport-js/providers.ts +258 -0
- package/templates/mongoose/src/app/passport-js/social-account.ts +141 -0
- package/templates/mongoose/src/app/queues/agenda.ts +24 -0
- package/templates/mongoose/src/app/queues/email.queue.ts +53 -0
- package/templates/mongoose/src/app/queues/index.ts +13 -0
- package/templates/mongoose/src/app/templates/otpEmailTemplate.ts +185 -0
- package/templates/mongoose/src/app/templates/resetPasswordEmailTemplate.ts +157 -0
- package/templates/mongoose/src/app/types/error.d.ts +7 -7
- package/templates/mongoose/src/app/types/express.d.ts +11 -0
- package/templates/mongoose/src/app/types/passport-oauth1.d.ts +51 -0
- package/templates/mongoose/src/app/utils/JWT.ts +38 -18
- package/templates/mongoose/src/app/utils/app_error.ts +10 -10
- package/templates/mongoose/src/app/utils/auth_cookie.ts +27 -0
- package/templates/mongoose/src/app/utils/catch_async.ts +8 -8
- package/templates/mongoose/src/app/utils/cloudinary.ts +26 -59
- package/templates/mongoose/src/app/utils/mail_sender.ts +26 -92
- package/templates/mongoose/src/app/utils/manage_response.ts +19 -19
- package/templates/mongoose/src/app/utils/otp.ts +20 -0
- package/templates/mongoose/src/app/utils/session.ts +55 -0
- package/templates/mongoose/src/app.ts +73 -42
- package/templates/mongoose/src/routes.ts +13 -13
- package/templates/mongoose/src/server.ts +38 -18
- package/templates/mongoose/src/swaggerOptions.ts +23 -23
- package/templates/mongoose/tests/app.test.ts +97 -0
- package/templates/mongoose/tests/global-setup.ts +15 -0
- package/templates/mongoose/tests/helpers/fixtures.ts +42 -0
- package/templates/mongoose/tests/routes/account.test.ts +614 -0
- package/templates/mongoose/tests/routes/profile.test.ts +95 -0
- package/templates/mongoose/tests/routes/social.test.ts +310 -0
- package/templates/mongoose/tests/setup.ts +25 -0
- package/templates/mongoose/tsconfig.json +16 -20
- package/templates/mongoose/vitest.config.ts +43 -0
- package/templates/prisma/.dockerignore +2 -0
- package/templates/prisma/.env.example +62 -6
- package/templates/prisma/.prettierignore +3 -0
- package/templates/prisma/.prettierrc +7 -0
- package/templates/prisma/.projectrc.json +1 -1
- package/templates/prisma/Dockerfile +21 -7
- package/templates/prisma/_gitignore +35 -0
- package/templates/prisma/docker-compose.yml +8 -7
- package/templates/prisma/eslint.config.js +35 -0
- package/templates/prisma/package-lock.json +7327 -0
- package/templates/prisma/package.json +47 -9
- package/templates/prisma/prisma/schema/account.schema.prisma +92 -10
- package/templates/prisma/prisma/schema/profile.schema.prisma +8 -3
- package/templates/prisma/prisma/schema/schema.prisma +1 -2
- package/templates/prisma/prisma.config.ts +5 -5
- package/templates/prisma/readme.md +81 -0
- package/templates/prisma/src/app/configs/index.ts +175 -26
- package/templates/prisma/src/app/errors/zodError.ts +16 -16
- package/templates/prisma/src/app/lib/logger.ts +12 -0
- package/templates/prisma/src/app/lib/prisma.ts +4 -6
- package/templates/prisma/src/app/middlewares/auth.ts +37 -16
- package/templates/prisma/src/app/middlewares/global_error_handler.ts +42 -35
- package/templates/prisma/src/app/middlewares/not_found_api.ts +7 -7
- package/templates/prisma/src/app/middlewares/rate_limiter.ts +124 -0
- package/templates/prisma/src/app/middlewares/request_validator.ts +4 -3
- package/templates/prisma/src/app/middlewares/uploader.ts +17 -13
- package/templates/prisma/src/app/modules/account/account.controller.ts +74 -34
- package/templates/prisma/src/app/modules/account/account.route.ts +68 -22
- package/templates/prisma/src/app/modules/account/account.service.ts +329 -309
- package/templates/prisma/src/app/modules/account/account.swagger.ts +167 -76
- package/templates/prisma/src/app/modules/account/account.validation.ts +41 -19
- package/templates/prisma/src/app/modules/profile/profile.controller.ts +4 -4
- package/templates/prisma/src/app/modules/profile/profile.route.ts +18 -12
- package/templates/prisma/src/app/modules/profile/profile.service.ts +11 -18
- package/templates/prisma/src/app/modules/profile/profile.swagger.ts +11 -11
- package/templates/prisma/src/app/modules/profile/profile.validation.ts +2 -2
- package/templates/prisma/src/app/passport-js/index.ts +87 -0
- package/templates/prisma/src/app/passport-js/oauth-state.ts +150 -0
- package/templates/prisma/src/app/passport-js/providers.ts +263 -0
- package/templates/prisma/src/app/passport-js/social-account.ts +147 -0
- package/templates/prisma/src/app/queues/boss.ts +8 -0
- package/templates/prisma/src/app/queues/email.queue.ts +56 -0
- package/templates/prisma/src/app/queues/index.ts +14 -0
- package/templates/prisma/src/app/queues/maintenance.queue.ts +30 -0
- package/templates/prisma/src/app/templates/otpEmailTemplate.ts +185 -0
- package/templates/prisma/src/app/templates/resetPasswordEmailTemplate.ts +157 -0
- package/templates/prisma/src/app/types/error.d.ts +7 -7
- package/templates/prisma/src/app/types/express.d.ts +11 -0
- package/templates/prisma/src/app/types/passport-oauth1.d.ts +51 -0
- package/templates/prisma/src/app/utils/JWT.ts +36 -17
- package/templates/prisma/src/app/utils/app_error.ts +10 -10
- package/templates/prisma/src/app/utils/auth_cookie.ts +27 -0
- package/templates/prisma/src/app/utils/catch_async.ts +8 -8
- package/templates/prisma/src/app/utils/cloudinary.ts +26 -59
- package/templates/prisma/src/app/utils/mail_sender.ts +26 -92
- package/templates/prisma/src/app/utils/manage_response.ts +19 -19
- package/templates/prisma/src/app/utils/otp.ts +20 -0
- package/templates/prisma/src/app/utils/session.ts +57 -0
- package/templates/prisma/src/app.ts +73 -42
- package/templates/prisma/src/routes.ts +12 -10
- package/templates/prisma/src/server.ts +37 -19
- package/templates/prisma/src/swaggerOptions.ts +22 -26
- package/templates/prisma/tests/app.test.ts +86 -0
- package/templates/prisma/tests/helpers/fixtures.ts +35 -0
- package/templates/prisma/tests/helpers/prisma.ts +32 -0
- package/templates/prisma/tests/routes/account.test.ts +627 -0
- package/templates/prisma/tests/routes/profile.test.ts +91 -0
- package/templates/prisma/tests/routes/social.test.ts +317 -0
- package/templates/prisma/tests/setup.ts +10 -0
- package/templates/prisma/tsconfig.json +16 -23
- package/templates/prisma/vitest.config.ts +38 -0
- package/bin/cli.js +0 -41
- package/src/createProject.js +0 -61
- package/src/generateModule.js +0 -93
- package/src/utils/getProjectType.js +0 -13
- package/src/utils/mongoose/controllerGenerator.js +0 -70
- package/src/utils/mongoose/routeGenerator.js +0 -27
- package/src/utils/mongoose/schemaGenerator.js +0 -11
- package/src/utils/mongoose/serviceGenerator.js +0 -49
- package/src/utils/mongoose/swaggerGenerator.js +0 -112
- package/src/utils/mongoose/validationGenerator.js +0 -13
- package/src/utils/prisma/prismaSchemaGenerator.js +0 -10
- package/src/utils/prisma/prismaServiceGenerator.js +0 -47
- package/src/utils/routeInjector.js +0 -40
- package/src/utils/swaggerInjector.js +0 -52
- package/src/utils/validateModuleName.js +0 -17
- package/src/utils/validateProjectName.js +0 -15
- package/src/utils.js +0 -26
- package/templates/mongoose/src/app/errors/castError.ts +0 -23
- package/templates/mongoose/src/app/errors/duplicateError.ts +0 -26
- package/templates/mongoose/src/app/errors/validationError.ts +0 -25
- package/templates/mongoose/src/app/modules/auth/auth.controller.ts +0 -127
- package/templates/mongoose/src/app/modules/auth/auth.interface.ts +0 -24
- package/templates/mongoose/src/app/modules/auth/auth.route.ts +0 -46
- package/templates/mongoose/src/app/modules/auth/auth.schema.ts +0 -19
- package/templates/mongoose/src/app/modules/auth/auth.service.ts +0 -324
- package/templates/mongoose/src/app/modules/auth/auth.swagger.ts +0 -214
- package/templates/mongoose/src/app/modules/auth/auth.validation.ts +0 -37
- package/templates/mongoose/src/app/modules/user/user.controller.ts +0 -17
- package/templates/mongoose/src/app/modules/user/user.interface.ts +0 -15
- package/templates/mongoose/src/app/modules/user/user.route.ts +0 -20
- package/templates/mongoose/src/app/modules/user/user.schema.ts +0 -22
- package/templates/mongoose/src/app/modules/user/user.service.ts +0 -22
- package/templates/mongoose/src/app/modules/user/user.swagger.ts +0 -75
- package/templates/mongoose/src/app/modules/user/user.validation.ts +0 -18
- package/templates/mongoose/src/app/types/index.d.ts +0 -12
- package/templates/mongoose/src/app/utils/isAccountExist.ts +0 -31
- package/templates/prisma/src/app/types/index.d.ts +0 -12
- package/templates/prisma/src/app/utils/otpGenerator.ts +0 -29
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
[](https://www.npmjs.com/package/exp-node-server)
|
|
5
5
|
[](https://www.npmjs.com/package/exp-node-server)
|
|
6
6
|
[](https://www.typescriptlang.org/)
|
|
7
|
-
[](https://nodejs.org/)
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
@@ -18,7 +18,6 @@ This tool helps developers quickly scaffold `clean`, `modular` `Express` APIs wi
|
|
|
18
18
|
|
|
19
19
|
- ⚡ Generate complete `Express` + `TypeScript` modules
|
|
20
20
|
- 🧩 Built-in `Mongoose` / `Prisma` support
|
|
21
|
-
- 📘 Adds Swagger documentation automatically
|
|
22
21
|
- 📘 Automatic `Swagger` documentation
|
|
23
22
|
- 🔐 `Zod` validation ready
|
|
24
23
|
- 🏗️ `Modular` clean architecture
|
|
@@ -30,19 +29,27 @@ This tool helps developers quickly scaffold `clean`, `modular` `Express` APIs wi
|
|
|
30
29
|
|
|
31
30
|
Run directly using npx (recommended):
|
|
32
31
|
|
|
33
|
-
```
|
|
32
|
+
```bash
|
|
34
33
|
npx exp-node-server -c my-api
|
|
35
34
|
```
|
|
36
35
|
|
|
37
|
-
|
|
36
|
+
You'll be asked to pick a template (Mongoose or Prisma). To skip the prompt:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npx exp-node-server create my-api --template prisma
|
|
40
|
+
```
|
|
38
41
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
+
Then:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
cd my-api
|
|
46
|
+
npm install
|
|
47
|
+
npm run dev
|
|
48
|
+
```
|
|
42
49
|
|
|
43
50
|
## 🧩 Generate a Module
|
|
44
51
|
|
|
45
|
-
Inside your project run:
|
|
52
|
+
Inside your project root run:
|
|
46
53
|
|
|
47
54
|
```bash
|
|
48
55
|
npx exp-node-server -g <module-name>
|
|
@@ -52,33 +59,71 @@ Example: `npx exp-node-server -g order`
|
|
|
52
59
|
|
|
53
60
|
Output:
|
|
54
61
|
|
|
55
|
-
```
|
|
56
|
-
✔ order.
|
|
57
|
-
✔ order.
|
|
58
|
-
✔ order.validation.ts created
|
|
59
|
-
✔ order.
|
|
60
|
-
✔ order.
|
|
61
|
-
✔ order.
|
|
62
|
-
✔ order.
|
|
62
|
+
```bash
|
|
63
|
+
✔ src/app/modules/order/order.controller.ts created
|
|
64
|
+
✔ src/app/modules/order/order.route.ts created
|
|
65
|
+
✔ src/app/modules/order/order.validation.ts created
|
|
66
|
+
✔ src/app/modules/order/order.swagger.ts created
|
|
67
|
+
✔ src/app/modules/order/order.interface.ts created
|
|
68
|
+
✔ src/app/modules/order/order.schema.ts created
|
|
69
|
+
✔ src/app/modules/order/order.service.ts created
|
|
70
|
+
✔ tests/routes/order.test.ts created
|
|
71
|
+
✔ Route registered in routes.ts
|
|
72
|
+
✔ Swagger docs registered in swaggerOptions.ts
|
|
73
|
+
```
|
|
63
74
|
|
|
64
|
-
|
|
65
|
-
|
|
75
|
+
The route and Swagger docs are wired up automatically. For Prisma projects a
|
|
76
|
+
`prisma/schema/<module>.prisma` model is created as well.
|
|
66
77
|
|
|
78
|
+
A ready-to-run [supertest](https://github.com/ladjs/supertest) suite is created in
|
|
79
|
+
`tests/routes/<module>.test.ts`. It covers create, list, get, update and delete,
|
|
80
|
+
including the 404s, and passes as generated:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
npm test -- tests/routes/order.test.ts
|
|
67
84
|
```
|
|
68
85
|
|
|
86
|
+
When you add fields, fill in `createPayload` / `updatePayload` at the top of the test.
|
|
87
|
+
Mongoose tests run against an in-memory MongoDB; Prisma tests use the Prisma mock in
|
|
88
|
+
`tests/helpers/prisma.ts`. Module names whose path is already taken (e.g. `auth`) are
|
|
89
|
+
refused.
|
|
90
|
+
|
|
91
|
+
## 🛠️ CLI Reference
|
|
92
|
+
|
|
93
|
+
```text
|
|
94
|
+
npx exp-node-server create <project-name> [--template <mongoose|prisma>]
|
|
95
|
+
npx exp-node-server generate <module-name> [--force]
|
|
96
|
+
|
|
97
|
+
Commands
|
|
98
|
+
create, -c <name> Create a new project from a template
|
|
99
|
+
generate, -g <name> Generate a module in the current project
|
|
100
|
+
|
|
101
|
+
Options
|
|
102
|
+
-t, --template <name> Template to use with create (mongoose | prisma)
|
|
103
|
+
-f, --force Overwrite an existing module when generating
|
|
104
|
+
-h, --help Show help
|
|
105
|
+
-v, --version Show version
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
- Project names are lowercased and spaces become dashes (`My API` → `my-api`).
|
|
109
|
+
- Module names are lowercased and spaces/dashes become underscores (`order-item` → `order_item`).
|
|
110
|
+
|
|
69
111
|
## 📁 Generated Module Structure
|
|
70
112
|
|
|
71
113
|
Each module follows a clean structure:
|
|
72
114
|
|
|
73
115
|
```Bash
|
|
74
116
|
src/app/modules/<module-name>/
|
|
75
|
-
├── <module>.interface.ts
|
|
76
|
-
├── <module>.schema.ts
|
|
117
|
+
├── <module>.interface.ts # mongoose
|
|
118
|
+
├── <module>.schema.ts # mongoose (prisma: prisma/schema/<module>.prisma)
|
|
77
119
|
├── <module>.validation.ts
|
|
78
120
|
├── <module>.route.ts
|
|
79
121
|
├── <module>.controller.ts
|
|
80
122
|
├── <module>.service.ts
|
|
81
123
|
└── <module>.swagger.ts
|
|
124
|
+
|
|
125
|
+
tests/routes/
|
|
126
|
+
└── <module>.test.ts
|
|
82
127
|
```
|
|
83
128
|
|
|
84
129
|
## ⚙️ Run the Project
|
|
@@ -103,6 +148,22 @@ http://localhost:5000/docs
|
|
|
103
148
|
- JWT — Authentication
|
|
104
149
|
- Swagger — API documentation
|
|
105
150
|
|
|
151
|
+
## 🧑🔧 Development
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
npm install
|
|
155
|
+
npm run dev # rebuild on change (tsup --watch)
|
|
156
|
+
npm run typecheck # tsc
|
|
157
|
+
npm test # vitest (unit + end-to-end against dist/)
|
|
158
|
+
npm run build # bundle to dist/cli.js
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Try the local build with `node dist/cli.js --help`, or run `npm link` to use
|
|
162
|
+
`exp-node-server` globally.
|
|
163
|
+
|
|
164
|
+
> Templates live in `templates/`. npm strips `.gitignore` files when
|
|
165
|
+
> publishing, so name them `_gitignore`. They're renamed when a project is created.
|
|
166
|
+
|
|
106
167
|
## 👨💻 Author
|
|
107
168
|
|
|
108
169
|
### Abumahid
|
|
@@ -124,3 +185,4 @@ https://linkedin.com/in/md-abu-mahid-islam
|
|
|
124
185
|
If you find this project helpful, consider giving it a ⭐ on `GitHub`.
|
|
125
186
|
|
|
126
187
|
It helps the project grow and reach more developers.
|
|
188
|
+
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var c=class extends Error{exitCode;constructor(t,r=1){super(t),this.name="CliError",this.exitCode=r}};import T from"chalk";var a={info:e=>console.log(e),success:e=>console.log(`${T.green("\u2714")} ${e}`),warn:e=>console.warn(`${T.yellow("\u26A0")} ${e}`),error:e=>console.error(`${T.red("\u2716")} ${e}`)};import S from"chalk";import{parseArgs as xe}from"util";import de from"@inquirer/select";import g from"chalk";import m from"fs";import h from"path";import pe from"ora";import j from"path";import{fileURLToPath as ce}from"url";var B=j.dirname(ce(import.meta.url)),C=j.basename(B)==="lib"?j.resolve(B,"../../templates"):j.resolve(B,"../templates");var le={_gitignore:".gitignore"},ue=new Set(["node_modules","dist","coverage",".env"]);async function k(e,{template:t,cwd:r=process.cwd()}={}){let s=h.join(r,e);if(m.existsSync(s))throw new c(`Folder '${e}' already exists.`);let n=t??await me(),i=h.join(C,n);if(!m.existsSync(i))throw new c(`Template '${n}' not found at ${i}.`);let o=pe(`Creating ${n} project...`).start();try{await m.promises.cp(i,s,{recursive:!0,filter:d=>!ue.has(h.basename(d))});for(let[d,f]of Object.entries(le)){let p=h.join(s,d);m.existsSync(p)&&await m.promises.rename(p,h.join(s,f))}await fe(s,e)}catch(d){throw o.fail("Failed to create project"),await m.promises.rm(s,{recursive:!0,force:!0}),d}o.succeed(`Project '${e}' created`),a.info(g.green(`
|
|
3
|
+
Next steps:`)),a.info(g.cyan(` cd ${e}`)),a.info(g.cyan(" npm install")),a.info(g.cyan(" cp .env.example .env # then fill in the secrets")),a.info(g.cyan(" docker compose up -d # starts the database")),n==="prisma"&&a.info(g.cyan(" npm run db:migrate")),a.info(g.cyan(" npm run dev")),a.info(g.cyan(" npm test"))}async function me(){return de({message:"Select a project template:",choices:[{name:"Mongoose (MongoDB)",value:"mongoose"},{name:"Prisma (PostgreSQL)",value:"prisma"}]})}async function fe(e,t){let r=h.join(e,"package.json");if(!m.existsSync(r))return;let s=JSON.parse(await m.promises.readFile(r,"utf-8"));s.name=t,await m.promises.writeFile(r,`${JSON.stringify(s,null,2)}
|
|
4
|
+
`)}import y from"chalk";import $ from"fs";import l from"path";var _=(e,t)=>{let r=`await ${e}_service.${t}(req);`,s=` const result = ${r}`;return s.length<=100?s:` const result =
|
|
5
|
+
${r}`},D=e=>`import catchAsync from '../../utils/catch_async.js';
|
|
6
|
+
import manageResponse from '../../utils/manage_response.js';
|
|
7
|
+
import { ${e}_service } from './${e}.service.js';
|
|
8
|
+
|
|
9
|
+
const get_all_${e} = catchAsync(async (req, res) => {
|
|
10
|
+
${_(e,`get_all_${e}_from_db`)}
|
|
11
|
+
manageResponse(res, {
|
|
12
|
+
success: true,
|
|
13
|
+
statusCode: 200,
|
|
14
|
+
message: 'All ${e} fetched successfully.',
|
|
15
|
+
data: result,
|
|
16
|
+
meta: {},
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const get_single_${e} = catchAsync(async (req, res) => {
|
|
21
|
+
${_(e,`get_single_${e}_from_db`)}
|
|
22
|
+
manageResponse(res, {
|
|
23
|
+
success: true,
|
|
24
|
+
statusCode: 200,
|
|
25
|
+
message: 'Single ${e} fetched successfully.',
|
|
26
|
+
data: result,
|
|
27
|
+
meta: {},
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const create_${e} = catchAsync(async (req, res) => {
|
|
32
|
+
${_(e,`create_${e}_into_db`)}
|
|
33
|
+
manageResponse(res, {
|
|
34
|
+
success: true,
|
|
35
|
+
statusCode: 201,
|
|
36
|
+
message: '${e} created successfully.',
|
|
37
|
+
data: result,
|
|
38
|
+
meta: {},
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const update_${e} = catchAsync(async (req, res) => {
|
|
43
|
+
${_(e,`update_${e}_into_db`)}
|
|
44
|
+
manageResponse(res, {
|
|
45
|
+
success: true,
|
|
46
|
+
statusCode: 200,
|
|
47
|
+
message: '${e} updated successfully.',
|
|
48
|
+
data: result,
|
|
49
|
+
meta: {},
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const delete_${e} = catchAsync(async (req, res) => {
|
|
54
|
+
${_(e,`delete_${e}_from_db`)}
|
|
55
|
+
manageResponse(res, {
|
|
56
|
+
success: true,
|
|
57
|
+
statusCode: 200,
|
|
58
|
+
message: '${e} deleted successfully.',
|
|
59
|
+
data: result,
|
|
60
|
+
meta: {},
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
export const ${e}_controller = {
|
|
65
|
+
get_all_${e},
|
|
66
|
+
get_single_${e},
|
|
67
|
+
create_${e},
|
|
68
|
+
update_${e},
|
|
69
|
+
delete_${e},
|
|
70
|
+
};
|
|
71
|
+
`;var x=(e,t)=>{let r=`router.${e}(${t.join(", ")});`;return r.length<=100?r:`router.${e}(
|
|
72
|
+
${t.map(s=>` ${s},`).join(`
|
|
73
|
+
`)}
|
|
74
|
+
);`},G=e=>`import { Router } from 'express';
|
|
75
|
+
import RequestValidator from '../../middlewares/request_validator.js';
|
|
76
|
+
import { ${e}_controller } from './${e}.controller.js';
|
|
77
|
+
import { ${e}_validations } from './${e}.validation.js';
|
|
78
|
+
|
|
79
|
+
const router = Router();
|
|
80
|
+
|
|
81
|
+
${x("get",["'/'",`${e}_controller.get_all_${e}`])}
|
|
82
|
+
${x("post",["'/'",`RequestValidator(${e}_validations.create_${e})`,`${e}_controller.create_${e}`])}
|
|
83
|
+
${x("get",["'/:id'",`${e}_controller.get_single_${e}`])}
|
|
84
|
+
${x("patch",["'/:id'",`RequestValidator(${e}_validations.update_${e})`,`${e}_controller.update_${e}`])}
|
|
85
|
+
${x("delete",["'/:id'",`${e}_controller.delete_${e}`])}
|
|
86
|
+
|
|
87
|
+
export default router;
|
|
88
|
+
`;var A=`{
|
|
89
|
+
name: 'id',
|
|
90
|
+
in: 'path',
|
|
91
|
+
required: true,
|
|
92
|
+
schema: { type: 'string' },
|
|
93
|
+
}`,F=e=>`export const ${e}SwaggerDocs = {
|
|
94
|
+
'/api/${e}': {
|
|
95
|
+
post: {
|
|
96
|
+
tags: ['${e}'],
|
|
97
|
+
summary: 'Create new ${e}',
|
|
98
|
+
description: '',
|
|
99
|
+
requestBody: {
|
|
100
|
+
required: true,
|
|
101
|
+
content: {
|
|
102
|
+
'application/json': {
|
|
103
|
+
example: JSON.stringify({}), // put your request body
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
responses: {
|
|
108
|
+
201: { description: '${e} created successfully' },
|
|
109
|
+
500: { description: 'Validation error or internal server error' },
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
get: {
|
|
113
|
+
tags: ['${e}'],
|
|
114
|
+
summary: 'Get all ${e}',
|
|
115
|
+
description: '',
|
|
116
|
+
parameters: [
|
|
117
|
+
{
|
|
118
|
+
name: 'page',
|
|
119
|
+
in: 'query',
|
|
120
|
+
required: false,
|
|
121
|
+
schema: { type: 'number' },
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
name: 'limit',
|
|
125
|
+
in: 'query',
|
|
126
|
+
required: false,
|
|
127
|
+
schema: { type: 'number' },
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
responses: {
|
|
131
|
+
200: { description: '${e} fetched successfully' },
|
|
132
|
+
401: { description: 'unauthorized' },
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
'/api/${e}/{id}': {
|
|
138
|
+
get: {
|
|
139
|
+
tags: ['${e}'],
|
|
140
|
+
summary: 'Get single ${e}',
|
|
141
|
+
description: '',
|
|
142
|
+
parameters: [
|
|
143
|
+
${A},
|
|
144
|
+
],
|
|
145
|
+
responses: {
|
|
146
|
+
200: { description: '${e} fetched successfully' },
|
|
147
|
+
401: { description: 'unauthorized' },
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
patch: {
|
|
151
|
+
tags: ['${e}'],
|
|
152
|
+
summary: 'Update ${e}',
|
|
153
|
+
description: '',
|
|
154
|
+
parameters: [
|
|
155
|
+
${A},
|
|
156
|
+
],
|
|
157
|
+
requestBody: {
|
|
158
|
+
required: true,
|
|
159
|
+
content: {
|
|
160
|
+
'application/json': {
|
|
161
|
+
example: JSON.stringify({}), // put your request body
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
responses: {
|
|
166
|
+
200: { description: '${e} updated successfully' },
|
|
167
|
+
500: { description: 'Validation error or internal server error' },
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
delete: {
|
|
171
|
+
tags: ['${e}'],
|
|
172
|
+
summary: 'Delete ${e}',
|
|
173
|
+
description: '',
|
|
174
|
+
parameters: [
|
|
175
|
+
${A},
|
|
176
|
+
],
|
|
177
|
+
responses: {
|
|
178
|
+
200: { description: '${e} deleted successfully' },
|
|
179
|
+
401: { description: 'unauthorized' },
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
`;var O=e=>`import { z } from 'zod';
|
|
185
|
+
|
|
186
|
+
const create_${e} = z.object({});
|
|
187
|
+
const update_${e} = z.object({});
|
|
188
|
+
|
|
189
|
+
export const ${e}_validations = {
|
|
190
|
+
create_${e},
|
|
191
|
+
update_${e},
|
|
192
|
+
};
|
|
193
|
+
`;var N=e=>`// add the fields of a ${e} here
|
|
194
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
195
|
+
export type T_${e} = {};
|
|
196
|
+
`;var M=e=>`import { Schema, model } from 'mongoose';
|
|
197
|
+
import type { T_${e} } from './${e}.interface.js';
|
|
198
|
+
|
|
199
|
+
const schema = new Schema<T_${e}>({}, { timestamps: true, versionKey: false });
|
|
200
|
+
|
|
201
|
+
export const ${e}_model = model('${e}', schema);
|
|
202
|
+
`;var L=e=>`import type { Request } from 'express';
|
|
203
|
+
import { AppError } from '../../utils/app_error.js';
|
|
204
|
+
import { ${e}_model } from './${e}.schema.js';
|
|
205
|
+
|
|
206
|
+
const notFound = () => new AppError('${e} not found', 404);
|
|
207
|
+
|
|
208
|
+
const get_all_${e}_from_db = async (_req: Request) => {
|
|
209
|
+
// define your own logic here
|
|
210
|
+
const result = await ${e}_model.find().lean();
|
|
211
|
+
return result;
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const get_single_${e}_from_db = async (req: Request) => {
|
|
215
|
+
// define your own logic here
|
|
216
|
+
const { id } = req.params;
|
|
217
|
+
const result = await ${e}_model.findById(id).lean();
|
|
218
|
+
if (!result) throw notFound();
|
|
219
|
+
return result;
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const create_${e}_into_db = async (req: Request) => {
|
|
223
|
+
// define your own logic here
|
|
224
|
+
const result = await ${e}_model.create(req.body);
|
|
225
|
+
return result;
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
const update_${e}_into_db = async (req: Request) => {
|
|
229
|
+
// define your own logic here
|
|
230
|
+
const { id } = req.params;
|
|
231
|
+
const result = await ${e}_model
|
|
232
|
+
.findByIdAndUpdate(id, req.body, { returnDocument: 'after' })
|
|
233
|
+
.lean();
|
|
234
|
+
if (!result) throw notFound();
|
|
235
|
+
return result;
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const delete_${e}_from_db = async (req: Request) => {
|
|
239
|
+
// define your own logic here
|
|
240
|
+
const { id } = req.params;
|
|
241
|
+
const result = await ${e}_model.findByIdAndDelete(id).lean();
|
|
242
|
+
if (!result) throw notFound();
|
|
243
|
+
return result;
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
export const ${e}_service = {
|
|
247
|
+
get_all_${e}_from_db,
|
|
248
|
+
get_single_${e}_from_db,
|
|
249
|
+
create_${e}_into_db,
|
|
250
|
+
update_${e}_into_db,
|
|
251
|
+
delete_${e}_from_db,
|
|
252
|
+
};
|
|
253
|
+
`;var U=e=>`import { Types } from 'mongoose';
|
|
254
|
+
import request from 'supertest';
|
|
255
|
+
import { describe, expect, it } from 'vitest';
|
|
256
|
+
import app from '../../src/app.js';
|
|
257
|
+
import { ${e}_model } from '../../src/app/modules/${e}/${e}.schema.js';
|
|
258
|
+
|
|
259
|
+
const BASE = '/api/${e}';
|
|
260
|
+
|
|
261
|
+
// Valid request bodies. Fill them in as you add fields to ${e}.schema.ts and
|
|
262
|
+
// ${e}.validation.ts (and protect the routes with auth() once they need it).
|
|
263
|
+
const createPayload = {};
|
|
264
|
+
const updatePayload = {};
|
|
265
|
+
|
|
266
|
+
/** Inserts a ${e} directly in the database, for the read / update / delete tests. */
|
|
267
|
+
const insert = () => ${e}_model.create(createPayload);
|
|
268
|
+
|
|
269
|
+
describe(BASE, () => {
|
|
270
|
+
it('POST / creates one', async () => {
|
|
271
|
+
const res = await request(app).post(BASE).send(createPayload);
|
|
272
|
+
|
|
273
|
+
expect(res.status).toBe(201);
|
|
274
|
+
expect(await ${e}_model.countDocuments()).toBe(1);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it('GET / lists them', async () => {
|
|
278
|
+
await insert();
|
|
279
|
+
|
|
280
|
+
const res = await request(app).get(BASE);
|
|
281
|
+
|
|
282
|
+
expect(res.status).toBe(200);
|
|
283
|
+
expect(res.body.data).toHaveLength(1);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it('GET /:id returns one', async () => {
|
|
287
|
+
const doc = await insert();
|
|
288
|
+
|
|
289
|
+
const res = await request(app).get(\`\${BASE}/\${doc._id}\`);
|
|
290
|
+
|
|
291
|
+
expect(res.status).toBe(200);
|
|
292
|
+
expect(res.body.data._id).toBe(String(doc._id));
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it('GET /:id is 404 when it does not exist', async () => {
|
|
296
|
+
const res = await request(app).get(\`\${BASE}/\${new Types.ObjectId()}\`);
|
|
297
|
+
|
|
298
|
+
expect(res.status).toBe(404);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it('GET /:id is 400 for a malformed id', async () => {
|
|
302
|
+
const res = await request(app).get(\`\${BASE}/not-an-id\`);
|
|
303
|
+
|
|
304
|
+
expect(res.status).toBe(400);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it('PATCH /:id updates it', async () => {
|
|
308
|
+
const doc = await insert();
|
|
309
|
+
|
|
310
|
+
const res = await request(app).patch(\`\${BASE}/\${doc._id}\`).send(updatePayload);
|
|
311
|
+
|
|
312
|
+
expect(res.status).toBe(200);
|
|
313
|
+
const stored = await ${e}_model.findById(doc._id).lean();
|
|
314
|
+
expect(stored).toMatchObject(updatePayload);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it('PATCH /:id is 404 when it does not exist', async () => {
|
|
318
|
+
const res = await request(app).patch(\`\${BASE}/\${new Types.ObjectId()}\`).send(updatePayload);
|
|
319
|
+
|
|
320
|
+
expect(res.status).toBe(404);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('DELETE /:id deletes it', async () => {
|
|
324
|
+
const doc = await insert();
|
|
325
|
+
|
|
326
|
+
const res = await request(app).delete(\`\${BASE}/\${doc._id}\`);
|
|
327
|
+
|
|
328
|
+
expect(res.status).toBe(200);
|
|
329
|
+
const stillThere = await ${e}_model.exists({ _id: doc._id });
|
|
330
|
+
expect(stillThere).toBeNull();
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it('DELETE /:id is 404 when it does not exist', async () => {
|
|
334
|
+
const res = await request(app).delete(\`\${BASE}/\${new Types.ObjectId()}\`);
|
|
335
|
+
|
|
336
|
+
expect(res.status).toBe(404);
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
`;var V=e=>e.charAt(0).toUpperCase()+e.slice(1);var H=e=>`model ${V(e)} {
|
|
340
|
+
id String @id @default(uuid())
|
|
341
|
+
createdAt DateTime @default(now())
|
|
342
|
+
updatedAt DateTime @updatedAt
|
|
343
|
+
}
|
|
344
|
+
`;var z=e=>`import type { Request } from 'express';
|
|
345
|
+
import { prisma } from '../../lib/prisma.js';
|
|
346
|
+
import { AppError } from '../../utils/app_error.js';
|
|
347
|
+
|
|
348
|
+
const get_all_${e}_from_db = async (_req: Request) => {
|
|
349
|
+
// define your own logic here
|
|
350
|
+
const result = await prisma.${e}.findMany();
|
|
351
|
+
return result;
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
const get_single_${e}_from_db = async (req: Request) => {
|
|
355
|
+
// define your own logic here
|
|
356
|
+
const { id } = req.params as { id: string };
|
|
357
|
+
const result = await prisma.${e}.findUnique({ where: { id } });
|
|
358
|
+
if (!result) throw new AppError('${e} not found', 404);
|
|
359
|
+
return result;
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
const create_${e}_into_db = async (req: Request) => {
|
|
363
|
+
// define your own logic here
|
|
364
|
+
const result = await prisma.${e}.create({ data: req.body });
|
|
365
|
+
return result;
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
// update / delete of a missing record throw Prisma's P2025, which the global
|
|
369
|
+
// error handler turns into a 404
|
|
370
|
+
const update_${e}_into_db = async (req: Request) => {
|
|
371
|
+
// define your own logic here
|
|
372
|
+
const { id } = req.params as { id: string };
|
|
373
|
+
const result = await prisma.${e}.update({ where: { id }, data: req.body });
|
|
374
|
+
return result;
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
const delete_${e}_from_db = async (req: Request) => {
|
|
378
|
+
// define your own logic here
|
|
379
|
+
const { id } = req.params as { id: string };
|
|
380
|
+
const result = await prisma.${e}.delete({ where: { id } });
|
|
381
|
+
return result;
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
export const ${e}_service = {
|
|
385
|
+
get_all_${e}_from_db,
|
|
386
|
+
get_single_${e}_from_db,
|
|
387
|
+
create_${e}_into_db,
|
|
388
|
+
update_${e}_into_db,
|
|
389
|
+
delete_${e}_from_db,
|
|
390
|
+
};
|
|
391
|
+
`;var J=e=>`import { Prisma } from '@prisma/client';
|
|
392
|
+
import request from 'supertest';
|
|
393
|
+
import { describe, expect, it } from 'vitest';
|
|
394
|
+
import app from '../../src/app.js';
|
|
395
|
+
import { prismaMock } from '../helpers/prisma.js';
|
|
396
|
+
|
|
397
|
+
const BASE = '/api/${e}';
|
|
398
|
+
const db = prismaMock.${e};
|
|
399
|
+
|
|
400
|
+
// Valid request bodies. Fill them in as you add fields to prisma/schema/${e}.prisma
|
|
401
|
+
// and ${e}.validation.ts (and protect the routes with auth() once they need it).
|
|
402
|
+
const createPayload = {};
|
|
403
|
+
const updatePayload = {};
|
|
404
|
+
|
|
405
|
+
// a row as the database would return it
|
|
406
|
+
const row = {
|
|
407
|
+
id: '9b2f3c1e-7a41-4c8e-9d6b-2f4e8a1c5b70',
|
|
408
|
+
...createPayload,
|
|
409
|
+
createdAt: new Date('2025-01-01'),
|
|
410
|
+
updatedAt: new Date('2025-01-01'),
|
|
411
|
+
};
|
|
412
|
+
// ...and as it looks in a JSON response
|
|
413
|
+
const json = JSON.parse(JSON.stringify(row));
|
|
414
|
+
|
|
415
|
+
const recordNotFound = () =>
|
|
416
|
+
new Prisma.PrismaClientKnownRequestError('Record not found', {
|
|
417
|
+
code: 'P2025',
|
|
418
|
+
clientVersion: 'test',
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
describe(BASE, () => {
|
|
422
|
+
it('POST / creates one', async () => {
|
|
423
|
+
db.create.mockResolvedValue(row);
|
|
424
|
+
|
|
425
|
+
const res = await request(app).post(BASE).send(createPayload);
|
|
426
|
+
|
|
427
|
+
expect(res.status).toBe(201);
|
|
428
|
+
expect(res.body.data).toEqual(json);
|
|
429
|
+
expect(db.create).toHaveBeenCalledWith({ data: createPayload });
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
it('GET / lists them', async () => {
|
|
433
|
+
db.findMany.mockResolvedValue([row]);
|
|
434
|
+
|
|
435
|
+
const res = await request(app).get(BASE);
|
|
436
|
+
|
|
437
|
+
expect(res.status).toBe(200);
|
|
438
|
+
expect(res.body.data).toEqual([json]);
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
it('GET /:id returns one', async () => {
|
|
442
|
+
db.findUnique.mockResolvedValue(row);
|
|
443
|
+
|
|
444
|
+
const res = await request(app).get(\`\${BASE}/\${row.id}\`);
|
|
445
|
+
|
|
446
|
+
expect(res.status).toBe(200);
|
|
447
|
+
expect(res.body.data).toEqual(json);
|
|
448
|
+
expect(db.findUnique).toHaveBeenCalledWith({ where: { id: row.id } });
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
it('GET /:id is 404 when it does not exist', async () => {
|
|
452
|
+
db.findUnique.mockResolvedValue(null);
|
|
453
|
+
|
|
454
|
+
const res = await request(app).get(\`\${BASE}/\${row.id}\`);
|
|
455
|
+
|
|
456
|
+
expect(res.status).toBe(404);
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
it('PATCH /:id updates it', async () => {
|
|
460
|
+
db.update.mockResolvedValue(row);
|
|
461
|
+
|
|
462
|
+
const res = await request(app).patch(\`\${BASE}/\${row.id}\`).send(updatePayload);
|
|
463
|
+
|
|
464
|
+
expect(res.status).toBe(200);
|
|
465
|
+
expect(db.update).toHaveBeenCalledWith({ where: { id: row.id }, data: updatePayload });
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
it('PATCH /:id is 404 when it does not exist', async () => {
|
|
469
|
+
db.update.mockRejectedValue(recordNotFound());
|
|
470
|
+
|
|
471
|
+
const res = await request(app).patch(\`\${BASE}/\${row.id}\`).send(updatePayload);
|
|
472
|
+
|
|
473
|
+
expect(res.status).toBe(404);
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
it('DELETE /:id deletes it', async () => {
|
|
477
|
+
db.delete.mockResolvedValue(row);
|
|
478
|
+
|
|
479
|
+
const res = await request(app).delete(\`\${BASE}/\${row.id}\`);
|
|
480
|
+
|
|
481
|
+
expect(res.status).toBe(200);
|
|
482
|
+
expect(db.delete).toHaveBeenCalledWith({ where: { id: row.id } });
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
it('DELETE /:id is 404 when it does not exist', async () => {
|
|
486
|
+
db.delete.mockRejectedValue(recordNotFound());
|
|
487
|
+
|
|
488
|
+
const res = await request(app).delete(\`\${BASE}/\${row.id}\`);
|
|
489
|
+
|
|
490
|
+
expect(res.status).toBe(404);
|
|
491
|
+
});
|
|
492
|
+
});
|
|
493
|
+
`;var ge=/^import\s[\s\S]*?from\s*(['"])[^'"]+\1;?[ \t]*$|^import\s*(['"])[^'"]+\2;?[ \t]*$/gm;function E(e){return{quote:e.match(/^import[\s\S]*?(['"])/m)?.[1]==="'"?"'":'"',jsExtension:/from\s*['"]\.{1,2}\/[^'"]+\.js['"]/.test(e)}}function q(e,t){let r=-1;for(let s of e.matchAll(ge))r=s.index+s[0].length;return r===-1?`${t}
|
|
494
|
+
${e}`:`${e.slice(0,r)}
|
|
495
|
+
${t}${e.slice(r)}`}function v(e,t,r,s){let n=e.match(t);if(n?.index===void 0)return null;let i=n.index+n[0].length,o=e.indexOf(r,i);if(o===-1)return null;let d=e.slice(i,o),f=d.match(/\n([ \t]*)$/)?.[1]??"",p=d.match(/\n([ \t]+)\S/)?.[1]??`${f} `,w=d.trimEnd(),ie=w.length>0&&!/[,[{]$/.test(w),ae=`${w}${ie?",":""}
|
|
496
|
+
${p}${s}
|
|
497
|
+
${f}`;return e.slice(0,i)+ae+e.slice(o)}function R(e,t){let r=e.includes(`\r
|
|
498
|
+
`),s=t(r?e.replace(/\r\n/g,`
|
|
499
|
+
`):e);return!r||s.status!=="updated"?s:{status:"updated",content:s.content.replace(/\n/g,`\r
|
|
500
|
+
`)}}function W(e,t){return R(e,r=>$e(r,t))}function $e(e,t){let{quote:r,jsExtension:s}=E(e),n=`./app/modules/${t}/${t}.route`,i=`${t}Route`,o=e;if(o.includes(n)||(o=q(o,`import ${i} from ${r}${n}${s?".js":""}${r};`)),!new RegExp(`route:\\s*${i}\\b`).test(o)){let d=v(o,/const moduleRoutes\s*=\s*\[/,"]",`{ path: ${r}/${t}${r}, route: ${i} },`);if(d===null)return{status:"failed",reason:"could not find `const moduleRoutes = [...]`"};o=d}return o===e?{status:"unchanged"}:{status:"updated",content:o}}function K(e,t){return R(e,r=>he(r,t))}function he(e,t){let{quote:r,jsExtension:s}=E(e),n=`./app/modules/${t}/${t}.swagger`,i=`${t}SwaggerDocs`,o=e;if(o.includes(n)||(o=q(o,`import { ${i} } from ${r}${n}${s?".js":""}${r};`)),!o.includes(`...${i}`)){let d=v(o,/paths:\s*\{/,"}",`...${i},`);if(d===null)return{status:"failed",reason:"could not find `paths: { ... }`"};o=d}return o===e?{status:"unchanged"}:{status:"updated",content:o}}import Q from"fs";import X from"path";function Y(e,t){for(let r of e)Q.mkdirSync(X.dirname(r.path),{recursive:!0}),Q.writeFileSync(r.path,r.content),a.success(`${X.relative(t,r.path)} created`)}import Z from"fs";import ye from"path";var b=["mongoose","prisma"],P=".projectrc.json",I=e=>typeof e=="string"&&b.includes(e);function ee(e){let t=ye.join(e,P);if(!Z.existsSync(t))throw new c(`${P} not found. Run this command from the root of a project created with exp-node-server.`);let r;try{r=JSON.parse(Z.readFileSync(t,"utf-8"))}catch{throw new c(`${P} is not valid JSON.`)}let s=r?.database;if(!I(s))throw new c(`Invalid "database" in ${P}. Expected one of: ${b.join(", ")}.`);return s}async function re(e,{cwd:t=process.cwd(),force:r=!1}={}){let s=ee(t),n=l.join(t,"src/app/modules",e);if($.existsSync(n)&&!r)throw new c(`Module '${e}' already exists at ${l.relative(t,n)}. Use --force to overwrite it.`);_e(l.join(t,"src/routes.ts"),e);let i=(p,w)=>({path:l.join(n,`${e}.${p}`),content:w}),o=[i("controller.ts",D(e)),i("route.ts",G(e)),i("validation.ts",O(e)),i("swagger.ts",F(e))];s==="mongoose"?o.push(i("interface.ts",N(e)),i("schema.ts",M(e)),i("service.ts",L(e))):o.push(i("service.ts",z(e)),{path:l.join(t,"prisma/schema",`${e}.prisma`),content:H(e)});let d=l.join(t,"tests/routes",`${e}.test.ts`),f=we.some(p=>$.existsSync(l.join(t,p)));f&&o.push({path:d,content:s==="mongoose"?U(e):J(e)}),Y(o,t),f||a.info(y.dim(" no vitest config found, skipped tests/routes test")),te(l.join(t,"src/routes.ts"),p=>W(p,e),"Route registered"),te(l.join(t,"src/swaggerOptions.ts"),p=>K(p,e),"Swagger docs registered"),s==="prisma"&&(a.info(y.blue(`
|
|
501
|
+
Next, run:`)),a.info(y.yellow(" npx prisma generate && npx prisma migrate dev"))),f&&a.info(y.blue(`
|
|
502
|
+
Test it with: npm test -- tests/routes/${e}.test.ts`)),a.info(y.green(`
|
|
503
|
+
Module '${e}' created successfully`))}var we=["vitest.config.ts","vitest.config.mts","vitest.config.js"];function _e(e,t){if(!$.existsSync(e))return;let r=$.readFileSync(e,"utf-8"),s=new RegExp(`path:\\s*['"]/${t}['"]\\s*,\\s*route:\\s*(\\w+)`).exec(r);if(s&&s[1]!==`${t}Route`)throw new c(`The path '/${t}' is already used by ${s[1]} in src/routes.ts. Pick another module name.`)}function te(e,t,r){let s=l.basename(e);if(!$.existsSync(e)){a.warn(`${s} not found, skipping registration`);return}let n=t($.readFileSync(e,"utf-8"));switch(n.status){case"updated":$.writeFileSync(e,n.content),a.success(`${r} in ${s}`);break;case"unchanged":a.info(y.dim(` already registered in ${s}`));break;case"failed":a.warn(`Couldn't update ${s} (${n.reason}). Register it manually.`);break}}function se(e){if(!e)return null;let t=e.trim().toLowerCase().replace(/[-\s]+/g,"_");return/^[a-z][a-z_]*$/.test(t)?t:null}function oe(e){if(!e)return null;let t=e.trim().toLowerCase().replace(/\s+/g,"-");return/^[a-z0-9][a-z0-9-]*$/.test(t)?t:null}var u="exp-node-server",be=`
|
|
504
|
+
${S.bold(u)} - scaffold Express + TypeScript APIs with Mongoose or Prisma
|
|
505
|
+
|
|
506
|
+
${S.bold("Usage")}
|
|
507
|
+
npx ${u} create <project-name> [--template <mongoose|prisma>]
|
|
508
|
+
npx ${u} generate <module-name> [--force]
|
|
509
|
+
|
|
510
|
+
${S.bold("Commands")}
|
|
511
|
+
create, -c <name> Create a new project from a template
|
|
512
|
+
generate, -g <name> Generate a module in the current project
|
|
513
|
+
|
|
514
|
+
${S.bold("Options")}
|
|
515
|
+
-t, --template <name> Template to use with create (${b.join(" | ")})
|
|
516
|
+
-f, --force Overwrite an existing module when generating
|
|
517
|
+
-h, --help Show this help
|
|
518
|
+
-v, --version Show version
|
|
519
|
+
|
|
520
|
+
${S.bold("Examples")}
|
|
521
|
+
npx ${u} -c my-api
|
|
522
|
+
npx ${u} create my-api --template prisma
|
|
523
|
+
npx ${u} -g order
|
|
524
|
+
`;async function ne(e){let{values:t,positionals:r}=Se(e);if(t.version){console.log("2.0.0");return}let s=je(t,r);if(t.help||!s){console.log(be);return}if(s.kind==="create"){let i=oe(s.name);if(!i)throw new c(s.name?`Invalid project name '${s.name}'. Use letters, numbers and dashes.`:"Project name required. Example: exp-node-server -c my-api");let{template:o}=t;if(o!==void 0&&!I(o))throw new c(`Unknown template '${o}'. Use one of: ${b.join(", ")}.`);await k(i,{template:o});return}let n=se(s.name);if(!n)throw new c(s.name?`Invalid module name '${s.name}'. Use letters and underscores, starting with a letter.`:"Module name required. Example: exp-node-server -g order");await re(n,{force:t.force??!1})}function Se(e){try{return xe({args:e,allowPositionals:!0,strict:!0,options:{create:{type:"string",short:"c"},generate:{type:"string",short:"g"},template:{type:"string",short:"t"},force:{type:"boolean",short:"f"},help:{type:"boolean",short:"h"},version:{type:"boolean",short:"v"}}})}catch(t){let r=t instanceof Error?t.message:String(t);throw new c(`${r}
|
|
525
|
+
Run '${u} --help' for usage.`)}}function je(e,t){if(e.create!==void 0&&e.generate!==void 0)throw new c("Use either -c (create) or -g (generate), not both.");if((e.create!==void 0||e.generate!==void 0)&&t.length>0)throw new c(`Unexpected argument '${t[0]}'. Run '${u} --help' for usage.`);if(e.create!==void 0)return{kind:"create",name:e.create};if(e.generate!==void 0)return{kind:"generate",name:e.generate};let[r,s,...n]=t;if(r===void 0)return null;if(n.length>0)throw new c(`Unexpected argument '${n[0]}'. Run '${u} --help' for usage.`);switch(r){case"create":case"c":return{kind:"create",name:s};case"generate":case"g":return{kind:"generate",name:s};default:throw new c(`Unknown command '${r}'. Run '${u} --help' for usage.`)}}try{await ne(process.argv.slice(2))}catch(e){e instanceof c?(a.error(e.message),process.exitCode=e.exitCode):e instanceof Error&&e.name==="ExitPromptError"?process.exitCode=130:(a.error("Unexpected error:"),console.error(e),process.exitCode=1)}
|