exp-node-server 2.0.0 → 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.
- package/README.md +83 -3
- package/dist/cli.js +191 -103
- package/package.json +1 -1
- package/templates/mongoose/.prettierignore +1 -0
- package/templates/mongoose/_gitignore +4 -1
- package/templates/mongoose/eslint.config.js +1 -1
- package/templates/mongoose/package-lock.json +5 -16
- package/templates/mongoose/package.json +1 -2
- package/templates/mongoose/readme.md +3 -0
- package/templates/mongoose/src/app/queues/agenda.ts +16 -4
- package/templates/mongoose/src/app/queues/email.queue.ts +34 -6
- package/templates/mongoose/src/app/queues/index.ts +10 -6
- package/templates/mongoose/tests/queues/email.queue.test.ts +137 -0
- package/templates/prisma/.prettierignore +2 -1
- package/templates/prisma/_gitignore +4 -1
- package/templates/prisma/eslint.config.js +1 -1
- package/templates/prisma/package-lock.json +5 -16
- package/templates/prisma/package.json +1 -2
- package/templates/prisma/readme.md +9 -0
- package/templates/prisma/src/app/queues/boss.ts +28 -3
- package/templates/prisma/src/app/queues/email.queue.ts +23 -8
- package/templates/prisma/src/app/queues/index.ts +16 -7
- package/templates/prisma/src/app/queues/maintenance.queue.ts +2 -2
- package/templates/prisma/tests/queues/email.queue.test.ts +162 -0
package/dist/cli.js
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
var
|
|
3
|
-
Next steps:`)),
|
|
4
|
-
`)
|
|
5
|
-
|
|
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';
|
|
6
11
|
import manageResponse from '../../utils/manage_response.js';
|
|
7
12
|
import { ${e}_service } from './${e}.service.js';
|
|
8
13
|
|
|
9
14
|
const get_all_${e} = catchAsync(async (req, res) => {
|
|
10
|
-
${
|
|
15
|
+
${P(e,`get_all_${e}_from_db`)}
|
|
11
16
|
manageResponse(res, {
|
|
12
17
|
success: true,
|
|
13
18
|
statusCode: 200,
|
|
@@ -18,7 +23,7 @@ ${_(e,`get_all_${e}_from_db`)}
|
|
|
18
23
|
});
|
|
19
24
|
|
|
20
25
|
const get_single_${e} = catchAsync(async (req, res) => {
|
|
21
|
-
${
|
|
26
|
+
${P(e,`get_single_${e}_from_db`)}
|
|
22
27
|
manageResponse(res, {
|
|
23
28
|
success: true,
|
|
24
29
|
statusCode: 200,
|
|
@@ -29,7 +34,7 @@ ${_(e,`get_single_${e}_from_db`)}
|
|
|
29
34
|
});
|
|
30
35
|
|
|
31
36
|
const create_${e} = catchAsync(async (req, res) => {
|
|
32
|
-
${
|
|
37
|
+
${P(e,`create_${e}_into_db`)}
|
|
33
38
|
manageResponse(res, {
|
|
34
39
|
success: true,
|
|
35
40
|
statusCode: 201,
|
|
@@ -40,7 +45,7 @@ ${_(e,`create_${e}_into_db`)}
|
|
|
40
45
|
});
|
|
41
46
|
|
|
42
47
|
const update_${e} = catchAsync(async (req, res) => {
|
|
43
|
-
${
|
|
48
|
+
${P(e,`update_${e}_into_db`)}
|
|
44
49
|
manageResponse(res, {
|
|
45
50
|
success: true,
|
|
46
51
|
statusCode: 200,
|
|
@@ -51,7 +56,7 @@ ${_(e,`update_${e}_into_db`)}
|
|
|
51
56
|
});
|
|
52
57
|
|
|
53
58
|
const delete_${e} = catchAsync(async (req, res) => {
|
|
54
|
-
${
|
|
59
|
+
${P(e,`delete_${e}_from_db`)}
|
|
55
60
|
manageResponse(res, {
|
|
56
61
|
success: true,
|
|
57
62
|
statusCode: 200,
|
|
@@ -68,29 +73,32 @@ export const ${e}_controller = {
|
|
|
68
73
|
update_${e},
|
|
69
74
|
delete_${e},
|
|
70
75
|
};
|
|
71
|
-
`;var
|
|
76
|
+
`;var q=(e,t)=>{let r=`router.${e}(${t.join(", ")});`;return r.length<=100?r:`router.${e}(
|
|
72
77
|
${t.map(s=>` ${s},`).join(`
|
|
73
78
|
`)}
|
|
74
|
-
);`},
|
|
75
|
-
import
|
|
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';
|
|
76
82
|
import { ${e}_controller } from './${e}.controller.js';
|
|
77
83
|
import { ${e}_validations } from './${e}.validation.js';
|
|
78
84
|
|
|
79
85
|
const router = Router();
|
|
80
86
|
|
|
81
|
-
${
|
|
82
|
-
|
|
83
|
-
${
|
|
84
|
-
${
|
|
85
|
-
${
|
|
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")])}
|
|
86
94
|
|
|
87
95
|
export default router;
|
|
88
|
-
|
|
96
|
+
`};var L=`{
|
|
89
97
|
name: 'id',
|
|
90
98
|
in: 'path',
|
|
91
99
|
required: true,
|
|
92
100
|
schema: { type: 'string' },
|
|
93
|
-
}`,
|
|
101
|
+
}`,X=e=>`export const ${e}SwaggerDocs = {
|
|
94
102
|
'/api/${e}': {
|
|
95
103
|
post: {
|
|
96
104
|
tags: ['${e}'],
|
|
@@ -100,7 +108,7 @@ export default router;
|
|
|
100
108
|
required: true,
|
|
101
109
|
content: {
|
|
102
110
|
'application/json': {
|
|
103
|
-
example: JSON.stringify({
|
|
111
|
+
example: JSON.stringify({ name: 'Example ${e}' }),
|
|
104
112
|
},
|
|
105
113
|
},
|
|
106
114
|
},
|
|
@@ -140,7 +148,7 @@ export default router;
|
|
|
140
148
|
summary: 'Get single ${e}',
|
|
141
149
|
description: '',
|
|
142
150
|
parameters: [
|
|
143
|
-
${
|
|
151
|
+
${L},
|
|
144
152
|
],
|
|
145
153
|
responses: {
|
|
146
154
|
200: { description: '${e} fetched successfully' },
|
|
@@ -152,13 +160,13 @@ export default router;
|
|
|
152
160
|
summary: 'Update ${e}',
|
|
153
161
|
description: '',
|
|
154
162
|
parameters: [
|
|
155
|
-
${
|
|
163
|
+
${L},
|
|
156
164
|
],
|
|
157
165
|
requestBody: {
|
|
158
166
|
required: true,
|
|
159
167
|
content: {
|
|
160
168
|
'application/json': {
|
|
161
|
-
example: JSON.stringify({
|
|
169
|
+
example: JSON.stringify({ name: 'Example ${e}' }),
|
|
162
170
|
},
|
|
163
171
|
},
|
|
164
172
|
},
|
|
@@ -172,7 +180,7 @@ export default router;
|
|
|
172
180
|
summary: 'Delete ${e}',
|
|
173
181
|
description: '',
|
|
174
182
|
parameters: [
|
|
175
|
-
${
|
|
183
|
+
${L},
|
|
176
184
|
],
|
|
177
185
|
responses: {
|
|
178
186
|
200: { description: '${e} deleted successfully' },
|
|
@@ -181,56 +189,75 @@ export default router;
|
|
|
181
189
|
},
|
|
182
190
|
},
|
|
183
191
|
};
|
|
184
|
-
`;var
|
|
192
|
+
`;var Y=e=>`import { z } from 'zod';
|
|
185
193
|
|
|
186
|
-
|
|
187
|
-
|
|
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();
|
|
188
202
|
|
|
189
203
|
export const ${e}_validations = {
|
|
190
204
|
create_${e},
|
|
191
205
|
update_${e},
|
|
192
206
|
};
|
|
193
|
-
`;var
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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';
|
|
197
212
|
import type { T_${e} } from './${e}.interface.js';
|
|
198
213
|
|
|
199
|
-
const schema = new Schema<T_${e}>(
|
|
214
|
+
const schema = new Schema<T_${e}>(
|
|
215
|
+
{
|
|
216
|
+
name: { type: String, required: true, trim: true },
|
|
217
|
+
},
|
|
218
|
+
{ timestamps: true, versionKey: false },
|
|
219
|
+
);
|
|
200
220
|
|
|
201
221
|
export const ${e}_model = model('${e}', schema);
|
|
202
|
-
`;var
|
|
222
|
+
`;var re=e=>`import type { Request } from 'express';
|
|
223
|
+
import type { z } from 'zod';
|
|
203
224
|
import { AppError } from '../../utils/app_error.js';
|
|
204
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}`)}
|
|
205
232
|
|
|
206
233
|
const notFound = () => new AppError('${e} not found', 404);
|
|
207
234
|
|
|
208
235
|
const get_all_${e}_from_db = async (_req: Request) => {
|
|
209
236
|
// define your own logic here
|
|
210
|
-
|
|
237
|
+
${R(`${e}_model`,[".find()",".lean()"])}
|
|
211
238
|
return result;
|
|
212
239
|
};
|
|
213
240
|
|
|
214
241
|
const get_single_${e}_from_db = async (req: Request) => {
|
|
215
242
|
// define your own logic here
|
|
216
243
|
const { id } = req.params;
|
|
217
|
-
|
|
244
|
+
${R(`${e}_model`,[".findById(id)",".lean()"])}
|
|
218
245
|
if (!result) throw notFound();
|
|
219
246
|
return result;
|
|
220
247
|
};
|
|
221
248
|
|
|
222
249
|
const create_${e}_into_db = async (req: Request) => {
|
|
223
250
|
// define your own logic here
|
|
224
|
-
const
|
|
251
|
+
const payload: CreatePayload = req.body;
|
|
252
|
+
const result = await ${e}_model.create(payload);
|
|
225
253
|
return result;
|
|
226
254
|
};
|
|
227
255
|
|
|
228
256
|
const update_${e}_into_db = async (req: Request) => {
|
|
229
257
|
// define your own logic here
|
|
230
258
|
const { id } = req.params;
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
.lean();
|
|
259
|
+
const payload: UpdatePayload = req.body;
|
|
260
|
+
${R(`${e}_model`,[".findByIdAndUpdate(id, payload, { returnDocument: 'after' })",".lean()"])}
|
|
234
261
|
if (!result) throw notFound();
|
|
235
262
|
return result;
|
|
236
263
|
};
|
|
@@ -238,7 +265,7 @@ const update_${e}_into_db = async (req: Request) => {
|
|
|
238
265
|
const delete_${e}_from_db = async (req: Request) => {
|
|
239
266
|
// define your own logic here
|
|
240
267
|
const { id } = req.params;
|
|
241
|
-
|
|
268
|
+
${R(`${e}_model`,[".findByIdAndDelete(id)",".lean()"])}
|
|
242
269
|
if (!result) throw notFound();
|
|
243
270
|
return result;
|
|
244
271
|
};
|
|
@@ -250,34 +277,53 @@ export const ${e}_service = {
|
|
|
250
277
|
update_${e}_into_db,
|
|
251
278
|
delete_${e}_from_db,
|
|
252
279
|
};
|
|
253
|
-
`;var
|
|
280
|
+
`;var se=(e,{auth:t=!1}={})=>`import { Types } from 'mongoose';
|
|
254
281
|
import request from 'supertest';
|
|
255
|
-
import { describe, expect, it } from 'vitest';
|
|
282
|
+
import { ${t?"beforeEach, describe, expect, it":"describe, expect, it"} } from 'vitest';
|
|
256
283
|
import app from '../../src/app.js';
|
|
257
284
|
import { ${e}_model } from '../../src/app/modules/${e}/${e}.schema.js';
|
|
258
|
-
|
|
285
|
+
${t?`import { bearer, createAccount } from '../helpers/fixtures.js';
|
|
286
|
+
`:""}
|
|
287
|
+
// Starter tests for the generated CRUD routes: extend them with your own rules.
|
|
259
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}' };
|
|
260
294
|
|
|
261
|
-
|
|
262
|
-
// ${e}.validation.ts (and protect the routes with auth() once they need it).
|
|
263
|
-
const createPayload = {};
|
|
264
|
-
const updatePayload = {};
|
|
295
|
+
${t?`let token = '';
|
|
265
296
|
|
|
266
|
-
|
|
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. */
|
|
267
305
|
const insert = () => ${e}_model.create(createPayload);
|
|
268
306
|
|
|
269
|
-
describe(BASE, () => {
|
|
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":""}
|
|
270
308
|
it('POST / creates one', async () => {
|
|
271
|
-
const res = await
|
|
309
|
+
const res = await api().post(BASE).send(createPayload);
|
|
272
310
|
|
|
273
311
|
expect(res.status).toBe(201);
|
|
312
|
+
expect(res.body.data).toMatchObject(createPayload);
|
|
274
313
|
expect(await ${e}_model.countDocuments()).toBe(1);
|
|
275
314
|
});
|
|
276
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
|
+
|
|
277
323
|
it('GET / lists them', async () => {
|
|
278
324
|
await insert();
|
|
279
325
|
|
|
280
|
-
const res = await
|
|
326
|
+
const res = await api().get(BASE);
|
|
281
327
|
|
|
282
328
|
expect(res.status).toBe(200);
|
|
283
329
|
expect(res.body.data).toHaveLength(1);
|
|
@@ -286,20 +332,20 @@ describe(BASE, () => {
|
|
|
286
332
|
it('GET /:id returns one', async () => {
|
|
287
333
|
const doc = await insert();
|
|
288
334
|
|
|
289
|
-
const res = await
|
|
335
|
+
const res = await api().get(\`\${BASE}/\${doc._id}\`);
|
|
290
336
|
|
|
291
337
|
expect(res.status).toBe(200);
|
|
292
|
-
expect(res.body.data
|
|
338
|
+
expect(res.body.data).toMatchObject({ _id: String(doc._id), ...createPayload });
|
|
293
339
|
});
|
|
294
340
|
|
|
295
341
|
it('GET /:id is 404 when it does not exist', async () => {
|
|
296
|
-
const res = await
|
|
342
|
+
const res = await api().get(\`\${BASE}/\${missingId}\`);
|
|
297
343
|
|
|
298
344
|
expect(res.status).toBe(404);
|
|
299
345
|
});
|
|
300
346
|
|
|
301
347
|
it('GET /:id is 400 for a malformed id', async () => {
|
|
302
|
-
const res = await
|
|
348
|
+
const res = await api().get(\`\${BASE}/not-an-id\`);
|
|
303
349
|
|
|
304
350
|
expect(res.status).toBe(400);
|
|
305
351
|
});
|
|
@@ -307,7 +353,7 @@ describe(BASE, () => {
|
|
|
307
353
|
it('PATCH /:id updates it', async () => {
|
|
308
354
|
const doc = await insert();
|
|
309
355
|
|
|
310
|
-
const res = await
|
|
356
|
+
const res = await api().patch(\`\${BASE}/\${doc._id}\`).send(updatePayload);
|
|
311
357
|
|
|
312
358
|
expect(res.status).toBe(200);
|
|
313
359
|
const stored = await ${e}_model.findById(doc._id).lean();
|
|
@@ -315,7 +361,7 @@ describe(BASE, () => {
|
|
|
315
361
|
});
|
|
316
362
|
|
|
317
363
|
it('PATCH /:id is 404 when it does not exist', async () => {
|
|
318
|
-
const res = await
|
|
364
|
+
const res = await api().patch(\`\${BASE}/\${missingId}\`).send(updatePayload);
|
|
319
365
|
|
|
320
366
|
expect(res.status).toBe(404);
|
|
321
367
|
});
|
|
@@ -323,7 +369,7 @@ describe(BASE, () => {
|
|
|
323
369
|
it('DELETE /:id deletes it', async () => {
|
|
324
370
|
const doc = await insert();
|
|
325
371
|
|
|
326
|
-
const res = await
|
|
372
|
+
const res = await api().delete(\`\${BASE}/\${doc._id}\`);
|
|
327
373
|
|
|
328
374
|
expect(res.status).toBe(200);
|
|
329
375
|
const stillThere = await ${e}_model.exists({ _id: doc._id });
|
|
@@ -331,19 +377,28 @@ describe(BASE, () => {
|
|
|
331
377
|
});
|
|
332
378
|
|
|
333
379
|
it('DELETE /:id is 404 when it does not exist', async () => {
|
|
334
|
-
const res = await
|
|
380
|
+
const res = await api().delete(\`\${BASE}/\${missingId}\`);
|
|
335
381
|
|
|
336
382
|
expect(res.status).toBe(404);
|
|
337
383
|
});
|
|
338
384
|
});
|
|
339
|
-
`;var
|
|
385
|
+
`;var oe=e=>e.charAt(0).toUpperCase()+e.slice(1);var ne=e=>`model ${oe(e)} {
|
|
340
386
|
id String @id @default(uuid())
|
|
387
|
+
// example field, keep it in sync with ${e}.validation.ts
|
|
388
|
+
name String
|
|
341
389
|
createdAt DateTime @default(now())
|
|
342
390
|
updatedAt DateTime @updatedAt
|
|
343
391
|
}
|
|
344
|
-
`;var
|
|
392
|
+
`;var ie=e=>`import type { Request } from 'express';
|
|
393
|
+
import type { z } from 'zod';
|
|
345
394
|
import { prisma } from '../../lib/prisma.js';
|
|
346
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}`)}
|
|
347
402
|
|
|
348
403
|
const get_all_${e}_from_db = async (_req: Request) => {
|
|
349
404
|
// define your own logic here
|
|
@@ -354,14 +409,19 @@ const get_all_${e}_from_db = async (_req: Request) => {
|
|
|
354
409
|
const get_single_${e}_from_db = async (req: Request) => {
|
|
355
410
|
// define your own logic here
|
|
356
411
|
const { id } = req.params as { id: string };
|
|
357
|
-
const result = await prisma.${e}.findUnique({
|
|
412
|
+
const result = await prisma.${e}.findUnique({
|
|
413
|
+
where: { id },
|
|
414
|
+
});
|
|
358
415
|
if (!result) throw new AppError('${e} not found', 404);
|
|
359
416
|
return result;
|
|
360
417
|
};
|
|
361
418
|
|
|
362
419
|
const create_${e}_into_db = async (req: Request) => {
|
|
363
420
|
// define your own logic here
|
|
364
|
-
const
|
|
421
|
+
const payload: CreatePayload = req.body;
|
|
422
|
+
const result = await prisma.${e}.create({
|
|
423
|
+
data: payload,
|
|
424
|
+
});
|
|
365
425
|
return result;
|
|
366
426
|
};
|
|
367
427
|
|
|
@@ -370,14 +430,20 @@ const create_${e}_into_db = async (req: Request) => {
|
|
|
370
430
|
const update_${e}_into_db = async (req: Request) => {
|
|
371
431
|
// define your own logic here
|
|
372
432
|
const { id } = req.params as { id: string };
|
|
373
|
-
const
|
|
433
|
+
const payload: UpdatePayload = req.body;
|
|
434
|
+
const result = await prisma.${e}.update({
|
|
435
|
+
where: { id },
|
|
436
|
+
data: payload,
|
|
437
|
+
});
|
|
374
438
|
return result;
|
|
375
439
|
};
|
|
376
440
|
|
|
377
441
|
const delete_${e}_from_db = async (req: Request) => {
|
|
378
442
|
// define your own logic here
|
|
379
443
|
const { id } = req.params as { id: string };
|
|
380
|
-
const result = await prisma.${e}.delete({
|
|
444
|
+
const result = await prisma.${e}.delete({
|
|
445
|
+
where: { id },
|
|
446
|
+
});
|
|
381
447
|
return result;
|
|
382
448
|
};
|
|
383
449
|
|
|
@@ -388,19 +454,20 @@ export const ${e}_service = {
|
|
|
388
454
|
update_${e}_into_db,
|
|
389
455
|
delete_${e}_from_db,
|
|
390
456
|
};
|
|
391
|
-
`;var
|
|
457
|
+
`;var ae=(e,{auth:t=!1}={})=>`import { Prisma } from '@prisma/client';
|
|
392
458
|
import request from 'supertest';
|
|
393
|
-
import { describe, expect, it } from 'vitest';
|
|
459
|
+
import { ${t?"beforeEach, describe, expect, it":"describe, expect, it"} } from 'vitest';
|
|
394
460
|
import app from '../../src/app.js';
|
|
395
|
-
import {
|
|
461
|
+
${t?`import { bearer, makeAccount } from '../helpers/fixtures.js';
|
|
462
|
+
`:""}import { prismaMock } from '../helpers/prisma.js';
|
|
396
463
|
|
|
464
|
+
// Starter tests for the generated CRUD routes: extend them with your own rules.
|
|
397
465
|
const BASE = '/api/${e}';
|
|
398
466
|
const db = prismaMock.${e};
|
|
399
467
|
|
|
400
|
-
// Valid request bodies.
|
|
401
|
-
|
|
402
|
-
const
|
|
403
|
-
const updatePayload = {};
|
|
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}' };
|
|
404
471
|
|
|
405
472
|
// a row as the database would return it
|
|
406
473
|
const row = {
|
|
@@ -418,21 +485,38 @@ const recordNotFound = () =>
|
|
|
418
485
|
clientVersion: 'test',
|
|
419
486
|
});
|
|
420
487
|
|
|
421
|
-
|
|
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":""}
|
|
422
499
|
it('POST / creates one', async () => {
|
|
423
500
|
db.create.mockResolvedValue(row);
|
|
424
501
|
|
|
425
|
-
const res = await
|
|
502
|
+
const res = await api().post(BASE).send(createPayload);
|
|
426
503
|
|
|
427
504
|
expect(res.status).toBe(201);
|
|
428
505
|
expect(res.body.data).toEqual(json);
|
|
429
506
|
expect(db.create).toHaveBeenCalledWith({ data: createPayload });
|
|
430
507
|
});
|
|
431
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
|
+
|
|
432
516
|
it('GET / lists them', async () => {
|
|
433
517
|
db.findMany.mockResolvedValue([row]);
|
|
434
518
|
|
|
435
|
-
const res = await
|
|
519
|
+
const res = await api().get(BASE);
|
|
436
520
|
|
|
437
521
|
expect(res.status).toBe(200);
|
|
438
522
|
expect(res.body.data).toEqual([json]);
|
|
@@ -441,7 +525,7 @@ describe(BASE, () => {
|
|
|
441
525
|
it('GET /:id returns one', async () => {
|
|
442
526
|
db.findUnique.mockResolvedValue(row);
|
|
443
527
|
|
|
444
|
-
const res = await
|
|
528
|
+
const res = await api().get(\`\${BASE}/\${row.id}\`);
|
|
445
529
|
|
|
446
530
|
expect(res.status).toBe(200);
|
|
447
531
|
expect(res.body.data).toEqual(json);
|
|
@@ -451,15 +535,15 @@ describe(BASE, () => {
|
|
|
451
535
|
it('GET /:id is 404 when it does not exist', async () => {
|
|
452
536
|
db.findUnique.mockResolvedValue(null);
|
|
453
537
|
|
|
454
|
-
const res = await
|
|
538
|
+
const res = await api().get(\`\${BASE}/\${row.id}\`);
|
|
455
539
|
|
|
456
540
|
expect(res.status).toBe(404);
|
|
457
541
|
});
|
|
458
542
|
|
|
459
543
|
it('PATCH /:id updates it', async () => {
|
|
460
|
-
db.update.mockResolvedValue(row);
|
|
544
|
+
db.update.mockResolvedValue({ ...row, ...updatePayload });
|
|
461
545
|
|
|
462
|
-
const res = await
|
|
546
|
+
const res = await api().patch(\`\${BASE}/\${row.id}\`).send(updatePayload);
|
|
463
547
|
|
|
464
548
|
expect(res.status).toBe(200);
|
|
465
549
|
expect(db.update).toHaveBeenCalledWith({ where: { id: row.id }, data: updatePayload });
|
|
@@ -468,7 +552,7 @@ describe(BASE, () => {
|
|
|
468
552
|
it('PATCH /:id is 404 when it does not exist', async () => {
|
|
469
553
|
db.update.mockRejectedValue(recordNotFound());
|
|
470
554
|
|
|
471
|
-
const res = await
|
|
555
|
+
const res = await api().patch(\`\${BASE}/\${row.id}\`).send(updatePayload);
|
|
472
556
|
|
|
473
557
|
expect(res.status).toBe(404);
|
|
474
558
|
});
|
|
@@ -476,7 +560,7 @@ describe(BASE, () => {
|
|
|
476
560
|
it('DELETE /:id deletes it', async () => {
|
|
477
561
|
db.delete.mockResolvedValue(row);
|
|
478
562
|
|
|
479
|
-
const res = await
|
|
563
|
+
const res = await api().delete(\`\${BASE}/\${row.id}\`);
|
|
480
564
|
|
|
481
565
|
expect(res.status).toBe(200);
|
|
482
566
|
expect(db.delete).toHaveBeenCalledWith({ where: { id: row.id } });
|
|
@@ -485,41 +569,45 @@ describe(BASE, () => {
|
|
|
485
569
|
it('DELETE /:id is 404 when it does not exist', async () => {
|
|
486
570
|
db.delete.mockRejectedValue(recordNotFound());
|
|
487
571
|
|
|
488
|
-
const res = await
|
|
572
|
+
const res = await api().delete(\`\${BASE}/\${row.id}\`);
|
|
489
573
|
|
|
490
574
|
expect(res.status).toBe(404);
|
|
491
575
|
});
|
|
492
576
|
});
|
|
493
|
-
`;var
|
|
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}
|
|
494
578
|
${e}`:`${e.slice(0,r)}
|
|
495
|
-
${t}${e.slice(r)}`}function
|
|
496
|
-
|
|
497
|
-
${
|
|
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
|
|
498
584
|
`),s=t(r?e.replace(/\r\n/g,`
|
|
499
585
|
`):e);return!r||s.status!=="updated"?s:{status:"updated",content:s.content.replace(/\n/g,`\r
|
|
500
|
-
`)}}function
|
|
501
|
-
Next, run:`)),
|
|
502
|
-
Test it with: npm test -- tests/routes/${e}.test.ts`)),
|
|
503
|
-
Module '${e}' created successfully`))}var
|
|
504
|
-
${
|
|
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
|
|
505
591
|
|
|
506
|
-
${
|
|
507
|
-
npx ${
|
|
508
|
-
npx ${
|
|
592
|
+
${B.bold("Usage")}
|
|
593
|
+
npx ${y} create <project-name> [--template <mongoose|prisma>]
|
|
594
|
+
npx ${y} generate <module-name> [--auth] [--force]
|
|
509
595
|
|
|
510
|
-
${
|
|
596
|
+
${B.bold("Commands")}
|
|
511
597
|
create, -c <name> Create a new project from a template
|
|
512
598
|
generate, -g <name> Generate a module in the current project
|
|
513
599
|
|
|
514
|
-
${
|
|
515
|
-
-t, --template <name> Template to use with create (${
|
|
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)
|
|
516
603
|
-f, --force Overwrite an existing module when generating
|
|
517
604
|
-h, --help Show this help
|
|
518
605
|
-v, --version Show version
|
|
519
606
|
|
|
520
|
-
${
|
|
521
|
-
npx ${
|
|
522
|
-
npx ${
|
|
523
|
-
npx ${
|
|
524
|
-
|
|
525
|
-
|
|
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)}
|