@openclaw/fs-safe 0.14.0 → 0.16.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 (370) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/README.md +38 -10
  3. package/dist/advanced.d.ts +2 -1
  4. package/dist/advanced.d.ts.map +1 -1
  5. package/dist/advanced.js +2 -1
  6. package/dist/archive-kind.d.ts +0 -1
  7. package/dist/archive-kind.d.ts.map +1 -1
  8. package/dist/archive-kind.js +5 -17
  9. package/dist/archive-merge.d.ts.map +1 -1
  10. package/dist/archive-merge.js +113 -46
  11. package/dist/archive-parser.wasm +0 -0
  12. package/dist/archive-read.d.ts.map +1 -1
  13. package/dist/archive-read.js +10 -11
  14. package/dist/archive-tar-stream.d.ts +3 -0
  15. package/dist/archive-tar-stream.d.ts.map +1 -1
  16. package/dist/archive-tar-stream.js +56 -37
  17. package/dist/archive-tar-wasm.d.ts +16 -4
  18. package/dist/archive-tar-wasm.d.ts.map +1 -1
  19. package/dist/archive-tar-wasm.js +134 -34
  20. package/dist/archive-zip-directory.d.ts +4 -0
  21. package/dist/archive-zip-directory.d.ts.map +1 -1
  22. package/dist/archive-zip-directory.js +2 -0
  23. package/dist/archive-zip-entry.d.ts +6 -2
  24. package/dist/archive-zip-entry.d.ts.map +1 -1
  25. package/dist/archive-zip-entry.js +23 -8
  26. package/dist/archive-zip-integrity.d.ts.map +1 -1
  27. package/dist/archive-zip-integrity.js +3 -4
  28. package/dist/archive-zip-loader.d.ts.map +1 -1
  29. package/dist/archive-zip-loader.js +107 -31
  30. package/dist/archive-zip-names.d.ts +1 -0
  31. package/dist/archive-zip-names.d.ts.map +1 -1
  32. package/dist/archive-zip-names.js +6 -0
  33. package/dist/archive.d.ts.map +1 -1
  34. package/dist/archive.js +11 -11
  35. package/dist/bounded-read-stream.d.ts +0 -1
  36. package/dist/bounded-read-stream.d.ts.map +1 -1
  37. package/dist/bounded-read-stream.js +0 -6
  38. package/dist/clone-metadata.d.ts +1 -0
  39. package/dist/clone-metadata.d.ts.map +1 -1
  40. package/dist/clone-metadata.js +6 -2
  41. package/dist/copy-publication.d.ts +6 -0
  42. package/dist/copy-publication.d.ts.map +1 -1
  43. package/dist/copy-publication.js +3 -0
  44. package/dist/copy-tree-portable.d.ts.map +1 -1
  45. package/dist/copy-tree-portable.js +44 -24
  46. package/dist/copy.d.ts.map +1 -1
  47. package/dist/copy.js +29 -11
  48. package/dist/create-directory.d.ts +20 -0
  49. package/dist/create-directory.d.ts.map +1 -0
  50. package/dist/create-directory.js +130 -0
  51. package/dist/create-file-async.d.ts +7 -0
  52. package/dist/create-file-async.d.ts.map +1 -0
  53. package/dist/create-file-async.js +121 -0
  54. package/dist/create-file.d.ts +8 -0
  55. package/dist/create-file.d.ts.map +1 -0
  56. package/dist/create-file.js +190 -0
  57. package/dist/create-owned-file.d.ts +8 -0
  58. package/dist/create-owned-file.d.ts.map +1 -0
  59. package/dist/create-owned-file.js +16 -0
  60. package/dist/create.d.ts +4 -0
  61. package/dist/create.d.ts.map +1 -0
  62. package/dist/create.js +2 -0
  63. package/dist/creation-darwin.d.ts +7 -0
  64. package/dist/creation-darwin.d.ts.map +1 -0
  65. package/dist/creation-darwin.js +79 -0
  66. package/dist/creation-file-state.d.ts +19 -0
  67. package/dist/creation-file-state.d.ts.map +1 -0
  68. package/dist/creation-file-state.js +118 -0
  69. package/dist/creation-path.d.ts +21 -0
  70. package/dist/creation-path.d.ts.map +1 -0
  71. package/dist/creation-path.js +71 -0
  72. package/dist/creation-permissions.d.ts +19 -0
  73. package/dist/creation-permissions.d.ts.map +1 -0
  74. package/dist/creation-permissions.js +125 -0
  75. package/dist/directory-durability.d.ts +1 -1
  76. package/dist/directory-durability.d.ts.map +1 -1
  77. package/dist/directory-durability.js +22 -80
  78. package/dist/directory-guard.d.ts +3 -0
  79. package/dist/directory-guard.d.ts.map +1 -1
  80. package/dist/directory-mode-node.d.ts +2 -0
  81. package/dist/directory-mode-node.d.ts.map +1 -1
  82. package/dist/directory-mode-node.js +8 -0
  83. package/dist/directory-mode-owner.js +5 -5
  84. package/dist/directory-receipt.d.ts +24 -0
  85. package/dist/directory-receipt.d.ts.map +1 -0
  86. package/dist/directory-receipt.js +127 -0
  87. package/dist/file-cleanup.d.ts +19 -0
  88. package/dist/file-cleanup.d.ts.map +1 -0
  89. package/dist/file-cleanup.js +78 -0
  90. package/dist/file-handle-transfer.d.ts +2 -0
  91. package/dist/file-handle-transfer.d.ts.map +1 -1
  92. package/dist/file-handle-transfer.js +57 -2
  93. package/dist/file-identity.d.ts.map +1 -1
  94. package/dist/file-identity.js +18 -4
  95. package/dist/file-lock-sync-admission.d.ts +19 -0
  96. package/dist/file-lock-sync-admission.d.ts.map +1 -0
  97. package/dist/file-lock-sync-admission.js +93 -0
  98. package/dist/file-lock-sync-root-acquire.d.ts +4 -0
  99. package/dist/file-lock-sync-root-acquire.d.ts.map +1 -0
  100. package/dist/file-lock-sync-root-acquire.js +370 -0
  101. package/dist/file-lock-sync-root-arbitration.d.ts +18 -0
  102. package/dist/file-lock-sync-root-arbitration.d.ts.map +1 -0
  103. package/dist/file-lock-sync-root-arbitration.js +66 -0
  104. package/dist/file-lock-sync-root-held.d.ts +34 -0
  105. package/dist/file-lock-sync-root-held.d.ts.map +1 -0
  106. package/dist/file-lock-sync-root-held.js +393 -0
  107. package/dist/file-lock-sync-root-io.d.ts +44 -0
  108. package/dist/file-lock-sync-root-io.d.ts.map +1 -0
  109. package/dist/file-lock-sync-root-io.js +209 -0
  110. package/dist/file-lock-sync-root-mutation.d.ts +17 -0
  111. package/dist/file-lock-sync-root-mutation.d.ts.map +1 -0
  112. package/dist/file-lock-sync-root-mutation.js +277 -0
  113. package/dist/file-lock-sync-root-options.d.ts +20 -0
  114. package/dist/file-lock-sync-root-options.d.ts.map +1 -0
  115. package/dist/file-lock-sync-root-options.js +58 -0
  116. package/dist/file-lock-sync-root-registration.d.ts +2 -0
  117. package/dist/file-lock-sync-root-registration.d.ts.map +1 -0
  118. package/dist/file-lock-sync-root-registration.js +90 -0
  119. package/dist/file-lock-sync-root.d.ts +36 -0
  120. package/dist/file-lock-sync-root.d.ts.map +1 -0
  121. package/dist/file-lock-sync-root.js +361 -0
  122. package/dist/file-lock-sync-stale-admission.d.ts +24 -0
  123. package/dist/file-lock-sync-stale-admission.d.ts.map +1 -0
  124. package/dist/file-lock-sync-stale-admission.js +205 -0
  125. package/dist/file-lock-sync.d.ts.map +1 -1
  126. package/dist/file-lock-sync.js +245 -205
  127. package/dist/file-observation.d.ts +1 -1
  128. package/dist/file-observation.d.ts.map +1 -1
  129. package/dist/file-store-boundary.d.ts +2 -6
  130. package/dist/file-store-boundary.d.ts.map +1 -1
  131. package/dist/file-store-boundary.js +3 -9
  132. package/dist/file-store-prune.d.ts.map +1 -1
  133. package/dist/file-store-prune.js +5 -1
  134. package/dist/file-store-sync-write.d.ts.map +1 -1
  135. package/dist/file-store-sync-write.js +5 -8
  136. package/dist/file-store.d.ts.map +1 -1
  137. package/dist/file-store.js +47 -12
  138. package/dist/guarded-mkdir.d.ts +1 -0
  139. package/dist/guarded-mkdir.d.ts.map +1 -1
  140. package/dist/guarded-mkdir.js +36 -7
  141. package/dist/json-document-store.d.ts.map +1 -1
  142. package/dist/json-document-store.js +22 -15
  143. package/dist/json-durable-queue-ownership.d.ts +0 -1
  144. package/dist/json-durable-queue-ownership.d.ts.map +1 -1
  145. package/dist/json-durable-queue-ownership.js +0 -6
  146. package/dist/move-path.js +1 -1
  147. package/dist/native-binding.d.ts +13 -1
  148. package/dist/native-binding.d.ts.map +1 -1
  149. package/dist/native-fallback-warning.d.ts +4 -0
  150. package/dist/native-fallback-warning.d.ts.map +1 -0
  151. package/dist/native-fallback-warning.js +11 -0
  152. package/dist/native-operations.d.ts +0 -2
  153. package/dist/native-operations.d.ts.map +1 -1
  154. package/dist/native-operations.js +0 -24
  155. package/dist/native-parent-admission.d.ts +5 -2
  156. package/dist/native-parent-admission.d.ts.map +1 -1
  157. package/dist/native-parent-admission.js +27 -7
  158. package/dist/native-pinned-write-windows.d.ts +1 -1
  159. package/dist/native-pinned-write-windows.d.ts.map +1 -1
  160. package/dist/native-pinned-write-windows.js +174 -29
  161. package/dist/native-pinned-write.d.ts.map +1 -1
  162. package/dist/native-pinned-write.js +26 -14
  163. package/dist/native-policy-parent-windows.d.ts +14 -0
  164. package/dist/native-policy-parent-windows.d.ts.map +1 -0
  165. package/dist/native-policy-parent-windows.js +209 -0
  166. package/dist/native-rename-outcome.d.ts +4 -0
  167. package/dist/native-rename-outcome.d.ts.map +1 -0
  168. package/dist/native-rename-outcome.js +8 -0
  169. package/dist/native-staged-file.d.ts +3 -2
  170. package/dist/native-staged-file.d.ts.map +1 -1
  171. package/dist/native-staged-file.js +121 -72
  172. package/dist/output.d.ts.map +1 -1
  173. package/dist/output.js +12 -8
  174. package/dist/owner-dacl.d.ts.map +1 -1
  175. package/dist/owner-dacl.js +10 -4
  176. package/dist/path-prefix.d.ts.map +1 -1
  177. package/dist/path-prefix.js +30 -8
  178. package/dist/path-suffix-aliases.d.ts +2 -0
  179. package/dist/path-suffix-aliases.d.ts.map +1 -1
  180. package/dist/path-suffix-aliases.js +25 -17
  181. package/dist/permission-exec.d.ts +2 -0
  182. package/dist/permission-exec.d.ts.map +1 -1
  183. package/dist/permission-exec.js +150 -21
  184. package/dist/permissions-windows.js +1 -1
  185. package/dist/pinned-mutation-admission.d.ts.map +1 -1
  186. package/dist/pinned-mutation-admission.js +10 -5
  187. package/dist/pinned-mutation-observation.d.ts +0 -1
  188. package/dist/pinned-mutation-observation.d.ts.map +1 -1
  189. package/dist/pinned-mutation-observation.js +0 -19
  190. package/dist/pinned-mutation-shared-route.d.ts +1 -0
  191. package/dist/pinned-mutation-shared-route.d.ts.map +1 -1
  192. package/dist/pinned-mutation-shared-route.js +1 -1
  193. package/dist/pinned-write-input.d.ts +4 -0
  194. package/dist/pinned-write-input.d.ts.map +1 -0
  195. package/dist/pinned-write-input.js +25 -0
  196. package/dist/pinned-write-mode.d.ts +5 -0
  197. package/dist/pinned-write-mode.d.ts.map +1 -0
  198. package/dist/pinned-write-mode.js +24 -0
  199. package/dist/pinned-write-staged.d.ts +6 -0
  200. package/dist/pinned-write-staged.d.ts.map +1 -0
  201. package/dist/pinned-write-staged.js +187 -0
  202. package/dist/pinned-write-types.d.ts +5 -0
  203. package/dist/pinned-write-types.d.ts.map +1 -1
  204. package/dist/pinned-write.d.ts.map +1 -1
  205. package/dist/pinned-write.js +35 -145
  206. package/dist/private-directory.d.ts.map +1 -1
  207. package/dist/private-directory.js +18 -4
  208. package/dist/private-producer-handoff-sync.d.ts +14 -0
  209. package/dist/private-producer-handoff-sync.d.ts.map +1 -0
  210. package/dist/private-producer-handoff-sync.js +114 -0
  211. package/dist/private-producer-handoff.d.ts +22 -4
  212. package/dist/private-producer-handoff.d.ts.map +1 -1
  213. package/dist/private-producer-handoff.js +140 -77
  214. package/dist/private-temp-workspace.d.ts.map +1 -1
  215. package/dist/private-temp-workspace.js +75 -121
  216. package/dist/publish-copy-stage.d.ts +2 -1
  217. package/dist/publish-copy-stage.d.ts.map +1 -1
  218. package/dist/publish-copy-stage.js +16 -7
  219. package/dist/publish-file.d.ts.map +1 -1
  220. package/dist/publish-file.js +2 -2
  221. package/dist/regular-file.d.ts.map +1 -1
  222. package/dist/regular-file.js +1 -15
  223. package/dist/replace-directory.d.ts.map +1 -1
  224. package/dist/replace-directory.js +256 -18
  225. package/dist/replace-file-copy-fallback.d.ts.map +1 -1
  226. package/dist/replace-file-copy-fallback.js +62 -70
  227. package/dist/replace-file-copy-source.d.ts.map +1 -1
  228. package/dist/replace-file-copy-source.js +10 -12
  229. package/dist/replace-file-temp-owner.d.ts +5 -9
  230. package/dist/replace-file-temp-owner.d.ts.map +1 -1
  231. package/dist/replace-file-temp-owner.js +56 -72
  232. package/dist/replace-file.js +6 -6
  233. package/dist/retained-directory-replacement.d.ts +26 -0
  234. package/dist/retained-directory-replacement.d.ts.map +1 -0
  235. package/dist/retained-directory-replacement.js +193 -0
  236. package/dist/root-boundary.d.ts +1 -0
  237. package/dist/root-boundary.d.ts.map +1 -1
  238. package/dist/root-boundary.js +4 -0
  239. package/dist/root-context.d.ts +0 -8
  240. package/dist/root-context.d.ts.map +1 -1
  241. package/dist/root-context.js +0 -3
  242. package/dist/root-create-input.d.ts +8 -1
  243. package/dist/root-create-input.d.ts.map +1 -1
  244. package/dist/root-create-input.js +17 -4
  245. package/dist/root-directory-creation.d.ts +3 -3
  246. package/dist/root-directory-creation.d.ts.map +1 -1
  247. package/dist/root-directory-creation.js +15 -3
  248. package/dist/root-directory-list.d.ts +1 -0
  249. package/dist/root-directory-list.d.ts.map +1 -1
  250. package/dist/root-directory-list.js +1 -0
  251. package/dist/root-impl.d.ts.map +1 -1
  252. package/dist/root-impl.js +46 -17
  253. package/dist/root-move-noreplace.d.ts.map +1 -1
  254. package/dist/root-move-noreplace.js +24 -15
  255. package/dist/root-options.d.ts +12 -4
  256. package/dist/root-options.d.ts.map +1 -1
  257. package/dist/root-path-errors.d.ts +1 -0
  258. package/dist/root-path-errors.d.ts.map +1 -1
  259. package/dist/root-path-errors.js +11 -2
  260. package/dist/root-path-existing.d.ts.map +1 -1
  261. package/dist/root-path-existing.js +11 -35
  262. package/dist/root-path-stat.d.ts.map +1 -1
  263. package/dist/root-path-stat.js +59 -7
  264. package/dist/root-path.js +1 -13
  265. package/dist/root-remove.d.ts +1 -0
  266. package/dist/root-remove.d.ts.map +1 -1
  267. package/dist/root-remove.js +4 -0
  268. package/dist/root-walk.d.ts +1 -1
  269. package/dist/root-walk.d.ts.map +1 -1
  270. package/dist/root-walk.js +17 -2
  271. package/dist/root-write-admission.d.ts +0 -2
  272. package/dist/root-write-admission.d.ts.map +1 -1
  273. package/dist/root-write-admission.js +1 -15
  274. package/dist/root-write-complete-parent.d.ts.map +1 -1
  275. package/dist/root-write-complete-parent.js +7 -23
  276. package/dist/root-write-publication.js +1 -1
  277. package/dist/root-write-verification.d.ts.map +1 -1
  278. package/dist/root-write-verification.js +29 -42
  279. package/dist/secret-file.d.ts.map +1 -1
  280. package/dist/secret-file.js +3 -24
  281. package/dist/secret-read-async.d.ts.map +1 -1
  282. package/dist/secret-read-async.js +3 -24
  283. package/dist/secret-read-policy.d.ts +6 -2
  284. package/dist/secret-read-policy.d.ts.map +1 -1
  285. package/dist/secret-read-policy.js +26 -2
  286. package/dist/secure-file-windows.d.ts +6 -0
  287. package/dist/secure-file-windows.d.ts.map +1 -1
  288. package/dist/secure-file-windows.js +34 -117
  289. package/dist/secure-file.js +2 -2
  290. package/dist/sibling-temp.d.ts.map +1 -1
  291. package/dist/sibling-temp.js +23 -12
  292. package/dist/sidecar-lock-acquire.d.ts +2 -28
  293. package/dist/sidecar-lock-acquire.d.ts.map +1 -1
  294. package/dist/sidecar-lock-acquire.js +288 -199
  295. package/dist/sidecar-lock-admission-context.d.ts +19 -0
  296. package/dist/sidecar-lock-admission-context.d.ts.map +1 -0
  297. package/dist/sidecar-lock-admission-context.js +60 -0
  298. package/dist/sidecar-lock-admission-parser.d.ts +43 -0
  299. package/dist/sidecar-lock-admission-parser.d.ts.map +1 -0
  300. package/dist/sidecar-lock-admission-parser.js +113 -0
  301. package/dist/sidecar-lock-admission.d.ts +35 -0
  302. package/dist/sidecar-lock-admission.d.ts.map +1 -0
  303. package/dist/sidecar-lock-admission.js +7 -0
  304. package/dist/sidecar-lock-reclaim.d.ts +9 -4
  305. package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
  306. package/dist/sidecar-lock-reclaim.js +80 -25
  307. package/dist/sidecar-lock-root.d.ts.map +1 -1
  308. package/dist/sidecar-lock-root.js +2 -1
  309. package/dist/sidecar-lock-stale-admission.d.ts +39 -0
  310. package/dist/sidecar-lock-stale-admission.d.ts.map +1 -0
  311. package/dist/sidecar-lock-stale-admission.js +232 -0
  312. package/dist/sidecar-lock-target.d.ts +8 -0
  313. package/dist/sidecar-lock-target.d.ts.map +1 -0
  314. package/dist/sidecar-lock-target.js +55 -0
  315. package/dist/sidecar-lock.d.ts.map +1 -1
  316. package/dist/sidecar-lock.js +100 -16
  317. package/dist/staged-directory.d.ts.map +1 -1
  318. package/dist/staged-directory.js +6 -6
  319. package/dist/staged-file-settlement.d.ts +17 -0
  320. package/dist/staged-file-settlement.d.ts.map +1 -0
  321. package/dist/staged-file-settlement.js +57 -0
  322. package/dist/temp-workspace-descriptor.d.ts.map +1 -1
  323. package/dist/temp-workspace-descriptor.js +9 -27
  324. package/dist/temp-workspace-owner.d.ts.map +1 -1
  325. package/dist/temp-workspace-owner.js +8 -8
  326. package/dist/walk.d.ts +5 -1
  327. package/dist/walk.d.ts.map +1 -1
  328. package/dist/walk.js +19 -6
  329. package/dist/windows-owner.d.ts.map +1 -1
  330. package/dist/windows-owner.js +4 -3
  331. package/dist/windows-security-bridge.cs +336 -0
  332. package/dist/windows-security-bridge.ps1 +15 -0
  333. package/dist/windows-security-command.d.ts +26 -0
  334. package/dist/windows-security-command.d.ts.map +1 -0
  335. package/dist/windows-security-command.js +363 -0
  336. package/dist/windows-security-facts.d.ts +6 -0
  337. package/dist/windows-security-facts.d.ts.map +1 -0
  338. package/dist/windows-security-facts.js +108 -0
  339. package/docs/advanced.md +4 -2
  340. package/docs/archive.md +97 -46
  341. package/docs/atomic.md +85 -8
  342. package/docs/config.md +6 -2
  343. package/docs/contributing.md +44 -4
  344. package/docs/copy.md +37 -0
  345. package/docs/creation.md +128 -0
  346. package/docs/durability.md +24 -0
  347. package/docs/file-store.md +19 -0
  348. package/docs/install.md +31 -7
  349. package/docs/json-store.md +5 -0
  350. package/docs/migrating-to-0.5.md +15 -6
  351. package/docs/migrating-to-0.6.md +9 -4
  352. package/docs/native-helper.md +32 -12
  353. package/docs/native.md +38 -7
  354. package/docs/output.md +6 -0
  355. package/docs/path-prefix.md +10 -0
  356. package/docs/path-suffix-aliases.md +51 -6
  357. package/docs/permissions.md +50 -14
  358. package/docs/public-api.md +3 -2
  359. package/docs/root.md +56 -3
  360. package/docs/secret-file.md +11 -2
  361. package/docs/secure-file.md +9 -4
  362. package/docs/sidecar-lock.md +114 -8
  363. package/docs/staged-file.md +12 -3
  364. package/docs/temp.md +20 -3
  365. package/docs/walk.md +67 -1
  366. package/docs/writing.md +76 -6
  367. package/package.json +19 -16
  368. package/dist/darwin-acl.d.ts +0 -4
  369. package/dist/darwin-acl.d.ts.map +0 -1
  370. package/dist/darwin-acl.js +0 -24
@@ -1,57 +1,133 @@
1
1
  import { ArchiveFormatError, ArchiveSecurityError } from "./archive-errors.js";
2
2
  import { validateArchiveEntryPath } from "./archive-entry.js";
3
- import { isZipSymlinkEntry } from "./archive-zip-entry.js";
3
+ import { registerAdmittedZipEntry } from "./archive-zip-entry.js";
4
4
  import { zipPathKey } from "./archive-zip-names.js";
5
+ function disagreement() {
6
+ throw new ArchiveFormatError("ZIP decoder disagrees with admitted directory metadata");
7
+ }
8
+ function collision() {
9
+ throw new ArchiveSecurityError("entry-path", "zip archive contains duplicate or colliding entry names");
10
+ }
11
+ function matchesData(value, physical) {
12
+ if (!value || typeof value !== "object")
13
+ return false;
14
+ const data = value;
15
+ return Number.isInteger(data.crc32) && data.crc32 >= -0x80000000 && data.crc32 <= 0xffffffff &&
16
+ (data.crc32 >>> 0) === physical.crc32 && data.uncompressedSize === physical.size &&
17
+ data.compressedSize === physical.compressedSize && typeof data.getContentWorker === "function" &&
18
+ data.compression?.magic === String.fromCharCode(physical.compressionMethod & 255, physical.compressionMethod >>> 8);
19
+ }
5
20
  /** Internal: the caller has admitted these unchanged bytes and their metadata. */
6
21
  export async function loadAdmittedZipArchive(buffer, admitted) {
22
+ const physicalByPath = new Map();
23
+ for (const entry of admitted) {
24
+ if (physicalByPath.has(entry.portableKey))
25
+ collision();
26
+ physicalByPath.set(entry.portableKey, entry);
27
+ }
7
28
  const JSZip = await importOptionalJsZip();
8
- let archive;
29
+ const archive = new JSZip();
30
+ const originalFile = archive.file;
31
+ const originalDescriptor = Object.getOwnPropertyDescriptor(archive, "file");
32
+ const inserted = new Map();
33
+ const remaining = new Map(physicalByPath);
34
+ // JSZip's fileAdd can misclassify UNIX type bits as directories, rename an
35
+ // entry, and erase its payload. Intercept only this private instance's load.
36
+ Object.defineProperty(archive, "file", { configurable: true, writable: true,
37
+ value: function (...args) {
38
+ if (args.length === 1)
39
+ return originalFile.apply(this, args);
40
+ const [name, data, options] = args;
41
+ if (this !== archive || args.length !== 3 || typeof name !== "string" || !options || typeof options !== "object")
42
+ disagreement();
43
+ validateArchiveEntryPath(name);
44
+ const key = zipPathKey(name);
45
+ const physical = remaining.get(key);
46
+ if (!physical) {
47
+ if (physicalByPath.has(key))
48
+ collision();
49
+ disagreement();
50
+ }
51
+ const fields = options;
52
+ const unixMode = physical.creatorSystem === 3 ? physical.externalAttributes >>> 16 : null;
53
+ const dosMode = physical.creatorSystem === 0 ? physical.externalAttributes & 0x3f : null;
54
+ const decoderDirectory = Boolean(physical.externalAttributes & 0x10) || physical.portableDirectory;
55
+ if (physical.index !== inserted.size || fields.unixPermissions !== unixMode || fields.dosPermissions !== dosMode ||
56
+ fields.dir !== decoderDirectory || !matchesData(data, physical))
57
+ disagreement();
58
+ const result = originalFile.call(this, name, data, {
59
+ ...fields, dir: false, unixPermissions: null, dosPermissions: null, createFolders: false,
60
+ });
61
+ const entry = archive.files[name];
62
+ if (!entry || typeof entry !== "object" || inserted.has(entry))
63
+ disagreement();
64
+ // fileAdd also substitutes an empty promise for declared-zero contents.
65
+ // Retain the original decoder object so size/CRC checks still read bytes.
66
+ entry._data = data;
67
+ inserted.set(entry, { physical, data });
68
+ remaining.delete(key);
69
+ return result;
70
+ },
71
+ });
9
72
  try {
10
- archive = await JSZip.loadAsync(buffer);
73
+ const loaded = await archive.loadAsync(buffer, { createFolders: false });
74
+ if (loaded !== archive)
75
+ disagreement();
11
76
  }
12
77
  catch (error) {
78
+ if (error instanceof ArchiveFormatError || error instanceof ArchiveSecurityError)
79
+ throw error;
13
80
  throw new ArchiveFormatError(`invalid ZIP archive: ${error instanceof Error ? error.message : String(error)}`, { cause: error instanceof Error ? error : undefined });
14
81
  }
15
- const names = Object.keys(archive.files);
16
- if (names.length !== admitted.length) {
17
- throw new ArchiveSecurityError("entry-path", "zip archive contains duplicate or colliding entry names");
18
- }
19
- // Object key order is not central-directory order (numeric names reorder).
20
- // Use the admitted portable interpretation, including legacy UTF-8 decoding.
21
- const physicalByPath = new Map();
22
- for (const entry of admitted) {
23
- physicalByPath.set(entry.portableKey, entry);
82
+ finally {
83
+ if (originalDescriptor)
84
+ Object.defineProperty(archive, "file", originalDescriptor);
85
+ else
86
+ Reflect.deleteProperty(archive, "file");
24
87
  }
88
+ const names = Object.keys(archive.files);
89
+ if (names.length !== admitted.length)
90
+ collision();
91
+ const normalized = [];
25
92
  for (const name of names) {
26
93
  validateArchiveEntryPath(name);
27
- const key = zipPathKey(name);
28
- const physical = physicalByPath.get(key);
29
94
  const entry = archive.files[name];
30
- const entryName = entry.name;
31
- let entryKey = key;
32
- if (entryName !== name) {
33
- validateArchiveEntryPath(entryName);
34
- entryKey = zipPathKey(entryName);
35
- }
36
- const kind = isZipSymlinkEntry(entry) ? "symlink" : entry.dir ? "directory" : "file";
37
- if (!physical || entryKey !== key || kind !== physical.kind) {
38
- throw new ArchiveFormatError("ZIP decoder disagrees with admitted directory metadata");
39
- }
40
- physicalByPath.delete(key);
95
+ if (!entry || typeof entry.name !== "string")
96
+ disagreement();
97
+ validateArchiveEntryPath(entry.name);
98
+ const insertion = inserted.get(entry);
99
+ if (!insertion || zipPathKey(name) !== insertion.physical.portableKey || zipPathKey(entry.name) !== insertion.physical.portableKey ||
100
+ entry.dir !== false || entry.unixPermissions !== null || entry.dosPermissions !== null ||
101
+ entry._data !== insertion.data || !matchesData(entry._data, insertion.physical))
102
+ disagreement();
103
+ normalized[insertion.physical.index] = [name, entry, insertion.physical];
104
+ inserted.delete(entry);
41
105
  }
42
- if (physicalByPath.size) {
43
- throw new ArchiveFormatError("ZIP decoder disagrees with admitted directory metadata");
106
+ if (remaining.size || inserted.size)
107
+ disagreement();
108
+ const files = Object.create(null);
109
+ for (const [name, entry, physical] of normalized) {
110
+ const appendSlash = physical.kind === "directory" && !name.endsWith("/");
111
+ const normalizedName = appendSlash ? `${name}/` : name;
112
+ if (Object.hasOwn(files, normalizedName))
113
+ collision();
114
+ if (appendSlash) {
115
+ entry.name = `${entry.name}/`;
116
+ }
117
+ files[normalizedName] = entry;
118
+ registerAdmittedZipEntry(entry, physical);
44
119
  }
120
+ archive.files = files;
45
121
  return archive;
46
122
  }
47
123
  async function importOptionalJsZip() {
48
124
  try {
49
125
  const module = await import("jszip");
50
126
  const candidate = typeof module === "function" ? module : module.default;
51
- if ((typeof candidate !== "object" && typeof candidate !== "function") ||
52
- candidate === null ||
53
- typeof candidate.loadAsync !== "function") {
54
- throw new Error('Optional archive dependency "jszip" does not expose loadAsync().');
127
+ if (typeof candidate !== "function" ||
128
+ typeof candidate.prototype?.file !== "function" ||
129
+ typeof candidate.prototype?.loadAsync !== "function") {
130
+ throw new Error('Optional archive dependency "jszip" does not expose its constructor, file(), and loadAsync().');
55
131
  }
56
132
  return candidate;
57
133
  }
@@ -12,6 +12,7 @@ export declare function admitZipNames(params: {
12
12
  }): {
13
13
  path?: string;
14
14
  portableKey: string;
15
+ portableDirectory: boolean;
15
16
  directory: boolean;
16
17
  };
17
18
  //# sourceMappingURL=archive-zip-names.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"archive-zip-names.d.ts","sourceRoot":"","sources":["../src/archive-zip-names.ts"],"names":[],"mappings":"AAKA,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,KAAK,CAEhD;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAK/D;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAcjE;AAUD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAG/C;AAoBD,wBAAgB,aAAa,CAAC,MAAM,EAAE;IACpC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAC9C,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnE,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACnB,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAiE7D"}
1
+ {"version":3,"file":"archive-zip-names.d.ts","sourceRoot":"","sources":["../src/archive-zip-names.ts"],"names":[],"mappings":"AAKA,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,KAAK,CAEhD;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAK/D;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAcjE;AAUD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAG/C;AAoBD,wBAAgB,aAAa,CAAC,MAAM,EAAE;IACpC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAC9C,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnE,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACnB,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,iBAAiB,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAuEzF"}
@@ -130,9 +130,15 @@ export function admitZipNames(params) {
130
130
  // Unflagged legacy names can decode differently and need their own key.
131
131
  const portableKey = portablePath === centralRaw ? centralKey
132
132
  : interpretations.includes(portablePath) ? interpretationKey : zipPathKey(portablePath);
133
+ // JSZip prioritizes flagged local UTF-8 and checks a central Unicode field
134
+ // against local bytes. Canonically equal names can end in different separators.
135
+ const decoderPath = flags & 0x800 ? localUtf8
136
+ : centralUnicode !== undefined && (sameName || centralField.readUInt32LE(1) === updateCrc32(0, local))
137
+ ? centralUnicode : local.toString("utf8");
133
138
  return {
134
139
  path,
135
140
  portableKey,
141
+ portableDirectory: decoderPath.endsWith("/"),
136
142
  directory,
137
143
  };
138
144
  }
@@ -1 +1 @@
1
- {"version":3,"file":"archive.d.ts","sourceRoot":"","sources":["../src/archive.ts"],"names":[],"mappings":"AA4DA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAIlE,YAAY,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AACjF,YAAY,EACV,kBAAkB,EAClB,gBAAgB,EAChB,sBAAsB,EACtB,0BAA0B,GAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,kBAAkB,EAClB,yBAAyB,EACzB,wBAAwB,EACxB,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC/F,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,KAAK,wBAAwB,EAAE,KAAK,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AACpH,OAAO,EACL,wBAAwB,EACxB,iBAAiB,EACjB,6BAA6B,EAC7B,mBAAmB,EACnB,2BAA2B,EAC3B,uBAAuB,EACvB,4BAA4B,EAC5B,iCAAiC,EACjC,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,GAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,kBAAkB,EAAE,KAAK,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AACtF,OAAO,EAAE,oBAAoB,EAAE,KAAK,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAC3F,OAAO,EACL,kCAAkC,EAClC,iCAAiC,EACjC,4BAA4B,EAC5B,wBAAwB,EACxB,4BAA4B,GAC7B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,8BAA8B,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrF,OAAO,EACL,2BAA2B,EAC3B,iCAAiC,EACjC,KAAK,mBAAmB,GACzB,MAAM,4BAA4B,CAAC;AAiNpC,wBAAsB,cAAc,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAiDjF"}
1
+ {"version":3,"file":"archive.d.ts","sourceRoot":"","sources":["../src/archive.ts"],"names":[],"mappings":"AA4DA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAIlE,YAAY,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AACjF,YAAY,EACV,kBAAkB,EAClB,gBAAgB,EAChB,sBAAsB,EACtB,0BAA0B,GAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,kBAAkB,EAClB,yBAAyB,EACzB,wBAAwB,EACxB,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC/F,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,KAAK,wBAAwB,EAAE,KAAK,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AACpH,OAAO,EACL,wBAAwB,EACxB,iBAAiB,EACjB,6BAA6B,EAC7B,mBAAmB,EACnB,2BAA2B,EAC3B,uBAAuB,EACvB,4BAA4B,EAC5B,iCAAiC,EACjC,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,GAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,kBAAkB,EAAE,KAAK,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AACtF,OAAO,EAAE,oBAAoB,EAAE,KAAK,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAC3F,OAAO,EACL,kCAAkC,EAClC,iCAAiC,EACjC,4BAA4B,EAC5B,wBAAwB,EACxB,4BAA4B,GAC7B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,8BAA8B,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrF,OAAO,EACL,2BAA2B,EAC3B,iCAAiC,EACjC,KAAK,mBAAmB,GACzB,MAAM,4BAA4B,CAAC;AAiNpC,wBAAsB,cAAc,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAgDjF"}
package/dist/archive.js CHANGED
@@ -9,11 +9,11 @@ import { pipeline } from "node:stream/promises";
9
9
  import { createArchiveOutputPathTracker, resolveArchiveOutputPath, stripArchivePath, validateArchiveEntryPath, } from "./archive-entry.js";
10
10
  import { createPipelineTimeoutError, waitForDeadline, withExtractionDeadline, } from "./archive-deadline.js";
11
11
  import { assertArchiveEntryCountWithinLimit, assertArchiveEntryPathComponentsWithinLimit, createByteBudgetTracker, createExtractBudgetTransform, resolveExtractLimits, resolveTarMeterLimits, } from "./archive-limits.js";
12
- import { assertPortableArchiveKind, resolveArchiveKind } from "./archive-kind.js";
12
+ import { resolveArchiveKind } from "./archive-kind.js";
13
13
  import { prepareArchiveDestinationGuard, preparePrivateArchiveOutputPath, withStagedArchiveDestination, } from "./archive-staging.js";
14
14
  import { mergePlannedArchiveIntoDestination } from "./archive-merge.js";
15
15
  import { loadZipArchiveWithPreflight } from "./archive-zip-preflight.js";
16
- import { isZipSymlinkEntry, zipEntryDeclaredSize, zipEntryMode, } from "./archive-zip-entry.js";
16
+ import { zipEntryKind, zipEntryDeclaredSize, zipEntryMode, } from "./archive-zip-entry.js";
17
17
  import { createZipIntegrityTransform, normalizeZipIntegrityError, } from "./archive-zip-integrity.js";
18
18
  import { FsSafeError } from "./errors.js";
19
19
  import { ArchiveSecurityError } from "./archive-errors.js";
@@ -95,7 +95,7 @@ async function writeZipFileEntry(params) {
95
95
  }
96
96
  params.deadline.check();
97
97
  if (!handleClosedByStream) {
98
- await tempHandle.close().catch(() => undefined);
98
+ await tempHandle.close();
99
99
  handleClosedByStream = true;
100
100
  }
101
101
  tempHandle = null;
@@ -153,8 +153,7 @@ async function extractZip(params) {
153
153
  }
154
154
  assertArchiveEntryPathComponentsWithinLimit(output.relPath, limits);
155
155
  trackOutputPath(output.relPath, entry.name);
156
- const isSymlink = isZipSymlinkEntry(entry);
157
- const entryKind = isSymlink ? "symlink" : entry.dir ? "directory" : "file";
156
+ const entryKind = zipEntryKind(entry);
158
157
  const entrySize = zipEntryDeclaredSize(entry);
159
158
  if (!shouldExtractArchiveEntry({
160
159
  filter: params.entryFilter,
@@ -163,9 +162,11 @@ async function extractZip(params) {
163
162
  })) {
164
163
  continue;
165
164
  }
166
- if (isSymlink) {
165
+ if (entryKind === "symlink") {
167
166
  throw new ArchiveSecurityError("entry-link", `zip entry is a link: ${entry.name}`);
168
167
  }
168
+ if (entryKind === "other")
169
+ continue;
169
170
  const mode = zipEntryMode(entry, params.entryModes);
170
171
  acceptedEntries.push({ path: output.relPath, kind: entry.dir ? "directory" : "file", mode });
171
172
  await preparePrivateArchiveOutputPath({
@@ -229,8 +230,7 @@ export async function extractArchive(params) {
229
230
  await withExtractionDeadline(params.timeoutMs, label, async (deadline) => extractNativeArchive({ ...options, binding: native, kind, tarLimits, deadline }));
230
231
  return;
231
232
  }
232
- assertPortableArchiveKind(kind);
233
- if (kind === "tar") {
233
+ if (kind !== "zip") {
234
234
  await withExtractionDeadline(params.timeoutMs, label, async (deadline) => {
235
235
  const stagedArchive = await stageArchiveFileForExtraction({
236
236
  archivePath: options.archivePath,
@@ -238,7 +238,7 @@ export async function extractArchive(params) {
238
238
  deadline,
239
239
  });
240
240
  try {
241
- await extractWasmTar({ archivePath: stagedArchive.path, options, limits, tarLimits, deadline });
241
+ await extractWasmTar({ archivePath: stagedArchive.path, kind, options, limits, tarLimits, deadline });
242
242
  }
243
243
  finally {
244
244
  await stagedArchive.cleanup();
@@ -251,7 +251,7 @@ export async function extractArchive(params) {
251
251
  async function extractWasmTar(params) {
252
252
  const { options, deadline, tarLimits } = params;
253
253
  const manifest = [];
254
- await inspectTar({ archivePath: params.archivePath, limits: tarLimits, signal: deadline.signal,
254
+ await inspectTar({ archivePath: params.archivePath, kind: params.kind, limits: tarLimits, signal: deadline.signal,
255
255
  onMember: (entry) => { manifest.push(entry); } });
256
256
  deadline.check();
257
257
  const destinationGuard = await prepareArchiveDestinationGuard(options.destDir);
@@ -264,7 +264,7 @@ async function extractWasmTar(params) {
264
264
  const planned = planEntry(entry);
265
265
  return planned ? [{ ...entry, ...planned }] : [];
266
266
  });
267
- await replayTar({ archivePath: params.archivePath, limits: tarLimits, signal: deadline.signal, members: accepted,
267
+ await replayTar({ archivePath: params.archivePath, kind: params.kind, limits: tarLimits, signal: deadline.signal, members: accepted,
268
268
  async consume(member, payload) {
269
269
  deadline.check();
270
270
  await preparePrivateArchiveOutputPath({ destinationDir: stagingDir, destinationRealDir: stagingDir,
@@ -1,4 +1,3 @@
1
1
  import { Transform } from "node:stream";
2
- export declare function createMaxBytesTransform(maxBytes: number): Transform;
3
2
  export declare function createByteLimitTransform(maxBytes: number, overflowError: (bytes: number) => Error): Transform;
4
3
  //# sourceMappingURL=bounded-read-stream.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"bounded-read-stream.d.ts","sourceRoot":"","sources":["../src/bounded-read-stream.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAIxC,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAMnE;AAED,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,KAAK,GACtC,SAAS,CAaX"}
1
+ {"version":3,"file":"bounded-read-stream.d.ts","sourceRoot":"","sources":["../src/bounded-read-stream.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,KAAK,GACtC,SAAS,CAaX"}
@@ -1,10 +1,4 @@
1
1
  import { Transform } from "node:stream";
2
- import { normalizeMaxBytes } from "./byte-budget.js";
3
- import { FsSafeError } from "./errors.js";
4
- export function createMaxBytesTransform(maxBytes) {
5
- normalizeMaxBytes(maxBytes);
6
- return createByteLimitTransform(maxBytes, (bytes) => new FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes (got at least ${bytes})`));
7
- }
8
2
  export function createByteLimitTransform(maxBytes, overflowError) {
9
3
  let bytes = 0;
10
4
  return new Transform({
@@ -13,6 +13,7 @@ export type CloneFileMetadata = {
13
13
  cloneId: bigint;
14
14
  };
15
15
  /** Batched APFS metadata snapshots; absent on unsupported files or filesystems.
16
+ * On macOS, these snapshots require the native helper.
16
17
  * These are point-in-time facts, not authorization or a guarantee against later edits.
17
18
  */
18
19
  export declare function readCloneFileMetadata(files: readonly string[]): Promise<(CloneFileMetadata | undefined)[]>;
@@ -1 +1 @@
1
- {"version":3,"file":"clone-metadata.d.ts","sourceRoot":"","sources":["../src/clone-metadata.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,iBAAiB,GAAG;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;GAEG;AACH,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,SAAS,MAAM,EAAE,GACvB,OAAO,CAAC,CAAC,iBAAiB,GAAG,SAAS,CAAC,EAAE,CAAC,CA4B5C"}
1
+ {"version":3,"file":"clone-metadata.d.ts","sourceRoot":"","sources":["../src/clone-metadata.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,iBAAiB,GAAG;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;;GAGG;AACH,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,SAAS,MAAM,EAAE,GACvB,OAAO,CAAC,CAAC,iBAAiB,GAAG,SAAS,CAAC,EAAE,CAAC,CA8B5C"}
@@ -1,11 +1,15 @@
1
1
  import { assertAbsolutePathInput } from "./absolute-path.js";
2
- import { requireNativeBinding } from "./native.js";
2
+ import { getNativeBinding, requireNativeBinding } from "./native.js";
3
3
  /** Batched APFS metadata snapshots; absent on unsupported files or filesystems.
4
+ * On macOS, these snapshots require the native helper.
4
5
  * These are point-in-time facts, not authorization or a guarantee against later edits.
5
6
  */
6
7
  export async function readCloneFileMetadata(files) {
7
8
  const paths = files.map(assertAbsolutePathInput);
8
- const results = await requireNativeBinding().readCloneFileMetadata(paths);
9
+ const native = process.platform === "darwin" ? requireNativeBinding() : getNativeBinding();
10
+ if (!native)
11
+ return paths.map(() => undefined);
12
+ const results = await native.readCloneFileMetadata(paths);
9
13
  return results.map((result) => {
10
14
  if (!result ||
11
15
  result.length !== 100 ||
@@ -1,3 +1,4 @@
1
+ import type { BigIntStats } from "node:fs";
1
2
  import type { PinnedWriteParams, PublishedWriteIdentity } from "./pinned-write.js";
2
3
  export type RootCopyPublicationReceipt = Readonly<{
3
4
  path: string;
@@ -9,7 +10,12 @@ export declare function createCopyPublicationObserver(path: string, notify?: (re
9
10
  rethrowObserverFailure(error: unknown): void;
10
11
  };
11
12
  export declare const onCopyPublication: unique symbol;
13
+ export declare const onCopySourceAdmission: unique symbol;
12
14
  export type CopyPublicationOptions = {
13
15
  [onCopyPublication]?: PinnedWriteParams["verifyPublished"];
16
+ [onCopySourceAdmission]?: (identity: BigIntStats, realPath: string) => {
17
+ mode: number;
18
+ verify(): void;
19
+ };
14
20
  };
15
21
  //# sourceMappingURL=copy-publication.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"copy-publication.d.ts","sourceRoot":"","sources":["../src/copy-publication.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAGnF,MAAM,MAAM,0BAA0B,GAAG,QAAQ,CAAC;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb,CAAC,CAAC;AAEH,wBAAgB,6BAA6B,CAC3C,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,0BAA0B,KAAK,IAAI;IAIpD,WAAW,WAAW,sBAAsB,GAAG,IAAI;IAcnD,sBAAsB,QAAQ,OAAO,GAAG,IAAI;EAO/C;AAID,eAAO,MAAM,iBAAiB,eAA8B,CAAC;AAC7D,MAAM,MAAM,sBAAsB,GAAG;IACnC,CAAC,iBAAiB,CAAC,CAAC,EAAE,iBAAiB,CAAC,iBAAiB,CAAC,CAAC;CAC5D,CAAC"}
1
+ {"version":3,"file":"copy-publication.d.ts","sourceRoot":"","sources":["../src/copy-publication.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAC3C,OAAO,KAAK,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAGnF,MAAM,MAAM,0BAA0B,GAAG,QAAQ,CAAC;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb,CAAC,CAAC;AAEH,wBAAgB,6BAA6B,CAC3C,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,0BAA0B,KAAK,IAAI;IAIpD,WAAW,WAAW,sBAAsB,GAAG,IAAI;IAcnD,sBAAsB,QAAQ,OAAO,GAAG,IAAI;EAO/C;AAID,eAAO,MAAM,iBAAiB,eAA8B,CAAC;AAG7D,eAAO,MAAM,qBAAqB,eAAkC,CAAC;AACrE,MAAM,MAAM,sBAAsB,GAAG;IACnC,CAAC,iBAAiB,CAAC,CAAC,EAAE,iBAAiB,CAAC,iBAAiB,CAAC,CAAC;IAC3D,CAAC,qBAAqB,CAAC,CAAC,EAAE,CAAC,QAAQ,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,KAAK;QACrE,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,IAAI,IAAI,CAAC;KAChB,CAAC;CACH,CAAC"}
@@ -28,3 +28,6 @@ export function createCopyPublicationObserver(path, notify) {
28
28
  // Internal composition hook; the descriptor is borrowed until the callback returns.
29
29
  // Kept off RootCopyOptions and all public package exports.
30
30
  export const onCopyPublication = Symbol("onCopyPublication");
31
+ // Reuse the copy's admitted descriptor identity before any bytes are staged.
32
+ // Internal callers can bind prior observations and select its publication mode.
33
+ export const onCopySourceAdmission = Symbol("onCopySourceAdmission");
@@ -1 +1 @@
1
- {"version":3,"file":"copy-tree-portable.d.ts","sourceRoot":"","sources":["../src/copy-tree-portable.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAGL,mBAAmB,EACpB,MAAM,uBAAuB,CAAC;AAQ/B,0FAA0F;AAC1F,wBAAsB,aAAa,CACjC,MAAM,EAAE,UAAU,CAAC,OAAO,mBAAmB,CAAC,EAC9C,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE;IACP,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,aAAa,CAAC,kBAAkB,CAAC,CAAC;CACtD,GACA,OAAO,CAAC,IAAI,CAAC,CAiNf"}
1
+ {"version":3,"file":"copy-tree-portable.d.ts","sourceRoot":"","sources":["../src/copy-tree-portable.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAGL,mBAAmB,EACpB,MAAM,uBAAuB,CAAC;AAU/B,0FAA0F;AAC1F,wBAAsB,aAAa,CACjC,MAAM,EAAE,UAAU,CAAC,OAAO,mBAAmB,CAAC,EAC9C,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE;IACP,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,aAAa,CAAC,kBAAkB,CAAC,CAAC;CACtD,GACA,OAAO,CAAC,IAAI,CAAC,CAoOf"}
@@ -15,16 +15,21 @@ export async function copyOwnedTree(source, destination, options) {
15
15
  const cancellation = new AbortController();
16
16
  const signal = cancellation.signal;
17
17
  setMaxListeners(options.concurrency, signal);
18
- const abort = () => cancellation.abort(options.signal?.reason);
19
- options.signal?.addEventListener("abort", abort, { once: true });
20
18
  const pending = new Set();
21
19
  const finishing = new Set();
22
20
  const buffers = [];
21
+ let failure = undefined;
22
+ function recordFailure(value) {
23
+ failure ??= { value };
24
+ cancellation.abort(failure);
25
+ }
26
+ const abort = () => recordFailure(options.signal?.reason);
27
+ options.signal?.addEventListener("abort", abort, { once: true });
23
28
  async function schedule(operation, children) {
24
29
  signal.throwIfAborted();
25
30
  const task = operation()
26
31
  .catch((error) => {
27
- cancellation.abort(error);
32
+ recordFailure(error);
28
33
  })
29
34
  .finally(() => {
30
35
  pending.delete(task);
@@ -65,13 +70,13 @@ export async function copyOwnedTree(source, destination, options) {
65
70
  try {
66
71
  let position = 0;
67
72
  while (true) {
68
- signal?.throwIfAborted();
73
+ signal.throwIfAborted();
69
74
  const { bytesRead } = await input.read(buffer, 0, buffer.length, position);
70
75
  if (bytesRead === 0)
71
76
  break;
72
77
  let written = 0;
73
78
  while (written < bytesRead) {
74
- signal?.throwIfAborted();
79
+ signal.throwIfAborted();
75
80
  const { bytesWritten } = await output.write(buffer, written, bytesRead - written, position + written);
76
81
  if (bytesWritten === 0)
77
82
  throw new Error("copy write made no progress");
@@ -90,20 +95,25 @@ export async function copyOwnedTree(source, destination, options) {
90
95
  await output.utimes(timestampSeconds(stat.atimeNs), timestampSeconds(stat.mtimeNs));
91
96
  }
92
97
  catch (error) {
93
- cancellation.abort(error);
94
- throw error;
98
+ recordFailure(error);
95
99
  }
96
- finally {
100
+ if (output) {
97
101
  try {
98
- await output?.close();
102
+ await output.close();
99
103
  }
100
- finally {
101
- await input.close();
104
+ catch (error) {
105
+ recordFailure(error);
102
106
  }
103
107
  }
108
+ try {
109
+ await input.close();
110
+ }
111
+ catch (error) {
112
+ recordFailure(error);
113
+ }
104
114
  }
105
115
  async function copyDirectory(from, to, parentChildren) {
106
- signal?.throwIfAborted();
116
+ signal.throwIfAborted();
107
117
  const stat = await fsp.lstat(from, { bigint: true });
108
118
  // Exclusive admission prevents merging a pre-existing directory, including
109
119
  // any destination left by an unsuccessful native clone.
@@ -117,7 +127,7 @@ export async function copyOwnedTree(source, destination, options) {
117
127
  throw new FsSafeError("path-mismatch", "copy source directory changed while opening");
118
128
  }
119
129
  for (const entry of await fsp.readdir(from, { withFileTypes: true })) {
120
- signal?.throwIfAborted();
130
+ signal.throwIfAborted();
121
131
  assertStagedDirectoryCurrent(target.receipt);
122
132
  assertStagedDirectoryCurrent(original.receipt);
123
133
  const childSource = path.join(from, entry.name);
@@ -151,7 +161,7 @@ export async function copyOwnedTree(source, destination, options) {
151
161
  throw error;
152
162
  }
153
163
  }
154
- signal?.throwIfAborted();
164
+ signal.throwIfAborted();
155
165
  await fsp.symlink(link, childTarget, type);
156
166
  }
157
167
  else {
@@ -160,7 +170,7 @@ export async function copyOwnedTree(source, destination, options) {
160
170
  }
161
171
  }
162
172
  catch (error) {
163
- cancellation.abort(error);
173
+ recordFailure(error);
164
174
  throw error;
165
175
  }
166
176
  finally {
@@ -179,18 +189,26 @@ export async function copyOwnedTree(source, destination, options) {
179
189
  assertStagedDirectoryCurrent(target.receipt);
180
190
  }
181
191
  }
182
- finally {
192
+ catch (error) {
193
+ recordFailure(error);
194
+ }
195
+ if (original) {
183
196
  try {
184
- if (original)
185
- fs.closeSync(original.fd);
197
+ fs.closeSync(original.fd);
186
198
  }
187
- finally {
188
- fs.closeSync(target.fd);
199
+ catch (error) {
200
+ recordFailure(error);
189
201
  }
190
202
  }
203
+ try {
204
+ fs.closeSync(target.fd);
205
+ }
206
+ catch (error) {
207
+ recordFailure(error);
208
+ }
191
209
  })()
192
210
  .catch((error) => {
193
- cancellation.abort(error);
211
+ recordFailure(error);
194
212
  })
195
213
  .finally(() => {
196
214
  finishing.delete(completion);
@@ -211,12 +229,14 @@ export async function copyOwnedTree(source, destination, options) {
211
229
  signal.throwIfAborted();
212
230
  }
213
231
  catch (error) {
214
- await Promise.all(finishing);
215
- options.signal?.throwIfAborted();
216
- throw error;
232
+ recordFailure(error);
217
233
  }
218
234
  finally {
219
235
  await Promise.all(finishing);
220
236
  options.signal?.removeEventListener("abort", abort);
221
237
  }
238
+ if (failure)
239
+ throw failure.value;
240
+ options.signal?.throwIfAborted();
241
+ signal.throwIfAborted();
222
242
  }
@@ -1 +1 @@
1
- {"version":3,"file":"copy.d.ts","sourceRoot":"","sources":["../src/copy.ts"],"names":[],"mappings":"AAGA,OAAO,EAAwB,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAK5E,OAAO,EAAE,qBAAqB,EAAE,KAAK,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACpF,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtD,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,CAAC;AACzE,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,uEAAuE;AACvE,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAS/E;AAED,0FAA0F;AAC1F,wBAAsB,iBAAiB,CACrC,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,IAAI,CAAC,eAAe,EAAE,QAAQ,CAAM,GAC5C,OAAO,CAAC,IAAI,CAAC,CAEf;AAED,mGAAmG;AACnG,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAIf"}
1
+ {"version":3,"file":"copy.d.ts","sourceRoot":"","sources":["../src/copy.ts"],"names":[],"mappings":"AAGA,OAAO,EAAwB,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAK5E,OAAO,EAAE,qBAAqB,EAAE,KAAK,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACpF,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtD,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,CAAC;AACzE,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAiBF,uEAAuE;AACvE,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAc/E;AAED,0FAA0F;AAC1F,wBAAsB,iBAAiB,CACrC,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,IAAI,CAAC,eAAe,EAAE,QAAQ,CAAM,GAC5C,OAAO,CAAC,IAAI,CAAC,CAEf;AAED,mGAAmG;AACnG,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAIf"}
package/dist/copy.js CHANGED
@@ -7,18 +7,36 @@ import { FsSafeError } from "./errors.js";
7
7
  import { getNativeBinding, requireNativeBinding } from "./native.js";
8
8
  import { assertStagedDirectoryCurrent, openStagedDirectory } from "./staged-directory.js";
9
9
  export { readCloneFileMetadata } from "./clone-metadata.js";
10
+ function firstFailure(current, value) {
11
+ return current ?? { value };
12
+ }
13
+ function closeDescriptor(fd, failure) {
14
+ try {
15
+ fs.closeSync(fd);
16
+ return failure;
17
+ }
18
+ catch (error) {
19
+ return firstFailure(failure, error);
20
+ }
21
+ }
10
22
  /** Inspect an existing real directory without creating probe files. */
11
23
  export function probeTreeClone(parentPath) {
12
24
  const native = getNativeBinding();
13
25
  if (!native)
14
26
  return undefined;
15
27
  const parent = openStagedDirectory(assertAbsolutePathInput(parentPath));
28
+ let backend;
29
+ let failure;
16
30
  try {
17
- return native.probeTreeClone(parent.fd) ?? undefined;
31
+ backend = native.probeTreeClone(parent.fd) ?? undefined;
18
32
  }
19
- finally {
20
- fs.closeSync(parent.fd);
33
+ catch (error) {
34
+ failure = firstFailure(failure, error);
21
35
  }
36
+ failure = closeDescriptor(parent.fd, failure);
37
+ if (failure)
38
+ throw failure.value;
39
+ return backend;
22
40
  }
23
41
  /** Create an empty clone source: a Btrfs subvolume or an ordinary supported directory. */
24
42
  export async function createCloneSource(destination, options = {}) {
@@ -56,6 +74,7 @@ async function materializeTree(source, destination, options, policy) {
56
74
  throw new FsSafeError("invalid-path", "clone destination must name a child directory");
57
75
  const parent = openStagedDirectory(path.dirname(target));
58
76
  let original;
77
+ let failure;
59
78
  try {
60
79
  const supported = native?.probeTreeClone(parent.fd);
61
80
  if (!supported && policy === "always") {
@@ -113,13 +132,12 @@ async function materializeTree(source, destination, options, policy) {
113
132
  assertStagedDirectoryCurrent(original.receipt);
114
133
  }
115
134
  }
116
- finally {
117
- try {
118
- if (original)
119
- fs.closeSync(original.fd);
120
- }
121
- finally {
122
- fs.closeSync(parent.fd);
123
- }
135
+ catch (error) {
136
+ failure = firstFailure(failure, error);
124
137
  }
138
+ if (original)
139
+ failure = closeDescriptor(original.fd, failure);
140
+ failure = closeDescriptor(parent.fd, failure);
141
+ if (failure)
142
+ throw failure.value;
125
143
  }
@@ -0,0 +1,20 @@
1
+ import { type BigIntStats } from "node:fs";
2
+ import { type CreationPermissions } from "./creation-permissions.js";
3
+ import { type CreationParentIdentity } from "./creation-path.js";
4
+ export type CreateDirectoryOptions = CreationPermissions & {
5
+ assertBeforeMutation?: () => void;
6
+ };
7
+ export type CreationAdmission = {
8
+ expectedParentIdentity?: CreationParentIdentity;
9
+ };
10
+ export type CreationDirectoryReceipt = {
11
+ stat: BigIntStats;
12
+ windowsIdentity?: string;
13
+ };
14
+ export declare function createDirectory(targetPath: string, options?: CreateDirectoryOptions): Promise<void>;
15
+ export declare function createDirectoryWithAdmission(targetPath: string, options?: CreateDirectoryOptions, admission?: CreationAdmission): Promise<void>;
16
+ export declare function createDirectoryWithReceipt(targetPath: string, options?: CreateDirectoryOptions, admission?: CreationAdmission): Promise<CreationDirectoryReceipt>;
17
+ export declare function createDirectorySync(targetPath: string, options?: CreateDirectoryOptions): void;
18
+ export declare function createDirectorySyncWithAdmission(targetPath: string, options?: CreateDirectoryOptions, admission?: CreationAdmission): void;
19
+ export declare function createDirectoryWithReceiptSync(targetPath: string, options?: CreateDirectoryOptions, admission?: CreationAdmission): CreationDirectoryReceipt;
20
+ //# sourceMappingURL=create-directory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-directory.d.ts","sourceRoot":"","sources":["../src/create-directory.ts"],"names":[],"mappings":"AAAA,OAAe,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAMnD,OAAO,EAML,KAAK,mBAAmB,EACzB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAA0C,KAAK,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AASzG,MAAM,MAAM,sBAAsB,GAAG,mBAAmB,GAAG;IAAE,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAA;CAAE,CAAC;AACjG,MAAM,MAAM,iBAAiB,GAAG;IAAE,sBAAsB,CAAC,EAAE,sBAAsB,CAAA;CAAE,CAAC;AACpF,MAAM,MAAM,wBAAwB,GAAG;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAwBvF,wBAAsB,eAAe,CACnC,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,IAAI,CAAC,CAEf;AAED,wBAAsB,4BAA4B,CAChD,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,sBAA2B,EACpC,SAAS,GAAE,iBAAsB,GAChC,OAAO,CAAC,IAAI,CAAC,CAEf;AAED,wBAAsB,0BAA0B,CAC9C,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,sBAA2B,EACpC,SAAS,GAAE,iBAAsB,GAChC,OAAO,CAAC,wBAAwB,CAAC,CAmCnC;AAED,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,sBAA2B,GACnC,IAAI,CAEN;AAED,wBAAgB,gCAAgC,CAC9C,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,sBAA2B,EACpC,SAAS,GAAE,iBAAsB,GAChC,IAAI,CAEN;AAED,wBAAgB,8BAA8B,CAC5C,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,sBAA2B,EACpC,SAAS,GAAE,iBAAsB,GAChC,wBAAwB,CAmC1B"}