@storyshelf/core 0.6.2 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (727) hide show
  1. package/README.md +49 -32
  2. package/dist/adapter/capture-queue.d.mts +44 -4
  3. package/dist/adapter/capture-runner.d.mts +134 -12
  4. package/dist/adapter/capture-runner.mjs +9 -12
  5. package/dist/adapter/database.d.mts +89 -10
  6. package/dist/adapter/database.mjs +4 -3
  7. package/dist/adapter/database.mjs.map +1 -1
  8. package/dist/adapter/design.d.mts +83 -0
  9. package/dist/adapter/design.mjs +27 -0
  10. package/dist/adapter/design.mjs.map +1 -0
  11. package/dist/adapter/email-sender.d.mts +0 -2
  12. package/dist/adapter/metadata.d.mts +3 -12
  13. package/dist/adapter/notifier.d.mts +6 -3
  14. package/dist/adapter/notifier.mjs +5 -1
  15. package/dist/adapter/oauth.d.mts +50 -0
  16. package/dist/adapter/setup.d.mts +4 -7
  17. package/dist/adapter/setup.mjs +3 -3
  18. package/dist/adapter/setup.mjs.map +1 -1
  19. package/dist/adapter/storage.d.mts +1 -3
  20. package/dist/adapter/webhook-events.d.mts +0 -2
  21. package/dist/adapter/webhook-events.mjs +13 -14
  22. package/dist/adapter/webhook-events.mjs.map +1 -1
  23. package/dist/adapters/capture-target-offers.d.mts +26 -0
  24. package/dist/adapters/capture-target-offers.mjs +57 -0
  25. package/dist/adapters/capture-target-offers.mjs.map +1 -0
  26. package/dist/adapters/capture-target.d.mts +69 -0
  27. package/dist/adapters/capture-target.mjs +75 -0
  28. package/dist/adapters/capture-target.mjs.map +1 -0
  29. package/dist/adapters/notifier/build-comment.d.mts +22 -0
  30. package/dist/adapters/notifier/build-comment.mjs +23 -0
  31. package/dist/adapters/notifier/build-comment.mjs.map +1 -0
  32. package/dist/adapters/notifier/build-state.d.mts +97 -0
  33. package/dist/adapters/notifier/build-state.mjs +217 -0
  34. package/dist/adapters/notifier/build-state.mjs.map +1 -0
  35. package/dist/adapters/notifier/events.d.mts +4 -4
  36. package/dist/adapters/notifier/events.mjs +32 -20
  37. package/dist/adapters/notifier/events.mjs.map +1 -1
  38. package/dist/adapters/notifier/format.d.mts +0 -2
  39. package/dist/adapters/notifier/format.mjs +1 -2
  40. package/dist/adapters/notifier/provider.d.mts +53 -9
  41. package/dist/adapters/notifier/provider.mjs +11 -0
  42. package/dist/adapters/notifier/provider.mjs.map +1 -0
  43. package/dist/adapters/notifier/state.d.mts +22 -0
  44. package/dist/adapters/notifier/state.mjs +65 -0
  45. package/dist/adapters/notifier/state.mjs.map +1 -0
  46. package/dist/adapters/notifier/types.d.mts +51 -4
  47. package/dist/adapters/remote-browser.d.mts +168 -0
  48. package/dist/adapters/remote-browser.mjs +201 -0
  49. package/dist/adapters/remote-browser.mjs.map +1 -0
  50. package/dist/adapters/remote-lease-attempt.mjs +39 -0
  51. package/dist/adapters/remote-lease-attempt.mjs.map +1 -0
  52. package/dist/adapters/remote-lease-error.d.mts +25 -0
  53. package/dist/adapters/remote-lease-error.mjs +54 -0
  54. package/dist/adapters/remote-lease-error.mjs.map +1 -0
  55. package/dist/adapters/remote-lease-held.d.mts +15 -0
  56. package/dist/adapters/remote-lease-held.mjs +86 -0
  57. package/dist/adapters/remote-lease-held.mjs.map +1 -0
  58. package/dist/adapters/remote-lease-shutdown.d.mts +12 -0
  59. package/dist/adapters/remote-lease-shutdown.mjs +32 -0
  60. package/dist/adapters/remote-lease-shutdown.mjs.map +1 -0
  61. package/dist/adapters/remote-lease.d.mts +39 -0
  62. package/dist/adapters/remote-lease.mjs +175 -0
  63. package/dist/adapters/remote-lease.mjs.map +1 -0
  64. package/dist/adapters/remote-limiter.mjs +121 -0
  65. package/dist/adapters/remote-limiter.mjs.map +1 -0
  66. package/dist/adapters/remote-pool.d.mts +56 -0
  67. package/dist/adapters/remote-pool.mjs +195 -0
  68. package/dist/adapters/remote-pool.mjs.map +1 -0
  69. package/dist/adapters/render-runs.d.mts +23 -0
  70. package/dist/adapters/render-runs.mjs +40 -0
  71. package/dist/adapters/render-runs.mjs.map +1 -0
  72. package/dist/adapters/setup-runner.d.mts +0 -2
  73. package/dist/adapters/setup-runner.mjs +3 -6
  74. package/dist/adapters/setup-runner.mjs.map +1 -1
  75. package/dist/adapters/sources.d.mts +10 -0
  76. package/dist/adapters/sources.mjs +19 -0
  77. package/dist/adapters/sources.mjs.map +1 -0
  78. package/dist/adapters/validate.d.mts +0 -2
  79. package/dist/adapters/validate.mjs +27 -20
  80. package/dist/adapters/validate.mjs.map +1 -1
  81. package/dist/ai.d.mts +103 -0
  82. package/dist/ai.mjs +18 -0
  83. package/dist/ai.mjs.map +1 -0
  84. package/dist/auth.d.mts +0 -2
  85. package/dist/auth.mjs +1 -2
  86. package/dist/brand-packs.d.mts +0 -2
  87. package/dist/brand-packs.mjs +0 -1
  88. package/dist/capture/a11y/axe-source.d.mts +16 -0
  89. package/dist/capture/a11y/axe-source.mjs +36 -0
  90. package/dist/capture/a11y/axe-source.mjs.map +1 -0
  91. package/dist/capture/a11y/in-page.d.mts +25 -0
  92. package/dist/capture/a11y/in-page.mjs +59 -0
  93. package/dist/capture/a11y/in-page.mjs.map +1 -0
  94. package/dist/capture/a11y/map.d.mts +22 -0
  95. package/dist/capture/a11y/map.mjs +47 -0
  96. package/dist/capture/a11y/map.mjs.map +1 -0
  97. package/dist/capture/a11y/options.d.mts +21 -0
  98. package/dist/capture/a11y/options.mjs +74 -0
  99. package/dist/capture/a11y/options.mjs.map +1 -0
  100. package/dist/capture/adapter.d.mts +46 -5
  101. package/dist/capture/adapter.mjs +1 -2
  102. package/dist/capture/adapter.mjs.map +1 -1
  103. package/dist/capture/affected.d.mts +2 -2
  104. package/dist/capture/affected.mjs +14 -6
  105. package/dist/capture/affected.mjs.map +1 -1
  106. package/dist/capture/attempt-logger.mjs +83 -0
  107. package/dist/capture/attempt-logger.mjs.map +1 -0
  108. package/dist/capture/browsers.d.mts +33 -0
  109. package/dist/capture/browsers.mjs +66 -0
  110. package/dist/capture/browsers.mjs.map +1 -0
  111. package/dist/capture/build-state/deliver.d.mts +30 -0
  112. package/dist/capture/build-state/deliver.mjs +117 -0
  113. package/dist/capture/build-state/deliver.mjs.map +1 -0
  114. package/dist/capture/build-state/index.d.mts +8 -0
  115. package/dist/capture/build-state/notify-job.d.mts +54 -0
  116. package/dist/capture/build-state/notify-job.mjs +132 -0
  117. package/dist/capture/build-state/notify-job.mjs.map +1 -0
  118. package/dist/capture/build-state/publisher.d.mts +21 -0
  119. package/dist/capture/build-state/publisher.mjs +106 -0
  120. package/dist/capture/build-state/publisher.mjs.map +1 -0
  121. package/dist/capture/build-state/resume.d.mts +7 -0
  122. package/dist/capture/build-state/resume.mjs +46 -0
  123. package/dist/capture/build-state/resume.mjs.map +1 -0
  124. package/dist/capture/build-state/types.d.mts +47 -0
  125. package/dist/capture/catalog/catalogue.d.mts +20 -0
  126. package/dist/capture/catalog/catalogue.mjs +52 -0
  127. package/dist/capture/catalog/catalogue.mjs.map +1 -0
  128. package/dist/capture/catalog/components.d.mts +21 -0
  129. package/dist/capture/catalog/components.mjs +54 -0
  130. package/dist/capture/catalog/components.mjs.map +1 -0
  131. package/dist/capture/catalog/history.d.mts +28 -0
  132. package/dist/capture/catalog/history.mjs +50 -0
  133. package/dist/capture/catalog/history.mjs.map +1 -0
  134. package/dist/capture/catalog/hook.d.mts +12 -0
  135. package/dist/capture/catalog/hook.mjs +18 -0
  136. package/dist/capture/catalog/hook.mjs.map +1 -0
  137. package/dist/capture/catalog/index.d.mts +11 -0
  138. package/dist/capture/catalog/load.d.mts +24 -0
  139. package/dist/capture/catalog/load.mjs +48 -0
  140. package/dist/capture/catalog/load.mjs.map +1 -0
  141. package/dist/capture/catalog/parse.d.mts +13 -0
  142. package/dist/capture/catalog/parse.mjs +55 -0
  143. package/dist/capture/catalog/parse.mjs.map +1 -0
  144. package/dist/capture/catalog/read.d.mts +15 -0
  145. package/dist/capture/catalog/read.mjs +39 -0
  146. package/dist/capture/catalog/read.mjs.map +1 -0
  147. package/dist/capture/catalog/types.d.mts +31 -0
  148. package/dist/capture/comparison-reference.d.mts +27 -0
  149. package/dist/capture/comparison-reference.mjs +75 -0
  150. package/dist/capture/comparison-reference.mjs.map +1 -0
  151. package/dist/capture/consumer/capture-handler.d.mts +56 -0
  152. package/dist/capture/consumer/capture-handler.mjs +117 -0
  153. package/dist/capture/consumer/capture-handler.mjs.map +1 -0
  154. package/dist/capture/consumer/idle.d.mts +15 -0
  155. package/dist/capture/consumer/idle.mjs +24 -0
  156. package/dist/capture/consumer/idle.mjs.map +1 -0
  157. package/dist/capture/consumer/index.d.mts +11 -0
  158. package/dist/capture/consumer/index.mjs +138 -0
  159. package/dist/capture/consumer/index.mjs.map +1 -0
  160. package/dist/capture/consumer/loop.mjs +109 -0
  161. package/dist/capture/consumer/loop.mjs.map +1 -0
  162. package/dist/capture/consumer/metrics.d.mts +39 -0
  163. package/dist/capture/consumer/metrics.mjs +37 -0
  164. package/dist/capture/consumer/metrics.mjs.map +1 -0
  165. package/dist/capture/consumer/retry.d.mts +31 -0
  166. package/dist/capture/consumer/retry.mjs +33 -0
  167. package/dist/capture/consumer/retry.mjs.map +1 -0
  168. package/dist/capture/consumer/run-job.mjs +194 -0
  169. package/dist/capture/consumer/run-job.mjs.map +1 -0
  170. package/dist/capture/consumer/startup.mjs +37 -0
  171. package/dist/capture/consumer/startup.mjs.map +1 -0
  172. package/dist/capture/consumer/types.d.mts +95 -0
  173. package/dist/capture/crash-failure.d.mts +23 -0
  174. package/dist/capture/crash-failure.mjs +69 -0
  175. package/dist/capture/crash-failure.mjs.map +1 -0
  176. package/dist/capture/db-queue/cadence.d.mts +30 -0
  177. package/dist/capture/db-queue/cadence.mjs +37 -0
  178. package/dist/capture/db-queue/cadence.mjs.map +1 -0
  179. package/dist/capture/db-queue/claim.d.mts +7 -0
  180. package/dist/capture/db-queue/claim.mjs +148 -0
  181. package/dist/capture/db-queue/claim.mjs.map +1 -0
  182. package/dist/capture/db-queue/clock.mjs +11 -0
  183. package/dist/capture/db-queue/clock.mjs.map +1 -0
  184. package/dist/capture/db-queue/codec.mjs +75 -0
  185. package/dist/capture/db-queue/codec.mjs.map +1 -0
  186. package/dist/capture/db-queue/index.d.mts +18 -0
  187. package/dist/capture/db-queue/index.mjs +89 -0
  188. package/dist/capture/db-queue/index.mjs.map +1 -0
  189. package/dist/capture/db-queue/keeper.d.mts +34 -0
  190. package/dist/capture/db-queue/keeper.mjs +91 -0
  191. package/dist/capture/db-queue/keeper.mjs.map +1 -0
  192. package/dist/capture/db-queue/lease.mjs +45 -0
  193. package/dist/capture/db-queue/lease.mjs.map +1 -0
  194. package/dist/capture/db-queue/lifecycle.mjs +35 -0
  195. package/dist/capture/db-queue/lifecycle.mjs.map +1 -0
  196. package/dist/capture/db-queue/operations.mjs +93 -0
  197. package/dist/capture/db-queue/operations.mjs.map +1 -0
  198. package/dist/capture/db-queue/poll-loop.d.mts +28 -0
  199. package/dist/capture/db-queue/poll-loop.mjs +88 -0
  200. package/dist/capture/db-queue/poll-loop.mjs.map +1 -0
  201. package/dist/capture/db-queue/poll.d.mts +8 -0
  202. package/dist/capture/db-queue/poll.mjs +68 -0
  203. package/dist/capture/db-queue/poll.mjs.map +1 -0
  204. package/dist/capture/db-queue/timers.d.mts +10 -0
  205. package/dist/capture/db-queue/timers.mjs +36 -0
  206. package/dist/capture/db-queue/timers.mjs.map +1 -0
  207. package/dist/capture/db-queue/types.d.mts +81 -0
  208. package/dist/capture/db-queue/types.mjs +11 -0
  209. package/dist/capture/db-queue/types.mjs.map +1 -0
  210. package/dist/capture/db-queue/wake.mjs +47 -0
  211. package/dist/capture/db-queue/wake.mjs.map +1 -0
  212. package/dist/capture/design.mjs +38 -0
  213. package/dist/capture/design.mjs.map +1 -0
  214. package/dist/capture/diff-record.mjs +24 -5
  215. package/dist/capture/diff-record.mjs.map +1 -1
  216. package/dist/capture/dispatch.d.mts +13 -10
  217. package/dist/capture/dispatch.mjs +102 -111
  218. package/dist/capture/dispatch.mjs.map +1 -1
  219. package/dist/capture/enrich.mjs +59 -0
  220. package/dist/capture/enrich.mjs.map +1 -0
  221. package/dist/capture/failure-sets.mjs +21 -0
  222. package/dist/capture/failure-sets.mjs.map +1 -0
  223. package/dist/capture/fingerprint.d.mts +40 -0
  224. package/dist/capture/fingerprint.mjs +82 -0
  225. package/dist/capture/fingerprint.mjs.map +1 -0
  226. package/dist/capture/forked/channel.d.mts +23 -0
  227. package/dist/capture/forked/child.d.mts +32 -0
  228. package/dist/capture/forked/child.mjs +201 -0
  229. package/dist/capture/forked/child.mjs.map +1 -0
  230. package/dist/capture/forked/crash-tracker.mjs +22 -0
  231. package/dist/capture/forked/crash-tracker.mjs.map +1 -0
  232. package/dist/capture/forked/decision.d.mts +24 -0
  233. package/dist/capture/forked/decision.mjs +26 -0
  234. package/dist/capture/forked/decision.mjs.map +1 -0
  235. package/dist/capture/forked/exit.mjs +11 -0
  236. package/dist/capture/forked/exit.mjs.map +1 -0
  237. package/dist/capture/forked/fork.d.mts +29 -0
  238. package/dist/capture/forked/fork.mjs +92 -0
  239. package/dist/capture/forked/fork.mjs.map +1 -0
  240. package/dist/capture/forked/forked-capture.d.mts +38 -0
  241. package/dist/capture/forked/forked-capture.mjs +239 -0
  242. package/dist/capture/forked/forked-capture.mjs.map +1 -0
  243. package/dist/capture/forked/lease-book.mjs +45 -0
  244. package/dist/capture/forked/lease-book.mjs.map +1 -0
  245. package/dist/capture/forked/protocol.d.mts +36 -0
  246. package/dist/capture/forked/protocol.mjs +24 -0
  247. package/dist/capture/forked/protocol.mjs.map +1 -0
  248. package/dist/capture/forked/runs.mjs +74 -0
  249. package/dist/capture/forked/runs.mjs.map +1 -0
  250. package/dist/capture/guards.mjs +1 -2
  251. package/dist/capture/ignore-regions.d.mts +36 -0
  252. package/dist/capture/ignore-regions.mjs +65 -0
  253. package/dist/capture/ignore-regions.mjs.map +1 -0
  254. package/dist/capture/isolate.mjs +140 -0
  255. package/dist/capture/isolate.mjs.map +1 -0
  256. package/dist/capture/job-lease.d.mts +26 -0
  257. package/dist/capture/job-lease.mjs +56 -0
  258. package/dist/capture/job-lease.mjs.map +1 -0
  259. package/dist/capture/matrix.mjs +298 -0
  260. package/dist/capture/matrix.mjs.map +1 -0
  261. package/dist/capture/modes.d.mts +23 -0
  262. package/dist/capture/modes.mjs +106 -0
  263. package/dist/capture/modes.mjs.map +1 -0
  264. package/dist/capture/orchestrator.d.mts +37 -7
  265. package/dist/capture/orchestrator.mjs +120 -110
  266. package/dist/capture/orchestrator.mjs.map +1 -1
  267. package/dist/capture/pipeline.d.mts +9 -4
  268. package/dist/capture/pipeline.mjs +284 -48
  269. package/dist/capture/pipeline.mjs.map +1 -1
  270. package/dist/capture/progress.d.mts +9 -0
  271. package/dist/capture/progress.mjs +27 -0
  272. package/dist/capture/progress.mjs.map +1 -0
  273. package/dist/capture/publish/accept.d.mts +37 -0
  274. package/dist/capture/publish/accept.mjs +27 -0
  275. package/dist/capture/publish/accept.mjs.map +1 -0
  276. package/dist/capture/publish/complete.d.mts +44 -0
  277. package/dist/capture/publish/complete.mjs +63 -0
  278. package/dist/capture/publish/complete.mjs.map +1 -0
  279. package/dist/capture/publish/extract.d.mts +18 -0
  280. package/dist/capture/publish/extract.mjs +120 -0
  281. package/dist/capture/publish/extract.mjs.map +1 -0
  282. package/dist/capture/publish/handler.d.mts +26 -0
  283. package/dist/capture/publish/handler.mjs +61 -0
  284. package/dist/capture/publish/handler.mjs.map +1 -0
  285. package/dist/capture/publish/index.d.mts +11 -0
  286. package/dist/capture/publish/invalid-zip.d.mts +8 -0
  287. package/dist/capture/publish/invalid-zip.mjs +11 -0
  288. package/dist/capture/publish/invalid-zip.mjs.map +1 -0
  289. package/dist/capture/publish/limits.d.mts +35 -0
  290. package/dist/capture/publish/limits.mjs +38 -0
  291. package/dist/capture/publish/limits.mjs.map +1 -0
  292. package/dist/capture/publish/published.d.mts +13 -0
  293. package/dist/capture/publish/published.mjs +17 -0
  294. package/dist/capture/publish/published.mjs.map +1 -0
  295. package/dist/capture/publish/run.d.mts +31 -0
  296. package/dist/capture/publish/run.mjs +29 -0
  297. package/dist/capture/publish/run.mjs.map +1 -0
  298. package/dist/capture/rediff.d.mts +1 -3
  299. package/dist/capture/rediff.mjs +23 -9
  300. package/dist/capture/rediff.mjs.map +1 -1
  301. package/dist/capture/render.mjs +52 -0
  302. package/dist/capture/render.mjs.map +1 -0
  303. package/dist/capture/scratch.d.mts +42 -0
  304. package/dist/capture/scratch.mjs +136 -0
  305. package/dist/capture/scratch.mjs.map +1 -0
  306. package/dist/capture/shard-plan.mjs +127 -0
  307. package/dist/capture/shard-plan.mjs.map +1 -0
  308. package/dist/capture/shards/context.mjs +51 -0
  309. package/dist/capture/shards/context.mjs.map +1 -0
  310. package/dist/capture/shards/execute.mjs +175 -0
  311. package/dist/capture/shards/execute.mjs.map +1 -0
  312. package/dist/capture/shards/fan-out.mjs +125 -0
  313. package/dist/capture/shards/fan-out.mjs.map +1 -0
  314. package/dist/capture/shards/history.mjs +34 -0
  315. package/dist/capture/shards/history.mjs.map +1 -0
  316. package/dist/capture/shards/reduce.mjs +96 -0
  317. package/dist/capture/shards/reduce.mjs.map +1 -0
  318. package/dist/capture/sizing.d.mts +0 -2
  319. package/dist/capture/sizing.mjs +2 -13
  320. package/dist/capture/sizing.mjs.map +1 -1
  321. package/dist/capture/skip-checks.mjs +3 -67
  322. package/dist/capture/skip-checks.mjs.map +1 -1
  323. package/dist/capture/statics.d.mts +7 -10
  324. package/dist/capture/statics.mjs +26 -74
  325. package/dist/capture/statics.mjs.map +1 -1
  326. package/dist/capture/storybook-urls.d.mts +38 -0
  327. package/dist/capture/storybook-urls.mjs +41 -0
  328. package/dist/capture/storybook-urls.mjs.map +1 -0
  329. package/dist/capture/storybook.d.mts +12 -6
  330. package/dist/capture/storybook.mjs +63 -19
  331. package/dist/capture/storybook.mjs.map +1 -1
  332. package/dist/capture/target.mjs +20 -0
  333. package/dist/capture/target.mjs.map +1 -0
  334. package/dist/capture/targets.d.mts +23 -0
  335. package/dist/capture/targets.mjs +31 -0
  336. package/dist/capture/targets.mjs.map +1 -0
  337. package/dist/capture/tests.d.mts +35 -0
  338. package/dist/capture/tests.mjs +136 -0
  339. package/dist/capture/tests.mjs.map +1 -0
  340. package/dist/capture/token-chunks.mjs +91 -0
  341. package/dist/capture/token-chunks.mjs.map +1 -0
  342. package/dist/capture.d.mts +63 -7
  343. package/dist/capture.mjs +51 -3
  344. package/dist/config.d.mts +136 -14
  345. package/dist/config.mjs +14 -8
  346. package/dist/config.mjs.map +1 -1
  347. package/dist/diff.d.mts +7 -3
  348. package/dist/diff.mjs +15 -3
  349. package/dist/diff.mjs.map +1 -0
  350. package/dist/index.d.mts +3 -3
  351. package/dist/index.mjs +2 -2
  352. package/dist/insights/budget.d.mts +18 -0
  353. package/dist/insights/budget.mjs +32 -0
  354. package/dist/insights/budget.mjs.map +1 -0
  355. package/dist/insights/envelope.d.mts +57 -0
  356. package/dist/insights/envelope.mjs +59 -0
  357. package/dist/insights/envelope.mjs.map +1 -0
  358. package/dist/insights/evidence-health.d.mts +30 -0
  359. package/dist/insights/evidence-health.mjs +27 -0
  360. package/dist/insights/evidence-health.mjs.map +1 -0
  361. package/dist/insights/evidence.d.mts +53 -0
  362. package/dist/insights/evidence.mjs +63 -0
  363. package/dist/insights/evidence.mjs.map +1 -0
  364. package/dist/insights/hash.d.mts +16 -0
  365. package/dist/insights/hash.mjs +20 -0
  366. package/dist/insights/hash.mjs.map +1 -0
  367. package/dist/insights/limits.d.mts +22 -0
  368. package/dist/insights/limits.mjs +23 -0
  369. package/dist/insights/limits.mjs.map +1 -0
  370. package/dist/insights/prompts/v1.d.mts +11 -0
  371. package/dist/insights/prompts/v1.mjs +33 -0
  372. package/dist/insights/prompts/v1.mjs.map +1 -0
  373. package/dist/insights/redact.d.mts +7 -0
  374. package/dist/insights/redact.mjs +23 -0
  375. package/dist/insights/redact.mjs.map +1 -0
  376. package/dist/insights/text.mjs +43 -0
  377. package/dist/insights/text.mjs.map +1 -0
  378. package/dist/insights.d.mts +11 -0
  379. package/dist/insights.mjs +12 -0
  380. package/dist/logger.d.mts +0 -2
  381. package/dist/logger.mjs +18 -3
  382. package/dist/logger.mjs.map +1 -1
  383. package/dist/models/ai-budget-alert.d.mts +21 -0
  384. package/dist/models/ai-budget-alert.mjs +37 -0
  385. package/dist/models/ai-budget-alert.mjs.map +1 -0
  386. package/dist/models/ai-usage.d.mts +38 -0
  387. package/dist/models/ai-usage.mjs +60 -0
  388. package/dist/models/ai-usage.mjs.map +1 -0
  389. package/dist/models/baseline-approval.d.mts +18 -0
  390. package/dist/models/baseline-approval.mjs +41 -0
  391. package/dist/models/baseline-approval.mjs.map +1 -0
  392. package/dist/models/baseline-archive.d.mts +82 -0
  393. package/dist/models/baseline-archive.mjs +105 -0
  394. package/dist/models/baseline-archive.mjs.map +1 -0
  395. package/dist/models/baseline-atomic.mjs +66 -0
  396. package/dist/models/baseline-atomic.mjs.map +1 -0
  397. package/dist/models/baseline-conflict.d.mts +6 -0
  398. package/dist/models/baseline-conflict.mjs +9 -0
  399. package/dist/models/baseline-conflict.mjs.map +1 -0
  400. package/dist/models/baseline-export.d.mts +17 -0
  401. package/dist/models/baseline-export.mjs +135 -0
  402. package/dist/models/baseline-export.mjs.map +1 -0
  403. package/dist/models/baseline-import.mjs +117 -0
  404. package/dist/models/baseline-import.mjs.map +1 -0
  405. package/dist/models/baseline-origin.d.mts +16 -0
  406. package/dist/models/baseline-origin.mjs +35 -0
  407. package/dist/models/baseline-origin.mjs.map +1 -0
  408. package/dist/models/baseline-status.d.mts +0 -2
  409. package/dist/models/baseline-status.mjs +1 -2
  410. package/dist/models/baseline-transfer.d.mts +48 -0
  411. package/dist/models/baseline-transfer.mjs +149 -0
  412. package/dist/models/baseline-transfer.mjs.map +1 -0
  413. package/dist/models/baseline.d.mts +37 -7
  414. package/dist/models/baseline.mjs +152 -28
  415. package/dist/models/baseline.mjs.map +1 -1
  416. package/dist/models/build-component.d.mts +26 -0
  417. package/dist/models/build-component.mjs +54 -0
  418. package/dist/models/build-component.mjs.map +1 -0
  419. package/dist/models/build-review-status.d.mts +27 -0
  420. package/dist/models/build-review-status.mjs +36 -0
  421. package/dist/models/build-review-status.mjs.map +1 -0
  422. package/dist/models/build-review.d.mts +26 -0
  423. package/dist/models/build-review.mjs +53 -0
  424. package/dist/models/build-review.mjs.map +1 -0
  425. package/dist/models/build.d.mts +25 -5
  426. package/dist/models/build.mjs +53 -15
  427. package/dist/models/build.mjs.map +1 -1
  428. package/dist/models/capture-attempt.d.mts +7 -3
  429. package/dist/models/capture-attempt.mjs +30 -6
  430. package/dist/models/capture-attempt.mjs.map +1 -1
  431. package/dist/models/capture-job.d.mts +104 -0
  432. package/dist/models/capture-job.mjs +287 -0
  433. package/dist/models/capture-job.mjs.map +1 -0
  434. package/dist/models/capture-log.d.mts +1 -3
  435. package/dist/models/capture-log.mjs +5 -5
  436. package/dist/models/capture-log.mjs.map +1 -1
  437. package/dist/models/capture-shard.d.mts +60 -0
  438. package/dist/models/capture-shard.mjs +189 -0
  439. package/dist/models/capture-shard.mjs.map +1 -0
  440. package/dist/models/changeset.d.mts +46 -0
  441. package/dist/models/changeset.mjs +80 -0
  442. package/dist/models/changeset.mjs.map +1 -0
  443. package/dist/models/columns.mjs +18 -0
  444. package/dist/models/columns.mjs.map +1 -0
  445. package/dist/models/comment.d.mts +4 -3
  446. package/dist/models/comment.mjs +8 -6
  447. package/dist/models/comment.mjs.map +1 -1
  448. package/dist/models/component.d.mts +41 -0
  449. package/dist/models/component.mjs +59 -0
  450. package/dist/models/component.mjs.map +1 -0
  451. package/dist/models/connections.d.mts +39 -0
  452. package/dist/models/connections.mjs +111 -0
  453. package/dist/models/connections.mjs.map +1 -0
  454. package/dist/models/content-ref.d.mts +1 -3
  455. package/dist/models/content-ref.mjs +2 -3
  456. package/dist/models/content-ref.mjs.map +1 -1
  457. package/dist/models/credentials.d.mts +0 -2
  458. package/dist/models/credentials.mjs +13 -4
  459. package/dist/models/credentials.mjs.map +1 -1
  460. package/dist/models/design-credentials.d.mts +39 -0
  461. package/dist/models/design-credentials.mjs +85 -0
  462. package/dist/models/design-credentials.mjs.map +1 -0
  463. package/dist/models/design-health.d.mts +14 -0
  464. package/dist/models/design-health.mjs +24 -0
  465. package/dist/models/design-health.mjs.map +1 -0
  466. package/dist/models/design-nodes.d.mts +81 -0
  467. package/dist/models/design-nodes.mjs +221 -0
  468. package/dist/models/design-nodes.mjs.map +1 -0
  469. package/dist/models/design-renders.mjs +73 -0
  470. package/dist/models/design-renders.mjs.map +1 -0
  471. package/dist/models/design-resolve.d.mts +27 -0
  472. package/dist/models/design-resolve.mjs +40 -0
  473. package/dist/models/design-resolve.mjs.map +1 -0
  474. package/dist/models/design.d.mts +37 -0
  475. package/dist/models/design.mjs +89 -0
  476. package/dist/models/design.mjs.map +1 -0
  477. package/dist/models/inherited-ratio.d.mts +19 -0
  478. package/dist/models/inherited-ratio.mjs +21 -0
  479. package/dist/models/inherited-ratio.mjs.map +1 -0
  480. package/dist/models/insight.d.mts +68 -0
  481. package/dist/models/insight.mjs +161 -0
  482. package/dist/models/insight.mjs.map +1 -0
  483. package/dist/models/label.d.mts +4 -4
  484. package/dist/models/label.mjs +26 -12
  485. package/dist/models/label.mjs.map +1 -1
  486. package/dist/models/member.d.mts +1 -3
  487. package/dist/models/member.mjs +6 -6
  488. package/dist/models/member.mjs.map +1 -1
  489. package/dist/models/notification-channel.d.mts +37 -5
  490. package/dist/models/notification-channel.mjs +89 -11
  491. package/dist/models/notification-channel.mjs.map +1 -1
  492. package/dist/models/notification-subscription.d.mts +1 -4
  493. package/dist/models/notification-subscription.mjs +8 -11
  494. package/dist/models/notification-subscription.mjs.map +1 -1
  495. package/dist/models/project-group-mapping.d.mts +6 -3
  496. package/dist/models/project-group-mapping.mjs +15 -6
  497. package/dist/models/project-group-mapping.mjs.map +1 -1
  498. package/dist/models/project.d.mts +9 -3
  499. package/dist/models/project.mjs +10 -6
  500. package/dist/models/project.mjs.map +1 -1
  501. package/dist/models/public-origin.d.mts +61 -0
  502. package/dist/models/public-origin.mjs +113 -0
  503. package/dist/models/public-origin.mjs.map +1 -0
  504. package/dist/models/review-switch-off.d.mts +12 -0
  505. package/dist/models/review-switch-off.mjs +25 -0
  506. package/dist/models/review-switch-off.mjs.map +1 -0
  507. package/dist/models/site-settings.d.mts +21 -0
  508. package/dist/models/site-settings.mjs +47 -0
  509. package/dist/models/site-settings.mjs.map +1 -0
  510. package/dist/models/snapshot.d.mts +39 -3
  511. package/dist/models/snapshot.mjs +54 -5
  512. package/dist/models/snapshot.mjs.map +1 -1
  513. package/dist/models/token.d.mts +13 -3
  514. package/dist/models/token.mjs +24 -7
  515. package/dist/models/token.mjs.map +1 -1
  516. package/dist/models/usage.d.mts +74 -0
  517. package/dist/models/usage.mjs +148 -0
  518. package/dist/models/usage.mjs.map +1 -0
  519. package/dist/models/user.d.mts +1 -3
  520. package/dist/models/user.mjs +5 -5
  521. package/dist/models/user.mjs.map +1 -1
  522. package/dist/models/webhook.d.mts +15 -3
  523. package/dist/models/webhook.mjs +34 -6
  524. package/dist/models/webhook.mjs.map +1 -1
  525. package/dist/models.d.mts +30 -4
  526. package/dist/models.mjs +31 -6
  527. package/dist/options.d.mts +16 -0
  528. package/dist/options.mjs +88 -0
  529. package/dist/options.mjs.map +1 -0
  530. package/dist/paths.d.mts +21 -6
  531. package/dist/paths.mjs +57 -12
  532. package/dist/paths.mjs.map +1 -1
  533. package/dist/retention/ai.d.mts +16 -0
  534. package/dist/retention/ai.mjs +22 -0
  535. package/dist/retention/ai.mjs.map +1 -0
  536. package/dist/retention/pending.mjs +46 -0
  537. package/dist/retention/pending.mjs.map +1 -0
  538. package/dist/retention/published-gzip-sweep.mjs +28 -0
  539. package/dist/retention/published-gzip-sweep.mjs.map +1 -0
  540. package/dist/retention/screenshot-sweep.d.mts +49 -0
  541. package/dist/retention/screenshot-sweep.mjs +127 -0
  542. package/dist/retention/screenshot-sweep.mjs.map +1 -0
  543. package/dist/retention.d.mts +16 -4
  544. package/dist/retention.mjs +76 -16
  545. package/dist/retention.mjs.map +1 -1
  546. package/dist/schema/baseline.d.mts +8 -2
  547. package/dist/schema/build-component.d.mts +23 -0
  548. package/dist/schema/build-review.d.mts +15 -0
  549. package/dist/schema/build.d.mts +7 -2
  550. package/dist/schema/capture-attempt.d.mts +0 -2
  551. package/dist/schema/capture-job.d.mts +58 -0
  552. package/dist/schema/capture-shard.d.mts +33 -0
  553. package/dist/schema/comment.d.mts +2 -2
  554. package/dist/schema/connection.d.mts +22 -0
  555. package/dist/schema/content-ref.d.mts +0 -2
  556. package/dist/schema/design.d.mts +56 -0
  557. package/dist/schema/insight.d.mts +52 -0
  558. package/dist/schema/label.d.mts +0 -2
  559. package/dist/schema/member.d.mts +0 -2
  560. package/dist/schema/notification.d.mts +4 -2
  561. package/dist/schema/project-group-mapping.d.mts +0 -2
  562. package/dist/schema/project.d.mts +21 -2
  563. package/dist/schema/site-setting.d.mts +19 -0
  564. package/dist/schema/snapshot.d.mts +17 -2
  565. package/dist/schema/token.d.mts +0 -2
  566. package/dist/schema/usage.d.mts +28 -0
  567. package/dist/schema/user.d.mts +0 -2
  568. package/dist/schema/webhook.d.mts +0 -2
  569. package/dist/schema.d.mts +12 -6
  570. package/dist/serverless.d.mts +19 -0
  571. package/dist/serverless.mjs +36 -0
  572. package/dist/serverless.mjs.map +1 -0
  573. package/dist/test-helpers/build-reference-contract.d.mts +14 -0
  574. package/dist/test-helpers/build-reference-contract.mjs +66 -0
  575. package/dist/test-helpers/build-reference-contract.mjs.map +1 -0
  576. package/dist/test-helpers/create-project.d.mts +1 -3
  577. package/dist/test-helpers/create-project.mjs +1 -2
  578. package/dist/test-helpers/database-contract.d.mts +0 -2
  579. package/dist/test-helpers/database-contract.mjs +37 -2
  580. package/dist/test-helpers/database-contract.mjs.map +1 -1
  581. package/dist/test-helpers/deferred.d.mts +37 -0
  582. package/dist/test-helpers/deferred.mjs +60 -0
  583. package/dist/test-helpers/deferred.mjs.map +1 -0
  584. package/dist/test-helpers/design-contract.d.mts +19 -0
  585. package/dist/test-helpers/design-contract.mjs +39 -0
  586. package/dist/test-helpers/design-contract.mjs.map +1 -0
  587. package/dist/test-helpers/fake-adapters.d.mts +0 -2
  588. package/dist/test-helpers/fake-adapters.mjs +1 -2
  589. package/dist/test-helpers/fake-database.d.mts +0 -2
  590. package/dist/test-helpers/fake-database.mjs +21 -4
  591. package/dist/test-helpers/fake-database.mjs.map +1 -1
  592. package/dist/test-helpers/fake-design.d.mts +41 -0
  593. package/dist/test-helpers/fake-design.mjs +113 -0
  594. package/dist/test-helpers/fake-design.mjs.map +1 -0
  595. package/dist/test-helpers/fake-notifier.d.mts +19 -0
  596. package/dist/test-helpers/fake-notifier.mjs +43 -0
  597. package/dist/test-helpers/fake-notifier.mjs.map +1 -0
  598. package/dist/test-helpers/fake-storage.d.mts +0 -2
  599. package/dist/test-helpers/fake-storage.mjs +1 -2
  600. package/dist/test-helpers/fake-tables.mjs +260 -69
  601. package/dist/test-helpers/fake-tables.mjs.map +1 -1
  602. package/dist/test-helpers/manual-clock.d.mts +25 -0
  603. package/dist/test-helpers/manual-clock.mjs +55 -0
  604. package/dist/test-helpers/manual-clock.mjs.map +1 -0
  605. package/dist/test-helpers/notifier-contract.d.mts +7 -3
  606. package/dist/test-helpers/notifier-contract.mjs +33 -5
  607. package/dist/test-helpers/notifier-contract.mjs.map +1 -1
  608. package/dist/test-helpers/queue-contract.d.mts +16 -4
  609. package/dist/test-helpers/queue-contract.mjs +196 -3
  610. package/dist/test-helpers/queue-contract.mjs.map +1 -1
  611. package/dist/test-helpers/queue-multi-instance.d.mts +18 -0
  612. package/dist/test-helpers/queue-multi-instance.mjs +205 -0
  613. package/dist/test-helpers/queue-multi-instance.mjs.map +1 -0
  614. package/dist/test-helpers/runner-contract.d.mts +0 -2
  615. package/dist/test-helpers/runner-contract.mjs +12 -2
  616. package/dist/test-helpers/runner-contract.mjs.map +1 -1
  617. package/dist/test-helpers/sql-chunks.mjs +25 -2
  618. package/dist/test-helpers/sql-chunks.mjs.map +1 -1
  619. package/dist/test-helpers/storage-contract.d.mts +0 -2
  620. package/dist/test-helpers/storage-contract.mjs +2 -3
  621. package/dist/test-helpers.d.mts +10 -4
  622. package/dist/test-helpers.mjs +9 -3
  623. package/dist/tracing.mjs +36 -6
  624. package/dist/tracing.mjs.map +1 -1
  625. package/dist/types.d.mts +14 -5
  626. package/dist/types.mjs +22 -6
  627. package/dist/types.mjs.map +1 -1
  628. package/dist/urls.d.mts +4 -2
  629. package/dist/urls.mjs +12 -4
  630. package/dist/urls.mjs.map +1 -1
  631. package/dist/utils/adaptive-concurrency.d.mts +64 -0
  632. package/dist/utils/adaptive-concurrency.mjs +148 -0
  633. package/dist/utils/adaptive-concurrency.mjs.map +1 -0
  634. package/dist/utils/connect-retry.d.mts +26 -0
  635. package/dist/utils/connect-retry.mjs +42 -0
  636. package/dist/utils/connect-retry.mjs.map +1 -0
  637. package/dist/utils/encrypt.d.mts +0 -2
  638. package/dist/utils/encrypt.mjs +1 -2
  639. package/dist/utils/environment.d.mts +17 -0
  640. package/dist/utils/environment.mjs +58 -0
  641. package/dist/utils/environment.mjs.map +1 -0
  642. package/dist/utils/hash.d.mts +0 -2
  643. package/dist/utils/hash.mjs +1 -2
  644. package/dist/utils/http.d.mts +29 -3
  645. package/dist/utils/http.mjs +100 -19
  646. package/dist/utils/http.mjs.map +1 -1
  647. package/dist/utils/map-adaptive.d.mts +13 -0
  648. package/dist/utils/map-adaptive.mjs +71 -0
  649. package/dist/utils/map-adaptive.mjs.map +1 -0
  650. package/dist/utils/map-limit.d.mts +8 -0
  651. package/dist/utils/map-limit.mjs +31 -0
  652. package/dist/utils/map-limit.mjs.map +1 -0
  653. package/dist/utils/mime.d.mts +0 -2
  654. package/dist/utils/mime.mjs +1 -2
  655. package/dist/utils/page-limiter.d.mts +35 -0
  656. package/dist/utils/page-limiter.mjs +55 -0
  657. package/dist/utils/page-limiter.mjs.map +1 -0
  658. package/dist/utils/path-security.d.mts +0 -2
  659. package/dist/utils/path-security.mjs +2 -4
  660. package/dist/utils/path-security.mjs.map +1 -1
  661. package/dist/utils/public-origin.d.mts +18 -0
  662. package/dist/utils/public-origin.mjs +74 -0
  663. package/dist/utils/public-origin.mjs.map +1 -0
  664. package/dist/utils/redact.d.mts +0 -2
  665. package/dist/utils/redact.mjs +4 -2
  666. package/dist/utils/redact.mjs.map +1 -1
  667. package/dist/utils/remote-environment.d.mts +76 -0
  668. package/dist/utils/remote-environment.mjs +163 -0
  669. package/dist/utils/remote-environment.mjs.map +1 -0
  670. package/dist/utils/resources.d.mts +62 -0
  671. package/dist/utils/resources.mjs +121 -0
  672. package/dist/utils/resources.mjs.map +1 -0
  673. package/dist/utils/screenshot-store.d.mts +13 -0
  674. package/dist/utils/screenshot-store.mjs +40 -0
  675. package/dist/utils/screenshot-store.mjs.map +1 -0
  676. package/dist/utils/session-gate.d.mts +23 -0
  677. package/dist/utils/session-gate.mjs +69 -0
  678. package/dist/utils/session-gate.mjs.map +1 -0
  679. package/dist/utils/storybook-token.d.mts +89 -0
  680. package/dist/utils/storybook-token.mjs +135 -0
  681. package/dist/utils/storybook-token.mjs.map +1 -0
  682. package/dist/utils/storybook-url.d.mts +13 -0
  683. package/dist/utils/storybook-url.mjs +20 -0
  684. package/dist/utils/storybook-url.mjs.map +1 -0
  685. package/dist/utils/timing.d.mts +0 -2
  686. package/dist/utils/timing.mjs +2 -3
  687. package/dist/utils/timing.mjs.map +1 -1
  688. package/dist/utils/ulid.d.mts +0 -2
  689. package/dist/utils/ulid.mjs +1 -2
  690. package/dist/utils/zip-read.mjs +128 -0
  691. package/dist/utils/zip-read.mjs.map +1 -0
  692. package/dist/utils/zip-write.d.mts +16 -0
  693. package/dist/utils/zip-write.mjs +187 -0
  694. package/dist/utils/zip-write.mjs.map +1 -0
  695. package/dist/utils.d.mts +17 -3
  696. package/dist/utils.mjs +18 -4
  697. package/package.json +35 -26
  698. package/dist/adapter/capture-runner.mjs.map +0 -1
  699. package/dist/adapter/git-host/comments.d.mts +0 -41
  700. package/dist/adapter/git-host/comments.mjs +0 -54
  701. package/dist/adapter/git-host/comments.mjs.map +0 -1
  702. package/dist/adapter/git-host/helpers.d.mts +0 -13
  703. package/dist/adapter/git-host/helpers.mjs +0 -30
  704. package/dist/adapter/git-host/helpers.mjs.map +0 -1
  705. package/dist/adapter/git-host.d.mts +0 -42
  706. package/dist/capture/queue.d.mts +0 -38
  707. package/dist/capture/queue.mjs +0 -98
  708. package/dist/capture/queue.mjs.map +0 -1
  709. package/dist/capture/recovery.d.mts +0 -21
  710. package/dist/capture/recovery.mjs +0 -83
  711. package/dist/capture/recovery.mjs.map +0 -1
  712. package/dist/capture/status-fanout.mjs +0 -72
  713. package/dist/capture/status-fanout.mjs.map +0 -1
  714. package/dist/diff/engine.d.mts +0 -16
  715. package/dist/diff/engine.mjs +0 -87
  716. package/dist/diff/engine.mjs.map +0 -1
  717. package/dist/diff/options.d.mts +0 -42
  718. package/dist/diff/options.mjs +0 -15
  719. package/dist/diff/options.mjs.map +0 -1
  720. package/dist/models/status-config.d.mts +0 -38
  721. package/dist/models/status-config.mjs +0 -63
  722. package/dist/models/status-config.mjs.map +0 -1
  723. package/dist/schema/status-config.d.mts +0 -16
  724. package/dist/test-helpers/git-contract.d.mts +0 -11
  725. package/dist/test-helpers/git-contract.mjs +0 -34
  726. package/dist/test-helpers/git-contract.mjs.map +0 -1
  727. /package/dist/adapter/{git-host.mjs → oauth.mjs} +0 -0
@@ -0,0 +1,121 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { availableParallelism, totalmem } from "node:os";
5
+ import { readFileSync } from "node:fs";
6
+ // src/utils/resources.ts
7
+ const MB = 1048576;
8
+ /** Headroom for Node, the server and Chromium's browser process. */
9
+ const BASE_MEMORY_MB = 512;
10
+ /** Typical cost of one rendering page. */
11
+ const PAGE_MEMORY_MB = 300;
12
+ const MAX_PAGES = 4;
13
+ /** Ceiling for the adaptive controller, in Chromium-page slots. */
14
+ const MAX_ADAPTIVE_PAGES = 8;
15
+ /**
16
+ * Detect the CPU and memory budget. Container limits win over the host's: a
17
+ * 0.5 vCPU / 1 GB container on a large machine must be sized as 0.5 / 1 GB.
18
+ */
19
+ function detectResources() {
20
+ const cpus = Math.min(availableParallelism(), cgroupCpus() ?? Infinity);
21
+ const constrained = typeof process.constrainedMemory === "function" ? process.constrainedMemory() : 0;
22
+ return {
23
+ cpus,
24
+ memoryBytes: constrained > 0 && constrained < totalmem() ? constrained : totalmem()
25
+ };
26
+ }
27
+ /**
28
+ * Cost of one page in Chromium-page slots. Firefox and WebKit pages cost about twice a Chromium
29
+ * page in CPU time (ray.run cross-browser benchmarks: Firefox +71%, WebKit +60%; some Playwright
30
+ * reports show WebKit worse), so they get half the page budget. Takes a capture target too.
31
+ */
32
+ function engineWeight(browser) {
33
+ const engine = browser?.split("@")[0];
34
+ return engine === "firefox" || engine === "webkit" ? 2 : 1;
35
+ }
36
+ /**
37
+ * Pages to render at once: no more than the CPUs (extra pages only slow each
38
+ * other down and trip timeouts) and no more than memory allows. A heavier engine
39
+ * (see {@link engineWeight}) spends the same budget on fewer pages.
40
+ */
41
+ function recommendedPageConcurrency(resources = detectResources(), browser) {
42
+ const budget = Math.min(Math.ceil(resources.cpus), memoryPages(resources), MAX_PAGES);
43
+ return Math.max(1, Math.floor(budget / engineWeight(browser)));
44
+ }
45
+ /**
46
+ * Most pages the adaptive controller may reach: memory is a hard limit, but CPU is only a
47
+ * starting point (latency tells us when it is saturated), so allow up to twice the CPUs.
48
+ */
49
+ function maxPageConcurrency(resources = detectResources(), browser) {
50
+ const budget = Math.min(Math.ceil(resources.cpus) * 2, memoryPages(resources), MAX_ADAPTIVE_PAGES);
51
+ return Math.max(recommendedPageConcurrency(resources, browser), Math.floor(budget / engineWeight(browser)));
52
+ }
53
+ function memoryPages(resources) {
54
+ return Math.floor((resources.memoryBytes / MB - BASE_MEMORY_MB) / PAGE_MEMORY_MB);
55
+ }
56
+ /** Builds to capture at once: each launches its own browser, so only on roomy hosts. */
57
+ function recommendedBuildConcurrency(resources = detectResources()) {
58
+ return resources.cpus >= 2 && resources.memoryBytes >= 3072 * MB ? 2 : 1;
59
+ }
60
+ /** Playwright's 30 s default assumes a full CPU; starved containers parse the Storybook bundle slower. */
61
+ function recommendedNavigationTimeoutMs(resources = detectResources()) {
62
+ if (resources.cpus < 1) return 9e4;
63
+ if (resources.cpus < 2) return 6e4;
64
+ return 3e4;
65
+ }
66
+ /** Captures between browser restarts: Chromium grows slowly over a long run, so recycle it. */
67
+ const DEFAULT_RECYCLE_BROWSER_EVERY = 150;
68
+ /** Loggable summary of the host budget and the capture settings derived from it. */
69
+ function describeSizing(settings) {
70
+ const resources = detectResources();
71
+ return {
72
+ cpus: Math.round(resources.cpus * 100) / 100,
73
+ memoryMb: Math.round(resources.memoryBytes / MB),
74
+ pages: settings.concurrency ?? recommendedPageConcurrency(resources, settings.browser),
75
+ maxPages: settings.concurrency ?? maxPageConcurrency(resources, settings.browser),
76
+ navigationTimeoutMs: settings.navigationTimeoutMs ?? recommendedNavigationTimeoutMs(resources),
77
+ recycleBrowserEvery: settings.recycleBrowserEvery ?? 150
78
+ };
79
+ }
80
+ /** Summarise the host budget and effective capture concurrency, for the System page. */
81
+ function describeCaptureHost(configuredBuilds, resources = detectResources()) {
82
+ return {
83
+ cpus: Math.round(resources.cpus * 100) / 100,
84
+ memoryMb: Math.round(resources.memoryBytes / MB),
85
+ builds: {
86
+ value: configuredBuilds ?? recommendedBuildConcurrency(resources),
87
+ auto: configuredBuilds === void 0
88
+ },
89
+ pages: {
90
+ start: recommendedPageConcurrency(resources),
91
+ max: maxPageConcurrency(resources)
92
+ }
93
+ };
94
+ }
95
+ function cgroupCpus() {
96
+ return cgroupV2Cpus() ?? cgroupV1Cpus();
97
+ }
98
+ function cgroupV2Cpus() {
99
+ const [quota, period] = readText("/sys/fs/cgroup/cpu.max")?.trim().split(/\s+/u) ?? [];
100
+ return quotaToCpus(quota, period);
101
+ }
102
+ function cgroupV1Cpus() {
103
+ const quota = readText("/sys/fs/cgroup/cpu/cpu.cfs_quota_us")?.trim();
104
+ const period = readText("/sys/fs/cgroup/cpu/cpu.cfs_period_us")?.trim();
105
+ return quotaToCpus(quota, period);
106
+ }
107
+ function quotaToCpus(quota, period) {
108
+ const q = Number(quota);
109
+ const p = Number(period);
110
+ return Number.isFinite(q) && q > 0 && Number.isFinite(p) && p > 0 ? q / p : null;
111
+ }
112
+ function readText(path) {
113
+ try {
114
+ return readFileSync(path, "utf8");
115
+ } catch {
116
+ return null;
117
+ }
118
+ }
119
+ export { DEFAULT_RECYCLE_BROWSER_EVERY, describeCaptureHost, describeSizing, detectResources, engineWeight, maxPageConcurrency, recommendedBuildConcurrency, recommendedNavigationTimeoutMs, recommendedPageConcurrency };
120
+
121
+ //# sourceMappingURL=resources.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resources.mjs","names":[],"sources":["../../src/utils/resources.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\nimport { availableParallelism, totalmem } from \"node:os\";\n\n/** CPU and memory actually available to this process (container limits included). */\nexport interface HostResources {\n /** Fractional vCPUs: a cgroup quota of 0.5 CPU reports `0.5`. */\n cpus: number;\n memoryBytes: number;\n}\n\nconst MB = 1024 * 1024;\n/** Headroom for Node, the server and Chromium's browser process. */\nconst BASE_MEMORY_MB = 512;\n/** Typical cost of one rendering page. */\nconst PAGE_MEMORY_MB = 300;\nconst MAX_PAGES = 4;\n/** Ceiling for the adaptive controller, in Chromium-page slots. */\nconst MAX_ADAPTIVE_PAGES = 8;\n\n/**\n * Detect the CPU and memory budget. Container limits win over the host's: a\n * 0.5 vCPU / 1 GB container on a large machine must be sized as 0.5 / 1 GB.\n */\nexport function detectResources(): HostResources {\n const cpus = Math.min(availableParallelism(), cgroupCpus() ?? Infinity);\n const constrained =\n typeof process.constrainedMemory === \"function\" ? process.constrainedMemory() : 0;\n const memoryBytes = constrained > 0 && constrained < totalmem() ? constrained : totalmem();\n return { cpus, memoryBytes };\n}\n\n/**\n * Cost of one page in Chromium-page slots. Firefox and WebKit pages cost about twice a Chromium\n * page in CPU time (ray.run cross-browser benchmarks: Firefox +71%, WebKit +60%; some Playwright\n * reports show WebKit worse), so they get half the page budget. Takes a capture target too.\n */\nexport function engineWeight(browser?: string): number {\n // A target is `<engine>[@<name>]`; the engine decides the page cost.\n const engine = browser?.split(\"@\")[0];\n return engine === \"firefox\" || engine === \"webkit\" ? 2 : 1;\n}\n\n/**\n * Pages to render at once: no more than the CPUs (extra pages only slow each\n * other down and trip timeouts) and no more than memory allows. A heavier engine\n * (see {@link engineWeight}) spends the same budget on fewer pages.\n */\nexport function recommendedPageConcurrency(\n resources: HostResources = detectResources(),\n browser?: string,\n): number {\n const budget = Math.min(Math.ceil(resources.cpus), memoryPages(resources), MAX_PAGES);\n return Math.max(1, Math.floor(budget / engineWeight(browser)));\n}\n\n/**\n * Most pages the adaptive controller may reach: memory is a hard limit, but CPU is only a\n * starting point (latency tells us when it is saturated), so allow up to twice the CPUs.\n */\nexport function maxPageConcurrency(\n resources: HostResources = detectResources(),\n browser?: string,\n): number {\n const budget = Math.min(\n Math.ceil(resources.cpus) * 2,\n memoryPages(resources),\n MAX_ADAPTIVE_PAGES,\n );\n return Math.max(\n recommendedPageConcurrency(resources, browser),\n Math.floor(budget / engineWeight(browser)),\n );\n}\n\nfunction memoryPages(resources: HostResources): number {\n return Math.floor((resources.memoryBytes / MB - BASE_MEMORY_MB) / PAGE_MEMORY_MB);\n}\n\n/** Builds to capture at once: each launches its own browser, so only on roomy hosts. */\nexport function recommendedBuildConcurrency(resources: HostResources = detectResources()): number {\n return resources.cpus >= 2 && resources.memoryBytes >= 3 * 1024 * MB ? 2 : 1;\n}\n\n/** Playwright's 30 s default assumes a full CPU; starved containers parse the Storybook bundle slower. */\nexport function recommendedNavigationTimeoutMs(\n resources: HostResources = detectResources(),\n): number {\n if (resources.cpus < 1) return 90_000;\n if (resources.cpus < 2) return 60_000;\n return 30_000;\n}\n\n/** Captures between browser restarts: Chromium grows slowly over a long run, so recycle it. */\nexport const DEFAULT_RECYCLE_BROWSER_EVERY = 150;\n\n/** Loggable summary of the host budget and the capture settings derived from it. */\nexport function describeSizing(settings: {\n browser?: string;\n concurrency?: number;\n navigationTimeoutMs?: number;\n recycleBrowserEvery?: number;\n}): Record<string, number> {\n const resources = detectResources();\n return {\n cpus: Math.round(resources.cpus * 100) / 100,\n memoryMb: Math.round(resources.memoryBytes / MB),\n pages: settings.concurrency ?? recommendedPageConcurrency(resources, settings.browser),\n maxPages: settings.concurrency ?? maxPageConcurrency(resources, settings.browser),\n navigationTimeoutMs: settings.navigationTimeoutMs ?? recommendedNavigationTimeoutMs(resources),\n recycleBrowserEvery: settings.recycleBrowserEvery ?? DEFAULT_RECYCLE_BROWSER_EVERY,\n };\n}\n\n/** What capture will use on this host: detected resources and the effective concurrency. */\nexport interface CaptureHostSummary {\n cpus: number;\n memoryMb: number;\n /** Builds captured at once, and whether that was sized from the hardware or configured. */\n builds: { value: number; auto: boolean };\n /** Pages per build when the runner is not pinned: where it starts and the most it may reach (Chromium). */\n pages: { start: number; max: number };\n}\n\n/** Summarise the host budget and effective capture concurrency, for the System page. */\nexport function describeCaptureHost(\n configuredBuilds?: number,\n resources: HostResources = detectResources(),\n): CaptureHostSummary {\n return {\n cpus: Math.round(resources.cpus * 100) / 100,\n memoryMb: Math.round(resources.memoryBytes / MB),\n builds: {\n value: configuredBuilds ?? recommendedBuildConcurrency(resources),\n auto: configuredBuilds === undefined,\n },\n pages: { start: recommendedPageConcurrency(resources), max: maxPageConcurrency(resources) },\n };\n}\n\nfunction cgroupCpus(): number | null {\n return cgroupV2Cpus() ?? cgroupV1Cpus();\n}\n\nfunction cgroupV2Cpus(): number | null {\n const [quota, period] = readText(\"/sys/fs/cgroup/cpu.max\")?.trim().split(/\\s+/u) ?? [];\n return quotaToCpus(quota, period);\n}\n\nfunction cgroupV1Cpus(): number | null {\n const quota = readText(\"/sys/fs/cgroup/cpu/cpu.cfs_quota_us\")?.trim();\n const period = readText(\"/sys/fs/cgroup/cpu/cpu.cfs_period_us\")?.trim();\n return quotaToCpus(quota, period);\n}\n\nfunction quotaToCpus(quota: string | undefined, period: string | undefined): number | null {\n const q = Number(quota);\n const p = Number(period);\n return Number.isFinite(q) && q > 0 && Number.isFinite(p) && p > 0 ? q / p : null;\n}\n\nfunction readText(path: string): string | null {\n try {\n return readFileSync(path, \"utf8\");\n } catch {\n return null;\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ import { StorageAdapter } from "../adapter/storage.mjs";
4
+ /**
5
+ * Store a screenshot under the SHA-256 of its bytes and return its path.
6
+ *
7
+ * An unchanged story renders the same bytes build after build, so the object usually exists
8
+ * already and nothing is uploaded. Snapshots and baselines then point at the same object, and
9
+ * identical paths mean identical pixels (the diff can be skipped without reading either image).
10
+ */
11
+ declare function storeScreenshot(storage: StorageAdapter, projectId: string, data: Buffer): Promise<string>;
12
+ export { storeScreenshot };
13
+ //# sourceMappingURL=screenshot-store.d.mts.map
@@ -0,0 +1,40 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { ulid } from "./ulid.mjs";
5
+ import { contentDir, contentPath } from "../paths.mjs";
6
+ import { createHash } from "node:crypto";
7
+ // src/utils/screenshot-store.ts
8
+ /** Content-addressed screenshot storage: identical bytes are stored once per project. */
9
+ /**
10
+ * Store a screenshot under the SHA-256 of its bytes and return its path.
11
+ *
12
+ * An unchanged story renders the same bytes build after build, so the object usually exists
13
+ * already and nothing is uploaded. Snapshots and baselines then point at the same object, and
14
+ * identical paths mean identical pixels (the diff can be skipped without reading either image).
15
+ */
16
+ async function storeScreenshot(storage, projectId, data) {
17
+ const path = contentPath(projectId, createHash("sha256").update(data).digest("hex"));
18
+ if (!await storage.exists(path)) await storage.write(path, data);
19
+ return path;
20
+ }
21
+ /** Folder inside a project's content directory holding in-flight reuse claims. */
22
+ const CLAIMS_DIR = ".claims";
23
+ /**
24
+ * Run `work` under a reuse claim the screenshot sweep honours: while the claim exists (and is
25
+ * younger than the sweep's grace period) nothing in the project's content is deleted. For work
26
+ * outside a capture that reuses objects before writing the rows that reference them (baseline
27
+ * import, approving a legacy snapshot); a capture is covered by its build's status instead.
28
+ */
29
+ async function withContentClaim(storage, projectId, work) {
30
+ const path = `${contentDir(projectId)}/${CLAIMS_DIR}/${ulid()}.json`;
31
+ await storage.write(path, Buffer.from(JSON.stringify({ at: Date.now() })));
32
+ try {
33
+ return await work();
34
+ } finally {
35
+ await storage.delete(path).catch(() => false);
36
+ }
37
+ }
38
+ export { CLAIMS_DIR, storeScreenshot, withContentClaim };
39
+
40
+ //# sourceMappingURL=screenshot-store.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"screenshot-store.mjs","names":[],"sources":["../../src/utils/screenshot-store.ts"],"sourcesContent":["/** Content-addressed screenshot storage: identical bytes are stored once per project. */\nimport { createHash } from \"node:crypto\";\nimport type { StorageAdapter } from \"../adapters/storage.ts\";\nimport { contentDir, contentPath } from \"./paths.ts\";\nimport { ulid } from \"./ulid.ts\";\n\n/**\n * Store a screenshot under the SHA-256 of its bytes and return its path.\n *\n * An unchanged story renders the same bytes build after build, so the object usually exists\n * already and nothing is uploaded. Snapshots and baselines then point at the same object, and\n * identical paths mean identical pixels (the diff can be skipped without reading either image).\n */\nexport async function storeScreenshot(\n storage: StorageAdapter,\n projectId: string,\n data: Buffer,\n): Promise<string> {\n const path = contentPath(projectId, createHash(\"sha256\").update(data).digest(\"hex\"));\n if (!(await storage.exists(path))) {\n await storage.write(path, data);\n }\n return path;\n}\n\n/** Folder inside a project's content directory holding in-flight reuse claims. */\nexport const CLAIMS_DIR = \".claims\";\n\n/**\n * Run `work` under a reuse claim the screenshot sweep honours: while the claim exists (and is\n * younger than the sweep's grace period) nothing in the project's content is deleted. For work\n * outside a capture that reuses objects before writing the rows that reference them (baseline\n * import, approving a legacy snapshot); a capture is covered by its build's status instead.\n */\nexport async function withContentClaim<T>(\n storage: StorageAdapter,\n projectId: string,\n work: () => Promise<T>,\n): Promise<T> {\n const path = `${contentDir(projectId)}/${CLAIMS_DIR}/${ulid()}.json`;\n await storage.write(path, Buffer.from(JSON.stringify({ at: Date.now() })));\n try {\n return await work();\n } finally {\n await storage.delete(path).catch(() => false);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;AAaA,eAAsB,gBACpB,SACA,WACA,MACiB;CACjB,MAAM,OAAO,YAAY,WAAW,WAAW,QAAQ,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,OAAO,KAAK,CAAC;CACnF,IAAI,CAAE,MAAM,QAAQ,OAAO,IAAI,GAC7B,MAAM,QAAQ,MAAM,MAAM,IAAI;CAEhC,OAAO;AACT;;AAGA,MAAa,aAAa;;;;;;;AAQ1B,eAAsB,iBACpB,SACA,WACA,MACY;CACZ,MAAM,OAAO,GAAG,WAAW,SAAS,EAAE,GAAG,WAAW,GAAG,KAAK,EAAE;CAC9D,MAAM,QAAQ,MAAM,MAAM,OAAO,KAAK,KAAK,UAAU,EAAE,IAAI,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC;CACzE,IAAI;EACF,OAAO,MAAM,KAAK;CACpB,UAAU;EACR,MAAM,QAAQ,OAAO,IAAI,CAAC,CAAC,YAAY,KAAK;CAC9C;AACF"}
@@ -0,0 +1,23 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ /** Per-target session caps for remote browsers, shared by every render in the process (ADR 0007, decision 4). */
4
+ /** Runs work under a fixed number of sessions. */
5
+ interface SessionGate {
6
+ /** Sessions the plan allows now (a scaling provider's cap can change between reads). */
7
+ readonly size: number;
8
+ /** Run `fn` once a session is free. */
9
+ run<T>(fn: () => Promise<T>): Promise<T>;
10
+ }
11
+ /**
12
+ * The gate of `target`, created on first use. One gate per target for the whole process, so
13
+ * concurrent shards share the cap instead of multiplying it. A later call with another `sessions`
14
+ * keeps the first gate: the size is the runner's configuration and does not change under it.
15
+ */
16
+ declare function sessionGateFor(target: string, sessions: number | (() => number), onInvalid?: (value: unknown) => void): SessionGate;
17
+ /**
18
+ * A gate of its own (not shared through {@link sessionGateFor}); `size` below 1 counts as 1. A
19
+ * function is read on every use, so a cap that grows lets waiting work in at the next release.
20
+ */
21
+ declare function createSessionGate(size: number | (() => number), onInvalid?: (value: unknown) => void): SessionGate;
22
+ export { SessionGate, createSessionGate, sessionGateFor };
23
+ //# sourceMappingURL=session-gate.d.mts.map
@@ -0,0 +1,69 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ // src/utils/session-gate.ts
5
+ const gates = /* @__PURE__ */ new Map();
6
+ /**
7
+ * The gate of `target`, created on first use. One gate per target for the whole process, so
8
+ * concurrent shards share the cap instead of multiplying it. A later call with another `sessions`
9
+ * keeps the first gate: the size is the runner's configuration and does not change under it.
10
+ */
11
+ function sessionGateFor(target, sessions, onInvalid) {
12
+ let gate = gates.get(target);
13
+ if (gate === void 0) {
14
+ gate = createSessionGate(sessions, onInvalid);
15
+ gates.set(target, gate);
16
+ }
17
+ return gate;
18
+ }
19
+ /**
20
+ * A gate of its own (not shared through {@link sessionGateFor}); `size` below 1 counts as 1. A
21
+ * function is read on every use, so a cap that grows lets waiting work in at the next release.
22
+ */
23
+ function createSessionGate(size, onInvalid) {
24
+ let last = 1;
25
+ let warned = false;
26
+ const limitNow = () => {
27
+ const raw = typeof size === "function" ? size() : size;
28
+ if (typeof raw === "number" && Number.isFinite(raw)) last = Math.max(1, Math.floor(raw));
29
+ else if (!warned) {
30
+ warned = true;
31
+ onInvalid?.(raw);
32
+ }
33
+ return last;
34
+ };
35
+ let active = 0;
36
+ const waiting = [];
37
+ const release = () => {
38
+ active -= 1;
39
+ while (active < limitNow() && waiting.length > 0) {
40
+ active += 1;
41
+ waiting.shift()?.();
42
+ }
43
+ };
44
+ const acquire = async () => {
45
+ if (active < limitNow()) {
46
+ active += 1;
47
+ return;
48
+ }
49
+ await new Promise((resolve) => {
50
+ waiting.push(resolve);
51
+ });
52
+ };
53
+ return {
54
+ get size() {
55
+ return limitNow();
56
+ },
57
+ run: async (fn) => {
58
+ await acquire();
59
+ try {
60
+ return await fn();
61
+ } finally {
62
+ release();
63
+ }
64
+ }
65
+ };
66
+ }
67
+ export { createSessionGate, sessionGateFor };
68
+
69
+ //# sourceMappingURL=session-gate.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-gate.mjs","names":[],"sources":["../../src/utils/session-gate.ts"],"sourcesContent":["/** Per-target session caps for remote browsers, shared by every render in the process (ADR 0007, decision 4). */\n\n/** Runs work under a fixed number of sessions. */\nexport interface SessionGate {\n /** Sessions the plan allows now (a scaling provider's cap can change between reads). */\n readonly size: number;\n /** Run `fn` once a session is free. */\n run<T>(fn: () => Promise<T>): Promise<T>;\n}\n\nconst gates = new Map<string, SessionGate>();\n\n/**\n * The gate of `target`, created on first use. One gate per target for the whole process, so\n * concurrent shards share the cap instead of multiplying it. A later call with another `sessions`\n * keeps the first gate: the size is the runner's configuration and does not change under it.\n */\nexport function sessionGateFor(\n target: string,\n sessions: number | (() => number),\n onInvalid?: (value: unknown) => void,\n): SessionGate {\n let gate = gates.get(target);\n if (gate === undefined) {\n gate = createSessionGate(sessions, onInvalid);\n gates.set(target, gate);\n }\n return gate;\n}\n\n/**\n * A gate of its own (not shared through {@link sessionGateFor}); `size` below 1 counts as 1. A\n * function is read on every use, so a cap that grows lets waiting work in at the next release.\n */\nexport function createSessionGate(\n size: number | (() => number),\n onInvalid?: (value: unknown) => void,\n): SessionGate {\n let last = 1;\n let warned = false;\n // A function returning garbage (NaN, a string) keeps the last good cap instead of blocking every page.\n const limitNow = (): number => {\n const raw: unknown = typeof size === \"function\" ? size() : size;\n if (typeof raw === \"number\" && Number.isFinite(raw)) {\n last = Math.max(1, Math.floor(raw));\n } else if (!warned) {\n warned = true;\n onInvalid?.(raw);\n }\n return last;\n };\n let active = 0;\n const waiting: (() => void)[] = [];\n // A cap that grew lets in as many waiters as it now has room for, not one per release.\n const release = (): void => {\n active -= 1;\n while (active < limitNow() && waiting.length > 0) {\n active += 1;\n waiting.shift()?.();\n }\n };\n const acquire = async (): Promise<void> => {\n if (active < limitNow()) {\n active += 1;\n return;\n }\n await new Promise<void>((resolve) => {\n waiting.push(resolve);\n });\n };\n return {\n get size() {\n return limitNow();\n },\n run: async (fn) => {\n await acquire();\n try {\n return await fn();\n } finally {\n release();\n }\n },\n };\n}\n"],"mappings":";;;;AAUA,MAAM,wBAAQ,IAAI,IAAyB;;;;;;AAO3C,SAAgB,eACd,QACA,UACA,WACa;CACb,IAAI,OAAO,MAAM,IAAI,MAAM;CAC3B,IAAI,SAAS,KAAA,GAAW;EACtB,OAAO,kBAAkB,UAAU,SAAS;EAC5C,MAAM,IAAI,QAAQ,IAAI;CACxB;CACA,OAAO;AACT;;;;;AAMA,SAAgB,kBACd,MACA,WACa;CACb,IAAI,OAAO;CACX,IAAI,SAAS;CAEb,MAAM,iBAAyB;EAC7B,MAAM,MAAe,OAAO,SAAS,aAAa,KAAK,IAAI;EAC3D,IAAI,OAAO,QAAQ,YAAY,OAAO,SAAS,GAAG,GAChD,OAAO,KAAK,IAAI,GAAG,KAAK,MAAM,GAAG,CAAC;OAC7B,IAAI,CAAC,QAAQ;GAClB,SAAS;GACT,YAAY,GAAG;EACjB;EACA,OAAO;CACT;CACA,IAAI,SAAS;CACb,MAAM,UAA0B,CAAC;CAEjC,MAAM,gBAAsB;EAC1B,UAAU;EACV,OAAO,SAAS,SAAS,KAAK,QAAQ,SAAS,GAAG;GAChD,UAAU;GACV,QAAQ,MAAM,CAAC,GAAG;EACpB;CACF;CACA,MAAM,UAAU,YAA2B;EACzC,IAAI,SAAS,SAAS,GAAG;GACvB,UAAU;GACV;EACF;EACA,MAAM,IAAI,SAAe,YAAY;GACnC,QAAQ,KAAK,OAAO;EACtB,CAAC;CACH;CACA,OAAO;EACL,IAAI,OAAO;GACT,OAAO,SAAS;EAClB;EACA,KAAK,OAAO,OAAO;GACjB,MAAM,QAAQ;GACd,IAAI;IACF,OAAO,MAAM,GAAG;GAClB,UAAU;IACR,QAAQ;GACV;EACF;CACF;AACF"}
@@ -0,0 +1,89 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ import { SecretInput } from "./encrypt.mjs";
4
+ /** The path prefix every signed Storybook URL starts with; `/_/` is the unrelated short link. */
5
+ declare const STORYBOOK_TOKEN_PREFIX = "/_s/";
6
+ /** Regex source of a token, shared by the shape check and the log redaction. */
7
+ declare const STORYBOOK_TOKEN_SOURCE = "[A-Za-z0-9_-]{8,200}\\.[A-Za-z0-9_-]{43}";
8
+ /** Floor of a derived lifetime, in seconds (2 hours). */
9
+ declare const DEFAULT_STORYBOOK_URL_TTL = 7200;
10
+ /** Shortest lifetime a token may be minted with (a render needs at least this). */
11
+ declare const MIN_STORYBOOK_URL_TTL = 60;
12
+ /** Ceiling of a derived lifetime and the longest accepted: a token claiming more is refused, whoever signed it. */
13
+ declare const MAX_STORYBOOK_URL_TTL = 86400;
14
+ /** Slack added to a render budget when deriving a lifetime (queue wait, retries, clock skew). */
15
+ declare const STORYBOOK_URL_TTL_GRACE = 300;
16
+ /** Extra time granted per capture of a render pass, in ms; sizes a pass's budget and its slices. */
17
+ declare const PER_CAPTURE_BUDGET_MS = 15000;
18
+ /** Smallest slice (captures), so a very short project override never renders a page per call. */
19
+ declare const MIN_CAPTURES_PER_SLICE = 50;
20
+ /**
21
+ * Shortest lifetime a project may set as its override (seconds): one minimum slice's budget plus
22
+ * the grace, so a slice never outlives its token. Single owner of the rule; every parser of the
23
+ * override (project API, settings form, page hint) reads it through {@link storybookUrlTtlOverrideError}.
24
+ */
25
+ declare const MIN_STORYBOOK_URL_TTL_OVERRIDE: number;
26
+ /**
27
+ * What a token was minted for. `capture` marks the tokens the server mints for a render pass or
28
+ * slice (`storybookUrlSource`); it is the only claim that may opt into browser-private caching of
29
+ * the build's assets. Interactive preview grants and legacy tokens carry none.
30
+ */
31
+ type StorybookTokenPurpose = "capture";
32
+ /** What a valid token grants: read-only access to one build's static files until `expiresAt`. */
33
+ interface StorybookTokenClaims {
34
+ projectId: string;
35
+ buildId: string;
36
+ /** Unix seconds. */
37
+ expiresAt: number;
38
+ /** Set only on tokens minted for a render pass; absent on previews and legacy tokens. */
39
+ purpose?: StorybookTokenPurpose;
40
+ }
41
+ /** Inputs for {@link signStorybookToken}. */
42
+ interface SignStorybookTokenInput {
43
+ /** The server secret (a rotation pair signs with its current key); throws when empty. */
44
+ secret: SecretInput;
45
+ projectId: string;
46
+ buildId: string;
47
+ /** Lifetime in seconds; defaults to {@link DEFAULT_STORYBOOK_URL_TTL}. See {@link storybookUrlTtl}. */
48
+ ttlSeconds?: number | undefined;
49
+ /** What the token is for; omit for an interactive preview. Signed into the token. */
50
+ purpose?: StorybookTokenPurpose | undefined;
51
+ /** Clock override for tests (ms). */
52
+ now?: number | undefined;
53
+ }
54
+ /** Why a token was refused (kept apart from the response, which is the same for all). */
55
+ type StorybookTokenFailure = "malformed" | "signature" | "expired" | "scope";
56
+ /** The outcome of {@link verifyStorybookToken}. */
57
+ type StorybookTokenResult = {
58
+ ok: true;
59
+ claims: StorybookTokenClaims;
60
+ } | {
61
+ ok: false;
62
+ reason: StorybookTokenFailure;
63
+ };
64
+ /** Sign a token for one build. Throws on an empty secret, bad ids or a lifetime out of range. */
65
+ declare function signStorybookToken(input: SignStorybookTokenInput): string;
66
+ /**
67
+ * Check a token against the current secret and, during a rotation, the previous one. The signature
68
+ * is checked (in constant time) before the payload is read, and every failure looks the same to
69
+ * the caller except for `reason`, which is for the server's own log.
70
+ */
71
+ declare function verifyStorybookToken(token: unknown, secret: SecretInput, now?: number): StorybookTokenResult;
72
+ /** The path of a token's root: `/_s/<token>/`. */
73
+ declare function storybookTokenPath(token: string): string;
74
+ /**
75
+ * The lifetime of a render's tokens: the project's override when it has one, otherwise the render
76
+ * budget (seconds) plus {@link STORYBOOK_URL_TTL_GRACE}, never below {@link DEFAULT_STORYBOOK_URL_TTL}
77
+ * and never above {@link MAX_STORYBOOK_URL_TTL}. A pass cannot outlive its budget, so a token
78
+ * derived from it lasts as long as the pass that carries it.
79
+ */
80
+ declare function storybookUrlTtl(input: {
81
+ override?: number | null | undefined;
82
+ budgetMs: number;
83
+ }): number;
84
+ /** The reason a project's lifetime override is refused, or `undefined` when it is acceptable. */
85
+ declare function storybookUrlTtlOverrideError(seconds: number): string | undefined;
86
+ /** Throws unless `seconds` is an integer within the allowed lifetime range. */
87
+ declare function assertStorybookUrlTtl(seconds: number): void;
88
+ export { DEFAULT_STORYBOOK_URL_TTL, MAX_STORYBOOK_URL_TTL, MIN_CAPTURES_PER_SLICE, MIN_STORYBOOK_URL_TTL, MIN_STORYBOOK_URL_TTL_OVERRIDE, PER_CAPTURE_BUDGET_MS, STORYBOOK_TOKEN_PREFIX, STORYBOOK_TOKEN_SOURCE, STORYBOOK_URL_TTL_GRACE, SignStorybookTokenInput, StorybookTokenClaims, StorybookTokenFailure, StorybookTokenPurpose, StorybookTokenResult, assertStorybookUrlTtl, signStorybookToken, storybookTokenPath, storybookUrlTtl, storybookUrlTtlOverrideError, verifyStorybookToken };
89
+ //# sourceMappingURL=storybook-token.d.mts.map
@@ -0,0 +1,135 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { timingSafeEqualString } from "./hash.mjs";
5
+ import { createHmac } from "node:crypto";
6
+ // src/utils/storybook-token.ts
7
+ /**
8
+ * Signed Storybook tokens (ADR 0007, decision 5): the single owner of everything about the path
9
+ * prefix of the URL a remote browser opens to read one build's published Storybook, `/_s/<token>/`:
10
+ * the prefix, the token shape, the derived signing key, signing, verification and the lifetime
11
+ * rules. Stateless: `base64url("<projectId>:<buildId>:<exp>[:<purpose>]")`, a dot, and the base64url
12
+ * HMAC-SHA-256 of that first part. The optional purpose is covered by the signature; a token
13
+ * without one is a legacy or interactive-preview grant. Imported through `@storyshelf/core/utils`.
14
+ */
15
+ /** The path prefix every signed Storybook URL starts with; `/_/` is the unrelated short link. */
16
+ const STORYBOOK_TOKEN_PREFIX = "/_s/";
17
+ /** Regex source of a token, shared by the shape check and the log redaction. */
18
+ const STORYBOOK_TOKEN_SOURCE = "[A-Za-z0-9_-]{8,200}\\.[A-Za-z0-9_-]{43}";
19
+ /** Floor of a derived lifetime, in seconds (2 hours). */
20
+ const DEFAULT_STORYBOOK_URL_TTL = 7200;
21
+ /** Shortest lifetime a token may be minted with (a render needs at least this). */
22
+ const MIN_STORYBOOK_URL_TTL = 60;
23
+ /** Ceiling of a derived lifetime and the longest accepted: a token claiming more is refused, whoever signed it. */
24
+ const MAX_STORYBOOK_URL_TTL = 86400;
25
+ /** Slack added to a render budget when deriving a lifetime (queue wait, retries, clock skew). */
26
+ const STORYBOOK_URL_TTL_GRACE = 300;
27
+ /** Extra time granted per capture of a render pass, in ms; sizes a pass's budget and its slices. */
28
+ const PER_CAPTURE_BUDGET_MS = 15e3;
29
+ /** Smallest slice (captures), so a very short project override never renders a page per call. */
30
+ const MIN_CAPTURES_PER_SLICE = 50;
31
+ /**
32
+ * Shortest lifetime a project may set as its override (seconds): one minimum slice's budget plus
33
+ * the grace, so a slice never outlives its token. Single owner of the rule; every parser of the
34
+ * override (project API, settings form, page hint) reads it through {@link storybookUrlTtlOverrideError}.
35
+ */
36
+ const MIN_STORYBOOK_URL_TTL_OVERRIDE = Math.ceil(50 * PER_CAPTURE_BUDGET_MS / 1e3) + 300;
37
+ /** Domain separation: the signing key is derived from the secret with this context, nothing else uses it. */
38
+ const KEY_CONTEXT = "storyshelf/storybook-url/v1";
39
+ const TOKEN_SHAPE = new RegExp(`^${STORYBOOK_TOKEN_SOURCE}$`, "u");
40
+ const CLAIMS_SHAPE = /^([A-Za-z0-9_-]{1,64}):([A-Za-z0-9_-]{1,64}):(\d{1,12})(?::(capture))?$/u;
41
+ /** Sign a token for one build. Throws on an empty secret, bad ids or a lifetime out of range. */
42
+ function signStorybookToken(input) {
43
+ const ttl = input.ttlSeconds ?? 7200;
44
+ assertStorybookUrlTtl(ttl);
45
+ const secret = currentSecret(input.secret);
46
+ const expiresAt = Math.floor((input.now ?? Date.now()) / 1e3) + ttl;
47
+ const claims = `${input.projectId}:${input.buildId}:${expiresAt}${input.purpose === void 0 ? "" : `:${input.purpose}`}`;
48
+ if (!CLAIMS_SHAPE.test(claims)) throw new Error("Cannot sign a Storybook URL: unexpected project or build id");
49
+ const payload = Buffer.from(claims, "utf8").toString("base64url");
50
+ return `${payload}.${signature(secret, payload)}`;
51
+ }
52
+ /**
53
+ * Check a token against the current secret and, during a rotation, the previous one. The signature
54
+ * is checked (in constant time) before the payload is read, and every failure looks the same to
55
+ * the caller except for `reason`, which is for the server's own log.
56
+ */
57
+ function verifyStorybookToken(token, secret, now = Date.now()) {
58
+ if (typeof token !== "string" || !TOKEN_SHAPE.test(token)) return {
59
+ ok: false,
60
+ reason: "malformed"
61
+ };
62
+ const [payload = "", given = ""] = token.split(".");
63
+ if (!secretsOf(secret).some((key) => timingSafeEqualString(signature(key, payload), given))) return {
64
+ ok: false,
65
+ reason: "signature"
66
+ };
67
+ return claimsOf(payload, Math.floor(now / 1e3));
68
+ }
69
+ /** The path of a token's root: `/_s/<token>/`. */
70
+ function storybookTokenPath(token) {
71
+ return `${STORYBOOK_TOKEN_PREFIX}${token}/`;
72
+ }
73
+ /**
74
+ * The lifetime of a render's tokens: the project's override when it has one, otherwise the render
75
+ * budget (seconds) plus {@link STORYBOOK_URL_TTL_GRACE}, never below {@link DEFAULT_STORYBOOK_URL_TTL}
76
+ * and never above {@link MAX_STORYBOOK_URL_TTL}. A pass cannot outlive its budget, so a token
77
+ * derived from it lasts as long as the pass that carries it.
78
+ */
79
+ function storybookUrlTtl(input) {
80
+ if (input.override !== void 0 && input.override !== null) return input.override;
81
+ const derived = Math.ceil(input.budgetMs / 1e3) + 300;
82
+ return Math.min(MAX_STORYBOOK_URL_TTL, Math.max(DEFAULT_STORYBOOK_URL_TTL, derived));
83
+ }
84
+ /** The reason a project's lifetime override is refused, or `undefined` when it is acceptable. */
85
+ function storybookUrlTtlOverrideError(seconds) {
86
+ if (Number.isInteger(seconds) && seconds >= 1050 && seconds <= 86400) return;
87
+ return `The signed URL lifetime is a whole number of seconds from ${MIN_STORYBOOK_URL_TTL_OVERRIDE} (one slice of 50 captures plus the 300 s grace) to ${MAX_STORYBOOK_URL_TTL}`;
88
+ }
89
+ /** Throws unless `seconds` is an integer within the allowed lifetime range. */
90
+ function assertStorybookUrlTtl(seconds) {
91
+ if (!Number.isInteger(seconds) || seconds < 60 || seconds > 86400) throw new Error(`A Storybook URL lifetime must be a whole number of seconds from 60 to ${MAX_STORYBOOK_URL_TTL}, got ${String(seconds)}`);
92
+ }
93
+ function claimsOf(payload, nowSeconds) {
94
+ const match = CLAIMS_SHAPE.exec(Buffer.from(payload, "base64url").toString("utf8"));
95
+ if (!match) return {
96
+ ok: false,
97
+ reason: "malformed"
98
+ };
99
+ const [, projectId = "", buildId = "", exp = "", purpose] = match;
100
+ const expiresAt = Number(exp);
101
+ if (expiresAt <= nowSeconds) return {
102
+ ok: false,
103
+ reason: "expired"
104
+ };
105
+ if (expiresAt > nowSeconds + 86400) return {
106
+ ok: false,
107
+ reason: "scope"
108
+ };
109
+ return {
110
+ ok: true,
111
+ claims: {
112
+ projectId,
113
+ buildId,
114
+ expiresAt,
115
+ ...purpose === "capture" ? { purpose } : {}
116
+ }
117
+ };
118
+ }
119
+ /** The key tokens are signed with; throws when none is configured. */
120
+ function currentSecret(secret) {
121
+ const current = typeof secret === "object" ? secret.current : secret;
122
+ if (!current) throw new Error("Cannot sign a Storybook URL: ShelfConfig.secret is not configured");
123
+ return current;
124
+ }
125
+ /** Every key a token may verify with: the current secret and, during a rotation, the previous one. */
126
+ function secretsOf(secret) {
127
+ return (typeof secret === "object" ? [secret.current, secret.previous] : [secret]).filter((key) => key !== void 0 && key !== "");
128
+ }
129
+ function signature(secret, payload) {
130
+ const key = createHmac("sha256", secret).update(KEY_CONTEXT).digest();
131
+ return createHmac("sha256", key).update(payload).digest("base64url");
132
+ }
133
+ export { DEFAULT_STORYBOOK_URL_TTL, MAX_STORYBOOK_URL_TTL, MIN_CAPTURES_PER_SLICE, MIN_STORYBOOK_URL_TTL, MIN_STORYBOOK_URL_TTL_OVERRIDE, PER_CAPTURE_BUDGET_MS, STORYBOOK_TOKEN_PREFIX, STORYBOOK_TOKEN_SOURCE, STORYBOOK_URL_TTL_GRACE, assertStorybookUrlTtl, signStorybookToken, storybookTokenPath, storybookUrlTtl, storybookUrlTtlOverrideError, verifyStorybookToken };
134
+
135
+ //# sourceMappingURL=storybook-token.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storybook-token.mjs","names":[],"sources":["../../src/utils/storybook-token.ts"],"sourcesContent":["/**\n * Signed Storybook tokens (ADR 0007, decision 5): the single owner of everything about the path\n * prefix of the URL a remote browser opens to read one build's published Storybook, `/_s/<token>/`:\n * the prefix, the token shape, the derived signing key, signing, verification and the lifetime\n * rules. Stateless: `base64url(\"<projectId>:<buildId>:<exp>[:<purpose>]\")`, a dot, and the base64url\n * HMAC-SHA-256 of that first part. The optional purpose is covered by the signature; a token\n * without one is a legacy or interactive-preview grant. Imported through `@storyshelf/core/utils`.\n */\nimport { createHmac } from \"node:crypto\";\nimport type { SecretInput } from \"./encrypt.ts\";\nimport { timingSafeEqualString } from \"./hash.ts\";\n\n/** The path prefix every signed Storybook URL starts with; `/_/` is the unrelated short link. */\nexport const STORYBOOK_TOKEN_PREFIX = \"/_s/\";\n/** Regex source of a token, shared by the shape check and the log redaction. */\nexport const STORYBOOK_TOKEN_SOURCE = \"[A-Za-z0-9_-]{8,200}\\\\.[A-Za-z0-9_-]{43}\";\n\n/** Floor of a derived lifetime, in seconds (2 hours). */\nexport const DEFAULT_STORYBOOK_URL_TTL = 7200;\n/** Shortest lifetime a token may be minted with (a render needs at least this). */\nexport const MIN_STORYBOOK_URL_TTL = 60;\n/** Ceiling of a derived lifetime and the longest accepted: a token claiming more is refused, whoever signed it. */\nexport const MAX_STORYBOOK_URL_TTL = 86_400;\n/** Slack added to a render budget when deriving a lifetime (queue wait, retries, clock skew). */\nexport const STORYBOOK_URL_TTL_GRACE = 300;\n\n/** Extra time granted per capture of a render pass, in ms; sizes a pass's budget and its slices. */\nexport const PER_CAPTURE_BUDGET_MS = 15_000;\n/** Smallest slice (captures), so a very short project override never renders a page per call. */\nexport const MIN_CAPTURES_PER_SLICE = 50;\n/**\n * Shortest lifetime a project may set as its override (seconds): one minimum slice's budget plus\n * the grace, so a slice never outlives its token. Single owner of the rule; every parser of the\n * override (project API, settings form, page hint) reads it through {@link storybookUrlTtlOverrideError}.\n */\nexport const MIN_STORYBOOK_URL_TTL_OVERRIDE =\n Math.ceil((MIN_CAPTURES_PER_SLICE * PER_CAPTURE_BUDGET_MS) / 1000) + STORYBOOK_URL_TTL_GRACE;\n\n/** Domain separation: the signing key is derived from the secret with this context, nothing else uses it. */\nconst KEY_CONTEXT = \"storyshelf/storybook-url/v1\";\nconst TOKEN_SHAPE = new RegExp(`^${STORYBOOK_TOKEN_SOURCE}$`, \"u\");\nconst CLAIMS_SHAPE = /^([A-Za-z0-9_-]{1,64}):([A-Za-z0-9_-]{1,64}):(\\d{1,12})(?::(capture))?$/u;\n\n/**\n * What a token was minted for. `capture` marks the tokens the server mints for a render pass or\n * slice (`storybookUrlSource`); it is the only claim that may opt into browser-private caching of\n * the build's assets. Interactive preview grants and legacy tokens carry none.\n */\nexport type StorybookTokenPurpose = \"capture\";\n\n/** What a valid token grants: read-only access to one build's static files until `expiresAt`. */\nexport interface StorybookTokenClaims {\n projectId: string;\n buildId: string;\n /** Unix seconds. */\n expiresAt: number;\n /** Set only on tokens minted for a render pass; absent on previews and legacy tokens. */\n purpose?: StorybookTokenPurpose;\n}\n\n/** Inputs for {@link signStorybookToken}. */\nexport interface SignStorybookTokenInput {\n /** The server secret (a rotation pair signs with its current key); throws when empty. */\n secret: SecretInput;\n projectId: string;\n buildId: string;\n /** Lifetime in seconds; defaults to {@link DEFAULT_STORYBOOK_URL_TTL}. See {@link storybookUrlTtl}. */\n ttlSeconds?: number | undefined;\n /** What the token is for; omit for an interactive preview. Signed into the token. */\n purpose?: StorybookTokenPurpose | undefined;\n /** Clock override for tests (ms). */\n now?: number | undefined;\n}\n\n/** Why a token was refused (kept apart from the response, which is the same for all). */\nexport type StorybookTokenFailure = \"malformed\" | \"signature\" | \"expired\" | \"scope\";\n\n/** The outcome of {@link verifyStorybookToken}. */\nexport type StorybookTokenResult =\n | { ok: true; claims: StorybookTokenClaims }\n | { ok: false; reason: StorybookTokenFailure };\n\n/** Sign a token for one build. Throws on an empty secret, bad ids or a lifetime out of range. */\nexport function signStorybookToken(input: SignStorybookTokenInput): string {\n const ttl = input.ttlSeconds ?? DEFAULT_STORYBOOK_URL_TTL;\n assertStorybookUrlTtl(ttl);\n const secret = currentSecret(input.secret);\n const expiresAt = Math.floor((input.now ?? Date.now()) / 1000) + ttl;\n const claims = `${input.projectId}:${input.buildId}:${expiresAt}${\n input.purpose === undefined ? \"\" : `:${input.purpose}`\n }`;\n if (!CLAIMS_SHAPE.test(claims)) {\n throw new Error(\"Cannot sign a Storybook URL: unexpected project or build id\");\n }\n const payload = Buffer.from(claims, \"utf8\").toString(\"base64url\");\n return `${payload}.${signature(secret, payload)}`;\n}\n\n/**\n * Check a token against the current secret and, during a rotation, the previous one. The signature\n * is checked (in constant time) before the payload is read, and every failure looks the same to\n * the caller except for `reason`, which is for the server's own log.\n */\nexport function verifyStorybookToken(\n token: unknown,\n secret: SecretInput,\n now: number = Date.now(),\n): StorybookTokenResult {\n if (typeof token !== \"string\" || !TOKEN_SHAPE.test(token)) {\n return { ok: false, reason: \"malformed\" };\n }\n const [payload = \"\", given = \"\"] = token.split(\".\");\n const signed = secretsOf(secret).some((key) =>\n timingSafeEqualString(signature(key, payload), given),\n );\n if (!signed) {\n return { ok: false, reason: \"signature\" };\n }\n return claimsOf(payload, Math.floor(now / 1000));\n}\n\n/** The path of a token's root: `/_s/<token>/`. */\nexport function storybookTokenPath(token: string): string {\n return `${STORYBOOK_TOKEN_PREFIX}${token}/`;\n}\n\n/**\n * The lifetime of a render's tokens: the project's override when it has one, otherwise the render\n * budget (seconds) plus {@link STORYBOOK_URL_TTL_GRACE}, never below {@link DEFAULT_STORYBOOK_URL_TTL}\n * and never above {@link MAX_STORYBOOK_URL_TTL}. A pass cannot outlive its budget, so a token\n * derived from it lasts as long as the pass that carries it.\n */\nexport function storybookUrlTtl(input: {\n override?: number | null | undefined;\n budgetMs: number;\n}): number {\n if (input.override !== undefined && input.override !== null) {\n return input.override;\n }\n const derived = Math.ceil(input.budgetMs / 1000) + STORYBOOK_URL_TTL_GRACE;\n return Math.min(MAX_STORYBOOK_URL_TTL, Math.max(DEFAULT_STORYBOOK_URL_TTL, derived));\n}\n\n/** The reason a project's lifetime override is refused, or `undefined` when it is acceptable. */\nexport function storybookUrlTtlOverrideError(seconds: number): string | undefined {\n if (\n Number.isInteger(seconds) &&\n seconds >= MIN_STORYBOOK_URL_TTL_OVERRIDE &&\n seconds <= MAX_STORYBOOK_URL_TTL\n ) {\n return undefined;\n }\n return `The signed URL lifetime is a whole number of seconds from ${MIN_STORYBOOK_URL_TTL_OVERRIDE} (one slice of ${MIN_CAPTURES_PER_SLICE} captures plus the ${STORYBOOK_URL_TTL_GRACE} s grace) to ${MAX_STORYBOOK_URL_TTL}`;\n}\n\n/** Throws unless `seconds` is an integer within the allowed lifetime range. */\nexport function assertStorybookUrlTtl(seconds: number): void {\n if (\n !Number.isInteger(seconds) ||\n seconds < MIN_STORYBOOK_URL_TTL ||\n seconds > MAX_STORYBOOK_URL_TTL\n ) {\n throw new Error(\n `A Storybook URL lifetime must be a whole number of seconds from ${MIN_STORYBOOK_URL_TTL} to ${MAX_STORYBOOK_URL_TTL}, got ${String(seconds)}`,\n );\n }\n}\n\nfunction claimsOf(payload: string, nowSeconds: number): StorybookTokenResult {\n const match = CLAIMS_SHAPE.exec(Buffer.from(payload, \"base64url\").toString(\"utf8\"));\n if (!match) {\n return { ok: false, reason: \"malformed\" };\n }\n const [, projectId = \"\", buildId = \"\", exp = \"\", purpose] = match;\n const expiresAt = Number(exp);\n if (expiresAt <= nowSeconds) {\n return { ok: false, reason: \"expired\" };\n }\n if (expiresAt > nowSeconds + MAX_STORYBOOK_URL_TTL) {\n return { ok: false, reason: \"scope\" };\n }\n return {\n ok: true,\n claims: { projectId, buildId, expiresAt, ...(purpose === \"capture\" ? { purpose } : {}) },\n };\n}\n\n/** The key tokens are signed with; throws when none is configured. */\nfunction currentSecret(secret: SecretInput): string {\n const current = typeof secret === \"object\" ? secret.current : secret;\n if (!current) {\n throw new Error(\"Cannot sign a Storybook URL: ShelfConfig.secret is not configured\");\n }\n return current;\n}\n\n/** Every key a token may verify with: the current secret and, during a rotation, the previous one. */\nfunction secretsOf(secret: SecretInput): string[] {\n const keys = typeof secret === \"object\" ? [secret.current, secret.previous] : [secret];\n return keys.filter((key): key is string => key !== undefined && key !== \"\");\n}\n\nfunction signature(secret: string, payload: string): string {\n const key = createHmac(\"sha256\", secret).update(KEY_CONTEXT).digest();\n return createHmac(\"sha256\", key).update(payload).digest(\"base64url\");\n}\n"],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ /** The Storybook URL a runner opens for a `reach: "url"` target (ADR 0007, decision 5). */
4
+ /**
5
+ * The root of a build's Storybook from the URL core hands a runner, without its trailing slash. The
6
+ * URL is the directory root `<origin>/_s/<token>/` and nothing else: no `iframe.html`, no query, no
7
+ * fragment, and a path ending in a slash. Anything else throws, so a runner never silently opens a
8
+ * different page than the one core signed. The URL carries a token and is never logged, nor is it
9
+ * quoted in the error.
10
+ */
11
+ declare function storybookBase(storybookUrl: string): string;
12
+ export { storybookBase };
13
+ //# sourceMappingURL=storybook-url.d.mts.map
@@ -0,0 +1,20 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ // src/utils/storybook-url.ts
5
+ /** The Storybook URL a runner opens for a `reach: "url"` target (ADR 0007, decision 5). */
6
+ /**
7
+ * The root of a build's Storybook from the URL core hands a runner, without its trailing slash. The
8
+ * URL is the directory root `<origin>/_s/<token>/` and nothing else: no `iframe.html`, no query, no
9
+ * fragment, and a path ending in a slash. Anything else throws, so a runner never silently opens a
10
+ * different page than the one core signed. The URL carries a token and is never logged, nor is it
11
+ * quoted in the error.
12
+ */
13
+ function storybookBase(storybookUrl) {
14
+ const url = new URL(storybookUrl);
15
+ if (url.search !== "" || url.hash !== "" || !url.pathname.endsWith("/")) throw new Error("The Storybook URL must be the build's directory root (ending in a slash, no query or fragment)");
16
+ return `${url.origin}${url.pathname.replace(/\/+$/u, "")}`;
17
+ }
18
+ export { storybookBase };
19
+
20
+ //# sourceMappingURL=storybook-url.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storybook-url.mjs","names":[],"sources":["../../src/utils/storybook-url.ts"],"sourcesContent":["/** The Storybook URL a runner opens for a `reach: \"url\"` target (ADR 0007, decision 5). */\n\n/**\n * The root of a build's Storybook from the URL core hands a runner, without its trailing slash. The\n * URL is the directory root `<origin>/_s/<token>/` and nothing else: no `iframe.html`, no query, no\n * fragment, and a path ending in a slash. Anything else throws, so a runner never silently opens a\n * different page than the one core signed. The URL carries a token and is never logged, nor is it\n * quoted in the error.\n */\nexport function storybookBase(storybookUrl: string): string {\n const url = new URL(storybookUrl);\n if (url.search !== \"\" || url.hash !== \"\" || !url.pathname.endsWith(\"/\")) {\n throw new Error(\n \"The Storybook URL must be the build's directory root (ending in a slash, no query or fragment)\",\n );\n }\n return `${url.origin}${url.pathname.replace(/\\/+$/u, \"\")}`;\n}\n"],"mappings":";;;;;;;;;;;;AASA,SAAgB,cAAc,cAA8B;CAC1D,MAAM,MAAM,IAAI,IAAI,YAAY;CAChC,IAAI,IAAI,WAAW,MAAM,IAAI,SAAS,MAAM,CAAC,IAAI,SAAS,SAAS,GAAG,GACpE,MAAM,IAAI,MACR,gGACF;CAEF,OAAO,GAAG,IAAI,SAAS,IAAI,SAAS,QAAQ,SAAS,EAAE;AACzD"}
@@ -1,6 +1,5 @@
1
1
  import __tsdown_shims_path from 'node:path';
2
2
  import __tsdown_shims_url from 'node:url';
3
- //#region src/utils/timing.d.ts
4
3
  /** Aggregated durations for one scope (family/phase name → total ms). */
5
4
  type TimingMap = Record<string, number>;
6
5
  /** Run `fn` with a fresh timing scope, returning its result and aggregates. */
@@ -14,6 +13,5 @@ declare function addTiming(name: string, durationMs: number): void;
14
13
  declare function timed<T>(name: string, fn: () => T | Promise<T>): Promise<T>;
15
14
  /** Copy of aggregates with whole-millisecond values for logs and headers. */
16
15
  declare function roundedTimings(timings: TimingMap): TimingMap;
17
- //#endregion
18
16
  export { TimingMap, addTiming, roundedTimings, runWithTimings, timed };
19
17
  //# sourceMappingURL=timing.d.mts.map