@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,9 @@
|
|
|
1
|
+
import { type CommitInfo, parseCoAuthorTrailers } from '@setzkasten-cms/core'
|
|
1
2
|
import type { APIRoute } from 'astro'
|
|
2
|
-
import { resolveStorageConfigForRequest } from './_storage-config'
|
|
3
|
-
import { resolveGitHubTokenForRequest } from './_github-token'
|
|
4
3
|
import { requireAdmin } from './_auth-guard'
|
|
5
|
-
import { parseCoAuthorTrailers, type CommitInfo } from '@setzkasten-cms/core'
|
|
6
4
|
import { cachedFetch } from './_github-cache'
|
|
5
|
+
import { resolveGitHubTokenForRequest } from './_github-token'
|
|
6
|
+
import { resolveStorageConfigForRequest } from './_storage-config'
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* GET /api/setzkasten/history?path=<contentPath>&before=<sha>
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type { APIRoute } from 'astro'
|
|
2
1
|
import {
|
|
3
2
|
LOCAL_ICONS_DISCOVERY_PATHS,
|
|
4
3
|
resolveLocalIconsPaths,
|
|
5
4
|
sanitizeSvg,
|
|
6
5
|
} from '@setzkasten-cms/core'
|
|
7
|
-
import {
|
|
6
|
+
import type { APIRoute } from 'astro'
|
|
8
7
|
import { resolveGitHubTokenForRequest } from './_github-token'
|
|
8
|
+
import { prefixPath, resolveStorageConfigForRequest } from './_storage-config'
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* GET /api/setzkasten/icons/local
|
|
@@ -1,11 +1,18 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { isSafeKey } from '@setzkasten-cms/core'
|
|
2
2
|
import type { InferredSection } from '@setzkasten-cms/core/init'
|
|
3
3
|
import { addSectionToConfig } from '@setzkasten-cms/core/init'
|
|
4
|
-
import {
|
|
5
|
-
import { patchTemplateForFields, stripTemplateFallbacks, detectChildImports, patchChildComponentForFieldPrefix } from '../init/template-patcher-v2'
|
|
4
|
+
import type { APIRoute } from 'astro'
|
|
6
5
|
import type { RepeatedGroup } from '../init/analyzer-types'
|
|
6
|
+
import {
|
|
7
|
+
detectChildImports,
|
|
8
|
+
patchChildComponentForFieldPrefix,
|
|
9
|
+
patchTemplateForFields,
|
|
10
|
+
stripTemplateFallbacks,
|
|
11
|
+
} from '../init/template-patcher-v2'
|
|
12
|
+
import { requireAdmin } from './_auth-guard'
|
|
7
13
|
import { withTrailers } from './_commit-trailers'
|
|
8
14
|
import { resolveGitHubTokenForRequest } from './_github-token'
|
|
15
|
+
import { prefixPath, resolveStorageConfigForRequest } from './_storage-config'
|
|
9
16
|
|
|
10
17
|
/**
|
|
11
18
|
* POST /api/setzkasten/init/add-section
|
|
@@ -17,10 +24,8 @@ import { resolveGitHubTokenForRequest } from './_github-token'
|
|
|
17
24
|
* Body: { owner, repo, branch?, projectRoot, section, pageKey, contentPath? }
|
|
18
25
|
*/
|
|
19
26
|
export const POST: APIRoute = async ({ request, cookies }) => {
|
|
20
|
-
const
|
|
21
|
-
if (
|
|
22
|
-
return new Response('Unauthorized', { status: 401 })
|
|
23
|
-
}
|
|
27
|
+
const denied = requireAdmin(cookies.get('setzkasten_session')?.value)
|
|
28
|
+
if (denied) return denied
|
|
24
29
|
|
|
25
30
|
const tokenResult = await resolveGitHubTokenForRequest(request)
|
|
26
31
|
if (!tokenResult.ok) {
|
|
@@ -29,7 +34,7 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
29
34
|
const githubToken = tokenResult.value
|
|
30
35
|
|
|
31
36
|
try {
|
|
32
|
-
const body = await request.json() as {
|
|
37
|
+
const body = (await request.json()) as {
|
|
33
38
|
owner?: string
|
|
34
39
|
repo?: string
|
|
35
40
|
branch?: string
|
|
@@ -42,7 +47,12 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
42
47
|
|
|
43
48
|
const storage = await resolveStorageConfigForRequest(request, body)
|
|
44
49
|
if (!storage) {
|
|
45
|
-
return Response.json(
|
|
50
|
+
return Response.json(
|
|
51
|
+
{
|
|
52
|
+
error: 'Could not resolve owner/repo. Set SETZKASTEN_OWNER and SETZKASTEN_REPO env vars.',
|
|
53
|
+
},
|
|
54
|
+
{ status: 400 },
|
|
55
|
+
)
|
|
46
56
|
}
|
|
47
57
|
const { owner, repo, branch, projectPrefix } = storage
|
|
48
58
|
const serverConfig = (globalThis as any).__SETZKASTEN_CONFIG__
|
|
@@ -52,6 +62,12 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
52
62
|
if (!section || !pageKey) {
|
|
53
63
|
return Response.json({ error: 'section and pageKey are required' }, { status: 400 })
|
|
54
64
|
}
|
|
65
|
+
if (!isSafeKey(pageKey)) {
|
|
66
|
+
return Response.json({ error: 'invalid pageKey' }, { status: 400 })
|
|
67
|
+
}
|
|
68
|
+
if (!isSafeKey(section.key)) {
|
|
69
|
+
return Response.json({ error: 'invalid section key' }, { status: 400 })
|
|
70
|
+
}
|
|
55
71
|
|
|
56
72
|
const headers = {
|
|
57
73
|
Authorization: `Bearer ${githubToken}`,
|
|
@@ -67,7 +83,12 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
67
83
|
const existingConfig = await fetchFileContent(owner, repo, branch, configPath, githubToken)
|
|
68
84
|
|
|
69
85
|
if (existingConfig) {
|
|
70
|
-
const updatedConfig = addSectionToConfig(
|
|
86
|
+
const updatedConfig = addSectionToConfig(
|
|
87
|
+
existingConfig,
|
|
88
|
+
section.key,
|
|
89
|
+
section,
|
|
90
|
+
section.allFields,
|
|
91
|
+
)
|
|
71
92
|
if (updatedConfig) {
|
|
72
93
|
filesToCommit.push({ path: configPath, content: updatedConfig })
|
|
73
94
|
}
|
|
@@ -75,13 +96,21 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
75
96
|
|
|
76
97
|
// 2. Generate content JSON — merge new fields into existing data (if any)
|
|
77
98
|
const sectionJsonPath = `${contentPath}/_sections/${section.key}.json`
|
|
78
|
-
const existingSectionJson = await fetchFileContent(
|
|
99
|
+
const existingSectionJson = await fetchFileContent(
|
|
100
|
+
owner,
|
|
101
|
+
repo,
|
|
102
|
+
branch,
|
|
103
|
+
sectionJsonPath,
|
|
104
|
+
githubToken,
|
|
105
|
+
)
|
|
79
106
|
|
|
80
107
|
let sectionData: Record<string, unknown> = {}
|
|
81
108
|
if (existingSectionJson) {
|
|
82
109
|
try {
|
|
83
110
|
sectionData = JSON.parse(existingSectionJson)
|
|
84
|
-
} catch {
|
|
111
|
+
} catch {
|
|
112
|
+
/* start fresh if invalid */
|
|
113
|
+
}
|
|
85
114
|
}
|
|
86
115
|
|
|
87
116
|
// Only add values for fields that don't already exist
|
|
@@ -94,7 +123,7 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
94
123
|
}
|
|
95
124
|
|
|
96
125
|
// Reorder JSON keys to match template order (allFields if available, else fields)
|
|
97
|
-
const orderedKeys = (section.allFields ?? section.fields).map(f => f.key)
|
|
126
|
+
const orderedKeys = (section.allFields ?? section.fields).map((f) => f.key)
|
|
98
127
|
const orderedData: Record<string, unknown> = {}
|
|
99
128
|
for (const key of orderedKeys) {
|
|
100
129
|
if (key in sectionData) {
|
|
@@ -116,7 +145,13 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
116
145
|
// 3. Update page config (add section to the end)
|
|
117
146
|
const configKey = '_' + pageKey.replace(/\//g, '_')
|
|
118
147
|
const pageConfigPath = `${contentPath}/pages/${configKey}.json`
|
|
119
|
-
const existingPageConfig = await fetchFileContent(
|
|
148
|
+
const existingPageConfig = await fetchFileContent(
|
|
149
|
+
owner,
|
|
150
|
+
repo,
|
|
151
|
+
branch,
|
|
152
|
+
pageConfigPath,
|
|
153
|
+
githubToken,
|
|
154
|
+
)
|
|
120
155
|
|
|
121
156
|
let pageConfig: { sections: Array<{ key: string; enabled: boolean }> }
|
|
122
157
|
if (existingPageConfig) {
|
|
@@ -154,9 +189,13 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
154
189
|
if (pageSource) resolvedPagePath = indexPath
|
|
155
190
|
}
|
|
156
191
|
if (pageSource) {
|
|
157
|
-
const repeatedGroups: RepeatedGroup[] =
|
|
192
|
+
const repeatedGroups: RepeatedGroup[] =
|
|
193
|
+
(section as any)._analyzerResult?.repeatedGroups ?? []
|
|
158
194
|
let patchedSource = await patchTemplateForFields(
|
|
159
|
-
pageSource,
|
|
195
|
+
pageSource,
|
|
196
|
+
section.key,
|
|
197
|
+
section.allFields ?? section.fields,
|
|
198
|
+
repeatedGroups,
|
|
160
199
|
{ mode: 'page' },
|
|
161
200
|
)
|
|
162
201
|
|
|
@@ -175,14 +214,19 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
175
214
|
// analyzer's text-only defaultValue.
|
|
176
215
|
const existingIsPlain = typeof existing === 'string' && !/<[a-z]/.test(existing)
|
|
177
216
|
const newIsHtml = typeof value === 'string' && /<[a-z]/.test(value as string)
|
|
178
|
-
if (
|
|
217
|
+
if (
|
|
218
|
+
!(key in sectionData) ||
|
|
219
|
+
existing === '' ||
|
|
220
|
+
existing === null ||
|
|
221
|
+
(existingIsPlain && newIsHtml)
|
|
222
|
+
) {
|
|
179
223
|
sectionData[key] = value
|
|
180
224
|
jsonUpdated = true
|
|
181
225
|
}
|
|
182
226
|
}
|
|
183
227
|
// Update the content JSON entry if we added values
|
|
184
228
|
if (jsonUpdated) {
|
|
185
|
-
const jsonIdx = filesToCommit.findIndex(f => f.path === sectionJsonPath)
|
|
229
|
+
const jsonIdx = filesToCommit.findIndex((f) => f.path === sectionJsonPath)
|
|
186
230
|
if (jsonIdx !== -1) {
|
|
187
231
|
filesToCommit[jsonIdx]!.content = JSON.stringify(sectionData, null, 2)
|
|
188
232
|
}
|
|
@@ -209,10 +253,12 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
209
253
|
// Update content JSON with any _classN fields from patcher
|
|
210
254
|
const fields = section.allFields ?? section.fields
|
|
211
255
|
for (const g of repeatedGroups) {
|
|
212
|
-
const topField = fields.find(f => f.key === g.fieldKey)
|
|
256
|
+
const topField = fields.find((f) => f.key === g.fieldKey)
|
|
213
257
|
if (!topField || !Array.isArray(topField.defaultValue)) continue
|
|
214
|
-
sectionData[g.fieldKey] = (topField.defaultValue as unknown[]).filter(
|
|
215
|
-
|
|
258
|
+
sectionData[g.fieldKey] = (topField.defaultValue as unknown[]).filter(
|
|
259
|
+
(item) => item != null,
|
|
260
|
+
)
|
|
261
|
+
const jsonIdx = filesToCommit.findIndex((f) => f.path === sectionJsonPath)
|
|
216
262
|
if (jsonIdx !== -1) {
|
|
217
263
|
filesToCommit[jsonIdx]!.content = JSON.stringify(sectionData, null, 2)
|
|
218
264
|
}
|
|
@@ -220,10 +266,22 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
220
266
|
}
|
|
221
267
|
// No patchPageFile / SECTION_COMPONENTS for page-level sections
|
|
222
268
|
} else if (section.componentPath) {
|
|
223
|
-
const componentSource = await fetchFileContent(
|
|
269
|
+
const componentSource = await fetchFileContent(
|
|
270
|
+
owner,
|
|
271
|
+
repo,
|
|
272
|
+
branch,
|
|
273
|
+
section.componentPath,
|
|
274
|
+
githubToken,
|
|
275
|
+
)
|
|
224
276
|
if (componentSource) {
|
|
225
|
-
const repeatedGroups: RepeatedGroup[] =
|
|
226
|
-
|
|
277
|
+
const repeatedGroups: RepeatedGroup[] =
|
|
278
|
+
(section as any)._analyzerResult?.repeatedGroups ?? []
|
|
279
|
+
const patchedSource = await patchTemplateForFields(
|
|
280
|
+
componentSource,
|
|
281
|
+
section.key,
|
|
282
|
+
section.allFields ?? section.fields,
|
|
283
|
+
repeatedGroups,
|
|
284
|
+
)
|
|
227
285
|
if (patchedSource !== componentSource) {
|
|
228
286
|
filesToCommit.push({ path: section.componentPath, content: patchedSource })
|
|
229
287
|
}
|
|
@@ -233,15 +291,17 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
233
291
|
// Re-read the now-mutated fields and update the content JSON entry.
|
|
234
292
|
const fields = section.allFields ?? section.fields
|
|
235
293
|
for (const g of repeatedGroups) {
|
|
236
|
-
const topField = fields.find(f => f.key === g.fieldKey)
|
|
294
|
+
const topField = fields.find((f) => f.key === g.fieldKey)
|
|
237
295
|
if (!topField || !Array.isArray(topField.defaultValue)) continue
|
|
238
|
-
const items = (topField.defaultValue as Array<Record<string, unknown>>).filter(
|
|
296
|
+
const items = (topField.defaultValue as Array<Record<string, unknown>>).filter(
|
|
297
|
+
(item) => item != null,
|
|
298
|
+
)
|
|
239
299
|
|
|
240
300
|
// Update sectionData with the enriched items array
|
|
241
301
|
sectionData[g.fieldKey] = items
|
|
242
302
|
|
|
243
303
|
// Rebuild and replace the content JSON in filesToCommit
|
|
244
|
-
const jsonIdx = filesToCommit.findIndex(f => f.path === sectionJsonPath)
|
|
304
|
+
const jsonIdx = filesToCommit.findIndex((f) => f.path === sectionJsonPath)
|
|
245
305
|
if (jsonIdx !== -1) {
|
|
246
306
|
filesToCommit[jsonIdx]!.content = JSON.stringify(sectionData, null, 2)
|
|
247
307
|
}
|
|
@@ -257,7 +317,13 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
257
317
|
const sectionDir = section.componentPath.replace(/\/[^/]+$/, '')
|
|
258
318
|
const resolvedChildPath = resolveRelativePath(sectionDir, child.importPath)
|
|
259
319
|
if (!resolvedChildPath) continue
|
|
260
|
-
const childSource = await fetchFileContent(
|
|
320
|
+
const childSource = await fetchFileContent(
|
|
321
|
+
owner,
|
|
322
|
+
repo,
|
|
323
|
+
branch,
|
|
324
|
+
resolvedChildPath,
|
|
325
|
+
githubToken,
|
|
326
|
+
)
|
|
261
327
|
if (!childSource) continue
|
|
262
328
|
const patchedChild = patchChildComponentForFieldPrefix(childSource, child.innerFields)
|
|
263
329
|
if (patchedChild !== childSource) {
|
|
@@ -271,7 +337,13 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
271
337
|
const fullPagePath = prefixPath(body.pagePath, projectPrefix)
|
|
272
338
|
const pageSource = await fetchFileContent(owner, repo, branch, fullPagePath, githubToken)
|
|
273
339
|
if (pageSource) {
|
|
274
|
-
const patched = patchPageFile(
|
|
340
|
+
const patched = patchPageFile(
|
|
341
|
+
pageSource,
|
|
342
|
+
section.key,
|
|
343
|
+
section.componentName,
|
|
344
|
+
section.componentPath,
|
|
345
|
+
body.pagePath,
|
|
346
|
+
)
|
|
275
347
|
if (patched && patched !== pageSource) {
|
|
276
348
|
filesToCommit.push({ path: fullPagePath, content: patched })
|
|
277
349
|
}
|
|
@@ -282,7 +354,13 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
282
354
|
const previewPath = prefixPath(`${pageDir}/sk-preview/[...page].astro`, projectPrefix)
|
|
283
355
|
const previewSource = await fetchFileContent(owner, repo, branch, previewPath, githubToken)
|
|
284
356
|
if (previewSource) {
|
|
285
|
-
const patchedPreview = patchPageFile(
|
|
357
|
+
const patchedPreview = patchPageFile(
|
|
358
|
+
previewSource,
|
|
359
|
+
section.key,
|
|
360
|
+
section.componentName,
|
|
361
|
+
section.componentPath,
|
|
362
|
+
`${pageDir}/sk-preview/[...page].astro`,
|
|
363
|
+
)
|
|
286
364
|
if (patchedPreview && patchedPreview !== previewSource) {
|
|
287
365
|
filesToCommit.push({ path: previewPath, content: patchedPreview })
|
|
288
366
|
}
|
|
@@ -300,9 +378,11 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
300
378
|
repo,
|
|
301
379
|
branch,
|
|
302
380
|
filesToCommit,
|
|
303
|
-
withTrailers(
|
|
304
|
-
|
|
305
|
-
|
|
381
|
+
withTrailers(
|
|
382
|
+
existingSectionJson
|
|
383
|
+
? `content: update ${section.key} section — add new fields`
|
|
384
|
+
: `content: add ${section.key} section to Setzkasten`,
|
|
385
|
+
),
|
|
306
386
|
headers,
|
|
307
387
|
)
|
|
308
388
|
|
|
@@ -332,15 +412,24 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
332
412
|
|
|
333
413
|
function getDefaultValue(fieldType: string): unknown {
|
|
334
414
|
switch (fieldType) {
|
|
335
|
-
case 'text':
|
|
336
|
-
|
|
337
|
-
case '
|
|
338
|
-
|
|
339
|
-
case '
|
|
340
|
-
|
|
341
|
-
case '
|
|
342
|
-
|
|
343
|
-
|
|
415
|
+
case 'text':
|
|
416
|
+
return ''
|
|
417
|
+
case 'number':
|
|
418
|
+
return 0
|
|
419
|
+
case 'boolean':
|
|
420
|
+
return false
|
|
421
|
+
case 'image':
|
|
422
|
+
return { path: '', alt: '' }
|
|
423
|
+
case 'array':
|
|
424
|
+
return []
|
|
425
|
+
case 'color':
|
|
426
|
+
return '#000000'
|
|
427
|
+
case 'date':
|
|
428
|
+
return ''
|
|
429
|
+
case 'icon':
|
|
430
|
+
return ''
|
|
431
|
+
default:
|
|
432
|
+
return ''
|
|
344
433
|
}
|
|
345
434
|
}
|
|
346
435
|
|
|
@@ -396,7 +485,11 @@ export function patchPageFile(
|
|
|
396
485
|
// Find the last entry and add after it
|
|
397
486
|
const lastEntryMatch = registryContent.match(/.*\w+Section,?\s*$/m)
|
|
398
487
|
if (lastEntryMatch && lastEntryMatch.index !== undefined) {
|
|
399
|
-
const insertPos =
|
|
488
|
+
const insertPos =
|
|
489
|
+
registryMatch.index! +
|
|
490
|
+
registryMatch[0].indexOf(registryContent) +
|
|
491
|
+
lastEntryMatch.index +
|
|
492
|
+
lastEntryMatch[0].length
|
|
400
493
|
const newEntry = `\n '${sectionKey}': ${componentName},`
|
|
401
494
|
patched = patched.slice(0, insertPos) + newEntry + patched.slice(insertPos)
|
|
402
495
|
}
|
|
@@ -419,7 +512,11 @@ export function calculateRelativePath(fromDir: string, toPath: string): string {
|
|
|
419
512
|
|
|
420
513
|
// Find common prefix length
|
|
421
514
|
let common = 0
|
|
422
|
-
while (
|
|
515
|
+
while (
|
|
516
|
+
common < fromParts.length &&
|
|
517
|
+
common < toParts.length &&
|
|
518
|
+
fromParts[common] === toParts[common]
|
|
519
|
+
) {
|
|
423
520
|
common++
|
|
424
521
|
}
|
|
425
522
|
|
|
@@ -440,7 +537,10 @@ function resolveRelativePath(baseDir: string, relativePath: string): string | nu
|
|
|
440
537
|
const parts = [...baseDir.split('/').filter(Boolean)]
|
|
441
538
|
for (const segment of relativePath.split('/')) {
|
|
442
539
|
if (segment === '.') continue
|
|
443
|
-
if (segment === '..') {
|
|
540
|
+
if (segment === '..') {
|
|
541
|
+
parts.pop()
|
|
542
|
+
continue
|
|
543
|
+
}
|
|
444
544
|
parts.push(segment)
|
|
445
545
|
}
|
|
446
546
|
const resolved = parts.join('/')
|
|
@@ -468,7 +568,7 @@ async function fetchFileContent(
|
|
|
468
568
|
},
|
|
469
569
|
)
|
|
470
570
|
if (!response.ok) return null
|
|
471
|
-
const data = await response.json() as { content: string; encoding: string }
|
|
571
|
+
const data = (await response.json()) as { content: string; encoding: string }
|
|
472
572
|
return data.encoding === 'base64'
|
|
473
573
|
? Buffer.from(data.content, 'base64').toString('utf-8')
|
|
474
574
|
: data.content
|
|
@@ -492,7 +592,7 @@ async function batchCommit(
|
|
|
492
592
|
{ headers },
|
|
493
593
|
)
|
|
494
594
|
if (!refRes.ok) return { ok: false, error: `Failed to get HEAD: ${refRes.status}` }
|
|
495
|
-
const refData = await refRes.json() as { object: { sha: string } }
|
|
595
|
+
const refData = (await refRes.json()) as { object: { sha: string } }
|
|
496
596
|
const headSha = refData.object.sha
|
|
497
597
|
|
|
498
598
|
// 2. Get base tree
|
|
@@ -501,43 +601,38 @@ async function batchCommit(
|
|
|
501
601
|
{ headers },
|
|
502
602
|
)
|
|
503
603
|
if (!commitRes.ok) return { ok: false, error: `Failed to get commit: ${commitRes.status}` }
|
|
504
|
-
const commitData = await commitRes.json() as { tree: { sha: string } }
|
|
604
|
+
const commitData = (await commitRes.json()) as { tree: { sha: string } }
|
|
505
605
|
|
|
506
606
|
// 3. Create new tree
|
|
507
|
-
const treeRes = await fetch(
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
}),
|
|
521
|
-
},
|
|
522
|
-
)
|
|
607
|
+
const treeRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/trees`, {
|
|
608
|
+
method: 'POST',
|
|
609
|
+
headers,
|
|
610
|
+
body: JSON.stringify({
|
|
611
|
+
base_tree: commitData.tree.sha,
|
|
612
|
+
tree: files.map((f) => ({
|
|
613
|
+
path: f.path,
|
|
614
|
+
mode: '100644',
|
|
615
|
+
type: 'blob',
|
|
616
|
+
content: f.content,
|
|
617
|
+
})),
|
|
618
|
+
}),
|
|
619
|
+
})
|
|
523
620
|
if (!treeRes.ok) return { ok: false, error: `Failed to create tree: ${treeRes.status}` }
|
|
524
|
-
const treeData = await treeRes.json() as { sha: string }
|
|
621
|
+
const treeData = (await treeRes.json()) as { sha: string }
|
|
525
622
|
|
|
526
623
|
// 4. Create commit
|
|
527
|
-
const newCommitRes = await fetch(
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
}
|
|
538
|
-
)
|
|
539
|
-
if (!newCommitRes.ok) return { ok: false, error: `Failed to create commit: ${newCommitRes.status}` }
|
|
540
|
-
const newCommitData = await newCommitRes.json() as { sha: string }
|
|
624
|
+
const newCommitRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/commits`, {
|
|
625
|
+
method: 'POST',
|
|
626
|
+
headers,
|
|
627
|
+
body: JSON.stringify({
|
|
628
|
+
tree: treeData.sha,
|
|
629
|
+
parents: [headSha],
|
|
630
|
+
message,
|
|
631
|
+
}),
|
|
632
|
+
})
|
|
633
|
+
if (!newCommitRes.ok)
|
|
634
|
+
return { ok: false, error: `Failed to create commit: ${newCommitRes.status}` }
|
|
635
|
+
const newCommitData = (await newCommitRes.json()) as { sha: string }
|
|
541
636
|
|
|
542
637
|
// 5. Update ref
|
|
543
638
|
const updateRes = await fetch(
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type ConfigGeneratorInput,
|
|
3
|
+
type InferredSection,
|
|
4
|
+
generateConfigFile,
|
|
5
|
+
} from '@setzkasten-cms/core/init'
|
|
1
6
|
import type { APIRoute } from 'astro'
|
|
2
|
-
import { generateConfigFile, type InferredSection, type ConfigGeneratorInput } from '@setzkasten-cms/core/init'
|
|
3
7
|
import { patchAstroConfig } from '../init/astro-config-patcher'
|
|
4
8
|
import { patchTemplateForFields } from '../init/template-patcher-v2'
|
|
9
|
+
import { requireAdmin } from './_auth-guard'
|
|
5
10
|
import { withTrailers } from './_commit-trailers'
|
|
6
11
|
import { resolveGitHubTokenForRequest } from './_github-token'
|
|
7
12
|
|
|
@@ -31,11 +36,8 @@ interface FileToCommit {
|
|
|
31
36
|
* Body: ApplyRequest
|
|
32
37
|
*/
|
|
33
38
|
export const POST: APIRoute = async ({ request, cookies }) => {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (!session) {
|
|
37
|
-
return new Response('Unauthorized', { status: 401 })
|
|
38
|
-
}
|
|
39
|
+
const denied = requireAdmin(cookies.get('setzkasten_session')?.value)
|
|
40
|
+
if (denied) return denied
|
|
39
41
|
|
|
40
42
|
const tokenResult = await resolveGitHubTokenForRequest(request)
|
|
41
43
|
if (!tokenResult.ok) {
|
|
@@ -44,7 +46,7 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
44
46
|
const githubToken = tokenResult.value
|
|
45
47
|
|
|
46
48
|
try {
|
|
47
|
-
const body = await request.json() as ApplyRequest
|
|
49
|
+
const body = (await request.json()) as ApplyRequest
|
|
48
50
|
const { owner, repo, branch = 'main', projectRoot, astroConfigPath, sections, pages } = body
|
|
49
51
|
const contentPath = body.contentPath ?? 'content'
|
|
50
52
|
|
|
@@ -62,7 +64,13 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
62
64
|
|
|
63
65
|
// 2. Patch astro.config if needed
|
|
64
66
|
if (astroConfigPath) {
|
|
65
|
-
const astroConfigSource = await fetchFileContent(
|
|
67
|
+
const astroConfigSource = await fetchFileContent(
|
|
68
|
+
owner,
|
|
69
|
+
repo,
|
|
70
|
+
branch,
|
|
71
|
+
astroConfigPath,
|
|
72
|
+
githubToken,
|
|
73
|
+
)
|
|
66
74
|
if (astroConfigSource) {
|
|
67
75
|
const patched = patchAstroConfig(astroConfigSource)
|
|
68
76
|
if (patched) {
|
|
@@ -87,7 +95,13 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
87
95
|
// 4. Patch component templates — add data-sk-field + CMS variables
|
|
88
96
|
for (const section of sections) {
|
|
89
97
|
if (!section.componentPath) continue
|
|
90
|
-
const componentSource = await fetchFileContent(
|
|
98
|
+
const componentSource = await fetchFileContent(
|
|
99
|
+
owner,
|
|
100
|
+
repo,
|
|
101
|
+
branch,
|
|
102
|
+
section.componentPath,
|
|
103
|
+
githubToken,
|
|
104
|
+
)
|
|
91
105
|
if (!componentSource) continue
|
|
92
106
|
const patched = await patchTemplateForFields(componentSource, section.key, section.fields)
|
|
93
107
|
if (patched !== componentSource) {
|
|
@@ -123,10 +137,7 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
123
137
|
)
|
|
124
138
|
|
|
125
139
|
if (!commitResult.ok) {
|
|
126
|
-
return Response.json(
|
|
127
|
-
{ error: commitResult.error },
|
|
128
|
-
{ status: 500 },
|
|
129
|
-
)
|
|
140
|
+
return Response.json({ error: commitResult.error }, { status: 500 })
|
|
130
141
|
}
|
|
131
142
|
|
|
132
143
|
return Response.json({
|
|
@@ -145,15 +156,24 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
145
156
|
|
|
146
157
|
function getDefaultValue(fieldType: string): unknown {
|
|
147
158
|
switch (fieldType) {
|
|
148
|
-
case 'text':
|
|
149
|
-
|
|
150
|
-
case '
|
|
151
|
-
|
|
152
|
-
case '
|
|
153
|
-
|
|
154
|
-
case '
|
|
155
|
-
|
|
156
|
-
|
|
159
|
+
case 'text':
|
|
160
|
+
return ''
|
|
161
|
+
case 'number':
|
|
162
|
+
return 0
|
|
163
|
+
case 'boolean':
|
|
164
|
+
return false
|
|
165
|
+
case 'image':
|
|
166
|
+
return { path: '', alt: '' }
|
|
167
|
+
case 'array':
|
|
168
|
+
return []
|
|
169
|
+
case 'color':
|
|
170
|
+
return '#000000'
|
|
171
|
+
case 'date':
|
|
172
|
+
return ''
|
|
173
|
+
case 'icon':
|
|
174
|
+
return ''
|
|
175
|
+
default:
|
|
176
|
+
return ''
|
|
157
177
|
}
|
|
158
178
|
}
|
|
159
179
|
|
|
@@ -176,7 +196,7 @@ async function fetchFileContent(
|
|
|
176
196
|
},
|
|
177
197
|
)
|
|
178
198
|
if (!response.ok) return null
|
|
179
|
-
const data = await response.json() as { content: string; encoding: string }
|
|
199
|
+
const data = (await response.json()) as { content: string; encoding: string }
|
|
180
200
|
return data.encoding === 'base64'
|
|
181
201
|
? Buffer.from(data.content, 'base64').toString('utf-8')
|
|
182
202
|
: data.content
|
|
@@ -207,7 +227,7 @@ async function batchCommit(
|
|
|
207
227
|
{ headers },
|
|
208
228
|
)
|
|
209
229
|
if (!refRes.ok) return { ok: false, error: `Failed to get HEAD: ${refRes.status}` }
|
|
210
|
-
const refData = await refRes.json() as { object: { sha: string } }
|
|
230
|
+
const refData = (await refRes.json()) as { object: { sha: string } }
|
|
211
231
|
const headSha = refData.object.sha
|
|
212
232
|
|
|
213
233
|
// 2. Get base tree
|
|
@@ -216,44 +236,39 @@ async function batchCommit(
|
|
|
216
236
|
{ headers },
|
|
217
237
|
)
|
|
218
238
|
if (!commitRes.ok) return { ok: false, error: `Failed to get commit: ${commitRes.status}` }
|
|
219
|
-
const commitData = await commitRes.json() as { tree: { sha: string } }
|
|
239
|
+
const commitData = (await commitRes.json()) as { tree: { sha: string } }
|
|
220
240
|
const baseTreeSha = commitData.tree.sha
|
|
221
241
|
|
|
222
242
|
// 3. Create new tree
|
|
223
|
-
const treeRes = await fetch(
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
}),
|
|
237
|
-
},
|
|
238
|
-
)
|
|
243
|
+
const treeRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/trees`, {
|
|
244
|
+
method: 'POST',
|
|
245
|
+
headers,
|
|
246
|
+
body: JSON.stringify({
|
|
247
|
+
base_tree: baseTreeSha,
|
|
248
|
+
tree: files.map((f) => ({
|
|
249
|
+
path: f.path,
|
|
250
|
+
mode: '100644',
|
|
251
|
+
type: 'blob',
|
|
252
|
+
content: f.content,
|
|
253
|
+
})),
|
|
254
|
+
}),
|
|
255
|
+
})
|
|
239
256
|
if (!treeRes.ok) return { ok: false, error: `Failed to create tree: ${treeRes.status}` }
|
|
240
|
-
const treeData = await treeRes.json() as { sha: string }
|
|
257
|
+
const treeData = (await treeRes.json()) as { sha: string }
|
|
241
258
|
|
|
242
259
|
// 4. Create commit
|
|
243
|
-
const newCommitRes = await fetch(
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
}
|
|
254
|
-
)
|
|
255
|
-
if (!newCommitRes.ok) return { ok: false, error: `Failed to create commit: ${newCommitRes.status}` }
|
|
256
|
-
const newCommitData = await newCommitRes.json() as { sha: string }
|
|
260
|
+
const newCommitRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/commits`, {
|
|
261
|
+
method: 'POST',
|
|
262
|
+
headers,
|
|
263
|
+
body: JSON.stringify({
|
|
264
|
+
tree: treeData.sha,
|
|
265
|
+
parents: [headSha],
|
|
266
|
+
message,
|
|
267
|
+
}),
|
|
268
|
+
})
|
|
269
|
+
if (!newCommitRes.ok)
|
|
270
|
+
return { ok: false, error: `Failed to create commit: ${newCommitRes.status}` }
|
|
271
|
+
const newCommitData = (await newCommitRes.json()) as { sha: string }
|
|
257
272
|
|
|
258
273
|
// 5. Update ref
|
|
259
274
|
const updateRes = await fetch(
|