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.
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 -347
  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 -284
  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,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"];