@storyshelf/core 0.1.0 → 0.1.3
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.
- package/LICENSE +21 -0
- package/README.md +9 -8
- package/dist/adapter/auth.mjs.map +1 -1
- package/dist/adapter/capture-runner.d.mts +4 -0
- package/dist/adapter/git-host/helpers.d.mts +1 -0
- package/dist/adapter/git-host/helpers.mjs +2 -1
- package/dist/adapter/git-host/helpers.mjs.map +1 -1
- package/dist/adapter/storage.d.mts +1 -0
- package/dist/adapters/capture-queue.d.mts +2 -0
- package/dist/adapters/webhook-events.mjs +8 -0
- package/dist/adapters/webhook-events.mjs.map +1 -1
- package/dist/capture/adapter.d.mts +11 -1
- package/dist/capture/adapter.mjs +19 -1
- package/dist/capture/adapter.mjs.map +1 -1
- package/dist/capture/orchestrator.d.mts +7 -0
- package/dist/capture/orchestrator.mjs +43 -10
- package/dist/capture/orchestrator.mjs.map +1 -1
- package/dist/capture/pipeline.d.mts +5 -3
- package/dist/capture/pipeline.mjs +18 -6
- package/dist/capture/pipeline.mjs.map +1 -1
- package/dist/capture/queue.d.mts +2 -0
- package/dist/capture/queue.mjs +2 -1
- package/dist/capture/queue.mjs.map +1 -1
- package/dist/capture/status-fanout.mjs +1 -0
- package/dist/capture/status-fanout.mjs.map +1 -1
- package/dist/capture/storybook.d.mts +1 -0
- package/dist/capture/storybook.mjs +12 -3
- package/dist/capture/storybook.mjs.map +1 -1
- package/dist/config.d.mts +27 -20
- package/dist/config.mjs +16 -2
- package/dist/config.mjs.map +1 -1
- package/dist/ddl.d.mts +2 -1
- package/dist/ddl.mjs +3 -0
- package/dist/ddl.mjs.map +1 -1
- package/dist/diff/engine.d.mts +8 -0
- package/dist/diff/engine.mjs +10 -2
- package/dist/diff/engine.mjs.map +1 -1
- package/dist/index.d.mts +7 -4
- package/dist/index.mjs +10 -3
- package/dist/index.mjs.map +1 -1
- package/dist/logger.d.mts +3 -3
- package/dist/logger.mjs.map +1 -1
- package/dist/middleware/csrf.mjs +2 -1
- package/dist/middleware/csrf.mjs.map +1 -1
- package/dist/middleware/rate-limit.mjs +1 -0
- package/dist/middleware/rate-limit.mjs.map +1 -1
- package/dist/models/baseline.d.mts +1 -0
- package/dist/models/baseline.mjs +3 -1
- package/dist/models/baseline.mjs.map +1 -1
- package/dist/models/build.d.mts +19 -1
- package/dist/models/build.mjs +22 -2
- package/dist/models/build.mjs.map +1 -1
- package/dist/models/comment.d.mts +2 -0
- package/dist/models/comment.mjs +3 -1
- package/dist/models/comment.mjs.map +1 -1
- package/dist/models/label.d.mts +6 -0
- package/dist/models/label.mjs +13 -1
- package/dist/models/label.mjs.map +1 -1
- package/dist/models/member.mjs +2 -1
- package/dist/models/member.mjs.map +1 -1
- package/dist/models/project.d.mts +1 -0
- package/dist/models/project.mjs +3 -2
- package/dist/models/project.mjs.map +1 -1
- package/dist/models/snapshot.d.mts +1 -0
- package/dist/models/snapshot.mjs +2 -1
- package/dist/models/snapshot.mjs.map +1 -1
- package/dist/models/status-config.mjs +2 -1
- package/dist/models/status-config.mjs.map +1 -1
- package/dist/models/token.mjs +2 -1
- package/dist/models/token.mjs.map +1 -1
- package/dist/models/webhook.d.mts +1 -0
- package/dist/models/webhook.mjs +2 -1
- package/dist/models/webhook.mjs.map +1 -1
- package/dist/pages/build-detail.mjs +2 -1
- package/dist/pages/build-detail.mjs.map +1 -1
- package/dist/pages/build-diff.mjs +1 -0
- package/dist/pages/build-diff.mjs.map +1 -1
- package/dist/pages/compute-jobs.mjs +2 -0
- package/dist/pages/compute-jobs.mjs.map +1 -1
- package/dist/pages/label-detail.mjs +227 -0
- package/dist/pages/label-detail.mjs.map +1 -0
- package/dist/pages/login.mjs +1 -0
- package/dist/pages/login.mjs.map +1 -1
- package/dist/pages/project-builds.mjs +1 -0
- package/dist/pages/project-builds.mjs.map +1 -1
- package/dist/pages/project-create.mjs +1 -0
- package/dist/pages/project-create.mjs.map +1 -1
- package/dist/pages/project-settings.mjs +33 -42
- package/dist/pages/project-settings.mjs.map +1 -1
- package/dist/pages/projects.mjs +1 -0
- package/dist/pages/projects.mjs.map +1 -1
- package/dist/pages/root.mjs +1 -0
- package/dist/pages/root.mjs.map +1 -1
- package/dist/pages/settings-general.mjs +1 -0
- package/dist/pages/settings-general.mjs.map +1 -1
- package/dist/pages/settings-labels.mjs +1 -0
- package/dist/pages/settings-labels.mjs.map +1 -1
- package/dist/pages/settings-members.mjs +1 -0
- package/dist/pages/settings-members.mjs.map +1 -1
- package/dist/pages/settings-status.mjs +1 -0
- package/dist/pages/settings-status.mjs.map +1 -1
- package/dist/pages/settings-tests.mjs +149 -0
- package/dist/pages/settings-tests.mjs.map +1 -0
- package/dist/pages/settings-tokens.mjs +1 -0
- package/dist/pages/settings-tokens.mjs.map +1 -1
- package/dist/pages/settings-webhooks.mjs +1 -0
- package/dist/pages/settings-webhooks.mjs.map +1 -1
- package/dist/pages/storybook.mjs +59 -0
- package/dist/pages/storybook.mjs.map +1 -0
- package/dist/retention/purge.d.mts +3 -0
- package/dist/retention/purge.mjs +2 -1
- package/dist/retention/purge.mjs.map +1 -1
- package/dist/routers/admin.mjs +1 -0
- package/dist/routers/admin.mjs.map +1 -1
- package/dist/routers/assets.mjs +1 -0
- package/dist/routers/assets.mjs.map +1 -1
- package/dist/routers/auth.mjs +1 -0
- package/dist/routers/auth.mjs.map +1 -1
- package/dist/routers/builds.handlers.mjs +15 -3
- package/dist/routers/builds.handlers.mjs.map +1 -1
- package/dist/routers/builds.mjs +5 -2
- package/dist/routers/builds.mjs.map +1 -1
- package/dist/routers/comments.mjs +1 -0
- package/dist/routers/comments.mjs.map +1 -1
- package/dist/routers/helpers.mjs +7 -1
- package/dist/routers/helpers.mjs.map +1 -1
- package/dist/routers/htmx.mjs +2 -0
- package/dist/routers/htmx.mjs.map +1 -1
- package/dist/routers/labels.mjs +34 -1
- package/dist/routers/labels.mjs.map +1 -1
- package/dist/routers/media.mjs +1 -0
- package/dist/routers/media.mjs.map +1 -1
- package/dist/routers/members.mjs +1 -0
- package/dist/routers/members.mjs.map +1 -1
- package/dist/routers/projects.mjs +1 -0
- package/dist/routers/projects.mjs.map +1 -1
- package/dist/routers/schemas.mjs +40 -4
- package/dist/routers/schemas.mjs.map +1 -1
- package/dist/routers/settings.mjs +22 -0
- package/dist/routers/settings.mjs.map +1 -1
- package/dist/routers/snapshots.mjs +1 -0
- package/dist/routers/snapshots.mjs.map +1 -1
- package/dist/routers/status-configs.mjs +1 -0
- package/dist/routers/status-configs.mjs.map +1 -1
- package/dist/routers/storybook.mjs +113 -0
- package/dist/routers/storybook.mjs.map +1 -0
- package/dist/routers/tokens.mjs +1 -0
- package/dist/routers/tokens.mjs.map +1 -1
- package/dist/routers/ui.mjs +25 -2
- package/dist/routers/ui.mjs.map +1 -1
- package/dist/routers/webhooks.mjs +1 -0
- package/dist/routers/webhooks.mjs.map +1 -1
- package/dist/schema-tables.mjs +166 -0
- package/dist/schema-tables.mjs.map +1 -0
- package/dist/schema.d.mts +194 -4352
- package/dist/schema.mjs +26 -136
- package/dist/schema.mjs.map +1 -1
- package/dist/store.mjs +2 -0
- package/dist/store.mjs.map +1 -1
- package/dist/types.d.mts +14 -1
- package/dist/types.mjs +8 -0
- package/dist/types.mjs.map +1 -1
- package/dist/ui/components.mjs +5 -0
- package/dist/ui/components.mjs.map +1 -1
- package/dist/ui/document.d.mts +1 -0
- package/dist/ui/document.mjs +10 -0
- package/dist/ui/document.mjs.map +1 -1
- package/dist/ui/styles.mjs +1 -0
- package/dist/ui/styles.mjs.map +1 -1
- package/dist/ui/theme.mjs +2 -0
- package/dist/ui/theme.mjs.map +1 -1
- package/dist/urls.d.mts +8 -0
- package/dist/urls.mjs +7 -0
- package/dist/urls.mjs.map +1 -1
- package/dist/utils/hash.mjs +3 -0
- package/dist/utils/hash.mjs.map +1 -1
- package/dist/utils/paths.d.mts +5 -0
- package/dist/utils/paths.mjs +5 -0
- package/dist/utils/paths.mjs.map +1 -1
- package/dist/utils/ulid.d.mts +2 -0
- package/dist/utils/ulid.mjs +5 -3
- package/dist/utils/ulid.mjs.map +1 -1
- package/package.json +76 -67
|
@@ -4,6 +4,7 @@ import.meta.url;
|
|
|
4
4
|
import { StatusConfigModel } from "../models/status-config.mjs";
|
|
5
5
|
import { buildCommentMarkdown } from "../adapter/git-host/helpers.mjs";
|
|
6
6
|
//#region src/capture/status-fanout.ts
|
|
7
|
+
/** Post build check statuses (and review comments) to every configured git provider. */
|
|
7
8
|
async function postStatusesForBuild(opts) {
|
|
8
9
|
if (opts.providers.length === 0) return;
|
|
9
10
|
const model = new StatusConfigModel(opts.db, opts.secret);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status-fanout.mjs","names":[],"sources":["../../src/capture/status-fanout.ts"],"sourcesContent":["import type { Logger } from \"pino\";\n\nimport { StatusConfigModel } from \"../models/status-config.ts\";\nimport type { CheckStatus, GitHostProvider } from \"../adapters/git-host/index.ts\";\nimport { buildCommentMarkdown } from \"../adapters/git-host/helpers.ts\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { Project } from \"../schema.ts\";\n\nasync function postStatusesForBuild(opts: {\n db: DatabaseAdapter;\n project: Project;\n sha: string;\n status: CheckStatus;\n url: string;\n providers: GitHostProvider[];\n secret: string | undefined;\n logger?: Logger;\n}): Promise<void> {\n if (opts.providers.length === 0) {\n return;\n }\n const model = new StatusConfigModel(opts.db, opts.secret);\n const rows = await model.list(opts.project.id);\n const ctx = `storyshelf/${opts.project.slug}`;\n await Promise.allSettled(\n rows.map(async (row) => {\n const provider = opts.providers.find((p) => p.metadata.kind === row.provider);\n if (!provider) {\n opts.logger?.warn({ provider: row.provider }, \"no provider registered for status config\");\n return;\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(row.config);\n provider.metadata.schema.parse(parsed);\n } catch (error: unknown) {\n opts.logger?.warn({ err: error, provider: row.provider }, \"invalid status config\");\n return;\n }\n let token: string;\n try {\n token = model.decryptToken(row);\n } catch (error: unknown) {\n opts.logger?.warn({ err: error }, \"failed to decrypt status token\");\n return;\n }\n const adapter = provider.create({ config: parsed, token, logger: opts.logger });\n await adapter.setStatus({ context: ctx, gitSha: opts.sha, status: opts.status, url: opts.url });\n if (adapter.upsertComment) {\n const markdown = buildCommentMarkdown(opts.status, opts.url, ctx);\n await adapter\n .upsertComment({ sha: opts.sha, url: opts.url, status: opts.status, markdown })\n .catch((error: unknown) => {\n opts.logger?.warn({ err: error, provider: row.provider }, \"failed to upsert comment\");\n });\n }\n }),\n );\n}\n\nexport { postStatusesForBuild };\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"status-fanout.mjs","names":[],"sources":["../../src/capture/status-fanout.ts"],"sourcesContent":["import type { Logger } from \"pino\";\n\nimport { StatusConfigModel } from \"../models/status-config.ts\";\nimport type { CheckStatus, GitHostProvider } from \"../adapters/git-host/index.ts\";\nimport { buildCommentMarkdown } from \"../adapters/git-host/helpers.ts\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { Project } from \"../schema.ts\";\n\n/** Post build check statuses (and review comments) to every configured git provider. */\nasync function postStatusesForBuild(opts: {\n db: DatabaseAdapter;\n project: Project;\n sha: string;\n status: CheckStatus;\n url: string;\n providers: GitHostProvider[];\n secret: string | undefined;\n logger?: Logger;\n}): Promise<void> {\n if (opts.providers.length === 0) {\n return;\n }\n const model = new StatusConfigModel(opts.db, opts.secret);\n const rows = await model.list(opts.project.id);\n const ctx = `storyshelf/${opts.project.slug}`;\n await Promise.allSettled(\n rows.map(async (row) => {\n const provider = opts.providers.find((p) => p.metadata.kind === row.provider);\n if (!provider) {\n opts.logger?.warn({ provider: row.provider }, \"no provider registered for status config\");\n return;\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(row.config);\n provider.metadata.schema.parse(parsed);\n } catch (error: unknown) {\n opts.logger?.warn({ err: error, provider: row.provider }, \"invalid status config\");\n return;\n }\n let token: string;\n try {\n token = model.decryptToken(row);\n } catch (error: unknown) {\n opts.logger?.warn({ err: error }, \"failed to decrypt status token\");\n return;\n }\n const adapter = provider.create({ config: parsed, token, logger: opts.logger });\n await adapter.setStatus({ context: ctx, gitSha: opts.sha, status: opts.status, url: opts.url });\n if (adapter.upsertComment) {\n const markdown = buildCommentMarkdown(opts.status, opts.url, ctx);\n await adapter\n .upsertComment({ sha: opts.sha, url: opts.url, status: opts.status, markdown })\n .catch((error: unknown) => {\n opts.logger?.warn({ err: error, provider: row.provider }, \"failed to upsert comment\");\n });\n }\n }),\n );\n}\n\nexport { postStatusesForBuild };\n"],"mappings":";;;;;;;AASA,eAAe,qBAAqB,MASlB;CAChB,IAAI,KAAK,UAAU,WAAW,GAC5B;CAEF,MAAM,QAAQ,IAAI,kBAAkB,KAAK,IAAI,KAAK,MAAM;CACxD,MAAM,OAAO,MAAM,MAAM,KAAK,KAAK,QAAQ,EAAE;CAC7C,MAAM,MAAM,cAAc,KAAK,QAAQ;CACvC,MAAM,QAAQ,WACZ,KAAK,IAAI,OAAO,QAAQ;EACtB,MAAM,WAAW,KAAK,UAAU,MAAM,MAAM,EAAE,SAAS,SAAS,IAAI,QAAQ;EAC5E,IAAI,CAAC,UAAU;GACb,KAAK,QAAQ,KAAK,EAAE,UAAU,IAAI,SAAS,GAAG,0CAA0C;GACxF;EACF;EACA,IAAI;EACJ,IAAI;GACF,SAAS,KAAK,MAAM,IAAI,MAAM;GAC9B,SAAS,SAAS,OAAO,MAAM,MAAM;EACvC,SAAS,OAAgB;GACvB,KAAK,QAAQ,KAAK;IAAE,KAAK;IAAO,UAAU,IAAI;GAAS,GAAG,uBAAuB;GACjF;EACF;EACA,IAAI;EACJ,IAAI;GACF,QAAQ,MAAM,aAAa,GAAG;EAChC,SAAS,OAAgB;GACvB,KAAK,QAAQ,KAAK,EAAE,KAAK,MAAM,GAAG,gCAAgC;GAClE;EACF;EACA,MAAM,UAAU,SAAS,OAAO;GAAE,QAAQ;GAAQ;GAAO,QAAQ,KAAK;EAAO,CAAC;EAC9E,MAAM,QAAQ,UAAU;GAAE,SAAS;GAAK,QAAQ,KAAK;GAAK,QAAQ,KAAK;GAAQ,KAAK,KAAK;EAAI,CAAC;EAC9F,IAAI,QAAQ,eAAe;GACzB,MAAM,WAAW,qBAAqB,KAAK,QAAQ,KAAK,KAAK,GAAG;GAChE,MAAM,QACH,cAAc;IAAE,KAAK,KAAK;IAAK,KAAK,KAAK;IAAK,QAAQ,KAAK;IAAQ;GAAS,CAAC,CAAC,CAC9E,OAAO,UAAmB;IACzB,KAAK,QAAQ,KAAK;KAAE,KAAK;KAAO,UAAU,IAAI;IAAS,GAAG,0BAA0B;GACtF,CAAC;EACL;CACF,CAAC,CACH;AACF"}
|
|
@@ -2,6 +2,7 @@ import __tsdown_shims_path from 'node:path';
|
|
|
2
2
|
import __tsdown_shims_url from 'node:url';
|
|
3
3
|
import { StoryEntry, StorySourceAdapter } from "./adapter.mjs";
|
|
4
4
|
//#region src/capture/storybook.d.ts
|
|
5
|
+
/** Discovers stories from a built Storybook via its index file. */
|
|
5
6
|
declare class StorybookAdapter implements StorySourceAdapter {
|
|
6
7
|
readonly name = "storybook";
|
|
7
8
|
readonly screenshotSelector = "#storybook-root";
|
|
@@ -4,25 +4,34 @@ import.meta.url;
|
|
|
4
4
|
import { readFile } from "node:fs/promises";
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
//#region src/capture/storybook.ts
|
|
7
|
+
function mergeParameters(entry) {
|
|
8
|
+
const merged = {
|
|
9
|
+
...entry.parameters?.chromatic,
|
|
10
|
+
...entry.parameters?.storyshelf
|
|
11
|
+
};
|
|
12
|
+
return Object.keys(merged).length > 0 ? merged : void 0;
|
|
13
|
+
}
|
|
14
|
+
/** Discovers stories from a built Storybook via its index file. */
|
|
7
15
|
var StorybookAdapter = class {
|
|
8
16
|
name = "storybook";
|
|
9
17
|
screenshotSelector = "#storybook-root";
|
|
10
18
|
async discover(source) {
|
|
11
19
|
const index = await this.readIndex(source);
|
|
12
|
-
return Object.values(index.entries).filter((entry) => entry.type !== "docs").map((entry) => ({
|
|
20
|
+
return Object.values(index.entries).filter((entry) => entry.type !== "docs" && entry.subtype !== "test").map((entry) => ({
|
|
13
21
|
id: entry.id,
|
|
14
22
|
title: entry.title,
|
|
15
23
|
name: entry.name,
|
|
16
24
|
importPath: entry.importPath,
|
|
17
25
|
tags: entry.tags,
|
|
18
|
-
type: entry.type === "docs" ? "docs" : "story"
|
|
26
|
+
type: entry.type === "docs" ? "docs" : "story",
|
|
27
|
+
parameters: mergeParameters(entry)
|
|
19
28
|
}));
|
|
20
29
|
}
|
|
21
30
|
buildUrl(baseUrl, storyId) {
|
|
22
31
|
return `${baseUrl}/iframe.html?id=${encodeURIComponent(storyId)}&viewMode=story`;
|
|
23
32
|
}
|
|
24
33
|
async readIndex(source) {
|
|
25
|
-
const found = (await Promise.all(["
|
|
34
|
+
const found = (await Promise.all(["stories.json", "index.json"].map(async (name) => {
|
|
26
35
|
try {
|
|
27
36
|
const raw = await readFile(join(source, name), "utf8");
|
|
28
37
|
return JSON.parse(raw);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storybook.mjs","names":[],"sources":["../../src/capture/storybook.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport type { StoryEntry, StorySourceAdapter } from \"./adapter.ts\";\n\ninterface StorybookIndex {\n v: number;\n entries: Record
|
|
1
|
+
{"version":3,"file":"storybook.mjs","names":[],"sources":["../../src/capture/storybook.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport type { StoryEntry, StoryParameters, StorySourceAdapter } from \"./adapter.ts\";\n\ninterface StorybookIndex {\n v: number;\n entries: Record<\n string,\n {\n id: string;\n name: string;\n title: string;\n importPath?: string;\n tags?: string[];\n type: string;\n subtype?: string;\n parameters?: { chromatic?: StoryParameters; storyshelf?: StoryParameters };\n }\n >;\n}\n\nfunction mergeParameters(entry: {\n parameters?: { chromatic?: StoryParameters; storyshelf?: StoryParameters };\n}): StoryParameters | undefined {\n const merged: StoryParameters = {\n ...entry.parameters?.chromatic,\n ...entry.parameters?.storyshelf,\n };\n return Object.keys(merged).length > 0 ? merged : undefined;\n}\n\n/** Discovers stories from a built Storybook via its index file. */\nexport class StorybookAdapter implements StorySourceAdapter {\n readonly name = \"storybook\";\n readonly screenshotSelector = \"#storybook-root\";\n\n async discover(source: string): Promise<StoryEntry[]> {\n const index = await this.readIndex(source);\n return Object.values(index.entries)\n .filter((entry) => entry.type !== \"docs\" && entry.subtype !== \"test\")\n .map((entry) => ({\n id: entry.id,\n title: entry.title,\n name: entry.name,\n importPath: entry.importPath,\n tags: entry.tags,\n type: entry.type === \"docs\" ? \"docs\" : \"story\",\n parameters: mergeParameters(entry),\n }));\n }\n\n // eslint-disable-next-line class-methods-use-this\n buildUrl(baseUrl: string, storyId: string): string {\n return `${baseUrl}/iframe.html?id=${encodeURIComponent(storyId)}&viewMode=story`;\n }\n\n // eslint-disable-next-line class-methods-use-this\n private async readIndex(source: string): Promise<StorybookIndex> {\n const candidates: string[] = [\"stories.json\", \"index.json\"];\n const results = await Promise.all(\n candidates.map(async (name) => {\n try {\n const raw = await readFile(join(source, name), \"utf8\");\n return JSON.parse(raw) as StorybookIndex;\n } catch {\n return null;\n }\n }),\n );\n const found = results.find((index) => index !== null);\n if (found) {\n return found;\n }\n throw new Error(`No Storybook index found in ${source}`);\n }\n}\n"],"mappings":";;;;;;AAsBA,SAAS,gBAAgB,OAEO;CAC9B,MAAM,SAA0B;EAC9B,GAAG,MAAM,YAAY;EACrB,GAAG,MAAM,YAAY;CACvB;CACA,OAAO,OAAO,KAAK,MAAM,CAAC,CAAC,SAAS,IAAI,SAAS,KAAA;AACnD;;AAGA,IAAa,mBAAb,MAA4D;CAC1D,OAAgB;CAChB,qBAA8B;CAE9B,MAAM,SAAS,QAAuC;EACpD,MAAM,QAAQ,MAAM,KAAK,UAAU,MAAM;EACzC,OAAO,OAAO,OAAO,MAAM,OAAO,CAAC,CAChC,QAAQ,UAAU,MAAM,SAAS,UAAU,MAAM,YAAY,MAAM,CAAC,CACpE,KAAK,WAAW;GACf,IAAI,MAAM;GACV,OAAO,MAAM;GACb,MAAM,MAAM;GACZ,YAAY,MAAM;GAClB,MAAM,MAAM;GACZ,MAAM,MAAM,SAAS,SAAS,SAAS;GACvC,YAAY,gBAAgB,KAAK;EACnC,EAAE;CACN;CAGA,SAAS,SAAiB,SAAyB;EACjD,OAAO,GAAG,QAAQ,kBAAkB,mBAAmB,OAAO,EAAE;CAClE;CAGA,MAAc,UAAU,QAAyC;EAY/D,MAAM,SAAQ,MAVQ,QAAQ,IAC5B,CAF4B,gBAAgB,YAEnC,CAAC,CAAC,IAAI,OAAO,SAAS;GAC7B,IAAI;IACF,MAAM,MAAM,MAAM,SAAS,KAAK,QAAQ,IAAI,GAAG,MAAM;IACrD,OAAO,KAAK,MAAM,GAAG;GACvB,QAAQ;IACN,OAAO;GACT;EACF,CAAC,CACH,EAAA,CACsB,MAAM,UAAU,UAAU,IAAI;EACpD,IAAI,OACF,OAAO;EAET,MAAM,IAAI,MAAM,+BAA+B,QAAQ;CACzD;AACF"}
|
package/dist/config.d.mts
CHANGED
|
@@ -9,6 +9,7 @@ import { StorageAdapter } from "./adapter/storage.mjs";
|
|
|
9
9
|
import { z } from "zod";
|
|
10
10
|
import { Logger } from "pino";
|
|
11
11
|
//#region src/config.d.ts
|
|
12
|
+
/** Brand color theme for the server-rendered UI. */
|
|
12
13
|
interface BrandTheme {
|
|
13
14
|
accent: string;
|
|
14
15
|
surface: {
|
|
@@ -26,6 +27,7 @@ interface BrandTheme {
|
|
|
26
27
|
rejected: string;
|
|
27
28
|
};
|
|
28
29
|
}
|
|
30
|
+
/** Branding overrides for the server-rendered UI. */
|
|
29
31
|
interface UIConfig {
|
|
30
32
|
name?: string;
|
|
31
33
|
logo?: string;
|
|
@@ -33,25 +35,30 @@ interface UIConfig {
|
|
|
33
35
|
lightTheme?: BrandTheme;
|
|
34
36
|
darkTheme?: BrandTheme;
|
|
35
37
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
38
|
+
/** A viewport in which stories are captured. */
|
|
39
|
+
interface ShelfViewport {
|
|
40
|
+
name: string;
|
|
41
|
+
width: number;
|
|
42
|
+
height: number;
|
|
43
|
+
}
|
|
44
|
+
/** Metadata snapshot of a configured adapter. */
|
|
45
|
+
interface AdapterSnapshot {
|
|
46
|
+
name: string;
|
|
47
|
+
version: string;
|
|
48
|
+
description?: string;
|
|
49
|
+
kind: string;
|
|
50
|
+
}
|
|
51
|
+
/** Shelf-level configuration (validated by {@link shelfConfigSchema}). */
|
|
52
|
+
interface ShelfConfig {
|
|
53
|
+
secret?: string;
|
|
54
|
+
publishedBaseDomain?: string;
|
|
55
|
+
captureConcurrency?: number;
|
|
56
|
+
scratchDir?: string;
|
|
57
|
+
purgeTtlDays?: number;
|
|
58
|
+
viewports?: ShelfViewport[];
|
|
59
|
+
adapters?: Record<string, AdapterSnapshot>;
|
|
60
|
+
}
|
|
61
|
+
/** Adapter and configuration options for creating the shelf router. */
|
|
55
62
|
interface ShelfOptions {
|
|
56
63
|
database: DatabaseAdapter;
|
|
57
64
|
storage: StorageAdapter;
|
|
@@ -64,5 +71,5 @@ interface ShelfOptions {
|
|
|
64
71
|
config?: ShelfConfig;
|
|
65
72
|
}
|
|
66
73
|
//#endregion
|
|
67
|
-
export { BrandTheme, ShelfConfig, ShelfOptions,
|
|
74
|
+
export { AdapterSnapshot, BrandTheme, ShelfConfig, ShelfOptions, ShelfViewport, UIConfig };
|
|
68
75
|
//# sourceMappingURL=config.d.mts.map
|
package/dist/config.mjs
CHANGED
|
@@ -31,6 +31,7 @@ const adapterSnapshotSchema = z.object({
|
|
|
31
31
|
description: z.string().optional(),
|
|
32
32
|
kind: z.string()
|
|
33
33
|
});
|
|
34
|
+
/** Zod schema validating the shelf-level configuration. */
|
|
34
35
|
const shelfConfigSchema = z.object({
|
|
35
36
|
secret: z.string().min(1).optional(),
|
|
36
37
|
publishedBaseDomain: z.string().optional(),
|
|
@@ -40,13 +41,20 @@ const shelfConfigSchema = z.object({
|
|
|
40
41
|
viewports: z.array(viewportSchema).min(1, "at least one viewport required").optional(),
|
|
41
42
|
adapters: z.record(z.string(), adapterSnapshotSchema).optional()
|
|
42
43
|
}).strict();
|
|
44
|
+
/** Zod schema validating the UI branding configuration. */
|
|
43
45
|
const uiConfigSchema = z.object({
|
|
44
46
|
name: z.string().optional(),
|
|
45
|
-
logo: z.url().optional(),
|
|
46
|
-
favicon: z.url().optional(),
|
|
47
|
+
logo: z.string().url().optional(),
|
|
48
|
+
favicon: z.string().url().optional(),
|
|
47
49
|
lightTheme: brandThemeSchema.optional(),
|
|
48
50
|
darkTheme: brandThemeSchema.optional()
|
|
49
51
|
}).strict();
|
|
52
|
+
/**
|
|
53
|
+
* Parse and validate a raw shelf-level configuration object.
|
|
54
|
+
*
|
|
55
|
+
* @param config - Unvalidated configuration record.
|
|
56
|
+
* @returns The validated shelf configuration.
|
|
57
|
+
*/
|
|
50
58
|
function validateConfig(config) {
|
|
51
59
|
const result = shelfConfigSchema.safeParse(config);
|
|
52
60
|
if (!result.success) {
|
|
@@ -55,6 +63,12 @@ function validateConfig(config) {
|
|
|
55
63
|
}
|
|
56
64
|
return result.data;
|
|
57
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* Parse and validate a raw UI branding configuration object.
|
|
68
|
+
*
|
|
69
|
+
* @param config - Unvalidated UI configuration record.
|
|
70
|
+
* @returns The validated UI configuration.
|
|
71
|
+
*/
|
|
58
72
|
function validateUiConfig(config) {
|
|
59
73
|
const result = uiConfigSchema.safeParse(config);
|
|
60
74
|
if (!result.success) {
|
package/dist/config.mjs.map
CHANGED
|
@@ -1 +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\
|
|
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\n/** Brand color theme for the server-rendered UI. */\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\n/** Branding overrides for the server-rendered UI. */\nexport interface UIConfig {\n name?: string;\n logo?: string;\n favicon?: string;\n lightTheme?: BrandTheme;\n darkTheme?: BrandTheme;\n}\n\n/** A viewport in which stories are captured. */\nexport interface ShelfViewport {\n name: string;\n width: number;\n height: number;\n}\n\n/** Metadata snapshot of a configured adapter. */\nexport interface AdapterSnapshot {\n name: string;\n version: string;\n description?: string;\n kind: string;\n}\n\n/** Shelf-level configuration (validated by {@link shelfConfigSchema}). */\nexport interface ShelfConfig {\n secret?: string;\n publishedBaseDomain?: string;\n captureConcurrency?: number;\n scratchDir?: string;\n purgeTtlDays?: number;\n viewports?: ShelfViewport[];\n adapters?: Record<string, AdapterSnapshot>;\n}\n\nconst viewportSchema: z.ZodType<ShelfViewport> = 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.ZodType<AdapterSnapshot> = z.object({\n name: z.string(),\n version: z.string(),\n description: z.string().optional(),\n kind: z.string(),\n});\n\n/** Zod schema validating the shelf-level configuration. */\nexport const shelfConfigSchema: z.ZodType<ShelfConfig> = 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\n/** Zod schema validating the UI branding configuration. */\nexport const uiConfigSchema = z\n .object({\n name: z.string().optional(),\n // oxlint-disable-next-line typescript/no-deprecated -- z.string().url() kept for zod v3 API compat\n logo: z.string().url().optional(),\n // oxlint-disable-next-line typescript/no-deprecated -- z.string().url() kept for zod v3 API compat\n favicon: z.string().url().optional(),\n lightTheme: brandThemeSchema.optional(),\n darkTheme: brandThemeSchema.optional(),\n })\n .strict();\n\n/**\n * Parse and validate a raw shelf-level configuration object.\n *\n * @param config - Unvalidated configuration record.\n * @returns The validated shelf configuration.\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\n/**\n * Parse and validate a raw UI branding configuration object.\n *\n * @param config - Unvalidated UI configuration record.\n * @returns The validated UI configuration.\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\n/** Adapter and configuration options for creating the shelf router. */\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":";;;;;AAsDA,MAAM,iBAA2C,EAAE,OAAO;CACxD,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,wBAAoD,EAAE,OAAO;CACjE,MAAM,EAAE,OAAO;CACf,SAAS,EAAE,OAAO;CAClB,aAAa,EAAE,OAAO,CAAC,CAAC,SAAS;CACjC,MAAM,EAAE,OAAO;AACjB,CAAC;;AAGD,MAAa,oBAA4C,EACtD,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;;AAGV,MAAa,iBAAiB,EAC3B,OAAO;CACN,MAAM,EAAE,OAAO,CAAC,CAAC,SAAS;CAE1B,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS;CAEhC,SAAS,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS;CACnC,YAAY,iBAAiB,SAAS;CACtC,WAAW,iBAAiB,SAAS;AACvC,CAAC,CAAC,CACD,OAAO;;;;;;;AAQV,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;;;;;;;AAQA,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
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import __tsdown_shims_path from 'node:path';
|
|
2
2
|
import __tsdown_shims_url from 'node:url';
|
|
3
3
|
//#region src/ddl.d.ts
|
|
4
|
-
|
|
4
|
+
/** Canonical SQLite DDL for all StoryShelf tables and indexes. */
|
|
5
|
+
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 execute_play INTEGER NOT NULL DEFAULT 0,\n play_timeout_ms INTEGER NOT NULL DEFAULT 10000,\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
6
|
//#endregion
|
|
6
7
|
export { DDL };
|
|
7
8
|
//# sourceMappingURL=ddl.d.mts.map
|
package/dist/ddl.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import "node:path";
|
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
4
|
//#region src/ddl.ts
|
|
5
|
+
/** Canonical SQLite DDL for all StoryShelf tables and indexes. */
|
|
5
6
|
const DDL = `
|
|
6
7
|
CREATE TABLE IF NOT EXISTS projects (
|
|
7
8
|
id TEXT PRIMARY KEY,
|
|
@@ -13,6 +14,8 @@ CREATE TABLE IF NOT EXISTS projects (
|
|
|
13
14
|
max_diff_ratio REAL NOT NULL DEFAULT 0.01,
|
|
14
15
|
public_branch_regex TEXT,
|
|
15
16
|
storybook_meta TEXT,
|
|
17
|
+
execute_play INTEGER NOT NULL DEFAULT 0,
|
|
18
|
+
play_timeout_ms INTEGER NOT NULL DEFAULT 10000,
|
|
16
19
|
created_at TEXT NOT NULL,
|
|
17
20
|
updated_at TEXT NOT NULL
|
|
18
21
|
);
|
package/dist/ddl.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ddl.mjs","names":[],"sources":["../src/ddl.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"ddl.mjs","names":[],"sources":["../src/ddl.ts"],"sourcesContent":["/** Canonical SQLite DDL for all StoryShelf tables and indexes. */\nexport 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 execute_play INTEGER NOT NULL DEFAULT 0,\n play_timeout_ms INTEGER NOT NULL DEFAULT 10000,\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":";;;;;AACA,MAAa,MAAM"}
|
package/dist/diff/engine.d.mts
CHANGED
|
@@ -2,6 +2,14 @@ import __tsdown_shims_path from 'node:path';
|
|
|
2
2
|
import __tsdown_shims_url from 'node:url';
|
|
3
3
|
import { DiffOptions, DiffResult } from "./options.mjs";
|
|
4
4
|
//#region src/diff/engine.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Compare baseline and current screenshots, returning pass/fail and a diff image.
|
|
7
|
+
*
|
|
8
|
+
* @param baseline - Baseline PNG buffer.
|
|
9
|
+
* @param current - Current capture PNG buffer.
|
|
10
|
+
* @param options - Pixel threshold and ratio limits.
|
|
11
|
+
* @returns The diff result with pixel counts and an optional overlay image.
|
|
12
|
+
*/
|
|
5
13
|
declare function diffImages(baseline: Buffer, current: Buffer, options: DiffOptions): DiffResult;
|
|
6
14
|
//#endregion
|
|
7
15
|
export { diffImages };
|
package/dist/diff/engine.mjs
CHANGED
|
@@ -4,6 +4,14 @@ import.meta.url;
|
|
|
4
4
|
import pixelmatch from "pixelmatch";
|
|
5
5
|
import { PNG } from "pngjs";
|
|
6
6
|
//#region src/diff/engine.ts
|
|
7
|
+
/**
|
|
8
|
+
* Compare baseline and current screenshots, returning pass/fail and a diff image.
|
|
9
|
+
*
|
|
10
|
+
* @param baseline - Baseline PNG buffer.
|
|
11
|
+
* @param current - Current capture PNG buffer.
|
|
12
|
+
* @param options - Pixel threshold and ratio limits.
|
|
13
|
+
* @returns The diff result with pixel counts and an optional overlay image.
|
|
14
|
+
*/
|
|
7
15
|
function diffImages(baseline, current, options) {
|
|
8
16
|
const baselinePng = PNG.sync.read(baseline);
|
|
9
17
|
const currentPng = PNG.sync.read(current);
|
|
@@ -39,8 +47,8 @@ function diffImages(baseline, current, options) {
|
|
|
39
47
|
sizeChanged: true
|
|
40
48
|
};
|
|
41
49
|
}
|
|
42
|
-
const width = baselineDimensions
|
|
43
|
-
const height = baselineDimensions
|
|
50
|
+
const { width } = baselineDimensions;
|
|
51
|
+
const { height } = baselineDimensions;
|
|
44
52
|
const overlap = compareRegions(baselinePng.data, currentPng.data, width, height, options);
|
|
45
53
|
const diffRatio = overlap.diffPixels / (width * height);
|
|
46
54
|
return {
|
package/dist/diff/engine.mjs.map
CHANGED
|
@@ -1 +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
|
|
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\n/**\n * Compare baseline and current screenshots, returning pass/fail and a diff image.\n *\n * @param baseline - Baseline PNG buffer.\n * @param current - Current capture PNG buffer.\n * @param options - Pixel threshold and ratio limits.\n * @returns The diff result with pixel counts and an optional overlay image.\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;\n const {height} = baselineDimensions;\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":";;;;;;;;;;;;;;AAaA,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,EAAC,UAAS;CAChB,MAAM,EAAC,WAAU;CACjB,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"}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import __tsdown_shims_path from 'node:path';
|
|
2
2
|
import __tsdown_shims_url from 'node:url';
|
|
3
3
|
import { AdapterMetadata, GitAdapterMetadata } from "./adapters/metadata.mjs";
|
|
4
|
+
import { Logger, LoggerOptions, PinoTransport, createShelfLogger } from "./logger.mjs";
|
|
4
5
|
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
6
|
import { AuthAdapter, AuthCallback, AuthUser } from "./adapter/auth.mjs";
|
|
6
7
|
import { DEFAULT_VIEWPORTS, StoryEntry, StorySourceAdapter, Viewport } from "./capture/adapter.mjs";
|
|
@@ -11,7 +12,6 @@ import { CheckStatus, GitHostAdapter, GitHostProvider } from "./adapter/git-host
|
|
|
11
12
|
import { buildCommentMarkdown, commentMarker, describeStatus } from "./adapter/git-host/helpers.mjs";
|
|
12
13
|
import { StorageAdapter } from "./adapter/storage.mjs";
|
|
13
14
|
import { BrandTheme, ShelfConfig, ShelfOptions, UIConfig } from "./config.mjs";
|
|
14
|
-
import { LoggerOptions, PinoTransport, createShelfLogger } from "./logger.mjs";
|
|
15
15
|
import { DiffOptions, DiffResult } from "./diff/options.mjs";
|
|
16
16
|
import { diffImages } from "./diff/engine.mjs";
|
|
17
17
|
import { RenderedContent } from "./ui/document.mjs";
|
|
@@ -26,7 +26,8 @@ import { slugify, ulid } from "./utils/ulid.mjs";
|
|
|
26
26
|
import { baselinePath, diffPath, screenshotPath, storybookDir, storybookZipPath } from "./utils/paths.mjs";
|
|
27
27
|
import { OpenAPIHono } from "@hono/zod-openapi";
|
|
28
28
|
//#region src/index.d.ts
|
|
29
|
-
|
|
29
|
+
/** Per-request shelf context (adapters, config, user, capture queue). */
|
|
30
|
+
interface ShelfContext {
|
|
30
31
|
requestId?: string;
|
|
31
32
|
db: DatabaseAdapter;
|
|
32
33
|
storage: StorageAdapter;
|
|
@@ -38,11 +39,13 @@ type ShelfContext = {
|
|
|
38
39
|
enqueueCapture?: (buildId: string, reqId?: string) => Promise<void>;
|
|
39
40
|
captureQueue: CaptureQueue | null;
|
|
40
41
|
gitHosts: GitHostProvider[];
|
|
41
|
-
}
|
|
42
|
+
}
|
|
43
|
+
/** Hono application type carrying the shelf context variables. */
|
|
42
44
|
type ShelfApp = OpenAPIHono<{
|
|
43
45
|
Variables: ShelfContext;
|
|
44
46
|
}>;
|
|
47
|
+
/** Create the StoryShelf Hono router with all API routes and HTML pages. */
|
|
45
48
|
declare function createShelfRouter(options: ShelfOptions): ShelfApp;
|
|
46
49
|
//#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 };
|
|
50
|
+
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 Logger, 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
51
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.mjs
CHANGED
|
@@ -5,7 +5,6 @@ import { validateConfig, validateUiConfig } from "./config.mjs";
|
|
|
5
5
|
import { createShelfLogger } from "./logger.mjs";
|
|
6
6
|
import { rateLimit } from "./middleware/rate-limit.mjs";
|
|
7
7
|
import { csrf } from "./middleware/csrf.mjs";
|
|
8
|
-
import { baselines, buildLabels, builds, comments, labelTypes, projectMembers, projectStatusConfigs, projects, schema, snapshots, tokens, users, webhooks } from "./schema.mjs";
|
|
9
8
|
import { slugify, ulid } from "./utils/ulid.mjs";
|
|
10
9
|
import { BuildModel } from "./models/build.mjs";
|
|
11
10
|
import { ProjectModel } from "./models/project.mjs";
|
|
@@ -22,6 +21,7 @@ import { registerMedia } from "./routers/media.mjs";
|
|
|
22
21
|
import { registerMembers } from "./routers/members.mjs";
|
|
23
22
|
import { registerProjects } from "./routers/projects.mjs";
|
|
24
23
|
import { registerStatusConfigs } from "./routers/status-configs.mjs";
|
|
24
|
+
import { registerStorybook } from "./routers/storybook.mjs";
|
|
25
25
|
import { registerTokens } from "./routers/tokens.mjs";
|
|
26
26
|
import { registerUiPages } from "./routers/ui.mjs";
|
|
27
27
|
import { registerWebhooks } from "./routers/webhooks.mjs";
|
|
@@ -34,10 +34,15 @@ import { InMemoryCaptureQueue } from "./capture/queue.mjs";
|
|
|
34
34
|
import { buildCommentMarkdown, commentMarker, describeStatus } from "./adapter/git-host/helpers.mjs";
|
|
35
35
|
import { postStatusesForBuild } from "./capture/status-fanout.mjs";
|
|
36
36
|
import { createUrlBuilder } from "./urls.mjs";
|
|
37
|
+
import { baselines, buildLabels, builds, comments, labelTypes, projectMembers, projectStatusConfigs, projects, schema, snapshots, tokens, users, webhooks } from "./schema.mjs";
|
|
37
38
|
import { swaggerUI } from "@hono/swagger-ui";
|
|
38
39
|
import { OpenAPIHono } from "@hono/zod-openapi";
|
|
39
40
|
import { requestId } from "hono/request-id";
|
|
40
41
|
//#region src/index.tsx
|
|
42
|
+
/**
|
|
43
|
+
* StoryShelf core: compose database, storage, capture, auth, and git-host
|
|
44
|
+
* adapters into a complete self-hosted visual-testing Hono server.
|
|
45
|
+
*/
|
|
41
46
|
function buildAdapterSnapshot(options) {
|
|
42
47
|
const snap = {};
|
|
43
48
|
if (options.database.metadata) snap["database"] = options.database.metadata;
|
|
@@ -92,6 +97,7 @@ async function resolveUser(c, options) {
|
|
|
92
97
|
if (c.req.header("authorization")) return null;
|
|
93
98
|
return await options.auth.check(c.req.raw);
|
|
94
99
|
}
|
|
100
|
+
/** Create the StoryShelf Hono router with all API routes and HTML pages. */
|
|
95
101
|
function createShelfRouter(options) {
|
|
96
102
|
const app = new OpenAPIHono();
|
|
97
103
|
const rawConfig = options.config ? validateConfig(options.config) : {};
|
|
@@ -290,8 +296,8 @@ function createShelfRouter(options) {
|
|
|
290
296
|
});
|
|
291
297
|
app.use("*", async (c, next) => {
|
|
292
298
|
const { user, authEnabled: isAuthEnabled } = getStore();
|
|
293
|
-
const path = c.req
|
|
294
|
-
if (!isAuthEnabled || user || path.startsWith("/api/") || path.startsWith("/auth/") || path.startsWith("/assets/")) return next();
|
|
299
|
+
const { path } = c.req;
|
|
300
|
+
if (!isAuthEnabled || user || path.startsWith("/api/") || path.startsWith("/auth/") || path.startsWith("/assets/") || path.startsWith("/projects/") && path.includes("/storybook")) return next();
|
|
295
301
|
if (c.req.header("HX-Request") === "true") {
|
|
296
302
|
c.header("HX-Redirect", "/auth/login");
|
|
297
303
|
return c.body(null, 204);
|
|
@@ -309,6 +315,7 @@ function createShelfRouter(options) {
|
|
|
309
315
|
registerAdmin(app);
|
|
310
316
|
if (options.auth) registerAuth(app, options.auth);
|
|
311
317
|
registerAssets(app);
|
|
318
|
+
registerStorybook(app);
|
|
312
319
|
registerUiPages(app);
|
|
313
320
|
app.doc("/api/v1/openapi.json", {
|
|
314
321
|
openapi: "3.0.0",
|