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,117 @@
1
+ import decompress from "decompress";
2
+ import { Buffer } from "node:buffer";
3
+ import fs from "node:fs";
4
+ import fsp from "node:fs/promises";
5
+ import os from "node:os";
6
+ import path from "node:path";
7
+ import type { StoryBookerFile } from "../adapters/_internal/storage.ts";
8
+ import { BuildsModel } from "../models/builds-model.ts";
9
+ import type { BuildUploadVariant } from "../models/builds-schema.ts";
10
+ import { generateStorageContainerId } from "../utils/adapter-utils.ts";
11
+ import { writeStreamToFile } from "../utils/file-utils.ts";
12
+ import { getMimeType } from "../utils/mime-utils.ts";
13
+ import { getStore } from "../utils/store.ts";
14
+
15
+ export async function handleProcessZip(
16
+ projectId: string,
17
+ buildId: string,
18
+ variant: BuildUploadVariant,
19
+ ): Promise<void> {
20
+ const { abortSignal, logger, storage } = getStore();
21
+ const debugLog = (...args: unknown[]): void => {
22
+ logger.log(`(${projectId}-${buildId}-${variant})`, ...args);
23
+ };
24
+
25
+ debugLog("Creating temp dir");
26
+ const localDirpath = fs.mkdtempSync(
27
+ path.join(os.tmpdir(), `storybooker-${projectId}-${buildId}-`),
28
+ );
29
+ const localZipFilePath = path.join(localDirpath, `${variant}.zip`);
30
+ const outputDirpath = path.join(localDirpath, variant);
31
+
32
+ const containerId = generateStorageContainerId(projectId);
33
+ const buildIdModel = new BuildsModel(projectId).id(buildId);
34
+
35
+ try {
36
+ await buildIdModel.update({ [variant]: "processing" });
37
+
38
+ debugLog("Downloading zip file");
39
+ const file = await storage.downloadFile(containerId, `${buildId}/${variant}.zip`, {
40
+ abortSignal,
41
+ logger,
42
+ });
43
+
44
+ if (!file.content) {
45
+ throw new Error("No file content found.");
46
+ }
47
+
48
+ if (typeof file.content === "string") {
49
+ await fsp.writeFile(localZipFilePath, file.content);
50
+ } else if (file.content instanceof Blob) {
51
+ const arrayBuffer = await file.content.arrayBuffer();
52
+ await fsp.writeFile(localZipFilePath, Buffer.from(arrayBuffer));
53
+ } else {
54
+ await writeStreamToFile(localZipFilePath, file.content);
55
+ }
56
+
57
+ debugLog("Decompress zip file");
58
+ await decompress(localZipFilePath, outputDirpath);
59
+
60
+ debugLog("Upload uncompressed dir");
61
+ await storage.uploadFiles(
62
+ containerId,
63
+ await dirpathToFiles(outputDirpath, `${buildId}/${variant}`),
64
+ { abortSignal, logger },
65
+ );
66
+
67
+ await buildIdModel.update({ [variant]: "ready" });
68
+ } finally {
69
+ debugLog("Cleaning up temp dir");
70
+ await fsp.rm(localDirpath, { force: true, recursive: true }).catch(logger.error);
71
+ }
72
+ }
73
+
74
+ async function dirpathToFiles(dirpath: string, prefix: string): Promise<StoryBookerFile[]> {
75
+ const allEntriesInDir = await fsp.readdir(dirpath, {
76
+ encoding: "utf8",
77
+ recursive: true,
78
+ withFileTypes: true,
79
+ });
80
+ const allFilesInDir = allEntriesInDir
81
+ .filter((file) => file.isFile() && !file.name.startsWith("."))
82
+ .map((file) => path.join(file.parentPath, file.name));
83
+
84
+ return allFilesInDir.map((filepath): StoryBookerFile => {
85
+ const relativePath = filepath.replace(`${dirpath}/`, "");
86
+ const content = createWebReadableStream(filepath);
87
+
88
+ return {
89
+ content,
90
+ mimeType: getMimeType(filepath),
91
+ path: path.posix.join(prefix, relativePath),
92
+ };
93
+ });
94
+ }
95
+
96
+ function createWebReadableStream(filepath: string): ReadableStream {
97
+ const readStream = fs.createReadStream(filepath);
98
+
99
+ return new ReadableStream({
100
+ cancel() {
101
+ readStream.destroy();
102
+ },
103
+ start(controller) {
104
+ readStream.on("data", (chunk) => {
105
+ controller.enqueue(chunk);
106
+ });
107
+
108
+ readStream.on("end", () => {
109
+ controller.close();
110
+ });
111
+
112
+ readStream.on("error", (error) => {
113
+ controller.error(error);
114
+ });
115
+ },
116
+ });
117
+ }
@@ -0,0 +1,65 @@
1
+ import type { LoggerAdapter } from "../adapters/_internal/logger.ts";
2
+ import { BuildsModel } from "../models/builds-model.ts";
3
+ import { ProjectsModel } from "../models/projects-model.ts";
4
+ import type { ProjectType } from "../models/projects-schema.ts";
5
+ import { TagsModel } from "../models/tags-model.ts";
6
+ import { DEFAULT_PURGE_AFTER_DAYS, ONE_DAY_IN_MS } from "../utils/constants.ts";
7
+ import { getStore } from "../utils/store.ts";
8
+
9
+ export type HandlePurge = (
10
+ params: { projectId?: string },
11
+ options: { abortSignal?: AbortSignal; logger?: LoggerAdapter },
12
+ ) => Promise<void>;
13
+
14
+ export const handlePurge: HandlePurge = async ({ projectId }) => {
15
+ const projectModel = new ProjectsModel();
16
+ if (projectId) {
17
+ const project = await projectModel.get(projectId);
18
+ await purgeProject(project);
19
+ } else {
20
+ const projects = await projectModel.list();
21
+ const promises = projects.map((project) => purgeProject(project));
22
+ await Promise.allSettled(promises);
23
+ }
24
+ };
25
+
26
+ async function purgeProject(project: ProjectType): Promise<void> {
27
+ const { locale, logger } = getStore();
28
+ const {
29
+ id: projectId,
30
+ gitHubDefaultBranch,
31
+ latestBuildId,
32
+ purgeBuildsAfterDays = DEFAULT_PURGE_AFTER_DAYS,
33
+ } = project;
34
+ const expiryTime = new Date(Date.now() - purgeBuildsAfterDays * ONE_DAY_IN_MS);
35
+ logger.log(
36
+ `[Project: ${projectId}] Purge builds which were last modified more than ${purgeBuildsAfterDays} days ago - before ${new Date(
37
+ expiryTime,
38
+ ).toLocaleString(locale)}`,
39
+ );
40
+
41
+ const buildsModel = new BuildsModel(projectId);
42
+ const expiredBuilds = await buildsModel.list({
43
+ filter: (item) => item.id !== latestBuildId && new Date(item.updatedAt) < expiryTime,
44
+ });
45
+ for (const build of expiredBuilds) {
46
+ // oxlint-disable-next-line no-await-in-loop
47
+ await buildsModel.delete(build.id, true);
48
+ }
49
+ logger.log(`[Project: ${projectId}] Purged ${expiredBuilds.length} expired builds.`);
50
+
51
+ const tagsModel = new TagsModel(projectId);
52
+ const emptyTags = await tagsModel.list({
53
+ filter: (item) => {
54
+ if (item.type === "branch" && item.value === gitHubDefaultBranch) {
55
+ return false;
56
+ }
57
+ return item.buildsCount === 0;
58
+ },
59
+ });
60
+ for (const tag of emptyTags) {
61
+ // oxlint-disable-next-line no-await-in-loop
62
+ await tagsModel.delete(tag.id);
63
+ }
64
+ logger.log(`[Project: ${projectId}] Purged ${emptyTags.length} empty tags...`);
65
+ }
@@ -0,0 +1,101 @@
1
+ import { SuperHeaders } from "@remix-run/headers";
2
+ import { HTTPException } from "hono/http-exception";
3
+ import path from "node:path";
4
+ import { urlBuilder } from "../urls.ts";
5
+ import { generateStorageContainerId } from "../utils/adapter-utils.ts";
6
+ import { authenticateOrThrow } from "../utils/auth.ts";
7
+ import { CACHE_CONTROL_PUBLIC_YEAR, SERVICE_NAME } from "../utils/constants.ts";
8
+ import { getMimeType } from "../utils/mime-utils.ts";
9
+ import { getStore } from "../utils/store.ts";
10
+
11
+ export async function handleServeStoryBook({
12
+ buildId,
13
+ filepath,
14
+ projectId,
15
+ }: {
16
+ buildId: string;
17
+ projectId: string;
18
+ filepath: string;
19
+ }): Promise<Response> {
20
+ const { abortSignal, logger, storage } = getStore();
21
+ const storageFilepath = path.posix.join(buildId, filepath);
22
+ authenticateOrThrow({ action: "read", projectId, resource: "build" });
23
+
24
+ try {
25
+ const { content, mimeType } = await storage.downloadFile(
26
+ generateStorageContainerId(projectId),
27
+ storageFilepath,
28
+ { abortSignal, logger },
29
+ );
30
+
31
+ if (!content) {
32
+ throw new HTTPException(404, { message: "File does not contain any content" });
33
+ }
34
+
35
+ const headers = new SuperHeaders();
36
+ headers.contentType = mimeType ?? getMimeType(filepath);
37
+ headers.cacheControl = CACHE_CONTROL_PUBLIC_YEAR;
38
+
39
+ if (filepath.endsWith("index.html")) {
40
+ // Appending custom UI to index.html
41
+ const data = typeof content === "string" ? content : await new Response(content).text();
42
+ const bodyWithBackButton = data.replace(
43
+ `</body>`,
44
+ `
45
+ <div><a id="view-all" href="${urlBuilder.buildsList(projectId)}"
46
+ style="position: fixed; bottom: 0.5rem; left: 0.5rem; z-index: 9999; padding: 0.25rem 0.5rem; background-color: black; color: white; border-radius: 0.25rem; text-decoration: none; font-size: 1rem; font-face: sans-serif; font-weight: 400;">
47
+ ← ${SERVICE_NAME}
48
+ </a></div>
49
+
50
+ ${relativeHrefScripts}
51
+ </body>`,
52
+ );
53
+
54
+ return new Response(bodyWithBackButton, { headers, status: 200 });
55
+ }
56
+
57
+ if (filepath.endsWith("iframe.html")) {
58
+ // Appending custom UI to index.html
59
+ const data = typeof content === "string" ? content : await new Response(content).text();
60
+ const bodyWithBackButton = data.replace(
61
+ `</body>`,
62
+ `
63
+ ${relativeHrefScripts}
64
+ </body>`,
65
+ );
66
+
67
+ return new Response(bodyWithBackButton, { headers, status: 200 });
68
+ }
69
+
70
+ if (content instanceof ReadableStream) {
71
+ const body = await new Response(content).arrayBuffer();
72
+ return new Response(body, { headers, status: 200 });
73
+ }
74
+
75
+ return new Response(content, { headers, status: 200 });
76
+ } catch (error) {
77
+ throw new HTTPException(404, { message: "File not found", cause: error });
78
+ }
79
+ }
80
+
81
+ const relativeHrefScripts = `
82
+ <script defer>
83
+ // script to replace absolute links with relative links
84
+ window.addEventListener('load', () => {
85
+ const linkElements = document.querySelectorAll("[href^='/']");
86
+ linkElements.forEach((el) => {
87
+ const href = typeof el.href === "string" ? el.href : el.href.baseVal;
88
+ const newHref = "." + href.replace(origin, "");
89
+ el.setAttribute("href", newHref);
90
+ });
91
+ const mediaElements = document.querySelectorAll("[src^='/']");
92
+ mediaElements.forEach((el) => {
93
+ const newSrc = el.src.replace(origin, ".");
94
+ el.setAttribute("src", newSrc);
95
+ if (el.hasAttribute("srcset")) {
96
+ const newSrcset = el.srcset.replaceAll(origin, ".");
97
+ el.setAttribute("srcset", newSrcset);
98
+ }
99
+ });
100
+ }, { once: true });
101
+ </script>`;
package/src/index.ts CHANGED
@@ -1,17 +1,82 @@
1
- import yargs from "yargs";
2
- import { hideBin } from "yargs/helpers";
3
- import { name, version } from "../package.json" with { type: "json" };
4
- import { createCommandModule } from "./commands/create";
5
- import { purgeCommandModule } from "./commands/purge";
6
- import { testCommandModule } from "./commands/test";
1
+ import { SuperHeaders } from "@remix-run/headers";
2
+ import { Hono } from "hono";
3
+ import { logger as loggerMiddleware } from "hono/logger";
4
+ import type { TimingVariables } from "hono/timing";
5
+ import { createConsoleLoggerAdapter, type StoryBookerUser } from "./adapters/_internal/index.ts";
6
+ import { handlePurge, type HandlePurge } from "./handlers/handle-purge.ts";
7
+ import { appRouter } from "./routers/_app-router.ts";
8
+ import type { PurgeHandlerOptions, RouterOptions } from "./types.ts";
9
+ import { DEFAULT_LOCALE } from "./utils/constants.ts";
10
+ import {
11
+ onUnhandledErrorHandler,
12
+ parseErrorMessage,
13
+ prettifyZodValidationErrorMiddleware,
14
+ } from "./utils/error.ts";
15
+ import { htmxRedirectResponse } from "./utils/response.ts";
16
+ import { localStore, setupStore } from "./utils/store.ts";
7
17
 
8
- await yargs(hideBin(process.argv))
9
- .scriptName(name)
10
- .usage(`npx -y $0 [command] (options)`)
11
- .version(version)
12
- .command(createCommandModule)
13
- .command(purgeCommandModule)
14
- .command(testCommandModule)
15
- .alias("h", "help")
16
- .alias("v", "version")
17
- .parse();
18
+ if ("setEncoding" in process.stdout) {
19
+ process.stdout.setEncoding("utf8");
20
+ }
21
+ export { appRouter, openapiConfig } from "./routers/_app-router.ts";
22
+
23
+ /**
24
+ * Callback to create a Hono App based on provided options.
25
+ * @param options Options for creating a request handler.
26
+ * @returns The Hono App which can be used wherever Hono is supported.
27
+ */
28
+ export function createHonoRouter<User extends StoryBookerUser>(
29
+ options: RouterOptions<User>,
30
+ ): Hono<{ Variables: TimingVariables }> {
31
+ const logger = options.logger ?? createConsoleLoggerAdapter();
32
+ const middlewares = options.config?.middlewares ?? [];
33
+ const initPromises = Promise.allSettled([
34
+ options.auth?.init?.({ logger }).catch(logger.error),
35
+ options.database.init?.({ logger }).catch(logger.error),
36
+ options.storage.init?.({ logger }).catch(logger.error),
37
+ ]);
38
+
39
+ return new Hono<{ Variables: TimingVariables }>({ strict: false })
40
+ .use(
41
+ loggerMiddleware(logger.log),
42
+ prettifyZodValidationErrorMiddleware(logger),
43
+ ...middlewares,
44
+ setupStore<User>(options, initPromises),
45
+ htmxRedirectResponse(),
46
+ )
47
+ .route("/", appRouter)
48
+ .onError(onUnhandledErrorHandler<User>(options));
49
+ }
50
+
51
+ /**
52
+ * Callback to create a purge-handler based on provided options.
53
+ * Purging deletes all builds older than certain days based on Project's configuration.
54
+ *
55
+ * Note: The latest build on project's default branch is not deleted.
56
+ */
57
+ export function createPurgeHandler(options: PurgeHandlerOptions): HandlePurge {
58
+ const logger = options.logger ?? createConsoleLoggerAdapter();
59
+
60
+ return async (...params: Parameters<HandlePurge>): Promise<void> => {
61
+ const dummyRequest = new Request("http://0.0.0.0/");
62
+ localStore.enterWith({
63
+ abortSignal: params[1].abortSignal,
64
+ database: options.database,
65
+ errorParser: options.errorParser,
66
+ headers: new SuperHeaders(),
67
+ locale: DEFAULT_LOCALE,
68
+ logger: params[1]?.logger ?? logger,
69
+ prefix: "/",
70
+ request: dummyRequest,
71
+ storage: options.storage,
72
+ url: dummyRequest.url,
73
+ user: null,
74
+ });
75
+
76
+ try {
77
+ await handlePurge(...params);
78
+ } catch (error) {
79
+ logger.error("PurgeError", parseErrorMessage(error, options.errorParser).errorMessage);
80
+ }
81
+ };
82
+ }
@@ -0,0 +1,51 @@
1
+ // oxlint-disable require-await
2
+
3
+ import {
4
+ StoryBookerPermissionsAllEnabled,
5
+ type AuthAdapter,
6
+ type StoryBookerUser,
7
+ } from "../adapters/_internal/auth.ts";
8
+
9
+ export const mockUser: StoryBookerUser = {
10
+ displayName: "Test User",
11
+ id: "test-user-id",
12
+ imageUrl: "https://example.com/avatar.png",
13
+ title: "Tester",
14
+ permissions: StoryBookerPermissionsAllEnabled,
15
+ };
16
+
17
+ export function mockAuthService(
18
+ permissions: Partial<StoryBookerUser["permissions"]> = {},
19
+ ): AuthAdapter {
20
+ const user = {
21
+ ...mockUser,
22
+ permissions: {
23
+ ...mockUser.permissions,
24
+ ...permissions,
25
+ },
26
+ };
27
+
28
+ return {
29
+ metadata: { name: "MockAuthService" },
30
+
31
+ init: async (_options): Promise<void> => {
32
+ // Mock init logic if needed
33
+ },
34
+ getUserDetails: async (_options) => {
35
+ // Always return the mock user
36
+ return user;
37
+ },
38
+ login: async (_options) => {
39
+ // Return a mock Response
40
+ return new Response("Logged in", { status: 200 });
41
+ },
42
+ logout: async (_user, _options) => {
43
+ // Return a mock Response
44
+ return new Response("Logged out", { status: 200 });
45
+ },
46
+ renderAccountDetails: async (_user, _options) => {
47
+ // Return mock HTML
48
+ return "<div>Mock Account Details</div>";
49
+ },
50
+ };
51
+ }
@@ -0,0 +1,26 @@
1
+ // oxlint-disable sort-keys
2
+
3
+ import { SuperHeaders } from "@remix-run/headers";
4
+ import { vi } from "vitest";
5
+ import type { DatabaseAdapter, StorageAdapter } from "../adapters/_internal/index.ts";
6
+ import type { Store } from "../utils/store.ts";
7
+ import { mockAuthService, mockUser } from "./mock-auth-service";
8
+
9
+ export const mockStore: Store = {
10
+ abortSignal: undefined,
11
+ auth: mockAuthService(),
12
+ headers: new SuperHeaders(),
13
+ logger: {
14
+ metadata: { name: "Mock Logger" },
15
+ debug: vi.fn(),
16
+ error: vi.fn(),
17
+ log: vi.fn(),
18
+ },
19
+ request: {} as Request,
20
+ database: {} as DatabaseAdapter,
21
+ locale: "en",
22
+ prefix: "/",
23
+ storage: {} as StorageAdapter,
24
+ url: "http://localhost/",
25
+ user: mockUser,
26
+ };