@setzkasten-cms/astro-admin 1.4.2 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +150 -48
- 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-RHJONMLK.js → chunk-CDXCYYQR.js} +222 -5
- 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-K22A4ZBS.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 +91 -97
- 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 +218 -88
- 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 +126 -0
- 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 +102 -16
- 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 +490 -56
|
@@ -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 } 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
|
}
|
|
@@ -192,18 +236,16 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
192
236
|
if (patchedSource !== pageSource) {
|
|
193
237
|
filesToCommit.push({ path: fullPagePath, content: patchedSource })
|
|
194
238
|
|
|
195
|
-
// Create
|
|
196
|
-
// The
|
|
197
|
-
//
|
|
198
|
-
//
|
|
199
|
-
// Use resolvedPagePath (not body.pagePath) to get correct clone depth for
|
|
200
|
-
// directory routes (docs/index.astro → sk-preview/docs/index.astro).
|
|
201
|
-
const previewCopySource = patchedSource
|
|
202
|
-
.replace(/\bexport\s+const\s+prerender\s*=\s*true\s*;?\s*\n?/, '')
|
|
203
|
-
.replace(/(from\s+')(\.\.\/)/g, '$1../$2')
|
|
204
|
-
.replace(/(from\s+")(\.\.\/)/g, '$1../$2')
|
|
239
|
+
// Create a thin SSR wrapper under sk-preview/ so the live preview iframe works.
|
|
240
|
+
// The wrapper imports the production page as a component — no content duplication.
|
|
241
|
+
// In SSR context (prerender=false), getSection() runs at request time (draft-aware).
|
|
242
|
+
// In static context (/impressum), the same component runs at build time.
|
|
205
243
|
// resolvedPagePath: e.g. "src/pages/docs/index.astro" (for directory routes)
|
|
206
244
|
const relativePage = resolvedPagePath.replace(/^src\/pages\//, '') // "docs/index.astro"
|
|
245
|
+
// Import depth: sk-preview/impressum.astro → '../impressum.astro'
|
|
246
|
+
// sk-preview/docs/index.astro → '../../docs/index.astro'
|
|
247
|
+
const importDepth = '../'.repeat(relativePage.split('/').length)
|
|
248
|
+
const previewCopySource = `---\nexport const prerender = false;\nimport Page from '${importDepth}${relativePage}';\n---\n<Page />\n`
|
|
207
249
|
const previewCopyPath = prefixPath(`src/pages/sk-preview/${relativePage}`, projectPrefix)
|
|
208
250
|
filesToCommit.push({ path: previewCopyPath, content: previewCopySource })
|
|
209
251
|
}
|
|
@@ -211,10 +253,12 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
211
253
|
// Update content JSON with any _classN fields from patcher
|
|
212
254
|
const fields = section.allFields ?? section.fields
|
|
213
255
|
for (const g of repeatedGroups) {
|
|
214
|
-
const topField = fields.find(f => f.key === g.fieldKey)
|
|
256
|
+
const topField = fields.find((f) => f.key === g.fieldKey)
|
|
215
257
|
if (!topField || !Array.isArray(topField.defaultValue)) continue
|
|
216
|
-
sectionData[g.fieldKey] = (topField.defaultValue as unknown[]).filter(
|
|
217
|
-
|
|
258
|
+
sectionData[g.fieldKey] = (topField.defaultValue as unknown[]).filter(
|
|
259
|
+
(item) => item != null,
|
|
260
|
+
)
|
|
261
|
+
const jsonIdx = filesToCommit.findIndex((f) => f.path === sectionJsonPath)
|
|
218
262
|
if (jsonIdx !== -1) {
|
|
219
263
|
filesToCommit[jsonIdx]!.content = JSON.stringify(sectionData, null, 2)
|
|
220
264
|
}
|
|
@@ -222,10 +266,22 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
222
266
|
}
|
|
223
267
|
// No patchPageFile / SECTION_COMPONENTS for page-level sections
|
|
224
268
|
} else if (section.componentPath) {
|
|
225
|
-
const componentSource = await fetchFileContent(
|
|
269
|
+
const componentSource = await fetchFileContent(
|
|
270
|
+
owner,
|
|
271
|
+
repo,
|
|
272
|
+
branch,
|
|
273
|
+
section.componentPath,
|
|
274
|
+
githubToken,
|
|
275
|
+
)
|
|
226
276
|
if (componentSource) {
|
|
227
|
-
const repeatedGroups: RepeatedGroup[] =
|
|
228
|
-
|
|
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
|
+
)
|
|
229
285
|
if (patchedSource !== componentSource) {
|
|
230
286
|
filesToCommit.push({ path: section.componentPath, content: patchedSource })
|
|
231
287
|
}
|
|
@@ -235,19 +291,45 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
235
291
|
// Re-read the now-mutated fields and update the content JSON entry.
|
|
236
292
|
const fields = section.allFields ?? section.fields
|
|
237
293
|
for (const g of repeatedGroups) {
|
|
238
|
-
const topField = fields.find(f => f.key === g.fieldKey)
|
|
294
|
+
const topField = fields.find((f) => f.key === g.fieldKey)
|
|
239
295
|
if (!topField || !Array.isArray(topField.defaultValue)) continue
|
|
240
|
-
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
|
+
)
|
|
241
299
|
|
|
242
300
|
// Update sectionData with the enriched items array
|
|
243
301
|
sectionData[g.fieldKey] = items
|
|
244
302
|
|
|
245
303
|
// Rebuild and replace the content JSON in filesToCommit
|
|
246
|
-
const jsonIdx = filesToCommit.findIndex(f => f.path === sectionJsonPath)
|
|
304
|
+
const jsonIdx = filesToCommit.findIndex((f) => f.path === sectionJsonPath)
|
|
247
305
|
if (jsonIdx !== -1) {
|
|
248
306
|
filesToCommit[jsonIdx]!.content = JSON.stringify(sectionData, null, 2)
|
|
249
307
|
}
|
|
250
308
|
}
|
|
309
|
+
|
|
310
|
+
// 4b. Patch child components (e.g. PricingCard) used by repeated-component groups.
|
|
311
|
+
// detectChildImports finds any import whose component name matches a field's
|
|
312
|
+
// options.sourceComponent, then patchChildComponentForFieldPrefix injects
|
|
313
|
+
// fieldPrefix prop + data-sk-field bindings into that component file.
|
|
314
|
+
const allFields = section.allFields ?? section.fields
|
|
315
|
+
const childPatches = detectChildImports(patchedSource, allFields as any)
|
|
316
|
+
for (const child of childPatches) {
|
|
317
|
+
const sectionDir = section.componentPath.replace(/\/[^/]+$/, '')
|
|
318
|
+
const resolvedChildPath = resolveRelativePath(sectionDir, child.importPath)
|
|
319
|
+
if (!resolvedChildPath) continue
|
|
320
|
+
const childSource = await fetchFileContent(
|
|
321
|
+
owner,
|
|
322
|
+
repo,
|
|
323
|
+
branch,
|
|
324
|
+
resolvedChildPath,
|
|
325
|
+
githubToken,
|
|
326
|
+
)
|
|
327
|
+
if (!childSource) continue
|
|
328
|
+
const patchedChild = patchChildComponentForFieldPrefix(childSource, child.innerFields)
|
|
329
|
+
if (patchedChild !== childSource) {
|
|
330
|
+
filesToCommit.push({ path: resolvedChildPath, content: patchedChild })
|
|
331
|
+
}
|
|
332
|
+
}
|
|
251
333
|
}
|
|
252
334
|
|
|
253
335
|
// 5. Patch page file — add import and registry entry for new section
|
|
@@ -255,7 +337,13 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
255
337
|
const fullPagePath = prefixPath(body.pagePath, projectPrefix)
|
|
256
338
|
const pageSource = await fetchFileContent(owner, repo, branch, fullPagePath, githubToken)
|
|
257
339
|
if (pageSource) {
|
|
258
|
-
const patched = patchPageFile(
|
|
340
|
+
const patched = patchPageFile(
|
|
341
|
+
pageSource,
|
|
342
|
+
section.key,
|
|
343
|
+
section.componentName,
|
|
344
|
+
section.componentPath,
|
|
345
|
+
body.pagePath,
|
|
346
|
+
)
|
|
259
347
|
if (patched && patched !== pageSource) {
|
|
260
348
|
filesToCommit.push({ path: fullPagePath, content: patched })
|
|
261
349
|
}
|
|
@@ -266,7 +354,13 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
266
354
|
const previewPath = prefixPath(`${pageDir}/sk-preview/[...page].astro`, projectPrefix)
|
|
267
355
|
const previewSource = await fetchFileContent(owner, repo, branch, previewPath, githubToken)
|
|
268
356
|
if (previewSource) {
|
|
269
|
-
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
|
+
)
|
|
270
364
|
if (patchedPreview && patchedPreview !== previewSource) {
|
|
271
365
|
filesToCommit.push({ path: previewPath, content: patchedPreview })
|
|
272
366
|
}
|
|
@@ -284,9 +378,11 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
284
378
|
repo,
|
|
285
379
|
branch,
|
|
286
380
|
filesToCommit,
|
|
287
|
-
withTrailers(
|
|
288
|
-
|
|
289
|
-
|
|
381
|
+
withTrailers(
|
|
382
|
+
existingSectionJson
|
|
383
|
+
? `content: update ${section.key} section — add new fields`
|
|
384
|
+
: `content: add ${section.key} section to Setzkasten`,
|
|
385
|
+
),
|
|
290
386
|
headers,
|
|
291
387
|
)
|
|
292
388
|
|
|
@@ -316,15 +412,24 @@ export const POST: APIRoute = async ({ request, cookies }) => {
|
|
|
316
412
|
|
|
317
413
|
function getDefaultValue(fieldType: string): unknown {
|
|
318
414
|
switch (fieldType) {
|
|
319
|
-
case 'text':
|
|
320
|
-
|
|
321
|
-
case '
|
|
322
|
-
|
|
323
|
-
case '
|
|
324
|
-
|
|
325
|
-
case '
|
|
326
|
-
|
|
327
|
-
|
|
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 ''
|
|
328
433
|
}
|
|
329
434
|
}
|
|
330
435
|
|
|
@@ -380,8 +485,12 @@ export function patchPageFile(
|
|
|
380
485
|
// Find the last entry and add after it
|
|
381
486
|
const lastEntryMatch = registryContent.match(/.*\w+Section,?\s*$/m)
|
|
382
487
|
if (lastEntryMatch && lastEntryMatch.index !== undefined) {
|
|
383
|
-
const insertPos =
|
|
384
|
-
|
|
488
|
+
const insertPos =
|
|
489
|
+
registryMatch.index! +
|
|
490
|
+
registryMatch[0].indexOf(registryContent) +
|
|
491
|
+
lastEntryMatch.index +
|
|
492
|
+
lastEntryMatch[0].length
|
|
493
|
+
const newEntry = `\n '${sectionKey}': ${componentName},`
|
|
385
494
|
patched = patched.slice(0, insertPos) + newEntry + patched.slice(insertPos)
|
|
386
495
|
}
|
|
387
496
|
}
|
|
@@ -403,7 +512,11 @@ export function calculateRelativePath(fromDir: string, toPath: string): string {
|
|
|
403
512
|
|
|
404
513
|
// Find common prefix length
|
|
405
514
|
let common = 0
|
|
406
|
-
while (
|
|
515
|
+
while (
|
|
516
|
+
common < fromParts.length &&
|
|
517
|
+
common < toParts.length &&
|
|
518
|
+
fromParts[common] === toParts[common]
|
|
519
|
+
) {
|
|
407
520
|
common++
|
|
408
521
|
}
|
|
409
522
|
|
|
@@ -414,6 +527,28 @@ export function calculateRelativePath(fromDir: string, toPath: string): string {
|
|
|
414
527
|
return '../'.repeat(ups) + remaining
|
|
415
528
|
}
|
|
416
529
|
|
|
530
|
+
/**
|
|
531
|
+
* Resolve a relative import path against a base directory to get the repo-root-relative path.
|
|
532
|
+
* e.g. resolveRelativePath("src/components/sections", "../components/PricingCard.astro")
|
|
533
|
+
* → "src/components/PricingCard.astro"
|
|
534
|
+
*/
|
|
535
|
+
function resolveRelativePath(baseDir: string, relativePath: string): string | null {
|
|
536
|
+
if (relativePath.startsWith('/')) return relativePath.replace(/^\//, '')
|
|
537
|
+
const parts = [...baseDir.split('/').filter(Boolean)]
|
|
538
|
+
for (const segment of relativePath.split('/')) {
|
|
539
|
+
if (segment === '.') continue
|
|
540
|
+
if (segment === '..') {
|
|
541
|
+
parts.pop()
|
|
542
|
+
continue
|
|
543
|
+
}
|
|
544
|
+
parts.push(segment)
|
|
545
|
+
}
|
|
546
|
+
const resolved = parts.join('/')
|
|
547
|
+
// Must stay within src/ or project root — reject anything that escapes
|
|
548
|
+
if (resolved.startsWith('../') || resolved.startsWith('/')) return null
|
|
549
|
+
return resolved
|
|
550
|
+
}
|
|
551
|
+
|
|
417
552
|
async function fetchFileContent(
|
|
418
553
|
owner: string,
|
|
419
554
|
repo: string,
|
|
@@ -433,7 +568,7 @@ async function fetchFileContent(
|
|
|
433
568
|
},
|
|
434
569
|
)
|
|
435
570
|
if (!response.ok) return null
|
|
436
|
-
const data = await response.json() as { content: string; encoding: string }
|
|
571
|
+
const data = (await response.json()) as { content: string; encoding: string }
|
|
437
572
|
return data.encoding === 'base64'
|
|
438
573
|
? Buffer.from(data.content, 'base64').toString('utf-8')
|
|
439
574
|
: data.content
|
|
@@ -457,7 +592,7 @@ async function batchCommit(
|
|
|
457
592
|
{ headers },
|
|
458
593
|
)
|
|
459
594
|
if (!refRes.ok) return { ok: false, error: `Failed to get HEAD: ${refRes.status}` }
|
|
460
|
-
const refData = await refRes.json() as { object: { sha: string } }
|
|
595
|
+
const refData = (await refRes.json()) as { object: { sha: string } }
|
|
461
596
|
const headSha = refData.object.sha
|
|
462
597
|
|
|
463
598
|
// 2. Get base tree
|
|
@@ -466,43 +601,38 @@ async function batchCommit(
|
|
|
466
601
|
{ headers },
|
|
467
602
|
)
|
|
468
603
|
if (!commitRes.ok) return { ok: false, error: `Failed to get commit: ${commitRes.status}` }
|
|
469
|
-
const commitData = await commitRes.json() as { tree: { sha: string } }
|
|
604
|
+
const commitData = (await commitRes.json()) as { tree: { sha: string } }
|
|
470
605
|
|
|
471
606
|
// 3. Create new tree
|
|
472
|
-
const treeRes = await fetch(
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
}),
|
|
486
|
-
},
|
|
487
|
-
)
|
|
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
|
+
})
|
|
488
620
|
if (!treeRes.ok) return { ok: false, error: `Failed to create tree: ${treeRes.status}` }
|
|
489
|
-
const treeData = await treeRes.json() as { sha: string }
|
|
621
|
+
const treeData = (await treeRes.json()) as { sha: string }
|
|
490
622
|
|
|
491
623
|
// 4. Create commit
|
|
492
|
-
const newCommitRes = await fetch(
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
}
|
|
503
|
-
)
|
|
504
|
-
if (!newCommitRes.ok) return { ok: false, error: `Failed to create commit: ${newCommitRes.status}` }
|
|
505
|
-
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 }
|
|
506
636
|
|
|
507
637
|
// 5. Update ref
|
|
508
638
|
const updateRes = await fetch(
|