typedapi-client-helpers 0.1.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.
@@ -0,0 +1,740 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import prettier from "prettier";
5
+ import { generateApi } from "swagger-typescript-api";
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+
10
+ const runtimePackageJsonPath = path.resolve(__dirname, "../package.json");
11
+ const runtimePackageJson = JSON.parse(
12
+ fs.readFileSync(runtimePackageJsonPath, "utf8"),
13
+ );
14
+
15
+ const consumerPackageJsonPath = path.resolve(process.cwd(), "package.json");
16
+ const consumerPackageJson = fs.existsSync(consumerPackageJsonPath)
17
+ ? JSON.parse(fs.readFileSync(consumerPackageJsonPath, "utf8"))
18
+ : {};
19
+
20
+ const isRunningInsidePackage =
21
+ path.resolve(process.cwd()) === path.resolve(__dirname, "..");
22
+
23
+ const runtimePackageName = isRunningInsidePackage
24
+ ? "../.."
25
+ : runtimePackageJson.name;
26
+
27
+ const cwd = process.cwd();
28
+
29
+ const config = {
30
+ ...(runtimePackageJson.config ?? {}),
31
+ ...(consumerPackageJson.config ?? {}),
32
+ };
33
+
34
+ const swaggerFile =
35
+ process.env.SWAGGER_FILE ??
36
+ process.env.npm_config_swagger_file ??
37
+ config.swaggerFile;
38
+
39
+ const swaggerUrl =
40
+ process.env.SWAGGER_URL ??
41
+ process.env.npm_config_swagger_url ??
42
+ config.swaggerUrl ??
43
+ "https://localhost:7000/swagger/v1/swagger.json";
44
+
45
+ const swaggerInput = swaggerFile ? path.resolve(cwd, swaggerFile) : swaggerUrl;
46
+
47
+ const apiRoot = path.resolve(
48
+ cwd,
49
+ process.env.API_OUTPUT ??
50
+ process.env.npm_config_api_output ??
51
+ config.apiOutput ??
52
+ "src/api",
53
+ );
54
+ const generatedDir = path.join(apiRoot, "generated");
55
+ const methodsDir = path.join(apiRoot, "methods");
56
+
57
+ const useTypeOnlyImports =
58
+ process.env.TYPED_API_USE_TYPE_ONLY_IMPORTS === "true" ||
59
+ process.env.npm_config_typed_api_use_type_only_imports === "true" ||
60
+ config.typedApiUseTypeOnlyImports === true;
61
+
62
+ const useFilterFormValues =
63
+ process.env.TYPED_API_USE_FILTER_FORM_VALUES === "true" ||
64
+ process.env.npm_config_typed_api_use_filter_form_values === "true" ||
65
+ config.typedApiUseFilterFormValues === true;
66
+
67
+ function pascalCase(value) {
68
+ return value
69
+ .split(/[-_]/)
70
+ .filter(Boolean)
71
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
72
+ .join("");
73
+ }
74
+
75
+ function toQueryName(methodName) {
76
+ return `${pascalCase(methodName)}Query`;
77
+ }
78
+
79
+ function cleanDirectory(directory) {
80
+ if (fs.existsSync(directory)) {
81
+ fs.rmSync(directory, { recursive: true, force: true });
82
+ }
83
+
84
+ fs.mkdirSync(directory, { recursive: true });
85
+ }
86
+
87
+ async function writeFormattedFile(filePath, content) {
88
+ const formatted = await prettier.format(content, {
89
+ parser: "typescript",
90
+ });
91
+
92
+ fs.writeFileSync(filePath, formatted);
93
+ }
94
+
95
+ async function generateOpenApiClient() {
96
+ cleanDirectory(generatedDir);
97
+ cleanDirectory(methodsDir);
98
+ fs.mkdirSync(methodsDir, { recursive: true });
99
+
100
+ const generateOptions = {
101
+ name: "Api.ts",
102
+ output: generatedDir,
103
+ httpClientType: "fetch",
104
+ modular: true,
105
+ cleanOutput: true,
106
+ generateClient: true,
107
+ generateRouteTypes: true,
108
+ extractRequestParams: true,
109
+ extractRequestBody: true,
110
+ moduleNameFirstTag: true,
111
+ };
112
+
113
+ if (swaggerFile) {
114
+ generateOptions.input = swaggerInput;
115
+ } else {
116
+ generateOptions.url = swaggerInput;
117
+ }
118
+
119
+ await generateApi(generateOptions);
120
+ }
121
+
122
+ function createImportStatement({ names, from, typeOnly = false }) {
123
+ if (!names || names.length === 0) {
124
+ return "";
125
+ }
126
+
127
+ const importKeyword = typeOnly ? "import type" : "import";
128
+
129
+ return `${importKeyword} {
130
+ ${names.map((name) => ` ${name},`).join("\n")}
131
+ } from "${from}";`;
132
+ }
133
+
134
+ async function createTypesFile() {
135
+ const httpClientImport = useTypeOnlyImports
136
+ ? `import type { HttpResponse, RequestParams } from "../generated/http-client";`
137
+ : `import { HttpResponse, RequestParams } from "../generated/http-client";`;
138
+
139
+ const content = `
140
+ ${httpClientImport}
141
+
142
+ export type ExtractResponse<T> =
143
+ T extends Promise<HttpResponse<infer R, any>> ? R : never;
144
+
145
+ export type UnwrapArray<T> = T extends (infer U)[] ? U : T;
146
+
147
+ export type ExtractDataIfPaginated<T> =
148
+ T extends { data?: (infer U)[] | null } ? U : T;
149
+
150
+ export type SortableKeys<T> = keyof UnwrapArray<ExtractDataIfPaginated<T>>;
151
+
152
+ export type WithoutRequestParams<T extends any[]> =
153
+ T extends [infer First, ...infer Rest]
154
+ ? First extends RequestParams
155
+ ? Rest
156
+ : [First, ...WithoutRequestParams<Rest>]
157
+ : [];
158
+ `.trim();
159
+
160
+ await writeFormattedFile(path.join(methodsDir, "Types.ts"), content);
161
+ }
162
+
163
+ function getAllTsFiles(dir) {
164
+ let results = [];
165
+
166
+ if (!fs.existsSync(dir)) {
167
+ return results;
168
+ }
169
+
170
+ const list = fs.readdirSync(dir);
171
+
172
+ for (const file of list) {
173
+ const filePath = path.join(dir, file);
174
+ const stat = fs.statSync(filePath);
175
+
176
+ if (stat.isDirectory()) {
177
+ results = results.concat(getAllTsFiles(filePath));
178
+ } else if (filePath.endsWith(".ts")) {
179
+ results.push(filePath);
180
+ }
181
+ }
182
+
183
+ return results;
184
+ }
185
+
186
+ async function convertGeneratedUploadMethods(apiDir) {
187
+ const tsFiles = getAllTsFiles(apiDir);
188
+ let changedMethods = 0;
189
+
190
+ console.log(
191
+ `Checking ${tsFiles.length} generated TypeScript files for multipart methods...`,
192
+ );
193
+
194
+ for (const filePath of tsFiles) {
195
+ if (
196
+ filePath.endsWith("data-contracts.ts") ||
197
+ filePath.endsWith("http-client.ts")
198
+ ) {
199
+ continue;
200
+ }
201
+
202
+ const originalContent = fs.readFileSync(filePath, "utf8");
203
+
204
+ const methodRegex =
205
+ /(\b\w+\s*=\s*\(\s*data\s*:\s*)([^,\n]+)(,\s*params\s*:\s*RequestParams\s*=\s*\{\}\s*,?\s*\)\s*=>\s*this\.request<[\s\S]*?type:\s*ContentType\.FormData,[\s\S]*?\}\);)/g;
206
+
207
+ let fileChanges = 0;
208
+
209
+ const updatedContent = originalContent.replace(
210
+ methodRegex,
211
+ (_match, start, currentType, rest) => {
212
+ console.log(
213
+ ` Replacing multipart payload type "${currentType.trim()}" with FormData`,
214
+ );
215
+
216
+ fileChanges++;
217
+ changedMethods++;
218
+
219
+ return `${start}FormData${rest}`;
220
+ },
221
+ );
222
+
223
+ if (fileChanges > 0) {
224
+ await writeFormattedFile(filePath, updatedContent);
225
+
226
+ console.log(
227
+ `Updated ${fileChanges} multipart method(s) in ${path.relative(
228
+ process.cwd(),
229
+ filePath,
230
+ )}`,
231
+ );
232
+ }
233
+ }
234
+
235
+ if (changedMethods === 0) {
236
+ throw new Error(
237
+ "No multipart methods were converted. Check the generated method format and output directory.",
238
+ );
239
+ }
240
+
241
+ console.log(`Converted ${changedMethods} multipart method(s) to FormData.`);
242
+ }
243
+
244
+ function isMultipartMethod(source, methodName) {
245
+ const startPattern = new RegExp(`\\b${methodName}\\s*=\\s*\\(`);
246
+
247
+ const startMatch = startPattern.exec(source);
248
+
249
+ if (!startMatch) {
250
+ return false;
251
+ }
252
+
253
+ const nextMethodPattern = /^\s*\w+\s*=\s*\(/gm;
254
+ nextMethodPattern.lastIndex = startMatch.index + startMatch[0].length;
255
+
256
+ const nextMatch = nextMethodPattern.exec(source);
257
+ const endIndex = nextMatch?.index ?? source.length;
258
+
259
+ const methodSource = source.slice(startMatch.index, endIndex);
260
+
261
+ return methodSource.includes("type: ContentType.FormData");
262
+ }
263
+
264
+ function getGeneratedMethods(source) {
265
+ const methodRegex = /^\s*(\w+)\s*=\s*\(/gm;
266
+ const matches = [...source.matchAll(methodRegex)];
267
+
268
+ return matches.map((match, index) => {
269
+ const methodName = match[1];
270
+ const startIndex = match.index ?? 0;
271
+ const endIndex =
272
+ index + 1 < matches.length ? matches[index + 1].index : source.length;
273
+
274
+ const methodSource = source.slice(startIndex, endIndex);
275
+
276
+ return {
277
+ methodName,
278
+ methodSource,
279
+ isFormData: methodSource.includes("type: ContentType.FormData"),
280
+ };
281
+ });
282
+ }
283
+
284
+ async function createMethodFiles() {
285
+ const generatedFiles = fs
286
+ .readdirSync(generatedDir)
287
+ .filter(
288
+ (file) =>
289
+ file.endsWith(".ts") &&
290
+ file !== "http-client.ts" &&
291
+ file !== "data-contracts.ts" &&
292
+ !file.endsWith("Route.ts") &&
293
+ !file.includes(".api"),
294
+ );
295
+
296
+ const methodExports = [];
297
+
298
+ for (const file of generatedFiles) {
299
+ const filePath = path.join(generatedDir, file);
300
+ const source = fs.readFileSync(filePath, "utf8");
301
+
302
+ const baseName = file.replace(".ts", "");
303
+ const className = baseName.split("-").map(pascalCase).join("");
304
+
305
+ const instanceName = `${className.charAt(0).toLowerCase()}${className.slice(1)}Api`;
306
+
307
+ const paginatedMethods = [];
308
+ const simpleQueryMethods = [];
309
+ const nonQueryMethods = [];
310
+
311
+ const generatedMethods = getGeneratedMethods(source);
312
+
313
+ for (const generatedMethod of generatedMethods) {
314
+ const { methodName, methodSource, isFormData } = generatedMethod;
315
+
316
+ const signatureRegex = new RegExp(
317
+ `${methodName}\\s*=\\s*\\(([^)]*)\\)\\s*=>`,
318
+ "m",
319
+ );
320
+
321
+ const signatureMatch = methodSource.match(signatureRegex);
322
+
323
+ if (!signatureMatch) {
324
+ continue;
325
+ }
326
+
327
+ const params = signatureMatch[1].trim();
328
+
329
+ const returnTypeMatch = methodSource.match(/this\.request<([^,>]+)/m);
330
+
331
+ const returnType = returnTypeMatch?.[1] ?? "";
332
+
333
+ const isPaginated =
334
+ returnType.includes("ApiPaginationResponse") ||
335
+ returnType.trim().endsWith("[]");
336
+
337
+ if (params.startsWith("query")) {
338
+ if (isPaginated) {
339
+ paginatedMethods.push(methodName);
340
+ } else {
341
+ simpleQueryMethods.push(methodName);
342
+ }
343
+
344
+ continue;
345
+ }
346
+
347
+ nonQueryMethods.push({
348
+ methodName,
349
+ isFormData,
350
+ });
351
+ }
352
+
353
+ const queryTypes = [...paginatedMethods, ...simpleQueryMethods]
354
+ .map((methodName) =>
355
+ `
356
+ export type ${toQueryName(methodName)} =
357
+ NonNullable<
358
+ Parameters<${className}["${methodName}"]>[0]
359
+ >;
360
+ `.trim(),
361
+ )
362
+ .join("\n\n");
363
+
364
+ const hasPaginatedMethods = paginatedMethods.length > 0;
365
+
366
+ const hasNonQueryMethods = nonQueryMethods.length > 0;
367
+
368
+ const hasFormDataMethods = nonQueryMethods.some(
369
+ ({ isFormData }) => isFormData,
370
+ );
371
+
372
+ const hasRegularNonQueryMethods = nonQueryMethods.some(
373
+ ({ isFormData }) => !isFormData,
374
+ );
375
+
376
+ const paginatedMethodWrappers = paginatedMethods
377
+ .map((methodName) => {
378
+ if (useFilterFormValues) {
379
+ return `
380
+ export async function ${methodName}(
381
+ filters: FilterFormValues<${toQueryName(methodName)}>[] = [],
382
+ page = 1,
383
+ pageSize = 100,
384
+ sortBy: SortableKeys<
385
+ ExtractResponse<ReturnType<${className}["${methodName}"]>>
386
+ > | null = null,
387
+ sortDirection?: SortDirection,
388
+ toastOptions?: ToastOptions
389
+ ): Promise<ApiResult<ExtractResponse<ReturnType<${className}["${methodName}"]>>>> {
390
+ return handleApiResponse(
391
+ () =>
392
+ ${instanceName}.${methodName}(
393
+ buildQuery<
394
+ ${toQueryName(methodName)},
395
+ UnwrapArray<
396
+ ExtractDataIfPaginated<
397
+ ExtractResponse<ReturnType<${className}["${methodName}"]>>
398
+ >
399
+ >
400
+ >(filters, page, pageSize, sortBy, sortDirection)
401
+ ),
402
+ toastOptions
403
+ );
404
+ }
405
+ `.trim();
406
+ }
407
+
408
+ return `
409
+ export async function ${methodName}(
410
+ query?: ${toQueryName(methodName)},
411
+ toastOptions?: ToastOptions
412
+ ): Promise<ApiResult<ExtractResponse<ReturnType<${className}["${methodName}"]>>>> {
413
+ return handleApiResponse(
414
+ () => ${instanceName}.${methodName}(query),
415
+ toastOptions
416
+ );
417
+ }
418
+ `.trim();
419
+ })
420
+ .join("\n\n");
421
+
422
+ const simpleQueryMethodWrappers = simpleQueryMethods
423
+ .map((methodName) =>
424
+ `
425
+ export async function ${methodName}(
426
+ query?: ${toQueryName(methodName)},
427
+ toastOptions?: ToastOptions
428
+ ): Promise<
429
+ ApiResult<
430
+ ExtractResponse<
431
+ ReturnType<
432
+ ${className}["${methodName}"]
433
+ >
434
+ >
435
+ >
436
+ > {
437
+ return handleApiResponse(
438
+ () =>
439
+ ${instanceName}.${methodName}(query),
440
+ toastOptions
441
+ );
442
+ }
443
+ `.trim(),
444
+ )
445
+ .join("\n\n");
446
+
447
+ const nonQueryMethodWrappers = nonQueryMethods
448
+ .map(({ methodName, isFormData }) => {
449
+ if (isFormData) {
450
+ return `
451
+ export async function ${methodName}(
452
+ data: Parameters<
453
+ ${className}["${methodName}"]
454
+ >[0],
455
+ toastOptions?: ToastOptions,
456
+ params?: RequestParams
457
+ ): Promise<
458
+ ApiResult<
459
+ ExtractResponse<
460
+ ReturnType<
461
+ ${className}["${methodName}"]
462
+ >
463
+ >
464
+ >
465
+ > {
466
+ const formData = toFormData(data);
467
+
468
+ return handleApiResponse(
469
+ () =>
470
+ ${instanceName}.${methodName}(
471
+ formData as unknown as Parameters<
472
+ ${className}["${methodName}"]
473
+ >[0],
474
+ params ?? {}
475
+ ),
476
+ toastOptions
477
+ );
478
+ }
479
+ `.trim();
480
+ }
481
+
482
+ return `
483
+ export async function ${methodName}(
484
+ ...argsWithToast: [
485
+ ...WithoutRequestParams<
486
+ Parameters<
487
+ ${className}["${methodName}"]
488
+ >
489
+ >,
490
+ ToastOptions?,
491
+ RequestParams?
492
+ ]
493
+ ): Promise<
494
+ ApiResult<
495
+ ExtractResponse<
496
+ ReturnType<
497
+ ${className}["${methodName}"]
498
+ >
499
+ >
500
+ >
501
+ > {
502
+ const {
503
+ args,
504
+ toastOptions,
505
+ params
506
+ } = extractArgsToastsAndParams<
507
+ WithoutRequestParams<
508
+ Parameters<
509
+ ${className}["${methodName}"]
510
+ >
511
+ >
512
+ >(argsWithToast);
513
+
514
+ const requestArgs = [
515
+ ...args,
516
+ params ?? {}
517
+ ] as unknown as Parameters<
518
+ ${className}["${methodName}"]
519
+ >;
520
+
521
+ return handleApiResponse(
522
+ () =>
523
+ ${instanceName}.${methodName}(
524
+ ...requestArgs
525
+ ),
526
+ toastOptions
527
+ );
528
+ }
529
+ `.trim();
530
+ })
531
+ .join("\n\n");
532
+
533
+ const runtimeValueImportNames = [
534
+ ...(hasPaginatedMethods && useFilterFormValues ? ["buildQuery"] : []),
535
+ ...(hasRegularNonQueryMethods ? ["extractArgsToastsAndParams"] : []),
536
+ ...(hasFormDataMethods ? ["toFormData"] : []),
537
+ "handleApiResponse",
538
+ ];
539
+
540
+ const runtimeTypeImportNames = [
541
+ "ApiResult",
542
+ ...(hasPaginatedMethods && useFilterFormValues
543
+ ? ["FilterFormValues", "SortDirection"]
544
+ : []),
545
+ "ToastOptions",
546
+ ];
547
+
548
+ const localTypeImportNames = [
549
+ ...(hasPaginatedMethods && useFilterFormValues
550
+ ? ["ExtractDataIfPaginated", "SortableKeys", "UnwrapArray"]
551
+ : []),
552
+ "ExtractResponse",
553
+ ...(hasRegularNonQueryMethods ? ["WithoutRequestParams"] : []),
554
+ ];
555
+
556
+ const runtimeValueImports = createImportStatement({
557
+ names: runtimeValueImportNames,
558
+ from: runtimePackageName,
559
+ });
560
+
561
+ const runtimeTypeImports = createImportStatement({
562
+ names: runtimeTypeImportNames,
563
+ from: runtimePackageName,
564
+ typeOnly: useTypeOnlyImports,
565
+ });
566
+
567
+ const generatedValueImports = createImportStatement({
568
+ names: [className],
569
+ from: `../generated/${baseName}`,
570
+ });
571
+
572
+ const generatedTypeImports = hasNonQueryMethods
573
+ ? createImportStatement({
574
+ names: ["RequestParams"],
575
+ from: "../generated/http-client",
576
+ typeOnly: useTypeOnlyImports,
577
+ })
578
+ : "";
579
+
580
+ const localTypeImports = createImportStatement({
581
+ names: localTypeImportNames,
582
+ from: "./Types",
583
+ typeOnly: useTypeOnlyImports,
584
+ });
585
+
586
+ const content = `
587
+ ${runtimeValueImports}
588
+
589
+ ${runtimeTypeImports}
590
+
591
+ ${generatedValueImports}
592
+
593
+ ${generatedTypeImports}
594
+
595
+ ${localTypeImports}
596
+
597
+ /* =======================
598
+ Query Types
599
+ ======================= */
600
+ ${queryTypes}
601
+
602
+ /* =======================
603
+ API Instance
604
+ ======================= */
605
+ const ${instanceName} = new ${className}();
606
+
607
+ /* =======================
608
+ Paginated Query Methods
609
+ ======================= */
610
+ ${paginatedMethodWrappers}
611
+
612
+ /* =======================
613
+ Simple Query Methods
614
+ ======================= */
615
+ ${simpleQueryMethodWrappers}
616
+
617
+ /* =======================
618
+ Non-Query Methods
619
+ ======================= */
620
+ ${nonQueryMethodWrappers}
621
+ `.trim();
622
+
623
+ const outputFile = path.join(methodsDir, `${className}.api.ts`);
624
+
625
+ await writeFormattedFile(outputFile, content);
626
+
627
+ methodExports.push(`export * from "./methods/${className}.api";`);
628
+ }
629
+
630
+ const apiIndexContent = `
631
+ export * from "./generated/data-contracts";
632
+ export * from "./generated/http-client";
633
+ export * from "./methods/Types";
634
+
635
+ ${methodExports.join("\n")}
636
+ `.trim();
637
+
638
+ await writeFormattedFile(path.join(apiRoot, "index.ts"), apiIndexContent);
639
+ }
640
+
641
+ async function fixGeneratedMixedImports(apiDir) {
642
+ const tsFiles = getAllTsFiles(apiDir);
643
+
644
+ for (const filePath of tsFiles) {
645
+ if (
646
+ filePath.endsWith("http-client.ts") ||
647
+ filePath.endsWith("data-contracts.ts")
648
+ ) {
649
+ continue;
650
+ }
651
+
652
+ const originalContent = fs.readFileSync(filePath, "utf8");
653
+ let content = originalContent;
654
+
655
+ /*
656
+ * Converts:
657
+ *
658
+ * import { ContentType, HttpClient, RequestParams }
659
+ * from "./http-client";
660
+ *
661
+ * into separate runtime and type imports.
662
+ */
663
+ content = content.replace(
664
+ /import\s*\{([\s\S]*?)\}\s*from\s*["']\.\/http-client["'];/g,
665
+ (_match, importsText) => {
666
+ const importedNames = importsText
667
+ .split(",")
668
+ .map((name) => name.trim())
669
+ .filter(Boolean);
670
+
671
+ const typeNames = importedNames.filter(
672
+ (name) => name === "RequestParams",
673
+ );
674
+
675
+ const valueNames = importedNames.filter(
676
+ (name) => name !== "RequestParams",
677
+ );
678
+
679
+ const statements = [];
680
+
681
+ if (valueNames.length > 0) {
682
+ statements.push(
683
+ `import { ${valueNames.join(", ")} } from "./http-client";`,
684
+ );
685
+ }
686
+
687
+ if (typeNames.length > 0 && useTypeOnlyImports) {
688
+ statements.push(
689
+ `import type { ${typeNames.join(", ")} } from "./http-client";`,
690
+ );
691
+ } else if (typeNames.length > 0) {
692
+ statements.push(
693
+ `import { ${typeNames.join(", ")} } from "./http-client";`,
694
+ );
695
+ }
696
+
697
+ return statements.join("\n");
698
+ },
699
+ );
700
+
701
+ /*
702
+ * Everything imported from data-contracts by generated API classes
703
+ * is normally used as a TypeScript type.
704
+ */
705
+ if (useTypeOnlyImports) {
706
+ content = content.replace(
707
+ /import\s*\{([\s\S]*?)\}\s*from\s*["']\.\/data-contracts["'];/g,
708
+ (_match, importsText) =>
709
+ `import type {${importsText}} from "./data-contracts";`,
710
+ );
711
+ }
712
+
713
+ if (content !== originalContent) {
714
+ await writeFormattedFile(filePath, content);
715
+
716
+ console.log(
717
+ `Fixed mixed imports in ${path.relative(process.cwd(), filePath)}`,
718
+ );
719
+ }
720
+ }
721
+ }
722
+
723
+ async function main() {
724
+ console.log(`Generating API from: ${swaggerInput}`);
725
+ console.log(`Output folder: ${apiRoot}`);
726
+
727
+ await generateOpenApiClient();
728
+
729
+ await fixGeneratedMixedImports(generatedDir);
730
+
731
+ await createTypesFile();
732
+ await createMethodFiles();
733
+
734
+ console.log("API generation completed.");
735
+ }
736
+
737
+ main().catch((error) => {
738
+ console.error(error);
739
+ process.exit(1);
740
+ });