jszy-swagger-doc-generator 1.1.1 → 1.4.1

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/index.js CHANGED
@@ -287,7 +287,7 @@ class SwaggerDocGenerator {
287
287
  return `export type ${typeName} = ${convertTypeToTs(schema, allSchemas)};\n`;
288
288
  }
289
289
  /**
290
- * Generates React hooks from the paths in Swagger doc
290
+ * Generates React hooks from the paths in Swagger doc organized by tag
291
291
  */
292
292
  generateReactHooks(swaggerDoc) {
293
293
  const hooksByTag = new Map();
@@ -304,10 +304,65 @@ class SwaggerDocGenerator {
304
304
  endpointsByTag[tag].push({ path, method, endpointInfo });
305
305
  });
306
306
  });
307
- // Generate hooks for each tag
307
+ // Generate hooks and types for each tag
308
308
  Object.entries(endpointsByTag).forEach(([tag, endpoints]) => {
309
- let tagContent = this.generateHeaderForTag(tag);
310
- // Generate all parameter interfaces for this tag first to avoid duplicates
309
+ // Generate TypeScript types for schemas used in this tag
310
+ let typesContent = `// ${toPascalCase(tag)} API Types\n\n`;
311
+ // First, find all directly used schemas in endpoints for this tag
312
+ const directlyUsedSchemas = new Set();
313
+ if (schemas) {
314
+ Object.entries(schemas).forEach(([typeName, schema]) => {
315
+ if (this.isSchemaUsedInEndpoints(typeName, endpoints, schemas)) {
316
+ directlyUsedSchemas.add(typeName);
317
+ }
318
+ });
319
+ }
320
+ // Then, find all referenced schemas (schemas that are referenced by the directly used ones)
321
+ const allNeededSchemas = this.findAllReferencedSchemas(directlyUsedSchemas, schemas);
322
+ // Generate types for all needed schemas
323
+ if (schemas) {
324
+ for (const typeName of allNeededSchemas) {
325
+ const schema = schemas[typeName];
326
+ if (schema) {
327
+ typesContent += this.generateSingleTypeDefinition(typeName, schema, schemas);
328
+ typesContent += '\n';
329
+ }
330
+ }
331
+ }
332
+ // Generate hooks content
333
+ let hooksContent = `// ${toPascalCase(tag)} API Hooks\n`;
334
+ hooksContent += `import { useQuery, useMutation, useQueryClient } from 'react-query';\n`;
335
+ hooksContent += `import axios from 'axios';\n`;
336
+ // Determine which types are actually used in this tag's endpoints and generate imports
337
+ const usedTypeNames = new Set();
338
+ // First, find all types that are directly used in endpoints for this tag
339
+ if (schemas) {
340
+ for (const [typeName, schema] of Object.entries(schemas)) {
341
+ if (this.isSchemaUsedInEndpoints(typeName, endpoints, schemas)) {
342
+ usedTypeNames.add(typeName);
343
+ }
344
+ }
345
+ }
346
+ // Then, add all transitive dependencies of the directly used types
347
+ const finalTypeNames = new Set();
348
+ for (const typeName of usedTypeNames) {
349
+ finalTypeNames.add(typeName);
350
+ // Find all types referenced by this type
351
+ const referencedTypes = this.findSchemaReferences(schemas[typeName], schemas);
352
+ for (const refName of referencedTypes) {
353
+ if (schemas[refName]) {
354
+ finalTypeNames.add(refName);
355
+ }
356
+ }
357
+ }
358
+ // Add import statement only if there are types to import
359
+ if (finalTypeNames.size > 0) {
360
+ hooksContent += `import type { ${Array.from(finalTypeNames).join(', ')} } from './${toCamelCase(tag)}.types';\n\n`;
361
+ }
362
+ else {
363
+ hooksContent += `\n`;
364
+ }
365
+ // Generate parameter interfaces for this tag
311
366
  const allParamInterfaces = [];
312
367
  endpoints.forEach(({ path, method, endpointInfo }) => {
313
368
  const paramInterface = this.generateParamInterface(path, method, endpointInfo, schemas);
@@ -317,22 +372,176 @@ class SwaggerDocGenerator {
317
372
  });
318
373
  // Add all unique parameter interfaces
319
374
  allParamInterfaces.forEach(interfaceCode => {
320
- tagContent += interfaceCode + '\n';
375
+ hooksContent += interfaceCode + '\n';
321
376
  });
322
- // Generate individual hooks
377
+ // Generate individual hooks using react-query and axios
323
378
  endpoints.forEach(({ path, method, endpointInfo }) => {
324
- const hookContent = this.generateSingleHookWithUniqueName(path, method, endpointInfo, schemas);
325
- tagContent += hookContent + '\n';
379
+ const hookContent = this.generateReactQueryHook(path, method, endpointInfo, schemas);
380
+ hooksContent += hookContent + '\n';
326
381
  });
327
- hooksByTag.set(tag, tagContent);
382
+ hooksByTag.set(tag, { hooks: hooksContent, types: typesContent });
328
383
  });
329
384
  return hooksByTag;
330
385
  }
331
386
  /**
332
- * Generates header content for a specific tag
387
+ * Checks if a schema is used in any of the endpoints
388
+ */
389
+ isSchemaUsedInEndpoints(schemaName, endpoints, allSchemas) {
390
+ for (const { endpointInfo } of endpoints) {
391
+ // Check if schema is used as response
392
+ if (endpointInfo.responses) {
393
+ for (const [, responseInfo] of Object.entries(endpointInfo.responses)) {
394
+ if (responseInfo.content) {
395
+ for (const [, contentInfo] of Object.entries(responseInfo.content)) {
396
+ if (contentInfo.schema) {
397
+ if (this.schemaContainsRef(contentInfo.schema, schemaName, allSchemas)) {
398
+ return true;
399
+ }
400
+ }
401
+ }
402
+ }
403
+ }
404
+ }
405
+ // Check if schema is used in parameters
406
+ if (endpointInfo.parameters) {
407
+ for (const param of endpointInfo.parameters) {
408
+ if (param.schema && this.schemaContainsRef(param.schema, schemaName, allSchemas)) {
409
+ return true;
410
+ }
411
+ }
412
+ }
413
+ // Check if schema is used in request body
414
+ if (endpointInfo.requestBody && endpointInfo.requestBody.content) {
415
+ for (const [, contentInfo] of Object.entries(endpointInfo.requestBody.content)) {
416
+ if (contentInfo.schema && this.schemaContainsRef(contentInfo.schema, schemaName, allSchemas)) {
417
+ return true;
418
+ }
419
+ }
420
+ }
421
+ }
422
+ return false;
423
+ }
424
+ /**
425
+ * Checks if a schema contains a reference to another schema
426
+ */
427
+ schemaContainsRef(schema, targetSchemaName, allSchemas) {
428
+ if (!schema)
429
+ return false;
430
+ // Check if this schema directly references the target
431
+ if (schema.$ref) {
432
+ const refTypeName = schema.$ref.split('/').pop();
433
+ if (refTypeName === targetSchemaName) {
434
+ return true;
435
+ }
436
+ }
437
+ // Recursively check nested properties
438
+ if (schema.properties) {
439
+ for (const [, propSchema] of Object.entries(schema.properties)) {
440
+ if (this.schemaContainsRef(propSchema, targetSchemaName, allSchemas)) {
441
+ return true;
442
+ }
443
+ }
444
+ }
445
+ // Check if it's an array schema
446
+ if (schema.items) {
447
+ if (this.schemaContainsRef(schema.items, targetSchemaName, allSchemas)) {
448
+ return true;
449
+ }
450
+ }
451
+ // Check allOf, oneOf, anyOf
452
+ if (schema.allOf) {
453
+ for (const item of schema.allOf) {
454
+ if (this.schemaContainsRef(item, targetSchemaName, allSchemas)) {
455
+ return true;
456
+ }
457
+ }
458
+ }
459
+ if (schema.oneOf) {
460
+ for (const item of schema.oneOf) {
461
+ if (this.schemaContainsRef(item, targetSchemaName, allSchemas)) {
462
+ return true;
463
+ }
464
+ }
465
+ }
466
+ if (schema.anyOf) {
467
+ for (const item of schema.anyOf) {
468
+ if (this.schemaContainsRef(item, targetSchemaName, allSchemas)) {
469
+ return true;
470
+ }
471
+ }
472
+ }
473
+ return false;
474
+ }
475
+ /**
476
+ * Find all referenced schemas from a set of directly used schemas
477
+ */
478
+ findAllReferencedSchemas(initialSchemas, allSchemas) {
479
+ const result = new Set([...initialSchemas]); // Start with initial schemas
480
+ let changed = true;
481
+ while (changed) {
482
+ changed = false;
483
+ for (const typeName of [...result]) { // Use spread to create a new array to avoid concurrent modification
484
+ const schema = allSchemas[typeName];
485
+ if (schema) {
486
+ // Check for references in the schema
487
+ const referencedSchemas = this.findSchemaReferences(schema, allSchemas);
488
+ for (const refName of referencedSchemas) {
489
+ if (!result.has(refName) && allSchemas[refName]) {
490
+ result.add(refName);
491
+ changed = true;
492
+ }
493
+ }
494
+ }
495
+ }
496
+ }
497
+ return result;
498
+ }
499
+ /**
500
+ * Find schema references in a given schema
333
501
  */
334
- generateHeaderForTag(tag) {
335
- return `// ${toPascalCase(tag)} API Hooks\n\n`;
502
+ findSchemaReferences(schema, allSchemas) {
503
+ const references = new Set();
504
+ if (!schema)
505
+ return references;
506
+ // Check direct $ref
507
+ if (schema.$ref) {
508
+ const refTypeName = schema.$ref.split('/').pop();
509
+ if (refTypeName && allSchemas[refTypeName]) {
510
+ references.add(refTypeName);
511
+ }
512
+ }
513
+ // Check properties
514
+ if (schema.properties) {
515
+ Object.values(schema.properties).forEach((propSchema) => {
516
+ const nestedRefs = this.findSchemaReferences(propSchema, allSchemas);
517
+ nestedRefs.forEach(ref => references.add(ref));
518
+ });
519
+ }
520
+ // Check array items
521
+ if (schema.items) {
522
+ const itemRefs = this.findSchemaReferences(schema.items, allSchemas);
523
+ itemRefs.forEach(ref => references.add(ref));
524
+ }
525
+ // Check allOf, oneOf, anyOf
526
+ if (schema.allOf) {
527
+ schema.allOf.forEach((item) => {
528
+ const itemRefs = this.findSchemaReferences(item, allSchemas);
529
+ itemRefs.forEach(ref => references.add(ref));
530
+ });
531
+ }
532
+ if (schema.oneOf) {
533
+ schema.oneOf.forEach((item) => {
534
+ const itemRefs = this.findSchemaReferences(item, allSchemas);
535
+ itemRefs.forEach(ref => references.add(ref));
536
+ });
537
+ }
538
+ if (schema.anyOf) {
539
+ schema.anyOf.forEach((item) => {
540
+ const itemRefs = this.findSchemaReferences(item, allSchemas);
541
+ itemRefs.forEach(ref => references.add(ref));
542
+ });
543
+ }
544
+ return references;
336
545
  }
337
546
  /**
338
547
  * Generates a parameter interface for an API endpoint
@@ -348,7 +557,22 @@ class SwaggerDocGenerator {
348
557
  }
349
558
  // Create a unique interface name based on the operation ID
350
559
  const operationId = endpointInfo.operationId || this.generateOperationId(path, method);
351
- const interfaceName = `${toPascalCase(operationId)}Params`;
560
+ // Extract action name from operationId to create cleaner parameter interface names
561
+ // e.g. configController_updateConfig -> UpdateConfigParams instead of ConfigController_updateConfigParams
562
+ let interfaceName;
563
+ if (operationId.includes('_')) {
564
+ const parts = operationId.split('_');
565
+ if (parts.length >= 2) {
566
+ // Use just the action part in the interface name
567
+ interfaceName = `${toPascalCase(parts[parts.length - 1])}Params`;
568
+ }
569
+ else {
570
+ interfaceName = `${toPascalCase(operationId)}Params`;
571
+ }
572
+ }
573
+ else {
574
+ interfaceName = `${toPascalCase(operationId)}Params`;
575
+ }
352
576
  let paramsInterface = `export interface ${interfaceName} {\n`;
353
577
  // Add path parameters
354
578
  pathParams.forEach((param) => {
@@ -366,24 +590,29 @@ class SwaggerDocGenerator {
366
590
  return paramsInterface;
367
591
  }
368
592
  /**
369
- * Generates a single React hook for an API endpoint with unique parameter interface
593
+ * Generates a React Query hook using axios
370
594
  */
371
- generateSingleHookWithUniqueName(path, method, endpointInfo, schemas) {
595
+ generateReactQueryHook(path, method, endpointInfo, schemas) {
372
596
  const operationId = endpointInfo.operationId || this.generateOperationId(path, method);
373
- const hookName = `use${toPascalCase(operationId)}`;
597
+ // Extract action name from operationId to create cleaner hook names
598
+ // e.g. configController_updateConfig -> useUpdateConfig instead of useConfigController_updateConfig
599
+ let hookName = `use${toPascalCase(operationId)}`;
600
+ // Check if operationId follows pattern controller_action and simplify to action
601
+ if (operationId.includes('_')) {
602
+ const parts = operationId.split('_');
603
+ if (parts.length >= 2) {
604
+ // Use just the action part as the hook name
605
+ hookName = `use${toPascalCase(parts[parts.length - 1])}`;
606
+ }
607
+ }
608
+ else {
609
+ // For operationIds without underscores, keep the original naming
610
+ hookName = `use${toPascalCase(operationId)}`;
611
+ }
612
+ const hookType = method.toLowerCase() === 'get' ? 'useQuery' : 'useMutation';
374
613
  // Use unique parameter interface name
375
614
  const pathParams = endpointInfo.parameters?.filter((p) => p.in === 'path') || [];
376
615
  const queryParams = endpointInfo.parameters?.filter((p) => p.in === 'query') || [];
377
- let paramsDeclaration = '';
378
- let paramsUsage = '{}';
379
- const hasParams = pathParams.length > 0 || queryParams.length > 0;
380
- if (hasParams) {
381
- const paramInterfaceName = `${toPascalCase(operationId)}Params`;
382
- paramsDeclaration = `params: ${paramInterfaceName}`;
383
- paramsUsage = 'params';
384
- }
385
- // Format the path for use in the code (handle path parameters)
386
- const pathWithParams = path.replace(/{(\w+)}/g, (_, param) => `\${params.${toCamelCase(param)}}`);
387
616
  // Determine response type
388
617
  let responseType = 'any';
389
618
  if (endpointInfo.responses && endpointInfo.responses['200']) {
@@ -393,60 +622,145 @@ class SwaggerDocGenerator {
393
622
  }
394
623
  }
395
624
  // Generate request body parameter if needed
396
- let requestBodyParam = '';
625
+ let requestBodyType = 'any';
626
+ let hasBody = false;
397
627
  if (method.toLowerCase() !== 'get' && method.toLowerCase() !== 'delete' && endpointInfo.requestBody) {
398
628
  const bodySchema = endpointInfo.requestBody.content?.['application/json']?.schema;
399
629
  if (bodySchema) {
400
- const bodyType = convertTypeToTs(bodySchema, schemas);
401
- requestBodyParam = `, body: ${bodyType}`;
630
+ requestBodyType = convertTypeToTs(bodySchema, schemas);
631
+ hasBody = true;
402
632
  }
403
633
  }
404
- // Create the hook function
405
- let hookCode = `export const ${hookName} = () => {\n`;
406
- hookCode += ` const apiCall = async (${paramsDeclaration}${requestBodyParam ? requestBodyParam : ''}) => {\n`;
407
- hookCode += ` const path = \`\${process.env.REACT_APP_API_BASE_URL || ''}${pathWithParams}\`;\n`;
408
- // Add query parameters
409
- if (endpointInfo.parameters && endpointInfo.parameters.some((p) => p.in === 'query')) {
410
- hookCode += ` const queryParams = new URLSearchParams();\n`;
411
- endpointInfo.parameters.forEach((param) => {
412
- if (param.in === 'query') {
413
- hookCode += ` if (params.${toCamelCase(param.name)}) queryParams.append('${param.name}', params.${toCamelCase(param.name)}.toString());\n`;
414
- }
415
- });
416
- hookCode += ` const queryString = queryParams.toString();\n`;
417
- hookCode += ` const url = \`\${path}\${queryString ? '?' + queryString : ''}\`;\n`;
634
+ // Format the path for use in the code (handle path parameters) - without base URL
635
+ const formattedPath = path.replace(/{(\w+)}/g, (_, param) => `\${params.${toCamelCase(param)}}`);
636
+ const axiosPath = `\`${formattedPath}\``;
637
+ // Generate the hook code
638
+ let hookCode = '';
639
+ if (method.toLowerCase() === 'get') {
640
+ // For GET requests, use useQuery
641
+ const hasParams = pathParams.length > 0 || queryParams.length > 0;
642
+ if (hasParams) {
643
+ // Generate simpler parameter interface name based on hook name instead of operationId
644
+ const paramInterfaceName = `${hookName.replace('use', '')}Params`;
645
+ hookCode += `export const ${hookName} = (params: ${paramInterfaceName}) => {\n`;
646
+ hookCode += ` return useQuery({\n`;
647
+ hookCode += ` queryKey: ['${operationId}', params],\n`;
648
+ hookCode += ` queryFn: async () => {\n`;
649
+ hookCode += ` const response = await axios.get<${responseType}>(${axiosPath}, { params });\n`;
650
+ hookCode += ` return response.data;\n`;
651
+ hookCode += ` },\n`;
652
+ hookCode += ` });\n`;
653
+ hookCode += `};\n`;
654
+ }
655
+ else {
656
+ hookCode += `export const ${hookName} = () => {\n`;
657
+ hookCode += ` return useQuery({\n`;
658
+ hookCode += ` queryKey: ['${operationId}'],\n`;
659
+ hookCode += ` queryFn: async () => {\n`;
660
+ hookCode += ` const response = await axios.get<${responseType}>(${axiosPath});\n`;
661
+ hookCode += ` return response.data;\n`;
662
+ hookCode += ` },\n`;
663
+ hookCode += ` });\n`;
664
+ hookCode += `};\n`;
665
+ }
418
666
  }
419
667
  else {
420
- hookCode += ` const url = path;\n`;
421
- }
422
- // Add fetch options
423
- hookCode += ` const options: RequestInit = {\n`;
424
- hookCode += ` method: '${method.toUpperCase()}',\n`;
425
- if (requestBodyParam) {
426
- hookCode += ` headers: {\n 'Content-Type': 'application/json',\n },\n`;
427
- hookCode += ` body: JSON.stringify(body),\n`;
428
- }
429
- hookCode += ` };\n\n`;
430
- hookCode += ` const result = await fetch(url, options);\n`;
431
- hookCode += ` return result.json() as Promise<${responseType}>;\n`;
432
- hookCode += ` };\n\n`;
433
- hookCode += ` return { ${toCamelCase(operationId)}: apiCall };\n`;
434
- hookCode += `};\n`;
668
+ // For non-GET requests, use useMutation
669
+ const hasPathParams = pathParams.length > 0;
670
+ if (hasPathParams) {
671
+ // Generate simpler parameter interface name based on hook name instead of operationId
672
+ const paramInterfaceName = `${hookName.replace('use', '')}Params`;
673
+ hookCode += `export const ${hookName} = () => {\n`;
674
+ hookCode += ` const queryClient = useQueryClient();\n\n`;
675
+ hookCode += ` return useMutation({\n`;
676
+ hookCode += ` mutationFn: async ({ params, data }: { params: ${paramInterfaceName}; data: ${requestBodyType} }) => {\n`;
677
+ hookCode += ` const response = await axios.${method.toLowerCase()}<${responseType}>(${axiosPath}, data);\n`;
678
+ hookCode += ` return response.data;\n`;
679
+ hookCode += ` },\n`;
680
+ hookCode += ` onSuccess: () => {\n`;
681
+ hookCode += ` // Invalidate and refetch related queries\n`;
682
+ hookCode += ` queryClient.invalidateQueries({ queryKey: ['${operationId}'] });\n`;
683
+ hookCode += ` },\n`;
684
+ hookCode += ` });\n`;
685
+ hookCode += `};\n`;
686
+ }
687
+ else {
688
+ hookCode += `export const ${hookName} = () => {\n`;
689
+ hookCode += ` const queryClient = useQueryClient();\n\n`;
690
+ hookCode += ` return useMutation({\n`;
691
+ hookCode += ` mutationFn: async (data: ${requestBodyType}) => {\n`;
692
+ hookCode += ` const response = await axios.${method.toLowerCase()}<${responseType}>(${axiosPath}, data);\n`;
693
+ hookCode += ` return response.data;\n`;
694
+ hookCode += ` },\n`;
695
+ hookCode += ` onSuccess: () => {\n`;
696
+ hookCode += ` // Invalidate and refetch related queries\n`;
697
+ hookCode += ` queryClient.invalidateQueries({ queryKey: ['${operationId}'] });\n`;
698
+ hookCode += ` },\n`;
699
+ hookCode += ` });\n`;
700
+ hookCode += `};\n`;
701
+ }
702
+ }
435
703
  return hookCode;
436
704
  }
437
- /**
438
- * Generates a single React hook for an API endpoint
439
- */
440
- generateSingleHook(path, method, endpointInfo, schemas) {
441
- // This method is kept for backward compatibility
442
- return this.generateSingleHookWithUniqueName(path, method, endpointInfo, schemas);
443
- }
444
705
  /**
445
706
  * Generate operation ID from path and method if not provided
446
707
  */
447
708
  generateOperationId(path, method) {
448
709
  return `${method.toLowerCase()}_${path.replace(/[\/{}]/g, '_')}`;
449
710
  }
711
+ /**
712
+ * Formats code using Prettier - sync version with child process
713
+ */
714
+ formatCode(code, filepath) {
715
+ // Skip formatting in test environment to avoid ESM issues
716
+ if (process.env.NODE_ENV === 'test' || typeof jest !== 'undefined') {
717
+ return code;
718
+ }
719
+ try {
720
+ // Use execSync to run prettier as a separate process to avoid ESM issues
721
+ const { execSync } = require('child_process');
722
+ const { writeFileSync, readFileSync, unlinkSync } = require('fs');
723
+ const { join, extname } = require('path');
724
+ const { tmpdir } = require('os');
725
+ // Determine the file extension to use for the temp file
726
+ const fileExtension = extname(filepath) || '.txt';
727
+ const tempPath = join(tmpdir(), `prettier-tmp-${Date.now()}-${Math.random().toString(36).substr(2, 9)}${fileExtension}`);
728
+ writeFileSync(tempPath, code, 'utf8');
729
+ // Format the file using prettier CLI
730
+ execSync(`npx prettier --write "${tempPath}" --single-quote --trailing-comma es5 --tab-width 2 --semi --print-width 80`, {
731
+ stdio: 'pipe'
732
+ });
733
+ // Read the formatted content back
734
+ const formattedCode = readFileSync(tempPath, 'utf8');
735
+ // Clean up the temporary file
736
+ unlinkSync(tempPath);
737
+ return formattedCode;
738
+ }
739
+ catch (error) {
740
+ console.warn(`Failed to format ${filepath} with Prettier:`, error);
741
+ return code; // Return unformatted code if formatting fails
742
+ }
743
+ }
744
+ /**
745
+ * Gets the parser based on file extension
746
+ */
747
+ getParserForFile(filepath) {
748
+ const ext = path.extname(filepath);
749
+ switch (ext) {
750
+ case '.ts':
751
+ case '.tsx':
752
+ return 'typescript';
753
+ case '.js':
754
+ case '.jsx':
755
+ return 'babel';
756
+ case '.json':
757
+ return 'json';
758
+ case '.md':
759
+ return 'markdown';
760
+ default:
761
+ return 'typescript';
762
+ }
763
+ }
450
764
  /**
451
765
  * Saves the generated documentation to a file
452
766
  */
@@ -455,7 +769,8 @@ class SwaggerDocGenerator {
455
769
  if (!fs.existsSync(dir)) {
456
770
  fs.mkdirSync(dir, { recursive: true });
457
771
  }
458
- fs.writeFileSync(outputPath, documentation, 'utf8');
772
+ const formattedDocumentation = this.formatCode(documentation, outputPath);
773
+ fs.writeFileSync(outputPath, formattedDocumentation, 'utf8');
459
774
  }
460
775
  /**
461
776
  * Saves the generated TypeScript types to a file
@@ -465,7 +780,8 @@ class SwaggerDocGenerator {
465
780
  if (!fs.existsSync(dir)) {
466
781
  fs.mkdirSync(dir, { recursive: true });
467
782
  }
468
- fs.writeFileSync(outputPath, types, 'utf8');
783
+ const formattedTypes = this.formatCode(types, outputPath);
784
+ fs.writeFileSync(outputPath, formattedTypes, 'utf8');
469
785
  }
470
786
  /**
471
787
  * Saves the generated React hooks to files organized by tag
@@ -475,13 +791,21 @@ class SwaggerDocGenerator {
475
791
  if (!fs.existsSync(dir)) {
476
792
  fs.mkdirSync(dir, { recursive: true });
477
793
  }
478
- for (const [tag, content] of hooksByTag) {
794
+ for (const [tag, { hooks, types }] of hooksByTag) {
479
795
  const tagDir = path.join(outputDir, toCamelCase(tag));
480
796
  if (!fs.existsSync(tagDir)) {
481
797
  fs.mkdirSync(tagDir, { recursive: true });
482
798
  }
483
- const fileName = path.join(tagDir, `${toCamelCase(tag)}.hooks.ts`);
484
- fs.writeFileSync(fileName, content, 'utf8');
799
+ // Save hooks to hooks file
800
+ const hooksFileName = path.join(tagDir, `${toCamelCase(tag)}.hooks.ts`);
801
+ const formattedHooks = this.formatCode(hooks, hooksFileName);
802
+ fs.writeFileSync(hooksFileName, formattedHooks, 'utf8');
803
+ // Save types to types file
804
+ if (types.trim()) { // Only save if there are types
805
+ const typesFileName = path.join(tagDir, `${toCamelCase(tag)}.types.ts`);
806
+ const formattedTypes = this.formatCode(types, typesFileName);
807
+ fs.writeFileSync(typesFileName, formattedTypes, 'utf8');
808
+ }
485
809
  }
486
810
  }
487
811
  }