@storyshelf/core 0.1.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 (189) hide show
  1. package/README.md +127 -0
  2. package/dist/adapter/auth.d.mts +50 -0
  3. package/dist/adapter/auth.mjs +10 -0
  4. package/dist/adapter/auth.mjs.map +1 -0
  5. package/dist/adapter/capture-runner.d.mts +70 -0
  6. package/dist/adapter/capture-runner.mjs +4 -0
  7. package/dist/adapter/database.d.mts +43 -0
  8. package/dist/adapter/database.mjs +4 -0
  9. package/dist/adapter/git-host/helpers.d.mts +12 -0
  10. package/dist/adapter/git-host/helpers.mjs +29 -0
  11. package/dist/adapter/git-host/helpers.mjs.map +1 -0
  12. package/dist/adapter/git-host.d.mts +46 -0
  13. package/dist/adapter/git-host.mjs +4 -0
  14. package/dist/adapter/storage.d.mts +22 -0
  15. package/dist/adapter/storage.mjs +4 -0
  16. package/dist/adapters/capture-queue.d.mts +49 -0
  17. package/dist/adapters/metadata.d.mts +24 -0
  18. package/dist/adapters/webhook-events.mjs +39 -0
  19. package/dist/adapters/webhook-events.mjs.map +1 -0
  20. package/dist/capture/adapter.d.mts +29 -0
  21. package/dist/capture/adapter.mjs +13 -0
  22. package/dist/capture/adapter.mjs.map +1 -0
  23. package/dist/capture/orchestrator.d.mts +23 -0
  24. package/dist/capture/orchestrator.mjs +103 -0
  25. package/dist/capture/orchestrator.mjs.map +1 -0
  26. package/dist/capture/pipeline.d.mts +41 -0
  27. package/dist/capture/pipeline.mjs +119 -0
  28. package/dist/capture/pipeline.mjs.map +1 -0
  29. package/dist/capture/queue.d.mts +35 -0
  30. package/dist/capture/queue.mjs +96 -0
  31. package/dist/capture/queue.mjs.map +1 -0
  32. package/dist/capture/status-fanout.mjs +66 -0
  33. package/dist/capture/status-fanout.mjs.map +1 -0
  34. package/dist/capture/storybook.d.mts +14 -0
  35. package/dist/capture/storybook.mjs +40 -0
  36. package/dist/capture/storybook.mjs.map +1 -0
  37. package/dist/config.d.mts +68 -0
  38. package/dist/config.mjs +69 -0
  39. package/dist/config.mjs.map +1 -0
  40. package/dist/ddl.d.mts +7 -0
  41. package/dist/ddl.mjs +155 -0
  42. package/dist/ddl.mjs.map +1 -0
  43. package/dist/diff/engine.d.mts +8 -0
  44. package/dist/diff/engine.mjs +79 -0
  45. package/dist/diff/engine.mjs.map +1 -0
  46. package/dist/diff/options.d.mts +40 -0
  47. package/dist/diff/options.mjs +15 -0
  48. package/dist/diff/options.mjs.map +1 -0
  49. package/dist/index.d.mts +48 -0
  50. package/dist/index.mjs +327 -0
  51. package/dist/index.mjs.map +1 -0
  52. package/dist/logger.d.mts +39 -0
  53. package/dist/logger.mjs +35 -0
  54. package/dist/logger.mjs.map +1 -0
  55. package/dist/middleware/csrf.mjs +39 -0
  56. package/dist/middleware/csrf.mjs.map +1 -0
  57. package/dist/middleware/rate-limit.mjs +38 -0
  58. package/dist/middleware/rate-limit.mjs.map +1 -0
  59. package/dist/models/baseline.d.mts +20 -0
  60. package/dist/models/baseline.mjs +89 -0
  61. package/dist/models/baseline.mjs.map +1 -0
  62. package/dist/models/build.d.mts +35 -0
  63. package/dist/models/build.mjs +77 -0
  64. package/dist/models/build.mjs.map +1 -0
  65. package/dist/models/comment.d.mts +20 -0
  66. package/dist/models/comment.mjs +42 -0
  67. package/dist/models/comment.mjs.map +1 -0
  68. package/dist/models/label.d.mts +43 -0
  69. package/dist/models/label.mjs +107 -0
  70. package/dist/models/label.mjs.map +1 -0
  71. package/dist/models/member.d.mts +27 -0
  72. package/dist/models/member.mjs +54 -0
  73. package/dist/models/member.mjs.map +1 -0
  74. package/dist/models/project.d.mts +42 -0
  75. package/dist/models/project.mjs +78 -0
  76. package/dist/models/project.mjs.map +1 -0
  77. package/dist/models/snapshot.d.mts +46 -0
  78. package/dist/models/snapshot.mjs +75 -0
  79. package/dist/models/snapshot.mjs.map +1 -0
  80. package/dist/models/status-config.mjs +61 -0
  81. package/dist/models/status-config.mjs.map +1 -0
  82. package/dist/models/token.d.mts +33 -0
  83. package/dist/models/token.mjs +60 -0
  84. package/dist/models/token.mjs.map +1 -0
  85. package/dist/models/webhook.d.mts +37 -0
  86. package/dist/models/webhook.mjs +67 -0
  87. package/dist/models/webhook.mjs.map +1 -0
  88. package/dist/pages/build-detail.mjs +333 -0
  89. package/dist/pages/build-detail.mjs.map +1 -0
  90. package/dist/pages/build-diff.mjs +437 -0
  91. package/dist/pages/build-diff.mjs.map +1 -0
  92. package/dist/pages/compute-jobs.mjs +196 -0
  93. package/dist/pages/compute-jobs.mjs.map +1 -0
  94. package/dist/pages/login.mjs +77 -0
  95. package/dist/pages/login.mjs.map +1 -0
  96. package/dist/pages/project-builds.mjs +228 -0
  97. package/dist/pages/project-builds.mjs.map +1 -0
  98. package/dist/pages/project-create.mjs +168 -0
  99. package/dist/pages/project-create.mjs.map +1 -0
  100. package/dist/pages/project-settings.mjs +111 -0
  101. package/dist/pages/project-settings.mjs.map +1 -0
  102. package/dist/pages/projects.mjs +178 -0
  103. package/dist/pages/projects.mjs.map +1 -0
  104. package/dist/pages/root.mjs +90 -0
  105. package/dist/pages/root.mjs.map +1 -0
  106. package/dist/pages/settings-general.mjs +191 -0
  107. package/dist/pages/settings-general.mjs.map +1 -0
  108. package/dist/pages/settings-labels.mjs +149 -0
  109. package/dist/pages/settings-labels.mjs.map +1 -0
  110. package/dist/pages/settings-members.mjs +124 -0
  111. package/dist/pages/settings-members.mjs.map +1 -0
  112. package/dist/pages/settings-status.mjs +150 -0
  113. package/dist/pages/settings-status.mjs.map +1 -0
  114. package/dist/pages/settings-tokens.mjs +95 -0
  115. package/dist/pages/settings-tokens.mjs.map +1 -0
  116. package/dist/pages/settings-webhooks.mjs +149 -0
  117. package/dist/pages/settings-webhooks.mjs.map +1 -0
  118. package/dist/retention/purge.d.mts +28 -0
  119. package/dist/retention/purge.mjs +66 -0
  120. package/dist/retention/purge.mjs.map +1 -0
  121. package/dist/routers/admin.mjs +41 -0
  122. package/dist/routers/admin.mjs.map +1 -0
  123. package/dist/routers/assets.mjs +24 -0
  124. package/dist/routers/assets.mjs.map +1 -0
  125. package/dist/routers/auth.mjs +77 -0
  126. package/dist/routers/auth.mjs.map +1 -0
  127. package/dist/routers/builds.handlers.mjs +87 -0
  128. package/dist/routers/builds.handlers.mjs.map +1 -0
  129. package/dist/routers/builds.mjs +162 -0
  130. package/dist/routers/builds.mjs.map +1 -0
  131. package/dist/routers/comments.mjs +89 -0
  132. package/dist/routers/comments.mjs.map +1 -0
  133. package/dist/routers/helpers.mjs +76 -0
  134. package/dist/routers/helpers.mjs.map +1 -0
  135. package/dist/routers/htmx.mjs +18 -0
  136. package/dist/routers/htmx.mjs.map +1 -0
  137. package/dist/routers/labels.mjs +77 -0
  138. package/dist/routers/labels.mjs.map +1 -0
  139. package/dist/routers/media.mjs +54 -0
  140. package/dist/routers/media.mjs.map +1 -0
  141. package/dist/routers/members.mjs +101 -0
  142. package/dist/routers/members.mjs.map +1 -0
  143. package/dist/routers/projects.mjs +121 -0
  144. package/dist/routers/projects.mjs.map +1 -0
  145. package/dist/routers/schemas.mjs +188 -0
  146. package/dist/routers/schemas.mjs.map +1 -0
  147. package/dist/routers/settings.mjs +235 -0
  148. package/dist/routers/settings.mjs.map +1 -0
  149. package/dist/routers/snapshots.mjs +143 -0
  150. package/dist/routers/snapshots.mjs.map +1 -0
  151. package/dist/routers/status-configs.mjs +100 -0
  152. package/dist/routers/status-configs.mjs.map +1 -0
  153. package/dist/routers/tokens.mjs +80 -0
  154. package/dist/routers/tokens.mjs.map +1 -0
  155. package/dist/routers/ui.mjs +142 -0
  156. package/dist/routers/ui.mjs.map +1 -0
  157. package/dist/routers/webhooks.mjs +89 -0
  158. package/dist/routers/webhooks.mjs.map +1 -0
  159. package/dist/schema.d.mts +4372 -0
  160. package/dist/schema.mjs +158 -0
  161. package/dist/schema.mjs.map +1 -0
  162. package/dist/store.mjs +18 -0
  163. package/dist/store.mjs.map +1 -0
  164. package/dist/types.d.mts +18 -0
  165. package/dist/types.mjs +49 -0
  166. package/dist/types.mjs.map +1 -0
  167. package/dist/ui/components.mjs +127 -0
  168. package/dist/ui/components.mjs.map +1 -0
  169. package/dist/ui/document.d.mts +8 -0
  170. package/dist/ui/document.mjs +338 -0
  171. package/dist/ui/document.mjs.map +1 -0
  172. package/dist/ui/styles.mjs +173 -0
  173. package/dist/ui/styles.mjs.map +1 -0
  174. package/dist/ui/theme.mjs +42 -0
  175. package/dist/ui/theme.mjs.map +1 -0
  176. package/dist/urls.d.mts +19 -0
  177. package/dist/urls.mjs +23 -0
  178. package/dist/urls.mjs.map +1 -0
  179. package/dist/utils/encrypt.mjs +49 -0
  180. package/dist/utils/encrypt.mjs.map +1 -0
  181. package/dist/utils/hash.mjs +22 -0
  182. package/dist/utils/hash.mjs.map +1 -0
  183. package/dist/utils/paths.d.mts +11 -0
  184. package/dist/utils/paths.mjs +23 -0
  185. package/dist/utils/paths.mjs.map +1 -0
  186. package/dist/utils/ulid.d.mts +8 -0
  187. package/dist/utils/ulid.mjs +41 -0
  188. package/dist/utils/ulid.mjs.map +1 -0
  189. package/package.json +162 -0
@@ -0,0 +1,69 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { z } from "zod";
5
+ //#region src/config.ts
6
+ const viewportSchema = z.object({
7
+ name: z.string().min(1),
8
+ width: z.number().int().positive(),
9
+ height: z.number().int().positive()
10
+ });
11
+ const brandThemeSchema = z.object({
12
+ accent: z.string(),
13
+ surface: z.object({
14
+ base: z.string(),
15
+ card: z.string()
16
+ }),
17
+ text: z.object({
18
+ primary: z.string(),
19
+ secondary: z.string()
20
+ }),
21
+ border: z.string(),
22
+ status: z.object({
23
+ approved: z.string(),
24
+ new: z.string(),
25
+ rejected: z.string()
26
+ })
27
+ });
28
+ const adapterSnapshotSchema = z.object({
29
+ name: z.string(),
30
+ version: z.string(),
31
+ description: z.string().optional(),
32
+ kind: z.string()
33
+ });
34
+ const shelfConfigSchema = z.object({
35
+ secret: z.string().min(1).optional(),
36
+ publishedBaseDomain: z.string().optional(),
37
+ captureConcurrency: z.number().int().positive().optional(),
38
+ scratchDir: z.string().optional(),
39
+ purgeTtlDays: z.number().int().positive().optional(),
40
+ viewports: z.array(viewportSchema).min(1, "at least one viewport required").optional(),
41
+ adapters: z.record(z.string(), adapterSnapshotSchema).optional()
42
+ }).strict();
43
+ const uiConfigSchema = z.object({
44
+ name: z.string().optional(),
45
+ logo: z.url().optional(),
46
+ favicon: z.url().optional(),
47
+ lightTheme: brandThemeSchema.optional(),
48
+ darkTheme: brandThemeSchema.optional()
49
+ }).strict();
50
+ function validateConfig(config) {
51
+ const result = shelfConfigSchema.safeParse(config);
52
+ if (!result.success) {
53
+ const issues = result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
54
+ throw new Error(`Invalid ShelfConfig: ${issues}`);
55
+ }
56
+ return result.data;
57
+ }
58
+ function validateUiConfig(config) {
59
+ const result = uiConfigSchema.safeParse(config);
60
+ if (!result.success) {
61
+ const issues = result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
62
+ throw new Error(`Invalid UIConfig: ${issues}`);
63
+ }
64
+ return result.data;
65
+ }
66
+ //#endregion
67
+ export { shelfConfigSchema, uiConfigSchema, validateConfig, validateUiConfig };
68
+
69
+ //# sourceMappingURL=config.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.mjs","names":[],"sources":["../src/config.ts"],"sourcesContent":["import { z } from \"zod\";\nimport type { Logger } from \"pino\";\n\nimport type { AuthAdapter } from \"./adapters/auth.ts\";\nimport type { CaptureQueue } from \"./adapters/capture-queue.ts\";\nimport type { CaptureRunner } from \"./adapters/capture-runner.ts\";\nimport type { DatabaseAdapter } from \"./adapters/database.ts\";\nimport type { GitHostProvider } from \"./adapters/git-host/index.ts\";\nimport type { StorageAdapter } from \"./adapters/storage.ts\";\n\nexport interface BrandTheme {\n accent: string;\n surface: { base: string; card: string };\n text: { primary: string; secondary: string };\n border: string;\n status: { approved: string; new: string; rejected: string };\n}\n\nexport interface UIConfig {\n name?: string;\n logo?: string;\n favicon?: string;\n lightTheme?: BrandTheme;\n darkTheme?: BrandTheme;\n}\n\nconst viewportSchema = z.object({\n name: z.string().min(1),\n width: z.number().int().positive(),\n height: z.number().int().positive(),\n});\n\nconst brandThemeSchema = z.object({\n accent: z.string(),\n surface: z.object({ base: z.string(), card: z.string() }),\n text: z.object({ primary: z.string(), secondary: z.string() }),\n border: z.string(),\n status: z.object({ approved: z.string(), new: z.string(), rejected: z.string() }),\n});\n\nconst adapterSnapshotSchema = z.object({\n name: z.string(),\n version: z.string(),\n description: z.string().optional(),\n kind: z.string(),\n});\n\nexport const shelfConfigSchema = z\n .object({\n secret: z.string().min(1).optional(),\n publishedBaseDomain: z.string().optional(),\n captureConcurrency: z.number().int().positive().optional(),\n scratchDir: z.string().optional(),\n purgeTtlDays: z.number().int().positive().optional(),\n viewports: z.array(viewportSchema).min(1, \"at least one viewport required\").optional(),\n adapters: z.record(z.string(), adapterSnapshotSchema).optional(),\n })\n .strict();\n\nexport const uiConfigSchema = z\n .object({\n name: z.string().optional(),\n logo: z.url().optional(),\n favicon: z.url().optional(),\n lightTheme: brandThemeSchema.optional(),\n darkTheme: brandThemeSchema.optional(),\n })\n .strict();\n\nexport type ShelfConfig = z.infer<typeof shelfConfigSchema>;\n\nexport function validateConfig(config: Record<string, unknown>): ShelfConfig {\n const result = shelfConfigSchema.safeParse(config);\n if (!result.success) {\n const issues = result.error.issues.map((i) => `${i.path.join(\".\")}: ${i.message}`).join(\"; \");\n throw new Error(`Invalid ShelfConfig: ${issues}`);\n }\n return result.data;\n}\n\nexport function validateUiConfig(config: Record<string, unknown>): UIConfig {\n const result = uiConfigSchema.safeParse(config);\n if (!result.success) {\n const issues = result.error.issues.map((i) => `${i.path.join(\".\")}: ${i.message}`).join(\"; \");\n throw new Error(`Invalid UIConfig: ${issues}`);\n }\n return result.data;\n}\n\nexport interface ShelfOptions {\n database: DatabaseAdapter;\n storage: StorageAdapter;\n captureRunner?: CaptureRunner;\n captureQueue?: CaptureQueue;\n auth?: AuthAdapter;\n gitHosts?: GitHostProvider[];\n logger?: Logger;\n ui?: UIConfig;\n config?: ShelfConfig;\n}\n"],"mappings":";;;;;AA0BA,MAAM,iBAAiB,EAAE,OAAO;CAC9B,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;CACtB,OAAO,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS;CACjC,QAAQ,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS;AACpC,CAAC;AAED,MAAM,mBAAmB,EAAE,OAAO;CAChC,QAAQ,EAAE,OAAO;CACjB,SAAS,EAAE,OAAO;EAAE,MAAM,EAAE,OAAO;EAAG,MAAM,EAAE,OAAO;CAAE,CAAC;CACxD,MAAM,EAAE,OAAO;EAAE,SAAS,EAAE,OAAO;EAAG,WAAW,EAAE,OAAO;CAAE,CAAC;CAC7D,QAAQ,EAAE,OAAO;CACjB,QAAQ,EAAE,OAAO;EAAE,UAAU,EAAE,OAAO;EAAG,KAAK,EAAE,OAAO;EAAG,UAAU,EAAE,OAAO;CAAE,CAAC;AAClF,CAAC;AAED,MAAM,wBAAwB,EAAE,OAAO;CACrC,MAAM,EAAE,OAAO;CACf,SAAS,EAAE,OAAO;CAClB,aAAa,EAAE,OAAO,CAAC,CAAC,SAAS;CACjC,MAAM,EAAE,OAAO;AACjB,CAAC;AAED,MAAa,oBAAoB,EAC9B,OAAO;CACN,QAAQ,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;CACnC,qBAAqB,EAAE,OAAO,CAAC,CAAC,SAAS;CACzC,oBAAoB,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS;CACzD,YAAY,EAAE,OAAO,CAAC,CAAC,SAAS;CAChC,cAAc,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS;CACnD,WAAW,EAAE,MAAM,cAAc,CAAC,CAAC,IAAI,GAAG,gCAAgC,CAAC,CAAC,SAAS;CACrF,UAAU,EAAE,OAAO,EAAE,OAAO,GAAG,qBAAqB,CAAC,CAAC,SAAS;AACjE,CAAC,CAAC,CACD,OAAO;AAEV,MAAa,iBAAiB,EAC3B,OAAO;CACN,MAAM,EAAE,OAAO,CAAC,CAAC,SAAS;CAC1B,MAAM,EAAE,IAAI,CAAC,CAAC,SAAS;CACvB,SAAS,EAAE,IAAI,CAAC,CAAC,SAAS;CAC1B,YAAY,iBAAiB,SAAS;CACtC,WAAW,iBAAiB,SAAS;AACvC,CAAC,CAAC,CACD,OAAO;AAIV,SAAgB,eAAe,QAA8C;CAC3E,MAAM,SAAS,kBAAkB,UAAU,MAAM;CACjD,IAAI,CAAC,OAAO,SAAS;EACnB,MAAM,SAAS,OAAO,MAAM,OAAO,KAAK,MAAM,GAAG,EAAE,KAAK,KAAK,GAAG,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC,KAAK,IAAI;EAC5F,MAAM,IAAI,MAAM,wBAAwB,QAAQ;CAClD;CACA,OAAO,OAAO;AAChB;AAEA,SAAgB,iBAAiB,QAA2C;CAC1E,MAAM,SAAS,eAAe,UAAU,MAAM;CAC9C,IAAI,CAAC,OAAO,SAAS;EACnB,MAAM,SAAS,OAAO,MAAM,OAAO,KAAK,MAAM,GAAG,EAAE,KAAK,KAAK,GAAG,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC,KAAK,IAAI;EAC5F,MAAM,IAAI,MAAM,qBAAqB,QAAQ;CAC/C;CACA,OAAO,OAAO;AAChB"}
package/dist/ddl.d.mts ADDED
@@ -0,0 +1,7 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ //#region src/ddl.d.ts
4
+ declare const DDL = "\nCREATE TABLE IF NOT EXISTS projects (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n slug TEXT NOT NULL UNIQUE,\n git_repository TEXT,\n git_default_branch TEXT NOT NULL DEFAULT 'main',\n pixel_threshold REAL NOT NULL DEFAULT 0.1,\n max_diff_ratio REAL NOT NULL DEFAULT 0.01,\n public_branch_regex TEXT,\n storybook_meta TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS project_status_configs (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n provider TEXT NOT NULL,\n config TEXT NOT NULL,\n token_encrypted TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS builds (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n git_sha TEXT NOT NULL,\n git_branch TEXT NOT NULL,\n is_default INTEGER NOT NULL DEFAULT 0,\n author_email TEXT,\n author_name TEXT,\n message TEXT,\n public INTEGER NOT NULL DEFAULT 0,\n status TEXT NOT NULL DEFAULT 'pending',\n snapshot_count INTEGER NOT NULL DEFAULT 0,\n changed_count INTEGER NOT NULL DEFAULT 0,\n approved_count INTEGER NOT NULL DEFAULT 0,\n rejected_count INTEGER NOT NULL DEFAULT 0,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n);\nCREATE UNIQUE INDEX IF NOT EXISTS builds_project_gitsha_idx ON builds (project_id, git_sha);\nCREATE INDEX IF NOT EXISTS builds_git_branch_idx ON builds (git_branch);\nCREATE TABLE IF NOT EXISTS snapshots (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n build_id TEXT NOT NULL REFERENCES builds(id) ON DELETE CASCADE,\n story_id TEXT NOT NULL,\n story_name TEXT NOT NULL,\n story_title TEXT NOT NULL,\n story_import_path TEXT,\n viewport_name TEXT NOT NULL DEFAULT 'desktop',\n viewport_width INTEGER NOT NULL DEFAULT 1280,\n viewport_height INTEGER NOT NULL DEFAULT 720,\n screenshot_path TEXT NOT NULL,\n diff_path TEXT,\n diff_pixels INTEGER,\n diff_ratio REAL,\n diff_passed INTEGER,\n status TEXT NOT NULL DEFAULT 'pending',\n reviewed_by TEXT,\n reviewed_at TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n);\nCREATE UNIQUE INDEX IF NOT EXISTS snapshots_build_story_viewport_idx ON snapshots (build_id, story_id, viewport_name);\nCREATE INDEX IF NOT EXISTS snapshots_build_id_idx ON snapshots (build_id);\nCREATE TABLE IF NOT EXISTS baselines (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n story_id TEXT NOT NULL,\n viewport_name TEXT NOT NULL DEFAULT 'desktop',\n branch TEXT NOT NULL,\n snapshot_id TEXT,\n screenshot_path TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n);\nCREATE UNIQUE INDEX IF NOT EXISTS baselines_project_story_viewport_branch_idx ON baselines (project_id, story_id, viewport_name, branch);\nCREATE INDEX IF NOT EXISTS baselines_project_story_idx ON baselines (project_id, story_id);\nCREATE TABLE IF NOT EXISTS comments (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n build_id TEXT NOT NULL REFERENCES builds(id) ON DELETE CASCADE,\n snapshot_id TEXT REFERENCES snapshots(id) ON DELETE CASCADE,\n user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,\n body TEXT NOT NULL,\n parent_id TEXT,\n resolved INTEGER NOT NULL DEFAULT 0,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n);\nCREATE INDEX IF NOT EXISTS comments_build_id_idx ON comments (build_id);\nCREATE TABLE IF NOT EXISTS label_types (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n key TEXT NOT NULL,\n name TEXT NOT NULL,\n link_template TEXT,\n color TEXT,\n created_at TEXT NOT NULL\n);\nCREATE UNIQUE INDEX IF NOT EXISTS label_types_project_key_idx ON label_types (project_id, key);\nCREATE TABLE IF NOT EXISTS build_labels (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n build_id TEXT NOT NULL REFERENCES builds(id) ON DELETE CASCADE,\n type_key TEXT NOT NULL,\n value TEXT NOT NULL,\n created_at TEXT NOT NULL\n);\nCREATE UNIQUE INDEX IF NOT EXISTS build_labels_build_type_value_idx ON build_labels (build_id, type_key, value);\nCREATE TABLE IF NOT EXISTS tokens (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n name TEXT NOT NULL,\n hash TEXT NOT NULL UNIQUE,\n last_used_at TEXT,\n created_at TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS webhooks (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n url TEXT NOT NULL,\n secret TEXT NOT NULL,\n events TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS users (\n id TEXT PRIMARY KEY,\n email TEXT NOT NULL UNIQUE,\n name TEXT NOT NULL,\n avatar_url TEXT,\n role TEXT NOT NULL DEFAULT 'member',\n last_login_at TEXT,\n created_at TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS project_members (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,\n role TEXT NOT NULL DEFAULT 'viewer',\n created_at TEXT NOT NULL\n);\nCREATE UNIQUE INDEX IF NOT EXISTS project_members_project_user_idx ON project_members (project_id, user_id);\n";
5
+ //#endregion
6
+ export { DDL };
7
+ //# sourceMappingURL=ddl.d.mts.map
package/dist/ddl.mjs ADDED
@@ -0,0 +1,155 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ //#region src/ddl.ts
5
+ const DDL = `
6
+ CREATE TABLE IF NOT EXISTS projects (
7
+ id TEXT PRIMARY KEY,
8
+ name TEXT NOT NULL,
9
+ slug TEXT NOT NULL UNIQUE,
10
+ git_repository TEXT,
11
+ git_default_branch TEXT NOT NULL DEFAULT 'main',
12
+ pixel_threshold REAL NOT NULL DEFAULT 0.1,
13
+ max_diff_ratio REAL NOT NULL DEFAULT 0.01,
14
+ public_branch_regex TEXT,
15
+ storybook_meta TEXT,
16
+ created_at TEXT NOT NULL,
17
+ updated_at TEXT NOT NULL
18
+ );
19
+ CREATE TABLE IF NOT EXISTS project_status_configs (
20
+ id TEXT PRIMARY KEY,
21
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
22
+ provider TEXT NOT NULL,
23
+ config TEXT NOT NULL,
24
+ token_encrypted TEXT NOT NULL,
25
+ created_at TEXT NOT NULL,
26
+ updated_at TEXT NOT NULL
27
+ );
28
+ CREATE TABLE IF NOT EXISTS builds (
29
+ id TEXT PRIMARY KEY,
30
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
31
+ git_sha TEXT NOT NULL,
32
+ git_branch TEXT NOT NULL,
33
+ is_default INTEGER NOT NULL DEFAULT 0,
34
+ author_email TEXT,
35
+ author_name TEXT,
36
+ message TEXT,
37
+ public INTEGER NOT NULL DEFAULT 0,
38
+ status TEXT NOT NULL DEFAULT 'pending',
39
+ snapshot_count INTEGER NOT NULL DEFAULT 0,
40
+ changed_count INTEGER NOT NULL DEFAULT 0,
41
+ approved_count INTEGER NOT NULL DEFAULT 0,
42
+ rejected_count INTEGER NOT NULL DEFAULT 0,
43
+ created_at TEXT NOT NULL,
44
+ updated_at TEXT NOT NULL
45
+ );
46
+ CREATE UNIQUE INDEX IF NOT EXISTS builds_project_gitsha_idx ON builds (project_id, git_sha);
47
+ CREATE INDEX IF NOT EXISTS builds_git_branch_idx ON builds (git_branch);
48
+ CREATE TABLE IF NOT EXISTS snapshots (
49
+ id TEXT PRIMARY KEY,
50
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
51
+ build_id TEXT NOT NULL REFERENCES builds(id) ON DELETE CASCADE,
52
+ story_id TEXT NOT NULL,
53
+ story_name TEXT NOT NULL,
54
+ story_title TEXT NOT NULL,
55
+ story_import_path TEXT,
56
+ viewport_name TEXT NOT NULL DEFAULT 'desktop',
57
+ viewport_width INTEGER NOT NULL DEFAULT 1280,
58
+ viewport_height INTEGER NOT NULL DEFAULT 720,
59
+ screenshot_path TEXT NOT NULL,
60
+ diff_path TEXT,
61
+ diff_pixels INTEGER,
62
+ diff_ratio REAL,
63
+ diff_passed INTEGER,
64
+ status TEXT NOT NULL DEFAULT 'pending',
65
+ reviewed_by TEXT,
66
+ reviewed_at TEXT,
67
+ created_at TEXT NOT NULL,
68
+ updated_at TEXT NOT NULL
69
+ );
70
+ CREATE UNIQUE INDEX IF NOT EXISTS snapshots_build_story_viewport_idx ON snapshots (build_id, story_id, viewport_name);
71
+ CREATE INDEX IF NOT EXISTS snapshots_build_id_idx ON snapshots (build_id);
72
+ CREATE TABLE IF NOT EXISTS baselines (
73
+ id TEXT PRIMARY KEY,
74
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
75
+ story_id TEXT NOT NULL,
76
+ viewport_name TEXT NOT NULL DEFAULT 'desktop',
77
+ branch TEXT NOT NULL,
78
+ snapshot_id TEXT,
79
+ screenshot_path TEXT NOT NULL,
80
+ created_at TEXT NOT NULL,
81
+ updated_at TEXT NOT NULL
82
+ );
83
+ CREATE UNIQUE INDEX IF NOT EXISTS baselines_project_story_viewport_branch_idx ON baselines (project_id, story_id, viewport_name, branch);
84
+ CREATE INDEX IF NOT EXISTS baselines_project_story_idx ON baselines (project_id, story_id);
85
+ CREATE TABLE IF NOT EXISTS comments (
86
+ id TEXT PRIMARY KEY,
87
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
88
+ build_id TEXT NOT NULL REFERENCES builds(id) ON DELETE CASCADE,
89
+ snapshot_id TEXT REFERENCES snapshots(id) ON DELETE CASCADE,
90
+ user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
91
+ body TEXT NOT NULL,
92
+ parent_id TEXT,
93
+ resolved INTEGER NOT NULL DEFAULT 0,
94
+ created_at TEXT NOT NULL,
95
+ updated_at TEXT NOT NULL
96
+ );
97
+ CREATE INDEX IF NOT EXISTS comments_build_id_idx ON comments (build_id);
98
+ CREATE TABLE IF NOT EXISTS label_types (
99
+ id TEXT PRIMARY KEY,
100
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
101
+ key TEXT NOT NULL,
102
+ name TEXT NOT NULL,
103
+ link_template TEXT,
104
+ color TEXT,
105
+ created_at TEXT NOT NULL
106
+ );
107
+ CREATE UNIQUE INDEX IF NOT EXISTS label_types_project_key_idx ON label_types (project_id, key);
108
+ CREATE TABLE IF NOT EXISTS build_labels (
109
+ id TEXT PRIMARY KEY,
110
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
111
+ build_id TEXT NOT NULL REFERENCES builds(id) ON DELETE CASCADE,
112
+ type_key TEXT NOT NULL,
113
+ value TEXT NOT NULL,
114
+ created_at TEXT NOT NULL
115
+ );
116
+ CREATE UNIQUE INDEX IF NOT EXISTS build_labels_build_type_value_idx ON build_labels (build_id, type_key, value);
117
+ CREATE TABLE IF NOT EXISTS tokens (
118
+ id TEXT PRIMARY KEY,
119
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
120
+ name TEXT NOT NULL,
121
+ hash TEXT NOT NULL UNIQUE,
122
+ last_used_at TEXT,
123
+ created_at TEXT NOT NULL
124
+ );
125
+ CREATE TABLE IF NOT EXISTS webhooks (
126
+ id TEXT PRIMARY KEY,
127
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
128
+ url TEXT NOT NULL,
129
+ secret TEXT NOT NULL,
130
+ events TEXT,
131
+ created_at TEXT NOT NULL,
132
+ updated_at TEXT NOT NULL
133
+ );
134
+ CREATE TABLE IF NOT EXISTS users (
135
+ id TEXT PRIMARY KEY,
136
+ email TEXT NOT NULL UNIQUE,
137
+ name TEXT NOT NULL,
138
+ avatar_url TEXT,
139
+ role TEXT NOT NULL DEFAULT 'member',
140
+ last_login_at TEXT,
141
+ created_at TEXT NOT NULL
142
+ );
143
+ CREATE TABLE IF NOT EXISTS project_members (
144
+ id TEXT PRIMARY KEY,
145
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
146
+ user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
147
+ role TEXT NOT NULL DEFAULT 'viewer',
148
+ created_at TEXT NOT NULL
149
+ );
150
+ CREATE UNIQUE INDEX IF NOT EXISTS project_members_project_user_idx ON project_members (project_id, user_id);
151
+ `;
152
+ //#endregion
153
+ export { DDL };
154
+
155
+ //# sourceMappingURL=ddl.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ddl.mjs","names":[],"sources":["../src/ddl.ts"],"sourcesContent":["export const DDL = `\nCREATE TABLE IF NOT EXISTS projects (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n slug TEXT NOT NULL UNIQUE,\n git_repository TEXT,\n git_default_branch TEXT NOT NULL DEFAULT 'main',\n pixel_threshold REAL NOT NULL DEFAULT 0.1,\n max_diff_ratio REAL NOT NULL DEFAULT 0.01,\n public_branch_regex TEXT,\n storybook_meta TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS project_status_configs (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n provider TEXT NOT NULL,\n config TEXT NOT NULL,\n token_encrypted TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS builds (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n git_sha TEXT NOT NULL,\n git_branch TEXT NOT NULL,\n is_default INTEGER NOT NULL DEFAULT 0,\n author_email TEXT,\n author_name TEXT,\n message TEXT,\n public INTEGER NOT NULL DEFAULT 0,\n status TEXT NOT NULL DEFAULT 'pending',\n snapshot_count INTEGER NOT NULL DEFAULT 0,\n changed_count INTEGER NOT NULL DEFAULT 0,\n approved_count INTEGER NOT NULL DEFAULT 0,\n rejected_count INTEGER NOT NULL DEFAULT 0,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n);\nCREATE UNIQUE INDEX IF NOT EXISTS builds_project_gitsha_idx ON builds (project_id, git_sha);\nCREATE INDEX IF NOT EXISTS builds_git_branch_idx ON builds (git_branch);\nCREATE TABLE IF NOT EXISTS snapshots (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n build_id TEXT NOT NULL REFERENCES builds(id) ON DELETE CASCADE,\n story_id TEXT NOT NULL,\n story_name TEXT NOT NULL,\n story_title TEXT NOT NULL,\n story_import_path TEXT,\n viewport_name TEXT NOT NULL DEFAULT 'desktop',\n viewport_width INTEGER NOT NULL DEFAULT 1280,\n viewport_height INTEGER NOT NULL DEFAULT 720,\n screenshot_path TEXT NOT NULL,\n diff_path TEXT,\n diff_pixels INTEGER,\n diff_ratio REAL,\n diff_passed INTEGER,\n status TEXT NOT NULL DEFAULT 'pending',\n reviewed_by TEXT,\n reviewed_at TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n);\nCREATE UNIQUE INDEX IF NOT EXISTS snapshots_build_story_viewport_idx ON snapshots (build_id, story_id, viewport_name);\nCREATE INDEX IF NOT EXISTS snapshots_build_id_idx ON snapshots (build_id);\nCREATE TABLE IF NOT EXISTS baselines (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n story_id TEXT NOT NULL,\n viewport_name TEXT NOT NULL DEFAULT 'desktop',\n branch TEXT NOT NULL,\n snapshot_id TEXT,\n screenshot_path TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n);\nCREATE UNIQUE INDEX IF NOT EXISTS baselines_project_story_viewport_branch_idx ON baselines (project_id, story_id, viewport_name, branch);\nCREATE INDEX IF NOT EXISTS baselines_project_story_idx ON baselines (project_id, story_id);\nCREATE TABLE IF NOT EXISTS comments (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n build_id TEXT NOT NULL REFERENCES builds(id) ON DELETE CASCADE,\n snapshot_id TEXT REFERENCES snapshots(id) ON DELETE CASCADE,\n user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,\n body TEXT NOT NULL,\n parent_id TEXT,\n resolved INTEGER NOT NULL DEFAULT 0,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n);\nCREATE INDEX IF NOT EXISTS comments_build_id_idx ON comments (build_id);\nCREATE TABLE IF NOT EXISTS label_types (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n key TEXT NOT NULL,\n name TEXT NOT NULL,\n link_template TEXT,\n color TEXT,\n created_at TEXT NOT NULL\n);\nCREATE UNIQUE INDEX IF NOT EXISTS label_types_project_key_idx ON label_types (project_id, key);\nCREATE TABLE IF NOT EXISTS build_labels (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n build_id TEXT NOT NULL REFERENCES builds(id) ON DELETE CASCADE,\n type_key TEXT NOT NULL,\n value TEXT NOT NULL,\n created_at TEXT NOT NULL\n);\nCREATE UNIQUE INDEX IF NOT EXISTS build_labels_build_type_value_idx ON build_labels (build_id, type_key, value);\nCREATE TABLE IF NOT EXISTS tokens (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n name TEXT NOT NULL,\n hash TEXT NOT NULL UNIQUE,\n last_used_at TEXT,\n created_at TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS webhooks (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n url TEXT NOT NULL,\n secret TEXT NOT NULL,\n events TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS users (\n id TEXT PRIMARY KEY,\n email TEXT NOT NULL UNIQUE,\n name TEXT NOT NULL,\n avatar_url TEXT,\n role TEXT NOT NULL DEFAULT 'member',\n last_login_at TEXT,\n created_at TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS project_members (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,\n role TEXT NOT NULL DEFAULT 'viewer',\n created_at TEXT NOT NULL\n);\nCREATE UNIQUE INDEX IF NOT EXISTS project_members_project_user_idx ON project_members (project_id, user_id);\n`;\n"],"mappings":";;;;AAAA,MAAa,MAAM"}
@@ -0,0 +1,8 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ import { DiffOptions, DiffResult } from "./options.mjs";
4
+ //#region src/diff/engine.d.ts
5
+ declare function diffImages(baseline: Buffer, current: Buffer, options: DiffOptions): DiffResult;
6
+ //#endregion
7
+ export { diffImages };
8
+ //# sourceMappingURL=engine.d.mts.map
@@ -0,0 +1,79 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import pixelmatch from "pixelmatch";
5
+ import { PNG } from "pngjs";
6
+ //#region src/diff/engine.ts
7
+ function diffImages(baseline, current, options) {
8
+ const baselinePng = PNG.sync.read(baseline);
9
+ const currentPng = PNG.sync.read(current);
10
+ const baselineDimensions = {
11
+ width: baselinePng.width,
12
+ height: baselinePng.height
13
+ };
14
+ const currentDimensions = {
15
+ width: currentPng.width,
16
+ height: currentPng.height
17
+ };
18
+ if (baselineDimensions.width !== currentDimensions.width || baselineDimensions.height !== currentDimensions.height) {
19
+ if (options.failOnSizeChange) return {
20
+ passed: false,
21
+ diffPixels: 0,
22
+ diffRatio: 1,
23
+ diffImage: null,
24
+ baselineDimensions,
25
+ currentDimensions,
26
+ sizeChanged: true
27
+ };
28
+ const width = Math.min(baselineDimensions.width, currentDimensions.width);
29
+ const height = Math.min(baselineDimensions.height, currentDimensions.height);
30
+ const overlap = compareRegions(cropRgba(baselinePng.data, baselineDimensions.width, width, height), cropRgba(currentPng.data, currentDimensions.width, width, height), width, height, options);
31
+ const diffRatio = overlap.diffPixels / (width * height);
32
+ return {
33
+ passed: diffRatio <= options.maxDiffRatio,
34
+ diffPixels: overlap.diffPixels,
35
+ diffRatio,
36
+ diffImage: overlap.diffImage,
37
+ baselineDimensions,
38
+ currentDimensions,
39
+ sizeChanged: true
40
+ };
41
+ }
42
+ const width = baselineDimensions.width;
43
+ const height = baselineDimensions.height;
44
+ const overlap = compareRegions(baselinePng.data, currentPng.data, width, height, options);
45
+ const diffRatio = overlap.diffPixels / (width * height);
46
+ return {
47
+ passed: diffRatio <= options.maxDiffRatio,
48
+ diffPixels: overlap.diffPixels,
49
+ diffRatio,
50
+ diffImage: overlap.diffImage,
51
+ baselineDimensions,
52
+ currentDimensions,
53
+ sizeChanged: false
54
+ };
55
+ }
56
+ function cropRgba(source, sourceWidth, width, height) {
57
+ const cropped = Buffer.alloc(width * height * 4);
58
+ const rowBytes = width * 4;
59
+ const sourceRowBytes = sourceWidth * 4;
60
+ for (let y = 0; y < height; y += 1) source.copy(cropped, y * rowBytes, y * sourceRowBytes, y * sourceRowBytes + rowBytes);
61
+ return cropped;
62
+ }
63
+ function compareRegions(baselineData, currentData, width, height, options) {
64
+ const diffPng = new PNG({
65
+ width,
66
+ height
67
+ });
68
+ return {
69
+ diffPixels: pixelmatch(baselineData, currentData, diffPng.data, width, height, {
70
+ threshold: options.pixelThreshold,
71
+ includeAA: options.includeAntialiasing
72
+ }),
73
+ diffImage: PNG.sync.write(diffPng)
74
+ };
75
+ }
76
+ //#endregion
77
+ export { diffImages };
78
+
79
+ //# sourceMappingURL=engine.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.mjs","names":[],"sources":["../../src/diff/engine.ts"],"sourcesContent":["import pixelmatch from \"pixelmatch\";\nimport { PNG } from \"pngjs\";\n\nimport type { DiffOptions, DiffResult } from \"./options.ts\";\n\nexport function diffImages(baseline: Buffer, current: Buffer, options: DiffOptions): DiffResult {\n const baselinePng = PNG.sync.read(baseline);\n const currentPng = PNG.sync.read(current);\n\n const baselineDimensions = { width: baselinePng.width, height: baselinePng.height };\n const currentDimensions = { width: currentPng.width, height: currentPng.height };\n const sizeChanged =\n baselineDimensions.width !== currentDimensions.width || baselineDimensions.height !== currentDimensions.height;\n\n if (sizeChanged) {\n if (options.failOnSizeChange) {\n return {\n passed: false,\n diffPixels: 0,\n diffRatio: 1,\n diffImage: null,\n baselineDimensions,\n currentDimensions,\n sizeChanged: true,\n };\n }\n\n // With `failOnSizeChange: false` a size mismatch is not a failure: diff only\n // The top-left overlapping region, normalised by its area. Pixelmatch needs\n // Equal-size inputs, so the larger image is cropped first.\n const width = Math.min(baselineDimensions.width, currentDimensions.width);\n const height = Math.min(baselineDimensions.height, currentDimensions.height);\n const overlap = compareRegions(\n cropRgba(baselinePng.data, baselineDimensions.width, width, height),\n cropRgba(currentPng.data, currentDimensions.width, width, height),\n width,\n height,\n options,\n );\n const diffRatio = overlap.diffPixels / (width * height);\n return {\n passed: diffRatio <= options.maxDiffRatio,\n diffPixels: overlap.diffPixels,\n diffRatio,\n diffImage: overlap.diffImage,\n baselineDimensions,\n currentDimensions,\n sizeChanged: true,\n };\n }\n\n const width = baselineDimensions.width;\n const height = baselineDimensions.height;\n const overlap = compareRegions(baselinePng.data, currentPng.data, width, height, options);\n const diffRatio = overlap.diffPixels / (width * height);\n return {\n passed: diffRatio <= options.maxDiffRatio,\n diffPixels: overlap.diffPixels,\n diffRatio,\n diffImage: overlap.diffImage,\n baselineDimensions,\n currentDimensions,\n sizeChanged: false,\n };\n}\n\nfunction cropRgba(source: Buffer, sourceWidth: number, width: number, height: number): Buffer {\n const cropped = Buffer.alloc(width * height * 4);\n const rowBytes = width * 4;\n const sourceRowBytes = sourceWidth * 4;\n for (let y = 0; y < height; y += 1) {\n source.copy(cropped, y * rowBytes, y * sourceRowBytes, y * sourceRowBytes + rowBytes);\n }\n return cropped;\n}\n\nfunction compareRegions(\n baselineData: Buffer,\n currentData: Buffer,\n width: number,\n height: number,\n options: DiffOptions,\n): { diffPixels: number; diffImage: Buffer } {\n const diffPng = new PNG({ width, height });\n const diffPixels = pixelmatch(baselineData, currentData, diffPng.data, width, height, {\n threshold: options.pixelThreshold,\n includeAA: options.includeAntialiasing,\n });\n return { diffPixels, diffImage: PNG.sync.write(diffPng) };\n}\n"],"mappings":";;;;;;AAKA,SAAgB,WAAW,UAAkB,SAAiB,SAAkC;CAC9F,MAAM,cAAc,IAAI,KAAK,KAAK,QAAQ;CAC1C,MAAM,aAAa,IAAI,KAAK,KAAK,OAAO;CAExC,MAAM,qBAAqB;EAAE,OAAO,YAAY;EAAO,QAAQ,YAAY;CAAO;CAClF,MAAM,oBAAoB;EAAE,OAAO,WAAW;EAAO,QAAQ,WAAW;CAAO;CAI/E,IAFE,mBAAmB,UAAU,kBAAkB,SAAS,mBAAmB,WAAW,kBAAkB,QAEzF;EACf,IAAI,QAAQ,kBACV,OAAO;GACL,QAAQ;GACR,YAAY;GACZ,WAAW;GACX,WAAW;GACX;GACA;GACA,aAAa;EACf;EAMF,MAAM,QAAQ,KAAK,IAAI,mBAAmB,OAAO,kBAAkB,KAAK;EACxE,MAAM,SAAS,KAAK,IAAI,mBAAmB,QAAQ,kBAAkB,MAAM;EAC3E,MAAM,UAAU,eACd,SAAS,YAAY,MAAM,mBAAmB,OAAO,OAAO,MAAM,GAClE,SAAS,WAAW,MAAM,kBAAkB,OAAO,OAAO,MAAM,GAChE,OACA,QACA,OACF;EACA,MAAM,YAAY,QAAQ,cAAc,QAAQ;EAChD,OAAO;GACL,QAAQ,aAAa,QAAQ;GAC7B,YAAY,QAAQ;GACpB;GACA,WAAW,QAAQ;GACnB;GACA;GACA,aAAa;EACf;CACF;CAEA,MAAM,QAAQ,mBAAmB;CACjC,MAAM,SAAS,mBAAmB;CAClC,MAAM,UAAU,eAAe,YAAY,MAAM,WAAW,MAAM,OAAO,QAAQ,OAAO;CACxF,MAAM,YAAY,QAAQ,cAAc,QAAQ;CAChD,OAAO;EACL,QAAQ,aAAa,QAAQ;EAC7B,YAAY,QAAQ;EACpB;EACA,WAAW,QAAQ;EACnB;EACA;EACA,aAAa;CACf;AACF;AAEA,SAAS,SAAS,QAAgB,aAAqB,OAAe,QAAwB;CAC5F,MAAM,UAAU,OAAO,MAAM,QAAQ,SAAS,CAAC;CAC/C,MAAM,WAAW,QAAQ;CACzB,MAAM,iBAAiB,cAAc;CACrC,KAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,KAAK,GAC/B,OAAO,KAAK,SAAS,IAAI,UAAU,IAAI,gBAAgB,IAAI,iBAAiB,QAAQ;CAEtF,OAAO;AACT;AAEA,SAAS,eACP,cACA,aACA,OACA,QACA,SAC2C;CAC3C,MAAM,UAAU,IAAI,IAAI;EAAE;EAAO;CAAO,CAAC;CAKzC,OAAO;EAAE,YAJU,WAAW,cAAc,aAAa,QAAQ,MAAM,OAAO,QAAQ;GACpF,WAAW,QAAQ;GACnB,WAAW,QAAQ;EACrB,CACkB;EAAG,WAAW,IAAI,KAAK,MAAM,OAAO;CAAE;AAC1D"}
@@ -0,0 +1,40 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ //#region src/diff/options.d.ts
4
+ /** Options that control how two images are compared. */
5
+ interface DiffOptions {
6
+ /** Per-pixel difference threshold (0-1) above which a pixel counts as different. */
7
+ pixelThreshold: number;
8
+ /** Maximum allowed ratio of differing pixels for a diff to pass. */
9
+ maxDiffRatio: number;
10
+ /** Whether to include antialiasing artifacts when counting differing pixels. */
11
+ includeAntialiasing: boolean;
12
+ /** Whether a size mismatch between images should fail the diff. When false, only the overlapping region is diffed. */
13
+ failOnSizeChange: boolean;
14
+ }
15
+ /** The outcome of comparing a baseline and a current screenshot. */
16
+ interface DiffResult {
17
+ /** Whether the current image is within the accepted tolerance. */
18
+ passed: boolean;
19
+ /** Number of differing pixels. */
20
+ diffPixels: number;
21
+ /** Ratio of differing pixels to total pixels. */
22
+ diffRatio: number;
23
+ /** Generated diff overlay image, or null when unavailable. */
24
+ diffImage: Buffer | null;
25
+ /** Dimensions of the baseline image. */
26
+ baselineDimensions: {
27
+ width: number;
28
+ height: number;
29
+ };
30
+ /** Dimensions of the current image. */
31
+ currentDimensions: {
32
+ width: number;
33
+ height: number;
34
+ };
35
+ /** Whether the two images have different dimensions. */
36
+ sizeChanged: boolean;
37
+ }
38
+ //#endregion
39
+ export { DiffOptions, DiffResult };
40
+ //# sourceMappingURL=options.d.mts.map
@@ -0,0 +1,15 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ //#region src/diff/options.ts
5
+ /** Default diff settings used when no project overrides are present. */
6
+ const DEFAULT_DIFF_OPTIONS = {
7
+ pixelThreshold: .1,
8
+ maxDiffRatio: .01,
9
+ includeAntialiasing: false,
10
+ failOnSizeChange: true
11
+ };
12
+ //#endregion
13
+ export { DEFAULT_DIFF_OPTIONS };
14
+
15
+ //# sourceMappingURL=options.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"options.mjs","names":[],"sources":["../../src/diff/options.ts"],"sourcesContent":["/** Options that control how two images are compared. */\nexport interface DiffOptions {\n /** Per-pixel difference threshold (0-1) above which a pixel counts as different. */\n pixelThreshold: number;\n /** Maximum allowed ratio of differing pixels for a diff to pass. */\n maxDiffRatio: number;\n /** Whether to include antialiasing artifacts when counting differing pixels. */\n includeAntialiasing: boolean;\n /** Whether a size mismatch between images should fail the diff. When false, only the overlapping region is diffed. */\n failOnSizeChange: boolean;\n}\n\n/** The outcome of comparing a baseline and a current screenshot. */\nexport interface DiffResult {\n /** Whether the current image is within the accepted tolerance. */\n passed: boolean;\n /** Number of differing pixels. */\n diffPixels: number;\n /** Ratio of differing pixels to total pixels. */\n diffRatio: number;\n /** Generated diff overlay image, or null when unavailable. */\n diffImage: Buffer | null;\n /** Dimensions of the baseline image. */\n baselineDimensions: { width: number; height: number };\n /** Dimensions of the current image. */\n currentDimensions: { width: number; height: number };\n /** Whether the two images have different dimensions. */\n sizeChanged: boolean;\n}\n\n/** Default diff settings used when no project overrides are present. */\nexport const DEFAULT_DIFF_OPTIONS: DiffOptions = {\n pixelThreshold: 0.1,\n maxDiffRatio: 0.01,\n includeAntialiasing: false,\n failOnSizeChange: true,\n};\n"],"mappings":";;;;;AA+BA,MAAa,uBAAoC;CAC/C,gBAAgB;CAChB,cAAc;CACd,qBAAqB;CACrB,kBAAkB;AACpB"}
@@ -0,0 +1,48 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ import { AdapterMetadata, GitAdapterMetadata } from "./adapters/metadata.mjs";
4
+ import { BUILD_STATUSES, BuildStatus, PERSISTENT_LABEL_KEY, PROJECT_ROLES, ProjectRole, RESERVED_LABEL_KEYS, SEEDED_LABEL_KEYS, SITE_ROLES, SNAPSHOT_STATUSES, SiteRole, SnapshotStatus, TERMINAL_BUILD_STATUSES } from "./types.mjs";
5
+ import { AuthAdapter, AuthCallback, AuthUser } from "./adapter/auth.mjs";
6
+ import { DEFAULT_VIEWPORTS, StoryEntry, StorySourceAdapter, Viewport } from "./capture/adapter.mjs";
7
+ import { CaptureJob, CaptureQueue, JobStatus, QueueEntry } from "./adapters/capture-queue.mjs";
8
+ import { CaptureRunner, RenderFailure, RenderResult, RenderedSnapshot } from "./adapter/capture-runner.mjs";
9
+ import { DatabaseAdapter, ListOptions } from "./adapter/database.mjs";
10
+ import { CheckStatus, GitHostAdapter, GitHostProvider } from "./adapter/git-host.mjs";
11
+ import { buildCommentMarkdown, commentMarker, describeStatus } from "./adapter/git-host/helpers.mjs";
12
+ import { StorageAdapter } from "./adapter/storage.mjs";
13
+ import { BrandTheme, ShelfConfig, ShelfOptions, UIConfig } from "./config.mjs";
14
+ import { LoggerOptions, PinoTransport, createShelfLogger } from "./logger.mjs";
15
+ import { DiffOptions, DiffResult } from "./diff/options.mjs";
16
+ import { diffImages } from "./diff/engine.mjs";
17
+ import { RenderedContent } from "./ui/document.mjs";
18
+ import { StorybookAdapter } from "./capture/storybook.mjs";
19
+ import { Baseline, Build, BuildLabel, Comment, LabelType, Project, ProjectMember, ProjectStatusConfig, Schema, Snapshot, StorybookMeta, Token, User, Webhook, baselines, buildLabels, builds, comments, labelTypes, projectMembers, projectStatusConfigs, projects, schema, snapshots, tokens, users, webhooks } from "./schema.mjs";
20
+ import { CaptureContext, persistCapture } from "./capture/pipeline.mjs";
21
+ import { CaptureJobOptions, executeCaptureJob } from "./capture/orchestrator.mjs";
22
+ import { InMemoryCaptureQueue, InMemoryCaptureQueueOptions } from "./capture/queue.mjs";
23
+ import { Retention } from "./retention/purge.mjs";
24
+ import { UrlBuilder, createUrlBuilder } from "./urls.mjs";
25
+ import { slugify, ulid } from "./utils/ulid.mjs";
26
+ import { baselinePath, diffPath, screenshotPath, storybookDir, storybookZipPath } from "./utils/paths.mjs";
27
+ import { OpenAPIHono } from "@hono/zod-openapi";
28
+ //#region src/index.d.ts
29
+ type ShelfContext = {
30
+ requestId?: string;
31
+ db: DatabaseAdapter;
32
+ storage: StorageAdapter;
33
+ config: ShelfConfig;
34
+ ui: UIConfig;
35
+ logger: ReturnType<typeof createShelfLogger>;
36
+ user: AuthUser | null;
37
+ authEnabled: boolean;
38
+ enqueueCapture?: (buildId: string, reqId?: string) => Promise<void>;
39
+ captureQueue: CaptureQueue | null;
40
+ gitHosts: GitHostProvider[];
41
+ };
42
+ type ShelfApp = OpenAPIHono<{
43
+ Variables: ShelfContext;
44
+ }>;
45
+ declare function createShelfRouter(options: ShelfOptions): ShelfApp;
46
+ //#endregion
47
+ export { type AdapterMetadata, type AuthAdapter, type AuthCallback, type AuthUser, BUILD_STATUSES, Baseline, type BrandTheme, Build, BuildLabel, BuildStatus, type CaptureContext, type CaptureJob, type CaptureJobOptions, type CaptureQueue, type CaptureRunner, type CheckStatus, Comment, DEFAULT_VIEWPORTS, type DatabaseAdapter, type DiffOptions, type DiffResult, type GitAdapterMetadata, type GitHostAdapter, type GitHostProvider, InMemoryCaptureQueue, type InMemoryCaptureQueueOptions, type JobStatus, LabelType, type ListOptions, type LoggerOptions, PERSISTENT_LABEL_KEY, PROJECT_ROLES, type PinoTransport, Project, ProjectMember, ProjectRole, ProjectStatusConfig, type QueueEntry, RESERVED_LABEL_KEYS, type RenderFailure, type RenderResult, type RenderedContent, type RenderedSnapshot, Retention, SEEDED_LABEL_KEYS, SITE_ROLES, SNAPSHOT_STATUSES, Schema, ShelfApp, type ShelfConfig, ShelfContext, type ShelfOptions, SiteRole, Snapshot, SnapshotStatus, type StorageAdapter, type StoryEntry, type StorySourceAdapter, StorybookAdapter, StorybookMeta, TERMINAL_BUILD_STATUSES, Token, type UIConfig, type UrlBuilder, User, type Viewport, Webhook, baselinePath, baselines, buildCommentMarkdown, buildLabels, builds, commentMarker, comments, createShelfLogger, createShelfRouter, createUrlBuilder, describeStatus, diffImages, diffPath, executeCaptureJob, labelTypes, persistCapture, projectMembers, projectStatusConfigs, projects, schema, screenshotPath, slugify, snapshots, storybookDir, storybookZipPath, tokens, ulid, users, webhooks };
48
+ //# sourceMappingURL=index.d.mts.map