@storyshelf/app 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -4
- package/dist/app-types.d.mts +1 -3
- package/dist/asset-manifest.mjs +13 -5
- package/dist/asset-manifest.mjs.map +1 -1
- package/dist/assets/{htmx.min.js → htmx.mjs} +10 -1
- package/dist/assets/htmx.mjs.map +1 -0
- package/dist/assets/mark.mjs +10 -0
- package/dist/assets/mark.mjs.map +1 -0
- package/dist/boot-tasks.mjs +80 -0
- package/dist/boot-tasks.mjs.map +1 -0
- package/dist/build-state-flags.mjs +18 -0
- package/dist/build-state-flags.mjs.map +1 -0
- package/dist/build-state-setup.mjs +75 -0
- package/dist/build-state-setup.mjs.map +1 -0
- package/dist/capture-alerts.mjs +46 -0
- package/dist/capture-alerts.mjs.map +1 -0
- package/dist/capture-process.mjs +59 -0
- package/dist/capture-process.mjs.map +1 -0
- package/dist/capture-setup.mjs +95 -117
- package/dist/capture-setup.mjs.map +1 -1
- package/dist/connections.mjs +34 -0
- package/dist/connections.mjs.map +1 -0
- package/dist/consumer-setup.mjs +58 -0
- package/dist/consumer-setup.mjs.map +1 -0
- package/dist/credentials.mjs +3 -2
- package/dist/credentials.mjs.map +1 -1
- package/dist/git-status.mjs +46 -0
- package/dist/git-status.mjs.map +1 -0
- package/dist/hidden-data.mjs +24 -0
- package/dist/hidden-data.mjs.map +1 -0
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +202 -124
- package/dist/index.mjs.map +1 -1
- package/dist/insights/budget.mjs +49 -0
- package/dist/insights/budget.mjs.map +1 -0
- package/dist/insights/deps.mjs +21 -0
- package/dist/insights/deps.mjs.map +1 -0
- package/dist/insights/errors.mjs +22 -0
- package/dist/insights/errors.mjs.map +1 -0
- package/dist/insights/health-panel.mjs +40 -0
- package/dist/insights/health-panel.mjs.map +1 -0
- package/dist/insights/job.mjs +103 -0
- package/dist/insights/job.mjs.map +1 -0
- package/dist/insights/load-health.mjs +59 -0
- package/dist/insights/load-health.mjs.map +1 -0
- package/dist/insights/load-triage.mjs +98 -0
- package/dist/insights/load-triage.mjs.map +1 -0
- package/dist/insights/panel.mjs +28 -0
- package/dist/insights/panel.mjs.map +1 -0
- package/dist/insights/request.mjs +113 -0
- package/dist/insights/request.mjs.map +1 -0
- package/dist/insights/stale.mjs +21 -0
- package/dist/insights/stale.mjs.map +1 -0
- package/dist/insights/usage-report.mjs +60 -0
- package/dist/insights/usage-report.mjs.map +1 -0
- package/dist/insights/view.mjs +48 -0
- package/dist/insights/view.mjs.map +1 -0
- package/dist/job-canceller.mjs +14 -0
- package/dist/job-canceller.mjs.map +1 -0
- package/dist/job-queue.mjs +51 -0
- package/dist/job-queue.mjs.map +1 -0
- package/dist/jobs-setup.mjs +132 -0
- package/dist/jobs-setup.mjs.map +1 -0
- package/dist/lifecycle.mjs +109 -19
- package/dist/lifecycle.mjs.map +1 -1
- package/dist/middleware/auth-gate.mjs +4 -2
- package/dist/middleware/auth-gate.mjs.map +1 -1
- package/dist/middleware/rate-limit.mjs +41 -33
- package/dist/middleware/rate-limit.mjs.map +1 -1
- package/dist/middleware/request-log.mjs +3 -2
- package/dist/middleware/request-log.mjs.map +1 -1
- package/dist/middleware/require-review.mjs +51 -0
- package/dist/middleware/require-review.mjs.map +1 -0
- package/dist/middleware/require-testing.mjs +41 -0
- package/dist/middleware/require-testing.mjs.map +1 -0
- package/dist/middleware/setup-gate.mjs.map +1 -1
- package/dist/middleware/store-scope.mjs +12 -2
- package/dist/middleware/store-scope.mjs.map +1 -1
- package/dist/notifications-pause.mjs +33 -0
- package/dist/notifications-pause.mjs.map +1 -0
- package/dist/notify.d.mts +2 -1
- package/dist/notify.mjs +78 -39
- package/dist/notify.mjs.map +1 -1
- package/dist/pages/a11y-violations.mjs +116 -0
- package/dist/pages/a11y-violations.mjs.map +1 -0
- package/dist/pages/admin-ai.mjs +78 -0
- package/dist/pages/admin-ai.mjs.map +1 -0
- package/dist/pages/admin-system-origin.mjs +123 -0
- package/dist/pages/admin-system-origin.mjs.map +1 -0
- package/dist/pages/admin-system-targets.mjs +53 -0
- package/dist/pages/admin-system-targets.mjs.map +1 -0
- package/dist/pages/admin-system.mjs +63 -13
- package/dist/pages/admin-system.mjs.map +1 -1
- package/dist/pages/build-detail.mjs +104 -76
- package/dist/pages/build-detail.mjs.map +1 -1
- package/dist/pages/build-diff-actions.mjs +87 -33
- package/dist/pages/build-diff-actions.mjs.map +1 -1
- package/dist/pages/build-diff-comments.mjs +16 -14
- package/dist/pages/build-diff-comments.mjs.map +1 -1
- package/dist/pages/build-diff-environment.mjs +82 -0
- package/dist/pages/build-diff-environment.mjs.map +1 -0
- package/dist/pages/build-diff-header.mjs +22 -4
- package/dist/pages/build-diff-header.mjs.map +1 -1
- package/dist/pages/build-diff-insight.mjs +113 -0
- package/dist/pages/build-diff-insight.mjs.map +1 -0
- package/dist/pages/build-diff-model.mjs +55 -10
- package/dist/pages/build-diff-model.mjs.map +1 -1
- package/dist/pages/build-diff-nav.mjs +28 -12
- package/dist/pages/build-diff-nav.mjs.map +1 -1
- package/dist/pages/build-diff-shortcuts.mjs +5 -1
- package/dist/pages/build-diff-shortcuts.mjs.map +1 -1
- package/dist/pages/build-diff-usage.mjs +63 -0
- package/dist/pages/build-diff-usage.mjs.map +1 -0
- package/dist/pages/build-diff-viewer.mjs +62 -5
- package/dist/pages/build-diff-viewer.mjs.map +1 -1
- package/dist/pages/build-diff.mjs +34 -13
- package/dist/pages/build-diff.mjs.map +1 -1
- package/dist/pages/build-tests.mjs +74 -0
- package/dist/pages/build-tests.mjs.map +1 -0
- package/dist/pages/component-detail.mjs +249 -0
- package/dist/pages/component-detail.mjs.map +1 -0
- package/dist/pages/component-stories.mjs +168 -0
- package/dist/pages/component-stories.mjs.map +1 -0
- package/dist/pages/compute-jobs.mjs +13 -26
- package/dist/pages/compute-jobs.mjs.map +1 -1
- package/dist/pages/design-links.mjs +182 -0
- package/dist/pages/design-links.mjs.map +1 -0
- package/dist/pages/environment-change.mjs +53 -0
- package/dist/pages/environment-change.mjs.map +1 -0
- package/dist/pages/health-panel.mjs +126 -0
- package/dist/pages/health-panel.mjs.map +1 -0
- package/dist/pages/library-grid.mjs +105 -0
- package/dist/pages/library-grid.mjs.map +1 -0
- package/dist/pages/library-model.mjs +47 -0
- package/dist/pages/library-model.mjs.map +1 -0
- package/dist/pages/library-snapshots.mjs +130 -0
- package/dist/pages/library-snapshots.mjs.map +1 -0
- package/dist/pages/library.mjs +112 -218
- package/dist/pages/library.mjs.map +1 -1
- package/dist/pages/notifications-paused.mjs +22 -0
- package/dist/pages/notifications-paused.mjs.map +1 -0
- package/dist/pages/profile.mjs +53 -48
- package/dist/pages/profile.mjs.map +1 -1
- package/dist/pages/project-builds-table.mjs +2 -2
- package/dist/pages/project-builds-table.mjs.map +1 -1
- package/dist/pages/project-builds.mjs +51 -21
- package/dist/pages/project-builds.mjs.map +1 -1
- package/dist/pages/project-settings.mjs +51 -12
- package/dist/pages/project-settings.mjs.map +1 -1
- package/dist/pages/projects-card.mjs +14 -1
- package/dist/pages/projects-card.mjs.map +1 -1
- package/dist/pages/projects.mjs +6 -2
- package/dist/pages/projects.mjs.map +1 -1
- package/dist/pages/review-changeset.mjs +116 -0
- package/dist/pages/review-changeset.mjs.map +1 -0
- package/dist/pages/review-comments.mjs +103 -0
- package/dist/pages/review-comments.mjs.map +1 -0
- package/dist/pages/review-verdict.mjs +102 -0
- package/dist/pages/review-verdict.mjs.map +1 -0
- package/dist/pages/settings-ai.mjs +69 -0
- package/dist/pages/settings-ai.mjs.map +1 -0
- package/dist/pages/settings-capture.mjs +124 -0
- package/dist/pages/settings-capture.mjs.map +1 -0
- package/dist/pages/settings-design-credential.mjs +99 -0
- package/dist/pages/settings-design-credential.mjs.map +1 -0
- package/dist/pages/settings-design-links.mjs +179 -0
- package/dist/pages/settings-design-links.mjs.map +1 -0
- package/dist/pages/settings-design.mjs +48 -0
- package/dist/pages/settings-design.mjs.map +1 -0
- package/dist/pages/settings-general.mjs +46 -21
- package/dist/pages/settings-general.mjs.map +1 -1
- package/dist/pages/settings-labels.mjs +43 -2
- package/dist/pages/settings-labels.mjs.map +1 -1
- package/dist/pages/settings-links.mjs +112 -0
- package/dist/pages/settings-links.mjs.map +1 -0
- package/dist/pages/settings-notifications-git.mjs +128 -0
- package/dist/pages/settings-notifications-git.mjs.map +1 -0
- package/dist/pages/settings-notifications.mjs +79 -42
- package/dist/pages/settings-notifications.mjs.map +1 -1
- package/dist/pages/settings-tests.mjs +33 -2
- package/dist/pages/settings-tests.mjs.map +1 -1
- package/dist/pages/target-choices.mjs +39 -0
- package/dist/pages/target-choices.mjs.map +1 -0
- package/dist/publish-setup.mjs +89 -0
- package/dist/publish-setup.mjs.map +1 -0
- package/dist/retention-timer.mjs +2 -1
- package/dist/retention-timer.mjs.map +1 -1
- package/dist/review-events.mjs +48 -0
- package/dist/review-events.mjs.map +1 -0
- package/dist/routers/admin-pages.mjs +45 -3
- package/dist/routers/admin-pages.mjs.map +1 -1
- package/dist/routers/admin-public-origin.mjs +85 -0
- package/dist/routers/admin-public-origin.mjs.map +1 -0
- package/dist/routers/assets.mjs +9 -2
- package/dist/routers/assets.mjs.map +1 -1
- package/dist/routers/auth.mjs +2 -2
- package/dist/routers/auth.mjs.map +1 -1
- package/dist/routers/baseline-guard.mjs +2 -2
- package/dist/routers/baseline-guard.mjs.map +1 -1
- package/dist/routers/baselines.handlers.mjs +64 -0
- package/dist/routers/baselines.handlers.mjs.map +1 -0
- package/dist/routers/baselines.mjs +125 -0
- package/dist/routers/baselines.mjs.map +1 -0
- package/dist/routers/build-cancel.mjs +68 -0
- package/dist/routers/build-cancel.mjs.map +1 -0
- package/dist/routers/build-changeset.mjs +70 -0
- package/dist/routers/build-changeset.mjs.map +1 -0
- package/dist/routers/build-reviews.handlers.mjs +86 -0
- package/dist/routers/build-reviews.handlers.mjs.map +1 -0
- package/dist/routers/build-reviews.mjs +74 -0
- package/dist/routers/build-reviews.mjs.map +1 -0
- package/dist/routers/builds.handlers.mjs +46 -55
- package/dist/routers/builds.handlers.mjs.map +1 -1
- package/dist/routers/builds.mjs +71 -17
- package/dist/routers/builds.mjs.map +1 -1
- package/dist/routers/capture-config.mjs +35 -0
- package/dist/routers/capture-config.mjs.map +1 -0
- package/dist/routers/comments.mjs +41 -15
- package/dist/routers/comments.mjs.map +1 -1
- package/dist/routers/component-catalog.handlers.mjs +71 -0
- package/dist/routers/component-catalog.handlers.mjs.map +1 -0
- package/dist/routers/component.handlers.mjs +121 -0
- package/dist/routers/component.handlers.mjs.map +1 -0
- package/dist/routers/connections.mjs +118 -0
- package/dist/routers/connections.mjs.map +1 -0
- package/dist/routers/design-connect.handlers.mjs +44 -0
- package/dist/routers/design-connect.handlers.mjs.map +1 -0
- package/dist/routers/design.handlers.mjs +114 -0
- package/dist/routers/design.handlers.mjs.map +1 -0
- package/dist/routers/health-report.mjs +2 -3
- package/dist/routers/health-report.mjs.map +1 -1
- package/dist/routers/health.mjs +4 -3
- package/dist/routers/health.mjs.map +1 -1
- package/dist/routers/helpers.mjs +19 -3
- package/dist/routers/helpers.mjs.map +1 -1
- package/dist/routers/insights-ui.mjs +64 -0
- package/dist/routers/insights-ui.mjs.map +1 -0
- package/dist/routers/insights.handlers.mjs +45 -0
- package/dist/routers/insights.handlers.mjs.map +1 -0
- package/dist/routers/insights.mjs +189 -0
- package/dist/routers/insights.mjs.map +1 -0
- package/dist/routers/links.handlers.mjs +80 -0
- package/dist/routers/links.handlers.mjs.map +1 -0
- package/dist/routers/links.mjs +139 -0
- package/dist/routers/links.mjs.map +1 -0
- package/dist/routers/media.mjs +18 -4
- package/dist/routers/media.mjs.map +1 -1
- package/dist/routers/members.mjs +2 -1
- package/dist/routers/members.mjs.map +1 -1
- package/dist/routers/notification-channels.mjs +17 -23
- package/dist/routers/notification-channels.mjs.map +1 -1
- package/dist/routers/oauth-state.mjs +45 -0
- package/dist/routers/oauth-state.mjs.map +1 -0
- package/dist/routers/preview-base.mjs +38 -0
- package/dist/routers/preview-base.mjs.map +1 -0
- package/dist/routers/preview-token.mjs +51 -0
- package/dist/routers/preview-token.mjs.map +1 -0
- package/dist/routers/profile.mjs +4 -2
- package/dist/routers/profile.mjs.map +1 -1
- package/dist/routers/projects.mjs +17 -1
- package/dist/routers/projects.mjs.map +1 -1
- package/dist/routers/publish-upload.mjs +78 -0
- package/dist/routers/publish-upload.mjs.map +1 -0
- package/dist/routers/published-access.mjs +52 -0
- package/dist/routers/published-access.mjs.map +1 -0
- package/dist/routers/published-files.mjs +52 -0
- package/dist/routers/published-files.mjs.map +1 -0
- package/dist/routers/review-forms.mjs +72 -0
- package/dist/routers/review-forms.mjs.map +1 -0
- package/dist/routers/review-section.handlers.mjs +85 -0
- package/dist/routers/review-section.handlers.mjs.map +1 -0
- package/dist/routers/review-switch-off.mjs +29 -0
- package/dist/routers/review-switch-off.mjs.map +1 -0
- package/dist/routers/schemas.mjs +140 -22
- package/dist/routers/schemas.mjs.map +1 -1
- package/dist/routers/scoped-models.mjs +22 -0
- package/dist/routers/scoped-models.mjs.map +1 -0
- package/dist/routers/settings-ai.mjs +35 -0
- package/dist/routers/settings-ai.mjs.map +1 -0
- package/dist/routers/settings-capture.mjs +77 -0
- package/dist/routers/settings-capture.mjs.map +1 -0
- package/dist/routers/settings-design.mjs +103 -0
- package/dist/routers/settings-design.mjs.map +1 -0
- package/dist/routers/settings-general.mjs +55 -10
- package/dist/routers/settings-general.mjs.map +1 -1
- package/dist/routers/settings-git-channel.mjs +77 -0
- package/dist/routers/settings-git-channel.mjs.map +1 -0
- package/dist/routers/settings-labels.mjs +27 -1
- package/dist/routers/settings-labels.mjs.map +1 -1
- package/dist/routers/settings-links.mjs +63 -0
- package/dist/routers/settings-links.mjs.map +1 -0
- package/dist/routers/settings-members.mjs +4 -7
- package/dist/routers/settings-members.mjs.map +1 -1
- package/dist/routers/settings-notifications.mjs +15 -10
- package/dist/routers/settings-notifications.mjs.map +1 -1
- package/dist/routers/settings-tokens.mjs +2 -5
- package/dist/routers/settings-tokens.mjs.map +1 -1
- package/dist/routers/settings-webhooks.mjs +7 -24
- package/dist/routers/settings-webhooks.mjs.map +1 -1
- package/dist/routers/settings.handlers.mjs +107 -26
- package/dist/routers/settings.handlers.mjs.map +1 -1
- package/dist/routers/settings.mjs +16 -4
- package/dist/routers/settings.mjs.map +1 -1
- package/dist/routers/signed-storybook.mjs +53 -0
- package/dist/routers/signed-storybook.mjs.map +1 -0
- package/dist/routers/snapshot-groups.mjs +63 -0
- package/dist/routers/snapshot-groups.mjs.map +1 -0
- package/dist/routers/snapshots.mjs +103 -5
- package/dist/routers/snapshots.mjs.map +1 -1
- package/dist/routers/storybook.mjs +37 -88
- package/dist/routers/storybook.mjs.map +1 -1
- package/dist/routers/tokens.mjs +2 -5
- package/dist/routers/tokens.mjs.map +1 -1
- package/dist/routers/ui.mjs +34 -5
- package/dist/routers/ui.mjs.map +1 -1
- package/dist/routers/webhooks.mjs +5 -11
- package/dist/routers/webhooks.mjs.map +1 -1
- package/dist/routers/well-known.mjs +11 -4
- package/dist/routers/well-known.mjs.map +1 -1
- package/dist/routes/api-routes.mjs +93 -0
- package/dist/routes/api-routes.mjs.map +1 -0
- package/dist/routes/classification.mjs +38 -0
- package/dist/routes/classification.mjs.map +1 -0
- package/dist/routes/openapi-classes.mjs +22 -0
- package/dist/routes/openapi-classes.mjs.map +1 -0
- package/dist/routes/page-routes.mjs +104 -0
- package/dist/routes/page-routes.mjs.map +1 -0
- package/dist/runtime.mjs +18 -13
- package/dist/runtime.mjs.map +1 -1
- package/dist/serverless.mjs +5 -0
- package/dist/store.mjs +19 -1
- package/dist/store.mjs.map +1 -1
- package/dist/targets.mjs +46 -0
- package/dist/targets.mjs.map +1 -0
- package/dist/ui/buttons.mjs +16 -3
- package/dist/ui/buttons.mjs.map +1 -1
- package/dist/ui/design-reference.mjs +97 -0
- package/dist/ui/design-reference.mjs.map +1 -0
- package/dist/ui/document.mjs +1 -2
- package/dist/ui/document.mjs.map +1 -1
- package/dist/ui/forms.mjs +6 -5
- package/dist/ui/forms.mjs.map +1 -1
- package/dist/ui/icons/paths.mjs +1 -0
- package/dist/ui/icons/paths.mjs.map +1 -1
- package/dist/ui/preview-embed.mjs +82 -0
- package/dist/ui/preview-embed.mjs.map +1 -0
- package/dist/ui/shell/client-script.mjs +85 -0
- package/dist/ui/shell/client-script.mjs.map +1 -1
- package/dist/ui/shell/nav.mjs +12 -8
- package/dist/ui/shell/nav.mjs.map +1 -1
- package/dist/ui/shell/review-script.mjs +8 -0
- package/dist/ui/shell/review-script.mjs.map +1 -1
- package/dist/ui/shell/sidebar.mjs +2 -2
- package/dist/ui/shell/sidebar.mjs.map +1 -1
- package/package.json +11 -6
- package/dist/asset-paths.mjs +0 -23
- package/dist/asset-paths.mjs.map +0 -1
- package/dist/assets/mark.svg +0 -1
- package/dist/pages/settings-status.mjs +0 -137
- package/dist/pages/settings-status.mjs.map +0 -1
- package/dist/routers/settings-status.mjs +0 -77
- package/dist/routers/settings-status.mjs.map +0 -1
- package/dist/routers/status-configs.mjs +0 -108
- package/dist/routers/status-configs.mjs.map +0 -1
package/dist/routers/tokens.mjs
CHANGED
|
@@ -6,7 +6,6 @@ import { notFound, resolveAuthorizedProject } from "./helpers.mjs";
|
|
|
6
6
|
import { notFound as notFound$1, tokenCreateSchema, tokenCreatedSchema, tokenPublicSchema, unauthorized } from "./schemas.mjs";
|
|
7
7
|
import { createRoute, z } from "@hono/zod-openapi";
|
|
8
8
|
import { TokenModel } from "@storyshelf/core/models";
|
|
9
|
-
import { randomToken } from "@storyshelf/core/utils";
|
|
10
9
|
//#region src/routers/tokens.ts
|
|
11
10
|
const ADMIN_ROLES = ["admin"];
|
|
12
11
|
const listTokensRoute = createRoute({
|
|
@@ -65,15 +64,13 @@ function registerTokens(app) {
|
|
|
65
64
|
app.openapi(createTokenRoute, async (c) => {
|
|
66
65
|
const project = await resolveAuthorizedProject(c, c.req.valid("param").slug, ...ADMIN_ROLES);
|
|
67
66
|
const body = c.req.valid("json");
|
|
68
|
-
const
|
|
69
|
-
await new TokenModel(getStore().db).create(project.id, {
|
|
67
|
+
const { value } = await new TokenModel(getStore().db).issue(project.id, {
|
|
70
68
|
name: body.name,
|
|
71
|
-
hash: token.hash,
|
|
72
69
|
userId: getStore().user?.id ?? null
|
|
73
70
|
});
|
|
74
71
|
return c.json({
|
|
75
72
|
...body,
|
|
76
|
-
token:
|
|
73
|
+
token: value
|
|
77
74
|
}, 201);
|
|
78
75
|
});
|
|
79
76
|
app.openapi(deleteTokenRoute, async (c) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tokens.mjs","names":["notFoundResponse"],"sources":["../../src/routers/tokens.ts"],"sourcesContent":["import { createRoute, z } from \"@hono/zod-openapi\";\nimport { TokenModel } from \"@storyshelf/core/models\";\nimport type { ProjectRole } from \"@storyshelf/core/types\";\nimport
|
|
1
|
+
{"version":3,"file":"tokens.mjs","names":["notFoundResponse"],"sources":["../../src/routers/tokens.ts"],"sourcesContent":["import { createRoute, z } from \"@hono/zod-openapi\";\nimport { TokenModel } from \"@storyshelf/core/models\";\nimport type { ProjectRole } from \"@storyshelf/core/types\";\nimport type { ShelfRouter } from \"../app-types.ts\";\nimport { getStore } from \"../store.ts\";\nimport { resolveAuthorizedProject, notFound } from \"./helpers.ts\";\nimport {\n notFound as notFoundResponse,\n tokenCreatedSchema,\n tokenCreateSchema,\n tokenPublicSchema,\n unauthorized,\n} from \"./schemas.ts\";\nconst ADMIN_ROLES: readonly ProjectRole[] = [\"admin\"];\n\nconst listTokensRoute = createRoute({\n method: \"get\",\n tags: [\"Tokens\"],\n summary: \"List API tokens\",\n path: \"/api/v1/projects/{slug}/tokens\",\n request: { params: z.object({ slug: z.string() }) },\n responses: {\n 200: {\n content: { \"application/json\": { schema: tokenPublicSchema.array() } },\n description: \"List API tokens\",\n },\n ...notFoundResponse,\n ...unauthorized,\n },\n});\n\nconst createTokenRoute = createRoute({\n method: \"post\",\n tags: [\"Tokens\"],\n summary: \"Create an API token\",\n path: \"/api/v1/projects/{slug}/tokens\",\n request: {\n params: z.object({ slug: z.string() }),\n body: { content: { \"application/json\": { schema: tokenCreateSchema } } },\n },\n responses: {\n 201: {\n content: { \"application/json\": { schema: tokenCreatedSchema } },\n description: \"Created token\",\n },\n ...notFoundResponse,\n },\n});\n\nconst deleteTokenRoute = createRoute({\n method: \"delete\",\n tags: [\"Tokens\"],\n summary: \"Delete an API token\",\n path: \"/api/v1/projects/{slug}/tokens/{tokenId}\",\n request: { params: z.object({ slug: z.string(), tokenId: z.string() }) },\n responses: {\n 204: { description: \"Deleted token\" },\n ...notFoundResponse,\n },\n});\n\n/** Register the project API token list, create, and revoke endpoints. */\nexport function registerTokens(app: ShelfRouter): void {\n app.openapi(listTokensRoute, async (c) => {\n const project = await resolveAuthorizedProject(c, c.req.valid(\"param\").slug, ...ADMIN_ROLES);\n const tokens = await new TokenModel(getStore().db).list(project.id);\n return c.json(tokens.map(({ hash: _hash, ...rest }) => rest));\n });\n\n app.openapi(createTokenRoute, async (c) => {\n const project = await resolveAuthorizedProject(c, c.req.valid(\"param\").slug, ...ADMIN_ROLES);\n const body = c.req.valid(\"json\");\n const { value } = await new TokenModel(getStore().db).issue(project.id, {\n name: body.name,\n userId: getStore().user?.id ?? null,\n });\n return c.json({ ...body, token: value }, 201);\n });\n\n app.openapi(deleteTokenRoute, async (c) => {\n const { slug, tokenId } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...ADMIN_ROLES);\n const found = await new TokenModel(getStore().db).get(project.id, tokenId);\n if (!found) {\n notFound(\"Token not found\");\n }\n await new TokenModel(getStore().db).remove(found.id);\n return c.body(null, 204);\n });\n}\n"],"mappings":";;;;;;;;;AAaA,MAAM,cAAsC,CAAC,OAAO;AAEpD,MAAM,kBAAkB,YAAY;CAClC,QAAQ;CACR,MAAM,CAAC,QAAQ;CACf,SAAS;CACT,MAAM;CACN,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE;CAClD,WAAW;EACT,KAAK;GACH,SAAS,EAAE,oBAAoB,EAAE,QAAQ,kBAAkB,MAAM,EAAE,EAAE;GACrE,aAAa;EACf;EACA,GAAGA;EACH,GAAG;CACL;AACF,CAAC;AAED,MAAM,mBAAmB,YAAY;CACnC,QAAQ;CACR,MAAM,CAAC,QAAQ;CACf,SAAS;CACT,MAAM;CACN,SAAS;EACP,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;EACrC,MAAM,EAAE,SAAS,EAAE,oBAAoB,EAAE,QAAQ,kBAAkB,EAAE,EAAE;CACzE;CACA,WAAW;EACT,KAAK;GACH,SAAS,EAAE,oBAAoB,EAAE,QAAQ,mBAAmB,EAAE;GAC9D,aAAa;EACf;EACA,GAAGA;CACL;AACF,CAAC;AAED,MAAM,mBAAmB,YAAY;CACnC,QAAQ;CACR,MAAM,CAAC,QAAQ;CACf,SAAS;CACT,MAAM;CACN,SAAS,EAAE,QAAQ,EAAE,OAAO;EAAE,MAAM,EAAE,OAAO;EAAG,SAAS,EAAE,OAAO;CAAE,CAAC,EAAE;CACvE,WAAW;EACT,KAAK,EAAE,aAAa,gBAAgB;EACpC,GAAGA;CACL;AACF,CAAC;;AAGD,SAAgB,eAAe,KAAwB;CACrD,IAAI,QAAQ,iBAAiB,OAAO,MAAM;EACxC,MAAM,UAAU,MAAM,yBAAyB,GAAG,EAAE,IAAI,MAAM,OAAO,CAAC,CAAC,MAAM,GAAG,WAAW;EAC3F,MAAM,SAAS,MAAM,IAAI,WAAW,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,QAAQ,EAAE;EAClE,OAAO,EAAE,KAAK,OAAO,KAAK,EAAE,MAAM,OAAO,GAAG,WAAW,IAAI,CAAC;CAC9D,CAAC;CAED,IAAI,QAAQ,kBAAkB,OAAO,MAAM;EACzC,MAAM,UAAU,MAAM,yBAAyB,GAAG,EAAE,IAAI,MAAM,OAAO,CAAC,CAAC,MAAM,GAAG,WAAW;EAC3F,MAAM,OAAO,EAAE,IAAI,MAAM,MAAM;EAC/B,MAAM,EAAE,UAAU,MAAM,IAAI,WAAW,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,QAAQ,IAAI;GACtE,MAAM,KAAK;GACX,QAAQ,SAAS,CAAC,CAAC,MAAM,MAAM;EACjC,CAAC;EACD,OAAO,EAAE,KAAK;GAAE,GAAG;GAAM,OAAO;EAAM,GAAG,GAAG;CAC9C,CAAC;CAED,IAAI,QAAQ,kBAAkB,OAAO,MAAM;EACzC,MAAM,EAAE,MAAM,YAAY,EAAE,IAAI,MAAM,OAAO;EAC7C,MAAM,UAAU,MAAM,yBAAyB,GAAG,MAAM,GAAG,WAAW;EACtE,MAAM,QAAQ,MAAM,IAAI,WAAW,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,QAAQ,IAAI,OAAO;EACzE,IAAI,CAAC,OACH,SAAS,iBAAiB;EAE5B,MAAM,IAAI,WAAW,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,OAAO,MAAM,EAAE;EACnD,OAAO,EAAE,KAAK,MAAM,GAAG;CACzB,CAAC;AACH"}
|
package/dist/routers/ui.mjs
CHANGED
|
@@ -2,12 +2,16 @@ import "node:path";
|
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
4
|
import { getStore } from "../store.mjs";
|
|
5
|
+
import { currentProjectRole } from "./helpers.mjs";
|
|
5
6
|
import { hxRedirect } from "./htmx.mjs";
|
|
6
7
|
import { flash } from "./flash.mjs";
|
|
7
|
-
import { currentProjectRole } from "./helpers.mjs";
|
|
8
8
|
import { loadBaselineView } from "./baseline-guard.mjs";
|
|
9
|
+
import { loadInsightPanel } from "../insights/panel.mjs";
|
|
10
|
+
import { linkedUsageByStory, linkedUsageFor } from "./links.handlers.mjs";
|
|
11
|
+
import { designUrlsFor, loadDesignResolutions, loadReviewDesign, returnPathOf } from "./design.handlers.mjs";
|
|
9
12
|
import { renderBuildDetailPage } from "../pages/build-detail.mjs";
|
|
10
|
-
import { renderBuildDiffPage } from "../pages/build-diff.mjs";
|
|
13
|
+
import { renderBuildDiffPage, selectedSnapshot } from "../pages/build-diff.mjs";
|
|
14
|
+
import { renderComponentPage } from "../pages/component-detail.mjs";
|
|
11
15
|
import { renderActiveQueue, renderComputeJobsPage } from "../pages/compute-jobs.mjs";
|
|
12
16
|
import { renderLabelDetailPage, renderLabelsPage } from "../pages/label-detail.mjs";
|
|
13
17
|
import { renderLibraryPage } from "../pages/library.mjs";
|
|
@@ -15,6 +19,7 @@ import { renderProjectBuildsPage } from "../pages/project-builds.mjs";
|
|
|
15
19
|
import { renderProjectCreatePage } from "../pages/project-create.mjs";
|
|
16
20
|
import { renderProjectsPage } from "../pages/projects.mjs";
|
|
17
21
|
import { renderRootPage } from "../pages/root.mjs";
|
|
22
|
+
import { loadComponentPage } from "./component.handlers.mjs";
|
|
18
23
|
import { registerSettingsPages } from "./settings.mjs";
|
|
19
24
|
import { BuildModel, CommentModel, LabelModel, ProjectModel, SnapshotModel } from "@storyshelf/core/models";
|
|
20
25
|
//#region src/routers/ui.ts
|
|
@@ -100,7 +105,8 @@ function registerUiPages(app) {
|
|
|
100
105
|
return c.html(html);
|
|
101
106
|
});
|
|
102
107
|
app.get("/projects/:slug/builds/:buildId", async (c) => {
|
|
103
|
-
const
|
|
108
|
+
const buildId = c.req.param("buildId");
|
|
109
|
+
const html = await renderBuildDetailPage(buildId, await loadBuildUsage(c, buildId));
|
|
104
110
|
if (!html) return c.notFound();
|
|
105
111
|
return c.html(html);
|
|
106
112
|
});
|
|
@@ -111,7 +117,7 @@ function registerUiPages(app) {
|
|
|
111
117
|
const queueView = await getQueueView();
|
|
112
118
|
const canRetry = await canManageJobs(project.id);
|
|
113
119
|
if (c.req.query("partial") === "queue") return c.html(renderActiveQueue(slug, queueView));
|
|
114
|
-
const html = await renderComputeJobsPage(slug, queueView, canRetry
|
|
120
|
+
const html = await renderComputeJobsPage(slug, queueView, canRetry);
|
|
115
121
|
if (!html) return c.notFound();
|
|
116
122
|
return c.html(html);
|
|
117
123
|
});
|
|
@@ -128,6 +134,8 @@ function registerUiPages(app) {
|
|
|
128
134
|
const { hasBaseline, drifted } = await loadBaselineView(project, build, snapshots);
|
|
129
135
|
const { user, authEnabled } = getStore();
|
|
130
136
|
const canReview = !authEnabled || Boolean(user);
|
|
137
|
+
const selected = selectedSnapshot(snapshots, snapshotId);
|
|
138
|
+
const design = selected ? await loadReviewDesign(project, selected, designUrlsFor(await loadDesignResolutions(project, snapshots), selected), returnPathOf(c)).catch(() => []) : [];
|
|
131
139
|
return c.html(renderBuildDiffPage({
|
|
132
140
|
project,
|
|
133
141
|
build,
|
|
@@ -136,7 +144,11 @@ function registerUiPages(app) {
|
|
|
136
144
|
selectedId: snapshotId,
|
|
137
145
|
canReview,
|
|
138
146
|
hasBaseline,
|
|
139
|
-
drifted
|
|
147
|
+
drifted,
|
|
148
|
+
insight: await loadInsightPanel(project, build),
|
|
149
|
+
design,
|
|
150
|
+
usage: await linkedUsageFor(c, project, build.id, selectedSnapshot(snapshots, snapshotId)?.storyImportPath ?? null).catch(() => []),
|
|
151
|
+
publishedBaseDomain: getStore().config.publishedBaseDomain
|
|
140
152
|
}));
|
|
141
153
|
});
|
|
142
154
|
app.get("/projects/:slug/labels", async (c) => {
|
|
@@ -153,6 +165,13 @@ function registerUiPages(app) {
|
|
|
153
165
|
if (!html) return c.notFound();
|
|
154
166
|
return c.html(html);
|
|
155
167
|
});
|
|
168
|
+
app.get("/projects/:slug/components/*", async (c) => {
|
|
169
|
+
const slug = c.req.param("slug");
|
|
170
|
+
const base = `/projects/${slug}/components/`;
|
|
171
|
+
const path = c.req.path.startsWith(base) ? decodeRest(c.req.path.slice(base.length)) : "";
|
|
172
|
+
const data = await loadComponentPage(c, slug, path, c.req.query("tab"), c.req.query("story"));
|
|
173
|
+
return data ? c.html(renderComponentPage(data)) : c.notFound();
|
|
174
|
+
});
|
|
156
175
|
app.get("/projects/:slug/library", async (c) => {
|
|
157
176
|
const html = await renderLibraryPage(c.req.param("slug"), c.req.query("branch"));
|
|
158
177
|
if (!html) return c.notFound();
|
|
@@ -160,6 +179,16 @@ function registerUiPages(app) {
|
|
|
160
179
|
});
|
|
161
180
|
registerSettingsPages(app);
|
|
162
181
|
}
|
|
182
|
+
/** Linked-package usage for every story of a build; empty on any failure so the page still renders. */
|
|
183
|
+
async function loadBuildUsage(c, buildId) {
|
|
184
|
+
try {
|
|
185
|
+
const build = await new BuildModel(getStore().db).get(buildId);
|
|
186
|
+
const project = build ? await new ProjectModel(getStore().db).get(build.projectId) : null;
|
|
187
|
+
return build && project ? await linkedUsageByStory(c, project, build.id) : /* @__PURE__ */ new Map();
|
|
188
|
+
} catch {
|
|
189
|
+
return /* @__PURE__ */ new Map();
|
|
190
|
+
}
|
|
191
|
+
}
|
|
163
192
|
//#endregion
|
|
164
193
|
export { registerUiPages };
|
|
165
194
|
|
package/dist/routers/ui.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ui.mjs","names":[],"sources":["../../src/routers/ui.ts"],"sourcesContent":["import { BuildModel } from \"@storyshelf/core/models\";\nimport { CommentModel } from \"@storyshelf/core/models\";\nimport { LabelModel } from \"@storyshelf/core/models\";\nimport { ProjectModel } from \"@storyshelf/core/models\";\nimport { SnapshotModel } from \"@storyshelf/core/models\";\nimport type { ProjectRole } from \"@storyshelf/core/types\";\nimport type { ShelfRouter } from \"../app-types.ts\";\nimport { renderBuildDetailPage } from \"../pages/build-detail.tsx\";\nimport { renderBuildDiffPage } from \"../pages/build-diff.tsx\";\nimport { renderComputeJobsPage, renderActiveQueue } from \"../pages/compute-jobs.tsx\";\nimport { renderLabelDetailPage, renderLabelsPage } from \"../pages/label-detail.tsx\";\nimport { renderLibraryPage } from \"../pages/library.tsx\";\nimport { renderProjectBuildsPage } from \"../pages/project-builds.tsx\";\nimport { renderProjectCreatePage } from \"../pages/project-create.tsx\";\nimport { renderProjectsPage } from \"../pages/projects.tsx\";\nimport { renderRootPage } from \"../pages/root.tsx\";\nimport { getStore } from \"../store.ts\";\nimport { loadBaselineView } from \"./baseline-guard.ts\";\nimport { flash } from \"./flash.ts\";\nimport { currentProjectRole } from \"./helpers.ts\";\nimport { hxRedirect } from \"./htmx.ts\";\nimport { registerSettingsPages } from \"./settings.ts\";\nfunction asString(value: FormDataEntryValue | null): string | undefined {\n return typeof value === \"string\" ? value.trim() : undefined;\n}\n\nfunction decodeRest(value: string): string {\n try {\n return decodeURIComponent(value);\n } catch {\n return \"\";\n }\n}\n\ninterface QueueView {\n buildId: string;\n status: string;\n queuedAt: string;\n startedAt?: string;\n finishedAt?: string;\n error?: string;\n}\n\nasync function getQueueView(): Promise<QueueView[]> {\n const queue = getStore().captureQueue;\n if (!queue) {\n return [];\n }\n const active = await queue.active();\n return active.map((entry) => ({\n buildId: entry.buildId,\n status: entry.status,\n queuedAt: entry.queuedAt,\n startedAt: entry.startedAt,\n finishedAt: entry.finishedAt,\n error: entry.error,\n }));\n}\n\nasync function canManageJobs(projectId: string): Promise<boolean> {\n const { authEnabled } = getStore();\n if (!authEnabled) {\n return true;\n }\n const roles: readonly ProjectRole[] = [\"developer\", \"approver\", \"admin\"];\n const role = await currentProjectRole(projectId);\n return Boolean(role && roles.includes(role));\n}\n\n/** Register the server-rendered HTML pages (projects, builds, diffs, labels, settings). */\nexport function registerUiPages(app: ShelfRouter): void {\n // eslint-disable-next-line promise-function-async -- renderRootPage returns RenderedContent (string | Promise<string>)\n app.get(\"/\", async (c) => c.html(await renderRootPage()));\n app.get(\"/projects\", async (c) => c.html(await renderProjectsPage()));\n // eslint-disable-next-line promise-function-async -- renderProjectCreatePage returns RenderedContent (string | Promise<string>)\n app.get(\"/projects/new\", async (c) => c.html(await renderProjectCreatePage()));\n\n app.post(\"/projects/new\", async (c) => {\n const form = await c.req.formData();\n const name = asString(form.get(\"name\"));\n const gitRepository = asString(form.get(\"gitRepository\"));\n const gitDefaultBranch = asString(form.get(\"gitDefaultBranch\"));\n if (!name) {\n return c.html(\n renderProjectCreatePage({\n values: { name, gitRepository, gitDefaultBranch },\n errors: { name: \"Name is required\" },\n }),\n 400,\n );\n }\n try {\n const project = await new ProjectModel(getStore().db).create({\n name,\n gitRepository,\n gitDefaultBranch,\n });\n await new LabelModel(getStore().db).seedFor(project.id);\n flash(c, `Created ${project.name}`);\n return hxRedirect(c, `/projects/${project.slug}/library`);\n } catch (error) {\n const message = error instanceof Error ? error.message : \"Failed to create project\";\n return c.html(\n renderProjectCreatePage({\n values: { name, gitRepository, gitDefaultBranch },\n globalError: message,\n }),\n 400,\n );\n }\n });\n\n app.get(\"/projects/:slug\", (c) => c.redirect(`/projects/${c.req.param(\"slug\")}/library`, 302));\n\n app.get(\"/projects/:slug/builds\", async (c) => {\n const html = await renderProjectBuildsPage(c.req.param(\"slug\"), {\n status: c.req.query(\"status\"),\n branch: c.req.query(\"branch\"),\n });\n if (!html) {\n return c.notFound();\n }\n return c.html(html);\n });\n\n app.get(\"/projects/:slug/builds/:buildId\", async (c) => {\n const html = await renderBuildDetailPage(c.req.param(\"buildId\"));\n if (!html) {\n return c.notFound();\n }\n return c.html(html);\n });\n\n app.get(\"/projects/:slug/jobs\", async (c) => {\n const slug = c.req.param(\"slug\");\n const project = await new ProjectModel(getStore().db).getBySlug(slug);\n if (!project) {\n return c.notFound();\n }\n const queueView = await getQueueView();\n const canRetry = await canManageJobs(project.id);\n if (c.req.query(\"partial\") === \"queue\") {\n return c.html(renderActiveQueue(slug, queueView));\n }\n const html = await renderComputeJobsPage(\n slug,\n queueView,\n canRetry,\n getStore().captureQueue?.metadata.kind === \"memory\",\n );\n if (!html) {\n return c.notFound();\n }\n return c.html(html);\n });\n\n app.get(\"/projects/:slug/builds/:buildId/diff\", async (c) => {\n const slug = c.req.param(\"slug\");\n const buildId = c.req.param(\"buildId\");\n const snapshotId = c.req.query(\"snapshot\");\n const project = await new ProjectModel(getStore().db).getBySlug(slug);\n if (!project) {\n return c.notFound();\n }\n const build = await new BuildModel(getStore().db).get(buildId);\n if (!build || build.projectId !== project.id) {\n return c.notFound();\n }\n const snapshots = await new SnapshotModel(getStore().db).listByBuild(build.id);\n const comments = await new CommentModel(getStore().db).listByBuild(build.id);\n const { hasBaseline, drifted } = await loadBaselineView(project, build, snapshots);\n const { user, authEnabled } = getStore();\n const canReview = !authEnabled || Boolean(user);\n return c.html(\n renderBuildDiffPage({\n project,\n build,\n snapshots,\n comments,\n selectedId: snapshotId,\n canReview,\n hasBaseline,\n drifted,\n }),\n );\n });\n\n app.get(\"/projects/:slug/labels\", async (c) => {\n const html = await renderLabelsPage(c.req.param(\"slug\"));\n if (!html) {\n return c.notFound();\n }\n return c.html(html);\n });\n\n app.get(\"/projects/:slug/labels/:key/*\", async (c) => {\n const slug = c.req.param(\"slug\");\n const key = c.req.param(\"key\");\n // Hono's bare `/*` wildcard is not exposed through `param()`, so derive the\n // label value from the raw request path and decode it ourselves.\n const base = `/projects/${slug}/labels/${key}/`;\n const value = c.req.path.startsWith(base) ? decodeRest(c.req.path.slice(base.length)) : \"\";\n const html = await renderLabelDetailPage(slug, key, value);\n if (!html) {\n return c.notFound();\n }\n return c.html(html);\n });\n\n app.get(\"/projects/:slug/library\", async (c) => {\n const html = await renderLibraryPage(c.req.param(\"slug\"), c.req.query(\"branch\"));\n if (!html) {\n return c.notFound();\n }\n return c.html(html);\n });\n\n registerSettingsPages(app);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ui.mjs","names":[],"sources":["../../src/routers/ui.ts"],"sourcesContent":["import { BuildModel } from \"@storyshelf/core/models\";\nimport { CommentModel } from \"@storyshelf/core/models\";\nimport { LabelModel } from \"@storyshelf/core/models\";\nimport { ProjectModel } from \"@storyshelf/core/models\";\nimport { SnapshotModel } from \"@storyshelf/core/models\";\nimport type { ProjectRole } from \"@storyshelf/core/types\";\nimport type { Context } from \"hono\";\nimport type { ShelfRouter } from \"../app-types.ts\";\nimport { loadInsightPanel } from \"../insights/panel.ts\";\nimport { renderBuildDetailPage } from \"../pages/build-detail.tsx\";\nimport type { LinkedUsageItem } from \"../pages/build-diff-usage.tsx\";\nimport { renderBuildDiffPage, selectedSnapshot } from \"../pages/build-diff.tsx\";\nimport { renderComponentPage } from \"../pages/component-detail.tsx\";\nimport { renderComputeJobsPage, renderActiveQueue } from \"../pages/compute-jobs.tsx\";\nimport { renderLabelDetailPage, renderLabelsPage } from \"../pages/label-detail.tsx\";\nimport { renderLibraryPage } from \"../pages/library.tsx\";\nimport { renderProjectBuildsPage } from \"../pages/project-builds.tsx\";\nimport { renderProjectCreatePage } from \"../pages/project-create.tsx\";\nimport { renderProjectsPage } from \"../pages/projects.tsx\";\nimport { renderRootPage } from \"../pages/root.tsx\";\nimport { getStore } from \"../store.ts\";\nimport { loadBaselineView } from \"./baseline-guard.ts\";\nimport { loadComponentPage } from \"./component.handlers.ts\";\nimport {\n designUrlsFor,\n loadDesignResolutions,\n loadReviewDesign,\n returnPathOf,\n} from \"./design.handlers.ts\";\nimport { flash } from \"./flash.ts\";\nimport { currentProjectRole } from \"./helpers.ts\";\nimport { hxRedirect } from \"./htmx.ts\";\nimport { linkedUsageByStory, linkedUsageFor } from \"./links.handlers.ts\";\nimport { registerSettingsPages } from \"./settings.ts\";\nfunction asString(value: FormDataEntryValue | null): string | undefined {\n return typeof value === \"string\" ? value.trim() : undefined;\n}\n\nfunction decodeRest(value: string): string {\n try {\n return decodeURIComponent(value);\n } catch {\n return \"\";\n }\n}\n\ninterface QueueView {\n buildId: string;\n status: string;\n queuedAt: string;\n startedAt?: string;\n finishedAt?: string;\n error?: string;\n}\n\nasync function getQueueView(): Promise<QueueView[]> {\n const queue = getStore().captureQueue;\n if (!queue) {\n return [];\n }\n const active = await queue.active();\n return active.map((entry) => ({\n buildId: entry.buildId,\n status: entry.status,\n queuedAt: entry.queuedAt,\n startedAt: entry.startedAt,\n finishedAt: entry.finishedAt,\n error: entry.error,\n }));\n}\n\nasync function canManageJobs(projectId: string): Promise<boolean> {\n const { authEnabled } = getStore();\n if (!authEnabled) {\n return true;\n }\n const roles: readonly ProjectRole[] = [\"developer\", \"approver\", \"admin\"];\n const role = await currentProjectRole(projectId);\n return Boolean(role && roles.includes(role));\n}\n\n/** Register the server-rendered HTML pages (projects, builds, diffs, labels, settings). */\nexport function registerUiPages(app: ShelfRouter): void {\n // eslint-disable-next-line promise-function-async -- renderRootPage returns RenderedContent (string | Promise<string>)\n app.get(\"/\", async (c) => c.html(await renderRootPage()));\n app.get(\"/projects\", async (c) => c.html(await renderProjectsPage()));\n // eslint-disable-next-line promise-function-async -- renderProjectCreatePage returns RenderedContent (string | Promise<string>)\n app.get(\"/projects/new\", async (c) => c.html(await renderProjectCreatePage()));\n\n app.post(\"/projects/new\", async (c) => {\n const form = await c.req.formData();\n const name = asString(form.get(\"name\"));\n const gitRepository = asString(form.get(\"gitRepository\"));\n const gitDefaultBranch = asString(form.get(\"gitDefaultBranch\"));\n if (!name) {\n return c.html(\n renderProjectCreatePage({\n values: { name, gitRepository, gitDefaultBranch },\n errors: { name: \"Name is required\" },\n }),\n 400,\n );\n }\n try {\n const project = await new ProjectModel(getStore().db).create({\n name,\n gitRepository,\n gitDefaultBranch,\n });\n await new LabelModel(getStore().db).seedFor(project.id);\n flash(c, `Created ${project.name}`);\n return hxRedirect(c, `/projects/${project.slug}/library`);\n } catch (error) {\n const message = error instanceof Error ? error.message : \"Failed to create project\";\n return c.html(\n renderProjectCreatePage({\n values: { name, gitRepository, gitDefaultBranch },\n globalError: message,\n }),\n 400,\n );\n }\n });\n\n app.get(\"/projects/:slug\", (c) => c.redirect(`/projects/${c.req.param(\"slug\")}/library`, 302));\n\n app.get(\"/projects/:slug/builds\", async (c) => {\n const html = await renderProjectBuildsPage(c.req.param(\"slug\"), {\n status: c.req.query(\"status\"),\n branch: c.req.query(\"branch\"),\n });\n if (!html) {\n return c.notFound();\n }\n return c.html(html);\n });\n\n app.get(\"/projects/:slug/builds/:buildId\", async (c) => {\n const buildId = c.req.param(\"buildId\");\n const html = await renderBuildDetailPage(buildId, await loadBuildUsage(c, buildId));\n if (!html) {\n return c.notFound();\n }\n return c.html(html);\n });\n\n app.get(\"/projects/:slug/jobs\", async (c) => {\n const slug = c.req.param(\"slug\");\n const project = await new ProjectModel(getStore().db).getBySlug(slug);\n if (!project) {\n return c.notFound();\n }\n const queueView = await getQueueView();\n const canRetry = await canManageJobs(project.id);\n if (c.req.query(\"partial\") === \"queue\") {\n return c.html(renderActiveQueue(slug, queueView));\n }\n const html = await renderComputeJobsPage(slug, queueView, canRetry);\n if (!html) {\n return c.notFound();\n }\n return c.html(html);\n });\n\n app.get(\"/projects/:slug/builds/:buildId/diff\", async (c) => {\n const slug = c.req.param(\"slug\");\n const buildId = c.req.param(\"buildId\");\n const snapshotId = c.req.query(\"snapshot\");\n const project = await new ProjectModel(getStore().db).getBySlug(slug);\n if (!project) {\n return c.notFound();\n }\n const build = await new BuildModel(getStore().db).get(buildId);\n if (!build || build.projectId !== project.id) {\n return c.notFound();\n }\n const snapshots = await new SnapshotModel(getStore().db).listByBuild(build.id);\n const comments = await new CommentModel(getStore().db).listByBuild(build.id);\n const { hasBaseline, drifted } = await loadBaselineView(project, build, snapshots);\n const { user, authEnabled } = getStore();\n const canReview = !authEnabled || Boolean(user);\n const selected = selectedSnapshot(snapshots, snapshotId);\n const design = selected\n ? await loadReviewDesign(\n project,\n selected,\n designUrlsFor(await loadDesignResolutions(project, snapshots), selected),\n returnPathOf(c),\n ).catch(() => [])\n : [];\n return c.html(\n renderBuildDiffPage({\n project,\n build,\n snapshots,\n comments,\n selectedId: snapshotId,\n canReview,\n hasBaseline,\n drifted,\n insight: await loadInsightPanel(project, build),\n design,\n usage: await linkedUsageFor(\n c,\n project,\n build.id,\n selectedSnapshot(snapshots, snapshotId)?.storyImportPath ?? null,\n ).catch(() => []),\n publishedBaseDomain: getStore().config.publishedBaseDomain,\n }),\n );\n });\n\n app.get(\"/projects/:slug/labels\", async (c) => {\n const html = await renderLabelsPage(c.req.param(\"slug\"));\n if (!html) {\n return c.notFound();\n }\n return c.html(html);\n });\n\n app.get(\"/projects/:slug/labels/:key/*\", async (c) => {\n const slug = c.req.param(\"slug\");\n const key = c.req.param(\"key\");\n // Hono's bare `/*` wildcard is not exposed through `param()`, so derive the\n // label value from the raw request path and decode it ourselves.\n const base = `/projects/${slug}/labels/${key}/`;\n const value = c.req.path.startsWith(base) ? decodeRest(c.req.path.slice(base.length)) : \"\";\n const html = await renderLabelDetailPage(slug, key, value);\n if (!html) {\n return c.notFound();\n }\n return c.html(html);\n });\n\n app.get(\"/projects/:slug/components/*\", async (c) => {\n const slug = c.req.param(\"slug\");\n const base = `/projects/${slug}/components/`;\n const path = c.req.path.startsWith(base) ? decodeRest(c.req.path.slice(base.length)) : \"\";\n const data = await loadComponentPage(c, slug, path, c.req.query(\"tab\"), c.req.query(\"story\"));\n return data ? c.html(renderComponentPage(data)) : c.notFound();\n });\n\n app.get(\"/projects/:slug/library\", async (c) => {\n const html = await renderLibraryPage(c.req.param(\"slug\"), c.req.query(\"branch\"));\n if (!html) {\n return c.notFound();\n }\n return c.html(html);\n });\n\n registerSettingsPages(app);\n}\n\n/** Linked-package usage for every story of a build; empty on any failure so the page still renders. */\nasync function loadBuildUsage(\n c: Context,\n buildId: string,\n): Promise<Map<string, LinkedUsageItem[]>> {\n try {\n const build = await new BuildModel(getStore().db).get(buildId);\n const project = build ? await new ProjectModel(getStore().db).get(build.projectId) : null;\n return build && project ? await linkedUsageByStory(c, project, build.id) : new Map();\n } catch {\n return new Map();\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAkCA,SAAS,SAAS,OAAsD;CACtE,OAAO,OAAO,UAAU,WAAW,MAAM,KAAK,IAAI,KAAA;AACpD;AAEA,SAAS,WAAW,OAAuB;CACzC,IAAI;EACF,OAAO,mBAAmB,KAAK;CACjC,QAAQ;EACN,OAAO;CACT;AACF;AAWA,eAAe,eAAqC;CAClD,MAAM,QAAQ,SAAS,CAAC,CAAC;CACzB,IAAI,CAAC,OACH,OAAO,CAAC;CAGV,QAAO,MADc,MAAM,OAAO,EAAA,CACpB,KAAK,WAAW;EAC5B,SAAS,MAAM;EACf,QAAQ,MAAM;EACd,UAAU,MAAM;EAChB,WAAW,MAAM;EACjB,YAAY,MAAM;EAClB,OAAO,MAAM;CACf,EAAE;AACJ;AAEA,eAAe,cAAc,WAAqC;CAChE,MAAM,EAAE,gBAAgB,SAAS;CACjC,IAAI,CAAC,aACH,OAAO;CAET,MAAM,QAAgC;EAAC;EAAa;EAAY;CAAO;CACvE,MAAM,OAAO,MAAM,mBAAmB,SAAS;CAC/C,OAAO,QAAQ,QAAQ,MAAM,SAAS,IAAI,CAAC;AAC7C;;AAGA,SAAgB,gBAAgB,KAAwB;CAEtD,IAAI,IAAI,KAAK,OAAO,MAAM,EAAE,KAAK,MAAM,eAAe,CAAC,CAAC;CACxD,IAAI,IAAI,aAAa,OAAO,MAAM,EAAE,KAAK,MAAM,mBAAmB,CAAC,CAAC;CAEpE,IAAI,IAAI,iBAAiB,OAAO,MAAM,EAAE,KAAK,MAAM,wBAAwB,CAAC,CAAC;CAE7E,IAAI,KAAK,iBAAiB,OAAO,MAAM;EACrC,MAAM,OAAO,MAAM,EAAE,IAAI,SAAS;EAClC,MAAM,OAAO,SAAS,KAAK,IAAI,MAAM,CAAC;EACtC,MAAM,gBAAgB,SAAS,KAAK,IAAI,eAAe,CAAC;EACxD,MAAM,mBAAmB,SAAS,KAAK,IAAI,kBAAkB,CAAC;EAC9D,IAAI,CAAC,MACH,OAAO,EAAE,KACP,wBAAwB;GACtB,QAAQ;IAAE;IAAM;IAAe;GAAiB;GAChD,QAAQ,EAAE,MAAM,mBAAmB;EACrC,CAAC,GACD,GACF;EAEF,IAAI;GACF,MAAM,UAAU,MAAM,IAAI,aAAa,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,OAAO;IAC3D;IACA;IACA;GACF,CAAC;GACD,MAAM,IAAI,WAAW,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,QAAQ,QAAQ,EAAE;GACtD,MAAM,GAAG,WAAW,QAAQ,MAAM;GAClC,OAAO,WAAW,GAAG,aAAa,QAAQ,KAAK,SAAS;EAC1D,SAAS,OAAO;GACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU;GACzD,OAAO,EAAE,KACP,wBAAwB;IACtB,QAAQ;KAAE;KAAM;KAAe;IAAiB;IAChD,aAAa;GACf,CAAC,GACD,GACF;EACF;CACF,CAAC;CAED,IAAI,IAAI,oBAAoB,MAAM,EAAE,SAAS,aAAa,EAAE,IAAI,MAAM,MAAM,EAAE,WAAW,GAAG,CAAC;CAE7F,IAAI,IAAI,0BAA0B,OAAO,MAAM;EAC7C,MAAM,OAAO,MAAM,wBAAwB,EAAE,IAAI,MAAM,MAAM,GAAG;GAC9D,QAAQ,EAAE,IAAI,MAAM,QAAQ;GAC5B,QAAQ,EAAE,IAAI,MAAM,QAAQ;EAC9B,CAAC;EACD,IAAI,CAAC,MACH,OAAO,EAAE,SAAS;EAEpB,OAAO,EAAE,KAAK,IAAI;CACpB,CAAC;CAED,IAAI,IAAI,mCAAmC,OAAO,MAAM;EACtD,MAAM,UAAU,EAAE,IAAI,MAAM,SAAS;EACrC,MAAM,OAAO,MAAM,sBAAsB,SAAS,MAAM,eAAe,GAAG,OAAO,CAAC;EAClF,IAAI,CAAC,MACH,OAAO,EAAE,SAAS;EAEpB,OAAO,EAAE,KAAK,IAAI;CACpB,CAAC;CAED,IAAI,IAAI,wBAAwB,OAAO,MAAM;EAC3C,MAAM,OAAO,EAAE,IAAI,MAAM,MAAM;EAC/B,MAAM,UAAU,MAAM,IAAI,aAAa,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,UAAU,IAAI;EACpE,IAAI,CAAC,SACH,OAAO,EAAE,SAAS;EAEpB,MAAM,YAAY,MAAM,aAAa;EACrC,MAAM,WAAW,MAAM,cAAc,QAAQ,EAAE;EAC/C,IAAI,EAAE,IAAI,MAAM,SAAS,MAAM,SAC7B,OAAO,EAAE,KAAK,kBAAkB,MAAM,SAAS,CAAC;EAElD,MAAM,OAAO,MAAM,sBAAsB,MAAM,WAAW,QAAQ;EAClE,IAAI,CAAC,MACH,OAAO,EAAE,SAAS;EAEpB,OAAO,EAAE,KAAK,IAAI;CACpB,CAAC;CAED,IAAI,IAAI,wCAAwC,OAAO,MAAM;EAC3D,MAAM,OAAO,EAAE,IAAI,MAAM,MAAM;EAC/B,MAAM,UAAU,EAAE,IAAI,MAAM,SAAS;EACrC,MAAM,aAAa,EAAE,IAAI,MAAM,UAAU;EACzC,MAAM,UAAU,MAAM,IAAI,aAAa,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,UAAU,IAAI;EACpE,IAAI,CAAC,SACH,OAAO,EAAE,SAAS;EAEpB,MAAM,QAAQ,MAAM,IAAI,WAAW,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,OAAO;EAC7D,IAAI,CAAC,SAAS,MAAM,cAAc,QAAQ,IACxC,OAAO,EAAE,SAAS;EAEpB,MAAM,YAAY,MAAM,IAAI,cAAc,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,YAAY,MAAM,EAAE;EAC7E,MAAM,WAAW,MAAM,IAAI,aAAa,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,YAAY,MAAM,EAAE;EAC3E,MAAM,EAAE,aAAa,YAAY,MAAM,iBAAiB,SAAS,OAAO,SAAS;EACjF,MAAM,EAAE,MAAM,gBAAgB,SAAS;EACvC,MAAM,YAAY,CAAC,eAAe,QAAQ,IAAI;EAC9C,MAAM,WAAW,iBAAiB,WAAW,UAAU;EACvD,MAAM,SAAS,WACX,MAAM,iBACJ,SACA,UACA,cAAc,MAAM,sBAAsB,SAAS,SAAS,GAAG,QAAQ,GACvE,aAAa,CAAC,CAChB,CAAC,CAAC,YAAY,CAAC,CAAC,IAChB,CAAC;EACL,OAAO,EAAE,KACP,oBAAoB;GAClB;GACA;GACA;GACA;GACA,YAAY;GACZ;GACA;GACA;GACA,SAAS,MAAM,iBAAiB,SAAS,KAAK;GAC9C;GACA,OAAO,MAAM,eACX,GACA,SACA,MAAM,IACN,iBAAiB,WAAW,UAAU,CAAC,EAAE,mBAAmB,IAC9D,CAAC,CAAC,YAAY,CAAC,CAAC;GAChB,qBAAqB,SAAS,CAAC,CAAC,OAAO;EACzC,CAAC,CACH;CACF,CAAC;CAED,IAAI,IAAI,0BAA0B,OAAO,MAAM;EAC7C,MAAM,OAAO,MAAM,iBAAiB,EAAE,IAAI,MAAM,MAAM,CAAC;EACvD,IAAI,CAAC,MACH,OAAO,EAAE,SAAS;EAEpB,OAAO,EAAE,KAAK,IAAI;CACpB,CAAC;CAED,IAAI,IAAI,iCAAiC,OAAO,MAAM;EACpD,MAAM,OAAO,EAAE,IAAI,MAAM,MAAM;EAC/B,MAAM,MAAM,EAAE,IAAI,MAAM,KAAK;EAG7B,MAAM,OAAO,aAAa,KAAK,UAAU,IAAI;EAC7C,MAAM,QAAQ,EAAE,IAAI,KAAK,WAAW,IAAI,IAAI,WAAW,EAAE,IAAI,KAAK,MAAM,KAAK,MAAM,CAAC,IAAI;EACxF,MAAM,OAAO,MAAM,sBAAsB,MAAM,KAAK,KAAK;EACzD,IAAI,CAAC,MACH,OAAO,EAAE,SAAS;EAEpB,OAAO,EAAE,KAAK,IAAI;CACpB,CAAC;CAED,IAAI,IAAI,gCAAgC,OAAO,MAAM;EACnD,MAAM,OAAO,EAAE,IAAI,MAAM,MAAM;EAC/B,MAAM,OAAO,aAAa,KAAK;EAC/B,MAAM,OAAO,EAAE,IAAI,KAAK,WAAW,IAAI,IAAI,WAAW,EAAE,IAAI,KAAK,MAAM,KAAK,MAAM,CAAC,IAAI;EACvF,MAAM,OAAO,MAAM,kBAAkB,GAAG,MAAM,MAAM,EAAE,IAAI,MAAM,KAAK,GAAG,EAAE,IAAI,MAAM,OAAO,CAAC;EAC5F,OAAO,OAAO,EAAE,KAAK,oBAAoB,IAAI,CAAC,IAAI,EAAE,SAAS;CAC/D,CAAC;CAED,IAAI,IAAI,2BAA2B,OAAO,MAAM;EAC9C,MAAM,OAAO,MAAM,kBAAkB,EAAE,IAAI,MAAM,MAAM,GAAG,EAAE,IAAI,MAAM,QAAQ,CAAC;EAC/E,IAAI,CAAC,MACH,OAAO,EAAE,SAAS;EAEpB,OAAO,EAAE,KAAK,IAAI;CACpB,CAAC;CAED,sBAAsB,GAAG;AAC3B;;AAGA,eAAe,eACb,GACA,SACyC;CACzC,IAAI;EACF,MAAM,QAAQ,MAAM,IAAI,WAAW,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,OAAO;EAC7D,MAAM,UAAU,QAAQ,MAAM,IAAI,aAAa,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,SAAS,IAAI;EACrF,OAAO,SAAS,UAAU,MAAM,mBAAmB,GAAG,SAAS,MAAM,EAAE,oBAAI,IAAI,IAAI;CACrF,QAAQ;EACN,uBAAO,IAAI,IAAI;CACjB;AACF"}
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import "node:path";
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
|
-
import { credentialKeys } from "../credential-keys.mjs";
|
|
5
|
-
import { getStore } from "../store.mjs";
|
|
6
4
|
import { notFound, resolveAuthorizedProject } from "./helpers.mjs";
|
|
7
5
|
import { notFound as notFound$1, unauthorized, webhookCreateSchema, webhookCreatedSchema, webhookPublicSchema } from "./schemas.mjs";
|
|
6
|
+
import { webhookModel } from "./scoped-models.mjs";
|
|
8
7
|
import { createRoute, z } from "@hono/zod-openapi";
|
|
9
8
|
import { WebhookModel } from "@storyshelf/core/models";
|
|
10
|
-
import { randomToken } from "@storyshelf/core/utils";
|
|
11
9
|
//#region src/routers/webhooks.ts
|
|
12
10
|
const ADMIN_ROLES = ["admin"];
|
|
13
11
|
const listWebhooksRoute = createRoute({
|
|
@@ -60,7 +58,7 @@ const deleteWebhookRoute = createRoute({
|
|
|
60
58
|
function registerWebhooks(app) {
|
|
61
59
|
app.openapi(listWebhooksRoute, async (c) => {
|
|
62
60
|
const project = await resolveAuthorizedProject(c, c.req.valid("param").slug, ...ADMIN_ROLES);
|
|
63
|
-
const webhooks = await
|
|
61
|
+
const webhooks = await webhookModel().list(project.id);
|
|
64
62
|
return c.json(webhooks.map((webhook) => ({
|
|
65
63
|
id: webhook.id,
|
|
66
64
|
url: webhook.url,
|
|
@@ -70,11 +68,7 @@ function registerWebhooks(app) {
|
|
|
70
68
|
app.openapi(createWebhookRoute, async (c) => {
|
|
71
69
|
const project = await resolveAuthorizedProject(c, c.req.valid("param").slug, ...ADMIN_ROLES);
|
|
72
70
|
const body = c.req.valid("json");
|
|
73
|
-
const secret =
|
|
74
|
-
const webhook = await new WebhookModel(getStore().db, void 0, credentialKeys(getStore().config)).create(project.id, {
|
|
75
|
-
...body,
|
|
76
|
-
secret
|
|
77
|
-
});
|
|
71
|
+
const { webhook, secret } = await webhookModel().issue(project.id, body);
|
|
78
72
|
return c.json({
|
|
79
73
|
id: webhook.id,
|
|
80
74
|
url: webhook.url,
|
|
@@ -85,9 +79,9 @@ function registerWebhooks(app) {
|
|
|
85
79
|
app.openapi(deleteWebhookRoute, async (c) => {
|
|
86
80
|
const { slug, webhookId } = c.req.valid("param");
|
|
87
81
|
const project = await resolveAuthorizedProject(c, slug, ...ADMIN_ROLES);
|
|
88
|
-
const webhook = await
|
|
82
|
+
const webhook = await webhookModel().get(project.id, webhookId);
|
|
89
83
|
if (!webhook) notFound("Webhook not found");
|
|
90
|
-
await
|
|
84
|
+
await webhookModel().remove(project.id, webhook.id);
|
|
91
85
|
return c.body(null, 204);
|
|
92
86
|
});
|
|
93
87
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhooks.mjs","names":["notFoundResponse"],"sources":["../../src/routers/webhooks.ts"],"sourcesContent":["import { createRoute, z } from \"@hono/zod-openapi\";\nimport { WebhookModel } from \"@storyshelf/core/models\";\nimport type { ProjectRole } from \"@storyshelf/core/types\";\nimport
|
|
1
|
+
{"version":3,"file":"webhooks.mjs","names":["notFoundResponse"],"sources":["../../src/routers/webhooks.ts"],"sourcesContent":["import { createRoute, z } from \"@hono/zod-openapi\";\nimport { WebhookModel } from \"@storyshelf/core/models\";\nimport type { ProjectRole } from \"@storyshelf/core/types\";\nimport type { ShelfRouter } from \"../app-types.ts\";\nimport { resolveAuthorizedProject, notFound } from \"./helpers.ts\";\nimport {\n notFound as notFoundResponse,\n unauthorized,\n webhookCreatedSchema,\n webhookCreateSchema,\n webhookPublicSchema,\n} from \"./schemas.ts\";\nimport { webhookModel } from \"./scoped-models.ts\";\nconst ADMIN_ROLES: readonly ProjectRole[] = [\"admin\"];\n\nconst listWebhooksRoute = createRoute({\n method: \"get\",\n tags: [\"Webhooks\"],\n summary: \"List webhooks\",\n path: \"/api/v1/projects/{slug}/webhooks\",\n request: { params: z.object({ slug: z.string() }) },\n responses: {\n 200: {\n content: { \"application/json\": { schema: webhookPublicSchema.array() } },\n description: \"List webhooks\",\n },\n ...notFoundResponse,\n ...unauthorized,\n },\n});\n\nconst createWebhookRoute = createRoute({\n method: \"post\",\n tags: [\"Webhooks\"],\n summary: \"Create a webhook\",\n path: \"/api/v1/projects/{slug}/webhooks\",\n request: {\n params: z.object({ slug: z.string() }),\n body: { content: { \"application/json\": { schema: webhookCreateSchema } } },\n },\n responses: {\n 201: {\n content: { \"application/json\": { schema: webhookCreatedSchema } },\n description: \"Created webhook\",\n },\n ...notFoundResponse,\n },\n});\n\nconst deleteWebhookRoute = createRoute({\n method: \"delete\",\n tags: [\"Webhooks\"],\n summary: \"Delete a webhook\",\n path: \"/api/v1/projects/{slug}/webhooks/{webhookId}\",\n request: { params: z.object({ slug: z.string(), webhookId: z.string() }) },\n responses: {\n 204: { description: \"Deleted webhook\" },\n ...notFoundResponse,\n },\n});\n\n/** Register the project webhook list, create, and delete endpoints. */\nexport function registerWebhooks(app: ShelfRouter): void {\n app.openapi(listWebhooksRoute, async (c) => {\n const project = await resolveAuthorizedProject(c, c.req.valid(\"param\").slug, ...ADMIN_ROLES);\n const webhooks = await webhookModel().list(project.id);\n return c.json(\n webhooks.map((webhook) => ({\n id: webhook.id,\n url: webhook.url,\n events: WebhookModel.eventsOf(webhook),\n })),\n );\n });\n\n app.openapi(createWebhookRoute, async (c) => {\n const project = await resolveAuthorizedProject(c, c.req.valid(\"param\").slug, ...ADMIN_ROLES);\n const body = c.req.valid(\"json\");\n const { webhook, secret } = await webhookModel().issue(project.id, body);\n return c.json({ id: webhook.id, url: webhook.url, events: body.events ?? [], secret }, 201);\n });\n\n app.openapi(deleteWebhookRoute, async (c) => {\n const { slug, webhookId } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...ADMIN_ROLES);\n const webhook = await webhookModel().get(project.id, webhookId);\n if (!webhook) {\n notFound(\"Webhook not found\");\n }\n await webhookModel().remove(project.id, webhook.id);\n return c.body(null, 204);\n });\n}\n"],"mappings":";;;;;;;;;AAaA,MAAM,cAAsC,CAAC,OAAO;AAEpD,MAAM,oBAAoB,YAAY;CACpC,QAAQ;CACR,MAAM,CAAC,UAAU;CACjB,SAAS;CACT,MAAM;CACN,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE;CAClD,WAAW;EACT,KAAK;GACH,SAAS,EAAE,oBAAoB,EAAE,QAAQ,oBAAoB,MAAM,EAAE,EAAE;GACvE,aAAa;EACf;EACA,GAAGA;EACH,GAAG;CACL;AACF,CAAC;AAED,MAAM,qBAAqB,YAAY;CACrC,QAAQ;CACR,MAAM,CAAC,UAAU;CACjB,SAAS;CACT,MAAM;CACN,SAAS;EACP,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;EACrC,MAAM,EAAE,SAAS,EAAE,oBAAoB,EAAE,QAAQ,oBAAoB,EAAE,EAAE;CAC3E;CACA,WAAW;EACT,KAAK;GACH,SAAS,EAAE,oBAAoB,EAAE,QAAQ,qBAAqB,EAAE;GAChE,aAAa;EACf;EACA,GAAGA;CACL;AACF,CAAC;AAED,MAAM,qBAAqB,YAAY;CACrC,QAAQ;CACR,MAAM,CAAC,UAAU;CACjB,SAAS;CACT,MAAM;CACN,SAAS,EAAE,QAAQ,EAAE,OAAO;EAAE,MAAM,EAAE,OAAO;EAAG,WAAW,EAAE,OAAO;CAAE,CAAC,EAAE;CACzE,WAAW;EACT,KAAK,EAAE,aAAa,kBAAkB;EACtC,GAAGA;CACL;AACF,CAAC;;AAGD,SAAgB,iBAAiB,KAAwB;CACvD,IAAI,QAAQ,mBAAmB,OAAO,MAAM;EAC1C,MAAM,UAAU,MAAM,yBAAyB,GAAG,EAAE,IAAI,MAAM,OAAO,CAAC,CAAC,MAAM,GAAG,WAAW;EAC3F,MAAM,WAAW,MAAM,aAAa,CAAC,CAAC,KAAK,QAAQ,EAAE;EACrD,OAAO,EAAE,KACP,SAAS,KAAK,aAAa;GACzB,IAAI,QAAQ;GACZ,KAAK,QAAQ;GACb,QAAQ,aAAa,SAAS,OAAO;EACvC,EAAE,CACJ;CACF,CAAC;CAED,IAAI,QAAQ,oBAAoB,OAAO,MAAM;EAC3C,MAAM,UAAU,MAAM,yBAAyB,GAAG,EAAE,IAAI,MAAM,OAAO,CAAC,CAAC,MAAM,GAAG,WAAW;EAC3F,MAAM,OAAO,EAAE,IAAI,MAAM,MAAM;EAC/B,MAAM,EAAE,SAAS,WAAW,MAAM,aAAa,CAAC,CAAC,MAAM,QAAQ,IAAI,IAAI;EACvE,OAAO,EAAE,KAAK;GAAE,IAAI,QAAQ;GAAI,KAAK,QAAQ;GAAK,QAAQ,KAAK,UAAU,CAAC;GAAG;EAAO,GAAG,GAAG;CAC5F,CAAC;CAED,IAAI,QAAQ,oBAAoB,OAAO,MAAM;EAC3C,MAAM,EAAE,MAAM,cAAc,EAAE,IAAI,MAAM,OAAO;EAC/C,MAAM,UAAU,MAAM,yBAAyB,GAAG,MAAM,GAAG,WAAW;EACtE,MAAM,UAAU,MAAM,aAAa,CAAC,CAAC,IAAI,QAAQ,IAAI,SAAS;EAC9D,IAAI,CAAC,SACH,SAAS,mBAAmB;EAE9B,MAAM,aAAa,CAAC,CAAC,OAAO,QAAQ,IAAI,QAAQ,EAAE;EAClD,OAAO,EAAE,KAAK,MAAM,GAAG;CACzB,CAAC;AACH"}
|
|
@@ -11,13 +11,20 @@ function ssoProviders(auth, origin) {
|
|
|
11
11
|
redirectUris: method.kind === "sso" ? [`${origin}/api/auth/sso/callback/${method.id}`, `${origin}/api/auth/sso/saml2/sp/acs/${method.id}`] : [`${origin}/api/auth/callback/${method.id}`]
|
|
12
12
|
}));
|
|
13
13
|
}
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
/**
|
|
15
|
+
* The origin of this interactive, request-bound document (ADR 0007, decision 6): the confirmed
|
|
16
|
+
* public origin, else the address the request came in on (the one place besides OAuth redirect
|
|
17
|
+
* URIs where authentication may use it).
|
|
18
|
+
*/
|
|
19
|
+
async function requestOrigin(c) {
|
|
20
|
+
const { publicOrigin, config } = getStore();
|
|
21
|
+
const own = new URL(c.req.url).origin;
|
|
22
|
+
return publicOrigin ? await publicOrigin.authOrigin(own) : config.publicBaseUrl ?? own;
|
|
16
23
|
}
|
|
17
24
|
/** Register public relying-party helper documents (no auth required). */
|
|
18
25
|
function registerWellKnown(app, auth) {
|
|
19
|
-
app.get("/.well-known/openid-configuration", (c) => {
|
|
20
|
-
const origin = requestOrigin(c);
|
|
26
|
+
app.get("/.well-known/openid-configuration", async (c) => {
|
|
27
|
+
const origin = await requestOrigin(c);
|
|
21
28
|
const providers = ssoProviders(auth, origin);
|
|
22
29
|
return c.json({
|
|
23
30
|
issuer: origin,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"well-known.mjs","names":[],"sources":["../../src/routers/well-known.ts"],"sourcesContent":["import type { Auth } from \"@storyshelf/auth\";\nimport type { Context } from \"hono\";\nimport type { ShelfRouter } from \"../app-types.ts\";\nimport { getStore } from \"../store.ts\";\n\n/** One SSO-capable login method with its IdP reply URLs. */\ninterface SsoProviderLink {\n id: string;\n label: string;\n redirectUris: string[];\n}\n\n/** SSO-capable login methods (oauth buttons + SSO) with IdP reply URLs. */\nfunction ssoProviders(auth: Auth, origin: string): SsoProviderLink[] {\n return auth\n .loginMethods()\n .filter((method) => method.kind === \"oauth\" || method.kind === \"sso\")\n .map((method) => ({\n id: method.id,\n label: method.label,\n redirectUris:\n method.kind === \"sso\"\n ? [\n `${origin}/api/auth/sso/callback/${method.id}`,\n `${origin}/api/auth/sso/saml2/sp/acs/${method.id}`,\n ]\n : [`${origin}/api/auth/callback/${method.id}`],\n }));\n}\n\
|
|
1
|
+
{"version":3,"file":"well-known.mjs","names":[],"sources":["../../src/routers/well-known.ts"],"sourcesContent":["import type { Auth } from \"@storyshelf/core/auth\";\nimport type { Context } from \"hono\";\nimport type { ShelfRouter } from \"../app-types.ts\";\nimport { getStore } from \"../store.ts\";\n\n/** One SSO-capable login method with its IdP reply URLs. */\ninterface SsoProviderLink {\n id: string;\n label: string;\n redirectUris: string[];\n}\n\n/** SSO-capable login methods (oauth buttons + SSO) with IdP reply URLs. */\nfunction ssoProviders(auth: Auth, origin: string): SsoProviderLink[] {\n return auth\n .loginMethods()\n .filter((method) => method.kind === \"oauth\" || method.kind === \"sso\")\n .map((method) => ({\n id: method.id,\n label: method.label,\n redirectUris:\n method.kind === \"sso\"\n ? [\n `${origin}/api/auth/sso/callback/${method.id}`,\n `${origin}/api/auth/sso/saml2/sp/acs/${method.id}`,\n ]\n : [`${origin}/api/auth/callback/${method.id}`],\n }));\n}\n\n/**\n * The origin of this interactive, request-bound document (ADR 0007, decision 6): the confirmed\n * public origin, else the address the request came in on (the one place besides OAuth redirect\n * URIs where authentication may use it).\n */\nasync function requestOrigin(c: Context): Promise<string> {\n const { publicOrigin, config } = getStore();\n const own = new URL(c.req.url).origin;\n return publicOrigin ? await publicOrigin.authOrigin(own) : (config.publicBaseUrl ?? own);\n}\n\n/** Register public relying-party helper documents (no auth required). */\nexport function registerWellKnown(app: ShelfRouter, auth: Auth): void {\n app.get(\"/.well-known/openid-configuration\", async (c) => {\n const origin = await requestOrigin(c);\n const providers = ssoProviders(auth, origin);\n return c.json(\n {\n issuer: origin,\n note: \"StoryShelf is an OpenID Connect relying party, not an identity provider. This document helps operators register redirect URIs with their provider; it is not provider metadata.\",\n response_types_supported: [\"code\"],\n scopes_supported: [\"openid\", \"email\", \"profile\"],\n redirect_uris: providers.flatMap((provider) => provider.redirectUris),\n providers: providers.map((method) => ({ id: method.id, label: method.label })),\n },\n 200,\n { \"Cache-Control\": \"public, max-age=300\" },\n );\n });\n\n // oxlint-disable-next-line typescript/promise-function-async -- Hono handler may return Response directly\n app.get(\"/.well-known/change-password\", (c) => c.redirect(\"/profile\", 302));\n}\n"],"mappings":";;;;;;AAaA,SAAS,aAAa,MAAY,QAAmC;CACnE,OAAO,KACJ,aAAa,CAAC,CACd,QAAQ,WAAW,OAAO,SAAS,WAAW,OAAO,SAAS,KAAK,CAAC,CACpE,KAAK,YAAY;EAChB,IAAI,OAAO;EACX,OAAO,OAAO;EACd,cACE,OAAO,SAAS,QACZ,CACE,GAAG,OAAO,yBAAyB,OAAO,MAC1C,GAAG,OAAO,6BAA6B,OAAO,IAChD,IACA,CAAC,GAAG,OAAO,qBAAqB,OAAO,IAAI;CACnD,EAAE;AACN;;;;;;AAOA,eAAe,cAAc,GAA6B;CACxD,MAAM,EAAE,cAAc,WAAW,SAAS;CAC1C,MAAM,MAAM,IAAI,IAAI,EAAE,IAAI,GAAG,CAAC,CAAC;CAC/B,OAAO,eAAe,MAAM,aAAa,WAAW,GAAG,IAAK,OAAO,iBAAiB;AACtF;;AAGA,SAAgB,kBAAkB,KAAkB,MAAkB;CACpE,IAAI,IAAI,qCAAqC,OAAO,MAAM;EACxD,MAAM,SAAS,MAAM,cAAc,CAAC;EACpC,MAAM,YAAY,aAAa,MAAM,MAAM;EAC3C,OAAO,EAAE,KACP;GACE,QAAQ;GACR,MAAM;GACN,0BAA0B,CAAC,MAAM;GACjC,kBAAkB;IAAC;IAAU;IAAS;GAAS;GAC/C,eAAe,UAAU,SAAS,aAAa,SAAS,YAAY;GACpE,WAAW,UAAU,KAAK,YAAY;IAAE,IAAI,OAAO;IAAI,OAAO,OAAO;GAAM,EAAE;EAC/E,GACA,KACA,EAAE,iBAAiB,sBAAsB,CAC3C;CACF,CAAC;CAGD,IAAI,IAAI,iCAAiC,MAAM,EAAE,SAAS,YAAY,GAAG,CAAC;AAC5E"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
//#region src/routes/api-routes.ts
|
|
5
|
+
/** JSON API routes, keyed by `METHOD /hono-pattern`. */
|
|
6
|
+
const API_ROUTE_CLASSES = {
|
|
7
|
+
"GET /api/v1/projects": "hosting",
|
|
8
|
+
"POST /api/v1/projects": "hosting",
|
|
9
|
+
"GET /api/v1/projects/:slug": "hosting",
|
|
10
|
+
"PATCH /api/v1/projects/:slug": "hosting",
|
|
11
|
+
"DELETE /api/v1/projects/:slug": "hosting",
|
|
12
|
+
"GET /api/v1/projects/:slug/builds": "hosting",
|
|
13
|
+
"POST /api/v1/projects/:slug/builds": "hosting",
|
|
14
|
+
"POST /api/v1/projects/:slug/builds/:buildId/usage": "hosting",
|
|
15
|
+
"PUT /api/v1/projects/:slug/builds/:buildId/zip": "hosting",
|
|
16
|
+
"POST /api/v1/projects/:slug/builds/:buildId/dedup": "hosting",
|
|
17
|
+
"POST /api/v1/projects/:slug/builds/:buildId/content": "hosting",
|
|
18
|
+
"POST /api/v1/projects/:slug/builds/:buildId/manifest": "hosting",
|
|
19
|
+
"GET /api/v1/projects/:slug/builds/:buildId": "hosting",
|
|
20
|
+
"DELETE /api/v1/projects/:slug/builds/:buildId": "hosting",
|
|
21
|
+
"GET /api/v1/projects/:slug/label-types": "hosting",
|
|
22
|
+
"POST /api/v1/projects/:slug/label-types": "hosting",
|
|
23
|
+
"DELETE /api/v1/projects/:slug/label-types/:key": "hosting",
|
|
24
|
+
"PATCH /api/v1/projects/:slug/label-types/:key": "hosting",
|
|
25
|
+
"GET /api/v1/projects/:slug/links": "hosting",
|
|
26
|
+
"PUT /api/v1/projects/:slug/links": "hosting",
|
|
27
|
+
"GET /api/v1/projects/:slug/members": "hosting",
|
|
28
|
+
"POST /api/v1/projects/:slug/members": "hosting",
|
|
29
|
+
"PATCH /api/v1/projects/:slug/members/:userId": "hosting",
|
|
30
|
+
"DELETE /api/v1/projects/:slug/members/:userId": "hosting",
|
|
31
|
+
"GET /api/v1/projects/:slug/group-mappings": "hosting",
|
|
32
|
+
"POST /api/v1/projects/:slug/group-mappings": "hosting",
|
|
33
|
+
"DELETE /api/v1/projects/:slug/group-mappings/:mappingId": "hosting",
|
|
34
|
+
"GET /api/v1/projects/:slug/tokens": "hosting",
|
|
35
|
+
"POST /api/v1/projects/:slug/tokens": "hosting",
|
|
36
|
+
"DELETE /api/v1/projects/:slug/tokens/:tokenId": "hosting",
|
|
37
|
+
"GET /api/v1/projects/:slug/webhooks": "hosting",
|
|
38
|
+
"POST /api/v1/projects/:slug/webhooks": "hosting",
|
|
39
|
+
"DELETE /api/v1/projects/:slug/webhooks/:webhookId": "hosting",
|
|
40
|
+
"GET /api/v1/projects/:slug/notification-channels": "hosting",
|
|
41
|
+
"POST /api/v1/projects/:slug/notification-channels": "hosting",
|
|
42
|
+
"DELETE /api/v1/projects/:slug/notification-channels/:channelId": "hosting",
|
|
43
|
+
"GET /api/v1/projects/:slug/notifications/me": "hosting",
|
|
44
|
+
"PUT /api/v1/projects/:slug/notifications/me": "hosting",
|
|
45
|
+
"DELETE /api/v1/projects/:slug/notifications/me": "hosting",
|
|
46
|
+
"GET /api/v1/projects/:slug/notifications": "hosting",
|
|
47
|
+
"GET /api/v1/admin/notification-channels": "hosting",
|
|
48
|
+
"POST /api/v1/admin/notification-channels": "hosting",
|
|
49
|
+
"DELETE /api/v1/admin/notification-channels/:channelId": "hosting",
|
|
50
|
+
"GET /api/v1/health": "hosting",
|
|
51
|
+
"POST /api/v1/health": "hosting",
|
|
52
|
+
"POST /api/v1/admin/credentials/reencrypt": "hosting",
|
|
53
|
+
"POST /api/v1/admin/purge": "hosting",
|
|
54
|
+
"GET /api/v1/openapi.json": "hosting",
|
|
55
|
+
"GET /api/v1/docs": "hosting",
|
|
56
|
+
"POST /api/v1/projects/:slug/builds/:buildId/affected": "testing",
|
|
57
|
+
"POST /api/v1/projects/:slug/builds/:buildId/retry": "testing",
|
|
58
|
+
"POST /api/v1/projects/:slug/builds/:buildId/cancel": "testing",
|
|
59
|
+
"GET /api/v1/projects/:slug/builds/:buildId/snapshots": "testing",
|
|
60
|
+
"GET /api/v1/projects/:slug/builds/:buildId/preview-token": "testing",
|
|
61
|
+
"POST /api/v1/projects/:slug/builds/:buildId/snapshots/:snapshotId/approve": "testing",
|
|
62
|
+
"POST /api/v1/projects/:slug/builds/:buildId/snapshots/:snapshotId/reject": "testing",
|
|
63
|
+
"POST /api/v1/projects/:slug/builds/:buildId/approve-all": "testing",
|
|
64
|
+
"POST /api/v1/projects/:slug/builds/:buildId/reject-all": "testing",
|
|
65
|
+
"POST /api/v1/projects/:slug/builds/:buildId/stories/:storyId/modes/:modeKey/approve": "testing",
|
|
66
|
+
"POST /api/v1/projects/:slug/builds/:buildId/stories/:storyId/modes/:modeKey/reject": "testing",
|
|
67
|
+
"POST /api/v1/projects/:slug/builds/:buildId/rediff": "testing",
|
|
68
|
+
"GET /api/v1/projects/:slug/builds/:buildId/attempts": "testing",
|
|
69
|
+
"GET /api/v1/projects/:slug/builds/:buildId/attempts/:attemptNo": "testing",
|
|
70
|
+
"GET /api/v1/projects/:slug/builds/:buildId/attempts/:attemptNo/logs": "testing",
|
|
71
|
+
"GET /api/v1/projects/:slug/builds/:buildId/snapshots/:snapshotId/usage": "testing",
|
|
72
|
+
"GET /api/v1/projects/:slug/builds/:buildId/snapshots/:snapshotId/image": "testing",
|
|
73
|
+
"GET /api/v1/projects/:slug/builds/:buildId/snapshots/:snapshotId/diff": "testing",
|
|
74
|
+
"GET /api/v1/projects/:slug/builds/:buildId/snapshots/:snapshotId/baseline": "testing",
|
|
75
|
+
"GET /api/v1/projects/:slug/design/renders/:renderId": "testing",
|
|
76
|
+
"GET /api/v1/projects/:slug/baselines/export": "testing",
|
|
77
|
+
"POST /api/v1/projects/:slug/baselines/import": "testing",
|
|
78
|
+
"GET /api/v1/projects/:slug/builds/:buildId/insights/latest": "testing",
|
|
79
|
+
"GET /api/v1/projects/:slug/builds/:buildId/insights": "testing",
|
|
80
|
+
"POST /api/v1/projects/:slug/builds/:buildId/insights": "testing",
|
|
81
|
+
"GET /api/v1/projects/:slug/insights/health": "testing",
|
|
82
|
+
"POST /api/v1/projects/:slug/insights/health": "testing",
|
|
83
|
+
"GET /api/v1/projects/:slug/builds/:buildId/comments": "review",
|
|
84
|
+
"POST /api/v1/projects/:slug/builds/:buildId/comments": "review",
|
|
85
|
+
"POST /api/v1/projects/:slug/builds/:buildId/comments/:commentId/resolve": "review",
|
|
86
|
+
"GET /api/v1/projects/:slug/builds/:buildId/reviews": "review",
|
|
87
|
+
"POST /api/v1/projects/:slug/builds/:buildId/reviews": "review",
|
|
88
|
+
"GET /api/v1/projects/:slug/builds/:buildId/changeset": "review"
|
|
89
|
+
};
|
|
90
|
+
//#endregion
|
|
91
|
+
export { API_ROUTE_CLASSES };
|
|
92
|
+
|
|
93
|
+
//# sourceMappingURL=api-routes.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-routes.mjs","names":[],"sources":["../../src/routes/api-routes.ts"],"sourcesContent":["/** Classes of the JSON API routes under `/api/v1`. */\nimport type { RouteClass } from \"./classification.ts\";\n\n/** JSON API routes, keyed by `METHOD /hono-pattern`. */\nexport const API_ROUTE_CLASSES: Readonly<Record<string, RouteClass>> = {\n // hosting\n \"GET /api/v1/projects\": \"hosting\",\n \"POST /api/v1/projects\": \"hosting\",\n \"GET /api/v1/projects/:slug\": \"hosting\",\n \"PATCH /api/v1/projects/:slug\": \"hosting\",\n \"DELETE /api/v1/projects/:slug\": \"hosting\",\n \"GET /api/v1/projects/:slug/builds\": \"hosting\",\n \"POST /api/v1/projects/:slug/builds\": \"hosting\",\n \"POST /api/v1/projects/:slug/builds/:buildId/usage\": \"hosting\",\n \"PUT /api/v1/projects/:slug/builds/:buildId/zip\": \"hosting\",\n \"POST /api/v1/projects/:slug/builds/:buildId/dedup\": \"hosting\",\n \"POST /api/v1/projects/:slug/builds/:buildId/content\": \"hosting\",\n \"POST /api/v1/projects/:slug/builds/:buildId/manifest\": \"hosting\",\n \"GET /api/v1/projects/:slug/builds/:buildId\": \"hosting\",\n \"DELETE /api/v1/projects/:slug/builds/:buildId\": \"hosting\",\n \"GET /api/v1/projects/:slug/label-types\": \"hosting\",\n \"POST /api/v1/projects/:slug/label-types\": \"hosting\",\n \"DELETE /api/v1/projects/:slug/label-types/:key\": \"hosting\",\n \"PATCH /api/v1/projects/:slug/label-types/:key\": \"hosting\",\n \"GET /api/v1/projects/:slug/links\": \"hosting\",\n \"PUT /api/v1/projects/:slug/links\": \"hosting\",\n \"GET /api/v1/projects/:slug/members\": \"hosting\",\n \"POST /api/v1/projects/:slug/members\": \"hosting\",\n \"PATCH /api/v1/projects/:slug/members/:userId\": \"hosting\",\n \"DELETE /api/v1/projects/:slug/members/:userId\": \"hosting\",\n \"GET /api/v1/projects/:slug/group-mappings\": \"hosting\",\n \"POST /api/v1/projects/:slug/group-mappings\": \"hosting\",\n \"DELETE /api/v1/projects/:slug/group-mappings/:mappingId\": \"hosting\",\n \"GET /api/v1/projects/:slug/tokens\": \"hosting\",\n \"POST /api/v1/projects/:slug/tokens\": \"hosting\",\n \"DELETE /api/v1/projects/:slug/tokens/:tokenId\": \"hosting\",\n \"GET /api/v1/projects/:slug/webhooks\": \"hosting\",\n \"POST /api/v1/projects/:slug/webhooks\": \"hosting\",\n \"DELETE /api/v1/projects/:slug/webhooks/:webhookId\": \"hosting\",\n \"GET /api/v1/projects/:slug/notification-channels\": \"hosting\",\n \"POST /api/v1/projects/:slug/notification-channels\": \"hosting\",\n \"DELETE /api/v1/projects/:slug/notification-channels/:channelId\": \"hosting\",\n \"GET /api/v1/projects/:slug/notifications/me\": \"hosting\",\n \"PUT /api/v1/projects/:slug/notifications/me\": \"hosting\",\n \"DELETE /api/v1/projects/:slug/notifications/me\": \"hosting\",\n \"GET /api/v1/projects/:slug/notifications\": \"hosting\",\n \"GET /api/v1/admin/notification-channels\": \"hosting\",\n \"POST /api/v1/admin/notification-channels\": \"hosting\",\n \"DELETE /api/v1/admin/notification-channels/:channelId\": \"hosting\",\n \"GET /api/v1/health\": \"hosting\",\n \"POST /api/v1/health\": \"hosting\",\n \"POST /api/v1/admin/credentials/reencrypt\": \"hosting\",\n \"POST /api/v1/admin/purge\": \"hosting\",\n \"GET /api/v1/openapi.json\": \"hosting\",\n \"GET /api/v1/docs\": \"hosting\",\n // testing\n \"POST /api/v1/projects/:slug/builds/:buildId/affected\": \"testing\",\n \"POST /api/v1/projects/:slug/builds/:buildId/retry\": \"testing\",\n \"POST /api/v1/projects/:slug/builds/:buildId/cancel\": \"testing\",\n \"GET /api/v1/projects/:slug/builds/:buildId/snapshots\": \"testing\",\n \"GET /api/v1/projects/:slug/builds/:buildId/preview-token\": \"testing\",\n \"POST /api/v1/projects/:slug/builds/:buildId/snapshots/:snapshotId/approve\": \"testing\",\n \"POST /api/v1/projects/:slug/builds/:buildId/snapshots/:snapshotId/reject\": \"testing\",\n \"POST /api/v1/projects/:slug/builds/:buildId/approve-all\": \"testing\",\n \"POST /api/v1/projects/:slug/builds/:buildId/reject-all\": \"testing\",\n \"POST /api/v1/projects/:slug/builds/:buildId/stories/:storyId/modes/:modeKey/approve\": \"testing\",\n \"POST /api/v1/projects/:slug/builds/:buildId/stories/:storyId/modes/:modeKey/reject\": \"testing\",\n \"POST /api/v1/projects/:slug/builds/:buildId/rediff\": \"testing\",\n \"GET /api/v1/projects/:slug/builds/:buildId/attempts\": \"testing\",\n \"GET /api/v1/projects/:slug/builds/:buildId/attempts/:attemptNo\": \"testing\",\n \"GET /api/v1/projects/:slug/builds/:buildId/attempts/:attemptNo/logs\": \"testing\",\n \"GET /api/v1/projects/:slug/builds/:buildId/snapshots/:snapshotId/usage\": \"testing\",\n \"GET /api/v1/projects/:slug/builds/:buildId/snapshots/:snapshotId/image\": \"testing\",\n \"GET /api/v1/projects/:slug/builds/:buildId/snapshots/:snapshotId/diff\": \"testing\",\n \"GET /api/v1/projects/:slug/builds/:buildId/snapshots/:snapshotId/baseline\": \"testing\",\n \"GET /api/v1/projects/:slug/design/renders/:renderId\": \"testing\",\n \"GET /api/v1/projects/:slug/baselines/export\": \"testing\",\n \"POST /api/v1/projects/:slug/baselines/import\": \"testing\",\n \"GET /api/v1/projects/:slug/builds/:buildId/insights/latest\": \"testing\",\n \"GET /api/v1/projects/:slug/builds/:buildId/insights\": \"testing\",\n \"POST /api/v1/projects/:slug/builds/:buildId/insights\": \"testing\",\n \"GET /api/v1/projects/:slug/insights/health\": \"testing\",\n \"POST /api/v1/projects/:slug/insights/health\": \"testing\",\n // review\n \"GET /api/v1/projects/:slug/builds/:buildId/comments\": \"review\",\n \"POST /api/v1/projects/:slug/builds/:buildId/comments\": \"review\",\n \"POST /api/v1/projects/:slug/builds/:buildId/comments/:commentId/resolve\": \"review\",\n \"GET /api/v1/projects/:slug/builds/:buildId/reviews\": \"review\",\n \"POST /api/v1/projects/:slug/builds/:buildId/reviews\": \"review\",\n \"GET /api/v1/projects/:slug/builds/:buildId/changeset\": \"review\",\n};\n"],"mappings":";;;;;AAIA,MAAa,oBAA0D;CAErE,wBAAwB;CACxB,yBAAyB;CACzB,8BAA8B;CAC9B,gCAAgC;CAChC,iCAAiC;CACjC,qCAAqC;CACrC,sCAAsC;CACtC,qDAAqD;CACrD,kDAAkD;CAClD,qDAAqD;CACrD,uDAAuD;CACvD,wDAAwD;CACxD,8CAA8C;CAC9C,iDAAiD;CACjD,0CAA0C;CAC1C,2CAA2C;CAC3C,kDAAkD;CAClD,iDAAiD;CACjD,oCAAoC;CACpC,oCAAoC;CACpC,sCAAsC;CACtC,uCAAuC;CACvC,gDAAgD;CAChD,iDAAiD;CACjD,6CAA6C;CAC7C,8CAA8C;CAC9C,2DAA2D;CAC3D,qCAAqC;CACrC,sCAAsC;CACtC,iDAAiD;CACjD,uCAAuC;CACvC,wCAAwC;CACxC,qDAAqD;CACrD,oDAAoD;CACpD,qDAAqD;CACrD,kEAAkE;CAClE,+CAA+C;CAC/C,+CAA+C;CAC/C,kDAAkD;CAClD,4CAA4C;CAC5C,2CAA2C;CAC3C,4CAA4C;CAC5C,yDAAyD;CACzD,sBAAsB;CACtB,uBAAuB;CACvB,4CAA4C;CAC5C,4BAA4B;CAC5B,4BAA4B;CAC5B,oBAAoB;CAEpB,wDAAwD;CACxD,qDAAqD;CACrD,sDAAsD;CACtD,wDAAwD;CACxD,4DAA4D;CAC5D,6EAA6E;CAC7E,4EAA4E;CAC5E,2DAA2D;CAC3D,0DAA0D;CAC1D,uFAAuF;CACvF,sFAAsF;CACtF,sDAAsD;CACtD,uDAAuD;CACvD,kEAAkE;CAClE,uEAAuE;CACvE,0EAA0E;CAC1E,0EAA0E;CAC1E,yEAAyE;CACzE,6EAA6E;CAC7E,uDAAuD;CACvD,+CAA+C;CAC/C,gDAAgD;CAChD,8DAA8D;CAC9D,uDAAuD;CACvD,wDAAwD;CACxD,8CAA8C;CAC9C,+CAA+C;CAE/C,uDAAuD;CACvD,wDAAwD;CACxD,2EAA2E;CAC3E,sDAAsD;CACtD,uDAAuD;CACvD,wDAAwD;AAC1D"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { API_ROUTE_CLASSES } from "./api-routes.mjs";
|
|
5
|
+
import { PAGE_ROUTE_CLASSES } from "./page-routes.mjs";
|
|
6
|
+
//#region src/routes/classification.ts
|
|
7
|
+
/**
|
|
8
|
+
* Route classification (ADR 0006, decision 8): every route the app registers declares whether it
|
|
9
|
+
* belongs to hosting, testing, review, or both. `classification.test.ts` fails on any registered
|
|
10
|
+
* route missing here and on any entry that no longer matches a route, so a new route must be
|
|
11
|
+
* classified in `api-routes.ts` or `page-routes.ts` in the same change.
|
|
12
|
+
*
|
|
13
|
+
* Today the table only describes the routes; nothing is gated by it yet (gating comes with the
|
|
14
|
+
* `testing` option work, which will apply it as middleware).
|
|
15
|
+
*/
|
|
16
|
+
/** The full table, keyed by `"METHOD /hono-pattern"` exactly as `app.routes` reports it. */
|
|
17
|
+
const ROUTE_CLASSES = {
|
|
18
|
+
...API_ROUTE_CLASSES,
|
|
19
|
+
...PAGE_ROUTE_CLASSES
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Route families whose individual patterns are not worth listing (content-hashed static assets).
|
|
23
|
+
* A rule applies to any method whose path starts with `prefix`.
|
|
24
|
+
*/
|
|
25
|
+
const ROUTE_PREFIX_CLASSES = [{
|
|
26
|
+
prefix: "/assets/",
|
|
27
|
+
routeClass: "hosting"
|
|
28
|
+
}];
|
|
29
|
+
/** The class of a registered route, or `undefined` when it is unclassified. */
|
|
30
|
+
function classOfRoute(method, path) {
|
|
31
|
+
const exact = ROUTE_CLASSES[`${method} ${path}`];
|
|
32
|
+
if (exact !== void 0) return exact;
|
|
33
|
+
return ROUTE_PREFIX_CLASSES.find((rule) => path.startsWith(rule.prefix))?.routeClass;
|
|
34
|
+
}
|
|
35
|
+
//#endregion
|
|
36
|
+
export { ROUTE_CLASSES, ROUTE_PREFIX_CLASSES, classOfRoute };
|
|
37
|
+
|
|
38
|
+
//# sourceMappingURL=classification.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"classification.mjs","names":[],"sources":["../../src/routes/classification.ts"],"sourcesContent":["/**\n * Route classification (ADR 0006, decision 8): every route the app registers declares whether it\n * belongs to hosting, testing, review, or both. `classification.test.ts` fails on any registered\n * route missing here and on any entry that no longer matches a route, so a new route must be\n * classified in `api-routes.ts` or `page-routes.ts` in the same change.\n *\n * Today the table only describes the routes; nothing is gated by it yet (gating comes with the\n * `testing` option work, which will apply it as middleware).\n */\nimport { API_ROUTE_CLASSES } from \"./api-routes.ts\";\nimport { PAGE_ROUTE_CLASSES } from \"./page-routes.ts\";\n\n/**\n * - `hosting`: available with and without `testing`.\n * - `testing`: 404 without `testing`.\n * - `review`: 404 when the project's review switch is off.\n * - `both`: present in both modes, with sections gated by `testing`.\n */\nexport type RouteClass = \"hosting\" | \"testing\" | \"review\" | \"both\";\n\nexport const ROUTE_CLASS_VALUES: readonly RouteClass[] = [\"hosting\", \"testing\", \"review\", \"both\"];\n\n/** The full table, keyed by `\"METHOD /hono-pattern\"` exactly as `app.routes` reports it. */\nexport const ROUTE_CLASSES: Readonly<Record<string, RouteClass>> = {\n ...API_ROUTE_CLASSES,\n ...PAGE_ROUTE_CLASSES,\n};\n\n/**\n * Route families whose individual patterns are not worth listing (content-hashed static assets).\n * A rule applies to any method whose path starts with `prefix`.\n */\nexport const ROUTE_PREFIX_CLASSES: readonly { prefix: string; routeClass: RouteClass }[] = [\n { prefix: \"/assets/\", routeClass: \"hosting\" },\n];\n\n/** The class of a registered route, or `undefined` when it is unclassified. */\nexport function classOfRoute(method: string, path: string): RouteClass | undefined {\n const exact = ROUTE_CLASSES[`${method} ${path}`];\n if (exact !== undefined) {\n return exact;\n }\n return ROUTE_PREFIX_CLASSES.find((rule) => path.startsWith(rule.prefix))?.routeClass;\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAuBA,MAAa,gBAAsD;CACjE,GAAG;CACH,GAAG;AACL;;;;;AAMA,MAAa,uBAA8E,CACzF;CAAE,QAAQ;CAAY,YAAY;AAAU,CAC9C;;AAGA,SAAgB,aAAa,QAAgB,MAAsC;CACjF,MAAM,QAAQ,cAAc,GAAG,OAAO,GAAG;CACzC,IAAI,UAAU,KAAA,GACZ,OAAO;CAET,OAAO,qBAAqB,MAAM,SAAS,KAAK,WAAW,KAAK,MAAM,CAAC,CAAC,EAAE;AAC5E"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { classOfRoute } from "./classification.mjs";
|
|
5
|
+
//#region src/routes/openapi-classes.ts
|
|
6
|
+
/** Tags every operation of an OpenAPI document with the class of its route (`x-storyshelf-class`). */
|
|
7
|
+
/** OpenAPI path templates (`{slug}`) as Hono patterns (`:slug`), the form the route table uses. */
|
|
8
|
+
function honoPattern(path) {
|
|
9
|
+
return path.replaceAll(/\{(?<name>[^}]+)\}/gu, ":$<name>");
|
|
10
|
+
}
|
|
11
|
+
/** `document` with `x-storyshelf-class` on each operation whose route the table classifies. */
|
|
12
|
+
function markRouteClasses(document) {
|
|
13
|
+
for (const [path, operations] of Object.entries(document.paths ?? {})) for (const [method, operation] of Object.entries(operations)) {
|
|
14
|
+
const routeClass = classOfRoute(method.toUpperCase(), honoPattern(path));
|
|
15
|
+
if (routeClass !== void 0 && typeof operation === "object" && operation !== null) operation["x-storyshelf-class"] = routeClass;
|
|
16
|
+
}
|
|
17
|
+
return document;
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
export { markRouteClasses };
|
|
21
|
+
|
|
22
|
+
//# sourceMappingURL=openapi-classes.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openapi-classes.mjs","names":[],"sources":["../../src/routes/openapi-classes.ts"],"sourcesContent":["/** Tags every operation of an OpenAPI document with the class of its route (`x-storyshelf-class`). */\nimport { classOfRoute } from \"./classification.ts\";\n\ninterface OpenApiLike {\n paths?: object | undefined;\n}\n\n/** OpenAPI path templates (`{slug}`) as Hono patterns (`:slug`), the form the route table uses. */\nfunction honoPattern(path: string): string {\n return path.replaceAll(/\\{(?<name>[^}]+)\\}/gu, \":$<name>\");\n}\n\n/** `document` with `x-storyshelf-class` on each operation whose route the table classifies. */\nexport function markRouteClasses<T extends OpenApiLike>(document: T): T {\n for (const [path, operations] of Object.entries(document.paths ?? {}) as [string, object][]) {\n for (const [method, operation] of Object.entries(operations)) {\n const routeClass = classOfRoute(method.toUpperCase(), honoPattern(path));\n if (routeClass !== undefined && typeof operation === \"object\" && operation !== null) {\n (operation as Record<string, unknown>)[\"x-storyshelf-class\"] = routeClass;\n }\n }\n }\n return document;\n}\n"],"mappings":";;;;;;;AAQA,SAAS,YAAY,MAAsB;CACzC,OAAO,KAAK,WAAW,wBAAwB,UAAU;AAC3D;;AAGA,SAAgB,iBAAwC,UAAgB;CACtE,KAAK,MAAM,CAAC,MAAM,eAAe,OAAO,QAAQ,SAAS,SAAS,CAAC,CAAC,GAClE,KAAK,MAAM,CAAC,QAAQ,cAAc,OAAO,QAAQ,UAAU,GAAG;EAC5D,MAAM,aAAa,aAAa,OAAO,YAAY,GAAG,YAAY,IAAI,CAAC;EACvE,IAAI,eAAe,KAAA,KAAa,OAAO,cAAc,YAAY,cAAc,MAC7E,UAAuC,wBAAwB;CAEnE;CAEF,OAAO;AACT"}
|