next-openapi-gen 0.10.4 → 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.
- package/dist/cli.d.ts +4 -0
- package/dist/cli.js +8599 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +8645 -26
- package/dist/next/index.d.ts +1 -0
- package/dist/next/index.js +7965 -0
- package/dist/react-router/index.d.ts +1 -0
- package/dist/react-router/index.js +7134 -0
- package/dist/vite/index.d.ts +1 -0
- package/dist/vite/index.js +7134 -0
- package/package.json +102 -79
- package/{dist/components/rapidoc.js → templates/init/ui/nextjs/rapidoc.tsx} +16 -20
- package/templates/init/ui/nextjs/redoc.tsx +11 -0
- package/{dist/components/scalar.js → templates/init/ui/nextjs/scalar.tsx} +15 -21
- package/{dist/components/stoplight.js → templates/init/ui/nextjs/stoplight.tsx} +11 -17
- package/templates/init/ui/nextjs/swagger.tsx +17 -0
- package/templates/init/ui/reactrouter/rapidoc.tsx +15 -0
- package/templates/init/ui/reactrouter/redoc.tsx +9 -0
- package/templates/init/ui/reactrouter/scalar.tsx +14 -0
- package/templates/init/ui/reactrouter/stoplight.tsx +10 -0
- package/templates/init/ui/reactrouter/swagger.tsx +11 -0
- package/templates/init/ui/tanstack/rapidoc.tsx +21 -0
- package/templates/init/ui/tanstack/redoc.tsx +14 -0
- package/templates/init/ui/tanstack/scalar.tsx +19 -0
- package/templates/init/ui/tanstack/stoplight.tsx +15 -0
- package/templates/init/ui/tanstack/swagger.tsx +16 -0
- package/templates/init/ui/template-types.d.ts +9 -0
- package/README.md +0 -1047
- package/dist/commands/generate.js +0 -24
- package/dist/commands/init.js +0 -194
- package/dist/components/redoc.js +0 -17
- package/dist/components/swagger.js +0 -21
- package/dist/lib/app-router-strategy.js +0 -66
- package/dist/lib/drizzle-zod-processor.js +0 -329
- package/dist/lib/logger.js +0 -39
- package/dist/lib/openapi-generator.js +0 -171
- package/dist/lib/pages-router-strategy.js +0 -198
- package/dist/lib/route-processor.js +0 -347
- package/dist/lib/router-strategy.js +0 -1
- package/dist/lib/schema-processor.js +0 -1612
- package/dist/lib/utils.js +0 -284
- package/dist/lib/zod-converter.js +0 -2133
- package/dist/openapi-template.js +0 -99
- package/dist/types.js +0 -1
|
@@ -1,347 +0,0 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import { SchemaProcessor } from "./schema-processor.js";
|
|
4
|
-
import { capitalize, extractPathParameters, getOperationId, } from "./utils.js";
|
|
5
|
-
import { logger } from "./logger.js";
|
|
6
|
-
import { AppRouterStrategy } from "./app-router-strategy.js";
|
|
7
|
-
import { PagesRouterStrategy } from "./pages-router-strategy.js";
|
|
8
|
-
const MUTATION_HTTP_METHODS = ["PATCH", "POST", "PUT"];
|
|
9
|
-
export class RouteProcessor {
|
|
10
|
-
swaggerPaths = {};
|
|
11
|
-
schemaProcessor;
|
|
12
|
-
config;
|
|
13
|
-
strategy;
|
|
14
|
-
directoryCache = {};
|
|
15
|
-
statCache = {};
|
|
16
|
-
processFileTracker = {};
|
|
17
|
-
constructor(config) {
|
|
18
|
-
this.config = config;
|
|
19
|
-
this.schemaProcessor = new SchemaProcessor(config.schemaDir, config.schemaType, config.schemaFiles, config.apiDir);
|
|
20
|
-
this.strategy = config.routerType === "pages"
|
|
21
|
-
? new PagesRouterStrategy(config)
|
|
22
|
-
: new AppRouterStrategy(config);
|
|
23
|
-
}
|
|
24
|
-
buildResponsesFromConfig(dataTypes, method) {
|
|
25
|
-
const responses = {};
|
|
26
|
-
// 1. Add success response
|
|
27
|
-
const successCode = dataTypes.successCode || this.getDefaultSuccessCode(method);
|
|
28
|
-
// Handle 204 No Content responses without a schema
|
|
29
|
-
if (successCode === "204" && !dataTypes.responseType) {
|
|
30
|
-
responses[successCode] = {
|
|
31
|
-
description: dataTypes.responseDescription || "No Content",
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
else if (dataTypes.responseType) {
|
|
35
|
-
// 204 No Content should not have a content section per HTTP/OpenAPI spec
|
|
36
|
-
if (successCode === "204") {
|
|
37
|
-
responses[successCode] = {
|
|
38
|
-
description: dataTypes.responseDescription || "No Content",
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
else {
|
|
42
|
-
// Handle array notation (e.g., "Type[]", "Type[][]", "Generic<T>[]")
|
|
43
|
-
let schema;
|
|
44
|
-
let baseType = dataTypes.responseType;
|
|
45
|
-
let arrayDepth = 0;
|
|
46
|
-
// Count and remove array brackets
|
|
47
|
-
while (baseType.endsWith('[]')) {
|
|
48
|
-
arrayDepth++;
|
|
49
|
-
baseType = baseType.slice(0, -2);
|
|
50
|
-
}
|
|
51
|
-
// Ensure the base schema is defined in components/schemas
|
|
52
|
-
this.schemaProcessor.getSchemaContent({
|
|
53
|
-
responseType: baseType,
|
|
54
|
-
});
|
|
55
|
-
// Build schema reference
|
|
56
|
-
if (arrayDepth === 0) {
|
|
57
|
-
// Not an array
|
|
58
|
-
schema = { $ref: `#/components/schemas/${baseType}` };
|
|
59
|
-
}
|
|
60
|
-
else {
|
|
61
|
-
// Build nested array schema
|
|
62
|
-
schema = { $ref: `#/components/schemas/${baseType}` };
|
|
63
|
-
for (let i = 0; i < arrayDepth; i++) {
|
|
64
|
-
schema = {
|
|
65
|
-
type: "array",
|
|
66
|
-
items: schema,
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
responses[successCode] = {
|
|
71
|
-
description: dataTypes.responseDescription || "Successful response",
|
|
72
|
-
content: {
|
|
73
|
-
"application/json": {
|
|
74
|
-
schema: schema,
|
|
75
|
-
},
|
|
76
|
-
},
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
// 2. Add responses from ResponseSet
|
|
81
|
-
const responseSetName = dataTypes.responseSet || this.config.defaultResponseSet;
|
|
82
|
-
if (responseSetName && responseSetName !== "none") {
|
|
83
|
-
const responseSets = this.config.responseSets || {};
|
|
84
|
-
const setNames = responseSetName.split(",").map((s) => s.trim());
|
|
85
|
-
setNames.forEach((setName) => {
|
|
86
|
-
const responseSet = responseSets[setName];
|
|
87
|
-
if (responseSet) {
|
|
88
|
-
responseSet.forEach((errorCode) => {
|
|
89
|
-
// Use $ref for components/responses
|
|
90
|
-
responses[errorCode] = {
|
|
91
|
-
$ref: `#/components/responses/${errorCode}`,
|
|
92
|
-
};
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
// 3. Add custom responses (@add)
|
|
98
|
-
if (dataTypes.addResponses) {
|
|
99
|
-
const customResponses = dataTypes.addResponses
|
|
100
|
-
.split(",")
|
|
101
|
-
.map((s) => s.trim());
|
|
102
|
-
customResponses.forEach((responseRef) => {
|
|
103
|
-
const [code, ref] = responseRef.split(":");
|
|
104
|
-
if (ref) {
|
|
105
|
-
// Custom schema: "409:ConflictResponse"
|
|
106
|
-
// 204 No Content should not have a content section per HTTP/OpenAPI spec
|
|
107
|
-
if (code === "204") {
|
|
108
|
-
responses[code] = {
|
|
109
|
-
description: this.getDefaultErrorDescription(code) || "No Content",
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
else {
|
|
113
|
-
responses[code] = {
|
|
114
|
-
description: this.getDefaultErrorDescription(code) || `HTTP ${code} response`,
|
|
115
|
-
content: {
|
|
116
|
-
"application/json": {
|
|
117
|
-
schema: { $ref: `#/components/schemas/${ref}` },
|
|
118
|
-
},
|
|
119
|
-
},
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
else {
|
|
124
|
-
// Only code: "409" - use $ref fro components/responses
|
|
125
|
-
responses[code] = {
|
|
126
|
-
$ref: `#/components/responses/${code}`,
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
return responses;
|
|
132
|
-
}
|
|
133
|
-
getDefaultSuccessCode(method) {
|
|
134
|
-
switch (method.toUpperCase()) {
|
|
135
|
-
case "POST":
|
|
136
|
-
return "201";
|
|
137
|
-
case "DELETE":
|
|
138
|
-
return "204";
|
|
139
|
-
default:
|
|
140
|
-
return "200";
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
getDefaultErrorDescription(code) {
|
|
144
|
-
const defaults = {
|
|
145
|
-
400: "Bad Request",
|
|
146
|
-
401: "Unauthorized",
|
|
147
|
-
403: "Forbidden",
|
|
148
|
-
404: "Not Found",
|
|
149
|
-
409: "Conflict",
|
|
150
|
-
422: "Unprocessable Entity",
|
|
151
|
-
429: "Too Many Requests",
|
|
152
|
-
500: "Internal Server Error",
|
|
153
|
-
};
|
|
154
|
-
return defaults[code] || `HTTP ${code}`;
|
|
155
|
-
}
|
|
156
|
-
/**
|
|
157
|
-
* Get the SchemaProcessor instance
|
|
158
|
-
*/
|
|
159
|
-
getSchemaProcessor() {
|
|
160
|
-
return this.schemaProcessor;
|
|
161
|
-
}
|
|
162
|
-
/**
|
|
163
|
-
* Check if a route should be ignored based on config patterns or @ignore tag
|
|
164
|
-
*/
|
|
165
|
-
shouldIgnoreRoute(routePath, dataTypes) {
|
|
166
|
-
// Check if route has @ignore tag
|
|
167
|
-
if (dataTypes.isIgnored) {
|
|
168
|
-
return true;
|
|
169
|
-
}
|
|
170
|
-
// Check if route matches any ignore patterns
|
|
171
|
-
const ignorePatterns = this.config.ignoreRoutes || [];
|
|
172
|
-
if (ignorePatterns.length === 0) {
|
|
173
|
-
return false;
|
|
174
|
-
}
|
|
175
|
-
return ignorePatterns.some((pattern) => {
|
|
176
|
-
// Support wildcards
|
|
177
|
-
const regexPattern = pattern.replace(/\*/g, ".*").replace(/\//g, "\\/");
|
|
178
|
-
const regex = new RegExp(`^${regexPattern}$`);
|
|
179
|
-
return regex.test(routePath);
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
/**
|
|
183
|
-
* Register a discovered route after filtering
|
|
184
|
-
*/
|
|
185
|
-
registerRoute(method, filePath, dataTypes) {
|
|
186
|
-
const routePath = this.strategy.getRoutePath(filePath);
|
|
187
|
-
if (this.shouldIgnoreRoute(routePath, dataTypes)) {
|
|
188
|
-
logger.debug(`Ignoring route: ${routePath}`);
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
if (this.config.includeOpenApiRoutes && !dataTypes.isOpenApi) {
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
const pathParams = extractPathParameters(routePath);
|
|
195
|
-
if (pathParams.length > 0 && !dataTypes.pathParamsType) {
|
|
196
|
-
logger.debug(`Route ${routePath} contains path parameters ${pathParams.join(", ")} but no @pathParams type is defined.`);
|
|
197
|
-
}
|
|
198
|
-
this.addRouteToPaths(method, filePath, dataTypes);
|
|
199
|
-
}
|
|
200
|
-
scanApiRoutes(dir) {
|
|
201
|
-
logger.debug(`Scanning API routes in: ${dir}`);
|
|
202
|
-
let files = this.directoryCache[dir];
|
|
203
|
-
if (!files) {
|
|
204
|
-
files = fs.readdirSync(dir);
|
|
205
|
-
this.directoryCache[dir] = files;
|
|
206
|
-
}
|
|
207
|
-
files.forEach((file) => {
|
|
208
|
-
const filePath = path.join(dir, file);
|
|
209
|
-
let stat = this.statCache[filePath];
|
|
210
|
-
if (!stat) {
|
|
211
|
-
stat = fs.statSync(filePath);
|
|
212
|
-
this.statCache[filePath] = stat;
|
|
213
|
-
}
|
|
214
|
-
if (stat.isDirectory()) {
|
|
215
|
-
this.scanApiRoutes(filePath);
|
|
216
|
-
}
|
|
217
|
-
else if (this.strategy.shouldProcessFile(file)) {
|
|
218
|
-
if (!this.processFileTracker[filePath]) {
|
|
219
|
-
this.strategy.processFile(filePath, (method, fp, dataTypes) => {
|
|
220
|
-
this.registerRoute(method, fp, dataTypes);
|
|
221
|
-
});
|
|
222
|
-
this.processFileTracker[filePath] = true;
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
});
|
|
226
|
-
}
|
|
227
|
-
addRouteToPaths(varName, filePath, dataTypes) {
|
|
228
|
-
const method = varName.toLowerCase();
|
|
229
|
-
const routePath = this.strategy.getRoutePath(filePath);
|
|
230
|
-
const rootPath = capitalize(routePath.split("/")[1]);
|
|
231
|
-
const operationId = dataTypes.operationId || getOperationId(routePath, method);
|
|
232
|
-
const { tag, summary, description, auth, isOpenApi, deprecated, bodyDescription, responseDescription, } = dataTypes;
|
|
233
|
-
if (this.config.includeOpenApiRoutes && !isOpenApi) {
|
|
234
|
-
// If flag is enabled and there is no @openapi tag, then skip path
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
if (!this.swaggerPaths[routePath]) {
|
|
238
|
-
this.swaggerPaths[routePath] = {};
|
|
239
|
-
}
|
|
240
|
-
const { params, pathParams, body, responses } = this.schemaProcessor.getSchemaContent(dataTypes);
|
|
241
|
-
const definition = {
|
|
242
|
-
operationId: operationId,
|
|
243
|
-
summary: summary,
|
|
244
|
-
description: description,
|
|
245
|
-
tags: [tag || rootPath],
|
|
246
|
-
parameters: [],
|
|
247
|
-
};
|
|
248
|
-
if (deprecated) {
|
|
249
|
-
definition.deprecated = true;
|
|
250
|
-
}
|
|
251
|
-
// Add auth
|
|
252
|
-
if (auth) {
|
|
253
|
-
const authItems = auth.split(",").map(item => item.trim());
|
|
254
|
-
definition.security = authItems.map(authItem => ({
|
|
255
|
-
[authItem]: [],
|
|
256
|
-
}));
|
|
257
|
-
}
|
|
258
|
-
if (params) {
|
|
259
|
-
definition.parameters =
|
|
260
|
-
this.schemaProcessor.createRequestParamsSchema(params);
|
|
261
|
-
}
|
|
262
|
-
// Add path parameters
|
|
263
|
-
const pathParamNames = extractPathParameters(routePath);
|
|
264
|
-
if (pathParamNames.length > 0) {
|
|
265
|
-
// If we have path parameters but no schema, create a default schema
|
|
266
|
-
if (!pathParams) {
|
|
267
|
-
const defaultPathParams = this.schemaProcessor.createDefaultPathParamsSchema(pathParamNames);
|
|
268
|
-
definition.parameters.push(...defaultPathParams);
|
|
269
|
-
}
|
|
270
|
-
else {
|
|
271
|
-
const moreParams = this.schemaProcessor.createRequestParamsSchema(pathParams, true);
|
|
272
|
-
definition.parameters.push(...moreParams);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
else if (pathParams) {
|
|
276
|
-
// If no path parameters in route but we have a schema, use it
|
|
277
|
-
const moreParams = this.schemaProcessor.createRequestParamsSchema(pathParams, true);
|
|
278
|
-
definition.parameters.push(...moreParams);
|
|
279
|
-
}
|
|
280
|
-
// Add request body
|
|
281
|
-
if (MUTATION_HTTP_METHODS.includes(method.toUpperCase())) {
|
|
282
|
-
if (dataTypes.bodyType) {
|
|
283
|
-
// Ensure the schema is defined in components/schemas
|
|
284
|
-
this.schemaProcessor.getSchemaContent({
|
|
285
|
-
bodyType: dataTypes.bodyType,
|
|
286
|
-
});
|
|
287
|
-
// Use reference to the schema
|
|
288
|
-
const contentType = this.schemaProcessor.detectContentType(dataTypes.bodyType || "", dataTypes.contentType);
|
|
289
|
-
definition.requestBody = {
|
|
290
|
-
content: {
|
|
291
|
-
[contentType]: {
|
|
292
|
-
schema: { $ref: `#/components/schemas/${dataTypes.bodyType}` },
|
|
293
|
-
},
|
|
294
|
-
},
|
|
295
|
-
};
|
|
296
|
-
if (bodyDescription) {
|
|
297
|
-
definition.requestBody.description = bodyDescription;
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
else if (body && Object.keys(body).length > 0) {
|
|
301
|
-
// Fallback to inline schema for backward compatibility
|
|
302
|
-
definition.requestBody = this.schemaProcessor.createRequestBodySchema(body, bodyDescription, dataTypes.contentType);
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
// Add responses
|
|
306
|
-
definition.responses = this.buildResponsesFromConfig(dataTypes, method);
|
|
307
|
-
// If there are no responses from config, use the old logic
|
|
308
|
-
if (Object.keys(definition.responses).length === 0) {
|
|
309
|
-
definition.responses = responses
|
|
310
|
-
? this.schemaProcessor.createResponseSchema(responses, responseDescription)
|
|
311
|
-
: {};
|
|
312
|
-
}
|
|
313
|
-
this.swaggerPaths[routePath][method] = definition;
|
|
314
|
-
}
|
|
315
|
-
getSortedPaths(paths) {
|
|
316
|
-
function comparePaths(a, b) {
|
|
317
|
-
const aMethods = this.swaggerPaths[a] || {};
|
|
318
|
-
const bMethods = this.swaggerPaths[b] || {};
|
|
319
|
-
// Extract tags for all methods in path a
|
|
320
|
-
const aTags = Object.values(aMethods).flatMap((method) => method.tags || []);
|
|
321
|
-
// Extract tags for all methods in path b
|
|
322
|
-
const bTags = Object.values(bMethods).flatMap((method) => method.tags || []);
|
|
323
|
-
// Let's user only the first tags
|
|
324
|
-
const aPrimaryTag = aTags[0] || "";
|
|
325
|
-
const bPrimaryTag = bTags[0] || "";
|
|
326
|
-
// Sort alphabetically based on the first tag
|
|
327
|
-
const tagComparison = aPrimaryTag.localeCompare(bPrimaryTag);
|
|
328
|
-
if (tagComparison !== 0) {
|
|
329
|
-
return tagComparison; // Return the result of tag comparison
|
|
330
|
-
}
|
|
331
|
-
// Compare lengths of the paths
|
|
332
|
-
const aLength = a.split("/").length;
|
|
333
|
-
const bLength = b.split("/").length;
|
|
334
|
-
return aLength - bLength; // Shorter paths come before longer ones
|
|
335
|
-
}
|
|
336
|
-
return Object.keys(paths)
|
|
337
|
-
.sort(comparePaths.bind(this))
|
|
338
|
-
.reduce((sorted, key) => {
|
|
339
|
-
sorted[key] = paths[key];
|
|
340
|
-
return sorted;
|
|
341
|
-
}, {});
|
|
342
|
-
}
|
|
343
|
-
getSwaggerPaths() {
|
|
344
|
-
const paths = this.getSortedPaths(this.swaggerPaths);
|
|
345
|
-
return this.getSortedPaths(paths);
|
|
346
|
-
}
|
|
347
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export const HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
|