@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
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
* - Commits triggered on every duplicate/delete instead of batched
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { describe,
|
|
12
|
-
import {
|
|
11
|
+
import { describe, expect, it } from 'vitest'
|
|
12
|
+
import { duplicateInPageConfig, generateDuplicateKey } from '../section-management'
|
|
13
13
|
|
|
14
14
|
// ---------------------------------------------------------------------------
|
|
15
15
|
// 1. Duplicate key generation (reused by prepare-copy)
|
|
@@ -25,8 +25,9 @@ describe('generateDuplicateKey (used by prepare-copy)', () => {
|
|
|
25
25
|
})
|
|
26
26
|
|
|
27
27
|
it('increments suffix until unique', () => {
|
|
28
|
-
expect(generateDuplicateKey(['hero', 'hero--copy', 'hero--copy2', 'hero--copy3'], 'hero'))
|
|
29
|
-
|
|
28
|
+
expect(generateDuplicateKey(['hero', 'hero--copy', 'hero--copy2', 'hero--copy3'], 'hero')).toBe(
|
|
29
|
+
'hero--copy4',
|
|
30
|
+
)
|
|
30
31
|
})
|
|
31
32
|
|
|
32
33
|
it('generates copy of a copy', () => {
|
|
@@ -82,9 +83,7 @@ describe('duplicateInPageConfig (used by prepare-copy)', () => {
|
|
|
82
83
|
|
|
83
84
|
it('preserves type field from original entry when present', () => {
|
|
84
85
|
const configWithType = {
|
|
85
|
-
sections: [
|
|
86
|
-
{ key: 'hero--about', type: 'hero', enabled: true, order: 0 },
|
|
87
|
-
],
|
|
86
|
+
sections: [{ key: 'hero--about', type: 'hero', enabled: true, order: 0 }],
|
|
88
87
|
}
|
|
89
88
|
const result = duplicateInPageConfig(configWithType, 'hero--about', 'hero--about--copy')
|
|
90
89
|
const copy = result.sections.find((s: any) => s.key === 'hero--about--copy')
|
|
@@ -98,9 +97,7 @@ describe('duplicateInPageConfig (used by prepare-copy)', () => {
|
|
|
98
97
|
// getSectionDef looks up 'testPricing--copy' in the catalog → not found → empty editor.
|
|
99
98
|
// resolveSectionType returns 'testPricing--copy' → no component → not in preview.
|
|
100
99
|
const configNoType = {
|
|
101
|
-
sections: [
|
|
102
|
-
{ key: 'testPricing', enabled: true, order: 0 },
|
|
103
|
-
],
|
|
100
|
+
sections: [{ key: 'testPricing', enabled: true, order: 0 }],
|
|
104
101
|
}
|
|
105
102
|
const result = duplicateInPageConfig(configNoType, 'testPricing', 'testPricing--copy')
|
|
106
103
|
const copy = result.sections.find((s: any) => s.key === 'testPricing--copy')
|
|
@@ -169,7 +166,7 @@ describe('commit-pending: commit message', () => {
|
|
|
169
166
|
function buildCommitMessage(pageKey: string, sections: Array<{ key: string }>): string {
|
|
170
167
|
const parts: string[] = []
|
|
171
168
|
if (sections.length > 0) {
|
|
172
|
-
const keys = sections.map(s => s.key).join(', ')
|
|
169
|
+
const keys = sections.map((s) => s.key).join(', ')
|
|
173
170
|
parts.push(`add ${sections.length} section${sections.length > 1 ? 's' : ''} (${keys})`)
|
|
174
171
|
}
|
|
175
172
|
return `content: ${parts.length > 0 ? parts.join(', ') : 'update page config'} on ${pageKey}`
|
|
@@ -210,7 +207,7 @@ describe('prepare-copy: type resolution', () => {
|
|
|
210
207
|
sections: Array<{ key: string; type?: string }>,
|
|
211
208
|
sectionKey: string,
|
|
212
209
|
): string {
|
|
213
|
-
const entry = sections.find(s => s.key === sectionKey)
|
|
210
|
+
const entry = sections.find((s) => s.key === sectionKey)
|
|
214
211
|
return entry?.type ?? sectionKey
|
|
215
212
|
}
|
|
216
213
|
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* 6. Secret header is sent when configured
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import {
|
|
13
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
14
14
|
|
|
15
15
|
// We import the POST handler directly; Astro's APIRoute type is just a function.
|
|
16
16
|
// We construct a minimal mock context manually.
|
|
@@ -40,9 +40,7 @@ describe('deploy-hook POST handler', () => {
|
|
|
40
40
|
return {
|
|
41
41
|
cookies: {
|
|
42
42
|
get: (name: string) =>
|
|
43
|
-
name === 'setzkasten_session' && sessionValue
|
|
44
|
-
? { value: sessionValue }
|
|
45
|
-
: undefined,
|
|
43
|
+
name === 'setzkasten_session' && sessionValue ? { value: sessionValue } : undefined,
|
|
46
44
|
},
|
|
47
45
|
}
|
|
48
46
|
}
|
|
@@ -64,10 +62,7 @@ describe('deploy-hook POST handler', () => {
|
|
|
64
62
|
;(globalThis as any).__SETZKASTEN_CONFIG__ = {
|
|
65
63
|
deployHook: { url: 'https://example.com/hook' },
|
|
66
64
|
}
|
|
67
|
-
vi.stubGlobal(
|
|
68
|
-
'fetch',
|
|
69
|
-
vi.fn().mockResolvedValue({ ok: true, status: 200 } as Response),
|
|
70
|
-
)
|
|
65
|
+
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({ ok: true, status: 200 } as Response))
|
|
71
66
|
|
|
72
67
|
const res = await POST(makeCtx('tok'))
|
|
73
68
|
expect(res.status).toBe(200)
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* 2. Nicht alle Vars gesetzt → auth error Result
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
|
10
9
|
import { generateKeyPairSync } from 'node:crypto'
|
|
10
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
11
11
|
|
|
12
12
|
const { privateKey: TEST_KEY } = generateKeyPairSync('rsa', { modulusLength: 2048 })
|
|
13
13
|
const TEST_PRIVATE_KEY_PEM = TEST_KEY.export({ type: 'pkcs8', format: 'pem' }) as string
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
2
|
|
|
3
3
|
vi.mock('../_storage-config', () => ({
|
|
4
4
|
resolveStorageConfig: vi.fn(() => ({ owner: 'o', repo: 'r', branch: 'main' })),
|
|
@@ -8,7 +8,7 @@ vi.stubGlobal('__SETZKASTEN_CONFIG__', { storage: { contentPath: 'content' } })
|
|
|
8
8
|
|
|
9
9
|
describe('GlobalConfig – theme field', () => {
|
|
10
10
|
it('GlobalConfig type accepts a theme object', async () => {
|
|
11
|
-
const {
|
|
11
|
+
const {} = await import('../global-config')
|
|
12
12
|
// Type-level check: if this compiles the type is correct
|
|
13
13
|
const cfg = {
|
|
14
14
|
theme: { primaryColor: '#ff0000', brandName: 'Acme', logo: '/logo.png' },
|
|
@@ -43,7 +43,7 @@ describe('config.ts – theme merge', () => {
|
|
|
43
43
|
|
|
44
44
|
const { GET } = await import('../config')
|
|
45
45
|
const res = await GET({} as any)
|
|
46
|
-
const body = await res.json() as any
|
|
46
|
+
const body = (await res.json()) as any
|
|
47
47
|
|
|
48
48
|
expect(body.theme.primaryColor).toBe('#abc123')
|
|
49
49
|
expect(body.theme.brandName).toBe('Dynamic Brand')
|
|
@@ -63,7 +63,7 @@ describe('config.ts – theme merge', () => {
|
|
|
63
63
|
|
|
64
64
|
const { GET } = await import('../config')
|
|
65
65
|
const res = await GET({} as any)
|
|
66
|
-
const body = await res.json() as any
|
|
66
|
+
const body = (await res.json()) as any
|
|
67
67
|
|
|
68
68
|
expect(body.theme.primaryColor).toBe('#ffffff')
|
|
69
69
|
expect(body.theme.brandName).toBe('Static Brand')
|
|
@@ -4,16 +4,17 @@
|
|
|
4
4
|
|
|
5
5
|
import { generateKeyPairSync } from 'node:crypto'
|
|
6
6
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
7
|
+
import { makeTestSessionCookie } from './_session-test-helper'
|
|
7
8
|
|
|
8
9
|
const { privateKey } = generateKeyPairSync('rsa', { modulusLength: 2048 })
|
|
9
10
|
const PEM = privateKey.export({ type: 'pkcs8', format: 'pem' }) as string
|
|
10
11
|
|
|
11
|
-
const ADMIN_SESSION =
|
|
12
|
+
const ADMIN_SESSION = makeTestSessionCookie({
|
|
12
13
|
user: { id: 'u1', email: 'admin@example.com', role: 'admin', provider: 'github' },
|
|
13
14
|
expiresAt: Date.now() + 60 * 60 * 1000,
|
|
14
15
|
})
|
|
15
16
|
|
|
16
|
-
const EDITOR_SESSION =
|
|
17
|
+
const EDITOR_SESSION = makeTestSessionCookie({
|
|
17
18
|
user: { id: 'u2', email: 'editor@example.com', role: 'editor', provider: 'google' },
|
|
18
19
|
expiresAt: Date.now() + 60 * 60 * 1000,
|
|
19
20
|
})
|
|
@@ -88,7 +89,9 @@ describe('POST /api/setzkasten/history/rollback', () => {
|
|
|
88
89
|
|
|
89
90
|
it('returns 400 when path or sha missing', async () => {
|
|
90
91
|
const { POST } = await import('../history-rollback')
|
|
91
|
-
const res = await (POST as (ctx: unknown) => Promise<Response>)(
|
|
92
|
+
const res = await (POST as (ctx: unknown) => Promise<Response>)(
|
|
93
|
+
makeCtx({ path: ROLLBACK_PATH }),
|
|
94
|
+
)
|
|
92
95
|
expect(res.status).toBe(400)
|
|
93
96
|
})
|
|
94
97
|
|
|
@@ -8,17 +8,22 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
8
8
|
const { privateKey } = generateKeyPairSync('rsa', { modulusLength: 2048 })
|
|
9
9
|
const PEM = privateKey.export({ type: 'pkcs8', format: 'pem' }) as string
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
import { makeTestSessionCookie } from './_session-test-helper'
|
|
12
|
+
|
|
13
|
+
const ADMIN_SESSION = makeTestSessionCookie({
|
|
12
14
|
user: { id: 'u1', email: 'admin@example.com', role: 'admin', provider: 'github' },
|
|
13
15
|
expiresAt: Date.now() + 60 * 60 * 1000,
|
|
14
16
|
})
|
|
15
17
|
|
|
16
|
-
const EDITOR_SESSION =
|
|
18
|
+
const EDITOR_SESSION = makeTestSessionCookie({
|
|
17
19
|
user: { id: 'u2', email: 'editor@example.com', role: 'editor', provider: 'google' },
|
|
18
20
|
expiresAt: Date.now() + 60 * 60 * 1000,
|
|
19
21
|
})
|
|
20
22
|
|
|
21
|
-
function makeCtx(
|
|
23
|
+
function makeCtx(
|
|
24
|
+
searchParams: Record<string, string>,
|
|
25
|
+
sessionValue: string | null = ADMIN_SESSION,
|
|
26
|
+
) {
|
|
22
27
|
const url = new URL('https://cms.example.com/api/setzkasten/history')
|
|
23
28
|
for (const [k, v] of Object.entries(searchParams)) url.searchParams.set(k, v)
|
|
24
29
|
const request = new Request(url, { method: 'GET' })
|
|
@@ -136,9 +141,7 @@ describe('GET /api/setzkasten/history', () => {
|
|
|
136
141
|
authorEmail: 'maria@example.com',
|
|
137
142
|
message: 'Update hero',
|
|
138
143
|
})
|
|
139
|
-
expect(body.commits[0].coAuthors).toEqual([
|
|
140
|
-
{ name: 'Editor', email: 'editor@example.com' },
|
|
141
|
-
])
|
|
144
|
+
expect(body.commits[0].coAuthors).toEqual([{ name: 'Editor', email: 'editor@example.com' }])
|
|
142
145
|
})
|
|
143
146
|
|
|
144
147
|
it('returns empty list when GitHub returns 404 for the path', async () => {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* @vitest-environment node
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import {
|
|
17
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
|
18
18
|
import { resolveFullConfig } from '../init-scan-page'
|
|
19
19
|
|
|
20
20
|
const SAMPLE_CONFIG = {
|
|
@@ -23,8 +23,16 @@ const SAMPLE_CONFIG = {
|
|
|
23
23
|
website: {
|
|
24
24
|
label: 'Website',
|
|
25
25
|
sections: {
|
|
26
|
-
_layout_header: {
|
|
27
|
-
|
|
26
|
+
_layout_header: {
|
|
27
|
+
label: 'header',
|
|
28
|
+
icon: 'panel-top',
|
|
29
|
+
fields: { items: { type: 'array' } },
|
|
30
|
+
},
|
|
31
|
+
_layout_footer: {
|
|
32
|
+
label: 'footer',
|
|
33
|
+
icon: 'file-text',
|
|
34
|
+
fields: { description: { type: 'text' } },
|
|
35
|
+
},
|
|
28
36
|
},
|
|
29
37
|
},
|
|
30
38
|
},
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { generateKeyPairSync } from 'node:crypto'
|
|
6
6
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
7
|
+
import { makeTestSessionCookie } from './_session-test-helper'
|
|
7
8
|
|
|
8
9
|
const { privateKey } = generateKeyPairSync('rsa', { modulusLength: 2048 })
|
|
9
10
|
const PEM = privateKey.export({ type: 'pkcs8', format: 'pem' }) as string
|
|
@@ -15,7 +16,10 @@ function makeCtx(body: unknown, sessionValue = 'valid', role: 'admin' | 'editor'
|
|
|
15
16
|
headers: { 'content-type': 'application/json' },
|
|
16
17
|
})
|
|
17
18
|
const sessionPayload = sessionValue
|
|
18
|
-
?
|
|
19
|
+
? makeTestSessionCookie({
|
|
20
|
+
user: { id: 'test-user', email: 'a@b.com', role, provider: 'github' },
|
|
21
|
+
expiresAt: Date.now() + 60_000,
|
|
22
|
+
})
|
|
19
23
|
: ''
|
|
20
24
|
return {
|
|
21
25
|
request,
|
|
@@ -21,7 +21,9 @@ afterEach(() => {
|
|
|
21
21
|
vi.unstubAllEnvs()
|
|
22
22
|
})
|
|
23
23
|
|
|
24
|
-
function fetchSequence(
|
|
24
|
+
function fetchSequence(
|
|
25
|
+
steps: Array<(url: string, init?: RequestInit) => Response | Promise<Response>>,
|
|
26
|
+
) {
|
|
25
27
|
let i = 0
|
|
26
28
|
const fetchMock = vi.fn(async (url: string, init?: RequestInit) => {
|
|
27
29
|
const handler = steps[Math.min(i++, steps.length - 1)]
|
|
@@ -51,7 +53,10 @@ describe('readPagesMeta', () => {
|
|
|
51
53
|
fetchSequence([
|
|
52
54
|
() =>
|
|
53
55
|
new Response(
|
|
54
|
-
JSON.stringify({
|
|
56
|
+
JSON.stringify({
|
|
57
|
+
content: Buffer.from(JSON.stringify(meta)).toString('base64'),
|
|
58
|
+
sha: 'abc',
|
|
59
|
+
}),
|
|
55
60
|
{ status: 200, headers: { 'content-type': 'application/json' } },
|
|
56
61
|
),
|
|
57
62
|
])
|
|
@@ -156,9 +161,9 @@ describe('recordPageEdit', () => {
|
|
|
156
161
|
() =>
|
|
157
162
|
new Response(
|
|
158
163
|
JSON.stringify({
|
|
159
|
-
content: Buffer.from(
|
|
160
|
-
|
|
161
|
-
),
|
|
164
|
+
content: Buffer.from(
|
|
165
|
+
JSON.stringify({ version: 1, pages: { x: { lastModified: 2 } } }),
|
|
166
|
+
).toString('base64'),
|
|
162
167
|
sha: 'second',
|
|
163
168
|
}),
|
|
164
169
|
{ status: 200, headers: { 'content-type': 'application/json' } },
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* @vitest-environment node
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import {
|
|
16
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
|
17
17
|
import { resolvePages } from '../pages'
|
|
18
18
|
|
|
19
19
|
interface PageInfo {
|
|
@@ -63,7 +63,12 @@ describe('resolvePages', () => {
|
|
|
63
63
|
it('handles multiple pages including nested paths', () => {
|
|
64
64
|
const pages: PageInfo[] = [
|
|
65
65
|
{ path: '/', pageKey: 'index', label: 'Startseite', hasConfig: true },
|
|
66
|
-
{
|
|
66
|
+
{
|
|
67
|
+
path: '/docs/architecture',
|
|
68
|
+
pageKey: 'docs/architecture',
|
|
69
|
+
label: 'Architecture',
|
|
70
|
+
hasConfig: false,
|
|
71
|
+
},
|
|
67
72
|
]
|
|
68
73
|
;(globalThis as Record<string, unknown>).__SETZKASTEN_PAGES__ = pages
|
|
69
74
|
expect(resolvePages()).toHaveLength(2)
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
* Bugs here mean sections either don't appear in the page or appear twice.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { describe,
|
|
13
|
-
import {
|
|
12
|
+
import { describe, expect, it } from 'vitest'
|
|
13
|
+
import { calculateRelativePath, patchPageFile } from '../init-add-section'
|
|
14
14
|
|
|
15
15
|
// ---------------------------------------------------------------------------
|
|
16
16
|
// Fixtures
|
|
@@ -64,29 +64,59 @@ describe('patchPageFile — new section injection', () => {
|
|
|
64
64
|
const componentPath = 'src/components/sections/FeaturesSection.astro'
|
|
65
65
|
|
|
66
66
|
it('should return a non-null result', () => {
|
|
67
|
-
const result = patchPageFile(
|
|
67
|
+
const result = patchPageFile(
|
|
68
|
+
PAGE_WITH_SECTIONS,
|
|
69
|
+
'features',
|
|
70
|
+
'FeaturesSection',
|
|
71
|
+
componentPath,
|
|
72
|
+
pagePath,
|
|
73
|
+
)
|
|
68
74
|
expect(result).not.toBeNull()
|
|
69
75
|
})
|
|
70
76
|
|
|
71
77
|
it('should add the import statement', () => {
|
|
72
|
-
const result = patchPageFile(
|
|
73
|
-
|
|
78
|
+
const result = patchPageFile(
|
|
79
|
+
PAGE_WITH_SECTIONS,
|
|
80
|
+
'features',
|
|
81
|
+
'FeaturesSection',
|
|
82
|
+
componentPath,
|
|
83
|
+
pagePath,
|
|
84
|
+
)!
|
|
85
|
+
expect(result).toContain('import FeaturesSection from')
|
|
74
86
|
})
|
|
75
87
|
|
|
76
88
|
it('should include the correct relative path', () => {
|
|
77
|
-
const result = patchPageFile(
|
|
89
|
+
const result = patchPageFile(
|
|
90
|
+
PAGE_WITH_SECTIONS,
|
|
91
|
+
'features',
|
|
92
|
+
'FeaturesSection',
|
|
93
|
+
componentPath,
|
|
94
|
+
pagePath,
|
|
95
|
+
)!
|
|
78
96
|
// From src/pages to src/components/sections → one level up → ../components/sections/...
|
|
79
97
|
expect(result).toContain('../components/sections/FeaturesSection.astro')
|
|
80
98
|
})
|
|
81
99
|
|
|
82
100
|
it('should add the section key to SECTION_COMPONENTS', () => {
|
|
83
|
-
const result = patchPageFile(
|
|
101
|
+
const result = patchPageFile(
|
|
102
|
+
PAGE_WITH_SECTIONS,
|
|
103
|
+
'features',
|
|
104
|
+
'FeaturesSection',
|
|
105
|
+
componentPath,
|
|
106
|
+
pagePath,
|
|
107
|
+
)!
|
|
84
108
|
expect(result).toContain('features')
|
|
85
109
|
expect(result).toContain('FeaturesSection')
|
|
86
110
|
})
|
|
87
111
|
|
|
88
112
|
it('should preserve existing sections in the registry', () => {
|
|
89
|
-
const result = patchPageFile(
|
|
113
|
+
const result = patchPageFile(
|
|
114
|
+
PAGE_WITH_SECTIONS,
|
|
115
|
+
'features',
|
|
116
|
+
'FeaturesSection',
|
|
117
|
+
componentPath,
|
|
118
|
+
pagePath,
|
|
119
|
+
)!
|
|
90
120
|
expect(result).toContain('HeroSection')
|
|
91
121
|
expect(result).toContain("normalize('hero')")
|
|
92
122
|
})
|
|
@@ -122,13 +152,25 @@ describe('patchPageFile — hardcoded tag removal', () => {
|
|
|
122
152
|
it('should remove the hardcoded <HeroSection /> when adding to registry', () => {
|
|
123
153
|
// PAGE_WITH_HARDCODED_COMPONENT has HeroSection imported but NOT in registry
|
|
124
154
|
// → patchPageFile adds it to registry AND removes the hardcoded tag
|
|
125
|
-
const result = patchPageFile(
|
|
155
|
+
const result = patchPageFile(
|
|
156
|
+
PAGE_WITH_HARDCODED_COMPONENT,
|
|
157
|
+
'hero',
|
|
158
|
+
'HeroSection',
|
|
159
|
+
componentPath,
|
|
160
|
+
pagePath,
|
|
161
|
+
)
|
|
126
162
|
expect(result).not.toBeNull()
|
|
127
163
|
expect(result).not.toMatch(/<HeroSection\s*\/>/)
|
|
128
164
|
})
|
|
129
165
|
|
|
130
166
|
it('should add the section to SECTION_COMPONENTS when removing hardcoded tag', () => {
|
|
131
|
-
const result = patchPageFile(
|
|
167
|
+
const result = patchPageFile(
|
|
168
|
+
PAGE_WITH_HARDCODED_COMPONENT,
|
|
169
|
+
'hero',
|
|
170
|
+
'HeroSection',
|
|
171
|
+
componentPath,
|
|
172
|
+
pagePath,
|
|
173
|
+
)!
|
|
132
174
|
expect(result).toContain('hero')
|
|
133
175
|
expect(result).toContain('HeroSection')
|
|
134
176
|
})
|
|
@@ -157,7 +199,13 @@ const SECTION_COMPONENTS = {
|
|
|
157
199
|
const pagePath = 'src/pages/docs/architecture.astro'
|
|
158
200
|
const componentPath = 'src/components/sections/FeaturesSection.astro'
|
|
159
201
|
|
|
160
|
-
const result = patchPageFile(
|
|
202
|
+
const result = patchPageFile(
|
|
203
|
+
PAGE_NESTED,
|
|
204
|
+
'features',
|
|
205
|
+
'FeaturesSection',
|
|
206
|
+
componentPath,
|
|
207
|
+
pagePath,
|
|
208
|
+
)
|
|
161
209
|
if (result) {
|
|
162
210
|
// From src/pages/docs to src/components/sections = ../../components/sections
|
|
163
211
|
expect(result).toContain('../../components/sections/FeaturesSection.astro')
|
|
@@ -175,19 +223,23 @@ describe('calculateRelativePath', () => {
|
|
|
175
223
|
})
|
|
176
224
|
|
|
177
225
|
it('one level up', () => {
|
|
178
|
-
expect(calculateRelativePath('src/pages', 'src/components/Hero.astro'))
|
|
179
|
-
|
|
226
|
+
expect(calculateRelativePath('src/pages', 'src/components/Hero.astro')).toBe(
|
|
227
|
+
'../components/Hero.astro',
|
|
228
|
+
)
|
|
180
229
|
})
|
|
181
230
|
|
|
182
231
|
it('two levels up', () => {
|
|
183
|
-
expect(calculateRelativePath('src/pages/docs', 'src/components/sections/Hero.astro'))
|
|
184
|
-
|
|
232
|
+
expect(calculateRelativePath('src/pages/docs', 'src/components/sections/Hero.astro')).toBe(
|
|
233
|
+
'../../components/sections/Hero.astro',
|
|
234
|
+
)
|
|
185
235
|
})
|
|
186
236
|
|
|
187
237
|
it('deeper nesting in monorepo', () => {
|
|
188
|
-
expect(
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
238
|
+
expect(
|
|
239
|
+
calculateRelativePath(
|
|
240
|
+
'apps/website/src/pages',
|
|
241
|
+
'apps/website/src/components/sections/FooterSection.astro',
|
|
242
|
+
),
|
|
243
|
+
).toBe('../components/sections/FooterSection.astro')
|
|
192
244
|
})
|
|
193
245
|
})
|
|
@@ -15,14 +15,14 @@
|
|
|
15
15
|
* ein Fehler, der erst beim Produktions-Build auffällt (ENOENT).
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import { resolve, dirname } from 'node:path'
|
|
18
|
+
import { readFileSync, readdirSync } from 'node:fs'
|
|
19
|
+
import { dirname, resolve } from 'node:path'
|
|
21
20
|
import { fileURLToPath } from 'node:url'
|
|
21
|
+
import { describe, expect, it } from 'vitest'
|
|
22
22
|
|
|
23
23
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
24
24
|
// Test sits at src/api-routes/__tests__, so go up three levels
|
|
25
|
-
const ADMIN_ROOT = resolve(__dirname, '../../../')
|
|
25
|
+
const ADMIN_ROOT = resolve(__dirname, '../../../') // packages/astro-admin
|
|
26
26
|
const ASTRO_ROOT = resolve(__dirname, '../../../../astro') // packages/astro
|
|
27
27
|
|
|
28
28
|
// ---------------------------------------------------------------------------
|
|
@@ -30,17 +30,14 @@ const ASTRO_ROOT = resolve(__dirname, '../../../../astro') // packages/astro
|
|
|
30
30
|
// ---------------------------------------------------------------------------
|
|
31
31
|
|
|
32
32
|
const routeFiles = readdirSync(resolve(ADMIN_ROOT, 'src/api-routes')).filter(
|
|
33
|
-
f => f.endsWith('.ts') && !f.endsWith('.test.ts'),
|
|
33
|
+
(f) => f.endsWith('.ts') && !f.endsWith('.test.ts'),
|
|
34
34
|
)
|
|
35
35
|
|
|
36
|
-
const packageJson = JSON.parse(
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
const packageJson = JSON.parse(readFileSync(resolve(ADMIN_ROOT, 'package.json'), 'utf-8')) as {
|
|
37
|
+
exports: Record<string, string>
|
|
38
|
+
}
|
|
39
39
|
|
|
40
|
-
const integrationSrc = readFileSync(
|
|
41
|
-
resolve(ASTRO_ROOT, 'src/integration.ts'),
|
|
42
|
-
'utf-8',
|
|
43
|
-
)
|
|
40
|
+
const integrationSrc = readFileSync(resolve(ASTRO_ROOT, 'src/integration.ts'), 'utf-8')
|
|
44
41
|
|
|
45
42
|
// ---------------------------------------------------------------------------
|
|
46
43
|
// Helpers
|
|
@@ -49,11 +46,7 @@ const integrationSrc = readFileSync(
|
|
|
49
46
|
/** Files that are internal — no export or injectRoute needed */
|
|
50
47
|
function isInternal(filename: string): boolean {
|
|
51
48
|
const base = filename.replace(/\.ts$/, '')
|
|
52
|
-
return (
|
|
53
|
-
base.startsWith('_') ||
|
|
54
|
-
base.endsWith('-helpers') ||
|
|
55
|
-
base.endsWith('-management')
|
|
56
|
-
)
|
|
49
|
+
return base.startsWith('_') || base.endsWith('-helpers') || base.endsWith('-management')
|
|
57
50
|
}
|
|
58
51
|
|
|
59
52
|
/** Export key as used in package.json, e.g. "catalog-list" → "./catalog" special-cased */
|
|
@@ -74,7 +67,7 @@ function entrypoint(filename: string): string {
|
|
|
74
67
|
// Tests
|
|
75
68
|
// ---------------------------------------------------------------------------
|
|
76
69
|
|
|
77
|
-
const publicRoutes = routeFiles.filter(f => !isInternal(f))
|
|
70
|
+
const publicRoutes = routeFiles.filter((f) => !isInternal(f))
|
|
78
71
|
|
|
79
72
|
describe('Route-Registry-Konsistenz', () => {
|
|
80
73
|
describe('package.json exports', () => {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* surface global nav/footer as editable sections in the admin.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import { describe,
|
|
17
|
+
import { describe, expect, it } from 'vitest'
|
|
18
18
|
import { extractLayoutRegions } from '../init-scan-page'
|
|
19
19
|
|
|
20
20
|
// ---------------------------------------------------------------------------
|
|
@@ -24,10 +24,11 @@ import { extractLayoutRegions } from '../init-scan-page'
|
|
|
24
24
|
// ---------------------------------------------------------------------------
|
|
25
25
|
|
|
26
26
|
function pagePathToSectionKey(pagePath: string): string {
|
|
27
|
-
const pageKeyNorm =
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
const pageKeyNorm =
|
|
28
|
+
pagePath
|
|
29
|
+
.replace(/^src\/pages\//, '')
|
|
30
|
+
.replace(/\/(index)?\.astro$/, '')
|
|
31
|
+
.replace(/\.astro$/, '') || 'index'
|
|
31
32
|
return '_page_' + pageKeyNorm.replace(/\//g, '_')
|
|
32
33
|
}
|
|
33
34
|
|
|
@@ -41,7 +42,9 @@ describe('pagePathToSectionKey — correct key derivation', () => {
|
|
|
41
42
|
})
|
|
42
43
|
|
|
43
44
|
it('nested page', () => {
|
|
44
|
-
expect(pagePathToSectionKey('src/pages/docs/architecture.astro')).toBe(
|
|
45
|
+
expect(pagePathToSectionKey('src/pages/docs/architecture.astro')).toBe(
|
|
46
|
+
'_page_docs_architecture',
|
|
47
|
+
)
|
|
45
48
|
})
|
|
46
49
|
|
|
47
50
|
it('uses underscore not double-dash for nested', () => {
|
|
@@ -95,24 +98,24 @@ import { getSection } from 'setzkasten:content'
|
|
|
95
98
|
|
|
96
99
|
it('should extract header region', () => {
|
|
97
100
|
const regions = extractLayoutRegions(layoutWithBoth)
|
|
98
|
-
expect(regions.some(r => r.name === 'header')).toBe(true)
|
|
101
|
+
expect(regions.some((r) => r.name === 'header')).toBe(true)
|
|
99
102
|
})
|
|
100
103
|
|
|
101
104
|
it('should extract footer region', () => {
|
|
102
105
|
const regions = extractLayoutRegions(layoutWithBoth)
|
|
103
|
-
expect(regions.some(r => r.name === 'footer')).toBe(true)
|
|
106
|
+
expect(regions.some((r) => r.name === 'footer')).toBe(true)
|
|
104
107
|
})
|
|
105
108
|
|
|
106
109
|
it('header region HTML should contain the nav content', () => {
|
|
107
110
|
const regions = extractLayoutRegions(layoutWithBoth)
|
|
108
|
-
const header = regions.find(r => r.name === 'header')!
|
|
111
|
+
const header = regions.find((r) => r.name === 'header')!
|
|
109
112
|
expect(header.html).toContain('<nav>')
|
|
110
113
|
expect(header.html).toContain('Home')
|
|
111
114
|
})
|
|
112
115
|
|
|
113
116
|
it('footer region HTML should contain the footer content', () => {
|
|
114
117
|
const regions = extractLayoutRegions(layoutWithBoth)
|
|
115
|
-
const footer = regions.find(r => r.name === 'footer')!
|
|
118
|
+
const footer = regions.find((r) => r.name === 'footer')!
|
|
116
119
|
expect(footer.html).toContain('Lilapixel')
|
|
117
120
|
})
|
|
118
121
|
|