exp-node-server 1.1.7 → 2.1.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.
Files changed (183) hide show
  1. package/README.md +162 -20
  2. package/dist/cli.js +613 -0
  3. package/package.json +29 -9
  4. package/templates/mongoose/.dockerignore +10 -0
  5. package/templates/mongoose/.env.example +56 -7
  6. package/templates/mongoose/.prettierignore +4 -0
  7. package/templates/mongoose/.prettierrc +7 -0
  8. package/templates/mongoose/Dockerfile +33 -0
  9. package/templates/mongoose/_gitignore +35 -0
  10. package/templates/mongoose/docker-compose.yml +21 -0
  11. package/templates/mongoose/eslint.config.js +35 -0
  12. package/templates/mongoose/package-lock.json +6366 -0
  13. package/templates/mongoose/package.json +49 -11
  14. package/templates/mongoose/readme.md +88 -0
  15. package/templates/mongoose/src/app/configs/index.ts +178 -26
  16. package/templates/mongoose/src/app/errors/zodError.ts +16 -16
  17. package/templates/mongoose/src/app/lib/db.ts +18 -0
  18. package/templates/mongoose/src/app/lib/logger.ts +12 -0
  19. package/templates/mongoose/src/app/middlewares/auth.ts +48 -44
  20. package/templates/mongoose/src/app/middlewares/global_error_handler.ts +59 -65
  21. package/templates/mongoose/src/app/middlewares/not_found_api.ts +7 -7
  22. package/templates/mongoose/src/app/middlewares/rate_limiter.ts +153 -0
  23. package/templates/mongoose/src/app/middlewares/request_validator.ts +11 -10
  24. package/templates/mongoose/src/app/middlewares/uploader.ts +17 -13
  25. package/templates/mongoose/src/app/modules/account/account.controller.ts +156 -0
  26. package/templates/mongoose/src/app/modules/account/account.interface.ts +65 -0
  27. package/templates/mongoose/src/app/modules/account/account.route.ts +101 -0
  28. package/templates/mongoose/src/app/modules/account/account.schema.ts +97 -0
  29. package/templates/mongoose/src/app/modules/account/account.service.ts +422 -0
  30. package/templates/mongoose/src/app/modules/account/account.swagger.ts +278 -0
  31. package/templates/mongoose/src/app/modules/account/account.validation.ts +63 -0
  32. package/templates/mongoose/src/app/modules/profile/profile.controller.ts +17 -0
  33. package/templates/mongoose/src/app/modules/profile/profile.interface.ts +7 -0
  34. package/templates/mongoose/src/app/modules/profile/profile.route.ts +28 -0
  35. package/templates/mongoose/src/app/modules/profile/profile.schema.ts +13 -0
  36. package/templates/mongoose/src/app/modules/profile/profile.service.ts +28 -0
  37. package/templates/mongoose/src/app/modules/profile/profile.swagger.ts +34 -0
  38. package/templates/mongoose/src/app/modules/profile/profile.validation.ts +8 -0
  39. package/templates/mongoose/src/app/passport-js/index.ts +87 -0
  40. package/templates/mongoose/src/app/passport-js/oauth-state.ts +150 -0
  41. package/templates/mongoose/src/app/passport-js/providers.ts +258 -0
  42. package/templates/mongoose/src/app/passport-js/social-account.ts +141 -0
  43. package/templates/mongoose/src/app/queues/agenda.ts +36 -0
  44. package/templates/mongoose/src/app/queues/email.queue.ts +81 -0
  45. package/templates/mongoose/src/app/queues/index.ts +17 -0
  46. package/templates/mongoose/src/app/templates/otpEmailTemplate.ts +185 -0
  47. package/templates/mongoose/src/app/templates/resetPasswordEmailTemplate.ts +157 -0
  48. package/templates/mongoose/src/app/types/error.d.ts +7 -7
  49. package/templates/mongoose/src/app/types/express.d.ts +11 -0
  50. package/templates/mongoose/src/app/types/passport-oauth1.d.ts +51 -0
  51. package/templates/mongoose/src/app/utils/JWT.ts +38 -18
  52. package/templates/mongoose/src/app/utils/app_error.ts +10 -10
  53. package/templates/mongoose/src/app/utils/auth_cookie.ts +27 -0
  54. package/templates/mongoose/src/app/utils/catch_async.ts +8 -8
  55. package/templates/mongoose/src/app/utils/cloudinary.ts +26 -59
  56. package/templates/mongoose/src/app/utils/mail_sender.ts +26 -92
  57. package/templates/mongoose/src/app/utils/manage_response.ts +19 -19
  58. package/templates/mongoose/src/app/utils/otp.ts +20 -0
  59. package/templates/mongoose/src/app/utils/session.ts +55 -0
  60. package/templates/mongoose/src/app.ts +73 -42
  61. package/templates/mongoose/src/routes.ts +13 -13
  62. package/templates/mongoose/src/server.ts +38 -18
  63. package/templates/mongoose/src/swaggerOptions.ts +23 -23
  64. package/templates/mongoose/tests/app.test.ts +97 -0
  65. package/templates/mongoose/tests/global-setup.ts +15 -0
  66. package/templates/mongoose/tests/helpers/fixtures.ts +42 -0
  67. package/templates/mongoose/tests/queues/email.queue.test.ts +137 -0
  68. package/templates/mongoose/tests/routes/account.test.ts +614 -0
  69. package/templates/mongoose/tests/routes/profile.test.ts +95 -0
  70. package/templates/mongoose/tests/routes/social.test.ts +310 -0
  71. package/templates/mongoose/tests/setup.ts +25 -0
  72. package/templates/mongoose/tsconfig.json +16 -20
  73. package/templates/mongoose/vitest.config.ts +43 -0
  74. package/templates/prisma/.dockerignore +2 -0
  75. package/templates/prisma/.env.example +62 -6
  76. package/templates/prisma/.prettierignore +4 -0
  77. package/templates/prisma/.prettierrc +7 -0
  78. package/templates/prisma/.projectrc.json +1 -1
  79. package/templates/prisma/Dockerfile +21 -7
  80. package/templates/prisma/_gitignore +38 -0
  81. package/templates/prisma/docker-compose.yml +8 -7
  82. package/templates/prisma/eslint.config.js +35 -0
  83. package/templates/prisma/package-lock.json +7316 -0
  84. package/templates/prisma/package.json +47 -10
  85. package/templates/prisma/prisma/schema/account.schema.prisma +92 -10
  86. package/templates/prisma/prisma/schema/profile.schema.prisma +8 -3
  87. package/templates/prisma/prisma/schema/schema.prisma +1 -2
  88. package/templates/prisma/prisma.config.ts +5 -5
  89. package/templates/prisma/readme.md +90 -0
  90. package/templates/prisma/src/app/configs/index.ts +175 -26
  91. package/templates/prisma/src/app/errors/zodError.ts +16 -16
  92. package/templates/prisma/src/app/lib/logger.ts +12 -0
  93. package/templates/prisma/src/app/lib/prisma.ts +4 -6
  94. package/templates/prisma/src/app/middlewares/auth.ts +37 -16
  95. package/templates/prisma/src/app/middlewares/global_error_handler.ts +42 -35
  96. package/templates/prisma/src/app/middlewares/not_found_api.ts +7 -7
  97. package/templates/prisma/src/app/middlewares/rate_limiter.ts +124 -0
  98. package/templates/prisma/src/app/middlewares/request_validator.ts +4 -3
  99. package/templates/prisma/src/app/middlewares/uploader.ts +17 -13
  100. package/templates/prisma/src/app/modules/account/account.controller.ts +74 -34
  101. package/templates/prisma/src/app/modules/account/account.route.ts +68 -22
  102. package/templates/prisma/src/app/modules/account/account.service.ts +329 -309
  103. package/templates/prisma/src/app/modules/account/account.swagger.ts +167 -76
  104. package/templates/prisma/src/app/modules/account/account.validation.ts +41 -19
  105. package/templates/prisma/src/app/modules/profile/profile.controller.ts +4 -4
  106. package/templates/prisma/src/app/modules/profile/profile.route.ts +18 -12
  107. package/templates/prisma/src/app/modules/profile/profile.service.ts +11 -18
  108. package/templates/prisma/src/app/modules/profile/profile.swagger.ts +11 -11
  109. package/templates/prisma/src/app/modules/profile/profile.validation.ts +2 -2
  110. package/templates/prisma/src/app/passport-js/index.ts +87 -0
  111. package/templates/prisma/src/app/passport-js/oauth-state.ts +150 -0
  112. package/templates/prisma/src/app/passport-js/providers.ts +263 -0
  113. package/templates/prisma/src/app/passport-js/social-account.ts +147 -0
  114. package/templates/prisma/src/app/queues/boss.ts +33 -0
  115. package/templates/prisma/src/app/queues/email.queue.ts +71 -0
  116. package/templates/prisma/src/app/queues/index.ts +23 -0
  117. package/templates/prisma/src/app/queues/maintenance.queue.ts +30 -0
  118. package/templates/prisma/src/app/templates/otpEmailTemplate.ts +185 -0
  119. package/templates/prisma/src/app/templates/resetPasswordEmailTemplate.ts +157 -0
  120. package/templates/prisma/src/app/types/error.d.ts +7 -7
  121. package/templates/prisma/src/app/types/express.d.ts +11 -0
  122. package/templates/prisma/src/app/types/passport-oauth1.d.ts +51 -0
  123. package/templates/prisma/src/app/utils/JWT.ts +36 -17
  124. package/templates/prisma/src/app/utils/app_error.ts +10 -10
  125. package/templates/prisma/src/app/utils/auth_cookie.ts +27 -0
  126. package/templates/prisma/src/app/utils/catch_async.ts +8 -8
  127. package/templates/prisma/src/app/utils/cloudinary.ts +26 -59
  128. package/templates/prisma/src/app/utils/mail_sender.ts +26 -92
  129. package/templates/prisma/src/app/utils/manage_response.ts +19 -19
  130. package/templates/prisma/src/app/utils/otp.ts +20 -0
  131. package/templates/prisma/src/app/utils/session.ts +57 -0
  132. package/templates/prisma/src/app.ts +73 -42
  133. package/templates/prisma/src/routes.ts +12 -10
  134. package/templates/prisma/src/server.ts +37 -19
  135. package/templates/prisma/src/swaggerOptions.ts +22 -26
  136. package/templates/prisma/tests/app.test.ts +86 -0
  137. package/templates/prisma/tests/helpers/fixtures.ts +35 -0
  138. package/templates/prisma/tests/helpers/prisma.ts +32 -0
  139. package/templates/prisma/tests/queues/email.queue.test.ts +162 -0
  140. package/templates/prisma/tests/routes/account.test.ts +627 -0
  141. package/templates/prisma/tests/routes/profile.test.ts +91 -0
  142. package/templates/prisma/tests/routes/social.test.ts +317 -0
  143. package/templates/prisma/tests/setup.ts +10 -0
  144. package/templates/prisma/tsconfig.json +16 -23
  145. package/templates/prisma/vitest.config.ts +38 -0
  146. package/bin/cli.js +0 -41
  147. package/src/createProject.js +0 -61
  148. package/src/generateModule.js +0 -93
  149. package/src/utils/getProjectType.js +0 -13
  150. package/src/utils/mongoose/controllerGenerator.js +0 -70
  151. package/src/utils/mongoose/routeGenerator.js +0 -27
  152. package/src/utils/mongoose/schemaGenerator.js +0 -11
  153. package/src/utils/mongoose/serviceGenerator.js +0 -49
  154. package/src/utils/mongoose/swaggerGenerator.js +0 -112
  155. package/src/utils/mongoose/validationGenerator.js +0 -13
  156. package/src/utils/prisma/prismaSchemaGenerator.js +0 -10
  157. package/src/utils/prisma/prismaServiceGenerator.js +0 -47
  158. package/src/utils/routeInjector.js +0 -40
  159. package/src/utils/swaggerInjector.js +0 -52
  160. package/src/utils/validateModuleName.js +0 -17
  161. package/src/utils/validateProjectName.js +0 -15
  162. package/src/utils.js +0 -26
  163. package/templates/mongoose/src/app/errors/castError.ts +0 -23
  164. package/templates/mongoose/src/app/errors/duplicateError.ts +0 -26
  165. package/templates/mongoose/src/app/errors/validationError.ts +0 -25
  166. package/templates/mongoose/src/app/modules/auth/auth.controller.ts +0 -127
  167. package/templates/mongoose/src/app/modules/auth/auth.interface.ts +0 -24
  168. package/templates/mongoose/src/app/modules/auth/auth.route.ts +0 -46
  169. package/templates/mongoose/src/app/modules/auth/auth.schema.ts +0 -19
  170. package/templates/mongoose/src/app/modules/auth/auth.service.ts +0 -324
  171. package/templates/mongoose/src/app/modules/auth/auth.swagger.ts +0 -214
  172. package/templates/mongoose/src/app/modules/auth/auth.validation.ts +0 -37
  173. package/templates/mongoose/src/app/modules/user/user.controller.ts +0 -17
  174. package/templates/mongoose/src/app/modules/user/user.interface.ts +0 -15
  175. package/templates/mongoose/src/app/modules/user/user.route.ts +0 -20
  176. package/templates/mongoose/src/app/modules/user/user.schema.ts +0 -22
  177. package/templates/mongoose/src/app/modules/user/user.service.ts +0 -22
  178. package/templates/mongoose/src/app/modules/user/user.swagger.ts +0 -75
  179. package/templates/mongoose/src/app/modules/user/user.validation.ts +0 -18
  180. package/templates/mongoose/src/app/types/index.d.ts +0 -12
  181. package/templates/mongoose/src/app/utils/isAccountExist.ts +0 -31
  182. package/templates/prisma/src/app/types/index.d.ts +0 -12
  183. package/templates/prisma/src/app/utils/otpGenerator.ts +0 -29
package/dist/cli.js ADDED
@@ -0,0 +1,613 @@
1
+ #!/usr/bin/env node
2
+ var p=class extends Error{exitCode;constructor(t,r=1){super(t),this.name="CliError",this.exitCode=r}};import U from"chalk";var d={info:e=>console.log(e),success:e=>console.log(`${U.green("\u2714")} ${e}`),warn:e=>console.warn(`${U.yellow("\u26A0")} ${e}`),error:e=>console.error(`${U.red("\u2716")} ${e}`)};import B from"chalk";import{parseArgs as Ge}from"util";import Se from"@inquirer/select";import x from"chalk";import $ from"fs";import b from"path";import je from"ora";import T from"path";import{fileURLToPath as be}from"url";var M=T.dirname(be(import.meta.url)),J=T.basename(M)==="lib"?T.resolve(M,"../../templates"):T.resolve(M,"../templates");var Ee={_gitignore:".gitignore"},ve=new Set(["node_modules","dist","coverage",".env"]);async function K(e,{template:t,cwd:r=process.cwd()}={}){let s=b.join(r,e);if($.existsSync(s))throw new p(`Folder '${e}' already exists.`);let o=t??await Re(),i=b.join(J,o);if(!$.existsSync(i))throw new p(`Template '${o}' not found at ${i}.`);let n=je(`Creating ${o} project...`).start();try{await $.promises.cp(i,s,{recursive:!0,filter:a=>!ve.has(b.basename(a))});for(let[a,l]of Object.entries(Ee)){let h=b.join(s,a);$.existsSync(h)&&await $.promises.rename(h,b.join(s,l))}await Pe(s,e)}catch(a){throw n.fail("Failed to create project"),await $.promises.rm(s,{recursive:!0,force:!0}),a}n.succeed(`Project '${e}' created`),d.info(x.green(`
3
+ Next steps:`)),d.info(x.cyan(` cd ${e}`)),d.info(x.cyan(" npm install")),d.info(x.cyan(" cp .env.example .env # then fill in the secrets")),d.info(x.cyan(" docker compose up -d # starts the database")),o==="prisma"&&d.info(x.cyan(" npm run db:migrate")),d.info(x.cyan(" npm run dev")),d.info(x.cyan(" npm test"))}async function Re(){return Se({message:"Select a project template:",choices:[{name:"Mongoose (MongoDB)",value:"mongoose"},{name:"Prisma (PostgreSQL)",value:"prisma"}]})}async function Pe(e,t){let r=b.join(e,"package.json");if(!$.existsSync(r))return;let s=JSON.parse(await $.promises.readFile(r,"utf-8"));s.name=t,await $.promises.writeFile(r,`${JSON.stringify(s,null,2)}
4
+ `);let o=b.join(e,"package-lock.json");if(!$.existsSync(o))return;let i=JSON.parse(await $.promises.readFile(o,"utf-8"));i.name=t;let n=i.packages?.[""];n&&(n.name=t),await $.promises.writeFile(o,`${JSON.stringify(i,null,2)}
5
+ `)}import v from"chalk";import w from"fs";import g from"path";var j=(e,t,r)=>{let s=`typeof ${t}_validations.${r}`,o=`type ${e} = z.infer<${s}>;`;return o.length<=100?o:`type ${e} = z.infer<
6
+ ${s}
7
+ >;`},R=(e,t)=>{let r=` const result = await ${e}${t.join("")};`;return r.length<=100?r:` const result = await ${e}
8
+ ${t.map(s=>` ${s}`).join(`
9
+ `)};`};var P=(e,t)=>{let r=`await ${e}_service.${t}(req);`,s=` const result = ${r}`;return s.length<=100?s:` const result =
10
+ ${r}`},Z=e=>`import catchAsync from '../../utils/catch_async.js';
11
+ import manageResponse from '../../utils/manage_response.js';
12
+ import { ${e}_service } from './${e}.service.js';
13
+
14
+ const get_all_${e} = catchAsync(async (req, res) => {
15
+ ${P(e,`get_all_${e}_from_db`)}
16
+ manageResponse(res, {
17
+ success: true,
18
+ statusCode: 200,
19
+ message: 'All ${e} fetched successfully.',
20
+ data: result,
21
+ meta: {},
22
+ });
23
+ });
24
+
25
+ const get_single_${e} = catchAsync(async (req, res) => {
26
+ ${P(e,`get_single_${e}_from_db`)}
27
+ manageResponse(res, {
28
+ success: true,
29
+ statusCode: 200,
30
+ message: 'Single ${e} fetched successfully.',
31
+ data: result,
32
+ meta: {},
33
+ });
34
+ });
35
+
36
+ const create_${e} = catchAsync(async (req, res) => {
37
+ ${P(e,`create_${e}_into_db`)}
38
+ manageResponse(res, {
39
+ success: true,
40
+ statusCode: 201,
41
+ message: '${e} created successfully.',
42
+ data: result,
43
+ meta: {},
44
+ });
45
+ });
46
+
47
+ const update_${e} = catchAsync(async (req, res) => {
48
+ ${P(e,`update_${e}_into_db`)}
49
+ manageResponse(res, {
50
+ success: true,
51
+ statusCode: 200,
52
+ message: '${e} updated successfully.',
53
+ data: result,
54
+ meta: {},
55
+ });
56
+ });
57
+
58
+ const delete_${e} = catchAsync(async (req, res) => {
59
+ ${P(e,`delete_${e}_from_db`)}
60
+ manageResponse(res, {
61
+ success: true,
62
+ statusCode: 200,
63
+ message: '${e} deleted successfully.',
64
+ data: result,
65
+ meta: {},
66
+ });
67
+ });
68
+
69
+ export const ${e}_controller = {
70
+ get_all_${e},
71
+ get_single_${e},
72
+ create_${e},
73
+ update_${e},
74
+ delete_${e},
75
+ };
76
+ `;var q=(e,t)=>{let r=`router.${e}(${t.join(", ")});`;return r.length<=100?r:`router.${e}(
77
+ ${t.map(s=>` ${s},`).join(`
78
+ `)}
79
+ );`},Q=(e,{auth:t=!1}={})=>{let r=t?["auth()"]:[],s=a=>`RequestValidator(${e}_validations.${a}_${e})`,o=a=>`${e}_controller.${a}_${e}`;return`import { Router } from 'express';
80
+ ${t?`import auth from '../../middlewares/auth.js';
81
+ `:""}import RequestValidator from '../../middlewares/request_validator.js';
82
+ import { ${e}_controller } from './${e}.controller.js';
83
+ import { ${e}_validations } from './${e}.validation.js';
84
+
85
+ const router = Router();
86
+
87
+ ${t?"// every route needs a signed-in user; use auth('ADMIN') to allow admins only":`// public routes: protect them with auth() from '../../middlewares/auth.js', e.g.
88
+ // router.post('/', auth('ADMIN'), ...), or generate the module with --auth`}
89
+ ${q("get",["'/'",...r,o("get_all")])}
90
+ ${q("post",["'/'",...r,s("create"),o("create")])}
91
+ ${q("get",["'/:id'",...r,o("get_single")])}
92
+ ${q("patch",["'/:id'",...r,s("update"),o("update")])}
93
+ ${q("delete",["'/:id'",...r,o("delete")])}
94
+
95
+ export default router;
96
+ `};var L=`{
97
+ name: 'id',
98
+ in: 'path',
99
+ required: true,
100
+ schema: { type: 'string' },
101
+ }`,X=e=>`export const ${e}SwaggerDocs = {
102
+ '/api/${e}': {
103
+ post: {
104
+ tags: ['${e}'],
105
+ summary: 'Create new ${e}',
106
+ description: '',
107
+ requestBody: {
108
+ required: true,
109
+ content: {
110
+ 'application/json': {
111
+ example: JSON.stringify({ name: 'Example ${e}' }),
112
+ },
113
+ },
114
+ },
115
+ responses: {
116
+ 201: { description: '${e} created successfully' },
117
+ 500: { description: 'Validation error or internal server error' },
118
+ },
119
+ },
120
+ get: {
121
+ tags: ['${e}'],
122
+ summary: 'Get all ${e}',
123
+ description: '',
124
+ parameters: [
125
+ {
126
+ name: 'page',
127
+ in: 'query',
128
+ required: false,
129
+ schema: { type: 'number' },
130
+ },
131
+ {
132
+ name: 'limit',
133
+ in: 'query',
134
+ required: false,
135
+ schema: { type: 'number' },
136
+ },
137
+ ],
138
+ responses: {
139
+ 200: { description: '${e} fetched successfully' },
140
+ 401: { description: 'unauthorized' },
141
+ },
142
+ },
143
+ },
144
+
145
+ '/api/${e}/{id}': {
146
+ get: {
147
+ tags: ['${e}'],
148
+ summary: 'Get single ${e}',
149
+ description: '',
150
+ parameters: [
151
+ ${L},
152
+ ],
153
+ responses: {
154
+ 200: { description: '${e} fetched successfully' },
155
+ 401: { description: 'unauthorized' },
156
+ },
157
+ },
158
+ patch: {
159
+ tags: ['${e}'],
160
+ summary: 'Update ${e}',
161
+ description: '',
162
+ parameters: [
163
+ ${L},
164
+ ],
165
+ requestBody: {
166
+ required: true,
167
+ content: {
168
+ 'application/json': {
169
+ example: JSON.stringify({ name: 'Example ${e}' }),
170
+ },
171
+ },
172
+ },
173
+ responses: {
174
+ 200: { description: '${e} updated successfully' },
175
+ 500: { description: 'Validation error or internal server error' },
176
+ },
177
+ },
178
+ delete: {
179
+ tags: ['${e}'],
180
+ summary: 'Delete ${e}',
181
+ description: '',
182
+ parameters: [
183
+ ${L},
184
+ ],
185
+ responses: {
186
+ 200: { description: '${e} deleted successfully' },
187
+ 401: { description: 'unauthorized' },
188
+ },
189
+ },
190
+ },
191
+ };
192
+ `;var Y=e=>`import { z } from 'zod';
193
+
194
+ // \`name\` is an example field: rename it or add yours here, in the database schema
195
+ // and in tests/routes/${e}.test.ts. Fields that aren't listed here are removed
196
+ // from req.body before it reaches the service.
197
+ const create_${e} = z.object({
198
+ name: z.string('Name is required').trim().min(1).max(200),
199
+ });
200
+
201
+ const update_${e} = create_${e}.partial();
202
+
203
+ export const ${e}_validations = {
204
+ create_${e},
205
+ update_${e},
206
+ };
207
+ `;var ee=e=>`// the fields of a ${e}; \`name\` is an example, keep it in sync with ${e}.validation.ts
208
+ export type T_${e} = {
209
+ name: string;
210
+ };
211
+ `;var te=e=>`import { Schema, model } from 'mongoose';
212
+ import type { T_${e} } from './${e}.interface.js';
213
+
214
+ const schema = new Schema<T_${e}>(
215
+ {
216
+ name: { type: String, required: true, trim: true },
217
+ },
218
+ { timestamps: true, versionKey: false },
219
+ );
220
+
221
+ export const ${e}_model = model('${e}', schema);
222
+ `;var re=e=>`import type { Request } from 'express';
223
+ import type { z } from 'zod';
224
+ import { AppError } from '../../utils/app_error.js';
225
+ import { ${e}_model } from './${e}.schema.js';
226
+ import type { ${e}_validations } from './${e}.validation.js';
227
+
228
+ // req.body was already parsed by RequestValidator (see ${e}.route.ts), so it only
229
+ // holds the fields of these schemas: add a field there before the service can use it
230
+ ${j("CreatePayload",e,`create_${e}`)}
231
+ ${j("UpdatePayload",e,`update_${e}`)}
232
+
233
+ const notFound = () => new AppError('${e} not found', 404);
234
+
235
+ const get_all_${e}_from_db = async (_req: Request) => {
236
+ // define your own logic here
237
+ ${R(`${e}_model`,[".find()",".lean()"])}
238
+ return result;
239
+ };
240
+
241
+ const get_single_${e}_from_db = async (req: Request) => {
242
+ // define your own logic here
243
+ const { id } = req.params;
244
+ ${R(`${e}_model`,[".findById(id)",".lean()"])}
245
+ if (!result) throw notFound();
246
+ return result;
247
+ };
248
+
249
+ const create_${e}_into_db = async (req: Request) => {
250
+ // define your own logic here
251
+ const payload: CreatePayload = req.body;
252
+ const result = await ${e}_model.create(payload);
253
+ return result;
254
+ };
255
+
256
+ const update_${e}_into_db = async (req: Request) => {
257
+ // define your own logic here
258
+ const { id } = req.params;
259
+ const payload: UpdatePayload = req.body;
260
+ ${R(`${e}_model`,[".findByIdAndUpdate(id, payload, { returnDocument: 'after' })",".lean()"])}
261
+ if (!result) throw notFound();
262
+ return result;
263
+ };
264
+
265
+ const delete_${e}_from_db = async (req: Request) => {
266
+ // define your own logic here
267
+ const { id } = req.params;
268
+ ${R(`${e}_model`,[".findByIdAndDelete(id)",".lean()"])}
269
+ if (!result) throw notFound();
270
+ return result;
271
+ };
272
+
273
+ export const ${e}_service = {
274
+ get_all_${e}_from_db,
275
+ get_single_${e}_from_db,
276
+ create_${e}_into_db,
277
+ update_${e}_into_db,
278
+ delete_${e}_from_db,
279
+ };
280
+ `;var se=(e,{auth:t=!1}={})=>`import { Types } from 'mongoose';
281
+ import request from 'supertest';
282
+ import { ${t?"beforeEach, describe, expect, it":"describe, expect, it"} } from 'vitest';
283
+ import app from '../../src/app.js';
284
+ import { ${e}_model } from '../../src/app/modules/${e}/${e}.schema.js';
285
+ ${t?`import { bearer, createAccount } from '../helpers/fixtures.js';
286
+ `:""}
287
+ // Starter tests for the generated CRUD routes: extend them with your own rules.
288
+ const BASE = '/api/${e}';
289
+ const missingId = new Types.ObjectId().toString();
290
+
291
+ // Valid request bodies, matching ${e}.validation.ts. Update them with your fields.
292
+ const createPayload = { name: 'Example ${e}' };
293
+ const updatePayload = { name: 'Updated ${e}' };
294
+
295
+ ${t?`let token = '';
296
+
297
+ beforeEach(async () => {
298
+ token = bearer(await createAccount());
299
+ });
300
+
301
+ /** Requests as a signed-in user. */
302
+ const api = () => request.agent(app).set('Authorization', token);`:"const api = () => request(app);"}
303
+
304
+ /** Inserts one directly in the database, for the read / update / delete tests. */
305
+ const insert = () => ${e}_model.create(createPayload);
306
+
307
+ describe(BASE, () => {${t?"\n it.each([\n ['get', BASE],\n ['post', BASE],\n ['get', `${BASE}/${missingId}`],\n ['patch', `${BASE}/${missingId}`],\n ['delete', `${BASE}/${missingId}`],\n ] as const)('%s %s needs a signed-in user', async (method, url) => {\n const res = await request(app)[method](url);\n\n expect(res.status).toBe(401);\n });\n":""}
308
+ it('POST / creates one', async () => {
309
+ const res = await api().post(BASE).send(createPayload);
310
+
311
+ expect(res.status).toBe(201);
312
+ expect(res.body.data).toMatchObject(createPayload);
313
+ expect(await ${e}_model.countDocuments()).toBe(1);
314
+ });
315
+
316
+ it('POST / rejects an invalid body', async () => {
317
+ const res = await api().post(BASE).send({});
318
+
319
+ expect(res.status).toBe(400);
320
+ expect(await ${e}_model.countDocuments()).toBe(0);
321
+ });
322
+
323
+ it('GET / lists them', async () => {
324
+ await insert();
325
+
326
+ const res = await api().get(BASE);
327
+
328
+ expect(res.status).toBe(200);
329
+ expect(res.body.data).toHaveLength(1);
330
+ });
331
+
332
+ it('GET /:id returns one', async () => {
333
+ const doc = await insert();
334
+
335
+ const res = await api().get(\`\${BASE}/\${doc._id}\`);
336
+
337
+ expect(res.status).toBe(200);
338
+ expect(res.body.data).toMatchObject({ _id: String(doc._id), ...createPayload });
339
+ });
340
+
341
+ it('GET /:id is 404 when it does not exist', async () => {
342
+ const res = await api().get(\`\${BASE}/\${missingId}\`);
343
+
344
+ expect(res.status).toBe(404);
345
+ });
346
+
347
+ it('GET /:id is 400 for a malformed id', async () => {
348
+ const res = await api().get(\`\${BASE}/not-an-id\`);
349
+
350
+ expect(res.status).toBe(400);
351
+ });
352
+
353
+ it('PATCH /:id updates it', async () => {
354
+ const doc = await insert();
355
+
356
+ const res = await api().patch(\`\${BASE}/\${doc._id}\`).send(updatePayload);
357
+
358
+ expect(res.status).toBe(200);
359
+ const stored = await ${e}_model.findById(doc._id).lean();
360
+ expect(stored).toMatchObject(updatePayload);
361
+ });
362
+
363
+ it('PATCH /:id is 404 when it does not exist', async () => {
364
+ const res = await api().patch(\`\${BASE}/\${missingId}\`).send(updatePayload);
365
+
366
+ expect(res.status).toBe(404);
367
+ });
368
+
369
+ it('DELETE /:id deletes it', async () => {
370
+ const doc = await insert();
371
+
372
+ const res = await api().delete(\`\${BASE}/\${doc._id}\`);
373
+
374
+ expect(res.status).toBe(200);
375
+ const stillThere = await ${e}_model.exists({ _id: doc._id });
376
+ expect(stillThere).toBeNull();
377
+ });
378
+
379
+ it('DELETE /:id is 404 when it does not exist', async () => {
380
+ const res = await api().delete(\`\${BASE}/\${missingId}\`);
381
+
382
+ expect(res.status).toBe(404);
383
+ });
384
+ });
385
+ `;var oe=e=>e.charAt(0).toUpperCase()+e.slice(1);var ne=e=>`model ${oe(e)} {
386
+ id String @id @default(uuid())
387
+ // example field, keep it in sync with ${e}.validation.ts
388
+ name String
389
+ createdAt DateTime @default(now())
390
+ updatedAt DateTime @updatedAt
391
+ }
392
+ `;var ie=e=>`import type { Request } from 'express';
393
+ import type { z } from 'zod';
394
+ import { prisma } from '../../lib/prisma.js';
395
+ import { AppError } from '../../utils/app_error.js';
396
+ import type { ${e}_validations } from './${e}.validation.js';
397
+
398
+ // req.body was already parsed by RequestValidator (see ${e}.route.ts), so it only
399
+ // holds the fields of these schemas: add a field there before the service can use it
400
+ ${j("CreatePayload",e,`create_${e}`)}
401
+ ${j("UpdatePayload",e,`update_${e}`)}
402
+
403
+ const get_all_${e}_from_db = async (_req: Request) => {
404
+ // define your own logic here
405
+ const result = await prisma.${e}.findMany();
406
+ return result;
407
+ };
408
+
409
+ const get_single_${e}_from_db = async (req: Request) => {
410
+ // define your own logic here
411
+ const { id } = req.params as { id: string };
412
+ const result = await prisma.${e}.findUnique({
413
+ where: { id },
414
+ });
415
+ if (!result) throw new AppError('${e} not found', 404);
416
+ return result;
417
+ };
418
+
419
+ const create_${e}_into_db = async (req: Request) => {
420
+ // define your own logic here
421
+ const payload: CreatePayload = req.body;
422
+ const result = await prisma.${e}.create({
423
+ data: payload,
424
+ });
425
+ return result;
426
+ };
427
+
428
+ // update / delete of a missing record throw Prisma's P2025, which the global
429
+ // error handler turns into a 404
430
+ const update_${e}_into_db = async (req: Request) => {
431
+ // define your own logic here
432
+ const { id } = req.params as { id: string };
433
+ const payload: UpdatePayload = req.body;
434
+ const result = await prisma.${e}.update({
435
+ where: { id },
436
+ data: payload,
437
+ });
438
+ return result;
439
+ };
440
+
441
+ const delete_${e}_from_db = async (req: Request) => {
442
+ // define your own logic here
443
+ const { id } = req.params as { id: string };
444
+ const result = await prisma.${e}.delete({
445
+ where: { id },
446
+ });
447
+ return result;
448
+ };
449
+
450
+ export const ${e}_service = {
451
+ get_all_${e}_from_db,
452
+ get_single_${e}_from_db,
453
+ create_${e}_into_db,
454
+ update_${e}_into_db,
455
+ delete_${e}_from_db,
456
+ };
457
+ `;var ae=(e,{auth:t=!1}={})=>`import { Prisma } from '@prisma/client';
458
+ import request from 'supertest';
459
+ import { ${t?"beforeEach, describe, expect, it":"describe, expect, it"} } from 'vitest';
460
+ import app from '../../src/app.js';
461
+ ${t?`import { bearer, makeAccount } from '../helpers/fixtures.js';
462
+ `:""}import { prismaMock } from '../helpers/prisma.js';
463
+
464
+ // Starter tests for the generated CRUD routes: extend them with your own rules.
465
+ const BASE = '/api/${e}';
466
+ const db = prismaMock.${e};
467
+
468
+ // Valid request bodies, matching ${e}.validation.ts. Update them with your fields.
469
+ const createPayload = { name: 'Example ${e}' };
470
+ const updatePayload = { name: 'Updated ${e}' };
471
+
472
+ // a row as the database would return it
473
+ const row = {
474
+ id: '9b2f3c1e-7a41-4c8e-9d6b-2f4e8a1c5b70',
475
+ ...createPayload,
476
+ createdAt: new Date('2025-01-01'),
477
+ updatedAt: new Date('2025-01-01'),
478
+ };
479
+ // ...and as it looks in a JSON response
480
+ const json = JSON.parse(JSON.stringify(row));
481
+
482
+ const recordNotFound = () =>
483
+ new Prisma.PrismaClientKnownRequestError('Record not found', {
484
+ code: 'P2025',
485
+ clientVersion: 'test',
486
+ });
487
+
488
+ ${t?`const account = makeAccount();
489
+
490
+ beforeEach(() => {
491
+ // the auth middleware looks the signed-in account up
492
+ prismaMock.account.findUnique.mockResolvedValue(account);
493
+ });
494
+
495
+ /** Requests as a signed-in user. */
496
+ const api = () => request.agent(app).set('Authorization', bearer(account));`:"const api = () => request(app);"}
497
+
498
+ describe(BASE, () => {${t?"\n it.each([\n ['get', BASE],\n ['post', BASE],\n ['get', `${BASE}/${row.id}`],\n ['patch', `${BASE}/${row.id}`],\n ['delete', `${BASE}/${row.id}`],\n ] as const)('%s %s needs a signed-in user', async (method, url) => {\n const res = await request(app)[method](url);\n\n expect(res.status).toBe(401);\n });\n":""}
499
+ it('POST / creates one', async () => {
500
+ db.create.mockResolvedValue(row);
501
+
502
+ const res = await api().post(BASE).send(createPayload);
503
+
504
+ expect(res.status).toBe(201);
505
+ expect(res.body.data).toEqual(json);
506
+ expect(db.create).toHaveBeenCalledWith({ data: createPayload });
507
+ });
508
+
509
+ it('POST / rejects an invalid body', async () => {
510
+ const res = await api().post(BASE).send({});
511
+
512
+ expect(res.status).toBe(400);
513
+ expect(db.create).not.toHaveBeenCalled();
514
+ });
515
+
516
+ it('GET / lists them', async () => {
517
+ db.findMany.mockResolvedValue([row]);
518
+
519
+ const res = await api().get(BASE);
520
+
521
+ expect(res.status).toBe(200);
522
+ expect(res.body.data).toEqual([json]);
523
+ });
524
+
525
+ it('GET /:id returns one', async () => {
526
+ db.findUnique.mockResolvedValue(row);
527
+
528
+ const res = await api().get(\`\${BASE}/\${row.id}\`);
529
+
530
+ expect(res.status).toBe(200);
531
+ expect(res.body.data).toEqual(json);
532
+ expect(db.findUnique).toHaveBeenCalledWith({ where: { id: row.id } });
533
+ });
534
+
535
+ it('GET /:id is 404 when it does not exist', async () => {
536
+ db.findUnique.mockResolvedValue(null);
537
+
538
+ const res = await api().get(\`\${BASE}/\${row.id}\`);
539
+
540
+ expect(res.status).toBe(404);
541
+ });
542
+
543
+ it('PATCH /:id updates it', async () => {
544
+ db.update.mockResolvedValue({ ...row, ...updatePayload });
545
+
546
+ const res = await api().patch(\`\${BASE}/\${row.id}\`).send(updatePayload);
547
+
548
+ expect(res.status).toBe(200);
549
+ expect(db.update).toHaveBeenCalledWith({ where: { id: row.id }, data: updatePayload });
550
+ });
551
+
552
+ it('PATCH /:id is 404 when it does not exist', async () => {
553
+ db.update.mockRejectedValue(recordNotFound());
554
+
555
+ const res = await api().patch(\`\${BASE}/\${row.id}\`).send(updatePayload);
556
+
557
+ expect(res.status).toBe(404);
558
+ });
559
+
560
+ it('DELETE /:id deletes it', async () => {
561
+ db.delete.mockResolvedValue(row);
562
+
563
+ const res = await api().delete(\`\${BASE}/\${row.id}\`);
564
+
565
+ expect(res.status).toBe(200);
566
+ expect(db.delete).toHaveBeenCalledWith({ where: { id: row.id } });
567
+ });
568
+
569
+ it('DELETE /:id is 404 when it does not exist', async () => {
570
+ db.delete.mockRejectedValue(recordNotFound());
571
+
572
+ const res = await api().delete(\`\${BASE}/\${row.id}\`);
573
+
574
+ expect(res.status).toBe(404);
575
+ });
576
+ });
577
+ `;var qe=/^import\s[\s\S]*?from\s*(['"])[^'"]+\1;?[ \t]*$|^import\s*(['"])[^'"]+\2;?[ \t]*$/gm;function I(e){return{quote:e.match(/^import[\s\S]*?(['"])/m)?.[1]==="'"?"'":'"',jsExtension:/from\s*['"]\.{1,2}\/[^'"]+\.js['"]/.test(e)}}function C(e,t){let r=-1;for(let s of e.matchAll(qe))r=s.index+s[0].length;return r===-1?`${t}
578
+ ${e}`:`${e.slice(0,r)}
579
+ ${t}${e.slice(r)}`}var k={"[":"]","{":"}","(":")"};function ce(e,t){let r=e[t],s=e[t+1];if(r==="/"&&s==="/"){let o=e.indexOf(`
580
+ `,t);return o===-1?e.length-1:o}if(r==="/"&&s==="*"){let o=e.indexOf("*/",t+2);return o===-1?-1:o+1}if(r==="'"||r==='"'){let o=t+1;for(;o<e.length&&e[o]!==r&&e[o]!==`
581
+ `;)o+=e[o]==="\\"?2:1;return e[o]===r?o:-1}if(r==="`"){let o=t+1;for(;o<e.length;)if(e[o]==="\\")o+=2;else{if(e[o]==="`")return o;if(e[o]==="$"&&e[o+1]==="{"){let i=D(e,o+2,["}"],!0);if(i===-1)return-1;o=i+1}else o++}return-1}return null}function D(e,t,r,s){for(let o=t;o<e.length;o++){let i=ce(e,o);if(i!==null){if(i===-1)return-1;o=i;continue}let n=e[o];if(n in k)r.push(k[n]);else if(n==="]"||n==="}"||n===")"){if(r.pop()!==n)return-1;if(s&&r.length===0)return o}}return!s&&r.length===0?e.length:-1}function V(e,t,r){return D(e,t,[r],!0)}function E(e){return D(e,0,[],!1)===e.length}function de(e,t,r){let s=[];for(let o=t;o<r;o++){let i=ce(e,o);if(i!==null){if(i===-1)return s;o=i;continue}let n=e[o];if(n in k){let a=D(e,o+1,[k[n]],!0);if(a===-1)return s;n==="{"&&s.push(e.slice(o,a+1)),o=a}}return s}function O(e,t,r,s){let o=e.match(t);if(o?.index===void 0)return null;let i=o.index+o[0].length,n=V(e,i,r);if(n===-1)return null;let a=e.slice(i,n),l=a.match(/\n([ \t]*)$/)?.[1]??"",h=a.match(/\n([ \t]+)\S/)?.[1]??`${l} `,c=a.trimEnd(),_=c.length>0&&!/[,[{]$/.test(c),f=`${c}${_?",":""}
582
+ ${h}${s}
583
+ ${l}`;return e.slice(0,i)+f+e.slice(n)}function F(e,t){let r=e.includes(`\r
584
+ `),s=t(r?e.replace(/\r\n/g,`
585
+ `):e);return!r||s.status!=="updated"?s:{status:"updated",content:s.content.replace(/\n/g,`\r
586
+ `)}}var pe=/const moduleRoutes\s*=\s*\[/;function H(e){let t=[],r=e.match(pe);if(r?.index!==void 0){let s=r.index+r[0].length,o=V(e,s,"]");if(o!==-1)for(let i of de(e,s,o)){let n=/\bpath\s*:\s*(['"`])([^'"`]*)\1/.exec(i)?.[2];if(n===void 0)continue;let a=/\broute\s*:\s*([A-Za-z_$][\w$]*)\s*[,}]/.exec(i)?.[1]??null;t.push({path:n,route:a})}}for(let s of e.matchAll(/\.use\(\s*(['"`])(\/[^'"`]*)\1\s*,\s*([A-Za-z_$][\w$]*)?/g))t.push({path:s[2],route:s[3]??null});return t}function le(e,t){return F(e,r=>Ae(r,t))}function Ae(e,t){let{quote:r,jsExtension:s}=I(e),o=`./app/modules/${t}/${t}.route`,i=`${t}Route`,n=l=>H(l).filter(h=>h.route===i).length,a=e;if(a.includes(o)||(a=C(a,`import ${i} from ${r}${o}${s?".js":""}${r};`)),n(a)===0){let l=O(a,pe,"]",`{ path: ${r}/${t}${r}, route: ${i} },`);if(l===null)return{status:"failed",reason:"could not find `const moduleRoutes = [...]`"};a=l}return a===e?{status:"unchanged"}:E(e)&&!E(a)||n(a)!==1?{status:"failed",reason:"the edited file didn't look right"}:{status:"updated",content:a}}function ue(e,t){return F(e,r=>Be(r,t))}function Be(e,t){let{quote:r,jsExtension:s}=I(e),o=`./app/modules/${t}/${t}.swagger`,i=`${t}SwaggerDocs`,n=e;if(n.includes(o)||(n=C(n,`import { ${i} } from ${r}${o}${s?".js":""}${r};`)),!n.includes(`...${i}`)){let l=O(n,/paths:\s*\{/,"}",`...${i},`);if(l===null)return{status:"failed",reason:"could not find `paths: { ... }`"};n=l}if(n===e)return{status:"unchanged"};let a=n.match(new RegExp(`\\.\\.\\.${i}\\b`,"g"))?.length??0;return E(e)&&!E(n)||a!==1?{status:"failed",reason:"the edited file didn't look right"}:{status:"updated",content:n}}import{randomBytes as Te}from"crypto";import u from"fs";import S from"path";function me(e,t,r={}){let s=new Map,o=[],i=[],n=e.filter(c=>!u.existsSync(c.path)||u.readFileSync(c.path,"utf-8")!==c.content);for(let c of n)u.existsSync(c.path)&&s.set(c.path,u.readFileSync(c.path,"utf-8"));let a=ke(s,t,r.backupDir),l=[];try{for(let c of n)s.has(c.path)||(i.push(...Ie(S.dirname(c.path))),u.mkdirSync(S.dirname(c.path),{recursive:!0}),o.push(c.path)),fe(c.path,c.content),s.has(c.path)&&l.push(c.path)}catch(c){let _=f=>{try{f()}catch{}};for(let f of l)_(()=>fe(f,s.get(f)));for(let f of o)_(()=>u.rmSync(f,{force:!0}));for(let f of[...new Set(i)].sort((m,N)=>N.length-m.length))_(()=>{u.existsSync(f)&&u.readdirSync(f).length===0&&u.rmdirSync(f)});throw c}let h=c=>S.relative(t,c);for(let c of o)d.success(`${h(c)} created`);for(let c of s.keys())d.success(`${h(c)} updated`);return{created:o,updated:[...s.keys()],backupDir:a}}function fe(e,t){let r=u.existsSync(e)?u.statSync(e):null;r&&u.accessSync(e,u.constants.W_OK);let s=`${e}.${Te(6).toString("hex")}.tmp`;try{u.writeFileSync(s,t,r?{mode:r.mode}:{}),u.renameSync(s,e)}catch(o){throw u.rmSync(s,{force:!0}),o}}function ke(e,t,r){if(!r||e.size===0)return null;for(let[s,o]of e){let i=S.join(r,S.relative(t,s));u.mkdirSync(S.dirname(i),{recursive:!0}),u.writeFileSync(i,o)}return r}function Ie(e){let t=[],r=e;for(;!u.existsSync(r);){t.unshift(r);let s=S.dirname(r);if(s===r)break;r=s}return t}import ge from"fs";import Ce from"path";var A=["mongoose","prisma"],G=".projectrc.json",W=e=>typeof e=="string"&&A.includes(e);function he(e){let t=Ce.join(e,G);if(!ge.existsSync(t))throw new p(`${G} not found. Run this command from the root of a project created with exp-node-server.`);let r;try{r=JSON.parse(ge.readFileSync(t,"utf-8"))}catch{throw new p(`${G} is not valid JSON.`)}let s=r?.database;if(!W(s))throw new p(`Invalid "database" in ${G}. Expected one of: ${A.join(", ")}.`);return s}async function ye(e,{cwd:t=process.cwd(),force:r=!1,auth:s=!1}={}){let o=he(t),i=g.join(t,"src/app/modules",e);if(w.existsSync(i)&&!r)throw new p(`Module '${e}' already exists at ${g.relative(t,i)}. Use --force to overwrite it.`);Fe(t,e);let n=(m,N)=>({path:g.join(i,`${e}.${m}`),content:N}),a=[n("controller.ts",Z(e)),n("route.ts",Q(e,{auth:s})),n("validation.ts",Y(e)),n("swagger.ts",X(e))];o==="mongoose"?a.push(n("interface.ts",ee(e)),n("schema.ts",te(e)),n("service.ts",re(e))):a.push(n("service.ts",ie(e)),{path:g.join(t,"prisma/schema",`${e}.prisma`),content:ne(e)});let l=g.join(t,"tests/routes",`${e}.test.ts`),h=De.some(m=>w.existsSync(g.join(t,m)));h&&a.push({path:l,content:o==="mongoose"?se(e,{auth:s}):ae(e,{auth:s})});let c=[$e(g.join(t,"src/routes.ts"),m=>le(m,e),"Route registered"),$e(g.join(t,"src/swaggerOptions.ts"),m=>ue(m,e),"Swagger docs registered")];for(let{file:m}of c)m&&a.push(m);let _=r?g.join(t,Oe,new Date().toISOString().replace(/[:.]/g,"-")):void 0,f=me(a,t,{backupDir:_});for(let{report:m}of c)m();h||d.info(v.dim(" no vitest config found, skipped tests/routes test")),f.backupDir&&d.warn(`${f.updated.length} existing file(s) were overwritten. Previous versions: ${g.relative(t,f.backupDir)}`),o==="prisma"&&(d.info(v.blue(`
587
+ Next, run:`)),d.info(v.yellow(" npx prisma generate && npx prisma migrate dev"))),h&&d.info(v.blue(`
588
+ Test it with: npm test -- tests/routes/${e}.test.ts`)),d.info(v.green(`
589
+ Module '${e}' created successfully`))}var De=["vitest.config.ts","vitest.config.mts","vitest.config.js"],Oe=".exp-node-server/backups";function Fe(e,t){let r=g.join(e,"src/routes.ts");if(w.existsSync(r)){let i=H(w.readFileSync(r,"utf-8")).find(n=>n.path===`/${t}`&&n.route!==`${t}Route`);if(i)throw new p(`The path '/${t}' is already used by ${i.route??"another router"} in src/routes.ts. Pick another module name.`)}let s=g.join(e,"src/app/modules");if(!w.existsSync(s))return;let o=new RegExp(`['"\`]/api/${t}(/[^'"\`]*)?['"\`]\\s*:`);for(let i of w.readdirSync(s)){if(i===t)continue;let n=g.join(s,i,`${i}.swagger.ts`);if(w.existsSync(n)&&o.test(w.readFileSync(n,"utf-8")))throw new p(`The path '/api/${t}' is already documented by the '${i}' module. Pick another module name.`)}}function $e(e,t,r){let s=g.basename(e);if(!w.existsSync(e))return{file:null,report:()=>d.warn(`${s} not found, skipping registration`)};let o=t(w.readFileSync(e,"utf-8"));switch(o.status){case"updated":return{file:{path:e,content:o.content},report:()=>d.success(`${r} in ${s}`)};case"unchanged":return{file:null,report:()=>d.info(v.dim(` already registered in ${s}`))};case"failed":return{file:null,report:()=>d.warn(`Couldn't update ${s} (${o.reason}). Register it manually.`)}}}function we(e){if(!e)return null;let t=e.trim().toLowerCase().replace(/[-\s]+/g,"_");return/^[a-z][a-z_]*$/.test(t)?t:null}function xe(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 y="exp-node-server",Ne=`
590
+ ${B.bold(y)} - scaffold Express + TypeScript APIs with Mongoose or Prisma
591
+
592
+ ${B.bold("Usage")}
593
+ npx ${y} create <project-name> [--template <mongoose|prisma>]
594
+ npx ${y} generate <module-name> [--auth] [--force]
595
+
596
+ ${B.bold("Commands")}
597
+ create, -c <name> Create a new project from a template
598
+ generate, -g <name> Generate a module in the current project
599
+
600
+ ${B.bold("Options")}
601
+ -t, --template <name> Template to use with create (${A.join(" | ")})
602
+ -a, --auth Generated routes require a signed-in user (default: public)
603
+ -f, --force Overwrite an existing module when generating
604
+ -h, --help Show this help
605
+ -v, --version Show version
606
+
607
+ ${B.bold("Examples")}
608
+ npx ${y} -c my-api
609
+ npx ${y} create my-api --template prisma
610
+ npx ${y} -g order
611
+ npx ${y} -g order --auth
612
+ `;async function _e(e){let{values:t,positionals:r}=Ue(e);if(t.version){console.log("2.1.0");return}let s=Me(t,r);if(t.help||!s){console.log(Ne);return}if(s.kind==="create"){if(t.auth||t.force)throw new p("--auth and --force only apply to generate.");let i=xe(s.name);if(!i)throw new p(s.name?`Invalid project name '${s.name}'. Use letters, numbers and dashes.`:"Project name required. Example: exp-node-server -c my-api");let{template:n}=t;if(n!==void 0&&!W(n))throw new p(`Unknown template '${n}'. Use one of: ${A.join(", ")}.`);await K(i,{template:n});return}let o=we(s.name);if(!o)throw new p(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 ye(o,{force:t.force??!1,auth:t.auth??!1})}function Ue(e){try{return Ge({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"},auth:{type:"boolean",short:"a"},help:{type:"boolean",short:"h"},version:{type:"boolean",short:"v"}}})}catch(t){let r=t instanceof Error?t.message:String(t);throw new p(`${r}
613
+ Run '${y} --help' for usage.`)}}function Me(e,t){if(e.create!==void 0&&e.generate!==void 0)throw new p("Use either -c (create) or -g (generate), not both.");if((e.create!==void 0||e.generate!==void 0)&&t.length>0)throw new p(`Unexpected argument '${t[0]}'. Run '${y} --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,...o]=t;if(r===void 0)return null;if(o.length>0)throw new p(`Unexpected argument '${o[0]}'. Run '${y} --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 p(`Unknown command '${r}'. Run '${y} --help' for usage.`)}}try{await _e(process.argv.slice(2))}catch(e){e instanceof p?(d.error(e.message),process.exitCode=e.exitCode):e instanceof Error&&e.name==="ExitPromptError"?process.exitCode=130:(d.error("Unexpected error:"),console.error(e),process.exitCode=1)}
package/package.json CHANGED
@@ -1,19 +1,37 @@
1
1
  {
2
2
  "name": "exp-node-server",
3
- "version": "1.1.7",
3
+ "version": "2.1.0",
4
+ "description": "CLI to scaffold Express + TypeScript APIs with Mongoose or Prisma, Zod validation and Swagger docs",
4
5
  "type": "module",
5
6
  "bin": {
6
- "exp-node-server": "./bin/cli.js"
7
+ "exp-node-server": "./dist/cli.js"
7
8
  },
8
9
  "files": [
9
- "bin",
10
- "src",
11
- "templates"
10
+ "dist",
11
+ "templates",
12
+ "!templates/*/node_modules",
13
+ "!templates/*/dist",
14
+ "!templates/*/coverage",
15
+ "!templates/*/.env"
12
16
  ],
17
+ "scripts": {
18
+ "build": "tsup",
19
+ "dev": "tsup --watch",
20
+ "typecheck": "tsc -p .",
21
+ "test": "vitest run",
22
+ "test:watch": "vitest",
23
+ "prepublishOnly": "npm run typecheck && npm test && npm run build"
24
+ },
13
25
  "dependencies": {
14
- "chalk": "^5.3.0",
15
- "ora": "^7.0.1",
16
- "inquirer": "^9.2.7"
26
+ "@inquirer/select": "^5.2.6",
27
+ "chalk": "^5.6.2",
28
+ "ora": "^9.4.1"
29
+ },
30
+ "devDependencies": {
31
+ "@types/node": "^20.19.43",
32
+ "tsup": "^8.5.1",
33
+ "typescript": "^7.0.2",
34
+ "vitest": "^3.2.7"
17
35
  },
18
36
  "keywords": [
19
37
  "express",
@@ -28,7 +46,9 @@
28
46
  "exp-node",
29
47
  "server"
30
48
  ],
49
+ "author": "Abumahid (https://abumahid.me)",
50
+ "homepage": "https://www.npmjs.com/package/exp-node-server",
31
51
  "engines": {
32
- "node": ">=20.0.0"
52
+ "node": ">=20.17.0"
33
53
  }
34
54
  }