jszy-swagger-doc-generator 1.1.1 → 1.4.0

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/src/cli.ts CHANGED
@@ -4,6 +4,7 @@ import { SwaggerDocGenerator, SwaggerDoc } from './index';
4
4
  import yargs from 'yargs';
5
5
  import { hideBin } from 'yargs/helpers';
6
6
  import * as fs from 'fs';
7
+ import * as path from 'path';
7
8
 
8
9
  const argv = yargs(hideBin(process.argv))
9
10
  .usage('Usage: $0 [options]')
@@ -57,11 +58,11 @@ const argv = yargs(hideBin(process.argv))
57
58
 
58
59
  async function run(): Promise<void> {
59
60
  try {
60
- // Create the generated directory if it doesn't exist
61
+ // Clean the entire generated directory before doing anything
61
62
  const generatedDir = './generated';
62
- if (!fs.existsSync(generatedDir)) {
63
- fs.mkdirSync(generatedDir, { recursive: true });
64
- console.log(`Created directory: ${generatedDir}`);
63
+ if (fs.existsSync(generatedDir)) {
64
+ fs.rmSync(generatedDir, { recursive: true, force: true });
65
+ console.log(`Cleared generated directory: ${generatedDir}`);
65
66
  }
66
67
 
67
68
  const generator = new SwaggerDocGenerator();
@@ -85,6 +86,11 @@ async function run(): Promise<void> {
85
86
 
86
87
  // Check if we need to generate types
87
88
  if (argv.generateTypes) {
89
+ // Create the generated directory if it doesn't exist
90
+ if (!fs.existsSync(generatedDir)) {
91
+ fs.mkdirSync(generatedDir, { recursive: true });
92
+ }
93
+
88
94
  console.log('Generating TypeScript type definitions...');
89
95
  const types = generator.generateTypeDefinitions(swaggerDoc);
90
96
  const typesOutputPath = argv.typesOutput.endsWith('.ts') ? argv.typesOutput :
@@ -95,14 +101,24 @@ async function run(): Promise<void> {
95
101
 
96
102
  // Check if we need to generate hooks
97
103
  if (argv.generateHooks) {
104
+ // Create the generated directory if it doesn't exist
105
+ if (!fs.existsSync(generatedDir)) {
106
+ fs.mkdirSync(generatedDir, { recursive: true });
107
+ }
108
+
98
109
  console.log('Generating React hooks...');
99
110
  const hooksByTag = generator.generateReactHooks(swaggerDoc);
100
111
  generator.saveHooksByTag(hooksByTag, argv.hooksOutput);
101
- console.log(`React hooks generated successfully in: ${argv.hooksOutput}/`);
112
+ console.log(`React hooks and types generated successfully in: ${argv.hooksOutput}/`);
102
113
  }
103
114
 
104
115
  // Generate documentation if not generating types or hooks (for backward compatibility)
105
116
  if (!argv.generateTypes && !argv.generateHooks) {
117
+ // Create the generated directory if it doesn't exist
118
+ if (!fs.existsSync(generatedDir)) {
119
+ fs.mkdirSync(generatedDir, { recursive: true });
120
+ }
121
+
106
122
  console.log('Generating documentation...');
107
123
  const documentation = generator.generateDocumentation(swaggerDoc);
108
124
  generator.saveDocumentationToFile(documentation, argv.output);
package/src/index.ts CHANGED
@@ -345,10 +345,10 @@ export class SwaggerDocGenerator {
345
345
  }
346
346
 
347
347
  /**
348
- * Generates React hooks from the paths in Swagger doc
348
+ * Generates React hooks from the paths in Swagger doc organized by tag
349
349
  */
350
- generateReactHooks(swaggerDoc: SwaggerDoc): Map<string, string> {
351
- const hooksByTag = new Map<string, string>();
350
+ generateReactHooks(swaggerDoc: SwaggerDoc): Map<string, { hooks: string, types: string }> {
351
+ const hooksByTag = new Map<string, { hooks: string, types: string }>();
352
352
  const schemas = swaggerDoc.components?.schemas || {};
353
353
 
354
354
  // Group endpoints by tag
@@ -366,11 +366,73 @@ export class SwaggerDocGenerator {
366
366
  });
367
367
  });
368
368
 
369
- // Generate hooks for each tag
369
+ // Generate hooks and types for each tag
370
370
  Object.entries(endpointsByTag).forEach(([tag, endpoints]) => {
371
- let tagContent = this.generateHeaderForTag(tag);
371
+ // Generate TypeScript types for schemas used in this tag
372
+ let typesContent = `// ${toPascalCase(tag)} API Types\n\n`;
373
+
374
+ // First, find all directly used schemas in endpoints for this tag
375
+ const directlyUsedSchemas = new Set<string>();
376
+ if (schemas) {
377
+ Object.entries(schemas).forEach(([typeName, schema]) => {
378
+ if (this.isSchemaUsedInEndpoints(typeName, endpoints, schemas)) {
379
+ directlyUsedSchemas.add(typeName);
380
+ }
381
+ });
382
+ }
383
+
384
+ // Then, find all referenced schemas (schemas that are referenced by the directly used ones)
385
+ const allNeededSchemas = this.findAllReferencedSchemas(directlyUsedSchemas, schemas);
386
+
387
+ // Generate types for all needed schemas
388
+ if (schemas) {
389
+ for (const typeName of allNeededSchemas) {
390
+ const schema = schemas[typeName];
391
+ if (schema) {
392
+ typesContent += this.generateSingleTypeDefinition(typeName, schema, schemas);
393
+ typesContent += '\n';
394
+ }
395
+ }
396
+ }
397
+
398
+ // Generate hooks content
399
+ let hooksContent = `// ${toPascalCase(tag)} API Hooks\n`;
400
+ hooksContent += `import { useQuery, useMutation, useQueryClient } from 'react-query';\n`;
401
+ hooksContent += `import axios from 'axios';\n`;
402
+
403
+ // Determine which types are actually used in this tag's endpoints and generate imports
404
+ const usedTypeNames = new Set<string>();
405
+
406
+ // First, find all types that are directly used in endpoints for this tag
407
+ if (schemas) {
408
+ for (const [typeName, schema] of Object.entries(schemas)) {
409
+ if (this.isSchemaUsedInEndpoints(typeName, endpoints, schemas)) {
410
+ usedTypeNames.add(typeName);
411
+ }
412
+ }
413
+ }
414
+
415
+ // Then, add all transitive dependencies of the directly used types
416
+ const finalTypeNames = new Set<string>();
417
+ for (const typeName of usedTypeNames) {
418
+ finalTypeNames.add(typeName);
419
+ // Find all types referenced by this type
420
+ const referencedTypes = this.findSchemaReferences(schemas[typeName], schemas);
421
+ for (const refName of referencedTypes) {
422
+ if (schemas[refName]) {
423
+ finalTypeNames.add(refName);
424
+ }
425
+ }
426
+ }
372
427
 
373
- // Generate all parameter interfaces for this tag first to avoid duplicates
428
+ // Add import statement only if there are types to import
429
+ if (finalTypeNames.size > 0) {
430
+ hooksContent += `import type { ${Array.from(finalTypeNames).join(', ')} } from './${toCamelCase(tag)}.types';\n\n`;
431
+ } else {
432
+ hooksContent += `\n`;
433
+ }
434
+
435
+ // Generate parameter interfaces for this tag
374
436
  const allParamInterfaces: string[] = [];
375
437
  endpoints.forEach(({ path, method, endpointInfo }) => {
376
438
  const paramInterface = this.generateParamInterface(path, method, endpointInfo, schemas);
@@ -381,26 +443,201 @@ export class SwaggerDocGenerator {
381
443
 
382
444
  // Add all unique parameter interfaces
383
445
  allParamInterfaces.forEach(interfaceCode => {
384
- tagContent += interfaceCode + '\n';
446
+ hooksContent += interfaceCode + '\n';
385
447
  });
386
448
 
387
- // Generate individual hooks
449
+ // Generate individual hooks using react-query and axios
388
450
  endpoints.forEach(({ path, method, endpointInfo }) => {
389
- const hookContent = this.generateSingleHookWithUniqueName(path, method, endpointInfo, schemas);
390
- tagContent += hookContent + '\n';
451
+ const hookContent = this.generateReactQueryHook(path, method, endpointInfo, schemas);
452
+ hooksContent += hookContent + '\n';
391
453
  });
392
454
 
393
- hooksByTag.set(tag, tagContent);
455
+ hooksByTag.set(tag, { hooks: hooksContent, types: typesContent });
394
456
  });
395
457
 
396
458
  return hooksByTag;
397
459
  }
398
460
 
399
461
  /**
400
- * Generates header content for a specific tag
462
+ * Checks if a schema is used in any of the endpoints
463
+ */
464
+ isSchemaUsedInEndpoints(schemaName: string, endpoints: Array<{ path: string, method: string, endpointInfo: any }>, allSchemas: { [key: string]: any }): boolean {
465
+ for (const { endpointInfo } of endpoints) {
466
+ // Check if schema is used as response
467
+ if (endpointInfo.responses) {
468
+ for (const [, responseInfo] of Object.entries(endpointInfo.responses) as [string, any]) {
469
+ if (responseInfo.content) {
470
+ for (const [, contentInfo] of Object.entries(responseInfo.content) as [string, any]) {
471
+ if (contentInfo.schema) {
472
+ if (this.schemaContainsRef(contentInfo.schema, schemaName, allSchemas)) {
473
+ return true;
474
+ }
475
+ }
476
+ }
477
+ }
478
+ }
479
+ }
480
+
481
+ // Check if schema is used in parameters
482
+ if (endpointInfo.parameters) {
483
+ for (const param of endpointInfo.parameters) {
484
+ if (param.schema && this.schemaContainsRef(param.schema, schemaName, allSchemas)) {
485
+ return true;
486
+ }
487
+ }
488
+ }
489
+
490
+ // Check if schema is used in request body
491
+ if (endpointInfo.requestBody && endpointInfo.requestBody.content) {
492
+ for (const [, contentInfo] of Object.entries(endpointInfo.requestBody.content) as [string, any]) {
493
+ if (contentInfo.schema && this.schemaContainsRef(contentInfo.schema, schemaName, allSchemas)) {
494
+ return true;
495
+ }
496
+ }
497
+ }
498
+ }
499
+ return false;
500
+ }
501
+
502
+ /**
503
+ * Checks if a schema contains a reference to another schema
504
+ */
505
+ schemaContainsRef(schema: any, targetSchemaName: string, allSchemas: { [key: string]: any }): boolean {
506
+ if (!schema) return false;
507
+
508
+ // Check if this schema directly references the target
509
+ if (schema.$ref) {
510
+ const refTypeName = schema.$ref.split('/').pop();
511
+ if (refTypeName === targetSchemaName) {
512
+ return true;
513
+ }
514
+ }
515
+
516
+ // Recursively check nested properties
517
+ if (schema.properties) {
518
+ for (const [, propSchema] of Object.entries(schema.properties)) {
519
+ if (this.schemaContainsRef(propSchema as any, targetSchemaName, allSchemas)) {
520
+ return true;
521
+ }
522
+ }
523
+ }
524
+
525
+ // Check if it's an array schema
526
+ if (schema.items) {
527
+ if (this.schemaContainsRef(schema.items, targetSchemaName, allSchemas)) {
528
+ return true;
529
+ }
530
+ }
531
+
532
+ // Check allOf, oneOf, anyOf
533
+ if (schema.allOf) {
534
+ for (const item of schema.allOf) {
535
+ if (this.schemaContainsRef(item, targetSchemaName, allSchemas)) {
536
+ return true;
537
+ }
538
+ }
539
+ }
540
+
541
+ if (schema.oneOf) {
542
+ for (const item of schema.oneOf) {
543
+ if (this.schemaContainsRef(item, targetSchemaName, allSchemas)) {
544
+ return true;
545
+ }
546
+ }
547
+ }
548
+
549
+ if (schema.anyOf) {
550
+ for (const item of schema.anyOf) {
551
+ if (this.schemaContainsRef(item, targetSchemaName, allSchemas)) {
552
+ return true;
553
+ }
554
+ }
555
+ }
556
+
557
+ return false;
558
+ }
559
+
560
+ /**
561
+ * Find all referenced schemas from a set of directly used schemas
562
+ */
563
+ findAllReferencedSchemas(initialSchemas: Set<string>, allSchemas: { [key: string]: any }): Set<string> {
564
+ const result = new Set<string>([...initialSchemas]); // Start with initial schemas
565
+ let changed = true;
566
+
567
+ while (changed) {
568
+ changed = false;
569
+
570
+ for (const typeName of [...result]) { // Use spread to create a new array to avoid concurrent modification
571
+ const schema = allSchemas[typeName];
572
+ if (schema) {
573
+ // Check for references in the schema
574
+ const referencedSchemas = this.findSchemaReferences(schema, allSchemas);
575
+ for (const refName of referencedSchemas) {
576
+ if (!result.has(refName) && allSchemas[refName]) {
577
+ result.add(refName);
578
+ changed = true;
579
+ }
580
+ }
581
+ }
582
+ }
583
+ }
584
+
585
+ return result;
586
+ }
587
+
588
+ /**
589
+ * Find schema references in a given schema
401
590
  */
402
- generateHeaderForTag(tag: string): string {
403
- return `// ${toPascalCase(tag)} API Hooks\n\n`;
591
+ findSchemaReferences(schema: any, allSchemas: { [key: string]: any }): Set<string> {
592
+ const references = new Set<string>();
593
+
594
+ if (!schema) return references;
595
+
596
+ // Check direct $ref
597
+ if (schema.$ref) {
598
+ const refTypeName = schema.$ref.split('/').pop();
599
+ if (refTypeName && allSchemas[refTypeName]) {
600
+ references.add(refTypeName);
601
+ }
602
+ }
603
+
604
+ // Check properties
605
+ if (schema.properties) {
606
+ Object.values(schema.properties).forEach((propSchema: any) => {
607
+ const nestedRefs = this.findSchemaReferences(propSchema, allSchemas);
608
+ nestedRefs.forEach(ref => references.add(ref));
609
+ });
610
+ }
611
+
612
+ // Check array items
613
+ if (schema.items) {
614
+ const itemRefs = this.findSchemaReferences(schema.items, allSchemas);
615
+ itemRefs.forEach(ref => references.add(ref));
616
+ }
617
+
618
+ // Check allOf, oneOf, anyOf
619
+ if (schema.allOf) {
620
+ schema.allOf.forEach((item: any) => {
621
+ const itemRefs = this.findSchemaReferences(item, allSchemas);
622
+ itemRefs.forEach(ref => references.add(ref));
623
+ });
624
+ }
625
+
626
+ if (schema.oneOf) {
627
+ schema.oneOf.forEach((item: any) => {
628
+ const itemRefs = this.findSchemaReferences(item, allSchemas);
629
+ itemRefs.forEach(ref => references.add(ref));
630
+ });
631
+ }
632
+
633
+ if (schema.anyOf) {
634
+ schema.anyOf.forEach((item: any) => {
635
+ const itemRefs = this.findSchemaReferences(item, allSchemas);
636
+ itemRefs.forEach(ref => references.add(ref));
637
+ });
638
+ }
639
+
640
+ return references;
404
641
  }
405
642
 
406
643
  /**
@@ -443,29 +680,17 @@ export class SwaggerDocGenerator {
443
680
  }
444
681
 
445
682
  /**
446
- * Generates a single React hook for an API endpoint with unique parameter interface
683
+ * Generates a React Query hook using axios
447
684
  */
448
- generateSingleHookWithUniqueName(path: string, method: string, endpointInfo: any, schemas: { [key: string]: any }): string {
685
+ generateReactQueryHook(path: string, method: string, endpointInfo: any, schemas: { [key: string]: any }): string {
449
686
  const operationId = endpointInfo.operationId || this.generateOperationId(path, method);
450
687
  const hookName = `use${toPascalCase(operationId)}`;
688
+ const hookType = method.toLowerCase() === 'get' ? 'useQuery' : 'useMutation';
451
689
 
452
690
  // Use unique parameter interface name
453
691
  const pathParams = endpointInfo.parameters?.filter((p: Parameter) => p.in === 'path') || [];
454
692
  const queryParams = endpointInfo.parameters?.filter((p: Parameter) => p.in === 'query') || [];
455
693
 
456
- let paramsDeclaration = '';
457
- let paramsUsage = '{}';
458
- const hasParams = pathParams.length > 0 || queryParams.length > 0;
459
-
460
- if (hasParams) {
461
- const paramInterfaceName = `${toPascalCase(operationId)}Params`;
462
- paramsDeclaration = `params: ${paramInterfaceName}`;
463
- paramsUsage = 'params';
464
- }
465
-
466
- // Format the path for use in the code (handle path parameters)
467
- const pathWithParams = path.replace(/{(\w+)}/g, (_, param) => `\${params.${toCamelCase(param)}}`);
468
-
469
694
  // Determine response type
470
695
  let responseType = 'any';
471
696
  if (endpointInfo.responses && endpointInfo.responses['200']) {
@@ -476,66 +701,155 @@ export class SwaggerDocGenerator {
476
701
  }
477
702
 
478
703
  // Generate request body parameter if needed
479
- let requestBodyParam = '';
704
+ let requestBodyType = 'any';
705
+ let hasBody = false;
480
706
  if (method.toLowerCase() !== 'get' && method.toLowerCase() !== 'delete' && endpointInfo.requestBody) {
481
707
  const bodySchema = endpointInfo.requestBody.content?.['application/json']?.schema;
482
708
  if (bodySchema) {
483
- const bodyType = convertTypeToTs(bodySchema, schemas);
484
- requestBodyParam = `, body: ${bodyType}`;
709
+ requestBodyType = convertTypeToTs(bodySchema, schemas);
710
+ hasBody = true;
485
711
  }
486
712
  }
487
713
 
488
- // Create the hook function
489
- let hookCode = `export const ${hookName} = () => {\n`;
490
- hookCode += ` const apiCall = async (${paramsDeclaration}${requestBodyParam ? requestBodyParam : ''}) => {\n`;
491
- hookCode += ` const path = \`\${process.env.REACT_APP_API_BASE_URL || ''}${pathWithParams}\`;\n`;
492
-
493
- // Add query parameters
494
- if (endpointInfo.parameters && endpointInfo.parameters.some((p: Parameter) => p.in === 'query')) {
495
- hookCode += ` const queryParams = new URLSearchParams();\n`;
496
- endpointInfo.parameters.forEach((param: Parameter) => {
497
- if (param.in === 'query') {
498
- hookCode += ` if (params.${toCamelCase(param.name)}) queryParams.append('${param.name}', params.${toCamelCase(param.name)}.toString());\n`;
499
- }
500
- });
501
- hookCode += ` const queryString = queryParams.toString();\n`;
502
- hookCode += ` const url = \`\${path}\${queryString ? '?' + queryString : ''}\`;\n`;
714
+ // Format the path for use in the code (handle path parameters)
715
+ const pathWithParams = path.replace(/{(\w+)}/g, (_, param) => `\${params.${toCamelCase(param)}}`);
716
+ const axiosPath = `\`\${process.env.REACT_APP_API_BASE_URL || ''}${pathWithParams}\``;
717
+
718
+ // Generate the hook code
719
+ let hookCode = '';
720
+
721
+ if (method.toLowerCase() === 'get') {
722
+ // For GET requests, use useQuery
723
+ const hasParams = pathParams.length > 0 || queryParams.length > 0;
724
+ if (hasParams) {
725
+ const paramInterfaceName = `${toPascalCase(operationId)}Params`;
726
+ hookCode += `export const ${hookName} = (params: ${paramInterfaceName}) => {\n`;
727
+ hookCode += ` return useQuery({\n`;
728
+ hookCode += ` queryKey: ['${operationId}', params],\n`;
729
+ hookCode += ` queryFn: async () => {\n`;
730
+ hookCode += ` const response = await axios.get<${responseType}>(${axiosPath}, { params });\n`;
731
+ hookCode += ` return response.data;\n`;
732
+ hookCode += ` },\n`;
733
+ hookCode += ` });\n`;
734
+ hookCode += `};\n`;
735
+ } else {
736
+ hookCode += `export const ${hookName} = () => {\n`;
737
+ hookCode += ` return useQuery({\n`;
738
+ hookCode += ` queryKey: ['${operationId}'],\n`;
739
+ hookCode += ` queryFn: async () => {\n`;
740
+ hookCode += ` const response = await axios.get<${responseType}>(${axiosPath});\n`;
741
+ hookCode += ` return response.data;\n`;
742
+ hookCode += ` },\n`;
743
+ hookCode += ` });\n`;
744
+ hookCode += `};\n`;
745
+ }
503
746
  } else {
504
- hookCode += ` const url = path;\n`;
747
+ // For non-GET requests, use useMutation
748
+ const hasPathParams = pathParams.length > 0;
749
+ if (hasPathParams) {
750
+ const paramInterfaceName = `${toPascalCase(operationId)}Params`;
751
+ hookCode += `export const ${hookName} = () => {\n`;
752
+ hookCode += ` const queryClient = useQueryClient();\n\n`;
753
+ hookCode += ` return useMutation({\n`;
754
+ hookCode += ` mutationFn: async ({ params, data }: { params: ${paramInterfaceName}; data: ${requestBodyType} }) => {\n`;
755
+ // Format the path for use in the code (handle path parameters)
756
+ let formattedPath = path.replace(/{(\w+)}/g, (_, param) => `\${params.${toCamelCase(param)}}`);
757
+ const pathWithParams = `\`\${process.env.REACT_APP_API_BASE_URL || ''}${formattedPath}\``;
758
+ hookCode += ` const response = await axios.${method.toLowerCase()}<${responseType}>(${pathWithParams}, data);\n`;
759
+ hookCode += ` return response.data;\n`;
760
+ hookCode += ` },\n`;
761
+ hookCode += ` onSuccess: () => {\n`;
762
+ hookCode += ` // Invalidate and refetch related queries\n`;
763
+ hookCode += ` queryClient.invalidateQueries({ queryKey: ['${operationId}'] });\n`;
764
+ hookCode += ` },\n`;
765
+ hookCode += ` });\n`;
766
+ hookCode += `};\n`;
767
+ } else {
768
+ hookCode += `export const ${hookName} = () => {\n`;
769
+ hookCode += ` const queryClient = useQueryClient();\n\n`;
770
+ hookCode += ` return useMutation({\n`;
771
+ hookCode += ` mutationFn: async (data: ${requestBodyType}) => {\n`;
772
+ hookCode += ` const response = await axios.${method.toLowerCase()}<${responseType}>(${axiosPath}, data);\n`;
773
+ hookCode += ` return response.data;\n`;
774
+ hookCode += ` },\n`;
775
+ hookCode += ` onSuccess: () => {\n`;
776
+ hookCode += ` // Invalidate and refetch related queries\n`;
777
+ hookCode += ` queryClient.invalidateQueries({ queryKey: ['${operationId}'] });\n`;
778
+ hookCode += ` },\n`;
779
+ hookCode += ` });\n`;
780
+ hookCode += `};\n`;
781
+ }
505
782
  }
506
783
 
507
- // Add fetch options
508
- hookCode += ` const options: RequestInit = {\n`;
509
- hookCode += ` method: '${method.toUpperCase()}',\n`;
510
-
511
- if (requestBodyParam) {
512
- hookCode += ` headers: {\n 'Content-Type': 'application/json',\n },\n`;
513
- hookCode += ` body: JSON.stringify(body),\n`;
514
- }
784
+ return hookCode;
785
+ }
515
786
 
516
- hookCode += ` };\n\n`;
517
- hookCode += ` const result = await fetch(url, options);\n`;
518
- hookCode += ` return result.json() as Promise<${responseType}>;\n`;
519
- hookCode += ` };\n\n`;
520
- hookCode += ` return { ${toCamelCase(operationId)}: apiCall };\n`;
521
- hookCode += `};\n`;
522
787
 
523
- return hookCode;
788
+ /**
789
+ * Generate operation ID from path and method if not provided
790
+ */
791
+ generateOperationId(path: string, method: string): string {
792
+ return `${method.toLowerCase()}_${path.replace(/[\/{}]/g, '_')}`;
524
793
  }
525
794
 
526
795
  /**
527
- * Generates a single React hook for an API endpoint
796
+ * Formats code using Prettier - sync version with child process
528
797
  */
529
- generateSingleHook(path: string, method: string, endpointInfo: any, schemas: { [key: string]: any }): string {
530
- // This method is kept for backward compatibility
531
- return this.generateSingleHookWithUniqueName(path, method, endpointInfo, schemas);
798
+ private formatCode(code: string, filepath: string): string {
799
+ // Skip formatting in test environment to avoid ESM issues
800
+ if (process.env.NODE_ENV === 'test' || typeof jest !== 'undefined') {
801
+ return code;
802
+ }
803
+
804
+ try {
805
+ // Use execSync to run prettier as a separate process to avoid ESM issues
806
+ const { execSync } = require('child_process');
807
+ const { writeFileSync, readFileSync, unlinkSync } = require('fs');
808
+ const { join, extname } = require('path');
809
+ const { tmpdir } = require('os');
810
+
811
+ // Determine the file extension to use for the temp file
812
+ const fileExtension = extname(filepath) || '.txt';
813
+ const tempPath = join(tmpdir(), `prettier-tmp-${Date.now()}-${Math.random().toString(36).substr(2, 9)}${fileExtension}`);
814
+ writeFileSync(tempPath, code, 'utf8');
815
+
816
+ // Format the file using prettier CLI
817
+ execSync(`npx prettier --write "${tempPath}" --single-quote --trailing-comma es5 --tab-width 2 --semi --print-width 80`, {
818
+ stdio: 'pipe'
819
+ });
820
+
821
+ // Read the formatted content back
822
+ const formattedCode = readFileSync(tempPath, 'utf8');
823
+
824
+ // Clean up the temporary file
825
+ unlinkSync(tempPath);
826
+
827
+ return formattedCode;
828
+ } catch (error) {
829
+ console.warn(`Failed to format ${filepath} with Prettier:`, error);
830
+ return code; // Return unformatted code if formatting fails
831
+ }
532
832
  }
533
833
 
534
834
  /**
535
- * Generate operation ID from path and method if not provided
835
+ * Gets the parser based on file extension
536
836
  */
537
- generateOperationId(path: string, method: string): string {
538
- return `${method.toLowerCase()}_${path.replace(/[\/{}]/g, '_')}`;
837
+ private getParserForFile(filepath: string): string {
838
+ const ext = path.extname(filepath);
839
+ switch (ext) {
840
+ case '.ts':
841
+ case '.tsx':
842
+ return 'typescript';
843
+ case '.js':
844
+ case '.jsx':
845
+ return 'babel';
846
+ case '.json':
847
+ return 'json';
848
+ case '.md':
849
+ return 'markdown';
850
+ default:
851
+ return 'typescript';
852
+ }
539
853
  }
540
854
 
541
855
  /**
@@ -547,7 +861,8 @@ export class SwaggerDocGenerator {
547
861
  fs.mkdirSync(dir, { recursive: true });
548
862
  }
549
863
 
550
- fs.writeFileSync(outputPath, documentation, 'utf8');
864
+ const formattedDocumentation = this.formatCode(documentation, outputPath);
865
+ fs.writeFileSync(outputPath, formattedDocumentation, 'utf8');
551
866
  }
552
867
 
553
868
  /**
@@ -559,26 +874,36 @@ export class SwaggerDocGenerator {
559
874
  fs.mkdirSync(dir, { recursive: true });
560
875
  }
561
876
 
562
- fs.writeFileSync(outputPath, types, 'utf8');
877
+ const formattedTypes = this.formatCode(types, outputPath);
878
+ fs.writeFileSync(outputPath, formattedTypes, 'utf8');
563
879
  }
564
880
 
565
881
  /**
566
882
  * Saves the generated React hooks to files organized by tag
567
883
  */
568
- saveHooksByTag(hooksByTag: Map<string, string>, outputDir: string): void {
884
+ saveHooksByTag(hooksByTag: Map<string, { hooks: string, types: string }>, outputDir: string): void {
569
885
  const dir = outputDir;
570
886
  if (!fs.existsSync(dir)) {
571
887
  fs.mkdirSync(dir, { recursive: true });
572
888
  }
573
889
 
574
- for (const [tag, content] of hooksByTag) {
890
+ for (const [tag, { hooks, types }] of hooksByTag) {
575
891
  const tagDir = path.join(outputDir, toCamelCase(tag));
576
892
  if (!fs.existsSync(tagDir)) {
577
893
  fs.mkdirSync(tagDir, { recursive: true });
578
894
  }
579
895
 
580
- const fileName = path.join(tagDir, `${toCamelCase(tag)}.hooks.ts`);
581
- fs.writeFileSync(fileName, content, 'utf8');
896
+ // Save hooks to hooks file
897
+ const hooksFileName = path.join(tagDir, `${toCamelCase(tag)}.hooks.ts`);
898
+ const formattedHooks = this.formatCode(hooks, hooksFileName);
899
+ fs.writeFileSync(hooksFileName, formattedHooks, 'utf8');
900
+
901
+ // Save types to types file
902
+ if (types.trim()) { // Only save if there are types
903
+ const typesFileName = path.join(tagDir, `${toCamelCase(tag)}.types.ts`);
904
+ const formattedTypes = this.formatCode(types, typesFileName);
905
+ fs.writeFileSync(typesFileName, formattedTypes, 'utf8');
906
+ }
582
907
  }
583
908
  }
584
909
  }