nextjs-swagger-autogen
Version:
Auto-generate Swagger documentation for Next.js API routes
188 lines (187 loc) • 7.45 kB
JavaScript
"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,
};
}