next-openapi-gen 0.6.5 → 0.6.7

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,21 +18,21 @@ class Logger {
18
18
  return 'Unknown';
19
19
  }
20
20
  log(message, ...args) {
21
- if (this.config?.debug) {
22
- const source = this.getCallerInfo();
23
- console.log(`[${source}] ${message}`, ...args);
24
- }
21
+ const source = this.getCallerInfo();
22
+ console.log(`[${source}] ${message}`, ...args);
25
23
  }
26
24
  warn(message, ...args) {
27
- if (this.config?.debug) {
28
- const source = this.getCallerInfo();
29
- console.warn(`[${source}] ${message}`, ...args);
30
- }
25
+ const source = this.getCallerInfo();
26
+ console.warn(`[${source}] ${message}`, ...args);
31
27
  }
32
28
  error(message, ...args) {
29
+ const source = this.getCallerInfo();
30
+ console.error(`[${source}] ${message}`, ...args);
31
+ }
32
+ debug(message, ...args) {
33
33
  if (this.config?.debug) {
34
34
  const source = this.getCallerInfo();
35
- console.error(`[${source}] ${message}`, ...args);
35
+ console.log(`[${source}] ${message}`, ...args);
36
36
  }
37
37
  }
38
38
  }
@@ -39,7 +39,7 @@ export class OpenApiGenerator {
39
39
  let appRouterApiDir = "";
40
40
  if (fs.existsSync(path.join(path.dirname(apiDir), "app", "api"))) {
41
41
  appRouterApiDir = path.join(path.dirname(apiDir), "app", "api");
42
- logger.log(`Found app router API directory at ${appRouterApiDir}`);
42
+ logger.debug(`Found app router API directory at ${appRouterApiDir}`);
43
43
  }
44
44
  // Scan pages router routes
45
45
  this.routeProcessor.scanApiRoutes(apiDir);
@@ -137,7 +137,7 @@ export class RouteProcessor {
137
137
  const pathParams = extractPathParameters(routePath);
138
138
  // If we have path parameters but no pathParamsType defined, we should log a warning
139
139
  if (pathParams.length > 0 && !dataTypes.pathParamsType) {
140
- logger.warn(`Route ${routePath} contains path parameters ${pathParams.join(", ")} but no @pathParams type is defined.`);
140
+ logger.debug(`Route ${routePath} contains path parameters ${pathParams.join(", ")} but no @pathParams type is defined.`);
141
141
  }
142
142
  this.addRouteToPaths(declaration.id.name, filePath, dataTypes);
143
143
  }
@@ -154,7 +154,7 @@ export class RouteProcessor {
154
154
  const routePath = this.getRoutePath(filePath);
155
155
  const pathParams = extractPathParameters(routePath);
156
156
  if (pathParams.length > 0 && !dataTypes.pathParamsType) {
157
- logger.warn(`Route ${routePath} contains path parameters ${pathParams.join(", ")} but no @pathParams type is defined.`);
157
+ logger.debug(`Route ${routePath} contains path parameters ${pathParams.join(", ")} but no @pathParams type is defined.`);
158
158
  }
159
159
  this.addRouteToPaths(decl.id.name, filePath, dataTypes);
160
160
  }
@@ -167,7 +167,7 @@ export class RouteProcessor {
167
167
  this.processFileTracker[filePath] = true;
168
168
  }
169
169
  scanApiRoutes(dir) {
170
- logger.log(`Scanning API routes in: ${dir}`);
170
+ logger.debug(`Scanning API routes in: ${dir}`);
171
171
  let files = this.directoryCache[dir];
172
172
  if (!files) {
173
173
  files = fs.readdirSync(dir);
@@ -16,6 +16,7 @@ export class SchemaProcessor {
16
16
  processingTypes = new Set();
17
17
  zodSchemaConverter;
18
18
  schemaType;
19
+ isResolvingPickOmitBase = false;
19
20
  constructor(schemaDir, schemaType = "typescript") {
20
21
  this.schemaDir = path.resolve(schemaDir);
21
22
  this.schemaType = schemaType;
@@ -43,20 +44,20 @@ export class SchemaProcessor {
43
44
  this.contentType = contentType;
44
45
  // Check if we should use Zod schemas
45
46
  if (this.schemaType === "zod") {
46
- logger.log(`Looking for Zod schema: ${schemaName}`);
47
+ logger.debug(`Looking for Zod schema: ${schemaName}`);
47
48
  // Check type mapping first
48
49
  const mappedSchemaName = this.zodSchemaConverter.typeToSchemaMapping[schemaName];
49
50
  if (mappedSchemaName) {
50
- logger.log(`Type '${schemaName}' is mapped to Zod schema '${mappedSchemaName}'`);
51
+ logger.debug(`Type '${schemaName}' is mapped to Zod schema '${mappedSchemaName}'`);
51
52
  }
52
53
  // Try to convert Zod schema
53
54
  const zodSchema = this.zodSchemaConverter.convertZodSchemaToOpenApi(schemaName);
54
55
  if (zodSchema) {
55
- logger.log(`Found and processed Zod schema: ${schemaName}`);
56
+ logger.debug(`Found and processed Zod schema: ${schemaName}`);
56
57
  this.openapiDefinitions[schemaName] = zodSchema;
57
58
  return zodSchema;
58
59
  }
59
- logger.log(`No Zod schema found for ${schemaName}, trying TypeScript fallback`);
60
+ logger.debug(`No Zod schema found for ${schemaName}, trying TypeScript fallback`);
60
61
  }
61
62
  // Fall back to TypeScript types
62
63
  this.scanSchemaDir(this.schemaDir, schemaName);
@@ -166,10 +167,21 @@ export class SchemaProcessor {
166
167
  const enumValues = this.processEnum(typeNode);
167
168
  return enumValues;
168
169
  }
169
- if (t.isTSTypeLiteral(typeNode) || t.isTSInterfaceBody(typeNode)) {
170
+ if (t.isTSTypeLiteral(typeNode) || t.isTSInterfaceBody(typeNode) || t.isTSInterfaceDeclaration(typeNode)) {
170
171
  const properties = {};
171
- if ("members" in typeNode) {
172
- (typeNode.members || []).forEach((member) => {
172
+ // Handle interface extends clause
173
+ if (t.isTSInterfaceDeclaration(typeNode) && typeNode.extends && typeNode.extends.length > 0) {
174
+ typeNode.extends.forEach((extendedType) => {
175
+ const extendedSchema = this.resolveTSNodeType(extendedType);
176
+ if (extendedSchema.properties) {
177
+ Object.assign(properties, extendedSchema.properties);
178
+ }
179
+ });
180
+ }
181
+ // Get members from interface declaration body or direct members
182
+ const members = t.isTSInterfaceDeclaration(typeNode) ? typeNode.body.body : typeNode.members;
183
+ if (members) {
184
+ (members || []).forEach((member) => {
173
185
  if (t.isTSPropertySignature(member) && t.isIdentifier(member.key)) {
174
186
  const propName = member.key.name;
175
187
  const options = this.getPropertyOptions(member);
@@ -192,6 +204,9 @@ export class SchemaProcessor {
192
204
  if (t.isTSUnionType(typeNode)) {
193
205
  return this.resolveTSNodeType(typeNode);
194
206
  }
207
+ if (t.isTSTypeReference(typeNode)) {
208
+ return this.resolveTSNodeType(typeNode);
209
+ }
195
210
  return {};
196
211
  }
197
212
  finally {
@@ -250,6 +265,18 @@ export class SchemaProcessor {
250
265
  };
251
266
  }
252
267
  }
268
+ // Handle TSExpressionWithTypeArguments (used in interface extends)
269
+ if (t.isTSExpressionWithTypeArguments(node)) {
270
+ if (t.isIdentifier(node.expression)) {
271
+ // Convert to TSTypeReference-like structure for processing
272
+ const syntheticNode = {
273
+ type: 'TSTypeReference',
274
+ typeName: node.expression,
275
+ typeParameters: node.typeParameters
276
+ };
277
+ return this.resolveTSNodeType(syntheticNode);
278
+ }
279
+ }
253
280
  if (t.isTSTypeReference(node) && t.isIdentifier(node.typeName)) {
254
281
  const typeName = node.typeName.name;
255
282
  // Special handling for built-in types
@@ -284,6 +311,34 @@ export class SchemaProcessor {
284
311
  }
285
312
  }
286
313
  if (typeName === "Pick" || typeName === "Omit") {
314
+ if (node.typeParameters && node.typeParameters.params.length > 1) {
315
+ const baseTypeParam = node.typeParameters.params[0];
316
+ const keysParam = node.typeParameters.params[1];
317
+ // Resolve base type without adding it to schema definitions
318
+ this.isResolvingPickOmitBase = true;
319
+ const baseType = this.resolveTSNodeType(baseTypeParam);
320
+ this.isResolvingPickOmitBase = false;
321
+ if (baseType.properties) {
322
+ const properties = {};
323
+ const keyNames = this.extractKeysFromLiteralType(keysParam);
324
+ if (typeName === "Pick") {
325
+ keyNames.forEach(key => {
326
+ if (baseType.properties[key]) {
327
+ properties[key] = baseType.properties[key];
328
+ }
329
+ });
330
+ }
331
+ else { // Omit
332
+ Object.entries(baseType.properties).forEach(([key, value]) => {
333
+ if (!keyNames.includes(key)) {
334
+ properties[key] = value;
335
+ }
336
+ });
337
+ }
338
+ return { type: "object", properties };
339
+ }
340
+ }
341
+ // Fallback to just the base type if we can't process properly
287
342
  if (node.typeParameters && node.typeParameters.params.length > 0) {
288
343
  return this.resolveTSNodeType(node.typeParameters.params[0]);
289
344
  }
@@ -395,7 +450,7 @@ export class SchemaProcessor {
395
450
  if (t.isTSTypeReference(node) && t.isIdentifier(node.typeName)) {
396
451
  return { $ref: `#/components/schemas/${node.typeName.name}` };
397
452
  }
398
- logger.log("Unrecognized TypeScript type node:", node);
453
+ logger.debug("Unrecognized TypeScript type node:", node);
399
454
  return { type: "object" }; // By default we return an object
400
455
  }
401
456
  processSchemaFile(filePath, schemaName) {
@@ -413,7 +468,9 @@ export class SchemaProcessor {
413
468
  // Reset the set of processed types before each schema processing
414
469
  this.processingTypes.clear();
415
470
  const definition = this.resolveType(schemaName);
416
- this.openapiDefinitions[schemaName] = definition;
471
+ if (!this.isResolvingPickOmitBase) {
472
+ this.openapiDefinitions[schemaName] = definition;
473
+ }
417
474
  this.processSchemaTracker[`${filePath}-${schemaName}`] = true;
418
475
  return definition;
419
476
  }
@@ -447,6 +504,21 @@ export class SchemaProcessor {
447
504
  });
448
505
  return enumSchema;
449
506
  }
507
+ extractKeysFromLiteralType(node) {
508
+ if (t.isTSLiteralType(node) && t.isStringLiteral(node.literal)) {
509
+ return [node.literal.value];
510
+ }
511
+ if (t.isTSUnionType(node)) {
512
+ const keys = [];
513
+ node.types.forEach((type) => {
514
+ if (t.isTSLiteralType(type) && t.isStringLiteral(type.literal)) {
515
+ keys.push(type.literal.value);
516
+ }
517
+ });
518
+ return keys;
519
+ }
520
+ return [];
521
+ }
450
522
  getPropertyOptions(node) {
451
523
  const isOptional = !!node.optional; // check if property is optional
452
524
  let description = null;
@@ -24,11 +24,11 @@ export class ZodSchemaConverter {
24
24
  if (Object.keys(this.typeToSchemaMapping).length === 0) {
25
25
  this.preScanForTypeMappings();
26
26
  }
27
- logger.log(`Looking for Zod schema: ${schemaName}`);
27
+ logger.debug(`Looking for Zod schema: ${schemaName}`);
28
28
  // Check mapped types
29
29
  const mappedSchemaName = this.typeToSchemaMapping[schemaName];
30
30
  if (mappedSchemaName) {
31
- logger.log(`Type '${schemaName}' is mapped to schema '${mappedSchemaName}'`);
31
+ logger.debug(`Type '${schemaName}' is mapped to schema '${mappedSchemaName}'`);
32
32
  schemaName = mappedSchemaName;
33
33
  }
34
34
  // Check for circular references
@@ -47,7 +47,7 @@ export class ZodSchemaConverter {
47
47
  for (const routeFile of routeFiles) {
48
48
  this.processFileForZodSchema(routeFile, schemaName);
49
49
  if (this.zodSchemas[schemaName]) {
50
- logger.log(`Found Zod schema '${schemaName}' in route file: ${routeFile}`);
50
+ logger.debug(`Found Zod schema '${schemaName}' in route file: ${routeFile}`);
51
51
  return this.zodSchemas[schemaName];
52
52
  }
53
53
  }
@@ -55,10 +55,10 @@ export class ZodSchemaConverter {
55
55
  this.scanDirectoryForZodSchema(this.schemaDir, schemaName);
56
56
  // Return the schema if found, or null if not
57
57
  if (this.zodSchemas[schemaName]) {
58
- logger.log(`Found and processed Zod schema: ${schemaName}`);
58
+ logger.debug(`Found and processed Zod schema: ${schemaName}`);
59
59
  return this.zodSchemas[schemaName];
60
60
  }
61
- logger.log(`Could not find Zod schema: ${schemaName}`);
61
+ logger.debug(`Could not find Zod schema: ${schemaName}`);
62
62
  return null;
63
63
  }
64
64
  finally {
@@ -105,7 +105,7 @@ export class ZodSchemaConverter {
105
105
  }
106
106
  }
107
107
  catch (error) {
108
- logger.log(`Error scanning directory ${dir} for route files: ${error}`);
108
+ logger.error(`Error scanning directory ${dir} for route files: ${error}`);
109
109
  }
110
110
  }
111
111
  /**
@@ -126,7 +126,7 @@ export class ZodSchemaConverter {
126
126
  }
127
127
  }
128
128
  catch (error) {
129
- logger.log(`Error scanning directory ${dir}: ${error}`);
129
+ logger.error(`Error scanning directory ${dir}: ${error}`);
130
130
  }
131
131
  }
132
132
  /**
@@ -273,7 +273,7 @@ export class ZodSchemaConverter {
273
273
  ? prop.key.value
274
274
  : null;
275
275
  if (key && schema.properties) {
276
- logger.log(`Removing property: ${key}`);
276
+ logger.debug(`Removing property: ${key}`);
277
277
  delete schema.properties[key];
278
278
  if (schema.required) {
279
279
  schema.required = schema.required.filter((r) => r !== key);
@@ -358,20 +358,20 @@ export class ZodSchemaConverter {
358
358
  if (t.isCallExpression(path.node.init)) {
359
359
  const baseSchemaName = findBaseSchema(path.node.init);
360
360
  if (baseSchemaName && baseSchemaName !== "z") {
361
- logger.log(`Found chained call starting from: ${baseSchemaName}`);
361
+ logger.debug(`Found chained call starting from: ${baseSchemaName}`);
362
362
  // First make sure the underlying schema is processed
363
363
  if (!this.zodSchemas[baseSchemaName]) {
364
- logger.log(`Base schema ${baseSchemaName} not found, processing it first`);
364
+ logger.debug(`Base schema ${baseSchemaName} not found, processing it first`);
365
365
  this.processFileForZodSchema(filePath, baseSchemaName);
366
366
  }
367
367
  if (this.zodSchemas[baseSchemaName]) {
368
- logger.log("Base schema found, applying transformations");
368
+ logger.debug("Base schema found, applying transformations");
369
369
  // Copy base schema
370
370
  const baseSchema = JSON.parse(JSON.stringify(this.zodSchemas[baseSchemaName]));
371
371
  // Process the entire call chain
372
372
  const finalSchema = processChainedCall(path.node.init, baseSchema);
373
373
  this.zodSchemas[schemaName] = finalSchema;
374
- logger.log(`Created ${schemaName} with properties: ${Object.keys(finalSchema.properties || {})}`);
374
+ logger.debug(`Created ${schemaName} with properties: ${Object.keys(finalSchema.properties || {})}`);
375
375
  return;
376
376
  }
377
377
  }
@@ -413,7 +413,7 @@ export class ZodSchemaConverter {
413
413
  const referencedSchemaName = param.exprName.name;
414
414
  // Save mapping: TypeName -> SchemaName
415
415
  this.typeToSchemaMapping[typeName] = referencedSchemaName;
416
- logger.log(`Mapped type '${typeName}' to schema '${referencedSchemaName}'`);
416
+ logger.debug(`Mapped type '${typeName}' to schema '${referencedSchemaName}'`);
417
417
  // Process the referenced schema if not already processed
418
418
  if (!this.zodSchemas[referencedSchemaName]) {
419
419
  this.processFileForZodSchema(filePath, referencedSchemaName);
@@ -450,7 +450,7 @@ export class ZodSchemaConverter {
450
450
  });
451
451
  }
452
452
  catch (error) {
453
- console.error(`Error processing file ${filePath} for schema ${schemaName}: ${error}`);
453
+ logger.error(`Error processing file ${filePath} for schema ${schemaName}: ${error}`);
454
454
  }
455
455
  }
456
456
  /**
@@ -490,7 +490,7 @@ export class ZodSchemaConverter {
490
490
  });
491
491
  }
492
492
  catch (error) {
493
- console.error(`Error processing all schemas in file ${filePath}: ${error}`);
493
+ logger.error(`Error processing all schemas in file ${filePath}: ${error}`);
494
494
  }
495
495
  }
496
496
  /**
@@ -557,7 +557,7 @@ export class ZodSchemaConverter {
557
557
  node.arguments.length > 0) {
558
558
  return this.processZodLazy(node);
559
559
  }
560
- console.warn("Unknown Zod schema node:", node);
560
+ logger.debug("Unknown Zod schema node:", node);
561
561
  return { type: "object" };
562
562
  }
563
563
  /**
@@ -736,7 +736,7 @@ export class ZodSchemaConverter {
736
736
  propName = prop.key.value;
737
737
  }
738
738
  else {
739
- console.log(`Skipping property ${index} - unsupported key type`);
739
+ logger.debug(`Skipping property ${index} - unsupported key type`);
740
740
  return; // Skip if key is not identifier or string literal
741
741
  }
742
742
  if (t.isCallExpression(prop.value) &&
@@ -1223,7 +1223,7 @@ export class ZodSchemaConverter {
1223
1223
  schema.description = baseSchema.description;
1224
1224
  }
1225
1225
  else {
1226
- logger.warn("Could not resolve base schema for extend");
1226
+ logger.debug("Could not resolve base schema for extend");
1227
1227
  schema = extendedProps || { type: "object" };
1228
1228
  }
1229
1229
  }
@@ -1314,7 +1314,7 @@ export class ZodSchemaConverter {
1314
1314
  * Pre-scan all files to build type mappings
1315
1315
  */
1316
1316
  preScanForTypeMappings() {
1317
- logger.log("Pre-scanning for type mappings...");
1317
+ logger.debug("Pre-scanning for type mappings...");
1318
1318
  // Scan route files
1319
1319
  const routeFiles = this.findRouteFiles();
1320
1320
  for (const routeFile of routeFiles) {
@@ -1360,7 +1360,7 @@ export class ZodSchemaConverter {
1360
1360
  if (t.isTSTypeQuery(param) && t.isIdentifier(param.exprName)) {
1361
1361
  const referencedSchemaName = param.exprName.name;
1362
1362
  this.typeToSchemaMapping[typeName] = referencedSchemaName;
1363
- logger.log(`Pre-scan: Mapped type '${typeName}' to schema '${referencedSchemaName}'`);
1363
+ logger.debug(`Pre-scan: Mapped type '${typeName}' to schema '${referencedSchemaName}'`);
1364
1364
  }
1365
1365
  }
1366
1366
  }
@@ -1369,7 +1369,7 @@ export class ZodSchemaConverter {
1369
1369
  });
1370
1370
  }
1371
1371
  catch (error) {
1372
- logger.log(`Error scanning file ${filePath} for type mappings: ${error}`);
1372
+ logger.error(`Error scanning file ${filePath} for type mappings: ${error}`);
1373
1373
  }
1374
1374
  }
1375
1375
  /**
@@ -1413,7 +1413,7 @@ export class ZodSchemaConverter {
1413
1413
  // Check if is Zos schema
1414
1414
  if (this.isZodSchema(declaration.init) &&
1415
1415
  !this.zodSchemas[schemaName]) {
1416
- logger.log(`Pre-processing Zod schema: ${schemaName}`);
1416
+ logger.debug(`Pre-processing Zod schema: ${schemaName}`);
1417
1417
  this.processingSchemas.add(schemaName);
1418
1418
  const schema = this.processZodNode(declaration.init);
1419
1419
  if (schema) {
@@ -1433,7 +1433,7 @@ export class ZodSchemaConverter {
1433
1433
  if (this.isZodSchema(declaration.init) &&
1434
1434
  !this.zodSchemas[schemaName] &&
1435
1435
  !this.processingSchemas.has(schemaName)) {
1436
- logger.log(`Pre-processing Zod schema: ${schemaName}`);
1436
+ logger.debug(`Pre-processing Zod schema: ${schemaName}`);
1437
1437
  this.processingSchemas.add(schemaName);
1438
1438
  const schema = this.processZodNode(declaration.init);
1439
1439
  if (schema) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-openapi-gen",
3
- "version": "0.6.5",
3
+ "version": "0.6.7",
4
4
  "description": "Automatically generate OpenAPI 3.0 documentation from Next.js projects, with support for TypeScript types and Zod schemas.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",