@storyshelf/core 0.6.1 → 0.7.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 (631) hide show
  1. package/README.md +49 -32
  2. package/dist/adapter/capture-queue.d.mts +44 -2
  3. package/dist/adapter/capture-runner.d.mts +127 -10
  4. package/dist/adapter/capture-runner.mjs +5 -12
  5. package/dist/adapter/database.d.mts +27 -8
  6. package/dist/adapter/database.mjs.map +1 -1
  7. package/dist/adapter/design.d.mts +85 -0
  8. package/dist/adapter/design.mjs +28 -0
  9. package/dist/adapter/design.mjs.map +1 -0
  10. package/dist/adapter/metadata.d.mts +3 -10
  11. package/dist/adapter/notifier.d.mts +6 -3
  12. package/dist/adapter/notifier.mjs +5 -1
  13. package/dist/adapter/oauth.d.mts +52 -0
  14. package/dist/adapter/setup.d.mts +4 -5
  15. package/dist/adapter/setup.mjs +2 -1
  16. package/dist/adapter/setup.mjs.map +1 -1
  17. package/dist/adapter/webhook-events.mjs +1 -1
  18. package/dist/adapters/capture-target-offers.d.mts +28 -0
  19. package/dist/adapters/capture-target-offers.mjs +58 -0
  20. package/dist/adapters/capture-target-offers.mjs.map +1 -0
  21. package/dist/adapters/capture-target.d.mts +71 -0
  22. package/dist/adapters/capture-target.mjs +76 -0
  23. package/dist/adapters/capture-target.mjs.map +1 -0
  24. package/dist/adapters/notifier/build-comment.d.mts +24 -0
  25. package/dist/adapters/notifier/build-comment.mjs +24 -0
  26. package/dist/adapters/notifier/build-comment.mjs.map +1 -0
  27. package/dist/adapters/notifier/build-state.d.mts +99 -0
  28. package/dist/adapters/notifier/build-state.mjs +218 -0
  29. package/dist/adapters/notifier/build-state.mjs.map +1 -0
  30. package/dist/adapters/notifier/events.d.mts +4 -2
  31. package/dist/adapters/notifier/events.mjs +31 -18
  32. package/dist/adapters/notifier/events.mjs.map +1 -1
  33. package/dist/adapters/notifier/provider.d.mts +53 -7
  34. package/dist/adapters/notifier/provider.mjs +12 -0
  35. package/dist/adapters/notifier/provider.mjs.map +1 -0
  36. package/dist/adapters/notifier/state.d.mts +24 -0
  37. package/dist/adapters/notifier/state.mjs +66 -0
  38. package/dist/adapters/notifier/state.mjs.map +1 -0
  39. package/dist/adapters/notifier/types.d.mts +51 -2
  40. package/dist/adapters/remote-browser.d.mts +87 -0
  41. package/dist/adapters/remote-browser.mjs +103 -0
  42. package/dist/adapters/remote-browser.mjs.map +1 -0
  43. package/dist/adapters/render-runs.d.mts +25 -0
  44. package/dist/adapters/render-runs.mjs +41 -0
  45. package/dist/adapters/render-runs.mjs.map +1 -0
  46. package/dist/adapters/setup-runner.mjs +2 -4
  47. package/dist/adapters/setup-runner.mjs.map +1 -1
  48. package/dist/adapters/sources.d.mts +12 -0
  49. package/dist/adapters/sources.mjs +20 -0
  50. package/dist/adapters/sources.mjs.map +1 -0
  51. package/dist/adapters/validate.mjs +25 -18
  52. package/dist/adapters/validate.mjs.map +1 -1
  53. package/dist/ai.d.mts +105 -0
  54. package/dist/ai.mjs +19 -0
  55. package/dist/ai.mjs.map +1 -0
  56. package/dist/auth.d.mts +8 -1
  57. package/dist/auth.mjs +14 -1
  58. package/dist/auth.mjs.map +1 -0
  59. package/dist/capture/a11y/axe-source.d.mts +18 -0
  60. package/dist/capture/a11y/axe-source.mjs +37 -0
  61. package/dist/capture/a11y/axe-source.mjs.map +1 -0
  62. package/dist/capture/a11y/in-page.d.mts +27 -0
  63. package/dist/capture/a11y/in-page.mjs +60 -0
  64. package/dist/capture/a11y/in-page.mjs.map +1 -0
  65. package/dist/capture/a11y/map.d.mts +24 -0
  66. package/dist/capture/a11y/map.mjs +48 -0
  67. package/dist/capture/a11y/map.mjs.map +1 -0
  68. package/dist/capture/a11y/options.d.mts +23 -0
  69. package/dist/capture/a11y/options.mjs +75 -0
  70. package/dist/capture/a11y/options.mjs.map +1 -0
  71. package/dist/capture/adapter.d.mts +41 -3
  72. package/dist/capture/adapter.mjs.map +1 -1
  73. package/dist/capture/affected.d.mts +2 -0
  74. package/dist/capture/affected.mjs +13 -4
  75. package/dist/capture/affected.mjs.map +1 -1
  76. package/dist/capture/attempt-logger.mjs +84 -0
  77. package/dist/capture/attempt-logger.mjs.map +1 -0
  78. package/dist/capture/browsers.d.mts +35 -0
  79. package/dist/capture/browsers.mjs +67 -0
  80. package/dist/capture/browsers.mjs.map +1 -0
  81. package/dist/capture/build-state/deliver.d.mts +32 -0
  82. package/dist/capture/build-state/deliver.mjs +118 -0
  83. package/dist/capture/build-state/deliver.mjs.map +1 -0
  84. package/dist/capture/build-state/index.d.mts +8 -0
  85. package/dist/capture/build-state/notify-job.d.mts +56 -0
  86. package/dist/capture/build-state/notify-job.mjs +133 -0
  87. package/dist/capture/build-state/notify-job.mjs.map +1 -0
  88. package/dist/capture/build-state/publisher.d.mts +23 -0
  89. package/dist/capture/build-state/publisher.mjs +107 -0
  90. package/dist/capture/build-state/publisher.mjs.map +1 -0
  91. package/dist/capture/build-state/resume.d.mts +9 -0
  92. package/dist/capture/build-state/resume.mjs +47 -0
  93. package/dist/capture/build-state/resume.mjs.map +1 -0
  94. package/dist/capture/build-state/types.d.mts +49 -0
  95. package/dist/capture/catalog/catalogue.d.mts +22 -0
  96. package/dist/capture/catalog/catalogue.mjs +53 -0
  97. package/dist/capture/catalog/catalogue.mjs.map +1 -0
  98. package/dist/capture/catalog/components.d.mts +23 -0
  99. package/dist/capture/catalog/components.mjs +55 -0
  100. package/dist/capture/catalog/components.mjs.map +1 -0
  101. package/dist/capture/catalog/history.d.mts +30 -0
  102. package/dist/capture/catalog/history.mjs +51 -0
  103. package/dist/capture/catalog/history.mjs.map +1 -0
  104. package/dist/capture/catalog/hook.d.mts +14 -0
  105. package/dist/capture/catalog/hook.mjs +19 -0
  106. package/dist/capture/catalog/hook.mjs.map +1 -0
  107. package/dist/capture/catalog/index.d.mts +11 -0
  108. package/dist/capture/catalog/load.d.mts +26 -0
  109. package/dist/capture/catalog/load.mjs +49 -0
  110. package/dist/capture/catalog/load.mjs.map +1 -0
  111. package/dist/capture/catalog/parse.d.mts +15 -0
  112. package/dist/capture/catalog/parse.mjs +56 -0
  113. package/dist/capture/catalog/parse.mjs.map +1 -0
  114. package/dist/capture/catalog/read.d.mts +12 -0
  115. package/dist/capture/catalog/read.mjs +33 -0
  116. package/dist/capture/catalog/read.mjs.map +1 -0
  117. package/dist/capture/catalog/types.d.mts +33 -0
  118. package/dist/capture/consumer/capture-handler.d.mts +58 -0
  119. package/dist/capture/consumer/capture-handler.mjs +118 -0
  120. package/dist/capture/consumer/capture-handler.mjs.map +1 -0
  121. package/dist/capture/consumer/idle.d.mts +17 -0
  122. package/dist/capture/consumer/idle.mjs +25 -0
  123. package/dist/capture/consumer/idle.mjs.map +1 -0
  124. package/dist/capture/consumer/index.d.mts +13 -0
  125. package/dist/capture/consumer/index.mjs +139 -0
  126. package/dist/capture/consumer/index.mjs.map +1 -0
  127. package/dist/capture/consumer/loop.mjs +110 -0
  128. package/dist/capture/consumer/loop.mjs.map +1 -0
  129. package/dist/capture/consumer/metrics.d.mts +41 -0
  130. package/dist/capture/consumer/metrics.mjs +38 -0
  131. package/dist/capture/consumer/metrics.mjs.map +1 -0
  132. package/dist/capture/consumer/retry.d.mts +33 -0
  133. package/dist/capture/consumer/retry.mjs +34 -0
  134. package/dist/capture/consumer/retry.mjs.map +1 -0
  135. package/dist/capture/consumer/run-job.mjs +195 -0
  136. package/dist/capture/consumer/run-job.mjs.map +1 -0
  137. package/dist/capture/consumer/startup.mjs +38 -0
  138. package/dist/capture/consumer/startup.mjs.map +1 -0
  139. package/dist/capture/consumer/types.d.mts +97 -0
  140. package/dist/capture/crash-failure.d.mts +25 -0
  141. package/dist/capture/crash-failure.mjs +70 -0
  142. package/dist/capture/crash-failure.mjs.map +1 -0
  143. package/dist/capture/db-queue/cadence.d.mts +32 -0
  144. package/dist/capture/db-queue/cadence.mjs +38 -0
  145. package/dist/capture/db-queue/cadence.mjs.map +1 -0
  146. package/dist/capture/db-queue/claim.d.mts +9 -0
  147. package/dist/capture/db-queue/claim.mjs +132 -0
  148. package/dist/capture/db-queue/claim.mjs.map +1 -0
  149. package/dist/capture/db-queue/clock.mjs +12 -0
  150. package/dist/capture/db-queue/clock.mjs.map +1 -0
  151. package/dist/capture/db-queue/codec.mjs +76 -0
  152. package/dist/capture/db-queue/codec.mjs.map +1 -0
  153. package/dist/capture/db-queue/index.d.mts +20 -0
  154. package/dist/capture/db-queue/index.mjs +89 -0
  155. package/dist/capture/db-queue/index.mjs.map +1 -0
  156. package/dist/capture/db-queue/keeper.d.mts +36 -0
  157. package/dist/capture/db-queue/keeper.mjs +92 -0
  158. package/dist/capture/db-queue/keeper.mjs.map +1 -0
  159. package/dist/capture/db-queue/lease.mjs +46 -0
  160. package/dist/capture/db-queue/lease.mjs.map +1 -0
  161. package/dist/capture/db-queue/lifecycle.mjs +36 -0
  162. package/dist/capture/db-queue/lifecycle.mjs.map +1 -0
  163. package/dist/capture/db-queue/operations.mjs +94 -0
  164. package/dist/capture/db-queue/operations.mjs.map +1 -0
  165. package/dist/capture/db-queue/poll-loop.d.mts +30 -0
  166. package/dist/capture/db-queue/poll-loop.mjs +89 -0
  167. package/dist/capture/db-queue/poll-loop.mjs.map +1 -0
  168. package/dist/capture/db-queue/poll.d.mts +10 -0
  169. package/dist/capture/db-queue/poll.mjs +69 -0
  170. package/dist/capture/db-queue/poll.mjs.map +1 -0
  171. package/dist/capture/db-queue/timers.d.mts +12 -0
  172. package/dist/capture/db-queue/timers.mjs +37 -0
  173. package/dist/capture/db-queue/timers.mjs.map +1 -0
  174. package/dist/capture/db-queue/types.d.mts +83 -0
  175. package/dist/capture/db-queue/types.mjs +12 -0
  176. package/dist/capture/db-queue/types.mjs.map +1 -0
  177. package/dist/capture/db-queue/wake.mjs +48 -0
  178. package/dist/capture/db-queue/wake.mjs.map +1 -0
  179. package/dist/capture/design.mjs +39 -0
  180. package/dist/capture/design.mjs.map +1 -0
  181. package/dist/capture/diff-record.mjs +23 -3
  182. package/dist/capture/diff-record.mjs.map +1 -1
  183. package/dist/capture/dispatch.d.mts +12 -7
  184. package/dist/capture/dispatch.mjs +86 -107
  185. package/dist/capture/dispatch.mjs.map +1 -1
  186. package/dist/capture/enrich.mjs +60 -0
  187. package/dist/capture/enrich.mjs.map +1 -0
  188. package/dist/capture/failure-sets.mjs +22 -0
  189. package/dist/capture/failure-sets.mjs.map +1 -0
  190. package/dist/capture/fingerprint.d.mts +42 -0
  191. package/dist/capture/fingerprint.mjs +83 -0
  192. package/dist/capture/fingerprint.mjs.map +1 -0
  193. package/dist/capture/forked/channel.d.mts +25 -0
  194. package/dist/capture/forked/child.d.mts +34 -0
  195. package/dist/capture/forked/child.mjs +202 -0
  196. package/dist/capture/forked/child.mjs.map +1 -0
  197. package/dist/capture/forked/crash-tracker.mjs +23 -0
  198. package/dist/capture/forked/crash-tracker.mjs.map +1 -0
  199. package/dist/capture/forked/decision.d.mts +26 -0
  200. package/dist/capture/forked/decision.mjs +27 -0
  201. package/dist/capture/forked/decision.mjs.map +1 -0
  202. package/dist/capture/forked/exit.mjs +12 -0
  203. package/dist/capture/forked/exit.mjs.map +1 -0
  204. package/dist/capture/forked/fork.d.mts +31 -0
  205. package/dist/capture/forked/fork.mjs +93 -0
  206. package/dist/capture/forked/fork.mjs.map +1 -0
  207. package/dist/capture/forked/forked-capture.d.mts +40 -0
  208. package/dist/capture/forked/forked-capture.mjs +240 -0
  209. package/dist/capture/forked/forked-capture.mjs.map +1 -0
  210. package/dist/capture/forked/lease-book.mjs +46 -0
  211. package/dist/capture/forked/lease-book.mjs.map +1 -0
  212. package/dist/capture/forked/protocol.d.mts +38 -0
  213. package/dist/capture/forked/protocol.mjs +25 -0
  214. package/dist/capture/forked/protocol.mjs.map +1 -0
  215. package/dist/capture/forked/runs.mjs +75 -0
  216. package/dist/capture/forked/runs.mjs.map +1 -0
  217. package/dist/capture/isolate.mjs +141 -0
  218. package/dist/capture/isolate.mjs.map +1 -0
  219. package/dist/capture/job-lease.d.mts +28 -0
  220. package/dist/capture/job-lease.mjs +57 -0
  221. package/dist/capture/job-lease.mjs.map +1 -0
  222. package/dist/capture/matrix.mjs +298 -0
  223. package/dist/capture/matrix.mjs.map +1 -0
  224. package/dist/capture/modes.d.mts +25 -0
  225. package/dist/capture/modes.mjs +107 -0
  226. package/dist/capture/modes.mjs.map +1 -0
  227. package/dist/capture/orchestrator.d.mts +37 -5
  228. package/dist/capture/orchestrator.mjs +118 -107
  229. package/dist/capture/orchestrator.mjs.map +1 -1
  230. package/dist/capture/pipeline.d.mts +9 -2
  231. package/dist/capture/pipeline.mjs +250 -45
  232. package/dist/capture/pipeline.mjs.map +1 -1
  233. package/dist/capture/progress.d.mts +11 -0
  234. package/dist/capture/progress.mjs +28 -0
  235. package/dist/capture/progress.mjs.map +1 -0
  236. package/dist/capture/publish/accept.d.mts +39 -0
  237. package/dist/capture/publish/accept.mjs +28 -0
  238. package/dist/capture/publish/accept.mjs.map +1 -0
  239. package/dist/capture/publish/complete.d.mts +46 -0
  240. package/dist/capture/publish/complete.mjs +64 -0
  241. package/dist/capture/publish/complete.mjs.map +1 -0
  242. package/dist/capture/publish/extract.d.mts +20 -0
  243. package/dist/capture/publish/extract.mjs +119 -0
  244. package/dist/capture/publish/extract.mjs.map +1 -0
  245. package/dist/capture/publish/handler.d.mts +28 -0
  246. package/dist/capture/publish/handler.mjs +62 -0
  247. package/dist/capture/publish/handler.mjs.map +1 -0
  248. package/dist/capture/publish/index.d.mts +10 -0
  249. package/dist/capture/publish/limits.d.mts +37 -0
  250. package/dist/capture/publish/limits.mjs +39 -0
  251. package/dist/capture/publish/limits.mjs.map +1 -0
  252. package/dist/capture/publish/published.d.mts +15 -0
  253. package/dist/capture/publish/published.mjs +18 -0
  254. package/dist/capture/publish/published.mjs.map +1 -0
  255. package/dist/capture/publish/run.d.mts +33 -0
  256. package/dist/capture/publish/run.mjs +30 -0
  257. package/dist/capture/publish/run.mjs.map +1 -0
  258. package/dist/capture/rediff.d.mts +1 -1
  259. package/dist/capture/rediff.mjs +4 -3
  260. package/dist/capture/rediff.mjs.map +1 -1
  261. package/dist/capture/render.mjs +53 -0
  262. package/dist/capture/render.mjs.map +1 -0
  263. package/dist/capture/scratch.d.mts +44 -0
  264. package/dist/capture/scratch.mjs +138 -0
  265. package/dist/capture/scratch.mjs.map +1 -0
  266. package/dist/capture/shard-plan.mjs +128 -0
  267. package/dist/capture/shard-plan.mjs.map +1 -0
  268. package/dist/capture/shards/context.mjs +51 -0
  269. package/dist/capture/shards/context.mjs.map +1 -0
  270. package/dist/capture/shards/execute.mjs +174 -0
  271. package/dist/capture/shards/execute.mjs.map +1 -0
  272. package/dist/capture/shards/fan-out.mjs +126 -0
  273. package/dist/capture/shards/fan-out.mjs.map +1 -0
  274. package/dist/capture/shards/history.mjs +35 -0
  275. package/dist/capture/shards/history.mjs.map +1 -0
  276. package/dist/capture/shards/reduce.mjs +97 -0
  277. package/dist/capture/shards/reduce.mjs.map +1 -0
  278. package/dist/capture/sizing.mjs +1 -11
  279. package/dist/capture/sizing.mjs.map +1 -1
  280. package/dist/capture/skip-checks.mjs +2 -65
  281. package/dist/capture/skip-checks.mjs.map +1 -1
  282. package/dist/capture/statics.d.mts +7 -8
  283. package/dist/capture/statics.mjs +25 -71
  284. package/dist/capture/statics.mjs.map +1 -1
  285. package/dist/capture/storybook-urls.d.mts +40 -0
  286. package/dist/capture/storybook-urls.mjs +41 -0
  287. package/dist/capture/storybook-urls.mjs.map +1 -0
  288. package/dist/capture/storybook.d.mts +12 -4
  289. package/dist/capture/storybook.mjs +56 -16
  290. package/dist/capture/storybook.mjs.map +1 -1
  291. package/dist/capture/target.mjs +21 -0
  292. package/dist/capture/target.mjs.map +1 -0
  293. package/dist/capture/targets.d.mts +25 -0
  294. package/dist/capture/targets.mjs +32 -0
  295. package/dist/capture/targets.mjs.map +1 -0
  296. package/dist/capture/tests.d.mts +37 -0
  297. package/dist/capture/tests.mjs +126 -0
  298. package/dist/capture/tests.mjs.map +1 -0
  299. package/dist/capture/token-chunks.mjs +74 -0
  300. package/dist/capture/token-chunks.mjs.map +1 -0
  301. package/dist/capture.d.mts +60 -7
  302. package/dist/capture.mjs +48 -3
  303. package/dist/config.d.mts +121 -12
  304. package/dist/config.mjs +9 -6
  305. package/dist/config.mjs.map +1 -1
  306. package/dist/diff.d.mts +9 -3
  307. package/dist/diff.mjs +16 -3
  308. package/dist/diff.mjs.map +1 -0
  309. package/dist/index.d.mts +1 -1
  310. package/dist/insights/budget.d.mts +20 -0
  311. package/dist/insights/budget.mjs +33 -0
  312. package/dist/insights/budget.mjs.map +1 -0
  313. package/dist/insights/envelope.d.mts +59 -0
  314. package/dist/insights/envelope.mjs +60 -0
  315. package/dist/insights/envelope.mjs.map +1 -0
  316. package/dist/insights/evidence-health.d.mts +32 -0
  317. package/dist/insights/evidence-health.mjs +28 -0
  318. package/dist/insights/evidence-health.mjs.map +1 -0
  319. package/dist/insights/evidence.d.mts +55 -0
  320. package/dist/insights/evidence.mjs +64 -0
  321. package/dist/insights/evidence.mjs.map +1 -0
  322. package/dist/insights/hash.d.mts +18 -0
  323. package/dist/insights/hash.mjs +21 -0
  324. package/dist/insights/hash.mjs.map +1 -0
  325. package/dist/insights/limits.d.mts +24 -0
  326. package/dist/insights/limits.mjs +24 -0
  327. package/dist/insights/limits.mjs.map +1 -0
  328. package/dist/insights/prompts/v1.d.mts +13 -0
  329. package/dist/insights/prompts/v1.mjs +34 -0
  330. package/dist/insights/prompts/v1.mjs.map +1 -0
  331. package/dist/insights/redact.d.mts +9 -0
  332. package/dist/insights/redact.mjs +24 -0
  333. package/dist/insights/redact.mjs.map +1 -0
  334. package/dist/insights/text.mjs +44 -0
  335. package/dist/insights/text.mjs.map +1 -0
  336. package/dist/insights.d.mts +11 -0
  337. package/dist/insights.mjs +12 -0
  338. package/dist/logger.mjs +17 -1
  339. package/dist/logger.mjs.map +1 -1
  340. package/dist/models/ai-budget-alert.d.mts +23 -0
  341. package/dist/models/ai-budget-alert.mjs +38 -0
  342. package/dist/models/ai-budget-alert.mjs.map +1 -0
  343. package/dist/models/ai-usage.d.mts +40 -0
  344. package/dist/models/ai-usage.mjs +61 -0
  345. package/dist/models/ai-usage.mjs.map +1 -0
  346. package/dist/models/baseline-archive.d.mts +84 -0
  347. package/dist/models/baseline-archive.mjs +106 -0
  348. package/dist/models/baseline-archive.mjs.map +1 -0
  349. package/dist/models/baseline-export.d.mts +19 -0
  350. package/dist/models/baseline-export.mjs +136 -0
  351. package/dist/models/baseline-export.mjs.map +1 -0
  352. package/dist/models/baseline-import.mjs +118 -0
  353. package/dist/models/baseline-import.mjs.map +1 -0
  354. package/dist/models/baseline-transfer.d.mts +49 -0
  355. package/dist/models/baseline-transfer.mjs +121 -0
  356. package/dist/models/baseline-transfer.mjs.map +1 -0
  357. package/dist/models/baseline.d.mts +32 -5
  358. package/dist/models/baseline.mjs +65 -20
  359. package/dist/models/baseline.mjs.map +1 -1
  360. package/dist/models/build-component.d.mts +28 -0
  361. package/dist/models/build-component.mjs +55 -0
  362. package/dist/models/build-component.mjs.map +1 -0
  363. package/dist/models/build-review-status.d.mts +29 -0
  364. package/dist/models/build-review-status.mjs +37 -0
  365. package/dist/models/build-review-status.mjs.map +1 -0
  366. package/dist/models/build-review.d.mts +28 -0
  367. package/dist/models/build-review.mjs +54 -0
  368. package/dist/models/build-review.mjs.map +1 -0
  369. package/dist/models/build.d.mts +25 -3
  370. package/dist/models/build.mjs +50 -11
  371. package/dist/models/build.mjs.map +1 -1
  372. package/dist/models/capture-attempt.d.mts +7 -1
  373. package/dist/models/capture-attempt.mjs +29 -4
  374. package/dist/models/capture-attempt.mjs.map +1 -1
  375. package/dist/models/capture-job.d.mts +106 -0
  376. package/dist/models/capture-job.mjs +288 -0
  377. package/dist/models/capture-job.mjs.map +1 -0
  378. package/dist/models/capture-log.d.mts +1 -1
  379. package/dist/models/capture-log.mjs +4 -3
  380. package/dist/models/capture-log.mjs.map +1 -1
  381. package/dist/models/capture-shard.d.mts +62 -0
  382. package/dist/models/capture-shard.mjs +190 -0
  383. package/dist/models/capture-shard.mjs.map +1 -0
  384. package/dist/models/changeset.d.mts +48 -0
  385. package/dist/models/changeset.mjs +81 -0
  386. package/dist/models/changeset.mjs.map +1 -0
  387. package/dist/models/columns.mjs +19 -0
  388. package/dist/models/columns.mjs.map +1 -0
  389. package/dist/models/comment.d.mts +4 -1
  390. package/dist/models/comment.mjs +7 -4
  391. package/dist/models/comment.mjs.map +1 -1
  392. package/dist/models/component.d.mts +43 -0
  393. package/dist/models/component.mjs +60 -0
  394. package/dist/models/component.mjs.map +1 -0
  395. package/dist/models/connections.d.mts +41 -0
  396. package/dist/models/connections.mjs +112 -0
  397. package/dist/models/connections.mjs.map +1 -0
  398. package/dist/models/content-ref.d.mts +1 -1
  399. package/dist/models/content-ref.mjs +1 -1
  400. package/dist/models/content-ref.mjs.map +1 -1
  401. package/dist/models/credentials.mjs +12 -2
  402. package/dist/models/credentials.mjs.map +1 -1
  403. package/dist/models/design-credentials.d.mts +41 -0
  404. package/dist/models/design-credentials.mjs +86 -0
  405. package/dist/models/design-credentials.mjs.map +1 -0
  406. package/dist/models/design-health.d.mts +16 -0
  407. package/dist/models/design-health.mjs +25 -0
  408. package/dist/models/design-health.mjs.map +1 -0
  409. package/dist/models/design-nodes.d.mts +83 -0
  410. package/dist/models/design-nodes.mjs +222 -0
  411. package/dist/models/design-nodes.mjs.map +1 -0
  412. package/dist/models/design-renders.mjs +74 -0
  413. package/dist/models/design-renders.mjs.map +1 -0
  414. package/dist/models/design-resolve.d.mts +29 -0
  415. package/dist/models/design-resolve.mjs +41 -0
  416. package/dist/models/design-resolve.mjs.map +1 -0
  417. package/dist/models/design.d.mts +39 -0
  418. package/dist/models/design.mjs +90 -0
  419. package/dist/models/design.mjs.map +1 -0
  420. package/dist/models/inherited-ratio.d.mts +21 -0
  421. package/dist/models/inherited-ratio.mjs +22 -0
  422. package/dist/models/inherited-ratio.mjs.map +1 -0
  423. package/dist/models/insight.d.mts +70 -0
  424. package/dist/models/insight.mjs +162 -0
  425. package/dist/models/insight.mjs.map +1 -0
  426. package/dist/models/label.d.mts +4 -2
  427. package/dist/models/label.mjs +25 -10
  428. package/dist/models/label.mjs.map +1 -1
  429. package/dist/models/member.d.mts +1 -1
  430. package/dist/models/member.mjs +5 -4
  431. package/dist/models/member.mjs.map +1 -1
  432. package/dist/models/notification-channel.d.mts +37 -3
  433. package/dist/models/notification-channel.mjs +88 -9
  434. package/dist/models/notification-channel.mjs.map +1 -1
  435. package/dist/models/notification-subscription.d.mts +1 -2
  436. package/dist/models/notification-subscription.mjs +7 -9
  437. package/dist/models/notification-subscription.mjs.map +1 -1
  438. package/dist/models/project-group-mapping.d.mts +6 -1
  439. package/dist/models/project-group-mapping.mjs +14 -4
  440. package/dist/models/project-group-mapping.mjs.map +1 -1
  441. package/dist/models/project.d.mts +9 -1
  442. package/dist/models/project.mjs +9 -4
  443. package/dist/models/project.mjs.map +1 -1
  444. package/dist/models/public-origin.d.mts +63 -0
  445. package/dist/models/public-origin.mjs +114 -0
  446. package/dist/models/public-origin.mjs.map +1 -0
  447. package/dist/models/review-switch-off.d.mts +14 -0
  448. package/dist/models/review-switch-off.mjs +26 -0
  449. package/dist/models/review-switch-off.mjs.map +1 -0
  450. package/dist/models/site-settings.d.mts +23 -0
  451. package/dist/models/site-settings.mjs +48 -0
  452. package/dist/models/site-settings.mjs.map +1 -0
  453. package/dist/models/snapshot.d.mts +36 -1
  454. package/dist/models/snapshot.mjs +50 -3
  455. package/dist/models/snapshot.mjs.map +1 -1
  456. package/dist/models/token.d.mts +13 -1
  457. package/dist/models/token.mjs +23 -5
  458. package/dist/models/token.mjs.map +1 -1
  459. package/dist/models/usage.d.mts +76 -0
  460. package/dist/models/usage.mjs +149 -0
  461. package/dist/models/usage.mjs.map +1 -0
  462. package/dist/models/user.d.mts +1 -1
  463. package/dist/models/user.mjs +4 -3
  464. package/dist/models/user.mjs.map +1 -1
  465. package/dist/models/webhook.d.mts +15 -1
  466. package/dist/models/webhook.mjs +33 -4
  467. package/dist/models/webhook.mjs.map +1 -1
  468. package/dist/models.d.mts +26 -3
  469. package/dist/models.mjs +27 -5
  470. package/dist/options.d.mts +18 -0
  471. package/dist/options.mjs +90 -0
  472. package/dist/options.mjs.map +1 -0
  473. package/dist/paths.d.mts +19 -4
  474. package/dist/paths.mjs +51 -10
  475. package/dist/paths.mjs.map +1 -1
  476. package/dist/retention/ai.d.mts +18 -0
  477. package/dist/retention/ai.mjs +23 -0
  478. package/dist/retention/ai.mjs.map +1 -0
  479. package/dist/retention/pending.mjs +47 -0
  480. package/dist/retention/pending.mjs.map +1 -0
  481. package/dist/retention/screenshot-sweep.d.mts +51 -0
  482. package/dist/retention/screenshot-sweep.mjs +128 -0
  483. package/dist/retention/screenshot-sweep.mjs.map +1 -0
  484. package/dist/retention.d.mts +16 -2
  485. package/dist/retention.mjs +71 -14
  486. package/dist/retention.mjs.map +1 -1
  487. package/dist/schema/baseline.d.mts +6 -0
  488. package/dist/schema/build-component.d.mts +25 -0
  489. package/dist/schema/build-review.d.mts +17 -0
  490. package/dist/schema/build.d.mts +7 -0
  491. package/dist/schema/capture-job.d.mts +60 -0
  492. package/dist/schema/capture-shard.d.mts +35 -0
  493. package/dist/schema/comment.d.mts +2 -0
  494. package/dist/schema/connection.d.mts +24 -0
  495. package/dist/schema/design.d.mts +58 -0
  496. package/dist/schema/insight.d.mts +54 -0
  497. package/dist/schema/notification.d.mts +4 -0
  498. package/dist/schema/project.d.mts +21 -0
  499. package/dist/schema/site-setting.d.mts +21 -0
  500. package/dist/schema/snapshot.d.mts +12 -0
  501. package/dist/schema/usage.d.mts +30 -0
  502. package/dist/schema.d.mts +11 -3
  503. package/dist/serverless.d.mts +21 -0
  504. package/dist/serverless.mjs +37 -0
  505. package/dist/serverless.mjs.map +1 -0
  506. package/dist/test-helpers/create-project.d.mts +1 -1
  507. package/dist/test-helpers/deferred.d.mts +39 -0
  508. package/dist/test-helpers/deferred.mjs +61 -0
  509. package/dist/test-helpers/deferred.mjs.map +1 -0
  510. package/dist/test-helpers/design-contract.d.mts +21 -0
  511. package/dist/test-helpers/design-contract.mjs +40 -0
  512. package/dist/test-helpers/design-contract.mjs.map +1 -0
  513. package/dist/test-helpers/fake-database.mjs +14 -2
  514. package/dist/test-helpers/fake-database.mjs.map +1 -1
  515. package/dist/test-helpers/fake-design.d.mts +43 -0
  516. package/dist/test-helpers/fake-design.mjs +114 -0
  517. package/dist/test-helpers/fake-design.mjs.map +1 -0
  518. package/dist/test-helpers/fake-notifier.d.mts +21 -0
  519. package/dist/test-helpers/fake-notifier.mjs +44 -0
  520. package/dist/test-helpers/fake-notifier.mjs.map +1 -0
  521. package/dist/test-helpers/fake-tables.mjs +255 -67
  522. package/dist/test-helpers/fake-tables.mjs.map +1 -1
  523. package/dist/test-helpers/manual-clock.d.mts +27 -0
  524. package/dist/test-helpers/manual-clock.mjs +56 -0
  525. package/dist/test-helpers/manual-clock.mjs.map +1 -0
  526. package/dist/test-helpers/notifier-contract.d.mts +7 -1
  527. package/dist/test-helpers/notifier-contract.mjs +32 -3
  528. package/dist/test-helpers/notifier-contract.mjs.map +1 -1
  529. package/dist/test-helpers/queue-contract.d.mts +16 -2
  530. package/dist/test-helpers/queue-contract.mjs +195 -1
  531. package/dist/test-helpers/queue-contract.mjs.map +1 -1
  532. package/dist/test-helpers/queue-multi-instance.d.mts +20 -0
  533. package/dist/test-helpers/queue-multi-instance.mjs +206 -0
  534. package/dist/test-helpers/queue-multi-instance.mjs.map +1 -0
  535. package/dist/test-helpers/runner-contract.mjs +11 -0
  536. package/dist/test-helpers/runner-contract.mjs.map +1 -1
  537. package/dist/test-helpers/sql-chunks.mjs +23 -0
  538. package/dist/test-helpers/sql-chunks.mjs.map +1 -1
  539. package/dist/test-helpers/storage-contract.mjs +1 -1
  540. package/dist/test-helpers.d.mts +9 -4
  541. package/dist/test-helpers.mjs +8 -3
  542. package/dist/types.d.mts +14 -3
  543. package/dist/types.mjs +21 -4
  544. package/dist/types.mjs.map +1 -1
  545. package/dist/urls.d.mts +4 -0
  546. package/dist/urls.mjs +11 -2
  547. package/dist/urls.mjs.map +1 -1
  548. package/dist/utils/adaptive-concurrency.d.mts +66 -0
  549. package/dist/utils/adaptive-concurrency.mjs +149 -0
  550. package/dist/utils/adaptive-concurrency.mjs.map +1 -0
  551. package/dist/utils/connect-retry.d.mts +26 -0
  552. package/dist/utils/connect-retry.mjs +43 -0
  553. package/dist/utils/connect-retry.mjs.map +1 -0
  554. package/dist/utils/environment.d.mts +19 -0
  555. package/dist/utils/environment.mjs +59 -0
  556. package/dist/utils/environment.mjs.map +1 -0
  557. package/dist/utils/http.d.mts +17 -1
  558. package/dist/utils/http.mjs +22 -3
  559. package/dist/utils/http.mjs.map +1 -1
  560. package/dist/utils/map-adaptive.d.mts +15 -0
  561. package/dist/utils/map-adaptive.mjs +72 -0
  562. package/dist/utils/map-adaptive.mjs.map +1 -0
  563. package/dist/utils/map-limit.d.mts +10 -0
  564. package/dist/utils/map-limit.mjs +32 -0
  565. package/dist/utils/map-limit.mjs.map +1 -0
  566. package/dist/utils/page-limiter.d.mts +37 -0
  567. package/dist/utils/page-limiter.mjs +56 -0
  568. package/dist/utils/page-limiter.mjs.map +1 -0
  569. package/dist/utils/public-origin.d.mts +20 -0
  570. package/dist/utils/public-origin.mjs +75 -0
  571. package/dist/utils/public-origin.mjs.map +1 -0
  572. package/dist/utils/redact.mjs +3 -0
  573. package/dist/utils/redact.mjs.map +1 -1
  574. package/dist/utils/remote-environment.d.mts +39 -0
  575. package/dist/utils/remote-environment.mjs +114 -0
  576. package/dist/utils/remote-environment.mjs.map +1 -0
  577. package/dist/utils/resources.d.mts +64 -0
  578. package/dist/utils/resources.mjs +122 -0
  579. package/dist/utils/resources.mjs.map +1 -0
  580. package/dist/utils/screenshot-store.d.mts +15 -0
  581. package/dist/utils/screenshot-store.mjs +41 -0
  582. package/dist/utils/screenshot-store.mjs.map +1 -0
  583. package/dist/utils/session-gate.d.mts +22 -0
  584. package/dist/utils/session-gate.mjs +53 -0
  585. package/dist/utils/session-gate.mjs.map +1 -0
  586. package/dist/utils/storybook-token.d.mts +81 -0
  587. package/dist/utils/storybook-token.mjs +134 -0
  588. package/dist/utils/storybook-token.mjs.map +1 -0
  589. package/dist/utils/storybook-url.d.mts +15 -0
  590. package/dist/utils/storybook-url.mjs +21 -0
  591. package/dist/utils/storybook-url.mjs.map +1 -0
  592. package/dist/utils/timing.mjs +1 -1
  593. package/dist/utils/timing.mjs.map +1 -1
  594. package/dist/utils/zip-read.mjs +129 -0
  595. package/dist/utils/zip-read.mjs.map +1 -0
  596. package/dist/utils/zip-write.d.mts +18 -0
  597. package/dist/utils/zip-write.mjs +188 -0
  598. package/dist/utils/zip-write.mjs.map +1 -0
  599. package/dist/utils.d.mts +17 -3
  600. package/dist/utils.mjs +18 -4
  601. package/package.json +32 -20
  602. package/dist/adapter/capture-runner.mjs.map +0 -1
  603. package/dist/adapter/git-host/comments.d.mts +0 -41
  604. package/dist/adapter/git-host/comments.mjs +0 -54
  605. package/dist/adapter/git-host/comments.mjs.map +0 -1
  606. package/dist/adapter/git-host/helpers.d.mts +0 -13
  607. package/dist/adapter/git-host/helpers.mjs +0 -30
  608. package/dist/adapter/git-host/helpers.mjs.map +0 -1
  609. package/dist/adapter/git-host.d.mts +0 -42
  610. package/dist/capture/queue.d.mts +0 -38
  611. package/dist/capture/queue.mjs +0 -98
  612. package/dist/capture/queue.mjs.map +0 -1
  613. package/dist/capture/recovery.d.mts +0 -21
  614. package/dist/capture/recovery.mjs +0 -83
  615. package/dist/capture/recovery.mjs.map +0 -1
  616. package/dist/capture/status-fanout.mjs +0 -72
  617. package/dist/capture/status-fanout.mjs.map +0 -1
  618. package/dist/diff/engine.d.mts +0 -16
  619. package/dist/diff/engine.mjs +0 -87
  620. package/dist/diff/engine.mjs.map +0 -1
  621. package/dist/diff/options.d.mts +0 -42
  622. package/dist/diff/options.mjs +0 -15
  623. package/dist/diff/options.mjs.map +0 -1
  624. package/dist/models/status-config.d.mts +0 -38
  625. package/dist/models/status-config.mjs +0 -63
  626. package/dist/models/status-config.mjs.map +0 -1
  627. package/dist/schema/status-config.d.mts +0 -16
  628. package/dist/test-helpers/git-contract.d.mts +0 -11
  629. package/dist/test-helpers/git-contract.mjs +0 -34
  630. package/dist/test-helpers/git-contract.mjs.map +0 -1
  631. /package/dist/adapter/{git-host.mjs → oauth.mjs} +0 -0
@@ -1,16 +1,18 @@
1
1
  import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
- import { BuildModel } from "../models/build.mjs";
4
+ import { DEFAULT_BROWSER } from "../adapters/capture-target.mjs";
5
5
  import { emitAttemptLog } from "../models/capture-attempt.mjs";
6
- import { diffPath, screenshotPath } from "../paths.mjs";
6
+ import { BuildModel } from "../models/build.mjs";
7
+ import { diffPath } from "../paths.mjs";
8
+ import { storeScreenshot } from "../utils/screenshot-store.mjs";
7
9
  import { BaselineModel } from "../models/baseline.mjs";
10
+ import { assertScreenshotBuffer } from "./guards.mjs";
8
11
  import { NO_BASELINE } from "../models/baseline-status.mjs";
9
12
  import { SnapshotModel } from "../models/snapshot.mjs";
13
+ import { mapLimit } from "../utils/map-limit.mjs";
10
14
  import { diffStoredScreenshots, writeDiffOverlay } from "./diff-record.mjs";
11
- import { assertScreenshotBuffer } from "./guards.mjs";
12
- import { SIZING_DEFAULTS, infraHashFor } from "./sizing.mjs";
13
- //#region src/capture/pipeline.ts
15
+ import { buildFingerprint, changedFields, fingerprintHash, mergeFingerprint, parseFingerprint } from "./fingerprint.mjs";
14
16
  /**
15
17
  * Persist a completed capture run: write screenshots, diff against the branch
16
18
  * baseline, create snapshots, and finalize the build.
@@ -24,13 +26,24 @@ import { SIZING_DEFAULTS, infraHashFor } from "./sizing.mjs";
24
26
  * @param flakyFailedStoryIds - Story ids that failed but are marked flaky
25
27
  * (non-blocking). They are logged as warnings and do not block the build.
26
28
  */
27
- async function persistCapture(ctx, renderFailedStoryIds = /* @__PURE__ */ new Set(), flakyFailedStoryIds = /* @__PURE__ */ new Set(), a11yFailedStoryIds = /* @__PURE__ */ new Set()) {
29
+ async function persistCapture(input, renderFailedStoryIds = /* @__PURE__ */ new Set(), flakyFailedStoryIds = /* @__PURE__ */ new Set(), a11yFailedStoryIds = /* @__PURE__ */ new Set()) {
30
+ const sets = await persistRendered(input, renderFailedStoryIds, flakyFailedStoryIds, a11yFailedStoryIds);
31
+ for (const id of await persistInherited(input)) sets.failed.add(id);
32
+ await finalizeBuild(input, allStoryIds(input), sets.failed, sets.flaky, sets.a11y);
33
+ }
34
+ /**
35
+ * Persist the rendered captures only: screenshots, diffs and snapshot rows, without the
36
+ * inherited snapshots or the build finalize. A sharded capture runs this once per shard.
37
+ */
38
+ async function persistRendered(input, renderFailedStoryIds = /* @__PURE__ */ new Set(), flakyFailedStoryIds = /* @__PURE__ */ new Set(), a11yFailedStoryIds = /* @__PURE__ */ new Set()) {
39
+ const ctx = await withFreshBrowsers(input);
28
40
  const failedStoryIds = new Set(renderFailedStoryIds);
29
41
  const flakyIds = new Set(flakyFailedStoryIds);
30
42
  const a11yIds = new Set(a11yFailedStoryIds);
31
- await Promise.all(ctx.captures.map(async (capture) => {
43
+ for (const group of capturesByPhase(ctx)) await mapLimit(group, 4, async (capture) => {
32
44
  try {
33
45
  await persistSnapshot(ctx, capture);
46
+ ctx.progress?.();
34
47
  } catch (error) {
35
48
  if (flakyIds.has(capture.story.id) || a11yIds.has(capture.story.id)) {
36
49
  if (flakyIds.has(capture.story.id)) flakyIds.add(capture.story.id);
@@ -55,7 +68,7 @@ async function persistCapture(ctx, renderFailedStoryIds = /* @__PURE__ */ new Se
55
68
  viewport: capture.viewportName
56
69
  });
57
70
  }
58
- }));
71
+ });
59
72
  for (const id of flakyIds) if (!failedStoryIds.has(id)) {
60
73
  ctx.logger?.warn({ storyId: id }, "flaky story failed (non-blocking)");
61
74
  await emitAttemptLog(ctx.recordLog, ctx.logger, "warn", "flaky story failed (non-blocking)", { storyId: id });
@@ -64,8 +77,62 @@ async function persistCapture(ctx, renderFailedStoryIds = /* @__PURE__ */ new Se
64
77
  ctx.logger?.warn({ storyId: id }, "a11y violations found (non-blocking)");
65
78
  await emitAttemptLog(ctx.recordLog, ctx.logger, "warn", "a11y violations found (non-blocking)", { storyId: id });
66
79
  }
67
- for (const id of await persistInherited(ctx)) failedStoryIds.add(id);
68
- await finalize(ctx, allStoryIds(ctx), failedStoryIds, flakyIds, a11yIds);
80
+ return {
81
+ failed: failedStoryIds,
82
+ flaky: flakyIds,
83
+ a11y: a11yIds
84
+ };
85
+ }
86
+ /** Engines of this run, primary (first) first. */
87
+ function runBrowsers(ctx) {
88
+ const listed = ctx.browsers && ctx.browsers.length > 0 ? ctx.browsers : [DEFAULT_BROWSER];
89
+ return [...new Set(listed)];
90
+ }
91
+ function primaryBrowser(ctx) {
92
+ return runBrowsers(ctx)[0] ?? "chromium";
93
+ }
94
+ /** The capture's axe violation texts as the JSON stored on the snapshot; null when there are none. */
95
+ function a11yJsonOf(capture) {
96
+ return capture.a11y && capture.a11y.length > 0 ? JSON.stringify(capture.a11y) : null;
97
+ }
98
+ function browserOf(capture) {
99
+ return capture.browser ?? "chromium";
100
+ }
101
+ /** Captures split into the primary engine's, then everyone else's. */
102
+ function capturesByPhase(ctx) {
103
+ const primary = primaryBrowser(ctx);
104
+ const first = ctx.captures.filter((capture) => browserOf(capture) === primary);
105
+ const rest = ctx.captures.filter((capture) => browserOf(capture) !== primary);
106
+ return rest.length > 0 ? [first, rest] : [first];
107
+ }
108
+ /** Secondary engines with no baseline yet (their first snapshots bootstrap from the primary). */
109
+ async function freshBrowsersOf(ctx) {
110
+ const primary = primaryBrowser(ctx);
111
+ const baselines = new BaselineModel(ctx.db, ctx.tables, ctx.storage, ctx.secret);
112
+ const secondary = runBrowsers(ctx).filter((browser) => browser !== primary);
113
+ const checks = await Promise.all(secondary.map(async (browser) => ({
114
+ browser,
115
+ fresh: !await baselines.hasAny(ctx.project.id, browser)
116
+ })));
117
+ return new Set(checks.filter((check) => check.fresh).map((check) => check.browser));
118
+ }
119
+ /** Mark secondary engines with no baseline yet: their first snapshots bootstrap. */
120
+ async function withFreshBrowsers(ctx) {
121
+ return ctx.freshBrowsers ? ctx : {
122
+ ...ctx,
123
+ freshBrowsers: await freshBrowsersOf(ctx)
124
+ };
125
+ }
126
+ /**
127
+ * A newly enabled engine accepts a story/mode's first snapshot when the
128
+ * primary engine's snapshot of it is clean (Chromatic's "auto-accepted where
129
+ * the primary browser was accepted"); anything else goes to review as `new`.
130
+ */
131
+ async function bootstrapAccepts(ctx, capture) {
132
+ const browser = browserOf(capture);
133
+ if (!ctx.freshBrowsers?.has(browser)) return false;
134
+ const primary = primaryBrowser(ctx);
135
+ return (await new SnapshotModel(ctx.db, ctx.tables).listByBuild(ctx.build.id)).some((snapshot) => snapshot.storyId === capture.story.id && snapshot.viewportName === capture.viewportName && (snapshot.browser ?? "chromium") === primary && (snapshot.status === "approved" || snapshot.status === "unchanged"));
69
136
  }
70
137
  /** Persist one inherited snapshot; returns the story id on failure. */
71
138
  async function tryPersistInherited(ctx, inherited) {
@@ -86,24 +153,33 @@ async function tryPersistInherited(ctx, inherited) {
86
153
  }
87
154
  }
88
155
  /** Persist inherited snapshots; a failure marks the story failed. */
156
+ /** Persist the inherited snapshots (stories reusing their baseline); returns the story ids that failed. */
89
157
  async function persistInherited(ctx) {
90
- const outcomes = await Promise.all((ctx.inherited ?? []).map(async (inherited) => await tryPersistInherited(ctx, inherited)));
158
+ const outcomes = await mapLimit(ctx.inherited ?? [], 4, async (inherited) => {
159
+ const failed = await tryPersistInherited(ctx, inherited);
160
+ ctx.progress?.();
161
+ return failed;
162
+ });
91
163
  return new Set(outcomes.filter((id) => id !== null));
92
164
  }
93
165
  /** Create an unchanged snapshot reusing the baseline screenshot. */
94
166
  async function persistInheritedSnapshot(ctx, inherited) {
95
- const infraHash = infraHashFor(ctx.project.browser ?? "chromium", ctx.viewports, SIZING_DEFAULTS);
167
+ const browser = browserOf(inherited);
168
+ const infraHash = inherited.baseline.infraHash;
96
169
  const snapshots = new SnapshotModel(ctx.db, ctx.tables);
97
170
  const snapshot = await snapshots.create(ctx.project.id, ctx.build.id, {
171
+ browser,
98
172
  storyId: inherited.story.id,
99
173
  storyName: inherited.story.name,
100
174
  storyTitle: inherited.story.title,
101
175
  storyImportPath: inherited.story.importPath ?? "",
176
+ designUrl: inherited.story.designUrl ?? null,
102
177
  viewportName: inherited.viewport.name,
103
178
  viewportWidth: inherited.viewport.width,
104
179
  viewportHeight: inherited.viewport.height,
105
180
  screenshotPath: inherited.baseline.screenshotPath,
106
181
  infraHash,
182
+ mode: modeJsonOf(inherited.viewport),
107
183
  inherited: true,
108
184
  baselineId: inherited.baseline.id,
109
185
  baselineVersion: inherited.baseline.updatedAt
@@ -123,6 +199,10 @@ function allStoryIds(ctx) {
123
199
  for (const inherited of ctx.inherited ?? []) ids.add(inherited.story.id);
124
200
  return ids;
125
201
  }
202
+ /** JSON for the snapshot `mode` column: the globals a mode applied (null for plain viewports). */
203
+ function modeJsonOf(viewport) {
204
+ return viewport?.globals ? JSON.stringify(viewport.globals) : null;
205
+ }
126
206
  function viewportByName(ctx, name) {
127
207
  return ctx.viewports.find((v) => v.name === name) ?? {
128
208
  name,
@@ -130,77 +210,194 @@ function viewportByName(ctx, name) {
130
210
  height: 0
131
211
  };
132
212
  }
133
- async function persistSnapshot(ctx, capture) {
213
+ /** Where the capture's screenshot lives: already stored by the orchestrator, or stored now. */
214
+ async function screenshotLocation(ctx, capture) {
215
+ if (capture.storedPath) return capture.storedPath;
134
216
  assertScreenshotBuffer(capture.screenshot);
217
+ return await storeScreenshot(ctx.storage, ctx.project.id, capture.screenshot);
218
+ }
219
+ async function persistSnapshot(ctx, capture) {
135
220
  const viewport = capture.viewport ?? viewportByName(ctx, capture.viewportName);
136
- const screenshot = screenshotPath(ctx.project.id, ctx.build.id, capture.story.id, capture.viewportName);
137
- await ctx.storage.write(screenshot, capture.screenshot);
138
- const baseline = await resolveBaseline(ctx, capture.story.id, capture.viewportName);
221
+ const browser = browserOf(capture);
222
+ const screenshot = await screenshotLocation(ctx, capture);
223
+ const baseline = await resolveBaseline(ctx, capture.story.id, capture.viewportName, browser);
139
224
  if (!baseline) {
140
225
  await createWithoutBaseline(ctx, capture, viewport, screenshot);
141
226
  return;
142
227
  }
143
228
  await createWithBaseline(ctx, capture, viewport, screenshot, baseline);
144
229
  }
145
- async function resolveBaseline(ctx, storyId, viewport) {
146
- return await new BaselineModel(ctx.db, ctx.tables, ctx.storage, ctx.secret).resolve(ctx.project.id, storyId, viewport, ctx.build.gitBranch, ctx.project.gitDefaultBranch);
230
+ async function resolveBaseline(ctx, storyId, viewport, browser) {
231
+ return await new BaselineModel(ctx.db, ctx.tables, ctx.storage, ctx.secret).resolve(ctx.project.id, storyId, viewport, ctx.build.gitBranch, ctx.project.gitDefaultBranch, browser);
147
232
  }
148
233
  async function createWithoutBaseline(ctx, capture, viewport, screenshot) {
149
234
  const snapshots = new SnapshotModel(ctx.db, ctx.tables);
150
- const status = ctx.build.isDefault ? "approved" : "new";
151
- const infraHash = infraHashFor(ctx.project.browser ?? "chromium", ctx.viewports, SIZING_DEFAULTS);
235
+ const browser = browserOf(capture);
236
+ const accepted = ctx.build.isDefault || await bootstrapAccepts(ctx, capture);
237
+ const status = accepted ? "approved" : "new";
238
+ const now = fingerprintOf(capture, viewport);
152
239
  const snapshot = await snapshots.create(ctx.project.id, ctx.build.id, {
240
+ browser,
241
+ durationMs: capture.durationMs ?? null,
242
+ a11yViolations: a11yJsonOf(capture),
153
243
  storyId: capture.story.id,
154
244
  storyName: capture.story.name,
155
245
  storyTitle: capture.story.title,
156
246
  storyImportPath: capture.story.importPath ?? "",
247
+ designUrl: capture.story.designUrl ?? null,
157
248
  viewportName: capture.viewportName,
158
249
  viewportWidth: viewport.width,
159
250
  viewportHeight: viewport.height,
160
251
  screenshotPath: screenshot,
161
- infraHash,
252
+ infraHash: now.infraHash,
253
+ mode: modeJsonOf(viewport),
162
254
  baselineVersion: NO_BASELINE
163
255
  });
164
256
  await snapshots.setStatus(snapshot.id, status);
165
- if (ctx.build.isDefault) await new BaselineModel(ctx.db, ctx.tables, ctx.storage, ctx.secret).upsert(ctx.project.id, capture.story.id, capture.viewportName, ctx.build.gitBranch, snapshot.id, screenshot, infraHash);
257
+ if (accepted) await new BaselineModel(ctx.db, ctx.tables, ctx.storage, ctx.secret).upsert(ctx.project.id, capture.story.id, capture.viewportName, ctx.build.gitBranch, snapshot.id, screenshot, now.infraHash, {
258
+ mode: modeJsonOf(viewport),
259
+ browser,
260
+ fingerprint: now.json
261
+ });
166
262
  }
263
+ function fingerprintOf(capture, viewport) {
264
+ return captureFingerprintOf(buildFingerprint(browserOf(capture), viewport, capture.environment));
265
+ }
266
+ /** A fingerprint as stored: its hash (the `infraHash`) and JSON. */
267
+ function captureFingerprintOf(fingerprint) {
268
+ return {
269
+ fingerprint,
270
+ infraHash: fingerprintHash(fingerprint),
271
+ json: JSON.stringify(fingerprint)
272
+ };
273
+ }
274
+ /**
275
+ * What changed in the capture environment since the baseline was recorded, or null when nothing
276
+ * did. Compared component by component, only where both sides report a value: a capture with no
277
+ * environment (spooled before a browser crash) is unknown, not a change. A baseline from before
278
+ * fingerprints cannot be compared, so it is never reported as changed; it is backfilled instead.
279
+ */
280
+ function environmentChange(baseline, now) {
281
+ const from = parseFingerprint(baseline.fingerprint);
282
+ if (!from) return null;
283
+ const changed = changedFields(from, now.fingerprint);
284
+ return changed.length > 0 ? {
285
+ changed,
286
+ from,
287
+ to: now.fingerprint
288
+ } : null;
289
+ }
290
+ /**
291
+ * A changed environment migrates the baseline without a diff only on the default branch, which is
292
+ * authoritative and auto-approves its own changes anyway. A feature branch still diffs: a real
293
+ * change is reported as `changed` with the environment change attached, never auto-accepted.
294
+ */
167
295
  async function createWithBaseline(ctx, capture, viewport, screenshot, baseline) {
168
- const result = await diffStoredScreenshots(ctx.storage, ctx.project, baseline.screenshotPath, screenshot, capture.story.parameters?.diffThreshold);
169
- const infraHash = infraHashFor(ctx.project.browser ?? "chromium", ctx.viewports, SIZING_DEFAULTS);
170
- const snapshots = new SnapshotModel(ctx.db, ctx.tables);
171
- const snapshot = await snapshots.create(ctx.project.id, ctx.build.id, {
296
+ const now = fingerprintOf(capture, viewport);
297
+ const change = environmentChange(baseline, now);
298
+ const input = {
299
+ ctx,
300
+ capture,
301
+ viewport,
302
+ screenshot,
303
+ baseline,
304
+ now,
305
+ change
306
+ };
307
+ if (change && ctx.build.isDefault) await persistMigration(input, change);
308
+ else await persistDiff(input);
309
+ }
310
+ /** The fingerprint to write on the baseline: the capture's, keeping components it did not report. */
311
+ function baselineFingerprintOf(input) {
312
+ const earlier = parseFingerprint(input.baseline.fingerprint);
313
+ return captureFingerprintOf(mergeFingerprint(earlier, input.now.fingerprint));
314
+ }
315
+ function snapshotInput(input) {
316
+ const { capture, viewport, screenshot, baseline, now } = input;
317
+ return {
318
+ browser: browserOf(capture),
319
+ durationMs: capture.durationMs ?? null,
320
+ a11yViolations: a11yJsonOf(capture),
172
321
  storyId: capture.story.id,
173
322
  storyName: capture.story.name,
174
323
  storyTitle: capture.story.title,
175
324
  storyImportPath: capture.story.importPath ?? "",
325
+ designUrl: capture.story.designUrl ?? null,
176
326
  viewportName: capture.viewportName,
177
327
  viewportWidth: viewport.width,
178
328
  viewportHeight: viewport.height,
179
329
  screenshotPath: screenshot,
180
- infraHash,
330
+ infraHash: now.infraHash,
331
+ mode: modeJsonOf(viewport),
181
332
  baselineId: baseline.id,
182
333
  baselineVersion: baseline.updatedAt
334
+ };
335
+ }
336
+ /**
337
+ * The default branch's capture environment changed (browser build, fonts, platform, scale): pixels
338
+ * would differ with no code change, so no diff is computed. The snapshot is accepted, records what
339
+ * changed, and the new screenshot becomes the baseline.
340
+ */
341
+ async function persistMigration(input, change) {
342
+ const { ctx, capture, viewport, screenshot, now } = input;
343
+ const snapshots = new SnapshotModel(ctx.db, ctx.tables);
344
+ const snapshot = await snapshots.create(ctx.project.id, ctx.build.id, {
345
+ ...snapshotInput(input),
346
+ envChange: JSON.stringify(change)
183
347
  });
184
- const shouldAutomigrate = result.sizeChanged && !result.passed && ctx.project.automigrate && baseline.infraHash !== null && baseline.infraHash !== infraHash;
185
- const status = shouldAutomigrate ? "unchanged" : diffStatus(result.passed, ctx.build.isDefault);
186
- const diff = diffPath(ctx.project.id, ctx.build.id, capture.story.id, capture.viewportName);
187
- const passed = shouldAutomigrate ? true : result.passed;
188
- const pixels = shouldAutomigrate ? 0 : result.diffPixels;
189
- const ratio = shouldAutomigrate ? 0 : result.diffRatio;
190
- const overlay = await writeDiffOverlay(ctx.storage, diff, result, passed);
191
348
  await snapshots.update(snapshot.id, {
192
- status,
349
+ status: "unchanged",
350
+ diffPath: null,
351
+ diffPixels: 0,
352
+ diffRatio: 0,
353
+ diffPassed: true,
354
+ infraHash: now.infraHash
355
+ });
356
+ await upsertDefaultBaseline(ctx, snapshot.id, capture, screenshot, baselineFingerprintOf(input));
357
+ ctx.logger?.debug({
358
+ storyId: capture.story.id,
359
+ viewport: viewport.name,
360
+ changed: change.changed
361
+ }, "capture environment changed; accepted without a diff");
362
+ }
363
+ /** Diff against the baseline; a feature-branch environment change rides along as `envChange`. */
364
+ async function persistDiff(input) {
365
+ const { ctx, capture, screenshot, baseline, now, change } = input;
366
+ const result = await diffStoredScreenshots(ctx.storage, ctx.project, baseline.screenshotPath, screenshot, capture.story.parameters?.diffThreshold);
367
+ const snapshots = new SnapshotModel(ctx.db, ctx.tables);
368
+ const snapshot = await snapshots.create(ctx.project.id, ctx.build.id, {
369
+ ...snapshotInput(input),
370
+ ...change ? { envChange: JSON.stringify(change) } : {}
371
+ });
372
+ const diff = diffPath(ctx.project.id, ctx.build.id, capture.story.id, capture.viewportName, browserOf(capture));
373
+ const overlay = await writeDiffOverlay(ctx.storage, diff, result, result.passed);
374
+ await snapshots.update(snapshot.id, {
375
+ status: diffStatus(result.passed, ctx.build.isDefault),
193
376
  diffPath: overlay,
194
- diffPixels: pixels,
195
- diffRatio: ratio,
196
- diffPassed: passed,
197
- infraHash
377
+ diffPixels: result.diffPixels,
378
+ diffRatio: result.diffRatio,
379
+ diffPassed: result.passed,
380
+ infraHash: now.infraHash
198
381
  });
199
- if (ctx.build.isDefault && (shouldAutomigrate || !passed)) await upsertDefaultBaseline(ctx, snapshot.id, capture, screenshot, infraHash);
382
+ if (ctx.build.isDefault) await refreshDefaultBaseline(input, snapshot.id, result.passed);
383
+ }
384
+ /**
385
+ * After a default-branch diff: a failed diff moves the baseline to the new screenshot; a passing
386
+ * one backfills the fingerprint when it was missing or had components the capture now reports.
387
+ */
388
+ async function refreshDefaultBaseline(input, snapshotId, passed) {
389
+ const { ctx, capture, screenshot, baseline } = input;
390
+ const target = baselineFingerprintOf(input);
391
+ if (!passed) await upsertDefaultBaseline(ctx, snapshotId, capture, screenshot, target);
392
+ else if (!baseline.fingerprint || baseline.infraHash !== target.infraHash) await new BaselineModel(ctx.db, ctx.tables, ctx.storage, ctx.secret).backfillFingerprint(baseline, target.infraHash, target.json);
200
393
  }
201
394
  /** Default-branch builds are authoritative: the captured screenshot becomes the baseline. */
202
- async function upsertDefaultBaseline(ctx, snapshotId, capture, screenshot, infraHash) {
203
- await new BaselineModel(ctx.db, ctx.tables, ctx.storage, ctx.secret).upsert(ctx.project.id, capture.story.id, capture.viewportName, ctx.build.gitBranch, snapshotId, screenshot, infraHash);
395
+ async function upsertDefaultBaseline(ctx, snapshotId, capture, screenshot, now) {
396
+ await new BaselineModel(ctx.db, ctx.tables, ctx.storage, ctx.secret).upsert(ctx.project.id, capture.story.id, capture.viewportName, ctx.build.gitBranch, snapshotId, screenshot, now.infraHash, {
397
+ mode: modeJsonOf(capture.viewport),
398
+ browser: browserOf(capture),
399
+ fingerprint: now.json
400
+ });
204
401
  }
205
402
  /** Snapshot status after diffing: default-branch changes are auto-approved, not queued for review. */
206
403
  function diffStatus(passed, isDefault) {
@@ -213,8 +410,16 @@ function finalBuildStatus(isDefault, failedCount, hasCaptures, build) {
213
410
  if (isDefault || hasCaptures && build.changedCount === 0) return "approved";
214
411
  return "reviewing";
215
412
  }
216
- async function finalize(ctx, storyIds, failedStoryIds, flakyFailedStoryIds = /* @__PURE__ */ new Set(), a11yFailedStoryIds = /* @__PURE__ */ new Set()) {
217
- const builds = new BuildModel(ctx.db, ctx.tables);
413
+ /**
414
+ * Close out a capture: roll the snapshot counts up, set the build's final status from the failed
415
+ * story ids, and (on the default branch) drop baselines of stories that no longer exist.
416
+ */
417
+ async function finalizeBuild(ctx, storyIds, failedStoryIds, flakyFailedStoryIds = /* @__PURE__ */ new Set(), a11yFailedStoryIds = /* @__PURE__ */ new Set()) {
418
+ const builds = new BuildModel(ctx.db, ctx.tables, ctx.logger);
419
+ if ((await builds.get(ctx.build.id))?.status === "cancelled") {
420
+ ctx.logger?.debug({ buildId: ctx.build.id }, "build is cancelled; finalize skipped");
421
+ return;
422
+ }
218
423
  const build = await builds.updateCounts(ctx.build.id);
219
424
  const hasCaptures = storyIds.size > 0;
220
425
  const status = finalBuildStatus(ctx.build.isDefault, failedStoryIds.size, hasCaptures, build);
@@ -230,6 +435,6 @@ async function finalize(ctx, storyIds, failedStoryIds, flakyFailedStoryIds = /*
230
435
  if (ctx.build.isDefault && hasCaptures) await new BaselineModel(ctx.db, ctx.tables, ctx.storage, ctx.secret).removeOrphans(ctx.project.id, new Set(storyIds));
231
436
  }
232
437
  //#endregion
233
- export { diffStatus, persistCapture };
438
+ export { diffStatus, finalizeBuild, freshBrowsersOf, persistCapture, persistInherited, persistRendered };
234
439
 
235
440
  //# sourceMappingURL=pipeline.mjs.map
@@ -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 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 { 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 { 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 snapshot = await snapshots.create(ctx.project.id, ctx.build.id, {\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 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 input = { ctx, capture, viewport, screenshot, baseline, now, change };\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}\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 };\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,\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 await new BaselineModel(ctx.db, ctx.tables, ctx.storage, ctx.secret).backfillFingerprint(\n baseline,\n target.infraHash,\n target.json,\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,11 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ //#region src/capture/progress.d.ts
4
+ /**
5
+ * Called by a capture as it moves: a screenshot taken, a snapshot persisted, a stage done. Cheap
6
+ * and synchronous, so the hot loops of render and persist can call it on every item.
7
+ */
8
+ type CaptureProgress = () => void;
9
+ //#endregion
10
+ export { CaptureProgress };
11
+ //# sourceMappingURL=progress.d.mts.map
@@ -0,0 +1,28 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ //#region 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
+ //#endregion
26
+ export { PROGRESS_THROTTLE_MS, leaseProgress };
27
+
28
+ //# 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"}