@storyshelf/core 0.1.2 → 0.3.1

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 (275) hide show
  1. package/README.md +50 -46
  2. package/dist/adapter/auth.d.mts +4 -4
  3. package/dist/adapter/auth.mjs.map +1 -1
  4. package/dist/{adapters → adapter}/capture-queue.d.mts +6 -4
  5. package/dist/adapter/capture-queue.mjs +4 -0
  6. package/dist/adapter/capture-runner.d.mts +7 -7
  7. package/dist/adapter/database.d.mts +20 -9
  8. package/dist/adapter/database.mjs +60 -1
  9. package/dist/adapter/database.mjs.map +1 -0
  10. package/dist/adapter/git-host/comments.d.mts +41 -0
  11. package/dist/adapter/git-host/comments.mjs +54 -0
  12. package/dist/adapter/git-host/comments.mjs.map +1 -0
  13. package/dist/adapter/git-host/helpers.d.mts +1 -0
  14. package/dist/adapter/git-host/helpers.mjs +1 -0
  15. package/dist/adapter/git-host/helpers.mjs.map +1 -1
  16. package/dist/adapter/git-host.d.mts +3 -7
  17. package/dist/adapter/init.d.mts +58 -0
  18. package/dist/adapter/init.mjs +93 -0
  19. package/dist/adapter/init.mjs.map +1 -0
  20. package/dist/adapter/metadata.d.mts +83 -0
  21. package/dist/adapter/metadata.mjs +4 -0
  22. package/dist/adapter/storage.d.mts +15 -4
  23. package/dist/adapters/webhook-events.mjs +20 -4
  24. package/dist/adapters/webhook-events.mjs.map +1 -1
  25. package/dist/capture/adapter.d.mts +7 -1
  26. package/dist/capture/adapter.mjs +3 -0
  27. package/dist/capture/adapter.mjs.map +1 -1
  28. package/dist/capture/dispatch.mjs +150 -0
  29. package/dist/capture/dispatch.mjs.map +1 -0
  30. package/dist/capture/orchestrator.d.mts +9 -0
  31. package/dist/capture/orchestrator.mjs +11 -44
  32. package/dist/capture/orchestrator.mjs.map +1 -1
  33. package/dist/capture/pipeline.d.mts +4 -1
  34. package/dist/capture/pipeline.mjs +6 -6
  35. package/dist/capture/pipeline.mjs.map +1 -1
  36. package/dist/capture/queue.d.mts +4 -1
  37. package/dist/capture/queue.mjs +3 -1
  38. package/dist/capture/queue.mjs.map +1 -1
  39. package/dist/capture/skip-checks.mjs +76 -0
  40. package/dist/capture/skip-checks.mjs.map +1 -0
  41. package/dist/capture/statics.mjs +124 -0
  42. package/dist/capture/statics.mjs.map +1 -0
  43. package/dist/capture/status-fanout.mjs +1 -0
  44. package/dist/capture/status-fanout.mjs.map +1 -1
  45. package/dist/capture/storybook.d.mts +1 -0
  46. package/dist/capture/storybook.mjs +1 -0
  47. package/dist/capture/storybook.mjs.map +1 -1
  48. package/dist/capture.d.mts +8 -0
  49. package/dist/capture.mjs +9 -0
  50. package/dist/config.d.mts +37 -22
  51. package/dist/config.mjs +28 -2
  52. package/dist/config.mjs.map +1 -1
  53. package/dist/ddl.d.mts +2 -1
  54. package/dist/ddl.mjs +2 -1
  55. package/dist/ddl.mjs.map +1 -1
  56. package/dist/diff/engine.d.mts +8 -0
  57. package/dist/diff/engine.mjs +8 -0
  58. package/dist/diff/engine.mjs.map +1 -1
  59. package/dist/diff/options.d.mts +3 -1
  60. package/dist/diff.d.mts +5 -0
  61. package/dist/diff.mjs +6 -0
  62. package/dist/index.d.mts +34 -24
  63. package/dist/index.mjs +211 -252
  64. package/dist/index.mjs.map +1 -1
  65. package/dist/logger.d.mts +3 -3
  66. package/dist/logger.mjs.map +1 -1
  67. package/dist/middleware/auth-gate.mjs +36 -0
  68. package/dist/middleware/auth-gate.mjs.map +1 -0
  69. package/dist/middleware/csrf.mjs +1 -0
  70. package/dist/middleware/csrf.mjs.map +1 -1
  71. package/dist/middleware/rate-limit.mjs +1 -0
  72. package/dist/middleware/rate-limit.mjs.map +1 -1
  73. package/dist/middleware/request-log.mjs +28 -0
  74. package/dist/middleware/request-log.mjs.map +1 -0
  75. package/dist/middleware/store-scope.mjs +29 -0
  76. package/dist/middleware/store-scope.mjs.map +1 -0
  77. package/dist/models/baseline.mjs +9 -5
  78. package/dist/models/baseline.mjs.map +1 -1
  79. package/dist/models/build.mjs +5 -1
  80. package/dist/models/build.mjs.map +1 -1
  81. package/dist/models/comment.mjs +4 -1
  82. package/dist/models/comment.mjs.map +1 -1
  83. package/dist/models/label.mjs +3 -1
  84. package/dist/models/label.mjs.map +1 -1
  85. package/dist/models/member.mjs +2 -1
  86. package/dist/models/member.mjs.map +1 -1
  87. package/dist/models/project.mjs +2 -1
  88. package/dist/models/project.mjs.map +1 -1
  89. package/dist/models/snapshot.mjs +2 -1
  90. package/dist/models/snapshot.mjs.map +1 -1
  91. package/dist/models/status-config.mjs +2 -1
  92. package/dist/models/status-config.mjs.map +1 -1
  93. package/dist/models/token.mjs +2 -1
  94. package/dist/models/token.mjs.map +1 -1
  95. package/dist/models/webhook.mjs +12 -3
  96. package/dist/models/webhook.mjs.map +1 -1
  97. package/dist/pages/build-detail.mjs +10 -4
  98. package/dist/pages/build-detail.mjs.map +1 -1
  99. package/dist/pages/build-diff-comments.mjs +113 -0
  100. package/dist/pages/build-diff-comments.mjs.map +1 -0
  101. package/dist/pages/build-diff-header.mjs +163 -0
  102. package/dist/pages/build-diff-header.mjs.map +1 -0
  103. package/dist/pages/build-diff-nav.mjs +66 -0
  104. package/dist/pages/build-diff-nav.mjs.map +1 -0
  105. package/dist/pages/build-diff-viewer.mjs +197 -0
  106. package/dist/pages/build-diff-viewer.mjs.map +1 -0
  107. package/dist/pages/build-diff.mjs +66 -413
  108. package/dist/pages/build-diff.mjs.map +1 -1
  109. package/dist/pages/compute-jobs.mjs +3 -1
  110. package/dist/pages/compute-jobs.mjs.map +1 -1
  111. package/dist/pages/label-detail.mjs +4 -3
  112. package/dist/pages/label-detail.mjs.map +1 -1
  113. package/dist/pages/login.mjs +1 -0
  114. package/dist/pages/login.mjs.map +1 -1
  115. package/dist/pages/project-builds.mjs +6 -3
  116. package/dist/pages/project-builds.mjs.map +1 -1
  117. package/dist/pages/project-create.mjs +1 -0
  118. package/dist/pages/project-create.mjs.map +1 -1
  119. package/dist/pages/project-settings.mjs +1 -0
  120. package/dist/pages/project-settings.mjs.map +1 -1
  121. package/dist/pages/projects.mjs +12 -6
  122. package/dist/pages/projects.mjs.map +1 -1
  123. package/dist/pages/root.mjs +7 -3
  124. package/dist/pages/root.mjs.map +1 -1
  125. package/dist/pages/settings-general.mjs +1 -0
  126. package/dist/pages/settings-general.mjs.map +1 -1
  127. package/dist/pages/settings-labels.mjs +2 -1
  128. package/dist/pages/settings-labels.mjs.map +1 -1
  129. package/dist/pages/settings-members.mjs +2 -1
  130. package/dist/pages/settings-members.mjs.map +1 -1
  131. package/dist/pages/settings-status.mjs +3 -1
  132. package/dist/pages/settings-status.mjs.map +1 -1
  133. package/dist/pages/settings-tests.mjs +4 -3
  134. package/dist/pages/settings-tests.mjs.map +1 -1
  135. package/dist/pages/settings-tokens.mjs +1 -0
  136. package/dist/pages/settings-tokens.mjs.map +1 -1
  137. package/dist/pages/settings-webhooks.mjs +1 -0
  138. package/dist/pages/settings-webhooks.mjs.map +1 -1
  139. package/dist/pages/storybook.mjs +38 -1
  140. package/dist/pages/storybook.mjs.map +1 -1
  141. package/dist/{utils/paths.d.mts → paths.d.mts} +5 -0
  142. package/dist/{utils/paths.mjs → paths.mjs} +5 -0
  143. package/dist/paths.mjs.map +1 -0
  144. package/dist/retention/purge.mjs +2 -1
  145. package/dist/retention/purge.mjs.map +1 -1
  146. package/dist/routers/admin.mjs +2 -1
  147. package/dist/routers/admin.mjs.map +1 -1
  148. package/dist/routers/assets.mjs +1 -0
  149. package/dist/routers/assets.mjs.map +1 -1
  150. package/dist/routers/auth.mjs +1 -0
  151. package/dist/routers/auth.mjs.map +1 -1
  152. package/dist/routers/builds.handlers.mjs +114 -19
  153. package/dist/routers/builds.handlers.mjs.map +1 -1
  154. package/dist/routers/builds.mjs +55 -34
  155. package/dist/routers/builds.mjs.map +1 -1
  156. package/dist/routers/comments.mjs +1 -0
  157. package/dist/routers/comments.mjs.map +1 -1
  158. package/dist/routers/health.mjs +137 -0
  159. package/dist/routers/health.mjs.map +1 -0
  160. package/dist/routers/helpers.mjs +8 -2
  161. package/dist/routers/helpers.mjs.map +1 -1
  162. package/dist/routers/htmx.mjs +2 -0
  163. package/dist/routers/htmx.mjs.map +1 -1
  164. package/dist/routers/labels.mjs +2 -1
  165. package/dist/routers/labels.mjs.map +1 -1
  166. package/dist/routers/media.mjs +3 -2
  167. package/dist/routers/media.mjs.map +1 -1
  168. package/dist/routers/members.mjs +1 -0
  169. package/dist/routers/members.mjs.map +1 -1
  170. package/dist/routers/projects.mjs +2 -1
  171. package/dist/routers/projects.mjs.map +1 -1
  172. package/dist/routers/schemas.mjs +25 -0
  173. package/dist/routers/schemas.mjs.map +1 -1
  174. package/dist/routers/settings-general.mjs +95 -0
  175. package/dist/routers/settings-general.mjs.map +1 -0
  176. package/dist/routers/settings-labels.mjs +49 -0
  177. package/dist/routers/settings-labels.mjs.map +1 -0
  178. package/dist/routers/settings-members.mjs +30 -0
  179. package/dist/routers/settings-members.mjs.map +1 -0
  180. package/dist/routers/settings-status.mjs +73 -0
  181. package/dist/routers/settings-status.mjs.map +1 -0
  182. package/dist/routers/settings-tokens.mjs +40 -0
  183. package/dist/routers/settings-tokens.mjs.map +1 -0
  184. package/dist/routers/settings-webhooks.mjs +63 -0
  185. package/dist/routers/settings-webhooks.mjs.map +1 -0
  186. package/dist/routers/settings.handlers.mjs +93 -0
  187. package/dist/routers/settings.handlers.mjs.map +1 -0
  188. package/dist/routers/settings.mjs +14 -246
  189. package/dist/routers/settings.mjs.map +1 -1
  190. package/dist/routers/snapshots.mjs +2 -1
  191. package/dist/routers/snapshots.mjs.map +1 -1
  192. package/dist/routers/status-configs.mjs +2 -1
  193. package/dist/routers/status-configs.mjs.map +1 -1
  194. package/dist/routers/storybook.mjs +12 -4
  195. package/dist/routers/storybook.mjs.map +1 -1
  196. package/dist/routers/tokens.mjs +2 -1
  197. package/dist/routers/tokens.mjs.map +1 -1
  198. package/dist/routers/ui.mjs +5 -4
  199. package/dist/routers/ui.mjs.map +1 -1
  200. package/dist/routers/webhooks.mjs +7 -6
  201. package/dist/routers/webhooks.mjs.map +1 -1
  202. package/dist/schema/baseline.d.mts +18 -0
  203. package/dist/schema/baseline.mjs +22 -0
  204. package/dist/schema/baseline.mjs.map +1 -0
  205. package/dist/schema/build.d.mts +26 -0
  206. package/dist/schema/build.mjs +29 -0
  207. package/dist/schema/build.mjs.map +1 -0
  208. package/dist/schema/comment.d.mts +19 -0
  209. package/dist/schema/comment.mjs +26 -0
  210. package/dist/schema/comment.mjs.map +1 -0
  211. package/dist/schema/label.d.mts +25 -0
  212. package/dist/schema/label.mjs +30 -0
  213. package/dist/schema/label.mjs.map +1 -0
  214. package/dist/schema/member.d.mts +15 -0
  215. package/dist/schema/member.mjs +19 -0
  216. package/dist/schema/member.mjs.map +1 -0
  217. package/dist/schema/project.d.mts +34 -0
  218. package/dist/schema/project.mjs +25 -0
  219. package/dist/schema/project.mjs.map +1 -0
  220. package/dist/schema/snapshot.d.mts +30 -0
  221. package/dist/schema/snapshot.mjs +34 -0
  222. package/dist/schema/snapshot.mjs.map +1 -0
  223. package/dist/schema/status-config.d.mts +16 -0
  224. package/dist/schema/status-config.mjs +20 -0
  225. package/dist/schema/status-config.mjs.map +1 -0
  226. package/dist/schema/token.d.mts +15 -0
  227. package/dist/schema/token.mjs +19 -0
  228. package/dist/schema/token.mjs.map +1 -0
  229. package/dist/schema/user.d.mts +17 -0
  230. package/dist/schema/user.mjs +19 -0
  231. package/dist/schema/user.mjs.map +1 -0
  232. package/dist/schema/webhook.d.mts +16 -0
  233. package/dist/schema/webhook.mjs +20 -0
  234. package/dist/schema/webhook.mjs.map +1 -0
  235. package/dist/schema.d.mts +52 -4433
  236. package/dist/schema.mjs +37 -139
  237. package/dist/schema.mjs.map +1 -1
  238. package/dist/store.mjs +2 -0
  239. package/dist/store.mjs.map +1 -1
  240. package/dist/types.d.mts +13 -2
  241. package/dist/types.mjs +8 -0
  242. package/dist/types.mjs.map +1 -1
  243. package/dist/ui/document.mjs +1 -0
  244. package/dist/ui/document.mjs.map +1 -1
  245. package/dist/ui/feedback.mjs +24 -0
  246. package/dist/ui/feedback.mjs.map +1 -0
  247. package/dist/ui/{components.mjs → forms.mjs} +6 -16
  248. package/dist/ui/forms.mjs.map +1 -0
  249. package/dist/ui/styles.mjs +1 -0
  250. package/dist/ui/styles.mjs.map +1 -1
  251. package/dist/ui/theme.mjs +2 -0
  252. package/dist/ui/theme.mjs.map +1 -1
  253. package/dist/urls.d.mts +8 -0
  254. package/dist/urls.mjs +7 -0
  255. package/dist/urls.mjs.map +1 -1
  256. package/dist/utils/hash.mjs +3 -0
  257. package/dist/utils/hash.mjs.map +1 -1
  258. package/dist/utils/ulid.mjs +2 -0
  259. package/dist/utils/ulid.mjs.map +1 -1
  260. package/package.json +47 -45
  261. package/dist/adapters/metadata.d.mts +0 -24
  262. package/dist/models/baseline.d.mts +0 -20
  263. package/dist/models/build.d.mts +0 -50
  264. package/dist/models/comment.d.mts +0 -20
  265. package/dist/models/label.d.mts +0 -49
  266. package/dist/models/member.d.mts +0 -27
  267. package/dist/models/project.d.mts +0 -42
  268. package/dist/models/snapshot.d.mts +0 -46
  269. package/dist/models/token.d.mts +0 -33
  270. package/dist/models/webhook.d.mts +0 -37
  271. package/dist/retention/purge.d.mts +0 -28
  272. package/dist/ui/components.mjs.map +0 -1
  273. package/dist/ui/document.d.mts +0 -8
  274. package/dist/utils/paths.mjs.map +0 -1
  275. package/dist/utils/ulid.d.mts +0 -8
@@ -3,19 +3,19 @@ import "node:url";
3
3
  import.meta.url;
4
4
  import { BuildModel } from "../models/build.mjs";
5
5
  import { ProjectModel } from "../models/project.mjs";
6
- import { LabelModel } from "../models/label.mjs";
6
+ import { BaselineModel } from "../models/baseline.mjs";
7
+ import { SnapshotModel } from "../models/snapshot.mjs";
7
8
  import { getStore } from "../store.mjs";
9
+ import { LabelModel } from "../models/label.mjs";
8
10
  import { currentProjectRole } from "./helpers.mjs";
9
11
  import { hxRedirect } from "./htmx.mjs";
10
- import { BaselineModel } from "../models/baseline.mjs";
11
- import { SnapshotModel } from "../models/snapshot.mjs";
12
12
  import { CommentModel } from "../models/comment.mjs";
13
13
  import { renderBuildDetailPage } from "../pages/build-detail.mjs";
14
14
  import { renderBuildDiffPage } from "../pages/build-diff.mjs";
15
15
  import { renderActiveQueue, renderComputeJobsPage } from "../pages/compute-jobs.mjs";
16
16
  import { renderLabelDetailPage, renderLabelsPage } from "../pages/label-detail.mjs";
17
- import { renderProjectCreatePage } from "../pages/project-create.mjs";
18
17
  import { renderProjectBuildsPage } from "../pages/project-builds.mjs";
18
+ import { renderProjectCreatePage } from "../pages/project-create.mjs";
19
19
  import { renderProjectsPage } from "../pages/projects.mjs";
20
20
  import { renderRootPage } from "../pages/root.mjs";
21
21
  import { registerSettingsPages } from "./settings.mjs";
@@ -53,6 +53,7 @@ async function canManageJobs(projectId) {
53
53
  const role = await currentProjectRole(projectId);
54
54
  return Boolean(role && roles.includes(role));
55
55
  }
56
+ /** Register the server-rendered HTML pages (projects, builds, diffs, labels, settings). */
56
57
  function registerUiPages(app) {
57
58
  app.get("/", async (c) => c.html(await renderRootPage()));
58
59
  app.get("/projects", async (c) => c.html(await renderProjectsPage()));
@@ -1 +1 @@
1
- {"version":3,"file":"ui.mjs","names":[],"sources":["../../src/routers/ui.ts"],"sourcesContent":["import type { ShelfApp } from \"../index.tsx\";\n\nimport { BaselineModel } from \"../models/baseline.ts\";\nimport { BuildModel } from \"../models/build.ts\";\nimport { CommentModel } from \"../models/comment.ts\";\nimport { LabelModel } from \"../models/label.ts\";\nimport { ProjectModel } from \"../models/project.ts\";\nimport { SnapshotModel } from \"../models/snapshot.ts\";\nimport { renderBuildDetailPage } from \"../pages/build-detail.tsx\";\nimport { renderBuildDiffPage } from \"../pages/build-diff.tsx\";\nimport { renderComputeJobsPage, renderActiveQueue } from \"../pages/compute-jobs.tsx\";\nimport { renderLabelDetailPage, renderLabelsPage } from \"../pages/label-detail.tsx\";\nimport { renderProjectCreatePage } from \"../pages/project-create.tsx\";\nimport { renderProjectBuildsPage } from \"../pages/project-builds.tsx\";\nimport { renderProjectsPage } from \"../pages/projects.tsx\";\nimport { renderRootPage } from \"../pages/root.tsx\";\nimport { getStore } from \"../store.ts\";\nimport type { ProjectRole } from \"../types.ts\";\nimport { currentProjectRole } from \"./helpers.ts\";\nimport { hxRedirect } from \"./htmx.ts\";\nimport { registerSettingsPages } from \"./settings.ts\";\n\nfunction asString(value: FormDataEntryValue | null): string | undefined {\n return typeof value === \"string\" ? value.trim() : undefined;\n}\n\nfunction decodeRest(value: string): string {\n try {\n return decodeURIComponent(value);\n } catch {\n return \"\";\n }\n}\n\ninterface QueueView {\n buildId: string;\n status: string;\n queuedAt: string;\n startedAt?: string;\n finishedAt?: string;\n error?: string;\n}\n\nasync function getQueueView(): Promise<QueueView[]> {\n const queue = getStore().captureQueue;\n if (!queue) {\n return [];\n }\n const active = await queue.active();\n return active.map((entry) => ({\n buildId: entry.buildId,\n status: entry.status,\n queuedAt: entry.queuedAt,\n startedAt: entry.startedAt,\n finishedAt: entry.finishedAt,\n error: entry.error,\n }));\n}\n\nasync function canManageJobs(projectId: string): Promise<boolean> {\n const { authEnabled } = getStore();\n if (!authEnabled) {\n return true;\n }\n const roles: readonly ProjectRole[] = [\"developer\", \"approver\", \"admin\"];\n const role = await currentProjectRole(projectId);\n return Boolean(role && roles.includes(role));\n}\n\nexport function registerUiPages(app: ShelfApp): void {\n // eslint-disable-next-line promise-function-async -- renderRootPage returns RenderedContent (string | Promise<string>)\n app.get(\"/\", async (c) => c.html(await renderRootPage()));\n app.get(\"/projects\", async (c) => c.html(await renderProjectsPage()));\n // eslint-disable-next-line promise-function-async -- renderProjectCreatePage returns RenderedContent (string | Promise<string>)\n app.get(\"/projects/new\", async (c) => c.html(await renderProjectCreatePage()));\n\n app.post(\"/projects/new\", async (c) => {\n const form = await c.req.formData();\n const name = asString(form.get(\"name\"));\n const gitRepository = asString(form.get(\"gitRepository\"));\n const gitDefaultBranch = asString(form.get(\"gitDefaultBranch\"));\n if (!name) {\n return c.html(renderProjectCreatePage({ values: { name, gitRepository, gitDefaultBranch }, errors: { name: \"Name is required\" } }), 400);\n }\n try {\n const project = await new ProjectModel(getStore().db).create({ name, gitRepository, gitDefaultBranch });\n await new LabelModel(getStore().db).seedFor(project.id);\n return hxRedirect(c, `/projects/${project.slug}/builds`);\n } catch (error) {\n const message = error instanceof Error ? error.message : \"Failed to create project\";\n return c.html(renderProjectCreatePage({ values: { name, gitRepository, gitDefaultBranch }, globalError: message }), 400);\n }\n });\n\n app.get(\"/projects/:slug/builds\", async (c) => {\n const html = await renderProjectBuildsPage(c.req.param(\"slug\"), {\n status: c.req.query(\"status\"),\n branch: c.req.query(\"branch\"),\n });\n if (!html) {\n return c.notFound();\n }\n return c.html(html);\n });\n\n app.get(\"/projects/:slug/builds/:buildId\", async (c) => {\n const html = await renderBuildDetailPage(c.req.param(\"buildId\"));\n if (!html) {\n return c.notFound();\n }\n return c.html(html);\n });\n\n app.get(\"/projects/:slug/jobs\", async (c) => {\n const slug = c.req.param(\"slug\");\n const project = await new ProjectModel(getStore().db).getBySlug(slug);\n if (!project) {\n return c.notFound();\n }\n const queueView = await getQueueView();\n const canRetry = await canManageJobs(project.id);\n if (c.req.query(\"partial\") === \"queue\") {\n return c.html(renderActiveQueue(slug, queueView));\n }\n const html = await renderComputeJobsPage(slug, queueView, canRetry);\n if (!html) {\n return c.notFound();\n }\n return c.html(html);\n });\n\n app.get(\"/projects/:slug/builds/:buildId/diff\", async (c) => {\n const slug = c.req.param(\"slug\");\n const buildId = c.req.param(\"buildId\");\n const snapshotId = c.req.query(\"snapshot\");\n const project = await new ProjectModel(getStore().db).getBySlug(slug);\n if (!project) {\n return c.notFound();\n }\n const build = await new BuildModel(getStore().db).get(buildId);\n if (!build || build.projectId !== project.id) {\n return c.notFound();\n }\n const snapshots = await new SnapshotModel(getStore().db).listByBuild(build.id);\n const comments = await new CommentModel(getStore().db).listByBuild(build.id);\n const baselines = new BaselineModel(getStore().db, getStore().storage);\n const hasBaselineEntries = await Promise.all(\n snapshots.map(async (snapshot) => {\n const baseline = await baselines.resolve(project.id, snapshot.storyId, snapshot.viewportName, build.gitBranch, project.gitDefaultBranch);\n return [snapshot.id, Boolean(baseline)] as const;\n }),\n );\n const hasBaseline = Object.fromEntries(hasBaselineEntries);\n const { user, authEnabled } = getStore();\n const canReview = !authEnabled || Boolean(user);\n return c.html(\n renderBuildDiffPage({\n project,\n build,\n snapshots,\n comments,\n selectedId: snapshotId,\n canReview,\n hasBaseline,\n }),\n );\n });\n\n app.get(\"/projects/:slug/labels\", async (c) => {\n const html = await renderLabelsPage(c.req.param(\"slug\"));\n if (!html) {\n return c.notFound();\n }\n return c.html(html);\n });\n\n app.get(\"/projects/:slug/labels/:key/*\", async (c) => {\n const slug = c.req.param(\"slug\");\n const key = c.req.param(\"key\");\n // Hono's bare `/*` wildcard is not exposed through `param()`, so derive the\n // label value from the raw request path and decode it ourselves.\n const base = `/projects/${slug}/labels/${key}/`;\n const value = c.req.path.startsWith(base) ? decodeRest(c.req.path.slice(base.length)) : \"\";\n const html = await renderLabelDetailPage(slug, key, value);\n if (!html) {\n return c.notFound();\n }\n return c.html(html);\n });\n\n registerSettingsPages(app);\n}"],"mappings":"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"}
1
+ {"version":3,"file":"ui.mjs","names":[],"sources":["../../src/routers/ui.ts"],"sourcesContent":["import type { ShelfApp } from \"../index.tsx\";\nimport { BaselineModel } from \"../models/baseline.ts\";\nimport { BuildModel } from \"../models/build.ts\";\nimport { CommentModel } from \"../models/comment.ts\";\nimport { LabelModel } from \"../models/label.ts\";\nimport { ProjectModel } from \"../models/project.ts\";\nimport { SnapshotModel } from \"../models/snapshot.ts\";\nimport { renderBuildDetailPage } from \"../pages/build-detail.tsx\";\nimport { renderBuildDiffPage } from \"../pages/build-diff.tsx\";\nimport { renderComputeJobsPage, renderActiveQueue } from \"../pages/compute-jobs.tsx\";\nimport { renderLabelDetailPage, renderLabelsPage } from \"../pages/label-detail.tsx\";\nimport { renderProjectBuildsPage } from \"../pages/project-builds.tsx\";\nimport { renderProjectCreatePage } from \"../pages/project-create.tsx\";\nimport { renderProjectsPage } from \"../pages/projects.tsx\";\nimport { renderRootPage } from \"../pages/root.tsx\";\nimport { getStore } from \"../store.ts\";\nimport type { ProjectRole } from \"../types.ts\";\nimport { currentProjectRole } from \"./helpers.ts\";\nimport { hxRedirect } from \"./htmx.ts\";\nimport { registerSettingsPages } from \"./settings.ts\";\n\nfunction asString(value: FormDataEntryValue | null): string | undefined {\n return typeof value === \"string\" ? value.trim() : undefined;\n}\n\nfunction decodeRest(value: string): string {\n try {\n return decodeURIComponent(value);\n } catch {\n return \"\";\n }\n}\n\ninterface QueueView {\n buildId: string;\n status: string;\n queuedAt: string;\n startedAt?: string;\n finishedAt?: string;\n error?: string;\n}\n\nasync function getQueueView(): Promise<QueueView[]> {\n const queue = getStore().captureQueue;\n if (!queue) {\n return [];\n }\n const active = await queue.active();\n return active.map((entry) => ({\n buildId: entry.buildId,\n status: entry.status,\n queuedAt: entry.queuedAt,\n startedAt: entry.startedAt,\n finishedAt: entry.finishedAt,\n error: entry.error,\n }));\n}\n\nasync function canManageJobs(projectId: string): Promise<boolean> {\n const { authEnabled } = getStore();\n if (!authEnabled) {\n return true;\n }\n const roles: readonly ProjectRole[] = [\"developer\", \"approver\", \"admin\"];\n const role = await currentProjectRole(projectId);\n return Boolean(role && roles.includes(role));\n}\n\n/** Register the server-rendered HTML pages (projects, builds, diffs, labels, settings). */\nexport function registerUiPages(app: ShelfApp): void {\n // eslint-disable-next-line promise-function-async -- renderRootPage returns RenderedContent (string | Promise<string>)\n app.get(\"/\", async (c) => c.html(await renderRootPage()));\n app.get(\"/projects\", async (c) => c.html(await renderProjectsPage()));\n // eslint-disable-next-line promise-function-async -- renderProjectCreatePage returns RenderedContent (string | Promise<string>)\n app.get(\"/projects/new\", async (c) => c.html(await renderProjectCreatePage()));\n\n app.post(\"/projects/new\", async (c) => {\n const form = await c.req.formData();\n const name = asString(form.get(\"name\"));\n const gitRepository = asString(form.get(\"gitRepository\"));\n const gitDefaultBranch = asString(form.get(\"gitDefaultBranch\"));\n if (!name) {\n return c.html(\n renderProjectCreatePage({\n values: { name, gitRepository, gitDefaultBranch },\n errors: { name: \"Name is required\" },\n }),\n 400,\n );\n }\n try {\n const project = await new ProjectModel(getStore().db).create({\n name,\n gitRepository,\n gitDefaultBranch,\n });\n await new LabelModel(getStore().db).seedFor(project.id);\n return hxRedirect(c, `/projects/${project.slug}/builds`);\n } catch (error) {\n const message = error instanceof Error ? error.message : \"Failed to create project\";\n return c.html(\n renderProjectCreatePage({\n values: { name, gitRepository, gitDefaultBranch },\n globalError: message,\n }),\n 400,\n );\n }\n });\n\n app.get(\"/projects/:slug/builds\", async (c) => {\n const html = await renderProjectBuildsPage(c.req.param(\"slug\"), {\n status: c.req.query(\"status\"),\n branch: c.req.query(\"branch\"),\n });\n if (!html) {\n return c.notFound();\n }\n return c.html(html);\n });\n\n app.get(\"/projects/:slug/builds/:buildId\", async (c) => {\n const html = await renderBuildDetailPage(c.req.param(\"buildId\"));\n if (!html) {\n return c.notFound();\n }\n return c.html(html);\n });\n\n app.get(\"/projects/:slug/jobs\", async (c) => {\n const slug = c.req.param(\"slug\");\n const project = await new ProjectModel(getStore().db).getBySlug(slug);\n if (!project) {\n return c.notFound();\n }\n const queueView = await getQueueView();\n const canRetry = await canManageJobs(project.id);\n if (c.req.query(\"partial\") === \"queue\") {\n return c.html(renderActiveQueue(slug, queueView));\n }\n const html = await renderComputeJobsPage(slug, queueView, canRetry);\n if (!html) {\n return c.notFound();\n }\n return c.html(html);\n });\n\n app.get(\"/projects/:slug/builds/:buildId/diff\", async (c) => {\n const slug = c.req.param(\"slug\");\n const buildId = c.req.param(\"buildId\");\n const snapshotId = c.req.query(\"snapshot\");\n const project = await new ProjectModel(getStore().db).getBySlug(slug);\n if (!project) {\n return c.notFound();\n }\n const build = await new BuildModel(getStore().db).get(buildId);\n if (!build || build.projectId !== project.id) {\n return c.notFound();\n }\n const snapshots = await new SnapshotModel(getStore().db).listByBuild(build.id);\n const comments = await new CommentModel(getStore().db).listByBuild(build.id);\n const baselines = new BaselineModel(getStore().db, getStore().storage);\n const hasBaselineEntries = await Promise.all(\n snapshots.map(async (snapshot) => {\n const baseline = await baselines.resolve(\n project.id,\n snapshot.storyId,\n snapshot.viewportName,\n build.gitBranch,\n project.gitDefaultBranch,\n );\n return [snapshot.id, Boolean(baseline)] as const;\n }),\n );\n const hasBaseline = Object.fromEntries(hasBaselineEntries);\n const { user, authEnabled } = getStore();\n const canReview = !authEnabled || Boolean(user);\n return c.html(\n renderBuildDiffPage({\n project,\n build,\n snapshots,\n comments,\n selectedId: snapshotId,\n canReview,\n hasBaseline,\n }),\n );\n });\n\n app.get(\"/projects/:slug/labels\", async (c) => {\n const html = await renderLabelsPage(c.req.param(\"slug\"));\n if (!html) {\n return c.notFound();\n }\n return c.html(html);\n });\n\n app.get(\"/projects/:slug/labels/:key/*\", async (c) => {\n const slug = c.req.param(\"slug\");\n const key = c.req.param(\"key\");\n // Hono's bare `/*` wildcard is not exposed through `param()`, so derive the\n // label value from the raw request path and decode it ourselves.\n const base = `/projects/${slug}/labels/${key}/`;\n const value = c.req.path.startsWith(base) ? decodeRest(c.req.path.slice(base.length)) : \"\";\n const html = await renderLabelDetailPage(slug, key, value);\n if (!html) {\n return c.notFound();\n }\n return c.html(html);\n });\n\n registerSettingsPages(app);\n}\n"],"mappings":"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"}
@@ -1,11 +1,11 @@
1
1
  import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
- import { getStore } from "../store.mjs";
4
+ import { WebhookModel } from "../models/webhook.mjs";
5
5
  import { randomToken } from "../utils/hash.mjs";
6
+ import { getStore } from "../store.mjs";
6
7
  import { notFound, resolveAuthorizedProject } from "./helpers.mjs";
7
8
  import { notFound as notFound$1, unauthorized, webhookCreateSchema, webhookCreatedSchema, webhookPublicSchema } from "./schemas.mjs";
8
- import { WebhookModel } from "../models/webhook.mjs";
9
9
  import { createRoute, z } from "@hono/zod-openapi";
10
10
  //#region src/routers/webhooks.ts
11
11
  const ADMIN_ROLES = ["admin"];
@@ -49,10 +49,11 @@ const deleteWebhookRoute = createRoute({
49
49
  ...notFound$1
50
50
  }
51
51
  });
52
+ /** Register the project webhook list, create, and delete endpoints. */
52
53
  function registerWebhooks(app) {
53
54
  app.openapi(listWebhooksRoute, async (c) => {
54
55
  const project = await resolveAuthorizedProject(c, c.req.valid("param").slug, ...ADMIN_ROLES);
55
- const webhooks = await new WebhookModel(getStore().db).list(project.id);
56
+ const webhooks = await new WebhookModel(getStore().db, getStore().config.secret).list(project.id);
56
57
  return c.json(webhooks.map((webhook) => ({
57
58
  id: webhook.id,
58
59
  url: webhook.url,
@@ -63,7 +64,7 @@ function registerWebhooks(app) {
63
64
  const project = await resolveAuthorizedProject(c, c.req.valid("param").slug, ...ADMIN_ROLES);
64
65
  const body = c.req.valid("json");
65
66
  const secret = randomToken("whsec_").value;
66
- const webhook = await new WebhookModel(getStore().db).create(project.id, {
67
+ const webhook = await new WebhookModel(getStore().db, getStore().config.secret).create(project.id, {
67
68
  ...body,
68
69
  secret
69
70
  });
@@ -77,9 +78,9 @@ function registerWebhooks(app) {
77
78
  app.openapi(deleteWebhookRoute, async (c) => {
78
79
  const { slug, webhookId } = c.req.valid("param");
79
80
  const project = await resolveAuthorizedProject(c, slug, ...ADMIN_ROLES);
80
- const webhook = await new WebhookModel(getStore().db).get(project.id, webhookId);
81
+ const webhook = await new WebhookModel(getStore().db, getStore().config.secret).get(project.id, webhookId);
81
82
  if (!webhook) notFound("Webhook not found");
82
- await new WebhookModel(getStore().db).remove(project.id, webhook.id);
83
+ await new WebhookModel(getStore().db, getStore().config.secret).remove(project.id, webhook.id);
83
84
  return c.body(null, 204);
84
85
  });
85
86
  }
@@ -1 +1 @@
1
- {"version":3,"file":"webhooks.mjs","names":["notFoundResponse"],"sources":["../../src/routers/webhooks.ts"],"sourcesContent":["import { createRoute, z } from \"@hono/zod-openapi\";\nimport type { ShelfApp } from \"../index.tsx\";\n\nimport { WebhookModel } from \"../models/webhook.ts\";\nimport { getStore } from \"../store.ts\";\nimport type { ProjectRole } from \"../types.ts\";\nimport { randomToken } from \"../utils/hash.ts\";\nimport { resolveAuthorizedProject, notFound } from \"./helpers.ts\";\nimport {\n notFound as notFoundResponse,\n unauthorized,\n webhookCreatedSchema,\n webhookCreateSchema,\n webhookPublicSchema,\n} from \"./schemas.ts\";\n\nconst ADMIN_ROLES: readonly ProjectRole[] = [\"admin\"];\n\nconst listWebhooksRoute = createRoute({\n method: \"get\",\n path: \"/api/v1/projects/{slug}/webhooks\",\n request: { params: z.object({ slug: z.string() }) },\n responses: {\n 200: { content: { \"application/json\": { schema: webhookPublicSchema.array() } }, description: \"List webhooks\" },\n ...notFoundResponse,\n ...unauthorized,\n },\n});\n\nconst createWebhookRoute = createRoute({\n method: \"post\",\n path: \"/api/v1/projects/{slug}/webhooks\",\n request: {\n params: z.object({ slug: z.string() }),\n body: { content: { \"application/json\": { schema: webhookCreateSchema } } },\n },\n responses: {\n 201: { content: { \"application/json\": { schema: webhookCreatedSchema } }, description: \"Created webhook\" },\n ...notFoundResponse,\n },\n});\n\nconst deleteWebhookRoute = createRoute({\n method: \"delete\",\n path: \"/api/v1/projects/{slug}/webhooks/{webhookId}\",\n request: { params: z.object({ slug: z.string(), webhookId: z.string() }) },\n responses: {\n 204: { description: \"Deleted webhook\" },\n ...notFoundResponse,\n },\n});\n\nexport function registerWebhooks(app: ShelfApp): void {\n app.openapi(listWebhooksRoute, async (c) => {\n const project = await resolveAuthorizedProject(c, c.req.valid(\"param\").slug, ...ADMIN_ROLES);\n const webhooks = await new WebhookModel(getStore().db).list(project.id);\n return c.json(webhooks.map((webhook) => ({ id: webhook.id, url: webhook.url, events: WebhookModel.eventsOf(webhook) })));\n });\n\n app.openapi(createWebhookRoute, async (c) => {\n const project = await resolveAuthorizedProject(c, c.req.valid(\"param\").slug, ...ADMIN_ROLES);\n const body = c.req.valid(\"json\");\n const secret = randomToken(\"whsec_\").value;\n const webhook = await new WebhookModel(getStore().db).create(project.id, { ...body, secret });\n return c.json({ id: webhook.id, url: webhook.url, events: body.events ?? [], secret }, 201);\n });\n\n app.openapi(deleteWebhookRoute, async (c) => {\n const { slug, webhookId } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...ADMIN_ROLES);\n const webhook = await new WebhookModel(getStore().db).get(project.id, webhookId);\n if (!webhook) {\n notFound(\"Webhook not found\");\n }\n await new WebhookModel(getStore().db).remove(project.id, webhook.id);\n return c.body(null, 204);\n });\n}"],"mappings":";;;;;;;;;;AAgBA,MAAM,cAAsC,CAAC,OAAO;AAEpD,MAAM,oBAAoB,YAAY;CACpC,QAAQ;CACR,MAAM;CACN,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE;CAClD,WAAW;EACT,KAAK;GAAE,SAAS,EAAE,oBAAoB,EAAE,QAAQ,oBAAoB,MAAM,EAAE,EAAE;GAAG,aAAa;EAAgB;EAC9G,GAAGA;EACH,GAAG;CACL;AACF,CAAC;AAED,MAAM,qBAAqB,YAAY;CACrC,QAAQ;CACR,MAAM;CACN,SAAS;EACP,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;EACrC,MAAM,EAAE,SAAS,EAAE,oBAAoB,EAAE,QAAQ,oBAAoB,EAAE,EAAE;CAC3E;CACA,WAAW;EACT,KAAK;GAAE,SAAS,EAAE,oBAAoB,EAAE,QAAQ,qBAAqB,EAAE;GAAG,aAAa;EAAkB;EACzG,GAAGA;CACL;AACF,CAAC;AAED,MAAM,qBAAqB,YAAY;CACrC,QAAQ;CACR,MAAM;CACN,SAAS,EAAE,QAAQ,EAAE,OAAO;EAAE,MAAM,EAAE,OAAO;EAAG,WAAW,EAAE,OAAO;CAAE,CAAC,EAAE;CACzE,WAAW;EACT,KAAK,EAAE,aAAa,kBAAkB;EACtC,GAAGA;CACL;AACF,CAAC;AAED,SAAgB,iBAAiB,KAAqB;CACpD,IAAI,QAAQ,mBAAmB,OAAO,MAAM;EAC1C,MAAM,UAAU,MAAM,yBAAyB,GAAG,EAAE,IAAI,MAAM,OAAO,CAAC,CAAC,MAAM,GAAG,WAAW;EAC3F,MAAM,WAAW,MAAM,IAAI,aAAa,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,QAAQ,EAAE;EACtE,OAAO,EAAE,KAAK,SAAS,KAAK,aAAa;GAAE,IAAI,QAAQ;GAAI,KAAK,QAAQ;GAAK,QAAQ,aAAa,SAAS,OAAO;EAAE,EAAE,CAAC;CACzH,CAAC;CAED,IAAI,QAAQ,oBAAoB,OAAO,MAAM;EAC3C,MAAM,UAAU,MAAM,yBAAyB,GAAG,EAAE,IAAI,MAAM,OAAO,CAAC,CAAC,MAAM,GAAG,WAAW;EAC3F,MAAM,OAAO,EAAE,IAAI,MAAM,MAAM;EAC/B,MAAM,SAAS,YAAY,QAAQ,CAAC,CAAC;EACrC,MAAM,UAAU,MAAM,IAAI,aAAa,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,OAAO,QAAQ,IAAI;GAAE,GAAG;GAAM;EAAO,CAAC;EAC5F,OAAO,EAAE,KAAK;GAAE,IAAI,QAAQ;GAAI,KAAK,QAAQ;GAAK,QAAQ,KAAK,UAAU,CAAC;GAAG;EAAO,GAAG,GAAG;CAC5F,CAAC;CAED,IAAI,QAAQ,oBAAoB,OAAO,MAAM;EAC3C,MAAM,EAAE,MAAM,cAAc,EAAE,IAAI,MAAM,OAAO;EAC/C,MAAM,UAAU,MAAM,yBAAyB,GAAG,MAAM,GAAG,WAAW;EACtE,MAAM,UAAU,MAAM,IAAI,aAAa,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,QAAQ,IAAI,SAAS;EAC/E,IAAI,CAAC,SACH,SAAS,mBAAmB;EAE9B,MAAM,IAAI,aAAa,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,OAAO,QAAQ,IAAI,QAAQ,EAAE;EACnE,OAAO,EAAE,KAAK,MAAM,GAAG;CACzB,CAAC;AACH"}
1
+ {"version":3,"file":"webhooks.mjs","names":["notFoundResponse"],"sources":["../../src/routers/webhooks.ts"],"sourcesContent":["import { createRoute, z } from \"@hono/zod-openapi\";\nimport type { ShelfApp } from \"../index.tsx\";\nimport { WebhookModel } from \"../models/webhook.ts\";\nimport { getStore } from \"../store.ts\";\nimport type { ProjectRole } from \"../types.ts\";\nimport { randomToken } from \"../utils/hash.ts\";\nimport { resolveAuthorizedProject, notFound } from \"./helpers.ts\";\nimport {\n notFound as notFoundResponse,\n unauthorized,\n webhookCreatedSchema,\n webhookCreateSchema,\n webhookPublicSchema,\n} from \"./schemas.ts\";\n\nconst ADMIN_ROLES: readonly ProjectRole[] = [\"admin\"];\n\nconst listWebhooksRoute = createRoute({\n method: \"get\",\n path: \"/api/v1/projects/{slug}/webhooks\",\n request: { params: z.object({ slug: z.string() }) },\n responses: {\n 200: {\n content: { \"application/json\": { schema: webhookPublicSchema.array() } },\n description: \"List webhooks\",\n },\n ...notFoundResponse,\n ...unauthorized,\n },\n});\n\nconst createWebhookRoute = createRoute({\n method: \"post\",\n path: \"/api/v1/projects/{slug}/webhooks\",\n request: {\n params: z.object({ slug: z.string() }),\n body: { content: { \"application/json\": { schema: webhookCreateSchema } } },\n },\n responses: {\n 201: {\n content: { \"application/json\": { schema: webhookCreatedSchema } },\n description: \"Created webhook\",\n },\n ...notFoundResponse,\n },\n});\n\nconst deleteWebhookRoute = createRoute({\n method: \"delete\",\n path: \"/api/v1/projects/{slug}/webhooks/{webhookId}\",\n request: { params: z.object({ slug: z.string(), webhookId: z.string() }) },\n responses: {\n 204: { description: \"Deleted webhook\" },\n ...notFoundResponse,\n },\n});\n\n/** Register the project webhook list, create, and delete endpoints. */\nexport function registerWebhooks(app: ShelfApp): void {\n app.openapi(listWebhooksRoute, async (c) => {\n const project = await resolveAuthorizedProject(c, c.req.valid(\"param\").slug, ...ADMIN_ROLES);\n const webhooks = await new WebhookModel(getStore().db, getStore().config.secret).list(project.id);\n return c.json(\n webhooks.map((webhook) => ({\n id: webhook.id,\n url: webhook.url,\n events: WebhookModel.eventsOf(webhook),\n })),\n );\n });\n\n app.openapi(createWebhookRoute, async (c) => {\n const project = await resolveAuthorizedProject(c, c.req.valid(\"param\").slug, ...ADMIN_ROLES);\n const body = c.req.valid(\"json\");\n const secret = randomToken(\"whsec_\").value;\n const webhook = await new WebhookModel(getStore().db, getStore().config.secret).create(project.id, { ...body, secret });\n return c.json({ id: webhook.id, url: webhook.url, events: body.events ?? [], secret }, 201);\n });\n\n app.openapi(deleteWebhookRoute, async (c) => {\n const { slug, webhookId } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...ADMIN_ROLES);\n const webhook = await new WebhookModel(getStore().db, getStore().config.secret).get(project.id, webhookId);\n if (!webhook) {\n notFound(\"Webhook not found\");\n }\n await new WebhookModel(getStore().db, getStore().config.secret).remove(project.id, webhook.id);\n return c.body(null, 204);\n });\n}\n"],"mappings":";;;;;;;;;;AAeA,MAAM,cAAsC,CAAC,OAAO;AAEpD,MAAM,oBAAoB,YAAY;CACpC,QAAQ;CACR,MAAM;CACN,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE;CAClD,WAAW;EACT,KAAK;GACH,SAAS,EAAE,oBAAoB,EAAE,QAAQ,oBAAoB,MAAM,EAAE,EAAE;GACvE,aAAa;EACf;EACA,GAAGA;EACH,GAAG;CACL;AACF,CAAC;AAED,MAAM,qBAAqB,YAAY;CACrC,QAAQ;CACR,MAAM;CACN,SAAS;EACP,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;EACrC,MAAM,EAAE,SAAS,EAAE,oBAAoB,EAAE,QAAQ,oBAAoB,EAAE,EAAE;CAC3E;CACA,WAAW;EACT,KAAK;GACH,SAAS,EAAE,oBAAoB,EAAE,QAAQ,qBAAqB,EAAE;GAChE,aAAa;EACf;EACA,GAAGA;CACL;AACF,CAAC;AAED,MAAM,qBAAqB,YAAY;CACrC,QAAQ;CACR,MAAM;CACN,SAAS,EAAE,QAAQ,EAAE,OAAO;EAAE,MAAM,EAAE,OAAO;EAAG,WAAW,EAAE,OAAO;CAAE,CAAC,EAAE;CACzE,WAAW;EACT,KAAK,EAAE,aAAa,kBAAkB;EACtC,GAAGA;CACL;AACF,CAAC;;AAGD,SAAgB,iBAAiB,KAAqB;CACpD,IAAI,QAAQ,mBAAmB,OAAO,MAAM;EAC1C,MAAM,UAAU,MAAM,yBAAyB,GAAG,EAAE,IAAI,MAAM,OAAO,CAAC,CAAC,MAAM,GAAG,WAAW;EAC3F,MAAM,WAAW,MAAM,IAAI,aAAa,SAAS,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC,OAAO,MAAM,CAAC,CAAC,KAAK,QAAQ,EAAE;EAChG,OAAO,EAAE,KACP,SAAS,KAAK,aAAa;GACzB,IAAI,QAAQ;GACZ,KAAK,QAAQ;GACb,QAAQ,aAAa,SAAS,OAAO;EACvC,EAAE,CACJ;CACF,CAAC;CAED,IAAI,QAAQ,oBAAoB,OAAO,MAAM;EAC3C,MAAM,UAAU,MAAM,yBAAyB,GAAG,EAAE,IAAI,MAAM,OAAO,CAAC,CAAC,MAAM,GAAG,WAAW;EAC3F,MAAM,OAAO,EAAE,IAAI,MAAM,MAAM;EAC/B,MAAM,SAAS,YAAY,QAAQ,CAAC,CAAC;EACrC,MAAM,UAAU,MAAM,IAAI,aAAa,SAAS,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC,OAAO,MAAM,CAAC,CAAC,OAAO,QAAQ,IAAI;GAAE,GAAG;GAAM;EAAO,CAAC;EACtH,OAAO,EAAE,KAAK;GAAE,IAAI,QAAQ;GAAI,KAAK,QAAQ;GAAK,QAAQ,KAAK,UAAU,CAAC;GAAG;EAAO,GAAG,GAAG;CAC5F,CAAC;CAED,IAAI,QAAQ,oBAAoB,OAAO,MAAM;EAC3C,MAAM,EAAE,MAAM,cAAc,EAAE,IAAI,MAAM,OAAO;EAC/C,MAAM,UAAU,MAAM,yBAAyB,GAAG,MAAM,GAAG,WAAW;EACtE,MAAM,UAAU,MAAM,IAAI,aAAa,SAAS,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC,OAAO,MAAM,CAAC,CAAC,IAAI,QAAQ,IAAI,SAAS;EACzG,IAAI,CAAC,SACH,SAAS,mBAAmB;EAE9B,MAAM,IAAI,aAAa,SAAS,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC,OAAO,MAAM,CAAC,CAAC,OAAO,QAAQ,IAAI,QAAQ,EAAE;EAC7F,OAAO,EAAE,KAAK,MAAM,GAAG;CACzB,CAAC;AACH"}
@@ -0,0 +1,18 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ //#region src/schema/baseline.d.ts
4
+ /** A baseline row. */
5
+ interface Baseline {
6
+ id: string;
7
+ projectId: string;
8
+ storyId: string;
9
+ viewportName: string;
10
+ branch: string;
11
+ snapshotId: string | null;
12
+ screenshotPath: string;
13
+ createdAt: string;
14
+ updatedAt: string;
15
+ }
16
+ //#endregion
17
+ export { Baseline };
18
+ //# sourceMappingURL=baseline.d.mts.map
@@ -0,0 +1,22 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { projects } from "./project.mjs";
5
+ import { index, sqliteTable, text, uniqueIndex } from "drizzle-orm/sqlite-core";
6
+ //#region src/schema/baseline.ts
7
+ /** Narrow `baselines` table definition. */
8
+ const baselines = sqliteTable("baselines", {
9
+ id: text("id").primaryKey(),
10
+ projectId: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
11
+ storyId: text("story_id").notNull(),
12
+ viewportName: text("viewport_name").notNull().default("desktop"),
13
+ branch: text("branch").notNull(),
14
+ snapshotId: text("snapshot_id"),
15
+ screenshotPath: text("screenshot_path").notNull(),
16
+ createdAt: text("created_at").notNull(),
17
+ updatedAt: text("updated_at").notNull()
18
+ }, (t) => [uniqueIndex("baselines_project_story_viewport_branch_idx").on(t.projectId, t.storyId, t.viewportName, t.branch), index("baselines_project_story_idx").on(t.projectId, t.storyId)]);
19
+ //#endregion
20
+ export { baselines };
21
+
22
+ //# sourceMappingURL=baseline.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"baseline.mjs","names":[],"sources":["../../src/schema/baseline.ts"],"sourcesContent":["import { index, sqliteTable, text, uniqueIndex } from \"drizzle-orm/sqlite-core\";\nimport { projects } from \"./project.ts\";\n\n/** Narrow `baselines` table definition. */\nexport const baselines = sqliteTable(\n \"baselines\",\n {\n id: text(\"id\").primaryKey(),\n projectId: text(\"project_id\")\n .notNull()\n .references(() => projects.id, { onDelete: \"cascade\" }),\n storyId: text(\"story_id\").notNull(),\n viewportName: text(\"viewport_name\").notNull().default(\"desktop\"),\n branch: text(\"branch\").notNull(),\n snapshotId: text(\"snapshot_id\"),\n screenshotPath: text(\"screenshot_path\").notNull(),\n createdAt: text(\"created_at\").notNull(),\n updatedAt: text(\"updated_at\").notNull(),\n },\n (t) => [\n uniqueIndex(\"baselines_project_story_viewport_branch_idx\").on(\n t.projectId,\n t.storyId,\n t.viewportName,\n t.branch,\n ),\n index(\"baselines_project_story_idx\").on(t.projectId, t.storyId),\n ],\n);\n\n/** A baseline row. */\nexport interface Baseline {\n id: string;\n projectId: string;\n storyId: string;\n viewportName: string;\n branch: string;\n snapshotId: string | null;\n screenshotPath: string;\n createdAt: string;\n updatedAt: string;\n}\n"],"mappings":";;;;;;;AAIA,MAAa,YAAY,YACvB,aACA;CACE,IAAI,KAAK,IAAI,CAAC,CAAC,WAAW;CAC1B,WAAW,KAAK,YAAY,CAAC,CAC1B,QAAQ,CAAC,CACT,iBAAiB,SAAS,IAAI,EAAE,UAAU,UAAU,CAAC;CACxD,SAAS,KAAK,UAAU,CAAC,CAAC,QAAQ;CAClC,cAAc,KAAK,eAAe,CAAC,CAAC,QAAQ,CAAC,CAAC,QAAQ,SAAS;CAC/D,QAAQ,KAAK,QAAQ,CAAC,CAAC,QAAQ;CAC/B,YAAY,KAAK,aAAa;CAC9B,gBAAgB,KAAK,iBAAiB,CAAC,CAAC,QAAQ;CAChD,WAAW,KAAK,YAAY,CAAC,CAAC,QAAQ;CACtC,WAAW,KAAK,YAAY,CAAC,CAAC,QAAQ;AACxC,IACC,MAAM,CACL,YAAY,6CAA6C,CAAC,CAAC,GACzD,EAAE,WACF,EAAE,SACF,EAAE,cACF,EAAE,MACJ,GACA,MAAM,6BAA6B,CAAC,CAAC,GAAG,EAAE,WAAW,EAAE,OAAO,CAChE,CACF"}
@@ -0,0 +1,26 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ import { BuildStatus } from "../types.mjs";
4
+ //#region src/schema/build.d.ts
5
+ /** A build row. */
6
+ interface Build {
7
+ id: string;
8
+ projectId: string;
9
+ gitSha: string;
10
+ gitBranch: string;
11
+ isDefault: boolean;
12
+ authorEmail: string | null;
13
+ authorName: string | null;
14
+ message: string | null;
15
+ public: boolean;
16
+ status: BuildStatus;
17
+ snapshotCount: number;
18
+ changedCount: number;
19
+ approvedCount: number;
20
+ rejectedCount: number;
21
+ createdAt: string;
22
+ updatedAt: string;
23
+ }
24
+ //#endregion
25
+ export { Build };
26
+ //# sourceMappingURL=build.d.mts.map
@@ -0,0 +1,29 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { projects } from "./project.mjs";
5
+ import { index, integer, sqliteTable, text, uniqueIndex } from "drizzle-orm/sqlite-core";
6
+ //#region src/schema/build.ts
7
+ /** Narrow `builds` table definition. */
8
+ const builds = sqliteTable("builds", {
9
+ id: text("id").primaryKey(),
10
+ projectId: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
11
+ gitSha: text("git_sha").notNull(),
12
+ gitBranch: text("git_branch").notNull(),
13
+ isDefault: integer("is_default", { mode: "boolean" }).notNull().default(false),
14
+ authorEmail: text("author_email"),
15
+ authorName: text("author_name"),
16
+ message: text("message"),
17
+ public: integer("public", { mode: "boolean" }).notNull().default(false),
18
+ status: text("status").$type().notNull().default("pending"),
19
+ snapshotCount: integer("snapshot_count").notNull().default(0),
20
+ changedCount: integer("changed_count").notNull().default(0),
21
+ approvedCount: integer("approved_count").notNull().default(0),
22
+ rejectedCount: integer("rejected_count").notNull().default(0),
23
+ createdAt: text("created_at").notNull(),
24
+ updatedAt: text("updated_at").notNull()
25
+ }, (t) => [uniqueIndex("builds_project_gitsha_idx").on(t.projectId, t.gitSha), index("builds_git_branch_idx").on(t.gitBranch)]);
26
+ //#endregion
27
+ export { builds };
28
+
29
+ //# sourceMappingURL=build.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.mjs","names":[],"sources":["../../src/schema/build.ts"],"sourcesContent":["import { index, integer, sqliteTable, text, uniqueIndex } from \"drizzle-orm/sqlite-core\";\nimport type { BuildStatus } from \"../types.ts\";\nimport { projects } from \"./project.ts\";\n\n/** Narrow `builds` table definition. */\nexport const builds = sqliteTable(\n \"builds\",\n {\n id: text(\"id\").primaryKey(),\n projectId: text(\"project_id\")\n .notNull()\n .references(() => projects.id, { onDelete: \"cascade\" }),\n gitSha: text(\"git_sha\").notNull(),\n gitBranch: text(\"git_branch\").notNull(),\n isDefault: integer(\"is_default\", { mode: \"boolean\" }).notNull().default(false),\n authorEmail: text(\"author_email\"),\n authorName: text(\"author_name\"),\n message: text(\"message\"),\n public: integer(\"public\", { mode: \"boolean\" }).notNull().default(false),\n status: text(\"status\").$type<BuildStatus>().notNull().default(\"pending\"),\n snapshotCount: integer(\"snapshot_count\").notNull().default(0),\n changedCount: integer(\"changed_count\").notNull().default(0),\n approvedCount: integer(\"approved_count\").notNull().default(0),\n rejectedCount: integer(\"rejected_count\").notNull().default(0),\n createdAt: text(\"created_at\").notNull(),\n updatedAt: text(\"updated_at\").notNull(),\n },\n (t) => [\n uniqueIndex(\"builds_project_gitsha_idx\").on(t.projectId, t.gitSha),\n index(\"builds_git_branch_idx\").on(t.gitBranch),\n ],\n);\n\n/** A build row. */\nexport interface Build {\n id: string;\n projectId: string;\n gitSha: string;\n gitBranch: string;\n isDefault: boolean;\n authorEmail: string | null;\n authorName: string | null;\n message: string | null;\n public: boolean;\n status: BuildStatus;\n snapshotCount: number;\n changedCount: number;\n approvedCount: number;\n rejectedCount: number;\n createdAt: string;\n updatedAt: string;\n}\n"],"mappings":";;;;;;;AAKA,MAAa,SAAS,YACpB,UACA;CACE,IAAI,KAAK,IAAI,CAAC,CAAC,WAAW;CAC1B,WAAW,KAAK,YAAY,CAAC,CAC1B,QAAQ,CAAC,CACT,iBAAiB,SAAS,IAAI,EAAE,UAAU,UAAU,CAAC;CACxD,QAAQ,KAAK,SAAS,CAAC,CAAC,QAAQ;CAChC,WAAW,KAAK,YAAY,CAAC,CAAC,QAAQ;CACtC,WAAW,QAAQ,cAAc,EAAE,MAAM,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,QAAQ,KAAK;CAC7E,aAAa,KAAK,cAAc;CAChC,YAAY,KAAK,aAAa;CAC9B,SAAS,KAAK,SAAS;CACvB,QAAQ,QAAQ,UAAU,EAAE,MAAM,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,QAAQ,KAAK;CACtE,QAAQ,KAAK,QAAQ,CAAC,CAAC,MAAmB,CAAC,CAAC,QAAQ,CAAC,CAAC,QAAQ,SAAS;CACvE,eAAe,QAAQ,gBAAgB,CAAC,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC;CAC5D,cAAc,QAAQ,eAAe,CAAC,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC;CAC1D,eAAe,QAAQ,gBAAgB,CAAC,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC;CAC5D,eAAe,QAAQ,gBAAgB,CAAC,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC;CAC5D,WAAW,KAAK,YAAY,CAAC,CAAC,QAAQ;CACtC,WAAW,KAAK,YAAY,CAAC,CAAC,QAAQ;AACxC,IACC,MAAM,CACL,YAAY,2BAA2B,CAAC,CAAC,GAAG,EAAE,WAAW,EAAE,MAAM,GACjE,MAAM,uBAAuB,CAAC,CAAC,GAAG,EAAE,SAAS,CAC/C,CACF"}
@@ -0,0 +1,19 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ //#region src/schema/comment.d.ts
4
+ /** A comment row. */
5
+ interface Comment {
6
+ id: string;
7
+ projectId: string;
8
+ buildId: string;
9
+ snapshotId: string | null;
10
+ userId: string;
11
+ body: string;
12
+ parentId: string | null;
13
+ resolved: boolean;
14
+ createdAt: string;
15
+ updatedAt: string;
16
+ }
17
+ //#endregion
18
+ export { Comment };
19
+ //# sourceMappingURL=comment.d.mts.map
@@ -0,0 +1,26 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { projects } from "./project.mjs";
5
+ import { builds } from "./build.mjs";
6
+ import { snapshots } from "./snapshot.mjs";
7
+ import { users } from "./user.mjs";
8
+ import { index, integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
9
+ //#region src/schema/comment.ts
10
+ /** Narrow `comments` table definition. */
11
+ const comments = sqliteTable("comments", {
12
+ id: text("id").primaryKey(),
13
+ projectId: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
14
+ buildId: text("build_id").notNull().references(() => builds.id, { onDelete: "cascade" }),
15
+ snapshotId: text("snapshot_id").references(() => snapshots.id, { onDelete: "cascade" }),
16
+ userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
17
+ body: text("body").notNull(),
18
+ parentId: text("parent_id"),
19
+ resolved: integer("resolved", { mode: "boolean" }).notNull().default(false),
20
+ createdAt: text("created_at").notNull(),
21
+ updatedAt: text("updated_at").notNull()
22
+ }, (t) => [index("comments_build_id_idx").on(t.buildId)]);
23
+ //#endregion
24
+ export { comments };
25
+
26
+ //# sourceMappingURL=comment.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comment.mjs","names":[],"sources":["../../src/schema/comment.ts"],"sourcesContent":["import { index, integer, sqliteTable, text } from \"drizzle-orm/sqlite-core\";\nimport { builds } from \"./build.ts\";\nimport { projects } from \"./project.ts\";\nimport { snapshots } from \"./snapshot.ts\";\nimport { users } from \"./user.ts\";\n\n/** Narrow `comments` table definition. */\nexport const comments = sqliteTable(\n \"comments\",\n {\n id: text(\"id\").primaryKey(),\n projectId: text(\"project_id\")\n .notNull()\n .references(() => projects.id, { onDelete: \"cascade\" }),\n buildId: text(\"build_id\")\n .notNull()\n .references(() => builds.id, { onDelete: \"cascade\" }),\n snapshotId: text(\"snapshot_id\").references(() => snapshots.id, { onDelete: \"cascade\" }),\n userId: text(\"user_id\")\n .notNull()\n .references(() => users.id, { onDelete: \"cascade\" }),\n body: text(\"body\").notNull(),\n parentId: text(\"parent_id\"),\n resolved: integer(\"resolved\", { mode: \"boolean\" }).notNull().default(false),\n createdAt: text(\"created_at\").notNull(),\n updatedAt: text(\"updated_at\").notNull(),\n },\n (t) => [index(\"comments_build_id_idx\").on(t.buildId)],\n);\n\n/** A comment row. */\nexport interface Comment {\n id: string;\n projectId: string;\n buildId: string;\n snapshotId: string | null;\n userId: string;\n body: string;\n parentId: string | null;\n resolved: boolean;\n createdAt: string;\n updatedAt: string;\n}\n"],"mappings":";;;;;;;;;;AAOA,MAAa,WAAW,YACtB,YACA;CACE,IAAI,KAAK,IAAI,CAAC,CAAC,WAAW;CAC1B,WAAW,KAAK,YAAY,CAAC,CAC1B,QAAQ,CAAC,CACT,iBAAiB,SAAS,IAAI,EAAE,UAAU,UAAU,CAAC;CACxD,SAAS,KAAK,UAAU,CAAC,CACtB,QAAQ,CAAC,CACT,iBAAiB,OAAO,IAAI,EAAE,UAAU,UAAU,CAAC;CACtD,YAAY,KAAK,aAAa,CAAC,CAAC,iBAAiB,UAAU,IAAI,EAAE,UAAU,UAAU,CAAC;CACtF,QAAQ,KAAK,SAAS,CAAC,CACpB,QAAQ,CAAC,CACT,iBAAiB,MAAM,IAAI,EAAE,UAAU,UAAU,CAAC;CACrD,MAAM,KAAK,MAAM,CAAC,CAAC,QAAQ;CAC3B,UAAU,KAAK,WAAW;CAC1B,UAAU,QAAQ,YAAY,EAAE,MAAM,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,QAAQ,KAAK;CAC1E,WAAW,KAAK,YAAY,CAAC,CAAC,QAAQ;CACtC,WAAW,KAAK,YAAY,CAAC,CAAC,QAAQ;AACxC,IACC,MAAM,CAAC,MAAM,uBAAuB,CAAC,CAAC,GAAG,EAAE,OAAO,CAAC,CACtD"}
@@ -0,0 +1,25 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ //#region src/schema/label.d.ts
4
+ /** A label-type row. */
5
+ interface LabelType {
6
+ id: string;
7
+ projectId: string;
8
+ key: string;
9
+ name: string;
10
+ linkTemplate: string | null;
11
+ color: string | null;
12
+ createdAt: string;
13
+ }
14
+ /** A build-label row. */
15
+ interface BuildLabel {
16
+ id: string;
17
+ projectId: string;
18
+ buildId: string;
19
+ typeKey: string;
20
+ value: string;
21
+ createdAt: string;
22
+ }
23
+ //#endregion
24
+ export { BuildLabel, LabelType };
25
+ //# sourceMappingURL=label.d.mts.map
@@ -0,0 +1,30 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { projects } from "./project.mjs";
5
+ import { builds } from "./build.mjs";
6
+ import { sqliteTable, text, uniqueIndex } from "drizzle-orm/sqlite-core";
7
+ //#region src/schema/label.ts
8
+ /** Narrow `label_types` table definition. */
9
+ const labelTypes = sqliteTable("label_types", {
10
+ id: text("id").primaryKey(),
11
+ projectId: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
12
+ key: text("key").notNull(),
13
+ name: text("name").notNull(),
14
+ linkTemplate: text("link_template"),
15
+ color: text("color"),
16
+ createdAt: text("created_at").notNull()
17
+ }, (t) => [uniqueIndex("label_types_project_key_idx").on(t.projectId, t.key)]);
18
+ /** Narrow `build_labels` table definition. */
19
+ const buildLabels = sqliteTable("build_labels", {
20
+ id: text("id").primaryKey(),
21
+ projectId: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
22
+ buildId: text("build_id").notNull().references(() => builds.id, { onDelete: "cascade" }),
23
+ typeKey: text("type_key").notNull(),
24
+ value: text("value").notNull(),
25
+ createdAt: text("created_at").notNull()
26
+ }, (t) => [uniqueIndex("build_labels_build_type_value_idx").on(t.buildId, t.typeKey, t.value)]);
27
+ //#endregion
28
+ export { buildLabels, labelTypes };
29
+
30
+ //# sourceMappingURL=label.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"label.mjs","names":[],"sources":["../../src/schema/label.ts"],"sourcesContent":["import { sqliteTable, text, uniqueIndex } from \"drizzle-orm/sqlite-core\";\nimport { builds } from \"./build.ts\";\nimport { projects } from \"./project.ts\";\n\n/** Narrow `label_types` table definition. */\nexport const labelTypes = sqliteTable(\n \"label_types\",\n {\n id: text(\"id\").primaryKey(),\n projectId: text(\"project_id\")\n .notNull()\n .references(() => projects.id, { onDelete: \"cascade\" }),\n key: text(\"key\").notNull(),\n name: text(\"name\").notNull(),\n linkTemplate: text(\"link_template\"),\n color: text(\"color\"),\n createdAt: text(\"created_at\").notNull(),\n },\n (t) => [uniqueIndex(\"label_types_project_key_idx\").on(t.projectId, t.key)],\n);\n\n/** Narrow `build_labels` table definition. */\nexport const buildLabels = sqliteTable(\n \"build_labels\",\n {\n id: text(\"id\").primaryKey(),\n projectId: text(\"project_id\")\n .notNull()\n .references(() => projects.id, { onDelete: \"cascade\" }),\n buildId: text(\"build_id\")\n .notNull()\n .references(() => builds.id, { onDelete: \"cascade\" }),\n typeKey: text(\"type_key\").notNull(),\n value: text(\"value\").notNull(),\n createdAt: text(\"created_at\").notNull(),\n },\n (t) => [uniqueIndex(\"build_labels_build_type_value_idx\").on(t.buildId, t.typeKey, t.value)],\n);\n\n/** A label-type row. */\nexport interface LabelType {\n id: string;\n projectId: string;\n key: string;\n name: string;\n linkTemplate: string | null;\n color: string | null;\n createdAt: string;\n}\n\n/** A build-label row. */\nexport interface BuildLabel {\n id: string;\n projectId: string;\n buildId: string;\n typeKey: string;\n value: string;\n createdAt: string;\n}\n"],"mappings":";;;;;;;;AAKA,MAAa,aAAa,YACxB,eACA;CACE,IAAI,KAAK,IAAI,CAAC,CAAC,WAAW;CAC1B,WAAW,KAAK,YAAY,CAAC,CAC1B,QAAQ,CAAC,CACT,iBAAiB,SAAS,IAAI,EAAE,UAAU,UAAU,CAAC;CACxD,KAAK,KAAK,KAAK,CAAC,CAAC,QAAQ;CACzB,MAAM,KAAK,MAAM,CAAC,CAAC,QAAQ;CAC3B,cAAc,KAAK,eAAe;CAClC,OAAO,KAAK,OAAO;CACnB,WAAW,KAAK,YAAY,CAAC,CAAC,QAAQ;AACxC,IACC,MAAM,CAAC,YAAY,6BAA6B,CAAC,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,CAAC,CAC3E;;AAGA,MAAa,cAAc,YACzB,gBACA;CACE,IAAI,KAAK,IAAI,CAAC,CAAC,WAAW;CAC1B,WAAW,KAAK,YAAY,CAAC,CAC1B,QAAQ,CAAC,CACT,iBAAiB,SAAS,IAAI,EAAE,UAAU,UAAU,CAAC;CACxD,SAAS,KAAK,UAAU,CAAC,CACtB,QAAQ,CAAC,CACT,iBAAiB,OAAO,IAAI,EAAE,UAAU,UAAU,CAAC;CACtD,SAAS,KAAK,UAAU,CAAC,CAAC,QAAQ;CAClC,OAAO,KAAK,OAAO,CAAC,CAAC,QAAQ;CAC7B,WAAW,KAAK,YAAY,CAAC,CAAC,QAAQ;AACxC,IACC,MAAM,CAAC,YAAY,mCAAmC,CAAC,CAAC,GAAG,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,CAAC,CAC5F"}
@@ -0,0 +1,15 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ import { ProjectRole } from "../types.mjs";
4
+ //#region src/schema/member.d.ts
5
+ /** A project-membership row. */
6
+ interface ProjectMember {
7
+ id: string;
8
+ projectId: string;
9
+ userId: string;
10
+ role: ProjectRole;
11
+ createdAt: string;
12
+ }
13
+ //#endregion
14
+ export { ProjectMember };
15
+ //# sourceMappingURL=member.d.mts.map
@@ -0,0 +1,19 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { projects } from "./project.mjs";
5
+ import { users } from "./user.mjs";
6
+ import { sqliteTable, text, uniqueIndex } from "drizzle-orm/sqlite-core";
7
+ //#region src/schema/member.ts
8
+ /** Narrow `project_members` table definition. */
9
+ const projectMembers = sqliteTable("project_members", {
10
+ id: text("id").primaryKey(),
11
+ projectId: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
12
+ userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
13
+ role: text("role").$type().notNull().default("viewer"),
14
+ createdAt: text("created_at").notNull()
15
+ }, (t) => [uniqueIndex("project_members_project_user_idx").on(t.projectId, t.userId)]);
16
+ //#endregion
17
+ export { projectMembers };
18
+
19
+ //# sourceMappingURL=member.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"member.mjs","names":[],"sources":["../../src/schema/member.ts"],"sourcesContent":["import { sqliteTable, text, uniqueIndex } from \"drizzle-orm/sqlite-core\";\nimport type { ProjectRole } from \"../types.ts\";\nimport { projects } from \"./project.ts\";\nimport { users } from \"./user.ts\";\n\n/** Narrow `project_members` table definition. */\nexport const projectMembers = sqliteTable(\n \"project_members\",\n {\n id: text(\"id\").primaryKey(),\n projectId: text(\"project_id\")\n .notNull()\n .references(() => projects.id, { onDelete: \"cascade\" }),\n userId: text(\"user_id\")\n .notNull()\n .references(() => users.id, { onDelete: \"cascade\" }),\n role: text(\"role\").$type<ProjectRole>().notNull().default(\"viewer\"),\n createdAt: text(\"created_at\").notNull(),\n },\n (t) => [uniqueIndex(\"project_members_project_user_idx\").on(t.projectId, t.userId)],\n);\n\n/** A project-membership row. */\nexport interface ProjectMember {\n id: string;\n projectId: string;\n userId: string;\n role: ProjectRole;\n createdAt: string;\n}\n"],"mappings":";;;;;;;;AAMA,MAAa,iBAAiB,YAC5B,mBACA;CACE,IAAI,KAAK,IAAI,CAAC,CAAC,WAAW;CAC1B,WAAW,KAAK,YAAY,CAAC,CAC1B,QAAQ,CAAC,CACT,iBAAiB,SAAS,IAAI,EAAE,UAAU,UAAU,CAAC;CACxD,QAAQ,KAAK,SAAS,CAAC,CACpB,QAAQ,CAAC,CACT,iBAAiB,MAAM,IAAI,EAAE,UAAU,UAAU,CAAC;CACrD,MAAM,KAAK,MAAM,CAAC,CAAC,MAAmB,CAAC,CAAC,QAAQ,CAAC,CAAC,QAAQ,QAAQ;CAClE,WAAW,KAAK,YAAY,CAAC,CAAC,QAAQ;AACxC,IACC,MAAM,CAAC,YAAY,kCAAkC,CAAC,CAAC,GAAG,EAAE,WAAW,EAAE,MAAM,CAAC,CACnF"}
@@ -0,0 +1,34 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ //#region src/schema/project.d.ts
4
+ /** A project row. */
5
+ interface Project {
6
+ id: string;
7
+ name: string;
8
+ slug: string;
9
+ gitRepository: string | null;
10
+ gitDefaultBranch: string;
11
+ pixelThreshold: number;
12
+ maxDiffRatio: number;
13
+ publicBranchRegex: string | null;
14
+ storybookMeta: string | null | undefined;
15
+ executePlay: boolean;
16
+ playTimeoutMs: number;
17
+ createdAt: string;
18
+ updatedAt: string;
19
+ }
20
+ /** Storybook metadata synced from a project's published Storybook. */
21
+ interface StorybookMeta {
22
+ framework?: {
23
+ name?: string;
24
+ options?: unknown;
25
+ };
26
+ addons?: string[];
27
+ storiesGlobs?: string[];
28
+ staticDirs?: string[];
29
+ packagePath?: string;
30
+ previewParameters?: Record<string, unknown>;
31
+ }
32
+ //#endregion
33
+ export { Project, StorybookMeta };
34
+ //# sourceMappingURL=project.d.mts.map
@@ -0,0 +1,25 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { integer, real, sqliteTable, text } from "drizzle-orm/sqlite-core";
5
+ //#region src/schema/project.ts
6
+ /** Narrow `projects` table definition. */
7
+ const projects = sqliteTable("projects", {
8
+ id: text("id").primaryKey(),
9
+ name: text("name").notNull(),
10
+ slug: text("slug").notNull(),
11
+ gitRepository: text("git_repository"),
12
+ gitDefaultBranch: text("git_default_branch").notNull().default("main"),
13
+ pixelThreshold: real("pixel_threshold").notNull().default(.1),
14
+ maxDiffRatio: real("max_diff_ratio").notNull().default(.01),
15
+ publicBranchRegex: text("public_branch_regex"),
16
+ storybookMeta: text("storybook_meta").$type().default(null),
17
+ executePlay: integer("execute_play", { mode: "boolean" }).notNull().default(false),
18
+ playTimeoutMs: integer("play_timeout_ms").notNull().default(1e4),
19
+ createdAt: text("created_at").notNull(),
20
+ updatedAt: text("updated_at").notNull()
21
+ });
22
+ //#endregion
23
+ export { projects };
24
+
25
+ //# sourceMappingURL=project.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project.mjs","names":[],"sources":["../../src/schema/project.ts"],"sourcesContent":["import { integer, real, sqliteTable, text } from \"drizzle-orm/sqlite-core\";\n\n/** Narrow `projects` table definition. */\nexport const projects = sqliteTable(\"projects\", {\n id: text(\"id\").primaryKey(),\n name: text(\"name\").notNull(),\n slug: text(\"slug\").notNull(),\n gitRepository: text(\"git_repository\"),\n gitDefaultBranch: text(\"git_default_branch\").notNull().default(\"main\"),\n pixelThreshold: real(\"pixel_threshold\").notNull().default(0.1),\n maxDiffRatio: real(\"max_diff_ratio\").notNull().default(0.01),\n publicBranchRegex: text(\"public_branch_regex\"),\n storybookMeta: text(\"storybook_meta\").$type<string | null | undefined>().default(null),\n executePlay: integer(\"execute_play\", { mode: \"boolean\" }).notNull().default(false),\n playTimeoutMs: integer(\"play_timeout_ms\").notNull().default(10_000),\n createdAt: text(\"created_at\").notNull(),\n updatedAt: text(\"updated_at\").notNull(),\n});\n\n/** A project row. */\nexport interface Project {\n id: string;\n name: string;\n slug: string;\n gitRepository: string | null;\n gitDefaultBranch: string;\n pixelThreshold: number;\n maxDiffRatio: number;\n publicBranchRegex: string | null;\n storybookMeta: string | null | undefined;\n executePlay: boolean;\n playTimeoutMs: number;\n createdAt: string;\n updatedAt: string;\n}\n\n/** Storybook metadata synced from a project's published Storybook. */\nexport interface StorybookMeta {\n framework?: { name?: string; options?: unknown };\n addons?: string[];\n storiesGlobs?: string[];\n staticDirs?: string[];\n packagePath?: string;\n previewParameters?: Record<string, unknown>;\n}\n"],"mappings":";;;;;;AAGA,MAAa,WAAW,YAAY,YAAY;CAC9C,IAAI,KAAK,IAAI,CAAC,CAAC,WAAW;CAC1B,MAAM,KAAK,MAAM,CAAC,CAAC,QAAQ;CAC3B,MAAM,KAAK,MAAM,CAAC,CAAC,QAAQ;CAC3B,eAAe,KAAK,gBAAgB;CACpC,kBAAkB,KAAK,oBAAoB,CAAC,CAAC,QAAQ,CAAC,CAAC,QAAQ,MAAM;CACrE,gBAAgB,KAAK,iBAAiB,CAAC,CAAC,QAAQ,CAAC,CAAC,QAAQ,EAAG;CAC7D,cAAc,KAAK,gBAAgB,CAAC,CAAC,QAAQ,CAAC,CAAC,QAAQ,GAAI;CAC3D,mBAAmB,KAAK,qBAAqB;CAC7C,eAAe,KAAK,gBAAgB,CAAC,CAAC,MAAiC,CAAC,CAAC,QAAQ,IAAI;CACrF,aAAa,QAAQ,gBAAgB,EAAE,MAAM,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,QAAQ,KAAK;CACjF,eAAe,QAAQ,iBAAiB,CAAC,CAAC,QAAQ,CAAC,CAAC,QAAQ,GAAM;CAClE,WAAW,KAAK,YAAY,CAAC,CAAC,QAAQ;CACtC,WAAW,KAAK,YAAY,CAAC,CAAC,QAAQ;AACxC,CAAC"}
@@ -0,0 +1,30 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ import { SnapshotStatus } from "../types.mjs";
4
+ //#region src/schema/snapshot.d.ts
5
+ /** A snapshot row. */
6
+ interface Snapshot {
7
+ id: string;
8
+ projectId: string;
9
+ buildId: string;
10
+ storyId: string;
11
+ storyName: string;
12
+ storyTitle: string;
13
+ storyImportPath: string | null;
14
+ viewportName: string;
15
+ viewportWidth: number;
16
+ viewportHeight: number;
17
+ screenshotPath: string;
18
+ diffPath: string | null;
19
+ diffPixels: number | null;
20
+ diffRatio: number | null;
21
+ diffPassed: boolean | null;
22
+ status: SnapshotStatus;
23
+ reviewedBy: string | null;
24
+ reviewedAt: string | null;
25
+ createdAt: string;
26
+ updatedAt: string;
27
+ }
28
+ //#endregion
29
+ export { Snapshot };
30
+ //# sourceMappingURL=snapshot.d.mts.map
@@ -0,0 +1,34 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { projects } from "./project.mjs";
5
+ import { builds } from "./build.mjs";
6
+ import { index, integer, real, sqliteTable, text, uniqueIndex } from "drizzle-orm/sqlite-core";
7
+ //#region src/schema/snapshot.ts
8
+ /** Narrow `snapshots` table definition. */
9
+ const snapshots = sqliteTable("snapshots", {
10
+ id: text("id").primaryKey(),
11
+ projectId: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
12
+ buildId: text("build_id").notNull().references(() => builds.id, { onDelete: "cascade" }),
13
+ storyId: text("story_id").notNull(),
14
+ storyName: text("story_name").notNull(),
15
+ storyTitle: text("story_title").notNull(),
16
+ storyImportPath: text("story_import_path"),
17
+ viewportName: text("viewport_name").notNull().default("desktop"),
18
+ viewportWidth: integer("viewport_width").notNull().default(1280),
19
+ viewportHeight: integer("viewport_height").notNull().default(720),
20
+ screenshotPath: text("screenshot_path").notNull(),
21
+ diffPath: text("diff_path"),
22
+ diffPixels: integer("diff_pixels"),
23
+ diffRatio: real("diff_ratio"),
24
+ diffPassed: integer("diff_passed", { mode: "boolean" }),
25
+ status: text("status").$type().notNull().default("pending"),
26
+ reviewedBy: text("reviewed_by"),
27
+ reviewedAt: text("reviewed_at"),
28
+ createdAt: text("created_at").notNull(),
29
+ updatedAt: text("updated_at").notNull()
30
+ }, (t) => [uniqueIndex("snapshots_build_story_viewport_idx").on(t.buildId, t.storyId, t.viewportName), index("snapshots_build_id_idx").on(t.buildId)]);
31
+ //#endregion
32
+ export { snapshots };
33
+
34
+ //# sourceMappingURL=snapshot.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshot.mjs","names":[],"sources":["../../src/schema/snapshot.ts"],"sourcesContent":["import { index, integer, real, sqliteTable, text, uniqueIndex } from \"drizzle-orm/sqlite-core\";\nimport type { SnapshotStatus } from \"../types.ts\";\nimport { builds } from \"./build.ts\";\nimport { projects } from \"./project.ts\";\n\n/** Narrow `snapshots` table definition. */\nexport const snapshots = sqliteTable(\n \"snapshots\",\n {\n id: text(\"id\").primaryKey(),\n projectId: text(\"project_id\")\n .notNull()\n .references(() => projects.id, { onDelete: \"cascade\" }),\n buildId: text(\"build_id\")\n .notNull()\n .references(() => builds.id, { onDelete: \"cascade\" }),\n storyId: text(\"story_id\").notNull(),\n storyName: text(\"story_name\").notNull(),\n storyTitle: text(\"story_title\").notNull(),\n storyImportPath: text(\"story_import_path\"),\n viewportName: text(\"viewport_name\").notNull().default(\"desktop\"),\n viewportWidth: integer(\"viewport_width\").notNull().default(1280),\n viewportHeight: integer(\"viewport_height\").notNull().default(720),\n screenshotPath: text(\"screenshot_path\").notNull(),\n diffPath: text(\"diff_path\"),\n diffPixels: integer(\"diff_pixels\"),\n diffRatio: real(\"diff_ratio\"),\n diffPassed: integer(\"diff_passed\", { mode: \"boolean\" }),\n status: text(\"status\").$type<SnapshotStatus>().notNull().default(\"pending\"),\n reviewedBy: text(\"reviewed_by\"),\n reviewedAt: text(\"reviewed_at\"),\n createdAt: text(\"created_at\").notNull(),\n updatedAt: text(\"updated_at\").notNull(),\n },\n (t) => [\n uniqueIndex(\"snapshots_build_story_viewport_idx\").on(t.buildId, t.storyId, t.viewportName),\n index(\"snapshots_build_id_idx\").on(t.buildId),\n ],\n);\n\n/** A snapshot row. */\nexport interface Snapshot {\n id: string;\n projectId: string;\n buildId: string;\n storyId: string;\n storyName: string;\n storyTitle: string;\n storyImportPath: string | null;\n viewportName: string;\n viewportWidth: number;\n viewportHeight: number;\n screenshotPath: string;\n diffPath: string | null;\n diffPixels: number | null;\n diffRatio: number | null;\n diffPassed: boolean | null;\n status: SnapshotStatus;\n reviewedBy: string | null;\n reviewedAt: string | null;\n createdAt: string;\n updatedAt: string;\n}\n"],"mappings":";;;;;;;;AAMA,MAAa,YAAY,YACvB,aACA;CACE,IAAI,KAAK,IAAI,CAAC,CAAC,WAAW;CAC1B,WAAW,KAAK,YAAY,CAAC,CAC1B,QAAQ,CAAC,CACT,iBAAiB,SAAS,IAAI,EAAE,UAAU,UAAU,CAAC;CACxD,SAAS,KAAK,UAAU,CAAC,CACtB,QAAQ,CAAC,CACT,iBAAiB,OAAO,IAAI,EAAE,UAAU,UAAU,CAAC;CACtD,SAAS,KAAK,UAAU,CAAC,CAAC,QAAQ;CAClC,WAAW,KAAK,YAAY,CAAC,CAAC,QAAQ;CACtC,YAAY,KAAK,aAAa,CAAC,CAAC,QAAQ;CACxC,iBAAiB,KAAK,mBAAmB;CACzC,cAAc,KAAK,eAAe,CAAC,CAAC,QAAQ,CAAC,CAAC,QAAQ,SAAS;CAC/D,eAAe,QAAQ,gBAAgB,CAAC,CAAC,QAAQ,CAAC,CAAC,QAAQ,IAAI;CAC/D,gBAAgB,QAAQ,iBAAiB,CAAC,CAAC,QAAQ,CAAC,CAAC,QAAQ,GAAG;CAChE,gBAAgB,KAAK,iBAAiB,CAAC,CAAC,QAAQ;CAChD,UAAU,KAAK,WAAW;CAC1B,YAAY,QAAQ,aAAa;CACjC,WAAW,KAAK,YAAY;CAC5B,YAAY,QAAQ,eAAe,EAAE,MAAM,UAAU,CAAC;CACtD,QAAQ,KAAK,QAAQ,CAAC,CAAC,MAAsB,CAAC,CAAC,QAAQ,CAAC,CAAC,QAAQ,SAAS;CAC1E,YAAY,KAAK,aAAa;CAC9B,YAAY,KAAK,aAAa;CAC9B,WAAW,KAAK,YAAY,CAAC,CAAC,QAAQ;CACtC,WAAW,KAAK,YAAY,CAAC,CAAC,QAAQ;AACxC,IACC,MAAM,CACL,YAAY,oCAAoC,CAAC,CAAC,GAAG,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,GACzF,MAAM,wBAAwB,CAAC,CAAC,GAAG,EAAE,OAAO,CAC9C,CACF"}
@@ -0,0 +1,16 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ //#region src/schema/status-config.d.ts
4
+ /** A project status-config row. */
5
+ interface ProjectStatusConfig {
6
+ id: string;
7
+ projectId: string;
8
+ provider: string;
9
+ config: string;
10
+ tokenEncrypted: string;
11
+ createdAt: string;
12
+ updatedAt: string;
13
+ }
14
+ //#endregion
15
+ export { ProjectStatusConfig };
16
+ //# sourceMappingURL=status-config.d.mts.map