@setzkasten-cms/astro-admin 1.4.0 → 1.4.2

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.
Files changed (153) hide show
  1. package/dist/api-routes/_auth-guard.d.ts +47 -0
  2. package/dist/api-routes/_auth-guard.js +18 -0
  3. package/dist/api-routes/_commit-trailers.d.ts +8 -0
  4. package/dist/api-routes/_commit-trailers.js +8 -0
  5. package/dist/api-routes/_feature-gate.d.ts +23 -0
  6. package/dist/api-routes/_feature-gate.js +7 -0
  7. package/dist/api-routes/_github-cache.d.ts +4 -0
  8. package/dist/api-routes/_github-cache.js +8 -0
  9. package/dist/api-routes/_github-token.d.ts +27 -0
  10. package/dist/api-routes/_github-token.js +8 -0
  11. package/dist/api-routes/_license-tier.d.ts +22 -0
  12. package/dist/api-routes/_license-tier.js +6 -0
  13. package/dist/api-routes/_pages-meta-store.d.ts +32 -0
  14. package/dist/api-routes/_pages-meta-store.js +9 -0
  15. package/dist/api-routes/_role-resolver.d.ts +15 -0
  16. package/dist/api-routes/_role-resolver.js +13 -0
  17. package/dist/api-routes/_session-cookie.d.ts +18 -0
  18. package/dist/api-routes/_session-cookie.js +6 -0
  19. package/dist/api-routes/_storage-config.d.ts +60 -0
  20. package/dist/api-routes/_storage-config.js +10 -0
  21. package/dist/api-routes/_vercel-origin.d.ts +16 -0
  22. package/dist/api-routes/_vercel-origin.js +6 -0
  23. package/dist/api-routes/_webhook-dispatcher.d.ts +13 -0
  24. package/dist/api-routes/_webhook-dispatcher.js +97 -0
  25. package/dist/api-routes/_webhook-signing.d.ts +11 -0
  26. package/dist/api-routes/_webhook-signing.js +6 -0
  27. package/dist/api-routes/_webhook-status-store.d.ts +19 -0
  28. package/dist/api-routes/_webhook-status-store.js +10 -0
  29. package/dist/api-routes/_website-resolver.d.ts +49 -0
  30. package/dist/api-routes/_website-resolver.js +14 -0
  31. package/dist/api-routes/_websites-store.d.ts +30 -0
  32. package/dist/api-routes/_websites-store.js +11 -0
  33. package/dist/api-routes/asset-proxy.d.ts +12 -0
  34. package/dist/api-routes/asset-proxy.js +67 -0
  35. package/dist/api-routes/auth-callback.d.ts +9 -0
  36. package/dist/api-routes/auth-callback.js +68 -0
  37. package/dist/api-routes/auth-login.d.ts +11 -0
  38. package/dist/api-routes/auth-login.js +27 -0
  39. package/dist/api-routes/auth-logout.d.ts +10 -0
  40. package/dist/api-routes/auth-logout.js +13 -0
  41. package/dist/api-routes/auth-session.d.ts +9 -0
  42. package/dist/api-routes/auth-session.js +31 -0
  43. package/dist/api-routes/auth-setzkasten-login.d.ts +18 -0
  44. package/dist/api-routes/auth-setzkasten-login.js +74 -0
  45. package/dist/api-routes/catalog-add.d.ts +14 -0
  46. package/dist/api-routes/catalog-add.js +153 -0
  47. package/dist/api-routes/catalog-export.d.ts +13 -0
  48. package/dist/api-routes/catalog-export.js +71 -0
  49. package/dist/api-routes/catalog-helpers.d.ts +41 -0
  50. package/dist/api-routes/catalog-helpers.js +11 -0
  51. package/dist/api-routes/catalog-list.d.ts +11 -0
  52. package/dist/api-routes/catalog-list.js +12 -0
  53. package/dist/api-routes/config.d.ts +12 -0
  54. package/dist/api-routes/config.js +43 -0
  55. package/dist/api-routes/deploy-hook.d.ts +14 -0
  56. package/dist/api-routes/deploy-hook.js +52 -0
  57. package/dist/api-routes/editors.d.ts +29 -0
  58. package/dist/api-routes/editors.js +18 -0
  59. package/dist/api-routes/github-proxy.d.ts +12 -0
  60. package/dist/api-routes/github-proxy.js +82 -0
  61. package/dist/api-routes/global-config.d.ts +20 -0
  62. package/dist/api-routes/global-config.js +19 -0
  63. package/dist/api-routes/history-rollback.d.ts +22 -0
  64. package/dist/api-routes/history-rollback.js +111 -0
  65. package/dist/api-routes/history-version.d.ts +11 -0
  66. package/dist/api-routes/history-version.js +57 -0
  67. package/dist/api-routes/history.d.ts +13 -0
  68. package/dist/api-routes/history.js +85 -0
  69. package/dist/api-routes/icons-local.d.ts +28 -0
  70. package/dist/api-routes/icons-local.js +115 -0
  71. package/dist/api-routes/init-add-section.d.ts +23 -0
  72. package/dist/api-routes/init-add-section.js +396 -0
  73. package/dist/api-routes/init-apply.d.ts +11 -0
  74. package/dist/api-routes/init-apply.js +266 -0
  75. package/dist/api-routes/init-migrate.d.ts +16 -0
  76. package/dist/api-routes/init-migrate.js +205 -0
  77. package/dist/api-routes/init-scan-page.d.ts +39 -0
  78. package/dist/api-routes/init-scan-page.js +260 -0
  79. package/dist/api-routes/init-scan.d.ts +11 -0
  80. package/dist/api-routes/init-scan.js +128 -0
  81. package/dist/api-routes/migrate-to-multi.d.ts +26 -0
  82. package/dist/api-routes/migrate-to-multi.js +188 -0
  83. package/dist/api-routes/pages.d.ts +39 -0
  84. package/dist/api-routes/pages.js +88 -0
  85. package/dist/api-routes/section-add.d.ts +18 -0
  86. package/dist/api-routes/section-add.js +173 -0
  87. package/dist/api-routes/section-commit-pending.d.ts +18 -0
  88. package/dist/api-routes/section-commit-pending.js +207 -0
  89. package/dist/api-routes/section-delete.d.ts +15 -0
  90. package/dist/api-routes/section-delete.js +149 -0
  91. package/dist/api-routes/section-duplicate.d.ts +15 -0
  92. package/dist/api-routes/section-duplicate.js +143 -0
  93. package/dist/api-routes/section-management.d.ts +41 -0
  94. package/dist/api-routes/section-management.js +14 -0
  95. package/dist/api-routes/section-prepare-copy.d.ts +25 -0
  96. package/dist/api-routes/section-prepare-copy.js +69 -0
  97. package/dist/api-routes/section-prepare.d.ts +18 -0
  98. package/dist/api-routes/section-prepare.js +104 -0
  99. package/dist/api-routes/setup-github-app-bounce.d.ts +13 -0
  100. package/dist/api-routes/setup-github-app-bounce.js +45 -0
  101. package/dist/api-routes/setup-github-app-branches.d.ts +14 -0
  102. package/dist/api-routes/setup-github-app-branches.js +58 -0
  103. package/dist/api-routes/setup-github-app-callback.d.ts +15 -0
  104. package/dist/api-routes/setup-github-app-callback.js +45 -0
  105. package/dist/api-routes/setup-github-app-installed.d.ts +15 -0
  106. package/dist/api-routes/setup-github-app-installed.js +33 -0
  107. package/dist/api-routes/setup-github-app-repos.d.ts +17 -0
  108. package/dist/api-routes/setup-github-app-repos.js +41 -0
  109. package/dist/api-routes/setup-github-app.d.ts +15 -0
  110. package/dist/api-routes/setup-github-app.js +41 -0
  111. package/dist/api-routes/updater-check.d.ts +10 -0
  112. package/dist/api-routes/updater-check.js +37 -0
  113. package/dist/api-routes/updater-register.d.ts +14 -0
  114. package/dist/api-routes/updater-register.js +71 -0
  115. package/dist/api-routes/updater-transfer.d.ts +11 -0
  116. package/dist/api-routes/updater-transfer.js +37 -0
  117. package/dist/api-routes/updater-unbind.d.ts +17 -0
  118. package/dist/api-routes/updater-unbind.js +35 -0
  119. package/dist/api-routes/webhooks-status.d.ts +12 -0
  120. package/dist/api-routes/webhooks-status.js +22 -0
  121. package/dist/api-routes/webhooks-test.d.ts +13 -0
  122. package/dist/api-routes/webhooks-test.js +124 -0
  123. package/dist/api-routes/webhooks.d.ts +6 -0
  124. package/dist/api-routes/webhooks.js +148 -0
  125. package/dist/api-routes/websites-add.d.ts +15 -0
  126. package/dist/api-routes/websites-add.js +92 -0
  127. package/dist/api-routes/websites-list.d.ts +12 -0
  128. package/dist/api-routes/websites-list.js +35 -0
  129. package/dist/api-routes/websites-remove.d.ts +15 -0
  130. package/dist/api-routes/websites-remove.js +69 -0
  131. package/dist/chunk-35S35OIV.js +80 -0
  132. package/dist/chunk-45ARVNT3.js +25 -0
  133. package/dist/chunk-5PIMDP4N.js +25 -0
  134. package/dist/chunk-5ZFTG4BW.js +10 -0
  135. package/dist/chunk-6UIKVKED.js +51 -0
  136. package/dist/chunk-737TIZRU.js +9 -0
  137. package/dist/chunk-AM4DZXXM.js +120 -0
  138. package/dist/chunk-FXNOTESI.js +87 -0
  139. package/dist/chunk-GHNK2GFE.js +48 -0
  140. package/dist/chunk-GRG3LNKH.js +37 -0
  141. package/dist/chunk-INIWFKQ3.js +236 -0
  142. package/dist/chunk-JHY6XTLL.js +24 -0
  143. package/dist/chunk-K22A4ZBS.js +1574 -0
  144. package/dist/chunk-KH22FJO5.js +17 -0
  145. package/dist/chunk-NKDATSPA.js +43 -0
  146. package/dist/chunk-RHJONMLK.js +1267 -0
  147. package/dist/chunk-TJNJKPUL.js +11 -0
  148. package/dist/chunk-V6IMPVF3.js +120 -0
  149. package/dist/chunk-W3QHY5GW.js +19 -0
  150. package/dist/chunk-ZQDGGWJP.js +43 -0
  151. package/package.json +249 -53
  152. package/src/api-routes/__tests__/route-registry.test.ts +7 -1
  153. package/tsconfig.json +0 -9
@@ -0,0 +1,47 @@
1
+ import { AuthSession, SetzKastenConfig } from '@setzkasten-cms/core';
2
+
3
+ declare function parseSession(raw: string | undefined): AuthSession | null;
4
+ /**
5
+ * Returns 401 if the request has no valid session, 403 if the user is not
6
+ * an admin, or null (= allowed) otherwise. Used by every admin-only API
7
+ * endpoint so the role check is mechanically applied — never just
8
+ * `if (!session) return 401`, which lets editors hit admin-only routes.
9
+ */
10
+ declare function requireAdmin(rawSession: string | undefined): Response | null;
11
+ /**
12
+ * Returns a 403/503 Response if the session user may NOT edit the given page,
13
+ * or null (= allowed) otherwise.
14
+ *
15
+ * Authorization is two-layered:
16
+ *
17
+ * 1. Global editors (_editors.json in the config-repo) decide which pages
18
+ * an editor account can touch at all. Admins always pass.
19
+ *
20
+ * 2. Per-website allowedEmails (in WebsiteEntry, multi-mode only) decide
21
+ * which editors may operate on the website the request is targeting.
22
+ * Without this layer, an editor could swap the X-SK-Website header
23
+ * to any registered website id and inherit edit access transitively
24
+ * from layer 1. Single-mode requests skip this layer because the
25
+ * resolver returns no Result.ok for them.
26
+ *
27
+ * Fail-modes for layer 1:
28
+ * - File absent (genuine 404) → undefined → canEditPage allows
29
+ * (semantic: "no restrictions configured")
30
+ * - File present and parsed → list → canEditPage applies it
31
+ * - Fetch failed / parse failed → 503, deny access (was a silent grant
32
+ * before review finding #1).
33
+ */
34
+ declare function guardPageAccess(session: AuthSession | null, pageKey: string, fullConfig: SetzKastenConfig | undefined, request?: Request): Promise<Response | null>;
35
+ /**
36
+ * Per-website authorization. Editors must be listed on
37
+ * `WebsiteEntry.allowedEmails` to operate on the website that the
38
+ * X-SK-Website header resolves to. Admins always pass; single-mode
39
+ * skips this check entirely (no website context).
40
+ *
41
+ * Returns 403 on deny, null on allow. Resolver failures are
42
+ * considered "no per-website restriction" — the global guard above
43
+ * already covers fail-closed for editors-file errors.
44
+ */
45
+ declare function guardWebsiteAccess(session: AuthSession, request: Request): Promise<Response | null>;
46
+
47
+ export { guardPageAccess, guardWebsiteAccess, parseSession, requireAdmin };
@@ -0,0 +1,18 @@
1
+ import {
2
+ guardPageAccess,
3
+ guardWebsiteAccess,
4
+ parseSession,
5
+ requireAdmin
6
+ } from "../chunk-INIWFKQ3.js";
7
+ import "../chunk-6UIKVKED.js";
8
+ import "../chunk-5PIMDP4N.js";
9
+ import "../chunk-45ARVNT3.js";
10
+ import "../chunk-NKDATSPA.js";
11
+ import "../chunk-TJNJKPUL.js";
12
+ import "../chunk-KH22FJO5.js";
13
+ export {
14
+ guardPageAccess,
15
+ guardWebsiteAccess,
16
+ parseSession,
17
+ requireAdmin
18
+ };
@@ -0,0 +1,8 @@
1
+ declare const SETZKASTEN_CO_AUTHOR = "Co-authored-by: Setzkasten <setzkasten@setzkasten.dev>";
2
+ /**
3
+ * Appends git commit trailers to a commit message.
4
+ * Always includes the Setzkasten co-author; optionally includes the editor.
5
+ */
6
+ declare function withTrailers(message: string, editorEmail?: string | null): string;
7
+
8
+ export { SETZKASTEN_CO_AUTHOR, withTrailers };
@@ -0,0 +1,8 @@
1
+ import {
2
+ SETZKASTEN_CO_AUTHOR,
3
+ withTrailers
4
+ } from "../chunk-KH22FJO5.js";
5
+ export {
6
+ SETZKASTEN_CO_AUTHOR,
7
+ withTrailers
8
+ };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Soft-fail feature-gate guard for API routes. Returns `null` when the
3
+ * feature is available at the current license tier; otherwise returns a
4
+ * 403 Response with a machine-readable JSON body.
5
+ *
6
+ * Usage:
7
+ *
8
+ * const gate = gateFeature('editors')
9
+ * if (gate) return gate
10
+ * // ... normal route logic
11
+ *
12
+ * The body shape:
13
+ *
14
+ * { error: string, code: 'feature-locked', feature: string, requiredTier: string }
15
+ *
16
+ * Routes use 403 (not 402) because UI clients robustly handle 403 Forbidden
17
+ * but often surface 402 Payment Required as a generic error. The `code`
18
+ * field lets the UI hook (`useFeatureGate`) distinguish locked features
19
+ * from real authorization failures.
20
+ */
21
+ declare function gateFeature(feature: string): Response | null;
22
+
23
+ export { gateFeature };
@@ -0,0 +1,7 @@
1
+ import {
2
+ gateFeature
3
+ } from "../chunk-5PIMDP4N.js";
4
+ import "../chunk-TJNJKPUL.js";
5
+ export {
6
+ gateFeature
7
+ };
@@ -0,0 +1,4 @@
1
+ declare function cachedFetch<T>(key: string, ttlMs: number, fetcher: () => Promise<T>): Promise<T>;
2
+ declare function invalidateCache(key: string): void;
3
+
4
+ export { cachedFetch, invalidateCache };
@@ -0,0 +1,8 @@
1
+ import {
2
+ cachedFetch,
3
+ invalidateCache
4
+ } from "../chunk-45ARVNT3.js";
5
+ export {
6
+ cachedFetch,
7
+ invalidateCache
8
+ };
@@ -0,0 +1,27 @@
1
+ import { Result } from '@setzkasten-cms/core';
2
+
3
+ /**
4
+ * Token for the **config repo** (in multi mode) or the **website repo**
5
+ * (in single mode). Reads the App credentials straight from ENV — no
6
+ * X-SK-Website routing involved. Used by config-management endpoints
7
+ * (`/websites/add`, `/websites/remove`) and any read of `_global_config.json`
8
+ * / `_editors.json` that lives next to the registry, not next to the
9
+ * editable content.
10
+ *
11
+ * In single mode this is the only relevant token; in multi mode it is
12
+ * the token for the config repo, while per-website operations use
13
+ * {@link resolveGitHubTokenForRequest}.
14
+ */
15
+ declare function resolveConfigRepoToken(): Promise<Result<string>>;
16
+ /**
17
+ * Token for the website resolved from the request's `X-SK-Website` header.
18
+ * In multi mode this picks the installation id of the active website;
19
+ * in single mode the resolver synthesises one website from build-time
20
+ * storage and returns the same token as {@link resolveConfigRepoToken}.
21
+ *
22
+ * The PEM private key always comes from `GITHUB_APP_PRIVATE_KEY` — one
23
+ * App, many installations.
24
+ */
25
+ declare function resolveGitHubTokenForRequest(request: Request): Promise<Result<string>>;
26
+
27
+ export { resolveConfigRepoToken, resolveGitHubTokenForRequest };
@@ -0,0 +1,8 @@
1
+ import {
2
+ resolveConfigRepoToken,
3
+ resolveGitHubTokenForRequest
4
+ } from "../chunk-NKDATSPA.js";
5
+ export {
6
+ resolveConfigRepoToken,
7
+ resolveGitHubTokenForRequest
8
+ };
@@ -0,0 +1,22 @@
1
+ import { FeatureTier } from '@setzkasten-cms/core';
2
+
3
+ /**
4
+ * Server-side license-tier lookup based on the configured license key.
5
+ *
6
+ * The prefix is the canonical signal:
7
+ * SK-PRO-... → 'pro'
8
+ * SK-ENT-... → 'enterprise'
9
+ * anything else (or unset) → 'free'
10
+ *
11
+ * The updater backend does the real validation (revocation, expiry); this
12
+ * function is enough to enforce honest-user limits at the API boundary.
13
+ * A bad actor faking a prefix will get 200 here but be flagged on the next
14
+ * dashboard register call — and the limit gate is a deterrent, not a
15
+ * payment system.
16
+ *
17
+ * Reads `SETZKASTEN_LICENSE_KEY` from the env first; future versions may
18
+ * add a config-repo `_global_config.json` fallback.
19
+ */
20
+ declare function resolveLicenseTier(): FeatureTier;
21
+
22
+ export { resolveLicenseTier };
@@ -0,0 +1,6 @@
1
+ import {
2
+ resolveLicenseTier
3
+ } from "../chunk-TJNJKPUL.js";
4
+ export {
5
+ resolveLicenseTier
6
+ };
@@ -0,0 +1,32 @@
1
+ import { Result, PagesMeta } from '@setzkasten-cms/core';
2
+
3
+ /**
4
+ * Server-side read/write helpers for `_pages-meta.json`.
5
+ *
6
+ * - `readPagesMeta` returns the parsed registry plus its current SHA, or
7
+ * an empty registry with `sha: null` when the file does not exist.
8
+ * - `recordPageEdit` updates a single page's `lastModified` timestamp and
9
+ * commits back, retrying once on 409 (concurrent edit). Failures
10
+ * propagate as network errors so callers can decide whether to log
11
+ * silently or surface them.
12
+ */
13
+ interface PagesMetaTarget {
14
+ readonly owner: string;
15
+ readonly repo: string;
16
+ readonly branch: string;
17
+ readonly contentPath: string;
18
+ readonly token: string;
19
+ }
20
+ interface PagesMetaSnapshot {
21
+ readonly meta: PagesMeta;
22
+ readonly sha: string | null;
23
+ }
24
+ declare function readPagesMeta(target: PagesMetaTarget): Promise<Result<PagesMetaSnapshot>>;
25
+ /**
26
+ * Records that `pageKey` was just edited. Reads the meta, sets the
27
+ * timestamp, writes back. On a 409 conflict (someone else committed
28
+ * meanwhile) the function re-reads and retries up to MAX_RETRIES times.
29
+ */
30
+ declare function recordPageEdit(target: PagesMetaTarget, pageKey: string, timestamp?: number): Promise<Result<void>>;
31
+
32
+ export { type PagesMetaTarget, readPagesMeta, recordPageEdit };
@@ -0,0 +1,9 @@
1
+ import {
2
+ readPagesMeta,
3
+ recordPageEdit
4
+ } from "../chunk-FXNOTESI.js";
5
+ import "../chunk-KH22FJO5.js";
6
+ export {
7
+ readPagesMeta,
8
+ recordPageEdit
9
+ };
@@ -0,0 +1,15 @@
1
+ import { AuthProviderKind, UserRole } from '@setzkasten-cms/core';
2
+
3
+ /**
4
+ * Builds the role-resolver callback that auth-adapters call per OAuth
5
+ * callback. Reads the live `_editors.json` (case-insensitive lookup) and
6
+ * combines it with the configured `allowedEmails` env list. Callers pass
7
+ * the resolver into `createGitHubAuth` / `createGoogleAuth` /
8
+ * `verifyFirebaseJwt` so role assignment happens once, in one place.
9
+ *
10
+ * Returns `null` when the user is not allowed at all, otherwise the
11
+ * effective role.
12
+ */
13
+ declare function makeRoleResolver(provider: AuthProviderKind, allowedEmails: readonly string[] | undefined): (email: string) => Promise<UserRole | null>;
14
+
15
+ export { makeRoleResolver };
@@ -0,0 +1,13 @@
1
+ import {
2
+ makeRoleResolver
3
+ } from "../chunk-ZQDGGWJP.js";
4
+ import "../chunk-INIWFKQ3.js";
5
+ import "../chunk-6UIKVKED.js";
6
+ import "../chunk-5PIMDP4N.js";
7
+ import "../chunk-45ARVNT3.js";
8
+ import "../chunk-NKDATSPA.js";
9
+ import "../chunk-TJNJKPUL.js";
10
+ import "../chunk-KH22FJO5.js";
11
+ export {
12
+ makeRoleResolver
13
+ };
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Centralised builder for the session-cookie attributes. Two routes set the
3
+ * cookie today (auth-callback for GitHub OAuth, auth-setzkasten-login for
4
+ * Firebase) — both must share the exact same domain/secure/path so the
5
+ * cookie can be read across the admin and (in standalone setups) the
6
+ * managed website on a sibling subdomain.
7
+ */
8
+ interface SessionCookieOptions {
9
+ readonly httpOnly: true;
10
+ readonly secure: boolean;
11
+ readonly sameSite: 'lax';
12
+ readonly path: '/';
13
+ readonly maxAge: number;
14
+ readonly domain?: string;
15
+ }
16
+ declare function sessionCookieOptions(secure: boolean): SessionCookieOptions;
17
+
18
+ export { type SessionCookieOptions, sessionCookieOptions };
@@ -0,0 +1,6 @@
1
+ import {
2
+ sessionCookieOptions
3
+ } from "../chunk-JHY6XTLL.js";
4
+ export {
5
+ sessionCookieOptions
6
+ };
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Storage resolution helpers for admin API routes.
3
+ *
4
+ * Two entry points:
5
+ *
6
+ * - {@link resolveStorageConfigForRequest} (default for content routes) —
7
+ * resolves the **active website** for a request. In single mode this
8
+ * is always the integration's website; in multi mode it is the website
9
+ * selected by the X-SK-Website header.
10
+ *
11
+ * - {@link resolveStorageConfig} (build-time only, used by auth-guard
12
+ * for the editors file) — returns the integration's build-time storage
13
+ * target. In single mode this is the website repo; in multi mode it
14
+ * is the config repo (where `_editors.json` lives next to
15
+ * `websites.json`).
16
+ *
17
+ * The lookup chain inside `resolveStorageConfig`:
18
+ * 1. Request body override (explicit `{ owner, repo, branch }` payload)
19
+ * 2. `__SETZKASTEN_STORAGE__` Vite define (embedded at build time)
20
+ * 3. `globalThis.__SETZKASTEN_CONFIG__` (injectScript, SSR-only fallback)
21
+ */
22
+ interface StorageConfig {
23
+ owner: string;
24
+ repo: string;
25
+ branch: string;
26
+ /** Monorepo prefix to prepend to file paths, e.g. 'apps/website' */
27
+ projectPrefix: string;
28
+ }
29
+ /**
30
+ * Returns the integration's build-time storage target. Used by the
31
+ * auth-guard to read the editors file (which lives next to the build-
32
+ * time storage in both single and multi mode) and by tests that want
33
+ * to bypass the per-request resolver. Most content routes should use
34
+ * {@link resolveStorageConfigForRequest} instead.
35
+ */
36
+ declare function resolveStorageConfig(body?: {
37
+ owner?: string;
38
+ repo?: string;
39
+ branch?: string;
40
+ }): StorageConfig | null;
41
+ /**
42
+ * Prefix a file path with the monorepo project prefix.
43
+ * e.g. prefixPath('src/pages/index.astro', 'apps/website') → 'apps/website/src/pages/index.astro'
44
+ */
45
+ declare function prefixPath(filePath: string, projectPrefix: string): string;
46
+ /**
47
+ * Standalone-aware variant: resolves storage from the active website
48
+ * (X-SK-Website header → WebsitesRegistry) before falling back to the
49
+ * single-repo build-time configuration.
50
+ *
51
+ * Body overrides still win over both — useful for routes that take an
52
+ * explicit `{ owner, repo, branch }` payload.
53
+ */
54
+ declare function resolveStorageConfigForRequest(request: Request, body?: {
55
+ owner?: string;
56
+ repo?: string;
57
+ branch?: string;
58
+ }): Promise<StorageConfig | null>;
59
+
60
+ export { type StorageConfig, prefixPath, resolveStorageConfig, resolveStorageConfigForRequest };
@@ -0,0 +1,10 @@
1
+ import {
2
+ prefixPath,
3
+ resolveStorageConfig,
4
+ resolveStorageConfigForRequest
5
+ } from "../chunk-6UIKVKED.js";
6
+ export {
7
+ prefixPath,
8
+ resolveStorageConfig,
9
+ resolveStorageConfigForRequest
10
+ };
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Derives the real public origin on Vercel (and any reverse-proxy setup).
3
+ *
4
+ * Problem: Inside a Vercel serverless function, both `request.url` and
5
+ * Astro's `url` context resolve to the internal function host
6
+ * (e.g. "https://localhost"), not the public hostname.
7
+ *
8
+ * Solution: Read the x-forwarded-host and x-forwarded-proto headers that
9
+ * Vercel's edge layer sets on every inbound request.
10
+ *
11
+ * Falls back gracefully to the `host` header and `https` for local dev
12
+ * or non-Vercel environments.
13
+ */
14
+ declare function getPublicOrigin(request: Request): string;
15
+
16
+ export { getPublicOrigin };
@@ -0,0 +1,6 @@
1
+ import {
2
+ getPublicOrigin
3
+ } from "../chunk-5ZFTG4BW.js";
4
+ export {
5
+ getPublicOrigin
6
+ };
@@ -0,0 +1,13 @@
1
+ import { WebhookEvent, WebhookPayload } from '@setzkasten-cms/core';
2
+
3
+ /**
4
+ * Fire all enabled webhooks subscribed to `event`. Best-effort —
5
+ * each request runs with a 5s timeout; failures are recorded in the
6
+ * in-memory status store but do not throw or block the caller.
7
+ *
8
+ * Caller-side: invoke as `void fireWebhooks(...)` to make the
9
+ * fire-and-forget intent explicit.
10
+ */
11
+ declare function fireWebhooks(event: WebhookEvent, payload: Omit<WebhookPayload, 'event' | 'timestamp'>, request: Request): Promise<void>;
12
+
13
+ export { fireWebhooks };
@@ -0,0 +1,97 @@
1
+ import {
2
+ signPayload
3
+ } from "../chunk-737TIZRU.js";
4
+ import {
5
+ recordWebhookFire
6
+ } from "../chunk-W3QHY5GW.js";
7
+ import {
8
+ resolveStorageConfigForRequest
9
+ } from "../chunk-6UIKVKED.js";
10
+ import {
11
+ cachedFetch
12
+ } from "../chunk-45ARVNT3.js";
13
+ import {
14
+ resolveGitHubTokenForRequest
15
+ } from "../chunk-NKDATSPA.js";
16
+
17
+ // src/api-routes/_webhook-dispatcher.ts
18
+ import {
19
+ parseWebhooksFile,
20
+ selectWebhooksForEvent
21
+ } from "@setzkasten-cms/core";
22
+ var WEBHOOKS_FILE = (contentPath) => `${contentPath}/_webhooks.json`;
23
+ var DISPATCH_TIMEOUT_MS = 5e3;
24
+ async function fireWebhooks(event, payload, request) {
25
+ try {
26
+ const webhooks = await loadWebhooksForRequest(request);
27
+ if (!webhooks || webhooks.length === 0) return;
28
+ const targets = selectWebhooksForEvent(webhooks, event);
29
+ if (targets.length === 0) return;
30
+ const fullPayload = {
31
+ event,
32
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
33
+ ...payload
34
+ };
35
+ const body = JSON.stringify(fullPayload);
36
+ await Promise.all(targets.map((w) => fireOne(w, event, body)));
37
+ } catch (err) {
38
+ console.error("[setzkasten] webhook dispatch failed:", err);
39
+ }
40
+ }
41
+ async function fireOne(webhook, event, body) {
42
+ const headers = {
43
+ "Content-Type": "application/json",
44
+ "X-Setzkasten-Event": event,
45
+ "X-Setzkasten-Delivery": crypto.randomUUID()
46
+ };
47
+ if (webhook.secret) {
48
+ headers["X-Setzkasten-Signature"] = `sha256=${signPayload(body, webhook.secret)}`;
49
+ }
50
+ try {
51
+ const res = await fetch(webhook.url, {
52
+ method: "POST",
53
+ headers,
54
+ body,
55
+ signal: AbortSignal.timeout(DISPATCH_TIMEOUT_MS)
56
+ });
57
+ recordWebhookFire(webhook.id, res.status);
58
+ } catch (err) {
59
+ recordWebhookFire(webhook.id, "error");
60
+ console.warn(`[setzkasten] webhook "${webhook.id}" failed:`, err);
61
+ }
62
+ }
63
+ async function loadWebhooksForRequest(request) {
64
+ const tokenResult = await resolveGitHubTokenForRequest(request);
65
+ if (!tokenResult.ok) return null;
66
+ const storage = await resolveStorageConfigForRequest(request);
67
+ if (!storage) return null;
68
+ const serverConfig = globalThis.__SETZKASTEN_CONFIG__;
69
+ const contentPath = serverConfig?.storage?.contentPath ?? "content";
70
+ const { owner, repo, branch } = storage;
71
+ const cacheKey = `webhooks:${owner}/${repo}:${branch}`;
72
+ return cachedFetch(cacheKey, 6e4, async () => {
73
+ const res = await fetch(
74
+ `https://api.github.com/repos/${owner}/${repo}/contents/${WEBHOOKS_FILE(contentPath)}?ref=${branch}`,
75
+ {
76
+ headers: {
77
+ Authorization: `Bearer ${tokenResult.value}`,
78
+ Accept: "application/vnd.github+json",
79
+ "X-GitHub-Api-Version": "2022-11-28"
80
+ }
81
+ }
82
+ );
83
+ if (res.status === 404) return [];
84
+ if (!res.ok) return null;
85
+ const data = await res.json();
86
+ const raw = data.encoding === "base64" ? Buffer.from(data.content, "base64").toString("utf-8") : data.content;
87
+ const parsed = parseWebhooksFile(raw);
88
+ if (!parsed.ok) {
89
+ console.warn("[setzkasten] _webhooks.json parse error:", parsed.error.message);
90
+ return null;
91
+ }
92
+ return parsed.value.webhooks;
93
+ });
94
+ }
95
+ export {
96
+ fireWebhooks
97
+ };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * HMAC-SHA256 signature of the webhook payload body, hex-encoded.
3
+ * Receivers verify with the same secret and the **raw** request body —
4
+ * pattern is identical to GitHub webhooks.
5
+ *
6
+ * Lives in astro-admin (not core) because it imports node:crypto.
7
+ * core's "zero external deps" rule keeps it edge/browser-runnable.
8
+ */
9
+ declare function signPayload(body: string, secret: string): string;
10
+
11
+ export { signPayload };
@@ -0,0 +1,6 @@
1
+ import {
2
+ signPayload
3
+ } from "../chunk-737TIZRU.js";
4
+ export {
5
+ signPayload
6
+ };
@@ -0,0 +1,19 @@
1
+ /**
2
+ * In-memory webhook status — last fired timestamp + last HTTP status per
3
+ * webhook id. Survives only the server process; cold-start losses are
4
+ * acceptable because this is a status display, not the source of truth.
5
+ *
6
+ * Persisting to `_webhooks.json` would create a commit-storm
7
+ * (every save → webhook fire → webhook commit → trigger save → …). The
8
+ * UI just refetches `/webhooks/status` after a test-fire or save.
9
+ */
10
+ interface WebhookStatusEntry {
11
+ readonly lastFiredAt: string;
12
+ readonly lastStatus: number | 'error';
13
+ }
14
+ declare function recordWebhookFire(id: string, status: number | 'error'): void;
15
+ declare function getWebhookStatus(): Record<string, WebhookStatusEntry>;
16
+ /** Test-only — clears the in-memory map. */
17
+ declare function _resetWebhookStatusForTests(): void;
18
+
19
+ export { type WebhookStatusEntry, _resetWebhookStatusForTests, getWebhookStatus, recordWebhookFire };
@@ -0,0 +1,10 @@
1
+ import {
2
+ _resetWebhookStatusForTests,
3
+ getWebhookStatus,
4
+ recordWebhookFire
5
+ } from "../chunk-W3QHY5GW.js";
6
+ export {
7
+ _resetWebhookStatusForTests,
8
+ getWebhookStatus,
9
+ recordWebhookFire
10
+ };
@@ -0,0 +1,49 @@
1
+ import { WebsitesRegistryProvider, WebsiteEntry, Result } from '@setzkasten-cms/core';
2
+
3
+ /**
4
+ * Per-request resolver for the active website.
5
+ *
6
+ * Standalone mode: looks up the entry matching the X-SK-Website request
7
+ * header in the websites registry (config-repo).
8
+ *
9
+ * Single-repo mode (backward compat): returns a synthesized WebsiteEntry
10
+ * built from the integration's build-time storage + GitHub-App ENV vars.
11
+ * The header is ignored.
12
+ */
13
+ interface MultiState {
14
+ readonly mode: 'multi';
15
+ readonly registry: WebsitesRegistryProvider;
16
+ }
17
+ interface SingleRepoState {
18
+ readonly mode: 'single';
19
+ readonly synthesized: WebsiteEntry;
20
+ }
21
+ type ResolverState = MultiState | SingleRepoState | null;
22
+ /** Test/admin hook: install or clear the resolver state. */
23
+ declare function __resetWebsiteResolverForTests(next: ResolverState): void;
24
+ /**
25
+ * Configure the resolver state explicitly. Currently only the test suite
26
+ * uses this — production wiring runs lazily through
27
+ * {@link bootstrapResolverFromGlobals} on the first request, since the
28
+ * Astro integration doesn't have an obvious "initialize once" hook
29
+ * (`astro:server:start` runs in dev only). The export stays available so
30
+ * a future integration-startup wire-up has a typed entry point.
31
+ */
32
+ declare function configureWebsiteResolver(next: ResolverState): void;
33
+ /**
34
+ * Lazy bootstrap from build-time globals. Reads `__SETZKASTEN_FULL_CONFIG__`
35
+ * and `__SETZKASTEN_STORAGE__` (set by the Astro integration via Vite
36
+ * define — literals only, NOT globalThis) to derive single-repo or
37
+ * standalone state. Idempotent — does nothing if the resolver is already
38
+ * configured.
39
+ */
40
+ declare function bootstrapResolverFromGlobals(): void;
41
+ declare function resolveCurrentWebsite(request: Request): Promise<Result<WebsiteEntry>>;
42
+ /**
43
+ * Lists all websites known to the resolver.
44
+ * Standalone mode: defers to the registry. Single-repo mode: returns the
45
+ * synthesized entry as a one-element list.
46
+ */
47
+ declare function listAllWebsites(): Promise<Result<readonly WebsiteEntry[]>>;
48
+
49
+ export { __resetWebsiteResolverForTests, bootstrapResolverFromGlobals, configureWebsiteResolver, listAllWebsites, resolveCurrentWebsite };
@@ -0,0 +1,14 @@
1
+ import {
2
+ __resetWebsiteResolverForTests,
3
+ bootstrapResolverFromGlobals,
4
+ configureWebsiteResolver,
5
+ listAllWebsites,
6
+ resolveCurrentWebsite
7
+ } from "../chunk-V6IMPVF3.js";
8
+ export {
9
+ __resetWebsiteResolverForTests,
10
+ bootstrapResolverFromGlobals,
11
+ configureWebsiteResolver,
12
+ listAllWebsites,
13
+ resolveCurrentWebsite
14
+ };
@@ -0,0 +1,30 @@
1
+ import { Result, WebsitesRegistry } from '@setzkasten-cms/core';
2
+
3
+ /**
4
+ * Read/write the standalone admin's `websites.json` from the config repo.
5
+ * Pure HTTP — no caching here; the GitHub-side cache lives in
6
+ * GitHubWebsitesRegistry, which API routes invalidate explicitly after a
7
+ * successful write.
8
+ */
9
+
10
+ interface ConfigRepoTarget {
11
+ readonly owner: string;
12
+ readonly repo: string;
13
+ readonly branch: string;
14
+ readonly path: string;
15
+ readonly token: string;
16
+ }
17
+ interface ReadResult {
18
+ readonly registry: WebsitesRegistry;
19
+ readonly sha: string | null;
20
+ }
21
+ declare function readWebsitesRegistryFromGitHub(target: ConfigRepoTarget): Promise<Result<ReadResult>>;
22
+ declare function writeWebsitesRegistryToGitHub(target: ConfigRepoTarget, registry: WebsitesRegistry, previousSha: string | null, commitMessage: string): Promise<Result<void>>;
23
+ /**
24
+ * Reads the standalone-admin storage config out of the build-time
25
+ * `__SETZKASTEN_FULL_CONFIG__` global. Returns null when the deployment
26
+ * is not in standalone mode (single-repo setups have no websites.json).
27
+ */
28
+ declare function resolveConfigRepoTargetFromGlobals(token: string): ConfigRepoTarget | null;
29
+
30
+ export { readWebsitesRegistryFromGitHub, resolveConfigRepoTargetFromGlobals, writeWebsitesRegistryToGitHub };