@backblaze-labs/b2-sdk 0.3.0 → 0.4.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 (393) hide show
  1. package/CHANGELOG.md +45 -1
  2. package/README.md +18 -5
  3. package/dist/_virtual/_b2-sdk-version-json.cjs +1 -1
  4. package/dist/_virtual/_b2-sdk-version-json.js +1 -1
  5. package/dist/auth/reauth-coalescer.cjs +65 -0
  6. package/dist/auth/reauth-coalescer.cjs.map +1 -0
  7. package/dist/auth/reauth-coalescer.d.cts +34 -0
  8. package/dist/auth/reauth-coalescer.d.cts.map +1 -0
  9. package/dist/auth/reauth-coalescer.d.ts +34 -0
  10. package/dist/auth/reauth-coalescer.d.ts.map +1 -0
  11. package/dist/auth/reauth-coalescer.js +65 -0
  12. package/dist/auth/reauth-coalescer.js.map +1 -0
  13. package/dist/backup/raw.cjs +37 -15
  14. package/dist/backup/raw.cjs.map +1 -1
  15. package/dist/backup/raw.d.cts +2 -2
  16. package/dist/backup/raw.d.cts.map +1 -1
  17. package/dist/backup/raw.d.ts +2 -2
  18. package/dist/backup/raw.d.ts.map +1 -1
  19. package/dist/backup/raw.js +37 -15
  20. package/dist/backup/raw.js.map +1 -1
  21. package/dist/bucket-validation.cjs +111 -0
  22. package/dist/bucket-validation.cjs.map +1 -0
  23. package/dist/bucket-validation.d.cts +27 -0
  24. package/dist/bucket-validation.d.cts.map +1 -0
  25. package/dist/bucket-validation.d.ts +27 -0
  26. package/dist/bucket-validation.d.ts.map +1 -0
  27. package/dist/bucket-validation.js +109 -0
  28. package/dist/bucket-validation.js.map +1 -0
  29. package/dist/bucket.cjs +123 -73
  30. package/dist/bucket.cjs.map +1 -1
  31. package/dist/bucket.d.cts +156 -61
  32. package/dist/bucket.d.cts.map +1 -1
  33. package/dist/bucket.d.ts +156 -61
  34. package/dist/bucket.d.ts.map +1 -1
  35. package/dist/bucket.js +123 -73
  36. package/dist/bucket.js.map +1 -1
  37. package/dist/client.cjs +29 -8
  38. package/dist/client.cjs.map +1 -1
  39. package/dist/client.d.cts +30 -9
  40. package/dist/client.d.cts.map +1 -1
  41. package/dist/client.d.ts +30 -9
  42. package/dist/client.d.ts.map +1 -1
  43. package/dist/client.js +29 -8
  44. package/dist/client.js.map +1 -1
  45. package/dist/download/single.cjs +38 -13
  46. package/dist/download/single.cjs.map +1 -1
  47. package/dist/download/single.d.cts +10 -4
  48. package/dist/download/single.d.cts.map +1 -1
  49. package/dist/download/single.d.ts +10 -4
  50. package/dist/download/single.d.ts.map +1 -1
  51. package/dist/download/single.js +38 -13
  52. package/dist/download/single.js.map +1 -1
  53. package/dist/errors/index.cjs +27 -4
  54. package/dist/errors/index.cjs.map +1 -1
  55. package/dist/errors/index.d.cts +15 -0
  56. package/dist/errors/index.d.cts.map +1 -1
  57. package/dist/errors/index.d.ts +15 -0
  58. package/dist/errors/index.d.ts.map +1 -1
  59. package/dist/errors/index.js +27 -5
  60. package/dist/errors/index.js.map +1 -1
  61. package/dist/http/non-idempotent-mutations.cjs +38 -0
  62. package/dist/http/non-idempotent-mutations.cjs.map +1 -0
  63. package/dist/http/non-idempotent-mutations.d.cts +25 -0
  64. package/dist/http/non-idempotent-mutations.d.cts.map +1 -0
  65. package/dist/http/non-idempotent-mutations.d.ts +25 -0
  66. package/dist/http/non-idempotent-mutations.d.ts.map +1 -0
  67. package/dist/http/non-idempotent-mutations.js +35 -0
  68. package/dist/http/non-idempotent-mutations.js.map +1 -0
  69. package/dist/http/transport.cjs +9 -8
  70. package/dist/http/transport.cjs.map +1 -1
  71. package/dist/http/transport.d.cts +5 -0
  72. package/dist/http/transport.d.cts.map +1 -1
  73. package/dist/http/transport.d.ts +5 -0
  74. package/dist/http/transport.d.ts.map +1 -1
  75. package/dist/http/transport.js +9 -8
  76. package/dist/http/transport.js.map +1 -1
  77. package/dist/http/user-agent.cjs +12 -1
  78. package/dist/http/user-agent.cjs.map +1 -1
  79. package/dist/http/user-agent.d.cts +8 -0
  80. package/dist/http/user-agent.d.cts.map +1 -1
  81. package/dist/http/user-agent.d.ts +8 -0
  82. package/dist/http/user-agent.d.ts.map +1 -1
  83. package/dist/http/user-agent.js +13 -3
  84. package/dist/http/user-agent.js.map +1 -1
  85. package/dist/index.cjs +25 -0
  86. package/dist/index.d.cts +10 -9
  87. package/dist/index.d.cts.map +1 -1
  88. package/dist/index.d.ts +10 -9
  89. package/dist/index.d.ts.map +1 -1
  90. package/dist/index.js +7 -6
  91. package/dist/notifications/index.cjs +3 -0
  92. package/dist/notifications/index.d.cts +2 -0
  93. package/dist/notifications/index.d.cts.map +1 -1
  94. package/dist/notifications/index.d.ts +2 -0
  95. package/dist/notifications/index.d.ts.map +1 -1
  96. package/dist/notifications/index.js +2 -1
  97. package/dist/object.cjs +12 -1
  98. package/dist/object.cjs.map +1 -1
  99. package/dist/object.d.cts +7 -1
  100. package/dist/object.d.cts.map +1 -1
  101. package/dist/object.d.ts +7 -1
  102. package/dist/object.d.ts.map +1 -1
  103. package/dist/object.js +12 -1
  104. package/dist/object.js.map +1 -1
  105. package/dist/partner/auth-core.cjs +4 -23
  106. package/dist/partner/auth-core.cjs.map +1 -1
  107. package/dist/partner/auth-core.d.cts +1 -1
  108. package/dist/partner/auth-core.d.cts.map +1 -1
  109. package/dist/partner/auth-core.d.ts +1 -1
  110. package/dist/partner/auth-core.d.ts.map +1 -1
  111. package/dist/partner/auth-core.js +5 -24
  112. package/dist/partner/auth-core.js.map +1 -1
  113. package/dist/partner/client.cjs +25 -10
  114. package/dist/partner/client.cjs.map +1 -1
  115. package/dist/partner/client.d.cts +32 -10
  116. package/dist/partner/client.d.cts.map +1 -1
  117. package/dist/partner/client.d.ts +32 -10
  118. package/dist/partner/client.d.ts.map +1 -1
  119. package/dist/partner/client.js +25 -10
  120. package/dist/partner/client.js.map +1 -1
  121. package/dist/partner/endpoint-url.cjs +1 -0
  122. package/dist/partner/endpoint-url.cjs.map +1 -1
  123. package/dist/partner/endpoint-url.d.cts +2 -0
  124. package/dist/partner/endpoint-url.d.cts.map +1 -1
  125. package/dist/partner/endpoint-url.d.ts +2 -0
  126. package/dist/partner/endpoint-url.d.ts.map +1 -1
  127. package/dist/partner/endpoint-url.js +1 -0
  128. package/dist/partner/endpoint-url.js.map +1 -1
  129. package/dist/partner/in-memory.cjs.map +1 -1
  130. package/dist/partner/in-memory.js.map +1 -1
  131. package/dist/partner/index.cjs +9 -0
  132. package/dist/partner/index.d.cts +11 -2
  133. package/dist/partner/index.d.cts.map +1 -1
  134. package/dist/partner/index.d.ts +11 -2
  135. package/dist/partner/index.d.ts.map +1 -1
  136. package/dist/partner/index.js +3 -1
  137. package/dist/partner/raw.cjs +52 -44
  138. package/dist/partner/raw.cjs.map +1 -1
  139. package/dist/partner/raw.d.cts +16 -19
  140. package/dist/partner/raw.d.cts.map +1 -1
  141. package/dist/partner/raw.d.ts +16 -19
  142. package/dist/partner/raw.d.ts.map +1 -1
  143. package/dist/partner/raw.js +52 -44
  144. package/dist/partner/raw.js.map +1 -1
  145. package/dist/partner/redaction.cjs +133 -108
  146. package/dist/partner/redaction.cjs.map +1 -1
  147. package/dist/partner/redaction.d.cts +47 -13
  148. package/dist/partner/redaction.d.cts.map +1 -1
  149. package/dist/partner/redaction.d.ts +47 -13
  150. package/dist/partner/redaction.d.ts.map +1 -1
  151. package/dist/partner/redaction.js +133 -109
  152. package/dist/partner/redaction.js.map +1 -1
  153. package/dist/raw/encoding.cjs +29 -10
  154. package/dist/raw/encoding.cjs.map +1 -1
  155. package/dist/raw/encoding.d.cts +3 -2
  156. package/dist/raw/encoding.d.cts.map +1 -1
  157. package/dist/raw/encoding.d.ts +3 -2
  158. package/dist/raw/encoding.d.ts.map +1 -1
  159. package/dist/raw/encoding.js +29 -10
  160. package/dist/raw/encoding.js.map +1 -1
  161. package/dist/raw/index.cjs +64 -39
  162. package/dist/raw/index.cjs.map +1 -1
  163. package/dist/raw/index.d.cts +21 -19
  164. package/dist/raw/index.d.cts.map +1 -1
  165. package/dist/raw/index.d.ts +21 -19
  166. package/dist/raw/index.d.ts.map +1 -1
  167. package/dist/raw/index.js +47 -40
  168. package/dist/raw/index.js.map +1 -1
  169. package/dist/raw/url.cjs.map +1 -1
  170. package/dist/raw/url.d.cts +3 -3
  171. package/dist/raw/url.d.ts +3 -3
  172. package/dist/raw/url.js.map +1 -1
  173. package/dist/simulator/index.cjs +456 -125
  174. package/dist/simulator/index.cjs.map +1 -1
  175. package/dist/simulator/index.d.cts +16 -1
  176. package/dist/simulator/index.d.cts.map +1 -1
  177. package/dist/simulator/index.d.ts +16 -1
  178. package/dist/simulator/index.d.ts.map +1 -1
  179. package/dist/simulator/index.js +449 -130
  180. package/dist/simulator/index.js.map +1 -1
  181. package/dist/simulator/partner-capabilities.cjs +77 -0
  182. package/dist/simulator/partner-capabilities.cjs.map +1 -0
  183. package/dist/simulator/partner-capabilities.d.cts +53 -0
  184. package/dist/simulator/partner-capabilities.d.cts.map +1 -0
  185. package/dist/simulator/partner-capabilities.d.ts +53 -0
  186. package/dist/simulator/partner-capabilities.d.ts.map +1 -0
  187. package/dist/simulator/partner-capabilities.js +73 -0
  188. package/dist/simulator/partner-capabilities.js.map +1 -0
  189. package/dist/simulator/partner.cjs +55 -61
  190. package/dist/simulator/partner.cjs.map +1 -1
  191. package/dist/simulator/partner.d.cts +5 -2
  192. package/dist/simulator/partner.d.cts.map +1 -1
  193. package/dist/simulator/partner.d.ts +5 -2
  194. package/dist/simulator/partner.d.ts.map +1 -1
  195. package/dist/simulator/partner.js +56 -61
  196. package/dist/simulator/partner.js.map +1 -1
  197. package/dist/simulator/validation.cjs +160 -30
  198. package/dist/simulator/validation.cjs.map +1 -1
  199. package/dist/simulator/validation.d.cts +28 -6
  200. package/dist/simulator/validation.d.cts.map +1 -1
  201. package/dist/simulator/validation.d.ts +28 -6
  202. package/dist/simulator/validation.d.ts.map +1 -1
  203. package/dist/simulator/validation.js +148 -31
  204. package/dist/simulator/validation.js.map +1 -1
  205. package/dist/streams/file-source.cjs +1 -1
  206. package/dist/streams/file-source.cjs.map +1 -1
  207. package/dist/streams/file-source.js +1 -1
  208. package/dist/streams/file-source.js.map +1 -1
  209. package/dist/streams/hash.cjs +246 -13
  210. package/dist/streams/hash.cjs.map +1 -1
  211. package/dist/streams/hash.d.cts +37 -12
  212. package/dist/streams/hash.d.cts.map +1 -1
  213. package/dist/streams/hash.d.ts +37 -12
  214. package/dist/streams/hash.d.ts.map +1 -1
  215. package/dist/streams/hash.js +246 -15
  216. package/dist/streams/hash.js.map +1 -1
  217. package/dist/streams/index.cjs +2 -0
  218. package/dist/streams/index.d.cts +1 -1
  219. package/dist/streams/index.d.cts.map +1 -1
  220. package/dist/streams/index.d.ts +1 -1
  221. package/dist/streams/index.d.ts.map +1 -1
  222. package/dist/streams/index.js +2 -2
  223. package/dist/streams/progress.cjs +63 -4
  224. package/dist/streams/progress.cjs.map +1 -1
  225. package/dist/streams/progress.d.cts +28 -2
  226. package/dist/streams/progress.d.cts.map +1 -1
  227. package/dist/streams/progress.d.ts +28 -2
  228. package/dist/streams/progress.d.ts.map +1 -1
  229. package/dist/streams/progress.js +63 -4
  230. package/dist/streams/progress.js.map +1 -1
  231. package/dist/sync/download-staging.cjs +147 -35
  232. package/dist/sync/download-staging.cjs.map +1 -1
  233. package/dist/sync/download-staging.d.cts +25 -2
  234. package/dist/sync/download-staging.d.cts.map +1 -1
  235. package/dist/sync/download-staging.d.ts +25 -2
  236. package/dist/sync/download-staging.d.ts.map +1 -1
  237. package/dist/sync/download-staging.js +146 -36
  238. package/dist/sync/download-staging.js.map +1 -1
  239. package/dist/sync/index.d.cts +1 -1
  240. package/dist/sync/index.d.cts.map +1 -1
  241. package/dist/sync/index.d.ts +1 -1
  242. package/dist/sync/index.d.ts.map +1 -1
  243. package/dist/sync/local-file-io.cjs +5 -1
  244. package/dist/sync/local-file-io.cjs.map +1 -1
  245. package/dist/sync/local-file-io.d.cts +2 -0
  246. package/dist/sync/local-file-io.d.cts.map +1 -1
  247. package/dist/sync/local-file-io.d.ts +2 -0
  248. package/dist/sync/local-file-io.d.ts.map +1 -1
  249. package/dist/sync/local-file-io.js +5 -1
  250. package/dist/sync/local-file-io.js.map +1 -1
  251. package/dist/sync/pairing.cjs +1 -0
  252. package/dist/sync/pairing.cjs.map +1 -1
  253. package/dist/sync/pairing.js +1 -0
  254. package/dist/sync/pairing.js.map +1 -1
  255. package/dist/sync/scanners/b2.cjs +2 -0
  256. package/dist/sync/scanners/b2.cjs.map +1 -1
  257. package/dist/sync/scanners/b2.d.cts.map +1 -1
  258. package/dist/sync/scanners/b2.d.ts.map +1 -1
  259. package/dist/sync/scanners/b2.js +2 -0
  260. package/dist/sync/scanners/b2.js.map +1 -1
  261. package/dist/sync/scanners/local.cjs +150 -33
  262. package/dist/sync/scanners/local.cjs.map +1 -1
  263. package/dist/sync/scanners/local.d.cts +6 -0
  264. package/dist/sync/scanners/local.d.cts.map +1 -1
  265. package/dist/sync/scanners/local.d.ts +6 -0
  266. package/dist/sync/scanners/local.d.ts.map +1 -1
  267. package/dist/sync/scanners/local.js +150 -33
  268. package/dist/sync/scanners/local.js.map +1 -1
  269. package/dist/sync/synchronizer.cjs +76 -29
  270. package/dist/sync/synchronizer.cjs.map +1 -1
  271. package/dist/sync/synchronizer.d.cts +1 -0
  272. package/dist/sync/synchronizer.d.cts.map +1 -1
  273. package/dist/sync/synchronizer.d.ts +1 -0
  274. package/dist/sync/synchronizer.d.ts.map +1 -1
  275. package/dist/sync/synchronizer.js +76 -29
  276. package/dist/sync/synchronizer.js.map +1 -1
  277. package/dist/sync/types.d.cts +19 -3
  278. package/dist/sync/types.d.cts.map +1 -1
  279. package/dist/sync/types.d.ts +19 -3
  280. package/dist/sync/types.d.ts.map +1 -1
  281. package/dist/types/backup.d.cts +6 -1
  282. package/dist/types/backup.d.cts.map +1 -1
  283. package/dist/types/backup.d.ts +6 -1
  284. package/dist/types/backup.d.ts.map +1 -1
  285. package/dist/types/bucket.cjs +81 -11
  286. package/dist/types/bucket.cjs.map +1 -1
  287. package/dist/types/bucket.d.cts +190 -39
  288. package/dist/types/bucket.d.cts.map +1 -1
  289. package/dist/types/bucket.d.ts +190 -39
  290. package/dist/types/bucket.d.ts.map +1 -1
  291. package/dist/types/bucket.js +67 -12
  292. package/dist/types/bucket.js.map +1 -1
  293. package/dist/types/download.cjs.map +1 -1
  294. package/dist/types/download.d.cts +3 -17
  295. package/dist/types/download.d.cts.map +1 -1
  296. package/dist/types/download.d.ts +3 -17
  297. package/dist/types/download.d.ts.map +1 -1
  298. package/dist/types/download.js.map +1 -1
  299. package/dist/types/encryption.cjs +18 -0
  300. package/dist/types/encryption.cjs.map +1 -1
  301. package/dist/types/encryption.d.cts +21 -11
  302. package/dist/types/encryption.d.cts.map +1 -1
  303. package/dist/types/encryption.d.ts +21 -11
  304. package/dist/types/encryption.d.ts.map +1 -1
  305. package/dist/types/encryption.js +18 -0
  306. package/dist/types/encryption.js.map +1 -1
  307. package/dist/types/file.cjs +3 -0
  308. package/dist/types/file.cjs.map +1 -1
  309. package/dist/types/file.d.cts +125 -16
  310. package/dist/types/file.d.cts.map +1 -1
  311. package/dist/types/file.d.ts +125 -16
  312. package/dist/types/file.d.ts.map +1 -1
  313. package/dist/types/file.js +3 -1
  314. package/dist/types/file.js.map +1 -1
  315. package/dist/types/index.d.cts +7 -7
  316. package/dist/types/index.d.cts.map +1 -1
  317. package/dist/types/index.d.ts +7 -7
  318. package/dist/types/index.d.ts.map +1 -1
  319. package/dist/types/key.d.cts +4 -3
  320. package/dist/types/key.d.cts.map +1 -1
  321. package/dist/types/key.d.ts +4 -3
  322. package/dist/types/key.d.ts.map +1 -1
  323. package/dist/types/notifications.cjs +30 -0
  324. package/dist/types/notifications.cjs.map +1 -1
  325. package/dist/types/notifications.d.cts +26 -1
  326. package/dist/types/notifications.d.cts.map +1 -1
  327. package/dist/types/notifications.d.ts +26 -1
  328. package/dist/types/notifications.d.ts.map +1 -1
  329. package/dist/types/notifications.js +29 -1
  330. package/dist/types/notifications.js.map +1 -1
  331. package/dist/types/partner.cjs.map +1 -1
  332. package/dist/types/partner.d.cts +28 -36
  333. package/dist/types/partner.d.cts.map +1 -1
  334. package/dist/types/partner.d.ts +28 -36
  335. package/dist/types/partner.d.ts.map +1 -1
  336. package/dist/types/partner.js.map +1 -1
  337. package/dist/types/upload.d.cts +4 -0
  338. package/dist/types/upload.d.cts.map +1 -1
  339. package/dist/types/upload.d.ts +4 -0
  340. package/dist/types/upload.d.ts.map +1 -1
  341. package/dist/upload/large.cjs +10 -7
  342. package/dist/upload/large.cjs.map +1 -1
  343. package/dist/upload/large.d.cts +15 -6
  344. package/dist/upload/large.d.cts.map +1 -1
  345. package/dist/upload/large.d.ts +15 -6
  346. package/dist/upload/large.d.ts.map +1 -1
  347. package/dist/upload/large.js +10 -7
  348. package/dist/upload/large.js.map +1 -1
  349. package/dist/upload/options.cjs.map +1 -1
  350. package/dist/upload/options.d.cts +12 -0
  351. package/dist/upload/options.d.cts.map +1 -1
  352. package/dist/upload/options.d.ts +12 -0
  353. package/dist/upload/options.d.ts.map +1 -1
  354. package/dist/upload/options.js.map +1 -1
  355. package/dist/upload/resume.cjs +4 -3
  356. package/dist/upload/resume.cjs.map +1 -1
  357. package/dist/upload/resume.d.cts +6 -4
  358. package/dist/upload/resume.d.cts.map +1 -1
  359. package/dist/upload/resume.d.ts +6 -4
  360. package/dist/upload/resume.d.ts.map +1 -1
  361. package/dist/upload/resume.js +4 -3
  362. package/dist/upload/resume.js.map +1 -1
  363. package/dist/upload/retry.cjs +17 -10
  364. package/dist/upload/retry.cjs.map +1 -1
  365. package/dist/upload/retry.d.cts.map +1 -1
  366. package/dist/upload/retry.d.ts.map +1 -1
  367. package/dist/upload/retry.js +17 -10
  368. package/dist/upload/retry.js.map +1 -1
  369. package/dist/upload/single.cjs +35 -31
  370. package/dist/upload/single.cjs.map +1 -1
  371. package/dist/upload/single.d.cts +6 -0
  372. package/dist/upload/single.d.cts.map +1 -1
  373. package/dist/upload/single.d.ts +6 -0
  374. package/dist/upload/single.d.ts.map +1 -1
  375. package/dist/upload/single.js +35 -31
  376. package/dist/upload/single.js.map +1 -1
  377. package/dist/upload/stream.cjs +12 -1
  378. package/dist/upload/stream.cjs.map +1 -1
  379. package/dist/upload/stream.d.cts +6 -0
  380. package/dist/upload/stream.d.cts.map +1 -1
  381. package/dist/upload/stream.d.ts +6 -0
  382. package/dist/upload/stream.d.ts.map +1 -1
  383. package/dist/upload/stream.js +12 -1
  384. package/dist/upload/stream.js.map +1 -1
  385. package/dist/version.cjs +56 -4
  386. package/dist/version.cjs.map +1 -1
  387. package/dist/version.d.cts +53 -4
  388. package/dist/version.d.cts.map +1 -1
  389. package/dist/version.d.ts +53 -4
  390. package/dist/version.d.ts.map +1 -1
  391. package/dist/version.js +52 -5
  392. package/dist/version.js.map +1 -1
  393. package/package.json +11 -4
@@ -1 +1 @@
1
- {"version":3,"file":"local.d.ts","sourceRoot":"","sources":["../../../src/sync/scanners/local.js"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,aAAa,EAAkB,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AA0B7F;;;;;;;;GAQG;AACH,qBAAa,WAAY,YAAW,UAAU;IAC5C,QAAQ,CAAC,IAAI,EAAG,OAAO,CAAS;IAChC,QAAQ,CAAC,kBAAkB,EAAG,IAAI,CAAS;IAC3C,QAAQ,CAAC,kBAAkB,EAAG,IAAI,CAAS;IAC3C,0DAA0D;IAC1D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IAErB;;;OAGG;gBACS,IAAI,EAAE,MAAM;IAKxB;;;OAGG;IACI,IAAI,CAAC,OAAO,GAAE,eAAoB,GAAG,cAAc,CAAC,aAAa,CAAC;IAazE;;;;;;;;OAQG;YACW,IAAI;IA8FlB,OAAO,CAAC,aAAa;CAetB"}
1
+ {"version":3,"file":"local.d.ts","sourceRoot":"","sources":["../../../src/sync/scanners/local.js"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,aAAa,EAAkB,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AA+B7F;;;;;;;;GAQG;AACH,qBAAa,WAAY,YAAW,UAAU;IAC5C,QAAQ,CAAC,IAAI,EAAG,OAAO,CAAS;IAChC,QAAQ,CAAC,kBAAkB,EAAG,IAAI,CAAS;IAC3C,QAAQ,CAAC,kBAAkB,EAAG,IAAI,CAAS;IAC3C,0DAA0D;IAC1D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IAErB;;;OAGG;gBACS,IAAI,EAAE,MAAM;IAKxB;;;OAGG;IACI,IAAI,CAAC,OAAO,GAAE,eAAoB,GAAG,cAAc,CAAC,aAAa,CAAC;IAqCzE;;;;;;;;;;;OAWG;YACW,IAAI;YAuFJ,aAAa;YA4Db,WAAW;YA4CX,aAAa;IAuF3B,OAAO,CAAC,aAAa;CAetB"}
@@ -4,9 +4,9 @@ import { directoryMayContainSyncPaths, pathPassesSyncFilters, pathSkippedByRegEx
4
4
  import { compareSyncRelativePaths } from "../path-order.js";
5
5
  import { assertScanEntryLimit, scanEntryLimit } from "../scan-limit.js";
6
6
  import { localFileIdentityFromStats } from "../local-file-identity.js";
7
+ import { localFilesystemErrorReason } from "../filesystem-errors.js";
7
8
  import { isReservedSyncTempFileName } from "../path-safety.js";
8
9
  import { isDownloadStagingDirectorySegment, isManagedDownloadStagingRoot } from "../download-staging.js";
9
- import { localFilesystemErrorReason } from "../filesystem-errors.js";
10
10
  import { registerLocalFilesystemRoot } from "../local-filesystem-root.js";
11
11
  //#region src/sync/scanners/local.ts
12
12
  /**
@@ -40,8 +40,17 @@ var LocalFolder = class {
40
40
  validateSyncFilters(options);
41
41
  const nodeDeps = await loadLocalNodeDeps();
42
42
  const root = nodeDeps.resolve(this.root);
43
+ const followSymlinks = options.localSymlinks === "follow" && options.requireLocalSafePaths !== true;
44
+ let scanRoot = root;
43
45
  const collected = [];
44
- await this.walk(root, root, collected, options, scanEntryLimit(options), nodeDeps);
46
+ const activeDirectoryIds = /* @__PURE__ */ new Set();
47
+ const followedDirectoryIds = /* @__PURE__ */ new Set();
48
+ if (followSymlinks) try {
49
+ scanRoot = await nodeDeps.realpath(root);
50
+ const rootStats = await nodeDeps.stat(scanRoot);
51
+ if (rootStats.isDirectory()) activeDirectoryIds.add(localNodeIdentity(rootStats));
52
+ } catch {}
53
+ await this.walk(scanRoot, scanRoot, "", collected, options, scanEntryLimit(options), nodeDeps, activeDirectoryIds, followedDirectoryIds);
45
54
  collected.sort((a, b) => compareSyncRelativePaths(a.relativePath, b.relativePath));
46
55
  for (const entry of collected) {
47
56
  throwIfScanAborted(options);
@@ -52,25 +61,28 @@ var LocalFolder = class {
52
61
  * Recursively collects files from {@link dir} into {@link out}.
53
62
  * @param root - Resolved scan root used for relative path calculation.
54
63
  * @param dir - Absolute path of the directory to scan.
64
+ * @param dirRel - Sync-relative path represented by {@link dir}.
55
65
  * @param out - Accumulator array that receives discovered file entries.
56
66
  * @param options - Optional scan controls.
57
67
  * @param maxScanEntries - Maximum number of entries to retain before failing.
58
68
  * @param nodeDeps - Lazy-loaded Node filesystem and path helpers.
69
+ * @param activeDirectoryIds - Directory identities in the current recursion stack.
70
+ * @param followedDirectoryIds - Followed symlink directory identities already traversed.
59
71
  */
60
- async walk(root, dir, out, options, maxScanEntries, nodeDeps) {
72
+ async walk(root, dir, dirRel, out, options, maxScanEntries, nodeDeps, activeDirectoryIds, followedDirectoryIds) {
61
73
  throwIfScanAborted(options);
62
74
  let entries;
63
75
  try {
64
76
  entries = await nodeDeps.readdir(dir, { withFileTypes: true });
65
77
  } catch (err) {
66
- const error = this.emitScanError(options, relativePathFromRoot(root, dir, nodeDeps), "directory", err);
78
+ const error = this.emitScanError(options, dirRel, "directory", err);
67
79
  if (dir === root) throw error;
68
80
  return;
69
81
  }
70
82
  for (const entry of entries) {
71
83
  throwIfScanAborted(options);
72
84
  const fullPath = nodeDeps.join(dir, entry.name);
73
- const rel = relativePathFromRoot(root, fullPath, nodeDeps);
85
+ const rel = joinSyncRelativePath(dirRel, entry.name);
74
86
  if (isDownloadStagingDirectorySegment(rel) && entry.isDirectory() && isDownloadStagingDirectorySegment(entry.name) && await isManagedDownloadStagingRoot(fullPath)) continue;
75
87
  if (rel.includes("\\")) {
76
88
  emitScannerSkip(options, {
@@ -93,35 +105,130 @@ var LocalFolder = class {
93
105
  continue;
94
106
  }
95
107
  if (entry.isDirectory()) {
96
- if (directoryMayContainSyncPaths(rel, options)) await this.walk(root, fullPath, out, options, maxScanEntries, nodeDeps);
97
- } else if (entry.isFile()) {
98
- if (!pathPassesSyncFilters(rel, options)) {
99
- if (pathSkippedByRegExpInputLimit(rel, options)) emitScannerSkip(options, regexpInputTooLongSkip(rel));
100
- continue;
101
- }
102
- let s;
103
- try {
104
- s = await nodeDeps.lstat(fullPath);
105
- /* v8 ignore start -- lstat race after a Dirent file result is not deterministic */
106
- if (!s.isFile()) {
107
- this.emitScanError(options, rel, "file", /* @__PURE__ */ new Error("not a regular file"));
108
- continue;
109
- }
110
- } catch (err) {
111
- /* v8 ignore next -- stat TOCTOU failures are not deterministic to trigger */
112
- this.emitScanError(options, relativePathFromRoot(root, fullPath, nodeDeps), "file", err);
113
- continue;
108
+ if (directoryMayContainSyncPaths(rel, options)) await this.walkDirectory(root, fullPath, rel, out, options, maxScanEntries, nodeDeps, activeDirectoryIds, followedDirectoryIds);
109
+ } else if (entry.isFile()) await this.collectFile(fullPath, rel, out, options, maxScanEntries, nodeDeps);
110
+ else if (entry.isSymbolicLink()) await this.handleSymlink(root, fullPath, rel, out, options, maxScanEntries, nodeDeps, activeDirectoryIds, followedDirectoryIds);
111
+ }
112
+ }
113
+ async walkDirectory(root, dir, rel, out, options, maxScanEntries, nodeDeps, activeDirectoryIds, followedDirectoryIds, knownStats) {
114
+ let stats = knownStats;
115
+ if (stats === void 0) try {
116
+ stats = await nodeDeps.stat(dir);
117
+ } catch (err) {
118
+ /* v8 ignore start -- stat TOCTOU failures after a Dirent directory result are not deterministic */
119
+ this.emitScanError(options, rel, "directory", err);
120
+ return;
121
+ }
122
+ /* v8 ignore start -- stat TOCTOU races after a Dirent directory result are not deterministic */
123
+ if (!stats.isDirectory()) {
124
+ this.emitScanError(options, rel, "directory", /* @__PURE__ */ new Error("not a directory"));
125
+ return;
126
+ }
127
+ /* v8 ignore stop */
128
+ const identity = localNodeIdentity(stats);
129
+ if (activeDirectoryIds.has(identity)) {
130
+ emitScannerSkip(options, {
131
+ type: "skip",
132
+ path: rel,
133
+ size: 0,
134
+ reason: "local-symlink-loop",
135
+ message: `Skipped local path ${JSON.stringify(rel)}: symlink would revisit an ancestor directory`
136
+ });
137
+ return;
138
+ }
139
+ activeDirectoryIds.add(identity);
140
+ try {
141
+ await this.walk(root, dir, rel, out, options, maxScanEntries, nodeDeps, activeDirectoryIds, followedDirectoryIds);
142
+ } finally {
143
+ activeDirectoryIds.delete(identity);
144
+ }
145
+ }
146
+ async collectFile(fullPath, rel, out, options, maxScanEntries, nodeDeps, knownStats) {
147
+ if (!pathPassesSyncFilters(rel, options)) {
148
+ if (pathSkippedByRegExpInputLimit(rel, options)) emitScannerSkip(options, regexpInputTooLongSkip(rel));
149
+ return;
150
+ }
151
+ let s = knownStats;
152
+ if (s === void 0) try {
153
+ s = await nodeDeps.lstat(fullPath);
154
+ /* v8 ignore start -- lstat race after a Dirent file result is not deterministic */
155
+ if (!s.isFile()) {
156
+ this.emitScanError(options, rel, "file", /* @__PURE__ */ new Error("not a regular file"));
157
+ return;
158
+ }
159
+ } catch (err) {
160
+ /* v8 ignore start -- stat TOCTOU failures are not deterministic to trigger */
161
+ this.emitScanError(options, rel, "file", err);
162
+ return;
163
+ }
164
+ assertScanEntryLimit(out.length + 1, maxScanEntries);
165
+ out.push({
166
+ relativePath: rel,
167
+ absolutePath: fullPath,
168
+ modTimeMillis: Math.floor(s.mtimeMs),
169
+ size: s.size,
170
+ fileIdentity: localFileIdentityFromStats(s)
171
+ });
172
+ }
173
+ async handleSymlink(root, fullPath, rel, out, options, maxScanEntries, nodeDeps, activeDirectoryIds, followedDirectoryIds) {
174
+ if ((options.localSymlinks ?? "skip") === "skip" || options.requireLocalSafePaths === true) {
175
+ emitScannerSkip(options, {
176
+ type: "skip",
177
+ path: rel,
178
+ size: 0,
179
+ reason: "local-symlink",
180
+ message: `Skipped local path ${JSON.stringify(rel)}: local symlinks are disabled`
181
+ });
182
+ return;
183
+ }
184
+ let targetPath;
185
+ let targetStats;
186
+ try {
187
+ targetPath = await nodeDeps.realpath(fullPath);
188
+ targetStats = await nodeDeps.stat(targetPath);
189
+ } catch (err) {
190
+ this.emitScanError(options, rel, "symlink", err);
191
+ return;
192
+ }
193
+ if (!isPathInsideRoot(root, targetPath, nodeDeps)) {
194
+ emitScannerSkip(options, {
195
+ type: "skip",
196
+ path: rel,
197
+ size: 0,
198
+ reason: "local-symlink",
199
+ message: `Skipped local path ${JSON.stringify(rel)}: symlink target is outside the sync root`
200
+ });
201
+ return;
202
+ }
203
+ if (targetStats.isDirectory()) {
204
+ if (directoryMayContainSyncPaths(rel, options)) {
205
+ const identity = localNodeIdentity(targetStats);
206
+ if (followedDirectoryIds.has(identity)) {
207
+ emitScannerSkip(options, {
208
+ type: "skip",
209
+ path: rel,
210
+ size: 0,
211
+ reason: "local-symlink-loop",
212
+ message: `Skipped local path ${JSON.stringify(rel)}: symlink target was already visited`
213
+ });
214
+ return;
114
215
  }
115
- assertScanEntryLimit(out.length + 1, maxScanEntries);
116
- out.push({
117
- relativePath: rel,
118
- absolutePath: fullPath,
119
- modTimeMillis: Math.floor(s.mtimeMs),
120
- size: s.size,
121
- fileIdentity: localFileIdentityFromStats(s)
122
- });
216
+ followedDirectoryIds.add(identity);
217
+ await this.walkDirectory(root, targetPath, rel, out, options, maxScanEntries, nodeDeps, activeDirectoryIds, followedDirectoryIds, targetStats);
123
218
  }
219
+ return;
124
220
  }
221
+ if (targetStats.isFile()) {
222
+ await this.collectFile(targetPath, rel, out, options, maxScanEntries, nodeDeps, targetStats);
223
+ return;
224
+ }
225
+ emitScannerSkip(options, {
226
+ type: "skip",
227
+ path: rel,
228
+ size: 0,
229
+ reason: "local-symlink",
230
+ message: `Skipped local path ${JSON.stringify(rel)}: symlink target is not a regular file or directory`
231
+ });
125
232
  }
126
233
  emitScanError(options, path, kind, err) {
127
234
  const event = {
@@ -139,9 +246,12 @@ async function loadLocalNodeDeps() {
139
246
  return {
140
247
  readdir: fsPromises.readdir,
141
248
  lstat: fsPromises.lstat,
249
+ stat: fsPromises.stat,
142
250
  join: path.join,
143
251
  relative: path.relative,
252
+ realpath: fsPromises.realpath,
144
253
  resolve: path.resolve,
254
+ isAbsolute: path.isAbsolute,
145
255
  sep: path.sep
146
256
  };
147
257
  }
@@ -205,8 +315,15 @@ function normalizePathSegments(segments, separator) {
205
315
  }
206
316
  return out.join(separator);
207
317
  }
208
- function relativePathFromRoot(root, path, nodeDeps) {
209
- return nodeDeps.relative(root, path).split(nodeDeps.sep).join("/");
318
+ function joinSyncRelativePath(parent, child) {
319
+ return parent === "" ? child : `${parent}/${child}`;
320
+ }
321
+ function isPathInsideRoot(root, path, nodeDeps) {
322
+ const rel = nodeDeps.relative(root, path);
323
+ return rel === "" || !rel.startsWith(`..${nodeDeps.sep}`) && rel !== ".." && !nodeDeps.isAbsolute(rel);
324
+ }
325
+ function localNodeIdentity(stats) {
326
+ return `${stats.dev}:${stats.ino}`;
210
327
  }
211
328
  function throwIfScanAborted(options) {
212
329
  if (options.signal?.aborted === true) throw options.signal.reason ?? new DOMException("Aborted", "AbortError");
@@ -1 +1 @@
1
- {"version":3,"file":"local.js","names":[],"sources":["../../../src/sync/scanners/local.ts"],"sourcesContent":["import {\n isDownloadStagingDirectorySegment,\n isManagedDownloadStagingRoot,\n} from '../download-staging.ts'\nimport { localFilesystemErrorReason } from '../filesystem-errors.ts'\nimport {\n directoryMayContainSyncPaths,\n pathPassesSyncFilters,\n pathSkippedByRegExpInputLimit,\n} from '../filters.ts'\nimport { localFileIdentityFromStats } from '../local-file-identity.ts'\nimport { registerLocalFilesystemRoot } from '../local-filesystem-root.ts'\nimport { compareSyncRelativePaths } from '../path-order.ts'\nimport { isReservedSyncTempFileName } from '../path-safety.ts'\nimport { validateSyncFilters } from '../regexp-safety.ts'\nimport { emitScannerSkip, regexpInputTooLongSkip } from '../scan-events.ts'\nimport { assertScanEntryLimit, scanEntryLimit } from '../scan-limit.ts'\nimport type { LocalSyncPath, SyncErrorEvent, SyncFolder, SyncScanOptions } from '../types.ts'\n\ntype LocalDirent = {\n readonly name: string\n isDirectory(): boolean\n isFile(): boolean\n}\n\ntype LocalStats = {\n readonly dev: number\n readonly ino: number\n readonly mtimeMs: number\n readonly ctimeMs: number\n readonly size: number\n isFile(): boolean\n}\n\ntype LocalNodeDeps = {\n readdir(path: string, options: { readonly withFileTypes: true }): Promise<LocalDirent[]>\n lstat(path: string): Promise<LocalStats>\n join(...paths: string[]): string\n relative(from: string, to: string): string\n resolve(...paths: string[]): string\n sep: string\n}\n\n/**\n * Scans a local directory tree and yields {@link LocalSyncPath} entries sorted by relative path.\n * A root directory read failure aborts the scan with an error diagnostic. Per-entry file or\n * directory failures are reported through `onError` and the scan continues over readable siblings.\n * SDK-managed partial download file names are skipped so unfinished internal\n * temp files are not synchronized.\n * The current implementation collects matching entries before sorting, so memory usage is\n * proportional to the number of matched files.\n */\nexport class LocalFolder implements SyncFolder {\n readonly type = 'local' as const\n readonly appliesScanFilters = true as const\n readonly appliesScanSorting = true as const\n /** Resolved absolute path to the local root directory. */\n readonly root: string\n\n /**\n * Creates a new LocalFolder for the given root directory.\n * @param root - Absolute or relative path to the local directory to scan.\n */\n constructor(root: string) {\n this.root = resolvePathAtConstruction(root)\n registerLocalFilesystemRoot(this)\n }\n\n /**\n * Recursively walks the directory and yields files in sync path order.\n * @param options - Optional scan controls.\n */\n async *scan(options: SyncScanOptions = {}): AsyncGenerator<LocalSyncPath> {\n validateSyncFilters(options)\n const nodeDeps = await loadLocalNodeDeps()\n const root = nodeDeps.resolve(this.root)\n const collected: LocalSyncPath[] = []\n await this.walk(root, root, collected, options, scanEntryLimit(options), nodeDeps)\n collected.sort((a, b) => compareSyncRelativePaths(a.relativePath, b.relativePath))\n for (const entry of collected) {\n throwIfScanAborted(options)\n yield entry\n }\n }\n\n /**\n * Recursively collects files from {@link dir} into {@link out}.\n * @param root - Resolved scan root used for relative path calculation.\n * @param dir - Absolute path of the directory to scan.\n * @param out - Accumulator array that receives discovered file entries.\n * @param options - Optional scan controls.\n * @param maxScanEntries - Maximum number of entries to retain before failing.\n * @param nodeDeps - Lazy-loaded Node filesystem and path helpers.\n */\n private async walk(\n root: string,\n dir: string,\n out: LocalSyncPath[],\n options: SyncScanOptions,\n maxScanEntries: number,\n nodeDeps: LocalNodeDeps,\n ): Promise<void> {\n throwIfScanAborted(options)\n let entries: LocalDirent[]\n try {\n entries = await nodeDeps.readdir(dir, { withFileTypes: true })\n } catch (err) {\n const error = this.emitScanError(\n options,\n relativePathFromRoot(root, dir, nodeDeps),\n 'directory',\n err,\n )\n if (dir === root) throw error\n return\n }\n\n for (const entry of entries) {\n throwIfScanAborted(options)\n const fullPath = nodeDeps.join(dir, entry.name)\n const rel = relativePathFromRoot(root, fullPath, nodeDeps)\n if (\n isDownloadStagingDirectorySegment(rel) &&\n entry.isDirectory() &&\n isDownloadStagingDirectorySegment(entry.name) &&\n (await isManagedDownloadStagingRoot(fullPath))\n ) {\n continue\n }\n if (rel.includes('\\\\')) {\n emitScannerSkip(options, {\n type: 'skip',\n path: rel,\n size: 0,\n reason: 'unsafe-name',\n message: `Skipped local path ${JSON.stringify(rel)}: backslashes are not safe sync path characters`,\n })\n continue\n }\n if (isReservedSyncTempFileName(entry.name)) {\n emitScannerSkip(options, {\n type: 'skip',\n path: rel,\n size: 0,\n reason: 'stale-download-partial',\n message: `Skipped local path ${JSON.stringify(rel)}: reserved SDK partial download file`,\n })\n continue\n }\n // Symlinks, FIFOs, sockets, and device nodes are not syncable files.\n // Ignore them without poisoning delete-mode orphan handling for unrelated paths.\n if (entry.isDirectory()) {\n if (directoryMayContainSyncPaths(rel, options)) {\n await this.walk(root, fullPath, out, options, maxScanEntries, nodeDeps)\n }\n } else if (entry.isFile()) {\n if (!pathPassesSyncFilters(rel, options)) {\n if (pathSkippedByRegExpInputLimit(rel, options)) {\n emitScannerSkip(options, regexpInputTooLongSkip(rel))\n }\n continue\n }\n let s: LocalStats\n try {\n s = await nodeDeps.lstat(fullPath)\n /* v8 ignore start -- lstat race after a Dirent file result is not deterministic */\n if (!s.isFile()) {\n this.emitScanError(options, rel, 'file', new Error('not a regular file'))\n continue\n }\n /* v8 ignore stop */\n } catch (err) {\n /* v8 ignore next -- stat TOCTOU failures are not deterministic to trigger */\n this.emitScanError(options, relativePathFromRoot(root, fullPath, nodeDeps), 'file', err)\n continue\n }\n assertScanEntryLimit(out.length + 1, maxScanEntries)\n out.push({\n relativePath: rel,\n absolutePath: fullPath,\n modTimeMillis: Math.floor(s.mtimeMs),\n size: s.size,\n fileIdentity: localFileIdentityFromStats(s),\n })\n }\n }\n }\n\n private emitScanError(\n options: SyncScanOptions,\n path: string,\n kind: 'directory' | 'file',\n err: unknown,\n ): Error {\n const event: SyncErrorEvent = {\n type: 'error',\n path,\n size: 0,\n message: `failed to scan local ${kind}: ${localFilesystemErrorReason(err)}`,\n }\n options.onError?.(event)\n return new Error(event.message)\n }\n}\n\nasync function loadLocalNodeDeps(): Promise<LocalNodeDeps> {\n const [fsPromises, path] = await Promise.all([import('node:fs/promises'), import('node:path')])\n return {\n readdir: fsPromises.readdir as LocalNodeDeps['readdir'],\n lstat: fsPromises.lstat as LocalNodeDeps['lstat'],\n join: path.join,\n relative: path.relative,\n resolve: path.resolve,\n sep: path.sep,\n }\n}\n\nfunction resolvePathAtConstruction(root: string): string {\n const processLike = (\n globalThis as {\n process?: { cwd?: () => string; platform?: string }\n }\n ).process\n if (typeof processLike?.cwd !== 'function') return root\n\n const cwd = processLike.cwd()\n if (processLike.platform === 'win32') return resolveWindowsPath(cwd, root)\n return resolvePosixPath(cwd, root)\n}\n\nfunction resolvePosixPath(cwd: string, root: string): string {\n const joined = root.startsWith('/') ? root : `${cwd}/${root}`\n const resolved = normalizePathSegments(joined.split('/'), '/')\n return resolved === '' ? '/' : `/${resolved}`\n}\n\nfunction resolveWindowsPath(cwd: string, root: string): string {\n const normalizedRoot = root.replaceAll('/', '\\\\')\n const normalizedCwd = cwd.replaceAll('/', '\\\\')\n const drive = /^[A-Za-z]:/.exec(normalizedCwd)?.[0] ?? ''\n const cwdUnc = splitUncPath(normalizedCwd)\n if (/^\\\\\\\\/.test(normalizedRoot)) return normalizeUncPath(normalizedRoot)\n if (/^[A-Za-z]:\\\\/.test(normalizedRoot)) {\n const prefix = normalizedRoot.slice(0, 2)\n const rest = normalizedRoot.slice(3).split('\\\\')\n return joinWindowsRoot(prefix, normalizePathSegments(rest, '\\\\'))\n }\n if (/^[A-Za-z]:/.test(normalizedRoot)) {\n throw new Error('LocalFolder root must not be a drive-relative Windows path')\n }\n if (normalizedRoot.startsWith('\\\\')) {\n const rest = normalizePathSegments(normalizedRoot.slice(1).split('\\\\'), '\\\\')\n return joinWindowsRoot(cwdUnc?.prefix ?? drive, rest)\n }\n if (cwdUnc !== undefined) {\n const resolved = normalizePathSegments([...cwdUnc.rest, ...normalizedRoot.split('\\\\')], '\\\\')\n return joinWindowsRoot(cwdUnc.prefix, resolved)\n }\n const base = /^[A-Za-z]:\\\\/.test(normalizedCwd) ? normalizedCwd : `${drive}\\\\`\n const prefix = /^[A-Za-z]:/.exec(base)?.[0] ?? drive\n const baseRest = base.slice(prefix.length).replace(/^\\\\/, '').split('\\\\')\n const resolved = normalizePathSegments([...baseRest, ...normalizedRoot.split('\\\\')], '\\\\')\n return joinWindowsRoot(prefix, resolved)\n}\n\nfunction normalizeUncPath(path: string): string {\n const unc = splitUncPath(path)\n if (unc === undefined) return path\n return joinWindowsRoot(unc.prefix, normalizePathSegments(unc.rest, '\\\\'))\n}\n\nfunction splitUncPath(\n path: string,\n): { readonly prefix: string; readonly rest: string[] } | undefined {\n if (!path.startsWith('\\\\\\\\')) return undefined\n const parts = path.split('\\\\').filter((part) => part !== '')\n const [server, share, ...rest] = parts\n if (server === undefined || share === undefined) return undefined\n return { prefix: `\\\\\\\\${server}\\\\${share}`, rest }\n}\n\nfunction joinWindowsRoot(prefix: string, rest: string): string {\n return rest === '' ? `${prefix}\\\\` : `${prefix}\\\\${rest}`\n}\n\nfunction normalizePathSegments(segments: readonly string[], separator: '/' | '\\\\'): string {\n const out: string[] = []\n for (const segment of segments) {\n if (segment === '' || segment === '.') continue\n if (segment === '..') {\n out.pop()\n continue\n }\n out.push(segment)\n }\n return out.join(separator)\n}\n\nfunction relativePathFromRoot(root: string, path: string, nodeDeps: LocalNodeDeps): string {\n return nodeDeps.relative(root, path).split(nodeDeps.sep).join('/')\n}\n\nfunction throwIfScanAborted(options: SyncScanOptions): void {\n if (options.signal?.aborted === true) {\n throw options.signal.reason ?? new DOMException('Aborted', 'AbortError')\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"local.js","names":[],"sources":["../../../src/sync/scanners/local.ts"],"sourcesContent":["import {\n isDownloadStagingDirectorySegment,\n isManagedDownloadStagingRoot,\n} from '../download-staging.ts'\nimport { localFilesystemErrorReason } from '../filesystem-errors.ts'\nimport {\n directoryMayContainSyncPaths,\n pathPassesSyncFilters,\n pathSkippedByRegExpInputLimit,\n} from '../filters.ts'\nimport { localFileIdentityFromStats } from '../local-file-identity.ts'\nimport { registerLocalFilesystemRoot } from '../local-filesystem-root.ts'\nimport { compareSyncRelativePaths } from '../path-order.ts'\nimport { isReservedSyncTempFileName } from '../path-safety.ts'\nimport { validateSyncFilters } from '../regexp-safety.ts'\nimport { emitScannerSkip, regexpInputTooLongSkip } from '../scan-events.ts'\nimport { assertScanEntryLimit, scanEntryLimit } from '../scan-limit.ts'\nimport type { LocalSyncPath, SyncErrorEvent, SyncFolder, SyncScanOptions } from '../types.ts'\n\ntype LocalDirent = {\n readonly name: string\n isDirectory(): boolean\n isFile(): boolean\n isSymbolicLink(): boolean\n}\n\ntype LocalStats = {\n readonly dev: number\n readonly ino: number\n readonly mtimeMs: number\n readonly ctimeMs: number\n readonly size: number\n isDirectory(): boolean\n isFile(): boolean\n}\n\ntype LocalNodeDeps = {\n readdir(path: string, options: { readonly withFileTypes: true }): Promise<LocalDirent[]>\n lstat(path: string): Promise<LocalStats>\n stat(path: string): Promise<LocalStats>\n join(...paths: string[]): string\n relative(from: string, to: string): string\n realpath(path: string): Promise<string>\n resolve(...paths: string[]): string\n isAbsolute(path: string): boolean\n sep: string\n}\n\n/**\n * Scans a local directory tree and yields {@link LocalSyncPath} entries sorted by relative path.\n * A root directory read failure aborts the scan with an error diagnostic. Per-entry file or\n * directory failures are reported through `onError` and the scan continues over readable siblings.\n * SDK-managed partial download file names are skipped so unfinished internal\n * temp files are not synchronized.\n * The current implementation collects matching entries before sorting, so memory usage is\n * proportional to the number of matched files.\n */\nexport class LocalFolder implements SyncFolder {\n readonly type = 'local' as const\n readonly appliesScanFilters = true as const\n readonly appliesScanSorting = true as const\n /** Resolved absolute path to the local root directory. */\n readonly root: string\n\n /**\n * Creates a new LocalFolder for the given root directory.\n * @param root - Absolute or relative path to the local directory to scan.\n */\n constructor(root: string) {\n this.root = resolvePathAtConstruction(root)\n registerLocalFilesystemRoot(this)\n }\n\n /**\n * Recursively walks the directory and yields files in sync path order.\n * @param options - Optional scan controls.\n */\n async *scan(options: SyncScanOptions = {}): AsyncGenerator<LocalSyncPath> {\n validateSyncFilters(options)\n const nodeDeps = await loadLocalNodeDeps()\n const root = nodeDeps.resolve(this.root)\n const followSymlinks =\n options.localSymlinks === 'follow' && options.requireLocalSafePaths !== true\n let scanRoot = root\n const collected: LocalSyncPath[] = []\n const activeDirectoryIds = new Set<string>()\n const followedDirectoryIds = new Set<string>()\n if (followSymlinks) {\n try {\n scanRoot = await nodeDeps.realpath(root)\n const rootStats = await nodeDeps.stat(scanRoot)\n if (rootStats.isDirectory()) activeDirectoryIds.add(localNodeIdentity(rootStats))\n } catch {\n // Root read errors are reported by walk(), preserving the existing diagnostic shape.\n }\n }\n await this.walk(\n scanRoot,\n scanRoot,\n '',\n collected,\n options,\n scanEntryLimit(options),\n nodeDeps,\n activeDirectoryIds,\n followedDirectoryIds,\n )\n collected.sort((a, b) => compareSyncRelativePaths(a.relativePath, b.relativePath))\n for (const entry of collected) {\n throwIfScanAborted(options)\n yield entry\n }\n }\n\n /**\n * Recursively collects files from {@link dir} into {@link out}.\n * @param root - Resolved scan root used for relative path calculation.\n * @param dir - Absolute path of the directory to scan.\n * @param dirRel - Sync-relative path represented by {@link dir}.\n * @param out - Accumulator array that receives discovered file entries.\n * @param options - Optional scan controls.\n * @param maxScanEntries - Maximum number of entries to retain before failing.\n * @param nodeDeps - Lazy-loaded Node filesystem and path helpers.\n * @param activeDirectoryIds - Directory identities in the current recursion stack.\n * @param followedDirectoryIds - Followed symlink directory identities already traversed.\n */\n private async walk(\n root: string,\n dir: string,\n dirRel: string,\n out: LocalSyncPath[],\n options: SyncScanOptions,\n maxScanEntries: number,\n nodeDeps: LocalNodeDeps,\n activeDirectoryIds: Set<string>,\n followedDirectoryIds: Set<string>,\n ): Promise<void> {\n throwIfScanAborted(options)\n let entries: LocalDirent[]\n try {\n entries = await nodeDeps.readdir(dir, { withFileTypes: true })\n } catch (err) {\n const error = this.emitScanError(options, dirRel, 'directory', err)\n if (dir === root) throw error\n return\n }\n\n for (const entry of entries) {\n throwIfScanAborted(options)\n const fullPath = nodeDeps.join(dir, entry.name)\n const rel = joinSyncRelativePath(dirRel, entry.name)\n if (\n isDownloadStagingDirectorySegment(rel) &&\n entry.isDirectory() &&\n isDownloadStagingDirectorySegment(entry.name) &&\n (await isManagedDownloadStagingRoot(fullPath))\n ) {\n continue\n }\n if (rel.includes('\\\\')) {\n emitScannerSkip(options, {\n type: 'skip',\n path: rel,\n size: 0,\n reason: 'unsafe-name',\n message: `Skipped local path ${JSON.stringify(rel)}: backslashes are not safe sync path characters`,\n })\n continue\n }\n if (isReservedSyncTempFileName(entry.name)) {\n emitScannerSkip(options, {\n type: 'skip',\n path: rel,\n size: 0,\n reason: 'stale-download-partial',\n message: `Skipped local path ${JSON.stringify(rel)}: reserved SDK partial download file`,\n })\n continue\n }\n // Symlinks, FIFOs, sockets, and device nodes are not syncable files.\n // Ignore them without poisoning delete-mode orphan handling for unrelated paths.\n if (entry.isDirectory()) {\n if (directoryMayContainSyncPaths(rel, options)) {\n await this.walkDirectory(\n root,\n fullPath,\n rel,\n out,\n options,\n maxScanEntries,\n nodeDeps,\n activeDirectoryIds,\n followedDirectoryIds,\n )\n }\n } else if (entry.isFile()) {\n await this.collectFile(fullPath, rel, out, options, maxScanEntries, nodeDeps)\n } else if (entry.isSymbolicLink()) {\n await this.handleSymlink(\n root,\n fullPath,\n rel,\n out,\n options,\n maxScanEntries,\n nodeDeps,\n activeDirectoryIds,\n followedDirectoryIds,\n )\n }\n }\n }\n\n private async walkDirectory(\n root: string,\n dir: string,\n rel: string,\n out: LocalSyncPath[],\n options: SyncScanOptions,\n maxScanEntries: number,\n nodeDeps: LocalNodeDeps,\n activeDirectoryIds: Set<string>,\n followedDirectoryIds: Set<string>,\n knownStats?: LocalStats,\n ): Promise<void> {\n let stats = knownStats\n if (stats === undefined) {\n try {\n stats = await nodeDeps.stat(dir)\n } catch (err) {\n /* v8 ignore start -- stat TOCTOU failures after a Dirent directory result are not deterministic */\n this.emitScanError(options, rel, 'directory', err)\n return\n /* v8 ignore stop */\n }\n }\n /* v8 ignore start -- stat TOCTOU races after a Dirent directory result are not deterministic */\n if (!stats.isDirectory()) {\n this.emitScanError(options, rel, 'directory', new Error('not a directory'))\n return\n }\n /* v8 ignore stop */\n\n const identity = localNodeIdentity(stats)\n if (activeDirectoryIds.has(identity)) {\n emitScannerSkip(options, {\n type: 'skip',\n path: rel,\n size: 0,\n reason: 'local-symlink-loop',\n message: `Skipped local path ${JSON.stringify(rel)}: symlink would revisit an ancestor directory`,\n })\n return\n }\n\n activeDirectoryIds.add(identity)\n try {\n await this.walk(\n root,\n dir,\n rel,\n out,\n options,\n maxScanEntries,\n nodeDeps,\n activeDirectoryIds,\n followedDirectoryIds,\n )\n } finally {\n activeDirectoryIds.delete(identity)\n }\n }\n\n private async collectFile(\n fullPath: string,\n rel: string,\n out: LocalSyncPath[],\n options: SyncScanOptions,\n maxScanEntries: number,\n nodeDeps: LocalNodeDeps,\n knownStats?: LocalStats,\n ): Promise<void> {\n if (!pathPassesSyncFilters(rel, options)) {\n if (pathSkippedByRegExpInputLimit(rel, options)) {\n emitScannerSkip(options, regexpInputTooLongSkip(rel))\n }\n return\n }\n\n let s = knownStats\n if (s === undefined) {\n try {\n s = await nodeDeps.lstat(fullPath)\n /* v8 ignore start -- lstat race after a Dirent file result is not deterministic */\n if (!s.isFile()) {\n this.emitScanError(options, rel, 'file', new Error('not a regular file'))\n return\n }\n /* v8 ignore stop */\n } catch (err) {\n /* v8 ignore start -- stat TOCTOU failures are not deterministic to trigger */\n this.emitScanError(options, rel, 'file', err)\n return\n /* v8 ignore stop */\n }\n }\n\n assertScanEntryLimit(out.length + 1, maxScanEntries)\n out.push({\n relativePath: rel,\n absolutePath: fullPath,\n modTimeMillis: Math.floor(s.mtimeMs),\n size: s.size,\n fileIdentity: localFileIdentityFromStats(s),\n })\n }\n\n private async handleSymlink(\n root: string,\n fullPath: string,\n rel: string,\n out: LocalSyncPath[],\n options: SyncScanOptions,\n maxScanEntries: number,\n nodeDeps: LocalNodeDeps,\n activeDirectoryIds: Set<string>,\n followedDirectoryIds: Set<string>,\n ): Promise<void> {\n if ((options.localSymlinks ?? 'skip') === 'skip' || options.requireLocalSafePaths === true) {\n emitScannerSkip(options, {\n type: 'skip',\n path: rel,\n size: 0,\n reason: 'local-symlink',\n message: `Skipped local path ${JSON.stringify(rel)}: local symlinks are disabled`,\n })\n return\n }\n\n let targetPath: string\n let targetStats: LocalStats\n try {\n targetPath = await nodeDeps.realpath(fullPath)\n targetStats = await nodeDeps.stat(targetPath)\n } catch (err) {\n this.emitScanError(options, rel, 'symlink', err)\n return\n }\n\n if (!isPathInsideRoot(root, targetPath, nodeDeps)) {\n emitScannerSkip(options, {\n type: 'skip',\n path: rel,\n size: 0,\n reason: 'local-symlink',\n message: `Skipped local path ${JSON.stringify(rel)}: symlink target is outside the sync root`,\n })\n return\n }\n\n if (targetStats.isDirectory()) {\n if (directoryMayContainSyncPaths(rel, options)) {\n const identity = localNodeIdentity(targetStats)\n if (followedDirectoryIds.has(identity)) {\n emitScannerSkip(options, {\n type: 'skip',\n path: rel,\n size: 0,\n reason: 'local-symlink-loop',\n message: `Skipped local path ${JSON.stringify(rel)}: symlink target was already visited`,\n })\n return\n }\n followedDirectoryIds.add(identity)\n await this.walkDirectory(\n root,\n targetPath,\n rel,\n out,\n options,\n maxScanEntries,\n nodeDeps,\n activeDirectoryIds,\n followedDirectoryIds,\n targetStats,\n )\n }\n return\n }\n\n if (targetStats.isFile()) {\n await this.collectFile(targetPath, rel, out, options, maxScanEntries, nodeDeps, targetStats)\n return\n }\n\n emitScannerSkip(options, {\n type: 'skip',\n path: rel,\n size: 0,\n reason: 'local-symlink',\n message: `Skipped local path ${JSON.stringify(rel)}: symlink target is not a regular file or directory`,\n })\n }\n\n private emitScanError(\n options: SyncScanOptions,\n path: string,\n kind: 'directory' | 'file' | 'symlink',\n err: unknown,\n ): Error {\n const event: SyncErrorEvent = {\n type: 'error',\n path,\n size: 0,\n message: `failed to scan local ${kind}: ${localFilesystemErrorReason(err)}`,\n }\n options.onError?.(event)\n return new Error(event.message)\n }\n}\n\nasync function loadLocalNodeDeps(): Promise<LocalNodeDeps> {\n const [fsPromises, path] = await Promise.all([import('node:fs/promises'), import('node:path')])\n return {\n readdir: fsPromises.readdir as LocalNodeDeps['readdir'],\n lstat: fsPromises.lstat as LocalNodeDeps['lstat'],\n stat: fsPromises.stat as LocalNodeDeps['stat'],\n join: path.join,\n relative: path.relative,\n realpath: fsPromises.realpath,\n resolve: path.resolve,\n isAbsolute: path.isAbsolute,\n sep: path.sep,\n }\n}\n\nfunction resolvePathAtConstruction(root: string): string {\n const processLike = (\n globalThis as {\n process?: { cwd?: () => string; platform?: string }\n }\n ).process\n if (typeof processLike?.cwd !== 'function') return root\n\n const cwd = processLike.cwd()\n if (processLike.platform === 'win32') return resolveWindowsPath(cwd, root)\n return resolvePosixPath(cwd, root)\n}\n\nfunction resolvePosixPath(cwd: string, root: string): string {\n const joined = root.startsWith('/') ? root : `${cwd}/${root}`\n const resolved = normalizePathSegments(joined.split('/'), '/')\n return resolved === '' ? '/' : `/${resolved}`\n}\n\nfunction resolveWindowsPath(cwd: string, root: string): string {\n const normalizedRoot = root.replaceAll('/', '\\\\')\n const normalizedCwd = cwd.replaceAll('/', '\\\\')\n const drive = /^[A-Za-z]:/.exec(normalizedCwd)?.[0] ?? ''\n const cwdUnc = splitUncPath(normalizedCwd)\n if (/^\\\\\\\\/.test(normalizedRoot)) return normalizeUncPath(normalizedRoot)\n if (/^[A-Za-z]:\\\\/.test(normalizedRoot)) {\n const prefix = normalizedRoot.slice(0, 2)\n const rest = normalizedRoot.slice(3).split('\\\\')\n return joinWindowsRoot(prefix, normalizePathSegments(rest, '\\\\'))\n }\n if (/^[A-Za-z]:/.test(normalizedRoot)) {\n throw new Error('LocalFolder root must not be a drive-relative Windows path')\n }\n if (normalizedRoot.startsWith('\\\\')) {\n const rest = normalizePathSegments(normalizedRoot.slice(1).split('\\\\'), '\\\\')\n return joinWindowsRoot(cwdUnc?.prefix ?? drive, rest)\n }\n if (cwdUnc !== undefined) {\n const resolved = normalizePathSegments([...cwdUnc.rest, ...normalizedRoot.split('\\\\')], '\\\\')\n return joinWindowsRoot(cwdUnc.prefix, resolved)\n }\n const base = /^[A-Za-z]:\\\\/.test(normalizedCwd) ? normalizedCwd : `${drive}\\\\`\n const prefix = /^[A-Za-z]:/.exec(base)?.[0] ?? drive\n const baseRest = base.slice(prefix.length).replace(/^\\\\/, '').split('\\\\')\n const resolved = normalizePathSegments([...baseRest, ...normalizedRoot.split('\\\\')], '\\\\')\n return joinWindowsRoot(prefix, resolved)\n}\n\nfunction normalizeUncPath(path: string): string {\n const unc = splitUncPath(path)\n if (unc === undefined) return path\n return joinWindowsRoot(unc.prefix, normalizePathSegments(unc.rest, '\\\\'))\n}\n\nfunction splitUncPath(\n path: string,\n): { readonly prefix: string; readonly rest: string[] } | undefined {\n if (!path.startsWith('\\\\\\\\')) return undefined\n const parts = path.split('\\\\').filter((part) => part !== '')\n const [server, share, ...rest] = parts\n if (server === undefined || share === undefined) return undefined\n return { prefix: `\\\\\\\\${server}\\\\${share}`, rest }\n}\n\nfunction joinWindowsRoot(prefix: string, rest: string): string {\n return rest === '' ? `${prefix}\\\\` : `${prefix}\\\\${rest}`\n}\n\nfunction normalizePathSegments(segments: readonly string[], separator: '/' | '\\\\'): string {\n const out: string[] = []\n for (const segment of segments) {\n if (segment === '' || segment === '.') continue\n if (segment === '..') {\n out.pop()\n continue\n }\n out.push(segment)\n }\n return out.join(separator)\n}\n\nfunction joinSyncRelativePath(parent: string, child: string): string {\n return parent === '' ? child : `${parent}/${child}`\n}\n\nfunction isPathInsideRoot(root: string, path: string, nodeDeps: LocalNodeDeps): boolean {\n const rel = nodeDeps.relative(root, path)\n return (\n rel === '' ||\n (!rel.startsWith(`..${nodeDeps.sep}`) && rel !== '..' && !nodeDeps.isAbsolute(rel))\n )\n}\n\nfunction localNodeIdentity(stats: LocalStats): string {\n return `${stats.dev}:${stats.ino}`\n}\n\nfunction throwIfScanAborted(options: SyncScanOptions): void {\n if (options.signal?.aborted === true) {\n throw options.signal.reason ?? new DOMException('Aborted', 'AbortError')\n }\n}\n"],"mappings":"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"}
@@ -7,9 +7,10 @@ const require_sha1_options = require("./sha1-options.cjs");
7
7
  const require_local_sha1 = require("./local-sha1.cjs");
8
8
  const require_compare = require("./policies/compare.cjs");
9
9
  const require_index$1 = require("./policies/index.cjs");
10
+ const require_filesystem_errors = require("./filesystem-errors.cjs");
10
11
  const require_path_safety = require("./path-safety.cjs");
12
+ const require_download_staging = require("./download-staging.cjs");
11
13
  const require_prefix = require("./prefix.cjs");
12
- const require_filesystem_errors = require("./filesystem-errors.cjs");
13
14
  const require_local_filesystem_root = require("./local-filesystem-root.cjs");
14
15
  const require_b2_sha1_reader = require("./b2-sha1-reader.cjs");
15
16
  const require_local_file_io = require("./local-file-io.cjs");
@@ -64,6 +65,7 @@ async function* synchronize(config) {
64
65
  ...options.exclude !== void 0 ? { exclude: options.exclude } : {},
65
66
  ...options.signal !== void 0 ? { signal: options.signal } : {},
66
67
  ...options.maxScanEntries !== void 0 ? { maxScanEntries: options.maxScanEntries } : {},
68
+ ...options.localSymlinks !== void 0 ? { localSymlinks: options.localSymlinks } : {},
67
69
  ...direction === "b2-to-local" ? { requireLocalSafePaths: true } : {},
68
70
  onError: (event) => {
69
71
  scanHadError = true;
@@ -71,7 +73,10 @@ async function* synchronize(config) {
71
73
  queueEvent(event);
72
74
  }
73
75
  };
74
- const factory = createActionFactory(config, localRootContexts);
76
+ const downloadStagingRoots = await downloadStagingRootCleanupTargets(direction, localRootContexts);
77
+ const factory = createActionFactory(config, localRootContexts, { onDownloadStagingRoot(target) {
78
+ downloadStagingRoots.set(target.managedDirectory, target);
79
+ } });
75
80
  const readB2Sha1 = dryRun ? void 0 : createB2Sha1Reader(config);
76
81
  const actionAbortController = new AbortController();
77
82
  const removeAbortForwarder = forwardAbortSignal(options.signal, actionAbortController);
@@ -127,6 +132,7 @@ async function* synchronize(config) {
127
132
  if (!completed) abortActionController(actionAbortController, new DOMException("Sync iterator closed", "AbortError"));
128
133
  removeAbortForwarder();
129
134
  await drainActions();
135
+ await removeDownloadStagingRootMarkersAndDirectoriesAfterSync(downloadStagingRoots);
130
136
  }
131
137
  async function collectPairs() {
132
138
  const pairs = [];
@@ -145,6 +151,7 @@ async function* synchronize(config) {
145
151
  return pairs;
146
152
  }
147
153
  async function* emitSha1Batch(batch) {
154
+ /* v8 ignore next -- SHA-1 batches are sliced only while index is within pairs.length */
148
155
  if (batch.length === 0) return false;
149
156
  await drainActions();
150
157
  yield* emitQueuedEvents();
@@ -174,6 +181,7 @@ async function* synchronize(config) {
174
181
  return false;
175
182
  }
176
183
  async function processPreparedBatch(batch) {
184
+ /* v8 ignore next -- callers guard empty SHA-1 batches before preparing pairs */
177
185
  if (batch.length === 0) return {
178
186
  items: [],
179
187
  aborted: false
@@ -201,6 +209,7 @@ async function* synchronize(config) {
201
209
  function planPreparedPair(pair, prepared) {
202
210
  const event = {
203
211
  type: "compare",
212
+ /* v8 ignore next -- zipFolders never emits a pair with both sides absent */
204
213
  path: (pair[0] ?? pair[1])?.relativePath ?? "",
205
214
  size: 0,
206
215
  bytesHashed: prepared.bytesHashed,
@@ -224,9 +233,9 @@ async function* synchronize(config) {
224
233
  event,
225
234
  actions: [new require_index.SkipAction(prepared.pair[1].relativePath, "not removed because scan errors occurred")]
226
235
  };
227
- if (sourceInventoryIncomplete(scanEvents) && prepared.pair[0] === null && prepared.pair[1] !== null) return {
236
+ if (scanEvents.sourceInventoryIncompleteMessage !== void 0 && prepared.pair[0] === null && prepared.pair[1] !== null) return {
228
237
  event,
229
- actions: [new require_index.SkipAction(prepared.pair[1].relativePath, scanEvents.sourceInventoryIncompleteMessage ?? "not removed because the source scan skipped unsafe B2 names")]
238
+ actions: [new require_index.SkipAction(prepared.pair[1].relativePath, scanEvents.sourceInventoryIncompleteMessage)]
230
239
  };
231
240
  return {
232
241
  event,
@@ -288,6 +297,24 @@ async function* synchronize(config) {
288
297
  while (runningActions.size > 0) await Promise.race(runningActions);
289
298
  }
290
299
  }
300
+ async function downloadStagingRootCleanupTargets(direction, localRootContexts) {
301
+ const targets = /* @__PURE__ */ new Map();
302
+ if (direction !== "b2-to-local" || localRootContexts.dest === void 0) return targets;
303
+ const target = {
304
+ managedDirectory: (await import("node:path")).join(localRootContexts.dest.realPath, require_download_staging.DOWNLOAD_STAGING_DIRECTORY_NAME),
305
+ rootRealPath: localRootContexts.dest.realPath
306
+ };
307
+ targets.set(target.managedDirectory, target);
308
+ return targets;
309
+ }
310
+ async function removeDownloadStagingRootMarkersAndDirectoriesAfterSync(targets) {
311
+ if (targets.size === 0) return;
312
+ const path = await import("node:path");
313
+ for (const target of targets.values()) {
314
+ await synchronizerTestHooks.beforeDownloadStagingRootCleanup?.(target.managedDirectory);
315
+ await require_download_staging.removeDownloadStagingRootMarkerAndDirectory(target.rootRealPath, target.managedDirectory, path);
316
+ }
317
+ }
291
318
  /**
292
319
  * Normalizes user-provided sync concurrency before it controls compare batches and transfers.
293
320
  *
@@ -463,10 +490,11 @@ function toSseCDownloadKey(setting) {
463
490
  *
464
491
  * @param config - Synchronizer configuration containing source, destination, and options.
465
492
  * @param localRootContexts - Resolved filesystem roots captured before action creation.
493
+ * @param lifecycleHooks - Hooks used to report action-scoped cleanup state.
466
494
  *
467
495
  * @returns An action factory bound to the provided configuration.
468
496
  */
469
- function createActionFactory(config, localRootContexts) {
497
+ function createActionFactory(config, localRootContexts, lifecycleHooks = {}) {
470
498
  const upConfig = config;
471
499
  const downConfig = config;
472
500
  const uploadPrefix = require_prefix.asRawB2KeyPrefix(upConfig.prefix ?? b2FolderRawPrefix(config.dest) ?? "");
@@ -481,16 +509,16 @@ function createActionFactory(config, localRootContexts) {
481
509
  const root = rootContext?.root ?? upConfig.source.root ?? "";
482
510
  const fileName = dest !== void 0 ? validateB2SyncPathInPrefix(uploadPrefix, dest) : `${uploadPrefix}${relPath}`;
483
511
  if (rootContext !== void 0) await assertLocalRootContextCurrent(rootContext, relPath, { allowSymlinkRoot: true });
484
- const targetPath = rootContext === void 0 ? await resolveContainedLocalPath(root, source.relativePath, absPath) : await resolveContainedLocalPath(rootContext.realPath, source.relativePath);
485
- throwIfAborted(signal);
512
+ const targetPath = rootContext === void 0 ? await resolveContainedLocalPath(root, source.relativePath, absPath) : config.options.localSymlinks === "follow" ? await resolveContainedLocalPath(rootContext.realPath, source.relativePath, absPath) : await resolveContainedLocalPath(rootContext.realPath, source.relativePath);
513
+ throwIfAborted(actionSignal(signal));
486
514
  const fileSource = await createValidatedUploadFileSource(source, targetPath);
487
- throwIfAborted(signal);
515
+ throwIfAborted(actionSignal(signal));
488
516
  const serverSideEncryption = config.options.encryptionProvider?.getSettingForUpload(fileName, fileSource.size);
489
517
  await bucket.upload({
490
518
  fileName,
491
519
  source: fileSource,
492
520
  ...serverSideEncryption !== void 0 ? { serverSideEncryption } : {},
493
- ...signal !== void 0 ? { signal } : {}
521
+ signal: actionSignal(signal)
494
522
  });
495
523
  });
496
524
  },
@@ -509,14 +537,22 @@ function createActionFactory(config, localRootContexts) {
509
537
  const serverSideEncryption = toSseCDownloadKey(config.options.encryptionProvider?.getSettingForDownload(source.selectedVersion));
510
538
  const result = await bucket.file(b2FileName).downloadById(source.selectedVersion.fileId, {
511
539
  ...serverSideEncryption !== void 0 ? { serverSideEncryption } : {},
512
- ...signal !== void 0 ? { signal } : {}
540
+ signal: actionSignal(signal)
513
541
  });
514
542
  try {
515
543
  await require_local_file_io.writeLocalStreamInsideRoot(root, relPath, result.body, {
516
544
  expectedBytes,
517
545
  ...scannedDest !== void 0 ? { expectedDestination: scannedDest } : {},
518
546
  idleTimeoutMillis,
519
- ...signal !== void 0 ? { signal } : {}
547
+ ...lifecycleHooks.onDownloadStagingRoot !== void 0 ? { onDownloadStagingRoot: (managedDirectory) => {
548
+ const rootRealPath = localRootContexts.dest?.realPath;
549
+ if (rootRealPath === void 0) return;
550
+ lifecycleHooks.onDownloadStagingRoot?.({
551
+ managedDirectory,
552
+ rootRealPath
553
+ });
554
+ } } : {},
555
+ signal: actionSignal(signal)
520
556
  });
521
557
  } catch (err) {
522
558
  await cancelReadableStreamBody(result.body, err);
@@ -534,7 +570,7 @@ function createActionFactory(config, localRootContexts) {
534
570
  const bucket = upConfig.bucket ?? downConfig.bucket;
535
571
  assertBucket(bucket, "hide");
536
572
  return new require_index.HideAction(path, async (_relPath, signal) => {
537
- await bucket.hideFile(`${uploadPrefix}${path}`, signal === void 0 ? void 0 : { signal });
573
+ await bucket.hideFile(`${uploadPrefix}${path}`, { signal: actionSignal(signal) });
538
574
  });
539
575
  },
540
576
  hideB2Path(path) {
@@ -542,7 +578,7 @@ function createActionFactory(config, localRootContexts) {
542
578
  assertBucket(bucket, "hide");
543
579
  const b2FileName = validateB2SyncPathInPrefix(uploadPrefix, path);
544
580
  return new require_index.HideAction(path.relativePath, async (_relPath, signal) => {
545
- await bucket.hideFile(b2FileName, signal === void 0 ? void 0 : { signal });
581
+ await bucket.hideFile(b2FileName, { signal: actionSignal(signal) });
546
582
  });
547
583
  },
548
584
  deleteRemote(path) {
@@ -550,16 +586,16 @@ function createActionFactory(config, localRootContexts) {
550
586
  assertBucket(bucket, "delete");
551
587
  const b2FileName = validateB2SyncPathInPrefix(uploadPrefix, path);
552
588
  return new require_index.DeleteRemoteAction(path.relativePath, path.selectedVersion.fileId, async (fileId$1, _fileName, signal) => {
553
- await bucket.deleteFileVersion(b2FileName, require_ids.fileId(fileId$1), signal === void 0 ? void 0 : { signal });
589
+ await bucket.deleteFileVersion(b2FileName, require_ids.fileId(fileId$1), { signal: actionSignal(signal) });
554
590
  });
555
591
  },
556
592
  deleteLocal(path) {
557
593
  const rootContext = localRootContexts.dest;
558
594
  const root = rootContext?.root ?? localSyncRoot(downConfig.dest);
559
595
  return new require_index.DeleteLocalAction(path.relativePath, path.absolutePath, async (absPath, signal) => {
560
- signal?.throwIfAborted();
596
+ actionSignal(signal).throwIfAborted();
561
597
  if (absPath !== path.absolutePath) throw new Error(`Refusing to delete outside sync root: ${path.relativePath}`);
562
- signal?.throwIfAborted();
598
+ actionSignal(signal).throwIfAborted();
563
599
  try {
564
600
  if (rootContext !== void 0) await assertLocalRootContextCurrent(rootContext, path.relativePath, { allowSymlinkRoot: false });
565
601
  await require_local_file_io.deleteLocalFileInsideRoot(root, path);
@@ -585,7 +621,7 @@ function createActionFactory(config, localRootContexts) {
585
621
  fileName: targetPath,
586
622
  ...destinationServerSideEncryption !== void 0 ? { destinationServerSideEncryption } : {},
587
623
  ...sourceServerSideEncryption !== void 0 ? { sourceServerSideEncryption } : {},
588
- ...signal !== void 0 ? { signal } : {}
624
+ signal: actionSignal(signal)
589
625
  });
590
626
  });
591
627
  }
@@ -651,10 +687,15 @@ function validateB2SyncPathInPrefix(prefix, path, operation = "mutate") {
651
687
  function isB2SyncPath(path) {
652
688
  return "selectedVersion" in path;
653
689
  }
690
+ var defaultActionSignal = new AbortController().signal;
691
+ function actionSignal(signal) {
692
+ return signal ?? defaultActionSignal;
693
+ }
654
694
  function throwIfAborted(signal) {
655
- if (signal?.aborted === true) throw signal.reason ?? new DOMException("Aborted", "AbortError");
695
+ if (signal.aborted) throw signal.reason ?? new DOMException("Aborted", "AbortError");
656
696
  }
657
697
  async function cancelReadableStreamBody(body, reason) {
698
+ /* v8 ignore next -- writeLocalStreamInsideRoot releases the body reader before throwing */
658
699
  if (body.locked) return;
659
700
  try {
660
701
  await body.cancel(reason);
@@ -704,10 +745,7 @@ function bufferScanEvent(buffer, event, direction, scanSide) {
704
745
  else buffer.dropped++;
705
746
  }
706
747
  function* drainScanEvents(buffer) {
707
- while (buffer.events.length > 0) {
708
- const event = buffer.events.shift();
709
- if (event) yield event;
710
- }
748
+ while (buffer.events.length > 0) yield buffer.events.shift();
711
749
  if (buffer.dropped > 0) {
712
750
  yield {
713
751
  type: "skip",
@@ -722,9 +760,6 @@ function* drainScanEvents(buffer) {
722
760
  function scanHadFilesystemError(scanEvents) {
723
761
  return scanEvents.fatalFilesystemErrorMessage !== void 0;
724
762
  }
725
- function sourceInventoryIncomplete(scanEvents) {
726
- return scanEvents.sourceInventoryIncompleteMessage !== void 0;
727
- }
728
763
  function scanFilesystemError(scanEvents) {
729
764
  return scanEvents.fatalFilesystemErrorMessage === void 0 ? void 0 : new Error(scanEvents.fatalFilesystemErrorMessage);
730
765
  }
@@ -743,14 +778,26 @@ function isLocalDeleteSafetyError(err) {
743
778
  }
744
779
  async function resolveContainedLocalPath(root, relativePath, absolutePath) {
745
780
  if (root === "") throw new Error("Local sync root required for filesystem mutation");
781
+ const { realpath } = await import("node:fs/promises");
746
782
  const { isAbsolute, relative, resolve, sep } = await import("node:path");
747
783
  const safeRoot = resolve(root);
748
784
  const target = absolutePath === void 0 ? resolve(safeRoot, relativePath) : resolve(absolutePath);
749
- const pathFromRoot = relative(safeRoot, target);
785
+ let containmentRoot = safeRoot;
786
+ let pathFromRoot = relative(containmentRoot, target);
787
+ let escapesRoot = pathFromRoot === ".." || pathFromRoot.startsWith(`..${sep}`) || isAbsolute(pathFromRoot);
788
+ if (escapesRoot && absolutePath !== void 0) {
789
+ const realRoot = await realpath(safeRoot).catch(() => safeRoot);
790
+ const realPathFromRoot = relative(realRoot, target);
791
+ if (!(realPathFromRoot === ".." || realPathFromRoot.startsWith(`..${sep}`) || isAbsolute(realPathFromRoot))) {
792
+ containmentRoot = realRoot;
793
+ pathFromRoot = realPathFromRoot;
794
+ escapesRoot = false;
795
+ }
796
+ }
750
797
  /* v8 ignore next -- defense-in-depth after prior no-follow and symlink checks. */
751
- if (pathFromRoot === ".." || pathFromRoot.startsWith(`..${sep}`) || isAbsolute(pathFromRoot)) throw new Error(`Refusing to access path outside sync root: ${relativePath}`);
752
- await assertLocalRootHasNoSymlink(safeRoot, relativePath);
753
- await assertPathHasNoSymlinkComponents(safeRoot, pathFromRoot, relativePath);
798
+ if (escapesRoot) throw new Error(`Refusing to access path outside sync root: ${relativePath}`);
799
+ await assertLocalRootHasNoSymlink(containmentRoot, relativePath);
800
+ await assertPathHasNoSymlinkComponents(containmentRoot, pathFromRoot, relativePath);
754
801
  return target;
755
802
  }
756
803
  async function assertLocalRootHasNoSymlink(safeRoot, relativePath) {