@storyshelf/core 0.6.2 → 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 (628) 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/capture/a11y/axe-source.d.mts +18 -0
  57. package/dist/capture/a11y/axe-source.mjs +37 -0
  58. package/dist/capture/a11y/axe-source.mjs.map +1 -0
  59. package/dist/capture/a11y/in-page.d.mts +27 -0
  60. package/dist/capture/a11y/in-page.mjs +60 -0
  61. package/dist/capture/a11y/in-page.mjs.map +1 -0
  62. package/dist/capture/a11y/map.d.mts +24 -0
  63. package/dist/capture/a11y/map.mjs +48 -0
  64. package/dist/capture/a11y/map.mjs.map +1 -0
  65. package/dist/capture/a11y/options.d.mts +23 -0
  66. package/dist/capture/a11y/options.mjs +75 -0
  67. package/dist/capture/a11y/options.mjs.map +1 -0
  68. package/dist/capture/adapter.d.mts +41 -3
  69. package/dist/capture/adapter.mjs.map +1 -1
  70. package/dist/capture/affected.d.mts +2 -0
  71. package/dist/capture/affected.mjs +13 -4
  72. package/dist/capture/affected.mjs.map +1 -1
  73. package/dist/capture/attempt-logger.mjs +84 -0
  74. package/dist/capture/attempt-logger.mjs.map +1 -0
  75. package/dist/capture/browsers.d.mts +35 -0
  76. package/dist/capture/browsers.mjs +67 -0
  77. package/dist/capture/browsers.mjs.map +1 -0
  78. package/dist/capture/build-state/deliver.d.mts +32 -0
  79. package/dist/capture/build-state/deliver.mjs +118 -0
  80. package/dist/capture/build-state/deliver.mjs.map +1 -0
  81. package/dist/capture/build-state/index.d.mts +8 -0
  82. package/dist/capture/build-state/notify-job.d.mts +56 -0
  83. package/dist/capture/build-state/notify-job.mjs +133 -0
  84. package/dist/capture/build-state/notify-job.mjs.map +1 -0
  85. package/dist/capture/build-state/publisher.d.mts +23 -0
  86. package/dist/capture/build-state/publisher.mjs +107 -0
  87. package/dist/capture/build-state/publisher.mjs.map +1 -0
  88. package/dist/capture/build-state/resume.d.mts +9 -0
  89. package/dist/capture/build-state/resume.mjs +47 -0
  90. package/dist/capture/build-state/resume.mjs.map +1 -0
  91. package/dist/capture/build-state/types.d.mts +49 -0
  92. package/dist/capture/catalog/catalogue.d.mts +22 -0
  93. package/dist/capture/catalog/catalogue.mjs +53 -0
  94. package/dist/capture/catalog/catalogue.mjs.map +1 -0
  95. package/dist/capture/catalog/components.d.mts +23 -0
  96. package/dist/capture/catalog/components.mjs +55 -0
  97. package/dist/capture/catalog/components.mjs.map +1 -0
  98. package/dist/capture/catalog/history.d.mts +30 -0
  99. package/dist/capture/catalog/history.mjs +51 -0
  100. package/dist/capture/catalog/history.mjs.map +1 -0
  101. package/dist/capture/catalog/hook.d.mts +14 -0
  102. package/dist/capture/catalog/hook.mjs +19 -0
  103. package/dist/capture/catalog/hook.mjs.map +1 -0
  104. package/dist/capture/catalog/index.d.mts +11 -0
  105. package/dist/capture/catalog/load.d.mts +26 -0
  106. package/dist/capture/catalog/load.mjs +49 -0
  107. package/dist/capture/catalog/load.mjs.map +1 -0
  108. package/dist/capture/catalog/parse.d.mts +15 -0
  109. package/dist/capture/catalog/parse.mjs +56 -0
  110. package/dist/capture/catalog/parse.mjs.map +1 -0
  111. package/dist/capture/catalog/read.d.mts +12 -0
  112. package/dist/capture/catalog/read.mjs +33 -0
  113. package/dist/capture/catalog/read.mjs.map +1 -0
  114. package/dist/capture/catalog/types.d.mts +33 -0
  115. package/dist/capture/consumer/capture-handler.d.mts +58 -0
  116. package/dist/capture/consumer/capture-handler.mjs +118 -0
  117. package/dist/capture/consumer/capture-handler.mjs.map +1 -0
  118. package/dist/capture/consumer/idle.d.mts +17 -0
  119. package/dist/capture/consumer/idle.mjs +25 -0
  120. package/dist/capture/consumer/idle.mjs.map +1 -0
  121. package/dist/capture/consumer/index.d.mts +13 -0
  122. package/dist/capture/consumer/index.mjs +139 -0
  123. package/dist/capture/consumer/index.mjs.map +1 -0
  124. package/dist/capture/consumer/loop.mjs +110 -0
  125. package/dist/capture/consumer/loop.mjs.map +1 -0
  126. package/dist/capture/consumer/metrics.d.mts +41 -0
  127. package/dist/capture/consumer/metrics.mjs +38 -0
  128. package/dist/capture/consumer/metrics.mjs.map +1 -0
  129. package/dist/capture/consumer/retry.d.mts +33 -0
  130. package/dist/capture/consumer/retry.mjs +34 -0
  131. package/dist/capture/consumer/retry.mjs.map +1 -0
  132. package/dist/capture/consumer/run-job.mjs +195 -0
  133. package/dist/capture/consumer/run-job.mjs.map +1 -0
  134. package/dist/capture/consumer/startup.mjs +38 -0
  135. package/dist/capture/consumer/startup.mjs.map +1 -0
  136. package/dist/capture/consumer/types.d.mts +97 -0
  137. package/dist/capture/crash-failure.d.mts +25 -0
  138. package/dist/capture/crash-failure.mjs +70 -0
  139. package/dist/capture/crash-failure.mjs.map +1 -0
  140. package/dist/capture/db-queue/cadence.d.mts +32 -0
  141. package/dist/capture/db-queue/cadence.mjs +38 -0
  142. package/dist/capture/db-queue/cadence.mjs.map +1 -0
  143. package/dist/capture/db-queue/claim.d.mts +9 -0
  144. package/dist/capture/db-queue/claim.mjs +132 -0
  145. package/dist/capture/db-queue/claim.mjs.map +1 -0
  146. package/dist/capture/db-queue/clock.mjs +12 -0
  147. package/dist/capture/db-queue/clock.mjs.map +1 -0
  148. package/dist/capture/db-queue/codec.mjs +76 -0
  149. package/dist/capture/db-queue/codec.mjs.map +1 -0
  150. package/dist/capture/db-queue/index.d.mts +20 -0
  151. package/dist/capture/db-queue/index.mjs +89 -0
  152. package/dist/capture/db-queue/index.mjs.map +1 -0
  153. package/dist/capture/db-queue/keeper.d.mts +36 -0
  154. package/dist/capture/db-queue/keeper.mjs +92 -0
  155. package/dist/capture/db-queue/keeper.mjs.map +1 -0
  156. package/dist/capture/db-queue/lease.mjs +46 -0
  157. package/dist/capture/db-queue/lease.mjs.map +1 -0
  158. package/dist/capture/db-queue/lifecycle.mjs +36 -0
  159. package/dist/capture/db-queue/lifecycle.mjs.map +1 -0
  160. package/dist/capture/db-queue/operations.mjs +94 -0
  161. package/dist/capture/db-queue/operations.mjs.map +1 -0
  162. package/dist/capture/db-queue/poll-loop.d.mts +30 -0
  163. package/dist/capture/db-queue/poll-loop.mjs +89 -0
  164. package/dist/capture/db-queue/poll-loop.mjs.map +1 -0
  165. package/dist/capture/db-queue/poll.d.mts +10 -0
  166. package/dist/capture/db-queue/poll.mjs +69 -0
  167. package/dist/capture/db-queue/poll.mjs.map +1 -0
  168. package/dist/capture/db-queue/timers.d.mts +12 -0
  169. package/dist/capture/db-queue/timers.mjs +37 -0
  170. package/dist/capture/db-queue/timers.mjs.map +1 -0
  171. package/dist/capture/db-queue/types.d.mts +83 -0
  172. package/dist/capture/db-queue/types.mjs +12 -0
  173. package/dist/capture/db-queue/types.mjs.map +1 -0
  174. package/dist/capture/db-queue/wake.mjs +48 -0
  175. package/dist/capture/db-queue/wake.mjs.map +1 -0
  176. package/dist/capture/design.mjs +39 -0
  177. package/dist/capture/design.mjs.map +1 -0
  178. package/dist/capture/diff-record.mjs +23 -3
  179. package/dist/capture/diff-record.mjs.map +1 -1
  180. package/dist/capture/dispatch.d.mts +12 -7
  181. package/dist/capture/dispatch.mjs +86 -107
  182. package/dist/capture/dispatch.mjs.map +1 -1
  183. package/dist/capture/enrich.mjs +60 -0
  184. package/dist/capture/enrich.mjs.map +1 -0
  185. package/dist/capture/failure-sets.mjs +22 -0
  186. package/dist/capture/failure-sets.mjs.map +1 -0
  187. package/dist/capture/fingerprint.d.mts +42 -0
  188. package/dist/capture/fingerprint.mjs +83 -0
  189. package/dist/capture/fingerprint.mjs.map +1 -0
  190. package/dist/capture/forked/channel.d.mts +25 -0
  191. package/dist/capture/forked/child.d.mts +34 -0
  192. package/dist/capture/forked/child.mjs +202 -0
  193. package/dist/capture/forked/child.mjs.map +1 -0
  194. package/dist/capture/forked/crash-tracker.mjs +23 -0
  195. package/dist/capture/forked/crash-tracker.mjs.map +1 -0
  196. package/dist/capture/forked/decision.d.mts +26 -0
  197. package/dist/capture/forked/decision.mjs +27 -0
  198. package/dist/capture/forked/decision.mjs.map +1 -0
  199. package/dist/capture/forked/exit.mjs +12 -0
  200. package/dist/capture/forked/exit.mjs.map +1 -0
  201. package/dist/capture/forked/fork.d.mts +31 -0
  202. package/dist/capture/forked/fork.mjs +93 -0
  203. package/dist/capture/forked/fork.mjs.map +1 -0
  204. package/dist/capture/forked/forked-capture.d.mts +40 -0
  205. package/dist/capture/forked/forked-capture.mjs +240 -0
  206. package/dist/capture/forked/forked-capture.mjs.map +1 -0
  207. package/dist/capture/forked/lease-book.mjs +46 -0
  208. package/dist/capture/forked/lease-book.mjs.map +1 -0
  209. package/dist/capture/forked/protocol.d.mts +38 -0
  210. package/dist/capture/forked/protocol.mjs +25 -0
  211. package/dist/capture/forked/protocol.mjs.map +1 -0
  212. package/dist/capture/forked/runs.mjs +75 -0
  213. package/dist/capture/forked/runs.mjs.map +1 -0
  214. package/dist/capture/isolate.mjs +141 -0
  215. package/dist/capture/isolate.mjs.map +1 -0
  216. package/dist/capture/job-lease.d.mts +28 -0
  217. package/dist/capture/job-lease.mjs +57 -0
  218. package/dist/capture/job-lease.mjs.map +1 -0
  219. package/dist/capture/matrix.mjs +298 -0
  220. package/dist/capture/matrix.mjs.map +1 -0
  221. package/dist/capture/modes.d.mts +25 -0
  222. package/dist/capture/modes.mjs +107 -0
  223. package/dist/capture/modes.mjs.map +1 -0
  224. package/dist/capture/orchestrator.d.mts +37 -5
  225. package/dist/capture/orchestrator.mjs +118 -107
  226. package/dist/capture/orchestrator.mjs.map +1 -1
  227. package/dist/capture/pipeline.d.mts +9 -2
  228. package/dist/capture/pipeline.mjs +250 -45
  229. package/dist/capture/pipeline.mjs.map +1 -1
  230. package/dist/capture/progress.d.mts +11 -0
  231. package/dist/capture/progress.mjs +28 -0
  232. package/dist/capture/progress.mjs.map +1 -0
  233. package/dist/capture/publish/accept.d.mts +39 -0
  234. package/dist/capture/publish/accept.mjs +28 -0
  235. package/dist/capture/publish/accept.mjs.map +1 -0
  236. package/dist/capture/publish/complete.d.mts +46 -0
  237. package/dist/capture/publish/complete.mjs +64 -0
  238. package/dist/capture/publish/complete.mjs.map +1 -0
  239. package/dist/capture/publish/extract.d.mts +20 -0
  240. package/dist/capture/publish/extract.mjs +119 -0
  241. package/dist/capture/publish/extract.mjs.map +1 -0
  242. package/dist/capture/publish/handler.d.mts +28 -0
  243. package/dist/capture/publish/handler.mjs +62 -0
  244. package/dist/capture/publish/handler.mjs.map +1 -0
  245. package/dist/capture/publish/index.d.mts +10 -0
  246. package/dist/capture/publish/limits.d.mts +37 -0
  247. package/dist/capture/publish/limits.mjs +39 -0
  248. package/dist/capture/publish/limits.mjs.map +1 -0
  249. package/dist/capture/publish/published.d.mts +15 -0
  250. package/dist/capture/publish/published.mjs +18 -0
  251. package/dist/capture/publish/published.mjs.map +1 -0
  252. package/dist/capture/publish/run.d.mts +33 -0
  253. package/dist/capture/publish/run.mjs +30 -0
  254. package/dist/capture/publish/run.mjs.map +1 -0
  255. package/dist/capture/rediff.d.mts +1 -1
  256. package/dist/capture/rediff.mjs +4 -3
  257. package/dist/capture/rediff.mjs.map +1 -1
  258. package/dist/capture/render.mjs +53 -0
  259. package/dist/capture/render.mjs.map +1 -0
  260. package/dist/capture/scratch.d.mts +44 -0
  261. package/dist/capture/scratch.mjs +138 -0
  262. package/dist/capture/scratch.mjs.map +1 -0
  263. package/dist/capture/shard-plan.mjs +128 -0
  264. package/dist/capture/shard-plan.mjs.map +1 -0
  265. package/dist/capture/shards/context.mjs +51 -0
  266. package/dist/capture/shards/context.mjs.map +1 -0
  267. package/dist/capture/shards/execute.mjs +174 -0
  268. package/dist/capture/shards/execute.mjs.map +1 -0
  269. package/dist/capture/shards/fan-out.mjs +126 -0
  270. package/dist/capture/shards/fan-out.mjs.map +1 -0
  271. package/dist/capture/shards/history.mjs +35 -0
  272. package/dist/capture/shards/history.mjs.map +1 -0
  273. package/dist/capture/shards/reduce.mjs +97 -0
  274. package/dist/capture/shards/reduce.mjs.map +1 -0
  275. package/dist/capture/sizing.mjs +1 -11
  276. package/dist/capture/sizing.mjs.map +1 -1
  277. package/dist/capture/skip-checks.mjs +2 -65
  278. package/dist/capture/skip-checks.mjs.map +1 -1
  279. package/dist/capture/statics.d.mts +7 -8
  280. package/dist/capture/statics.mjs +25 -71
  281. package/dist/capture/statics.mjs.map +1 -1
  282. package/dist/capture/storybook-urls.d.mts +40 -0
  283. package/dist/capture/storybook-urls.mjs +41 -0
  284. package/dist/capture/storybook-urls.mjs.map +1 -0
  285. package/dist/capture/storybook.d.mts +12 -4
  286. package/dist/capture/storybook.mjs +56 -16
  287. package/dist/capture/storybook.mjs.map +1 -1
  288. package/dist/capture/target.mjs +21 -0
  289. package/dist/capture/target.mjs.map +1 -0
  290. package/dist/capture/targets.d.mts +25 -0
  291. package/dist/capture/targets.mjs +32 -0
  292. package/dist/capture/targets.mjs.map +1 -0
  293. package/dist/capture/tests.d.mts +37 -0
  294. package/dist/capture/tests.mjs +126 -0
  295. package/dist/capture/tests.mjs.map +1 -0
  296. package/dist/capture/token-chunks.mjs +74 -0
  297. package/dist/capture/token-chunks.mjs.map +1 -0
  298. package/dist/capture.d.mts +60 -7
  299. package/dist/capture.mjs +48 -3
  300. package/dist/config.d.mts +121 -12
  301. package/dist/config.mjs +9 -6
  302. package/dist/config.mjs.map +1 -1
  303. package/dist/diff.d.mts +9 -3
  304. package/dist/diff.mjs +16 -3
  305. package/dist/diff.mjs.map +1 -0
  306. package/dist/index.d.mts +1 -1
  307. package/dist/insights/budget.d.mts +20 -0
  308. package/dist/insights/budget.mjs +33 -0
  309. package/dist/insights/budget.mjs.map +1 -0
  310. package/dist/insights/envelope.d.mts +59 -0
  311. package/dist/insights/envelope.mjs +60 -0
  312. package/dist/insights/envelope.mjs.map +1 -0
  313. package/dist/insights/evidence-health.d.mts +32 -0
  314. package/dist/insights/evidence-health.mjs +28 -0
  315. package/dist/insights/evidence-health.mjs.map +1 -0
  316. package/dist/insights/evidence.d.mts +55 -0
  317. package/dist/insights/evidence.mjs +64 -0
  318. package/dist/insights/evidence.mjs.map +1 -0
  319. package/dist/insights/hash.d.mts +18 -0
  320. package/dist/insights/hash.mjs +21 -0
  321. package/dist/insights/hash.mjs.map +1 -0
  322. package/dist/insights/limits.d.mts +24 -0
  323. package/dist/insights/limits.mjs +24 -0
  324. package/dist/insights/limits.mjs.map +1 -0
  325. package/dist/insights/prompts/v1.d.mts +13 -0
  326. package/dist/insights/prompts/v1.mjs +34 -0
  327. package/dist/insights/prompts/v1.mjs.map +1 -0
  328. package/dist/insights/redact.d.mts +9 -0
  329. package/dist/insights/redact.mjs +24 -0
  330. package/dist/insights/redact.mjs.map +1 -0
  331. package/dist/insights/text.mjs +44 -0
  332. package/dist/insights/text.mjs.map +1 -0
  333. package/dist/insights.d.mts +11 -0
  334. package/dist/insights.mjs +12 -0
  335. package/dist/logger.mjs +17 -1
  336. package/dist/logger.mjs.map +1 -1
  337. package/dist/models/ai-budget-alert.d.mts +23 -0
  338. package/dist/models/ai-budget-alert.mjs +38 -0
  339. package/dist/models/ai-budget-alert.mjs.map +1 -0
  340. package/dist/models/ai-usage.d.mts +40 -0
  341. package/dist/models/ai-usage.mjs +61 -0
  342. package/dist/models/ai-usage.mjs.map +1 -0
  343. package/dist/models/baseline-archive.d.mts +84 -0
  344. package/dist/models/baseline-archive.mjs +106 -0
  345. package/dist/models/baseline-archive.mjs.map +1 -0
  346. package/dist/models/baseline-export.d.mts +19 -0
  347. package/dist/models/baseline-export.mjs +136 -0
  348. package/dist/models/baseline-export.mjs.map +1 -0
  349. package/dist/models/baseline-import.mjs +118 -0
  350. package/dist/models/baseline-import.mjs.map +1 -0
  351. package/dist/models/baseline-transfer.d.mts +49 -0
  352. package/dist/models/baseline-transfer.mjs +121 -0
  353. package/dist/models/baseline-transfer.mjs.map +1 -0
  354. package/dist/models/baseline.d.mts +32 -5
  355. package/dist/models/baseline.mjs +65 -20
  356. package/dist/models/baseline.mjs.map +1 -1
  357. package/dist/models/build-component.d.mts +28 -0
  358. package/dist/models/build-component.mjs +55 -0
  359. package/dist/models/build-component.mjs.map +1 -0
  360. package/dist/models/build-review-status.d.mts +29 -0
  361. package/dist/models/build-review-status.mjs +37 -0
  362. package/dist/models/build-review-status.mjs.map +1 -0
  363. package/dist/models/build-review.d.mts +28 -0
  364. package/dist/models/build-review.mjs +54 -0
  365. package/dist/models/build-review.mjs.map +1 -0
  366. package/dist/models/build.d.mts +25 -3
  367. package/dist/models/build.mjs +50 -11
  368. package/dist/models/build.mjs.map +1 -1
  369. package/dist/models/capture-attempt.d.mts +7 -1
  370. package/dist/models/capture-attempt.mjs +29 -4
  371. package/dist/models/capture-attempt.mjs.map +1 -1
  372. package/dist/models/capture-job.d.mts +106 -0
  373. package/dist/models/capture-job.mjs +288 -0
  374. package/dist/models/capture-job.mjs.map +1 -0
  375. package/dist/models/capture-log.d.mts +1 -1
  376. package/dist/models/capture-log.mjs +4 -3
  377. package/dist/models/capture-log.mjs.map +1 -1
  378. package/dist/models/capture-shard.d.mts +62 -0
  379. package/dist/models/capture-shard.mjs +190 -0
  380. package/dist/models/capture-shard.mjs.map +1 -0
  381. package/dist/models/changeset.d.mts +48 -0
  382. package/dist/models/changeset.mjs +81 -0
  383. package/dist/models/changeset.mjs.map +1 -0
  384. package/dist/models/columns.mjs +19 -0
  385. package/dist/models/columns.mjs.map +1 -0
  386. package/dist/models/comment.d.mts +4 -1
  387. package/dist/models/comment.mjs +7 -4
  388. package/dist/models/comment.mjs.map +1 -1
  389. package/dist/models/component.d.mts +43 -0
  390. package/dist/models/component.mjs +60 -0
  391. package/dist/models/component.mjs.map +1 -0
  392. package/dist/models/connections.d.mts +41 -0
  393. package/dist/models/connections.mjs +112 -0
  394. package/dist/models/connections.mjs.map +1 -0
  395. package/dist/models/content-ref.d.mts +1 -1
  396. package/dist/models/content-ref.mjs +1 -1
  397. package/dist/models/content-ref.mjs.map +1 -1
  398. package/dist/models/credentials.mjs +12 -2
  399. package/dist/models/credentials.mjs.map +1 -1
  400. package/dist/models/design-credentials.d.mts +41 -0
  401. package/dist/models/design-credentials.mjs +86 -0
  402. package/dist/models/design-credentials.mjs.map +1 -0
  403. package/dist/models/design-health.d.mts +16 -0
  404. package/dist/models/design-health.mjs +25 -0
  405. package/dist/models/design-health.mjs.map +1 -0
  406. package/dist/models/design-nodes.d.mts +83 -0
  407. package/dist/models/design-nodes.mjs +222 -0
  408. package/dist/models/design-nodes.mjs.map +1 -0
  409. package/dist/models/design-renders.mjs +74 -0
  410. package/dist/models/design-renders.mjs.map +1 -0
  411. package/dist/models/design-resolve.d.mts +29 -0
  412. package/dist/models/design-resolve.mjs +41 -0
  413. package/dist/models/design-resolve.mjs.map +1 -0
  414. package/dist/models/design.d.mts +39 -0
  415. package/dist/models/design.mjs +90 -0
  416. package/dist/models/design.mjs.map +1 -0
  417. package/dist/models/inherited-ratio.d.mts +21 -0
  418. package/dist/models/inherited-ratio.mjs +22 -0
  419. package/dist/models/inherited-ratio.mjs.map +1 -0
  420. package/dist/models/insight.d.mts +70 -0
  421. package/dist/models/insight.mjs +162 -0
  422. package/dist/models/insight.mjs.map +1 -0
  423. package/dist/models/label.d.mts +4 -2
  424. package/dist/models/label.mjs +25 -10
  425. package/dist/models/label.mjs.map +1 -1
  426. package/dist/models/member.d.mts +1 -1
  427. package/dist/models/member.mjs +5 -4
  428. package/dist/models/member.mjs.map +1 -1
  429. package/dist/models/notification-channel.d.mts +37 -3
  430. package/dist/models/notification-channel.mjs +88 -9
  431. package/dist/models/notification-channel.mjs.map +1 -1
  432. package/dist/models/notification-subscription.d.mts +1 -2
  433. package/dist/models/notification-subscription.mjs +7 -9
  434. package/dist/models/notification-subscription.mjs.map +1 -1
  435. package/dist/models/project-group-mapping.d.mts +6 -1
  436. package/dist/models/project-group-mapping.mjs +14 -4
  437. package/dist/models/project-group-mapping.mjs.map +1 -1
  438. package/dist/models/project.d.mts +9 -1
  439. package/dist/models/project.mjs +9 -4
  440. package/dist/models/project.mjs.map +1 -1
  441. package/dist/models/public-origin.d.mts +63 -0
  442. package/dist/models/public-origin.mjs +114 -0
  443. package/dist/models/public-origin.mjs.map +1 -0
  444. package/dist/models/review-switch-off.d.mts +14 -0
  445. package/dist/models/review-switch-off.mjs +26 -0
  446. package/dist/models/review-switch-off.mjs.map +1 -0
  447. package/dist/models/site-settings.d.mts +23 -0
  448. package/dist/models/site-settings.mjs +48 -0
  449. package/dist/models/site-settings.mjs.map +1 -0
  450. package/dist/models/snapshot.d.mts +36 -1
  451. package/dist/models/snapshot.mjs +50 -3
  452. package/dist/models/snapshot.mjs.map +1 -1
  453. package/dist/models/token.d.mts +13 -1
  454. package/dist/models/token.mjs +23 -5
  455. package/dist/models/token.mjs.map +1 -1
  456. package/dist/models/usage.d.mts +76 -0
  457. package/dist/models/usage.mjs +149 -0
  458. package/dist/models/usage.mjs.map +1 -0
  459. package/dist/models/user.d.mts +1 -1
  460. package/dist/models/user.mjs +4 -3
  461. package/dist/models/user.mjs.map +1 -1
  462. package/dist/models/webhook.d.mts +15 -1
  463. package/dist/models/webhook.mjs +33 -4
  464. package/dist/models/webhook.mjs.map +1 -1
  465. package/dist/models.d.mts +26 -3
  466. package/dist/models.mjs +27 -5
  467. package/dist/options.d.mts +18 -0
  468. package/dist/options.mjs +90 -0
  469. package/dist/options.mjs.map +1 -0
  470. package/dist/paths.d.mts +19 -4
  471. package/dist/paths.mjs +51 -10
  472. package/dist/paths.mjs.map +1 -1
  473. package/dist/retention/ai.d.mts +18 -0
  474. package/dist/retention/ai.mjs +23 -0
  475. package/dist/retention/ai.mjs.map +1 -0
  476. package/dist/retention/pending.mjs +47 -0
  477. package/dist/retention/pending.mjs.map +1 -0
  478. package/dist/retention/screenshot-sweep.d.mts +51 -0
  479. package/dist/retention/screenshot-sweep.mjs +128 -0
  480. package/dist/retention/screenshot-sweep.mjs.map +1 -0
  481. package/dist/retention.d.mts +16 -2
  482. package/dist/retention.mjs +71 -14
  483. package/dist/retention.mjs.map +1 -1
  484. package/dist/schema/baseline.d.mts +6 -0
  485. package/dist/schema/build-component.d.mts +25 -0
  486. package/dist/schema/build-review.d.mts +17 -0
  487. package/dist/schema/build.d.mts +7 -0
  488. package/dist/schema/capture-job.d.mts +60 -0
  489. package/dist/schema/capture-shard.d.mts +35 -0
  490. package/dist/schema/comment.d.mts +2 -0
  491. package/dist/schema/connection.d.mts +24 -0
  492. package/dist/schema/design.d.mts +58 -0
  493. package/dist/schema/insight.d.mts +54 -0
  494. package/dist/schema/notification.d.mts +4 -0
  495. package/dist/schema/project.d.mts +21 -0
  496. package/dist/schema/site-setting.d.mts +21 -0
  497. package/dist/schema/snapshot.d.mts +12 -0
  498. package/dist/schema/usage.d.mts +30 -0
  499. package/dist/schema.d.mts +11 -3
  500. package/dist/serverless.d.mts +21 -0
  501. package/dist/serverless.mjs +37 -0
  502. package/dist/serverless.mjs.map +1 -0
  503. package/dist/test-helpers/create-project.d.mts +1 -1
  504. package/dist/test-helpers/deferred.d.mts +39 -0
  505. package/dist/test-helpers/deferred.mjs +61 -0
  506. package/dist/test-helpers/deferred.mjs.map +1 -0
  507. package/dist/test-helpers/design-contract.d.mts +21 -0
  508. package/dist/test-helpers/design-contract.mjs +40 -0
  509. package/dist/test-helpers/design-contract.mjs.map +1 -0
  510. package/dist/test-helpers/fake-database.mjs +14 -2
  511. package/dist/test-helpers/fake-database.mjs.map +1 -1
  512. package/dist/test-helpers/fake-design.d.mts +43 -0
  513. package/dist/test-helpers/fake-design.mjs +114 -0
  514. package/dist/test-helpers/fake-design.mjs.map +1 -0
  515. package/dist/test-helpers/fake-notifier.d.mts +21 -0
  516. package/dist/test-helpers/fake-notifier.mjs +44 -0
  517. package/dist/test-helpers/fake-notifier.mjs.map +1 -0
  518. package/dist/test-helpers/fake-tables.mjs +255 -67
  519. package/dist/test-helpers/fake-tables.mjs.map +1 -1
  520. package/dist/test-helpers/manual-clock.d.mts +27 -0
  521. package/dist/test-helpers/manual-clock.mjs +56 -0
  522. package/dist/test-helpers/manual-clock.mjs.map +1 -0
  523. package/dist/test-helpers/notifier-contract.d.mts +7 -1
  524. package/dist/test-helpers/notifier-contract.mjs +32 -3
  525. package/dist/test-helpers/notifier-contract.mjs.map +1 -1
  526. package/dist/test-helpers/queue-contract.d.mts +16 -2
  527. package/dist/test-helpers/queue-contract.mjs +195 -1
  528. package/dist/test-helpers/queue-contract.mjs.map +1 -1
  529. package/dist/test-helpers/queue-multi-instance.d.mts +20 -0
  530. package/dist/test-helpers/queue-multi-instance.mjs +206 -0
  531. package/dist/test-helpers/queue-multi-instance.mjs.map +1 -0
  532. package/dist/test-helpers/runner-contract.mjs +11 -0
  533. package/dist/test-helpers/runner-contract.mjs.map +1 -1
  534. package/dist/test-helpers/sql-chunks.mjs +23 -0
  535. package/dist/test-helpers/sql-chunks.mjs.map +1 -1
  536. package/dist/test-helpers/storage-contract.mjs +1 -1
  537. package/dist/test-helpers.d.mts +9 -4
  538. package/dist/test-helpers.mjs +8 -3
  539. package/dist/types.d.mts +14 -3
  540. package/dist/types.mjs +21 -4
  541. package/dist/types.mjs.map +1 -1
  542. package/dist/urls.d.mts +4 -0
  543. package/dist/urls.mjs +11 -2
  544. package/dist/urls.mjs.map +1 -1
  545. package/dist/utils/adaptive-concurrency.d.mts +66 -0
  546. package/dist/utils/adaptive-concurrency.mjs +149 -0
  547. package/dist/utils/adaptive-concurrency.mjs.map +1 -0
  548. package/dist/utils/connect-retry.d.mts +26 -0
  549. package/dist/utils/connect-retry.mjs +43 -0
  550. package/dist/utils/connect-retry.mjs.map +1 -0
  551. package/dist/utils/environment.d.mts +19 -0
  552. package/dist/utils/environment.mjs +59 -0
  553. package/dist/utils/environment.mjs.map +1 -0
  554. package/dist/utils/http.d.mts +17 -1
  555. package/dist/utils/http.mjs +22 -3
  556. package/dist/utils/http.mjs.map +1 -1
  557. package/dist/utils/map-adaptive.d.mts +15 -0
  558. package/dist/utils/map-adaptive.mjs +72 -0
  559. package/dist/utils/map-adaptive.mjs.map +1 -0
  560. package/dist/utils/map-limit.d.mts +10 -0
  561. package/dist/utils/map-limit.mjs +32 -0
  562. package/dist/utils/map-limit.mjs.map +1 -0
  563. package/dist/utils/page-limiter.d.mts +37 -0
  564. package/dist/utils/page-limiter.mjs +56 -0
  565. package/dist/utils/page-limiter.mjs.map +1 -0
  566. package/dist/utils/public-origin.d.mts +20 -0
  567. package/dist/utils/public-origin.mjs +75 -0
  568. package/dist/utils/public-origin.mjs.map +1 -0
  569. package/dist/utils/redact.mjs +3 -0
  570. package/dist/utils/redact.mjs.map +1 -1
  571. package/dist/utils/remote-environment.d.mts +39 -0
  572. package/dist/utils/remote-environment.mjs +114 -0
  573. package/dist/utils/remote-environment.mjs.map +1 -0
  574. package/dist/utils/resources.d.mts +64 -0
  575. package/dist/utils/resources.mjs +122 -0
  576. package/dist/utils/resources.mjs.map +1 -0
  577. package/dist/utils/screenshot-store.d.mts +15 -0
  578. package/dist/utils/screenshot-store.mjs +41 -0
  579. package/dist/utils/screenshot-store.mjs.map +1 -0
  580. package/dist/utils/session-gate.d.mts +22 -0
  581. package/dist/utils/session-gate.mjs +53 -0
  582. package/dist/utils/session-gate.mjs.map +1 -0
  583. package/dist/utils/storybook-token.d.mts +81 -0
  584. package/dist/utils/storybook-token.mjs +134 -0
  585. package/dist/utils/storybook-token.mjs.map +1 -0
  586. package/dist/utils/storybook-url.d.mts +15 -0
  587. package/dist/utils/storybook-url.mjs +21 -0
  588. package/dist/utils/storybook-url.mjs.map +1 -0
  589. package/dist/utils/timing.mjs +1 -1
  590. package/dist/utils/timing.mjs.map +1 -1
  591. package/dist/utils/zip-read.mjs +129 -0
  592. package/dist/utils/zip-read.mjs.map +1 -0
  593. package/dist/utils/zip-write.d.mts +18 -0
  594. package/dist/utils/zip-write.mjs +188 -0
  595. package/dist/utils/zip-write.mjs.map +1 -0
  596. package/dist/utils.d.mts +17 -3
  597. package/dist/utils.mjs +18 -4
  598. package/package.json +32 -23
  599. package/dist/adapter/capture-runner.mjs.map +0 -1
  600. package/dist/adapter/git-host/comments.d.mts +0 -41
  601. package/dist/adapter/git-host/comments.mjs +0 -54
  602. package/dist/adapter/git-host/comments.mjs.map +0 -1
  603. package/dist/adapter/git-host/helpers.d.mts +0 -13
  604. package/dist/adapter/git-host/helpers.mjs +0 -30
  605. package/dist/adapter/git-host/helpers.mjs.map +0 -1
  606. package/dist/adapter/git-host.d.mts +0 -42
  607. package/dist/capture/queue.d.mts +0 -38
  608. package/dist/capture/queue.mjs +0 -98
  609. package/dist/capture/queue.mjs.map +0 -1
  610. package/dist/capture/recovery.d.mts +0 -21
  611. package/dist/capture/recovery.mjs +0 -83
  612. package/dist/capture/recovery.mjs.map +0 -1
  613. package/dist/capture/status-fanout.mjs +0 -72
  614. package/dist/capture/status-fanout.mjs.map +0 -1
  615. package/dist/diff/engine.d.mts +0 -16
  616. package/dist/diff/engine.mjs +0 -87
  617. package/dist/diff/engine.mjs.map +0 -1
  618. package/dist/diff/options.d.mts +0 -42
  619. package/dist/diff/options.mjs +0 -15
  620. package/dist/diff/options.mjs.map +0 -1
  621. package/dist/models/status-config.d.mts +0 -38
  622. package/dist/models/status-config.mjs +0 -63
  623. package/dist/models/status-config.mjs.map +0 -1
  624. package/dist/schema/status-config.d.mts +0 -16
  625. package/dist/test-helpers/git-contract.d.mts +0 -11
  626. package/dist/test-helpers/git-contract.mjs +0 -34
  627. package/dist/test-helpers/git-contract.mjs.map +0 -1
  628. /package/dist/adapter/{git-host.mjs → oauth.mjs} +0 -0
@@ -15,6 +15,23 @@ interface StoryViewportConfig {
15
15
  defaultViewport?: string;
16
16
  viewports?: Record<string, StoryViewportDefinition>;
17
17
  }
18
+ /** Viewport a mode captures at: a width, a Storybook viewport name, or explicit dimensions. */
19
+ type ModeViewportValue = number | string | {
20
+ width: number;
21
+ height: number;
22
+ };
23
+ /**
24
+ * One entry of `parameters.{chromatic,storyshelf}.modes`. `viewport` sizes the
25
+ * page, `disable` removes an inherited mode, every other key is a Storybook
26
+ * global (theme, locale, backgrounds, …) applied through the `globals` URL arg.
27
+ */
28
+ interface ModeDefinition {
29
+ viewport?: ModeViewportValue;
30
+ disable?: boolean;
31
+ [global: string]: unknown;
32
+ }
33
+ /** Named modes keyed by display name (the slug of the name is the baseline identity). */
34
+ type ModeMap = Record<string, ModeDefinition>;
18
35
  /** Per-story capture parameters from the Storybook index. */
19
36
  interface StoryParameters {
20
37
  disableSnapshot?: boolean;
@@ -39,6 +56,8 @@ interface StoryParameters {
39
56
  waitForFonts?: boolean;
40
57
  /** Storybook viewport config; adds the story's default viewport to the project list at capture time. */
41
58
  viewport?: StoryViewportConfig;
59
+ /** Named modes: extra captures of this story under different globals/viewports. */
60
+ modes?: ModeMap;
42
61
  }
43
62
  /** Return whether a story is marked flaky (failures stay non-blocking). */
44
63
  declare function isFlakyStory(entry: Pick<StoryEntry, "tags" | "parameters">): boolean;
@@ -52,18 +71,35 @@ interface StoryEntry {
52
71
  importPath?: string;
53
72
  tags?: string[];
54
73
  type: "story" | "docs";
74
+ /**
75
+ * Set on a Storybook 10 `.test()` entry (index `subtype: "test"`): the id of the story it is
76
+ * written under. Test entries are never captured; a project that runs tests runs them in the
77
+ * iframe (ADR 0004, decision 10).
78
+ */
79
+ parentId?: string;
55
80
  parameters?: StoryParameters;
81
+ /** Figma URL declared via `parameters.design` / `parameters.storyshelf.figma`. */
82
+ designUrl?: string;
83
+ /** Declared design value that is not a usable Figma URL (ignored; logged at capture). */
84
+ designInvalid?: string;
56
85
  }
57
86
  /** Minimal structural page handle handed to `StorySourceAdapter.waitForReady`. */
58
87
  interface CapturePage {
59
88
  evaluate(pageFunction: string | ((...args: unknown[]) => unknown), arg?: unknown): Promise<unknown>;
60
89
  waitForSelector(selector: string, options?: Record<string, unknown>): Promise<unknown>;
61
90
  }
91
+ /** Optional discovery inputs. */
92
+ interface DiscoverOptions {
93
+ /** Validates declared design links (`design` / `storyshelf.figma`); without it none are extracted. */
94
+ parseDesignUrl?: (raw: string) => {
95
+ url: string;
96
+ } | null;
97
+ }
62
98
  /** Adapter that discovers and renders stories from a Storybook build. */
63
99
  interface StorySourceAdapter {
64
100
  name: string;
65
- discover(source: string): Promise<StoryEntry[]>;
66
- buildUrl(baseUrl: string, storyId: string): string;
101
+ discover(source: string, options?: DiscoverOptions): Promise<StoryEntry[]>;
102
+ buildUrl(baseUrl: string, storyId: string, globals?: Record<string, unknown>): string;
67
103
  screenshotSelector?: string;
68
104
  /**
69
105
  * Optional hook loading the module dependency graph of an extracted build,
@@ -85,9 +121,11 @@ interface Viewport {
85
121
  name: string;
86
122
  width: number;
87
123
  height: number;
124
+ /** Storybook globals applied for this capture (set for modes only). */
125
+ globals?: Record<string, unknown>;
88
126
  }
89
127
  /** Default viewport used when none is configured. */
90
128
  declare const DEFAULT_VIEWPORTS: Viewport[];
91
129
  //#endregion
92
- export { CapturePage, DEFAULT_VIEWPORTS, StoryEntry, StoryParameters, StorySourceAdapter, StoryViewportConfig, StoryViewportDefinition, Viewport, isDisabledStory, isFlakyStory };
130
+ export { CapturePage, DEFAULT_VIEWPORTS, DiscoverOptions, ModeDefinition, ModeMap, ModeViewportValue, StoryEntry, StoryParameters, StorySourceAdapter, StoryViewportConfig, StoryViewportDefinition, Viewport, isDisabledStory, isFlakyStory };
93
131
  //# sourceMappingURL=adapter.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.mjs","names":[],"sources":["../../src/capture/adapter.ts"],"sourcesContent":["import type { DepGraph } from \"@storyshelf/affected\";\n\n/** A custom viewport registered on a story via `parameters.viewport`. */\nexport interface StoryViewportDefinition {\n name?: string;\n styles?: {\n width?: string | number;\n height?: string | number;\n };\n}\n\n/** Storybook `parameters.viewport` config; carried through discovery for per-story viewport capture. */\nexport interface StoryViewportConfig {\n defaultViewport?: string;\n viewports?: Record<string, StoryViewportDefinition>;\n}\n\n/** Per-story capture parameters from the Storybook index. */\nexport interface StoryParameters {\n disableSnapshot?: boolean;\n delay?: number;\n diffThreshold?: number;\n pauseAnimationAtEnd?: boolean;\n flakyTest?: boolean;\n /** Force auto-crop (true) or full viewport (false) for this story; undefined uses 60% whitespace rule. */\n autoCrop?: boolean;\n /** Whitespace fraction (0..1) above which we crop; default 0.6. */\n autoCropThreshold?: number;\n /** Whether overflow should trigger fullPage; default true. */\n fullPageOnOverflow?: boolean;\n /** Min dimensions for the final PNG (clamp after padding). */\n minWidth?: number;\n minHeight?: number;\n maxWidth?: number;\n maxHeight?: number;\n /** Burst count for steady capture (1 = single shot, 3 = burst 100ms); default 1. */\n burst?: number;\n /** Whether to wait for fonts.ready before screenshot; default true when burst >1. */\n waitForFonts?: boolean;\n /** Storybook viewport config; adds the story's default viewport to the project list at capture time. */\n viewport?: StoryViewportConfig;\n}\n\n/** Return whether a story is marked flaky (failures stay non-blocking). */\nexport function isFlakyStory(entry: Pick<StoryEntry, \"tags\" | \"parameters\">): boolean {\n if (entry.parameters?.flakyTest) return true;\n const tags = entry.tags ?? [];\n for (const t of tags) {\n if (t.toLowerCase() === \"flaky-test\") return true;\n }\n return false;\n}\n\n/** Return whether a story is excluded from snapshot capture. */\nexport function isDisabledStory(entry: Pick<StoryEntry, \"tags\" | \"parameters\">): boolean {\n if (entry.parameters?.disableSnapshot) return true;\n const tags = entry.tags ?? [];\n for (const t of tags) {\n const lower = t.toLowerCase();\n if (lower === \"skip\" || lower === \"disable\" || lower === \"disable-snapshot\") return true;\n }\n return false;\n}\n\n/** A single discoverable story or docs entry within a Storybook. */\nexport interface StoryEntry {\n id: string;\n title: string;\n name: string;\n importPath?: string;\n tags?: string[];\n type: \"story\" | \"docs\";\n parameters?: StoryParameters;\n}\n\n/** Minimal structural page handle handed to `StorySourceAdapter.waitForReady`. */\nexport interface CapturePage {\n evaluate(\n pageFunction: string | ((...args: unknown[]) => unknown),\n arg?: unknown,\n ): Promise<unknown>;\n waitForSelector(selector: string, options?: Record<string, unknown>): Promise<unknown>;\n}\n\n/** Adapter that discovers and renders stories from a Storybook build. */\nexport interface StorySourceAdapter {\n name: string;\n discover(source: string): Promise<StoryEntry[]>;\n buildUrl(baseUrl: string, storyId: string): string;\n screenshotSelector?: string;\n /**\n * Optional hook loading the module dependency graph of an extracted build,\n * enabling affected capture (render only stories downstream of a change).\n * Absent (or resolving `null`) means the source cannot be traced, so the\n * orchestrator renders every story.\n */\n loadDependencyGraph?(sourceDir: string): Promise<DepGraph | null>;\n /**\n * Optional hook invoked after navigation and before the screenshot sequence,\n * letting a custom source wait until it is ready to capture. The built-in\n * Storybook adapter leaves this undefined (rendered via iframe). See\n * ADR 0005.\n */\n waitForReady?(page: CapturePage): Promise<void>;\n}\n\n/** A viewport size at which stories are captured. */\nexport interface Viewport {\n name: string;\n width: number;\n height: number;\n}\n\n/** Default viewport used when none is configured. */\nexport const DEFAULT_VIEWPORTS: Viewport[] = [{ name: \"desktop\", width: 1280, height: 720 }];\n"],"mappings":";;;;;AA4CA,SAAgB,aAAa,OAAyD;CACpF,IAAI,MAAM,YAAY,WAAW,OAAO;CACxC,MAAM,OAAO,MAAM,QAAQ,CAAC;CAC5B,KAAK,MAAM,KAAK,MACd,IAAI,EAAE,YAAY,MAAM,cAAc,OAAO;CAE/C,OAAO;AACT;;AAGA,SAAgB,gBAAgB,OAAyD;CACvF,IAAI,MAAM,YAAY,iBAAiB,OAAO;CAC9C,MAAM,OAAO,MAAM,QAAQ,CAAC;CAC5B,KAAK,MAAM,KAAK,MAAM;EACpB,MAAM,QAAQ,EAAE,YAAY;EAC5B,IAAI,UAAU,UAAU,UAAU,aAAa,UAAU,oBAAoB,OAAO;CACtF;CACA,OAAO;AACT;;AAoDA,MAAa,oBAAgC,CAAC;CAAE,MAAM;CAAW,OAAO;CAAM,QAAQ;AAAI,CAAC"}
1
+ {"version":3,"file":"adapter.mjs","names":[],"sources":["../../src/capture/adapter.ts"],"sourcesContent":["import type { DepGraph } from \"@storyshelf/affected\";\n\n/** A custom viewport registered on a story via `parameters.viewport`. */\nexport interface StoryViewportDefinition {\n name?: string;\n styles?: {\n width?: string | number;\n height?: string | number;\n };\n}\n\n/** Storybook `parameters.viewport` config; carried through discovery for per-story viewport capture. */\nexport interface StoryViewportConfig {\n defaultViewport?: string;\n viewports?: Record<string, StoryViewportDefinition>;\n}\n\n/** Viewport a mode captures at: a width, a Storybook viewport name, or explicit dimensions. */\nexport type ModeViewportValue = number | string | { width: number; height: number };\n\n/**\n * One entry of `parameters.{chromatic,storyshelf}.modes`. `viewport` sizes the\n * page, `disable` removes an inherited mode, every other key is a Storybook\n * global (theme, locale, backgrounds, …) applied through the `globals` URL arg.\n */\nexport interface ModeDefinition {\n viewport?: ModeViewportValue;\n disable?: boolean;\n [global: string]: unknown;\n}\n\n/** Named modes keyed by display name (the slug of the name is the baseline identity). */\nexport type ModeMap = Record<string, ModeDefinition>;\n\n/** Per-story capture parameters from the Storybook index. */\nexport interface StoryParameters {\n disableSnapshot?: boolean;\n delay?: number;\n diffThreshold?: number;\n pauseAnimationAtEnd?: boolean;\n flakyTest?: boolean;\n /** Force auto-crop (true) or full viewport (false) for this story; undefined uses 60% whitespace rule. */\n autoCrop?: boolean;\n /** Whitespace fraction (0..1) above which we crop; default 0.6. */\n autoCropThreshold?: number;\n /** Whether overflow should trigger fullPage; default true. */\n fullPageOnOverflow?: boolean;\n /** Min dimensions for the final PNG (clamp after padding). */\n minWidth?: number;\n minHeight?: number;\n maxWidth?: number;\n maxHeight?: number;\n /** Burst count for steady capture (1 = single shot, 3 = burst 100ms); default 1. */\n burst?: number;\n /** Whether to wait for fonts.ready before screenshot; default true when burst >1. */\n waitForFonts?: boolean;\n /** Storybook viewport config; adds the story's default viewport to the project list at capture time. */\n viewport?: StoryViewportConfig;\n /** Named modes: extra captures of this story under different globals/viewports. */\n modes?: ModeMap;\n}\n\n/** Return whether a story is marked flaky (failures stay non-blocking). */\nexport function isFlakyStory(entry: Pick<StoryEntry, \"tags\" | \"parameters\">): boolean {\n if (entry.parameters?.flakyTest) return true;\n const tags = entry.tags ?? [];\n for (const t of tags) {\n if (t.toLowerCase() === \"flaky-test\") return true;\n }\n return false;\n}\n\n/** Return whether a story is excluded from snapshot capture. */\nexport function isDisabledStory(entry: Pick<StoryEntry, \"tags\" | \"parameters\">): boolean {\n if (entry.parameters?.disableSnapshot) return true;\n const tags = entry.tags ?? [];\n for (const t of tags) {\n const lower = t.toLowerCase();\n if (lower === \"skip\" || lower === \"disable\" || lower === \"disable-snapshot\") return true;\n }\n return false;\n}\n\n/** A single discoverable story or docs entry within a Storybook. */\nexport interface StoryEntry {\n id: string;\n title: string;\n name: string;\n importPath?: string;\n tags?: string[];\n type: \"story\" | \"docs\";\n /**\n * Set on a Storybook 10 `.test()` entry (index `subtype: \"test\"`): the id of the story it is\n * written under. Test entries are never captured; a project that runs tests runs them in the\n * iframe (ADR 0004, decision 10).\n */\n parentId?: string;\n parameters?: StoryParameters;\n /** Figma URL declared via `parameters.design` / `parameters.storyshelf.figma`. */\n designUrl?: string;\n /** Declared design value that is not a usable Figma URL (ignored; logged at capture). */\n designInvalid?: string;\n}\n\n/** Minimal structural page handle handed to `StorySourceAdapter.waitForReady`. */\nexport interface CapturePage {\n evaluate(\n pageFunction: string | ((...args: unknown[]) => unknown),\n arg?: unknown,\n ): Promise<unknown>;\n waitForSelector(selector: string, options?: Record<string, unknown>): Promise<unknown>;\n}\n\n/** Optional discovery inputs. */\nexport interface DiscoverOptions {\n /** Validates declared design links (`design` / `storyshelf.figma`); without it none are extracted. */\n parseDesignUrl?: (raw: string) => { url: string } | null;\n}\n\n/** Adapter that discovers and renders stories from a Storybook build. */\nexport interface StorySourceAdapter {\n name: string;\n discover(source: string, options?: DiscoverOptions): Promise<StoryEntry[]>;\n buildUrl(baseUrl: string, storyId: string, globals?: Record<string, unknown>): string;\n screenshotSelector?: string;\n /**\n * Optional hook loading the module dependency graph of an extracted build,\n * enabling affected capture (render only stories downstream of a change).\n * Absent (or resolving `null`) means the source cannot be traced, so the\n * orchestrator renders every story.\n */\n loadDependencyGraph?(sourceDir: string): Promise<DepGraph | null>;\n /**\n * Optional hook invoked after navigation and before the screenshot sequence,\n * letting a custom source wait until it is ready to capture. The built-in\n * Storybook adapter leaves this undefined (rendered via iframe). See\n * ADR 0005.\n */\n waitForReady?(page: CapturePage): Promise<void>;\n}\n\n/** A viewport size at which stories are captured. */\nexport interface Viewport {\n name: string;\n width: number;\n height: number;\n /** Storybook globals applied for this capture (set for modes only). */\n globals?: Record<string, unknown>;\n}\n\n/** Default viewport used when none is configured. */\nexport const DEFAULT_VIEWPORTS: Viewport[] = [{ name: \"desktop\", width: 1280, height: 720 }];\n"],"mappings":";;;;;AA+DA,SAAgB,aAAa,OAAyD;CACpF,IAAI,MAAM,YAAY,WAAW,OAAO;CACxC,MAAM,OAAO,MAAM,QAAQ,CAAC;CAC5B,KAAK,MAAM,KAAK,MACd,IAAI,EAAE,YAAY,MAAM,cAAc,OAAO;CAE/C,OAAO;AACT;;AAGA,SAAgB,gBAAgB,OAAyD;CACvF,IAAI,MAAM,YAAY,iBAAiB,OAAO;CAC9C,MAAM,OAAO,MAAM,QAAQ,CAAC;CAC5B,KAAK,MAAM,KAAK,MAAM;EACpB,MAAM,QAAQ,EAAE,YAAY;EAC5B,IAAI,UAAU,UAAU,UAAU,aAAa,UAAU,oBAAoB,OAAO;CACtF;CACA,OAAO;AACT;;AAsEA,MAAa,oBAAgC,CAAC;CAAE,MAAM;CAAW,OAAO;CAAM,QAAQ;AAAI,CAAC"}
@@ -10,6 +10,8 @@ interface InheritedStory {
10
10
  story: StoryEntry;
11
11
  viewport: Viewport;
12
12
  baseline: Baseline;
13
+ /** Engine whose baseline is reused (chromium when absent). */
14
+ browser?: string;
13
15
  }
14
16
  //#endregion
15
17
  export { InheritedStory };
@@ -1,10 +1,18 @@
1
1
  import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
- import { BaselineModel } from "../models/baseline.mjs";
5
4
  import { resolveStoryViewports } from "./storybook.mjs";
5
+ import { BuildModel } from "../models/build.mjs";
6
+ import { BaselineModel } from "../models/baseline.mjs";
6
7
  import { selectAffectedStories } from "@storyshelf/affected";
7
8
  //#region src/capture/affected.ts
9
+ /**
10
+ * Resolve the affected import paths for a build, or `null` for full capture (affected capture
11
+ * disabled, or no computation was posted). The one rule for the whole and the sharded path alike.
12
+ */
13
+ function affectedPathsFor(build) {
14
+ return build.affectedOnly ? BuildModel.affectedPaths(build) : null;
15
+ }
8
16
  /** Split one inherit candidate by per-viewport baseline availability. */
9
17
  async function splitStory(baselines, input, story) {
10
18
  const render = [];
@@ -12,12 +20,13 @@ async function splitStory(baselines, input, story) {
12
20
  const viewports = resolveStoryViewports(story, input.viewports);
13
21
  const settled = await Promise.all(viewports.map(async (viewport) => ({
14
22
  viewport,
15
- baseline: await baselines.resolve(input.projectId, story.id, viewport.name, input.branch, input.defaultBranch)
23
+ baseline: await baselines.resolve(input.projectId, story.id, viewport.name, input.branch, input.defaultBranch, input.browser)
16
24
  })));
17
25
  for (const { viewport, baseline } of settled) if (baseline) inherited.push({
18
26
  story,
19
27
  viewport,
20
- baseline
28
+ baseline,
29
+ ...input.browser ? { browser: input.browser } : {}
21
30
  });
22
31
  else if (!render.includes(story)) render.push(story);
23
32
  return {
@@ -59,6 +68,6 @@ async function partitionAffectedStories(input) {
59
68
  };
60
69
  }
61
70
  //#endregion
62
- export { partitionAffectedStories };
71
+ export { affectedPathsFor, partitionAffectedStories };
63
72
 
64
73
  //# sourceMappingURL=affected.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"affected.mjs","names":[],"sources":["../../src/capture/affected.ts"],"sourcesContent":["import { selectAffectedStories } from \"@storyshelf/affected\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport { BaselineModel, type BaselineTables } from \"../models/baseline.ts\";\nimport type { Baseline } from \"../schema/baseline.ts\";\nimport type { StoryEntry, Viewport } from \"./adapter.ts\";\nimport { resolveStoryViewports } from \"./storybook.ts\";\n\n/** An inherit candidate paired with the baseline it reuses. */\nexport interface InheritedStory {\n story: StoryEntry;\n viewport: Viewport;\n baseline: Baseline;\n}\n\n/** Stories split into capture-now versus inherit-from-baseline sets. */\nexport interface AffectedPartition {\n render: StoryEntry[];\n inherited: InheritedStory[];\n}\n\n/** Inputs for partitioning a build's stories by affected status. */\nexport interface PartitionInput {\n db: DatabaseAdapter;\n tables: BaselineTables;\n projectId: string;\n branch: string;\n defaultBranch: string;\n stories: StoryEntry[];\n viewports: Viewport[];\n /** Affected import paths, or `null` to render everything. */\n affectedPaths: string[] | null;\n}\n\n/** Split one inherit candidate by per-viewport baseline availability. */\nasync function splitStory(\n baselines: BaselineModel,\n input: PartitionInput,\n story: StoryEntry,\n): Promise<AffectedPartition> {\n const render: StoryEntry[] = [];\n const inherited: InheritedStory[] = [];\n const viewports = resolveStoryViewports(story, input.viewports);\n const settled = await Promise.all(\n viewports.map(async (viewport) => ({\n viewport,\n baseline: await baselines.resolve(\n input.projectId,\n story.id,\n viewport.name,\n input.branch,\n input.defaultBranch,\n ),\n })),\n );\n for (const { viewport, baseline } of settled) {\n if (baseline) {\n inherited.push({ story, viewport, baseline });\n } else if (!render.includes(story)) {\n render.push(story);\n }\n }\n return { render, inherited };\n}\n\n/** Merge per-story partitions into a single render/inherit split. */\nfunction mergePartitions(partitions: AffectedPartition[]): AffectedPartition {\n const render: StoryEntry[] = [];\n const inherited: InheritedStory[] = [];\n for (const partition of partitions) {\n render.push(...partition.render);\n inherited.push(...partition.inherited);\n }\n return { render, inherited };\n}\n\n/**\n * Partition discovered stories into render versus inherit sets.\n *\n * Stories outside the affected set inherit their baseline without rendering,\n * except when no baseline exists (new stories always render). A `null`\n * affected set renders everything.\n */\nexport async function partitionAffectedStories(input: PartitionInput): Promise<AffectedPartition> {\n const selected = selectAffectedStories(input.stories, input.affectedPaths);\n if (selected.inherit.length === 0) {\n return { render: selected.render, inherited: [] };\n }\n const baselines = new BaselineModel(input.db, input.tables);\n const partitions = await Promise.all(\n selected.inherit.map(async (story) => await splitStory(baselines, input, story)),\n );\n const merged = mergePartitions(partitions);\n return { render: [...selected.render, ...merged.render], inherited: merged.inherited };\n}\n"],"mappings":";;;;;;;;AAkCA,eAAe,WACb,WACA,OACA,OAC4B;CAC5B,MAAM,SAAuB,CAAC;CAC9B,MAAM,YAA8B,CAAC;CACrC,MAAM,YAAY,sBAAsB,OAAO,MAAM,SAAS;CAC9D,MAAM,UAAU,MAAM,QAAQ,IAC5B,UAAU,IAAI,OAAO,cAAc;EACjC;EACA,UAAU,MAAM,UAAU,QACxB,MAAM,WACN,MAAM,IACN,SAAS,MACT,MAAM,QACN,MAAM,aACR;CACF,EAAE,CACJ;CACA,KAAK,MAAM,EAAE,UAAU,cAAc,SACnC,IAAI,UACF,UAAU,KAAK;EAAE;EAAO;EAAU;CAAS,CAAC;MACvC,IAAI,CAAC,OAAO,SAAS,KAAK,GAC/B,OAAO,KAAK,KAAK;CAGrB,OAAO;EAAE;EAAQ;CAAU;AAC7B;;AAGA,SAAS,gBAAgB,YAAoD;CAC3E,MAAM,SAAuB,CAAC;CAC9B,MAAM,YAA8B,CAAC;CACrC,KAAK,MAAM,aAAa,YAAY;EAClC,OAAO,KAAK,GAAG,UAAU,MAAM;EAC/B,UAAU,KAAK,GAAG,UAAU,SAAS;CACvC;CACA,OAAO;EAAE;EAAQ;CAAU;AAC7B;;;;;;;;AASA,eAAsB,yBAAyB,OAAmD;CAChG,MAAM,WAAW,sBAAsB,MAAM,SAAS,MAAM,aAAa;CACzE,IAAI,SAAS,QAAQ,WAAW,GAC9B,OAAO;EAAE,QAAQ,SAAS;EAAQ,WAAW,CAAC;CAAE;CAElD,MAAM,YAAY,IAAI,cAAc,MAAM,IAAI,MAAM,MAAM;CAI1D,MAAM,SAAS,gBAAgB,MAHN,QAAQ,IAC/B,SAAS,QAAQ,IAAI,OAAO,UAAU,MAAM,WAAW,WAAW,OAAO,KAAK,CAAC,CACjF,CACyC;CACzC,OAAO;EAAE,QAAQ,CAAC,GAAG,SAAS,QAAQ,GAAG,OAAO,MAAM;EAAG,WAAW,OAAO;CAAU;AACvF"}
1
+ {"version":3,"file":"affected.mjs","names":[],"sources":["../../src/capture/affected.ts"],"sourcesContent":["import { selectAffectedStories } from \"@storyshelf/affected\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport { BaselineModel, type BaselineTables } from \"../models/baseline.ts\";\nimport { BuildModel } from \"../models/build.ts\";\nimport type { Baseline } from \"../schema/baseline.ts\";\nimport type { Build } from \"../schema/build.ts\";\nimport type { StoryEntry, Viewport } from \"./adapter.ts\";\nimport { resolveStoryViewports } from \"./storybook.ts\";\n\n/**\n * Resolve the affected import paths for a build, or `null` for full capture (affected capture\n * disabled, or no computation was posted). The one rule for the whole and the sharded path alike.\n */\nexport function affectedPathsFor(\n build: Pick<Build, \"affectedOnly\" | \"affectedImportPaths\">,\n): string[] | null {\n return build.affectedOnly ? BuildModel.affectedPaths(build) : null;\n}\n\n/** An inherit candidate paired with the baseline it reuses. */\nexport interface InheritedStory {\n story: StoryEntry;\n viewport: Viewport;\n baseline: Baseline;\n /** Engine whose baseline is reused (chromium when absent). */\n browser?: string;\n}\n\n/** Stories split into capture-now versus inherit-from-baseline sets. */\nexport interface AffectedPartition {\n render: StoryEntry[];\n inherited: InheritedStory[];\n}\n\n/** Inputs for partitioning a build's stories by affected status. */\nexport interface PartitionInput {\n db: DatabaseAdapter;\n tables: BaselineTables;\n projectId: string;\n branch: string;\n defaultBranch: string;\n stories: StoryEntry[];\n viewports: Viewport[];\n /** Engine to resolve baselines for (default chromium). */\n browser?: string;\n /** Affected import paths, or `null` to render everything. */\n affectedPaths: string[] | null;\n}\n\n/** Split one inherit candidate by per-viewport baseline availability. */\nasync function splitStory(\n baselines: BaselineModel,\n input: PartitionInput,\n story: StoryEntry,\n): Promise<AffectedPartition> {\n const render: StoryEntry[] = [];\n const inherited: InheritedStory[] = [];\n const viewports = resolveStoryViewports(story, input.viewports);\n const settled = await Promise.all(\n viewports.map(async (viewport) => ({\n viewport,\n baseline: await baselines.resolve(\n input.projectId,\n story.id,\n viewport.name,\n input.branch,\n input.defaultBranch,\n input.browser,\n ),\n })),\n );\n for (const { viewport, baseline } of settled) {\n if (baseline) {\n inherited.push({\n story,\n viewport,\n baseline,\n ...(input.browser ? { browser: input.browser } : {}),\n });\n } else if (!render.includes(story)) {\n render.push(story);\n }\n }\n return { render, inherited };\n}\n\n/** Merge per-story partitions into a single render/inherit split. */\nfunction mergePartitions(partitions: AffectedPartition[]): AffectedPartition {\n const render: StoryEntry[] = [];\n const inherited: InheritedStory[] = [];\n for (const partition of partitions) {\n render.push(...partition.render);\n inherited.push(...partition.inherited);\n }\n return { render, inherited };\n}\n\n/**\n * Partition discovered stories into render versus inherit sets.\n *\n * Stories outside the affected set inherit their baseline without rendering,\n * except when no baseline exists (new stories always render). A `null`\n * affected set renders everything.\n */\nexport async function partitionAffectedStories(input: PartitionInput): Promise<AffectedPartition> {\n const selected = selectAffectedStories(input.stories, input.affectedPaths);\n if (selected.inherit.length === 0) {\n return { render: selected.render, inherited: [] };\n }\n const baselines = new BaselineModel(input.db, input.tables);\n const partitions = await Promise.all(\n selected.inherit.map(async (story) => await splitStory(baselines, input, story)),\n );\n const merged = mergePartitions(partitions);\n return { render: [...selected.render, ...merged.render], inherited: merged.inherited };\n}\n"],"mappings":";;;;;;;;;;;;AAaA,SAAgB,iBACd,OACiB;CACjB,OAAO,MAAM,eAAe,WAAW,cAAc,KAAK,IAAI;AAChE;;AAiCA,eAAe,WACb,WACA,OACA,OAC4B;CAC5B,MAAM,SAAuB,CAAC;CAC9B,MAAM,YAA8B,CAAC;CACrC,MAAM,YAAY,sBAAsB,OAAO,MAAM,SAAS;CAC9D,MAAM,UAAU,MAAM,QAAQ,IAC5B,UAAU,IAAI,OAAO,cAAc;EACjC;EACA,UAAU,MAAM,UAAU,QACxB,MAAM,WACN,MAAM,IACN,SAAS,MACT,MAAM,QACN,MAAM,eACN,MAAM,OACR;CACF,EAAE,CACJ;CACA,KAAK,MAAM,EAAE,UAAU,cAAc,SACnC,IAAI,UACF,UAAU,KAAK;EACb;EACA;EACA;EACA,GAAI,MAAM,UAAU,EAAE,SAAS,MAAM,QAAQ,IAAI,CAAC;CACpD,CAAC;MACI,IAAI,CAAC,OAAO,SAAS,KAAK,GAC/B,OAAO,KAAK,KAAK;CAGrB,OAAO;EAAE;EAAQ;CAAU;AAC7B;;AAGA,SAAS,gBAAgB,YAAoD;CAC3E,MAAM,SAAuB,CAAC;CAC9B,MAAM,YAA8B,CAAC;CACrC,KAAK,MAAM,aAAa,YAAY;EAClC,OAAO,KAAK,GAAG,UAAU,MAAM;EAC/B,UAAU,KAAK,GAAG,UAAU,SAAS;CACvC;CACA,OAAO;EAAE;EAAQ;CAAU;AAC7B;;;;;;;;AASA,eAAsB,yBAAyB,OAAmD;CAChG,MAAM,WAAW,sBAAsB,MAAM,SAAS,MAAM,aAAa;CACzE,IAAI,SAAS,QAAQ,WAAW,GAC9B,OAAO;EAAE,QAAQ,SAAS;EAAQ,WAAW,CAAC;CAAE;CAElD,MAAM,YAAY,IAAI,cAAc,MAAM,IAAI,MAAM,MAAM;CAI1D,MAAM,SAAS,gBAAgB,MAHN,QAAQ,IAC/B,SAAS,QAAQ,IAAI,OAAO,UAAU,MAAM,WAAW,WAAW,OAAO,KAAK,CAAC,CACjF,CACyC;CACzC,OAAO;EAAE,QAAQ,CAAC,GAAG,SAAS,QAAQ,GAAG,OAAO,MAAM;EAAG,WAAW,OAAO;CAAU;AACvF"}
@@ -0,0 +1,84 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { redactSecrets } from "../utils/redact.mjs";
5
+ import { emitAttemptLog } from "../models/capture-attempt.mjs";
6
+ //#region src/capture/attempt-logger.ts
7
+ /** Longest error text recorded on an attempt-log line. */
8
+ const ERROR_TEXT_MAX = 500;
9
+ /** Mirrored levels; `debug` stays in pino only so attempt logs keep the signal. */
10
+ const MIRRORED_LEVELS = [
11
+ "info",
12
+ "warn",
13
+ "error"
14
+ ];
15
+ /**
16
+ * Mirror a logger's `info`/`warn`/`error` lines onto the attempt log.
17
+ *
18
+ * Writes go through one promise chain: `capture_logs` numbers lines with a
19
+ * read-then-insert, so concurrent appends (parallel pages failing at once)
20
+ * would collide on `seq`. With no recorder the logger is returned unchanged.
21
+ */
22
+ function mirrorAttemptLog(logger, record, levelFor = (level) => level) {
23
+ let chain = Promise.resolve();
24
+ const enqueue = (level, message, fields) => {
25
+ if (!record) return;
26
+ const recorded = levelFor(level, fields);
27
+ chain = chain.then(async () => {
28
+ await emitAttemptLog(record, logger, recorded, message, fields);
29
+ });
30
+ };
31
+ const base = logger ?? silentLogger();
32
+ return {
33
+ logger: new Proxy(base, { get(target, property, receiver) {
34
+ const level = MIRRORED_LEVELS.find((name) => name === property);
35
+ if (!level) return Reflect.get(target, property, receiver);
36
+ return (first, second, ...rest) => {
37
+ const args = [
38
+ first,
39
+ second,
40
+ ...rest
41
+ ];
42
+ while (args.length > 1 && args.at(-1) === void 0) args.pop();
43
+ Reflect.apply(target[level], target, args);
44
+ const [fields, message] = parseArgs(first, second);
45
+ enqueue(level, message, fields);
46
+ };
47
+ } }),
48
+ log: (level, message, fields) => {
49
+ base[level](fields ?? {}, message);
50
+ enqueue(level, message, fields === void 0 ? void 0 : plainFields(fields));
51
+ },
52
+ flush: async () => {
53
+ await chain;
54
+ }
55
+ };
56
+ }
57
+ /** pino call shapes: `(fields, message)` or `(message)`. */
58
+ function parseArgs(first, second) {
59
+ if (typeof first === "string") return [void 0, first];
60
+ return [first && typeof first === "object" ? plainFields(first) : void 0, typeof second === "string" ? second : ""];
61
+ }
62
+ /** JSON-safe copy: an `Error` under `err` becomes its capped message. */
63
+ function plainFields(fields) {
64
+ const copy = { ...fields };
65
+ const err = copy["err"];
66
+ if (err instanceof Error) copy["err"] = redactSecrets(err.message).slice(0, ERROR_TEXT_MAX);
67
+ return copy;
68
+ }
69
+ function noop() {}
70
+ /** Stand-in when no logger was configured, so callers can log unconditionally. */
71
+ function silentLogger() {
72
+ const stub = {
73
+ info: noop,
74
+ warn: noop,
75
+ error: noop,
76
+ debug: noop
77
+ };
78
+ stub["child"] = () => stub;
79
+ return stub;
80
+ }
81
+ //#endregion
82
+ export { mirrorAttemptLog };
83
+
84
+ //# sourceMappingURL=attempt-logger.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attempt-logger.mjs","names":[],"sources":["../../src/capture/attempt-logger.ts"],"sourcesContent":["import type { Logger } from \"pino\";\nimport { emitAttemptLog, type AttemptLogRecorder } from \"../models/capture-attempt.ts\";\nimport type { CaptureLogLevel } from \"../schema/capture-attempt.ts\";\nimport { redactSecrets } from \"../utils/redact.ts\";\n\n/** Longest error text recorded on an attempt-log line. */\nconst ERROR_TEXT_MAX = 500;\n\n/** Mirrored levels; `debug` stays in pino only so attempt logs keep the signal. */\nconst MIRRORED_LEVELS = [\"info\", \"warn\", \"error\"] as const;\n\n/** A logger that also records `info`/`warn`/`error` lines on the build attempt. */\nexport interface AttemptMirror {\n /** Logger to hand to a runner: same as the original, plus attempt-log recording. */\n logger: Logger;\n /** Record one line on the attempt (and in pino); serialized with the mirrored lines. */\n log(level: CaptureLogLevel, message: string, fields?: Record<string, unknown>): void;\n /** Wait for every recorded line to be written. */\n flush(): Promise<void>;\n}\n\n/**\n * Mirror a logger's `info`/`warn`/`error` lines onto the attempt log.\n *\n * Writes go through one promise chain: `capture_logs` numbers lines with a\n * read-then-insert, so concurrent appends (parallel pages failing at once)\n * would collide on `seq`. With no recorder the logger is returned unchanged.\n */\nexport function mirrorAttemptLog(\n logger: Logger | undefined,\n record: AttemptLogRecorder | undefined,\n /** Optionally re-level a recorded line (pino keeps the original level). */\n levelFor: (level: CaptureLogLevel, fields?: Record<string, unknown>) => CaptureLogLevel = (\n level,\n ) => level,\n): AttemptMirror {\n let chain: Promise<void> = Promise.resolve();\n const enqueue = (level: CaptureLogLevel, message: string, fields?: unknown): void => {\n if (!record) return;\n const recorded = levelFor(level, fields as Record<string, unknown> | undefined);\n chain = chain.then(async () => {\n await emitAttemptLog(record, logger, recorded, message, fields);\n });\n };\n const base = logger ?? silentLogger();\n const mirrored = new Proxy(base, {\n get(target, property, receiver): unknown {\n const level = MIRRORED_LEVELS.find((name) => name === property);\n if (!level) return Reflect.get(target, property, receiver);\n return (first: unknown, second?: unknown, ...rest: unknown[]): void => {\n const args = [first, second, ...rest];\n while (args.length > 1 && args.at(-1) === undefined) args.pop();\n Reflect.apply(target[level], target, args);\n const [fields, message] = parseArgs(first, second);\n enqueue(level, message, fields);\n };\n },\n });\n return {\n logger: mirrored,\n log: (level, message, fields) => {\n base[level](fields ?? {}, message);\n enqueue(level, message, fields === undefined ? undefined : plainFields(fields));\n },\n flush: async () => {\n await chain;\n },\n };\n}\n\n/** pino call shapes: `(fields, message)` or `(message)`. */\nfunction parseArgs(first: unknown, second: unknown): [Record<string, unknown> | undefined, string] {\n if (typeof first === \"string\") return [undefined, first];\n const fields = first && typeof first === \"object\" ? plainFields(first) : undefined;\n return [fields, typeof second === \"string\" ? second : \"\"];\n}\n\n/** JSON-safe copy: an `Error` under `err` becomes its capped message. */\nfunction plainFields(fields: object): Record<string, unknown> {\n const copy: Record<string, unknown> = { ...fields };\n const err = copy[\"err\"];\n if (err instanceof Error) copy[\"err\"] = redactSecrets(err.message).slice(0, ERROR_TEXT_MAX);\n return copy;\n}\n\nfunction noop(): void {}\n\n/** Stand-in when no logger was configured, so callers can log unconditionally. */\nfunction silentLogger(): Logger {\n const stub: Record<string, unknown> = { info: noop, warn: noop, error: noop, debug: noop };\n stub[\"child\"] = (): unknown => stub;\n return stub as unknown as Logger;\n}\n"],"mappings":";;;;;;;AAMA,MAAM,iBAAiB;;AAGvB,MAAM,kBAAkB;CAAC;CAAQ;CAAQ;AAAO;;;;;;;;AAmBhD,SAAgB,iBACd,QACA,QAEA,YACE,UACG,OACU;CACf,IAAI,QAAuB,QAAQ,QAAQ;CAC3C,MAAM,WAAW,OAAwB,SAAiB,WAA2B;EACnF,IAAI,CAAC,QAAQ;EACb,MAAM,WAAW,SAAS,OAAO,MAA6C;EAC9E,QAAQ,MAAM,KAAK,YAAY;GAC7B,MAAM,eAAe,QAAQ,QAAQ,UAAU,SAAS,MAAM;EAChE,CAAC;CACH;CACA,MAAM,OAAO,UAAU,aAAa;CAcpC,OAAO;EACL,QAAQ,IAdW,MAAM,MAAM,EAC/B,IAAI,QAAQ,UAAU,UAAmB;GACvC,MAAM,QAAQ,gBAAgB,MAAM,SAAS,SAAS,QAAQ;GAC9D,IAAI,CAAC,OAAO,OAAO,QAAQ,IAAI,QAAQ,UAAU,QAAQ;GACzD,QAAQ,OAAgB,QAAkB,GAAG,SAA0B;IACrE,MAAM,OAAO;KAAC;KAAO;KAAQ,GAAG;IAAI;IACpC,OAAO,KAAK,SAAS,KAAK,KAAK,GAAG,EAAE,MAAM,KAAA,GAAW,KAAK,IAAI;IAC9D,QAAQ,MAAM,OAAO,QAAQ,QAAQ,IAAI;IACzC,MAAM,CAAC,QAAQ,WAAW,UAAU,OAAO,MAAM;IACjD,QAAQ,OAAO,SAAS,MAAM;GAChC;EACF,EACF,CAEiB;EACf,MAAM,OAAO,SAAS,WAAW;GAC/B,KAAK,MAAM,CAAC,UAAU,CAAC,GAAG,OAAO;GACjC,QAAQ,OAAO,SAAS,WAAW,KAAA,IAAY,KAAA,IAAY,YAAY,MAAM,CAAC;EAChF;EACA,OAAO,YAAY;GACjB,MAAM;EACR;CACF;AACF;;AAGA,SAAS,UAAU,OAAgB,QAAgE;CACjG,IAAI,OAAO,UAAU,UAAU,OAAO,CAAC,KAAA,GAAW,KAAK;CAEvD,OAAO,CADQ,SAAS,OAAO,UAAU,WAAW,YAAY,KAAK,IAAI,KAAA,GACzD,OAAO,WAAW,WAAW,SAAS,EAAE;AAC1D;;AAGA,SAAS,YAAY,QAAyC;CAC5D,MAAM,OAAgC,EAAE,GAAG,OAAO;CAClD,MAAM,MAAM,KAAK;CACjB,IAAI,eAAe,OAAO,KAAK,SAAS,cAAc,IAAI,OAAO,CAAC,CAAC,MAAM,GAAG,cAAc;CAC1F,OAAO;AACT;AAEA,SAAS,OAAa,CAAC;;AAGvB,SAAS,eAAuB;CAC9B,MAAM,OAAgC;EAAE,MAAM;EAAM,MAAM;EAAM,OAAO;EAAM,OAAO;CAAK;CACzF,KAAK,iBAA0B;CAC/B,OAAO;AACT"}
@@ -0,0 +1,35 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ import { CaptureTarget, RunnerTarget } from "../adapters/capture-target.mjs";
4
+ //#region src/capture/browsers.d.ts
5
+ /** Upper bound on targets per project (each multiplies render time). */
6
+ declare const MAX_BROWSERS = 3;
7
+ /** Parse the project's JSON `browsers` column; `null` when unset or malformed. */
8
+ declare function parseBrowsers(raw: string | null | undefined): CaptureTarget[] | null;
9
+ /**
10
+ * Targets to capture, primary first: the project's `browsers` list, else its single `browser`.
11
+ * Throws when the list exceeds {@link MAX_BROWSERS}.
12
+ */
13
+ declare function resolveBrowsers(project: {
14
+ browser?: string | undefined;
15
+ browsers?: string | null | undefined;
16
+ }): CaptureTarget[];
17
+ /**
18
+ * The primary target of a resolved target list: its first entry. The single owner of "primary":
19
+ * axe runs only here (ADR 0004, decision 6), so one story is not checked once per target.
20
+ */
21
+ declare function primaryTargetOf(targets: readonly CaptureTarget[]): CaptureTarget | undefined;
22
+ /** Throw when more than {@link MAX_BROWSERS} targets are requested. */
23
+ declare function assertBrowserCount(browsers: readonly string[]): void;
24
+ /**
25
+ * Why `target` cannot render on a runner offering `offered`: it is not on the list, or it is and is
26
+ * unavailable. `null` when it can, and always when the runner publishes no list.
27
+ */
28
+ declare function refusalFor(target: CaptureTarget, offered?: readonly RunnerTarget[]): string | null;
29
+ /** The targets of `offered`, for messages; `none` for an empty list. */
30
+ declare function listTargets(offered: readonly RunnerTarget[]): string;
31
+ /** Throw, naming every refusal, when the runner cannot render one of `targets`. */
32
+ declare function assertSupportedBrowsers(targets: readonly CaptureTarget[], offered?: readonly RunnerTarget[]): void;
33
+ //#endregion
34
+ export { MAX_BROWSERS, assertBrowserCount, assertSupportedBrowsers, listTargets, parseBrowsers, primaryTargetOf, refusalFor, resolveBrowsers };
35
+ //# sourceMappingURL=browsers.d.mts.map
@@ -0,0 +1,67 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { DEFAULT_BROWSER, isCaptureTarget, unavailableReason } from "../adapters/capture-target.mjs";
5
+ //#region src/capture/browsers.ts
6
+ /** Which capture targets a project renders on, and the guards around that list (ADR 0007). */
7
+ /** Upper bound on targets per project (each multiplies render time). */
8
+ const MAX_BROWSERS = 3;
9
+ /** Parse the project's JSON `browsers` column; `null` when unset or malformed. */
10
+ function parseBrowsers(raw) {
11
+ if (!raw) return null;
12
+ try {
13
+ const parsed = JSON.parse(raw);
14
+ return Array.isArray(parsed) && parsed.every((entry) => isCaptureTarget(entry)) ? dedupe(parsed) : null;
15
+ } catch {
16
+ return null;
17
+ }
18
+ }
19
+ /**
20
+ * Targets to capture, primary first: the project's `browsers` list, else its single `browser`.
21
+ * Throws when the list exceeds {@link MAX_BROWSERS}.
22
+ */
23
+ function resolveBrowsers(project) {
24
+ const listed = parseBrowsers(project.browsers);
25
+ const single = isCaptureTarget(project.browser) ? project.browser : DEFAULT_BROWSER;
26
+ const browsers = listed && listed.length > 0 ? listed : [single];
27
+ assertBrowserCount(browsers);
28
+ return browsers;
29
+ }
30
+ /**
31
+ * The primary target of a resolved target list: its first entry. The single owner of "primary":
32
+ * axe runs only here (ADR 0004, decision 6), so one story is not checked once per target.
33
+ */
34
+ function primaryTargetOf(targets) {
35
+ return targets[0];
36
+ }
37
+ /** Throw when more than {@link MAX_BROWSERS} targets are requested. */
38
+ function assertBrowserCount(browsers) {
39
+ if (browsers.length > 3) throw new Error(`At most 3 browsers are supported (got ${browsers.length})`);
40
+ }
41
+ /**
42
+ * Why `target` cannot render on a runner offering `offered`: it is not on the list, or it is and is
43
+ * unavailable. `null` when it can, and always when the runner publishes no list.
44
+ */
45
+ function refusalFor(target, offered) {
46
+ if (offered === void 0) return null;
47
+ const entry = offered.find((candidate) => candidate.target === target);
48
+ if (entry === void 0) return `${target} is not offered by this capture runner (offered: ${listTargets(offered)})`;
49
+ const reason = unavailableReason(entry.status);
50
+ return reason === null ? null : `${target} is unavailable: ${reason}`;
51
+ }
52
+ /** The targets of `offered`, for messages; `none` for an empty list. */
53
+ function listTargets(offered) {
54
+ return offered.length > 0 ? offered.map((entry) => entry.target).join(", ") : "none";
55
+ }
56
+ /** Throw, naming every refusal, when the runner cannot render one of `targets`. */
57
+ function assertSupportedBrowsers(targets, offered) {
58
+ const refusals = targets.flatMap((target) => refusalFor(target, offered) ?? []);
59
+ if (refusals.length > 0) throw new Error(`Capture runner cannot render: ${refusals.join("; ")}`);
60
+ }
61
+ function dedupe(browsers) {
62
+ return [...new Set(browsers)];
63
+ }
64
+ //#endregion
65
+ export { MAX_BROWSERS, assertBrowserCount, assertSupportedBrowsers, listTargets, parseBrowsers, primaryTargetOf, refusalFor, resolveBrowsers };
66
+
67
+ //# sourceMappingURL=browsers.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browsers.mjs","names":[],"sources":["../../src/capture/browsers.ts"],"sourcesContent":["/** Which capture targets a project renders on, and the guards around that list (ADR 0007). */\nimport {\n DEFAULT_BROWSER,\n isCaptureTarget,\n unavailableReason,\n type CaptureTarget,\n type RunnerTarget,\n} from \"../adapters/capture-target.ts\";\n\n/** Upper bound on targets per project (each multiplies render time). */\nexport const MAX_BROWSERS = 3;\n\n/** Parse the project's JSON `browsers` column; `null` when unset or malformed. */\nexport function parseBrowsers(raw: string | null | undefined): CaptureTarget[] | null {\n if (!raw) return null;\n try {\n const parsed: unknown = JSON.parse(raw);\n return Array.isArray(parsed) && parsed.every((entry) => isCaptureTarget(entry))\n ? dedupe(parsed)\n : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Targets to capture, primary first: the project's `browsers` list, else its single `browser`.\n * Throws when the list exceeds {@link MAX_BROWSERS}.\n */\nexport function resolveBrowsers(project: {\n browser?: string | undefined;\n browsers?: string | null | undefined;\n}): CaptureTarget[] {\n const listed = parseBrowsers(project.browsers);\n const single = isCaptureTarget(project.browser) ? project.browser : DEFAULT_BROWSER;\n const browsers = listed && listed.length > 0 ? listed : [single];\n assertBrowserCount(browsers);\n return browsers;\n}\n\n/**\n * The primary target of a resolved target list: its first entry. The single owner of \"primary\":\n * axe runs only here (ADR 0004, decision 6), so one story is not checked once per target.\n */\nexport function primaryTargetOf(targets: readonly CaptureTarget[]): CaptureTarget | undefined {\n return targets[0];\n}\n\n/** Throw when more than {@link MAX_BROWSERS} targets are requested. */\nexport function assertBrowserCount(browsers: readonly string[]): void {\n if (browsers.length > MAX_BROWSERS) {\n throw new Error(`At most ${MAX_BROWSERS} browsers are supported (got ${browsers.length})`);\n }\n}\n\n/**\n * Why `target` cannot render on a runner offering `offered`: it is not on the list, or it is and is\n * unavailable. `null` when it can, and always when the runner publishes no list.\n */\nexport function refusalFor(\n target: CaptureTarget,\n offered?: readonly RunnerTarget[],\n): string | null {\n if (offered === undefined) {\n return null;\n }\n const entry = offered.find((candidate) => candidate.target === target);\n if (entry === undefined) {\n return `${target} is not offered by this capture runner (offered: ${listTargets(offered)})`;\n }\n const reason = unavailableReason(entry.status);\n return reason === null ? null : `${target} is unavailable: ${reason}`;\n}\n\n/** The targets of `offered`, for messages; `none` for an empty list. */\nexport function listTargets(offered: readonly RunnerTarget[]): string {\n return offered.length > 0 ? offered.map((entry) => entry.target).join(\", \") : \"none\";\n}\n\n/** Throw, naming every refusal, when the runner cannot render one of `targets`. */\nexport function assertSupportedBrowsers(\n targets: readonly CaptureTarget[],\n offered?: readonly RunnerTarget[],\n): void {\n const refusals = targets.flatMap((target) => refusalFor(target, offered) ?? []);\n if (refusals.length > 0) {\n throw new Error(`Capture runner cannot render: ${refusals.join(\"; \")}`);\n }\n}\n\nfunction dedupe(browsers: CaptureTarget[]): CaptureTarget[] {\n return [...new Set(browsers)];\n}\n"],"mappings":";;;;;;;AAUA,MAAa,eAAe;;AAG5B,SAAgB,cAAc,KAAwD;CACpF,IAAI,CAAC,KAAK,OAAO;CACjB,IAAI;EACF,MAAM,SAAkB,KAAK,MAAM,GAAG;EACtC,OAAO,MAAM,QAAQ,MAAM,KAAK,OAAO,OAAO,UAAU,gBAAgB,KAAK,CAAC,IAC1E,OAAO,MAAM,IACb;CACN,QAAQ;EACN,OAAO;CACT;AACF;;;;;AAMA,SAAgB,gBAAgB,SAGZ;CAClB,MAAM,SAAS,cAAc,QAAQ,QAAQ;CAC7C,MAAM,SAAS,gBAAgB,QAAQ,OAAO,IAAI,QAAQ,UAAU;CACpE,MAAM,WAAW,UAAU,OAAO,SAAS,IAAI,SAAS,CAAC,MAAM;CAC/D,mBAAmB,QAAQ;CAC3B,OAAO;AACT;;;;;AAMA,SAAgB,gBAAgB,SAA8D;CAC5F,OAAO,QAAQ;AACjB;;AAGA,SAAgB,mBAAmB,UAAmC;CACpE,IAAI,SAAS,SAAA,GACX,MAAM,IAAI,MAAM,yCAAuD,SAAS,OAAO,EAAE;AAE7F;;;;;AAMA,SAAgB,WACd,QACA,SACe;CACf,IAAI,YAAY,KAAA,GACd,OAAO;CAET,MAAM,QAAQ,QAAQ,MAAM,cAAc,UAAU,WAAW,MAAM;CACrE,IAAI,UAAU,KAAA,GACZ,OAAO,GAAG,OAAO,mDAAmD,YAAY,OAAO,EAAE;CAE3F,MAAM,SAAS,kBAAkB,MAAM,MAAM;CAC7C,OAAO,WAAW,OAAO,OAAO,GAAG,OAAO,mBAAmB;AAC/D;;AAGA,SAAgB,YAAY,SAA0C;CACpE,OAAO,QAAQ,SAAS,IAAI,QAAQ,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC,KAAK,IAAI,IAAI;AAChF;;AAGA,SAAgB,wBACd,SACA,SACM;CACN,MAAM,WAAW,QAAQ,SAAS,WAAW,WAAW,QAAQ,OAAO,KAAK,CAAC,CAAC;CAC9E,IAAI,SAAS,SAAS,GACpB,MAAM,IAAI,MAAM,iCAAiC,SAAS,KAAK,IAAI,GAAG;AAE1E;AAEA,SAAS,OAAO,UAA4C;CAC1D,OAAO,CAAC,GAAG,IAAI,IAAI,QAAQ,CAAC;AAC9B"}
@@ -0,0 +1,32 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ import { Build } from "../../schema/build.mjs";
4
+ import { Project } from "../../schema/project.mjs";
5
+ import { BuildStateDeps, SkipReason, StateTarget } from "./types.mjs";
6
+ //#region src/capture/build-state/deliver.d.ts
7
+ /**
8
+ * Show the build's current state on one channel. Throws when the host refuses a status, so the
9
+ * caller can retry; resolves to why nothing was written when the build or channel is gone.
10
+ */
11
+ declare function deliverBuildState(deps: BuildStateDeps, ids: {
12
+ buildId: string;
13
+ channelId: string;
14
+ }): Promise<"delivered" | SkipReason>;
15
+ /** The build and its project, or null when either is gone. */
16
+ declare function loadBuild(deps: BuildStateDeps, buildId: string): Promise<{
17
+ build: Build;
18
+ project: Project;
19
+ } | null>;
20
+ /** Write `target`'s view of the build, linking to `origin`; throws on a refused status. */
21
+ declare function writeTo(deps: BuildStateDeps, loaded: {
22
+ build: Build;
23
+ project: Project;
24
+ }, where: {
25
+ target: StateTarget;
26
+ origin: string;
27
+ }): Promise<void>;
28
+ /** The project's enabled channels with a wired `state` provider; unreadable secrets are logged and skipped. */
29
+ declare function stateTargets(deps: BuildStateDeps, projectId: string): Promise<StateTarget[]>;
30
+ //#endregion
31
+ export { deliverBuildState, loadBuild, stateTargets, writeTo };
32
+ //# sourceMappingURL=deliver.d.mts.map
@@ -0,0 +1,118 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { isStateProvider } from "../../adapters/notifier/provider.mjs";
5
+ import { statusesForBuild } from "../../adapters/notifier/build-state.mjs";
6
+ import { channelPrefix, writeBuildState } from "../../adapters/notifier/state.mjs";
7
+ import { BuildModel } from "../../models/build.mjs";
8
+ import { SnapshotModel } from "../../models/snapshot.mjs";
9
+ import { ProjectModel } from "../../models/project.mjs";
10
+ import { latestVerdicts } from "../../models/build-review-status.mjs";
11
+ import { BuildReviewModel } from "../../models/build-review.mjs";
12
+ import { NotificationChannelModel } from "../../models/notification-channel.mjs";
13
+ import { createUrlBuilder } from "../../urls.mjs";
14
+ import { publishingOf } from "../publish/published.mjs";
15
+ //#region src/capture/build-state/deliver.ts
16
+ /**
17
+ * Delivering a build's state to git channels: read the build now, compute what each channel shows
18
+ * ({@link statusesForBuild}), write it. Reading at delivery time is what makes a retried, delayed
19
+ * or reordered request harmless (ADR 0014, decision 7).
20
+ */
21
+ /**
22
+ * Show the build's current state on one channel. Throws when the host refuses a status, so the
23
+ * caller can retry; resolves to why nothing was written when the build or channel is gone.
24
+ */
25
+ async function deliverBuildState(deps, ids) {
26
+ const loaded = await loadBuild(deps, ids.buildId);
27
+ const origin = loaded === null ? void 0 : await deps.publicOrigin();
28
+ if (loaded === null || !origin) return loaded === null ? "no-build" : "no-origin";
29
+ const target = await loadTarget(deps, ids.channelId, loaded.project.id);
30
+ if (typeof target === "string") return target;
31
+ await writeTo(deps, loaded, {
32
+ target,
33
+ origin
34
+ });
35
+ return "delivered";
36
+ }
37
+ /** The build and its project, or null when either is gone. */
38
+ async function loadBuild(deps, buildId) {
39
+ const build = await new BuildModel(deps.db).get(buildId);
40
+ const project = build ? await new ProjectModel(deps.db).get(build.projectId) : null;
41
+ return build && project ? {
42
+ build,
43
+ project
44
+ } : null;
45
+ }
46
+ /** Write `target`'s view of the build, linking to `origin`; throws on a refused status. */
47
+ async function writeTo(deps, loaded, where) {
48
+ const { build, project } = loaded;
49
+ const { target, origin } = where;
50
+ const states = statusesForBuild({
51
+ build,
52
+ projectSlug: project.slug,
53
+ prefix: channelPrefix(target.channel),
54
+ urls: createUrlBuilder(origin, deps.config.publishedBaseDomain),
55
+ flags: deps.flags(project),
56
+ publishing: await publishingOf(deps.storage, build),
57
+ approvals: await approvalsOf(deps, build, project),
58
+ changes: await changesOf(deps, build, project)
59
+ });
60
+ if (states.statuses.length === 0) {
61
+ deps.logger.info({ buildId: build.id }, "no commit to show a state on");
62
+ return;
63
+ }
64
+ await writeBuildState(target.provider, target.channel, {
65
+ sha: build.gitSha,
66
+ states,
67
+ logger: deps.logger
68
+ });
69
+ }
70
+ /** The project's enabled channels with a wired `state` provider; unreadable secrets are logged and skipped. */
71
+ async function stateTargets(deps, projectId) {
72
+ const model = channelModel(deps);
73
+ return (await model.list(projectId)).flatMap((row) => {
74
+ const view = row.enabled ? model.toView(row) : null;
75
+ if (row.enabled && view === null) deps.logger.warn({
76
+ channelId: row.id,
77
+ provider: row.provider
78
+ }, "failed to decrypt channel secret");
79
+ return view === null ? [] : targetOf(deps, view);
80
+ });
81
+ }
82
+ /** One channel by id, or why it cannot take part. */
83
+ async function loadTarget(deps, channelId, projectId) {
84
+ const model = channelModel(deps);
85
+ const row = await model.get(channelId);
86
+ if (row === null || row.projectId !== projectId) return "no-channel";
87
+ if (!row.enabled) return "disabled";
88
+ const view = model.toView(row);
89
+ if (view === null) throw new Error("channel secret cannot be decrypted with the configured keys");
90
+ return targetOf(deps, view)[0] ?? "no-provider";
91
+ }
92
+ function targetOf(deps, channel) {
93
+ const provider = deps.notifiers.find((candidate) => candidate.metadata.kind === channel.provider);
94
+ return provider !== void 0 && isStateProvider(provider) ? [{
95
+ channel,
96
+ provider
97
+ }] : [];
98
+ }
99
+ function channelModel(deps) {
100
+ return new NotificationChannelModel(deps.db, { notificationChannels: deps.db.tables.notificationChannels }, deps.secret);
101
+ }
102
+ /** Approval progress for `<prefix>/review`; only read when that status is the gate. */
103
+ async function approvalsOf(deps, build, project) {
104
+ const flags = deps.flags(project);
105
+ if (flags.testing || !flags.review) return;
106
+ return {
107
+ given: latestVerdicts(await new BuildReviewModel(deps.db).listByBuild(build.id)).filter((review) => review.verdict === "approved").length,
108
+ required: Math.max(1, project.reviewApprovals ?? 1)
109
+ };
110
+ }
111
+ /** The build's snapshots for the annotations; read only when `testing` is on and something changed. */
112
+ async function changesOf(deps, build, project) {
113
+ return deps.flags(project).testing && build.changedCount > 0 ? await new SnapshotModel(deps.db).listByBuild(build.id) : void 0;
114
+ }
115
+ //#endregion
116
+ export { deliverBuildState, loadBuild, stateTargets, writeTo };
117
+
118
+ //# sourceMappingURL=deliver.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deliver.mjs","names":[],"sources":["../../../src/capture/build-state/deliver.ts"],"sourcesContent":["/**\n * Delivering a build's state to git channels: read the build now, compute what each channel shows\n * ({@link statusesForBuild}), write it. Reading at delivery time is what makes a retried, delayed\n * or reordered request harmless (ADR 0014, decision 7).\n */\nimport { channelPrefix, statusesForBuild, writeBuildState } from \"../../adapters/notifier/index.ts\";\nimport { isStateProvider } from \"../../adapters/notifier/provider.ts\";\nimport { latestVerdicts } from \"../../models/build-review-status.ts\";\nimport { BuildReviewModel } from \"../../models/build-review.ts\";\nimport { BuildModel } from \"../../models/build.ts\";\nimport { NotificationChannelModel } from \"../../models/notification-channel.ts\";\nimport { ProjectModel } from \"../../models/project.ts\";\nimport { SnapshotModel } from \"../../models/snapshot.ts\";\nimport type { Build } from \"../../schema/build.ts\";\nimport type { Project } from \"../../schema/project.ts\";\nimport type { Snapshot } from \"../../schema/snapshot.ts\";\nimport { createUrlBuilder } from \"../../urls.ts\";\nimport { publishingOf } from \"../publish/published.ts\";\nimport type { BuildStateDeps, SkipReason, StateTarget } from \"./types.ts\";\n\n/**\n * Show the build's current state on one channel. Throws when the host refuses a status, so the\n * caller can retry; resolves to why nothing was written when the build or channel is gone.\n */\nexport async function deliverBuildState(\n deps: BuildStateDeps,\n ids: { buildId: string; channelId: string },\n): Promise<\"delivered\" | SkipReason> {\n const loaded = await loadBuild(deps, ids.buildId);\n // The origin can disappear between queueing and running (a cleared setting): nothing is shown.\n const origin = loaded === null ? undefined : await deps.publicOrigin();\n if (loaded === null || !origin) {\n return loaded === null ? \"no-build\" : \"no-origin\";\n }\n const target = await loadTarget(deps, ids.channelId, loaded.project.id);\n if (typeof target === \"string\") {\n return target;\n }\n await writeTo(deps, loaded, { target, origin });\n return \"delivered\";\n}\n\n/** The build and its project, or null when either is gone. */\nexport async function loadBuild(\n deps: BuildStateDeps,\n buildId: string,\n): Promise<{ build: Build; project: Project } | null> {\n const build = await new BuildModel(deps.db).get(buildId);\n const project = build ? await new ProjectModel(deps.db).get(build.projectId) : null;\n return build && project ? { build, project } : null;\n}\n\n/** Write `target`'s view of the build, linking to `origin`; throws on a refused status. */\nexport async function writeTo(\n deps: BuildStateDeps,\n loaded: { build: Build; project: Project },\n where: { target: StateTarget; origin: string },\n): Promise<void> {\n const { build, project } = loaded;\n const { target, origin } = where;\n const states = statusesForBuild({\n build,\n projectSlug: project.slug,\n prefix: channelPrefix(target.channel),\n urls: createUrlBuilder(origin, deps.config.publishedBaseDomain),\n flags: deps.flags(project),\n publishing: await publishingOf(deps.storage, build),\n approvals: await approvalsOf(deps, build, project),\n changes: await changesOf(deps, build, project),\n });\n if (states.statuses.length === 0) {\n deps.logger.info({ buildId: build.id }, \"no commit to show a state on\");\n return;\n }\n await writeBuildState(target.provider, target.channel, {\n sha: build.gitSha,\n states,\n logger: deps.logger,\n });\n}\n\n/** The project's enabled channels with a wired `state` provider; unreadable secrets are logged and skipped. */\nexport async function stateTargets(\n deps: BuildStateDeps,\n projectId: string,\n): Promise<StateTarget[]> {\n const model = channelModel(deps);\n const rows = await model.list(projectId);\n return rows.flatMap((row) => {\n const view = row.enabled ? model.toView(row) : null;\n if (row.enabled && view === null) {\n deps.logger.warn(\n { channelId: row.id, provider: row.provider },\n \"failed to decrypt channel secret\",\n );\n }\n return view === null ? [] : targetOf(deps, view);\n });\n}\n\n/** One channel by id, or why it cannot take part. */\nasync function loadTarget(\n deps: BuildStateDeps,\n channelId: string,\n projectId: string,\n): Promise<StateTarget | Extract<SkipReason, \"no-channel\" | \"disabled\" | \"no-provider\">> {\n const model = channelModel(deps);\n const row = await model.get(channelId);\n if (row === null || row.projectId !== projectId) {\n return \"no-channel\";\n }\n if (!row.enabled) {\n return \"disabled\";\n }\n const view = model.toView(row);\n if (view === null) {\n throw new Error(\"channel secret cannot be decrypted with the configured keys\");\n }\n return targetOf(deps, view)[0] ?? \"no-provider\";\n}\n\nfunction targetOf(deps: BuildStateDeps, channel: StateTarget[\"channel\"]): StateTarget[] {\n const provider = deps.notifiers.find((candidate) => candidate.metadata.kind === channel.provider);\n return provider !== undefined && isStateProvider(provider) ? [{ channel, provider }] : [];\n}\n\nfunction channelModel(deps: BuildStateDeps): NotificationChannelModel {\n return new NotificationChannelModel(\n deps.db,\n { notificationChannels: deps.db.tables.notificationChannels },\n deps.secret,\n );\n}\n\n/** Approval progress for `<prefix>/review`; only read when that status is the gate. */\nasync function approvalsOf(\n deps: BuildStateDeps,\n build: Build,\n project: Project,\n): Promise<{ given: number; required: number } | undefined> {\n const flags = deps.flags(project);\n if (flags.testing || !flags.review) {\n return undefined;\n }\n const reviews = latestVerdicts(await new BuildReviewModel(deps.db).listByBuild(build.id));\n return {\n given: reviews.filter((review) => review.verdict === \"approved\").length,\n required: Math.max(1, project.reviewApprovals ?? 1),\n };\n}\n\n/** The build's snapshots for the annotations; read only when `testing` is on and something changed. */\nasync function changesOf(\n deps: BuildStateDeps,\n build: Build,\n project: Project,\n): Promise<Snapshot[] | undefined> {\n const wanted = deps.flags(project).testing && build.changedCount > 0;\n return wanted ? await new SnapshotModel(deps.db).listByBuild(build.id) : undefined;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAwBA,eAAsB,kBACpB,MACA,KACmC;CACnC,MAAM,SAAS,MAAM,UAAU,MAAM,IAAI,OAAO;CAEhD,MAAM,SAAS,WAAW,OAAO,KAAA,IAAY,MAAM,KAAK,aAAa;CACrE,IAAI,WAAW,QAAQ,CAAC,QACtB,OAAO,WAAW,OAAO,aAAa;CAExC,MAAM,SAAS,MAAM,WAAW,MAAM,IAAI,WAAW,OAAO,QAAQ,EAAE;CACtE,IAAI,OAAO,WAAW,UACpB,OAAO;CAET,MAAM,QAAQ,MAAM,QAAQ;EAAE;EAAQ;CAAO,CAAC;CAC9C,OAAO;AACT;;AAGA,eAAsB,UACpB,MACA,SACoD;CACpD,MAAM,QAAQ,MAAM,IAAI,WAAW,KAAK,EAAE,CAAC,CAAC,IAAI,OAAO;CACvD,MAAM,UAAU,QAAQ,MAAM,IAAI,aAAa,KAAK,EAAE,CAAC,CAAC,IAAI,MAAM,SAAS,IAAI;CAC/E,OAAO,SAAS,UAAU;EAAE;EAAO;CAAQ,IAAI;AACjD;;AAGA,eAAsB,QACpB,MACA,QACA,OACe;CACf,MAAM,EAAE,OAAO,YAAY;CAC3B,MAAM,EAAE,QAAQ,WAAW;CAC3B,MAAM,SAAS,iBAAiB;EAC9B;EACA,aAAa,QAAQ;EACrB,QAAQ,cAAc,OAAO,OAAO;EACpC,MAAM,iBAAiB,QAAQ,KAAK,OAAO,mBAAmB;EAC9D,OAAO,KAAK,MAAM,OAAO;EACzB,YAAY,MAAM,aAAa,KAAK,SAAS,KAAK;EAClD,WAAW,MAAM,YAAY,MAAM,OAAO,OAAO;EACjD,SAAS,MAAM,UAAU,MAAM,OAAO,OAAO;CAC/C,CAAC;CACD,IAAI,OAAO,SAAS,WAAW,GAAG;EAChC,KAAK,OAAO,KAAK,EAAE,SAAS,MAAM,GAAG,GAAG,8BAA8B;EACtE;CACF;CACA,MAAM,gBAAgB,OAAO,UAAU,OAAO,SAAS;EACrD,KAAK,MAAM;EACX;EACA,QAAQ,KAAK;CACf,CAAC;AACH;;AAGA,eAAsB,aACpB,MACA,WACwB;CACxB,MAAM,QAAQ,aAAa,IAAI;CAE/B,QAAO,MADY,MAAM,KAAK,SAAS,EAAA,CAC3B,SAAS,QAAQ;EAC3B,MAAM,OAAO,IAAI,UAAU,MAAM,OAAO,GAAG,IAAI;EAC/C,IAAI,IAAI,WAAW,SAAS,MAC1B,KAAK,OAAO,KACV;GAAE,WAAW,IAAI;GAAI,UAAU,IAAI;EAAS,GAC5C,kCACF;EAEF,OAAO,SAAS,OAAO,CAAC,IAAI,SAAS,MAAM,IAAI;CACjD,CAAC;AACH;;AAGA,eAAe,WACb,MACA,WACA,WACuF;CACvF,MAAM,QAAQ,aAAa,IAAI;CAC/B,MAAM,MAAM,MAAM,MAAM,IAAI,SAAS;CACrC,IAAI,QAAQ,QAAQ,IAAI,cAAc,WACpC,OAAO;CAET,IAAI,CAAC,IAAI,SACP,OAAO;CAET,MAAM,OAAO,MAAM,OAAO,GAAG;CAC7B,IAAI,SAAS,MACX,MAAM,IAAI,MAAM,6DAA6D;CAE/E,OAAO,SAAS,MAAM,IAAI,CAAC,CAAC,MAAM;AACpC;AAEA,SAAS,SAAS,MAAsB,SAAgD;CACtF,MAAM,WAAW,KAAK,UAAU,MAAM,cAAc,UAAU,SAAS,SAAS,QAAQ,QAAQ;CAChG,OAAO,aAAa,KAAA,KAAa,gBAAgB,QAAQ,IAAI,CAAC;EAAE;EAAS;CAAS,CAAC,IAAI,CAAC;AAC1F;AAEA,SAAS,aAAa,MAAgD;CACpE,OAAO,IAAI,yBACT,KAAK,IACL,EAAE,sBAAsB,KAAK,GAAG,OAAO,qBAAqB,GAC5D,KAAK,MACP;AACF;;AAGA,eAAe,YACb,MACA,OACA,SAC0D;CAC1D,MAAM,QAAQ,KAAK,MAAM,OAAO;CAChC,IAAI,MAAM,WAAW,CAAC,MAAM,QAC1B;CAGF,OAAO;EACL,OAFc,eAAe,MAAM,IAAI,iBAAiB,KAAK,EAAE,CAAC,CAAC,YAAY,MAAM,EAAE,CAExE,CAAC,CAAC,QAAQ,WAAW,OAAO,YAAY,UAAU,CAAC,CAAC;EACjE,UAAU,KAAK,IAAI,GAAG,QAAQ,mBAAmB,CAAC;CACpD;AACF;;AAGA,eAAe,UACb,MACA,OACA,SACiC;CAEjC,OADe,KAAK,MAAM,OAAO,CAAC,CAAC,WAAW,MAAM,eAAe,IACnD,MAAM,IAAI,cAAc,KAAK,EAAE,CAAC,CAAC,YAAY,MAAM,EAAE,IAAI,KAAA;AAC3E"}
@@ -0,0 +1,8 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ import { BuildStateDeps, BuildStatePublisher, SkipReason, StateTarget } from "./types.mjs";
4
+ import { deliverBuildState, loadBuild, stateTargets, writeTo } from "./deliver.mjs";
5
+ import { NOTIFY_BACKOFF, NOTIFY_MAX_ATTEMPTS, NotifyFailure, NotifyHandlerOptions, NotifyJobQueue, createNotifyEnqueuer, createNotifyHandler, notifyExhausted } from "./notify-job.mjs";
6
+ import { BuildStatePublisherOptions, PAUSED_MESSAGE, createBuildStatePublisher } from "./publisher.mjs";
7
+ import { requestOpenBuildStates } from "./resume.mjs";
8
+ export { type BuildStateDeps, type BuildStatePublisher, type BuildStatePublisherOptions, NOTIFY_BACKOFF, NOTIFY_MAX_ATTEMPTS, type NotifyFailure, type NotifyHandlerOptions, type NotifyJobQueue, PAUSED_MESSAGE, type SkipReason, type StateTarget, createBuildStatePublisher, createNotifyEnqueuer, createNotifyHandler, deliverBuildState, loadBuild, notifyExhausted, requestOpenBuildStates, stateTargets, writeTo };