@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
|
@@ -7,7 +7,7 @@ import { FormActions, SelectField } from "../ui/forms.mjs";
|
|
|
7
7
|
import { Card, SectionTitle } from "../ui/layout.mjs";
|
|
8
8
|
import { csrfField } from "../ui/csrf-field.mjs";
|
|
9
9
|
import { jsx, jsxs } from "hono/jsx/jsx-runtime";
|
|
10
|
-
|
|
10
|
+
// src/pages/settings-ai.tsx
|
|
11
11
|
function ProfileForm(props) {
|
|
12
12
|
const { project, ai } = props;
|
|
13
13
|
return /* @__PURE__ */ jsxs("form", {
|
|
@@ -63,7 +63,6 @@ function renderSettingsAi(project, ai, formState) {
|
|
|
63
63
|
] })]
|
|
64
64
|
});
|
|
65
65
|
}
|
|
66
|
-
//#endregion
|
|
67
66
|
export { renderSettingsAi };
|
|
68
67
|
|
|
69
68
|
//# sourceMappingURL=settings-ai.mjs.map
|
|
@@ -11,7 +11,7 @@ import { MAX_STORYBOOK_URL_TTL, MIN_STORYBOOK_URL_TTL_OVERRIDE } from "@storyshe
|
|
|
11
11
|
import { MAX_BROWSERS, parseBrowsers, parseModesAllowlist } from "@storyshelf/core/capture";
|
|
12
12
|
import { Fragment, jsx, jsxs } from "hono/jsx/jsx-runtime";
|
|
13
13
|
import { isCaptureTarget } from "@storyshelf/core/adapter/capture-runner";
|
|
14
|
-
|
|
14
|
+
// src/pages/settings-capture.tsx
|
|
15
15
|
/** Primary engine select plus one checkbox per additional engine. */
|
|
16
16
|
function BrowserFields(props) {
|
|
17
17
|
const { primary, extras, disabled, targets } = props;
|
|
@@ -118,7 +118,6 @@ function renderSettingsCapture(project, isAdmin, formState, offered) {
|
|
|
118
118
|
] })]
|
|
119
119
|
});
|
|
120
120
|
}
|
|
121
|
-
//#endregion
|
|
122
121
|
export { renderSettingsCapture };
|
|
123
122
|
|
|
124
123
|
//# sourceMappingURL=settings-capture.mjs.map
|
|
@@ -8,7 +8,7 @@ import { Card, SectionTitle } from "../ui/layout.mjs";
|
|
|
8
8
|
import { csrfField } from "../ui/csrf-field.mjs";
|
|
9
9
|
import { RemoveForm } from "./settings-design-links.mjs";
|
|
10
10
|
import { Fragment, jsx, jsxs } from "hono/jsx/jsx-runtime";
|
|
11
|
-
|
|
11
|
+
// src/pages/settings-design-credential.tsx
|
|
12
12
|
const STATUS_BADGES = {
|
|
13
13
|
unknown: {
|
|
14
14
|
tone: "neutral",
|
|
@@ -93,7 +93,6 @@ function CredentialForm(props) {
|
|
|
93
93
|
]
|
|
94
94
|
});
|
|
95
95
|
}
|
|
96
|
-
//#endregion
|
|
97
96
|
export { CredentialCard };
|
|
98
97
|
|
|
99
98
|
//# sourceMappingURL=settings-design-credential.mjs.map
|
|
@@ -8,7 +8,7 @@ import { Card, SectionTitle } from "../ui/layout.mjs";
|
|
|
8
8
|
import { Table } from "../ui/table.mjs";
|
|
9
9
|
import { csrfField } from "../ui/csrf-field.mjs";
|
|
10
10
|
import { jsx, jsxs } from "hono/jsx/jsx-runtime";
|
|
11
|
-
|
|
11
|
+
// src/pages/settings-design-links.tsx
|
|
12
12
|
function FilesCard(props) {
|
|
13
13
|
const { base, files, isAdmin, formState, urlHint } = props;
|
|
14
14
|
return /* @__PURE__ */ jsxs(Card, { children: [
|
|
@@ -173,7 +173,6 @@ function RemoveForm(props) {
|
|
|
173
173
|
})]
|
|
174
174
|
});
|
|
175
175
|
}
|
|
176
|
-
//#endregion
|
|
177
176
|
export { ComponentsCard, FilesCard, RemoveForm };
|
|
178
177
|
|
|
179
178
|
//# sourceMappingURL=settings-design-links.mjs.map
|
|
@@ -5,7 +5,7 @@ import { Alert, EmptyState } from "../ui/feedback.mjs";
|
|
|
5
5
|
import { ComponentsCard, FilesCard } from "./settings-design-links.mjs";
|
|
6
6
|
import { CredentialCard } from "./settings-design-credential.mjs";
|
|
7
7
|
import { jsx, jsxs } from "hono/jsx/jsx-runtime";
|
|
8
|
-
|
|
8
|
+
// src/pages/settings-design.tsx
|
|
9
9
|
/** Design settings tab: project file links, component mapping and provider credentials (admin-only parts gated). */
|
|
10
10
|
function renderSettingsDesign(project, design, isAdmin, formState) {
|
|
11
11
|
const base = `/projects/${project.slug}/settings/design`;
|
|
@@ -42,7 +42,6 @@ function renderSettingsDesign(project, design, isAdmin, formState) {
|
|
|
42
42
|
]
|
|
43
43
|
});
|
|
44
44
|
}
|
|
45
|
-
//#endregion
|
|
46
45
|
export { renderSettingsDesign };
|
|
47
46
|
|
|
48
47
|
//# sourceMappingURL=settings-design.mjs.map
|
|
@@ -9,7 +9,7 @@ import { csrfField } from "../ui/csrf-field.mjs";
|
|
|
9
9
|
import { targetChoices } from "./target-choices.mjs";
|
|
10
10
|
import { jsx, jsxs } from "hono/jsx/jsx-runtime";
|
|
11
11
|
import { isCaptureTarget } from "@storyshelf/core/adapter/capture-runner";
|
|
12
|
-
|
|
12
|
+
// src/pages/settings-general.tsx
|
|
13
13
|
/** General settings tab: project metadata, diff thresholds, and danger zone. */
|
|
14
14
|
function renderSettingsGeneral(project, formState, isAdmin, offered) {
|
|
15
15
|
const errors = formState?.errors ?? {};
|
|
@@ -157,7 +157,6 @@ function renderSettingsGeneral(project, formState, isAdmin, offered) {
|
|
|
157
157
|
]
|
|
158
158
|
});
|
|
159
159
|
}
|
|
160
|
-
//#endregion
|
|
161
160
|
export { renderSettingsGeneral };
|
|
162
161
|
|
|
163
162
|
//# sourceMappingURL=settings-general.mjs.map
|
|
@@ -8,7 +8,7 @@ import { Card, SectionTitle } from "../ui/layout.mjs";
|
|
|
8
8
|
import { Table } from "../ui/table.mjs";
|
|
9
9
|
import { csrfField } from "../ui/csrf-field.mjs";
|
|
10
10
|
import { jsx, jsxs } from "hono/jsx/jsx-runtime";
|
|
11
|
-
|
|
11
|
+
// src/pages/settings-labels.tsx
|
|
12
12
|
/** Labels settings tab: label-type table plus the create-type form. */
|
|
13
13
|
function renderSettingsLabels(project, labelTypes, isAdmin, formState) {
|
|
14
14
|
const editing = isAdmin ? labelTypes.find((item) => item.key === formState?.editKey) : void 0;
|
|
@@ -142,7 +142,6 @@ function renderSettingsLabels(project, labelTypes, isAdmin, formState) {
|
|
|
142
142
|
]
|
|
143
143
|
});
|
|
144
144
|
}
|
|
145
|
-
//#endregion
|
|
146
145
|
export { renderSettingsLabels };
|
|
147
146
|
|
|
148
147
|
//# sourceMappingURL=settings-labels.mjs.map
|
|
@@ -8,7 +8,7 @@ import { Card, SectionTitle } from "../ui/layout.mjs";
|
|
|
8
8
|
import { Table } from "../ui/table.mjs";
|
|
9
9
|
import { csrfField } from "../ui/csrf-field.mjs";
|
|
10
10
|
import { jsx, jsxs } from "hono/jsx/jsx-runtime";
|
|
11
|
-
|
|
11
|
+
// src/pages/settings-links.tsx
|
|
12
12
|
const linkRow = (base, link, isAdmin) => /* @__PURE__ */ jsxs("tr", { children: [
|
|
13
13
|
/* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx(Badge, {
|
|
14
14
|
tone: "neutral",
|
|
@@ -106,7 +106,6 @@ function AddLinkCard(props) {
|
|
|
106
106
|
]
|
|
107
107
|
})] });
|
|
108
108
|
}
|
|
109
|
-
//#endregion
|
|
110
109
|
export { renderSettingsLinks };
|
|
111
110
|
|
|
112
111
|
//# sourceMappingURL=settings-links.mjs.map
|
|
@@ -8,7 +8,7 @@ import { Card, SectionTitle } from "../ui/layout.mjs";
|
|
|
8
8
|
import { Table } from "../ui/table.mjs";
|
|
9
9
|
import { csrfField } from "../ui/csrf-field.mjs";
|
|
10
10
|
import { jsx, jsxs } from "hono/jsx/jsx-runtime";
|
|
11
|
-
|
|
11
|
+
// src/pages/settings-members.tsx
|
|
12
12
|
/** Members settings tab: member roster plus the add-member form. */
|
|
13
13
|
function renderSettingsMembers(project, members, groupMappings, isAdmin, formState) {
|
|
14
14
|
return /* @__PURE__ */ jsxs("div", {
|
|
@@ -186,7 +186,6 @@ function renderSettingsMembers(project, members, groupMappings, isAdmin, formSta
|
|
|
186
186
|
]
|
|
187
187
|
});
|
|
188
188
|
}
|
|
189
|
-
//#endregion
|
|
190
189
|
export { renderSettingsMembers };
|
|
191
190
|
|
|
192
191
|
//# sourceMappingURL=settings-members.mjs.map
|
|
@@ -8,7 +8,7 @@ import { Card, SectionTitle } from "../ui/layout.mjs";
|
|
|
8
8
|
import { csrfField } from "../ui/csrf-field.mjs";
|
|
9
9
|
import { isServerlessHost } from "@storyshelf/core/config";
|
|
10
10
|
import { Fragment, jsx, jsxs } from "hono/jsx/jsx-runtime";
|
|
11
|
-
|
|
11
|
+
// src/pages/settings-notifications-git.tsx
|
|
12
12
|
/** GitHub only: show builds as check runs posted by a GitHub App, not as commit statuses. */
|
|
13
13
|
const GitHubAppFields = ({ errors }) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
14
14
|
/* @__PURE__ */ jsx(SelectField, {
|
|
@@ -122,7 +122,6 @@ const CreateGitChannelCard = ({ project, providers, formState }) => /* @__PURE__
|
|
|
122
122
|
]
|
|
123
123
|
})
|
|
124
124
|
] });
|
|
125
|
-
//#endregion
|
|
126
125
|
export { CreateGitChannelCard };
|
|
127
126
|
|
|
128
127
|
//# sourceMappingURL=settings-notifications-git.mjs.map
|
|
@@ -12,7 +12,7 @@ import { csrfField } from "../ui/csrf-field.mjs";
|
|
|
12
12
|
import { NotificationsPausedNotice } from "./notifications-paused.mjs";
|
|
13
13
|
import { CreateGitChannelCard } from "./settings-notifications-git.mjs";
|
|
14
14
|
import { jsx, jsxs } from "hono/jsx/jsx-runtime";
|
|
15
|
-
|
|
15
|
+
// src/pages/settings-notifications.tsx
|
|
16
16
|
/** Notification topic catalog (mirrors the webhook event names). */
|
|
17
17
|
const NOTIFICATION_TOPICS = [
|
|
18
18
|
{
|
|
@@ -271,7 +271,6 @@ function renderSettingsNotifications(project, channels, providers, mySubscriptio
|
|
|
271
271
|
]
|
|
272
272
|
});
|
|
273
273
|
}
|
|
274
|
-
//#endregion
|
|
275
274
|
export { NOTIFICATION_TOPICS, renderSettingsNotifications };
|
|
276
275
|
|
|
277
276
|
//# sourceMappingURL=settings-notifications.mjs.map
|
|
@@ -7,7 +7,7 @@ import { CheckField, Field, FormActions } from "../ui/forms.mjs";
|
|
|
7
7
|
import { Card, SectionTitle } from "../ui/layout.mjs";
|
|
8
8
|
import { csrfField } from "../ui/csrf-field.mjs";
|
|
9
9
|
import { jsx, jsxs } from "hono/jsx/jsx-runtime";
|
|
10
|
-
|
|
10
|
+
// src/pages/settings-tests.tsx
|
|
11
11
|
/** Tests settings tab: play execution, Storybook 10 test runs, accessibility checks, play timeout. */
|
|
12
12
|
function renderSettingsTests(project, isAdmin, formState) {
|
|
13
13
|
return /* @__PURE__ */ jsxs("div", {
|
|
@@ -136,7 +136,6 @@ export const Flaky2: Story = {
|
|
|
136
136
|
]
|
|
137
137
|
});
|
|
138
138
|
}
|
|
139
|
-
//#endregion
|
|
140
139
|
export { renderSettingsTests };
|
|
141
140
|
|
|
142
141
|
//# sourceMappingURL=settings-tests.mjs.map
|
|
@@ -8,7 +8,7 @@ import { Card, SectionTitle } from "../ui/layout.mjs";
|
|
|
8
8
|
import { Table } from "../ui/table.mjs";
|
|
9
9
|
import { csrfField } from "../ui/csrf-field.mjs";
|
|
10
10
|
import { jsx, jsxs } from "hono/jsx/jsx-runtime";
|
|
11
|
-
|
|
11
|
+
// src/pages/settings-tokens.tsx
|
|
12
12
|
/** Tokens settings tab: project CLI tokens plus the create-token form. */
|
|
13
13
|
function renderSettingsTokens(project, tokens, isAdmin, formState) {
|
|
14
14
|
return /* @__PURE__ */ jsxs("div", {
|
|
@@ -84,7 +84,6 @@ function renderSettingsTokens(project, tokens, isAdmin, formState) {
|
|
|
84
84
|
]
|
|
85
85
|
});
|
|
86
86
|
}
|
|
87
|
-
//#endregion
|
|
88
87
|
export { renderSettingsTokens };
|
|
89
88
|
|
|
90
89
|
//# sourceMappingURL=settings-tokens.mjs.map
|
|
@@ -9,7 +9,7 @@ import { HStack } from "../ui/stacks.mjs";
|
|
|
9
9
|
import { Table } from "../ui/table.mjs";
|
|
10
10
|
import { csrfField } from "../ui/csrf-field.mjs";
|
|
11
11
|
import { jsx, jsxs } from "hono/jsx/jsx-runtime";
|
|
12
|
-
|
|
12
|
+
// src/pages/settings-webhooks.tsx
|
|
13
13
|
/** Webhooks settings tab: webhook list plus the create-webhook form. */
|
|
14
14
|
function renderSettingsWebhooks(project, webhooks, isAdmin, formState) {
|
|
15
15
|
return /* @__PURE__ */ jsxs("div", {
|
|
@@ -98,7 +98,6 @@ function renderSettingsWebhooks(project, webhooks, isAdmin, formState) {
|
|
|
98
98
|
]
|
|
99
99
|
});
|
|
100
100
|
}
|
|
101
|
-
//#endregion
|
|
102
101
|
export { renderSettingsWebhooks };
|
|
103
102
|
|
|
104
103
|
//# sourceMappingURL=settings-webhooks.mjs.map
|
package/dist/pages/storybook.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import "node:path";
|
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
4
|
import { getStore } from "../store.mjs";
|
|
5
|
-
|
|
5
|
+
// src/pages/storybook.tsx
|
|
6
6
|
/**
|
|
7
7
|
* Placeholder shown while a build's statics are not yet available.
|
|
8
8
|
* Refreshes itself; once extraction lands, the route serves the live page.
|
|
@@ -110,7 +110,6 @@ function escapeHtml(value) {
|
|
|
110
110
|
function escapeAttr(value) {
|
|
111
111
|
return escapeHtml(value);
|
|
112
112
|
}
|
|
113
|
-
//#endregion
|
|
114
113
|
export { renderStorybookPage, renderStorybookPreparingPage };
|
|
115
114
|
|
|
116
115
|
//# sourceMappingURL=storybook.mjs.map
|
|
@@ -3,7 +3,7 @@ import "node:url";
|
|
|
3
3
|
import.meta.url;
|
|
4
4
|
import { targetLabel } from "../targets.mjs";
|
|
5
5
|
import { BROWSER_NAMES, unavailableReason } from "@storyshelf/core/adapter/capture-runner";
|
|
6
|
-
|
|
6
|
+
// src/pages/target-choices.ts
|
|
7
7
|
/** The capture targets a project can pick, from what the runner offers (ADR 0007, decision 7). */
|
|
8
8
|
/**
|
|
9
9
|
* Selectable targets: every ready one; a project's current target that is no longer ready stays,
|
|
@@ -33,7 +33,6 @@ function targetChoices(offered, current) {
|
|
|
33
33
|
})
|
|
34
34
|
};
|
|
35
35
|
}
|
|
36
|
-
//#endregion
|
|
37
36
|
export { targetChoices };
|
|
38
37
|
|
|
39
38
|
//# sourceMappingURL=target-choices.mjs.map
|
package/dist/publish-setup.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import "node:url";
|
|
|
3
3
|
import.meta.url;
|
|
4
4
|
import { DEFAULT_SCRATCH_DIR } from "@storyshelf/core/config";
|
|
5
5
|
import { createCatalogHook, createCatalogLoader, createPublishCompleter, createPublishHandler, extractionLimitsOf, resolvePublishLimits } from "@storyshelf/core/capture";
|
|
6
|
-
|
|
6
|
+
// src/publish-setup.ts
|
|
7
7
|
/**
|
|
8
8
|
* Publishing wiring (ADR 0006, decision 10): the completer every upload route ends in, and the
|
|
9
9
|
* `publish` job kind on the shared database queue. Its handler runs on every instance with a
|
|
@@ -83,7 +83,6 @@ function publishHooks(catalog, captureWillRun, delivery) {
|
|
|
83
83
|
}
|
|
84
84
|
];
|
|
85
85
|
}
|
|
86
|
-
//#endregion
|
|
87
86
|
export { setupPublishing };
|
|
88
87
|
|
|
89
88
|
//# sourceMappingURL=publish-setup.mjs.map
|
package/dist/retention-timer.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import "node:url";
|
|
|
3
3
|
import.meta.url;
|
|
4
4
|
import { ProjectModel } from "@storyshelf/core/models";
|
|
5
5
|
import { Retention, purgeAiData } from "@storyshelf/core/retention";
|
|
6
|
-
|
|
6
|
+
// src/retention-timer.ts
|
|
7
7
|
const DEFAULT_BRANCH_TTL_DAYS = 30;
|
|
8
8
|
const DEFAULT_BRANCH_GC_INTERVAL_MS = 864e5;
|
|
9
9
|
/**
|
|
@@ -17,16 +17,20 @@ const DEFAULT_BRANCH_GC_INTERVAL_MS = 864e5;
|
|
|
17
17
|
* @param logger - Structured logger for sweep diagnostics
|
|
18
18
|
* @returns Timer handle with `stop()`, or `null` if GC is disabled
|
|
19
19
|
*/
|
|
20
|
-
function startBranchGcTimer(db, storage, config, logger) {
|
|
20
|
+
function startBranchGcTimer(db, storage, config, logger, testing = false) {
|
|
21
21
|
const ttlDays = config.branchTtlDays ?? DEFAULT_BRANCH_TTL_DAYS;
|
|
22
22
|
if (ttlDays === null) return null;
|
|
23
23
|
const intervalMs = config.branchGcIntervalMs ?? DEFAULT_BRANCH_GC_INTERVAL_MS;
|
|
24
24
|
if (intervalMs <= 0) return null;
|
|
25
|
-
return scheduleBranchGc(createBranchGcRunner(db, storage, logger, ttlDays), intervalMs);
|
|
25
|
+
return scheduleBranchGc(createBranchGcRunner(db, storage, logger, ttlDays, testing), intervalMs);
|
|
26
26
|
}
|
|
27
|
-
function createBranchGcRunner(db, storage, logger, ttlDays) {
|
|
27
|
+
function createBranchGcRunner(db, storage, logger, ttlDays, testing) {
|
|
28
28
|
return async () => {
|
|
29
29
|
try {
|
|
30
|
+
if (testing) {
|
|
31
|
+
await purgeAiData(db, /* @__PURE__ */ new Date(), logger);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
30
34
|
const projects = await new ProjectModel(db).list();
|
|
31
35
|
const retention = new Retention(db, storage, {
|
|
32
36
|
builds: db.tables.builds,
|
|
@@ -47,20 +51,15 @@ function createBranchGcRunner(db, storage, logger, ttlDays) {
|
|
|
47
51
|
};
|
|
48
52
|
}
|
|
49
53
|
function scheduleBranchGc(run, intervalMs) {
|
|
50
|
-
const timeout = setTimeout(() =>
|
|
51
|
-
run();
|
|
52
|
-
}, Math.min(36e5, intervalMs));
|
|
54
|
+
const timeout = setTimeout(() => run(), Math.min(36e5, intervalMs));
|
|
53
55
|
timeout.unref?.();
|
|
54
|
-
const interval = setInterval(() =>
|
|
55
|
-
run();
|
|
56
|
-
}, intervalMs);
|
|
56
|
+
const interval = setInterval(() => run(), intervalMs);
|
|
57
57
|
interval.unref?.();
|
|
58
58
|
return { stop: () => {
|
|
59
59
|
clearTimeout(timeout);
|
|
60
60
|
clearInterval(interval);
|
|
61
61
|
} };
|
|
62
62
|
}
|
|
63
|
-
//#endregion
|
|
64
63
|
export { startBranchGcTimer };
|
|
65
64
|
|
|
66
65
|
//# sourceMappingURL=retention-timer.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"retention-timer.mjs","names":[],"sources":["../src/retention-timer.ts"],"sourcesContent":["import type { DatabaseAdapter } from \"@storyshelf/core/adapter/database\";\nimport type { StorageAdapter } from \"@storyshelf/core/adapter/storage\";\nimport type { ShelfConfig } from \"@storyshelf/core/config\";\nimport type { Logger } from \"@storyshelf/core/logger\";\n/* oxlint-disable typescript/promise-function-async -- interval helpers return promise factories */\nimport { ProjectModel } from \"@storyshelf/core/models\";\nimport { Retention, purgeAiData } from \"@storyshelf/core/retention\";\n\nconst DEFAULT_BRANCH_TTL_DAYS = 30;\nconst DEFAULT_BRANCH_GC_INTERVAL_MS = 86_400_000;\n\n/** Handle for the background branch-GC interval; call `stop()` on server shutdown. */\nexport interface BranchGcTimer {\n stop(): void;\n}\n\n/**\n * Start a daily sweep that removes baselines for branches that have not\n * been updated within `branchTtlDays`. Returns a timer handle or `null`\n * when GC is disabled (`branchTtlDays: null` or non-positive interval).\n *\n * @param db - Database adapter to list projects\n * @param storage - Storage adapter for baseline cleanup\n * @param config - Shelf config supplying `branchTtlDays` and `branchGcIntervalMs`\n * @param logger - Structured logger for sweep diagnostics\n * @returns Timer handle with `stop()`, or `null` if GC is disabled\n */\nexport function startBranchGcTimer(\n db: DatabaseAdapter,\n storage: StorageAdapter,\n config: ShelfConfig,\n logger: Logger,\n): BranchGcTimer | null {\n const ttlDays = config.branchTtlDays ?? DEFAULT_BRANCH_TTL_DAYS;\n if (ttlDays === null) {\n return null;\n }\n const intervalMs = config.branchGcIntervalMs ?? DEFAULT_BRANCH_GC_INTERVAL_MS;\n if (intervalMs <= 0) {\n return null;\n }\n const run = createBranchGcRunner(db, storage, logger, ttlDays);\n return scheduleBranchGc(run, intervalMs);\n}\n\n// oxlint-disable-next-line typescript/promise-function-async\nfunction createBranchGcRunner(\n db: DatabaseAdapter,\n storage: StorageAdapter,\n logger: Logger,\n ttlDays: number,\n): () => Promise<void> {\n return async (): Promise<void> => {\n try {\n const projects = await new ProjectModel(db).list();\n const retention = new Retention(\n db,\n storage,\n {\n builds: db.tables.builds,\n buildLabels: db.tables.buildLabels,\n baselines: db.tables.baselines,\n },\n logger,\n );\n const results = await Promise.all(\n projects.map((project) => retention.purgeStaleBranches(project, ttlDays)),\n );\n const totalBranches = results.reduce(\n (sum: number, r: { removedBranches: number }) => sum + r.removedBranches,\n 0,\n );\n const totalBaselines = results.reduce(\n (sum: number, r: { removedBaselines: number }) => sum + r.removedBaselines,\n 0,\n );\n await purgeAiData(db, new Date(), logger);\n if (totalBranches > 0 || totalBaselines > 0) {\n logger.info({ totalBranches, totalBaselines }, \"branch GC daily sweep complete\");\n }\n } catch (error) {\n logger.error({ err: error }, \"branch GC sweep failed\");\n }\n };\n}\n\nfunction scheduleBranchGc(run: () => Promise<void>, intervalMs: number): BranchGcTimer {\n const firstDelay = Math.min(3_600_000, intervalMs);\n
|
|
1
|
+
{"version":3,"file":"retention-timer.mjs","names":[],"sources":["../src/retention-timer.ts"],"sourcesContent":["import type { DatabaseAdapter } from \"@storyshelf/core/adapter/database\";\nimport type { StorageAdapter } from \"@storyshelf/core/adapter/storage\";\nimport type { ShelfConfig } from \"@storyshelf/core/config\";\nimport type { Logger } from \"@storyshelf/core/logger\";\n/* oxlint-disable typescript/promise-function-async -- interval helpers return promise factories */\nimport { ProjectModel } from \"@storyshelf/core/models\";\nimport { Retention, purgeAiData } from \"@storyshelf/core/retention\";\n\nconst DEFAULT_BRANCH_TTL_DAYS = 30;\nconst DEFAULT_BRANCH_GC_INTERVAL_MS = 86_400_000;\n\n/** Handle for the background branch-GC interval; call `stop()` on server shutdown. */\nexport interface BranchGcTimer {\n stop(): void;\n}\n\n/**\n * Start a daily sweep that removes baselines for branches that have not\n * been updated within `branchTtlDays`. Returns a timer handle or `null`\n * when GC is disabled (`branchTtlDays: null` or non-positive interval).\n *\n * @param db - Database adapter to list projects\n * @param storage - Storage adapter for baseline cleanup\n * @param config - Shelf config supplying `branchTtlDays` and `branchGcIntervalMs`\n * @param logger - Structured logger for sweep diagnostics\n * @returns Timer handle with `stop()`, or `null` if GC is disabled\n */\nexport function startBranchGcTimer(\n db: DatabaseAdapter,\n storage: StorageAdapter,\n config: ShelfConfig,\n logger: Logger,\n testing = false,\n): BranchGcTimer | null {\n const ttlDays = config.branchTtlDays ?? DEFAULT_BRANCH_TTL_DAYS;\n if (ttlDays === null) {\n return null;\n }\n const intervalMs = config.branchGcIntervalMs ?? DEFAULT_BRANCH_GC_INTERVAL_MS;\n if (intervalMs <= 0) {\n return null;\n }\n const run = createBranchGcRunner(db, storage, logger, ttlDays, testing);\n return scheduleBranchGc(run, intervalMs);\n}\n\n// oxlint-disable-next-line typescript/promise-function-async\nfunction createBranchGcRunner(\n db: DatabaseAdapter,\n storage: StorageAdapter,\n logger: Logger,\n ttlDays: number,\n testing: boolean,\n): () => Promise<void> {\n // oxlint-disable-next-line eslint/max-statements -- the sweep and its error boundary are kept together\n return async (): Promise<void> => {\n try {\n // Baseline removal must be coordinated with references before testing deployments can GC.\n // Keep independent AI-data cleanup running even while branch GC is disabled.\n if (testing) {\n await purgeAiData(db, new Date(), logger);\n return;\n }\n const projects = await new ProjectModel(db).list();\n const retention = new Retention(\n db,\n storage,\n {\n builds: db.tables.builds,\n buildLabels: db.tables.buildLabels,\n baselines: db.tables.baselines,\n },\n logger,\n );\n const results = await Promise.all(\n projects.map((project) => retention.purgeStaleBranches(project, ttlDays)),\n );\n const totalBranches = results.reduce(\n (sum: number, r: { removedBranches: number }) => sum + r.removedBranches,\n 0,\n );\n const totalBaselines = results.reduce(\n (sum: number, r: { removedBaselines: number }) => sum + r.removedBaselines,\n 0,\n );\n await purgeAiData(db, new Date(), logger);\n if (totalBranches > 0 || totalBaselines > 0) {\n logger.info({ totalBranches, totalBaselines }, \"branch GC daily sweep complete\");\n }\n } catch (error) {\n logger.error({ err: error }, \"branch GC sweep failed\");\n }\n };\n}\n\nfunction scheduleBranchGc(run: () => Promise<void>, intervalMs: number): BranchGcTimer {\n const firstDelay = Math.min(3_600_000, intervalMs);\n // oxlint-disable-next-line typescript/no-misused-promises, typescript/strict-void-return -- run catches failures; returning it allows deterministic completion in tests\n const timeout = setTimeout(() => run(), firstDelay);\n timeout.unref?.();\n\n // oxlint-disable-next-line typescript/no-misused-promises, typescript/strict-void-return -- run catches failures\n const interval = setInterval(() => run(), intervalMs);\n interval.unref?.();\n\n return {\n stop: () => {\n clearTimeout(timeout);\n clearInterval(interval);\n },\n };\n}\n"],"mappings":";;;;;;AAQA,MAAM,0BAA0B;AAChC,MAAM,gCAAgC;;;;;;;;;;;;AAkBtC,SAAgB,mBACd,IACA,SACA,QACA,QACA,UAAU,OACY;CACtB,MAAM,UAAU,OAAO,iBAAiB;CACxC,IAAI,YAAY,MACd,OAAO;CAET,MAAM,aAAa,OAAO,sBAAsB;CAChD,IAAI,cAAc,GAChB,OAAO;CAGT,OAAO,iBADK,qBAAqB,IAAI,SAAS,QAAQ,SAAS,OACrC,GAAG,UAAU;AACzC;AAGA,SAAS,qBACP,IACA,SACA,QACA,SACA,SACqB;CAErB,OAAO,YAA2B;EAChC,IAAI;GAGF,IAAI,SAAS;IACX,MAAM,YAAY,oBAAI,IAAI,KAAK,GAAG,MAAM;IACxC;GACF;GACA,MAAM,WAAW,MAAM,IAAI,aAAa,EAAE,CAAC,CAAC,KAAK;GACjD,MAAM,YAAY,IAAI,UACpB,IACA,SACA;IACE,QAAQ,GAAG,OAAO;IAClB,aAAa,GAAG,OAAO;IACvB,WAAW,GAAG,OAAO;GACvB,GACA,MACF;GACA,MAAM,UAAU,MAAM,QAAQ,IAC5B,SAAS,KAAK,YAAY,UAAU,mBAAmB,SAAS,OAAO,CAAC,CAC1E;GACA,MAAM,gBAAgB,QAAQ,QAC3B,KAAa,MAAmC,MAAM,EAAE,iBACzD,CACF;GACA,MAAM,iBAAiB,QAAQ,QAC5B,KAAa,MAAoC,MAAM,EAAE,kBAC1D,CACF;GACA,MAAM,YAAY,oBAAI,IAAI,KAAK,GAAG,MAAM;GACxC,IAAI,gBAAgB,KAAK,iBAAiB,GACxC,OAAO,KAAK;IAAE;IAAe;GAAe,GAAG,gCAAgC;EAEnF,SAAS,OAAO;GACd,OAAO,MAAM,EAAE,KAAK,MAAM,GAAG,wBAAwB;EACvD;CACF;AACF;AAEA,SAAS,iBAAiB,KAA0B,YAAmC;CAGrF,MAAM,UAAU,iBAAiB,IAAI,GAFlB,KAAK,IAAI,MAAW,UAEU,CAAC;CAClD,QAAQ,QAAQ;CAGhB,MAAM,WAAW,kBAAkB,IAAI,GAAG,UAAU;CACpD,SAAS,QAAQ;CAEjB,OAAO,EACL,YAAY;EACV,aAAa,OAAO;EACpB,cAAc,QAAQ;CACxB,EACF;AACF"}
|
package/dist/review-events.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { credentialKeys } from "./credential-keys.mjs";
|
|
|
5
5
|
import { getStore } from "./store.mjs";
|
|
6
6
|
import { notifyProjectWith } from "./notify.mjs";
|
|
7
7
|
import { emitWebhookEvent } from "@storyshelf/core/adapter/webhook-events";
|
|
8
|
-
|
|
8
|
+
// src/review-events.ts
|
|
9
9
|
/**
|
|
10
10
|
* Review events (ADR 0013, decisions 4 and 10; ADR 0014, decision 10). They ride the existing
|
|
11
11
|
* paths: signed webhooks to the project's subscribers and the notification fan-out (stored
|
|
@@ -42,7 +42,6 @@ function reviewRequestedData(build) {
|
|
|
42
42
|
message: build.message
|
|
43
43
|
};
|
|
44
44
|
}
|
|
45
|
-
//#endregion
|
|
46
45
|
export { REVIEW_EVENTS, announceReview, announceReviewWith, reviewRequestedData };
|
|
47
46
|
|
|
48
47
|
//# sourceMappingURL=review-events.mjs.map
|
|
@@ -15,7 +15,7 @@ import { hxRedirect } from "./htmx.mjs";
|
|
|
15
15
|
import { flash } from "./flash.mjs";
|
|
16
16
|
import { collectHealthReport } from "./health-report.mjs";
|
|
17
17
|
import { describeCaptureHost } from "@storyshelf/core/utils";
|
|
18
|
-
|
|
18
|
+
// src/routers/admin-pages.ts
|
|
19
19
|
/** Register the site-admin System page (adapter inventory + in-depth health). */
|
|
20
20
|
function registerAdminPages(app, deps, auth) {
|
|
21
21
|
app.get("/admin", async (c) => {
|
|
@@ -84,7 +84,6 @@ async function publicOriginView() {
|
|
|
84
84
|
learns: authEnabled
|
|
85
85
|
};
|
|
86
86
|
}
|
|
87
|
-
//#endregion
|
|
88
87
|
export { registerAdminPages };
|
|
89
88
|
|
|
90
89
|
//# sourceMappingURL=admin-pages.mjs.map
|
|
@@ -8,7 +8,7 @@ import { flash } from "./flash.mjs";
|
|
|
8
8
|
import { requestOpenBuildStatesNow } from "../git-status.mjs";
|
|
9
9
|
import { normalizeOrigin } from "@storyshelf/core/utils";
|
|
10
10
|
import { HTTPException } from "hono/http-exception";
|
|
11
|
-
|
|
11
|
+
// src/routers/admin-public-origin.ts
|
|
12
12
|
/**
|
|
13
13
|
* The public origin, learnt from an admin's browser (ADR 0007, decision 6): the System page reports
|
|
14
14
|
* `location.origin`, an admin confirms or replaces it, and background jobs read it from the database.
|
|
@@ -20,6 +20,13 @@ function registerPublicOriginRoutes(app) {
|
|
|
20
20
|
app.post("/admin/public-origin", handleSet);
|
|
21
21
|
app.post("/admin/public-origin/clear", handleClear);
|
|
22
22
|
}
|
|
23
|
+
/** What an admin is told when the origin is not theirs to set. */
|
|
24
|
+
const FIXED_ORIGIN_MESSAGE = "The public origin comes from PUBLIC_BASE_URL or the hosting platform, so there is nothing to confirm";
|
|
25
|
+
/** Whether the configuration or the platform answers: an origin an admin cannot confirm or replace. */
|
|
26
|
+
async function isFixed() {
|
|
27
|
+
const source = (await getStore().publicOrigin?.resolve())?.source;
|
|
28
|
+
return source === "config" || source === "platform";
|
|
29
|
+
}
|
|
23
30
|
/** The origin a report names, once it matches what the browser says it used; throws 400 otherwise. */
|
|
24
31
|
async function reportedOrigin(c) {
|
|
25
32
|
const body = await c.req.json().catch(() => null);
|
|
@@ -31,7 +38,7 @@ async function reportedOrigin(c) {
|
|
|
31
38
|
async function handleReport(c) {
|
|
32
39
|
requireSiteAdmin(c);
|
|
33
40
|
const { authEnabled, publicOrigin, user } = getStore();
|
|
34
|
-
if (!authEnabled || publicOrigin === void 0) return c.body(null, 204);
|
|
41
|
+
if (!authEnabled || publicOrigin === void 0 || await isFixed()) return c.body(null, 204);
|
|
35
42
|
const reported = await reportedOrigin(c);
|
|
36
43
|
const before = await publicOrigin.stored();
|
|
37
44
|
const wasResolved = await publicOrigin.url() !== void 0;
|
|
@@ -48,6 +55,7 @@ async function resumeIfResolved(wasResolved) {
|
|
|
48
55
|
}
|
|
49
56
|
/** Store what an admin typed; the message says why not when it is no origin. */
|
|
50
57
|
async function storeTyped(c) {
|
|
58
|
+
if (await isFixed()) return FIXED_ORIGIN_MESSAGE;
|
|
51
59
|
const { publicOrigin, user } = getStore();
|
|
52
60
|
const origin = (await c.req.formData()).get("origin");
|
|
53
61
|
try {
|
|
@@ -79,7 +87,6 @@ async function handleClear(c) {
|
|
|
79
87
|
flash(c, "Stored public origin removed");
|
|
80
88
|
return hxRedirect(c, "/admin");
|
|
81
89
|
}
|
|
82
|
-
//#endregion
|
|
83
90
|
export { registerPublicOriginRoutes };
|
|
84
91
|
|
|
85
92
|
//# sourceMappingURL=admin-public-origin.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin-public-origin.mjs","names":[],"sources":["../../src/routers/admin-public-origin.ts"],"sourcesContent":["/**\n * The public origin, learnt from an admin's browser (ADR 0007, decision 6): the System page reports\n * `location.origin`, an admin confirms or replaces it, and background jobs read it from the database.\n * Site-admin and CSRF protected; with authentication off there is no admin, so nothing is learnt.\n */\nimport { normalizeOrigin } from \"@storyshelf/core/utils\";\nimport type { Context } from \"hono\";\nimport { HTTPException } from \"hono/http-exception\";\nimport type { ShelfRouter } from \"../app-types.ts\";\nimport { requestOpenBuildStatesNow } from \"../git-status.ts\";\nimport { getStore } from \"../store.ts\";\nimport { flash } from \"./flash.ts\";\nimport { requireSiteAdmin } from \"./helpers.ts\";\nimport { hxRedirect } from \"./htmx.ts\";\n\n/** Register the public-origin routes of the System page. */\nexport function registerPublicOriginRoutes(app: ShelfRouter): void {\n app.post(\"/admin/public-origin/report\", handleReport);\n app.post(\"/admin/public-origin\", handleSet);\n app.post(\"/admin/public-origin/clear\", handleClear);\n}\n\n/** The origin a report names, once it matches what the browser says it used; throws 400 otherwise. */\nasync function reportedOrigin(c: Context): Promise<string> {\n const body = (await c.req.json().catch(() => null)) as { origin?: unknown } | null;\n const reported = typeof body?.origin === \"string\" ? normalizeOrigin(body.origin) : undefined;\n // The browser states the origin it used itself; a report that disagrees is not one.\n if (reported === undefined || c.req.header(\"origin\") !== reported) {\n throw new HTTPException(400, { message: \"Origin does not match the request\" });\n }\n return reported;\n}\n\n/** The page reports the origin it was loaded from; the first report is kept. */\nasync function handleReport(c: Context): Promise<Response> {\n requireSiteAdmin(c);\n const { authEnabled, publicOrigin, user } = getStore();\n if (!authEnabled || publicOrigin === undefined) {\n return c.body(null, 204);\n }\n const reported = await reportedOrigin(c);\n const before = await publicOrigin.stored();\n const wasResolved = (await publicOrigin.url()) !== undefined;\n const stored = await publicOrigin.report(reported, user?.id);\n await resumeIfResolved(wasResolved);\n return c.json(stored, before === null ? 201 : 200);\n}\n\n/**\n * Notifications were paused and an origin now resolves (an admin confirmed one; a mere report\n * resolves nothing): re-request the state of open builds.\n */\nasync function resumeIfResolved(wasResolved: boolean): Promise<void> {\n if (!wasResolved && (await getStore().publicOrigin?.url()) !== undefined) {\n requestOpenBuildStatesNow().catch(() => 0);\n }\n}\n\n/** Store what an admin typed; the message says why not when it is no origin. */\nasync function storeTyped(c: Context): Promise<string> {\n const { publicOrigin, user } = getStore();\n const origin = (await c.req.formData()).get(\"origin\");\n try {\n const wasResolved = (await publicOrigin?.url()) !== undefined;\n await publicOrigin?.set(typeof origin === \"string\" ? origin : \"\", user?.id);\n await resumeIfResolved(wasResolved);\n return \"Public origin saved\";\n } catch (error) {\n return error instanceof Error ? error.message : \"Invalid origin\";\n }\n}\n\n/** An admin confirmed the detected origin or entered another. */\nasync function handleSet(c: Context): Promise<Response> {\n requireSiteAdmin(c);\n const { authEnabled, publicOrigin } = getStore();\n if (!authEnabled || publicOrigin === undefined) {\n flash(\n c,\n \"The public origin is learnt from an admin's browser, which needs authentication\",\n \"warning\",\n );\n return hxRedirect(c, \"/admin\");\n }\n const message = await storeTyped(c);\n flash(c, message, message === \"Public origin saved\" ? \"success\" : \"warning\");\n return hxRedirect(c, \"/admin\");\n}\n\n/** An admin dropped the stored origin; the next admin visit reports a fresh one. */\nasync function handleClear(c: Context): Promise<Response> {\n requireSiteAdmin(c);\n const { publicOrigin } = getStore();\n await publicOrigin?.clear();\n flash(c, \"Stored public origin removed\");\n return hxRedirect(c, \"/admin\");\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAgBA,SAAgB,2BAA2B,KAAwB;CACjE,IAAI,KAAK,+BAA+B,YAAY;CACpD,IAAI,KAAK,wBAAwB,SAAS;CAC1C,IAAI,KAAK,8BAA8B,WAAW;AACpD;;AAGA,eAAe,eAAe,GAA6B;CACzD,MAAM,OAAQ,MAAM,EAAE,IAAI,KAAK,CAAC,CAAC,YAAY,IAAI;CACjD,MAAM,WAAW,OAAO,MAAM,WAAW,WAAW,gBAAgB,KAAK,MAAM,IAAI,KAAA;CAEnF,IAAI,aAAa,KAAA,KAAa,EAAE,IAAI,OAAO,QAAQ,MAAM,UACvD,MAAM,IAAI,cAAc,KAAK,EAAE,SAAS,oCAAoC,CAAC;CAE/E,OAAO;AACT;;AAGA,eAAe,aAAa,GAA+B;CACzD,iBAAiB,CAAC;CAClB,MAAM,EAAE,aAAa,cAAc,SAAS,SAAS;CACrD,IAAI,CAAC,eAAe,iBAAiB,KAAA,
|
|
1
|
+
{"version":3,"file":"admin-public-origin.mjs","names":[],"sources":["../../src/routers/admin-public-origin.ts"],"sourcesContent":["/**\n * The public origin, learnt from an admin's browser (ADR 0007, decision 6): the System page reports\n * `location.origin`, an admin confirms or replaces it, and background jobs read it from the database.\n * Site-admin and CSRF protected; with authentication off there is no admin, so nothing is learnt.\n */\nimport { normalizeOrigin } from \"@storyshelf/core/utils\";\nimport type { Context } from \"hono\";\nimport { HTTPException } from \"hono/http-exception\";\nimport type { ShelfRouter } from \"../app-types.ts\";\nimport { requestOpenBuildStatesNow } from \"../git-status.ts\";\nimport { getStore } from \"../store.ts\";\nimport { flash } from \"./flash.ts\";\nimport { requireSiteAdmin } from \"./helpers.ts\";\nimport { hxRedirect } from \"./htmx.ts\";\n\n/** Register the public-origin routes of the System page. */\nexport function registerPublicOriginRoutes(app: ShelfRouter): void {\n app.post(\"/admin/public-origin/report\", handleReport);\n app.post(\"/admin/public-origin\", handleSet);\n app.post(\"/admin/public-origin/clear\", handleClear);\n}\n\n/** What an admin is told when the origin is not theirs to set. */\nconst FIXED_ORIGIN_MESSAGE =\n \"The public origin comes from PUBLIC_BASE_URL or the hosting platform, so there is nothing to confirm\";\n\n/** Whether the configuration or the platform answers: an origin an admin cannot confirm or replace. */\nasync function isFixed(): Promise<boolean> {\n const source = (await getStore().publicOrigin?.resolve())?.source;\n return source === \"config\" || source === \"platform\";\n}\n\n/** The origin a report names, once it matches what the browser says it used; throws 400 otherwise. */\nasync function reportedOrigin(c: Context): Promise<string> {\n const body = (await c.req.json().catch(() => null)) as { origin?: unknown } | null;\n const reported = typeof body?.origin === \"string\" ? normalizeOrigin(body.origin) : undefined;\n // The browser states the origin it used itself; a report that disagrees is not one.\n if (reported === undefined || c.req.header(\"origin\") !== reported) {\n throw new HTTPException(400, { message: \"Origin does not match the request\" });\n }\n return reported;\n}\n\n/** The page reports the origin it was loaded from; the first report is kept. */\nasync function handleReport(c: Context): Promise<Response> {\n requireSiteAdmin(c);\n const { authEnabled, publicOrigin, user } = getStore();\n if (!authEnabled || publicOrigin === undefined || (await isFixed())) {\n return c.body(null, 204);\n }\n const reported = await reportedOrigin(c);\n const before = await publicOrigin.stored();\n const wasResolved = (await publicOrigin.url()) !== undefined;\n const stored = await publicOrigin.report(reported, user?.id);\n await resumeIfResolved(wasResolved);\n return c.json(stored, before === null ? 201 : 200);\n}\n\n/**\n * Notifications were paused and an origin now resolves (an admin confirmed one; a mere report\n * resolves nothing): re-request the state of open builds.\n */\nasync function resumeIfResolved(wasResolved: boolean): Promise<void> {\n if (!wasResolved && (await getStore().publicOrigin?.url()) !== undefined) {\n requestOpenBuildStatesNow().catch(() => 0);\n }\n}\n\n/** Store what an admin typed; the message says why not when it is no origin. */\nasync function storeTyped(c: Context): Promise<string> {\n if (await isFixed()) {\n return FIXED_ORIGIN_MESSAGE;\n }\n const { publicOrigin, user } = getStore();\n const origin = (await c.req.formData()).get(\"origin\");\n try {\n const wasResolved = (await publicOrigin?.url()) !== undefined;\n await publicOrigin?.set(typeof origin === \"string\" ? origin : \"\", user?.id);\n await resumeIfResolved(wasResolved);\n return \"Public origin saved\";\n } catch (error) {\n return error instanceof Error ? error.message : \"Invalid origin\";\n }\n}\n\n/** An admin confirmed the detected origin or entered another. */\nasync function handleSet(c: Context): Promise<Response> {\n requireSiteAdmin(c);\n const { authEnabled, publicOrigin } = getStore();\n if (!authEnabled || publicOrigin === undefined) {\n flash(\n c,\n \"The public origin is learnt from an admin's browser, which needs authentication\",\n \"warning\",\n );\n return hxRedirect(c, \"/admin\");\n }\n const message = await storeTyped(c);\n flash(c, message, message === \"Public origin saved\" ? \"success\" : \"warning\");\n return hxRedirect(c, \"/admin\");\n}\n\n/** An admin dropped the stored origin; the next admin visit reports a fresh one. */\nasync function handleClear(c: Context): Promise<Response> {\n requireSiteAdmin(c);\n const { publicOrigin } = getStore();\n await publicOrigin?.clear();\n flash(c, \"Stored public origin removed\");\n return hxRedirect(c, \"/admin\");\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAgBA,SAAgB,2BAA2B,KAAwB;CACjE,IAAI,KAAK,+BAA+B,YAAY;CACpD,IAAI,KAAK,wBAAwB,SAAS;CAC1C,IAAI,KAAK,8BAA8B,WAAW;AACpD;;AAGA,MAAM,uBACJ;;AAGF,eAAe,UAA4B;CACzC,MAAM,UAAU,MAAM,SAAS,CAAC,CAAC,cAAc,QAAQ,EAAA,EAAI;CAC3D,OAAO,WAAW,YAAY,WAAW;AAC3C;;AAGA,eAAe,eAAe,GAA6B;CACzD,MAAM,OAAQ,MAAM,EAAE,IAAI,KAAK,CAAC,CAAC,YAAY,IAAI;CACjD,MAAM,WAAW,OAAO,MAAM,WAAW,WAAW,gBAAgB,KAAK,MAAM,IAAI,KAAA;CAEnF,IAAI,aAAa,KAAA,KAAa,EAAE,IAAI,OAAO,QAAQ,MAAM,UACvD,MAAM,IAAI,cAAc,KAAK,EAAE,SAAS,oCAAoC,CAAC;CAE/E,OAAO;AACT;;AAGA,eAAe,aAAa,GAA+B;CACzD,iBAAiB,CAAC;CAClB,MAAM,EAAE,aAAa,cAAc,SAAS,SAAS;CACrD,IAAI,CAAC,eAAe,iBAAiB,KAAA,KAAc,MAAM,QAAQ,GAC/D,OAAO,EAAE,KAAK,MAAM,GAAG;CAEzB,MAAM,WAAW,MAAM,eAAe,CAAC;CACvC,MAAM,SAAS,MAAM,aAAa,OAAO;CACzC,MAAM,cAAe,MAAM,aAAa,IAAI,MAAO,KAAA;CACnD,MAAM,SAAS,MAAM,aAAa,OAAO,UAAU,MAAM,EAAE;CAC3D,MAAM,iBAAiB,WAAW;CAClC,OAAO,EAAE,KAAK,QAAQ,WAAW,OAAO,MAAM,GAAG;AACnD;;;;;AAMA,eAAe,iBAAiB,aAAqC;CACnE,IAAI,CAAC,eAAgB,MAAM,SAAS,CAAC,CAAC,cAAc,IAAI,MAAO,KAAA,GAC7D,0BAA0B,CAAC,CAAC,YAAY,CAAC;AAE7C;;AAGA,eAAe,WAAW,GAA6B;CACrD,IAAI,MAAM,QAAQ,GAChB,OAAO;CAET,MAAM,EAAE,cAAc,SAAS,SAAS;CACxC,MAAM,UAAU,MAAM,EAAE,IAAI,SAAS,EAAA,CAAG,IAAI,QAAQ;CACpD,IAAI;EACF,MAAM,cAAe,MAAM,cAAc,IAAI,MAAO,KAAA;EACpD,MAAM,cAAc,IAAI,OAAO,WAAW,WAAW,SAAS,IAAI,MAAM,EAAE;EAC1E,MAAM,iBAAiB,WAAW;EAClC,OAAO;CACT,SAAS,OAAO;EACd,OAAO,iBAAiB,QAAQ,MAAM,UAAU;CAClD;AACF;;AAGA,eAAe,UAAU,GAA+B;CACtD,iBAAiB,CAAC;CAClB,MAAM,EAAE,aAAa,iBAAiB,SAAS;CAC/C,IAAI,CAAC,eAAe,iBAAiB,KAAA,GAAW;EAC9C,MACE,GACA,mFACA,SACF;EACA,OAAO,WAAW,GAAG,QAAQ;CAC/B;CACA,MAAM,UAAU,MAAM,WAAW,CAAC;CAClC,MAAM,GAAG,SAAS,YAAY,wBAAwB,YAAY,SAAS;CAC3E,OAAO,WAAW,GAAG,QAAQ;AAC/B;;AAGA,eAAe,YAAY,GAA+B;CACxD,iBAAiB,CAAC;CAClB,MAAM,EAAE,iBAAiB,SAAS;CAClC,MAAM,cAAc,MAAM;CAC1B,MAAM,GAAG,8BAA8B;CACvC,OAAO,WAAW,GAAG,QAAQ;AAC/B"}
|
package/dist/routers/admin.mjs
CHANGED
|
@@ -5,11 +5,11 @@ import { getStore } from "../store.mjs";
|
|
|
5
5
|
import { notifySystem } from "../notify.mjs";
|
|
6
6
|
import { reencryptWithCurrent } from "../credentials.mjs";
|
|
7
7
|
import { requireSiteAdmin } from "./helpers.mjs";
|
|
8
|
-
import { forbidden, purgeInputSchema, purgeSchema } from "./schemas.mjs";
|
|
8
|
+
import { errorSchema, forbidden, purgeInputSchema, purgeSchema } from "./schemas.mjs";
|
|
9
9
|
import { createRoute, z } from "@hono/zod-openapi";
|
|
10
10
|
import { ProjectModel } from "@storyshelf/core/models";
|
|
11
11
|
import { Retention } from "@storyshelf/core/retention";
|
|
12
|
-
|
|
12
|
+
// src/routers/admin.ts
|
|
13
13
|
const purgeRoute = createRoute({
|
|
14
14
|
method: "post",
|
|
15
15
|
tags: ["Admin"],
|
|
@@ -21,6 +21,10 @@ const purgeRoute = createRoute({
|
|
|
21
21
|
content: { "application/json": { schema: purgeSchema } },
|
|
22
22
|
description: "Retention purge run"
|
|
23
23
|
},
|
|
24
|
+
503: {
|
|
25
|
+
content: { "application/json": { schema: errorSchema } },
|
|
26
|
+
description: "Testing retention requires atomic build lifecycle support"
|
|
27
|
+
},
|
|
24
28
|
...forbidden
|
|
25
29
|
}
|
|
26
30
|
});
|
|
@@ -61,6 +65,7 @@ function registerAdmin(app) {
|
|
|
61
65
|
});
|
|
62
66
|
app.openapi(purgeRoute, async (c) => {
|
|
63
67
|
requireSiteAdmin(c);
|
|
68
|
+
if (getStore().testing) return c.json({ message: "Build purge is unavailable for testing deployments until the database adapter supports safe baseline retention. Builds are retained; contact your administrator." }, 503);
|
|
64
69
|
const ttlDays = c.req.valid("json").ttlDays ?? getStore().config.purgeTtlDays ?? 30;
|
|
65
70
|
const removedBuilds = await purgeBuilds(ttlDays);
|
|
66
71
|
const branch = await purgeBranches();
|
|
@@ -74,7 +79,7 @@ function registerAdmin(app) {
|
|
|
74
79
|
removedBuilds,
|
|
75
80
|
removedBranches: branch.removedBranches,
|
|
76
81
|
removedBaselines: branch.removedBaselines
|
|
77
|
-
});
|
|
82
|
+
}, 200);
|
|
78
83
|
});
|
|
79
84
|
}
|
|
80
85
|
async function purgeBuilds(ttlDays) {
|
|
@@ -107,7 +112,6 @@ async function purgeBranches() {
|
|
|
107
112
|
removedBaselines: results.reduce((sum, r) => sum + r.removedBaselines, 0)
|
|
108
113
|
};
|
|
109
114
|
}
|
|
110
|
-
//#endregion
|
|
111
115
|
export { registerAdmin };
|
|
112
116
|
|
|
113
117
|
//# sourceMappingURL=admin.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin.mjs","names":["forbiddenResponse"],"sources":["../../src/routers/admin.ts"],"sourcesContent":["/* oxlint-disable typescript/promise-function-async -- admin helpers are async by design */\nimport { createRoute, z } from \"@hono/zod-openapi\";\nimport { ProjectModel } from \"@storyshelf/core/models\";\nimport { Retention } from \"@storyshelf/core/retention\";\nimport type { ShelfRouter } from \"../app-types.ts\";\nimport { reencryptWithCurrent } from \"../credentials.ts\";\nimport { notifySystem } from \"../notify.ts\";\nimport { getStore } from \"../store.ts\";\nimport { requireSiteAdmin } from \"./helpers.ts\";\nimport {
|
|
1
|
+
{"version":3,"file":"admin.mjs","names":["forbiddenResponse"],"sources":["../../src/routers/admin.ts"],"sourcesContent":["/* oxlint-disable typescript/promise-function-async -- admin helpers are async by design */\nimport { createRoute, z } from \"@hono/zod-openapi\";\nimport { ProjectModel } from \"@storyshelf/core/models\";\nimport { Retention } from \"@storyshelf/core/retention\";\nimport type { ShelfRouter } from \"../app-types.ts\";\nimport { reencryptWithCurrent } from \"../credentials.ts\";\nimport { notifySystem } from \"../notify.ts\";\nimport { getStore } from \"../store.ts\";\nimport { requireSiteAdmin } from \"./helpers.ts\";\nimport {\n errorSchema,\n forbidden as forbiddenResponse,\n purgeInputSchema,\n purgeSchema,\n} from \"./schemas.ts\";\nconst purgeRoute = createRoute({\n method: \"post\",\n tags: [\"Admin\"],\n summary: \"Run the retention purge\",\n path: \"/api/v1/admin/purge\",\n request: { body: { content: { \"application/json\": { schema: purgeInputSchema } } } },\n responses: {\n 200: {\n content: { \"application/json\": { schema: purgeSchema } },\n description: \"Retention purge run\",\n },\n 503: {\n content: { \"application/json\": { schema: errorSchema } },\n description: \"Testing retention requires atomic build lifecycle support\",\n },\n ...forbiddenResponse,\n },\n});\n\nconst reencryptSchema = z.object({\n reencrypted: z.number().int(),\n failed: z.number().int(),\n unreadable: z.number().int(),\n});\n\nconst reencryptRoute = createRoute({\n method: \"post\",\n path: \"/api/v1/admin/credentials/reencrypt\",\n tags: [\"Admin\"],\n summary: \"Re-encrypt stored credentials with the current secret\",\n responses: {\n 200: {\n content: { \"application/json\": { schema: reencryptSchema } },\n description: \"Credentials re-encrypted with the current secret\",\n },\n 409: {\n content: { \"application/json\": { schema: z.object({ error: z.string() }) } },\n description: \"No previousSecret configured\",\n },\n ...forbiddenResponse,\n },\n});\n\n/** Register the site-admin endpoints: retention purge and credential re-encryption. */\nexport function registerAdmin(app: ShelfRouter): void {\n app.openapi(reencryptRoute, async (c) => {\n requireSiteAdmin(c);\n const { db, config } = getStore();\n if (!config.previousSecret) {\n return c.json({ error: \"No previousSecret is configured; nothing to re-encrypt\" }, 409);\n }\n const result = await reencryptWithCurrent(db, config);\n return c.json(\n {\n reencrypted: result.reencrypted,\n failed: result.failed,\n unreadable: result.unreadable.length,\n },\n 200,\n );\n });\n\n app.openapi(purgeRoute, async (c) => {\n requireSiteAdmin(c);\n // Until the database provides atomic reference admission and tombstones, deletion in a\n // testing deployment can race a baseline promotion. Do not expose unsafe purge.\n if (getStore().testing) {\n return c.json(\n {\n message:\n \"Build purge is unavailable for testing deployments until the database adapter supports safe baseline retention. Builds are retained; contact your administrator.\",\n },\n 503,\n );\n }\n const body = c.req.valid(\"json\");\n const ttlDays = body.ttlDays ?? getStore().config.purgeTtlDays ?? 30;\n const removedBuilds = await purgeBuilds(ttlDays);\n const branch = await purgeBranches();\n await notifySystem(\"sys:purge-completed\", {\n removedBuilds,\n removedBranches: branch.removedBranches,\n removedBaselines: branch.removedBaselines,\n ttlDays,\n });\n return c.json(\n {\n removedBuilds,\n removedBranches: branch.removedBranches,\n removedBaselines: branch.removedBaselines,\n },\n 200,\n );\n });\n}\n\n// oxlint-disable-next-line typescript/promise-function-async\nasync function purgeBuilds(ttlDays: number): Promise<number> {\n const projects = await new ProjectModel(getStore().db).list();\n const retention = new Retention(\n getStore().db,\n getStore().storage,\n {\n builds: getStore().db.tables.builds,\n buildLabels: getStore().db.tables.buildLabels,\n baselines: getStore().db.tables.baselines,\n },\n getStore().logger,\n );\n const results = await Promise.all(\n projects.map((project) => retention.purge(project, { ttlDays, keepLatestPerBranch: true })),\n );\n return results.reduce((sum, result) => sum + result.removedBuilds, 0);\n}\n\n// oxlint-disable-next-line typescript/promise-function-async\nasync function purgeBranches(): Promise<{ removedBranches: number; removedBaselines: number }> {\n const branchTtl = getStore().config.branchTtlDays ?? 30;\n if (branchTtl === null) {\n return { removedBranches: 0, removedBaselines: 0 };\n }\n const projects = await new ProjectModel(getStore().db).list();\n const retention = new Retention(\n getStore().db,\n getStore().storage,\n {\n builds: getStore().db.tables.builds,\n buildLabels: getStore().db.tables.buildLabels,\n baselines: getStore().db.tables.baselines,\n },\n getStore().logger,\n );\n const results = await Promise.all(\n projects.map((project) => retention.purgeStaleBranches(project, branchTtl)),\n );\n const removedBranches = results.reduce(\n (sum: number, r: { removedBranches: number }) => sum + r.removedBranches,\n 0,\n );\n const removedBaselines = results.reduce(\n (sum: number, r: { removedBaselines: number }) => sum + r.removedBaselines,\n 0,\n );\n return { removedBranches, removedBaselines };\n}\n"],"mappings":";;;;;;;;;;;;AAeA,MAAM,aAAa,YAAY;CAC7B,QAAQ;CACR,MAAM,CAAC,OAAO;CACd,SAAS;CACT,MAAM;CACN,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,oBAAoB,EAAE,QAAQ,iBAAiB,EAAE,EAAE,EAAE;CACnF,WAAW;EACT,KAAK;GACH,SAAS,EAAE,oBAAoB,EAAE,QAAQ,YAAY,EAAE;GACvD,aAAa;EACf;EACA,KAAK;GACH,SAAS,EAAE,oBAAoB,EAAE,QAAQ,YAAY,EAAE;GACvD,aAAa;EACf;EACA,GAAGA;CACL;AACF,CAAC;AAED,MAAM,kBAAkB,EAAE,OAAO;CAC/B,aAAa,EAAE,OAAO,CAAC,CAAC,IAAI;CAC5B,QAAQ,EAAE,OAAO,CAAC,CAAC,IAAI;CACvB,YAAY,EAAE,OAAO,CAAC,CAAC,IAAI;AAC7B,CAAC;AAED,MAAM,iBAAiB,YAAY;CACjC,QAAQ;CACR,MAAM;CACN,MAAM,CAAC,OAAO;CACd,SAAS;CACT,WAAW;EACT,KAAK;GACH,SAAS,EAAE,oBAAoB,EAAE,QAAQ,gBAAgB,EAAE;GAC3D,aAAa;EACf;EACA,KAAK;GACH,SAAS,EAAE,oBAAoB,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,EAAE;GAC3E,aAAa;EACf;EACA,GAAGA;CACL;AACF,CAAC;;AAGD,SAAgB,cAAc,KAAwB;CACpD,IAAI,QAAQ,gBAAgB,OAAO,MAAM;EACvC,iBAAiB,CAAC;EAClB,MAAM,EAAE,IAAI,WAAW,SAAS;EAChC,IAAI,CAAC,OAAO,gBACV,OAAO,EAAE,KAAK,EAAE,OAAO,yDAAyD,GAAG,GAAG;EAExF,MAAM,SAAS,MAAM,qBAAqB,IAAI,MAAM;EACpD,OAAO,EAAE,KACP;GACE,aAAa,OAAO;GACpB,QAAQ,OAAO;GACf,YAAY,OAAO,WAAW;EAChC,GACA,GACF;CACF,CAAC;CAED,IAAI,QAAQ,YAAY,OAAO,MAAM;EACnC,iBAAiB,CAAC;EAGlB,IAAI,SAAS,CAAC,CAAC,SACb,OAAO,EAAE,KACP,EACE,SACE,mKACJ,GACA,GACF;EAGF,MAAM,UADO,EAAE,IAAI,MAAM,MACN,CAAC,CAAC,WAAW,SAAS,CAAC,CAAC,OAAO,gBAAgB;EAClE,MAAM,gBAAgB,MAAM,YAAY,OAAO;EAC/C,MAAM,SAAS,MAAM,cAAc;EACnC,MAAM,aAAa,uBAAuB;GACxC;GACA,iBAAiB,OAAO;GACxB,kBAAkB,OAAO;GACzB;EACF,CAAC;EACD,OAAO,EAAE,KACP;GACE;GACA,iBAAiB,OAAO;GACxB,kBAAkB,OAAO;EAC3B,GACA,GACF;CACF,CAAC;AACH;AAGA,eAAe,YAAY,SAAkC;CAC3D,MAAM,WAAW,MAAM,IAAI,aAAa,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK;CAC5D,MAAM,YAAY,IAAI,UACpB,SAAS,CAAC,CAAC,IACX,SAAS,CAAC,CAAC,SACX;EACE,QAAQ,SAAS,CAAC,CAAC,GAAG,OAAO;EAC7B,aAAa,SAAS,CAAC,CAAC,GAAG,OAAO;EAClC,WAAW,SAAS,CAAC,CAAC,GAAG,OAAO;CAClC,GACA,SAAS,CAAC,CAAC,MACb;CAIA,QAAO,MAHe,QAAQ,IAC5B,SAAS,KAAK,YAAY,UAAU,MAAM,SAAS;EAAE;EAAS,qBAAqB;CAAK,CAAC,CAAC,CAC5F,EAAA,CACe,QAAQ,KAAK,WAAW,MAAM,OAAO,eAAe,CAAC;AACtE;AAGA,eAAe,gBAAgF;CAC7F,MAAM,YAAY,SAAS,CAAC,CAAC,OAAO,iBAAiB;CACrD,IAAI,cAAc,MAChB,OAAO;EAAE,iBAAiB;EAAG,kBAAkB;CAAE;CAEnD,MAAM,WAAW,MAAM,IAAI,aAAa,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK;CAC5D,MAAM,YAAY,IAAI,UACpB,SAAS,CAAC,CAAC,IACX,SAAS,CAAC,CAAC,SACX;EACE,QAAQ,SAAS,CAAC,CAAC,GAAG,OAAO;EAC7B,aAAa,SAAS,CAAC,CAAC,GAAG,OAAO;EAClC,WAAW,SAAS,CAAC,CAAC,GAAG,OAAO;CAClC,GACA,SAAS,CAAC,CAAC,MACb;CACA,MAAM,UAAU,MAAM,QAAQ,IAC5B,SAAS,KAAK,YAAY,UAAU,mBAAmB,SAAS,SAAS,CAAC,CAC5E;CASA,OAAO;EAAE,iBARe,QAAQ,QAC7B,KAAa,MAAmC,MAAM,EAAE,iBACzD,CAMqB;EAAG,kBAJD,QAAQ,QAC9B,KAAa,MAAoC,MAAM,EAAE,kBAC1D,CAEuC;CAAE;AAC7C"}
|
package/dist/routers/assets.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import "node:path";
|
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
4
|
import { assetManifest } from "../asset-manifest.mjs";
|
|
5
|
-
|
|
5
|
+
// src/routers/assets.ts
|
|
6
6
|
const IMMUTABLE = "public, max-age=31536000, immutable";
|
|
7
7
|
/**
|
|
8
8
|
* Serve a content-addressed asset. A matching hash is cacheable forever and
|
|
@@ -65,7 +65,6 @@ function registerAssets(app) {
|
|
|
65
65
|
});
|
|
66
66
|
});
|
|
67
67
|
}
|
|
68
|
-
//#endregion
|
|
69
68
|
export { registerAssets };
|
|
70
69
|
|
|
71
70
|
//# sourceMappingURL=assets.mjs.map
|
|
@@ -7,7 +7,7 @@ import { attemptLogSchema, attemptSchema, notFound as notFound$1, unauthorized }
|
|
|
7
7
|
import { VIEW_ROLES, buildForProject } from "./builds.handlers.mjs";
|
|
8
8
|
import { createRoute, z } from "@hono/zod-openapi";
|
|
9
9
|
import { CaptureAttemptModel, CaptureLogModel } from "@storyshelf/core/models";
|
|
10
|
-
|
|
10
|
+
// src/routers/attempts.ts
|
|
11
11
|
/** Register the capture-attempt history and per-attempt log endpoints. */
|
|
12
12
|
function registerAttempts(app) {
|
|
13
13
|
app.openapi(listAttemptsRoute, async (c) => {
|
|
@@ -90,7 +90,6 @@ async function attemptForBuild(build, attemptNo) {
|
|
|
90
90
|
if (!attempt) notFound("Attempt not found");
|
|
91
91
|
return attempt;
|
|
92
92
|
}
|
|
93
|
-
//#endregion
|
|
94
93
|
export { registerAttempts };
|
|
95
94
|
|
|
96
95
|
//# sourceMappingURL=attempts.mjs.map
|