next-openapi-gen 0.4.5 → 0.5.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/lib/route-processor.js +2 -2
- package/dist/lib/schema-processor.js +23 -5
- package/dist/lib/utils.js +12 -12
- package/dist/lib/zod-converter.js +175 -30
- package/package.json +1 -1
|
@@ -50,7 +50,7 @@ export class RouteProcessor {
|
|
|
50
50
|
const routePath = this.getRoutePath(filePath);
|
|
51
51
|
const pathParams = extractPathParameters(routePath);
|
|
52
52
|
// If we have path parameters but no pathParamsType defined, we should log a warning
|
|
53
|
-
if (pathParams.length > 0 && !dataTypes.
|
|
53
|
+
if (pathParams.length > 0 && !dataTypes.pathParamsType) {
|
|
54
54
|
console.warn(`Route ${routePath} contains path parameters ${pathParams.join(", ")} but no @pathParams type is defined.`);
|
|
55
55
|
}
|
|
56
56
|
this.addRouteToPaths(declaration.id.name, filePath, dataTypes);
|
|
@@ -67,7 +67,7 @@ export class RouteProcessor {
|
|
|
67
67
|
(this.config.includeOpenApiRoutes && dataTypes.isOpenApi)) {
|
|
68
68
|
const routePath = this.getRoutePath(filePath);
|
|
69
69
|
const pathParams = extractPathParameters(routePath);
|
|
70
|
-
if (pathParams.length > 0 && !dataTypes.
|
|
70
|
+
if (pathParams.length > 0 && !dataTypes.pathParamsType) {
|
|
71
71
|
console.warn(`Route ${routePath} contains path parameters ${pathParams.join(", ")} but no @pathParams type is defined.`);
|
|
72
72
|
}
|
|
73
73
|
this.addRouteToPaths(decl.id.name, filePath, dataTypes);
|
|
@@ -26,6 +26,14 @@ export class SchemaProcessor {
|
|
|
26
26
|
* Get all defined schemas (for components.schemas section)
|
|
27
27
|
*/
|
|
28
28
|
getDefinedSchemas() {
|
|
29
|
+
// If using Zod, also include all processed Zod schemas
|
|
30
|
+
if (this.schemaType === "zod" && this.zodSchemaConverter) {
|
|
31
|
+
const zodSchemas = this.zodSchemaConverter.getProcessedSchemas();
|
|
32
|
+
return {
|
|
33
|
+
...this.openapiDefinitions,
|
|
34
|
+
...zodSchemas,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
29
37
|
return this.openapiDefinitions;
|
|
30
38
|
}
|
|
31
39
|
findSchemaDefinition(schemaName, contentType) {
|
|
@@ -441,10 +449,7 @@ export class SchemaProcessor {
|
|
|
441
449
|
if (description) {
|
|
442
450
|
options.description = description;
|
|
443
451
|
}
|
|
444
|
-
if (this.contentType === "
|
|
445
|
-
options.required = !isOptional;
|
|
446
|
-
}
|
|
447
|
-
else if (this.contentType === "body") {
|
|
452
|
+
if (this.contentType === "body") {
|
|
448
453
|
options.nullable = isOptional;
|
|
449
454
|
}
|
|
450
455
|
return options;
|
|
@@ -526,7 +531,7 @@ export class SchemaProcessor {
|
|
|
526
531
|
schema: {
|
|
527
532
|
type: value.type,
|
|
528
533
|
},
|
|
529
|
-
required: isPathParam ? true : value.required, // Path parameters are always required
|
|
534
|
+
required: isPathParam ? true : !!value.required, // Path parameters are always required
|
|
530
535
|
};
|
|
531
536
|
if (value.enum) {
|
|
532
537
|
param.schema.enum = value.enum;
|
|
@@ -589,6 +594,19 @@ export class SchemaProcessor {
|
|
|
589
594
|
this.findSchemaDefinition(responseType, "response");
|
|
590
595
|
responses = this.openapiDefinitions[responseType] || {};
|
|
591
596
|
}
|
|
597
|
+
if (this.schemaType === "zod") {
|
|
598
|
+
const schemasToProcess = [
|
|
599
|
+
paramsType,
|
|
600
|
+
pathParamsType,
|
|
601
|
+
bodyType,
|
|
602
|
+
responseType,
|
|
603
|
+
].filter(Boolean);
|
|
604
|
+
schemasToProcess.forEach((schemaName) => {
|
|
605
|
+
if (!this.openapiDefinitions[schemaName]) {
|
|
606
|
+
this.findSchemaDefinition(schemaName, "");
|
|
607
|
+
}
|
|
608
|
+
});
|
|
609
|
+
}
|
|
592
610
|
return {
|
|
593
611
|
params,
|
|
594
612
|
pathParams,
|
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
|
|
22
|
-
let
|
|
23
|
-
let
|
|
24
|
-
let
|
|
21
|
+
let paramsType = "";
|
|
22
|
+
let pathParamsType = "";
|
|
23
|
+
let bodyType = "";
|
|
24
|
+
let responseType = "";
|
|
25
25
|
let auth = "";
|
|
26
26
|
let isOpenApi = false;
|
|
27
27
|
if (comments) {
|
|
@@ -51,16 +51,16 @@ export function extractJSDocComments(path) {
|
|
|
51
51
|
description = commentValue.match(regex)[1].trim();
|
|
52
52
|
}
|
|
53
53
|
if (commentValue.includes("@params")) {
|
|
54
|
-
|
|
54
|
+
paramsType = extractTypeFromComment(commentValue, "@params");
|
|
55
55
|
}
|
|
56
56
|
if (commentValue.includes("@pathParams")) {
|
|
57
|
-
|
|
57
|
+
pathParamsType = extractTypeFromComment(commentValue, "@pathParams");
|
|
58
58
|
}
|
|
59
59
|
if (commentValue.includes("@body")) {
|
|
60
|
-
|
|
60
|
+
bodyType = extractTypeFromComment(commentValue, "@body");
|
|
61
61
|
}
|
|
62
62
|
if (commentValue.includes("@response")) {
|
|
63
|
-
|
|
63
|
+
responseType = 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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
paramsType,
|
|
72
|
+
pathParamsType,
|
|
73
|
+
bodyType,
|
|
74
|
+
responseType,
|
|
75
75
|
isOpenApi,
|
|
76
76
|
};
|
|
77
77
|
}
|
|
@@ -9,7 +9,6 @@ import * as t from "@babel/types";
|
|
|
9
9
|
export class ZodSchemaConverter {
|
|
10
10
|
schemaDir;
|
|
11
11
|
zodSchemas = {};
|
|
12
|
-
processedFiles = {};
|
|
13
12
|
processingSchemas = new Set();
|
|
14
13
|
processedModules = new Set();
|
|
15
14
|
constructor(schemaDir) {
|
|
@@ -111,10 +110,6 @@ export class ZodSchemaConverter {
|
|
|
111
110
|
}
|
|
112
111
|
else if (file.endsWith(".ts") || file.endsWith(".tsx")) {
|
|
113
112
|
this.processFileForZodSchema(filePath, schemaName);
|
|
114
|
-
// Stop searching if we found the schema
|
|
115
|
-
if (this.zodSchemas[schemaName]) {
|
|
116
|
-
break;
|
|
117
|
-
}
|
|
118
113
|
}
|
|
119
114
|
}
|
|
120
115
|
}
|
|
@@ -126,12 +121,12 @@ export class ZodSchemaConverter {
|
|
|
126
121
|
* Process a file to find Zod schema definitions
|
|
127
122
|
*/
|
|
128
123
|
processFileForZodSchema(filePath, schemaName) {
|
|
129
|
-
// Skip if already processed
|
|
130
|
-
if (this.processedFiles[filePath])
|
|
131
|
-
return;
|
|
132
|
-
this.processedFiles[filePath] = true;
|
|
133
124
|
try {
|
|
134
125
|
const content = fs.readFileSync(filePath, "utf-8");
|
|
126
|
+
// Check if file contains schema we are looking for
|
|
127
|
+
if (!content.includes(schemaName)) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
135
130
|
// Parse the file
|
|
136
131
|
const ast = parse(content, {
|
|
137
132
|
sourceType: "module",
|
|
@@ -161,9 +156,25 @@ export class ZodSchemaConverter {
|
|
|
161
156
|
if (t.isIdentifier(declaration.id) &&
|
|
162
157
|
declaration.id.name === schemaName &&
|
|
163
158
|
declaration.init) {
|
|
164
|
-
|
|
165
|
-
if (
|
|
166
|
-
|
|
159
|
+
// Check if this is a call expression with .extend()
|
|
160
|
+
if (t.isCallExpression(declaration.init) &&
|
|
161
|
+
t.isMemberExpression(declaration.init.callee) &&
|
|
162
|
+
t.isIdentifier(declaration.init.callee.property) &&
|
|
163
|
+
declaration.init.callee.property.name === "extend") {
|
|
164
|
+
const schema = this.processZodNode(declaration.init);
|
|
165
|
+
if (schema) {
|
|
166
|
+
this.zodSchemas[schemaName] = schema;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// Existing code for z.object({...})
|
|
170
|
+
else if (t.isCallExpression(declaration.init) &&
|
|
171
|
+
t.isMemberExpression(declaration.init.callee) &&
|
|
172
|
+
t.isIdentifier(declaration.init.callee.object) &&
|
|
173
|
+
declaration.init.callee.object.name === "z") {
|
|
174
|
+
const schema = this.processZodNode(declaration.init);
|
|
175
|
+
if (schema) {
|
|
176
|
+
this.zodSchemas[schemaName] = schema;
|
|
177
|
+
}
|
|
167
178
|
}
|
|
168
179
|
}
|
|
169
180
|
});
|
|
@@ -208,9 +219,22 @@ export class ZodSchemaConverter {
|
|
|
208
219
|
if (t.isIdentifier(path.node.id) &&
|
|
209
220
|
path.node.id.name === schemaName &&
|
|
210
221
|
path.node.init) {
|
|
211
|
-
|
|
212
|
-
if (
|
|
213
|
-
|
|
222
|
+
// Check if it is .extend()
|
|
223
|
+
if (t.isCallExpression(path.node.init) &&
|
|
224
|
+
t.isMemberExpression(path.node.init.callee) &&
|
|
225
|
+
t.isIdentifier(path.node.init.callee.property) &&
|
|
226
|
+
path.node.init.callee.property.name === "extend") {
|
|
227
|
+
const schema = this.processZodNode(path.node.init);
|
|
228
|
+
if (schema) {
|
|
229
|
+
this.zodSchemas[schemaName] = schema;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
// Existing code
|
|
233
|
+
else {
|
|
234
|
+
const schema = this.processZodNode(path.node.init);
|
|
235
|
+
if (schema) {
|
|
236
|
+
this.zodSchemas[schemaName] = schema;
|
|
237
|
+
}
|
|
214
238
|
}
|
|
215
239
|
}
|
|
216
240
|
},
|
|
@@ -243,10 +267,64 @@ export class ZodSchemaConverter {
|
|
|
243
267
|
console.error(`Error processing file ${filePath} for schema ${schemaName}: ${error}`);
|
|
244
268
|
}
|
|
245
269
|
}
|
|
270
|
+
/**
|
|
271
|
+
* Process all exported schemas in a file, not just the one we're looking for
|
|
272
|
+
*/
|
|
273
|
+
processAllSchemasInFile(filePath) {
|
|
274
|
+
try {
|
|
275
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
276
|
+
const ast = parse(content, {
|
|
277
|
+
sourceType: "module",
|
|
278
|
+
plugins: ["typescript", "decorators-legacy"],
|
|
279
|
+
});
|
|
280
|
+
traverse.default(ast, {
|
|
281
|
+
ExportNamedDeclaration: (path) => {
|
|
282
|
+
if (t.isVariableDeclaration(path.node.declaration)) {
|
|
283
|
+
path.node.declaration.declarations.forEach((declaration) => {
|
|
284
|
+
if (t.isIdentifier(declaration.id) &&
|
|
285
|
+
declaration.init &&
|
|
286
|
+
t.isCallExpression(declaration.init) &&
|
|
287
|
+
t.isMemberExpression(declaration.init.callee) &&
|
|
288
|
+
t.isIdentifier(declaration.init.callee.object) &&
|
|
289
|
+
declaration.init.callee.object.name === "z") {
|
|
290
|
+
const schemaName = declaration.id.name;
|
|
291
|
+
if (!this.zodSchemas[schemaName] &&
|
|
292
|
+
!this.processingSchemas.has(schemaName)) {
|
|
293
|
+
this.processingSchemas.add(schemaName);
|
|
294
|
+
const schema = this.processZodNode(declaration.init);
|
|
295
|
+
if (schema) {
|
|
296
|
+
this.zodSchemas[schemaName] = schema;
|
|
297
|
+
}
|
|
298
|
+
this.processingSchemas.delete(schemaName);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
},
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
catch (error) {
|
|
307
|
+
console.error(`Error processing all schemas in file ${filePath}: ${error}`);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
246
310
|
/**
|
|
247
311
|
* Process a Zod node and convert it to OpenAPI schema
|
|
248
312
|
*/
|
|
249
313
|
processZodNode(node) {
|
|
314
|
+
// Handle reference to another schema (e.g. UserBaseSchema.extend)
|
|
315
|
+
if (t.isCallExpression(node) &&
|
|
316
|
+
t.isMemberExpression(node.callee) &&
|
|
317
|
+
t.isIdentifier(node.callee.object) &&
|
|
318
|
+
t.isIdentifier(node.callee.property) &&
|
|
319
|
+
node.callee.property.name === "extend") {
|
|
320
|
+
const baseSchemaName = node.callee.object.name;
|
|
321
|
+
// Check if the base schema already exists
|
|
322
|
+
if (!this.zodSchemas[baseSchemaName]) {
|
|
323
|
+
// Try to find the basic pattern
|
|
324
|
+
this.convertZodSchemaToOpenApi(baseSchemaName);
|
|
325
|
+
}
|
|
326
|
+
return this.processZodChain(node);
|
|
327
|
+
}
|
|
250
328
|
// Handle z.object({...})
|
|
251
329
|
if (t.isCallExpression(node) &&
|
|
252
330
|
t.isMemberExpression(node.callee) &&
|
|
@@ -461,7 +539,7 @@ export class ZodSchemaConverter {
|
|
|
461
539
|
const objectExpression = node.arguments[0];
|
|
462
540
|
const properties = {};
|
|
463
541
|
const required = [];
|
|
464
|
-
objectExpression.properties.forEach((prop) => {
|
|
542
|
+
objectExpression.properties.forEach((prop, index) => {
|
|
465
543
|
if (t.isObjectProperty(prop)) {
|
|
466
544
|
let propName;
|
|
467
545
|
// Handle both identifier and string literal keys
|
|
@@ -472,15 +550,56 @@ export class ZodSchemaConverter {
|
|
|
472
550
|
propName = prop.key.value;
|
|
473
551
|
}
|
|
474
552
|
else {
|
|
553
|
+
console.log(`Skipping property ${index} - unsupported key type`);
|
|
475
554
|
return; // Skip if key is not identifier or string literal
|
|
476
555
|
}
|
|
556
|
+
// Check if the property value is an identifier (reference to another schema)
|
|
557
|
+
if (t.isIdentifier(prop.value)) {
|
|
558
|
+
const referencedSchemaName = prop.value.name;
|
|
559
|
+
// Try to find and convert the referenced schema
|
|
560
|
+
if (!this.zodSchemas[referencedSchemaName]) {
|
|
561
|
+
this.convertZodSchemaToOpenApi(referencedSchemaName);
|
|
562
|
+
}
|
|
563
|
+
// Create a reference
|
|
564
|
+
properties[propName] = {
|
|
565
|
+
$ref: `#/components/schemas/${referencedSchemaName}`,
|
|
566
|
+
};
|
|
567
|
+
required.push(propName); // Assuming it's required unless marked optional
|
|
568
|
+
}
|
|
569
|
+
// For array of schemas (like z.array(PaymentMethodSchema))
|
|
570
|
+
if (t.isCallExpression(prop.value) &&
|
|
571
|
+
t.isMemberExpression(prop.value.callee) &&
|
|
572
|
+
t.isIdentifier(prop.value.callee.object) &&
|
|
573
|
+
prop.value.callee.object.name === "z" &&
|
|
574
|
+
t.isIdentifier(prop.value.callee.property) &&
|
|
575
|
+
prop.value.callee.property.name === "array" &&
|
|
576
|
+
prop.value.arguments.length > 0 &&
|
|
577
|
+
t.isIdentifier(prop.value.arguments[0])) {
|
|
578
|
+
const itemSchemaName = prop.value.arguments[0].name;
|
|
579
|
+
// Try to find and convert the referenced schema
|
|
580
|
+
if (!this.zodSchemas[itemSchemaName]) {
|
|
581
|
+
this.convertZodSchemaToOpenApi(itemSchemaName);
|
|
582
|
+
}
|
|
583
|
+
// Process as array with reference
|
|
584
|
+
const arraySchema = this.processZodNode(prop.value);
|
|
585
|
+
arraySchema.items = {
|
|
586
|
+
$ref: `#/components/schemas/${itemSchemaName}`,
|
|
587
|
+
};
|
|
588
|
+
properties[propName] = arraySchema;
|
|
589
|
+
const isOptional = this.isOptional(prop.value) || this.hasOptionalMethod(prop.value);
|
|
590
|
+
if (!isOptional) {
|
|
591
|
+
required.push(propName);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
477
594
|
// Process property value (a Zod schema)
|
|
478
595
|
const propSchema = this.processZodNode(prop.value);
|
|
479
596
|
if (propSchema) {
|
|
480
597
|
properties[propName] = propSchema;
|
|
481
598
|
// If the property is not marked as optional, add it to required list
|
|
599
|
+
const isOptional =
|
|
482
600
|
// @ts-ignore
|
|
483
|
-
|
|
601
|
+
this.isOptional(prop.value) || this.hasOptionalMethod(prop.value);
|
|
602
|
+
if (!isOptional) {
|
|
484
603
|
required.push(propName);
|
|
485
604
|
}
|
|
486
605
|
}
|
|
@@ -534,7 +653,20 @@ export class ZodSchemaConverter {
|
|
|
534
653
|
case "array":
|
|
535
654
|
let itemsType = { type: "string" };
|
|
536
655
|
if (node.arguments.length > 0) {
|
|
537
|
-
|
|
656
|
+
// Check if argument is an identifier (schema reference)
|
|
657
|
+
if (t.isIdentifier(node.arguments[0])) {
|
|
658
|
+
const schemaName = node.arguments[0].name;
|
|
659
|
+
// Try to find and convert the referenced schema
|
|
660
|
+
if (!this.zodSchemas[schemaName]) {
|
|
661
|
+
this.convertZodSchemaToOpenApi(schemaName);
|
|
662
|
+
}
|
|
663
|
+
// @ts-ignore
|
|
664
|
+
itemsType = { $ref: `#/components/schemas/${schemaName}` };
|
|
665
|
+
}
|
|
666
|
+
else {
|
|
667
|
+
// @ts-ignore
|
|
668
|
+
itemsType = this.processZodNode(node.arguments[0]);
|
|
669
|
+
}
|
|
538
670
|
}
|
|
539
671
|
schema = { type: "array", items: itemsType };
|
|
540
672
|
break;
|
|
@@ -806,7 +938,10 @@ export class ZodSchemaConverter {
|
|
|
806
938
|
case "extend":
|
|
807
939
|
if (node.arguments.length > 0 &&
|
|
808
940
|
t.isObjectExpression(node.arguments[0])) {
|
|
809
|
-
|
|
941
|
+
// Get the base schema by processing the object that extend is called on
|
|
942
|
+
const baseSchema = this.processZodNode(node.callee.object);
|
|
943
|
+
// Process the extension object
|
|
944
|
+
const extendNode = {
|
|
810
945
|
type: "CallExpression",
|
|
811
946
|
callee: {
|
|
812
947
|
type: "MemberExpression",
|
|
@@ -816,18 +951,28 @@ export class ZodSchemaConverter {
|
|
|
816
951
|
optional: false,
|
|
817
952
|
},
|
|
818
953
|
arguments: [node.arguments[0]],
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
954
|
+
};
|
|
955
|
+
const extendedProps = this.processZodObject(extendNode);
|
|
956
|
+
// Merge base schema and extensions
|
|
957
|
+
if (baseSchema && baseSchema.properties) {
|
|
958
|
+
schema = {
|
|
959
|
+
type: "object",
|
|
960
|
+
properties: {
|
|
961
|
+
...baseSchema.properties,
|
|
962
|
+
...(extendedProps?.properties || {}),
|
|
963
|
+
},
|
|
964
|
+
required: [
|
|
965
|
+
...(baseSchema.required || []),
|
|
966
|
+
...(extendedProps?.required || []),
|
|
967
|
+
].filter((item, index, arr) => arr.indexOf(item) === index), // Remove duplicates
|
|
824
968
|
};
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
969
|
+
// Copy other properties from base schema
|
|
970
|
+
if (baseSchema.description)
|
|
971
|
+
schema.description = baseSchema.description;
|
|
972
|
+
}
|
|
973
|
+
else {
|
|
974
|
+
console.log(`Warning: Could not resolve base schema for extend`);
|
|
975
|
+
schema = extendedProps || { type: "object" };
|
|
831
976
|
}
|
|
832
977
|
}
|
|
833
978
|
break;
|
package/package.json
CHANGED