jszy-swagger-doc-generator 1.4.0 → 1.5.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/dist/index.js CHANGED
@@ -40,6 +40,8 @@ exports.SwaggerDocGenerator = void 0;
40
40
  const axios_1 = __importDefault(require("axios"));
41
41
  const fs = __importStar(require("fs"));
42
42
  const path = __importStar(require("path"));
43
+ const template_helpers_1 = require("./helpers/template.helpers");
44
+ const type_helpers_1 = require("./helpers/type.helpers");
43
45
  /**
44
46
  * Transforms a string to PascalCase
45
47
  */
@@ -60,76 +62,27 @@ function toCamelCase(str) {
60
62
  })
61
63
  .replace(/\s+/g, '');
62
64
  }
63
- /**
64
- * Converts OpenAPI types to TypeScript types
65
- */
66
- function convertTypeToTs(typeDef, schemaComponents) {
67
- if (!typeDef)
68
- return 'any';
69
- if (typeDef.$ref) {
70
- // Extract the type name from the reference
71
- const refTypeName = typeDef.$ref.split('/').pop();
72
- return refTypeName || 'any';
73
- }
74
- // Handle allOf (used for composition/references)
75
- if (typeDef.allOf && Array.isArray(typeDef.allOf)) {
76
- return typeDef.allOf.map((item) => {
77
- if (item.$ref) {
78
- return item.$ref.split('/').pop();
79
- }
80
- else if (item.type) {
81
- return convertTypeToTs(item, schemaComponents);
82
- }
83
- return 'any';
84
- }).filter(Boolean).join(' & ') || 'any';
85
- }
86
- if (Array.isArray(typeDef.type)) {
87
- // Handle union types like ["string", "null"]
88
- if (typeDef.type.includes('null')) {
89
- const nonNullType = typeDef.type.find((t) => t !== 'null');
90
- return `${convertTypeToTs({ ...typeDef, type: nonNullType }, schemaComponents)} | null`;
91
- }
92
- return 'any';
65
+ class SwaggerDocGenerator {
66
+ /**
67
+ * Transforms a string to PascalCase
68
+ */
69
+ toPascalCase(str) {
70
+ return str
71
+ .replace(/(?:^\w|[A-Z]|\b\w)/g, (word, index) => {
72
+ return index === 0 ? word.toUpperCase() : word.toUpperCase();
73
+ })
74
+ .replace(/\s+/g, '');
93
75
  }
94
- switch (typeDef.type) {
95
- case 'string':
96
- if (typeDef.enum) {
97
- return `"${typeDef.enum.join('" | "')}"`;
98
- }
99
- if (typeDef.format === 'date' || typeDef.format === 'date-time') {
100
- return 'string';
101
- }
102
- return 'string';
103
- case 'integer':
104
- case 'number':
105
- return 'number';
106
- case 'boolean':
107
- return 'boolean';
108
- case 'array':
109
- if (typeDef.items) {
110
- return `${convertTypeToTs(typeDef.items, schemaComponents)}[]`;
111
- }
112
- return 'any[]';
113
- case 'object':
114
- if (typeDef.properties) {
115
- // Inline object definition
116
- const fields = Object.entries(typeDef.properties)
117
- .map(([propName, propSchema]) => {
118
- const required = typeDef.required && typeDef.required.includes(propName);
119
- const optional = !required ? '?' : '';
120
- return ` ${propName}${optional}: ${convertTypeToTs(propSchema, schemaComponents)};`;
121
- })
122
- .join('\n');
123
- return `{\n${fields}\n}`;
124
- }
125
- return 'Record<string, any>';
126
- case 'null':
127
- return 'null';
128
- default:
129
- return 'any';
76
+ /**
77
+ * Transforms a string to camelCase
78
+ */
79
+ toCamelCase(str) {
80
+ return str
81
+ .replace(/(?:^\w|[A-Z]|\b\w)/g, (word, index) => {
82
+ return index === 0 ? word.toLowerCase() : word.toUpperCase();
83
+ })
84
+ .replace(/\s+/g, '');
130
85
  }
131
- }
132
- class SwaggerDocGenerator {
133
86
  /**
134
87
  * Fetches the Swagger/OpenAPI JSON from a given URL
135
88
  */
@@ -242,6 +195,149 @@ class SwaggerDocGenerator {
242
195
  });
243
196
  return typeDefs;
244
197
  }
198
+ /**
199
+ * Generates React hooks from the paths in Swagger doc organized by tag
200
+ */
201
+ /**
202
+ * Converts OpenAPI types to TypeScript types
203
+ */
204
+ convertTypeToTs(typeDef, schemaComponents) {
205
+ if (!typeDef)
206
+ return 'any';
207
+ if (typeDef.$ref) {
208
+ // Extract the type name from the reference
209
+ const refTypeName = typeDef.$ref.split('/').pop();
210
+ return refTypeName || 'any';
211
+ }
212
+ // Handle allOf (used for composition/references) - combine all properties
213
+ if (typeDef.allOf && Array.isArray(typeDef.allOf)) {
214
+ const combinedProperties = {};
215
+ const refTypes = [];
216
+ for (const item of typeDef.allOf) {
217
+ if (item.$ref) {
218
+ // Extract the type name from the reference
219
+ const refTypeName = item.$ref.split('/').pop();
220
+ if (refTypeName) {
221
+ refTypes.push(refTypeName);
222
+ }
223
+ }
224
+ else if (item.type === 'object' && item.properties) {
225
+ // Combine properties from inline object definitions
226
+ Object.assign(combinedProperties, item.properties);
227
+ }
228
+ }
229
+ if (refTypes.length > 0 && Object.keys(combinedProperties).length > 0) {
230
+ // We have both references and inline properties
231
+ const inlineDef = {
232
+ type: 'object',
233
+ properties: combinedProperties,
234
+ required: typeDef.required
235
+ };
236
+ const inlineType = this.convertTypeToTs(inlineDef, schemaComponents);
237
+ return `${refTypes.join(' & ')} & ${inlineType}`;
238
+ }
239
+ else if (refTypes.length > 0) {
240
+ // Only references
241
+ return refTypes.join(' & ');
242
+ }
243
+ else if (Object.keys(combinedProperties).length > 0) {
244
+ // Only inline properties
245
+ return this.convertTypeToTs({
246
+ type: 'object',
247
+ properties: combinedProperties,
248
+ required: typeDef.required
249
+ }, schemaComponents);
250
+ }
251
+ else {
252
+ return 'any';
253
+ }
254
+ }
255
+ // Handle oneOf (union types)
256
+ if (typeDef.oneOf && Array.isArray(typeDef.oneOf)) {
257
+ return typeDef.oneOf.map((item) => {
258
+ if (item.$ref) {
259
+ return item.$ref.split('/').pop();
260
+ }
261
+ else if (item.type) {
262
+ return this.convertTypeToTs(item, schemaComponents);
263
+ }
264
+ return 'any';
265
+ }).filter(Boolean).join(' | ') || 'any';
266
+ }
267
+ // Handle anyOf (union types)
268
+ if (typeDef.anyOf && Array.isArray(typeDef.anyOf)) {
269
+ return typeDef.anyOf.map((item) => {
270
+ if (item.$ref) {
271
+ return item.$ref.split('/').pop();
272
+ }
273
+ else if (item.type) {
274
+ return this.convertTypeToTs(item, schemaComponents);
275
+ }
276
+ return 'any';
277
+ }).filter(Boolean).join(' | ') || 'any';
278
+ }
279
+ if (Array.isArray(typeDef.type)) {
280
+ // Handle union types like ["string", "null"]
281
+ if (typeDef.type.includes('null')) {
282
+ const nonNullTypes = typeDef.type.filter((t) => t !== 'null');
283
+ if (nonNullTypes.length === 1) {
284
+ return `${this.convertTypeToTs({ ...typeDef, type: nonNullTypes[0] }, schemaComponents)} | null`;
285
+ }
286
+ else {
287
+ // Handle complex union types with null
288
+ const nonNullTypeStr = nonNullTypes
289
+ .map((t) => this.convertTypeToTs({ ...typeDef, type: t }, schemaComponents))
290
+ .join(' | ');
291
+ return `${nonNullTypeStr} | null`;
292
+ }
293
+ }
294
+ // Handle other array type unions
295
+ return typeDef.type
296
+ .map((t) => this.convertTypeToTs({ ...typeDef, type: t }, schemaComponents))
297
+ .join(' | ') || 'any';
298
+ }
299
+ switch (typeDef.type) {
300
+ case 'string':
301
+ if (typeDef.enum) {
302
+ return `"${typeDef.enum.join('" | "')}"`;
303
+ }
304
+ if (typeDef.format === 'date' || typeDef.format === 'date-time') {
305
+ return 'string';
306
+ }
307
+ return 'string';
308
+ case 'integer':
309
+ case 'number':
310
+ return 'number';
311
+ case 'boolean':
312
+ return 'boolean';
313
+ case 'array':
314
+ if (typeDef.items) {
315
+ return `${this.convertTypeToTs(typeDef.items, schemaComponents)}[]`;
316
+ }
317
+ return 'any[]';
318
+ case 'object':
319
+ if (typeDef.properties) {
320
+ // Inline object definition
321
+ const fields = Object.entries(typeDef.properties)
322
+ .map(([propName, propSchema]) => {
323
+ const required = typeDef.required && typeDef.required.includes(propName);
324
+ const optional = !required ? '?' : '';
325
+ const type = this.convertTypeToTs(propSchema, schemaComponents);
326
+ // Get the description for JSDoc if available
327
+ const propDescription = propSchema.description || propSchema.title;
328
+ const jsDoc = propDescription ? ` /** ${propDescription} */\n` : '';
329
+ return `${jsDoc} ${propName}${optional}: ${type};`;
330
+ })
331
+ .join('\n');
332
+ return `{\n${fields}\n }`;
333
+ }
334
+ return 'Record<string, any>';
335
+ case 'null':
336
+ return 'null';
337
+ default:
338
+ return 'any';
339
+ }
340
+ }
245
341
  /**
246
342
  * Generates a single TypeScript type definition
247
343
  */
@@ -250,15 +346,15 @@ class SwaggerDocGenerator {
250
346
  // Enum type
251
347
  return `export type ${typeName} = ${schema.enum.map((val) => `'${val}'`).join(' | ')};\n`;
252
348
  }
253
- if (schema.oneOf || schema.anyOf || schema.allOf) {
254
- // Union type or complex type
255
- const typeOption = schema.oneOf ? 'oneOf' : schema.anyOf ? 'anyOf' : 'allOf';
349
+ if (schema.oneOf || schema.anyOf) {
350
+ // Union type or complex type (oneOf/anyOf)
351
+ const typeOption = schema.oneOf ? 'oneOf' : 'anyOf';
256
352
  const types = schema[typeOption].map((item) => {
257
353
  if (item.$ref) {
258
354
  return item.$ref.split('/').pop();
259
355
  }
260
356
  else if (item.type) {
261
- return convertTypeToTs(item, allSchemas);
357
+ return this.convertTypeToTs(item, allSchemas);
262
358
  }
263
359
  else {
264
360
  return 'any';
@@ -266,6 +362,24 @@ class SwaggerDocGenerator {
266
362
  }).filter(Boolean);
267
363
  return `export type ${typeName} = ${types.join(' | ')};\n`;
268
364
  }
365
+ if (schema.allOf) {
366
+ // Handle allOf - composition of multiple schemas
367
+ const allParts = [];
368
+ for (const part of schema.allOf) {
369
+ if (part.$ref) {
370
+ const refTypeName = part.$ref.split('/').pop();
371
+ if (refTypeName) {
372
+ allParts.push(refTypeName);
373
+ }
374
+ }
375
+ else if (part.type === 'object' && part.properties) {
376
+ // Create a temporary interface for inline object
377
+ const inlineInterface = this.generateInlineObjectInterface(part, `${typeName}Inline`, allSchemas);
378
+ allParts.push(inlineInterface);
379
+ }
380
+ }
381
+ return `export type ${typeName} = ${allParts.join(' & ')};\n`;
382
+ }
269
383
  if (schema.type === 'object') {
270
384
  // Object type
271
385
  let result = `export interface ${typeName} {\n`;
@@ -273,18 +387,39 @@ class SwaggerDocGenerator {
273
387
  Object.entries(schema.properties).forEach(([propName, propSchema]) => {
274
388
  const required = schema.required && schema.required.includes(propName);
275
389
  const optional = !required ? '?' : '';
276
- // Add JSDoc comment if available
277
- if (propSchema.description) {
278
- result += ` /** ${propSchema.description} */\n`;
390
+ // Add JSDoc comment if available (using title or description)
391
+ const jsDoc = propSchema.title || propSchema.description;
392
+ if (jsDoc) {
393
+ result += ` /** ${jsDoc} */\n`;
279
394
  }
280
- result += ` ${propName}${optional}: ${convertTypeToTs(propSchema, allSchemas)};\n`;
395
+ result += ` ${propName}${optional}: ${this.convertTypeToTs(propSchema, allSchemas)};\n`;
281
396
  });
282
397
  }
283
398
  result += '}\n';
284
399
  return result;
285
400
  }
286
401
  // For other types (string, number, etc.) that might have additional properties
287
- return `export type ${typeName} = ${convertTypeToTs(schema, allSchemas)};\n`;
402
+ return `export type ${typeName} = ${this.convertTypeToTs(schema, allSchemas)};\n`;
403
+ }
404
+ /**
405
+ * Generates an inline object interface for allOf composition
406
+ */
407
+ generateInlineObjectInterface(schema, tempName, allSchemas) {
408
+ if (!schema.properties)
409
+ return 'any';
410
+ let result = '{\n';
411
+ Object.entries(schema.properties).forEach(([propName, propSchema]) => {
412
+ const required = schema.required && schema.required.includes(propName);
413
+ const optional = !required ? '?' : '';
414
+ // Add JSDoc comment if available (using title or description)
415
+ const jsDoc = propSchema.title || propSchema.description;
416
+ if (jsDoc) {
417
+ result += ` /** ${jsDoc} */\n`;
418
+ }
419
+ result += ` ${propName}${optional}: ${this.convertTypeToTs(propSchema, allSchemas)};\n`;
420
+ });
421
+ result += ' }';
422
+ return result;
288
423
  }
289
424
  /**
290
425
  * Generates React hooks from the paths in Swagger doc organized by tag
@@ -557,19 +692,34 @@ class SwaggerDocGenerator {
557
692
  }
558
693
  // Create a unique interface name based on the operation ID
559
694
  const operationId = endpointInfo.operationId || this.generateOperationId(path, method);
560
- const interfaceName = `${toPascalCase(operationId)}Params`;
695
+ // Extract action name from operationId to create cleaner parameter interface names
696
+ // e.g. configController_updateConfig -> UpdateConfigParams instead of ConfigController_updateConfigParams
697
+ let interfaceName;
698
+ if (operationId.includes('_')) {
699
+ const parts = operationId.split('_');
700
+ if (parts.length >= 2) {
701
+ // Use just the action part in the interface name
702
+ interfaceName = `${toPascalCase(parts[parts.length - 1])}Params`;
703
+ }
704
+ else {
705
+ interfaceName = `${toPascalCase(operationId)}Params`;
706
+ }
707
+ }
708
+ else {
709
+ interfaceName = `${toPascalCase(operationId)}Params`;
710
+ }
561
711
  let paramsInterface = `export interface ${interfaceName} {\n`;
562
712
  // Add path parameters
563
713
  pathParams.forEach((param) => {
564
714
  const required = param.required ? '' : '?';
565
- const type = convertTypeToTs(param.schema || {}, schemas);
566
- paramsInterface += ` ${toCamelCase(param.name)}${required}: ${type};\n`;
715
+ const type = this.convertTypeToTs(param.schema || {}, schemas);
716
+ paramsInterface += ` ${this.toCamelCase(param.name)}${required}: ${type};\n`;
567
717
  });
568
718
  // Add query parameters
569
719
  queryParams.forEach((param) => {
570
720
  const required = param.required ? '' : '?';
571
- const type = convertTypeToTs(param.schema || {}, schemas);
572
- paramsInterface += ` ${toCamelCase(param.name)}${required}: ${type};\n`;
721
+ const type = this.convertTypeToTs(param.schema || {}, schemas);
722
+ paramsInterface += ` ${this.toCamelCase(param.name)}${required}: ${type};\n`;
573
723
  });
574
724
  paramsInterface += '}\n';
575
725
  return paramsInterface;
@@ -579,17 +729,38 @@ class SwaggerDocGenerator {
579
729
  */
580
730
  generateReactQueryHook(path, method, endpointInfo, schemas) {
581
731
  const operationId = endpointInfo.operationId || this.generateOperationId(path, method);
582
- const hookName = `use${toPascalCase(operationId)}`;
732
+ // Extract action name from operationId to create cleaner hook names
733
+ // e.g. configController_updateConfig -> useUpdateConfig instead of useConfigController_updateConfig
734
+ let hookName = `use${this.toPascalCase(operationId)}`;
735
+ // Check if operationId follows pattern controller_action and simplify to action
736
+ if (operationId.includes('_')) {
737
+ const parts = operationId.split('_');
738
+ if (parts.length >= 2) {
739
+ // Use just the action part as the hook name
740
+ hookName = `use${this.toPascalCase(parts[parts.length - 1])}`;
741
+ }
742
+ }
743
+ else {
744
+ // For operationIds without underscores, keep the original naming
745
+ hookName = `use${this.toPascalCase(operationId)}`;
746
+ }
583
747
  const hookType = method.toLowerCase() === 'get' ? 'useQuery' : 'useMutation';
584
748
  // Use unique parameter interface name
585
749
  const pathParams = endpointInfo.parameters?.filter((p) => p.in === 'path') || [];
586
750
  const queryParams = endpointInfo.parameters?.filter((p) => p.in === 'query') || [];
587
- // Determine response type
751
+ // Determine response type by checking common success response codes
588
752
  let responseType = 'any';
589
- if (endpointInfo.responses && endpointInfo.responses['200']) {
590
- const responseSchema = endpointInfo.responses['200'].content?.['application/json']?.schema;
591
- if (responseSchema) {
592
- responseType = convertTypeToTs(responseSchema, schemas);
753
+ if (endpointInfo.responses) {
754
+ // Check for success responses in order of preference: 200, 201, 204, etc.
755
+ const successCodes = ['200', '201', '204', '202', '203', '205'];
756
+ for (const code of successCodes) {
757
+ if (endpointInfo.responses[code]) {
758
+ const responseSchema = endpointInfo.responses[code].content?.['application/json']?.schema;
759
+ if (responseSchema) {
760
+ responseType = this.convertTypeToTs(responseSchema, schemas);
761
+ break; // Use the first success response found
762
+ }
763
+ }
593
764
  }
594
765
  }
595
766
  // Generate request body parameter if needed
@@ -598,81 +769,39 @@ class SwaggerDocGenerator {
598
769
  if (method.toLowerCase() !== 'get' && method.toLowerCase() !== 'delete' && endpointInfo.requestBody) {
599
770
  const bodySchema = endpointInfo.requestBody.content?.['application/json']?.schema;
600
771
  if (bodySchema) {
601
- requestBodyType = convertTypeToTs(bodySchema, schemas);
772
+ requestBodyType = this.convertTypeToTs(bodySchema, schemas);
602
773
  hasBody = true;
603
774
  }
604
775
  }
605
- // Format the path for use in the code (handle path parameters)
606
- const pathWithParams = path.replace(/{(\w+)}/g, (_, param) => `\${params.${toCamelCase(param)}}`);
607
- const axiosPath = `\`\${process.env.REACT_APP_API_BASE_URL || ''}${pathWithParams}\``;
608
- // Generate the hook code
609
- let hookCode = '';
610
- if (method.toLowerCase() === 'get') {
611
- // For GET requests, use useQuery
612
- const hasParams = pathParams.length > 0 || queryParams.length > 0;
613
- if (hasParams) {
614
- const paramInterfaceName = `${toPascalCase(operationId)}Params`;
615
- hookCode += `export const ${hookName} = (params: ${paramInterfaceName}) => {\n`;
616
- hookCode += ` return useQuery({\n`;
617
- hookCode += ` queryKey: ['${operationId}', params],\n`;
618
- hookCode += ` queryFn: async () => {\n`;
619
- hookCode += ` const response = await axios.get<${responseType}>(${axiosPath}, { params });\n`;
620
- hookCode += ` return response.data;\n`;
621
- hookCode += ` },\n`;
622
- hookCode += ` });\n`;
623
- hookCode += `};\n`;
624
- }
625
- else {
626
- hookCode += `export const ${hookName} = () => {\n`;
627
- hookCode += ` return useQuery({\n`;
628
- hookCode += ` queryKey: ['${operationId}'],\n`;
629
- hookCode += ` queryFn: async () => {\n`;
630
- hookCode += ` const response = await axios.get<${responseType}>(${axiosPath});\n`;
631
- hookCode += ` return response.data;\n`;
632
- hookCode += ` },\n`;
633
- hookCode += ` });\n`;
634
- hookCode += `};\n`;
635
- }
776
+ // Format the path for use in the code (handle path parameters) - without base URL
777
+ const formattedPath = path.replace(/{(\w+)}/g, (_, param) => `\${params.${this.toCamelCase(param)}}`);
778
+ // Prepare data for the template
779
+ const hookData = {
780
+ hookName: hookName,
781
+ operationId: operationId,
782
+ method: method.toLowerCase(),
783
+ responseType: responseType,
784
+ requestBodyType: requestBodyType,
785
+ hasParams: pathParams.length > 0 || queryParams.length > 0,
786
+ hasPathParams: pathParams.length > 0,
787
+ paramInterfaceName: `${hookName.replace('use', '')}Params`,
788
+ formattedPath: formattedPath,
789
+ isGetRequest: method.toLowerCase() === 'get'
790
+ };
791
+ // Load and compile the individual hook template
792
+ const fs = require('fs');
793
+ const pathModule = require('path');
794
+ const templatePath = pathModule.join(__dirname, '..', 'templates', 'hooks', 'individual-hook.hbs');
795
+ try {
796
+ const templateSource = fs.readFileSync(templatePath, 'utf8');
797
+ const Handlebars = require('handlebars');
798
+ const template = Handlebars.compile(templateSource);
799
+ return template(hookData);
800
+ }
801
+ catch (error) {
802
+ console.error(`Error reading template file: ${error.message}`);
803
+ return `// Error generating hook for ${operationId}: ${error.message}`;
636
804
  }
637
- else {
638
- // For non-GET requests, use useMutation
639
- const hasPathParams = pathParams.length > 0;
640
- if (hasPathParams) {
641
- const paramInterfaceName = `${toPascalCase(operationId)}Params`;
642
- hookCode += `export const ${hookName} = () => {\n`;
643
- hookCode += ` const queryClient = useQueryClient();\n\n`;
644
- hookCode += ` return useMutation({\n`;
645
- hookCode += ` mutationFn: async ({ params, data }: { params: ${paramInterfaceName}; data: ${requestBodyType} }) => {\n`;
646
- // Format the path for use in the code (handle path parameters)
647
- let formattedPath = path.replace(/{(\w+)}/g, (_, param) => `\${params.${toCamelCase(param)}}`);
648
- const pathWithParams = `\`\${process.env.REACT_APP_API_BASE_URL || ''}${formattedPath}\``;
649
- hookCode += ` const response = await axios.${method.toLowerCase()}<${responseType}>(${pathWithParams}, data);\n`;
650
- hookCode += ` return response.data;\n`;
651
- hookCode += ` },\n`;
652
- hookCode += ` onSuccess: () => {\n`;
653
- hookCode += ` // Invalidate and refetch related queries\n`;
654
- hookCode += ` queryClient.invalidateQueries({ queryKey: ['${operationId}'] });\n`;
655
- hookCode += ` },\n`;
656
- hookCode += ` });\n`;
657
- hookCode += `};\n`;
658
- }
659
- else {
660
- hookCode += `export const ${hookName} = () => {\n`;
661
- hookCode += ` const queryClient = useQueryClient();\n\n`;
662
- hookCode += ` return useMutation({\n`;
663
- hookCode += ` mutationFn: async (data: ${requestBodyType}) => {\n`;
664
- hookCode += ` const response = await axios.${method.toLowerCase()}<${responseType}>(${axiosPath}, data);\n`;
665
- hookCode += ` return response.data;\n`;
666
- hookCode += ` },\n`;
667
- hookCode += ` onSuccess: () => {\n`;
668
- hookCode += ` // Invalidate and refetch related queries\n`;
669
- hookCode += ` queryClient.invalidateQueries({ queryKey: ['${operationId}'] });\n`;
670
- hookCode += ` },\n`;
671
- hookCode += ` });\n`;
672
- hookCode += `};\n`;
673
- }
674
- }
675
- return hookCode;
676
805
  }
677
806
  /**
678
807
  * Generate operation ID from path and method if not provided
@@ -780,6 +909,150 @@ class SwaggerDocGenerator {
780
909
  }
781
910
  }
782
911
  }
912
+ /**
913
+ * Generates frontend resources using Handlebars templates
914
+ */
915
+ generateHandlebarsResources(swaggerDoc, templatePaths = {}) {
916
+ const resourcesByTag = new Map();
917
+ const schemas = swaggerDoc.components?.schemas || {};
918
+ // Group endpoints by tag
919
+ const endpointsByTag = {};
920
+ Object.entries(swaggerDoc.paths).forEach(([path, methods]) => {
921
+ Object.entries(methods).forEach(([method, endpointInfo]) => {
922
+ // Determine the tag for this endpoint
923
+ const tag = (endpointInfo.tags && endpointInfo.tags[0]) ? endpointInfo.tags[0] : 'General';
924
+ if (!endpointsByTag[tag]) {
925
+ endpointsByTag[tag] = [];
926
+ }
927
+ endpointsByTag[tag].push({ path, method, endpointInfo });
928
+ });
929
+ });
930
+ // Generate resources for each tag
931
+ Object.entries(endpointsByTag).forEach(([tag, endpoints]) => {
932
+ // Prepare context for templates
933
+ const context = {
934
+ title: swaggerDoc.info.title,
935
+ description: swaggerDoc.info.description || swaggerDoc.info.title,
936
+ version: swaggerDoc.info.version,
937
+ tag: tag,
938
+ endpoints: endpoints.map(e => ({
939
+ path: e.path,
940
+ method: e.method.toUpperCase(),
941
+ operationId: e.endpointInfo.operationId || this.generateOperationId(e.path, e.method),
942
+ summary: e.endpointInfo.summary,
943
+ description: e.endpointInfo.description,
944
+ parameters: e.endpointInfo.parameters || [],
945
+ responses: e.endpointInfo.responses,
946
+ requestBody: e.endpointInfo.requestBody
947
+ })),
948
+ schemas: schemas,
949
+ hasImportTypes: false,
950
+ usedTypeNames: [],
951
+ paramInterfaces: [],
952
+ hooks: [],
953
+ typeDefinitions: []
954
+ };
955
+ // Find types used in this tag
956
+ const directlyUsedSchemas = new Set();
957
+ if (schemas) {
958
+ Object.entries(schemas).forEach(([typeName, schema]) => {
959
+ if (this.isSchemaUsedInEndpoints(typeName, endpoints, schemas)) {
960
+ directlyUsedSchemas.add(typeName);
961
+ }
962
+ });
963
+ }
964
+ const allNeededSchemas = this.findAllReferencedSchemas(directlyUsedSchemas, schemas);
965
+ // Generate TypeScript types
966
+ let typesContent = '';
967
+ if (schemas) {
968
+ for (const typeName of allNeededSchemas) {
969
+ const schema = schemas[typeName];
970
+ if (schema) {
971
+ const typeDef = (0, type_helpers_1.generateSingleTypeDefinition)(typeName, schema, schemas);
972
+ typesContent += typeDef + '\n';
973
+ context.typeDefinitions.push(typeDef);
974
+ }
975
+ }
976
+ }
977
+ // Check if there are used types for import
978
+ if (allNeededSchemas.size > 0) {
979
+ context.hasImportTypes = true;
980
+ context.usedTypeNames = Array.from(allNeededSchemas);
981
+ }
982
+ // Generate parameter interfaces
983
+ const allParamInterfaces = [];
984
+ endpoints.forEach(({ path, method, endpointInfo }) => {
985
+ const paramInterface = this.generateParamInterface(path, method, endpointInfo, schemas);
986
+ if (paramInterface && !allParamInterfaces.includes(paramInterface)) {
987
+ allParamInterfaces.push(paramInterface);
988
+ }
989
+ });
990
+ context.paramInterfaces = allParamInterfaces;
991
+ // Generate individual hooks
992
+ const allHooks = [];
993
+ const endpointHookContents = [];
994
+ endpoints.forEach(({ path, method, endpointInfo }) => {
995
+ const hookContent = this.generateReactQueryHook(path, method, endpointInfo, schemas);
996
+ allHooks.push(hookContent);
997
+ endpointHookContents.push(hookContent); // Store for template context
998
+ });
999
+ context.hooks = allHooks;
1000
+ context.endpointHooks = endpointHookContents;
1001
+ // Generate resources using specified templates
1002
+ let hooksContent = '';
1003
+ if (templatePaths.hooks) {
1004
+ try {
1005
+ // Add utility functions to context for use in templates
1006
+ context['camelCase'] = (str) => this.toCamelCase(str);
1007
+ context['pascalCase'] = (str) => this.toPascalCase(str);
1008
+ hooksContent = (0, template_helpers_1.compileTemplate)(templatePaths.hooks, context);
1009
+ }
1010
+ catch (error) {
1011
+ // If template doesn't exist or fails, fall back to default generation
1012
+ console.warn(`Failed to compile hooks template: ${templatePaths.hooks}`, error);
1013
+ // Use the existing method as fallback
1014
+ hooksContent = `// ${this.toPascalCase(tag)} API Hooks\n`;
1015
+ hooksContent += `import { useQuery, useMutation, useQueryClient } from 'react-query';\n`;
1016
+ hooksContent += `import axios from 'axios';\n`;
1017
+ if (context.hasImportTypes) {
1018
+ hooksContent += `import type { ${context.usedTypeNames.join(', ')} } from './${this.toCamelCase(tag)}.types';\n\n`;
1019
+ }
1020
+ else {
1021
+ hooksContent += `\n`;
1022
+ }
1023
+ allParamInterfaces.forEach(interfaceCode => {
1024
+ hooksContent += interfaceCode + '\n';
1025
+ });
1026
+ allHooks.forEach(hookCode => {
1027
+ hooksContent += hookCode + '\n';
1028
+ });
1029
+ }
1030
+ }
1031
+ else {
1032
+ // Default generation if no template is provided
1033
+ hooksContent = `// ${this.toPascalCase(tag)} API Hooks\n`;
1034
+ hooksContent += `import { useQuery, useMutation, useQueryClient } from 'react-query';\n`;
1035
+ hooksContent += `import axios from 'axios';\n`;
1036
+ if (context.hasImportTypes) {
1037
+ hooksContent += `import type { ${context.usedTypeNames.join(', ')} } from './${this.toCamelCase(tag)}.types';\n\n`;
1038
+ }
1039
+ else {
1040
+ hooksContent += `\n`;
1041
+ }
1042
+ allParamInterfaces.forEach(interfaceCode => {
1043
+ hooksContent += interfaceCode + '\n';
1044
+ });
1045
+ allHooks.forEach(hookCode => {
1046
+ hooksContent += hookCode + '\n';
1047
+ });
1048
+ }
1049
+ resourcesByTag.set(tag, {
1050
+ hooks: hooksContent,
1051
+ types: typesContent
1052
+ });
1053
+ });
1054
+ return resourcesByTag;
1055
+ }
783
1056
  }
784
1057
  exports.SwaggerDocGenerator = SwaggerDocGenerator;
785
1058
  //# sourceMappingURL=index.js.map