@storyshelf/core 0.3.1 → 0.5.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/README.md +38 -31
- package/dist/adapter/capture-queue.d.mts +29 -1
- package/dist/adapter/capture-runner.d.mts +18 -2
- package/dist/adapter/capture-runner.mjs +12 -1
- package/dist/adapter/capture-runner.mjs.map +1 -0
- package/dist/adapter/database.d.mts +67 -17
- package/dist/adapter/database.mjs +22 -55
- package/dist/adapter/database.mjs.map +1 -1
- package/dist/adapter/email-sender.d.mts +27 -0
- package/dist/adapter/email-sender.mjs +4 -0
- package/dist/adapter/metadata.d.mts +21 -15
- package/dist/adapter/notifier.d.mts +7 -0
- package/dist/adapter/notifier.mjs +6 -0
- package/dist/adapter/setup.d.mts +61 -0
- package/dist/adapter/setup.mjs +29 -0
- package/dist/adapter/setup.mjs.map +1 -0
- package/dist/adapter/webhook-events.d.mts +29 -0
- package/dist/adapter/webhook-events.mjs +83 -0
- package/dist/adapter/webhook-events.mjs.map +1 -0
- package/dist/adapters/notifier/events.d.mts +26 -0
- package/dist/adapters/notifier/events.mjs +66 -0
- package/dist/adapters/notifier/events.mjs.map +1 -0
- package/dist/adapters/notifier/format.d.mts +14 -0
- package/dist/adapters/notifier/format.mjs +96 -0
- package/dist/adapters/notifier/format.mjs.map +1 -0
- package/dist/adapters/notifier/provider.d.mts +32 -0
- package/dist/adapters/notifier/types.d.mts +56 -0
- package/dist/adapters/setup-runner.d.mts +23 -0
- package/dist/adapters/setup-runner.mjs +149 -0
- package/dist/adapters/setup-runner.mjs.map +1 -0
- package/dist/adapters/validate.d.mts +30 -0
- package/dist/adapters/validate.mjs +313 -0
- package/dist/adapters/validate.mjs.map +1 -0
- package/dist/auth.d.mts +94 -0
- package/dist/auth.mjs +4 -0
- package/dist/brand-packs.d.mts +16 -0
- package/dist/brand-packs.mjs +130 -0
- package/dist/brand-packs.mjs.map +1 -0
- package/dist/capture/adapter.d.mts +51 -1
- package/dist/capture/adapter.mjs.map +1 -1
- package/dist/capture/affected.d.mts +16 -0
- package/dist/capture/affected.mjs +64 -0
- package/dist/capture/affected.mjs.map +1 -0
- package/dist/capture/diff-record.mjs +30 -0
- package/dist/capture/diff-record.mjs.map +1 -0
- package/dist/capture/dispatch.d.mts +36 -0
- package/dist/capture/dispatch.mjs +88 -31
- package/dist/capture/dispatch.mjs.map +1 -1
- package/dist/capture/guards.mjs +31 -0
- package/dist/capture/guards.mjs.map +1 -0
- package/dist/capture/orchestrator.d.mts +41 -6
- package/dist/capture/orchestrator.mjs +151 -28
- package/dist/capture/orchestrator.mjs.map +1 -1
- package/dist/capture/pipeline.d.mts +30 -16
- package/dist/capture/pipeline.mjs +143 -39
- package/dist/capture/pipeline.mjs.map +1 -1
- package/dist/capture/recovery.d.mts +21 -0
- package/dist/capture/recovery.mjs +83 -0
- package/dist/capture/recovery.mjs.map +1 -0
- package/dist/capture/rediff.d.mts +35 -0
- package/dist/capture/rediff.mjs +77 -0
- package/dist/capture/rediff.mjs.map +1 -0
- package/dist/capture/sizing.d.mts +58 -0
- package/dist/capture/sizing.mjs +119 -0
- package/dist/capture/sizing.mjs.map +1 -0
- package/dist/capture/skip-checks.mjs +3 -3
- package/dist/capture/skip-checks.mjs.map +1 -1
- package/dist/capture/statics.d.mts +38 -0
- package/dist/capture/statics.mjs +38 -4
- package/dist/capture/statics.mjs.map +1 -1
- package/dist/capture/status-fanout.mjs +7 -2
- package/dist/capture/status-fanout.mjs.map +1 -1
- package/dist/capture/storybook.d.mts +31 -2
- package/dist/capture/storybook.mjs +77 -2
- package/dist/capture/storybook.mjs.map +1 -1
- package/dist/capture.d.mts +10 -4
- package/dist/capture.mjs +7 -2
- package/dist/config.d.mts +121 -4
- package/dist/config.mjs +55 -8
- package/dist/config.mjs.map +1 -1
- package/dist/index.d.mts +5 -56
- package/dist/index.mjs +4 -284
- package/dist/logger.d.mts +26 -17
- package/dist/logger.mjs +10 -5
- package/dist/logger.mjs.map +1 -1
- package/dist/models/baseline-status.d.mts +29 -0
- package/dist/models/baseline-status.mjs +26 -0
- package/dist/models/baseline-status.mjs.map +1 -0
- package/dist/models/baseline.d.mts +33 -0
- package/dist/models/baseline.mjs +35 -13
- package/dist/models/baseline.mjs.map +1 -1
- package/dist/models/build.d.mts +87 -0
- package/dist/models/build.mjs +83 -35
- package/dist/models/build.mjs.map +1 -1
- package/dist/models/capture-attempt.d.mts +40 -0
- package/dist/models/capture-attempt.mjs +85 -0
- package/dist/models/capture-attempt.mjs.map +1 -0
- package/dist/models/capture-log.d.mts +21 -0
- package/dist/models/capture-log.mjs +44 -0
- package/dist/models/capture-log.mjs.map +1 -0
- package/dist/models/comment.d.mts +29 -0
- package/dist/models/comment.mjs +11 -8
- package/dist/models/comment.mjs.map +1 -1
- package/dist/models/content-ref.d.mts +21 -0
- package/dist/models/content-ref.mjs +49 -0
- package/dist/models/content-ref.mjs.map +1 -0
- package/dist/models/credentials.d.mts +36 -0
- package/dist/models/credentials.mjs +103 -0
- package/dist/models/credentials.mjs.map +1 -0
- package/dist/models/label.d.mts +58 -0
- package/dist/models/label.mjs +32 -27
- package/dist/models/label.mjs.map +1 -1
- package/dist/models/member.d.mts +34 -0
- package/dist/models/member.mjs +20 -11
- package/dist/models/member.mjs.map +1 -1
- package/dist/models/notification-channel.d.mts +52 -0
- package/dist/models/notification-channel.mjs +93 -0
- package/dist/models/notification-channel.mjs.map +1 -0
- package/dist/models/notification-subscription.d.mts +44 -0
- package/dist/models/notification-subscription.mjs +89 -0
- package/dist/models/notification-subscription.mjs.map +1 -0
- package/dist/models/project-group-mapping.d.mts +30 -0
- package/dist/models/project-group-mapping.mjs +46 -0
- package/dist/models/project-group-mapping.mjs.map +1 -0
- package/dist/models/project.d.mts +54 -0
- package/dist/models/project.mjs +15 -11
- package/dist/models/project.mjs.map +1 -1
- package/dist/models/snapshot.d.mts +58 -0
- package/dist/models/snapshot.mjs +13 -7
- package/dist/models/snapshot.mjs.map +1 -1
- package/dist/models/status-config.d.mts +38 -0
- package/dist/models/status-config.mjs +12 -11
- package/dist/models/status-config.mjs.map +1 -1
- package/dist/models/token.d.mts +44 -0
- package/dist/models/token.mjs +18 -15
- package/dist/models/token.mjs.map +1 -1
- package/dist/models/user.d.mts +42 -0
- package/dist/models/user.mjs +65 -0
- package/dist/models/user.mjs.map +1 -0
- package/dist/models/webhook.d.mts +50 -0
- package/dist/models/webhook.mjs +12 -10
- package/dist/models/webhook.mjs.map +1 -1
- package/dist/models.d.mts +22 -0
- package/dist/models.mjs +23 -0
- package/dist/paths.mjs +29 -0
- package/dist/paths.mjs.map +1 -1
- package/dist/retention.d.mts +45 -0
- package/dist/retention.mjs +145 -0
- package/dist/retention.mjs.map +1 -0
- package/dist/schema/baseline.d.mts +1 -0
- package/dist/schema/build.d.mts +8 -0
- package/dist/schema/capture-attempt.d.mts +38 -0
- package/dist/schema/comment.d.mts +1 -1
- package/dist/schema/content-ref.d.mts +13 -0
- package/dist/schema/member.d.mts +2 -0
- package/dist/schema/notification.d.mts +35 -0
- package/dist/schema/project-group-mapping.d.mts +15 -0
- package/dist/schema/project.d.mts +4 -0
- package/dist/schema/snapshot.d.mts +7 -0
- package/dist/schema/token.d.mts +2 -0
- package/dist/schema/user.d.mts +11 -1
- package/dist/schema.d.mts +9 -46
- package/dist/schema.mjs +1 -55
- package/dist/test-helpers/create-project.d.mts +15 -0
- package/dist/test-helpers/create-project.mjs +28 -0
- package/dist/test-helpers/create-project.mjs.map +1 -0
- package/dist/test-helpers/database-contract.d.mts +13 -0
- package/dist/test-helpers/database-contract.mjs +58 -0
- package/dist/test-helpers/database-contract.mjs.map +1 -0
- package/dist/test-helpers/fake-adapters.d.mts +14 -0
- package/dist/test-helpers/fake-adapters.mjs +20 -0
- package/dist/test-helpers/fake-adapters.mjs.map +1 -0
- package/dist/test-helpers/fake-database.d.mts +11 -0
- package/dist/test-helpers/fake-database.mjs +104 -0
- package/dist/test-helpers/fake-database.mjs.map +1 -0
- package/dist/test-helpers/fake-storage.d.mts +14 -0
- package/dist/test-helpers/fake-storage.mjs +49 -0
- package/dist/test-helpers/fake-storage.mjs.map +1 -0
- package/dist/test-helpers/fake-tables.mjs +276 -0
- package/dist/test-helpers/fake-tables.mjs.map +1 -0
- package/dist/test-helpers/git-contract.d.mts +11 -0
- package/dist/test-helpers/git-contract.mjs +34 -0
- package/dist/test-helpers/git-contract.mjs.map +1 -0
- package/dist/test-helpers/notifier-contract.d.mts +11 -0
- package/dist/test-helpers/notifier-contract.mjs +34 -0
- package/dist/test-helpers/notifier-contract.mjs.map +1 -0
- package/dist/test-helpers/queue-contract.d.mts +11 -0
- package/dist/test-helpers/queue-contract.mjs +64 -0
- package/dist/test-helpers/queue-contract.mjs.map +1 -0
- package/dist/test-helpers/runner-contract.d.mts +11 -0
- package/dist/test-helpers/runner-contract.mjs +32 -0
- package/dist/test-helpers/runner-contract.mjs.map +1 -0
- package/dist/test-helpers/sql-chunks.mjs +138 -0
- package/dist/test-helpers/sql-chunks.mjs.map +1 -0
- package/dist/test-helpers/storage-contract.d.mts +11 -0
- package/dist/test-helpers/storage-contract.mjs +87 -0
- package/dist/test-helpers/storage-contract.mjs.map +1 -0
- package/dist/test-helpers.d.mts +13 -0
- package/dist/test-helpers.mjs +14 -0
- package/dist/tracing.mjs +63 -0
- package/dist/tracing.mjs.map +1 -0
- package/dist/types.d.mts +38 -5
- package/dist/types.mjs +18 -5
- package/dist/types.mjs.map +1 -1
- package/dist/urls.d.mts +12 -1
- package/dist/urls.mjs +22 -3
- package/dist/urls.mjs.map +1 -1
- package/dist/utils/encrypt.d.mts +48 -0
- package/dist/utils/encrypt.mjs +67 -13
- package/dist/utils/encrypt.mjs.map +1 -1
- package/dist/utils/hash.d.mts +17 -0
- package/dist/utils/hash.mjs +8 -2
- package/dist/utils/hash.mjs.map +1 -1
- package/dist/utils/http.d.mts +58 -0
- package/dist/utils/http.mjs +147 -0
- package/dist/utils/http.mjs.map +1 -0
- package/dist/utils/mime.d.mts +9 -0
- package/dist/utils/mime.mjs +33 -0
- package/dist/utils/mime.mjs.map +1 -0
- package/dist/utils/path-security.d.mts +12 -0
- package/dist/utils/path-security.mjs +24 -0
- package/dist/utils/path-security.mjs.map +1 -0
- package/dist/utils/redact.d.mts +17 -0
- package/dist/utils/redact.mjs +42 -0
- package/dist/utils/redact.mjs.map +1 -0
- package/dist/utils/timing.d.mts +19 -0
- package/dist/utils/timing.mjs +46 -0
- package/dist/utils/timing.mjs.map +1 -0
- package/dist/utils/ulid.d.mts +10 -0
- package/dist/utils/ulid.mjs +8 -8
- package/dist/utils/ulid.mjs.map +1 -1
- package/dist/utils.d.mts +12 -0
- package/dist/utils.mjs +13 -0
- package/package.json +69 -28
- package/dist/adapter/auth.d.mts +0 -50
- package/dist/adapter/auth.mjs +0 -10
- package/dist/adapter/auth.mjs.map +0 -1
- package/dist/adapter/init.d.mts +0 -58
- package/dist/adapter/init.mjs +0 -93
- package/dist/adapter/init.mjs.map +0 -1
- package/dist/adapters/webhook-events.mjs +0 -55
- package/dist/adapters/webhook-events.mjs.map +0 -1
- package/dist/ddl.d.mts +0 -8
- package/dist/ddl.mjs +0 -158
- package/dist/ddl.mjs.map +0 -1
- package/dist/index.mjs.map +0 -1
- package/dist/middleware/auth-gate.mjs +0 -36
- package/dist/middleware/auth-gate.mjs.map +0 -1
- package/dist/middleware/csrf.mjs +0 -40
- package/dist/middleware/csrf.mjs.map +0 -1
- package/dist/middleware/rate-limit.mjs +0 -39
- package/dist/middleware/rate-limit.mjs.map +0 -1
- package/dist/middleware/request-log.mjs +0 -28
- package/dist/middleware/request-log.mjs.map +0 -1
- package/dist/middleware/store-scope.mjs +0 -29
- package/dist/middleware/store-scope.mjs.map +0 -1
- package/dist/pages/build-detail.mjs +0 -339
- package/dist/pages/build-detail.mjs.map +0 -1
- package/dist/pages/build-diff-comments.mjs +0 -113
- package/dist/pages/build-diff-comments.mjs.map +0 -1
- package/dist/pages/build-diff-header.mjs +0 -163
- package/dist/pages/build-diff-header.mjs.map +0 -1
- package/dist/pages/build-diff-nav.mjs +0 -66
- package/dist/pages/build-diff-nav.mjs.map +0 -1
- package/dist/pages/build-diff-viewer.mjs +0 -197
- package/dist/pages/build-diff-viewer.mjs.map +0 -1
- package/dist/pages/build-diff.mjs +0 -90
- package/dist/pages/build-diff.mjs.map +0 -1
- package/dist/pages/compute-jobs.mjs +0 -198
- package/dist/pages/compute-jobs.mjs.map +0 -1
- package/dist/pages/label-detail.mjs +0 -228
- package/dist/pages/label-detail.mjs.map +0 -1
- package/dist/pages/login.mjs +0 -78
- package/dist/pages/login.mjs.map +0 -1
- package/dist/pages/project-builds.mjs +0 -231
- package/dist/pages/project-builds.mjs.map +0 -1
- package/dist/pages/project-create.mjs +0 -169
- package/dist/pages/project-create.mjs.map +0 -1
- package/dist/pages/project-settings.mjs +0 -102
- package/dist/pages/project-settings.mjs.map +0 -1
- package/dist/pages/projects.mjs +0 -184
- package/dist/pages/projects.mjs.map +0 -1
- package/dist/pages/root.mjs +0 -94
- package/dist/pages/root.mjs.map +0 -1
- package/dist/pages/settings-general.mjs +0 -192
- package/dist/pages/settings-general.mjs.map +0 -1
- package/dist/pages/settings-labels.mjs +0 -150
- package/dist/pages/settings-labels.mjs.map +0 -1
- package/dist/pages/settings-members.mjs +0 -125
- package/dist/pages/settings-members.mjs.map +0 -1
- package/dist/pages/settings-status.mjs +0 -152
- package/dist/pages/settings-status.mjs.map +0 -1
- package/dist/pages/settings-tests.mjs +0 -149
- package/dist/pages/settings-tests.mjs.map +0 -1
- package/dist/pages/settings-tokens.mjs +0 -96
- package/dist/pages/settings-tokens.mjs.map +0 -1
- package/dist/pages/settings-webhooks.mjs +0 -150
- package/dist/pages/settings-webhooks.mjs.map +0 -1
- package/dist/pages/storybook.mjs +0 -96
- package/dist/pages/storybook.mjs.map +0 -1
- package/dist/retention/purge.mjs +0 -67
- package/dist/retention/purge.mjs.map +0 -1
- package/dist/routers/admin.mjs +0 -42
- package/dist/routers/admin.mjs.map +0 -1
- package/dist/routers/assets.mjs +0 -25
- package/dist/routers/assets.mjs.map +0 -1
- package/dist/routers/auth.mjs +0 -78
- package/dist/routers/auth.mjs.map +0 -1
- package/dist/routers/builds.handlers.mjs +0 -184
- package/dist/routers/builds.handlers.mjs.map +0 -1
- package/dist/routers/builds.mjs +0 -185
- package/dist/routers/builds.mjs.map +0 -1
- package/dist/routers/comments.mjs +0 -90
- package/dist/routers/comments.mjs.map +0 -1
- package/dist/routers/health.mjs +0 -137
- package/dist/routers/health.mjs.map +0 -1
- package/dist/routers/helpers.mjs +0 -82
- package/dist/routers/helpers.mjs.map +0 -1
- package/dist/routers/htmx.mjs +0 -20
- package/dist/routers/htmx.mjs.map +0 -1
- package/dist/routers/labels.mjs +0 -110
- package/dist/routers/labels.mjs.map +0 -1
- package/dist/routers/media.mjs +0 -55
- package/dist/routers/media.mjs.map +0 -1
- package/dist/routers/members.mjs +0 -102
- package/dist/routers/members.mjs.map +0 -1
- package/dist/routers/projects.mjs +0 -122
- package/dist/routers/projects.mjs.map +0 -1
- package/dist/routers/schemas.mjs +0 -224
- package/dist/routers/schemas.mjs.map +0 -1
- package/dist/routers/settings-general.mjs +0 -95
- package/dist/routers/settings-general.mjs.map +0 -1
- package/dist/routers/settings-labels.mjs +0 -49
- package/dist/routers/settings-labels.mjs.map +0 -1
- package/dist/routers/settings-members.mjs +0 -30
- package/dist/routers/settings-members.mjs.map +0 -1
- package/dist/routers/settings-status.mjs +0 -73
- package/dist/routers/settings-status.mjs.map +0 -1
- package/dist/routers/settings-tokens.mjs +0 -40
- package/dist/routers/settings-tokens.mjs.map +0 -1
- package/dist/routers/settings-webhooks.mjs +0 -63
- package/dist/routers/settings-webhooks.mjs.map +0 -1
- package/dist/routers/settings.handlers.mjs +0 -93
- package/dist/routers/settings.handlers.mjs.map +0 -1
- package/dist/routers/settings.mjs +0 -23
- package/dist/routers/settings.mjs.map +0 -1
- package/dist/routers/snapshots.mjs +0 -144
- package/dist/routers/snapshots.mjs.map +0 -1
- package/dist/routers/status-configs.mjs +0 -101
- package/dist/routers/status-configs.mjs.map +0 -1
- package/dist/routers/storybook.mjs +0 -120
- package/dist/routers/storybook.mjs.map +0 -1
- package/dist/routers/tokens.mjs +0 -81
- package/dist/routers/tokens.mjs.map +0 -1
- package/dist/routers/ui.mjs +0 -165
- package/dist/routers/ui.mjs.map +0 -1
- package/dist/routers/webhooks.mjs +0 -90
- package/dist/routers/webhooks.mjs.map +0 -1
- package/dist/schema/baseline.mjs +0 -22
- package/dist/schema/baseline.mjs.map +0 -1
- package/dist/schema/build.mjs +0 -29
- package/dist/schema/build.mjs.map +0 -1
- package/dist/schema/comment.mjs +0 -26
- package/dist/schema/comment.mjs.map +0 -1
- package/dist/schema/label.mjs +0 -30
- package/dist/schema/label.mjs.map +0 -1
- package/dist/schema/member.mjs +0 -19
- package/dist/schema/member.mjs.map +0 -1
- package/dist/schema/project.mjs +0 -25
- package/dist/schema/project.mjs.map +0 -1
- package/dist/schema/snapshot.mjs +0 -34
- package/dist/schema/snapshot.mjs.map +0 -1
- package/dist/schema/status-config.mjs +0 -20
- package/dist/schema/status-config.mjs.map +0 -1
- package/dist/schema/token.mjs +0 -19
- package/dist/schema/token.mjs.map +0 -1
- package/dist/schema/user.mjs +0 -19
- package/dist/schema/user.mjs.map +0 -1
- package/dist/schema/webhook.mjs +0 -20
- package/dist/schema/webhook.mjs.map +0 -1
- package/dist/schema.mjs.map +0 -1
- package/dist/store.mjs +0 -20
- package/dist/store.mjs.map +0 -1
- package/dist/ui/document.mjs +0 -348
- package/dist/ui/document.mjs.map +0 -1
- package/dist/ui/feedback.mjs +0 -24
- package/dist/ui/feedback.mjs.map +0 -1
- package/dist/ui/forms.mjs +0 -117
- package/dist/ui/forms.mjs.map +0 -1
- package/dist/ui/styles.mjs +0 -174
- package/dist/ui/styles.mjs.map +0 -1
- package/dist/ui/theme.mjs +0 -44
- package/dist/ui/theme.mjs.map +0 -1
package/dist/ddl.mjs
DELETED
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
import "node:path";
|
|
2
|
-
import "node:url";
|
|
3
|
-
import.meta.url;
|
|
4
|
-
//#region src/ddl.ts
|
|
5
|
-
/** Canonical SQLite DDL for all StoryShelf tables and indexes. */
|
|
6
|
-
const DDL = `
|
|
7
|
-
CREATE TABLE IF NOT EXISTS projects (
|
|
8
|
-
id TEXT PRIMARY KEY,
|
|
9
|
-
name TEXT NOT NULL,
|
|
10
|
-
slug TEXT NOT NULL UNIQUE,
|
|
11
|
-
git_repository TEXT,
|
|
12
|
-
git_default_branch TEXT NOT NULL DEFAULT 'main',
|
|
13
|
-
pixel_threshold REAL NOT NULL DEFAULT 0.1,
|
|
14
|
-
max_diff_ratio REAL NOT NULL DEFAULT 0.01,
|
|
15
|
-
public_branch_regex TEXT,
|
|
16
|
-
storybook_meta TEXT,
|
|
17
|
-
execute_play INTEGER NOT NULL DEFAULT 0,
|
|
18
|
-
play_timeout_ms INTEGER NOT NULL DEFAULT 10000,
|
|
19
|
-
created_at TEXT NOT NULL,
|
|
20
|
-
updated_at TEXT NOT NULL
|
|
21
|
-
);
|
|
22
|
-
CREATE TABLE IF NOT EXISTS project_status_configs (
|
|
23
|
-
id TEXT PRIMARY KEY,
|
|
24
|
-
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
25
|
-
provider TEXT NOT NULL,
|
|
26
|
-
config TEXT NOT NULL,
|
|
27
|
-
token_encrypted TEXT NOT NULL,
|
|
28
|
-
created_at TEXT NOT NULL,
|
|
29
|
-
updated_at TEXT NOT NULL
|
|
30
|
-
);
|
|
31
|
-
CREATE TABLE IF NOT EXISTS builds (
|
|
32
|
-
id TEXT PRIMARY KEY,
|
|
33
|
-
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
34
|
-
git_sha TEXT NOT NULL,
|
|
35
|
-
git_branch TEXT NOT NULL,
|
|
36
|
-
is_default INTEGER NOT NULL DEFAULT 0,
|
|
37
|
-
author_email TEXT,
|
|
38
|
-
author_name TEXT,
|
|
39
|
-
message TEXT,
|
|
40
|
-
public INTEGER NOT NULL DEFAULT 0,
|
|
41
|
-
status TEXT NOT NULL DEFAULT 'pending',
|
|
42
|
-
snapshot_count INTEGER NOT NULL DEFAULT 0,
|
|
43
|
-
changed_count INTEGER NOT NULL DEFAULT 0,
|
|
44
|
-
approved_count INTEGER NOT NULL DEFAULT 0,
|
|
45
|
-
rejected_count INTEGER NOT NULL DEFAULT 0,
|
|
46
|
-
created_at TEXT NOT NULL,
|
|
47
|
-
updated_at TEXT NOT NULL
|
|
48
|
-
);
|
|
49
|
-
CREATE UNIQUE INDEX IF NOT EXISTS builds_project_gitsha_idx ON builds (project_id, git_sha);
|
|
50
|
-
CREATE INDEX IF NOT EXISTS builds_git_branch_idx ON builds (git_branch);
|
|
51
|
-
CREATE TABLE IF NOT EXISTS snapshots (
|
|
52
|
-
id TEXT PRIMARY KEY,
|
|
53
|
-
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
54
|
-
build_id TEXT NOT NULL REFERENCES builds(id) ON DELETE CASCADE,
|
|
55
|
-
story_id TEXT NOT NULL,
|
|
56
|
-
story_name TEXT NOT NULL,
|
|
57
|
-
story_title TEXT NOT NULL,
|
|
58
|
-
story_import_path TEXT,
|
|
59
|
-
viewport_name TEXT NOT NULL DEFAULT 'desktop',
|
|
60
|
-
viewport_width INTEGER NOT NULL DEFAULT 1280,
|
|
61
|
-
viewport_height INTEGER NOT NULL DEFAULT 720,
|
|
62
|
-
screenshot_path TEXT NOT NULL,
|
|
63
|
-
diff_path TEXT,
|
|
64
|
-
diff_pixels INTEGER,
|
|
65
|
-
diff_ratio REAL,
|
|
66
|
-
diff_passed INTEGER,
|
|
67
|
-
status TEXT NOT NULL DEFAULT 'pending',
|
|
68
|
-
reviewed_by TEXT,
|
|
69
|
-
reviewed_at TEXT,
|
|
70
|
-
created_at TEXT NOT NULL,
|
|
71
|
-
updated_at TEXT NOT NULL
|
|
72
|
-
);
|
|
73
|
-
CREATE UNIQUE INDEX IF NOT EXISTS snapshots_build_story_viewport_idx ON snapshots (build_id, story_id, viewport_name);
|
|
74
|
-
CREATE INDEX IF NOT EXISTS snapshots_build_id_idx ON snapshots (build_id);
|
|
75
|
-
CREATE TABLE IF NOT EXISTS baselines (
|
|
76
|
-
id TEXT PRIMARY KEY,
|
|
77
|
-
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
78
|
-
story_id TEXT NOT NULL,
|
|
79
|
-
viewport_name TEXT NOT NULL DEFAULT 'desktop',
|
|
80
|
-
branch TEXT NOT NULL,
|
|
81
|
-
snapshot_id TEXT,
|
|
82
|
-
screenshot_path TEXT NOT NULL,
|
|
83
|
-
created_at TEXT NOT NULL,
|
|
84
|
-
updated_at TEXT NOT NULL
|
|
85
|
-
);
|
|
86
|
-
CREATE UNIQUE INDEX IF NOT EXISTS baselines_project_story_viewport_branch_idx ON baselines (project_id, story_id, viewport_name, branch);
|
|
87
|
-
CREATE INDEX IF NOT EXISTS baselines_project_story_idx ON baselines (project_id, story_id);
|
|
88
|
-
CREATE TABLE IF NOT EXISTS comments (
|
|
89
|
-
id TEXT PRIMARY KEY,
|
|
90
|
-
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
91
|
-
build_id TEXT NOT NULL REFERENCES builds(id) ON DELETE CASCADE,
|
|
92
|
-
snapshot_id TEXT REFERENCES snapshots(id) ON DELETE CASCADE,
|
|
93
|
-
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
94
|
-
body TEXT NOT NULL,
|
|
95
|
-
parent_id TEXT,
|
|
96
|
-
resolved INTEGER NOT NULL DEFAULT 0,
|
|
97
|
-
created_at TEXT NOT NULL,
|
|
98
|
-
updated_at TEXT NOT NULL
|
|
99
|
-
);
|
|
100
|
-
CREATE INDEX IF NOT EXISTS comments_build_id_idx ON comments (build_id);
|
|
101
|
-
CREATE TABLE IF NOT EXISTS label_types (
|
|
102
|
-
id TEXT PRIMARY KEY,
|
|
103
|
-
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
104
|
-
key TEXT NOT NULL,
|
|
105
|
-
name TEXT NOT NULL,
|
|
106
|
-
link_template TEXT,
|
|
107
|
-
color TEXT,
|
|
108
|
-
created_at TEXT NOT NULL
|
|
109
|
-
);
|
|
110
|
-
CREATE UNIQUE INDEX IF NOT EXISTS label_types_project_key_idx ON label_types (project_id, key);
|
|
111
|
-
CREATE TABLE IF NOT EXISTS build_labels (
|
|
112
|
-
id TEXT PRIMARY KEY,
|
|
113
|
-
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
114
|
-
build_id TEXT NOT NULL REFERENCES builds(id) ON DELETE CASCADE,
|
|
115
|
-
type_key TEXT NOT NULL,
|
|
116
|
-
value TEXT NOT NULL,
|
|
117
|
-
created_at TEXT NOT NULL
|
|
118
|
-
);
|
|
119
|
-
CREATE UNIQUE INDEX IF NOT EXISTS build_labels_build_type_value_idx ON build_labels (build_id, type_key, value);
|
|
120
|
-
CREATE TABLE IF NOT EXISTS tokens (
|
|
121
|
-
id TEXT PRIMARY KEY,
|
|
122
|
-
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
123
|
-
name TEXT NOT NULL,
|
|
124
|
-
hash TEXT NOT NULL UNIQUE,
|
|
125
|
-
last_used_at TEXT,
|
|
126
|
-
created_at TEXT NOT NULL
|
|
127
|
-
);
|
|
128
|
-
CREATE TABLE IF NOT EXISTS webhooks (
|
|
129
|
-
id TEXT PRIMARY KEY,
|
|
130
|
-
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
131
|
-
url TEXT NOT NULL,
|
|
132
|
-
secret_encrypted TEXT NOT NULL,
|
|
133
|
-
events TEXT,
|
|
134
|
-
created_at TEXT NOT NULL,
|
|
135
|
-
updated_at TEXT NOT NULL
|
|
136
|
-
);
|
|
137
|
-
CREATE TABLE IF NOT EXISTS users (
|
|
138
|
-
id TEXT PRIMARY KEY,
|
|
139
|
-
email TEXT NOT NULL UNIQUE,
|
|
140
|
-
name TEXT NOT NULL,
|
|
141
|
-
avatar_url TEXT,
|
|
142
|
-
role TEXT NOT NULL DEFAULT 'member',
|
|
143
|
-
last_login_at TEXT,
|
|
144
|
-
created_at TEXT NOT NULL
|
|
145
|
-
);
|
|
146
|
-
CREATE TABLE IF NOT EXISTS project_members (
|
|
147
|
-
id TEXT PRIMARY KEY,
|
|
148
|
-
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
149
|
-
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
150
|
-
role TEXT NOT NULL DEFAULT 'viewer',
|
|
151
|
-
created_at TEXT NOT NULL
|
|
152
|
-
);
|
|
153
|
-
CREATE UNIQUE INDEX IF NOT EXISTS project_members_project_user_idx ON project_members (project_id, user_id);
|
|
154
|
-
`;
|
|
155
|
-
//#endregion
|
|
156
|
-
export { DDL };
|
|
157
|
-
|
|
158
|
-
//# sourceMappingURL=ddl.mjs.map
|
package/dist/ddl.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
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_encrypted 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/index.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
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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"}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import "node:path";
|
|
2
|
-
import "node:url";
|
|
3
|
-
import.meta.url;
|
|
4
|
-
import { getStore } from "../store.mjs";
|
|
5
|
-
//#region src/middleware/auth-gate.ts
|
|
6
|
-
/** Resolve the request user from the auth adapter (null when auth is off or a token is used). */
|
|
7
|
-
async function resolveRequestUser(c, auth) {
|
|
8
|
-
if (!auth) return null;
|
|
9
|
-
if (c.req.header("authorization")) return null;
|
|
10
|
-
return await auth.check(c.req.raw);
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Gate the server-rendered UI behind auth (ADR 0008): unauthenticated HTML
|
|
14
|
-
* requests redirect to login. API/auth routes answer 401/403 themselves, and
|
|
15
|
-
* published-Storybook routes enforce their own auth (public builds are
|
|
16
|
-
* viewable without a session, ADR 0011).
|
|
17
|
-
*/
|
|
18
|
-
function authGate() {
|
|
19
|
-
return async (c, next) => {
|
|
20
|
-
const { user, authEnabled: isAuthEnabled } = getStore();
|
|
21
|
-
const { path } = c.req;
|
|
22
|
-
if (!isAuthEnabled || user || path.startsWith("/api/") || path.startsWith("/auth/") || path.startsWith("/assets/") || path.startsWith("/projects/") && path.includes("/storybook")) {
|
|
23
|
-
await next();
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
if (c.req.header("HX-Request") === "true") {
|
|
27
|
-
c.header("HX-Redirect", "/auth/login");
|
|
28
|
-
return c.body(null, 204);
|
|
29
|
-
}
|
|
30
|
-
return c.redirect("/auth/login", 302);
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
//#endregion
|
|
34
|
-
export { authGate, resolveRequestUser };
|
|
35
|
-
|
|
36
|
-
//# sourceMappingURL=auth-gate.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"auth-gate.mjs","names":[],"sources":["../../src/middleware/auth-gate.ts"],"sourcesContent":["import type { Context, Next } from \"hono\";\nimport type { AuthAdapter, AuthUser } from \"../adapters/auth.ts\";\nimport { getStore } from \"../store.ts\";\n\n/** Resolve the request user from the auth adapter (null when auth is off or a token is used). */\nexport async function resolveRequestUser(\n c: Pick<Context, \"req\">,\n auth?: AuthAdapter,\n): Promise<AuthUser | null> {\n if (!auth) {\n return null;\n }\n if (c.req.header(\"authorization\")) {\n return null;\n }\n return await auth.check(c.req.raw);\n}\n\n/**\n * Gate the server-rendered UI behind auth (ADR 0008): unauthenticated HTML\n * requests redirect to login. API/auth routes answer 401/403 themselves, and\n * published-Storybook routes enforce their own auth (public builds are\n * viewable without a session, ADR 0011).\n */\nexport function authGate() {\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 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 (path.startsWith(\"/projects/\") && path.includes(\"/storybook\"))\n ) {\n await next();\n return;\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"],"mappings":";;;;;;AAKA,eAAsB,mBACpB,GACA,MAC0B;CAC1B,IAAI,CAAC,MACH,OAAO;CAET,IAAI,EAAE,IAAI,OAAO,eAAe,GAC9B,OAAO;CAET,OAAO,MAAM,KAAK,MAAM,EAAE,IAAI,GAAG;AACnC;;;;;;;AAQA,SAAgB,WAAW;CAEzB,OAAO,OAAO,GAAY,SAAyC;EACjE,MAAM,EAAE,MAAM,aAAa,kBAAkB,SAAS;EACtD,MAAM,EAAE,SAAS,EAAE;EACnB,IACE,CAAC,iBACD,QACA,KAAK,WAAW,OAAO,KACvB,KAAK,WAAW,QAAQ,KACxB,KAAK,WAAW,UAAU,KACzB,KAAK,WAAW,YAAY,KAAK,KAAK,SAAS,YAAY,GAC5D;GACA,MAAM,KAAK;GACX;EACF;EACA,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;AACF"}
|
package/dist/middleware/csrf.mjs
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import "node:path";
|
|
2
|
-
import "node:url";
|
|
3
|
-
import.meta.url;
|
|
4
|
-
import { createHash, randomBytes } from "node:crypto";
|
|
5
|
-
//#region src/middleware/csrf.ts
|
|
6
|
-
const CSRF_SECRET = process.env["CSRF_SECRET"] ?? randomBytes(32).toString("hex");
|
|
7
|
-
function generateToken(sessionId) {
|
|
8
|
-
const payload = `${sessionId}:${Date.now().toString(36)}`;
|
|
9
|
-
return `${payload}:${createHash("sha256").update(`${CSRF_SECRET}:${payload}`).digest("hex").slice(0, 16)}`;
|
|
10
|
-
}
|
|
11
|
-
function verifyToken(token, sessionId) {
|
|
12
|
-
const parts = token.split(":");
|
|
13
|
-
if (parts.length !== 3) return false;
|
|
14
|
-
const [payloadTimestamp, signature] = parts.slice(1);
|
|
15
|
-
if (!payloadTimestamp || !signature) return false;
|
|
16
|
-
if (signature !== createHash("sha256").update(`${CSRF_SECRET}:${sessionId}:${payloadTimestamp}`).digest("hex").slice(0, 16)) return false;
|
|
17
|
-
const timestamp = Number.parseInt(payloadTimestamp, 36);
|
|
18
|
-
const age = Date.now() - timestamp;
|
|
19
|
-
return age > 0 && age < 864e5;
|
|
20
|
-
}
|
|
21
|
-
/** Hono middleware issuing CSRF tokens on safe methods and verifying them on writes. */
|
|
22
|
-
function csrf() {
|
|
23
|
-
return async (c, next) => {
|
|
24
|
-
const { method } = c.req;
|
|
25
|
-
if (method === "GET" || method === "HEAD" || method === "OPTIONS") {
|
|
26
|
-
const token = generateToken(c.req.header("session-id") ?? "default");
|
|
27
|
-
c.header("X-CSRF-Token", token);
|
|
28
|
-
await next();
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
const sessionId = c.req.header("session-id") ?? "default";
|
|
32
|
-
const token = c.req.header("x-csrf-token") ?? c.req.query("csrf_token");
|
|
33
|
-
if (!token || !verifyToken(token, sessionId)) return c.json({ error: "Invalid CSRF token" }, 403);
|
|
34
|
-
await next();
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
//#endregion
|
|
38
|
-
export { csrf };
|
|
39
|
-
|
|
40
|
-
//# sourceMappingURL=csrf.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"csrf.mjs","names":[],"sources":["../../src/middleware/csrf.ts"],"sourcesContent":["import type { Context, Next } from \"hono\";\nimport { createHash, randomBytes } from \"node:crypto\";\n\nconst CSRF_SECRET = process.env[\"CSRF_SECRET\"] ?? randomBytes(32).toString(\"hex\");\n\nfunction generateToken(sessionId: string): string {\n const timestamp = Date.now().toString(36);\n const payload = `${sessionId}:${timestamp}`;\n const signature = createHash(\"sha256\")\n .update(`${CSRF_SECRET}:${payload}`)\n .digest(\"hex\")\n .slice(0, 16);\n return `${payload}:${signature}`;\n}\n\nfunction verifyToken(token: string, sessionId: string): boolean {\n const parts = token.split(\":\");\n if (parts.length !== 3) {\n return false;\n }\n const [payloadTimestamp, signature] = parts.slice(1);\n if (!payloadTimestamp || !signature) {\n return false;\n }\n const expectedSignature = createHash(\"sha256\")\n .update(`${CSRF_SECRET}:${sessionId}:${payloadTimestamp}`)\n .digest(\"hex\")\n .slice(0, 16);\n if (signature !== expectedSignature) {\n return false;\n }\n const timestamp = Number.parseInt(payloadTimestamp, 36);\n const age = Date.now() - timestamp;\n return age > 0 && age < 24 * 60 * 60 * 1000;\n}\n\n/** Hono middleware issuing CSRF tokens on safe methods and verifying them on writes. */\nexport function csrf() {\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 const { method } = c.req;\n if (method === \"GET\" || method === \"HEAD\" || method === \"OPTIONS\") {\n const sessionId = c.req.header(\"session-id\") ?? \"default\";\n const token = generateToken(sessionId);\n c.header(\"X-CSRF-Token\", token);\n await next();\n return;\n }\n const sessionId = c.req.header(\"session-id\") ?? \"default\";\n const token = c.req.header(\"x-csrf-token\") ?? c.req.query(\"csrf_token\");\n if (!token || !verifyToken(token, sessionId)) {\n return c.json({ error: \"Invalid CSRF token\" }, 403);\n }\n await next();\n };\n}\n\n/** Generate a CSRF token for the given session. */\nexport function getCsrfToken(sessionId: string): string {\n return generateToken(sessionId);\n}\n"],"mappings":";;;;;AAGA,MAAM,cAAc,QAAQ,IAAI,kBAAkB,YAAY,EAAE,CAAC,CAAC,SAAS,KAAK;AAEhF,SAAS,cAAc,WAA2B;CAEhD,MAAM,UAAU,GAAG,UAAU,GADX,KAAK,IAAI,CAAC,CAAC,SAAS,EACE;CAKxC,OAAO,GAAG,QAAQ,GAJA,WAAW,QAAQ,CAAC,CACnC,OAAO,GAAG,YAAY,GAAG,SAAS,CAAC,CACnC,OAAO,KAAK,CAAC,CACb,MAAM,GAAG,EACiB;AAC/B;AAEA,SAAS,YAAY,OAAe,WAA4B;CAC9D,MAAM,QAAQ,MAAM,MAAM,GAAG;CAC7B,IAAI,MAAM,WAAW,GACnB,OAAO;CAET,MAAM,CAAC,kBAAkB,aAAa,MAAM,MAAM,CAAC;CACnD,IAAI,CAAC,oBAAoB,CAAC,WACxB,OAAO;CAMT,IAAI,cAJsB,WAAW,QAAQ,CAAC,CAC3C,OAAO,GAAG,YAAY,GAAG,UAAU,GAAG,kBAAkB,CAAC,CACzD,OAAO,KAAK,CAAC,CACb,MAAM,GAAG,EACsB,GAChC,OAAO;CAET,MAAM,YAAY,OAAO,SAAS,kBAAkB,EAAE;CACtD,MAAM,MAAM,KAAK,IAAI,IAAI;CACzB,OAAO,MAAM,KAAK,MAAM;AAC1B;;AAGA,SAAgB,OAAO;CAErB,OAAO,OAAO,GAAY,SAAyC;EACjE,MAAM,EAAE,WAAW,EAAE;EACrB,IAAI,WAAW,SAAS,WAAW,UAAU,WAAW,WAAW;GAEjE,MAAM,QAAQ,cADI,EAAE,IAAI,OAAO,YAAY,KAAK,SACX;GACrC,EAAE,OAAO,gBAAgB,KAAK;GAC9B,MAAM,KAAK;GACX;EACF;EACA,MAAM,YAAY,EAAE,IAAI,OAAO,YAAY,KAAK;EAChD,MAAM,QAAQ,EAAE,IAAI,OAAO,cAAc,KAAK,EAAE,IAAI,MAAM,YAAY;EACtE,IAAI,CAAC,SAAS,CAAC,YAAY,OAAO,SAAS,GACzC,OAAO,EAAE,KAAK,EAAE,OAAO,qBAAqB,GAAG,GAAG;EAEpD,MAAM,KAAK;CACb;AACF"}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import "node:path";
|
|
2
|
-
import "node:url";
|
|
3
|
-
import.meta.url;
|
|
4
|
-
//#region src/middleware/rate-limit.ts
|
|
5
|
-
const stores = /* @__PURE__ */ new Map();
|
|
6
|
-
function cleanup() {
|
|
7
|
-
const now = Date.now();
|
|
8
|
-
for (const [key, entry] of stores) if (now > entry.resetAt) stores.delete(key);
|
|
9
|
-
}
|
|
10
|
-
setInterval(cleanup, 6e4).unref();
|
|
11
|
-
/** Hono middleware limiting requests per key within a sliding window. */
|
|
12
|
-
function rateLimit(options) {
|
|
13
|
-
const { windowMs, max, keyGenerator } = options;
|
|
14
|
-
return async (c, next) => {
|
|
15
|
-
const key = keyGenerator ? keyGenerator(c) : c.req.header("x-forwarded-for") ?? "anonymous";
|
|
16
|
-
const now = Date.now();
|
|
17
|
-
let entry = stores.get(key);
|
|
18
|
-
if (!entry || now > entry.resetAt) {
|
|
19
|
-
entry = {
|
|
20
|
-
count: 0,
|
|
21
|
-
resetAt: now + windowMs
|
|
22
|
-
};
|
|
23
|
-
stores.set(key, entry);
|
|
24
|
-
}
|
|
25
|
-
entry.count += 1;
|
|
26
|
-
if (entry.count > max) {
|
|
27
|
-
const retryAfter = Math.ceil((entry.resetAt - now) / 1e3);
|
|
28
|
-
c.header("Retry-After", String(retryAfter));
|
|
29
|
-
return c.json({ error: "Too many requests" }, 429);
|
|
30
|
-
}
|
|
31
|
-
c.header("X-RateLimit-Limit", String(max));
|
|
32
|
-
c.header("X-RateLimit-Remaining", String(Math.max(0, max - entry.count)));
|
|
33
|
-
await next();
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
//#endregion
|
|
37
|
-
export { rateLimit };
|
|
38
|
-
|
|
39
|
-
//# sourceMappingURL=rate-limit.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"rate-limit.mjs","names":[],"sources":["../../src/middleware/rate-limit.ts"],"sourcesContent":["import type { Context, Next } from \"hono\";\n\ninterface RateLimitEntry {\n count: number;\n resetAt: number;\n}\n\ninterface RateLimitOptions {\n windowMs: number;\n max: number;\n keyGenerator?: (c: Context) => string;\n}\n\nconst stores = new Map<string, RateLimitEntry>();\n\nfunction cleanup(): void {\n const now = Date.now();\n for (const [key, entry] of stores) {\n if (now > entry.resetAt) {\n stores.delete(key);\n }\n }\n}\n\nsetInterval(cleanup, 60_000).unref();\n\n/** Hono middleware limiting requests per key within a sliding window. */\nexport function rateLimit(options: RateLimitOptions) {\n const { windowMs, max, keyGenerator } = options;\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 const key = keyGenerator ? keyGenerator(c) : (c.req.header(\"x-forwarded-for\") ?? \"anonymous\");\n const now = Date.now();\n let entry = stores.get(key);\n if (!entry || now > entry.resetAt) {\n entry = { count: 0, resetAt: now + windowMs };\n stores.set(key, entry);\n }\n entry.count += 1;\n if (entry.count > max) {\n const retryAfter = Math.ceil((entry.resetAt - now) / 1000);\n c.header(\"Retry-After\", String(retryAfter));\n return c.json({ error: \"Too many requests\" }, 429);\n }\n c.header(\"X-RateLimit-Limit\", String(max));\n c.header(\"X-RateLimit-Remaining\", String(Math.max(0, max - entry.count)));\n await next();\n };\n}\n"],"mappings":";;;;AAaA,MAAM,yBAAS,IAAI,IAA4B;AAE/C,SAAS,UAAgB;CACvB,MAAM,MAAM,KAAK,IAAI;CACrB,KAAK,MAAM,CAAC,KAAK,UAAU,QACzB,IAAI,MAAM,MAAM,SACd,OAAO,OAAO,GAAG;AAGvB;AAEA,YAAY,SAAS,GAAM,CAAC,CAAC,MAAM;;AAGnC,SAAgB,UAAU,SAA2B;CACnD,MAAM,EAAE,UAAU,KAAK,iBAAiB;CAExC,OAAO,OAAO,GAAY,SAAyC;EACjE,MAAM,MAAM,eAAe,aAAa,CAAC,IAAK,EAAE,IAAI,OAAO,iBAAiB,KAAK;EACjF,MAAM,MAAM,KAAK,IAAI;EACrB,IAAI,QAAQ,OAAO,IAAI,GAAG;EAC1B,IAAI,CAAC,SAAS,MAAM,MAAM,SAAS;GACjC,QAAQ;IAAE,OAAO;IAAG,SAAS,MAAM;GAAS;GAC5C,OAAO,IAAI,KAAK,KAAK;EACvB;EACA,MAAM,SAAS;EACf,IAAI,MAAM,QAAQ,KAAK;GACrB,MAAM,aAAa,KAAK,MAAM,MAAM,UAAU,OAAO,GAAI;GACzD,EAAE,OAAO,eAAe,OAAO,UAAU,CAAC;GAC1C,OAAO,EAAE,KAAK,EAAE,OAAO,oBAAoB,GAAG,GAAG;EACnD;EACA,EAAE,OAAO,qBAAqB,OAAO,GAAG,CAAC;EACzC,EAAE,OAAO,yBAAyB,OAAO,KAAK,IAAI,GAAG,MAAM,MAAM,KAAK,CAAC,CAAC;EACxE,MAAM,KAAK;CACb;AACF"}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import "node:path";
|
|
2
|
-
import "node:url";
|
|
3
|
-
import.meta.url;
|
|
4
|
-
//#region src/middleware/request-log.ts
|
|
5
|
-
/** Hono middleware logging request start/end with duration. */
|
|
6
|
-
function requestLogging(logger) {
|
|
7
|
-
return async (c, next) => {
|
|
8
|
-
const started = performance.now();
|
|
9
|
-
const id = c.get("requestId");
|
|
10
|
-
logger.info({
|
|
11
|
-
reqId: id,
|
|
12
|
-
method: c.req.method,
|
|
13
|
-
url: c.req.path
|
|
14
|
-
}, "request start");
|
|
15
|
-
await next();
|
|
16
|
-
logger.info({
|
|
17
|
-
reqId: id,
|
|
18
|
-
method: c.req.method,
|
|
19
|
-
url: c.req.path,
|
|
20
|
-
status: c.res.status,
|
|
21
|
-
durationMs: Math.round(performance.now() - started)
|
|
22
|
-
}, "request end");
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
//#endregion
|
|
26
|
-
export { requestLogging };
|
|
27
|
-
|
|
28
|
-
//# sourceMappingURL=request-log.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"request-log.mjs","names":[],"sources":["../../src/middleware/request-log.ts"],"sourcesContent":["import type { Context, Next } from \"hono\";\nimport type { Logger } from \"../logger.ts\";\n\n/** Hono middleware logging request start/end with duration. */\nexport function requestLogging(logger: Logger) {\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 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"],"mappings":";;;;;AAIA,SAAgB,eAAe,QAAgB;CAE7C,OAAO,OAAO,GAAY,SAAyC;EACjE,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;AACF"}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import "node:path";
|
|
2
|
-
import "node:url";
|
|
3
|
-
import.meta.url;
|
|
4
|
-
import { runWithStore } from "../store.mjs";
|
|
5
|
-
//#region src/middleware/store-scope.ts
|
|
6
|
-
/** Hono middleware running downstream handlers inside the request store. */
|
|
7
|
-
function storeScope(deps) {
|
|
8
|
-
return async (c, next) => {
|
|
9
|
-
const user = await deps.resolveUser(c);
|
|
10
|
-
await runWithStore({
|
|
11
|
-
db: deps.db,
|
|
12
|
-
storage: deps.storage,
|
|
13
|
-
config: deps.config,
|
|
14
|
-
ui: deps.ui,
|
|
15
|
-
logger: deps.logger,
|
|
16
|
-
user,
|
|
17
|
-
authEnabled: deps.authEnabled,
|
|
18
|
-
enqueueCapture: deps.enqueueCapture,
|
|
19
|
-
captureQueue: deps.captureQueue,
|
|
20
|
-
gitHosts: deps.gitHosts
|
|
21
|
-
}, async () => {
|
|
22
|
-
await next();
|
|
23
|
-
});
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
//#endregion
|
|
27
|
-
export { storeScope };
|
|
28
|
-
|
|
29
|
-
//# sourceMappingURL=store-scope.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"store-scope.mjs","names":[],"sources":["../../src/middleware/store-scope.ts"],"sourcesContent":["import type { Context, Next } from \"hono\";\nimport type { AuthUser } from \"../adapters/auth.ts\";\nimport type { CaptureQueue } from \"../adapters/capture-queue.ts\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { GitHostProvider } from \"../adapters/git-host/index.ts\";\nimport type { StorageAdapter } from \"../adapters/storage.ts\";\nimport type { ShelfConfig, UIConfig } from \"../config.ts\";\nimport type { Logger } from \"../logger.ts\";\nimport { runWithStore } from \"../store.ts\";\n\n/** Dependencies scoped into the request store for router handlers. */\nexport interface StoreScopeDeps {\n db: DatabaseAdapter;\n storage: StorageAdapter;\n config: ShelfConfig;\n ui: UIConfig;\n logger: Logger;\n authEnabled: boolean;\n enqueueCapture?: (buildId: string, reqId?: string) => Promise<void>;\n captureQueue: CaptureQueue | null;\n gitHosts: GitHostProvider[];\n resolveUser: (c: Context) => Promise<AuthUser | null>;\n}\n\n/** Hono middleware running downstream handlers inside the request store. */\nexport function storeScope(deps: StoreScopeDeps) {\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 const user = await deps.resolveUser(c);\n await runWithStore(\n {\n db: deps.db,\n storage: deps.storage,\n config: deps.config,\n ui: deps.ui,\n logger: deps.logger,\n user,\n authEnabled: deps.authEnabled,\n enqueueCapture: deps.enqueueCapture,\n captureQueue: deps.captureQueue,\n gitHosts: deps.gitHosts,\n },\n async () => {\n await next();\n },\n );\n };\n}\n"],"mappings":";;;;;;AAyBA,SAAgB,WAAW,MAAsB;CAE/C,OAAO,OAAO,GAAY,SAAyC;EACjE,MAAM,OAAO,MAAM,KAAK,YAAY,CAAC;EACrC,MAAM,aACJ;GACE,IAAI,KAAK;GACT,SAAS,KAAK;GACd,QAAQ,KAAK;GACb,IAAI,KAAK;GACT,QAAQ,KAAK;GACb;GACA,aAAa,KAAK;GAClB,gBAAgB,KAAK;GACrB,cAAc,KAAK;GACnB,UAAU,KAAK;EACjB,GACA,YAAY;GACV,MAAM,KAAK;EACb,CACF;CACF;AACF"}
|