next-openapi-gen 0.10.0 → 0.10.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 +2 -2
- package/dist/lib/drizzle-zod-processor.js +5 -3
- package/dist/lib/schema-processor.js +20 -5
- package/dist/lib/zod-converter.js +24 -29
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -72,7 +72,7 @@ During initialization (`npx next-openapi-gen init`), a configuration file `next.
|
|
|
72
72
|
],
|
|
73
73
|
"apiDir": "src/app/api", // or "pages/api" for Pages Router
|
|
74
74
|
"routerType": "app", // "app" (default) or "pages" for legacy Pages Router
|
|
75
|
-
"schemaDir": "src/types", // or "src/schemas" for
|
|
75
|
+
"schemaDir": "src/types", // or ["src/types", "src/schemas"] for multiple directories
|
|
76
76
|
"schemaType": "zod", // or "typescript", or ["zod", "typescript"] for multiple
|
|
77
77
|
"schemaFiles": [], // Optional: ["./schemas/models.yaml", "./schemas/api.json"]
|
|
78
78
|
"outputFile": "openapi.json",
|
|
@@ -90,7 +90,7 @@ During initialization (`npx next-openapi-gen init`), a configuration file `next.
|
|
|
90
90
|
| ---------------------- | ----------------------------------------------------------------------------- |
|
|
91
91
|
| `apiDir` | Path to the API directory |
|
|
92
92
|
| `routerType` | Router type: `"app"` (default) or `"pages"` for legacy Pages Router |
|
|
93
|
-
| `schemaDir` | Path to
|
|
93
|
+
| `schemaDir` | Path to types/schemas directory, or array of paths for multiple directories |
|
|
94
94
|
| `schemaType` | Schema type: `"zod"`, `"typescript"`, or `["zod", "typescript"]` for multiple |
|
|
95
95
|
| `schemaFiles` | Optional: Array of custom OpenAPI schema files (YAML/JSON) to include |
|
|
96
96
|
| `outputFile` | Name of the OpenAPI output file |
|
|
@@ -119,7 +119,6 @@ export class DrizzleZodProcessor {
|
|
|
119
119
|
? node.callee.property.name
|
|
120
120
|
: null;
|
|
121
121
|
if (methodName === "optional" ||
|
|
122
|
-
methodName === "nullable" ||
|
|
123
122
|
methodName === "nullish") {
|
|
124
123
|
return true;
|
|
125
124
|
}
|
|
@@ -291,10 +290,13 @@ export class DrizzleZodProcessor {
|
|
|
291
290
|
result.type = "integer";
|
|
292
291
|
break;
|
|
293
292
|
case "optional":
|
|
293
|
+
// Handled by isFieldOptional check, no schema modification needed
|
|
294
|
+
break;
|
|
294
295
|
case "nullable":
|
|
296
|
+
result.nullable = true;
|
|
297
|
+
break;
|
|
295
298
|
case "nullish":
|
|
296
|
-
|
|
297
|
-
// Don't modify the schema here
|
|
299
|
+
result.nullable = true;
|
|
298
300
|
break;
|
|
299
301
|
case "describe":
|
|
300
302
|
if (args.length > 0 && t.isStringLiteral(args[0])) {
|
|
@@ -14,8 +14,14 @@ import { logger } from "./logger.js";
|
|
|
14
14
|
function normalizeSchemaTypes(schemaType) {
|
|
15
15
|
return Array.isArray(schemaType) ? schemaType : [schemaType];
|
|
16
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* Normalize schemaDir to array
|
|
19
|
+
*/
|
|
20
|
+
function normalizeSchemaDirs(schemaDir) {
|
|
21
|
+
return Array.isArray(schemaDir) ? schemaDir : [schemaDir];
|
|
22
|
+
}
|
|
17
23
|
export class SchemaProcessor {
|
|
18
|
-
|
|
24
|
+
schemaDirs;
|
|
19
25
|
typeDefinitions = {};
|
|
20
26
|
openapiDefinitions = {};
|
|
21
27
|
contentType = "";
|
|
@@ -31,7 +37,7 @@ export class SchemaProcessor {
|
|
|
31
37
|
importMap = {}; // { filePath: { importName: importPath } }
|
|
32
38
|
currentFilePath = ""; // Track the file being processed
|
|
33
39
|
constructor(schemaDir, schemaType = "typescript", schemaFiles) {
|
|
34
|
-
this.
|
|
40
|
+
this.schemaDirs = normalizeSchemaDirs(schemaDir).map((d) => path.resolve(d));
|
|
35
41
|
this.schemaTypes = normalizeSchemaTypes(schemaType);
|
|
36
42
|
// Initialize Zod converter if Zod is enabled
|
|
37
43
|
if (this.schemaTypes.includes("zod")) {
|
|
@@ -140,9 +146,18 @@ export class SchemaProcessor {
|
|
|
140
146
|
logger.debug(`No Zod schema found for ${schemaName}, trying TypeScript fallback`);
|
|
141
147
|
}
|
|
142
148
|
// Fall back to TypeScript types
|
|
143
|
-
this.
|
|
149
|
+
this.scanAllSchemaDirs(schemaName);
|
|
144
150
|
return this.openapiDefinitions[schemaName] || {};
|
|
145
151
|
}
|
|
152
|
+
scanAllSchemaDirs(schemaName) {
|
|
153
|
+
for (const dir of this.schemaDirs) {
|
|
154
|
+
if (!fs.existsSync(dir)) {
|
|
155
|
+
logger.warn(`Schema directory not found: ${dir}`);
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
this.scanSchemaDir(dir, schemaName);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
146
161
|
scanSchemaDir(dir, schemaName) {
|
|
147
162
|
let files = this.directoryCache[dir];
|
|
148
163
|
if (typeof files === "undefined") {
|
|
@@ -1229,7 +1244,7 @@ export class SchemaProcessor {
|
|
|
1229
1244
|
}
|
|
1230
1245
|
const { baseTypeName, typeArguments } = parsed;
|
|
1231
1246
|
// Find the base generic type definition
|
|
1232
|
-
this.
|
|
1247
|
+
this.scanAllSchemaDirs(baseTypeName);
|
|
1233
1248
|
const genericDefEntry = this.typeDefinitions[baseTypeName];
|
|
1234
1249
|
const genericTypeDefinition = genericDefEntry?.node || genericDefEntry;
|
|
1235
1250
|
if (!genericTypeDefinition) {
|
|
@@ -1241,7 +1256,7 @@ export class SchemaProcessor {
|
|
|
1241
1256
|
// If it's a simple type reference (not another generic), find its definition
|
|
1242
1257
|
if (!argTypeName.includes("<") &&
|
|
1243
1258
|
!this.isGenericTypeParameter(argTypeName)) {
|
|
1244
|
-
this.
|
|
1259
|
+
this.scanAllSchemaDirs(argTypeName);
|
|
1245
1260
|
}
|
|
1246
1261
|
});
|
|
1247
1262
|
// Create AST nodes for the type arguments by parsing them
|
|
@@ -11,7 +11,7 @@ import { DrizzleZodProcessor } from "./drizzle-zod-processor.js";
|
|
|
11
11
|
* Class for converting Zod schemas to OpenAPI specifications
|
|
12
12
|
*/
|
|
13
13
|
export class ZodSchemaConverter {
|
|
14
|
-
|
|
14
|
+
schemaDirs;
|
|
15
15
|
zodSchemas = {};
|
|
16
16
|
processingSchemas = new Set();
|
|
17
17
|
processedModules = new Set();
|
|
@@ -26,7 +26,8 @@ export class ZodSchemaConverter {
|
|
|
26
26
|
currentAST;
|
|
27
27
|
currentImports;
|
|
28
28
|
constructor(schemaDir) {
|
|
29
|
-
|
|
29
|
+
const dirs = Array.isArray(schemaDir) ? schemaDir : [schemaDir];
|
|
30
|
+
this.schemaDirs = dirs.map((d) => path.resolve(d));
|
|
30
31
|
}
|
|
31
32
|
/**
|
|
32
33
|
* Find a Zod schema by name and convert it to OpenAPI spec
|
|
@@ -63,8 +64,12 @@ export class ZodSchemaConverter {
|
|
|
63
64
|
return this.zodSchemas[schemaName];
|
|
64
65
|
}
|
|
65
66
|
}
|
|
66
|
-
// Scan schema
|
|
67
|
-
|
|
67
|
+
// Scan schema directories
|
|
68
|
+
for (const dir of this.schemaDirs) {
|
|
69
|
+
this.scanDirectoryForZodSchema(dir, schemaName);
|
|
70
|
+
if (this.zodSchemas[schemaName])
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
68
73
|
// Return the schema if found, or null if not
|
|
69
74
|
if (this.zodSchemas[schemaName]) {
|
|
70
75
|
logger.debug(`Found and processed Zod schema: ${schemaName}`);
|
|
@@ -347,12 +352,8 @@ export class ZodSchemaConverter {
|
|
|
347
352
|
}
|
|
348
353
|
break;
|
|
349
354
|
case "partial":
|
|
350
|
-
// All fields become optional
|
|
355
|
+
// All fields become optional (T | undefined), not nullable
|
|
351
356
|
if (schema.properties) {
|
|
352
|
-
Object.keys(schema.properties).forEach((key) => {
|
|
353
|
-
schema.properties[key].nullable = true;
|
|
354
|
-
});
|
|
355
|
-
// Remove all required
|
|
356
357
|
delete schema.required;
|
|
357
358
|
}
|
|
358
359
|
break;
|
|
@@ -390,14 +391,9 @@ export class ZodSchemaConverter {
|
|
|
390
391
|
}
|
|
391
392
|
break;
|
|
392
393
|
case "required":
|
|
393
|
-
// All fields become required
|
|
394
|
+
// All fields become required — preserve genuine nullable flags
|
|
394
395
|
if (schema.properties) {
|
|
395
|
-
|
|
396
|
-
schema.required = requiredFields;
|
|
397
|
-
// Remove nullable from fields
|
|
398
|
-
Object.keys(schema.properties).forEach((key) => {
|
|
399
|
-
delete schema.properties[key].nullable;
|
|
400
|
-
});
|
|
396
|
+
schema.required = Object.keys(schema.properties);
|
|
401
397
|
}
|
|
402
398
|
break;
|
|
403
399
|
case "extend":
|
|
@@ -418,8 +414,9 @@ export class ZodSchemaConverter {
|
|
|
418
414
|
const propSchema = this.processZodNode(prop.value);
|
|
419
415
|
if (propSchema) {
|
|
420
416
|
extensionProperties[key] = propSchema;
|
|
421
|
-
|
|
422
|
-
|
|
417
|
+
const isOptional =
|
|
418
|
+
// @ts-ignore
|
|
419
|
+
this.isOptional(prop.value) || this.hasOptionalMethod(prop.value);
|
|
423
420
|
if (!isOptional) {
|
|
424
421
|
extensionRequired.push(key);
|
|
425
422
|
}
|
|
@@ -1239,20 +1236,17 @@ export class ZodSchemaConverter {
|
|
|
1239
1236
|
// Apply the current method
|
|
1240
1237
|
switch (methodName) {
|
|
1241
1238
|
case "optional":
|
|
1242
|
-
//
|
|
1243
|
-
//
|
|
1244
|
-
if (!schema.allOf) {
|
|
1245
|
-
schema.nullable = true;
|
|
1246
|
-
}
|
|
1239
|
+
// optional means T | undefined — not in required array, no nullable flag
|
|
1240
|
+
// Required array exclusion is handled by hasOptionalMethod() in processZodObject()
|
|
1247
1241
|
break;
|
|
1248
1242
|
case "nullable":
|
|
1249
|
-
//
|
|
1243
|
+
// nullable means T | null — field stays required but can be null
|
|
1250
1244
|
if (!schema.allOf) {
|
|
1251
1245
|
schema.nullable = true;
|
|
1252
1246
|
}
|
|
1253
1247
|
break;
|
|
1254
|
-
case "nullish": //
|
|
1255
|
-
//
|
|
1248
|
+
case "nullish": // T | null | undefined
|
|
1249
|
+
// Not in required array (handled by hasOptionalMethod) AND can be null
|
|
1256
1250
|
if (!schema.allOf) {
|
|
1257
1251
|
schema.nullable = true;
|
|
1258
1252
|
}
|
|
@@ -1540,7 +1534,6 @@ export class ZodSchemaConverter {
|
|
|
1540
1534
|
if (t.isMemberExpression(node.callee) &&
|
|
1541
1535
|
t.isIdentifier(node.callee.property) &&
|
|
1542
1536
|
(node.callee.property.name === "optional" ||
|
|
1543
|
-
node.callee.property.name === "nullable" ||
|
|
1544
1537
|
node.callee.property.name === "nullish")) {
|
|
1545
1538
|
return true;
|
|
1546
1539
|
}
|
|
@@ -1566,8 +1559,10 @@ export class ZodSchemaConverter {
|
|
|
1566
1559
|
for (const routeFile of routeFiles) {
|
|
1567
1560
|
this.scanFileForTypeMappings(routeFile);
|
|
1568
1561
|
}
|
|
1569
|
-
// Scan schema
|
|
1570
|
-
|
|
1562
|
+
// Scan schema directories
|
|
1563
|
+
for (const dir of this.schemaDirs) {
|
|
1564
|
+
this.scanDirectoryForTypeMappings(dir);
|
|
1565
|
+
}
|
|
1571
1566
|
}
|
|
1572
1567
|
/**
|
|
1573
1568
|
* Scan a single file for type mappings
|
package/package.json
CHANGED