@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
@@ -1 +1 @@
1
- {"version":3,"file":"skip-checks.mjs","names":[],"sources":["../../src/capture/skip-checks.ts"],"sourcesContent":["import type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { GitHostProvider } from \"../adapters/git-host/index.ts\";\nimport type { Logger } from \"../logger.ts\";\nimport { BuildModel, type BuildTables } from \"../models/build.ts\";\nimport { StatusConfigModel, type StatusConfigTables } from \"../models/status-config.ts\";\nimport type { ProjectStatusConfig } from \"../schema/status-config.ts\";\nimport type { SecretInput } from \"../utils/encrypt.ts\";\n\n/** Skip capture when another approved build already covers the same commit. */\nexport async function hasApprovedBuildForSha(\n db: DatabaseAdapter,\n tables: BuildTables,\n projectId: string,\n sha: string,\n excludeBuildId: string,\n): Promise<boolean> {\n const builds = await new BuildModel(db, tables).list(projectId);\n return builds.some((b) => b.gitSha === sha && b.id !== excludeBuildId && b.status === \"approved\");\n}\n\n/** Inputs for checking whether a commit is already merged upstream. */\nexport interface MergeCheck {\n providers: GitHostProvider[];\n sha: string;\n branch: string;\n secret: SecretInput;\n db: DatabaseAdapter;\n tables: StatusConfigTables;\n projectId: string;\n logger?: Logger;\n}\n\ninterface ParsedRow {\n provider: GitHostProvider;\n config: unknown;\n token: string;\n}\n\nfunction findRowProvider(\n providers: GitHostProvider[],\n row: { provider: string },\n): GitHostProvider | undefined {\n return providers.find((p) => p.metadata.kind === row.provider);\n}\n\nfunction decodeRowConfig(provider: GitHostProvider, configJson: string): unknown {\n try {\n const parsed: unknown = JSON.parse(configJson);\n provider.metadata.schema.parse(parsed);\n return parsed;\n } catch {\n return null;\n }\n}\n\nfunction decodeRowToken(model: StatusConfigModel, row: ProjectStatusConfig): string | null {\n try {\n return model.decryptToken(row);\n } catch {\n return null;\n }\n}\n\nfunction parseRow(\n model: StatusConfigModel,\n row: ProjectStatusConfig,\n providers: GitHostProvider[],\n): ParsedRow | null {\n const provider = findRowProvider(providers, row);\n if (!provider) {\n return null;\n }\n const parsed = decodeRowConfig(provider, row.config);\n if (parsed === null) {\n return null;\n }\n const token = decodeRowToken(model, row);\n if (token === null) {\n return null;\n }\n return { provider, config: parsed, token };\n}\n\nasync function checkRowMerged(\n parsed: ParsedRow,\n sha: string,\n branch: string,\n logger?: Logger,\n): Promise<boolean> {\n const adapter = parsed.provider.create({ config: parsed.config, token: parsed.token, logger });\n if (!adapter.isMerged) {\n return false;\n }\n try {\n return await adapter.isMerged({ sha, branch });\n } catch {\n // Ignore provider errors — do not skip on failure\n return false;\n }\n}\n\n/** Check one status-config row for an upstream merge. */\nasync function checkRow(\n model: StatusConfigModel,\n row: ProjectStatusConfig,\n providers: GitHostProvider[],\n sha: string,\n branch: string,\n logger?: Logger,\n): Promise<boolean> {\n const parsed = parseRow(model, row, providers);\n if (!parsed) {\n return false;\n }\n return await checkRowMerged(parsed, sha, branch, logger);\n}\n\n/** True when any configured provider reports the commit already merged. */\nexport async function isAlreadyMerged(opts: MergeCheck): Promise<boolean> {\n if (opts.providers.length === 0) {\n return false;\n }\n const model = new StatusConfigModel(opts.db, opts.tables, opts.secret);\n const rows = await model.list(opts.projectId);\n for (const row of rows) {\n // eslint-disable-next-line no-await-in-loop -- short-circuit on first merged status\n if (await checkRow(model, row, opts.providers, opts.sha, opts.branch, opts.logger)) {\n return true;\n }\n }\n return false;\n}\n"],"mappings":";;;;;;;AASA,eAAsB,uBACpB,IACA,QACA,WACA,KACA,gBACkB;CAElB,QAAO,MADc,IAAI,WAAW,IAAI,MAAM,CAAC,CAAC,KAAK,SAAS,EAAA,CAChD,MAAM,MAAM,EAAE,WAAW,OAAO,EAAE,OAAO,kBAAkB,EAAE,WAAW,UAAU;AAClG;AAoBA,SAAS,gBACP,WACA,KAC6B;CAC7B,OAAO,UAAU,MAAM,MAAM,EAAE,SAAS,SAAS,IAAI,QAAQ;AAC/D;AAEA,SAAS,gBAAgB,UAA2B,YAA6B;CAC/E,IAAI;EACF,MAAM,SAAkB,KAAK,MAAM,UAAU;EAC7C,SAAS,SAAS,OAAO,MAAM,MAAM;EACrC,OAAO;CACT,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAS,eAAe,OAA0B,KAAyC;CACzF,IAAI;EACF,OAAO,MAAM,aAAa,GAAG;CAC/B,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAS,SACP,OACA,KACA,WACkB;CAClB,MAAM,WAAW,gBAAgB,WAAW,GAAG;CAC/C,IAAI,CAAC,UACH,OAAO;CAET,MAAM,SAAS,gBAAgB,UAAU,IAAI,MAAM;CACnD,IAAI,WAAW,MACb,OAAO;CAET,MAAM,QAAQ,eAAe,OAAO,GAAG;CACvC,IAAI,UAAU,MACZ,OAAO;CAET,OAAO;EAAE;EAAU,QAAQ;EAAQ;CAAM;AAC3C;AAEA,eAAe,eACb,QACA,KACA,QACA,QACkB;CAClB,MAAM,UAAU,OAAO,SAAS,OAAO;EAAE,QAAQ,OAAO;EAAQ,OAAO,OAAO;EAAO;CAAO,CAAC;CAC7F,IAAI,CAAC,QAAQ,UACX,OAAO;CAET,IAAI;EACF,OAAO,MAAM,QAAQ,SAAS;GAAE;GAAK;EAAO,CAAC;CAC/C,QAAQ;EAEN,OAAO;CACT;AACF;;AAGA,eAAe,SACb,OACA,KACA,WACA,KACA,QACA,QACkB;CAClB,MAAM,SAAS,SAAS,OAAO,KAAK,SAAS;CAC7C,IAAI,CAAC,QACH,OAAO;CAET,OAAO,MAAM,eAAe,QAAQ,KAAK,QAAQ,MAAM;AACzD;;AAGA,eAAsB,gBAAgB,MAAoC;CACxE,IAAI,KAAK,UAAU,WAAW,GAC5B,OAAO;CAET,MAAM,QAAQ,IAAI,kBAAkB,KAAK,IAAI,KAAK,QAAQ,KAAK,MAAM;CACrE,MAAM,OAAO,MAAM,MAAM,KAAK,KAAK,SAAS;CAC5C,KAAK,MAAM,OAAO,MAEhB,IAAI,MAAM,SAAS,OAAO,KAAK,KAAK,WAAW,KAAK,KAAK,KAAK,QAAQ,KAAK,MAAM,GAC/E,OAAO;CAGX,OAAO;AACT"}
1
+ {"version":3,"file":"skip-checks.mjs","names":[],"sources":["../../src/capture/skip-checks.ts"],"sourcesContent":["import type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport { BuildModel, type BuildTables } from \"../models/build.ts\";\n\n/** Skip capture when another approved build already covers the same commit. */\nexport async function hasApprovedBuildForSha(\n db: DatabaseAdapter,\n tables: BuildTables,\n projectId: string,\n sha: string,\n excludeBuildId: string,\n): Promise<boolean> {\n const approved = await new BuildModel(db, tables).list(projectId, { status: \"approved\" });\n return approved.some((build) => build.gitSha === sha && build.id !== excludeBuildId);\n}\n"],"mappings":";;;;;;AAIA,eAAsB,uBACpB,IACA,QACA,WACA,KACA,gBACkB;CAElB,QAAO,MADgB,IAAI,WAAW,IAAI,MAAM,CAAC,CAAC,KAAK,WAAW,EAAE,QAAQ,WAAW,CAAC,EAAA,CACxE,MAAM,UAAU,MAAM,WAAW,OAAO,MAAM,OAAO,cAAc;AACrF"}
@@ -2,23 +2,22 @@ import __tsdown_shims_path from 'node:path';
2
2
  import __tsdown_shims_url from 'node:url';
3
3
  import { DatabaseAdapter } from "../adapter/database.mjs";
4
4
  import { StorageAdapter } from "../adapter/storage.mjs";
5
+ import { ExtractionLimits } from "./publish/limits.mjs";
5
6
  //#region src/capture/statics.d.ts
6
7
  /**
7
- * Shared Storybook statics handling: unzip a stored build into a scratch
8
- * directory and persist the extracted files back to storage. Used by the
9
- * capture orchestrator and by the upload path for opt-in inline extraction
10
- * (`maxInlineUnzipSize`); both write identical keys, so either may run first.
11
- */
12
- /**
13
- * Extract a build's stored zip into a dedicated scratch directory.
8
+ * Extract a build's stored zip into a dedicated scratch directory. The caller removes it when
9
+ * done (`removeJobScratch`).
14
10
  *
15
11
  * @param storage - Storage holding the uploaded zip.
16
12
  * @param scratchDir - Root under which per-build scratch dirs are created.
17
13
  * @param projectId - Owning project id.
18
14
  * @param buildId - Build whose zip to extract.
15
+ * @param shardId - Shard extracting it; gives the shard a directory of its own.
16
+ * @param limits - Entry and uncompressed-byte caps (safe defaults when omitted).
19
17
  * @returns The extracted Storybook directory.
18
+ * @throws {ZipLimitError} When the archive breaks a cap or escapes the target directory.
20
19
  */
21
- declare function extractStorybookToScratch(storage: StorageAdapter, scratchDir: string, projectId: string, buildId: string): Promise<string>;
20
+ declare function extractStorybookToScratch(storage: StorageAdapter, scratchDir: string, projectId: string, buildId: string, shardId?: string, limits?: ExtractionLimits): Promise<string>;
22
21
  /**
23
22
  * Persist an extracted Storybook directory to storage for published serving.
24
23
  *
@@ -2,11 +2,13 @@ import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
4
  import { storybookDir, storybookZipPath } from "../paths.mjs";
5
- import { dirname, join, relative, resolve, sep } from "node:path";
5
+ import { mapLimit } from "../utils/map-limit.mjs";
6
+ import { jobScratchDir, shardScratchDir } from "./scratch.mjs";
7
+ import { DEFAULT_MAX_UNZIPPED_BYTES, DEFAULT_MAX_ZIP_ENTRIES } from "./publish/limits.mjs";
8
+ import { extractZipStream } from "./publish/extract.mjs";
9
+ import { join, relative, resolve } from "node:path";
10
+ import { createHash } from "node:crypto";
6
11
  import { mkdir, readFile, readdir, rm } from "node:fs/promises";
7
- import { createWriteStream } from "node:fs";
8
- import { pipeline } from "node:stream/promises";
9
- import { Parse } from "unzipper";
10
12
  //#region src/capture/statics.ts
11
13
  /**
12
14
  * Shared Storybook statics handling: unzip a stored build into a scratch
@@ -14,23 +16,29 @@ import { Parse } from "unzipper";
14
16
  * capture orchestrator and by the upload path for opt-in inline extraction
15
17
  * (`maxInlineUnzipSize`); both write identical keys, so either may run first.
16
18
  */
19
+ const DEFAULT_EXTRACTION_LIMITS = {
20
+ maxEntries: DEFAULT_MAX_ZIP_ENTRIES,
21
+ maxBytes: DEFAULT_MAX_UNZIPPED_BYTES
22
+ };
17
23
  /**
18
- * Extract a build's stored zip into a dedicated scratch directory.
24
+ * Extract a build's stored zip into a dedicated scratch directory. The caller removes it when
25
+ * done (`removeJobScratch`).
19
26
  *
20
27
  * @param storage - Storage holding the uploaded zip.
21
28
  * @param scratchDir - Root under which per-build scratch dirs are created.
22
29
  * @param projectId - Owning project id.
23
30
  * @param buildId - Build whose zip to extract.
31
+ * @param shardId - Shard extracting it; gives the shard a directory of its own.
32
+ * @param limits - Entry and uncompressed-byte caps (safe defaults when omitted).
24
33
  * @returns The extracted Storybook directory.
34
+ * @throws {ZipLimitError} When the archive breaks a cap or escapes the target directory.
25
35
  */
26
- async function extractStorybookToScratch(storage, scratchDir, projectId, buildId) {
27
- const targetDir = join(scratchDir, projectId, "builds", buildId, "storybook");
36
+ async function extractStorybookToScratch(storage, scratchDir, projectId, buildId, shardId, limits = DEFAULT_EXTRACTION_LIMITS) {
37
+ const base = shardId ? shardScratchDir(scratchDir, projectId, buildId, shardId) : jobScratchDir(scratchDir, projectId, buildId);
38
+ const targetDir = join(base, "storybook");
28
39
  const root = await prepareTargetDir(targetDir);
29
40
  const source = await storage.readStream(storybookZipPath(projectId, buildId));
30
- const writes = [];
31
- const parseError = await runParser(root, source, writes);
32
- if (parseError) throw parseError;
33
- await awaitWrites(writes);
41
+ await extractZipStream(source, root, limits);
34
42
  return targetDir;
35
43
  }
36
44
  /** Reset a scratch target directory, returning its resolved root. */
@@ -42,11 +50,6 @@ async function prepareTargetDir(targetDir) {
42
50
  await mkdir(targetDir, { recursive: true });
43
51
  return resolve(targetDir);
44
52
  }
45
- /** Await streamed zip writes, throwing the first write failure. */
46
- async function awaitWrites(writes) {
47
- const writeError = (await Promise.allSettled(writes)).find((outcome) => outcome.status === "rejected");
48
- if (writeError) throw writeError.reason;
49
- }
50
53
  /**
51
54
  * Persist an extracted Storybook directory to storage for published serving.
52
55
  *
@@ -65,22 +68,19 @@ async function persistStorybookStatics(storage, sourceDir, projectId, buildId, d
65
68
  const destinationPrefix = storybookDir(projectId, buildId);
66
69
  const files = await walkFiles(root);
67
70
  const manifest = {};
68
- await Promise.all(files.map(async (file) => {
71
+ await mapLimit(files, STATICS_CONCURRENCY, async (file) => {
69
72
  const rel = relative(root, file);
70
73
  const buffer = await readFile(file);
71
- const hash = await hashBuffer(buffer);
72
- const contentPath = `content/${hash}`;
74
+ const hash = createHash("sha256").update(buffer).digest("hex");
73
75
  manifest[rel] = hash;
74
- if (!await storage.exists(contentPath)) await storage.write(contentPath, buffer);
76
+ if (!await storage.exists(`content/${hash}`)) await storage.write(`content/${hash}`, buffer);
75
77
  await storage.write(`${destinationPrefix}/${rel}`, buffer);
76
78
  if (db) await upsertContentRef(db, hash);
77
- }));
79
+ });
78
80
  await storage.write(`${destinationPrefix}/manifest.json`, Buffer.from(JSON.stringify(manifest)));
79
81
  }
80
- async function hashBuffer(buffer) {
81
- const { createHash } = await import("node:crypto");
82
- return createHash("sha256").update(buffer).digest("hex");
83
- }
82
+ /** Files persisted at once; bounds how many file buffers are in memory together. */
83
+ const STATICS_CONCURRENCY = 8;
84
84
  async function upsertContentRef(db, hash) {
85
85
  try {
86
86
  const contentRefs = db.tables.contentRefs;
@@ -98,52 +98,6 @@ async function upsertContentRef(db, hash) {
98
98
  });
99
99
  } catch {}
100
100
  }
101
- function blockedTarget(root, entryName) {
102
- const candidate = resolve(join(root, entryName));
103
- return candidate !== root && !candidate.startsWith(root + sep);
104
- }
105
- /** Pipe one zip entry to disk, tracking the write for later aggregation. */
106
- function handleEntry(root, entry, writes, fail) {
107
- if (blockedTarget(root, entry.path)) {
108
- entry.autodrain();
109
- fail(/* @__PURE__ */ new Error(`Blocked path traversal in uploaded Storybook: ${entry.path}`));
110
- return;
111
- }
112
- if (entry.type === "Directory") {
113
- entry.autodrain();
114
- return;
115
- }
116
- writeEntry(root, entry, writes);
117
- }
118
- /** Stream one zip entry to disk, tracking the write for later aggregation. */
119
- function writeEntry(root, entry, writes) {
120
- const target = join(root, entry.path);
121
- const done = (async () => {
122
- await mkdir(dirname(target), { recursive: true });
123
- await pipeline(entry, createWriteStream(target));
124
- })();
125
- writes.push(done);
126
- done.catch(() => {});
127
- }
128
- /** Run the unzipper parser, resolving with the first parse error (or null). */
129
- function runParser(root, source, writes) {
130
- return new Promise((settle) => {
131
- const parser = Parse();
132
- const fail = (error) => {
133
- source.destroy();
134
- settle(error);
135
- };
136
- parser.on("entry", (entry) => {
137
- handleEntry(root, entry, writes, fail);
138
- });
139
- parser.on("error", fail);
140
- parser.on("close", () => {
141
- settle(null);
142
- });
143
- source.on("error", fail);
144
- source.pipe(parser);
145
- });
146
- }
147
101
  async function walkFiles(dir) {
148
102
  const entries = await readdir(dir, { withFileTypes: true });
149
103
  return (await Promise.all(entries.map(async (entry) => {
@@ -1 +1 @@
1
- {"version":3,"file":"statics.mjs","names":[],"sources":["../../src/capture/statics.ts"],"sourcesContent":["import { createWriteStream } from \"node:fs\";\nimport { mkdir, readdir, readFile, rm } from \"node:fs/promises\";\nimport { dirname, join, relative, resolve, sep } from \"node:path\";\nimport { pipeline } from \"node:stream/promises\";\nimport { Parse, type Entry } from \"unzipper\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { StorageAdapter } from \"../adapters/storage.ts\";\nimport { storybookDir, storybookZipPath } from \"../utils/paths.ts\";\n\n/**\n * Shared Storybook statics handling: unzip a stored build into a scratch\n * directory and persist the extracted files back to storage. Used by the\n * capture orchestrator and by the upload path for opt-in inline extraction\n * (`maxInlineUnzipSize`); both write identical keys, so either may run first.\n */\n\n/**\n * Extract a build's stored zip into a dedicated scratch directory.\n *\n * @param storage - Storage holding the uploaded zip.\n * @param scratchDir - Root under which per-build scratch dirs are created.\n * @param projectId - Owning project id.\n * @param buildId - Build whose zip to extract.\n * @returns The extracted Storybook directory.\n */\nexport async function extractStorybookToScratch(\n storage: StorageAdapter,\n scratchDir: string,\n projectId: string,\n buildId: string,\n): Promise<string> {\n const targetDir = join(scratchDir, projectId, \"builds\", buildId, \"storybook\");\n const root = await prepareTargetDir(targetDir);\n const source = await storage.readStream(storybookZipPath(projectId, buildId));\n const writes: Promise<void>[] = [];\n const parseError = await runParser(root, source, writes);\n if (parseError) {\n throw parseError;\n }\n await awaitWrites(writes);\n return targetDir;\n}\n\n/** Reset a scratch target directory, returning its resolved root. */\nasync function prepareTargetDir(targetDir: string): Promise<string> {\n await rm(targetDir, { recursive: true, force: true });\n await mkdir(targetDir, { recursive: true });\n return resolve(targetDir);\n}\n\n/** Await streamed zip writes, throwing the first write failure. */\nasync function awaitWrites(writes: Promise<void>[]): Promise<void> {\n const outcomes = await Promise.allSettled(writes);\n const writeError = outcomes.find(\n (outcome): outcome is PromiseRejectedResult => outcome.status === \"rejected\",\n );\n if (writeError) {\n throw writeError.reason;\n }\n}\n\n/**\n * Persist an extracted Storybook directory to storage for published serving.\n *\n * Uses content-addressed deduplication: each file is hashed and stored once\n * under `content/<hash>`, with a per-build manifest at\n * `${projectId}/builds/${buildId}/storybook/manifest.json`.\n *\n * @param storage - Destination storage adapter.\n * @param sourceDir - Extracted Storybook directory.\n * @param projectId - Owning project id.\n * @param buildId - Build the files belong to.\n * @param db - Optional database for content ref tracking (enables GC).\n */\nexport async function persistStorybookStatics(\n storage: StorageAdapter,\n sourceDir: string,\n projectId: string,\n buildId: string,\n db?: DatabaseAdapter,\n): Promise<void> {\n const root = resolve(sourceDir);\n const destinationPrefix = storybookDir(projectId, buildId);\n const files = await walkFiles(root);\n const manifest: Record<string, string> = {};\n await Promise.all(\n files.map(async (file) => {\n const rel = relative(root, file);\n const buffer = await readFile(file);\n const hash = await hashBuffer(buffer);\n const contentPath = `content/${hash}`;\n manifest[rel] = hash;\n // Only write if not already exists (dedup)\n if (!(await storage.exists(contentPath))) {\n await storage.write(contentPath, buffer);\n }\n // Also write to legacy per-build path for backward compat (can be removed after migration)\n // and maintain content_refs for GC\n await storage.write(`${destinationPrefix}/${rel}`, buffer);\n if (db) {\n await upsertContentRef(db, hash);\n }\n }),\n );\n await storage.write(`${destinationPrefix}/manifest.json`, Buffer.from(JSON.stringify(manifest)));\n}\n\nasync function hashBuffer(buffer: Buffer): Promise<string> {\n const { createHash } = await import(\"node:crypto\");\n return createHash(\"sha256\").update(buffer).digest(\"hex\");\n}\n\nasync function upsertContentRef(db: DatabaseAdapter, hash: string): Promise<void> {\n try {\n const contentRefs = db.tables.contentRefs;\n const now = new Date().toISOString();\n const existing = (await db.get(contentRefs, hash)) as unknown as { refCount: number } | null;\n if (existing) {\n await db.update(contentRefs, hash, {\n refCount: existing.refCount + 1,\n lastSeenAt: now,\n });\n } else {\n await db.insert(contentRefs, { hash, refCount: 1, lastSeenAt: now, createdAt: now });\n }\n } catch {\n // content_refs table may not exist yet (old DB) — ignore\n }\n}\n\nfunction blockedTarget(root: string, entryName: string): boolean {\n const candidate = resolve(join(root, entryName));\n return candidate !== root && !candidate.startsWith(root + sep);\n}\n\n/** Pipe one zip entry to disk, tracking the write for later aggregation. */\nfunction handleEntry(\n root: string,\n entry: Entry,\n writes: Promise<void>[],\n fail: (error: unknown) => void,\n): void {\n if (blockedTarget(root, entry.path)) {\n entry.autodrain();\n fail(new Error(`Blocked path traversal in uploaded Storybook: ${entry.path}`));\n return;\n }\n if (entry.type === \"Directory\") {\n entry.autodrain();\n return;\n }\n writeEntry(root, entry, writes);\n}\n\n/** Stream one zip entry to disk, tracking the write for later aggregation. */\nfunction writeEntry(root: string, entry: Entry, writes: Promise<void>[]): void {\n const target = join(root, entry.path);\n const done = (async (): Promise<void> => {\n await mkdir(dirname(target), { recursive: true });\n await pipeline(entry, createWriteStream(target));\n })();\n writes.push(done);\n done.catch(() => {}); // Intentionally empty — aggregated via allSettled by the caller\n}\n\n/** Run the unzipper parser, resolving with the first parse error (or null). */\n// oxlint-disable-next-line typescript/promise-function-async -- executor-style promise wrapper around events\nfunction runParser(\n root: string,\n source: import(\"node:stream\").Readable,\n writes: Promise<void>[],\n): Promise<unknown> {\n return new Promise((settle) => {\n const parser = Parse();\n const fail = (error: unknown): void => {\n source.destroy();\n settle(error);\n };\n parser.on(\"entry\", (entry: Entry) => {\n handleEntry(root, entry, writes, fail);\n });\n parser.on(\"error\", fail);\n parser.on(\"close\", () => {\n settle(null);\n });\n source.on(\"error\", fail);\n source.pipe(parser);\n });\n}\n\nasync function walkFiles(dir: string): Promise<string[]> {\n const entries = await readdir(dir, { withFileTypes: true });\n const nested = await Promise.all(\n entries.map(async (entry) => {\n const full = join(dir, entry.name);\n if (entry.isDirectory()) {\n return await walkFiles(full);\n }\n return [full];\n }),\n );\n return nested.flat();\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"statics.mjs","names":[],"sources":["../../src/capture/statics.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { mkdir, readdir, readFile, rm } from \"node:fs/promises\";\nimport { join, relative, resolve } from \"node:path\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { StorageAdapter } from \"../adapters/storage.ts\";\nimport { mapLimit } from \"../utils/map-limit.ts\";\nimport { storybookDir, storybookZipPath } from \"../utils/paths.ts\";\nimport { extractZipStream } from \"./publish/extract.ts\";\nimport {\n DEFAULT_MAX_UNZIPPED_BYTES,\n DEFAULT_MAX_ZIP_ENTRIES,\n type ExtractionLimits,\n} from \"./publish/limits.ts\";\nimport { jobScratchDir, shardScratchDir } from \"./scratch.ts\";\n\n/**\n * Shared Storybook statics handling: unzip a stored build into a scratch\n * directory and persist the extracted files back to storage. Used by the\n * capture orchestrator and by the upload path for opt-in inline extraction\n * (`maxInlineUnzipSize`); both write identical keys, so either may run first.\n */\n\nconst DEFAULT_EXTRACTION_LIMITS: ExtractionLimits = {\n maxEntries: DEFAULT_MAX_ZIP_ENTRIES,\n maxBytes: DEFAULT_MAX_UNZIPPED_BYTES,\n};\n\n/**\n * Extract a build's stored zip into a dedicated scratch directory. The caller removes it when\n * done (`removeJobScratch`).\n *\n * @param storage - Storage holding the uploaded zip.\n * @param scratchDir - Root under which per-build scratch dirs are created.\n * @param projectId - Owning project id.\n * @param buildId - Build whose zip to extract.\n * @param shardId - Shard extracting it; gives the shard a directory of its own.\n * @param limits - Entry and uncompressed-byte caps (safe defaults when omitted).\n * @returns The extracted Storybook directory.\n * @throws {ZipLimitError} When the archive breaks a cap or escapes the target directory.\n */\nexport async function extractStorybookToScratch(\n storage: StorageAdapter,\n scratchDir: string,\n projectId: string,\n buildId: string,\n shardId?: string,\n limits: ExtractionLimits = DEFAULT_EXTRACTION_LIMITS,\n): Promise<string> {\n const base = shardId\n ? shardScratchDir(scratchDir, projectId, buildId, shardId)\n : jobScratchDir(scratchDir, projectId, buildId);\n const targetDir = join(base, \"storybook\");\n const root = await prepareTargetDir(targetDir);\n const source = await storage.readStream(storybookZipPath(projectId, buildId));\n await extractZipStream(source, root, limits);\n return targetDir;\n}\n\n/** Reset a scratch target directory, returning its resolved root. */\nasync function prepareTargetDir(targetDir: string): Promise<string> {\n await rm(targetDir, { recursive: true, force: true });\n await mkdir(targetDir, { recursive: true });\n return resolve(targetDir);\n}\n\n/**\n * Persist an extracted Storybook directory to storage for published serving.\n *\n * Uses content-addressed deduplication: each file is hashed and stored once\n * under `content/<hash>`, with a per-build manifest at\n * `${projectId}/builds/${buildId}/storybook/manifest.json`.\n *\n * @param storage - Destination storage adapter.\n * @param sourceDir - Extracted Storybook directory.\n * @param projectId - Owning project id.\n * @param buildId - Build the files belong to.\n * @param db - Optional database for content ref tracking (enables GC).\n */\nexport async function persistStorybookStatics(\n storage: StorageAdapter,\n sourceDir: string,\n projectId: string,\n buildId: string,\n db?: DatabaseAdapter,\n): Promise<void> {\n const root = resolve(sourceDir);\n const destinationPrefix = storybookDir(projectId, buildId);\n const files = await walkFiles(root);\n const manifest: Record<string, string> = {};\n // Bounded: a large Storybook has thousands of files, and buffering them all\n // at once exhausts memory on small hosts. Each file is read fully before any\n // write: the scratch dir is often the storage root, so the per-build path\n // below can be the very file being read (a streamed write would truncate it).\n await mapLimit(files, STATICS_CONCURRENCY, async (file) => {\n const rel = relative(root, file);\n const buffer = await readFile(file);\n const hash = createHash(\"sha256\").update(buffer).digest(\"hex\");\n manifest[rel] = hash;\n // Only write if not already exists (dedup)\n if (!(await storage.exists(`content/${hash}`))) {\n await storage.write(`content/${hash}`, buffer);\n }\n // Also write to legacy per-build path for backward compat (can be removed after migration)\n // and maintain content_refs for GC\n await storage.write(`${destinationPrefix}/${rel}`, buffer);\n if (db) {\n await upsertContentRef(db, hash);\n }\n });\n await storage.write(`${destinationPrefix}/manifest.json`, Buffer.from(JSON.stringify(manifest)));\n}\n\n/** Files persisted at once; bounds how many file buffers are in memory together. */\nconst STATICS_CONCURRENCY = 8;\n\nasync function upsertContentRef(db: DatabaseAdapter, hash: string): Promise<void> {\n try {\n const contentRefs = db.tables.contentRefs;\n const now = new Date().toISOString();\n const existing = (await db.get(contentRefs, hash)) as unknown as { refCount: number } | null;\n if (existing) {\n await db.update(contentRefs, hash, {\n refCount: existing.refCount + 1,\n lastSeenAt: now,\n });\n } else {\n await db.insert(contentRefs, { hash, refCount: 1, lastSeenAt: now, createdAt: now });\n }\n } catch {\n // content_refs table may not exist yet (old DB) — ignore\n }\n}\n\nasync function walkFiles(dir: string): Promise<string[]> {\n const entries = await readdir(dir, { withFileTypes: true });\n const nested = await Promise.all(\n entries.map(async (entry) => {\n const full = join(dir, entry.name);\n if (entry.isDirectory()) {\n return await walkFiles(full);\n }\n return [full];\n }),\n );\n return nested.flat();\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAsBA,MAAM,4BAA8C;CAClD,YAAY;CACZ,UAAU;AACZ;;;;;;;;;;;;;;AAeA,eAAsB,0BACpB,SACA,YACA,WACA,SACA,SACA,SAA2B,2BACV;CACjB,MAAM,OAAO,UACT,gBAAgB,YAAY,WAAW,SAAS,OAAO,IACvD,cAAc,YAAY,WAAW,OAAO;CAChD,MAAM,YAAY,KAAK,MAAM,WAAW;CACxC,MAAM,OAAO,MAAM,iBAAiB,SAAS;CAC7C,MAAM,SAAS,MAAM,QAAQ,WAAW,iBAAiB,WAAW,OAAO,CAAC;CAC5E,MAAM,iBAAiB,QAAQ,MAAM,MAAM;CAC3C,OAAO;AACT;;AAGA,eAAe,iBAAiB,WAAoC;CAClE,MAAM,GAAG,WAAW;EAAE,WAAW;EAAM,OAAO;CAAK,CAAC;CACpD,MAAM,MAAM,WAAW,EAAE,WAAW,KAAK,CAAC;CAC1C,OAAO,QAAQ,SAAS;AAC1B;;;;;;;;;;;;;;AAeA,eAAsB,wBACpB,SACA,WACA,WACA,SACA,IACe;CACf,MAAM,OAAO,QAAQ,SAAS;CAC9B,MAAM,oBAAoB,aAAa,WAAW,OAAO;CACzD,MAAM,QAAQ,MAAM,UAAU,IAAI;CAClC,MAAM,WAAmC,CAAC;CAK1C,MAAM,SAAS,OAAO,qBAAqB,OAAO,SAAS;EACzD,MAAM,MAAM,SAAS,MAAM,IAAI;EAC/B,MAAM,SAAS,MAAM,SAAS,IAAI;EAClC,MAAM,OAAO,WAAW,QAAQ,CAAC,CAAC,OAAO,MAAM,CAAC,CAAC,OAAO,KAAK;EAC7D,SAAS,OAAO;EAEhB,IAAI,CAAE,MAAM,QAAQ,OAAO,WAAW,MAAM,GAC1C,MAAM,QAAQ,MAAM,WAAW,QAAQ,MAAM;EAI/C,MAAM,QAAQ,MAAM,GAAG,kBAAkB,GAAG,OAAO,MAAM;EACzD,IAAI,IACF,MAAM,iBAAiB,IAAI,IAAI;CAEnC,CAAC;CACD,MAAM,QAAQ,MAAM,GAAG,kBAAkB,iBAAiB,OAAO,KAAK,KAAK,UAAU,QAAQ,CAAC,CAAC;AACjG;;AAGA,MAAM,sBAAsB;AAE5B,eAAe,iBAAiB,IAAqB,MAA6B;CAChF,IAAI;EACF,MAAM,cAAc,GAAG,OAAO;EAC9B,MAAM,uBAAM,IAAI,KAAK,EAAA,CAAE,YAAY;EACnC,MAAM,WAAY,MAAM,GAAG,IAAI,aAAa,IAAI;EAChD,IAAI,UACF,MAAM,GAAG,OAAO,aAAa,MAAM;GACjC,UAAU,SAAS,WAAW;GAC9B,YAAY;EACd,CAAC;OAED,MAAM,GAAG,OAAO,aAAa;GAAE;GAAM,UAAU;GAAG,YAAY;GAAK,WAAW;EAAI,CAAC;CAEvF,QAAQ,CAER;AACF;AAEA,eAAe,UAAU,KAAgC;CACvD,MAAM,UAAU,MAAM,QAAQ,KAAK,EAAE,eAAe,KAAK,CAAC;CAU1D,QAAO,MATc,QAAQ,IAC3B,QAAQ,IAAI,OAAO,UAAU;EAC3B,MAAM,OAAO,KAAK,KAAK,MAAM,IAAI;EACjC,IAAI,MAAM,YAAY,GACpB,OAAO,MAAM,UAAU,IAAI;EAE7B,OAAO,CAAC,IAAI;CACd,CAAC,CACH,EAAA,CACc,KAAK;AACrB"}
@@ -0,0 +1,40 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ import { CaptureTarget } from "../adapters/capture-target.mjs";
4
+ import { CaptureRunner } from "../adapter/capture-runner.mjs";
5
+ import { SecretInput } from "../utils/encrypt.mjs";
6
+ import { Build } from "../schema/build.mjs";
7
+ import { Project } from "../schema/project.mjs";
8
+ //#region src/capture/storybook-urls.d.ts
9
+ /** What a pass tells the source: how long it may run, which sizes the token's lifetime. */
10
+ interface StorybookUrlPass {
11
+ /** The render budget of the pass in ms (`renderBudgetMs`); unset when the pass has none. */
12
+ budgetMs?: number | undefined;
13
+ }
14
+ /** Gives the URL a target's browser opens, or `undefined` when it needs none (or none can be made). */
15
+ type StorybookUrlSource = ((target: CaptureTarget, pass?: StorybookUrlPass) => Promise<string | undefined>) & {
16
+ /**
17
+ * The lifetime in seconds a token minted for a pass of this budget gets (the project's override
18
+ * or the derived, capped one). A pass that outlasts it is rendered in slices, each with a fresh
19
+ * token (`renderInSlices`). `undefined` for a target that needs no signed URL (it is never
20
+ * sliced), and absent on a source that has no such limit.
21
+ */
22
+ ttlSeconds?: (target: CaptureTarget, budgetMs: number) => number | undefined;
23
+ };
24
+ /** What minting needs of a capture job. */
25
+ interface StorybookUrlOptions {
26
+ runner: Pick<CaptureRunner, "metadata">;
27
+ secret?: SecretInput;
28
+ publicOrigin?: (() => Promise<string | undefined>) | undefined;
29
+ }
30
+ /**
31
+ * A source of signed URLs for one build. Every call mints a fresh token, so a shard that waited in
32
+ * the queue or a retried pass is never handed a token minted before it started.
33
+ */
34
+ declare function storybookUrlSource(options: StorybookUrlOptions, subject: {
35
+ build: Pick<Build, "id" | "projectId">;
36
+ project: Pick<Project, "storybookUrlTtlSeconds">;
37
+ }): StorybookUrlSource;
38
+ //#endregion
39
+ export { StorybookUrlOptions, StorybookUrlPass, StorybookUrlSource, storybookUrlSource };
40
+ //# sourceMappingURL=storybook-urls.d.mts.map
@@ -0,0 +1,41 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { signStorybookToken, storybookTokenPath, storybookUrlTtl } from "../utils/storybook-token.mjs";
5
+ //#region src/capture/storybook-urls.ts
6
+ /**
7
+ * A source of signed URLs for one build. Every call mints a fresh token, so a shard that waited in
8
+ * the queue or a retried pass is never handed a token minted before it started.
9
+ */
10
+ function storybookUrlSource(options, subject) {
11
+ const source = async (target, pass = {}) => {
12
+ const origin = needsUrl(options.runner, target) ? await options.publicOrigin?.() : void 0;
13
+ return origin !== void 0 && options.secret ? `${origin}${mint(options.secret, subject, pass)}` : void 0;
14
+ };
15
+ source.ttlSeconds = (target, budgetMs) => needsUrl(options.runner, target) ? storybookUrlTtl({
16
+ override: subject.project.storybookUrlTtlSeconds,
17
+ budgetMs
18
+ }) : void 0;
19
+ return source;
20
+ }
21
+ /** The path of a fresh token for one pass of the build. */
22
+ function mint(secret, subject, pass) {
23
+ const ttlSeconds = storybookUrlTtl({
24
+ override: subject.project.storybookUrlTtlSeconds,
25
+ budgetMs: pass.budgetMs ?? 0
26
+ });
27
+ const { id: buildId, projectId } = subject.build;
28
+ return storybookTokenPath(signStorybookToken({
29
+ secret,
30
+ projectId,
31
+ buildId,
32
+ ttlSeconds
33
+ }));
34
+ }
35
+ function needsUrl(runner, target) {
36
+ return runner.metadata.targets?.some((t) => t.target === target && t.reach === "url") ?? false;
37
+ }
38
+ //#endregion
39
+ export { storybookUrlSource };
40
+
41
+ //# sourceMappingURL=storybook-urls.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storybook-urls.mjs","names":[],"sources":["../../src/capture/storybook-urls.ts"],"sourcesContent":["/**\n * The signed Storybook URL of a render (ADR 0007, decision 5): minted at the moment a pass starts,\n * only for a target whose browser cannot reach this host (`reach: \"url\"`). The URL is the build's\n * Storybook root, `<publicOrigin>/_s/<token>/`; it carries the token and is never logged. Its\n * lifetime comes from the pass's render budget unless the project sets one (`storybookUrlTtl`).\n */\nimport type { CaptureRunner, CaptureTarget } from \"../adapters/capture-runner.ts\";\nimport type { Build } from \"../schema/build.ts\";\nimport type { Project } from \"../schema/project.ts\";\nimport type { SecretInput } from \"../utils/encrypt.ts\";\nimport {\n signStorybookToken,\n storybookTokenPath,\n storybookUrlTtl,\n} from \"../utils/storybook-token.ts\";\n\n/** What a pass tells the source: how long it may run, which sizes the token's lifetime. */\nexport interface StorybookUrlPass {\n /** The render budget of the pass in ms (`renderBudgetMs`); unset when the pass has none. */\n budgetMs?: number | undefined;\n}\n\n/** Gives the URL a target's browser opens, or `undefined` when it needs none (or none can be made). */\nexport type StorybookUrlSource = ((\n target: CaptureTarget,\n pass?: StorybookUrlPass,\n) => Promise<string | undefined>) & {\n /**\n * The lifetime in seconds a token minted for a pass of this budget gets (the project's override\n * or the derived, capped one). A pass that outlasts it is rendered in slices, each with a fresh\n * token (`renderInSlices`). `undefined` for a target that needs no signed URL (it is never\n * sliced), and absent on a source that has no such limit.\n */\n ttlSeconds?: (target: CaptureTarget, budgetMs: number) => number | undefined;\n};\n\n/** What minting needs of a capture job. */\nexport interface StorybookUrlOptions {\n runner: Pick<CaptureRunner, \"metadata\">;\n secret?: SecretInput;\n publicOrigin?: (() => Promise<string | undefined>) | undefined;\n}\n\n/**\n * A source of signed URLs for one build. Every call mints a fresh token, so a shard that waited in\n * the queue or a retried pass is never handed a token minted before it started.\n */\nexport function storybookUrlSource(\n options: StorybookUrlOptions,\n subject: {\n build: Pick<Build, \"id\" | \"projectId\">;\n project: Pick<Project, \"storybookUrlTtlSeconds\">;\n },\n): StorybookUrlSource {\n const source: StorybookUrlSource = async (target, pass = {}) => {\n const origin = needsUrl(options.runner, target) ? await options.publicOrigin?.() : undefined;\n return origin !== undefined && options.secret\n ? `${origin}${mint(options.secret, subject, pass)}`\n : undefined;\n };\n source.ttlSeconds = (target, budgetMs): number | undefined =>\n needsUrl(options.runner, target)\n ? storybookUrlTtl({ override: subject.project.storybookUrlTtlSeconds, budgetMs })\n : undefined;\n return source;\n}\n\n/** The path of a fresh token for one pass of the build. */\nfunction mint(\n secret: SecretInput,\n subject: Parameters<typeof storybookUrlSource>[1],\n pass: StorybookUrlPass,\n): string {\n const ttlSeconds = storybookUrlTtl({\n override: subject.project.storybookUrlTtlSeconds,\n budgetMs: pass.budgetMs ?? 0,\n });\n const { id: buildId, projectId } = subject.build;\n return storybookTokenPath(signStorybookToken({ secret, projectId, buildId, ttlSeconds }));\n}\n\nfunction needsUrl(runner: Pick<CaptureRunner, \"metadata\">, target: CaptureTarget): boolean {\n return runner.metadata.targets?.some((t) => t.target === target && t.reach === \"url\") ?? false;\n}\n"],"mappings":";;;;;;;;;AA+CA,SAAgB,mBACd,SACA,SAIoB;CACpB,MAAM,SAA6B,OAAO,QAAQ,OAAO,CAAC,MAAM;EAC9D,MAAM,SAAS,SAAS,QAAQ,QAAQ,MAAM,IAAI,MAAM,QAAQ,eAAe,IAAI,KAAA;EACnF,OAAO,WAAW,KAAA,KAAa,QAAQ,SACnC,GAAG,SAAS,KAAK,QAAQ,QAAQ,SAAS,IAAI,MAC9C,KAAA;CACN;CACA,OAAO,cAAc,QAAQ,aAC3B,SAAS,QAAQ,QAAQ,MAAM,IAC3B,gBAAgB;EAAE,UAAU,QAAQ,QAAQ;EAAwB;CAAS,CAAC,IAC9E,KAAA;CACN,OAAO;AACT;;AAGA,SAAS,KACP,QACA,SACA,MACQ;CACR,MAAM,aAAa,gBAAgB;EACjC,UAAU,QAAQ,QAAQ;EAC1B,UAAU,KAAK,YAAY;CAC7B,CAAC;CACD,MAAM,EAAE,IAAI,SAAS,cAAc,QAAQ;CAC3C,OAAO,mBAAmB,mBAAmB;EAAE;EAAQ;EAAW;EAAS;CAAW,CAAC,CAAC;AAC1F;AAEA,SAAS,SAAS,QAAyC,QAAgC;CACzF,OAAO,OAAO,SAAS,SAAS,MAAM,MAAM,EAAE,WAAW,UAAU,EAAE,UAAU,KAAK,KAAK;AAC3F"}
@@ -1,13 +1,16 @@
1
1
  import __tsdown_shims_path from 'node:path';
2
2
  import __tsdown_shims_url from 'node:url';
3
- import { StoryEntry, StoryParameters, StorySourceAdapter, StoryViewportConfig, Viewport } from "./adapter.mjs";
3
+ import { DiscoverOptions, StoryEntry, StoryParameters, StorySourceAdapter, StoryViewportConfig, Viewport } from "./adapter.mjs";
4
4
  import { DepGraph } from "@storyshelf/affected";
5
5
  //#region src/capture/storybook.d.ts
6
6
  /** Story parameters as serialized by the Storybook index. */
7
7
  interface StorybookParameters {
8
8
  chromatic?: StoryParameters;
9
- storyshelf?: StoryParameters;
9
+ storyshelf?: StoryParameters & {
10
+ figma?: unknown;
11
+ };
10
12
  viewport?: StoryViewportConfig;
13
+ design?: unknown;
11
14
  }
12
15
  /** Merge the `chromatic` and `storyshelf` parameter layers (`storyshelf` wins). */
13
16
  declare function mergeParameters(entry: {
@@ -29,8 +32,13 @@ declare function resolveStoryViewports(entry: Pick<StoryEntry, "parameters">, gl
29
32
  declare class StorybookAdapter implements StorySourceAdapter {
30
33
  readonly name = "storybook";
31
34
  readonly screenshotSelector = "#storybook-root";
32
- discover(source: string): Promise<StoryEntry[]>;
33
- buildUrl(baseUrl: string, storyId: string): string;
35
+ discover(source: string, options?: DiscoverOptions): Promise<StoryEntry[]>;
36
+ /**
37
+ * The `.test()` entries of a Storybook 10 build (index `subtype: "test"`), which {@link discover}
38
+ * leaves out. Empty for older Storybooks, whose index has no such entries.
39
+ */
40
+ discoverTests(source: string): Promise<StoryEntry[]>;
41
+ buildUrl(baseUrl: string, storyId: string, globals?: Record<string, unknown>): string;
34
42
  /**
35
43
  * Load the Vite stats dependency graph of an extracted build for affected
36
44
  * capture. Resolves `null` when the build ships no usable stats, in which
@@ -1,19 +1,32 @@
1
1
  import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
- import { STATS_FILENAME, loadDepGraph } from "@storyshelf/affected";
4
+ import { applyModes, buildGlobalsParam } from "./modes.mjs";
5
+ import { extractDesignUrl } from "./design.mjs";
5
6
  import { join } from "node:path";
7
+ import { STATS_FILENAME, loadDepGraph } from "@storyshelf/affected";
6
8
  import { readFile } from "node:fs/promises";
7
9
  //#region src/capture/storybook.ts
8
10
  /** Merge the `chromatic` and `storyshelf` parameter layers (`storyshelf` wins). */
9
11
  function mergeParameters(entry) {
12
+ const { figma: _figma, ...storyshelf } = entry.parameters?.storyshelf ?? {};
10
13
  const merged = {
11
14
  ...entry.parameters?.chromatic,
12
- ...entry.parameters?.storyshelf
15
+ ...storyshelf
13
16
  };
17
+ const modes = mergeModes(entry.parameters?.chromatic?.modes, entry.parameters?.storyshelf?.modes);
18
+ if (modes) merged.modes = modes;
14
19
  if (entry.parameters?.viewport) merged.viewport = entry.parameters.viewport;
15
20
  return Object.keys(merged).length > 0 ? merged : void 0;
16
21
  }
22
+ /** Union both parameter keys' modes by name (`storyshelf` wins per mode). */
23
+ function mergeModes(chromatic, storyshelf) {
24
+ const merged = {
25
+ ...chromatic,
26
+ ...storyshelf
27
+ };
28
+ return Object.keys(merged).length > 0 ? merged : void 0;
29
+ }
17
30
  /**
18
31
  * The Storybook `MINIMAL_VIEWPORTS` snapshot used to resolve a story's
19
32
  * `defaultViewport` name at capture time. Dimensions are the documented
@@ -48,8 +61,13 @@ const STORYBOOK_BUILTIN_VIEWPORTS = {
48
61
  */
49
62
  function resolveStoryViewports(entry, globalViewports) {
50
63
  const resolved = storyViewport(entry);
51
- if (resolved && !globalViewports.some((v) => v.name === resolved.name)) return [...globalViewports, resolved];
52
- return [...globalViewports];
64
+ const withDefault = resolved && !globalViewports.some((v) => v.name === resolved.name) ? [...globalViewports, resolved] : [...globalViewports];
65
+ return applyModes(entry.parameters?.modes, withDefault, (name) => namedViewport(entry, name));
66
+ }
67
+ /** Resolve a Storybook viewport name via the story's inline config, then builtins. */
68
+ function namedViewport(entry, name) {
69
+ const inline = entry.parameters?.viewport?.viewports?.[name];
70
+ return (inline ? viewportFromInline(name, inline) : void 0) ?? STORYBOOK_BUILTIN_VIEWPORTS[name];
53
71
  }
54
72
  function storyViewport(entry) {
55
73
  const config = entry.parameters?.viewport;
@@ -78,24 +96,46 @@ function pxOf(value) {
78
96
  const match = /^(\d+(?:\.\d+)?)px$/u.exec(bare);
79
97
  return match ? Number(match[1]) : void 0;
80
98
  }
99
+ function toTestEntry(entry) {
100
+ const test = toStoryEntry(entry);
101
+ if (entry.parent !== void 0) test.parentId = entry.parent;
102
+ return test;
103
+ }
104
+ function toStoryEntry(entry, parseDesignUrl) {
105
+ const design = parseDesignUrl ? extractDesignUrl(entry.parameters, parseDesignUrl) : {};
106
+ const story = {
107
+ id: entry.id,
108
+ title: entry.title,
109
+ name: entry.name,
110
+ importPath: entry.importPath,
111
+ tags: entry.tags,
112
+ type: entry.type === "docs" ? "docs" : "story",
113
+ parameters: mergeParameters(entry)
114
+ };
115
+ if (design.url) story.designUrl = design.url;
116
+ if (design.invalid) story.designInvalid = design.invalid;
117
+ return story;
118
+ }
81
119
  /** Discovers stories from a built Storybook via its index file. */
82
120
  var StorybookAdapter = class {
83
121
  name = "storybook";
84
122
  screenshotSelector = "#storybook-root";
85
- async discover(source) {
123
+ async discover(source, options = {}) {
124
+ const index = await this.readIndex(source);
125
+ return Object.values(index.entries).filter((entry) => entry.type !== "docs" && entry.subtype !== "test").map((entry) => toStoryEntry(entry, options.parseDesignUrl));
126
+ }
127
+ /**
128
+ * The `.test()` entries of a Storybook 10 build (index `subtype: "test"`), which {@link discover}
129
+ * leaves out. Empty for older Storybooks, whose index has no such entries.
130
+ */
131
+ async discoverTests(source) {
86
132
  const index = await this.readIndex(source);
87
- return Object.values(index.entries).filter((entry) => entry.type !== "docs" && entry.subtype !== "test").map((entry) => ({
88
- id: entry.id,
89
- title: entry.title,
90
- name: entry.name,
91
- importPath: entry.importPath,
92
- tags: entry.tags,
93
- type: entry.type === "docs" ? "docs" : "story",
94
- parameters: mergeParameters(entry)
95
- }));
133
+ return Object.values(index.entries).filter((entry) => entry.subtype === "test" && entry.type !== "docs").map((entry) => toTestEntry(entry));
96
134
  }
97
- buildUrl(baseUrl, storyId) {
98
- return `${baseUrl}/iframe.html?id=${encodeURIComponent(storyId)}&viewMode=story`;
135
+ buildUrl(baseUrl, storyId, globals) {
136
+ const url = `${baseUrl}/iframe.html?id=${encodeURIComponent(storyId)}&viewMode=story`;
137
+ const param = globals ? buildGlobalsParam(globals) : "";
138
+ return param === "" ? url : `${url}&globals=${encodeURIComponent(param)}`;
99
139
  }
100
140
  /**
101
141
  * Load the Vite stats dependency graph of an extracted build for affected
@@ -1 +1 @@
1
- {"version":3,"file":"storybook.mjs","names":[],"sources":["../../src/capture/storybook.ts"],"sourcesContent":["import { loadDepGraph, STATS_FILENAME, type DepGraph } from \"@storyshelf/affected\";\nimport { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type {\n StoryEntry,\n StoryParameters,\n StorySourceAdapter,\n StoryViewportConfig,\n StoryViewportDefinition,\n Viewport,\n} from \"./adapter.ts\";\n\ninterface StorybookIndex {\n v: number;\n entries: Record<\n string,\n {\n id: string;\n name: string;\n title: string;\n importPath?: string;\n tags?: string[];\n type: string;\n subtype?: string;\n parameters?: StorybookParameters;\n }\n >;\n}\n\n/** Story parameters as serialized by the Storybook index. */\ninterface StorybookParameters {\n chromatic?: StoryParameters;\n storyshelf?: StoryParameters;\n viewport?: StoryViewportConfig;\n}\n\n/** Merge the `chromatic` and `storyshelf` parameter layers (`storyshelf` wins). */\nexport function mergeParameters(entry: {\n parameters?: StorybookParameters;\n}): StoryParameters | undefined {\n const merged: StoryParameters = {\n ...entry.parameters?.chromatic,\n ...entry.parameters?.storyshelf,\n };\n if (entry.parameters?.viewport) merged.viewport = entry.parameters.viewport;\n return Object.keys(merged).length > 0 ? merged : undefined;\n}\n\n/**\n * The Storybook `MINIMAL_VIEWPORTS` snapshot used to resolve a story's\n * `defaultViewport` name at capture time. Dimensions are the documented\n * stable set; inline `parameters.viewport.viewports` always win over these.\n */\nexport const STORYBOOK_BUILTIN_VIEWPORTS: Readonly<Record<string, Viewport>> = {\n mobile1: { name: \"mobile1\", width: 320, height: 568 },\n mobile2: { name: \"mobile2\", width: 414, height: 896 },\n tablet: { name: \"tablet\", width: 834, height: 1112 },\n desktop: { name: \"desktop\", width: 1024, height: 1280 },\n};\n\n/**\n * Resolve the viewports a story should be captured at: the project's global\n * list plus the story's Storybook default viewport (union), deduplicated by\n * name. An unresolvable or duplicate default contributes nothing extra.\n */\nexport function resolveStoryViewports(\n entry: Pick<StoryEntry, \"parameters\">,\n globalViewports: Viewport[],\n): Viewport[] {\n const resolved = storyViewport(entry);\n if (resolved && !globalViewports.some((v) => v.name === resolved.name)) {\n return [...globalViewports, resolved];\n }\n return [...globalViewports];\n}\n\nfunction storyViewport(entry: Pick<StoryEntry, \"parameters\">): Viewport | undefined {\n const config = entry.parameters?.viewport;\n const name = config?.defaultViewport;\n const inline = name ? config?.viewports?.[name] : undefined;\n const resolved =\n name && inline\n ? viewportFromInline(name, inline)\n : name\n ? STORYBOOK_BUILTIN_VIEWPORTS[name]\n : undefined;\n return resolved;\n}\n\nfunction viewportFromInline(\n defaultName: string,\n inline: StoryViewportDefinition,\n): Viewport | undefined {\n const dims = viewportDims(inline.styles);\n return dims ? { name: inline.name ?? defaultName, ...dims } : undefined;\n}\n\nfunction viewportDims(\n styles: StoryViewportDefinition[\"styles\"] | undefined,\n): { width: number; height: number } | undefined {\n const width = pxOf(styles?.width);\n const height = pxOf(styles?.height);\n return width !== undefined && height !== undefined ? { width, height } : undefined;\n}\n\nfunction pxOf(value: string | number | undefined): number | undefined {\n if (typeof value === \"number\") return Math.round(value);\n const bare = typeof value === \"string\" ? value.trim() : \"\";\n const match = /^(\\d+(?:\\.\\d+)?)px$/u.exec(bare);\n return match ? Number(match[1]) : undefined;\n}\n\n/** Discovers stories from a built Storybook via its index file. */\nexport class StorybookAdapter implements StorySourceAdapter {\n readonly name = \"storybook\";\n readonly screenshotSelector = \"#storybook-root\";\n\n async discover(source: string): Promise<StoryEntry[]> {\n const index = await this.readIndex(source);\n return Object.values(index.entries)\n .filter((entry) => entry.type !== \"docs\" && entry.subtype !== \"test\")\n .map((entry) => ({\n id: entry.id,\n title: entry.title,\n name: entry.name,\n importPath: entry.importPath,\n tags: entry.tags,\n type: entry.type === \"docs\" ? \"docs\" : \"story\",\n parameters: mergeParameters(entry),\n }));\n }\n\n // eslint-disable-next-line class-methods-use-this\n buildUrl(baseUrl: string, storyId: string): string {\n return `${baseUrl}/iframe.html?id=${encodeURIComponent(storyId)}&viewMode=story`;\n }\n\n /**\n * Load the Vite stats dependency graph of an extracted build for affected\n * capture. Resolves `null` when the build ships no usable stats, in which\n * case the orchestrator renders every story.\n */\n // eslint-disable-next-line class-methods-use-this\n async loadDependencyGraph(source: string): Promise<DepGraph | null> {\n return await loadDepGraph(join(source, STATS_FILENAME));\n }\n\n // eslint-disable-next-line class-methods-use-this\n private async readIndex(source: string): Promise<StorybookIndex> {\n const candidates: string[] = [\"stories.json\", \"index.json\"];\n const results = await Promise.all(\n candidates.map(async (name) => {\n try {\n const raw = await readFile(join(source, name), \"utf8\");\n return JSON.parse(raw) as StorybookIndex;\n } catch {\n return null;\n }\n }),\n );\n const found = results.find((index) => index !== null);\n if (found) {\n return found;\n }\n throw new Error(`No Storybook index found in ${source}`);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"storybook.mjs","names":[],"sources":["../../src/capture/storybook.ts"],"sourcesContent":["import { loadDepGraph, STATS_FILENAME, type DepGraph } from \"@storyshelf/affected\";\nimport { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type {\n ModeMap,\n StoryEntry,\n StoryParameters,\n StorySourceAdapter,\n StoryViewportConfig,\n StoryViewportDefinition,\n Viewport,\n} from \"./adapter.ts\";\nimport type { DiscoverOptions } from \"./adapter.ts\";\nimport { extractDesignUrl, type DesignUrlParser } from \"./design.ts\";\nimport { applyModes, buildGlobalsParam } from \"./modes.ts\";\n\ninterface StorybookIndex {\n v: number;\n entries: Record<\n string,\n {\n id: string;\n name: string;\n title: string;\n importPath?: string;\n tags?: string[];\n type: string;\n subtype?: string;\n /** Test entries only: the id of the story the `.test()` is written under. */\n parent?: string;\n parameters?: StorybookParameters;\n }\n >;\n}\n\n/** Story parameters as serialized by the Storybook index. */\ninterface StorybookParameters {\n chromatic?: StoryParameters;\n storyshelf?: StoryParameters & { figma?: unknown };\n viewport?: StoryViewportConfig;\n design?: unknown;\n}\n\n/** Merge the `chromatic` and `storyshelf` parameter layers (`storyshelf` wins). */\nexport function mergeParameters(entry: {\n parameters?: StorybookParameters;\n}): StoryParameters | undefined {\n const { figma: _figma, ...storyshelf } = entry.parameters?.storyshelf ?? {};\n const merged: StoryParameters = { ...entry.parameters?.chromatic, ...storyshelf };\n const modes = mergeModes(entry.parameters?.chromatic?.modes, entry.parameters?.storyshelf?.modes);\n if (modes) merged.modes = modes;\n if (entry.parameters?.viewport) merged.viewport = entry.parameters.viewport;\n return Object.keys(merged).length > 0 ? merged : undefined;\n}\n\n/** Union both parameter keys' modes by name (`storyshelf` wins per mode). */\nfunction mergeModes(chromatic?: ModeMap, storyshelf?: ModeMap): ModeMap | undefined {\n const merged = { ...chromatic, ...storyshelf };\n return Object.keys(merged).length > 0 ? merged : undefined;\n}\n\n/**\n * The Storybook `MINIMAL_VIEWPORTS` snapshot used to resolve a story's\n * `defaultViewport` name at capture time. Dimensions are the documented\n * stable set; inline `parameters.viewport.viewports` always win over these.\n */\nexport const STORYBOOK_BUILTIN_VIEWPORTS: Readonly<Record<string, Viewport>> = {\n mobile1: { name: \"mobile1\", width: 320, height: 568 },\n mobile2: { name: \"mobile2\", width: 414, height: 896 },\n tablet: { name: \"tablet\", width: 834, height: 1112 },\n desktop: { name: \"desktop\", width: 1024, height: 1280 },\n};\n\n/**\n * Resolve the viewports a story should be captured at: the project's global\n * list plus the story's Storybook default viewport (union), deduplicated by\n * name. An unresolvable or duplicate default contributes nothing extra.\n */\nexport function resolveStoryViewports(\n entry: Pick<StoryEntry, \"parameters\">,\n globalViewports: Viewport[],\n): Viewport[] {\n const resolved = storyViewport(entry);\n const withDefault =\n resolved && !globalViewports.some((v) => v.name === resolved.name)\n ? [...globalViewports, resolved]\n : [...globalViewports];\n return applyModes(entry.parameters?.modes, withDefault, (name) => namedViewport(entry, name));\n}\n\n/** Resolve a Storybook viewport name via the story's inline config, then builtins. */\nfunction namedViewport(entry: Pick<StoryEntry, \"parameters\">, name: string): Viewport | undefined {\n const inline = entry.parameters?.viewport?.viewports?.[name];\n return (\n (inline ? viewportFromInline(name, inline) : undefined) ?? STORYBOOK_BUILTIN_VIEWPORTS[name]\n );\n}\n\nfunction storyViewport(entry: Pick<StoryEntry, \"parameters\">): Viewport | undefined {\n const config = entry.parameters?.viewport;\n const name = config?.defaultViewport;\n const inline = name ? config?.viewports?.[name] : undefined;\n const resolved =\n name && inline\n ? viewportFromInline(name, inline)\n : name\n ? STORYBOOK_BUILTIN_VIEWPORTS[name]\n : undefined;\n return resolved;\n}\n\nfunction viewportFromInline(\n defaultName: string,\n inline: StoryViewportDefinition,\n): Viewport | undefined {\n const dims = viewportDims(inline.styles);\n return dims ? { name: inline.name ?? defaultName, ...dims } : undefined;\n}\n\nfunction viewportDims(\n styles: StoryViewportDefinition[\"styles\"] | undefined,\n): { width: number; height: number } | undefined {\n const width = pxOf(styles?.width);\n const height = pxOf(styles?.height);\n return width !== undefined && height !== undefined ? { width, height } : undefined;\n}\n\nfunction pxOf(value: string | number | undefined): number | undefined {\n if (typeof value === \"number\") return Math.round(value);\n const bare = typeof value === \"string\" ? value.trim() : \"\";\n const match = /^(\\d+(?:\\.\\d+)?)px$/u.exec(bare);\n return match ? Number(match[1]) : undefined;\n}\n\nfunction toTestEntry(entry: StorybookIndex[\"entries\"][string]): StoryEntry {\n const test = toStoryEntry(entry);\n if (entry.parent !== undefined) test.parentId = entry.parent;\n return test;\n}\n\nfunction toStoryEntry(\n entry: StorybookIndex[\"entries\"][string],\n parseDesignUrl?: DesignUrlParser,\n): StoryEntry {\n const design = parseDesignUrl ? extractDesignUrl(entry.parameters, parseDesignUrl) : {};\n const story: StoryEntry = {\n id: entry.id,\n title: entry.title,\n name: entry.name,\n importPath: entry.importPath,\n tags: entry.tags,\n type: entry.type === \"docs\" ? \"docs\" : \"story\",\n parameters: mergeParameters(entry),\n };\n if (design.url) story.designUrl = design.url;\n if (design.invalid) story.designInvalid = design.invalid;\n return story;\n}\n\n/** Discovers stories from a built Storybook via its index file. */\nexport class StorybookAdapter implements StorySourceAdapter {\n readonly name = \"storybook\";\n readonly screenshotSelector = \"#storybook-root\";\n\n async discover(source: string, options: DiscoverOptions = {}): Promise<StoryEntry[]> {\n const index = await this.readIndex(source);\n return Object.values(index.entries)\n .filter((entry) => entry.type !== \"docs\" && entry.subtype !== \"test\")\n .map((entry) => toStoryEntry(entry, options.parseDesignUrl));\n }\n\n /**\n * The `.test()` entries of a Storybook 10 build (index `subtype: \"test\"`), which {@link discover}\n * leaves out. Empty for older Storybooks, whose index has no such entries.\n */\n async discoverTests(source: string): Promise<StoryEntry[]> {\n const index = await this.readIndex(source);\n return Object.values(index.entries)\n .filter((entry) => entry.subtype === \"test\" && entry.type !== \"docs\")\n .map((entry) => toTestEntry(entry));\n }\n\n // eslint-disable-next-line class-methods-use-this\n buildUrl(baseUrl: string, storyId: string, globals?: Record<string, unknown>): string {\n const url = `${baseUrl}/iframe.html?id=${encodeURIComponent(storyId)}&viewMode=story`;\n const param = globals ? buildGlobalsParam(globals) : \"\";\n return param === \"\" ? url : `${url}&globals=${encodeURIComponent(param)}`;\n }\n\n /**\n * Load the Vite stats dependency graph of an extracted build for affected\n * capture. Resolves `null` when the build ships no usable stats, in which\n * case the orchestrator renders every story.\n */\n // eslint-disable-next-line class-methods-use-this\n async loadDependencyGraph(source: string): Promise<DepGraph | null> {\n return await loadDepGraph(join(source, STATS_FILENAME));\n }\n\n // eslint-disable-next-line class-methods-use-this\n private async readIndex(source: string): Promise<StorybookIndex> {\n const candidates: string[] = [\"stories.json\", \"index.json\"];\n const results = await Promise.all(\n candidates.map(async (name) => {\n try {\n const raw = await readFile(join(source, name), \"utf8\");\n return JSON.parse(raw) as StorybookIndex;\n } catch {\n return null;\n }\n }),\n );\n const found = results.find((index) => index !== null);\n if (found) {\n return found;\n }\n throw new Error(`No Storybook index found in ${source}`);\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { BuildModel } from "../models/build.mjs";
5
+ import { ProjectModel } from "../models/project.mjs";
6
+ //#region src/capture/target.ts
7
+ /** Load a build and its project, throwing when either is gone. */
8
+ async function loadTarget(options, buildId) {
9
+ const build = await new BuildModel(options.db, options.tables).get(buildId);
10
+ if (!build) throw new Error(`Build not found: ${buildId}`);
11
+ const project = await new ProjectModel(options.db, options.tables).get(build.projectId);
12
+ if (!project) throw new Error(`Project not found: ${build.projectId}`);
13
+ return {
14
+ build,
15
+ project
16
+ };
17
+ }
18
+ //#endregion
19
+ export { loadTarget };
20
+
21
+ //# sourceMappingURL=target.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"target.mjs","names":[],"sources":["../../src/capture/target.ts"],"sourcesContent":["import type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport { BuildModel, type BuildTables } from \"../models/build.ts\";\nimport { ProjectModel, type ProjectTables } from \"../models/project.ts\";\nimport type { Build } from \"../schema/build.ts\";\nimport type { Project } from \"../schema/project.ts\";\n\n/** Load a build and its project, throwing when either is gone. */\nexport async function loadTarget(\n options: { db: DatabaseAdapter; tables: BuildTables & ProjectTables },\n buildId: string,\n): Promise<{ build: Build; project: Project }> {\n const build = await new BuildModel(options.db, options.tables).get(buildId);\n if (!build) {\n throw new Error(`Build not found: ${buildId}`);\n }\n const project = await new ProjectModel(options.db, options.tables).get(build.projectId);\n if (!project) {\n throw new Error(`Project not found: ${build.projectId}`);\n }\n return { build, project };\n}\n"],"mappings":";;;;;;;AAOA,eAAsB,WACpB,SACA,SAC6C;CAC7C,MAAM,QAAQ,MAAM,IAAI,WAAW,QAAQ,IAAI,QAAQ,MAAM,CAAC,CAAC,IAAI,OAAO;CAC1E,IAAI,CAAC,OACH,MAAM,IAAI,MAAM,oBAAoB,SAAS;CAE/C,MAAM,UAAU,MAAM,IAAI,aAAa,QAAQ,IAAI,QAAQ,MAAM,CAAC,CAAC,IAAI,MAAM,SAAS;CACtF,IAAI,CAAC,SACH,MAAM,IAAI,MAAM,sBAAsB,MAAM,WAAW;CAEzD,OAAO;EAAE;EAAO;CAAQ;AAC1B"}
@@ -0,0 +1,25 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ import { RunnerTarget } from "../adapters/capture-target.mjs";
4
+ import { CaptureRunner } from "../adapter/capture-runner.mjs";
5
+ //#region src/capture/targets.d.ts
6
+ /** Why a `reach: "url"` target is unavailable without a secret (the first words are matched by tests and docs). */
7
+ declare const NO_SECRET_REASON = "no secret: set the server secret, it signs the Storybook URL the remote browser opens";
8
+ /** Why it is unavailable without a confirmed public origin. */
9
+ declare const NO_ORIGIN_REASON = "no public origin: set PUBLIC_BASE_URL, or confirm the detected origin on the System page";
10
+ /** What a target that needs a URL depends on. */
11
+ interface TargetPrerequisites {
12
+ /** The confirmed public origin (ADR 0007, decision 6); absent while none is confirmed. */
13
+ publicOrigin: string | undefined;
14
+ /** Whether the server has a `secret` to sign Storybook URLs with. */
15
+ hasSecret: boolean;
16
+ }
17
+ /**
18
+ * The runner's targets with `reach: "url"` ones marked `unavailable` while they lack a `secret` or a
19
+ * public origin (ADR 0007), so a settings save refuses them and a build fails at once with the
20
+ * reason. `undefined` when the runner publishes no list (it offers any target).
21
+ */
22
+ declare function offeredTargets(runner: Pick<CaptureRunner, "metadata">, prerequisites: TargetPrerequisites): readonly RunnerTarget[] | undefined;
23
+ //#endregion
24
+ export { NO_ORIGIN_REASON, NO_SECRET_REASON, TargetPrerequisites, offeredTargets };
25
+ //# sourceMappingURL=targets.d.mts.map
@@ -0,0 +1,32 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ //#region src/capture/targets.ts
5
+ /** Why a `reach: "url"` target is unavailable without a secret (the first words are matched by tests and docs). */
6
+ const NO_SECRET_REASON = "no secret: set the server secret, it signs the Storybook URL the remote browser opens";
7
+ /** Why it is unavailable without a confirmed public origin. */
8
+ const NO_ORIGIN_REASON = "no public origin: set PUBLIC_BASE_URL, or confirm the detected origin on the System page";
9
+ /**
10
+ * The runner's targets with `reach: "url"` ones marked `unavailable` while they lack a `secret` or a
11
+ * public origin (ADR 0007), so a settings save refuses them and a build fails at once with the
12
+ * reason. `undefined` when the runner publishes no list (it offers any target).
13
+ */
14
+ function offeredTargets(runner, prerequisites) {
15
+ return runner.metadata.targets?.map((entry) => withPrerequisites(entry, prerequisites));
16
+ }
17
+ function withPrerequisites(entry, prerequisites) {
18
+ if (entry.reach !== "url" || entry.status !== "ready") return entry;
19
+ if (!prerequisites.hasSecret) return {
20
+ ...entry,
21
+ status: { unavailable: NO_SECRET_REASON }
22
+ };
23
+ if (prerequisites.publicOrigin === void 0) return {
24
+ ...entry,
25
+ status: { unavailable: NO_ORIGIN_REASON }
26
+ };
27
+ return entry;
28
+ }
29
+ //#endregion
30
+ export { NO_ORIGIN_REASON, NO_SECRET_REASON, offeredTargets };
31
+
32
+ //# sourceMappingURL=targets.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"targets.mjs","names":[],"sources":["../../src/capture/targets.ts"],"sourcesContent":["/** What a runner offers right now: its declared targets, minus the ones this deployment cannot reach. */\nimport type { CaptureRunner, RunnerTarget } from \"../adapters/capture-runner.ts\";\n\n/** Why a `reach: \"url\"` target is unavailable without a secret (the first words are matched by tests and docs). */\nexport const NO_SECRET_REASON =\n \"no secret: set the server secret, it signs the Storybook URL the remote browser opens\";\n\n/** Why it is unavailable without a confirmed public origin. */\nexport const NO_ORIGIN_REASON =\n \"no public origin: set PUBLIC_BASE_URL, or confirm the detected origin on the System page\";\n\n/** What a target that needs a URL depends on. */\nexport interface TargetPrerequisites {\n /** The confirmed public origin (ADR 0007, decision 6); absent while none is confirmed. */\n publicOrigin: string | undefined;\n /** Whether the server has a `secret` to sign Storybook URLs with. */\n hasSecret: boolean;\n}\n\n/**\n * The runner's targets with `reach: \"url\"` ones marked `unavailable` while they lack a `secret` or a\n * public origin (ADR 0007), so a settings save refuses them and a build fails at once with the\n * reason. `undefined` when the runner publishes no list (it offers any target).\n */\nexport function offeredTargets(\n runner: Pick<CaptureRunner, \"metadata\">,\n prerequisites: TargetPrerequisites,\n): readonly RunnerTarget[] | undefined {\n return runner.metadata.targets?.map((entry) => withPrerequisites(entry, prerequisites));\n}\n\nfunction withPrerequisites(entry: RunnerTarget, prerequisites: TargetPrerequisites): RunnerTarget {\n if (entry.reach !== \"url\" || entry.status !== \"ready\") {\n return entry;\n }\n if (!prerequisites.hasSecret) {\n return { ...entry, status: { unavailable: NO_SECRET_REASON } };\n }\n if (prerequisites.publicOrigin === undefined) {\n return { ...entry, status: { unavailable: NO_ORIGIN_REASON } };\n }\n return entry;\n}\n"],"mappings":";;;;;AAIA,MAAa,mBACX;;AAGF,MAAa,mBACX;;;;;;AAeF,SAAgB,eACd,QACA,eACqC;CACrC,OAAO,OAAO,SAAS,SAAS,KAAK,UAAU,kBAAkB,OAAO,aAAa,CAAC;AACxF;AAEA,SAAS,kBAAkB,OAAqB,eAAkD;CAChG,IAAI,MAAM,UAAU,SAAS,MAAM,WAAW,SAC5C,OAAO;CAET,IAAI,CAAC,cAAc,WACjB,OAAO;EAAE,GAAG;EAAO,QAAQ,EAAE,aAAa,iBAAiB;CAAE;CAE/D,IAAI,cAAc,iBAAiB,KAAA,GACjC,OAAO;EAAE,GAAG;EAAO,QAAQ,EAAE,aAAa,iBAAiB;CAAE;CAE/D,OAAO;AACT"}