UNPKG

swagger-auto-generator-typescript

Version:

188 lines (187 loc) 8.82 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; const swagger_ui_express_1 = __importDefault(require("swagger-ui-express")); const yamljs_1 = __importDefault(require("yamljs")); const swagger_jsdoc_1 = __importDefault(require("swagger-jsdoc")); const fs_1 = __importDefault(require("fs")); const js_yaml_1 = __importDefault(require("js-yaml")); const path_1 = __importDefault(require("path")); function groupRoutesByBasePath(app, API_Pattern = "/v1") { const groupedRoutes = {}; const processStack = (stack, basePath = '') => { stack.forEach((layer) => { if (layer.route) { const basePath2 = basePath?.replace(API_Pattern, "")?.replace(/\/\?\(\?=\/\|\$\)/g, '')?.replace(/\/+/g, '/'); const methods = Object.keys(layer.route.methods).map(m => m.toUpperCase()).join(', '); const fullPath = (basePath + layer.route.path)?.replace(/\/\?\(\?=\/\|\$\)/g, '')?.replace(/\/+/g, '/'); ; const segments = basePath2.split('/'); // e.g. ['', 'v1', 'users'] let base; if (segments.length == 0) { base = '/'; // root } else { base = '/' + segments[1]; // like '/v1/users' → pick 'v1' } base = (base == "/undefined" ? "/" : base?.replace("/", "")); if (!groupedRoutes[base]) groupedRoutes[base] = []; groupedRoutes[base].push({ method: methods, path: fullPath, basePath: base, summary: 'Auto-discovered route', description: 'Dynamically extracted from Express', parameters: [ { $ref: '#/components/parameters/FrontEndUrl' } ], security: [ { BearerAuth: [] } ], tags: [base], responses: { '200': { description: 'A JSON object containing this route' } } }); } else if (layer.name === 'router' && layer.handle.stack) { const routePath = layer.regexp?.source.replace(/\\\//g, '/').replace(/\(\?:\(\?=\/\|\$\)\)\?\$/, '').replace(/^\^/, '') || ''; const newBasePath = basePath + routePath; processStack(layer.handle.stack, newBasePath); } }); }; processStack(app?._router?.stack); return groupedRoutes; } module.exports = { setup: function ({ Express, apiName, BackEndUrl, WEBSITE_URL, envName = 'local', pathName = './swagger-ui-express/swagger.yaml', components = {}, info = {}, swaggerOptions = {}, moreoptions = {}, API_PATTERN = "/v1" }) { if (!Express || typeof Express.use !== 'function') throw new Error("Invalid Express instance provided."); if (!apiName || typeof apiName !== 'string') throw new Error("API route base path (apiName) must be a valid string."); if (!BackEndUrl || typeof BackEndUrl !== 'string') throw new Error("BackEndUrl must be a valid URL string."); if (!WEBSITE_URL || typeof WEBSITE_URL !== 'string') throw new Error("WEBSITE_URL is required."); const isValidPath = typeof pathName === 'string' && path_1.default.extname(pathName) === '.yaml'; if (!isValidPath) throw new Error("pathName must be a valid .yaml file path."); const dirPath = path_1.default.dirname(pathName); if (!fs_1.default.existsSync(dirPath)) fs_1.default.mkdirSync(dirPath, { recursive: true }); Express.use(apiName, swagger_ui_express_1.default.serve, (req, res, next) => { const groupedRoutes = groupRoutesByBasePath(Express, API_PATTERN); if (!groupedRoutes || typeof groupedRoutes !== 'object') throw new Error("Failed to group routes."); const swaggerPaths = {}; const swaggerTags = []; const addedTagNames = new Set(); Object.values(groupedRoutes).flat().forEach((route) => { const cleanPath = route.path.replace('/?(?=/|$)', '').replace(/\/+/g, '/'); const method = route.method.toLowerCase(); if (!swaggerPaths[cleanPath]) swaggerPaths[cleanPath] = {}; if (route.basePath && !addedTagNames.has(route.basePath)) { swaggerTags.push({ name: route.basePath, description: `Endpoints related to ${route.basePath} operations` }); addedTagNames.add(route.basePath); } swaggerPaths[cleanPath][method] = { summary: route.summary, description: route.description, parameters: route.parameters, security: route.security, tags: route.tags, responses: route.responses, ...(method === "post" || method === "put" || method === "patch" ? { requestBody: { required: false, content: { 'application/json': { schema: { type: 'object', properties: {} } } } } } : {}) }; }); const swaggerDoc = { openapi: '3.0.0', info: { title: 'Auto-generated Swagger', version: '1.0.0', ...info }, servers: [{ url: BackEndUrl, description: `${envName} server` }], tags: swaggerTags, paths: swaggerPaths, components: { parameters: { FrontEndUrl: { name: 'FrontEndUrl', in: 'header', required: false, deprecated: true, 'x-deprecated': true, schema: { type: 'string', example: WEBSITE_URL }, description: WEBSITE_URL } }, securitySchemes: { BearerAuth: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' } }, ...components } }; try { const yamlStr = js_yaml_1.default.dump(swaggerDoc, { lineWidth: -1 }); fs_1.default.writeFileSync(pathName, yamlStr, 'utf8'); console.log('✅ Swagger YAML written successfully!'); const swaggerDocument = yamljs_1.default.load(pathName); const options = { definition: swaggerDocument, apis: ['./**/routes.ts'], // Update for TypeScript ...moreoptions }; const swaggerSpec = (0, swagger_jsdoc_1.default)(options); swagger_ui_express_1.default.setup(swaggerSpec, { swaggerOptions: { authActions: { bearerAuth: { name: 'Authorization', schema: { type: 'apiKey', in: 'header', name: 'Authorization', description: "JWT token in the format 'Bearer <token>'" }, value: '<JWT token>' } }, ...swaggerOptions } })(req, res, next); } catch (err) { console.error('❌ Error writing Swagger YAML:', err); } }); } };