next-openapi-gen 0.5.0 → 0.5.2

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/README.md CHANGED
@@ -57,8 +57,8 @@ During initialization (`npx next-openapi-gen init`), a configuration file `next.
57
57
  }
58
58
  ],
59
59
  "apiDir": "src/app/api",
60
- "schemaDir": "src/types",
61
- "schemaType": "typescript", // or "zod" for Zod schemas
60
+ "schemaDir": "src/types", // or e.g. "src/schemas" for Zod schemas
61
+ "schemaType": "typescript", // or "zod" for Zod schemas
62
62
  "outputFile": "openapi.json",
63
63
  "docsUrl": "/api-docs",
64
64
  "includeOpenApiRoutes": false
@@ -67,13 +67,13 @@ During initialization (`npx next-openapi-gen init`), a configuration file `next.
67
67
 
68
68
  ### Configuration Options
69
69
 
70
- | Option | Description |
71
- |-------|------|
72
- | `apiDir` | Path to the API directory |
73
- | `schemaDir` | Path to the types/schemas directory |
74
- | `schemaType` | Schema type: `"typescript"` or `"zod"` |
75
- | `outputFile` | Path to the OpenAPI output file |
76
- | `docsUrl` | API documentation URL (for Swagger UI) |
70
+ | Option | Description |
71
+ | ---------------------- | ------------------------------------------------ |
72
+ | `apiDir` | Path to the API directory |
73
+ | `schemaDir` | Path to the types/schemas directory |
74
+ | `schemaType` | Schema type: `"typescript"` or `"zod"` |
75
+ | `outputFile` | Path to the OpenAPI output file |
76
+ | `docsUrl` | API documentation URL (for Swagger UI) |
77
77
  | `includeOpenApiRoutes` | Whether to include only routes with @openapi tag |
78
78
 
79
79
  ## Documenting Your API
@@ -83,7 +83,7 @@ During initialization (`npx next-openapi-gen init`), a configuration file `next.
83
83
  ```typescript
84
84
  // src/app/api/users/[id]/route.ts
85
85
 
86
- import { NextRequest, NextResponse } from 'next/server';
86
+ import { NextRequest, NextResponse } from "next/server";
87
87
 
88
88
  type UserParams = {
89
89
  id: string; // User ID
@@ -115,17 +115,17 @@ export async function GET(
115
115
  ```typescript
116
116
  // src/app/api/products/[id]/route.ts
117
117
 
118
- import { NextRequest, NextResponse } from 'next/server';
119
- import { z } from 'zod';
118
+ import { NextRequest, NextResponse } from "next/server";
119
+ import { z } from "zod";
120
120
 
121
121
  export const ProductParams = z.object({
122
- id: z.string().describe("Product ID")
122
+ id: z.string().describe("Product ID"),
123
123
  });
124
124
 
125
125
  export const ProductResponse = z.object({
126
126
  id: z.string().describe("Product ID"),
127
127
  name: z.string().describe("Product name"),
128
- price: z.number().positive().describe("Product price")
128
+ price: z.number().positive().describe("Product price"),
129
129
  });
130
130
 
131
131
  /**
@@ -145,15 +145,16 @@ export async function GET(
145
145
 
146
146
  ## JSDoc Documentation Tags
147
147
 
148
- | Tag | Description |
149
- |-----|------|
150
- | `@desc` | Endpoint description |
151
- | `@pathParams` | Path parameters type/schema |
152
- | `@params` | Query parameters type/schema |
153
- | `@body` | Request body type/schema |
154
- | `@response` | Response type/schema |
155
- | `@auth` | Authorization type (`bearer`, `basic`, `apikey`) |
156
- | `@openapi` | Marks the route for inclusion in documentation (if includeOpenApiRoutes is enabled) |
148
+ | Tag | Description |
149
+ | ------------- | ----------------------------------------------------------------------------------- |
150
+ | `@desc` | Endpoint description |
151
+ | `@pathParams` | Path parameters type/schema |
152
+ | `@params` | Query parameters type/schema |
153
+ | `@body` | Request body type/schema |
154
+ | `@response` | Response type/schema |
155
+ | `@auth` | Authorization type (`bearer`, `basic`, `apikey`) |
156
+ | `@tag` | Custom tag |
157
+ | `@openapi` | Marks the route for inclusion in documentation (if includeOpenApiRoutes is enabled) |
157
158
 
158
159
  ## CLI Usage
159
160
 
@@ -164,6 +165,7 @@ npx next-openapi-gen init
164
165
  ```
165
166
 
166
167
  This command will generate following elements:
168
+
167
169
  - Generate `next.openapi.json` configuration file
168
170
  - Install UI interface (default `Scalar`)
169
171
  - Add `/api-docs` page to display OpenAPI documentation
@@ -175,6 +177,7 @@ npx next-openapi-gen generate
175
177
  ```
176
178
 
177
179
  This command will generate OpenAPI documentation based on your API code:
180
+
178
181
  - Scan API directories for routes
179
182
  - Analyze types/schemas
180
183
  - Generate OpenAPI file (`openapi.json`) in `public` folder
@@ -182,7 +185,7 @@ This command will generate OpenAPI documentation based on your API code:
182
185
 
183
186
  ### 3. View API Documentation
184
187
 
185
- To see API documenation go to `http://localhost:3000/api-docs`
188
+ To see API documenation go to `http://localhost:3000/api-docs`
186
189
 
187
190
  ## Examples
188
191
 
@@ -198,7 +201,7 @@ type UserParams = {
198
201
 
199
202
  // Or Zod
200
203
  const UserParams = z.object({
201
- id: z.string().describe("User ID")
204
+ id: z.string().describe("User ID"),
202
205
  });
203
206
 
204
207
  /**
@@ -225,7 +228,7 @@ type UsersQueryParams = {
225
228
  const UsersQueryParams = z.object({
226
229
  page: z.number().optional().describe("Page number"),
227
230
  limit: z.number().optional().describe("Results per page"),
228
- search: z.string().optional().describe("Search phrase")
231
+ search: z.string().optional().describe("Search phrase"),
229
232
  });
230
233
 
231
234
  /**
@@ -252,7 +255,7 @@ type CreateUserBody = {
252
255
  const CreateUserBody = z.object({
253
256
  name: z.string().describe("Full name"),
254
257
  email: z.string().email().describe("Email address"),
255
- password: z.string().min(8).describe("Password")
258
+ password: z.string().min(8).describe("Password"),
256
259
  });
257
260
 
258
261
  /**
@@ -281,7 +284,7 @@ const UserResponse = z.object({
281
284
  id: z.string().describe("User ID"),
282
285
  name: z.string().describe("Full name"),
283
286
  email: z.string().email().describe("Email address"),
284
- createdAt: z.date().describe("Creation date")
287
+ createdAt: z.date().describe("Creation date"),
285
288
  });
286
289
 
287
290
  /**
@@ -326,14 +329,14 @@ If no type/schema is provided for path parameters, a default schema will be gene
326
329
 
327
330
  The library generates intelligent examples for parameters based on their name:
328
331
 
329
- | Parameter name | Example |
330
- |----------------|----------|
331
- | `id`, `*Id` | `"123"` or `123` |
332
- | `slug` | `"example-slug"` |
333
- | `uuid` | `"123e4567-e89b-12d3-a456-426614174000"` |
334
- | `email` | `"user@example.com"` |
335
- | `name` | `"example-name"` |
336
- | `date` | `"2023-01-01"` |
332
+ | Parameter name | Example |
333
+ | -------------- | ---------------------------------------- |
334
+ | `id`, `*Id` | `"123"` or `123` |
335
+ | `slug` | `"example-slug"` |
336
+ | `uuid` | `"123e4567-e89b-12d3-a456-426614174000"` |
337
+ | `email` | `"user@example.com"` |
338
+ | `name` | `"example-name"` |
339
+ | `date` | `"2023-01-01"` |
337
340
 
338
341
  ## Advanced Zod Features
339
342
 
@@ -343,7 +346,12 @@ The library supports advanced Zod features such as:
343
346
 
344
347
  ```typescript
345
348
  // Zod validation chains are properly converted to OpenAPI schemas
346
- const EmailSchema = z.string().email().min(5).max(100).describe("Email address");
349
+ const EmailSchema = z
350
+ .string()
351
+ .email()
352
+ .min(5)
353
+ .max(100)
354
+ .describe("Email address");
347
355
 
348
356
  // Converts to OpenAPI with email format, minLength and maxLength
349
357
  ```
@@ -352,11 +360,11 @@ const EmailSchema = z.string().email().min(5).max(100).describe("Email address")
352
360
 
353
361
  ```typescript
354
362
  // You can use TypeScript with Zod types
355
- import { z } from 'zod';
363
+ import { z } from "zod";
356
364
 
357
365
  const UserSchema = z.object({
358
366
  id: z.string().uuid(),
359
- name: z.string().min(2)
367
+ name: z.string().min(2),
360
368
  });
361
369
 
362
370
  // Use z.infer to create a TypeScript type
@@ -109,7 +109,7 @@ export class RouteProcessor {
109
109
  const routePath = this.getRoutePath(filePath);
110
110
  const rootPath = capitalize(routePath.split("/")[1]);
111
111
  const operationId = getOperationId(routePath, method);
112
- const { summary, description, auth, isOpenApi } = dataTypes;
112
+ const { tag, summary, description, auth, isOpenApi } = dataTypes;
113
113
  if (this.config.includeOpenApiRoutes && !isOpenApi) {
114
114
  // If flag is enabled and there is no @openapi tag, then skip path
115
115
  return;
@@ -122,7 +122,7 @@ export class RouteProcessor {
122
122
  operationId: operationId,
123
123
  summary: summary,
124
124
  description: description,
125
- tags: [rootPath],
125
+ tags: [tag || rootPath],
126
126
  parameters: [],
127
127
  };
128
128
  // Add auth
@@ -43,6 +43,11 @@ export class SchemaProcessor {
43
43
  // Check if we should use Zod schemas
44
44
  if (this.schemaType === "zod") {
45
45
  console.log(`Looking for Zod schema: ${schemaName}`);
46
+ // Check type mapping first
47
+ const mappedSchemaName = this.zodSchemaConverter.typeToSchemaMapping[schemaName];
48
+ if (mappedSchemaName) {
49
+ console.log(`Type '${schemaName}' is mapped to Zod schema '${mappedSchemaName}'`);
50
+ }
46
51
  // Try to convert Zod schema
47
52
  const zodSchema = this.zodSchemaConverter.convertZodSchemaToOpenApi(schemaName);
48
53
  if (zodSchema) {
@@ -571,7 +576,7 @@ export class SchemaProcessor {
571
576
  },
572
577
  };
573
578
  }
574
- getSchemaContent({ paramsType, pathParamsType, bodyType, responseType, }) {
579
+ getSchemaContent({ tag, paramsType, pathParamsType, bodyType, responseType, }) {
575
580
  let params = paramsType ? this.openapiDefinitions[paramsType] : {};
576
581
  let pathParams = pathParamsType
577
582
  ? this.openapiDefinitions[pathParamsType]
@@ -608,6 +613,7 @@ export class SchemaProcessor {
608
613
  });
609
614
  }
610
615
  return {
616
+ tag,
611
617
  params,
612
618
  pathParams,
613
619
  body,
package/dist/lib/utils.js CHANGED
@@ -16,6 +16,7 @@ export function extractPathParameters(routePath) {
16
16
  }
17
17
  export function extractJSDocComments(path) {
18
18
  const comments = path.node.leadingComments;
19
+ let tag = "";
19
20
  let summary = "";
20
21
  let description = "";
21
22
  let paramsType = "";
@@ -50,6 +51,13 @@ export function extractJSDocComments(path) {
50
51
  const regex = /@desc\s*(.*)/;
51
52
  description = commentValue.match(regex)[1].trim();
52
53
  }
54
+ if (commentValue.includes("@tag")) {
55
+ const regex = /@tag\s*(.*)/;
56
+ const match = commentValue.match(regex);
57
+ if (match && match[1]) {
58
+ tag = match[1].trim();
59
+ }
60
+ }
53
61
  if (commentValue.includes("@params")) {
54
62
  paramsType = extractTypeFromComment(commentValue, "@params");
55
63
  }
@@ -65,6 +73,7 @@ export function extractJSDocComments(path) {
65
73
  });
66
74
  }
67
75
  return {
76
+ tag,
68
77
  auth,
69
78
  summary,
70
79
  description,
@@ -11,6 +11,7 @@ export class ZodSchemaConverter {
11
11
  zodSchemas = {};
12
12
  processingSchemas = new Set();
13
13
  processedModules = new Set();
14
+ typeToSchemaMapping = {};
14
15
  constructor(schemaDir) {
15
16
  this.schemaDir = path.resolve(schemaDir);
16
17
  }
@@ -18,7 +19,17 @@ export class ZodSchemaConverter {
18
19
  * Find a Zod schema by name and convert it to OpenAPI spec
19
20
  */
20
21
  convertZodSchemaToOpenApi(schemaName) {
22
+ // Run pre-scan only one time
23
+ if (Object.keys(this.typeToSchemaMapping).length === 0) {
24
+ this.preScanForTypeMappings();
25
+ }
21
26
  console.log(`Looking for Zod schema: ${schemaName}`);
27
+ // Check mapped types
28
+ const mappedSchemaName = this.typeToSchemaMapping[schemaName];
29
+ if (mappedSchemaName) {
30
+ console.log(`Type '${schemaName}' is mapped to schema '${mappedSchemaName}'`);
31
+ schemaName = mappedSchemaName;
32
+ }
22
33
  // Check for circular references
23
34
  if (this.processingSchemas.has(schemaName)) {
24
35
  return { $ref: `#/components/schemas/${schemaName}` };
@@ -240,22 +251,51 @@ export class ZodSchemaConverter {
240
251
  },
241
252
  // For type aliases that reference Zod schemas
242
253
  TSTypeAliasDeclaration: (path) => {
243
- if (t.isIdentifier(path.node.id) &&
244
- path.node.id.name === schemaName) {
245
- // Try to find if this is a z.infer<typeof SchemaName> pattern
254
+ if (t.isIdentifier(path.node.id)) {
255
+ const typeName = path.node.id.name;
246
256
  if (t.isTSTypeReference(path.node.typeAnnotation) &&
247
- t.isIdentifier(path.node.typeAnnotation.typeName) &&
248
- path.node.typeAnnotation.typeName.name === "infer" &&
249
- path.node.typeAnnotation.typeParameters &&
250
- path.node.typeAnnotation.typeParameters.params.length > 0) {
251
- const param = path.node.typeAnnotation.typeParameters.params[0];
252
- if (t.isTSTypeQuery(param) && t.isIdentifier(param.exprName)) {
253
- const referencedSchemaName = param.exprName.name;
254
- // Find the referenced schema
255
- this.processFileForZodSchema(filePath, referencedSchemaName);
256
- if (this.zodSchemas[referencedSchemaName]) {
257
- this.zodSchemas[schemaName] =
258
- this.zodSchemas[referencedSchemaName];
257
+ t.isTSQualifiedName(path.node.typeAnnotation.typeName) &&
258
+ t.isIdentifier(path.node.typeAnnotation.typeName.left) &&
259
+ path.node.typeAnnotation.typeName.left.name === "z" &&
260
+ t.isIdentifier(path.node.typeAnnotation.typeName.right) &&
261
+ path.node.typeAnnotation.typeName.right.name === "infer") {
262
+ // Extract schema name from z.infer<typeof SchemaName>
263
+ if (path.node.typeAnnotation.typeParameters &&
264
+ path.node.typeAnnotation.typeParameters.params.length > 0) {
265
+ const param = path.node.typeAnnotation.typeParameters.params[0];
266
+ if (t.isTSTypeQuery(param) && t.isIdentifier(param.exprName)) {
267
+ const referencedSchemaName = param.exprName.name;
268
+ // Save mapping: TypeName -> SchemaName
269
+ this.typeToSchemaMapping[typeName] = referencedSchemaName;
270
+ console.log(`Mapped type '${typeName}' to schema '${referencedSchemaName}'`);
271
+ // Process the referenced schema if not already processed
272
+ if (!this.zodSchemas[referencedSchemaName]) {
273
+ this.processFileForZodSchema(filePath, referencedSchemaName);
274
+ }
275
+ // Use the referenced schema for this type
276
+ if (this.zodSchemas[referencedSchemaName]) {
277
+ this.zodSchemas[typeName] =
278
+ this.zodSchemas[referencedSchemaName];
279
+ }
280
+ }
281
+ }
282
+ }
283
+ if (path.node.id.name === schemaName) {
284
+ // Try to find if this is a z.infer<typeof SchemaName> pattern
285
+ if (t.isTSTypeReference(path.node.typeAnnotation) &&
286
+ t.isIdentifier(path.node.typeAnnotation.typeName) &&
287
+ path.node.typeAnnotation.typeName.name === "infer" &&
288
+ path.node.typeAnnotation.typeParameters &&
289
+ path.node.typeAnnotation.typeParameters.params.length > 0) {
290
+ const param = path.node.typeAnnotation.typeParameters.params[0];
291
+ if (t.isTSTypeQuery(param) && t.isIdentifier(param.exprName)) {
292
+ const referencedSchemaName = param.exprName.name;
293
+ // Find the referenced schema
294
+ this.processFileForZodSchema(filePath, referencedSchemaName);
295
+ if (this.zodSchemas[referencedSchemaName]) {
296
+ this.zodSchemas[schemaName] =
297
+ this.zodSchemas[referencedSchemaName];
298
+ }
259
299
  }
260
300
  }
261
301
  }
@@ -1058,4 +1098,87 @@ export class ZodSchemaConverter {
1058
1098
  getProcessedSchemas() {
1059
1099
  return this.zodSchemas;
1060
1100
  }
1101
+ /**
1102
+ * Pre-scan all files to build type mappings
1103
+ */
1104
+ preScanForTypeMappings() {
1105
+ console.log("Pre-scanning for type mappings...");
1106
+ // Scan route files
1107
+ const routeFiles = this.findRouteFiles();
1108
+ for (const routeFile of routeFiles) {
1109
+ this.scanFileForTypeMappings(routeFile);
1110
+ }
1111
+ // Scan schema directory
1112
+ this.scanDirectoryForTypeMappings(this.schemaDir);
1113
+ }
1114
+ /**
1115
+ * Scan a single file for type mappings
1116
+ */
1117
+ scanFileForTypeMappings(filePath) {
1118
+ try {
1119
+ const content = fs.readFileSync(filePath, "utf-8");
1120
+ const ast = parse(content, {
1121
+ sourceType: "module",
1122
+ plugins: ["typescript", "decorators-legacy"],
1123
+ });
1124
+ traverse.default(ast, {
1125
+ TSTypeAliasDeclaration: (path) => {
1126
+ if (t.isIdentifier(path.node.id)) {
1127
+ const typeName = path.node.id.name;
1128
+ // Check for z.infer<typeof SchemaName> pattern
1129
+ if (t.isTSTypeReference(path.node.typeAnnotation)) {
1130
+ const typeRef = path.node.typeAnnotation;
1131
+ // Handle both z.infer and just infer (when z is imported)
1132
+ let isInferType = false;
1133
+ if (t.isTSQualifiedName(typeRef.typeName) &&
1134
+ t.isIdentifier(typeRef.typeName.left) &&
1135
+ typeRef.typeName.left.name === "z" &&
1136
+ t.isIdentifier(typeRef.typeName.right) &&
1137
+ typeRef.typeName.right.name === "infer") {
1138
+ isInferType = true;
1139
+ }
1140
+ else if (t.isIdentifier(typeRef.typeName) &&
1141
+ typeRef.typeName.name === "infer") {
1142
+ isInferType = true;
1143
+ }
1144
+ if (isInferType &&
1145
+ typeRef.typeParameters &&
1146
+ typeRef.typeParameters.params.length > 0) {
1147
+ const param = typeRef.typeParameters.params[0];
1148
+ if (t.isTSTypeQuery(param) && t.isIdentifier(param.exprName)) {
1149
+ const referencedSchemaName = param.exprName.name;
1150
+ this.typeToSchemaMapping[typeName] = referencedSchemaName;
1151
+ console.log(`Pre-scan: Mapped type '${typeName}' to schema '${referencedSchemaName}'`);
1152
+ }
1153
+ }
1154
+ }
1155
+ }
1156
+ },
1157
+ });
1158
+ }
1159
+ catch (error) {
1160
+ console.error(`Error scanning file ${filePath} for type mappings:`, error);
1161
+ }
1162
+ }
1163
+ /**
1164
+ * Recursively scan directory for type mappings
1165
+ */
1166
+ scanDirectoryForTypeMappings(dir) {
1167
+ try {
1168
+ const files = fs.readdirSync(dir);
1169
+ for (const file of files) {
1170
+ const filePath = path.join(dir, file);
1171
+ const stats = fs.statSync(filePath);
1172
+ if (stats.isDirectory()) {
1173
+ this.scanDirectoryForTypeMappings(filePath);
1174
+ }
1175
+ else if (file.endsWith(".ts") || file.endsWith(".tsx")) {
1176
+ this.scanFileForTypeMappings(filePath);
1177
+ }
1178
+ }
1179
+ }
1180
+ catch (error) {
1181
+ console.error(`Error scanning directory ${dir} for type mappings:`, error);
1182
+ }
1183
+ }
1061
1184
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-openapi-gen",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "Automatically generate OpenAPI 3.0 documentation from Next.js projects, with support for TypeScript types and Zod schemas.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",