@setzkasten-cms/astro-admin 0.8.0 → 1.1.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/package.json +16 -6
- package/src/admin-page.astro +1 -1
- package/src/api-routes/__tests__/auth-guard.test.ts +134 -0
- package/src/api-routes/__tests__/github-token-for-request.test.ts +112 -0
- package/src/api-routes/__tests__/github-token.test.ts +78 -0
- package/src/api-routes/__tests__/global-config-theme.test.ts +71 -0
- package/src/api-routes/__tests__/init-scan-page-resolve-config.test.ts +61 -0
- package/src/api-routes/__tests__/license-tier.test.ts +45 -0
- package/src/api-routes/__tests__/migrate-to-multi.test.ts +189 -0
- package/src/api-routes/__tests__/pages-meta-store.test.ts +179 -0
- package/src/api-routes/__tests__/route-registry.test.ts +120 -0
- package/src/api-routes/__tests__/session-cookie.test.ts +67 -0
- package/src/api-routes/__tests__/setup-github-app-callback.test.ts +145 -0
- package/src/api-routes/__tests__/setup-github-app-repos.test.ts +192 -0
- package/src/api-routes/__tests__/setup-github-app.test.ts +107 -0
- package/src/api-routes/__tests__/storage-config-for-request.test.ts +78 -0
- package/src/api-routes/__tests__/website-resolver-bootstrap-standalone.test.ts +85 -0
- package/src/api-routes/__tests__/website-resolver-bootstrap.test.ts +108 -0
- package/src/api-routes/__tests__/website-resolver.test.ts +123 -0
- package/src/api-routes/__tests__/websites-add.test.ts +305 -0
- package/src/api-routes/__tests__/websites-list.test.ts +112 -0
- package/src/api-routes/__tests__/websites-remove.test.ts +155 -0
- package/src/api-routes/_auth-guard.ts +134 -13
- package/src/api-routes/_github-token.ts +64 -0
- package/src/api-routes/_license-tier.ts +25 -0
- package/src/api-routes/_pages-meta-store.ts +134 -0
- package/src/api-routes/_session-cookie.ts +42 -0
- package/src/api-routes/_storage-config.ts +64 -4
- package/src/api-routes/_vercel-origin.ts +22 -0
- package/src/api-routes/_website-resolver.ts +243 -0
- package/src/api-routes/_websites-store.ts +120 -0
- package/src/api-routes/asset-proxy.ts +6 -4
- package/src/api-routes/auth-callback.ts +6 -7
- package/src/api-routes/auth-logout.ts +5 -1
- package/src/api-routes/auth-setzkasten-login.ts +21 -10
- package/src/api-routes/catalog-add.ts +9 -5
- package/src/api-routes/catalog-export.ts +8 -4
- package/src/api-routes/config.ts +12 -5
- package/src/api-routes/editors.ts +79 -10
- package/src/api-routes/github-proxy.ts +5 -5
- package/src/api-routes/global-config.ts +23 -6
- package/src/api-routes/init-add-section.ts +13 -5
- package/src/api-routes/init-apply.ts +5 -3
- package/src/api-routes/init-migrate.ts +7 -5
- package/src/api-routes/init-scan-page.ts +26 -6
- package/src/api-routes/init-scan.ts +5 -3
- package/src/api-routes/migrate-to-multi.ts +255 -0
- package/src/api-routes/pages.ts +118 -4
- package/src/api-routes/section-add.ts +15 -5
- package/src/api-routes/section-commit-pending.ts +18 -5
- package/src/api-routes/section-delete.ts +15 -5
- package/src/api-routes/section-duplicate.ts +15 -5
- package/src/api-routes/section-prepare-copy.ts +15 -4
- package/src/api-routes/section-prepare.ts +9 -5
- package/src/api-routes/setup-github-app-bounce.ts +52 -0
- package/src/api-routes/setup-github-app-branches.ts +63 -0
- package/src/api-routes/setup-github-app-callback.ts +53 -0
- package/src/api-routes/setup-github-app-installed.ts +44 -0
- package/src/api-routes/setup-github-app-repos.ts +46 -0
- package/src/api-routes/setup-github-app.ts +58 -0
- package/src/api-routes/updater-register.ts +6 -23
- package/src/api-routes/updater-transfer.ts +1 -12
- package/src/api-routes/websites-add.ts +113 -0
- package/src/api-routes/websites-list.ts +40 -0
- package/src/api-routes/websites-remove.ts +74 -0
- package/src/init/__tests__/patcher-mixed-content-wrapper.test.ts +90 -0
- package/src/init/template-patcher-v2.ts +33 -0
- package/LICENSE +0 -37
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
import type { APIRoute } from 'astro'
|
|
2
2
|
import { resolveStorageConfig } from './_storage-config'
|
|
3
3
|
import { parseSession } from './_auth-guard'
|
|
4
|
+
import { resolveConfigRepoToken } from './_github-token'
|
|
4
5
|
import type { ContentEditorConfig } from '@setzkasten-cms/core'
|
|
5
6
|
import { cachedFetch, invalidateCache } from './_github-cache'
|
|
6
7
|
import { withTrailers } from './_commit-trailers'
|
|
7
8
|
|
|
8
9
|
const EDITORS_FILE = (contentPath: string) => `${contentPath}/_editors.json`
|
|
9
10
|
|
|
11
|
+
// In Multi-Mode editors are global across all websites and live in the
|
|
12
|
+
// config-repo; in Single-Mode the config-repo IS the website-repo. Either
|
|
13
|
+
// way the answer is "the build-time-configured storage" — never the
|
|
14
|
+
// per-website storage that the X-SK-Website header would route to.
|
|
15
|
+
function configRepoStorage(): { owner: string; repo: string; branch: string } | null {
|
|
16
|
+
const storage = resolveStorageConfig()
|
|
17
|
+
if (!storage) return null
|
|
18
|
+
return { owner: storage.owner, repo: storage.repo, branch: storage.branch }
|
|
19
|
+
}
|
|
20
|
+
|
|
10
21
|
// ---------------------------------------------------------------------------
|
|
11
22
|
// GET /api/setzkasten/editors
|
|
12
23
|
// Returns the current editors list from _editors.json.
|
|
@@ -17,17 +28,18 @@ export const GET: APIRoute = async ({ cookies }) => {
|
|
|
17
28
|
const session = parseSession(cookies.get('setzkasten_session')?.value)
|
|
18
29
|
if (!session) return new Response('Unauthorized', { status: 401 })
|
|
19
30
|
|
|
20
|
-
const
|
|
21
|
-
if (!
|
|
31
|
+
const tokenResult = await resolveConfigRepoToken()
|
|
32
|
+
if (!tokenResult.ok) return new Response('GitHub token not configured', { status: 500 })
|
|
22
33
|
|
|
23
|
-
const storage =
|
|
34
|
+
const storage = configRepoStorage()
|
|
24
35
|
if (!storage) return Response.json({ error: 'Could not resolve storage config' }, { status: 400 })
|
|
25
36
|
|
|
26
|
-
const serverConfig = (globalThis as
|
|
37
|
+
const serverConfig = (globalThis as { __SETZKASTEN_CONFIG__?: { storage?: { contentPath?: string } } })
|
|
38
|
+
.__SETZKASTEN_CONFIG__
|
|
27
39
|
const contentPath = serverConfig?.storage?.contentPath ?? 'content'
|
|
28
40
|
const { owner, repo, branch } = storage
|
|
29
41
|
|
|
30
|
-
const raw = await readEditorsFile(owner, repo, branch, contentPath,
|
|
42
|
+
const raw = await readEditorsFile(owner, repo, branch, contentPath, tokenResult.value)
|
|
31
43
|
return Response.json(raw ?? [])
|
|
32
44
|
}
|
|
33
45
|
|
|
@@ -42,13 +54,14 @@ export const PUT: APIRoute = async ({ request, cookies }) => {
|
|
|
42
54
|
if (!session) return new Response('Unauthorized', { status: 401 })
|
|
43
55
|
if (session.user.role !== 'admin') return new Response('Forbidden', { status: 403 })
|
|
44
56
|
|
|
45
|
-
const
|
|
46
|
-
if (!
|
|
57
|
+
const tokenResult = await resolveConfigRepoToken()
|
|
58
|
+
if (!tokenResult.ok) return new Response('GitHub token not configured', { status: 500 })
|
|
47
59
|
|
|
48
|
-
const storage =
|
|
60
|
+
const storage = configRepoStorage()
|
|
49
61
|
if (!storage) return Response.json({ error: 'Could not resolve storage config' }, { status: 400 })
|
|
50
62
|
|
|
51
|
-
const serverConfig = (globalThis as
|
|
63
|
+
const serverConfig = (globalThis as { __SETZKASTEN_CONFIG__?: { storage?: { contentPath?: string } } })
|
|
64
|
+
.__SETZKASTEN_CONFIG__
|
|
52
65
|
const contentPath = serverConfig?.storage?.contentPath ?? 'content'
|
|
53
66
|
const { owner, repo, branch } = storage
|
|
54
67
|
|
|
@@ -63,7 +76,7 @@ export const PUT: APIRoute = async ({ request, cookies }) => {
|
|
|
63
76
|
const filePath = EDITORS_FILE(contentPath)
|
|
64
77
|
const fileContent = JSON.stringify(editors, null, 2)
|
|
65
78
|
const headers = {
|
|
66
|
-
Authorization: `Bearer ${
|
|
79
|
+
Authorization: `Bearer ${tokenResult.value}`,
|
|
67
80
|
Accept: 'application/vnd.github+json',
|
|
68
81
|
'X-GitHub-Api-Version': '2022-11-28',
|
|
69
82
|
'Content-Type': 'application/json',
|
|
@@ -134,3 +147,59 @@ export async function readEditorsFile(
|
|
|
134
147
|
return JSON.parse(raw) as ContentEditorConfig[]
|
|
135
148
|
})
|
|
136
149
|
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Discriminated result for the editors-file fetch. Lets callers decide the
|
|
153
|
+
* fail-mode policy: the auth-guard wants to ALLOW when the file is genuinely
|
|
154
|
+
* absent (no restrictions configured) but DENY when the fetch errors out.
|
|
155
|
+
* The basic readEditorsFile() above returns null for both cases, which is
|
|
156
|
+
* unsafe for authorization checks.
|
|
157
|
+
*
|
|
158
|
+
* Caller is responsible for caching — this function never reads from or
|
|
159
|
+
* writes to the shared cache, because caching an "error" state would
|
|
160
|
+
* silently extend privilege-escalation windows.
|
|
161
|
+
*/
|
|
162
|
+
export type EditorsStatus =
|
|
163
|
+
| { kind: 'absent' }
|
|
164
|
+
| { kind: 'present'; editors: ContentEditorConfig[] }
|
|
165
|
+
| { kind: 'error'; message: string }
|
|
166
|
+
|
|
167
|
+
export async function readEditorsFileStatus(
|
|
168
|
+
owner: string,
|
|
169
|
+
repo: string,
|
|
170
|
+
branch: string,
|
|
171
|
+
contentPath: string,
|
|
172
|
+
token: string,
|
|
173
|
+
): Promise<EditorsStatus> {
|
|
174
|
+
let res: Response
|
|
175
|
+
try {
|
|
176
|
+
res = await fetch(
|
|
177
|
+
`https://api.github.com/repos/${owner}/${repo}/contents/${EDITORS_FILE(contentPath)}?ref=${branch}`,
|
|
178
|
+
{
|
|
179
|
+
headers: {
|
|
180
|
+
Authorization: `Bearer ${token}`,
|
|
181
|
+
Accept: 'application/vnd.github+json',
|
|
182
|
+
'X-GitHub-Api-Version': '2022-11-28',
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
)
|
|
186
|
+
} catch (err) {
|
|
187
|
+
return { kind: 'error', message: err instanceof Error ? err.message : 'network error' }
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (res.status === 404) return { kind: 'absent' }
|
|
191
|
+
if (!res.ok) {
|
|
192
|
+
return { kind: 'error', message: `GitHub returned ${res.status}` }
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
try {
|
|
196
|
+
const data = (await res.json()) as { content: string; encoding: string }
|
|
197
|
+
const raw =
|
|
198
|
+
data.encoding === 'base64'
|
|
199
|
+
? Buffer.from(data.content, 'base64').toString('utf-8')
|
|
200
|
+
: data.content
|
|
201
|
+
return { kind: 'present', editors: JSON.parse(raw) as ContentEditorConfig[] }
|
|
202
|
+
} catch (err) {
|
|
203
|
+
return { kind: 'error', message: err instanceof Error ? err.message : 'parse error' }
|
|
204
|
+
}
|
|
205
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { APIRoute } from 'astro'
|
|
2
2
|
import { writeFile } from 'node:fs/promises'
|
|
3
3
|
import { join } from 'node:path'
|
|
4
|
+
import { resolveGitHubTokenForRequest } from './_github-token'
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Server-side proxy for GitHub API calls.
|
|
@@ -21,12 +22,11 @@ export const ALL: APIRoute = async ({ params, request, cookies }) => {
|
|
|
21
22
|
return new Response('Missing path', { status: 400 })
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if (!githubToken) {
|
|
28
|
-
return new Response('GitHub token not configured', { status: 500 })
|
|
25
|
+
const tokenResult = await resolveGitHubTokenForRequest(request)
|
|
26
|
+
if (!tokenResult.ok) {
|
|
27
|
+
return new Response(tokenResult.error.message, { status: 500 })
|
|
29
28
|
}
|
|
29
|
+
const githubToken = tokenResult.value
|
|
30
30
|
|
|
31
31
|
const githubUrl = `https://api.github.com/${githubPath}`
|
|
32
32
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { APIRoute } from 'astro'
|
|
2
2
|
import { parseSession } from './_auth-guard'
|
|
3
3
|
import { resolveStorageConfig } from './_storage-config'
|
|
4
|
+
import { resolveConfigRepoToken } from './_github-token'
|
|
4
5
|
import { cachedFetch, invalidateCache } from './_github-cache'
|
|
5
6
|
import { withTrailers } from './_commit-trailers'
|
|
6
7
|
|
|
@@ -12,6 +13,11 @@ export interface GlobalConfig {
|
|
|
12
13
|
authDomain: string
|
|
13
14
|
projectId: string
|
|
14
15
|
}
|
|
16
|
+
theme?: {
|
|
17
|
+
primaryColor?: string
|
|
18
|
+
brandName?: string
|
|
19
|
+
logo?: string
|
|
20
|
+
}
|
|
15
21
|
}
|
|
16
22
|
|
|
17
23
|
// ---------------------------------------------------------------------------
|
|
@@ -42,7 +48,7 @@ export const PUT: APIRoute = async ({ request, cookies }) => {
|
|
|
42
48
|
return Response.json({ error: 'Invalid request body' }, { status: 400 })
|
|
43
49
|
}
|
|
44
50
|
|
|
45
|
-
const current = await readGlobalConfig() ?? {}
|
|
51
|
+
const current = (await readGlobalConfig()) ?? {}
|
|
46
52
|
const next: GlobalConfig = { ...current }
|
|
47
53
|
for (const [k, v] of Object.entries(patch)) {
|
|
48
54
|
if (v === null) delete (next as Record<string, unknown>)[k]
|
|
@@ -54,23 +60,34 @@ export const PUT: APIRoute = async ({ request, cookies }) => {
|
|
|
54
60
|
|
|
55
61
|
// ---------------------------------------------------------------------------
|
|
56
62
|
// Helpers
|
|
63
|
+
//
|
|
64
|
+
// Global config is a Setzkasten-instance-level file that lives in the
|
|
65
|
+
// config-repo regardless of which website the request is targeting:
|
|
66
|
+
// - Single-Mode: config-repo == website-repo, so this is fine
|
|
67
|
+
// - Multi-Mode: config-repo holds editors + global config + websites.json
|
|
68
|
+
// We deliberately ignore the request and X-SK-Website here; otherwise
|
|
69
|
+
// global config would ping-pong between per-website locations as users
|
|
70
|
+
// switch active sites.
|
|
57
71
|
// ---------------------------------------------------------------------------
|
|
58
72
|
|
|
59
|
-
function getStorageParams() {
|
|
60
|
-
const serverConfig = (globalThis as
|
|
73
|
+
async function getStorageParams() {
|
|
74
|
+
const serverConfig = (globalThis as { __SETZKASTEN_CONFIG__?: { storage?: { contentPath?: string } } })
|
|
75
|
+
.__SETZKASTEN_CONFIG__
|
|
61
76
|
const storage = resolveStorageConfig()
|
|
62
77
|
if (!storage) return null
|
|
78
|
+
const tokenResult = await resolveConfigRepoToken()
|
|
79
|
+
if (!tokenResult.ok) return null
|
|
63
80
|
return {
|
|
64
81
|
owner: storage.owner,
|
|
65
82
|
repo: storage.repo,
|
|
66
83
|
branch: storage.branch,
|
|
67
84
|
contentPath: serverConfig?.storage?.contentPath ?? 'content',
|
|
68
|
-
token:
|
|
85
|
+
token: tokenResult.value,
|
|
69
86
|
}
|
|
70
87
|
}
|
|
71
88
|
|
|
72
89
|
export async function readGlobalConfig(): Promise<GlobalConfig | null> {
|
|
73
|
-
const params = getStorageParams()
|
|
90
|
+
const params = await getStorageParams()
|
|
74
91
|
if (!params) return null
|
|
75
92
|
const { owner, repo, branch, contentPath, token } = params
|
|
76
93
|
const key = `global-config:${owner}/${repo}:${branch}`
|
|
@@ -89,7 +106,7 @@ export async function readGlobalConfig(): Promise<GlobalConfig | null> {
|
|
|
89
106
|
}
|
|
90
107
|
|
|
91
108
|
export async function writeGlobalConfig(config: GlobalConfig): Promise<void> {
|
|
92
|
-
const params = getStorageParams()
|
|
109
|
+
const params = await getStorageParams()
|
|
93
110
|
if (!params) throw new Error('Storage not configured')
|
|
94
111
|
const { owner, repo, branch, contentPath, token } = params
|
|
95
112
|
invalidateCache(`global-config:${owner}/${repo}:${branch}`)
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { APIRoute } from 'astro'
|
|
2
2
|
import type { InferredSection } from '@setzkasten-cms/core/init'
|
|
3
3
|
import { addSectionToConfig } from '@setzkasten-cms/core/init'
|
|
4
|
-
import {
|
|
4
|
+
import { prefixPath, resolveStorageConfigForRequest } from './_storage-config'
|
|
5
5
|
import { patchTemplateForFields, stripTemplateFallbacks } from '../init/template-patcher-v2'
|
|
6
6
|
import type { RepeatedGroup } from '../init/analyzer-types'
|
|
7
7
|
import { withTrailers } from './_commit-trailers'
|
|
8
|
+
import { resolveGitHubTokenForRequest } from './_github-token'
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* POST /api/setzkasten/init/add-section
|
|
@@ -21,10 +22,11 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
21
22
|
return new Response('Unauthorized', { status: 401 })
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
const
|
|
25
|
-
if (!
|
|
26
|
-
return new Response(
|
|
25
|
+
const tokenResult = await resolveGitHubTokenForRequest(request)
|
|
26
|
+
if (!tokenResult.ok) {
|
|
27
|
+
return new Response(tokenResult.error.message, { status: 500 })
|
|
27
28
|
}
|
|
29
|
+
const githubToken = tokenResult.value
|
|
28
30
|
|
|
29
31
|
try {
|
|
30
32
|
const body = await request.json() as {
|
|
@@ -38,7 +40,7 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
38
40
|
contentPath?: string
|
|
39
41
|
}
|
|
40
42
|
|
|
41
|
-
const storage =
|
|
43
|
+
const storage = await resolveStorageConfigForRequest(request, body)
|
|
42
44
|
if (!storage) {
|
|
43
45
|
return Response.json({ error: 'Could not resolve owner/repo. Set SETZKASTEN_OWNER and SETZKASTEN_REPO env vars.' }, { status: 400 })
|
|
44
46
|
}
|
|
@@ -292,6 +294,12 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
292
294
|
return Response.json({ error: commitResult.error }, { status: 500 })
|
|
293
295
|
}
|
|
294
296
|
|
|
297
|
+
const { recordPageEdit } = await import('./_pages-meta-store.js')
|
|
298
|
+
await recordPageEdit(
|
|
299
|
+
{ owner, repo, branch, contentPath, token: tokenResult.value },
|
|
300
|
+
pageKey,
|
|
301
|
+
).catch(() => {})
|
|
302
|
+
|
|
295
303
|
return Response.json({
|
|
296
304
|
success: true,
|
|
297
305
|
commitSha: commitResult.sha,
|
|
@@ -3,6 +3,7 @@ import { generateConfigFile, type InferredSection, type ConfigGeneratorInput } f
|
|
|
3
3
|
import { patchAstroConfig } from '../init/astro-config-patcher'
|
|
4
4
|
import { patchTemplateForFields } from '../init/template-patcher-v2'
|
|
5
5
|
import { withTrailers } from './_commit-trailers'
|
|
6
|
+
import { resolveGitHubTokenForRequest } from './_github-token'
|
|
6
7
|
|
|
7
8
|
interface ApplyRequest {
|
|
8
9
|
owner: string
|
|
@@ -36,10 +37,11 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
36
37
|
return new Response('Unauthorized', { status: 401 })
|
|
37
38
|
}
|
|
38
39
|
|
|
39
|
-
const
|
|
40
|
-
if (!
|
|
41
|
-
return new Response(
|
|
40
|
+
const tokenResult = await resolveGitHubTokenForRequest(request)
|
|
41
|
+
if (!tokenResult.ok) {
|
|
42
|
+
return new Response(tokenResult.error.message, { status: 500 })
|
|
42
43
|
}
|
|
44
|
+
const githubToken = tokenResult.value
|
|
43
45
|
|
|
44
46
|
try {
|
|
45
47
|
const body = await request.json() as ApplyRequest
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { APIRoute } from 'astro'
|
|
2
2
|
import type { SetzKastenConfig } from '@setzkasten-cms/core'
|
|
3
|
-
import {
|
|
3
|
+
import { prefixPath, resolveStorageConfigForRequest } from './_storage-config'
|
|
4
4
|
import { patchTemplateForFields } from '../init/template-patcher-v2'
|
|
5
5
|
import { withTrailers } from './_commit-trailers'
|
|
6
|
+
import { resolveGitHubTokenForRequest } from './_github-token'
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* POST /api/setzkasten/init/migrate
|
|
@@ -21,10 +22,11 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
21
22
|
return new Response('Unauthorized', { status: 401 })
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
const
|
|
25
|
-
if (!
|
|
26
|
-
return new Response(
|
|
25
|
+
const tokenResult = await resolveGitHubTokenForRequest(request)
|
|
26
|
+
if (!tokenResult.ok) {
|
|
27
|
+
return new Response(tokenResult.error.message, { status: 500 })
|
|
27
28
|
}
|
|
29
|
+
const githubToken = tokenResult.value
|
|
28
30
|
|
|
29
31
|
try {
|
|
30
32
|
const body = await request.json() as {
|
|
@@ -35,7 +37,7 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
35
37
|
componentPath?: string
|
|
36
38
|
}
|
|
37
39
|
|
|
38
|
-
const storage =
|
|
40
|
+
const storage = await resolveStorageConfigForRequest(request, body)
|
|
39
41
|
if (!storage) {
|
|
40
42
|
return Response.json({ error: 'Could not resolve owner/repo. Set SETZKASTEN_OWNER and SETZKASTEN_REPO env vars.' }, { status: 400 })
|
|
41
43
|
}
|
|
@@ -1,9 +1,28 @@
|
|
|
1
1
|
import type { APIRoute } from 'astro'
|
|
2
2
|
import type { SetzKastenConfig } from '@setzkasten-cms/core'
|
|
3
|
-
import {
|
|
3
|
+
import { prefixPath, resolveStorageConfigForRequest } from './_storage-config'
|
|
4
4
|
import { extractSectionImports, extractLayoutImport } from '../init/astro-detector'
|
|
5
5
|
import { analyzeAstroSection } from '../init/astro-section-analyzer-v2'
|
|
6
6
|
import type { RepoFile } from '@setzkasten-cms/core/init'
|
|
7
|
+
import { resolveGitHubTokenForRequest } from './_github-token'
|
|
8
|
+
|
|
9
|
+
// Build-time constant injected by the Vite define plugin — always available in
|
|
10
|
+
// compiled API routes (unlike page-ssr injectScript which only runs for SSR pages).
|
|
11
|
+
declare const __SETZKASTEN_FULL_CONFIG__: SetzKastenConfig | null | undefined
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Resolves the full Setzkasten config.
|
|
15
|
+
* Reads the Vite build-time constant first; falls back to globalThis for
|
|
16
|
+
* local dev / test environments where the define is not applied.
|
|
17
|
+
*
|
|
18
|
+
* Without the build-time fallback, cold-start Vercel function invocations of
|
|
19
|
+
* this API route see managedSections={} and offer every adopted section
|
|
20
|
+
* (including _layout_header / _layout_footer) for re-adoption.
|
|
21
|
+
*/
|
|
22
|
+
export function resolveFullConfig(): SetzKastenConfig | undefined {
|
|
23
|
+
const buildConfig = typeof __SETZKASTEN_FULL_CONFIG__ !== 'undefined' ? __SETZKASTEN_FULL_CONFIG__ : null
|
|
24
|
+
return (buildConfig ?? (globalThis as any).__SETZKASTEN_FULL_CONFIG__) as SetzKastenConfig | undefined
|
|
25
|
+
}
|
|
7
26
|
|
|
8
27
|
/**
|
|
9
28
|
* POST /api/setzkasten/init/scan-page
|
|
@@ -21,10 +40,11 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
21
40
|
return new Response('Unauthorized', { status: 401 })
|
|
22
41
|
}
|
|
23
42
|
|
|
24
|
-
const
|
|
25
|
-
if (!
|
|
26
|
-
return new Response(
|
|
43
|
+
const tokenResult = await resolveGitHubTokenForRequest(request)
|
|
44
|
+
if (!tokenResult.ok) {
|
|
45
|
+
return new Response(tokenResult.error.message, { status: 500 })
|
|
27
46
|
}
|
|
47
|
+
const githubToken = tokenResult.value
|
|
28
48
|
|
|
29
49
|
try {
|
|
30
50
|
const body = await request.json() as {
|
|
@@ -35,7 +55,7 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
35
55
|
projectRoot?: string
|
|
36
56
|
}
|
|
37
57
|
|
|
38
|
-
const storage =
|
|
58
|
+
const storage = await resolveStorageConfigForRequest(request, body)
|
|
39
59
|
if (!storage) {
|
|
40
60
|
return Response.json({ error: 'Could not resolve owner/repo. Set SETZKASTEN_OWNER and SETZKASTEN_REPO env vars.' }, { status: 400 })
|
|
41
61
|
}
|
|
@@ -47,7 +67,7 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
47
67
|
}
|
|
48
68
|
|
|
49
69
|
// Get current schema to know which sections are already managed + their fields
|
|
50
|
-
const config = (
|
|
70
|
+
const config = resolveFullConfig()
|
|
51
71
|
const managedSections = new Map<string, Set<string>>() // key → field keys
|
|
52
72
|
if (config) {
|
|
53
73
|
for (const product of Object.values(config.products)) {
|
|
@@ -2,6 +2,7 @@ import type { APIRoute } from 'astro'
|
|
|
2
2
|
import { analyzeProject, type RepoFile } from '@setzkasten-cms/core/init'
|
|
3
3
|
import { findAstroPages, extractSectionImports } from '../init/astro-detector'
|
|
4
4
|
import { analyzeAstroSection } from '../init/astro-section-analyzer-v2'
|
|
5
|
+
import { resolveGitHubTokenForRequest } from './_github-token'
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* POST /api/setzkasten/init/scan
|
|
@@ -16,10 +17,11 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
16
17
|
return new Response('Unauthorized', { status: 401 })
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
const
|
|
20
|
-
if (!
|
|
21
|
-
return new Response(
|
|
20
|
+
const tokenResult = await resolveGitHubTokenForRequest(request)
|
|
21
|
+
if (!tokenResult.ok) {
|
|
22
|
+
return new Response(tokenResult.error.message, { status: 500 })
|
|
22
23
|
}
|
|
24
|
+
const githubToken = tokenResult.value
|
|
23
25
|
|
|
24
26
|
try {
|
|
25
27
|
const body = await request.json() as { owner: string; repo: string; branch?: string }
|