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 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 Zod schemas
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 the types/schemas directory |
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
- // These are handled by the isFieldOptional check
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
- schemaDir;
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.schemaDir = path.resolve(schemaDir);
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.scanSchemaDir(this.schemaDir, schemaName);
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.scanSchemaDir(this.schemaDir, baseTypeName);
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.scanSchemaDir(this.schemaDir, argTypeName);
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
- schemaDir;
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
- this.schemaDir = path.resolve(schemaDir);
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 directory
67
- this.scanDirectoryForZodSchema(this.schemaDir, schemaName);
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
- const requiredFields = Object.keys(schema.properties);
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
- // Check if the schema itself has nullable set (which processZodNode sets for optional fields)
422
- const isOptional = propSchema.nullable === true;
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
- // Don't add nullable for schema references wrapped in allOf
1243
- // as it doesn't make sense in that context
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
- // Don't add nullable for schema references wrapped in allOf
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": // Handles both null and undefined
1255
- // Don't add nullable for schema references wrapped in allOf
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 directory
1570
- this.scanDirectoryForTypeMappings(this.schemaDir);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-openapi-gen",
3
- "version": "0.10.0",
3
+ "version": "0.10.2",
4
4
  "description": "Automatically generate OpenAPI 3.0 documentation from Next.js projects, with support for Zod schemas and TypeScript types.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",