storybooker 0.19.4 → 0.22.0-canary.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (215) hide show
  1. package/README.md +40 -18
  2. package/dist/adapters/_internal/queue.d.mts +127 -0
  3. package/dist/aws-dynamodb.d.mts +22 -0
  4. package/dist/aws-dynamodb.mjs +118 -0
  5. package/dist/aws-dynamodb.mjs.map +1 -0
  6. package/dist/aws-s3.d.mts +20 -0
  7. package/dist/aws-s3.mjs +96 -0
  8. package/dist/aws-s3.mjs.map +1 -0
  9. package/dist/azure-blob-storage.d.mts +20 -0
  10. package/dist/azure-blob-storage.mjs +126 -0
  11. package/dist/azure-blob-storage.mjs.map +1 -0
  12. package/dist/azure-cosmos-db.d.mts +23 -0
  13. package/dist/azure-cosmos-db.mjs +87 -0
  14. package/dist/azure-cosmos-db.mjs.map +1 -0
  15. package/dist/azure-data-tables.d.mts +23 -0
  16. package/dist/azure-data-tables.mjs +127 -0
  17. package/dist/azure-data-tables.mjs.map +1 -0
  18. package/dist/azure-easy-auth.d.mts +50 -0
  19. package/dist/azure-easy-auth.mjs +88 -0
  20. package/dist/azure-easy-auth.mjs.map +1 -0
  21. package/dist/azure-functions.d.mts +62 -0
  22. package/dist/azure-functions.mjs +147 -0
  23. package/dist/azure-functions.mjs.map +1 -0
  24. package/dist/fs.d.mts +37 -0
  25. package/dist/fs.mjs +240 -0
  26. package/dist/fs.mjs.map +1 -0
  27. package/dist/gcp-big-table.d.mts +23 -0
  28. package/dist/gcp-big-table.mjs +92 -0
  29. package/dist/gcp-big-table.mjs.map +1 -0
  30. package/dist/gcp-firestore.d.mts +22 -0
  31. package/dist/gcp-firestore.mjs +87 -0
  32. package/dist/gcp-firestore.mjs.map +1 -0
  33. package/dist/gcp-storage.d.mts +20 -0
  34. package/dist/gcp-storage.mjs +96 -0
  35. package/dist/gcp-storage.mjs.map +1 -0
  36. package/dist/handlers/handle-process-zip.mjs +90 -0
  37. package/dist/handlers/handle-process-zip.mjs.map +1 -0
  38. package/dist/handlers/handle-purge.d.mts +12 -0
  39. package/dist/handlers/handle-purge.mjs +36 -0
  40. package/dist/handlers/handle-purge.mjs.map +1 -0
  41. package/dist/handlers/handle-serve-storybook.mjs +94 -0
  42. package/dist/handlers/handle-serve-storybook.mjs.map +1 -0
  43. package/dist/index.d.mts +28 -0
  44. package/dist/index.mjs +62 -0
  45. package/dist/index.mjs.map +1 -0
  46. package/dist/models/builds-model.mjs +248 -0
  47. package/dist/models/builds-model.mjs.map +1 -0
  48. package/dist/models/builds-schema.d.mts +171 -0
  49. package/dist/models/builds-schema.mjs +67 -0
  50. package/dist/models/builds-schema.mjs.map +1 -0
  51. package/dist/models/projects-model.mjs +122 -0
  52. package/dist/models/projects-model.mjs.map +1 -0
  53. package/dist/models/projects-schema.d.mts +70 -0
  54. package/dist/models/projects-schema.mjs +37 -0
  55. package/dist/models/projects-schema.mjs.map +1 -0
  56. package/dist/models/tags-model.mjs +110 -0
  57. package/dist/models/tags-model.mjs.map +1 -0
  58. package/dist/models/tags-schema.d.mts +76 -0
  59. package/dist/models/tags-schema.mjs +34 -0
  60. package/dist/models/tags-schema.mjs.map +1 -0
  61. package/dist/models/~model.mjs +43 -0
  62. package/dist/models/~model.mjs.map +1 -0
  63. package/dist/models/~shared-schema.d.mts +1 -0
  64. package/dist/models/~shared-schema.mjs +20 -0
  65. package/dist/models/~shared-schema.mjs.map +1 -0
  66. package/dist/mysql.d.mts +39 -0
  67. package/dist/mysql.mjs +151 -0
  68. package/dist/mysql.mjs.map +1 -0
  69. package/dist/redis.d.mts +33 -0
  70. package/dist/redis.mjs +118 -0
  71. package/dist/redis.mjs.map +1 -0
  72. package/dist/routers/account-router.mjs +91 -0
  73. package/dist/routers/account-router.mjs.map +1 -0
  74. package/dist/routers/builds-router.mjs +347 -0
  75. package/dist/routers/builds-router.mjs.map +1 -0
  76. package/dist/routers/projects-router.mjs +236 -0
  77. package/dist/routers/projects-router.mjs.map +1 -0
  78. package/dist/routers/root-router.mjs +108 -0
  79. package/dist/routers/root-router.mjs.map +1 -0
  80. package/dist/routers/tags-router.mjs +269 -0
  81. package/dist/routers/tags-router.mjs.map +1 -0
  82. package/dist/routers/tasks-router.mjs +71 -0
  83. package/dist/routers/tasks-router.mjs.map +1 -0
  84. package/dist/urls.d.mts +47 -0
  85. package/dist/urls.mjs +208 -0
  86. package/dist/urls.mjs.map +1 -0
  87. package/dist/utils/adapter-utils.d.mts +14 -0
  88. package/dist/utils/adapter-utils.mjs +14 -0
  89. package/dist/utils/adapter-utils.mjs.map +1 -0
  90. package/dist/utils/auth.mjs +25 -0
  91. package/dist/utils/auth.mjs.map +1 -0
  92. package/dist/utils/error.d.mts +21 -0
  93. package/dist/utils/error.mjs +109 -0
  94. package/dist/utils/error.mjs.map +1 -0
  95. package/dist/utils/file-utils.mjs +16 -0
  96. package/dist/utils/file-utils.mjs.map +1 -0
  97. package/dist/utils/openapi-utils.mjs +45 -0
  98. package/dist/utils/openapi-utils.mjs.map +1 -0
  99. package/dist/utils/request.mjs +35 -0
  100. package/dist/utils/request.mjs.map +1 -0
  101. package/dist/utils/response.mjs +24 -0
  102. package/dist/utils/response.mjs.map +1 -0
  103. package/dist/utils/store.mjs +54 -0
  104. package/dist/utils/store.mjs.map +1 -0
  105. package/dist/utils/ui-utils.mjs +38 -0
  106. package/dist/utils/ui-utils.mjs.map +1 -0
  107. package/dist/utils/url-utils.d.mts +10 -0
  108. package/dist/utils/url-utils.mjs +54 -0
  109. package/dist/utils/url-utils.mjs.map +1 -0
  110. package/dist/~internal/adapter/auth.d.mts +123 -0
  111. package/dist/~internal/adapter/auth.mjs +20 -0
  112. package/dist/~internal/adapter/auth.mjs.map +1 -0
  113. package/dist/~internal/adapter/database.d.mts +240 -0
  114. package/dist/~internal/adapter/database.mjs +63 -0
  115. package/dist/~internal/adapter/database.mjs.map +1 -0
  116. package/dist/~internal/adapter/logger.d.mts +34 -0
  117. package/dist/~internal/adapter/logger.mjs +13 -0
  118. package/dist/~internal/adapter/logger.mjs.map +1 -0
  119. package/dist/~internal/adapter/storage.d.mts +208 -0
  120. package/dist/~internal/adapter/storage.mjs +63 -0
  121. package/dist/~internal/adapter/storage.mjs.map +1 -0
  122. package/dist/~internal/adapter/ui.d.mts +109 -0
  123. package/dist/~internal/adapter/ui.mjs +1 -0
  124. package/dist/~internal/adapter.d.mts +8 -0
  125. package/dist/~internal/adapter.mjs +6 -0
  126. package/dist/~internal/constants.d.mts +24 -0
  127. package/dist/~internal/constants.mjs +32 -0
  128. package/dist/~internal/constants.mjs.map +1 -0
  129. package/dist/~internal/mimes.d.mts +449 -0
  130. package/dist/~internal/mimes.mjs +454 -0
  131. package/dist/~internal/mimes.mjs.map +1 -0
  132. package/dist/~internal/router.d.mts +1651 -0
  133. package/dist/~internal/router.mjs +39 -0
  134. package/dist/~internal/router.mjs.map +1 -0
  135. package/dist/~internal/types.d.mts +77 -0
  136. package/dist/~internal/types.mjs +1 -0
  137. package/dist/~internal/utils.d.mts +4 -0
  138. package/dist/~internal/utils.mjs +5 -0
  139. package/openapi.json +3162 -0
  140. package/package.json +148 -27
  141. package/src/adapters/_internal/auth.ts +135 -0
  142. package/src/adapters/_internal/database.ts +241 -0
  143. package/src/adapters/_internal/index.ts +8 -0
  144. package/src/adapters/_internal/logger.ts +41 -0
  145. package/src/adapters/_internal/queue.ts +151 -0
  146. package/src/adapters/_internal/storage.ts +197 -0
  147. package/src/adapters/_internal/ui.ts +103 -0
  148. package/src/adapters/aws-dynamodb.ts +201 -0
  149. package/src/adapters/aws-s3.ts +160 -0
  150. package/src/adapters/azure-blob-storage.ts +223 -0
  151. package/src/adapters/azure-cosmos-db.ts +158 -0
  152. package/src/adapters/azure-data-tables.ts +223 -0
  153. package/src/adapters/azure-easy-auth.ts +174 -0
  154. package/src/adapters/azure-functions.ts +242 -0
  155. package/src/adapters/fs.ts +398 -0
  156. package/src/adapters/gcp-big-table.ts +157 -0
  157. package/src/adapters/gcp-firestore.ts +146 -0
  158. package/src/adapters/gcp-storage.ts +141 -0
  159. package/src/adapters/mysql.ts +296 -0
  160. package/src/adapters/redis.ts +242 -0
  161. package/src/handlers/handle-process-zip.ts +117 -0
  162. package/src/handlers/handle-purge.ts +65 -0
  163. package/src/handlers/handle-serve-storybook.ts +101 -0
  164. package/src/index.ts +81 -16
  165. package/src/mocks/mock-auth-service.ts +51 -0
  166. package/src/mocks/mock-store.ts +26 -0
  167. package/src/models/builds-model.ts +373 -0
  168. package/src/models/builds-schema.ts +84 -0
  169. package/src/models/projects-model.ts +177 -0
  170. package/src/models/projects-schema.ts +69 -0
  171. package/src/models/tags-model.ts +138 -0
  172. package/src/models/tags-schema.ts +45 -0
  173. package/src/models/~model.ts +79 -0
  174. package/src/models/~shared-schema.ts +14 -0
  175. package/src/routers/_app-router.ts +57 -0
  176. package/src/routers/account-router.ts +136 -0
  177. package/src/routers/builds-router.ts +464 -0
  178. package/src/routers/projects-router.ts +309 -0
  179. package/src/routers/root-router.ts +127 -0
  180. package/src/routers/tags-router.ts +339 -0
  181. package/src/routers/tasks-router.ts +75 -0
  182. package/src/types.ts +107 -0
  183. package/src/urls.ts +327 -0
  184. package/src/utils/adapter-utils.ts +26 -0
  185. package/src/utils/auth.test.ts +71 -0
  186. package/src/utils/auth.ts +39 -0
  187. package/src/utils/constants.ts +31 -0
  188. package/src/utils/date-utils.ts +10 -0
  189. package/src/utils/error.test.ts +86 -0
  190. package/src/utils/error.ts +140 -0
  191. package/src/utils/file-utils.test.ts +65 -0
  192. package/src/utils/file-utils.ts +43 -0
  193. package/src/utils/index.ts +3 -0
  194. package/src/utils/mime-utils.ts +457 -0
  195. package/src/utils/openapi-utils.ts +49 -0
  196. package/src/utils/request.ts +97 -0
  197. package/src/utils/response.ts +20 -0
  198. package/src/utils/store.ts +85 -0
  199. package/src/utils/story-utils.ts +42 -0
  200. package/src/utils/text-utils.ts +10 -0
  201. package/src/utils/ui-utils.ts +57 -0
  202. package/src/utils/url-utils.ts +113 -0
  203. package/dist/index.js +0 -554
  204. package/src/commands/create.ts +0 -263
  205. package/src/commands/purge.ts +0 -70
  206. package/src/commands/test.ts +0 -42
  207. package/src/service-schema.d.ts +0 -2023
  208. package/src/utils/auth-utils.ts +0 -31
  209. package/src/utils/pkg-utils.ts +0 -37
  210. package/src/utils/sb-build.ts +0 -55
  211. package/src/utils/sb-test.ts +0 -115
  212. package/src/utils/schema-utils.ts +0 -123
  213. package/src/utils/stream-utils.ts +0 -72
  214. package/src/utils/types.ts +0 -4
  215. package/src/utils/zip.ts +0 -77
@@ -0,0 +1,464 @@
1
+ import { createRoute, OpenAPIHono } from "@hono/zod-openapi";
2
+ import { SuperHeaders } from "@remix-run/headers";
3
+ import { HTTPException } from "hono/http-exception";
4
+ import type { ContentfulStatusCode } from "hono/utils/http-status";
5
+ import { z } from "zod";
6
+ import { BuildsModel } from "../models/builds-model.ts";
7
+ import {
8
+ BuildCreateSchema,
9
+ BuildIdSchema,
10
+ BuildsGetResultSchema,
11
+ BuildsListResultSchema,
12
+ BuildUpdateSchema,
13
+ BuildUploadFormBodySchema,
14
+ BuildUploadQueryParamsSchema,
15
+ } from "../models/builds-schema.ts";
16
+ import { ProjectsModel } from "../models/projects-model.ts";
17
+ import { ProjectIdSchema } from "../models/projects-schema.ts";
18
+ import { urlBuilder } from "../urls.ts";
19
+ import { authenticateOrThrow } from "../utils/auth.ts";
20
+ import { QUERY_PARAMS } from "../utils/constants.ts";
21
+ import { mimes } from "../utils/mime-utils.ts";
22
+ import {
23
+ openapiCommonErrorResponses,
24
+ openapiErrorResponseContent,
25
+ openapiResponseRedirect,
26
+ openapiResponsesHtml,
27
+ } from "../utils/openapi-utils.ts";
28
+ import { checkIsHTMLRequest, validateBuildUploadZipBody } from "../utils/request.ts";
29
+ import { getStore } from "../utils/store.ts";
30
+ import { createUIResultResponse } from "../utils/ui-utils.ts";
31
+
32
+ const buildTag = "Builds";
33
+ const projectIdPathParams = z.object({ projectId: ProjectIdSchema });
34
+ const buildIdPathParams = z.object({
35
+ projectId: ProjectIdSchema,
36
+ buildId: BuildIdSchema,
37
+ });
38
+
39
+ /**
40
+ * @private
41
+ */
42
+ export const buildsRouter = new OpenAPIHono()
43
+ .openapi(
44
+ createRoute({
45
+ summary: "List builds",
46
+ method: "get",
47
+ path: "/projects/{projectId}/builds",
48
+ tags: [buildTag],
49
+ request: {
50
+ params: projectIdPathParams,
51
+ },
52
+ responses: {
53
+ 200: {
54
+ description: "A list of builds in the project.",
55
+ content: {
56
+ [mimes.json]: { schema: BuildsListResultSchema },
57
+ ...openapiResponsesHtml,
58
+ },
59
+ },
60
+ ...openapiCommonErrorResponses,
61
+ },
62
+ }),
63
+ async (context) => {
64
+ const { projectId } = context.req.valid("param");
65
+ const { ui } = getStore();
66
+
67
+ authenticateOrThrow({
68
+ action: "read",
69
+ projectId,
70
+ resource: "build",
71
+ });
72
+
73
+ const builds = await new BuildsModel(projectId).list();
74
+
75
+ if (ui?.renderBuildsListPage && checkIsHTMLRequest()) {
76
+ const project = await new ProjectsModel().get(projectId);
77
+
78
+ return createUIResultResponse(context, ui.renderBuildsListPage, { builds, project });
79
+ }
80
+
81
+ return context.json({ builds });
82
+ },
83
+ )
84
+ .openapi(
85
+ createRoute({
86
+ summary: "Create build - UI",
87
+ method: "get",
88
+ path: "/projects/{projectId}/builds/create",
89
+ tags: [buildTag],
90
+ request: {
91
+ params: projectIdPathParams,
92
+ query: z.object({ [QUERY_PARAMS.tagId]: z.string().optional() }),
93
+ },
94
+ responses: {
95
+ 200: {
96
+ description: "UI to create build",
97
+ content: openapiResponsesHtml,
98
+ },
99
+ ...openapiCommonErrorResponses,
100
+ },
101
+ }),
102
+ async (context) => {
103
+ const { ui } = getStore();
104
+ if (!ui?.renderBuildCreatePage) {
105
+ throw new HTTPException(405, { message: "UI not available for this route." });
106
+ }
107
+
108
+ const { projectId } = context.req.valid("param");
109
+ const { tagId } = context.req.valid("query");
110
+
111
+ authenticateOrThrow({
112
+ action: "create",
113
+ projectId,
114
+ resource: "build",
115
+ });
116
+
117
+ const project = await new ProjectsModel().get(projectId);
118
+
119
+ return createUIResultResponse(context, ui.renderBuildCreatePage, { project, tagId });
120
+ },
121
+ )
122
+ .openapi(
123
+ createRoute({
124
+ summary: "Create build - action",
125
+ method: "post",
126
+ path: "/projects/{projectId}/builds/create",
127
+ tags: [buildTag],
128
+ request: {
129
+ params: projectIdPathParams,
130
+ body: {
131
+ content: { [mimes.formEncoded]: { schema: BuildCreateSchema } },
132
+ required: true,
133
+ },
134
+ },
135
+ responses: {
136
+ 201: {
137
+ description: "Build created successfully",
138
+ content: { [mimes.json]: { schema: BuildsGetResultSchema } },
139
+ },
140
+ 303: openapiResponseRedirect("Redirect to build."),
141
+ 409: {
142
+ content: openapiErrorResponseContent,
143
+ description: "Build already exists.",
144
+ },
145
+ 415: {
146
+ content: openapiErrorResponseContent,
147
+ description: "Unsupported Media Type",
148
+ },
149
+ ...openapiCommonErrorResponses,
150
+ },
151
+ }),
152
+ async (context) => {
153
+ const { projectId } = context.req.valid("param");
154
+
155
+ const projectModel = new ProjectsModel().id(projectId);
156
+ if (!(await projectModel.has())) {
157
+ throw new HTTPException(404, { message: `The project '${projectId}' does not exist.` });
158
+ }
159
+
160
+ authenticateOrThrow({
161
+ action: "create",
162
+ projectId,
163
+ resource: "build",
164
+ });
165
+
166
+ const data = context.req.valid("form");
167
+ const build = await new BuildsModel(projectId).create(data);
168
+ const url = urlBuilder.buildDetails(projectId, build.id);
169
+
170
+ if (checkIsHTMLRequest(true)) {
171
+ return context.redirect(url, 303);
172
+ }
173
+
174
+ return context.json({ build, url }, 201);
175
+ },
176
+ )
177
+ .openapi(
178
+ createRoute({
179
+ summary: "Build details",
180
+ method: "get",
181
+ path: "/projects/{projectId}/builds/{buildId}",
182
+ tags: [buildTag],
183
+ request: { params: buildIdPathParams },
184
+ responses: {
185
+ 200: {
186
+ description: "Details of the build",
187
+ content: {
188
+ [mimes.json]: { schema: BuildsGetResultSchema },
189
+ ...openapiResponsesHtml,
190
+ },
191
+ },
192
+ 404: {
193
+ description: "Matching build not found.",
194
+ content: openapiErrorResponseContent,
195
+ },
196
+ ...openapiCommonErrorResponses,
197
+ },
198
+ }),
199
+ async (context) => {
200
+ const { projectId, buildId } = context.req.valid("param");
201
+ const { ui } = getStore();
202
+
203
+ authenticateOrThrow({
204
+ action: "read",
205
+ projectId,
206
+ resource: "build",
207
+ });
208
+
209
+ const model = new BuildsModel(projectId);
210
+ const build = await model.get(buildId);
211
+
212
+ if (ui?.renderBuildDetailsPage && checkIsHTMLRequest()) {
213
+ // const [hasDeletePermission, hasUpdatePermission] = await Promise.all([
214
+ // model.checkAuth("delete"),
215
+ // model.checkAuth("update"),
216
+ // ]);
217
+ // const canDeleteBuild =
218
+ // hasDeletePermission && project.latestBuildId !== build.id;
219
+
220
+ const project = await new ProjectsModel().get(projectId);
221
+ const stories = await model.getStories(build);
222
+
223
+ return createUIResultResponse(context, ui.renderBuildDetailsPage, {
224
+ build,
225
+ stories,
226
+ project,
227
+ });
228
+ }
229
+
230
+ return context.json({ build, url: context.req.url });
231
+ },
232
+ )
233
+ .openapi(
234
+ createRoute({
235
+ summary: "Delete build - action",
236
+ method: "post",
237
+ path: "/projects/{projectId}/builds/{buildId}/delete",
238
+ tags: [buildTag],
239
+ request: { params: buildIdPathParams },
240
+ responses: {
241
+ 204: { description: "Build deleted successfully." },
242
+ 303: openapiResponseRedirect("Redirect to builds list."),
243
+ 404: {
244
+ description: "Matching build not found.",
245
+ content: openapiErrorResponseContent,
246
+ },
247
+ ...openapiCommonErrorResponses,
248
+ },
249
+ }),
250
+ async (context) => {
251
+ const { projectId, buildId } = context.req.valid("param");
252
+ authenticateOrThrow({
253
+ action: "delete",
254
+ projectId,
255
+ resource: "build",
256
+ });
257
+
258
+ await new BuildsModel(projectId).delete(buildId, true);
259
+
260
+ if (checkIsHTMLRequest(true)) {
261
+ return context.redirect(urlBuilder.buildsList(projectId), 303);
262
+ }
263
+
264
+ return new Response(null, { status: 204 });
265
+ },
266
+ )
267
+ .openapi(
268
+ createRoute({
269
+ summary: "Update build - action",
270
+ method: "post",
271
+ path: "/projects/{projectId}/builds/{buildId}/update",
272
+ tags: [buildTag],
273
+ request: {
274
+ params: buildIdPathParams,
275
+ body: {
276
+ content: { [mimes.formEncoded]: { schema: BuildUpdateSchema } },
277
+ required: true,
278
+ },
279
+ },
280
+ responses: {
281
+ 202: { description: "Build updated successfully" },
282
+ 303: openapiResponseRedirect("Redirect to build."),
283
+ 404: {
284
+ description: "Matching project or build not found.",
285
+ content: openapiErrorResponseContent,
286
+ },
287
+ 415: {
288
+ content: openapiErrorResponseContent,
289
+ description: "Unsupported Media Type",
290
+ },
291
+ ...openapiCommonErrorResponses,
292
+ },
293
+ }),
294
+ async (context) => {
295
+ const { buildId, projectId } = context.req.valid("param");
296
+
297
+ const buildsModel = new BuildsModel(projectId);
298
+
299
+ if (!(await buildsModel.has(buildId))) {
300
+ throw new HTTPException(404, {
301
+ message: `The build '${buildId}' does not exist in project '${projectId}'.`,
302
+ });
303
+ }
304
+
305
+ authenticateOrThrow({
306
+ action: "update",
307
+ projectId,
308
+ resource: "build",
309
+ });
310
+
311
+ const data = context.req.valid("form");
312
+ await buildsModel.update(buildId, data);
313
+
314
+ if (checkIsHTMLRequest(true)) {
315
+ return context.redirect(urlBuilder.buildDetails(projectId, buildId), 303);
316
+ }
317
+
318
+ return new Response(null, { status: 202 });
319
+ },
320
+ )
321
+ .openapi(
322
+ createRoute({
323
+ summary: "Upload build - UI",
324
+ method: "get",
325
+ path: "/projects/{projectId}/builds/{buildId}/upload",
326
+ tags: [buildTag],
327
+ request: {
328
+ params: buildIdPathParams,
329
+ query: BuildUploadQueryParamsSchema,
330
+ },
331
+ responses: {
332
+ 200: {
333
+ description: "UI to upload build",
334
+ content: openapiResponsesHtml,
335
+ },
336
+ 404: {
337
+ description: "Matching build not found.",
338
+ content: openapiErrorResponseContent,
339
+ },
340
+ ...openapiCommonErrorResponses,
341
+ },
342
+ }),
343
+ async (context) => {
344
+ const { ui } = getStore();
345
+ if (!ui?.renderBuildUploadPage) {
346
+ throw new HTTPException(405, { message: "UI not available for this route." });
347
+ }
348
+
349
+ const { buildId, projectId } = context.req.valid("param");
350
+
351
+ authenticateOrThrow({
352
+ action: "update",
353
+ projectId,
354
+ resource: "build",
355
+ });
356
+
357
+ const build = await new BuildsModel(projectId).get(buildId);
358
+ const project = await new ProjectsModel().get(projectId);
359
+ const { variant: uploadVariant } = context.req.valid("query");
360
+
361
+ return createUIResultResponse(context, ui.renderBuildUploadPage, {
362
+ build,
363
+ uploadVariant,
364
+ project,
365
+ });
366
+ },
367
+ )
368
+ .openapi(
369
+ createRoute({
370
+ summary: "Upload build - action",
371
+ method: "post",
372
+ path: "/projects/{projectId}/builds/{buildId}/upload",
373
+ tags: [buildTag],
374
+ request: {
375
+ params: buildIdPathParams,
376
+ query: BuildUploadQueryParamsSchema,
377
+ body: {
378
+ description: "Compressed zip containing files.",
379
+ content: {
380
+ [mimes.formMultipart]: { schema: BuildUploadFormBodySchema },
381
+ [mimes.zip]: {
382
+ schema: { format: "binary", type: "string" },
383
+ example: "storybook.zip",
384
+ },
385
+ },
386
+ required: true,
387
+ },
388
+ },
389
+ responses: {
390
+ 204: { description: "File uploaded successfully" },
391
+ 303: openapiResponseRedirect("Redirect to project."),
392
+ 404: {
393
+ description: "Matching project not found.",
394
+ content: openapiErrorResponseContent,
395
+ },
396
+ 415: {
397
+ content: openapiErrorResponseContent,
398
+ description: "Unsupported Media Type",
399
+ },
400
+ ...openapiCommonErrorResponses,
401
+ },
402
+ }),
403
+ async (context) => {
404
+ const { buildId, projectId } = context.req.valid("param");
405
+
406
+ const buildsModel = new BuildsModel(projectId);
407
+
408
+ if (!(await buildsModel.has(buildId))) {
409
+ throw new HTTPException(404, {
410
+ message: `The build '${buildId}' does not exist in project '${projectId}'.`,
411
+ });
412
+ }
413
+
414
+ authenticateOrThrow({
415
+ action: "update",
416
+ projectId,
417
+ resource: "build",
418
+ });
419
+
420
+ const { contentType } = new SuperHeaders(context.req.header());
421
+
422
+ if (!contentType.toString()) {
423
+ throw new HTTPException(400, { message: "Content-Type header is required" });
424
+ }
425
+
426
+ const redirectUrl = urlBuilder.buildDetails(projectId, buildId);
427
+
428
+ // Form submission
429
+ if (contentType.mediaType?.startsWith(mimes.formMultipart)) {
430
+ const { file, variant } = BuildUploadFormBodySchema.parse(await context.req.parseBody());
431
+
432
+ await buildsModel.upload(buildId, variant, file);
433
+
434
+ if (checkIsHTMLRequest(true)) {
435
+ return context.redirect(redirectUrl, 303);
436
+ }
437
+
438
+ return new Response(null, { status: 204 });
439
+ }
440
+
441
+ if (contentType.mediaType?.startsWith(mimes.zip)) {
442
+ const bodyError = validateBuildUploadZipBody(context.req.raw);
443
+ if (bodyError) {
444
+ throw new HTTPException(bodyError.status as ContentfulStatusCode, {
445
+ message: bodyError.message,
446
+ });
447
+ }
448
+
449
+ const { variant } = context.req.valid("query");
450
+
451
+ await buildsModel.upload(buildId, variant);
452
+
453
+ if (checkIsHTMLRequest(true)) {
454
+ return context.redirect(redirectUrl, 303);
455
+ }
456
+
457
+ return new Response(null, { status: 204 });
458
+ }
459
+
460
+ throw new HTTPException(415, {
461
+ message: `Invalid content type, expected ${mimes.zip} or ${mimes.formMultipart}.`,
462
+ });
463
+ },
464
+ );