@setzkasten-cms/astro-admin 1.4.6 → 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.
- package/dist/api-routes/_auth-guard.d.ts +27 -3
- package/dist/api-routes/_auth-guard.js +5 -2
- package/dist/api-routes/_dev-session-secret.d.ts +8 -0
- package/dist/api-routes/_dev-session-secret.js +8 -0
- package/dist/api-routes/_github-token.js +1 -1
- package/dist/api-routes/_role-resolver.js +6 -3
- package/dist/api-routes/_session-secret.d.ts +19 -0
- package/dist/api-routes/_session-secret.js +7 -0
- package/dist/api-routes/_session-signing.d.ts +45 -0
- package/dist/api-routes/_session-signing.js +8 -0
- package/dist/api-routes/_webhook-dispatcher.js +4 -4
- package/dist/api-routes/asset-proxy.js +1 -1
- package/dist/api-routes/auth-callback.js +12 -5
- package/dist/api-routes/auth-logout.d.ts +4 -4
- package/dist/api-routes/auth-logout.js +8 -2
- package/dist/api-routes/auth-session.d.ts +6 -0
- package/dist/api-routes/auth-session.js +19 -19
- package/dist/api-routes/auth-setzkasten-login.js +14 -7
- package/dist/api-routes/catalog-add.js +59 -17
- package/dist/api-routes/catalog-export.js +14 -4
- package/dist/api-routes/config.d.ts +10 -3
- package/dist/api-routes/config.js +26 -4
- package/dist/api-routes/deploy-hook.js +8 -8
- package/dist/api-routes/editors.d.ts +1 -1
- package/dist/api-routes/editors.js +5 -2
- package/dist/api-routes/github-proxy.js +30 -8
- package/dist/api-routes/global-config.js +6 -3
- package/dist/api-routes/history-rollback.js +31 -14
- package/dist/api-routes/history-version.js +8 -6
- package/dist/api-routes/history.js +5 -2
- package/dist/api-routes/icons-local.js +1 -1
- package/dist/api-routes/init-add-section.js +113 -47
- package/dist/api-routes/init-apply.js +56 -42
- package/dist/api-routes/init-migrate.js +43 -36
- package/dist/api-routes/init-scan-page.d.ts +1 -1
- package/dist/api-routes/init-scan-page.js +59 -13
- package/dist/api-routes/init-scan.js +22 -7
- package/dist/api-routes/migrate-to-multi.js +5 -2
- package/dist/api-routes/pages.js +15 -4
- package/dist/api-routes/section-add.js +68 -16
- package/dist/api-routes/section-commit-pending.js +70 -22
- package/dist/api-routes/section-delete.js +49 -14
- package/dist/api-routes/section-duplicate.js +65 -16
- package/dist/api-routes/section-prepare-copy.js +15 -2
- package/dist/api-routes/section-prepare.js +25 -4
- package/dist/api-routes/setup-github-app-bounce.js +15 -1
- package/dist/api-routes/setup-github-app-branches.js +9 -6
- package/dist/api-routes/setup-github-app-callback.js +24 -1
- package/dist/api-routes/setup-github-app-credentials.d.ts +27 -0
- package/dist/api-routes/setup-github-app-credentials.js +43 -0
- package/dist/api-routes/setup-github-app-installed.js +22 -1
- package/dist/api-routes/setup-github-app-repos.js +5 -2
- package/dist/api-routes/setup-github-app.d.ts +4 -0
- package/dist/api-routes/setup-github-app.js +19 -2
- package/dist/api-routes/updater-register.js +7 -1
- package/dist/api-routes/webhooks-status.js +5 -2
- package/dist/api-routes/webhooks-test.js +9 -8
- package/dist/api-routes/webhooks.js +12 -14
- package/dist/api-routes/websites-add.js +5 -2
- package/dist/api-routes/websites-remove.js +5 -2
- package/dist/{chunk-ZQDGGWJP.js → chunk-5KMGSFCZ.js} +2 -2
- package/dist/{chunk-Q3N336KR.js → chunk-CDXCYYQR.js} +29 -24
- package/dist/{chunk-NKDATSPA.js → chunk-DP6RTINQ.js} +1 -1
- package/dist/chunk-KENFINT4.js +76 -0
- package/dist/chunk-ONP6BRZO.js +47 -0
- package/dist/{chunk-INIWFKQ3.js → chunk-Q5HV47DW.js} +33 -19
- package/dist/chunk-QVCW6EF3.js +26 -0
- package/dist/{chunk-TD76R3A6.js → chunk-UHI6323G.js} +293 -174
- package/dist/{chunk-AM4DZXXM.js → chunk-UJAFZEX2.js} +76 -9
- package/package.json +12 -6
- package/src/api-routes/__tests__/_session-signing.test.ts +114 -0
- package/src/api-routes/__tests__/_session-test-helper.ts +27 -0
- package/src/api-routes/__tests__/add-section-helpers.test.ts +59 -25
- package/src/api-routes/__tests__/auth-guard.test.ts +46 -7
- package/src/api-routes/__tests__/catalog-api.test.ts +14 -6
- package/src/api-routes/__tests__/commit-trailers.test.ts +5 -5
- package/src/api-routes/__tests__/deferred-operations.test.ts +9 -12
- package/src/api-routes/__tests__/deploy-hook.test.ts +3 -8
- package/src/api-routes/__tests__/feature-gate.test.ts +1 -1
- package/src/api-routes/__tests__/github-cache.test.ts +1 -1
- package/src/api-routes/__tests__/github-token.test.ts +1 -1
- package/src/api-routes/__tests__/global-config-theme.test.ts +4 -4
- package/src/api-routes/__tests__/history-rollback.test.ts +6 -3
- package/src/api-routes/__tests__/history.test.ts +9 -6
- package/src/api-routes/__tests__/init-scan-page-resolve-config.test.ts +11 -3
- package/src/api-routes/__tests__/migrate-to-multi.test.ts +5 -1
- package/src/api-routes/__tests__/pages-meta-store.test.ts +10 -5
- package/src/api-routes/__tests__/pages.test.ts +7 -2
- package/src/api-routes/__tests__/patch-page-file.test.ts +71 -19
- package/src/api-routes/__tests__/route-registry.test.ts +11 -18
- package/src/api-routes/__tests__/scan-page-helpers.test.ts +13 -10
- package/src/api-routes/__tests__/section-management.test.ts +28 -28
- package/src/api-routes/__tests__/setup-github-app-callback.test.ts +58 -16
- package/src/api-routes/__tests__/setup-github-app-repos.test.ts +4 -5
- package/src/api-routes/__tests__/setup-github-app.test.ts +27 -7
- package/src/api-routes/__tests__/storage-config-for-request.test.ts +83 -0
- package/src/api-routes/__tests__/updater-register.test.ts +230 -0
- package/src/api-routes/__tests__/webhook-signing.test.ts +1 -1
- package/src/api-routes/__tests__/webhooks.test.ts +19 -7
- package/src/api-routes/__tests__/websites-add.test.ts +2 -1
- package/src/api-routes/__tests__/websites-remove.test.ts +2 -1
- package/src/api-routes/_auth-guard.ts +47 -15
- package/src/api-routes/_commit-trailers.ts +3 -2
- package/src/api-routes/_dev-session-secret.ts +79 -0
- package/src/api-routes/_github-token.ts +1 -1
- package/src/api-routes/_pages-meta-store.ts +2 -2
- package/src/api-routes/_role-resolver.ts +7 -5
- package/src/api-routes/_session-secret.ts +46 -0
- package/src/api-routes/_session-signing.ts +135 -0
- package/src/api-routes/_vercel-origin.ts +2 -6
- package/src/api-routes/_webhook-dispatcher.ts +12 -16
- package/src/api-routes/_website-resolver.ts +3 -10
- package/src/api-routes/auth-callback.ts +9 -5
- package/src/api-routes/auth-login.ts +5 -3
- package/src/api-routes/auth-logout.ts +18 -1
- package/src/api-routes/auth-session.ts +13 -21
- package/src/api-routes/auth-setzkasten-login.ts +12 -9
- package/src/api-routes/catalog-add.ts +89 -31
- package/src/api-routes/catalog-export.ts +30 -10
- package/src/api-routes/config.ts +39 -6
- package/src/api-routes/deploy-hook.ts +13 -11
- package/src/api-routes/editors.ts +33 -22
- package/src/api-routes/github-proxy.ts +25 -11
- package/src/api-routes/global-config.ts +103 -18
- package/src/api-routes/history-rollback.ts +41 -14
- package/src/api-routes/history-version.ts +5 -6
- package/src/api-routes/history.ts +3 -3
- package/src/api-routes/icons-local.ts +2 -2
- package/src/api-routes/init-add-section.ts +174 -79
- package/src/api-routes/init-apply.ts +71 -56
- package/src/api-routes/init-migrate.ts +54 -48
- package/src/api-routes/init-scan-page.ts +77 -30
- package/src/api-routes/init-scan.ts +19 -11
- package/src/api-routes/pages.ts +16 -11
- package/src/api-routes/section-add.ts +98 -27
- package/src/api-routes/section-commit-pending.ts +87 -34
- package/src/api-routes/section-delete.ts +76 -27
- package/src/api-routes/section-duplicate.ts +95 -28
- package/src/api-routes/section-management.ts +3 -7
- package/src/api-routes/section-prepare-copy.ts +29 -8
- package/src/api-routes/section-prepare.ts +38 -10
- package/src/api-routes/setup-github-app-bounce.ts +7 -1
- package/src/api-routes/setup-github-app-branches.ts +6 -7
- package/src/api-routes/setup-github-app-callback.ts +18 -1
- package/src/api-routes/setup-github-app-credentials.ts +55 -0
- package/src/api-routes/setup-github-app-installed.ts +12 -1
- package/src/api-routes/setup-github-app-repos.ts +2 -3
- package/src/api-routes/setup-github-app.ts +14 -5
- package/src/api-routes/updater-check.ts +6 -4
- package/src/api-routes/updater-register.ts +34 -20
- package/src/api-routes/updater-transfer.ts +8 -6
- package/src/api-routes/updater-unbind.ts +14 -10
- package/src/api-routes/webhooks-test.ts +9 -11
- package/src/api-routes/webhooks.ts +15 -19
- package/src/init/__tests__/page-level.test.ts +279 -105
- package/src/init/__tests__/page-list-coverage.test.ts +70 -70
- package/src/init/__tests__/patcher-child-component.test.ts +12 -3
- package/src/init/__tests__/patcher-edge-cases.test.ts +47 -23
- package/src/init/__tests__/patcher-mixed-content-wrapper.test.ts +16 -6
- package/src/init/__tests__/patcher-page-mode.test.ts +30 -20
- package/src/init/__tests__/section-pipeline.test.ts +53 -19
- package/src/init/astro-config-patcher.ts +4 -18
- package/src/init/astro-detector.ts +2 -7
- package/src/init/astro-section-analyzer-v2.ts +475 -193
- package/src/init/field-label-enricher.ts +6 -6
- package/src/init/template-patcher-v2.ts +218 -97
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import type { APIRoute } from 'astro'
|
|
2
1
|
import type { SetzKastenConfig } from '@setzkasten-cms/core'
|
|
3
|
-
import {
|
|
2
|
+
import type { APIRoute } from 'astro'
|
|
4
3
|
import { patchTemplateForFields } from '../init/template-patcher-v2'
|
|
4
|
+
import { requireAdmin } from './_auth-guard'
|
|
5
5
|
import { withTrailers } from './_commit-trailers'
|
|
6
6
|
import { resolveGitHubTokenForRequest } from './_github-token'
|
|
7
|
+
import { prefixPath, resolveStorageConfigForRequest } from './_storage-config'
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* POST /api/setzkasten/init/migrate
|
|
@@ -17,10 +18,8 @@ import { resolveGitHubTokenForRequest } from './_github-token'
|
|
|
17
18
|
* Returns: { commitSha, patchedSource, originalSource }
|
|
18
19
|
*/
|
|
19
20
|
export const POST: APIRoute = async ({ request, cookies }) => {
|
|
20
|
-
const
|
|
21
|
-
if (
|
|
22
|
-
return new Response('Unauthorized', { status: 401 })
|
|
23
|
-
}
|
|
21
|
+
const denied = requireAdmin(cookies.get('setzkasten_session')?.value)
|
|
22
|
+
if (denied) return denied
|
|
24
23
|
|
|
25
24
|
const tokenResult = await resolveGitHubTokenForRequest(request)
|
|
26
25
|
if (!tokenResult.ok) {
|
|
@@ -29,7 +28,7 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
29
28
|
const githubToken = tokenResult.value
|
|
30
29
|
|
|
31
30
|
try {
|
|
32
|
-
const body = await request.json() as {
|
|
31
|
+
const body = (await request.json()) as {
|
|
33
32
|
owner?: string
|
|
34
33
|
repo?: string
|
|
35
34
|
branch?: string
|
|
@@ -39,7 +38,12 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
39
38
|
|
|
40
39
|
const storage = await resolveStorageConfigForRequest(request, body)
|
|
41
40
|
if (!storage) {
|
|
42
|
-
return Response.json(
|
|
41
|
+
return Response.json(
|
|
42
|
+
{
|
|
43
|
+
error: 'Could not resolve owner/repo. Set SETZKASTEN_OWNER and SETZKASTEN_REPO env vars.',
|
|
44
|
+
},
|
|
45
|
+
{ status: 400 },
|
|
46
|
+
)
|
|
43
47
|
}
|
|
44
48
|
const { owner, repo, branch, projectPrefix } = storage
|
|
45
49
|
const { sectionKey } = body
|
|
@@ -73,14 +77,20 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
73
77
|
const sectionJsonPath = `${contentPath}/_sections/${sectionKey}.json`
|
|
74
78
|
const sectionJson = await fetchFileContent(owner, repo, branch, sectionJsonPath, githubToken)
|
|
75
79
|
|
|
76
|
-
const fullConfig = (globalThis as any).__SETZKASTEN_FULL_CONFIG__ as
|
|
80
|
+
const fullConfig = (globalThis as any).__SETZKASTEN_FULL_CONFIG__ as
|
|
81
|
+
| SetzKastenConfig
|
|
82
|
+
| undefined
|
|
77
83
|
const fieldDefs = getFieldDefs(fullConfig, sectionKey)
|
|
78
84
|
|
|
79
85
|
// Build fields: combine config field types with content JSON values
|
|
80
86
|
const fields: Array<{ key: string; type: string; defaultValue?: unknown }> = []
|
|
81
87
|
let contentData: Record<string, unknown> = {}
|
|
82
88
|
if (sectionJson) {
|
|
83
|
-
try {
|
|
89
|
+
try {
|
|
90
|
+
contentData = JSON.parse(sectionJson)
|
|
91
|
+
} catch {
|
|
92
|
+
/* ignore */
|
|
93
|
+
}
|
|
84
94
|
}
|
|
85
95
|
|
|
86
96
|
// Add fields from config definitions
|
|
@@ -155,10 +165,11 @@ function getFieldDefs(
|
|
|
155
165
|
}
|
|
156
166
|
|
|
157
167
|
function deriveComponentPath(sectionKey: string): string {
|
|
158
|
-
const componentName =
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
168
|
+
const componentName =
|
|
169
|
+
sectionKey
|
|
170
|
+
.split('-')
|
|
171
|
+
.map((s) => s.charAt(0).toUpperCase() + s.slice(1))
|
|
172
|
+
.join('') + 'Section'
|
|
162
173
|
return `src/components/sections/${componentName}.astro`
|
|
163
174
|
}
|
|
164
175
|
|
|
@@ -181,7 +192,7 @@ async function fetchFileContent(
|
|
|
181
192
|
},
|
|
182
193
|
)
|
|
183
194
|
if (!response.ok) return null
|
|
184
|
-
const data = await response.json() as { content: string; encoding: string }
|
|
195
|
+
const data = (await response.json()) as { content: string; encoding: string }
|
|
185
196
|
return data.encoding === 'base64'
|
|
186
197
|
? Buffer.from(data.content, 'base64').toString('utf-8')
|
|
187
198
|
: data.content
|
|
@@ -204,7 +215,7 @@ async function batchCommit(
|
|
|
204
215
|
{ headers },
|
|
205
216
|
)
|
|
206
217
|
if (!refRes.ok) return { ok: false, error: `Failed to get HEAD: ${refRes.status}` }
|
|
207
|
-
const refData = await refRes.json() as { object: { sha: string } }
|
|
218
|
+
const refData = (await refRes.json()) as { object: { sha: string } }
|
|
208
219
|
const headSha = refData.object.sha
|
|
209
220
|
|
|
210
221
|
const commitRes = await fetch(
|
|
@@ -212,41 +223,36 @@ async function batchCommit(
|
|
|
212
223
|
{ headers },
|
|
213
224
|
)
|
|
214
225
|
if (!commitRes.ok) return { ok: false, error: `Failed to get commit: ${commitRes.status}` }
|
|
215
|
-
const commitData = await commitRes.json() as { tree: { sha: string } }
|
|
226
|
+
const commitData = (await commitRes.json()) as { tree: { sha: string } }
|
|
216
227
|
|
|
217
|
-
const treeRes = await fetch(
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
}),
|
|
231
|
-
},
|
|
232
|
-
)
|
|
228
|
+
const treeRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/trees`, {
|
|
229
|
+
method: 'POST',
|
|
230
|
+
headers,
|
|
231
|
+
body: JSON.stringify({
|
|
232
|
+
base_tree: commitData.tree.sha,
|
|
233
|
+
tree: files.map((f) => ({
|
|
234
|
+
path: f.path,
|
|
235
|
+
mode: '100644',
|
|
236
|
+
type: 'blob',
|
|
237
|
+
content: f.content,
|
|
238
|
+
})),
|
|
239
|
+
}),
|
|
240
|
+
})
|
|
233
241
|
if (!treeRes.ok) return { ok: false, error: `Failed to create tree: ${treeRes.status}` }
|
|
234
|
-
const treeData = await treeRes.json() as { sha: string }
|
|
242
|
+
const treeData = (await treeRes.json()) as { sha: string }
|
|
235
243
|
|
|
236
|
-
const newCommitRes = await fetch(
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
}
|
|
247
|
-
)
|
|
248
|
-
if (!newCommitRes.ok) return { ok: false, error: `Failed to create commit: ${newCommitRes.status}` }
|
|
249
|
-
const newCommitData = await newCommitRes.json() as { sha: string }
|
|
244
|
+
const newCommitRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/commits`, {
|
|
245
|
+
method: 'POST',
|
|
246
|
+
headers,
|
|
247
|
+
body: JSON.stringify({
|
|
248
|
+
tree: treeData.sha,
|
|
249
|
+
parents: [headSha],
|
|
250
|
+
message,
|
|
251
|
+
}),
|
|
252
|
+
})
|
|
253
|
+
if (!newCommitRes.ok)
|
|
254
|
+
return { ok: false, error: `Failed to create commit: ${newCommitRes.status}` }
|
|
255
|
+
const newCommitData = (await newCommitRes.json()) as { sha: string }
|
|
250
256
|
|
|
251
257
|
const updateRes = await fetch(
|
|
252
258
|
`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`,
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import type { APIRoute } from 'astro'
|
|
2
1
|
import type { SetzKastenConfig } from '@setzkasten-cms/core'
|
|
3
|
-
import { prefixPath, resolveStorageConfigForRequest } from './_storage-config'
|
|
4
|
-
import { extractSectionImports, extractLayoutImport } from '../init/astro-detector'
|
|
5
|
-
import { analyzeAstroSection } from '../init/astro-section-analyzer-v2'
|
|
6
2
|
import type { RepoFile } from '@setzkasten-cms/core/init'
|
|
3
|
+
import type { APIRoute } from 'astro'
|
|
4
|
+
import { extractLayoutImport, extractSectionImports } from '../init/astro-detector'
|
|
5
|
+
import { analyzeAstroSection } from '../init/astro-section-analyzer-v2'
|
|
6
|
+
import { requireAdmin } from './_auth-guard'
|
|
7
7
|
import { resolveGitHubTokenForRequest } from './_github-token'
|
|
8
|
+
import { prefixPath, resolveStorageConfigForRequest } from './_storage-config'
|
|
8
9
|
|
|
9
10
|
// Build-time constant injected by the Vite define plugin — always available in
|
|
10
11
|
// compiled API routes (unlike page-ssr injectScript which only runs for SSR pages).
|
|
@@ -20,8 +21,11 @@ declare const __SETZKASTEN_FULL_CONFIG__: SetzKastenConfig | null | undefined
|
|
|
20
21
|
* (including _layout_header / _layout_footer) for re-adoption.
|
|
21
22
|
*/
|
|
22
23
|
export function resolveFullConfig(): SetzKastenConfig | undefined {
|
|
23
|
-
const buildConfig =
|
|
24
|
-
|
|
24
|
+
const buildConfig =
|
|
25
|
+
typeof __SETZKASTEN_FULL_CONFIG__ !== 'undefined' ? __SETZKASTEN_FULL_CONFIG__ : null
|
|
26
|
+
return (buildConfig ?? (globalThis as any).__SETZKASTEN_FULL_CONFIG__) as
|
|
27
|
+
| SetzKastenConfig
|
|
28
|
+
| undefined
|
|
25
29
|
}
|
|
26
30
|
|
|
27
31
|
/**
|
|
@@ -35,10 +39,8 @@ export function resolveFullConfig(): SetzKastenConfig | undefined {
|
|
|
35
39
|
* Returns: { unmanagedSections: InferredSection[], managedUpdates: ManagedUpdate[] }
|
|
36
40
|
*/
|
|
37
41
|
export const POST: APIRoute = async ({ request, cookies }) => {
|
|
38
|
-
const
|
|
39
|
-
if (
|
|
40
|
-
return new Response('Unauthorized', { status: 401 })
|
|
41
|
-
}
|
|
42
|
+
const denied = requireAdmin(cookies.get('setzkasten_session')?.value)
|
|
43
|
+
if (denied) return denied
|
|
42
44
|
|
|
43
45
|
const tokenResult = await resolveGitHubTokenForRequest(request)
|
|
44
46
|
if (!tokenResult.ok) {
|
|
@@ -47,7 +49,7 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
47
49
|
const githubToken = tokenResult.value
|
|
48
50
|
|
|
49
51
|
try {
|
|
50
|
-
const body = await request.json() as {
|
|
52
|
+
const body = (await request.json()) as {
|
|
51
53
|
owner?: string
|
|
52
54
|
repo?: string
|
|
53
55
|
branch?: string
|
|
@@ -57,7 +59,12 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
57
59
|
|
|
58
60
|
const storage = await resolveStorageConfigForRequest(request, body)
|
|
59
61
|
if (!storage) {
|
|
60
|
-
return Response.json(
|
|
62
|
+
return Response.json(
|
|
63
|
+
{
|
|
64
|
+
error: 'Could not resolve owner/repo. Set SETZKASTEN_OWNER and SETZKASTEN_REPO env vars.',
|
|
65
|
+
},
|
|
66
|
+
{ status: 400 },
|
|
67
|
+
)
|
|
61
68
|
}
|
|
62
69
|
const { owner, repo, branch, projectPrefix } = storage
|
|
63
70
|
const { pagePath } = body
|
|
@@ -89,7 +96,7 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
89
96
|
)
|
|
90
97
|
}
|
|
91
98
|
|
|
92
|
-
const treeData = await treeResponse.json() as {
|
|
99
|
+
const treeData = (await treeResponse.json()) as {
|
|
93
100
|
tree: Array<{ path: string; type: string }>
|
|
94
101
|
}
|
|
95
102
|
const files: RepoFile[] = treeData.tree.map((item) => ({
|
|
@@ -108,7 +115,10 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
108
115
|
pageSource = await fetchFileContent(owner, repo, branch, fullPagePath, githubToken)
|
|
109
116
|
}
|
|
110
117
|
if (!pageSource) {
|
|
111
|
-
return Response.json(
|
|
118
|
+
return Response.json(
|
|
119
|
+
{ error: `Could not read page source: ${fullPagePath}` },
|
|
120
|
+
{ status: 404 },
|
|
121
|
+
)
|
|
112
122
|
}
|
|
113
123
|
|
|
114
124
|
// Extract section imports
|
|
@@ -131,7 +141,13 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
131
141
|
|
|
132
142
|
if (!managedSections.has(imp.sectionKey)) {
|
|
133
143
|
// Unmanaged section — full analysis
|
|
134
|
-
const sectionSource = await fetchFileContent(
|
|
144
|
+
const sectionSource = await fetchFileContent(
|
|
145
|
+
owner,
|
|
146
|
+
repo,
|
|
147
|
+
branch,
|
|
148
|
+
imp.resolvedPath,
|
|
149
|
+
githubToken,
|
|
150
|
+
)
|
|
135
151
|
if (!sectionSource) continue
|
|
136
152
|
|
|
137
153
|
const section = await analyzeAstroSection(
|
|
@@ -144,7 +160,13 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
144
160
|
} else {
|
|
145
161
|
// Managed section — check for missing fields
|
|
146
162
|
const existingFieldKeys = managedSections.get(imp.sectionKey)!
|
|
147
|
-
const sectionSource = await fetchFileContent(
|
|
163
|
+
const sectionSource = await fetchFileContent(
|
|
164
|
+
owner,
|
|
165
|
+
repo,
|
|
166
|
+
branch,
|
|
167
|
+
imp.resolvedPath,
|
|
168
|
+
githubToken,
|
|
169
|
+
)
|
|
148
170
|
if (!sectionSource) continue
|
|
149
171
|
|
|
150
172
|
const inferred = await analyzeAstroSection(
|
|
@@ -157,7 +179,13 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
157
179
|
// Load existing content to filter out fields whose value already exists
|
|
158
180
|
// (e.g. template has ctaText="GitHub öffnen" but schema has buttonText with same value)
|
|
159
181
|
const sectionJsonPath = `${contentPath}/_sections/${imp.sectionKey}.json`
|
|
160
|
-
const sectionJson = await fetchFileContent(
|
|
182
|
+
const sectionJson = await fetchFileContent(
|
|
183
|
+
owner,
|
|
184
|
+
repo,
|
|
185
|
+
branch,
|
|
186
|
+
sectionJsonPath,
|
|
187
|
+
githubToken,
|
|
188
|
+
)
|
|
161
189
|
const existingValues = new Set<string>()
|
|
162
190
|
if (sectionJson) {
|
|
163
191
|
try {
|
|
@@ -165,11 +193,13 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
165
193
|
for (const val of Object.values(data)) {
|
|
166
194
|
if (typeof val === 'string' && val.length >= 2) existingValues.add(val)
|
|
167
195
|
}
|
|
168
|
-
} catch {
|
|
196
|
+
} catch {
|
|
197
|
+
/* ignore parse errors */
|
|
198
|
+
}
|
|
169
199
|
}
|
|
170
200
|
|
|
171
201
|
// Find fields that aren't in the schema AND whose value isn't already stored
|
|
172
|
-
const missingFields = inferred.fields.filter(f => {
|
|
202
|
+
const missingFields = inferred.fields.filter((f) => {
|
|
173
203
|
if (existingFieldKeys.has(f.key)) return false
|
|
174
204
|
if (typeof f.defaultValue === 'string' && existingValues.has(f.defaultValue)) return false
|
|
175
205
|
return true
|
|
@@ -189,10 +219,11 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
189
219
|
// Always analyze page-level inline content (in addition to imported sections).
|
|
190
220
|
// A page can have imported sections AND inline content (headings, text, arrays).
|
|
191
221
|
{
|
|
192
|
-
const pageKeyNorm =
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
222
|
+
const pageKeyNorm =
|
|
223
|
+
pagePath
|
|
224
|
+
.replace(/^src\/pages\//, '')
|
|
225
|
+
.replace(/\/(index)?\.astro$/, '')
|
|
226
|
+
.replace(/\.astro$/, '') || 'index'
|
|
196
227
|
const sectionKey = '_page_' + pageKeyNorm.replace(/\//g, '_')
|
|
197
228
|
|
|
198
229
|
const pageSection = await analyzeAstroSection(
|
|
@@ -213,7 +244,13 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
213
244
|
// Already in config — check for new unbound fields
|
|
214
245
|
const existingFieldKeys = managedSections.get(sectionKey)!
|
|
215
246
|
const sectionJsonPath = `${contentPath}/_sections/${sectionKey}.json`
|
|
216
|
-
const sectionJson = await fetchFileContent(
|
|
247
|
+
const sectionJson = await fetchFileContent(
|
|
248
|
+
owner,
|
|
249
|
+
repo,
|
|
250
|
+
branch,
|
|
251
|
+
sectionJsonPath,
|
|
252
|
+
githubToken,
|
|
253
|
+
)
|
|
217
254
|
const existingValues = new Set<string>()
|
|
218
255
|
if (sectionJson) {
|
|
219
256
|
try {
|
|
@@ -221,9 +258,11 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
221
258
|
for (const val of Object.values(data)) {
|
|
222
259
|
if (typeof val === 'string' && val.length >= 2) existingValues.add(val)
|
|
223
260
|
}
|
|
224
|
-
} catch {
|
|
261
|
+
} catch {
|
|
262
|
+
/* ignore */
|
|
263
|
+
}
|
|
225
264
|
}
|
|
226
|
-
const missingFields = pageSection.fields.filter(f => {
|
|
265
|
+
const missingFields = pageSection.fields.filter((f) => {
|
|
227
266
|
if (existingFieldKeys.has(f.key)) return false
|
|
228
267
|
if (typeof f.defaultValue === 'string' && existingValues.has(f.defaultValue)) return false
|
|
229
268
|
return true
|
|
@@ -246,7 +285,13 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
246
285
|
{
|
|
247
286
|
const layoutImport = extractLayoutImport(pageSource, fullPagePath, files, projectPrefix)
|
|
248
287
|
if (layoutImport?.resolvedPath) {
|
|
249
|
-
const layoutSource = await fetchFileContent(
|
|
288
|
+
const layoutSource = await fetchFileContent(
|
|
289
|
+
owner,
|
|
290
|
+
repo,
|
|
291
|
+
branch,
|
|
292
|
+
layoutImport.resolvedPath,
|
|
293
|
+
githubToken,
|
|
294
|
+
)
|
|
250
295
|
if (layoutSource) {
|
|
251
296
|
for (const region of extractLayoutRegions(layoutSource)) {
|
|
252
297
|
const sectionKey = `_layout_${region.name}`
|
|
@@ -255,8 +300,10 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
255
300
|
// Wrap the region in a minimal Astro file for the analyzer
|
|
256
301
|
const regionSource = `---\n---\n\n${region.html}`
|
|
257
302
|
const section = await analyzeAstroSection(
|
|
258
|
-
regionSource,
|
|
259
|
-
|
|
303
|
+
regionSource,
|
|
304
|
+
sectionKey,
|
|
305
|
+
region.name,
|
|
306
|
+
layoutImport.resolvedPath,
|
|
260
307
|
{ mode: 'page' },
|
|
261
308
|
)
|
|
262
309
|
;(section as any).isPageLevel = true
|
|
@@ -346,7 +393,7 @@ async function fetchFileContent(
|
|
|
346
393
|
token,
|
|
347
394
|
)
|
|
348
395
|
if (!response.ok) return null
|
|
349
|
-
const data = await response.json() as { content: string; encoding: string }
|
|
396
|
+
const data = (await response.json()) as { content: string; encoding: string }
|
|
350
397
|
return data.encoding === 'base64'
|
|
351
398
|
? Buffer.from(data.content, 'base64').toString('utf-8')
|
|
352
399
|
: data.content
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { type RepoFile, analyzeProject } from '@setzkasten-cms/core/init'
|
|
1
2
|
import type { APIRoute } from 'astro'
|
|
2
|
-
import {
|
|
3
|
-
import { findAstroPages, extractSectionImports } from '../init/astro-detector'
|
|
3
|
+
import { extractSectionImports, findAstroPages } from '../init/astro-detector'
|
|
4
4
|
import { analyzeAstroSection } from '../init/astro-section-analyzer-v2'
|
|
5
|
+
import { requireAdmin } from './_auth-guard'
|
|
5
6
|
import { resolveGitHubTokenForRequest } from './_github-token'
|
|
6
7
|
|
|
7
8
|
/**
|
|
@@ -11,11 +12,12 @@ import { resolveGitHubTokenForRequest } from './_github-token'
|
|
|
11
12
|
* Body: { owner: string, repo: string, branch?: string }
|
|
12
13
|
*/
|
|
13
14
|
export const POST: APIRoute = async ({ request, cookies }) => {
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
// Init wizard is admin-only — editors don't run it, and the route mints
|
|
16
|
+
// App-installation tokens against arbitrary repos from the body. Cookie-
|
|
17
|
+
// presence check used to be the only gate, which let any editor (or any
|
|
18
|
+
// forger pre-C1) probe / scan installations.
|
|
19
|
+
const denied = requireAdmin(cookies.get('setzkasten_session')?.value)
|
|
20
|
+
if (denied) return denied
|
|
19
21
|
|
|
20
22
|
const tokenResult = await resolveGitHubTokenForRequest(request)
|
|
21
23
|
if (!tokenResult.ok) {
|
|
@@ -24,7 +26,7 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
24
26
|
const githubToken = tokenResult.value
|
|
25
27
|
|
|
26
28
|
try {
|
|
27
|
-
const body = await request.json() as { owner: string; repo: string; branch?: string }
|
|
29
|
+
const body = (await request.json()) as { owner: string; repo: string; branch?: string }
|
|
28
30
|
const { owner, repo, branch = 'main' } = body
|
|
29
31
|
|
|
30
32
|
if (!owner || !repo) {
|
|
@@ -44,7 +46,7 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
44
46
|
)
|
|
45
47
|
}
|
|
46
48
|
|
|
47
|
-
const treeData = await treeResponse.json() as {
|
|
49
|
+
const treeData = (await treeResponse.json()) as {
|
|
48
50
|
tree: Array<{ path: string; type: string; sha: string }>
|
|
49
51
|
}
|
|
50
52
|
|
|
@@ -88,7 +90,13 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
88
90
|
if (allSections.has(imp.sectionKey)) continue
|
|
89
91
|
if (!imp.resolvedPath) continue
|
|
90
92
|
|
|
91
|
-
const sectionSource = await fetchFileContent(
|
|
93
|
+
const sectionSource = await fetchFileContent(
|
|
94
|
+
owner,
|
|
95
|
+
repo,
|
|
96
|
+
branch,
|
|
97
|
+
imp.resolvedPath,
|
|
98
|
+
githubToken,
|
|
99
|
+
)
|
|
92
100
|
if (!sectionSource) continue
|
|
93
101
|
|
|
94
102
|
const section = await analyzeAstroSection(
|
|
@@ -153,7 +161,7 @@ async function fetchFileContent(
|
|
|
153
161
|
)
|
|
154
162
|
if (!response.ok) return null
|
|
155
163
|
|
|
156
|
-
const data = await response.json() as { content: string; encoding: string }
|
|
164
|
+
const data = (await response.json()) as { content: string; encoding: string }
|
|
157
165
|
if (data.encoding === 'base64') {
|
|
158
166
|
return Buffer.from(data.content, 'base64').toString('utf-8')
|
|
159
167
|
}
|
package/src/api-routes/pages.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { APIRoute } from 'astro'
|
|
2
|
+
import { parseSession } from './_auth-guard'
|
|
3
|
+
import { cachedFetch } from './_github-cache'
|
|
2
4
|
import { resolveGitHubTokenForRequest } from './_github-token'
|
|
3
|
-
import {
|
|
5
|
+
import { type PagesMetaTarget, readPagesMeta } from './_pages-meta-store'
|
|
4
6
|
import { resolveStorageConfigForRequest } from './_storage-config'
|
|
5
|
-
import { cachedFetch } from './_github-cache'
|
|
6
7
|
|
|
7
8
|
interface PageInfo {
|
|
8
9
|
path: string
|
|
@@ -29,7 +30,9 @@ declare const __SETZKASTEN_PAGES__: PageInfo[] | undefined
|
|
|
29
30
|
*/
|
|
30
31
|
export function resolvePages(): PageInfo[] {
|
|
31
32
|
const buildPages = typeof __SETZKASTEN_PAGES__ !== 'undefined' ? __SETZKASTEN_PAGES__ : null
|
|
32
|
-
return
|
|
33
|
+
return (
|
|
34
|
+
buildPages ?? ((globalThis as Record<string, unknown>).__SETZKASTEN_PAGES__ as PageInfo[]) ?? []
|
|
35
|
+
)
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
/**
|
|
@@ -47,11 +50,16 @@ export function resolvePages(): PageInfo[] {
|
|
|
47
50
|
* would see the admin's own page list — typically a single "index"
|
|
48
51
|
* stub — instead of its real pages.
|
|
49
52
|
*/
|
|
50
|
-
export const GET: APIRoute = async ({ request }) => {
|
|
53
|
+
export const GET: APIRoute = async ({ request, cookies }) => {
|
|
54
|
+
// Authenticated users only — pre-fix this was an unauthenticated
|
|
55
|
+
// reconnaissance endpoint that also minted an installation token to
|
|
56
|
+
// crawl `src/pages/` of any website the App could reach.
|
|
57
|
+
if (!parseSession(cookies.get('setzkasten_session')?.value)) {
|
|
58
|
+
return new Response('Unauthorized', { status: 401 })
|
|
59
|
+
}
|
|
60
|
+
|
|
51
61
|
const isMulti = request.headers.get('x-sk-website') !== null
|
|
52
|
-
const pages = isMulti
|
|
53
|
-
? await fetchPagesFromGitHub(request).catch(() => [])
|
|
54
|
-
: resolvePages()
|
|
62
|
+
const pages = isMulti ? await fetchPagesFromGitHub(request).catch(() => []) : resolvePages()
|
|
55
63
|
|
|
56
64
|
const enriched = await enrichWithLastModified(pages, request).catch(() => pages)
|
|
57
65
|
|
|
@@ -116,10 +124,7 @@ async function fetchPagesFromGitHub(request: Request): Promise<PageInfo[]> {
|
|
|
116
124
|
})
|
|
117
125
|
}
|
|
118
126
|
|
|
119
|
-
async function enrichWithLastModified(
|
|
120
|
-
pages: PageInfo[],
|
|
121
|
-
request: Request,
|
|
122
|
-
): Promise<PageInfo[]> {
|
|
127
|
+
async function enrichWithLastModified(pages: PageInfo[], request: Request): Promise<PageInfo[]> {
|
|
123
128
|
if (pages.length === 0) return pages
|
|
124
129
|
|
|
125
130
|
const storage = await resolveStorageConfigForRequest(request)
|