@openclaw/fs-safe 0.13.1 → 0.15.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 (256) hide show
  1. package/CHANGELOG.md +64 -1
  2. package/README.md +10 -4
  3. package/dist/advanced.d.ts +1 -1
  4. package/dist/advanced.d.ts.map +1 -1
  5. package/dist/advanced.js +1 -1
  6. package/dist/archive-merge.d.ts.map +1 -1
  7. package/dist/archive-merge.js +113 -46
  8. package/dist/archive-read.d.ts.map +1 -1
  9. package/dist/archive-read.js +4 -4
  10. package/dist/archive-zip-directory.d.ts +4 -0
  11. package/dist/archive-zip-directory.d.ts.map +1 -1
  12. package/dist/archive-zip-directory.js +2 -0
  13. package/dist/archive-zip-entry.d.ts +6 -2
  14. package/dist/archive-zip-entry.d.ts.map +1 -1
  15. package/dist/archive-zip-entry.js +23 -8
  16. package/dist/archive-zip-integrity.d.ts.map +1 -1
  17. package/dist/archive-zip-integrity.js +3 -4
  18. package/dist/archive-zip-loader.d.ts.map +1 -1
  19. package/dist/archive-zip-loader.js +107 -31
  20. package/dist/archive-zip-names.d.ts +1 -0
  21. package/dist/archive-zip-names.d.ts.map +1 -1
  22. package/dist/archive-zip-names.js +6 -0
  23. package/dist/archive.js +6 -5
  24. package/dist/bounded-read-stream.d.ts +0 -1
  25. package/dist/bounded-read-stream.d.ts.map +1 -1
  26. package/dist/bounded-read-stream.js +0 -6
  27. package/dist/copy-publication.d.ts +6 -0
  28. package/dist/copy-publication.d.ts.map +1 -1
  29. package/dist/copy-publication.js +3 -0
  30. package/dist/copy-tree-portable.d.ts.map +1 -1
  31. package/dist/copy-tree-portable.js +44 -24
  32. package/dist/copy.d.ts.map +1 -1
  33. package/dist/copy.js +29 -11
  34. package/dist/directory-mode-owner.js +5 -5
  35. package/dist/file-handle-transfer.d.ts +2 -0
  36. package/dist/file-handle-transfer.d.ts.map +1 -1
  37. package/dist/file-handle-transfer.js +57 -2
  38. package/dist/file-identity.d.ts.map +1 -1
  39. package/dist/file-identity.js +18 -4
  40. package/dist/file-lock-sync-admission.d.ts +19 -0
  41. package/dist/file-lock-sync-admission.d.ts.map +1 -0
  42. package/dist/file-lock-sync-admission.js +93 -0
  43. package/dist/file-lock-sync-root-acquire.d.ts +4 -0
  44. package/dist/file-lock-sync-root-acquire.d.ts.map +1 -0
  45. package/dist/file-lock-sync-root-acquire.js +370 -0
  46. package/dist/file-lock-sync-root-arbitration.d.ts +18 -0
  47. package/dist/file-lock-sync-root-arbitration.d.ts.map +1 -0
  48. package/dist/file-lock-sync-root-arbitration.js +66 -0
  49. package/dist/file-lock-sync-root-held.d.ts +34 -0
  50. package/dist/file-lock-sync-root-held.d.ts.map +1 -0
  51. package/dist/file-lock-sync-root-held.js +393 -0
  52. package/dist/file-lock-sync-root-io.d.ts +44 -0
  53. package/dist/file-lock-sync-root-io.d.ts.map +1 -0
  54. package/dist/file-lock-sync-root-io.js +209 -0
  55. package/dist/file-lock-sync-root-mutation.d.ts +17 -0
  56. package/dist/file-lock-sync-root-mutation.d.ts.map +1 -0
  57. package/dist/file-lock-sync-root-mutation.js +277 -0
  58. package/dist/file-lock-sync-root-options.d.ts +20 -0
  59. package/dist/file-lock-sync-root-options.d.ts.map +1 -0
  60. package/dist/file-lock-sync-root-options.js +58 -0
  61. package/dist/file-lock-sync-root-registration.d.ts +2 -0
  62. package/dist/file-lock-sync-root-registration.d.ts.map +1 -0
  63. package/dist/file-lock-sync-root-registration.js +90 -0
  64. package/dist/file-lock-sync-root.d.ts +36 -0
  65. package/dist/file-lock-sync-root.d.ts.map +1 -0
  66. package/dist/file-lock-sync-root.js +361 -0
  67. package/dist/file-lock-sync-stale-admission.d.ts +24 -0
  68. package/dist/file-lock-sync-stale-admission.d.ts.map +1 -0
  69. package/dist/file-lock-sync-stale-admission.js +205 -0
  70. package/dist/file-lock-sync.d.ts.map +1 -1
  71. package/dist/file-lock-sync.js +245 -205
  72. package/dist/file-store-prune.d.ts.map +1 -1
  73. package/dist/file-store-prune.js +5 -1
  74. package/dist/file-store-sync-directory.d.ts.map +1 -1
  75. package/dist/file-store-sync-directory.js +14 -7
  76. package/dist/file-store-sync-write.js +3 -3
  77. package/dist/file-store.d.ts.map +1 -1
  78. package/dist/file-store.js +47 -12
  79. package/dist/guest-dispatch-python.js +1 -1
  80. package/dist/json-document-store.d.ts.map +1 -1
  81. package/dist/json-document-store.js +22 -15
  82. package/dist/json-durable-queue-ownership.d.ts +0 -1
  83. package/dist/json-durable-queue-ownership.d.ts.map +1 -1
  84. package/dist/json-durable-queue-ownership.js +0 -6
  85. package/dist/native-binding.d.ts +8 -0
  86. package/dist/native-binding.d.ts.map +1 -1
  87. package/dist/native-parent-admission.d.ts +3 -2
  88. package/dist/native-parent-admission.d.ts.map +1 -1
  89. package/dist/native-parent-admission.js +24 -5
  90. package/dist/native-pinned-write-windows.d.ts.map +1 -1
  91. package/dist/native-pinned-write-windows.js +7 -7
  92. package/dist/native-pinned-write.d.ts.map +1 -1
  93. package/dist/native-pinned-write.js +202 -172
  94. package/dist/native-policy-directory-observation.d.ts +12 -0
  95. package/dist/native-policy-directory-observation.d.ts.map +1 -0
  96. package/dist/native-policy-directory-observation.js +61 -0
  97. package/dist/native-policy-parent-windows.d.ts +14 -0
  98. package/dist/native-policy-parent-windows.d.ts.map +1 -0
  99. package/dist/native-policy-parent-windows.js +200 -0
  100. package/dist/native-rename-outcome.d.ts +4 -0
  101. package/dist/native-rename-outcome.d.ts.map +1 -0
  102. package/dist/native-rename-outcome.js +8 -0
  103. package/dist/native-staged-file.d.ts +2 -2
  104. package/dist/native-staged-file.d.ts.map +1 -1
  105. package/dist/native-staged-file.js +42 -40
  106. package/dist/output.d.ts.map +1 -1
  107. package/dist/output.js +12 -8
  108. package/dist/path-prefix.d.ts.map +1 -1
  109. package/dist/path-prefix.js +30 -8
  110. package/dist/path-suffix-aliases.d.ts +2 -0
  111. package/dist/path-suffix-aliases.d.ts.map +1 -1
  112. package/dist/path-suffix-aliases.js +25 -17
  113. package/dist/permission-exec.d.ts +2 -0
  114. package/dist/permission-exec.d.ts.map +1 -1
  115. package/dist/permission-exec.js +150 -21
  116. package/dist/permissions-windows.js +1 -1
  117. package/dist/pinned-mutation-admission.d.ts.map +1 -1
  118. package/dist/pinned-mutation-admission.js +23 -29
  119. package/dist/pinned-mutation-observation.d.ts +7 -2
  120. package/dist/pinned-mutation-observation.d.ts.map +1 -1
  121. package/dist/pinned-mutation-observation.js +81 -18
  122. package/dist/pinned-mutation-shared-route.d.ts +1 -0
  123. package/dist/pinned-mutation-shared-route.d.ts.map +1 -1
  124. package/dist/pinned-mutation-shared-route.js +1 -1
  125. package/dist/pinned-write-types.d.ts +2 -0
  126. package/dist/pinned-write-types.d.ts.map +1 -1
  127. package/dist/private-temp-workspace.d.ts.map +1 -1
  128. package/dist/private-temp-workspace.js +75 -121
  129. package/dist/regular-file.d.ts.map +1 -1
  130. package/dist/regular-file.js +1 -15
  131. package/dist/replace-directory.d.ts.map +1 -1
  132. package/dist/replace-directory.js +256 -18
  133. package/dist/replace-file-copy-fallback.d.ts.map +1 -1
  134. package/dist/replace-file-copy-fallback.js +62 -70
  135. package/dist/replace-file-copy-source.d.ts.map +1 -1
  136. package/dist/replace-file-copy-source.js +10 -12
  137. package/dist/replace-file-temp-owner.d.ts +5 -2
  138. package/dist/replace-file-temp-owner.d.ts.map +1 -1
  139. package/dist/replace-file-temp-owner.js +65 -30
  140. package/dist/replace-file.js +6 -6
  141. package/dist/retained-directory-replacement.d.ts +26 -0
  142. package/dist/retained-directory-replacement.d.ts.map +1 -0
  143. package/dist/retained-directory-replacement.js +193 -0
  144. package/dist/root-boundary.d.ts +1 -0
  145. package/dist/root-boundary.d.ts.map +1 -1
  146. package/dist/root-boundary.js +4 -0
  147. package/dist/root-context.d.ts +0 -8
  148. package/dist/root-context.d.ts.map +1 -1
  149. package/dist/root-context.js +0 -3
  150. package/dist/root-create-input.d.ts +6 -0
  151. package/dist/root-create-input.d.ts.map +1 -1
  152. package/dist/root-create-input.js +5 -1
  153. package/dist/root-directory-creation.d.ts.map +1 -1
  154. package/dist/root-directory-creation.js +5 -4
  155. package/dist/root-directory-list.d.ts +1 -0
  156. package/dist/root-directory-list.d.ts.map +1 -1
  157. package/dist/root-directory-list.js +1 -0
  158. package/dist/root-impl.d.ts.map +1 -1
  159. package/dist/root-impl.js +20 -11
  160. package/dist/root-move-noreplace.d.ts.map +1 -1
  161. package/dist/root-move-noreplace.js +2 -2
  162. package/dist/root-path-errors.d.ts +1 -0
  163. package/dist/root-path-errors.d.ts.map +1 -1
  164. package/dist/root-path-errors.js +11 -2
  165. package/dist/root-path-existing.d.ts.map +1 -1
  166. package/dist/root-path-existing.js +12 -37
  167. package/dist/root-path.js +1 -13
  168. package/dist/root-remove.d.ts +1 -0
  169. package/dist/root-remove.d.ts.map +1 -1
  170. package/dist/root-remove.js +4 -0
  171. package/dist/root-walk.d.ts +1 -1
  172. package/dist/root-walk.d.ts.map +1 -1
  173. package/dist/root-walk.js +17 -2
  174. package/dist/root-write-admission.d.ts +0 -2
  175. package/dist/root-write-admission.d.ts.map +1 -1
  176. package/dist/root-write-admission.js +1 -15
  177. package/dist/root-write-compatibility.d.ts +1 -2
  178. package/dist/root-write-compatibility.d.ts.map +1 -1
  179. package/dist/root-write-compatibility.js +17 -68
  180. package/dist/root-write-complete-parent.d.ts.map +1 -1
  181. package/dist/root-write-complete-parent.js +10 -24
  182. package/dist/root-write-lock-binding.d.ts +15 -0
  183. package/dist/root-write-lock-binding.d.ts.map +1 -0
  184. package/dist/root-write-lock-binding.js +162 -0
  185. package/dist/root-write-verification.d.ts.map +1 -1
  186. package/dist/root-write-verification.js +29 -42
  187. package/dist/secret-file.d.ts.map +1 -1
  188. package/dist/secret-file.js +2 -24
  189. package/dist/secret-read-async.d.ts.map +1 -1
  190. package/dist/secret-read-async.js +3 -24
  191. package/dist/secret-read-policy.d.ts +6 -2
  192. package/dist/secret-read-policy.d.ts.map +1 -1
  193. package/dist/secret-read-policy.js +26 -2
  194. package/dist/sibling-temp.d.ts.map +1 -1
  195. package/dist/sibling-temp.js +23 -12
  196. package/dist/sidecar-lock-acquire.d.ts +2 -28
  197. package/dist/sidecar-lock-acquire.d.ts.map +1 -1
  198. package/dist/sidecar-lock-acquire.js +288 -199
  199. package/dist/sidecar-lock-admission-context.d.ts +19 -0
  200. package/dist/sidecar-lock-admission-context.d.ts.map +1 -0
  201. package/dist/sidecar-lock-admission-context.js +60 -0
  202. package/dist/sidecar-lock-admission-parser.d.ts +43 -0
  203. package/dist/sidecar-lock-admission-parser.d.ts.map +1 -0
  204. package/dist/sidecar-lock-admission-parser.js +113 -0
  205. package/dist/sidecar-lock-admission.d.ts +35 -0
  206. package/dist/sidecar-lock-admission.d.ts.map +1 -0
  207. package/dist/sidecar-lock-admission.js +7 -0
  208. package/dist/sidecar-lock-reclaim.d.ts +9 -4
  209. package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
  210. package/dist/sidecar-lock-reclaim.js +80 -25
  211. package/dist/sidecar-lock-stale-admission.d.ts +39 -0
  212. package/dist/sidecar-lock-stale-admission.d.ts.map +1 -0
  213. package/dist/sidecar-lock-stale-admission.js +232 -0
  214. package/dist/sidecar-lock-target.d.ts +8 -0
  215. package/dist/sidecar-lock-target.d.ts.map +1 -0
  216. package/dist/sidecar-lock-target.js +55 -0
  217. package/dist/sidecar-lock.d.ts.map +1 -1
  218. package/dist/sidecar-lock.js +100 -16
  219. package/dist/staged-directory.d.ts +5 -2
  220. package/dist/staged-directory.d.ts.map +1 -1
  221. package/dist/staged-directory.js +37 -1
  222. package/dist/temp-workspace-admission.d.ts.map +1 -1
  223. package/dist/temp-workspace-admission.js +28 -4
  224. package/dist/temp-workspace-descriptor.d.ts.map +1 -1
  225. package/dist/temp-workspace-descriptor.js +9 -27
  226. package/dist/temp-workspace-owner.d.ts.map +1 -1
  227. package/dist/temp-workspace-owner.js +8 -8
  228. package/dist/walk.d.ts +5 -1
  229. package/dist/walk.d.ts.map +1 -1
  230. package/dist/walk.js +19 -6
  231. package/dist/windows-owner.d.ts.map +1 -1
  232. package/dist/windows-owner.js +2 -2
  233. package/docs/advanced.md +1 -1
  234. package/docs/archive.md +36 -9
  235. package/docs/atomic.md +85 -8
  236. package/docs/copy.md +35 -0
  237. package/docs/file-store.md +19 -0
  238. package/docs/guest.md +1 -1
  239. package/docs/json-store.md +5 -0
  240. package/docs/native-helper.md +10 -3
  241. package/docs/native.md +8 -0
  242. package/docs/output.md +6 -0
  243. package/docs/path-prefix.md +10 -0
  244. package/docs/path-suffix-aliases.md +51 -6
  245. package/docs/permissions.md +13 -0
  246. package/docs/public-api.md +3 -2
  247. package/docs/root.md +22 -3
  248. package/docs/sidecar-lock.md +109 -4
  249. package/docs/staged-file.md +7 -3
  250. package/docs/temp.md +20 -3
  251. package/docs/walk.md +67 -1
  252. package/docs/writing.md +11 -1
  253. package/package.json +10 -10
  254. package/dist/darwin-acl.d.ts +0 -4
  255. package/dist/darwin-acl.d.ts.map +0 -1
  256. package/dist/darwin-acl.js +0 -24
@@ -1 +1 @@
1
- {"version":3,"file":"replace-file-copy-fallback.d.ts","sourceRoot":"","sources":["../src/replace-file-copy-fallback.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,EAAE,EAAE,KAAK,WAAW,EAAc,MAAM,SAAS,CAAC;AAQ/D,MAAM,MAAM,oCAAoC,GAAG,QAAQ,CAAC;AAC5D,MAAM,MAAM,oCAAoC,GAAG,kBAAkB,GAAG,MAAM,CAAC;AAC/E,MAAM,MAAM,+BAA+B,GAAG,UAAU,GAAG,gBAAgB,CAAC;AAC5E,MAAM,MAAM,sCAAsC,GAAG;IACnD,OAAO,EAAE,+BAA+B,CAAC;CAC1C,CAAC;AAEF,KAAK,eAAe,GAAG;IACrB,KAAK,EAAE,cAAc,kBAAkB,EAAE,KAAK,CAAC;IAC/C,IAAI,EAAE,cAAc,kBAAkB,EAAE,IAAI,CAAC;IAC7C,EAAE,EAAE,cAAc,kBAAkB,EAAE,EAAE,CAAC;IACzC,MAAM,EAAE,cAAc,kBAAkB,EAAE,MAAM,CAAC;CAClD,CAAC;AAEF,KAAK,cAAc,GAAG,IAAI,CACxB,OAAO,MAAM,EACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,eAAe,GACf,WAAW,GACX,UAAU,GACV,UAAU,GACV,QAAQ,GACR,YAAY,GACZ,WAAW,CACd,CAAC;AAyFF,wBAAsB,+BAA+B,CACnD,QAAQ,EAAE,eAAe,EACzB,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,oCAAoC,GAC5C,OAAO,CAAC,IAAI,CAAC,CAwBf;AAED,wBAAgB,mCAAmC,CACjD,QAAQ,EAAE,cAAc,EACxB,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,oCAAoC,GAC5C,IAAI,CAwBN;AA+HD,wBAAsB,mBAAmB,CAAC,MAAM,EAAE;IAChD,QAAQ,EAAE,eAAe,CAAC;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB,CAAC,EAAE,oCAAoC,CAAC;IAC5D,OAAO,EAAE,oCAAoC,CAAC;IAC9C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sBAAsB,CAAC,EAAE,WAAW,CAAC;IACrC,IAAI,EAAE,OAAO,CAAC;CACf,GAAG,OAAO,CAAC,IAAI,CAAC,CA4DhB;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE;IAC9C,QAAQ,EAAE,cAAc,CAAC;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB,CAAC,EAAE,oCAAoC,CAAC;IAC5D,OAAO,EAAE,oCAAoC,CAAC;IAC9C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sBAAsB,CAAC,EAAE,WAAW,CAAC;IACrC,UAAU,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,IAAI,EAAE,OAAO,CAAC;CACf,GAAG,IAAI,CAkEP"}
1
+ {"version":3,"file":"replace-file-copy-fallback.d.ts","sourceRoot":"","sources":["../src/replace-file-copy-fallback.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,EAAE,EAAE,KAAK,WAAW,EAAc,MAAM,SAAS,CAAC;AAQ/D,MAAM,MAAM,oCAAoC,GAAG,QAAQ,CAAC;AAC5D,MAAM,MAAM,oCAAoC,GAAG,kBAAkB,GAAG,MAAM,CAAC;AAC/E,MAAM,MAAM,+BAA+B,GAAG,UAAU,GAAG,gBAAgB,CAAC;AAC5E,MAAM,MAAM,sCAAsC,GAAG;IACnD,OAAO,EAAE,+BAA+B,CAAC;CAC1C,CAAC;AAEF,KAAK,eAAe,GAAG;IACrB,KAAK,EAAE,cAAc,kBAAkB,EAAE,KAAK,CAAC;IAC/C,IAAI,EAAE,cAAc,kBAAkB,EAAE,IAAI,CAAC;IAC7C,EAAE,EAAE,cAAc,kBAAkB,EAAE,EAAE,CAAC;IACzC,MAAM,EAAE,cAAc,kBAAkB,EAAE,MAAM,CAAC;CAClD,CAAC;AAEF,KAAK,cAAc,GAAG,IAAI,CACxB,OAAO,MAAM,EACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,eAAe,GACf,WAAW,GACX,UAAU,GACV,UAAU,GACV,QAAQ,GACR,YAAY,GACZ,WAAW,CACd,CAAC;AAqHF,wBAAsB,+BAA+B,CACnD,QAAQ,EAAE,eAAe,EACzB,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,oCAAoC,GAC5C,OAAO,CAAC,IAAI,CAAC,CAIf;AAED,wBAAgB,mCAAmC,CACjD,QAAQ,EAAE,cAAc,EACxB,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,oCAAoC,GAC5C,IAAI,CAIN;AA+HD,wBAAsB,mBAAmB,CAAC,MAAM,EAAE;IAChD,QAAQ,EAAE,eAAe,CAAC;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB,CAAC,EAAE,oCAAoC,CAAC;IAC5D,OAAO,EAAE,oCAAoC,CAAC;IAC9C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sBAAsB,CAAC,EAAE,WAAW,CAAC;IACrC,IAAI,EAAE,OAAO,CAAC;CACf,GAAG,OAAO,CAAC,IAAI,CAAC,CAuEhB;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE;IAC9C,QAAQ,EAAE,cAAc,CAAC;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB,CAAC,EAAE,oCAAoC,CAAC;IAC5D,OAAO,EAAE,oCAAoC,CAAC;IAC9C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sBAAsB,CAAC,EAAE,WAAW,CAAC;IACrC,UAAU,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,IAAI,EAAE,OAAO,CAAC;CACf,GAAG,IAAI,CAuEP"}
@@ -10,10 +10,28 @@ const NOFOLLOW = SUPPORTS_NOFOLLOW ? syncFs.constants.O_NOFOLLOW : 0;
10
10
  const OPEN_READ_FLAGS = resolveReadOpenFlags();
11
11
  const OPEN_READ_WRITE_FLAGS = syncFs.constants.O_RDWR | NOFOLLOW;
12
12
  const OPEN_WRITE_EXCLUSIVE_FLAGS = syncFs.constants.O_WRONLY | syncFs.constants.O_CREAT | syncFs.constants.O_EXCL | NOFOLLOW;
13
+ function closeSyncAfterAdmissionFailure(fsModule, fd, error) {
14
+ try {
15
+ fsModule.closeSync(fd);
16
+ }
17
+ catch {
18
+ // Preserve the already-selected admission failure.
19
+ }
20
+ throw error;
21
+ }
13
22
  function notFound(error) {
14
23
  return error.code === "ENOENT";
15
24
  }
16
- function assertPinnedDestination(pathname, opened, dest, hardlinks) {
25
+ function assertPinnedDestination(pathname, opened, dest, hardlinks, admission) {
26
+ if (admission === "hardlinks") {
27
+ if (pathname.isSymbolicLink() || !pathname.isFile() || !sameFileIdentity(pathname, opened)) {
28
+ throw new FsSafeError("path-mismatch", `Atomic replace destination changed while opening: ${dest}`);
29
+ }
30
+ if (opened.nlink > 1) {
31
+ throw new FsSafeError("hardlink", `Hardlinked atomic replace destination not allowed: ${dest}`);
32
+ }
33
+ return;
34
+ }
17
35
  if (pathname.isSymbolicLink()) {
18
36
  throw new FsSafeError("symlink", `Refusing copy fallback through symlink destination: ${dest}`);
19
37
  }
@@ -27,7 +45,7 @@ function assertPinnedDestination(pathname, opened, dest, hardlinks) {
27
45
  throw new FsSafeError("hardlink", `Hardlinked copy fallback destination not allowed: ${dest}`);
28
46
  }
29
47
  }
30
- async function openPinnedDestination(fsModule, dest, hardlinks) {
48
+ async function openPinnedDestination(fsModule, dest, admission, hardlinks) {
31
49
  let preview;
32
50
  try {
33
51
  preview = fsModule === fs ? syncFs.lstatSync(dest) : await fsModule.lstat(dest);
@@ -39,22 +57,24 @@ async function openPinnedDestination(fsModule, dest, hardlinks) {
39
57
  }
40
58
  if (!preview)
41
59
  return null;
42
- if (preview.isSymbolicLink()) {
60
+ if (admission === "hardlinks" && (preview.isSymbolicLink() || !preview.isFile()))
61
+ return null;
62
+ if (admission === "restore" && preview.isSymbolicLink()) {
43
63
  throw new FsSafeError("symlink", `Refusing copy fallback through symlink destination: ${dest}`);
44
64
  }
45
- const handle = await fsModule.open(dest, OPEN_READ_WRITE_FLAGS);
65
+ const handle = await fsModule.open(dest, admission === "restore" ? OPEN_READ_WRITE_FLAGS : OPEN_READ_FLAGS);
46
66
  try {
47
67
  const opened = fsModule === fs ? syncFs.fstatSync(handle.fd) : await handle.stat();
48
68
  const current = fsModule === fs ? syncFs.lstatSync(dest) : await fsModule.lstat(dest);
49
- assertPinnedDestination(current, opened, dest, hardlinks);
50
- return { handle, stat: opened };
69
+ assertPinnedDestination(current, opened, dest, hardlinks, admission);
70
+ return handle;
51
71
  }
52
72
  catch (error) {
53
73
  await handle.close().catch(() => undefined);
54
74
  throw error;
55
75
  }
56
76
  }
57
- function openPinnedDestinationSync(fsModule, dest, hardlinks) {
77
+ function openPinnedDestinationSync(fsModule, dest, admission, hardlinks) {
58
78
  let preview;
59
79
  try {
60
80
  preview = fsModule.lstatSync(dest);
@@ -64,77 +84,34 @@ function openPinnedDestinationSync(fsModule, dest, hardlinks) {
64
84
  return null;
65
85
  throw error;
66
86
  }
67
- if (preview.isSymbolicLink()) {
87
+ if (admission === "hardlinks" && (preview.isSymbolicLink() || !preview.isFile()))
88
+ return null;
89
+ if (admission === "restore" && preview.isSymbolicLink()) {
68
90
  throw new FsSafeError("symlink", `Refusing copy fallback through symlink destination: ${dest}`);
69
91
  }
70
- const fd = fsModule.openSync(dest, OPEN_READ_WRITE_FLAGS);
92
+ const fd = fsModule.openSync(dest, admission === "restore" ? OPEN_READ_WRITE_FLAGS : OPEN_READ_FLAGS);
71
93
  try {
72
94
  const opened = fsModule.fstatSync(fd);
73
- assertPinnedDestination(fsModule.lstatSync(dest), opened, dest, hardlinks);
74
- return { fd, stat: opened };
95
+ assertPinnedDestination(fsModule.lstatSync(dest), opened, dest, hardlinks, admission);
96
+ return fd;
75
97
  }
76
98
  catch (error) {
77
- fsModule.closeSync(fd);
78
- throw error;
99
+ closeSyncAfterAdmissionFailure(fsModule, fd, error);
79
100
  }
80
101
  }
81
102
  export async function assertDestinationHardlinkPolicy(fsModule, dest, policy) {
82
103
  if (policy !== "reject")
83
104
  return;
84
- let preview;
85
- try {
86
- preview = fsModule === fs ? syncFs.lstatSync(dest) : await fsModule.lstat(dest);
87
- }
88
- catch (error) {
89
- if (notFound(error))
90
- return;
91
- throw error;
92
- }
93
- if (!preview || preview.isSymbolicLink() || !preview.isFile())
94
- return;
95
- const handle = await fsModule.open(dest, OPEN_READ_FLAGS);
96
- try {
97
- const opened = fsModule === fs ? syncFs.fstatSync(handle.fd) : await handle.stat();
98
- const current = fsModule === fs ? syncFs.lstatSync(dest) : await fsModule.lstat(dest);
99
- if (current.isSymbolicLink() || !current.isFile() || !sameFileIdentity(current, opened)) {
100
- throw new FsSafeError("path-mismatch", `Atomic replace destination changed while opening: ${dest}`);
101
- }
102
- if (opened.nlink > 1) {
103
- throw new FsSafeError("hardlink", `Hardlinked atomic replace destination not allowed: ${dest}`);
104
- }
105
- }
106
- finally {
105
+ const handle = await openPinnedDestination(fsModule, dest, "hardlinks");
106
+ if (handle)
107
107
  await handle.close().catch(() => undefined);
108
- }
109
108
  }
110
109
  export function assertDestinationHardlinkPolicySync(fsModule, dest, policy) {
111
110
  if (policy !== "reject")
112
111
  return;
113
- let preview;
114
- try {
115
- preview = fsModule.lstatSync(dest);
116
- }
117
- catch (error) {
118
- if (notFound(error))
119
- return;
120
- throw error;
121
- }
122
- if (preview.isSymbolicLink() || !preview.isFile())
123
- return;
124
- const fd = fsModule.openSync(dest, OPEN_READ_FLAGS);
125
- try {
126
- const opened = fsModule.fstatSync(fd);
127
- const current = fsModule.lstatSync(dest);
128
- if (current.isSymbolicLink() || !current.isFile() || !sameFileIdentity(current, opened)) {
129
- throw new FsSafeError("path-mismatch", `Atomic replace destination changed while opening: ${dest}`);
130
- }
131
- if (opened.nlink > 1) {
132
- throw new FsSafeError("hardlink", `Hardlinked atomic replace destination not allowed: ${dest}`);
133
- }
134
- }
135
- finally {
112
+ const fd = openPinnedDestinationSync(fsModule, dest, "hardlinks");
113
+ if (fd !== null)
136
114
  fsModule.closeSync(fd);
137
- }
138
115
  }
139
116
  function restoreReadOptions(stat, maxBytes) {
140
117
  return {
@@ -183,7 +160,7 @@ function writeAllSync(fsModule, fd, data) {
183
160
  function restoreFailure(writeError, cleanup, restoreError) {
184
161
  const primary = writeError instanceof Error ? writeError : new Error(String(writeError));
185
162
  const details = { cleanup };
186
- const cause = restoreError
163
+ const cause = cleanup === "restore-failed"
187
164
  ? new AggregateError([primary, restoreError], "copy fallback and original restoration both failed")
188
165
  : primary;
189
166
  return new FsSafeError("helper-failed", cleanup === "restored"
@@ -246,11 +223,12 @@ export async function copyFallbackReplace(params) {
246
223
  });
247
224
  const { replacement } = source;
248
225
  let destHandle = null;
226
+ let closeRequiredForSuccess = false;
249
227
  try {
250
228
  if (params.restore === "restore-original") {
251
- const pinned = await openPinnedDestination(params.fsModule, params.dest, params.destinationHardlinks);
229
+ const pinned = await openPinnedDestination(params.fsModule, params.dest, "restore", params.destinationHardlinks);
252
230
  if (pinned) {
253
- destHandle = pinned.handle;
231
+ destHandle = pinned;
254
232
  await replacePinnedWithRestore(params.fsModule, destHandle, replacement, params.maxRestoreBytes, source.mode);
255
233
  }
256
234
  }
@@ -277,10 +255,20 @@ export async function copyFallbackReplace(params) {
277
255
  if (params.sync) {
278
256
  await destHandle.sync();
279
257
  }
258
+ closeRequiredForSuccess = !params.sync;
280
259
  }
281
260
  }
282
261
  finally {
283
- await destHandle?.close().catch(() => undefined);
262
+ if (destHandle) {
263
+ try {
264
+ await destHandle.close();
265
+ }
266
+ catch (closeError) {
267
+ if (closeRequiredForSuccess) {
268
+ throw closeError;
269
+ }
270
+ }
271
+ }
284
272
  }
285
273
  }
286
274
  export function copyFallbackReplaceSync(params) {
@@ -291,11 +279,12 @@ export function copyFallbackReplaceSync(params) {
291
279
  });
292
280
  const { replacement } = source;
293
281
  let destFd;
282
+ let closeRequiredForSuccess = false;
294
283
  try {
295
284
  if (params.restore === "restore-original") {
296
- const pinned = openPinnedDestinationSync(params.fsModule, params.dest, params.destinationHardlinks);
297
- if (pinned) {
298
- destFd = pinned.fd;
285
+ const pinned = openPinnedDestinationSync(params.fsModule, params.dest, "restore", params.destinationHardlinks);
286
+ if (pinned !== null) {
287
+ destFd = pinned;
299
288
  replacePinnedWithRestoreSync(params.fsModule, destFd, replacement, params.maxRestoreBytes, source.mode, params.fchmodSync);
300
289
  }
301
290
  }
@@ -321,6 +310,7 @@ export function copyFallbackReplaceSync(params) {
321
310
  if (params.sync) {
322
311
  params.fsModule.fsyncSync(destFd);
323
312
  }
313
+ closeRequiredForSuccess = !params.sync;
324
314
  }
325
315
  }
326
316
  finally {
@@ -328,8 +318,10 @@ export function copyFallbackReplaceSync(params) {
328
318
  try {
329
319
  params.fsModule.closeSync(destFd);
330
320
  }
331
- catch {
332
- // Best-effort close after fallback replacement.
321
+ catch (closeError) {
322
+ if (closeRequiredForSuccess) {
323
+ throw closeError;
324
+ }
333
325
  }
334
326
  }
335
327
  }
@@ -1 +1 @@
1
- {"version":3,"file":"replace-file-copy-source.d.ts","sourceRoot":"","sources":["../src/replace-file-copy-source.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,EAAE,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAQnD,KAAK,qBAAqB,GAAG,IAAI,CAAC,cAAc,kBAAkB,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC,CAAC;AACvF,KAAK,oBAAoB,GAAG,IAAI,CAC9B,OAAO,MAAM,EACb,WAAW,GAAG,WAAW,GAAG,WAAW,GAAG,UAAU,GAAG,UAAU,CAClE,CAAC;AAqDF,wBAAsB,mBAAmB,CAAC,MAAM,EAAE;IAChD,QAAQ,EAAE,qBAAqB,CAAC;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,gBAAgB,CAAC,EAAE,WAAW,CAAC;CAChC,GAAG,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CA8BjD;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE;IAC9C,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,gBAAgB,CAAC,EAAE,WAAW,CAAC;CAChC,GAAG;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CA8BxC"}
1
+ {"version":3,"file":"replace-file-copy-source.d.ts","sourceRoot":"","sources":["../src/replace-file-copy-source.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,EAAE,EAAE,KAAK,WAAW,EAAc,MAAM,SAAS,CAAC;AAQ/D,KAAK,qBAAqB,GAAG,IAAI,CAAC,cAAc,kBAAkB,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC,CAAC;AACvF,KAAK,oBAAoB,GAAG,IAAI,CAC9B,OAAO,MAAM,EACb,WAAW,GAAG,WAAW,GAAG,WAAW,GAAG,UAAU,GAAG,UAAU,CAClE,CAAC;AA8DF,wBAAsB,mBAAmB,CAAC,MAAM,EAAE;IAChD,QAAQ,EAAE,qBAAqB,CAAC;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,gBAAgB,CAAC,EAAE,WAAW,CAAC;CAChC,GAAG,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAyBjD;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE;IAC9C,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,gBAAgB,CAAC,EAAE,WAAW,CAAC;CAChC,GAAG;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAyBxC"}
@@ -17,6 +17,14 @@ function assertSourcePreview(source, src) {
17
17
  throw new FsSafeError("hardlink", `Hardlinked copy fallback source not allowed: ${src}`);
18
18
  }
19
19
  }
20
+ function assertOpenedSource(opened, current, src) {
21
+ if (!opened.isFile() || current.isSymbolicLink() || !sameFileIdentity(opened, current)) {
22
+ throw new FsSafeError("path-mismatch", `Copy fallback source changed while opening: ${src}`);
23
+ }
24
+ if (opened.nlink !== 1) {
25
+ throw new FsSafeError("hardlink", `Hardlinked copy fallback source not allowed: ${src}`);
26
+ }
27
+ }
20
28
  function sourceSymlinkError(src, cause) {
21
29
  return new FsSafeError("symlink", `Refusing copy fallback from non-file source: ${src}`, {
22
30
  cause,
@@ -66,12 +74,7 @@ export async function readOwnedCopySource(params) {
66
74
  const opened = params.fsModule === fs ? syncFs.fstatSync(handle.fd) : await handle.stat();
67
75
  const current = params.fsModule === fs
68
76
  ? syncFs.lstatSync(params.src) : await params.fsModule.lstat(params.src);
69
- if (!opened.isFile() || current.isSymbolicLink() || !sameFileIdentity(opened, current)) {
70
- throw new FsSafeError("path-mismatch", `Copy fallback source changed while opening: ${params.src}`);
71
- }
72
- if (opened.nlink !== 1) {
73
- throw new FsSafeError("hardlink", `Hardlinked copy fallback source not allowed: ${params.src}`);
74
- }
77
+ assertOpenedSource(opened, current, params.src);
75
78
  return { replacement: await handle.readFile(), mode: opened.mode };
76
79
  }
77
80
  finally {
@@ -88,12 +91,7 @@ export function readOwnedCopySourceSync(params) {
88
91
  }
89
92
  const opened = params.fsModule.fstatSync(fd);
90
93
  const current = params.fsModule.lstatSync(params.src);
91
- if (!opened.isFile() || current.isSymbolicLink() || !sameFileIdentity(opened, current)) {
92
- throw new FsSafeError("path-mismatch", `Copy fallback source changed while opening: ${params.src}`);
93
- }
94
- if (opened.nlink !== 1) {
95
- throw new FsSafeError("hardlink", `Hardlinked copy fallback source not allowed: ${params.src}`);
96
- }
94
+ assertOpenedSource(opened, current, params.src);
97
95
  return { replacement: readAllSync(params.fsModule, fd, opened.size), mode: opened.mode };
98
96
  }
99
97
  finally {
@@ -3,6 +3,9 @@ import fs, { type FileHandle } from "node:fs/promises";
3
3
  import { type AnyAsyncDirectoryGuard } from "./directory-guard.js";
4
4
  type AsyncOwnerFileSystem = Pick<typeof fs, "lstat" | "open" | "unlink">;
5
5
  type SyncOwnerFileSystem = Pick<typeof syncFs, "closeSync" | "fstatSync" | "lstatSync" | "openSync" | "readFileSync" | "unlinkSync">;
6
+ export type AtomicTempFailure = Readonly<{
7
+ error: unknown;
8
+ }>;
6
9
  export declare function removePathIfIdentityUnchanged(targetPath: string, identity: Pick<BigIntStats, "dev" | "ino">): Promise<void>;
7
10
  export declare function cleanupPinnedFilePath(params: {
8
11
  pathname: string;
@@ -26,7 +29,7 @@ export declare class AsyncAtomicTempOwner {
26
29
  markRenamed(): void;
27
30
  finish(params: {
28
31
  fsModule: AsyncOwnerFileSystem;
29
- originalError?: unknown;
32
+ originalFailure?: AtomicTempFailure;
30
33
  throwOnCleanupError: boolean;
31
34
  }): Promise<void>;
32
35
  }
@@ -46,7 +49,7 @@ export declare class SyncAtomicTempOwner {
46
49
  markRenamed(): void;
47
50
  finish(params: {
48
51
  fsModule: SyncOwnerFileSystem;
49
- originalError?: unknown;
52
+ originalFailure?: AtomicTempFailure;
50
53
  throwOnCleanupError: boolean;
51
54
  }): void;
52
55
  }
@@ -1 +1 @@
1
- {"version":3,"file":"replace-file-temp-owner.d.ts","sourceRoot":"","sources":["../src/replace-file-temp-owner.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,EAAE,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,EAAE,EAAE,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEvD,OAAO,EAAwD,KAAK,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAQzH,KAAK,oBAAoB,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC,CAAC;AACzE,KAAK,mBAAmB,GAAG,IAAI,CAC7B,OAAO,MAAM,EACb,WAAW,GAAG,WAAW,GAAG,WAAW,GAAG,UAAU,GAAG,cAAc,GAAG,YAAY,CACrF,CAAC;AAIF,wBAAsB,6BAA6B,CACjD,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,IAAI,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC,GACzC,OAAO,CAAC,IAAI,CAAC,CAOf;AA8DD,wBAAsB,qBAAqB,CAAC,MAAM,EAAE;IAClD,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,UAAU,CAAC;IACnB,QAAQ,CAAC,EAAE,WAAW,CAAC;IACvB,WAAW,EAAE,sBAAsB,CAAC;CACrC,GAAG,OAAO,CAAC,IAAI,CAAC,CAuBhB;AA+BD,qBAAa,oBAAoB;;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAM1B,YAAY,QAAQ,EAAE,MAAM,EAG3B;IAED,KAAK,IAAI,IAAI,CAEZ;IAED,QAAQ,CAAC,UAAU,aAAc,WAAW,KAAG,IAAI,CAGjD;IAEF,KAAK,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,UAAU,CAAC;QAAC,QAAQ,EAAE,WAAW,CAAA;KAAE,GAAG,IAAI,CAG/D;IAED,IAAI,QAAQ,IAAI,WAAW,CAG1B;IAEK,aAAa,CAAC,QAAQ,EAAE,oBAAoB,EAAE,QAAQ,SAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAoB3F;IAEK,eAAe,CACnB,QAAQ,EAAE,oBAAoB,EAC9B,QAAQ,EAAE,MAAM,EAChB,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,IAAI,CAAC,CA4Cf;IAED,WAAW,IAAI,IAAI,CAGlB;IAEK,MAAM,CAAC,MAAM,EAAE;QACnB,QAAQ,EAAE,oBAAoB,CAAC;QAC/B,aAAa,CAAC,EAAE,OAAO,CAAC;QACxB,mBAAmB,EAAE,OAAO,CAAC;KAC9B,GAAG,OAAO,CAAC,IAAI,CAAC,CA8BhB;CACF;AAED,qBAAa,mBAAmB;;IAC9B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAM1B,YAAY,QAAQ,EAAE,MAAM,EAG3B;IAED,KAAK,IAAI,IAAI,CAEZ;IAED,QAAQ,CAAC,UAAU,aAAc,WAAW,KAAG,IAAI,CAGjD;IAEF,KAAK,CAAC,IAAI,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,WAAW,CAAA;KAAE,GAAG,IAAI,CAGvD;IAED,IAAI,QAAQ,IAAI,WAAW,CAG1B;IAED,aAAa,CAAC,QAAQ,EAAE,mBAAmB,EAAE,QAAQ,SAAgB,GAAG,IAAI,CAkB3E;IAED,eAAe,CACb,QAAQ,EAAE,mBAAmB,EAC7B,QAAQ,EAAE,MAAM,EAChB,YAAY,CAAC,EAAE,MAAM,GACpB,IAAI,CAkDN;IAED,WAAW,IAAI,IAAI,CAGlB;IAED,MAAM,CAAC,MAAM,EAAE;QACb,QAAQ,EAAE,mBAAmB,CAAC;QAC9B,aAAa,CAAC,EAAE,OAAO,CAAC;QACxB,mBAAmB,EAAE,OAAO,CAAC;KAC9B,GAAG,IAAI,CAkCP;CACF"}
1
+ {"version":3,"file":"replace-file-temp-owner.d.ts","sourceRoot":"","sources":["../src/replace-file-temp-owner.ts"],"names":[],"mappings":"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"}
@@ -9,6 +9,33 @@ import { withAsyncDirectoryGuards } from "./guarded-mutation.js";
9
9
  import { inspectFileIdentity, inspectFileIdentitySync } from "./strict-file-identity.js";
10
10
  import { registerTempPathForExit } from "./temp-cleanup.js";
11
11
  const PUBLISHED_READ_FLAGS = resolveReadOpenFlags();
12
+ function hasErrorCode(error, expected) {
13
+ if ((typeof error !== "object" || error === null) && typeof error !== "function") {
14
+ return false;
15
+ }
16
+ try {
17
+ return Reflect.get(error, "code") === expected;
18
+ }
19
+ catch {
20
+ return false;
21
+ }
22
+ }
23
+ function describeFailure(error) {
24
+ try {
25
+ return String(error);
26
+ }
27
+ catch {
28
+ return "<unprintable failure>";
29
+ }
30
+ }
31
+ function isErrorValue(error) {
32
+ try {
33
+ return error instanceof Error;
34
+ }
35
+ catch {
36
+ return false;
37
+ }
38
+ }
12
39
  export async function removePathIfIdentityUnchanged(targetPath, identity) {
13
40
  const parentGuard = await createAsyncDirectoryGuard(path.dirname(targetPath), { bigint: true });
14
41
  await withAsyncDirectoryGuards([parentGuard], async () => {
@@ -34,11 +61,11 @@ function missingOwnedFile(pathname, cause) {
34
61
  cause,
35
62
  });
36
63
  }
37
- function cleanupFailure(originalError, cleanupError) {
38
- if (originalError !== undefined) {
39
- return new Error(`Atomic file replace failed (${String(originalError)}); cleanup also failed (${String(cleanupError)})`, { cause: originalError });
64
+ function cleanupFailure(originalFailure, cleanupError) {
65
+ if (originalFailure) {
66
+ return new Error(`Atomic file replace failed (${describeFailure(originalFailure.error)}); cleanup also failed (${describeFailure(cleanupError)})`, { cause: originalFailure.error });
40
67
  }
41
- return cleanupError instanceof Error ? cleanupError : new Error(String(cleanupError));
68
+ return isErrorValue(cleanupError) ? cleanupError : new Error(describeFailure(cleanupError));
42
69
  }
43
70
  async function cleanupOwnedPath(params) {
44
71
  if (!params.identity)
@@ -57,10 +84,10 @@ async function cleanupOwnedPath(params) {
57
84
  return true;
58
85
  }
59
86
  catch (cleanupError) {
60
- if (cleanupError.code === "ENOENT")
87
+ if (hasErrorCode(cleanupError, "ENOENT"))
61
88
  return true;
62
89
  if (params.throwOnCleanupError) {
63
- throw cleanupFailure(params.originalError, cleanupError);
90
+ throw cleanupFailure(params.originalFailure, cleanupError);
64
91
  }
65
92
  return false;
66
93
  }
@@ -108,10 +135,10 @@ function cleanupOwnedPathSync(params) {
108
135
  return true;
109
136
  }
110
137
  catch (cleanupError) {
111
- if (cleanupError.code === "ENOENT")
138
+ if (hasErrorCode(cleanupError, "ENOENT"))
112
139
  return true;
113
140
  if (params.throwOnCleanupError) {
114
- throw cleanupFailure(params.originalError, cleanupError);
141
+ throw cleanupFailure(params.originalFailure, cleanupError);
115
142
  }
116
143
  return false;
117
144
  }
@@ -202,7 +229,9 @@ export class AsyncAtomicTempOwner {
202
229
  if (sha256Hex(await published.readFile()) !== expectedHash) {
203
230
  throw new FsSafeError("path-mismatch", `Atomic replace published content changed: ${pathname}`);
204
231
  }
205
- await this.#handle?.close();
232
+ const previousHandle = this.#handle;
233
+ this.#handle = undefined;
234
+ await previousHandle?.close();
206
235
  this.#handle = published;
207
236
  this.#identity = identity;
208
237
  published = undefined;
@@ -216,7 +245,7 @@ export class AsyncAtomicTempOwner {
216
245
  this.#unregister();
217
246
  }
218
247
  async finish(params) {
219
- let deferredError;
248
+ let deferredFailure;
220
249
  let cleanupComplete = !this.#exists;
221
250
  if (this.#exists) {
222
251
  try {
@@ -224,28 +253,32 @@ export class AsyncAtomicTempOwner {
224
253
  fsModule: params.fsModule,
225
254
  pathname: this.pathname,
226
255
  identity: this.#identity,
227
- originalError: params.originalError,
256
+ originalFailure: params.originalFailure,
228
257
  throwOnCleanupError: params.throwOnCleanupError,
229
258
  });
230
259
  }
231
260
  catch (error) {
232
- deferredError = error;
261
+ deferredFailure = { error };
233
262
  }
234
263
  }
235
264
  if (cleanupComplete)
236
265
  this.#unregister();
266
+ const handle = this.#handle;
267
+ this.#handle = undefined;
237
268
  try {
238
- await this.#handle?.close();
269
+ await handle?.close();
239
270
  }
240
271
  catch (closeError) {
241
- deferredError = deferredError
242
- ? new AggregateError([deferredError, closeError], "Atomic temp cleanup and close failed")
243
- : params.originalError !== undefined
244
- ? new AggregateError([params.originalError, closeError], "Atomic file replace and close failed")
245
- : closeError;
272
+ deferredFailure = {
273
+ error: deferredFailure
274
+ ? new AggregateError([deferredFailure.error, closeError], "Atomic temp cleanup and close failed")
275
+ : params.originalFailure
276
+ ? new AggregateError([params.originalFailure.error, closeError], "Atomic file replace and close failed")
277
+ : closeError,
278
+ };
246
279
  }
247
- if (deferredError)
248
- throw deferredError;
280
+ if (deferredFailure)
281
+ throw deferredFailure.error;
249
282
  }
250
283
  }
251
284
  export class SyncAtomicTempOwner {
@@ -353,7 +386,7 @@ export class SyncAtomicTempOwner {
353
386
  this.#unregister();
354
387
  }
355
388
  finish(params) {
356
- let deferredError;
389
+ let deferredFailure;
357
390
  let cleanupComplete = !this.#exists;
358
391
  if (this.#exists) {
359
392
  try {
@@ -361,12 +394,12 @@ export class SyncAtomicTempOwner {
361
394
  fsModule: params.fsModule,
362
395
  pathname: this.pathname,
363
396
  identity: this.#identity,
364
- originalError: params.originalError,
397
+ originalFailure: params.originalFailure,
365
398
  throwOnCleanupError: params.throwOnCleanupError,
366
399
  });
367
400
  }
368
401
  catch (error) {
369
- deferredError = error;
402
+ deferredFailure = { error };
370
403
  }
371
404
  }
372
405
  if (cleanupComplete)
@@ -378,14 +411,16 @@ export class SyncAtomicTempOwner {
378
411
  params.fsModule.closeSync(fd);
379
412
  }
380
413
  catch (closeError) {
381
- deferredError = deferredError
382
- ? new AggregateError([deferredError, closeError], "Atomic temp cleanup and close failed")
383
- : params.originalError !== undefined
384
- ? new AggregateError([params.originalError, closeError], "Atomic file replace and close failed")
385
- : closeError;
414
+ deferredFailure = {
415
+ error: deferredFailure
416
+ ? new AggregateError([deferredFailure.error, closeError], "Atomic temp cleanup and close failed")
417
+ : params.originalFailure
418
+ ? new AggregateError([params.originalFailure.error, closeError], "Atomic file replace and close failed")
419
+ : closeError,
420
+ };
386
421
  }
387
422
  }
388
- if (deferredError)
389
- throw deferredError;
423
+ if (deferredFailure)
424
+ throw deferredFailure.error;
390
425
  }
391
426
  }
@@ -171,7 +171,7 @@ async function replaceFileAtomicUnserialized(options, filePath, renameIdentity,
171
171
  const expectedHash = atomicExpectedContentHash(renameIdentity, options.content);
172
172
  const tempPath = buildReplaceTempPath(filePath, options.tempPrefix);
173
173
  const tempOwner = new AsyncAtomicTempOwner(tempPath);
174
- let originalError;
174
+ let originalFailure;
175
175
  try {
176
176
  await fsModule.mkdir(fsModule === fs ? recursiveMkdirPath(dir) : dir, { recursive: true, mode: dirMode });
177
177
  await applyDirectoryMode({ fsModule, dirPath: dir, mode: dirMode });
@@ -229,13 +229,13 @@ async function replaceFileAtomicUnserialized(options, filePath, renameIdentity,
229
229
  return result;
230
230
  }
231
231
  catch (error) {
232
- originalError = error;
232
+ originalFailure = { error };
233
233
  throw error;
234
234
  }
235
235
  finally {
236
236
  await tempOwner.finish({
237
237
  fsModule,
238
- originalError,
238
+ originalFailure,
239
239
  throwOnCleanupError: options.throwOnCleanupError === true,
240
240
  });
241
241
  }
@@ -271,7 +271,7 @@ function replaceFileAtomicSyncUnserialized(options, filePath, renameIdentity) {
271
271
  }
272
272
  const tempPath = buildReplaceTempPath(filePath, options.tempPrefix);
273
273
  const tempOwner = new SyncAtomicTempOwner(tempPath);
274
- let originalError;
274
+ let originalFailure;
275
275
  try {
276
276
  fsModule.mkdirSync(fsModule === syncFs ? recursiveMkdirPath(dir) : dir, { recursive: true, mode: dirMode });
277
277
  applyDirectoryModeSync({ fsModule, dirPath: dir, mode: dirMode, fchmodSync });
@@ -327,13 +327,13 @@ function replaceFileAtomicSyncUnserialized(options, filePath, renameIdentity) {
327
327
  return result;
328
328
  }
329
329
  catch (error) {
330
- originalError = error;
330
+ originalFailure = { error };
331
331
  throw error;
332
332
  }
333
333
  finally {
334
334
  tempOwner.finish({
335
335
  fsModule,
336
- originalError,
336
+ originalFailure,
337
337
  throwOnCleanupError: options.throwOnCleanupError === true,
338
338
  });
339
339
  }
@@ -0,0 +1,26 @@
1
+ import type { BigIntStats } from "node:fs";
2
+ import type { NativeBinding, NativeOwnedTreeRemovalResult } from "./native-binding.js";
3
+ type RenameBinding = NativeBinding & Required<Pick<NativeBinding, "renameNoReplaceWithIdentity">>;
4
+ export type RetainedReplacementParent = Readonly<{
5
+ path: string;
6
+ realPath: string;
7
+ identity: Pick<BigIntStats, "dev" | "ino">;
8
+ }>;
9
+ export declare class RetainedDirectoryReplacement {
10
+ #private;
11
+ private constructor();
12
+ static requireBinding(): RenameBinding;
13
+ static retain(params: {
14
+ targetParent: RetainedReplacementParent;
15
+ stagedParent: RetainedReplacementParent;
16
+ originalTarget?: Readonly<{
17
+ path: string;
18
+ identity: Pick<BigIntStats, "dev" | "ino">;
19
+ }>;
20
+ }, binding?: RenameBinding): RetainedDirectoryReplacement;
21
+ renameNoReplace(sourceParent: "staged" | "target", sourceBasename: string, targetBasename: string, expectedSource: Pick<BigIntStats, "dev" | "ino">): void;
22
+ removeOriginal(backupBasename: string): Promise<NativeOwnedTreeRemovalResult["outcome"]>;
23
+ close(): void;
24
+ }
25
+ export {};
26
+ //# sourceMappingURL=retained-directory-replacement.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retained-directory-replacement.d.ts","sourceRoot":"","sources":["../src/retained-directory-replacement.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAI3C,OAAO,KAAK,EAAE,aAAa,EAAE,4BAA4B,EAAE,MAAM,qBAAqB,CAAC;AAGvF,KAAK,aAAa,GAAG,aAAa,GAAG,QAAQ,CAAC,IAAI,CAAC,aAAa,EAC9D,6BAA6B,CAAC,CAAC,CAAC;AAIlC,MAAM,MAAM,yBAAyB,GAAG,QAAQ,CAAC;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,IAAI,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC,CAAC;CAC5C,CAAC,CAAC;AAsHH,qBAAa,4BAA4B;;IAQvC,OAAO,eAUN;IAED,MAAM,CAAC,cAAc,IAAI,aAAa,CAQrC;IAED,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE;QACpB,YAAY,EAAE,yBAAyB,CAAC;QACxC,YAAY,EAAE,yBAAyB,CAAC;QACxC,cAAc,CAAC,EAAE,QAAQ,CAAC;YACxB,IAAI,EAAE,MAAM,CAAC;YACb,QAAQ,EAAE,IAAI,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC,CAAC;SAC5C,CAAC,CAAC;KACJ,EAAE,OAAO,gBAAgD,GAAG,4BAA4B,CAiDxF;IAED,eAAe,CACb,YAAY,EAAE,QAAQ,GAAG,QAAQ,EACjC,cAAc,EAAE,MAAM,EACtB,cAAc,EAAE,MAAM,EACtB,cAAc,EAAE,IAAI,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC,GAC/C,IAAI,CAYN;IAEK,cAAc,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,4BAA4B,CAAC,SAAS,CAAC,CAAC,CA2B7F;IAED,KAAK,IAAI,IAAI,CAQZ;CACF"}