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
package/src/urls.ts ADDED
@@ -0,0 +1,327 @@
1
+ import path from "node:path";
2
+ import type { BuildUploadVariant } from "./models/builds-schema.ts";
3
+ import type { TagVariant } from "./models/tags-schema.ts";
4
+ import { getStore } from "./utils/store.ts";
5
+ import { linkRoute, urlJoin } from "./utils/url-utils.ts";
6
+
7
+ /**
8
+ * URL builder for the Storybooks router.
9
+ */
10
+ export class UrlBuilder {
11
+ #useStore: boolean;
12
+
13
+ constructor(useStore: boolean) {
14
+ this.#useStore = useStore;
15
+ }
16
+
17
+ get #baseUrl(): string {
18
+ if (!this.#useStore) {
19
+ return "";
20
+ }
21
+
22
+ return new URL(getStore().url).origin;
23
+ }
24
+
25
+ homepage(): string {
26
+ return linkRoute((client) => client.index.$url(), {
27
+ baseUrl: this.#baseUrl,
28
+ });
29
+ }
30
+ openapi(): string {
31
+ return linkRoute((client) => client.openapi.$url(), {
32
+ baseUrl: this.#baseUrl,
33
+ });
34
+ }
35
+ staticFile(filepath: string): string {
36
+ return linkRoute((client) => client[":filepath{.+}"].$url({ param: { filepath } }), {
37
+ baseUrl: this.#baseUrl,
38
+ });
39
+ }
40
+
41
+ // accounts
42
+ account(): string {
43
+ return linkRoute((client) => client.account.$url(), {
44
+ baseUrl: this.#baseUrl,
45
+ });
46
+ }
47
+
48
+ login(redirect?: string): string {
49
+ return linkRoute((client) => client.account.login.$url({ query: { redirect } }), {
50
+ baseUrl: this.#baseUrl,
51
+ });
52
+ }
53
+ logout(): string {
54
+ return linkRoute((client) => client.account.logout.$url(), {
55
+ baseUrl: this.#baseUrl,
56
+ });
57
+ }
58
+
59
+ // projects
60
+ projectsList(): string {
61
+ return linkRoute((client) => client.projects.$url(), {
62
+ baseUrl: this.#baseUrl,
63
+ });
64
+ }
65
+ projectCreate(): string {
66
+ return linkRoute((client) => client.projects.create.$url(), {
67
+ baseUrl: this.#baseUrl,
68
+ });
69
+ }
70
+ projectDetails(projectId: string): string {
71
+ return linkRoute((client) => client.projects[":projectId"].$url({ param: { projectId } }), {
72
+ baseUrl: this.#baseUrl,
73
+ });
74
+ }
75
+ projectUpdate(projectId: string): string {
76
+ return linkRoute(
77
+ (client) => client.projects[":projectId"].update.$url({ param: { projectId } }),
78
+ { baseUrl: this.#baseUrl },
79
+ );
80
+ }
81
+ projectDelete(projectId: string): string {
82
+ return linkRoute(
83
+ (client) => client.projects[":projectId"].delete.$url({ param: { projectId } }),
84
+ { baseUrl: this.#baseUrl },
85
+ );
86
+ }
87
+
88
+ // builds
89
+ buildsList(projectId: string): string {
90
+ return linkRoute(
91
+ (client) => client.projects[":projectId"].builds.$url({ param: { projectId } }),
92
+ { baseUrl: this.#baseUrl },
93
+ );
94
+ }
95
+ buildDetails(projectId: string, buildId: string): string {
96
+ return linkRoute(
97
+ (client) =>
98
+ client.projects[":projectId"].builds[":buildId"].$url({
99
+ param: { buildId, projectId },
100
+ }),
101
+ { baseUrl: this.#baseUrl },
102
+ );
103
+ }
104
+ buildCreate(projectId: string, tagId?: string): string {
105
+ return linkRoute(
106
+ (client) =>
107
+ client.projects[":projectId"].builds.create.$url({
108
+ param: { projectId },
109
+ query: { tagId },
110
+ }),
111
+ { baseUrl: this.#baseUrl },
112
+ );
113
+ }
114
+ buildDelete(projectId: string, buildId: string): string {
115
+ return linkRoute(
116
+ (client) =>
117
+ client.projects[":projectId"].builds[":buildId"].delete.$url({
118
+ param: { projectId, buildId },
119
+ }),
120
+ { baseUrl: this.#baseUrl },
121
+ );
122
+ }
123
+ buildUpdate(projectId: string, buildId: string): string {
124
+ return linkRoute(
125
+ (client) =>
126
+ client.projects[":projectId"].builds[":buildId"].update.$url({
127
+ param: { projectId, buildId },
128
+ }),
129
+ { baseUrl: this.#baseUrl },
130
+ );
131
+ }
132
+ buildUpload(projectId: string, buildId: string, variant?: BuildUploadVariant): string {
133
+ return linkRoute(
134
+ (client) =>
135
+ client.projects[":projectId"].builds[":buildId"].upload.$url({
136
+ param: { buildId, projectId },
137
+ query: { variant },
138
+ }),
139
+ { baseUrl: this.#baseUrl },
140
+ );
141
+ }
142
+
143
+ // tags
144
+ tagsList(projectId: string, type?: TagVariant): string {
145
+ return linkRoute(
146
+ (client) =>
147
+ client.projects[":projectId"].tags.$url({
148
+ param: { projectId },
149
+ query: { type },
150
+ }),
151
+ { baseUrl: this.#baseUrl },
152
+ );
153
+ }
154
+ tagCreate(projectId: string): string {
155
+ return linkRoute(
156
+ (client) =>
157
+ client.projects[":projectId"].tags.create.$url({
158
+ param: { projectId },
159
+ }),
160
+ { baseUrl: this.#baseUrl },
161
+ );
162
+ }
163
+ tagDetails(projectId: string, tagId: string): string {
164
+ return linkRoute(
165
+ (client) =>
166
+ client.projects[":projectId"].tags[":tagId"].$url({
167
+ param: { projectId, tagId },
168
+ }),
169
+ { baseUrl: this.#baseUrl },
170
+ );
171
+ }
172
+ tagDelete(projectId: string, tagId: string): string {
173
+ return linkRoute(
174
+ (client) =>
175
+ client.projects[":projectId"].tags[":tagId"].delete.$url({
176
+ param: { projectId, tagId },
177
+ }),
178
+ { baseUrl: this.#baseUrl },
179
+ );
180
+ }
181
+ tagUpdate(projectId: string, tagId: string): string {
182
+ return linkRoute(
183
+ (client) =>
184
+ client.projects[":projectId"].tags[":tagId"].update.$url({
185
+ param: { projectId, tagId },
186
+ }),
187
+ { baseUrl: this.#baseUrl },
188
+ );
189
+ }
190
+ // tagLatest: (projectId: string, tagId: string): string => {
191
+ // return linkRoute((client) =>
192
+ // client.projects[":projectId"].tags[":tagId"].latest.$url({
193
+ // param: { projectId, tagId },
194
+ // }),
195
+ // );
196
+ // },
197
+
198
+ // tasks
199
+ taskPurge(projectId?: string): string {
200
+ return linkRoute((client) => client.tasks.purge.$url({ query: { projectId } }), {
201
+ baseUrl: this.#baseUrl,
202
+ });
203
+ }
204
+ taskProcessZip(projectId: string, buildId: string, variant: BuildUploadVariant): string {
205
+ return linkRoute(
206
+ (client) =>
207
+ client.tasks["process-zip"].$url({
208
+ query: { projectId, buildId, variant },
209
+ }),
210
+ { baseUrl: this.#baseUrl },
211
+ );
212
+ }
213
+
214
+ // serve
215
+ storybookIndexHtml(projectId: string, buildId: string, storyId?: string): string {
216
+ return linkRoute(
217
+ (client) =>
218
+ client._[":projectId"][":buildId"][":filepath{.+}"].$url({
219
+ param: {
220
+ projectId,
221
+ buildId,
222
+ filepath: "storybook/index.html",
223
+ },
224
+ query: {
225
+ path: storyId ? `/story/${storyId}` : undefined,
226
+ },
227
+ }),
228
+ { baseUrl: this.#baseUrl },
229
+ );
230
+ }
231
+ storybookIFrameHtml(projectId: string, buildId: string, storyId: string): string {
232
+ return linkRoute(
233
+ (client) =>
234
+ client._[":projectId"][":buildId"][":filepath{.+}"].$url({
235
+ param: {
236
+ projectId,
237
+ buildId,
238
+ filepath: "storybook/iframe.html",
239
+ },
240
+ query: { id: storyId, viewMode: "story" },
241
+ }),
242
+ { baseUrl: this.#baseUrl },
243
+ );
244
+ }
245
+ storybookDownload(projectId: string, buildId: string): string {
246
+ return linkRoute(
247
+ (client) =>
248
+ client._[":projectId"][":buildId"][":filepath{.+}"].$url({
249
+ param: {
250
+ projectId,
251
+ buildId,
252
+ filepath: "storybook.zip",
253
+ },
254
+ query: {},
255
+ }),
256
+ { baseUrl: this.#baseUrl },
257
+ );
258
+ }
259
+ storybookTestReport(projectId: string, buildId: string): string {
260
+ return linkRoute(
261
+ (client) =>
262
+ client._[":projectId"][":buildId"][":filepath{.+}"].$url({
263
+ param: {
264
+ projectId,
265
+ buildId,
266
+ filepath: "testReport/index.html",
267
+ },
268
+ query: {},
269
+ }),
270
+ { baseUrl: this.#baseUrl },
271
+ );
272
+ }
273
+ storybookCoverage(projectId: string, buildId: string): string {
274
+ return linkRoute(
275
+ (client) =>
276
+ client._[":projectId"][":buildId"][":filepath{.+}"].$url({
277
+ param: {
278
+ projectId,
279
+ buildId,
280
+ filepath: "coverage/index.html",
281
+ },
282
+ query: {},
283
+ }),
284
+ { baseUrl: this.#baseUrl },
285
+ );
286
+ }
287
+ storybookScreenshot(projectId: string, buildId: string, ...filename: string[]): string {
288
+ return linkRoute(
289
+ (client) =>
290
+ client._[":projectId"][":buildId"][":filepath{.+}"].$url({
291
+ param: {
292
+ projectId,
293
+ buildId,
294
+ filepath: path.posix.join("screenshots", ...filename),
295
+ },
296
+ query: {},
297
+ }),
298
+ { baseUrl: this.#baseUrl },
299
+ );
300
+ }
301
+ storybookScreenshotsDownload(projectId: string, buildId: string): string {
302
+ return linkRoute(
303
+ (client) =>
304
+ client._[":projectId"][":buildId"][":filepath{.+}"].$url({
305
+ param: {
306
+ projectId,
307
+ buildId,
308
+ filepath: "screenshots.zip",
309
+ },
310
+ query: {},
311
+ }),
312
+ { baseUrl: this.#baseUrl },
313
+ );
314
+ }
315
+
316
+ // external
317
+ // oxlint-disable-next-line class-methods-use-this
318
+ gitHub(gitHubRepo: string, ...pathnames: string[]): string {
319
+ const url = new URL(urlJoin(gitHubRepo, ...pathnames), "https://github.com");
320
+ return url.toString();
321
+ }
322
+ }
323
+
324
+ /** @private */
325
+ export const urlBuilder: UrlBuilder = new UrlBuilder(true);
326
+ /** @private */
327
+ export const urlBuilderWithoutStore: UrlBuilder = new UrlBuilder(false);
@@ -0,0 +1,26 @@
1
+ import { SERVICE_NAME } from "../utils/constants.ts";
2
+
3
+ export function generateDatabaseCollectionId(
4
+ projectId: string,
5
+ suffix: "Tags" | "Builds" | "",
6
+ ): string {
7
+ if (!suffix) {
8
+ return `${SERVICE_NAME}-${projectId}`;
9
+ }
10
+
11
+ return `${SERVICE_NAME}-${projectId}-${suffix}`;
12
+ }
13
+
14
+ export function generateStorageContainerId(projectId: string): string {
15
+ return `${SERVICE_NAME}-${projectId}`;
16
+ }
17
+
18
+ /**
19
+ * Metadata information about a StoryBooker adapter.
20
+ */
21
+ export interface StoryBookerAdapterMetadata {
22
+ name: string;
23
+ description?: string;
24
+ version?: string;
25
+ [key: string]: unknown;
26
+ }
@@ -0,0 +1,71 @@
1
+ import { HTTPException } from "hono/http-exception";
2
+ import { beforeEach, describe, expect, it, vi } from "vitest";
3
+ import { mockUser } from "../mocks/mock-auth-service";
4
+ import { mockStore } from "../mocks/mock-store";
5
+ import { authenticateOrThrow, checkAuthorisation } from "./auth";
6
+ import { getStore } from "./store";
7
+
8
+ vi.mock("./store");
9
+
10
+ describe("authenticateOrThrow", () => {
11
+ beforeEach(() => {
12
+ vi.clearAllMocks();
13
+ });
14
+
15
+ it("allows all actions if no auth service", () => {
16
+ vi.mocked(getStore).mockReturnValue({ ...mockStore, auth: undefined });
17
+ const actual = authenticateOrThrow({
18
+ resource: "build",
19
+ action: "delete",
20
+ });
21
+
22
+ expect(actual).toBeUndefined();
23
+ });
24
+
25
+ it("throws 401 if user is missing", () => {
26
+ vi.mocked(getStore).mockReturnValue({
27
+ ...mockStore,
28
+ user: null,
29
+ });
30
+
31
+ expect(() => {
32
+ authenticateOrThrow({ resource: "build", action: "delete" });
33
+ }).toThrow(HTTPException);
34
+ });
35
+
36
+ it("returns if auth.authorise returns true", () => {
37
+ vi.mocked(getStore).mockReturnValue(mockStore);
38
+ expect(authenticateOrThrow({ resource: "build", action: "delete" })).toBeUndefined();
39
+ });
40
+
41
+ it("throws 403 if user is not authorised", () => {
42
+ vi.mocked(getStore).mockReturnValue({
43
+ ...mockStore,
44
+ user: { ...mockUser, permissions: { ...mockUser.permissions, "build:delete": false } },
45
+ });
46
+
47
+ expect(() => {
48
+ authenticateOrThrow({ resource: "build", action: "delete" });
49
+ }).toThrow(HTTPException);
50
+ });
51
+ });
52
+
53
+ describe("checkAuthorisation", () => {
54
+ it("returns true if no auth service", () => {
55
+ vi.mocked(getStore).mockReturnValue({ ...mockStore, auth: undefined });
56
+ expect(checkAuthorisation({ resource: "build", action: "delete" })).toBe(true);
57
+ });
58
+
59
+ it("returns true if user is authorised", () => {
60
+ vi.mocked(getStore).mockReturnValue(mockStore);
61
+ expect(checkAuthorisation({ resource: "build", action: "delete" })).toBe(true);
62
+ });
63
+
64
+ it("returns false if user is not authorised", () => {
65
+ vi.mocked(getStore).mockReturnValue({
66
+ ...mockStore,
67
+ user: { ...mockUser, permissions: { ...mockUser.permissions, "build:delete": false } },
68
+ });
69
+ expect(checkAuthorisation({ resource: "build", action: "delete" })).toBe(false);
70
+ });
71
+ });
@@ -0,0 +1,39 @@
1
+ import { HTTPException } from "hono/http-exception";
2
+ import type {
3
+ StoryBookerPermission,
4
+ StoryBookerPermissionKey,
5
+ } from "../adapters/_internal/auth.ts";
6
+ import { getStore } from "../utils/store.ts";
7
+
8
+ export function authenticateOrThrow(permission: StoryBookerPermission): void {
9
+ const { auth, logger, user } = getStore();
10
+ if (!auth) {
11
+ // No authentication service configured, allow all actions
12
+ return;
13
+ }
14
+
15
+ if (!user) {
16
+ throw new HTTPException(401, { message: "Unauthenticated. Please log in to continue." });
17
+ }
18
+
19
+ const key: StoryBookerPermissionKey = `${permission.resource}:${permission.action}`;
20
+ logger.debug?.("[Auth] Check authorisation for '%s'", key);
21
+
22
+ // Authorized
23
+ const hasPermission = user.permissions[key];
24
+
25
+ if (hasPermission) {
26
+ return;
27
+ }
28
+
29
+ throw new HTTPException(403, { message: `Permission denied [${key}]` });
30
+ }
31
+
32
+ export function checkAuthorisation(permission: StoryBookerPermission): boolean {
33
+ try {
34
+ authenticateOrThrow(permission);
35
+ return true;
36
+ } catch {
37
+ return false;
38
+ }
39
+ }
@@ -0,0 +1,31 @@
1
+ export const SERVICE_NAME = "StoryBooker";
2
+ export const SERVICE_SHORT_NAME = "SBR";
3
+ export const CACHE_CONTROL_PUBLIC_YEAR = "public, max-age=31536000, immutable";
4
+
5
+ export const DEFAULT_PURGE_AFTER_DAYS = 30;
6
+ export const DEFAULT_GITHUB_BRANCH = "main";
7
+ export const ONE_DAY_IN_MS: number = 24 * 60 * 60 * 1000;
8
+ export const DEFAULT_LOCALE = "en";
9
+
10
+ export const QUERY_PARAMS = {
11
+ tagId: "tagId",
12
+ uploadVariant: "variant",
13
+ redirect: "redirect",
14
+ } as const;
15
+
16
+ export const PATTERNS = {
17
+ projectId: {
18
+ message: "Should contain only lowercase alphabets, numbers and hyphen.",
19
+ pattern: "^[a-z0-9][a-z0-9-]{0,60}$",
20
+ },
21
+ } satisfies Record<
22
+ string,
23
+ {
24
+ pattern: string | RegExp;
25
+ patternGlobal?: string | RegExp;
26
+ message?: string;
27
+ }
28
+ >;
29
+
30
+ export const TagTypes = ["branch", "pr", "jira"] as const;
31
+ export const buildUploadVariants = ["storybook", "testReport", "coverage", "screenshots"] as const;
@@ -0,0 +1,10 @@
1
+ import { getStore } from "./store";
2
+
3
+ export function renderLocalTime(
4
+ value: string | Date | number,
5
+ options?: Intl.DateTimeFormatOptions,
6
+ ): string {
7
+ const { locale } = getStore();
8
+
9
+ return new Date(value).toLocaleString(locale, options);
10
+ }
@@ -0,0 +1,86 @@
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { z } from "zod";
3
+ import { mockStore } from "../mocks/mock-store";
4
+ import { parseErrorMessage } from "./error";
5
+
6
+ // Mock #store and getStoreOrNull
7
+ vi.mock("./store", () => ({
8
+ getStoreOrNull: vi.fn(),
9
+ }));
10
+
11
+ const { getStoreOrNull } = await import("./store");
12
+
13
+ describe("parseErrorMessage", () => {
14
+ beforeEach(() => {
15
+ vi.resetAllMocks();
16
+ });
17
+
18
+ it("returns custom error if errorParser is provided", () => {
19
+ const customParser = vi.fn().mockReturnValue({
20
+ errorMessage: "custom",
21
+ errorType: "custom",
22
+ });
23
+ const result = parseErrorMessage("err", customParser);
24
+ expect(result).toEqual({ errorMessage: "custom", errorType: "custom" });
25
+ expect(customParser).toHaveBeenCalledWith("err");
26
+ });
27
+
28
+ it("returns custom error if errorParser from store is provided", () => {
29
+ vi.mocked(getStoreOrNull).mockReturnValue({
30
+ ...mockStore,
31
+ errorParser: vi.fn().mockReturnValue({
32
+ errorMessage: "store",
33
+ errorType: "store",
34
+ }),
35
+ });
36
+ const result = parseErrorMessage("err");
37
+ expect(result).toEqual({ errorMessage: "store", errorType: "store" });
38
+ });
39
+
40
+ it("returns string error", () => {
41
+ vi.mocked(getStoreOrNull).mockReturnValue(mockStore);
42
+ const result = parseErrorMessage("simple error");
43
+ expect(result).toEqual({
44
+ errorMessage: "simple error",
45
+ errorType: "string",
46
+ });
47
+ });
48
+
49
+ it("returns Zod error", () => {
50
+ vi.mocked(getStoreOrNull).mockReturnValue(mockStore);
51
+ const schema = z.object({ foo: z.string() });
52
+ let newError: unknown = null;
53
+ try {
54
+ schema.parse({ foo: 123 });
55
+ } catch (error) {
56
+ newError = error;
57
+ }
58
+
59
+ const result = parseErrorMessage(newError);
60
+ expect(result).toEqual({
61
+ errorMessage: "✖ Invalid input: expected string, received number\n → at foo",
62
+ errorStatus: 400,
63
+ errorType: "Zod",
64
+ });
65
+ });
66
+
67
+ it("returns Error object", () => {
68
+ vi.mocked(getStoreOrNull).mockReturnValue(mockStore);
69
+ const err = new Error("fail");
70
+ const result = parseErrorMessage(err);
71
+ expect(result).toEqual({ errorMessage: "fail", errorType: "error" });
72
+ });
73
+
74
+ it("returns error with message property", () => {
75
+ vi.mocked(getStoreOrNull).mockReturnValue(mockStore);
76
+ const err = { message: "msg" };
77
+ const result = parseErrorMessage(err);
78
+ expect(result).toEqual({ errorMessage: "msg", errorType: "error" });
79
+ });
80
+
81
+ it("returns unknown error", () => {
82
+ vi.mocked(getStoreOrNull).mockReturnValue(mockStore);
83
+ const result = parseErrorMessage(12_345);
84
+ expect(result).toEqual({ errorMessage: "12345", errorType: "unknown" });
85
+ });
86
+ });