UNPKG

nextjs-swagger-autogen

Version:

Auto-generate Swagger documentation for Next.js API routes

188 lines (187 loc) 7.45 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.generateOpenApiSpec = generateOpenApiSpec; const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const defaultConfig = { openapi: "3.0.0", info: { title: "Next.js API Documentation", version: "1.0.0", description: "Auto-generated Swagger documentation for Next.js API routes", }, servers: [ { url: "http://localhost:3000/api", description: "Development server", }, ], }; const defaultMethods = ["GET", "POST", "PUT", "DELETE", "PATCH"]; function generateOpenApiSpec(options = {}) { const { apiPath = path_1.default.join(process.cwd(), "src/app/api"), config = {}, includeMethods = defaultMethods, excludePaths = [], customPaths = {}, } = options; const mergedConfig = { ...defaultConfig, ...config, info: { ...defaultConfig.info, ...config.info, }, servers: config.servers || defaultConfig.servers, }; const paths = { ...customPaths }; function extractMethodsFromFile(filePath) { try { const content = fs_1.default.readFileSync(filePath, "utf-8"); const methods = []; // Look for exported functions that match HTTP methods const methodRegex = /export\s+(?:async\s+)?function\s+(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s*\(/g; let match; while ((match = methodRegex.exec(content)) !== null) { if (includeMethods.includes(match[1])) { methods.push(match[1]); } } return methods; } catch (error) { console.warn(`Could not read file ${filePath}:`, error); return []; } } function generateMethodSpec(method, urlPath) { const methodLower = method.toLowerCase(); return { [methodLower]: { summary: `${method} ${urlPath}`, description: `${method} endpoint for ${urlPath}`, tags: [getTagFromPath(urlPath)], parameters: extractDynamicParams(urlPath), responses: { 200: { description: "Successful response", content: { "application/json": { schema: { type: "object", properties: { success: { type: "boolean", example: true, }, data: { type: "object", description: "Response data", }, }, }, }, }, }, 400: { description: "Bad Request", content: { "application/json": { schema: { type: "object", properties: { error: { type: "string", example: "Invalid request parameters", }, }, }, }, }, }, 500: { description: "Internal Server Error", content: { "application/json": { schema: { type: "object", properties: { error: { type: "string", example: "Internal server error", }, }, }, }, }, }, }, }, }; } function getTagFromPath(urlPath) { const segments = urlPath.split("/").filter(Boolean); return segments[0] || "default"; } function extractDynamicParams(urlPath) { const params = []; const dynamicSegments = urlPath.match(/\[([^\]]+)\]/g); if (dynamicSegments) { dynamicSegments.forEach((segment) => { const paramName = segment.replace(/[\[\]]/g, ""); const isOptional = paramName.includes("..."); const cleanName = paramName.replace("...", ""); params.push({ name: cleanName, in: "path", required: !isOptional, schema: { type: "string", }, description: `Dynamic path parameter: ${cleanName}`, }); }); } return params; } function walk(dir, parentRoute = "") { if (!fs_1.default.existsSync(dir)) { console.warn(`API directory not found: ${dir}`); return; } const entries = fs_1.default.readdirSync(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path_1.default.join(dir, entry.name); if (entry.isDirectory()) { walk(fullPath, path_1.default.join(parentRoute, entry.name)); } else if (entry.name === "route.ts" || entry.name === "route.js") { let urlPath = parentRoute.replace(/\/route$/, "").replace(/\/$/, "") || "/"; if (!urlPath.startsWith("/")) { urlPath = "/" + urlPath; } // Skip excluded paths if (excludePaths.some((excluded) => urlPath.includes(excluded))) { continue; } // Extract methods from the route file const methods = extractMethodsFromFile(fullPath); if (methods.length > 0) { paths[urlPath] = {}; methods.forEach((method) => { const methodSpec = generateMethodSpec(method, urlPath); Object.assign(paths[urlPath], methodSpec); }); } else { // Fallback: assume GET method if no methods found const methodSpec = generateMethodSpec("GET", urlPath); paths[urlPath] = methodSpec; } } } } walk(apiPath); return { ...mergedConfig, paths, }; }