@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
|
@@ -38,7 +38,11 @@ const normalizeWs = (s: string) => s.replace(/\s+/g, ' ').trim()
|
|
|
38
38
|
* Find text in a string where the needle may have collapsed whitespace
|
|
39
39
|
* but the haystack preserves original whitespace (incl. newlines).
|
|
40
40
|
*/
|
|
41
|
-
function findNormalizedText(
|
|
41
|
+
function findNormalizedText(
|
|
42
|
+
haystack: string,
|
|
43
|
+
needle: string,
|
|
44
|
+
startFrom: number,
|
|
45
|
+
): { start: number; end: number } | null {
|
|
42
46
|
// Try exact match first
|
|
43
47
|
const exactIdx = haystack.indexOf(needle, startFrom)
|
|
44
48
|
if (exactIdx !== -1) return { start: exactIdx, end: exactIdx + needle.length }
|
|
@@ -49,7 +53,8 @@ function findNormalizedText(haystack: string, needle: string, startFrom: number)
|
|
|
49
53
|
const escaped = normalized.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '\\s+')
|
|
50
54
|
const regex = new RegExp(escaped)
|
|
51
55
|
const match = regex.exec(haystack.slice(startFrom))
|
|
52
|
-
if (match)
|
|
56
|
+
if (match)
|
|
57
|
+
return { start: startFrom + match.index, end: startFrom + match.index + match[0].length }
|
|
53
58
|
|
|
54
59
|
return null
|
|
55
60
|
}
|
|
@@ -128,7 +133,10 @@ export async function patchTemplateForFields(
|
|
|
128
133
|
// Adjust positions in-place on the original groups so mutations
|
|
129
134
|
// (like _class fields from makeDynamicClasses) are visible to the caller.
|
|
130
135
|
for (const g of repeatedGroups) {
|
|
131
|
-
for (const inst of g.instances) {
|
|
136
|
+
for (const inst of g.instances) {
|
|
137
|
+
inst.start += shift
|
|
138
|
+
inst.end += shift
|
|
139
|
+
}
|
|
132
140
|
for (const f of g.fields) {
|
|
133
141
|
for (let i = 0; i < f.positions.length; i++) {
|
|
134
142
|
const p = f.positions[i]
|
|
@@ -143,7 +151,13 @@ export async function patchTemplateForFields(
|
|
|
143
151
|
}
|
|
144
152
|
|
|
145
153
|
// Patch expressions first (old vars still present → patcher detects them)
|
|
146
|
-
const patched = await patchTemplateForFields(
|
|
154
|
+
const patched = await patchTemplateForFields(
|
|
155
|
+
modifiedSource,
|
|
156
|
+
sectionKey,
|
|
157
|
+
fields,
|
|
158
|
+
repeatedGroups,
|
|
159
|
+
options,
|
|
160
|
+
)
|
|
147
161
|
|
|
148
162
|
// Now remove old variable declarations from the patched result
|
|
149
163
|
return removeOldVarDeclarations(patched, fields, repeatedGroups, varName)
|
|
@@ -183,8 +197,8 @@ export async function patchTemplateForFields(
|
|
|
183
197
|
// Collect elements with mixed content (text + inline child elements)
|
|
184
198
|
// These are rich text fields where the whole element is one CMS field.
|
|
185
199
|
if (isElement(node) && node.children && node.children.length > 1) {
|
|
186
|
-
const hasText = node.children.some(c => c.type === 'text' && c.value?.trim())
|
|
187
|
-
const hasChild = node.children.some(c => isElement(c) || c.type === 'expression')
|
|
200
|
+
const hasText = node.children.some((c) => c.type === 'text' && c.value?.trim())
|
|
201
|
+
const hasChild = node.children.some((c) => isElement(c) || c.type === 'expression')
|
|
188
202
|
if (hasText && hasChild) {
|
|
189
203
|
const fullText = normalizeWs(getElementTextContent(node))
|
|
190
204
|
if (fullText) mixedElements.push({ node, normalizedText: fullText })
|
|
@@ -249,13 +263,18 @@ export async function patchTemplateForFields(
|
|
|
249
263
|
// built the defaultValue array before patching. Without this sync, the
|
|
250
264
|
// content JSON (built from field.defaultValue) would be missing _classN values.
|
|
251
265
|
for (const g of repeatedGroups) {
|
|
252
|
-
const topField = fields.find(f => f.key === g.fieldKey)
|
|
266
|
+
const topField = fields.find((f) => f.key === g.fieldKey)
|
|
253
267
|
if (!topField || !Array.isArray(topField.defaultValue)) continue
|
|
254
268
|
const items = topField.defaultValue as Array<Record<string, unknown>>
|
|
255
269
|
for (const innerField of g.fields) {
|
|
256
270
|
for (let ii = 0; ii < items.length && ii < innerField.defaultValues.length; ii++) {
|
|
257
271
|
const val = innerField.defaultValues[ii]
|
|
258
|
-
if (
|
|
272
|
+
if (
|
|
273
|
+
val != null &&
|
|
274
|
+
items[ii] &&
|
|
275
|
+
typeof items[ii] === 'object' &&
|
|
276
|
+
!(innerField.key in items[ii]!)
|
|
277
|
+
) {
|
|
259
278
|
items[ii]![innerField.key] = val
|
|
260
279
|
}
|
|
261
280
|
}
|
|
@@ -332,7 +351,7 @@ export async function patchTemplateForFields(
|
|
|
332
351
|
// Treat the whole element as a rich text field with set:html for CMS rendering.
|
|
333
352
|
if (edits.length === editsBefore) {
|
|
334
353
|
const dv = field.defaultValue as string
|
|
335
|
-
const mixedMatch = mixedElements.find(m => m.normalizedText === normalizeWs(dv))
|
|
354
|
+
const mixedMatch = mixedElements.find((m) => m.normalizedText === normalizeWs(dv))
|
|
336
355
|
if (mixedMatch) {
|
|
337
356
|
patchMixedContentField(source, sectionKey, field, varName, mixedMatch.node, edits)
|
|
338
357
|
}
|
|
@@ -372,7 +391,10 @@ export function convertToSetHtml(source: string): string {
|
|
|
372
391
|
const ch = result[i]!
|
|
373
392
|
if (ch === '{') braceDepth++
|
|
374
393
|
else if (ch === '}') braceDepth--
|
|
375
|
-
else if (ch === '>' && braceDepth === 0) {
|
|
394
|
+
else if (ch === '>' && braceDepth === 0) {
|
|
395
|
+
tagEnd = i
|
|
396
|
+
break
|
|
397
|
+
}
|
|
376
398
|
}
|
|
377
399
|
if (tagEnd === -1) continue
|
|
378
400
|
|
|
@@ -474,7 +496,10 @@ function convertAstPositions(node: AstNode, b2c: (offset: number) => number): vo
|
|
|
474
496
|
* AST expression positions can extend beyond the actual braces, so we search
|
|
475
497
|
* the source directly using the child text content as anchor.
|
|
476
498
|
*/
|
|
477
|
-
function findExpressionBounds(
|
|
499
|
+
function findExpressionBounds(
|
|
500
|
+
source: string,
|
|
501
|
+
node: AstNode,
|
|
502
|
+
): { start: number; end: number } | null {
|
|
478
503
|
const approxStart = node.position?.start?.offset
|
|
479
504
|
if (approxStart == null) return null
|
|
480
505
|
|
|
@@ -510,7 +535,10 @@ function findExpressionBounds(source: string, node: AstNode): { start: number; e
|
|
|
510
535
|
if (ch === inStr && source[i - 1] !== '\\') inStr = null
|
|
511
536
|
continue
|
|
512
537
|
}
|
|
513
|
-
if (ch === "'" || ch === '"' || ch === '`') {
|
|
538
|
+
if (ch === "'" || ch === '"' || ch === '`') {
|
|
539
|
+
inStr = ch
|
|
540
|
+
continue
|
|
541
|
+
}
|
|
514
542
|
if (ch === '{') depth++
|
|
515
543
|
if (ch === '}') {
|
|
516
544
|
depth--
|
|
@@ -560,7 +588,10 @@ function findTextBounds(source: string, node: AstNode): { start: number; end: nu
|
|
|
560
588
|
function getElementTextContent(node: AstNode): string {
|
|
561
589
|
if (node.type === 'text') return node.value ?? ''
|
|
562
590
|
if (node.type === 'expression') {
|
|
563
|
-
return (node.children ?? [])
|
|
591
|
+
return (node.children ?? [])
|
|
592
|
+
.filter((c) => c.type === 'text')
|
|
593
|
+
.map((c) => c.value ?? '')
|
|
594
|
+
.join('')
|
|
564
595
|
}
|
|
565
596
|
return (node.children ?? []).map(getElementTextContent).join('')
|
|
566
597
|
}
|
|
@@ -599,7 +630,7 @@ function patchMixedContentField(
|
|
|
599
630
|
const bindingKey = `${sectionKey}.${field.key}`
|
|
600
631
|
|
|
601
632
|
// Skip if already has data-sk-field
|
|
602
|
-
if (element.attributes?.some(a => a.name === 'data-sk-field')) return
|
|
633
|
+
if (element.attributes?.some((a) => a.name === 'data-sk-field')) return
|
|
603
634
|
|
|
604
635
|
// Find the element's opening tag in source
|
|
605
636
|
const startOffset = element.position?.start?.offset
|
|
@@ -687,14 +718,15 @@ function patchIconOrImageField(
|
|
|
687
718
|
if (targetParent) return // already found
|
|
688
719
|
if (!parent || !isElement(parent)) return
|
|
689
720
|
// Skip if parent already has data-sk-field
|
|
690
|
-
if (parent.attributes?.some(a => a.name === 'data-sk-field')) return
|
|
721
|
+
if (parent.attributes?.some((a) => a.name === 'data-sk-field')) return
|
|
691
722
|
|
|
692
723
|
if (field.type === 'icon') {
|
|
693
724
|
// Match component named *Icon* or element with icon/name attr matching default
|
|
694
725
|
const isIconComp = isElement(node) && /icon/i.test(node.name ?? '')
|
|
695
726
|
const hasIconAttr = node.attributes?.some(
|
|
696
|
-
|
|
697
|
-
|
|
727
|
+
(a) =>
|
|
728
|
+
(a.name === 'icon' || a.name === 'name' || a.name === 'data-icon') &&
|
|
729
|
+
(a.value === defaultStr || a.value.includes(field.key)),
|
|
698
730
|
)
|
|
699
731
|
if (isIconComp || hasIconAttr) {
|
|
700
732
|
targetParent = parent
|
|
@@ -704,7 +736,7 @@ function patchIconOrImageField(
|
|
|
704
736
|
const tag = node.name ?? ''
|
|
705
737
|
const isImgEl = /^(img|Image|picture)$/.test(tag)
|
|
706
738
|
const hasSrcAttr = node.attributes?.some(
|
|
707
|
-
a => a.name === 'src' && (a.value === defaultStr || a.value.includes(field.key))
|
|
739
|
+
(a) => a.name === 'src' && (a.value === defaultStr || a.value.includes(field.key)),
|
|
708
740
|
)
|
|
709
741
|
if (isImgEl || hasSrcAttr) {
|
|
710
742
|
targetParent = parent
|
|
@@ -910,15 +942,20 @@ function patchArrayField(
|
|
|
910
942
|
// Match by looking for the field's first item value inside each expression's source.
|
|
911
943
|
let mapExpr = mapExpressions[0]!
|
|
912
944
|
|
|
913
|
-
if (
|
|
945
|
+
if (
|
|
946
|
+
mapExpressions.length > 1 &&
|
|
947
|
+
Array.isArray(field.defaultValue) &&
|
|
948
|
+
field.defaultValue.length > 0
|
|
949
|
+
) {
|
|
914
950
|
const firstItem = field.defaultValue[0]
|
|
915
|
-
const searchStr =
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
951
|
+
const searchStr =
|
|
952
|
+
typeof firstItem === 'string'
|
|
953
|
+
? firstItem.slice(0, 30)
|
|
954
|
+
: typeof firstItem === 'object' && firstItem !== null
|
|
955
|
+
? (Object.values(firstItem as Record<string, unknown>)
|
|
956
|
+
.find((v): v is string => typeof v === 'string' && v.length >= 3)
|
|
957
|
+
?.slice(0, 30) ?? '')
|
|
958
|
+
: ''
|
|
922
959
|
|
|
923
960
|
if (searchStr.length >= 3) {
|
|
924
961
|
for (const expr of mapExpressions) {
|
|
@@ -983,7 +1020,7 @@ function patchArrayField(
|
|
|
983
1020
|
})
|
|
984
1021
|
} else {
|
|
985
1022
|
// Try variable reference: varName.map( → (cmsVar?.field ?? []).map(
|
|
986
|
-
const varMapRegex =
|
|
1023
|
+
const varMapRegex = /(\w+)\.map\s*\(/
|
|
987
1024
|
const varMapMatch = exprSource.match(varMapRegex)
|
|
988
1025
|
if (varMapMatch) {
|
|
989
1026
|
const varRef = varMapMatch[1]!
|
|
@@ -1042,15 +1079,19 @@ function patchStaticListField(
|
|
|
1042
1079
|
walkAst(node, (n) => {
|
|
1043
1080
|
if (hasMap) return
|
|
1044
1081
|
if (n.type === 'expression') {
|
|
1045
|
-
const code = (n.children ?? []).map(c => c.value ?? '').join('')
|
|
1082
|
+
const code = (n.children ?? []).map((c) => c.value ?? '').join('')
|
|
1046
1083
|
if (/\.map\s*\(/.test(code)) hasMap = true
|
|
1047
1084
|
}
|
|
1048
1085
|
})
|
|
1049
1086
|
if (hasMap) return
|
|
1050
1087
|
// Compare <li> text content against field items.
|
|
1051
1088
|
// items[i] may contain HTML (if formatting: true) — strip tags for comparison.
|
|
1052
|
-
const stripHtml = (s: string) =>
|
|
1053
|
-
|
|
1089
|
+
const stripHtml = (s: string) =>
|
|
1090
|
+
s
|
|
1091
|
+
.replace(/<[^>]+>/g, '')
|
|
1092
|
+
.replace(/\s+/g, ' ')
|
|
1093
|
+
.trim()
|
|
1094
|
+
const liChildren = (node.children ?? []).filter((c) => c.type === 'element' && c.name === 'li')
|
|
1054
1095
|
if (liChildren.length !== items.length) return
|
|
1055
1096
|
const allMatch = liChildren.every((li, i) => {
|
|
1056
1097
|
const text = getElementTextContent(li).replace(/\s+/g, ' ').trim()
|
|
@@ -1063,7 +1104,7 @@ function patchStaticListField(
|
|
|
1063
1104
|
|
|
1064
1105
|
const ulNode = matchedUl as AstNode
|
|
1065
1106
|
const liChildren = (ulNode.children ?? []).filter(
|
|
1066
|
-
c => c.type === 'element' && c.name === 'li',
|
|
1107
|
+
(c) => c.type === 'element' && c.name === 'li',
|
|
1067
1108
|
) as AstNode[]
|
|
1068
1109
|
if (liChildren.length === 0) return
|
|
1069
1110
|
|
|
@@ -1101,15 +1142,16 @@ function patchStaticListField(
|
|
|
1101
1142
|
|
|
1102
1143
|
// Build the fallback array literal.
|
|
1103
1144
|
// HTML items (formatting: true) are wrapped in backtick strings to avoid escaping issues.
|
|
1104
|
-
const hasHtmlItems = items.some(s => /<[a-z]/.test(s))
|
|
1145
|
+
const hasHtmlItems = items.some((s) => /<[a-z]/.test(s))
|
|
1105
1146
|
const fallbackItems = hasHtmlItems
|
|
1106
|
-
? items.map(s => `\`${s.replace(/`/g, '\\`').replace(/\$/g, '\\$')}\``).join(', ')
|
|
1107
|
-
: items.map(s => `'${s.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`).join(', ')
|
|
1147
|
+
? items.map((s) => `\`${s.replace(/`/g, '\\`').replace(/\$/g, '\\$')}\``).join(', ')
|
|
1148
|
+
: items.map((s) => `'${s.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`).join(', ')
|
|
1108
1149
|
|
|
1109
1150
|
// Determine indentation of the first <li> for formatting
|
|
1110
1151
|
const textBeforeFirstLi = source.slice(0, rangeStart)
|
|
1111
1152
|
const lastNewline = textBeforeFirstLi.lastIndexOf('\n')
|
|
1112
|
-
const indent =
|
|
1153
|
+
const indent =
|
|
1154
|
+
lastNewline >= 0 ? (textBeforeFirstLi.slice(lastNewline + 1).match(/^[ \t]*/)?.[0] ?? '') : ''
|
|
1113
1155
|
|
|
1114
1156
|
const replacement =
|
|
1115
1157
|
`{(${varName}?.${field.key} ?? [${fallbackItems}]).map((item, _i) => (\n` +
|
|
@@ -1351,7 +1393,7 @@ function collectDynamicClassEdits(
|
|
|
1351
1393
|
function sharedClassCount(instValue: string): number {
|
|
1352
1394
|
const instParts = new Set(instValue.split(/\s+/).filter(Boolean))
|
|
1353
1395
|
if (tmplParts.size === 0 && instParts.size === 0) return 1 // both empty = match
|
|
1354
|
-
return [...tmplParts].filter(p => instParts.has(p)).length
|
|
1396
|
+
return [...tmplParts].filter((p) => instParts.has(p)).length
|
|
1355
1397
|
}
|
|
1356
1398
|
|
|
1357
1399
|
// 1. Exact path — only accept if at least 1 CSS class is shared
|
|
@@ -1365,7 +1407,11 @@ function collectDynamicClassEdits(
|
|
|
1365
1407
|
}
|
|
1366
1408
|
|
|
1367
1409
|
// 2. Same tag-path (without indices) — pick the one with most shared classes
|
|
1368
|
-
const stripIdx = (p: string) =>
|
|
1410
|
+
const stripIdx = (p: string) =>
|
|
1411
|
+
p
|
|
1412
|
+
.split('/')
|
|
1413
|
+
.map((s) => s.replace(/:\d+$/, ''))
|
|
1414
|
+
.join('/')
|
|
1369
1415
|
const tmplTagPath = stripIdx(tmplAttr.path)
|
|
1370
1416
|
let bestIdx = -1
|
|
1371
1417
|
let bestShared = 0
|
|
@@ -1373,7 +1419,10 @@ function collectDynamicClassEdits(
|
|
|
1373
1419
|
if (claimed.has(i)) continue
|
|
1374
1420
|
if (stripIdx(instAttrs[i]!.path) !== tmplTagPath) continue
|
|
1375
1421
|
const shared = sharedClassCount(instAttrs[i]!.value)
|
|
1376
|
-
if (shared > bestShared) {
|
|
1422
|
+
if (shared > bestShared) {
|
|
1423
|
+
bestShared = shared
|
|
1424
|
+
bestIdx = i
|
|
1425
|
+
}
|
|
1377
1426
|
}
|
|
1378
1427
|
if (bestIdx >= 0 && bestShared > 0) {
|
|
1379
1428
|
claimed.add(bestIdx)
|
|
@@ -1403,7 +1452,7 @@ function collectDynamicClassEdits(
|
|
|
1403
1452
|
// adjustments can put the class attr offset slightly after the text offset.
|
|
1404
1453
|
const fieldTag = field.tag
|
|
1405
1454
|
let bestAttrIdx = -1
|
|
1406
|
-
let bestDist =
|
|
1455
|
+
let bestDist = Number.POSITIVE_INFINITY
|
|
1407
1456
|
for (let ai = 0; ai < tmplAttrs.length; ai++) {
|
|
1408
1457
|
// Extract leaf tag from path: "div:0/div:0/span:1" → "span"
|
|
1409
1458
|
const pathParts = tmplAttrs[ai]!.path.split('/')
|
|
@@ -1555,7 +1604,7 @@ function patchRepeatedComponentInstances(
|
|
|
1555
1604
|
// data-sk-field on component calls is not forwarded to the DOM by Astro.
|
|
1556
1605
|
propLines.push(`fieldPrefix={\`${sectionKey}.${field.key}.\${_i}\`}`)
|
|
1557
1606
|
|
|
1558
|
-
const propsStr = propLines.map(p => ` ${p}`).join('\n')
|
|
1607
|
+
const propsStr = propLines.map((p) => ` ${p}`).join('\n')
|
|
1559
1608
|
const mapExpr =
|
|
1560
1609
|
`{(${varName}?.${field.key} ?? ${defaultJson}).map((item, _i) => (\n` +
|
|
1561
1610
|
` <${compName}\n${propsStr}\n />\n` +
|
|
@@ -1592,14 +1641,30 @@ function findComponentTagEnd(source: string, tagStart: number, compName: string)
|
|
|
1592
1641
|
const ch = source[i]!
|
|
1593
1642
|
if (inQuote) {
|
|
1594
1643
|
if (ch === inQuote && source[i - 1] !== '\\') inQuote = null
|
|
1595
|
-
i
|
|
1644
|
+
i++
|
|
1645
|
+
continue
|
|
1646
|
+
}
|
|
1647
|
+
if (ch === '{') {
|
|
1648
|
+
inExpr++
|
|
1649
|
+
i++
|
|
1650
|
+
continue
|
|
1651
|
+
}
|
|
1652
|
+
if (ch === '}' && inExpr > 0) {
|
|
1653
|
+
inExpr--
|
|
1654
|
+
i++
|
|
1655
|
+
continue
|
|
1656
|
+
}
|
|
1657
|
+
if (inExpr > 0) {
|
|
1658
|
+
i++
|
|
1659
|
+
continue
|
|
1660
|
+
}
|
|
1661
|
+
if (ch === '"' || ch === "'") {
|
|
1662
|
+
inQuote = ch
|
|
1663
|
+
i++
|
|
1664
|
+
continue
|
|
1596
1665
|
}
|
|
1597
|
-
if (ch === '{') { inExpr++; i++; continue }
|
|
1598
|
-
if (ch === '}' && inExpr > 0) { inExpr--; i++; continue }
|
|
1599
|
-
if (inExpr > 0) { i++; continue }
|
|
1600
|
-
if (ch === '"' || ch === "'") { inQuote = ch; i++; continue }
|
|
1601
1666
|
if (ch === '/' && source[i + 1] === '>') return i + 2 // self-closing />
|
|
1602
|
-
if (ch === '>'
|
|
1667
|
+
if (ch === '>') {
|
|
1603
1668
|
// Check for explicit closing tag </CompName>
|
|
1604
1669
|
const closing = `</${compName}>`
|
|
1605
1670
|
const closingIdx = source.indexOf(closing, i + 1)
|
|
@@ -1738,7 +1803,8 @@ function patchRepeatedGroups(
|
|
|
1738
1803
|
if (typeof defaultVal === 'string' && defaultVal.length >= 1) {
|
|
1739
1804
|
const found = findNormalizedText(elementSrc, defaultVal, 0)
|
|
1740
1805
|
if (found) {
|
|
1741
|
-
elementSrc =
|
|
1806
|
+
elementSrc =
|
|
1807
|
+
elementSrc.slice(0, found.start) + `{item.${field.key}}` + elementSrc.slice(found.end)
|
|
1742
1808
|
}
|
|
1743
1809
|
}
|
|
1744
1810
|
|
|
@@ -1759,7 +1825,10 @@ function patchRepeatedGroups(
|
|
|
1759
1825
|
// Apply inner edits in reverse order
|
|
1760
1826
|
innerEdits.sort((a, b) => b.offset - a.offset)
|
|
1761
1827
|
for (const edit of innerEdits) {
|
|
1762
|
-
templateSrc =
|
|
1828
|
+
templateSrc =
|
|
1829
|
+
templateSrc.slice(0, edit.offset) +
|
|
1830
|
+
edit.insert +
|
|
1831
|
+
templateSrc.slice(edit.offset + edit.deleteCount)
|
|
1763
1832
|
}
|
|
1764
1833
|
|
|
1765
1834
|
// Add data-sk-field to the template element's opening tag (container binding)
|
|
@@ -1788,9 +1857,13 @@ function patchRepeatedGroups(
|
|
|
1788
1857
|
const inst = instances[i]!
|
|
1789
1858
|
// Also remove surrounding whitespace/newlines
|
|
1790
1859
|
let deleteStart = inst.start
|
|
1791
|
-
|
|
1860
|
+
const deleteEnd = inst.end
|
|
1792
1861
|
// Extend backwards to eat preceding whitespace
|
|
1793
|
-
while (
|
|
1862
|
+
while (
|
|
1863
|
+
deleteStart > 0 &&
|
|
1864
|
+
/\s/.test(source[deleteStart - 1]!) &&
|
|
1865
|
+
source[deleteStart - 1] !== '\n'
|
|
1866
|
+
) {
|
|
1794
1867
|
deleteStart--
|
|
1795
1868
|
}
|
|
1796
1869
|
// Eat the preceding newline too
|
|
@@ -1837,11 +1910,23 @@ function addInnerFieldBindings(
|
|
|
1837
1910
|
let inE = 0
|
|
1838
1911
|
for (let i = tagStart; i < idx; i++) {
|
|
1839
1912
|
const ch = templateSrc[i]!
|
|
1840
|
-
if (inQ) {
|
|
1841
|
-
|
|
1842
|
-
|
|
1913
|
+
if (inQ) {
|
|
1914
|
+
if (ch === inQ && templateSrc[i - 1] !== '\\') inQ = null
|
|
1915
|
+
continue
|
|
1916
|
+
}
|
|
1917
|
+
if (ch === '{') {
|
|
1918
|
+
inE++
|
|
1919
|
+
continue
|
|
1920
|
+
}
|
|
1921
|
+
if (ch === '}' && inE > 0) {
|
|
1922
|
+
inE--
|
|
1923
|
+
continue
|
|
1924
|
+
}
|
|
1843
1925
|
if (inE > 0) continue
|
|
1844
|
-
if (ch === '"' || ch === "'") {
|
|
1926
|
+
if (ch === '"' || ch === "'") {
|
|
1927
|
+
inQ = ch
|
|
1928
|
+
continue
|
|
1929
|
+
}
|
|
1845
1930
|
if (ch === '>') tagEnd = i
|
|
1846
1931
|
}
|
|
1847
1932
|
if (tagEnd === -1) continue
|
|
@@ -1945,7 +2030,9 @@ function extractVarName(frontmatter: string): string | null {
|
|
|
1945
2030
|
}
|
|
1946
2031
|
|
|
1947
2032
|
/** Type guard for element-like nodes (element, component, custom-element). */
|
|
1948
|
-
function isElement(
|
|
2033
|
+
function isElement(
|
|
2034
|
+
node: AstNode,
|
|
2035
|
+
): node is AstNode & { attributes: AstAttr[]; children: AstNode[] } {
|
|
1949
2036
|
return (
|
|
1950
2037
|
(node.type === 'element' || node.type === 'component' || node.type === 'custom-element') &&
|
|
1951
2038
|
Array.isArray(node.attributes)
|
|
@@ -1953,7 +2040,11 @@ function isElement(node: AstNode): node is AstNode & { attributes: AstAttr[]; ch
|
|
|
1953
2040
|
}
|
|
1954
2041
|
|
|
1955
2042
|
/** Recursive AST walk with parent tracking. */
|
|
1956
|
-
function walkAst(
|
|
2043
|
+
function walkAst(
|
|
2044
|
+
node: AstNode,
|
|
2045
|
+
callback: (node: AstNode, parent: AstNode | null) => void,
|
|
2046
|
+
parent: AstNode | null = null,
|
|
2047
|
+
): void {
|
|
1957
2048
|
callback(node, parent)
|
|
1958
2049
|
if (node.children) {
|
|
1959
2050
|
for (const child of node.children) {
|
|
@@ -1978,7 +2069,12 @@ function findFirstChildElement(node: AstNode): (AstNode & { attributes: AstAttr[
|
|
|
1978
2069
|
* Inserts before the closing `>` of the opening tag.
|
|
1979
2070
|
* Uses the element's tag name to locate it reliably in the source.
|
|
1980
2071
|
*/
|
|
1981
|
-
function addAttributeToElement(
|
|
2072
|
+
function addAttributeToElement(
|
|
2073
|
+
source: string,
|
|
2074
|
+
element: AstNode,
|
|
2075
|
+
attrStr: string,
|
|
2076
|
+
edits: Edit[],
|
|
2077
|
+
): void {
|
|
1982
2078
|
const approxStart = element.position?.start?.offset
|
|
1983
2079
|
if (approxStart == null) return
|
|
1984
2080
|
|
|
@@ -2014,10 +2110,19 @@ function findOpeningTagEnd(source: string, startOffset: number): number {
|
|
|
2014
2110
|
if (ch === inQuote && source[i - 1] !== '\\') inQuote = null
|
|
2015
2111
|
continue
|
|
2016
2112
|
}
|
|
2017
|
-
if (ch === '{') {
|
|
2018
|
-
|
|
2113
|
+
if (ch === '{') {
|
|
2114
|
+
inExpr++
|
|
2115
|
+
continue
|
|
2116
|
+
}
|
|
2117
|
+
if (ch === '}' && inExpr > 0) {
|
|
2118
|
+
inExpr--
|
|
2119
|
+
continue
|
|
2120
|
+
}
|
|
2019
2121
|
if (inExpr > 0) continue
|
|
2020
|
-
if (ch === '"' || ch === "'") {
|
|
2122
|
+
if (ch === '"' || ch === "'") {
|
|
2123
|
+
inQuote = ch
|
|
2124
|
+
continue
|
|
2125
|
+
}
|
|
2021
2126
|
if (ch === '>') return i
|
|
2022
2127
|
}
|
|
2023
2128
|
return -1
|
|
@@ -2032,7 +2137,8 @@ function applyEdits(source: string, edits: Edit[]): string {
|
|
|
2032
2137
|
|
|
2033
2138
|
let result = source
|
|
2034
2139
|
for (const edit of sorted) {
|
|
2035
|
-
result =
|
|
2140
|
+
result =
|
|
2141
|
+
result.slice(0, edit.offset) + edit.insert + result.slice(edit.offset + edit.deleteCount)
|
|
2036
2142
|
}
|
|
2037
2143
|
return result
|
|
2038
2144
|
}
|
|
@@ -2042,7 +2148,12 @@ function applyEdits(source: string, edits: Edit[]): string {
|
|
|
2042
2148
|
* Called AFTER expression patching so that variable references have already
|
|
2043
2149
|
* been replaced with CMS expressions.
|
|
2044
2150
|
*/
|
|
2045
|
-
function removeOldVarDeclarations(
|
|
2151
|
+
function removeOldVarDeclarations(
|
|
2152
|
+
source: string,
|
|
2153
|
+
fields: PatchField[],
|
|
2154
|
+
repeatedGroups?: RepeatedGroup[],
|
|
2155
|
+
cmsVarName = 'skData',
|
|
2156
|
+
): string {
|
|
2046
2157
|
const fmStart = source.indexOf('---')
|
|
2047
2158
|
if (fmStart === -1) return source
|
|
2048
2159
|
const fmEnd = source.indexOf('---', fmStart + 3)
|
|
@@ -2113,7 +2224,10 @@ function removeOldVarDeclarations(source: string, fields: PatchField[], repeated
|
|
|
2113
2224
|
if (ch === inStr && rhs[i - 1] !== '\\') inStr = null
|
|
2114
2225
|
continue
|
|
2115
2226
|
}
|
|
2116
|
-
if (ch === "'" || ch === '"' || ch === '`') {
|
|
2227
|
+
if (ch === "'" || ch === '"' || ch === '`') {
|
|
2228
|
+
inStr = ch
|
|
2229
|
+
continue
|
|
2230
|
+
}
|
|
2117
2231
|
if (ch === open) depth++
|
|
2118
2232
|
if (ch === close) depth--
|
|
2119
2233
|
}
|
|
@@ -2193,7 +2307,8 @@ export function patchChildComponentForFieldPrefix(
|
|
|
2193
2307
|
innerFields: Array<{ key: string; type: string }>,
|
|
2194
2308
|
): string {
|
|
2195
2309
|
// Idempotency guard — already patched
|
|
2196
|
-
if (source.includes('fieldPrefix?: string') || source.includes(
|
|
2310
|
+
if (source.includes('fieldPrefix?: string') || source.includes('fieldPrefix?:string'))
|
|
2311
|
+
return source
|
|
2197
2312
|
|
|
2198
2313
|
const fmStart = source.indexOf('---')
|
|
2199
2314
|
const fmEnd = source.indexOf('---', fmStart + 3)
|
|
@@ -2207,36 +2322,33 @@ export function patchChildComponentForFieldPrefix(
|
|
|
2207
2322
|
if (interfaceMatch) {
|
|
2208
2323
|
const ifaceEnd = result.indexOf(interfaceMatch[0]) + interfaceMatch[0].length
|
|
2209
2324
|
const lastPropLine = interfaceMatch[0].slice(0, -1).trimEnd() // remove trailing `}`
|
|
2210
|
-
result =
|
|
2211
|
-
result.slice(0, ifaceEnd - 1) +
|
|
2212
|
-
'\n fieldPrefix?: string;\n}' +
|
|
2213
|
-
result.slice(ifaceEnd)
|
|
2325
|
+
result = result.slice(0, ifaceEnd - 1) + '\n fieldPrefix?: string;\n}' + result.slice(ifaceEnd)
|
|
2214
2326
|
} else {
|
|
2215
2327
|
// No interface — insert before closing ---
|
|
2216
2328
|
const closeFm = result.indexOf('---', result.indexOf('---') + 3)
|
|
2217
|
-
result =
|
|
2329
|
+
result =
|
|
2330
|
+
result.slice(0, closeFm) +
|
|
2331
|
+
'interface Props { fieldPrefix?: string }\n' +
|
|
2332
|
+
result.slice(closeFm)
|
|
2218
2333
|
}
|
|
2219
2334
|
|
|
2220
2335
|
// 2. Add fieldPrefix to destructuring
|
|
2221
2336
|
// Match: const { ..., highlight = false } = Astro.props
|
|
2222
|
-
result = result.replace(
|
|
2223
|
-
|
|
2224
|
-
(
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
return `${trimmed}${sep}fieldPrefix${after}`
|
|
2230
|
-
},
|
|
2231
|
-
)
|
|
2337
|
+
result = result.replace(/(\bconst\s*\{[^}]*)(}\s*=\s*Astro\.props)/, (_m, before, after) => {
|
|
2338
|
+
// Avoid adding twice
|
|
2339
|
+
if (before.includes('fieldPrefix')) return _m
|
|
2340
|
+
const trimmed = before.trimEnd()
|
|
2341
|
+
const sep = trimmed.endsWith(',') ? ' ' : ',\n '
|
|
2342
|
+
return `${trimmed}${sep}fieldPrefix${after}`
|
|
2343
|
+
})
|
|
2232
2344
|
|
|
2233
2345
|
// 3. Patch template elements — work on the template part only
|
|
2234
2346
|
const closingFm = result.indexOf('---', result.indexOf('---') + 3)
|
|
2235
2347
|
const frontmatterPart = result.slice(0, closingFm + 3)
|
|
2236
2348
|
let templatePart = result.slice(closingFm + 3)
|
|
2237
2349
|
|
|
2238
|
-
const scalarFields = innerFields.filter(f => f.type !== 'array')
|
|
2239
|
-
const arrayFields = innerFields.filter(f => f.type === 'array')
|
|
2350
|
+
const scalarFields = innerFields.filter((f) => f.type !== 'array')
|
|
2351
|
+
const arrayFields = innerFields.filter((f) => f.type === 'array')
|
|
2240
2352
|
|
|
2241
2353
|
// 3a. Scalar fields: add data-sk-field to the element containing {propName}
|
|
2242
2354
|
for (const field of scalarFields) {
|
|
@@ -2266,10 +2378,7 @@ export function patchChildComponentForFieldPrefix(
|
|
|
2266
2378
|
// 3b. Array fields: add index param + data-sk-field on innermost element
|
|
2267
2379
|
for (const field of arrayFields) {
|
|
2268
2380
|
// Match: features.map((feature) => or features.map((feature, fi) =>
|
|
2269
|
-
const mapParamRegex = new RegExp(
|
|
2270
|
-
`(${field.key}\\.map\\s*\\(\\s*\\(\\s*)(\\w+)(\\s*\\))`,
|
|
2271
|
-
'g',
|
|
2272
|
-
)
|
|
2381
|
+
const mapParamRegex = new RegExp(`(${field.key}\\.map\\s*\\(\\s*\\(\\s*)(\\w+)(\\s*\\))`, 'g')
|
|
2273
2382
|
// Add _fi index if missing, and annotate the ul/ol containing the map
|
|
2274
2383
|
templatePart = templatePart.replace(
|
|
2275
2384
|
new RegExp(`(<(?:ul|ol)[^>]*?)(\\/?>)([\\s\\S]*?${field.key}\\.map)`, 'g'),
|
|
@@ -2293,11 +2402,14 @@ export function patchChildComponentForFieldPrefix(
|
|
|
2293
2402
|
`(${field.key}\\.map\\s*\\([^)]*\\)\\s*=>\\s*\\([\\s\\S]*?)(<(?:span|li|td)\\b)([^>]*?>)([^<]*\\{\\w+\\})`,
|
|
2294
2403
|
'g',
|
|
2295
2404
|
)
|
|
2296
|
-
templatePart = templatePart.replace(
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2405
|
+
templatePart = templatePart.replace(
|
|
2406
|
+
mapBlockRegex,
|
|
2407
|
+
(_m, mapHead, tagOpen, tagClose, content) => {
|
|
2408
|
+
if (tagClose.includes('data-sk-field')) return _m
|
|
2409
|
+
const attr = ` data-sk-field={fieldPrefix ? \`\${fieldPrefix}.${field.key}.\${_fi}\` : undefined}`
|
|
2410
|
+
return `${mapHead}${tagOpen}${tagClose.slice(0, -1)}${attr}>${content}`
|
|
2411
|
+
},
|
|
2412
|
+
)
|
|
2301
2413
|
}
|
|
2302
2414
|
|
|
2303
2415
|
return frontmatterPart + templatePart
|
|
@@ -2310,18 +2422,27 @@ export function patchChildComponentForFieldPrefix(
|
|
|
2310
2422
|
export function detectChildImports(
|
|
2311
2423
|
source: string,
|
|
2312
2424
|
fields: PatchField[],
|
|
2313
|
-
): Array<{
|
|
2314
|
-
|
|
2425
|
+
): Array<{
|
|
2426
|
+
compName: string
|
|
2427
|
+
importPath: string
|
|
2428
|
+
innerFields: Array<{ key: string; type: string }>
|
|
2429
|
+
}> {
|
|
2430
|
+
const result: Array<{
|
|
2431
|
+
compName: string
|
|
2432
|
+
importPath: string
|
|
2433
|
+
innerFields: Array<{ key: string; type: string }>
|
|
2434
|
+
}> = []
|
|
2315
2435
|
|
|
2316
2436
|
for (const field of fields) {
|
|
2317
2437
|
const compName = (field as any).options?.sourceComponent as string | undefined
|
|
2318
2438
|
if (!compName) continue
|
|
2319
2439
|
|
|
2320
|
-
const innerFields: Array<{ key: string; type: string }> =
|
|
2321
|
-
(
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2440
|
+
const innerFields: Array<{ key: string; type: string }> = (
|
|
2441
|
+
(field as any).options?.arrayItem?.fields ?? []
|
|
2442
|
+
).map((f: { key: string; type: string }) => ({
|
|
2443
|
+
key: f.key,
|
|
2444
|
+
type: f.type,
|
|
2445
|
+
}))
|
|
2325
2446
|
|
|
2326
2447
|
// Find: import CompName from '...'
|
|
2327
2448
|
const importRegex = new RegExp(`import\\s+${compName}\\s+from\\s+['"]([^'"]+)['"]`)
|