next-openapi-gen 0.5.1 → 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/dist/lib/schema-processor.js +5 -0
- package/dist/lib/zod-converter.js +138 -15
- package/package.json +1 -1
|
@@ -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) {
|
|
@@ -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
|
|
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.
|
|
248
|
-
path.node.typeAnnotation.typeName.
|
|
249
|
-
path.node.typeAnnotation.
|
|
250
|
-
path.node.typeAnnotation.
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
if (
|
|
257
|
-
|
|
258
|
-
|
|
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