create-wabck 1.1.0 ā 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/package.json +1 -1
- package/src/generator.js +3 -0
- package/src/index.js +1 -1
- package/src/templates/base.js +8 -5
- package/src/templates/config.js +1 -106
- package/src/templates/index.js +5 -1
- package/src/templates/swagger.js +601 -0
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ npm create wabck@latest my-api
|
|
|
23
23
|
| š Auth | JWT, Clerk, Firebase, None |
|
|
24
24
|
| ā
Validation | Zod, Joi, Express Validator |
|
|
25
25
|
| š API Style | REST or GraphQL (Apollo Server 4) |
|
|
26
|
-
| š Docs | Swagger
|
|
26
|
+
| š Docs | Swagger UI + openapi.yaml (OpenAPI 3.0) |
|
|
27
27
|
| š³ Docker | Multi-stage Dockerfile + docker-compose |
|
|
28
28
|
| ā” Package managers | npm, pnpm, yarn, bun |
|
|
29
29
|
|
|
@@ -53,6 +53,7 @@ my-api/
|
|
|
53
53
|
ā āāā graphql/ (GraphQL only)
|
|
54
54
|
ā āāā app.js
|
|
55
55
|
ā āāā server.js
|
|
56
|
+
āāā openapi.yaml (optional, OpenAPI 3.0 YAML spec)
|
|
56
57
|
āāā .env.example
|
|
57
58
|
āāā .prettierrc
|
|
58
59
|
āāā eslint.config.js
|
|
@@ -74,6 +75,7 @@ my-api/
|
|
|
74
75
|
| POST | `/api/v1/auth/login` | Login (JWT) |
|
|
75
76
|
| GET | `/api/v1/auth/me` | Profile (JWT) |
|
|
76
77
|
| GET | `/api-docs` | Swagger UI (Interactive Docs) |
|
|
78
|
+
| GET | `/api-docs.yaml` | OpenAPI 3.0 YAML Spec |
|
|
77
79
|
| GET | `/api-docs.json` | OpenAPI 3.0 JSON Spec |
|
|
78
80
|
|
|
79
81
|
## š License
|
package/package.json
CHANGED
package/src/generator.js
CHANGED
|
@@ -94,6 +94,9 @@ export async function generate(options) {
|
|
|
94
94
|
await write('.prettierrc', T.prettierrc());
|
|
95
95
|
await write('eslint.config.js', T.eslintConfig(options));
|
|
96
96
|
await write('README.md', T.readme(options));
|
|
97
|
+
if (hasSwagger) {
|
|
98
|
+
await write('openapi.yaml', T.swaggerYaml(options));
|
|
99
|
+
}
|
|
97
100
|
|
|
98
101
|
// āā TypeScript extras āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
99
102
|
if (isTS) {
|
package/src/index.js
CHANGED
|
@@ -85,7 +85,7 @@ function printSuccessSummary(options, projectDir) {
|
|
|
85
85
|
log.label(' Database', database === 'none' ? 'None' : database);
|
|
86
86
|
log.label(' Auth', auth === 'none' ? 'None' : auth);
|
|
87
87
|
if (validation && validation !== 'none') log.label(' Validation', validation);
|
|
88
|
-
if (swagger) log.label('
|
|
88
|
+
if (swagger) log.label(' OpenAPI Docs', 'Swagger UI ā /api-docs (openapi.yaml)');
|
|
89
89
|
if (docker) log.label(' Docker', 'Dockerfile + docker-compose.yml');
|
|
90
90
|
log.blank();
|
|
91
91
|
|
package/src/templates/base.js
CHANGED
|
@@ -103,7 +103,7 @@ export function server(options) {
|
|
|
103
103
|
const dbImport = hasDB ? `import connectDB from './config/db.js';\n` : '';
|
|
104
104
|
const dbConnect = hasDB ? ` await connectDB();\n` : '';
|
|
105
105
|
const swaggerLog = hasSwagger
|
|
106
|
-
? ` console.log(\` ā Docs: http://localhost:\${PORT}/api-docs\`);\n`
|
|
106
|
+
? ` console.log(\` ā Docs: http://localhost:\${PORT}/api-docs\`);\n console.log(\` ā OpenAPI: http://localhost:\${PORT}/api-docs.yaml (or openapi.yaml)\`);\n`
|
|
107
107
|
: '';
|
|
108
108
|
|
|
109
109
|
return `import 'dotenv/config';
|
|
@@ -346,7 +346,10 @@ ${pm} run dev
|
|
|
346
346
|
\`\`\`
|
|
347
347
|
|
|
348
348
|
The API will be available at **http://localhost:5000**
|
|
349
|
-
${swagger ? `\nš **API Documentation
|
|
349
|
+
${swagger ? `\nš **API Documentation:**
|
|
350
|
+
- **Swagger UI:** http://localhost:5000/api-docs
|
|
351
|
+
- **OpenAPI 3.0 YAML:** \`openapi.yaml\` or http://localhost:5000/api-docs.yaml
|
|
352
|
+
- **OpenAPI 3.0 JSON:** http://localhost:5000/api-docs.json\n` : ''}
|
|
350
353
|
## š Project Structure
|
|
351
354
|
|
|
352
355
|
\`\`\`
|
|
@@ -363,7 +366,7 @@ ${validation !== 'none' ? `ā āāā validators/ # Input validation\n
|
|
|
363
366
|
ā āāā constants/ # App-wide constants
|
|
364
367
|
ā āāā app.${isTS ? 'ts' : 'js'} # Express app
|
|
365
368
|
ā āāā server.${isTS ? 'ts' : 'js'} # Entry point
|
|
366
|
-
āāā .env.example
|
|
369
|
+
${swagger ? 'āāā openapi.yaml # OpenAPI 3.0 specification (YAML)\n' : ''}āāā .env.example
|
|
367
370
|
āāā .prettierrc
|
|
368
371
|
āāā eslint.config.js
|
|
369
372
|
\`\`\`
|
|
@@ -376,7 +379,7 @@ ${validation !== 'none' ? `ā āāā validators/ # Input validation\n
|
|
|
376
379
|
| Database | ${database === 'none' ? 'ā' : database} |
|
|
377
380
|
| Auth | ${auth === 'none' ? 'ā' : auth} |
|
|
378
381
|
${validation !== 'none' ? `| Validation | ${validation} |\n` : ''}| API Style | ${apiStyle === 'graphql' ? 'GraphQL (Apollo Server 4)' : 'REST'} |
|
|
379
|
-
${swagger ? '| API Docs | ā Swagger / OpenAPI 3.0 (/api-docs)
|
|
382
|
+
${swagger ? '| API Docs | ā Swagger / OpenAPI 3.0 (/api-docs + openapi.yaml) |\n' : ''}| Docker | ${docker ? 'ā Dockerfile + docker-compose' : 'ā'} |
|
|
380
383
|
|
|
381
384
|
## š Endpoints
|
|
382
385
|
|
|
@@ -390,7 +393,7 @@ ${swagger ? '| API Docs | ā Swagger / OpenAPI 3.0 (/api-docs)
|
|
|
390
393
|
| PUT | \`/api/v1/users/:id\` | Update user |
|
|
391
394
|
| DELETE | \`/api/v1/users/:id\` | Delete user |
|
|
392
395
|
${auth === 'jwt' ? `| POST | \`/api/v1/auth/register\` | Register |\n| POST | \`/api/v1/auth/login\` | Login |` : ''}
|
|
393
|
-
${swagger ? `| GET | \`/api-docs\` | Swagger UI (interactive) |\n| GET | \`/api-docs.json\` |
|
|
396
|
+
${swagger ? `| GET | \`/api-docs\` | Swagger UI (interactive) |\n| GET | \`/api-docs.yaml\` | OpenAPI 3.0 YAML spec |\n| GET | \`/api-docs.json\` | OpenAPI 3.0 JSON spec |\n` : ''}
|
|
394
397
|
${docker ? `## š³ Docker\n\n\`\`\`bash\ndocker compose up --build\n\`\`\`\n` : ''}
|
|
395
398
|
${isTS ? `## š Build\n\n\`\`\`bash\n${pm} run build\n${pm} start\n\`\`\`\n` : ''}
|
|
396
399
|
## š License
|
package/src/templates/config.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
2
|
-
// Config Templates ā db.js
|
|
2
|
+
// Config Templates ā db.js & env.js
|
|
3
3
|
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
4
4
|
|
|
5
5
|
/** Generate src/config/db.js or db.ts */
|
|
@@ -215,108 +215,3 @@ ${database === 'mongodb' ? " mongoUri: process.env.MONGODB_URI || '',\n" : '
|
|
|
215
215
|
`;
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
-
/** Generate src/config/swagger.js or swagger.ts */
|
|
219
|
-
export function swaggerConfig(options) {
|
|
220
|
-
const { projectName, language } = options;
|
|
221
|
-
const isTS = language === 'typescript';
|
|
222
|
-
const ext = isTS ? 'ts' : 'js';
|
|
223
|
-
|
|
224
|
-
return `import swaggerJsdoc from 'swagger-jsdoc';
|
|
225
|
-
import swaggerUi from 'swagger-ui-express';
|
|
226
|
-
import { readFileSync } from 'fs';
|
|
227
|
-
import { fileURLToPath } from 'url';
|
|
228
|
-
import path from 'path';
|
|
229
|
-
${isTS ? `import type { Express } from 'express';\n` : ''}
|
|
230
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
231
|
-
const __dirname = path.dirname(__filename);
|
|
232
|
-
|
|
233
|
-
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
234
|
-
// Swagger / OpenAPI 3.0 Configuration
|
|
235
|
-
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
236
|
-
|
|
237
|
-
// Read version from package.json
|
|
238
|
-
let version = '1.0.0';
|
|
239
|
-
try {
|
|
240
|
-
const pkg = JSON.parse(readFileSync(path.join(__dirname, '..', '..', 'package.json'), 'utf-8'));
|
|
241
|
-
version = pkg.version;
|
|
242
|
-
} catch { /* use default */ }
|
|
243
|
-
|
|
244
|
-
const swaggerDefinition = {
|
|
245
|
-
openapi: '3.0.0',
|
|
246
|
-
info: {
|
|
247
|
-
title: '${projectName} API',
|
|
248
|
-
version,
|
|
249
|
-
description: 'REST API documentation for ${projectName}',
|
|
250
|
-
license: { name: 'MIT' },
|
|
251
|
-
contact: { name: '${projectName} Team' },
|
|
252
|
-
},
|
|
253
|
-
servers: [
|
|
254
|
-
{ url: \`http://localhost:\${process.env.PORT || 5000}/api/v1\`, description: 'Development' },
|
|
255
|
-
{ url: '/api/v1', description: 'Relative (proxy-friendly)' },
|
|
256
|
-
],
|
|
257
|
-
components: {
|
|
258
|
-
securitySchemes: {
|
|
259
|
-
bearerAuth: {
|
|
260
|
-
type: 'http',
|
|
261
|
-
scheme: 'bearer',
|
|
262
|
-
bearerFormat: 'JWT',
|
|
263
|
-
description: 'Enter your JWT token. Example: Bearer eyJhbGci...',
|
|
264
|
-
},
|
|
265
|
-
},
|
|
266
|
-
schemas: {
|
|
267
|
-
ApiResponse: {
|
|
268
|
-
type: 'object',
|
|
269
|
-
properties: {
|
|
270
|
-
success: { type: 'boolean' },
|
|
271
|
-
statusCode: { type: 'integer' },
|
|
272
|
-
message: { type: 'string' },
|
|
273
|
-
data: { },
|
|
274
|
-
},
|
|
275
|
-
},
|
|
276
|
-
ApiError: {
|
|
277
|
-
type: 'object',
|
|
278
|
-
properties: {
|
|
279
|
-
success: { type: 'boolean', example: false },
|
|
280
|
-
statusCode: { type: 'integer' },
|
|
281
|
-
message: { type: 'string' },
|
|
282
|
-
errors: { type: 'array', items: { type: 'string' } },
|
|
283
|
-
},
|
|
284
|
-
},
|
|
285
|
-
},
|
|
286
|
-
},
|
|
287
|
-
security: [{ bearerAuth: [] }],
|
|
288
|
-
};
|
|
289
|
-
|
|
290
|
-
const swaggerOptions = {
|
|
291
|
-
definition: swaggerDefinition,
|
|
292
|
-
// Scans all route files for @swagger JSDoc annotations
|
|
293
|
-
apis: [
|
|
294
|
-
path.join(__dirname, '../routes/*.js'),
|
|
295
|
-
path.join(__dirname, '../routes/*.ts'),
|
|
296
|
-
],
|
|
297
|
-
};
|
|
298
|
-
|
|
299
|
-
export const swaggerSpec = swaggerJsdoc(swaggerOptions);
|
|
300
|
-
|
|
301
|
-
/**
|
|
302
|
-
* Mounts the Swagger UI at /api-docs
|
|
303
|
-
* @param {${isTS ? 'Express' : 'import("express").Express'}} app
|
|
304
|
-
*/
|
|
305
|
-
export function setupSwagger(app${isTS ? ': Express' : ''}) {
|
|
306
|
-
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec, {
|
|
307
|
-
customSiteTitle: '${projectName} API Docs',
|
|
308
|
-
swaggerOptions: {
|
|
309
|
-
persistAuthorization: true,
|
|
310
|
-
docExpansion: 'list',
|
|
311
|
-
filter: true,
|
|
312
|
-
},
|
|
313
|
-
}));
|
|
314
|
-
|
|
315
|
-
// Raw JSON spec endpoint (useful for Postman / code-gen)
|
|
316
|
-
app.get('/api-docs.json', (_req, res) => {
|
|
317
|
-
res.setHeader('Content-Type', 'application/json');
|
|
318
|
-
res.send(swaggerSpec);
|
|
319
|
-
});
|
|
320
|
-
}
|
|
321
|
-
`;
|
|
322
|
-
}
|
package/src/templates/index.js
CHANGED
|
@@ -0,0 +1,601 @@
|
|
|
1
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
2
|
+
// Swagger / OpenAPI Templates ā src/config/swagger.js/ts & openapi.yaml
|
|
3
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
4
|
+
|
|
5
|
+
/** Generate src/config/swagger.js or src/config/swagger.ts */
|
|
6
|
+
export function swaggerConfig(options) {
|
|
7
|
+
const { projectName, language, auth } = options;
|
|
8
|
+
const isTS = language === 'typescript';
|
|
9
|
+
const hasJwt = auth === 'jwt';
|
|
10
|
+
|
|
11
|
+
return `import swaggerJsdoc from 'swagger-jsdoc';
|
|
12
|
+
import swaggerUi from 'swagger-ui-express';
|
|
13
|
+
import { readFileSync, existsSync } from 'fs';
|
|
14
|
+
import { fileURLToPath } from 'url';
|
|
15
|
+
import path from 'path';
|
|
16
|
+
${isTS ? `import type { Express } from 'express';\n` : ''}
|
|
17
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
18
|
+
const __dirname = path.dirname(__filename);
|
|
19
|
+
|
|
20
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
21
|
+
// Production Swagger / OpenAPI 3.0 Specification
|
|
22
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
23
|
+
|
|
24
|
+
// Dynamically read version from package.json
|
|
25
|
+
let version = '1.0.0';
|
|
26
|
+
try {
|
|
27
|
+
const pkgPath = path.resolve(__dirname, '../../package.json');
|
|
28
|
+
if (existsSync(pkgPath)) {
|
|
29
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
30
|
+
version = pkg.version || '1.0.0';
|
|
31
|
+
}
|
|
32
|
+
} catch { /* use fallback version */ }
|
|
33
|
+
|
|
34
|
+
const swaggerDefinition = {
|
|
35
|
+
openapi: '3.0.3',
|
|
36
|
+
info: {
|
|
37
|
+
title: '${projectName} API',
|
|
38
|
+
version,
|
|
39
|
+
description: 'Production-ready REST API documentation with interactive Swagger UI and OpenAPI 3.0 specification.',
|
|
40
|
+
license: {
|
|
41
|
+
name: 'MIT',
|
|
42
|
+
url: 'https://opensource.org/licenses/MIT',
|
|
43
|
+
},
|
|
44
|
+
contact: {
|
|
45
|
+
name: '${projectName} Team',
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
servers: [
|
|
49
|
+
{
|
|
50
|
+
url: \`http://localhost:\${process.env.PORT || 5000}/api/v1\`,
|
|
51
|
+
description: 'Local development server',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
url: '/api/v1',
|
|
55
|
+
description: 'Current host / Reverse proxy relative path',
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
tags: [
|
|
59
|
+
{ name: 'Health', description: 'System health & diagnostic endpoints' },
|
|
60
|
+
{ name: 'Users', description: 'User management and CRUD operations' },
|
|
61
|
+
${hasJwt ? " { name: 'Auth', description: 'Authentication, sessions, and token issuance' },\n" : ''} ],
|
|
62
|
+
components: {
|
|
63
|
+
securitySchemes: {
|
|
64
|
+
bearerAuth: {
|
|
65
|
+
type: 'http',
|
|
66
|
+
scheme: 'bearer',
|
|
67
|
+
bearerFormat: 'JWT',
|
|
68
|
+
description: 'Provide your Bearer token in the format: \`Bearer <token>\`',
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
schemas: {
|
|
72
|
+
ApiResponse: {
|
|
73
|
+
type: 'object',
|
|
74
|
+
properties: {
|
|
75
|
+
success: { type: 'boolean', example: true },
|
|
76
|
+
statusCode: { type: 'integer', example: 200 },
|
|
77
|
+
message: { type: 'string', example: 'Success' },
|
|
78
|
+
data: { type: 'object' },
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
ApiError: {
|
|
82
|
+
type: 'object',
|
|
83
|
+
properties: {
|
|
84
|
+
success: { type: 'boolean', example: false },
|
|
85
|
+
statusCode: { type: 'integer', example: 400 },
|
|
86
|
+
message: { type: 'string', example: 'Validation or operation error' },
|
|
87
|
+
errors: { type: 'array', items: { type: 'string' } },
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
User: {
|
|
91
|
+
type: 'object',
|
|
92
|
+
properties: {
|
|
93
|
+
_id: { type: 'string', example: '661fae4e5bc24a1b8c9d0123' },
|
|
94
|
+
name: { type: 'string', example: 'Alex Mercer' },
|
|
95
|
+
email: { type: 'string', format: 'email', example: 'alex@example.com' },
|
|
96
|
+
role: { type: 'string', enum: ['user', 'admin'], example: 'user' },
|
|
97
|
+
isActive: { type: 'boolean', example: true },
|
|
98
|
+
createdAt: { type: 'string', format: 'date-time' },
|
|
99
|
+
updatedAt: { type: 'string', format: 'date-time' },
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
security: [
|
|
105
|
+
{ bearerAuth: [] },
|
|
106
|
+
],
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const swaggerOptions = {
|
|
110
|
+
definition: swaggerDefinition,
|
|
111
|
+
apis: [
|
|
112
|
+
path.join(__dirname, '../routes/*.js'),
|
|
113
|
+
path.join(__dirname, '../routes/*.ts'),
|
|
114
|
+
],
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
export const swaggerSpec = swaggerJsdoc(swaggerOptions);
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Configure and mount Swagger UI and OpenAPI endpoints on the Express application.
|
|
121
|
+
* @param {${isTS ? 'Express' : 'import("express").Express'}} app
|
|
122
|
+
*/
|
|
123
|
+
export function setupSwagger(app${isTS ? ': Express' : ''}) {
|
|
124
|
+
// Custom dark-accent styling for modern presentation
|
|
125
|
+
const customCss = \`
|
|
126
|
+
.swagger-ui .topbar { display: none }
|
|
127
|
+
.swagger-ui .info { margin: 25px 0 }
|
|
128
|
+
.swagger-ui .info .title { font-family: system-ui, sans-serif; font-weight: 700 }
|
|
129
|
+
.swagger-ui .scheme-container { background: #f8fafc; padding: 15px; border-radius: 8px; margin-bottom: 20px }
|
|
130
|
+
\`;
|
|
131
|
+
|
|
132
|
+
// Interactive Swagger UI
|
|
133
|
+
app.use(
|
|
134
|
+
'/api-docs',
|
|
135
|
+
swaggerUi.serve,
|
|
136
|
+
swaggerUi.setup(swaggerSpec, {
|
|
137
|
+
customSiteTitle: '${projectName} API Documentation',
|
|
138
|
+
customCss,
|
|
139
|
+
swaggerOptions: {
|
|
140
|
+
persistAuthorization: true,
|
|
141
|
+
docExpansion: 'list',
|
|
142
|
+
filter: true,
|
|
143
|
+
tryItOutEnabled: true,
|
|
144
|
+
displayRequestDuration: true,
|
|
145
|
+
},
|
|
146
|
+
})
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
// Raw OpenAPI 3.0 JSON specification (for Postman, Insomnia, or code generators)
|
|
150
|
+
app.get('/api-docs.json', (_req, res) => {
|
|
151
|
+
res.setHeader('Content-Type', 'application/json');
|
|
152
|
+
res.send(swaggerSpec);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Raw OpenAPI 3.0 YAML specification file
|
|
156
|
+
app.get('/api-docs.yaml', (_req, res) => {
|
|
157
|
+
const yamlPath = path.resolve(__dirname, '../../openapi.yaml');
|
|
158
|
+
if (existsSync(yamlPath)) {
|
|
159
|
+
res.setHeader('Content-Type', 'text/yaml; charset=utf-8');
|
|
160
|
+
return res.sendFile(yamlPath);
|
|
161
|
+
}
|
|
162
|
+
// Fallback: send JSON representation if yaml file is relocated
|
|
163
|
+
res.setHeader('Content-Type', 'application/json');
|
|
164
|
+
res.send(swaggerSpec);
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
`;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Generate openapi.yaml for the root directory of the scaffolded project */
|
|
171
|
+
export function swaggerYaml(options) {
|
|
172
|
+
const { projectName, auth } = options;
|
|
173
|
+
const hasJwt = auth === 'jwt';
|
|
174
|
+
|
|
175
|
+
return `openapi: 3.0.3
|
|
176
|
+
info:
|
|
177
|
+
title: ${projectName} API
|
|
178
|
+
description: Production-grade REST API specification for ${projectName}. Auto-generated by wabck.
|
|
179
|
+
version: 1.0.0
|
|
180
|
+
license:
|
|
181
|
+
name: MIT
|
|
182
|
+
url: https://opensource.org/licenses/MIT
|
|
183
|
+
contact:
|
|
184
|
+
name: ${projectName} Team
|
|
185
|
+
|
|
186
|
+
servers:
|
|
187
|
+
- url: http://localhost:5000/api/v1
|
|
188
|
+
description: Local development server
|
|
189
|
+
- url: /api/v1
|
|
190
|
+
description: Current host / Reverse proxy relative path
|
|
191
|
+
|
|
192
|
+
tags:
|
|
193
|
+
- name: Health
|
|
194
|
+
description: System health & diagnostic endpoints
|
|
195
|
+
- name: Users
|
|
196
|
+
description: User management and CRUD operations
|
|
197
|
+
${hasJwt ? ` - name: Auth
|
|
198
|
+
description: Authentication, sessions, and token issuance
|
|
199
|
+
` : ''}
|
|
200
|
+
paths:
|
|
201
|
+
/health:
|
|
202
|
+
get:
|
|
203
|
+
tags:
|
|
204
|
+
- Health
|
|
205
|
+
summary: Health check & server diagnostics
|
|
206
|
+
description: Returns service uptime, memory metrics, runtime environment, and current version.
|
|
207
|
+
responses:
|
|
208
|
+
'200':
|
|
209
|
+
description: Service is healthy and responding
|
|
210
|
+
content:
|
|
211
|
+
application/json:
|
|
212
|
+
schema:
|
|
213
|
+
type: object
|
|
214
|
+
properties:
|
|
215
|
+
status:
|
|
216
|
+
type: string
|
|
217
|
+
example: ok
|
|
218
|
+
uptime:
|
|
219
|
+
type: integer
|
|
220
|
+
example: 120
|
|
221
|
+
version:
|
|
222
|
+
type: string
|
|
223
|
+
example: 1.0.0
|
|
224
|
+
env:
|
|
225
|
+
type: string
|
|
226
|
+
example: development
|
|
227
|
+
timestamp:
|
|
228
|
+
type: string
|
|
229
|
+
format: date-time
|
|
230
|
+
example: '2026-01-01T00:00:00.000Z'
|
|
231
|
+
memory:
|
|
232
|
+
type: object
|
|
233
|
+
properties:
|
|
234
|
+
used:
|
|
235
|
+
type: string
|
|
236
|
+
example: 32 MB
|
|
237
|
+
total:
|
|
238
|
+
type: string
|
|
239
|
+
example: 64 MB
|
|
240
|
+
|
|
241
|
+
/users:
|
|
242
|
+
get:
|
|
243
|
+
tags:
|
|
244
|
+
- Users
|
|
245
|
+
summary: Retrieve all users
|
|
246
|
+
description: Fetches a list of registered users.
|
|
247
|
+
responses:
|
|
248
|
+
'200':
|
|
249
|
+
description: Users retrieved successfully
|
|
250
|
+
content:
|
|
251
|
+
application/json:
|
|
252
|
+
schema:
|
|
253
|
+
type: object
|
|
254
|
+
properties:
|
|
255
|
+
success:
|
|
256
|
+
type: boolean
|
|
257
|
+
example: true
|
|
258
|
+
data:
|
|
259
|
+
type: array
|
|
260
|
+
items:
|
|
261
|
+
$ref: '#/components/schemas/User'
|
|
262
|
+
post:
|
|
263
|
+
tags:
|
|
264
|
+
- Users
|
|
265
|
+
summary: Create a new user
|
|
266
|
+
description: Adds a new user record to the database.
|
|
267
|
+
requestBody:
|
|
268
|
+
required: true
|
|
269
|
+
content:
|
|
270
|
+
application/json:
|
|
271
|
+
schema:
|
|
272
|
+
$ref: '#/components/schemas/CreateUserInput'
|
|
273
|
+
responses:
|
|
274
|
+
'201':
|
|
275
|
+
description: User created successfully
|
|
276
|
+
content:
|
|
277
|
+
application/json:
|
|
278
|
+
schema:
|
|
279
|
+
$ref: '#/components/schemas/ApiResponse'
|
|
280
|
+
'400':
|
|
281
|
+
description: Bad request or validation failure
|
|
282
|
+
content:
|
|
283
|
+
application/json:
|
|
284
|
+
schema:
|
|
285
|
+
$ref: '#/components/schemas/ApiError'
|
|
286
|
+
|
|
287
|
+
/users/{id}:
|
|
288
|
+
get:
|
|
289
|
+
tags:
|
|
290
|
+
- Users
|
|
291
|
+
summary: Get user by ID
|
|
292
|
+
parameters:
|
|
293
|
+
- name: id
|
|
294
|
+
in: path
|
|
295
|
+
required: true
|
|
296
|
+
description: Unique identifier of the user
|
|
297
|
+
schema:
|
|
298
|
+
type: string
|
|
299
|
+
responses:
|
|
300
|
+
'200':
|
|
301
|
+
description: User found
|
|
302
|
+
content:
|
|
303
|
+
application/json:
|
|
304
|
+
schema:
|
|
305
|
+
$ref: '#/components/schemas/ApiResponse'
|
|
306
|
+
'404':
|
|
307
|
+
description: User not found
|
|
308
|
+
content:
|
|
309
|
+
application/json:
|
|
310
|
+
schema:
|
|
311
|
+
$ref: '#/components/schemas/ApiError'
|
|
312
|
+
put:
|
|
313
|
+
tags:
|
|
314
|
+
- Users
|
|
315
|
+
summary: Update an existing user
|
|
316
|
+
parameters:
|
|
317
|
+
- name: id
|
|
318
|
+
in: path
|
|
319
|
+
required: true
|
|
320
|
+
description: Unique identifier of the user
|
|
321
|
+
schema:
|
|
322
|
+
type: string
|
|
323
|
+
requestBody:
|
|
324
|
+
required: true
|
|
325
|
+
content:
|
|
326
|
+
application/json:
|
|
327
|
+
schema:
|
|
328
|
+
$ref: '#/components/schemas/UpdateUserInput'
|
|
329
|
+
responses:
|
|
330
|
+
'200':
|
|
331
|
+
description: User updated successfully
|
|
332
|
+
content:
|
|
333
|
+
application/json:
|
|
334
|
+
schema:
|
|
335
|
+
$ref: '#/components/schemas/ApiResponse'
|
|
336
|
+
'404':
|
|
337
|
+
description: User not found
|
|
338
|
+
delete:
|
|
339
|
+
tags:
|
|
340
|
+
- Users
|
|
341
|
+
summary: Delete a user by ID
|
|
342
|
+
security:
|
|
343
|
+
- bearerAuth: []
|
|
344
|
+
parameters:
|
|
345
|
+
- name: id
|
|
346
|
+
in: path
|
|
347
|
+
required: true
|
|
348
|
+
description: Unique identifier of the user
|
|
349
|
+
schema:
|
|
350
|
+
type: string
|
|
351
|
+
responses:
|
|
352
|
+
'200':
|
|
353
|
+
description: User deleted successfully
|
|
354
|
+
content:
|
|
355
|
+
application/json:
|
|
356
|
+
schema:
|
|
357
|
+
$ref: '#/components/schemas/ApiResponse'
|
|
358
|
+
'404':
|
|
359
|
+
description: User not found
|
|
360
|
+
${hasJwt ? `
|
|
361
|
+
/auth/register:
|
|
362
|
+
post:
|
|
363
|
+
tags:
|
|
364
|
+
- Auth
|
|
365
|
+
summary: Register a new account
|
|
366
|
+
description: Creates a new user profile and returns access tokens.
|
|
367
|
+
requestBody:
|
|
368
|
+
required: true
|
|
369
|
+
content:
|
|
370
|
+
application/json:
|
|
371
|
+
schema:
|
|
372
|
+
$ref: '#/components/schemas/RegisterInput'
|
|
373
|
+
responses:
|
|
374
|
+
'201':
|
|
375
|
+
description: User successfully registered
|
|
376
|
+
content:
|
|
377
|
+
application/json:
|
|
378
|
+
schema:
|
|
379
|
+
$ref: '#/components/schemas/AuthResponse'
|
|
380
|
+
'400':
|
|
381
|
+
description: Validation error or email already in use
|
|
382
|
+
content:
|
|
383
|
+
application/json:
|
|
384
|
+
schema:
|
|
385
|
+
$ref: '#/components/schemas/ApiError'
|
|
386
|
+
|
|
387
|
+
/auth/login:
|
|
388
|
+
post:
|
|
389
|
+
tags:
|
|
390
|
+
- Auth
|
|
391
|
+
summary: Authenticate user & issue tokens
|
|
392
|
+
description: Validates email and password, issuing access & refresh tokens.
|
|
393
|
+
requestBody:
|
|
394
|
+
required: true
|
|
395
|
+
content:
|
|
396
|
+
application/json:
|
|
397
|
+
schema:
|
|
398
|
+
$ref: '#/components/schemas/LoginInput'
|
|
399
|
+
responses:
|
|
400
|
+
'200':
|
|
401
|
+
description: Login successful
|
|
402
|
+
content:
|
|
403
|
+
application/json:
|
|
404
|
+
schema:
|
|
405
|
+
$ref: '#/components/schemas/AuthResponse'
|
|
406
|
+
'401':
|
|
407
|
+
description: Invalid credentials
|
|
408
|
+
content:
|
|
409
|
+
application/json:
|
|
410
|
+
schema:
|
|
411
|
+
$ref: '#/components/schemas/ApiError'
|
|
412
|
+
|
|
413
|
+
/auth/logout:
|
|
414
|
+
post:
|
|
415
|
+
tags:
|
|
416
|
+
- Auth
|
|
417
|
+
summary: Invalidate user session
|
|
418
|
+
security:
|
|
419
|
+
- bearerAuth: []
|
|
420
|
+
responses:
|
|
421
|
+
'200':
|
|
422
|
+
description: Logged out successfully
|
|
423
|
+
content:
|
|
424
|
+
application/json:
|
|
425
|
+
schema:
|
|
426
|
+
$ref: '#/components/schemas/ApiResponse'
|
|
427
|
+
|
|
428
|
+
/auth/me:
|
|
429
|
+
get:
|
|
430
|
+
tags:
|
|
431
|
+
- Auth
|
|
432
|
+
summary: Retrieve current authenticated user profile
|
|
433
|
+
security:
|
|
434
|
+
- bearerAuth: []
|
|
435
|
+
responses:
|
|
436
|
+
'200':
|
|
437
|
+
description: Profile retrieved successfully
|
|
438
|
+
content:
|
|
439
|
+
application/json:
|
|
440
|
+
schema:
|
|
441
|
+
$ref: '#/components/schemas/ApiResponse'
|
|
442
|
+
'401':
|
|
443
|
+
description: Unauthorized
|
|
444
|
+
` : ''}
|
|
445
|
+
components:
|
|
446
|
+
securitySchemes:
|
|
447
|
+
bearerAuth:
|
|
448
|
+
type: http
|
|
449
|
+
scheme: bearer
|
|
450
|
+
bearerFormat: JWT
|
|
451
|
+
description: Supply your JSON Web Token as 'Bearer <token>'
|
|
452
|
+
|
|
453
|
+
schemas:
|
|
454
|
+
ApiResponse:
|
|
455
|
+
type: object
|
|
456
|
+
properties:
|
|
457
|
+
success:
|
|
458
|
+
type: boolean
|
|
459
|
+
example: true
|
|
460
|
+
statusCode:
|
|
461
|
+
type: integer
|
|
462
|
+
example: 200
|
|
463
|
+
message:
|
|
464
|
+
type: string
|
|
465
|
+
example: Success
|
|
466
|
+
data:
|
|
467
|
+
type: object
|
|
468
|
+
|
|
469
|
+
ApiError:
|
|
470
|
+
type: object
|
|
471
|
+
properties:
|
|
472
|
+
success:
|
|
473
|
+
type: boolean
|
|
474
|
+
example: false
|
|
475
|
+
statusCode:
|
|
476
|
+
type: integer
|
|
477
|
+
example: 400
|
|
478
|
+
message:
|
|
479
|
+
type: string
|
|
480
|
+
example: Error occurred
|
|
481
|
+
errors:
|
|
482
|
+
type: array
|
|
483
|
+
items:
|
|
484
|
+
type: string
|
|
485
|
+
|
|
486
|
+
User:
|
|
487
|
+
type: object
|
|
488
|
+
properties:
|
|
489
|
+
_id:
|
|
490
|
+
type: string
|
|
491
|
+
example: 661fae4e5bc24a1b8c9d0123
|
|
492
|
+
name:
|
|
493
|
+
type: string
|
|
494
|
+
example: Alex Mercer
|
|
495
|
+
email:
|
|
496
|
+
type: string
|
|
497
|
+
format: email
|
|
498
|
+
example: alex@example.com
|
|
499
|
+
role:
|
|
500
|
+
type: string
|
|
501
|
+
enum: [user, admin]
|
|
502
|
+
example: user
|
|
503
|
+
isActive:
|
|
504
|
+
type: boolean
|
|
505
|
+
example: true
|
|
506
|
+
createdAt:
|
|
507
|
+
type: string
|
|
508
|
+
format: date-time
|
|
509
|
+
updatedAt:
|
|
510
|
+
type: string
|
|
511
|
+
format: date-time
|
|
512
|
+
|
|
513
|
+
CreateUserInput:
|
|
514
|
+
type: object
|
|
515
|
+
required:
|
|
516
|
+
- name
|
|
517
|
+
- email
|
|
518
|
+
- password
|
|
519
|
+
properties:
|
|
520
|
+
name:
|
|
521
|
+
type: string
|
|
522
|
+
example: Alex Mercer
|
|
523
|
+
email:
|
|
524
|
+
type: string
|
|
525
|
+
format: email
|
|
526
|
+
example: alex@example.com
|
|
527
|
+
password:
|
|
528
|
+
type: string
|
|
529
|
+
format: password
|
|
530
|
+
example: SecurePass123!
|
|
531
|
+
|
|
532
|
+
UpdateUserInput:
|
|
533
|
+
type: object
|
|
534
|
+
properties:
|
|
535
|
+
name:
|
|
536
|
+
type: string
|
|
537
|
+
example: Alex Mercer
|
|
538
|
+
email:
|
|
539
|
+
type: string
|
|
540
|
+
format: email
|
|
541
|
+
example: alex@example.com
|
|
542
|
+
|
|
543
|
+
RegisterInput:
|
|
544
|
+
type: object
|
|
545
|
+
required:
|
|
546
|
+
- name
|
|
547
|
+
- email
|
|
548
|
+
- password
|
|
549
|
+
properties:
|
|
550
|
+
name:
|
|
551
|
+
type: string
|
|
552
|
+
example: Jane Doe
|
|
553
|
+
email:
|
|
554
|
+
type: string
|
|
555
|
+
format: email
|
|
556
|
+
example: jane@example.com
|
|
557
|
+
password:
|
|
558
|
+
type: string
|
|
559
|
+
format: password
|
|
560
|
+
example: SecretPass123!
|
|
561
|
+
|
|
562
|
+
LoginInput:
|
|
563
|
+
type: object
|
|
564
|
+
required:
|
|
565
|
+
- email
|
|
566
|
+
- password
|
|
567
|
+
properties:
|
|
568
|
+
email:
|
|
569
|
+
type: string
|
|
570
|
+
format: email
|
|
571
|
+
example: jane@example.com
|
|
572
|
+
password:
|
|
573
|
+
type: string
|
|
574
|
+
format: password
|
|
575
|
+
example: SecretPass123!
|
|
576
|
+
|
|
577
|
+
AuthResponse:
|
|
578
|
+
type: object
|
|
579
|
+
properties:
|
|
580
|
+
success:
|
|
581
|
+
type: boolean
|
|
582
|
+
example: true
|
|
583
|
+
statusCode:
|
|
584
|
+
type: integer
|
|
585
|
+
example: 200
|
|
586
|
+
message:
|
|
587
|
+
type: string
|
|
588
|
+
example: Authenticated
|
|
589
|
+
data:
|
|
590
|
+
type: object
|
|
591
|
+
properties:
|
|
592
|
+
user:
|
|
593
|
+
$ref: '#/components/schemas/User'
|
|
594
|
+
accessToken:
|
|
595
|
+
type: string
|
|
596
|
+
example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
|
597
|
+
refreshToken:
|
|
598
|
+
type: string
|
|
599
|
+
example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
|
600
|
+
`;
|
|
601
|
+
}
|