@openclaw/fs-safe 0.8.6 → 0.10.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 (246) hide show
  1. package/CHANGELOG.md +93 -0
  2. package/README.md +17 -0
  3. package/dist/advanced.d.ts +1 -0
  4. package/dist/advanced.d.ts.map +1 -1
  5. package/dist/advanced.js +1 -0
  6. package/dist/archive-crc32.d.ts.map +1 -1
  7. package/dist/archive-crc32.js +6 -1
  8. package/dist/archive-deadline.d.ts.map +1 -1
  9. package/dist/archive-deadline.js +3 -4
  10. package/dist/archive-durability.d.ts +24 -0
  11. package/dist/archive-durability.d.ts.map +1 -0
  12. package/dist/archive-durability.js +180 -0
  13. package/dist/archive-gzip-tail.d.ts +2 -0
  14. package/dist/archive-gzip-tail.d.ts.map +1 -1
  15. package/dist/archive-gzip-tail.js +22 -4
  16. package/dist/archive-input.js +4 -4
  17. package/dist/archive-kind.d.ts.map +1 -1
  18. package/dist/archive-kind.js +3 -2
  19. package/dist/archive-merge.d.ts +1 -0
  20. package/dist/archive-merge.d.ts.map +1 -1
  21. package/dist/archive-merge.js +42 -9
  22. package/dist/archive-native.d.ts +1 -0
  23. package/dist/archive-native.d.ts.map +1 -1
  24. package/dist/archive-native.js +4 -2
  25. package/dist/archive-options.d.ts +2 -0
  26. package/dist/archive-options.d.ts.map +1 -1
  27. package/dist/archive-parser.wasm +0 -0
  28. package/dist/archive-read.d.ts.map +1 -1
  29. package/dist/archive-read.js +72 -70
  30. package/dist/archive-staging.d.ts +1 -1
  31. package/dist/archive-staging.d.ts.map +1 -1
  32. package/dist/archive-staging.js +24 -19
  33. package/dist/archive-tar-stream.d.ts +11 -4
  34. package/dist/archive-tar-stream.d.ts.map +1 -1
  35. package/dist/archive-tar-stream.js +20 -8
  36. package/dist/archive-tar-wasm.d.ts.map +1 -1
  37. package/dist/archive-tar-wasm.js +2 -1
  38. package/dist/archive.d.ts.map +1 -1
  39. package/dist/archive.js +9 -4
  40. package/dist/bounded-read.d.ts +7 -0
  41. package/dist/bounded-read.d.ts.map +1 -1
  42. package/dist/bounded-read.js +73 -43
  43. package/dist/clone-metadata.d.ts +19 -0
  44. package/dist/clone-metadata.d.ts.map +1 -0
  45. package/dist/clone-metadata.js +32 -0
  46. package/dist/copy-file-input.d.ts +23 -0
  47. package/dist/copy-file-input.d.ts.map +1 -0
  48. package/dist/copy-file-input.js +91 -0
  49. package/dist/copy-policy.d.ts +3 -0
  50. package/dist/copy-policy.d.ts.map +1 -0
  51. package/dist/copy-policy.js +8 -0
  52. package/dist/copy-publication.d.ts +15 -0
  53. package/dist/copy-publication.d.ts.map +1 -0
  54. package/dist/copy-publication.js +30 -0
  55. package/dist/copy-tree-portable.d.ts +9 -0
  56. package/dist/copy-tree-portable.d.ts.map +1 -0
  57. package/dist/copy-tree-portable.js +191 -0
  58. package/dist/copy.d.ts +16 -0
  59. package/dist/copy.d.ts.map +1 -0
  60. package/dist/copy.js +123 -0
  61. package/dist/directory-mode-node.d.ts.map +1 -1
  62. package/dist/directory-mode-node.js +4 -4
  63. package/dist/durability.d.ts +1 -1
  64. package/dist/durability.d.ts.map +1 -1
  65. package/dist/error-detail.d.ts.map +1 -1
  66. package/dist/error-detail.js +4 -1
  67. package/dist/file-hash.d.ts +6 -2
  68. package/dist/file-hash.d.ts.map +1 -1
  69. package/dist/file-hash.js +49 -12
  70. package/dist/file-store-sync-write.d.ts +1 -0
  71. package/dist/file-store-sync-write.d.ts.map +1 -1
  72. package/dist/file-store-sync-write.js +9 -6
  73. package/dist/file-store.d.ts +4 -0
  74. package/dist/file-store.d.ts.map +1 -1
  75. package/dist/file-store.js +10 -1
  76. package/dist/filename.d.ts.map +1 -1
  77. package/dist/filename.js +2 -12
  78. package/dist/fs.d.ts.map +1 -1
  79. package/dist/fs.js +1 -2
  80. package/dist/guarded-mkdir.d.ts +1 -0
  81. package/dist/guarded-mkdir.d.ts.map +1 -1
  82. package/dist/guarded-mkdir.js +1 -0
  83. package/dist/guarded-mutation.d.ts +2 -0
  84. package/dist/guarded-mutation.d.ts.map +1 -1
  85. package/dist/guarded-mutation.js +9 -5
  86. package/dist/home-dir.d.ts.map +1 -1
  87. package/dist/home-dir.js +9 -7
  88. package/dist/index.d.ts +1 -1
  89. package/dist/index.d.ts.map +1 -1
  90. package/dist/install-path.d.ts.map +1 -1
  91. package/dist/install-path.js +9 -13
  92. package/dist/json-document-store.d.ts +3 -0
  93. package/dist/json-document-store.d.ts.map +1 -1
  94. package/dist/json-document-store.js +1 -0
  95. package/dist/json-durable-queue-directory.js +3 -3
  96. package/dist/json-durable-queue-ownership.js +3 -2
  97. package/dist/json-durable-queue.d.ts.map +1 -1
  98. package/dist/json-durable-queue.js +31 -27
  99. package/dist/local-roots.d.ts.map +1 -1
  100. package/dist/local-roots.js +22 -24
  101. package/dist/move-path-cleanup.d.ts.map +1 -1
  102. package/dist/move-path-cleanup.js +4 -3
  103. package/dist/move-path-stage.d.ts +8 -0
  104. package/dist/move-path-stage.d.ts.map +1 -0
  105. package/dist/move-path-stage.js +55 -0
  106. package/dist/move-path.d.ts.map +1 -1
  107. package/dist/move-path.js +55 -47
  108. package/dist/mutation-authority.d.ts +8 -0
  109. package/dist/mutation-authority.d.ts.map +1 -0
  110. package/dist/mutation-authority.js +36 -0
  111. package/dist/native-binding.d.ts +24 -1
  112. package/dist/native-binding.d.ts.map +1 -1
  113. package/dist/native-operations.d.ts +1 -1
  114. package/dist/native-operations.d.ts.map +1 -1
  115. package/dist/native-operations.js +11 -5
  116. package/dist/native-pinned-write-windows.d.ts.map +1 -1
  117. package/dist/native-pinned-write-windows.js +18 -7
  118. package/dist/native-pinned-write.d.ts.map +1 -1
  119. package/dist/native-pinned-write.js +1 -0
  120. package/dist/native-staged-file.d.ts +3 -3
  121. package/dist/native-staged-file.d.ts.map +1 -1
  122. package/dist/native-staged-file.js +35 -8
  123. package/dist/opened-file-failure.d.ts +1 -1
  124. package/dist/opened-file-failure.d.ts.map +1 -1
  125. package/dist/opened-file-failure.js +3 -2
  126. package/dist/path.d.ts.map +1 -1
  127. package/dist/path.js +3 -1
  128. package/dist/permissions-windows.d.ts.map +1 -1
  129. package/dist/permissions-windows.js +38 -48
  130. package/dist/permissions.js +3 -3
  131. package/dist/pinned-operation.js +1 -1
  132. package/dist/pinned-write.d.ts +5 -1
  133. package/dist/pinned-write.d.ts.map +1 -1
  134. package/dist/pinned-write.js +50 -30
  135. package/dist/positional-read.d.ts +9 -0
  136. package/dist/positional-read.d.ts.map +1 -0
  137. package/dist/positional-read.js +36 -0
  138. package/dist/private-temp-workspace.d.ts.map +1 -1
  139. package/dist/private-temp-workspace.js +9 -3
  140. package/dist/publish-copy-stage.d.ts +13 -0
  141. package/dist/publish-copy-stage.d.ts.map +1 -0
  142. package/dist/publish-copy-stage.js +47 -0
  143. package/dist/publish-file.d.ts.map +1 -1
  144. package/dist/publish-file.js +26 -25
  145. package/dist/replace-file-copy-fallback.d.ts.map +1 -1
  146. package/dist/replace-file-copy-fallback.js +30 -17
  147. package/dist/replace-file-copy-source.d.ts.map +1 -1
  148. package/dist/replace-file-copy-source.js +10 -5
  149. package/dist/replace-file-temp-owner.d.ts.map +1 -1
  150. package/dist/replace-file-temp-owner.js +6 -2
  151. package/dist/replace-file.d.ts.map +1 -1
  152. package/dist/replace-file.js +8 -3
  153. package/dist/root-context.d.ts +3 -0
  154. package/dist/root-context.d.ts.map +1 -1
  155. package/dist/root-context.js +51 -26
  156. package/dist/root-directory-list.d.ts +24 -0
  157. package/dist/root-directory-list.d.ts.map +1 -0
  158. package/dist/root-directory-list.js +201 -0
  159. package/dist/root-errors.d.ts +1 -0
  160. package/dist/root-errors.d.ts.map +1 -1
  161. package/dist/root-errors.js +3 -0
  162. package/dist/root-file.d.ts +2 -0
  163. package/dist/root-file.d.ts.map +1 -1
  164. package/dist/root-file.js +12 -4
  165. package/dist/root-impl.d.ts +6 -50
  166. package/dist/root-impl.d.ts.map +1 -1
  167. package/dist/root-impl.js +342 -208
  168. package/dist/root-options.d.ts +66 -0
  169. package/dist/root-options.d.ts.map +1 -0
  170. package/dist/root-options.js +18 -0
  171. package/dist/root-path-existing.d.ts +5 -0
  172. package/dist/root-path-existing.d.ts.map +1 -1
  173. package/dist/root-path-existing.js +56 -1
  174. package/dist/root-path.d.ts +1 -0
  175. package/dist/root-path.d.ts.map +1 -1
  176. package/dist/root-path.js +74 -162
  177. package/dist/root-symlink-policy.d.ts +13 -0
  178. package/dist/root-symlink-policy.d.ts.map +1 -0
  179. package/dist/root-symlink-policy.js +34 -0
  180. package/dist/root-walk.d.ts +5 -4
  181. package/dist/root-walk.d.ts.map +1 -1
  182. package/dist/root-walk.js +99 -50
  183. package/dist/root-write-mode.d.ts.map +1 -1
  184. package/dist/root-write-mode.js +1 -4
  185. package/dist/root.d.ts +3 -1
  186. package/dist/root.d.ts.map +1 -1
  187. package/dist/secret-file.d.ts +1 -0
  188. package/dist/secret-file.d.ts.map +1 -1
  189. package/dist/secret-file.js +1 -0
  190. package/dist/secret-read-async.js +5 -5
  191. package/dist/secure-file.d.ts +1 -1
  192. package/dist/secure-file.d.ts.map +1 -1
  193. package/dist/secure-file.js +23 -13
  194. package/dist/sibling-staged-file.js +7 -7
  195. package/dist/sibling-temp.d.ts.map +1 -1
  196. package/dist/sibling-temp.js +16 -7
  197. package/dist/sidecar-lock-acquire.d.ts +1 -1
  198. package/dist/sidecar-lock-acquire.d.ts.map +1 -1
  199. package/dist/sidecar-lock-acquire.js +6 -4
  200. package/dist/sidecar-lock-policy.d.ts.map +1 -1
  201. package/dist/sidecar-lock-policy.js +5 -3
  202. package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
  203. package/dist/sidecar-lock-reclaim.js +16 -4
  204. package/dist/sidecar-lock.d.ts.map +1 -1
  205. package/dist/sidecar-lock.js +2 -1
  206. package/dist/temp-cleanup.d.ts.map +1 -1
  207. package/dist/temp-cleanup.js +3 -2
  208. package/dist/temp-target.d.ts.map +1 -1
  209. package/dist/temp-target.js +9 -4
  210. package/dist/timing.d.ts +1 -0
  211. package/dist/timing.d.ts.map +1 -1
  212. package/dist/timing.js +25 -6
  213. package/dist/trash.js +2 -2
  214. package/dist/walk.d.ts.map +1 -1
  215. package/dist/walk.js +7 -27
  216. package/dist/windows-owner.d.ts +9 -12
  217. package/dist/windows-owner.d.ts.map +1 -1
  218. package/dist/windows-owner.js +24 -58
  219. package/dist/write-file-handle.d.ts +6 -0
  220. package/dist/write-file-handle.d.ts.map +1 -0
  221. package/dist/write-file-handle.js +25 -0
  222. package/dist/write-open-flags.d.ts.map +1 -1
  223. package/dist/write-open-flags.js +1 -2
  224. package/docs/advanced.md +18 -4
  225. package/docs/archive.md +57 -9
  226. package/docs/atomic.md +7 -0
  227. package/docs/contributing.md +7 -0
  228. package/docs/copy.md +86 -0
  229. package/docs/durability.md +17 -2
  230. package/docs/errors.md +1 -1
  231. package/docs/file-store.md +48 -3
  232. package/docs/json-store.md +10 -0
  233. package/docs/local-roots.md +8 -1
  234. package/docs/native.md +14 -1
  235. package/docs/permissions.md +20 -10
  236. package/docs/positional-read.md +63 -0
  237. package/docs/reading.md +6 -0
  238. package/docs/root.md +168 -6
  239. package/docs/secret-file.md +6 -0
  240. package/docs/security-model.md +14 -0
  241. package/docs/sidecar-lock.md +2 -0
  242. package/docs/timing.md +2 -0
  243. package/docs/types.md +18 -11
  244. package/docs/walk.md +54 -5
  245. package/docs/writing.md +25 -5
  246. package/package.json +16 -11
@@ -90,7 +90,13 @@ export async function readSidecarLockRawSnapshot(lockPath, options = {}) {
90
90
  await opened.handle.close().catch(() => undefined);
91
91
  }
92
92
  }
93
- const before = await fs.lstat(lockPath).catch(missingSnapshotPath);
93
+ let before;
94
+ try {
95
+ before = fsSync.lstatSync(lockPath);
96
+ }
97
+ catch (error) {
98
+ before = missingSnapshotPath(error);
99
+ }
94
100
  if (!before)
95
101
  return null;
96
102
  if (!before.isFile() || before.isSymbolicLink()) {
@@ -119,7 +125,7 @@ export async function readSidecarLockRawSnapshot(lockPath, options = {}) {
119
125
  options.onOpenFailure?.(error);
120
126
  throw error;
121
127
  }
122
- const opened = await handle.stat();
128
+ const opened = fsSync.fstatSync(handle.fd);
123
129
  if (!opened.isFile()) {
124
130
  if (options.rejectNonFile) {
125
131
  throw new FsSafeError("not-file", `sidecar lock is not a regular file: ${lockPath}`);
@@ -129,7 +135,13 @@ export async function readSidecarLockRawSnapshot(lockPath, options = {}) {
129
135
  if (!options.allowDescriptorIdentityDrift && !sameFileIdentity(before, opened))
130
136
  return null;
131
137
  const raw = (await readFileHandleBounded(handle, MAX_LOCK_PAYLOAD_BYTES)).toString("utf8");
132
- const after = await fs.lstat(lockPath).catch(missingSnapshotPath);
138
+ let after;
139
+ try {
140
+ after = fsSync.lstatSync(lockPath);
141
+ }
142
+ catch (error) {
143
+ after = missingSnapshotPath(error);
144
+ }
133
145
  if (!after || !after.isFile() || !sameFileIdentity(before, after))
134
146
  return null;
135
147
  return { raw, stat: after };
@@ -241,7 +253,7 @@ export async function sidecarLockSnapshotStillPresent(lockPath, observed, option
241
253
  }
242
254
  export async function sidecarReclaimGuardExists(pathname) {
243
255
  try {
244
- await fs.lstat(pathname);
256
+ fsSync.lstatSync(pathname);
245
257
  return true;
246
258
  }
247
259
  catch (err) {
@@ -1 +1 @@
1
- {"version":3,"file":"sidecar-lock.d.ts","sourceRoot":"","sources":["../src/sidecar-lock.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACV,yBAAyB,EACzB,iBAAiB,EACjB,oBAAoB,EACpB,sBAAsB,EACvB,MAAM,yBAAyB,CAAC;AACjC,YAAY,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAC1E,YAAY,EACV,yBAAyB,EACzB,0BAA0B,EAC1B,iBAAiB,EACjB,oBAAoB,EACpB,uBAAuB,EACvB,wBAAwB,EACxB,sBAAsB,GACvB,MAAM,yBAAyB,CAAC;AAqIjC,0FAA0F;AAC1F,wBAAgB,uBAAuB,IAAI,OAAO,CAMjD;AAuFD,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM;cAU3B,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,WACpD,yBAAyB,CAAC,QAAQ,CAAC,KAC3C,OAAO,CAAC,iBAAiB,CAAC;eAkBL,CAAC,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,WACxD,yBAAyB,CAAC,QAAQ,CAAC,MACxC,MAAM,OAAO,CAAC,CAAC,CAAC,KACnB,OAAO,CAAC,CAAC,CAAC;iBAqBW,OAAO,CAAC,IAAI,CAAC;iBAQnB,IAAI;uBAIE,oBAAoB,EAAE;EAW/C;AAED,wBAAsB,eAAe,CAAC,CAAC,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/E,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,sBAAsB,CAAC,QAAQ,CAAC,EACzC,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACnB,OAAO,CAAC,CAAC,CAAC,CAMZ"}
1
+ {"version":3,"file":"sidecar-lock.d.ts","sourceRoot":"","sources":["../src/sidecar-lock.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACV,yBAAyB,EACzB,iBAAiB,EACjB,oBAAoB,EACpB,sBAAsB,EACvB,MAAM,yBAAyB,CAAC;AACjC,YAAY,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAC1E,YAAY,EACV,yBAAyB,EACzB,0BAA0B,EAC1B,iBAAiB,EACjB,oBAAoB,EACpB,uBAAuB,EACvB,wBAAwB,EACxB,sBAAsB,GACvB,MAAM,yBAAyB,CAAC;AAsIjC,0FAA0F;AAC1F,wBAAgB,uBAAuB,IAAI,OAAO,CAMjD;AAuFD,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM;cAU3B,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,WACpD,yBAAyB,CAAC,QAAQ,CAAC,KAC3C,OAAO,CAAC,iBAAiB,CAAC;eAkBL,CAAC,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,WACxD,yBAAyB,CAAC,QAAQ,CAAC,MACxC,MAAM,OAAO,CAAC,CAAC,CAAC,KACnB,OAAO,CAAC,CAAC,CAAC;iBAqBW,OAAO,CAAC,IAAI,CAAC;iBAQnB,IAAI;uBAIE,oBAAoB,EAAE;EAW/C;AAED,wBAAsB,eAAe,CAAC,CAAC,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/E,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,sBAAsB,CAAC,QAAQ,CAAC,EACzC,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACnB,OAAO,CAAC,CAAC,CAAC,CAMZ"}
@@ -85,7 +85,8 @@ function snapshotMatchesSync(lockPath, observed) {
85
85
  }
86
86
  }
87
87
  function releaseAllReclaimGuardsSync(state) {
88
- for (const reclaimGuardPath of state.reclaimGuards) {
88
+ // Exit cleanup also visits managers created by older copies and never reopened here.
89
+ for (const reclaimGuardPath of state.reclaimGuards ?? []) {
89
90
  try {
90
91
  fsSync.rmdirSync(reclaimGuardPath);
91
92
  state.reclaimGuards.delete(reclaimGuardPath);
@@ -1 +1 @@
1
- {"version":3,"file":"temp-cleanup.d.ts","sourceRoot":"","sources":["../src/temp-cleanup.ts"],"names":[],"mappings":"AACA,OAAO,EAA8B,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEvF,MAAM,MAAM,uBAAuB,GAAG,gBAAgB,CAAC;AAEvD,MAAM,MAAM,oBAAoB,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG;IAChD,WAAW,CAAC,QAAQ,EAAE,gBAAgB,GAAG,IAAI,CAAC;CAC/C,CAAC;AAkDF,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE;IACR,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAC5B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC;CAC7B,GACA,oBAAoB,CA2BtB;AAED,wBAAgB,mCAAmC,IAAI,IAAI,CAE1D;AAED,wBAAgB,kCAAkC,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAUzE"}
1
+ {"version":3,"file":"temp-cleanup.d.ts","sourceRoot":"","sources":["../src/temp-cleanup.ts"],"names":[],"mappings":"AACA,OAAO,EAA8B,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEvF,MAAM,MAAM,uBAAuB,GAAG,gBAAgB,CAAC;AAEvD,MAAM,MAAM,oBAAoB,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG;IAChD,WAAW,CAAC,QAAQ,EAAE,gBAAgB,GAAG,IAAI,CAAC;CAC/C,CAAC;AAmDF,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE;IACR,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAC5B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC;CAC7B,GACA,oBAAoB,CA2BtB;AAED,wBAAgB,mCAAmC,IAAI,IAAI,CAE1D;AAED,wBAAgB,kCAAkC,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAUzE"}
@@ -11,8 +11,9 @@ function pathStillMatchesReceipt(entry) {
11
11
  return (!entry.singleLinkFile || (current.isFile() && current.nlink === 1n)) &&
12
12
  sameFileIdentityForCleanup(current, entry.identity);
13
13
  }
14
- catch (error) {
15
- return !entry.singleLinkFile && error.code === "ENOENT";
14
+ catch {
15
+ // A missing pathname grants no authority over an entry created after this lookup.
16
+ return false;
16
17
  }
17
18
  }
18
19
  function removeRegisteredPathSync(entry) {
@@ -1 +1 @@
1
- {"version":3,"file":"temp-target.d.ts","sourceRoot":"","sources":["../src/temp-target.ts"],"names":[],"mappings":"AAQA,MAAM,MAAM,QAAQ,GAAG;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAChC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC,CAAC;AA8DF,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAI7D;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE;IAC9C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GAAG,MAAM,CAaT;AAsCD,wBAAsB,QAAQ,CAAC,MAAM,EAAE;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CAC3C,GAAG,OAAO,CAAC,QAAQ,CAAC,CAwBpB;AAED,wBAAsB,YAAY,CAAC,CAAC,EAClC,MAAM,EAAE;IACN,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CAC3C,EACD,EAAE,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,GAClC,OAAO,CAAC,CAAC,CAAC,CAOZ"}
1
+ {"version":3,"file":"temp-target.d.ts","sourceRoot":"","sources":["../src/temp-target.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,QAAQ,GAAG;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAChC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC,CAAC;AA8DF,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAI7D;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE;IAC9C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GAAG,MAAM,CAaT;AAyCD,wBAAsB,QAAQ,CAAC,MAAM,EAAE;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CAC3C,GAAG,OAAO,CAAC,QAAQ,CAAC,CAwBpB;AAED,wBAAsB,YAAY,CAAC,CAAC,EAClC,MAAM,EAAE;IACN,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CAC3C,EACD,EAAE,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,GAClC,OAAO,CAAC,CAAC,CAAC,CAOZ"}
@@ -1,4 +1,5 @@
1
1
  import crypto from "node:crypto";
2
+ import fsSync from "node:fs";
2
3
  import fs from "node:fs/promises";
3
4
  import path from "node:path";
4
5
  import { sameFileIdentityForCleanup } from "./file-identity.js";
@@ -81,12 +82,16 @@ function isNodeErrorWithCode(err, code) {
81
82
  }
82
83
  async function cleanupTempDir(dir, identity, onCleanupError) {
83
84
  try {
84
- const current = await fs.lstat(dir, { bigint: true }).catch((error) => {
85
+ let current;
86
+ try {
87
+ current = fsSync.lstatSync(dir, { bigint: true });
88
+ }
89
+ catch (error) {
85
90
  if (isNodeErrorWithCode(error, "ENOENT")) {
86
- return undefined;
91
+ return;
87
92
  }
88
93
  throw error;
89
- });
94
+ }
90
95
  if (!current || !sameFileIdentityForCleanup(current, identity)) {
91
96
  return;
92
97
  }
@@ -107,7 +112,7 @@ export async function tempFile(params) {
107
112
  const dir = await fs.mkdtemp(path.join(rootDir, prefix));
108
113
  // Windows file indexes can exceed Number.MAX_SAFE_INTEGER. Cleanup receipts
109
114
  // must retain the exact identity or adjacent directories can compare equal.
110
- const identity = await fs.lstat(dir, { bigint: true });
115
+ const identity = fsSync.lstatSync(dir, { bigint: true });
111
116
  const unregisterTempDir = registerTempPathForExit(dir, { recursive: true, identity });
112
117
  const file = (fileName) => path.join(dir, sanitizeTempFileName(fileName ?? params.fileName ?? "download.bin"));
113
118
  const cleanup = async () => {
package/dist/timing.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export declare function scheduleTimeout(callback: () => void, ms: number): () => void;
1
2
  export declare function sleep(ms: number): Promise<void>;
2
3
  export declare function sleepSync(ms: number): void;
3
4
  export declare function withTimeout<T>(promise: Promise<T>, timeoutMs: number, labelOrOptions?: string | {
@@ -1 +1 @@
1
- {"version":3,"file":"timing.d.ts","sourceRoot":"","sources":["../src/timing.ts"],"names":[],"mappings":"AAAA,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/C;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAK1C;AAED,wBAAsB,WAAW,CAAC,CAAC,EACjC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EACnB,SAAS,EAAE,MAAM,EACjB,cAAc,GAAE,MAAM,GAAG;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,KAAK,CAAA;CAErF,GACA,OAAO,CAAC,CAAC,CAAC,CA6BZ"}
1
+ {"version":3,"file":"timing.d.ts","sourceRoot":"","sources":["../src/timing.ts"],"names":[],"mappings":"AAEA,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,IAAI,CAgB5E;AAED,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/C;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAK1C;AAED,wBAAsB,WAAW,CAAC,CAAC,EACjC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EACnB,SAAS,EAAE,MAAM,EACjB,cAAc,GAAE,MAAM,GAAG;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,KAAK,CAAA;CAErF,GACA,OAAO,CAAC,CAAC,CAAC,CA2BZ"}
package/dist/timing.js CHANGED
@@ -1,5 +1,26 @@
1
+ const MAX_TIMER_DELAY_MS = 2 ** 31 - 1;
2
+ export function scheduleTimeout(callback, ms) {
3
+ let timer;
4
+ if (Number.isFinite(ms) && ms > MAX_TIMER_DELAY_MS) {
5
+ const startedAt = performance.now();
6
+ const tick = () => {
7
+ const remaining = ms - (performance.now() - startedAt);
8
+ if (remaining <= 0)
9
+ callback();
10
+ else
11
+ timer = setTimeout(tick, Math.min(remaining, MAX_TIMER_DELAY_MS));
12
+ };
13
+ // Node clamps overflowing delays to 1 ms. Rearm bounded timers against a
14
+ // monotonic clock so clock changes and event-loop stalls cannot shorten them.
15
+ timer = setTimeout(tick, MAX_TIMER_DELAY_MS);
16
+ }
17
+ else {
18
+ timer = setTimeout(callback, ms);
19
+ }
20
+ return () => clearTimeout(timer);
21
+ }
1
22
  export function sleep(ms) {
2
- return new Promise((resolve) => setTimeout(resolve, ms));
23
+ return new Promise((resolve) => { scheduleTimeout(resolve, ms); });
3
24
  }
4
25
  export function sleepSync(ms) {
5
26
  if (ms <= 0) {
@@ -16,12 +37,12 @@ export async function withTimeout(promise, timeoutMs, labelOrOptions = {
16
37
  const options = typeof labelOrOptions === "string" ? { label: labelOrOptions } : labelOrOptions;
17
38
  const createError = options.createError ??
18
39
  (() => new Error(options.message ?? `${options.label ?? "operation"} timed out after ${timeoutMs}ms`));
19
- let timeoutId;
40
+ let cancelTimeout;
20
41
  try {
21
42
  return await Promise.race([
22
43
  promise,
23
44
  new Promise((_, reject) => {
24
- timeoutId = setTimeout(() => {
45
+ cancelTimeout = scheduleTimeout(() => {
25
46
  try {
26
47
  reject(createError());
27
48
  }
@@ -33,8 +54,6 @@ export async function withTimeout(promise, timeoutMs, labelOrOptions = {
33
54
  ]);
34
55
  }
35
56
  finally {
36
- if (timeoutId) {
37
- clearTimeout(timeoutId);
38
- }
57
+ cancelTimeout?.();
39
58
  }
40
59
  }
package/dist/trash.js CHANGED
@@ -18,7 +18,7 @@ function isTrashDestinationCollision(error) {
18
18
  return Boolean(code && TRASH_DESTINATION_COLLISION_CODES.has(code));
19
19
  }
20
20
  function isSameOrChildPath(candidate, parent) {
21
- return candidate === parent || candidate.startsWith(`${parent}${path.sep}`);
21
+ return candidate === parent || candidate.startsWith(parent.endsWith(path.sep) ? parent : `${parent}${path.sep}`);
22
22
  }
23
23
  function resolveAllowedTrashRoots(allowedRoots) {
24
24
  const roots = [...(allowedRoots ?? [os.homedir(), os.tmpdir()])].flatMap((root) => {
@@ -107,7 +107,7 @@ function resolveContainedPath(root, leaf) {
107
107
  return resolvedPath;
108
108
  }
109
109
  function reserveTrashDestination(trashDir, base, timestamp) {
110
- const containerPrefix = resolveContainedPath(trashDir, `${base}-${timestamp}-`);
110
+ const containerPrefix = resolveContainedPath(trashDir, `.fs-safe-trash-${timestamp}-`);
111
111
  const container = fs.mkdtempSync(containerPrefix);
112
112
  const resolvedContainer = path.resolve(container);
113
113
  const resolvedTrashDir = path.resolve(trashDir);
@@ -1 +1 @@
1
- {"version":3,"file":"walk.d.ts","sourceRoot":"","sources":["../src/walk.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,SAAS,CAAC;AAI7B,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,WAAW,GAAG,SAAS,GAAG,OAAO,CAAC;AACvE,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE9D,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,aAAa,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,OAAO,CAAC;IACjD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,OAAO,CAAC;CAClD,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,kBAAkB,EAAE,CAAC;IAC9B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,OAAO,CAAC;IAGnB,UAAU,CAAC,EAAE,oBAAoB,EAAE,CAAC;CACrC,CAAC;AAEF,KAAK,+BAA+B,GAAG,mBAAmB,GAAG;IAC3D,UAAU,EAAE,oBAAoB,EAAE,CAAC;CACpC,CAAC;AA+FF,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,oBAAyB,GACjC,+BAA+B,CAyDjC;AAED,wBAAsB,aAAa,CACjC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,+BAA+B,CAAC,CAyD1C"}
1
+ {"version":3,"file":"walk.d.ts","sourceRoot":"","sources":["../src/walk.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,SAAS,CAAC;AAI7B,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,WAAW,GAAG,SAAS,GAAG,OAAO,CAAC;AACvE,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE9D,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,aAAa,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,OAAO,CAAC;IACjD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,OAAO,CAAC;CAClD,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,kBAAkB,EAAE,CAAC;IAC9B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,OAAO,CAAC;IAGnB,UAAU,CAAC,EAAE,oBAAoB,EAAE,CAAC;CACrC,CAAC;AAEF,KAAK,+BAA+B,GAAG,mBAAmB,GAAG;IAC3D,UAAU,EAAE,oBAAoB,EAAE,CAAC;CACpC,CAAC;AAgFF,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,oBAAyB,GACjC,+BAA+B,CAyDjC;AAED,wBAAsB,aAAa,CACjC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,+BAA+B,CAAC,CAyD1C"}
package/dist/walk.js CHANGED
@@ -27,7 +27,7 @@ function shouldStop(result, options) {
27
27
  return options.maxEntries !== undefined && result.scannedEntryCount >= Math.max(0, options.maxEntries);
28
28
  }
29
29
  function buildEntry(params) {
30
- const fullPath = path.join(params.dir, params.dirent.name);
30
+ const fullPath = params.fullPath;
31
31
  const relativePath = path.relative(params.rootDir, fullPath) || params.dirent.name;
32
32
  return {
33
33
  name: params.dirent.name,
@@ -47,7 +47,7 @@ function recordFailedDir(result, root, dir, depth, error) {
47
47
  error,
48
48
  });
49
49
  }
50
- function resolveSyncKind(fullPath, dirent, symlinks) {
50
+ function resolveKind(fullPath, dirent, symlinks) {
51
51
  const kind = kindForDirent(dirent);
52
52
  if (kind !== "symlink")
53
53
  return kind;
@@ -67,26 +67,6 @@ function resolveSyncKind(fullPath, dirent, symlinks) {
67
67
  }
68
68
  return "other";
69
69
  }
70
- async function resolveAsyncKind(fullPath, dirent, symlinks) {
71
- const kind = kindForDirent(dirent);
72
- if (kind !== "symlink")
73
- return kind;
74
- if (symlinks === "skip")
75
- return null;
76
- if (symlinks === "include")
77
- return "symlink";
78
- try {
79
- const stat = await fs.stat(fullPath);
80
- if (stat.isDirectory())
81
- return "directory";
82
- if (stat.isFile())
83
- return "file";
84
- }
85
- catch {
86
- return null;
87
- }
88
- return "other";
89
- }
90
70
  export function walkDirectorySync(rootDir, options = {}) {
91
71
  validateWalkOptions(options);
92
72
  const root = path.resolve(rootDir);
@@ -127,10 +107,10 @@ export function walkDirectorySync(rootDir, options = {}) {
127
107
  }
128
108
  result.scannedEntryCount += 1;
129
109
  const fullPath = path.join(dir, dirent.name);
130
- const kind = resolveSyncKind(fullPath, dirent, symlinks);
110
+ const kind = resolveKind(fullPath, dirent, symlinks);
131
111
  if (!kind)
132
112
  continue;
133
- const entry = buildEntry({ rootDir: root, dir, dirent, depth, kind });
113
+ const entry = buildEntry({ rootDir: root, fullPath, dirent, depth, kind });
134
114
  if (options.include?.(entry) ?? true) {
135
115
  result.entries.push(entry);
136
116
  }
@@ -162,7 +142,7 @@ export async function walkDirectory(rootDir, options = {}) {
162
142
  return;
163
143
  let realDir;
164
144
  try {
165
- realDir = await fs.realpath(dir);
145
+ realDir = fsSync.realpathSync.native(dir);
166
146
  }
167
147
  catch (error) {
168
148
  recordFailedDir(result, root, dir, depth, error);
@@ -186,10 +166,10 @@ export async function walkDirectory(rootDir, options = {}) {
186
166
  }
187
167
  result.scannedEntryCount += 1;
188
168
  const fullPath = path.join(dir, dirent.name);
189
- const kind = await resolveAsyncKind(fullPath, dirent, symlinks);
169
+ const kind = resolveKind(fullPath, dirent, symlinks);
190
170
  if (!kind)
191
171
  continue;
192
- const entry = buildEntry({ rootDir: root, dir, dirent, depth, kind });
172
+ const entry = buildEntry({ rootDir: root, fullPath, dirent, depth, kind });
193
173
  if (options.include?.(entry) ?? true) {
194
174
  result.entries.push(entry);
195
175
  }
@@ -6,24 +6,21 @@ export type WindowsOwnerExec = (command: string, args: string[]) => Promise<{
6
6
  export type WindowsOwnerSummary = {
7
7
  sid?: string;
8
8
  currentUserSid?: string;
9
- principalSids?: Record<string, string>;
10
- principalTranslationFailed?: boolean;
9
+ daclPresent?: boolean;
10
+ aces?: WindowsOwnerAce[];
11
+ aclError?: string;
11
12
  remote?: boolean;
12
13
  trusted?: boolean;
13
14
  error?: string;
14
15
  errorDetail?: PermissionCommandFailure;
15
16
  errorCause?: unknown;
16
17
  };
17
- export declare function resolveWindowsPrincipalSids(params: {
18
- principals: string[];
19
- known?: Record<string, string>;
20
- env?: NodeJS.ProcessEnv;
21
- exec: WindowsOwnerExec;
22
- }): Promise<Record<string, string>>;
23
- export declare function resolveWindowsCurrentUserSid(params: {
24
- env?: NodeJS.ProcessEnv;
25
- exec: WindowsOwnerExec;
26
- }): Promise<string | null>;
18
+ export type WindowsOwnerAce = {
19
+ sid: string;
20
+ mask: number;
21
+ deny: boolean;
22
+ inheritOnly: boolean;
23
+ };
27
24
  export declare function inspectWindowsOwner(params: {
28
25
  targetPath: string;
29
26
  env?: NodeJS.ProcessEnv;
@@ -1 +1 @@
1
- {"version":3,"file":"windows-owner.d.ts","sourceRoot":"","sources":["../src/windows-owner.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,wBAAwB,EAC9B,MAAM,sBAAsB,CAAC;AAG9B,MAAM,MAAM,gBAAgB,GAAG,CAC7B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,KACX,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAEjD,MAAM,MAAM,mBAAmB,GAAG;IAChC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,0BAA0B,CAAC,EAAE,OAAO,CAAC;IACrC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,wBAAwB,CAAC;IACvC,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC;AA4DF,wBAAsB,2BAA2B,CAAC,MAAM,EAAE;IACxD,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,IAAI,EAAE,gBAAgB,CAAC;CACxB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAyBlC;AAED,wBAAsB,4BAA4B,CAAC,MAAM,EAAE;IACzD,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,IAAI,EAAE,gBAAgB,CAAC;CACxB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAWzB;AAED,wBAAsB,mBAAmB,CAAC,MAAM,EAAE;IAChD,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,IAAI,EAAE,gBAAgB,CAAC;CACxB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAkD/B"}
1
+ {"version":3,"file":"windows-owner.d.ts","sourceRoot":"","sources":["../src/windows-owner.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,wBAAwB,EAC9B,MAAM,sBAAsB,CAAC;AAG9B,MAAM,MAAM,gBAAgB,GAAG,CAC7B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,KACX,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAEjD,MAAM,MAAM,mBAAmB,GAAG;IAChC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,IAAI,CAAC,EAAE,eAAe,EAAE,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,wBAAwB,CAAC;IACvC,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;IACd,WAAW,EAAE,OAAO,CAAC;CACtB,CAAC;AAqDF,wBAAsB,mBAAmB,CAAC,MAAM,EAAE;IAChD,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,IAAI,EAAE,gBAAgB,CAAC;CACxB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CA+C/B"}
@@ -23,65 +23,28 @@ function windowsOwnerQueryCommand(targetPath) {
23
23
  "$driveRoot=if($extendedDrive){$p.Substring(4,3)}else{$root}",
24
24
  "$namespacePath=$p.StartsWith('\\\\')",
25
25
  "$remote=($namespacePath -and -not $extendedDrive) -or ([IO.DriveInfo]::new($driveRoot).DriveType -eq [IO.DriveType]::Network)",
26
- "$rules=$acl.GetAccessRules($true,$true,[System.Security.Principal.SecurityIdentifier])",
27
- "$principalSids=@($rules|ForEach-Object {$identity=$_.IdentityReference;$sid=$identity.Value;@{name=$sid;sid=$sid};try{@{name=$identity.Translate([System.Security.Principal.NTAccount]).Value;sid=$sid}}catch{}})",
28
- "@{ownerSid=$ownerSid;currentUserSid=$currentSid;principalSids=$principalSids;principalTranslationFailed=$false;remote=$remote}|ConvertTo-Json -Depth 4 -Compress",
26
+ // Emit only ASCII SID and numeric facts: console encodings can replace both
27
+ // Unicode pathnames and account names before JavaScript receives the bytes.
28
+ "$raw=[System.Security.AccessControl.RawSecurityDescriptor]::new($acl.GetSecurityDescriptorBinaryForm(),0)",
29
+ "$dacl=$raw.DiscretionaryAcl;$complete=$true",
30
+ "$aces=@(foreach($ace in $dacl){if($ace -isnot [System.Security.AccessControl.CommonAce] -or $ace.IsCallback -or [int]$ace.AceType -notin @(0,1)){$complete=$false;continue};@{sid=$ace.SecurityIdentifier.Value;mask=([long]$ace.AccessMask -band 4294967295);deny=([int]$ace.AceType -eq 1);inheritOnly=([int]$ace.AceFlags -band 8) -ne 0}})",
31
+ "@{ownerSid=$ownerSid;currentUserSid=$currentSid;daclPresent=($null -ne $dacl);aces=$aces;complete=$complete;remote=$remote}|ConvertTo-Json -Depth 4 -Compress",
29
32
  ].join(";");
30
33
  }
31
- function windowsPrincipalQueryCommand(principals) {
32
- const encodedPrincipals = Buffer.from(JSON.stringify(principals), "utf8").toString("base64");
33
- return [
34
- "$ErrorActionPreference='Stop'",
35
- `$names=[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${encodedPrincipals}'))|ConvertFrom-Json`,
36
- "$rows=@($names|ForEach-Object {@{name=$_;sid=(New-Object System.Security.Principal.NTAccount($_)).Translate([System.Security.Principal.SecurityIdentifier]).Value}})",
37
- "ConvertTo-Json -InputObject $rows -Compress",
38
- ].join(";");
39
- }
40
- function parsePrincipalSidRows(value) {
41
- const rows = Array.isArray(value) ? value : value ? [value] : [];
42
- const result = {};
43
- for (const row of rows) {
44
- if (!row || typeof row !== "object") {
45
- continue;
46
- }
47
- const name = "name" in row && typeof row.name === "string" ? row.name.trim() : "";
48
- const sid = "sid" in row && typeof row.sid === "string" ? normalizeSid(row.sid) : "";
49
- if (name && SID_RE.test(sid)) {
50
- result[name.toLowerCase()] = sid;
51
- }
52
- }
53
- return result;
54
- }
55
- export async function resolveWindowsPrincipalSids(params) {
56
- const principals = [...new Set(params.principals.map((value) => value.trim()).filter(Boolean))];
57
- const known = Object.fromEntries(Object.entries(params.known ?? {}).map(([name, sid]) => [name.toLowerCase(), normalizeSid(sid)]));
58
- const unresolved = principals.filter((principal) => !known[principal.toLowerCase()]);
59
- if (unresolved.length === 0) {
60
- return known;
34
+ function parseWindowsAclFacts(parsed) {
35
+ if (parsed.complete !== true || typeof parsed.daclPresent !== "boolean" || !Array.isArray(parsed.aces)) {
36
+ return { aclError: "Windows ACL query returned incomplete descriptor data" };
61
37
  }
62
- const command = resolveWindowsSystemCommand(String.raw `WindowsPowerShell\v1.0\powershell.exe`, params.env);
63
- const { stdout } = await params.exec(command, [
64
- "-NoLogo",
65
- "-NoProfile",
66
- "-NonInteractive",
67
- "-EncodedCommand",
68
- encodePowerShellCommand(windowsPrincipalQueryCommand(unresolved)),
69
- ]);
70
- const resolved = { ...known, ...parsePrincipalSidRows(JSON.parse(stdout.trim())) };
71
- if (principals.some((principal) => !resolved[principal.toLowerCase()])) {
72
- throw new Error("Windows ACL principal translation returned incomplete SID data");
73
- }
74
- return resolved;
75
- }
76
- export async function resolveWindowsCurrentUserSid(params) {
77
- try {
78
- const { stdout, stderr } = await params.exec(resolveWindowsSystemCommand("whoami.exe", params.env), ["/user", "/fo", "csv", "/nh"]);
79
- const match = `${stdout}\n${stderr}`.match(/\*?S-\d+-\d+(?:-\d+)+/i);
80
- return match ? normalizeSid(match[0]) : null;
81
- }
82
- catch {
83
- return null;
38
+ const aces = [];
39
+ for (const row of parsed.aces) {
40
+ if (!row || typeof row !== "object" || typeof row.sid !== "string" || !SID_RE.test(row.sid) ||
41
+ typeof row.mask !== "number" || !Number.isInteger(row.mask) || row.mask < 0 || row.mask > 0xffff_ffff ||
42
+ typeof row.deny !== "boolean" || typeof row.inheritOnly !== "boolean") {
43
+ return { aclError: "Windows ACL query returned invalid access-rule data" };
44
+ }
45
+ aces.push({ sid: normalizeSid(row.sid), mask: row.mask, deny: row.deny, inheritOnly: row.inheritOnly });
84
46
  }
47
+ return { daclPresent: parsed.daclPresent, aces };
85
48
  }
86
49
  export async function inspectWindowsOwner(params) {
87
50
  let command = "";
@@ -96,7 +59,11 @@ export async function inspectWindowsOwner(params) {
96
59
  "-EncodedCommand",
97
60
  encodePowerShellCommand(windowsOwnerQueryCommand(params.targetPath)),
98
61
  ]);
99
- const parsed = JSON.parse(stdout.trim());
62
+ const value = JSON.parse(stdout.trim());
63
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
64
+ return { error: "Windows owner query returned invalid SID data" };
65
+ }
66
+ const parsed = value;
100
67
  const ownerSid = typeof parsed.ownerSid === "string" && SID_RE.test(parsed.ownerSid)
101
68
  ? normalizeSid(parsed.ownerSid)
102
69
  : undefined;
@@ -110,8 +77,7 @@ export async function inspectWindowsOwner(params) {
110
77
  return {
111
78
  sid: ownerSid,
112
79
  currentUserSid,
113
- principalSids: parsePrincipalSidRows(parsed.principalSids),
114
- principalTranslationFailed: parsed.principalTranslationFailed === true,
80
+ ...parseWindowsAclFacts(parsed),
115
81
  remote,
116
82
  trusted: !remote && (ownerSid === currentUserSid || TRUSTED_OWNER_SIDS.has(ownerSid)),
117
83
  };
@@ -0,0 +1,6 @@
1
+ import type { FileHandle } from "node:fs/promises";
2
+ export declare function writeAllToFile(target: FileHandle | number, data: string | Uint8Array, options?: {
3
+ encoding?: BufferEncoding;
4
+ assertBeforeMutation?: () => void;
5
+ }): Promise<void>;
6
+ //# sourceMappingURL=write-file-handle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write-file-handle.d.ts","sourceRoot":"","sources":["../src/write-file-handle.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAKnD,wBAAsB,cAAc,CAClC,MAAM,EAAE,UAAU,GAAG,MAAM,EAC3B,IAAI,EAAE,MAAM,GAAG,UAAU,EACzB,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,cAAc,CAAC;IAAC,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAA;CAAO,GAC7E,OAAO,CAAC,IAAI,CAAC,CAmBf"}
@@ -0,0 +1,25 @@
1
+ import fs from "node:fs";
2
+ import { FsSafeError } from "./errors.js";
3
+ const WRITE_CHUNK_BYTES = 512 * 1024;
4
+ export async function writeAllToFile(target, data, options = {}) {
5
+ const buffer = typeof data === "string" ? Buffer.from(data, options.encoding ?? "utf8") : data;
6
+ let offset = 0;
7
+ while (offset < buffer.byteLength) {
8
+ const length = Math.min(WRITE_CHUNK_BYTES, buffer.byteLength - offset);
9
+ options.assertBeforeMutation?.();
10
+ const written = typeof target === "number"
11
+ ? await new Promise((resolve, reject) => {
12
+ fs.write(target, buffer, offset, length, null, (error, bytesWritten) => {
13
+ if (error)
14
+ reject(error);
15
+ else
16
+ resolve(bytesWritten);
17
+ });
18
+ })
19
+ : (await target.write(buffer, offset, length, null)).bytesWritten;
20
+ if (written <= 0) {
21
+ throw new FsSafeError("helper-failed", "file write made no progress");
22
+ }
23
+ offset += written;
24
+ }
25
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"write-open-flags.d.ts","sourceRoot":"","sources":["../src/write-open-flags.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,SAAS,CAAC;AAI7B,wBAAgB,2BAA2B,CACzC,SAAS,GAAE,OAAO,CAAC,IAAI,CAAC,OAAO,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,CAAoB,GACjF,MAAM,CAIR;AAUD,wBAAsB,0BAA0B,CAC9C,KAAK,EAAE,OAAO,EACd,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,OAAO,CAAC,CAOlB;AAED,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,OAAO,EACd,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,GACZ,OAAO,CAOT"}
1
+ {"version":3,"file":"write-open-flags.d.ts","sourceRoot":"","sources":["../src/write-open-flags.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,SAAS,CAAC;AAG7B,wBAAgB,2BAA2B,CACzC,SAAS,GAAE,OAAO,CAAC,IAAI,CAAC,OAAO,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,CAAoB,GACjF,MAAM,CAIR;AAUD,wBAAsB,0BAA0B,CAC9C,KAAK,EAAE,OAAO,EACd,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,OAAO,CAAC,CAOlB;AAED,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,OAAO,EACd,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,GACZ,OAAO,CAOT"}
@@ -1,5 +1,4 @@
1
1
  import fsSync from "node:fs";
2
- import fs from "node:fs/promises";
3
2
  import { hasNodeErrorCode } from "./path.js";
4
3
  export function resolveNonblockingWriteFlag(constants = fsSync.constants) {
5
4
  return process.platform !== "win32" && typeof constants.O_NONBLOCK === "number"
@@ -17,7 +16,7 @@ export async function isNonRegularWriteOpenError(error, filePath, flags) {
17
16
  if (!isNonblockingWriteEnxio(error, flags))
18
17
  return false;
19
18
  try {
20
- return !(await fs.lstat(filePath)).isFile();
19
+ return !fsSync.lstatSync(filePath).isFile();
21
20
  }
22
21
  catch {
23
22
  return false;
package/docs/advanced.md CHANGED
@@ -65,7 +65,8 @@ Operational filesystem failures such as permissions or I/O errors are rethrown.
65
65
  | Export | Page | Notes |
66
66
  |---|---|---|
67
67
  | `readFileDescriptorBounded`, `readFileDescriptorBoundedSync`, `readFileHandleBounded` | – | Incremental whole-file reads for already-open descriptors/handles. They consume at most `maxBytes + 1`, do not close the input, and throw `FsSafeError("too-large")` on overflow. |
68
- | `openRootFile`, `openRootFileSync`, `canUseRootFileOpen`, `matchRootFileOpenFailure`, related types | – | Low-level root-bounded open; rejects every symlink component by default, with `rejectSymlinks: false` for explicit in-root following. |
68
+ | `readFileWindowFully`, `readFileWindowFullySync`, `ReadFileWindowOptions` | [positional-read.md](positional-read.md) | Fill a caller-owned buffer at an explicit file position, completing short reads and returning the EOF count without moving or closing the descriptor. |
69
+ | `openRootFile`, `openRootFileSync`, `canUseRootFileOpen`, `matchRootFileOpenFailure`, related types | – | Low-level root-bounded open; rejects every symlink component by default, with `symlinks: "follow-parents-within-root"` for contained parent aliases or `"follow-within-root"` for final links too. |
69
70
  | `appendRegularFile`, `appendRegularFileSync`, `readRegularFile`, `readRegularFileSync`, `statRegularFile`, `statRegularFileSync`, `resolveRegularFileAppendFlags`, `AppendRegularFileOptions`, `RegularFileStatResult` | [regular-file.md](regular-file.md) | Type-checked regular-file I/O. |
70
71
  | `sameFileIdentity`, `FileIdentityStat` | – | Compare two stats for same-inode equality. |
71
72
  | `pathExists`, `pathExistsSync` | – | Boolean existence check that does not throw on `ENOENT`. |
@@ -79,6 +80,19 @@ receipts remain numeric. Custom `ioFs` adapters must honor `{ bigint: true }` fo
79
80
  Windows identities receive one re-inspection without reopening, then fail
80
81
  validation if still unknown.
81
82
 
83
+ The explicit `symlinks` policy takes precedence over the existing `rejectSymlinks`
84
+ boolean. Without `symlinks`, `rejectSymlinks: false` retains its existing behavior
85
+ of following contained links, and omission still rejects all symlink components.
86
+
87
+ The bounded descriptor helpers cap their initial speculative allocation at
88
+ 16 MiB plus the overflow-probe byte, even when a file reports a much larger
89
+ size. Regular files up to that size can return from one read without copying
90
+ chunks; larger files and short reads continue incrementally under the same
91
+ byte limit. Continuation buffers grow only after filling with actual bytes,
92
+ up to the byte budget plus its probe; file-size hints cannot force that growth.
93
+ Unknown-size inputs start with at most 64 KiB. This avoids per-chunk copies and
94
+ a final concatenation when a file exceeds the initial allocation.
95
+
82
96
  The bounded descriptor helpers start at the descriptor's current offset and
83
97
  leave ownership with the caller. They are intended for the second half of a
84
98
  safe read: first open and validate the path using the boundary appropriate to
@@ -117,7 +131,7 @@ component is followed by another segment, both helpers throw
117
131
  |---|---|---|
118
132
  | `safeDirName`, `safePathSegmentHashed`, `resolveSafeInstallDir`, `assertCanonicalPathWithinBase` | [install-path.md](install-path.md) | Build install-target directories from caller-supplied identifiers. |
119
133
  | `sanitizeUntrustedFileName` | [filename.md](filename.md) | Coerce an untrusted string into a safe filename. |
120
- | `resolveHomeRelativePath` | – | Expand `~`-prefixed paths. |
134
+ | `resolveHomeRelativePath` | – | Expand a leading `~` before resolving `.` and `..`; tildes inside relative paths stay literal. |
121
135
 
122
136
  ### Temp targets and sibling-temp writes
123
137
 
@@ -139,7 +153,7 @@ component is followed by another segment, both helpers throw
139
153
  | Export | Page | Notes |
140
154
  |---|---|---|
141
155
  | `createAsyncLock` | – | In-process async lock (separate from cross-process file locks). |
142
- | `withTimeout` | [timing.md](timing.md) | Wrap a promise with a timeout that raises `FsSafeError("timeout")`. |
156
+ | `withTimeout` | [timing.md](timing.md) | Wrap a promise with a timeout that raises `Error` by default, or an error supplied by `createError`. |
143
157
  | `movePathToTrash`, `MovePathToTrashOptions` | – | Best-effort move to the platform trash. |
144
158
 
145
159
  ## Stability
@@ -149,5 +163,5 @@ Items in this surface can change shape between minor versions if a higher-level
149
163
  ## Related pages
150
164
 
151
165
  - [Root API](root.md) — built on top of these helpers.
152
- - [Errors](errors.md) — every helper here surfaces failures as `FsSafeError`.
166
+ - [Errors](errors.md) — shared filesystem error codes; generic timing helpers retain their documented error types.
153
167
  - [Security model](security-model.md) — what the underlying boundary checks promise.