next-openapi-gen 0.10.5 → 1.0.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.
Files changed (44) hide show
  1. package/dist/cli.d.ts +4 -0
  2. package/dist/cli.js +8599 -0
  3. package/dist/index.d.ts +18 -0
  4. package/dist/index.js +8645 -26
  5. package/dist/next/index.d.ts +1 -0
  6. package/dist/next/index.js +7965 -0
  7. package/dist/react-router/index.d.ts +1 -0
  8. package/dist/react-router/index.js +7134 -0
  9. package/dist/vite/index.d.ts +1 -0
  10. package/dist/vite/index.js +7134 -0
  11. package/package.json +102 -79
  12. package/{dist/components/rapidoc.js → templates/init/ui/nextjs/rapidoc.tsx} +16 -20
  13. package/templates/init/ui/nextjs/redoc.tsx +11 -0
  14. package/{dist/components/scalar.js → templates/init/ui/nextjs/scalar.tsx} +15 -21
  15. package/{dist/components/stoplight.js → templates/init/ui/nextjs/stoplight.tsx} +11 -17
  16. package/templates/init/ui/nextjs/swagger.tsx +17 -0
  17. package/templates/init/ui/reactrouter/rapidoc.tsx +15 -0
  18. package/templates/init/ui/reactrouter/redoc.tsx +9 -0
  19. package/templates/init/ui/reactrouter/scalar.tsx +14 -0
  20. package/templates/init/ui/reactrouter/stoplight.tsx +10 -0
  21. package/templates/init/ui/reactrouter/swagger.tsx +11 -0
  22. package/templates/init/ui/tanstack/rapidoc.tsx +21 -0
  23. package/templates/init/ui/tanstack/redoc.tsx +14 -0
  24. package/templates/init/ui/tanstack/scalar.tsx +19 -0
  25. package/templates/init/ui/tanstack/stoplight.tsx +15 -0
  26. package/templates/init/ui/tanstack/swagger.tsx +16 -0
  27. package/templates/init/ui/template-types.d.ts +9 -0
  28. package/README.md +0 -1047
  29. package/dist/commands/generate.js +0 -24
  30. package/dist/commands/init.js +0 -194
  31. package/dist/components/redoc.js +0 -17
  32. package/dist/components/swagger.js +0 -21
  33. package/dist/lib/app-router-strategy.js +0 -66
  34. package/dist/lib/drizzle-zod-processor.js +0 -329
  35. package/dist/lib/logger.js +0 -39
  36. package/dist/lib/openapi-generator.js +0 -171
  37. package/dist/lib/pages-router-strategy.js +0 -198
  38. package/dist/lib/route-processor.js +0 -349
  39. package/dist/lib/router-strategy.js +0 -1
  40. package/dist/lib/schema-processor.js +0 -1612
  41. package/dist/lib/utils.js +0 -283
  42. package/dist/lib/zod-converter.js +0 -2133
  43. package/dist/openapi-template.js +0 -99
  44. package/dist/types.js +0 -1
@@ -1,171 +0,0 @@
1
- import path from "path";
2
- import fs from "fs";
3
- import { RouteProcessor } from "./route-processor.js";
4
- import { cleanSpec } from "./utils.js";
5
- import { logger } from "./logger.js";
6
- export class OpenApiGenerator {
7
- config;
8
- template;
9
- routeProcessor;
10
- constructor(opts = {}) {
11
- const templatePath = opts.templatePath || path.resolve("./next.openapi.json");
12
- this.template = JSON.parse(fs.readFileSync(templatePath, "utf-8"));
13
- this.config = this.getConfig();
14
- this.routeProcessor = new RouteProcessor(this.config);
15
- // Initialize logger
16
- logger.init(this.config);
17
- }
18
- getConfig() {
19
- // @ts-ignore
20
- const { apiDir, routerType, schemaDir, docsUrl, ui, outputFile, outputDir, includeOpenApiRoutes, ignoreRoutes, schemaType = "typescript", schemaFiles, defaultResponseSet, responseSets, errorConfig, debug } = this.template;
21
- return {
22
- apiDir: apiDir || "./src/app/api",
23
- routerType: routerType || "app",
24
- schemaDir: schemaDir || "./src",
25
- docsUrl: docsUrl || "api-docs",
26
- ui: ui || "scalar",
27
- outputFile: outputFile || "openapi.json",
28
- outputDir: outputDir || "./public",
29
- includeOpenApiRoutes: includeOpenApiRoutes || false,
30
- ignoreRoutes: ignoreRoutes || [],
31
- schemaType,
32
- schemaFiles: schemaFiles || [],
33
- defaultResponseSet,
34
- responseSets,
35
- errorConfig,
36
- debug: debug || false,
37
- };
38
- }
39
- generate() {
40
- logger.log("Starting OpenAPI generation...");
41
- const { apiDir } = this.config;
42
- // Check if app router structure exists
43
- let appRouterApiDir = "";
44
- if (fs.existsSync(path.join(path.dirname(apiDir), "app", "api"))) {
45
- appRouterApiDir = path.join(path.dirname(apiDir), "app", "api");
46
- logger.debug(`Found app router API directory at ${appRouterApiDir}`);
47
- }
48
- // Scan pages router routes
49
- this.routeProcessor.scanApiRoutes(apiDir);
50
- // If app router directory exists, scan it as well
51
- if (appRouterApiDir) {
52
- this.routeProcessor.scanApiRoutes(appRouterApiDir);
53
- }
54
- this.template.paths = this.routeProcessor.getSwaggerPaths();
55
- // Add server URL for examples if not already defined
56
- if (!this.template.servers || this.template.servers.length === 0) {
57
- this.template.servers = [
58
- {
59
- url: this.template.basePath || "",
60
- description: "API server",
61
- },
62
- ];
63
- }
64
- // Ensure there's a components section if not already defined
65
- if (!this.template.components) {
66
- this.template.components = {};
67
- }
68
- // Add schemas section if not already defined
69
- if (!this.template.components.schemas) {
70
- this.template.components.schemas = {};
71
- }
72
- // Generate error responses using errorConfig or manual definitions
73
- if (!this.template.components.responses) {
74
- this.template.components.responses = {};
75
- }
76
- const errorConfig = this.config.errorConfig;
77
- if (errorConfig) {
78
- this.generateErrorResponsesFromConfig(errorConfig);
79
- }
80
- else if (this.config.errorDefinitions) {
81
- // Use manual definitions (existing logic - if exists)
82
- Object.entries(this.config.errorDefinitions).forEach(([code, errorDef]) => {
83
- this.template.components.responses[code] =
84
- this.createErrorResponseComponent(code, errorDef);
85
- });
86
- }
87
- // Get defined schemas from the processor
88
- const definedSchemas = this.routeProcessor
89
- .getSchemaProcessor()
90
- .getDefinedSchemas();
91
- if (definedSchemas && Object.keys(definedSchemas).length > 0) {
92
- this.template.components.schemas = {
93
- ...this.template.components.schemas,
94
- ...definedSchemas,
95
- };
96
- }
97
- const openapiSpec = cleanSpec(this.template);
98
- logger.log("OpenAPI generation completed");
99
- return openapiSpec;
100
- }
101
- generateErrorResponsesFromConfig(errorConfig) {
102
- const { template, codes, variables: globalVars = {} } = errorConfig;
103
- Object.entries(codes).forEach(([errorCode, config]) => {
104
- const httpStatus = (config.httpStatus || this.guessHttpStatus(errorCode)).toString();
105
- // Merge variables: global + per-code + built-in
106
- const allVariables = {
107
- ...globalVars,
108
- ...config.variables,
109
- ERROR_CODE: errorCode,
110
- DESCRIPTION: config.description,
111
- HTTP_STATUS: httpStatus,
112
- };
113
- const processedSchema = this.processTemplate(template, allVariables);
114
- this.template.components.responses[httpStatus] = {
115
- description: config.description,
116
- content: {
117
- "application/json": {
118
- schema: processedSchema,
119
- },
120
- },
121
- };
122
- });
123
- }
124
- processTemplate(template, variables) {
125
- const jsonStr = JSON.stringify(template);
126
- let result = jsonStr;
127
- Object.entries(variables).forEach(([key, value]) => {
128
- result = result.replace(new RegExp(`{{${key}}}`, "g"), value);
129
- });
130
- return JSON.parse(result);
131
- }
132
- guessHttpStatus(errorCode) {
133
- const numericCode = parseInt(errorCode);
134
- if (numericCode >= 100 && numericCode < 600) {
135
- return numericCode;
136
- }
137
- const statusMap = {
138
- bad: 400,
139
- invalid: 400,
140
- validation: 422,
141
- unauthorized: 401,
142
- auth: 401,
143
- forbidden: 403,
144
- permission: 403,
145
- not_found: 404,
146
- missing: 404,
147
- conflict: 409,
148
- duplicate: 409,
149
- rate_limit: 429,
150
- too_many: 429,
151
- server: 500,
152
- internal: 500,
153
- };
154
- for (const [key, status] of Object.entries(statusMap)) {
155
- if (errorCode.toLowerCase().includes(key)) {
156
- return status;
157
- }
158
- }
159
- return 500;
160
- }
161
- createErrorResponseComponent(code, errorDef) {
162
- return {
163
- description: errorDef.description,
164
- content: {
165
- "application/json": {
166
- schema: errorDef.schema,
167
- },
168
- },
169
- };
170
- }
171
- }
@@ -1,198 +0,0 @@
1
- import fs from "fs";
2
- import traverseModule from "@babel/traverse";
3
- const traverse = traverseModule.default || traverseModule;
4
- import { HTTP_METHODS } from "./router-strategy.js";
5
- import { parseTypeScriptFile, performAuthPresetReplacements } from "./utils.js";
6
- export class PagesRouterStrategy {
7
- config;
8
- constructor(config) {
9
- this.config = config;
10
- }
11
- shouldProcessFile(fileName) {
12
- return (!fileName.startsWith("_") &&
13
- (fileName.endsWith(".ts") || fileName.endsWith(".tsx")));
14
- }
15
- processFile(filePath, addRoute) {
16
- const content = fs.readFileSync(filePath, "utf-8");
17
- const ast = parseTypeScriptFile(content);
18
- const methodComments = [];
19
- traverse(ast, {
20
- ExportDefaultDeclaration: (nodePath) => {
21
- const allComments = ast.comments || [];
22
- const exportStart = nodePath.node.start || 0;
23
- allComments.forEach((comment) => {
24
- if (comment.type === "CommentBlock" &&
25
- (comment.end || 0) < exportStart) {
26
- const commentValue = comment.value;
27
- if (commentValue.includes("@method")) {
28
- const dataTypes = this.extractJSDocFromComment(commentValue);
29
- if (dataTypes.method && HTTP_METHODS.includes(dataTypes.method)) {
30
- methodComments.push({
31
- method: dataTypes.method,
32
- dataTypes,
33
- });
34
- }
35
- }
36
- }
37
- });
38
- methodComments.forEach(({ method, dataTypes }) => {
39
- addRoute(method, filePath, dataTypes);
40
- });
41
- },
42
- });
43
- }
44
- getRoutePath(filePath) {
45
- const normalizedPath = filePath.replaceAll("\\", "/");
46
- const normalizedApiDir = this.config.apiDir
47
- .replaceAll("\\", "/")
48
- .replace(/^\.\//, "")
49
- .replace(/\/$/, "");
50
- const apiDirIndex = normalizedPath.indexOf(normalizedApiDir);
51
- if (apiDirIndex === -1) {
52
- throw new Error(`Could not find apiDir "${this.config.apiDir}" in file path "${filePath}"`);
53
- }
54
- let relativePath = normalizedPath.substring(apiDirIndex + normalizedApiDir.length);
55
- // Remove the file extension (.ts or .tsx)
56
- relativePath = relativePath.replace(/\.tsx?$/, "");
57
- // Remove /index suffix (pages/api/users/index.ts -> /users)
58
- relativePath = relativePath.replace(/\/index$/, "");
59
- if (!relativePath.startsWith("/")) {
60
- relativePath = "/" + relativePath;
61
- }
62
- relativePath = relativePath.replace(/\/$/, "");
63
- // Handle catch-all routes before dynamic routes
64
- relativePath = relativePath.replace(/\/\[\.\.\.(.*?)\]/g, "/{$1}");
65
- // Convert Next.js dynamic route syntax to OpenAPI parameter syntax
66
- relativePath = relativePath.replace(/\/\[([^\]]+)\]/g, "/{$1}");
67
- return relativePath || "/";
68
- }
69
- /**
70
- * Extract JSDoc data from a raw comment string (Pages Router specific)
71
- */
72
- extractJSDocFromComment(commentValue) {
73
- const cleanedComment = commentValue.replace(/\*\s*/g, "").trim();
74
- let tag = "";
75
- let summary = "";
76
- let description = "";
77
- let paramsType = "";
78
- let pathParamsType = "";
79
- let bodyType = "";
80
- let auth = "";
81
- let isOpenApi = cleanedComment.includes("@openapi");
82
- let isIgnored = cleanedComment.includes("@ignore");
83
- let deprecated = cleanedComment.includes("@deprecated");
84
- let bodyDescription = "";
85
- let contentType = "";
86
- let responseType = "";
87
- let responseDescription = "";
88
- let responseSet = "";
89
- let addResponses = "";
90
- let successCode = "";
91
- let operationId = "";
92
- let method = "";
93
- const methodMatch = cleanedComment.match(/@method\s+(\S+)/);
94
- if (methodMatch) {
95
- method = methodMatch[1].trim().toUpperCase();
96
- }
97
- const firstLine = cleanedComment.split("\n")[0];
98
- if (!firstLine.trim().startsWith("@")) {
99
- summary = firstLine.trim();
100
- }
101
- const descMatch = cleanedComment.match(/@description\s+(.*)/);
102
- if (descMatch) {
103
- description = descMatch[1].trim();
104
- }
105
- const tagMatch = cleanedComment.match(/@tag\s+(.*)/);
106
- if (tagMatch) {
107
- tag = tagMatch[1].trim();
108
- }
109
- const paramsMatch = cleanedComment.match(/@queryParams\s+([\w<>,\s\[\]]+)/) ||
110
- cleanedComment.match(/@params\s+([\w<>,\s\[\]]+)/);
111
- if (paramsMatch) {
112
- paramsType = paramsMatch[1].trim();
113
- }
114
- const pathParamsMatch = cleanedComment.match(/@pathParams\s+([\w<>,\s\[\]]+)/);
115
- if (pathParamsMatch) {
116
- pathParamsType = pathParamsMatch[1].trim();
117
- }
118
- const bodyMatch = cleanedComment.match(/@body\s+([\w<>,\s\[\]]+)/);
119
- if (bodyMatch) {
120
- bodyType = bodyMatch[1].trim();
121
- }
122
- const bodyDescMatch = cleanedComment.match(/@bodyDescription\s+(.*)/);
123
- if (bodyDescMatch) {
124
- bodyDescription = bodyDescMatch[1].trim();
125
- }
126
- const contentTypeMatch = cleanedComment.match(/@contentType\s+(.*)/);
127
- if (contentTypeMatch) {
128
- contentType = contentTypeMatch[1].trim();
129
- }
130
- const responseMatch = cleanedComment.match(/@response\s+(?:(\d+):)?([^@\n\r]+)/);
131
- if (responseMatch) {
132
- const [, code, type] = responseMatch;
133
- const trimmedType = type?.trim();
134
- if (!code && trimmedType && /^\d{3}$/.test(trimmedType)) {
135
- successCode = trimmedType;
136
- responseType = undefined;
137
- }
138
- else {
139
- successCode = code || "";
140
- responseType = trimmedType;
141
- }
142
- }
143
- const respDescMatch = cleanedComment.match(/@responseDescription\s+(.*)/);
144
- if (respDescMatch) {
145
- responseDescription = respDescMatch[1].trim();
146
- }
147
- const respSetMatch = cleanedComment.match(/@responseSet\s+(.*)/);
148
- if (respSetMatch) {
149
- responseSet = respSetMatch[1].trim();
150
- }
151
- const addMatches = [...cleanedComment.matchAll(/@add\s+([^\n\r@]*)/g)];
152
- if (addMatches.length > 0) {
153
- addResponses = addMatches.map((m) => m[1].trim()).join(",");
154
- }
155
- const opIdMatch = cleanedComment.match(/@operationId\s+(\S+)/);
156
- if (opIdMatch) {
157
- operationId = opIdMatch[1].trim();
158
- }
159
- const authMatch = cleanedComment.match(/@auth\s+(.*)/);
160
- if (authMatch) {
161
- const authValue = authMatch[1].trim();
162
- switch (authValue) {
163
- case "bearer":
164
- auth = "BearerAuth";
165
- break;
166
- case "basic":
167
- auth = "BasicAuth";
168
- break;
169
- case "apikey":
170
- auth = "ApiKeyAuth";
171
- break;
172
- default:
173
- auth = performAuthPresetReplacements(authValue);
174
- }
175
- }
176
- return {
177
- tag,
178
- auth,
179
- summary,
180
- description,
181
- paramsType,
182
- pathParamsType,
183
- bodyType,
184
- isOpenApi,
185
- isIgnored,
186
- deprecated,
187
- bodyDescription,
188
- contentType,
189
- responseType,
190
- responseDescription,
191
- responseSet,
192
- addResponses,
193
- successCode,
194
- operationId,
195
- method,
196
- };
197
- }
198
- }