typedapi-client-helpers 0.1.15 → 0.2.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.
Files changed (137) hide show
  1. package/README.md +220 -490
  2. package/dist/index.cjs +274 -47
  3. package/dist/index.d.cts +79 -95
  4. package/dist/index.d.ts +79 -95
  5. package/dist/index.js +267 -47
  6. package/package.json +54 -12
  7. package/scripts/generate-api-minimal.mjs +476 -959
  8. package/scripts/openapi-generator/controller-template.mjs +246 -0
  9. package/scripts/openapi-generator/http-client-template.mjs +34 -0
  10. package/scripts/openapi-generator/index.mjs +113 -0
  11. package/scripts/openapi-generator/method-template.mjs +440 -0
  12. package/scripts/openapi-generator/names.mjs +145 -0
  13. package/scripts/openapi-generator/openapi-utils.mjs +147 -0
  14. package/scripts/openapi-generator/schema.mjs +455 -0
  15. package/bin/typedapi-generate.js +0 -3
  16. package/dist/api/generated/Import.d.ts +0 -22
  17. package/dist/api/generated/Import.d.ts.map +0 -1
  18. package/dist/api/generated/Import.js +0 -48
  19. package/dist/api/generated/Import.js.map +0 -1
  20. package/dist/api/generated/ImportRoute.d.ts +0 -30
  21. package/dist/api/generated/ImportRoute.d.ts.map +0 -1
  22. package/dist/api/generated/ImportRoute.js +0 -13
  23. package/dist/api/generated/ImportRoute.js.map +0 -1
  24. package/dist/api/generated/Order.d.ts +0 -38
  25. package/dist/api/generated/Order.d.ts.map +0 -1
  26. package/dist/api/generated/Order.js +0 -75
  27. package/dist/api/generated/Order.js.map +0 -1
  28. package/dist/api/generated/OrderRoute.d.ts +0 -83
  29. package/dist/api/generated/OrderRoute.d.ts.map +0 -1
  30. package/dist/api/generated/OrderRoute.js +0 -13
  31. package/dist/api/generated/OrderRoute.js.map +0 -1
  32. package/dist/api/generated/Product.d.ts +0 -62
  33. package/dist/api/generated/Product.d.ts.map +0 -1
  34. package/dist/api/generated/Product.js +0 -114
  35. package/dist/api/generated/Product.js.map +0 -1
  36. package/dist/api/generated/ProductRoute.d.ts +0 -140
  37. package/dist/api/generated/ProductRoute.d.ts.map +0 -1
  38. package/dist/api/generated/ProductRoute.js +0 -13
  39. package/dist/api/generated/ProductRoute.js.map +0 -1
  40. package/dist/api/generated/Report.d.ts +0 -14
  41. package/dist/api/generated/Report.d.ts.map +0 -1
  42. package/dist/api/generated/Report.js +0 -32
  43. package/dist/api/generated/Report.js.map +0 -1
  44. package/dist/api/generated/ReportRoute.d.ts +0 -23
  45. package/dist/api/generated/ReportRoute.d.ts.map +0 -1
  46. package/dist/api/generated/ReportRoute.js +0 -13
  47. package/dist/api/generated/ReportRoute.js.map +0 -1
  48. package/dist/api/generated/Supplier.d.ts +0 -46
  49. package/dist/api/generated/Supplier.d.ts.map +0 -1
  50. package/dist/api/generated/Supplier.js +0 -87
  51. package/dist/api/generated/Supplier.js.map +0 -1
  52. package/dist/api/generated/SupplierRoute.d.ts +0 -101
  53. package/dist/api/generated/SupplierRoute.d.ts.map +0 -1
  54. package/dist/api/generated/SupplierRoute.js +0 -13
  55. package/dist/api/generated/SupplierRoute.js.map +0 -1
  56. package/dist/api/generated/data-contracts.d.ts +0 -277
  57. package/dist/api/generated/data-contracts.d.ts.map +0 -1
  58. package/dist/api/generated/data-contracts.js +0 -26
  59. package/dist/api/generated/data-contracts.js.map +0 -1
  60. package/dist/api/generated/http-client.d.ts +0 -61
  61. package/dist/api/generated/http-client.d.ts.map +0 -1
  62. package/dist/api/generated/http-client.js +0 -171
  63. package/dist/api/generated/http-client.js.map +0 -1
  64. package/dist/api/index.d.ts +0 -8
  65. package/dist/api/index.d.ts.map +0 -1
  66. package/dist/api/index.js +0 -8
  67. package/dist/api/index.js.map +0 -1
  68. package/dist/api/methods/Import.api.d.ts +0 -6
  69. package/dist/api/methods/Import.api.d.ts.map +0 -1
  70. package/dist/api/methods/Import.api.js +0 -27
  71. package/dist/api/methods/Import.api.js.map +0 -1
  72. package/dist/api/methods/Order.api.d.ts +0 -29
  73. package/dist/api/methods/Order.api.d.ts.map +0 -1
  74. package/dist/api/methods/Order.api.js +0 -52
  75. package/dist/api/methods/Order.api.js.map +0 -1
  76. package/dist/api/methods/Product.api.d.ts +0 -39
  77. package/dist/api/methods/Product.api.d.ts.map +0 -1
  78. package/dist/api/methods/Product.api.js +0 -69
  79. package/dist/api/methods/Product.api.js.map +0 -1
  80. package/dist/api/methods/Report.api.d.ts +0 -6
  81. package/dist/api/methods/Report.api.d.ts.map +0 -1
  82. package/dist/api/methods/Report.api.js +0 -19
  83. package/dist/api/methods/Report.api.js.map +0 -1
  84. package/dist/api/methods/Supplier.api.d.ts +0 -35
  85. package/dist/api/methods/Supplier.api.d.ts.map +0 -1
  86. package/dist/api/methods/Supplier.api.js +0 -63
  87. package/dist/api/methods/Supplier.api.js.map +0 -1
  88. package/dist/index.d.ts.map +0 -1
  89. package/dist/index.js.map +0 -1
  90. package/dist/interfaces/ApiResult.d.ts +0 -17
  91. package/dist/interfaces/ApiResult.d.ts.map +0 -1
  92. package/dist/interfaces/ApiResult.js +0 -2
  93. package/dist/interfaces/ApiResult.js.map +0 -1
  94. package/dist/interfaces/Filter.d.ts +0 -31
  95. package/dist/interfaces/Filter.d.ts.map +0 -1
  96. package/dist/interfaces/Filter.js +0 -2
  97. package/dist/interfaces/Filter.js.map +0 -1
  98. package/dist/interfaces/SortType.d.ts +0 -7
  99. package/dist/interfaces/SortType.d.ts.map +0 -1
  100. package/dist/interfaces/SortType.js +0 -2
  101. package/dist/interfaces/SortType.js.map +0 -1
  102. package/dist/types/ApiCallOptions.d.ts +0 -9
  103. package/dist/types/ApiCallOptions.d.ts.map +0 -1
  104. package/dist/types/ApiCallOptions.js +0 -2
  105. package/dist/types/ApiCallOptions.js.map +0 -1
  106. package/dist/types/ApiMethodTypes.d.ts +0 -24
  107. package/dist/types/ApiMethodTypes.d.ts.map +0 -1
  108. package/dist/types/ApiMethodTypes.js +0 -2
  109. package/dist/types/ApiMethodTypes.js.map +0 -1
  110. package/dist/types/HttpResponse.d.ts +0 -16
  111. package/dist/types/HttpResponse.d.ts.map +0 -1
  112. package/dist/types/HttpResponse.js +0 -2
  113. package/dist/types/HttpResponse.js.map +0 -1
  114. package/dist/utils/SortTypeFromSortDirection.d.ts +0 -22
  115. package/dist/utils/SortTypeFromSortDirection.d.ts.map +0 -1
  116. package/dist/utils/SortTypeFromSortDirection.js +0 -45
  117. package/dist/utils/SortTypeFromSortDirection.js.map +0 -1
  118. package/dist/utils/buildQuery.d.ts +0 -11
  119. package/dist/utils/buildQuery.d.ts.map +0 -1
  120. package/dist/utils/buildQuery.js +0 -94
  121. package/dist/utils/buildQuery.js.map +0 -1
  122. package/dist/utils/extractArgsCallbacksAndParams.d.ts +0 -22
  123. package/dist/utils/extractArgsCallbacksAndParams.d.ts.map +0 -1
  124. package/dist/utils/extractArgsCallbacksAndParams.js +0 -44
  125. package/dist/utils/extractArgsCallbacksAndParams.js.map +0 -1
  126. package/dist/utils/extractArgsToastsAndParams.d.ts +0 -18
  127. package/dist/utils/extractArgsToastsAndParams.d.ts.map +0 -1
  128. package/dist/utils/extractArgsToastsAndParams.js +0 -40
  129. package/dist/utils/extractArgsToastsAndParams.js.map +0 -1
  130. package/dist/utils/handleApiResponse.d.ts +0 -9
  131. package/dist/utils/handleApiResponse.d.ts.map +0 -1
  132. package/dist/utils/handleApiResponse.js +0 -49
  133. package/dist/utils/handleApiResponse.js.map +0 -1
  134. package/dist/utils/toFormData.d.ts +0 -8
  135. package/dist/utils/toFormData.d.ts.map +0 -1
  136. package/dist/utils/toFormData.js +0 -68
  137. package/dist/utils/toFormData.js.map +0 -1
@@ -1,959 +1,476 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import { fileURLToPath } from "node:url";
4
- import { generateApi } from "swagger-typescript-api";
5
-
6
- const __filename = fileURLToPath(import.meta.url);
7
- const __dirname = path.dirname(__filename);
8
-
9
- const runtimePackageJsonPath = path.resolve(__dirname, "../package.json");
10
- const runtimePackageJson = JSON.parse(
11
- fs.readFileSync(runtimePackageJsonPath, "utf8"),
12
- );
13
-
14
- const consumerPackageJsonPath = path.resolve(process.cwd(), "package.json");
15
- const consumerPackageJson = fs.existsSync(consumerPackageJsonPath)
16
- ? JSON.parse(fs.readFileSync(consumerPackageJsonPath, "utf8"))
17
- : {};
18
-
19
- const isRunningInsidePackage =
20
- path.resolve(process.cwd()) === path.resolve(__dirname, "..");
21
-
22
- const runtimePackageName = isRunningInsidePackage
23
- ? "../.."
24
- : runtimePackageJson.name;
25
-
26
- const cwd = process.cwd();
27
-
28
- const config = {
29
- ...(runtimePackageJson.config ?? {}),
30
- ...(consumerPackageJson.config ?? {}),
31
- };
32
-
33
- const swaggerFile =
34
- process.env.SWAGGER_FILE ??
35
- process.env.npm_config_swagger_file ??
36
- config.swaggerFile;
37
-
38
- const swaggerUrl =
39
- process.env.SWAGGER_URL ??
40
- process.env.npm_config_swagger_url ??
41
- config.swaggerUrl;
42
-
43
- const swaggerInput = swaggerFile
44
- ? path.resolve(cwd, swaggerFile)
45
- : (swaggerUrl ?? path.resolve(cwd, "swagger/swagger.json"));
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
-
55
- const generatedDir = path.join(apiRoot, "generated");
56
- const methodsDir = path.join(apiRoot, "methods");
57
-
58
- const useTypeOnlyImports =
59
- process.env.TYPED_API_USE_TYPE_ONLY_IMPORTS === "true" ||
60
- process.env.npm_config_typed_api_use_type_only_imports === "true" ||
61
- config.typedApiUseTypeOnlyImports === true;
62
-
63
- const useFilterFormValues =
64
- process.env.TYPED_API_USE_FILTER_FORM_VALUES === "true" ||
65
- process.env.npm_config_typed_api_use_filter_form_values === "true" ||
66
- config.typedApiUseFilterFormValues === true;
67
-
68
- function getStringSetting(...values) {
69
- for (const value of values) {
70
- if (typeof value === "string" && value.trim() !== "") {
71
- return value.trim();
72
- }
73
- }
74
-
75
- return undefined;
76
- }
77
-
78
- const fallbackDefaultFunctionsPath = "defaultApiFunctions";
79
-
80
- const defaultFunctionsPathSetting = getStringSetting(
81
- process.env.TYPED_API_DEFAULT_FUNCTIONS_PATH,
82
- process.env.npm_config_typed_api_default_functions_path,
83
- config.typedApiDefaultFunctionsPath,
84
- fallbackDefaultFunctionsPath,
85
- );
86
-
87
- const defaultFunctionsFilePath = path.resolve(cwd, defaultFunctionsPathSetting);
88
-
89
- const defaultSuccessHandlerName = getStringSetting(
90
- process.env.TYPED_API_DEFAULT_SUCCESS_HANDLER,
91
- process.env.npm_config_typed_api_default_success_handler,
92
- config.typedApiDefaultSuccessHandler,
93
- "handleGoodResult",
94
- );
95
-
96
- const defaultErrorHandlerName = getStringSetting(
97
- process.env.TYPED_API_DEFAULT_ERROR_HANDLER,
98
- process.env.npm_config_typed_api_default_error_handler,
99
- config.typedApiDefaultErrorHandler,
100
- "handleErrors",
101
- );
102
-
103
- function pascalCase(value) {
104
- return value
105
- .split(/[-_]/)
106
- .filter(Boolean)
107
- .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
108
- .join("");
109
- }
110
-
111
- function toQueryName(methodName) {
112
- return `${pascalCase(methodName)}Query`;
113
- }
114
-
115
- function cleanDirectory(directory) {
116
- if (fs.existsSync(directory)) {
117
- fs.rmSync(directory, { recursive: true, force: true });
118
- }
119
-
120
- fs.mkdirSync(directory, { recursive: true });
121
- }
122
-
123
- async function writeFormattedFile(filePath, content) {
124
- fs.writeFileSync(filePath, content);
125
- }
126
-
127
- function removeGeneratedRouteFiles() {
128
- const files = fs.readdirSync(generatedDir);
129
-
130
- for (const file of files) {
131
- if (/route\.ts$/i.test(file)) {
132
- fs.rmSync(path.join(generatedDir, file), { force: true });
133
- }
134
- }
135
- }
136
-
137
- async function generateOpenApiClient() {
138
- cleanDirectory(generatedDir);
139
- cleanDirectory(methodsDir);
140
- fs.mkdirSync(methodsDir, { recursive: true });
141
-
142
- const generateOptions = {
143
- name: "Api.ts",
144
- output: generatedDir,
145
- httpClientType: "fetch",
146
- modular: true,
147
- cleanOutput: true,
148
- generateClient: true,
149
- generateRouteTypes: true,
150
- extractRequestParams: true,
151
- extractRequestBody: true,
152
- moduleNameFirstTag: true,
153
- };
154
-
155
- if (swaggerFile) {
156
- generateOptions.input = swaggerInput;
157
- } else {
158
- generateOptions.url = swaggerInput;
159
- }
160
-
161
- await generateApi(generateOptions);
162
-
163
- removeGeneratedRouteFiles();
164
- }
165
-
166
- function createImportStatement({ names, from, typeOnly = false }) {
167
- if (!names || names.length === 0) {
168
- return "";
169
- }
170
-
171
- const importKeyword = typeOnly ? "import type" : "import";
172
-
173
- return `${importKeyword} {
174
- ${names.map((name) => ` ${name},`).join("\n")}
175
- } from "${from}";`;
176
- }
177
-
178
- function createAliasedImportStatement({ imports, from }) {
179
- if (!imports || imports.length === 0) {
180
- return "";
181
- }
182
-
183
- return `import {
184
- ${imports
185
- .map(({ name, alias }) =>
186
- alias && alias !== name ? ` ${name} as ${alias},` : ` ${name},`,
187
- )
188
- .join("\n")}
189
- } from "${from}";`;
190
- }
191
-
192
- function resolveImportFilePath(fromDirectory, importPath) {
193
- if (!importPath.startsWith(".")) {
194
- return undefined;
195
- }
196
-
197
- const basePath = path.resolve(fromDirectory, importPath);
198
- const candidates = [
199
- basePath,
200
- `${basePath}.ts`,
201
- `${basePath}.tsx`,
202
- `${basePath}.js`,
203
- `${basePath}.jsx`,
204
- path.join(basePath, "index.ts"),
205
- path.join(basePath, "index.tsx"),
206
- path.join(basePath, "index.js"),
207
- path.join(basePath, "index.jsx"),
208
- ];
209
-
210
- return candidates.find((candidate) => fs.existsSync(candidate));
211
- }
212
-
213
- function createDefaultFunctionsFileIfMissing(filePath) {
214
- const extension = path.extname(filePath);
215
- const resolvedFilePath = extension ? filePath : `${filePath}.ts`;
216
-
217
- if (fs.existsSync(resolvedFilePath)) {
218
- return resolvedFilePath;
219
- }
220
-
221
- fs.mkdirSync(path.dirname(resolvedFilePath), { recursive: true });
222
-
223
- fs.writeFileSync(
224
- resolvedFilePath,
225
- `
226
- ${createImportStatement({
227
- names: ["ApiErrorResult", "ApiSuccessResult"],
228
- from: "typedapi-client-helpers",
229
- typeOnly: true,
230
- })}
231
-
232
- export function ${defaultSuccessHandlerName}<T>(
233
- _response: ApiSuccessResult<T>,
234
- ): void | Promise<void> {
235
- // Add your default success handling here.
236
- }
237
-
238
- export function ${defaultErrorHandlerName}<T>(
239
- _error: ApiErrorResult<T>,
240
- ): void | Promise<void> {
241
- // Add your default error handling here.
242
- }
243
- `,
244
- );
245
-
246
- console.log(
247
- `Created default API handler file: ${path.relative(process.cwd(), resolvedFilePath)}`,
248
- );
249
-
250
- return resolvedFilePath;
251
- }
252
-
253
- function toRelativeImportPath(fromDirectory, filePath) {
254
- let relativePath = path.relative(fromDirectory, filePath);
255
-
256
- if (!relativePath.startsWith(".")) {
257
- relativePath = `./${relativePath}`;
258
- }
259
-
260
- relativePath = relativePath.replaceAll(path.sep, "/");
261
-
262
- return relativePath.replace(/\.(ts|tsx|js|jsx)$/, "");
263
- }
264
-
265
- function escapeRegExp(value) {
266
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
267
- }
268
-
269
- function fileExportsName(source, exportName) {
270
- const escapedName = escapeRegExp(exportName);
271
-
272
- const directExportPattern = new RegExp(
273
- `export\\s+(?:async\\s+)?(?:function|const|let|var|class)\\s+${escapedName}\\b`,
274
- "m",
275
- );
276
-
277
- if (directExportPattern.test(source)) {
278
- return true;
279
- }
280
-
281
- const namedExportBlocks = source.matchAll(/export\s*\{([\s\S]*?)\}/g);
282
-
283
- for (const match of namedExportBlocks) {
284
- const exportedNames = match[1]
285
- .split(",")
286
- .map((part) => part.trim())
287
- .filter(Boolean);
288
-
289
- if (
290
- exportedNames.some((part) => {
291
- const [localName, aliasName] = part
292
- .split(/\s+as\s+/)
293
- .map((name) => name.trim());
294
-
295
- return (
296
- aliasName === exportName || (!aliasName && localName === exportName)
297
- );
298
- })
299
- ) {
300
- return true;
301
- }
302
- }
303
-
304
- return false;
305
- }
306
-
307
- function resolveDefaultHandlers(importFromDirectory) {
308
- const defaultFunctionsFile =
309
- resolveImportFilePath(importFromDirectory, defaultFunctionsFilePath) ??
310
- createDefaultFunctionsFileIfMissing(defaultFunctionsFilePath);
311
-
312
- const source = fs.readFileSync(defaultFunctionsFile, "utf8");
313
- const hasSuccessHandler = fileExportsName(source, defaultSuccessHandlerName);
314
- const hasErrorHandler = fileExportsName(source, defaultErrorHandlerName);
315
-
316
- if (!hasSuccessHandler || !hasErrorHandler) {
317
- const missingNames = [
318
- ...(!hasSuccessHandler ? [defaultSuccessHandlerName] : []),
319
- ...(!hasErrorHandler ? [defaultErrorHandlerName] : []),
320
- ];
321
-
322
- console.warn(
323
- `Default API handlers were not imported because ${path.relative(
324
- process.cwd(),
325
- defaultFunctionsFile,
326
- )} does not export: ${missingNames.join(", ")}.`,
327
- );
328
-
329
- return undefined;
330
- }
331
-
332
- return {
333
- defaultFunctionsPath: toRelativeImportPath(
334
- importFromDirectory,
335
- defaultFunctionsFile,
336
- ),
337
- defaultSuccessHandlerName,
338
- defaultErrorHandlerName,
339
- };
340
- }
341
-
342
- function callbackOptionsExpression(defaultHandlers) {
343
- if (defaultHandlers) {
344
- return `{
345
- onSuccess: onSuccess ?? typedApiDefaultSuccessHandler,
346
- onError: onError ?? typedApiDefaultErrorHandler
347
- }`;
348
- }
349
-
350
- return `{
351
- onSuccess,
352
- onError
353
- }`;
354
- }
355
-
356
- function getAllTsFiles(dir) {
357
- let results = [];
358
-
359
- if (!fs.existsSync(dir)) {
360
- return results;
361
- }
362
-
363
- const list = fs.readdirSync(dir);
364
-
365
- for (const file of list) {
366
- const filePath = path.join(dir, file);
367
- const stat = fs.statSync(filePath);
368
-
369
- if (stat.isDirectory()) {
370
- results = results.concat(getAllTsFiles(filePath));
371
- } else if (filePath.endsWith(".ts")) {
372
- results.push(filePath);
373
- }
374
- }
375
-
376
- return results;
377
- }
378
-
379
- function getGeneratedMethods(source) {
380
- const methodRegex = /^\s*(\w+)\s*=\s*\(/gm;
381
- const matches = [...source.matchAll(methodRegex)];
382
-
383
- return matches.map((match, index) => {
384
- const methodName = match[1];
385
- const startIndex = match.index ?? 0;
386
- const endIndex =
387
- index + 1 < matches.length ? matches[index + 1].index : source.length;
388
-
389
- const methodSource = source.slice(startIndex, endIndex);
390
-
391
- return {
392
- methodName,
393
- methodSource,
394
- isFormData: methodSource.includes("type: ContentType.FormData"),
395
- };
396
- });
397
- }
398
-
399
- function splitTopLevelArguments(value) {
400
- const args = [];
401
- let current = "";
402
- let depth = 0;
403
- let quote = "";
404
-
405
- for (let index = 0; index < value.length; index++) {
406
- const char = value[index];
407
- const previousChar = value[index - 1];
408
-
409
- if (quote) {
410
- current += char;
411
-
412
- if (char === quote && previousChar !== "\\") {
413
- quote = "";
414
- }
415
-
416
- continue;
417
- }
418
-
419
- if (char === '"' || char === "'" || char === "`") {
420
- quote = char;
421
- current += char;
422
- continue;
423
- }
424
-
425
- if (char === "(" || char === "{" || char === "[" || char === "<") {
426
- depth++;
427
- current += char;
428
- continue;
429
- }
430
-
431
- if (char === ")" || char === "}" || char === "]" || char === ">") {
432
- depth--;
433
- current += char;
434
- continue;
435
- }
436
-
437
- if (char === "," && depth === 0) {
438
- if (current.trim()) {
439
- args.push(current.trim());
440
- }
441
-
442
- current = "";
443
- continue;
444
- }
445
-
446
- current += char;
447
- }
448
-
449
- if (current.trim()) {
450
- args.push(current.trim());
451
- }
452
-
453
- return args;
454
- }
455
-
456
- function getGeneratedMethodArgumentCount(signatureParams) {
457
- const args = splitTopLevelArguments(signatureParams);
458
-
459
- if (args.length === 0) {
460
- return 0;
461
- }
462
-
463
- const lastArg = args[args.length - 1];
464
-
465
- if (/\bparams\s*:\s*RequestParams\b/.test(lastArg)) {
466
- return args.length - 1;
467
- }
468
-
469
- return args.length;
470
- }
471
-
472
- async function createMethodFiles() {
473
- const defaultHandlers = resolveDefaultHandlers(methodsDir);
474
-
475
- const generatedFiles = fs
476
- .readdirSync(generatedDir)
477
- .filter(
478
- (file) =>
479
- file.endsWith(".ts") &&
480
- file !== "http-client.ts" &&
481
- file !== "data-contracts.ts" &&
482
- !/route\.ts$/i.test(file) &&
483
- !file.includes(".api"),
484
- );
485
-
486
- const methodExports = [];
487
-
488
- for (const file of generatedFiles) {
489
- const filePath = path.join(generatedDir, file);
490
- const source = fs.readFileSync(filePath, "utf8");
491
-
492
- const baseName = file.replace(".ts", "");
493
- const className = baseName.split("-").map(pascalCase).join("");
494
-
495
- const instanceName = `${className.charAt(0).toLowerCase()}${className.slice(1)}Api`;
496
-
497
- const paginatedMethods = [];
498
- const simpleQueryMethods = [];
499
- const nonQueryMethods = [];
500
-
501
- const generatedMethods = getGeneratedMethods(source);
502
-
503
- for (const generatedMethod of generatedMethods) {
504
- const { methodName, methodSource, isFormData } = generatedMethod;
505
-
506
- const signatureRegex = new RegExp(
507
- `${methodName}\\s*=\\s*\\(([^)]*)\\)\\s*=>`,
508
- "m",
509
- );
510
-
511
- const signatureMatch = methodSource.match(signatureRegex);
512
-
513
- if (!signatureMatch) {
514
- continue;
515
- }
516
-
517
- const params = signatureMatch[1].trim();
518
- const methodArgumentCount = getGeneratedMethodArgumentCount(params);
519
-
520
- const returnTypeMatch = methodSource.match(/this\.request<([^,>]+)/m);
521
- const returnType = returnTypeMatch?.[1] ?? "";
522
-
523
- const isPaginated =
524
- returnType.includes("ApiPaginationResponse") ||
525
- returnType.trim().endsWith("[]");
526
-
527
- if (params.startsWith("query")) {
528
- if (isPaginated) {
529
- paginatedMethods.push(methodName);
530
- } else {
531
- simpleQueryMethods.push(methodName);
532
- }
533
-
534
- continue;
535
- }
536
-
537
- nonQueryMethods.push({
538
- methodName,
539
- isFormData,
540
- methodArgumentCount,
541
- });
542
- }
543
-
544
- const queryTypes = [...paginatedMethods, ...simpleQueryMethods]
545
- .map((methodName) =>
546
- `
547
- export type ${toQueryName(methodName)} =
548
- NonNullable<
549
- Parameters<${className}["${methodName}"]>[0]
550
- >;
551
- `.trim(),
552
- )
553
- .join("\n\n");
554
-
555
- const hasPaginatedMethods = paginatedMethods.length > 0;
556
-
557
- const hasFormDataMethods = nonQueryMethods.some(
558
- ({ isFormData }) => isFormData,
559
- );
560
-
561
- const hasRegularNonQueryMethods = nonQueryMethods.some(
562
- ({ isFormData }) => !isFormData,
563
- );
564
-
565
- const paginatedMethodWrappers = paginatedMethods
566
- .map((methodName) => {
567
- if (useFilterFormValues) {
568
- return `
569
- export async function ${methodName}(
570
- filters: FilterFormValues<${toQueryName(methodName)}>[] = [],
571
- page = 1,
572
- pageSize = 100,
573
- sortBy: SortableKeys<
574
- ExtractResponse<ReturnType<${className}["${methodName}"]>>
575
- > | null = null,
576
- sortDirection?: SortDirection,
577
- options: ApiMethodOptions<
578
- ExtractResponse<ReturnType<${className}["${methodName}"]>>,
579
- ExtractError<ReturnType<${className}["${methodName}"]>>,
580
- RequestParams
581
- > = {}
582
- ): Promise<ApiResult<ExtractResponse<ReturnType<${className}["${methodName}"]>>>> {
583
- const { onSuccess, onError, params } = options;
584
-
585
- return handleApiResponse(
586
- () =>
587
- ${instanceName}.${methodName}(
588
- buildQuery<
589
- ${toQueryName(methodName)},
590
- UnwrapArray<
591
- ExtractDataIfPaginated<
592
- ExtractResponse<ReturnType<${className}["${methodName}"]>>
593
- >
594
- >
595
- >(filters, page, pageSize, sortBy, sortDirection),
596
- params ?? {}
597
- ),
598
- ${callbackOptionsExpression(defaultHandlers)}
599
- );
600
- }
601
- `.trim();
602
- }
603
-
604
- return `
605
- export async function ${methodName}(
606
- query?: ${toQueryName(methodName)},
607
- options: ApiMethodOptions<
608
- ExtractResponse<ReturnType<${className}["${methodName}"]>>,
609
- ExtractError<ReturnType<${className}["${methodName}"]>>,
610
- RequestParams
611
- > = {}
612
- ): Promise<ApiResult<ExtractResponse<ReturnType<${className}["${methodName}"]>>>> {
613
- const { onSuccess, onError, params } = options;
614
-
615
- return handleApiResponse(
616
- () =>
617
- ${instanceName}.${methodName}(
618
- (query ?? {}) as ${toQueryName(methodName)},
619
- params ?? {}
620
- ),
621
- ${callbackOptionsExpression(defaultHandlers)}
622
- );
623
- }
624
- `.trim();
625
- })
626
- .join("\n\n");
627
-
628
- const simpleQueryMethodWrappers = simpleQueryMethods
629
- .map((methodName) =>
630
- `
631
- export async function ${methodName}(
632
- query?: ${toQueryName(methodName)},
633
- options: ApiMethodOptions<
634
- ExtractResponse<ReturnType<${className}["${methodName}"]>>,
635
- ExtractError<ReturnType<${className}["${methodName}"]>>,
636
- RequestParams
637
- > = {}
638
- ): Promise<
639
- ApiResult<
640
- ExtractResponse<
641
- ReturnType<
642
- ${className}["${methodName}"]
643
- >
644
- >
645
- >
646
- > {
647
- const { onSuccess, onError, params } = options;
648
-
649
- return handleApiResponse(
650
- () =>
651
- ${instanceName}.${methodName}(
652
- (query ?? {}) as ${toQueryName(methodName)},
653
- params ?? {}
654
- ),
655
- ${callbackOptionsExpression(defaultHandlers)}
656
- );
657
- }
658
- `.trim(),
659
- )
660
- .join("\n\n");
661
-
662
- const nonQueryMethodWrappers = nonQueryMethods
663
- .map(({ methodName, isFormData, methodArgumentCount }) => {
664
- if (isFormData) {
665
- return `
666
- export async function ${methodName}(
667
- data: Parameters<
668
- ${className}["${methodName}"]
669
- >[0],
670
- options: ApiMethodOptions<
671
- ExtractResponse<ReturnType<${className}["${methodName}"]>>,
672
- ExtractError<ReturnType<${className}["${methodName}"]>>,
673
- RequestParams
674
- > = {}
675
- ): Promise<
676
- ApiResult<
677
- ExtractResponse<
678
- ReturnType<
679
- ${className}["${methodName}"]
680
- >
681
- >
682
- >
683
- > {
684
- const { onSuccess, onError, params } = options;
685
- const formData = toFormData(data);
686
-
687
- return handleApiResponse(
688
- () =>
689
- ${instanceName}.${methodName}(
690
- formData as unknown as Parameters<
691
- ${className}["${methodName}"]
692
- >[0],
693
- params ?? {}
694
- ),
695
- ${callbackOptionsExpression(defaultHandlers)}
696
- );
697
- }
698
- `.trim();
699
- }
700
-
701
- return `
702
- export async function ${methodName}(
703
- ...argsWithOptions: [
704
- ...ApiMethodArguments<
705
- ${className}["${methodName}"],
706
- RequestParams
707
- >,
708
- ApiMethodOptions<
709
- ExtractResponse<ReturnType<${className}["${methodName}"]>>,
710
- ExtractError<ReturnType<${className}["${methodName}"]>>,
711
- RequestParams
712
- >?
713
- ]
714
- ): Promise<
715
- ApiResult<
716
- ExtractResponse<
717
- ReturnType<
718
- ${className}["${methodName}"]
719
- >
720
- >
721
- >
722
- > {
723
- const args = [...argsWithOptions] as unknown[];
724
-
725
- const options =
726
- args.length > ${methodArgumentCount}
727
- ? (args.pop() as ApiMethodOptions<
728
- ExtractResponse<ReturnType<${className}["${methodName}"]>>,
729
- ExtractError<ReturnType<${className}["${methodName}"]>>,
730
- RequestParams
731
- >)
732
- : {};
733
-
734
- const { onSuccess, onError, params } = options ?? {};
735
-
736
- const requestArgs = [
737
- ...args,
738
- params ?? {}
739
- ] as unknown as Parameters<
740
- ${className}["${methodName}"]
741
- >;
742
-
743
- return handleApiResponse(
744
- () =>
745
- ${instanceName}.${methodName}(
746
- ...requestArgs
747
- ),
748
- ${callbackOptionsExpression(defaultHandlers)}
749
- );
750
- }
751
- `.trim();
752
- })
753
- .join("\n\n");
754
-
755
- const runtimeValueImportNames = [
756
- ...(hasPaginatedMethods && useFilterFormValues ? ["buildQuery"] : []),
757
- ...(hasFormDataMethods ? ["toFormData"] : []),
758
- "handleApiResponse",
759
- ];
760
-
761
- const runtimeTypeImportNames = [
762
- "ApiResult",
763
- "ApiMethodOptions",
764
- ...(hasRegularNonQueryMethods ? ["ApiMethodArguments"] : []),
765
- "ExtractResponse",
766
- "ExtractError",
767
-
768
- ...(hasPaginatedMethods && useFilterFormValues
769
- ? [
770
- "ExtractDataIfPaginated",
771
- "FilterFormValues",
772
- "SortableKeys",
773
- "SortDirection",
774
- "UnwrapArray",
775
- ]
776
- : []),
777
- ];
778
-
779
- const runtimeValueImports = createImportStatement({
780
- names: runtimeValueImportNames,
781
- from: runtimePackageName,
782
- });
783
-
784
- const defaultFunctionImports = defaultHandlers
785
- ? createAliasedImportStatement({
786
- imports: [
787
- {
788
- name: defaultHandlers.defaultSuccessHandlerName,
789
- alias: "typedApiDefaultSuccessHandler",
790
- },
791
- {
792
- name: defaultHandlers.defaultErrorHandlerName,
793
- alias: "typedApiDefaultErrorHandler",
794
- },
795
- ],
796
- from: defaultHandlers.defaultFunctionsPath,
797
- })
798
- : "";
799
-
800
- const runtimeTypeImports = createImportStatement({
801
- names: runtimeTypeImportNames,
802
- from: runtimePackageName,
803
- typeOnly: useTypeOnlyImports,
804
- });
805
-
806
- const generatedValueImports = createImportStatement({
807
- names: [className],
808
- from: `../generated/${baseName}`,
809
- });
810
-
811
- const generatedTypeImports =
812
- generatedMethods.length > 0
813
- ? createImportStatement({
814
- names: ["RequestParams"],
815
- from: "../generated/http-client",
816
- typeOnly: useTypeOnlyImports,
817
- })
818
- : "";
819
-
820
- const content = `
821
- ${runtimeValueImports}
822
-
823
- ${defaultFunctionImports}
824
-
825
- ${runtimeTypeImports}
826
-
827
- ${generatedValueImports}
828
-
829
- ${generatedTypeImports}
830
-
831
- /* =======================
832
- Query Types
833
- ======================= */
834
- ${queryTypes}
835
-
836
- /* =======================
837
- API Instance
838
- ======================= */
839
- const ${instanceName} = new ${className}();
840
-
841
- /* =======================
842
- Paginated Query Methods
843
- ======================= */
844
- ${paginatedMethodWrappers}
845
-
846
- /* =======================
847
- Simple Query Methods
848
- ======================= */
849
- ${simpleQueryMethodWrappers}
850
-
851
- /* =======================
852
- Non-Query Methods
853
- ======================= */
854
- ${nonQueryMethodWrappers}
855
- `.trim();
856
-
857
- const outputFile = path.join(methodsDir, `${className}.api.ts`);
858
-
859
- await writeFormattedFile(outputFile, content);
860
-
861
- methodExports.push(`export * from "./methods/${className}.api";`);
862
- }
863
-
864
- const apiIndexContent = `
865
- export * from "./generated/data-contracts";
866
- export * from "./generated/http-client";
867
- ${methodExports.join("\n")}
868
- `.trim();
869
-
870
- await writeFormattedFile(path.join(apiRoot, "index.ts"), apiIndexContent);
871
- }
872
-
873
- async function fixGeneratedMixedImports(apiDir) {
874
- const tsFiles = getAllTsFiles(apiDir);
875
-
876
- for (const filePath of tsFiles) {
877
- if (
878
- filePath.endsWith("http-client.ts") ||
879
- filePath.endsWith("data-contracts.ts")
880
- ) {
881
- continue;
882
- }
883
-
884
- const originalContent = fs.readFileSync(filePath, "utf8");
885
- let content = originalContent;
886
-
887
- content = content.replace(
888
- /import\s*\{([\s\S]*?)\}\s*from\s*["']\.\/http-client["'];/g,
889
- (_match, importsText) => {
890
- const importedNames = importsText
891
- .split(",")
892
- .map((name) => name.trim())
893
- .filter(Boolean);
894
-
895
- const typeNames = importedNames.filter(
896
- (name) => name === "RequestParams",
897
- );
898
-
899
- const valueNames = importedNames.filter(
900
- (name) => name !== "RequestParams",
901
- );
902
-
903
- const statements = [];
904
-
905
- if (valueNames.length > 0) {
906
- statements.push(
907
- `import { ${valueNames.join(", ")} } from "./http-client";`,
908
- );
909
- }
910
-
911
- if (typeNames.length > 0 && useTypeOnlyImports) {
912
- statements.push(
913
- `import type { ${typeNames.join(", ")} } from "./http-client";`,
914
- );
915
- } else if (typeNames.length > 0) {
916
- statements.push(
917
- `import { ${typeNames.join(", ")} } from "./http-client";`,
918
- );
919
- }
920
-
921
- return statements.join("\n");
922
- },
923
- );
924
-
925
- if (useTypeOnlyImports) {
926
- content = content.replace(
927
- /import\s*\{([\s\S]*?)\}\s*from\s*["']\.\/data-contracts["'];/g,
928
- (_match, importsText) =>
929
- `import type {${importsText}} from "./data-contracts";`,
930
- );
931
- }
932
-
933
- if (content !== originalContent) {
934
- await writeFormattedFile(filePath, content);
935
-
936
- console.log(
937
- `Fixed mixed imports in ${path.relative(process.cwd(), filePath)}`,
938
- );
939
- }
940
- }
941
- }
942
-
943
- async function main() {
944
- console.log(`Generating API from: ${swaggerInput}`);
945
- console.log(`Output folder: ${apiRoot}`);
946
-
947
- await generateOpenApiClient();
948
-
949
- await fixGeneratedMixedImports(generatedDir);
950
-
951
- await createMethodFiles();
952
-
953
- console.log("API generation completed.");
954
- }
955
-
956
- main().catch((error) => {
957
- console.error(error);
958
- process.exit(1);
959
- });
1
+ #!/usr/bin/env node
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { generateApi } from "./openapi-generator/index.mjs";
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+
10
+ const packageRoot = path.resolve(__dirname, "..");
11
+ const runtimePackageJsonPath = path.join(packageRoot, "package.json");
12
+ const runtimePackageJson = JSON.parse(
13
+ fs.readFileSync(runtimePackageJsonPath, "utf8"),
14
+ );
15
+
16
+ const consumerPackageJsonPath = path.resolve(process.cwd(), "package.json");
17
+ const consumerPackageJson = fs.existsSync(consumerPackageJsonPath)
18
+ ? JSON.parse(fs.readFileSync(consumerPackageJsonPath, "utf8"))
19
+ : {};
20
+
21
+ const cwd = process.cwd();
22
+ const config = {
23
+ ...(runtimePackageJson.config ?? {}),
24
+ ...(consumerPackageJson.config ?? {}),
25
+ };
26
+
27
+ const runtimePackageName =
28
+ path.resolve(cwd) === packageRoot ? "../.." : runtimePackageJson.name;
29
+
30
+ const swaggerFile = getStringSetting(
31
+ process.env.SWAGGER_FILE,
32
+ process.env.npm_config_swagger_file,
33
+ config.swaggerFile,
34
+ config.input,
35
+ );
36
+
37
+ const swaggerUrl = getStringSetting(
38
+ process.env.SWAGGER_URL,
39
+ process.env.npm_config_swagger_url,
40
+ config.swaggerUrl,
41
+ config.url,
42
+ );
43
+
44
+ const defaultSwaggerUrl = getStringSetting(
45
+ config.defaultSwaggerUrl,
46
+ "https://localhost:7000/swagger/v1/swagger.json",
47
+ );
48
+
49
+ const swaggerBackupPath = path.resolve(
50
+ cwd,
51
+ getStringSetting(
52
+ process.env.TYPED_API_SWAGGER_BACKUP_FILE,
53
+ process.env.npm_config_typed_api_swagger_backup_file,
54
+ config.typedApiSwaggerBackupFile,
55
+ "swagger/swagger.backup.json",
56
+ ),
57
+ );
58
+
59
+ const apiRoot = path.resolve(
60
+ cwd,
61
+ getStringSetting(
62
+ process.env.API_OUTPUT,
63
+ process.env.npm_config_api_output,
64
+ config.apiOutput,
65
+ config.output,
66
+ "src/api",
67
+ ),
68
+ );
69
+
70
+ const methodsDir = path.join(apiRoot, "methods");
71
+
72
+ const defaultFunctionsPathSetting = getStringSetting(
73
+ process.env.TYPED_API_DEFAULT_FUNCTIONS_PATH,
74
+ process.env.npm_config_typed_api_default_functions_path,
75
+ config.typedApiDefaultFunctionsPath,
76
+ "src/defaultApiFunctions",
77
+ );
78
+
79
+ const defaultFunctionsFilePath = path.resolve(cwd, defaultFunctionsPathSetting);
80
+
81
+ const defaultSuccessHandlerName = getStringSetting(
82
+ process.env.TYPED_API_DEFAULT_SUCCESS_HANDLER,
83
+ process.env.npm_config_typed_api_default_success_handler,
84
+ config.typedApiDefaultSuccessHandler,
85
+ "handleGoodResult",
86
+ );
87
+
88
+ const defaultErrorHandlerName = getStringSetting(
89
+ process.env.TYPED_API_DEFAULT_ERROR_HANDLER,
90
+ process.env.npm_config_typed_api_default_error_handler,
91
+ config.typedApiDefaultErrorHandler,
92
+ "handleErrors",
93
+ );
94
+
95
+ const generatorOptions = {
96
+ cleanOutput: getBooleanSetting(
97
+ process.env.TYPED_API_CLEAN_OUTPUT,
98
+ process.env.npm_config_typed_api_clean_output,
99
+ process.env.npm_config_clean_output,
100
+ config.typedApiCleanOutput,
101
+ config.cleanOutput,
102
+ true,
103
+ ),
104
+ moduleNameFirstTag: getBooleanSetting(
105
+ process.env.TYPED_API_MODULE_NAME_FIRST_TAG,
106
+ process.env.npm_config_typed_api_module_name_first_tag,
107
+ process.env.npm_config_module_name_first_tag,
108
+ config.typedApiModuleNameFirstTag,
109
+ config.moduleNameFirstTag,
110
+ true,
111
+ ),
112
+ defaultResponseAsSuccess: getBooleanSetting(
113
+ process.env.TYPED_API_DEFAULT_RESPONSE_AS_SUCCESS,
114
+ process.env.npm_config_typed_api_default_response_as_success,
115
+ process.env.npm_config_default_response_as_success,
116
+ config.typedApiDefaultResponseAsSuccess,
117
+ config.defaultResponseAsSuccess,
118
+ false,
119
+ ),
120
+ generateUnionEnums: getBooleanSetting(
121
+ process.env.TYPED_API_GENERATE_UNION_ENUMS,
122
+ process.env.npm_config_typed_api_generate_union_enums,
123
+ process.env.npm_config_generate_union_enums,
124
+ config.typedApiGenerateUnionEnums,
125
+ config.generateUnionEnums,
126
+ false,
127
+ ),
128
+ enumNamesAsValues: getBooleanSetting(
129
+ process.env.TYPED_API_ENUM_NAMES_AS_VALUES,
130
+ process.env.npm_config_typed_api_enum_names_as_values,
131
+ process.env.npm_config_enum_names_as_values,
132
+ config.typedApiEnumNamesAsValues,
133
+ config.enumNamesAsValues,
134
+ false,
135
+ ),
136
+ useTypeOnlyImports: getBooleanSetting(
137
+ process.env.TYPED_API_USE_TYPE_ONLY_IMPORTS,
138
+ process.env.npm_config_typed_api_use_type_only_imports,
139
+ process.env.npm_config_use_type_only_imports,
140
+ config.typedApiUseTypeOnlyImports,
141
+ config.useTypeOnlyImports,
142
+ true,
143
+ ),
144
+ useFilterFormValues: getBooleanSetting(
145
+ process.env.TYPED_API_USE_FILTER_FORM_VALUES,
146
+ process.env.npm_config_typed_api_use_filter_form_values,
147
+ config.typedApiUseFilterFormValues,
148
+ true,
149
+ ),
150
+ baseUrl: getStringSetting(
151
+ process.env.TYPED_API_BASE_URL,
152
+ process.env.npm_config_typed_api_base_url,
153
+ config.typedApiBaseUrl,
154
+ ),
155
+ runtimePackageName,
156
+ };
157
+
158
+ function getStringSetting(...values) {
159
+ for (const value of values) {
160
+ if (typeof value === "string" && value.trim() !== "") {
161
+ return value.trim();
162
+ }
163
+ }
164
+
165
+ return undefined;
166
+ }
167
+
168
+ function getBooleanSetting(...values) {
169
+ const fallback = values[values.length - 1];
170
+
171
+ for (const value of values.slice(0, -1)) {
172
+ if (typeof value === "boolean") return value;
173
+ if (typeof value === "string" && value.trim() !== "") {
174
+ const normalized = value.trim().toLowerCase();
175
+ if (["1", "true", "yes", "on"].includes(normalized)) return true;
176
+ if (["0", "false", "no", "off"].includes(normalized)) return false;
177
+ }
178
+ }
179
+
180
+ return Boolean(fallback);
181
+ }
182
+
183
+ function fileExists(filePath) {
184
+ try {
185
+ return fs.existsSync(filePath) && fs.statSync(filePath).isFile();
186
+ } catch {
187
+ return false;
188
+ }
189
+ }
190
+
191
+ function displayPath(filePath) {
192
+ return path.relative(cwd, filePath) || ".";
193
+ }
194
+
195
+ function writeSwaggerBackup(content, sourceLabel) {
196
+ fs.mkdirSync(path.dirname(swaggerBackupPath), { recursive: true });
197
+ fs.writeFileSync(swaggerBackupPath, content);
198
+
199
+ console.log(
200
+ `Updated Swagger backup from ${sourceLabel}: ${displayPath(swaggerBackupPath)}`,
201
+ );
202
+ }
203
+
204
+ function copySwaggerBackupFromFile(filePath) {
205
+ const content = fs.readFileSync(filePath, "utf8");
206
+ JSON.parse(content);
207
+ writeSwaggerBackup(content, path.relative(cwd, filePath));
208
+ }
209
+
210
+ async function downloadSwaggerBackupFromUrl(url) {
211
+ const response = await fetch(url);
212
+
213
+ if (!response.ok) {
214
+ throw new Error(
215
+ `Swagger URL returned ${response.status} ${response.statusText}: ${url}`,
216
+ );
217
+ }
218
+
219
+ const content = await response.text();
220
+ JSON.parse(content);
221
+ writeSwaggerBackup(content, url);
222
+ }
223
+
224
+ function useBackupSwaggerInput(reason) {
225
+ if (!fileExists(swaggerBackupPath)) {
226
+ return undefined;
227
+ }
228
+
229
+ console.warn(
230
+ `${reason} Using Swagger backup: ${displayPath(swaggerBackupPath)}`,
231
+ );
232
+
233
+ return {
234
+ input: swaggerBackupPath,
235
+ label: swaggerBackupPath,
236
+ };
237
+ }
238
+
239
+ async function resolveSwaggerInput() {
240
+ if (swaggerFile) {
241
+ const swaggerFilePath = path.resolve(cwd, swaggerFile);
242
+
243
+ if (fileExists(swaggerFilePath)) {
244
+ copySwaggerBackupFromFile(swaggerFilePath);
245
+
246
+ return {
247
+ input: swaggerFilePath,
248
+ label: swaggerFilePath,
249
+ };
250
+ }
251
+
252
+ const backupInput = useBackupSwaggerInput(
253
+ `Swagger file was not found: ${swaggerFilePath}.`,
254
+ );
255
+
256
+ if (backupInput) {
257
+ return backupInput;
258
+ }
259
+
260
+ throw new Error(
261
+ `Swagger file was not found and no backup exists: ${swaggerFilePath}`,
262
+ );
263
+ }
264
+
265
+ if (swaggerUrl) {
266
+ try {
267
+ await downloadSwaggerBackupFromUrl(swaggerUrl);
268
+
269
+ return {
270
+ input: swaggerBackupPath,
271
+ label: swaggerUrl,
272
+ };
273
+ } catch (error) {
274
+ const backupInput = useBackupSwaggerInput(
275
+ `Swagger URL is not available: ${swaggerUrl}.`,
276
+ );
277
+
278
+ if (backupInput) {
279
+ return backupInput;
280
+ }
281
+
282
+ throw error;
283
+ }
284
+ }
285
+
286
+ const defaultSwaggerFile = path.resolve(cwd, "swagger/swagger.json");
287
+
288
+ if (fileExists(defaultSwaggerFile)) {
289
+ copySwaggerBackupFromFile(defaultSwaggerFile);
290
+
291
+ return {
292
+ input: defaultSwaggerFile,
293
+ label: defaultSwaggerFile,
294
+ };
295
+ }
296
+
297
+ try {
298
+ await downloadSwaggerBackupFromUrl(defaultSwaggerUrl);
299
+
300
+ return {
301
+ input: swaggerBackupPath,
302
+ label: defaultSwaggerUrl,
303
+ };
304
+ } catch {
305
+ const backupInput = useBackupSwaggerInput(
306
+ "Default Swagger file was not found and default Swagger URL is not available.",
307
+ );
308
+
309
+ if (backupInput) {
310
+ return backupInput;
311
+ }
312
+
313
+ throw new Error(
314
+ `No Swagger input found. Add swagger/swagger.json, configure swaggerFile, or configure swaggerUrl.`,
315
+ );
316
+ }
317
+ }
318
+
319
+ function createImportStatement({ names, from, typeOnly = false }) {
320
+ const cleanNames = [...new Set(names.filter(Boolean))];
321
+ if (cleanNames.length === 0) return "";
322
+ const importKeyword = typeOnly ? "import type" : "import";
323
+ return `${importKeyword} { ${cleanNames.join(", ")} } from ${JSON.stringify(from)};`;
324
+ }
325
+
326
+ function createDefaultFunctionsFileIfMissing(filePath) {
327
+ const extension = path.extname(filePath);
328
+ const resolvedFilePath = extension ? filePath : `${filePath}.ts`;
329
+
330
+ if (fs.existsSync(resolvedFilePath)) {
331
+ return resolvedFilePath;
332
+ }
333
+
334
+ fs.mkdirSync(path.dirname(resolvedFilePath), { recursive: true });
335
+
336
+ fs.writeFileSync(
337
+ resolvedFilePath,
338
+ `${createImportStatement({
339
+ names: ["ApiErrorResult", "ApiSuccessResult"],
340
+ from: runtimePackageName,
341
+ typeOnly: generatorOptions.useTypeOnlyImports !== false,
342
+ })}
343
+
344
+ export function ${defaultSuccessHandlerName}<T>(
345
+ _response: ApiSuccessResult<T>,
346
+ ): void | Promise<void> {
347
+ // Add your default success handling here.
348
+ }
349
+
350
+ export function ${defaultErrorHandlerName}<T>(
351
+ _error: ApiErrorResult<T>,
352
+ ): void | Promise<void> {
353
+ // Add your default error handling here.
354
+ }
355
+ `,
356
+ );
357
+
358
+ console.log(
359
+ `Created default API handler file: ${path.relative(process.cwd(), resolvedFilePath)}`,
360
+ );
361
+
362
+ return resolvedFilePath;
363
+ }
364
+
365
+ function toRelativeImportPath(fromDirectory, filePath) {
366
+ let relativePath = path.relative(fromDirectory, filePath);
367
+
368
+ if (!relativePath.startsWith(".")) {
369
+ relativePath = `./${relativePath}`;
370
+ }
371
+
372
+ return relativePath
373
+ .replaceAll(path.sep, "/")
374
+ .replace(/\.(ts|tsx|js|jsx)$/, "");
375
+ }
376
+
377
+ function escapeRegExp(value) {
378
+ return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
379
+ }
380
+
381
+ function fileExportsName(source, exportName) {
382
+ const escapedName = escapeRegExp(exportName);
383
+
384
+ const directExportPattern = new RegExp(
385
+ `export\\s+(?:async\\s+)?(?:function|const|let|var|class)\\s+${escapedName}\\b`,
386
+ "m",
387
+ );
388
+
389
+ if (directExportPattern.test(source)) {
390
+ return true;
391
+ }
392
+
393
+ const namedExportBlocks = source.matchAll(/export\s*\{([\s\S]*?)\}/g);
394
+
395
+ for (const match of namedExportBlocks) {
396
+ const exportedNames = match[1]
397
+ .split(",")
398
+ .map((part) => part.trim())
399
+ .filter(Boolean);
400
+
401
+ if (
402
+ exportedNames.some((part) => {
403
+ const [localName, aliasName] = part
404
+ .split(/\s+as\s+/)
405
+ .map((name) => name.trim());
406
+
407
+ return (
408
+ aliasName === exportName || (!aliasName && localName === exportName)
409
+ );
410
+ })
411
+ ) {
412
+ return true;
413
+ }
414
+ }
415
+
416
+ return false;
417
+ }
418
+
419
+ function resolveDefaultHandlers() {
420
+ const defaultFunctionsFile = createDefaultFunctionsFileIfMissing(
421
+ defaultFunctionsFilePath,
422
+ );
423
+ const source = fs.readFileSync(defaultFunctionsFile, "utf8");
424
+ const hasSuccessHandler = fileExportsName(source, defaultSuccessHandlerName);
425
+ const hasErrorHandler = fileExportsName(source, defaultErrorHandlerName);
426
+
427
+ if (!hasSuccessHandler || !hasErrorHandler) {
428
+ const missingNames = [
429
+ ...(!hasSuccessHandler ? [defaultSuccessHandlerName] : []),
430
+ ...(!hasErrorHandler ? [defaultErrorHandlerName] : []),
431
+ ];
432
+
433
+ console.warn(
434
+ `Default API handlers were not imported because ${path.relative(
435
+ process.cwd(),
436
+ defaultFunctionsFile,
437
+ )} does not export: ${missingNames.join(", ")}.`,
438
+ );
439
+
440
+ return undefined;
441
+ }
442
+
443
+ return {
444
+ path: toRelativeImportPath(methodsDir, defaultFunctionsFile),
445
+ success: defaultSuccessHandlerName,
446
+ error: defaultErrorHandlerName,
447
+ };
448
+ }
449
+
450
+ async function main() {
451
+ const swaggerInput = await resolveSwaggerInput();
452
+
453
+ console.log(`Generating API from: ${swaggerInput.label}`);
454
+ console.log(`Output folder: ${apiRoot}`);
455
+ console.log(
456
+ "Generator: local typedapi OpenAPI generator, function-only methods",
457
+ );
458
+
459
+ const result = await generateApi({
460
+ input: swaggerInput.input,
461
+ output: apiRoot,
462
+ ...generatorOptions,
463
+ defaultHandlers: resolveDefaultHandlers(),
464
+ });
465
+
466
+ for (const file of result.files) {
467
+ console.log(`Generated ${path.relative(cwd, file)}`);
468
+ }
469
+
470
+ console.log("API generation completed.");
471
+ }
472
+
473
+ main().catch((error) => {
474
+ console.error(error);
475
+ process.exit(1);
476
+ });