create-charcole 2.0.4 → 2.2.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/CHANGELOG.md +290 -14
- package/README.md +258 -312
- package/bin/index.js +392 -55
- package/bin/lib/pkgManager.js +8 -25
- package/bin/lib/templateHandler.js +5 -42
- package/create-charcole-2.1.0.tgz +0 -0
- package/package.json +2 -2
- package/packages/swagger/BACKWARD_COMPATIBILITY.md +145 -0
- package/packages/swagger/CHANGELOG.md +404 -0
- package/packages/swagger/README.md +578 -0
- package/packages/swagger/charcole-swagger-1.0.0.tgz +0 -0
- package/packages/swagger/package-lock.json +1715 -0
- package/packages/swagger/package.json +44 -0
- package/packages/swagger/src/helpers.js +427 -0
- package/packages/swagger/src/index.d.ts +126 -0
- package/packages/swagger/src/index.js +12 -0
- package/packages/swagger/src/setup.js +100 -0
- package/template/js/.env.example +8 -0
- package/template/js/README.md +128 -5
- package/template/js/basePackage.json +11 -13
- package/template/js/src/app.js +8 -2
- package/template/js/src/config/swagger.config.js +15 -0
- package/template/js/src/lib/swagger/SWAGGER_GUIDE.md +561 -0
- package/template/js/src/modules/auth/auth.constants.js +3 -0
- package/template/js/src/modules/auth/auth.controller.js +29 -0
- package/template/js/src/modules/auth/auth.middlewares.js +19 -0
- package/template/js/src/modules/auth/auth.routes.js +131 -0
- package/template/js/src/modules/auth/auth.schemas.js +60 -0
- package/template/js/src/modules/auth/auth.service.js +67 -0
- package/template/js/src/modules/auth/package.json +6 -0
- package/template/js/src/modules/health/controller.js +104 -3
- package/template/js/src/modules/swagger/charcole-swagger-1.0.0.tgz +0 -0
- package/template/js/src/modules/swagger/package.json +5 -0
- package/template/js/src/repositories/user.repo.js +19 -0
- package/template/js/src/routes/index.js +25 -0
- package/template/js/src/routes/protected.js +57 -0
- package/template/ts/.env.example +8 -0
- package/template/ts/README.md +128 -5
- package/template/ts/basePackage.json +19 -15
- package/template/ts/build.js +46 -0
- package/template/ts/src/app.ts +12 -7
- package/template/ts/src/config/swagger.config.ts +30 -0
- package/template/ts/src/lib/swagger/SWAGGER_GUIDE.md +561 -0
- package/template/ts/src/middlewares/errorHandler.ts +15 -23
- package/template/ts/src/middlewares/requestLogger.ts +1 -1
- package/template/ts/src/middlewares/validateRequest.ts +1 -1
- package/template/ts/src/modules/auth/auth.constants.ts +6 -0
- package/template/ts/src/modules/auth/auth.controller.ts +32 -0
- package/template/ts/src/modules/auth/auth.middlewares.ts +46 -0
- package/template/ts/src/modules/auth/auth.routes.ts +52 -0
- package/template/ts/src/modules/auth/auth.schemas.ts +73 -0
- package/template/ts/src/modules/auth/auth.service.ts +106 -0
- package/template/ts/src/modules/auth/package.json +10 -0
- package/template/ts/src/modules/health/controller.ts +61 -45
- package/template/ts/src/modules/swagger/charcole-swagger-1.0.0.tgz +0 -0
- package/template/ts/src/modules/swagger/package.json +5 -0
- package/template/ts/src/repositories/user.repo.ts +33 -0
- package/template/ts/src/routes/index.ts +24 -0
- package/template/ts/src/routes/protected.ts +46 -0
- package/template/ts/src/server.ts +3 -4
- package/template/ts/src/utils/logger.ts +1 -1
- package/template/ts/tsconfig.json +14 -7
- package/tmpclaude-1049-cwd +1 -0
- package/tmpclaude-3e37-cwd +1 -0
- package/tmpclaude-4d73-cwd +1 -0
- package/tmpclaude-8a8e-cwd +1 -0
- package/template/js/ARCHITECTURE_DIAGRAMS.md +0 -283
- package/template/js/CHECKLIST.md +0 -279
- package/template/js/COMPLETE.md +0 -405
- package/template/js/ERROR_HANDLING.md +0 -393
- package/template/js/IMPLEMENTATION.md +0 -368
- package/template/js/IMPLEMENTATION_COMPLETE.md +0 -363
- package/template/js/INDEX.md +0 -290
- package/template/js/QUICK_REFERENCE.md +0 -270
- package/template/js/package.json +0 -28
- package/template/js/src/routes.js +0 -17
- package/template/js/test-api.js +0 -100
- package/template/ts/ARCHITECTURE_DIAGRAMS.md +0 -283
- package/template/ts/CHECKLIST.md +0 -279
- package/template/ts/COMPLETE.md +0 -405
- package/template/ts/ERROR_HANDLING.md +0 -393
- package/template/ts/IMPLEMENTATION.md +0 -368
- package/template/ts/IMPLEMENTATION_COMPLETE.md +0 -363
- package/template/ts/INDEX.md +0 -290
- package/template/ts/QUICK_REFERENCE.md +0 -270
- package/template/ts/package.json +0 -32
- package/template/ts/src/app.js +0 -75
- package/template/ts/src/config/constants.js +0 -20
- package/template/ts/src/config/env.js +0 -26
- package/template/ts/src/middlewares/errorHandler.js +0 -180
- package/template/ts/src/middlewares/requestLogger.js +0 -33
- package/template/ts/src/middlewares/validateRequest.js +0 -42
- package/template/ts/src/modules/health/controller.js +0 -50
- package/template/ts/src/routes.js +0 -17
- package/template/ts/src/routes.ts +0 -16
- package/template/ts/src/server.js +0 -38
- package/template/ts/src/utils/AppError.js +0 -182
- package/template/ts/src/utils/logger.js +0 -73
- package/template/ts/src/utils/response.js +0 -51
- package/template/ts/test-api.js +0 -100
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@charcoles/swagger",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Auto-generated Swagger documentation for Charcole APIs",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"types": "src/index.d.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./src/index.d.ts",
|
|
11
|
+
"default": "./src/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"test": "echo \"No tests yet\" && exit 0",
|
|
16
|
+
"prepare": "echo 'Ready for npm link'"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"swagger-ui-express": "^4.6.3",
|
|
20
|
+
"swagger-jsdoc": "^6.2.8",
|
|
21
|
+
"glob": "^10.3.10",
|
|
22
|
+
"zod-to-json-schema": "^3.22.4"
|
|
23
|
+
},
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"zod": "^3.0.0"
|
|
26
|
+
},
|
|
27
|
+
"peerDependenciesMeta": {
|
|
28
|
+
"zod": {
|
|
29
|
+
"optional": true
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"charcole",
|
|
34
|
+
"swagger",
|
|
35
|
+
"openapi",
|
|
36
|
+
"documentation",
|
|
37
|
+
"express"
|
|
38
|
+
],
|
|
39
|
+
"author": "Sheraz Manzoor",
|
|
40
|
+
"license": "ISC",
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Convert Zod schema to OpenAPI JSON Schema
|
|
5
|
+
* @param {import('zod').ZodType} zodSchema - Zod schema to convert
|
|
6
|
+
* @param {string} name - Schema name for reference
|
|
7
|
+
* @returns {Object} OpenAPI-compatible JSON Schema
|
|
8
|
+
*/
|
|
9
|
+
export function convertZodToOpenAPI(zodSchema, name) {
|
|
10
|
+
if (!zodSchema) return null;
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
// Convert Zod to JSON Schema
|
|
14
|
+
const jsonSchema = zodToJsonSchema(zodSchema, {
|
|
15
|
+
name,
|
|
16
|
+
target: "openApi3",
|
|
17
|
+
$refStrategy: "none",
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// Remove $schema property as it's not needed in OpenAPI components
|
|
21
|
+
if (jsonSchema.$schema) {
|
|
22
|
+
delete jsonSchema.$schema;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Handle case where zodToJsonSchema returns a $ref with definitions
|
|
26
|
+
// This happens with complex nested schemas
|
|
27
|
+
if (jsonSchema.$ref && jsonSchema.definitions) {
|
|
28
|
+
// Extract the actual schema from definitions
|
|
29
|
+
const refName = jsonSchema.$ref.split("/").pop();
|
|
30
|
+
if (jsonSchema.definitions[refName]) {
|
|
31
|
+
const actualSchema = jsonSchema.definitions[refName];
|
|
32
|
+
// Remove $schema from the extracted definition too
|
|
33
|
+
if (actualSchema.$schema) {
|
|
34
|
+
delete actualSchema.$schema;
|
|
35
|
+
}
|
|
36
|
+
return actualSchema;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Remove definitions if present (OpenAPI handles these at component level)
|
|
41
|
+
if (jsonSchema.definitions) {
|
|
42
|
+
delete jsonSchema.definitions;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return jsonSchema;
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.warn(`Failed to convert Zod schema "${name}":`, error.message);
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Extract body schema from a Zod schema that has a .body property
|
|
54
|
+
* Common pattern: z.object({ body: z.object({ ... }) })
|
|
55
|
+
* @param {import('zod').ZodType} schema - Zod schema
|
|
56
|
+
* @returns {import('zod').ZodType|null} Body schema if found
|
|
57
|
+
*/
|
|
58
|
+
export function extractBodySchema(schema) {
|
|
59
|
+
if (!schema) return null;
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
// Check if schema has a .body property (common pattern)
|
|
63
|
+
if (schema._def && schema._def.shape && schema._def.shape().body) {
|
|
64
|
+
return schema._def.shape().body;
|
|
65
|
+
}
|
|
66
|
+
} catch (error) {
|
|
67
|
+
// If extraction fails, return original schema
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return schema;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Generate common response schemas
|
|
75
|
+
* @returns {Object} OpenAPI response components
|
|
76
|
+
*/
|
|
77
|
+
export function getCommonResponses() {
|
|
78
|
+
return {
|
|
79
|
+
Success: {
|
|
80
|
+
description: "Success",
|
|
81
|
+
content: {
|
|
82
|
+
"application/json": {
|
|
83
|
+
schema: {
|
|
84
|
+
type: "object",
|
|
85
|
+
properties: {
|
|
86
|
+
success: {
|
|
87
|
+
type: "boolean",
|
|
88
|
+
example: true,
|
|
89
|
+
},
|
|
90
|
+
message: {
|
|
91
|
+
type: "string",
|
|
92
|
+
example: "Operation successful",
|
|
93
|
+
},
|
|
94
|
+
data: {
|
|
95
|
+
type: "object",
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
ValidationError: {
|
|
103
|
+
description: "Validation Error",
|
|
104
|
+
content: {
|
|
105
|
+
"application/json": {
|
|
106
|
+
schema: {
|
|
107
|
+
type: "object",
|
|
108
|
+
properties: {
|
|
109
|
+
success: {
|
|
110
|
+
type: "boolean",
|
|
111
|
+
example: false,
|
|
112
|
+
},
|
|
113
|
+
message: {
|
|
114
|
+
type: "string",
|
|
115
|
+
example: "Validation failed",
|
|
116
|
+
},
|
|
117
|
+
errors: {
|
|
118
|
+
type: "array",
|
|
119
|
+
items: {
|
|
120
|
+
type: "object",
|
|
121
|
+
properties: {
|
|
122
|
+
field: {
|
|
123
|
+
type: "string",
|
|
124
|
+
},
|
|
125
|
+
message: {
|
|
126
|
+
type: "string",
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
Unauthorized: {
|
|
137
|
+
description: "Unauthorized - Invalid or missing token",
|
|
138
|
+
content: {
|
|
139
|
+
"application/json": {
|
|
140
|
+
schema: {
|
|
141
|
+
type: "object",
|
|
142
|
+
properties: {
|
|
143
|
+
success: {
|
|
144
|
+
type: "boolean",
|
|
145
|
+
example: false,
|
|
146
|
+
},
|
|
147
|
+
message: {
|
|
148
|
+
type: "string",
|
|
149
|
+
example: "Unauthorized",
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
Forbidden: {
|
|
157
|
+
description: "Forbidden - Insufficient permissions",
|
|
158
|
+
content: {
|
|
159
|
+
"application/json": {
|
|
160
|
+
schema: {
|
|
161
|
+
type: "object",
|
|
162
|
+
properties: {
|
|
163
|
+
success: {
|
|
164
|
+
type: "boolean",
|
|
165
|
+
example: false,
|
|
166
|
+
},
|
|
167
|
+
message: {
|
|
168
|
+
type: "string",
|
|
169
|
+
example: "Forbidden",
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
NotFound: {
|
|
177
|
+
description: "Resource not found",
|
|
178
|
+
content: {
|
|
179
|
+
"application/json": {
|
|
180
|
+
schema: {
|
|
181
|
+
type: "object",
|
|
182
|
+
properties: {
|
|
183
|
+
success: {
|
|
184
|
+
type: "boolean",
|
|
185
|
+
example: false,
|
|
186
|
+
},
|
|
187
|
+
message: {
|
|
188
|
+
type: "string",
|
|
189
|
+
example: "Resource not found",
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
InternalError: {
|
|
197
|
+
description: "Internal server error",
|
|
198
|
+
content: {
|
|
199
|
+
"application/json": {
|
|
200
|
+
schema: {
|
|
201
|
+
type: "object",
|
|
202
|
+
properties: {
|
|
203
|
+
success: {
|
|
204
|
+
type: "boolean",
|
|
205
|
+
example: false,
|
|
206
|
+
},
|
|
207
|
+
message: {
|
|
208
|
+
type: "string",
|
|
209
|
+
example: "Internal server error",
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Detect security requirements from middleware chain
|
|
221
|
+
* @param {Array} middlewares - Array of middleware functions
|
|
222
|
+
* @returns {Array} Security requirements
|
|
223
|
+
*/
|
|
224
|
+
export function detectSecurity(middlewares) {
|
|
225
|
+
if (!Array.isArray(middlewares)) return [];
|
|
226
|
+
|
|
227
|
+
const securityRequirements = [];
|
|
228
|
+
|
|
229
|
+
for (const middleware of middlewares) {
|
|
230
|
+
const name = middleware.name || "";
|
|
231
|
+
|
|
232
|
+
// Check for authentication middleware
|
|
233
|
+
if (
|
|
234
|
+
name.includes("auth") ||
|
|
235
|
+
name.includes("Auth") ||
|
|
236
|
+
name.includes("requireAuth") ||
|
|
237
|
+
name.includes("authenticate") ||
|
|
238
|
+
name.includes("jwt") ||
|
|
239
|
+
name.includes("JWT")
|
|
240
|
+
) {
|
|
241
|
+
securityRequirements.push({ bearerAuth: [] });
|
|
242
|
+
break; // Only add once
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return securityRequirements;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Create a minimal Swagger doc helper
|
|
251
|
+
* Generates a @swagger comment block from simple options
|
|
252
|
+
* @param {Object} options - Documentation options
|
|
253
|
+
* @returns {string} JSDoc comment block
|
|
254
|
+
*/
|
|
255
|
+
export function createSwaggerDoc(options) {
|
|
256
|
+
const {
|
|
257
|
+
method = "get",
|
|
258
|
+
path,
|
|
259
|
+
summary,
|
|
260
|
+
description,
|
|
261
|
+
tags = [],
|
|
262
|
+
requestSchema,
|
|
263
|
+
responseSchemaName,
|
|
264
|
+
security = false,
|
|
265
|
+
parameters = [],
|
|
266
|
+
} = options;
|
|
267
|
+
|
|
268
|
+
let doc = `/**\n * @swagger\n * ${path}:\n * ${method.toLowerCase()}:\n`;
|
|
269
|
+
|
|
270
|
+
if (summary) {
|
|
271
|
+
doc += ` * summary: ${summary}\n`;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (description) {
|
|
275
|
+
doc += ` * description: ${description}\n`;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (tags.length > 0) {
|
|
279
|
+
doc += ` * tags:\n`;
|
|
280
|
+
tags.forEach((tag) => {
|
|
281
|
+
doc += ` * - ${tag}\n`;
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (security) {
|
|
286
|
+
doc += ` * security:\n * - bearerAuth: []\n`;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (parameters.length > 0) {
|
|
290
|
+
doc += ` * parameters:\n`;
|
|
291
|
+
parameters.forEach((param) => {
|
|
292
|
+
doc += ` * - in: ${param.in}\n`;
|
|
293
|
+
doc += ` * name: ${param.name}\n`;
|
|
294
|
+
if (param.required) {
|
|
295
|
+
doc += ` * required: true\n`;
|
|
296
|
+
}
|
|
297
|
+
doc += ` * schema:\n`;
|
|
298
|
+
doc += ` * type: ${param.type || "string"}\n`;
|
|
299
|
+
if (param.description) {
|
|
300
|
+
doc += ` * description: ${param.description}\n`;
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (
|
|
306
|
+
requestSchema &&
|
|
307
|
+
(method === "post" || method === "put" || method === "patch")
|
|
308
|
+
) {
|
|
309
|
+
doc += ` * requestBody:\n`;
|
|
310
|
+
doc += ` * required: true\n`;
|
|
311
|
+
doc += ` * content:\n`;
|
|
312
|
+
doc += ` * application/json:\n`;
|
|
313
|
+
doc += ` * schema:\n`;
|
|
314
|
+
doc += ` * $ref: '#/components/schemas/${requestSchema}'\n`;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
doc += ` * responses:\n`;
|
|
318
|
+
if (responseSchemaName) {
|
|
319
|
+
doc += ` * 200:\n`;
|
|
320
|
+
doc += ` * $ref: '#/components/responses/${responseSchemaName}'\n`;
|
|
321
|
+
} else {
|
|
322
|
+
doc += ` * 200:\n`;
|
|
323
|
+
doc += ` * description: Success\n`;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (method === "post" || method === "put" || method === "patch") {
|
|
327
|
+
doc += ` * 400:\n`;
|
|
328
|
+
doc += ` * $ref: '#/components/responses/ValidationError'\n`;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (security) {
|
|
332
|
+
doc += ` * 401:\n`;
|
|
333
|
+
doc += ` * $ref: '#/components/responses/Unauthorized'\n`;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
doc += ` */`;
|
|
337
|
+
|
|
338
|
+
return doc;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Register Zod schemas for auto-documentation
|
|
343
|
+
* @param {Object} schemas - Object containing Zod schemas
|
|
344
|
+
* @returns {Object} OpenAPI components schemas
|
|
345
|
+
*/
|
|
346
|
+
export function registerSchemas(schemas) {
|
|
347
|
+
const components = {};
|
|
348
|
+
|
|
349
|
+
for (const [name, schema] of Object.entries(schemas)) {
|
|
350
|
+
if (!schema) continue;
|
|
351
|
+
|
|
352
|
+
// Extract body schema if it exists
|
|
353
|
+
const actualSchema = extractBodySchema(schema) || schema;
|
|
354
|
+
|
|
355
|
+
// Convert to OpenAPI
|
|
356
|
+
const converted = convertZodToOpenAPI(actualSchema, name);
|
|
357
|
+
if (converted) {
|
|
358
|
+
components[name] = converted;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return components;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Simplified API for quick endpoint documentation
|
|
367
|
+
* @param {string} method - HTTP method
|
|
368
|
+
* @param {string} path - Endpoint path
|
|
369
|
+
* @param {Object} options - Documentation options
|
|
370
|
+
* @returns {Object} OpenAPI path object
|
|
371
|
+
*/
|
|
372
|
+
export function endpoint(method, path, options = {}) {
|
|
373
|
+
const {
|
|
374
|
+
summary,
|
|
375
|
+
description,
|
|
376
|
+
tags = [],
|
|
377
|
+
schema,
|
|
378
|
+
responseSchema,
|
|
379
|
+
security = false,
|
|
380
|
+
params = [],
|
|
381
|
+
} = options;
|
|
382
|
+
|
|
383
|
+
const pathItem = {
|
|
384
|
+
summary,
|
|
385
|
+
description,
|
|
386
|
+
tags,
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
if (security) {
|
|
390
|
+
pathItem.security = [{ bearerAuth: [] }];
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (params.length > 0) {
|
|
394
|
+
pathItem.parameters = params;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (schema && (method === "post" || method === "put" || method === "patch")) {
|
|
398
|
+
pathItem.requestBody = {
|
|
399
|
+
required: true,
|
|
400
|
+
content: {
|
|
401
|
+
"application/json": {
|
|
402
|
+
schema: { $ref: `#/components/schemas/${schema}` },
|
|
403
|
+
},
|
|
404
|
+
},
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
pathItem.responses = {
|
|
409
|
+
200: responseSchema
|
|
410
|
+
? { $ref: `#/components/responses/${responseSchema}` }
|
|
411
|
+
: { description: "Success" },
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
if (method === "post" || method === "put" || method === "patch") {
|
|
415
|
+
pathItem.responses["400"] = {
|
|
416
|
+
$ref: "#/components/responses/ValidationError",
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (security) {
|
|
421
|
+
pathItem.responses["401"] = {
|
|
422
|
+
$ref: "#/components/responses/Unauthorized",
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
return { [path]: { [method.toLowerCase()]: pathItem } };
|
|
427
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { Application } from "express";
|
|
2
|
+
import { ZodType, ZodSchema } from "zod";
|
|
3
|
+
|
|
4
|
+
export interface SwaggerServer {
|
|
5
|
+
url: string;
|
|
6
|
+
description: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface SwaggerOptions {
|
|
10
|
+
title?: string;
|
|
11
|
+
version?: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
path?: string;
|
|
14
|
+
servers?: SwaggerServer[];
|
|
15
|
+
// NEW: Auto-register Zod schemas
|
|
16
|
+
schemas?: Record<string, ZodType<any>>;
|
|
17
|
+
// NEW: Include common response templates (default: true)
|
|
18
|
+
includeCommonResponses?: boolean;
|
|
19
|
+
// NEW: Custom response schemas
|
|
20
|
+
customResponses?: Record<string, any>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface OpenAPISpec {
|
|
24
|
+
openapi: string;
|
|
25
|
+
info: {
|
|
26
|
+
title: string;
|
|
27
|
+
version: string;
|
|
28
|
+
description: string;
|
|
29
|
+
};
|
|
30
|
+
servers: SwaggerServer[];
|
|
31
|
+
components: {
|
|
32
|
+
securitySchemes: {
|
|
33
|
+
bearerAuth: {
|
|
34
|
+
type: string;
|
|
35
|
+
scheme: string;
|
|
36
|
+
bearerFormat: string;
|
|
37
|
+
description: string;
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
schemas?: Record<string, any>;
|
|
41
|
+
responses?: Record<string, any>;
|
|
42
|
+
};
|
|
43
|
+
paths?: Record<string, any>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function setupSwagger(
|
|
47
|
+
app: Application,
|
|
48
|
+
options?: SwaggerOptions,
|
|
49
|
+
): OpenAPISpec;
|
|
50
|
+
|
|
51
|
+
// Helper functions
|
|
52
|
+
export interface EndpointParameter {
|
|
53
|
+
in: "path" | "query" | "header";
|
|
54
|
+
name: string;
|
|
55
|
+
required?: boolean;
|
|
56
|
+
type?: string;
|
|
57
|
+
description?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface EndpointOptions {
|
|
61
|
+
summary: string;
|
|
62
|
+
description?: string;
|
|
63
|
+
tags?: string[];
|
|
64
|
+
schema?: string;
|
|
65
|
+
responseSchema?: string;
|
|
66
|
+
security?: boolean;
|
|
67
|
+
params?: EndpointParameter[];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface SwaggerDocOptions {
|
|
71
|
+
method: string;
|
|
72
|
+
path: string;
|
|
73
|
+
summary: string;
|
|
74
|
+
description?: string;
|
|
75
|
+
tags?: string[];
|
|
76
|
+
requestSchema?: string;
|
|
77
|
+
responseSchemaName?: string;
|
|
78
|
+
security?: boolean;
|
|
79
|
+
parameters?: EndpointParameter[];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Convert Zod schema to OpenAPI JSON Schema
|
|
84
|
+
*/
|
|
85
|
+
export function convertZodToOpenAPI(
|
|
86
|
+
zodSchema: ZodType<any>,
|
|
87
|
+
name: string,
|
|
88
|
+
): object | null;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Extract body schema from a Zod schema that has a .body property
|
|
92
|
+
*/
|
|
93
|
+
export function extractBodySchema(schema: ZodType<any>): ZodType<any> | null;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Get common response schemas (Success, ValidationError, Unauthorized, etc.)
|
|
97
|
+
*/
|
|
98
|
+
export function getCommonResponses(): Record<string, any>;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Detect security requirements from middleware chain
|
|
102
|
+
*/
|
|
103
|
+
export function detectSecurity(
|
|
104
|
+
middlewares: Function[],
|
|
105
|
+
): Array<{ bearerAuth: [] }>;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Create a Swagger documentation comment block
|
|
109
|
+
*/
|
|
110
|
+
export function createSwaggerDoc(options: SwaggerDocOptions): string;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Register Zod schemas for auto-documentation
|
|
114
|
+
*/
|
|
115
|
+
export function registerSchemas(
|
|
116
|
+
schemas: Record<string, ZodType<any>>,
|
|
117
|
+
): Record<string, any>;
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Simplified API for quick endpoint documentation
|
|
121
|
+
*/
|
|
122
|
+
export function endpoint(
|
|
123
|
+
method: string,
|
|
124
|
+
path: string,
|
|
125
|
+
options?: EndpointOptions,
|
|
126
|
+
): Record<string, any>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { setupSwagger } from "./setup.js";
|
|
2
|
+
|
|
3
|
+
// Export helper functions for advanced usage
|
|
4
|
+
export {
|
|
5
|
+
convertZodToOpenAPI,
|
|
6
|
+
extractBodySchema,
|
|
7
|
+
getCommonResponses,
|
|
8
|
+
detectSecurity,
|
|
9
|
+
createSwaggerDoc,
|
|
10
|
+
registerSchemas,
|
|
11
|
+
endpoint,
|
|
12
|
+
} from "./helpers.js";
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import swaggerUi from "swagger-ui-express";
|
|
2
|
+
import swaggerJSDoc from "swagger-jsdoc";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { registerSchemas, getCommonResponses } from "./helpers.js";
|
|
6
|
+
|
|
7
|
+
export function setupSwagger(app, options = {}) {
|
|
8
|
+
const defaultOptions = {
|
|
9
|
+
title: "Charcole API",
|
|
10
|
+
version: "1.0.0",
|
|
11
|
+
description: "Auto-generated API documentation",
|
|
12
|
+
path: "/api-docs",
|
|
13
|
+
servers: [{ url: "http://localhost:3000", description: "Local server" }],
|
|
14
|
+
// NEW: Auto-register Zod schemas
|
|
15
|
+
schemas: {},
|
|
16
|
+
// NEW: Include common response templates
|
|
17
|
+
includeCommonResponses: true,
|
|
18
|
+
// NEW: Custom response schemas
|
|
19
|
+
customResponses: {},
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const config = { ...defaultOptions, ...options };
|
|
23
|
+
|
|
24
|
+
// Detect if running TypeScript or JavaScript by checking if src directory has .ts files
|
|
25
|
+
const srcPath = path.join(process.cwd(), "src");
|
|
26
|
+
const hasSrcDir = fs.existsSync(srcPath);
|
|
27
|
+
|
|
28
|
+
let isTypeScript = false;
|
|
29
|
+
if (hasSrcDir) {
|
|
30
|
+
// Check if there are any .ts files in src directory
|
|
31
|
+
const files = fs.readdirSync(srcPath);
|
|
32
|
+
isTypeScript = files.some((file) => file.endsWith(".ts"));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Determine file extensions to scan
|
|
36
|
+
const fileExtension = isTypeScript ? "ts" : "js";
|
|
37
|
+
|
|
38
|
+
// Build API paths based on project structure
|
|
39
|
+
const apiPaths = [
|
|
40
|
+
`${process.cwd()}/src/modules/**/*.${fileExtension}`,
|
|
41
|
+
`${process.cwd()}/src/routes/**/*.${fileExtension}`,
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
// NEW: Build components with auto-registered schemas
|
|
45
|
+
const components = {
|
|
46
|
+
securitySchemes: {
|
|
47
|
+
bearerAuth: {
|
|
48
|
+
type: "http",
|
|
49
|
+
scheme: "bearer",
|
|
50
|
+
bearerFormat: "JWT",
|
|
51
|
+
description: "Enter your JWT token in the format: your-token-here",
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
schemas: {},
|
|
55
|
+
responses: {},
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// NEW: Register Zod schemas if provided
|
|
59
|
+
if (config.schemas && Object.keys(config.schemas).length > 0) {
|
|
60
|
+
try {
|
|
61
|
+
const registeredSchemas = registerSchemas(config.schemas);
|
|
62
|
+
components.schemas = { ...registeredSchemas };
|
|
63
|
+
console.log(
|
|
64
|
+
`✅ Auto-registered ${Object.keys(registeredSchemas).length} Zod schemas`,
|
|
65
|
+
);
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.warn("⚠️ Failed to register some Zod schemas:", error.message);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// NEW: Add common response templates
|
|
72
|
+
if (config.includeCommonResponses) {
|
|
73
|
+
components.responses = {
|
|
74
|
+
...getCommonResponses(),
|
|
75
|
+
...config.customResponses,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const openApiSpec = swaggerJSDoc({
|
|
80
|
+
definition: {
|
|
81
|
+
openapi: "3.0.0",
|
|
82
|
+
info: {
|
|
83
|
+
title: config.title,
|
|
84
|
+
version: config.version,
|
|
85
|
+
description: config.description,
|
|
86
|
+
},
|
|
87
|
+
servers: config.servers,
|
|
88
|
+
components,
|
|
89
|
+
},
|
|
90
|
+
apis: apiPaths,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
app.use(config.path, swaggerUi.serve, swaggerUi.setup(openApiSpec));
|
|
94
|
+
|
|
95
|
+
console.log(
|
|
96
|
+
`✅ Swagger UI available at http://localhost:${process.env.PORT || 3000}${config.path}`,
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
return openApiSpec;
|
|
100
|
+
}
|