@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
@@ -69,10 +69,7 @@ function deriveSectionKey(componentName) {
69
69
  function resolveImportPath(importPath, fromFile, allFiles, projectRoot) {
70
70
  if (importPath.startsWith("@/") || importPath.startsWith("~/")) {
71
71
  const aliasPath = importPath.replace(/^[@~]\//, "");
72
- const candidates = [
73
- `${projectRoot}/src/${aliasPath}`,
74
- `${projectRoot}/src/${aliasPath}.astro`
75
- ];
72
+ const candidates = [`${projectRoot}/src/${aliasPath}`, `${projectRoot}/src/${aliasPath}.astro`];
76
73
  for (const candidate of candidates) {
77
74
  if (allFiles.some((f) => f.path === candidate)) return candidate;
78
75
  }
@@ -170,7 +167,10 @@ async function analyzeAstroSection(source, sectionKey, componentName, componentP
170
167
  field.defaultValue = extractFrontmatterValue(frontmatter, field.key);
171
168
  }
172
169
  }
173
- const { fields: templateFields, repeatedGroups } = await extractTemplateFields(template, frontmatter);
170
+ const { fields: templateFields, repeatedGroups } = await extractTemplateFields(
171
+ template,
172
+ frontmatter
173
+ );
174
174
  const posAdjust = templateOffset;
175
175
  for (const group of repeatedGroups) {
176
176
  for (const inst of group.instances) {
@@ -206,15 +206,14 @@ async function analyzeAstroSection(source, sectionKey, componentName, componentP
206
206
  field._pos = usageMatch.index;
207
207
  } else {
208
208
  const mapUsage = template.indexOf(`${field.key}.map(`);
209
- field._pos = mapUsage !== -1 ? mapUsage : Infinity;
209
+ field._pos = mapUsage !== -1 ? mapUsage : Number.POSITIVE_INFINITY;
210
210
  }
211
211
  }
212
212
  const existingKeys = new Set(variableFields.map((f) => f.key));
213
- const allFields = [
214
- ...variableFields,
215
- ...templateFields.filter((f) => !existingKeys.has(f.key))
216
- ];
217
- allFields.sort((a, b) => (a._pos ?? Infinity) - (b._pos ?? Infinity));
213
+ const allFields = [...variableFields, ...templateFields.filter((f) => !existingKeys.has(f.key))];
214
+ allFields.sort(
215
+ (a, b) => (a._pos ?? Number.POSITIVE_INFINITY) - (b._pos ?? Number.POSITIVE_INFINITY)
216
+ );
218
217
  const fields = allFields.map(({ ...field }) => {
219
218
  delete field._pos;
220
219
  return field;
@@ -399,10 +398,10 @@ function convertAstPositions(node, b2c) {
399
398
  }
400
399
  var WRAPPER_OFFSET = 8;
401
400
  function nodeOffset(node) {
402
- return (node.position?.start?.offset ?? Infinity) - WRAPPER_OFFSET;
401
+ return (node.position?.start?.offset ?? Number.POSITIVE_INFINITY) - WRAPPER_OFFSET;
403
402
  }
404
403
  function nodeEnd(node) {
405
- return (node.position?.end?.offset ?? Infinity) - WRAPPER_OFFSET;
404
+ return (node.position?.end?.offset ?? Number.POSITIVE_INFINITY) - WRAPPER_OFFSET;
406
405
  }
407
406
  function getClassValue(node) {
408
407
  if (!node.attributes) return "";
@@ -547,8 +546,10 @@ function inferInnerFieldType(name) {
547
546
  if (/icon/.test(n)) return "icon";
548
547
  if (/image|img|photo|avatar|logo|thumbnail|src/.test(n)) return "image";
549
548
  if (/color|colour/.test(n)) return "color";
550
- if (/count|amount|number|quantity|total|rating|score|percent|order|index|size|width|height/.test(n)) return "number";
551
- if (/^is[A-Z]/.test(name) || /^has[A-Z]/.test(name) || /enabled|disabled|visible|hidden|active|checked|selected|highlight|accent|featured/.test(n)) return "boolean";
549
+ if (/count|amount|number|quantity|total|rating|score|percent|order|index|size|width|height/.test(n))
550
+ return "number";
551
+ if (/^is[A-Z]/.test(name) || /^has[A-Z]/.test(name) || /enabled|disabled|visible|hidden|active|checked|selected|highlight|accent|featured/.test(n))
552
+ return "boolean";
552
553
  return "text";
553
554
  }
554
555
  function extractInlineArrayValues(arrayContent, isObjectArray, isStringArray) {
@@ -682,7 +683,7 @@ ${template}`;
682
683
  const keyMatch = keyRegex.exec(template);
683
684
  if (keyMatch) finalPos = keyMatch.index;
684
685
  }
685
- fields.push({ ...field, _pos: finalPos === -1 ? Infinity : finalPos });
686
+ fields.push({ ...field, _pos: finalPos === -1 ? Number.POSITIVE_INFINITY : finalPos });
686
687
  }
687
688
  }
688
689
  function numberedKey(base, count) {
@@ -772,26 +773,34 @@ ${template}`;
772
773
  items.push(m[1] ?? m[2] ?? m[3] ?? "");
773
774
  }
774
775
  const hasFormatting = items.some((s) => /<[a-z]/.test(s));
775
- addField({
776
- key: fieldKey,
777
- type: "array",
778
- label: camelToLabel(fieldKey),
779
- confidence: "high",
780
- defaultValue: items.length > 0 ? items : void 0,
781
- options: { arrayItem: { type: "text", ...hasFormatting ? { formatting: true } : {} } }
782
- }, nodeOffset(node));
776
+ addField(
777
+ {
778
+ key: fieldKey,
779
+ type: "array",
780
+ label: camelToLabel(fieldKey),
781
+ confidence: "high",
782
+ defaultValue: items.length > 0 ? items : void 0,
783
+ options: {
784
+ arrayItem: { type: "text", ...hasFormatting ? { formatting: true } : {} }
785
+ }
786
+ },
787
+ nodeOffset(node)
788
+ );
783
789
  } else {
784
790
  const strFallback = exprCode.match(/\?\?\s*(?:`([\s\S]*?)`|'([^']*)'|"([^"]*)")/);
785
791
  const defaultValue = strFallback ? (strFallback[1] ?? strFallback[2] ?? strFallback[3] ?? "").trim() : void 0;
786
792
  const hasHtml = defaultValue ? /<[a-z]/.test(defaultValue) : false;
787
- addField({
788
- key: fieldKey,
789
- type: "text",
790
- label: camelToLabel(fieldKey),
791
- confidence: "high",
792
- ...defaultValue ? { defaultValue } : {},
793
- ...hasHtml ? { options: { formatting: true } } : {}
794
- }, nodeOffset(node));
793
+ addField(
794
+ {
795
+ key: fieldKey,
796
+ type: "text",
797
+ label: camelToLabel(fieldKey),
798
+ confidence: "high",
799
+ ...defaultValue ? { defaultValue } : {},
800
+ ...hasHtml ? { options: { formatting: true } } : {}
801
+ },
802
+ nodeOffset(node)
803
+ );
795
804
  }
796
805
  cmsBoundOffsets.add(nodeOffset(node));
797
806
  }
@@ -800,7 +809,9 @@ ${template}`;
800
809
  if (node.type !== "element" && node.type !== "component") return;
801
810
  for (const attr of node.attributes ?? []) {
802
811
  if (attr.kind !== "expression") continue;
803
- const cmsAttrMatch = attr.value.match(/^\s*\w+\?\.\s*(\w+)\s*\?\?\s*(?:`([\s\S]*?)`|'([^']*)'|"([^"]*)")/);
812
+ const cmsAttrMatch = attr.value.match(
813
+ /^\s*\w+\?\.\s*(\w+)\s*\?\?\s*(?:`([\s\S]*?)`|'([^']*)'|"([^"]*)")/
814
+ );
804
815
  if (cmsAttrMatch) {
805
816
  const fieldKey = cmsAttrMatch[1];
806
817
  const fallback = (cmsAttrMatch[2] ?? cmsAttrMatch[3] ?? cmsAttrMatch[4] ?? "").trim();
@@ -808,14 +819,17 @@ ${template}`;
808
819
  const isSetHtml = attr.name === "set:html";
809
820
  const fallbackHasHtml = fallback ? /<[a-z]/.test(fallback) : false;
810
821
  const hasHtml = fallbackHasHtml || isSetHtml && !fallback;
811
- addField({
812
- key: fieldKey,
813
- type: "text",
814
- label: camelToLabel(fieldKey),
815
- confidence: "high",
816
- ...fallback ? { defaultValue: fallback } : {},
817
- ...hasHtml ? { options: { formatting: true } } : {}
818
- }, pos);
822
+ addField(
823
+ {
824
+ key: fieldKey,
825
+ type: "text",
826
+ label: camelToLabel(fieldKey),
827
+ confidence: "high",
828
+ ...fallback ? { defaultValue: fallback } : {},
829
+ ...hasHtml ? { options: { formatting: true } } : {}
830
+ },
831
+ pos
832
+ );
819
833
  cmsBoundOffsets.add(nodeOffset(node));
820
834
  }
821
835
  }
@@ -835,10 +849,21 @@ ${template}`;
835
849
  if (!/uppercase|tracking-widest/.test(classVal)) return;
836
850
  const text = extractTextContent(node, true).trim();
837
851
  if (text.length >= 2 && text.length <= 80) {
838
- addField({ key: "overline", type: "text", label: "Overline", confidence: "medium", defaultValue: text }, nodeOffset(node));
852
+ addField(
853
+ {
854
+ key: "overline",
855
+ type: "text",
856
+ label: "Overline",
857
+ confidence: "medium",
858
+ defaultValue: text
859
+ },
860
+ nodeOffset(node)
861
+ );
839
862
  }
840
863
  });
841
- let headingCount = Array.from(usedKeys).filter((k) => k === "heading" || /^heading\d+$/.test(k)).length;
864
+ let headingCount = Array.from(usedKeys).filter(
865
+ (k) => k === "heading" || /^heading\d+$/.test(k)
866
+ ).length;
842
867
  walkAst(ast, (node) => {
843
868
  if (node.type !== "element") return;
844
869
  if (!/^h[1-6]$/.test(node.name ?? "")) return;
@@ -848,17 +873,22 @@ ${template}`;
848
873
  headingCount++;
849
874
  const headingOpts = { required: true };
850
875
  if (hasInlineFormatting(node)) headingOpts.formatting = true;
851
- addField({
852
- key: numberedKey("heading", headingCount),
853
- type: "text",
854
- label: numberedLabel("Heading", headingCount),
855
- confidence: "high",
856
- defaultValue: text,
857
- options: headingOpts
858
- }, nodeOffset(node));
876
+ addField(
877
+ {
878
+ key: numberedKey("heading", headingCount),
879
+ type: "text",
880
+ label: numberedLabel("Heading", headingCount),
881
+ confidence: "high",
882
+ defaultValue: text,
883
+ options: headingOpts
884
+ },
885
+ nodeOffset(node)
886
+ );
859
887
  }
860
888
  });
861
- let descCount = Array.from(usedKeys).filter((k) => k === "description" || /^description\d+$/.test(k)).length;
889
+ let descCount = Array.from(usedKeys).filter(
890
+ (k) => k === "description" || /^description\d+$/.test(k)
891
+ ).length;
862
892
  walkAst(ast, (node) => {
863
893
  if (node.type !== "element") return;
864
894
  if (node.name !== "p" && node.name !== "div") return;
@@ -866,16 +896,20 @@ ${template}`;
866
896
  const classVal = getClassValue(node);
867
897
  const serialized = serializeNode(node);
868
898
  if (/uppercase|tracking-widest/.test(classVal) && serialized.length < 250) return;
869
- if (/text-2xl|text-3xl|text-\[11px\]|text-\[10px\]|text-\[9px\]|text-\[8px\]/.test(classVal)) return;
899
+ if (/text-2xl|text-3xl|text-\[11px\]|text-\[10px\]|text-\[9px\]|text-\[8px\]/.test(classVal))
900
+ return;
870
901
  if (node.name === "div" && (containsElement(node, "a") || containsElement(node, "button"))) {
871
902
  const hasMixedText = (node.children ?? []).some(
872
903
  (c) => c.type === "text" && (c.value ?? "").trim().length > 0
873
904
  );
874
905
  if (!hasMixedText) return;
875
906
  }
876
- if (node.name === "div" && ["h1", "h2", "h3", "h4", "h5", "h6"].some((h) => containsElement(node, h))) return;
907
+ if (node.name === "div" && ["h1", "h2", "h3", "h4", "h5", "h6"].some((h) => containsElement(node, h)))
908
+ return;
877
909
  if (node.name === "div") {
878
- const contentChildren = (node.children ?? []).filter((c) => c.type !== "text" || (c.value ?? "").trim().length > 0);
910
+ const contentChildren = (node.children ?? []).filter(
911
+ (c) => c.type !== "text" || (c.value ?? "").trim().length > 0
912
+ );
879
913
  const hasOnlyElementChildren = contentChildren.length > 0 && contentChildren.every((c) => c.type === "element" || c.type === "component");
880
914
  if (hasOnlyElementChildren) return;
881
915
  }
@@ -884,14 +918,17 @@ ${template}`;
884
918
  descCount++;
885
919
  const descOpts = { multiline: true };
886
920
  if (hasInlineFormatting(node)) descOpts.formatting = true;
887
- addField({
888
- key: numberedKey("description", descCount),
889
- type: "text",
890
- label: numberedLabel("Beschreibung", descCount),
891
- confidence: "medium",
892
- defaultValue: text,
893
- options: descOpts
894
- }, nodeOffset(node));
921
+ addField(
922
+ {
923
+ key: numberedKey("description", descCount),
924
+ type: "text",
925
+ label: numberedLabel("Beschreibung", descCount),
926
+ confidence: "medium",
927
+ defaultValue: text,
928
+ options: descOpts
929
+ },
930
+ nodeOffset(node)
931
+ );
895
932
  });
896
933
  let richCount = 0;
897
934
  walkAst(ast, (node) => {
@@ -905,14 +942,17 @@ ${template}`;
905
942
  const strMatch = expr.match(/['"]([^'"]+)['"]/);
906
943
  const fallbackMatch = expr.match(/\?\?\s*['"]([^'"]+)['"]/);
907
944
  const value = fallbackMatch?.[1] ?? strMatch?.[1] ?? "";
908
- addField({
909
- key: numberedKey("richText", richCount),
910
- type: "text",
911
- label: numberedLabel("Rich Text", richCount),
912
- confidence: "high",
913
- defaultValue: value,
914
- options: { multiline: true, formatting: true }
915
- }, nodeOffset(node));
945
+ addField(
946
+ {
947
+ key: numberedKey("richText", richCount),
948
+ type: "text",
949
+ label: numberedLabel("Rich Text", richCount),
950
+ confidence: "high",
951
+ defaultValue: value,
952
+ options: { multiline: true, formatting: true }
953
+ },
954
+ nodeOffset(node)
955
+ );
916
956
  }
917
957
  });
918
958
  let ctaCount = 0;
@@ -925,13 +965,16 @@ ${template}`;
925
965
  const text = extractTextContent(node, true).replace(/\s+/g, " ").trim();
926
966
  if (text.length >= 2 && text.length <= 60) {
927
967
  ctaCount++;
928
- addField({
929
- key: numberedKey("ctaText", ctaCount),
930
- type: "text",
931
- label: numberedLabel("Button Text", ctaCount),
932
- confidence: "medium",
933
- defaultValue: text
934
- }, nodeOffset(node));
968
+ addField(
969
+ {
970
+ key: numberedKey("ctaText", ctaCount),
971
+ type: "text",
972
+ label: numberedLabel("Button Text", ctaCount),
973
+ confidence: "medium",
974
+ defaultValue: text
975
+ },
976
+ nodeOffset(node)
977
+ );
935
978
  }
936
979
  });
937
980
  let linkCount = 0;
@@ -953,13 +996,16 @@ ${template}`;
953
996
  if (!href) return;
954
997
  if (href.startsWith("#") || href.startsWith("javascript:")) return;
955
998
  linkCount++;
956
- addField({
957
- key: numberedKey("ctaLink", linkCount),
958
- type: "text",
959
- label: numberedLabel("Button Link", linkCount),
960
- confidence: "medium",
961
- defaultValue: href
962
- }, nodeOffset(node));
999
+ addField(
1000
+ {
1001
+ key: numberedKey("ctaLink", linkCount),
1002
+ type: "text",
1003
+ label: numberedLabel("Button Link", linkCount),
1004
+ confidence: "medium",
1005
+ defaultValue: href
1006
+ },
1007
+ nodeOffset(node)
1008
+ );
963
1009
  });
964
1010
  let imgCount = 0;
965
1011
  let altCount = 0;
@@ -978,26 +1024,32 @@ ${template}`;
978
1024
  }
979
1025
  if (src) {
980
1026
  imgCount++;
981
- addField({
982
- key: numberedKey("image", imgCount),
983
- type: "image",
984
- label: numberedLabel("Bild", imgCount),
985
- confidence: "high",
986
- defaultValue: { path: src.trim(), alt: "" }
987
- }, nodeOffset(node));
1027
+ addField(
1028
+ {
1029
+ key: numberedKey("image", imgCount),
1030
+ type: "image",
1031
+ label: numberedLabel("Bild", imgCount),
1032
+ confidence: "high",
1033
+ defaultValue: { path: src.trim(), alt: "" }
1034
+ },
1035
+ nodeOffset(node)
1036
+ );
988
1037
  }
989
1038
  }
990
1039
  if (tagName === "img" || tagName === "Image") {
991
1040
  const altAttr = getAttr(node, "alt");
992
1041
  if (altAttr && altAttr.kind === "quoted" && altAttr.value.trim().length >= 2) {
993
1042
  altCount++;
994
- addField({
995
- key: numberedKey("imageAlt", altCount),
996
- type: "text",
997
- label: numberedLabel("Bild Alt-Text", altCount),
998
- confidence: "medium",
999
- defaultValue: altAttr.value.trim()
1000
- }, nodeOffset(node));
1043
+ addField(
1044
+ {
1045
+ key: numberedKey("imageAlt", altCount),
1046
+ type: "text",
1047
+ label: numberedLabel("Bild Alt-Text", altCount),
1048
+ confidence: "medium",
1049
+ defaultValue: altAttr.value.trim()
1050
+ },
1051
+ nodeOffset(node)
1052
+ );
1001
1053
  }
1002
1054
  }
1003
1055
  });
@@ -1009,12 +1061,15 @@ ${template}`;
1009
1061
  if (svgSource.length < 100) return;
1010
1062
  if (parentNode && (parentNode.name === "a" || parentNode.name === "button")) return;
1011
1063
  iconCount++;
1012
- addField({
1013
- key: numberedKey("icon", iconCount),
1014
- type: "icon",
1015
- label: numberedLabel("Icon", iconCount),
1016
- confidence: "low"
1017
- }, nodeOffset(node));
1064
+ addField(
1065
+ {
1066
+ key: numberedKey("icon", iconCount),
1067
+ type: "icon",
1068
+ label: numberedLabel("Icon", iconCount),
1069
+ confidence: "low"
1070
+ },
1071
+ nodeOffset(node)
1072
+ );
1018
1073
  });
1019
1074
  let foundIconProp = false;
1020
1075
  walkAst(ast, (node) => {
@@ -1031,7 +1086,9 @@ ${template}`;
1031
1086
  walkAst(ast, (node) => {
1032
1087
  if (node.type !== "expression") return;
1033
1088
  const exprCode = (node.children ?? []).map((c) => c.value ?? "").join("");
1034
- const inlineArrayMatch = exprCode.match(/^\s*\[([\s\S]*?)\]\s*\.map\s*\(\s*\(?\s*(\{[^}]*\}|\w+)/);
1089
+ const inlineArrayMatch = exprCode.match(
1090
+ /^\s*\[([\s\S]*?)\]\s*\.map\s*\(\s*\(?\s*(\{[^}]*\}|\w+)/
1091
+ );
1035
1092
  if (!inlineArrayMatch) return;
1036
1093
  arrayCount++;
1037
1094
  const arrayContent = inlineArrayMatch[1];
@@ -1064,23 +1121,29 @@ ${template}`;
1064
1121
  label: camelToLabel(prop),
1065
1122
  confidence: "medium"
1066
1123
  }));
1067
- addField({
1068
- key: numberedKey("items", arrayCount),
1069
- type: "array",
1070
- label: numberedLabel("Liste", arrayCount),
1071
- confidence: "high",
1072
- defaultValue: arrayDefaultValue,
1073
- options: { arrayItem: { type: "object", fields: innerFields } }
1074
- }, nodeOffset(node));
1124
+ addField(
1125
+ {
1126
+ key: numberedKey("items", arrayCount),
1127
+ type: "array",
1128
+ label: numberedLabel("Liste", arrayCount),
1129
+ confidence: "high",
1130
+ defaultValue: arrayDefaultValue,
1131
+ options: { arrayItem: { type: "object", fields: innerFields } }
1132
+ },
1133
+ nodeOffset(node)
1134
+ );
1075
1135
  } else {
1076
- addField({
1077
- key: numberedKey("items", arrayCount),
1078
- type: "array",
1079
- label: numberedLabel("Liste", arrayCount),
1080
- confidence: "high",
1081
- defaultValue: arrayDefaultValue,
1082
- options: { arrayItem: { type: "text" } }
1083
- }, nodeOffset(node));
1136
+ addField(
1137
+ {
1138
+ key: numberedKey("items", arrayCount),
1139
+ type: "array",
1140
+ label: numberedLabel("Liste", arrayCount),
1141
+ confidence: "high",
1142
+ defaultValue: arrayDefaultValue,
1143
+ options: { arrayItem: { type: "text" } }
1144
+ },
1145
+ nodeOffset(node)
1146
+ );
1084
1147
  }
1085
1148
  });
1086
1149
  let staticListCount = 0;
@@ -1144,14 +1207,19 @@ ${template}`;
1144
1207
  }
1145
1208
  if (listItems.length < 1) return;
1146
1209
  staticListCount++;
1147
- addField({
1148
- key: numberedKey("items", arrayCount + staticListCount),
1149
- type: "array",
1150
- label: numberedLabel("Liste", arrayCount + staticListCount),
1151
- confidence: "high",
1152
- defaultValue: listItems,
1153
- options: { arrayItem: { type: "text", ...listHasFormatting ? { formatting: true } : {} } }
1154
- }, nodeOffset(node));
1210
+ addField(
1211
+ {
1212
+ key: numberedKey("items", arrayCount + staticListCount),
1213
+ type: "array",
1214
+ label: numberedLabel("Liste", arrayCount + staticListCount),
1215
+ confidence: "high",
1216
+ defaultValue: listItems,
1217
+ options: {
1218
+ arrayItem: { type: "text", ...listHasFormatting ? { formatting: true } : {} }
1219
+ }
1220
+ },
1221
+ nodeOffset(node)
1222
+ );
1155
1223
  });
1156
1224
  const componentCounts = /* @__PURE__ */ new Map();
1157
1225
  walkAst(ast, (node) => {
@@ -1166,32 +1234,47 @@ ${template}`;
1166
1234
  if (attr.kind === "quoted") {
1167
1235
  const propName = attr.name;
1168
1236
  const propValue = attr.value;
1169
- if (/^(class|className|id|style|type|role|width|height|viewBox|fill|stroke|xmlns|d|cx|cy|r|rx|ry|x|y|x1|y1|x2|y2)$/.test(propName)) continue;
1170
- if (/^(lang|language|filename|file|format|variant|size|loading|decoding|transition|client:.*)$/.test(propName)) continue;
1237
+ if (/^(class|className|id|style|type|role|width|height|viewBox|fill|stroke|xmlns|d|cx|cy|r|rx|ry|x|y|x1|y1|x2|y2)$/.test(
1238
+ propName
1239
+ ))
1240
+ continue;
1241
+ if (/^(lang|language|filename|file|format|variant|size|loading|decoding|transition|client:.*)$/.test(
1242
+ propName
1243
+ ))
1244
+ continue;
1171
1245
  if (/^(aria-|data-)/.test(propName)) continue;
1172
1246
  if (propValue.length < 2) continue;
1173
1247
  if (propValue === "true" || propValue === "false" || /^\d+$/.test(propValue)) continue;
1174
1248
  if (propValue.includes("/") && !propValue.includes(" ")) continue;
1175
- addField({
1176
- key: propName,
1177
- type: propName === "icon" ? "icon" : propName === "src" ? "image" : "text",
1178
- label: camelToLabel(propName),
1179
- confidence: "medium",
1180
- defaultValue: propValue
1181
- }, nodeOffset(node));
1249
+ addField(
1250
+ {
1251
+ key: propName,
1252
+ type: propName === "icon" ? "icon" : propName === "src" ? "image" : "text",
1253
+ label: camelToLabel(propName),
1254
+ confidence: "medium",
1255
+ defaultValue: propValue
1256
+ },
1257
+ nodeOffset(node)
1258
+ );
1182
1259
  } else if (attr.kind === "expression") {
1183
1260
  const propName = attr.name;
1184
- if (/^(class|className|id|style|type|role|lang|language|filename|file|format|variant|size|loading|decoding)$/.test(propName)) continue;
1261
+ if (/^(class|className|id|style|type|role|lang|language|filename|file|format|variant|size|loading|decoding)$/.test(
1262
+ propName
1263
+ ))
1264
+ continue;
1185
1265
  if (/^\s*\w+\s*$/.test(attr.value)) continue;
1186
1266
  const fallbackMatch = attr.value.match(/\?\?\s*['"]([^'"]+)['"]/);
1187
1267
  if (fallbackMatch) {
1188
- addField({
1189
- key: propName,
1190
- type: propName === "icon" ? "icon" : propName === "src" ? "image" : "text",
1191
- label: camelToLabel(propName),
1192
- confidence: "medium",
1193
- defaultValue: fallbackMatch[1]
1194
- }, nodeOffset(node));
1268
+ addField(
1269
+ {
1270
+ key: propName,
1271
+ type: propName === "icon" ? "icon" : propName === "src" ? "image" : "text",
1272
+ label: camelToLabel(propName),
1273
+ confidence: "medium",
1274
+ defaultValue: fallbackMatch[1]
1275
+ },
1276
+ nodeOffset(node)
1277
+ );
1195
1278
  }
1196
1279
  }
1197
1280
  }
@@ -1232,23 +1315,43 @@ ${template}`;
1232
1315
  if (Object.keys(item).length > 0) instanceValues.push(item);
1233
1316
  }
1234
1317
  if (allProps.size > 0) {
1235
- const cardKey = compName.replace(/([A-Z])/g, (_m, c, i) => i === 0 ? c.toLowerCase() : "_" + c.toLowerCase()).replace(/_/g, "") + "s";
1318
+ const cardKey = compName.replace(
1319
+ /([A-Z])/g,
1320
+ (_m, c, i) => i === 0 ? c.toLowerCase() : "_" + c.toLowerCase()
1321
+ ).replace(/_/g, "") + "s";
1236
1322
  if (!usedKeys.has(cardKey)) {
1237
1323
  const innerFields = [...allProps].map((p) => {
1238
1324
  const isArray = instanceValues.some((iv) => Array.isArray(iv[p]));
1239
1325
  if (isArray) {
1240
- return { key: p, type: "array", label: camelToLabel(p), confidence: "medium", options: { arrayItem: { type: "text" } } };
1326
+ return {
1327
+ key: p,
1328
+ type: "array",
1329
+ label: camelToLabel(p),
1330
+ confidence: "medium",
1331
+ options: { arrayItem: { type: "text" } }
1332
+ };
1241
1333
  }
1242
- return { key: p, type: inferInnerFieldType(p), label: camelToLabel(p), confidence: "medium" };
1334
+ return {
1335
+ key: p,
1336
+ type: inferInnerFieldType(p),
1337
+ label: camelToLabel(p),
1338
+ confidence: "medium"
1339
+ };
1243
1340
  });
1244
- addField({
1245
- key: cardKey,
1246
- type: "array",
1247
- label: `${compName} Liste`,
1248
- confidence: "medium",
1249
- defaultValue: instanceValues.length > 0 ? instanceValues : void 0,
1250
- options: { arrayItem: { type: "object", fields: innerFields }, sourceComponent: compName }
1251
- }, nodeOffset(instances[0]));
1341
+ addField(
1342
+ {
1343
+ key: cardKey,
1344
+ type: "array",
1345
+ label: `${compName} Liste`,
1346
+ confidence: "medium",
1347
+ defaultValue: instanceValues.length > 0 ? instanceValues : void 0,
1348
+ options: {
1349
+ arrayItem: { type: "object", fields: innerFields },
1350
+ sourceComponent: compName
1351
+ }
1352
+ },
1353
+ nodeOffset(instances[0])
1354
+ );
1252
1355
  }
1253
1356
  }
1254
1357
  }
@@ -1371,8 +1474,8 @@ ${template}`;
1371
1474
  const tag = item.tag;
1372
1475
  const isArray = tag === "__array__";
1373
1476
  const isLink = tag === "a";
1374
- let fieldType = isArray ? "array" : isLink ? "link" : "text";
1375
- let keyBase = /^h[1-6]$/.test(tag) ? "heading" : isLink ? "link" : isArray ? "list" : "text";
1477
+ const fieldType = isArray ? "array" : isLink ? "link" : "text";
1478
+ const keyBase = /^h[1-6]$/.test(tag) ? "heading" : isLink ? "link" : isArray ? "list" : "text";
1376
1479
  typeCounts[keyBase] = (typeCounts[keyBase] ?? 0) + 1;
1377
1480
  const key = typeCounts[keyBase] === 1 ? keyBase : `${keyBase}${typeCounts[keyBase]}`;
1378
1481
  const positions = new Array(instanceCount).fill(null);
@@ -1395,7 +1498,14 @@ ${template}`;
1395
1498
  const ltDefaults = new Array(instanceCount).fill(null);
1396
1499
  ltPositions[ii] = positions[ii] ?? null;
1397
1500
  ltDefaults[ii] = item.text || null;
1398
- innerFields.push({ key: ltKey, type: "text", tag: "a", required: false, positions: ltPositions, defaultValues: ltDefaults });
1501
+ innerFields.push({
1502
+ key: ltKey,
1503
+ type: "text",
1504
+ tag: "a",
1505
+ required: false,
1506
+ positions: ltPositions,
1507
+ defaultValues: ltDefaults
1508
+ });
1399
1509
  }
1400
1510
  }
1401
1511
  }
@@ -1434,18 +1544,21 @@ ${template}`;
1434
1544
  };
1435
1545
  });
1436
1546
  const groupLabel = group.tag === "tr" ? "Tabellen-Zeilen" : `${group.tag.charAt(0).toUpperCase() + group.tag.slice(1)} Liste`;
1437
- addField({
1438
- key: fieldKey,
1439
- type: "array",
1440
- label: groupLabel,
1441
- confidence: "high",
1442
- defaultValue,
1443
- options: {
1444
- arrayItem: { type: "object", fields: innerFieldDefs },
1445
- _repeatedTag: group.tag,
1446
- _instanceCount: instanceCount
1447
- }
1448
- }, instanceBounds[0].start);
1547
+ addField(
1548
+ {
1549
+ key: fieldKey,
1550
+ type: "array",
1551
+ label: groupLabel,
1552
+ confidence: "high",
1553
+ defaultValue,
1554
+ options: {
1555
+ arrayItem: { type: "object", fields: innerFieldDefs },
1556
+ _repeatedTag: group.tag,
1557
+ _instanceCount: instanceCount
1558
+ }
1559
+ },
1560
+ instanceBounds[0].start
1561
+ );
1449
1562
  const classAttrs = group.instances.map((inst) => collectClassAttrs(inst, "", template));
1450
1563
  repeatedGroups.push({
1451
1564
  tag: group.tag,
@@ -1535,7 +1648,13 @@ function walkContentNodes(root, depth, items, frontmatter) {
1535
1648
  }
1536
1649
  }
1537
1650
  function findMatchingItem(targetFp, canonicalItem, canonicalIndex, allCanonical, consumed) {
1538
- const result = findMatchingItemCore(targetFp, canonicalItem, canonicalIndex, allCanonical, consumed);
1651
+ const result = findMatchingItemCore(
1652
+ targetFp,
1653
+ canonicalItem,
1654
+ canonicalIndex,
1655
+ allCanonical,
1656
+ consumed
1657
+ );
1539
1658
  if (result) consumed.add(result.idx);
1540
1659
  return result?.item ?? null;
1541
1660
  }