@storyshelf/core 0.1.2 → 0.3.1
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/README.md +50 -46
- package/dist/adapter/auth.d.mts +4 -4
- package/dist/adapter/auth.mjs.map +1 -1
- package/dist/{adapters → adapter}/capture-queue.d.mts +6 -4
- package/dist/adapter/capture-queue.mjs +4 -0
- package/dist/adapter/capture-runner.d.mts +7 -7
- package/dist/adapter/database.d.mts +20 -9
- package/dist/adapter/database.mjs +60 -1
- package/dist/adapter/database.mjs.map +1 -0
- package/dist/adapter/git-host/comments.d.mts +41 -0
- package/dist/adapter/git-host/comments.mjs +54 -0
- package/dist/adapter/git-host/comments.mjs.map +1 -0
- package/dist/adapter/git-host/helpers.d.mts +1 -0
- package/dist/adapter/git-host/helpers.mjs +1 -0
- package/dist/adapter/git-host/helpers.mjs.map +1 -1
- package/dist/adapter/git-host.d.mts +3 -7
- package/dist/adapter/init.d.mts +58 -0
- package/dist/adapter/init.mjs +93 -0
- package/dist/adapter/init.mjs.map +1 -0
- package/dist/adapter/metadata.d.mts +83 -0
- package/dist/adapter/metadata.mjs +4 -0
- package/dist/adapter/storage.d.mts +15 -4
- package/dist/adapters/webhook-events.mjs +20 -4
- package/dist/adapters/webhook-events.mjs.map +1 -1
- package/dist/capture/adapter.d.mts +7 -1
- package/dist/capture/adapter.mjs +3 -0
- package/dist/capture/adapter.mjs.map +1 -1
- package/dist/capture/dispatch.mjs +150 -0
- package/dist/capture/dispatch.mjs.map +1 -0
- package/dist/capture/orchestrator.d.mts +9 -0
- package/dist/capture/orchestrator.mjs +11 -44
- package/dist/capture/orchestrator.mjs.map +1 -1
- package/dist/capture/pipeline.d.mts +4 -1
- package/dist/capture/pipeline.mjs +6 -6
- package/dist/capture/pipeline.mjs.map +1 -1
- package/dist/capture/queue.d.mts +4 -1
- package/dist/capture/queue.mjs +3 -1
- package/dist/capture/queue.mjs.map +1 -1
- package/dist/capture/skip-checks.mjs +76 -0
- package/dist/capture/skip-checks.mjs.map +1 -0
- package/dist/capture/statics.mjs +124 -0
- package/dist/capture/statics.mjs.map +1 -0
- 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 +1 -0
- package/dist/capture/storybook.mjs.map +1 -1
- package/dist/capture.d.mts +8 -0
- package/dist/capture.mjs +9 -0
- package/dist/config.d.mts +37 -22
- package/dist/config.mjs +28 -2
- package/dist/config.mjs.map +1 -1
- package/dist/ddl.d.mts +2 -1
- package/dist/ddl.mjs +2 -1
- package/dist/ddl.mjs.map +1 -1
- package/dist/diff/engine.d.mts +8 -0
- package/dist/diff/engine.mjs +8 -0
- package/dist/diff/engine.mjs.map +1 -1
- package/dist/diff/options.d.mts +3 -1
- package/dist/diff.d.mts +5 -0
- package/dist/diff.mjs +6 -0
- package/dist/index.d.mts +34 -24
- package/dist/index.mjs +211 -252
- package/dist/index.mjs.map +1 -1
- package/dist/logger.d.mts +3 -3
- package/dist/logger.mjs.map +1 -1
- package/dist/middleware/auth-gate.mjs +36 -0
- package/dist/middleware/auth-gate.mjs.map +1 -0
- package/dist/middleware/csrf.mjs +1 -0
- 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/middleware/request-log.mjs +28 -0
- package/dist/middleware/request-log.mjs.map +1 -0
- package/dist/middleware/store-scope.mjs +29 -0
- package/dist/middleware/store-scope.mjs.map +1 -0
- package/dist/models/baseline.mjs +9 -5
- package/dist/models/baseline.mjs.map +1 -1
- package/dist/models/build.mjs +5 -1
- package/dist/models/build.mjs.map +1 -1
- package/dist/models/comment.mjs +4 -1
- package/dist/models/comment.mjs.map +1 -1
- package/dist/models/label.mjs +3 -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.mjs +2 -1
- package/dist/models/project.mjs.map +1 -1
- 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.mjs +12 -3
- package/dist/models/webhook.mjs.map +1 -1
- package/dist/pages/build-detail.mjs +10 -4
- package/dist/pages/build-detail.mjs.map +1 -1
- package/dist/pages/build-diff-comments.mjs +113 -0
- package/dist/pages/build-diff-comments.mjs.map +1 -0
- package/dist/pages/build-diff-header.mjs +163 -0
- package/dist/pages/build-diff-header.mjs.map +1 -0
- package/dist/pages/build-diff-nav.mjs +66 -0
- package/dist/pages/build-diff-nav.mjs.map +1 -0
- package/dist/pages/build-diff-viewer.mjs +197 -0
- package/dist/pages/build-diff-viewer.mjs.map +1 -0
- package/dist/pages/build-diff.mjs +66 -413
- package/dist/pages/build-diff.mjs.map +1 -1
- package/dist/pages/compute-jobs.mjs +3 -1
- package/dist/pages/compute-jobs.mjs.map +1 -1
- package/dist/pages/label-detail.mjs +4 -3
- package/dist/pages/label-detail.mjs.map +1 -1
- package/dist/pages/login.mjs +1 -0
- package/dist/pages/login.mjs.map +1 -1
- package/dist/pages/project-builds.mjs +6 -3
- 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 +1 -0
- package/dist/pages/project-settings.mjs.map +1 -1
- package/dist/pages/projects.mjs +12 -6
- package/dist/pages/projects.mjs.map +1 -1
- package/dist/pages/root.mjs +7 -3
- 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 +2 -1
- package/dist/pages/settings-labels.mjs.map +1 -1
- package/dist/pages/settings-members.mjs +2 -1
- package/dist/pages/settings-members.mjs.map +1 -1
- package/dist/pages/settings-status.mjs +3 -1
- package/dist/pages/settings-status.mjs.map +1 -1
- package/dist/pages/settings-tests.mjs +4 -3
- package/dist/pages/settings-tests.mjs.map +1 -1
- 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 +38 -1
- package/dist/pages/storybook.mjs.map +1 -1
- package/dist/{utils/paths.d.mts → paths.d.mts} +5 -0
- package/dist/{utils/paths.mjs → paths.mjs} +5 -0
- package/dist/paths.mjs.map +1 -0
- package/dist/retention/purge.mjs +2 -1
- package/dist/retention/purge.mjs.map +1 -1
- package/dist/routers/admin.mjs +2 -1
- 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 +114 -19
- package/dist/routers/builds.handlers.mjs.map +1 -1
- package/dist/routers/builds.mjs +55 -34
- 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/health.mjs +137 -0
- package/dist/routers/health.mjs.map +1 -0
- package/dist/routers/helpers.mjs +8 -2
- 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 +2 -1
- package/dist/routers/labels.mjs.map +1 -1
- package/dist/routers/media.mjs +3 -2
- 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 +2 -1
- package/dist/routers/projects.mjs.map +1 -1
- package/dist/routers/schemas.mjs +25 -0
- package/dist/routers/schemas.mjs.map +1 -1
- package/dist/routers/settings-general.mjs +95 -0
- package/dist/routers/settings-general.mjs.map +1 -0
- package/dist/routers/settings-labels.mjs +49 -0
- package/dist/routers/settings-labels.mjs.map +1 -0
- package/dist/routers/settings-members.mjs +30 -0
- package/dist/routers/settings-members.mjs.map +1 -0
- package/dist/routers/settings-status.mjs +73 -0
- package/dist/routers/settings-status.mjs.map +1 -0
- package/dist/routers/settings-tokens.mjs +40 -0
- package/dist/routers/settings-tokens.mjs.map +1 -0
- package/dist/routers/settings-webhooks.mjs +63 -0
- package/dist/routers/settings-webhooks.mjs.map +1 -0
- package/dist/routers/settings.handlers.mjs +93 -0
- package/dist/routers/settings.handlers.mjs.map +1 -0
- package/dist/routers/settings.mjs +14 -246
- package/dist/routers/settings.mjs.map +1 -1
- package/dist/routers/snapshots.mjs +2 -1
- package/dist/routers/snapshots.mjs.map +1 -1
- package/dist/routers/status-configs.mjs +2 -1
- package/dist/routers/status-configs.mjs.map +1 -1
- package/dist/routers/storybook.mjs +12 -4
- package/dist/routers/storybook.mjs.map +1 -1
- package/dist/routers/tokens.mjs +2 -1
- package/dist/routers/tokens.mjs.map +1 -1
- package/dist/routers/ui.mjs +5 -4
- package/dist/routers/ui.mjs.map +1 -1
- package/dist/routers/webhooks.mjs +7 -6
- package/dist/routers/webhooks.mjs.map +1 -1
- package/dist/schema/baseline.d.mts +18 -0
- package/dist/schema/baseline.mjs +22 -0
- package/dist/schema/baseline.mjs.map +1 -0
- package/dist/schema/build.d.mts +26 -0
- package/dist/schema/build.mjs +29 -0
- package/dist/schema/build.mjs.map +1 -0
- package/dist/schema/comment.d.mts +19 -0
- package/dist/schema/comment.mjs +26 -0
- package/dist/schema/comment.mjs.map +1 -0
- package/dist/schema/label.d.mts +25 -0
- package/dist/schema/label.mjs +30 -0
- package/dist/schema/label.mjs.map +1 -0
- package/dist/schema/member.d.mts +15 -0
- package/dist/schema/member.mjs +19 -0
- package/dist/schema/member.mjs.map +1 -0
- package/dist/schema/project.d.mts +34 -0
- package/dist/schema/project.mjs +25 -0
- package/dist/schema/project.mjs.map +1 -0
- package/dist/schema/snapshot.d.mts +30 -0
- package/dist/schema/snapshot.mjs +34 -0
- package/dist/schema/snapshot.mjs.map +1 -0
- package/dist/schema/status-config.d.mts +16 -0
- package/dist/schema/status-config.mjs +20 -0
- package/dist/schema/status-config.mjs.map +1 -0
- package/dist/schema/token.d.mts +15 -0
- package/dist/schema/token.mjs +19 -0
- package/dist/schema/token.mjs.map +1 -0
- package/dist/schema/user.d.mts +17 -0
- package/dist/schema/user.mjs +19 -0
- package/dist/schema/user.mjs.map +1 -0
- package/dist/schema/webhook.d.mts +16 -0
- package/dist/schema/webhook.mjs +20 -0
- package/dist/schema/webhook.mjs.map +1 -0
- package/dist/schema.d.mts +52 -4433
- package/dist/schema.mjs +37 -139
- 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 +13 -2
- package/dist/types.mjs +8 -0
- package/dist/types.mjs.map +1 -1
- package/dist/ui/document.mjs +1 -0
- package/dist/ui/document.mjs.map +1 -1
- package/dist/ui/feedback.mjs +24 -0
- package/dist/ui/feedback.mjs.map +1 -0
- package/dist/ui/{components.mjs → forms.mjs} +6 -16
- package/dist/ui/forms.mjs.map +1 -0
- 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/ulid.mjs +2 -0
- package/dist/utils/ulid.mjs.map +1 -1
- package/package.json +47 -45
- package/dist/adapters/metadata.d.mts +0 -24
- package/dist/models/baseline.d.mts +0 -20
- package/dist/models/build.d.mts +0 -50
- package/dist/models/comment.d.mts +0 -20
- package/dist/models/label.d.mts +0 -49
- package/dist/models/member.d.mts +0 -27
- package/dist/models/project.d.mts +0 -42
- package/dist/models/snapshot.d.mts +0 -46
- package/dist/models/token.d.mts +0 -33
- package/dist/models/webhook.d.mts +0 -37
- package/dist/retention/purge.d.mts +0 -28
- package/dist/ui/components.mjs.map +0 -1
- package/dist/ui/document.d.mts +0 -8
- package/dist/utils/paths.mjs.map +0 -1
- package/dist/utils/ulid.d.mts +0 -8
package/dist/index.mjs
CHANGED
|
@@ -1,22 +1,21 @@
|
|
|
1
1
|
import "node:path";
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
|
+
import { AdapterLifecycleError, collectCloses, collectInits, runAdapterCloses, runAdapterInits } from "./adapter/init.mjs";
|
|
5
|
+
import { createDispatchJob } from "./capture/dispatch.mjs";
|
|
6
|
+
import { InMemoryCaptureQueue } from "./capture/queue.mjs";
|
|
4
7
|
import { validateConfig, validateUiConfig } from "./config.mjs";
|
|
5
8
|
import { createShelfLogger } from "./logger.mjs";
|
|
6
9
|
import { rateLimit } from "./middleware/rate-limit.mjs";
|
|
7
10
|
import { csrf } from "./middleware/csrf.mjs";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import { ProjectModel } from "./models/project.mjs";
|
|
12
|
-
import { BUILD_STATUSES, PERSISTENT_LABEL_KEY, PROJECT_ROLES, RESERVED_LABEL_KEYS, SEEDED_LABEL_KEYS, SITE_ROLES, SNAPSHOT_STATUSES, TERMINAL_BUILD_STATUSES } from "./types.mjs";
|
|
13
|
-
import { Retention } from "./retention/purge.mjs";
|
|
14
|
-
import { getStore, runWithStore } from "./store.mjs";
|
|
11
|
+
import { requestLogging } from "./middleware/request-log.mjs";
|
|
12
|
+
import { storeScope } from "./middleware/store-scope.mjs";
|
|
13
|
+
import { authGate, resolveRequestUser } from "./middleware/auth-gate.mjs";
|
|
15
14
|
import { registerAdmin } from "./routers/admin.mjs";
|
|
16
15
|
import { registerAssets } from "./routers/assets.mjs";
|
|
17
16
|
import { registerAuth } from "./routers/auth.mjs";
|
|
18
|
-
import { baselinePath, diffPath, screenshotPath, storybookDir, storybookZipPath } from "./utils/paths.mjs";
|
|
19
17
|
import { registerBuilds } from "./routers/builds.mjs";
|
|
18
|
+
import { registerHealth } from "./routers/health.mjs";
|
|
20
19
|
import { registerLabels } from "./routers/labels.mjs";
|
|
21
20
|
import { registerMedia } from "./routers/media.mjs";
|
|
22
21
|
import { registerMembers } from "./routers/members.mjs";
|
|
@@ -26,239 +25,160 @@ import { registerStorybook } from "./routers/storybook.mjs";
|
|
|
26
25
|
import { registerTokens } from "./routers/tokens.mjs";
|
|
27
26
|
import { registerUiPages } from "./routers/ui.mjs";
|
|
28
27
|
import { registerWebhooks } from "./routers/webhooks.mjs";
|
|
29
|
-
import { diffImages } from "./diff/engine.mjs";
|
|
30
|
-
import { persistCapture } from "./capture/pipeline.mjs";
|
|
31
|
-
import { StorybookAdapter } from "./capture/storybook.mjs";
|
|
32
|
-
import { DEFAULT_VIEWPORTS } from "./capture/adapter.mjs";
|
|
33
|
-
import { executeCaptureJob } from "./capture/orchestrator.mjs";
|
|
34
|
-
import { InMemoryCaptureQueue } from "./capture/queue.mjs";
|
|
35
|
-
import { buildCommentMarkdown, commentMarker, describeStatus } from "./adapter/git-host/helpers.mjs";
|
|
36
|
-
import { postStatusesForBuild } from "./capture/status-fanout.mjs";
|
|
37
|
-
import { createUrlBuilder } from "./urls.mjs";
|
|
38
28
|
import { swaggerUI } from "@hono/swagger-ui";
|
|
39
29
|
import { OpenAPIHono } from "@hono/zod-openapi";
|
|
40
30
|
import { requestId } from "hono/request-id";
|
|
41
31
|
//#region src/index.tsx
|
|
32
|
+
/**
|
|
33
|
+
* StoryShelf core: compose database, storage, capture, auth, and git-host
|
|
34
|
+
* adapters into a complete self-hosted visual-testing Hono server.
|
|
35
|
+
*/
|
|
36
|
+
function addOptionalSnapshots(snap, options) {
|
|
37
|
+
if (options.captureRunner) snap["captureRunner"] = options.captureRunner.metadata;
|
|
38
|
+
if (options.captureQueue) snap["captureQueue"] = options.captureQueue.metadata;
|
|
39
|
+
if (options.auth) snap["auth"] = options.auth.metadata;
|
|
40
|
+
}
|
|
42
41
|
function buildAdapterSnapshot(options) {
|
|
43
|
-
const snap = {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (options.auth?.metadata) snap["auth"] = options.auth.metadata;
|
|
42
|
+
const snap = {
|
|
43
|
+
database: options.database.metadata,
|
|
44
|
+
storage: options.storage.metadata
|
|
45
|
+
};
|
|
46
|
+
addOptionalSnapshots(snap, options);
|
|
49
47
|
for (const p of options.gitHosts ?? []) snap[`git:${p.metadata.kind}`] = p.metadata;
|
|
50
48
|
return snap;
|
|
51
49
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
async function isAlreadyMerged(opts) {
|
|
56
|
-
if (opts.providers.length === 0) return false;
|
|
57
|
-
const { StatusConfigModel } = await import("./models/status-config.mjs");
|
|
58
|
-
const model = new StatusConfigModel(opts.db, opts.secret);
|
|
59
|
-
const rows = await model.list(opts.projectId);
|
|
60
|
-
for (const row of rows) {
|
|
61
|
-
const provider = opts.providers.find((p) => p.metadata.kind === row.provider);
|
|
62
|
-
if (!provider) continue;
|
|
63
|
-
let parsed;
|
|
64
|
-
try {
|
|
65
|
-
parsed = JSON.parse(row.config);
|
|
66
|
-
provider.metadata.schema.parse(parsed);
|
|
67
|
-
} catch {
|
|
68
|
-
continue;
|
|
69
|
-
}
|
|
70
|
-
let token;
|
|
71
|
-
try {
|
|
72
|
-
token = model.decryptToken(row);
|
|
73
|
-
} catch {
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
const adapter = provider.create({
|
|
77
|
-
config: parsed,
|
|
78
|
-
token,
|
|
79
|
-
logger: opts.logger
|
|
80
|
-
});
|
|
81
|
-
if (!adapter.isMerged) continue;
|
|
82
|
-
try {
|
|
83
|
-
if (await adapter.isMerged({
|
|
84
|
-
sha: opts.sha,
|
|
85
|
-
branch: opts.branch
|
|
86
|
-
})) return true;
|
|
87
|
-
} catch {}
|
|
88
|
-
}
|
|
89
|
-
return false;
|
|
90
|
-
}
|
|
91
|
-
async function resolveUser(c, options) {
|
|
92
|
-
if (!options.auth) return null;
|
|
93
|
-
if (c.req.header("authorization")) return null;
|
|
94
|
-
return await options.auth.check(c.req.raw);
|
|
95
|
-
}
|
|
96
|
-
function createShelfRouter(options) {
|
|
97
|
-
const app = new OpenAPIHono();
|
|
50
|
+
/** Validate config/ui and derive runtime singletons from options. */
|
|
51
|
+
function resolveRuntime(options) {
|
|
98
52
|
const rawConfig = options.config ? validateConfig(options.config) : {};
|
|
99
53
|
const ui = options.ui ? validateUiConfig(options.ui) : {};
|
|
100
54
|
const logger = options.logger ?? createShelfLogger();
|
|
101
55
|
const authEnabled = options.auth !== void 0;
|
|
102
56
|
const gitHosts = options.gitHosts ?? [];
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
57
|
+
return {
|
|
58
|
+
config: rawConfig.adapters ? rawConfig : {
|
|
59
|
+
...rawConfig,
|
|
60
|
+
adapters: buildAdapterSnapshot(options)
|
|
61
|
+
},
|
|
62
|
+
ui,
|
|
63
|
+
logger,
|
|
64
|
+
authEnabled,
|
|
65
|
+
gitHosts
|
|
106
66
|
};
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const pendingUrl = `/projects/${project.slug}/builds/${job.buildId}`;
|
|
141
|
-
await postStatusesForBuild({
|
|
142
|
-
db: options.database,
|
|
143
|
-
project,
|
|
144
|
-
sha: build.gitSha,
|
|
145
|
-
status: "pending",
|
|
146
|
-
url: pendingUrl,
|
|
147
|
-
providers: gitHosts,
|
|
148
|
-
secret: config.secret,
|
|
149
|
-
logger
|
|
150
|
-
}).catch((error) => {
|
|
151
|
-
logger.error({ err: error }, "failed to post pending status");
|
|
152
|
-
});
|
|
153
|
-
if (!build.isDefault) {
|
|
154
|
-
if (await isAlreadyMerged({
|
|
155
|
-
providers: gitHosts,
|
|
156
|
-
sha: build.gitSha,
|
|
157
|
-
branch: build.gitBranch,
|
|
158
|
-
secret: config.secret,
|
|
159
|
-
db: options.database,
|
|
160
|
-
projectId: project.id,
|
|
161
|
-
logger
|
|
162
|
-
}).catch(() => false)) {
|
|
163
|
-
logger.info({
|
|
164
|
-
buildId: build.id,
|
|
165
|
-
sha: build.gitSha,
|
|
166
|
-
branch: build.gitBranch
|
|
167
|
-
}, "skipping capture — already merged");
|
|
168
|
-
await builds.setStatus(build.id, "approved").catch(() => {});
|
|
169
|
-
await postStatusesForBuild({
|
|
170
|
-
db: options.database,
|
|
171
|
-
project,
|
|
172
|
-
sha: build.gitSha,
|
|
173
|
-
status: "success",
|
|
174
|
-
url: pendingUrl,
|
|
175
|
-
providers: gitHosts,
|
|
176
|
-
secret: config.secret,
|
|
177
|
-
logger
|
|
178
|
-
}).catch(() => {});
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
if (await hasApprovedBuildForSha(options.database, project.id, build.gitSha, build.id)) {
|
|
183
|
-
logger.info({
|
|
184
|
-
buildId: build.id,
|
|
185
|
-
sha: build.gitSha
|
|
186
|
-
}, "skipping capture — duplicate sha already approved");
|
|
187
|
-
await builds.setStatus(build.id, "approved").catch(() => {});
|
|
188
|
-
await postStatusesForBuild({
|
|
189
|
-
db: options.database,
|
|
190
|
-
project,
|
|
191
|
-
sha: build.gitSha,
|
|
192
|
-
status: "success",
|
|
193
|
-
url: pendingUrl,
|
|
194
|
-
providers: gitHosts,
|
|
195
|
-
secret: config.secret,
|
|
196
|
-
logger
|
|
197
|
-
}).catch(() => {});
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
try {
|
|
201
|
-
await executeCaptureJob({
|
|
202
|
-
buildId: job.buildId,
|
|
203
|
-
reqId: job.reqId
|
|
204
|
-
}, jobOptions);
|
|
205
|
-
const updated = await builds.get(job.buildId);
|
|
206
|
-
if (updated) {
|
|
207
|
-
const terminal = updated.status === "approved" ? "success" : updated.status === "rejected" || updated.status === "failed" ? "failure" : null;
|
|
208
|
-
if (terminal) await postStatusesForBuild({
|
|
209
|
-
db: options.database,
|
|
210
|
-
project,
|
|
211
|
-
sha: updated.gitSha,
|
|
212
|
-
status: terminal,
|
|
213
|
-
url: pendingUrl,
|
|
214
|
-
providers: gitHosts,
|
|
215
|
-
secret: config.secret,
|
|
216
|
-
logger
|
|
217
|
-
}).catch((error) => {
|
|
218
|
-
logger.error({ err: error }, "failed to post terminal status");
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
} catch (error) {
|
|
222
|
-
await postStatusesForBuild({
|
|
223
|
-
db: options.database,
|
|
224
|
-
project,
|
|
225
|
-
sha: build.gitSha,
|
|
226
|
-
status: "failure",
|
|
227
|
-
url: pendingUrl,
|
|
228
|
-
providers: gitHosts,
|
|
229
|
-
secret: config.secret,
|
|
230
|
-
logger
|
|
231
|
-
}).catch(() => {});
|
|
232
|
-
throw error;
|
|
233
|
-
}
|
|
234
|
-
}
|
|
67
|
+
}
|
|
68
|
+
/** Assemble the capture queue and its enqueue hook when a runner is configured. */
|
|
69
|
+
function setupCaptureQueue(options, config, gitHosts, logger) {
|
|
70
|
+
if (!options.captureRunner) return {
|
|
71
|
+
queue: null,
|
|
72
|
+
enqueueCapture: void 0
|
|
73
|
+
};
|
|
74
|
+
if (!config.scratchDir) throw new Error("captureRunner is enabled but ShelfConfig.scratchDir is not set");
|
|
75
|
+
const jobOptions = {
|
|
76
|
+
db: options.database,
|
|
77
|
+
storage: options.storage,
|
|
78
|
+
runner: options.captureRunner,
|
|
79
|
+
scratchDir: config.scratchDir,
|
|
80
|
+
viewports: config.viewports,
|
|
81
|
+
logger,
|
|
82
|
+
secret: config.secret
|
|
83
|
+
};
|
|
84
|
+
const runJob = createDispatchJob({
|
|
85
|
+
db: options.database,
|
|
86
|
+
jobOptions,
|
|
87
|
+
gitHosts,
|
|
88
|
+
secret: config.secret,
|
|
89
|
+
logger
|
|
90
|
+
});
|
|
91
|
+
const captureQueue = options.captureQueue ?? new InMemoryCaptureQueue({
|
|
92
|
+
concurrency: config.captureConcurrency ?? 2,
|
|
93
|
+
logger,
|
|
94
|
+
runJob
|
|
95
|
+
});
|
|
96
|
+
const enqueueCapture = async (buildId, reqId) => {
|
|
97
|
+
await captureQueue.enqueue({
|
|
98
|
+
buildId,
|
|
99
|
+
reqId
|
|
235
100
|
});
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
101
|
+
};
|
|
102
|
+
return {
|
|
103
|
+
queue: captureQueue,
|
|
104
|
+
enqueueCapture
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/** Failures safe to expose on the init gate's 503 (no secrets). */
|
|
108
|
+
function publicFailures(result) {
|
|
109
|
+
return result.failures.map((failure) => ({
|
|
110
|
+
category: failure.category,
|
|
111
|
+
kind: failure.kind,
|
|
112
|
+
error: failure.error
|
|
113
|
+
}));
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Block requests until adapter init settles; answer 503 with the per-adapter
|
|
117
|
+
* failures when init failed. Health probes are exempt (they report init
|
|
118
|
+
* state themselves instead of being masked by the gate).
|
|
119
|
+
*/
|
|
120
|
+
function initGate(getReady) {
|
|
121
|
+
return async (c, next) => {
|
|
122
|
+
if (c.req.path === "/api/v1/health") {
|
|
123
|
+
await next();
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const result = await getReady();
|
|
127
|
+
if (!result.ok) return c.json({
|
|
128
|
+
error: "Adapters failed to initialize",
|
|
129
|
+
failures: publicFailures(result)
|
|
130
|
+
}, 503);
|
|
253
131
|
await next();
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function trackSettlement(cell, promise) {
|
|
135
|
+
promise.then((result) => {
|
|
136
|
+
cell.settled = result;
|
|
137
|
+
}, () => {
|
|
138
|
+
cell.settled = {
|
|
139
|
+
ok: false,
|
|
140
|
+
failures: []
|
|
141
|
+
};
|
|
261
142
|
});
|
|
143
|
+
}
|
|
144
|
+
/** Kick the eager background init run (resolves; never rejects). */
|
|
145
|
+
function kickInit(options, ctx, logger, cell) {
|
|
146
|
+
cell.ready = runAdapterInits(collectInits(options), ctx, logger);
|
|
147
|
+
trackSettlement(cell, cell.ready);
|
|
148
|
+
}
|
|
149
|
+
/** Kick eager init and attach the `app.lifecycle` namespace (single run). */
|
|
150
|
+
function attachLifecycle(app, options, runtime, cell) {
|
|
151
|
+
const initCtx = {
|
|
152
|
+
config: runtime.config,
|
|
153
|
+
logger: runtime.logger
|
|
154
|
+
};
|
|
155
|
+
kickInit(options, initCtx, runtime.logger, cell);
|
|
156
|
+
Object.assign(app, { lifecycle: createLifecycle(options, initCtx, runtime.logger, cell) });
|
|
157
|
+
}
|
|
158
|
+
/** Build the `app.lifecycle` namespace closing over one settlement cell. */
|
|
159
|
+
function createLifecycle(options, ctx, logger, cell) {
|
|
160
|
+
return {
|
|
161
|
+
get ready() {
|
|
162
|
+
return cell.ready;
|
|
163
|
+
},
|
|
164
|
+
init: async () => {
|
|
165
|
+
const result = await runAdapterInits(collectInits(options), ctx, logger);
|
|
166
|
+
cell.ready = Promise.resolve(result);
|
|
167
|
+
cell.settled = result;
|
|
168
|
+
if (!result.ok) throw new AdapterLifecycleError("init", result.failures);
|
|
169
|
+
},
|
|
170
|
+
close: async () => {
|
|
171
|
+
const result = await runAdapterCloses(collectCloses(options), ctx, logger);
|
|
172
|
+
if (!result.ok) throw new AdapterLifecycleError("close", result.failures);
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
/** Attach global middleware: ids, logging, init gate, limits, store scope, auth gate. */
|
|
177
|
+
function wireMiddleware(app, wiring) {
|
|
178
|
+
const { options, config, ui, logger, authEnabled, enqueueCapture, queue, gitHosts, getReady } = wiring;
|
|
179
|
+
app.use("*", requestId());
|
|
180
|
+
app.use("*", requestLogging(logger));
|
|
181
|
+
app.use("*", initGate(getReady));
|
|
262
182
|
app.use("/api/v1/*", rateLimit({
|
|
263
183
|
windowMs: 6e4,
|
|
264
184
|
max: 100
|
|
@@ -272,33 +192,22 @@ function createShelfRouter(options) {
|
|
|
272
192
|
max: 20
|
|
273
193
|
}));
|
|
274
194
|
app.use("/projects/:slug/settings/*", csrf());
|
|
275
|
-
app.use("*",
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
});
|
|
292
|
-
app.use("*", async (c, next) => {
|
|
293
|
-
const { user, authEnabled: isAuthEnabled } = getStore();
|
|
294
|
-
const { path } = c.req;
|
|
295
|
-
if (!isAuthEnabled || user || path.startsWith("/api/") || path.startsWith("/auth/") || path.startsWith("/assets/") || path.startsWith("/projects/") && path.includes("/storybook")) return next();
|
|
296
|
-
if (c.req.header("HX-Request") === "true") {
|
|
297
|
-
c.header("HX-Redirect", "/auth/login");
|
|
298
|
-
return c.body(null, 204);
|
|
299
|
-
}
|
|
300
|
-
return c.redirect("/auth/login", 302);
|
|
301
|
-
});
|
|
195
|
+
app.use("*", storeScope({
|
|
196
|
+
db: options.database,
|
|
197
|
+
storage: options.storage,
|
|
198
|
+
config,
|
|
199
|
+
ui,
|
|
200
|
+
logger,
|
|
201
|
+
authEnabled,
|
|
202
|
+
enqueueCapture,
|
|
203
|
+
captureQueue: queue,
|
|
204
|
+
gitHosts,
|
|
205
|
+
resolveUser: async (c) => await resolveRequestUser(c, options.auth)
|
|
206
|
+
}));
|
|
207
|
+
app.use("*", authGate());
|
|
208
|
+
}
|
|
209
|
+
/** Register every JSON API router. */
|
|
210
|
+
function registerApiRoutes(app, health) {
|
|
302
211
|
registerProjects(app);
|
|
303
212
|
registerBuilds(app);
|
|
304
213
|
registerLabels(app);
|
|
@@ -307,11 +216,18 @@ function createShelfRouter(options) {
|
|
|
307
216
|
registerTokens(app);
|
|
308
217
|
registerWebhooks(app);
|
|
309
218
|
registerStatusConfigs(app);
|
|
219
|
+
registerHealth(app, health);
|
|
310
220
|
registerAdmin(app);
|
|
221
|
+
}
|
|
222
|
+
/** Register HTML pages, assets, and the optional auth flow. */
|
|
223
|
+
function registerPageRoutes(app, options) {
|
|
311
224
|
if (options.auth) registerAuth(app, options.auth);
|
|
312
225
|
registerAssets(app);
|
|
313
226
|
registerStorybook(app);
|
|
314
227
|
registerUiPages(app);
|
|
228
|
+
}
|
|
229
|
+
/** Register the OpenAPI document and interactive docs page. */
|
|
230
|
+
function registerDocs(app) {
|
|
315
231
|
app.doc("/api/v1/openapi.json", {
|
|
316
232
|
openapi: "3.0.0",
|
|
317
233
|
info: {
|
|
@@ -321,9 +237,52 @@ function createShelfRouter(options) {
|
|
|
321
237
|
}
|
|
322
238
|
});
|
|
323
239
|
app.get("/api/v1/docs", swaggerUI({ url: "/api/v1/openapi.json" }));
|
|
240
|
+
}
|
|
241
|
+
/** Register every API router, page set, and optional auth flow. */
|
|
242
|
+
function registerAllRoutes(app, options, health) {
|
|
243
|
+
registerApiRoutes(app, health);
|
|
244
|
+
registerPageRoutes(app, options);
|
|
245
|
+
}
|
|
246
|
+
/** Package version injected at build via __PKG_VERSION__. */
|
|
247
|
+
function packageVersion() {
|
|
248
|
+
return globalThis.__PKG_VERSION__ ?? "0.0.0";
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Create the StoryShelf Hono router with all API routes and HTML pages.
|
|
252
|
+
*
|
|
253
|
+
* Adapter `init` hooks kick off eagerly in the background (the constructor
|
|
254
|
+
* stays synchronous). Await `app.lifecycle.init()` before serving for
|
|
255
|
+
* fail-fast startup, or let the first request gate on settlement.
|
|
256
|
+
*/
|
|
257
|
+
function createShelfRouter(options) {
|
|
258
|
+
const app = new OpenAPIHono();
|
|
259
|
+
const runtime = resolveRuntime(options);
|
|
260
|
+
const cell = {
|
|
261
|
+
ready: Promise.resolve({
|
|
262
|
+
ok: true,
|
|
263
|
+
failures: []
|
|
264
|
+
}),
|
|
265
|
+
settled: null
|
|
266
|
+
};
|
|
267
|
+
attachLifecycle(app, options, runtime, cell);
|
|
268
|
+
const { queue, enqueueCapture } = setupCaptureQueue(options, runtime.config, runtime.gitHosts, runtime.logger);
|
|
269
|
+
wireMiddleware(app, {
|
|
270
|
+
...runtime,
|
|
271
|
+
queue,
|
|
272
|
+
enqueueCapture,
|
|
273
|
+
options,
|
|
274
|
+
getReady: async () => await cell.ready
|
|
275
|
+
});
|
|
276
|
+
registerAllRoutes(app, options, {
|
|
277
|
+
sources: options,
|
|
278
|
+
getSettled: () => cell.settled,
|
|
279
|
+
bootTimeMs: Date.now(),
|
|
280
|
+
version: packageVersion()
|
|
281
|
+
});
|
|
282
|
+
registerDocs(app);
|
|
324
283
|
return app;
|
|
325
284
|
}
|
|
326
285
|
//#endregion
|
|
327
|
-
export {
|
|
286
|
+
export { createShelfRouter };
|
|
328
287
|
|
|
329
288
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.tsx"],"sourcesContent":["import { swaggerUI } from \"@hono/swagger-ui\";\nimport { OpenAPIHono } from \"@hono/zod-openapi\";\nimport { requestId } from \"hono/request-id\";\nimport type { AuthUser } from \"./adapters/auth.ts\";\nimport type { CaptureQueue } from \"./adapters/capture-queue.ts\";\nimport type { ShelfOptions } from \"./config.ts\";\nimport type { AdapterMetadata, GitAdapterMetadata } from \"./adapters/metadata.ts\";\nimport { validateConfig, validateUiConfig } from \"./config.ts\";\nimport { createShelfLogger } from \"./logger.ts\";\nimport { csrf, rateLimit } from \"./middleware/index.ts\";\nimport { BuildModel } from \"./models/build.ts\";\nimport { ProjectModel } from \"./models/project.ts\";\nimport { registerAdmin } from \"./routers/admin.ts\";\nimport { registerAssets } from \"./routers/assets.ts\";\nimport { registerAuth } from \"./routers/auth.ts\";\nimport { registerBuilds } from \"./routers/builds.ts\";\nimport { registerLabels } from \"./routers/labels.ts\";\nimport { registerMedia } from \"./routers/media.ts\";\nimport { registerMembers } from \"./routers/members.ts\";\nimport { registerProjects } from \"./routers/projects.ts\";\nimport { registerStatusConfigs } from \"./routers/status-configs.ts\";\nimport { registerStorybook } from \"./routers/storybook.ts\";\nimport { registerTokens } from \"./routers/tokens.ts\";\nimport { registerUiPages } from \"./routers/ui.ts\";\nimport { registerWebhooks } from \"./routers/webhooks.ts\";\nimport { getStore, runWithStore } from \"./store.ts\";\nimport { executeCaptureJob, type CaptureJobOptions } from \"./capture/orchestrator.ts\";\nimport { InMemoryCaptureQueue } from \"./capture/queue.ts\";\nimport { postStatusesForBuild } from \"./capture/status-fanout.ts\";\n\nexport interface ShelfContext {\n requestId?: string;\n db: import(\"./adapters/database.ts\").DatabaseAdapter;\n storage: import(\"./adapters/storage.ts\").StorageAdapter;\n config: import(\"./config.ts\").ShelfConfig;\n ui: import(\"./config.ts\").UIConfig;\n logger: ReturnType<typeof createShelfLogger>;\n user: import(\"./adapters/auth.ts\").AuthUser | null;\n authEnabled: boolean;\n enqueueCapture?: (buildId: string, reqId?: string) => Promise<void>;\n captureQueue: import(\"./adapters/capture-queue.ts\").CaptureQueue | null;\n gitHosts: import(\"./adapters/git-host/index.ts\").GitHostProvider[];\n}\n\nexport type ShelfApp = OpenAPIHono<{ Variables: ShelfContext }>;\n\nfunction buildAdapterSnapshot(options: ShelfOptions): Record<string, AdapterMetadata | GitAdapterMetadata> {\n const snap: Record<string, AdapterMetadata | GitAdapterMetadata> = {};\n if (options.database.metadata) {snap[\"database\"] = options.database.metadata;}\n if (options.storage.metadata) {snap[\"storage\"] = options.storage.metadata;}\n if (options.captureRunner?.metadata) {snap[\"captureRunner\"] = options.captureRunner.metadata;}\n if (options.captureQueue?.metadata) {snap[\"captureQueue\"] = options.captureQueue.metadata;}\n if (options.auth?.metadata) {snap[\"auth\"] = options.auth.metadata;}\n for (const p of options.gitHosts ?? []) {\n snap[`git:${p.metadata.kind}`] = p.metadata;\n }\n return snap;\n}\n\nasync function hasApprovedBuildForSha(\n db: import(\"./adapters/database.ts\").DatabaseAdapter,\n projectId: string,\n sha: string,\n excludeBuildId: string,\n): Promise<boolean> {\n const builds = await new BuildModel(db).list(projectId);\n return builds.some((b) => b.gitSha === sha && b.id !== excludeBuildId && b.status === \"approved\");\n}\n\n// eslint-disable-next-line max-statements, complexity\nasync function isAlreadyMerged(opts: {\n providers: import(\"./adapters/git-host/index.ts\").GitHostProvider[];\n sha: string;\n branch: string;\n secret: string | undefined;\n db: import(\"./adapters/database.ts\").DatabaseAdapter;\n projectId: string;\n logger?: import(\"./logger.ts\").Logger;\n}): Promise<boolean> {\n if (opts.providers.length === 0) {return false;}\n const { StatusConfigModel } = await import(\"./models/status-config.ts\");\n const model = new StatusConfigModel(opts.db, opts.secret);\n const rows = await model.list(opts.projectId);\n for (const row of rows) {\n const provider = opts.providers.find((p) => p.metadata.kind === row.provider);\n if (!provider) {continue;}\n let parsed: unknown;\n try {\n parsed = JSON.parse(row.config);\n provider.metadata.schema.parse(parsed);\n } catch {\n continue;\n }\n let token: string;\n try {\n token = model.decryptToken(row);\n } catch {\n continue;\n }\n const adapter = provider.create({ config: parsed, token, logger: opts.logger });\n if (!adapter.isMerged) {continue;}\n try {\n // eslint-disable-next-line no-await-in-loop -- short-circuit on first merged status\n const merged = await adapter.isMerged({ sha: opts.sha, branch: opts.branch });\n if (merged) {return true;}\n } catch {\n // Ignore provider errors — do not skip on failure\n }\n }\n return false;\n}\n\nasync function resolveUser(\n c: { req: { raw: Request; header: (name: string) => string | undefined } },\n options: ShelfOptions,\n): Promise<AuthUser | null> {\n if (!options.auth) {\n return null;\n }\n if (c.req.header(\"authorization\")) {\n return null;\n }\n return await options.auth.check(c.req.raw);\n}\n\n\nexport function createShelfRouter(options: ShelfOptions): ShelfApp {\n const app = new OpenAPIHono<{ Variables: ShelfContext }>();\n // eslint-disable-next-line typescript/no-unnecessary-type-assertion -- ShelfConfig lacks index signature\n const rawConfig = options.config ? validateConfig(options.config as unknown as Record<string, unknown>) : {};\n // eslint-disable-next-line typescript/no-unnecessary-type-assertion -- UIConfig lacks index signature\n const ui = options.ui ? validateUiConfig(options.ui as unknown as Record<string, unknown>) : {};\n const logger = options.logger ?? createShelfLogger();\n const authEnabled = options.auth !== undefined;\n\n const gitHosts = options.gitHosts ?? [];\n\n // Adapter introspection — auto-populate config.adapters if not supplied\n const config: import(\"./config.ts\").ShelfConfig = rawConfig.adapters\n ? rawConfig\n : {\n ...rawConfig,\n adapters: buildAdapterSnapshot(options),\n };\n\n let queue: CaptureQueue | null = null;\n let enqueueCapture: ((buildId: string, reqId?: string) => Promise<void>) | undefined;\n if (options.captureRunner) {\n if (!config.scratchDir) {\n throw new Error(\"captureRunner is enabled but ShelfConfig.scratchDir is not set\");\n }\n const jobOptions: CaptureJobOptions = {\n db: options.database,\n storage: options.storage,\n runner: options.captureRunner,\n scratchDir: config.scratchDir,\n viewports: config.viewports,\n logger,\n };\n const captureQueue =\n options.captureQueue ??\n new InMemoryCaptureQueue({\n concurrency: config.captureConcurrency ?? 2,\n logger,\n runJob: async (job): Promise<void> => {\n const builds = new BuildModel(options.database);\n const build = await builds.get(job.buildId);\n if (!build) {\n await executeCaptureJob({ buildId: job.buildId, reqId: job.reqId }, jobOptions);\n return;\n }\n const project = await new ProjectModel(options.database).get(build.projectId);\n if (!project) {\n await executeCaptureJob({ buildId: job.buildId, reqId: job.reqId }, jobOptions);\n return;\n }\n const pendingUrl = `/projects/${project.slug}/builds/${job.buildId}`;\n await postStatusesForBuild({\n db: options.database,\n project,\n sha: build.gitSha,\n status: \"pending\",\n url: pendingUrl,\n providers: gitHosts,\n secret: config.secret,\n logger,\n }).catch((error: unknown) => {\n logger.error({ err: error }, \"failed to post pending status\");\n });\n // Skip capture if already merged (PR closed) or locally deduped\n if (!build.isDefault) {\n const merged = await isAlreadyMerged({\n providers: gitHosts,\n sha: build.gitSha,\n branch: build.gitBranch,\n secret: config.secret,\n db: options.database,\n projectId: project.id,\n logger,\n }).catch(() => false);\n if (merged) {\n logger.info({ buildId: build.id, sha: build.gitSha, branch: build.gitBranch }, \"skipping capture — already merged\");\n await builds.setStatus(build.id, \"approved\").catch(() => {}); // Intentionally empty — fire-and-forget\n await postStatusesForBuild({\n db: options.database,\n project,\n sha: build.gitSha,\n status: \"success\",\n url: pendingUrl,\n providers: gitHosts,\n secret: config.secret,\n logger,\n }).catch(() => {}); // Intentionally empty — fire-and-forget\n return;\n }\n }\n // Local dedupe: if another approved build for same sha exists, skip render\n const dup = await hasApprovedBuildForSha(options.database, project.id, build.gitSha, build.id);\n if (dup) {\n logger.info({ buildId: build.id, sha: build.gitSha }, \"skipping capture — duplicate sha already approved\");\n await builds.setStatus(build.id, \"approved\").catch(() => {}); // Intentionally empty — fire-and-forget\n await postStatusesForBuild({\n db: options.database,\n project,\n sha: build.gitSha,\n status: \"success\",\n url: pendingUrl,\n providers: gitHosts,\n secret: config.secret,\n logger,\n }).catch(() => {}); // Intentionally empty — fire-and-forget\n return;\n }\n try {\n await executeCaptureJob({ buildId: job.buildId, reqId: job.reqId }, jobOptions);\n const updated = await builds.get(job.buildId);\n if (updated) {\n const terminal =\n updated.status === \"approved\"\n ? \"success\"\n : (updated.status === \"rejected\" || updated.status === \"failed\"\n ? \"failure\"\n : null);\n if (terminal) {\n await postStatusesForBuild({\n db: options.database,\n project,\n sha: updated.gitSha,\n status: terminal,\n url: pendingUrl,\n providers: gitHosts,\n secret: config.secret,\n logger,\n }).catch((error: unknown) => {\n logger.error({ err: error }, \"failed to post terminal status\");\n });\n }\n }\n } catch (error: unknown) {\n await postStatusesForBuild({\n db: options.database,\n project,\n sha: build.gitSha,\n status: \"failure\",\n url: pendingUrl,\n providers: gitHosts,\n secret: config.secret,\n logger,\n }).catch(() => {\n // Ignore: status post failure already logged\n });\n throw error;\n }\n },\n });\n queue = captureQueue;\n enqueueCapture = async (buildId: string, reqId?: string): Promise<void> => {\n await captureQueue.enqueue({ buildId, reqId });\n };\n }\n\n app.use(\"*\", requestId());\n\n // Structured request logging. Uses a Hono-native middleware rather than\n // Pino-http, which expects a Node server response (`res.on`) incompatible\n // With Hono's Web `Request`/`Response` model.\n app.use(\"*\", async (c, next) => {\n const started = performance.now();\n const id = c.get(\"requestId\");\n logger.info({ reqId: id, method: c.req.method, url: c.req.path }, \"request start\");\n await next();\n logger.info(\n {\n reqId: id,\n method: c.req.method,\n url: c.req.path,\n status: c.res.status,\n durationMs: Math.round(performance.now() - started),\n },\n \"request end\",\n );\n });\n\n app.use(\"/api/v1/*\", rateLimit({ windowMs: 60_000, max: 100 }));\n app.use(\"/api/v1/tokens/*\", rateLimit({ windowMs: 60_000, max: 10 }));\n app.use(\"/api/v1/webhooks/*\", rateLimit({ windowMs: 60_000, max: 20 }));\n app.use(\"/projects/:slug/settings/*\", csrf());\n\n app.use(\"*\", async (c, next) => {\n const user = await resolveUser(c, options);\n return runWithStore(\n {\n db: options.database,\n storage: options.storage,\n config,\n ui,\n logger,\n user,\n authEnabled,\n enqueueCapture,\n captureQueue: queue,\n gitHosts,\n },\n async () => {\n await next();\n },\n );\n });\n\n // Gate the server-rendered UI behind auth (ADR 0008): unauthenticated HTML\n // Requests are redirected to the login page. API and auth routes are handled\n // By their own routers (401/403 vs the login flow).\n // eslint-disable-next-line require-await -- async is required to match Hono's middleware signature\n app.use(\"*\", async (c, next) => {\n const { user, authEnabled: isAuthEnabled } = getStore();\n const {path} = c.req;\n if (\n !isAuthEnabled ||\n user ||\n path.startsWith(\"/api/\") ||\n path.startsWith(\"/auth/\") ||\n path.startsWith(\"/assets/\") ||\n // Published Storybook routes enforce their own auth inside the handler\n // (public builds are viewable without a session, ADR 0011).\n path.startsWith(\"/projects/\") && path.includes(\"/storybook\")\n ) {\n return next();\n }\n if (c.req.header(\"HX-Request\") === \"true\") {\n c.header(\"HX-Redirect\", \"/auth/login\");\n return c.body(null, 204);\n }\n return c.redirect(\"/auth/login\", 302);\n });\n\n registerProjects(app);\n registerBuilds(app);\n registerLabels(app);\n registerMedia(app);\n registerMembers(app);\n registerTokens(app);\n registerWebhooks(app);\n registerStatusConfigs(app);\n registerAdmin(app);\n if (options.auth) {\n registerAuth(app, options.auth);\n }\n registerAssets(app);\n registerStorybook(app);\n registerUiPages(app);\n\n app.doc(\"/api/v1/openapi.json\", {\n openapi: \"3.0.0\",\n info: {\n title: \"StoryShelf API\",\n description:\n \"REST API for the StoryShelf visual testing platform. JSON endpoints live under /api/v1; HTML pages are served at /.\",\n version: \"1.0.0\",\n },\n });\n app.get(\"/api/v1/docs\", swaggerUI({ url: \"/api/v1/openapi.json\" }));\n\n return app;\n}\n\nexport type { ShelfOptions, ShelfConfig, UIConfig, BrandTheme } from \"./config.ts\";\nexport type { DatabaseAdapter, ListOptions } from \"./adapters/database.ts\";\nexport type { StorageAdapter } from \"./adapters/storage.ts\";\nexport type {\n CaptureRunner,\n JobStatus,\n RenderedSnapshot,\n RenderResult,\n RenderFailure,\n} from \"./adapters/capture-runner.ts\";\nexport type { CaptureQueue, CaptureJob, QueueEntry } from \"./adapters/capture-queue.ts\";\nexport type { AuthAdapter, AuthUser, AuthCallback } from \"./adapters/auth.ts\";\nexport type { GitHostProvider, GitHostAdapter, CheckStatus } from \"./adapters/git-host/index.ts\";\nexport type { AdapterMetadata, GitAdapterMetadata } from \"./adapters/metadata.ts\";\nexport { describeStatus, buildCommentMarkdown, commentMarker } from \"./adapters/git-host/helpers.ts\";\nexport type { DiffOptions, DiffResult } from \"./diff/options.ts\";\nexport type { Viewport, StoryEntry, StorySourceAdapter } from \"./capture/adapter.ts\";\nexport { createShelfLogger, type LoggerOptions, type Logger, type PinoTransport } from \"./logger.ts\";\nexport { diffImages } from \"./diff/engine.ts\";\nexport type { RenderedContent } from \"./ui/document.tsx\";\nexport { StorybookAdapter } from \"./capture/storybook.ts\";\nexport { persistCapture, type CaptureContext } from \"./capture/pipeline.ts\";\nexport { executeCaptureJob, type CaptureJobOptions } from \"./capture/orchestrator.ts\";\nexport { DEFAULT_VIEWPORTS } from \"./capture/adapter.ts\";\nexport { InMemoryCaptureQueue, type InMemoryCaptureQueueOptions } from \"./capture/queue.ts\";\nexport { Retention } from \"./retention/purge.ts\";\nexport { createUrlBuilder, type UrlBuilder } from \"./urls.ts\";\nexport { ulid, slugify } from \"./utils/ulid.ts\";\nexport {\n baselinePath,\n diffPath,\n screenshotPath,\n storybookDir,\n storybookZipPath,\n} from \"./utils/paths.ts\";\nexport * from \"./schema.ts\";\nexport * from \"./types.ts\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8CA,SAAS,qBAAqB,SAA6E;CACzG,MAAM,OAA6D,CAAC;CACpE,IAAI,QAAQ,SAAS,UAAW,KAAK,cAAc,QAAQ,SAAS;CACpE,IAAI,QAAQ,QAAQ,UAAW,KAAK,aAAa,QAAQ,QAAQ;CACjE,IAAI,QAAQ,eAAe,UAAW,KAAK,mBAAmB,QAAQ,cAAc;CACpF,IAAI,QAAQ,cAAc,UAAW,KAAK,kBAAkB,QAAQ,aAAa;CACjF,IAAI,QAAQ,MAAM,UAAW,KAAK,UAAU,QAAQ,KAAK;CACzD,KAAK,MAAM,KAAK,QAAQ,YAAY,CAAC,GACnC,KAAK,OAAO,EAAE,SAAS,UAAU,EAAE;CAErC,OAAO;AACT;AAEA,eAAe,uBACb,IACA,WACA,KACA,gBACkB;CAElB,QAAO,MADc,IAAI,WAAW,EAAE,CAAC,CAAC,KAAK,SAAS,EAAA,CACxC,MAAM,MAAM,EAAE,WAAW,OAAO,EAAE,OAAO,kBAAkB,EAAE,WAAW,UAAU;AAClG;AAGA,eAAe,gBAAgB,MAQV;CACnB,IAAI,KAAK,UAAU,WAAW,GAAI,OAAO;CACzC,MAAM,EAAE,sBAAsB,MAAM,OAAO;CAC3C,MAAM,QAAQ,IAAI,kBAAkB,KAAK,IAAI,KAAK,MAAM;CACxD,MAAM,OAAO,MAAM,MAAM,KAAK,KAAK,SAAS;CAC5C,KAAK,MAAM,OAAO,MAAM;EACtB,MAAM,WAAW,KAAK,UAAU,MAAM,MAAM,EAAE,SAAS,SAAS,IAAI,QAAQ;EAC5E,IAAI,CAAC,UAAW;EAChB,IAAI;EACJ,IAAI;GACF,SAAS,KAAK,MAAM,IAAI,MAAM;GAC9B,SAAS,SAAS,OAAO,MAAM,MAAM;EACvC,QAAQ;GACN;EACF;EACA,IAAI;EACJ,IAAI;GACF,QAAQ,MAAM,aAAa,GAAG;EAChC,QAAQ;GACN;EACF;EACA,MAAM,UAAU,SAAS,OAAO;GAAE,QAAQ;GAAQ;GAAO,QAAQ,KAAK;EAAO,CAAC;EAC9E,IAAI,CAAC,QAAQ,UAAW;EACxB,IAAI;GAGF,IAAI,MADiB,QAAQ,SAAS;IAAE,KAAK,KAAK;IAAK,QAAQ,KAAK;GAAO,CAAC,GAC/D,OAAO;EACtB,QAAQ,CAER;CACF;CACA,OAAO;AACT;AAEA,eAAe,YACb,GACA,SAC0B;CAC1B,IAAI,CAAC,QAAQ,MACX,OAAO;CAET,IAAI,EAAE,IAAI,OAAO,eAAe,GAC9B,OAAO;CAET,OAAO,MAAM,QAAQ,KAAK,MAAM,EAAE,IAAI,GAAG;AAC3C;AAGA,SAAgB,kBAAkB,SAAiC;CACjE,MAAM,MAAM,IAAI,YAAyC;CAEzD,MAAM,YAAY,QAAQ,SAAS,eAAe,QAAQ,MAA4C,IAAI,CAAC;CAE3G,MAAM,KAAK,QAAQ,KAAK,iBAAiB,QAAQ,EAAwC,IAAI,CAAC;CAC9F,MAAM,SAAS,QAAQ,UAAU,kBAAkB;CACnD,MAAM,cAAc,QAAQ,SAAS,KAAA;CAErC,MAAM,WAAW,QAAQ,YAAY,CAAC;CAGtC,MAAM,SAA4C,UAAU,WACxD,YACA;EACE,GAAG;EACH,UAAU,qBAAqB,OAAO;CACxC;CAEJ,IAAI,QAA6B;CACjC,IAAI;CACJ,IAAI,QAAQ,eAAe;EACzB,IAAI,CAAC,OAAO,YACV,MAAM,IAAI,MAAM,gEAAgE;EAElF,MAAM,aAAgC;GACpC,IAAI,QAAQ;GACZ,SAAS,QAAQ;GACjB,QAAQ,QAAQ;GAChB,YAAY,OAAO;GACnB,WAAW,OAAO;GAClB;EACF;EACA,MAAM,eACJ,QAAQ,gBACR,IAAI,qBAAqB;GACvB,aAAa,OAAO,sBAAsB;GAC1C;GACA,QAAQ,OAAO,QAAuB;IACpC,MAAM,SAAS,IAAI,WAAW,QAAQ,QAAQ;IAC9C,MAAM,QAAQ,MAAM,OAAO,IAAI,IAAI,OAAO;IAC1C,IAAI,CAAC,OAAO;KACV,MAAM,kBAAkB;MAAE,SAAS,IAAI;MAAS,OAAO,IAAI;KAAM,GAAG,UAAU;KAC9E;IACF;IACA,MAAM,UAAU,MAAM,IAAI,aAAa,QAAQ,QAAQ,CAAC,CAAC,IAAI,MAAM,SAAS;IAC5E,IAAI,CAAC,SAAS;KACZ,MAAM,kBAAkB;MAAE,SAAS,IAAI;MAAS,OAAO,IAAI;KAAM,GAAG,UAAU;KAC9E;IACF;IACA,MAAM,aAAa,aAAa,QAAQ,KAAK,UAAU,IAAI;IAC3D,MAAM,qBAAqB;KACzB,IAAI,QAAQ;KACZ;KACA,KAAK,MAAM;KACX,QAAQ;KACR,KAAK;KACL,WAAW;KACX,QAAQ,OAAO;KACf;IACF,CAAC,CAAC,CAAC,OAAO,UAAmB;KAC3B,OAAO,MAAM,EAAE,KAAK,MAAM,GAAG,+BAA+B;IAC9D,CAAC;IAED,IAAI,CAAC,MAAM,WAUL;SAAA,MATiB,gBAAgB;MACnC,WAAW;MACX,KAAK,MAAM;MACX,QAAQ,MAAM;MACd,QAAQ,OAAO;MACf,IAAI,QAAQ;MACZ,WAAW,QAAQ;MACnB;KACF,CAAC,CAAC,CAAC,YAAY,KAAK,GACR;MACV,OAAO,KAAK;OAAE,SAAS,MAAM;OAAI,KAAK,MAAM;OAAQ,QAAQ,MAAM;MAAU,GAAG,mCAAmC;MAClH,MAAM,OAAO,UAAU,MAAM,IAAI,UAAU,CAAC,CAAC,YAAY,CAAC,CAAC;MAC3D,MAAM,qBAAqB;OACzB,IAAI,QAAQ;OACZ;OACA,KAAK,MAAM;OACX,QAAQ;OACR,KAAK;OACL,WAAW;OACX,QAAQ,OAAO;OACf;MACF,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;MACjB;KACF;;IAIF,IAAI,MADc,uBAAuB,QAAQ,UAAU,QAAQ,IAAI,MAAM,QAAQ,MAAM,EAAE,GACpF;KACP,OAAO,KAAK;MAAE,SAAS,MAAM;MAAI,KAAK,MAAM;KAAO,GAAG,mDAAmD;KACzG,MAAM,OAAO,UAAU,MAAM,IAAI,UAAU,CAAC,CAAC,YAAY,CAAC,CAAC;KAC3D,MAAM,qBAAqB;MACzB,IAAI,QAAQ;MACZ;MACA,KAAK,MAAM;MACX,QAAQ;MACR,KAAK;MACL,WAAW;MACX,QAAQ,OAAO;MACf;KACF,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;KACjB;IACF;IACA,IAAI;KACF,MAAM,kBAAkB;MAAE,SAAS,IAAI;MAAS,OAAO,IAAI;KAAM,GAAG,UAAU;KAC9E,MAAM,UAAU,MAAM,OAAO,IAAI,IAAI,OAAO;KAC5C,IAAI,SAAS;MACX,MAAM,WACJ,QAAQ,WAAW,aACf,YACC,QAAQ,WAAW,cAAc,QAAQ,WAAW,WACnD,YACA;MACR,IAAI,UACF,MAAM,qBAAqB;OACzB,IAAI,QAAQ;OACZ;OACA,KAAK,QAAQ;OACb,QAAQ;OACR,KAAK;OACL,WAAW;OACX,QAAQ,OAAO;OACf;MACF,CAAC,CAAC,CAAC,OAAO,UAAmB;OAC3B,OAAO,MAAM,EAAE,KAAK,MAAM,GAAG,gCAAgC;MAC/D,CAAC;KAEL;IACF,SAAS,OAAgB;KACvB,MAAM,qBAAqB;MACzB,IAAI,QAAQ;MACZ;MACA,KAAK,MAAM;MACX,QAAQ;MACR,KAAK;MACL,WAAW;MACX,QAAQ,OAAO;MACf;KACF,CAAC,CAAC,CAAC,YAAY,CAEf,CAAC;KACD,MAAM;IACR;GACF;EACF,CAAC;EACH,QAAQ;EACR,iBAAiB,OAAO,SAAiB,UAAkC;GACzE,MAAM,aAAa,QAAQ;IAAE;IAAS;GAAM,CAAC;EAC/C;CACF;CAEA,IAAI,IAAI,KAAK,UAAU,CAAC;CAKxB,IAAI,IAAI,KAAK,OAAO,GAAG,SAAS;EAC9B,MAAM,UAAU,YAAY,IAAI;EAChC,MAAM,KAAK,EAAE,IAAI,WAAW;EAC5B,OAAO,KAAK;GAAE,OAAO;GAAI,QAAQ,EAAE,IAAI;GAAQ,KAAK,EAAE,IAAI;EAAK,GAAG,eAAe;EACjF,MAAM,KAAK;EACX,OAAO,KACL;GACE,OAAO;GACP,QAAQ,EAAE,IAAI;GACd,KAAK,EAAE,IAAI;GACX,QAAQ,EAAE,IAAI;GACd,YAAY,KAAK,MAAM,YAAY,IAAI,IAAI,OAAO;EACpD,GACA,aACF;CACF,CAAC;CAED,IAAI,IAAI,aAAa,UAAU;EAAE,UAAU;EAAQ,KAAK;CAAI,CAAC,CAAC;CAC9D,IAAI,IAAI,oBAAoB,UAAU;EAAE,UAAU;EAAQ,KAAK;CAAG,CAAC,CAAC;CACpE,IAAI,IAAI,sBAAsB,UAAU;EAAE,UAAU;EAAQ,KAAK;CAAG,CAAC,CAAC;CACtE,IAAI,IAAI,8BAA8B,KAAK,CAAC;CAE5C,IAAI,IAAI,KAAK,OAAO,GAAG,SAAS;EAC9B,MAAM,OAAO,MAAM,YAAY,GAAG,OAAO;EACzC,OAAO,aACL;GACE,IAAI,QAAQ;GACZ,SAAS,QAAQ;GACjB;GACA;GACA;GACA;GACA;GACA;GACA,cAAc;GACd;EACF,GACA,YAAY;GACV,MAAM,KAAK;EACb,CACF;CACF,CAAC;CAMD,IAAI,IAAI,KAAK,OAAO,GAAG,SAAS;EAC9B,MAAM,EAAE,MAAM,aAAa,kBAAkB,SAAS;EACtD,MAAM,EAAC,SAAQ,EAAE;EACjB,IACE,CAAC,iBACD,QACA,KAAK,WAAW,OAAO,KACvB,KAAK,WAAW,QAAQ,KACxB,KAAK,WAAW,UAAU,KAG1B,KAAK,WAAW,YAAY,KAAK,KAAK,SAAS,YAAY,GAE3D,OAAO,KAAK;EAEd,IAAI,EAAE,IAAI,OAAO,YAAY,MAAM,QAAQ;GACzC,EAAE,OAAO,eAAe,aAAa;GACrC,OAAO,EAAE,KAAK,MAAM,GAAG;EACzB;EACA,OAAO,EAAE,SAAS,eAAe,GAAG;CACtC,CAAC;CAED,iBAAiB,GAAG;CACpB,eAAe,GAAG;CAClB,eAAe,GAAG;CAClB,cAAc,GAAG;CACjB,gBAAgB,GAAG;CACnB,eAAe,GAAG;CAClB,iBAAiB,GAAG;CACpB,sBAAsB,GAAG;CACzB,cAAc,GAAG;CACjB,IAAI,QAAQ,MACV,aAAa,KAAK,QAAQ,IAAI;CAEhC,eAAe,GAAG;CAClB,kBAAkB,GAAG;CACrB,gBAAgB,GAAG;CAEnB,IAAI,IAAI,wBAAwB;EAC9B,SAAS;EACT,MAAM;GACJ,OAAO;GACP,aACE;GACF,SAAS;EACX;CACF,CAAC;CACD,IAAI,IAAI,gBAAgB,UAAU,EAAE,KAAK,uBAAuB,CAAC,CAAC;CAElE,OAAO;AACT"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.tsx"],"sourcesContent":["/**\n * StoryShelf core: compose database, storage, capture, auth, and git-host\n * adapters into a complete self-hosted visual-testing Hono server.\n */\nimport { swaggerUI } from \"@hono/swagger-ui\";\nimport { OpenAPIHono } from \"@hono/zod-openapi\";\nimport type { Context, Next } from \"hono\";\nimport { requestId } from \"hono/request-id\";\nimport type { CaptureQueue } from \"./adapters/capture-queue.ts\";\nimport {\n AdapterLifecycleError,\n collectCloses,\n collectInits,\n runAdapterCloses,\n runAdapterInits,\n} from \"./adapters/init.ts\";\nimport type { AdapterInitResult } from \"./adapters/init.ts\";\nimport type {\n AdapterInitContext,\n AdapterMetadata,\n GitAdapterMetadata,\n} from \"./adapters/metadata.ts\";\nimport { createDispatchJob } from \"./capture/dispatch.ts\";\nimport type { CaptureJobOptions } from \"./capture/orchestrator.ts\";\nimport { InMemoryCaptureQueue } from \"./capture/queue.ts\";\nimport type { ShelfOptions } from \"./config.ts\";\nimport { validateConfig, validateUiConfig } from \"./config.ts\";\nimport { createShelfLogger } from \"./logger.ts\";\nimport type { Logger } from \"./logger.ts\";\nimport {\n authGate,\n csrf,\n rateLimit,\n requestLogging,\n resolveRequestUser,\n storeScope,\n} from \"./middleware/index.ts\";\nimport { registerAdmin } from \"./routers/admin.ts\";\nimport { registerAssets } from \"./routers/assets.ts\";\nimport { registerAuth } from \"./routers/auth.ts\";\nimport { registerBuilds } from \"./routers/builds.ts\";\nimport { registerHealth } from \"./routers/health.ts\";\nimport type { HealthDeps } from \"./routers/health.ts\";\nimport { registerLabels } from \"./routers/labels.ts\";\nimport { registerMedia } from \"./routers/media.ts\";\nimport { registerMembers } from \"./routers/members.ts\";\nimport { registerProjects } from \"./routers/projects.ts\";\nimport { registerStatusConfigs } from \"./routers/status-configs.ts\";\nimport { registerStorybook } from \"./routers/storybook.ts\";\nimport { registerTokens } from \"./routers/tokens.ts\";\nimport { registerUiPages } from \"./routers/ui.ts\";\nimport { registerWebhooks } from \"./routers/webhooks.ts\";\n\n/** Per-request shelf context (adapters, config, user, capture queue). */\nexport interface ShelfContext {\n requestId?: string;\n db: import(\"./adapters/database.ts\").DatabaseAdapter;\n storage: import(\"./adapters/storage.ts\").StorageAdapter;\n config: import(\"./config.ts\").ShelfConfig;\n ui: import(\"./config.ts\").UIConfig;\n logger: ReturnType<typeof createShelfLogger>;\n user: import(\"./adapters/auth.ts\").AuthUser | null;\n authEnabled: boolean;\n enqueueCapture?: (buildId: string, reqId?: string) => Promise<void>;\n captureQueue: import(\"./adapters/capture-queue.ts\").CaptureQueue | null;\n gitHosts: import(\"./adapters/git-host/index.ts\").GitHostProvider[];\n}\n\n/** Hono application type carrying the shelf context variables. */\nexport type ShelfApp = OpenAPIHono<{ Variables: ShelfContext }>;\n\n/** App-scoped adapter lifecycle namespace (single `lifecycle` prop avoids Hono collisions). */\nexport interface ShelfLifecycle {\n /** Settled summary of the eager background init run; never rejects. */\n readonly ready: Promise<AdapterInitResult>;\n /**\n * Fail-fast await for server startup (`await app.lifecycle.init()`).\n * Re-runs init so transient failures are retryable; throws AdapterLifecycleError.\n * Optional — when skipped, the first request gates on `ready` instead.\n */\n init(): Promise<void>;\n /** Graceful teardown for SIGTERM/SIGINT (`await app.lifecycle.close()`). Idempotent. */\n close(): Promise<void>;\n}\n\n/** Hono app with the shelf lifecycle namespace attached. */\nexport type ShelfRouter = ShelfApp & { readonly lifecycle: ShelfLifecycle };\n\nfunction addOptionalSnapshots(\n snap: Record<string, AdapterMetadata | GitAdapterMetadata>,\n options: ShelfOptions,\n): void {\n if (options.captureRunner) {\n snap[\"captureRunner\"] = options.captureRunner.metadata;\n }\n if (options.captureQueue) {\n snap[\"captureQueue\"] = options.captureQueue.metadata;\n }\n if (options.auth) {\n snap[\"auth\"] = options.auth.metadata;\n }\n}\n\nfunction buildAdapterSnapshot(\n options: ShelfOptions,\n): Record<string, AdapterMetadata | GitAdapterMetadata> {\n const snap: Record<string, AdapterMetadata | GitAdapterMetadata> = {\n database: options.database.metadata,\n storage: options.storage.metadata,\n };\n addOptionalSnapshots(snap, options);\n for (const p of options.gitHosts ?? []) {\n snap[`git:${p.metadata.kind}`] = p.metadata;\n }\n return snap;\n}\n\ninterface ServerRuntime {\n config: import(\"./config.ts\").ShelfConfig;\n ui: import(\"./config.ts\").UIConfig;\n logger: import(\"./logger.ts\").Logger;\n authEnabled: boolean;\n gitHosts: import(\"./adapters/git-host/index.ts\").GitHostProvider[];\n}\n\n/** Validate config/ui and derive runtime singletons from options. */\nfunction resolveRuntime(options: ShelfOptions): ServerRuntime {\n // eslint-disable-next-line typescript/no-unnecessary-type-assertion -- ShelfConfig lacks index signature\n const rawConfig = options.config\n ? validateConfig(options.config as unknown as Record<string, unknown>)\n : {};\n // eslint-disable-next-line typescript/no-unnecessary-type-assertion -- UIConfig lacks index signature\n const ui = options.ui ? validateUiConfig(options.ui as unknown as Record<string, unknown>) : {};\n const logger = options.logger ?? createShelfLogger();\n const authEnabled = options.auth !== undefined;\n const gitHosts = options.gitHosts ?? [];\n // Adapter introspection — auto-populate config.adapters if not supplied\n const config: import(\"./config.ts\").ShelfConfig = rawConfig.adapters\n ? rawConfig\n : {\n ...rawConfig,\n adapters: buildAdapterSnapshot(options),\n };\n return { config, ui, logger, authEnabled, gitHosts };\n}\n\ninterface QueueWiring {\n queue: CaptureQueue | null;\n enqueueCapture: ((buildId: string, reqId?: string) => Promise<void>) | undefined;\n}\n\n/** Assemble the capture queue and its enqueue hook when a runner is configured. */\nfunction setupCaptureQueue(\n options: ShelfOptions,\n config: import(\"./config.ts\").ShelfConfig,\n gitHosts: import(\"./adapters/git-host/index.ts\").GitHostProvider[],\n logger: import(\"./logger.ts\").Logger,\n): QueueWiring {\n if (!options.captureRunner) {\n return { queue: null, enqueueCapture: undefined };\n }\n if (!config.scratchDir) {\n throw new Error(\"captureRunner is enabled but ShelfConfig.scratchDir is not set\");\n }\n const jobOptions: CaptureJobOptions = {\n db: options.database,\n storage: options.storage,\n runner: options.captureRunner,\n scratchDir: config.scratchDir,\n viewports: config.viewports,\n logger,\n secret: config.secret,\n };\n const runJob = createDispatchJob({\n db: options.database,\n jobOptions,\n gitHosts,\n secret: config.secret,\n logger,\n });\n const captureQueue =\n options.captureQueue ??\n new InMemoryCaptureQueue({\n concurrency: config.captureConcurrency ?? 2,\n logger,\n runJob,\n });\n const enqueueCapture = async (buildId: string, reqId?: string): Promise<void> => {\n await captureQueue.enqueue({ buildId, reqId });\n };\n return { queue: captureQueue, enqueueCapture };\n}\n\ninterface MiddlewareWiring extends ServerRuntime, QueueWiring {\n options: ShelfOptions;\n getReady: () => Promise<AdapterInitResult>;\n}\n\n/** Failures safe to expose on the init gate's 503 (no secrets). */\nfunction publicFailures(\n result: AdapterInitResult,\n): { category: string; kind: string; error: string }[] {\n return result.failures.map((failure) => ({\n category: failure.category,\n kind: failure.kind,\n error: failure.error,\n }));\n}\n\n/**\n * Block requests until adapter init settles; answer 503 with the per-adapter\n * failures when init failed. Health probes are exempt (they report init\n * state themselves instead of being masked by the gate).\n */\nfunction initGate(getReady: () => Promise<AdapterInitResult>) {\n // oxlint-disable-next-line typescript/no-invalid-void-type -- Hono middleware may not return Response\n return async (c: Context, next: Next): Promise<Response | void> => {\n if (c.req.path === \"/api/v1/health\") {\n await next();\n return;\n }\n const result = await getReady();\n if (!result.ok) {\n return c.json(\n { error: \"Adapters failed to initialize\", failures: publicFailures(result) },\n 503,\n );\n }\n await next();\n };\n}\n\n/** Mutable init-settlement cell shared by the gate, health, and `init()`. */\ninterface LifecycleCell {\n ready: Promise<AdapterInitResult>;\n settled: AdapterInitResult | null;\n}\n\nfunction trackSettlement(cell: LifecycleCell, promise: Promise<AdapterInitResult>): void {\n promise.then(\n (result) => {\n cell.settled = result;\n },\n () => {\n cell.settled = { ok: false, failures: [] };\n },\n );\n}\n\n/** Kick the eager background init run (resolves; never rejects). */\nfunction kickInit(\n options: ShelfOptions,\n ctx: AdapterInitContext,\n logger: Logger,\n cell: LifecycleCell,\n): void {\n cell.ready = runAdapterInits(collectInits(options), ctx, logger);\n trackSettlement(cell, cell.ready);\n}\n\n/** Kick eager init and attach the `app.lifecycle` namespace (single run). */\nfunction attachLifecycle(\n app: ShelfApp,\n options: ShelfOptions,\n runtime: ServerRuntime,\n cell: LifecycleCell,\n): void {\n const initCtx: AdapterInitContext = { config: runtime.config, logger: runtime.logger };\n kickInit(options, initCtx, runtime.logger, cell);\n Object.assign(app, { lifecycle: createLifecycle(options, initCtx, runtime.logger, cell) });\n}\n\n/** Build the `app.lifecycle` namespace closing over one settlement cell. */\nfunction createLifecycle(\n options: ShelfOptions,\n ctx: AdapterInitContext,\n logger: Logger,\n cell: LifecycleCell,\n): ShelfLifecycle {\n return {\n get ready() {\n return cell.ready;\n },\n init: async () => {\n const result = await runAdapterInits(collectInits(options), ctx, logger);\n cell.ready = Promise.resolve(result);\n cell.settled = result;\n if (!result.ok) {\n throw new AdapterLifecycleError(\"init\", result.failures);\n }\n },\n close: async () => {\n const result = await runAdapterCloses(collectCloses(options), ctx, logger);\n if (!result.ok) {\n throw new AdapterLifecycleError(\"close\", result.failures);\n }\n },\n };\n}\n\n/** Attach global middleware: ids, logging, init gate, limits, store scope, auth gate. */\nfunction wireMiddleware(app: ShelfApp, wiring: MiddlewareWiring): void {\n const { options, config, ui, logger, authEnabled, enqueueCapture, queue, gitHosts, getReady } =\n wiring;\n app.use(\"*\", requestId());\n // Structured request logging. Uses a Hono-native middleware rather than\n // Pino-http, which expects a Node server response (`res.on`) incompatible\n // With Hono's Web `Request`/`Response` model.\n app.use(\"*\", requestLogging(logger));\n app.use(\"*\", initGate(getReady));\n app.use(\"/api/v1/*\", rateLimit({ windowMs: 60_000, max: 100 }));\n app.use(\"/api/v1/tokens/*\", rateLimit({ windowMs: 60_000, max: 10 }));\n app.use(\"/api/v1/webhooks/*\", rateLimit({ windowMs: 60_000, max: 20 }));\n app.use(\"/projects/:slug/settings/*\", csrf());\n app.use(\n \"*\",\n storeScope({\n db: options.database,\n storage: options.storage,\n config,\n ui,\n logger,\n authEnabled,\n enqueueCapture,\n captureQueue: queue,\n gitHosts,\n resolveUser: async (c) => await resolveRequestUser(c, options.auth),\n }),\n );\n app.use(\"*\", authGate());\n}\n\n/** Register every JSON API router. */\nfunction registerApiRoutes(app: ShelfApp, health: HealthDeps): void {\n registerProjects(app);\n registerBuilds(app);\n registerLabels(app);\n registerMedia(app);\n registerMembers(app);\n registerTokens(app);\n registerWebhooks(app);\n registerStatusConfigs(app);\n registerHealth(app, health);\n registerAdmin(app);\n}\n\n/** Register HTML pages, assets, and the optional auth flow. */\nfunction registerPageRoutes(app: ShelfApp, options: ShelfOptions): void {\n if (options.auth) {\n registerAuth(app, options.auth);\n }\n registerAssets(app);\n registerStorybook(app);\n registerUiPages(app);\n}\n\n/** Register the OpenAPI document and interactive docs page. */\nfunction registerDocs(app: ShelfApp): void {\n app.doc(\"/api/v1/openapi.json\", {\n openapi: \"3.0.0\",\n info: {\n title: \"StoryShelf API\",\n description:\n \"REST API for the StoryShelf visual testing platform. JSON endpoints live under /api/v1; HTML pages are served at /.\",\n version: \"1.0.0\",\n },\n });\n app.get(\"/api/v1/docs\", swaggerUI({ url: \"/api/v1/openapi.json\" }));\n}\n\n/** Register every API router, page set, and optional auth flow. */\nfunction registerAllRoutes(app: ShelfApp, options: ShelfOptions, health: HealthDeps): void {\n registerApiRoutes(app, health);\n registerPageRoutes(app, options);\n}\n\n/** Package version injected at build via __PKG_VERSION__. */\nfunction packageVersion(): string {\n return (globalThis as unknown as { __PKG_VERSION__?: string }).__PKG_VERSION__ ?? \"0.0.0\";\n}\n\n/**\n * Create the StoryShelf Hono router with all API routes and HTML pages.\n *\n * Adapter `init` hooks kick off eagerly in the background (the constructor\n * stays synchronous). Await `app.lifecycle.init()` before serving for\n * fail-fast startup, or let the first request gate on settlement.\n */\nexport function createShelfRouter(options: ShelfOptions): ShelfRouter {\n const app = new OpenAPIHono<{ Variables: ShelfContext }>() as ShelfRouter;\n const runtime = resolveRuntime(options);\n const cell: LifecycleCell = { ready: Promise.resolve({ ok: true, failures: [] }), settled: null };\n attachLifecycle(app, options, runtime, cell);\n const { queue, enqueueCapture } = setupCaptureQueue(\n options,\n runtime.config,\n runtime.gitHosts,\n runtime.logger,\n );\n wireMiddleware(app, {\n ...runtime,\n queue,\n enqueueCapture,\n options,\n getReady: async () => await cell.ready,\n });\n registerAllRoutes(app, options, {\n sources: options,\n getSettled: () => cell.settled,\n bootTimeMs: Date.now(),\n version: packageVersion(),\n });\n registerDocs(app);\n\n return app;\n}\n\n/**\n * Public surface: the router and its types only.\n *\n * `createShelfRouter` plus the option/config types needed to call it. Adapter\n * interfaces live under `core/adapter/*`; runtime helpers under `core/logger`,\n * `core/capture`, `core/paths`, `core/urls`, `core/diff`; tables and row types\n * under `core/schema`. Models and internal tooling (`store`, `middleware`,\n * `retention`, pages) have no public entry. Importing the barrel\n * must never pull the Hono router into bundles that do not serve it.\n */\nexport type { ShelfOptions, ShelfConfig, UIConfig, BrandTheme } from \"./config.ts\";\n"],"mappings":"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"}
|
package/dist/logger.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import __tsdown_shims_path from 'node:path';
|
|
2
2
|
import __tsdown_shims_url from 'node:url';
|
|
3
|
-
import { Logger
|
|
3
|
+
import { Logger } from "pino";
|
|
4
4
|
//#region src/logger.d.ts
|
|
5
5
|
/** A pino worker transport to attach to the logger output. */
|
|
6
6
|
interface PinoTransport {
|
|
@@ -33,7 +33,7 @@ interface LoggerOptions {
|
|
|
33
33
|
* Datadog, GCP, OTEL collector, etc.) are added as pino worker `transports` —
|
|
34
34
|
* they are sinks for this logger, not standalone loggers.
|
|
35
35
|
*/
|
|
36
|
-
declare function createShelfLogger(options?: LoggerOptions): Logger
|
|
36
|
+
declare function createShelfLogger(options?: LoggerOptions): Logger;
|
|
37
37
|
//#endregion
|
|
38
|
-
export { type Logger
|
|
38
|
+
export { type Logger, LoggerOptions, PinoTransport, createShelfLogger };
|
|
39
39
|
//# sourceMappingURL=logger.d.mts.map
|