@storyshelf/core 0.6.2 → 0.8.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 (727) hide show
  1. package/README.md +49 -32
  2. package/dist/adapter/capture-queue.d.mts +44 -4
  3. package/dist/adapter/capture-runner.d.mts +134 -12
  4. package/dist/adapter/capture-runner.mjs +9 -12
  5. package/dist/adapter/database.d.mts +89 -10
  6. package/dist/adapter/database.mjs +4 -3
  7. package/dist/adapter/database.mjs.map +1 -1
  8. package/dist/adapter/design.d.mts +83 -0
  9. package/dist/adapter/design.mjs +27 -0
  10. package/dist/adapter/design.mjs.map +1 -0
  11. package/dist/adapter/email-sender.d.mts +0 -2
  12. package/dist/adapter/metadata.d.mts +3 -12
  13. package/dist/adapter/notifier.d.mts +6 -3
  14. package/dist/adapter/notifier.mjs +5 -1
  15. package/dist/adapter/oauth.d.mts +50 -0
  16. package/dist/adapter/setup.d.mts +4 -7
  17. package/dist/adapter/setup.mjs +3 -3
  18. package/dist/adapter/setup.mjs.map +1 -1
  19. package/dist/adapter/storage.d.mts +1 -3
  20. package/dist/adapter/webhook-events.d.mts +0 -2
  21. package/dist/adapter/webhook-events.mjs +13 -14
  22. package/dist/adapter/webhook-events.mjs.map +1 -1
  23. package/dist/adapters/capture-target-offers.d.mts +26 -0
  24. package/dist/adapters/capture-target-offers.mjs +57 -0
  25. package/dist/adapters/capture-target-offers.mjs.map +1 -0
  26. package/dist/adapters/capture-target.d.mts +69 -0
  27. package/dist/adapters/capture-target.mjs +75 -0
  28. package/dist/adapters/capture-target.mjs.map +1 -0
  29. package/dist/adapters/notifier/build-comment.d.mts +22 -0
  30. package/dist/adapters/notifier/build-comment.mjs +23 -0
  31. package/dist/adapters/notifier/build-comment.mjs.map +1 -0
  32. package/dist/adapters/notifier/build-state.d.mts +97 -0
  33. package/dist/adapters/notifier/build-state.mjs +217 -0
  34. package/dist/adapters/notifier/build-state.mjs.map +1 -0
  35. package/dist/adapters/notifier/events.d.mts +4 -4
  36. package/dist/adapters/notifier/events.mjs +32 -20
  37. package/dist/adapters/notifier/events.mjs.map +1 -1
  38. package/dist/adapters/notifier/format.d.mts +0 -2
  39. package/dist/adapters/notifier/format.mjs +1 -2
  40. package/dist/adapters/notifier/provider.d.mts +53 -9
  41. package/dist/adapters/notifier/provider.mjs +11 -0
  42. package/dist/adapters/notifier/provider.mjs.map +1 -0
  43. package/dist/adapters/notifier/state.d.mts +22 -0
  44. package/dist/adapters/notifier/state.mjs +65 -0
  45. package/dist/adapters/notifier/state.mjs.map +1 -0
  46. package/dist/adapters/notifier/types.d.mts +51 -4
  47. package/dist/adapters/remote-browser.d.mts +168 -0
  48. package/dist/adapters/remote-browser.mjs +201 -0
  49. package/dist/adapters/remote-browser.mjs.map +1 -0
  50. package/dist/adapters/remote-lease-attempt.mjs +39 -0
  51. package/dist/adapters/remote-lease-attempt.mjs.map +1 -0
  52. package/dist/adapters/remote-lease-error.d.mts +25 -0
  53. package/dist/adapters/remote-lease-error.mjs +54 -0
  54. package/dist/adapters/remote-lease-error.mjs.map +1 -0
  55. package/dist/adapters/remote-lease-held.d.mts +15 -0
  56. package/dist/adapters/remote-lease-held.mjs +86 -0
  57. package/dist/adapters/remote-lease-held.mjs.map +1 -0
  58. package/dist/adapters/remote-lease-shutdown.d.mts +12 -0
  59. package/dist/adapters/remote-lease-shutdown.mjs +32 -0
  60. package/dist/adapters/remote-lease-shutdown.mjs.map +1 -0
  61. package/dist/adapters/remote-lease.d.mts +39 -0
  62. package/dist/adapters/remote-lease.mjs +175 -0
  63. package/dist/adapters/remote-lease.mjs.map +1 -0
  64. package/dist/adapters/remote-limiter.mjs +121 -0
  65. package/dist/adapters/remote-limiter.mjs.map +1 -0
  66. package/dist/adapters/remote-pool.d.mts +56 -0
  67. package/dist/adapters/remote-pool.mjs +195 -0
  68. package/dist/adapters/remote-pool.mjs.map +1 -0
  69. package/dist/adapters/render-runs.d.mts +23 -0
  70. package/dist/adapters/render-runs.mjs +40 -0
  71. package/dist/adapters/render-runs.mjs.map +1 -0
  72. package/dist/adapters/setup-runner.d.mts +0 -2
  73. package/dist/adapters/setup-runner.mjs +3 -6
  74. package/dist/adapters/setup-runner.mjs.map +1 -1
  75. package/dist/adapters/sources.d.mts +10 -0
  76. package/dist/adapters/sources.mjs +19 -0
  77. package/dist/adapters/sources.mjs.map +1 -0
  78. package/dist/adapters/validate.d.mts +0 -2
  79. package/dist/adapters/validate.mjs +27 -20
  80. package/dist/adapters/validate.mjs.map +1 -1
  81. package/dist/ai.d.mts +103 -0
  82. package/dist/ai.mjs +18 -0
  83. package/dist/ai.mjs.map +1 -0
  84. package/dist/auth.d.mts +0 -2
  85. package/dist/auth.mjs +1 -2
  86. package/dist/brand-packs.d.mts +0 -2
  87. package/dist/brand-packs.mjs +0 -1
  88. package/dist/capture/a11y/axe-source.d.mts +16 -0
  89. package/dist/capture/a11y/axe-source.mjs +36 -0
  90. package/dist/capture/a11y/axe-source.mjs.map +1 -0
  91. package/dist/capture/a11y/in-page.d.mts +25 -0
  92. package/dist/capture/a11y/in-page.mjs +59 -0
  93. package/dist/capture/a11y/in-page.mjs.map +1 -0
  94. package/dist/capture/a11y/map.d.mts +22 -0
  95. package/dist/capture/a11y/map.mjs +47 -0
  96. package/dist/capture/a11y/map.mjs.map +1 -0
  97. package/dist/capture/a11y/options.d.mts +21 -0
  98. package/dist/capture/a11y/options.mjs +74 -0
  99. package/dist/capture/a11y/options.mjs.map +1 -0
  100. package/dist/capture/adapter.d.mts +46 -5
  101. package/dist/capture/adapter.mjs +1 -2
  102. package/dist/capture/adapter.mjs.map +1 -1
  103. package/dist/capture/affected.d.mts +2 -2
  104. package/dist/capture/affected.mjs +14 -6
  105. package/dist/capture/affected.mjs.map +1 -1
  106. package/dist/capture/attempt-logger.mjs +83 -0
  107. package/dist/capture/attempt-logger.mjs.map +1 -0
  108. package/dist/capture/browsers.d.mts +33 -0
  109. package/dist/capture/browsers.mjs +66 -0
  110. package/dist/capture/browsers.mjs.map +1 -0
  111. package/dist/capture/build-state/deliver.d.mts +30 -0
  112. package/dist/capture/build-state/deliver.mjs +117 -0
  113. package/dist/capture/build-state/deliver.mjs.map +1 -0
  114. package/dist/capture/build-state/index.d.mts +8 -0
  115. package/dist/capture/build-state/notify-job.d.mts +54 -0
  116. package/dist/capture/build-state/notify-job.mjs +132 -0
  117. package/dist/capture/build-state/notify-job.mjs.map +1 -0
  118. package/dist/capture/build-state/publisher.d.mts +21 -0
  119. package/dist/capture/build-state/publisher.mjs +106 -0
  120. package/dist/capture/build-state/publisher.mjs.map +1 -0
  121. package/dist/capture/build-state/resume.d.mts +7 -0
  122. package/dist/capture/build-state/resume.mjs +46 -0
  123. package/dist/capture/build-state/resume.mjs.map +1 -0
  124. package/dist/capture/build-state/types.d.mts +47 -0
  125. package/dist/capture/catalog/catalogue.d.mts +20 -0
  126. package/dist/capture/catalog/catalogue.mjs +52 -0
  127. package/dist/capture/catalog/catalogue.mjs.map +1 -0
  128. package/dist/capture/catalog/components.d.mts +21 -0
  129. package/dist/capture/catalog/components.mjs +54 -0
  130. package/dist/capture/catalog/components.mjs.map +1 -0
  131. package/dist/capture/catalog/history.d.mts +28 -0
  132. package/dist/capture/catalog/history.mjs +50 -0
  133. package/dist/capture/catalog/history.mjs.map +1 -0
  134. package/dist/capture/catalog/hook.d.mts +12 -0
  135. package/dist/capture/catalog/hook.mjs +18 -0
  136. package/dist/capture/catalog/hook.mjs.map +1 -0
  137. package/dist/capture/catalog/index.d.mts +11 -0
  138. package/dist/capture/catalog/load.d.mts +24 -0
  139. package/dist/capture/catalog/load.mjs +48 -0
  140. package/dist/capture/catalog/load.mjs.map +1 -0
  141. package/dist/capture/catalog/parse.d.mts +13 -0
  142. package/dist/capture/catalog/parse.mjs +55 -0
  143. package/dist/capture/catalog/parse.mjs.map +1 -0
  144. package/dist/capture/catalog/read.d.mts +15 -0
  145. package/dist/capture/catalog/read.mjs +39 -0
  146. package/dist/capture/catalog/read.mjs.map +1 -0
  147. package/dist/capture/catalog/types.d.mts +31 -0
  148. package/dist/capture/comparison-reference.d.mts +27 -0
  149. package/dist/capture/comparison-reference.mjs +75 -0
  150. package/dist/capture/comparison-reference.mjs.map +1 -0
  151. package/dist/capture/consumer/capture-handler.d.mts +56 -0
  152. package/dist/capture/consumer/capture-handler.mjs +117 -0
  153. package/dist/capture/consumer/capture-handler.mjs.map +1 -0
  154. package/dist/capture/consumer/idle.d.mts +15 -0
  155. package/dist/capture/consumer/idle.mjs +24 -0
  156. package/dist/capture/consumer/idle.mjs.map +1 -0
  157. package/dist/capture/consumer/index.d.mts +11 -0
  158. package/dist/capture/consumer/index.mjs +138 -0
  159. package/dist/capture/consumer/index.mjs.map +1 -0
  160. package/dist/capture/consumer/loop.mjs +109 -0
  161. package/dist/capture/consumer/loop.mjs.map +1 -0
  162. package/dist/capture/consumer/metrics.d.mts +39 -0
  163. package/dist/capture/consumer/metrics.mjs +37 -0
  164. package/dist/capture/consumer/metrics.mjs.map +1 -0
  165. package/dist/capture/consumer/retry.d.mts +31 -0
  166. package/dist/capture/consumer/retry.mjs +33 -0
  167. package/dist/capture/consumer/retry.mjs.map +1 -0
  168. package/dist/capture/consumer/run-job.mjs +194 -0
  169. package/dist/capture/consumer/run-job.mjs.map +1 -0
  170. package/dist/capture/consumer/startup.mjs +37 -0
  171. package/dist/capture/consumer/startup.mjs.map +1 -0
  172. package/dist/capture/consumer/types.d.mts +95 -0
  173. package/dist/capture/crash-failure.d.mts +23 -0
  174. package/dist/capture/crash-failure.mjs +69 -0
  175. package/dist/capture/crash-failure.mjs.map +1 -0
  176. package/dist/capture/db-queue/cadence.d.mts +30 -0
  177. package/dist/capture/db-queue/cadence.mjs +37 -0
  178. package/dist/capture/db-queue/cadence.mjs.map +1 -0
  179. package/dist/capture/db-queue/claim.d.mts +7 -0
  180. package/dist/capture/db-queue/claim.mjs +148 -0
  181. package/dist/capture/db-queue/claim.mjs.map +1 -0
  182. package/dist/capture/db-queue/clock.mjs +11 -0
  183. package/dist/capture/db-queue/clock.mjs.map +1 -0
  184. package/dist/capture/db-queue/codec.mjs +75 -0
  185. package/dist/capture/db-queue/codec.mjs.map +1 -0
  186. package/dist/capture/db-queue/index.d.mts +18 -0
  187. package/dist/capture/db-queue/index.mjs +89 -0
  188. package/dist/capture/db-queue/index.mjs.map +1 -0
  189. package/dist/capture/db-queue/keeper.d.mts +34 -0
  190. package/dist/capture/db-queue/keeper.mjs +91 -0
  191. package/dist/capture/db-queue/keeper.mjs.map +1 -0
  192. package/dist/capture/db-queue/lease.mjs +45 -0
  193. package/dist/capture/db-queue/lease.mjs.map +1 -0
  194. package/dist/capture/db-queue/lifecycle.mjs +35 -0
  195. package/dist/capture/db-queue/lifecycle.mjs.map +1 -0
  196. package/dist/capture/db-queue/operations.mjs +93 -0
  197. package/dist/capture/db-queue/operations.mjs.map +1 -0
  198. package/dist/capture/db-queue/poll-loop.d.mts +28 -0
  199. package/dist/capture/db-queue/poll-loop.mjs +88 -0
  200. package/dist/capture/db-queue/poll-loop.mjs.map +1 -0
  201. package/dist/capture/db-queue/poll.d.mts +8 -0
  202. package/dist/capture/db-queue/poll.mjs +68 -0
  203. package/dist/capture/db-queue/poll.mjs.map +1 -0
  204. package/dist/capture/db-queue/timers.d.mts +10 -0
  205. package/dist/capture/db-queue/timers.mjs +36 -0
  206. package/dist/capture/db-queue/timers.mjs.map +1 -0
  207. package/dist/capture/db-queue/types.d.mts +81 -0
  208. package/dist/capture/db-queue/types.mjs +11 -0
  209. package/dist/capture/db-queue/types.mjs.map +1 -0
  210. package/dist/capture/db-queue/wake.mjs +47 -0
  211. package/dist/capture/db-queue/wake.mjs.map +1 -0
  212. package/dist/capture/design.mjs +38 -0
  213. package/dist/capture/design.mjs.map +1 -0
  214. package/dist/capture/diff-record.mjs +24 -5
  215. package/dist/capture/diff-record.mjs.map +1 -1
  216. package/dist/capture/dispatch.d.mts +13 -10
  217. package/dist/capture/dispatch.mjs +102 -111
  218. package/dist/capture/dispatch.mjs.map +1 -1
  219. package/dist/capture/enrich.mjs +59 -0
  220. package/dist/capture/enrich.mjs.map +1 -0
  221. package/dist/capture/failure-sets.mjs +21 -0
  222. package/dist/capture/failure-sets.mjs.map +1 -0
  223. package/dist/capture/fingerprint.d.mts +40 -0
  224. package/dist/capture/fingerprint.mjs +82 -0
  225. package/dist/capture/fingerprint.mjs.map +1 -0
  226. package/dist/capture/forked/channel.d.mts +23 -0
  227. package/dist/capture/forked/child.d.mts +32 -0
  228. package/dist/capture/forked/child.mjs +201 -0
  229. package/dist/capture/forked/child.mjs.map +1 -0
  230. package/dist/capture/forked/crash-tracker.mjs +22 -0
  231. package/dist/capture/forked/crash-tracker.mjs.map +1 -0
  232. package/dist/capture/forked/decision.d.mts +24 -0
  233. package/dist/capture/forked/decision.mjs +26 -0
  234. package/dist/capture/forked/decision.mjs.map +1 -0
  235. package/dist/capture/forked/exit.mjs +11 -0
  236. package/dist/capture/forked/exit.mjs.map +1 -0
  237. package/dist/capture/forked/fork.d.mts +29 -0
  238. package/dist/capture/forked/fork.mjs +92 -0
  239. package/dist/capture/forked/fork.mjs.map +1 -0
  240. package/dist/capture/forked/forked-capture.d.mts +38 -0
  241. package/dist/capture/forked/forked-capture.mjs +239 -0
  242. package/dist/capture/forked/forked-capture.mjs.map +1 -0
  243. package/dist/capture/forked/lease-book.mjs +45 -0
  244. package/dist/capture/forked/lease-book.mjs.map +1 -0
  245. package/dist/capture/forked/protocol.d.mts +36 -0
  246. package/dist/capture/forked/protocol.mjs +24 -0
  247. package/dist/capture/forked/protocol.mjs.map +1 -0
  248. package/dist/capture/forked/runs.mjs +74 -0
  249. package/dist/capture/forked/runs.mjs.map +1 -0
  250. package/dist/capture/guards.mjs +1 -2
  251. package/dist/capture/ignore-regions.d.mts +36 -0
  252. package/dist/capture/ignore-regions.mjs +65 -0
  253. package/dist/capture/ignore-regions.mjs.map +1 -0
  254. package/dist/capture/isolate.mjs +140 -0
  255. package/dist/capture/isolate.mjs.map +1 -0
  256. package/dist/capture/job-lease.d.mts +26 -0
  257. package/dist/capture/job-lease.mjs +56 -0
  258. package/dist/capture/job-lease.mjs.map +1 -0
  259. package/dist/capture/matrix.mjs +298 -0
  260. package/dist/capture/matrix.mjs.map +1 -0
  261. package/dist/capture/modes.d.mts +23 -0
  262. package/dist/capture/modes.mjs +106 -0
  263. package/dist/capture/modes.mjs.map +1 -0
  264. package/dist/capture/orchestrator.d.mts +37 -7
  265. package/dist/capture/orchestrator.mjs +120 -110
  266. package/dist/capture/orchestrator.mjs.map +1 -1
  267. package/dist/capture/pipeline.d.mts +9 -4
  268. package/dist/capture/pipeline.mjs +284 -48
  269. package/dist/capture/pipeline.mjs.map +1 -1
  270. package/dist/capture/progress.d.mts +9 -0
  271. package/dist/capture/progress.mjs +27 -0
  272. package/dist/capture/progress.mjs.map +1 -0
  273. package/dist/capture/publish/accept.d.mts +37 -0
  274. package/dist/capture/publish/accept.mjs +27 -0
  275. package/dist/capture/publish/accept.mjs.map +1 -0
  276. package/dist/capture/publish/complete.d.mts +44 -0
  277. package/dist/capture/publish/complete.mjs +63 -0
  278. package/dist/capture/publish/complete.mjs.map +1 -0
  279. package/dist/capture/publish/extract.d.mts +18 -0
  280. package/dist/capture/publish/extract.mjs +120 -0
  281. package/dist/capture/publish/extract.mjs.map +1 -0
  282. package/dist/capture/publish/handler.d.mts +26 -0
  283. package/dist/capture/publish/handler.mjs +61 -0
  284. package/dist/capture/publish/handler.mjs.map +1 -0
  285. package/dist/capture/publish/index.d.mts +11 -0
  286. package/dist/capture/publish/invalid-zip.d.mts +8 -0
  287. package/dist/capture/publish/invalid-zip.mjs +11 -0
  288. package/dist/capture/publish/invalid-zip.mjs.map +1 -0
  289. package/dist/capture/publish/limits.d.mts +35 -0
  290. package/dist/capture/publish/limits.mjs +38 -0
  291. package/dist/capture/publish/limits.mjs.map +1 -0
  292. package/dist/capture/publish/published.d.mts +13 -0
  293. package/dist/capture/publish/published.mjs +17 -0
  294. package/dist/capture/publish/published.mjs.map +1 -0
  295. package/dist/capture/publish/run.d.mts +31 -0
  296. package/dist/capture/publish/run.mjs +29 -0
  297. package/dist/capture/publish/run.mjs.map +1 -0
  298. package/dist/capture/rediff.d.mts +1 -3
  299. package/dist/capture/rediff.mjs +23 -9
  300. package/dist/capture/rediff.mjs.map +1 -1
  301. package/dist/capture/render.mjs +52 -0
  302. package/dist/capture/render.mjs.map +1 -0
  303. package/dist/capture/scratch.d.mts +42 -0
  304. package/dist/capture/scratch.mjs +136 -0
  305. package/dist/capture/scratch.mjs.map +1 -0
  306. package/dist/capture/shard-plan.mjs +127 -0
  307. package/dist/capture/shard-plan.mjs.map +1 -0
  308. package/dist/capture/shards/context.mjs +51 -0
  309. package/dist/capture/shards/context.mjs.map +1 -0
  310. package/dist/capture/shards/execute.mjs +175 -0
  311. package/dist/capture/shards/execute.mjs.map +1 -0
  312. package/dist/capture/shards/fan-out.mjs +125 -0
  313. package/dist/capture/shards/fan-out.mjs.map +1 -0
  314. package/dist/capture/shards/history.mjs +34 -0
  315. package/dist/capture/shards/history.mjs.map +1 -0
  316. package/dist/capture/shards/reduce.mjs +96 -0
  317. package/dist/capture/shards/reduce.mjs.map +1 -0
  318. package/dist/capture/sizing.d.mts +0 -2
  319. package/dist/capture/sizing.mjs +2 -13
  320. package/dist/capture/sizing.mjs.map +1 -1
  321. package/dist/capture/skip-checks.mjs +3 -67
  322. package/dist/capture/skip-checks.mjs.map +1 -1
  323. package/dist/capture/statics.d.mts +7 -10
  324. package/dist/capture/statics.mjs +26 -74
  325. package/dist/capture/statics.mjs.map +1 -1
  326. package/dist/capture/storybook-urls.d.mts +38 -0
  327. package/dist/capture/storybook-urls.mjs +41 -0
  328. package/dist/capture/storybook-urls.mjs.map +1 -0
  329. package/dist/capture/storybook.d.mts +12 -6
  330. package/dist/capture/storybook.mjs +63 -19
  331. package/dist/capture/storybook.mjs.map +1 -1
  332. package/dist/capture/target.mjs +20 -0
  333. package/dist/capture/target.mjs.map +1 -0
  334. package/dist/capture/targets.d.mts +23 -0
  335. package/dist/capture/targets.mjs +31 -0
  336. package/dist/capture/targets.mjs.map +1 -0
  337. package/dist/capture/tests.d.mts +35 -0
  338. package/dist/capture/tests.mjs +136 -0
  339. package/dist/capture/tests.mjs.map +1 -0
  340. package/dist/capture/token-chunks.mjs +91 -0
  341. package/dist/capture/token-chunks.mjs.map +1 -0
  342. package/dist/capture.d.mts +63 -7
  343. package/dist/capture.mjs +51 -3
  344. package/dist/config.d.mts +136 -14
  345. package/dist/config.mjs +14 -8
  346. package/dist/config.mjs.map +1 -1
  347. package/dist/diff.d.mts +7 -3
  348. package/dist/diff.mjs +15 -3
  349. package/dist/diff.mjs.map +1 -0
  350. package/dist/index.d.mts +3 -3
  351. package/dist/index.mjs +2 -2
  352. package/dist/insights/budget.d.mts +18 -0
  353. package/dist/insights/budget.mjs +32 -0
  354. package/dist/insights/budget.mjs.map +1 -0
  355. package/dist/insights/envelope.d.mts +57 -0
  356. package/dist/insights/envelope.mjs +59 -0
  357. package/dist/insights/envelope.mjs.map +1 -0
  358. package/dist/insights/evidence-health.d.mts +30 -0
  359. package/dist/insights/evidence-health.mjs +27 -0
  360. package/dist/insights/evidence-health.mjs.map +1 -0
  361. package/dist/insights/evidence.d.mts +53 -0
  362. package/dist/insights/evidence.mjs +63 -0
  363. package/dist/insights/evidence.mjs.map +1 -0
  364. package/dist/insights/hash.d.mts +16 -0
  365. package/dist/insights/hash.mjs +20 -0
  366. package/dist/insights/hash.mjs.map +1 -0
  367. package/dist/insights/limits.d.mts +22 -0
  368. package/dist/insights/limits.mjs +23 -0
  369. package/dist/insights/limits.mjs.map +1 -0
  370. package/dist/insights/prompts/v1.d.mts +11 -0
  371. package/dist/insights/prompts/v1.mjs +33 -0
  372. package/dist/insights/prompts/v1.mjs.map +1 -0
  373. package/dist/insights/redact.d.mts +7 -0
  374. package/dist/insights/redact.mjs +23 -0
  375. package/dist/insights/redact.mjs.map +1 -0
  376. package/dist/insights/text.mjs +43 -0
  377. package/dist/insights/text.mjs.map +1 -0
  378. package/dist/insights.d.mts +11 -0
  379. package/dist/insights.mjs +12 -0
  380. package/dist/logger.d.mts +0 -2
  381. package/dist/logger.mjs +18 -3
  382. package/dist/logger.mjs.map +1 -1
  383. package/dist/models/ai-budget-alert.d.mts +21 -0
  384. package/dist/models/ai-budget-alert.mjs +37 -0
  385. package/dist/models/ai-budget-alert.mjs.map +1 -0
  386. package/dist/models/ai-usage.d.mts +38 -0
  387. package/dist/models/ai-usage.mjs +60 -0
  388. package/dist/models/ai-usage.mjs.map +1 -0
  389. package/dist/models/baseline-approval.d.mts +18 -0
  390. package/dist/models/baseline-approval.mjs +41 -0
  391. package/dist/models/baseline-approval.mjs.map +1 -0
  392. package/dist/models/baseline-archive.d.mts +82 -0
  393. package/dist/models/baseline-archive.mjs +105 -0
  394. package/dist/models/baseline-archive.mjs.map +1 -0
  395. package/dist/models/baseline-atomic.mjs +66 -0
  396. package/dist/models/baseline-atomic.mjs.map +1 -0
  397. package/dist/models/baseline-conflict.d.mts +6 -0
  398. package/dist/models/baseline-conflict.mjs +9 -0
  399. package/dist/models/baseline-conflict.mjs.map +1 -0
  400. package/dist/models/baseline-export.d.mts +17 -0
  401. package/dist/models/baseline-export.mjs +135 -0
  402. package/dist/models/baseline-export.mjs.map +1 -0
  403. package/dist/models/baseline-import.mjs +117 -0
  404. package/dist/models/baseline-import.mjs.map +1 -0
  405. package/dist/models/baseline-origin.d.mts +16 -0
  406. package/dist/models/baseline-origin.mjs +35 -0
  407. package/dist/models/baseline-origin.mjs.map +1 -0
  408. package/dist/models/baseline-status.d.mts +0 -2
  409. package/dist/models/baseline-status.mjs +1 -2
  410. package/dist/models/baseline-transfer.d.mts +48 -0
  411. package/dist/models/baseline-transfer.mjs +149 -0
  412. package/dist/models/baseline-transfer.mjs.map +1 -0
  413. package/dist/models/baseline.d.mts +37 -7
  414. package/dist/models/baseline.mjs +152 -28
  415. package/dist/models/baseline.mjs.map +1 -1
  416. package/dist/models/build-component.d.mts +26 -0
  417. package/dist/models/build-component.mjs +54 -0
  418. package/dist/models/build-component.mjs.map +1 -0
  419. package/dist/models/build-review-status.d.mts +27 -0
  420. package/dist/models/build-review-status.mjs +36 -0
  421. package/dist/models/build-review-status.mjs.map +1 -0
  422. package/dist/models/build-review.d.mts +26 -0
  423. package/dist/models/build-review.mjs +53 -0
  424. package/dist/models/build-review.mjs.map +1 -0
  425. package/dist/models/build.d.mts +25 -5
  426. package/dist/models/build.mjs +53 -15
  427. package/dist/models/build.mjs.map +1 -1
  428. package/dist/models/capture-attempt.d.mts +7 -3
  429. package/dist/models/capture-attempt.mjs +30 -6
  430. package/dist/models/capture-attempt.mjs.map +1 -1
  431. package/dist/models/capture-job.d.mts +104 -0
  432. package/dist/models/capture-job.mjs +287 -0
  433. package/dist/models/capture-job.mjs.map +1 -0
  434. package/dist/models/capture-log.d.mts +1 -3
  435. package/dist/models/capture-log.mjs +5 -5
  436. package/dist/models/capture-log.mjs.map +1 -1
  437. package/dist/models/capture-shard.d.mts +60 -0
  438. package/dist/models/capture-shard.mjs +189 -0
  439. package/dist/models/capture-shard.mjs.map +1 -0
  440. package/dist/models/changeset.d.mts +46 -0
  441. package/dist/models/changeset.mjs +80 -0
  442. package/dist/models/changeset.mjs.map +1 -0
  443. package/dist/models/columns.mjs +18 -0
  444. package/dist/models/columns.mjs.map +1 -0
  445. package/dist/models/comment.d.mts +4 -3
  446. package/dist/models/comment.mjs +8 -6
  447. package/dist/models/comment.mjs.map +1 -1
  448. package/dist/models/component.d.mts +41 -0
  449. package/dist/models/component.mjs +59 -0
  450. package/dist/models/component.mjs.map +1 -0
  451. package/dist/models/connections.d.mts +39 -0
  452. package/dist/models/connections.mjs +111 -0
  453. package/dist/models/connections.mjs.map +1 -0
  454. package/dist/models/content-ref.d.mts +1 -3
  455. package/dist/models/content-ref.mjs +2 -3
  456. package/dist/models/content-ref.mjs.map +1 -1
  457. package/dist/models/credentials.d.mts +0 -2
  458. package/dist/models/credentials.mjs +13 -4
  459. package/dist/models/credentials.mjs.map +1 -1
  460. package/dist/models/design-credentials.d.mts +39 -0
  461. package/dist/models/design-credentials.mjs +85 -0
  462. package/dist/models/design-credentials.mjs.map +1 -0
  463. package/dist/models/design-health.d.mts +14 -0
  464. package/dist/models/design-health.mjs +24 -0
  465. package/dist/models/design-health.mjs.map +1 -0
  466. package/dist/models/design-nodes.d.mts +81 -0
  467. package/dist/models/design-nodes.mjs +221 -0
  468. package/dist/models/design-nodes.mjs.map +1 -0
  469. package/dist/models/design-renders.mjs +73 -0
  470. package/dist/models/design-renders.mjs.map +1 -0
  471. package/dist/models/design-resolve.d.mts +27 -0
  472. package/dist/models/design-resolve.mjs +40 -0
  473. package/dist/models/design-resolve.mjs.map +1 -0
  474. package/dist/models/design.d.mts +37 -0
  475. package/dist/models/design.mjs +89 -0
  476. package/dist/models/design.mjs.map +1 -0
  477. package/dist/models/inherited-ratio.d.mts +19 -0
  478. package/dist/models/inherited-ratio.mjs +21 -0
  479. package/dist/models/inherited-ratio.mjs.map +1 -0
  480. package/dist/models/insight.d.mts +68 -0
  481. package/dist/models/insight.mjs +161 -0
  482. package/dist/models/insight.mjs.map +1 -0
  483. package/dist/models/label.d.mts +4 -4
  484. package/dist/models/label.mjs +26 -12
  485. package/dist/models/label.mjs.map +1 -1
  486. package/dist/models/member.d.mts +1 -3
  487. package/dist/models/member.mjs +6 -6
  488. package/dist/models/member.mjs.map +1 -1
  489. package/dist/models/notification-channel.d.mts +37 -5
  490. package/dist/models/notification-channel.mjs +89 -11
  491. package/dist/models/notification-channel.mjs.map +1 -1
  492. package/dist/models/notification-subscription.d.mts +1 -4
  493. package/dist/models/notification-subscription.mjs +8 -11
  494. package/dist/models/notification-subscription.mjs.map +1 -1
  495. package/dist/models/project-group-mapping.d.mts +6 -3
  496. package/dist/models/project-group-mapping.mjs +15 -6
  497. package/dist/models/project-group-mapping.mjs.map +1 -1
  498. package/dist/models/project.d.mts +9 -3
  499. package/dist/models/project.mjs +10 -6
  500. package/dist/models/project.mjs.map +1 -1
  501. package/dist/models/public-origin.d.mts +61 -0
  502. package/dist/models/public-origin.mjs +113 -0
  503. package/dist/models/public-origin.mjs.map +1 -0
  504. package/dist/models/review-switch-off.d.mts +12 -0
  505. package/dist/models/review-switch-off.mjs +25 -0
  506. package/dist/models/review-switch-off.mjs.map +1 -0
  507. package/dist/models/site-settings.d.mts +21 -0
  508. package/dist/models/site-settings.mjs +47 -0
  509. package/dist/models/site-settings.mjs.map +1 -0
  510. package/dist/models/snapshot.d.mts +39 -3
  511. package/dist/models/snapshot.mjs +54 -5
  512. package/dist/models/snapshot.mjs.map +1 -1
  513. package/dist/models/token.d.mts +13 -3
  514. package/dist/models/token.mjs +24 -7
  515. package/dist/models/token.mjs.map +1 -1
  516. package/dist/models/usage.d.mts +74 -0
  517. package/dist/models/usage.mjs +148 -0
  518. package/dist/models/usage.mjs.map +1 -0
  519. package/dist/models/user.d.mts +1 -3
  520. package/dist/models/user.mjs +5 -5
  521. package/dist/models/user.mjs.map +1 -1
  522. package/dist/models/webhook.d.mts +15 -3
  523. package/dist/models/webhook.mjs +34 -6
  524. package/dist/models/webhook.mjs.map +1 -1
  525. package/dist/models.d.mts +30 -4
  526. package/dist/models.mjs +31 -6
  527. package/dist/options.d.mts +16 -0
  528. package/dist/options.mjs +88 -0
  529. package/dist/options.mjs.map +1 -0
  530. package/dist/paths.d.mts +21 -6
  531. package/dist/paths.mjs +57 -12
  532. package/dist/paths.mjs.map +1 -1
  533. package/dist/retention/ai.d.mts +16 -0
  534. package/dist/retention/ai.mjs +22 -0
  535. package/dist/retention/ai.mjs.map +1 -0
  536. package/dist/retention/pending.mjs +46 -0
  537. package/dist/retention/pending.mjs.map +1 -0
  538. package/dist/retention/published-gzip-sweep.mjs +28 -0
  539. package/dist/retention/published-gzip-sweep.mjs.map +1 -0
  540. package/dist/retention/screenshot-sweep.d.mts +49 -0
  541. package/dist/retention/screenshot-sweep.mjs +127 -0
  542. package/dist/retention/screenshot-sweep.mjs.map +1 -0
  543. package/dist/retention.d.mts +16 -4
  544. package/dist/retention.mjs +76 -16
  545. package/dist/retention.mjs.map +1 -1
  546. package/dist/schema/baseline.d.mts +8 -2
  547. package/dist/schema/build-component.d.mts +23 -0
  548. package/dist/schema/build-review.d.mts +15 -0
  549. package/dist/schema/build.d.mts +7 -2
  550. package/dist/schema/capture-attempt.d.mts +0 -2
  551. package/dist/schema/capture-job.d.mts +58 -0
  552. package/dist/schema/capture-shard.d.mts +33 -0
  553. package/dist/schema/comment.d.mts +2 -2
  554. package/dist/schema/connection.d.mts +22 -0
  555. package/dist/schema/content-ref.d.mts +0 -2
  556. package/dist/schema/design.d.mts +56 -0
  557. package/dist/schema/insight.d.mts +52 -0
  558. package/dist/schema/label.d.mts +0 -2
  559. package/dist/schema/member.d.mts +0 -2
  560. package/dist/schema/notification.d.mts +4 -2
  561. package/dist/schema/project-group-mapping.d.mts +0 -2
  562. package/dist/schema/project.d.mts +21 -2
  563. package/dist/schema/site-setting.d.mts +19 -0
  564. package/dist/schema/snapshot.d.mts +17 -2
  565. package/dist/schema/token.d.mts +0 -2
  566. package/dist/schema/usage.d.mts +28 -0
  567. package/dist/schema/user.d.mts +0 -2
  568. package/dist/schema/webhook.d.mts +0 -2
  569. package/dist/schema.d.mts +12 -6
  570. package/dist/serverless.d.mts +19 -0
  571. package/dist/serverless.mjs +36 -0
  572. package/dist/serverless.mjs.map +1 -0
  573. package/dist/test-helpers/build-reference-contract.d.mts +14 -0
  574. package/dist/test-helpers/build-reference-contract.mjs +66 -0
  575. package/dist/test-helpers/build-reference-contract.mjs.map +1 -0
  576. package/dist/test-helpers/create-project.d.mts +1 -3
  577. package/dist/test-helpers/create-project.mjs +1 -2
  578. package/dist/test-helpers/database-contract.d.mts +0 -2
  579. package/dist/test-helpers/database-contract.mjs +37 -2
  580. package/dist/test-helpers/database-contract.mjs.map +1 -1
  581. package/dist/test-helpers/deferred.d.mts +37 -0
  582. package/dist/test-helpers/deferred.mjs +60 -0
  583. package/dist/test-helpers/deferred.mjs.map +1 -0
  584. package/dist/test-helpers/design-contract.d.mts +19 -0
  585. package/dist/test-helpers/design-contract.mjs +39 -0
  586. package/dist/test-helpers/design-contract.mjs.map +1 -0
  587. package/dist/test-helpers/fake-adapters.d.mts +0 -2
  588. package/dist/test-helpers/fake-adapters.mjs +1 -2
  589. package/dist/test-helpers/fake-database.d.mts +0 -2
  590. package/dist/test-helpers/fake-database.mjs +21 -4
  591. package/dist/test-helpers/fake-database.mjs.map +1 -1
  592. package/dist/test-helpers/fake-design.d.mts +41 -0
  593. package/dist/test-helpers/fake-design.mjs +113 -0
  594. package/dist/test-helpers/fake-design.mjs.map +1 -0
  595. package/dist/test-helpers/fake-notifier.d.mts +19 -0
  596. package/dist/test-helpers/fake-notifier.mjs +43 -0
  597. package/dist/test-helpers/fake-notifier.mjs.map +1 -0
  598. package/dist/test-helpers/fake-storage.d.mts +0 -2
  599. package/dist/test-helpers/fake-storage.mjs +1 -2
  600. package/dist/test-helpers/fake-tables.mjs +260 -69
  601. package/dist/test-helpers/fake-tables.mjs.map +1 -1
  602. package/dist/test-helpers/manual-clock.d.mts +25 -0
  603. package/dist/test-helpers/manual-clock.mjs +55 -0
  604. package/dist/test-helpers/manual-clock.mjs.map +1 -0
  605. package/dist/test-helpers/notifier-contract.d.mts +7 -3
  606. package/dist/test-helpers/notifier-contract.mjs +33 -5
  607. package/dist/test-helpers/notifier-contract.mjs.map +1 -1
  608. package/dist/test-helpers/queue-contract.d.mts +16 -4
  609. package/dist/test-helpers/queue-contract.mjs +196 -3
  610. package/dist/test-helpers/queue-contract.mjs.map +1 -1
  611. package/dist/test-helpers/queue-multi-instance.d.mts +18 -0
  612. package/dist/test-helpers/queue-multi-instance.mjs +205 -0
  613. package/dist/test-helpers/queue-multi-instance.mjs.map +1 -0
  614. package/dist/test-helpers/runner-contract.d.mts +0 -2
  615. package/dist/test-helpers/runner-contract.mjs +12 -2
  616. package/dist/test-helpers/runner-contract.mjs.map +1 -1
  617. package/dist/test-helpers/sql-chunks.mjs +25 -2
  618. package/dist/test-helpers/sql-chunks.mjs.map +1 -1
  619. package/dist/test-helpers/storage-contract.d.mts +0 -2
  620. package/dist/test-helpers/storage-contract.mjs +2 -3
  621. package/dist/test-helpers.d.mts +10 -4
  622. package/dist/test-helpers.mjs +9 -3
  623. package/dist/tracing.mjs +36 -6
  624. package/dist/tracing.mjs.map +1 -1
  625. package/dist/types.d.mts +14 -5
  626. package/dist/types.mjs +22 -6
  627. package/dist/types.mjs.map +1 -1
  628. package/dist/urls.d.mts +4 -2
  629. package/dist/urls.mjs +12 -4
  630. package/dist/urls.mjs.map +1 -1
  631. package/dist/utils/adaptive-concurrency.d.mts +64 -0
  632. package/dist/utils/adaptive-concurrency.mjs +148 -0
  633. package/dist/utils/adaptive-concurrency.mjs.map +1 -0
  634. package/dist/utils/connect-retry.d.mts +26 -0
  635. package/dist/utils/connect-retry.mjs +42 -0
  636. package/dist/utils/connect-retry.mjs.map +1 -0
  637. package/dist/utils/encrypt.d.mts +0 -2
  638. package/dist/utils/encrypt.mjs +1 -2
  639. package/dist/utils/environment.d.mts +17 -0
  640. package/dist/utils/environment.mjs +58 -0
  641. package/dist/utils/environment.mjs.map +1 -0
  642. package/dist/utils/hash.d.mts +0 -2
  643. package/dist/utils/hash.mjs +1 -2
  644. package/dist/utils/http.d.mts +29 -3
  645. package/dist/utils/http.mjs +100 -19
  646. package/dist/utils/http.mjs.map +1 -1
  647. package/dist/utils/map-adaptive.d.mts +13 -0
  648. package/dist/utils/map-adaptive.mjs +71 -0
  649. package/dist/utils/map-adaptive.mjs.map +1 -0
  650. package/dist/utils/map-limit.d.mts +8 -0
  651. package/dist/utils/map-limit.mjs +31 -0
  652. package/dist/utils/map-limit.mjs.map +1 -0
  653. package/dist/utils/mime.d.mts +0 -2
  654. package/dist/utils/mime.mjs +1 -2
  655. package/dist/utils/page-limiter.d.mts +35 -0
  656. package/dist/utils/page-limiter.mjs +55 -0
  657. package/dist/utils/page-limiter.mjs.map +1 -0
  658. package/dist/utils/path-security.d.mts +0 -2
  659. package/dist/utils/path-security.mjs +2 -4
  660. package/dist/utils/path-security.mjs.map +1 -1
  661. package/dist/utils/public-origin.d.mts +18 -0
  662. package/dist/utils/public-origin.mjs +74 -0
  663. package/dist/utils/public-origin.mjs.map +1 -0
  664. package/dist/utils/redact.d.mts +0 -2
  665. package/dist/utils/redact.mjs +4 -2
  666. package/dist/utils/redact.mjs.map +1 -1
  667. package/dist/utils/remote-environment.d.mts +76 -0
  668. package/dist/utils/remote-environment.mjs +163 -0
  669. package/dist/utils/remote-environment.mjs.map +1 -0
  670. package/dist/utils/resources.d.mts +62 -0
  671. package/dist/utils/resources.mjs +121 -0
  672. package/dist/utils/resources.mjs.map +1 -0
  673. package/dist/utils/screenshot-store.d.mts +13 -0
  674. package/dist/utils/screenshot-store.mjs +40 -0
  675. package/dist/utils/screenshot-store.mjs.map +1 -0
  676. package/dist/utils/session-gate.d.mts +23 -0
  677. package/dist/utils/session-gate.mjs +69 -0
  678. package/dist/utils/session-gate.mjs.map +1 -0
  679. package/dist/utils/storybook-token.d.mts +89 -0
  680. package/dist/utils/storybook-token.mjs +135 -0
  681. package/dist/utils/storybook-token.mjs.map +1 -0
  682. package/dist/utils/storybook-url.d.mts +13 -0
  683. package/dist/utils/storybook-url.mjs +20 -0
  684. package/dist/utils/storybook-url.mjs.map +1 -0
  685. package/dist/utils/timing.d.mts +0 -2
  686. package/dist/utils/timing.mjs +2 -3
  687. package/dist/utils/timing.mjs.map +1 -1
  688. package/dist/utils/ulid.d.mts +0 -2
  689. package/dist/utils/ulid.mjs +1 -2
  690. package/dist/utils/zip-read.mjs +128 -0
  691. package/dist/utils/zip-read.mjs.map +1 -0
  692. package/dist/utils/zip-write.d.mts +16 -0
  693. package/dist/utils/zip-write.mjs +187 -0
  694. package/dist/utils/zip-write.mjs.map +1 -0
  695. package/dist/utils.d.mts +17 -3
  696. package/dist/utils.mjs +18 -4
  697. package/package.json +35 -26
  698. package/dist/adapter/capture-runner.mjs.map +0 -1
  699. package/dist/adapter/git-host/comments.d.mts +0 -41
  700. package/dist/adapter/git-host/comments.mjs +0 -54
  701. package/dist/adapter/git-host/comments.mjs.map +0 -1
  702. package/dist/adapter/git-host/helpers.d.mts +0 -13
  703. package/dist/adapter/git-host/helpers.mjs +0 -30
  704. package/dist/adapter/git-host/helpers.mjs.map +0 -1
  705. package/dist/adapter/git-host.d.mts +0 -42
  706. package/dist/capture/queue.d.mts +0 -38
  707. package/dist/capture/queue.mjs +0 -98
  708. package/dist/capture/queue.mjs.map +0 -1
  709. package/dist/capture/recovery.d.mts +0 -21
  710. package/dist/capture/recovery.mjs +0 -83
  711. package/dist/capture/recovery.mjs.map +0 -1
  712. package/dist/capture/status-fanout.mjs +0 -72
  713. package/dist/capture/status-fanout.mjs.map +0 -1
  714. package/dist/diff/engine.d.mts +0 -16
  715. package/dist/diff/engine.mjs +0 -87
  716. package/dist/diff/engine.mjs.map +0 -1
  717. package/dist/diff/options.d.mts +0 -42
  718. package/dist/diff/options.mjs +0 -15
  719. package/dist/diff/options.mjs.map +0 -1
  720. package/dist/models/status-config.d.mts +0 -38
  721. package/dist/models/status-config.mjs +0 -63
  722. package/dist/models/status-config.mjs.map +0 -1
  723. package/dist/schema/status-config.d.mts +0 -16
  724. package/dist/test-helpers/git-contract.d.mts +0 -11
  725. package/dist/test-helpers/git-contract.mjs +0 -34
  726. package/dist/test-helpers/git-contract.mjs.map +0 -1
  727. /package/dist/adapter/{git-host.mjs → oauth.mjs} +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"pipeline.mjs","names":[],"sources":["../../src/capture/pipeline.ts"],"sourcesContent":["import type { Logger } from \"pino\";\nimport type { RenderedSnapshot } from \"../adapters/capture-runner.ts\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { StorageAdapter } from \"../adapters/storage.ts\";\nimport { NO_BASELINE } from \"../models/baseline-status.ts\";\nimport { BaselineModel, type BaselineTables } from \"../models/baseline.ts\";\nimport { BuildModel, type BuildTables } from \"../models/build.ts\";\nimport { emitAttemptLog, type AttemptLogRecorder } from \"../models/capture-attempt.ts\";\nimport { SnapshotModel, type SnapshotTables } from \"../models/snapshot.ts\";\nimport type { Baseline } from \"../schema/baseline.ts\";\nimport type { Build } from \"../schema/build.ts\";\nimport type { Project } from \"../schema/project.ts\";\nimport type { BuildStatus, SnapshotStatus } from \"../types.ts\";\nimport type { SecretInput } from \"../utils/encrypt.ts\";\nimport { diffPath, screenshotPath } from \"../utils/paths.ts\";\nimport type { Viewport } from \"./adapter.ts\";\nimport type { InheritedStory } from \"./affected.ts\";\nimport { diffStoredScreenshots, writeDiffOverlay } from \"./diff-record.ts\";\nimport { assertScreenshotBuffer } from \"./guards.ts\";\nimport { infraHashFor, SIZING_DEFAULTS } from \"./sizing.ts\";\n\n/**\n * Persist a completed capture run: write screenshots, diff against the branch\n * baseline, create snapshots, and finalize the build.\n *\n * Rendering is performed by a pure `CaptureRunner`; this function owns only\n * storage writes and record keeping, so it can run against any renderer.\n *\n * @param ctx - Capture context.\n * @param renderFailedStoryIds - Story ids that the renderer could not capture\n * (blocking failures). They force the build to `failed`.\n * @param flakyFailedStoryIds - Story ids that failed but are marked flaky\n * (non-blocking). They are logged as warnings and do not block the build.\n */\nexport async function persistCapture(\n ctx: CaptureContext,\n renderFailedStoryIds: ReadonlySet<string> = new Set(),\n flakyFailedStoryIds: ReadonlySet<string> = new Set(),\n a11yFailedStoryIds: ReadonlySet<string> = new Set(),\n): Promise<void> {\n const failedStoryIds = new Set(renderFailedStoryIds);\n const flakyIds = new Set(flakyFailedStoryIds);\n const a11yIds = new Set(a11yFailedStoryIds);\n await Promise.all(\n ctx.captures.map(async (capture) => {\n try {\n await persistSnapshot(ctx, capture);\n } catch (error) {\n // A failure in one story/viewport must not abort the other persists.\n // Flaky and a11y stories remain non-blocking even on persist failure.\n if (flakyIds.has(capture.story.id) || a11yIds.has(capture.story.id)) {\n if (flakyIds.has(capture.story.id)) flakyIds.add(capture.story.id);\n if (a11yIds.has(capture.story.id)) a11yIds.add(capture.story.id);\n ctx.logger?.warn(\n { storyId: capture.story.id, viewport: capture.viewportName, err: error },\n \"capture failed for flaky/a11y story (non-blocking)\",\n );\n await emitAttemptLog(\n ctx.recordLog,\n ctx.logger,\n \"warn\",\n \"capture failed for flaky/a11y story (non-blocking)\",\n {\n storyId: capture.story.id,\n viewport: capture.viewportName,\n },\n );\n } else {\n failedStoryIds.add(capture.story.id);\n }\n ctx.logger?.error(\n { storyId: capture.story.id, viewport: capture.viewportName, err: error },\n \"capture failed for story\",\n );\n await emitAttemptLog(ctx.recordLog, ctx.logger, \"error\", \"capture failed for story\", {\n storyId: capture.story.id,\n viewport: capture.viewportName,\n });\n }\n }),\n );\n // Also log flaky and a11y render failures as warnings\n for (const id of flakyIds) {\n if (!failedStoryIds.has(id)) {\n ctx.logger?.warn({ storyId: id }, \"flaky story failed (non-blocking)\");\n await emitAttemptLog(ctx.recordLog, ctx.logger, \"warn\", \"flaky story failed (non-blocking)\", {\n storyId: id,\n });\n }\n }\n for (const id of a11yIds) {\n if (!failedStoryIds.has(id) && !flakyIds.has(id)) {\n ctx.logger?.warn({ storyId: id }, \"a11y violations found (non-blocking)\");\n await emitAttemptLog(\n ctx.recordLog,\n ctx.logger,\n \"warn\",\n \"a11y violations found (non-blocking)\",\n {\n storyId: id,\n },\n );\n }\n }\n for (const id of await persistInherited(ctx)) {\n failedStoryIds.add(id);\n }\n await finalize(ctx, allStoryIds(ctx), failedStoryIds, flakyIds, a11yIds);\n}\n\n/** Persist one inherited snapshot; returns the story id on failure. */\nasync function tryPersistInherited(\n ctx: CaptureContext,\n inherited: InheritedStory,\n): Promise<string | null> {\n try {\n await persistInheritedSnapshot(ctx, inherited);\n return null;\n } catch (error) {\n ctx.logger?.error(\n { storyId: inherited.story.id, viewport: inherited.viewport.name, err: error },\n \"inherit failed for story\",\n );\n await emitAttemptLog(ctx.recordLog, ctx.logger, \"error\", \"inherit failed for story\", {\n storyId: inherited.story.id,\n viewport: inherited.viewport.name,\n });\n return inherited.story.id;\n }\n}\n\n/** Persist inherited snapshots; a failure marks the story failed. */\nasync function persistInherited(ctx: CaptureContext): Promise<ReadonlySet<string>> {\n const outcomes = await Promise.all(\n (ctx.inherited ?? []).map(async (inherited) => await tryPersistInherited(ctx, inherited)),\n );\n return new Set(outcomes.filter((id): id is string => id !== null));\n}\n\n/** Create an unchanged snapshot reusing the baseline screenshot. */\nasync function persistInheritedSnapshot(\n ctx: CaptureContext,\n inherited: InheritedStory,\n): Promise<void> {\n const infraHash = infraHashFor(ctx.project.browser ?? \"chromium\", ctx.viewports, SIZING_DEFAULTS);\n const snapshots = new SnapshotModel(ctx.db, ctx.tables);\n const snapshot = await snapshots.create(ctx.project.id, ctx.build.id, {\n storyId: inherited.story.id,\n storyName: inherited.story.name,\n storyTitle: inherited.story.title,\n storyImportPath: inherited.story.importPath ?? \"\",\n viewportName: inherited.viewport.name,\n viewportWidth: inherited.viewport.width,\n viewportHeight: inherited.viewport.height,\n screenshotPath: inherited.baseline.screenshotPath,\n infraHash,\n inherited: true,\n baselineId: inherited.baseline.id,\n baselineVersion: inherited.baseline.updatedAt,\n });\n await snapshots.update(snapshot.id, {\n status: \"unchanged\",\n diffPixels: 0,\n diffRatio: 0,\n diffPassed: true,\n diffPath: null,\n infraHash,\n });\n}\n\n/** Every story covered by a capture run, rendered or inherited. */\nfunction allStoryIds(ctx: CaptureContext): ReadonlySet<string> {\n const ids = new Set(ctx.captures.map((capture) => capture.story.id));\n for (const inherited of ctx.inherited ?? []) {\n ids.add(inherited.story.id);\n }\n return ids;\n}\n\n/** Table handles required by the capture pipeline. */\nexport type PipelineTables = BuildTables & BaselineTables & SnapshotTables;\n\n/** Persistence inputs for a completed capture run. */\nexport interface CaptureContext {\n /** Database adapter. */\n db: DatabaseAdapter;\n /** Table handles. */\n tables: PipelineTables;\n /** Storage adapter. */\n storage: StorageAdapter;\n /** The project being captured. */\n project: Project;\n /** The build being captured. */\n build: Build;\n /** Viewports at which stories were captured. */\n viewports: Viewport[];\n /** Screenshot buffers produced by the capture renderer. */\n captures: RenderedSnapshot[];\n /** Stories inheriting their baseline without rendering (affected capture). */\n inherited?: InheritedStory[];\n /** Optional logger for capture diagnostics. */\n logger?: Logger;\n /** Optional per-attempt log recorder (mirrors logger lines into the DB). */\n recordLog?: AttemptLogRecorder;\n /** Server secret for decrypting webhook secrets at send time. */\n secret?: SecretInput;\n}\n\nfunction viewportByName(ctx: CaptureContext, name: string): Viewport {\n return ctx.viewports.find((v) => v.name === name) ?? { name, width: 0, height: 0 };\n}\n\nasync function persistSnapshot(ctx: CaptureContext, capture: RenderedSnapshot): Promise<void> {\n assertScreenshotBuffer(capture.screenshot);\n const viewport = capture.viewport ?? viewportByName(ctx, capture.viewportName);\n const screenshot = screenshotPath(\n ctx.project.id,\n ctx.build.id,\n capture.story.id,\n capture.viewportName,\n );\n await ctx.storage.write(screenshot, capture.screenshot);\n\n const baseline = await resolveBaseline(ctx, capture.story.id, capture.viewportName);\n if (!baseline) {\n await createWithoutBaseline(ctx, capture, viewport, screenshot);\n return;\n }\n await createWithBaseline(ctx, capture, viewport, screenshot, baseline);\n}\n\nasync function resolveBaseline(\n ctx: CaptureContext,\n storyId: string,\n viewport: string,\n): Promise<Baseline | null> {\n const baselines = new BaselineModel(ctx.db, ctx.tables, ctx.storage, ctx.secret);\n return await baselines.resolve(\n ctx.project.id,\n storyId,\n viewport,\n ctx.build.gitBranch,\n ctx.project.gitDefaultBranch,\n );\n}\n\nasync function createWithoutBaseline(\n ctx: CaptureContext,\n capture: RenderedSnapshot,\n viewport: Viewport,\n screenshot: string,\n): Promise<void> {\n const snapshots = new SnapshotModel(ctx.db, ctx.tables);\n const status = ctx.build.isDefault ? \"approved\" : \"new\";\n const infraHash = infraHashFor(ctx.project.browser ?? \"chromium\", ctx.viewports, SIZING_DEFAULTS);\n const snapshot = await snapshots.create(ctx.project.id, ctx.build.id, {\n storyId: capture.story.id,\n storyName: capture.story.name,\n storyTitle: capture.story.title,\n storyImportPath: capture.story.importPath ?? \"\",\n viewportName: capture.viewportName,\n viewportWidth: viewport.width,\n viewportHeight: viewport.height,\n screenshotPath: screenshot,\n infraHash,\n baselineVersion: NO_BASELINE,\n });\n await snapshots.setStatus(snapshot.id, status);\n\n if (ctx.build.isDefault) {\n const baselines = new BaselineModel(ctx.db, ctx.tables, ctx.storage, ctx.secret);\n await baselines.upsert(\n ctx.project.id,\n capture.story.id,\n capture.viewportName,\n ctx.build.gitBranch,\n snapshot.id,\n screenshot,\n infraHash,\n );\n }\n}\n\nasync function createWithBaseline(\n ctx: CaptureContext,\n capture: RenderedSnapshot,\n viewport: Viewport,\n screenshot: string,\n baseline: Baseline,\n): Promise<void> {\n const result = await diffStoredScreenshots(\n ctx.storage,\n ctx.project,\n baseline.screenshotPath,\n screenshot,\n capture.story.parameters?.diffThreshold,\n );\n\n const infraHash = infraHashFor(ctx.project.browser ?? \"chromium\", ctx.viewports, SIZING_DEFAULTS);\n const snapshots = new SnapshotModel(ctx.db, ctx.tables);\n const snapshot = await snapshots.create(ctx.project.id, ctx.build.id, {\n storyId: capture.story.id,\n storyName: capture.story.name,\n storyTitle: capture.story.title,\n storyImportPath: capture.story.importPath ?? \"\",\n viewportName: capture.viewportName,\n viewportWidth: viewport.width,\n viewportHeight: viewport.height,\n screenshotPath: screenshot,\n infraHash,\n baselineId: baseline.id,\n baselineVersion: baseline.updatedAt,\n });\n\n // Automigrate: infra-induced size change with project.automigrate enabled\n const shouldAutomigrate =\n result.sizeChanged &&\n !result.passed &&\n (ctx.project as unknown as { automigrate?: boolean }).automigrate &&\n baseline.infraHash !== null &&\n baseline.infraHash !== infraHash;\n\n const status = shouldAutomigrate ? \"unchanged\" : diffStatus(result.passed, ctx.build.isDefault);\n const diff = diffPath(ctx.project.id, ctx.build.id, capture.story.id, capture.viewportName);\n const passed = shouldAutomigrate ? true : result.passed;\n const pixels = shouldAutomigrate ? 0 : result.diffPixels;\n const ratio = shouldAutomigrate ? 0 : result.diffRatio;\n const overlay = await writeDiffOverlay(ctx.storage, diff, result, passed);\n await snapshots.update(snapshot.id, {\n status,\n diffPath: overlay,\n diffPixels: pixels,\n diffRatio: ratio,\n diffPassed: passed,\n infraHash,\n });\n if (ctx.build.isDefault && (shouldAutomigrate || !passed)) {\n await upsertDefaultBaseline(ctx, snapshot.id, capture, screenshot, infraHash);\n }\n}\n\n/** Default-branch builds are authoritative: the captured screenshot becomes the baseline. */\nasync function upsertDefaultBaseline(\n ctx: CaptureContext,\n snapshotId: string,\n capture: RenderedSnapshot,\n screenshot: string,\n infraHash: string,\n): Promise<void> {\n const baselines = new BaselineModel(ctx.db, ctx.tables, ctx.storage, ctx.secret);\n await baselines.upsert(\n ctx.project.id,\n capture.story.id,\n capture.viewportName,\n ctx.build.gitBranch,\n snapshotId,\n screenshot,\n infraHash,\n );\n}\n\n/** Snapshot status after diffing: default-branch changes are auto-approved, not queued for review. */\nexport function diffStatus(passed: boolean, isDefault: boolean): SnapshotStatus {\n if (passed) {\n return \"unchanged\";\n }\n return isDefault ? \"approved\" : \"changed\";\n}\n\n/** A failed capture fails the build; default-branch builds otherwise auto-approve. */\nfunction finalBuildStatus(\n isDefault: boolean,\n failedCount: number,\n hasCaptures: boolean,\n build: { changedCount: number },\n): BuildStatus {\n if (failedCount > 0) {\n return \"failed\";\n }\n if (isDefault || (hasCaptures && build.changedCount === 0)) {\n return \"approved\";\n }\n return \"reviewing\";\n}\n\nasync function finalize(\n ctx: CaptureContext,\n storyIds: ReadonlySet<string>,\n failedStoryIds: ReadonlySet<string>,\n flakyFailedStoryIds: ReadonlySet<string> = new Set(),\n a11yFailedStoryIds: ReadonlySet<string> = new Set(),\n): Promise<void> {\n const builds = new BuildModel(ctx.db, ctx.tables);\n const build = await builds.updateCounts(ctx.build.id);\n const hasCaptures = storyIds.size > 0;\n const status = finalBuildStatus(ctx.build.isDefault, failedStoryIds.size, hasCaptures, build);\n // Flaky and a11y failures do not block: log warning if any failed non-blocking\n if (flakyFailedStoryIds.size > 0 && failedStoryIds.size === 0) {\n ctx.logger?.warn(\n { flakyStoryIds: [...flakyFailedStoryIds] },\n \"flaky stories failed (non-blocking)\",\n );\n await emitAttemptLog(ctx.recordLog, ctx.logger, \"warn\", \"flaky stories failed (non-blocking)\", {\n flakyStoryIds: [...flakyFailedStoryIds],\n });\n }\n if (a11yFailedStoryIds.size > 0 && failedStoryIds.size === 0) {\n ctx.logger?.warn(\n { a11yStoryIds: [...a11yFailedStoryIds] },\n \"a11y violations found (non-blocking)\",\n );\n await emitAttemptLog(\n ctx.recordLog,\n ctx.logger,\n \"warn\",\n \"a11y violations found (non-blocking)\",\n {\n a11yStoryIds: [...a11yFailedStoryIds],\n },\n );\n }\n await builds.setStatus(ctx.build.id, status);\n\n if (ctx.build.isDefault && hasCaptures) {\n const baselines = new BaselineModel(ctx.db, ctx.tables, ctx.storage, ctx.secret);\n await baselines.removeOrphans(ctx.project.id, new Set(storyIds));\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"pipeline.mjs","names":[],"sources":["../../src/capture/pipeline.ts"],"sourcesContent":["import type { Logger } from \"pino\";\nimport { DEFAULT_BROWSER, type RenderedSnapshot } from \"../adapters/capture-runner.ts\";\nimport { ReferenceGenerationConflict, type DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { StorageAdapter } from \"../adapters/storage.ts\";\nimport { BaselineWriteConflict } from \"../models/baseline-conflict.ts\";\nimport { NO_BASELINE } from \"../models/baseline-status.ts\";\nimport { BaselineModel, type BaselineTables } from \"../models/baseline.ts\";\nimport { BuildModel, type BuildTables } from \"../models/build.ts\";\nimport { emitAttemptLog, type AttemptLogRecorder } from \"../models/capture-attempt.ts\";\nimport { SnapshotModel, type SnapshotTables } from \"../models/snapshot.ts\";\nimport type { Baseline } from \"../schema/baseline.ts\";\nimport type { Build } from \"../schema/build.ts\";\nimport type { Project } from \"../schema/project.ts\";\nimport type { BuildStatus, SnapshotStatus } from \"../types.ts\";\nimport type { SecretInput } from \"../utils/encrypt.ts\";\nimport { mapLimit } from \"../utils/map-limit.ts\";\nimport { diffPath } from \"../utils/paths.ts\";\nimport { storeScreenshot } from \"../utils/screenshot-store.ts\";\nimport type { Viewport } from \"./adapter.ts\";\nimport type { InheritedStory } from \"./affected.ts\";\nimport { freezeComparison, sourceSnapshotId } from \"./comparison-reference.ts\";\nimport { diffStoredScreenshots, writeDiffOverlay } from \"./diff-record.ts\";\nimport {\n buildFingerprint,\n changedFields,\n fingerprintHash,\n mergeFingerprint,\n parseFingerprint,\n type EnvironmentChange,\n type Fingerprint,\n} from \"./fingerprint.ts\";\nimport { assertScreenshotBuffer } from \"./guards.ts\";\nimport type { CaptureProgress } from \"./progress.ts\";\n\n/** Snapshots persisted at once: each decodes two PNGs for the diff, so unbounded fan-out spikes memory. */\nexport const PERSIST_CONCURRENCY = 4;\n\n/**\n * Persist a completed capture run: write screenshots, diff against the branch\n * baseline, create snapshots, and finalize the build.\n *\n * Rendering is performed by a pure `CaptureRunner`; this function owns only\n * storage writes and record keeping, so it can run against any renderer.\n *\n * @param ctx - Capture context.\n * @param renderFailedStoryIds - Story ids that the renderer could not capture\n * (blocking failures). They force the build to `failed`.\n * @param flakyFailedStoryIds - Story ids that failed but are marked flaky\n * (non-blocking). They are logged as warnings and do not block the build.\n */\nexport async function persistCapture(\n input: CaptureContext,\n renderFailedStoryIds: ReadonlySet<string> = new Set(),\n flakyFailedStoryIds: ReadonlySet<string> = new Set(),\n a11yFailedStoryIds: ReadonlySet<string> = new Set(),\n): Promise<void> {\n const sets = await persistRendered(\n input,\n renderFailedStoryIds,\n flakyFailedStoryIds,\n a11yFailedStoryIds,\n );\n for (const id of await persistInherited(input)) {\n sets.failed.add(id);\n }\n await finalizeBuild(input, allStoryIds(input), sets.failed, sets.flaky, sets.a11y);\n}\n\n/** Blocking, flaky and a11y story ids a persist run ended with. */\nexport interface PersistedSets {\n failed: Set<string>;\n flaky: Set<string>;\n a11y: Set<string>;\n}\n\n/**\n * Persist the rendered captures only: screenshots, diffs and snapshot rows, without the\n * inherited snapshots or the build finalize. A sharded capture runs this once per shard.\n */\nexport async function persistRendered(\n input: CaptureContext,\n renderFailedStoryIds: ReadonlySet<string> = new Set(),\n flakyFailedStoryIds: ReadonlySet<string> = new Set(),\n a11yFailedStoryIds: ReadonlySet<string> = new Set(),\n): Promise<PersistedSets> {\n const ctx = await withFreshBrowsers(input);\n const failedStoryIds = new Set(renderFailedStoryIds);\n const flakyIds = new Set(flakyFailedStoryIds);\n const a11yIds = new Set(a11yFailedStoryIds);\n // Primary engine first: a newly enabled engine bootstraps from its counterpart.\n for (const group of capturesByPhase(ctx)) {\n // oxlint-disable-next-line no-await-in-loop -- phases must run in order\n await mapLimit(group, PERSIST_CONCURRENCY, async (capture) => {\n try {\n await persistSnapshot(ctx, capture);\n // Each diff is progress: a long, healthy persist phase must not read as a hung job.\n ctx.progress?.();\n } catch (error) {\n // A failure in one story/viewport must not abort the other persists.\n // Flaky and a11y stories remain non-blocking even on persist failure.\n if (flakyIds.has(capture.story.id) || a11yIds.has(capture.story.id)) {\n if (flakyIds.has(capture.story.id)) flakyIds.add(capture.story.id);\n if (a11yIds.has(capture.story.id)) a11yIds.add(capture.story.id);\n ctx.logger?.warn(\n { storyId: capture.story.id, viewport: capture.viewportName, err: error },\n \"capture failed for flaky/a11y story (non-blocking)\",\n );\n await emitAttemptLog(\n ctx.recordLog,\n ctx.logger,\n \"warn\",\n \"capture failed for flaky/a11y story (non-blocking)\",\n {\n storyId: capture.story.id,\n viewport: capture.viewportName,\n },\n );\n } else {\n failedStoryIds.add(capture.story.id);\n }\n ctx.logger?.error(\n { storyId: capture.story.id, viewport: capture.viewportName, err: error },\n \"capture failed for story\",\n );\n await emitAttemptLog(ctx.recordLog, ctx.logger, \"error\", \"capture failed for story\", {\n storyId: capture.story.id,\n viewport: capture.viewportName,\n });\n }\n });\n }\n // Also log flaky and a11y render failures as warnings\n for (const id of flakyIds) {\n if (!failedStoryIds.has(id)) {\n ctx.logger?.warn({ storyId: id }, \"flaky story failed (non-blocking)\");\n await emitAttemptLog(ctx.recordLog, ctx.logger, \"warn\", \"flaky story failed (non-blocking)\", {\n storyId: id,\n });\n }\n }\n for (const id of a11yIds) {\n if (!failedStoryIds.has(id) && !flakyIds.has(id)) {\n ctx.logger?.warn({ storyId: id }, \"a11y violations found (non-blocking)\");\n await emitAttemptLog(\n ctx.recordLog,\n ctx.logger,\n \"warn\",\n \"a11y violations found (non-blocking)\",\n {\n storyId: id,\n },\n );\n }\n }\n return { failed: failedStoryIds, flaky: flakyIds, a11y: a11yIds };\n}\n\n/** Engines of this run, primary (first) first. */\nfunction runBrowsers(ctx: CaptureContext): string[] {\n const listed = ctx.browsers && ctx.browsers.length > 0 ? ctx.browsers : [DEFAULT_BROWSER];\n return [...new Set(listed)];\n}\n\nfunction primaryBrowser(ctx: CaptureContext): string {\n return runBrowsers(ctx)[0] ?? DEFAULT_BROWSER;\n}\n\n/** The capture's axe violation texts as the JSON stored on the snapshot; null when there are none. */\nfunction a11yJsonOf(capture: { a11y?: string[] }): string | null {\n return capture.a11y && capture.a11y.length > 0 ? JSON.stringify(capture.a11y) : null;\n}\n\nfunction browserOf(capture: { browser?: string }): string {\n return capture.browser ?? DEFAULT_BROWSER;\n}\n\n/** Captures split into the primary engine's, then everyone else's. */\nfunction capturesByPhase(ctx: CaptureContext): RenderedSnapshot[][] {\n const primary = primaryBrowser(ctx);\n const first = ctx.captures.filter((capture) => browserOf(capture) === primary);\n const rest = ctx.captures.filter((capture) => browserOf(capture) !== primary);\n return rest.length > 0 ? [first, rest] : [first];\n}\n\n/** Secondary engines with no baseline yet (their first snapshots bootstrap from the primary). */\nexport async function freshBrowsersOf(ctx: CaptureContext): Promise<Set<string>> {\n const primary = primaryBrowser(ctx);\n const baselines = new BaselineModel(ctx.db, ctx.tables, ctx.storage, ctx.secret);\n const secondary = runBrowsers(ctx).filter((browser) => browser !== primary);\n const checks = await Promise.all(\n secondary.map(async (browser) => ({\n browser,\n fresh: !(await baselines.hasAny(ctx.project.id, browser)),\n })),\n );\n return new Set(checks.filter((check) => check.fresh).map((check) => check.browser));\n}\n\n/** Mark secondary engines with no baseline yet: their first snapshots bootstrap. */\nasync function withFreshBrowsers(ctx: CaptureContext): Promise<CaptureContext> {\n return ctx.freshBrowsers ? ctx : { ...ctx, freshBrowsers: await freshBrowsersOf(ctx) };\n}\n\n/**\n * A newly enabled engine accepts a story/mode's first snapshot when the\n * primary engine's snapshot of it is clean (Chromatic's \"auto-accepted where\n * the primary browser was accepted\"); anything else goes to review as `new`.\n */\nasync function bootstrapAccepts(ctx: CaptureContext, capture: RenderedSnapshot): Promise<boolean> {\n const browser = browserOf(capture);\n if (!ctx.freshBrowsers?.has(browser)) return false;\n const primary = primaryBrowser(ctx);\n const siblings = await new SnapshotModel(ctx.db, ctx.tables).listByBuild(ctx.build.id);\n return siblings.some(\n (snapshot) =>\n snapshot.storyId === capture.story.id &&\n snapshot.viewportName === capture.viewportName &&\n (snapshot.browser ?? DEFAULT_BROWSER) === primary &&\n (snapshot.status === \"approved\" || snapshot.status === \"unchanged\"),\n );\n}\n\n/** Persist one inherited snapshot; returns the story id on failure. */\nasync function tryPersistInherited(\n ctx: CaptureContext,\n inherited: InheritedStory,\n): Promise<string | null> {\n try {\n await persistInheritedSnapshot(ctx, inherited);\n return null;\n } catch (error) {\n ctx.logger?.error(\n { storyId: inherited.story.id, viewport: inherited.viewport.name, err: error },\n \"inherit failed for story\",\n );\n await emitAttemptLog(ctx.recordLog, ctx.logger, \"error\", \"inherit failed for story\", {\n storyId: inherited.story.id,\n viewport: inherited.viewport.name,\n });\n return inherited.story.id;\n }\n}\n\n/** Persist inherited snapshots; a failure marks the story failed. */\n/** Persist the inherited snapshots (stories reusing their baseline); returns the story ids that failed. */\nexport async function persistInherited(ctx: CaptureContext): Promise<ReadonlySet<string>> {\n const outcomes = await mapLimit(ctx.inherited ?? [], PERSIST_CONCURRENCY, async (inherited) => {\n const failed = await tryPersistInherited(ctx, inherited);\n ctx.progress?.();\n return failed;\n });\n return new Set(outcomes.filter((id): id is string => id !== null));\n}\n\n/** Create an unchanged snapshot reusing the baseline screenshot. */\nasync function persistInheritedSnapshot(\n ctx: CaptureContext,\n inherited: InheritedStory,\n): Promise<void> {\n const browser = browserOf(inherited);\n // Nothing rendered, so there is no new fingerprint: the snapshot carries the baseline's.\n const infraHash = inherited.baseline.infraHash;\n const snapshots = new SnapshotModel(ctx.db, ctx.tables);\n const comparison = await freezeComparison(\n ctx.storage,\n ctx.project.id,\n ctx.build.id,\n inherited.baseline,\n {\n defaultBranch: ctx.project.gitDefaultBranch,\n mode: modeJsonOf(inherited.viewport),\n target: browser,\n width: inherited.viewport.width,\n height: inherited.viewport.height,\n captureFingerprint: inherited.baseline.fingerprint ?? null,\n sourceSnapshotId: await sourceSnapshotId(ctx.db, inherited.baseline),\n },\n );\n const snapshot = await snapshots.create(ctx.project.id, ctx.build.id, {\n ...comparison,\n browser,\n storyId: inherited.story.id,\n storyName: inherited.story.name,\n storyTitle: inherited.story.title,\n storyImportPath: inherited.story.importPath ?? \"\",\n designUrl: inherited.story.designUrl ?? null,\n viewportName: inherited.viewport.name,\n viewportWidth: inherited.viewport.width,\n viewportHeight: inherited.viewport.height,\n screenshotPath: inherited.baseline.screenshotPath,\n infraHash,\n mode: modeJsonOf(inherited.viewport),\n inherited: true,\n sourceBuildId: inherited.baseline.sourceBuildId ?? null,\n baselineId: inherited.baseline.id,\n baselineVersion: inherited.baseline.updatedAt,\n });\n await snapshots.update(snapshot.id, {\n status: \"unchanged\",\n diffPixels: 0,\n diffRatio: 0,\n diffPassed: true,\n diffPath: null,\n infraHash,\n });\n}\n\n/** Every story covered by a capture run, rendered or inherited. */\nfunction allStoryIds(ctx: CaptureContext): ReadonlySet<string> {\n const ids = new Set(ctx.captures.map((capture) => capture.story.id));\n for (const inherited of ctx.inherited ?? []) {\n ids.add(inherited.story.id);\n }\n return ids;\n}\n\n/** Table handles required by the capture pipeline. */\nexport type PipelineTables = BuildTables & BaselineTables & SnapshotTables;\n\n/** Persistence inputs for a completed capture run. */\nexport interface CaptureContext {\n /** Database adapter. */\n db: DatabaseAdapter;\n /** Table handles. */\n tables: PipelineTables;\n /** Storage adapter. */\n storage: StorageAdapter;\n /** The project being captured. */\n project: Project;\n /** The build being captured. */\n build: Build;\n /** Viewports at which stories were captured. */\n viewports: Viewport[];\n /** Engines captured this run, primary first (default: chromium only). */\n browsers?: string[];\n /** Secondary engines with no baselines yet; worked out by `persistCapture`, or fixed up front by a shard plan. */\n freshBrowsers?: ReadonlySet<string>;\n /** Screenshot buffers produced by the capture renderer. */\n captures: RenderedSnapshot[];\n /** Stories inheriting their baseline without rendering (affected capture). */\n inherited?: InheritedStory[];\n /** Optional logger for capture diagnostics. */\n logger?: Logger;\n /** Optional per-attempt log recorder (mirrors logger lines into the DB). */\n recordLog?: AttemptLogRecorder;\n /** Called per snapshot persisted, so the job's lease sees a long diff phase making progress. */\n progress?: CaptureProgress | undefined;\n /** Server secret for decrypting webhook secrets at send time. */\n secret?: SecretInput;\n}\n\n/** JSON for the snapshot `mode` column: the globals a mode applied (null for plain viewports). */\nfunction modeJsonOf(viewport: Viewport | undefined): string | null {\n return viewport?.globals ? JSON.stringify(viewport.globals) : null;\n}\n\nfunction viewportByName(ctx: CaptureContext, name: string): Viewport {\n return ctx.viewports.find((v) => v.name === name) ?? { name, width: 0, height: 0 };\n}\n\n/** Where the capture's screenshot lives: already stored by the orchestrator, or stored now. */\nasync function screenshotLocation(ctx: CaptureContext, capture: RenderedSnapshot): Promise<string> {\n if (capture.storedPath) {\n return capture.storedPath;\n }\n assertScreenshotBuffer(capture.screenshot);\n return await storeScreenshot(ctx.storage, ctx.project.id, capture.screenshot);\n}\n\nasync function persistSnapshot(ctx: CaptureContext, capture: RenderedSnapshot): Promise<void> {\n const viewport = capture.viewport ?? viewportByName(ctx, capture.viewportName);\n const browser = browserOf(capture);\n const screenshot = await screenshotLocation(ctx, capture);\n\n const baseline = await resolveBaseline(ctx, capture.story.id, capture.viewportName, browser);\n if (!baseline) {\n await createWithoutBaseline(ctx, capture, viewport, screenshot);\n return;\n }\n await createWithBaseline(ctx, capture, viewport, screenshot, baseline);\n}\n\nasync function resolveBaseline(\n ctx: CaptureContext,\n storyId: string,\n viewport: string,\n browser: string,\n): Promise<Baseline | null> {\n const baselines = new BaselineModel(ctx.db, ctx.tables, ctx.storage, ctx.secret);\n return await baselines.resolve(\n ctx.project.id,\n storyId,\n viewport,\n ctx.build.gitBranch,\n ctx.project.gitDefaultBranch,\n browser,\n );\n}\n\nasync function createWithoutBaseline(\n ctx: CaptureContext,\n capture: RenderedSnapshot,\n viewport: Viewport,\n screenshot: string,\n): Promise<void> {\n const snapshots = new SnapshotModel(ctx.db, ctx.tables);\n const browser = browserOf(capture);\n const accepted = ctx.build.isDefault || (await bootstrapAccepts(ctx, capture));\n const status = accepted ? \"approved\" : \"new\";\n const now = fingerprintOf(capture, viewport);\n const snapshot = await snapshots.create(ctx.project.id, ctx.build.id, {\n browser,\n durationMs: capture.durationMs ?? null,\n a11yViolations: a11yJsonOf(capture),\n storyId: capture.story.id,\n storyName: capture.story.name,\n storyTitle: capture.story.title,\n storyImportPath: capture.story.importPath ?? \"\",\n designUrl: capture.story.designUrl ?? null,\n viewportName: capture.viewportName,\n viewportWidth: viewport.width,\n viewportHeight: viewport.height,\n screenshotPath: screenshot,\n infraHash: now.infraHash,\n mode: modeJsonOf(viewport),\n baselineVersion: NO_BASELINE,\n });\n await snapshots.setStatus(snapshot.id, status);\n\n if (accepted) {\n const baselines = new BaselineModel(ctx.db, ctx.tables, ctx.storage, ctx.secret);\n await baselines.upsert(\n ctx.project.id,\n capture.story.id,\n capture.viewportName,\n ctx.build.gitBranch,\n snapshot.id,\n screenshot,\n now.infraHash,\n { mode: modeJsonOf(viewport), browser, fingerprint: now.json },\n );\n }\n}\n\n/** A capture's fingerprint: its components, their hash (stored as `infraHash`), and the stored JSON. */\ninterface CaptureFingerprint {\n fingerprint: Fingerprint;\n infraHash: string;\n json: string;\n}\n\nfunction fingerprintOf(capture: RenderedSnapshot, viewport: Viewport): CaptureFingerprint {\n return captureFingerprintOf(buildFingerprint(browserOf(capture), viewport, capture.environment));\n}\n\n/** A fingerprint as stored: its hash (the `infraHash`) and JSON. */\nfunction captureFingerprintOf(fingerprint: Fingerprint): CaptureFingerprint {\n return {\n fingerprint,\n infraHash: fingerprintHash(fingerprint),\n json: JSON.stringify(fingerprint),\n };\n}\n\n/**\n * What changed in the capture environment since the baseline was recorded, or null when nothing\n * did. Compared component by component, only where both sides report a value: a capture with no\n * environment (spooled before a browser crash) is unknown, not a change. A baseline from before\n * fingerprints cannot be compared, so it is never reported as changed; it is backfilled instead.\n */\nfunction environmentChange(baseline: Baseline, now: CaptureFingerprint): EnvironmentChange | null {\n const from = parseFingerprint(baseline.fingerprint);\n if (!from) {\n return null;\n }\n const changed = changedFields(from, now.fingerprint);\n return changed.length > 0 ? { changed, from, to: now.fingerprint } : null;\n}\n\n/**\n * A changed environment migrates the baseline without a diff only on the default branch, which is\n * authoritative and auto-approves its own changes anyway. A feature branch still diffs: a real\n * change is reported as `changed` with the environment change attached, never auto-accepted.\n */\nasync function createWithBaseline(\n ctx: CaptureContext,\n capture: RenderedSnapshot,\n viewport: Viewport,\n screenshot: string,\n baseline: Baseline,\n): Promise<void> {\n const now = fingerprintOf(capture, viewport);\n const change = environmentChange(baseline, now);\n const comparison = await freezeComparison(ctx.storage, ctx.project.id, ctx.build.id, baseline, {\n defaultBranch: ctx.project.gitDefaultBranch,\n mode: modeJsonOf(viewport),\n target: browserOf(capture),\n width: viewport.width,\n height: viewport.height,\n captureFingerprint: now.json,\n sourceSnapshotId: await sourceSnapshotId(ctx.db, baseline),\n });\n const input = { ctx, capture, viewport, screenshot, baseline, now, change, comparison };\n if (change && ctx.build.isDefault) {\n await persistMigration(input, change);\n } else {\n await persistDiff(input);\n }\n}\n\ninterface BaselineCapture {\n ctx: CaptureContext;\n capture: RenderedSnapshot;\n viewport: Viewport;\n screenshot: string;\n baseline: Baseline;\n now: CaptureFingerprint;\n change: EnvironmentChange | null;\n comparison: { comparisonImagePath: string; comparisonMetadata: string };\n}\n\n/** The fingerprint to write on the baseline: the capture's, keeping components it did not report. */\nfunction baselineFingerprintOf(input: BaselineCapture): CaptureFingerprint {\n const earlier = parseFingerprint(input.baseline.fingerprint);\n return captureFingerprintOf(mergeFingerprint(earlier, input.now.fingerprint));\n}\n\nfunction snapshotInput(input: BaselineCapture): Parameters<SnapshotModel[\"create\"]>[2] {\n const { capture, viewport, screenshot, baseline, now } = input;\n return {\n browser: browserOf(capture),\n durationMs: capture.durationMs ?? null,\n a11yViolations: a11yJsonOf(capture),\n storyId: capture.story.id,\n storyName: capture.story.name,\n storyTitle: capture.story.title,\n storyImportPath: capture.story.importPath ?? \"\",\n designUrl: capture.story.designUrl ?? null,\n viewportName: capture.viewportName,\n viewportWidth: viewport.width,\n viewportHeight: viewport.height,\n screenshotPath: screenshot,\n infraHash: now.infraHash,\n mode: modeJsonOf(viewport),\n baselineId: baseline.id,\n baselineVersion: baseline.updatedAt,\n ...input.comparison,\n };\n}\n\n/**\n * The default branch's capture environment changed (browser build, fonts, platform, scale): pixels\n * would differ with no code change, so no diff is computed. The snapshot is accepted, records what\n * changed, and the new screenshot becomes the baseline.\n */\nasync function persistMigration(input: BaselineCapture, change: EnvironmentChange): Promise<void> {\n const { ctx, capture, viewport, screenshot, now } = input;\n const snapshots = new SnapshotModel(ctx.db, ctx.tables);\n const snapshot = await snapshots.create(ctx.project.id, ctx.build.id, {\n ...snapshotInput(input),\n envChange: JSON.stringify(change),\n });\n await snapshots.update(snapshot.id, {\n status: \"unchanged\",\n diffPath: null,\n diffPixels: 0,\n diffRatio: 0,\n diffPassed: true,\n infraHash: now.infraHash,\n });\n await upsertDefaultBaseline(ctx, snapshot.id, capture, screenshot, baselineFingerprintOf(input));\n ctx.logger?.debug(\n { storyId: capture.story.id, viewport: viewport.name, changed: change.changed },\n \"capture environment changed; accepted without a diff\",\n );\n}\n\n/** Diff against the baseline; a feature-branch environment change rides along as `envChange`. */\nasync function persistDiff(input: BaselineCapture): Promise<void> {\n const { ctx, capture, screenshot, baseline, now, change } = input;\n const result = await diffStoredScreenshots(\n ctx.storage,\n ctx.project,\n baseline.screenshotPath === screenshot ? screenshot : input.comparison.comparisonImagePath,\n screenshot,\n capture.story.parameters?.diffThreshold,\n );\n const snapshots = new SnapshotModel(ctx.db, ctx.tables);\n const snapshot = await snapshots.create(ctx.project.id, ctx.build.id, {\n ...snapshotInput(input),\n ...(change ? { envChange: JSON.stringify(change) } : {}),\n });\n const diff = diffPath(\n ctx.project.id,\n ctx.build.id,\n capture.story.id,\n capture.viewportName,\n browserOf(capture),\n );\n const overlay = await writeDiffOverlay(ctx.storage, diff, result, result.passed);\n await snapshots.update(snapshot.id, {\n status: diffStatus(result.passed, ctx.build.isDefault),\n diffPath: overlay,\n diffPixels: result.diffPixels,\n diffRatio: result.diffRatio,\n diffPassed: result.passed,\n infraHash: now.infraHash,\n });\n if (ctx.build.isDefault) {\n await refreshDefaultBaseline(input, snapshot.id, result.passed);\n }\n}\n\n/**\n * After a default-branch diff: a failed diff moves the baseline to the new screenshot; a passing\n * one backfills the fingerprint when it was missing or had components the capture now reports.\n */\nasync function refreshDefaultBaseline(\n input: BaselineCapture,\n snapshotId: string,\n passed: boolean,\n): Promise<void> {\n const { ctx, capture, screenshot, baseline } = input;\n const target = baselineFingerprintOf(input);\n if (!passed) {\n await upsertDefaultBaseline(ctx, snapshotId, capture, screenshot, target);\n } else if (!baseline.fingerprint || baseline.infraHash !== target.infraHash) {\n try {\n await new BaselineModel(ctx.db, ctx.tables, ctx.storage, ctx.secret).backfillFingerprint(\n baseline,\n target.infraHash,\n target.json,\n );\n } catch (error) {\n if (!(error instanceof BaselineWriteConflict || error instanceof ReferenceGenerationConflict))\n throw error;\n ctx.logger?.debug(\n { err: error, baselineId: baseline.id },\n \"skipped baseline fingerprint backfill after concurrent write\",\n );\n }\n }\n}\n\n/** Default-branch builds are authoritative: the captured screenshot becomes the baseline. */\nasync function upsertDefaultBaseline(\n ctx: CaptureContext,\n snapshotId: string,\n capture: RenderedSnapshot,\n screenshot: string,\n now: CaptureFingerprint,\n): Promise<void> {\n const baselines = new BaselineModel(ctx.db, ctx.tables, ctx.storage, ctx.secret);\n await baselines.upsert(\n ctx.project.id,\n capture.story.id,\n capture.viewportName,\n ctx.build.gitBranch,\n snapshotId,\n screenshot,\n now.infraHash,\n { mode: modeJsonOf(capture.viewport), browser: browserOf(capture), fingerprint: now.json },\n );\n}\n\n/** Snapshot status after diffing: default-branch changes are auto-approved, not queued for review. */\nexport function diffStatus(passed: boolean, isDefault: boolean): SnapshotStatus {\n if (passed) {\n return \"unchanged\";\n }\n return isDefault ? \"approved\" : \"changed\";\n}\n\n/** A failed capture fails the build; default-branch builds otherwise auto-approve. */\nfunction finalBuildStatus(\n isDefault: boolean,\n failedCount: number,\n hasCaptures: boolean,\n build: { changedCount: number },\n): BuildStatus {\n if (failedCount > 0) {\n return \"failed\";\n }\n if (isDefault || (hasCaptures && build.changedCount === 0)) {\n return \"approved\";\n }\n return \"reviewing\";\n}\n\n/**\n * Close out a capture: roll the snapshot counts up, set the build's final status from the failed\n * story ids, and (on the default branch) drop baselines of stories that no longer exist.\n */\nexport async function finalizeBuild(\n ctx: CaptureContext,\n storyIds: ReadonlySet<string>,\n failedStoryIds: ReadonlySet<string>,\n flakyFailedStoryIds: ReadonlySet<string> = new Set(),\n a11yFailedStoryIds: ReadonlySet<string> = new Set(),\n): Promise<void> {\n const builds = new BuildModel(ctx.db, ctx.tables, ctx.logger);\n if ((await builds.get(ctx.build.id))?.status === \"cancelled\") {\n ctx.logger?.debug({ buildId: ctx.build.id }, \"build is cancelled; finalize skipped\");\n return;\n }\n const build = await builds.updateCounts(ctx.build.id);\n const hasCaptures = storyIds.size > 0;\n const status = finalBuildStatus(ctx.build.isDefault, failedStoryIds.size, hasCaptures, build);\n // Flaky and a11y failures do not block: log warning if any failed non-blocking\n if (flakyFailedStoryIds.size > 0 && failedStoryIds.size === 0) {\n ctx.logger?.warn(\n { flakyStoryIds: [...flakyFailedStoryIds] },\n \"flaky stories failed (non-blocking)\",\n );\n await emitAttemptLog(ctx.recordLog, ctx.logger, \"warn\", \"flaky stories failed (non-blocking)\", {\n flakyStoryIds: [...flakyFailedStoryIds],\n });\n }\n if (a11yFailedStoryIds.size > 0 && failedStoryIds.size === 0) {\n ctx.logger?.warn(\n { a11yStoryIds: [...a11yFailedStoryIds] },\n \"a11y violations found (non-blocking)\",\n );\n await emitAttemptLog(\n ctx.recordLog,\n ctx.logger,\n \"warn\",\n \"a11y violations found (non-blocking)\",\n {\n a11yStoryIds: [...a11yFailedStoryIds],\n },\n );\n }\n await builds.setStatus(ctx.build.id, status);\n\n if (ctx.build.isDefault && hasCaptures) {\n const baselines = new BaselineModel(ctx.db, ctx.tables, ctx.storage, ctx.secret);\n await baselines.removeOrphans(ctx.project.id, new Set(storyIds));\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ /**
4
+ * Called by a capture as it moves: a screenshot taken, a snapshot persisted, a stage done. Cheap
5
+ * and synchronous, so the hot loops of render and persist can call it on every item.
6
+ */
7
+ type CaptureProgress = () => void;
8
+ export { CaptureProgress };
9
+ //# sourceMappingURL=progress.d.mts.map
@@ -0,0 +1,27 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ // src/capture/progress.ts
5
+ /** Shortest gap between two lease touches: a forked child sends one IPC message per touch. */
6
+ const PROGRESS_THROTTLE_MS = 1e3;
7
+ /**
8
+ * The progress callback for a job: touches its lease, at most once per `everyMs`. The first call
9
+ * always goes through. Undefined without a lease, so callers skip the work entirely.
10
+ *
11
+ * Liveness comes from the job reporting work done rather than from a timer, so a hung render or
12
+ * diff still lets the lease expire (see {@link JobLease}).
13
+ */
14
+ function leaseProgress(lease, everyMs = PROGRESS_THROTTLE_MS, now = Date.now) {
15
+ if (!lease) return;
16
+ let last = Number.NEGATIVE_INFINITY;
17
+ return () => {
18
+ const at = now();
19
+ if (at - last >= everyMs) {
20
+ last = at;
21
+ lease.touch();
22
+ }
23
+ };
24
+ }
25
+ export { PROGRESS_THROTTLE_MS, leaseProgress };
26
+
27
+ //# sourceMappingURL=progress.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"progress.mjs","names":[],"sources":["../../src/capture/progress.ts"],"sourcesContent":["/** Progress reporting for a running capture job, kept apart from its logging. */\nimport type { JobLease } from \"./job-lease.ts\";\n\n/**\n * Called by a capture as it moves: a screenshot taken, a snapshot persisted, a stage done. Cheap\n * and synchronous, so the hot loops of render and persist can call it on every item.\n */\nexport type CaptureProgress = () => void;\n\n/** Shortest gap between two lease touches: a forked child sends one IPC message per touch. */\nexport const PROGRESS_THROTTLE_MS = 1000;\n\n/**\n * The progress callback for a job: touches its lease, at most once per `everyMs`. The first call\n * always goes through. Undefined without a lease, so callers skip the work entirely.\n *\n * Liveness comes from the job reporting work done rather than from a timer, so a hung render or\n * diff still lets the lease expire (see {@link JobLease}).\n */\nexport function leaseProgress(\n lease?: JobLease,\n everyMs: number = PROGRESS_THROTTLE_MS,\n now: () => number = Date.now,\n): CaptureProgress | undefined {\n if (!lease) {\n return undefined;\n }\n let last = Number.NEGATIVE_INFINITY;\n return () => {\n const at = now();\n if (at - last >= everyMs) {\n last = at;\n lease.touch();\n }\n };\n}\n"],"mappings":";;;;;AAUA,MAAa,uBAAuB;;;;;;;;AASpC,SAAgB,cACd,OACA,UAAkB,sBAClB,MAAoB,KAAK,KACI;CAC7B,IAAI,CAAC,OACH;CAEF,IAAI,OAAO,OAAO;CAClB,aAAa;EACX,MAAM,KAAK,IAAI;EACf,IAAI,KAAK,QAAQ,SAAS;GACxB,OAAO;GACP,MAAM,MAAM;EACd;CACF;AACF"}
@@ -0,0 +1,37 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ /**
4
+ * The one rule for when publishing accepts a build (ADR 0006, decision 2). Pure: callers read the
5
+ * facts, apply the verdict, and stay free of branching.
6
+ */
7
+ /** Facts the verdict depends on. */
8
+ interface PublishFacts {
9
+ /** Whether publishing succeeded. */
10
+ published: boolean;
11
+ /** Whether a capture will run for this build (`testing` is on); capture then owns the status. */
12
+ captureWillRun: boolean;
13
+ /** Whether the build is on the project's default branch. */
14
+ defaultBranch: boolean;
15
+ /** The project's review switch (`projects.review_enabled`, ADR 0013, decision 1). */
16
+ reviewEnabled: boolean;
17
+ /** The build's status right now. */
18
+ status: string;
19
+ }
20
+ /**
21
+ * What publishing does to the build's status.
22
+ *
23
+ * - A capture will run: `unchanged`, the capture sets the status.
24
+ * - Not `pending` any more (already accepted, failed, cancelled or reviewed): `unchanged`, so a repeated
25
+ * publish (job redelivery after a lost lease) changes nothing.
26
+ * - Publish failed: `failed`.
27
+ * - Published on the default branch: `approved`, whatever the review switch says.
28
+ * - Published on another branch with review off: `approved`, nobody will sign it off.
29
+ * - Published on another branch with review on: `unchanged`, the build waits as `pending` for a
30
+ * build-level verdict (ADR 0013, decision 3).
31
+ *
32
+ * Accepting here is not an approval: no event, webhook or notification (`build:created` announced
33
+ * the upload), so nothing is asked of a reviewer and nothing is reported as one.
34
+ */
35
+ declare function publishVerdict(facts: PublishFacts): "approved" | "failed" | "unchanged";
36
+ export { PublishFacts, publishVerdict };
37
+ //# sourceMappingURL=accept.d.mts.map
@@ -0,0 +1,27 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ // src/capture/publish/accept.ts
5
+ /**
6
+ * What publishing does to the build's status.
7
+ *
8
+ * - A capture will run: `unchanged`, the capture sets the status.
9
+ * - Not `pending` any more (already accepted, failed, cancelled or reviewed): `unchanged`, so a repeated
10
+ * publish (job redelivery after a lost lease) changes nothing.
11
+ * - Publish failed: `failed`.
12
+ * - Published on the default branch: `approved`, whatever the review switch says.
13
+ * - Published on another branch with review off: `approved`, nobody will sign it off.
14
+ * - Published on another branch with review on: `unchanged`, the build waits as `pending` for a
15
+ * build-level verdict (ADR 0013, decision 3).
16
+ *
17
+ * Accepting here is not an approval: no event, webhook or notification (`build:created` announced
18
+ * the upload), so nothing is asked of a reviewer and nothing is reported as one.
19
+ */
20
+ function publishVerdict(facts) {
21
+ if (facts.captureWillRun || facts.status !== "pending") return "unchanged";
22
+ if (!facts.published) return "failed";
23
+ return facts.defaultBranch || !facts.reviewEnabled ? "approved" : "unchanged";
24
+ }
25
+ export { publishVerdict };
26
+
27
+ //# sourceMappingURL=accept.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accept.mjs","names":[],"sources":["../../../src/capture/publish/accept.ts"],"sourcesContent":["/**\n * The one rule for when publishing accepts a build (ADR 0006, decision 2). Pure: callers read the\n * facts, apply the verdict, and stay free of branching.\n */\n\n/** Facts the verdict depends on. */\nexport interface PublishFacts {\n /** Whether publishing succeeded. */\n published: boolean;\n /** Whether a capture will run for this build (`testing` is on); capture then owns the status. */\n captureWillRun: boolean;\n /** Whether the build is on the project's default branch. */\n defaultBranch: boolean;\n /** The project's review switch (`projects.review_enabled`, ADR 0013, decision 1). */\n reviewEnabled: boolean;\n /** The build's status right now. */\n status: string;\n}\n\n/**\n * What publishing does to the build's status.\n *\n * - A capture will run: `unchanged`, the capture sets the status.\n * - Not `pending` any more (already accepted, failed, cancelled or reviewed): `unchanged`, so a repeated\n * publish (job redelivery after a lost lease) changes nothing.\n * - Publish failed: `failed`.\n * - Published on the default branch: `approved`, whatever the review switch says.\n * - Published on another branch with review off: `approved`, nobody will sign it off.\n * - Published on another branch with review on: `unchanged`, the build waits as `pending` for a\n * build-level verdict (ADR 0013, decision 3).\n *\n * Accepting here is not an approval: no event, webhook or notification (`build:created` announced\n * the upload), so nothing is asked of a reviewer and nothing is reported as one.\n */\nexport function publishVerdict(facts: PublishFacts): \"approved\" | \"failed\" | \"unchanged\" {\n if (facts.captureWillRun || facts.status !== \"pending\") {\n return \"unchanged\";\n }\n if (!facts.published) {\n return \"failed\";\n }\n return facts.defaultBranch || !facts.reviewEnabled ? \"approved\" : \"unchanged\";\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAkCA,SAAgB,eAAe,OAA0D;CACvF,IAAI,MAAM,kBAAkB,MAAM,WAAW,WAC3C,OAAO;CAET,IAAI,CAAC,MAAM,WACT,OAAO;CAET,OAAO,MAAM,iBAAiB,CAAC,MAAM,gBAAgB,aAAa;AACpE"}
@@ -0,0 +1,44 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ import { Logger } from "../../logger.mjs";
4
+ import { DatabaseAdapter } from "../../adapter/database.mjs";
5
+ import { Build } from "../../schema/build.mjs";
6
+ import { Project } from "../../schema/project.mjs";
7
+ /** How publishing ended. */
8
+ type PublishOutcome = {
9
+ ok: true;
10
+ } | {
11
+ ok: false;
12
+ error: string;
13
+ };
14
+ /** What an `afterPublish` hook is given. */
15
+ interface AfterPublishContext {
16
+ /** The build as it is after the verdict was applied. */
17
+ build: Build;
18
+ project: Project;
19
+ outcome: PublishOutcome;
20
+ logger: Logger;
21
+ }
22
+ /** Runs once per finished publish (extension point of the catalog). Failures are logged, never thrown on. */
23
+ type AfterPublishHook = (context: AfterPublishContext) => Promise<void>;
24
+ /** Finishes a build's publishing. */
25
+ interface PublishCompleter {
26
+ /**
27
+ * Record the end of a build's publishing. Resolves to the build, or null when it is gone.
28
+ * Safe to repeat: a build that left `pending` keeps its status.
29
+ */
30
+ complete(buildId: string, outcome: PublishOutcome): Promise<Build | null>;
31
+ }
32
+ /** What {@link createPublishCompleter} needs. */
33
+ interface PublishCompleterOptions {
34
+ db: DatabaseAdapter;
35
+ /** Whether a capture runs for uploads (`testing`); capture then owns the build status. */
36
+ captureWillRun: boolean;
37
+ /** Hooks, read on every call so later registrations count. */
38
+ afterPublish: readonly AfterPublishHook[];
39
+ logger: Logger;
40
+ }
41
+ /** A completer over `options.db`. */
42
+ declare function createPublishCompleter(options: PublishCompleterOptions): PublishCompleter;
43
+ export { AfterPublishContext, AfterPublishHook, PublishCompleter, PublishCompleterOptions, PublishOutcome, createPublishCompleter };
44
+ //# sourceMappingURL=complete.d.mts.map
@@ -0,0 +1,63 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { BuildModel } from "../../models/build.mjs";
5
+ import { ProjectModel } from "../../models/project.mjs";
6
+ import { publishVerdict } from "./accept.mjs";
7
+ // src/capture/publish/complete.ts
8
+ /** A completer over `options.db`. */
9
+ function createPublishCompleter(options) {
10
+ return { complete: async (buildId, outcome) => {
11
+ const target = await load(options.db, buildId);
12
+ if (!target) {
13
+ options.logger.warn({ buildId }, "build is gone; nothing to complete");
14
+ return null;
15
+ }
16
+ const build = await applyVerdict(options, target.build, target.project, outcome);
17
+ await runHooks(options, {
18
+ build,
19
+ project: target.project,
20
+ outcome
21
+ });
22
+ return build;
23
+ } };
24
+ }
25
+ async function load(db, buildId) {
26
+ const build = await new BuildModel(db).get(buildId);
27
+ const project = build ? await new ProjectModel(db).get(build.projectId) : null;
28
+ return build && project ? {
29
+ build,
30
+ project
31
+ } : null;
32
+ }
33
+ /** Move the build per the rule. No approval event or webhook: `build:created` announced the upload. */
34
+ async function applyVerdict(options, build, project, outcome) {
35
+ const verdict = publishVerdict({
36
+ published: outcome.ok,
37
+ captureWillRun: options.captureWillRun,
38
+ defaultBranch: build.gitBranch === project.gitDefaultBranch,
39
+ reviewEnabled: project.reviewEnabled !== false,
40
+ status: build.status
41
+ });
42
+ if (!outcome.ok) options.logger.error({
43
+ buildId: build.id,
44
+ error: outcome.error,
45
+ verdict
46
+ }, "publish failed");
47
+ if (verdict === "unchanged") return build;
48
+ return await new BuildModel(options.db).setStatus(build.id, verdict);
49
+ }
50
+ async function runHooks(options, context) {
51
+ const logger = options.logger.child({ buildId: context.build.id });
52
+ for (const hook of options.afterPublish) try {
53
+ await hook({
54
+ ...context,
55
+ logger
56
+ });
57
+ } catch (error) {
58
+ logger.error({ err: error }, "afterPublish hook failed");
59
+ }
60
+ }
61
+ export { createPublishCompleter };
62
+
63
+ //# sourceMappingURL=complete.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"complete.mjs","names":[],"sources":["../../../src/capture/publish/complete.ts"],"sourcesContent":["/**\n * Publishing's end: apply {@link publishVerdict} to the build and run the `afterPublish` hooks.\n * Every publish route (inline zip, `publish` job, file-by-file manifest) ends here, so this is the\n * single owner of the acceptance rule.\n */\nimport type { DatabaseAdapter } from \"../../adapters/database.ts\";\nimport type { Logger } from \"../../logger.ts\";\nimport { BuildModel } from \"../../models/build.ts\";\nimport { ProjectModel } from \"../../models/project.ts\";\nimport type { Build } from \"../../schema/build.ts\";\nimport type { Project } from \"../../schema/project.ts\";\nimport { publishVerdict } from \"./accept.ts\";\n\n/** How publishing ended. */\nexport type PublishOutcome = { ok: true } | { ok: false; error: string };\n\n/** What an `afterPublish` hook is given. */\nexport interface AfterPublishContext {\n /** The build as it is after the verdict was applied. */\n build: Build;\n project: Project;\n outcome: PublishOutcome;\n logger: Logger;\n}\n\n/** Runs once per finished publish (extension point of the catalog). Failures are logged, never thrown on. */\nexport type AfterPublishHook = (context: AfterPublishContext) => Promise<void>;\n\n/** Finishes a build's publishing. */\nexport interface PublishCompleter {\n /**\n * Record the end of a build's publishing. Resolves to the build, or null when it is gone.\n * Safe to repeat: a build that left `pending` keeps its status.\n */\n complete(buildId: string, outcome: PublishOutcome): Promise<Build | null>;\n}\n\n/** What {@link createPublishCompleter} needs. */\nexport interface PublishCompleterOptions {\n db: DatabaseAdapter;\n /** Whether a capture runs for uploads (`testing`); capture then owns the build status. */\n captureWillRun: boolean;\n /** Hooks, read on every call so later registrations count. */\n afterPublish: readonly AfterPublishHook[];\n logger: Logger;\n}\n\n/** A completer over `options.db`. */\nexport function createPublishCompleter(options: PublishCompleterOptions): PublishCompleter {\n return {\n complete: async (buildId, outcome) => {\n const target = await load(options.db, buildId);\n if (!target) {\n options.logger.warn({ buildId }, \"build is gone; nothing to complete\");\n return null;\n }\n const build = await applyVerdict(options, target.build, target.project, outcome);\n await runHooks(options, { build, project: target.project, outcome });\n return build;\n },\n };\n}\n\nasync function load(\n db: DatabaseAdapter,\n buildId: string,\n): Promise<{ build: Build; project: Project } | null> {\n const build = await new BuildModel(db).get(buildId);\n const project = build ? await new ProjectModel(db).get(build.projectId) : null;\n return build && project ? { build, project } : null;\n}\n\n/** Move the build per the rule. No approval event or webhook: `build:created` announced the upload. */\nasync function applyVerdict(\n options: PublishCompleterOptions,\n build: Build,\n project: Project,\n outcome: PublishOutcome,\n): Promise<Build> {\n const verdict = publishVerdict({\n published: outcome.ok,\n captureWillRun: options.captureWillRun,\n defaultBranch: build.gitBranch === project.gitDefaultBranch,\n reviewEnabled: project.reviewEnabled !== false,\n status: build.status,\n });\n if (!outcome.ok) {\n options.logger.error({ buildId: build.id, error: outcome.error, verdict }, \"publish failed\");\n }\n if (verdict === \"unchanged\") {\n return build;\n }\n return await new BuildModel(options.db).setStatus(build.id, verdict);\n}\n\nasync function runHooks(\n options: PublishCompleterOptions,\n context: Omit<AfterPublishContext, \"logger\">,\n): Promise<void> {\n const logger = options.logger.child({ buildId: context.build.id });\n for (const hook of options.afterPublish) {\n try {\n // eslint-disable-next-line no-await-in-loop -- hooks run in registration order\n await hook({ ...context, logger });\n } catch (error) {\n logger.error({ err: error }, \"afterPublish hook failed\");\n }\n }\n}\n"],"mappings":";;;;;;;;AAgDA,SAAgB,uBAAuB,SAAoD;CACzF,OAAO,EACL,UAAU,OAAO,SAAS,YAAY;EACpC,MAAM,SAAS,MAAM,KAAK,QAAQ,IAAI,OAAO;EAC7C,IAAI,CAAC,QAAQ;GACX,QAAQ,OAAO,KAAK,EAAE,QAAQ,GAAG,oCAAoC;GACrE,OAAO;EACT;EACA,MAAM,QAAQ,MAAM,aAAa,SAAS,OAAO,OAAO,OAAO,SAAS,OAAO;EAC/E,MAAM,SAAS,SAAS;GAAE;GAAO,SAAS,OAAO;GAAS;EAAQ,CAAC;EACnE,OAAO;CACT,EACF;AACF;AAEA,eAAe,KACb,IACA,SACoD;CACpD,MAAM,QAAQ,MAAM,IAAI,WAAW,EAAE,CAAC,CAAC,IAAI,OAAO;CAClD,MAAM,UAAU,QAAQ,MAAM,IAAI,aAAa,EAAE,CAAC,CAAC,IAAI,MAAM,SAAS,IAAI;CAC1E,OAAO,SAAS,UAAU;EAAE;EAAO;CAAQ,IAAI;AACjD;;AAGA,eAAe,aACb,SACA,OACA,SACA,SACgB;CAChB,MAAM,UAAU,eAAe;EAC7B,WAAW,QAAQ;EACnB,gBAAgB,QAAQ;EACxB,eAAe,MAAM,cAAc,QAAQ;EAC3C,eAAe,QAAQ,kBAAkB;EACzC,QAAQ,MAAM;CAChB,CAAC;CACD,IAAI,CAAC,QAAQ,IACX,QAAQ,OAAO,MAAM;EAAE,SAAS,MAAM;EAAI,OAAO,QAAQ;EAAO;CAAQ,GAAG,gBAAgB;CAE7F,IAAI,YAAY,aACd,OAAO;CAET,OAAO,MAAM,IAAI,WAAW,QAAQ,EAAE,CAAC,CAAC,UAAU,MAAM,IAAI,OAAO;AACrE;AAEA,eAAe,SACb,SACA,SACe;CACf,MAAM,SAAS,QAAQ,OAAO,MAAM,EAAE,SAAS,QAAQ,MAAM,GAAG,CAAC;CACjE,KAAK,MAAM,QAAQ,QAAQ,cACzB,IAAI;EAEF,MAAM,KAAK;GAAE,GAAG;GAAS;EAAO,CAAC;CACnC,SAAS,OAAO;EACd,OAAO,MAAM,EAAE,KAAK,MAAM,GAAG,0BAA0B;CACzD;AAEJ"}
@@ -0,0 +1,18 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ import { ExtractionLimits } from "./limits.mjs";
4
+ import { Readable } from "node:stream";
5
+ /** What an extraction produced. */
6
+ interface ExtractionResult {
7
+ entries: number;
8
+ bytes: number;
9
+ }
10
+ /**
11
+ * Extract `source` (a zip stream) under `root`, which must exist.
12
+ *
13
+ * @throws {ZipLimitError} When a cap is exceeded or an entry path escapes `root`; the source is
14
+ * destroyed at once, and the files written so far are left for the caller's scratch cleanup.
15
+ */
16
+ declare function extractZipStream(source: Readable, root: string, limits: ExtractionLimits): Promise<ExtractionResult>;
17
+ export { ExtractionResult, extractZipStream };
18
+ //# sourceMappingURL=extract.d.mts.map
@@ -0,0 +1,120 @@
1
+ import { dirname, isAbsolute, join, resolve, sep } from "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { ZipInvalidError } from "./invalid-zip.mjs";
5
+ import { ZipLimitError } from "./limits.mjs";
6
+ import { mkdir } from "node:fs/promises";
7
+ import { createWriteStream } from "node:fs";
8
+ import { Transform } from "node:stream";
9
+ import { pipeline } from "node:stream/promises";
10
+ import { Parse } from "unzipper";
11
+ // src/capture/publish/extract.ts
12
+ /**
13
+ * Streaming, capped extraction of an uploaded Storybook zip into a directory. Entry count and
14
+ * uncompressed bytes are counted while the archive streams (never from a header or from
15
+ * `Content-Length`), and a path that leaves the target is refused.
16
+ */
17
+ /**
18
+ * Extract `source` (a zip stream) under `root`, which must exist.
19
+ *
20
+ * @throws {ZipLimitError} When a cap is exceeded or an entry path escapes `root`; the source is
21
+ * destroyed at once, and the files written so far are left for the caller's scratch cleanup.
22
+ */
23
+ async function extractZipStream(source, root, limits) {
24
+ const run = new Extraction(resolve(root), limits);
25
+ const failure = await run.parse(source);
26
+ const outcomes = await Promise.allSettled(run.writes);
27
+ if (failure) throw failure;
28
+ const writeError = outcomes.find((outcome) => outcome.status === "rejected");
29
+ if (writeError) throw writeError.reason;
30
+ return {
31
+ entries: run.entries,
32
+ bytes: run.bytes
33
+ };
34
+ }
35
+ function ignore() {}
36
+ /** State of one extraction. */
37
+ var Extraction = class {
38
+ entries = 0;
39
+ bytes = 0;
40
+ writes = [];
41
+ root;
42
+ limits;
43
+ #failed = false;
44
+ #fail = ignore;
45
+ constructor(root, limits) {
46
+ this.root = root;
47
+ this.limits = limits;
48
+ }
49
+ /** Resolves with the first failure, or null once the archive was read to its end. */
50
+ parse(source) {
51
+ return new Promise((settle) => {
52
+ const parser = Parse();
53
+ this.#fail = (error) => {
54
+ if (!this.#failed) {
55
+ this.#failed = true;
56
+ source.destroy();
57
+ parser.destroy();
58
+ settle(error);
59
+ }
60
+ };
61
+ parser.on("entry", (entry) => {
62
+ this.#entry(entry);
63
+ });
64
+ parser.on("error", () => {
65
+ this.#fail(new ZipInvalidError("Invalid Storybook zip"));
66
+ });
67
+ parser.on("close", () => {
68
+ settle(null);
69
+ });
70
+ source.on("error", this.#fail);
71
+ source.pipe(parser);
72
+ });
73
+ }
74
+ #entry(entry) {
75
+ this.entries += 1;
76
+ const problem = this.#problemWith(entry);
77
+ if (problem) this.#fail(problem);
78
+ if (this.#failed || entry.type === "Directory") {
79
+ entry.autodrain();
80
+ return;
81
+ }
82
+ this.#write(entry);
83
+ }
84
+ #problemWith(entry) {
85
+ if (this.entries > this.limits.maxEntries) return new ZipLimitError(`Zip holds more than ${this.limits.maxEntries} entries`);
86
+ if (unsafePath(this.root, entry.path)) return new ZipLimitError(`Blocked path traversal in uploaded Storybook: ${entry.path}`);
87
+ return null;
88
+ }
89
+ #write(entry) {
90
+ const target = join(this.root, entry.path);
91
+ const done = (async () => {
92
+ await mkdir(dirname(target), { recursive: true });
93
+ await pipeline(entry, this.#counter(), createWriteStream(target));
94
+ })();
95
+ this.writes.push(done);
96
+ done.catch(ignore);
97
+ }
98
+ /** Counts the bytes that pass; past the cap the whole extraction fails at once. */
99
+ #counter() {
100
+ return new Transform({ transform: (chunk, _encoding, callback) => {
101
+ this.bytes += chunk.length;
102
+ if (this.bytes > this.limits.maxBytes) {
103
+ const error = new ZipLimitError(`Zip expands beyond ${this.limits.maxBytes} bytes`);
104
+ this.#fail(error);
105
+ callback(error);
106
+ return;
107
+ }
108
+ callback(null, chunk);
109
+ } });
110
+ }
111
+ };
112
+ /** Absolute paths, drive letters and anything resolving outside `root`. */
113
+ function unsafePath(root, entryPath) {
114
+ if (isAbsolute(entryPath) || /^[a-zA-Z]:/u.test(entryPath)) return true;
115
+ const candidate = resolve(join(root, entryPath));
116
+ return candidate !== root && !candidate.startsWith(root + sep);
117
+ }
118
+ export { extractZipStream };
119
+
120
+ //# sourceMappingURL=extract.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract.mjs","names":[],"sources":["../../../src/capture/publish/extract.ts"],"sourcesContent":["/**\n * Streaming, capped extraction of an uploaded Storybook zip into a directory. Entry count and\n * uncompressed bytes are counted while the archive streams (never from a header or from\n * `Content-Length`), and a path that leaves the target is refused.\n */\nimport { createWriteStream } from \"node:fs\";\nimport { mkdir } from \"node:fs/promises\";\nimport { dirname, isAbsolute, join, resolve, sep } from \"node:path\";\nimport { Transform, type Readable } from \"node:stream\";\nimport { pipeline } from \"node:stream/promises\";\nimport { Parse, type Entry } from \"unzipper\";\nimport { ZipInvalidError } from \"./invalid-zip.ts\";\nimport { ZipLimitError, type ExtractionLimits } from \"./limits.ts\";\n\n/** What an extraction produced. */\nexport interface ExtractionResult {\n entries: number;\n bytes: number;\n}\n\n/**\n * Extract `source` (a zip stream) under `root`, which must exist.\n *\n * @throws {ZipLimitError} When a cap is exceeded or an entry path escapes `root`; the source is\n * destroyed at once, and the files written so far are left for the caller's scratch cleanup.\n */\nexport async function extractZipStream(\n source: Readable,\n root: string,\n limits: ExtractionLimits,\n): Promise<ExtractionResult> {\n const run = new Extraction(resolve(root), limits);\n const failure = await run.parse(source);\n // Writes may still be in flight after a failure; settle them before anyone removes the files.\n const outcomes = await Promise.allSettled(run.writes);\n if (failure) {\n throw failure;\n }\n const writeError = outcomes.find(\n (outcome): outcome is PromiseRejectedResult => outcome.status === \"rejected\",\n );\n if (writeError) {\n throw writeError.reason;\n }\n return { entries: run.entries, bytes: run.bytes };\n}\n\nfunction ignore(): void {}\n\n/** State of one extraction. */\nclass Extraction {\n entries = 0;\n bytes = 0;\n readonly writes: Promise<void>[] = [];\n readonly root: string;\n readonly limits: ExtractionLimits;\n #failed = false;\n #fail: (error: unknown) => void = ignore;\n\n constructor(root: string, limits: ExtractionLimits) {\n this.root = root;\n this.limits = limits;\n }\n\n /** Resolves with the first failure, or null once the archive was read to its end. */\n // oxlint-disable-next-line typescript/promise-function-async -- executor-style wrapper around stream events\n parse(source: Readable): Promise<unknown> {\n return new Promise((settle) => {\n const parser = Parse();\n this.#fail = (error: unknown): void => {\n if (!this.#failed) {\n this.#failed = true;\n source.destroy();\n parser.destroy();\n settle(error);\n }\n };\n parser.on(\"entry\", (entry: Entry) => {\n this.#entry(entry);\n });\n parser.on(\"error\", () => {\n this.#fail(new ZipInvalidError(\"Invalid Storybook zip\"));\n });\n parser.on(\"close\", () => {\n settle(null);\n });\n source.on(\"error\", this.#fail);\n source.pipe(parser);\n });\n }\n\n #entry(entry: Entry): void {\n this.entries += 1;\n const problem = this.#problemWith(entry);\n if (problem) {\n this.#fail(problem);\n }\n if (this.#failed || entry.type === \"Directory\") {\n entry.autodrain();\n return;\n }\n this.#write(entry);\n }\n\n #problemWith(entry: Entry): ZipLimitError | null {\n if (this.entries > this.limits.maxEntries) {\n return new ZipLimitError(`Zip holds more than ${this.limits.maxEntries} entries`);\n }\n if (unsafePath(this.root, entry.path)) {\n return new ZipLimitError(`Blocked path traversal in uploaded Storybook: ${entry.path}`);\n }\n return null;\n }\n\n #write(entry: Entry): void {\n const target = join(this.root, entry.path);\n const done = (async (): Promise<void> => {\n await mkdir(dirname(target), { recursive: true });\n await pipeline(entry, this.#counter(), createWriteStream(target));\n })();\n this.writes.push(done);\n done.catch(ignore); // Aggregated through allSettled by the caller\n }\n\n /** Counts the bytes that pass; past the cap the whole extraction fails at once. */\n #counter(): Transform {\n return new Transform({\n transform: (chunk: Buffer, _encoding, callback) => {\n this.bytes += chunk.length;\n if (this.bytes > this.limits.maxBytes) {\n const error = new ZipLimitError(`Zip expands beyond ${this.limits.maxBytes} bytes`);\n this.#fail(error);\n callback(error);\n return;\n }\n callback(null, chunk);\n },\n });\n }\n}\n\n/** Absolute paths, drive letters and anything resolving outside `root`. */\nfunction unsafePath(root: string, entryPath: string): boolean {\n if (isAbsolute(entryPath) || /^[a-zA-Z]:/u.test(entryPath)) {\n return true;\n }\n const candidate = resolve(join(root, entryPath));\n return candidate !== root && !candidate.startsWith(root + sep);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AA0BA,eAAsB,iBACpB,QACA,MACA,QAC2B;CAC3B,MAAM,MAAM,IAAI,WAAW,QAAQ,IAAI,GAAG,MAAM;CAChD,MAAM,UAAU,MAAM,IAAI,MAAM,MAAM;CAEtC,MAAM,WAAW,MAAM,QAAQ,WAAW,IAAI,MAAM;CACpD,IAAI,SACF,MAAM;CAER,MAAM,aAAa,SAAS,MACzB,YAA8C,QAAQ,WAAW,UACpE;CACA,IAAI,YACF,MAAM,WAAW;CAEnB,OAAO;EAAE,SAAS,IAAI;EAAS,OAAO,IAAI;CAAM;AAClD;AAEA,SAAS,SAAe,CAAC;;AAGzB,IAAM,aAAN,MAAiB;CACf,UAAU;CACV,QAAQ;CACR,SAAmC,CAAC;CACpC;CACA;CACA,UAAU;CACV,QAAkC;CAElC,YAAY,MAAc,QAA0B;EAClD,KAAK,OAAO;EACZ,KAAK,SAAS;CAChB;;CAIA,MAAM,QAAoC;EACxC,OAAO,IAAI,SAAS,WAAW;GAC7B,MAAM,SAAS,MAAM;GACrB,KAAK,SAAS,UAAyB;IACrC,IAAI,CAAC,KAAK,SAAS;KACjB,KAAK,UAAU;KACf,OAAO,QAAQ;KACf,OAAO,QAAQ;KACf,OAAO,KAAK;IACd;GACF;GACA,OAAO,GAAG,UAAU,UAAiB;IACnC,KAAK,OAAO,KAAK;GACnB,CAAC;GACD,OAAO,GAAG,eAAe;IACvB,KAAK,MAAM,IAAI,gBAAgB,uBAAuB,CAAC;GACzD,CAAC;GACD,OAAO,GAAG,eAAe;IACvB,OAAO,IAAI;GACb,CAAC;GACD,OAAO,GAAG,SAAS,KAAK,KAAK;GAC7B,OAAO,KAAK,MAAM;EACpB,CAAC;CACH;CAEA,OAAO,OAAoB;EACzB,KAAK,WAAW;EAChB,MAAM,UAAU,KAAK,aAAa,KAAK;EACvC,IAAI,SACF,KAAK,MAAM,OAAO;EAEpB,IAAI,KAAK,WAAW,MAAM,SAAS,aAAa;GAC9C,MAAM,UAAU;GAChB;EACF;EACA,KAAK,OAAO,KAAK;CACnB;CAEA,aAAa,OAAoC;EAC/C,IAAI,KAAK,UAAU,KAAK,OAAO,YAC7B,OAAO,IAAI,cAAc,uBAAuB,KAAK,OAAO,WAAW,SAAS;EAElF,IAAI,WAAW,KAAK,MAAM,MAAM,IAAI,GAClC,OAAO,IAAI,cAAc,iDAAiD,MAAM,MAAM;EAExF,OAAO;CACT;CAEA,OAAO,OAAoB;EACzB,MAAM,SAAS,KAAK,KAAK,MAAM,MAAM,IAAI;EACzC,MAAM,QAAQ,YAA2B;GACvC,MAAM,MAAM,QAAQ,MAAM,GAAG,EAAE,WAAW,KAAK,CAAC;GAChD,MAAM,SAAS,OAAO,KAAK,SAAS,GAAG,kBAAkB,MAAM,CAAC;EAClE,EAAA,CAAG;EACH,KAAK,OAAO,KAAK,IAAI;EACrB,KAAK,MAAM,MAAM;CACnB;;CAGA,WAAsB;EACpB,OAAO,IAAI,UAAU,EACnB,YAAY,OAAe,WAAW,aAAa;GACjD,KAAK,SAAS,MAAM;GACpB,IAAI,KAAK,QAAQ,KAAK,OAAO,UAAU;IACrC,MAAM,QAAQ,IAAI,cAAc,sBAAsB,KAAK,OAAO,SAAS,OAAO;IAClF,KAAK,MAAM,KAAK;IAChB,SAAS,KAAK;IACd;GACF;GACA,SAAS,MAAM,KAAK;EACtB,EACF,CAAC;CACH;AACF;;AAGA,SAAS,WAAW,MAAc,WAA4B;CAC5D,IAAI,WAAW,SAAS,KAAK,cAAc,KAAK,SAAS,GACvD,OAAO;CAET,MAAM,YAAY,QAAQ,KAAK,MAAM,SAAS,CAAC;CAC/C,OAAO,cAAc,QAAQ,CAAC,UAAU,WAAW,OAAO,GAAG;AAC/D"}
@@ -0,0 +1,26 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ import { Logger } from "../../logger.mjs";
4
+ import { DatabaseAdapter } from "../../adapter/database.mjs";
5
+ import { StorageAdapter } from "../../adapter/storage.mjs";
6
+ import { ExtractionLimits } from "./limits.mjs";
7
+ import { KindHandler } from "../consumer/types.mjs";
8
+ import { PublishCompleter } from "./complete.mjs";
9
+ /** What {@link createPublishHandler} needs. */
10
+ interface PublishHandlerOptions {
11
+ db: DatabaseAdapter;
12
+ storage: StorageAdapter;
13
+ scratchDir: string;
14
+ limits: ExtractionLimits;
15
+ completer: PublishCompleter;
16
+ logger: Logger;
17
+ }
18
+ /**
19
+ * The handler. Resolving acks the job. A zip that breaks a cap is a final answer (retrying cannot
20
+ * help): the build is failed and the job acked. Any other error is retried by the consumer, and
21
+ * the build is failed on the last attempt. A redelivery after a lost lease repeats the extraction
22
+ * (same keys) and leaves an already accepted build alone.
23
+ */
24
+ declare function createPublishHandler(options: PublishHandlerOptions): KindHandler;
25
+ export { PublishHandlerOptions, createPublishHandler };
26
+ //# sourceMappingURL=handler.d.mts.map
@@ -0,0 +1,61 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { BuildModel } from "../../models/build.mjs";
5
+ import { ZipLimitError } from "./limits.mjs";
6
+ import { publishStoredZip } from "./run.mjs";
7
+ // src/capture/publish/handler.ts
8
+ /** A `publish` job needs the build to publish. */
9
+ function hasBuild(job) {
10
+ return typeof job.buildId === "string" && job.buildId.length > 0;
11
+ }
12
+ /**
13
+ * The handler. Resolving acks the job. A zip that breaks a cap is a final answer (retrying cannot
14
+ * help): the build is failed and the job acked. Any other error is retried by the consumer, and
15
+ * the build is failed on the last attempt. A redelivery after a lost lease repeats the extraction
16
+ * (same keys) and leaves an already accepted build alone.
17
+ */
18
+ function createPublishHandler(options) {
19
+ return {
20
+ accepts: hasBuild,
21
+ run: async (job, context) => {
22
+ const build = await new BuildModel(options.db).get(job.buildId);
23
+ if (!build) {
24
+ context.logger.warn("build is gone; dropping publish job");
25
+ return;
26
+ }
27
+ await publishOnce(options, job, context, build.projectId);
28
+ }
29
+ };
30
+ }
31
+ async function publishOnce(options, job, context, projectId) {
32
+ const { completer, storage, scratchDir, limits, db } = options;
33
+ try {
34
+ await publishStoredZip({
35
+ storage,
36
+ scratchDir,
37
+ limits,
38
+ db,
39
+ logger: context.logger,
40
+ touch: context.touch
41
+ }, {
42
+ projectId,
43
+ buildId: job.buildId
44
+ });
45
+ } catch (error) {
46
+ const permanent = error instanceof ZipLimitError;
47
+ if (permanent || (job.attempt ?? 1) >= (job.maxAttempts ?? 1)) await completer.complete(job.buildId, {
48
+ ok: false,
49
+ error: messageOf(error)
50
+ });
51
+ if (permanent) return;
52
+ throw error;
53
+ }
54
+ await completer.complete(job.buildId, { ok: true });
55
+ }
56
+ function messageOf(error) {
57
+ return error instanceof Error ? error.message : String(error);
58
+ }
59
+ export { createPublishHandler };
60
+
61
+ //# sourceMappingURL=handler.mjs.map