@storyshelf/app 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 (361) hide show
  1. package/README.md +42 -4
  2. package/dist/app-types.d.mts +1 -3
  3. package/dist/asset-manifest.mjs +13 -5
  4. package/dist/asset-manifest.mjs.map +1 -1
  5. package/dist/assets/{htmx.min.js → htmx.mjs} +10 -1
  6. package/dist/assets/htmx.mjs.map +1 -0
  7. package/dist/assets/mark.mjs +10 -0
  8. package/dist/assets/mark.mjs.map +1 -0
  9. package/dist/boot-tasks.mjs +80 -0
  10. package/dist/boot-tasks.mjs.map +1 -0
  11. package/dist/build-state-flags.mjs +18 -0
  12. package/dist/build-state-flags.mjs.map +1 -0
  13. package/dist/build-state-setup.mjs +75 -0
  14. package/dist/build-state-setup.mjs.map +1 -0
  15. package/dist/capture-alerts.mjs +46 -0
  16. package/dist/capture-alerts.mjs.map +1 -0
  17. package/dist/capture-process.mjs +59 -0
  18. package/dist/capture-process.mjs.map +1 -0
  19. package/dist/capture-setup.mjs +95 -117
  20. package/dist/capture-setup.mjs.map +1 -1
  21. package/dist/connections.mjs +34 -0
  22. package/dist/connections.mjs.map +1 -0
  23. package/dist/consumer-setup.mjs +58 -0
  24. package/dist/consumer-setup.mjs.map +1 -0
  25. package/dist/git-status.mjs +46 -0
  26. package/dist/git-status.mjs.map +1 -0
  27. package/dist/hidden-data.mjs +24 -0
  28. package/dist/hidden-data.mjs.map +1 -0
  29. package/dist/index.d.mts +3 -3
  30. package/dist/index.mjs +203 -125
  31. package/dist/index.mjs.map +1 -1
  32. package/dist/insights/budget.mjs +49 -0
  33. package/dist/insights/budget.mjs.map +1 -0
  34. package/dist/insights/deps.mjs +21 -0
  35. package/dist/insights/deps.mjs.map +1 -0
  36. package/dist/insights/errors.mjs +22 -0
  37. package/dist/insights/errors.mjs.map +1 -0
  38. package/dist/insights/health-panel.mjs +40 -0
  39. package/dist/insights/health-panel.mjs.map +1 -0
  40. package/dist/insights/job.mjs +103 -0
  41. package/dist/insights/job.mjs.map +1 -0
  42. package/dist/insights/load-health.mjs +59 -0
  43. package/dist/insights/load-health.mjs.map +1 -0
  44. package/dist/insights/load-triage.mjs +98 -0
  45. package/dist/insights/load-triage.mjs.map +1 -0
  46. package/dist/insights/panel.mjs +28 -0
  47. package/dist/insights/panel.mjs.map +1 -0
  48. package/dist/insights/request.mjs +113 -0
  49. package/dist/insights/request.mjs.map +1 -0
  50. package/dist/insights/stale.mjs +21 -0
  51. package/dist/insights/stale.mjs.map +1 -0
  52. package/dist/insights/usage-report.mjs +60 -0
  53. package/dist/insights/usage-report.mjs.map +1 -0
  54. package/dist/insights/view.mjs +48 -0
  55. package/dist/insights/view.mjs.map +1 -0
  56. package/dist/job-canceller.mjs +14 -0
  57. package/dist/job-canceller.mjs.map +1 -0
  58. package/dist/job-queue.mjs +51 -0
  59. package/dist/job-queue.mjs.map +1 -0
  60. package/dist/jobs-setup.mjs +132 -0
  61. package/dist/jobs-setup.mjs.map +1 -0
  62. package/dist/lifecycle.mjs +91 -19
  63. package/dist/lifecycle.mjs.map +1 -1
  64. package/dist/middleware/auth-gate.mjs +4 -2
  65. package/dist/middleware/auth-gate.mjs.map +1 -1
  66. package/dist/middleware/rate-limit.mjs +41 -33
  67. package/dist/middleware/rate-limit.mjs.map +1 -1
  68. package/dist/middleware/request-log.mjs +3 -2
  69. package/dist/middleware/request-log.mjs.map +1 -1
  70. package/dist/middleware/require-review.mjs +51 -0
  71. package/dist/middleware/require-review.mjs.map +1 -0
  72. package/dist/middleware/require-testing.mjs +41 -0
  73. package/dist/middleware/require-testing.mjs.map +1 -0
  74. package/dist/middleware/setup-gate.mjs.map +1 -1
  75. package/dist/middleware/store-scope.mjs +12 -2
  76. package/dist/middleware/store-scope.mjs.map +1 -1
  77. package/dist/notifications-pause.mjs +33 -0
  78. package/dist/notifications-pause.mjs.map +1 -0
  79. package/dist/notify.d.mts +2 -1
  80. package/dist/notify.mjs +78 -39
  81. package/dist/notify.mjs.map +1 -1
  82. package/dist/pages/a11y-violations.mjs +116 -0
  83. package/dist/pages/a11y-violations.mjs.map +1 -0
  84. package/dist/pages/admin-ai.mjs +78 -0
  85. package/dist/pages/admin-ai.mjs.map +1 -0
  86. package/dist/pages/admin-system-origin.mjs +123 -0
  87. package/dist/pages/admin-system-origin.mjs.map +1 -0
  88. package/dist/pages/admin-system-targets.mjs +53 -0
  89. package/dist/pages/admin-system-targets.mjs.map +1 -0
  90. package/dist/pages/admin-system.mjs +63 -13
  91. package/dist/pages/admin-system.mjs.map +1 -1
  92. package/dist/pages/build-detail.mjs +104 -76
  93. package/dist/pages/build-detail.mjs.map +1 -1
  94. package/dist/pages/build-diff-actions.mjs +87 -33
  95. package/dist/pages/build-diff-actions.mjs.map +1 -1
  96. package/dist/pages/build-diff-comments.mjs +16 -14
  97. package/dist/pages/build-diff-comments.mjs.map +1 -1
  98. package/dist/pages/build-diff-environment.mjs +82 -0
  99. package/dist/pages/build-diff-environment.mjs.map +1 -0
  100. package/dist/pages/build-diff-header.mjs +22 -4
  101. package/dist/pages/build-diff-header.mjs.map +1 -1
  102. package/dist/pages/build-diff-insight.mjs +113 -0
  103. package/dist/pages/build-diff-insight.mjs.map +1 -0
  104. package/dist/pages/build-diff-model.mjs +55 -10
  105. package/dist/pages/build-diff-model.mjs.map +1 -1
  106. package/dist/pages/build-diff-nav.mjs +28 -12
  107. package/dist/pages/build-diff-nav.mjs.map +1 -1
  108. package/dist/pages/build-diff-shortcuts.mjs +5 -1
  109. package/dist/pages/build-diff-shortcuts.mjs.map +1 -1
  110. package/dist/pages/build-diff-usage.mjs +63 -0
  111. package/dist/pages/build-diff-usage.mjs.map +1 -0
  112. package/dist/pages/build-diff-viewer.mjs +62 -5
  113. package/dist/pages/build-diff-viewer.mjs.map +1 -1
  114. package/dist/pages/build-diff.mjs +34 -13
  115. package/dist/pages/build-diff.mjs.map +1 -1
  116. package/dist/pages/build-tests.mjs +74 -0
  117. package/dist/pages/build-tests.mjs.map +1 -0
  118. package/dist/pages/component-detail.mjs +249 -0
  119. package/dist/pages/component-detail.mjs.map +1 -0
  120. package/dist/pages/component-stories.mjs +168 -0
  121. package/dist/pages/component-stories.mjs.map +1 -0
  122. package/dist/pages/compute-jobs.mjs +13 -26
  123. package/dist/pages/compute-jobs.mjs.map +1 -1
  124. package/dist/pages/design-links.mjs +182 -0
  125. package/dist/pages/design-links.mjs.map +1 -0
  126. package/dist/pages/environment-change.mjs +53 -0
  127. package/dist/pages/environment-change.mjs.map +1 -0
  128. package/dist/pages/health-panel.mjs +126 -0
  129. package/dist/pages/health-panel.mjs.map +1 -0
  130. package/dist/pages/library-grid.mjs +105 -0
  131. package/dist/pages/library-grid.mjs.map +1 -0
  132. package/dist/pages/library-model.mjs +47 -0
  133. package/dist/pages/library-model.mjs.map +1 -0
  134. package/dist/pages/library-snapshots.mjs +130 -0
  135. package/dist/pages/library-snapshots.mjs.map +1 -0
  136. package/dist/pages/library.mjs +112 -218
  137. package/dist/pages/library.mjs.map +1 -1
  138. package/dist/pages/notifications-paused.mjs +22 -0
  139. package/dist/pages/notifications-paused.mjs.map +1 -0
  140. package/dist/pages/profile.mjs +53 -48
  141. package/dist/pages/profile.mjs.map +1 -1
  142. package/dist/pages/project-builds-table.mjs +2 -2
  143. package/dist/pages/project-builds-table.mjs.map +1 -1
  144. package/dist/pages/project-builds.mjs +51 -21
  145. package/dist/pages/project-builds.mjs.map +1 -1
  146. package/dist/pages/project-settings.mjs +51 -12
  147. package/dist/pages/project-settings.mjs.map +1 -1
  148. package/dist/pages/projects-card.mjs +14 -1
  149. package/dist/pages/projects-card.mjs.map +1 -1
  150. package/dist/pages/projects.mjs +6 -2
  151. package/dist/pages/projects.mjs.map +1 -1
  152. package/dist/pages/review-changeset.mjs +116 -0
  153. package/dist/pages/review-changeset.mjs.map +1 -0
  154. package/dist/pages/review-comments.mjs +103 -0
  155. package/dist/pages/review-comments.mjs.map +1 -0
  156. package/dist/pages/review-verdict.mjs +102 -0
  157. package/dist/pages/review-verdict.mjs.map +1 -0
  158. package/dist/pages/settings-ai.mjs +69 -0
  159. package/dist/pages/settings-ai.mjs.map +1 -0
  160. package/dist/pages/settings-capture.mjs +124 -0
  161. package/dist/pages/settings-capture.mjs.map +1 -0
  162. package/dist/pages/settings-design-credential.mjs +99 -0
  163. package/dist/pages/settings-design-credential.mjs.map +1 -0
  164. package/dist/pages/settings-design-links.mjs +179 -0
  165. package/dist/pages/settings-design-links.mjs.map +1 -0
  166. package/dist/pages/settings-design.mjs +48 -0
  167. package/dist/pages/settings-design.mjs.map +1 -0
  168. package/dist/pages/settings-general.mjs +46 -21
  169. package/dist/pages/settings-general.mjs.map +1 -1
  170. package/dist/pages/settings-labels.mjs +43 -2
  171. package/dist/pages/settings-labels.mjs.map +1 -1
  172. package/dist/pages/settings-links.mjs +112 -0
  173. package/dist/pages/settings-links.mjs.map +1 -0
  174. package/dist/pages/settings-notifications-git.mjs +128 -0
  175. package/dist/pages/settings-notifications-git.mjs.map +1 -0
  176. package/dist/pages/settings-notifications.mjs +79 -42
  177. package/dist/pages/settings-notifications.mjs.map +1 -1
  178. package/dist/pages/settings-tests.mjs +33 -2
  179. package/dist/pages/settings-tests.mjs.map +1 -1
  180. package/dist/pages/target-choices.mjs +39 -0
  181. package/dist/pages/target-choices.mjs.map +1 -0
  182. package/dist/publish-setup.mjs +89 -0
  183. package/dist/publish-setup.mjs.map +1 -0
  184. package/dist/retention-timer.mjs +2 -1
  185. package/dist/retention-timer.mjs.map +1 -1
  186. package/dist/review-events.mjs +48 -0
  187. package/dist/review-events.mjs.map +1 -0
  188. package/dist/routers/admin-pages.mjs +45 -3
  189. package/dist/routers/admin-pages.mjs.map +1 -1
  190. package/dist/routers/admin-public-origin.mjs +85 -0
  191. package/dist/routers/admin-public-origin.mjs.map +1 -0
  192. package/dist/routers/assets.mjs +9 -2
  193. package/dist/routers/assets.mjs.map +1 -1
  194. package/dist/routers/auth.mjs +1 -1
  195. package/dist/routers/baseline-guard.mjs +2 -2
  196. package/dist/routers/baseline-guard.mjs.map +1 -1
  197. package/dist/routers/baselines.handlers.mjs +64 -0
  198. package/dist/routers/baselines.handlers.mjs.map +1 -0
  199. package/dist/routers/baselines.mjs +125 -0
  200. package/dist/routers/baselines.mjs.map +1 -0
  201. package/dist/routers/build-cancel.mjs +68 -0
  202. package/dist/routers/build-cancel.mjs.map +1 -0
  203. package/dist/routers/build-changeset.mjs +70 -0
  204. package/dist/routers/build-changeset.mjs.map +1 -0
  205. package/dist/routers/build-reviews.handlers.mjs +86 -0
  206. package/dist/routers/build-reviews.handlers.mjs.map +1 -0
  207. package/dist/routers/build-reviews.mjs +74 -0
  208. package/dist/routers/build-reviews.mjs.map +1 -0
  209. package/dist/routers/builds.handlers.mjs +46 -55
  210. package/dist/routers/builds.handlers.mjs.map +1 -1
  211. package/dist/routers/builds.mjs +71 -17
  212. package/dist/routers/builds.mjs.map +1 -1
  213. package/dist/routers/capture-config.mjs +35 -0
  214. package/dist/routers/capture-config.mjs.map +1 -0
  215. package/dist/routers/comments.mjs +41 -15
  216. package/dist/routers/comments.mjs.map +1 -1
  217. package/dist/routers/component-catalog.handlers.mjs +71 -0
  218. package/dist/routers/component-catalog.handlers.mjs.map +1 -0
  219. package/dist/routers/component.handlers.mjs +121 -0
  220. package/dist/routers/component.handlers.mjs.map +1 -0
  221. package/dist/routers/connections.mjs +118 -0
  222. package/dist/routers/connections.mjs.map +1 -0
  223. package/dist/routers/design-connect.handlers.mjs +44 -0
  224. package/dist/routers/design-connect.handlers.mjs.map +1 -0
  225. package/dist/routers/design.handlers.mjs +114 -0
  226. package/dist/routers/design.handlers.mjs.map +1 -0
  227. package/dist/routers/health-report.mjs +2 -3
  228. package/dist/routers/health-report.mjs.map +1 -1
  229. package/dist/routers/health.mjs +4 -3
  230. package/dist/routers/health.mjs.map +1 -1
  231. package/dist/routers/helpers.mjs +19 -3
  232. package/dist/routers/helpers.mjs.map +1 -1
  233. package/dist/routers/insights-ui.mjs +64 -0
  234. package/dist/routers/insights-ui.mjs.map +1 -0
  235. package/dist/routers/insights.handlers.mjs +45 -0
  236. package/dist/routers/insights.handlers.mjs.map +1 -0
  237. package/dist/routers/insights.mjs +189 -0
  238. package/dist/routers/insights.mjs.map +1 -0
  239. package/dist/routers/links.handlers.mjs +80 -0
  240. package/dist/routers/links.handlers.mjs.map +1 -0
  241. package/dist/routers/links.mjs +139 -0
  242. package/dist/routers/links.mjs.map +1 -0
  243. package/dist/routers/media.mjs +18 -4
  244. package/dist/routers/media.mjs.map +1 -1
  245. package/dist/routers/members.mjs +2 -1
  246. package/dist/routers/members.mjs.map +1 -1
  247. package/dist/routers/notification-channels.mjs +17 -23
  248. package/dist/routers/notification-channels.mjs.map +1 -1
  249. package/dist/routers/oauth-state.mjs +45 -0
  250. package/dist/routers/oauth-state.mjs.map +1 -0
  251. package/dist/routers/preview-base.mjs +38 -0
  252. package/dist/routers/preview-base.mjs.map +1 -0
  253. package/dist/routers/preview-token.mjs +51 -0
  254. package/dist/routers/preview-token.mjs.map +1 -0
  255. package/dist/routers/profile.mjs +3 -1
  256. package/dist/routers/profile.mjs.map +1 -1
  257. package/dist/routers/projects.mjs +17 -1
  258. package/dist/routers/projects.mjs.map +1 -1
  259. package/dist/routers/publish-upload.mjs +78 -0
  260. package/dist/routers/publish-upload.mjs.map +1 -0
  261. package/dist/routers/published-access.mjs +52 -0
  262. package/dist/routers/published-access.mjs.map +1 -0
  263. package/dist/routers/published-files.mjs +52 -0
  264. package/dist/routers/published-files.mjs.map +1 -0
  265. package/dist/routers/review-forms.mjs +72 -0
  266. package/dist/routers/review-forms.mjs.map +1 -0
  267. package/dist/routers/review-section.handlers.mjs +85 -0
  268. package/dist/routers/review-section.handlers.mjs.map +1 -0
  269. package/dist/routers/review-switch-off.mjs +29 -0
  270. package/dist/routers/review-switch-off.mjs.map +1 -0
  271. package/dist/routers/schemas.mjs +140 -22
  272. package/dist/routers/schemas.mjs.map +1 -1
  273. package/dist/routers/scoped-models.mjs +22 -0
  274. package/dist/routers/scoped-models.mjs.map +1 -0
  275. package/dist/routers/settings-ai.mjs +35 -0
  276. package/dist/routers/settings-ai.mjs.map +1 -0
  277. package/dist/routers/settings-capture.mjs +77 -0
  278. package/dist/routers/settings-capture.mjs.map +1 -0
  279. package/dist/routers/settings-design.mjs +103 -0
  280. package/dist/routers/settings-design.mjs.map +1 -0
  281. package/dist/routers/settings-general.mjs +55 -10
  282. package/dist/routers/settings-general.mjs.map +1 -1
  283. package/dist/routers/settings-git-channel.mjs +77 -0
  284. package/dist/routers/settings-git-channel.mjs.map +1 -0
  285. package/dist/routers/settings-labels.mjs +27 -1
  286. package/dist/routers/settings-labels.mjs.map +1 -1
  287. package/dist/routers/settings-links.mjs +63 -0
  288. package/dist/routers/settings-links.mjs.map +1 -0
  289. package/dist/routers/settings-members.mjs +4 -7
  290. package/dist/routers/settings-members.mjs.map +1 -1
  291. package/dist/routers/settings-notifications.mjs +15 -10
  292. package/dist/routers/settings-notifications.mjs.map +1 -1
  293. package/dist/routers/settings-tokens.mjs +2 -5
  294. package/dist/routers/settings-tokens.mjs.map +1 -1
  295. package/dist/routers/settings-webhooks.mjs +7 -24
  296. package/dist/routers/settings-webhooks.mjs.map +1 -1
  297. package/dist/routers/settings.handlers.mjs +107 -26
  298. package/dist/routers/settings.handlers.mjs.map +1 -1
  299. package/dist/routers/settings.mjs +16 -4
  300. package/dist/routers/settings.mjs.map +1 -1
  301. package/dist/routers/signed-storybook.mjs +53 -0
  302. package/dist/routers/signed-storybook.mjs.map +1 -0
  303. package/dist/routers/snapshot-groups.mjs +63 -0
  304. package/dist/routers/snapshot-groups.mjs.map +1 -0
  305. package/dist/routers/snapshots.mjs +103 -5
  306. package/dist/routers/snapshots.mjs.map +1 -1
  307. package/dist/routers/storybook.mjs +37 -88
  308. package/dist/routers/storybook.mjs.map +1 -1
  309. package/dist/routers/tokens.mjs +2 -5
  310. package/dist/routers/tokens.mjs.map +1 -1
  311. package/dist/routers/ui.mjs +34 -5
  312. package/dist/routers/ui.mjs.map +1 -1
  313. package/dist/routers/webhooks.mjs +5 -11
  314. package/dist/routers/webhooks.mjs.map +1 -1
  315. package/dist/routers/well-known.mjs +11 -4
  316. package/dist/routers/well-known.mjs.map +1 -1
  317. package/dist/routes/api-routes.mjs +93 -0
  318. package/dist/routes/api-routes.mjs.map +1 -0
  319. package/dist/routes/classification.mjs +38 -0
  320. package/dist/routes/classification.mjs.map +1 -0
  321. package/dist/routes/openapi-classes.mjs +22 -0
  322. package/dist/routes/openapi-classes.mjs.map +1 -0
  323. package/dist/routes/page-routes.mjs +104 -0
  324. package/dist/routes/page-routes.mjs.map +1 -0
  325. package/dist/runtime.mjs +18 -13
  326. package/dist/runtime.mjs.map +1 -1
  327. package/dist/serverless.mjs +5 -0
  328. package/dist/store.mjs +19 -1
  329. package/dist/store.mjs.map +1 -1
  330. package/dist/targets.mjs +46 -0
  331. package/dist/targets.mjs.map +1 -0
  332. package/dist/ui/buttons.mjs +16 -3
  333. package/dist/ui/buttons.mjs.map +1 -1
  334. package/dist/ui/design-reference.mjs +97 -0
  335. package/dist/ui/design-reference.mjs.map +1 -0
  336. package/dist/ui/document.mjs +1 -2
  337. package/dist/ui/document.mjs.map +1 -1
  338. package/dist/ui/forms.mjs +6 -5
  339. package/dist/ui/forms.mjs.map +1 -1
  340. package/dist/ui/icons/paths.mjs +1 -0
  341. package/dist/ui/icons/paths.mjs.map +1 -1
  342. package/dist/ui/preview-embed.mjs +82 -0
  343. package/dist/ui/preview-embed.mjs.map +1 -0
  344. package/dist/ui/shell/client-script.mjs +85 -0
  345. package/dist/ui/shell/client-script.mjs.map +1 -1
  346. package/dist/ui/shell/nav.mjs +12 -8
  347. package/dist/ui/shell/nav.mjs.map +1 -1
  348. package/dist/ui/shell/review-script.mjs +8 -0
  349. package/dist/ui/shell/review-script.mjs.map +1 -1
  350. package/dist/ui/shell/sidebar.mjs +2 -2
  351. package/dist/ui/shell/sidebar.mjs.map +1 -1
  352. package/package.json +11 -9
  353. package/dist/asset-paths.mjs +0 -23
  354. package/dist/asset-paths.mjs.map +0 -1
  355. package/dist/assets/mark.svg +0 -1
  356. package/dist/pages/settings-status.mjs +0 -137
  357. package/dist/pages/settings-status.mjs.map +0 -1
  358. package/dist/routers/settings-status.mjs +0 -77
  359. package/dist/routers/settings-status.mjs.map +0 -1
  360. package/dist/routers/status-configs.mjs +0 -108
  361. package/dist/routers/status-configs.mjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"webhooks.mjs","names":["notFoundResponse"],"sources":["../../src/routers/webhooks.ts"],"sourcesContent":["import { createRoute, z } from \"@hono/zod-openapi\";\nimport { WebhookModel } from \"@storyshelf/core/models\";\nimport type { ProjectRole } from \"@storyshelf/core/types\";\nimport { randomToken } from \"@storyshelf/core/utils\";\nimport type { ShelfRouter } from \"../app-types.ts\";\nimport { credentialKeys } from \"../credential-keys.ts\";\nimport { getStore } from \"../store.ts\";\nimport { resolveAuthorizedProject, notFound } from \"./helpers.ts\";\nimport {\n notFound as notFoundResponse,\n unauthorized,\n webhookCreatedSchema,\n webhookCreateSchema,\n webhookPublicSchema,\n} from \"./schemas.ts\";\nconst ADMIN_ROLES: readonly ProjectRole[] = [\"admin\"];\n\nconst listWebhooksRoute = createRoute({\n method: \"get\",\n tags: [\"Webhooks\"],\n summary: \"List webhooks\",\n path: \"/api/v1/projects/{slug}/webhooks\",\n request: { params: z.object({ slug: z.string() }) },\n responses: {\n 200: {\n content: { \"application/json\": { schema: webhookPublicSchema.array() } },\n description: \"List webhooks\",\n },\n ...notFoundResponse,\n ...unauthorized,\n },\n});\n\nconst createWebhookRoute = createRoute({\n method: \"post\",\n tags: [\"Webhooks\"],\n summary: \"Create a webhook\",\n path: \"/api/v1/projects/{slug}/webhooks\",\n request: {\n params: z.object({ slug: z.string() }),\n body: { content: { \"application/json\": { schema: webhookCreateSchema } } },\n },\n responses: {\n 201: {\n content: { \"application/json\": { schema: webhookCreatedSchema } },\n description: \"Created webhook\",\n },\n ...notFoundResponse,\n },\n});\n\nconst deleteWebhookRoute = createRoute({\n method: \"delete\",\n tags: [\"Webhooks\"],\n summary: \"Delete a webhook\",\n path: \"/api/v1/projects/{slug}/webhooks/{webhookId}\",\n request: { params: z.object({ slug: z.string(), webhookId: z.string() }) },\n responses: {\n 204: { description: \"Deleted webhook\" },\n ...notFoundResponse,\n },\n});\n\n/** Register the project webhook list, create, and delete endpoints. */\nexport function registerWebhooks(app: ShelfRouter): void {\n app.openapi(listWebhooksRoute, async (c) => {\n const project = await resolveAuthorizedProject(c, c.req.valid(\"param\").slug, ...ADMIN_ROLES);\n const webhooks = await new WebhookModel(\n getStore().db,\n undefined,\n credentialKeys(getStore().config),\n ).list(project.id);\n return c.json(\n webhooks.map((webhook) => ({\n id: webhook.id,\n url: webhook.url,\n events: WebhookModel.eventsOf(webhook),\n })),\n );\n });\n\n app.openapi(createWebhookRoute, async (c) => {\n const project = await resolveAuthorizedProject(c, c.req.valid(\"param\").slug, ...ADMIN_ROLES);\n const body = c.req.valid(\"json\");\n const secret = randomToken(\"whsec_\").value;\n const webhook = await new WebhookModel(\n getStore().db,\n undefined,\n credentialKeys(getStore().config),\n ).create(project.id, { ...body, secret });\n return c.json({ id: webhook.id, url: webhook.url, events: body.events ?? [], secret }, 201);\n });\n\n app.openapi(deleteWebhookRoute, async (c) => {\n const { slug, webhookId } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...ADMIN_ROLES);\n const webhook = await new WebhookModel(\n getStore().db,\n undefined,\n credentialKeys(getStore().config),\n ).get(project.id, webhookId);\n if (!webhook) {\n notFound(\"Webhook not found\");\n }\n await new WebhookModel(getStore().db, undefined, credentialKeys(getStore().config)).remove(\n project.id,\n webhook.id,\n );\n return c.body(null, 204);\n });\n}\n"],"mappings":";;;;;;;;;;;AAeA,MAAM,cAAsC,CAAC,OAAO;AAEpD,MAAM,oBAAoB,YAAY;CACpC,QAAQ;CACR,MAAM,CAAC,UAAU;CACjB,SAAS;CACT,MAAM;CACN,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE;CAClD,WAAW;EACT,KAAK;GACH,SAAS,EAAE,oBAAoB,EAAE,QAAQ,oBAAoB,MAAM,EAAE,EAAE;GACvE,aAAa;EACf;EACA,GAAGA;EACH,GAAG;CACL;AACF,CAAC;AAED,MAAM,qBAAqB,YAAY;CACrC,QAAQ;CACR,MAAM,CAAC,UAAU;CACjB,SAAS;CACT,MAAM;CACN,SAAS;EACP,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;EACrC,MAAM,EAAE,SAAS,EAAE,oBAAoB,EAAE,QAAQ,oBAAoB,EAAE,EAAE;CAC3E;CACA,WAAW;EACT,KAAK;GACH,SAAS,EAAE,oBAAoB,EAAE,QAAQ,qBAAqB,EAAE;GAChE,aAAa;EACf;EACA,GAAGA;CACL;AACF,CAAC;AAED,MAAM,qBAAqB,YAAY;CACrC,QAAQ;CACR,MAAM,CAAC,UAAU;CACjB,SAAS;CACT,MAAM;CACN,SAAS,EAAE,QAAQ,EAAE,OAAO;EAAE,MAAM,EAAE,OAAO;EAAG,WAAW,EAAE,OAAO;CAAE,CAAC,EAAE;CACzE,WAAW;EACT,KAAK,EAAE,aAAa,kBAAkB;EACtC,GAAGA;CACL;AACF,CAAC;;AAGD,SAAgB,iBAAiB,KAAwB;CACvD,IAAI,QAAQ,mBAAmB,OAAO,MAAM;EAC1C,MAAM,UAAU,MAAM,yBAAyB,GAAG,EAAE,IAAI,MAAM,OAAO,CAAC,CAAC,MAAM,GAAG,WAAW;EAC3F,MAAM,WAAW,MAAM,IAAI,aACzB,SAAS,CAAC,CAAC,IACX,KAAA,GACA,eAAe,SAAS,CAAC,CAAC,MAAM,CAClC,CAAC,CAAC,KAAK,QAAQ,EAAE;EACjB,OAAO,EAAE,KACP,SAAS,KAAK,aAAa;GACzB,IAAI,QAAQ;GACZ,KAAK,QAAQ;GACb,QAAQ,aAAa,SAAS,OAAO;EACvC,EAAE,CACJ;CACF,CAAC;CAED,IAAI,QAAQ,oBAAoB,OAAO,MAAM;EAC3C,MAAM,UAAU,MAAM,yBAAyB,GAAG,EAAE,IAAI,MAAM,OAAO,CAAC,CAAC,MAAM,GAAG,WAAW;EAC3F,MAAM,OAAO,EAAE,IAAI,MAAM,MAAM;EAC/B,MAAM,SAAS,YAAY,QAAQ,CAAC,CAAC;EACrC,MAAM,UAAU,MAAM,IAAI,aACxB,SAAS,CAAC,CAAC,IACX,KAAA,GACA,eAAe,SAAS,CAAC,CAAC,MAAM,CAClC,CAAC,CAAC,OAAO,QAAQ,IAAI;GAAE,GAAG;GAAM;EAAO,CAAC;EACxC,OAAO,EAAE,KAAK;GAAE,IAAI,QAAQ;GAAI,KAAK,QAAQ;GAAK,QAAQ,KAAK,UAAU,CAAC;GAAG;EAAO,GAAG,GAAG;CAC5F,CAAC;CAED,IAAI,QAAQ,oBAAoB,OAAO,MAAM;EAC3C,MAAM,EAAE,MAAM,cAAc,EAAE,IAAI,MAAM,OAAO;EAC/C,MAAM,UAAU,MAAM,yBAAyB,GAAG,MAAM,GAAG,WAAW;EACtE,MAAM,UAAU,MAAM,IAAI,aACxB,SAAS,CAAC,CAAC,IACX,KAAA,GACA,eAAe,SAAS,CAAC,CAAC,MAAM,CAClC,CAAC,CAAC,IAAI,QAAQ,IAAI,SAAS;EAC3B,IAAI,CAAC,SACH,SAAS,mBAAmB;EAE9B,MAAM,IAAI,aAAa,SAAS,CAAC,CAAC,IAAI,KAAA,GAAW,eAAe,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAClF,QAAQ,IACR,QAAQ,EACV;EACA,OAAO,EAAE,KAAK,MAAM,GAAG;CACzB,CAAC;AACH"}
1
+ {"version":3,"file":"webhooks.mjs","names":["notFoundResponse"],"sources":["../../src/routers/webhooks.ts"],"sourcesContent":["import { createRoute, z } from \"@hono/zod-openapi\";\nimport { WebhookModel } from \"@storyshelf/core/models\";\nimport type { ProjectRole } from \"@storyshelf/core/types\";\nimport type { ShelfRouter } from \"../app-types.ts\";\nimport { resolveAuthorizedProject, notFound } from \"./helpers.ts\";\nimport {\n notFound as notFoundResponse,\n unauthorized,\n webhookCreatedSchema,\n webhookCreateSchema,\n webhookPublicSchema,\n} from \"./schemas.ts\";\nimport { webhookModel } from \"./scoped-models.ts\";\nconst ADMIN_ROLES: readonly ProjectRole[] = [\"admin\"];\n\nconst listWebhooksRoute = createRoute({\n method: \"get\",\n tags: [\"Webhooks\"],\n summary: \"List webhooks\",\n path: \"/api/v1/projects/{slug}/webhooks\",\n request: { params: z.object({ slug: z.string() }) },\n responses: {\n 200: {\n content: { \"application/json\": { schema: webhookPublicSchema.array() } },\n description: \"List webhooks\",\n },\n ...notFoundResponse,\n ...unauthorized,\n },\n});\n\nconst createWebhookRoute = createRoute({\n method: \"post\",\n tags: [\"Webhooks\"],\n summary: \"Create a webhook\",\n path: \"/api/v1/projects/{slug}/webhooks\",\n request: {\n params: z.object({ slug: z.string() }),\n body: { content: { \"application/json\": { schema: webhookCreateSchema } } },\n },\n responses: {\n 201: {\n content: { \"application/json\": { schema: webhookCreatedSchema } },\n description: \"Created webhook\",\n },\n ...notFoundResponse,\n },\n});\n\nconst deleteWebhookRoute = createRoute({\n method: \"delete\",\n tags: [\"Webhooks\"],\n summary: \"Delete a webhook\",\n path: \"/api/v1/projects/{slug}/webhooks/{webhookId}\",\n request: { params: z.object({ slug: z.string(), webhookId: z.string() }) },\n responses: {\n 204: { description: \"Deleted webhook\" },\n ...notFoundResponse,\n },\n});\n\n/** Register the project webhook list, create, and delete endpoints. */\nexport function registerWebhooks(app: ShelfRouter): void {\n app.openapi(listWebhooksRoute, async (c) => {\n const project = await resolveAuthorizedProject(c, c.req.valid(\"param\").slug, ...ADMIN_ROLES);\n const webhooks = await webhookModel().list(project.id);\n return c.json(\n webhooks.map((webhook) => ({\n id: webhook.id,\n url: webhook.url,\n events: WebhookModel.eventsOf(webhook),\n })),\n );\n });\n\n app.openapi(createWebhookRoute, async (c) => {\n const project = await resolveAuthorizedProject(c, c.req.valid(\"param\").slug, ...ADMIN_ROLES);\n const body = c.req.valid(\"json\");\n const { webhook, secret } = await webhookModel().issue(project.id, body);\n return c.json({ id: webhook.id, url: webhook.url, events: body.events ?? [], secret }, 201);\n });\n\n app.openapi(deleteWebhookRoute, async (c) => {\n const { slug, webhookId } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...ADMIN_ROLES);\n const webhook = await webhookModel().get(project.id, webhookId);\n if (!webhook) {\n notFound(\"Webhook not found\");\n }\n await webhookModel().remove(project.id, webhook.id);\n return c.body(null, 204);\n });\n}\n"],"mappings":";;;;;;;;;AAaA,MAAM,cAAsC,CAAC,OAAO;AAEpD,MAAM,oBAAoB,YAAY;CACpC,QAAQ;CACR,MAAM,CAAC,UAAU;CACjB,SAAS;CACT,MAAM;CACN,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE;CAClD,WAAW;EACT,KAAK;GACH,SAAS,EAAE,oBAAoB,EAAE,QAAQ,oBAAoB,MAAM,EAAE,EAAE;GACvE,aAAa;EACf;EACA,GAAGA;EACH,GAAG;CACL;AACF,CAAC;AAED,MAAM,qBAAqB,YAAY;CACrC,QAAQ;CACR,MAAM,CAAC,UAAU;CACjB,SAAS;CACT,MAAM;CACN,SAAS;EACP,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;EACrC,MAAM,EAAE,SAAS,EAAE,oBAAoB,EAAE,QAAQ,oBAAoB,EAAE,EAAE;CAC3E;CACA,WAAW;EACT,KAAK;GACH,SAAS,EAAE,oBAAoB,EAAE,QAAQ,qBAAqB,EAAE;GAChE,aAAa;EACf;EACA,GAAGA;CACL;AACF,CAAC;AAED,MAAM,qBAAqB,YAAY;CACrC,QAAQ;CACR,MAAM,CAAC,UAAU;CACjB,SAAS;CACT,MAAM;CACN,SAAS,EAAE,QAAQ,EAAE,OAAO;EAAE,MAAM,EAAE,OAAO;EAAG,WAAW,EAAE,OAAO;CAAE,CAAC,EAAE;CACzE,WAAW;EACT,KAAK,EAAE,aAAa,kBAAkB;EACtC,GAAGA;CACL;AACF,CAAC;;AAGD,SAAgB,iBAAiB,KAAwB;CACvD,IAAI,QAAQ,mBAAmB,OAAO,MAAM;EAC1C,MAAM,UAAU,MAAM,yBAAyB,GAAG,EAAE,IAAI,MAAM,OAAO,CAAC,CAAC,MAAM,GAAG,WAAW;EAC3F,MAAM,WAAW,MAAM,aAAa,CAAC,CAAC,KAAK,QAAQ,EAAE;EACrD,OAAO,EAAE,KACP,SAAS,KAAK,aAAa;GACzB,IAAI,QAAQ;GACZ,KAAK,QAAQ;GACb,QAAQ,aAAa,SAAS,OAAO;EACvC,EAAE,CACJ;CACF,CAAC;CAED,IAAI,QAAQ,oBAAoB,OAAO,MAAM;EAC3C,MAAM,UAAU,MAAM,yBAAyB,GAAG,EAAE,IAAI,MAAM,OAAO,CAAC,CAAC,MAAM,GAAG,WAAW;EAC3F,MAAM,OAAO,EAAE,IAAI,MAAM,MAAM;EAC/B,MAAM,EAAE,SAAS,WAAW,MAAM,aAAa,CAAC,CAAC,MAAM,QAAQ,IAAI,IAAI;EACvE,OAAO,EAAE,KAAK;GAAE,IAAI,QAAQ;GAAI,KAAK,QAAQ;GAAK,QAAQ,KAAK,UAAU,CAAC;GAAG;EAAO,GAAG,GAAG;CAC5F,CAAC;CAED,IAAI,QAAQ,oBAAoB,OAAO,MAAM;EAC3C,MAAM,EAAE,MAAM,cAAc,EAAE,IAAI,MAAM,OAAO;EAC/C,MAAM,UAAU,MAAM,yBAAyB,GAAG,MAAM,GAAG,WAAW;EACtE,MAAM,UAAU,MAAM,aAAa,CAAC,CAAC,IAAI,QAAQ,IAAI,SAAS;EAC9D,IAAI,CAAC,SACH,SAAS,mBAAmB;EAE9B,MAAM,aAAa,CAAC,CAAC,OAAO,QAAQ,IAAI,QAAQ,EAAE;EAClD,OAAO,EAAE,KAAK,MAAM,GAAG;CACzB,CAAC;AACH"}
@@ -11,13 +11,20 @@ function ssoProviders(auth, origin) {
11
11
  redirectUris: method.kind === "sso" ? [`${origin}/api/auth/sso/callback/${method.id}`, `${origin}/api/auth/sso/saml2/sp/acs/${method.id}`] : [`${origin}/api/auth/callback/${method.id}`]
12
12
  }));
13
13
  }
14
- function requestOrigin(c) {
15
- return getStore().config.publicBaseUrl ?? new URL(c.req.url).origin;
14
+ /**
15
+ * The origin of this interactive, request-bound document (ADR 0007, decision 6): the confirmed
16
+ * public origin, else the address the request came in on (the one place besides OAuth redirect
17
+ * URIs where authentication may use it).
18
+ */
19
+ async function requestOrigin(c) {
20
+ const { publicOrigin, config } = getStore();
21
+ const own = new URL(c.req.url).origin;
22
+ return publicOrigin ? await publicOrigin.authOrigin(own) : config.publicBaseUrl ?? own;
16
23
  }
17
24
  /** Register public relying-party helper documents (no auth required). */
18
25
  function registerWellKnown(app, auth) {
19
- app.get("/.well-known/openid-configuration", (c) => {
20
- const origin = requestOrigin(c);
26
+ app.get("/.well-known/openid-configuration", async (c) => {
27
+ const origin = await requestOrigin(c);
21
28
  const providers = ssoProviders(auth, origin);
22
29
  return c.json({
23
30
  issuer: origin,
@@ -1 +1 @@
1
- {"version":3,"file":"well-known.mjs","names":[],"sources":["../../src/routers/well-known.ts"],"sourcesContent":["import type { Auth } from \"@storyshelf/core/auth\";\nimport type { Context } from \"hono\";\nimport type { ShelfRouter } from \"../app-types.ts\";\nimport { getStore } from \"../store.ts\";\n\n/** One SSO-capable login method with its IdP reply URLs. */\ninterface SsoProviderLink {\n id: string;\n label: string;\n redirectUris: string[];\n}\n\n/** SSO-capable login methods (oauth buttons + SSO) with IdP reply URLs. */\nfunction ssoProviders(auth: Auth, origin: string): SsoProviderLink[] {\n return auth\n .loginMethods()\n .filter((method) => method.kind === \"oauth\" || method.kind === \"sso\")\n .map((method) => ({\n id: method.id,\n label: method.label,\n redirectUris:\n method.kind === \"sso\"\n ? [\n `${origin}/api/auth/sso/callback/${method.id}`,\n `${origin}/api/auth/sso/saml2/sp/acs/${method.id}`,\n ]\n : [`${origin}/api/auth/callback/${method.id}`],\n }));\n}\n\nfunction requestOrigin(c: Context): string {\n return getStore().config.publicBaseUrl ?? new URL(c.req.url).origin;\n}\n\n/** Register public relying-party helper documents (no auth required). */\nexport function registerWellKnown(app: ShelfRouter, auth: Auth): void {\n app.get(\"/.well-known/openid-configuration\", (c) => {\n const origin = requestOrigin(c);\n const providers = ssoProviders(auth, origin);\n return c.json(\n {\n issuer: origin,\n note: \"StoryShelf is an OpenID Connect relying party, not an identity provider. This document helps operators register redirect URIs with their provider; it is not provider metadata.\",\n response_types_supported: [\"code\"],\n scopes_supported: [\"openid\", \"email\", \"profile\"],\n redirect_uris: providers.flatMap((provider) => provider.redirectUris),\n providers: providers.map((method) => ({ id: method.id, label: method.label })),\n },\n 200,\n { \"Cache-Control\": \"public, max-age=300\" },\n );\n });\n\n // oxlint-disable-next-line typescript/promise-function-async -- Hono handler may return Response directly\n app.get(\"/.well-known/change-password\", (c) => c.redirect(\"/profile\", 302));\n}\n"],"mappings":";;;;;;AAaA,SAAS,aAAa,MAAY,QAAmC;CACnE,OAAO,KACJ,aAAa,CAAC,CACd,QAAQ,WAAW,OAAO,SAAS,WAAW,OAAO,SAAS,KAAK,CAAC,CACpE,KAAK,YAAY;EAChB,IAAI,OAAO;EACX,OAAO,OAAO;EACd,cACE,OAAO,SAAS,QACZ,CACE,GAAG,OAAO,yBAAyB,OAAO,MAC1C,GAAG,OAAO,6BAA6B,OAAO,IAChD,IACA,CAAC,GAAG,OAAO,qBAAqB,OAAO,IAAI;CACnD,EAAE;AACN;AAEA,SAAS,cAAc,GAAoB;CACzC,OAAO,SAAS,CAAC,CAAC,OAAO,iBAAiB,IAAI,IAAI,EAAE,IAAI,GAAG,CAAC,CAAC;AAC/D;;AAGA,SAAgB,kBAAkB,KAAkB,MAAkB;CACpE,IAAI,IAAI,sCAAsC,MAAM;EAClD,MAAM,SAAS,cAAc,CAAC;EAC9B,MAAM,YAAY,aAAa,MAAM,MAAM;EAC3C,OAAO,EAAE,KACP;GACE,QAAQ;GACR,MAAM;GACN,0BAA0B,CAAC,MAAM;GACjC,kBAAkB;IAAC;IAAU;IAAS;GAAS;GAC/C,eAAe,UAAU,SAAS,aAAa,SAAS,YAAY;GACpE,WAAW,UAAU,KAAK,YAAY;IAAE,IAAI,OAAO;IAAI,OAAO,OAAO;GAAM,EAAE;EAC/E,GACA,KACA,EAAE,iBAAiB,sBAAsB,CAC3C;CACF,CAAC;CAGD,IAAI,IAAI,iCAAiC,MAAM,EAAE,SAAS,YAAY,GAAG,CAAC;AAC5E"}
1
+ {"version":3,"file":"well-known.mjs","names":[],"sources":["../../src/routers/well-known.ts"],"sourcesContent":["import type { Auth } from \"@storyshelf/core/auth\";\nimport type { Context } from \"hono\";\nimport type { ShelfRouter } from \"../app-types.ts\";\nimport { getStore } from \"../store.ts\";\n\n/** One SSO-capable login method with its IdP reply URLs. */\ninterface SsoProviderLink {\n id: string;\n label: string;\n redirectUris: string[];\n}\n\n/** SSO-capable login methods (oauth buttons + SSO) with IdP reply URLs. */\nfunction ssoProviders(auth: Auth, origin: string): SsoProviderLink[] {\n return auth\n .loginMethods()\n .filter((method) => method.kind === \"oauth\" || method.kind === \"sso\")\n .map((method) => ({\n id: method.id,\n label: method.label,\n redirectUris:\n method.kind === \"sso\"\n ? [\n `${origin}/api/auth/sso/callback/${method.id}`,\n `${origin}/api/auth/sso/saml2/sp/acs/${method.id}`,\n ]\n : [`${origin}/api/auth/callback/${method.id}`],\n }));\n}\n\n/**\n * The origin of this interactive, request-bound document (ADR 0007, decision 6): the confirmed\n * public origin, else the address the request came in on (the one place besides OAuth redirect\n * URIs where authentication may use it).\n */\nasync function requestOrigin(c: Context): Promise<string> {\n const { publicOrigin, config } = getStore();\n const own = new URL(c.req.url).origin;\n return publicOrigin ? await publicOrigin.authOrigin(own) : (config.publicBaseUrl ?? own);\n}\n\n/** Register public relying-party helper documents (no auth required). */\nexport function registerWellKnown(app: ShelfRouter, auth: Auth): void {\n app.get(\"/.well-known/openid-configuration\", async (c) => {\n const origin = await requestOrigin(c);\n const providers = ssoProviders(auth, origin);\n return c.json(\n {\n issuer: origin,\n note: \"StoryShelf is an OpenID Connect relying party, not an identity provider. This document helps operators register redirect URIs with their provider; it is not provider metadata.\",\n response_types_supported: [\"code\"],\n scopes_supported: [\"openid\", \"email\", \"profile\"],\n redirect_uris: providers.flatMap((provider) => provider.redirectUris),\n providers: providers.map((method) => ({ id: method.id, label: method.label })),\n },\n 200,\n { \"Cache-Control\": \"public, max-age=300\" },\n );\n });\n\n // oxlint-disable-next-line typescript/promise-function-async -- Hono handler may return Response directly\n app.get(\"/.well-known/change-password\", (c) => c.redirect(\"/profile\", 302));\n}\n"],"mappings":";;;;;;AAaA,SAAS,aAAa,MAAY,QAAmC;CACnE,OAAO,KACJ,aAAa,CAAC,CACd,QAAQ,WAAW,OAAO,SAAS,WAAW,OAAO,SAAS,KAAK,CAAC,CACpE,KAAK,YAAY;EAChB,IAAI,OAAO;EACX,OAAO,OAAO;EACd,cACE,OAAO,SAAS,QACZ,CACE,GAAG,OAAO,yBAAyB,OAAO,MAC1C,GAAG,OAAO,6BAA6B,OAAO,IAChD,IACA,CAAC,GAAG,OAAO,qBAAqB,OAAO,IAAI;CACnD,EAAE;AACN;;;;;;AAOA,eAAe,cAAc,GAA6B;CACxD,MAAM,EAAE,cAAc,WAAW,SAAS;CAC1C,MAAM,MAAM,IAAI,IAAI,EAAE,IAAI,GAAG,CAAC,CAAC;CAC/B,OAAO,eAAe,MAAM,aAAa,WAAW,GAAG,IAAK,OAAO,iBAAiB;AACtF;;AAGA,SAAgB,kBAAkB,KAAkB,MAAkB;CACpE,IAAI,IAAI,qCAAqC,OAAO,MAAM;EACxD,MAAM,SAAS,MAAM,cAAc,CAAC;EACpC,MAAM,YAAY,aAAa,MAAM,MAAM;EAC3C,OAAO,EAAE,KACP;GACE,QAAQ;GACR,MAAM;GACN,0BAA0B,CAAC,MAAM;GACjC,kBAAkB;IAAC;IAAU;IAAS;GAAS;GAC/C,eAAe,UAAU,SAAS,aAAa,SAAS,YAAY;GACpE,WAAW,UAAU,KAAK,YAAY;IAAE,IAAI,OAAO;IAAI,OAAO,OAAO;GAAM,EAAE;EAC/E,GACA,KACA,EAAE,iBAAiB,sBAAsB,CAC3C;CACF,CAAC;CAGD,IAAI,IAAI,iCAAiC,MAAM,EAAE,SAAS,YAAY,GAAG,CAAC;AAC5E"}
@@ -0,0 +1,93 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ //#region src/routes/api-routes.ts
5
+ /** JSON API routes, keyed by `METHOD /hono-pattern`. */
6
+ const API_ROUTE_CLASSES = {
7
+ "GET /api/v1/projects": "hosting",
8
+ "POST /api/v1/projects": "hosting",
9
+ "GET /api/v1/projects/:slug": "hosting",
10
+ "PATCH /api/v1/projects/:slug": "hosting",
11
+ "DELETE /api/v1/projects/:slug": "hosting",
12
+ "GET /api/v1/projects/:slug/builds": "hosting",
13
+ "POST /api/v1/projects/:slug/builds": "hosting",
14
+ "POST /api/v1/projects/:slug/builds/:buildId/usage": "hosting",
15
+ "PUT /api/v1/projects/:slug/builds/:buildId/zip": "hosting",
16
+ "POST /api/v1/projects/:slug/builds/:buildId/dedup": "hosting",
17
+ "POST /api/v1/projects/:slug/builds/:buildId/content": "hosting",
18
+ "POST /api/v1/projects/:slug/builds/:buildId/manifest": "hosting",
19
+ "GET /api/v1/projects/:slug/builds/:buildId": "hosting",
20
+ "DELETE /api/v1/projects/:slug/builds/:buildId": "hosting",
21
+ "GET /api/v1/projects/:slug/label-types": "hosting",
22
+ "POST /api/v1/projects/:slug/label-types": "hosting",
23
+ "DELETE /api/v1/projects/:slug/label-types/:key": "hosting",
24
+ "PATCH /api/v1/projects/:slug/label-types/:key": "hosting",
25
+ "GET /api/v1/projects/:slug/links": "hosting",
26
+ "PUT /api/v1/projects/:slug/links": "hosting",
27
+ "GET /api/v1/projects/:slug/members": "hosting",
28
+ "POST /api/v1/projects/:slug/members": "hosting",
29
+ "PATCH /api/v1/projects/:slug/members/:userId": "hosting",
30
+ "DELETE /api/v1/projects/:slug/members/:userId": "hosting",
31
+ "GET /api/v1/projects/:slug/group-mappings": "hosting",
32
+ "POST /api/v1/projects/:slug/group-mappings": "hosting",
33
+ "DELETE /api/v1/projects/:slug/group-mappings/:mappingId": "hosting",
34
+ "GET /api/v1/projects/:slug/tokens": "hosting",
35
+ "POST /api/v1/projects/:slug/tokens": "hosting",
36
+ "DELETE /api/v1/projects/:slug/tokens/:tokenId": "hosting",
37
+ "GET /api/v1/projects/:slug/webhooks": "hosting",
38
+ "POST /api/v1/projects/:slug/webhooks": "hosting",
39
+ "DELETE /api/v1/projects/:slug/webhooks/:webhookId": "hosting",
40
+ "GET /api/v1/projects/:slug/notification-channels": "hosting",
41
+ "POST /api/v1/projects/:slug/notification-channels": "hosting",
42
+ "DELETE /api/v1/projects/:slug/notification-channels/:channelId": "hosting",
43
+ "GET /api/v1/projects/:slug/notifications/me": "hosting",
44
+ "PUT /api/v1/projects/:slug/notifications/me": "hosting",
45
+ "DELETE /api/v1/projects/:slug/notifications/me": "hosting",
46
+ "GET /api/v1/projects/:slug/notifications": "hosting",
47
+ "GET /api/v1/admin/notification-channels": "hosting",
48
+ "POST /api/v1/admin/notification-channels": "hosting",
49
+ "DELETE /api/v1/admin/notification-channels/:channelId": "hosting",
50
+ "GET /api/v1/health": "hosting",
51
+ "POST /api/v1/health": "hosting",
52
+ "POST /api/v1/admin/credentials/reencrypt": "hosting",
53
+ "POST /api/v1/admin/purge": "hosting",
54
+ "GET /api/v1/openapi.json": "hosting",
55
+ "GET /api/v1/docs": "hosting",
56
+ "POST /api/v1/projects/:slug/builds/:buildId/affected": "testing",
57
+ "POST /api/v1/projects/:slug/builds/:buildId/retry": "testing",
58
+ "POST /api/v1/projects/:slug/builds/:buildId/cancel": "testing",
59
+ "GET /api/v1/projects/:slug/builds/:buildId/snapshots": "testing",
60
+ "GET /api/v1/projects/:slug/builds/:buildId/preview-token": "testing",
61
+ "POST /api/v1/projects/:slug/builds/:buildId/snapshots/:snapshotId/approve": "testing",
62
+ "POST /api/v1/projects/:slug/builds/:buildId/snapshots/:snapshotId/reject": "testing",
63
+ "POST /api/v1/projects/:slug/builds/:buildId/approve-all": "testing",
64
+ "POST /api/v1/projects/:slug/builds/:buildId/reject-all": "testing",
65
+ "POST /api/v1/projects/:slug/builds/:buildId/stories/:storyId/modes/:modeKey/approve": "testing",
66
+ "POST /api/v1/projects/:slug/builds/:buildId/stories/:storyId/modes/:modeKey/reject": "testing",
67
+ "POST /api/v1/projects/:slug/builds/:buildId/rediff": "testing",
68
+ "GET /api/v1/projects/:slug/builds/:buildId/attempts": "testing",
69
+ "GET /api/v1/projects/:slug/builds/:buildId/attempts/:attemptNo": "testing",
70
+ "GET /api/v1/projects/:slug/builds/:buildId/attempts/:attemptNo/logs": "testing",
71
+ "GET /api/v1/projects/:slug/builds/:buildId/snapshots/:snapshotId/usage": "testing",
72
+ "GET /api/v1/projects/:slug/builds/:buildId/snapshots/:snapshotId/image": "testing",
73
+ "GET /api/v1/projects/:slug/builds/:buildId/snapshots/:snapshotId/diff": "testing",
74
+ "GET /api/v1/projects/:slug/builds/:buildId/snapshots/:snapshotId/baseline": "testing",
75
+ "GET /api/v1/projects/:slug/design/renders/:renderId": "testing",
76
+ "GET /api/v1/projects/:slug/baselines/export": "testing",
77
+ "POST /api/v1/projects/:slug/baselines/import": "testing",
78
+ "GET /api/v1/projects/:slug/builds/:buildId/insights/latest": "testing",
79
+ "GET /api/v1/projects/:slug/builds/:buildId/insights": "testing",
80
+ "POST /api/v1/projects/:slug/builds/:buildId/insights": "testing",
81
+ "GET /api/v1/projects/:slug/insights/health": "testing",
82
+ "POST /api/v1/projects/:slug/insights/health": "testing",
83
+ "GET /api/v1/projects/:slug/builds/:buildId/comments": "review",
84
+ "POST /api/v1/projects/:slug/builds/:buildId/comments": "review",
85
+ "POST /api/v1/projects/:slug/builds/:buildId/comments/:commentId/resolve": "review",
86
+ "GET /api/v1/projects/:slug/builds/:buildId/reviews": "review",
87
+ "POST /api/v1/projects/:slug/builds/:buildId/reviews": "review",
88
+ "GET /api/v1/projects/:slug/builds/:buildId/changeset": "review"
89
+ };
90
+ //#endregion
91
+ export { API_ROUTE_CLASSES };
92
+
93
+ //# sourceMappingURL=api-routes.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-routes.mjs","names":[],"sources":["../../src/routes/api-routes.ts"],"sourcesContent":["/** Classes of the JSON API routes under `/api/v1`. */\nimport type { RouteClass } from \"./classification.ts\";\n\n/** JSON API routes, keyed by `METHOD /hono-pattern`. */\nexport const API_ROUTE_CLASSES: Readonly<Record<string, RouteClass>> = {\n // hosting\n \"GET /api/v1/projects\": \"hosting\",\n \"POST /api/v1/projects\": \"hosting\",\n \"GET /api/v1/projects/:slug\": \"hosting\",\n \"PATCH /api/v1/projects/:slug\": \"hosting\",\n \"DELETE /api/v1/projects/:slug\": \"hosting\",\n \"GET /api/v1/projects/:slug/builds\": \"hosting\",\n \"POST /api/v1/projects/:slug/builds\": \"hosting\",\n \"POST /api/v1/projects/:slug/builds/:buildId/usage\": \"hosting\",\n \"PUT /api/v1/projects/:slug/builds/:buildId/zip\": \"hosting\",\n \"POST /api/v1/projects/:slug/builds/:buildId/dedup\": \"hosting\",\n \"POST /api/v1/projects/:slug/builds/:buildId/content\": \"hosting\",\n \"POST /api/v1/projects/:slug/builds/:buildId/manifest\": \"hosting\",\n \"GET /api/v1/projects/:slug/builds/:buildId\": \"hosting\",\n \"DELETE /api/v1/projects/:slug/builds/:buildId\": \"hosting\",\n \"GET /api/v1/projects/:slug/label-types\": \"hosting\",\n \"POST /api/v1/projects/:slug/label-types\": \"hosting\",\n \"DELETE /api/v1/projects/:slug/label-types/:key\": \"hosting\",\n \"PATCH /api/v1/projects/:slug/label-types/:key\": \"hosting\",\n \"GET /api/v1/projects/:slug/links\": \"hosting\",\n \"PUT /api/v1/projects/:slug/links\": \"hosting\",\n \"GET /api/v1/projects/:slug/members\": \"hosting\",\n \"POST /api/v1/projects/:slug/members\": \"hosting\",\n \"PATCH /api/v1/projects/:slug/members/:userId\": \"hosting\",\n \"DELETE /api/v1/projects/:slug/members/:userId\": \"hosting\",\n \"GET /api/v1/projects/:slug/group-mappings\": \"hosting\",\n \"POST /api/v1/projects/:slug/group-mappings\": \"hosting\",\n \"DELETE /api/v1/projects/:slug/group-mappings/:mappingId\": \"hosting\",\n \"GET /api/v1/projects/:slug/tokens\": \"hosting\",\n \"POST /api/v1/projects/:slug/tokens\": \"hosting\",\n \"DELETE /api/v1/projects/:slug/tokens/:tokenId\": \"hosting\",\n \"GET /api/v1/projects/:slug/webhooks\": \"hosting\",\n \"POST /api/v1/projects/:slug/webhooks\": \"hosting\",\n \"DELETE /api/v1/projects/:slug/webhooks/:webhookId\": \"hosting\",\n \"GET /api/v1/projects/:slug/notification-channels\": \"hosting\",\n \"POST /api/v1/projects/:slug/notification-channels\": \"hosting\",\n \"DELETE /api/v1/projects/:slug/notification-channels/:channelId\": \"hosting\",\n \"GET /api/v1/projects/:slug/notifications/me\": \"hosting\",\n \"PUT /api/v1/projects/:slug/notifications/me\": \"hosting\",\n \"DELETE /api/v1/projects/:slug/notifications/me\": \"hosting\",\n \"GET /api/v1/projects/:slug/notifications\": \"hosting\",\n \"GET /api/v1/admin/notification-channels\": \"hosting\",\n \"POST /api/v1/admin/notification-channels\": \"hosting\",\n \"DELETE /api/v1/admin/notification-channels/:channelId\": \"hosting\",\n \"GET /api/v1/health\": \"hosting\",\n \"POST /api/v1/health\": \"hosting\",\n \"POST /api/v1/admin/credentials/reencrypt\": \"hosting\",\n \"POST /api/v1/admin/purge\": \"hosting\",\n \"GET /api/v1/openapi.json\": \"hosting\",\n \"GET /api/v1/docs\": \"hosting\",\n // testing\n \"POST /api/v1/projects/:slug/builds/:buildId/affected\": \"testing\",\n \"POST /api/v1/projects/:slug/builds/:buildId/retry\": \"testing\",\n \"POST /api/v1/projects/:slug/builds/:buildId/cancel\": \"testing\",\n \"GET /api/v1/projects/:slug/builds/:buildId/snapshots\": \"testing\",\n \"GET /api/v1/projects/:slug/builds/:buildId/preview-token\": \"testing\",\n \"POST /api/v1/projects/:slug/builds/:buildId/snapshots/:snapshotId/approve\": \"testing\",\n \"POST /api/v1/projects/:slug/builds/:buildId/snapshots/:snapshotId/reject\": \"testing\",\n \"POST /api/v1/projects/:slug/builds/:buildId/approve-all\": \"testing\",\n \"POST /api/v1/projects/:slug/builds/:buildId/reject-all\": \"testing\",\n \"POST /api/v1/projects/:slug/builds/:buildId/stories/:storyId/modes/:modeKey/approve\": \"testing\",\n \"POST /api/v1/projects/:slug/builds/:buildId/stories/:storyId/modes/:modeKey/reject\": \"testing\",\n \"POST /api/v1/projects/:slug/builds/:buildId/rediff\": \"testing\",\n \"GET /api/v1/projects/:slug/builds/:buildId/attempts\": \"testing\",\n \"GET /api/v1/projects/:slug/builds/:buildId/attempts/:attemptNo\": \"testing\",\n \"GET /api/v1/projects/:slug/builds/:buildId/attempts/:attemptNo/logs\": \"testing\",\n \"GET /api/v1/projects/:slug/builds/:buildId/snapshots/:snapshotId/usage\": \"testing\",\n \"GET /api/v1/projects/:slug/builds/:buildId/snapshots/:snapshotId/image\": \"testing\",\n \"GET /api/v1/projects/:slug/builds/:buildId/snapshots/:snapshotId/diff\": \"testing\",\n \"GET /api/v1/projects/:slug/builds/:buildId/snapshots/:snapshotId/baseline\": \"testing\",\n \"GET /api/v1/projects/:slug/design/renders/:renderId\": \"testing\",\n \"GET /api/v1/projects/:slug/baselines/export\": \"testing\",\n \"POST /api/v1/projects/:slug/baselines/import\": \"testing\",\n \"GET /api/v1/projects/:slug/builds/:buildId/insights/latest\": \"testing\",\n \"GET /api/v1/projects/:slug/builds/:buildId/insights\": \"testing\",\n \"POST /api/v1/projects/:slug/builds/:buildId/insights\": \"testing\",\n \"GET /api/v1/projects/:slug/insights/health\": \"testing\",\n \"POST /api/v1/projects/:slug/insights/health\": \"testing\",\n // review\n \"GET /api/v1/projects/:slug/builds/:buildId/comments\": \"review\",\n \"POST /api/v1/projects/:slug/builds/:buildId/comments\": \"review\",\n \"POST /api/v1/projects/:slug/builds/:buildId/comments/:commentId/resolve\": \"review\",\n \"GET /api/v1/projects/:slug/builds/:buildId/reviews\": \"review\",\n \"POST /api/v1/projects/:slug/builds/:buildId/reviews\": \"review\",\n \"GET /api/v1/projects/:slug/builds/:buildId/changeset\": \"review\",\n};\n"],"mappings":";;;;;AAIA,MAAa,oBAA0D;CAErE,wBAAwB;CACxB,yBAAyB;CACzB,8BAA8B;CAC9B,gCAAgC;CAChC,iCAAiC;CACjC,qCAAqC;CACrC,sCAAsC;CACtC,qDAAqD;CACrD,kDAAkD;CAClD,qDAAqD;CACrD,uDAAuD;CACvD,wDAAwD;CACxD,8CAA8C;CAC9C,iDAAiD;CACjD,0CAA0C;CAC1C,2CAA2C;CAC3C,kDAAkD;CAClD,iDAAiD;CACjD,oCAAoC;CACpC,oCAAoC;CACpC,sCAAsC;CACtC,uCAAuC;CACvC,gDAAgD;CAChD,iDAAiD;CACjD,6CAA6C;CAC7C,8CAA8C;CAC9C,2DAA2D;CAC3D,qCAAqC;CACrC,sCAAsC;CACtC,iDAAiD;CACjD,uCAAuC;CACvC,wCAAwC;CACxC,qDAAqD;CACrD,oDAAoD;CACpD,qDAAqD;CACrD,kEAAkE;CAClE,+CAA+C;CAC/C,+CAA+C;CAC/C,kDAAkD;CAClD,4CAA4C;CAC5C,2CAA2C;CAC3C,4CAA4C;CAC5C,yDAAyD;CACzD,sBAAsB;CACtB,uBAAuB;CACvB,4CAA4C;CAC5C,4BAA4B;CAC5B,4BAA4B;CAC5B,oBAAoB;CAEpB,wDAAwD;CACxD,qDAAqD;CACrD,sDAAsD;CACtD,wDAAwD;CACxD,4DAA4D;CAC5D,6EAA6E;CAC7E,4EAA4E;CAC5E,2DAA2D;CAC3D,0DAA0D;CAC1D,uFAAuF;CACvF,sFAAsF;CACtF,sDAAsD;CACtD,uDAAuD;CACvD,kEAAkE;CAClE,uEAAuE;CACvE,0EAA0E;CAC1E,0EAA0E;CAC1E,yEAAyE;CACzE,6EAA6E;CAC7E,uDAAuD;CACvD,+CAA+C;CAC/C,gDAAgD;CAChD,8DAA8D;CAC9D,uDAAuD;CACvD,wDAAwD;CACxD,8CAA8C;CAC9C,+CAA+C;CAE/C,uDAAuD;CACvD,wDAAwD;CACxD,2EAA2E;CAC3E,sDAAsD;CACtD,uDAAuD;CACvD,wDAAwD;AAC1D"}
@@ -0,0 +1,38 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { API_ROUTE_CLASSES } from "./api-routes.mjs";
5
+ import { PAGE_ROUTE_CLASSES } from "./page-routes.mjs";
6
+ //#region src/routes/classification.ts
7
+ /**
8
+ * Route classification (ADR 0006, decision 8): every route the app registers declares whether it
9
+ * belongs to hosting, testing, review, or both. `classification.test.ts` fails on any registered
10
+ * route missing here and on any entry that no longer matches a route, so a new route must be
11
+ * classified in `api-routes.ts` or `page-routes.ts` in the same change.
12
+ *
13
+ * Today the table only describes the routes; nothing is gated by it yet (gating comes with the
14
+ * `testing` option work, which will apply it as middleware).
15
+ */
16
+ /** The full table, keyed by `"METHOD /hono-pattern"` exactly as `app.routes` reports it. */
17
+ const ROUTE_CLASSES = {
18
+ ...API_ROUTE_CLASSES,
19
+ ...PAGE_ROUTE_CLASSES
20
+ };
21
+ /**
22
+ * Route families whose individual patterns are not worth listing (content-hashed static assets).
23
+ * A rule applies to any method whose path starts with `prefix`.
24
+ */
25
+ const ROUTE_PREFIX_CLASSES = [{
26
+ prefix: "/assets/",
27
+ routeClass: "hosting"
28
+ }];
29
+ /** The class of a registered route, or `undefined` when it is unclassified. */
30
+ function classOfRoute(method, path) {
31
+ const exact = ROUTE_CLASSES[`${method} ${path}`];
32
+ if (exact !== void 0) return exact;
33
+ return ROUTE_PREFIX_CLASSES.find((rule) => path.startsWith(rule.prefix))?.routeClass;
34
+ }
35
+ //#endregion
36
+ export { ROUTE_CLASSES, ROUTE_PREFIX_CLASSES, classOfRoute };
37
+
38
+ //# sourceMappingURL=classification.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classification.mjs","names":[],"sources":["../../src/routes/classification.ts"],"sourcesContent":["/**\n * Route classification (ADR 0006, decision 8): every route the app registers declares whether it\n * belongs to hosting, testing, review, or both. `classification.test.ts` fails on any registered\n * route missing here and on any entry that no longer matches a route, so a new route must be\n * classified in `api-routes.ts` or `page-routes.ts` in the same change.\n *\n * Today the table only describes the routes; nothing is gated by it yet (gating comes with the\n * `testing` option work, which will apply it as middleware).\n */\nimport { API_ROUTE_CLASSES } from \"./api-routes.ts\";\nimport { PAGE_ROUTE_CLASSES } from \"./page-routes.ts\";\n\n/**\n * - `hosting`: available with and without `testing`.\n * - `testing`: 404 without `testing`.\n * - `review`: 404 when the project's review switch is off.\n * - `both`: present in both modes, with sections gated by `testing`.\n */\nexport type RouteClass = \"hosting\" | \"testing\" | \"review\" | \"both\";\n\nexport const ROUTE_CLASS_VALUES: readonly RouteClass[] = [\"hosting\", \"testing\", \"review\", \"both\"];\n\n/** The full table, keyed by `\"METHOD /hono-pattern\"` exactly as `app.routes` reports it. */\nexport const ROUTE_CLASSES: Readonly<Record<string, RouteClass>> = {\n ...API_ROUTE_CLASSES,\n ...PAGE_ROUTE_CLASSES,\n};\n\n/**\n * Route families whose individual patterns are not worth listing (content-hashed static assets).\n * A rule applies to any method whose path starts with `prefix`.\n */\nexport const ROUTE_PREFIX_CLASSES: readonly { prefix: string; routeClass: RouteClass }[] = [\n { prefix: \"/assets/\", routeClass: \"hosting\" },\n];\n\n/** The class of a registered route, or `undefined` when it is unclassified. */\nexport function classOfRoute(method: string, path: string): RouteClass | undefined {\n const exact = ROUTE_CLASSES[`${method} ${path}`];\n if (exact !== undefined) {\n return exact;\n }\n return ROUTE_PREFIX_CLASSES.find((rule) => path.startsWith(rule.prefix))?.routeClass;\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAuBA,MAAa,gBAAsD;CACjE,GAAG;CACH,GAAG;AACL;;;;;AAMA,MAAa,uBAA8E,CACzF;CAAE,QAAQ;CAAY,YAAY;AAAU,CAC9C;;AAGA,SAAgB,aAAa,QAAgB,MAAsC;CACjF,MAAM,QAAQ,cAAc,GAAG,OAAO,GAAG;CACzC,IAAI,UAAU,KAAA,GACZ,OAAO;CAET,OAAO,qBAAqB,MAAM,SAAS,KAAK,WAAW,KAAK,MAAM,CAAC,CAAC,EAAE;AAC5E"}
@@ -0,0 +1,22 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { classOfRoute } from "./classification.mjs";
5
+ //#region src/routes/openapi-classes.ts
6
+ /** Tags every operation of an OpenAPI document with the class of its route (`x-storyshelf-class`). */
7
+ /** OpenAPI path templates (`{slug}`) as Hono patterns (`:slug`), the form the route table uses. */
8
+ function honoPattern(path) {
9
+ return path.replaceAll(/\{(?<name>[^}]+)\}/gu, ":$<name>");
10
+ }
11
+ /** `document` with `x-storyshelf-class` on each operation whose route the table classifies. */
12
+ function markRouteClasses(document) {
13
+ for (const [path, operations] of Object.entries(document.paths ?? {})) for (const [method, operation] of Object.entries(operations)) {
14
+ const routeClass = classOfRoute(method.toUpperCase(), honoPattern(path));
15
+ if (routeClass !== void 0 && typeof operation === "object" && operation !== null) operation["x-storyshelf-class"] = routeClass;
16
+ }
17
+ return document;
18
+ }
19
+ //#endregion
20
+ export { markRouteClasses };
21
+
22
+ //# sourceMappingURL=openapi-classes.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openapi-classes.mjs","names":[],"sources":["../../src/routes/openapi-classes.ts"],"sourcesContent":["/** Tags every operation of an OpenAPI document with the class of its route (`x-storyshelf-class`). */\nimport { classOfRoute } from \"./classification.ts\";\n\ninterface OpenApiLike {\n paths?: object | undefined;\n}\n\n/** OpenAPI path templates (`{slug}`) as Hono patterns (`:slug`), the form the route table uses. */\nfunction honoPattern(path: string): string {\n return path.replaceAll(/\\{(?<name>[^}]+)\\}/gu, \":$<name>\");\n}\n\n/** `document` with `x-storyshelf-class` on each operation whose route the table classifies. */\nexport function markRouteClasses<T extends OpenApiLike>(document: T): T {\n for (const [path, operations] of Object.entries(document.paths ?? {}) as [string, object][]) {\n for (const [method, operation] of Object.entries(operations)) {\n const routeClass = classOfRoute(method.toUpperCase(), honoPattern(path));\n if (routeClass !== undefined && typeof operation === \"object\" && operation !== null) {\n (operation as Record<string, unknown>)[\"x-storyshelf-class\"] = routeClass;\n }\n }\n }\n return document;\n}\n"],"mappings":";;;;;;;AAQA,SAAS,YAAY,MAAsB;CACzC,OAAO,KAAK,WAAW,wBAAwB,UAAU;AAC3D;;AAGA,SAAgB,iBAAwC,UAAgB;CACtE,KAAK,MAAM,CAAC,MAAM,eAAe,OAAO,QAAQ,SAAS,SAAS,CAAC,CAAC,GAClE,KAAK,MAAM,CAAC,QAAQ,cAAc,OAAO,QAAQ,UAAU,GAAG;EAC5D,MAAM,aAAa,aAAa,OAAO,YAAY,GAAG,YAAY,IAAI,CAAC;EACvE,IAAI,eAAe,KAAA,KAAa,OAAO,cAAc,YAAY,cAAc,MAC7E,UAAuC,wBAAwB;CAEnE;CAEF,OAAO;AACT"}
@@ -0,0 +1,104 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ //#region src/routes/page-routes.ts
5
+ /** HTML and auth routes, keyed by `METHOD /hono-pattern`. */
6
+ const PAGE_ROUTE_CLASSES = {
7
+ "GET /": "hosting",
8
+ "GET /projects": "hosting",
9
+ "GET /projects/new": "hosting",
10
+ "POST /projects/new": "hosting",
11
+ "GET /projects/:slug/labels": "hosting",
12
+ "GET /projects/:slug/labels/:key/*": "hosting",
13
+ "GET /projects/:slug/settings": "hosting",
14
+ "POST /projects/:slug/settings": "hosting",
15
+ "POST /projects/:slug/settings/review": "hosting",
16
+ "POST /projects/:slug/delete": "hosting",
17
+ "GET /projects/:slug/settings/labels": "hosting",
18
+ "POST /projects/:slug/settings/labels": "hosting",
19
+ "POST /projects/:slug/settings/labels/:key": "hosting",
20
+ "POST /projects/:slug/settings/labels/:key/delete": "hosting",
21
+ "GET /projects/:slug/settings/links": "hosting",
22
+ "POST /projects/:slug/settings/links": "hosting",
23
+ "POST /projects/:slug/settings/links/delete": "hosting",
24
+ "GET /projects/:slug/settings/design": "hosting",
25
+ "POST /projects/:slug/settings/design/files": "hosting",
26
+ "POST /projects/:slug/settings/design/files/:id/delete": "hosting",
27
+ "POST /projects/:slug/settings/design/components": "hosting",
28
+ "POST /projects/:slug/settings/design/components/:id/delete": "hosting",
29
+ "POST /projects/:slug/settings/design/:provider/token": "hosting",
30
+ "POST /projects/:slug/settings/design/:provider/token/delete": "hosting",
31
+ "GET /projects/:slug/settings/tokens": "hosting",
32
+ "POST /projects/:slug/settings/tokens": "hosting",
33
+ "POST /projects/:slug/settings/tokens/:tokenId/delete": "hosting",
34
+ "GET /projects/:slug/settings/webhooks": "hosting",
35
+ "POST /projects/:slug/settings/webhooks": "hosting",
36
+ "POST /projects/:slug/settings/webhooks/:webhookId/delete": "hosting",
37
+ "GET /projects/:slug/settings/notifications": "hosting",
38
+ "POST /projects/:slug/settings/notifications": "hosting",
39
+ "POST /projects/:slug/settings/notifications/:channelId/delete": "hosting",
40
+ "POST /projects/:slug/settings/notifications/me": "hosting",
41
+ "GET /projects/:slug/settings/members": "hosting",
42
+ "POST /projects/:slug/settings/members": "hosting",
43
+ "POST /projects/:slug/settings/members/:userId/remove": "hosting",
44
+ "POST /projects/:slug/settings/members/groups": "hosting",
45
+ "POST /projects/:slug/settings/members/groups/:mappingId/remove": "hosting",
46
+ "GET /admin": "hosting",
47
+ "POST /admin/credentials/reencrypt": "hosting",
48
+ "POST /admin/public-origin": "hosting",
49
+ "POST /admin/public-origin/report": "hosting",
50
+ "POST /admin/public-origin/clear": "hosting",
51
+ "GET /_": "hosting",
52
+ "GET /_/": "hosting",
53
+ "GET /_/:id": "hosting",
54
+ "GET /_/:id/*": "hosting",
55
+ "GET /projects/:slug/storybook": "hosting",
56
+ "GET /projects/:slug/storybook/:key/:value": "hosting",
57
+ "GET /projects/:slug/storybook/build/:buildId/*": "hosting",
58
+ "GET /_s/:token/*": "testing",
59
+ "GET /auth/login": "hosting",
60
+ "GET /auth/engine/login": "hosting",
61
+ "POST /auth/engine/login": "hosting",
62
+ "GET /auth/engine/:providerId": "hosting",
63
+ "GET /auth/invites/:inviteId": "hosting",
64
+ "POST /auth/invites/:inviteId": "hosting",
65
+ "POST /auth/logout": "hosting",
66
+ "GET /.well-known/openid-configuration": "hosting",
67
+ "GET /.well-known/change-password": "hosting",
68
+ "GET /profile": "hosting",
69
+ "POST /profile": "hosting",
70
+ "POST /profile/password": "hosting",
71
+ "POST /profile/sessions/revoke": "hosting",
72
+ "POST /profile/sessions/revoke-others": "hosting",
73
+ "POST /profile/passkeys/:passkeyId/delete": "hosting",
74
+ "POST /profile/notifications": "hosting",
75
+ "GET /connections/:service/start": "hosting",
76
+ "GET /connections/:service/callback": "hosting",
77
+ "POST /connections/:service/disconnect": "hosting",
78
+ "ALL /api/auth/*": "hosting",
79
+ "GET /projects/:slug/jobs": "testing",
80
+ "GET /projects/:slug/builds/:buildId/diff": "testing",
81
+ "GET /projects/:slug/settings/tests": "testing",
82
+ "POST /projects/:slug/settings/tests": "testing",
83
+ "GET /projects/:slug/settings/capture": "testing",
84
+ "POST /projects/:slug/settings/capture": "testing",
85
+ "GET /projects/:slug/settings/ai": "testing",
86
+ "POST /projects/:slug/settings/ai": "testing",
87
+ "GET /admin/ai": "testing",
88
+ "GET /projects/:slug/insights/health/panel": "testing",
89
+ "POST /projects/:slug/insights/health/generate": "testing",
90
+ "GET /projects/:slug/builds/:buildId/insights/panel": "testing",
91
+ "POST /projects/:slug/builds/:buildId/insights/generate": "testing",
92
+ "POST /projects/:slug/builds/:buildId/comments": "review",
93
+ "POST /projects/:slug/builds/:buildId/comments/:commentId/resolve": "review",
94
+ "POST /projects/:slug/builds/:buildId/reviews": "review",
95
+ "GET /projects/:slug": "both",
96
+ "GET /projects/:slug/builds": "both",
97
+ "GET /projects/:slug/builds/:buildId": "both",
98
+ "GET /projects/:slug/library": "both",
99
+ "GET /projects/:slug/components/*": "both"
100
+ };
101
+ //#endregion
102
+ export { PAGE_ROUTE_CLASSES };
103
+
104
+ //# sourceMappingURL=page-routes.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-routes.mjs","names":[],"sources":["../../src/routes/page-routes.ts"],"sourcesContent":["/** Classes of the HTML pages, Storybook serving, auth flow and engine routes. */\nimport type { RouteClass } from \"./classification.ts\";\n\n/** HTML and auth routes, keyed by `METHOD /hono-pattern`. */\nexport const PAGE_ROUTE_CLASSES: Readonly<Record<string, RouteClass>> = {\n // hosting\n \"GET /\": \"hosting\",\n \"GET /projects\": \"hosting\",\n \"GET /projects/new\": \"hosting\",\n \"POST /projects/new\": \"hosting\",\n \"GET /projects/:slug/labels\": \"hosting\",\n \"GET /projects/:slug/labels/:key/*\": \"hosting\",\n \"GET /projects/:slug/settings\": \"hosting\",\n \"POST /projects/:slug/settings\": \"hosting\",\n \"POST /projects/:slug/settings/review\": \"hosting\",\n \"POST /projects/:slug/delete\": \"hosting\",\n \"GET /projects/:slug/settings/labels\": \"hosting\",\n \"POST /projects/:slug/settings/labels\": \"hosting\",\n \"POST /projects/:slug/settings/labels/:key\": \"hosting\",\n \"POST /projects/:slug/settings/labels/:key/delete\": \"hosting\",\n \"GET /projects/:slug/settings/links\": \"hosting\",\n \"POST /projects/:slug/settings/links\": \"hosting\",\n \"POST /projects/:slug/settings/links/delete\": \"hosting\",\n \"GET /projects/:slug/settings/design\": \"hosting\",\n \"POST /projects/:slug/settings/design/files\": \"hosting\",\n \"POST /projects/:slug/settings/design/files/:id/delete\": \"hosting\",\n \"POST /projects/:slug/settings/design/components\": \"hosting\",\n \"POST /projects/:slug/settings/design/components/:id/delete\": \"hosting\",\n \"POST /projects/:slug/settings/design/:provider/token\": \"hosting\",\n \"POST /projects/:slug/settings/design/:provider/token/delete\": \"hosting\",\n \"GET /projects/:slug/settings/tokens\": \"hosting\",\n \"POST /projects/:slug/settings/tokens\": \"hosting\",\n \"POST /projects/:slug/settings/tokens/:tokenId/delete\": \"hosting\",\n \"GET /projects/:slug/settings/webhooks\": \"hosting\",\n \"POST /projects/:slug/settings/webhooks\": \"hosting\",\n \"POST /projects/:slug/settings/webhooks/:webhookId/delete\": \"hosting\",\n \"GET /projects/:slug/settings/notifications\": \"hosting\",\n \"POST /projects/:slug/settings/notifications\": \"hosting\",\n \"POST /projects/:slug/settings/notifications/:channelId/delete\": \"hosting\",\n \"POST /projects/:slug/settings/notifications/me\": \"hosting\",\n \"GET /projects/:slug/settings/members\": \"hosting\",\n \"POST /projects/:slug/settings/members\": \"hosting\",\n \"POST /projects/:slug/settings/members/:userId/remove\": \"hosting\",\n \"POST /projects/:slug/settings/members/groups\": \"hosting\",\n \"POST /projects/:slug/settings/members/groups/:mappingId/remove\": \"hosting\",\n \"GET /admin\": \"hosting\",\n \"POST /admin/credentials/reencrypt\": \"hosting\",\n \"POST /admin/public-origin\": \"hosting\",\n \"POST /admin/public-origin/report\": \"hosting\",\n \"POST /admin/public-origin/clear\": \"hosting\",\n \"GET /_\": \"hosting\",\n \"GET /_/\": \"hosting\",\n \"GET /_/:id\": \"hosting\",\n \"GET /_/:id/*\": \"hosting\",\n \"GET /projects/:slug/storybook\": \"hosting\",\n \"GET /projects/:slug/storybook/:key/:value\": \"hosting\",\n \"GET /projects/:slug/storybook/build/:buildId/*\": \"hosting\",\n \"GET /_s/:token/*\": \"testing\",\n \"GET /auth/login\": \"hosting\",\n \"GET /auth/engine/login\": \"hosting\",\n \"POST /auth/engine/login\": \"hosting\",\n \"GET /auth/engine/:providerId\": \"hosting\",\n \"GET /auth/invites/:inviteId\": \"hosting\",\n \"POST /auth/invites/:inviteId\": \"hosting\",\n \"POST /auth/logout\": \"hosting\",\n \"GET /.well-known/openid-configuration\": \"hosting\",\n \"GET /.well-known/change-password\": \"hosting\",\n \"GET /profile\": \"hosting\",\n \"POST /profile\": \"hosting\",\n \"POST /profile/password\": \"hosting\",\n \"POST /profile/sessions/revoke\": \"hosting\",\n \"POST /profile/sessions/revoke-others\": \"hosting\",\n \"POST /profile/passkeys/:passkeyId/delete\": \"hosting\",\n \"POST /profile/notifications\": \"hosting\",\n \"GET /connections/:service/start\": \"hosting\",\n \"GET /connections/:service/callback\": \"hosting\",\n \"POST /connections/:service/disconnect\": \"hosting\",\n \"ALL /api/auth/*\": \"hosting\",\n // testing\n \"GET /projects/:slug/jobs\": \"testing\",\n \"GET /projects/:slug/builds/:buildId/diff\": \"testing\",\n \"GET /projects/:slug/settings/tests\": \"testing\",\n \"POST /projects/:slug/settings/tests\": \"testing\",\n \"GET /projects/:slug/settings/capture\": \"testing\",\n \"POST /projects/:slug/settings/capture\": \"testing\",\n \"GET /projects/:slug/settings/ai\": \"testing\",\n \"POST /projects/:slug/settings/ai\": \"testing\",\n \"GET /admin/ai\": \"testing\",\n \"GET /projects/:slug/insights/health/panel\": \"testing\",\n \"POST /projects/:slug/insights/health/generate\": \"testing\",\n \"GET /projects/:slug/builds/:buildId/insights/panel\": \"testing\",\n \"POST /projects/:slug/builds/:buildId/insights/generate\": \"testing\",\n // review\n \"POST /projects/:slug/builds/:buildId/comments\": \"review\",\n \"POST /projects/:slug/builds/:buildId/comments/:commentId/resolve\": \"review\",\n \"POST /projects/:slug/builds/:buildId/reviews\": \"review\",\n // both\n \"GET /projects/:slug\": \"both\",\n \"GET /projects/:slug/builds\": \"both\",\n \"GET /projects/:slug/builds/:buildId\": \"both\",\n \"GET /projects/:slug/library\": \"both\",\n \"GET /projects/:slug/components/*\": \"both\",\n};\n"],"mappings":";;;;;AAIA,MAAa,qBAA2D;CAEtE,SAAS;CACT,iBAAiB;CACjB,qBAAqB;CACrB,sBAAsB;CACtB,8BAA8B;CAC9B,qCAAqC;CACrC,gCAAgC;CAChC,iCAAiC;CACjC,wCAAwC;CACxC,+BAA+B;CAC/B,uCAAuC;CACvC,wCAAwC;CACxC,6CAA6C;CAC7C,oDAAoD;CACpD,sCAAsC;CACtC,uCAAuC;CACvC,8CAA8C;CAC9C,uCAAuC;CACvC,8CAA8C;CAC9C,yDAAyD;CACzD,mDAAmD;CACnD,8DAA8D;CAC9D,wDAAwD;CACxD,+DAA+D;CAC/D,uCAAuC;CACvC,wCAAwC;CACxC,wDAAwD;CACxD,yCAAyC;CACzC,0CAA0C;CAC1C,4DAA4D;CAC5D,8CAA8C;CAC9C,+CAA+C;CAC/C,iEAAiE;CACjE,kDAAkD;CAClD,wCAAwC;CACxC,yCAAyC;CACzC,wDAAwD;CACxD,gDAAgD;CAChD,kEAAkE;CAClE,cAAc;CACd,qCAAqC;CACrC,6BAA6B;CAC7B,oCAAoC;CACpC,mCAAmC;CACnC,UAAU;CACV,WAAW;CACX,cAAc;CACd,gBAAgB;CAChB,iCAAiC;CACjC,6CAA6C;CAC7C,kDAAkD;CAClD,oBAAoB;CACpB,mBAAmB;CACnB,0BAA0B;CAC1B,2BAA2B;CAC3B,gCAAgC;CAChC,+BAA+B;CAC/B,gCAAgC;CAChC,qBAAqB;CACrB,yCAAyC;CACzC,oCAAoC;CACpC,gBAAgB;CAChB,iBAAiB;CACjB,0BAA0B;CAC1B,iCAAiC;CACjC,wCAAwC;CACxC,4CAA4C;CAC5C,+BAA+B;CAC/B,mCAAmC;CACnC,sCAAsC;CACtC,yCAAyC;CACzC,mBAAmB;CAEnB,4BAA4B;CAC5B,4CAA4C;CAC5C,sCAAsC;CACtC,uCAAuC;CACvC,wCAAwC;CACxC,yCAAyC;CACzC,mCAAmC;CACnC,oCAAoC;CACpC,iBAAiB;CACjB,6CAA6C;CAC7C,iDAAiD;CACjD,sDAAsD;CACtD,0DAA0D;CAE1D,iDAAiD;CACjD,oEAAoE;CACpE,gDAAgD;CAEhD,uBAAuB;CACvB,8BAA8B;CAC9B,uCAAuC;CACvC,+BAA+B;CAC/B,oCAAoC;AACtC"}
package/dist/runtime.mjs CHANGED
@@ -2,11 +2,11 @@ import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
4
  import { validateConfig, validateUiConfig } from "@storyshelf/core/config";
5
+ import { createPublicOriginResolver } from "@storyshelf/core/models";
5
6
  import { createShelfLogger } from "@storyshelf/core/logger";
6
7
  //#region src/runtime.ts
7
8
  function addOptionalSnapshots(snap, options) {
8
- if (options.captureRunner) snap["captureRunner"] = options.captureRunner.metadata;
9
- if (options.captureQueue) snap["captureQueue"] = options.captureQueue.metadata;
9
+ if (options.testing?.runner) snap["captureRunner"] = options.testing.runner.metadata;
10
10
  }
11
11
  /**
12
12
  * Build a snapshot of adapter metadata for the `/api/v1/health` and docs.
@@ -14,7 +14,7 @@ function addOptionalSnapshots(snap, options) {
14
14
  * the running configuration at a glance.
15
15
  *
16
16
  * @param options - Router options containing the wired adapters
17
- * @returns Metadata map keyed by adapter category (e.g. `database`, `git:github`)
17
+ * @returns Metadata map keyed by adapter category (e.g. `database`, `notify:github`)
18
18
  */
19
19
  function buildAdapterSnapshot(options) {
20
20
  const snap = {
@@ -22,8 +22,8 @@ function buildAdapterSnapshot(options) {
22
22
  storage: options.storage.metadata
23
23
  };
24
24
  addOptionalSnapshots(snap, options);
25
- for (const p of options.gitHosts ?? []) snap[`git:${p.metadata.kind}`] = p.metadata;
26
- for (const n of options.notifiers ?? []) snap[`notify:${n.metadata.kind}`] = n.metadata;
25
+ for (const p of options.design ?? []) snap[`design:${p.metadata.kind}`] = p.metadata;
26
+ for (const n of options.notifications?.notifiers ?? []) snap[`notify:${n.metadata.kind}`] = n.metadata;
27
27
  return snap;
28
28
  }
29
29
  /** Validate config/ui and derive runtime singletons from options. */
@@ -32,18 +32,23 @@ function resolveRuntime(options) {
32
32
  const ui = options.ui ? validateUiConfig(options.ui) : {};
33
33
  const logger = options.logger ?? createShelfLogger();
34
34
  const authEnabled = options.auth !== void 0;
35
- const gitHosts = options.gitHosts ?? [];
36
- const notifiers = options.notifiers ?? [];
35
+ const design = options.design ?? [];
36
+ const notifiers = options.notifications?.notifiers ?? [];
37
+ const config = rawConfig.adapters ? rawConfig : {
38
+ ...rawConfig,
39
+ adapters: buildAdapterSnapshot(options)
40
+ };
37
41
  return {
38
- config: rawConfig.adapters ? rawConfig : {
39
- ...rawConfig,
40
- adapters: buildAdapterSnapshot(options)
41
- },
42
+ config,
42
43
  ui,
43
44
  logger,
44
45
  authEnabled,
45
- gitHosts,
46
- notifiers
46
+ design,
47
+ notifiers,
48
+ publicOrigin: createPublicOriginResolver({
49
+ db: options.database,
50
+ config
51
+ })
47
52
  };
48
53
  }
49
54
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.mjs","names":[],"sources":["../src/runtime.ts"],"sourcesContent":["import type { GitHostProvider } from \"@storyshelf/core/adapter/git-host\";\nimport type { AdapterMetadata, GitAdapterMetadata } from \"@storyshelf/core/adapter/metadata\";\nimport type { NotifierProvider } from \"@storyshelf/core/adapter/notifier\";\nimport type { ShelfConfig, ShelfOptions, UIConfig } from \"@storyshelf/core/config\";\nimport { validateConfig, validateUiConfig } from \"@storyshelf/core/config\";\nimport type { Logger } from \"@storyshelf/core/logger\";\nimport { createShelfLogger } from \"@storyshelf/core/logger\";\n\n/**\n * Resolved server runtime derived from {@link ShelfOptions}.\n * Holds the validated config, UI branding, structured logger, and\n * computed helpers like whether auth is enabled and which git hosts are wired.\n */\nexport interface ServerRuntime {\n config: ShelfConfig;\n ui: UIConfig;\n logger: Logger;\n authEnabled: boolean;\n gitHosts: GitHostProvider[];\n notifiers: NotifierProvider[];\n}\n\nfunction addOptionalSnapshots(\n snap: Record<string, AdapterMetadata | GitAdapterMetadata>,\n options: ShelfOptions,\n): void {\n if (options.captureRunner) {\n snap[\"captureRunner\"] = options.captureRunner.metadata;\n }\n if (options.captureQueue) {\n snap[\"captureQueue\"] = options.captureQueue.metadata;\n }\n}\n\n/**\n * Build a snapshot of adapter metadata for the `/api/v1/health` and docs.\n * Collects the name/version of every wired adapter so operators can verify\n * the running configuration at a glance.\n *\n * @param options - Router options containing the wired adapters\n * @returns Metadata map keyed by adapter category (e.g. `database`, `git:github`)\n */\nexport function buildAdapterSnapshot(\n options: ShelfOptions,\n): Record<string, AdapterMetadata | GitAdapterMetadata> {\n const snap: Record<string, AdapterMetadata | GitAdapterMetadata> = {\n database: options.database.metadata,\n storage: options.storage.metadata,\n };\n addOptionalSnapshots(snap, options);\n for (const p of options.gitHosts ?? []) {\n snap[`git:${p.metadata.kind}`] = p.metadata;\n }\n for (const n of options.notifiers ?? []) {\n snap[`notify:${n.metadata.kind}`] = n.metadata;\n }\n return snap;\n}\n\n/** Validate config/ui and derive runtime singletons from options. */\nexport function resolveRuntime(options: ShelfOptions): ServerRuntime {\n // eslint-disable-next-line typescript/no-unnecessary-type-assertion -- ShelfConfig lacks index signature\n const rawConfig = options.config\n ? validateConfig(options.config as unknown as Record<string, unknown>)\n : {};\n // eslint-disable-next-line typescript/no-unnecessary-type-assertion -- UIConfig lacks index signature\n const ui = options.ui ? validateUiConfig(options.ui as unknown as Record<string, unknown>) : {};\n const logger = options.logger ?? createShelfLogger();\n const authEnabled = options.auth !== undefined;\n const gitHosts = options.gitHosts ?? [];\n const notifiers = options.notifiers ?? [];\n // Adapter introspection — auto-populate config.adapters if not supplied\n const config: ShelfConfig = rawConfig.adapters\n ? rawConfig\n : {\n ...rawConfig,\n adapters: buildAdapterSnapshot(options),\n };\n return { config, ui, logger, authEnabled, gitHosts, notifiers };\n}\n"],"mappings":";;;;;;AAsBA,SAAS,qBACP,MACA,SACM;CACN,IAAI,QAAQ,eACV,KAAK,mBAAmB,QAAQ,cAAc;CAEhD,IAAI,QAAQ,cACV,KAAK,kBAAkB,QAAQ,aAAa;AAEhD;;;;;;;;;AAUA,SAAgB,qBACd,SACsD;CACtD,MAAM,OAA6D;EACjE,UAAU,QAAQ,SAAS;EAC3B,SAAS,QAAQ,QAAQ;CAC3B;CACA,qBAAqB,MAAM,OAAO;CAClC,KAAK,MAAM,KAAK,QAAQ,YAAY,CAAC,GACnC,KAAK,OAAO,EAAE,SAAS,UAAU,EAAE;CAErC,KAAK,MAAM,KAAK,QAAQ,aAAa,CAAC,GACpC,KAAK,UAAU,EAAE,SAAS,UAAU,EAAE;CAExC,OAAO;AACT;;AAGA,SAAgB,eAAe,SAAsC;CAEnE,MAAM,YAAY,QAAQ,SACtB,eAAe,QAAQ,MAA4C,IACnE,CAAC;CAEL,MAAM,KAAK,QAAQ,KAAK,iBAAiB,QAAQ,EAAwC,IAAI,CAAC;CAC9F,MAAM,SAAS,QAAQ,UAAU,kBAAkB;CACnD,MAAM,cAAc,QAAQ,SAAS,KAAA;CACrC,MAAM,WAAW,QAAQ,YAAY,CAAC;CACtC,MAAM,YAAY,QAAQ,aAAa,CAAC;CAQxC,OAAO;EAAE,QANmB,UAAU,WAClC,YACA;GACE,GAAG;GACH,UAAU,qBAAqB,OAAO;EACxC;EACa;EAAI;EAAQ;EAAa;EAAU;CAAU;AAChE"}
1
+ {"version":3,"file":"runtime.mjs","names":[],"sources":["../src/runtime.ts"],"sourcesContent":["import type { DesignProvider } from \"@storyshelf/core/adapter/design\";\nimport type { AdapterMetadata } from \"@storyshelf/core/adapter/metadata\";\nimport type { NotifierProvider } from \"@storyshelf/core/adapter/notifier\";\nimport type { ShelfConfig, ShelfOptions, UIConfig } from \"@storyshelf/core/config\";\nimport { validateConfig, validateUiConfig } from \"@storyshelf/core/config\";\nimport type { Logger } from \"@storyshelf/core/logger\";\nimport { createShelfLogger } from \"@storyshelf/core/logger\";\nimport { createPublicOriginResolver, type PublicOriginResolver } from \"@storyshelf/core/models\";\n\n/**\n * Resolved server runtime derived from {@link ShelfOptions}.\n * Holds the validated config, UI branding, structured logger, and\n * computed helpers like whether auth is enabled and which notifiers are wired.\n */\nexport interface ServerRuntime {\n config: ShelfConfig;\n ui: UIConfig;\n logger: Logger;\n authEnabled: boolean;\n design: DesignProvider[];\n notifiers: NotifierProvider[];\n /** The public origin for links built in the background (ADR 0007, decision 6). */\n publicOrigin: PublicOriginResolver;\n}\n\nfunction addOptionalSnapshots(snap: Record<string, AdapterMetadata>, options: ShelfOptions): void {\n if (options.testing?.runner) {\n snap[\"captureRunner\"] = options.testing.runner.metadata;\n }\n}\n\n/**\n * Build a snapshot of adapter metadata for the `/api/v1/health` and docs.\n * Collects the name/version of every wired adapter so operators can verify\n * the running configuration at a glance.\n *\n * @param options - Router options containing the wired adapters\n * @returns Metadata map keyed by adapter category (e.g. `database`, `notify:github`)\n */\nexport function buildAdapterSnapshot(options: ShelfOptions): Record<string, AdapterMetadata> {\n const snap: Record<string, AdapterMetadata> = {\n database: options.database.metadata,\n storage: options.storage.metadata,\n };\n addOptionalSnapshots(snap, options);\n for (const p of options.design ?? []) {\n snap[`design:${p.metadata.kind}`] = p.metadata;\n }\n for (const n of options.notifications?.notifiers ?? []) {\n snap[`notify:${n.metadata.kind}`] = n.metadata;\n }\n return snap;\n}\n\n/** Validate config/ui and derive runtime singletons from options. */\nexport function resolveRuntime(options: ShelfOptions): ServerRuntime {\n // eslint-disable-next-line typescript/no-unnecessary-type-assertion -- ShelfConfig lacks index signature\n const rawConfig = options.config\n ? validateConfig(options.config as unknown as Record<string, unknown>)\n : {};\n // eslint-disable-next-line typescript/no-unnecessary-type-assertion -- UIConfig lacks index signature\n const ui = options.ui ? validateUiConfig(options.ui as unknown as Record<string, unknown>) : {};\n const logger = options.logger ?? createShelfLogger();\n const authEnabled = options.auth !== undefined;\n const design = options.design ?? [];\n const notifiers = options.notifications?.notifiers ?? [];\n // Adapter introspection — auto-populate config.adapters if not supplied\n const config: ShelfConfig = rawConfig.adapters\n ? rawConfig\n : {\n ...rawConfig,\n adapters: buildAdapterSnapshot(options),\n };\n const publicOrigin = createPublicOriginResolver({ db: options.database, config });\n return { config, ui, logger, authEnabled, design, notifiers, publicOrigin };\n}\n"],"mappings":";;;;;;;AAyBA,SAAS,qBAAqB,MAAuC,SAA6B;CAChG,IAAI,QAAQ,SAAS,QACnB,KAAK,mBAAmB,QAAQ,QAAQ,OAAO;AAEnD;;;;;;;;;AAUA,SAAgB,qBAAqB,SAAwD;CAC3F,MAAM,OAAwC;EAC5C,UAAU,QAAQ,SAAS;EAC3B,SAAS,QAAQ,QAAQ;CAC3B;CACA,qBAAqB,MAAM,OAAO;CAClC,KAAK,MAAM,KAAK,QAAQ,UAAU,CAAC,GACjC,KAAK,UAAU,EAAE,SAAS,UAAU,EAAE;CAExC,KAAK,MAAM,KAAK,QAAQ,eAAe,aAAa,CAAC,GACnD,KAAK,UAAU,EAAE,SAAS,UAAU,EAAE;CAExC,OAAO;AACT;;AAGA,SAAgB,eAAe,SAAsC;CAEnE,MAAM,YAAY,QAAQ,SACtB,eAAe,QAAQ,MAA4C,IACnE,CAAC;CAEL,MAAM,KAAK,QAAQ,KAAK,iBAAiB,QAAQ,EAAwC,IAAI,CAAC;CAC9F,MAAM,SAAS,QAAQ,UAAU,kBAAkB;CACnD,MAAM,cAAc,QAAQ,SAAS,KAAA;CACrC,MAAM,SAAS,QAAQ,UAAU,CAAC;CAClC,MAAM,YAAY,QAAQ,eAAe,aAAa,CAAC;CAEvD,MAAM,SAAsB,UAAU,WAClC,YACA;EACE,GAAG;EACH,UAAU,qBAAqB,OAAO;CACxC;CAEJ,OAAO;EAAE;EAAQ;EAAI;EAAQ;EAAa;EAAQ;EAAW,cADxC,2BAA2B;GAAE,IAAI,QAAQ;GAAU;EAAO,CACP;CAAE;AAC5E"}
@@ -0,0 +1,5 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { isServerlessHost as isServerlessHost$1 } from "@storyshelf/core/config";
5
+ export { isServerlessHost$1 as isServerlessHost };
package/dist/store.mjs CHANGED
@@ -1,6 +1,7 @@
1
1
  import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
+ import { createCatalogLoader } from "@storyshelf/core/capture";
4
5
  import { AsyncLocalStorage } from "node:async_hooks";
5
6
  //#region src/store.ts
6
7
  const storage = new AsyncLocalStorage();
@@ -14,7 +15,24 @@ function getStore() {
14
15
  if (!store) throw new Error("Store not available. Use runWithStore().");
15
16
  return store;
16
17
  }
18
+ /**
19
+ * Whether this instance has `testing` (`options.testing` is set): it captures and compares builds,
20
+ * so pages layer snapshot data (screenshots, statuses, design links) onto what the catalog gives
21
+ * them. Without it the same pages render from the published Storybook alone, and the routes
22
+ * classified `testing` answer 404 (`requireTesting`).
23
+ */
24
+ function testingEnabled(store = getStore()) {
25
+ return store.testing;
26
+ }
27
+ /** The catalog loader pages read; hand-built stores without one get an uncached loader. */
28
+ function catalogOf(store = getStore()) {
29
+ return store.publish?.catalog ?? createCatalogLoader({
30
+ db: store.db,
31
+ storage: store.storage,
32
+ logger: store.logger
33
+ });
34
+ }
17
35
  //#endregion
18
- export { getStore, runWithStore };
36
+ export { catalogOf, getStore, runWithStore, testingEnabled };
19
37
 
20
38
  //# sourceMappingURL=store.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"store.mjs","names":[],"sources":["../src/store.ts"],"sourcesContent":["import type { CaptureQueue } from \"@storyshelf/core/adapter/capture-queue\";\nimport type { DatabaseAdapter } from \"@storyshelf/core/adapter/database\";\nimport type { GitHostProvider } from \"@storyshelf/core/adapter/git-host\";\nimport type { NotifierProvider } from \"@storyshelf/core/adapter/notifier\";\nimport type { StorageAdapter } from \"@storyshelf/core/adapter/storage\";\nimport type { ShelfConfig, UIConfig } from \"@storyshelf/core/config\";\nimport type { Logger } from \"@storyshelf/core/logger\";\nimport type { AuthUser } from \"@storyshelf/core/types\";\nimport { AsyncLocalStorage } from \"node:async_hooks\";\n\n/** Request-scoped dependencies and session state. */\nexport interface Store {\n db: DatabaseAdapter;\n storage: StorageAdapter;\n config: ShelfConfig;\n ui: UIConfig;\n logger: Logger;\n user: AuthUser | null;\n authEnabled: boolean;\n /** Raw session cookie value (\"default\" when anonymous); binds CSRF tokens. */\n sessionId: string;\n enqueueCapture?: (buildId: string, reqId?: string) => Promise<void>;\n captureQueue?: CaptureQueue | null;\n gitHosts: GitHostProvider[];\n notifiers: NotifierProvider[];\n}\n\nconst storage = new AsyncLocalStorage<Store>();\n\n/** Run a function with the given request store in scope. */\nexport function runWithStore<T>(store: Store, fn: () => T): T {\n return storage.run(store, fn);\n}\n\n/** Return the current request store, throwing outside a request scope. */\nexport function getStore(): Store {\n const store = storage.getStore();\n if (!store) {\n throw new Error(\"Store not available. Use runWithStore().\");\n }\n return store;\n}\n"],"mappings":";;;;;AA2BA,MAAM,UAAU,IAAI,kBAAyB;;AAG7C,SAAgB,aAAgB,OAAc,IAAgB;CAC5D,OAAO,QAAQ,IAAI,OAAO,EAAE;AAC9B;;AAGA,SAAgB,WAAkB;CAChC,MAAM,QAAQ,QAAQ,SAAS;CAC/B,IAAI,CAAC,OACH,MAAM,IAAI,MAAM,0CAA0C;CAE5D,OAAO;AACT"}
1
+ {"version":3,"file":"store.mjs","names":[],"sources":["../src/store.ts"],"sourcesContent":["import type { CaptureQueue } from \"@storyshelf/core/adapter/capture-queue\";\nimport type { CaptureRunner } from \"@storyshelf/core/adapter/capture-runner\";\nimport type { DatabaseAdapter } from \"@storyshelf/core/adapter/database\";\nimport type { DesignProvider } from \"@storyshelf/core/adapter/design\";\nimport type { NotifierProvider } from \"@storyshelf/core/adapter/notifier\";\nimport type { StorageAdapter } from \"@storyshelf/core/adapter/storage\";\nimport type { Ai } from \"@storyshelf/core/ai\";\nimport {\n createCatalogLoader,\n type BuildStatePublisher,\n type CatalogLoader,\n} from \"@storyshelf/core/capture\";\nimport type { NotificationsConfig, ShelfConfig, UIConfig } from \"@storyshelf/core/config\";\nimport type { Logger } from \"@storyshelf/core/logger\";\nimport type { PublicOriginResolver } from \"@storyshelf/core/models\";\nimport type { AuthUser } from \"@storyshelf/core/types\";\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport type { PublishService } from \"./publish-setup.ts\";\n\n/** Request-scoped dependencies and session state. */\nexport interface Store {\n db: DatabaseAdapter;\n storage: StorageAdapter;\n config: ShelfConfig;\n ui: UIConfig;\n logger: Logger;\n user: AuthUser | null;\n authEnabled: boolean;\n /** Raw session cookie value (\"default\" when anonymous); binds CSRF tokens. */\n sessionId: string;\n /** `options.testing` is set: capture, compare and baselines run here (ADR 0006). */\n testing: boolean;\n enqueueCapture?: (buildId: string, reqId?: string) => Promise<void>;\n captureQueue?: CaptureQueue | null;\n /** This instance runs capture jobs itself, so retry and the compute-jobs controls apply. */\n capturesHere?: boolean;\n /** Publishing: completion and the `publish` job queue (absent in hand-built stores). */\n publish?: PublishService;\n /** Cancels the unfinished jobs of a build, of every kind (absent in hand-built stores). */\n cancelBuildJobs?: (buildId: string) => Promise<boolean>;\n /** Shows a build's state on its git channels (absent in hand-built stores: delivered inline). */\n buildState?: BuildStatePublisher;\n /** Explicit `testing.concurrency`; unset means sized from the hardware. */\n captureConcurrency?: number;\n /** The capture runner (`options.testing.runner`); its targets decide which browsers a project may pick. */\n runner?: Pick<CaptureRunner, \"metadata\"> | undefined;\n /** The public origin: configured, detected from the platform, or reported by an admin (ADR 0007). */\n publicOrigin?: PublicOriginResolver | undefined;\n /** Design providers (Figma, …); empty when none is wired. */\n design: DesignProvider[];\n notifiers: NotifierProvider[];\n /** Brand/sender settings for outgoing notifications (`notifications.config`). */\n notificationsConfig?: NotificationsConfig;\n /** AI insights surface; absent ⇒ AI off site-wide. */\n ai?: Ai;\n}\n\nconst storage = new AsyncLocalStorage<Store>();\n\n/** Run a function with the given request store in scope. */\nexport function runWithStore<T>(store: Store, fn: () => T): T {\n return storage.run(store, fn);\n}\n\n/** Return the current request store, throwing outside a request scope. */\nexport function getStore(): Store {\n const store = storage.getStore();\n if (!store) {\n throw new Error(\"Store not available. Use runWithStore().\");\n }\n return store;\n}\n\n/**\n * Whether this instance has `testing` (`options.testing` is set): it captures and compares builds,\n * so pages layer snapshot data (screenshots, statuses, design links) onto what the catalog gives\n * them. Without it the same pages render from the published Storybook alone, and the routes\n * classified `testing` answer 404 (`requireTesting`).\n */\nexport function testingEnabled(store: Store = getStore()): boolean {\n return store.testing;\n}\n\n/** The catalog loader pages read; hand-built stores without one get an uncached loader. */\nexport function catalogOf(store: Store = getStore()): CatalogLoader {\n return (\n store.publish?.catalog ??\n createCatalogLoader({ db: store.db, storage: store.storage, logger: store.logger })\n );\n}\n"],"mappings":";;;;;;AAyDA,MAAM,UAAU,IAAI,kBAAyB;;AAG7C,SAAgB,aAAgB,OAAc,IAAgB;CAC5D,OAAO,QAAQ,IAAI,OAAO,EAAE;AAC9B;;AAGA,SAAgB,WAAkB;CAChC,MAAM,QAAQ,QAAQ,SAAS;CAC/B,IAAI,CAAC,OACH,MAAM,IAAI,MAAM,0CAA0C;CAE5D,OAAO;AACT;;;;;;;AAQA,SAAgB,eAAe,QAAe,SAAS,GAAY;CACjE,OAAO,MAAM;AACf;;AAGA,SAAgB,UAAU,QAAe,SAAS,GAAkB;CAClE,OACE,MAAM,SAAS,WACf,oBAAoB;EAAE,IAAI,MAAM;EAAI,SAAS,MAAM;EAAS,QAAQ,MAAM;CAAO,CAAC;AAEtF"}
@@ -0,0 +1,46 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { getStore } from "./store.mjs";
5
+ import { MAX_BROWSERS, assertSupportedBrowsers, offeredTargets } from "@storyshelf/core/capture";
6
+ import { HTTPException } from "hono/http-exception";
7
+ import { isCaptureTarget, parseTarget } from "@storyshelf/core/adapter/capture-runner";
8
+ //#region src/targets.ts
9
+ /** Which capture targets this instance offers right now, and the checks a settings save runs (ADR 0007). */
10
+ /**
11
+ * The targets the runner offers, with the ones that need a URL marked `unavailable` while there is
12
+ * no `secret` or public origin. `undefined` when there is no runner or it publishes no list: any
13
+ * target is accepted, as before.
14
+ */
15
+ async function runnerTargets(store = getStore()) {
16
+ if (store.runner === void 0) return;
17
+ return offeredTargets(store.runner, {
18
+ publicOrigin: await store.publicOrigin?.url(),
19
+ hasSecret: store.config.secret !== void 0
20
+ });
21
+ }
22
+ /** Engine label for a target (`Chromium`), plus the remote's name (`Chromium on grid`). */
23
+ function targetLabel(target) {
24
+ const { engine, name } = parseTarget(target);
25
+ const label = engine === "webkit" ? "WebKit" : `${engine[0]?.toUpperCase()}${engine.slice(1)}`;
26
+ return name === void 0 ? label : `${label} on ${name}`;
27
+ }
28
+ /**
29
+ * Refuse, with 400 and the reason, a target list the runner cannot render (not offered, or
30
+ * unavailable), or one that is malformed or too long. Runs when a setting is saved, so a build
31
+ * never fails one by one with an unexplained error.
32
+ */
33
+ async function assertTargetsSelectable(targets, current = []) {
34
+ const bad = targets.find((target) => !isCaptureTarget(target));
35
+ if (bad !== void 0) throw new HTTPException(400, { message: `Invalid capture target "${bad}"` });
36
+ if (targets.length > MAX_BROWSERS) throw new HTTPException(400, { message: `Choose at most ${MAX_BROWSERS} browsers` });
37
+ try {
38
+ assertSupportedBrowsers(targets.filter((target) => !current.includes(target)), await runnerTargets());
39
+ } catch (error) {
40
+ throw new HTTPException(400, { message: error instanceof Error ? error.message : "Refused" });
41
+ }
42
+ }
43
+ //#endregion
44
+ export { assertTargetsSelectable, runnerTargets, targetLabel };
45
+
46
+ //# sourceMappingURL=targets.mjs.map