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,349 +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
- // Ensure the referenced schema is resolved (triggers Zod converter)
106
- this.schemaProcessor.getSchemaContent({ responseType: ref });
107
- // Custom schema: "409:ConflictResponse"
108
- // 204 No Content should not have a content section per HTTP/OpenAPI spec
109
- if (code === "204") {
110
- responses[code] = {
111
- description: this.getDefaultErrorDescription(code) || "No Content",
112
- };
113
- }
114
- else {
115
- responses[code] = {
116
- description: this.getDefaultErrorDescription(code) || `HTTP ${code} response`,
117
- content: {
118
- "application/json": {
119
- schema: { $ref: `#/components/schemas/${ref}` },
120
- },
121
- },
122
- };
123
- }
124
- }
125
- else {
126
- // Only code: "409" - use $ref fro components/responses
127
- responses[code] = {
128
- $ref: `#/components/responses/${code}`,
129
- };
130
- }
131
- });
132
- }
133
- return responses;
134
- }
135
- getDefaultSuccessCode(method) {
136
- switch (method.toUpperCase()) {
137
- case "POST":
138
- return "201";
139
- case "DELETE":
140
- return "204";
141
- default:
142
- return "200";
143
- }
144
- }
145
- getDefaultErrorDescription(code) {
146
- const defaults = {
147
- 400: "Bad Request",
148
- 401: "Unauthorized",
149
- 403: "Forbidden",
150
- 404: "Not Found",
151
- 409: "Conflict",
152
- 422: "Unprocessable Entity",
153
- 429: "Too Many Requests",
154
- 500: "Internal Server Error",
155
- };
156
- return defaults[code] || `HTTP ${code}`;
157
- }
158
- /**
159
- * Get the SchemaProcessor instance
160
- */
161
- getSchemaProcessor() {
162
- return this.schemaProcessor;
163
- }
164
- /**
165
- * Check if a route should be ignored based on config patterns or @ignore tag
166
- */
167
- shouldIgnoreRoute(routePath, dataTypes) {
168
- // Check if route has @ignore tag
169
- if (dataTypes.isIgnored) {
170
- return true;
171
- }
172
- // Check if route matches any ignore patterns
173
- const ignorePatterns = this.config.ignoreRoutes || [];
174
- if (ignorePatterns.length === 0) {
175
- return false;
176
- }
177
- return ignorePatterns.some((pattern) => {
178
- // Support wildcards
179
- const regexPattern = pattern.replace(/\*/g, ".*").replace(/\//g, "\\/");
180
- const regex = new RegExp(`^${regexPattern}$`);
181
- return regex.test(routePath);
182
- });
183
- }
184
- /**
185
- * Register a discovered route after filtering
186
- */
187
- registerRoute(method, filePath, dataTypes) {
188
- const routePath = this.strategy.getRoutePath(filePath);
189
- if (this.shouldIgnoreRoute(routePath, dataTypes)) {
190
- logger.debug(`Ignoring route: ${routePath}`);
191
- return;
192
- }
193
- if (this.config.includeOpenApiRoutes && !dataTypes.isOpenApi) {
194
- return;
195
- }
196
- const pathParams = extractPathParameters(routePath);
197
- if (pathParams.length > 0 && !dataTypes.pathParamsType) {
198
- logger.debug(`Route ${routePath} contains path parameters ${pathParams.join(", ")} but no @pathParams type is defined.`);
199
- }
200
- this.addRouteToPaths(method, filePath, dataTypes);
201
- }
202
- scanApiRoutes(dir) {
203
- logger.debug(`Scanning API routes in: ${dir}`);
204
- let files = this.directoryCache[dir];
205
- if (!files) {
206
- files = fs.readdirSync(dir);
207
- this.directoryCache[dir] = files;
208
- }
209
- files.forEach((file) => {
210
- const filePath = path.join(dir, file);
211
- let stat = this.statCache[filePath];
212
- if (!stat) {
213
- stat = fs.statSync(filePath);
214
- this.statCache[filePath] = stat;
215
- }
216
- if (stat.isDirectory()) {
217
- this.scanApiRoutes(filePath);
218
- }
219
- else if (this.strategy.shouldProcessFile(file)) {
220
- if (!this.processFileTracker[filePath]) {
221
- this.strategy.processFile(filePath, (method, fp, dataTypes) => {
222
- this.registerRoute(method, fp, dataTypes);
223
- });
224
- this.processFileTracker[filePath] = true;
225
- }
226
- }
227
- });
228
- }
229
- addRouteToPaths(varName, filePath, dataTypes) {
230
- const method = varName.toLowerCase();
231
- const routePath = this.strategy.getRoutePath(filePath);
232
- const rootPath = capitalize(routePath.split("/")[1]);
233
- const operationId = dataTypes.operationId || getOperationId(routePath, method);
234
- const { tag, summary, description, auth, isOpenApi, deprecated, bodyDescription, responseDescription, } = dataTypes;
235
- if (this.config.includeOpenApiRoutes && !isOpenApi) {
236
- // If flag is enabled and there is no @openapi tag, then skip path
237
- return;
238
- }
239
- if (!this.swaggerPaths[routePath]) {
240
- this.swaggerPaths[routePath] = {};
241
- }
242
- const { params, pathParams, body, responses } = this.schemaProcessor.getSchemaContent(dataTypes);
243
- const definition = {
244
- operationId: operationId,
245
- summary: summary,
246
- description: description,
247
- tags: [tag || rootPath],
248
- parameters: [],
249
- };
250
- if (deprecated) {
251
- definition.deprecated = true;
252
- }
253
- // Add auth
254
- if (auth) {
255
- const authItems = auth.split(",").map(item => item.trim());
256
- definition.security = authItems.map(authItem => ({
257
- [authItem]: [],
258
- }));
259
- }
260
- if (params) {
261
- definition.parameters =
262
- this.schemaProcessor.createRequestParamsSchema(params);
263
- }
264
- // Add path parameters
265
- const pathParamNames = extractPathParameters(routePath);
266
- if (pathParamNames.length > 0) {
267
- // If we have path parameters but no schema, create a default schema
268
- if (!pathParams) {
269
- const defaultPathParams = this.schemaProcessor.createDefaultPathParamsSchema(pathParamNames);
270
- definition.parameters.push(...defaultPathParams);
271
- }
272
- else {
273
- const moreParams = this.schemaProcessor.createRequestParamsSchema(pathParams, true);
274
- definition.parameters.push(...moreParams);
275
- }
276
- }
277
- else if (pathParams) {
278
- // If no path parameters in route but we have a schema, use it
279
- const moreParams = this.schemaProcessor.createRequestParamsSchema(pathParams, true);
280
- definition.parameters.push(...moreParams);
281
- }
282
- // Add request body
283
- if (MUTATION_HTTP_METHODS.includes(method.toUpperCase())) {
284
- if (dataTypes.bodyType) {
285
- // Ensure the schema is defined in components/schemas
286
- this.schemaProcessor.getSchemaContent({
287
- bodyType: dataTypes.bodyType,
288
- });
289
- // Use reference to the schema
290
- const contentType = this.schemaProcessor.detectContentType(dataTypes.bodyType || "", dataTypes.contentType);
291
- definition.requestBody = {
292
- content: {
293
- [contentType]: {
294
- schema: { $ref: `#/components/schemas/${dataTypes.bodyType}` },
295
- },
296
- },
297
- };
298
- if (bodyDescription) {
299
- definition.requestBody.description = bodyDescription;
300
- }
301
- }
302
- else if (body && Object.keys(body).length > 0) {
303
- // Fallback to inline schema for backward compatibility
304
- definition.requestBody = this.schemaProcessor.createRequestBodySchema(body, bodyDescription, dataTypes.contentType);
305
- }
306
- }
307
- // Add responses
308
- definition.responses = this.buildResponsesFromConfig(dataTypes, method);
309
- // If there are no responses from config, use the old logic
310
- if (Object.keys(definition.responses).length === 0) {
311
- definition.responses = responses
312
- ? this.schemaProcessor.createResponseSchema(responses, responseDescription)
313
- : {};
314
- }
315
- this.swaggerPaths[routePath][method] = definition;
316
- }
317
- getSortedPaths(paths) {
318
- function comparePaths(a, b) {
319
- const aMethods = this.swaggerPaths[a] || {};
320
- const bMethods = this.swaggerPaths[b] || {};
321
- // Extract tags for all methods in path a
322
- const aTags = Object.values(aMethods).flatMap((method) => method.tags || []);
323
- // Extract tags for all methods in path b
324
- const bTags = Object.values(bMethods).flatMap((method) => method.tags || []);
325
- // Let's user only the first tags
326
- const aPrimaryTag = aTags[0] || "";
327
- const bPrimaryTag = bTags[0] || "";
328
- // Sort alphabetically based on the first tag
329
- const tagComparison = aPrimaryTag.localeCompare(bPrimaryTag);
330
- if (tagComparison !== 0) {
331
- return tagComparison; // Return the result of tag comparison
332
- }
333
- // Compare lengths of the paths
334
- const aLength = a.split("/").length;
335
- const bLength = b.split("/").length;
336
- return aLength - bLength; // Shorter paths come before longer ones
337
- }
338
- return Object.keys(paths)
339
- .sort(comparePaths.bind(this))
340
- .reduce((sorted, key) => {
341
- sorted[key] = paths[key];
342
- return sorted;
343
- }, {});
344
- }
345
- getSwaggerPaths() {
346
- const paths = this.getSortedPaths(this.swaggerPaths);
347
- return this.getSortedPaths(paths);
348
- }
349
- }
@@ -1 +0,0 @@
1
- export const HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];