swagger-auto-generator-typescript
Version:
188 lines (187 loc) • 8.82 kB
JavaScript
;
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);
}
});
}
};