@setzkasten-cms/astro-admin 1.4.2 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/dist/api-routes/_auth-guard.d.ts +27 -3
  2. package/dist/api-routes/_auth-guard.js +5 -2
  3. package/dist/api-routes/_dev-session-secret.d.ts +8 -0
  4. package/dist/api-routes/_dev-session-secret.js +8 -0
  5. package/dist/api-routes/_github-token.js +1 -1
  6. package/dist/api-routes/_role-resolver.js +6 -3
  7. package/dist/api-routes/_session-secret.d.ts +19 -0
  8. package/dist/api-routes/_session-secret.js +7 -0
  9. package/dist/api-routes/_session-signing.d.ts +45 -0
  10. package/dist/api-routes/_session-signing.js +8 -0
  11. package/dist/api-routes/_webhook-dispatcher.js +4 -4
  12. package/dist/api-routes/asset-proxy.js +1 -1
  13. package/dist/api-routes/auth-callback.js +12 -5
  14. package/dist/api-routes/auth-logout.d.ts +4 -4
  15. package/dist/api-routes/auth-logout.js +8 -2
  16. package/dist/api-routes/auth-session.d.ts +6 -0
  17. package/dist/api-routes/auth-session.js +19 -19
  18. package/dist/api-routes/auth-setzkasten-login.js +14 -7
  19. package/dist/api-routes/catalog-add.js +59 -17
  20. package/dist/api-routes/catalog-export.js +14 -4
  21. package/dist/api-routes/config.d.ts +10 -3
  22. package/dist/api-routes/config.js +26 -4
  23. package/dist/api-routes/deploy-hook.js +8 -8
  24. package/dist/api-routes/editors.d.ts +1 -1
  25. package/dist/api-routes/editors.js +5 -2
  26. package/dist/api-routes/github-proxy.js +30 -8
  27. package/dist/api-routes/global-config.js +6 -3
  28. package/dist/api-routes/history-rollback.js +31 -14
  29. package/dist/api-routes/history-version.js +8 -6
  30. package/dist/api-routes/history.js +5 -2
  31. package/dist/api-routes/icons-local.js +1 -1
  32. package/dist/api-routes/init-add-section.js +150 -48
  33. package/dist/api-routes/init-apply.js +56 -42
  34. package/dist/api-routes/init-migrate.js +43 -36
  35. package/dist/api-routes/init-scan-page.d.ts +1 -1
  36. package/dist/api-routes/init-scan-page.js +59 -13
  37. package/dist/api-routes/init-scan.js +22 -7
  38. package/dist/api-routes/migrate-to-multi.js +5 -2
  39. package/dist/api-routes/pages.js +15 -4
  40. package/dist/api-routes/section-add.js +68 -16
  41. package/dist/api-routes/section-commit-pending.js +70 -22
  42. package/dist/api-routes/section-delete.js +49 -14
  43. package/dist/api-routes/section-duplicate.js +65 -16
  44. package/dist/api-routes/section-prepare-copy.js +15 -2
  45. package/dist/api-routes/section-prepare.js +25 -4
  46. package/dist/api-routes/setup-github-app-bounce.js +15 -1
  47. package/dist/api-routes/setup-github-app-branches.js +9 -6
  48. package/dist/api-routes/setup-github-app-callback.js +24 -1
  49. package/dist/api-routes/setup-github-app-credentials.d.ts +27 -0
  50. package/dist/api-routes/setup-github-app-credentials.js +43 -0
  51. package/dist/api-routes/setup-github-app-installed.js +22 -1
  52. package/dist/api-routes/setup-github-app-repos.js +5 -2
  53. package/dist/api-routes/setup-github-app.d.ts +4 -0
  54. package/dist/api-routes/setup-github-app.js +19 -2
  55. package/dist/api-routes/updater-register.js +7 -1
  56. package/dist/api-routes/webhooks-status.js +5 -2
  57. package/dist/api-routes/webhooks-test.js +9 -8
  58. package/dist/api-routes/webhooks.js +12 -14
  59. package/dist/api-routes/websites-add.js +5 -2
  60. package/dist/api-routes/websites-remove.js +5 -2
  61. package/dist/{chunk-ZQDGGWJP.js → chunk-5KMGSFCZ.js} +2 -2
  62. package/dist/{chunk-RHJONMLK.js → chunk-CDXCYYQR.js} +222 -5
  63. package/dist/{chunk-NKDATSPA.js → chunk-DP6RTINQ.js} +1 -1
  64. package/dist/chunk-KENFINT4.js +76 -0
  65. package/dist/chunk-ONP6BRZO.js +47 -0
  66. package/dist/{chunk-INIWFKQ3.js → chunk-Q5HV47DW.js} +33 -19
  67. package/dist/chunk-QVCW6EF3.js +26 -0
  68. package/dist/{chunk-K22A4ZBS.js → chunk-UHI6323G.js} +293 -174
  69. package/dist/{chunk-AM4DZXXM.js → chunk-UJAFZEX2.js} +76 -9
  70. package/package.json +12 -6
  71. package/src/api-routes/__tests__/_session-signing.test.ts +114 -0
  72. package/src/api-routes/__tests__/_session-test-helper.ts +27 -0
  73. package/src/api-routes/__tests__/add-section-helpers.test.ts +91 -97
  74. package/src/api-routes/__tests__/auth-guard.test.ts +46 -7
  75. package/src/api-routes/__tests__/catalog-api.test.ts +14 -6
  76. package/src/api-routes/__tests__/commit-trailers.test.ts +5 -5
  77. package/src/api-routes/__tests__/deferred-operations.test.ts +9 -12
  78. package/src/api-routes/__tests__/deploy-hook.test.ts +3 -8
  79. package/src/api-routes/__tests__/feature-gate.test.ts +1 -1
  80. package/src/api-routes/__tests__/github-cache.test.ts +1 -1
  81. package/src/api-routes/__tests__/github-token.test.ts +1 -1
  82. package/src/api-routes/__tests__/global-config-theme.test.ts +4 -4
  83. package/src/api-routes/__tests__/history-rollback.test.ts +6 -3
  84. package/src/api-routes/__tests__/history.test.ts +9 -6
  85. package/src/api-routes/__tests__/init-scan-page-resolve-config.test.ts +11 -3
  86. package/src/api-routes/__tests__/migrate-to-multi.test.ts +5 -1
  87. package/src/api-routes/__tests__/pages-meta-store.test.ts +10 -5
  88. package/src/api-routes/__tests__/pages.test.ts +7 -2
  89. package/src/api-routes/__tests__/patch-page-file.test.ts +71 -19
  90. package/src/api-routes/__tests__/route-registry.test.ts +11 -18
  91. package/src/api-routes/__tests__/scan-page-helpers.test.ts +13 -10
  92. package/src/api-routes/__tests__/section-management.test.ts +28 -28
  93. package/src/api-routes/__tests__/setup-github-app-callback.test.ts +58 -16
  94. package/src/api-routes/__tests__/setup-github-app-repos.test.ts +4 -5
  95. package/src/api-routes/__tests__/setup-github-app.test.ts +27 -7
  96. package/src/api-routes/__tests__/storage-config-for-request.test.ts +83 -0
  97. package/src/api-routes/__tests__/updater-register.test.ts +230 -0
  98. package/src/api-routes/__tests__/webhook-signing.test.ts +1 -1
  99. package/src/api-routes/__tests__/webhooks.test.ts +19 -7
  100. package/src/api-routes/__tests__/websites-add.test.ts +2 -1
  101. package/src/api-routes/__tests__/websites-remove.test.ts +2 -1
  102. package/src/api-routes/_auth-guard.ts +47 -15
  103. package/src/api-routes/_commit-trailers.ts +3 -2
  104. package/src/api-routes/_dev-session-secret.ts +79 -0
  105. package/src/api-routes/_github-token.ts +1 -1
  106. package/src/api-routes/_pages-meta-store.ts +2 -2
  107. package/src/api-routes/_role-resolver.ts +7 -5
  108. package/src/api-routes/_session-secret.ts +46 -0
  109. package/src/api-routes/_session-signing.ts +135 -0
  110. package/src/api-routes/_vercel-origin.ts +2 -6
  111. package/src/api-routes/_webhook-dispatcher.ts +12 -16
  112. package/src/api-routes/_website-resolver.ts +3 -10
  113. package/src/api-routes/auth-callback.ts +9 -5
  114. package/src/api-routes/auth-login.ts +5 -3
  115. package/src/api-routes/auth-logout.ts +18 -1
  116. package/src/api-routes/auth-session.ts +13 -21
  117. package/src/api-routes/auth-setzkasten-login.ts +12 -9
  118. package/src/api-routes/catalog-add.ts +89 -31
  119. package/src/api-routes/catalog-export.ts +30 -10
  120. package/src/api-routes/config.ts +39 -6
  121. package/src/api-routes/deploy-hook.ts +13 -11
  122. package/src/api-routes/editors.ts +33 -22
  123. package/src/api-routes/github-proxy.ts +25 -11
  124. package/src/api-routes/global-config.ts +103 -18
  125. package/src/api-routes/history-rollback.ts +41 -14
  126. package/src/api-routes/history-version.ts +5 -6
  127. package/src/api-routes/history.ts +3 -3
  128. package/src/api-routes/icons-local.ts +2 -2
  129. package/src/api-routes/init-add-section.ts +218 -88
  130. package/src/api-routes/init-apply.ts +71 -56
  131. package/src/api-routes/init-migrate.ts +54 -48
  132. package/src/api-routes/init-scan-page.ts +77 -30
  133. package/src/api-routes/init-scan.ts +19 -11
  134. package/src/api-routes/pages.ts +16 -11
  135. package/src/api-routes/section-add.ts +98 -27
  136. package/src/api-routes/section-commit-pending.ts +87 -34
  137. package/src/api-routes/section-delete.ts +76 -27
  138. package/src/api-routes/section-duplicate.ts +95 -28
  139. package/src/api-routes/section-management.ts +3 -7
  140. package/src/api-routes/section-prepare-copy.ts +29 -8
  141. package/src/api-routes/section-prepare.ts +38 -10
  142. package/src/api-routes/setup-github-app-bounce.ts +7 -1
  143. package/src/api-routes/setup-github-app-branches.ts +6 -7
  144. package/src/api-routes/setup-github-app-callback.ts +18 -1
  145. package/src/api-routes/setup-github-app-credentials.ts +55 -0
  146. package/src/api-routes/setup-github-app-installed.ts +12 -1
  147. package/src/api-routes/setup-github-app-repos.ts +2 -3
  148. package/src/api-routes/setup-github-app.ts +14 -5
  149. package/src/api-routes/updater-check.ts +6 -4
  150. package/src/api-routes/updater-register.ts +34 -20
  151. package/src/api-routes/updater-transfer.ts +8 -6
  152. package/src/api-routes/updater-unbind.ts +14 -10
  153. package/src/api-routes/webhooks-test.ts +9 -11
  154. package/src/api-routes/webhooks.ts +15 -19
  155. package/src/init/__tests__/page-level.test.ts +279 -105
  156. package/src/init/__tests__/page-list-coverage.test.ts +70 -70
  157. package/src/init/__tests__/patcher-child-component.test.ts +126 -0
  158. package/src/init/__tests__/patcher-edge-cases.test.ts +47 -23
  159. package/src/init/__tests__/patcher-mixed-content-wrapper.test.ts +16 -6
  160. package/src/init/__tests__/patcher-page-mode.test.ts +30 -20
  161. package/src/init/__tests__/section-pipeline.test.ts +102 -16
  162. package/src/init/astro-config-patcher.ts +4 -18
  163. package/src/init/astro-detector.ts +2 -7
  164. package/src/init/astro-section-analyzer-v2.ts +475 -193
  165. package/src/init/field-label-enricher.ts +6 -6
  166. package/src/init/template-patcher-v2.ts +490 -56
@@ -0,0 +1,43 @@
1
+ import {
2
+ requireAdmin
3
+ } from "../chunk-Q5HV47DW.js";
4
+ import "../chunk-QVCW6EF3.js";
5
+ import "../chunk-KENFINT4.js";
6
+ import "../chunk-6UIKVKED.js";
7
+ import "../chunk-ONP6BRZO.js";
8
+ import "../chunk-5PIMDP4N.js";
9
+ import "../chunk-45ARVNT3.js";
10
+ import "../chunk-DP6RTINQ.js";
11
+ import "../chunk-TJNJKPUL.js";
12
+ import "../chunk-KH22FJO5.js";
13
+
14
+ // src/api-routes/setup-github-app-credentials.ts
15
+ var COOKIE_NAME = "sk_app_setup";
16
+ var GET = async ({ cookies }) => {
17
+ const denied = requireAdmin(cookies.get("setzkasten_session")?.value);
18
+ if (denied) return denied;
19
+ const raw = cookies.get(COOKIE_NAME)?.value;
20
+ if (!raw) {
21
+ return Response.json({ available: false }, { status: 404 });
22
+ }
23
+ let parsed;
24
+ try {
25
+ parsed = JSON.parse(raw);
26
+ } catch {
27
+ return Response.json({ available: false, error: "malformed" }, { status: 500 });
28
+ }
29
+ if (!parsed || typeof parsed !== "object") {
30
+ return Response.json({ available: false }, { status: 404 });
31
+ }
32
+ return Response.json({ available: true, credentials: parsed });
33
+ };
34
+ var DELETE = async ({ cookies }) => {
35
+ const denied = requireAdmin(cookies.get("setzkasten_session")?.value);
36
+ if (denied) return denied;
37
+ cookies.delete(COOKIE_NAME, { path: "/" });
38
+ return Response.json({ ok: true });
39
+ };
40
+ export {
41
+ DELETE,
42
+ GET
43
+ };
@@ -1,11 +1,25 @@
1
1
  import {
2
2
  getPublicOrigin
3
3
  } from "../chunk-5ZFTG4BW.js";
4
+ import {
5
+ requireAdmin
6
+ } from "../chunk-Q5HV47DW.js";
7
+ import "../chunk-QVCW6EF3.js";
8
+ import "../chunk-KENFINT4.js";
9
+ import "../chunk-6UIKVKED.js";
10
+ import "../chunk-ONP6BRZO.js";
11
+ import "../chunk-5PIMDP4N.js";
12
+ import "../chunk-45ARVNT3.js";
13
+ import "../chunk-DP6RTINQ.js";
14
+ import "../chunk-TJNJKPUL.js";
15
+ import "../chunk-KH22FJO5.js";
4
16
 
5
17
  // src/api-routes/setup-github-app-installed.ts
6
18
  var COOKIE_NAME = "sk_app_setup";
7
19
  var COOKIE_MAX_AGE = 600;
8
20
  var GET = async ({ url, request, cookies }) => {
21
+ const denied = requireAdmin(cookies.get("setzkasten_session")?.value);
22
+ if (denied) return denied;
9
23
  const config = globalThis.__SETZKASTEN_CONFIG__;
10
24
  const adminPath = config?.adminPath ?? "/admin";
11
25
  const adminUrl = new URL(adminPath, getPublicOrigin(request));
@@ -20,7 +34,14 @@ var GET = async ({ url, request, cookies }) => {
20
34
  cookies.set(
21
35
  COOKIE_NAME,
22
36
  JSON.stringify({ ...data, installationId }),
23
- { httpOnly: false, sameSite: "lax", maxAge: COOKIE_MAX_AGE, path: "/" }
37
+ // C6: same httpOnly hardening as the callback route.
38
+ {
39
+ httpOnly: true,
40
+ secure: import.meta.env.PROD,
41
+ sameSite: "lax",
42
+ maxAge: COOKIE_MAX_AGE,
43
+ path: "/"
44
+ }
24
45
  );
25
46
  } catch {
26
47
  adminUrl.searchParams.set("github-app-error", "invalid_session");
@@ -1,10 +1,13 @@
1
1
  import {
2
2
  requireAdmin
3
- } from "../chunk-INIWFKQ3.js";
3
+ } from "../chunk-Q5HV47DW.js";
4
+ import "../chunk-QVCW6EF3.js";
5
+ import "../chunk-KENFINT4.js";
4
6
  import "../chunk-6UIKVKED.js";
7
+ import "../chunk-ONP6BRZO.js";
5
8
  import "../chunk-5PIMDP4N.js";
6
9
  import "../chunk-45ARVNT3.js";
7
- import "../chunk-NKDATSPA.js";
10
+ import "../chunk-DP6RTINQ.js";
8
11
  import "../chunk-TJNJKPUL.js";
9
12
  import "../chunk-KH22FJO5.js";
10
13
 
@@ -8,6 +8,10 @@ import { APIRoute } from 'astro';
8
8
  *
9
9
  * Credentials werden NICHT persistiert – der Nutzer setzt die env vars manuell.
10
10
  * Der POST-Endpunkt validiert die Verbindung durch einen echten Token-Request.
11
+ *
12
+ * Both methods are admin-only. Pre-fix the GET let any unauthenticated
13
+ * visitor probe whether a deployment was configured (reconnaissance for
14
+ * targeting half-set-up instances).
11
15
  */
12
16
  declare const GET: APIRoute;
13
17
  declare const POST: APIRoute;
@@ -1,13 +1,30 @@
1
+ import {
2
+ requireAdmin
3
+ } from "../chunk-Q5HV47DW.js";
4
+ import "../chunk-QVCW6EF3.js";
5
+ import "../chunk-KENFINT4.js";
6
+ import "../chunk-6UIKVKED.js";
7
+ import "../chunk-ONP6BRZO.js";
8
+ import "../chunk-5PIMDP4N.js";
9
+ import "../chunk-45ARVNT3.js";
10
+ import "../chunk-DP6RTINQ.js";
11
+ import "../chunk-TJNJKPUL.js";
12
+ import "../chunk-KH22FJO5.js";
13
+
1
14
  // src/api-routes/setup-github-app.ts
2
15
  import { GitHubAppClient } from "@setzkasten-cms/github-adapter";
3
- var GET = async () => {
16
+ var GET = async ({ cookies }) => {
17
+ const denied = requireAdmin(cookies.get("setzkasten_session")?.value);
18
+ if (denied) return denied;
4
19
  const appId = process.env.GITHUB_APP_ID;
5
20
  const privateKey = process.env.GITHUB_APP_PRIVATE_KEY;
6
21
  const installationId = process.env.GITHUB_APP_INSTALLATION_ID;
7
22
  const configured = Boolean(appId && privateKey && installationId);
8
23
  return Response.json({ configured, ...configured ? { appId } : {} });
9
24
  };
10
- var POST = async ({ request }) => {
25
+ var POST = async ({ request, cookies }) => {
26
+ const denied = requireAdmin(cookies.get("setzkasten_session")?.value);
27
+ if (denied) return denied;
11
28
  let body;
12
29
  try {
13
30
  body = await request.json();
@@ -1,3 +1,7 @@
1
+ import {
2
+ listAllWebsites
3
+ } from "../chunk-V6IMPVF3.js";
4
+
1
5
  // src/api-routes/updater-register.ts
2
6
  var POST = async ({ cookies, request }) => {
3
7
  const session = cookies.get("setzkasten_session")?.value;
@@ -34,6 +38,8 @@ var POST = async ({ cookies, request }) => {
34
38
  }
35
39
  } catch {
36
40
  }
41
+ const websitesResult = await listAllWebsites();
42
+ const managedWebsites = websitesResult.ok ? websitesResult.value.map((w) => w.id) : [];
37
43
  try {
38
44
  const response = await fetch(`${updaterUrl}/api/register`, {
39
45
  method: "POST",
@@ -45,7 +51,7 @@ var POST = async ({ cookies, request }) => {
45
51
  licenseEmail,
46
52
  licenseKey,
47
53
  telemetryEnabled: true,
48
- managedWebsites: []
54
+ managedWebsites
49
55
  }),
50
56
  signal: AbortSignal.timeout(5e3)
51
57
  });
@@ -3,11 +3,14 @@ import {
3
3
  } from "../chunk-W3QHY5GW.js";
4
4
  import {
5
5
  requireAdmin
6
- } from "../chunk-INIWFKQ3.js";
6
+ } from "../chunk-Q5HV47DW.js";
7
+ import "../chunk-QVCW6EF3.js";
8
+ import "../chunk-KENFINT4.js";
7
9
  import "../chunk-6UIKVKED.js";
10
+ import "../chunk-ONP6BRZO.js";
8
11
  import "../chunk-5PIMDP4N.js";
9
12
  import "../chunk-45ARVNT3.js";
10
- import "../chunk-NKDATSPA.js";
13
+ import "../chunk-DP6RTINQ.js";
11
14
  import "../chunk-TJNJKPUL.js";
12
15
  import "../chunk-KH22FJO5.js";
13
16
 
@@ -1,30 +1,31 @@
1
- import {
2
- signPayload
3
- } from "../chunk-737TIZRU.js";
4
1
  import {
5
2
  recordWebhookFire
6
3
  } from "../chunk-W3QHY5GW.js";
4
+ import {
5
+ signPayload
6
+ } from "../chunk-737TIZRU.js";
7
7
  import {
8
8
  parseSession,
9
9
  requireAdmin
10
- } from "../chunk-INIWFKQ3.js";
10
+ } from "../chunk-Q5HV47DW.js";
11
+ import "../chunk-QVCW6EF3.js";
12
+ import "../chunk-KENFINT4.js";
11
13
  import {
12
14
  resolveStorageConfigForRequest
13
15
  } from "../chunk-6UIKVKED.js";
16
+ import "../chunk-ONP6BRZO.js";
14
17
  import {
15
18
  gateFeature
16
19
  } from "../chunk-5PIMDP4N.js";
17
20
  import "../chunk-45ARVNT3.js";
18
21
  import {
19
22
  resolveGitHubTokenForRequest
20
- } from "../chunk-NKDATSPA.js";
23
+ } from "../chunk-DP6RTINQ.js";
21
24
  import "../chunk-TJNJKPUL.js";
22
25
  import "../chunk-KH22FJO5.js";
23
26
 
24
27
  // src/api-routes/webhooks-test.ts
25
- import {
26
- parseWebhooksFile
27
- } from "@setzkasten-cms/core";
28
+ import { parseWebhooksFile } from "@setzkasten-cms/core";
28
29
  var WEBHOOKS_FILE = (contentPath) => `${contentPath}/_webhooks.json`;
29
30
  var TEST_TIMEOUT_MS = 1e4;
30
31
  var POST = async ({ request, cookies }) => {
@@ -1,10 +1,13 @@
1
1
  import {
2
2
  parseSession,
3
3
  requireAdmin
4
- } from "../chunk-INIWFKQ3.js";
4
+ } from "../chunk-Q5HV47DW.js";
5
+ import "../chunk-QVCW6EF3.js";
6
+ import "../chunk-KENFINT4.js";
5
7
  import {
6
8
  resolveStorageConfigForRequest
7
9
  } from "../chunk-6UIKVKED.js";
10
+ import "../chunk-ONP6BRZO.js";
8
11
  import {
9
12
  gateFeature
10
13
  } from "../chunk-5PIMDP4N.js";
@@ -14,17 +17,14 @@ import {
14
17
  } from "../chunk-45ARVNT3.js";
15
18
  import {
16
19
  resolveGitHubTokenForRequest
17
- } from "../chunk-NKDATSPA.js";
20
+ } from "../chunk-DP6RTINQ.js";
18
21
  import "../chunk-TJNJKPUL.js";
19
22
  import {
20
23
  withTrailers
21
24
  } from "../chunk-KH22FJO5.js";
22
25
 
23
26
  // src/api-routes/webhooks.ts
24
- import {
25
- parseWebhooksFile,
26
- validateWebhookConfig
27
- } from "@setzkasten-cms/core";
27
+ import { parseWebhooksFile, validateWebhookConfig } from "@setzkasten-cms/core";
28
28
  var WEBHOOKS_FILE = (contentPath) => `${contentPath}/_webhooks.json`;
29
29
  var GET = async ({ request, cookies }) => {
30
30
  const denied = requireAdmin(cookies.get("setzkasten_session")?.value);
@@ -82,10 +82,7 @@ var PUT = async ({ request, cookies }) => {
82
82
  for (let i = 0; i < body.webhooks.length; i++) {
83
83
  const result = validateWebhookConfig(body.webhooks[i]);
84
84
  if (!result.ok) {
85
- return Response.json(
86
- { error: result.error.message, index: i },
87
- { status: 400 }
88
- );
85
+ return Response.json({ error: result.error.message, index: i }, { status: 400 });
89
86
  }
90
87
  if (seenIds.has(result.value.id)) {
91
88
  return Response.json(
@@ -127,10 +124,11 @@ var PUT = async ({ request, cookies }) => {
127
124
  branch
128
125
  };
129
126
  if (currentSha) putBody.sha = currentSha;
130
- const putRes = await fetch(
131
- `https://api.github.com/repos/${owner}/${repo}/contents/${filePath}`,
132
- { method: "PUT", headers, body: JSON.stringify(putBody) }
133
- );
127
+ const putRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/contents/${filePath}`, {
128
+ method: "PUT",
129
+ headers,
130
+ body: JSON.stringify(putBody)
131
+ });
134
132
  if (!putRes.ok) {
135
133
  const text = await putRes.text();
136
134
  return Response.json({ error: `Webhook write failed: ${text}` }, { status: 502 });
@@ -5,13 +5,16 @@ import {
5
5
  } from "../chunk-35S35OIV.js";
6
6
  import {
7
7
  requireAdmin
8
- } from "../chunk-INIWFKQ3.js";
8
+ } from "../chunk-Q5HV47DW.js";
9
+ import "../chunk-QVCW6EF3.js";
10
+ import "../chunk-KENFINT4.js";
9
11
  import "../chunk-6UIKVKED.js";
12
+ import "../chunk-ONP6BRZO.js";
10
13
  import "../chunk-5PIMDP4N.js";
11
14
  import "../chunk-45ARVNT3.js";
12
15
  import {
13
16
  resolveConfigRepoToken
14
- } from "../chunk-NKDATSPA.js";
17
+ } from "../chunk-DP6RTINQ.js";
15
18
  import {
16
19
  resolveLicenseTier
17
20
  } from "../chunk-TJNJKPUL.js";
@@ -5,13 +5,16 @@ import {
5
5
  } from "../chunk-35S35OIV.js";
6
6
  import {
7
7
  requireAdmin
8
- } from "../chunk-INIWFKQ3.js";
8
+ } from "../chunk-Q5HV47DW.js";
9
+ import "../chunk-QVCW6EF3.js";
10
+ import "../chunk-KENFINT4.js";
9
11
  import "../chunk-6UIKVKED.js";
12
+ import "../chunk-ONP6BRZO.js";
10
13
  import "../chunk-5PIMDP4N.js";
11
14
  import "../chunk-45ARVNT3.js";
12
15
  import {
13
16
  resolveConfigRepoToken
14
- } from "../chunk-NKDATSPA.js";
17
+ } from "../chunk-DP6RTINQ.js";
15
18
  import "../chunk-TJNJKPUL.js";
16
19
  import "../chunk-KH22FJO5.js";
17
20
 
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  readEditorsFileStatus
3
- } from "./chunk-INIWFKQ3.js";
3
+ } from "./chunk-Q5HV47DW.js";
4
4
  import {
5
5
  resolveStorageConfig
6
6
  } from "./chunk-6UIKVKED.js";
7
7
  import {
8
8
  resolveConfigRepoToken
9
- } from "./chunk-NKDATSPA.js";
9
+ } from "./chunk-DP6RTINQ.js";
10
10
 
11
11
  // src/api-routes/_role-resolver.ts
12
12
  import { resolveRoleForUser } from "@setzkasten-cms/core";
@@ -9,7 +9,8 @@ function findNormalizedText(haystack, needle, startFrom) {
9
9
  const escaped = normalized.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/ /g, "\\s+");
10
10
  const regex = new RegExp(escaped);
11
11
  const match = regex.exec(haystack.slice(startFrom));
12
- if (match) return { start: startFrom + match.index, end: startFrom + match.index + match[0].length };
12
+ if (match)
13
+ return { start: startFrom + match.index, end: startFrom + match.index + match[0].length };
13
14
  return null;
14
15
  }
15
16
  async function patchTemplateForFields(source, sectionKey, fields, repeatedGroups = [], options) {
@@ -64,7 +65,13 @@ const { data: ${varName} } = Astro.props
64
65
  }
65
66
  }
66
67
  }
67
- const patched2 = await patchTemplateForFields(modifiedSource, sectionKey, fields, repeatedGroups, options);
68
+ const patched2 = await patchTemplateForFields(
69
+ modifiedSource,
70
+ sectionKey,
71
+ fields,
72
+ repeatedGroups,
73
+ options
74
+ );
68
75
  return removeOldVarDeclarations(patched2, fields, repeatedGroups, varName);
69
76
  }
70
77
  const existingBindings = /* @__PURE__ */ new Set();
@@ -155,6 +162,9 @@ const { data: ${varName} } = Astro.props
155
162
  if (edits.length === editsBefore2) {
156
163
  patchStaticListField(source, sectionKey, field, varName, ast, edits);
157
164
  }
165
+ if (edits.length === editsBefore2) {
166
+ patchRepeatedComponentInstances(source, sectionKey, field, varName, ast, edits);
167
+ }
158
168
  continue;
159
169
  }
160
170
  const cmsExpr = cmsExpressions.find((e) => e.fieldKey === field.key);
@@ -576,7 +586,7 @@ function patchArrayField(source, sectionKey, field, varName, mapExpressions, edi
576
586
  insert: `(${varName}?.${field.key} ?? [])`
577
587
  });
578
588
  } else {
579
- const varMapRegex = new RegExp(`(\\w+)\\.map\\s*\\(`);
589
+ const varMapRegex = /(\w+)\.map\s*\(/;
580
590
  const varMapMatch = exprSource.match(varMapRegex);
581
591
  if (varMapMatch) {
582
592
  const varRef = varMapMatch[1];
@@ -799,7 +809,7 @@ function collectDynamicClassEdits(innerEdits, source, instances, tmpl, group) {
799
809
  if (!tmplPos) continue;
800
810
  const fieldTag = field.tag;
801
811
  let bestAttrIdx = -1;
802
- let bestDist = Infinity;
812
+ let bestDist = Number.POSITIVE_INFINITY;
803
813
  for (let ai = 0; ai < tmplAttrs.length; ai++) {
804
814
  const pathParts = tmplAttrs[ai].path.split("/");
805
815
  const leafTag = (pathParts[pathParts.length - 1] ?? "").replace(/:\d+$/, "");
@@ -856,6 +866,111 @@ function collectDynamicClassEdits(innerEdits, source, instances, tmpl, group) {
856
866
  });
857
867
  }
858
868
  }
869
+ function patchRepeatedComponentInstances(source, sectionKey, field, varName, ast, edits) {
870
+ const compName = field.options?.sourceComponent;
871
+ if (!compName) return;
872
+ const instanceNodes = [];
873
+ walkAst(ast, (node) => {
874
+ if (node.type === "component" && node.name === compName) {
875
+ instanceNodes.push(node);
876
+ }
877
+ });
878
+ if (instanceNodes.length < 2) return;
879
+ const ranges = [];
880
+ for (const node of instanceNodes) {
881
+ const approxStart = node.position?.start?.offset;
882
+ if (approxStart == null) return;
883
+ const searchFrom = Math.max(0, approxStart - 2);
884
+ const tagStart = source.indexOf(`<${compName}`, searchFrom);
885
+ if (tagStart === -1 || tagStart > approxStart + 2) return;
886
+ const tagEnd = findComponentTagEnd(source, tagStart, compName);
887
+ if (tagEnd === -1) return;
888
+ ranges.push({ start: tagStart, end: tagEnd, attrs: node.attributes ?? [] });
889
+ }
890
+ if (ranges.length < 2) return;
891
+ const defaultItems = Array.isArray(field.defaultValue) ? field.defaultValue : [];
892
+ const defaultJson = JSON.stringify(defaultItems);
893
+ const innerFieldKeys = new Set(
894
+ (field.options?.arrayItem?.fields ?? []).map((f) => f.key)
895
+ );
896
+ const skipPropRegex = /^(class|className|id|style|type|role|aria-|data-)$/;
897
+ const firstAttrs = ranges[0].attrs;
898
+ const propLines = [];
899
+ for (const attr of firstAttrs) {
900
+ const name = attr.name;
901
+ if (skipPropRegex.test(name) || name.startsWith("aria-") || name.startsWith("data-")) {
902
+ const raw = attr.kind === "quoted" ? `${name}="${attr.value}"` : `${name}={${attr.value}}`;
903
+ propLines.push(raw);
904
+ continue;
905
+ }
906
+ if (innerFieldKeys.has(name)) {
907
+ propLines.push(`${name}={item.${name}}`);
908
+ } else {
909
+ const raw = attr.kind === "quoted" ? `${name}="${attr.value}"` : `${name}={${attr.value}}`;
910
+ propLines.push(raw);
911
+ }
912
+ }
913
+ propLines.push(`fieldPrefix={\`${sectionKey}.${field.key}.\${_i}\`}`);
914
+ const propsStr = propLines.map((p) => ` ${p}`).join("\n");
915
+ const mapExpr = `{(${varName}?.${field.key} ?? ${defaultJson}).map((item, _i) => (
916
+ <${compName}
917
+ ${propsStr}
918
+ />
919
+ ))}`;
920
+ edits.push({
921
+ offset: ranges[0].start,
922
+ deleteCount: ranges[0].end - ranges[0].start,
923
+ insert: mapExpr
924
+ });
925
+ for (let i = 1; i < ranges.length; i++) {
926
+ const r = ranges[i];
927
+ let deleteStart = r.start;
928
+ while (deleteStart > 0 && /[ \t]/.test(source[deleteStart - 1])) deleteStart--;
929
+ if (deleteStart > 0 && source[deleteStart - 1] === "\n") deleteStart--;
930
+ edits.push({ offset: deleteStart, deleteCount: r.end - deleteStart, insert: "" });
931
+ }
932
+ }
933
+ function findComponentTagEnd(source, tagStart, compName) {
934
+ let i = tagStart + 1;
935
+ let inQuote = null;
936
+ let inExpr = 0;
937
+ while (i < source.length) {
938
+ const ch = source[i];
939
+ if (inQuote) {
940
+ if (ch === inQuote && source[i - 1] !== "\\") inQuote = null;
941
+ i++;
942
+ continue;
943
+ }
944
+ if (ch === "{") {
945
+ inExpr++;
946
+ i++;
947
+ continue;
948
+ }
949
+ if (ch === "}" && inExpr > 0) {
950
+ inExpr--;
951
+ i++;
952
+ continue;
953
+ }
954
+ if (inExpr > 0) {
955
+ i++;
956
+ continue;
957
+ }
958
+ if (ch === '"' || ch === "'") {
959
+ inQuote = ch;
960
+ i++;
961
+ continue;
962
+ }
963
+ if (ch === "/" && source[i + 1] === ">") return i + 2;
964
+ if (ch === ">") {
965
+ const closing = `</${compName}>`;
966
+ const closingIdx = source.indexOf(closing, i + 1);
967
+ if (closingIdx !== -1) return closingIdx + closing.length;
968
+ return i + 1;
969
+ }
970
+ i++;
971
+ }
972
+ return -1;
973
+ }
859
974
  function patchRepeatedGroups(source, sectionKey, varName, groups) {
860
975
  const edits = [];
861
976
  for (const group of groups) {
@@ -983,7 +1098,7 @@ ${indent}))}`;
983
1098
  for (let i = instances.length - 1; i >= 1; i--) {
984
1099
  const inst = instances[i];
985
1100
  let deleteStart = inst.start;
986
- let deleteEnd = inst.end;
1101
+ const deleteEnd = inst.end;
987
1102
  while (deleteStart > 0 && /\s/.test(source[deleteStart - 1]) && source[deleteStart - 1] !== "\n") {
988
1103
  deleteStart--;
989
1104
  }
@@ -1173,6 +1288,15 @@ function removeOldVarDeclarations(source, fields, repeatedGroups, cmsVarName = "
1173
1288
  }
1174
1289
  }
1175
1290
  }
1291
+ const templatePart0 = source.slice(fmEnd + 3);
1292
+ const fmVarRegex2 = /(?:const|let)\s+(\w+)\s*=\s*\[/g;
1293
+ let m;
1294
+ while ((m = fmVarRegex2.exec(frontmatter)) !== null) {
1295
+ const vName = m[1];
1296
+ if (fieldKeys.has(vName)) continue;
1297
+ const stillUsed = new RegExp(`\\b${vName}\\b`).test(templatePart0);
1298
+ if (!stillUsed) fieldKeys.add(vName);
1299
+ }
1176
1300
  const removals = [];
1177
1301
  const aliases = [];
1178
1302
  const templatePart = source.slice(fmEnd + 3);
@@ -1233,6 +1357,97 @@ function removeOldVarDeclarations(source, fields, repeatedGroups, cmsVarName = "
1233
1357
  frontmatter = frontmatter.replace(/\n{3,}/g, "\n\n");
1234
1358
  return source.slice(0, fmStart + 4) + frontmatter + source.slice(fmEnd);
1235
1359
  }
1360
+ function patchChildComponentForFieldPrefix(source, innerFields) {
1361
+ if (source.includes("fieldPrefix?: string") || source.includes("fieldPrefix?:string"))
1362
+ return source;
1363
+ const fmStart = source.indexOf("---");
1364
+ const fmEnd = source.indexOf("---", fmStart + 3);
1365
+ if (fmStart === -1 || fmEnd === -1) return source;
1366
+ let result = source;
1367
+ const interfaceMatch = result.match(/export\s+interface\s+Props\s*\{([\s\S]*?)\}/);
1368
+ if (interfaceMatch) {
1369
+ const ifaceEnd = result.indexOf(interfaceMatch[0]) + interfaceMatch[0].length;
1370
+ const lastPropLine = interfaceMatch[0].slice(0, -1).trimEnd();
1371
+ result = result.slice(0, ifaceEnd - 1) + "\n fieldPrefix?: string;\n}" + result.slice(ifaceEnd);
1372
+ } else {
1373
+ const closeFm = result.indexOf("---", result.indexOf("---") + 3);
1374
+ result = result.slice(0, closeFm) + "interface Props { fieldPrefix?: string }\n" + result.slice(closeFm);
1375
+ }
1376
+ result = result.replace(/(\bconst\s*\{[^}]*)(}\s*=\s*Astro\.props)/, (_m, before, after) => {
1377
+ if (before.includes("fieldPrefix")) return _m;
1378
+ const trimmed = before.trimEnd();
1379
+ const sep = trimmed.endsWith(",") ? " " : ",\n ";
1380
+ return `${trimmed}${sep}fieldPrefix${after}`;
1381
+ });
1382
+ const closingFm = result.indexOf("---", result.indexOf("---") + 3);
1383
+ const frontmatterPart = result.slice(0, closingFm + 3);
1384
+ let templatePart = result.slice(closingFm + 3);
1385
+ const scalarFields = innerFields.filter((f) => f.type !== "array");
1386
+ const arrayFields = innerFields.filter((f) => f.type === "array");
1387
+ for (const field of scalarFields) {
1388
+ const propExpr = `{${field.key}}`;
1389
+ if (field.key.toLowerCase().includes("href") || field.key.toLowerCase().includes("link")) {
1390
+ templatePart = templatePart.replace(
1391
+ new RegExp(`href=\\{${field.key}\\}`, "g"),
1392
+ `href={${field.key}} data-sk-field={fieldPrefix ? \`\${fieldPrefix}.${field.key}\` : undefined}`
1393
+ );
1394
+ continue;
1395
+ }
1396
+ const tagWithExprRegex = new RegExp(
1397
+ `(<(?!/)(?:p|span|h[1-6]|div|li|td|th|dt|dd|label|button|a)\\b[^>]*?)(\\/?>)([^<]*\\{${field.key}\\})`,
1398
+ "g"
1399
+ );
1400
+ templatePart = templatePart.replace(tagWithExprRegex, (_m, tagOpen, tagClose, rest) => {
1401
+ if (tagOpen.includes("data-sk-field")) return _m;
1402
+ const attr = ` data-sk-field={fieldPrefix ? \`\${fieldPrefix}.${field.key}\` : undefined}`;
1403
+ return `${tagOpen}${attr}${tagClose}${rest}`;
1404
+ });
1405
+ }
1406
+ for (const field of arrayFields) {
1407
+ const mapParamRegex = new RegExp(`(${field.key}\\.map\\s*\\(\\s*\\(\\s*)(\\w+)(\\s*\\))`, "g");
1408
+ templatePart = templatePart.replace(
1409
+ new RegExp(`(<(?:ul|ol)[^>]*?)(\\/?>)([\\s\\S]*?${field.key}\\.map)`, "g"),
1410
+ (_m, ulOpen, ulClose, rest) => {
1411
+ if (ulOpen.includes("data-sk-field")) return _m;
1412
+ const attr = ` data-sk-field={fieldPrefix ? \`\${fieldPrefix}.${field.key}\` : undefined}`;
1413
+ return `${ulOpen}${attr}${ulClose}${rest}`;
1414
+ }
1415
+ );
1416
+ templatePart = templatePart.replace(mapParamRegex, (_m, before, param, close) => {
1417
+ if (close.trim().startsWith(",")) return _m;
1418
+ return `${before}${param}, _fi${close}`;
1419
+ });
1420
+ const mapBlockRegex = new RegExp(
1421
+ `(${field.key}\\.map\\s*\\([^)]*\\)\\s*=>\\s*\\([\\s\\S]*?)(<(?:span|li|td)\\b)([^>]*?>)([^<]*\\{\\w+\\})`,
1422
+ "g"
1423
+ );
1424
+ templatePart = templatePart.replace(
1425
+ mapBlockRegex,
1426
+ (_m, mapHead, tagOpen, tagClose, content) => {
1427
+ if (tagClose.includes("data-sk-field")) return _m;
1428
+ const attr = ` data-sk-field={fieldPrefix ? \`\${fieldPrefix}.${field.key}.\${_fi}\` : undefined}`;
1429
+ return `${mapHead}${tagOpen}${tagClose.slice(0, -1)}${attr}>${content}`;
1430
+ }
1431
+ );
1432
+ }
1433
+ return frontmatterPart + templatePart;
1434
+ }
1435
+ function detectChildImports(source, fields) {
1436
+ const result = [];
1437
+ for (const field of fields) {
1438
+ const compName = field.options?.sourceComponent;
1439
+ if (!compName) continue;
1440
+ const innerFields = (field.options?.arrayItem?.fields ?? []).map((f) => ({
1441
+ key: f.key,
1442
+ type: f.type
1443
+ }));
1444
+ const importRegex = new RegExp(`import\\s+${compName}\\s+from\\s+['"]([^'"]+)['"]`);
1445
+ const m = source.match(importRegex);
1446
+ if (!m) continue;
1447
+ result.push({ compName, importPath: m[1], innerFields });
1448
+ }
1449
+ return result;
1450
+ }
1236
1451
  function stripTemplateFallbacks(source) {
1237
1452
  const fallbacks = {};
1238
1453
  let result = source;
@@ -1263,5 +1478,7 @@ function stripTemplateFallbacks(source) {
1263
1478
  export {
1264
1479
  patchTemplateForFields,
1265
1480
  convertToSetHtml,
1481
+ patchChildComponentForFieldPrefix,
1482
+ detectChildImports,
1266
1483
  stripTemplateFallbacks
1267
1484
  };
@@ -1,5 +1,5 @@
1
1
  // src/api-routes/_github-token.ts
2
- import { err, authError } from "@setzkasten-cms/core";
2
+ import { authError, err } from "@setzkasten-cms/core";
3
3
  import { GitHubAppClient } from "@setzkasten-cms/github-adapter";
4
4
  async function resolveConfigRepoToken() {
5
5
  const appId = process.env.GITHUB_APP_ID;