meno-core 1.0.53 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (415) hide show
  1. package/.claude/settings.local.json +1 -3
  2. package/bin/cli.ts +66 -261
  3. package/build-astro.ts +150 -128
  4. package/dist/bin/cli.js +42 -185
  5. package/dist/bin/cli.js.map +2 -2
  6. package/dist/chunks/chunk-2AR55GYH.js +42 -0
  7. package/dist/chunks/chunk-2AR55GYH.js.map +7 -0
  8. package/dist/chunks/{chunk-JGWFTO6P.js → chunk-2IIQK7T3.js} +2624 -1389
  9. package/dist/chunks/chunk-2IIQK7T3.js.map +7 -0
  10. package/dist/chunks/chunk-4ZRU52J2.js +169 -0
  11. package/dist/chunks/chunk-4ZRU52J2.js.map +7 -0
  12. package/dist/chunks/{chunk-UB44F4Z2.js → chunk-5X4HCN7J.js} +5 -7
  13. package/dist/chunks/chunk-5X4HCN7J.js.map +7 -0
  14. package/dist/chunks/{chunk-WQFG7PAH.js → chunk-7CCVOL5M.js} +15 -19
  15. package/dist/chunks/chunk-7CCVOL5M.js.map +7 -0
  16. package/dist/chunks/{chunk-YBLHKYFF.js → chunk-AR7LHIFY.js} +8 -2
  17. package/dist/chunks/chunk-AR7LHIFY.js.map +7 -0
  18. package/dist/chunks/{chunk-O3NAGJP4.js → chunk-NUP7H7D3.js} +135 -41
  19. package/dist/chunks/chunk-NUP7H7D3.js.map +7 -0
  20. package/dist/chunks/{chunk-X754AHS5.js → chunk-QWTQZHG3.js} +194 -90
  21. package/dist/chunks/chunk-QWTQZHG3.js.map +7 -0
  22. package/dist/chunks/{chunk-H3GJ4H2U.js → chunk-XTKNX4FW.js} +216 -87
  23. package/dist/chunks/chunk-XTKNX4FW.js.map +7 -0
  24. package/dist/chunks/{fs-JGINUXGL.js → fs-G3ANWEFP.js} +2 -2
  25. package/dist/lib/client/index.js +1003 -475
  26. package/dist/lib/client/index.js.map +3 -3
  27. package/dist/lib/server/index.js +14393 -6371
  28. package/dist/lib/server/index.js.map +4 -4
  29. package/dist/lib/shared/index.js +342 -101
  30. package/dist/lib/shared/index.js.map +4 -4
  31. package/dist/lib/shared/richtext/index.js +1 -1
  32. package/dist/lib/test-utils/index.js +64 -80
  33. package/dist/lib/test-utils/index.js.map +2 -2
  34. package/entries/client-router.tsx +10 -8
  35. package/lib/client/ClientInitializer.ts +8 -8
  36. package/lib/client/ErrorBoundary.test.tsx +246 -186
  37. package/lib/client/ErrorBoundary.tsx +183 -132
  38. package/lib/client/componentRegistry.test.ts +96 -108
  39. package/lib/client/componentRegistry.ts +1 -3
  40. package/lib/client/contexts/ThemeContext.tsx +3 -2
  41. package/lib/client/core/ComponentBuilder.test.ts +663 -560
  42. package/lib/client/core/ComponentBuilder.ts +427 -155
  43. package/lib/client/core/ComponentRenderer.test.tsx +2 -4
  44. package/lib/client/core/ComponentRenderer.tsx +46 -33
  45. package/lib/client/core/builders/embedBuilder.ts +242 -54
  46. package/lib/client/core/builders/linkBuilder.ts +72 -45
  47. package/lib/client/core/builders/linkNodeBuilder.ts +79 -54
  48. package/lib/client/core/builders/listBuilder.ts +155 -89
  49. package/lib/client/core/builders/localeListBuilder.ts +97 -62
  50. package/lib/client/core/builders/types.ts +5 -5
  51. package/lib/client/core/cmsTemplateProcessor.ts +8 -9
  52. package/lib/client/elementRegistry.ts +3 -3
  53. package/lib/client/fontFamiliesService.test.ts +2 -10
  54. package/lib/client/fontFamiliesService.ts +3 -3
  55. package/lib/client/hmr/HMRManager.tsx +47 -30
  56. package/lib/client/hmrCssReload.ts +49 -14
  57. package/lib/client/hmrWebSocket.ts +14 -19
  58. package/lib/client/hooks/useColorVariables.test.ts +21 -21
  59. package/lib/client/hooks/useColorVariables.ts +23 -18
  60. package/lib/client/hooks/usePropertyAutocomplete.ts +4 -6
  61. package/lib/client/hooks/useVariables.ts +6 -4
  62. package/lib/client/hydration/HydrationUtils.test.ts +24 -25
  63. package/lib/client/hydration/HydrationUtils.ts +3 -4
  64. package/lib/client/i18nConfigService.test.ts +2 -7
  65. package/lib/client/i18nConfigService.ts +2 -2
  66. package/lib/client/meno-filter/MenoFilter.test.ts +47 -49
  67. package/lib/client/meno-filter/MenoFilter.ts +14 -15
  68. package/lib/client/meno-filter/bindings.ts +18 -43
  69. package/lib/client/meno-filter/init.ts +4 -4
  70. package/lib/client/meno-filter/renderer.ts +30 -32
  71. package/lib/client/meno-filter/script.generated.ts +1 -3
  72. package/lib/client/meno-filter/ui.ts +4 -6
  73. package/lib/client/meno-filter/updates.ts +18 -23
  74. package/lib/client/meno-filter/utils.ts +1 -1
  75. package/lib/client/navigation.test.ts +174 -178
  76. package/lib/client/navigation.ts +0 -1
  77. package/lib/client/responsiveStyleResolver.knownTokens.test.ts +71 -0
  78. package/lib/client/responsiveStyleResolver.test.ts +234 -232
  79. package/lib/client/responsiveStyleResolver.ts +74 -20
  80. package/lib/client/routing/RouteLoader.test.ts +24 -25
  81. package/lib/client/routing/RouteLoader.ts +27 -35
  82. package/lib/client/routing/Router.tsx +210 -153
  83. package/lib/client/scripts/ScriptExecutor.test.ts +157 -158
  84. package/lib/client/scripts/ScriptExecutor.ts +18 -36
  85. package/lib/client/services/PrefetchService.test.ts +3 -3
  86. package/lib/client/services/PrefetchService.ts +23 -29
  87. package/lib/client/styleProcessor.test.ts +9 -9
  88. package/lib/client/styleProcessor.ts +24 -15
  89. package/lib/client/styles/StyleInjector.test.ts +122 -115
  90. package/lib/client/styles/StyleInjector.ts +10 -7
  91. package/lib/client/styles/UtilityClassCollector.ts +62 -28
  92. package/lib/client/styles/cspNonce.test.ts +2 -5
  93. package/lib/client/templateEngine.test.ts +554 -488
  94. package/lib/client/templateEngine.ts +203 -73
  95. package/lib/client/theme.test.ts +1 -1
  96. package/lib/client/theme.ts +0 -1
  97. package/lib/client/utils/toast.ts +0 -1
  98. package/lib/server/__integration__/api-routes.test.ts +8 -4
  99. package/lib/server/__integration__/cms-integration.test.ts +1 -4
  100. package/lib/server/__integration__/server-lifecycle.test.ts +4 -5
  101. package/lib/server/__integration__/ssr-rendering.test.ts +47 -37
  102. package/lib/server/__integration__/static-assets.test.ts +3 -3
  103. package/lib/server/__integration__/test-helpers.ts +87 -74
  104. package/lib/server/ab/generateFunctions.ts +12 -10
  105. package/lib/server/astro/cmsPageEmitter.ts +26 -42
  106. package/lib/server/astro/componentEmitter.ts +35 -43
  107. package/lib/server/astro/cssCollector.ts +10 -26
  108. package/lib/server/astro/nodeToAstro.test.ts +1754 -34
  109. package/lib/server/astro/nodeToAstro.ts +232 -216
  110. package/lib/server/astro/normalizeOrphanTemplateProps.test.ts +115 -119
  111. package/lib/server/astro/normalizeOrphanTemplateProps.ts +11 -18
  112. package/lib/server/astro/pageEmitter.ts +9 -13
  113. package/lib/server/astro/tailwindMapper.test.ts +10 -37
  114. package/lib/server/astro/tailwindMapper.ts +33 -40
  115. package/lib/server/astro/templateTransformer.ts +17 -20
  116. package/lib/server/createServer.ts +7 -19
  117. package/lib/server/cssGenerator.test.ts +35 -44
  118. package/lib/server/cssGenerator.ts +8 -19
  119. package/lib/server/fileWatcher.test.ts +164 -13
  120. package/lib/server/fileWatcher.ts +209 -124
  121. package/lib/server/index.ts +14 -17
  122. package/lib/server/jsonLoader.test.ts +39 -2
  123. package/lib/server/jsonLoader.ts +39 -34
  124. package/lib/server/middleware/cors.test.ts +21 -21
  125. package/lib/server/middleware/cors.ts +28 -4
  126. package/lib/server/middleware/errorHandler.test.ts +7 -5
  127. package/lib/server/middleware/errorHandler.ts +3 -8
  128. package/lib/server/middleware/index.ts +0 -1
  129. package/lib/server/middleware/logger.test.ts +10 -8
  130. package/lib/server/middleware/logger.ts +17 -29
  131. package/lib/server/migrateTemplates.ts +3 -3
  132. package/lib/server/pageCache.test.ts +76 -77
  133. package/lib/server/pageCache.ts +0 -1
  134. package/lib/server/projectContext.ts +5 -4
  135. package/lib/server/providers/fileSystemCMSProvider.test.ts +142 -99
  136. package/lib/server/providers/fileSystemCMSProvider.ts +62 -38
  137. package/lib/server/routes/api/cms.test.ts +26 -14
  138. package/lib/server/routes/api/cms.ts +9 -14
  139. package/lib/server/routes/api/colors.test.ts +2 -2
  140. package/lib/server/routes/api/components.ts +31 -35
  141. package/lib/server/routes/api/config.ts +3 -4
  142. package/lib/server/routes/api/core-routes.ts +49 -76
  143. package/lib/server/routes/api/enums.test.ts +2 -2
  144. package/lib/server/routes/api/functions.ts +9 -17
  145. package/lib/server/routes/api/index.ts +3 -6
  146. package/lib/server/routes/api/pages.ts +8 -11
  147. package/lib/server/routes/api/shared.test.ts +1 -1
  148. package/lib/server/routes/api/shared.ts +12 -9
  149. package/lib/server/routes/api/variables.test.ts +3 -5
  150. package/lib/server/routes/api/variables.ts +1 -1
  151. package/lib/server/routes/index.ts +25 -28
  152. package/lib/server/routes/pages.ts +35 -31
  153. package/lib/server/routes/static.ts +17 -6
  154. package/lib/server/runtime/bundler.ts +98 -61
  155. package/lib/server/runtime/fs.ts +16 -26
  156. package/lib/server/runtime/httpServer.ts +8 -12
  157. package/lib/server/services/ColorService.test.ts +3 -3
  158. package/lib/server/services/ColorService.ts +37 -29
  159. package/lib/server/services/EnumService.test.ts +4 -8
  160. package/lib/server/services/EnumService.ts +11 -3
  161. package/lib/server/services/VariableService.test.ts +8 -12
  162. package/lib/server/services/VariableService.ts +11 -3
  163. package/lib/server/services/cmsService.test.ts +158 -120
  164. package/lib/server/services/cmsService.ts +36 -146
  165. package/lib/server/services/componentService.test.ts +125 -45
  166. package/lib/server/services/componentService.ts +70 -46
  167. package/lib/server/services/configService.test.ts +22 -45
  168. package/lib/server/services/configService.ts +50 -27
  169. package/lib/server/services/fileWatcherService.ts +42 -8
  170. package/lib/server/services/index.ts +0 -1
  171. package/lib/server/services/pageService.test.ts +8 -10
  172. package/lib/server/services/pageService.ts +32 -18
  173. package/lib/server/ssr/attributeBuilder.ts +18 -8
  174. package/lib/server/ssr/buildErrorOverlay.ts +11 -12
  175. package/lib/server/ssr/clientDataInjector.ts +7 -21
  176. package/lib/server/ssr/cmsSSRProcessor.ts +4 -8
  177. package/lib/server/ssr/cssCollector.ts +1 -1
  178. package/lib/server/ssr/errorOverlay.test.ts +1 -1
  179. package/lib/server/ssr/errorOverlay.ts +3 -9
  180. package/lib/server/ssr/htmlGenerator.nonce.test.ts +9 -14
  181. package/lib/server/ssr/htmlGenerator.test.ts +120 -44
  182. package/lib/server/ssr/htmlGenerator.ts +126 -91
  183. package/lib/server/ssr/imageMetadata.test.ts +3 -1
  184. package/lib/server/ssr/imageMetadata.ts +27 -21
  185. package/lib/server/ssr/jsCollector.test.ts +8 -18
  186. package/lib/server/ssr/jsCollector.ts +3 -8
  187. package/lib/server/ssr/liveReloadIntegration.test.ts +76 -36
  188. package/lib/server/ssr/metaTagGenerator.ts +2 -2
  189. package/lib/server/ssr/ssrRenderer.branches.test.ts +1103 -0
  190. package/lib/server/ssr/ssrRenderer.test.ts +316 -256
  191. package/lib/server/ssr/ssrRenderer.ts +795 -243
  192. package/lib/server/ssrRenderer.test.ts +1048 -954
  193. package/lib/server/utils/jsonLineMapper.test.ts +28 -28
  194. package/lib/server/utils/jsonLineMapper.ts +7 -7
  195. package/lib/server/validateStyleCoverage.ts +18 -21
  196. package/lib/server/websocketManager.ts +28 -19
  197. package/lib/shared/attributeNodeUtils.test.ts +15 -15
  198. package/lib/shared/attributeNodeUtils.ts +5 -12
  199. package/lib/shared/breakpoints.test.ts +5 -5
  200. package/lib/shared/breakpoints.ts +4 -11
  201. package/lib/shared/cmsQuery.test.ts +81 -0
  202. package/lib/shared/cmsQuery.ts +92 -0
  203. package/lib/shared/cmsQueryParser.test.ts +50 -42
  204. package/lib/shared/cmsQueryParser.ts +60 -38
  205. package/lib/shared/colorConversions.ts +9 -9
  206. package/lib/shared/colorVariableUtils.test.ts +27 -5
  207. package/lib/shared/colorVariableUtils.ts +21 -12
  208. package/lib/shared/componentRefs.ts +1 -5
  209. package/lib/shared/constants.test.ts +3 -3
  210. package/lib/shared/constants.ts +8 -8
  211. package/lib/shared/cssGeneration.test.ts +285 -144
  212. package/lib/shared/cssGeneration.ts +217 -531
  213. package/lib/shared/cssNamedColors.ts +152 -30
  214. package/lib/shared/cssProperties.test.ts +69 -70
  215. package/lib/shared/cssProperties.ts +298 -114
  216. package/lib/shared/elementClassName.test.ts +109 -109
  217. package/lib/shared/elementClassName.ts +3 -3
  218. package/lib/shared/elementUtils.ts +12 -16
  219. package/lib/shared/errorLogger.test.ts +12 -12
  220. package/lib/shared/errorLogger.ts +2 -10
  221. package/lib/shared/errors.test.ts +2 -13
  222. package/lib/shared/errors.ts +2 -8
  223. package/lib/shared/expressionEvaluator.test.ts +145 -0
  224. package/lib/shared/expressionEvaluator.ts +52 -24
  225. package/lib/shared/fontLoader.test.ts +32 -18
  226. package/lib/shared/fontLoader.ts +2 -2
  227. package/lib/shared/friendlyError.ts +2 -3
  228. package/lib/shared/gradientUtils.test.ts +3 -7
  229. package/lib/shared/gradientUtils.ts +17 -14
  230. package/lib/shared/hrefRefs.test.ts +2 -2
  231. package/lib/shared/hrefRefs.ts +2 -10
  232. package/lib/shared/i18n.test.ts +4 -4
  233. package/lib/shared/i18n.ts +18 -38
  234. package/lib/shared/index.ts +27 -0
  235. package/lib/shared/inlineSvgStyleRules.ts +14 -13
  236. package/lib/shared/interactiveStyleMappings.test.ts +32 -54
  237. package/lib/shared/interactiveStyleMappings.ts +11 -24
  238. package/lib/shared/interactiveStyles.test.ts +165 -188
  239. package/lib/shared/interfaces/contentProvider.ts +1 -1
  240. package/lib/shared/itemTemplateUtils.test.ts +11 -17
  241. package/lib/shared/itemTemplateUtils.ts +27 -39
  242. package/lib/shared/jsonRepair.ts +16 -10
  243. package/lib/shared/libraryLoader.test.ts +21 -55
  244. package/lib/shared/libraryLoader.ts +7 -22
  245. package/lib/shared/linkUtils.ts +1 -1
  246. package/lib/shared/logger.test.ts +66 -0
  247. package/lib/shared/logger.ts +94 -0
  248. package/lib/shared/markdown.ts +25 -0
  249. package/lib/shared/netlifyLocale404.test.ts +179 -0
  250. package/lib/shared/netlifyLocale404.ts +110 -0
  251. package/lib/shared/nodeUtils.test.ts +28 -16
  252. package/lib/shared/nodeUtils.ts +76 -23
  253. package/lib/shared/pathArrayUtils.test.ts +1 -2
  254. package/lib/shared/pathArrayUtils.ts +1 -1
  255. package/lib/shared/pathSecurity.ts +2 -2
  256. package/lib/shared/pathUtils.test.ts +4 -6
  257. package/lib/shared/pathUtils.ts +42 -49
  258. package/lib/shared/paths/Path.test.ts +2 -2
  259. package/lib/shared/paths/Path.ts +0 -1
  260. package/lib/shared/paths/PathConverter.test.ts +1 -1
  261. package/lib/shared/paths/PathConverter.ts +17 -20
  262. package/lib/shared/paths/PathUtils.ts +13 -13
  263. package/lib/shared/paths/PathValidator.test.ts +2 -15
  264. package/lib/shared/paths/PathValidator.ts +12 -10
  265. package/lib/shared/paths/index.ts +1 -2
  266. package/lib/shared/permissions.test.ts +168 -0
  267. package/lib/shared/permissions.ts +162 -0
  268. package/lib/shared/propResolver.test.ts +240 -244
  269. package/lib/shared/propResolver.ts +15 -26
  270. package/lib/shared/pxToRem.test.ts +7 -6
  271. package/lib/shared/pxToRem.ts +2 -5
  272. package/lib/shared/registry/BaseNodeTypeRegistry.test.ts +12 -6
  273. package/lib/shared/registry/ClientRegistry.ts +0 -2
  274. package/lib/shared/registry/ComponentRegistry.test.ts +46 -32
  275. package/lib/shared/registry/ComponentRegistry.ts +12 -9
  276. package/lib/shared/registry/NodeTypeDefinition.ts +16 -9
  277. package/lib/shared/registry/RegistryManager.ts +1 -2
  278. package/lib/shared/registry/SSRRegistry.ts +0 -2
  279. package/lib/shared/registry/createNodeType.ts +7 -9
  280. package/lib/shared/registry/defineNodeType.ts +2 -6
  281. package/lib/shared/registry/fieldPresets.ts +3 -1
  282. package/lib/shared/registry/index.ts +0 -1
  283. package/lib/shared/registry/nodeTypes/ComponentInstanceNodeType.ts +14 -15
  284. package/lib/shared/registry/nodeTypes/CustomNodeType.ts +103 -0
  285. package/lib/shared/registry/nodeTypes/EmbedNodeType.ts +18 -11
  286. package/lib/shared/registry/nodeTypes/HtmlNodeType.ts +48 -19
  287. package/lib/shared/registry/nodeTypes/IslandNodeType.ts +129 -0
  288. package/lib/shared/registry/nodeTypes/LinkNodeType.ts +21 -19
  289. package/lib/shared/registry/nodeTypes/ListNodeType.ts +117 -77
  290. package/lib/shared/registry/nodeTypes/LocaleListNodeType.ts +27 -21
  291. package/lib/shared/registry/nodeTypes/MarkdownNodeType.ts +78 -0
  292. package/lib/shared/registry/nodeTypes/SlotMarkerType.ts +12 -8
  293. package/lib/shared/registry/nodeTypes/index.ts +26 -3
  294. package/lib/shared/responsiveScaling.test.ts +16 -34
  295. package/lib/shared/responsiveScaling.ts +61 -42
  296. package/lib/shared/responsiveStyleUtils.test.ts +0 -1
  297. package/lib/shared/responsiveStyleUtils.ts +11 -13
  298. package/lib/shared/richtext/htmlToTiptap.test.ts +139 -130
  299. package/lib/shared/richtext/htmlToTiptap.ts +2 -4
  300. package/lib/shared/richtext/tiptapToHtml.test.ts +5 -6
  301. package/lib/shared/richtext/types.ts +1 -8
  302. package/lib/shared/slugTranslator.test.ts +13 -13
  303. package/lib/shared/slugTranslator.ts +12 -16
  304. package/lib/shared/slugify.ts +9 -15
  305. package/lib/shared/styleNodeUtils.test.ts +8 -8
  306. package/lib/shared/styleNodeUtils.ts +18 -20
  307. package/lib/shared/styleUtils.test.ts +87 -61
  308. package/lib/shared/styleUtils.ts +5 -6
  309. package/lib/shared/themeDefaults.test.ts +11 -14
  310. package/lib/shared/themeDefaults.ts +3 -4
  311. package/lib/shared/tree/PathBuilder.test.ts +63 -145
  312. package/lib/shared/tree/PathBuilder.ts +59 -47
  313. package/lib/shared/treePathUtils.test.ts +51 -12
  314. package/lib/shared/treePathUtils.ts +91 -60
  315. package/lib/shared/types/api.ts +105 -8
  316. package/lib/shared/types/cms.test.ts +34 -0
  317. package/lib/shared/types/cms.ts +90 -34
  318. package/lib/shared/types/comment.ts +49 -16
  319. package/lib/shared/types/components.ts +51 -25
  320. package/lib/shared/types/errors.test.ts +1 -6
  321. package/lib/shared/types/errors.ts +3 -7
  322. package/lib/shared/types/experiments.ts +28 -28
  323. package/lib/shared/types/index.ts +25 -2
  324. package/lib/shared/types/permissions.ts +139 -0
  325. package/lib/shared/types/styles.ts +0 -1
  326. package/lib/shared/types/variables.test.ts +4 -13
  327. package/lib/shared/types/variables.ts +51 -30
  328. package/lib/shared/types.ts +1 -2
  329. package/lib/shared/utilityClassConfig.ts +751 -321
  330. package/lib/shared/utilityClassMapper.durability.test.ts +117 -0
  331. package/lib/shared/utilityClassMapper.test.ts +433 -95
  332. package/lib/shared/utilityClassMapper.ts +322 -249
  333. package/lib/shared/utilityClassNames.test.ts +38 -0
  334. package/lib/shared/utilityClassNames.ts +458 -0
  335. package/lib/shared/utils.test.ts +2 -10
  336. package/lib/shared/utils.ts +19 -10
  337. package/lib/shared/validation/cmsValidators.ts +2 -1
  338. package/lib/shared/validation/commentValidators.test.ts +53 -0
  339. package/lib/shared/validation/commentValidators.ts +12 -1
  340. package/lib/shared/validation/index.ts +1 -0
  341. package/lib/shared/validation/permissionsValidators.test.ts +49 -0
  342. package/lib/shared/validation/permissionsValidators.ts +49 -0
  343. package/lib/shared/validation/propValidator.test.ts +19 -21
  344. package/lib/shared/validation/propValidator.ts +13 -18
  345. package/lib/shared/validation/schemas.test.ts +64 -32
  346. package/lib/shared/validation/schemas.ts +616 -345
  347. package/lib/shared/validation/validators.test.ts +3 -8
  348. package/lib/shared/validation/validators.ts +89 -68
  349. package/lib/shared/viewportUnits.integration.test.ts +14 -10
  350. package/lib/shared/viewportUnits.test.ts +24 -23
  351. package/lib/shared/viewportUnits.ts +12 -5
  352. package/lib/test-utils/dom-setup.ts +1 -1
  353. package/lib/test-utils/factories/ConsoleMockFactory.ts +10 -14
  354. package/lib/test-utils/factories/DomMockFactory.ts +17 -23
  355. package/lib/test-utils/factories/EventMockFactory.ts +7 -13
  356. package/lib/test-utils/factories/FetchMockFactory.ts +39 -57
  357. package/lib/test-utils/factories/ServerMockFactory.ts +6 -10
  358. package/lib/test-utils/factories/StoreMockFactory.ts +18 -29
  359. package/lib/test-utils/fixtures.ts +45 -45
  360. package/lib/test-utils/helpers/asyncHelpers.test.ts +15 -18
  361. package/lib/test-utils/helpers/asyncHelpers.ts +11 -20
  362. package/lib/test-utils/helpers.ts +1 -5
  363. package/lib/test-utils/index.ts +0 -4
  364. package/lib/test-utils/mockFactories.ts +13 -19
  365. package/lib/test-utils/mocks.ts +6 -4
  366. package/package.json +3 -5
  367. package/scripts/build-for-publish.mjs +2 -4
  368. package/scripts/build-meno-filter.ts +4 -7
  369. package/tsconfig.json +1 -0
  370. package/vite.config.ts +4 -4
  371. package/build-next.ts +0 -1361
  372. package/build-static.test.ts +0 -424
  373. package/build-static.ts +0 -1074
  374. package/dist/build-static.js +0 -36
  375. package/dist/chunks/chunk-2MHDV5BF.js +0 -251
  376. package/dist/chunks/chunk-2MHDV5BF.js.map +0 -7
  377. package/dist/chunks/chunk-AZQYF6KE.js +0 -559
  378. package/dist/chunks/chunk-AZQYF6KE.js.map +0 -7
  379. package/dist/chunks/chunk-GZHGVVW3.js +0 -322
  380. package/dist/chunks/chunk-GZHGVVW3.js.map +0 -7
  381. package/dist/chunks/chunk-H3GJ4H2U.js.map +0 -7
  382. package/dist/chunks/chunk-I7YIGZXT.js +0 -467
  383. package/dist/chunks/chunk-I7YIGZXT.js.map +0 -7
  384. package/dist/chunks/chunk-IGYR22T6.js +0 -6192
  385. package/dist/chunks/chunk-IGYR22T6.js.map +0 -7
  386. package/dist/chunks/chunk-JGP5A3Y5.js +0 -1136
  387. package/dist/chunks/chunk-JGP5A3Y5.js.map +0 -7
  388. package/dist/chunks/chunk-JGWFTO6P.js.map +0 -7
  389. package/dist/chunks/chunk-O3NAGJP4.js.map +0 -7
  390. package/dist/chunks/chunk-QB2LNO4W.js +0 -77
  391. package/dist/chunks/chunk-QB2LNO4W.js.map +0 -7
  392. package/dist/chunks/chunk-R6XHAFBF.js +0 -3821
  393. package/dist/chunks/chunk-R6XHAFBF.js.map +0 -7
  394. package/dist/chunks/chunk-UB44F4Z2.js.map +0 -7
  395. package/dist/chunks/chunk-WQFG7PAH.js.map +0 -7
  396. package/dist/chunks/chunk-X754AHS5.js.map +0 -7
  397. package/dist/chunks/chunk-YBLHKYFF.js.map +0 -7
  398. package/dist/chunks/configService-R3OGU2UD.js +0 -13
  399. package/dist/chunks/configService-R3OGU2UD.js.map +0 -7
  400. package/dist/chunks/constants-STK2YBIW.js +0 -46
  401. package/dist/chunks/constants-STK2YBIW.js.map +0 -7
  402. package/dist/chunks/fs-JGINUXGL.js.map +0 -7
  403. package/dist/entries/server-router.js +0 -67
  404. package/dist/entries/server-router.js.map +0 -7
  405. package/entries/server-router.tsx +0 -81
  406. package/lib/server/providers/fileSystemPageProvider.ts +0 -160
  407. package/lib/server/webflow/buildWebflow.ts +0 -635
  408. package/lib/server/webflow/index.ts +0 -23
  409. package/lib/server/webflow/nodeToWebflow.test.ts +0 -3170
  410. package/lib/server/webflow/nodeToWebflow.ts +0 -2435
  411. package/lib/server/webflow/styleMapper.test.ts +0 -389
  412. package/lib/server/webflow/styleMapper.ts +0 -695
  413. package/lib/server/webflow/templateWrapper.ts +0 -49
  414. package/lib/server/webflow/types.ts +0 -396
  415. /package/dist/{build-static.js.map → chunks/fs-G3ANWEFP.js.map} +0 -0
@@ -6,7 +6,6 @@
6
6
  import type {
7
7
  ComponentNode,
8
8
  ComponentDefinition,
9
- StructuredComponentDefinition,
10
9
  PropDefinition,
11
10
  HtmlNode,
12
11
  ComponentInstanceNode,
@@ -30,14 +29,20 @@ import type {
30
29
  import { responsiveStylesToTailwind, propertyToTailwind } from './tailwindMapper';
31
30
  import type { BreakpointConfig } from '../../shared/breakpoints';
32
31
  import type { ResponsiveScales } from '../../shared/responsiveScaling';
33
- import { generateElementClassName, type ElementClassContext } from '../../shared/elementClassName';
34
- import { isVoidElement, hasIf, hasChildren, isSlotContent as isSlotContentNode } from '../../shared/nodeUtils';
32
+ import { generateElementClassName } from '../../shared/elementClassName';
33
+ import { isVoidElement, hasIf } from '../../shared/nodeUtils';
35
34
  import { deepMergeStyles } from '../../shared/styleNodeUtils';
36
35
  import { NODE_TYPE, RAW_HTML_PREFIX } from '../../shared/constants';
37
36
  import { extractInteractiveStyleMappings, hasInteractiveStyleMappings } from '../../shared/interactiveStyleMappings';
38
37
  import type { ImageMetadataMap } from '../ssr/imageMetadata';
39
38
  import { isI18nValue, resolveI18nValue, DEFAULT_I18N_CONFIG, buildLocalizedPath } from '../../shared/i18n';
40
- import { transformCMSTemplate, isTemplateExpression, transformItemTemplate, replaceItemMetaVars, rewriteItemVar } from './templateTransformer';
39
+ import {
40
+ transformCMSTemplate,
41
+ isTemplateExpression,
42
+ transformItemTemplate,
43
+ replaceItemMetaVars,
44
+ rewriteItemVar,
45
+ } from './templateTransformer';
41
46
  import type { SlugMap } from '../../shared/slugTranslator';
42
47
  import { buildSlugIndex, translatePath } from '../../shared/slugTranslator';
43
48
  import { stripRawHtmlPrefixDeep, astroComponentName } from './astroEmitHelpers';
@@ -177,20 +182,12 @@ function localizeHref(href: string, ctx: AstroEmitContext): string {
177
182
 
178
183
  function isStyleMapping(value: unknown): value is StyleMapping {
179
184
  return (
180
- typeof value === 'object' &&
181
- value !== null &&
182
- '_mapping' in value &&
183
- (value as StyleMapping)._mapping === true
185
+ typeof value === 'object' && value !== null && '_mapping' in value && (value as StyleMapping)._mapping === true
184
186
  );
185
187
  }
186
188
 
187
189
  function isLinkMapping(value: unknown): value is LinkMapping {
188
- return (
189
- typeof value === 'object' &&
190
- value !== null &&
191
- '_mapping' in value &&
192
- (value as LinkMapping)._mapping === true
193
- );
190
+ return typeof value === 'object' && value !== null && '_mapping' in value && (value as LinkMapping)._mapping === true;
194
191
  }
195
192
 
196
193
  /**
@@ -199,20 +196,18 @@ function isLinkMapping(value: unknown): value is LinkMapping {
199
196
  function emitAttrValue(key: string, value: string, ctx: AstroEmitContext): string {
200
197
  if (ctx.cmsMode && /\{\{cms\./.test(value)) {
201
198
  const b = ctx.cmsEntryBinding || 'entry';
202
- const w = (expr: string) => ctx.cmsWrapFn ? `${ctx.cmsWrapFn}(${expr})` : expr;
199
+ const w = (expr: string) => (ctx.cmsWrapFn ? `${ctx.cmsWrapFn}(${expr})` : expr);
203
200
  const fullMatch = value.match(/^\{\{cms\.([^}]+)\}\}$/);
204
201
  if (fullMatch) {
205
- return `${key}={${w(`${b}.data.${fullMatch[1].trim()}`)}}`;
202
+ return `${key}={${w(`${b}.data.${(fullMatch[1] ?? '').trim()}`)}}`;
206
203
  }
207
- const replaced = value.replace(/\{\{cms\.([^}]+)\}\}/g, (_, fp) =>
208
- `\${${w(`${b}.data.${fp.trim()}`)}}`
209
- );
204
+ const replaced = value.replace(/\{\{cms\.([^}]+)\}\}/g, (_, fp) => `\${${w(`${b}.data.${fp.trim()}`)}}`);
210
205
  return `${key}={\`${replaced}\`}`;
211
206
  }
212
207
  if (ctx.listItemBinding && /\{\{/.test(value)) {
213
208
  const fullMatch = value.match(/^\{\{(.+)\}\}$/);
214
209
  if (fullMatch) {
215
- let expr = fullMatch[1].trim();
210
+ let expr = (fullMatch[1] ?? '').trim();
216
211
  expr = rewriteItemVar(expr, ctx.listItemBinding);
217
212
  if (ctx.listIndexVar) expr = replaceItemMetaVars(expr, ctx.listIndexVar, ctx.listSourceVar);
218
213
  return `${key}={${expr}}`;
@@ -229,17 +224,10 @@ function emitAttrValue(key: string, value: string, ctx: AstroEmitContext): strin
229
224
  }
230
225
 
231
226
  function isHtmlMapping(value: unknown): value is HtmlMapping {
232
- return (
233
- typeof value === 'object' &&
234
- value !== null &&
235
- '_mapping' in value &&
236
- (value as HtmlMapping)._mapping === true
237
- );
227
+ return typeof value === 'object' && value !== null && '_mapping' in value && (value as HtmlMapping)._mapping === true;
238
228
  }
239
229
 
240
- function isResponsiveStyle(
241
- style: StyleObject | ResponsiveStyleObject
242
- ): style is ResponsiveStyleObject {
230
+ function isResponsiveStyle(style: StyleObject | ResponsiveStyleObject): style is ResponsiveStyleObject {
243
231
  return 'base' in style || 'tablet' in style || 'mobile' in style;
244
232
  }
245
233
 
@@ -266,7 +254,7 @@ function escapeTemplateLiteral(s: string): string {
266
254
  * Get all style mappings from a style value, collecting { prop, property, values } tuples
267
255
  */
268
256
  function collectStyleMappings(
269
- style: StyleObject | ResponsiveStyleObject | undefined
257
+ style: StyleObject | ResponsiveStyleObject | undefined,
270
258
  ): Array<{ property: string; mapping: StyleMapping; breakpoint?: string }> {
271
259
  if (!style) return [];
272
260
  const result: Array<{ property: string; mapping: StyleMapping; breakpoint?: string }> = [];
@@ -299,17 +287,28 @@ function mappingToClassListEntries(
299
287
  mapping: StyleMapping,
300
288
  property: string,
301
289
  breakpointPrefix: string,
302
- ctx: AstroEmitContext
290
+ ctx: AstroEmitContext,
303
291
  ): string[] {
304
292
  const entries: string[] = [];
305
293
  const values = Object.entries(mapping.values);
306
294
  if (values.length === 0) return entries;
307
295
 
296
+ // Mappings reference component props by bare identifier. Those identifiers
297
+ // only exist inside a component definition — on a page the reference would
298
+ // be an undefined variable (an Astro build error). Mirror the runtime, where
299
+ // resolveStyleMapping() receives no props at page scope and drops the
300
+ // property: emit no conditional entries outside a component def.
301
+ if (!ctx.isComponentDef) return entries;
302
+
308
303
  // Generate utility class for each possible value
309
- const propRef = ctx.isComponentDef ? mapping.prop : mapping.prop;
304
+ const propRef = mapping.prop;
310
305
 
311
306
  if (values.length === 2) {
312
- const [[val1, css1], [val2, css2]] = values;
307
+ const entry1 = values[0];
308
+ const entry2 = values[1];
309
+ if (entry1 === undefined || entry2 === undefined) return entries;
310
+ const [val1, css1] = entry1;
311
+ const [, css2] = entry2;
313
312
  // Generate Tailwind classes for each possible mapping value
314
313
  const cls1 = getClassForValue(property, css1, breakpointPrefix);
315
314
  const cls2 = getClassForValue(property, css2, breakpointPrefix);
@@ -342,11 +341,7 @@ function coerceValue(val: string): string | boolean {
342
341
  /**
343
342
  * Generate a Tailwind class name for a single property:value pair
344
343
  */
345
- function getClassForValue(
346
- property: string,
347
- value: string | number,
348
- breakpointPrefix: string
349
- ): string | null {
344
+ function getClassForValue(property: string, value: string | number, breakpointPrefix: string): string | null {
350
345
  const twClass = propertyToTailwind(property, value);
351
346
  if (!twClass) return null;
352
347
  return breakpointPrefix ? `${breakpointPrefix}${twClass}` : twClass;
@@ -360,7 +355,7 @@ function buildClassAndStyleExpression(
360
355
  style: StyleObject | ResponsiveStyleObject | undefined,
361
356
  interactiveStyles: InteractiveStyles | undefined,
362
357
  elementClass: string | null,
363
- ctx: AstroEmitContext
358
+ ctx: AstroEmitContext,
364
359
  ): { classExpr: string; styleAttr: string } {
365
360
  // Static Tailwind classes from non-mapping styles
366
361
  const result = style
@@ -379,11 +374,12 @@ function buildClassAndStyleExpression(
379
374
  const mappings = collectStyleMappings(style);
380
375
 
381
376
  for (const { property, mapping, breakpoint } of mappings) {
382
- const bpValue = breakpoint === 'tablet'
383
- ? ctx.breakpoints.tablet?.breakpoint ?? 1024
384
- : breakpoint === 'mobile'
385
- ? ctx.breakpoints.mobile?.breakpoint ?? 540
386
- : 0;
377
+ const bpValue =
378
+ breakpoint === 'tablet'
379
+ ? (ctx.breakpoints.tablet?.breakpoint ?? 1024)
380
+ : breakpoint === 'mobile'
381
+ ? (ctx.breakpoints.mobile?.breakpoint ?? 540)
382
+ : 0;
387
383
  const prefix = bpValue ? `max-[${bpValue}px]:` : '';
388
384
  const entries = mappingToClassListEntries(mapping, property, prefix, ctx);
389
385
  conditionals.push(...entries);
@@ -392,17 +388,6 @@ function buildClassAndStyleExpression(
392
388
  // Build style attribute for dynamic (template expression) styles
393
389
  let styleAttr = '';
394
390
  if (Object.keys(dynamicStyles).length > 0 && ctx.isComponentDef) {
395
- const styleParts: string[] = [];
396
- for (const [cssProp, value] of Object.entries(dynamicStyles)) {
397
- // Convert {{propName}} to Astro expression in style
398
- const resolved = value.replace(/\{\{(.+?)\}\}/g, (_, expr) => {
399
- let trimmed = expr.trim();
400
- if (ctx.listItemBinding) trimmed = rewriteItemVar(trimmed, ctx.listItemBinding);
401
- if (ctx.listIndexVar) trimmed = replaceItemMetaVars(trimmed, ctx.listIndexVar, ctx.listSourceVar);
402
- return `\${${trimmed}}`;
403
- });
404
- styleParts.push(`${cssProp}: \${${resolved.includes('${') ? resolved.replace(/\$\{(.+?)\}/g, '$1') : `'${resolved}'`}}`);
405
- }
406
391
  // Build as template literal style attribute
407
392
  const entries: string[] = [];
408
393
  for (const [cssProp, value] of Object.entries(dynamicStyles)) {
@@ -418,7 +403,12 @@ function buildClassAndStyleExpression(
418
403
  }
419
404
 
420
405
  // Handle interactive style mappings: emit --is-N CSS variables as inline style
421
- if (interactiveStyles && interactiveStyles.length > 0 && ctx.isComponentDef && hasInteractiveStyleMappings(interactiveStyles)) {
406
+ if (
407
+ interactiveStyles &&
408
+ interactiveStyles.length > 0 &&
409
+ ctx.isComponentDef &&
410
+ hasInteractiveStyleMappings(interactiveStyles)
411
+ ) {
422
412
  const { mappings } = extractInteractiveStyleMappings(interactiveStyles);
423
413
  if (mappings.length > 0) {
424
414
  // Group mappings by prop for efficient ternary generation
@@ -428,9 +418,14 @@ function buildClassAndStyleExpression(
428
418
  const varName = `--is-${variableIndex}`;
429
419
  const entries = Object.entries(mapping.values);
430
420
 
431
- if (entries.length === 2) {
432
- const [[val1, css1], [val2, css2]] = entries;
433
- varParts.push(`'${varName}': ${mapping.prop} === ${JSON.stringify(coerceValue(val1))} ? '${css1}' : '${css2}'`);
421
+ const entry1 = entries[0];
422
+ const entry2 = entries[1];
423
+ if (entries.length === 2 && entry1 && entry2) {
424
+ const [val1, css1] = entry1;
425
+ const [_val2, css2] = entry2;
426
+ varParts.push(
427
+ `'${varName}': ${mapping.prop} === ${JSON.stringify(coerceValue(val1))} ? '${css1}' : '${css2}'`,
428
+ );
434
429
  } else {
435
430
  // Build a lookup object inline
436
431
  const lookupEntries = entries
@@ -443,9 +438,7 @@ function buildClassAndStyleExpression(
443
438
 
444
439
  // Merge with existing dynamic styles
445
440
  if (varParts.length > 0) {
446
- const existingStyleParts = styleAttr
447
- ? styleAttr.replace(/^ style=\{`/, '').replace(/`\}$/, '')
448
- : '';
441
+ const existingStyleParts = styleAttr ? styleAttr.replace(/^ style=\{`/, '').replace(/`\}$/, '') : '';
449
442
  const varStyleExpr = varParts.join(', ');
450
443
  if (existingStyleParts) {
451
444
  styleAttr = ` style={\`${existingStyleParts}; \${ Object.entries({${varStyleExpr}}).map(([k,v]) => \`\${k}:\${v}\`).join(';') }\`}`;
@@ -488,7 +481,7 @@ function resolveTemplate(text: string, ctx: AstroEmitContext): string {
488
481
  // Check if entire text is a single {{expression}}
489
482
  const fullMatch = text.match(/^\{\{(.+)\}\}$/);
490
483
  if (fullMatch) {
491
- let propName = fullMatch[1].trim();
484
+ let propName = (fullMatch[1] ?? '').trim();
492
485
  if (ctx.listItemBinding) propName = rewriteItemVar(propName, ctx.listItemBinding);
493
486
  if (ctx.listIndexVar) propName = replaceItemMetaVars(propName, ctx.listIndexVar, ctx.listSourceVar);
494
487
  // Rich-text props contain HTML - render unescaped via set:html
@@ -516,10 +509,7 @@ function hasTemplates(text: string): boolean {
516
509
  /**
517
510
  * Build element class name for interactive styles
518
511
  */
519
- function buildElementClass(
520
- ctx: AstroEmitContext,
521
- label: string | undefined
522
- ): string {
512
+ function buildElementClass(ctx: AstroEmitContext, label: string | undefined): string {
523
513
  return generateElementClassName({
524
514
  fileType: ctx.fileType,
525
515
  fileName: ctx.fileName,
@@ -528,9 +518,7 @@ function buildElementClass(
528
518
  });
529
519
  }
530
520
 
531
- function isResponsiveStyleObject(
532
- style: StyleObject | ResponsiveStyleObject
533
- ): style is ResponsiveStyleObject {
521
+ function isResponsiveStyleObject(style: StyleObject | ResponsiveStyleObject): style is ResponsiveStyleObject {
534
522
  return 'base' in style || 'tablet' in style || 'mobile' in style;
535
523
  }
536
524
 
@@ -539,14 +527,10 @@ function isResponsiveStyleObject(
539
527
  * link roots, or `props.style` for a component/link-instance root. Used to
540
528
  * replicate SSR's per-breakpoint merge of an instance override into the root.
541
529
  */
542
- function getComponentRootStyle(
543
- def: ComponentDefinition | undefined
544
- ): StyleObject | ResponsiveStyleObject | undefined {
545
- const root = def?.component?.structure as
546
- | { style?: unknown; props?: { style?: unknown } }
547
- | undefined;
530
+ function getComponentRootStyle(def: ComponentDefinition | undefined): StyleObject | ResponsiveStyleObject | undefined {
531
+ const root = def?.component?.structure as { style?: unknown; props?: { style?: unknown } } | undefined;
548
532
  if (!root || typeof root !== 'object') return undefined;
549
- const s = (root.style ?? root.props?.style);
533
+ const s = root.style ?? root.props?.style;
550
534
  return s && typeof s === 'object' ? (s as StyleObject | ResponsiveStyleObject) : undefined;
551
535
  }
552
536
 
@@ -559,7 +543,7 @@ function getComponentRootStyle(
559
543
  function hasNonEmptyStyle(style: StyleObject | ResponsiveStyleObject): boolean {
560
544
  if (isResponsiveStyleObject(style)) {
561
545
  return Object.values(style).some(
562
- (branch) => !!branch && typeof branch === 'object' && Object.keys(branch as object).length > 0
546
+ (branch) => !!branch && typeof branch === 'object' && Object.keys(branch as object).length > 0,
563
547
  );
564
548
  }
565
549
  return Object.keys(style).length > 0;
@@ -583,22 +567,19 @@ function stripTemplateExpressionStyle<T extends StyleObject | ResponsiveStyleObj
583
567
  if (isResponsiveStyleObject(style)) {
584
568
  const out: Record<string, unknown> = {};
585
569
  for (const [bp, branch] of Object.entries(style as Record<string, unknown>)) {
586
- out[bp] = branch && typeof branch === 'object'
587
- ? cleanFlat(branch as Record<string, unknown>)
588
- : branch;
570
+ out[bp] = branch && typeof branch === 'object' ? cleanFlat(branch as Record<string, unknown>) : branch;
589
571
  }
590
572
  return out as T;
591
573
  }
592
574
  return cleanFlat(style as Record<string, unknown>) as T;
593
575
  }
594
576
 
595
-
596
577
  /**
597
578
  * Build HTML attributes string from node attributes
598
579
  */
599
580
  function buildAttributesString(
600
581
  attributes: Record<string, string | number | boolean> | undefined,
601
- ctx: AstroEmitContext
582
+ ctx: AstroEmitContext,
602
583
  ): string {
603
584
  if (!attributes) return '';
604
585
  const parts: string[] = [];
@@ -611,7 +592,7 @@ function buildAttributesString(
611
592
  // Check if entire value is a single {{expression}}
612
593
  const fullMatch = strVal.match(/^\{\{(.+)\}\}$/);
613
594
  if (fullMatch) {
614
- let expr = fullMatch[1].trim();
595
+ let expr = (fullMatch[1] ?? '').trim();
615
596
  if (ctx.listItemBinding) expr = rewriteItemVar(expr, ctx.listItemBinding);
616
597
  if (ctx.listIndexVar) expr = replaceItemMetaVars(expr, ctx.listIndexVar, ctx.listSourceVar);
617
598
  const propDef = ctx.componentProps[expr];
@@ -635,7 +616,7 @@ function buildAttributesString(
635
616
  // List item binding: transform {{item.field}} in attributes
636
617
  const fullMatch = strVal.match(/^\{\{(.+)\}\}$/);
637
618
  if (fullMatch) {
638
- let expr = fullMatch[1].trim();
619
+ let expr = (fullMatch[1] ?? '').trim();
639
620
  expr = rewriteItemVar(expr, ctx.listItemBinding);
640
621
  if (ctx.listIndexVar) expr = replaceItemMetaVars(expr, ctx.listIndexVar, ctx.listSourceVar);
641
622
  parts.push(`${key}={${expr} || undefined}`);
@@ -651,10 +632,10 @@ function buildAttributesString(
651
632
  } else if (ctx.cmsMode && /\{\{cms\./.test(strVal)) {
652
633
  // CMS mode: transform {{cms.field}} in attributes
653
634
  const b = ctx.cmsEntryBinding || 'entry';
654
- const w = (expr: string) => ctx.cmsWrapFn ? `${ctx.cmsWrapFn}(${expr})` : expr;
635
+ const w = (expr: string) => (ctx.cmsWrapFn ? `${ctx.cmsWrapFn}(${expr})` : expr);
655
636
  const fullMatch = strVal.match(/^\{\{cms\.([^}]+)\}\}$/);
656
637
  if (fullMatch) {
657
- parts.push(`${key}={${w(`${b}.data.${fullMatch[1].trim()}`)}}`);
638
+ parts.push(`${key}={${w(`${b}.data.${(fullMatch[1] ?? '').trim()}`)}}`);
658
639
  } else {
659
640
  const replaced = strVal.replace(/\{\{cms\.([^}]+)\}\}/g, (_, fieldPath) => {
660
641
  return `\${${w(`${b}.data.${fieldPath.trim()}`)}}`;
@@ -666,7 +647,7 @@ function buildAttributesString(
666
647
  }
667
648
  }
668
649
  }
669
- return parts.length > 0 ? ' ' + parts.join(' ') : '';
650
+ return parts.length > 0 ? ` ${parts.join(' ')}` : '';
670
651
  }
671
652
 
672
653
  /**
@@ -702,7 +683,7 @@ function resolveI18n(value: unknown, ctx: AstroEmitContext): unknown {
702
683
  */
703
684
  export function nodeToAstro(
704
685
  node: ComponentNode | ComponentNode[] | string | number | null | undefined,
705
- ctx: AstroEmitContext
686
+ ctx: AstroEmitContext,
706
687
  ): string {
707
688
  if (node === null || node === undefined) return '';
708
689
 
@@ -718,6 +699,18 @@ export function nodeToAstro(
718
699
  ctx.needsI18nResolver = true;
719
700
  return `${ind(ctx)}{r(${JSON.stringify(stripRawHtmlPrefixDeep(resolved))})}\n`;
720
701
  }
702
+ // Page context without a locale: resolve through the default locale
703
+ // (resolveTranslation falls back exact → default → first available →
704
+ // empty) instead of stringifying the i18n object to "[object Object]".
705
+ if (isI18nValue(resolved)) {
706
+ const config = ctx.i18nConfig ?? DEFAULT_I18N_CONFIG;
707
+ const fallbackLocale = ctx.defaultLocale ?? config.defaultLocale;
708
+ const fallbackResolved = resolveI18nValue(resolved, fallbackLocale, config);
709
+ if (typeof fallbackResolved === 'string') {
710
+ return nodeToAstro(fallbackResolved, ctx);
711
+ }
712
+ return `${ind(ctx)}${String(fallbackResolved ?? '')}\n`;
713
+ }
721
714
  // If resolution returned a non-string (e.g. array), stringify it
722
715
  return `${ind(ctx)}${String(resolved ?? '')}\n`;
723
716
  }
@@ -726,7 +719,12 @@ export function nodeToAstro(
726
719
  if (typeof node === 'string') {
727
720
  // CMS mode: transform {{cms.field}} expressions to entry data access
728
721
  if (ctx.cmsMode && isTemplateExpression(node) && /\{\{cms\./.test(node)) {
729
- const transformed = transformCMSTemplate(node, ctx.cmsEntryBinding || 'entry', ctx.cmsRichTextFields, ctx.cmsWrapFn);
722
+ const transformed = transformCMSTemplate(
723
+ node,
724
+ ctx.cmsEntryBinding || 'entry',
725
+ ctx.cmsRichTextFields,
726
+ ctx.cmsWrapFn,
727
+ );
730
728
  return `${ind(ctx)}${transformed}\n`;
731
729
  }
732
730
  // List item binding: transform {{item.field}} expressions
@@ -821,7 +819,7 @@ function splitImageClasses(allClasses: string[]): { pictureClasses: string[]; im
821
819
  for (const cls of allClasses) {
822
820
  // Strip responsive prefix (e.g., "md:object-cover" -> "object-cover") for matching
823
821
  const baseCls = cls.includes(':') ? cls.split(':').pop()! : cls;
824
- if (IMG_TAILWIND_PREFIXES.some(p => baseCls.startsWith(p)) || IMG_OPACITY_PATTERN.test(baseCls)) {
822
+ if (IMG_TAILWIND_PREFIXES.some((p) => baseCls.startsWith(p)) || IMG_OPACITY_PATTERN.test(baseCls)) {
825
823
  imgClasses.push(cls);
826
824
  } else {
827
825
  pictureClasses.push(cls);
@@ -840,7 +838,7 @@ function extractPxWidth(val: unknown): number | null {
840
838
  if (typeof val !== 'string') return null;
841
839
  const match = val.trim().match(/^(\d+(?:\.\d+)?)px$/);
842
840
  if (!match) return null;
843
- const n = parseFloat(match[1]);
841
+ const n = parseFloat(match[1] ?? '');
844
842
  return n > 0 ? n : null;
845
843
  }
846
844
 
@@ -852,7 +850,7 @@ function extractPxWidth(val: unknown): number | null {
852
850
  */
853
851
  function computeSizesAttribute(
854
852
  style: StyleObject | ResponsiveStyleObject | undefined,
855
- breakpoints: BreakpointConfig
853
+ breakpoints: BreakpointConfig,
856
854
  ): string {
857
855
  if (!style) return DEFAULT_SIZES;
858
856
 
@@ -868,9 +866,7 @@ function computeSizesAttribute(
868
866
 
869
867
  // Walk breakpoints from smallest to largest so the emitted media queries
870
868
  // are ordered from narrowest to widest, with the unconditional base last.
871
- const bpEntries = Object.entries(breakpoints).sort(
872
- (a, b) => a[1].breakpoint - b[1].breakpoint
873
- );
869
+ const bpEntries = Object.entries(breakpoints).sort((a, b) => a[1].breakpoint - b[1].breakpoint);
874
870
 
875
871
  const parts: string[] = [];
876
872
  // Track the "effective" width as we walk up: if a breakpoint doesn't set its
@@ -905,13 +901,34 @@ function computeSizesAttribute(
905
901
  }
906
902
 
907
903
  /**
908
- * Merge additional CSS declarations into an existing ` style="..."` attribute
909
- * string. If no style attribute exists yet, builds one. The inputs are raw CSS
910
- * already escaped for HTML attribute context.
904
+ * Merge additional CSS declarations into an existing ` style` attribute
905
+ * string. If no style attribute exists yet, builds a static one. Handles all
906
+ * three attr shapes the emitter produces: static `style="..."`, dynamic
907
+ * template-literal `style={\`...\`}`, and dynamic expression `style={...}`.
908
+ * The inputs are raw CSS already escaped for HTML attribute context.
911
909
  */
912
910
  function injectInlineStyle(styleAttr: string, extraCss: string): string {
913
911
  if (!extraCss) return styleAttr;
914
912
  if (!styleAttr) return ` style="${extraCss}"`;
913
+
914
+ // Dynamic template-literal style attr (` style={`...`}`): append the extra
915
+ // declarations inside the template literal so they survive the merge.
916
+ const dynLiteralMatch = styleAttr.match(/^ style=\{`([\s\S]*)`\}$/);
917
+ if (dynLiteralMatch) {
918
+ const existing = dynLiteralMatch[1] ?? '';
919
+ const trimmed = existing.trimEnd();
920
+ const sep = trimmed.length > 0 && !trimmed.endsWith(';') ? ';' : '';
921
+ return ` style={\`${existing}${sep}${extraCss}\`}`;
922
+ }
923
+
924
+ // Dynamic expression style attr (` style={expr}`, e.g. the Object.entries()
925
+ // form for interactive style mappings): wrap the expression in a template
926
+ // literal and append the extra declarations after it.
927
+ const dynExprMatch = styleAttr.match(/^ style=\{([\s\S]*)\}$/);
928
+ if (dynExprMatch) {
929
+ return ` style={\`\${${dynExprMatch[1]}};${extraCss}\`}`;
930
+ }
931
+
915
932
  return styleAttr.replace(/style="([^"]*)"/, (_, existing: string) => {
916
933
  const trimmed = existing.trimEnd();
917
934
  const sep = trimmed.length > 0 && !trimmed.endsWith(';') ? ';' : '';
@@ -932,7 +949,10 @@ const PICTURE_WIDTHS = [500, 800, 1080, 1600, 2400];
932
949
  * identifier (`imgSubHeroPhoto`) for use as an ESM import variable.
933
950
  */
934
951
  function imagePathToVarName(srcPath: string): string {
935
- const stripped = srcPath.replace(/^\/+/, '').replace(/^images\//, '').replace(/\.[^.]+$/, '');
952
+ const stripped = srcPath
953
+ .replace(/^\/+/, '')
954
+ .replace(/^images\//, '')
955
+ .replace(/\.[^.]+$/, '');
936
956
  const parts = stripped.split(/[/_\-\s.]+/).filter(Boolean);
937
957
  if (parts.length === 0) return 'imgAsset';
938
958
  const camel = parts
@@ -940,11 +960,11 @@ function imagePathToVarName(srcPath: string): string {
940
960
  const clean = p.replace(/[^a-zA-Z0-9]/g, '');
941
961
  if (!clean) return '';
942
962
  if (i === 0) return clean.toLowerCase();
943
- return clean[0].toUpperCase() + clean.slice(1).toLowerCase();
963
+ return (clean[0] ?? '').toUpperCase() + clean.slice(1).toLowerCase();
944
964
  })
945
965
  .join('');
946
966
  if (!camel) return 'imgAsset';
947
- return 'img' + camel[0].toUpperCase() + camel.slice(1);
967
+ return `img${(camel[0] ?? '').toUpperCase()}${camel.slice(1)}`;
948
968
  }
949
969
 
950
970
  /**
@@ -1003,13 +1023,11 @@ function registerStaticImageImport(src: string, ctx: AstroEmitContext): string {
1003
1023
  * object-cover / opacity-* keep landing on the <img>. Returns null when the
1004
1024
  * class expression is dynamic (class:list) — callers fall back to div wrap.
1005
1025
  */
1006
- function splitStaticClassExpr(
1007
- classExpr: string
1008
- ): { outerClasses: string[]; innerClasses: string[] } | null {
1026
+ function splitStaticClassExpr(classExpr: string): { outerClasses: string[]; innerClasses: string[] } | null {
1009
1027
  if (classExpr.includes('class:list=')) return null;
1010
1028
  const classMatch = classExpr.match(/class="([^"]*)"/);
1011
1029
  if (!classMatch) return { outerClasses: [], innerClasses: [] };
1012
- const all = classMatch[1].split(/\s+/).filter(Boolean);
1030
+ const all = (classMatch[1] ?? '').split(/\s+/).filter(Boolean);
1013
1031
  const { pictureClasses, imgClasses } = splitImageClasses(all);
1014
1032
  return { outerClasses: pictureClasses, innerClasses: imgClasses };
1015
1033
  }
@@ -1018,10 +1036,7 @@ function splitStaticClassExpr(
1018
1036
  * Format a static class+style pair as an Astro `pictureAttributes={{...}}`
1019
1037
  * prop. `classValue` comes pre-split (outer layout classes only).
1020
1038
  */
1021
- function formatPictureAttributesProp(
1022
- classValue: string,
1023
- styleAttr: string
1024
- ): string {
1039
+ function formatPictureAttributesProp(classValue: string, styleAttr: string): string {
1025
1040
  const styleMatch = styleAttr.match(/style="([^"]*)"/);
1026
1041
  const parts: string[] = [];
1027
1042
  if (classValue) parts.push(`class: ${JSON.stringify(classValue)}`);
@@ -1045,7 +1060,7 @@ function emitStaticPictureImage(
1045
1060
  ifExpr: string,
1046
1061
  ifClose: string,
1047
1062
  blurHash: string | undefined,
1048
- ctx: AstroEmitContext
1063
+ ctx: AstroEmitContext,
1049
1064
  ): string {
1050
1065
  const varName = registerStaticImageImport(src, ctx);
1051
1066
 
@@ -1093,9 +1108,7 @@ function emitStaticPictureImage(
1093
1108
  // on the <picture>, inner img-specific classes as `class` on <Picture>.
1094
1109
  if (split) {
1095
1110
  const outerClassValue = split.outerClasses.join(' ');
1096
- const innerClassAttr = split.innerClasses.length > 0
1097
- ? ` class="${split.innerClasses.join(' ')}"`
1098
- : '';
1111
+ const innerClassAttr = split.innerClasses.length > 0 ? ` class="${split.innerClasses.join(' ')}"` : '';
1099
1112
  const pictureAttrs = formatPictureAttributesProp(outerClassValue, styleAttr);
1100
1113
  return `${ifExpr}${ind(ctx)}<Picture${pictureAttrs} src={${varName}}${altAttr}${innerClassAttr} formats={['avif','webp']} widths={${widthsLiteral}} sizes="${escapeJSX(sizesValue)}"${loadingAttr}${fetchpriorityAttr} />\n${ifClose}`;
1101
1114
  }
@@ -1113,10 +1126,7 @@ function emitImageNode(node: HtmlNode, ctx: AstroEmitContext): string {
1113
1126
 
1114
1127
  // Element class for interactive styles
1115
1128
  let elementClass: string | null = null;
1116
- if (
1117
- (node.interactiveStyles && node.interactiveStyles.length > 0) ||
1118
- node.generateElementClass
1119
- ) {
1129
+ if ((node.interactiveStyles && node.interactiveStyles.length > 0) || node.generateElementClass) {
1120
1130
  elementClass = buildElementClass(ctx, node.label);
1121
1131
  if (elementClass && node.interactiveStyles?.length && ctx.collectedInteractiveStyles) {
1122
1132
  ctx.collectedInteractiveStyles.set(elementClass, node.interactiveStyles as InteractiveStyles);
@@ -1127,7 +1137,7 @@ function emitImageNode(node: HtmlNode, ctx: AstroEmitContext): string {
1127
1137
  style,
1128
1138
  node.interactiveStyles as InteractiveStyles | undefined,
1129
1139
  elementClass,
1130
- ctx
1140
+ ctx,
1131
1141
  );
1132
1142
 
1133
1143
  // Extract image-specific attributes
@@ -1166,7 +1176,7 @@ function emitImageNode(node: HtmlNode, ctx: AstroEmitContext): string {
1166
1176
  emitIfOpen(node, ctx),
1167
1177
  emitIfClose(node, ctx),
1168
1178
  metadata?.blurHash,
1169
- ctx
1179
+ ctx,
1170
1180
  );
1171
1181
  }
1172
1182
 
@@ -1198,12 +1208,8 @@ function emitImageNode(node: HtmlNode, ctx: AstroEmitContext): string {
1198
1208
  // is a replaced element and doesn't paint CSS backgrounds reliably).
1199
1209
  // Once the real image loads we clear the background on the wrapper so the
1200
1210
  // low-quality placeholder disappears.
1201
- const blurWrapperCss = hasBlur
1202
- ? `background-image:url(${escapeJSX(metadata!.blurHash!)});background-size:cover`
1203
- : '';
1204
- const blurOnload = hasBlur
1205
- ? ` onload="this.parentElement.style.backgroundImage=''"`
1206
- : '';
1211
+ const blurWrapperCss = hasBlur ? `background-image:url(${escapeJSX(metadata!.blurHash!)});background-size:cover` : '';
1212
+ const blurOnload = hasBlur ? ` onload="this.parentElement.style.backgroundImage=''"` : '';
1207
1213
 
1208
1214
  // Conditional rendering
1209
1215
  const ifExpr = emitIfOpen(node, ctx);
@@ -1225,7 +1231,7 @@ function emitImageNode(node: HtmlNode, ctx: AstroEmitContext): string {
1225
1231
  pictureClassExpr = classExpr;
1226
1232
  imgClassAttr = ` class="${imgFillClasses.join(' ')}"`;
1227
1233
  } else {
1228
- const allClasses = classMatch ? classMatch[1].split(/\s+/).filter(Boolean) : [];
1234
+ const allClasses = classMatch ? (classMatch[1] ?? '').split(/\s+/).filter(Boolean) : [];
1229
1235
  const { pictureClasses, imgClasses } = splitImageClasses(allClasses);
1230
1236
  const fullImgClasses = [...imgClasses, ...imgFillClasses];
1231
1237
  pictureClassExpr = pictureClasses.length > 0 ? ` class="${pictureClasses.join(' ')}"` : '';
@@ -1302,17 +1308,19 @@ function emitHtmlNode(node: HtmlNode, ctx: AstroEmitContext): string {
1302
1308
 
1303
1309
  // Element class for interactive styles
1304
1310
  let elementClass: string | null = null;
1305
- if (
1306
- (node.interactiveStyles && node.interactiveStyles.length > 0) ||
1307
- node.generateElementClass
1308
- ) {
1311
+ if ((node.interactiveStyles && node.interactiveStyles.length > 0) || node.generateElementClass) {
1309
1312
  elementClass = buildElementClass(ctx, label);
1310
1313
  if (elementClass && node.interactiveStyles?.length && ctx.collectedInteractiveStyles) {
1311
1314
  ctx.collectedInteractiveStyles.set(elementClass, node.interactiveStyles as InteractiveStyles);
1312
1315
  }
1313
1316
  }
1314
1317
 
1315
- const { classExpr, styleAttr } = buildClassAndStyleExpression(style, node.interactiveStyles as InteractiveStyles | undefined, elementClass, ctx);
1318
+ const { classExpr, styleAttr } = buildClassAndStyleExpression(
1319
+ style,
1320
+ node.interactiveStyles as InteractiveStyles | undefined,
1321
+ elementClass,
1322
+ ctx,
1323
+ );
1316
1324
  const attrs = buildAttributesString(node.attributes, ctx);
1317
1325
 
1318
1326
  // Dynamic tags use the variable name directly (capitalized, defined in frontmatter)
@@ -1354,7 +1362,7 @@ function emitComponentInstance(node: ComponentInstanceNode, ctx: AstroEmitContex
1354
1362
  if (typeof value === 'string' && hasTemplates(value) && ctx.isComponentDef) {
1355
1363
  const fullMatch = value.match(/^\{\{(.+)\}\}$/);
1356
1364
  if (fullMatch) {
1357
- let expr = fullMatch[1].trim();
1365
+ let expr = (fullMatch[1] ?? '').trim();
1358
1366
  if (ctx.listItemBinding) expr = rewriteItemVar(expr, ctx.listItemBinding);
1359
1367
  if (ctx.listIndexVar) expr = replaceItemMetaVars(expr, ctx.listIndexVar, ctx.listSourceVar);
1360
1368
  propParts.push(`${key}={${expr}}`);
@@ -1369,10 +1377,10 @@ function emitComponentInstance(node: ComponentInstanceNode, ctx: AstroEmitContex
1369
1377
  }
1370
1378
  } else if (typeof value === 'string' && ctx.cmsMode && /\{\{cms\./.test(value)) {
1371
1379
  const b = ctx.cmsEntryBinding || 'entry';
1372
- const w = (expr: string) => ctx.cmsWrapFn ? `${ctx.cmsWrapFn}(${expr})` : expr;
1380
+ const w = (expr: string) => (ctx.cmsWrapFn ? `${ctx.cmsWrapFn}(${expr})` : expr);
1373
1381
  const fullMatch = value.match(/^\{\{cms\.([^}]+)\}\}$/);
1374
1382
  if (fullMatch) {
1375
- propParts.push(`${key}={${w(`${b}.data.${fullMatch[1].trim()}`)}}`);
1383
+ propParts.push(`${key}={${w(`${b}.data.${(fullMatch[1] ?? '').trim()}`)}}`);
1376
1384
  } else {
1377
1385
  const replaced = value.replace(/\{\{cms\.([^}]+)\}\}/g, (_, fieldPath) => {
1378
1386
  return `\${${w(`${b}.data.${fieldPath.trim()}`)}}`;
@@ -1433,9 +1441,9 @@ function emitComponentInstance(node: ComponentInstanceNode, ctx: AstroEmitContex
1433
1441
  // root are dropped downstream (styleObjectToCSS skips mappings) /
1434
1442
  // stripped here, falling back to the component's own emission.
1435
1443
  const rootStyle = getComponentRootStyle(ctx.globalComponents[node.component]);
1436
- const merged = (rootStyle
1437
- ? deepMergeStyles(rootStyle, instanceStyle!)
1438
- : instanceStyle!) as StyleObject | ResponsiveStyleObject;
1444
+ const merged = (rootStyle ? deepMergeStyles(rootStyle, instanceStyle!) : instanceStyle!) as
1445
+ | StyleObject
1446
+ | ResponsiveStyleObject;
1439
1447
  const cssSafe = stripTemplateExpressionStyle(merged);
1440
1448
  if (hasNonEmptyStyle(cssSafe)) rules.push({ style: cssSafe }); // no prefix/postfix → `.cls {…}`
1441
1449
  }
@@ -1448,7 +1456,7 @@ function emitComponentInstance(node: ComponentInstanceNode, ctx: AstroEmitContex
1448
1456
  }
1449
1457
  }
1450
1458
 
1451
- const propsStr = propParts.length > 0 ? ' ' + propParts.join(' ') : '';
1459
+ const propsStr = propParts.length > 0 ? ` ${propParts.join(' ')}` : '';
1452
1460
 
1453
1461
  // Reset elementPath to [0] when entering a component instance, matching SSR's
1454
1462
  // renderComponent behavior. This ensures hash-based element class names for
@@ -1460,22 +1468,14 @@ function emitComponentInstance(node: ComponentInstanceNode, ctx: AstroEmitContex
1460
1468
  return `${ifExpr}${ind(ctx)}<${name}${propsStr} />\n${emitIfClose(node, ctx)}`;
1461
1469
  }
1462
1470
 
1463
- return (
1464
- `${ifExpr}${ind(ctx)}<${name}${propsStr}>\n` +
1465
- children +
1466
- `${ind(ctx)}</${name}>\n${emitIfClose(node, ctx)}`
1467
- );
1471
+ return `${ifExpr}${ind(ctx)}<${name}${propsStr}>\n${children}${ind(ctx)}</${name}>\n${emitIfClose(node, ctx)}`;
1468
1472
  }
1469
1473
 
1470
1474
  function emitSlotMarker(node: SlotMarker, ctx: AstroEmitContext): string {
1471
1475
  if (node.default) {
1472
1476
  const defaultContent = emitChildren(node.default as (ComponentNode | string)[] | string | undefined, ctx);
1473
1477
  if (defaultContent.trim()) {
1474
- return (
1475
- `${ind(ctx)}<slot>\n` +
1476
- defaultContent +
1477
- `${ind(ctx)}</slot>\n`
1478
- );
1478
+ return `${ind(ctx)}<slot>\n${defaultContent}${ind(ctx)}</slot>\n`;
1479
1479
  }
1480
1480
  }
1481
1481
  return `${ind(ctx)}<slot />\n`;
@@ -1486,17 +1486,19 @@ function emitEmbedNode(node: EmbedNode, ctx: AstroEmitContext): string {
1486
1486
  const style = node.style as StyleObject | ResponsiveStyleObject | undefined;
1487
1487
 
1488
1488
  let elementClass: string | null = null;
1489
- if (
1490
- (node.interactiveStyles && node.interactiveStyles.length > 0) ||
1491
- node.generateElementClass
1492
- ) {
1489
+ if ((node.interactiveStyles && node.interactiveStyles.length > 0) || node.generateElementClass) {
1493
1490
  elementClass = buildElementClass(ctx, node.label);
1494
1491
  if (elementClass && node.interactiveStyles?.length && ctx.collectedInteractiveStyles) {
1495
1492
  ctx.collectedInteractiveStyles.set(elementClass, node.interactiveStyles as InteractiveStyles);
1496
1493
  }
1497
1494
  }
1498
1495
 
1499
- const { classExpr, styleAttr } = buildClassAndStyleExpression(style, node.interactiveStyles as InteractiveStyles | undefined, elementClass, ctx);
1496
+ const { classExpr, styleAttr: _styleAttr } = buildClassAndStyleExpression(
1497
+ style,
1498
+ node.interactiveStyles as InteractiveStyles | undefined,
1499
+ elementClass,
1500
+ ctx,
1501
+ );
1500
1502
  const attrs = buildAttributesString(node.attributes, ctx);
1501
1503
 
1502
1504
  // Handle HTML mapping (prop-dependent content)
@@ -1515,7 +1517,7 @@ function emitEmbedNode(node: EmbedNode, ctx: AstroEmitContext): string {
1515
1517
  if (typeof node.html === 'string' && hasTemplates(node.html) && ctx.isComponentDef) {
1516
1518
  const fullMatch = node.html.match(/^\{\{(.+)\}\}$/);
1517
1519
  if (fullMatch) {
1518
- let propRef = fullMatch[1].trim();
1520
+ let propRef = (fullMatch[1] ?? '').trim();
1519
1521
  if (ctx.listItemBinding) propRef = rewriteItemVar(propRef, ctx.listItemBinding);
1520
1522
  if (ctx.listIndexVar) propRef = replaceItemMetaVars(propRef, ctx.listIndexVar, ctx.listSourceVar);
1521
1523
 
@@ -1561,10 +1563,7 @@ function emitLinkNode(node: LinkNode, ctx: AstroEmitContext): string {
1561
1563
  const style = node.style as StyleObject | ResponsiveStyleObject | undefined;
1562
1564
 
1563
1565
  let elementClass: string | null = null;
1564
- if (
1565
- (node.interactiveStyles && node.interactiveStyles.length > 0) ||
1566
- node.generateElementClass
1567
- ) {
1566
+ if ((node.interactiveStyles && node.interactiveStyles.length > 0) || node.generateElementClass) {
1568
1567
  elementClass = buildElementClass(ctx, node.label);
1569
1568
  if (elementClass && node.interactiveStyles?.length && ctx.collectedInteractiveStyles) {
1570
1569
  ctx.collectedInteractiveStyles.set(elementClass, node.interactiveStyles as InteractiveStyles);
@@ -1572,7 +1571,12 @@ function emitLinkNode(node: LinkNode, ctx: AstroEmitContext): string {
1572
1571
  }
1573
1572
 
1574
1573
  // Build class expression with olink base class
1575
- const { classExpr, styleAttr } = buildClassAndStyleExpression(style, node.interactiveStyles as InteractiveStyles | undefined, elementClass, ctx);
1574
+ const { classExpr, styleAttr } = buildClassAndStyleExpression(
1575
+ style,
1576
+ node.interactiveStyles as InteractiveStyles | undefined,
1577
+ elementClass,
1578
+ ctx,
1579
+ );
1576
1580
  let finalClassExpr = classExpr;
1577
1581
  if (!classExpr.includes('olink')) {
1578
1582
  if (classExpr) {
@@ -1601,7 +1605,7 @@ function emitLinkNode(node: LinkNode, ctx: AstroEmitContext): string {
1601
1605
  if (hasTemplates(href) && ctx.isComponentDef) {
1602
1606
  const fullMatch = href.match(/^\{\{(.+)\}\}$/);
1603
1607
  if (fullMatch) {
1604
- let expr = fullMatch[1].trim();
1608
+ let expr = (fullMatch[1] ?? '').trim();
1605
1609
  if (ctx.listItemBinding) expr = rewriteItemVar(expr, ctx.listItemBinding);
1606
1610
  if (ctx.listIndexVar) expr = replaceItemMetaVars(expr, ctx.listIndexVar, ctx.listSourceVar);
1607
1611
  const propDef = ctx.componentProps[expr];
@@ -1622,10 +1626,10 @@ function emitLinkNode(node: LinkNode, ctx: AstroEmitContext): string {
1622
1626
  }
1623
1627
  } else if (ctx.cmsMode && /\{\{cms\./.test(href)) {
1624
1628
  const b = ctx.cmsEntryBinding || 'entry';
1625
- const w = (expr: string) => ctx.cmsWrapFn ? `${ctx.cmsWrapFn}(${expr})` : expr;
1629
+ const w = (expr: string) => (ctx.cmsWrapFn ? `${ctx.cmsWrapFn}(${expr})` : expr);
1626
1630
  const fullMatch = href.match(/^\{\{cms\.([^}]+)\}\}$/);
1627
1631
  if (fullMatch) {
1628
- hrefAttr = ` href={${w(`${b}.data.${fullMatch[1].trim()}`)}}`;
1632
+ hrefAttr = ` href={${w(`${b}.data.${(fullMatch[1] ?? '').trim()}`)}}`;
1629
1633
  } else {
1630
1634
  const replaced = href.replace(/\{\{cms\.([^}]+)\}\}/g, (_, fieldPath) => {
1631
1635
  return `\${${w(`${b}.data.${fieldPath.trim()}`)}}`;
@@ -1660,10 +1664,7 @@ function emitImageTypeNode(node: any, ctx: AstroEmitContext): string {
1660
1664
  const style = node.style as StyleObject | ResponsiveStyleObject | undefined;
1661
1665
 
1662
1666
  let elementClass: string | null = null;
1663
- if (
1664
- (node.interactiveStyles && node.interactiveStyles.length > 0) ||
1665
- node.generateElementClass
1666
- ) {
1667
+ if ((node.interactiveStyles && node.interactiveStyles.length > 0) || node.generateElementClass) {
1667
1668
  elementClass = buildElementClass(ctx, node.label);
1668
1669
  if (elementClass && node.interactiveStyles?.length && ctx.collectedInteractiveStyles) {
1669
1670
  ctx.collectedInteractiveStyles.set(elementClass, node.interactiveStyles as InteractiveStyles);
@@ -1674,7 +1675,7 @@ function emitImageTypeNode(node: any, ctx: AstroEmitContext): string {
1674
1675
  style,
1675
1676
  node.interactiveStyles as InteractiveStyles | undefined,
1676
1677
  elementClass,
1677
- ctx
1678
+ ctx,
1678
1679
  );
1679
1680
 
1680
1681
  const src = node.src as string | undefined;
@@ -1694,7 +1695,7 @@ function emitImageTypeNode(node: any, ctx: AstroEmitContext): string {
1694
1695
  '',
1695
1696
  '',
1696
1697
  staticMeta.blurHash,
1697
- ctx
1698
+ ctx,
1698
1699
  );
1699
1700
  }
1700
1701
 
@@ -1718,17 +1719,13 @@ function emitImageTypeNode(node: any, ctx: AstroEmitContext): string {
1718
1719
  const hasBlur = !!metadata.blurHash;
1719
1720
  const useWrapper = hasAvif || hasBlur;
1720
1721
 
1721
- const blurWrapperCss = hasBlur
1722
- ? `background-image:url(${escapeJSX(metadata.blurHash!)});background-size:cover`
1723
- : '';
1724
- const blurOnload = hasBlur
1725
- ? ` onload="this.parentElement.style.backgroundImage=''"`
1726
- : '';
1722
+ const blurWrapperCss = hasBlur ? `background-image:url(${escapeJSX(metadata.blurHash!)});background-size:cover` : '';
1723
+ const blurOnload = hasBlur ? ` onload="this.parentElement.style.backgroundImage=''"` : '';
1727
1724
 
1728
1725
  if (useWrapper) {
1729
1726
  const imgFillClasses = IMG_FILL_CLASSES.slice();
1730
1727
  const classMatch = classExpr.match(/class="([^"]*)"/);
1731
- const allClasses = classMatch ? classMatch[1].split(/\s+/).filter(Boolean) : [];
1728
+ const allClasses = classMatch ? (classMatch[1] ?? '').split(/\s+/).filter(Boolean) : [];
1732
1729
  const { pictureClasses, imgClasses } = splitImageClasses(allClasses);
1733
1730
  const fullImgClasses = [...imgClasses, ...imgFillClasses];
1734
1731
 
@@ -1787,7 +1784,7 @@ function emitListNode(node: ListNode, ctx: AstroEmitContext): string {
1787
1784
  let source = node.source || 'items';
1788
1785
  const templateMatch = source.match(/^\{\{(.+)\}\}$/);
1789
1786
  if (templateMatch) {
1790
- source = templateMatch[1].trim();
1787
+ source = (templateMatch[1] ?? '').trim();
1791
1788
  }
1792
1789
 
1793
1790
  // Build the .map() source expression (compute before children so ctx has it)
@@ -1812,19 +1809,17 @@ function emitListNode(node: ListNode, ctx: AstroEmitContext): string {
1812
1809
  };
1813
1810
 
1814
1811
  const children = node.children
1815
- ? (node.children as (ComponentNode | string)[]).map((child, i) => {
1816
- const childCtx = { ...innerCtx, elementPath: [...ctx.elementPath, i] };
1817
- const out = nodeToAstro(child, childCtx);
1818
- if (childCtx.needsI18nResolver) ctx.needsI18nResolver = true;
1819
- return out;
1820
- }).join('')
1812
+ ? (node.children as (ComponentNode | string)[])
1813
+ .map((child, i) => {
1814
+ const childCtx = { ...innerCtx, elementPath: [...ctx.elementPath, i] };
1815
+ const out = nodeToAstro(child, childCtx);
1816
+ if (childCtx.needsI18nResolver) ctx.needsI18nResolver = true;
1817
+ return out;
1818
+ })
1819
+ .join('')
1821
1820
  : '';
1822
1821
 
1823
- return (
1824
- `${ind(ctx)}{${mapSource}.map((${itemAs}, ${indexVar}) => (\n` +
1825
- children +
1826
- `${ind(ctx)}))}\n`
1827
- );
1822
+ return `${ind(ctx)}{${mapSource}.map((${itemAs}, ${indexVar}) => (\n${children}${ind(ctx)}))}\n`;
1828
1823
  }
1829
1824
 
1830
1825
  /**
@@ -1876,8 +1871,7 @@ function emitCollectionListNode(node: ListNode, ctx: AstroEmitContext): string {
1876
1871
  // shape the templates expect ({{item.field}} → item.field), and synthesize the
1877
1872
  // `_url`/`_id` meta the CMS data model exposes. Filter/sort above operate on the
1878
1873
  // raw entries (e.data.*); this map runs last so it sees the final set.
1879
- const urlExpr = ctx.collectionUrlExpr?.get(source)
1880
- ?? `\`/${source}/\${e.data.slug ?? e.id}\``;
1874
+ const urlExpr = ctx.collectionUrlExpr?.get(source) ?? `\`/${source}/\${e.data.slug ?? e.id}\``;
1881
1875
  queryChain += `.then(items => items.map((e) => ({ ...e.data, _id: e.id, _url: ${urlExpr} })))`;
1882
1876
 
1883
1877
  ctx.frontmatterLines.push(`const ${collectionVar} = ${queryChain};`);
@@ -1896,19 +1890,17 @@ function emitCollectionListNode(node: ListNode, ctx: AstroEmitContext): string {
1896
1890
  };
1897
1891
 
1898
1892
  const children = node.children
1899
- ? (node.children as (ComponentNode | string)[]).map((child, i) => {
1900
- const childCtx = { ...innerCtx, elementPath: [...ctx.elementPath, i] };
1901
- const out = nodeToAstro(child, childCtx);
1902
- if (childCtx.needsI18nResolver) ctx.needsI18nResolver = true;
1903
- return out;
1904
- }).join('')
1893
+ ? (node.children as (ComponentNode | string)[])
1894
+ .map((child, i) => {
1895
+ const childCtx = { ...innerCtx, elementPath: [...ctx.elementPath, i] };
1896
+ const out = nodeToAstro(child, childCtx);
1897
+ if (childCtx.needsI18nResolver) ctx.needsI18nResolver = true;
1898
+ return out;
1899
+ })
1900
+ .join('')
1905
1901
  : '';
1906
1902
 
1907
- return (
1908
- `${ind(ctx)}{${collectionVar}.map((${itemAs}, ${indexVar}) => (\n` +
1909
- children +
1910
- `${ind(ctx)}))}\n`
1911
- );
1903
+ return `${ind(ctx)}{${collectionVar}.map((${itemAs}, ${indexVar}) => (\n${children}${ind(ctx)}))}\n`;
1912
1904
  }
1913
1905
 
1914
1906
  /**
@@ -1932,7 +1924,10 @@ function emitLocaleListNode(node: LocaleListNode, ctx: AstroEmitContext): string
1932
1924
  const style = node.style as StyleObject | ResponsiveStyleObject | undefined;
1933
1925
 
1934
1926
  let elementClass: string | null = null;
1935
- if ((node.interactiveStyles && (node.interactiveStyles as InteractiveStyles).length > 0) || node.generateElementClass) {
1927
+ if (
1928
+ (node.interactiveStyles && (node.interactiveStyles as InteractiveStyles).length > 0) ||
1929
+ node.generateElementClass
1930
+ ) {
1936
1931
  elementClass = buildElementClass(ctx, node.label);
1937
1932
  if (elementClass && (node.interactiveStyles as InteractiveStyles)?.length && ctx.collectedInteractiveStyles) {
1938
1933
  ctx.collectedInteractiveStyles.set(elementClass, node.interactiveStyles as InteractiveStyles);
@@ -1943,22 +1938,28 @@ function emitLocaleListNode(node: LocaleListNode, ctx: AstroEmitContext): string
1943
1938
  style,
1944
1939
  node.interactiveStyles as InteractiveStyles | undefined,
1945
1940
  elementClass,
1946
- ctx
1941
+ ctx,
1947
1942
  );
1948
1943
 
1949
1944
  // Build item classes
1950
1945
  const itemStyle = node.itemStyle as StyleObject | ResponsiveStyleObject | undefined;
1951
- const itemResult = itemStyle ? responsiveStylesToTailwind(itemStyle, ctx.breakpoints, ctx.responsiveScales) : { classes: [], dynamicStyles: {} };
1946
+ const itemResult = itemStyle
1947
+ ? responsiveStylesToTailwind(itemStyle, ctx.breakpoints, ctx.responsiveScales)
1948
+ : { classes: [], dynamicStyles: {} };
1952
1949
  const itemClasses = itemResult.classes;
1953
1950
 
1954
1951
  // Build active item classes (item + active combined)
1955
1952
  const activeItemStyle = node.activeItemStyle as StyleObject | ResponsiveStyleObject | undefined;
1956
- const activeResult = activeItemStyle ? responsiveStylesToTailwind(activeItemStyle, ctx.breakpoints, ctx.responsiveScales) : { classes: [], dynamicStyles: {} };
1953
+ const activeResult = activeItemStyle
1954
+ ? responsiveStylesToTailwind(activeItemStyle, ctx.breakpoints, ctx.responsiveScales)
1955
+ : { classes: [], dynamicStyles: {} };
1957
1956
  const activeItemClasses = [...itemClasses, ...activeResult.classes];
1958
1957
 
1959
1958
  // Build separator classes
1960
1959
  const separatorStyle = node.separatorStyle as StyleObject | ResponsiveStyleObject | undefined;
1961
- const sepResult = separatorStyle ? responsiveStylesToTailwind(separatorStyle, ctx.breakpoints, ctx.responsiveScales) : { classes: [], dynamicStyles: {} };
1960
+ const sepResult = separatorStyle
1961
+ ? responsiveStylesToTailwind(separatorStyle, ctx.breakpoints, ctx.responsiveScales)
1962
+ : { classes: [], dynamicStyles: {} };
1962
1963
  const separatorClasses = sepResult.classes;
1963
1964
 
1964
1965
  // Build locale icon map
@@ -1971,7 +1972,9 @@ function emitLocaleListNode(node: LocaleListNode, ctx: AstroEmitContext): string
1971
1972
 
1972
1973
  // Flag classes
1973
1974
  const flagStyle = node.flagStyle as StyleObject | ResponsiveStyleObject | undefined;
1974
- const flagResult = flagStyle ? responsiveStylesToTailwind(flagStyle, ctx.breakpoints, ctx.responsiveScales) : { classes: [], dynamicStyles: {} };
1975
+ const flagResult = flagStyle
1976
+ ? responsiveStylesToTailwind(flagStyle, ctx.breakpoints, ctx.responsiveScales)
1977
+ : { classes: [], dynamicStyles: {} };
1975
1978
  const flagClasses = flagResult.classes;
1976
1979
 
1977
1980
  // Build links
@@ -1993,9 +1996,15 @@ function emitLocaleListNode(node: LocaleListNode, ctx: AstroEmitContext): string
1993
1996
  // Display text
1994
1997
  let displayText: string;
1995
1998
  switch (displayType) {
1996
- case 'code': displayText = code.toUpperCase(); break;
1997
- case 'name': displayText = localeConfig.name; break;
1998
- case 'nativeName': default: displayText = localeConfig.nativeName; break;
1999
+ case 'code':
2000
+ displayText = code.toUpperCase();
2001
+ break;
2002
+ case 'name':
2003
+ displayText = localeConfig.name;
2004
+ break;
2005
+ default:
2006
+ displayText = localeConfig.nativeName;
2007
+ break;
1999
2008
  }
2000
2009
 
2001
2010
  // Build link content
@@ -2007,7 +2016,9 @@ function emitLocaleListNode(node: LocaleListNode, ctx: AstroEmitContext): string
2007
2016
  }
2008
2017
  linkContent += `<div>${escapeJSX(displayText)}</div>`;
2009
2018
 
2010
- links.push(`${ind(ctx)} <a href="${escapeJSX(path)}"${hreflangAttr}${currentAttr} data-locale="${escapeJSX(code)}"${classAttr}>${linkContent}</a>`);
2019
+ links.push(
2020
+ `${ind(ctx)} <a href="${escapeJSX(path)}"${hreflangAttr}${currentAttr} data-locale="${escapeJSX(code)}"${classAttr}>${linkContent}</a>`,
2021
+ );
2011
2022
  }
2012
2023
 
2013
2024
  // Join with separator
@@ -2023,7 +2034,8 @@ function emitLocaleListNode(node: LocaleListNode, ctx: AstroEmitContext): string
2023
2034
 
2024
2035
  return (
2025
2036
  `${ind(ctx)}<div data-locale-list="true"${containerClassExpr}${containerStyleAttr}${attrs}>\n` +
2026
- linksContent + '\n' +
2037
+ linksContent +
2038
+ '\n' +
2027
2039
  `${ind(ctx)}</div>\n`
2028
2040
  );
2029
2041
  }
@@ -2072,7 +2084,7 @@ function emitIfOpen(node: ComponentNode, ctx: AstroEmitContext): string {
2072
2084
  const match = ifValue.match(/^\{\{cms\.([^}]+)\}\}$/);
2073
2085
  if (match) {
2074
2086
  const binding = ctx.cmsEntryBinding || 'entry';
2075
- return `${ind(ctx)}{${binding}.data.${match[1].trim()} && (\n`;
2087
+ return `${ind(ctx)}{${binding}.data.${(match[1] ?? '').trim()} && (\n`;
2076
2088
  }
2077
2089
  }
2078
2090
 
@@ -2080,7 +2092,7 @@ function emitIfOpen(node: ComponentNode, ctx: AstroEmitContext): string {
2080
2092
  if (ctx.listItemBinding && /\{\{/.test(ifValue)) {
2081
2093
  const match = ifValue.match(/^\{\{([^}]+)\}\}$/);
2082
2094
  if (match) {
2083
- let expr = match[1].trim();
2095
+ let expr = (match[1] ?? '').trim();
2084
2096
  expr = rewriteItemVar(expr, ctx.listItemBinding);
2085
2097
  if (ctx.listIndexVar) expr = replaceItemMetaVars(expr, ctx.listIndexVar, ctx.listSourceVar);
2086
2098
  return `${ind(ctx)}{${expr} && (\n`;
@@ -2094,7 +2106,7 @@ function emitIfOpen(node: ComponentNode, ctx: AstroEmitContext): string {
2094
2106
  // Plain string without template syntax and not a valid identifier — always truthy, skip conditional
2095
2107
  return '';
2096
2108
  }
2097
- let expr = fullMatch ? fullMatch[1].trim() : ifValue.replace(/\{\{(.+?)\}\}/g, '$1');
2109
+ let expr = fullMatch ? (fullMatch[1] ?? '').trim() : ifValue.replace(/\{\{(.+?)\}\}/g, '$1');
2098
2110
  if (ctx.listItemBinding) expr = rewriteItemVar(expr, ctx.listItemBinding);
2099
2111
  if (ctx.listIndexVar) expr = replaceItemMetaVars(expr, ctx.listIndexVar, ctx.listSourceVar);
2100
2112
  return `${ind(ctx)}{${expr} && (\n`;
@@ -2111,6 +2123,10 @@ function emitIfClose(node: ComponentNode, ctx: AstroEmitContext): string {
2111
2123
  if (typeof ifValue === 'boolean') return '';
2112
2124
 
2113
2125
  if (typeof ifValue === 'object' && ifValue._mapping && ctx.isComponentDef) {
2126
+ // Degenerate mapping (no true values): emitIfOpen emitted only a
2127
+ // `{/* hidden */}` comment, not a `{cond && (` opener — close nothing.
2128
+ const hasTrueValue = Object.values(ifValue.values).some((v) => v === true);
2129
+ if (!hasTrueValue) return '';
2114
2130
  return `${ind(ctx)})}\n`;
2115
2131
  }
2116
2132
 
@@ -2139,7 +2155,7 @@ function emitIfClose(node: ComponentNode, ctx: AstroEmitContext): string {
2139
2155
 
2140
2156
  function emitChildren(
2141
2157
  children: (ComponentNode | string)[] | string | ComponentNode | null | undefined,
2142
- ctx: AstroEmitContext
2158
+ ctx: AstroEmitContext,
2143
2159
  ): string {
2144
2160
  if (!children) return '';
2145
2161