@storyshelf/core 0.3.2 → 0.5.4

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 (393) hide show
  1. package/README.md +38 -31
  2. package/dist/adapter/capture-queue.d.mts +29 -1
  3. package/dist/adapter/capture-runner.d.mts +18 -2
  4. package/dist/adapter/capture-runner.mjs +12 -1
  5. package/dist/adapter/capture-runner.mjs.map +1 -0
  6. package/dist/adapter/database.d.mts +67 -17
  7. package/dist/adapter/database.mjs +22 -55
  8. package/dist/adapter/database.mjs.map +1 -1
  9. package/dist/adapter/email-sender.d.mts +27 -0
  10. package/dist/adapter/email-sender.mjs +4 -0
  11. package/dist/adapter/metadata.d.mts +21 -15
  12. package/dist/adapter/notifier.d.mts +7 -0
  13. package/dist/adapter/notifier.mjs +6 -0
  14. package/dist/adapter/setup.d.mts +61 -0
  15. package/dist/adapter/setup.mjs +29 -0
  16. package/dist/adapter/setup.mjs.map +1 -0
  17. package/dist/adapter/webhook-events.d.mts +29 -0
  18. package/dist/adapter/webhook-events.mjs +83 -0
  19. package/dist/adapter/webhook-events.mjs.map +1 -0
  20. package/dist/adapters/notifier/events.d.mts +26 -0
  21. package/dist/adapters/notifier/events.mjs +66 -0
  22. package/dist/adapters/notifier/events.mjs.map +1 -0
  23. package/dist/adapters/notifier/format.d.mts +14 -0
  24. package/dist/adapters/notifier/format.mjs +96 -0
  25. package/dist/adapters/notifier/format.mjs.map +1 -0
  26. package/dist/adapters/notifier/provider.d.mts +32 -0
  27. package/dist/adapters/notifier/types.d.mts +56 -0
  28. package/dist/adapters/setup-runner.d.mts +23 -0
  29. package/dist/adapters/setup-runner.mjs +149 -0
  30. package/dist/adapters/setup-runner.mjs.map +1 -0
  31. package/dist/adapters/validate.d.mts +30 -0
  32. package/dist/adapters/validate.mjs +313 -0
  33. package/dist/adapters/validate.mjs.map +1 -0
  34. package/dist/auth.d.mts +94 -0
  35. package/dist/auth.mjs +4 -0
  36. package/dist/brand-packs.d.mts +16 -0
  37. package/dist/brand-packs.mjs +130 -0
  38. package/dist/brand-packs.mjs.map +1 -0
  39. package/dist/capture/adapter.d.mts +51 -1
  40. package/dist/capture/adapter.mjs.map +1 -1
  41. package/dist/capture/affected.d.mts +16 -0
  42. package/dist/capture/affected.mjs +64 -0
  43. package/dist/capture/affected.mjs.map +1 -0
  44. package/dist/capture/diff-record.mjs +30 -0
  45. package/dist/capture/diff-record.mjs.map +1 -0
  46. package/dist/capture/dispatch.d.mts +36 -0
  47. package/dist/capture/dispatch.mjs +88 -31
  48. package/dist/capture/dispatch.mjs.map +1 -1
  49. package/dist/capture/guards.mjs +31 -0
  50. package/dist/capture/guards.mjs.map +1 -0
  51. package/dist/capture/orchestrator.d.mts +41 -6
  52. package/dist/capture/orchestrator.mjs +151 -28
  53. package/dist/capture/orchestrator.mjs.map +1 -1
  54. package/dist/capture/pipeline.d.mts +30 -16
  55. package/dist/capture/pipeline.mjs +143 -39
  56. package/dist/capture/pipeline.mjs.map +1 -1
  57. package/dist/capture/recovery.d.mts +21 -0
  58. package/dist/capture/recovery.mjs +83 -0
  59. package/dist/capture/recovery.mjs.map +1 -0
  60. package/dist/capture/rediff.d.mts +35 -0
  61. package/dist/capture/rediff.mjs +77 -0
  62. package/dist/capture/rediff.mjs.map +1 -0
  63. package/dist/capture/sizing.d.mts +58 -0
  64. package/dist/capture/sizing.mjs +119 -0
  65. package/dist/capture/sizing.mjs.map +1 -0
  66. package/dist/capture/skip-checks.mjs +3 -3
  67. package/dist/capture/skip-checks.mjs.map +1 -1
  68. package/dist/capture/statics.d.mts +38 -0
  69. package/dist/capture/statics.mjs +38 -4
  70. package/dist/capture/statics.mjs.map +1 -1
  71. package/dist/capture/status-fanout.mjs +7 -2
  72. package/dist/capture/status-fanout.mjs.map +1 -1
  73. package/dist/capture/storybook.d.mts +31 -2
  74. package/dist/capture/storybook.mjs +77 -2
  75. package/dist/capture/storybook.mjs.map +1 -1
  76. package/dist/capture.d.mts +10 -4
  77. package/dist/capture.mjs +7 -2
  78. package/dist/config.d.mts +121 -4
  79. package/dist/config.mjs +55 -8
  80. package/dist/config.mjs.map +1 -1
  81. package/dist/index.d.mts +5 -56
  82. package/dist/index.mjs +4 -284
  83. package/dist/logger.d.mts +26 -17
  84. package/dist/logger.mjs +10 -5
  85. package/dist/logger.mjs.map +1 -1
  86. package/dist/models/baseline-status.d.mts +29 -0
  87. package/dist/models/baseline-status.mjs +26 -0
  88. package/dist/models/baseline-status.mjs.map +1 -0
  89. package/dist/models/baseline.d.mts +33 -0
  90. package/dist/models/baseline.mjs +35 -13
  91. package/dist/models/baseline.mjs.map +1 -1
  92. package/dist/models/build.d.mts +87 -0
  93. package/dist/models/build.mjs +83 -35
  94. package/dist/models/build.mjs.map +1 -1
  95. package/dist/models/capture-attempt.d.mts +40 -0
  96. package/dist/models/capture-attempt.mjs +85 -0
  97. package/dist/models/capture-attempt.mjs.map +1 -0
  98. package/dist/models/capture-log.d.mts +21 -0
  99. package/dist/models/capture-log.mjs +44 -0
  100. package/dist/models/capture-log.mjs.map +1 -0
  101. package/dist/models/comment.d.mts +29 -0
  102. package/dist/models/comment.mjs +11 -8
  103. package/dist/models/comment.mjs.map +1 -1
  104. package/dist/models/content-ref.d.mts +21 -0
  105. package/dist/models/content-ref.mjs +49 -0
  106. package/dist/models/content-ref.mjs.map +1 -0
  107. package/dist/models/credentials.d.mts +36 -0
  108. package/dist/models/credentials.mjs +103 -0
  109. package/dist/models/credentials.mjs.map +1 -0
  110. package/dist/models/label.d.mts +58 -0
  111. package/dist/models/label.mjs +32 -27
  112. package/dist/models/label.mjs.map +1 -1
  113. package/dist/models/member.d.mts +34 -0
  114. package/dist/models/member.mjs +20 -11
  115. package/dist/models/member.mjs.map +1 -1
  116. package/dist/models/notification-channel.d.mts +52 -0
  117. package/dist/models/notification-channel.mjs +93 -0
  118. package/dist/models/notification-channel.mjs.map +1 -0
  119. package/dist/models/notification-subscription.d.mts +44 -0
  120. package/dist/models/notification-subscription.mjs +89 -0
  121. package/dist/models/notification-subscription.mjs.map +1 -0
  122. package/dist/models/project-group-mapping.d.mts +30 -0
  123. package/dist/models/project-group-mapping.mjs +46 -0
  124. package/dist/models/project-group-mapping.mjs.map +1 -0
  125. package/dist/models/project.d.mts +54 -0
  126. package/dist/models/project.mjs +15 -11
  127. package/dist/models/project.mjs.map +1 -1
  128. package/dist/models/snapshot.d.mts +58 -0
  129. package/dist/models/snapshot.mjs +13 -7
  130. package/dist/models/snapshot.mjs.map +1 -1
  131. package/dist/models/status-config.d.mts +38 -0
  132. package/dist/models/status-config.mjs +12 -11
  133. package/dist/models/status-config.mjs.map +1 -1
  134. package/dist/models/token.d.mts +44 -0
  135. package/dist/models/token.mjs +18 -15
  136. package/dist/models/token.mjs.map +1 -1
  137. package/dist/models/user.d.mts +42 -0
  138. package/dist/models/user.mjs +65 -0
  139. package/dist/models/user.mjs.map +1 -0
  140. package/dist/models/webhook.d.mts +50 -0
  141. package/dist/models/webhook.mjs +12 -10
  142. package/dist/models/webhook.mjs.map +1 -1
  143. package/dist/models.d.mts +22 -0
  144. package/dist/models.mjs +23 -0
  145. package/dist/paths.mjs +29 -0
  146. package/dist/paths.mjs.map +1 -1
  147. package/dist/retention.d.mts +45 -0
  148. package/dist/retention.mjs +145 -0
  149. package/dist/retention.mjs.map +1 -0
  150. package/dist/schema/baseline.d.mts +1 -0
  151. package/dist/schema/build.d.mts +8 -0
  152. package/dist/schema/capture-attempt.d.mts +38 -0
  153. package/dist/schema/comment.d.mts +1 -1
  154. package/dist/schema/content-ref.d.mts +13 -0
  155. package/dist/schema/member.d.mts +2 -0
  156. package/dist/schema/notification.d.mts +35 -0
  157. package/dist/schema/project-group-mapping.d.mts +15 -0
  158. package/dist/schema/project.d.mts +4 -0
  159. package/dist/schema/snapshot.d.mts +7 -0
  160. package/dist/schema/token.d.mts +2 -0
  161. package/dist/schema/user.d.mts +11 -1
  162. package/dist/schema.d.mts +9 -46
  163. package/dist/schema.mjs +1 -55
  164. package/dist/test-helpers/create-project.d.mts +15 -0
  165. package/dist/test-helpers/create-project.mjs +28 -0
  166. package/dist/test-helpers/create-project.mjs.map +1 -0
  167. package/dist/test-helpers/database-contract.d.mts +13 -0
  168. package/dist/test-helpers/database-contract.mjs +58 -0
  169. package/dist/test-helpers/database-contract.mjs.map +1 -0
  170. package/dist/test-helpers/fake-adapters.d.mts +14 -0
  171. package/dist/test-helpers/fake-adapters.mjs +20 -0
  172. package/dist/test-helpers/fake-adapters.mjs.map +1 -0
  173. package/dist/test-helpers/fake-database.d.mts +11 -0
  174. package/dist/test-helpers/fake-database.mjs +104 -0
  175. package/dist/test-helpers/fake-database.mjs.map +1 -0
  176. package/dist/test-helpers/fake-storage.d.mts +14 -0
  177. package/dist/test-helpers/fake-storage.mjs +49 -0
  178. package/dist/test-helpers/fake-storage.mjs.map +1 -0
  179. package/dist/test-helpers/fake-tables.mjs +276 -0
  180. package/dist/test-helpers/fake-tables.mjs.map +1 -0
  181. package/dist/test-helpers/git-contract.d.mts +11 -0
  182. package/dist/test-helpers/git-contract.mjs +34 -0
  183. package/dist/test-helpers/git-contract.mjs.map +1 -0
  184. package/dist/test-helpers/notifier-contract.d.mts +11 -0
  185. package/dist/test-helpers/notifier-contract.mjs +34 -0
  186. package/dist/test-helpers/notifier-contract.mjs.map +1 -0
  187. package/dist/test-helpers/queue-contract.d.mts +11 -0
  188. package/dist/test-helpers/queue-contract.mjs +64 -0
  189. package/dist/test-helpers/queue-contract.mjs.map +1 -0
  190. package/dist/test-helpers/runner-contract.d.mts +11 -0
  191. package/dist/test-helpers/runner-contract.mjs +32 -0
  192. package/dist/test-helpers/runner-contract.mjs.map +1 -0
  193. package/dist/test-helpers/sql-chunks.mjs +138 -0
  194. package/dist/test-helpers/sql-chunks.mjs.map +1 -0
  195. package/dist/test-helpers/storage-contract.d.mts +11 -0
  196. package/dist/test-helpers/storage-contract.mjs +87 -0
  197. package/dist/test-helpers/storage-contract.mjs.map +1 -0
  198. package/dist/test-helpers.d.mts +13 -0
  199. package/dist/test-helpers.mjs +14 -0
  200. package/dist/tracing.mjs +63 -0
  201. package/dist/tracing.mjs.map +1 -0
  202. package/dist/types.d.mts +38 -5
  203. package/dist/types.mjs +18 -5
  204. package/dist/types.mjs.map +1 -1
  205. package/dist/urls.d.mts +12 -1
  206. package/dist/urls.mjs +22 -3
  207. package/dist/urls.mjs.map +1 -1
  208. package/dist/utils/encrypt.d.mts +48 -0
  209. package/dist/utils/encrypt.mjs +67 -13
  210. package/dist/utils/encrypt.mjs.map +1 -1
  211. package/dist/utils/hash.d.mts +17 -0
  212. package/dist/utils/hash.mjs +8 -2
  213. package/dist/utils/hash.mjs.map +1 -1
  214. package/dist/utils/http.d.mts +58 -0
  215. package/dist/utils/http.mjs +147 -0
  216. package/dist/utils/http.mjs.map +1 -0
  217. package/dist/utils/mime.d.mts +9 -0
  218. package/dist/utils/mime.mjs +33 -0
  219. package/dist/utils/mime.mjs.map +1 -0
  220. package/dist/utils/path-security.d.mts +12 -0
  221. package/dist/utils/path-security.mjs +24 -0
  222. package/dist/utils/path-security.mjs.map +1 -0
  223. package/dist/utils/redact.d.mts +17 -0
  224. package/dist/utils/redact.mjs +42 -0
  225. package/dist/utils/redact.mjs.map +1 -0
  226. package/dist/utils/timing.d.mts +19 -0
  227. package/dist/utils/timing.mjs +46 -0
  228. package/dist/utils/timing.mjs.map +1 -0
  229. package/dist/utils/ulid.d.mts +10 -0
  230. package/dist/utils/ulid.mjs +8 -8
  231. package/dist/utils/ulid.mjs.map +1 -1
  232. package/dist/utils.d.mts +12 -0
  233. package/dist/utils.mjs +13 -0
  234. package/package.json +69 -28
  235. package/dist/adapter/auth.d.mts +0 -50
  236. package/dist/adapter/auth.mjs +0 -10
  237. package/dist/adapter/auth.mjs.map +0 -1
  238. package/dist/adapter/init.d.mts +0 -58
  239. package/dist/adapter/init.mjs +0 -93
  240. package/dist/adapter/init.mjs.map +0 -1
  241. package/dist/adapters/webhook-events.mjs +0 -55
  242. package/dist/adapters/webhook-events.mjs.map +0 -1
  243. package/dist/ddl.d.mts +0 -8
  244. package/dist/ddl.mjs +0 -158
  245. package/dist/ddl.mjs.map +0 -1
  246. package/dist/index.mjs.map +0 -1
  247. package/dist/middleware/auth-gate.mjs +0 -36
  248. package/dist/middleware/auth-gate.mjs.map +0 -1
  249. package/dist/middleware/csrf.mjs +0 -40
  250. package/dist/middleware/csrf.mjs.map +0 -1
  251. package/dist/middleware/rate-limit.mjs +0 -39
  252. package/dist/middleware/rate-limit.mjs.map +0 -1
  253. package/dist/middleware/request-log.mjs +0 -28
  254. package/dist/middleware/request-log.mjs.map +0 -1
  255. package/dist/middleware/store-scope.mjs +0 -29
  256. package/dist/middleware/store-scope.mjs.map +0 -1
  257. package/dist/pages/build-detail.mjs +0 -339
  258. package/dist/pages/build-detail.mjs.map +0 -1
  259. package/dist/pages/build-diff-comments.mjs +0 -113
  260. package/dist/pages/build-diff-comments.mjs.map +0 -1
  261. package/dist/pages/build-diff-header.mjs +0 -163
  262. package/dist/pages/build-diff-header.mjs.map +0 -1
  263. package/dist/pages/build-diff-nav.mjs +0 -66
  264. package/dist/pages/build-diff-nav.mjs.map +0 -1
  265. package/dist/pages/build-diff-viewer.mjs +0 -197
  266. package/dist/pages/build-diff-viewer.mjs.map +0 -1
  267. package/dist/pages/build-diff.mjs +0 -90
  268. package/dist/pages/build-diff.mjs.map +0 -1
  269. package/dist/pages/compute-jobs.mjs +0 -198
  270. package/dist/pages/compute-jobs.mjs.map +0 -1
  271. package/dist/pages/label-detail.mjs +0 -228
  272. package/dist/pages/label-detail.mjs.map +0 -1
  273. package/dist/pages/login.mjs +0 -78
  274. package/dist/pages/login.mjs.map +0 -1
  275. package/dist/pages/project-builds.mjs +0 -231
  276. package/dist/pages/project-builds.mjs.map +0 -1
  277. package/dist/pages/project-create.mjs +0 -169
  278. package/dist/pages/project-create.mjs.map +0 -1
  279. package/dist/pages/project-settings.mjs +0 -102
  280. package/dist/pages/project-settings.mjs.map +0 -1
  281. package/dist/pages/projects.mjs +0 -184
  282. package/dist/pages/projects.mjs.map +0 -1
  283. package/dist/pages/root.mjs +0 -94
  284. package/dist/pages/root.mjs.map +0 -1
  285. package/dist/pages/settings-general.mjs +0 -192
  286. package/dist/pages/settings-general.mjs.map +0 -1
  287. package/dist/pages/settings-labels.mjs +0 -150
  288. package/dist/pages/settings-labels.mjs.map +0 -1
  289. package/dist/pages/settings-members.mjs +0 -125
  290. package/dist/pages/settings-members.mjs.map +0 -1
  291. package/dist/pages/settings-status.mjs +0 -152
  292. package/dist/pages/settings-status.mjs.map +0 -1
  293. package/dist/pages/settings-tests.mjs +0 -149
  294. package/dist/pages/settings-tests.mjs.map +0 -1
  295. package/dist/pages/settings-tokens.mjs +0 -96
  296. package/dist/pages/settings-tokens.mjs.map +0 -1
  297. package/dist/pages/settings-webhooks.mjs +0 -150
  298. package/dist/pages/settings-webhooks.mjs.map +0 -1
  299. package/dist/pages/storybook.mjs +0 -96
  300. package/dist/pages/storybook.mjs.map +0 -1
  301. package/dist/retention/purge.mjs +0 -70
  302. package/dist/retention/purge.mjs.map +0 -1
  303. package/dist/routers/admin.mjs +0 -42
  304. package/dist/routers/admin.mjs.map +0 -1
  305. package/dist/routers/assets.mjs +0 -25
  306. package/dist/routers/assets.mjs.map +0 -1
  307. package/dist/routers/auth.mjs +0 -78
  308. package/dist/routers/auth.mjs.map +0 -1
  309. package/dist/routers/builds.handlers.mjs +0 -184
  310. package/dist/routers/builds.handlers.mjs.map +0 -1
  311. package/dist/routers/builds.mjs +0 -185
  312. package/dist/routers/builds.mjs.map +0 -1
  313. package/dist/routers/comments.mjs +0 -90
  314. package/dist/routers/comments.mjs.map +0 -1
  315. package/dist/routers/health.mjs +0 -137
  316. package/dist/routers/health.mjs.map +0 -1
  317. package/dist/routers/helpers.mjs +0 -82
  318. package/dist/routers/helpers.mjs.map +0 -1
  319. package/dist/routers/htmx.mjs +0 -20
  320. package/dist/routers/htmx.mjs.map +0 -1
  321. package/dist/routers/labels.mjs +0 -110
  322. package/dist/routers/labels.mjs.map +0 -1
  323. package/dist/routers/media.mjs +0 -55
  324. package/dist/routers/media.mjs.map +0 -1
  325. package/dist/routers/members.mjs +0 -102
  326. package/dist/routers/members.mjs.map +0 -1
  327. package/dist/routers/projects.mjs +0 -122
  328. package/dist/routers/projects.mjs.map +0 -1
  329. package/dist/routers/schemas.mjs +0 -224
  330. package/dist/routers/schemas.mjs.map +0 -1
  331. package/dist/routers/settings-general.mjs +0 -95
  332. package/dist/routers/settings-general.mjs.map +0 -1
  333. package/dist/routers/settings-labels.mjs +0 -49
  334. package/dist/routers/settings-labels.mjs.map +0 -1
  335. package/dist/routers/settings-members.mjs +0 -30
  336. package/dist/routers/settings-members.mjs.map +0 -1
  337. package/dist/routers/settings-status.mjs +0 -73
  338. package/dist/routers/settings-status.mjs.map +0 -1
  339. package/dist/routers/settings-tokens.mjs +0 -40
  340. package/dist/routers/settings-tokens.mjs.map +0 -1
  341. package/dist/routers/settings-webhooks.mjs +0 -63
  342. package/dist/routers/settings-webhooks.mjs.map +0 -1
  343. package/dist/routers/settings.handlers.mjs +0 -93
  344. package/dist/routers/settings.handlers.mjs.map +0 -1
  345. package/dist/routers/settings.mjs +0 -23
  346. package/dist/routers/settings.mjs.map +0 -1
  347. package/dist/routers/snapshots.mjs +0 -144
  348. package/dist/routers/snapshots.mjs.map +0 -1
  349. package/dist/routers/status-configs.mjs +0 -101
  350. package/dist/routers/status-configs.mjs.map +0 -1
  351. package/dist/routers/storybook.mjs +0 -120
  352. package/dist/routers/storybook.mjs.map +0 -1
  353. package/dist/routers/tokens.mjs +0 -81
  354. package/dist/routers/tokens.mjs.map +0 -1
  355. package/dist/routers/ui.mjs +0 -165
  356. package/dist/routers/ui.mjs.map +0 -1
  357. package/dist/routers/webhooks.mjs +0 -90
  358. package/dist/routers/webhooks.mjs.map +0 -1
  359. package/dist/schema/baseline.mjs +0 -22
  360. package/dist/schema/baseline.mjs.map +0 -1
  361. package/dist/schema/build.mjs +0 -29
  362. package/dist/schema/build.mjs.map +0 -1
  363. package/dist/schema/comment.mjs +0 -26
  364. package/dist/schema/comment.mjs.map +0 -1
  365. package/dist/schema/label.mjs +0 -30
  366. package/dist/schema/label.mjs.map +0 -1
  367. package/dist/schema/member.mjs +0 -19
  368. package/dist/schema/member.mjs.map +0 -1
  369. package/dist/schema/project.mjs +0 -25
  370. package/dist/schema/project.mjs.map +0 -1
  371. package/dist/schema/snapshot.mjs +0 -34
  372. package/dist/schema/snapshot.mjs.map +0 -1
  373. package/dist/schema/status-config.mjs +0 -20
  374. package/dist/schema/status-config.mjs.map +0 -1
  375. package/dist/schema/token.mjs +0 -19
  376. package/dist/schema/token.mjs.map +0 -1
  377. package/dist/schema/user.mjs +0 -19
  378. package/dist/schema/user.mjs.map +0 -1
  379. package/dist/schema/webhook.mjs +0 -20
  380. package/dist/schema/webhook.mjs.map +0 -1
  381. package/dist/schema.mjs.map +0 -1
  382. package/dist/store.mjs +0 -20
  383. package/dist/store.mjs.map +0 -1
  384. package/dist/ui/document.mjs +0 -348
  385. package/dist/ui/document.mjs.map +0 -1
  386. package/dist/ui/feedback.mjs +0 -24
  387. package/dist/ui/feedback.mjs.map +0 -1
  388. package/dist/ui/forms.mjs +0 -117
  389. package/dist/ui/forms.mjs.map +0 -1
  390. package/dist/ui/styles.mjs +0 -174
  391. package/dist/ui/styles.mjs.map +0 -1
  392. package/dist/ui/theme.mjs +0 -44
  393. package/dist/ui/theme.mjs.map +0 -1
@@ -1,185 +0,0 @@
1
- import "node:path";
2
- import "node:url";
3
- import.meta.url;
4
- import { BuildModel } from "../models/build.mjs";
5
- import { storybookZipPath } from "../paths.mjs";
6
- import "../config.mjs";
7
- import { getStore } from "../store.mjs";
8
- import { resolveAuthorizedProject } from "./helpers.mjs";
9
- import { badRequest, buildSchema, forbidden, notFound, unauthorized } from "./schemas.mjs";
10
- import { APPROVER_ROLES, DEVELOPER_ROLES, VIEW_ROLES, buildCreateJsonSchema, buildForProject, buildListQuery, createBuildRecord, persistInlineStatics, storeUploadStream } from "./builds.handlers.mjs";
11
- import { registerComments } from "./comments.mjs";
12
- import { registerSnapshots } from "./snapshots.mjs";
13
- import { createRoute, z } from "@hono/zod-openapi";
14
- import { HTTPException } from "hono/http-exception";
15
- //#region src/routers/builds.ts
16
- const listBuildsRoute = createRoute({
17
- method: "get",
18
- path: "/api/v1/projects/{slug}/builds",
19
- request: {
20
- params: z.object({ slug: z.string() }),
21
- query: buildListQuery
22
- },
23
- responses: {
24
- 200: {
25
- content: { "application/json": { schema: buildSchema.array() } },
26
- description: "List builds for a project"
27
- },
28
- ...notFound,
29
- ...unauthorized
30
- }
31
- });
32
- const buildCreatedSchema = z.object({
33
- build: buildSchema,
34
- uploadUrl: z.string()
35
- }).openapi("BuildCreated");
36
- const createBuildRoute = createRoute({
37
- method: "post",
38
- path: "/api/v1/projects/{slug}/builds",
39
- request: {
40
- params: z.object({ slug: z.string() }),
41
- body: { content: { "application/json": { schema: buildCreateJsonSchema } } }
42
- },
43
- responses: {
44
- 202: {
45
- content: { "application/json": { schema: buildCreatedSchema } },
46
- description: "Build created. PUT the Storybook zip to uploadUrl to queue capture."
47
- },
48
- ...badRequest,
49
- ...forbidden,
50
- ...notFound
51
- }
52
- });
53
- const uploadZipRoute = createRoute({
54
- method: "put",
55
- path: "/api/v1/projects/{slug}/builds/{buildId}/zip",
56
- request: {
57
- params: z.object({
58
- slug: z.string(),
59
- buildId: z.string()
60
- }),
61
- body: { content: { "application/zip": { schema: z.string().openapi({
62
- type: "string",
63
- format: "binary"
64
- }) } } }
65
- },
66
- responses: {
67
- 202: {
68
- content: { "application/json": { schema: buildSchema } },
69
- description: "Zip stored and capture queued"
70
- },
71
- 413: {
72
- content: { "application/json": { schema: z.object({ message: z.string() }).openapi("Error") } },
73
- description: "Upload exceeds the configured size limit"
74
- },
75
- ...badRequest,
76
- ...forbidden,
77
- ...notFound
78
- }
79
- });
80
- const getBuildRoute = createRoute({
81
- method: "get",
82
- path: "/api/v1/projects/{slug}/builds/{buildId}",
83
- request: { params: z.object({
84
- slug: z.string(),
85
- buildId: z.string()
86
- }) },
87
- responses: {
88
- 200: {
89
- content: { "application/json": { schema: buildSchema } },
90
- description: "Fetch a build"
91
- },
92
- ...notFound,
93
- ...unauthorized
94
- }
95
- });
96
- const retryBuildRoute = createRoute({
97
- method: "post",
98
- path: "/api/v1/projects/{slug}/builds/{buildId}/retry",
99
- request: { params: z.object({
100
- slug: z.string(),
101
- buildId: z.string()
102
- }) },
103
- responses: {
104
- 202: {
105
- content: { "application/json": { schema: buildSchema } },
106
- description: "Build reset to pending"
107
- },
108
- ...notFound
109
- }
110
- });
111
- const deleteBuildRoute = createRoute({
112
- method: "delete",
113
- path: "/api/v1/projects/{slug}/builds/{buildId}",
114
- request: { params: z.object({
115
- slug: z.string(),
116
- buildId: z.string()
117
- }) },
118
- responses: {
119
- 204: { description: "Build deleted" },
120
- ...notFound
121
- }
122
- });
123
- /** Register the build list, upload, fetch, retry, and delete endpoints. */
124
- function registerBuilds(app) {
125
- app.openapi(listBuildsRoute, async (c) => {
126
- const project = await resolveAuthorizedProject(c, c.req.valid("param").slug, ...VIEW_ROLES);
127
- const { status, branch, labelKey, labelValue } = c.req.valid("query");
128
- const builds = new BuildModel(getStore().db).list(project.id, {
129
- status,
130
- branch: branch ?? void 0,
131
- labelKey: labelKey ?? void 0,
132
- labelValue: labelValue ?? void 0
133
- });
134
- return c.json(await builds);
135
- });
136
- app.openapi(createBuildRoute, async (c) => {
137
- const project = await resolveAuthorizedProject(c, c.req.valid("param").slug, ...DEVELOPER_ROLES);
138
- const parsed = buildCreateJsonSchema.safeParse(await c.req.json());
139
- if (!parsed.success) throw new HTTPException(400, { message: parsed.error.message });
140
- const build = await createBuildRecord(project, parsed.data);
141
- const uploadUrl = `/api/v1/projects/${project.slug}/builds/${build.id}/zip`;
142
- return c.json({
143
- build,
144
- uploadUrl
145
- }, 202);
146
- });
147
- app.openapi(uploadZipRoute, async (c) => {
148
- const { slug, buildId } = c.req.valid("param");
149
- const project = await resolveAuthorizedProject(c, slug, ...DEVELOPER_ROLES);
150
- const build = await buildForProject(project.id, buildId);
151
- const maxBytes = getStore().config.maxUploadBytes ?? 1073741824;
152
- await storeUploadStream(getStore().storage, storybookZipPath(project.id, build.id), c.req.raw.body, maxBytes);
153
- const reqId = c.get("requestId");
154
- await getStore().enqueueCapture?.(build.id, reqId);
155
- const { storage, config, logger } = getStore();
156
- await persistInlineStatics(storage, config.scratchDir, config.maxInlineUnzipSize, c.req.header("content-length"), project.id, build.id, logger);
157
- return c.json(build, 202);
158
- });
159
- app.openapi(getBuildRoute, async (c) => {
160
- const { slug, buildId } = c.req.valid("param");
161
- const project = await resolveAuthorizedProject(c, slug, ...VIEW_ROLES);
162
- const build = await buildForProject(project.id, buildId);
163
- return c.json(build);
164
- });
165
- app.openapi(retryBuildRoute, async (c) => {
166
- const { slug, buildId } = c.req.valid("param");
167
- const project = await resolveAuthorizedProject(c, slug, ...DEVELOPER_ROLES);
168
- const build = await buildForProject(project.id, buildId);
169
- const updated = await new BuildModel(getStore().db).setStatus(build.id, "pending");
170
- return c.json(updated, 202);
171
- });
172
- app.openapi(deleteBuildRoute, async (c) => {
173
- const { slug, buildId } = c.req.valid("param");
174
- const project = await resolveAuthorizedProject(c, slug, ...APPROVER_ROLES);
175
- const build = await buildForProject(project.id, buildId);
176
- await new BuildModel(getStore().db).remove(build.id);
177
- return c.body(null, 204);
178
- });
179
- registerSnapshots(app);
180
- registerComments(app);
181
- }
182
- //#endregion
183
- export { registerBuilds };
184
-
185
- //# sourceMappingURL=builds.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"builds.mjs","names":["notFoundResponse","forbiddenResponse"],"sources":["../../src/routers/builds.ts"],"sourcesContent":["import { createRoute, z } from \"@hono/zod-openapi\";\nimport { HTTPException } from \"hono/http-exception\";\nimport { DEFAULT_MAX_UPLOAD_BYTES } from \"../config.ts\";\nimport type { ShelfApp } from \"../index.tsx\";\nimport { BuildModel } from \"../models/build.ts\";\nimport { getStore } from \"../store.ts\";\nimport { storybookZipPath } from \"../utils/paths.ts\";\nimport {\n VIEW_ROLES,\n DEVELOPER_ROLES,\n APPROVER_ROLES,\n buildCreateJsonSchema,\n buildListQuery,\n buildForProject,\n createBuildRecord,\n persistInlineStatics,\n storeUploadStream,\n} from \"./builds.handlers.ts\";\nimport { registerComments } from \"./comments.ts\";\nimport { resolveAuthorizedProject } from \"./helpers.ts\";\nimport {\n badRequest,\n buildSchema,\n forbidden as forbiddenResponse,\n notFound as notFoundResponse,\n unauthorized,\n} from \"./schemas.ts\";\nimport { registerSnapshots } from \"./snapshots.ts\";\n\nconst listBuildsRoute = createRoute({\n method: \"get\",\n path: \"/api/v1/projects/{slug}/builds\",\n request: { params: z.object({ slug: z.string() }), query: buildListQuery },\n responses: {\n 200: {\n content: { \"application/json\": { schema: buildSchema.array() } },\n description: \"List builds for a project\",\n },\n ...notFoundResponse,\n ...unauthorized,\n },\n});\n\nconst buildCreatedSchema = z\n .object({ build: buildSchema, uploadUrl: z.string() })\n .openapi(\"BuildCreated\");\n\nconst createBuildRoute = createRoute({\n method: \"post\",\n path: \"/api/v1/projects/{slug}/builds\",\n request: {\n params: z.object({ slug: z.string() }),\n body: {\n content: {\n \"application/json\": { schema: buildCreateJsonSchema },\n },\n },\n },\n responses: {\n 202: {\n content: {\n \"application/json\": {\n schema: buildCreatedSchema,\n },\n },\n description: \"Build created. PUT the Storybook zip to uploadUrl to queue capture.\",\n },\n ...badRequest,\n ...forbiddenResponse,\n ...notFoundResponse,\n },\n});\n\nconst uploadZipRoute = createRoute({\n method: \"put\",\n path: \"/api/v1/projects/{slug}/builds/{buildId}/zip\",\n request: {\n params: z.object({ slug: z.string(), buildId: z.string() }),\n body: {\n content: {\n \"application/zip\": {\n schema: z.string().openapi({ type: \"string\", format: \"binary\" }),\n },\n },\n },\n },\n responses: {\n 202: {\n content: { \"application/json\": { schema: buildSchema } },\n description: \"Zip stored and capture queued\",\n },\n 413: {\n content: {\n \"application/json\": {\n schema: z.object({ message: z.string() }).openapi(\"Error\"),\n },\n },\n description: \"Upload exceeds the configured size limit\",\n },\n ...badRequest,\n ...forbiddenResponse,\n ...notFoundResponse,\n },\n});\n\nconst getBuildRoute = createRoute({\n method: \"get\",\n path: \"/api/v1/projects/{slug}/builds/{buildId}\",\n request: { params: z.object({ slug: z.string(), buildId: z.string() }) },\n responses: {\n 200: { content: { \"application/json\": { schema: buildSchema } }, description: \"Fetch a build\" },\n ...notFoundResponse,\n ...unauthorized,\n },\n});\n\nconst retryBuildRoute = createRoute({\n method: \"post\",\n path: \"/api/v1/projects/{slug}/builds/{buildId}/retry\",\n request: { params: z.object({ slug: z.string(), buildId: z.string() }) },\n responses: {\n 202: {\n content: { \"application/json\": { schema: buildSchema } },\n description: \"Build reset to pending\",\n },\n ...notFoundResponse,\n },\n});\n\nconst deleteBuildRoute = createRoute({\n method: \"delete\",\n path: \"/api/v1/projects/{slug}/builds/{buildId}\",\n request: { params: z.object({ slug: z.string(), buildId: z.string() }) },\n responses: {\n 204: { description: \"Build deleted\" },\n ...notFoundResponse,\n },\n});\n\n/** Register the build list, upload, fetch, retry, and delete endpoints. */\nexport function registerBuilds(app: ShelfApp): void {\n app.openapi(listBuildsRoute, async (c) => {\n const project = await resolveAuthorizedProject(c, c.req.valid(\"param\").slug, ...VIEW_ROLES);\n const { status, branch, labelKey, labelValue } = c.req.valid(\"query\");\n const builds = new BuildModel(getStore().db).list(project.id, {\n status,\n branch: branch ?? undefined,\n labelKey: labelKey ?? undefined,\n labelValue: labelValue ?? undefined,\n });\n return c.json(await builds);\n });\n\n app.openapi(createBuildRoute, async (c) => {\n const project = await resolveAuthorizedProject(\n c,\n c.req.valid(\"param\").slug,\n ...DEVELOPER_ROLES,\n );\n const parsed = buildCreateJsonSchema.safeParse(await c.req.json());\n if (!parsed.success) {\n throw new HTTPException(400, { message: parsed.error.message });\n }\n const build = await createBuildRecord(project, parsed.data);\n const uploadUrl = `/api/v1/projects/${project.slug}/builds/${build.id}/zip`;\n return c.json({ build, uploadUrl }, 202);\n });\n\n app.openapi(uploadZipRoute, async (c) => {\n const { slug, buildId } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...DEVELOPER_ROLES);\n const build = await buildForProject(project.id, buildId);\n const maxBytes = getStore().config.maxUploadBytes ?? DEFAULT_MAX_UPLOAD_BYTES;\n await storeUploadStream(\n getStore().storage,\n storybookZipPath(project.id, build.id),\n c.req.raw.body,\n maxBytes,\n );\n\n const reqId = c.get(\"requestId\");\n await getStore().enqueueCapture?.(build.id, reqId);\n const { storage, config, logger } = getStore();\n await persistInlineStatics(\n storage,\n config.scratchDir,\n config.maxInlineUnzipSize,\n c.req.header(\"content-length\"),\n project.id,\n build.id,\n logger,\n );\n return c.json(build, 202);\n });\n\n app.openapi(getBuildRoute, async (c) => {\n const { slug, buildId } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...VIEW_ROLES);\n const build = await buildForProject(project.id, buildId);\n return c.json(build);\n });\n\n app.openapi(retryBuildRoute, async (c) => {\n const { slug, buildId } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...DEVELOPER_ROLES);\n const build = await buildForProject(project.id, buildId);\n const updated = await new BuildModel(getStore().db).setStatus(build.id, \"pending\");\n return c.json(updated, 202);\n });\n\n app.openapi(deleteBuildRoute, async (c) => {\n const { slug, buildId } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...APPROVER_ROLES);\n const build = await buildForProject(project.id, buildId);\n await new BuildModel(getStore().db).remove(build.id);\n return c.body(null, 204);\n });\n\n registerSnapshots(app);\n registerComments(app);\n}\n"],"mappings":"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"}
@@ -1,90 +0,0 @@
1
- import "node:path";
2
- import "node:url";
3
- import.meta.url;
4
- import { getStore } from "../store.mjs";
5
- import { notFound, resolveAuthorizedProject } from "./helpers.mjs";
6
- import { commentCreateSchema, commentSchema, notFound as notFound$1, unauthorized } from "./schemas.mjs";
7
- import { DEVELOPER_ROLES, VIEW_ROLES, buildForProject } from "./builds.handlers.mjs";
8
- import { CommentModel } from "../models/comment.mjs";
9
- import { createRoute, z } from "@hono/zod-openapi";
10
- //#region src/routers/comments.ts
11
- const listCommentsRoute = createRoute({
12
- method: "get",
13
- path: "/api/v1/projects/{slug}/builds/{buildId}/comments",
14
- request: { params: z.object({
15
- slug: z.string(),
16
- buildId: z.string()
17
- }) },
18
- responses: {
19
- 200: {
20
- content: { "application/json": { schema: commentSchema.array() } },
21
- description: "List comments on a build"
22
- },
23
- ...notFound$1,
24
- ...unauthorized
25
- }
26
- });
27
- const createCommentRoute = createRoute({
28
- method: "post",
29
- path: "/api/v1/projects/{slug}/builds/{buildId}/comments",
30
- request: {
31
- params: z.object({
32
- slug: z.string(),
33
- buildId: z.string()
34
- }),
35
- body: { content: { "application/json": { schema: commentCreateSchema } } }
36
- },
37
- responses: {
38
- 201: {
39
- content: { "application/json": { schema: commentSchema } },
40
- description: "Comment created"
41
- },
42
- ...notFound$1
43
- }
44
- });
45
- const resolveCommentRoute = createRoute({
46
- method: "post",
47
- path: "/api/v1/projects/{slug}/builds/{buildId}/comments/{commentId}/resolve",
48
- request: { params: z.object({
49
- slug: z.string(),
50
- buildId: z.string(),
51
- commentId: z.string()
52
- }) },
53
- responses: {
54
- 200: {
55
- content: { "application/json": { schema: commentSchema } },
56
- description: "Comment resolved"
57
- },
58
- ...notFound$1
59
- }
60
- });
61
- /** Register the build comment list, create, and resolve endpoints. */
62
- function registerComments(app) {
63
- app.openapi(listCommentsRoute, async (c) => {
64
- const { slug, buildId } = c.req.valid("param");
65
- const project = await resolveAuthorizedProject(c, slug, ...VIEW_ROLES);
66
- const build = await buildForProject(project.id, buildId);
67
- return c.json(await new CommentModel(getStore().db).listByBuild(build.id));
68
- });
69
- app.openapi(createCommentRoute, async (c) => {
70
- const { slug, buildId } = c.req.valid("param");
71
- const project = await resolveAuthorizedProject(c, slug, ...DEVELOPER_ROLES);
72
- const build = await buildForProject(project.id, buildId);
73
- const body = c.req.valid("json");
74
- const userId = getStore().user?.id ?? "anonymous";
75
- const comment = await new CommentModel(getStore().db).create(project.id, build.id, userId, body);
76
- return c.json(comment, 201);
77
- });
78
- app.openapi(resolveCommentRoute, async (c) => {
79
- const { slug, buildId, commentId } = c.req.valid("param");
80
- const project = await resolveAuthorizedProject(c, slug, ...DEVELOPER_ROLES);
81
- const build = await buildForProject(project.id, buildId);
82
- const comment = await new CommentModel(getStore().db).resolve(commentId);
83
- if (comment.buildId !== build.id) notFound("Comment not found");
84
- return c.json(comment);
85
- });
86
- }
87
- //#endregion
88
- export { registerComments };
89
-
90
- //# sourceMappingURL=comments.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"comments.mjs","names":["notFound"],"sources":["../../src/routers/comments.ts"],"sourcesContent":["import { createRoute, z } from \"@hono/zod-openapi\";\nimport type { ShelfApp } from \"../index.tsx\";\nimport { CommentModel } from \"../models/comment.ts\";\nimport { getStore } from \"../store.ts\";\nimport { VIEW_ROLES, DEVELOPER_ROLES, buildForProject } from \"./builds.handlers.ts\";\nimport { resolveAuthorizedProject, notFound as throwNotFound } from \"./helpers.ts\";\nimport { commentSchema, commentCreateSchema, notFound, unauthorized } from \"./schemas.ts\";\n\nconst listCommentsRoute = createRoute({\n method: \"get\",\n path: \"/api/v1/projects/{slug}/builds/{buildId}/comments\",\n request: { params: z.object({ slug: z.string(), buildId: z.string() }) },\n responses: {\n 200: {\n content: { \"application/json\": { schema: commentSchema.array() } },\n description: \"List comments on a build\",\n },\n ...notFound,\n ...unauthorized,\n },\n});\n\nconst createCommentRoute = createRoute({\n method: \"post\",\n path: \"/api/v1/projects/{slug}/builds/{buildId}/comments\",\n request: {\n params: z.object({ slug: z.string(), buildId: z.string() }),\n body: { content: { \"application/json\": { schema: commentCreateSchema } } },\n },\n responses: {\n 201: {\n content: { \"application/json\": { schema: commentSchema } },\n description: \"Comment created\",\n },\n ...notFound,\n },\n});\n\nconst resolveCommentRoute = createRoute({\n method: \"post\",\n path: \"/api/v1/projects/{slug}/builds/{buildId}/comments/{commentId}/resolve\",\n request: { params: z.object({ slug: z.string(), buildId: z.string(), commentId: z.string() }) },\n responses: {\n 200: {\n content: { \"application/json\": { schema: commentSchema } },\n description: \"Comment resolved\",\n },\n ...notFound,\n },\n});\n\n/** Register the build comment list, create, and resolve endpoints. */\nexport function registerComments(app: ShelfApp): void {\n app.openapi(listCommentsRoute, async (c) => {\n const { slug, buildId } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...VIEW_ROLES);\n const build = await buildForProject(project.id, buildId);\n return c.json(await new CommentModel(getStore().db).listByBuild(build.id));\n });\n\n app.openapi(createCommentRoute, async (c) => {\n const { slug, buildId } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...DEVELOPER_ROLES);\n const build = await buildForProject(project.id, buildId);\n const body = c.req.valid(\"json\");\n const userId = getStore().user?.id ?? \"anonymous\";\n const comment = await new CommentModel(getStore().db).create(\n project.id,\n build.id,\n userId,\n body,\n );\n return c.json(comment, 201);\n });\n\n app.openapi(resolveCommentRoute, async (c) => {\n const { slug, buildId, commentId } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...DEVELOPER_ROLES);\n const build = await buildForProject(project.id, buildId);\n const comment = await new CommentModel(getStore().db).resolve(commentId);\n if (comment.buildId !== build.id) {\n throwNotFound(\"Comment not found\");\n }\n return c.json(comment);\n });\n}\n"],"mappings":";;;;;;;;;;AAQA,MAAM,oBAAoB,YAAY;CACpC,QAAQ;CACR,MAAM;CACN,SAAS,EAAE,QAAQ,EAAE,OAAO;EAAE,MAAM,EAAE,OAAO;EAAG,SAAS,EAAE,OAAO;CAAE,CAAC,EAAE;CACvE,WAAW;EACT,KAAK;GACH,SAAS,EAAE,oBAAoB,EAAE,QAAQ,cAAc,MAAM,EAAE,EAAE;GACjE,aAAa;EACf;EACA,GAAGA;EACH,GAAG;CACL;AACF,CAAC;AAED,MAAM,qBAAqB,YAAY;CACrC,QAAQ;CACR,MAAM;CACN,SAAS;EACP,QAAQ,EAAE,OAAO;GAAE,MAAM,EAAE,OAAO;GAAG,SAAS,EAAE,OAAO;EAAE,CAAC;EAC1D,MAAM,EAAE,SAAS,EAAE,oBAAoB,EAAE,QAAQ,oBAAoB,EAAE,EAAE;CAC3E;CACA,WAAW;EACT,KAAK;GACH,SAAS,EAAE,oBAAoB,EAAE,QAAQ,cAAc,EAAE;GACzD,aAAa;EACf;EACA,GAAGA;CACL;AACF,CAAC;AAED,MAAM,sBAAsB,YAAY;CACtC,QAAQ;CACR,MAAM;CACN,SAAS,EAAE,QAAQ,EAAE,OAAO;EAAE,MAAM,EAAE,OAAO;EAAG,SAAS,EAAE,OAAO;EAAG,WAAW,EAAE,OAAO;CAAE,CAAC,EAAE;CAC9F,WAAW;EACT,KAAK;GACH,SAAS,EAAE,oBAAoB,EAAE,QAAQ,cAAc,EAAE;GACzD,aAAa;EACf;EACA,GAAGA;CACL;AACF,CAAC;;AAGD,SAAgB,iBAAiB,KAAqB;CACpD,IAAI,QAAQ,mBAAmB,OAAO,MAAM;EAC1C,MAAM,EAAE,MAAM,YAAY,EAAE,IAAI,MAAM,OAAO;EAC7C,MAAM,UAAU,MAAM,yBAAyB,GAAG,MAAM,GAAG,UAAU;EACrE,MAAM,QAAQ,MAAM,gBAAgB,QAAQ,IAAI,OAAO;EACvD,OAAO,EAAE,KAAK,MAAM,IAAI,aAAa,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,YAAY,MAAM,EAAE,CAAC;CAC3E,CAAC;CAED,IAAI,QAAQ,oBAAoB,OAAO,MAAM;EAC3C,MAAM,EAAE,MAAM,YAAY,EAAE,IAAI,MAAM,OAAO;EAC7C,MAAM,UAAU,MAAM,yBAAyB,GAAG,MAAM,GAAG,eAAe;EAC1E,MAAM,QAAQ,MAAM,gBAAgB,QAAQ,IAAI,OAAO;EACvD,MAAM,OAAO,EAAE,IAAI,MAAM,MAAM;EAC/B,MAAM,SAAS,SAAS,CAAC,CAAC,MAAM,MAAM;EACtC,MAAM,UAAU,MAAM,IAAI,aAAa,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,OACpD,QAAQ,IACR,MAAM,IACN,QACA,IACF;EACA,OAAO,EAAE,KAAK,SAAS,GAAG;CAC5B,CAAC;CAED,IAAI,QAAQ,qBAAqB,OAAO,MAAM;EAC5C,MAAM,EAAE,MAAM,SAAS,cAAc,EAAE,IAAI,MAAM,OAAO;EACxD,MAAM,UAAU,MAAM,yBAAyB,GAAG,MAAM,GAAG,eAAe;EAC1E,MAAM,QAAQ,MAAM,gBAAgB,QAAQ,IAAI,OAAO;EACvD,MAAM,UAAU,MAAM,IAAI,aAAa,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,QAAQ,SAAS;EACvE,IAAI,QAAQ,YAAY,MAAM,IAC5B,SAAc,mBAAmB;EAEnC,OAAO,EAAE,KAAK,OAAO;CACvB,CAAC;AACH"}
@@ -1,137 +0,0 @@
1
- import "node:path";
2
- import "node:url";
3
- import.meta.url;
4
- import { requireSiteAdmin } from "./helpers.mjs";
5
- //#region src/routers/health.ts
6
- const PROBE_TIMEOUT_MS = 2e3;
7
- const DETAIL_MAX_LENGTH = 500;
8
- function pushTarget(targets, adapter) {
9
- if (adapter) targets.push(adapter);
10
- }
11
- function targetsOf(sources) {
12
- const targets = [];
13
- pushTarget(targets, sources.database);
14
- pushTarget(targets, sources.storage);
15
- pushTarget(targets, sources.captureRunner);
16
- pushTarget(targets, sources.captureQueue);
17
- pushTarget(targets, sources.auth);
18
- for (const provider of sources.gitHosts ?? []) pushTarget(targets, provider);
19
- return targets;
20
- }
21
- function sanitize(detail) {
22
- return detail.length > DETAIL_MAX_LENGTH ? detail.slice(0, DETAIL_MAX_LENGTH) : detail;
23
- }
24
- function messageOf(reason) {
25
- return sanitize(reason instanceof Error ? reason.message : String(reason));
26
- }
27
- async function timeoutError() {
28
- await new Promise((resolve) => {
29
- setTimeout(resolve, PROBE_TIMEOUT_MS);
30
- });
31
- throw new Error("health probe timed out");
32
- }
33
- function okReport(target, latencyMs, detail) {
34
- const { category, kind, name } = target.metadata;
35
- return {
36
- category,
37
- kind,
38
- name,
39
- state: "ok",
40
- latencyMs,
41
- ...detail === void 0 ? {} : { detail }
42
- };
43
- }
44
- function failedReport(target, latencyMs, detail) {
45
- const { category, kind, name } = target.metadata;
46
- return {
47
- category,
48
- kind,
49
- name,
50
- state: "failed",
51
- latencyMs,
52
- detail
53
- };
54
- }
55
- function detailOf(result, fallback) {
56
- return result.detail === void 0 ? fallback : sanitize(result.detail);
57
- }
58
- async function raceProbe(target, probe, started) {
59
- try {
60
- const result = await Promise.race([probe(), timeoutError()]);
61
- const latencyMs = Date.now() - started;
62
- if (!result.ok) return failedReport(target, latencyMs, detailOf(result, "probe reported failure"));
63
- return okReport(target, latencyMs, result.detail === void 0 ? void 0 : sanitize(result.detail));
64
- } catch (error) {
65
- return failedReport(target, Date.now() - started, messageOf(error));
66
- }
67
- }
68
- async function probeHealth(target) {
69
- const probe = target.lifecycle?.health;
70
- if (!probe) {
71
- const { category, kind, name } = target.metadata;
72
- return {
73
- category,
74
- kind,
75
- name,
76
- state: "ok"
77
- };
78
- }
79
- return await raceProbe(target, probe, Date.now());
80
- }
81
- async function probeTarget(target, initErrors, starting) {
82
- const { category, kind, name } = target.metadata;
83
- const initError = initErrors.get(`${category}/${kind}`);
84
- if (initError !== void 0) return {
85
- category,
86
- kind,
87
- name,
88
- state: "failed",
89
- detail: sanitize(initError)
90
- };
91
- if (starting) return {
92
- category,
93
- kind,
94
- name,
95
- state: "starting"
96
- };
97
- return await probeHealth(target);
98
- }
99
- function collectInitErrors(settled) {
100
- const initErrors = /* @__PURE__ */ new Map();
101
- if (settled) for (const failure of settled.failures) initErrors.set(`${failure.category}/${failure.kind}`, failure.error);
102
- return initErrors;
103
- }
104
- function overallStatus(reports) {
105
- if (reports.some((report) => report.state === "failed")) return "degraded";
106
- if (reports.some((report) => report.state === "starting")) return "starting";
107
- return "ok";
108
- }
109
- /** Register `GET /api/v1/health` (open liveness) and `POST` (authed deep readiness). */
110
- function registerHealth(app, deps) {
111
- app.get("/api/v1/health", (c) => {
112
- const body = {
113
- status: "ok",
114
- uptimeSecs: Math.floor((Date.now() - deps.bootTimeMs) / 1e3),
115
- version: deps.version
116
- };
117
- c.header("Cache-Control", "no-store");
118
- return c.json(body);
119
- });
120
- app.post("/api/v1/health", async (c) => {
121
- requireSiteAdmin();
122
- const settled = deps.getSettled();
123
- const initErrors = collectInitErrors(settled);
124
- const targets = targetsOf(deps.sources);
125
- const reports = await Promise.all(targets.map(async (target) => await probeTarget(target, initErrors, settled === null)));
126
- const body = {
127
- status: overallStatus(reports),
128
- adapters: reports
129
- };
130
- c.header("Cache-Control", "no-store");
131
- return c.json(body, body.status === "ok" ? 200 : 503);
132
- });
133
- }
134
- //#endregion
135
- export { registerHealth };
136
-
137
- //# sourceMappingURL=health.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"health.mjs","names":[],"sources":["../../src/routers/health.ts"],"sourcesContent":["/**\n * Two-tier health: lightweight liveness (GET, no auth) and deep readiness\n * (POST, site-admin). Neither is gated by adapter init — liveness must\n * answer mid-migration so orchestrators don't kill the machine, and deep\n * reports the init settlement state instead of being masked by the gate.\n */\nimport type { AdapterInitResult, AdapterInitSources } from \"../adapters/init.ts\";\nimport type { AdapterHealth, AdapterLifecycle, AdapterMetadata } from \"../adapters/metadata.ts\";\nimport type { ShelfApp } from \"../index.tsx\";\nimport { requireSiteAdmin } from \"./helpers.ts\";\n\n/** Dependencies for the health routes (no adapter I/O on the GET path). */\nexport interface HealthDeps {\n sources: AdapterInitSources;\n getSettled: () => AdapterInitResult | null;\n bootTimeMs: number;\n version: string;\n}\n\n/** Liveness body: process-level only, safe for unauthenticated probes. */\nexport interface LivenessBody {\n status: \"ok\";\n uptimeSecs: number;\n version: string;\n}\n\ntype AdapterState = \"ok\" | \"starting\" | \"failed\";\n\n/** Per-adapter readiness entry (error text sanitized — no secrets). */\nexport interface AdapterReport {\n category: string;\n kind: string;\n name: string;\n state: AdapterState;\n latencyMs?: number;\n detail?: string;\n}\n\n/** Deep readiness body. */\nexport interface ReadinessBody {\n status: \"ok\" | \"starting\" | \"degraded\";\n adapters: AdapterReport[];\n}\n\ninterface ProbeTarget {\n readonly metadata: AdapterMetadata;\n readonly lifecycle?: AdapterLifecycle;\n}\n\nconst PROBE_TIMEOUT_MS = 2000;\nconst DETAIL_MAX_LENGTH = 500;\n\nfunction pushTarget(targets: ProbeTarget[], adapter: ProbeTarget | undefined): void {\n if (adapter) {\n targets.push(adapter);\n }\n}\n\nfunction targetsOf(sources: AdapterInitSources): ProbeTarget[] {\n const targets: ProbeTarget[] = [];\n pushTarget(targets, sources.database);\n pushTarget(targets, sources.storage);\n pushTarget(targets, sources.captureRunner);\n pushTarget(targets, sources.captureQueue);\n pushTarget(targets, sources.auth);\n for (const provider of sources.gitHosts ?? []) {\n pushTarget(targets, provider);\n }\n return targets;\n}\n\nfunction sanitize(detail: string): string {\n return detail.length > DETAIL_MAX_LENGTH ? detail.slice(0, DETAIL_MAX_LENGTH) : detail;\n}\n\nfunction messageOf(reason: unknown): string {\n const message = reason instanceof Error ? reason.message : String(reason);\n return sanitize(message);\n}\n\nasync function timeoutError(): Promise<never> {\n await new Promise<void>((resolve) => {\n setTimeout(resolve, PROBE_TIMEOUT_MS);\n });\n throw new Error(\"health probe timed out\");\n}\n\nfunction okReport(\n target: ProbeTarget,\n latencyMs: number,\n detail: string | undefined,\n): AdapterReport {\n const { category, kind, name } = target.metadata;\n return {\n category,\n kind,\n name,\n state: \"ok\",\n latencyMs,\n ...(detail === undefined ? {} : { detail }),\n };\n}\n\nfunction failedReport(target: ProbeTarget, latencyMs: number, detail: string): AdapterReport {\n const { category, kind, name } = target.metadata;\n return { category, kind, name, state: \"failed\", latencyMs, detail };\n}\n\nfunction detailOf(result: AdapterHealth, fallback: string): string {\n return result.detail === undefined ? fallback : sanitize(result.detail);\n}\n\nasync function raceProbe(\n target: ProbeTarget,\n probe: () => Promise<AdapterHealth>,\n started: number,\n): Promise<AdapterReport> {\n try {\n const result = await Promise.race([probe(), timeoutError()]);\n const latencyMs = Date.now() - started;\n if (!result.ok) {\n return failedReport(target, latencyMs, detailOf(result, \"probe reported failure\"));\n }\n return okReport(\n target,\n latencyMs,\n result.detail === undefined ? undefined : sanitize(result.detail),\n );\n } catch (error) {\n return failedReport(target, Date.now() - started, messageOf(error));\n }\n}\n\nasync function probeHealth(target: ProbeTarget): Promise<AdapterReport> {\n const probe = target.lifecycle?.health;\n if (!probe) {\n const { category, kind, name } = target.metadata;\n return { category, kind, name, state: \"ok\" };\n }\n return await raceProbe(target, probe, Date.now());\n}\n\nasync function probeTarget(\n target: ProbeTarget,\n initErrors: ReadonlyMap<string, string>,\n starting: boolean,\n): Promise<AdapterReport> {\n const { category, kind, name } = target.metadata;\n const initError = initErrors.get(`${category}/${kind}`);\n if (initError !== undefined) {\n return { category, kind, name, state: \"failed\", detail: sanitize(initError) };\n }\n if (starting) {\n return { category, kind, name, state: \"starting\" };\n }\n return await probeHealth(target);\n}\n\nfunction collectInitErrors(settled: AdapterInitResult | null): ReadonlyMap<string, string> {\n const initErrors = new Map<string, string>();\n if (settled) {\n for (const failure of settled.failures) {\n initErrors.set(`${failure.category}/${failure.kind}`, failure.error);\n }\n }\n return initErrors;\n}\n\nfunction overallStatus(reports: AdapterReport[]): ReadinessBody[\"status\"] {\n if (reports.some((report) => report.state === \"failed\")) {\n return \"degraded\";\n }\n if (reports.some((report) => report.state === \"starting\")) {\n return \"starting\";\n }\n return \"ok\";\n}\n\n/** Register `GET /api/v1/health` (open liveness) and `POST` (authed deep readiness). */\nexport function registerHealth(app: ShelfApp, deps: HealthDeps): void {\n app.get(\"/api/v1/health\", (c) => {\n const body: LivenessBody = {\n status: \"ok\",\n uptimeSecs: Math.floor((Date.now() - deps.bootTimeMs) / 1000),\n version: deps.version,\n };\n c.header(\"Cache-Control\", \"no-store\");\n return c.json(body);\n });\n\n app.post(\"/api/v1/health\", async (c) => {\n requireSiteAdmin();\n const settled = deps.getSettled();\n const initErrors = collectInitErrors(settled);\n const targets = targetsOf(deps.sources);\n const reports = await Promise.all(\n targets.map(async (target) => await probeTarget(target, initErrors, settled === null)),\n );\n const body: ReadinessBody = { status: overallStatus(reports), adapters: reports };\n c.header(\"Cache-Control\", \"no-store\");\n return c.json(body, body.status === \"ok\" ? 200 : 503);\n });\n}\n"],"mappings":";;;;;AAiDA,MAAM,mBAAmB;AACzB,MAAM,oBAAoB;AAE1B,SAAS,WAAW,SAAwB,SAAwC;CAClF,IAAI,SACF,QAAQ,KAAK,OAAO;AAExB;AAEA,SAAS,UAAU,SAA4C;CAC7D,MAAM,UAAyB,CAAC;CAChC,WAAW,SAAS,QAAQ,QAAQ;CACpC,WAAW,SAAS,QAAQ,OAAO;CACnC,WAAW,SAAS,QAAQ,aAAa;CACzC,WAAW,SAAS,QAAQ,YAAY;CACxC,WAAW,SAAS,QAAQ,IAAI;CAChC,KAAK,MAAM,YAAY,QAAQ,YAAY,CAAC,GAC1C,WAAW,SAAS,QAAQ;CAE9B,OAAO;AACT;AAEA,SAAS,SAAS,QAAwB;CACxC,OAAO,OAAO,SAAS,oBAAoB,OAAO,MAAM,GAAG,iBAAiB,IAAI;AAClF;AAEA,SAAS,UAAU,QAAyB;CAE1C,OAAO,SADS,kBAAkB,QAAQ,OAAO,UAAU,OAAO,MAAM,CACjD;AACzB;AAEA,eAAe,eAA+B;CAC5C,MAAM,IAAI,SAAe,YAAY;EACnC,WAAW,SAAS,gBAAgB;CACtC,CAAC;CACD,MAAM,IAAI,MAAM,wBAAwB;AAC1C;AAEA,SAAS,SACP,QACA,WACA,QACe;CACf,MAAM,EAAE,UAAU,MAAM,SAAS,OAAO;CACxC,OAAO;EACL;EACA;EACA;EACA,OAAO;EACP;EACA,GAAI,WAAW,KAAA,IAAY,CAAC,IAAI,EAAE,OAAO;CAC3C;AACF;AAEA,SAAS,aAAa,QAAqB,WAAmB,QAA+B;CAC3F,MAAM,EAAE,UAAU,MAAM,SAAS,OAAO;CACxC,OAAO;EAAE;EAAU;EAAM;EAAM,OAAO;EAAU;EAAW;CAAO;AACpE;AAEA,SAAS,SAAS,QAAuB,UAA0B;CACjE,OAAO,OAAO,WAAW,KAAA,IAAY,WAAW,SAAS,OAAO,MAAM;AACxE;AAEA,eAAe,UACb,QACA,OACA,SACwB;CACxB,IAAI;EACF,MAAM,SAAS,MAAM,QAAQ,KAAK,CAAC,MAAM,GAAG,aAAa,CAAC,CAAC;EAC3D,MAAM,YAAY,KAAK,IAAI,IAAI;EAC/B,IAAI,CAAC,OAAO,IACV,OAAO,aAAa,QAAQ,WAAW,SAAS,QAAQ,wBAAwB,CAAC;EAEnF,OAAO,SACL,QACA,WACA,OAAO,WAAW,KAAA,IAAY,KAAA,IAAY,SAAS,OAAO,MAAM,CAClE;CACF,SAAS,OAAO;EACd,OAAO,aAAa,QAAQ,KAAK,IAAI,IAAI,SAAS,UAAU,KAAK,CAAC;CACpE;AACF;AAEA,eAAe,YAAY,QAA6C;CACtE,MAAM,QAAQ,OAAO,WAAW;CAChC,IAAI,CAAC,OAAO;EACV,MAAM,EAAE,UAAU,MAAM,SAAS,OAAO;EACxC,OAAO;GAAE;GAAU;GAAM;GAAM,OAAO;EAAK;CAC7C;CACA,OAAO,MAAM,UAAU,QAAQ,OAAO,KAAK,IAAI,CAAC;AAClD;AAEA,eAAe,YACb,QACA,YACA,UACwB;CACxB,MAAM,EAAE,UAAU,MAAM,SAAS,OAAO;CACxC,MAAM,YAAY,WAAW,IAAI,GAAG,SAAS,GAAG,MAAM;CACtD,IAAI,cAAc,KAAA,GAChB,OAAO;EAAE;EAAU;EAAM;EAAM,OAAO;EAAU,QAAQ,SAAS,SAAS;CAAE;CAE9E,IAAI,UACF,OAAO;EAAE;EAAU;EAAM;EAAM,OAAO;CAAW;CAEnD,OAAO,MAAM,YAAY,MAAM;AACjC;AAEA,SAAS,kBAAkB,SAAgE;CACzF,MAAM,6BAAa,IAAI,IAAoB;CAC3C,IAAI,SACF,KAAK,MAAM,WAAW,QAAQ,UAC5B,WAAW,IAAI,GAAG,QAAQ,SAAS,GAAG,QAAQ,QAAQ,QAAQ,KAAK;CAGvE,OAAO;AACT;AAEA,SAAS,cAAc,SAAmD;CACxE,IAAI,QAAQ,MAAM,WAAW,OAAO,UAAU,QAAQ,GACpD,OAAO;CAET,IAAI,QAAQ,MAAM,WAAW,OAAO,UAAU,UAAU,GACtD,OAAO;CAET,OAAO;AACT;;AAGA,SAAgB,eAAe,KAAe,MAAwB;CACpE,IAAI,IAAI,mBAAmB,MAAM;EAC/B,MAAM,OAAqB;GACzB,QAAQ;GACR,YAAY,KAAK,OAAO,KAAK,IAAI,IAAI,KAAK,cAAc,GAAI;GAC5D,SAAS,KAAK;EAChB;EACA,EAAE,OAAO,iBAAiB,UAAU;EACpC,OAAO,EAAE,KAAK,IAAI;CACpB,CAAC;CAED,IAAI,KAAK,kBAAkB,OAAO,MAAM;EACtC,iBAAiB;EACjB,MAAM,UAAU,KAAK,WAAW;EAChC,MAAM,aAAa,kBAAkB,OAAO;EAC5C,MAAM,UAAU,UAAU,KAAK,OAAO;EACtC,MAAM,UAAU,MAAM,QAAQ,IAC5B,QAAQ,IAAI,OAAO,WAAW,MAAM,YAAY,QAAQ,YAAY,YAAY,IAAI,CAAC,CACvF;EACA,MAAM,OAAsB;GAAE,QAAQ,cAAc,OAAO;GAAG,UAAU;EAAQ;EAChF,EAAE,OAAO,iBAAiB,UAAU;EACpC,OAAO,EAAE,KAAK,MAAM,KAAK,WAAW,OAAO,MAAM,GAAG;CACtD,CAAC;AACH"}
@@ -1,82 +0,0 @@
1
- import "node:path";
2
- import "node:url";
3
- import.meta.url;
4
- import { projects } from "../schema/project.mjs";
5
- import { ProjectModel } from "../models/project.mjs";
6
- import { sha256 } from "../utils/hash.mjs";
7
- import { getStore } from "../store.mjs";
8
- import { MemberModel } from "../models/member.mjs";
9
- import { TokenModel } from "../models/token.mjs";
10
- import { eq } from "drizzle-orm";
11
- import { HTTPException } from "hono/http-exception";
12
- //#region src/routers/helpers.ts
13
- /** Throw a 401 Unauthorized error for unauthenticated requests. */
14
- function unauthorized() {
15
- throw new HTTPException(401, { message: "Unauthorized" });
16
- }
17
- /** Throw a 403 Forbidden error for requests lacking the required role. */
18
- function forbidden() {
19
- throw new HTTPException(403, { message: "Forbidden" });
20
- }
21
- /** Throw a 404 Not Found error with a custom message. */
22
- function notFound(message = "Not found") {
23
- throw new HTTPException(404, { message });
24
- }
25
- /** Resolve the current session user's effective role on a project. */
26
- async function currentProjectRole(projectId) {
27
- const { db, user } = getStore();
28
- if (!user) return null;
29
- return await new MemberModel(db).effectiveRole(user.role, projectId, user.id);
30
- }
31
- /** Look up a project by its URL slug, returning null when absent. */
32
- async function findProjectBySlug(slug) {
33
- const { db } = getStore();
34
- return (await db.list(projects, {
35
- where: eq(projects.slug, slug),
36
- limit: 1
37
- }))[0] ?? null;
38
- }
39
- async function resolveProjectByToken(c, slug) {
40
- const authHeader = c.req.header("authorization");
41
- if (authHeader?.startsWith("Bearer ")) {
42
- const token = authHeader.slice(7);
43
- const found = await new TokenModel(getStore().db).findByHash(sha256(token));
44
- if (!found) unauthorized();
45
- const project = await new ProjectModel(getStore().db).get(found.projectId);
46
- if (!project || project.slug !== slug) forbidden();
47
- return project;
48
- }
49
- return null;
50
- }
51
- /**
52
- * Enforce the caller's project role for a given project.
53
- *
54
- * Authorization model (ADR 0008):
55
- * - No auth adapter configured -> all operations permitted (development mode).
56
- * - Bearer token (CLI) -> grants access to its own project regardless of role.
57
- * - Session user -> must have an effective role in `minRoles`.
58
- */
59
- async function assertRole(projectId, ...minRoles) {
60
- if (!getStore().authEnabled) return;
61
- const role = await currentProjectRole(projectId);
62
- if (!role || !minRoles.includes(role)) forbidden();
63
- }
64
- /** Resolve a project by slug and enforce the caller's minimum role. */
65
- async function resolveAuthorizedProject(c, slug, ...minRoles) {
66
- const project = await resolveProjectByToken(c, slug);
67
- if (project) return project;
68
- const found = await findProjectBySlug(slug);
69
- if (!found) notFound("Project not found");
70
- await assertRole(found.id, ...minRoles);
71
- return found;
72
- }
73
- /** Require a site-level admin (or permit when auth is disabled). */
74
- function requireSiteAdmin() {
75
- if (!getStore().authEnabled) return;
76
- const { user } = getStore();
77
- if (user?.role !== "admin") forbidden();
78
- }
79
- //#endregion
80
- export { assertRole, currentProjectRole, findProjectBySlug, forbidden, notFound, requireSiteAdmin, resolveAuthorizedProject, unauthorized };
81
-
82
- //# sourceMappingURL=helpers.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"helpers.mjs","names":[],"sources":["../../src/routers/helpers.ts"],"sourcesContent":["import { eq } from \"drizzle-orm\";\nimport type { Context, Next } from \"hono\";\nimport { HTTPException } from \"hono/http-exception\";\nimport type { ContentfulStatusCode } from \"hono/utils/http-status\";\nimport type { ZodType } from \"zod\";\nimport { MemberModel } from \"../models/member.ts\";\nimport { ProjectModel } from \"../models/project.ts\";\nimport { TokenModel } from \"../models/token.ts\";\nimport { projects } from \"../schema/project.ts\";\nimport type { Project } from \"../schema/project.ts\";\nimport { getStore } from \"../store.ts\";\nimport type { ProjectRole } from \"../types.ts\";\nimport { sha256 } from \"../utils/hash.ts\";\n\n/** Send a JSON response with the given status code. */\nexport function json(c: Context, data: unknown, status: ContentfulStatusCode = 200): Response {\n return c.json(data, status);\n}\n\n/** Parse the request body as JSON and validate it against a Zod schema. */\nexport async function validJson<T>(c: Context, schema: ZodType<T>): Promise<T> {\n const result = schema.safeParse(await c.req.json());\n if (!result.success) {\n throw new HTTPException(400, { message: result.error.message });\n }\n return result.data;\n}\n\n/** Throw a 401 Unauthorized error for unauthenticated requests. */\nexport function unauthorized(): never {\n throw new HTTPException(401, { message: \"Unauthorized\" });\n}\n\n/** Throw a 403 Forbidden error for requests lacking the required role. */\nexport function forbidden(): never {\n throw new HTTPException(403, { message: \"Forbidden\" });\n}\n\n/** Throw a 404 Not Found error with a custom message. */\nexport function notFound(message = \"Not found\"): never {\n throw new HTTPException(404, { message });\n}\n\n/** Resolve the current session user's effective role on a project. */\nexport async function currentProjectRole(projectId: string): Promise<ProjectRole | null> {\n const { db, user } = getStore();\n if (!user) {\n return null;\n }\n return await new MemberModel(db).effectiveRole(user.role, projectId, user.id);\n}\n\n/** Build middleware that requires one of the given project roles. */\nexport function requireRole(...roles: ProjectRole[]) {\n return async (c: Context, next: Next): Promise<void> => {\n const projectId = c.req.param(\"projectId\") ?? c.req.param(\"slug\");\n if (!projectId) {\n forbidden();\n }\n const role = await currentProjectRole(projectId);\n if (!role || !roles.includes(role)) {\n forbidden();\n }\n await next();\n };\n}\n\n/** Look up a project by its URL slug, returning null when absent. */\nexport async function findProjectBySlug(slug: string): Promise<Project | null> {\n const { db } = getStore();\n const rows = await db.list(projects, { where: eq(projects.slug, slug), limit: 1 });\n return rows[0] ?? null;\n}\n\nasync function resolveProjectByToken(c: Context, slug: string): Promise<Project | null> {\n const authHeader = c.req.header(\"authorization\");\n if (authHeader?.startsWith(\"Bearer \")) {\n const token = authHeader.slice(\"Bearer \".length);\n const found = await new TokenModel(getStore().db).findByHash(sha256(token));\n if (!found) {\n unauthorized();\n }\n const project = await new ProjectModel(getStore().db).get(found.projectId);\n if (!project || project.slug !== slug) {\n forbidden();\n }\n return project;\n }\n return null;\n}\n\n/** Resolve a project by slug, honoring CLI bearer-token access. */\nexport async function resolveProject(c: Context, slug: string): Promise<Project> {\n const project = await resolveProjectByToken(c, slug);\n if (project) {\n return project;\n }\n const found = await findProjectBySlug(slug);\n if (!found) {\n notFound(\"Project not found\");\n }\n return found;\n}\n\n/**\n * Enforce the caller's project role for a given project.\n *\n * Authorization model (ADR 0008):\n * - No auth adapter configured -> all operations permitted (development mode).\n * - Bearer token (CLI) -> grants access to its own project regardless of role.\n * - Session user -> must have an effective role in `minRoles`.\n */\nexport async function assertRole(projectId: string, ...minRoles: ProjectRole[]): Promise<void> {\n if (!getStore().authEnabled) {\n return;\n }\n const role = await currentProjectRole(projectId);\n if (!role || !minRoles.includes(role)) {\n forbidden();\n }\n}\n\n/** Resolve a project by slug and enforce the caller's minimum role. */\nexport async function resolveAuthorizedProject(\n c: Context,\n slug: string,\n ...minRoles: ProjectRole[]\n): Promise<Project> {\n const project = await resolveProjectByToken(c, slug);\n if (project) {\n return project;\n }\n const found = await findProjectBySlug(slug);\n if (!found) {\n notFound(\"Project not found\");\n }\n await assertRole(found.id, ...minRoles);\n return found;\n}\n\n/** Require a site-level admin (or permit when auth is disabled). */\nexport function requireSiteAdmin(): void {\n if (!getStore().authEnabled) {\n return;\n }\n const { user } = getStore();\n if (user?.role !== \"admin\") {\n forbidden();\n }\n}\n\n/** Require the current session user to hold one of the given project roles. */\nexport async function requireProjectRole(\n projectId: string,\n ...minRoles: ProjectRole[]\n): Promise<void> {\n await assertRole(projectId, ...minRoles);\n}\n"],"mappings":";;;;;;;;;;;;;AA6BA,SAAgB,eAAsB;CACpC,MAAM,IAAI,cAAc,KAAK,EAAE,SAAS,eAAe,CAAC;AAC1D;;AAGA,SAAgB,YAAmB;CACjC,MAAM,IAAI,cAAc,KAAK,EAAE,SAAS,YAAY,CAAC;AACvD;;AAGA,SAAgB,SAAS,UAAU,aAAoB;CACrD,MAAM,IAAI,cAAc,KAAK,EAAE,QAAQ,CAAC;AAC1C;;AAGA,eAAsB,mBAAmB,WAAgD;CACvF,MAAM,EAAE,IAAI,SAAS,SAAS;CAC9B,IAAI,CAAC,MACH,OAAO;CAET,OAAO,MAAM,IAAI,YAAY,EAAE,CAAC,CAAC,cAAc,KAAK,MAAM,WAAW,KAAK,EAAE;AAC9E;;AAkBA,eAAsB,kBAAkB,MAAuC;CAC7E,MAAM,EAAE,OAAO,SAAS;CAExB,QAAO,MADY,GAAG,KAAK,UAAU;EAAE,OAAO,GAAG,SAAS,MAAM,IAAI;EAAG,OAAO;CAAE,CAAC,EAAA,CACrE,MAAM;AACpB;AAEA,eAAe,sBAAsB,GAAY,MAAuC;CACtF,MAAM,aAAa,EAAE,IAAI,OAAO,eAAe;CAC/C,IAAI,YAAY,WAAW,SAAS,GAAG;EACrC,MAAM,QAAQ,WAAW,MAAM,CAAgB;EAC/C,MAAM,QAAQ,MAAM,IAAI,WAAW,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,WAAW,OAAO,KAAK,CAAC;EAC1E,IAAI,CAAC,OACH,aAAa;EAEf,MAAM,UAAU,MAAM,IAAI,aAAa,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,SAAS;EACzE,IAAI,CAAC,WAAW,QAAQ,SAAS,MAC/B,UAAU;EAEZ,OAAO;CACT;CACA,OAAO;AACT;;;;;;;;;AAuBA,eAAsB,WAAW,WAAmB,GAAG,UAAwC;CAC7F,IAAI,CAAC,SAAS,CAAC,CAAC,aACd;CAEF,MAAM,OAAO,MAAM,mBAAmB,SAAS;CAC/C,IAAI,CAAC,QAAQ,CAAC,SAAS,SAAS,IAAI,GAClC,UAAU;AAEd;;AAGA,eAAsB,yBACpB,GACA,MACA,GAAG,UACe;CAClB,MAAM,UAAU,MAAM,sBAAsB,GAAG,IAAI;CACnD,IAAI,SACF,OAAO;CAET,MAAM,QAAQ,MAAM,kBAAkB,IAAI;CAC1C,IAAI,CAAC,OACH,SAAS,mBAAmB;CAE9B,MAAM,WAAW,MAAM,IAAI,GAAG,QAAQ;CACtC,OAAO;AACT;;AAGA,SAAgB,mBAAyB;CACvC,IAAI,CAAC,SAAS,CAAC,CAAC,aACd;CAEF,MAAM,EAAE,SAAS,SAAS;CAC1B,IAAI,MAAM,SAAS,SACjB,UAAU;AAEd"}
@@ -1,20 +0,0 @@
1
- import "node:path";
2
- import "node:url";
3
- import.meta.url;
4
- //#region src/routers/htmx.ts
5
- /** Check whether the request came from HTMX (HX-Request header). */
6
- function isHxRequest(c) {
7
- return c.req.header("HX-Request") === "true";
8
- }
9
- /** Redirect HTMX requests via HX-Redirect and plain requests via 302. */
10
- function hxRedirect(c, url) {
11
- if (isHxRequest(c)) {
12
- c.header("HX-Redirect", url);
13
- return c.body(null, 204);
14
- }
15
- return c.redirect(url, 302);
16
- }
17
- //#endregion
18
- export { hxRedirect, isHxRequest };
19
-
20
- //# sourceMappingURL=htmx.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"htmx.mjs","names":[],"sources":["../../src/routers/htmx.ts"],"sourcesContent":["import type { Context } from \"hono\";\n\n/** Check whether the request came from HTMX (HX-Request header). */\nexport function isHxRequest(c: Context): boolean {\n return c.req.header(\"HX-Request\") === \"true\";\n}\n\n/** Redirect HTMX requests via HX-Redirect and plain requests via 302. */\nexport function hxRedirect(c: Context, url: string): Response {\n if (isHxRequest(c)) {\n c.header(\"HX-Redirect\", url);\n return c.body(null, 204);\n }\n return c.redirect(url, 302);\n}\n"],"mappings":";;;;;AAGA,SAAgB,YAAY,GAAqB;CAC/C,OAAO,EAAE,IAAI,OAAO,YAAY,MAAM;AACxC;;AAGA,SAAgB,WAAW,GAAY,KAAuB;CAC5D,IAAI,YAAY,CAAC,GAAG;EAClB,EAAE,OAAO,eAAe,GAAG;EAC3B,OAAO,EAAE,KAAK,MAAM,GAAG;CACzB;CACA,OAAO,EAAE,SAAS,KAAK,GAAG;AAC5B"}