meno-core 1.0.39 → 1.0.40

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 (49) hide show
  1. package/build-astro.ts +195 -68
  2. package/dist/bin/cli.js +1 -1
  3. package/dist/build-static.js +6 -6
  4. package/dist/chunks/{chunk-WK5XLASY.js → chunk-3NOZVNM4.js} +3 -3
  5. package/dist/chunks/{chunk-W6HDII4T.js → chunk-GKICS7CF.js} +27 -14
  6. package/dist/chunks/chunk-GKICS7CF.js.map +7 -0
  7. package/dist/chunks/{chunk-P3FX5HJM.js → chunk-LOJLO2EY.js} +1 -1
  8. package/dist/chunks/chunk-LOJLO2EY.js.map +7 -0
  9. package/dist/chunks/{chunk-HNAS6BSS.js → chunk-MOCRENNU.js} +55 -5
  10. package/dist/chunks/{chunk-HNAS6BSS.js.map → chunk-MOCRENNU.js.map} +3 -3
  11. package/dist/chunks/{chunk-NV25WXCA.js → chunk-OJ5SROQN.js} +5 -3
  12. package/dist/chunks/chunk-OJ5SROQN.js.map +7 -0
  13. package/dist/chunks/{chunk-AIXKUVNG.js → chunk-V4SVSX3X.js} +3 -3
  14. package/dist/chunks/{chunk-KULPBDC7.js → chunk-Z7SAOCDG.js} +5 -2
  15. package/dist/chunks/{chunk-KULPBDC7.js.map → chunk-Z7SAOCDG.js.map} +2 -2
  16. package/dist/chunks/{constants-5CRJRQNR.js → constants-L75FR445.js} +2 -2
  17. package/dist/entries/server-router.js +6 -6
  18. package/dist/lib/client/index.js +5 -5
  19. package/dist/lib/client/index.js.map +2 -2
  20. package/dist/lib/server/index.js +2007 -197
  21. package/dist/lib/server/index.js.map +4 -4
  22. package/dist/lib/shared/index.js +3 -3
  23. package/dist/lib/test-utils/index.js +1 -1
  24. package/lib/client/core/builders/embedBuilder.ts +2 -2
  25. package/lib/server/astro/cmsPageEmitter.ts +417 -0
  26. package/lib/server/astro/componentEmitter.ts +90 -5
  27. package/lib/server/astro/nodeToAstro.ts +830 -37
  28. package/lib/server/astro/pageEmitter.ts +39 -3
  29. package/lib/server/astro/tailwindMapper.ts +69 -8
  30. package/lib/server/astro/templateTransformer.ts +107 -0
  31. package/lib/server/index.ts +9 -0
  32. package/lib/server/routes/api/components.ts +62 -0
  33. package/lib/server/routes/api/core-routes.ts +8 -0
  34. package/lib/server/ssr/ssrRenderer.ts +30 -10
  35. package/lib/server/webflow/buildWebflow.ts +415 -0
  36. package/lib/server/webflow/index.ts +22 -0
  37. package/lib/server/webflow/nodeToWebflow.ts +423 -0
  38. package/lib/server/webflow/styleMapper.ts +241 -0
  39. package/lib/server/webflow/types.ts +196 -0
  40. package/lib/shared/constants.ts +2 -0
  41. package/lib/shared/types/components.ts +1 -0
  42. package/lib/shared/validation/schemas.ts +1 -0
  43. package/package.json +1 -1
  44. package/dist/chunks/chunk-NV25WXCA.js.map +0 -7
  45. package/dist/chunks/chunk-P3FX5HJM.js.map +0 -7
  46. package/dist/chunks/chunk-W6HDII4T.js.map +0 -7
  47. /package/dist/chunks/{chunk-WK5XLASY.js.map → chunk-3NOZVNM4.js.map} +0 -0
  48. /package/dist/chunks/{chunk-AIXKUVNG.js.map → chunk-V4SVSX3X.js.map} +0 -0
  49. /package/dist/chunks/{constants-5CRJRQNR.js.map → constants-L75FR445.js.map} +0 -0
@@ -3,7 +3,7 @@ import {
3
3
  } from "../../chunks/chunk-4OFZP5NQ.js";
4
4
  import {
5
5
  buildStaticPages
6
- } from "../../chunks/chunk-WK5XLASY.js";
6
+ } from "../../chunks/chunk-3NOZVNM4.js";
7
7
  import {
8
8
  ComponentService,
9
9
  EnumService,
@@ -32,7 +32,7 @@ import {
32
32
  logResponseTime,
33
33
  withErrorHandling,
34
34
  withLogging
35
- } from "../../chunks/chunk-HNAS6BSS.js";
35
+ } from "../../chunks/chunk-MOCRENNU.js";
36
36
  import {
37
37
  CMSService,
38
38
  ColorService,
@@ -43,6 +43,7 @@ import {
43
43
  buildAttributes,
44
44
  buildComponentHTML,
45
45
  buildImageMetadataMap,
46
+ buildSlugIndex,
46
47
  clearJSValidationCache,
47
48
  collectComponentCSS,
48
49
  collectComponentJavaScript,
@@ -75,8 +76,9 @@ import {
75
76
  renderPageSSR,
76
77
  resetFontConfig,
77
78
  styleToString,
79
+ translatePath,
78
80
  variableService
79
- } from "../../chunks/chunk-W6HDII4T.js";
81
+ } from "../../chunks/chunk-GKICS7CF.js";
80
82
  import {
81
83
  ConfigService,
82
84
  configService
@@ -110,18 +112,23 @@ import {
110
112
  writeFile
111
113
  } from "../../chunks/chunk-WQFG7PAH.js";
112
114
  import "../../chunks/chunk-LIHJ6OUH.js";
113
- import "../../chunks/chunk-P3FX5HJM.js";
114
- import "../../chunks/chunk-AIXKUVNG.js";
115
+ import "../../chunks/chunk-LOJLO2EY.js";
116
+ import "../../chunks/chunk-V4SVSX3X.js";
115
117
  import {
116
118
  extractInteractiveStyleMappings,
119
+ extractUtilityClassesFromHTML,
117
120
  generateAllInteractiveCSS,
118
121
  generateElementClassName,
122
+ generateUtilityCSS,
119
123
  hasInteractiveStyleMappings,
120
124
  isItemDraftForLocale,
121
- isVoidElement
122
- } from "../../chunks/chunk-NV25WXCA.js";
125
+ isVoidElement,
126
+ singularize
127
+ } from "../../chunks/chunk-OJ5SROQN.js";
123
128
  import {
124
129
  DEFAULT_BREAKPOINTS,
130
+ DEFAULT_I18N_CONFIG,
131
+ buildLocalizedPath,
125
132
  isI18nValue,
126
133
  resolveI18nValue
127
134
  } from "../../chunks/chunk-PGH3ATYI.js";
@@ -130,10 +137,11 @@ import {
130
137
  HMR_ROUTE,
131
138
  MAX_PORT_ATTEMPTS,
132
139
  NODE_TYPE,
140
+ RAW_HTML_PREFIX,
133
141
  SERVER_PORT,
134
142
  SERVE_PORT,
135
143
  init_constants
136
- } from "../../chunks/chunk-KULPBDC7.js";
144
+ } from "../../chunks/chunk-Z7SAOCDG.js";
137
145
  import "../../chunks/chunk-KSBZ2L7C.js";
138
146
 
139
147
  // build-astro.ts
@@ -374,7 +382,11 @@ var singleValueMatches = {
374
382
  "top:0": "top-0",
375
383
  "right:0": "right-0",
376
384
  "bottom:0": "bottom-0",
377
- "left:0": "left-0"
385
+ "left:0": "left-0",
386
+ "outline:none": "[outline:none]",
387
+ "background:none": "[background:none]",
388
+ "background:transparent": "[background:transparent]",
389
+ "backgroundColor:transparent": "bg-transparent"
378
390
  };
379
391
  var arbitraryPrefixMap = {
380
392
  // Spacing
@@ -433,7 +445,7 @@ var arbitraryPrefixMap = {
433
445
  backdropFilter: "backdrop",
434
446
  transform: "[transform]",
435
447
  transformOrigin: "origin",
436
- transition: "transition",
448
+ transition: "[transition]",
437
449
  mixBlendMode: "mix-blend",
438
450
  clipPath: "[clip-path]",
439
451
  // Positioning
@@ -485,10 +497,29 @@ function propertyToTailwind(property, value) {
485
497
  if (property === "color" || property === "backgroundColor" || property === "borderColor") {
486
498
  const prefix = property === "color" ? "text" : property === "backgroundColor" ? "bg" : "border";
487
499
  if (strValue.includes("var(")) {
488
- return `${prefix}-[${strValue}]`;
500
+ return `${prefix}-[color:${strValue}]`;
489
501
  }
490
502
  if (!strValue.match(/^[#\d]/) && !strValue.includes("rgb") && !strValue.includes("hsl")) {
491
- return `${prefix}-[var(--${strValue})]`;
503
+ return `${prefix}-[color:var(--${strValue})]`;
504
+ }
505
+ }
506
+ if (property === "borderColor" || property === "color") {
507
+ const prefix = property === "color" ? "text" : "border";
508
+ const sanitized2 = strValue.replace(/\s+/g, "_");
509
+ return `${prefix}-[color:${sanitized2}]`;
510
+ }
511
+ if (property === "border" || property === "borderTop" || property === "borderRight" || property === "borderBottom" || property === "borderLeft") {
512
+ if (strValue.includes("solid") || strValue.includes("dashed") || strValue.includes("dotted") || strValue.includes("none")) {
513
+ const cssProp = property.replace(/([A-Z])/g, "-$1").toLowerCase();
514
+ const sanitized2 = strValue.replace(/\s+/g, "_");
515
+ return `[${cssProp}:${sanitized2}]`;
516
+ }
517
+ }
518
+ if (property === "background") {
519
+ const isSimpleColor = /^(#[0-9a-fA-F]{3,8}|rgb|hsl|var\()/.test(strValue);
520
+ if (!isSimpleColor) {
521
+ const sanitized2 = strValue.replace(/\s+/g, "_");
522
+ return `[background:${sanitized2}]`;
492
523
  }
493
524
  }
494
525
  const twPrefix = arbitraryPrefixMap[property];
@@ -502,14 +533,28 @@ function propertyToTailwind(property, value) {
502
533
  return `${twPrefix.slice(0, -1)}:${sanitized2}]`;
503
534
  }
504
535
  const sanitized = strValue.replace(/\s+/g, "_");
505
- if (property === "fontFamily" && !sanitized.includes(",") && !sanitized.startsWith("'")) {
506
- return `${twPrefix}-['${sanitized}']`;
536
+ if (property === "fontSize") {
537
+ return `text-[length:${sanitized}]`;
538
+ }
539
+ if (property === "fontFamily") {
540
+ return `font-[family-name:${sanitized}]`;
541
+ }
542
+ if (property === "fontWeight") {
543
+ return `font-[number:${sanitized}]`;
507
544
  }
508
545
  return `${twPrefix}-[${sanitized}]`;
509
546
  }
510
547
  function stylesToTailwind(style) {
511
548
  const classes = [];
512
549
  const dynamicStyles = {};
550
+ const hasBorderColor = "borderColor" in style && !isStyleMapping(style.borderColor);
551
+ const borderShorthands = /* @__PURE__ */ new Set([
552
+ "border",
553
+ "borderTop",
554
+ "borderRight",
555
+ "borderBottom",
556
+ "borderLeft"
557
+ ]);
513
558
  for (const [prop, value] of Object.entries(style)) {
514
559
  if (isStyleMapping(value)) continue;
515
560
  const strValue = String(value);
@@ -518,6 +563,15 @@ function stylesToTailwind(style) {
518
563
  dynamicStyles[cssProp] = strValue;
519
564
  continue;
520
565
  }
566
+ if (hasBorderColor && borderShorthands.has(prop)) {
567
+ const parts = strValue.split(/\s+/);
568
+ const width = parts.find((p) => /^\d/.test(p) || p === "0");
569
+ const borderStyle = parts.find((p) => /^(solid|dashed|dotted|double|groove|ridge|inset|outset|none|hidden)$/.test(p));
570
+ const cssProp = prop.replace(/([A-Z])/g, "-$1").toLowerCase();
571
+ if (width) classes.push(`[${cssProp}-width:${width}]`);
572
+ if (borderStyle) classes.push(`[${cssProp}-style:${borderStyle}]`);
573
+ continue;
574
+ }
521
575
  const twClass = propertyToTailwind(prop, value);
522
576
  if (twClass) {
523
577
  classes.push(twClass);
@@ -569,15 +623,121 @@ function responsiveStylesToTailwind(style, breakpoints) {
569
623
 
570
624
  // lib/server/astro/nodeToAstro.ts
571
625
  init_constants();
626
+
627
+ // lib/server/astro/templateTransformer.ts
628
+ var CMS_TEMPLATE_PATTERN = /\{\{cms\.([^}]+)\}\}/g;
629
+ var ITEM_TEMPLATE_PATTERN = /\{\{([^}]+)\}\}/g;
630
+ function isTemplateExpression(text) {
631
+ return /\{\{.+?\}\}/.test(text);
632
+ }
633
+ function transformCMSTemplate(text, binding = "entry", richTextFields, wrapFn) {
634
+ const w = (expr) => wrapFn ? `${wrapFn}(${expr})` : expr;
635
+ const fullMatch = text.match(/^\{\{cms\.([^}]+)\}\}$/);
636
+ if (fullMatch) {
637
+ const fieldPath = fullMatch[1].trim();
638
+ const topLevelField = fieldPath.split(".")[0];
639
+ if (richTextFields?.has(topLevelField)) {
640
+ return `<Fragment set:html={${w(`${binding}.data.${fieldPath}`)}} />`;
641
+ }
642
+ return `{${w(`${binding}.data.${fieldPath}`)}}`;
643
+ }
644
+ if (CMS_TEMPLATE_PATTERN.test(text)) {
645
+ CMS_TEMPLATE_PATTERN.lastIndex = 0;
646
+ const replaced = text.replace(CMS_TEMPLATE_PATTERN, (_, fieldPath) => {
647
+ return `\${${w(`${binding}.data.${fieldPath.trim()}`)}}`;
648
+ });
649
+ return `{\`${replaced}\`}`;
650
+ }
651
+ return text;
652
+ }
653
+ function rewriteItemVar(expr, itemVar) {
654
+ if (itemVar === "item") return expr;
655
+ return expr.replace(/\bitem\./g, `${itemVar}.`);
656
+ }
657
+ function replaceItemMetaVars(expr, indexVar, sourceVar, itemVar) {
658
+ const lastExpr = sourceVar ? `(${indexVar} === ${sourceVar}.length - 1)` : `false /* itemLast not supported */`;
659
+ let result = expr.replace(/\bitemIndex\b/g, indexVar).replace(/\bitemFirst\b/g, `(${indexVar} === 0)`).replace(/\bitemLast\b/g, lastExpr);
660
+ if (itemVar) result = rewriteItemVar(result, itemVar);
661
+ return result;
662
+ }
663
+ function transformItemTemplate(text, itemVar = "item", indexVar, sourceVar) {
664
+ const fullMatch = text.match(/^\{\{(.+)\}\}$/);
665
+ if (fullMatch) {
666
+ let expr = fullMatch[1].trim();
667
+ expr = rewriteItemVar(expr, itemVar);
668
+ if (indexVar) expr = replaceItemMetaVars(expr, indexVar, sourceVar);
669
+ if (expr.startsWith(`${itemVar}.`)) {
670
+ return `{${expr}}`;
671
+ }
672
+ return `{${expr}}`;
673
+ }
674
+ if (ITEM_TEMPLATE_PATTERN.test(text)) {
675
+ ITEM_TEMPLATE_PATTERN.lastIndex = 0;
676
+ const replaced = text.replace(ITEM_TEMPLATE_PATTERN, (_, expr) => {
677
+ let trimmed = expr.trim();
678
+ trimmed = rewriteItemVar(trimmed, itemVar);
679
+ if (indexVar) trimmed = replaceItemMetaVars(trimmed, indexVar, sourceVar);
680
+ return `\${${trimmed}}`;
681
+ });
682
+ return `{\`${replaced}\`}`;
683
+ }
684
+ return text;
685
+ }
686
+
687
+ // lib/server/astro/nodeToAstro.ts
572
688
  function ind(ctx) {
573
689
  return " ".repeat(ctx.indent);
574
690
  }
691
+ function localizeHref(href, ctx) {
692
+ if (!href.startsWith("/") || href.startsWith("//")) return href;
693
+ const { locale, i18nDefaultLocale, slugMappings } = ctx;
694
+ if (!locale || !i18nDefaultLocale) return href;
695
+ if (slugMappings && slugMappings.length > 0) {
696
+ const slugIndex = buildSlugIndex(slugMappings);
697
+ return translatePath(href, locale, i18nDefaultLocale, i18nDefaultLocale, slugIndex);
698
+ } else if (locale !== i18nDefaultLocale) {
699
+ return buildLocalizedPath(href, locale);
700
+ }
701
+ return href;
702
+ }
575
703
  function isStyleMapping2(value) {
576
704
  return typeof value === "object" && value !== null && "_mapping" in value && value._mapping === true;
577
705
  }
578
706
  function isLinkMapping(value) {
579
707
  return typeof value === "object" && value !== null && "_mapping" in value && value._mapping === true;
580
708
  }
709
+ function emitAttrValue(key, value, ctx) {
710
+ if (ctx.cmsMode && /\{\{cms\./.test(value)) {
711
+ const b = ctx.cmsEntryBinding || "entry";
712
+ const w = (expr) => ctx.cmsWrapFn ? `${ctx.cmsWrapFn}(${expr})` : expr;
713
+ const fullMatch = value.match(/^\{\{cms\.([^}]+)\}\}$/);
714
+ if (fullMatch) {
715
+ return `${key}={${w(`${b}.data.${fullMatch[1].trim()}`)}}`;
716
+ }
717
+ const replaced = value.replace(
718
+ /\{\{cms\.([^}]+)\}\}/g,
719
+ (_, fp) => `\${${w(`${b}.data.${fp.trim()}`)}}`
720
+ );
721
+ return `${key}={\`${replaced}\`}`;
722
+ }
723
+ if (ctx.listItemBinding && /\{\{/.test(value)) {
724
+ const fullMatch = value.match(/^\{\{(.+)\}\}$/);
725
+ if (fullMatch) {
726
+ let expr = fullMatch[1].trim();
727
+ expr = rewriteItemVar(expr, ctx.listItemBinding);
728
+ if (ctx.listIndexVar) expr = replaceItemMetaVars(expr, ctx.listIndexVar, ctx.listSourceVar);
729
+ return `${key}={${expr}}`;
730
+ }
731
+ const replaced = value.replace(/\{\{(.+?)\}\}/g, (_, expr) => {
732
+ let trimmed = expr.trim();
733
+ trimmed = rewriteItemVar(trimmed, ctx.listItemBinding);
734
+ if (ctx.listIndexVar) trimmed = replaceItemMetaVars(trimmed, ctx.listIndexVar, ctx.listSourceVar);
735
+ return `\${${trimmed}}`;
736
+ });
737
+ return `${key}={\`${replaced}\`}`;
738
+ }
739
+ return `${key}="${escapeJSX(value)}"`;
740
+ }
581
741
  function isHtmlMapping(value) {
582
742
  return typeof value === "object" && value !== null && "_mapping" in value && value._mapping === true;
583
743
  }
@@ -622,13 +782,13 @@ function mappingToClassListEntries(mapping, property, breakpointPrefix, ctx) {
622
782
  const cls1 = getClassForValue(property, css1, breakpointPrefix);
623
783
  const cls2 = getClassForValue(property, css2, breakpointPrefix);
624
784
  if (cls1 && cls2) {
625
- entries.push(`${propRef} === ${JSON.stringify(coerceValue(val1))} ? '${cls1}' : '${cls2}'`);
785
+ entries.push(`String(${propRef}) === ${JSON.stringify(String(coerceValue(val1)))} ? '${cls1}' : '${cls2}'`);
626
786
  }
627
787
  } else {
628
788
  for (const [val, cssValue] of values) {
629
789
  const cls = getClassForValue(property, cssValue, breakpointPrefix);
630
790
  if (cls) {
631
- entries.push(`${propRef} === ${JSON.stringify(coerceValue(val))} && '${cls}'`);
791
+ entries.push(`String(${propRef}) === ${JSON.stringify(String(coerceValue(val)))} && '${cls}'`);
632
792
  }
633
793
  }
634
794
  }
@@ -663,12 +823,22 @@ function buildClassAndStyleExpression(style, interactiveStyles, elementClass, ct
663
823
  if (Object.keys(dynamicStyles).length > 0 && ctx.isComponentDef) {
664
824
  const styleParts = [];
665
825
  for (const [cssProp, value] of Object.entries(dynamicStyles)) {
666
- const resolved = value.replace(/\{\{(.+?)\}\}/g, (_, expr) => `\${${expr.trim()}}`);
826
+ const resolved = value.replace(/\{\{(.+?)\}\}/g, (_, expr) => {
827
+ let trimmed = expr.trim();
828
+ if (ctx.listItemBinding) trimmed = rewriteItemVar(trimmed, ctx.listItemBinding);
829
+ if (ctx.listIndexVar) trimmed = replaceItemMetaVars(trimmed, ctx.listIndexVar, ctx.listSourceVar);
830
+ return `\${${trimmed}}`;
831
+ });
667
832
  styleParts.push(`${cssProp}: \${${resolved.includes("${") ? resolved.replace(/\$\{(.+?)\}/g, "$1") : `'${resolved}'`}}`);
668
833
  }
669
834
  const entries = [];
670
835
  for (const [cssProp, value] of Object.entries(dynamicStyles)) {
671
- const resolved = value.replace(/\{\{(.+?)\}\}/g, (_, expr) => `\${${expr.trim()}}`);
836
+ const resolved = value.replace(/\{\{(.+?)\}\}/g, (_, expr) => {
837
+ let trimmed = expr.trim();
838
+ if (ctx.listItemBinding) trimmed = rewriteItemVar(trimmed, ctx.listItemBinding);
839
+ if (ctx.listIndexVar) trimmed = replaceItemMetaVars(trimmed, ctx.listIndexVar, ctx.listSourceVar);
840
+ return `\${${trimmed}}`;
841
+ });
672
842
  entries.push(`${cssProp}: ${resolved}`);
673
843
  }
674
844
  styleAttr = ` style={\`${entries.join("; ")}\`}`;
@@ -723,9 +893,20 @@ function resolveTemplate(text, ctx) {
723
893
  }
724
894
  const fullMatch = text.match(/^\{\{(.+)\}\}$/);
725
895
  if (fullMatch) {
726
- return `{${fullMatch[1].trim()}}`;
896
+ let propName = fullMatch[1].trim();
897
+ if (ctx.listItemBinding) propName = rewriteItemVar(propName, ctx.listItemBinding);
898
+ if (ctx.listIndexVar) propName = replaceItemMetaVars(propName, ctx.listIndexVar, ctx.listSourceVar);
899
+ if (ctx.componentProps[propName]?.type === "rich-text") {
900
+ return `<Fragment set:html={${propName}} />`;
901
+ }
902
+ return `{${propName}}`;
727
903
  }
728
- return text.replace(/\{\{(.+?)\}\}/g, (_, expr) => `{${expr.trim()}}`);
904
+ return text.replace(/\{\{(.+?)\}\}/g, (_, expr) => {
905
+ let trimmed = expr.trim();
906
+ if (ctx.listItemBinding) trimmed = rewriteItemVar(trimmed, ctx.listItemBinding);
907
+ if (ctx.listIndexVar) trimmed = replaceItemMetaVars(trimmed, ctx.listIndexVar, ctx.listSourceVar);
908
+ return `{${trimmed}}`;
909
+ });
729
910
  }
730
911
  function hasTemplates(text) {
731
912
  return /\{\{.+?\}\}/.test(text);
@@ -749,11 +930,53 @@ function buildAttributesString(attributes, ctx) {
749
930
  if (hasTemplates(strVal) && ctx.isComponentDef) {
750
931
  const fullMatch = strVal.match(/^\{\{(.+)\}\}$/);
751
932
  if (fullMatch) {
752
- parts.push(`${key}={${fullMatch[1].trim()}}`);
933
+ let expr = fullMatch[1].trim();
934
+ if (ctx.listItemBinding) expr = rewriteItemVar(expr, ctx.listItemBinding);
935
+ if (ctx.listIndexVar) expr = replaceItemMetaVars(expr, ctx.listIndexVar, ctx.listSourceVar);
936
+ const propDef = ctx.componentProps[expr];
937
+ if (propDef && propDef.type === "link") {
938
+ parts.push(`${key}={${expr}?.href ?? "#"}`);
939
+ } else {
940
+ parts.push(`${key}={${expr} || undefined}`);
941
+ }
942
+ } else {
943
+ const resolved = strVal.replace(/\{\{(.+?)\}\}/g, (_, expr) => {
944
+ let trimmed = expr.trim();
945
+ if (ctx.listItemBinding) trimmed = rewriteItemVar(trimmed, ctx.listItemBinding);
946
+ if (ctx.listIndexVar) trimmed = replaceItemMetaVars(trimmed, ctx.listIndexVar, ctx.listSourceVar);
947
+ const pd = ctx.componentProps[trimmed];
948
+ return pd?.type === "link" ? `\${${trimmed}?.href ?? "#"}` : `\${${trimmed}}`;
949
+ });
950
+ parts.push(`${key}={\`${resolved}\`}`);
951
+ }
952
+ } else if (ctx.listItemBinding && hasTemplates(strVal)) {
953
+ const fullMatch = strVal.match(/^\{\{(.+)\}\}$/);
954
+ if (fullMatch) {
955
+ let expr = fullMatch[1].trim();
956
+ expr = rewriteItemVar(expr, ctx.listItemBinding);
957
+ if (ctx.listIndexVar) expr = replaceItemMetaVars(expr, ctx.listIndexVar, ctx.listSourceVar);
958
+ parts.push(`${key}={${expr} || undefined}`);
753
959
  } else {
754
- const resolved = strVal.replace(/\{\{(.+?)\}\}/g, (_, expr) => `\${${expr.trim()}}`);
960
+ const resolved = strVal.replace(/\{\{(.+?)\}\}/g, (_, expr) => {
961
+ let trimmed = expr.trim();
962
+ trimmed = rewriteItemVar(trimmed, ctx.listItemBinding);
963
+ if (ctx.listIndexVar) trimmed = replaceItemMetaVars(trimmed, ctx.listIndexVar, ctx.listSourceVar);
964
+ return `\${${trimmed}}`;
965
+ });
755
966
  parts.push(`${key}={\`${resolved}\`}`);
756
967
  }
968
+ } else if (ctx.cmsMode && /\{\{cms\./.test(strVal)) {
969
+ const b = ctx.cmsEntryBinding || "entry";
970
+ const w = (expr) => ctx.cmsWrapFn ? `${ctx.cmsWrapFn}(${expr})` : expr;
971
+ const fullMatch = strVal.match(/^\{\{cms\.([^}]+)\}\}$/);
972
+ if (fullMatch) {
973
+ parts.push(`${key}={${w(`${b}.data.${fullMatch[1].trim()}`)}}`);
974
+ } else {
975
+ const replaced = strVal.replace(/\{\{cms\.([^}]+)\}\}/g, (_, fieldPath) => {
976
+ return `\${${w(`${b}.data.${fieldPath.trim()}`)}}`;
977
+ });
978
+ parts.push(`${key}={\`${replaced}\`}`);
979
+ }
757
980
  } else {
758
981
  parts.push(`${key}="${escapeJSX(strVal)}"`);
759
982
  }
@@ -768,11 +991,46 @@ function formatPropValue(value) {
768
991
  if (value === null || value === void 0) return `{undefined}`;
769
992
  return `{${JSON.stringify(value)}}`;
770
993
  }
994
+ function resolveI18n(value, ctx) {
995
+ if (ctx.locale && isI18nValue(value)) {
996
+ return resolveI18nValue(value, ctx.locale, DEFAULT_I18N_CONFIG);
997
+ }
998
+ return value;
999
+ }
771
1000
  function nodeToAstro(node, ctx) {
772
1001
  if (node === null || node === void 0) return "";
1002
+ if (typeof node === "object" && !Array.isArray(node) && isI18nValue(node)) {
1003
+ const resolved = resolveI18n(node, ctx);
1004
+ if (typeof resolved === "string") {
1005
+ return `${ind(ctx)}${escapeJSX(resolved)}
1006
+ `;
1007
+ }
1008
+ if (ctx.isComponentDef && isI18nValue(resolved)) {
1009
+ ctx.needsI18nResolver = true;
1010
+ return `${ind(ctx)}{r(${JSON.stringify(resolved)})}
1011
+ `;
1012
+ }
1013
+ return `${ind(ctx)}${String(resolved ?? "")}
1014
+ `;
1015
+ }
773
1016
  if (typeof node === "string") {
1017
+ if (ctx.cmsMode && isTemplateExpression(node) && /\{\{cms\./.test(node)) {
1018
+ const transformed = transformCMSTemplate(node, ctx.cmsEntryBinding || "entry", ctx.cmsRichTextFields, ctx.cmsWrapFn);
1019
+ return `${ind(ctx)}${transformed}
1020
+ `;
1021
+ }
1022
+ if (ctx.listItemBinding && isTemplateExpression(node)) {
1023
+ const transformed = transformItemTemplate(node, ctx.listItemBinding, ctx.listIndexVar, ctx.listSourceVar);
1024
+ return `${ind(ctx)}${transformed}
1025
+ `;
1026
+ }
774
1027
  if (hasTemplates(node) && ctx.isComponentDef) {
775
1028
  return `${ind(ctx)}${resolveTemplate(node, ctx)}
1029
+ `;
1030
+ }
1031
+ if (node.startsWith(RAW_HTML_PREFIX)) {
1032
+ const rawHtml = node.slice(RAW_HTML_PREFIX.length);
1033
+ return `${ind(ctx)}<Fragment set:html={\`${escapeTemplateLiteral(rawHtml)}\`} />
776
1034
  `;
777
1035
  }
778
1036
  return `${ind(ctx)}${escapeJSX(node)}
@@ -787,7 +1045,7 @@ function nodeToAstro(node, ctx) {
787
1045
  for (let i = 0; i < node.length; i++) {
788
1046
  const child = node[i];
789
1047
  const savedPath = [...ctx.elementPath];
790
- ctx.elementPath = [...ctx.elementPath, i];
1048
+ ctx.elementPath = [...ctx.elementPath.slice(0, -1), i];
791
1049
  result += nodeToAstro(child, ctx);
792
1050
  ctx.elementPath = savedPath;
793
1051
  }
@@ -805,16 +1063,116 @@ function nodeToAstro(node, ctx) {
805
1063
  case NODE_TYPE.LINK:
806
1064
  return emitLinkNode(node, ctx);
807
1065
  case NODE_TYPE.LOCALE_LIST:
808
- return emitFallback(ctx);
1066
+ return emitLocaleListNode(node, ctx);
809
1067
  case NODE_TYPE.LIST:
810
1068
  case "cms-list":
811
- return emitFallback(ctx);
1069
+ return emitListNode(node, ctx);
1070
+ case "image":
1071
+ return emitImageTypeNode(node, ctx);
812
1072
  default:
813
1073
  return emitFallback(ctx);
814
1074
  }
815
1075
  }
1076
+ var IMG_TAILWIND_PREFIXES = ["object-", "rounded", "border", "shadow", "[filter", "[transform", "mix-blend"];
1077
+ var IMG_OPACITY_PATTERN = /^opacity-/;
1078
+ var DEFAULT_SIZES2 = "100vw";
1079
+ function splitImageClasses(allClasses) {
1080
+ const imgClasses = [];
1081
+ const pictureClasses = [];
1082
+ for (const cls of allClasses) {
1083
+ const baseCls = cls.includes(":") ? cls.split(":").pop() : cls;
1084
+ if (IMG_TAILWIND_PREFIXES.some((p) => baseCls.startsWith(p)) || IMG_OPACITY_PATTERN.test(baseCls)) {
1085
+ imgClasses.push(cls);
1086
+ } else {
1087
+ pictureClasses.push(cls);
1088
+ }
1089
+ }
1090
+ return { pictureClasses, imgClasses };
1091
+ }
1092
+ function emitImageNode(node, ctx) {
1093
+ const style = node.style;
1094
+ let elementClass = null;
1095
+ if (node.interactiveStyles && node.interactiveStyles.length > 0 || node.generateElementClass) {
1096
+ elementClass = buildElementClass(ctx, node.label);
1097
+ }
1098
+ const { classExpr, styleAttr } = buildClassAndStyleExpression(
1099
+ style,
1100
+ node.interactiveStyles,
1101
+ elementClass,
1102
+ ctx
1103
+ );
1104
+ const attrs = node.attributes || {};
1105
+ const src = attrs.src;
1106
+ const alt = attrs.alt;
1107
+ const loading = attrs.loading;
1108
+ const fetchpriority = attrs.fetchpriority;
1109
+ let width = attrs.width;
1110
+ let height = attrs.height;
1111
+ const sizes = attrs.sizes;
1112
+ const metadata = src ? ctx.imageMetadataMap?.get(String(src)) : void 0;
1113
+ if (metadata) {
1114
+ if (width === void 0 && metadata.width) width = metadata.width;
1115
+ if (height === void 0 && metadata.height) height = metadata.height;
1116
+ }
1117
+ const sizesValue = sizes || DEFAULT_SIZES2;
1118
+ const imageSpecificKeys = /* @__PURE__ */ new Set(["src", "alt", "loading", "width", "height", "sizes", "srcset", "fetchpriority"]);
1119
+ const otherAttrs = {};
1120
+ if (node.attributes) {
1121
+ for (const [k, v] of Object.entries(node.attributes)) {
1122
+ if (!imageSpecificKeys.has(k)) otherAttrs[k] = v;
1123
+ }
1124
+ }
1125
+ const otherAttrsStr = buildAttributesString(otherAttrs, ctx);
1126
+ let imgAttrs = "";
1127
+ if (src) imgAttrs += ` ${emitAttrValue("src", String(src), ctx)}`;
1128
+ if (alt !== void 0) imgAttrs += ` ${emitAttrValue("alt", String(alt), ctx)}`;
1129
+ if (fetchpriority) imgAttrs += ` fetchpriority="${escapeJSX(String(fetchpriority))}"`;
1130
+ if (loading) imgAttrs += ` loading="${escapeJSX(String(loading))}"`;
1131
+ if (width !== void 0) imgAttrs += ` width="${escapeJSX(String(width))}"`;
1132
+ if (height !== void 0) imgAttrs += ` height="${escapeJSX(String(height))}"`;
1133
+ let blurStyle = "";
1134
+ if (metadata?.blurHash) {
1135
+ blurStyle = ` style="background-image: url(${escapeJSX(metadata.blurHash)}); background-size: cover;" onload="this.style.backgroundImage=''"`;
1136
+ }
1137
+ const ifExpr = emitIfOpen(node, ctx);
1138
+ const ifClose = emitIfClose(node, ctx);
1139
+ if (metadata?.avifSrcset) {
1140
+ const classMatch = classExpr.match(/class="([^"]*)"/);
1141
+ const classListMatch = classExpr.match(/class:list={\[(.+)\]}/);
1142
+ if (classListMatch) {
1143
+ return `${ifExpr}${ind(ctx)}<picture${classExpr}${styleAttr}>
1144
+ ${ind(ctx)} <source type="image/avif" srcset="${escapeJSX(metadata.avifSrcset)}" sizes="${escapeJSX(sizesValue)}" />
1145
+ ${ind(ctx)} <source type="image/webp" srcset="${escapeJSX(metadata.srcset)}" sizes="${escapeJSX(sizesValue)}" />
1146
+ ${ind(ctx)} <img${imgAttrs}${blurStyle}${otherAttrsStr} />
1147
+ ${ind(ctx)}</picture>
1148
+ ${ifClose}`;
1149
+ }
1150
+ const allClasses = classMatch ? classMatch[1].split(/\s+/).filter(Boolean) : [];
1151
+ const { pictureClasses, imgClasses } = splitImageClasses(allClasses);
1152
+ const pictureClassAttr = pictureClasses.length > 0 ? ` class="${pictureClasses.join(" ")}"` : "";
1153
+ const imgClassAttr = imgClasses.length > 0 ? ` class="${imgClasses.join(" ")}"` : "";
1154
+ return `${ifExpr}${ind(ctx)}<picture${pictureClassAttr}${styleAttr}>
1155
+ ${ind(ctx)} <source type="image/avif" srcset="${escapeJSX(metadata.avifSrcset)}" sizes="${escapeJSX(sizesValue)}" />
1156
+ ${ind(ctx)} <source type="image/webp" srcset="${escapeJSX(metadata.srcset)}" sizes="${escapeJSX(sizesValue)}" />
1157
+ ${ind(ctx)} <img${imgClassAttr}${imgAttrs}${blurStyle}${otherAttrsStr} />
1158
+ ${ind(ctx)}</picture>
1159
+ ${ifClose}`;
1160
+ }
1161
+ if (metadata?.srcset) {
1162
+ imgAttrs += ` srcset="${escapeJSX(metadata.srcset)}"`;
1163
+ imgAttrs += ` sizes="${escapeJSX(sizesValue)}"`;
1164
+ }
1165
+ return `${ifExpr}${ind(ctx)}<img${classExpr}${styleAttr}${imgAttrs}${blurStyle}${otherAttrsStr} />
1166
+ ${ifClose}`;
1167
+ }
816
1168
  function emitHtmlNode(node, ctx) {
817
1169
  let tag = node.tag;
1170
+ if (tag && /^[A-Z]/.test(tag)) {
1171
+ tag = tag.toLowerCase();
1172
+ }
1173
+ if (tag === "img" && ctx.imageMetadataMap) {
1174
+ return emitImageNode(node, ctx);
1175
+ }
818
1176
  const label = node.label;
819
1177
  const style = node.style;
820
1178
  let isDynamic = false;
@@ -854,16 +1212,40 @@ function emitComponentInstance(node, ctx) {
854
1212
  const ifExpr = emitIfOpen(node, ctx);
855
1213
  const propParts = [];
856
1214
  if (node.props) {
857
- for (const [key, value] of Object.entries(node.props)) {
1215
+ for (const [key, rawValue] of Object.entries(node.props)) {
858
1216
  if (key === "children") continue;
1217
+ const value = resolveI18n(rawValue, ctx);
859
1218
  if (typeof value === "string" && hasTemplates(value) && ctx.isComponentDef) {
860
1219
  const fullMatch = value.match(/^\{\{(.+)\}\}$/);
861
1220
  if (fullMatch) {
862
- propParts.push(`${key}={${fullMatch[1].trim()}}`);
1221
+ let expr = fullMatch[1].trim();
1222
+ if (ctx.listItemBinding) expr = rewriteItemVar(expr, ctx.listItemBinding);
1223
+ if (ctx.listIndexVar) expr = replaceItemMetaVars(expr, ctx.listIndexVar, ctx.listSourceVar);
1224
+ propParts.push(`${key}={${expr}}`);
863
1225
  } else {
864
- const resolved = value.replace(/\{\{(.+?)\}\}/g, (_, expr) => `\${${expr.trim()}}`);
1226
+ const resolved = value.replace(/\{\{(.+?)\}\}/g, (_, expr) => {
1227
+ let trimmed = expr.trim();
1228
+ if (ctx.listItemBinding) trimmed = rewriteItemVar(trimmed, ctx.listItemBinding);
1229
+ if (ctx.listIndexVar) trimmed = replaceItemMetaVars(trimmed, ctx.listIndexVar, ctx.listSourceVar);
1230
+ return `\${${trimmed}}`;
1231
+ });
865
1232
  propParts.push(`${key}={\`${resolved}\`}`);
866
1233
  }
1234
+ } else if (typeof value === "string" && ctx.cmsMode && /\{\{cms\./.test(value)) {
1235
+ const b = ctx.cmsEntryBinding || "entry";
1236
+ const w = (expr) => ctx.cmsWrapFn ? `${ctx.cmsWrapFn}(${expr})` : expr;
1237
+ const fullMatch = value.match(/^\{\{cms\.([^}]+)\}\}$/);
1238
+ if (fullMatch) {
1239
+ propParts.push(`${key}={${w(`${b}.data.${fullMatch[1].trim()}`)}}`);
1240
+ } else {
1241
+ const replaced = value.replace(/\{\{cms\.([^}]+)\}\}/g, (_, fieldPath) => {
1242
+ return `\${${w(`${b}.data.${fieldPath.trim()}`)}}`;
1243
+ });
1244
+ propParts.push(`${key}={\`${replaced}\`}`);
1245
+ }
1246
+ } else if (ctx.isComponentDef && isI18nValue(value)) {
1247
+ ctx.needsI18nResolver = true;
1248
+ propParts.push(`${key}={r(${JSON.stringify(value)})}`);
867
1249
  } else {
868
1250
  propParts.push(`${key}=${formatPropValue(value)}`);
869
1251
  }
@@ -946,26 +1328,55 @@ function emitLinkNode(node, ctx) {
946
1328
  finalClassExpr = ' class="olink"';
947
1329
  }
948
1330
  }
1331
+ const resolvedHref = resolveI18n(node.href, ctx);
1332
+ const nodeHref = resolvedHref;
949
1333
  let hrefAttr;
950
- if (isLinkMapping(node.href)) {
1334
+ if (isLinkMapping(nodeHref)) {
951
1335
  if (ctx.isComponentDef) {
952
- const propRef = node.href.prop;
953
- hrefAttr = ` href={${propRef}.href}`;
1336
+ const propRef = nodeHref.prop;
1337
+ hrefAttr = ` href={${propRef}?.href ?? "#"}`;
954
1338
  } else {
955
1339
  hrefAttr = ' href="#"';
956
1340
  }
957
1341
  } else {
958
- const href = typeof node.href === "string" ? node.href : "#";
1342
+ const href = typeof nodeHref === "string" ? nodeHref : "#";
959
1343
  if (hasTemplates(href) && ctx.isComponentDef) {
960
1344
  const fullMatch = href.match(/^\{\{(.+)\}\}$/);
961
1345
  if (fullMatch) {
962
- hrefAttr = ` href={${fullMatch[1].trim()}}`;
1346
+ let expr = fullMatch[1].trim();
1347
+ if (ctx.listItemBinding) expr = rewriteItemVar(expr, ctx.listItemBinding);
1348
+ if (ctx.listIndexVar) expr = replaceItemMetaVars(expr, ctx.listIndexVar, ctx.listSourceVar);
1349
+ const propDef = ctx.componentProps[expr];
1350
+ if (propDef && propDef.type === "link") {
1351
+ hrefAttr = ` href={${expr}?.href ?? "#"}`;
1352
+ } else {
1353
+ hrefAttr = ` href={${expr}}`;
1354
+ }
963
1355
  } else {
964
- const resolved = href.replace(/\{\{(.+?)\}\}/g, (_, expr) => `\${${expr.trim()}}`);
1356
+ const resolved = href.replace(/\{\{(.+?)\}\}/g, (_, expr) => {
1357
+ let trimmed = expr.trim();
1358
+ if (ctx.listItemBinding) trimmed = rewriteItemVar(trimmed, ctx.listItemBinding);
1359
+ if (ctx.listIndexVar) trimmed = replaceItemMetaVars(trimmed, ctx.listIndexVar, ctx.listSourceVar);
1360
+ const pd = ctx.componentProps[trimmed];
1361
+ return pd?.type === "link" ? `\${${trimmed}?.href ?? "#"}` : `\${${trimmed}}`;
1362
+ });
965
1363
  hrefAttr = ` href={\`${resolved}\`}`;
966
1364
  }
1365
+ } else if (ctx.cmsMode && /\{\{cms\./.test(href)) {
1366
+ const b = ctx.cmsEntryBinding || "entry";
1367
+ const w = (expr) => ctx.cmsWrapFn ? `${ctx.cmsWrapFn}(${expr})` : expr;
1368
+ const fullMatch = href.match(/^\{\{cms\.([^}]+)\}\}$/);
1369
+ if (fullMatch) {
1370
+ hrefAttr = ` href={${w(`${b}.data.${fullMatch[1].trim()}`)}}`;
1371
+ } else {
1372
+ const replaced = href.replace(/\{\{cms\.([^}]+)\}\}/g, (_, fieldPath) => {
1373
+ return `\${${w(`${b}.data.${fieldPath.trim()}`)}}`;
1374
+ });
1375
+ hrefAttr = ` href={\`${replaced}\`}`;
1376
+ }
967
1377
  } else {
968
- hrefAttr = ` href="${escapeJSX(href)}"`;
1378
+ const localizedHref = localizeHref(href, ctx);
1379
+ hrefAttr = ` href="${escapeJSX(localizedHref)}"`;
969
1380
  }
970
1381
  }
971
1382
  const attrs = buildAttributesString(node.attributes, ctx);
@@ -978,6 +1389,234 @@ ${emitIfClose(node, ctx)}`;
978
1389
  ` + children + `${ind(ctx)}</a>
979
1390
  ${emitIfClose(node, ctx)}`;
980
1391
  }
1392
+ function emitImageTypeNode(node, ctx) {
1393
+ const style = node.style;
1394
+ let elementClass = null;
1395
+ if (node.interactiveStyles && node.interactiveStyles.length > 0 || node.generateElementClass) {
1396
+ elementClass = buildElementClass(ctx, node.label);
1397
+ }
1398
+ const { classExpr, styleAttr } = buildClassAndStyleExpression(
1399
+ style,
1400
+ node.interactiveStyles,
1401
+ elementClass,
1402
+ ctx
1403
+ );
1404
+ const src = node.src;
1405
+ const alt = node.alt;
1406
+ let imgAttrs = "";
1407
+ if (src) imgAttrs += ` src="${escapeJSX(String(src))}"`;
1408
+ if (alt !== void 0) imgAttrs += ` alt="${escapeJSX(String(alt))}"`;
1409
+ const metadata = src ? ctx.imageMetadataMap?.get(String(src)) : void 0;
1410
+ if (metadata) {
1411
+ let width = metadata.width;
1412
+ let height = metadata.height;
1413
+ if (width !== void 0) imgAttrs += ` width="${width}"`;
1414
+ if (height !== void 0) imgAttrs += ` height="${height}"`;
1415
+ let blurStyle = "";
1416
+ if (metadata.blurHash) {
1417
+ blurStyle = ` style="background-image: url(${escapeJSX(metadata.blurHash)}); background-size: cover;" onload="this.style.backgroundImage=''"`;
1418
+ }
1419
+ const sizesValue = DEFAULT_SIZES2;
1420
+ if (metadata.avifSrcset) {
1421
+ const classMatch = classExpr.match(/class="([^"]*)"/);
1422
+ const allClasses = classMatch ? classMatch[1].split(/\s+/).filter(Boolean) : [];
1423
+ const { pictureClasses, imgClasses } = splitImageClasses(allClasses);
1424
+ const pictureClassAttr = pictureClasses.length > 0 ? ` class="${pictureClasses.join(" ")}"` : "";
1425
+ const imgClassAttr = imgClasses.length > 0 ? ` class="${imgClasses.join(" ")}"` : "";
1426
+ return `${ind(ctx)}<picture${pictureClassAttr}${styleAttr}>
1427
+ ${ind(ctx)} <source type="image/avif" srcset="${escapeJSX(metadata.avifSrcset)}" sizes="${escapeJSX(sizesValue)}" />
1428
+ ${ind(ctx)} <source type="image/webp" srcset="${escapeJSX(metadata.srcset)}" sizes="${escapeJSX(sizesValue)}" />
1429
+ ${ind(ctx)} <img${imgClassAttr}${imgAttrs}${blurStyle} />
1430
+ ${ind(ctx)}</picture>
1431
+ `;
1432
+ }
1433
+ if (metadata.srcset) {
1434
+ imgAttrs += ` srcset="${escapeJSX(metadata.srcset)}"`;
1435
+ imgAttrs += ` sizes="${escapeJSX(sizesValue)}"`;
1436
+ }
1437
+ }
1438
+ return `${ind(ctx)}<img${classExpr}${styleAttr}${imgAttrs} />
1439
+ `;
1440
+ }
1441
+ function emitListNode(node, ctx) {
1442
+ const sourceType = node.sourceType || "prop";
1443
+ const itemAs = node.itemAs || "item";
1444
+ if (sourceType === "collection") {
1445
+ return emitCollectionListNode(node, ctx);
1446
+ }
1447
+ if (!ctx.isComponentDef && !ctx.listItemBinding) {
1448
+ return emitFallback(ctx);
1449
+ }
1450
+ let source = node.source || "items";
1451
+ const templateMatch = source.match(/^\{\{(.+)\}\}$/);
1452
+ if (templateMatch) {
1453
+ source = templateMatch[1].trim();
1454
+ }
1455
+ let mapSource = source;
1456
+ if (node.offset && node.limit) {
1457
+ mapSource = `${source}.slice(${node.offset}, ${node.offset + node.limit})`;
1458
+ } else if (node.offset) {
1459
+ mapSource = `${source}.slice(${node.offset})`;
1460
+ } else if (node.limit) {
1461
+ mapSource = `${source}.slice(0, ${node.limit})`;
1462
+ }
1463
+ const indexVar = `${itemAs}Index`;
1464
+ const innerCtx = {
1465
+ ...ctx,
1466
+ indent: ctx.indent + 1,
1467
+ listItemBinding: itemAs,
1468
+ listIndexVar: indexVar,
1469
+ listSourceVar: mapSource,
1470
+ elementPath: [...ctx.elementPath, 0]
1471
+ };
1472
+ const children = node.children ? node.children.map((child, i) => {
1473
+ const childCtx = { ...innerCtx, elementPath: [...ctx.elementPath, i] };
1474
+ const out = nodeToAstro(child, childCtx);
1475
+ if (childCtx.needsI18nResolver) ctx.needsI18nResolver = true;
1476
+ return out;
1477
+ }).join("") : "";
1478
+ return `${ind(ctx)}{${mapSource}.map((${itemAs}, ${indexVar}) => (
1479
+ ` + children + `${ind(ctx)}))}
1480
+ `;
1481
+ }
1482
+ function emitCollectionListNode(node, ctx) {
1483
+ const source = node.source || "";
1484
+ const itemAs = node.itemAs || singularize(source);
1485
+ if (!ctx.frontmatterLines) ctx.frontmatterLines = [];
1486
+ if (!ctx.astroImports) ctx.astroImports = /* @__PURE__ */ new Set();
1487
+ ctx.astroImports.add("getCollection");
1488
+ const collectionVar = `${source}List`;
1489
+ let queryChain = `await getCollection('${source}')`;
1490
+ if (node.filter) {
1491
+ if (typeof node.filter === "object" && !Array.isArray(node.filter) && "field" in node.filter) {
1492
+ const f = node.filter;
1493
+ const op = f.operator || "eq";
1494
+ if (op === "eq") {
1495
+ queryChain += `.then(items => items.filter(e => e.data.${f.field} === ${JSON.stringify(f.value)}))`;
1496
+ }
1497
+ }
1498
+ }
1499
+ if (node.sort) {
1500
+ const sortConfig = Array.isArray(node.sort) ? node.sort[0] : node.sort;
1501
+ if (sortConfig) {
1502
+ const order = sortConfig.order === "desc" ? -1 : 1;
1503
+ queryChain += `.then(items => items.sort((a, b) => a.data.${sortConfig.field} > b.data.${sortConfig.field} ? ${order} : ${-order}))`;
1504
+ }
1505
+ }
1506
+ if (node.offset || node.limit) {
1507
+ const start = node.offset || 0;
1508
+ const end = node.limit ? start + node.limit : void 0;
1509
+ queryChain += `.then(items => items.slice(${start}${end !== void 0 ? `, ${end}` : ""}))`;
1510
+ }
1511
+ ctx.frontmatterLines.push(`const ${collectionVar} = ${queryChain};`);
1512
+ const indexVar = `${itemAs}Index`;
1513
+ const innerCtx = {
1514
+ ...ctx,
1515
+ indent: ctx.indent + 1,
1516
+ listItemBinding: itemAs,
1517
+ listIndexVar: indexVar,
1518
+ listSourceVar: collectionVar,
1519
+ cmsMode: true,
1520
+ cmsEntryBinding: itemAs,
1521
+ elementPath: [...ctx.elementPath, 0]
1522
+ };
1523
+ const children = node.children ? node.children.map((child, i) => {
1524
+ const childCtx = { ...innerCtx, elementPath: [...ctx.elementPath, i] };
1525
+ const out = nodeToAstro(child, childCtx);
1526
+ if (childCtx.needsI18nResolver) ctx.needsI18nResolver = true;
1527
+ return out;
1528
+ }).join("") : "";
1529
+ return `${ind(ctx)}{${collectionVar}.map((${itemAs}, ${indexVar}) => (
1530
+ ` + children + `${ind(ctx)}))}
1531
+ `;
1532
+ }
1533
+ function emitLocaleListNode(node, ctx) {
1534
+ if (!ctx.i18nConfig || !ctx.currentPageSlugMap) {
1535
+ return emitFallback(ctx);
1536
+ }
1537
+ const i18nConfig = ctx.i18nConfig;
1538
+ const slugMap = ctx.currentPageSlugMap;
1539
+ const showCurrent = node.showCurrent !== false;
1540
+ const showSeparator = node.showSeparator !== false;
1541
+ const showFlag = node.showFlag !== false;
1542
+ const displayType = node.displayType || "nativeName";
1543
+ const style = node.style;
1544
+ let elementClass = null;
1545
+ if (node.interactiveStyles && node.interactiveStyles.length > 0 || node.generateElementClass) {
1546
+ elementClass = buildElementClass(ctx, node.label);
1547
+ }
1548
+ const { classExpr: containerClassExpr, styleAttr: containerStyleAttr } = buildClassAndStyleExpression(
1549
+ style,
1550
+ node.interactiveStyles,
1551
+ elementClass,
1552
+ ctx
1553
+ );
1554
+ const itemStyle = node.itemStyle;
1555
+ const itemResult = itemStyle ? responsiveStylesToTailwind(itemStyle, ctx.breakpoints) : { classes: [], dynamicStyles: {} };
1556
+ const itemClasses = itemResult.classes;
1557
+ const activeItemStyle = node.activeItemStyle;
1558
+ const activeResult = activeItemStyle ? responsiveStylesToTailwind(activeItemStyle, ctx.breakpoints) : { classes: [], dynamicStyles: {} };
1559
+ const activeItemClasses = [...itemClasses, ...activeResult.classes];
1560
+ const separatorStyle = node.separatorStyle;
1561
+ const sepResult = separatorStyle ? responsiveStylesToTailwind(separatorStyle, ctx.breakpoints) : { classes: [], dynamicStyles: {} };
1562
+ const separatorClasses = sepResult.classes;
1563
+ const localeIconMap = /* @__PURE__ */ new Map();
1564
+ for (const localeConfig of i18nConfig.locales) {
1565
+ if (localeConfig.icon) {
1566
+ localeIconMap.set(localeConfig.code, localeConfig.icon);
1567
+ }
1568
+ }
1569
+ const flagStyle = node.flagStyle;
1570
+ const flagResult = flagStyle ? responsiveStylesToTailwind(flagStyle, ctx.breakpoints) : { classes: [], dynamicStyles: {} };
1571
+ const flagClasses = flagResult.classes;
1572
+ const links = [];
1573
+ const currentLocale = ctx.locale || i18nConfig.defaultLocale;
1574
+ for (const localeConfig of i18nConfig.locales) {
1575
+ const code = localeConfig.code;
1576
+ const isCurrent = code === currentLocale;
1577
+ if (!showCurrent && isCurrent) continue;
1578
+ const path = slugMap[code] || "/";
1579
+ const classes = isCurrent ? activeItemClasses : itemClasses;
1580
+ const classAttr = classes.length > 0 ? ` class="${classes.join(" ")}"` : "";
1581
+ const currentAttr = isCurrent ? ' data-current="true"' : ' data-current="false"';
1582
+ const hreflangAttr = ` hreflang="${localeConfig.langTag}"`;
1583
+ let displayText;
1584
+ switch (displayType) {
1585
+ case "code":
1586
+ displayText = code.toUpperCase();
1587
+ break;
1588
+ case "name":
1589
+ displayText = localeConfig.name;
1590
+ break;
1591
+ case "nativeName":
1592
+ default:
1593
+ displayText = localeConfig.nativeName;
1594
+ break;
1595
+ }
1596
+ let linkContent = "";
1597
+ const localeIcon = localeIconMap.get(code);
1598
+ if (showFlag && localeIcon) {
1599
+ const flagClassAttr = flagClasses.length > 0 ? ` class="${flagClasses.join(" ")}"` : "";
1600
+ linkContent += `<img src="${escapeJSX(localeIcon)}" alt="${escapeJSX(localeConfig.nativeName)} flag"${flagClassAttr}>`;
1601
+ }
1602
+ linkContent += `<div>${escapeJSX(displayText)}</div>`;
1603
+ links.push(`${ind(ctx)} <a href="${escapeJSX(path)}"${hreflangAttr}${currentAttr} data-locale="${escapeJSX(code)}"${classAttr}>${linkContent}</a>`);
1604
+ }
1605
+ let linksContent;
1606
+ if (showSeparator && links.length > 1) {
1607
+ const sepClassAttr = separatorClasses.length > 0 ? ` class="${separatorClasses.join(" ")}"` : "";
1608
+ linksContent = links.join(`
1609
+ ${ind(ctx)} <span${sepClassAttr}></span>
1610
+ `);
1611
+ } else {
1612
+ linksContent = links.join("\n");
1613
+ }
1614
+ const attrs = buildAttributesString(node.attributes, ctx);
1615
+ return `${ind(ctx)}<div data-locale-list="true"${containerClassExpr}${containerStyleAttr}${attrs}>
1616
+ ` + linksContent + `
1617
+ ${ind(ctx)}</div>
1618
+ `;
1619
+ }
981
1620
  function emitFallback(ctx) {
982
1621
  const pathKey = ctx.elementPath.join(".");
983
1622
  const ssrHtml = ctx.ssrFallbacks.get(pathKey);
@@ -997,14 +1636,43 @@ function emitIfOpen(node, ctx) {
997
1636
  `;
998
1637
  }
999
1638
  if (typeof ifValue === "object" && ifValue._mapping && ctx.isComponentDef) {
1000
- return `${ind(ctx)}{${ifValue.prop} && (
1639
+ const trueValues = Object.entries(ifValue.values).filter(([, v]) => v === true).map(([k]) => `'${k}'`);
1640
+ if (trueValues.length === 0) return `${ind(ctx)}{/* hidden */}
1641
+ `;
1642
+ if (trueValues.length === 1) {
1643
+ return `${ind(ctx)}{${ifValue.prop} === ${trueValues[0]} && (
1644
+ `;
1645
+ }
1646
+ return `${ind(ctx)}{[${trueValues.join(", ")}].includes(${ifValue.prop}) && (
1001
1647
  `;
1002
1648
  }
1003
- if (typeof ifValue === "string" && ctx.isComponentDef) {
1004
- const fullMatch = ifValue.match(/^\{\{(.+)\}\}$/);
1005
- const expr = fullMatch ? fullMatch[1].trim() : ifValue.replace(/\{\{(.+?)\}\}/g, "$1");
1006
- return `${ind(ctx)}{${expr} && (
1649
+ if (typeof ifValue === "string") {
1650
+ if (ctx.cmsMode && ifValue.includes("{{cms.")) {
1651
+ const match = ifValue.match(/^\{\{cms\.([^}]+)\}\}$/);
1652
+ if (match) {
1653
+ const binding = ctx.cmsEntryBinding || "entry";
1654
+ return `${ind(ctx)}{${binding}.data.${match[1].trim()} && (
1007
1655
  `;
1656
+ }
1657
+ }
1658
+ if (ctx.listItemBinding && /\{\{/.test(ifValue)) {
1659
+ const match = ifValue.match(/^\{\{([^}]+)\}\}$/);
1660
+ if (match) {
1661
+ let expr = match[1].trim();
1662
+ expr = rewriteItemVar(expr, ctx.listItemBinding);
1663
+ if (ctx.listIndexVar) expr = replaceItemMetaVars(expr, ctx.listIndexVar, ctx.listSourceVar);
1664
+ return `${ind(ctx)}{${expr} && (
1665
+ `;
1666
+ }
1667
+ }
1668
+ if (ctx.isComponentDef) {
1669
+ const fullMatch = ifValue.match(/^\{\{(.+)\}\}$/);
1670
+ let expr = fullMatch ? fullMatch[1].trim() : ifValue.replace(/\{\{(.+?)\}\}/g, "$1");
1671
+ if (ctx.listItemBinding) expr = rewriteItemVar(expr, ctx.listItemBinding);
1672
+ if (ctx.listIndexVar) expr = replaceItemMetaVars(expr, ctx.listIndexVar, ctx.listSourceVar);
1673
+ return `${ind(ctx)}{${expr} && (
1674
+ `;
1675
+ }
1008
1676
  }
1009
1677
  return "";
1010
1678
  }
@@ -1012,22 +1680,40 @@ function emitIfClose(node, ctx) {
1012
1680
  const ifValue = node.if;
1013
1681
  if (ifValue === void 0 || ifValue === true) return "";
1014
1682
  if (typeof ifValue === "boolean") return "";
1015
- if (typeof ifValue === "object" && ifValue._mapping && ctx.isComponentDef || typeof ifValue === "string" && ctx.isComponentDef) {
1683
+ if (typeof ifValue === "object" && ifValue._mapping && ctx.isComponentDef) {
1016
1684
  return `${ind(ctx)})}
1017
1685
  `;
1018
1686
  }
1687
+ if (typeof ifValue === "string") {
1688
+ const hasCmsCondition = ctx.cmsMode && ifValue.includes("{{cms.");
1689
+ const hasItemCondition = ctx.listItemBinding && /\{\{/.test(ifValue);
1690
+ if (hasCmsCondition || hasItemCondition || ctx.isComponentDef) {
1691
+ return `${ind(ctx)})}
1692
+ `;
1693
+ }
1694
+ }
1019
1695
  return "";
1020
1696
  }
1021
1697
  function emitChildren(children, ctx) {
1022
1698
  if (!children) return "";
1023
1699
  const innerCtx = { ...ctx, indent: ctx.indent + 1, elementPath: [...ctx.elementPath] };
1024
1700
  if (typeof children === "string") {
1025
- return nodeToAstro(children, innerCtx);
1701
+ const out2 = nodeToAstro(children, innerCtx);
1702
+ if (innerCtx.needsI18nResolver) ctx.needsI18nResolver = true;
1703
+ return out2;
1026
1704
  }
1027
1705
  if (Array.isArray(children)) {
1028
- return nodeToAstro(children, innerCtx);
1706
+ let result = "";
1707
+ for (let i = 0; i < children.length; i++) {
1708
+ const childCtx = { ...innerCtx, elementPath: [...ctx.elementPath, i] };
1709
+ result += nodeToAstro(children[i], childCtx);
1710
+ if (childCtx.needsI18nResolver) ctx.needsI18nResolver = true;
1711
+ }
1712
+ return result;
1029
1713
  }
1030
- return nodeToAstro(children, innerCtx);
1714
+ const out = nodeToAstro(children, innerCtx);
1715
+ if (innerCtx.needsI18nResolver) ctx.needsI18nResolver = true;
1716
+ return out;
1031
1717
  }
1032
1718
 
1033
1719
  // lib/server/astro/componentEmitter.ts
@@ -1073,7 +1759,7 @@ function formatDefault(def) {
1073
1759
  }
1074
1760
  return JSON.stringify(val);
1075
1761
  }
1076
- function emitAstroComponent(name, def, allComponents, breakpoints = DEFAULT_BREAKPOINTS) {
1762
+ function emitAstroComponent(name, def, allComponents, breakpoints = DEFAULT_BREAKPOINTS, defaultLocale = "en") {
1077
1763
  const comp = def.component;
1078
1764
  const propDefs = comp.interface || {};
1079
1765
  const structure = comp.structure;
@@ -1090,25 +1776,22 @@ function emitAstroComponent(name, def, allComponents, breakpoints = DEFAULT_BREA
1090
1776
  elementPath: [0],
1091
1777
  fileType: "component",
1092
1778
  fileName: name,
1093
- breakpoints
1779
+ breakpoints,
1780
+ defaultLocale
1094
1781
  };
1095
1782
  const templateBody = nodeToAstro(structure, ctx);
1096
- const frontmatter = buildFrontmatter(name, propDefs, ctx.imports, ctx.dynamicTags);
1783
+ const frontmatter = buildFrontmatter(name, propDefs, ctx.imports, ctx.dynamicTags, ctx.needsI18nResolver ? defaultLocale : void 0);
1097
1784
  const styleSection = comp.css ? `
1098
1785
  <style>
1099
1786
  ${comp.css}
1100
1787
  </style>
1101
1788
  ` : "";
1102
- const scriptSection = comp.javascript ? `
1103
- <script>
1104
- ${comp.javascript}
1105
- </script>
1106
- ` : "";
1789
+ const scriptSection = comp.javascript ? buildScriptSection(comp.javascript, comp, propDefs) : "";
1107
1790
  return `---
1108
1791
  ${frontmatter}---
1109
1792
  ${templateBody}${styleSection}${scriptSection}`;
1110
1793
  }
1111
- function buildFrontmatter(componentName, propDefs, imports, dynamicTags) {
1794
+ function buildFrontmatter(componentName, propDefs, imports, dynamicTags, i18nDefaultLocale) {
1112
1795
  const lines = [];
1113
1796
  for (const imp of Array.from(imports).sort()) {
1114
1797
  lines.push(`import ${imp} from './${imp}.astro';`);
@@ -1131,6 +1814,8 @@ function buildFrontmatter(componentName, propDefs, imports, dynamicTags) {
1131
1814
  const defaultVal = formatDefault(propDef);
1132
1815
  if (defaultVal !== null) {
1133
1816
  destructParts.push(`${propName} = ${defaultVal}`);
1817
+ } else if (propDef.type === "link") {
1818
+ destructParts.push(`${propName} = { href: "#" }`);
1134
1819
  } else {
1135
1820
  destructParts.push(propName);
1136
1821
  }
@@ -1153,6 +1838,16 @@ function buildFrontmatter(componentName, propDefs, imports, dynamicTags) {
1153
1838
  lines.push(`const ${varName} = \`${templateExpr}\`;`);
1154
1839
  }
1155
1840
  }
1841
+ if (i18nDefaultLocale) {
1842
+ lines.push("");
1843
+ lines.push(`const r = (v: any) => {`);
1844
+ lines.push(` if (v && typeof v === 'object' && v._i18n) {`);
1845
+ lines.push(` const locale = Astro.currentLocale ?? '${i18nDefaultLocale}';`);
1846
+ lines.push(` return v[locale] ?? v['${i18nDefaultLocale}'] ?? Object.values(v).find((s: any) => typeof s === 'string' && s !== '') ?? '';`);
1847
+ lines.push(` }`);
1848
+ lines.push(` return v ?? '';`);
1849
+ lines.push(`};`);
1850
+ }
1156
1851
  if (lines.length > 0) lines.push("");
1157
1852
  return lines.join("\n");
1158
1853
  }
@@ -1164,12 +1859,56 @@ ${comp.css}
1164
1859
  </style>
1165
1860
  `;
1166
1861
  if (comp.javascript) content += `
1167
- <script>
1862
+ <script is:inline>
1168
1863
  ${comp.javascript}
1169
1864
  </script>
1170
1865
  `;
1171
1866
  return content;
1172
1867
  }
1868
+ function transformDefineVarsJS(js, varNames) {
1869
+ let result = js;
1870
+ result = result.replace(
1871
+ /^\s*(const|let|var)\s+\{([^}]+)\}\s*=\s*props\s*;?\s*$/gm,
1872
+ (match, _keyword, inner) => {
1873
+ const names = inner.split(",").map((s) => s.trim()).filter(Boolean);
1874
+ if (names.every((n) => varNames.includes(n))) return "";
1875
+ return match;
1876
+ }
1877
+ );
1878
+ const sorted = [...varNames].sort((a, b) => b.length - a.length);
1879
+ for (const name of sorted) {
1880
+ result = result.replace(new RegExp(`props\\.${name}\\b`, "g"), name);
1881
+ }
1882
+ for (const name of varNames) {
1883
+ result = result.replace(
1884
+ new RegExp(`^\\s*(var|let|const)\\s+${name}\\s*=[^;]*;?\\s*$`, "gm"),
1885
+ ""
1886
+ );
1887
+ }
1888
+ return result;
1889
+ }
1890
+ function buildScriptSection(js, comp, propDefs) {
1891
+ const elInit = "const el = document.currentScript.previousElementSibling;";
1892
+ if (comp.defineVars) {
1893
+ const vars = comp.defineVars === true ? Object.keys(propDefs).filter((k) => k !== "children") : comp.defineVars;
1894
+ if (vars.length > 0) {
1895
+ const transformedJS = transformDefineVarsJS(js, vars);
1896
+ const defineVarsObj = `{ ${vars.join(", ")} }`;
1897
+ return `
1898
+ <script define:vars={${defineVarsObj}}>
1899
+ ${elInit}
1900
+ ${transformedJS}
1901
+ </script>
1902
+ `;
1903
+ }
1904
+ }
1905
+ return `
1906
+ <script is:inline>
1907
+ ${elInit}
1908
+ ${js}
1909
+ </script>
1910
+ `;
1911
+ }
1173
1912
 
1174
1913
  // lib/server/astro/pageEmitter.ts
1175
1914
  function escapeTemplateLiteral2(s) {
@@ -1197,7 +1936,11 @@ function emitAstroPage(options) {
1197
1936
  fileDepth,
1198
1937
  ssrFallbacks,
1199
1938
  pageName,
1200
- breakpoints: breakpointsOpt
1939
+ breakpoints: breakpointsOpt,
1940
+ imageMetadataMap,
1941
+ i18nConfig,
1942
+ currentPageSlugMap,
1943
+ slugMappings
1201
1944
  } = options;
1202
1945
  const breakpoints = breakpointsOpt ?? DEFAULT_BREAKPOINTS;
1203
1946
  const root = pageData.root;
@@ -1215,10 +1958,22 @@ function emitAstroPage(options) {
1215
1958
  elementPath: [0],
1216
1959
  fileType: "page",
1217
1960
  fileName: pageName,
1218
- breakpoints
1961
+ breakpoints,
1962
+ imageMetadataMap,
1963
+ locale,
1964
+ i18nConfig,
1965
+ currentPageSlugMap,
1966
+ frontmatterLines: [],
1967
+ astroImports: /* @__PURE__ */ new Set(),
1968
+ slugMappings,
1969
+ i18nDefaultLocale: i18nConfig?.defaultLocale
1219
1970
  };
1220
1971
  const templateBody = nodeToAstro(root, ctx);
1221
1972
  const importLines = [];
1973
+ if (ctx.astroImports && ctx.astroImports.size > 0) {
1974
+ const astroImports = Array.from(ctx.astroImports);
1975
+ importLines.push(`import { ${astroImports.join(", ")} } from 'astro:content';`);
1976
+ }
1222
1977
  importLines.push(`import BaseLayout from '${layoutImportPath2}';`);
1223
1978
  const componentImports = Array.from(ctx.imports).sort();
1224
1979
  for (const comp of componentImports) {
@@ -1229,8 +1984,9 @@ function emitAstroPage(options) {
1229
1984
  const libraryTagsLiteral = `{ headCSS: \`${escapeTemplateLiteral2(libraryTags.headCSS || "")}\`, headJS: \`${escapeTemplateLiteral2(libraryTags.headJS || "")}\`, bodyEndJS: \`${escapeTemplateLiteral2(libraryTags.bodyEndJS || "")}\` }`;
1230
1985
  const escapedMeta = escapeTemplateLiteral2(meta);
1231
1986
  const escapedFontPreloads = escapeTemplateLiteral2(fontPreloads);
1987
+ const extraFrontmatter = ctx.frontmatterLines && ctx.frontmatterLines.length > 0 ? "\n" + ctx.frontmatterLines.join("\n") : "";
1232
1988
  return `---
1233
- ${importLines.join("\n")}
1989
+ ${importLines.join("\n")}${extraFrontmatter}
1234
1990
  ---
1235
1991
  <BaseLayout
1236
1992
  title="${escapeJSX2(title)}"
@@ -1241,7 +1997,9 @@ ${importLines.join("\n")}
1241
1997
  fontPreloads={\`${escapedFontPreloads}\`}
1242
1998
  libraryTags={${libraryTagsLiteral}}
1243
1999
  >
1244
- ${templateBody}</BaseLayout>
2000
+ <div id="root">
2001
+ ${templateBody} </div>
2002
+ </BaseLayout>
1245
2003
  `;
1246
2004
  }
1247
2005
  function buildEmptyPage(layoutImport, title, meta, locale, theme, fontPreloads, libraryTags, scriptPaths) {
@@ -1265,88 +2023,333 @@ import BaseLayout from '${layoutImport}';
1265
2023
  `;
1266
2024
  }
1267
2025
 
1268
- // lib/server/astro/cssCollector.ts
1269
- function isStyleMapping3(value) {
1270
- return typeof value === "object" && value !== null && "_mapping" in value && value._mapping === true;
2026
+ // lib/server/astro/cmsPageEmitter.ts
2027
+ function escapeTemplateLiteral3(s) {
2028
+ return s.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
1271
2029
  }
1272
- function isResponsiveStyle3(style) {
1273
- return "base" in style || "tablet" in style || "mobile" in style;
2030
+ function escapeJSX3(s) {
2031
+ return s.replace(/&/g, "&amp;").replace(/"/g, "&quot;");
1274
2032
  }
1275
- function collectFromStyle(style, classes, breakpoints) {
1276
- if (!style) return;
1277
- if (isResponsiveStyle3(style)) {
1278
- for (const [bp, bpStyle] of Object.entries(style)) {
1279
- if (!bpStyle) continue;
1280
- let prefix = "";
1281
- if (bp !== "base") {
1282
- const bpValue = breakpoints[bp]?.breakpoint;
1283
- if (bpValue) {
1284
- prefix = `max-[${bpValue}px]:`;
1285
- }
1286
- }
1287
- collectFromFlatStyle(bpStyle, prefix, classes);
1288
- }
1289
- } else {
1290
- collectFromFlatStyle(style, "", classes);
1291
- }
2033
+ function componentImportPath2(fileDepth, componentName) {
2034
+ const ups = "../".repeat(fileDepth + 1);
2035
+ return `${ups}components/${componentName}.astro`;
1292
2036
  }
1293
- function collectFromFlatStyle(style, prefix, classes) {
1294
- for (const [property, value] of Object.entries(style)) {
1295
- if (!isStyleMapping3(value)) continue;
1296
- for (const [, cssValue] of Object.entries(value.values)) {
1297
- const twClass = propertyToTailwind(property, cssValue);
1298
- if (twClass) {
1299
- classes.add(prefix ? `${prefix}${twClass}` : twClass);
1300
- }
2037
+ function collectRichTextFields(schema) {
2038
+ const richTextFields = /* @__PURE__ */ new Set();
2039
+ for (const [fieldName, fieldDef] of Object.entries(schema.fields)) {
2040
+ if (fieldDef.type === "rich-text") {
2041
+ richTextFields.add(fieldName);
1301
2042
  }
1302
2043
  }
2044
+ return richTextFields;
1303
2045
  }
1304
- function walkNode(node, classes, breakpoints) {
1305
- if (!node || typeof node === "string" || typeof node === "number") return;
1306
- if (Array.isArray(node)) {
1307
- for (const child of node) {
1308
- walkNode(child, classes, breakpoints);
1309
- }
1310
- return;
1311
- }
1312
- if ("style" in node && node.style) {
1313
- collectFromStyle(node.style, classes, breakpoints);
1314
- }
1315
- if ("interactiveStyles" in node && Array.isArray(node.interactiveStyles)) {
1316
- for (const rule of node.interactiveStyles) {
1317
- if (rule.style) {
1318
- collectFromStyle(rule.style, classes, breakpoints);
1319
- }
1320
- }
2046
+ function transformTitleExpression(title, binding, richTextFields, wrapFn) {
2047
+ if (!/\{\{cms\./.test(title)) {
2048
+ return `"${escapeJSX3(title)}"`;
1321
2049
  }
1322
- if ("children" in node && node.children) {
1323
- if (Array.isArray(node.children)) {
1324
- for (const child of node.children) {
1325
- walkNode(child, classes, breakpoints);
1326
- }
1327
- }
2050
+ const w = (expr) => wrapFn ? `${wrapFn}(${expr})` : expr;
2051
+ const fullMatch = title.match(/^\{\{cms\.([^}]+)\}\}$/);
2052
+ if (fullMatch) {
2053
+ return `{${w(`${binding}.data.${fullMatch[1].trim()}`)}}`;
1328
2054
  }
2055
+ const replaced = title.replace(/\{\{cms\.([^}]+)\}\}/g, (_, fieldPath) => {
2056
+ return `\${${w(`${binding}.data.${fieldPath.trim()}`)}}`;
2057
+ });
2058
+ return `{\`${replaced}\`}`;
1329
2059
  }
1330
- function collectAllMappingClasses(componentDefs, breakpoints = DEFAULT_BREAKPOINTS) {
1331
- const classes = /* @__PURE__ */ new Set();
1332
- for (const def of Object.values(componentDefs)) {
1333
- const structure = def.component?.structure;
1334
- if (structure) {
1335
- walkNode(structure, classes, breakpoints);
2060
+ function extractPathPrefix(urlPattern) {
2061
+ const withoutLeading = urlPattern.replace(/^\//, "");
2062
+ const idx = withoutLeading.indexOf("{{");
2063
+ if (idx <= 0) return "";
2064
+ return withoutLeading.substring(0, idx);
2065
+ }
2066
+ function buildGetStaticPaths(schema, isMultiLocale, i18nConfig, locale) {
2067
+ const collectionId = schema.id;
2068
+ const slugField = schema.slugField || "slug";
2069
+ const pathPrefix = extractPathPrefix(schema.urlPattern);
2070
+ const targetLocale = locale || i18nConfig.defaultLocale;
2071
+ if (!isMultiLocale) {
2072
+ const slugExpr = i18nConfig.locales.length > 1 ? `entry.data.${slugField}?.${targetLocale} || entry.data.${slugField} || entry.id` : `entry.data.${slugField} || entry.id`;
2073
+ return [
2074
+ `export async function getStaticPaths() {`,
2075
+ ` const entries = await getCollection('${collectionId}');`,
2076
+ ` return entries.map(entry => ({`,
2077
+ ` params: { slug: ${slugExpr} },`,
2078
+ ` props: { entry },`,
2079
+ ` }));`,
2080
+ `}`,
2081
+ ``,
2082
+ `const { entry } = Astro.props;`
2083
+ ].join("\n");
2084
+ }
2085
+ const defaultLocale = i18nConfig.defaultLocale;
2086
+ const locales = i18nConfig.locales;
2087
+ const lines = [
2088
+ `export async function getStaticPaths() {`,
2089
+ ` const entries = await getCollection('${collectionId}');`,
2090
+ ` const paths = [];`,
2091
+ ` for (const entry of entries) {`
2092
+ ];
2093
+ for (const locale2 of locales) {
2094
+ const code = locale2.code;
2095
+ const slugExpr = `entry.data.${slugField}?.${code} || entry.data.${slugField} || entry.id`;
2096
+ if (code === defaultLocale) {
2097
+ if (pathPrefix) {
2098
+ lines.push(
2099
+ ` paths.push({`,
2100
+ ` params: { slug: \`${pathPrefix}\${${slugExpr}}\` },`,
2101
+ ` props: { entry, locale: '${code}' },`,
2102
+ ` });`
2103
+ );
2104
+ } else {
2105
+ lines.push(
2106
+ ` paths.push({`,
2107
+ ` params: { slug: ${slugExpr} },`,
2108
+ ` props: { entry, locale: '${code}' },`,
2109
+ ` });`
2110
+ );
2111
+ }
2112
+ } else {
2113
+ lines.push(
2114
+ ` paths.push({`,
2115
+ ` params: { slug: \`${code}/${pathPrefix}\${${slugExpr}}\` },`,
2116
+ ` props: { entry, locale: '${code}' },`,
2117
+ ` });`
2118
+ );
1336
2119
  }
1337
2120
  }
1338
- return classes;
1339
- }
1340
-
1341
- // build-astro.ts
1342
- function hashContent2(content) {
1343
- return createHash("sha256").update(content).digest("hex").slice(0, 8);
2121
+ lines.push(
2122
+ ` }`,
2123
+ ` return paths;`,
2124
+ `}`,
2125
+ ``,
2126
+ `const { entry, locale = '${defaultLocale}' } = Astro.props;`
2127
+ );
2128
+ return lines.join("\n");
1344
2129
  }
1345
- function copyDirectory(src, dest) {
1346
- if (!existsSync(src)) return;
1347
- if (!existsSync(dest)) mkdirSync(dest, { recursive: true });
1348
- const files = readdirSync(src);
1349
- for (const file of files) {
2130
+ function emitCMSPage(options) {
2131
+ const {
2132
+ pageData,
2133
+ globalComponents,
2134
+ cmsSchema,
2135
+ title,
2136
+ meta,
2137
+ locale,
2138
+ theme,
2139
+ fontPreloads,
2140
+ libraryTags,
2141
+ scriptPaths,
2142
+ layoutImportPath: layoutImportPath2,
2143
+ fileDepth,
2144
+ ssrFallbacks,
2145
+ pageName,
2146
+ breakpoints: breakpointsOpt,
2147
+ imageMetadataMap,
2148
+ i18nConfig,
2149
+ isMultiLocale,
2150
+ slugMappings
2151
+ } = options;
2152
+ const breakpoints = breakpointsOpt ?? DEFAULT_BREAKPOINTS;
2153
+ const binding = "entry";
2154
+ const richTextFields = collectRichTextFields(cmsSchema);
2155
+ const wrapFn = "r";
2156
+ const root = pageData.root;
2157
+ if (!root) {
2158
+ return buildEmptyCMSPage(
2159
+ layoutImportPath2,
2160
+ title,
2161
+ meta,
2162
+ locale,
2163
+ theme,
2164
+ fontPreloads,
2165
+ libraryTags,
2166
+ scriptPaths,
2167
+ cmsSchema,
2168
+ isMultiLocale,
2169
+ i18nConfig,
2170
+ binding,
2171
+ richTextFields
2172
+ );
2173
+ }
2174
+ const ctx = {
2175
+ imports: /* @__PURE__ */ new Set(),
2176
+ isComponentDef: false,
2177
+ componentProps: {},
2178
+ globalComponents,
2179
+ indent: 1,
2180
+ // inside BaseLayout
2181
+ ssrFallbacks,
2182
+ elementPath: [0],
2183
+ fileType: "page",
2184
+ fileName: pageName,
2185
+ breakpoints,
2186
+ imageMetadataMap,
2187
+ locale,
2188
+ cmsMode: true,
2189
+ cmsEntryBinding: binding,
2190
+ cmsRichTextFields: richTextFields,
2191
+ cmsWrapFn: wrapFn,
2192
+ slugMappings,
2193
+ i18nDefaultLocale: i18nConfig.defaultLocale
2194
+ };
2195
+ const templateBody = nodeToAstro(root, ctx);
2196
+ const importLines = [];
2197
+ importLines.push(`import { getCollection } from 'astro:content';`);
2198
+ importLines.push(`import BaseLayout from '${layoutImportPath2}';`);
2199
+ const componentImports = Array.from(ctx.imports).sort();
2200
+ for (const comp of componentImports) {
2201
+ const path = componentImportPath2(fileDepth, comp);
2202
+ importLines.push(`import ${comp} from '${path}';`);
2203
+ }
2204
+ const staticPaths = buildGetStaticPaths(cmsSchema, isMultiLocale, i18nConfig, locale);
2205
+ const scriptsArrayLiteral = scriptPaths.length > 0 ? `[${scriptPaths.map((s) => `"${s}"`).join(", ")}]` : "[]";
2206
+ const libraryTagsLiteral = `{ headCSS: \`${escapeTemplateLiteral3(libraryTags.headCSS || "")}\`, headJS: \`${escapeTemplateLiteral3(libraryTags.headJS || "")}\`, bodyEndJS: \`${escapeTemplateLiteral3(libraryTags.bodyEndJS || "")}\` }`;
2207
+ const escapedMeta = escapeTemplateLiteral3(meta).replace(
2208
+ /\{\{cms\.([^}]+)\}\}/g,
2209
+ (_, fieldPath) => `\${${wrapFn}(${binding}.data.${fieldPath.trim()})}`
2210
+ );
2211
+ const escapedFontPreloads = escapeTemplateLiteral3(fontPreloads);
2212
+ const titleExpr = transformTitleExpression(title, binding, richTextFields, wrapFn);
2213
+ const resolverHelper = `function r(v) {
2214
+ if (v && typeof v === 'object' && v._i18n) return v['${locale}'] ?? v['${i18nConfig.defaultLocale}'] ?? Object.values(v).find(x => x !== true && x !== undefined) ?? '';
2215
+ return v ?? '';
2216
+ }`;
2217
+ return `---
2218
+ ${importLines.join("\n")}
2219
+
2220
+ ${staticPaths}
2221
+
2222
+ ${resolverHelper}
2223
+ ---
2224
+ <BaseLayout
2225
+ title=${titleExpr}
2226
+ meta={\`${escapedMeta}\`}
2227
+ scripts={${scriptsArrayLiteral}}
2228
+ locale="${locale}"
2229
+ theme="${theme}"
2230
+ fontPreloads={\`${escapedFontPreloads}\`}
2231
+ libraryTags={${libraryTagsLiteral}}
2232
+ >
2233
+ <div id="root">
2234
+ ${templateBody} </div>
2235
+ </BaseLayout>
2236
+ `;
2237
+ }
2238
+ function buildEmptyCMSPage(layoutImport, title, meta, locale, theme, fontPreloads, libraryTags, scriptPaths, cmsSchema, isMultiLocale, i18nConfig, binding, richTextFields) {
2239
+ const escapedMeta = escapeTemplateLiteral3(meta);
2240
+ const escapedFontPreloads = escapeTemplateLiteral3(fontPreloads);
2241
+ const scriptsArrayLiteral = scriptPaths.length > 0 ? `[${scriptPaths.map((s) => `"${s}"`).join(", ")}]` : "[]";
2242
+ const libraryTagsLiteral = `{ headCSS: \`${escapeTemplateLiteral3(libraryTags.headCSS || "")}\`, headJS: \`${escapeTemplateLiteral3(libraryTags.headJS || "")}\`, bodyEndJS: \`${escapeTemplateLiteral3(libraryTags.bodyEndJS || "")}\` }`;
2243
+ const wrapFn = "r";
2244
+ const staticPaths = buildGetStaticPaths(cmsSchema, isMultiLocale, i18nConfig, locale);
2245
+ const titleExpr = transformTitleExpression(title, binding, richTextFields, wrapFn);
2246
+ const resolverHelper = `function r(v) {
2247
+ if (v && typeof v === 'object' && v._i18n) return v['${locale}'] ?? v['${i18nConfig.defaultLocale}'] ?? Object.values(v).find(x => x !== true && x !== undefined) ?? '';
2248
+ return v ?? '';
2249
+ }`;
2250
+ return `---
2251
+ import { getCollection } from 'astro:content';
2252
+ import BaseLayout from '${layoutImport}';
2253
+
2254
+ ${staticPaths}
2255
+
2256
+ ${resolverHelper}
2257
+ ---
2258
+ <BaseLayout
2259
+ title=${titleExpr}
2260
+ meta={\`${escapedMeta}\`}
2261
+ scripts={${scriptsArrayLiteral}}
2262
+ locale="${locale}"
2263
+ theme="${theme}"
2264
+ fontPreloads={\`${escapedFontPreloads}\`}
2265
+ libraryTags={${libraryTagsLiteral}}
2266
+ >
2267
+ </BaseLayout>
2268
+ `;
2269
+ }
2270
+
2271
+ // lib/server/astro/cssCollector.ts
2272
+ function isStyleMapping3(value) {
2273
+ return typeof value === "object" && value !== null && "_mapping" in value && value._mapping === true;
2274
+ }
2275
+ function isResponsiveStyle3(style) {
2276
+ return "base" in style || "tablet" in style || "mobile" in style;
2277
+ }
2278
+ function collectFromStyle(style, classes, breakpoints) {
2279
+ if (!style) return;
2280
+ if (isResponsiveStyle3(style)) {
2281
+ for (const [bp, bpStyle] of Object.entries(style)) {
2282
+ if (!bpStyle) continue;
2283
+ let prefix = "";
2284
+ if (bp !== "base") {
2285
+ const bpValue = breakpoints[bp]?.breakpoint;
2286
+ if (bpValue) {
2287
+ prefix = `max-[${bpValue}px]:`;
2288
+ }
2289
+ }
2290
+ collectFromFlatStyle(bpStyle, prefix, classes);
2291
+ }
2292
+ } else {
2293
+ collectFromFlatStyle(style, "", classes);
2294
+ }
2295
+ }
2296
+ function collectFromFlatStyle(style, prefix, classes) {
2297
+ for (const [property, value] of Object.entries(style)) {
2298
+ if (!isStyleMapping3(value)) continue;
2299
+ for (const [, cssValue] of Object.entries(value.values)) {
2300
+ const twClass = propertyToTailwind(property, cssValue);
2301
+ if (twClass) {
2302
+ classes.add(prefix ? `${prefix}${twClass}` : twClass);
2303
+ }
2304
+ }
2305
+ }
2306
+ }
2307
+ function walkNode(node, classes, breakpoints) {
2308
+ if (!node || typeof node === "string" || typeof node === "number") return;
2309
+ if (Array.isArray(node)) {
2310
+ for (const child of node) {
2311
+ walkNode(child, classes, breakpoints);
2312
+ }
2313
+ return;
2314
+ }
2315
+ if ("style" in node && node.style) {
2316
+ collectFromStyle(node.style, classes, breakpoints);
2317
+ }
2318
+ if ("interactiveStyles" in node && Array.isArray(node.interactiveStyles)) {
2319
+ for (const rule of node.interactiveStyles) {
2320
+ if (rule.style) {
2321
+ collectFromStyle(rule.style, classes, breakpoints);
2322
+ }
2323
+ }
2324
+ }
2325
+ if ("children" in node && node.children) {
2326
+ if (Array.isArray(node.children)) {
2327
+ for (const child of node.children) {
2328
+ walkNode(child, classes, breakpoints);
2329
+ }
2330
+ }
2331
+ }
2332
+ }
2333
+ function collectAllMappingClasses(componentDefs, breakpoints = DEFAULT_BREAKPOINTS) {
2334
+ const classes = /* @__PURE__ */ new Set();
2335
+ for (const def of Object.values(componentDefs)) {
2336
+ const structure = def.component?.structure;
2337
+ if (structure) {
2338
+ walkNode(structure, classes, breakpoints);
2339
+ }
2340
+ }
2341
+ return classes;
2342
+ }
2343
+
2344
+ // build-astro.ts
2345
+ function hashContent2(content) {
2346
+ return createHash("sha256").update(content).digest("hex").slice(0, 8);
2347
+ }
2348
+ function copyDirectory(src, dest) {
2349
+ if (!existsSync(src)) return;
2350
+ if (!existsSync(dest)) mkdirSync(dest, { recursive: true });
2351
+ const files = readdirSync(src);
2352
+ for (const file of files) {
1350
2353
  const srcPath = join(src, file);
1351
2354
  const destPath = join(dest, file);
1352
2355
  const stat = statSync(srcPath);
@@ -1357,13 +2360,6 @@ function copyDirectory(src, dest) {
1357
2360
  function isCMSPage(pageData) {
1358
2361
  return pageData.meta?.source === "cms" && !!pageData.meta?.cms;
1359
2362
  }
1360
- function buildCMSItemPath(urlPattern, item, slugField, locale, i18nConfig) {
1361
- let slug = item[slugField] ?? item._slug ?? item._id;
1362
- if (isI18nValue(slug)) {
1363
- slug = resolveI18nValue(slug, locale, i18nConfig);
1364
- }
1365
- return urlPattern.replace("{{slug}}", String(slug));
1366
- }
1367
2363
  function scanJSONFiles(dir, prefix = "") {
1368
2364
  const results = [];
1369
2365
  if (!existsSync(dir)) return results;
@@ -1381,15 +2377,29 @@ function layoutImportPath(fileDepth) {
1381
2377
  const ups = "../".repeat(fileDepth + 1);
1382
2378
  return `${ups}layouts/BaseLayout.astro`;
1383
2379
  }
1384
- function escapeTemplateLiteral3(s) {
2380
+ function escapeTemplateLiteral4(s) {
1385
2381
  return s.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
1386
2382
  }
2383
+ function computePageSlugMap(slugs, i18nConfig) {
2384
+ const map = {};
2385
+ for (const localeConfig of i18nConfig.locales) {
2386
+ const code = localeConfig.code;
2387
+ const isDefault = code === i18nConfig.defaultLocale;
2388
+ const slug = slugs[code] || "";
2389
+ if (isDefault) {
2390
+ map[code] = slug === "" ? "/" : `/${slug}`;
2391
+ } else {
2392
+ map[code] = slug === "" ? `/${code}` : `/${code}/${slug}`;
2393
+ }
2394
+ }
2395
+ return map;
2396
+ }
1387
2397
  function cmsFieldToZod(field) {
1388
2398
  switch (field.type) {
1389
2399
  case "string":
1390
2400
  case "text":
1391
2401
  case "rich-text":
1392
- return "z.string()";
2402
+ return "z.union([z.string(), z.object({ _i18n: z.literal(true) }).passthrough()])";
1393
2403
  case "number":
1394
2404
  return "z.number()";
1395
2405
  case "boolean":
@@ -1412,11 +2422,11 @@ function cmsFieldToZod(field) {
1412
2422
  }
1413
2423
  }
1414
2424
  function buildSSRFallbackPage(result, importPath, fontPreloads, libraryTags, defaultTheme, scriptPaths) {
1415
- const escapedMeta = escapeTemplateLiteral3(result.meta);
1416
- const escapedHTML = escapeTemplateLiteral3(result.html);
1417
- const escapedFontPreloads = escapeTemplateLiteral3(fontPreloads);
2425
+ const escapedMeta = escapeTemplateLiteral4(result.meta);
2426
+ const escapedHTML = escapeTemplateLiteral4(result.html);
2427
+ const escapedFontPreloads = escapeTemplateLiteral4(fontPreloads);
1418
2428
  const scriptsArrayLiteral = scriptPaths.length > 0 ? `[${scriptPaths.map((s) => `"${s}"`).join(", ")}]` : "[]";
1419
- const libraryTagsLiteral = `{ headCSS: \`${escapeTemplateLiteral3(libraryTags.headCSS || "")}\`, headJS: \`${escapeTemplateLiteral3(libraryTags.headJS || "")}\`, bodyEndJS: \`${escapeTemplateLiteral3(libraryTags.bodyEndJS || "")}\` }`;
2429
+ const libraryTagsLiteral = `{ headCSS: \`${escapeTemplateLiteral4(libraryTags.headCSS || "")}\`, headJS: \`${escapeTemplateLiteral4(libraryTags.headJS || "")}\`, bodyEndJS: \`${escapeTemplateLiteral4(libraryTags.bodyEndJS || "")}\` }`;
1420
2430
  return `---
1421
2431
  import BaseLayout from '${importPath}';
1422
2432
  ---
@@ -1463,6 +2473,11 @@ async function buildAstroProject(projectRoot, outputDir) {
1463
2473
  await configService.load();
1464
2474
  const globalLibraries = configService.getLibraries();
1465
2475
  const componentLibraries = collectComponentLibraries(globalComponents);
2476
+ const imageMetadataMap = await buildImageMetadataMap();
2477
+ if (imageMetadataMap.size > 0) {
2478
+ console.log(`Loaded image metadata for ${imageMetadataMap.size} image(s)
2479
+ `);
2480
+ }
1466
2481
  const outDir = outputDir || join(projectPaths.project, "astro-export");
1467
2482
  if (existsSync(outDir)) {
1468
2483
  rmSync(outDir, { recursive: true, force: true });
@@ -1517,7 +2532,7 @@ async function buildAstroProject(projectRoot, outputDir) {
1517
2532
  }
1518
2533
  }
1519
2534
  }
1520
- function processRenderResult(result, urlPath, astroFilePath, fileDepth, pageData, pageName, isCMSPage2) {
2535
+ function processRenderResult(result, urlPath, astroFilePath, fileDepth, pageData, pageName, isCMSPage3) {
1521
2536
  mergeInteractiveStyles(result.interactiveStylesMap);
1522
2537
  if (result.componentCSS) {
1523
2538
  allComponentCSS.add(result.componentCSS);
@@ -1541,7 +2556,8 @@ async function buildAstroProject(projectRoot, outputDir) {
1541
2556
  astroFilePath,
1542
2557
  pageData,
1543
2558
  pageName,
1544
- isCMSPage: isCMSPage2
2559
+ isCMSPage: isCMSPage3,
2560
+ ssrFallbackCollector: result.ssrFallbackCollector
1545
2561
  });
1546
2562
  }
1547
2563
  for (const file of pageFiles) {
@@ -1598,6 +2614,11 @@ async function buildAstroProject(projectRoot, outputDir) {
1598
2614
  errorCount++;
1599
2615
  }
1600
2616
  }
2617
+ const fontPreloads = generateFontPreloadTags();
2618
+ const mergedLibraries = mergeLibraries(globalLibraries, componentLibraries);
2619
+ const buildLibraries = filterLibrariesByContext(mergedLibraries, "build");
2620
+ const libraryTags = generateLibraryTags(buildLibraries);
2621
+ const defaultTheme = themeConfig.default || "light";
1601
2622
  const templatesDir = projectPaths.templates();
1602
2623
  const templateSchemas = [];
1603
2624
  let cmsPageCount = 0;
@@ -1626,42 +2647,87 @@ Processing ${templateFiles.length} CMS template(s)...
1626
2647
  templateSchemas.push(cmsSchema);
1627
2648
  console.log(` CMS Collection: ${cmsSchema.id}`);
1628
2649
  const items = await cmsService.queryItems({ collection: cmsSchema.id });
1629
- if (items.length === 0) {
2650
+ const itemCount = items.length;
2651
+ if (itemCount === 0) {
1630
2652
  console.log(` No items found in cms/${cmsSchema.id}/`);
1631
- continue;
2653
+ } else {
2654
+ console.log(` Found ${itemCount} item(s)`);
1632
2655
  }
1633
- console.log(` Found ${items.length} item(s)`);
1634
- for (const item of items) {
1635
- for (const localeConfig of i18nConfig.locales) {
1636
- const locale = localeConfig.code;
1637
- const isDefault = locale === i18nConfig.defaultLocale;
1638
- const isDevBuild2 = process.env.MENO_DEV_BUILD === "true";
1639
- if (!isDevBuild2 && isItemDraftForLocale(item, locale)) {
1640
- continue;
1641
- }
1642
- const itemPath = buildCMSItemPath(cmsSchema.urlPattern, item, cmsSchema.slugField, locale, i18nConfig);
1643
- const itemWithUrl = { ...item, _url: itemPath };
1644
- const result = await renderPageSSR(
1645
- pageData,
1646
- globalComponents,
1647
- itemPath,
1648
- siteUrl,
1649
- locale,
1650
- i18nConfig,
1651
- slugMappings,
1652
- { cms: itemWithUrl },
1653
- cmsService,
1654
- true
1655
- );
1656
- const pathWithoutSlash = itemPath.startsWith("/") ? itemPath.substring(1) : itemPath;
1657
- const astroFilePath = isDefault ? `${pathWithoutSlash}.astro` : `${locale}/${pathWithoutSlash}.astro`;
1658
- const fileDepth = astroFilePath.split("/").length - 1;
1659
- const urlPath = isDefault ? itemPath : `/${locale}${itemPath}`;
1660
- processRenderResult(result, urlPath, astroFilePath, fileDepth, pageData, file.replace(".json", ""), true);
1661
- console.log(` Rendered: ${urlPath}`);
1662
- cmsPageCount++;
2656
+ const defaultLocale = i18nConfig.defaultLocale;
2657
+ const dummyPath = cmsSchema.urlPattern.replace("{{slug}}", "__placeholder__");
2658
+ const metaResult = await renderPageSSR(
2659
+ pageData,
2660
+ globalComponents,
2661
+ dummyPath,
2662
+ siteUrl,
2663
+ defaultLocale,
2664
+ i18nConfig,
2665
+ slugMappings,
2666
+ void 0,
2667
+ // no CMS context - just collecting metadata
2668
+ cmsService,
2669
+ true
2670
+ );
2671
+ mergeInteractiveStyles(metaResult.interactiveStylesMap);
2672
+ if (metaResult.componentCSS) {
2673
+ allComponentCSS.add(metaResult.componentCSS);
2674
+ }
2675
+ const scriptPaths = [];
2676
+ if (metaResult.javascript) {
2677
+ const hash = hashContent2(metaResult.javascript);
2678
+ if (!jsContents.has(hash)) {
2679
+ jsContents.set(hash, metaResult.javascript);
2680
+ }
2681
+ scriptPaths.push(`/_scripts/${hash}.js`);
2682
+ }
2683
+ const isMultiLocale = i18nConfig.locales.length > 1;
2684
+ const urlPatternWithoutSlash = cmsSchema.urlPattern.replace(/^\//, "");
2685
+ const slugPlaceholderIdx = urlPatternWithoutSlash.indexOf("{{");
2686
+ const pathPrefix = slugPlaceholderIdx > 0 ? urlPatternWithoutSlash.substring(0, slugPlaceholderIdx) : "";
2687
+ const ssrFallbacks = metaResult.ssrFallbackCollector ?? /* @__PURE__ */ new Map();
2688
+ const localesToEmit = isMultiLocale ? i18nConfig.locales : [{ code: i18nConfig.defaultLocale }];
2689
+ for (const localeEntry of localesToEmit) {
2690
+ const localeCode = localeEntry.code;
2691
+ const isDefault = localeCode === i18nConfig.defaultLocale;
2692
+ let astroFilePath;
2693
+ if (pathPrefix) {
2694
+ astroFilePath = isDefault ? `${pathPrefix}[slug].astro` : `${localeCode}/${pathPrefix}[slug].astro`;
2695
+ } else {
2696
+ astroFilePath = isDefault ? "[slug].astro" : `${localeCode}/[slug].astro`;
1663
2697
  }
2698
+ const fileDepth = astroFilePath.split("/").length - 1;
2699
+ const importPath = layoutImportPath(fileDepth);
2700
+ const astroContent = emitCMSPage({
2701
+ pageData,
2702
+ globalComponents,
2703
+ cmsSchema,
2704
+ title: String(pageData.meta?.title || cmsSchema.name),
2705
+ meta: metaResult.meta,
2706
+ locale: localeCode,
2707
+ theme: defaultTheme,
2708
+ fontPreloads,
2709
+ libraryTags,
2710
+ scriptPaths,
2711
+ layoutImportPath: importPath,
2712
+ fileDepth,
2713
+ ssrFallbacks,
2714
+ pageName: file.replace(".json", ""),
2715
+ breakpoints,
2716
+ imageMetadataMap,
2717
+ i18nConfig,
2718
+ isMultiLocale: false,
2719
+ // Each file handles one locale
2720
+ slugMappings
2721
+ });
2722
+ const astroFileFull = join(pagesOutDir, astroFilePath);
2723
+ const astroFileDir = astroFileFull.substring(0, astroFileFull.lastIndexOf("/"));
2724
+ if (!existsSync(astroFileDir)) {
2725
+ mkdirSync(astroFileDir, { recursive: true });
2726
+ }
2727
+ await writeFile2(astroFileFull, astroContent, "utf-8");
1664
2728
  }
2729
+ console.log(` Generated: ${pathPrefix}[slug].astro (${itemCount} items \xD7 ${localesToEmit.length} locale(s))`);
2730
+ cmsPageCount += itemCount * i18nConfig.locales.length;
1665
2731
  } catch (error) {
1666
2732
  console.error(` Error processing template ${file}:`, error?.message || error);
1667
2733
  errorCount++;
@@ -1690,10 +2756,6 @@ Processing ${templateFiles.length} CMS template(s)...
1690
2756
  await writeFile2(join(stylesDir, "global.css"), globalCSS, "utf-8");
1691
2757
  console.log(`
1692
2758
  Generated global.css (${(globalCSS.length / 1024).toFixed(1)} KB)`);
1693
- const fontPreloads = generateFontPreloadTags();
1694
- const mergedLibraries = mergeLibraries(globalLibraries, componentLibraries);
1695
- const buildLibraries = filterLibrariesByContext(mergedLibraries, "build");
1696
- const libraryTags = generateLibraryTags(buildLibraries);
1697
2759
  const baseLayoutContent = `---
1698
2760
  import '../styles/global.css';
1699
2761
 
@@ -1732,7 +2794,7 @@ const { title, meta = '', scripts = [], locale = 'en', theme = '${themeConfig.de
1732
2794
  let componentFileCount = 0;
1733
2795
  for (const [compName, compDef] of Object.entries(globalComponents)) {
1734
2796
  try {
1735
- const astroContent = emitAstroComponent(compName, compDef, globalComponents, breakpoints);
2797
+ const astroContent = emitAstroComponent(compName, compDef, globalComponents, breakpoints, i18nConfig.defaultLocale);
1736
2798
  await writeFile2(join(componentsOutDir, `${compName}.astro`), astroContent, "utf-8");
1737
2799
  componentFileCount++;
1738
2800
  } catch (error) {
@@ -1740,7 +2802,7 @@ const { title, meta = '', scripts = [], locale = 'en', theme = '${themeConfig.de
1740
2802
  }
1741
2803
  }
1742
2804
  console.log(`Generated ${componentFileCount} component .astro file(s)`);
1743
- const defaultTheme = themeConfig.default || "light";
2805
+ const allFallbackHtml = [];
1744
2806
  for (const result of allResults) {
1745
2807
  const importPath = layoutImportPath(result.fileDepth);
1746
2808
  const scriptPaths = [];
@@ -1758,8 +2820,13 @@ const { title, meta = '', scripts = [], locale = 'en', theme = '${themeConfig.de
1758
2820
  scriptPaths.push(scriptPublicPath);
1759
2821
  }
1760
2822
  let astroContent;
1761
- if (result.pageData && !result.isCMSPage) {
2823
+ if (result.pageData) {
1762
2824
  try {
2825
+ const ssrFallbacks = result.ssrFallbackCollector ?? /* @__PURE__ */ new Map();
2826
+ ssrFallbacks.forEach((html) => {
2827
+ allFallbackHtml.push(html);
2828
+ });
2829
+ const pageSlugMap = result.pageData.meta?.slugs ? computePageSlugMap(result.pageData.meta.slugs, i18nConfig) : void 0;
1763
2830
  astroContent = emitAstroPage({
1764
2831
  pageData: result.pageData,
1765
2832
  globalComponents,
@@ -1772,10 +2839,13 @@ const { title, meta = '', scripts = [], locale = 'en', theme = '${themeConfig.de
1772
2839
  scriptPaths,
1773
2840
  layoutImportPath: importPath,
1774
2841
  fileDepth: result.fileDepth,
1775
- ssrFallbacks: /* @__PURE__ */ new Map(),
1776
- // SSR fallbacks for complex nodes
2842
+ ssrFallbacks,
1777
2843
  pageName: result.pageName || "index",
1778
- breakpoints
2844
+ breakpoints,
2845
+ imageMetadataMap,
2846
+ i18nConfig: i18nConfig.locales.length > 1 ? i18nConfig : void 0,
2847
+ currentPageSlugMap: pageSlugMap,
2848
+ slugMappings: i18nConfig.locales.length > 1 ? slugMappings : void 0
1779
2849
  });
1780
2850
  } catch (error) {
1781
2851
  console.warn(` Warning: component emission failed for ${result.urlPath}, using SSR fallback: ${error?.message}`);
@@ -1783,6 +2853,7 @@ const { title, meta = '', scripts = [], locale = 'en', theme = '${themeConfig.de
1783
2853
  }
1784
2854
  } else {
1785
2855
  astroContent = buildSSRFallbackPage(result, importPath, fontPreloads, libraryTags, defaultTheme, scriptPaths);
2856
+ allFallbackHtml.push(result.html);
1786
2857
  }
1787
2858
  const astroFileFull = join(pagesOutDir, result.astroFilePath);
1788
2859
  const astroFileDir = astroFileFull.substring(0, astroFileFull.lastIndexOf("/"));
@@ -1792,6 +2863,22 @@ const { title, meta = '', scripts = [], locale = 'en', theme = '${themeConfig.de
1792
2863
  await writeFile2(astroFileFull, astroContent, "utf-8");
1793
2864
  }
1794
2865
  console.log(`Generated ${allResults.length} .astro page file(s)`);
2866
+ if (allFallbackHtml.length > 0) {
2867
+ const allClasses = /* @__PURE__ */ new Set();
2868
+ for (const html of allFallbackHtml) {
2869
+ for (const cls of extractUtilityClassesFromHTML(html)) {
2870
+ allClasses.add(cls);
2871
+ }
2872
+ }
2873
+ if (allClasses.size > 0) {
2874
+ const utilityCSS = generateUtilityCSS(allClasses, breakpoints, responsiveScales);
2875
+ if (utilityCSS) {
2876
+ const existingCSS = await readFile(join(stylesDir, "global.css"), "utf-8");
2877
+ await writeFile2(join(stylesDir, "global.css"), existingCSS + "\n\n/* SSR fallback utility classes */\n" + utilityCSS, "utf-8");
2878
+ console.log(`Added ${allClasses.size} utility classes for SSR fallback content`);
2879
+ }
2880
+ }
2881
+ }
1795
2882
  let collectionCount = 0;
1796
2883
  if (templateSchemas.length > 0) {
1797
2884
  const contentDir = join(srcDir, "content");
@@ -1807,14 +2894,7 @@ const { title, meta = '', scripts = [], locale = 'en', theme = '${themeConfig.de
1807
2894
  try {
1808
2895
  const rawContent = await readFile(join(cmsItemsDir, itemFile), "utf-8");
1809
2896
  const item = JSON.parse(rawContent);
1810
- const resolved = {};
1811
- for (const [key, value] of Object.entries(item)) {
1812
- if (isI18nValue(value)) {
1813
- resolved[key] = resolveI18nValue(value, i18nConfig.defaultLocale, i18nConfig);
1814
- } else {
1815
- resolved[key] = value;
1816
- }
1817
- }
2897
+ const resolved = { ...item };
1818
2898
  await writeFile2(
1819
2899
  join(collectionDir, itemFile),
1820
2900
  JSON.stringify(resolved, null, 2),
@@ -1882,19 +2962,24 @@ export { collections };
1882
2962
  },
1883
2963
  dependencies: {
1884
2964
  "astro": "^4.0.0",
1885
- "@astrojs/sitemap": "^3.0.0",
1886
2965
  "@astrojs/tailwind": "^5.0.0",
1887
2966
  "tailwindcss": "^3.4.0"
1888
2967
  }
1889
2968
  };
1890
2969
  await writeFile2(join(outDir, "package.json"), JSON.stringify(packageJson, null, 2), "utf-8");
2970
+ const localeCodes = i18nConfig.locales.map((l) => l.code);
2971
+ const i18nBlock = i18nConfig.locales.length > 1 ? `
2972
+ i18n: {
2973
+ defaultLocale: '${i18nConfig.defaultLocale}',
2974
+ locales: [${localeCodes.map((c) => `'${c}'`).join(", ")}],
2975
+ routing: { prefixDefaultLocale: false },
2976
+ },` : "";
1891
2977
  const astroConfig = `import { defineConfig } from 'astro/config';
1892
- import sitemap from '@astrojs/sitemap';
1893
2978
  import tailwind from '@astrojs/tailwind';
1894
2979
 
1895
2980
  export default defineConfig({${siteUrl ? `
1896
- site: '${siteUrl}',` : ""}
1897
- integrations: [sitemap(), tailwind({ applyBaseStyles: false })],
2981
+ site: '${siteUrl}',` : ""}${i18nBlock}
2982
+ integrations: [tailwind({ applyBaseStyles: false })],
1898
2983
  });
1899
2984
  `;
1900
2985
  const safelistArray = Array.from(mappingClasses);
@@ -1943,6 +3028,730 @@ export default {
1943
3028
  };
1944
3029
  }
1945
3030
 
3031
+ // lib/server/webflow/buildWebflow.ts
3032
+ import { existsSync as existsSync2, readdirSync as readdirSync2 } from "fs";
3033
+ import { join as join2 } from "path";
3034
+
3035
+ // lib/server/webflow/types.ts
3036
+ function mapCMSFieldType(menoType) {
3037
+ switch (menoType) {
3038
+ case "string":
3039
+ return "PlainText";
3040
+ case "text":
3041
+ case "rich-text":
3042
+ return "RichText";
3043
+ case "number":
3044
+ return "Number";
3045
+ case "boolean":
3046
+ return "Switch";
3047
+ case "image":
3048
+ return "Image";
3049
+ case "date":
3050
+ return "Date";
3051
+ case "select":
3052
+ return "Option";
3053
+ case "file":
3054
+ return "File";
3055
+ case "reference":
3056
+ return "Reference";
3057
+ default:
3058
+ return "PlainText";
3059
+ }
3060
+ }
3061
+
3062
+ // lib/server/webflow/nodeToWebflow.ts
3063
+ init_constants();
3064
+
3065
+ // lib/server/webflow/styleMapper.ts
3066
+ var UNITLESS_PROPERTIES = /* @__PURE__ */ new Set([
3067
+ "opacity",
3068
+ "z-index",
3069
+ "flex-grow",
3070
+ "flex-shrink",
3071
+ "flex",
3072
+ "order",
3073
+ "orphans",
3074
+ "widows",
3075
+ "column-count",
3076
+ "font-weight",
3077
+ "tab-size"
3078
+ ]);
3079
+ function isStyleMapping4(value) {
3080
+ return typeof value === "object" && value !== null && "_mapping" in value && value._mapping === true;
3081
+ }
3082
+ function isResponsiveStyle4(style) {
3083
+ return "base" in style || "tablet" in style || "mobile" in style;
3084
+ }
3085
+ function toKebabCase(prop) {
3086
+ return prop.replace(/([A-Z])/g, "-$1").toLowerCase();
3087
+ }
3088
+ function styleObjectToCSS(style) {
3089
+ const css = {};
3090
+ for (const [prop, value] of Object.entries(style)) {
3091
+ if (isStyleMapping4(value)) continue;
3092
+ if (value === "" || value === void 0 || value === null) continue;
3093
+ if (typeof value === "boolean" || typeof value === "object") continue;
3094
+ const cssProp = toKebabCase(prop);
3095
+ if (typeof value === "number") {
3096
+ if (isNaN(value)) continue;
3097
+ css[cssProp] = UNITLESS_PROPERTIES.has(cssProp) ? String(value) : `${value}px`;
3098
+ } else {
3099
+ css[cssProp] = String(value);
3100
+ }
3101
+ }
3102
+ return css;
3103
+ }
3104
+ function collectStyleMappings2(style) {
3105
+ if (!style) return [];
3106
+ const result = [];
3107
+ if (isResponsiveStyle4(style)) {
3108
+ const base = style.base;
3109
+ if (base) {
3110
+ for (const [prop, value] of Object.entries(base)) {
3111
+ if (isStyleMapping4(value)) {
3112
+ result.push({ property: prop, mapping: value });
3113
+ }
3114
+ }
3115
+ }
3116
+ } else {
3117
+ for (const [prop, value] of Object.entries(style)) {
3118
+ if (isStyleMapping4(value)) {
3119
+ result.push({ property: prop, mapping: value });
3120
+ }
3121
+ }
3122
+ }
3123
+ return result;
3124
+ }
3125
+ function postfixToPseudoState(postfix) {
3126
+ if (postfix.includes(":hover")) return "hover";
3127
+ if (postfix.includes(":focus-visible")) return "focus-visible";
3128
+ if (postfix.includes(":focus")) return "focus";
3129
+ if (postfix.includes(":active")) return "active";
3130
+ if (postfix.includes(":visited")) return "visited";
3131
+ return null;
3132
+ }
3133
+ function mapStylesToWebflow(className, style, interactiveStyles, breakpoints) {
3134
+ const webflowClassName = className.replace(/_/g, "-");
3135
+ const primaryClass = {
3136
+ name: webflowClassName,
3137
+ base: {}
3138
+ };
3139
+ if (style) {
3140
+ if (isResponsiveStyle4(style)) {
3141
+ const responsive = style;
3142
+ if (responsive.base) {
3143
+ primaryClass.base = styleObjectToCSS(responsive.base);
3144
+ }
3145
+ if (responsive.tablet) {
3146
+ if (!primaryClass.breakpoints) primaryClass.breakpoints = {};
3147
+ primaryClass.breakpoints.Tablet = styleObjectToCSS(responsive.tablet);
3148
+ }
3149
+ if (responsive.mobile) {
3150
+ if (!primaryClass.breakpoints) primaryClass.breakpoints = {};
3151
+ primaryClass.breakpoints.MobilePortrait = styleObjectToCSS(responsive.mobile);
3152
+ }
3153
+ for (const [bpName, bpStyle] of Object.entries(responsive)) {
3154
+ if (!bpStyle || bpName === "base" || bpName === "tablet" || bpName === "mobile") continue;
3155
+ if (!primaryClass.breakpoints) primaryClass.breakpoints = {};
3156
+ primaryClass.breakpoints.Tablet = {
3157
+ ...primaryClass.breakpoints.Tablet,
3158
+ ...styleObjectToCSS(bpStyle)
3159
+ };
3160
+ }
3161
+ } else {
3162
+ primaryClass.base = styleObjectToCSS(style);
3163
+ }
3164
+ }
3165
+ if (interactiveStyles && interactiveStyles.length > 0) {
3166
+ for (const rule of interactiveStyles) {
3167
+ if (!rule.postfix) continue;
3168
+ const pseudoState = postfixToPseudoState(rule.postfix);
3169
+ if (!pseudoState) continue;
3170
+ const ruleStyle = rule.style;
3171
+ if (!primaryClass.pseudoStates) primaryClass.pseudoStates = {};
3172
+ if (isResponsiveStyle4(ruleStyle)) {
3173
+ const responsive = ruleStyle;
3174
+ if (responsive.base) {
3175
+ primaryClass.pseudoStates[pseudoState] = {
3176
+ ...primaryClass.pseudoStates[pseudoState],
3177
+ ...styleObjectToCSS(responsive.base)
3178
+ };
3179
+ }
3180
+ } else {
3181
+ primaryClass.pseudoStates[pseudoState] = {
3182
+ ...primaryClass.pseudoStates[pseudoState],
3183
+ ...styleObjectToCSS(ruleStyle)
3184
+ };
3185
+ }
3186
+ }
3187
+ }
3188
+ const comboClasses = [];
3189
+ const mappings = collectStyleMappings2(style);
3190
+ for (const { property, mapping } of mappings) {
3191
+ for (const [value, cssValue] of Object.entries(mapping.values)) {
3192
+ if (cssValue === "" || cssValue === void 0) continue;
3193
+ const comboName = `is-${sanitizeClassName(mapping.prop)}-${sanitizeClassName(String(value))}`;
3194
+ const comboClass = {
3195
+ name: comboName,
3196
+ base: {
3197
+ [toKebabCase(property)]: typeof cssValue === "number" ? UNITLESS_PROPERTIES.has(toKebabCase(property)) ? String(cssValue) : `${cssValue}px` : String(cssValue)
3198
+ },
3199
+ comboParent: webflowClassName
3200
+ };
3201
+ comboClasses.push(comboClass);
3202
+ }
3203
+ }
3204
+ return { primaryClass, comboClasses };
3205
+ }
3206
+ function sanitizeClassName(name) {
3207
+ return name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
3208
+ }
3209
+
3210
+ // lib/server/webflow/nodeToWebflow.ts
3211
+ function buildElementClass2(ctx, label) {
3212
+ return generateElementClassName({
3213
+ fileType: ctx.fileType,
3214
+ fileName: ctx.fileName,
3215
+ label,
3216
+ path: ctx.elementPath
3217
+ });
3218
+ }
3219
+ function resolveTemplate2(text, props) {
3220
+ if (!props) return text;
3221
+ return text.replace(/\{\{(.+?)\}\}/g, (_, expr) => {
3222
+ const trimmed = expr.trim();
3223
+ const orMatch = trimmed.match(/^(.+?)\s*\|\|\s*['"](.+?)['"]$/);
3224
+ if (orMatch) {
3225
+ const value2 = resolveNestedProp(props, orMatch[1].trim());
3226
+ return value2 !== void 0 && value2 !== "" && value2 !== null ? String(value2) : orMatch[2];
3227
+ }
3228
+ const ternaryMatch = trimmed.match(/^(.+?)\s*\?\s*['"](.+?)['"]\s*:\s*['"](.+?)['"]$/);
3229
+ if (ternaryMatch) {
3230
+ const value2 = resolveNestedProp(props, ternaryMatch[1].trim());
3231
+ return value2 ? ternaryMatch[2] : ternaryMatch[3];
3232
+ }
3233
+ const value = resolveNestedProp(props, trimmed);
3234
+ return value !== void 0 ? String(value) : "";
3235
+ });
3236
+ }
3237
+ function resolveNestedProp(obj, path) {
3238
+ const parts = path.split(".");
3239
+ let current = obj;
3240
+ for (const part of parts) {
3241
+ if (current === null || current === void 0 || typeof current !== "object") return void 0;
3242
+ current = current[part];
3243
+ }
3244
+ return current;
3245
+ }
3246
+ function hasTemplates2(text) {
3247
+ return /\{\{.+?\}\}/.test(text);
3248
+ }
3249
+ function nodeToWebflow(node, ctx, instanceProps) {
3250
+ if (node === null || node === void 0) return [];
3251
+ if (typeof node === "string") {
3252
+ const text = instanceProps ? resolveTemplate2(node, instanceProps) : node;
3253
+ return [{ tag: "span", textContent: text }];
3254
+ }
3255
+ if (typeof node === "number") {
3256
+ return [{ tag: "span", textContent: String(node) }];
3257
+ }
3258
+ if (Array.isArray(node)) {
3259
+ const results = [];
3260
+ for (let i = 0; i < node.length; i++) {
3261
+ const child = node[i];
3262
+ const savedPath = [...ctx.elementPath];
3263
+ ctx.elementPath = [...ctx.elementPath, i];
3264
+ results.push(...nodeToWebflow(child, ctx, instanceProps));
3265
+ ctx.elementPath = savedPath;
3266
+ }
3267
+ return results;
3268
+ }
3269
+ switch (node.type) {
3270
+ case NODE_TYPE.NODE:
3271
+ return [emitHtmlNode2(node, ctx, instanceProps)];
3272
+ case NODE_TYPE.COMPONENT:
3273
+ return emitComponentInstance2(node, ctx, instanceProps);
3274
+ case NODE_TYPE.SLOT:
3275
+ return emitSlotMarker2(node, ctx, instanceProps);
3276
+ case NODE_TYPE.EMBED:
3277
+ return [emitEmbedNode2(node, ctx, instanceProps)];
3278
+ case NODE_TYPE.LINK:
3279
+ return [emitLinkNode2(node, ctx, instanceProps)];
3280
+ case NODE_TYPE.LIST:
3281
+ case "cms-list":
3282
+ case NODE_TYPE.LOCALE_LIST:
3283
+ return [{ tag: "div", attributes: { "data-meno-type": node.type } }];
3284
+ default:
3285
+ return [];
3286
+ }
3287
+ }
3288
+ function emitHtmlNode2(node, ctx, instanceProps) {
3289
+ const tag = hasTemplates2(node.tag) && instanceProps ? resolveTemplate2(node.tag, instanceProps) : node.tag;
3290
+ const style = node.style;
3291
+ const interactiveStyles = node.interactiveStyles;
3292
+ const needsClass = style || interactiveStyles && interactiveStyles.length > 0 || node.generateElementClass;
3293
+ let className;
3294
+ let comboClassNames;
3295
+ if (needsClass) {
3296
+ const elementClass = buildElementClass2(ctx, node.label);
3297
+ const { primaryClass, comboClasses } = mapStylesToWebflow(
3298
+ elementClass,
3299
+ style,
3300
+ interactiveStyles,
3301
+ ctx.breakpoints
3302
+ );
3303
+ className = primaryClass.name;
3304
+ ctx.styleClasses.set(primaryClass.name, primaryClass);
3305
+ if (comboClasses.length > 0) {
3306
+ comboClassNames = [];
3307
+ for (const combo of comboClasses) {
3308
+ ctx.styleClasses.set(combo.name, combo);
3309
+ comboClassNames.push(combo.name);
3310
+ }
3311
+ }
3312
+ }
3313
+ const attributes = {};
3314
+ if (node.attributes) {
3315
+ for (const [key, value] of Object.entries(node.attributes)) {
3316
+ if (instanceProps && typeof value === "string" && hasTemplates2(value)) {
3317
+ attributes[key] = resolveTemplate2(value, instanceProps);
3318
+ } else {
3319
+ attributes[key] = value;
3320
+ }
3321
+ }
3322
+ }
3323
+ let children;
3324
+ let textContent;
3325
+ if (!isVoidElement(tag) && node.children) {
3326
+ if (typeof node.children === "string") {
3327
+ textContent = instanceProps ? resolveTemplate2(node.children, instanceProps) : node.children;
3328
+ } else if (Array.isArray(node.children) && node.children.length === 1 && typeof node.children[0] === "string") {
3329
+ const text = node.children[0];
3330
+ textContent = instanceProps ? resolveTemplate2(text, instanceProps) : text;
3331
+ } else {
3332
+ const innerCtx = { ...ctx, elementPath: [...ctx.elementPath] };
3333
+ children = convertChildren(node.children, innerCtx, instanceProps);
3334
+ if (children.length === 0) children = void 0;
3335
+ }
3336
+ }
3337
+ let conditional;
3338
+ const ifValue = node.if;
3339
+ if (ifValue !== void 0 && ifValue !== true) {
3340
+ if (typeof ifValue === "object" && ifValue._mapping) {
3341
+ conditional = { prop: ifValue.prop, condition: "truthy" };
3342
+ } else if (typeof ifValue === "string") {
3343
+ const match = ifValue.match(/^\{\{(.+)\}\}$/);
3344
+ conditional = { prop: match ? match[1].trim() : ifValue, condition: "truthy" };
3345
+ }
3346
+ }
3347
+ const element = { tag };
3348
+ if (className) element.className = className;
3349
+ if (comboClassNames) element.comboClasses = comboClassNames;
3350
+ if (textContent) element.textContent = textContent;
3351
+ if (children) element.children = children;
3352
+ if (Object.keys(attributes).length > 0) element.attributes = attributes;
3353
+ if (conditional) element.conditional = conditional;
3354
+ return element;
3355
+ }
3356
+ function emitComponentInstance2(node, ctx, parentProps) {
3357
+ const compDef = ctx.globalComponents[node.component];
3358
+ if (!compDef) {
3359
+ return [{ tag: "div", attributes: { "data-component": node.component } }];
3360
+ }
3361
+ const resolvedProps = {};
3362
+ const structured = compDef.component;
3363
+ if (structured?.interface) {
3364
+ for (const [key, propDef] of Object.entries(structured.interface)) {
3365
+ resolvedProps[key] = propDef.default;
3366
+ }
3367
+ }
3368
+ if (node.props) {
3369
+ for (const [key, value] of Object.entries(node.props)) {
3370
+ if (key === "children") continue;
3371
+ if (typeof value === "string" && hasTemplates2(value) && parentProps) {
3372
+ resolvedProps[key] = resolveTemplate2(value, parentProps);
3373
+ } else {
3374
+ resolvedProps[key] = value;
3375
+ }
3376
+ }
3377
+ }
3378
+ const body = structured?.structure || compDef.node;
3379
+ if (!body) return [];
3380
+ const compCtx = {
3381
+ ...ctx,
3382
+ fileType: "component",
3383
+ fileName: node.component,
3384
+ elementPath: [0],
3385
+ slotChildren: node.children
3386
+ };
3387
+ return nodeToWebflow(body, compCtx, resolvedProps);
3388
+ }
3389
+ function emitSlotMarker2(node, ctx, instanceProps) {
3390
+ if (ctx.slotChildren) {
3391
+ const parentCtx = {
3392
+ ...ctx,
3393
+ slotChildren: void 0
3394
+ // prevent infinite slot nesting
3395
+ };
3396
+ return convertChildren(ctx.slotChildren, parentCtx, instanceProps);
3397
+ }
3398
+ if (node.default) {
3399
+ return convertChildren(node.default, ctx, instanceProps);
3400
+ }
3401
+ return [];
3402
+ }
3403
+ function emitEmbedNode2(node, ctx, instanceProps) {
3404
+ const style = node.style;
3405
+ const interactiveStyles = node.interactiveStyles;
3406
+ let className;
3407
+ if (style || interactiveStyles && interactiveStyles.length > 0) {
3408
+ const elementClass = buildElementClass2(ctx, node.label);
3409
+ const { primaryClass } = mapStylesToWebflow(
3410
+ elementClass,
3411
+ style,
3412
+ interactiveStyles,
3413
+ ctx.breakpoints
3414
+ );
3415
+ className = primaryClass.name;
3416
+ ctx.styleClasses.set(primaryClass.name, primaryClass);
3417
+ }
3418
+ const htmlStr = typeof node.html === "string" ? node.html : "";
3419
+ const element = {
3420
+ tag: "div",
3421
+ rawHtml: htmlStr
3422
+ };
3423
+ if (className) element.className = className;
3424
+ return element;
3425
+ }
3426
+ function emitLinkNode2(node, ctx, instanceProps) {
3427
+ const style = node.style;
3428
+ const interactiveStyles = node.interactiveStyles;
3429
+ let className;
3430
+ if (style || interactiveStyles && interactiveStyles.length > 0) {
3431
+ const elementClass = buildElementClass2(ctx, node.label);
3432
+ const { primaryClass } = mapStylesToWebflow(
3433
+ elementClass,
3434
+ style,
3435
+ interactiveStyles,
3436
+ ctx.breakpoints
3437
+ );
3438
+ className = primaryClass.name;
3439
+ ctx.styleClasses.set(primaryClass.name, primaryClass);
3440
+ }
3441
+ let href = "#";
3442
+ if (typeof node.href === "string") {
3443
+ href = instanceProps && hasTemplates2(node.href) ? resolveTemplate2(node.href, instanceProps) : node.href;
3444
+ }
3445
+ const attributes = { href };
3446
+ if (node.attributes) {
3447
+ for (const [key, value] of Object.entries(node.attributes)) {
3448
+ attributes[key] = value;
3449
+ }
3450
+ }
3451
+ let children;
3452
+ if (node.children) {
3453
+ children = convertChildren(node.children, ctx, instanceProps);
3454
+ if (children.length === 0) children = void 0;
3455
+ }
3456
+ const element = { tag: "a", attributes };
3457
+ if (className) element.className = className;
3458
+ if (children) element.children = children;
3459
+ return element;
3460
+ }
3461
+ function convertChildren(children, ctx, instanceProps) {
3462
+ if (!children) return [];
3463
+ if (typeof children === "string") {
3464
+ return nodeToWebflow(children, ctx, instanceProps);
3465
+ }
3466
+ if (Array.isArray(children)) {
3467
+ return nodeToWebflow(children, ctx, instanceProps);
3468
+ }
3469
+ return nodeToWebflow(children, ctx, instanceProps);
3470
+ }
3471
+
3472
+ // lib/server/webflow/buildWebflow.ts
3473
+ function scanJSONFiles2(dir, prefix = "") {
3474
+ const results = [];
3475
+ if (!existsSync2(dir)) return results;
3476
+ const entries = readdirSync2(dir, { withFileTypes: true });
3477
+ for (const entry of entries) {
3478
+ if (entry.isFile() && entry.name.endsWith(".json")) {
3479
+ results.push(prefix ? `${prefix}/${entry.name}` : entry.name);
3480
+ } else if (entry.isDirectory()) {
3481
+ results.push(...scanJSONFiles2(join2(dir, entry.name), prefix ? `${prefix}/${entry.name}` : entry.name));
3482
+ }
3483
+ }
3484
+ return results;
3485
+ }
3486
+ function isCMSPage2(pageData) {
3487
+ return pageData.meta?.source === "cms" && !!pageData.meta?.cms;
3488
+ }
3489
+ function buildCMSItemPath(urlPattern, item, slugField, locale, i18nConfig) {
3490
+ let slug = item[slugField] ?? item._slug ?? item._id;
3491
+ if (isI18nValue(slug)) {
3492
+ slug = resolveI18nValue(slug, locale, i18nConfig);
3493
+ }
3494
+ return urlPattern.replace("{{slug}}", String(slug));
3495
+ }
3496
+ function scanAssets(projectRoot) {
3497
+ const assets = [];
3498
+ const assetDirs = [
3499
+ { dir: "images", type: "image" },
3500
+ { dir: "fonts", type: "font" },
3501
+ { dir: "videos", type: "video" },
3502
+ { dir: "assets", type: "file" }
3503
+ ];
3504
+ for (const { dir, type } of assetDirs) {
3505
+ const fullDir = join2(projectRoot, dir);
3506
+ if (!existsSync2(fullDir)) continue;
3507
+ const files = scanJSONFiles2(fullDir).map((f) => f.replace(".json", ""));
3508
+ const allFiles = scanAllFiles(fullDir);
3509
+ for (const file of allFiles) {
3510
+ assets.push({
3511
+ localPath: `${dir}/${file}`,
3512
+ type,
3513
+ fileName: file.split("/").pop()
3514
+ });
3515
+ }
3516
+ }
3517
+ return assets;
3518
+ }
3519
+ function scanAllFiles(dir, prefix = "") {
3520
+ const results = [];
3521
+ if (!existsSync2(dir)) return results;
3522
+ const entries = readdirSync2(dir, { withFileTypes: true });
3523
+ for (const entry of entries) {
3524
+ const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
3525
+ if (entry.isFile()) {
3526
+ results.push(relativePath);
3527
+ } else if (entry.isDirectory()) {
3528
+ results.push(...scanAllFiles(join2(dir, entry.name), relativePath));
3529
+ }
3530
+ }
3531
+ return results;
3532
+ }
3533
+ function extractCSSVariables(themeColorCSS, variablesCSS) {
3534
+ const vars = {};
3535
+ const regex = /--([\w-]+)\s*:\s*([^;]+)/g;
3536
+ for (const css of [themeColorCSS, variablesCSS]) {
3537
+ let match;
3538
+ while ((match = regex.exec(css)) !== null) {
3539
+ vars[`--${match[1]}`] = match[2].trim();
3540
+ }
3541
+ }
3542
+ return vars;
3543
+ }
3544
+ async function buildWebflowPayload(projectRoot) {
3545
+ configService.reset();
3546
+ const projectConfig = await loadProjectConfig();
3547
+ const siteUrl = projectConfig.siteUrl?.replace(/\/$/, "") || "";
3548
+ const i18nConfig = await loadI18nConfig();
3549
+ await migrateTemplatesDirectory();
3550
+ const { components } = await loadComponentDirectory(projectPaths.components());
3551
+ const globalComponents = {};
3552
+ components.forEach((value, key) => {
3553
+ globalComponents[key] = value;
3554
+ });
3555
+ const cmsProvider = new FileSystemCMSProvider(projectPaths.templates(), projectPaths.cms());
3556
+ const cmsService = new CMSService(cmsProvider);
3557
+ await cmsService.initialize();
3558
+ const themeConfig = await colorService.loadThemeConfig();
3559
+ const variablesConfig = await variableService.loadConfig();
3560
+ const breakpoints = await loadBreakpointConfig();
3561
+ const responsiveScales = await loadResponsiveScalesConfig();
3562
+ await configService.load();
3563
+ const pagesDir = projectPaths.pages();
3564
+ if (!existsSync2(pagesDir)) {
3565
+ return emptyPayload();
3566
+ }
3567
+ const pageFiles = scanJSONFiles2(pagesDir);
3568
+ if (pageFiles.length === 0) {
3569
+ return emptyPayload();
3570
+ }
3571
+ const slugMappings = [];
3572
+ for (const file of pageFiles) {
3573
+ const pageName = file.replace(".json", "");
3574
+ const basePath = mapPageNameToPath(pageName);
3575
+ const pageContent = await loadJSONFile(join2(pagesDir, file));
3576
+ if (!pageContent) continue;
3577
+ try {
3578
+ const pageData = parseJSON(pageContent);
3579
+ if (pageData.meta?.slugs) {
3580
+ const pageId = basePath === "/" ? "index" : basePath.substring(1);
3581
+ slugMappings.push({ pageId, slugs: pageData.meta.slugs });
3582
+ }
3583
+ } catch {
3584
+ }
3585
+ }
3586
+ const allPages = [];
3587
+ const allStyleClasses = /* @__PURE__ */ new Map();
3588
+ for (const file of pageFiles) {
3589
+ const pageName = file.replace(".json", "");
3590
+ const basePath = mapPageNameToPath(pageName);
3591
+ const pageContent = await loadJSONFile(join2(pagesDir, file));
3592
+ if (!pageContent) continue;
3593
+ try {
3594
+ const pageData = parseJSON(pageContent);
3595
+ if (pageData.meta?.draft === true) continue;
3596
+ const slugs = pageData.meta?.slugs;
3597
+ for (const localeConfig of i18nConfig.locales) {
3598
+ const locale = localeConfig.code;
3599
+ const isDefault = locale === i18nConfig.defaultLocale;
3600
+ let slug;
3601
+ if (slugs && slugs[locale]) {
3602
+ slug = slugs[locale];
3603
+ } else if (basePath === "/") {
3604
+ slug = "";
3605
+ } else {
3606
+ slug = basePath.substring(1);
3607
+ }
3608
+ const urlPath = isDefault ? slug === "" ? "/" : `/${slug}` : slug === "" ? `/${locale}` : `/${locale}/${slug}`;
3609
+ const result = await renderPageSSR(
3610
+ pageData,
3611
+ globalComponents,
3612
+ urlPath,
3613
+ siteUrl,
3614
+ locale,
3615
+ i18nConfig,
3616
+ slugMappings,
3617
+ void 0,
3618
+ cmsService,
3619
+ true
3620
+ );
3621
+ const ctx = {
3622
+ globalComponents,
3623
+ elementPath: [0],
3624
+ fileType: "page",
3625
+ fileName: pageName,
3626
+ breakpoints,
3627
+ styleClasses: allStyleClasses
3628
+ };
3629
+ const body = pageData.root || pageData.node;
3630
+ const elements = body ? nodeToWebflow(body, ctx) : [];
3631
+ allPages.push({
3632
+ title: result.title,
3633
+ slug: slug || "index",
3634
+ metaDescription: typeof pageData.meta?.description === "string" ? pageData.meta.description : void 0,
3635
+ elements,
3636
+ locale
3637
+ });
3638
+ }
3639
+ } catch (error) {
3640
+ console.error(`Error processing ${basePath}:`, error?.message);
3641
+ }
3642
+ }
3643
+ const templatesDir = projectPaths.templates();
3644
+ const cmsCollections = [];
3645
+ if (existsSync2(templatesDir)) {
3646
+ const templateFiles = readdirSync2(templatesDir).filter((f) => f.endsWith(".json"));
3647
+ for (const file of templateFiles) {
3648
+ const templateContent = await loadJSONFile(join2(templatesDir, file));
3649
+ if (!templateContent) continue;
3650
+ try {
3651
+ const pageData = parseJSON(templateContent);
3652
+ if (pageData.meta?.draft === true) continue;
3653
+ if (!isCMSPage2(pageData)) continue;
3654
+ const cmsSchema = pageData.meta.cms;
3655
+ const items = await cmsService.queryItems({ collection: cmsSchema.id });
3656
+ const fields = [];
3657
+ if (cmsSchema.fields) {
3658
+ for (const [fieldName, fieldDef] of Object.entries(cmsSchema.fields)) {
3659
+ fields.push({
3660
+ name: fieldDef.label || fieldName,
3661
+ slug: fieldName,
3662
+ type: mapCMSFieldType(fieldDef.type),
3663
+ required: fieldDef.required,
3664
+ options: fieldDef.options
3665
+ });
3666
+ }
3667
+ }
3668
+ const resolvedItems = [];
3669
+ for (const item of items) {
3670
+ const resolved = {};
3671
+ for (const [key, value] of Object.entries(item)) {
3672
+ if (isI18nValue(value)) {
3673
+ resolved[key] = resolveI18nValue(value, i18nConfig.defaultLocale, i18nConfig);
3674
+ } else {
3675
+ resolved[key] = value;
3676
+ }
3677
+ }
3678
+ resolvedItems.push(resolved);
3679
+ }
3680
+ cmsCollections.push({
3681
+ name: cmsSchema.id,
3682
+ slug: cmsSchema.id,
3683
+ urlPattern: cmsSchema.urlPattern,
3684
+ fields,
3685
+ items: resolvedItems
3686
+ });
3687
+ for (const item of items) {
3688
+ for (const localeConfig of i18nConfig.locales) {
3689
+ const locale = localeConfig.code;
3690
+ if (isItemDraftForLocale(item, locale)) continue;
3691
+ const itemPath = buildCMSItemPath(cmsSchema.urlPattern, item, cmsSchema.slugField, locale, i18nConfig);
3692
+ const itemWithUrl = { ...item, _url: itemPath };
3693
+ const result = await renderPageSSR(
3694
+ pageData,
3695
+ globalComponents,
3696
+ itemPath,
3697
+ siteUrl,
3698
+ locale,
3699
+ i18nConfig,
3700
+ slugMappings,
3701
+ { cms: itemWithUrl },
3702
+ cmsService,
3703
+ true
3704
+ );
3705
+ const ctx = {
3706
+ globalComponents,
3707
+ elementPath: [0],
3708
+ fileType: "page",
3709
+ fileName: file.replace(".json", ""),
3710
+ breakpoints,
3711
+ styleClasses: allStyleClasses
3712
+ };
3713
+ const body = pageData.root || pageData.node;
3714
+ const cmsProps = { cms: itemWithUrl };
3715
+ const elements = body ? nodeToWebflow(body, ctx, cmsProps) : [];
3716
+ const slug = itemPath.startsWith("/") ? itemPath.substring(1) : itemPath;
3717
+ allPages.push({
3718
+ title: result.title,
3719
+ slug,
3720
+ elements,
3721
+ locale
3722
+ });
3723
+ }
3724
+ }
3725
+ } catch (error) {
3726
+ console.error(`Error processing template ${file}:`, error?.message);
3727
+ }
3728
+ }
3729
+ }
3730
+ const themeColorCSS = generateThemeColorVariablesCSS(themeConfig);
3731
+ const variablesCSS = generateVariablesCSS(variablesConfig, breakpoints, responsiveScales);
3732
+ const cssVariables = extractCSSVariables(themeColorCSS, variablesCSS);
3733
+ const assets = scanAssets(projectPaths.project);
3734
+ return {
3735
+ version: 1,
3736
+ exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
3737
+ pages: allPages,
3738
+ styles: Array.from(allStyleClasses.values()),
3739
+ cms: cmsCollections,
3740
+ assets,
3741
+ cssVariables: Object.keys(cssVariables).length > 0 ? cssVariables : void 0
3742
+ };
3743
+ }
3744
+ function emptyPayload() {
3745
+ return {
3746
+ version: 1,
3747
+ exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
3748
+ pages: [],
3749
+ styles: [],
3750
+ cms: [],
3751
+ assets: []
3752
+ };
3753
+ }
3754
+
1946
3755
  // lib/server/index.ts
1947
3756
  init_constants();
1948
3757
  export {
@@ -1970,6 +3779,7 @@ export {
1970
3779
  buildImageMetadataMap,
1971
3780
  buildLineMap,
1972
3781
  buildStaticPages,
3782
+ buildWebflowPayload,
1973
3783
  bundleFile,
1974
3784
  clearJSValidationCache,
1975
3785
  collectComponentCSS,