@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.
Files changed (166) hide show
  1. package/dist/api-routes/_auth-guard.d.ts +27 -3
  2. package/dist/api-routes/_auth-guard.js +5 -2
  3. package/dist/api-routes/_dev-session-secret.d.ts +8 -0
  4. package/dist/api-routes/_dev-session-secret.js +8 -0
  5. package/dist/api-routes/_github-token.js +1 -1
  6. package/dist/api-routes/_role-resolver.js +6 -3
  7. package/dist/api-routes/_session-secret.d.ts +19 -0
  8. package/dist/api-routes/_session-secret.js +7 -0
  9. package/dist/api-routes/_session-signing.d.ts +45 -0
  10. package/dist/api-routes/_session-signing.js +8 -0
  11. package/dist/api-routes/_webhook-dispatcher.js +4 -4
  12. package/dist/api-routes/asset-proxy.js +1 -1
  13. package/dist/api-routes/auth-callback.js +12 -5
  14. package/dist/api-routes/auth-logout.d.ts +4 -4
  15. package/dist/api-routes/auth-logout.js +8 -2
  16. package/dist/api-routes/auth-session.d.ts +6 -0
  17. package/dist/api-routes/auth-session.js +19 -19
  18. package/dist/api-routes/auth-setzkasten-login.js +14 -7
  19. package/dist/api-routes/catalog-add.js +59 -17
  20. package/dist/api-routes/catalog-export.js +14 -4
  21. package/dist/api-routes/config.d.ts +10 -3
  22. package/dist/api-routes/config.js +26 -4
  23. package/dist/api-routes/deploy-hook.js +8 -8
  24. package/dist/api-routes/editors.d.ts +1 -1
  25. package/dist/api-routes/editors.js +5 -2
  26. package/dist/api-routes/github-proxy.js +30 -8
  27. package/dist/api-routes/global-config.js +6 -3
  28. package/dist/api-routes/history-rollback.js +31 -14
  29. package/dist/api-routes/history-version.js +8 -6
  30. package/dist/api-routes/history.js +5 -2
  31. package/dist/api-routes/icons-local.js +1 -1
  32. package/dist/api-routes/init-add-section.js +113 -47
  33. package/dist/api-routes/init-apply.js +56 -42
  34. package/dist/api-routes/init-migrate.js +43 -36
  35. package/dist/api-routes/init-scan-page.d.ts +1 -1
  36. package/dist/api-routes/init-scan-page.js +59 -13
  37. package/dist/api-routes/init-scan.js +22 -7
  38. package/dist/api-routes/migrate-to-multi.js +5 -2
  39. package/dist/api-routes/pages.js +15 -4
  40. package/dist/api-routes/section-add.js +68 -16
  41. package/dist/api-routes/section-commit-pending.js +70 -22
  42. package/dist/api-routes/section-delete.js +49 -14
  43. package/dist/api-routes/section-duplicate.js +65 -16
  44. package/dist/api-routes/section-prepare-copy.js +15 -2
  45. package/dist/api-routes/section-prepare.js +25 -4
  46. package/dist/api-routes/setup-github-app-bounce.js +15 -1
  47. package/dist/api-routes/setup-github-app-branches.js +9 -6
  48. package/dist/api-routes/setup-github-app-callback.js +24 -1
  49. package/dist/api-routes/setup-github-app-credentials.d.ts +27 -0
  50. package/dist/api-routes/setup-github-app-credentials.js +43 -0
  51. package/dist/api-routes/setup-github-app-installed.js +22 -1
  52. package/dist/api-routes/setup-github-app-repos.js +5 -2
  53. package/dist/api-routes/setup-github-app.d.ts +4 -0
  54. package/dist/api-routes/setup-github-app.js +19 -2
  55. package/dist/api-routes/updater-register.js +7 -1
  56. package/dist/api-routes/webhooks-status.js +5 -2
  57. package/dist/api-routes/webhooks-test.js +9 -8
  58. package/dist/api-routes/webhooks.js +12 -14
  59. package/dist/api-routes/websites-add.js +5 -2
  60. package/dist/api-routes/websites-remove.js +5 -2
  61. package/dist/{chunk-ZQDGGWJP.js → chunk-5KMGSFCZ.js} +2 -2
  62. package/dist/{chunk-Q3N336KR.js → chunk-CDXCYYQR.js} +29 -24
  63. package/dist/{chunk-NKDATSPA.js → chunk-DP6RTINQ.js} +1 -1
  64. package/dist/chunk-KENFINT4.js +76 -0
  65. package/dist/chunk-ONP6BRZO.js +47 -0
  66. package/dist/{chunk-INIWFKQ3.js → chunk-Q5HV47DW.js} +33 -19
  67. package/dist/chunk-QVCW6EF3.js +26 -0
  68. package/dist/{chunk-TD76R3A6.js → chunk-UHI6323G.js} +293 -174
  69. package/dist/{chunk-AM4DZXXM.js → chunk-UJAFZEX2.js} +76 -9
  70. package/package.json +12 -6
  71. package/src/api-routes/__tests__/_session-signing.test.ts +114 -0
  72. package/src/api-routes/__tests__/_session-test-helper.ts +27 -0
  73. package/src/api-routes/__tests__/add-section-helpers.test.ts +59 -25
  74. package/src/api-routes/__tests__/auth-guard.test.ts +46 -7
  75. package/src/api-routes/__tests__/catalog-api.test.ts +14 -6
  76. package/src/api-routes/__tests__/commit-trailers.test.ts +5 -5
  77. package/src/api-routes/__tests__/deferred-operations.test.ts +9 -12
  78. package/src/api-routes/__tests__/deploy-hook.test.ts +3 -8
  79. package/src/api-routes/__tests__/feature-gate.test.ts +1 -1
  80. package/src/api-routes/__tests__/github-cache.test.ts +1 -1
  81. package/src/api-routes/__tests__/github-token.test.ts +1 -1
  82. package/src/api-routes/__tests__/global-config-theme.test.ts +4 -4
  83. package/src/api-routes/__tests__/history-rollback.test.ts +6 -3
  84. package/src/api-routes/__tests__/history.test.ts +9 -6
  85. package/src/api-routes/__tests__/init-scan-page-resolve-config.test.ts +11 -3
  86. package/src/api-routes/__tests__/migrate-to-multi.test.ts +5 -1
  87. package/src/api-routes/__tests__/pages-meta-store.test.ts +10 -5
  88. package/src/api-routes/__tests__/pages.test.ts +7 -2
  89. package/src/api-routes/__tests__/patch-page-file.test.ts +71 -19
  90. package/src/api-routes/__tests__/route-registry.test.ts +11 -18
  91. package/src/api-routes/__tests__/scan-page-helpers.test.ts +13 -10
  92. package/src/api-routes/__tests__/section-management.test.ts +28 -28
  93. package/src/api-routes/__tests__/setup-github-app-callback.test.ts +58 -16
  94. package/src/api-routes/__tests__/setup-github-app-repos.test.ts +4 -5
  95. package/src/api-routes/__tests__/setup-github-app.test.ts +27 -7
  96. package/src/api-routes/__tests__/storage-config-for-request.test.ts +83 -0
  97. package/src/api-routes/__tests__/updater-register.test.ts +230 -0
  98. package/src/api-routes/__tests__/webhook-signing.test.ts +1 -1
  99. package/src/api-routes/__tests__/webhooks.test.ts +19 -7
  100. package/src/api-routes/__tests__/websites-add.test.ts +2 -1
  101. package/src/api-routes/__tests__/websites-remove.test.ts +2 -1
  102. package/src/api-routes/_auth-guard.ts +47 -15
  103. package/src/api-routes/_commit-trailers.ts +3 -2
  104. package/src/api-routes/_dev-session-secret.ts +79 -0
  105. package/src/api-routes/_github-token.ts +1 -1
  106. package/src/api-routes/_pages-meta-store.ts +2 -2
  107. package/src/api-routes/_role-resolver.ts +7 -5
  108. package/src/api-routes/_session-secret.ts +46 -0
  109. package/src/api-routes/_session-signing.ts +135 -0
  110. package/src/api-routes/_vercel-origin.ts +2 -6
  111. package/src/api-routes/_webhook-dispatcher.ts +12 -16
  112. package/src/api-routes/_website-resolver.ts +3 -10
  113. package/src/api-routes/auth-callback.ts +9 -5
  114. package/src/api-routes/auth-login.ts +5 -3
  115. package/src/api-routes/auth-logout.ts +18 -1
  116. package/src/api-routes/auth-session.ts +13 -21
  117. package/src/api-routes/auth-setzkasten-login.ts +12 -9
  118. package/src/api-routes/catalog-add.ts +89 -31
  119. package/src/api-routes/catalog-export.ts +30 -10
  120. package/src/api-routes/config.ts +39 -6
  121. package/src/api-routes/deploy-hook.ts +13 -11
  122. package/src/api-routes/editors.ts +33 -22
  123. package/src/api-routes/github-proxy.ts +25 -11
  124. package/src/api-routes/global-config.ts +103 -18
  125. package/src/api-routes/history-rollback.ts +41 -14
  126. package/src/api-routes/history-version.ts +5 -6
  127. package/src/api-routes/history.ts +3 -3
  128. package/src/api-routes/icons-local.ts +2 -2
  129. package/src/api-routes/init-add-section.ts +174 -79
  130. package/src/api-routes/init-apply.ts +71 -56
  131. package/src/api-routes/init-migrate.ts +54 -48
  132. package/src/api-routes/init-scan-page.ts +77 -30
  133. package/src/api-routes/init-scan.ts +19 -11
  134. package/src/api-routes/pages.ts +16 -11
  135. package/src/api-routes/section-add.ts +98 -27
  136. package/src/api-routes/section-commit-pending.ts +87 -34
  137. package/src/api-routes/section-delete.ts +76 -27
  138. package/src/api-routes/section-duplicate.ts +95 -28
  139. package/src/api-routes/section-management.ts +3 -7
  140. package/src/api-routes/section-prepare-copy.ts +29 -8
  141. package/src/api-routes/section-prepare.ts +38 -10
  142. package/src/api-routes/setup-github-app-bounce.ts +7 -1
  143. package/src/api-routes/setup-github-app-branches.ts +6 -7
  144. package/src/api-routes/setup-github-app-callback.ts +18 -1
  145. package/src/api-routes/setup-github-app-credentials.ts +55 -0
  146. package/src/api-routes/setup-github-app-installed.ts +12 -1
  147. package/src/api-routes/setup-github-app-repos.ts +2 -3
  148. package/src/api-routes/setup-github-app.ts +14 -5
  149. package/src/api-routes/updater-check.ts +6 -4
  150. package/src/api-routes/updater-register.ts +34 -20
  151. package/src/api-routes/updater-transfer.ts +8 -6
  152. package/src/api-routes/updater-unbind.ts +14 -10
  153. package/src/api-routes/webhooks-test.ts +9 -11
  154. package/src/api-routes/webhooks.ts +15 -19
  155. package/src/init/__tests__/page-level.test.ts +279 -105
  156. package/src/init/__tests__/page-list-coverage.test.ts +70 -70
  157. package/src/init/__tests__/patcher-child-component.test.ts +12 -3
  158. package/src/init/__tests__/patcher-edge-cases.test.ts +47 -23
  159. package/src/init/__tests__/patcher-mixed-content-wrapper.test.ts +16 -6
  160. package/src/init/__tests__/patcher-page-mode.test.ts +30 -20
  161. package/src/init/__tests__/section-pipeline.test.ts +53 -19
  162. package/src/init/astro-config-patcher.ts +4 -18
  163. package/src/init/astro-detector.ts +2 -7
  164. package/src/init/astro-section-analyzer-v2.ts +475 -193
  165. package/src/init/field-label-enricher.ts +6 -6
  166. package/src/init/template-patcher-v2.ts +218 -97
@@ -10,13 +10,13 @@
10
10
  * 3. Patcher mode:'page' (injects getSection instead of Astro.props)
11
11
  */
12
12
 
13
- import { describe, it, expect, beforeAll } from 'vitest'
14
13
  import { readFileSync, readdirSync } from 'fs'
15
- import { join, basename } from 'path'
14
+ import { basename, join } from 'path'
15
+ import type { RepoFile } from '@setzkasten-cms/core/init'
16
+ import { beforeAll, describe, expect, it } from 'vitest'
17
+ import { extractLayoutImport, extractSectionImports } from '../../init/astro-detector'
16
18
  import { analyzeAstroSection } from '../../init/astro-section-analyzer-v2'
17
19
  import { patchTemplateForFields } from '../../init/template-patcher-v2'
18
- import { extractSectionImports, extractLayoutImport } from '../../init/astro-detector'
19
- import type { RepoFile } from '@setzkasten-cms/core/init'
20
20
 
21
21
  const SECTIONS_DIR = join(import.meta.dirname!, '..', '..', '..', '..', '..', 'test-sections')
22
22
  const PAGES_DIR = join(SECTIONS_DIR, 'pages')
@@ -34,9 +34,17 @@ describe('Page-level: pure inline content', () => {
34
34
 
35
35
  beforeAll(async () => {
36
36
  source = readFileSync(join(SECTIONS_DIR, 'PageInlineContent.astro'), 'utf-8')
37
- section = await analyzeAstroSection(source, '_page_docs', 'docsPage', 'src/pages/docs/index.astro', { mode: 'page' })
37
+ section = await analyzeAstroSection(
38
+ source,
39
+ '_page_docs',
40
+ 'docsPage',
41
+ 'src/pages/docs/index.astro',
42
+ { mode: 'page' },
43
+ )
38
44
  groups = (section as any)._analyzerResult?.repeatedGroups ?? []
39
- patched = await patchTemplateForFields(source, '_page_docs', section.fields, groups, { mode: 'page' })
45
+ patched = await patchTemplateForFields(source, '_page_docs', section.fields, groups, {
46
+ mode: 'page',
47
+ })
40
48
  })
41
49
 
42
50
  it('should detect fields from inline content', () => {
@@ -44,10 +52,10 @@ describe('Page-level: pure inline content', () => {
44
52
  })
45
53
 
46
54
  it('should detect text fields (headings, paragraphs, overline)', () => {
47
- const textFields = section.fields.filter(f => f.type === 'text')
55
+ const textFields = section.fields.filter((f) => f.type === 'text')
48
56
  expect(textFields.length).toBeGreaterThan(0)
49
57
  // Should find the heading and description
50
- const keys = textFields.map(f => f.key)
58
+ const keys = textFields.map((f) => f.key)
51
59
  expect(keys).toContain('heading')
52
60
  expect(keys).toContain('description')
53
61
  })
@@ -95,7 +103,13 @@ describe('Page-level: mixed content (imports + inline)', () => {
95
103
  beforeAll(async () => {
96
104
  source = readFileSync(join(SECTIONS_DIR, 'PageMixedContent.astro'), 'utf-8')
97
105
  // Analyze the page as a page-level section (same as init-scan-page.ts would)
98
- section = await analyzeAstroSection(source, '_page_index', 'indexPage', 'src/pages/index.astro', { mode: 'page' })
106
+ section = await analyzeAstroSection(
107
+ source,
108
+ '_page_index',
109
+ 'indexPage',
110
+ 'src/pages/index.astro',
111
+ { mode: 'page' },
112
+ )
99
113
  groups = (section as any)._analyzerResult?.repeatedGroups ?? []
100
114
  })
101
115
 
@@ -104,16 +118,17 @@ describe('Page-level: mixed content (imports + inline)', () => {
104
118
  })
105
119
 
106
120
  it('should detect inline text fields', () => {
107
- const textFields = section.fields.filter(f => f.type === 'text')
121
+ const textFields = section.fields.filter((f) => f.type === 'text')
108
122
  expect(textFields.length).toBeGreaterThan(0)
109
123
  })
110
124
 
111
125
  it('should detect inline text fields (heading, paragraph)', () => {
112
- const textFields = section.fields.filter(f => f.type === 'text')
126
+ const textFields = section.fields.filter((f) => f.type === 'text')
113
127
  expect(textFields.length).toBeGreaterThan(0)
114
128
  // Should find "Was unsere Kunden sagen" or "Feedback von echten Nutzern."
115
129
  const hasExpectedText = textFields.some(
116
- f => typeof f.defaultValue === 'string' &&
130
+ (f) =>
131
+ typeof f.defaultValue === 'string' &&
117
132
  (f.defaultValue.includes('Kunden') || f.defaultValue.includes('Feedback')),
118
133
  )
119
134
  expect(hasExpectedText, 'Should detect inline heading or paragraph text').toBe(true)
@@ -143,7 +158,13 @@ describe('Patcher mode: component vs page', () => {
143
158
 
144
159
  beforeAll(async () => {
145
160
  source = readFileSync(join(SECTIONS_DIR, 'PageInlineContent.astro'), 'utf-8')
146
- section = await analyzeAstroSection(source, '_page_docs', 'docsPage', 'src/pages/docs/index.astro', { mode: 'page' })
161
+ section = await analyzeAstroSection(
162
+ source,
163
+ '_page_docs',
164
+ 'docsPage',
165
+ 'src/pages/docs/index.astro',
166
+ { mode: 'page' },
167
+ )
147
168
  groups = (section as any)._analyzerResult?.repeatedGroups ?? []
148
169
  })
149
170
 
@@ -154,7 +175,9 @@ describe('Patcher mode: component vs page', () => {
154
175
  })
155
176
 
156
177
  it('page mode should inject getSection', async () => {
157
- const patched = await patchTemplateForFields(source, '_page_docs', section.fields, groups, { mode: 'page' })
178
+ const patched = await patchTemplateForFields(source, '_page_docs', section.fields, groups, {
179
+ mode: 'page',
180
+ })
158
181
  expect(patched).toContain("import { getSection } from 'setzkasten:content'")
159
182
  expect(patched).toContain("getSection('_page_docs')")
160
183
  expect(patched).not.toContain('Astro.props')
@@ -174,7 +197,13 @@ describe('Page-level: minimal page (no content)', () => {
174
197
  `
175
198
 
176
199
  it('should detect zero fields for a truly empty page', async () => {
177
- const section = await analyzeAstroSection(minimalPage, '_page_empty', 'emptyPage', 'src/pages/empty.astro', { mode: 'page' })
200
+ const section = await analyzeAstroSection(
201
+ minimalPage,
202
+ '_page_empty',
203
+ 'emptyPage',
204
+ 'src/pages/empty.astro',
205
+ { mode: 'page' },
206
+ )
178
207
  expect(section.fields).toHaveLength(0)
179
208
  })
180
209
  })
@@ -196,7 +225,13 @@ const skData = getSection('_page_about');
196
225
  `
197
226
 
198
227
  it('should detect alreadyIntegrated flag', async () => {
199
- const section = await analyzeAstroSection(integratedPage, '_page_about', 'aboutPage', 'src/pages/about.astro', { mode: 'page' })
228
+ const section = await analyzeAstroSection(
229
+ integratedPage,
230
+ '_page_about',
231
+ 'aboutPage',
232
+ 'src/pages/about.astro',
233
+ { mode: 'page' },
234
+ )
200
235
  expect(section.alreadyIntegrated).toBe(true)
201
236
  })
202
237
  })
@@ -229,18 +264,24 @@ describe('Full scan flow: mixed page (imports + inline)', () => {
229
264
  imports = extractSectionImports(source, 'src/pages/index.astro', repoFiles, '')
230
265
 
231
266
  // Step 2: page-level analysis (same as init-scan-page.ts after the imports loop)
232
- pageSection = await analyzeAstroSection(source, '_page_index', 'indexPage', 'src/pages/index.astro', { mode: 'page' })
267
+ pageSection = await analyzeAstroSection(
268
+ source,
269
+ '_page_index',
270
+ 'indexPage',
271
+ 'src/pages/index.astro',
272
+ { mode: 'page' },
273
+ )
233
274
  })
234
275
 
235
276
  it('should find imported section components via extractSectionImports', () => {
236
277
  expect(imports.length).toBeGreaterThan(0)
237
- const componentNames = imports.map(i => i.componentName)
278
+ const componentNames = imports.map((i) => i.componentName)
238
279
  expect(componentNames).toContain('HeroSection')
239
280
  expect(componentNames).toContain('FeaturesSection')
240
281
  })
241
282
 
242
283
  it('should resolve section keys from imports', () => {
243
- const keys = imports.map(i => i.sectionKey)
284
+ const keys = imports.map((i) => i.sectionKey)
244
285
  expect(keys).toContain('hero')
245
286
  expect(keys).toContain('features')
246
287
  })
@@ -255,7 +296,8 @@ describe('Full scan flow: mixed page (imports + inline)', () => {
255
296
  expect(pageSection.fields.length).toBeGreaterThan(0)
256
297
  // Page-level should find the inline "Was unsere Kunden sagen" heading
257
298
  const hasInlineContent = pageSection.fields.some(
258
- f => typeof f.defaultValue === 'string' &&
299
+ (f) =>
300
+ typeof f.defaultValue === 'string' &&
259
301
  (f.defaultValue.includes('Kunden') || f.defaultValue.includes('Feedback')),
260
302
  )
261
303
  expect(hasInlineContent, 'Page-level should find inline text content').toBe(true)
@@ -264,7 +306,7 @@ describe('Full scan flow: mixed page (imports + inline)', () => {
264
306
  it('should produce non-overlapping results (imports vs inline)', () => {
265
307
  // Import keys are "hero", "features" — page-level key is "_page_index"
266
308
  // They should never collide
267
- const importKeys = new Set(imports.map(i => i.sectionKey))
309
+ const importKeys = new Set(imports.map((i) => i.sectionKey))
268
310
  expect(importKeys.has(pageSection.key)).toBe(false)
269
311
  })
270
312
 
@@ -296,7 +338,13 @@ describe('Full scan flow: pure inline page (no imports)', () => {
296
338
  ]
297
339
 
298
340
  imports = extractSectionImports(source, 'src/pages/docs/index.astro', repoFiles, '')
299
- pageSection = await analyzeAstroSection(source, '_page_docs', 'docsPage', 'src/pages/docs/index.astro', { mode: 'page' })
341
+ pageSection = await analyzeAstroSection(
342
+ source,
343
+ '_page_docs',
344
+ 'docsPage',
345
+ 'src/pages/docs/index.astro',
346
+ { mode: 'page' },
347
+ )
300
348
  })
301
349
 
302
350
  it('should find zero section imports for a pure inline page', () => {
@@ -308,7 +356,7 @@ describe('Full scan flow: pure inline page (no imports)', () => {
308
356
  })
309
357
 
310
358
  it('should detect heading and description fields', () => {
311
- const keys = pageSection.fields.map(f => f.key)
359
+ const keys = pageSection.fields.map((f) => f.key)
312
360
  expect(keys).toContain('heading')
313
361
  expect(keys).toContain('description')
314
362
  })
@@ -327,23 +375,34 @@ describe('Page-level: .map()-only arrays detected', () => {
327
375
  beforeAll(async () => {
328
376
  const source = readFileSync(join(SECTIONS_DIR, 'PageInlineContent.astro'), 'utf-8')
329
377
  // Page mode: .map()-only arrays should be kept
330
- section = await analyzeAstroSection(source, '_page_docs', 'docsPage', 'src/pages/docs/index.astro', { mode: 'page' })
378
+ section = await analyzeAstroSection(
379
+ source,
380
+ '_page_docs',
381
+ 'docsPage',
382
+ 'src/pages/docs/index.astro',
383
+ { mode: 'page' },
384
+ )
331
385
  // Component mode (default): .map()-only arrays should be filtered
332
- sectionComponent = await analyzeAstroSection(source, 'docs', 'DocsSection', 'src/components/docs.astro')
386
+ sectionComponent = await analyzeAstroSection(
387
+ source,
388
+ 'docs',
389
+ 'DocsSection',
390
+ 'src/components/docs.astro',
391
+ )
333
392
  })
334
393
 
335
394
  it('should detect the sections array on page mode', () => {
336
- const arrayFields = section.fields.filter(f => f.type === 'array')
395
+ const arrayFields = section.fields.filter((f) => f.type === 'array')
337
396
  expect(arrayFields.length).toBeGreaterThan(0)
338
397
  })
339
398
 
340
399
  it('should detect "sections" variable as a field on page mode', () => {
341
- const keys = section.fields.map(f => f.key)
400
+ const keys = section.fields.map((f) => f.key)
342
401
  expect(keys).toContain('sections')
343
402
  })
344
403
 
345
404
  it('should NOT detect "sections" variable in component mode (.map()-only filter)', () => {
346
- const keys = sectionComponent.fields.map(f => f.key)
405
+ const keys = sectionComponent.fields.map((f) => f.key)
347
406
  expect(keys).not.toContain('sections')
348
407
  })
349
408
  })
@@ -370,15 +429,33 @@ const title = 'Meine Seite';
370
429
  `
371
430
 
372
431
  it('should not remove export const prerender', async () => {
373
- const section = await analyzeAstroSection(pageWithPrerender, '_page_test', 'testPage', 'test.astro', { mode: 'page' })
374
- const patched = await patchTemplateForFields(pageWithPrerender, '_page_test', section.fields, [], { mode: 'page' })
432
+ const section = await analyzeAstroSection(
433
+ pageWithPrerender,
434
+ '_page_test',
435
+ 'testPage',
436
+ 'test.astro',
437
+ { mode: 'page' },
438
+ )
439
+ const patched = await patchTemplateForFields(
440
+ pageWithPrerender,
441
+ '_page_test',
442
+ section.fields,
443
+ [],
444
+ { mode: 'page' },
445
+ )
375
446
  expect(patched).toContain('export const prerender = true')
376
447
  expect(patched).not.toMatch(/^export\s*$/m) // no bare "export " line
377
448
  })
378
449
 
379
450
  it('should not detect prerender as a content field', async () => {
380
- const section = await analyzeAstroSection(pageWithPrerender, '_page_test', 'testPage', 'test.astro', { mode: 'page' })
381
- const keys = section.fields.map(f => f.key)
451
+ const section = await analyzeAstroSection(
452
+ pageWithPrerender,
453
+ '_page_test',
454
+ 'testPage',
455
+ 'test.astro',
456
+ { mode: 'page' },
457
+ )
458
+ const keys = section.fields.map((f) => f.key)
382
459
  expect(keys).not.toContain('prerender')
383
460
  })
384
461
  })
@@ -412,7 +489,13 @@ const SECTION_COMPONENTS: Record<string, any> = {
412
489
  `
413
490
 
414
491
  it('should detect alreadyIntegrated and skip adoption', async () => {
415
- const section = await analyzeAstroSection(integratedPage, '_page_index', 'indexPage', 'test.astro', { mode: 'page' })
492
+ const section = await analyzeAstroSection(
493
+ integratedPage,
494
+ '_page_index',
495
+ 'indexPage',
496
+ 'test.astro',
497
+ { mode: 'page' },
498
+ )
416
499
  expect(section.alreadyIntegrated).toBe(true)
417
500
  })
418
501
  })
@@ -471,7 +554,13 @@ describe('Layout regions: header and footer analysis', () => {
471
554
  expect(headerHtml).toBeTruthy()
472
555
 
473
556
  const regionSource = `---\n---\n\n${headerHtml}`
474
- const section = await analyzeAstroSection(regionSource, '_layout_header', 'header', 'test-layout.astro', { mode: 'page' })
557
+ const section = await analyzeAstroSection(
558
+ regionSource,
559
+ '_layout_header',
560
+ 'header',
561
+ 'test-layout.astro',
562
+ { mode: 'page' },
563
+ )
475
564
  expect(section.fields.length).toBeGreaterThan(0)
476
565
  })
477
566
 
@@ -480,21 +569,36 @@ describe('Layout regions: header and footer analysis', () => {
480
569
  expect(footerHtml).toBeTruthy()
481
570
 
482
571
  const regionSource = `---\n---\n\n${footerHtml}`
483
- const section = await analyzeAstroSection(regionSource, '_layout_footer', 'footer', 'test-layout.astro', { mode: 'page' })
572
+ const section = await analyzeAstroSection(
573
+ regionSource,
574
+ '_layout_footer',
575
+ 'footer',
576
+ 'test-layout.astro',
577
+ { mode: 'page' },
578
+ )
484
579
  expect(section.fields.length).toBeGreaterThan(0)
485
580
  })
486
581
 
487
582
  it('should detect text fields in header (brand name, nav links)', async () => {
488
583
  const headerHtml = extractRegionHtml(layoutSource, 'header')!
489
584
  const regionSource = `---\n---\n\n${headerHtml}`
490
- const section = await analyzeAstroSection(regionSource, '_layout_header', 'header', 'test-layout.astro', { mode: 'page' })
585
+ const section = await analyzeAstroSection(
586
+ regionSource,
587
+ '_layout_header',
588
+ 'header',
589
+ 'test-layout.astro',
590
+ { mode: 'page' },
591
+ )
491
592
 
492
- const textFields = section.fields.filter(f => f.type === 'text')
593
+ const textFields = section.fields.filter((f) => f.type === 'text')
493
594
  expect(textFields.length).toBeGreaterThan(0)
494
595
  // Should find brand name or nav link text
495
596
  const hasNavContent = textFields.some(
496
- f => typeof f.defaultValue === 'string' &&
497
- (f.defaultValue.includes('MeinBrand') || f.defaultValue.includes('Features') || f.defaultValue.includes('Jetzt starten')),
597
+ (f) =>
598
+ typeof f.defaultValue === 'string' &&
599
+ (f.defaultValue.includes('MeinBrand') ||
600
+ f.defaultValue.includes('Features') ||
601
+ f.defaultValue.includes('Jetzt starten')),
498
602
  )
499
603
  expect(hasNavContent, 'Should detect header text content').toBe(true)
500
604
  })
@@ -502,13 +606,22 @@ describe('Layout regions: header and footer analysis', () => {
502
606
  it('should detect text fields in footer (brand, tagline, copyright)', async () => {
503
607
  const footerHtml = extractRegionHtml(layoutSource, 'footer')!
504
608
  const regionSource = `---\n---\n\n${footerHtml}`
505
- const section = await analyzeAstroSection(regionSource, '_layout_footer', 'footer', 'test-layout.astro', { mode: 'page' })
609
+ const section = await analyzeAstroSection(
610
+ regionSource,
611
+ '_layout_footer',
612
+ 'footer',
613
+ 'test-layout.astro',
614
+ { mode: 'page' },
615
+ )
506
616
 
507
- const textFields = section.fields.filter(f => f.type === 'text')
617
+ const textFields = section.fields.filter((f) => f.type === 'text')
508
618
  expect(textFields.length).toBeGreaterThan(0)
509
619
  const hasFooterContent = textFields.some(
510
- f => typeof f.defaultValue === 'string' &&
511
- (f.defaultValue.includes('MeinBrand') || f.defaultValue.includes('CMS') || f.defaultValue.includes('Rechte')),
620
+ (f) =>
621
+ typeof f.defaultValue === 'string' &&
622
+ (f.defaultValue.includes('MeinBrand') ||
623
+ f.defaultValue.includes('CMS') ||
624
+ f.defaultValue.includes('Rechte')),
512
625
  )
513
626
  expect(hasFooterContent, 'Should detect footer text content').toBe(true)
514
627
  })
@@ -521,7 +634,9 @@ describe('Layout regions: header and footer analysis', () => {
521
634
  // ---------------------------------------------------------------------------
522
635
 
523
636
  describe('Page sweep: all pages in pages/', () => {
524
- const pageFiles = readdirSync(PAGES_DIR).filter(f => f.endsWith('.astro')).sort()
637
+ const pageFiles = readdirSync(PAGES_DIR)
638
+ .filter((f) => f.endsWith('.astro'))
639
+ .sort()
525
640
 
526
641
  for (const file of pageFiles) {
527
642
  describe(file, () => {
@@ -530,8 +645,13 @@ describe('Page sweep: all pages in pages/', () => {
530
645
 
531
646
  beforeAll(async () => {
532
647
  source = readFileSync(join(PAGES_DIR, file), 'utf-8')
533
- const pageKey = `_page_${basename(file, '.astro').replace(/([A-Z])/g, '_$1').toLowerCase().replace(/^_/, '')}`
534
- section = await analyzeAstroSection(source, pageKey, 'testPage', `src/pages/${file}`, { mode: 'page' })
648
+ const pageKey = `_page_${basename(file, '.astro')
649
+ .replace(/([A-Z])/g, '_$1')
650
+ .toLowerCase()
651
+ .replace(/^_/, '')}`
652
+ section = await analyzeAstroSection(source, pageKey, 'testPage', `src/pages/${file}`, {
653
+ mode: 'page',
654
+ })
535
655
  })
536
656
 
537
657
  it('should not crash during analysis', () => {
@@ -567,22 +687,26 @@ describe('DocsIndexPage: nested sections[].items[] detection', () => {
567
687
  beforeAll(async () => {
568
688
  source = readFileSync(join(PAGES_DIR, 'DocsIndexPage.astro'), 'utf-8')
569
689
  section = await analyzeAstroSection(
570
- source, '_page_docs', 'docsPage', 'src/pages/docs/index.astro', { mode: 'page' },
690
+ source,
691
+ '_page_docs',
692
+ 'docsPage',
693
+ 'src/pages/docs/index.astro',
694
+ { mode: 'page' },
571
695
  )
572
696
  })
573
697
 
574
698
  it('should detect the outer sections array', () => {
575
- const keys = section.fields.map(f => f.key)
699
+ const keys = section.fields.map((f) => f.key)
576
700
  expect(keys).toContain('sections')
577
701
  })
578
702
 
579
703
  it('should detect sections as an array field', () => {
580
- const sectionsField = section.fields.find(f => f.key === 'sections')
704
+ const sectionsField = section.fields.find((f) => f.key === 'sections')
581
705
  expect(sectionsField?.type).toBe('array')
582
706
  })
583
707
 
584
708
  it('should detect items inside each section as an array field (not [object Object])', () => {
585
- const sectionsField = section.fields.find(f => f.key === 'sections')
709
+ const sectionsField = section.fields.find((f) => f.key === 'sections')
586
710
  const rows = sectionsField?.defaultValue as Array<Record<string, unknown>> | undefined
587
711
  expect(Array.isArray(rows)).toBe(true)
588
712
  if (rows) {
@@ -600,7 +724,7 @@ describe('DocsIndexPage: nested sections[].items[] detection', () => {
600
724
  const allValues = JSON.stringify(section.fields)
601
725
  expect(allValues).not.toContain('[object Object]')
602
726
  // At least one item object should have a title key
603
- const sectionsField = section.fields.find(f => f.key === 'sections')
727
+ const sectionsField = section.fields.find((f) => f.key === 'sections')
604
728
  const rows = sectionsField?.defaultValue as Array<Record<string, unknown>> | undefined
605
729
  if (rows?.[0]) {
606
730
  const items = rows[0]['items'] as Array<Record<string, unknown>> | undefined
@@ -622,17 +746,21 @@ describe('EventSchedulePage: nested days[].sessions[] detection', () => {
622
746
  beforeAll(async () => {
623
747
  source = readFileSync(join(PAGES_DIR, 'EventSchedulePage.astro'), 'utf-8')
624
748
  section = await analyzeAstroSection(
625
- source, '_page_event_schedule', 'eventPage', 'src/pages/event.astro', { mode: 'page' },
749
+ source,
750
+ '_page_event_schedule',
751
+ 'eventPage',
752
+ 'src/pages/event.astro',
753
+ { mode: 'page' },
626
754
  )
627
755
  })
628
756
 
629
757
  it('should detect the outer days array', () => {
630
- const keys = section.fields.map(f => f.key)
758
+ const keys = section.fields.map((f) => f.key)
631
759
  expect(keys).toContain('days')
632
760
  })
633
761
 
634
762
  it('should detect sessions inside each day without [object Object]', () => {
635
- const daysField = section.fields.find(f => f.key === 'days')
763
+ const daysField = section.fields.find((f) => f.key === 'days')
636
764
  const rows = daysField?.defaultValue as Array<Record<string, unknown>> | undefined
637
765
  expect(Array.isArray(rows)).toBe(true)
638
766
  if (rows) {
@@ -644,7 +772,7 @@ describe('EventSchedulePage: nested days[].sessions[] detection', () => {
644
772
  })
645
773
 
646
774
  it('should detect session sub-fields (time, title, speaker)', () => {
647
- const daysField = section.fields.find(f => f.key === 'days')
775
+ const daysField = section.fields.find((f) => f.key === 'days')
648
776
  const rows = daysField?.defaultValue as Array<Record<string, unknown>> | undefined
649
777
  const sessions = rows?.[0]?.['sessions'] as Array<Record<string, unknown>> | undefined
650
778
  expect(sessions?.[0]).toHaveProperty('title')
@@ -652,8 +780,10 @@ describe('EventSchedulePage: nested days[].sessions[] detection', () => {
652
780
  })
653
781
 
654
782
  it('should also detect frontmatter scalar vars (eventTitle, eventLocation)', () => {
655
- const keys = section.fields.map(f => f.key)
656
- const hasScalars = keys.some(k => k === 'eventTitle' || k === 'eventLocation' || k === 'eventDate')
783
+ const keys = section.fields.map((f) => f.key)
784
+ const hasScalars = keys.some(
785
+ (k) => k === 'eventTitle' || k === 'eventLocation' || k === 'eventDate',
786
+ )
657
787
  expect(hasScalars).toBe(true)
658
788
  })
659
789
  })
@@ -669,17 +799,21 @@ describe('ChangelogPage: nested releases[].changes[] detection', () => {
669
799
  beforeAll(async () => {
670
800
  source = readFileSync(join(PAGES_DIR, 'ChangelogPage.astro'), 'utf-8')
671
801
  section = await analyzeAstroSection(
672
- source, '_page_changelog', 'changelogPage', 'src/pages/changelog.astro', { mode: 'page' },
802
+ source,
803
+ '_page_changelog',
804
+ 'changelogPage',
805
+ 'src/pages/changelog.astro',
806
+ { mode: 'page' },
673
807
  )
674
808
  })
675
809
 
676
810
  it('should detect the releases array', () => {
677
- const keys = section.fields.map(f => f.key)
811
+ const keys = section.fields.map((f) => f.key)
678
812
  expect(keys).toContain('releases')
679
813
  })
680
814
 
681
815
  it('should detect changes inside each release without [object Object]', () => {
682
- const releasesField = section.fields.find(f => f.key === 'releases')
816
+ const releasesField = section.fields.find((f) => f.key === 'releases')
683
817
  const rows = releasesField?.defaultValue as Array<Record<string, unknown>> | undefined
684
818
  expect(Array.isArray(rows)).toBe(true)
685
819
  if (rows) {
@@ -691,7 +825,7 @@ describe('ChangelogPage: nested releases[].changes[] detection', () => {
691
825
  })
692
826
 
693
827
  it('should detect change sub-fields (type, text)', () => {
694
- const releasesField = section.fields.find(f => f.key === 'releases')
828
+ const releasesField = section.fields.find((f) => f.key === 'releases')
695
829
  const rows = releasesField?.defaultValue as Array<Record<string, unknown>> | undefined
696
830
  const changes = rows?.[0]?.['changes'] as Array<Record<string, unknown>> | undefined
697
831
  expect(changes?.[0]).toHaveProperty('text')
@@ -710,17 +844,21 @@ describe('BlogListPage: posts array and featuredPost object', () => {
710
844
  beforeAll(async () => {
711
845
  source = readFileSync(join(PAGES_DIR, 'BlogListPage.astro'), 'utf-8')
712
846
  section = await analyzeAstroSection(
713
- source, '_page_blog', 'blogPage', 'src/pages/blog/index.astro', { mode: 'page' },
847
+ source,
848
+ '_page_blog',
849
+ 'blogPage',
850
+ 'src/pages/blog/index.astro',
851
+ { mode: 'page' },
714
852
  )
715
853
  })
716
854
 
717
855
  it('should detect the posts array', () => {
718
- const keys = section.fields.map(f => f.key)
856
+ const keys = section.fields.map((f) => f.key)
719
857
  expect(keys).toContain('posts')
720
858
  })
721
859
 
722
860
  it('should detect posts as an array-of-objects with expected sub-fields', () => {
723
- const postsField = section.fields.find(f => f.key === 'posts')
861
+ const postsField = section.fields.find((f) => f.key === 'posts')
724
862
  expect(postsField?.type).toBe('array')
725
863
  const items = postsField?.defaultValue as Array<Record<string, unknown>> | undefined
726
864
  expect(items?.[0]).toHaveProperty('title')
@@ -732,9 +870,11 @@ describe('BlogListPage: posts array and featuredPost object', () => {
732
870
  })
733
871
 
734
872
  it('should detect featuredPost or its scalar fields', () => {
735
- const keys = section.fields.map(f => f.key)
873
+ const keys = section.fields.map((f) => f.key)
736
874
  // Either as a single object field or its sub-fields are detected
737
- const hasFeatured = keys.some(k => k.toLowerCase().includes('featured') || k.toLowerCase().includes('post'))
875
+ const hasFeatured = keys.some(
876
+ (k) => k.toLowerCase().includes('featured') || k.toLowerCase().includes('post'),
877
+ )
738
878
  expect(hasFeatured).toBe(true)
739
879
  })
740
880
  })
@@ -750,22 +890,26 @@ describe('PricingPage: plans with nested features[] and table', () => {
750
890
  beforeAll(async () => {
751
891
  source = readFileSync(join(PAGES_DIR, 'PricingPage.astro'), 'utf-8')
752
892
  section = await analyzeAstroSection(
753
- source, '_page_pricing', 'pricingPage', 'src/pages/pricing.astro', { mode: 'page' },
893
+ source,
894
+ '_page_pricing',
895
+ 'pricingPage',
896
+ 'src/pages/pricing.astro',
897
+ { mode: 'page' },
754
898
  )
755
899
  })
756
900
 
757
901
  it('should detect the plans array', () => {
758
- const keys = section.fields.map(f => f.key)
902
+ const keys = section.fields.map((f) => f.key)
759
903
  expect(keys).toContain('plans')
760
904
  })
761
905
 
762
906
  it('should not contain [object Object] in plans or nested features', () => {
763
- const plansField = section.fields.find(f => f.key === 'plans')
907
+ const plansField = section.fields.find((f) => f.key === 'plans')
764
908
  expect(JSON.stringify(plansField)).not.toContain('[object Object]')
765
909
  })
766
910
 
767
911
  it('should detect faqItems array', () => {
768
- const keys = section.fields.map(f => f.key)
912
+ const keys = section.fields.map((f) => f.key)
769
913
  expect(keys).toContain('faqItems')
770
914
  })
771
915
 
@@ -785,18 +929,22 @@ describe('LandingIndexPage: inline maps and steps array', () => {
785
929
  beforeAll(async () => {
786
930
  source = readFileSync(join(PAGES_DIR, 'LandingIndexPage.astro'), 'utf-8')
787
931
  section = await analyzeAstroSection(
788
- source, '_page_index', 'indexPage', 'src/pages/index.astro', { mode: 'page' },
932
+ source,
933
+ '_page_index',
934
+ 'indexPage',
935
+ 'src/pages/index.astro',
936
+ { mode: 'page' },
789
937
  )
790
938
  })
791
939
 
792
940
  it('should detect frontmatter scalar vars (headline, ctaText)', () => {
793
- const keys = section.fields.map(f => f.key)
794
- const hasHero = keys.some(k => k === 'headline' || k === 'ctaText' || k === 'subheadline')
941
+ const keys = section.fields.map((f) => f.key)
942
+ const hasHero = keys.some((k) => k === 'headline' || k === 'ctaText' || k === 'subheadline')
795
943
  expect(hasHero).toBe(true)
796
944
  })
797
945
 
798
946
  it('should detect the steps array', () => {
799
- const keys = section.fields.map(f => f.key)
947
+ const keys = section.fields.map((f) => f.key)
800
948
  expect(keys).toContain('steps')
801
949
  })
802
950
 
@@ -816,23 +964,27 @@ describe('AboutPage: team, milestones, inline values', () => {
816
964
  beforeAll(async () => {
817
965
  source = readFileSync(join(PAGES_DIR, 'AboutPage.astro'), 'utf-8')
818
966
  section = await analyzeAstroSection(
819
- source, '_page_about', 'aboutPage', 'src/pages/about.astro', { mode: 'page' },
967
+ source,
968
+ '_page_about',
969
+ 'aboutPage',
970
+ 'src/pages/about.astro',
971
+ { mode: 'page' },
820
972
  )
821
973
  })
822
974
 
823
975
  it('should detect the team array', () => {
824
- const keys = section.fields.map(f => f.key)
976
+ const keys = section.fields.map((f) => f.key)
825
977
  expect(keys).toContain('team')
826
978
  })
827
979
 
828
980
  it('should detect team members as objects with name/role/bio', () => {
829
- const teamField = section.fields.find(f => f.key === 'team')
981
+ const teamField = section.fields.find((f) => f.key === 'team')
830
982
  const items = teamField?.defaultValue as Array<Record<string, unknown>> | undefined
831
983
  expect(items?.[0]).toHaveProperty('name')
832
984
  })
833
985
 
834
986
  it('should detect the milestones array', () => {
835
- const keys = section.fields.map(f => f.key)
987
+ const keys = section.fields.map((f) => f.key)
836
988
  expect(keys).toContain('milestones')
837
989
  })
838
990
 
@@ -854,9 +1006,9 @@ function hasStructuralObjectString(value: unknown): boolean {
854
1006
  // A string that IS exactly [object Object] or a comma-joined list of them is structural
855
1007
  return /^\[object Object\](,\[object Object\])*$/.test(value.trim())
856
1008
  }
857
- if (Array.isArray(value)) return value.some(item => hasStructuralObjectString(item))
1009
+ if (Array.isArray(value)) return value.some((item) => hasStructuralObjectString(item))
858
1010
  if (value !== null && typeof value === 'object') {
859
- return Object.values(value as object).some(v => hasStructuralObjectString(v))
1011
+ return Object.values(value as object).some((v) => hasStructuralObjectString(v))
860
1012
  }
861
1013
  return false
862
1014
  }
@@ -892,17 +1044,20 @@ describe('Docs architecture page: table row detection', () => {
892
1044
  beforeAll(async () => {
893
1045
  source = readFileSync(join(PAGES_DIR, 'DocsArchitecturePage.astro'), 'utf-8')
894
1046
  section = await analyzeAstroSection(
895
- source, '_page_docs_architecture', 'architecturePage',
896
- 'src/pages/docs/architecture.astro', { mode: 'page' },
1047
+ source,
1048
+ '_page_docs_architecture',
1049
+ 'architecturePage',
1050
+ 'src/pages/docs/architecture.astro',
1051
+ { mode: 'page' },
897
1052
  )
898
1053
  })
899
1054
 
900
1055
  it('should detect the table as an array field', () => {
901
- const arrayFields = section.fields.filter(f => f.type === 'array')
1056
+ const arrayFields = section.fields.filter((f) => f.type === 'array')
902
1057
  expect(arrayFields.length).toBeGreaterThan(0)
903
1058
  // Should find an array with 3 entries (the 3 tbody rows)
904
- const tableField = arrayFields.find(f =>
905
- Array.isArray(f.defaultValue) && f.defaultValue.length === 3,
1059
+ const tableField = arrayFields.find(
1060
+ (f) => Array.isArray(f.defaultValue) && f.defaultValue.length === 3,
906
1061
  )
907
1062
  expect(tableField).toBeDefined()
908
1063
  })
@@ -924,14 +1079,14 @@ describe('Docs architecture page: table row detection', () => {
924
1079
 
925
1080
  it('should label the table field "Tabellen-Zeilen"', () => {
926
1081
  const tableField = section.fields.find(
927
- f => f.type === 'array' && Array.isArray(f.defaultValue) && f.defaultValue.length === 3,
1082
+ (f) => f.type === 'array' && Array.isArray(f.defaultValue) && f.defaultValue.length === 3,
928
1083
  )
929
1084
  expect(tableField?.label).toBe('Tabellen-Zeilen')
930
1085
  })
931
1086
 
932
1087
  it('should include td text in defaultValue', () => {
933
1088
  const tableField = section.fields.find(
934
- f => f.type === 'array' && Array.isArray(f.defaultValue) && f.defaultValue.length === 3,
1089
+ (f) => f.type === 'array' && Array.isArray(f.defaultValue) && f.defaultValue.length === 3,
935
1090
  )
936
1091
  const rows = tableField?.defaultValue as Array<Record<string, unknown>>
937
1092
  // First row should contain "ContentRepository"
@@ -940,7 +1095,7 @@ describe('Docs architecture page: table row detection', () => {
940
1095
  })
941
1096
 
942
1097
  it('should also detect headings outside the table', () => {
943
- const headings = section.fields.filter(f => f.key.startsWith('heading'))
1098
+ const headings = section.fields.filter((f) => f.key.startsWith('heading'))
944
1099
  expect(headings.length).toBeGreaterThan(0)
945
1100
  })
946
1101
  })
@@ -959,30 +1114,37 @@ describe('Docs installation page: CodeBlock import handling', () => {
959
1114
  })
960
1115
 
961
1116
  it('should NOT detect CodeBlock as a section import', () => {
962
- const imports = extractSectionImports(
963
- source, 'src/pages/docs/installation.astro', [], '',
964
- )
965
- const codeBlockImport = imports.find(i => i.componentName === 'CodeBlock')
1117
+ const imports = extractSectionImports(source, 'src/pages/docs/installation.astro', [], '')
1118
+ const codeBlockImport = imports.find((i) => i.componentName === 'CodeBlock')
966
1119
  expect(codeBlockImport).toBeUndefined()
967
1120
  })
968
1121
 
969
1122
  it('should detect page-level headings on installation page', async () => {
970
1123
  const section = await analyzeAstroSection(
971
- source, '_page_docs_installation', 'installationPage',
972
- 'src/pages/docs/installation.astro', { mode: 'page' },
1124
+ source,
1125
+ '_page_docs_installation',
1126
+ 'installationPage',
1127
+ 'src/pages/docs/installation.astro',
1128
+ { mode: 'page' },
973
1129
  )
974
- const headings = section.fields.filter(f => f.key.startsWith('heading'))
1130
+ const headings = section.fields.filter((f) => f.key.startsWith('heading'))
975
1131
  expect(headings.length).toBeGreaterThan(0)
976
1132
  })
977
1133
 
978
1134
  it('should detect repeated CodeBlock components as array in page-level scan', async () => {
979
1135
  const section = await analyzeAstroSection(
980
- source, '_page_docs_installation', 'installationPage',
981
- 'src/pages/docs/installation.astro', { mode: 'page' },
1136
+ source,
1137
+ '_page_docs_installation',
1138
+ 'installationPage',
1139
+ 'src/pages/docs/installation.astro',
1140
+ { mode: 'page' },
982
1141
  )
983
1142
  // 4 CodeBlock components → detected as a "codeBlocks" array
984
1143
  const codeBlockField = section.fields.find(
985
- f => f.type === 'array' && typeof f.key === 'string' && f.key.toLowerCase().includes('codeblock'),
1144
+ (f) =>
1145
+ f.type === 'array' &&
1146
+ typeof f.key === 'string' &&
1147
+ f.key.toLowerCase().includes('codeblock'),
986
1148
  )
987
1149
  expect(codeBlockField).toBeDefined()
988
1150
  expect(Array.isArray(codeBlockField?.defaultValue)).toBe(true)
@@ -1005,10 +1167,16 @@ describe('Nested map live-edit: inner items have data-sk-field', () => {
1005
1167
  beforeAll(async () => {
1006
1168
  source = readFileSync(join(PAGES_DIR, 'DocsIndexPage.astro'), 'utf-8')
1007
1169
  section = await analyzeAstroSection(
1008
- source, '_page_docs', 'docsPage', 'src/pages/docs/index.astro', { mode: 'page' },
1170
+ source,
1171
+ '_page_docs',
1172
+ 'docsPage',
1173
+ 'src/pages/docs/index.astro',
1174
+ { mode: 'page' },
1009
1175
  )
1010
1176
  const groups = (section as any)._analyzerResult?.repeatedGroups ?? []
1011
- patched = await patchTemplateForFields(source, '_page_docs', section.fields, groups, { mode: 'page' })
1177
+ patched = await patchTemplateForFields(source, '_page_docs', section.fields, groups, {
1178
+ mode: 'page',
1179
+ })
1012
1180
  })
1013
1181
 
1014
1182
  it('should add _j index to inner .map() call', () => {
@@ -1042,17 +1210,23 @@ describe('Page-level: template literal variables not extracted as fields', () =>
1042
1210
 
1043
1211
  beforeAll(async () => {
1044
1212
  const source = readFileSync(join(SECTIONS_DIR, 'PageWithTemplateLiterals.astro'), 'utf-8')
1045
- section = await analyzeAstroSection(source, '_page_docs_example', 'docsExamplePage', 'src/pages/docs/example.astro', { mode: 'page' })
1213
+ section = await analyzeAstroSection(
1214
+ source,
1215
+ '_page_docs_example',
1216
+ 'docsExamplePage',
1217
+ 'src/pages/docs/example.astro',
1218
+ { mode: 'page' },
1219
+ )
1046
1220
  })
1047
1221
 
1048
1222
  it('should detect the real content fields (heading, description)', () => {
1049
- const keys = section.fields.map(f => f.key)
1223
+ const keys = section.fields.map((f) => f.key)
1050
1224
  expect(keys).toContain('heading')
1051
1225
  expect(keys).toContain('description')
1052
1226
  })
1053
1227
 
1054
1228
  it('should NOT extract variables from inside template literal strings (templates, hero, json, template)', () => {
1055
- const keys = section.fields.map(f => f.key)
1229
+ const keys = section.fields.map((f) => f.key)
1056
1230
  expect(keys).not.toContain('templates')
1057
1231
  expect(keys).not.toContain('hero')
1058
1232
  expect(keys).not.toContain('json')
@@ -1060,14 +1234,14 @@ describe('Page-level: template literal variables not extracted as fields', () =>
1060
1234
  })
1061
1235
 
1062
1236
  it('should NOT extract the code variables themselves (exampleCode, cliCode, apiCode)', () => {
1063
- const keys = section.fields.map(f => f.key)
1237
+ const keys = section.fields.map((f) => f.key)
1064
1238
  expect(keys).not.toContain('exampleCode')
1065
1239
  expect(keys).not.toContain('cliCode')
1066
1240
  expect(keys).not.toContain('apiCode')
1067
1241
  })
1068
1242
 
1069
1243
  it('should find only legitimate fields (no spurious template-literal variables)', () => {
1070
- const keys = section.fields.map(f => f.key)
1244
+ const keys = section.fields.map((f) => f.key)
1071
1245
  // Only real content fields should be present — no code variable names
1072
1246
  const spurious = ['templates', 'hero', 'json', 'template', 'exampleCode', 'cliCode', 'apiCode']
1073
1247
  for (const key of spurious) {