@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
@@ -30,10 +30,10 @@ var POST = async ({ cookies }) => {
30
30
  });
31
31
  if (!response.ok) {
32
32
  console.warn(`[setzkasten] Deploy hook antwortete mit ${response.status}: ${url}`);
33
- return new Response(
34
- JSON.stringify({ ok: false, status: response.status }),
35
- { status: 200, headers: { "Content-Type": "application/json" } }
36
- );
33
+ return new Response(JSON.stringify({ ok: false, status: response.status }), {
34
+ status: 200,
35
+ headers: { "Content-Type": "application/json" }
36
+ });
37
37
  }
38
38
  return new Response(JSON.stringify({ ok: true }), {
39
39
  status: 200,
@@ -41,10 +41,10 @@ var POST = async ({ cookies }) => {
41
41
  });
42
42
  } catch (error) {
43
43
  console.error("[setzkasten] Deploy hook fehlgeschlagen:", error);
44
- return new Response(
45
- JSON.stringify({ ok: false, error: String(error) }),
46
- { status: 200, headers: { "Content-Type": "application/json" } }
47
- );
44
+ return new Response(JSON.stringify({ ok: false, error: String(error) }), {
45
+ status: 200,
46
+ headers: { "Content-Type": "application/json" }
47
+ });
48
48
  }
49
49
  };
50
50
  export {
@@ -1,5 +1,5 @@
1
- import { APIRoute } from 'astro';
2
1
  import { ContentEditorConfig } from '@setzkasten-cms/core';
2
+ import { APIRoute } from 'astro';
3
3
 
4
4
  declare const GET: APIRoute;
5
5
  declare const PUT: APIRoute;
@@ -3,11 +3,14 @@ import {
3
3
  PUT,
4
4
  readEditorsFile,
5
5
  readEditorsFileStatus
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
  export {
@@ -1,12 +1,25 @@
1
+ import {
2
+ parseSession
3
+ } from "../chunk-Q5HV47DW.js";
4
+ import "../chunk-QVCW6EF3.js";
5
+ import "../chunk-KENFINT4.js";
6
+ import {
7
+ resolveStorageConfigForRequest
8
+ } from "../chunk-6UIKVKED.js";
9
+ import "../chunk-ONP6BRZO.js";
10
+ import "../chunk-5PIMDP4N.js";
11
+ import "../chunk-45ARVNT3.js";
1
12
  import {
2
13
  resolveGitHubTokenForRequest
3
- } from "../chunk-NKDATSPA.js";
14
+ } from "../chunk-DP6RTINQ.js";
15
+ import "../chunk-TJNJKPUL.js";
16
+ import "../chunk-KH22FJO5.js";
4
17
 
5
18
  // src/api-routes/github-proxy.ts
6
19
  import { writeFile } from "fs/promises";
7
20
  import { join } from "path";
8
21
  var ALL = async ({ params, request, cookies }) => {
9
- const session = cookies.get("setzkasten_session")?.value;
22
+ const session = parseSession(cookies.get("setzkasten_session")?.value);
10
23
  if (!session) {
11
24
  return new Response("Unauthorized", { status: 401 });
12
25
  }
@@ -14,6 +27,17 @@ var ALL = async ({ params, request, cookies }) => {
14
27
  if (!githubPath) {
15
28
  return new Response("Missing path", { status: 400 });
16
29
  }
30
+ const storage = await resolveStorageConfigForRequest(request);
31
+ if (!storage) {
32
+ return new Response("Could not resolve owner/repo", { status: 400 });
33
+ }
34
+ const allowedPrefix = `repos/${storage.owner}/${storage.repo}/`;
35
+ const allowedExact = `repos/${storage.owner}/${storage.repo}`;
36
+ if (githubPath !== allowedExact && !githubPath.startsWith(allowedPrefix)) {
37
+ return new Response(`Forbidden: proxy is scoped to ${storage.owner}/${storage.repo}`, {
38
+ status: 403
39
+ });
40
+ }
17
41
  const tokenResult = await resolveGitHubTokenForRequest(request);
18
42
  if (!tokenResult.ok) {
19
43
  return new Response(tokenResult.error.message, { status: 500 });
@@ -38,11 +62,7 @@ var ALL = async ({ params, request, cookies }) => {
38
62
  });
39
63
  const responseHeaders = new Headers();
40
64
  responseHeaders.set("Content-Type", response.headers.get("content-type") ?? "application/json");
41
- const rateLimitHeaders = [
42
- "x-ratelimit-limit",
43
- "x-ratelimit-remaining",
44
- "x-ratelimit-reset"
45
- ];
65
+ const rateLimitHeaders = ["x-ratelimit-limit", "x-ratelimit-remaining", "x-ratelimit-reset"];
46
66
  for (const header of rateLimitHeaders) {
47
67
  const value = response.headers.get(header);
48
68
  if (value) responseHeaders.set(header, value);
@@ -59,7 +79,9 @@ var ALL = async ({ params, request, cookies }) => {
59
79
  const filePath = contentsMatch[1];
60
80
  const parsed = JSON.parse(body);
61
81
  if (parsed.content) {
62
- const decoded = Buffer.from(parsed.content.replace(/\s/g, ""), "base64").toString("utf-8");
82
+ const decoded = Buffer.from(parsed.content.replace(/\s/g, ""), "base64").toString(
83
+ "utf-8"
84
+ );
63
85
  await writeFile(join(repoRoot, filePath), decoded, "utf-8").catch(() => {
64
86
  });
65
87
  }
@@ -3,12 +3,15 @@ import {
3
3
  PUT,
4
4
  readGlobalConfig,
5
5
  writeGlobalConfig
6
- } from "../chunk-AM4DZXXM.js";
7
- import "../chunk-INIWFKQ3.js";
6
+ } from "../chunk-UJAFZEX2.js";
7
+ import "../chunk-Q5HV47DW.js";
8
+ import "../chunk-QVCW6EF3.js";
9
+ import "../chunk-KENFINT4.js";
8
10
  import "../chunk-6UIKVKED.js";
11
+ import "../chunk-ONP6BRZO.js";
9
12
  import "../chunk-5PIMDP4N.js";
10
13
  import "../chunk-45ARVNT3.js";
11
- import "../chunk-NKDATSPA.js";
14
+ import "../chunk-DP6RTINQ.js";
12
15
  import "../chunk-TJNJKPUL.js";
13
16
  import "../chunk-KH22FJO5.js";
14
17
  export {
@@ -1,17 +1,20 @@
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 "../chunk-5PIMDP4N.js";
9
12
  import {
10
13
  invalidateCache
11
14
  } from "../chunk-45ARVNT3.js";
12
15
  import {
13
16
  resolveGitHubTokenForRequest
14
- } from "../chunk-NKDATSPA.js";
17
+ } from "../chunk-DP6RTINQ.js";
15
18
  import "../chunk-TJNJKPUL.js";
16
19
  import {
17
20
  withTrailers
@@ -40,6 +43,14 @@ var POST = async ({ request, cookies }) => {
40
43
  return Response.json({ error: "Could not resolve owner/repo" }, { status: 400 });
41
44
  }
42
45
  const { owner, repo, branch } = storage;
46
+ const serverConfig = globalThis.__SETZKASTEN_CONFIG__;
47
+ const contentPath = serverConfig?.storage?.contentPath ?? "content";
48
+ if (!isPathInsideContent(path, contentPath)) {
49
+ return Response.json(
50
+ { error: `Rollback restricted to the content folder (${contentPath}/...)` },
51
+ { status: 400 }
52
+ );
53
+ }
43
54
  const headers = {
44
55
  Authorization: `Bearer ${tokenResult.value}`,
45
56
  Accept: "application/vnd.github+json",
@@ -57,10 +68,7 @@ var POST = async ({ request, cookies }) => {
57
68
  );
58
69
  }
59
70
  if (!versionRes.ok) {
60
- return Response.json(
61
- { error: `Failed to read version: ${versionRes.status}` },
62
- { status: 502 }
63
- );
71
+ return Response.json({ error: `Failed to read version: ${versionRes.status}` }, { status: 502 });
64
72
  }
65
73
  const versionData = await versionRes.json();
66
74
  const targetContent = versionData.encoding === "base64" ? Buffer.from(versionData.content, "base64").toString("utf-8") : versionData.content;
@@ -84,20 +92,18 @@ var POST = async ({ request, cookies }) => {
84
92
  }
85
93
  const shortSha = sha.slice(0, 7);
86
94
  const fileName = path.split("/").pop() ?? path;
87
- const message = withTrailers(
88
- `revert(${fileName}): rollback to ${shortSha}`,
89
- session.user.email
90
- );
95
+ const message = withTrailers(`revert(${fileName}): rollback to ${shortSha}`, session.user.email);
91
96
  const putBody = {
92
97
  message,
93
98
  content: Buffer.from(targetContent).toString("base64"),
94
99
  branch
95
100
  };
96
101
  if (currentSha) putBody.sha = currentSha;
97
- const putRes = await fetch(
98
- `https://api.github.com/repos/${owner}/${repo}/contents/${path}`,
99
- { method: "PUT", headers, body: JSON.stringify(putBody) }
100
- );
102
+ const putRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/contents/${path}`, {
103
+ method: "PUT",
104
+ headers,
105
+ body: JSON.stringify(putBody)
106
+ });
101
107
  if (!putRes.ok) {
102
108
  const text = await putRes.text();
103
109
  return Response.json({ error: `Rollback write failed: ${text}` }, { status: 502 });
@@ -106,6 +112,17 @@ var POST = async ({ request, cookies }) => {
106
112
  const putData = await putRes.json();
107
113
  return Response.json({ ok: true, commitSha: putData.commit.sha });
108
114
  };
115
+ function isPathInsideContent(target, base) {
116
+ if (typeof target !== "string" || target.length === 0) return false;
117
+ if (target.includes("\0") || target.includes("\\")) return false;
118
+ if (target.startsWith("/")) return false;
119
+ const segments = target.split("/");
120
+ for (const seg of segments) {
121
+ if (seg === "" || seg === "." || seg === "..") return false;
122
+ }
123
+ const normalizedBase = base.replace(/^\/+|\/+$/g, "");
124
+ return target === normalizedBase || target.startsWith(`${normalizedBase}/`);
125
+ }
109
126
  export {
110
127
  POST
111
128
  };
@@ -1,16 +1,19 @@
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 {
5
7
  resolveStorageConfigForRequest
6
8
  } from "../chunk-6UIKVKED.js";
9
+ import "../chunk-ONP6BRZO.js";
7
10
  import "../chunk-5PIMDP4N.js";
8
11
  import {
9
12
  cachedFetch
10
13
  } from "../chunk-45ARVNT3.js";
11
14
  import {
12
15
  resolveGitHubTokenForRequest
13
- } from "../chunk-NKDATSPA.js";
16
+ } from "../chunk-DP6RTINQ.js";
14
17
  import "../chunk-TJNJKPUL.js";
15
18
  import "../chunk-KH22FJO5.js";
16
19
 
@@ -32,9 +35,7 @@ var GET = async ({ request, url, cookies }) => {
32
35
  const { owner, repo } = storage;
33
36
  const cacheKey = `history-version:${owner}/${repo}:${path}:${sha}`;
34
37
  const result = await cachedFetch(cacheKey, 5 * 6e4, async () => {
35
- const u = new URL(
36
- `https://api.github.com/repos/${owner}/${repo}/contents/${path}`
37
- );
38
+ const u = new URL(`https://api.github.com/repos/${owner}/${repo}/contents/${path}`);
38
39
  u.searchParams.set("ref", sha);
39
40
  const res = await fetch(u, {
40
41
  headers: {
@@ -43,7 +44,8 @@ var GET = async ({ request, url, cookies }) => {
43
44
  "X-GitHub-Api-Version": "2022-11-28"
44
45
  }
45
46
  });
46
- if (res.status === 404) return { ok: false, status: 404, error: "File not found at given sha" };
47
+ if (res.status === 404)
48
+ return { ok: false, status: 404, error: "File not found at given sha" };
47
49
  if (!res.ok) return { ok: false, status: 502, error: `GitHub returned ${res.status}` };
48
50
  const data = await res.json();
49
51
  const raw = data.encoding === "base64" ? Buffer.from(data.content, "base64").toString("utf-8") : data.content;
@@ -1,16 +1,19 @@
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 {
5
7
  resolveStorageConfigForRequest
6
8
  } from "../chunk-6UIKVKED.js";
9
+ import "../chunk-ONP6BRZO.js";
7
10
  import "../chunk-5PIMDP4N.js";
8
11
  import {
9
12
  cachedFetch
10
13
  } from "../chunk-45ARVNT3.js";
11
14
  import {
12
15
  resolveGitHubTokenForRequest
13
- } from "../chunk-NKDATSPA.js";
16
+ } from "../chunk-DP6RTINQ.js";
14
17
  import "../chunk-TJNJKPUL.js";
15
18
  import "../chunk-KH22FJO5.js";
16
19
 
@@ -4,7 +4,7 @@ import {
4
4
  } from "../chunk-6UIKVKED.js";
5
5
  import {
6
6
  resolveGitHubTokenForRequest
7
- } from "../chunk-NKDATSPA.js";
7
+ } from "../chunk-DP6RTINQ.js";
8
8
 
9
9
  // src/api-routes/icons-local.ts
10
10
  import {
@@ -1,25 +1,35 @@
1
1
  import {
2
+ detectChildImports,
3
+ patchChildComponentForFieldPrefix,
2
4
  patchTemplateForFields,
3
5
  stripTemplateFallbacks
4
- } from "../chunk-RHJONMLK.js";
6
+ } from "../chunk-CDXCYYQR.js";
7
+ import {
8
+ requireAdmin
9
+ } from "../chunk-Q5HV47DW.js";
10
+ import "../chunk-QVCW6EF3.js";
11
+ import "../chunk-KENFINT4.js";
5
12
  import {
6
13
  prefixPath,
7
14
  resolveStorageConfigForRequest
8
15
  } from "../chunk-6UIKVKED.js";
16
+ import "../chunk-ONP6BRZO.js";
17
+ import "../chunk-5PIMDP4N.js";
18
+ import "../chunk-45ARVNT3.js";
9
19
  import {
10
20
  resolveGitHubTokenForRequest
11
- } from "../chunk-NKDATSPA.js";
21
+ } from "../chunk-DP6RTINQ.js";
22
+ import "../chunk-TJNJKPUL.js";
12
23
  import {
13
24
  withTrailers
14
25
  } from "../chunk-KH22FJO5.js";
15
26
 
16
27
  // src/api-routes/init-add-section.ts
28
+ import { isSafeKey } from "@setzkasten-cms/core";
17
29
  import { addSectionToConfig } from "@setzkasten-cms/core/init";
18
30
  var POST = async ({ request, cookies }) => {
19
- const session = cookies.get("setzkasten_session")?.value;
20
- if (!session) {
21
- return new Response("Unauthorized", { status: 401 });
22
- }
31
+ const denied = requireAdmin(cookies.get("setzkasten_session")?.value);
32
+ if (denied) return denied;
23
33
  const tokenResult = await resolveGitHubTokenForRequest(request);
24
34
  if (!tokenResult.ok) {
25
35
  return new Response(tokenResult.error.message, { status: 500 });
@@ -29,7 +39,12 @@ var POST = async ({ request, cookies }) => {
29
39
  const body = await request.json();
30
40
  const storage = await resolveStorageConfigForRequest(request, body);
31
41
  if (!storage) {
32
- return Response.json({ error: "Could not resolve owner/repo. Set SETZKASTEN_OWNER and SETZKASTEN_REPO env vars." }, { status: 400 });
42
+ return Response.json(
43
+ {
44
+ error: "Could not resolve owner/repo. Set SETZKASTEN_OWNER and SETZKASTEN_REPO env vars."
45
+ },
46
+ { status: 400 }
47
+ );
33
48
  }
34
49
  const { owner, repo, branch, projectPrefix } = storage;
35
50
  const serverConfig = globalThis.__SETZKASTEN_CONFIG__;
@@ -38,6 +53,12 @@ var POST = async ({ request, cookies }) => {
38
53
  if (!section || !pageKey) {
39
54
  return Response.json({ error: "section and pageKey are required" }, { status: 400 });
40
55
  }
56
+ if (!isSafeKey(pageKey)) {
57
+ return Response.json({ error: "invalid pageKey" }, { status: 400 });
58
+ }
59
+ if (!isSafeKey(section.key)) {
60
+ return Response.json({ error: "invalid section key" }, { status: 400 });
61
+ }
41
62
  const headers = {
42
63
  Authorization: `Bearer ${githubToken}`,
43
64
  Accept: "application/vnd.github+json",
@@ -48,13 +69,24 @@ var POST = async ({ request, cookies }) => {
48
69
  const configPath = prefixPath("setzkasten.config.ts", projectPrefix);
49
70
  const existingConfig = await fetchFileContent(owner, repo, branch, configPath, githubToken);
50
71
  if (existingConfig) {
51
- const updatedConfig = addSectionToConfig(existingConfig, section.key, section, section.allFields);
72
+ const updatedConfig = addSectionToConfig(
73
+ existingConfig,
74
+ section.key,
75
+ section,
76
+ section.allFields
77
+ );
52
78
  if (updatedConfig) {
53
79
  filesToCommit.push({ path: configPath, content: updatedConfig });
54
80
  }
55
81
  }
56
82
  const sectionJsonPath = `${contentPath}/_sections/${section.key}.json`;
57
- const existingSectionJson = await fetchFileContent(owner, repo, branch, sectionJsonPath, githubToken);
83
+ const existingSectionJson = await fetchFileContent(
84
+ owner,
85
+ repo,
86
+ branch,
87
+ sectionJsonPath,
88
+ githubToken
89
+ );
58
90
  let sectionData = {};
59
91
  if (existingSectionJson) {
60
92
  try {
@@ -87,7 +119,13 @@ var POST = async ({ request, cookies }) => {
87
119
  });
88
120
  const configKey = "_" + pageKey.replace(/\//g, "_");
89
121
  const pageConfigPath = `${contentPath}/pages/${configKey}.json`;
90
- const existingPageConfig = await fetchFileContent(owner, repo, branch, pageConfigPath, githubToken);
122
+ const existingPageConfig = await fetchFileContent(
123
+ owner,
124
+ repo,
125
+ branch,
126
+ pageConfigPath,
127
+ githubToken
128
+ );
91
129
  let pageConfig;
92
130
  if (existingPageConfig) {
93
131
  pageConfig = JSON.parse(existingPageConfig);
@@ -146,8 +184,14 @@ var POST = async ({ request, cookies }) => {
146
184
  }
147
185
  if (patchedSource !== pageSource) {
148
186
  filesToCommit.push({ path: fullPagePath, content: patchedSource });
149
- const previewCopySource = patchedSource.replace(/\bexport\s+const\s+prerender\s*=\s*true\s*;?\s*\n?/, "").replace(/(from\s+')(\.\.\/)/g, "$1../$2").replace(/(from\s+")(\.\.\/)/g, "$1../$2");
150
187
  const relativePage = resolvedPagePath.replace(/^src\/pages\//, "");
188
+ const importDepth = "../".repeat(relativePage.split("/").length);
189
+ const previewCopySource = `---
190
+ export const prerender = false;
191
+ import Page from '${importDepth}${relativePage}';
192
+ ---
193
+ <Page />
194
+ `;
151
195
  const previewCopyPath = prefixPath(`src/pages/sk-preview/${relativePage}`, projectPrefix);
152
196
  filesToCommit.push({ path: previewCopyPath, content: previewCopySource });
153
197
  }
@@ -155,7 +199,9 @@ var POST = async ({ request, cookies }) => {
155
199
  for (const g of repeatedGroups) {
156
200
  const topField = fields.find((f) => f.key === g.fieldKey);
157
201
  if (!topField || !Array.isArray(topField.defaultValue)) continue;
158
- sectionData[g.fieldKey] = topField.defaultValue.filter((item) => item != null);
202
+ sectionData[g.fieldKey] = topField.defaultValue.filter(
203
+ (item) => item != null
204
+ );
159
205
  const jsonIdx = filesToCommit.findIndex((f) => f.path === sectionJsonPath);
160
206
  if (jsonIdx !== -1) {
161
207
  filesToCommit[jsonIdx].content = JSON.stringify(sectionData, null, 2);
@@ -163,10 +209,21 @@ var POST = async ({ request, cookies }) => {
163
209
  }
164
210
  }
165
211
  } else if (section.componentPath) {
166
- const componentSource = await fetchFileContent(owner, repo, branch, section.componentPath, githubToken);
212
+ const componentSource = await fetchFileContent(
213
+ owner,
214
+ repo,
215
+ branch,
216
+ section.componentPath,
217
+ githubToken
218
+ );
167
219
  if (componentSource) {
168
220
  const repeatedGroups = section._analyzerResult?.repeatedGroups ?? [];
169
- const patchedSource = await patchTemplateForFields(componentSource, section.key, section.allFields ?? section.fields, repeatedGroups);
221
+ const patchedSource = await patchTemplateForFields(
222
+ componentSource,
223
+ section.key,
224
+ section.allFields ?? section.fields,
225
+ repeatedGroups
226
+ );
170
227
  if (patchedSource !== componentSource) {
171
228
  filesToCommit.push({ path: section.componentPath, content: patchedSource });
172
229
  }
@@ -174,19 +231,46 @@ var POST = async ({ request, cookies }) => {
174
231
  for (const g of repeatedGroups) {
175
232
  const topField = fields.find((f) => f.key === g.fieldKey);
176
233
  if (!topField || !Array.isArray(topField.defaultValue)) continue;
177
- const items = topField.defaultValue.filter((item) => item != null);
234
+ const items = topField.defaultValue.filter(
235
+ (item) => item != null
236
+ );
178
237
  sectionData[g.fieldKey] = items;
179
238
  const jsonIdx = filesToCommit.findIndex((f) => f.path === sectionJsonPath);
180
239
  if (jsonIdx !== -1) {
181
240
  filesToCommit[jsonIdx].content = JSON.stringify(sectionData, null, 2);
182
241
  }
183
242
  }
243
+ const allFields = section.allFields ?? section.fields;
244
+ const childPatches = detectChildImports(patchedSource, allFields);
245
+ for (const child of childPatches) {
246
+ const sectionDir = section.componentPath.replace(/\/[^/]+$/, "");
247
+ const resolvedChildPath = resolveRelativePath(sectionDir, child.importPath);
248
+ if (!resolvedChildPath) continue;
249
+ const childSource = await fetchFileContent(
250
+ owner,
251
+ repo,
252
+ branch,
253
+ resolvedChildPath,
254
+ githubToken
255
+ );
256
+ if (!childSource) continue;
257
+ const patchedChild = patchChildComponentForFieldPrefix(childSource, child.innerFields);
258
+ if (patchedChild !== childSource) {
259
+ filesToCommit.push({ path: resolvedChildPath, content: patchedChild });
260
+ }
261
+ }
184
262
  }
185
263
  if (body.pagePath && section.componentName) {
186
264
  const fullPagePath = prefixPath(body.pagePath, projectPrefix);
187
265
  const pageSource = await fetchFileContent(owner, repo, branch, fullPagePath, githubToken);
188
266
  if (pageSource) {
189
- const patched = patchPageFile(pageSource, section.key, section.componentName, section.componentPath, body.pagePath);
267
+ const patched = patchPageFile(
268
+ pageSource,
269
+ section.key,
270
+ section.componentName,
271
+ section.componentPath,
272
+ body.pagePath
273
+ );
190
274
  if (patched && patched !== pageSource) {
191
275
  filesToCommit.push({ path: fullPagePath, content: patched });
192
276
  }
@@ -195,7 +279,13 @@ var POST = async ({ request, cookies }) => {
195
279
  const previewPath = prefixPath(`${pageDir}/sk-preview/[...page].astro`, projectPrefix);
196
280
  const previewSource = await fetchFileContent(owner, repo, branch, previewPath, githubToken);
197
281
  if (previewSource) {
198
- const patchedPreview = patchPageFile(previewSource, section.key, section.componentName, section.componentPath, `${pageDir}/sk-preview/[...page].astro`);
282
+ const patchedPreview = patchPageFile(
283
+ previewSource,
284
+ section.key,
285
+ section.componentName,
286
+ section.componentPath,
287
+ `${pageDir}/sk-preview/[...page].astro`
288
+ );
199
289
  if (patchedPreview && patchedPreview !== previewSource) {
200
290
  filesToCommit.push({ path: previewPath, content: patchedPreview });
201
291
  }
@@ -210,7 +300,9 @@ var POST = async ({ request, cookies }) => {
210
300
  repo,
211
301
  branch,
212
302
  filesToCommit,
213
- withTrailers(existingSectionJson ? `content: update ${section.key} section \u2014 add new fields` : `content: add ${section.key} section to Setzkasten`),
303
+ withTrailers(
304
+ existingSectionJson ? `content: update ${section.key} section \u2014 add new fields` : `content: add ${section.key} section to Setzkasten`
305
+ ),
214
306
  headers
215
307
  );
216
308
  if (!commitResult.ok) {
@@ -288,7 +380,7 @@ function patchPageFile(source, sectionKey, componentName, componentPath, pagePat
288
380
  if (lastEntryMatch && lastEntryMatch.index !== void 0) {
289
381
  const insertPos = registryMatch.index + registryMatch[0].indexOf(registryContent) + lastEntryMatch.index + lastEntryMatch[0].length;
290
382
  const newEntry = `
291
- [normalize('${sectionKey}')]: ${componentName},`;
383
+ '${sectionKey}': ${componentName},`;
292
384
  patched = patched.slice(0, insertPos) + newEntry + patched.slice(insertPos);
293
385
  }
294
386
  }
@@ -309,6 +401,21 @@ function calculateRelativePath(fromDir, toPath) {
309
401
  if (ups === 0) return "./" + remaining;
310
402
  return "../".repeat(ups) + remaining;
311
403
  }
404
+ function resolveRelativePath(baseDir, relativePath) {
405
+ if (relativePath.startsWith("/")) return relativePath.replace(/^\//, "");
406
+ const parts = [...baseDir.split("/").filter(Boolean)];
407
+ for (const segment of relativePath.split("/")) {
408
+ if (segment === ".") continue;
409
+ if (segment === "..") {
410
+ parts.pop();
411
+ continue;
412
+ }
413
+ parts.push(segment);
414
+ }
415
+ const resolved = parts.join("/");
416
+ if (resolved.startsWith("../") || resolved.startsWith("/")) return null;
417
+ return resolved;
418
+ }
312
419
  async function fetchFileContent(owner, repo, branch, path, token) {
313
420
  try {
314
421
  const response = await fetch(
@@ -343,37 +450,32 @@ async function batchCommit(owner, repo, branch, files, message, headers) {
343
450
  );
344
451
  if (!commitRes.ok) return { ok: false, error: `Failed to get commit: ${commitRes.status}` };
345
452
  const commitData = await commitRes.json();
346
- const treeRes = await fetch(
347
- `https://api.github.com/repos/${owner}/${repo}/git/trees`,
348
- {
349
- method: "POST",
350
- headers,
351
- body: JSON.stringify({
352
- base_tree: commitData.tree.sha,
353
- tree: files.map((f) => ({
354
- path: f.path,
355
- mode: "100644",
356
- type: "blob",
357
- content: f.content
358
- }))
359
- })
360
- }
361
- );
453
+ const treeRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/trees`, {
454
+ method: "POST",
455
+ headers,
456
+ body: JSON.stringify({
457
+ base_tree: commitData.tree.sha,
458
+ tree: files.map((f) => ({
459
+ path: f.path,
460
+ mode: "100644",
461
+ type: "blob",
462
+ content: f.content
463
+ }))
464
+ })
465
+ });
362
466
  if (!treeRes.ok) return { ok: false, error: `Failed to create tree: ${treeRes.status}` };
363
467
  const treeData = await treeRes.json();
364
- const newCommitRes = await fetch(
365
- `https://api.github.com/repos/${owner}/${repo}/git/commits`,
366
- {
367
- method: "POST",
368
- headers,
369
- body: JSON.stringify({
370
- tree: treeData.sha,
371
- parents: [headSha],
372
- message
373
- })
374
- }
375
- );
376
- if (!newCommitRes.ok) return { ok: false, error: `Failed to create commit: ${newCommitRes.status}` };
468
+ const newCommitRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/commits`, {
469
+ method: "POST",
470
+ headers,
471
+ body: JSON.stringify({
472
+ tree: treeData.sha,
473
+ parents: [headSha],
474
+ message
475
+ })
476
+ });
477
+ if (!newCommitRes.ok)
478
+ return { ok: false, error: `Failed to create commit: ${newCommitRes.status}` };
377
479
  const newCommitData = await newCommitRes.json();
378
480
  const updateRes = await fetch(
379
481
  `https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`,