typedapi-client-helpers 0.2.1 → 0.3.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.
Files changed (124) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/README.md +231 -220
  3. package/dist/cjs/httpClient.js +258 -0
  4. package/dist/cjs/httpClient.js.map +1 -0
  5. package/dist/cjs/index.js +32 -0
  6. package/dist/cjs/index.js.map +1 -0
  7. package/dist/cjs/interfaces/ApiResult.js +3 -0
  8. package/dist/cjs/interfaces/ApiResult.js.map +1 -0
  9. package/dist/cjs/interfaces/Filter.js +3 -0
  10. package/dist/cjs/interfaces/Filter.js.map +1 -0
  11. package/dist/cjs/interfaces/SortType.js +3 -0
  12. package/dist/cjs/interfaces/SortType.js.map +1 -0
  13. package/dist/cjs/package.json +3 -0
  14. package/dist/cjs/types/ApiCallOptions.js +3 -0
  15. package/dist/cjs/types/ApiCallOptions.js.map +1 -0
  16. package/dist/cjs/types/ApiMethodArguments.js +3 -0
  17. package/dist/cjs/types/ApiMethodArguments.js.map +1 -0
  18. package/dist/cjs/types/ApiMethodOptions.js +3 -0
  19. package/dist/cjs/types/ApiMethodOptions.js.map +1 -0
  20. package/dist/cjs/types/ApiMethodTypes.js +3 -0
  21. package/dist/cjs/types/ApiMethodTypes.js.map +1 -0
  22. package/dist/cjs/types/HttpResponse.js +3 -0
  23. package/dist/cjs/types/HttpResponse.js.map +1 -0
  24. package/dist/cjs/types/httpClientTypes.js +3 -0
  25. package/dist/cjs/types/httpClientTypes.js.map +1 -0
  26. package/dist/cjs/utils/SortTypeFromSortDirection.js +43 -0
  27. package/dist/cjs/utils/SortTypeFromSortDirection.js.map +1 -0
  28. package/dist/cjs/utils/buildQuery.js +96 -0
  29. package/dist/cjs/utils/buildQuery.js.map +1 -0
  30. package/dist/cjs/utils/extractArgsCallbacksAndParams.js +51 -0
  31. package/dist/cjs/utils/extractArgsCallbacksAndParams.js.map +1 -0
  32. package/dist/cjs/utils/extractArgsToastsAndParams.js +49 -0
  33. package/dist/cjs/utils/extractArgsToastsAndParams.js.map +1 -0
  34. package/dist/cjs/utils/handleApiResponse.js +132 -0
  35. package/dist/cjs/utils/handleApiResponse.js.map +1 -0
  36. package/dist/cjs/utils/toFormData.js +58 -0
  37. package/dist/cjs/utils/toFormData.js.map +1 -0
  38. package/dist/cjs/utils/wireMapping.js +123 -0
  39. package/dist/cjs/utils/wireMapping.js.map +1 -0
  40. package/dist/esm/httpClient.js +251 -0
  41. package/dist/esm/httpClient.js.map +1 -0
  42. package/dist/esm/index.js +16 -0
  43. package/dist/esm/index.js.map +1 -0
  44. package/dist/esm/interfaces/ApiResult.js +2 -0
  45. package/dist/esm/interfaces/ApiResult.js.map +1 -0
  46. package/dist/esm/interfaces/Filter.js +2 -0
  47. package/dist/esm/interfaces/Filter.js.map +1 -0
  48. package/dist/esm/interfaces/SortType.js +2 -0
  49. package/dist/esm/interfaces/SortType.js.map +1 -0
  50. package/dist/esm/types/ApiCallOptions.js +2 -0
  51. package/dist/esm/types/ApiCallOptions.js.map +1 -0
  52. package/dist/esm/types/ApiMethodArguments.js +2 -0
  53. package/dist/esm/types/ApiMethodArguments.js.map +1 -0
  54. package/dist/esm/types/ApiMethodOptions.js +2 -0
  55. package/dist/esm/types/ApiMethodOptions.js.map +1 -0
  56. package/dist/esm/types/ApiMethodTypes.js +2 -0
  57. package/dist/esm/types/ApiMethodTypes.js.map +1 -0
  58. package/dist/esm/types/HttpResponse.js +2 -0
  59. package/dist/esm/types/HttpResponse.js.map +1 -0
  60. package/dist/esm/types/httpClientTypes.js +2 -0
  61. package/dist/esm/types/httpClientTypes.js.map +1 -0
  62. package/dist/esm/utils/SortTypeFromSortDirection.js +38 -0
  63. package/dist/esm/utils/SortTypeFromSortDirection.js.map +1 -0
  64. package/dist/esm/utils/buildQuery.js +93 -0
  65. package/dist/esm/utils/buildQuery.js.map +1 -0
  66. package/dist/esm/utils/extractArgsCallbacksAndParams.js +48 -0
  67. package/dist/esm/utils/extractArgsCallbacksAndParams.js.map +1 -0
  68. package/dist/esm/utils/extractArgsToastsAndParams.js +46 -0
  69. package/dist/esm/utils/extractArgsToastsAndParams.js.map +1 -0
  70. package/dist/esm/utils/handleApiResponse.js +128 -0
  71. package/dist/esm/utils/handleApiResponse.js.map +1 -0
  72. package/dist/esm/utils/toFormData.js +55 -0
  73. package/dist/esm/utils/toFormData.js.map +1 -0
  74. package/dist/esm/utils/wireMapping.js +119 -0
  75. package/dist/esm/utils/wireMapping.js.map +1 -0
  76. package/dist/types/httpClient.d.ts +63 -0
  77. package/dist/types/httpClient.d.ts.map +1 -0
  78. package/dist/types/index.d.ts +16 -0
  79. package/dist/types/index.d.ts.map +1 -0
  80. package/dist/types/interfaces/ApiResult.d.ts +41 -0
  81. package/dist/types/interfaces/ApiResult.d.ts.map +1 -0
  82. package/dist/types/interfaces/Filter.d.ts +31 -0
  83. package/dist/types/interfaces/Filter.d.ts.map +1 -0
  84. package/dist/types/interfaces/SortType.d.ts +7 -0
  85. package/dist/types/interfaces/SortType.d.ts.map +1 -0
  86. package/dist/types/types/ApiCallOptions.d.ts +26 -0
  87. package/dist/types/types/ApiCallOptions.d.ts.map +1 -0
  88. package/dist/types/types/ApiMethodArguments.d.ts +6 -0
  89. package/dist/types/types/ApiMethodArguments.d.ts.map +1 -0
  90. package/dist/types/types/ApiMethodOptions.d.ts +8 -0
  91. package/dist/types/types/ApiMethodOptions.d.ts.map +1 -0
  92. package/dist/types/types/ApiMethodTypes.d.ts +47 -0
  93. package/dist/types/types/ApiMethodTypes.d.ts.map +1 -0
  94. package/dist/types/types/HttpResponse.d.ts +2 -0
  95. package/dist/types/types/HttpResponse.d.ts.map +1 -0
  96. package/dist/types/types/httpClientTypes.d.ts +2 -0
  97. package/dist/types/types/httpClientTypes.d.ts.map +1 -0
  98. package/dist/types/utils/SortTypeFromSortDirection.d.ts +13 -0
  99. package/dist/types/utils/SortTypeFromSortDirection.d.ts.map +1 -0
  100. package/dist/types/utils/buildQuery.d.ts +16 -0
  101. package/dist/types/utils/buildQuery.d.ts.map +1 -0
  102. package/dist/types/utils/extractArgsCallbacksAndParams.d.ts +22 -0
  103. package/dist/types/utils/extractArgsCallbacksAndParams.d.ts.map +1 -0
  104. package/dist/types/utils/extractArgsToastsAndParams.d.ts +20 -0
  105. package/dist/types/utils/extractArgsToastsAndParams.d.ts.map +1 -0
  106. package/dist/types/utils/handleApiResponse.d.ts +12 -0
  107. package/dist/types/utils/handleApiResponse.d.ts.map +1 -0
  108. package/dist/types/utils/toFormData.d.ts +10 -0
  109. package/dist/types/utils/toFormData.d.ts.map +1 -0
  110. package/dist/types/utils/wireMapping.d.ts +30 -0
  111. package/dist/types/utils/wireMapping.d.ts.map +1 -0
  112. package/package.json +90 -84
  113. package/scripts/build.mjs +74 -0
  114. package/scripts/generate-api-minimal.mjs +568 -476
  115. package/scripts/openapi-generator/controller-template.mjs +1 -1
  116. package/scripts/openapi-generator/index.mjs +348 -113
  117. package/scripts/openapi-generator/method-template.mjs +405 -440
  118. package/scripts/openapi-generator/names.mjs +37 -86
  119. package/scripts/openapi-generator/openapi-utils.mjs +411 -147
  120. package/scripts/openapi-generator/schema.mjs +737 -455
  121. package/dist/index.cjs +0 -536
  122. package/dist/index.d.cts +0 -285
  123. package/dist/index.d.ts +0 -285
  124. package/dist/index.js +0 -495
@@ -154,7 +154,7 @@ function createMethod(operationInfo, operationTypes) {
154
154
  * ${description.replace(/\n/g, "\n * ")}
155
155
  *
156
156
  * @tags ${tags}
157
- * @name ${operationTypes.operationId}
157
+ * @name ${operationTypes.methodNameSource ?? operationTypes.operationId}
158
158
  * @request ${operationInfo.method}:${operationInfo.routePath}
159
159
  */
160
160
  ${operationTypes.methodName} = (${methodArguments(operationTypes)}): Promise<HttpResponse<${operationTypes.responseType}, any>> =>
@@ -1,113 +1,348 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import { collectOperations } from "./openapi-utils.mjs";
4
- import { generateDataContracts } from "./schema.mjs";
5
- import { generateHttpClient } from "./http-client-template.mjs";
6
- import {
7
- controllerNameForOperation,
8
- generateMethodFile,
9
- methodFileNameForController,
10
- } from "./method-template.mjs";
11
-
12
- export async function generateApi(options) {
13
- const inputPath = path.resolve(options.input);
14
- const output = path.resolve(options.output);
15
- const openApi = JSON.parse(fs.readFileSync(inputPath, "utf8"));
16
-
17
- if (!openApi.paths || typeof openApi.paths !== "object") {
18
- throw new Error(
19
- "The OpenAPI document does not contain a valid paths object.",
20
- );
21
- }
22
-
23
- const generatedDir = path.join(output, "generated");
24
- const methodsDir = path.join(output, "methods");
25
-
26
- if (options.cleanOutput !== false) {
27
- cleanApiOutput(output);
28
- }
29
-
30
- fs.mkdirSync(generatedDir, { recursive: true });
31
- fs.mkdirSync(methodsDir, { recursive: true });
32
-
33
- const operations = collectOperations(openApi).filter(
34
- (operationInfo) => operationInfo.operation?.operationId,
35
- );
36
-
37
- const normalizedOptions = {
38
- moduleNameFirstTag: options.moduleNameFirstTag !== false,
39
- defaultResponseAsSuccess: Boolean(options.defaultResponseAsSuccess),
40
- generateUnionEnums: Boolean(options.generateUnionEnums),
41
- enumNamesAsValues: Boolean(options.enumNamesAsValues),
42
- useTypeOnlyImports: options.useTypeOnlyImports !== false,
43
- useFilterFormValues: options.useFilterFormValues !== false,
44
- baseUrl: options.baseUrl,
45
- runtimePackageName: options.runtimePackageName,
46
- defaultHandlers: options.defaultHandlers,
47
- };
48
-
49
- fs.writeFileSync(
50
- path.join(generatedDir, "data-contracts.ts"),
51
- generateDataContracts(openApi, operations, normalizedOptions),
52
- );
53
-
54
- fs.writeFileSync(
55
- path.join(generatedDir, "http-client.ts"),
56
- generateHttpClient(openApi, normalizedOptions),
57
- );
58
-
59
- const controllers = new Map();
60
- for (const operationInfo of operations) {
61
- const controllerName = controllerNameForOperation(
62
- operationInfo,
63
- normalizedOptions,
64
- );
65
- const controllerOperations = controllers.get(controllerName) ?? [];
66
- controllerOperations.push(operationInfo);
67
- controllers.set(controllerName, controllerOperations);
68
- }
69
-
70
- const controllerNames = [...controllers.keys()].sort((a, b) =>
71
- a.localeCompare(b),
72
- );
73
-
74
- for (const controllerName of controllerNames) {
75
- fs.writeFileSync(
76
- path.join(methodsDir, methodFileNameForController(controllerName)),
77
- generateMethodFile(
78
- openApi,
79
- controllerName,
80
- controllers.get(controllerName),
81
- normalizedOptions,
82
- ),
83
- );
84
- }
85
-
86
-
87
- return {
88
- files: [
89
- path.join(generatedDir, "data-contracts.ts"),
90
- path.join(generatedDir, "http-client.ts"),
91
- ...controllerNames.map((controllerName) =>
92
- path.join(methodsDir, methodFileNameForController(controllerName)),
93
- ),
94
- ],
95
- };
96
- }
97
-
98
- function cleanApiOutput(output) {
99
- const removable = [
100
- path.join(output, "controllers"),
101
- path.join(output, "generated"),
102
- path.join(output, "methods"),
103
- path.join(output, "data-contracts.ts"),
104
- path.join(output, "http-client.ts"),
105
- path.join(output, "index.ts"),
106
- ];
107
-
108
- for (const item of removable) {
109
- if (fs.existsSync(item)) {
110
- fs.rmSync(item, { recursive: true, force: true });
111
- }
112
- }
113
- }
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { collectOperations, validateOpenApiDocument } from "./openapi-utils.mjs";
6
+ import { generateDataContracts } from "./schema.mjs";
7
+ import { generateHttpClient } from "./http-client-template.mjs";
8
+ import {
9
+ controllerNameForOperation,
10
+ generateMethodFile,
11
+ methodFileNameForController,
12
+ } from "./method-template.mjs";
13
+
14
+ const MANAGED_TOP_LEVEL = ["generated", "methods", "index.ts", "typedapi.manifest.json"];
15
+
16
+ export async function generateApi(options) {
17
+ const inputPath = path.resolve(options.input);
18
+ const output = path.resolve(options.output);
19
+ const source = fs.readFileSync(inputPath, "utf8");
20
+ const openApi = JSON.parse(source);
21
+
22
+ validateOpenApiDocument(openApi, { supportedContractVersion: 1 });
23
+ const operations = collectOperations(openApi, {
24
+ generateMissingOperationIds: Boolean(options.generateMissingOperationIds),
25
+ methodNameStyle: options.methodNameStyle ?? "operationId",
26
+ prefixMethodNamesWithController:
27
+ options.prefixMethodNamesWithController !== false,
28
+ });
29
+
30
+ const normalizedOptions = {
31
+ moduleNameFirstTag: options.moduleNameFirstTag !== false,
32
+ defaultResponseAsSuccess: Boolean(options.defaultResponseAsSuccess),
33
+ generateUnionEnums: Boolean(options.generateUnionEnums),
34
+ enumNamesAsValues: Boolean(options.enumNamesAsValues),
35
+ useTypeOnlyImports: options.useTypeOnlyImports !== false,
36
+ useFilterFormValues: options.useFilterFormValues !== false,
37
+ baseUrl: options.baseUrl,
38
+ runtimePackageName: options.runtimePackageName,
39
+ defaultHandlers: options.defaultHandlers,
40
+ methodNameStyle: options.methodNameStyle ?? "operationId",
41
+ prefixMethodNamesWithController:
42
+ options.prefixMethodNamesWithController !== false,
43
+ };
44
+
45
+ const files = new Map();
46
+ files.set(
47
+ "generated/data-contracts.ts",
48
+ generateDataContracts(openApi, operations, normalizedOptions),
49
+ );
50
+ files.set(
51
+ "generated/http-client.ts",
52
+ generateHttpClient(openApi, normalizedOptions),
53
+ );
54
+
55
+ const controllers = new Map();
56
+ for (const operationInfo of operations) {
57
+ const controllerName = controllerNameForOperation(operationInfo, normalizedOptions);
58
+ const controllerOperations = controllers.get(controllerName) ?? [];
59
+ controllerOperations.push(operationInfo);
60
+ controllers.set(controllerName, controllerOperations);
61
+ }
62
+
63
+ const controllerNames = [...controllers.keys()].sort((a, b) => a.localeCompare(b));
64
+ for (const controllerName of controllerNames) {
65
+ files.set(
66
+ `methods/${methodFileNameForController(controllerName)}`,
67
+ generateMethodFile(
68
+ openApi,
69
+ controllerName,
70
+ controllers.get(controllerName),
71
+ normalizedOptions,
72
+ ),
73
+ );
74
+ }
75
+
76
+ const schemaHash = crypto.createHash("sha256").update(source).digest("hex");
77
+ files.set(
78
+ "typedapi.manifest.json",
79
+ `${JSON.stringify(
80
+ {
81
+ contractVersion: 1,
82
+ generator: options.runtimePackageName ?? "typedapi-client-helpers",
83
+ generatorVersion: options.generatorVersion ?? "unknown",
84
+ schemaSha256: schemaHash,
85
+ producer: openApi["x-typedapi"] ?? null,
86
+ operationCount: operations.length,
87
+ methodNameStyle: normalizedOptions.methodNameStyle,
88
+ prefixMethodNamesWithController:
89
+ normalizedOptions.prefixMethodNamesWithController,
90
+ },
91
+ null,
92
+ 2,
93
+ )}\n`,
94
+ );
95
+
96
+ const changes = compareOutput(output, files);
97
+ if (options.check) {
98
+ if (changes.length > 0) {
99
+ throw new Error(
100
+ `Generated API is out of date:\n${changes.map((item) => `- ${item}`).join("\n")}`,
101
+ );
102
+ }
103
+ return { files: [...files.keys()].map((file) => path.join(output, file)), changed: false };
104
+ }
105
+
106
+ const writeStrategy = writeOutputTransactionally(
107
+ output,
108
+ files,
109
+ options.cleanOutput !== false,
110
+ Boolean(options.forceInPlaceOutput),
111
+ );
112
+ return {
113
+ files: [...files.keys()].map((file) => path.join(output, file)),
114
+ changed: changes.length > 0,
115
+ writeStrategy,
116
+ };
117
+ }
118
+
119
+ function compareOutput(output, files) {
120
+ const changes = [];
121
+ for (const [relativePath, expected] of files) {
122
+ const target = path.join(output, relativePath);
123
+ if (!fs.existsSync(target)) {
124
+ changes.push(`${relativePath} is missing`);
125
+ continue;
126
+ }
127
+ if (fs.readFileSync(target, "utf8") !== expected) changes.push(`${relativePath} differs`);
128
+ }
129
+
130
+ for (const relativePath of listManagedFiles(output)) {
131
+ if (!files.has(relativePath)) changes.push(`${relativePath} is stale`);
132
+ }
133
+ return changes;
134
+ }
135
+
136
+ function listManagedFiles(output) {
137
+ const result = [];
138
+ for (const topLevel of MANAGED_TOP_LEVEL) {
139
+ const target = path.join(output, topLevel);
140
+ if (!fs.existsSync(target)) continue;
141
+ if (fs.statSync(target).isFile()) {
142
+ result.push(topLevel);
143
+ continue;
144
+ }
145
+ walk(target, (file) => result.push(path.relative(output, file).replaceAll(path.sep, "/")));
146
+ }
147
+ return result;
148
+ }
149
+
150
+ function walk(directory, callback) {
151
+ for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
152
+ const entryPath = path.join(directory, entry.name);
153
+ if (entry.isDirectory()) walk(entryPath, callback);
154
+ else if (entry.isFile()) callback(entryPath);
155
+ }
156
+ }
157
+
158
+ function removeManagedOutput(root) {
159
+ for (const item of MANAGED_TOP_LEVEL) {
160
+ fs.rmSync(path.join(root, item), { recursive: true, force: true });
161
+ }
162
+ }
163
+
164
+ const RETRYABLE_FS_ERROR_CODES = new Set([
165
+ "EACCES",
166
+ "EBUSY",
167
+ "EMFILE",
168
+ "ENFILE",
169
+ "ENOTEMPTY",
170
+ "EPERM",
171
+ ]);
172
+
173
+ function writeOutputTransactionally(output, files, cleanOutput, forceInPlaceOutput = false) {
174
+ const parent = path.dirname(output);
175
+ fs.mkdirSync(parent, { recursive: true });
176
+ const staging = fs.mkdtempSync(path.join(parent, `.${path.basename(output)}.typedapi-staging-`));
177
+ const backup = path.join(parent, `.${path.basename(output)}.typedapi-backup-${process.pid}-${Date.now()}`);
178
+
179
+ try {
180
+ if (fs.existsSync(output)) fs.cpSync(output, staging, { recursive: true, force: true });
181
+ if (cleanOutput) removeManagedOutput(staging);
182
+
183
+ for (const [relativePath, content] of files) {
184
+ const target = path.join(staging, relativePath);
185
+ fs.mkdirSync(path.dirname(target), { recursive: true });
186
+ fs.writeFileSync(target, content);
187
+ }
188
+
189
+ if (forceInPlaceOutput) {
190
+ writeManagedOutputInPlace(output, staging);
191
+ return "managed-file-sync";
192
+ }
193
+
194
+ if (fs.existsSync(output)) {
195
+ try {
196
+ retryFileSystemOperation(() => fs.renameSync(output, backup));
197
+ } catch (error) {
198
+ if (!isRetryableFileSystemError(error)) throw error;
199
+ writeManagedOutputInPlace(output, staging);
200
+ return "managed-file-sync";
201
+ }
202
+ }
203
+
204
+ try {
205
+ retryFileSystemOperation(() => fs.renameSync(staging, output));
206
+ } catch (error) {
207
+ let restoreError;
208
+ if (fs.existsSync(backup) && !fs.existsSync(output)) {
209
+ try {
210
+ retryFileSystemOperation(() => fs.renameSync(backup, output));
211
+ } catch (caughtRestoreError) {
212
+ restoreError = caughtRestoreError;
213
+ }
214
+ }
215
+
216
+ if (restoreError) {
217
+ throw new AggregateError(
218
+ [error, restoreError],
219
+ "TypedApi could not replace the generated output or restore the previous output.",
220
+ );
221
+ }
222
+
223
+ if (isRetryableFileSystemError(error) && fs.existsSync(output)) {
224
+ writeManagedOutputInPlace(output, staging);
225
+ return "managed-file-sync";
226
+ }
227
+ throw error;
228
+ }
229
+
230
+ removePathWithRetry(backup);
231
+ return "directory-swap";
232
+ } finally {
233
+ removePathWithRetry(staging);
234
+ if (fs.existsSync(backup) && fs.existsSync(output)) removePathWithRetry(backup);
235
+ }
236
+ }
237
+
238
+ function writeManagedOutputInPlace(output, staging) {
239
+ const parent = path.dirname(output);
240
+ fs.mkdirSync(output, { recursive: true });
241
+ const managedBackup = fs.mkdtempSync(
242
+ path.join(parent, `.${path.basename(output)}.typedapi-managed-backup-`),
243
+ );
244
+ const originallyPresent = new Set();
245
+
246
+ try {
247
+ for (const item of MANAGED_TOP_LEVEL) {
248
+ const current = path.join(output, item);
249
+ if (!fs.existsSync(current)) continue;
250
+ originallyPresent.add(item);
251
+ syncPath(current, path.join(managedBackup, item));
252
+ }
253
+
254
+ for (const item of MANAGED_TOP_LEVEL) {
255
+ const source = path.join(staging, item);
256
+ const target = path.join(output, item);
257
+ if (fs.existsSync(source)) syncPath(source, target);
258
+ else removePathWithRetry(target);
259
+ }
260
+ } catch (error) {
261
+ try {
262
+ for (const item of MANAGED_TOP_LEVEL) {
263
+ const target = path.join(output, item);
264
+ const saved = path.join(managedBackup, item);
265
+ if (originallyPresent.has(item)) syncPath(saved, target);
266
+ else removePathWithRetry(target);
267
+ }
268
+ } catch (rollbackError) {
269
+ throw new AggregateError(
270
+ [error, rollbackError],
271
+ "TypedApi generation failed and the previous managed output could not be fully restored.",
272
+ );
273
+ }
274
+ throw error;
275
+ } finally {
276
+ removePathWithRetry(managedBackup);
277
+ }
278
+ }
279
+
280
+ function syncPath(source, target) {
281
+ const sourceStat = fs.lstatSync(source);
282
+
283
+ if (sourceStat.isDirectory()) {
284
+ if (fs.existsSync(target) && !fs.lstatSync(target).isDirectory()) {
285
+ removePathWithRetry(target);
286
+ }
287
+ fs.mkdirSync(target, { recursive: true });
288
+
289
+ const sourceEntries = new Set(fs.readdirSync(source));
290
+ for (const entry of sourceEntries) {
291
+ syncPath(path.join(source, entry), path.join(target, entry));
292
+ }
293
+ for (const entry of fs.readdirSync(target)) {
294
+ if (!sourceEntries.has(entry)) removePathWithRetry(path.join(target, entry));
295
+ }
296
+ return;
297
+ }
298
+
299
+ if (sourceStat.isSymbolicLink()) {
300
+ const linkTarget = fs.readlinkSync(source);
301
+ if (fs.existsSync(target)) removePathWithRetry(target);
302
+ fs.mkdirSync(path.dirname(target), { recursive: true });
303
+ retryFileSystemOperation(() => fs.symlinkSync(linkTarget, target));
304
+ return;
305
+ }
306
+
307
+ if (fs.existsSync(target) && fs.lstatSync(target).isDirectory()) {
308
+ removePathWithRetry(target);
309
+ }
310
+ fs.mkdirSync(path.dirname(target), { recursive: true });
311
+ const content = fs.readFileSync(source);
312
+ retryFileSystemOperation(() => fs.writeFileSync(target, content));
313
+ }
314
+
315
+ function removePathWithRetry(target) {
316
+ if (!target || !fs.existsSync(target)) return;
317
+ retryFileSystemOperation(() =>
318
+ fs.rmSync(target, {
319
+ recursive: true,
320
+ force: true,
321
+ maxRetries: 4,
322
+ retryDelay: 50,
323
+ }),
324
+ );
325
+ }
326
+
327
+ function retryFileSystemOperation(operation, attempts = 12) {
328
+ let lastError;
329
+ for (let attempt = 0; attempt < attempts; attempt += 1) {
330
+ try {
331
+ return operation();
332
+ } catch (error) {
333
+ lastError = error;
334
+ if (!isRetryableFileSystemError(error) || attempt === attempts - 1) throw error;
335
+ sleepSynchronously(Math.min(25 * 2 ** attempt, 250));
336
+ }
337
+ }
338
+ throw lastError;
339
+ }
340
+
341
+ function isRetryableFileSystemError(error) {
342
+ return Boolean(error && typeof error === "object" && RETRYABLE_FS_ERROR_CODES.has(error.code));
343
+ }
344
+
345
+ function sleepSynchronously(milliseconds) {
346
+ const sleeper = new Int32Array(new SharedArrayBuffer(4));
347
+ Atomics.wait(sleeper, 0, 0, milliseconds);
348
+ }