@openclaw/fs-safe 0.10.0 → 0.11.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 (259) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/LICENSE +1 -0
  3. package/README.md +36 -5
  4. package/dist/absolute-path.d.ts.map +1 -1
  5. package/dist/absolute-path.js +3 -2
  6. package/dist/advanced.d.ts +4 -0
  7. package/dist/advanced.d.ts.map +1 -1
  8. package/dist/advanced.js +4 -0
  9. package/dist/archive-durability.d.ts +6 -6
  10. package/dist/archive-durability.d.ts.map +1 -1
  11. package/dist/archive-durability.js +1 -1
  12. package/dist/archive-entry.d.ts.map +1 -1
  13. package/dist/archive-entry.js +4 -5
  14. package/dist/archive-gzip-tail.d.ts +1 -0
  15. package/dist/archive-gzip-tail.d.ts.map +1 -1
  16. package/dist/archive-gzip-tail.js +3 -0
  17. package/dist/archive-input.d.ts.map +1 -1
  18. package/dist/archive-input.js +4 -2
  19. package/dist/archive-merge.d.ts +5 -1
  20. package/dist/archive-merge.d.ts.map +1 -1
  21. package/dist/archive-merge.js +15 -12
  22. package/dist/archive-native.js +4 -4
  23. package/dist/archive-parser.wasm +0 -0
  24. package/dist/archive-read.d.ts.map +1 -1
  25. package/dist/archive-read.js +17 -9
  26. package/dist/archive-staging.d.ts +6 -3
  27. package/dist/archive-staging.d.ts.map +1 -1
  28. package/dist/archive-staging.js +42 -22
  29. package/dist/archive-tar-stream.d.ts.map +1 -1
  30. package/dist/archive-tar-stream.js +7 -6
  31. package/dist/archive-tar-wasm.d.ts.map +1 -1
  32. package/dist/archive-tar-wasm.js +16 -13
  33. package/dist/archive-zip-admission.d.ts +1 -1
  34. package/dist/archive-zip-admission.d.ts.map +1 -1
  35. package/dist/archive-zip-admission.js +48 -12
  36. package/dist/archive-zip-loader.d.ts +6 -0
  37. package/dist/archive-zip-loader.d.ts.map +1 -0
  38. package/dist/archive-zip-loader.js +38 -0
  39. package/dist/archive-zip-names.d.ts.map +1 -1
  40. package/dist/archive-zip-names.js +13 -8
  41. package/dist/archive-zip-preflight.d.ts +2 -3
  42. package/dist/archive-zip-preflight.d.ts.map +1 -1
  43. package/dist/archive-zip-preflight.js +2 -34
  44. package/dist/archive.d.ts.map +1 -1
  45. package/dist/archive.js +12 -10
  46. package/dist/bounded-read.d.ts +5 -0
  47. package/dist/bounded-read.d.ts.map +1 -1
  48. package/dist/bounded-read.js +16 -9
  49. package/dist/copy-file-input.d.ts +0 -1
  50. package/dist/copy-file-input.d.ts.map +1 -1
  51. package/dist/copy-file-input.js +4 -26
  52. package/dist/copy-tree-portable.d.ts.map +1 -1
  53. package/dist/copy-tree-portable.js +57 -26
  54. package/dist/copy.d.ts +1 -1
  55. package/dist/copy.d.ts.map +1 -1
  56. package/dist/copy.js +3 -1
  57. package/dist/directory-durability.d.ts.map +1 -1
  58. package/dist/directory-durability.js +5 -4
  59. package/dist/directory-guard.d.ts +11 -1
  60. package/dist/directory-guard.d.ts.map +1 -1
  61. package/dist/directory-guard.js +53 -11
  62. package/dist/durability.d.ts +1 -1
  63. package/dist/durability.d.ts.map +1 -1
  64. package/dist/durability.js +1 -1
  65. package/dist/file-handle-transfer.d.ts +14 -0
  66. package/dist/file-handle-transfer.d.ts.map +1 -0
  67. package/dist/file-handle-transfer.js +64 -0
  68. package/dist/file-hash.d.ts +3 -0
  69. package/dist/file-hash.d.ts.map +1 -1
  70. package/dist/file-hash.js +91 -31
  71. package/dist/file-lock-sync.d.ts.map +1 -1
  72. package/dist/file-lock-sync.js +8 -4
  73. package/dist/file-store-boundary.d.ts.map +1 -1
  74. package/dist/file-store-boundary.js +7 -5
  75. package/dist/file-store-path.d.ts +3 -0
  76. package/dist/file-store-path.d.ts.map +1 -0
  77. package/dist/file-store-path.js +27 -0
  78. package/dist/file-store-prune.d.ts.map +1 -1
  79. package/dist/file-store-prune.js +6 -4
  80. package/dist/file-store-sync-write.d.ts.map +1 -1
  81. package/dist/file-store-sync-write.js +56 -44
  82. package/dist/file-store.d.ts.map +1 -1
  83. package/dist/file-store.js +2 -18
  84. package/dist/filename.d.ts.map +1 -1
  85. package/dist/filename.js +2 -1
  86. package/dist/guarded-mkdir.d.ts.map +1 -1
  87. package/dist/guarded-mkdir.js +3 -2
  88. package/dist/guest-dispatch-python.d.ts +2 -0
  89. package/dist/guest-dispatch-python.d.ts.map +1 -0
  90. package/dist/guest-dispatch-python.js +117 -0
  91. package/dist/guest-native-python.d.ts +4 -0
  92. package/dist/guest-native-python.d.ts.map +1 -0
  93. package/dist/guest-native-python.js +135 -0
  94. package/dist/guest.d.ts +9 -0
  95. package/dist/guest.d.ts.map +1 -0
  96. package/dist/guest.js +413 -0
  97. package/dist/index.d.ts +1 -1
  98. package/dist/index.d.ts.map +1 -1
  99. package/dist/install-path.d.ts.map +1 -1
  100. package/dist/install-path.js +3 -2
  101. package/dist/json-durable-queue-directory.js +3 -3
  102. package/dist/json-durable-queue.d.ts.map +1 -1
  103. package/dist/json-durable-queue.js +6 -4
  104. package/dist/json.d.ts.map +1 -1
  105. package/dist/json.js +2 -1
  106. package/dist/local-roots.d.ts.map +1 -1
  107. package/dist/local-roots.js +2 -1
  108. package/dist/move-path-stage.d.ts.map +1 -1
  109. package/dist/move-path-stage.js +2 -1
  110. package/dist/move-path.d.ts.map +1 -1
  111. package/dist/move-path.js +4 -3
  112. package/dist/mutation-authority.d.ts +1 -0
  113. package/dist/mutation-authority.d.ts.map +1 -1
  114. package/dist/mutation-authority.js +4 -4
  115. package/dist/native-binding.d.ts +7 -2
  116. package/dist/native-binding.d.ts.map +1 -1
  117. package/dist/native-pinned-write-windows.d.ts.map +1 -1
  118. package/dist/native-pinned-write-windows.js +3 -2
  119. package/dist/native-pinned-write.d.ts.map +1 -1
  120. package/dist/native-pinned-write.js +2 -1
  121. package/dist/opened-realpath.d.ts.map +1 -1
  122. package/dist/opened-realpath.js +5 -4
  123. package/dist/output.d.ts +2 -0
  124. package/dist/output.d.ts.map +1 -1
  125. package/dist/output.js +2 -0
  126. package/dist/overwrite-file-handle.d.ts +8 -0
  127. package/dist/overwrite-file-handle.d.ts.map +1 -0
  128. package/dist/overwrite-file-handle.js +42 -0
  129. package/dist/path-case.d.ts +7 -0
  130. package/dist/path-case.d.ts.map +1 -0
  131. package/dist/path-case.js +136 -0
  132. package/dist/path.d.ts.map +1 -1
  133. package/dist/path.js +2 -1
  134. package/dist/permissions-windows.d.ts +1 -1
  135. package/dist/permissions-windows.d.ts.map +1 -1
  136. package/dist/permissions-windows.js +48 -6
  137. package/dist/pinned-open.d.ts.map +1 -1
  138. package/dist/pinned-open.js +3 -1
  139. package/dist/pinned-write.d.ts +2 -2
  140. package/dist/pinned-write.d.ts.map +1 -1
  141. package/dist/pinned-write.js +2 -1
  142. package/dist/private-temp-workspace.d.ts.map +1 -1
  143. package/dist/private-temp-workspace.js +6 -4
  144. package/dist/realpath.d.ts +4 -0
  145. package/dist/realpath.d.ts.map +1 -0
  146. package/dist/realpath.js +43 -0
  147. package/dist/recursive-mkdir-path.d.ts +3 -0
  148. package/dist/recursive-mkdir-path.d.ts.map +1 -0
  149. package/dist/recursive-mkdir-path.js +8 -0
  150. package/dist/replace-directory.d.ts.map +1 -1
  151. package/dist/replace-directory.js +2 -1
  152. package/dist/replace-file-copy-fallback.d.ts.map +1 -1
  153. package/dist/replace-file-copy-fallback.js +23 -31
  154. package/dist/replace-file-copy-source.d.ts.map +1 -1
  155. package/dist/replace-file-copy-source.js +7 -12
  156. package/dist/replace-file-mode.d.ts +3 -0
  157. package/dist/replace-file-mode.d.ts.map +1 -0
  158. package/dist/replace-file-mode.js +10 -0
  159. package/dist/replace-file.d.ts +1 -0
  160. package/dist/replace-file.d.ts.map +1 -1
  161. package/dist/replace-file.js +14 -8
  162. package/dist/root-context.d.ts.map +1 -1
  163. package/dist/root-context.js +8 -8
  164. package/dist/root-create-input.d.ts +10 -0
  165. package/dist/root-create-input.d.ts.map +1 -0
  166. package/dist/root-create-input.js +80 -0
  167. package/dist/root-directory-list.d.ts +3 -1
  168. package/dist/root-directory-list.d.ts.map +1 -1
  169. package/dist/root-directory-list.js +21 -3
  170. package/dist/root-entries.d.ts +11 -0
  171. package/dist/root-entries.d.ts.map +1 -0
  172. package/dist/root-entries.js +61 -0
  173. package/dist/root-errors.d.ts +5 -5
  174. package/dist/root-errors.d.ts.map +1 -1
  175. package/dist/root-errors.js +13 -12
  176. package/dist/root-impl.d.ts +13 -3
  177. package/dist/root-impl.d.ts.map +1 -1
  178. package/dist/root-impl.js +40 -36
  179. package/dist/root-options.d.ts +12 -1
  180. package/dist/root-options.d.ts.map +1 -1
  181. package/dist/root-path-existing.d.ts.map +1 -1
  182. package/dist/root-path-existing.js +4 -3
  183. package/dist/root-path-symlink.d.ts.map +1 -1
  184. package/dist/root-path-symlink.js +3 -2
  185. package/dist/root-paths.d.ts.map +1 -1
  186. package/dist/root-paths.js +13 -9
  187. package/dist/root-remove.d.ts +5 -0
  188. package/dist/root-remove.d.ts.map +1 -0
  189. package/dist/root-remove.js +286 -0
  190. package/dist/root-symlink-policy.d.ts +2 -1
  191. package/dist/root-symlink-policy.d.ts.map +1 -1
  192. package/dist/root-symlink-policy.js +2 -2
  193. package/dist/root-write-mode.d.ts.map +1 -1
  194. package/dist/root-write-mode.js +2 -1
  195. package/dist/root.d.ts +2 -1
  196. package/dist/root.d.ts.map +1 -1
  197. package/dist/secret-file.d.ts.map +1 -1
  198. package/dist/secret-file.js +2 -1
  199. package/dist/secret-read-async.d.ts.map +1 -1
  200. package/dist/secret-read-async.js +2 -1
  201. package/dist/secure-file.d.ts.map +1 -1
  202. package/dist/secure-file.js +21 -4
  203. package/dist/secure-temp-dir.d.ts.map +1 -1
  204. package/dist/secure-temp-dir.js +2 -1
  205. package/dist/sibling-staged-file.d.ts +1 -0
  206. package/dist/sibling-staged-file.d.ts.map +1 -1
  207. package/dist/sibling-staged-file.js +42 -8
  208. package/dist/sibling-temp.d.ts +2 -0
  209. package/dist/sibling-temp.d.ts.map +1 -1
  210. package/dist/sibling-temp.js +6 -4
  211. package/dist/sidecar-lock-acquire.d.ts.map +1 -1
  212. package/dist/sidecar-lock-acquire.js +15 -4
  213. package/dist/sidecar-lock-policy.d.ts +2 -0
  214. package/dist/sidecar-lock-policy.d.ts.map +1 -1
  215. package/dist/sidecar-lock-policy.js +17 -0
  216. package/dist/staged-directory.d.ts.map +1 -1
  217. package/dist/staged-directory.js +4 -3
  218. package/dist/temp-target.d.ts +14 -12
  219. package/dist/temp-target.d.ts.map +1 -1
  220. package/dist/temp-target.js +12 -6
  221. package/dist/trash.d.ts.map +1 -1
  222. package/dist/trash.js +7 -5
  223. package/dist/unicode-path.d.ts +3 -0
  224. package/dist/unicode-path.d.ts.map +1 -0
  225. package/dist/unicode-path.js +13 -0
  226. package/dist/walk.d.ts.map +1 -1
  227. package/dist/walk.js +3 -2
  228. package/dist/write-file-handle.d.ts +1 -0
  229. package/dist/write-file-handle.d.ts.map +1 -1
  230. package/dist/write-file-handle.js +3 -2
  231. package/docs/advanced.md +4 -0
  232. package/docs/archive.md +25 -5
  233. package/docs/atomic.md +17 -1
  234. package/docs/config.md +1 -0
  235. package/docs/contributing.md +29 -1
  236. package/docs/copy.md +75 -6
  237. package/docs/directory-identity.md +85 -0
  238. package/docs/durability.md +36 -1
  239. package/docs/entries.md +109 -0
  240. package/docs/errors.md +3 -3
  241. package/docs/file-store.md +15 -0
  242. package/docs/guest.md +141 -0
  243. package/docs/in-place-write.md +81 -0
  244. package/docs/index.md +2 -0
  245. package/docs/install.md +31 -0
  246. package/docs/native-helper.md +10 -3
  247. package/docs/native.md +4 -0
  248. package/docs/output.md +32 -6
  249. package/docs/path-case.md +64 -0
  250. package/docs/path-scope.md +1 -1
  251. package/docs/permissions.md +11 -2
  252. package/docs/public-api.md +31 -2
  253. package/docs/root.md +30 -2
  254. package/docs/secure-file.md +2 -0
  255. package/docs/sidecar-lock.md +12 -3
  256. package/docs/temp.md +35 -6
  257. package/docs/types.md +1 -1
  258. package/docs/writing.md +153 -3
  259. package/package.json +14 -8
package/dist/archive.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { createTarEntryPlanner } from "./archive-tar.js";
2
2
  import { inspectTar, replayTar } from "./archive-tar-stream.js";
3
3
  import { runPinnedWriteHelper } from "./pinned-write.js";
4
- import fsSync, { constants as fsConstants } from "node:fs";
4
+ import { constants as fsConstants } from "node:fs";
5
5
  import fs from "node:fs/promises";
6
6
  import path from "node:path";
7
7
  import { Readable } from "node:stream";
@@ -10,7 +10,7 @@ import { createArchiveOutputPathTracker, resolveArchiveOutputPath, stripArchiveP
10
10
  import { createPipelineTimeoutError, waitForDeadline, withExtractionDeadline, } from "./archive-deadline.js";
11
11
  import { assertArchiveEntryCountWithinLimit, assertArchiveEntryPathComponentsWithinLimit, createByteBudgetTracker, createExtractBudgetTransform, resolveExtractLimits, resolveTarMeterLimits, } from "./archive-limits.js";
12
12
  import { assertPortableArchiveKind, resolveArchiveKind } from "./archive-kind.js";
13
- import { prepareArchiveDestinationDir, preparePrivateArchiveOutputPath, withStagedArchiveDestination, } from "./archive-staging.js";
13
+ import { prepareArchiveDestinationGuard, preparePrivateArchiveOutputPath, withStagedArchiveDestination, } from "./archive-staging.js";
14
14
  import { mergePlannedArchiveIntoDestination } from "./archive-merge.js";
15
15
  import { loadZipArchiveWithPreflight } from "./archive-zip-preflight.js";
16
16
  import { isZipSymlinkEntry, zipEntryDeclaredSize, zipEntryMode, } from "./archive-zip-entry.js";
@@ -22,6 +22,7 @@ import { stageArchiveFileForExtraction } from "./archive-input.js";
22
22
  import { getNativeBinding } from "./native.js";
23
23
  import { resolveArchiveFilteredEntryPolicy, shouldExtractArchiveEntry, } from "./archive-policy.js";
24
24
  import { writeSiblingTempFile } from "./sibling-temp.js";
25
+ import { realpathSync } from "./realpath.js";
25
26
  export { isWindowsDrivePath, normalizeArchiveEntryPath, resolveArchiveOutputPath, stripArchivePath, validateArchiveEntryPath, } from "./archive-entry.js";
26
27
  export { resolveArchiveKind, resolvePackedRootDir } from "./archive-kind.js";
27
28
  export { readArchiveEntry } from "./archive-read.js";
@@ -122,7 +123,8 @@ async function extractZip(params) {
122
123
  deadline: params.deadline,
123
124
  });
124
125
  try {
125
- const destinationRealDir = await prepareArchiveDestinationDir(params.destDir);
126
+ const destinationGuard = await prepareArchiveDestinationGuard(params.destDir);
127
+ const destinationRealDir = destinationGuard.realPath;
126
128
  params.deadline.check();
127
129
  const buffer = await fs.readFile(stagedArchive.path, { signal: params.deadline.signal });
128
130
  params.deadline.check();
@@ -136,7 +138,7 @@ async function extractZip(params) {
136
138
  await withStagedArchiveDestination({
137
139
  destinationRealDir,
138
140
  run: async (stagingDir) => {
139
- const stagingRealDir = fsSync.realpathSync.native(stagingDir);
141
+ const stagingRealDir = realpathSync.native(stagingDir);
140
142
  const acceptedEntries = [];
141
143
  for (const entry of entries) {
142
144
  params.deadline.check();
@@ -189,8 +191,7 @@ async function extractZip(params) {
189
191
  entries: acceptedEntries,
190
192
  durable: params.durable,
191
193
  sourceDir: stagingRealDir,
192
- destinationDir: params.destDir,
193
- destinationRealDir,
194
+ destinationGuard,
194
195
  deadline: params.deadline,
195
196
  });
196
197
  params.deadline.check();
@@ -248,9 +249,10 @@ async function extractWasmTar(params) {
248
249
  await inspectTar({ archivePath: params.archivePath, limits: tarLimits, signal: deadline.signal,
249
250
  onMember: (entry) => { manifest.push(entry); } });
250
251
  deadline.check();
251
- const destinationRealDir = await prepareArchiveDestinationDir(options.destDir);
252
+ const destinationGuard = await prepareArchiveDestinationGuard(options.destDir);
253
+ const destinationRealDir = destinationGuard.realPath;
252
254
  await withStagedArchiveDestination({ destinationRealDir, run: async (stagingPath) => {
253
- const stagingDir = fsSync.realpathSync.native(stagingPath);
255
+ const stagingDir = realpathSync.native(stagingPath);
254
256
  const planEntry = createTarEntryPlanner({ ...options, rootDir: destinationRealDir, limits: params.limits });
255
257
  const accepted = manifest.flatMap((entry) => {
256
258
  deadline.check();
@@ -273,8 +275,8 @@ async function extractWasmTar(params) {
273
275
  },
274
276
  });
275
277
  deadline.check();
276
- await mergePlannedArchiveIntoDestination({ entries: accepted, sourceDir: stagingDir,
277
- destinationDir: options.destDir, destinationRealDir, deadline, durable: options.durable });
278
+ await mergePlannedArchiveIntoDestination({ entries: accepted, sourceDir: stagingDir, destinationGuard,
279
+ deadline, durable: options.durable });
278
280
  deadline.check();
279
281
  } });
280
282
  }
@@ -15,6 +15,11 @@ export declare function readBoundedAsync(maxBytes: number, readChunk: (scratch:
15
15
  export declare function readFileHandleBounded(handle: ReadableFileHandle, maxBytes: number): Promise<Buffer>;
16
16
  /** Async bounded read from a numeric descriptor. The caller owns the descriptor. */
17
17
  export declare function readFileDescriptorBounded(fd: number, maxBytes: number): Promise<Buffer>;
18
+ export declare function readBoundedSync(maxBytes: number, readChunk: (scratch: Buffer, length: number) => number, options?: {
19
+ observeRegularFileSize?: () => number | undefined;
20
+ initialSize?: number;
21
+ createLimitError?: () => Error;
22
+ }): Buffer;
18
23
  /** Sync bounded read from a numeric descriptor. The caller owns the descriptor. */
19
24
  export declare function readFileDescriptorBoundedSync(fd: number, maxBytes: number): Buffer;
20
25
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"bounded-read.d.ts","sourceRoot":"","sources":["../src/bounded-read.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAQnD,KAAK,kBAAkB,GAAG,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;AA8CnD,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,EAC/D,OAAO,GAAE;IACP,sBAAsB,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IAClD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,KAAK,CAAC;IAC/B,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,OAAO,CAAC;CACf,GACL,OAAO,CAAC,MAAM,CAAC,CAuBjB;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,kBAAkB,EAC1B,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,CAAC,CAMjB;AAyBD,oFAAoF;AACpF,wBAAsB,yBAAyB,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAK7F;AAED,mFAAmF;AACnF,wBAAgB,6BAA6B,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAoBlF"}
1
+ {"version":3,"file":"bounded-read.d.ts","sourceRoot":"","sources":["../src/bounded-read.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAQnD,KAAK,kBAAkB,GAAG,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;AA8CnD,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,EAC/D,OAAO,GAAE;IACP,sBAAsB,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IAClD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,KAAK,CAAC;IAC/B,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,OAAO,CAAC;CACf,GACL,OAAO,CAAC,MAAM,CAAC,CAuBjB;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,kBAAkB,EAC1B,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,CAAC,CAMjB;AAyBD,oFAAoF;AACpF,wBAAsB,yBAAyB,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAK7F;AAED,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,MAAM,EACtD,OAAO,GAAE;IACP,sBAAsB,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IAClD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,KAAK,CAAC;CAC3B,GACL,MAAM,CAoBR;AAED,mFAAmF;AACnF,wBAAgB,6BAA6B,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAMlF"}
@@ -108,19 +108,18 @@ export async function readFileDescriptorBounded(fd, maxBytes) {
108
108
  return await readDescriptorChunk(fd, scratch, length);
109
109
  }, { observeRegularFileSize: () => regularFileSize(fd) });
110
110
  }
111
- /** Sync bounded read from a numeric descriptor. The caller owns the descriptor. */
112
- export function readFileDescriptorBoundedSync(fd, maxBytes) {
111
+ export function readBoundedSync(maxBytes, readChunk, options = {}) {
113
112
  normalizeMaxBytes(maxBytes);
114
113
  let total = 0;
115
- // Small budgets already bound the first allocation without a size lookup.
116
- const size = maxBytes <= READ_CHUNK_BYTES ? maxBytes : regularFileSize(fd);
114
+ const { observeRegularFileSize, createLimitError } = options;
115
+ const size = options.initialSize ?? observeRegularFileSize?.();
117
116
  let buffer = size === undefined ? createScratchBuffer(maxBytes) : createInitialBuffer(maxBytes, size);
118
117
  if (size !== undefined) {
119
- const bytesRead = fs.readSync(fd, buffer, 0, buffer.length, null);
118
+ const bytesRead = readChunk(buffer, buffer.length);
120
119
  if (bytesRead === 0)
121
120
  return finishReadBuffer(buffer, 0);
122
- const currentSize = bytesRead < buffer.length ? regularFileSize(fd) : undefined;
123
- total = addReadBytes(total, bytesRead, maxBytes);
121
+ const currentSize = bytesRead < buffer.length ? observeRegularFileSize?.() : undefined;
122
+ total = addReadBytes(total, bytesRead, maxBytes, createLimitError);
124
123
  if (currentSize !== undefined && bytesRead >= currentSize)
125
124
  return finishReadBuffer(buffer, total);
126
125
  }
@@ -128,9 +127,17 @@ export function readFileDescriptorBoundedSync(fd, maxBytes) {
128
127
  if (total === buffer.length)
129
128
  buffer = growReadBuffer(buffer, maxBytes);
130
129
  const remaining = buffer.subarray(total);
131
- const bytesRead = fs.readSync(fd, remaining, 0, remaining.length, null);
130
+ const bytesRead = readChunk(remaining, remaining.length);
132
131
  if (bytesRead === 0)
133
132
  return finishReadBuffer(buffer, total);
134
- total = addReadBytes(total, bytesRead, maxBytes);
133
+ total = addReadBytes(total, bytesRead, maxBytes, createLimitError);
135
134
  }
136
135
  }
136
+ /** Sync bounded read from a numeric descriptor. The caller owns the descriptor. */
137
+ export function readFileDescriptorBoundedSync(fd, maxBytes) {
138
+ return readBoundedSync(maxBytes, (buffer, length) => fs.readSync(fd, buffer, 0, length, null), {
139
+ // Small budgets already bound the first allocation without a size lookup.
140
+ initialSize: maxBytes <= READ_CHUNK_BYTES ? maxBytes : undefined,
141
+ observeRegularFileSize: () => regularFileSize(fd),
142
+ });
143
+ }
@@ -16,7 +16,6 @@ export declare function assertCopySourceCurrent(source: {
16
16
  handle: FileHandle;
17
17
  realPath: string;
18
18
  }, identity: BigIntStats): Promise<void>;
19
- export declare function copyFileChunks(input: CopyFileInput, maxBytes?: number): AsyncGenerator<Buffer>;
20
19
  export declare function writeCopyFileToFd(fd: number, input: CopyFileInput, maxBytes?: number, assertBeforeMutation?: () => void): Promise<void>;
21
20
  export declare function createNativeCopyFile(native: NativeBinding, input: CopyFileInput, parentFd: number, basename: string, maxBytes: number | undefined, sync: boolean): Promise<NativeFileCopyResult | undefined>;
22
21
  export declare function assertNativeCopyCompleted(input: CopyFileInput, copied?: NativeFileCopyResult): void;
@@ -1 +1 @@
1
- {"version":3,"file":"copy-file-input.d.ts","sourceRoot":"","sources":["../src/copy-file-input.ts"],"names":[],"mappings":"AAAA,OAAW,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAwB,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAE5E,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAIhE,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,UAAU,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,aAAa,CAAC;IACrB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/B,CAAC;AAEF,wBAAgB,wBAAwB,CAAC,IAAI,CAAC,EAAE,aAAa,GAAG,aAAa,CAM5E;AAED,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,EAChD,QAAQ,EAAE,WAAW,GACpB,OAAO,CAAC,IAAI,CAAC,CASf;AAED,wBAAuB,cAAc,CACnC,KAAK,EAAE,aAAa,EACpB,QAAQ,SAAW,GAClB,cAAc,CAAC,MAAM,CAAC,CAiBxB;AAED,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,aAAa,EACpB,QAAQ,CAAC,EAAE,MAAM,EACjB,oBAAoB,CAAC,EAAE,MAAM,IAAI,GAChC,OAAO,CAAC,IAAI,CAAC,CASf;AAED,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,aAAa,EACrB,KAAK,EAAE,aAAa,EACpB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,IAAI,EAAE,OAAO,GACZ,OAAO,CAAC,oBAAoB,GAAG,SAAS,CAAC,CA8B3C;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,aAAa,EAAE,MAAM,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAWnG"}
1
+ {"version":3,"file":"copy-file-input.d.ts","sourceRoot":"","sources":["../src/copy-file-input.ts"],"names":[],"mappings":"AAAA,OAAW,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAwB,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAE5E,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAIhE,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,UAAU,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,aAAa,CAAC;IACrB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/B,CAAC;AAEF,wBAAgB,wBAAwB,CAAC,IAAI,CAAC,EAAE,aAAa,GAAG,aAAa,CAM5E;AAED,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,EAChD,QAAQ,EAAE,WAAW,GACpB,OAAO,CAAC,IAAI,CAAC,CASf;AAED,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,aAAa,EACpB,QAAQ,CAAC,EAAE,MAAM,EACjB,oBAAoB,CAAC,EAAE,MAAM,IAAI,GAChC,OAAO,CAAC,IAAI,CAAC,CAIf;AAED,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,aAAa,EACrB,KAAK,EAAE,aAAa,EACpB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,IAAI,EAAE,OAAO,GACZ,OAAO,CAAC,oBAAoB,GAAG,SAAS,CAAC,CA8B3C;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,aAAa,EAAE,MAAM,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAWnG"}
@@ -3,7 +3,7 @@ import { resolveCopyCloneMode } from "./copy-policy.js";
3
3
  import { FsSafeError } from "./errors.js";
4
4
  import { getNativeBinding } from "./native.js";
5
5
  import { inspectFileIdentity } from "./strict-file-identity.js";
6
- import { writeAllToFile } from "./write-file-handle.js";
6
+ import { transferFileHandle } from "./file-handle-transfer.js";
7
7
  export function resolveFileCopyCloneMode(mode) {
8
8
  const clone = resolveCopyCloneMode(mode, "never");
9
9
  if (clone === "always" && !getNativeBinding()?.copyFileExclusive) {
@@ -21,32 +21,10 @@ export async function assertCopySourceCurrent(source, identity) {
21
21
  return current;
22
22
  }, identity);
23
23
  }
24
- export async function* copyFileChunks(input, maxBytes = Infinity) {
25
- const buffer = Buffer.allocUnsafe(Math.min(256 * 1024, Math.max(64 * 1024, input.size)));
26
- let position = 0;
27
- while (true) {
28
- input.signal?.throwIfAborted();
29
- const { bytesRead } = await input.handle.read(buffer, 0, Math.min(buffer.length, maxBytes - position + 1), position);
30
- input.signal?.throwIfAborted();
31
- if (bytesRead > maxBytes - position) {
32
- throw new FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes`);
33
- }
34
- if (bytesRead === 0)
35
- return;
36
- position += bytesRead;
37
- // The consumer finishes writing before the next iteration reuses this buffer.
38
- yield buffer.subarray(0, bytesRead);
39
- }
40
- }
41
24
  export async function writeCopyFileToFd(fd, input, maxBytes, assertBeforeMutation) {
42
- for await (const chunk of copyFileChunks(input, maxBytes)) {
43
- await writeAllToFile(fd, chunk, {
44
- assertBeforeMutation: () => {
45
- input.signal?.throwIfAborted();
46
- assertBeforeMutation?.();
47
- },
48
- });
49
- }
25
+ await transferFileHandle(input.handle, fd, {
26
+ maxBytes, sizeHint: input.size, signal: input.signal, assertBeforeMutation,
27
+ });
50
28
  }
51
29
  export async function createNativeCopyFile(native, input, parentFd, basename, maxBytes, sync) {
52
30
  input.signal?.throwIfAborted();
@@ -1 +1 @@
1
- {"version":3,"file":"copy-tree-portable.d.ts","sourceRoot":"","sources":["../src/copy-tree-portable.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAGL,mBAAmB,EACpB,MAAM,uBAAuB,CAAC;AAE/B,0FAA0F;AAC1F,wBAAsB,aAAa,CACjC,MAAM,EAAE,UAAU,CAAC,OAAO,mBAAmB,CAAC,EAC9C,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE;IACP,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,aAAa,CAAC,kBAAkB,CAAC,CAAC;CACtD,GACA,OAAO,CAAC,IAAI,CAAC,CAkLf"}
1
+ {"version":3,"file":"copy-tree-portable.d.ts","sourceRoot":"","sources":["../src/copy-tree-portable.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAGL,mBAAmB,EACpB,MAAM,uBAAuB,CAAC;AAQ/B,0FAA0F;AAC1F,wBAAsB,aAAa,CACjC,MAAM,EAAE,UAAU,CAAC,OAAO,mBAAmB,CAAC,EAC9C,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE;IACP,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,aAAa,CAAC,kBAAkB,CAAC,CAAC;CACtD,GACA,OAAO,CAAC,IAAI,CAAC,CAiNf"}
@@ -4,6 +4,11 @@ import fsp, {} from "node:fs/promises";
4
4
  import path from "node:path";
5
5
  import { FsSafeError } from "./errors.js";
6
6
  import { assertStagedDirectoryCurrent, exactIdentityMatches, openStagedDirectory, } from "./staged-directory.js";
7
+ function timestampSeconds(nanoseconds) {
8
+ // Dates discard sub-millisecond precision; negative numbers mean "now" in
9
+ // Node's utimes API. Numeric strings retain fractional, pre-epoch timestamps.
10
+ return String(Number(nanoseconds / 1000000000n) + Number(nanoseconds % 1000000000n) / 1e9);
11
+ }
7
12
  /** Byte-copy adapter for the shared immutable-source, caller-owned namespace contract. */
8
13
  export async function copyOwnedTree(source, destination, options) {
9
14
  options.signal?.throwIfAborted();
@@ -13,11 +18,9 @@ export async function copyOwnedTree(source, destination, options) {
13
18
  const abort = () => cancellation.abort(options.signal?.reason);
14
19
  options.signal?.addEventListener("abort", abort, { once: true });
15
20
  const pending = new Set();
21
+ const finishing = new Set();
16
22
  const buffers = [];
17
- const settle = async () => {
18
- await Promise.all(pending);
19
- };
20
- async function schedule(operation) {
23
+ async function schedule(operation, children) {
21
24
  signal.throwIfAborted();
22
25
  const task = operation()
23
26
  .catch((error) => {
@@ -25,8 +28,10 @@ export async function copyOwnedTree(source, destination, options) {
25
28
  })
26
29
  .finally(() => {
27
30
  pending.delete(task);
31
+ children.delete(task);
28
32
  });
29
33
  pending.add(task);
34
+ children.add(task);
30
35
  if (pending.size >= options.concurrency)
31
36
  await Promise.race(pending);
32
37
  signal.throwIfAborted();
@@ -82,7 +87,7 @@ export async function copyOwnedTree(source, destination, options) {
82
87
  signal.throwIfAborted();
83
88
  if (process.platform !== "win32")
84
89
  await output.chmod(Number(stat.mode & 4095n));
85
- await output.utimes(stat.atime, stat.mtime);
90
+ await output.utimes(timestampSeconds(stat.atimeNs), timestampSeconds(stat.mtimeNs));
86
91
  }
87
92
  catch (error) {
88
93
  cancellation.abort(error);
@@ -97,13 +102,14 @@ export async function copyOwnedTree(source, destination, options) {
97
102
  }
98
103
  }
99
104
  }
100
- async function copyDirectory(from, to) {
105
+ async function copyDirectory(from, to, parentChildren) {
101
106
  signal?.throwIfAborted();
102
107
  const stat = await fsp.lstat(from, { bigint: true });
103
108
  // Exclusive admission prevents merging a pre-existing directory, including
104
109
  // any destination left by an unsuccessful native clone.
105
110
  await fsp.mkdir(to, { mode: 0o700 });
106
111
  const target = openStagedDirectory(to);
112
+ const children = new Set();
107
113
  let original;
108
114
  try {
109
115
  original = openStagedDirectory(from);
@@ -118,10 +124,10 @@ export async function copyOwnedTree(source, destination, options) {
118
124
  const childTarget = path.join(to, entry.name);
119
125
  const child = await fsp.lstat(childSource, { bigint: true });
120
126
  if (child.isDirectory()) {
121
- await copyDirectory(childSource, childTarget);
127
+ await copyDirectory(childSource, childTarget, children);
122
128
  }
123
129
  else if (child.isFile()) {
124
- await schedule(() => copyFile(childSource, childTarget, child));
130
+ await schedule(() => copyFile(childSource, childTarget, child), children);
125
131
  }
126
132
  else if (child.isSymbolicLink()) {
127
133
  const link = process.platform === "win32"
@@ -152,40 +158,65 @@ export async function copyOwnedTree(source, destination, options) {
152
158
  throw new FsSafeError("not-file", "tree copying does not support special files");
153
159
  }
154
160
  }
155
- await settle();
156
- signal.throwIfAborted();
157
- assertStagedDirectoryCurrent(original.receipt);
158
- assertStagedDirectoryCurrent(target.receipt);
159
- if (process.platform !== "win32")
160
- fs.fchmodSync(target.fd, Number(stat.mode & 4095n));
161
- await fsp.utimes(to, stat.atime, stat.mtime);
162
- assertStagedDirectoryCurrent(target.receipt);
163
161
  }
164
162
  catch (error) {
165
163
  cancellation.abort(error);
166
164
  throw error;
167
165
  }
168
166
  finally {
169
- // Directory descriptors and destination ownership outlive every admitted
170
- // write, including when traversal, a sibling copy, or cancellation fails.
171
- await settle();
172
- try {
173
- if (original)
174
- fs.closeSync(original.fd);
175
- }
176
- finally {
177
- fs.closeSync(target.fd);
178
- }
167
+ // Traversal can advance into siblings while this directory's own files
168
+ // settle. Children retain their parents and finish metadata bottom-up.
169
+ const completion = (async () => {
170
+ try {
171
+ await Promise.all(children);
172
+ signal.throwIfAborted();
173
+ if (original) {
174
+ assertStagedDirectoryCurrent(original.receipt);
175
+ assertStagedDirectoryCurrent(target.receipt);
176
+ if (process.platform !== "win32")
177
+ fs.fchmodSync(target.fd, Number(stat.mode & 4095n));
178
+ await fsp.utimes(to, timestampSeconds(stat.atimeNs), timestampSeconds(stat.mtimeNs));
179
+ assertStagedDirectoryCurrent(target.receipt);
180
+ }
181
+ }
182
+ finally {
183
+ try {
184
+ if (original)
185
+ fs.closeSync(original.fd);
186
+ }
187
+ finally {
188
+ fs.closeSync(target.fd);
189
+ }
190
+ }
191
+ })()
192
+ .catch((error) => {
193
+ cancellation.abort(error);
194
+ })
195
+ .finally(() => {
196
+ finishing.delete(completion);
197
+ parentChildren?.delete(completion);
198
+ });
199
+ finishing.add(completion);
200
+ parentChildren?.add(completion);
201
+ // Bound traversed-but-unsettled directories as well as file workers;
202
+ // ancestors stay open along the current traversal path.
203
+ if (finishing.size >= options.concurrency)
204
+ await Promise.race(finishing);
179
205
  }
206
+ signal.throwIfAborted();
180
207
  }
181
208
  try {
182
209
  await copyDirectory(source.receipt.realPath, destination);
210
+ await Promise.all(finishing);
211
+ signal.throwIfAborted();
183
212
  }
184
213
  catch (error) {
214
+ await Promise.all(finishing);
185
215
  options.signal?.throwIfAborted();
186
216
  throw error;
187
217
  }
188
218
  finally {
219
+ await Promise.all(finishing);
189
220
  options.signal?.removeEventListener("abort", abort);
190
221
  }
191
222
  }
package/dist/copy.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { type CopyCloneMode } from "./copy-policy.js";
2
2
  export { readCloneFileMetadata, type CloneFileMetadata } from "./clone-metadata.js";
3
3
  export type { CopyCloneMode } from "./copy-policy.js";
4
- export type TreeCloneBackend = "apfs" | "btrfs" | "refs" | "xfs";
4
+ export type TreeCloneBackend = "apfs" | "btrfs" | "refs" | "xfs" | "zfs";
5
5
  export type CopyTreeOptions = {
6
6
  clone?: CopyCloneMode;
7
7
  signal?: AbortSignal;
@@ -1 +1 @@
1
- {"version":3,"file":"copy.d.ts","sourceRoot":"","sources":["../src/copy.ts"],"names":[],"mappings":"AAGA,OAAO,EAAwB,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAK5E,OAAO,EAAE,qBAAqB,EAAE,KAAK,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACpF,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtD,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,KAAK,CAAC;AACjE,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,uEAAuE;AACvE,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAS/E;AAED,0FAA0F;AAC1F,wBAAsB,iBAAiB,CACrC,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,IAAI,CAAC,eAAe,EAAE,QAAQ,CAAM,GAC5C,OAAO,CAAC,IAAI,CAAC,CAEf;AAED,mGAAmG;AACnG,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAIf"}
1
+ {"version":3,"file":"copy.d.ts","sourceRoot":"","sources":["../src/copy.ts"],"names":[],"mappings":"AAGA,OAAO,EAAwB,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAK5E,OAAO,EAAE,qBAAqB,EAAE,KAAK,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACpF,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtD,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,CAAC;AACzE,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,uEAAuE;AACvE,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAS/E;AAED,0FAA0F;AAC1F,wBAAsB,iBAAiB,CACrC,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,IAAI,CAAC,eAAe,EAAE,QAAQ,CAAM,GAC5C,OAAO,CAAC,IAAI,CAAC,CAEf;AAED,mGAAmG;AACnG,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAIf"}
package/dist/copy.js CHANGED
@@ -104,7 +104,9 @@ async function materializeTree(source, destination, options, policy) {
104
104
  await copyOwnedTree(original, target, {
105
105
  signal: options.signal,
106
106
  concurrency: options.concurrency ?? (process.platform === "win32" ? 4 : 1),
107
- copyFileContents: process.platform === "win32" ? native?.copyFileContents : undefined,
107
+ copyFileContents: process.platform === "win32" || process.platform === "linux"
108
+ ? native?.copyFileContents
109
+ : undefined,
108
110
  });
109
111
  options.signal?.throwIfAborted();
110
112
  assertStagedDirectoryCurrent(parent.receipt);
@@ -1 +1 @@
1
- {"version":3,"file":"directory-durability.d.ts","sourceRoot":"","sources":["../src/directory-durability.ts"],"names":[],"mappings":"AAAA,OAAe,EAAE,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAM7C,OAAO,EAAoB,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAE7E,MAAM,MAAM,oBAAoB,GAC5B;IAAE,MAAM,EAAE,QAAQ,CAAA;CAAE,GACpB;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7C,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,KAAK,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,gBAAgB,GAAG;IACvD,UAAU,EAAE,oBAAoB,GAAG;QAAE,MAAM,EAAE,YAAY,CAAA;KAAE,CAAC;CAC7D,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;IACnC,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,IAAI,IAAI,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACtC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG;IAC1C,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wBAAwB,CAAC,EAAE,gBAAgB,CAAC;IAC5C,MAAM,CAAC,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACnD,CAAC;AAgKF,wBAAsB,YAAY,CAChC,SAAS,EAAE,MAAM,GAAG,gBAAgB,EACpC,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/B,OAAO,CAAC,eAAe,CAAC,CAa1B;AAED,wBAAsB,aAAa,CACjC,SAAS,EAAE,MAAM,GAAG,gBAAgB,EACpC,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/B,OAAO,CAAC,oBAAoB,CAAC,CAmB/B;AAED,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,GAAG,gBAAgB,EACpC,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/B,oBAAoB,CAuCtB;AAED,wBAAsB,uBAAuB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAElF;AAED,wBAAgB,2BAA2B,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAMvE;AAuBD,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,uBAAuB,CAAC,CA8ElC"}
1
+ {"version":3,"file":"directory-durability.d.ts","sourceRoot":"","sources":["../src/directory-durability.ts"],"names":[],"mappings":"AAAA,OAAe,EAAE,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAM7C,OAAO,EAAoB,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAG7E,MAAM,MAAM,oBAAoB,GAC5B;IAAE,MAAM,EAAE,QAAQ,CAAA;CAAE,GACpB;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7C,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,KAAK,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,gBAAgB,GAAG;IACvD,UAAU,EAAE,oBAAoB,GAAG;QAAE,MAAM,EAAE,YAAY,CAAA;KAAE,CAAC;CAC7D,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;IACnC,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,IAAI,IAAI,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACtC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG;IAC1C,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wBAAwB,CAAC,EAAE,gBAAgB,CAAC;IAC5C,MAAM,CAAC,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACnD,CAAC;AAgKF,wBAAsB,YAAY,CAChC,SAAS,EAAE,MAAM,GAAG,gBAAgB,EACpC,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/B,OAAO,CAAC,eAAe,CAAC,CAa1B;AAED,wBAAsB,aAAa,CACjC,SAAS,EAAE,MAAM,GAAG,gBAAgB,EACpC,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/B,OAAO,CAAC,oBAAoB,CAAC,CAmB/B;AAED,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,GAAG,gBAAgB,EACpC,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/B,oBAAoB,CAuCtB;AAED,wBAAsB,uBAAuB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAElF;AAED,wBAAgB,2BAA2B,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAMvE;AAuBD,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,uBAAuB,CAAC,CA8ElC"}
@@ -4,6 +4,7 @@ import path from "node:path";
4
4
  import { ensureAbsoluteDirectory } from "./absolute-path.js";
5
5
  import { FsSafeError } from "./errors.js";
6
6
  import { sameFileIdentity } from "./file-identity.js";
7
+ import { realpathSync } from "./realpath.js";
7
8
  function directoryOpenFlags() {
8
9
  if (process.platform === "win32") {
9
10
  return "r";
@@ -47,7 +48,7 @@ async function createDirectoryReceipt(directoryPath, label) {
47
48
  assertDirectory(identity, resolvedPath, label);
48
49
  return {
49
50
  path: resolvedPath,
50
- realPath: fsSync.realpathSync.native(resolvedPath),
51
+ realPath: realpathSync.native(resolvedPath),
51
52
  identity,
52
53
  };
53
54
  }
@@ -57,7 +58,7 @@ function createDirectoryReceiptSync(directoryPath, label) {
57
58
  assertDirectory(identity, resolvedPath, label);
58
59
  return {
59
60
  path: resolvedPath,
60
- realPath: fsSync.realpathSync(resolvedPath),
61
+ realPath: realpathSync(resolvedPath),
61
62
  identity,
62
63
  };
63
64
  }
@@ -65,7 +66,7 @@ async function assertDirectoryReceiptCurrent(receipt, label) {
65
66
  const currentIdentity = fsSync.lstatSync(receipt.path);
66
67
  assertDirectory(currentIdentity, receipt.path, label);
67
68
  if (!sameFileIdentity(receipt.identity, currentIdentity) ||
68
- (fsSync.realpathSync.native(receipt.path)) !== receipt.realPath) {
69
+ (realpathSync.native(receipt.path)) !== receipt.realPath) {
69
70
  throw new FsSafeError("path-mismatch", `${label} changed during durable directory operation: ${receipt.path}`);
70
71
  }
71
72
  }
@@ -73,7 +74,7 @@ function assertDirectoryReceiptCurrentSync(receipt, label) {
73
74
  const currentIdentity = fsSync.lstatSync(receipt.path);
74
75
  assertDirectory(currentIdentity, receipt.path, label);
75
76
  if (!sameFileIdentity(receipt.identity, currentIdentity) ||
76
- fsSync.realpathSync(receipt.path) !== receipt.realPath) {
77
+ realpathSync(receipt.path) !== receipt.realPath) {
77
78
  throw new FsSafeError("path-mismatch", `${label} changed during durable directory operation: ${receipt.path}`);
78
79
  }
79
80
  }
@@ -10,6 +10,15 @@ export type SyncDirectoryGuard = {
10
10
  realPath: string;
11
11
  stat: Stats;
12
12
  };
13
+ export type DirectoryIdentity = Readonly<{
14
+ dev: bigint;
15
+ ino: bigint;
16
+ realPath: string;
17
+ }>;
18
+ export declare function readDirectoryIdentity(dir: string): Promise<DirectoryIdentity>;
19
+ export declare function assertDirectoryIdentitySync(observedPath: string, expected: Pick<DirectoryIdentity, "dev" | "ino"> & {
20
+ realPath?: string;
21
+ }): void;
13
22
  export declare function createAsyncDirectoryGuard(dir: string, options: {
14
23
  bigint: true;
15
24
  }): Promise<AsyncDirectoryGuard<BigIntStats>>;
@@ -21,11 +30,12 @@ export declare function createAsyncDirectoryGuard(dir: string, options: {
21
30
  }): Promise<AnyAsyncDirectoryGuard>;
22
31
  export declare function assertAsyncDirectoryGuard(guard: AnyAsyncDirectoryGuard): Promise<void>;
23
32
  export declare function createSyncDirectoryGuard(dir: string): SyncDirectoryGuard;
24
- export declare function assertSyncDirectoryGuard(guard: SyncDirectoryGuard): void;
33
+ export declare function assertSyncDirectoryGuard(guard: SyncDirectoryGuard | AnyAsyncDirectoryGuard): void;
25
34
  export declare function createNearestExistingDirectoryGuard(rootReal: string, targetPath: string): Promise<AsyncDirectoryGuard>;
26
35
  export declare function createNearestExistingDirectoryGuard(rootReal: string, targetPath: string, options: {
27
36
  bigint: boolean;
28
37
  }): Promise<AnyAsyncDirectoryGuard>;
29
38
  export declare function createNearestExistingSyncDirectoryGuard(rootReal: string, targetPath: string): SyncDirectoryGuard;
30
39
  export declare function inspectDirectoryIdentity(dir: string, expected?: Pick<BigIntStats, "dev" | "ino">): Promise<BigIntStats>;
40
+ export declare function inspectDirectoryIdentitySync(dir: string, expected?: Pick<BigIntStats, "dev" | "ino">): BigIntStats;
31
41
  //# sourceMappingURL=directory-guard.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"directory-guard.d.ts","sourceRoot":"","sources":["../src/directory-guard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AASlD,MAAM,MAAM,mBAAmB,CAAC,CAAC,SAAS,KAAK,GAAG,WAAW,GAAG,KAAK,IAAI;IACvE,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,CAAC,CAAC;CACT,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG,mBAAmB,CAAC,KAAK,GAAG,WAAW,CAAC,CAAC;AAE9E,MAAM,MAAM,kBAAkB,GAAG;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,KAAK,CAAC;CACb,CAAC;AAEF,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE;IAAE,MAAM,EAAE,IAAI,CAAA;CAAE,GAAG,OAAO,CAAC,mBAAmB,CAAC,WAAW,CAAC,CAAC,CAAC;AAC7H,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,KAAK,CAAA;CAAE,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;AACnH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;AAStH,wBAAsB,yBAAyB,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAU5F;AAED,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,kBAAkB,CAMxE;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,kBAAkB,GAAG,IAAI,CAQxE;AAED,wBAAgB,mCAAmC,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;AACxH,wBAAgB,mCAAmC,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;AAqBzJ,wBAAgB,uCAAuC,CACrD,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GACjB,kBAAkB,CAcpB;AAGD,wBAAsB,wBAAwB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,CAM7H"}
1
+ {"version":3,"file":"directory-guard.d.ts","sourceRoot":"","sources":["../src/directory-guard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAUlD,MAAM,MAAM,mBAAmB,CAAC,CAAC,SAAS,KAAK,GAAG,WAAW,GAAG,KAAK,IAAI;IACvE,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,CAAC,CAAC;CACT,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG,mBAAmB,CAAC,KAAK,GAAG,WAAW,CAAC,CAAC;AAE9E,MAAM,MAAM,kBAAkB,GAAG;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,KAAK,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;IACvC,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC,CAAC;AAEH,wBAAsB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAGnF;AAED,wBAAgB,2BAA2B,CACzC,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,IAAI,CAAC,iBAAiB,EAAE,KAAK,GAAG,KAAK,CAAC,GAAG;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GACvE,IAAI,CAKN;AAED,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE;IAAE,MAAM,EAAE,IAAI,CAAA;CAAE,GAAG,OAAO,CAAC,mBAAmB,CAAC,WAAW,CAAC,CAAC,CAAC;AAC7H,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,KAAK,CAAA;CAAE,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;AACnH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;AAStH,wBAAsB,yBAAyB,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAY5F;AAED,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,kBAAkB,CAMxE;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,kBAAkB,GAAG,sBAAsB,GAAG,IAAI,CAajG;AAED,wBAAgB,mCAAmC,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;AACxH,wBAAgB,mCAAmC,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;AAqBzJ,wBAAgB,uCAAuC,CACrD,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GACjB,kBAAkB,CAcpB;AAGD,wBAAsB,wBAAwB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,CAE7H;AAmBD,wBAAgB,4BAA4B,CAC1C,GAAG,EAAE,MAAM,EACX,QAAQ,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC,GAC1C,WAAW,CAQb"}
@@ -2,24 +2,37 @@ import fsSync from "node:fs";
2
2
  import path from "node:path";
3
3
  import { FsSafeError } from "./errors.js";
4
4
  import { sameFileIdentity } from "./file-identity.js";
5
- import { inspectFileIdentity } from "./strict-file-identity.js";
5
+ import { inspectFileIdentitySync } from "./strict-file-identity.js";
6
6
  import { isNotFoundPathError } from "./path.js";
7
7
  import { directoryComponentNotDirectoryError } from "./root-errors.js";
8
+ import { realpathSync } from "./realpath.js";
9
+ export async function readDirectoryIdentity(dir) {
10
+ const guard = await createAsyncDirectoryGuard(dir, { bigint: true });
11
+ return Object.freeze({ dev: guard.stat.dev, ino: guard.stat.ino, realPath: guard.realPath });
12
+ }
13
+ export function assertDirectoryIdentitySync(observedPath, expected) {
14
+ inspectDirectoryIdentitySync(observedPath, expected);
15
+ if (expected.realPath !== undefined && realpathSync.native(observedPath) !== expected.realPath) {
16
+ throw new FsSafeError("path-mismatch", "directory changed during operation");
17
+ }
18
+ }
8
19
  export async function createAsyncDirectoryGuard(dir, options) {
9
- const stat = options?.bigint ? await inspectDirectoryIdentity(dir) : fsSync.lstatSync(dir);
20
+ const stat = options?.bigint ? inspectDirectoryIdentitySync(dir) : fsSync.lstatSync(dir);
10
21
  if (stat.isSymbolicLink() || !stat.isDirectory()) {
11
22
  throw directoryComponentNotDirectoryError();
12
23
  }
13
- return { dir, realPath: fsSync.realpathSync.native(dir), stat };
24
+ return { dir, realPath: realpathSync.native(dir), stat };
14
25
  }
15
26
  export async function assertAsyncDirectoryGuard(guard) {
16
- const stat = typeof guard.stat.dev === "bigint" && typeof guard.stat.ino === "bigint"
17
- ? await inspectDirectoryIdentity(guard.dir, { dev: guard.stat.dev, ino: guard.stat.ino })
18
- : fsSync.lstatSync(guard.dir);
27
+ if (typeof guard.stat.dev === "bigint" && typeof guard.stat.ino === "bigint") {
28
+ assertDirectoryIdentitySync(guard.dir, { dev: guard.stat.dev, ino: guard.stat.ino, realPath: guard.realPath });
29
+ return;
30
+ }
31
+ const stat = fsSync.lstatSync(guard.dir);
19
32
  if (stat.isSymbolicLink() || !stat.isDirectory()) {
20
33
  throw directoryComponentNotDirectoryError();
21
34
  }
22
- if (!sameFileIdentity(stat, guard.stat) || fsSync.realpathSync.native(guard.dir) !== guard.realPath) {
35
+ if (!sameFileIdentity(stat, guard.stat) || realpathSync.native(guard.dir) !== guard.realPath) {
23
36
  throw new FsSafeError("path-mismatch", "directory changed during operation");
24
37
  }
25
38
  }
@@ -28,14 +41,19 @@ export function createSyncDirectoryGuard(dir) {
28
41
  if (stat.isSymbolicLink() || !stat.isDirectory()) {
29
42
  throw directoryComponentNotDirectoryError();
30
43
  }
31
- return { dir, realPath: fsSync.realpathSync(dir), stat };
44
+ return { dir, realPath: realpathSync(dir), stat };
32
45
  }
33
46
  export function assertSyncDirectoryGuard(guard) {
47
+ if (typeof guard.stat.dev === "bigint" && typeof guard.stat.ino === "bigint") {
48
+ assertDirectoryIdentitySync(guard.dir, { dev: guard.stat.dev, ino: guard.stat.ino, realPath: guard.realPath });
49
+ return;
50
+ }
34
51
  const stat = fsSync.lstatSync(guard.dir);
35
52
  if (stat.isSymbolicLink() || !stat.isDirectory()) {
36
53
  throw directoryComponentNotDirectoryError();
37
54
  }
38
- if (!sameFileIdentity(stat, guard.stat) || fsSync.realpathSync(guard.dir) !== guard.realPath) {
55
+ const realPath = realpathSync(guard.dir);
56
+ if (!sameFileIdentity(stat, guard.stat) || realPath !== guard.realPath) {
39
57
  throw new FsSafeError("path-mismatch", "directory changed during operation");
40
58
  }
41
59
  }
@@ -73,8 +91,32 @@ export function createNearestExistingSyncDirectoryGuard(rootReal, targetPath) {
73
91
  }
74
92
  // Recovery receipts must retain every identity bit, including on Windows.
75
93
  export async function inspectDirectoryIdentity(dir, expected) {
76
- return await inspectFileIdentity(async () => {
77
- const stat = fsSync.lstatSync(dir, { bigint: true });
94
+ return inspectDirectoryIdentitySync(dir, expected);
95
+ }
96
+ function directoryEntryPath(dir) {
97
+ const windows = process.platform === "win32";
98
+ if (typeof dir === "string") {
99
+ const last = dir[dir.length - 1];
100
+ if (last !== "/" && (!windows || last !== "\\"))
101
+ return dir;
102
+ }
103
+ let rootLength = (windows ? path.win32 : path.posix).parse(dir).root.length;
104
+ if (windows && /^[\\/]{2}[?.][\\/]UNC[\\/]/i.test(dir)) {
105
+ // Node parses the namespace prefix as the root; retain the complete UNC share instead.
106
+ const uncRoot = path.win32.parse(`\\\\${dir.slice(8)}`).root;
107
+ if (uncRoot.length > 1)
108
+ rootLength = Math.max(rootLength, uncRoot.length + 6);
109
+ }
110
+ let end = dir.length;
111
+ while (end > rootLength && (dir[end - 1] === "/" || (windows && dir[end - 1] === "\\")))
112
+ end--;
113
+ return end === rootLength || end === dir.length ? dir : dir.slice(0, end);
114
+ }
115
+ export function inspectDirectoryIdentitySync(dir, expected) {
116
+ // A trailing separator makes lstat follow a final directory symlink.
117
+ const entryPath = directoryEntryPath(dir);
118
+ return inspectFileIdentitySync(() => {
119
+ const stat = fsSync.lstatSync(entryPath, { bigint: true });
78
120
  if (stat.isSymbolicLink() || !stat.isDirectory())
79
121
  throw directoryComponentNotDirectoryError();
80
122
  return stat;
@@ -1,4 +1,4 @@
1
1
  export { ensureDurableDirectory, pinDirectory, syncDirectory, syncDirectoryBestEffort, syncDirectoryBestEffortSync, syncDirectorySync, type DirectoryReceipt, type DirectorySyncOutcome, type DurableDirectoryReceipt, type EnsureDurableDirectoryOptions, type PinnedDirectory, } from "./directory-durability.js";
2
2
  export { isHardlinkFallbackError, publishFileExclusive, type PublishFileExclusiveResult, type PublishFileExclusiveStrategy, type PublishFileExclusiveCleanup, type PublishFileExclusiveDirectorySyncFailure, type PublishFileExclusiveFailureDetails, type PublishFileExclusiveFailurePhase, type PublishFileExclusiveSyncFailurePolicy, } from "./publish-file.js";
3
- export { sha256File, type Sha256FileInput, type Sha256FileOptions, type Sha256FileResult, } from "./file-hash.js";
3
+ export { sha256File, sha256FileSync, type Sha256FileInput, type Sha256FileSyncInput, type Sha256FileOptions, type Sha256FileResult, } from "./file-hash.js";
4
4
  //# sourceMappingURL=durability.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"durability.d.ts","sourceRoot":"","sources":["../src/durability.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,YAAY,EACZ,aAAa,EACb,uBAAuB,EACvB,2BAA2B,EAC3B,iBAAiB,EACjB,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EACzB,KAAK,uBAAuB,EAC5B,KAAK,6BAA6B,EAClC,KAAK,eAAe,GACrB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,uBAAuB,EACvB,oBAAoB,EACpB,KAAK,0BAA0B,EAC/B,KAAK,4BAA4B,EACjC,KAAK,2BAA2B,EAChC,KAAK,wCAAwC,EAC7C,KAAK,kCAAkC,EACvC,KAAK,gCAAgC,EACrC,KAAK,qCAAqC,GAC3C,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,UAAU,EACV,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,GACtB,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"durability.d.ts","sourceRoot":"","sources":["../src/durability.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,YAAY,EACZ,aAAa,EACb,uBAAuB,EACvB,2BAA2B,EAC3B,iBAAiB,EACjB,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EACzB,KAAK,uBAAuB,EAC5B,KAAK,6BAA6B,EAClC,KAAK,eAAe,GACrB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,uBAAuB,EACvB,oBAAoB,EACpB,KAAK,0BAA0B,EAC/B,KAAK,4BAA4B,EACjC,KAAK,2BAA2B,EAChC,KAAK,wCAAwC,EAC7C,KAAK,kCAAkC,EACvC,KAAK,gCAAgC,EACrC,KAAK,qCAAqC,GAC3C,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,UAAU,EACV,cAAc,EACd,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,GACtB,MAAM,gBAAgB,CAAC"}
@@ -1,3 +1,3 @@
1
1
  export { ensureDurableDirectory, pinDirectory, syncDirectory, syncDirectoryBestEffort, syncDirectoryBestEffortSync, syncDirectorySync, } from "./directory-durability.js";
2
2
  export { isHardlinkFallbackError, publishFileExclusive, } from "./publish-file.js";
3
- export { sha256File, } from "./file-hash.js";
3
+ export { sha256File, sha256FileSync, } from "./file-hash.js";