@openclaw/feishu 2026.9.8 → 2026.10.1-beta.2

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 (225) hide show
  1. package/dist/.setup/{channel-DV4hM2Uf.mjs → channel-CX62T71S.mjs} +146 -245
  2. package/dist/.setup/{channel.runtime-CXQcH5zZ.mjs → channel.runtime-COiaq-Rv.mjs} +6 -12
  3. package/dist/.setup/{chat-D8MlFAZi.mjs → chat-BdyvImsY.mjs} +6 -9
  4. package/dist/.setup/{client-BYG-_IZl.mjs → client-BsBVWJzN.mjs} +1 -1
  5. package/dist/.setup/{doctor-contract-3XC0vbIw.mjs → doctor-contract-B1B9ogdd.mjs} +212 -27
  6. package/dist/.setup/{monitor-D_rsHBZ6.mjs → monitor-7DvwUs4Y.mjs} +5 -6
  7. package/dist/.setup/{monitor.account-DvvY7i6k.mjs → monitor.account-7xVu7XWY.mjs} +190 -393
  8. package/dist/.setup/{probe-_ViqSi0j.mjs → probe-B9o7iey6.mjs} +13 -39
  9. package/dist/.setup/{reply-delivery-result-COAjaukc.mjs → reply-delivery-result-Bt-BWBEA.mjs} +151 -297
  10. package/dist/.setup/{setup-api-C4S5I3ac.mjs → setup-api-DmzX9S01.mjs} +1 -1
  11. package/dist/.setup/{subagent-hooks-BaFoMu8W.mjs → subagent-hooks-CUXg0pC-.mjs} +5 -11
  12. package/dist/.setup/{thread-bindings-EdLXPXzw.mjs → thread-bindings-jW2x5VuN.mjs} +23 -41
  13. package/dist/api.js +234 -297
  14. package/dist/channel-plugin-api.js +1 -1
  15. package/dist/doctor-contract-api.js +2 -2
  16. package/dist/session-binding-contract-api.js +1 -1
  17. package/dist/setup-api.js +1 -1
  18. package/dist/setup-entry.js +1 -1
  19. package/dist/subagent-hooks-api.js +1 -1
  20. package/node_modules/@openclaw/fs-safe/CHANGELOG.md +95 -0
  21. package/node_modules/@openclaw/fs-safe/README.md +70 -379
  22. package/node_modules/@openclaw/fs-safe/dist/absolute-path.js +35 -51
  23. package/node_modules/@openclaw/fs-safe/dist/advanced.d.ts +2 -0
  24. package/node_modules/@openclaw/fs-safe/dist/advanced.js +1 -0
  25. package/node_modules/@openclaw/fs-safe/dist/archive-input.js +2 -0
  26. package/node_modules/@openclaw/fs-safe/dist/archive-parser.wasm +0 -0
  27. package/node_modules/@openclaw/fs-safe/dist/archive-staging.d.ts +1 -1
  28. package/node_modules/@openclaw/fs-safe/dist/archive-staging.js +9 -10
  29. package/node_modules/@openclaw/fs-safe/dist/archive.js +2 -5
  30. package/node_modules/@openclaw/fs-safe/dist/atomic-io.d.ts +51 -0
  31. package/node_modules/@openclaw/fs-safe/dist/atomic-io.js +242 -0
  32. package/node_modules/@openclaw/fs-safe/dist/config.d.ts +1 -1
  33. package/node_modules/@openclaw/fs-safe/dist/config.js +1 -1
  34. package/node_modules/@openclaw/fs-safe/dist/copy-file-input.d.ts +1 -1
  35. package/node_modules/@openclaw/fs-safe/dist/copy-file-input.js +2 -2
  36. package/node_modules/@openclaw/fs-safe/dist/copy-tree-portable.js +2 -0
  37. package/node_modules/@openclaw/fs-safe/dist/create.js +12 -5
  38. package/node_modules/@openclaw/fs-safe/dist/directory-guard.d.ts +11 -0
  39. package/node_modules/@openclaw/fs-safe/dist/directory-guard.js +4 -7
  40. package/node_modules/@openclaw/fs-safe/dist/entry-publication-types.d.ts +55 -0
  41. package/node_modules/@openclaw/fs-safe/dist/entry-publication-types.js +1 -0
  42. package/node_modules/@openclaw/fs-safe/dist/entry-publication.d.ts +8 -0
  43. package/node_modules/@openclaw/fs-safe/dist/entry-publication.js +262 -0
  44. package/node_modules/@openclaw/fs-safe/dist/exclusive-create.d.ts +3 -0
  45. package/node_modules/@openclaw/fs-safe/dist/exclusive-create.js +22 -0
  46. package/node_modules/@openclaw/fs-safe/dist/file-cleanup.d.ts +1 -6
  47. package/node_modules/@openclaw/fs-safe/dist/file-cleanup.js +3 -6
  48. package/node_modules/@openclaw/fs-safe/dist/file-identity.js +3 -6
  49. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-io.js +2 -2
  50. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-mutation.js +9 -0
  51. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root.js +4 -6
  52. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-stale-admission.js +1 -1
  53. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync.js +8 -0
  54. package/node_modules/@openclaw/fs-safe/dist/file-store-boundary.js +2 -0
  55. package/node_modules/@openclaw/fs-safe/dist/file-store-sync-write.js +20 -18
  56. package/node_modules/@openclaw/fs-safe/dist/file-store.js +4 -10
  57. package/node_modules/@openclaw/fs-safe/dist/fs.d.ts +2 -10
  58. package/node_modules/@openclaw/fs-safe/dist/fs.js +2 -10
  59. package/node_modules/@openclaw/fs-safe/dist/guarded-mkdir.d.ts +3 -4
  60. package/node_modules/@openclaw/fs-safe/dist/guarded-mkdir.js +6 -18
  61. package/node_modules/@openclaw/fs-safe/dist/guest-dispatch-python.js +1 -1
  62. package/node_modules/@openclaw/fs-safe/dist/guest-native-python.js +0 -2
  63. package/node_modules/@openclaw/fs-safe/dist/guest.js +3 -9
  64. package/node_modules/@openclaw/fs-safe/dist/index.d.ts +1 -1
  65. package/node_modules/@openclaw/fs-safe/dist/index.js +1 -1
  66. package/node_modules/@openclaw/fs-safe/dist/json-durable-queue-directory.js +1 -5
  67. package/node_modules/@openclaw/fs-safe/dist/json.js +19 -37
  68. package/node_modules/@openclaw/fs-safe/dist/move-path.js +2 -0
  69. package/node_modules/@openclaw/fs-safe/dist/mutation-authority.d.ts +1 -0
  70. package/node_modules/@openclaw/fs-safe/dist/mutation-authority.js +9 -1
  71. package/node_modules/@openclaw/fs-safe/dist/native-binding.d.ts +47 -2
  72. package/node_modules/@openclaw/fs-safe/dist/native-config.d.ts +1 -9
  73. package/node_modules/@openclaw/fs-safe/dist/native-config.js +6 -40
  74. package/node_modules/@openclaw/fs-safe/dist/native-parent-admission.d.ts +2 -0
  75. package/node_modules/@openclaw/fs-safe/dist/native-parent-admission.js +5 -2
  76. package/node_modules/@openclaw/fs-safe/dist/native-pinned-write.js +11 -316
  77. package/node_modules/@openclaw/fs-safe/dist/native-policy-parent-windows.d.ts +7 -7
  78. package/node_modules/@openclaw/fs-safe/dist/native-policy-parent-windows.js +28 -175
  79. package/node_modules/@openclaw/fs-safe/dist/native-policy-parent.d.ts +15 -0
  80. package/node_modules/@openclaw/fs-safe/dist/native-policy-parent.js +418 -0
  81. package/node_modules/@openclaw/fs-safe/dist/native-rename-outcome.js +9 -4
  82. package/node_modules/@openclaw/fs-safe/dist/native-staged-file.js +17 -13
  83. package/node_modules/@openclaw/fs-safe/dist/native-staged-symlink.js +6 -24
  84. package/node_modules/@openclaw/fs-safe/dist/native.js +5 -1
  85. package/node_modules/@openclaw/fs-safe/dist/path-case.js +10 -9
  86. package/node_modules/@openclaw/fs-safe/dist/path-scope-lexical.js +4 -1
  87. package/node_modules/@openclaw/fs-safe/dist/path-segment-route.d.ts +1 -0
  88. package/node_modules/@openclaw/fs-safe/dist/path-segment-route.js +3 -0
  89. package/node_modules/@openclaw/fs-safe/dist/path.js +4 -1
  90. package/node_modules/@openclaw/fs-safe/dist/permissions-windows.d.ts +0 -1
  91. package/node_modules/@openclaw/fs-safe/dist/pinned-mutation-admission.js +1 -3
  92. package/node_modules/@openclaw/fs-safe/dist/pinned-write-staged.js +2 -0
  93. package/node_modules/@openclaw/fs-safe/dist/pinned-write.js +9 -10
  94. package/node_modules/@openclaw/fs-safe/dist/private-producer-handoff.js +3 -6
  95. package/node_modules/@openclaw/fs-safe/dist/publish-copy-stage.js +2 -2
  96. package/node_modules/@openclaw/fs-safe/dist/publish-file.js +4 -3
  97. package/node_modules/@openclaw/fs-safe/dist/replace-file-buffer.d.ts +3 -4
  98. package/node_modules/@openclaw/fs-safe/dist/replace-file-buffer.js +18 -32
  99. package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-fallback.d.ts +5 -17
  100. package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-fallback.js +92 -230
  101. package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-source.d.ts +4 -15
  102. package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-source.js +22 -63
  103. package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.d.ts +9 -34
  104. package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.js +78 -83
  105. package/node_modules/@openclaw/fs-safe/dist/replace-file-destination.d.ts +18 -20
  106. package/node_modules/@openclaw/fs-safe/dist/replace-file-destination.js +64 -98
  107. package/node_modules/@openclaw/fs-safe/dist/replace-file-temp-owner.d.ts +16 -33
  108. package/node_modules/@openclaw/fs-safe/dist/replace-file-temp-owner.js +61 -200
  109. package/node_modules/@openclaw/fs-safe/dist/replace-file-types.d.ts +1 -6
  110. package/node_modules/@openclaw/fs-safe/dist/replace-file.js +76 -216
  111. package/node_modules/@openclaw/fs-safe/dist/root-boundary.js +14 -24
  112. package/node_modules/@openclaw/fs-safe/dist/root-context.d.ts +1 -4
  113. package/node_modules/@openclaw/fs-safe/dist/root-context.js +4 -19
  114. package/node_modules/@openclaw/fs-safe/dist/root-create-native.d.ts +52 -0
  115. package/node_modules/@openclaw/fs-safe/dist/root-create-native.js +350 -0
  116. package/node_modules/@openclaw/fs-safe/dist/root-directory-list-types.d.ts +35 -0
  117. package/node_modules/@openclaw/fs-safe/dist/root-directory-list-types.js +1 -0
  118. package/node_modules/@openclaw/fs-safe/dist/root-directory-list.d.ts +2 -27
  119. package/node_modules/@openclaw/fs-safe/dist/root-directory-list.js +31 -28
  120. package/node_modules/@openclaw/fs-safe/dist/root-directory.js +1 -4
  121. package/node_modules/@openclaw/fs-safe/dist/root-errors.js +34 -0
  122. package/node_modules/@openclaw/fs-safe/dist/root-file.js +9 -21
  123. package/node_modules/@openclaw/fs-safe/dist/root-impl.js +105 -37
  124. package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.d.ts +7 -2
  125. package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.js +67 -38
  126. package/node_modules/@openclaw/fs-safe/dist/root-observed-path.d.ts +1 -1
  127. package/node_modules/@openclaw/fs-safe/dist/root-observed-path.js +0 -2
  128. package/node_modules/@openclaw/fs-safe/dist/root-options.d.ts +2 -3
  129. package/node_modules/@openclaw/fs-safe/dist/root-path-existing.js +3 -1
  130. package/node_modules/@openclaw/fs-safe/dist/root-path-stat.js +68 -85
  131. package/node_modules/@openclaw/fs-safe/dist/root-path.js +6 -1
  132. package/node_modules/@openclaw/fs-safe/dist/root-paths-lexical.d.ts +4 -0
  133. package/node_modules/@openclaw/fs-safe/dist/root-paths-lexical.js +1 -1
  134. package/node_modules/@openclaw/fs-safe/dist/root-paths.js +4 -9
  135. package/node_modules/@openclaw/fs-safe/dist/root-remove-native.d.ts +4 -0
  136. package/node_modules/@openclaw/fs-safe/dist/root-remove-native.js +311 -0
  137. package/node_modules/@openclaw/fs-safe/dist/root-remove.d.ts +11 -0
  138. package/node_modules/@openclaw/fs-safe/dist/root-remove.js +3 -1
  139. package/node_modules/@openclaw/fs-safe/dist/root-walk.d.ts +2 -2
  140. package/node_modules/@openclaw/fs-safe/dist/root-walk.js +1 -3
  141. package/node_modules/@openclaw/fs-safe/dist/root-write-admission.d.ts +3 -3
  142. package/node_modules/@openclaw/fs-safe/dist/root-write-admission.js +8 -10
  143. package/node_modules/@openclaw/fs-safe/dist/safe-path-segment.d.ts +0 -1
  144. package/node_modules/@openclaw/fs-safe/dist/safe-path-segment.js +0 -4
  145. package/node_modules/@openclaw/fs-safe/dist/secret-file.js +12 -9
  146. package/node_modules/@openclaw/fs-safe/dist/secure-temp-dir.d.ts +0 -2
  147. package/node_modules/@openclaw/fs-safe/dist/sibling-staged-file.js +2 -0
  148. package/node_modules/@openclaw/fs-safe/dist/sibling-temp.js +7 -4
  149. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-acquire.js +13 -6
  150. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.d.ts +0 -3
  151. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js +5 -23
  152. package/node_modules/@openclaw/fs-safe/dist/staged-file-settlement.d.ts +2 -1
  153. package/node_modules/@openclaw/fs-safe/dist/staged-file-settlement.js +11 -6
  154. package/node_modules/@openclaw/fs-safe/dist/temp-cleanup.js +1 -8
  155. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-child-admission.d.ts +1 -1
  156. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-child-admission.js +2 -9
  157. package/node_modules/@openclaw/fs-safe/dist/text-atomic.d.ts +1 -6
  158. package/node_modules/@openclaw/fs-safe/dist/trash.js +6 -2
  159. package/node_modules/@openclaw/fs-safe/dist/walk.js +100 -46
  160. package/node_modules/@openclaw/fs-safe/dist/watch-hints.d.ts +7 -0
  161. package/node_modules/@openclaw/fs-safe/dist/watch-hints.js +235 -19
  162. package/node_modules/@openclaw/fs-safe/dist/watch-native.d.ts +21 -2
  163. package/node_modules/@openclaw/fs-safe/dist/watch-native.js +21 -3
  164. package/node_modules/@openclaw/fs-safe/dist/watch-rescan.d.ts +6 -0
  165. package/node_modules/@openclaw/fs-safe/dist/watch-rescan.js +111 -0
  166. package/node_modules/@openclaw/fs-safe/dist/watch-scan.d.ts +13 -0
  167. package/node_modules/@openclaw/fs-safe/dist/watch-scan.js +27 -4
  168. package/node_modules/@openclaw/fs-safe/dist/watch-stream.js +2 -0
  169. package/node_modules/@openclaw/fs-safe/dist/watch.js +131 -40
  170. package/node_modules/@openclaw/fs-safe/dist/windows-path-alias.d.ts +8 -0
  171. package/node_modules/@openclaw/fs-safe/dist/windows-path-alias.js +24 -6
  172. package/node_modules/@openclaw/fs-safe/dist/windows-path-syntax.d.ts +13 -0
  173. package/node_modules/@openclaw/fs-safe/dist/windows-path-syntax.js +51 -0
  174. package/node_modules/@openclaw/fs-safe/docs/advanced.md +4 -0
  175. package/node_modules/@openclaw/fs-safe/docs/atomic.md +4 -1
  176. package/node_modules/@openclaw/fs-safe/docs/config.md +6 -14
  177. package/node_modules/@openclaw/fs-safe/docs/contributing.md +4 -14
  178. package/node_modules/@openclaw/fs-safe/docs/durability.md +6 -29
  179. package/node_modules/@openclaw/fs-safe/docs/entry-publication.md +146 -0
  180. package/node_modules/@openclaw/fs-safe/docs/errors.md +16 -0
  181. package/node_modules/@openclaw/fs-safe/docs/file-store.md +28 -5
  182. package/node_modules/@openclaw/fs-safe/docs/index.md +2 -31
  183. package/node_modules/@openclaw/fs-safe/docs/install.md +10 -31
  184. package/node_modules/@openclaw/fs-safe/docs/json.md +1 -1
  185. package/node_modules/@openclaw/fs-safe/docs/local-roots.md +0 -1
  186. package/node_modules/@openclaw/fs-safe/docs/migrating-to-0.5.md +12 -12
  187. package/node_modules/@openclaw/fs-safe/docs/native-helper.md +35 -96
  188. package/node_modules/@openclaw/fs-safe/docs/native.md +22 -20
  189. package/node_modules/@openclaw/fs-safe/docs/path.md +1 -1
  190. package/node_modules/@openclaw/fs-safe/docs/permissions.md +1 -2
  191. package/node_modules/@openclaw/fs-safe/docs/public-api.md +1 -2
  192. package/node_modules/@openclaw/fs-safe/docs/reading.md +1 -2
  193. package/node_modules/@openclaw/fs-safe/docs/root.md +26 -184
  194. package/node_modules/@openclaw/fs-safe/docs/secret-file.md +1 -1
  195. package/node_modules/@openclaw/fs-safe/docs/security-model.md +115 -3
  196. package/node_modules/@openclaw/fs-safe/docs/store.md +2 -2
  197. package/node_modules/@openclaw/fs-safe/docs/temp.md +29 -65
  198. package/node_modules/@openclaw/fs-safe/docs/testing.md +90 -58
  199. package/node_modules/@openclaw/fs-safe/docs/types.md +3 -16
  200. package/node_modules/@openclaw/fs-safe/docs/walk.md +33 -5
  201. package/node_modules/@openclaw/fs-safe/docs/watch.md +96 -14
  202. package/node_modules/@openclaw/fs-safe/docs/writing.md +172 -16
  203. package/node_modules/@openclaw/fs-safe/package.json +10 -10
  204. package/node_modules/@openclaw/fs-safe-darwin-arm64/fs-safe-native.node +0 -0
  205. package/node_modules/@openclaw/fs-safe-darwin-arm64/package.json +1 -1
  206. package/node_modules/@openclaw/fs-safe-darwin-x64/fs-safe-native.node +0 -0
  207. package/node_modules/@openclaw/fs-safe-darwin-x64/package.json +1 -1
  208. package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/fs-safe-native.node +0 -0
  209. package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/package.json +1 -1
  210. package/node_modules/@openclaw/fs-safe-linux-arm64-musl/fs-safe-native.node +0 -0
  211. package/node_modules/@openclaw/fs-safe-linux-arm64-musl/package.json +1 -1
  212. package/node_modules/@openclaw/fs-safe-linux-x64-gnu/fs-safe-native.node +0 -0
  213. package/node_modules/@openclaw/fs-safe-linux-x64-gnu/package.json +1 -1
  214. package/node_modules/@openclaw/fs-safe-linux-x64-musl/fs-safe-native.node +0 -0
  215. package/node_modules/@openclaw/fs-safe-linux-x64-musl/package.json +1 -1
  216. package/node_modules/@openclaw/fs-safe-win32-x64-msvc/fs-safe-native.node +0 -0
  217. package/node_modules/@openclaw/fs-safe-win32-x64-msvc/package.json +1 -1
  218. package/package.json +5 -5
  219. package/skills/feishu-wiki/SKILL.md +1 -1
  220. package/dist/.setup/accounts-wRqItHug.mjs +0 -206
  221. package/node_modules/@openclaw/fs-safe/dist/watch-alias.d.ts +0 -6
  222. package/node_modules/@openclaw/fs-safe/dist/watch-alias.js +0 -88
  223. package/node_modules/@openclaw/fs-safe/docs/mutation-policy-proof.md +0 -69
  224. package/node_modules/@openclaw/fs-safe/docs/private-file-store.md +0 -68
  225. package/node_modules/@openclaw/fs-safe/docs/test-hooks.md +0 -110
@@ -1,7 +1,7 @@
1
1
  import fsSync from "node:fs";
2
2
  import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
- import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard, } from "./directory-guard.js";
4
+ import { assertDirectoryGuard, captureDirectoryGuard, } from "./directory-guard.js";
5
5
  import { FsSafeError } from "./errors.js";
6
6
  import { pathExists } from "./fs.js";
7
7
  import { realpathSync } from "./realpath.js";
@@ -22,27 +22,35 @@ function ensureDirectoryFailure(code, message, cause) {
22
22
  error: new FsSafeError(code, message, { cause }),
23
23
  };
24
24
  }
25
- async function assertGuardResult(guard, scopeLabel) {
25
+ function directoryGuardResult(target, scopeLabel) {
26
26
  try {
27
- await assertAsyncDirectoryGuard(guard);
28
- return { ok: true };
27
+ const guard = typeof target === "string" ? captureDirectoryGuard(target, "native", { bigint: true }) : target;
28
+ if (typeof target !== "string")
29
+ assertDirectoryGuard(guard, "native");
30
+ return { ok: true, guard };
29
31
  }
30
32
  catch (err) {
31
- if (err instanceof FsSafeError) {
32
- return await directoryGuardFailure(err, guard.dir, scopeLabel);
33
+ if (!(err instanceof FsSafeError))
34
+ throw err;
35
+ const dir = typeof target === "string" ? target : target.dir;
36
+ if (err.code !== "not-file") {
37
+ return { ok: false, code: err.code, error: err };
33
38
  }
34
- throw err;
35
- }
36
- }
37
- async function createDirectoryGuardResult(dir, scopeLabel) {
38
- try {
39
- return { ok: true, guard: await createAsyncDirectoryGuard(dir) };
40
- }
41
- catch (err) {
42
- if (err instanceof FsSafeError) {
43
- return await directoryGuardFailure(err, dir, scopeLabel);
39
+ try {
40
+ const stat = fsSync.lstatSync(pathForWindowsFilesystem(dir));
41
+ const failure = classifyExistingDirectorySegment(stat, scopeLabel);
42
+ if (failure) {
43
+ return failure;
44
+ }
44
45
  }
45
- throw err;
46
+ catch (lookupErr) {
47
+ const failure = classifyDirectoryLookupError(lookupErr, scopeLabel);
48
+ if (failure) {
49
+ return failure;
50
+ }
51
+ throw lookupErr;
52
+ }
53
+ return { ok: false, code: err.code, error: err };
46
54
  }
47
55
  }
48
56
  function classifyDirectoryLookupError(err, scopeLabel) {
@@ -64,27 +72,7 @@ function classifyExistingDirectorySegment(stat, scopeLabel) {
64
72
  }
65
73
  return null;
66
74
  }
67
- async function directoryGuardFailure(err, dir, scopeLabel) {
68
- if (err.code !== "not-file") {
69
- return { ok: false, code: err.code, error: err };
70
- }
71
- try {
72
- const stat = fsSync.lstatSync(pathForWindowsFilesystem(dir));
73
- const failure = classifyExistingDirectorySegment(stat, scopeLabel);
74
- if (failure) {
75
- return failure;
76
- }
77
- }
78
- catch (lookupErr) {
79
- const failure = classifyDirectoryLookupError(lookupErr, scopeLabel);
80
- if (failure) {
81
- return failure;
82
- }
83
- throw lookupErr;
84
- }
85
- return { ok: false, code: err.code, error: err };
86
- }
87
- async function resolveTrustedDirectoryPrefix(targetPath, scopeLabel) {
75
+ function resolveTrustedDirectoryPrefix(targetPath, scopeLabel) {
88
76
  const root = path.parse(targetPath).root;
89
77
  let current = root;
90
78
  let currentStat;
@@ -119,7 +107,6 @@ async function resolveTrustedDirectoryPrefix(targetPath, scopeLabel) {
119
107
  return segmentFailure;
120
108
  }
121
109
  current = next;
122
- currentStat = nextStat;
123
110
  }
124
111
  catch (err) {
125
112
  const code = err.code;
@@ -192,7 +179,7 @@ export async function ensureAbsoluteDirectory(dirPath, options = {}) {
192
179
  return prefix;
193
180
  }
194
181
  let current = prefix.ancestorPath;
195
- const initialGuard = await createDirectoryGuardResult(prefix.ancestorPath, scopeLabel);
182
+ const initialGuard = await directoryGuardResult(prefix.ancestorPath, scopeLabel);
196
183
  if (!initialGuard.ok) {
197
184
  return initialGuard;
198
185
  }
@@ -200,25 +187,22 @@ export async function ensureAbsoluteDirectory(dirPath, options = {}) {
200
187
  for (const segment of prefix.missingSegments) {
201
188
  current = path.join(current, segment);
202
189
  while (true) {
203
- const guardResult = await assertGuardResult(currentGuard, scopeLabel);
190
+ const guardResult = await directoryGuardResult(currentGuard, scopeLabel);
204
191
  if (!guardResult.ok) {
205
192
  return guardResult;
206
193
  }
207
194
  try {
208
195
  const stat = fsSync.lstatSync(current);
209
- if (stat.isSymbolicLink()) {
210
- return ensureDirectoryFailure("symlink", `directory path traverses a symlink within ${scopeLabel}`);
211
- }
212
- if (!stat.isDirectory()) {
213
- return ensureDirectoryFailure("not-file", `path must be a real directory within ${scopeLabel}`);
214
- }
196
+ const failure = classifyExistingDirectorySegment(stat, scopeLabel);
197
+ if (failure)
198
+ return failure;
215
199
  break;
216
200
  }
217
201
  catch (err) {
218
202
  if (err.code !== "ENOENT") {
219
203
  throw err;
220
204
  }
221
- const parentStillValid = await assertGuardResult(currentGuard, scopeLabel);
205
+ const parentStillValid = await directoryGuardResult(currentGuard, scopeLabel);
222
206
  if (!parentStillValid.ok) {
223
207
  return parentStillValid;
224
208
  }
@@ -233,17 +217,17 @@ export async function ensureAbsoluteDirectory(dirPath, options = {}) {
233
217
  }
234
218
  }
235
219
  }
236
- const nextGuard = await createDirectoryGuardResult(current, scopeLabel);
220
+ const nextGuard = await directoryGuardResult(current, scopeLabel);
237
221
  if (!nextGuard.ok) {
238
222
  return nextGuard;
239
223
  }
240
- const previousGuardStillValid = await assertGuardResult(currentGuard, scopeLabel);
224
+ const previousGuardStillValid = await directoryGuardResult(currentGuard, scopeLabel);
241
225
  if (!previousGuardStillValid.ok) {
242
226
  return previousGuardStillValid;
243
227
  }
244
228
  currentGuard = nextGuard.guard;
245
229
  }
246
- const finalGuardResult = await assertGuardResult(currentGuard, scopeLabel);
230
+ const finalGuardResult = await directoryGuardResult(currentGuard, scopeLabel);
247
231
  if (!finalGuardResult.ok) {
248
232
  return finalGuardResult;
249
233
  }
@@ -39,3 +39,5 @@ export { createIcaclsResetCommand, formatIcaclsResetCommand, formatWindowsAclSum
39
39
  export type { PermissionCommandFailure } from "./permission-exec.js";
40
40
  export { retainFileInDirectory } from "./retained-file.js";
41
41
  export type { RetainedFile, RetainedFileAdmission, RetainedFileExpected, RetainedFileIssue, RetainedFileReceipt, RetainedFileResult, RetainFileInDirectoryOptions, } from "./retained-file-types.js";
42
+ export { retainEntryForPublication } from "./entry-publication.js";
43
+ export type { PublicationIdentity, PublicationParent, RetainEntryForPublicationOptions, EntryPublicationReceipt, EntryPublicationIssue, EntryPublicationResult, RetainedEntryPublication, } from "./entry-publication-types.js";
@@ -38,3 +38,4 @@ export { buildRandomTempFilePath, sanitizeTempFileName, tempFile, withTempFile,
38
38
  export { writeSiblingTempFile, writeViaSiblingTempPath, } from "./sibling-temp.js";
39
39
  export { createIcaclsResetCommand, formatIcaclsResetCommand, formatWindowsAclSummary, inspectWindowsAcl, parseIcaclsOutput, resolveWindowsUserPrincipal, summarizeWindowsAcl, } from "./permissions-windows.js";
40
40
  export { retainFileInDirectory } from "./retained-file.js";
41
+ export { retainEntryForPublication } from "./entry-publication.js";
@@ -6,6 +6,7 @@ import { FsSafeError } from "./errors.js";
6
6
  import { inspectFileIdentity } from "./strict-file-identity.js";
7
7
  import { resolveReadOpenFlags } from "./read-open-flags.js";
8
8
  import { tempFile } from "./temp-target.js";
9
+ import { assertExclusiveCreateLeaf } from "./exclusive-create.js";
9
10
  import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
10
11
  async function closeFileHandle(handle) {
11
12
  if (handle)
@@ -64,6 +65,7 @@ export async function stageArchiveFileForExtraction(params) {
64
65
  (process.platform !== "win32" && "O_NOFOLLOW" in fsConstants
65
66
  ? fsConstants.O_NOFOLLOW
66
67
  : 0);
68
+ assertExclusiveCreateLeaf(staged.path);
67
69
  output = await fs.open(staged.path, flags, 0o600);
68
70
  const buffer = Buffer.allocUnsafe(Math.min(512 * 1024, Math.max(64 * 1024, Number(opened.size)), params.limits.maxArchiveBytes + 1));
69
71
  let written = 0;
@@ -3,6 +3,7 @@ import { type AsyncDirectoryGuard } from "./directory-guard.js";
3
3
  import { type ExtractionDeadline } from "./archive-deadline.js";
4
4
  import { ArchiveSecurityError } from "./archive-errors.js";
5
5
  export type ArchiveDirectoryGuard = AsyncDirectoryGuard<BigIntStats>;
6
+ export declare function createArchiveSymlinkTraversalError(originalPath: string): ArchiveSecurityError;
6
7
  export declare function createDirectoryIdentityGuard(dir: string): Promise<ArchiveDirectoryGuard>;
7
8
  export declare function assertDirectoryIdentityGuard(guard: ArchiveDirectoryGuard): Promise<void>;
8
9
  export declare function prepareArchiveDestinationGuard(destDir: string): Promise<ArchiveDirectoryGuard>;
@@ -28,5 +29,4 @@ export declare function withStagedArchiveDestination<T>(params: {
28
29
  stagingDirPrefix?: string;
29
30
  run: (stagingDir: string) => Promise<T>;
30
31
  }): Promise<T>;
31
- export declare function createArchiveSymlinkTraversalError(originalPath: string): ArchiveSecurityError;
32
32
  export {};
@@ -14,14 +14,14 @@ import { mkdirPathComponentsWithGuards } from "./guarded-mkdir.js";
14
14
  import { expandRelativePathWithHome } from "./root-context.js";
15
15
  import { resolveRootPath } from "./root-path.js";
16
16
  import { inspectFileIdentitySync } from "./strict-file-identity.js";
17
- import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
17
+ import { assertNoWindowsPathAlias, isForeignWindowsShareOrDevicePath } from "./windows-path-alias.js";
18
18
  import { realpathSync } from "./realpath.js";
19
19
  const ERROR_ARCHIVE_ENTRY_TRAVERSES_SYMLINK = "archive entry traverses symlink in destination";
20
20
  const ARCHIVE_STAGING_MODE = 0o700;
21
21
  function checkExtractionDeadline(deadline) {
22
22
  deadline?.check();
23
23
  }
24
- function symlinkTraversalError(originalPath) {
24
+ export function createArchiveSymlinkTraversalError(originalPath) {
25
25
  return new ArchiveSecurityError("destination-symlink-traversal", `${ERROR_ARCHIVE_ENTRY_TRAVERSES_SYMLINK}: ${formatErrorDetail(originalPath)}`);
26
26
  }
27
27
  export async function createDirectoryIdentityGuard(dir) {
@@ -106,7 +106,7 @@ async function assertNoSymlinkTraversal(params) {
106
106
  throw err;
107
107
  }
108
108
  if (stat.isSymbolicLink()) {
109
- throw symlinkTraversalError(params.originalPath);
109
+ throw createArchiveSymlinkTraversalError(params.originalPath);
110
110
  }
111
111
  }
112
112
  }
@@ -128,7 +128,7 @@ export async function assertResolvedInsideDestination(params) {
128
128
  }
129
129
  assertNoWindowsPathAlias(resolved);
130
130
  if (!isPathInside(destinationRealDir, resolved)) {
131
- throw symlinkTraversalError(originalPath);
131
+ throw createArchiveSymlinkTraversalError(originalPath);
132
132
  }
133
133
  }
134
134
  async function mkdirArchiveOutput(params) {
@@ -139,7 +139,7 @@ async function mkdirArchiveOutput(params) {
139
139
  }
140
140
  catch (error) {
141
141
  if (error instanceof FsSafeError) {
142
- throw symlinkTraversalError(params.originalPath);
142
+ throw createArchiveSymlinkTraversalError(params.originalPath);
143
143
  }
144
144
  throw error;
145
145
  }
@@ -166,6 +166,9 @@ export async function preparePrivateArchiveOutputPath(params, assertGuards, dest
166
166
  await prepareOutputPath(ownArchiveOutputPathParams(params), assertGuards, true, destinationGuard);
167
167
  }
168
168
  async function prepareOutputPath(params, assertGuards, privateWorkingMode = false, existingDestinationGuard) {
169
+ if (isForeignWindowsShareOrDevicePath(params.outPath, [params.destinationDir, params.destinationRealDir])) {
170
+ throw createArchiveSymlinkTraversalError(params.originalPath);
171
+ }
169
172
  const targetRoot = privateWorkingMode ? {
170
173
  async mkdir(relativePath) {
171
174
  // Retain Root.mkdir's strict alias admission before the shared traversal,
@@ -298,11 +301,7 @@ export async function withStagedArchiveDestination(params) {
298
301
  await fs.rm(stagingDir, { recursive: true, force: true }).catch(() => undefined);
299
302
  }
300
303
  catch {
301
- // The staging path identity changed; deleting by name could target data
302
- // outside the private temp tree, so fail closed and leave it for OS cleanup.
304
+ // Preserve staging when its identity cannot be verified; pathname cleanup is unsafe.
303
305
  }
304
306
  }
305
307
  }
306
- export function createArchiveSymlinkTraversalError(originalPath) {
307
- return symlinkTraversalError(originalPath);
308
- }
@@ -17,6 +17,7 @@ import { ArchiveSecurityError, ArchiveFormatError, isArchiveFormatErrorMessage,
17
17
  import { stageArchiveFileForExtraction } from "./archive-input.js";
18
18
  import { getNativeBinding, } from "./native.js";
19
19
  import { writeSiblingTempFile } from "./sibling-temp.js";
20
+ import { assertExclusiveCreateLeaf } from "./exclusive-create.js";
20
21
  import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
21
22
  import { classifyArchiveParserError } from "./archive-parser-errors.js";
22
23
  import { validateArchiveEntryPath } from "./archive-entry.js";
@@ -61,6 +62,7 @@ async function writeZipFileEntry(params) {
61
62
  syncTempFile: false,
62
63
  syncParentDir: false,
63
64
  writeTemp: async (tempPath) => {
65
+ assertExclusiveCreateLeaf(tempPath);
64
66
  tempHandle = await fs.open(tempPath, OPEN_WRITE_CREATE_FLAGS, 0o600);
65
67
  const writable = tempHandle.createWriteStream();
66
68
  writable.once("close", () => {
@@ -83,11 +85,6 @@ async function writeZipFileEntry(params) {
83
85
  resolveFinalPath: (filePath) => filePath,
84
86
  });
85
87
  }
86
- catch (err) {
87
- // Failures here happen before the temp has been committed. The destination
88
- // parent may already be untrusted, so cleanup must stay limited to temp state.
89
- throw err;
90
- }
91
88
  finally {
92
89
  const openTempHandle = tempHandle;
93
90
  if (openTempHandle && !handleClosedByStream) {
@@ -0,0 +1,51 @@
1
+ import syncFs, { type BigIntStats, type Stats } from "node:fs";
2
+ import fs, { type FileHandle } from "node:fs/promises";
3
+ export type Procedure<T> = Generator<unknown, T, unknown>;
4
+ type SyncFchmod = (fd: number, mode: number) => void;
5
+ type AsyncAtomicFileSystem = Partial<Pick<typeof fs, "open" | "lstat" | "mkdir" | "rename" | "rm" | "unlink" | "writeFile">>;
6
+ type SyncAtomicFileSystem = Partial<Pick<typeof syncFs, "openSync" | "lstatSync" | "mkdirSync" | "renameSync" | "rmSync" | "unlinkSync" | "fstatSync" | "closeSync" | "fsyncSync" | "writeFileSync" | "readFileSync" | "readSync" | "writeSync" | "ftruncateSync">>;
7
+ export declare function wait<T>(value: T | PromiseLike<T>): Procedure<T>;
8
+ export declare function runAsync<T>(procedure: Procedure<T>): Promise<T>;
9
+ export declare function runSync<T>(procedure: Procedure<T>): T;
10
+ export declare function inspectAtomicIdentity<T extends Pick<BigIntStats, "dev" | "ino">>(io: AtomicIo, read: () => T | Promise<T>, expected?: Pick<BigIntStats, "dev" | "ino">, synchronous?: boolean, admit?: (stat: T) => void): T | Promise<T>;
11
+ /** One adapter per operation; each admitted descriptor receives one AtomicFile. */
12
+ export declare class AtomicIo {
13
+ readonly asyncFs: AsyncAtomicFileSystem | undefined;
14
+ readonly syncFs: SyncAtomicFileSystem | undefined;
15
+ fchmodSync?: SyncFchmod | undefined;
16
+ readonly asynchronous: boolean;
17
+ private constructor();
18
+ static async(fsModule: AsyncAtomicFileSystem): AtomicIo;
19
+ static sync(fsModule: SyncAtomicFileSystem, fchmodSync?: SyncFchmod): AtomicIo;
20
+ wrap(resource: FileHandle | number): AtomicFile;
21
+ open(pathname: string, flags: string | number, mode?: number): Procedure<AtomicFile>;
22
+ lstat(pathname: string): Procedure<Stats>;
23
+ lstatExact(pathname: string, synchronousBuiltin?: boolean): BigIntStats | Promise<BigIntStats>;
24
+ mkdir(directory: string, mode: number): Procedure<void>;
25
+ rename(source: string, destination: string): Procedure<void>;
26
+ remove(pathname: string): Procedure<void>;
27
+ unlink(pathname: string): Procedure<void>;
28
+ delay(milliseconds: number): Procedure<void>;
29
+ }
30
+ export declare class AtomicFile {
31
+ readonly io: AtomicIo;
32
+ readonly resource: FileHandle | number;
33
+ constructor(io: AtomicIo, resource: FileHandle | number);
34
+ get fd(): number;
35
+ stat(): Procedure<Stats>;
36
+ statExact(): BigIntStats | Promise<BigIntStats>;
37
+ close(): void | Promise<void>;
38
+ sync(): Procedure<void>;
39
+ syncBestEffort(): Procedure<void>;
40
+ chmod(mode: number): void | Promise<void>;
41
+ writeFile(data: string | Uint8Array, throughModule?: boolean): void | Promise<void>;
42
+ readFile(sizeHint?: number): Procedure<Buffer>;
43
+ read(buffer: Buffer, offset: number, length: number, position: number | null): number | Promise<{
44
+ bytesRead: number;
45
+ }>;
46
+ write(buffer: Buffer, offset: number, length: number, position: number | null): number | Promise<{
47
+ bytesWritten: number;
48
+ }>;
49
+ truncate(length: number): Procedure<void>;
50
+ }
51
+ export {};
@@ -0,0 +1,242 @@
1
+ import syncFs, {} from "node:fs";
2
+ import fs, {} from "node:fs/promises";
3
+ import { readBoundedSync } from "./bounded-read.js";
4
+ import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
5
+ import { inspectFileIdentity, inspectFileIdentitySync } from "./strict-file-identity.js";
6
+ import { sleep, sleepSync } from "./timing.js";
7
+ import { assertExclusiveCreateStat } from "./exclusive-create.js";
8
+ import { hasNodeErrorCode } from "./path.js";
9
+ export function* wait(value) {
10
+ return (yield value);
11
+ }
12
+ export async function runAsync(procedure) {
13
+ let step = procedure.next();
14
+ while (!step.done) {
15
+ let value;
16
+ try {
17
+ value = await step.value;
18
+ }
19
+ catch (error) {
20
+ step = procedure.throw(error);
21
+ continue;
22
+ }
23
+ step = procedure.next(value);
24
+ }
25
+ return step.value;
26
+ }
27
+ export function runSync(procedure) {
28
+ let step = procedure.next();
29
+ // Sync adapters retain their ordinary return values; this driver never reads then.
30
+ while (!step.done)
31
+ step = procedure.next(step.value);
32
+ return step.value;
33
+ }
34
+ export function inspectAtomicIdentity(io, read, expected, synchronous = false, admit) {
35
+ if (io.asynchronous && !synchronous) {
36
+ const inspect = admit ? async () => {
37
+ const stat = await read();
38
+ admit(stat);
39
+ return stat;
40
+ } : read;
41
+ return inspectFileIdentity(inspect, expected);
42
+ }
43
+ // Sync metadata is an ordinary value, including objects with a then getter.
44
+ const inspect = admit ? () => {
45
+ const stat = read();
46
+ admit(stat);
47
+ return stat;
48
+ } : read;
49
+ return inspectFileIdentitySync(inspect, expected);
50
+ }
51
+ /** One adapter per operation; each admitted descriptor receives one AtomicFile. */
52
+ export class AtomicIo {
53
+ asyncFs;
54
+ syncFs;
55
+ fchmodSync;
56
+ asynchronous;
57
+ constructor(asyncFs, syncFs, fchmodSync) {
58
+ this.asyncFs = asyncFs;
59
+ this.syncFs = syncFs;
60
+ this.fchmodSync = fchmodSync;
61
+ this.asynchronous = asyncFs !== undefined;
62
+ }
63
+ static async(fsModule) {
64
+ return new AtomicIo(fsModule, undefined);
65
+ }
66
+ static sync(fsModule, fchmodSync) {
67
+ return new AtomicIo(undefined, fsModule, fchmodSync);
68
+ }
69
+ wrap(resource) {
70
+ return new AtomicFile(this, resource);
71
+ }
72
+ *open(pathname, flags, mode) {
73
+ const withMode = arguments.length > 2;
74
+ const exclusive = syncFs.constants.O_CREAT | syncFs.constants.O_EXCL;
75
+ if (process.platform === "win32" && (typeof flags === "string"
76
+ ? flags.includes("x") : (flags & exclusive) === exclusive)) {
77
+ try {
78
+ assertExclusiveCreateStat(pathname, yield* this.lstat(pathname));
79
+ }
80
+ catch (error) {
81
+ if (!hasNodeErrorCode(error, "ENOENT"))
82
+ throw error;
83
+ }
84
+ }
85
+ const resource = this.asyncFs
86
+ ? yield* wait(withMode
87
+ ? this.asyncFs.open(pathname, flags, mode)
88
+ : this.asyncFs.open(pathname, flags))
89
+ : withMode
90
+ ? this.syncFs.openSync(pathname, flags, mode)
91
+ : this.syncFs.openSync(pathname, flags);
92
+ return this.wrap(resource);
93
+ }
94
+ *lstat(pathname) {
95
+ if (!this.asyncFs)
96
+ return this.syncFs.lstatSync(pathname);
97
+ if (this.asyncFs === fs)
98
+ return syncFs.lstatSync(pathname);
99
+ return yield* wait(this.asyncFs.lstat(pathname));
100
+ }
101
+ lstatExact(pathname, synchronousBuiltin = true) {
102
+ if (!this.asyncFs)
103
+ return this.syncFs.lstatSync(pathname, { bigint: true });
104
+ if (synchronousBuiltin && this.asyncFs === fs)
105
+ return syncFs.lstatSync(pathname, { bigint: true });
106
+ return this.asyncFs.lstat(pathname, { bigint: true });
107
+ }
108
+ *mkdir(directory, mode) {
109
+ if (this.asyncFs) {
110
+ const pathname = this.asyncFs === fs ? recursiveMkdirPath(directory) : directory;
111
+ yield* wait(this.asyncFs.mkdir(pathname, { recursive: true, mode }));
112
+ }
113
+ else {
114
+ const pathname = this.syncFs === syncFs ? recursiveMkdirPath(directory) : directory;
115
+ this.syncFs.mkdirSync(pathname, { recursive: true, mode });
116
+ }
117
+ }
118
+ *rename(source, destination) {
119
+ if (this.asyncFs)
120
+ yield* wait(this.asyncFs.rename(source, destination));
121
+ else
122
+ this.syncFs.renameSync(source, destination);
123
+ }
124
+ *remove(pathname) {
125
+ if (this.asyncFs)
126
+ yield* wait(this.asyncFs.rm(pathname, { force: true }));
127
+ else
128
+ this.syncFs.rmSync(pathname, { force: true });
129
+ }
130
+ *unlink(pathname) {
131
+ if (this.asyncFs)
132
+ yield* wait(this.asyncFs.unlink(pathname));
133
+ else
134
+ this.syncFs.unlinkSync(pathname);
135
+ }
136
+ *delay(milliseconds) {
137
+ if (this.asynchronous)
138
+ yield* wait(sleep(milliseconds));
139
+ else
140
+ sleepSync(milliseconds);
141
+ }
142
+ }
143
+ export class AtomicFile {
144
+ io;
145
+ resource;
146
+ constructor(io, resource) {
147
+ this.io = io;
148
+ this.resource = resource;
149
+ }
150
+ get fd() {
151
+ return typeof this.resource === "number" ? this.resource : this.resource.fd;
152
+ }
153
+ *stat() {
154
+ if (typeof this.resource === "number")
155
+ return this.io.syncFs.fstatSync(this.resource);
156
+ if (this.io.asyncFs === fs)
157
+ return syncFs.fstatSync(this.resource.fd);
158
+ return yield* wait(this.resource.stat());
159
+ }
160
+ statExact() {
161
+ if (typeof this.resource === "number")
162
+ return this.io.syncFs.fstatSync(this.resource, { bigint: true });
163
+ if (this.io.asyncFs === fs)
164
+ return syncFs.fstatSync(this.resource.fd, { bigint: true });
165
+ return this.resource.stat({ bigint: true });
166
+ }
167
+ close() {
168
+ if (typeof this.resource === "number")
169
+ this.io.syncFs.closeSync(this.resource);
170
+ else
171
+ return this.resource.close();
172
+ }
173
+ *sync() {
174
+ if (typeof this.resource === "number")
175
+ this.io.syncFs.fsyncSync(this.resource);
176
+ else
177
+ yield* wait(this.resource.sync());
178
+ }
179
+ *syncBestEffort() {
180
+ try {
181
+ yield* this.sync();
182
+ }
183
+ catch (error) {
184
+ if (error?.code !== "EPERM")
185
+ throw error;
186
+ }
187
+ }
188
+ chmod(mode) {
189
+ if (typeof this.resource === "number") {
190
+ const chmod = this.io.fchmodSync;
191
+ chmod?.(this.resource, mode);
192
+ }
193
+ else {
194
+ return this.resource.chmod(mode);
195
+ }
196
+ }
197
+ writeFile(data, throughModule = false) {
198
+ if (typeof this.resource === "number") {
199
+ this.io.syncFs.writeFileSync(this.resource, data);
200
+ }
201
+ else if (throughModule) {
202
+ return this.io.asyncFs.writeFile(this.resource, data);
203
+ }
204
+ else {
205
+ return this.resource.writeFile(data);
206
+ }
207
+ }
208
+ *readFile(sizeHint) {
209
+ if (typeof this.resource !== "number")
210
+ return yield* wait(this.resource.readFile());
211
+ if (sizeHint === undefined)
212
+ return this.io.syncFs.readFileSync(this.resource);
213
+ const fd = this.resource;
214
+ let position = 0;
215
+ return readBoundedSync(Infinity, (buffer, length) => {
216
+ const bytesRead = this.io.syncFs.readSync(fd, buffer, 0, length, position);
217
+ position += bytesRead;
218
+ return bytesRead;
219
+ }, { initialSize: Number.isSafeInteger(sizeHint) && sizeHint >= 0 ? sizeHint : undefined });
220
+ }
221
+ read(buffer, offset, length, position) {
222
+ if (typeof this.resource === "number") {
223
+ return this.io.syncFs.readSync(this.resource, buffer, offset, length, position);
224
+ }
225
+ return this.resource.read(buffer, offset, length, position);
226
+ }
227
+ write(buffer, offset, length, position) {
228
+ if (typeof this.resource === "number") {
229
+ const written = this.io.syncFs.writeSync(this.resource, buffer, offset, length, position);
230
+ if (written === 0)
231
+ throw new Error("Copy fallback write made no progress");
232
+ return written;
233
+ }
234
+ return this.resource.write(buffer, offset, length, position);
235
+ }
236
+ *truncate(length) {
237
+ if (typeof this.resource === "number")
238
+ this.io.syncFs.ftruncateSync(this.resource, length);
239
+ else
240
+ yield* wait(this.resource.truncate(length));
241
+ }
242
+ }
@@ -1,2 +1,2 @@
1
- export { configureFsSafePython, configureFsSafeNative, getFsSafeNativeConfig, type FsSafePythonConfig, type FsSafeNativeConfig, type FsSafeNativeMode, } from "./native-config.js";
1
+ export { configureFsSafeNative, getFsSafeNativeConfig, type FsSafeNativeConfig, type FsSafeNativeMode, } from "./native-config.js";
2
2
  export { configureFsSafeLocks, getFsSafeLockConfig, type FsSafeLockConfig, } from "./lock-config.js";
@@ -1,2 +1,2 @@
1
- export { configureFsSafePython, configureFsSafeNative, getFsSafeNativeConfig, } from "./native-config.js";
1
+ export { configureFsSafeNative, getFsSafeNativeConfig, } from "./native-config.js";
2
2
  export { configureFsSafeLocks, getFsSafeLockConfig, } from "./lock-config.js";
@@ -17,5 +17,5 @@ export declare function assertCopySourceCurrent(source: {
17
17
  realPath: string;
18
18
  }, identity: BigIntStats): Promise<void>;
19
19
  export declare function writeCopyFileToFd(fd: number, input: CopyFileInput, maxBytes?: number, assertBeforeMutation?: () => void): Promise<void>;
20
- export declare function createNativeCopyFile(native: NativeBinding, input: CopyFileInput, parentFd: number, basename: string, maxBytes: number | undefined, sync: boolean): Promise<NativeFileCopyResult | undefined>;
20
+ export declare function createNativeCopyFile(native: NativeBinding, input: CopyFileInput, parentFd: number, basename: string, maxBytes: number | undefined): Promise<NativeFileCopyResult | undefined>;
21
21
  export declare function assertNativeCopyCompleted(input: CopyFileInput, copied?: NativeFileCopyResult): void;
@@ -27,7 +27,7 @@ export async function writeCopyFileToFd(fd, input, maxBytes, assertBeforeMutatio
27
27
  maxBytes, sizeHint: input.size, signal: input.signal, assertBeforeMutation,
28
28
  });
29
29
  }
30
- export async function createNativeCopyFile(native, input, parentFd, basename, maxBytes, sync) {
30
+ export async function createNativeCopyFile(native, input, parentFd, basename, maxBytes) {
31
31
  input.signal?.throwIfAborted();
32
32
  if (!native.copyFileExclusive) {
33
33
  if (input.clone === "always") {
@@ -39,7 +39,7 @@ export async function createNativeCopyFile(native, input, parentFd, basename, ma
39
39
  const nativeSignal = input.signal ? AbortSignal.any([input.signal]) : undefined;
40
40
  try {
41
41
  // The caller adopts this descriptor before observing a later cancellation.
42
- return await native.copyFileExclusive(input.handle.fd, parentFd, basename, input.clone, maxBytes !== undefined && Number.isFinite(maxBytes) ? maxBytes : undefined, nativeSignal, sync);
42
+ return await native.copyFileExclusive(input.handle.fd, parentFd, basename, input.clone, maxBytes !== undefined && Number.isFinite(maxBytes) ? maxBytes : undefined, nativeSignal);
43
43
  }
44
44
  catch (error) {
45
45
  const code = error?.code;
@@ -3,6 +3,7 @@ import fs from "node:fs";
3
3
  import fsp, {} from "node:fs/promises";
4
4
  import path from "node:path";
5
5
  import { FsSafeError } from "./errors.js";
6
+ import { assertExclusiveCreateLeaf } from "./exclusive-create.js";
6
7
  import { assertStagedDirectoryCurrent, exactIdentityMatches, openStagedDirectory, } from "./staged-directory.js";
7
8
  function timestampSeconds(nanoseconds) {
8
9
  // Dates discard sub-millisecond precision; negative numbers mean "now" in
@@ -49,6 +50,7 @@ export async function copyOwnedTree(source, destination, options) {
49
50
  if (!opened.isFile() || !exactIdentityMatches(stat, opened)) {
50
51
  throw new FsSafeError("path-mismatch", "copy source changed while opening");
51
52
  }
53
+ assertExclusiveCreateLeaf(to);
52
54
  output = await fsp.open(to, "wx", 0o600);
53
55
  if (options.copyFileContents) {
54
56
  // napi-rs owns onabort. Each admitted file gets a separate signal; the