next-openapi-gen 0.3.2 → 0.4.1

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.
@@ -18,6 +18,12 @@ export class RouteProcessor {
18
18
  this.config = config;
19
19
  this.schemaProcessor = new SchemaProcessor(config.schemaDir);
20
20
  }
21
+ /**
22
+ * Get the SchemaProcessor instance
23
+ */
24
+ getSchemaProcessor() {
25
+ return this.schemaProcessor;
26
+ }
21
27
  isRoute(varName) {
22
28
  return HTTP_METHODS.includes(varName);
23
29
  }
@@ -39,8 +45,16 @@ export class RouteProcessor {
39
45
  if (this.isRoute(declaration.id.name)) {
40
46
  // Don't bother adding routes for processing if only including OpenAPI routes and the route is not OpenAPI
41
47
  if (!this.config.includeOpenApiRoutes ||
42
- (this.config.includeOpenApiRoutes && dataTypes.isOpenApi))
48
+ (this.config.includeOpenApiRoutes && dataTypes.isOpenApi)) {
49
+ // Check for URL parameters in the route path
50
+ const routePath = this.getRoutePath(filePath);
51
+ const pathParams = extractPathParameters(routePath);
52
+ // If we have path parameters but no pathParamsType defined, we should log a warning
53
+ if (pathParams.length > 0 && !dataTypes.pathParams) {
54
+ console.warn(`Route ${routePath} contains path parameters ${pathParams.join(", ")} but no @pathParams type is defined.`);
55
+ }
43
56
  this.addRouteToPaths(declaration.id.name, filePath, dataTypes);
57
+ }
44
58
  }
45
59
  }
46
60
  if (t.isVariableDeclaration(declaration)) {
@@ -50,8 +64,14 @@ export class RouteProcessor {
50
64
  const dataTypes = extractJSDocComments(path);
51
65
  // Don't bother adding routes for processing if only including OpenAPI routes and the route is not OpenAPI
52
66
  if (!this.config.includeOpenApiRoutes ||
53
- (this.config.includeOpenApiRoutes && dataTypes.isOpenApi))
67
+ (this.config.includeOpenApiRoutes && dataTypes.isOpenApi)) {
68
+ const routePath = this.getRoutePath(filePath);
69
+ const pathParams = extractPathParameters(routePath);
70
+ if (pathParams.length > 0 && !dataTypes.pathParams) {
71
+ console.warn(`Route ${routePath} contains path parameters ${pathParams.join(", ")} but no @pathParams type is defined.`);
72
+ }
54
73
  this.addRouteToPaths(decl.id.name, filePath, dataTypes);
74
+ }
55
75
  }
56
76
  }
57
77
  });
@@ -103,6 +123,7 @@ export class RouteProcessor {
103
123
  summary: summary,
104
124
  description: description,
105
125
  tags: [rootPath],
126
+ parameters: [],
106
127
  };
107
128
  // Add auth
108
129
  if (auth) {
@@ -112,7 +133,6 @@ export class RouteProcessor {
112
133
  },
113
134
  ];
114
135
  }
115
- definition.parameters = [];
116
136
  if (params) {
117
137
  definition.parameters =
118
138
  this.schemaProcessor.createRequestParamsSchema(params);
@@ -3,6 +3,7 @@ import path from "path";
3
3
  import { parse } from "@babel/parser";
4
4
  import traverse from "@babel/traverse";
5
5
  import * as t from "@babel/types";
6
+ import { ZodSchemaConverter } from "./zod-converter";
6
7
  export class SchemaProcessor {
7
8
  schemaDir;
8
9
  typeDefinitions = {};
@@ -12,13 +13,38 @@ export class SchemaProcessor {
12
13
  statCache = {};
13
14
  processSchemaTracker = {};
14
15
  processingTypes = new Set();
15
- constructor(schemaDir) {
16
+ zodSchemaConverter;
17
+ schemaType;
18
+ constructor(schemaDir, schemaType = "typescript") {
16
19
  this.schemaDir = path.resolve(schemaDir);
20
+ this.schemaType = schemaType;
21
+ if (schemaType === "zod") {
22
+ this.zodSchemaConverter = new ZodSchemaConverter(schemaDir);
23
+ }
24
+ }
25
+ /**
26
+ * Get all defined schemas (for components.schemas section)
27
+ */
28
+ getDefinedSchemas() {
29
+ return this.openapiDefinitions;
17
30
  }
18
31
  findSchemaDefinition(schemaName, contentType) {
19
32
  let schemaNode = null;
20
- // assign type that is actually processed
33
+ // Assign type that is actually processed
21
34
  this.contentType = contentType;
35
+ // Check if we should use Zod schemas
36
+ if (this.schemaType === "zod") {
37
+ console.log(`Looking for Zod schema: ${schemaName}`);
38
+ // Try to convert Zod schema
39
+ const zodSchema = this.zodSchemaConverter.convertZodSchemaToOpenApi(schemaName);
40
+ if (zodSchema) {
41
+ console.log(`Found and processed Zod schema: ${schemaName}`);
42
+ this.openapiDefinitions[schemaName] = zodSchema;
43
+ return zodSchema;
44
+ }
45
+ console.log(`No Zod schema found for ${schemaName}, trying TypeScript fallback`);
46
+ }
47
+ // Fall back to TypeScript types
22
48
  this.scanSchemaDir(this.schemaDir, schemaName);
23
49
  return schemaNode;
24
50
  }
@@ -38,7 +64,7 @@ export class SchemaProcessor {
38
64
  if (stat.isDirectory()) {
39
65
  this.scanSchemaDir(filePath, schemaName);
40
66
  }
41
- else if (file.endsWith(".ts")) {
67
+ else if (file.endsWith(".ts") || file.endsWith(".tsx")) {
42
68
  this.processSchemaFile(filePath, schemaName);
43
69
  }
44
70
  });
@@ -69,6 +95,25 @@ export class SchemaProcessor {
69
95
  this.typeDefinitions[name] = path.node;
70
96
  }
71
97
  },
98
+ // Collect exported zod schemas
99
+ ExportNamedDeclaration: (path) => {
100
+ if (t.isVariableDeclaration(path.node.declaration)) {
101
+ path.node.declaration.declarations.forEach((declaration) => {
102
+ if (t.isIdentifier(declaration.id) &&
103
+ declaration.id.name === schemaName &&
104
+ declaration.init) {
105
+ // Check if is Zod schema
106
+ if (t.isCallExpression(declaration.init) &&
107
+ t.isMemberExpression(declaration.init.callee) &&
108
+ t.isIdentifier(declaration.init.callee.object) &&
109
+ declaration.init.callee.object.name === "z") {
110
+ const name = declaration.id.name;
111
+ this.typeDefinitions[name] = declaration.init;
112
+ }
113
+ }
114
+ });
115
+ }
116
+ },
72
117
  });
73
118
  }
74
119
  resolveType(typeName) {
@@ -76,12 +121,33 @@ export class SchemaProcessor {
76
121
  // Return reference to type to avoid infinite recursion
77
122
  return { $ref: `#/components/schemas/${typeName}` };
78
123
  }
79
- // Add type to precessing types
124
+ // Add type to processing types
80
125
  this.processingTypes.add(typeName);
81
126
  try {
127
+ // If we are using Zod and the given type is not found yet, try using Zod converter first
128
+ if (this.schemaType === "zod" && !this.openapiDefinitions[typeName]) {
129
+ const zodSchema = this.zodSchemaConverter.convertZodSchemaToOpenApi(typeName);
130
+ if (zodSchema) {
131
+ this.openapiDefinitions[typeName] = zodSchema;
132
+ return zodSchema;
133
+ }
134
+ }
82
135
  const typeNode = this.typeDefinitions[typeName.toString()];
83
136
  if (!typeNode)
84
137
  return {};
138
+ // Check if node is Zod
139
+ if (t.isCallExpression(typeNode) &&
140
+ t.isMemberExpression(typeNode.callee) &&
141
+ t.isIdentifier(typeNode.callee.object) &&
142
+ typeNode.callee.object.name === "z") {
143
+ if (this.schemaType === "zod") {
144
+ const zodSchema = this.zodSchemaConverter.processZodNode(typeNode);
145
+ if (zodSchema) {
146
+ this.openapiDefinitions[typeName] = zodSchema;
147
+ return zodSchema;
148
+ }
149
+ }
150
+ }
85
151
  if (t.isTSEnumDeclaration(typeNode)) {
86
152
  const enumValues = this.processEnum(typeNode);
87
153
  return enumValues;
@@ -296,7 +362,6 @@ export class SchemaProcessor {
296
362
  if (resolvedType.type === "object" && resolvedType.properties) {
297
363
  Object.entries(resolvedType.properties).forEach(([key, value]) => {
298
364
  allProperties[key] = value;
299
- // @ts-ignore
300
365
  if (value.required) {
301
366
  requiredProperties.push(key);
302
367
  }
@@ -358,7 +423,9 @@ export class SchemaProcessor {
358
423
  enumSchema.type = "number";
359
424
  }
360
425
  const targetValue = value || name;
361
- enumSchema.enum.push(targetValue);
426
+ if (enumSchema.enum) {
427
+ enumSchema.enum.push(targetValue);
428
+ }
362
429
  }
363
430
  });
364
431
  return enumSchema;
@@ -390,12 +457,12 @@ export class SchemaProcessor {
390
457
  if (paramName === "id" ||
391
458
  paramName.endsWith("Id") ||
392
459
  paramName.endsWith("_id")) {
393
- return type === "string" ? "123abc" : 123;
460
+ return type === "string" ? "123" : 123;
394
461
  }
395
462
  // For specific common parameter names
396
463
  switch (paramName.toLowerCase()) {
397
464
  case "slug":
398
- return "example-slug";
465
+ return "slug";
399
466
  case "uuid":
400
467
  return "123e4567-e89b-12d3-a456-426614174000";
401
468
  case "username":
@@ -403,11 +470,13 @@ export class SchemaProcessor {
403
470
  case "email":
404
471
  return "user@example.com";
405
472
  case "name":
406
- return "example-name";
473
+ return "name";
407
474
  case "date":
408
475
  return "2023-01-01";
409
476
  case "page":
410
477
  return 1;
478
+ case "role":
479
+ return "admin";
411
480
  default:
412
481
  // Default examples by type
413
482
  if (type === "string")
@@ -498,25 +567,27 @@ export class SchemaProcessor {
498
567
  };
499
568
  }
500
569
  getSchemaContent({ paramsType, pathParamsType, bodyType, responseType, }) {
501
- let params = this.openapiDefinitions[paramsType];
502
- let pathParams = this.openapiDefinitions[pathParamsType];
503
- let body = this.openapiDefinitions[bodyType];
504
- let responses = this.openapiDefinitions[responseType];
570
+ let params = paramsType ? this.openapiDefinitions[paramsType] : {};
571
+ let pathParams = pathParamsType
572
+ ? this.openapiDefinitions[pathParamsType]
573
+ : {};
574
+ let body = bodyType ? this.openapiDefinitions[bodyType] : {};
575
+ let responses = responseType ? this.openapiDefinitions[responseType] : {};
505
576
  if (paramsType && !params) {
506
577
  this.findSchemaDefinition(paramsType, "params");
507
- params = this.openapiDefinitions[paramsType];
578
+ params = this.openapiDefinitions[paramsType] || {};
508
579
  }
509
580
  if (pathParamsType && !pathParams) {
510
581
  this.findSchemaDefinition(pathParamsType, "pathParams");
511
- pathParams = this.openapiDefinitions[pathParamsType];
582
+ pathParams = this.openapiDefinitions[pathParamsType] || {};
512
583
  }
513
584
  if (bodyType && !body) {
514
585
  this.findSchemaDefinition(bodyType, "body");
515
- body = this.openapiDefinitions[bodyType];
586
+ body = this.openapiDefinitions[bodyType] || {};
516
587
  }
517
588
  if (responseType && !responses) {
518
589
  this.findSchemaDefinition(responseType, "response");
519
- responses = this.openapiDefinitions[responseType];
590
+ responses = this.openapiDefinitions[responseType] || {};
520
591
  }
521
592
  return {
522
593
  params,
package/dist/lib/utils.js CHANGED
@@ -18,10 +18,10 @@ export function extractJSDocComments(path) {
18
18
  const comments = path.node.leadingComments;
19
19
  let summary = "";
20
20
  let description = "";
21
- let paramsType = "";
22
- let pathParamsType = "";
23
- let bodyType = "";
24
- let responseType = "";
21
+ let params = "";
22
+ let pathParams = "";
23
+ let body = "";
24
+ let response = "";
25
25
  let auth = "";
26
26
  let isOpenApi = false;
27
27
  if (comments) {
@@ -32,7 +32,7 @@ export function extractJSDocComments(path) {
32
32
  summary = commentValue.split("\n")[0];
33
33
  }
34
34
  if (commentValue.includes("@auth")) {
35
- const regex = /@auth:\s*(.*)/;
35
+ const regex = /@auth\s*(.*)/;
36
36
  const value = commentValue.match(regex)[1].trim();
37
37
  switch (value) {
38
38
  case "bearer":
@@ -47,20 +47,20 @@ export function extractJSDocComments(path) {
47
47
  }
48
48
  }
49
49
  if (commentValue.includes("@desc")) {
50
- const regex = /@desc:\s*(.*)/;
50
+ const regex = /@desc\s*(.*)/;
51
51
  description = commentValue.match(regex)[1].trim();
52
52
  }
53
53
  if (commentValue.includes("@params")) {
54
- paramsType = extractTypeFromComment(commentValue, "@params");
54
+ params = extractTypeFromComment(commentValue, "@params");
55
55
  }
56
56
  if (commentValue.includes("@pathParams")) {
57
- pathParamsType = extractTypeFromComment(commentValue, "@pathParams");
57
+ pathParams = extractTypeFromComment(commentValue, "@pathParams");
58
58
  }
59
59
  if (commentValue.includes("@body")) {
60
- bodyType = extractTypeFromComment(commentValue, "@body");
60
+ body = extractTypeFromComment(commentValue, "@body");
61
61
  }
62
62
  if (commentValue.includes("@response")) {
63
- responseType = extractTypeFromComment(commentValue, "@response");
63
+ response = extractTypeFromComment(commentValue, "@response");
64
64
  }
65
65
  });
66
66
  }
@@ -68,10 +68,10 @@ export function extractJSDocComments(path) {
68
68
  auth,
69
69
  summary,
70
70
  description,
71
- paramsType,
72
- pathParamsType,
73
- bodyType,
74
- responseType,
71
+ params,
72
+ pathParams,
73
+ body,
74
+ response,
75
75
  isOpenApi,
76
76
  };
77
77
  }