@storyshelf/app 0.7.0 → 0.9.0-next-d2cc082
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/dist/app-types.d.mts +1 -3
- package/dist/asset-manifest.mjs +1 -2
- package/dist/assets/htmx.mjs +1 -2
- package/dist/assets/mark.mjs +1 -2
- package/dist/boot-tasks.mjs +1 -2
- package/dist/build-state-flags.mjs +1 -2
- package/dist/build-state-setup.mjs +1 -2
- package/dist/capture-alerts.mjs +1 -2
- package/dist/capture-process.mjs +1 -2
- package/dist/capture-setup.mjs +1 -2
- package/dist/connections.mjs +1 -2
- package/dist/consumer-setup.mjs +1 -2
- package/dist/credential-keys.mjs +1 -2
- package/dist/credentials.mjs +1 -2
- package/dist/design-events.mjs +71 -0
- package/dist/design-events.mjs.map +1 -0
- package/dist/git-status.mjs +1 -2
- package/dist/hidden-data.mjs +1 -2
- package/dist/index.d.mts +0 -2
- package/dist/index.mjs +2 -3
- package/dist/insights/budget.mjs +1 -2
- package/dist/insights/deps.mjs +1 -2
- package/dist/insights/errors.mjs +1 -2
- package/dist/insights/health-panel.mjs +0 -1
- package/dist/insights/job.mjs +1 -2
- package/dist/insights/load-health.mjs +1 -2
- package/dist/insights/load-triage.mjs +1 -2
- package/dist/insights/panel.mjs +1 -2
- package/dist/insights/request.mjs +1 -2
- package/dist/insights/stale.mjs +1 -2
- package/dist/insights/usage-report.mjs +1 -2
- package/dist/insights/view.mjs +1 -2
- package/dist/job-canceller.mjs +1 -2
- package/dist/job-queue.mjs +1 -2
- package/dist/jobs-setup.mjs +1 -2
- package/dist/lifecycle.mjs +2 -3
- package/dist/lifecycle.mjs.map +1 -1
- package/dist/middleware/auth-gate.mjs +1 -2
- package/dist/middleware/csrf.mjs +1 -2
- package/dist/middleware/rate-limit.mjs +1 -2
- package/dist/middleware/request-log.mjs +1 -2
- package/dist/middleware/require-review.mjs +1 -2
- package/dist/middleware/require-testing.mjs +1 -2
- package/dist/middleware/server-timing.mjs +1 -2
- package/dist/middleware/setup-gate.mjs +1 -2
- package/dist/middleware/store-scope.mjs +1 -2
- package/dist/notifications-pause.mjs +1 -2
- package/dist/notify.d.mts +1 -3
- package/dist/notify.mjs +5 -3
- package/dist/notify.mjs.map +1 -1
- package/dist/pages/a11y-violations.mjs +1 -2
- package/dist/pages/admin-ai.mjs +1 -2
- package/dist/pages/admin-system-origin.mjs +19 -10
- package/dist/pages/admin-system-origin.mjs.map +1 -1
- package/dist/pages/admin-system-targets.mjs +1 -2
- package/dist/pages/admin-system.mjs +1 -2
- package/dist/pages/build-detail.mjs +1 -2
- package/dist/pages/build-diff-actions.mjs +1 -2
- package/dist/pages/build-diff-comments.mjs +1 -2
- package/dist/pages/build-diff-default.mjs +53 -0
- package/dist/pages/build-diff-default.mjs.map +1 -0
- package/dist/pages/build-diff-environment.mjs +34 -5
- package/dist/pages/build-diff-environment.mjs.map +1 -1
- package/dist/pages/build-diff-header.mjs +1 -2
- package/dist/pages/build-diff-insight.mjs +1 -2
- package/dist/pages/build-diff-model.mjs +20 -3
- package/dist/pages/build-diff-model.mjs.map +1 -1
- package/dist/pages/build-diff-nav.mjs +1 -2
- package/dist/pages/build-diff-reference.mjs +84 -0
- package/dist/pages/build-diff-reference.mjs.map +1 -0
- package/dist/pages/build-diff-shortcuts.mjs +1 -2
- package/dist/pages/build-diff-stale.mjs +1 -2
- package/dist/pages/build-diff-usage.mjs +1 -2
- package/dist/pages/build-diff-viewer.mjs +38 -12
- package/dist/pages/build-diff-viewer.mjs.map +1 -1
- package/dist/pages/build-diff.mjs +6 -12
- package/dist/pages/build-diff.mjs.map +1 -1
- package/dist/pages/build-tests.mjs +1 -2
- package/dist/pages/component-detail.mjs +1 -2
- package/dist/pages/component-stories.mjs +1 -2
- package/dist/pages/compute-jobs.mjs +1 -2
- package/dist/pages/design-links.mjs +1 -2
- package/dist/pages/environment-change.mjs +16 -4
- package/dist/pages/environment-change.mjs.map +1 -1
- package/dist/pages/health-panel.mjs +1 -2
- package/dist/pages/invite.mjs +1 -2
- package/dist/pages/label-detail.mjs +1 -2
- package/dist/pages/library-grid.mjs +1 -2
- package/dist/pages/library-model.mjs +1 -2
- package/dist/pages/library-snapshots.mjs +1 -2
- package/dist/pages/library.mjs +1 -2
- package/dist/pages/login.mjs +1 -2
- package/dist/pages/notifications-paused.mjs +1 -2
- package/dist/pages/passkey-ceremony.mjs +1 -2
- package/dist/pages/profile.mjs +1 -2
- package/dist/pages/project-builds-table.mjs +1 -2
- package/dist/pages/project-builds.mjs +1 -2
- package/dist/pages/project-create.mjs +1 -2
- package/dist/pages/project-settings.mjs +1 -2
- package/dist/pages/projects-card.mjs +1 -2
- package/dist/pages/projects.mjs +1 -2
- package/dist/pages/review-changeset.mjs +1 -2
- package/dist/pages/review-comments.mjs +1 -2
- package/dist/pages/review-verdict.mjs +1 -2
- package/dist/pages/root.mjs +1 -2
- package/dist/pages/settings-ai.mjs +1 -2
- package/dist/pages/settings-capture.mjs +1 -2
- package/dist/pages/settings-design-credential.mjs +1 -2
- package/dist/pages/settings-design-links.mjs +1 -2
- package/dist/pages/settings-design.mjs +1 -2
- package/dist/pages/settings-general.mjs +1 -2
- package/dist/pages/settings-labels.mjs +1 -2
- package/dist/pages/settings-links.mjs +1 -2
- package/dist/pages/settings-members.mjs +1 -2
- package/dist/pages/settings-notifications-git.mjs +1 -2
- package/dist/pages/settings-notifications.mjs +1 -2
- package/dist/pages/settings-tests.mjs +1 -2
- package/dist/pages/settings-tokens.mjs +1 -2
- package/dist/pages/settings-webhooks.mjs +1 -2
- package/dist/pages/storybook.mjs +1 -2
- package/dist/pages/target-choices.mjs +1 -2
- package/dist/publish-setup.mjs +1 -2
- package/dist/retention-timer.mjs +10 -11
- package/dist/retention-timer.mjs.map +1 -1
- package/dist/review-events.mjs +1 -2
- package/dist/routers/admin-pages.mjs +1 -2
- package/dist/routers/admin-public-origin.mjs +10 -3
- package/dist/routers/admin-public-origin.mjs.map +1 -1
- package/dist/routers/admin.mjs +8 -4
- package/dist/routers/admin.mjs.map +1 -1
- package/dist/routers/assets.mjs +1 -2
- package/dist/routers/attempts.mjs +1 -2
- package/dist/routers/auth-sync.mjs +1 -2
- package/dist/routers/auth.mjs +1 -2
- package/dist/routers/baseline-guard.mjs +2 -3
- package/dist/routers/baseline-guard.mjs.map +1 -1
- package/dist/routers/baseline-reference.mjs +72 -0
- package/dist/routers/baseline-reference.mjs.map +1 -0
- package/dist/routers/baselines.handlers.mjs +1 -2
- package/dist/routers/baselines.mjs +1 -2
- package/dist/routers/build-cancel.mjs +1 -2
- package/dist/routers/build-changeset.mjs +1 -2
- package/dist/routers/build-reviews.handlers.mjs +1 -2
- package/dist/routers/build-reviews.mjs +1 -2
- package/dist/routers/builds.handlers.mjs +23 -9
- package/dist/routers/builds.handlers.mjs.map +1 -1
- package/dist/routers/builds.mjs +7 -3
- package/dist/routers/builds.mjs.map +1 -1
- package/dist/routers/capture-cache.mjs +29 -0
- package/dist/routers/capture-cache.mjs.map +1 -0
- package/dist/routers/capture-config.mjs +1 -2
- package/dist/routers/comments.mjs +1 -2
- package/dist/routers/comparison-image.mjs +29 -0
- package/dist/routers/comparison-image.mjs.map +1 -0
- package/dist/routers/component-catalog.handlers.mjs +1 -2
- package/dist/routers/component.handlers.mjs +1 -2
- package/dist/routers/connections.mjs +1 -2
- package/dist/routers/design-connect.handlers.mjs +1 -2
- package/dist/routers/design.handlers.mjs +8 -2
- package/dist/routers/design.handlers.mjs.map +1 -1
- package/dist/routers/flash.mjs +1 -2
- package/dist/routers/health-report.mjs +1 -2
- package/dist/routers/health.mjs +1 -2
- package/dist/routers/helpers.mjs +1 -2
- package/dist/routers/htmx.mjs +1 -2
- package/dist/routers/insights-ui.mjs +1 -2
- package/dist/routers/insights.handlers.mjs +1 -2
- package/dist/routers/insights.mjs +1 -2
- package/dist/routers/labels.mjs +1 -2
- package/dist/routers/links.handlers.mjs +1 -2
- package/dist/routers/links.mjs +1 -2
- package/dist/routers/media.mjs +14 -5
- package/dist/routers/media.mjs.map +1 -1
- package/dist/routers/members.mjs +1 -2
- package/dist/routers/notification-channels.mjs +1 -2
- package/dist/routers/notification-subscriptions.mjs +1 -2
- package/dist/routers/oauth-state.mjs +1 -2
- package/dist/routers/preview-base.mjs +1 -2
- package/dist/routers/preview-token.mjs +1 -2
- package/dist/routers/profile.mjs +1 -2
- package/dist/routers/projects.mjs +1 -2
- package/dist/routers/publish-upload.mjs +19 -12
- package/dist/routers/publish-upload.mjs.map +1 -1
- package/dist/routers/published-access.mjs +1 -2
- package/dist/routers/published-compression.mjs +109 -0
- package/dist/routers/published-compression.mjs.map +1 -0
- package/dist/routers/published-files.mjs +26 -9
- package/dist/routers/published-files.mjs.map +1 -1
- package/dist/routers/review-forms.mjs +1 -2
- package/dist/routers/review-next.mjs +1 -2
- package/dist/routers/review-section.handlers.mjs +1 -2
- package/dist/routers/review-switch-off.mjs +1 -2
- package/dist/routers/schemas.mjs +1 -2
- package/dist/routers/scoped-models.mjs +1 -2
- package/dist/routers/settings-ai.mjs +1 -2
- package/dist/routers/settings-capture.mjs +1 -2
- package/dist/routers/settings-design.mjs +1 -2
- package/dist/routers/settings-general.mjs +1 -2
- package/dist/routers/settings-git-channel.mjs +1 -2
- package/dist/routers/settings-labels.mjs +1 -2
- package/dist/routers/settings-links.mjs +1 -2
- package/dist/routers/settings-members.mjs +1 -2
- package/dist/routers/settings-notifications.mjs +1 -2
- package/dist/routers/settings-tokens.mjs +1 -2
- package/dist/routers/settings-webhooks.mjs +1 -2
- package/dist/routers/settings.handlers.mjs +1 -2
- package/dist/routers/settings.mjs +1 -2
- package/dist/routers/signed-storybook.mjs +5 -3
- package/dist/routers/signed-storybook.mjs.map +1 -1
- package/dist/routers/snapshot-groups.mjs +1 -2
- package/dist/routers/snapshots.mjs +1 -2
- package/dist/routers/storybook.mjs +21 -17
- package/dist/routers/storybook.mjs.map +1 -1
- package/dist/routers/tokens.mjs +1 -2
- package/dist/routers/ui.mjs +4 -2
- package/dist/routers/ui.mjs.map +1 -1
- package/dist/routers/webhooks.mjs +1 -2
- package/dist/routers/well-known.mjs +1 -2
- package/dist/routes/api-routes.mjs +1 -2
- package/dist/routes/classification.mjs +1 -2
- package/dist/routes/openapi-classes.mjs +1 -2
- package/dist/routes/page-routes.mjs +2 -3
- package/dist/routes/page-routes.mjs.map +1 -1
- package/dist/runtime.mjs +1 -2
- package/dist/serverless.mjs +1 -2
- package/dist/store.mjs +1 -2
- package/dist/targets.mjs +1 -2
- package/dist/ui/avatar.mjs +1 -2
- package/dist/ui/buttons.mjs +1 -2
- package/dist/ui/code-block.mjs +1 -2
- package/dist/ui/compare-stage.mjs +86 -54
- package/dist/ui/compare-stage.mjs.map +1 -1
- package/dist/ui/csrf-field.mjs +1 -2
- package/dist/ui/css.mjs +1 -2
- package/dist/ui/design-reference.mjs +1 -2
- package/dist/ui/document.mjs +1 -2
- package/dist/ui/dropdown.mjs +1 -2
- package/dist/ui/feedback.mjs +1 -2
- package/dist/ui/filter-input.mjs +1 -2
- package/dist/ui/forms.mjs +1 -2
- package/dist/ui/icons/icon.mjs +1 -2
- package/dist/ui/icons/paths.mjs +1 -2
- package/dist/ui/icons/sprite.mjs +1 -2
- package/dist/ui/kbd.mjs +1 -2
- package/dist/ui/layout.mjs +1 -2
- package/dist/ui/preview-embed.mjs +1 -2
- package/dist/ui/progress.mjs +1 -2
- package/dist/ui/segmented.mjs +1 -2
- package/dist/ui/shell/account.mjs +1 -2
- package/dist/ui/shell/bare.mjs +1 -2
- package/dist/ui/shell/boot-script.mjs +1 -2
- package/dist/ui/shell/client-script.mjs +1 -2
- package/dist/ui/shell/nav.mjs +1 -2
- package/dist/ui/shell/review-script.mjs +17 -3
- package/dist/ui/shell/review-script.mjs.map +1 -1
- package/dist/ui/shell/sidebar.mjs +1 -2
- package/dist/ui/shell/topbar.mjs +1 -2
- package/dist/ui/stacks.mjs +1 -2
- package/dist/ui/styles/base.mjs +1 -2
- package/dist/ui/styles/behavior.mjs +1 -2
- package/dist/ui/styles/review-layout.mjs +1 -2
- package/dist/ui/styles/review.mjs +1 -2
- package/dist/ui/styles/tokens.mjs +1 -2
- package/dist/ui/styles.mjs +1 -2
- package/dist/ui/subnav.mjs +1 -2
- package/dist/ui/table.mjs +1 -2
- package/dist/ui/theme.mjs +1 -2
- package/dist/ui/thumbnail.mjs +1 -2
- package/dist/ui/time.mjs +1 -2
- package/package.json +9 -18
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"builds.mjs","names":["notFoundResponse","forbiddenResponse"],"sources":["../../src/routers/builds.ts"],"sourcesContent":["import { createRoute, z } from \"@hono/zod-openapi\";\nimport { DEFAULT_MAX_UPLOAD_BYTES } from \"@storyshelf/core/config\";\nimport { BuildModel, PackageUsageModel } from \"@storyshelf/core/models\";\nimport { storybookZipPath } from \"@storyshelf/core/utils\";\nimport { HTTPException } from \"hono/http-exception\";\nimport type { ShelfRouter } from \"../app-types.ts\";\nimport { getStore } from \"../store.ts\";\nimport { registerAttempts } from \"./attempts.ts\";\nimport {\n VIEW_ROLES,\n DEVELOPER_ROLES,\n APPROVER_ROLES,\n buildCreateJsonSchema,\n buildListQuery,\n buildForProject,\n createBuildRecord,\n storeUploadStream,\n} from \"./builds.handlers.ts\";\nimport { discardZip, planUpload, publishUpload } from \"./publish-upload.ts\";\n\n/** Decide how the stored zip is published; a zip this host cannot publish is removed and refused. */\nasync function planOrDiscard(\n zipBytes: number,\n projectId: string,\n buildId: string,\n): Promise<\"inline\" | \"job\"> {\n try {\n return planUpload(zipBytes);\n } catch (error) {\n await discardZip(projectId, buildId);\n throw error;\n }\n}\n\nasync function upsertContentRef(hash: string): Promise<void> {\n try {\n const contentRefs = getStore().db.tables.contentRefs;\n const now = new Date().toISOString();\n const existing = await getStore().db.get(contentRefs, hash);\n if (existing) {\n await getStore().db.update(contentRefs, hash, {\n refCount: (existing as { refCount: number }).refCount + 1,\n lastSeenAt: now,\n });\n } else {\n await getStore().db.insert(contentRefs, {\n hash,\n refCount: 1,\n lastSeenAt: now,\n createdAt: now,\n });\n }\n } catch {\n // ignore — content_refs may not exist on old DB\n }\n}\nimport { registerBuildCancel } from \"./build-cancel.ts\";\nimport { registerBuildChangeset } from \"./build-changeset.ts\";\nimport { registerBuildReviews } from \"./build-reviews.ts\";\nimport { registerComments } from \"./comments.ts\";\nimport { flashHx } from \"./flash.ts\";\nimport { resolveAuthorizedProject } from \"./helpers.ts\";\nimport { hxRefresh } from \"./htmx.ts\";\nimport {\n badRequest,\n buildSchema,\n forbidden as forbiddenResponse,\n notFound as notFoundResponse,\n unauthorized,\n} from \"./schemas.ts\";\nimport { registerSnapshots } from \"./snapshots.ts\";\n/** Register the build list, upload, fetch, retry, and delete endpoints. */\nexport function registerBuilds(app: ShelfRouter): void {\n app.openapi(listBuildsRoute, async (c) => {\n const project = await resolveAuthorizedProject(c, c.req.valid(\"param\").slug, ...VIEW_ROLES);\n const { status, branch, labelKey, labelValue } = c.req.valid(\"query\");\n const builds = new BuildModel(getStore().db).list(project.id, {\n status,\n branch: branch ?? undefined,\n labelKey: labelKey ?? undefined,\n labelValue: labelValue ?? undefined,\n });\n return c.json(await builds);\n });\n\n app.openapi(createBuildRoute, async (c) => {\n const project = await resolveAuthorizedProject(\n c,\n c.req.valid(\"param\").slug,\n ...DEVELOPER_ROLES,\n );\n const build = await createBuildRecord(project, c.req.valid(\"json\"));\n const uploadUrl = `/api/v1/projects/${project.slug}/builds/${build.id}/zip`;\n return c.json(\n { build, uploadUrl, baselineSha: build.baselineSha, testing: getStore().testing },\n 202,\n );\n });\n\n app.openapi(affectedRoute, async (c) => {\n const { slug, buildId } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...DEVELOPER_ROLES);\n const build = await buildForProject(project.id, buildId);\n const { baselineSha, changedFiles, affectedImportPaths } = c.req.valid(\"json\");\n const updated = await new BuildModel(getStore().db).setAffected(build.id, {\n baselineSha,\n changedFiles,\n affectedImportPaths,\n });\n return c.json(updated);\n });\n\n app.openapi(usageRoute, async (c) => {\n const { slug, buildId } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...DEVELOPER_ROLES);\n const build = await buildForProject(project.id, buildId);\n const { usage } = c.req.valid(\"json\");\n const stored = await new PackageUsageModel(getStore().db).replaceForBuild(\n project.id,\n build.id,\n usage,\n );\n return c.json({ stored });\n });\n\n app.openapi(uploadZipRoute, async (c) => {\n const { slug, buildId } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...DEVELOPER_ROLES);\n const build = await buildForProject(project.id, buildId);\n const maxBytes = getStore().config.maxUploadBytes ?? DEFAULT_MAX_UPLOAD_BYTES;\n const zipBytes = await storeUploadStream(\n getStore().storage,\n storybookZipPath(project.id, build.id),\n c.req.raw.body,\n maxBytes,\n );\n const route = await planOrDiscard(zipBytes, project.id, build.id);\n const reqId = c.get(\"requestId\");\n await getStore().enqueueCapture?.(build.id, reqId);\n return c.json(await publishUpload(route, build, reqId), 202);\n });\n\n app.openapi(dedupRoute, async (c) => {\n const { slug, buildId } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...DEVELOPER_ROLES);\n await buildForProject(project.id, buildId);\n const { hashes } = c.req.valid(\"json\");\n const needed: string[] = [];\n for (const hash of hashes) {\n if (!(await getStore().storage.exists(`content/${hash}`))) {\n needed.push(hash);\n }\n }\n return c.json({ needed });\n });\n\n // oxlint-disable eslint(max-depth) -- dedup upload handler is intentionally nested for batch handling\n app.openapi(contentUploadRoute, async (c) => {\n const { slug, buildId } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...DEVELOPER_ROLES);\n await buildForProject(project.id, buildId);\n const contentType = c.req.header(\"content-type\") ?? \"\";\n if (contentType.includes(\"multipart/\")) {\n const form = await c.req.formData();\n for (const [key, value] of form.entries()) {\n if (value instanceof File) {\n const hash = key;\n const buffer = Buffer.from(await value.arrayBuffer());\n await getStore().storage.write(`content/${hash}`, buffer);\n await upsertContentRef(hash);\n }\n }\n } else {\n // Single content upload via PUT with hash header\n const hash = c.req.header(\"x-content-hash\") ?? \"\";\n if (!hash) throw new HTTPException(400, { message: \"Missing X-Content-Hash\" });\n const buffer = Buffer.from(await c.req.arrayBuffer());\n await getStore().storage.write(`content/${hash}`, buffer);\n }\n return c.json({ ok: true });\n });\n\n app.openapi(manifestRoute, async (c) => {\n const { slug, buildId } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...DEVELOPER_ROLES);\n const build = await buildForProject(project.id, buildId);\n const { files } = c.req.valid(\"json\");\n const manifest: Record<string, string> = {};\n for (const f of files) {\n manifest[f.rel] = f.hash;\n }\n await getStore().storage.write(\n `${project.id}/builds/${build.id}/storybook/manifest.json`,\n Buffer.from(JSON.stringify(manifest)),\n );\n // Upsert content_refs for each hash\n for (const f of files) {\n try {\n const contentRefs = getStore().db.tables.contentRefs;\n const now = new Date().toISOString();\n const existing = await getStore().db.get(contentRefs, f.hash);\n // oxlint-disable-next-line unicorn/no-if-else -- upsert is clearer as if/else\n if (!existing) {\n await getStore().db.insert(contentRefs, {\n hash: f.hash,\n refCount: 1,\n lastSeenAt: now,\n createdAt: now,\n });\n } else {\n await getStore().db.update(contentRefs, f.hash, {\n lastSeenAt: now,\n });\n }\n } catch {\n // ignore — content_refs may not exist\n }\n }\n // The files are stored content-addressed: nothing to extract, so publishing is complete.\n await getStore().publish?.completer.complete(build.id, { ok: true });\n const reqId = c.get(\"requestId\");\n await getStore().enqueueCapture?.(build.id, reqId);\n return c.json({ ok: true });\n });\n\n app.openapi(getBuildRoute, async (c) => {\n const { slug, buildId } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...VIEW_ROLES);\n const build = await buildForProject(project.id, buildId);\n return c.json(build);\n });\n\n app.openapi(retryBuildRoute, async (c) => {\n const { slug, buildId } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...DEVELOPER_ROLES);\n const build = await buildForProject(project.id, buildId);\n const updated = await new BuildModel(getStore().db).resetStatus(build.id, \"pending\");\n await getStore().enqueueCapture?.(build.id, c.get(\"requestId\"));\n hxRefresh(c);\n flashHx(c, \"Capture queued\");\n return c.json(updated, 202);\n });\n\n app.openapi(deleteBuildRoute, async (c) => {\n const { slug, buildId } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...APPROVER_ROLES);\n const build = await buildForProject(project.id, buildId);\n await new BuildModel(getStore().db).remove(build.id);\n return c.body(null, 204);\n });\n\n registerSnapshots(app);\n registerComments(app);\n registerBuildReviews(app);\n registerBuildChangeset(app);\n registerBuildCancel(app);\n registerAttempts(app);\n}\n\nconst listBuildsRoute = createRoute({\n method: \"get\",\n tags: [\"Builds\"],\n summary: \"List builds\",\n path: \"/api/v1/projects/{slug}/builds\",\n request: { params: z.object({ slug: z.string() }), query: buildListQuery },\n responses: {\n 200: {\n content: { \"application/json\": { schema: buildSchema.array() } },\n description: \"List builds for a project\",\n },\n ...notFoundResponse,\n ...unauthorized,\n },\n});\n\nconst buildCreatedSchema = z\n .object({\n build: buildSchema,\n uploadUrl: z.string(),\n baselineSha: z.string().nullable(),\n testing: z.boolean().openapi({\n description:\n \"False when the server has no `testing` option: the build is published but never captured or compared.\",\n }),\n })\n .openapi(\"BuildCreated\");\n\nconst createBuildRoute = createRoute({\n method: \"post\",\n tags: [\"Builds\"],\n summary: \"Create a build\",\n path: \"/api/v1/projects/{slug}/builds\",\n request: {\n params: z.object({ slug: z.string() }),\n body: {\n content: {\n \"application/json\": { schema: buildCreateJsonSchema },\n },\n },\n },\n responses: {\n 202: {\n content: {\n \"application/json\": {\n schema: buildCreatedSchema,\n },\n },\n description:\n \"Build created. PUT the Storybook zip to uploadUrl to publish it (and queue capture when testing is on).\",\n },\n ...badRequest,\n ...forbiddenResponse,\n ...notFoundResponse,\n },\n});\n\nconst uploadZipRoute = createRoute({\n method: \"put\",\n tags: [\"Builds\"],\n summary: \"Upload the Storybook zip\",\n path: \"/api/v1/projects/{slug}/builds/{buildId}/zip\",\n request: {\n params: z.object({ slug: z.string(), buildId: z.string() }),\n body: {\n content: {\n \"application/zip\": {\n schema: z.string().openapi({ type: \"string\", format: \"binary\" }),\n },\n },\n },\n },\n responses: {\n 202: {\n content: { \"application/json\": { schema: buildSchema } },\n description:\n \"Zip stored. A zip up to maxInlineUnzipSize (default 50 MB) is extracted inline in this request; a larger one is published by a background job. With testing off, the build becomes approved when publishing finishes on the project's default branch (failed if it cannot be published) and stays pending on other branches; with testing on, capture sets the status.\",\n },\n 413: {\n content: {\n \"application/json\": {\n schema: z.object({ message: z.string() }).openapi(\"Error\"),\n },\n },\n description:\n \"Upload exceeds the configured size limit, or the zip is over the inline limit and this host (serverless) runs no publish consumer: upload file by file instead (POST /dedup, PUT /content, POST /manifest)\",\n },\n ...badRequest,\n ...forbiddenResponse,\n ...notFoundResponse,\n },\n});\n\nconst dedupHashesSchema = z.object({ hashes: z.array(z.string()) });\nconst dedupNeededSchema = z.object({ needed: z.array(z.string()) });\nconst dedupRoute = createRoute({\n method: \"post\",\n tags: [\"Builds\"],\n summary: \"Check which files need uploading\",\n description:\n \"File-by-file upload, step 1 (dedup, content, manifest): needs no zip extraction and works on any host, serverless included. Send the content hashes; the response lists those the server does not hold yet.\",\n path: \"/api/v1/projects/{slug}/builds/{buildId}/dedup\",\n request: {\n params: z.object({ slug: z.string(), buildId: z.string() }),\n body: { content: { \"application/json\": { schema: dedupHashesSchema } } },\n },\n responses: {\n 200: {\n content: { \"application/json\": { schema: dedupNeededSchema } },\n description: \"Hashes needed for upload\",\n },\n ...notFoundResponse,\n },\n});\n\nconst contentUploadRoute = createRoute({\n method: \"post\",\n tags: [\"Builds\"],\n summary: \"Upload build content\",\n description:\n \"File-by-file upload, step 2: store the missing files content-addressed (multipart, or one file with an X-Content-Hash header).\",\n path: \"/api/v1/projects/{slug}/builds/{buildId}/content\",\n request: {\n params: z.object({ slug: z.string(), buildId: z.string() }),\n body: { content: { \"multipart/form-data\": { schema: z.any() } } },\n },\n responses: {\n 200: {\n content: { \"application/json\": { schema: z.object({ ok: z.boolean() }) } },\n description: \"Content uploaded\",\n },\n ...notFoundResponse,\n },\n});\n\nconst manifestFileSchema = z.object({ rel: z.string(), hash: z.string(), size: z.number() });\nconst manifestRoute = createRoute({\n method: \"post\",\n tags: [\"Builds\"],\n summary: \"Store the build manifest\",\n description:\n \"File-by-file upload, step 3: store the path-to-hash manifest. This completes publishing. With testing off the build becomes approved when it is on the project's default branch and stays pending on other branches; with testing on, a capture is queued and sets the status.\",\n path: \"/api/v1/projects/{slug}/builds/{buildId}/manifest\",\n request: {\n params: z.object({ slug: z.string(), buildId: z.string() }),\n body: {\n content: {\n \"application/json\": {\n schema: z.object({ files: z.array(manifestFileSchema) }),\n },\n },\n },\n },\n responses: {\n 200: {\n content: { \"application/json\": { schema: z.object({ ok: z.boolean() }) } },\n description: \"Manifest stored\",\n },\n ...notFoundResponse,\n },\n});\n\nconst affectedPayloadSchema = z.object({\n baselineSha: z.string().nullable(),\n changedFiles: z.array(z.string()).max(20_000),\n affectedImportPaths: z.array(z.string()).max(20_000).nullable(),\n});\nconst affectedRoute = createRoute({\n method: \"post\",\n tags: [\"Builds\"],\n summary: \"Record the affected stories\",\n path: \"/api/v1/projects/{slug}/builds/{buildId}/affected\",\n request: {\n params: z.object({ slug: z.string(), buildId: z.string() }),\n body: { content: { \"application/json\": { schema: affectedPayloadSchema } } },\n },\n responses: {\n 200: {\n content: { \"application/json\": { schema: buildSchema } },\n description: \"Affected set recorded\",\n },\n ...badRequest,\n ...forbiddenResponse,\n ...notFoundResponse,\n },\n});\n\nconst usagePayloadSchema = z.object({\n usage: z\n .array(\n z.object({\n storyImportPath: z.string().min(1).max(1024),\n packageName: z.string().min(1).max(214),\n modulePath: z.string().min(1).max(2048),\n version: z.string().max(128).nullable().optional(),\n }),\n )\n .max(20_000),\n});\nconst usageRoute = createRoute({\n method: \"post\",\n tags: [\"Builds\"],\n summary: \"Record the dependency packages each story uses\",\n path: \"/api/v1/projects/{slug}/builds/{buildId}/usage\",\n request: {\n params: z.object({ slug: z.string(), buildId: z.string() }),\n body: { content: { \"application/json\": { schema: usagePayloadSchema } } },\n },\n responses: {\n 200: {\n content: { \"application/json\": { schema: z.object({ stored: z.number() }) } },\n description: \"Usage recorded (replaces any earlier report for the build)\",\n },\n ...badRequest,\n ...forbiddenResponse,\n ...notFoundResponse,\n },\n});\n\nconst getBuildRoute = createRoute({\n method: \"get\",\n tags: [\"Builds\"],\n summary: \"Get a build\",\n path: \"/api/v1/projects/{slug}/builds/{buildId}\",\n request: { params: z.object({ slug: z.string(), buildId: z.string() }) },\n responses: {\n 200: { content: { \"application/json\": { schema: buildSchema } }, description: \"Fetch a build\" },\n ...notFoundResponse,\n ...unauthorized,\n },\n});\n\nconst retryBuildRoute = createRoute({\n method: \"post\",\n tags: [\"Builds\"],\n summary: \"Retry capture\",\n path: \"/api/v1/projects/{slug}/builds/{buildId}/retry\",\n request: { params: z.object({ slug: z.string(), buildId: z.string() }) },\n responses: {\n 202: {\n content: { \"application/json\": { schema: buildSchema } },\n description: \"Build reset to pending and capture re-queued (new attempt)\",\n },\n ...notFoundResponse,\n },\n});\n\nconst deleteBuildRoute = createRoute({\n method: \"delete\",\n tags: [\"Builds\"],\n summary: \"Delete a build\",\n path: \"/api/v1/projects/{slug}/builds/{buildId}\",\n request: { params: z.object({ slug: z.string(), buildId: z.string() }) },\n responses: {\n 204: { description: \"Build deleted\" },\n ...notFoundResponse,\n },\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"builds.mjs","names":["notFoundResponse","forbiddenResponse"],"sources":["../../src/routers/builds.ts"],"sourcesContent":["import { createRoute, z } from \"@hono/zod-openapi\";\nimport { DEFAULT_MAX_UPLOAD_BYTES } from \"@storyshelf/core/config\";\nimport { BuildModel, PackageUsageModel } from \"@storyshelf/core/models\";\nimport { storybookZipPath } from \"@storyshelf/core/utils\";\nimport { HTTPException } from \"hono/http-exception\";\nimport type { ShelfRouter } from \"../app-types.ts\";\nimport { getStore } from \"../store.ts\";\nimport { registerAttempts } from \"./attempts.ts\";\nimport {\n VIEW_ROLES,\n DEVELOPER_ROLES,\n APPROVER_ROLES,\n buildCreateJsonSchema,\n buildListQuery,\n buildForProject,\n createBuildRecord,\n storeUploadStream,\n} from \"./builds.handlers.ts\";\nimport { discardZip, planUpload, publishUpload } from \"./publish-upload.ts\";\n\n/** Decide how the stored zip is published; a zip this host cannot publish is removed and refused. */\nasync function planOrDiscard(\n zipBytes: number,\n projectId: string,\n buildId: string,\n): Promise<\"inline\" | \"job\"> {\n try {\n return planUpload(zipBytes);\n } catch (error) {\n await discardZip(projectId, buildId);\n throw error;\n }\n}\n\nasync function upsertContentRef(hash: string): Promise<void> {\n try {\n const contentRefs = getStore().db.tables.contentRefs;\n const now = new Date().toISOString();\n const existing = await getStore().db.get(contentRefs, hash);\n if (existing) {\n await getStore().db.update(contentRefs, hash, {\n refCount: (existing as { refCount: number }).refCount + 1,\n lastSeenAt: now,\n });\n } else {\n await getStore().db.insert(contentRefs, {\n hash,\n refCount: 1,\n lastSeenAt: now,\n createdAt: now,\n });\n }\n } catch {\n // ignore — content_refs may not exist on old DB\n }\n}\nimport { registerBuildCancel } from \"./build-cancel.ts\";\nimport { registerBuildChangeset } from \"./build-changeset.ts\";\nimport { registerBuildReviews } from \"./build-reviews.ts\";\nimport { registerComments } from \"./comments.ts\";\nimport { flashHx } from \"./flash.ts\";\nimport { resolveAuthorizedProject } from \"./helpers.ts\";\nimport { hxRefresh } from \"./htmx.ts\";\nimport {\n badRequest,\n buildSchema,\n forbidden as forbiddenResponse,\n notFound as notFoundResponse,\n unauthorized,\n} from \"./schemas.ts\";\nimport { registerSnapshots } from \"./snapshots.ts\";\n/** Register the build list, upload, fetch, retry, and delete endpoints. */\nexport function registerBuilds(app: ShelfRouter): void {\n app.openapi(listBuildsRoute, async (c) => {\n const project = await resolveAuthorizedProject(c, c.req.valid(\"param\").slug, ...VIEW_ROLES);\n const { status, branch, labelKey, labelValue } = c.req.valid(\"query\");\n const builds = new BuildModel(getStore().db).list(project.id, {\n status,\n branch: branch ?? undefined,\n labelKey: labelKey ?? undefined,\n labelValue: labelValue ?? undefined,\n });\n return c.json(await builds);\n });\n\n app.openapi(createBuildRoute, async (c) => {\n const project = await resolveAuthorizedProject(\n c,\n c.req.valid(\"param\").slug,\n ...DEVELOPER_ROLES,\n );\n const build = await createBuildRecord(project, c.req.valid(\"json\"));\n const uploadUrl = `/api/v1/projects/${project.slug}/builds/${build.id}/zip`;\n return c.json(\n { build, uploadUrl, baselineSha: build.baselineSha, testing: getStore().testing },\n 202,\n );\n });\n\n app.openapi(affectedRoute, async (c) => {\n const { slug, buildId } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...DEVELOPER_ROLES);\n const build = await buildForProject(project.id, buildId);\n const { baselineSha, changedFiles, affectedImportPaths } = c.req.valid(\"json\");\n const updated = await new BuildModel(getStore().db).setAffected(build.id, {\n baselineSha,\n changedFiles,\n affectedImportPaths,\n });\n return c.json(updated);\n });\n\n app.openapi(usageRoute, async (c) => {\n const { slug, buildId } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...DEVELOPER_ROLES);\n const build = await buildForProject(project.id, buildId);\n const { usage } = c.req.valid(\"json\");\n const stored = await new PackageUsageModel(getStore().db).replaceForBuild(\n project.id,\n build.id,\n usage,\n );\n return c.json({ stored });\n });\n\n app.openapi(uploadZipRoute, async (c) => {\n const { slug, buildId } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...DEVELOPER_ROLES);\n const build = await buildForProject(project.id, buildId);\n const maxBytes = getStore().config.maxUploadBytes ?? DEFAULT_MAX_UPLOAD_BYTES;\n const zipBytes = await storeUploadStream(\n getStore().storage,\n storybookZipPath(project.id, build.id),\n c.req.raw.body,\n maxBytes,\n );\n const route = await planOrDiscard(zipBytes, project.id, build.id);\n const reqId = c.get(\"requestId\");\n const published = await publishUpload(route, build, reqId);\n await getStore().enqueueCapture?.(build.id, reqId);\n return c.json(published, 202);\n });\n\n app.openapi(dedupRoute, async (c) => {\n const { slug, buildId } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...DEVELOPER_ROLES);\n await buildForProject(project.id, buildId);\n const { hashes } = c.req.valid(\"json\");\n const needed: string[] = [];\n for (const hash of hashes) {\n if (!(await getStore().storage.exists(`content/${hash}`))) {\n needed.push(hash);\n }\n }\n return c.json({ needed });\n });\n\n // oxlint-disable eslint(max-depth) -- dedup upload handler is intentionally nested for batch handling\n app.openapi(contentUploadRoute, async (c) => {\n const { slug, buildId } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...DEVELOPER_ROLES);\n await buildForProject(project.id, buildId);\n const contentType = c.req.header(\"content-type\") ?? \"\";\n if (contentType.includes(\"multipart/\")) {\n const form = await c.req.formData();\n for (const [key, value] of form.entries()) {\n if (value instanceof File) {\n const hash = key;\n const buffer = Buffer.from(await value.arrayBuffer());\n await getStore().storage.write(`content/${hash}`, buffer);\n await upsertContentRef(hash);\n }\n }\n } else {\n // Single content upload via PUT with hash header\n const hash = c.req.header(\"x-content-hash\") ?? \"\";\n if (!hash) throw new HTTPException(400, { message: \"Missing X-Content-Hash\" });\n const buffer = Buffer.from(await c.req.arrayBuffer());\n await getStore().storage.write(`content/${hash}`, buffer);\n }\n return c.json({ ok: true });\n });\n\n app.openapi(manifestRoute, async (c) => {\n const { slug, buildId } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...DEVELOPER_ROLES);\n const build = await buildForProject(project.id, buildId);\n const { files } = c.req.valid(\"json\");\n const manifest: Record<string, string> = {};\n for (const f of files) {\n manifest[f.rel] = f.hash;\n }\n await getStore().storage.write(\n `${project.id}/builds/${build.id}/storybook/manifest.json`,\n Buffer.from(JSON.stringify(manifest)),\n );\n // Upsert content_refs for each hash\n for (const f of files) {\n try {\n const contentRefs = getStore().db.tables.contentRefs;\n const now = new Date().toISOString();\n const existing = await getStore().db.get(contentRefs, f.hash);\n // oxlint-disable-next-line unicorn/no-if-else -- upsert is clearer as if/else\n if (!existing) {\n await getStore().db.insert(contentRefs, {\n hash: f.hash,\n refCount: 1,\n lastSeenAt: now,\n createdAt: now,\n });\n } else {\n await getStore().db.update(contentRefs, f.hash, {\n lastSeenAt: now,\n });\n }\n } catch {\n // ignore — content_refs may not exist\n }\n }\n // The files are stored content-addressed: nothing to extract, so publishing is complete.\n await getStore().publish?.completer.complete(build.id, { ok: true });\n const reqId = c.get(\"requestId\");\n await getStore().enqueueCapture?.(build.id, reqId);\n return c.json({ ok: true });\n });\n\n app.openapi(getBuildRoute, async (c) => {\n const { slug, buildId } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...VIEW_ROLES);\n const build = await buildForProject(project.id, buildId);\n return c.json(build);\n });\n\n app.openapi(retryBuildRoute, async (c) => {\n const { slug, buildId } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...DEVELOPER_ROLES);\n const build = await buildForProject(project.id, buildId);\n const updated = await new BuildModel(getStore().db).resetStatus(build.id, \"pending\");\n await getStore().enqueueCapture?.(build.id, c.get(\"requestId\"));\n hxRefresh(c);\n flashHx(c, \"Capture queued\");\n return c.json(updated, 202);\n });\n\n app.openapi(deleteBuildRoute, async (c) => {\n const { slug, buildId } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...APPROVER_ROLES);\n const build = await buildForProject(project.id, buildId);\n await new BuildModel(getStore().db).remove(build.id);\n return c.body(null, 204);\n });\n\n registerSnapshots(app);\n registerComments(app);\n registerBuildReviews(app);\n registerBuildChangeset(app);\n registerBuildCancel(app);\n registerAttempts(app);\n}\n\nconst listBuildsRoute = createRoute({\n method: \"get\",\n tags: [\"Builds\"],\n summary: \"List builds\",\n path: \"/api/v1/projects/{slug}/builds\",\n request: { params: z.object({ slug: z.string() }), query: buildListQuery },\n responses: {\n 200: {\n content: { \"application/json\": { schema: buildSchema.array() } },\n description: \"List builds for a project\",\n },\n ...notFoundResponse,\n ...unauthorized,\n },\n});\n\nconst buildCreatedSchema = z\n .object({\n build: buildSchema,\n uploadUrl: z.string(),\n baselineSha: z.string().nullable(),\n testing: z.boolean().openapi({\n description:\n \"False when the server has no `testing` option: the build is published but never captured or compared.\",\n }),\n })\n .openapi(\"BuildCreated\");\n\nconst createBuildRoute = createRoute({\n method: \"post\",\n tags: [\"Builds\"],\n summary: \"Create a build\",\n path: \"/api/v1/projects/{slug}/builds\",\n request: {\n params: z.object({ slug: z.string() }),\n body: {\n content: {\n \"application/json\": { schema: buildCreateJsonSchema },\n },\n },\n },\n responses: {\n 202: {\n content: {\n \"application/json\": {\n schema: buildCreatedSchema,\n },\n },\n description:\n \"Build created. PUT the Storybook zip to uploadUrl to publish it (and queue capture when testing is on).\",\n },\n ...badRequest,\n ...forbiddenResponse,\n ...notFoundResponse,\n },\n});\n\nconst uploadZipRoute = createRoute({\n method: \"put\",\n tags: [\"Builds\"],\n summary: \"Upload the Storybook zip\",\n path: \"/api/v1/projects/{slug}/builds/{buildId}/zip\",\n request: {\n params: z.object({ slug: z.string(), buildId: z.string() }),\n body: {\n content: {\n \"application/zip\": {\n schema: z.string().openapi({ type: \"string\", format: \"binary\" }),\n },\n },\n },\n },\n responses: {\n 202: {\n content: { \"application/json\": { schema: buildSchema } },\n description:\n \"Zip stored. A zip up to maxInlineUnzipSize (default 50 MB) is extracted inline in this request; a larger one is published by a background job. With testing off, the build becomes approved when publishing finishes on the project's default branch (failed if it cannot be published) and stays pending on other branches; with testing on, capture sets the status.\",\n },\n 422: {\n content: { \"text/plain\": { schema: z.string() } },\n description: \"The inline zip is invalid, unsafe, or exceeds the extraction limits\",\n },\n 413: {\n content: {\n \"application/json\": {\n schema: z.object({ message: z.string() }).openapi(\"Error\"),\n },\n },\n description:\n \"Upload exceeds the configured size limit, or the zip is over the inline limit and this host (serverless) runs no publish consumer: upload file by file instead (POST /dedup, PUT /content, POST /manifest)\",\n },\n ...badRequest,\n ...forbiddenResponse,\n ...notFoundResponse,\n },\n});\n\nconst dedupHashesSchema = z.object({ hashes: z.array(z.string()) });\nconst dedupNeededSchema = z.object({ needed: z.array(z.string()) });\nconst dedupRoute = createRoute({\n method: \"post\",\n tags: [\"Builds\"],\n summary: \"Check which files need uploading\",\n description:\n \"File-by-file upload, step 1 (dedup, content, manifest): needs no zip extraction and works on any host, serverless included. Send the content hashes; the response lists those the server does not hold yet.\",\n path: \"/api/v1/projects/{slug}/builds/{buildId}/dedup\",\n request: {\n params: z.object({ slug: z.string(), buildId: z.string() }),\n body: { content: { \"application/json\": { schema: dedupHashesSchema } } },\n },\n responses: {\n 200: {\n content: { \"application/json\": { schema: dedupNeededSchema } },\n description: \"Hashes needed for upload\",\n },\n ...notFoundResponse,\n },\n});\n\nconst contentUploadRoute = createRoute({\n method: \"post\",\n tags: [\"Builds\"],\n summary: \"Upload build content\",\n description:\n \"File-by-file upload, step 2: store the missing files content-addressed (multipart, or one file with an X-Content-Hash header).\",\n path: \"/api/v1/projects/{slug}/builds/{buildId}/content\",\n request: {\n params: z.object({ slug: z.string(), buildId: z.string() }),\n body: { content: { \"multipart/form-data\": { schema: z.any() } } },\n },\n responses: {\n 200: {\n content: { \"application/json\": { schema: z.object({ ok: z.boolean() }) } },\n description: \"Content uploaded\",\n },\n ...notFoundResponse,\n },\n});\n\nconst manifestFileSchema = z.object({ rel: z.string(), hash: z.string(), size: z.number() });\nconst manifestRoute = createRoute({\n method: \"post\",\n tags: [\"Builds\"],\n summary: \"Store the build manifest\",\n description:\n \"File-by-file upload, step 3: store the path-to-hash manifest. This completes publishing. With testing off the build becomes approved when it is on the project's default branch and stays pending on other branches; with testing on, a capture is queued and sets the status.\",\n path: \"/api/v1/projects/{slug}/builds/{buildId}/manifest\",\n request: {\n params: z.object({ slug: z.string(), buildId: z.string() }),\n body: {\n content: {\n \"application/json\": {\n schema: z.object({ files: z.array(manifestFileSchema) }),\n },\n },\n },\n },\n responses: {\n 200: {\n content: { \"application/json\": { schema: z.object({ ok: z.boolean() }) } },\n description: \"Manifest stored\",\n },\n ...notFoundResponse,\n },\n});\n\nconst affectedPayloadSchema = z.object({\n baselineSha: z.string().nullable(),\n changedFiles: z.array(z.string()).max(20_000),\n affectedImportPaths: z.array(z.string()).max(20_000).nullable(),\n});\nconst affectedRoute = createRoute({\n method: \"post\",\n tags: [\"Builds\"],\n summary: \"Record the affected stories\",\n path: \"/api/v1/projects/{slug}/builds/{buildId}/affected\",\n request: {\n params: z.object({ slug: z.string(), buildId: z.string() }),\n body: { content: { \"application/json\": { schema: affectedPayloadSchema } } },\n },\n responses: {\n 200: {\n content: { \"application/json\": { schema: buildSchema } },\n description: \"Affected set recorded\",\n },\n ...badRequest,\n ...forbiddenResponse,\n ...notFoundResponse,\n },\n});\n\nconst usagePayloadSchema = z.object({\n usage: z\n .array(\n z.object({\n storyImportPath: z.string().min(1).max(1024),\n packageName: z.string().min(1).max(214),\n modulePath: z.string().min(1).max(2048),\n version: z.string().max(128).nullable().optional(),\n }),\n )\n .max(20_000),\n});\nconst usageRoute = createRoute({\n method: \"post\",\n tags: [\"Builds\"],\n summary: \"Record the dependency packages each story uses\",\n path: \"/api/v1/projects/{slug}/builds/{buildId}/usage\",\n request: {\n params: z.object({ slug: z.string(), buildId: z.string() }),\n body: { content: { \"application/json\": { schema: usagePayloadSchema } } },\n },\n responses: {\n 200: {\n content: { \"application/json\": { schema: z.object({ stored: z.number() }) } },\n description: \"Usage recorded (replaces any earlier report for the build)\",\n },\n ...badRequest,\n ...forbiddenResponse,\n ...notFoundResponse,\n },\n});\n\nconst getBuildRoute = createRoute({\n method: \"get\",\n tags: [\"Builds\"],\n summary: \"Get a build\",\n path: \"/api/v1/projects/{slug}/builds/{buildId}\",\n request: { params: z.object({ slug: z.string(), buildId: z.string() }) },\n responses: {\n 200: { content: { \"application/json\": { schema: buildSchema } }, description: \"Fetch a build\" },\n ...notFoundResponse,\n ...unauthorized,\n },\n});\n\nconst retryBuildRoute = createRoute({\n method: \"post\",\n tags: [\"Builds\"],\n summary: \"Retry capture\",\n path: \"/api/v1/projects/{slug}/builds/{buildId}/retry\",\n request: { params: z.object({ slug: z.string(), buildId: z.string() }) },\n responses: {\n 202: {\n content: { \"application/json\": { schema: buildSchema } },\n description: \"Build reset to pending and capture re-queued (new attempt)\",\n },\n ...notFoundResponse,\n },\n});\n\nconst deleteBuildRoute = createRoute({\n method: \"delete\",\n tags: [\"Builds\"],\n summary: \"Delete a build\",\n path: \"/api/v1/projects/{slug}/builds/{buildId}\",\n request: { params: z.object({ slug: z.string(), buildId: z.string() }) },\n responses: {\n 204: { description: \"Build deleted\" },\n ...notFoundResponse,\n },\n});\n"],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { MAX_CAPTURE_ASSET_CACHE_SECONDS } from "@storyshelf/core/config";
|
|
5
|
+
// src/routers/capture-cache.ts
|
|
6
|
+
/**
|
|
7
|
+
* `Cache-Control` of a signed Storybook answer (ADR 0007, decision 5): `private, no-store` unless
|
|
8
|
+
* the server has opted a capture-scoped grant into browser-private freshness. The single owner of
|
|
9
|
+
* that rule; the runner never sets headers, it only receives a URL.
|
|
10
|
+
*/
|
|
11
|
+
/** Every signed answer's default, and the only value for an error, a preview or a legacy token. */
|
|
12
|
+
const SIGNED_NO_STORE = "private, no-store";
|
|
13
|
+
/**
|
|
14
|
+
* `private, max-age=N` with N the whole seconds of the smallest of the configured cap, one hour
|
|
15
|
+
* and the grant's remaining verified lifetime at this response; `no-store` when freshness is not
|
|
16
|
+
* opted in, the grant is not a capture grant, the file is not content-addressed, or none is left.
|
|
17
|
+
* It never grants freshness past the token's expiry. It does not affect authorization, which the
|
|
18
|
+
* route re-checks on every network request.
|
|
19
|
+
*/
|
|
20
|
+
function signedCacheControl(input) {
|
|
21
|
+
const { cacheMaxAgeSeconds, grant, immutable } = input;
|
|
22
|
+
if (!cacheMaxAgeSeconds || cacheMaxAgeSeconds <= 0 || !immutable || grant?.purpose !== "capture") return SIGNED_NO_STORE;
|
|
23
|
+
const remaining = grant.expiresAt - (input.now ?? Date.now()) / 1e3;
|
|
24
|
+
const seconds = Math.floor(Math.min(cacheMaxAgeSeconds, MAX_CAPTURE_ASSET_CACHE_SECONDS, remaining));
|
|
25
|
+
return seconds > 0 ? `private, max-age=${String(seconds)}` : SIGNED_NO_STORE;
|
|
26
|
+
}
|
|
27
|
+
export { SIGNED_NO_STORE, signedCacheControl };
|
|
28
|
+
|
|
29
|
+
//# sourceMappingURL=capture-cache.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capture-cache.mjs","names":[],"sources":["../../src/routers/capture-cache.ts"],"sourcesContent":["/**\n * `Cache-Control` of a signed Storybook answer (ADR 0007, decision 5): `private, no-store` unless\n * the server has opted a capture-scoped grant into browser-private freshness. The single owner of\n * that rule; the runner never sets headers, it only receives a URL.\n */\nimport { MAX_CAPTURE_ASSET_CACHE_SECONDS } from \"@storyshelf/core/config\";\nimport type { StorybookTokenClaims } from \"@storyshelf/core/utils\";\n\n/** Every signed answer's default, and the only value for an error, a preview or a legacy token. */\nexport const SIGNED_NO_STORE = \"private, no-store\";\n\n/** What decides the freshness of one successful signed answer. */\nexport interface SignedCacheInput {\n /** `config.captureAssets.cacheMaxAgeSeconds`; unset or 0 keeps `no-store`. */\n cacheMaxAgeSeconds: number | undefined;\n /** The verified claims of the token that authorized the request. */\n grant: Pick<StorybookTokenClaims, \"purpose\" | \"expiresAt\"> | undefined;\n /** Whether the file came through the build's finalized, content-addressed manifest. */\n immutable: boolean;\n /** Clock override for tests (ms). */\n now?: number;\n}\n\n/**\n * `private, max-age=N` with N the whole seconds of the smallest of the configured cap, one hour\n * and the grant's remaining verified lifetime at this response; `no-store` when freshness is not\n * opted in, the grant is not a capture grant, the file is not content-addressed, or none is left.\n * It never grants freshness past the token's expiry. It does not affect authorization, which the\n * route re-checks on every network request.\n */\nexport function signedCacheControl(input: SignedCacheInput): string {\n const { cacheMaxAgeSeconds, grant, immutable } = input;\n if (\n !cacheMaxAgeSeconds ||\n cacheMaxAgeSeconds <= 0 ||\n !immutable ||\n grant?.purpose !== \"capture\"\n ) {\n return SIGNED_NO_STORE;\n }\n const remaining = grant.expiresAt - (input.now ?? Date.now()) / 1000;\n const seconds = Math.floor(\n Math.min(cacheMaxAgeSeconds, MAX_CAPTURE_ASSET_CACHE_SECONDS, remaining),\n );\n return seconds > 0 ? `private, max-age=${String(seconds)}` : SIGNED_NO_STORE;\n}\n"],"mappings":";;;;;;;;;;;AASA,MAAa,kBAAkB;;;;;;;;AAqB/B,SAAgB,mBAAmB,OAAiC;CAClE,MAAM,EAAE,oBAAoB,OAAO,cAAc;CACjD,IACE,CAAC,sBACD,sBAAsB,KACtB,CAAC,aACD,OAAO,YAAY,WAEnB,OAAO;CAET,MAAM,YAAY,MAAM,aAAa,MAAM,OAAO,KAAK,IAAI,KAAK;CAChE,MAAM,UAAU,KAAK,MACnB,KAAK,IAAI,oBAAoB,iCAAiC,SAAS,CACzE;CACA,OAAO,UAAU,IAAI,oBAAoB,OAAO,OAAO,MAAM;AAC/D"}
|
|
@@ -3,7 +3,7 @@ import "node:url";
|
|
|
3
3
|
import.meta.url;
|
|
4
4
|
import { modeSlug } from "@storyshelf/core/capture";
|
|
5
5
|
import { HTTPException } from "hono/http-exception";
|
|
6
|
-
|
|
6
|
+
// src/routers/capture-config.ts
|
|
7
7
|
/** Translating the capture settings (browsers, mode allowlist) into stored project columns. */
|
|
8
8
|
/**
|
|
9
9
|
* Translate the array fields into the stored JSON columns and keep the legacy
|
|
@@ -29,7 +29,6 @@ function assertModeNames(names) {
|
|
|
29
29
|
}
|
|
30
30
|
return [...new Set(names)];
|
|
31
31
|
}
|
|
32
|
-
//#endregion
|
|
33
32
|
export { assertModeNames, toProjectPatch };
|
|
34
33
|
|
|
35
34
|
//# sourceMappingURL=capture-config.mjs.map
|
|
@@ -12,7 +12,7 @@ import { DEVELOPER_ROLES, VIEW_ROLES, buildForProject } from "./builds.handlers.
|
|
|
12
12
|
import { createRoute, z } from "@hono/zod-openapi";
|
|
13
13
|
import { CommentModel } from "@storyshelf/core/models";
|
|
14
14
|
import { HTTPException } from "hono/http-exception";
|
|
15
|
-
|
|
15
|
+
// src/routers/comments.ts
|
|
16
16
|
const listCommentsRoute = createRoute({
|
|
17
17
|
method: "get",
|
|
18
18
|
tags: ["Review"],
|
|
@@ -129,7 +129,6 @@ async function createComment(project, buildId, body) {
|
|
|
129
129
|
await announceComment(project, buildId, comment);
|
|
130
130
|
return comment;
|
|
131
131
|
}
|
|
132
|
-
//#endregion
|
|
133
132
|
export { createComment, registerComments, resolveBuildComment };
|
|
134
133
|
|
|
135
134
|
//# sourceMappingURL=comments.mjs.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { getStore } from "../store.mjs";
|
|
5
|
+
import { isComparisonImagePath, parseComparisonReference } from "@storyshelf/core/capture";
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
7
|
+
// src/routers/comparison-image.ts
|
|
8
|
+
/** Validate provenance and ownership before reading a frozen reference. */
|
|
9
|
+
function validFrozenReference(snapshot, projectId, buildId) {
|
|
10
|
+
const reference = parseComparisonReference(snapshot.comparisonMetadata);
|
|
11
|
+
const path = snapshot.comparisonImagePath;
|
|
12
|
+
if (!reference || !path || !isComparisonImagePath(path, projectId, buildId) || reference.baselineId !== snapshot.baselineId || reference.baselineVersion !== snapshot.baselineVersion) return null;
|
|
13
|
+
return {
|
|
14
|
+
reference,
|
|
15
|
+
path
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/** A comparison is usable only when its bytes match the capture-time record. */
|
|
19
|
+
async function readComparisonImage(path, sha256) {
|
|
20
|
+
try {
|
|
21
|
+
const image = await getStore().storage.read(path);
|
|
22
|
+
return createHash("sha256").update(image).digest("hex") === sha256 ? image : null;
|
|
23
|
+
} catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export { readComparisonImage, validFrozenReference };
|
|
28
|
+
|
|
29
|
+
//# sourceMappingURL=comparison-image.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"comparison-image.mjs","names":[],"sources":["../../src/routers/comparison-image.ts"],"sourcesContent":["import {\n isComparisonImagePath,\n parseComparisonReference,\n type ComparisonReference,\n} from \"@storyshelf/core/capture\";\nimport type { Snapshot } from \"@storyshelf/core/schema\";\nimport { createHash } from \"node:crypto\";\nimport { getStore } from \"../store.ts\";\n\n/** Validate provenance and ownership before reading a frozen reference. */\nexport function validFrozenReference(\n snapshot: Snapshot,\n projectId: string,\n buildId: string,\n): { reference: ComparisonReference; path: string } | null {\n const reference = parseComparisonReference(snapshot.comparisonMetadata);\n const path = snapshot.comparisonImagePath;\n if (\n !reference ||\n !path ||\n !isComparisonImagePath(path, projectId, buildId) ||\n reference.baselineId !== snapshot.baselineId ||\n reference.baselineVersion !== snapshot.baselineVersion\n )\n return null;\n return { reference, path };\n}\n\n/** A comparison is usable only when its bytes match the capture-time record. */\nexport async function readComparisonImage(path: string, sha256: string): Promise<Buffer | null> {\n try {\n const image = await getStore().storage.read(path);\n return createHash(\"sha256\").update(image).digest(\"hex\") === sha256 ? image : null;\n } catch {\n return null;\n }\n}\n"],"mappings":";;;;;;;;AAUA,SAAgB,qBACd,UACA,WACA,SACyD;CACzD,MAAM,YAAY,yBAAyB,SAAS,kBAAkB;CACtE,MAAM,OAAO,SAAS;CACtB,IACE,CAAC,aACD,CAAC,QACD,CAAC,sBAAsB,MAAM,WAAW,OAAO,KAC/C,UAAU,eAAe,SAAS,cAClC,UAAU,oBAAoB,SAAS,iBAEvC,OAAO;CACT,OAAO;EAAE;EAAW;CAAK;AAC3B;;AAGA,eAAsB,oBAAoB,MAAc,QAAwC;CAC9F,IAAI;EACF,MAAM,QAAQ,MAAM,SAAS,CAAC,CAAC,QAAQ,KAAK,IAAI;EAChD,OAAO,WAAW,QAAQ,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO,KAAK,MAAM,SAAS,QAAQ;CAC/E,QAAQ;EACN,OAAO;CACT;AACF"}
|
|
@@ -4,7 +4,7 @@ import.meta.url;
|
|
|
4
4
|
import { catalogOf, getStore } from "../store.mjs";
|
|
5
5
|
import { catalogPresence, componentBuildHistory, diffEntries } from "@storyshelf/core/capture";
|
|
6
6
|
import { BuildComponentModel, BuildModel } from "@storyshelf/core/models";
|
|
7
|
-
|
|
7
|
+
// src/routers/component-catalog.handlers.ts
|
|
8
8
|
/** Newest builds a component's history lists. */
|
|
9
9
|
const HISTORY_LIMIT = 50;
|
|
10
10
|
/** Changed rows whose added/removed/renamed counts are worked out (each loads two catalogs). */
|
|
@@ -65,7 +65,6 @@ async function withChanges(projectId, importPath, history) {
|
|
|
65
65
|
};
|
|
66
66
|
}));
|
|
67
67
|
}
|
|
68
|
-
//#endregion
|
|
69
68
|
export { latestProjectBuild, loadCatalogView };
|
|
70
69
|
|
|
71
70
|
//# sourceMappingURL=component-catalog.handlers.mjs.map
|
|
@@ -11,7 +11,7 @@ import { componentTabs } from "../pages/component-detail.mjs";
|
|
|
11
11
|
import { entriesFromSnapshots } from "../pages/library-model.mjs";
|
|
12
12
|
import { latestProjectBuild, loadCatalogView } from "./component-catalog.handlers.mjs";
|
|
13
13
|
import { BuildModel, ProjectModel, SnapshotModel, componentHistory, componentPresence, normalizeImportPath } from "@storyshelf/core/models";
|
|
14
|
-
|
|
14
|
+
// src/routers/component.handlers.ts
|
|
15
15
|
async function buildsOf(snapshots) {
|
|
16
16
|
const model = new BuildModel(getStore().db);
|
|
17
17
|
return (await Promise.all([...new Set(snapshots.map((snap) => snap.buildId))].map(async (id) => await model.get(id)))).filter((build) => build !== null);
|
|
@@ -115,7 +115,6 @@ function viewOfCaptured(loaded) {
|
|
|
115
115
|
function resolveTab(requested, testing, captured) {
|
|
116
116
|
return componentTabs(testing).find((candidate) => candidate === requested) ?? (testing && captured ? "states" : "stories");
|
|
117
117
|
}
|
|
118
|
-
//#endregion
|
|
119
118
|
export { loadComponentPage };
|
|
120
119
|
|
|
121
120
|
//# sourceMappingURL=component.handlers.mjs.map
|
|
@@ -7,7 +7,7 @@ import { flash } from "./flash.mjs";
|
|
|
7
7
|
import { connectionModel, findConnectable } from "../connections.mjs";
|
|
8
8
|
import { safeReturnPath, signState, verifyState } from "./oauth-state.mjs";
|
|
9
9
|
import { deleteCookie, getCookie, setCookie } from "hono/cookie";
|
|
10
|
-
|
|
10
|
+
// src/routers/connections.ts
|
|
11
11
|
const COOKIE = "storyshelf_oauth_state";
|
|
12
12
|
const COOKIE_PATH = "/connections";
|
|
13
13
|
/**
|
|
@@ -112,7 +112,6 @@ async function handleDisconnect(c) {
|
|
|
112
112
|
function formString(value) {
|
|
113
113
|
return typeof value === "string" ? value : "";
|
|
114
114
|
}
|
|
115
|
-
//#endregion
|
|
116
115
|
export { registerConnections };
|
|
117
116
|
|
|
118
117
|
//# sourceMappingURL=connections.mjs.map
|
|
@@ -5,7 +5,7 @@ import { credentialKeys } from "../credential-keys.mjs";
|
|
|
5
5
|
import { getStore } from "../store.mjs";
|
|
6
6
|
import { canConnect, connectionModel } from "../connections.mjs";
|
|
7
7
|
import { DesignCredentialModel } from "@storyshelf/core/models";
|
|
8
|
-
|
|
8
|
+
// src/routers/design-connect.handlers.ts
|
|
9
9
|
/** The signed-in user whose own connection may serve previews (none when auth is off). */
|
|
10
10
|
function viewerIdOf() {
|
|
11
11
|
const { user, authEnabled } = getStore();
|
|
@@ -38,7 +38,6 @@ function connectOffer(kind, row, returnTo) {
|
|
|
38
38
|
returnTo
|
|
39
39
|
};
|
|
40
40
|
}
|
|
41
|
-
//#endregion
|
|
42
41
|
export { loadConnectStates, viewerIdOf };
|
|
43
42
|
|
|
44
43
|
//# sourceMappingURL=design-connect.handlers.mjs.map
|
|
@@ -4,10 +4,11 @@ import.meta.url;
|
|
|
4
4
|
import { credentialKeys } from "../credential-keys.mjs";
|
|
5
5
|
import { getStore } from "../store.mjs";
|
|
6
6
|
import { connectionModel } from "../connections.mjs";
|
|
7
|
+
import { announceDesignChangesWith } from "../design-events.mjs";
|
|
7
8
|
import { loadConnectStates, viewerIdOf } from "./design-connect.handlers.mjs";
|
|
8
9
|
import { DesignCredentialModel, DesignModel, DesignNodeModel, isDesignStale, normalizeImportPath, resolveDesigns } from "@storyshelf/core/models";
|
|
9
10
|
import { parseDesignUrl } from "@storyshelf/core/adapter/design";
|
|
10
|
-
|
|
11
|
+
// src/routers/design.handlers.ts
|
|
11
12
|
/** Most a page render waits for fresh design metadata; slower fetches finish into the cache for next time. */
|
|
12
13
|
const REFRESH_BUDGET_MS = 1500;
|
|
13
14
|
/** Path + query of the current request, to return to after a connect round trip. */
|
|
@@ -47,6 +48,12 @@ async function loadReviewDesign(project, snapshot, urls, returnTo = "/") {
|
|
|
47
48
|
const viewerId = viewerIdOf();
|
|
48
49
|
await refreshWithinBudget(nodes, project.id, urls, viewerId);
|
|
49
50
|
const cached = await nodes.cachedFor(project.id, urls, viewerId).catch(() => /* @__PURE__ */ new Map());
|
|
51
|
+
await announceDesignChangesWith(getStore(), nodes, project, urls.map((url) => ({
|
|
52
|
+
url,
|
|
53
|
+
node: cached.get(url),
|
|
54
|
+
componentPath: snapshot.storyImportPath,
|
|
55
|
+
baselineVersion: snapshot.baselineVersion
|
|
56
|
+
})));
|
|
50
57
|
const connects = await loadConnectStates(project.id, urls.map((url) => kindOf(url)), returnTo).catch(() => /* @__PURE__ */ new Map());
|
|
51
58
|
return urls.map((url) => {
|
|
52
59
|
const node = cached.get(url);
|
|
@@ -108,7 +115,6 @@ async function refreshWithinBudget(nodes, projectId, urls, viewerId) {
|
|
|
108
115
|
...viewerId ? { viewerId } : {}
|
|
109
116
|
}).catch(() => /* @__PURE__ */ new Map());
|
|
110
117
|
}
|
|
111
|
-
//#endregion
|
|
112
118
|
export { designUrlsFor, loadDesignPreviews, loadDesignResolutions, loadProjectDesignLinks, loadReviewDesign, providerNameFor, returnPathOf };
|
|
113
119
|
|
|
114
120
|
//# sourceMappingURL=design.handlers.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"design.handlers.mjs","names":[],"sources":["../../src/routers/design.handlers.ts"],"sourcesContent":["import { parseDesignUrl } from \"@storyshelf/core/adapter/design\";\nimport {\n DesignCredentialModel,\n DesignModel,\n DesignNodeModel,\n isDesignStale,\n resolveDesigns,\n normalizeImportPath,\n type DesignResolution,\n} from \"@storyshelf/core/models\";\nimport type { DesignNode, Project, Snapshot } from \"@storyshelf/core/schema\";\nimport { connectionModel } from \"../connections.ts\";\nimport { credentialKeys } from \"../credential-keys.ts\";\nimport type {\n DesignConnect,\n DesignItem,\n DesignPreviewInfo,\n ProjectDesignLink,\n} from \"../pages/design-links.tsx\";\nimport { getStore } from \"../store.ts\";\nimport { loadConnectStates, viewerIdOf } from \"./design-connect.handlers.ts\";\n\n/** Most a page render waits for fresh design metadata; slower fetches finish into the cache for next time. */\nconst REFRESH_BUDGET_MS = 1500;\n\n/** Path + query of the current request, to return to after a connect round trip. */\nexport function returnPathOf(c: { req: { url: string } }): string {\n const url = new URL(c.req.url);\n return `${url.pathname}${url.search}`;\n}\n\n/** Component (story file) → design link(s) for a build's snapshots; files without a link are absent. */\nexport async function loadDesignResolutions(\n project: Project,\n snapshots: readonly Snapshot[],\n): Promise<Map<string, DesignResolution>> {\n const { db, design } = getStore();\n if (design.length === 0) return new Map();\n const model = new DesignModel(db, design);\n return resolveDesigns(await model.componentLinks(project.id), snapshots);\n}\n\n/** Design URLs of one snapshot's component. */\nexport function designUrlsFor(\n resolutions: Map<string, DesignResolution>,\n snapshot: Pick<Snapshot, \"storyImportPath\">,\n): string[] {\n const key = snapshot.storyImportPath ? normalizeImportPath(snapshot.storyImportPath) : \"\";\n return resolutions.get(key)?.urls ?? [];\n}\n\n/** Project-level design file links for the library header. */\nexport async function loadProjectDesignLinks(project: Project): Promise<ProjectDesignLink[]> {\n const { db, design } = getStore();\n if (design.length === 0) return [];\n const links = await new DesignModel(db, design).projectLinks(project.id);\n return links.map((link) => ({ label: link.label ?? \"Design\", url: link.url }));\n}\n\n/**\n * Design items for the review page: each link with cached name/state and a\n * stale flag against the snapshot's baseline. Refreshes expired metadata\n * within a short budget; failures degrade to plain deep links, never blocking review.\n */\nexport async function loadReviewDesign(\n project: Project,\n snapshot: Snapshot,\n urls: readonly string[],\n returnTo = \"/\",\n): Promise<DesignItem[]> {\n if (urls.length === 0) return [];\n const nodes = nodeModel();\n const viewerId = viewerIdOf();\n await refreshWithinBudget(nodes, project.id, urls, viewerId);\n const cached = await nodes\n .cachedFor(project.id, urls, viewerId)\n .catch(() => new Map<string, DesignNode>());\n const connects = await loadConnectStates(\n project.id,\n urls.map((url) => kindOf(url)),\n returnTo,\n ).catch(() => new Map<string, DesignConnect>());\n return urls.map((url) => {\n const node = cached.get(url);\n const connect = connects.get(kindOf(url));\n return {\n ...(connect ? { connect } : {}),\n url,\n name: node?.name ?? null,\n providerName: providerNameFor(url),\n renderUrl: renderUrlOf(project, node),\n state: node?.state ?? \"unknown\",\n stale: isDesignStale(node, snapshot.baselineVersion),\n };\n });\n}\n\n/** Most distinct design links a library render refreshes (the model bounds file concurrency). */\nconst WARM_LIMIT = 10;\n\n/**\n * Cached name/render per design URL for library popovers. Missing or expired\n * entries are refreshed first, within the page-render budget and with bounded\n * concurrency, so a slow design API never holds the library up or fans out unbounded.\n */\nexport async function loadDesignPreviews(\n project: Project,\n urls: readonly string[],\n): Promise<Map<string, DesignPreviewInfo>> {\n const distinct = [...new Set(urls)];\n if (distinct.length === 0) return new Map();\n const nodes = nodeModel();\n const viewerId = viewerIdOf();\n await refreshWithinBudget(nodes, project.id, distinct.slice(0, WARM_LIMIT), viewerId);\n const cached = await nodes\n .cachedFor(project.id, distinct, viewerId)\n .catch(() => new Map<string, DesignNode>());\n return new Map(\n [...cached].map(([url, node]) => [\n url,\n { name: node.name, renderUrl: renderUrlOf(project, node) },\n ]),\n );\n}\n\nfunction nodeModel(): DesignNodeModel {\n const { db, config, logger, storage, design } = getStore();\n const credentials = new DesignCredentialModel(db, credentialKeys(config));\n return new DesignNodeModel(db, credentials, design, {\n logger,\n storage,\n connections: connectionModel(),\n });\n}\n\nfunction kindOf(url: string): string {\n return parseDesignUrl(getStore().design, url)?.provider.metadata.kind ?? \"\";\n}\n\n/** Display name of the provider that owns a design link (\"Figma\"), or a generic fallback. */\nexport function providerNameFor(url: string): string {\n return parseDesignUrl(getStore().design, url)?.provider.metadata.name ?? \"Design\";\n}\n\n/** API URL of a node's cached render (version-busted), or null when none is cached. */\nfunction renderUrlOf(project: Project, node: DesignNode | undefined): string | null {\n return node?.renderPath\n ? `/api/v1/projects/${project.slug}/design/renders/${node.id}?v=${encodeURIComponent(node.renderVersion ?? \"\")}`\n : null;\n}\n\n/** Refresh the links, aborting every provider call once the budget is spent (the cache keeps what landed). */\nasync function refreshWithinBudget(\n nodes: DesignNodeModel,\n projectId: string,\n urls: readonly string[],\n viewerId?: string,\n): Promise<void> {\n await nodes\n .refreshMany(projectId, urls, {\n signal: AbortSignal.timeout(REFRESH_BUDGET_MS),\n ...(viewerId ? { viewerId } : {}),\n })\n .catch(() => new Map());\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"design.handlers.mjs","names":[],"sources":["../../src/routers/design.handlers.ts"],"sourcesContent":["import { parseDesignUrl } from \"@storyshelf/core/adapter/design\";\nimport {\n DesignCredentialModel,\n DesignModel,\n DesignNodeModel,\n isDesignStale,\n resolveDesigns,\n normalizeImportPath,\n type DesignResolution,\n} from \"@storyshelf/core/models\";\nimport type { DesignNode, Project, Snapshot } from \"@storyshelf/core/schema\";\nimport { connectionModel } from \"../connections.ts\";\nimport { credentialKeys } from \"../credential-keys.ts\";\nimport { announceDesignChangesWith } from \"../design-events.ts\";\nimport type {\n DesignConnect,\n DesignItem,\n DesignPreviewInfo,\n ProjectDesignLink,\n} from \"../pages/design-links.tsx\";\nimport { getStore } from \"../store.ts\";\nimport { loadConnectStates, viewerIdOf } from \"./design-connect.handlers.ts\";\n\n/** Most a page render waits for fresh design metadata; slower fetches finish into the cache for next time. */\nconst REFRESH_BUDGET_MS = 1500;\n\n/** Path + query of the current request, to return to after a connect round trip. */\nexport function returnPathOf(c: { req: { url: string } }): string {\n const url = new URL(c.req.url);\n return `${url.pathname}${url.search}`;\n}\n\n/** Component (story file) → design link(s) for a build's snapshots; files without a link are absent. */\nexport async function loadDesignResolutions(\n project: Project,\n snapshots: readonly Snapshot[],\n): Promise<Map<string, DesignResolution>> {\n const { db, design } = getStore();\n if (design.length === 0) return new Map();\n const model = new DesignModel(db, design);\n return resolveDesigns(await model.componentLinks(project.id), snapshots);\n}\n\n/** Design URLs of one snapshot's component. */\nexport function designUrlsFor(\n resolutions: Map<string, DesignResolution>,\n snapshot: Pick<Snapshot, \"storyImportPath\">,\n): string[] {\n const key = snapshot.storyImportPath ? normalizeImportPath(snapshot.storyImportPath) : \"\";\n return resolutions.get(key)?.urls ?? [];\n}\n\n/** Project-level design file links for the library header. */\nexport async function loadProjectDesignLinks(project: Project): Promise<ProjectDesignLink[]> {\n const { db, design } = getStore();\n if (design.length === 0) return [];\n const links = await new DesignModel(db, design).projectLinks(project.id);\n return links.map((link) => ({ label: link.label ?? \"Design\", url: link.url }));\n}\n\n/**\n * Design items for the review page: each link with cached name/state and a\n * stale flag against the snapshot's baseline. Refreshes expired metadata\n * within a short budget; failures degrade to plain deep links, never blocking review.\n */\nexport async function loadReviewDesign(\n project: Project,\n snapshot: Snapshot,\n urls: readonly string[],\n returnTo = \"/\",\n): Promise<DesignItem[]> {\n if (urls.length === 0) return [];\n const nodes = nodeModel();\n const viewerId = viewerIdOf();\n await refreshWithinBudget(nodes, project.id, urls, viewerId);\n const cached = await nodes\n .cachedFor(project.id, urls, viewerId)\n .catch(() => new Map<string, DesignNode>());\n await announceDesignChangesWith(\n getStore(),\n nodes,\n project,\n urls.map((url) => ({\n url,\n node: cached.get(url),\n componentPath: snapshot.storyImportPath,\n baselineVersion: snapshot.baselineVersion,\n })),\n );\n const connects = await loadConnectStates(\n project.id,\n urls.map((url) => kindOf(url)),\n returnTo,\n ).catch(() => new Map<string, DesignConnect>());\n return urls.map((url) => {\n const node = cached.get(url);\n const connect = connects.get(kindOf(url));\n return {\n ...(connect ? { connect } : {}),\n url,\n name: node?.name ?? null,\n providerName: providerNameFor(url),\n renderUrl: renderUrlOf(project, node),\n state: node?.state ?? \"unknown\",\n stale: isDesignStale(node, snapshot.baselineVersion),\n };\n });\n}\n\n/** Most distinct design links a library render refreshes (the model bounds file concurrency). */\nconst WARM_LIMIT = 10;\n\n/**\n * Cached name/render per design URL for library popovers. Missing or expired\n * entries are refreshed first, within the page-render budget and with bounded\n * concurrency, so a slow design API never holds the library up or fans out unbounded.\n */\nexport async function loadDesignPreviews(\n project: Project,\n urls: readonly string[],\n): Promise<Map<string, DesignPreviewInfo>> {\n const distinct = [...new Set(urls)];\n if (distinct.length === 0) return new Map();\n const nodes = nodeModel();\n const viewerId = viewerIdOf();\n await refreshWithinBudget(nodes, project.id, distinct.slice(0, WARM_LIMIT), viewerId);\n const cached = await nodes\n .cachedFor(project.id, distinct, viewerId)\n .catch(() => new Map<string, DesignNode>());\n return new Map(\n [...cached].map(([url, node]) => [\n url,\n { name: node.name, renderUrl: renderUrlOf(project, node) },\n ]),\n );\n}\n\nfunction nodeModel(): DesignNodeModel {\n const { db, config, logger, storage, design } = getStore();\n const credentials = new DesignCredentialModel(db, credentialKeys(config));\n return new DesignNodeModel(db, credentials, design, {\n logger,\n storage,\n connections: connectionModel(),\n });\n}\n\nfunction kindOf(url: string): string {\n return parseDesignUrl(getStore().design, url)?.provider.metadata.kind ?? \"\";\n}\n\n/** Display name of the provider that owns a design link (\"Figma\"), or a generic fallback. */\nexport function providerNameFor(url: string): string {\n return parseDesignUrl(getStore().design, url)?.provider.metadata.name ?? \"Design\";\n}\n\n/** API URL of a node's cached render (version-busted), or null when none is cached. */\nfunction renderUrlOf(project: Project, node: DesignNode | undefined): string | null {\n return node?.renderPath\n ? `/api/v1/projects/${project.slug}/design/renders/${node.id}?v=${encodeURIComponent(node.renderVersion ?? \"\")}`\n : null;\n}\n\n/** Refresh the links, aborting every provider call once the budget is spent (the cache keeps what landed). */\nasync function refreshWithinBudget(\n nodes: DesignNodeModel,\n projectId: string,\n urls: readonly string[],\n viewerId?: string,\n): Promise<void> {\n await nodes\n .refreshMany(projectId, urls, {\n signal: AbortSignal.timeout(REFRESH_BUDGET_MS),\n ...(viewerId ? { viewerId } : {}),\n })\n .catch(() => new Map());\n}\n"],"mappings":"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"}
|
package/dist/routers/flash.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import "node:url";
|
|
|
3
3
|
import.meta.url;
|
|
4
4
|
import { isHxRequest } from "./htmx.mjs";
|
|
5
5
|
import { setCookie } from "hono/cookie";
|
|
6
|
-
|
|
6
|
+
// src/routers/flash.ts
|
|
7
7
|
/** Cookie the client script reads, shows as a toast, and clears. */
|
|
8
8
|
const FLASH_COOKIE = "storyshelf_flash";
|
|
9
9
|
const MAX_MESSAGE_LENGTH = 140;
|
|
@@ -29,7 +29,6 @@ function flash(c, message, tone = "success") {
|
|
|
29
29
|
function flashHx(c, message, tone = "success") {
|
|
30
30
|
if (isHxRequest(c)) flash(c, message, tone);
|
|
31
31
|
}
|
|
32
|
-
//#endregion
|
|
33
32
|
export { FLASH_COOKIE, flash, flashHx };
|
|
34
33
|
|
|
35
34
|
//# sourceMappingURL=flash.mjs.map
|
|
@@ -2,7 +2,7 @@ import "node:path";
|
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
4
|
import { sanitizeErrorText } from "@storyshelf/core/utils";
|
|
5
|
-
|
|
5
|
+
// src/routers/health-report.ts
|
|
6
6
|
const PROBE_TIMEOUT_MS = 2e3;
|
|
7
7
|
const DETAIL_MAX_LENGTH = 500;
|
|
8
8
|
function pushTarget(targets, adapter) {
|
|
@@ -152,7 +152,6 @@ async function collectHealthReport(sources, settled, bootTimeMs, version, creden
|
|
|
152
152
|
adapters
|
|
153
153
|
};
|
|
154
154
|
}
|
|
155
|
-
//#endregion
|
|
156
155
|
export { collectHealthReport };
|
|
157
156
|
|
|
158
157
|
//# sourceMappingURL=health-report.mjs.map
|
package/dist/routers/health.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { getStore, testingEnabled } from "../store.mjs";
|
|
|
5
5
|
import { loadCredentialProbe } from "../credentials.mjs";
|
|
6
6
|
import { requireSiteAdmin } from "./helpers.mjs";
|
|
7
7
|
import { collectHealthReport } from "./health-report.mjs";
|
|
8
|
-
|
|
8
|
+
// src/routers/health.ts
|
|
9
9
|
/** Register `GET /api/v1/health` (open liveness) and `POST` (authed deep readiness). */
|
|
10
10
|
function registerHealth(app, deps) {
|
|
11
11
|
app.get("/api/v1/health", (c) => {
|
|
@@ -41,7 +41,6 @@ function registerHealth(app, deps) {
|
|
|
41
41
|
return c.json(body, body.status === "ok" ? 200 : 503);
|
|
42
42
|
});
|
|
43
43
|
}
|
|
44
|
-
//#endregion
|
|
45
44
|
export { registerHealth };
|
|
46
45
|
|
|
47
46
|
//# sourceMappingURL=health.mjs.map
|
package/dist/routers/helpers.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { getStore } from "../store.mjs";
|
|
|
5
5
|
import { sha256, timingSafeEqualString } from "@storyshelf/core/utils";
|
|
6
6
|
import { MemberModel, ProjectModel, TokenModel, UserModel } from "@storyshelf/core/models";
|
|
7
7
|
import { HTTPException } from "hono/http-exception";
|
|
8
|
-
|
|
8
|
+
// src/routers/helpers.ts
|
|
9
9
|
/** Throw a 401 Unauthorized error for unauthenticated requests. */
|
|
10
10
|
function unauthorized() {
|
|
11
11
|
throw new HTTPException(401, { message: "Unauthorized" });
|
|
@@ -120,7 +120,6 @@ function requireSiteAdmin(c) {
|
|
|
120
120
|
if (user?.role === "admin" || requestHasAdminToken(c)) return;
|
|
121
121
|
forbidden();
|
|
122
122
|
}
|
|
123
|
-
//#endregion
|
|
124
123
|
export { assertRole, currentProjectRole, findProjectBySlug, forbidden, notFound, requestHasAdminToken, requireSiteAdmin, resolveAuthorizedProject, resolveReviewer, unauthorized };
|
|
125
124
|
|
|
126
125
|
//# sourceMappingURL=helpers.mjs.map
|
package/dist/routers/htmx.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "node:path";
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
|
-
|
|
4
|
+
// src/routers/htmx.ts
|
|
5
5
|
/** Check whether the request came from HTMX (HX-Request header). */
|
|
6
6
|
function isHxRequest(c) {
|
|
7
7
|
return c.req.header("HX-Request") === "true";
|
|
@@ -18,7 +18,6 @@ function hxRedirect(c, url) {
|
|
|
18
18
|
function hxRefresh(c) {
|
|
19
19
|
if (isHxRequest(c)) c.header("HX-Refresh", "true");
|
|
20
20
|
}
|
|
21
|
-
//#endregion
|
|
22
21
|
export { hxRedirect, hxRefresh, isHxRequest };
|
|
23
22
|
|
|
24
23
|
//# sourceMappingURL=htmx.mjs.map
|
|
@@ -10,7 +10,7 @@ import { InsightPanel } from "../pages/build-diff-insight.mjs";
|
|
|
10
10
|
import { HealthPanel } from "../pages/health-panel.mjs";
|
|
11
11
|
import { projectForRegenerate, projectForView } from "./insights.handlers.mjs";
|
|
12
12
|
import { jsx } from "hono/jsx/jsx-runtime";
|
|
13
|
-
|
|
13
|
+
// src/routers/insights-ui.tsx
|
|
14
14
|
/** Register the HTMX fragments for the project health panel. */
|
|
15
15
|
function registerHealthFragments(app) {
|
|
16
16
|
app.get("/projects/:slug/insights/health/panel", async (c) => {
|
|
@@ -58,7 +58,6 @@ function registerInsightFragments(app) {
|
|
|
58
58
|
})) : notFound();
|
|
59
59
|
});
|
|
60
60
|
}
|
|
61
|
-
//#endregion
|
|
62
61
|
export { registerInsightFragments };
|
|
63
62
|
|
|
64
63
|
//# sourceMappingURL=insights-ui.mjs.map
|
|
@@ -6,7 +6,7 @@ import { failWith } from "../insights/errors.mjs";
|
|
|
6
6
|
import { assertRole, requestHasAdminToken, resolveAuthorizedProject } from "./helpers.mjs";
|
|
7
7
|
import { insightDepsFromStore } from "../insights/deps.mjs";
|
|
8
8
|
import { APPROVER_ROLES, VIEW_ROLES } from "./builds.handlers.mjs";
|
|
9
|
-
|
|
9
|
+
// src/routers/insights.handlers.ts
|
|
10
10
|
/** AI deps or `501 ai-disabled` when the site has no AI configured. */
|
|
11
11
|
function requireAi() {
|
|
12
12
|
const deps = insightDepsFromStore();
|
|
@@ -39,7 +39,6 @@ async function projectForRegenerate(c, slug) {
|
|
|
39
39
|
project
|
|
40
40
|
};
|
|
41
41
|
}
|
|
42
|
-
//#endregion
|
|
43
42
|
export { isSiteAdmin, projectForRegenerate, projectForView, requireAi, requireProjectAi };
|
|
44
43
|
|
|
45
44
|
//# sourceMappingURL=insights.handlers.mjs.map
|
|
@@ -11,7 +11,7 @@ import { requestHealth, requestTriage } from "../insights/request.mjs";
|
|
|
11
11
|
import { isSiteAdmin, projectForRegenerate, projectForView } from "./insights.handlers.mjs";
|
|
12
12
|
import { createRoute, z } from "@hono/zod-openapi";
|
|
13
13
|
import { InsightModel } from "@storyshelf/core/models";
|
|
14
|
-
|
|
14
|
+
// src/routers/insights.ts
|
|
15
15
|
const BUILD_PARAMS = z.object({
|
|
16
16
|
slug: z.string(),
|
|
17
17
|
buildId: z.string()
|
|
@@ -183,7 +183,6 @@ function registerInsights(app) {
|
|
|
183
183
|
registerBuildInsights(app);
|
|
184
184
|
registerHealthInsights(app);
|
|
185
185
|
}
|
|
186
|
-
//#endregion
|
|
187
186
|
export { registerInsights };
|
|
188
187
|
|
|
189
188
|
//# sourceMappingURL=insights.mjs.map
|
package/dist/routers/labels.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import { labelTypeCreateSchema, labelTypeSchema, labelTypeUpdateSchema, notFound
|
|
|
7
7
|
import { createRoute, z } from "@hono/zod-openapi";
|
|
8
8
|
import { LabelModel } from "@storyshelf/core/models";
|
|
9
9
|
import { HTTPException } from "hono/http-exception";
|
|
10
|
-
|
|
10
|
+
// src/routers/labels.ts
|
|
11
11
|
const VIEW_ROLES = [
|
|
12
12
|
"viewer",
|
|
13
13
|
"developer",
|
|
@@ -112,7 +112,6 @@ function registerLabels(app) {
|
|
|
112
112
|
}
|
|
113
113
|
});
|
|
114
114
|
}
|
|
115
|
-
//#endregion
|
|
116
115
|
export { registerLabels };
|
|
117
116
|
|
|
118
117
|
//# sourceMappingURL=labels.mjs.map
|
|
@@ -4,7 +4,7 @@ import.meta.url;
|
|
|
4
4
|
import { catalogOf, getStore } from "../store.mjs";
|
|
5
5
|
import { resolveAuthorizedProject } from "./helpers.mjs";
|
|
6
6
|
import { BuildModel, PackageUsageModel, ProjectModel, SnapshotModel, matchUpstreamStory } from "@storyshelf/core/models";
|
|
7
|
-
|
|
7
|
+
// src/routers/links.handlers.ts
|
|
8
8
|
const VIEW_ROLES = [
|
|
9
9
|
"viewer",
|
|
10
10
|
"developer",
|
|
@@ -74,7 +74,6 @@ async function linkedUsageFor(c, project, buildId, storyImportPath) {
|
|
|
74
74
|
if (!storyImportPath) return [];
|
|
75
75
|
return (await linkedUsageByStory(c, project, buildId, storyImportPath)).get(storyImportPath) ?? [];
|
|
76
76
|
}
|
|
77
|
-
//#endregion
|
|
78
77
|
export { linkedUsageByStory, linkedUsageFor };
|
|
79
78
|
|
|
80
79
|
//# sourceMappingURL=links.handlers.mjs.map
|
package/dist/routers/links.mjs
CHANGED
|
@@ -9,7 +9,7 @@ import { linkedUsageFor } from "./links.handlers.mjs";
|
|
|
9
9
|
import { createRoute, z } from "@hono/zod-openapi";
|
|
10
10
|
import { PackageUsageModel, ProjectModel } from "@storyshelf/core/models";
|
|
11
11
|
import { HTTPException } from "hono/http-exception";
|
|
12
|
-
|
|
12
|
+
// src/routers/links.ts
|
|
13
13
|
const VIEW_ROLES = [
|
|
14
14
|
"viewer",
|
|
15
15
|
"developer",
|
|
@@ -133,7 +133,6 @@ function registerLinks(app) {
|
|
|
133
133
|
return c.json(await linkedUsageFor(c, project, build.id, snapshot.storyImportPath));
|
|
134
134
|
});
|
|
135
135
|
}
|
|
136
|
-
//#endregion
|
|
137
136
|
export { registerLinks };
|
|
138
137
|
|
|
139
138
|
//# sourceMappingURL=links.mjs.map
|