@storyshelf/app 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 (429) hide show
  1. package/README.md +42 -4
  2. package/dist/app-types.d.mts +2 -6
  3. package/dist/asset-manifest.mjs +14 -7
  4. package/dist/asset-manifest.mjs.map +1 -1
  5. package/dist/assets/{htmx.min.js → htmx.mjs} +9 -1
  6. package/dist/assets/htmx.mjs.map +1 -0
  7. package/dist/assets/mark.mjs +9 -0
  8. package/dist/assets/mark.mjs.map +1 -0
  9. package/dist/boot-tasks.mjs +79 -0
  10. package/dist/boot-tasks.mjs.map +1 -0
  11. package/dist/build-state-flags.mjs +17 -0
  12. package/dist/build-state-flags.mjs.map +1 -0
  13. package/dist/build-state-setup.mjs +74 -0
  14. package/dist/build-state-setup.mjs.map +1 -0
  15. package/dist/capture-alerts.mjs +45 -0
  16. package/dist/capture-alerts.mjs.map +1 -0
  17. package/dist/capture-process.mjs +58 -0
  18. package/dist/capture-process.mjs.map +1 -0
  19. package/dist/capture-setup.mjs +96 -119
  20. package/dist/capture-setup.mjs.map +1 -1
  21. package/dist/connections.mjs +33 -0
  22. package/dist/connections.mjs.map +1 -0
  23. package/dist/consumer-setup.mjs +57 -0
  24. package/dist/consumer-setup.mjs.map +1 -0
  25. package/dist/credential-keys.mjs +1 -2
  26. package/dist/credentials.mjs +1 -2
  27. package/dist/git-status.mjs +45 -0
  28. package/dist/git-status.mjs.map +1 -0
  29. package/dist/hidden-data.mjs +23 -0
  30. package/dist/hidden-data.mjs.map +1 -0
  31. package/dist/index.d.mts +3 -5
  32. package/dist/index.mjs +204 -127
  33. package/dist/index.mjs.map +1 -1
  34. package/dist/insights/budget.mjs +48 -0
  35. package/dist/insights/budget.mjs.map +1 -0
  36. package/dist/insights/deps.mjs +20 -0
  37. package/dist/insights/deps.mjs.map +1 -0
  38. package/dist/insights/errors.mjs +21 -0
  39. package/dist/insights/errors.mjs.map +1 -0
  40. package/dist/insights/health-panel.mjs +39 -0
  41. package/dist/insights/health-panel.mjs.map +1 -0
  42. package/dist/insights/job.mjs +102 -0
  43. package/dist/insights/job.mjs.map +1 -0
  44. package/dist/insights/load-health.mjs +58 -0
  45. package/dist/insights/load-health.mjs.map +1 -0
  46. package/dist/insights/load-triage.mjs +97 -0
  47. package/dist/insights/load-triage.mjs.map +1 -0
  48. package/dist/insights/panel.mjs +27 -0
  49. package/dist/insights/panel.mjs.map +1 -0
  50. package/dist/insights/request.mjs +112 -0
  51. package/dist/insights/request.mjs.map +1 -0
  52. package/dist/insights/stale.mjs +20 -0
  53. package/dist/insights/stale.mjs.map +1 -0
  54. package/dist/insights/usage-report.mjs +59 -0
  55. package/dist/insights/usage-report.mjs.map +1 -0
  56. package/dist/insights/view.mjs +47 -0
  57. package/dist/insights/view.mjs.map +1 -0
  58. package/dist/job-canceller.mjs +13 -0
  59. package/dist/job-canceller.mjs.map +1 -0
  60. package/dist/job-queue.mjs +50 -0
  61. package/dist/job-queue.mjs.map +1 -0
  62. package/dist/jobs-setup.mjs +131 -0
  63. package/dist/jobs-setup.mjs.map +1 -0
  64. package/dist/lifecycle.mjs +93 -22
  65. package/dist/lifecycle.mjs.map +1 -1
  66. package/dist/middleware/auth-gate.mjs +5 -4
  67. package/dist/middleware/auth-gate.mjs.map +1 -1
  68. package/dist/middleware/csrf.mjs +1 -2
  69. package/dist/middleware/rate-limit.mjs +42 -35
  70. package/dist/middleware/rate-limit.mjs.map +1 -1
  71. package/dist/middleware/request-log.mjs +4 -4
  72. package/dist/middleware/request-log.mjs.map +1 -1
  73. package/dist/middleware/require-review.mjs +50 -0
  74. package/dist/middleware/require-review.mjs.map +1 -0
  75. package/dist/middleware/require-testing.mjs +40 -0
  76. package/dist/middleware/require-testing.mjs.map +1 -0
  77. package/dist/middleware/server-timing.mjs +1 -2
  78. package/dist/middleware/setup-gate.mjs +1 -2
  79. package/dist/middleware/setup-gate.mjs.map +1 -1
  80. package/dist/middleware/store-scope.mjs +13 -4
  81. package/dist/middleware/store-scope.mjs.map +1 -1
  82. package/dist/notifications-pause.mjs +32 -0
  83. package/dist/notifications-pause.mjs.map +1 -0
  84. package/dist/notify.d.mts +3 -4
  85. package/dist/notify.mjs +79 -41
  86. package/dist/notify.mjs.map +1 -1
  87. package/dist/pages/a11y-violations.mjs +115 -0
  88. package/dist/pages/a11y-violations.mjs.map +1 -0
  89. package/dist/pages/admin-ai.mjs +77 -0
  90. package/dist/pages/admin-ai.mjs.map +1 -0
  91. package/dist/pages/admin-system-origin.mjs +132 -0
  92. package/dist/pages/admin-system-origin.mjs.map +1 -0
  93. package/dist/pages/admin-system-targets.mjs +52 -0
  94. package/dist/pages/admin-system-targets.mjs.map +1 -0
  95. package/dist/pages/admin-system.mjs +64 -15
  96. package/dist/pages/admin-system.mjs.map +1 -1
  97. package/dist/pages/build-detail.mjs +105 -78
  98. package/dist/pages/build-detail.mjs.map +1 -1
  99. package/dist/pages/build-diff-actions.mjs +88 -35
  100. package/dist/pages/build-diff-actions.mjs.map +1 -1
  101. package/dist/pages/build-diff-comments.mjs +17 -16
  102. package/dist/pages/build-diff-comments.mjs.map +1 -1
  103. package/dist/pages/build-diff-default.mjs +53 -0
  104. package/dist/pages/build-diff-default.mjs.map +1 -0
  105. package/dist/pages/build-diff-environment.mjs +81 -0
  106. package/dist/pages/build-diff-environment.mjs.map +1 -0
  107. package/dist/pages/build-diff-header.mjs +23 -6
  108. package/dist/pages/build-diff-header.mjs.map +1 -1
  109. package/dist/pages/build-diff-insight.mjs +112 -0
  110. package/dist/pages/build-diff-insight.mjs.map +1 -0
  111. package/dist/pages/build-diff-model.mjs +74 -12
  112. package/dist/pages/build-diff-model.mjs.map +1 -1
  113. package/dist/pages/build-diff-nav.mjs +29 -14
  114. package/dist/pages/build-diff-nav.mjs.map +1 -1
  115. package/dist/pages/build-diff-reference.mjs +84 -0
  116. package/dist/pages/build-diff-reference.mjs.map +1 -0
  117. package/dist/pages/build-diff-shortcuts.mjs +6 -3
  118. package/dist/pages/build-diff-shortcuts.mjs.map +1 -1
  119. package/dist/pages/build-diff-stale.mjs +1 -2
  120. package/dist/pages/build-diff-usage.mjs +62 -0
  121. package/dist/pages/build-diff-usage.mjs.map +1 -0
  122. package/dist/pages/build-diff-viewer.mjs +97 -14
  123. package/dist/pages/build-diff-viewer.mjs.map +1 -1
  124. package/dist/pages/build-diff.mjs +37 -22
  125. package/dist/pages/build-diff.mjs.map +1 -1
  126. package/dist/pages/build-tests.mjs +73 -0
  127. package/dist/pages/build-tests.mjs.map +1 -0
  128. package/dist/pages/component-detail.mjs +248 -0
  129. package/dist/pages/component-detail.mjs.map +1 -0
  130. package/dist/pages/component-stories.mjs +167 -0
  131. package/dist/pages/component-stories.mjs.map +1 -0
  132. package/dist/pages/compute-jobs.mjs +14 -28
  133. package/dist/pages/compute-jobs.mjs.map +1 -1
  134. package/dist/pages/design-links.mjs +181 -0
  135. package/dist/pages/design-links.mjs.map +1 -0
  136. package/dist/pages/environment-change.mjs +52 -0
  137. package/dist/pages/environment-change.mjs.map +1 -0
  138. package/dist/pages/health-panel.mjs +125 -0
  139. package/dist/pages/health-panel.mjs.map +1 -0
  140. package/dist/pages/invite.mjs +1 -2
  141. package/dist/pages/label-detail.mjs +1 -2
  142. package/dist/pages/library-grid.mjs +104 -0
  143. package/dist/pages/library-grid.mjs.map +1 -0
  144. package/dist/pages/library-model.mjs +46 -0
  145. package/dist/pages/library-model.mjs.map +1 -0
  146. package/dist/pages/library-snapshots.mjs +129 -0
  147. package/dist/pages/library-snapshots.mjs.map +1 -0
  148. package/dist/pages/library.mjs +113 -220
  149. package/dist/pages/library.mjs.map +1 -1
  150. package/dist/pages/login.mjs +1 -2
  151. package/dist/pages/notifications-paused.mjs +21 -0
  152. package/dist/pages/notifications-paused.mjs.map +1 -0
  153. package/dist/pages/passkey-ceremony.mjs +1 -2
  154. package/dist/pages/profile.mjs +54 -50
  155. package/dist/pages/profile.mjs.map +1 -1
  156. package/dist/pages/project-builds-table.mjs +3 -4
  157. package/dist/pages/project-builds-table.mjs.map +1 -1
  158. package/dist/pages/project-builds.mjs +52 -23
  159. package/dist/pages/project-builds.mjs.map +1 -1
  160. package/dist/pages/project-create.mjs +1 -2
  161. package/dist/pages/project-settings.mjs +52 -14
  162. package/dist/pages/project-settings.mjs.map +1 -1
  163. package/dist/pages/projects-card.mjs +15 -3
  164. package/dist/pages/projects-card.mjs.map +1 -1
  165. package/dist/pages/projects.mjs +7 -4
  166. package/dist/pages/projects.mjs.map +1 -1
  167. package/dist/pages/review-changeset.mjs +115 -0
  168. package/dist/pages/review-changeset.mjs.map +1 -0
  169. package/dist/pages/review-comments.mjs +102 -0
  170. package/dist/pages/review-comments.mjs.map +1 -0
  171. package/dist/pages/review-verdict.mjs +101 -0
  172. package/dist/pages/review-verdict.mjs.map +1 -0
  173. package/dist/pages/root.mjs +1 -2
  174. package/dist/pages/settings-ai.mjs +68 -0
  175. package/dist/pages/settings-ai.mjs.map +1 -0
  176. package/dist/pages/settings-capture.mjs +123 -0
  177. package/dist/pages/settings-capture.mjs.map +1 -0
  178. package/dist/pages/settings-design-credential.mjs +98 -0
  179. package/dist/pages/settings-design-credential.mjs.map +1 -0
  180. package/dist/pages/settings-design-links.mjs +178 -0
  181. package/dist/pages/settings-design-links.mjs.map +1 -0
  182. package/dist/pages/settings-design.mjs +47 -0
  183. package/dist/pages/settings-design.mjs.map +1 -0
  184. package/dist/pages/settings-general.mjs +47 -23
  185. package/dist/pages/settings-general.mjs.map +1 -1
  186. package/dist/pages/settings-labels.mjs +44 -4
  187. package/dist/pages/settings-labels.mjs.map +1 -1
  188. package/dist/pages/settings-links.mjs +111 -0
  189. package/dist/pages/settings-links.mjs.map +1 -0
  190. package/dist/pages/settings-members.mjs +1 -2
  191. package/dist/pages/settings-notifications-git.mjs +127 -0
  192. package/dist/pages/settings-notifications-git.mjs.map +1 -0
  193. package/dist/pages/settings-notifications.mjs +80 -44
  194. package/dist/pages/settings-notifications.mjs.map +1 -1
  195. package/dist/pages/settings-tests.mjs +34 -4
  196. package/dist/pages/settings-tests.mjs.map +1 -1
  197. package/dist/pages/settings-tokens.mjs +1 -2
  198. package/dist/pages/settings-webhooks.mjs +1 -2
  199. package/dist/pages/storybook.mjs +1 -2
  200. package/dist/pages/target-choices.mjs +38 -0
  201. package/dist/pages/target-choices.mjs.map +1 -0
  202. package/dist/publish-setup.mjs +88 -0
  203. package/dist/publish-setup.mjs.map +1 -0
  204. package/dist/retention-timer.mjs +12 -12
  205. package/dist/retention-timer.mjs.map +1 -1
  206. package/dist/review-events.mjs +47 -0
  207. package/dist/review-events.mjs.map +1 -0
  208. package/dist/routers/admin-pages.mjs +46 -5
  209. package/dist/routers/admin-pages.mjs.map +1 -1
  210. package/dist/routers/admin-public-origin.mjs +92 -0
  211. package/dist/routers/admin-public-origin.mjs.map +1 -0
  212. package/dist/routers/admin.mjs +8 -4
  213. package/dist/routers/admin.mjs.map +1 -1
  214. package/dist/routers/assets.mjs +10 -4
  215. package/dist/routers/assets.mjs.map +1 -1
  216. package/dist/routers/attempts.mjs +1 -2
  217. package/dist/routers/auth-sync.mjs +1 -2
  218. package/dist/routers/auth.mjs +2 -3
  219. package/dist/routers/baseline-guard.mjs +4 -5
  220. package/dist/routers/baseline-guard.mjs.map +1 -1
  221. package/dist/routers/baseline-reference.mjs +72 -0
  222. package/dist/routers/baseline-reference.mjs.map +1 -0
  223. package/dist/routers/baselines.handlers.mjs +63 -0
  224. package/dist/routers/baselines.handlers.mjs.map +1 -0
  225. package/dist/routers/baselines.mjs +124 -0
  226. package/dist/routers/baselines.mjs.map +1 -0
  227. package/dist/routers/build-cancel.mjs +67 -0
  228. package/dist/routers/build-cancel.mjs.map +1 -0
  229. package/dist/routers/build-changeset.mjs +69 -0
  230. package/dist/routers/build-changeset.mjs.map +1 -0
  231. package/dist/routers/build-reviews.handlers.mjs +85 -0
  232. package/dist/routers/build-reviews.handlers.mjs.map +1 -0
  233. package/dist/routers/build-reviews.mjs +73 -0
  234. package/dist/routers/build-reviews.mjs.map +1 -0
  235. package/dist/routers/builds.handlers.mjs +65 -60
  236. package/dist/routers/builds.handlers.mjs.map +1 -1
  237. package/dist/routers/builds.mjs +77 -19
  238. package/dist/routers/builds.mjs.map +1 -1
  239. package/dist/routers/capture-cache.mjs +29 -0
  240. package/dist/routers/capture-cache.mjs.map +1 -0
  241. package/dist/routers/capture-config.mjs +34 -0
  242. package/dist/routers/capture-config.mjs.map +1 -0
  243. package/dist/routers/comments.mjs +42 -17
  244. package/dist/routers/comments.mjs.map +1 -1
  245. package/dist/routers/comparison-image.mjs +29 -0
  246. package/dist/routers/comparison-image.mjs.map +1 -0
  247. package/dist/routers/component-catalog.handlers.mjs +70 -0
  248. package/dist/routers/component-catalog.handlers.mjs.map +1 -0
  249. package/dist/routers/component.handlers.mjs +120 -0
  250. package/dist/routers/component.handlers.mjs.map +1 -0
  251. package/dist/routers/connections.mjs +117 -0
  252. package/dist/routers/connections.mjs.map +1 -0
  253. package/dist/routers/design-connect.handlers.mjs +43 -0
  254. package/dist/routers/design-connect.handlers.mjs.map +1 -0
  255. package/dist/routers/design.handlers.mjs +113 -0
  256. package/dist/routers/design.handlers.mjs.map +1 -0
  257. package/dist/routers/flash.mjs +1 -2
  258. package/dist/routers/health-report.mjs +3 -5
  259. package/dist/routers/health-report.mjs.map +1 -1
  260. package/dist/routers/health.mjs +5 -5
  261. package/dist/routers/health.mjs.map +1 -1
  262. package/dist/routers/helpers.mjs +20 -5
  263. package/dist/routers/helpers.mjs.map +1 -1
  264. package/dist/routers/htmx.mjs +1 -2
  265. package/dist/routers/insights-ui.mjs +63 -0
  266. package/dist/routers/insights-ui.mjs.map +1 -0
  267. package/dist/routers/insights.handlers.mjs +44 -0
  268. package/dist/routers/insights.handlers.mjs.map +1 -0
  269. package/dist/routers/insights.mjs +188 -0
  270. package/dist/routers/insights.mjs.map +1 -0
  271. package/dist/routers/labels.mjs +1 -2
  272. package/dist/routers/links.handlers.mjs +79 -0
  273. package/dist/routers/links.handlers.mjs.map +1 -0
  274. package/dist/routers/links.mjs +138 -0
  275. package/dist/routers/links.mjs.map +1 -0
  276. package/dist/routers/media.mjs +30 -7
  277. package/dist/routers/media.mjs.map +1 -1
  278. package/dist/routers/members.mjs +3 -3
  279. package/dist/routers/members.mjs.map +1 -1
  280. package/dist/routers/notification-channels.mjs +18 -25
  281. package/dist/routers/notification-channels.mjs.map +1 -1
  282. package/dist/routers/notification-subscriptions.mjs +1 -2
  283. package/dist/routers/oauth-state.mjs +44 -0
  284. package/dist/routers/oauth-state.mjs.map +1 -0
  285. package/dist/routers/preview-base.mjs +37 -0
  286. package/dist/routers/preview-base.mjs.map +1 -0
  287. package/dist/routers/preview-token.mjs +50 -0
  288. package/dist/routers/preview-token.mjs.map +1 -0
  289. package/dist/routers/profile.mjs +4 -3
  290. package/dist/routers/profile.mjs.map +1 -1
  291. package/dist/routers/projects.mjs +18 -3
  292. package/dist/routers/projects.mjs.map +1 -1
  293. package/dist/routers/publish-upload.mjs +85 -0
  294. package/dist/routers/publish-upload.mjs.map +1 -0
  295. package/dist/routers/published-access.mjs +51 -0
  296. package/dist/routers/published-access.mjs.map +1 -0
  297. package/dist/routers/published-compression.mjs +109 -0
  298. package/dist/routers/published-compression.mjs.map +1 -0
  299. package/dist/routers/published-files.mjs +69 -0
  300. package/dist/routers/published-files.mjs.map +1 -0
  301. package/dist/routers/review-forms.mjs +71 -0
  302. package/dist/routers/review-forms.mjs.map +1 -0
  303. package/dist/routers/review-next.mjs +1 -2
  304. package/dist/routers/review-section.handlers.mjs +84 -0
  305. package/dist/routers/review-section.handlers.mjs.map +1 -0
  306. package/dist/routers/review-switch-off.mjs +28 -0
  307. package/dist/routers/review-switch-off.mjs.map +1 -0
  308. package/dist/routers/schemas.mjs +141 -24
  309. package/dist/routers/schemas.mjs.map +1 -1
  310. package/dist/routers/scoped-models.mjs +21 -0
  311. package/dist/routers/scoped-models.mjs.map +1 -0
  312. package/dist/routers/settings-ai.mjs +34 -0
  313. package/dist/routers/settings-ai.mjs.map +1 -0
  314. package/dist/routers/settings-capture.mjs +76 -0
  315. package/dist/routers/settings-capture.mjs.map +1 -0
  316. package/dist/routers/settings-design.mjs +102 -0
  317. package/dist/routers/settings-design.mjs.map +1 -0
  318. package/dist/routers/settings-general.mjs +56 -12
  319. package/dist/routers/settings-general.mjs.map +1 -1
  320. package/dist/routers/settings-git-channel.mjs +76 -0
  321. package/dist/routers/settings-git-channel.mjs.map +1 -0
  322. package/dist/routers/settings-labels.mjs +28 -3
  323. package/dist/routers/settings-labels.mjs.map +1 -1
  324. package/dist/routers/settings-links.mjs +62 -0
  325. package/dist/routers/settings-links.mjs.map +1 -0
  326. package/dist/routers/settings-members.mjs +5 -9
  327. package/dist/routers/settings-members.mjs.map +1 -1
  328. package/dist/routers/settings-notifications.mjs +16 -12
  329. package/dist/routers/settings-notifications.mjs.map +1 -1
  330. package/dist/routers/settings-tokens.mjs +3 -7
  331. package/dist/routers/settings-tokens.mjs.map +1 -1
  332. package/dist/routers/settings-webhooks.mjs +8 -26
  333. package/dist/routers/settings-webhooks.mjs.map +1 -1
  334. package/dist/routers/settings.handlers.mjs +108 -28
  335. package/dist/routers/settings.handlers.mjs.map +1 -1
  336. package/dist/routers/settings.mjs +17 -6
  337. package/dist/routers/settings.mjs.map +1 -1
  338. package/dist/routers/signed-storybook.mjs +55 -0
  339. package/dist/routers/signed-storybook.mjs.map +1 -0
  340. package/dist/routers/snapshot-groups.mjs +62 -0
  341. package/dist/routers/snapshot-groups.mjs.map +1 -0
  342. package/dist/routers/snapshots.mjs +104 -7
  343. package/dist/routers/snapshots.mjs.map +1 -1
  344. package/dist/routers/storybook.mjs +57 -104
  345. package/dist/routers/storybook.mjs.map +1 -1
  346. package/dist/routers/tokens.mjs +3 -7
  347. package/dist/routers/tokens.mjs.map +1 -1
  348. package/dist/routers/ui.mjs +38 -7
  349. package/dist/routers/ui.mjs.map +1 -1
  350. package/dist/routers/webhooks.mjs +6 -13
  351. package/dist/routers/webhooks.mjs.map +1 -1
  352. package/dist/routers/well-known.mjs +12 -6
  353. package/dist/routers/well-known.mjs.map +1 -1
  354. package/dist/routes/api-routes.mjs +92 -0
  355. package/dist/routes/api-routes.mjs.map +1 -0
  356. package/dist/routes/classification.mjs +37 -0
  357. package/dist/routes/classification.mjs.map +1 -0
  358. package/dist/routes/openapi-classes.mjs +21 -0
  359. package/dist/routes/openapi-classes.mjs.map +1 -0
  360. package/dist/routes/page-routes.mjs +103 -0
  361. package/dist/routes/page-routes.mjs.map +1 -0
  362. package/dist/runtime.mjs +19 -15
  363. package/dist/runtime.mjs.map +1 -1
  364. package/dist/serverless.mjs +4 -0
  365. package/dist/store.mjs +20 -3
  366. package/dist/store.mjs.map +1 -1
  367. package/dist/targets.mjs +45 -0
  368. package/dist/targets.mjs.map +1 -0
  369. package/dist/ui/avatar.mjs +1 -2
  370. package/dist/ui/buttons.mjs +17 -5
  371. package/dist/ui/buttons.mjs.map +1 -1
  372. package/dist/ui/code-block.mjs +1 -2
  373. package/dist/ui/compare-stage.mjs +86 -54
  374. package/dist/ui/compare-stage.mjs.map +1 -1
  375. package/dist/ui/csrf-field.mjs +1 -2
  376. package/dist/ui/css.mjs +1 -2
  377. package/dist/ui/design-reference.mjs +96 -0
  378. package/dist/ui/design-reference.mjs.map +1 -0
  379. package/dist/ui/document.mjs +2 -4
  380. package/dist/ui/document.mjs.map +1 -1
  381. package/dist/ui/dropdown.mjs +1 -2
  382. package/dist/ui/feedback.mjs +1 -2
  383. package/dist/ui/filter-input.mjs +1 -2
  384. package/dist/ui/forms.mjs +7 -7
  385. package/dist/ui/forms.mjs.map +1 -1
  386. package/dist/ui/icons/icon.mjs +1 -2
  387. package/dist/ui/icons/paths.mjs +2 -2
  388. package/dist/ui/icons/paths.mjs.map +1 -1
  389. package/dist/ui/icons/sprite.mjs +1 -2
  390. package/dist/ui/kbd.mjs +1 -2
  391. package/dist/ui/layout.mjs +1 -2
  392. package/dist/ui/preview-embed.mjs +81 -0
  393. package/dist/ui/preview-embed.mjs.map +1 -0
  394. package/dist/ui/progress.mjs +1 -2
  395. package/dist/ui/segmented.mjs +1 -2
  396. package/dist/ui/shell/account.mjs +1 -2
  397. package/dist/ui/shell/bare.mjs +1 -2
  398. package/dist/ui/shell/boot-script.mjs +1 -2
  399. package/dist/ui/shell/client-script.mjs +86 -2
  400. package/dist/ui/shell/client-script.mjs.map +1 -1
  401. package/dist/ui/shell/nav.mjs +13 -10
  402. package/dist/ui/shell/nav.mjs.map +1 -1
  403. package/dist/ui/shell/review-script.mjs +25 -3
  404. package/dist/ui/shell/review-script.mjs.map +1 -1
  405. package/dist/ui/shell/sidebar.mjs +3 -4
  406. package/dist/ui/shell/sidebar.mjs.map +1 -1
  407. package/dist/ui/shell/topbar.mjs +1 -2
  408. package/dist/ui/stacks.mjs +1 -2
  409. package/dist/ui/styles/base.mjs +1 -2
  410. package/dist/ui/styles/behavior.mjs +1 -2
  411. package/dist/ui/styles/review-layout.mjs +1 -2
  412. package/dist/ui/styles/review.mjs +1 -2
  413. package/dist/ui/styles/tokens.mjs +1 -2
  414. package/dist/ui/styles.mjs +1 -2
  415. package/dist/ui/subnav.mjs +1 -2
  416. package/dist/ui/table.mjs +1 -2
  417. package/dist/ui/theme.mjs +1 -2
  418. package/dist/ui/thumbnail.mjs +1 -2
  419. package/dist/ui/time.mjs +1 -2
  420. package/package.json +14 -12
  421. package/dist/asset-paths.mjs +0 -23
  422. package/dist/asset-paths.mjs.map +0 -1
  423. package/dist/assets/mark.svg +0 -1
  424. package/dist/pages/settings-status.mjs +0 -137
  425. package/dist/pages/settings-status.mjs.map +0 -1
  426. package/dist/routers/settings-status.mjs +0 -77
  427. package/dist/routers/settings-status.mjs.map +0 -1
  428. package/dist/routers/status-configs.mjs +0 -108
  429. package/dist/routers/status-configs.mjs.map +0 -1
@@ -0,0 +1,63 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { notFound } from "./helpers.mjs";
5
+ import { buildForProject } from "./builds.handlers.mjs";
6
+ import { loadHealthPanel } from "../insights/health-panel.mjs";
7
+ import { loadInsightPanel } from "../insights/panel.mjs";
8
+ import { requestHealth, requestTriage } from "../insights/request.mjs";
9
+ import { InsightPanel } from "../pages/build-diff-insight.mjs";
10
+ import { HealthPanel } from "../pages/health-panel.mjs";
11
+ import { projectForRegenerate, projectForView } from "./insights.handlers.mjs";
12
+ import { jsx } from "hono/jsx/jsx-runtime";
13
+ // src/routers/insights-ui.tsx
14
+ /** Register the HTMX fragments for the project health panel. */
15
+ function registerHealthFragments(app) {
16
+ app.get("/projects/:slug/insights/health/panel", async (c) => {
17
+ const { project } = await projectForView(c, c.req.param("slug"));
18
+ const data = await loadHealthPanel(project);
19
+ return data ? c.html(/* @__PURE__ */ jsx(HealthPanel, {
20
+ project,
21
+ data
22
+ })) : notFound();
23
+ });
24
+ app.post("/projects/:slug/insights/health/generate", async (c) => {
25
+ const { deps, project } = await projectForRegenerate(c, c.req.param("slug"));
26
+ const form = await c.req.formData();
27
+ await requestHealth(deps, project, "30d", { force: form.get("force") === "true" });
28
+ const data = await loadHealthPanel(project);
29
+ return data ? c.html(/* @__PURE__ */ jsx(HealthPanel, {
30
+ project,
31
+ data
32
+ })) : notFound();
33
+ });
34
+ }
35
+ /** Register the HTMX fragments for the build-review AI triage panel. */
36
+ function registerInsightFragments(app) {
37
+ registerHealthFragments(app);
38
+ app.get("/projects/:slug/builds/:buildId/insights/panel", async (c) => {
39
+ const { project } = await projectForView(c, c.req.param("slug"));
40
+ const build = await buildForProject(project.id, c.req.param("buildId"));
41
+ const data = await loadInsightPanel(project, build);
42
+ return data ? c.html(/* @__PURE__ */ jsx(InsightPanel, {
43
+ project,
44
+ build,
45
+ data
46
+ })) : notFound();
47
+ });
48
+ app.post("/projects/:slug/builds/:buildId/insights/generate", async (c) => {
49
+ const { deps, project } = await projectForRegenerate(c, c.req.param("slug"));
50
+ const build = await buildForProject(project.id, c.req.param("buildId"));
51
+ const form = await c.req.formData();
52
+ await requestTriage(deps, project, build, { force: form.get("force") === "true" });
53
+ const data = await loadInsightPanel(project, build);
54
+ return data ? c.html(/* @__PURE__ */ jsx(InsightPanel, {
55
+ project,
56
+ build,
57
+ data
58
+ })) : notFound();
59
+ });
60
+ }
61
+ export { registerInsightFragments };
62
+
63
+ //# sourceMappingURL=insights-ui.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"insights-ui.mjs","names":[],"sources":["../../src/routers/insights-ui.tsx"],"sourcesContent":["import type { ShelfRouter } from \"../app-types.ts\";\nimport { HEALTH_UI_WINDOW, loadHealthPanel } from \"../insights/health-panel.ts\";\nimport { loadInsightPanel } from \"../insights/panel.ts\";\nimport { requestHealth, requestTriage } from \"../insights/request.ts\";\nimport { InsightPanel } from \"../pages/build-diff-insight.tsx\";\nimport { HealthPanel } from \"../pages/health-panel.tsx\";\nimport { buildForProject } from \"./builds.handlers.ts\";\nimport { notFound } from \"./helpers.ts\";\nimport { projectForRegenerate, projectForView } from \"./insights.handlers.ts\";\n\n/** Register the HTMX fragments for the project health panel. */\nfunction registerHealthFragments(app: ShelfRouter): void {\n app.get(\"/projects/:slug/insights/health/panel\", async (c) => {\n const { project } = await projectForView(c, c.req.param(\"slug\"));\n const data = await loadHealthPanel(project);\n return data ? c.html(<HealthPanel project={project} data={data} />) : notFound();\n });\n\n app.post(\"/projects/:slug/insights/health/generate\", async (c) => {\n const { deps, project } = await projectForRegenerate(c, c.req.param(\"slug\"));\n const form = await c.req.formData();\n await requestHealth(deps, project, HEALTH_UI_WINDOW, { force: form.get(\"force\") === \"true\" });\n const data = await loadHealthPanel(project);\n return data ? c.html(<HealthPanel project={project} data={data} />) : notFound();\n });\n}\n\n/** Register the HTMX fragments for the build-review AI triage panel. */\nexport function registerInsightFragments(app: ShelfRouter): void {\n registerHealthFragments(app);\n app.get(\"/projects/:slug/builds/:buildId/insights/panel\", async (c) => {\n const { project } = await projectForView(c, c.req.param(\"slug\"));\n const build = await buildForProject(project.id, c.req.param(\"buildId\"));\n const data = await loadInsightPanel(project, build);\n return data ? c.html(<InsightPanel project={project} build={build} data={data} />) : notFound();\n });\n\n app.post(\"/projects/:slug/builds/:buildId/insights/generate\", async (c) => {\n const { deps, project } = await projectForRegenerate(c, c.req.param(\"slug\"));\n const build = await buildForProject(project.id, c.req.param(\"buildId\"));\n const form = await c.req.formData();\n await requestTriage(deps, project, build, { force: form.get(\"force\") === \"true\" });\n const data = await loadInsightPanel(project, build);\n return data ? c.html(<InsightPanel project={project} build={build} data={data} />) : notFound();\n });\n}\n"],"mappings":";;;;;;;;;;;;;;AAWA,SAAS,wBAAwB,KAAwB;CACvD,IAAI,IAAI,yCAAyC,OAAO,MAAM;EAC5D,MAAM,EAAE,YAAY,MAAM,eAAe,GAAG,EAAE,IAAI,MAAM,MAAM,CAAC;EAC/D,MAAM,OAAO,MAAM,gBAAgB,OAAO;EAC1C,OAAO,OAAO,EAAE,KAAK,oBAAC,aAAD;GAAsB;GAAe;EAAO,CAAA,CAAC,IAAI,SAAS;CACjF,CAAC;CAED,IAAI,KAAK,4CAA4C,OAAO,MAAM;EAChE,MAAM,EAAE,MAAM,YAAY,MAAM,qBAAqB,GAAG,EAAE,IAAI,MAAM,MAAM,CAAC;EAC3E,MAAM,OAAO,MAAM,EAAE,IAAI,SAAS;EAClC,MAAM,cAAc,MAAM,SAAA,OAA2B,EAAE,OAAO,KAAK,IAAI,OAAO,MAAM,OAAO,CAAC;EAC5F,MAAM,OAAO,MAAM,gBAAgB,OAAO;EAC1C,OAAO,OAAO,EAAE,KAAK,oBAAC,aAAD;GAAsB;GAAe;EAAO,CAAA,CAAC,IAAI,SAAS;CACjF,CAAC;AACH;;AAGA,SAAgB,yBAAyB,KAAwB;CAC/D,wBAAwB,GAAG;CAC3B,IAAI,IAAI,kDAAkD,OAAO,MAAM;EACrE,MAAM,EAAE,YAAY,MAAM,eAAe,GAAG,EAAE,IAAI,MAAM,MAAM,CAAC;EAC/D,MAAM,QAAQ,MAAM,gBAAgB,QAAQ,IAAI,EAAE,IAAI,MAAM,SAAS,CAAC;EACtE,MAAM,OAAO,MAAM,iBAAiB,SAAS,KAAK;EAClD,OAAO,OAAO,EAAE,KAAK,oBAAC,cAAD;GAAuB;GAAgB;GAAa;EAAO,CAAA,CAAC,IAAI,SAAS;CAChG,CAAC;CAED,IAAI,KAAK,qDAAqD,OAAO,MAAM;EACzE,MAAM,EAAE,MAAM,YAAY,MAAM,qBAAqB,GAAG,EAAE,IAAI,MAAM,MAAM,CAAC;EAC3E,MAAM,QAAQ,MAAM,gBAAgB,QAAQ,IAAI,EAAE,IAAI,MAAM,SAAS,CAAC;EACtE,MAAM,OAAO,MAAM,EAAE,IAAI,SAAS;EAClC,MAAM,cAAc,MAAM,SAAS,OAAO,EAAE,OAAO,KAAK,IAAI,OAAO,MAAM,OAAO,CAAC;EACjF,MAAM,OAAO,MAAM,iBAAiB,SAAS,KAAK;EAClD,OAAO,OAAO,EAAE,KAAK,oBAAC,cAAD;GAAuB;GAAgB;GAAa;EAAO,CAAA,CAAC,IAAI,SAAS;CAChG,CAAC;AACH"}
@@ -0,0 +1,44 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { getStore } from "../store.mjs";
5
+ import { failWith } from "../insights/errors.mjs";
6
+ import { assertRole, requestHasAdminToken, resolveAuthorizedProject } from "./helpers.mjs";
7
+ import { insightDepsFromStore } from "../insights/deps.mjs";
8
+ import { APPROVER_ROLES, VIEW_ROLES } from "./builds.handlers.mjs";
9
+ // src/routers/insights.handlers.ts
10
+ /** AI deps or `501 ai-disabled` when the site has no AI configured. */
11
+ function requireAi() {
12
+ const deps = insightDepsFromStore();
13
+ if (!deps) failWith(501, "ai-disabled");
14
+ return deps;
15
+ }
16
+ /** `409 ai-disabled-for-project` unless the project selected a profile. */
17
+ function requireProjectAi(project) {
18
+ if (project.aiProfile === null || project.aiProfile === void 0) failWith(409, "ai-disabled-for-project");
19
+ }
20
+ /** Whether the caller is a site admin (or auth is off / bootstrap admin token). */
21
+ function isSiteAdmin(c) {
22
+ const store = getStore();
23
+ return !store.authEnabled || store.user?.role === "admin" || requestHasAdminToken(c);
24
+ }
25
+ /** Resolve a project for reading insights (all four roles). */
26
+ async function projectForView(c, slug) {
27
+ return {
28
+ deps: requireAi(),
29
+ project: await resolveAuthorizedProject(c, slug, ...VIEW_ROLES)
30
+ };
31
+ }
32
+ /** Resolve a project for regenerating (approver + admin) in the ADR guard order. */
33
+ async function projectForRegenerate(c, slug) {
34
+ const { deps, project } = await projectForView(c, slug);
35
+ requireProjectAi(project);
36
+ if (!requestHasAdminToken(c)) await assertRole(project.id, ...APPROVER_ROLES);
37
+ return {
38
+ deps,
39
+ project
40
+ };
41
+ }
42
+ export { isSiteAdmin, projectForRegenerate, projectForView, requireAi, requireProjectAi };
43
+
44
+ //# sourceMappingURL=insights.handlers.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"insights.handlers.mjs","names":[],"sources":["../../src/routers/insights.handlers.ts"],"sourcesContent":["import type { Project } from \"@storyshelf/core/schema\";\n/** Shared guards for the insights API (ADR 0016 §6 guard order). */\nimport type { Context } from \"hono\";\nimport { insightDepsFromStore, type InsightDeps } from \"../insights/deps.ts\";\nimport { failWith } from \"../insights/errors.ts\";\nimport { getStore } from \"../store.ts\";\nimport { APPROVER_ROLES, VIEW_ROLES } from \"./builds.handlers.ts\";\nimport { assertRole, requestHasAdminToken, resolveAuthorizedProject } from \"./helpers.ts\";\n\n/** AI deps or `501 ai-disabled` when the site has no AI configured. */\nexport function requireAi(): InsightDeps {\n const deps = insightDepsFromStore();\n if (!deps) {\n failWith(501, \"ai-disabled\");\n }\n return deps;\n}\n\n/** `409 ai-disabled-for-project` unless the project selected a profile. */\nexport function requireProjectAi(project: Project): void {\n if (project.aiProfile === null || project.aiProfile === undefined) {\n failWith(409, \"ai-disabled-for-project\");\n }\n}\n\n/** Whether the caller is a site admin (or auth is off / bootstrap admin token). */\nexport function isSiteAdmin(c: Context): boolean {\n const store = getStore();\n return !store.authEnabled || store.user?.role === \"admin\" || requestHasAdminToken(c);\n}\n\n/** Resolve a project for reading insights (all four roles). */\nexport async function projectForView(\n c: Context,\n slug: string,\n): Promise<{ deps: InsightDeps; project: Project }> {\n const deps = requireAi();\n const project = await resolveAuthorizedProject(c, slug, ...VIEW_ROLES);\n return { deps, project };\n}\n\n/** Resolve a project for regenerating (approver + admin) in the ADR guard order. */\nexport async function projectForRegenerate(\n c: Context,\n slug: string,\n): Promise<{ deps: InsightDeps; project: Project }> {\n const { deps, project } = await projectForView(c, slug);\n requireProjectAi(project);\n if (!requestHasAdminToken(c)) {\n await assertRole(project.id, ...APPROVER_ROLES);\n }\n return { deps, project };\n}\n"],"mappings":";;;;;;;;;;AAUA,SAAgB,YAAyB;CACvC,MAAM,OAAO,qBAAqB;CAClC,IAAI,CAAC,MACH,SAAS,KAAK,aAAa;CAE7B,OAAO;AACT;;AAGA,SAAgB,iBAAiB,SAAwB;CACvD,IAAI,QAAQ,cAAc,QAAQ,QAAQ,cAAc,KAAA,GACtD,SAAS,KAAK,yBAAyB;AAE3C;;AAGA,SAAgB,YAAY,GAAqB;CAC/C,MAAM,QAAQ,SAAS;CACvB,OAAO,CAAC,MAAM,eAAe,MAAM,MAAM,SAAS,WAAW,qBAAqB,CAAC;AACrF;;AAGA,eAAsB,eACpB,GACA,MACkD;CAGlD,OAAO;EAAE,MAFI,UAED;EAAG,SAAA,MADO,yBAAyB,GAAG,MAAM,GAAG,UAAU;CAC9C;AACzB;;AAGA,eAAsB,qBACpB,GACA,MACkD;CAClD,MAAM,EAAE,MAAM,YAAY,MAAM,eAAe,GAAG,IAAI;CACtD,iBAAiB,OAAO;CACxB,IAAI,CAAC,qBAAqB,CAAC,GACzB,MAAM,WAAW,QAAQ,IAAI,GAAG,cAAc;CAEhD,OAAO;EAAE;EAAM;CAAQ;AACzB"}
@@ -0,0 +1,188 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { failWith } from "../insights/errors.mjs";
5
+ import { notFound } from "./helpers.mjs";
6
+ import { badRequest, errorSchema, healthRequestSchema, insightRequestSchema, insightSchema, notFound as notFound$1, unauthorized } from "./schemas.mjs";
7
+ import { buildForProject } from "./builds.handlers.mjs";
8
+ import { freshen } from "../insights/stale.mjs";
9
+ import { toInsightView } from "../insights/view.mjs";
10
+ import { requestHealth, requestTriage } from "../insights/request.mjs";
11
+ import { isSiteAdmin, projectForRegenerate, projectForView } from "./insights.handlers.mjs";
12
+ import { createRoute, z } from "@hono/zod-openapi";
13
+ import { InsightModel } from "@storyshelf/core/models";
14
+ // src/routers/insights.ts
15
+ const BUILD_PARAMS = z.object({
16
+ slug: z.string(),
17
+ buildId: z.string()
18
+ });
19
+ const json = (schema) => ({ content: { "application/json": { schema } } });
20
+ const guardResponses = {
21
+ 501: {
22
+ ...json(errorSchema),
23
+ description: "AI is not configured on this server (ai-disabled)"
24
+ },
25
+ 409: {
26
+ ...json(errorSchema),
27
+ description: "AI is disabled for this project"
28
+ },
29
+ ...notFound$1,
30
+ ...unauthorized
31
+ };
32
+ const latestRoute = createRoute({
33
+ method: "get",
34
+ tags: ["Insights"],
35
+ summary: "Latest build triage",
36
+ path: "/api/v1/projects/{slug}/builds/{buildId}/insights/latest",
37
+ request: { params: BUILD_PARAMS },
38
+ responses: {
39
+ 200: {
40
+ ...json(insightSchema),
41
+ description: "Latest triage"
42
+ },
43
+ ...guardResponses
44
+ }
45
+ });
46
+ const listRoute = createRoute({
47
+ method: "get",
48
+ tags: ["Insights"],
49
+ summary: "Build triage history",
50
+ path: "/api/v1/projects/{slug}/builds/{buildId}/insights",
51
+ request: {
52
+ params: BUILD_PARAMS,
53
+ query: z.object({ limit: z.coerce.number().int().min(1).max(50).optional() })
54
+ },
55
+ responses: {
56
+ 200: {
57
+ ...json(insightSchema.array()),
58
+ description: "Triage history"
59
+ },
60
+ ...guardResponses
61
+ }
62
+ });
63
+ const generateRoute = createRoute({
64
+ method: "post",
65
+ tags: ["Insights"],
66
+ summary: "Generate a build triage",
67
+ path: "/api/v1/projects/{slug}/builds/{buildId}/insights",
68
+ request: {
69
+ params: BUILD_PARAMS,
70
+ body: { content: { "application/json": { schema: insightRequestSchema } } }
71
+ },
72
+ responses: {
73
+ 200: {
74
+ ...json(insightSchema),
75
+ description: "Cached insight (unbilled)"
76
+ },
77
+ 202: {
78
+ ...json(insightSchema),
79
+ description: "Generation started; poll the latest endpoint"
80
+ },
81
+ 429: {
82
+ ...json(errorSchema),
83
+ description: "Budget or rate limit reached (see Retry-After)"
84
+ },
85
+ ...guardResponses
86
+ }
87
+ });
88
+ const healthGetRoute = createRoute({
89
+ method: "get",
90
+ tags: ["Insights"],
91
+ summary: "Project health digest",
92
+ path: "/api/v1/projects/{slug}/insights/health",
93
+ request: {
94
+ params: z.object({ slug: z.string() }),
95
+ query: z.object({ window: z.string().optional() })
96
+ },
97
+ responses: {
98
+ 200: {
99
+ ...json(insightSchema),
100
+ description: "Cached digest"
101
+ },
102
+ ...guardResponses
103
+ }
104
+ });
105
+ const healthPostRoute = createRoute({
106
+ method: "post",
107
+ tags: ["Insights"],
108
+ summary: "Generate a project health digest",
109
+ path: "/api/v1/projects/{slug}/insights/health",
110
+ request: {
111
+ params: z.object({ slug: z.string() }),
112
+ body: { content: { "application/json": { schema: healthRequestSchema } } }
113
+ },
114
+ responses: {
115
+ 200: {
116
+ ...json(insightSchema),
117
+ description: "Cached digest (unbilled)"
118
+ },
119
+ 202: {
120
+ ...json(insightSchema),
121
+ description: "Generation started"
122
+ },
123
+ 429: {
124
+ ...json(errorSchema),
125
+ description: "Budget or rate limit reached (see Retry-After)"
126
+ },
127
+ ...badRequest,
128
+ ...guardResponses
129
+ }
130
+ });
131
+ /** Register the build triage endpoints. */
132
+ function registerBuildInsights(app) {
133
+ app.openapi(latestRoute, async (c) => {
134
+ const { slug, buildId } = c.req.valid("param");
135
+ const { deps, project } = await projectForView(c, slug);
136
+ const build = await buildForProject(project.id, buildId);
137
+ const [latest] = await new InsightModel(deps.db).listForBuild(build.id, 1);
138
+ return latest ? c.json(toInsightView(await freshen(deps, latest)), 200) : notFound("never-generated: POST to this build's insights to create one");
139
+ });
140
+ app.openapi(listRoute, async (c) => {
141
+ const { slug, buildId } = c.req.valid("param");
142
+ const { limit } = c.req.valid("query");
143
+ const { deps, project } = await projectForView(c, slug);
144
+ const build = await buildForProject(project.id, buildId);
145
+ const rows = await new InsightModel(deps.db).listForBuild(build.id, limit ?? 20);
146
+ return c.json(await Promise.all(rows.map(async (row) => toInsightView(await freshen(deps, row)))), 200);
147
+ });
148
+ app.openapi(generateRoute, async (c) => {
149
+ const { slug, buildId } = c.req.valid("param");
150
+ const body = c.req.valid("json");
151
+ const { deps, project } = await projectForRegenerate(c, slug);
152
+ const build = await buildForProject(project.id, buildId);
153
+ const outcome = await requestTriage(deps, project, build, {
154
+ force: body.force ?? false,
155
+ profile: isSiteAdmin(c) ? body.profile : void 0
156
+ });
157
+ const view = toInsightView(outcome.row);
158
+ return outcome.status === 200 ? c.json(view, 200) : c.json(view, 202, { location: `/api/v1/projects/${slug}/builds/${build.id}/insights/latest` });
159
+ });
160
+ }
161
+ /** Register the project-health endpoints. */
162
+ function registerHealthInsights(app) {
163
+ app.openapi(healthGetRoute, async (c) => {
164
+ const { slug } = c.req.valid("param");
165
+ const { window } = c.req.valid("query");
166
+ const { deps, project } = await projectForView(c, slug);
167
+ const row = await new InsightModel(deps.db).getHealth(project.id, window ?? "30d");
168
+ return row ? c.json(toInsightView(await freshen(deps, row)), 200) : notFound("never-generated: POST to create a health digest");
169
+ });
170
+ app.openapi(healthPostRoute, async (c) => {
171
+ const { slug } = c.req.valid("param");
172
+ const body = c.req.valid("json");
173
+ const { deps, project } = await projectForRegenerate(c, slug);
174
+ const window = body.window ?? "30d";
175
+ const outcome = await requestHealth(deps, project, window, { force: body.force ?? false });
176
+ if (!outcome) failWith(400, "window must look like 30d (1-365 days)");
177
+ const view = toInsightView(outcome.row);
178
+ return outcome.status === 200 ? c.json(view, 200) : c.json(view, 202, { location: `/api/v1/projects/${slug}/insights/health?window=${window}` });
179
+ });
180
+ }
181
+ /** Register the AI insights endpoints (build triage and project health). */
182
+ function registerInsights(app) {
183
+ registerBuildInsights(app);
184
+ registerHealthInsights(app);
185
+ }
186
+ export { registerInsights };
187
+
188
+ //# sourceMappingURL=insights.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"insights.mjs","names":["notFound","throwNotFound"],"sources":["../../src/routers/insights.ts"],"sourcesContent":["import { createRoute, z } from \"@hono/zod-openapi\";\nimport { InsightModel } from \"@storyshelf/core/models\";\nimport type { ShelfRouter } from \"../app-types.ts\";\nimport { failWith } from \"../insights/errors.ts\";\nimport { requestHealth, requestTriage } from \"../insights/request.ts\";\nimport { freshen } from \"../insights/stale.ts\";\nimport { toInsightView } from \"../insights/view.ts\";\nimport { buildForProject } from \"./builds.handlers.ts\";\nimport { notFound as throwNotFound } from \"./helpers.ts\";\nimport { isSiteAdmin, projectForRegenerate, projectForView } from \"./insights.handlers.ts\";\nimport {\n badRequest,\n errorSchema,\n healthRequestSchema,\n insightRequestSchema,\n insightSchema,\n notFound,\n unauthorized,\n} from \"./schemas.ts\";\n\nconst BUILD_PARAMS = z.object({ slug: z.string(), buildId: z.string() });\nconst json = (schema: z.ZodType): { content: { \"application/json\": { schema: z.ZodType } } } => ({\n content: { \"application/json\": { schema } },\n});\nconst guardResponses = {\n 501: { ...json(errorSchema), description: \"AI is not configured on this server (ai-disabled)\" },\n 409: { ...json(errorSchema), description: \"AI is disabled for this project\" },\n ...notFound,\n ...unauthorized,\n} as const;\n\nconst latestRoute = createRoute({\n method: \"get\",\n tags: [\"Insights\"],\n summary: \"Latest build triage\",\n path: \"/api/v1/projects/{slug}/builds/{buildId}/insights/latest\",\n request: { params: BUILD_PARAMS },\n responses: { 200: { ...json(insightSchema), description: \"Latest triage\" }, ...guardResponses },\n});\n\nconst listRoute = createRoute({\n method: \"get\",\n tags: [\"Insights\"],\n summary: \"Build triage history\",\n path: \"/api/v1/projects/{slug}/builds/{buildId}/insights\",\n request: {\n params: BUILD_PARAMS,\n query: z.object({ limit: z.coerce.number().int().min(1).max(50).optional() }),\n },\n responses: {\n 200: { ...json(insightSchema.array()), description: \"Triage history\" },\n ...guardResponses,\n },\n});\n\nconst generateRoute = createRoute({\n method: \"post\",\n tags: [\"Insights\"],\n summary: \"Generate a build triage\",\n path: \"/api/v1/projects/{slug}/builds/{buildId}/insights\",\n request: {\n params: BUILD_PARAMS,\n body: { content: { \"application/json\": { schema: insightRequestSchema } } },\n },\n responses: {\n 200: { ...json(insightSchema), description: \"Cached insight (unbilled)\" },\n 202: { ...json(insightSchema), description: \"Generation started; poll the latest endpoint\" },\n 429: { ...json(errorSchema), description: \"Budget or rate limit reached (see Retry-After)\" },\n ...guardResponses,\n },\n});\n\nconst healthGetRoute = createRoute({\n method: \"get\",\n tags: [\"Insights\"],\n summary: \"Project health digest\",\n path: \"/api/v1/projects/{slug}/insights/health\",\n request: {\n params: z.object({ slug: z.string() }),\n query: z.object({ window: z.string().optional() }),\n },\n responses: { 200: { ...json(insightSchema), description: \"Cached digest\" }, ...guardResponses },\n});\n\nconst healthPostRoute = createRoute({\n method: \"post\",\n tags: [\"Insights\"],\n summary: \"Generate a project health digest\",\n path: \"/api/v1/projects/{slug}/insights/health\",\n request: {\n params: z.object({ slug: z.string() }),\n body: { content: { \"application/json\": { schema: healthRequestSchema } } },\n },\n responses: {\n 200: { ...json(insightSchema), description: \"Cached digest (unbilled)\" },\n 202: { ...json(insightSchema), description: \"Generation started\" },\n 429: { ...json(errorSchema), description: \"Budget or rate limit reached (see Retry-After)\" },\n ...badRequest,\n ...guardResponses,\n },\n});\n\n/** Register the build triage endpoints. */\nfunction registerBuildInsights(app: ShelfRouter): void {\n app.openapi(latestRoute, async (c) => {\n const { slug, buildId } = c.req.valid(\"param\");\n const { deps, project } = await projectForView(c, slug);\n const build = await buildForProject(project.id, buildId);\n const [latest] = await new InsightModel(deps.db).listForBuild(build.id, 1);\n return latest\n ? c.json(toInsightView(await freshen(deps, latest)), 200)\n : throwNotFound(\"never-generated: POST to this build's insights to create one\");\n });\n\n app.openapi(listRoute, async (c) => {\n const { slug, buildId } = c.req.valid(\"param\");\n const { limit } = c.req.valid(\"query\");\n const { deps, project } = await projectForView(c, slug);\n const build = await buildForProject(project.id, buildId);\n const rows = await new InsightModel(deps.db).listForBuild(build.id, limit ?? 20);\n return c.json(\n await Promise.all(rows.map(async (row) => toInsightView(await freshen(deps, row)))),\n 200,\n );\n });\n\n app.openapi(generateRoute, async (c) => {\n const { slug, buildId } = c.req.valid(\"param\");\n const body = c.req.valid(\"json\");\n const { deps, project } = await projectForRegenerate(c, slug);\n const build = await buildForProject(project.id, buildId);\n const outcome = await requestTriage(deps, project, build, {\n force: body.force ?? false,\n profile: isSiteAdmin(c) ? body.profile : undefined,\n });\n const view = toInsightView(outcome.row);\n return outcome.status === 200\n ? c.json(view, 200)\n : c.json(view, 202, {\n location: `/api/v1/projects/${slug}/builds/${build.id}/insights/latest`,\n });\n });\n}\n\n/** Register the project-health endpoints. */\nfunction registerHealthInsights(app: ShelfRouter): void {\n app.openapi(healthGetRoute, async (c) => {\n const { slug } = c.req.valid(\"param\");\n const { window } = c.req.valid(\"query\");\n const { deps, project } = await projectForView(c, slug);\n const row = await new InsightModel(deps.db).getHealth(project.id, window ?? \"30d\");\n return row\n ? c.json(toInsightView(await freshen(deps, row)), 200)\n : throwNotFound(\"never-generated: POST to create a health digest\");\n });\n\n app.openapi(healthPostRoute, async (c) => {\n const { slug } = c.req.valid(\"param\");\n const body = c.req.valid(\"json\");\n const { deps, project } = await projectForRegenerate(c, slug);\n const window = body.window ?? \"30d\";\n const outcome = await requestHealth(deps, project, window, { force: body.force ?? false });\n if (!outcome) {\n failWith(400, \"window must look like 30d (1-365 days)\");\n }\n const view = toInsightView(outcome.row);\n return outcome.status === 200\n ? c.json(view, 200)\n : c.json(view, 202, {\n location: `/api/v1/projects/${slug}/insights/health?window=${window}`,\n });\n });\n}\n\n/** Register the AI insights endpoints (build triage and project health). */\nexport function registerInsights(app: ShelfRouter): void {\n registerBuildInsights(app);\n registerHealthInsights(app);\n}\n"],"mappings":"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"}
@@ -7,7 +7,7 @@ import { labelTypeCreateSchema, labelTypeSchema, labelTypeUpdateSchema, notFound
7
7
  import { createRoute, z } from "@hono/zod-openapi";
8
8
  import { LabelModel } from "@storyshelf/core/models";
9
9
  import { HTTPException } from "hono/http-exception";
10
- //#region src/routers/labels.ts
10
+ // src/routers/labels.ts
11
11
  const VIEW_ROLES = [
12
12
  "viewer",
13
13
  "developer",
@@ -112,7 +112,6 @@ function registerLabels(app) {
112
112
  }
113
113
  });
114
114
  }
115
- //#endregion
116
115
  export { registerLabels };
117
116
 
118
117
  //# sourceMappingURL=labels.mjs.map
@@ -0,0 +1,79 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { catalogOf, getStore } from "../store.mjs";
5
+ import { resolveAuthorizedProject } from "./helpers.mjs";
6
+ import { BuildModel, PackageUsageModel, ProjectModel, SnapshotModel, matchUpstreamStory } from "@storyshelf/core/models";
7
+ // src/routers/links.handlers.ts
8
+ const VIEW_ROLES = [
9
+ "viewer",
10
+ "developer",
11
+ "approver",
12
+ "admin"
13
+ ];
14
+ /**
15
+ * Upstream project resolved from its latest approved default build: its stories from the build's
16
+ * snapshots when it has them, from its Storybook catalog otherwise (a hosting-only build).
17
+ */
18
+ async function resolveUpstream(upstream) {
19
+ const build = await new BuildModel(getStore().db).latestApprovedDefault(upstream);
20
+ const stories = (build ? await new SnapshotModel(getStore().db).listByBuild(build.id) : []).flatMap((snapshot) => snapshot.storyImportPath ? [{
21
+ importPath: snapshot.storyImportPath,
22
+ storyId: snapshot.storyId
23
+ }] : []);
24
+ return {
25
+ slug: upstream.slug,
26
+ buildId: build?.id ?? null,
27
+ stories: stories.length > 0 || !build ? stories : await catalogStories(upstream, build.id)
28
+ };
29
+ }
30
+ async function catalogStories(upstream, buildId) {
31
+ return ((await catalogOf().load(upstream.id, buildId).catch(() => null))?.entries ?? []).flatMap((entry) => entry.type === "story" && entry.importPath ? [{
32
+ importPath: entry.importPath,
33
+ storyId: entry.id
34
+ }] : []);
35
+ }
36
+ /** Resolve each linked package once; upstreams the viewer cannot open are omitted. */
37
+ async function resolveLinkedUpstreams(c, project, packages) {
38
+ const links = (await new PackageUsageModel(getStore().db).listLinks(project.id)).filter((link) => packages.has(link.packageName));
39
+ const resolved = await Promise.all(links.map(async (link) => {
40
+ const upstream = await new ProjectModel(getStore().db).get(link.upstreamId);
41
+ const visible = upstream ? await resolveAuthorizedProject(c, upstream.slug, ...VIEW_ROLES).catch(() => null) : null;
42
+ return upstream && visible ? [link.packageName, await resolveUpstream(upstream)] : null;
43
+ }));
44
+ return new Map(resolved.filter((entry) => entry !== null));
45
+ }
46
+ /**
47
+ * Linked-package usage grouped by story file, resolved with one pass over the
48
+ * build's usage rows and one lookup per linked upstream (never per story).
49
+ * Omits upstreams the viewer cannot open; pass `storyImportPath` to narrow to one story.
50
+ */
51
+ async function linkedUsageByStory(c, project, buildId, storyImportPath) {
52
+ const model = new PackageUsageModel(getStore().db);
53
+ const rows = storyImportPath ? await model.listForStory(buildId, storyImportPath) : await model.listForBuild(buildId);
54
+ const upstreams = await resolveLinkedUpstreams(c, project, new Set(rows.map((row) => row.packageName)));
55
+ const byStory = /* @__PURE__ */ new Map();
56
+ for (const row of rows) {
57
+ const upstream = upstreams.get(row.packageName);
58
+ if (upstream) {
59
+ const upstreamStoryId = matchUpstreamStory(row.modulePath, upstream.stories);
60
+ byStory.set(row.storyImportPath, [...byStory.get(row.storyImportPath) ?? [], {
61
+ package: row.packageName,
62
+ version: row.version,
63
+ upstreamSlug: upstream.slug,
64
+ upstreamBuildId: upstream.buildId,
65
+ upstreamStoryId,
66
+ upstreamImportPath: upstream.stories.find((story) => story.storyId === upstreamStoryId)?.importPath ?? null
67
+ }]);
68
+ }
69
+ }
70
+ return byStory;
71
+ }
72
+ /** Linked-package usage of one story, omitting upstreams the viewer cannot open. */
73
+ async function linkedUsageFor(c, project, buildId, storyImportPath) {
74
+ if (!storyImportPath) return [];
75
+ return (await linkedUsageByStory(c, project, buildId, storyImportPath)).get(storyImportPath) ?? [];
76
+ }
77
+ export { linkedUsageByStory, linkedUsageFor };
78
+
79
+ //# sourceMappingURL=links.handlers.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"links.handlers.mjs","names":[],"sources":["../../src/routers/links.handlers.ts"],"sourcesContent":["import {\n BuildModel,\n matchUpstreamStory,\n PackageUsageModel,\n ProjectModel,\n SnapshotModel,\n} from \"@storyshelf/core/models\";\nimport type { Project } from \"@storyshelf/core/schema\";\nimport type { ProjectRole } from \"@storyshelf/core/types\";\nimport type { LinkedUsageItem } from \"../pages/build-diff-usage.tsx\";\nimport { catalogOf, getStore } from \"../store.ts\";\nimport { resolveAuthorizedProject } from \"./helpers.ts\";\n\nconst VIEW_ROLES: readonly ProjectRole[] = [\"viewer\", \"developer\", \"approver\", \"admin\"];\n\ntype Caller = Parameters<typeof resolveAuthorizedProject>[0];\n\n/** What a link resolves to: the upstream project's latest published build and its stories. */\ninterface ResolvedUpstream {\n slug: string;\n buildId: string | null;\n stories: { importPath: string; storyId: string }[];\n}\n\n/**\n * Upstream project resolved from its latest approved default build: its stories from the build's\n * snapshots when it has them, from its Storybook catalog otherwise (a hosting-only build).\n */\nasync function resolveUpstream(upstream: Project): Promise<ResolvedUpstream> {\n const build = await new BuildModel(getStore().db).latestApprovedDefault(upstream);\n const snapshots = build ? await new SnapshotModel(getStore().db).listByBuild(build.id) : [];\n const stories = snapshots.flatMap((snapshot) =>\n snapshot.storyImportPath\n ? [{ importPath: snapshot.storyImportPath, storyId: snapshot.storyId }]\n : [],\n );\n return {\n slug: upstream.slug,\n buildId: build?.id ?? null,\n stories: stories.length > 0 || !build ? stories : await catalogStories(upstream, build.id),\n };\n}\n\nasync function catalogStories(\n upstream: Project,\n buildId: string,\n): Promise<ResolvedUpstream[\"stories\"]> {\n const catalog = await catalogOf()\n .load(upstream.id, buildId)\n .catch(() => null);\n return (catalog?.entries ?? []).flatMap((entry) =>\n entry.type === \"story\" && entry.importPath\n ? [{ importPath: entry.importPath, storyId: entry.id }]\n : [],\n );\n}\n\n/** Resolve each linked package once; upstreams the viewer cannot open are omitted. */\nasync function resolveLinkedUpstreams(\n c: Caller,\n project: Project,\n packages: Set<string>,\n): Promise<Map<string, ResolvedUpstream>> {\n const links = (await new PackageUsageModel(getStore().db).listLinks(project.id)).filter((link) =>\n packages.has(link.packageName),\n );\n const resolved = await Promise.all(\n links.map(async (link) => {\n const upstream = await new ProjectModel(getStore().db).get(link.upstreamId);\n const visible = upstream\n ? await resolveAuthorizedProject(c, upstream.slug, ...VIEW_ROLES).catch(() => null)\n : null;\n return upstream && visible\n ? ([link.packageName, await resolveUpstream(upstream)] as const)\n : null;\n }),\n );\n return new Map(resolved.filter((entry) => entry !== null));\n}\n\n/**\n * Linked-package usage grouped by story file, resolved with one pass over the\n * build's usage rows and one lookup per linked upstream (never per story).\n * Omits upstreams the viewer cannot open; pass `storyImportPath` to narrow to one story.\n */\nexport async function linkedUsageByStory(\n c: Caller,\n project: Project,\n buildId: string,\n storyImportPath?: string,\n): Promise<Map<string, LinkedUsageItem[]>> {\n const model = new PackageUsageModel(getStore().db);\n const rows = storyImportPath\n ? await model.listForStory(buildId, storyImportPath)\n : await model.listForBuild(buildId);\n const upstreams = await resolveLinkedUpstreams(\n c,\n project,\n new Set(rows.map((row) => row.packageName)),\n );\n const byStory = new Map<string, LinkedUsageItem[]>();\n for (const row of rows) {\n const upstream = upstreams.get(row.packageName);\n if (upstream) {\n const upstreamStoryId = matchUpstreamStory(row.modulePath, upstream.stories);\n byStory.set(row.storyImportPath, [\n ...(byStory.get(row.storyImportPath) ?? []),\n {\n package: row.packageName,\n version: row.version,\n upstreamSlug: upstream.slug,\n upstreamBuildId: upstream.buildId,\n upstreamStoryId,\n upstreamImportPath:\n upstream.stories.find((story) => story.storyId === upstreamStoryId)?.importPath ?? null,\n },\n ]);\n }\n }\n return byStory;\n}\n\n/** Linked-package usage of one story, omitting upstreams the viewer cannot open. */\nexport async function linkedUsageFor(\n c: Caller,\n project: Project,\n buildId: string,\n storyImportPath: string | null,\n): Promise<LinkedUsageItem[]> {\n if (!storyImportPath) {\n return [];\n }\n return (\n (await linkedUsageByStory(c, project, buildId, storyImportPath)).get(storyImportPath) ?? []\n );\n}\n"],"mappings":";;;;;;;AAaA,MAAM,aAAqC;CAAC;CAAU;CAAa;CAAY;AAAO;;;;;AAetF,eAAe,gBAAgB,UAA8C;CAC3E,MAAM,QAAQ,MAAM,IAAI,WAAW,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,sBAAsB,QAAQ;CAEhF,MAAM,WADY,QAAQ,MAAM,IAAI,cAAc,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,YAAY,MAAM,EAAE,IAAI,CAAC,EAAA,CAChE,SAAS,aACjC,SAAS,kBACL,CAAC;EAAE,YAAY,SAAS;EAAiB,SAAS,SAAS;CAAQ,CAAC,IACpE,CAAC,CACP;CACA,OAAO;EACL,MAAM,SAAS;EACf,SAAS,OAAO,MAAM;EACtB,SAAS,QAAQ,SAAS,KAAK,CAAC,QAAQ,UAAU,MAAM,eAAe,UAAU,MAAM,EAAE;CAC3F;AACF;AAEA,eAAe,eACb,UACA,SACsC;CAItC,SAAQ,MAHc,UAAU,CAAC,CAC9B,KAAK,SAAS,IAAI,OAAO,CAAC,CAC1B,YAAY,IAAI,EAAA,EACF,WAAW,CAAC,EAAA,CAAG,SAAS,UACvC,MAAM,SAAS,WAAW,MAAM,aAC5B,CAAC;EAAE,YAAY,MAAM;EAAY,SAAS,MAAM;CAAG,CAAC,IACpD,CAAC,CACP;AACF;;AAGA,eAAe,uBACb,GACA,SACA,UACwC;CACxC,MAAM,SAAS,MAAM,IAAI,kBAAkB,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,UAAU,QAAQ,EAAE,EAAA,CAAG,QAAQ,SACvF,SAAS,IAAI,KAAK,WAAW,CAC/B;CACA,MAAM,WAAW,MAAM,QAAQ,IAC7B,MAAM,IAAI,OAAO,SAAS;EACxB,MAAM,WAAW,MAAM,IAAI,aAAa,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,KAAK,UAAU;EAC1E,MAAM,UAAU,WACZ,MAAM,yBAAyB,GAAG,SAAS,MAAM,GAAG,UAAU,CAAC,CAAC,YAAY,IAAI,IAChF;EACJ,OAAO,YAAY,UACd,CAAC,KAAK,aAAa,MAAM,gBAAgB,QAAQ,CAAC,IACnD;CACN,CAAC,CACH;CACA,OAAO,IAAI,IAAI,SAAS,QAAQ,UAAU,UAAU,IAAI,CAAC;AAC3D;;;;;;AAOA,eAAsB,mBACpB,GACA,SACA,SACA,iBACyC;CACzC,MAAM,QAAQ,IAAI,kBAAkB,SAAS,CAAC,CAAC,EAAE;CACjD,MAAM,OAAO,kBACT,MAAM,MAAM,aAAa,SAAS,eAAe,IACjD,MAAM,MAAM,aAAa,OAAO;CACpC,MAAM,YAAY,MAAM,uBACtB,GACA,SACA,IAAI,IAAI,KAAK,KAAK,QAAQ,IAAI,WAAW,CAAC,CAC5C;CACA,MAAM,0BAAU,IAAI,IAA+B;CACnD,KAAK,MAAM,OAAO,MAAM;EACtB,MAAM,WAAW,UAAU,IAAI,IAAI,WAAW;EAC9C,IAAI,UAAU;GACZ,MAAM,kBAAkB,mBAAmB,IAAI,YAAY,SAAS,OAAO;GAC3E,QAAQ,IAAI,IAAI,iBAAiB,CAC/B,GAAI,QAAQ,IAAI,IAAI,eAAe,KAAK,CAAC,GACzC;IACE,SAAS,IAAI;IACb,SAAS,IAAI;IACb,cAAc,SAAS;IACvB,iBAAiB,SAAS;IAC1B;IACA,oBACE,SAAS,QAAQ,MAAM,UAAU,MAAM,YAAY,eAAe,CAAC,EAAE,cAAc;GACvF,CACF,CAAC;EACH;CACF;CACA,OAAO;AACT;;AAGA,eAAsB,eACpB,GACA,SACA,SACA,iBAC4B;CAC5B,IAAI,CAAC,iBACH,OAAO,CAAC;CAEV,QACG,MAAM,mBAAmB,GAAG,SAAS,SAAS,eAAe,EAAA,CAAG,IAAI,eAAe,KAAK,CAAC;AAE9F"}
@@ -0,0 +1,138 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { getStore } from "../store.mjs";
5
+ import { resolveAuthorizedProject } from "./helpers.mjs";
6
+ import { badRequest, forbidden, notFound, unauthorized } from "./schemas.mjs";
7
+ import { buildForProject, snapshotForBuild } from "./builds.handlers.mjs";
8
+ import { linkedUsageFor } from "./links.handlers.mjs";
9
+ import { createRoute, z } from "@hono/zod-openapi";
10
+ import { PackageUsageModel, ProjectModel } from "@storyshelf/core/models";
11
+ import { HTTPException } from "hono/http-exception";
12
+ // src/routers/links.ts
13
+ const VIEW_ROLES = [
14
+ "viewer",
15
+ "developer",
16
+ "approver",
17
+ "admin"
18
+ ];
19
+ const ADMIN_ROLES = ["admin"];
20
+ const linkSchema = z.object({
21
+ package: z.string().min(1).max(214),
22
+ upstreamSlug: z.string().min(1)
23
+ });
24
+ const linksBodySchema = z.object({ links: z.array(linkSchema).max(200) });
25
+ const usageItemSchema = z.object({
26
+ package: z.string(),
27
+ version: z.string().nullable(),
28
+ upstreamSlug: z.string(),
29
+ /** Latest approved default build of the upstream project; null when it has none. */
30
+ upstreamBuildId: z.string().nullable(),
31
+ /** Upstream story the package module maps to; null falls back to the library. */
32
+ upstreamStoryId: z.string().nullable(),
33
+ /** Story file of the matched upstream story; null when nothing matched. */
34
+ upstreamImportPath: z.string().nullable().optional()
35
+ });
36
+ const projectParams = z.object({ slug: z.string() });
37
+ const listLinksRoute = createRoute({
38
+ method: "get",
39
+ tags: ["Links"],
40
+ summary: "List the packages this project consumes from other projects",
41
+ path: "/api/v1/projects/{slug}/links",
42
+ request: { params: projectParams },
43
+ responses: {
44
+ 200: {
45
+ content: { "application/json": { schema: linksBodySchema } },
46
+ description: "Package links of the project"
47
+ },
48
+ ...notFound,
49
+ ...unauthorized
50
+ }
51
+ });
52
+ const putLinksRoute = createRoute({
53
+ method: "put",
54
+ tags: ["Links"],
55
+ summary: "Replace the packages this project consumes from other projects",
56
+ path: "/api/v1/projects/{slug}/links",
57
+ request: {
58
+ params: projectParams,
59
+ body: { content: { "application/json": { schema: linksBodySchema } } }
60
+ },
61
+ responses: {
62
+ 200: {
63
+ content: { "application/json": { schema: linksBodySchema } },
64
+ description: "Links replaced"
65
+ },
66
+ ...badRequest,
67
+ ...forbidden,
68
+ ...notFound
69
+ }
70
+ });
71
+ const usageRoute = createRoute({
72
+ method: "get",
73
+ tags: ["Links"],
74
+ summary: "Linked packages a snapshot's story uses",
75
+ path: "/api/v1/projects/{slug}/builds/{buildId}/snapshots/{snapshotId}/usage",
76
+ request: { params: z.object({
77
+ slug: z.string(),
78
+ buildId: z.string(),
79
+ snapshotId: z.string()
80
+ }) },
81
+ responses: {
82
+ 200: {
83
+ content: { "application/json": { schema: usageItemSchema.array() } },
84
+ description: "Usage of linked packages; empty when the build reported none"
85
+ },
86
+ ...notFound,
87
+ ...unauthorized
88
+ }
89
+ });
90
+ /** Resolve link slugs to projects, rejecting unknown, self, duplicate, or unviewable ones. */
91
+ async function resolveLinkTargets(c, project, links) {
92
+ if (new Set(links.map((link) => link.package)).size !== links.length) throw new HTTPException(400, { message: "Each package can be linked once" });
93
+ return await Promise.all(links.map(async (link) => {
94
+ if (link.upstreamSlug === project.slug) throw new HTTPException(400, { message: "A project cannot link to itself" });
95
+ return {
96
+ upstreamId: (await resolveAuthorizedProject(c, link.upstreamSlug, ...VIEW_ROLES).catch(() => {
97
+ throw new HTTPException(400, { message: `Unknown project: ${link.upstreamSlug}` });
98
+ })).id,
99
+ packageName: link.package
100
+ };
101
+ }));
102
+ }
103
+ /** Links of a project as API shape, dropping upstreams that no longer exist. */
104
+ async function linkViews(project) {
105
+ const db = getStore().db;
106
+ const links = await new PackageUsageModel(db).listLinks(project.id);
107
+ return (await Promise.all(links.map(async (link) => {
108
+ const upstream = await new ProjectModel(db).get(link.upstreamId);
109
+ return upstream ? {
110
+ package: link.packageName,
111
+ upstreamSlug: upstream.slug
112
+ } : null;
113
+ }))).filter((view) => view !== null);
114
+ }
115
+ /** Register the package-link and snapshot-usage endpoints. */
116
+ function registerLinks(app) {
117
+ app.openapi(listLinksRoute, async (c) => {
118
+ const project = await resolveAuthorizedProject(c, c.req.valid("param").slug, ...VIEW_ROLES);
119
+ return c.json({ links: await linkViews(project) });
120
+ });
121
+ app.openapi(putLinksRoute, async (c) => {
122
+ const project = await resolveAuthorizedProject(c, c.req.valid("param").slug, ...ADMIN_ROLES);
123
+ const { links } = c.req.valid("json");
124
+ const targets = await resolveLinkTargets(c, project, links);
125
+ await new PackageUsageModel(getStore().db).replaceLinks(project.id, targets);
126
+ return c.json({ links: await linkViews(project) });
127
+ });
128
+ app.openapi(usageRoute, async (c) => {
129
+ const { slug, buildId, snapshotId } = c.req.valid("param");
130
+ const project = await resolveAuthorizedProject(c, slug, ...VIEW_ROLES);
131
+ const build = await buildForProject(project.id, buildId);
132
+ const snapshot = await snapshotForBuild(build, snapshotId);
133
+ return c.json(await linkedUsageFor(c, project, build.id, snapshot.storyImportPath));
134
+ });
135
+ }
136
+ export { registerLinks };
137
+
138
+ //# sourceMappingURL=links.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"links.mjs","names":[],"sources":["../../src/routers/links.ts"],"sourcesContent":["import { createRoute, z } from \"@hono/zod-openapi\";\nimport { PackageUsageModel, ProjectModel } from \"@storyshelf/core/models\";\nimport type { Project } from \"@storyshelf/core/schema\";\nimport type { ProjectRole } from \"@storyshelf/core/types\";\nimport { HTTPException } from \"hono/http-exception\";\nimport type { ShelfRouter } from \"../app-types.ts\";\nimport { getStore } from \"../store.ts\";\nimport { buildForProject, snapshotForBuild } from \"./builds.handlers.ts\";\nimport { resolveAuthorizedProject } from \"./helpers.ts\";\nimport { linkedUsageFor } from \"./links.handlers.ts\";\nimport { badRequest, forbidden, notFound, unauthorized } from \"./schemas.ts\";\n\nconst VIEW_ROLES: readonly ProjectRole[] = [\"viewer\", \"developer\", \"approver\", \"admin\"];\nconst ADMIN_ROLES: readonly ProjectRole[] = [\"admin\"];\n\nconst linkSchema = z.object({\n package: z.string().min(1).max(214),\n upstreamSlug: z.string().min(1),\n});\nconst linksBodySchema = z.object({ links: z.array(linkSchema).max(200) });\nconst usageItemSchema = z.object({\n package: z.string(),\n version: z.string().nullable(),\n upstreamSlug: z.string(),\n /** Latest approved default build of the upstream project; null when it has none. */\n upstreamBuildId: z.string().nullable(),\n /** Upstream story the package module maps to; null falls back to the library. */\n upstreamStoryId: z.string().nullable(),\n /** Story file of the matched upstream story; null when nothing matched. */\n upstreamImportPath: z.string().nullable().optional(),\n});\n\nconst projectParams = z.object({ slug: z.string() });\n\nconst listLinksRoute = createRoute({\n method: \"get\",\n tags: [\"Links\"],\n summary: \"List the packages this project consumes from other projects\",\n path: \"/api/v1/projects/{slug}/links\",\n request: { params: projectParams },\n responses: {\n 200: {\n content: { \"application/json\": { schema: linksBodySchema } },\n description: \"Package links of the project\",\n },\n ...notFound,\n ...unauthorized,\n },\n});\n\nconst putLinksRoute = createRoute({\n method: \"put\",\n tags: [\"Links\"],\n summary: \"Replace the packages this project consumes from other projects\",\n path: \"/api/v1/projects/{slug}/links\",\n request: {\n params: projectParams,\n body: { content: { \"application/json\": { schema: linksBodySchema } } },\n },\n responses: {\n 200: {\n content: { \"application/json\": { schema: linksBodySchema } },\n description: \"Links replaced\",\n },\n ...badRequest,\n ...forbidden,\n ...notFound,\n },\n});\n\nconst usageRoute = createRoute({\n method: \"get\",\n tags: [\"Links\"],\n summary: \"Linked packages a snapshot's story uses\",\n path: \"/api/v1/projects/{slug}/builds/{buildId}/snapshots/{snapshotId}/usage\",\n request: {\n params: z.object({ slug: z.string(), buildId: z.string(), snapshotId: z.string() }),\n },\n responses: {\n 200: {\n content: { \"application/json\": { schema: usageItemSchema.array() } },\n description: \"Usage of linked packages; empty when the build reported none\",\n },\n ...notFound,\n ...unauthorized,\n },\n});\n\n/** Resolve link slugs to projects, rejecting unknown, self, duplicate, or unviewable ones. */\nasync function resolveLinkTargets(\n c: Parameters<typeof resolveAuthorizedProject>[0],\n project: Project,\n links: z.infer<typeof linkSchema>[],\n): Promise<{ upstreamId: string; packageName: string }[]> {\n if (new Set(links.map((link) => link.package)).size !== links.length) {\n throw new HTTPException(400, { message: \"Each package can be linked once\" });\n }\n return await Promise.all(\n links.map(async (link) => {\n if (link.upstreamSlug === project.slug) {\n throw new HTTPException(400, { message: \"A project cannot link to itself\" });\n }\n const upstream = await resolveAuthorizedProject(c, link.upstreamSlug, ...VIEW_ROLES).catch(\n () => {\n throw new HTTPException(400, { message: `Unknown project: ${link.upstreamSlug}` });\n },\n );\n return { upstreamId: upstream.id, packageName: link.package };\n }),\n );\n}\n\n/** Links of a project as API shape, dropping upstreams that no longer exist. */\nasync function linkViews(project: Project): Promise<z.infer<typeof linkSchema>[]> {\n const db = getStore().db;\n const links = await new PackageUsageModel(db).listLinks(project.id);\n const views = await Promise.all(\n links.map(async (link) => {\n const upstream = await new ProjectModel(db).get(link.upstreamId);\n return upstream ? { package: link.packageName, upstreamSlug: upstream.slug } : null;\n }),\n );\n return views.filter((view): view is z.infer<typeof linkSchema> => view !== null);\n}\n\n/** Register the package-link and snapshot-usage endpoints. */\nexport function registerLinks(app: ShelfRouter): void {\n app.openapi(listLinksRoute, async (c) => {\n const project = await resolveAuthorizedProject(c, c.req.valid(\"param\").slug, ...VIEW_ROLES);\n return c.json({ links: await linkViews(project) });\n });\n\n app.openapi(putLinksRoute, async (c) => {\n const project = await resolveAuthorizedProject(c, c.req.valid(\"param\").slug, ...ADMIN_ROLES);\n const { links } = c.req.valid(\"json\");\n const targets = await resolveLinkTargets(c, project, links);\n await new PackageUsageModel(getStore().db).replaceLinks(project.id, targets);\n return c.json({ links: await linkViews(project) });\n });\n\n app.openapi(usageRoute, async (c) => {\n const { slug, buildId, snapshotId } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...VIEW_ROLES);\n const build = await buildForProject(project.id, buildId);\n const snapshot = await snapshotForBuild(build, snapshotId);\n return c.json(await linkedUsageFor(c, project, build.id, snapshot.storyImportPath));\n });\n}\n"],"mappings":"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"}