meno-core 1.0.52 → 1.0.54

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 (399) hide show
  1. package/.claude/settings.local.json +1 -3
  2. package/bin/cli.ts +48 -57
  3. package/build-astro.ts +296 -108
  4. package/build-next.ts +1374 -0
  5. package/build-static.test.ts +39 -10
  6. package/build-static.ts +127 -127
  7. package/dist/bin/cli.js +34 -38
  8. package/dist/bin/cli.js.map +2 -2
  9. package/dist/build-static.js +12 -11
  10. package/dist/chunks/chunk-2AR55GYH.js +42 -0
  11. package/dist/chunks/chunk-2AR55GYH.js.map +7 -0
  12. package/dist/chunks/{chunk-CXCBV2M7.js → chunk-2FN4UOVO.js} +581 -502
  13. package/dist/chunks/chunk-2FN4UOVO.js.map +7 -0
  14. package/dist/chunks/chunk-3XER4E5W.js +168 -0
  15. package/dist/chunks/chunk-3XER4E5W.js.map +7 -0
  16. package/dist/chunks/chunk-5ETZFREW.js +514 -0
  17. package/dist/chunks/chunk-5ETZFREW.js.map +7 -0
  18. package/dist/chunks/{chunk-2MHDV5BF.js → chunk-7E4IF5L7.js} +15 -21
  19. package/dist/chunks/chunk-7E4IF5L7.js.map +7 -0
  20. package/dist/chunks/{chunk-7NIC4I3V.js → chunk-7HWQUVTU.js} +1691 -1316
  21. package/dist/chunks/chunk-7HWQUVTU.js.map +7 -0
  22. package/dist/chunks/{chunk-EDQSMAMP.js → chunk-AE3QK5QW.js} +189 -19
  23. package/dist/chunks/chunk-AE3QK5QW.js.map +7 -0
  24. package/dist/chunks/{chunk-HNLUO36W.js → chunk-F6KTJYGV.js} +8 -8
  25. package/dist/chunks/chunk-F6KTJYGV.js.map +7 -0
  26. package/dist/chunks/{chunk-WQFG7PAH.js → chunk-FZITJSSS.js} +2 -6
  27. package/dist/chunks/chunk-FZITJSSS.js.map +7 -0
  28. package/dist/chunks/{chunk-H4JSCDNW.js → chunk-GSYYA5GX.js} +25 -2
  29. package/dist/chunks/chunk-GSYYA5GX.js.map +7 -0
  30. package/dist/chunks/{chunk-2QK6U5UK.js → chunk-HIZMY3EP.js} +12 -2
  31. package/dist/chunks/chunk-HIZMY3EP.js.map +7 -0
  32. package/dist/chunks/{chunk-AZQYF6KE.js → chunk-I2WEGYA7.js} +41 -176
  33. package/dist/chunks/chunk-I2WEGYA7.js.map +7 -0
  34. package/dist/chunks/{chunk-I7YIGZXT.js → chunk-JNO3CNLJ.js} +6 -9
  35. package/dist/chunks/chunk-JNO3CNLJ.js.map +7 -0
  36. package/dist/chunks/{chunk-UB44F4Z2.js → chunk-NVRBTSQG.js} +2 -4
  37. package/dist/chunks/chunk-NVRBTSQG.js.map +7 -0
  38. package/dist/chunks/{chunk-LHLHPYSP.js → chunk-Q4OBWKXG.js} +48 -40
  39. package/dist/chunks/chunk-Q4OBWKXG.js.map +7 -0
  40. package/dist/chunks/{chunk-A725KYFK.js → chunk-QTE32Y53.js} +780 -323
  41. package/dist/chunks/chunk-QTE32Y53.js.map +7 -0
  42. package/dist/chunks/{chunk-LPVETICS.js → chunk-STDY3OVM.js} +397 -84
  43. package/dist/chunks/chunk-STDY3OVM.js.map +7 -0
  44. package/dist/chunks/configService-PRJZF7Y6.js +14 -0
  45. package/dist/chunks/{constants-GWBAD66U.js → constants-KIQEYMAM.js} +2 -2
  46. package/dist/chunks/{fs-JGINUXGL.js → fs-ZI5JEU7V.js} +2 -2
  47. package/dist/entries/server-router.js +14 -19
  48. package/dist/entries/server-router.js.map +2 -2
  49. package/dist/lib/client/index.js +957 -356
  50. package/dist/lib/client/index.js.map +4 -4
  51. package/dist/lib/server/index.js +1538 -328
  52. package/dist/lib/server/index.js.map +4 -4
  53. package/dist/lib/shared/index.js +277 -73
  54. package/dist/lib/shared/index.js.map +4 -4
  55. package/dist/lib/shared/richtext/index.js +1 -1
  56. package/dist/lib/test-utils/index.js +38 -60
  57. package/dist/lib/test-utils/index.js.map +2 -2
  58. package/entries/client-router.tsx +14 -172
  59. package/entries/server-router.tsx +1 -7
  60. package/lib/client/ClientInitializer.ts +8 -8
  61. package/lib/client/ErrorBoundary.test.tsx +156 -151
  62. package/lib/client/ErrorBoundary.tsx +184 -121
  63. package/lib/client/componentRegistry.test.ts +96 -108
  64. package/lib/client/componentRegistry.ts +1 -2
  65. package/lib/client/contexts/ThemeContext.tsx +3 -2
  66. package/lib/client/core/ComponentBuilder.test.ts +513 -560
  67. package/lib/client/core/ComponentBuilder.ts +335 -146
  68. package/lib/client/core/ComponentRenderer.test.tsx +1 -2
  69. package/lib/client/core/ComponentRenderer.tsx +46 -33
  70. package/lib/client/core/builders/embedBuilder.ts +246 -54
  71. package/lib/client/core/builders/linkBuilder.ts +71 -44
  72. package/lib/client/core/builders/linkNodeBuilder.ts +78 -53
  73. package/lib/client/core/builders/listBuilder.ts +137 -89
  74. package/lib/client/core/builders/localeListBuilder.ts +95 -60
  75. package/lib/client/core/builders/types.ts +5 -5
  76. package/lib/client/core/cmsTemplateProcessor.ts +7 -7
  77. package/lib/client/elementRegistry.ts +3 -3
  78. package/lib/client/fontFamiliesService.test.ts +68 -0
  79. package/lib/client/fontFamiliesService.ts +69 -0
  80. package/lib/client/hmr/HMRManager.tsx +8 -0
  81. package/lib/client/hmrCssReload.ts +166 -0
  82. package/lib/client/hmrWebSocket.ts +9 -14
  83. package/lib/client/hooks/useColorVariables.test.ts +21 -21
  84. package/lib/client/hooks/useColorVariables.ts +14 -10
  85. package/lib/client/hooks/usePropertyAutocomplete.ts +3 -5
  86. package/lib/client/hooks/useVariables.ts +4 -4
  87. package/lib/client/hydration/HydrationUtils.test.ts +24 -25
  88. package/lib/client/hydration/HydrationUtils.ts +3 -4
  89. package/lib/client/i18nConfigService.test.ts +2 -7
  90. package/lib/client/i18nConfigService.ts +2 -2
  91. package/lib/client/index.ts +4 -0
  92. package/lib/client/meno-filter/MenoFilter.test.ts +19 -21
  93. package/lib/client/meno-filter/MenoFilter.ts +5 -9
  94. package/lib/client/meno-filter/bindings.ts +15 -40
  95. package/lib/client/meno-filter/init.ts +1 -1
  96. package/lib/client/meno-filter/renderer.ts +23 -29
  97. package/lib/client/meno-filter/script.generated.ts +1 -3
  98. package/lib/client/meno-filter/ui.ts +5 -5
  99. package/lib/client/meno-filter/updates.ts +15 -21
  100. package/lib/client/navigation.test.ts +159 -159
  101. package/lib/client/navigation.ts +0 -1
  102. package/lib/client/responsiveStyleResolver.test.ts +230 -228
  103. package/lib/client/responsiveStyleResolver.ts +13 -16
  104. package/lib/client/routing/RouteLoader.test.ts +25 -26
  105. package/lib/client/routing/RouteLoader.ts +30 -37
  106. package/lib/client/routing/Router.tsx +112 -18
  107. package/lib/client/scripts/ScriptExecutor.test.ts +270 -128
  108. package/lib/client/scripts/ScriptExecutor.ts +69 -33
  109. package/lib/client/services/PrefetchService.test.ts +2 -2
  110. package/lib/client/services/PrefetchService.ts +10 -24
  111. package/lib/client/styleProcessor.test.ts +9 -9
  112. package/lib/client/styleProcessor.ts +18 -15
  113. package/lib/client/styles/StyleInjector.test.ts +122 -115
  114. package/lib/client/styles/StyleInjector.ts +25 -7
  115. package/lib/client/styles/UtilityClassCollector.ts +26 -27
  116. package/lib/client/styles/cspNonce.test.ts +64 -0
  117. package/lib/client/styles/cspNonce.ts +63 -0
  118. package/lib/client/templateEngine.test.ts +600 -448
  119. package/lib/client/templateEngine.ts +205 -64
  120. package/lib/client/theme.ts +0 -1
  121. package/lib/client/utils/toast.ts +0 -1
  122. package/lib/server/__integration__/api-routes.test.ts +8 -4
  123. package/lib/server/__integration__/cms-integration.test.ts +1 -4
  124. package/lib/server/__integration__/server-lifecycle.test.ts +2 -5
  125. package/lib/server/__integration__/ssr-rendering.test.ts +47 -37
  126. package/lib/server/__integration__/static-assets.test.ts +1 -1
  127. package/lib/server/__integration__/test-helpers.ts +84 -70
  128. package/lib/server/ab/generateFunctions.ts +12 -10
  129. package/lib/server/astro/cmsPageEmitter.ts +47 -32
  130. package/lib/server/astro/componentEmitter.ts +82 -37
  131. package/lib/server/astro/cssCollector.ts +10 -26
  132. package/lib/server/astro/nodeToAstro.test.ts +1750 -30
  133. package/lib/server/astro/nodeToAstro.ts +327 -178
  134. package/lib/server/astro/normalizeOrphanTemplateProps.test.ts +260 -0
  135. package/lib/server/astro/normalizeOrphanTemplateProps.ts +176 -0
  136. package/lib/server/astro/pageEmitter.ts +9 -13
  137. package/lib/server/astro/tailwindMapper.test.ts +10 -37
  138. package/lib/server/astro/tailwindMapper.ts +33 -40
  139. package/lib/server/astro/templateTransformer.ts +14 -17
  140. package/lib/server/createServer.ts +16 -17
  141. package/lib/server/cssGenerator.test.ts +35 -44
  142. package/lib/server/cssGenerator.ts +6 -17
  143. package/lib/server/draftPageStore.ts +49 -0
  144. package/lib/server/fileWatcher.test.ts +124 -10
  145. package/lib/server/fileWatcher.ts +164 -98
  146. package/lib/server/index.ts +20 -2
  147. package/lib/server/jsonLoader.test.ts +39 -2
  148. package/lib/server/jsonLoader.ts +33 -31
  149. package/lib/server/middleware/cors.test.ts +20 -20
  150. package/lib/server/middleware/cors.ts +28 -4
  151. package/lib/server/middleware/errorHandler.test.ts +5 -3
  152. package/lib/server/middleware/errorHandler.ts +3 -8
  153. package/lib/server/middleware/index.ts +0 -1
  154. package/lib/server/middleware/logger.test.ts +7 -5
  155. package/lib/server/middleware/logger.ts +10 -22
  156. package/lib/server/pageCache.test.ts +76 -77
  157. package/lib/server/pageCache.ts +0 -1
  158. package/lib/server/projectContext.ts +4 -3
  159. package/lib/server/providers/fileSystemCMSProvider.test.ts +124 -95
  160. package/lib/server/providers/fileSystemCMSProvider.ts +35 -20
  161. package/lib/server/providers/fileSystemPageProvider.test.ts +84 -0
  162. package/lib/server/providers/fileSystemPageProvider.ts +39 -12
  163. package/lib/server/routes/api/cms.test.ts +26 -14
  164. package/lib/server/routes/api/cms.ts +9 -14
  165. package/lib/server/routes/api/components.ts +35 -34
  166. package/lib/server/routes/api/config.ts +0 -1
  167. package/lib/server/routes/api/core-routes.ts +49 -76
  168. package/lib/server/routes/api/functions.ts +8 -16
  169. package/lib/server/routes/api/index.ts +3 -6
  170. package/lib/server/routes/api/pages.ts +21 -32
  171. package/lib/server/routes/api/shared.test.ts +1 -1
  172. package/lib/server/routes/api/shared.ts +65 -6
  173. package/lib/server/routes/api/variables.test.ts +1 -3
  174. package/lib/server/routes/api/variables.ts +1 -1
  175. package/lib/server/routes/index.ts +106 -19
  176. package/lib/server/routes/pages.ts +47 -35
  177. package/lib/server/routes/static.ts +16 -4
  178. package/lib/server/runtime/bundler.ts +47 -32
  179. package/lib/server/runtime/fs.ts +3 -13
  180. package/lib/server/runtime/httpServer.ts +5 -9
  181. package/lib/server/services/ColorService.ts +32 -27
  182. package/lib/server/services/EnumService.test.ts +2 -6
  183. package/lib/server/services/EnumService.ts +7 -2
  184. package/lib/server/services/VariableService.test.ts +1 -5
  185. package/lib/server/services/VariableService.ts +6 -1
  186. package/lib/server/services/cmsService.test.ts +116 -78
  187. package/lib/server/services/cmsService.ts +24 -54
  188. package/lib/server/services/componentService.test.ts +303 -20
  189. package/lib/server/services/componentService.ts +397 -76
  190. package/lib/server/services/configService.test.ts +9 -31
  191. package/lib/server/services/configService.ts +20 -27
  192. package/lib/server/services/fileWatcherService.ts +44 -30
  193. package/lib/server/services/index.ts +0 -1
  194. package/lib/server/services/pageService.test.ts +24 -3
  195. package/lib/server/services/pageService.ts +135 -16
  196. package/lib/server/ssr/attributeBuilder.ts +24 -8
  197. package/lib/server/ssr/buildErrorOverlay.ts +12 -13
  198. package/lib/server/ssr/clientDataInjector.ts +7 -21
  199. package/lib/server/ssr/cmsSSRProcessor.ts +3 -6
  200. package/lib/server/ssr/cssCollector.ts +1 -1
  201. package/lib/server/ssr/errorOverlay.test.ts +21 -2
  202. package/lib/server/ssr/errorOverlay.ts +39 -18
  203. package/lib/server/ssr/htmlGenerator.nonce.test.ts +3 -9
  204. package/lib/server/ssr/htmlGenerator.test.ts +173 -56
  205. package/lib/server/ssr/htmlGenerator.ts +191 -112
  206. package/lib/server/ssr/imageMetadata.test.ts +3 -1
  207. package/lib/server/ssr/imageMetadata.ts +25 -19
  208. package/lib/server/ssr/jsCollector.test.ts +3 -13
  209. package/lib/server/ssr/jsCollector.ts +3 -8
  210. package/lib/server/ssr/liveReloadIntegration.test.ts +184 -22
  211. package/lib/server/ssr/metaTagGenerator.ts +2 -2
  212. package/lib/server/ssr/ssrRenderer.branches.test.ts +1103 -0
  213. package/lib/server/ssr/ssrRenderer.test.ts +263 -246
  214. package/lib/server/ssr/ssrRenderer.ts +700 -231
  215. package/lib/server/ssrRenderer.test.ts +1044 -950
  216. package/lib/server/utils/jsonLineMapper.test.ts +28 -28
  217. package/lib/server/utils/jsonLineMapper.ts +1 -1
  218. package/lib/server/validateStyleCoverage.ts +18 -20
  219. package/lib/server/webflow/buildWebflow.ts +41 -53
  220. package/lib/server/webflow/nodeToWebflow.test.ts +150 -218
  221. package/lib/server/webflow/nodeToWebflow.ts +195 -258
  222. package/lib/server/webflow/styleMapper.test.ts +15 -56
  223. package/lib/server/webflow/styleMapper.ts +33 -41
  224. package/lib/server/webflow/types.ts +1 -8
  225. package/lib/server/websocketManager.ts +16 -20
  226. package/lib/shared/attributeNodeUtils.test.ts +15 -15
  227. package/lib/shared/attributeNodeUtils.ts +5 -12
  228. package/lib/shared/breakpoints.ts +4 -11
  229. package/lib/shared/cmsQueryParser.test.ts +50 -42
  230. package/lib/shared/cmsQueryParser.ts +49 -31
  231. package/lib/shared/colorVariableUtils.test.ts +5 -5
  232. package/lib/shared/colorVariableUtils.ts +3 -8
  233. package/lib/shared/componentRefs.ts +41 -0
  234. package/lib/shared/constants.test.ts +3 -3
  235. package/lib/shared/constants.ts +12 -8
  236. package/lib/shared/cssGeneration.test.ts +262 -144
  237. package/lib/shared/cssGeneration.ts +189 -514
  238. package/lib/shared/cssNamedColors.ts +152 -30
  239. package/lib/shared/cssProperties.test.ts +5 -6
  240. package/lib/shared/cssProperties.ts +479 -111
  241. package/lib/shared/elementClassName.test.ts +109 -109
  242. package/lib/shared/elementClassName.ts +1 -1
  243. package/lib/shared/elementUtils.ts +12 -16
  244. package/lib/shared/errorLogger.ts +2 -10
  245. package/lib/shared/errors.test.ts +2 -13
  246. package/lib/shared/errors.ts +2 -8
  247. package/lib/shared/expressionEvaluator.test.ts +119 -0
  248. package/lib/shared/expressionEvaluator.ts +95 -22
  249. package/lib/shared/fontCss.ts +101 -0
  250. package/lib/shared/fontLoader.test.ts +19 -5
  251. package/lib/shared/fontLoader.ts +8 -86
  252. package/lib/shared/friendlyError.test.ts +87 -0
  253. package/lib/shared/friendlyError.ts +120 -0
  254. package/lib/shared/gradientUtils.test.ts +1 -5
  255. package/lib/shared/gradientUtils.ts +2 -6
  256. package/lib/shared/hrefRefs.test.ts +130 -0
  257. package/lib/shared/hrefRefs.ts +92 -0
  258. package/lib/shared/i18n.test.ts +1 -1
  259. package/lib/shared/i18n.ts +13 -34
  260. package/lib/shared/index.ts +56 -0
  261. package/lib/shared/inlineSvgStyleRules.test.ts +108 -0
  262. package/lib/shared/inlineSvgStyleRules.ts +132 -0
  263. package/lib/shared/interactiveStyleMappings.test.ts +11 -33
  264. package/lib/shared/interactiveStyleMappings.ts +9 -16
  265. package/lib/shared/interactiveStyles.test.ts +165 -188
  266. package/lib/shared/interfaces/contentProvider.ts +14 -1
  267. package/lib/shared/itemTemplateUtils.test.ts +20 -12
  268. package/lib/shared/itemTemplateUtils.ts +23 -36
  269. package/lib/shared/jsonRepair.ts +8 -2
  270. package/lib/shared/libraryLoader.test.ts +15 -49
  271. package/lib/shared/libraryLoader.ts +7 -22
  272. package/lib/shared/netlifyLocale404.test.ts +179 -0
  273. package/lib/shared/netlifyLocale404.ts +110 -0
  274. package/lib/shared/nodeUtils.test.ts +24 -16
  275. package/lib/shared/nodeUtils.ts +49 -19
  276. package/lib/shared/pathArrayUtils.test.ts +1 -2
  277. package/lib/shared/pathArrayUtils.ts +1 -1
  278. package/lib/shared/pathSecurity.ts +1 -1
  279. package/lib/shared/pathUtils.test.ts +4 -6
  280. package/lib/shared/pathUtils.ts +42 -48
  281. package/lib/shared/paths/Path.test.ts +2 -2
  282. package/lib/shared/paths/Path.ts +0 -1
  283. package/lib/shared/paths/PathConverter.test.ts +1 -1
  284. package/lib/shared/paths/PathConverter.ts +14 -17
  285. package/lib/shared/paths/PathUtils.ts +9 -10
  286. package/lib/shared/paths/PathValidator.test.ts +2 -15
  287. package/lib/shared/paths/PathValidator.ts +11 -9
  288. package/lib/shared/paths/index.ts +1 -2
  289. package/lib/shared/propResolver.test.ts +240 -244
  290. package/lib/shared/propResolver.ts +14 -25
  291. package/lib/shared/pxToRem.test.ts +7 -6
  292. package/lib/shared/pxToRem.ts +2 -5
  293. package/lib/shared/registry/BaseNodeTypeRegistry.test.ts +9 -5
  294. package/lib/shared/registry/ClientRegistry.ts +0 -1
  295. package/lib/shared/registry/ComponentRegistry.test.ts +43 -29
  296. package/lib/shared/registry/ComponentRegistry.ts +9 -11
  297. package/lib/shared/registry/NodeTypeDefinition.ts +15 -8
  298. package/lib/shared/registry/RegistryManager.ts +1 -2
  299. package/lib/shared/registry/SSRRegistry.ts +0 -1
  300. package/lib/shared/registry/createNodeType.ts +7 -9
  301. package/lib/shared/registry/defineNodeType.ts +2 -6
  302. package/lib/shared/registry/index.ts +0 -1
  303. package/lib/shared/registry/nodeTypes/ComponentInstanceNodeType.ts +14 -15
  304. package/lib/shared/registry/nodeTypes/EmbedNodeType.ts +18 -11
  305. package/lib/shared/registry/nodeTypes/HtmlNodeType.ts +47 -18
  306. package/lib/shared/registry/nodeTypes/LinkNodeType.ts +22 -20
  307. package/lib/shared/registry/nodeTypes/ListNodeType.ts +78 -74
  308. package/lib/shared/registry/nodeTypes/LocaleListNodeType.ts +27 -21
  309. package/lib/shared/registry/nodeTypes/SlotMarkerType.ts +6 -7
  310. package/lib/shared/registry/nodeTypes/index.ts +10 -2
  311. package/lib/shared/responsiveScaling.test.ts +15 -31
  312. package/lib/shared/responsiveScaling.ts +55 -37
  313. package/lib/shared/responsiveStyleUtils.ts +11 -13
  314. package/lib/shared/richtext/htmlToTiptap.test.ts +23 -14
  315. package/lib/shared/richtext/htmlToTiptap.ts +1 -3
  316. package/lib/shared/richtext/tiptapToHtml.test.ts +5 -6
  317. package/lib/shared/richtext/types.ts +1 -8
  318. package/lib/shared/slugTranslator.test.ts +37 -13
  319. package/lib/shared/slugTranslator.ts +31 -11
  320. package/lib/shared/slugify.ts +9 -15
  321. package/lib/shared/styleNodeUtils.test.ts +8 -8
  322. package/lib/shared/styleNodeUtils.ts +9 -11
  323. package/lib/shared/styleUtils.test.ts +87 -61
  324. package/lib/shared/styleUtils.ts +5 -6
  325. package/lib/shared/themeDefaults.test.ts +11 -11
  326. package/lib/shared/themeDefaults.ts +3 -4
  327. package/lib/shared/tree/PathBuilder.test.ts +160 -109
  328. package/lib/shared/tree/PathBuilder.ts +121 -59
  329. package/lib/shared/treePathUtils.test.ts +2 -10
  330. package/lib/shared/treePathUtils.ts +54 -59
  331. package/lib/shared/types/api.ts +1 -2
  332. package/lib/shared/types/cms.ts +25 -21
  333. package/lib/shared/types/comment.ts +132 -0
  334. package/lib/shared/types/components.ts +27 -25
  335. package/lib/shared/types/errors.test.ts +1 -6
  336. package/lib/shared/types/errors.ts +3 -7
  337. package/lib/shared/types/experiments.ts +28 -28
  338. package/lib/shared/types/index.ts +14 -2
  339. package/lib/shared/types/rendering.ts +8 -0
  340. package/lib/shared/types/styles.ts +0 -1
  341. package/lib/shared/types/variables.test.ts +4 -13
  342. package/lib/shared/types/variables.ts +48 -27
  343. package/lib/shared/types.ts +1 -2
  344. package/lib/shared/utilityClassConfig.ts +648 -319
  345. package/lib/shared/utilityClassMapper.test.ts +213 -78
  346. package/lib/shared/utilityClassMapper.ts +188 -246
  347. package/lib/shared/utilityClassNames.ts +326 -0
  348. package/lib/shared/utils.test.ts +2 -10
  349. package/lib/shared/utils.ts +19 -10
  350. package/lib/shared/validation/cmsValidators.ts +2 -1
  351. package/lib/shared/validation/commentValidators.test.ts +53 -0
  352. package/lib/shared/validation/commentValidators.ts +80 -0
  353. package/lib/shared/validation/index.ts +1 -0
  354. package/lib/shared/validation/propValidator.test.ts +18 -20
  355. package/lib/shared/validation/propValidator.ts +12 -17
  356. package/lib/shared/validation/schemas.test.ts +24 -33
  357. package/lib/shared/validation/schemas.ts +469 -344
  358. package/lib/shared/validation/validators.test.ts +1 -6
  359. package/lib/shared/validation/validators.ts +89 -68
  360. package/lib/shared/viewportUnits.integration.test.ts +46 -0
  361. package/lib/shared/viewportUnits.test.ts +91 -0
  362. package/lib/shared/viewportUnits.ts +63 -0
  363. package/lib/test-utils/dom-setup.ts +7 -1
  364. package/lib/test-utils/factories/ConsoleMockFactory.ts +3 -7
  365. package/lib/test-utils/factories/DomMockFactory.ts +7 -19
  366. package/lib/test-utils/factories/EventMockFactory.ts +7 -13
  367. package/lib/test-utils/factories/FetchMockFactory.ts +39 -57
  368. package/lib/test-utils/factories/ServerMockFactory.ts +5 -9
  369. package/lib/test-utils/factories/StoreMockFactory.ts +14 -25
  370. package/lib/test-utils/fixtures.ts +45 -45
  371. package/lib/test-utils/helpers/asyncHelpers.test.ts +15 -18
  372. package/lib/test-utils/helpers/asyncHelpers.ts +11 -20
  373. package/lib/test-utils/helpers.ts +1 -5
  374. package/lib/test-utils/index.ts +0 -4
  375. package/lib/test-utils/mockFactories.ts +12 -18
  376. package/lib/test-utils/mocks.ts +4 -2
  377. package/package.json +1 -1
  378. package/scripts/build-meno-filter.ts +1 -4
  379. package/vite.config.ts +4 -4
  380. package/dist/chunks/chunk-2MHDV5BF.js.map +0 -7
  381. package/dist/chunks/chunk-2QK6U5UK.js.map +0 -7
  382. package/dist/chunks/chunk-7NIC4I3V.js.map +0 -7
  383. package/dist/chunks/chunk-A725KYFK.js.map +0 -7
  384. package/dist/chunks/chunk-AZQYF6KE.js.map +0 -7
  385. package/dist/chunks/chunk-CXCBV2M7.js.map +0 -7
  386. package/dist/chunks/chunk-EDQSMAMP.js.map +0 -7
  387. package/dist/chunks/chunk-H4JSCDNW.js.map +0 -7
  388. package/dist/chunks/chunk-HNLUO36W.js.map +0 -7
  389. package/dist/chunks/chunk-I7YIGZXT.js.map +0 -7
  390. package/dist/chunks/chunk-J23ZX5AP.js +0 -241
  391. package/dist/chunks/chunk-J23ZX5AP.js.map +0 -7
  392. package/dist/chunks/chunk-LHLHPYSP.js.map +0 -7
  393. package/dist/chunks/chunk-LPVETICS.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/configService-R3OGU2UD.js +0 -13
  397. /package/dist/chunks/{configService-R3OGU2UD.js.map → configService-PRJZF7Y6.js.map} +0 -0
  398. /package/dist/chunks/{constants-GWBAD66U.js.map → constants-KIQEYMAM.js.map} +0 -0
  399. /package/dist/chunks/{fs-JGINUXGL.js.map → fs-ZI5JEU7V.js.map} +0 -0
package/build-astro.ts CHANGED
@@ -4,45 +4,54 @@
4
4
  * with a shared layout, global CSS, and optional CMS content collections.
5
5
  */
6
6
 
7
- import { existsSync, readdirSync, mkdirSync, rmSync, statSync, copyFileSync, writeFileSync } from "fs";
8
- import { writeFile, readFile } from "fs/promises";
9
- import { join } from "path";
10
- import { createHash } from "crypto";
7
+ import { existsSync, readdirSync, mkdirSync, rmSync, statSync, copyFileSync, writeFileSync } from 'fs';
8
+ import { writeFile, readFile } from 'fs/promises';
9
+ import { join } from 'path';
10
+ import { createHash } from 'crypto';
11
11
  import { inspect, minifyJS as runtimeMinifyJS } from './lib/server/runtime';
12
12
  import {
13
13
  loadJSONFile,
14
14
  loadComponentDirectory,
15
15
  mapPageNameToPath,
16
16
  parseJSON,
17
- loadI18nConfig
18
- } from "./lib/server/jsonLoader";
19
- import { generateSSRHTML } from "./lib/server/ssrRenderer";
20
- import type { SSRHTMLResult } from "./lib/server/ssr/htmlGenerator";
21
- import { projectPaths } from "./lib/server/projectContext";
22
- import { loadProjectConfig, generateFontCSS, generateFontPreloadTags } from "./lib/shared/fontLoader";
23
- import { FileSystemCMSProvider } from "./lib/server/providers/fileSystemCMSProvider";
24
- import { CMSService } from "./lib/server/services/cmsService";
25
- import { isI18nValue, resolveI18nValue } from "./lib/shared/i18n";
26
- import type { ComponentDefinition, JSONPage, CMSSchema, CMSItem, I18nConfig } from "./lib/shared/types";
27
- import type { CMSFieldDefinition } from "./lib/shared/types/cms";
28
- import { isItemDraftForLocale } from "./lib/shared/types";
29
- import type { SlugMap } from "./lib/shared/slugTranslator";
30
- import { renderPageSSR } from "./lib/server/ssr/ssrRenderer";
31
- import { generateThemeColorVariablesCSS, generateVariablesCSS } from "./lib/server/cssGenerator";
32
- import { colorService } from "./lib/server/services/ColorService";
33
- import { variableService } from "./lib/server/services/VariableService";
34
- import { configService } from "./lib/server/services/configService";
35
- import { loadBreakpointConfig, loadIconsConfig } from "./lib/server/jsonLoader";
36
- import type { InteractiveStyles } from "./lib/shared/types/styles";
37
- import { collectComponentLibraries, filterLibrariesByContext, mergeLibraries, generateLibraryTags } from "./lib/shared/libraryLoader";
38
- import { migrateTemplatesDirectory } from "./lib/server/migrateTemplates";
39
- import { emitAstroComponent } from "./lib/server/astro/componentEmitter";
40
- import { emitAstroPage } from "./lib/server/astro/pageEmitter";
41
- import { emitCMSPage } from './lib/server/astro/cmsPageEmitter';
42
- import { collectAllMappingClasses } from "./lib/server/astro/cssCollector";
43
- import { buildImageMetadataMap, RESPONSIVE_WIDTHS } from "./lib/server/ssr/imageMetadata";
44
- import { needsFormHandler, formHandlerScript } from "./lib/client/scripts/formHandler";
45
-
17
+ loadI18nConfig,
18
+ } from './lib/server/jsonLoader';
19
+ import { generateSSRHTML } from './lib/server/ssrRenderer';
20
+ import type { SSRHTMLResult } from './lib/server/ssr/htmlGenerator';
21
+ import { projectPaths } from './lib/server/projectContext';
22
+ import { loadProjectConfig, generateFontCSS, generateFontPreloadTags } from './lib/shared/fontLoader';
23
+ import { FileSystemCMSProvider } from './lib/server/providers/fileSystemCMSProvider';
24
+ import { CMSService } from './lib/server/services/cmsService';
25
+ import { isI18nValue, resolveI18nValue } from './lib/shared/i18n';
26
+ import { syncNetlifyLocale404Block } from './lib/shared/netlifyLocale404';
27
+ import { tiptapToHtml } from './lib/shared/richtext/tiptapToHtml';
28
+ import { isTiptapDocument } from './lib/shared/richtext/types';
29
+ import type { ComponentDefinition, JSONPage, CMSSchema, CMSItem, I18nConfig } from './lib/shared/types';
30
+ import type { CMSFieldDefinition } from './lib/shared/types/cms';
31
+ import { isItemDraftForLocale } from './lib/shared/types';
32
+ import { CMS_DRAFT_SUFFIX } from './lib/shared/pathSecurity';
33
+ import type { SlugMap } from './lib/shared/slugTranslator';
34
+ import { renderPageSSR } from './lib/server/ssr/ssrRenderer';
35
+ import { generateThemeColorVariablesCSS, generateVariablesCSS } from './lib/server/cssGenerator';
36
+ import { colorService } from './lib/server/services/ColorService';
37
+ import { variableService } from './lib/server/services/VariableService';
38
+ import { configService } from './lib/server/services/configService';
39
+ import { loadBreakpointConfig, loadIconsConfig } from './lib/server/jsonLoader';
40
+ import type { InteractiveStyles } from './lib/shared/types/styles';
41
+ import {
42
+ collectComponentLibraries,
43
+ filterLibrariesByContext,
44
+ mergeLibraries,
45
+ generateLibraryTags,
46
+ } from './lib/shared/libraryLoader';
47
+ import { migrateTemplatesDirectory } from './lib/server/migrateTemplates';
48
+ import { emitAstroComponent } from './lib/server/astro/componentEmitter';
49
+ import { emitAstroPage } from './lib/server/astro/pageEmitter';
50
+ import { normalizeOrphanTemplateProps } from './lib/server/astro/normalizeOrphanTemplateProps';
51
+ import { emitCMSPage, CMS_SLUG_PLACEHOLDER } from './lib/server/astro/cmsPageEmitter';
52
+ import { collectAllMappingClasses } from './lib/server/astro/cssCollector';
53
+ import { buildImageMetadataMap, RESPONSIVE_WIDTHS } from './lib/server/ssr/imageMetadata';
54
+ import { needsFormHandler, formHandlerScript } from './lib/client/scripts/formHandler';
46
55
 
47
56
  // ---------------------------------------------------------------------------
48
57
  // Helpers
@@ -66,11 +75,7 @@ function writePageScript(javascript: string | undefined, scriptsDir: string): st
66
75
  return [`/_scripts/${scriptFile}`];
67
76
  }
68
77
 
69
- function copyDirectory(
70
- src: string,
71
- dest: string,
72
- filter?: (filename: string) => boolean,
73
- ): void {
78
+ function copyDirectory(src: string, dest: string, filter?: (filename: string) => boolean): void {
74
79
  if (!existsSync(src)) return;
75
80
  if (!existsSync(dest)) mkdirSync(dest, { recursive: true });
76
81
  const files = readdirSync(src);
@@ -87,9 +92,7 @@ function copyDirectory(
87
92
  // Astro's <Picture> re-derives responsive variants from originals at build
88
93
  // time, so the pre-baked -{width}.webp/.avif files and the SSR-only
89
94
  // manifest.json are dead weight in the exported project.
90
- const imageVariantSuffixRe = new RegExp(
91
- `-(${RESPONSIVE_WIDTHS.join('|')})\\.(webp|avif)$`,
92
- );
95
+ const imageVariantSuffixRe = new RegExp(`-(${RESPONSIVE_WIDTHS.join('|')})\\.(webp|avif)$`);
93
96
  function shouldCopyImageForAstro(filename: string): boolean {
94
97
  if (filename === 'manifest.json') return false;
95
98
  if (imageVariantSuffixRe.test(filename)) return false;
@@ -108,7 +111,7 @@ function buildCMSItemPath(
108
111
  item: CMSItem,
109
112
  slugField: string,
110
113
  locale: string,
111
- i18nConfig: I18nConfig
114
+ i18nConfig: I18nConfig,
112
115
  ): string {
113
116
  let slug = item[slugField] ?? item._slug ?? item._id;
114
117
  if (isI18nValue(slug)) {
@@ -156,10 +159,7 @@ function escapeTemplateLiteral(s: string): string {
156
159
  * Compute locale → URL path map for a page's slug translations.
157
160
  * Used by locale list rendering to generate correct links.
158
161
  */
159
- function computePageSlugMap(
160
- slugs: Record<string, string>,
161
- i18nConfig: I18nConfig
162
- ): Record<string, string> {
162
+ function computePageSlugMap(slugs: Record<string, string>, i18nConfig: I18nConfig): Record<string, string> {
163
163
  const map: Record<string, string> = {};
164
164
  for (const localeConfig of i18nConfig.locales) {
165
165
  const code = localeConfig.code;
@@ -182,7 +182,9 @@ function cmsFieldToZod(field: CMSFieldDefinition): string {
182
182
  case 'string':
183
183
  case 'text':
184
184
  case 'rich-text':
185
- // Support both plain strings and i18n objects { _i18n: true, en: "...", pl: "..." }
185
+ // Rich-text is serialized to an HTML string at item-copy time (see the
186
+ // CMS collection emission below), so by the time Astro validates it the
187
+ // value is a string (or an i18n object of strings). Support both.
186
188
  return 'z.union([z.string(), z.object({ _i18n: z.literal(true) }).passthrough()])';
187
189
  case 'number':
188
190
  return 'z.number()';
@@ -192,7 +194,7 @@ function cmsFieldToZod(field: CMSFieldDefinition): string {
192
194
  return 'z.coerce.date()';
193
195
  case 'select':
194
196
  if (field.options && field.options.length > 0) {
195
- const opts = field.options.map(o => `'${o.replace(/'/g, "\\'")}'`).join(', ');
197
+ const opts = field.options.map((o) => `'${o.replace(/'/g, "\\'")}'`).join(', ');
196
198
  return `z.enum([${opts}])`;
197
199
  }
198
200
  return 'z.string()';
@@ -206,6 +208,101 @@ function cmsFieldToZod(field: CMSFieldDefinition): string {
206
208
  }
207
209
  }
208
210
 
211
+ /**
212
+ * Serialize a single rich-text field value to an HTML string. Handles plain
213
+ * Tiptap doc objects, the `{ html: "..." }` marker shape, i18n-wrapped values
214
+ * ({ _i18n: true, en: <doc|string>, ... }), and already-serialized strings.
215
+ */
216
+ function serializeRichTextValue(value: unknown): unknown {
217
+ const one = (v: unknown): unknown => {
218
+ if (v == null || typeof v === 'string') return v ?? '';
219
+ if (isTiptapDocument(v)) return tiptapToHtml(v);
220
+ if (typeof v === 'object' && v !== null && typeof (v as { html?: unknown }).html === 'string') {
221
+ return (v as { html: string }).html;
222
+ }
223
+ return v;
224
+ };
225
+
226
+ if (isI18nValue(value)) {
227
+ const out: Record<string, unknown> = { _i18n: true };
228
+ for (const [k, v] of Object.entries(value as Record<string, unknown>)) {
229
+ if (k === '_i18n') continue;
230
+ out[k] = one(v);
231
+ }
232
+ return out;
233
+ }
234
+ return one(value);
235
+ }
236
+
237
+ /**
238
+ * Walk a component structure and collect the names of every component instance
239
+ * it references (`{ type: 'component', component: 'X' }`).
240
+ */
241
+ function collectComponentRefs(node: unknown, acc: Set<string>): void {
242
+ if (Array.isArray(node)) {
243
+ for (const child of node) collectComponentRefs(child, acc);
244
+ return;
245
+ }
246
+ if (!node || typeof node !== 'object') return;
247
+ const n = node as Record<string, unknown>;
248
+ if (n.type === 'component' && typeof n.component === 'string') {
249
+ acc.add(n.component);
250
+ }
251
+ for (const value of Object.values(n)) {
252
+ if (value && typeof value === 'object') collectComponentRefs(value, acc);
253
+ }
254
+ }
255
+
256
+ /**
257
+ * Compute the set of components that consume the CMS entry (`{{cms.*}}`),
258
+ * transitively including any component that renders a consumer in its own
259
+ * structure (it must forward the `cms` prop down). Astro components have
260
+ * isolated scopes, so each consumer needs the entry threaded in explicitly.
261
+ */
262
+ function computeCmsConsumerComponents(components: Record<string, ComponentDefinition>): Set<string> {
263
+ const consumers = new Set<string>();
264
+ const refsByComponent = new Map<string, Set<string>>();
265
+
266
+ for (const [name, def] of Object.entries(components)) {
267
+ const structure = def.component?.structure;
268
+ if (structure && JSON.stringify(structure).includes('{{cms.')) {
269
+ consumers.add(name);
270
+ }
271
+ const refs = new Set<string>();
272
+ collectComponentRefs(structure, refs);
273
+ refsByComponent.set(name, refs);
274
+ }
275
+
276
+ // Fixpoint: a component that renders a consumer is itself a consumer/forwarder.
277
+ let changed = true;
278
+ while (changed) {
279
+ changed = false;
280
+ for (const [name, refs] of refsByComponent) {
281
+ if (consumers.has(name)) continue;
282
+ for (const ref of refs) {
283
+ if (consumers.has(ref)) {
284
+ consumers.add(name);
285
+ changed = true;
286
+ break;
287
+ }
288
+ }
289
+ }
290
+ }
291
+ return consumers;
292
+ }
293
+
294
+ /**
295
+ * Build the `_url` expression (a JS template literal over the `e` collection
296
+ * entry) for a CMS collection, so flattened list items expose a usable link.
297
+ * E.g. urlPattern "/blog/{{slug}}" + slugField "slug" → `/blog/${e.data.slug ?? e.id}`.
298
+ */
299
+ function buildCollectionUrlExpr(schema: CMSSchema): string {
300
+ const slugField = schema.slugField || 'slug';
301
+ const pattern = schema.urlPattern || `/${schema.id}/{{slug}}`;
302
+ const body = pattern.replace(/\{\{[^}]+\}\}/, '${e.data.' + slugField + ' ?? e.id}');
303
+ return '`' + body + '`';
304
+ }
305
+
209
306
  // ---------------------------------------------------------------------------
210
307
  // Types
211
308
  // ---------------------------------------------------------------------------
@@ -260,15 +357,13 @@ function buildSSRFallbackPage(
260
357
  fontPreloads: string,
261
358
  libraryTags: { headCSS?: string; headJS?: string; bodyEndJS?: string },
262
359
  defaultTheme: string,
263
- scriptPaths: string[]
360
+ scriptPaths: string[],
264
361
  ): string {
265
362
  const escapedMeta = escapeTemplateLiteral(result.meta);
266
363
  const escapedHTML = escapeTemplateLiteral(result.html);
267
364
  const escapedFontPreloads = escapeTemplateLiteral(fontPreloads);
268
365
 
269
- const scriptsArrayLiteral = scriptPaths.length > 0
270
- ? `[${scriptPaths.map(s => `"${s}"`).join(', ')}]`
271
- : '[]';
366
+ const scriptsArrayLiteral = scriptPaths.length > 0 ? `[${scriptPaths.map((s) => `"${s}"`).join(', ')}]` : '[]';
272
367
 
273
368
  const libraryTagsLiteral = `{ headCSS: \`${escapeTemplateLiteral(libraryTags.headCSS || '')}\`, headJS: \`${escapeTemplateLiteral(libraryTags.headJS || '')}\`, bodyEndJS: \`${escapeTemplateLiteral(libraryTags.bodyEndJS || '')}\` }`;
274
369
 
@@ -293,10 +388,7 @@ import BaseLayout from '${importPath}';
293
388
  // Main export
294
389
  // ---------------------------------------------------------------------------
295
390
 
296
- export async function buildAstroProject(
297
- projectRoot?: string,
298
- outputDir?: string
299
- ): Promise<AstroBuildStats> {
391
+ export async function buildAstroProject(projectRoot?: string, outputDir?: string): Promise<AstroBuildStats> {
300
392
  // ----------------------------------------------------------
301
393
  // 1. Setup: load project configuration
302
394
  // ----------------------------------------------------------
@@ -311,7 +403,9 @@ export async function buildAstroProject(
311
403
 
312
404
  const { components, warnings, errors: compErrors } = await loadComponentDirectory(projectPaths.components());
313
405
  const globalComponents: Record<string, ComponentDefinition> = {};
314
- components.forEach((value, key) => { globalComponents[key] = value; });
406
+ components.forEach((value, key) => {
407
+ globalComponents[key] = value;
408
+ });
315
409
  for (const w of warnings) console.warn(` Warning: ${w}`);
316
410
  for (const e of compErrors) console.error(` Error: ${e}`);
317
411
 
@@ -382,7 +476,9 @@ export async function buildAstroProject(
382
476
  const pageId = basePath === '/' ? 'index' : basePath.substring(1);
383
477
  slugMappings.push({ pageId, slugs: pageData.meta.slugs });
384
478
  }
385
- } catch { /* ignore parse errors in first pass */ }
479
+ } catch {
480
+ /* ignore parse errors in first pass */
481
+ }
386
482
  }
387
483
 
388
484
  // ----------------------------------------------------------
@@ -406,13 +502,25 @@ export async function buildAstroProject(
406
502
 
407
503
  // Helper to process a render result
408
504
  function processRenderResult(
409
- result: { html: string; meta: string; title: string; javascript: string; componentCSS?: string; locale: string; interactiveStylesMap: Map<string, InteractiveStyles>; preloadImages: any[]; neededCollections: Set<string>; ssrFallbackCollector?: Map<string, string>; processedRawHtmlCollector?: Map<string, string> },
505
+ result: {
506
+ html: string;
507
+ meta: string;
508
+ title: string;
509
+ javascript: string;
510
+ componentCSS?: string;
511
+ locale: string;
512
+ interactiveStylesMap: Map<string, InteractiveStyles>;
513
+ preloadImages: any[];
514
+ neededCollections: Set<string>;
515
+ ssrFallbackCollector?: Map<string, string>;
516
+ processedRawHtmlCollector?: Map<string, string>;
517
+ },
410
518
  urlPath: string,
411
519
  astroFilePath: string,
412
520
  fileDepth: number,
413
521
  pageData?: JSONPage,
414
522
  pageName?: string,
415
- isCMSPage?: boolean
523
+ isCMSPage?: boolean,
416
524
  ): void {
417
525
  // Collect interactive styles
418
526
  mergeInteractiveStyles(result.interactiveStylesMap);
@@ -484,7 +592,12 @@ export async function buildAstroProject(
484
592
  // Compute URL path
485
593
  let slug: string;
486
594
  if (slugs && slugs[locale]) {
487
- slug = slugs[locale];
595
+ // Slugs may be authored with a leading slash (e.g. "/blog" from the
596
+ // page-rename flow). Normalize to the bare form the rest of the
597
+ // pipeline expects — matches buildPageUrlForLocale(). A leading slash
598
+ // here would otherwise inflate fileDepth (".../blog.astro".split('/'))
599
+ // and produce a wrong "../../layouts" import.
600
+ slug = slugs[locale].replace(/^\/+/, '');
488
601
  } else if (basePath === '/') {
489
602
  slug = '';
490
603
  } else {
@@ -492,8 +605,12 @@ export async function buildAstroProject(
492
605
  }
493
606
 
494
607
  const urlPath = isDefault
495
- ? (slug === '' ? '/' : `/${slug}`)
496
- : (slug === '' ? `/${locale}` : `/${locale}/${slug}`);
608
+ ? slug === ''
609
+ ? '/'
610
+ : `/${slug}`
611
+ : slug === ''
612
+ ? `/${locale}`
613
+ : `/${locale}/${slug}`;
497
614
 
498
615
  // Determine .astro file path relative to src/pages/
499
616
  const astroFileName = slug === '' ? 'index.astro' : `${slug}.astro`;
@@ -510,7 +627,7 @@ export async function buildAstroProject(
510
627
  slugMappings,
511
628
  undefined, // cmsContext
512
629
  cmsService,
513
- true // isProductionBuild
630
+ true, // isProductionBuild
514
631
  );
515
632
 
516
633
  processRenderResult(result, urlPath, astroFilePath, fileDepth, pageData, pageName, false);
@@ -584,8 +701,34 @@ export async function buildAstroProject(
584
701
  const templateSchemas: CMSSchema[] = [];
585
702
  let cmsPageCount = 0;
586
703
 
704
+ // Pre-pass: components that consume the CMS entry (so the page/component
705
+ // emitters know which instances to thread `cms={...}` into), plus per-
706
+ // collection `_url` expressions (for flattened collection lists). Built
707
+ // before the template loop so the very first emitted CMS page sees the
708
+ // full picture even when it lists items from another collection.
709
+ const cmsConsumerComponents = computeCmsConsumerComponents(globalComponents);
710
+ const collectionUrlExpr = new Map<string, string>();
711
+ const mergedRichTextFields = new Set<string>();
712
+ if (existsSync(templatesDir)) {
713
+ for (const file of readdirSync(templatesDir).filter((f) => f.endsWith('.json'))) {
714
+ const tc = await loadJSONFile(join(templatesDir, file));
715
+ if (!tc) continue;
716
+ try {
717
+ const pd = parseJSON<JSONPage>(tc);
718
+ const schema = pd.meta?.cms as CMSSchema | undefined;
719
+ if (!schema?.id) continue;
720
+ collectionUrlExpr.set(schema.id, buildCollectionUrlExpr(schema));
721
+ for (const [fn, fd] of Object.entries(schema.fields || {})) {
722
+ if (fd.type === 'rich-text') mergedRichTextFields.add(fn);
723
+ }
724
+ } catch {
725
+ /* ignore parse errors; handled in main loop */
726
+ }
727
+ }
728
+ }
729
+
587
730
  if (existsSync(templatesDir)) {
588
- const templateFiles = readdirSync(templatesDir).filter(f => f.endsWith('.json'));
731
+ const templateFiles = readdirSync(templatesDir).filter((f) => f.endsWith('.json'));
589
732
 
590
733
  for (const file of templateFiles) {
591
734
  const templateContent = await loadJSONFile(join(templatesDir, file));
@@ -613,7 +756,7 @@ export async function buildAstroProject(
613
756
 
614
757
  // Render SSR once for metadata collection (interactive styles, component CSS, JS)
615
758
  const defaultLocale = i18nConfig.defaultLocale;
616
- const dummyPath = cmsSchema.urlPattern.replace('{{slug}}', '__placeholder__');
759
+ const dummyPath = cmsSchema.urlPattern.replace('{{slug}}', CMS_SLUG_PLACEHOLDER);
617
760
 
618
761
  const metaResult = await renderPageSSR(
619
762
  pageData,
@@ -625,7 +768,7 @@ export async function buildAstroProject(
625
768
  slugMappings,
626
769
  undefined, // no CMS context - just collecting metadata
627
770
  cmsService,
628
- true
771
+ true,
629
772
  );
630
773
 
631
774
  // Collect interactive styles and component CSS
@@ -663,13 +806,9 @@ export async function buildAstroProject(
663
806
  // Route file path: blog/[slug].astro for default, pl/blog/[slug].astro for non-default
664
807
  let astroFilePath: string;
665
808
  if (pathPrefix) {
666
- astroFilePath = isDefault
667
- ? `${pathPrefix}[slug].astro`
668
- : `${localeCode}/${pathPrefix}[slug].astro`;
809
+ astroFilePath = isDefault ? `${pathPrefix}[slug].astro` : `${localeCode}/${pathPrefix}[slug].astro`;
669
810
  } else {
670
- astroFilePath = isDefault
671
- ? '[slug].astro'
672
- : `${localeCode}/[slug].astro`;
811
+ astroFilePath = isDefault ? '[slug].astro' : `${localeCode}/[slug].astro`;
673
812
  }
674
813
 
675
814
  const fileDepth = astroFilePath.split('/').length - 1;
@@ -699,6 +838,8 @@ export async function buildAstroProject(
699
838
  imageFormat: configService.getImageFormat(),
700
839
  processedRawHtml: metaResult.processedRawHtmlCollector,
701
840
  remConfig: remConversionConfig,
841
+ cmsConsumers: cmsConsumerComponents,
842
+ collectionUrlExpr,
702
843
  });
703
844
 
704
845
  const astroFileFull = join(pagesOutDir, astroFilePath);
@@ -740,9 +881,7 @@ export async function buildAstroProject(
740
881
  }`;
741
882
 
742
883
  const safelistClasses = Array.from(mappingClasses);
743
- const safelistDirectives = safelistClasses
744
- .map(c => `@source inline("${c}");`)
745
- .join('\n');
884
+ const safelistDirectives = safelistClasses.map((c) => `@source inline("${c}");`).join('\n');
746
885
  const tailwindDirectives = safelistDirectives
747
886
  ? `@import "tailwindcss";\n\n${safelistDirectives}`
748
887
  : `@import "tailwindcss";`;
@@ -758,19 +897,14 @@ export async function buildAstroProject(
758
897
  // ----------------------------------------------------------
759
898
  // Escape for embedding inside Astro <Fragment set:html={`...`}> template
760
899
  // literals in the generated BaseLayout file.
761
- const escForTemplateLiteral = (s: string) => s
762
- .replace(/\\/g, '\\\\')
763
- .replace(/`/g, '\\`')
764
- .replace(/\$\{/g, '\\${');
900
+ const escForTemplateLiteral = (s: string) => s.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$\{/g, '\\${');
765
901
 
766
902
  const customHeadLiteral = escForTemplateLiteral(customCode.head || '');
767
903
  const customBodyStartLiteral = escForTemplateLiteral(customCode.bodyStart || '');
768
904
  const customBodyEndLiteral = escForTemplateLiteral(customCode.bodyEnd || '');
769
905
  const iconTagsLiteral = escForTemplateLiteral(iconTagsHtml);
770
906
 
771
- const formHandlerBlock = projectNeedsFormHandler
772
- ? `\n <script is:inline>\n${formHandlerScript}\n </script>`
773
- : '';
907
+ const formHandlerBlock = projectNeedsFormHandler ? `\n <script is:inline>\n${formHandlerScript}\n </script>` : '';
774
908
 
775
909
  const baseLayoutContent = `---
776
910
  import '../styles/global.css';
@@ -815,10 +949,29 @@ const { title, meta = '', scripts = [], locale = 'en', theme = '${themeConfig.de
815
949
  // ----------------------------------------------------------
816
950
  // 7.5. Generate component .astro files
817
951
  // ----------------------------------------------------------
952
+ // SSR/Next.js resolve orphan `{{x}}` refs in a component's structure via
953
+ // the parentProps cascade in templateEngine. Astro can't fall back at
954
+ // runtime (each .astro file is its own scope), so we lift orphan refs
955
+ // onto each component's interface and forward them from hosts that
956
+ // declare the same prop. See `normalizeOrphanTemplateProps` for details.
957
+ const emittableComponents = normalizeOrphanTemplateProps(globalComponents);
818
958
  let componentFileCount = 0;
819
- for (const [compName, compDef] of Object.entries(globalComponents)) {
959
+ for (const [compName, compDef] of Object.entries(emittableComponents)) {
820
960
  try {
821
- const astroContent = emitAstroComponent(compName, compDef, globalComponents, breakpoints, i18nConfig.defaultLocale, responsiveScales, remConversionConfig);
961
+ const astroContent = emitAstroComponent(
962
+ compName,
963
+ compDef,
964
+ emittableComponents,
965
+ breakpoints,
966
+ i18nConfig.defaultLocale,
967
+ responsiveScales,
968
+ remConversionConfig,
969
+ {
970
+ cmsConsumers: cmsConsumerComponents,
971
+ cmsRichTextFields: mergedRichTextFields,
972
+ collectionUrlExpr,
973
+ },
974
+ );
822
975
  await writeFile(join(componentsOutDir, `${compName}.astro`), astroContent, 'utf-8');
823
976
  componentFileCount++;
824
977
  } catch (error: any) {
@@ -843,16 +996,15 @@ const { title, meta = '', scripts = [], locale = 'en', theme = '${themeConfig.de
843
996
  const ssrFallbacks = result.ssrFallbackCollector ?? new Map<string, string>();
844
997
 
845
998
  // Compute slug map for locale list rendering
846
- const pageSlugMap: Record<string, string> | undefined =
847
- result.pageData.meta?.slugs
848
- ? computePageSlugMap(result.pageData.meta.slugs, i18nConfig)
849
- : undefined;
999
+ const pageSlugMap: Record<string, string> | undefined = result.pageData.meta?.slugs
1000
+ ? computePageSlugMap(result.pageData.meta.slugs, i18nConfig)
1001
+ : undefined;
850
1002
 
851
1003
  // Component-structured pages don't need page-level _scripts/*.js
852
1004
  // because each .astro component already has its own inline <script>
853
1005
  astroContent = emitAstroPage({
854
1006
  pageData: result.pageData,
855
- globalComponents,
1007
+ globalComponents: emittableComponents,
856
1008
  title: result.title,
857
1009
  meta: result.meta,
858
1010
  locale: result.locale,
@@ -876,7 +1028,9 @@ const { title, meta = '', scripts = [], locale = 'en', theme = '${themeConfig.de
876
1028
  });
877
1029
  } catch (error: any) {
878
1030
  // Fallback to SSR HTML if component emission fails — needs page-level script
879
- console.warn(` Warning: component emission failed for ${result.urlPath}, using SSR fallback: ${error?.message}`);
1031
+ console.warn(
1032
+ ` Warning: component emission failed for ${result.urlPath}, using SSR fallback: ${error?.message}`,
1033
+ );
880
1034
  scriptPaths = writePageScript(result.javascript, scriptsDir);
881
1035
  astroContent = buildSSRFallbackPage(result, importPath, fontPreloads, libraryTags, defaultTheme, scriptPaths);
882
1036
  }
@@ -931,10 +1085,22 @@ export const GET: APIRoute = () => {
931
1085
  const collectionDir = join(contentDir, schema.id);
932
1086
  mkdirSync(collectionDir, { recursive: true });
933
1087
 
1088
+ // Rich-text fields are stored as Tiptap doc objects; Astro's content
1089
+ // schema (and `<Fragment set:html>`) expects HTML strings. Mirror the
1090
+ // live SSR (cmsSSRProcessor) by serializing them to HTML at copy time.
1091
+ const richTextFieldNames = Object.entries(schema.fields || {})
1092
+ .filter(([, fd]) => fd.type === 'rich-text')
1093
+ .map(([fn]) => fn);
1094
+
934
1095
  // Copy CMS item JSON files, resolving i18n values to default locale
935
1096
  const cmsItemsDir = join(projectPaths.cms(), schema.id);
936
1097
  if (existsSync(cmsItemsDir)) {
937
- const itemFiles = readdirSync(cmsItemsDir).filter(f => f.endsWith('.json'));
1098
+ // Skip `*.draft.json` siblings in production builds — they mirror the
1099
+ // published item-list filter and may hold partial/invalid WIP data.
1100
+ const isDevBuild = process.env.MENO_DEV_BUILD === 'true';
1101
+ const itemFiles = readdirSync(cmsItemsDir).filter(
1102
+ (f) => f.endsWith('.json') && (isDevBuild || !f.endsWith(`${CMS_DRAFT_SUFFIX}.json`)),
1103
+ );
938
1104
 
939
1105
  for (const itemFile of itemFiles) {
940
1106
  try {
@@ -944,11 +1110,13 @@ export const GET: APIRoute = () => {
944
1110
  // Keep i18n values as-is so getStaticPaths() can resolve per-locale
945
1111
  const resolved: Record<string, unknown> = { ...item };
946
1112
 
947
- await writeFile(
948
- join(collectionDir, itemFile),
949
- JSON.stringify(resolved, null, 2),
950
- 'utf-8'
951
- );
1113
+ // Serialize rich-text fields (Tiptap doc → HTML string), handling
1114
+ // i18n-wrapped values ({ _i18n: true, en: <doc>, ... }) per-locale.
1115
+ for (const fieldName of richTextFieldNames) {
1116
+ resolved[fieldName] = serializeRichTextValue(resolved[fieldName]);
1117
+ }
1118
+
1119
+ await writeFile(join(collectionDir, itemFile), JSON.stringify(resolved, null, 2), 'utf-8');
952
1120
  } catch (err: any) {
953
1121
  console.warn(` Warning: could not process CMS item ${itemFile}: ${err?.message}`);
954
1122
  }
@@ -1015,7 +1183,7 @@ export { collections };
1015
1183
  const srcAssetDir = join(projectPaths.project, dir);
1016
1184
  if (existsSync(srcAssetDir)) {
1017
1185
  copyDirectory(srcAssetDir, join(publicDir, dir));
1018
- }
1186
+ }
1019
1187
  }
1020
1188
 
1021
1189
  // Copy libraries folder if it exists
@@ -1052,24 +1220,23 @@ export { collections };
1052
1220
  preview: 'astro preview',
1053
1221
  },
1054
1222
  dependencies: {
1055
- 'astro': '^6.0.0',
1223
+ // Astro 5 (stable Vite), NOT 6 — Astro 6's rolldown-vite breaks
1224
+ // @tailwindcss/vite at build time ("Missing field `tsconfigPaths`").
1225
+ astro: '^5.0.0',
1056
1226
  '@astrojs/sitemap': '^3.0.0',
1057
1227
  '@tailwindcss/vite': '^4.0.0',
1058
- 'tailwindcss': '^4.0.0',
1059
- },
1060
- // Astro 6 expects Vite 7; pin it so npm doesn't pull Vite 8+ and warn.
1061
- overrides: {
1062
- 'vite': '^7.0.0',
1228
+ tailwindcss: '^4.0.0',
1063
1229
  },
1064
1230
  };
1065
1231
 
1066
1232
  await writeFile(join(outDir, 'package.json'), JSON.stringify(packageJson, null, 2), 'utf-8');
1067
1233
 
1068
1234
  // astro.config.mjs
1069
- const localeCodes = i18nConfig.locales.map(l => l.code);
1070
- const i18nBlock = i18nConfig.locales.length > 1
1071
- ? `\n i18n: {\n defaultLocale: '${i18nConfig.defaultLocale}',\n locales: [${localeCodes.map(c => `'${c}'`).join(', ')}],\n routing: { prefixDefaultLocale: false },\n },`
1072
- : '';
1235
+ const localeCodes = i18nConfig.locales.map((l) => l.code);
1236
+ const i18nBlock =
1237
+ i18nConfig.locales.length > 1
1238
+ ? `\n i18n: {\n defaultLocale: '${i18nConfig.defaultLocale}',\n locales: [${localeCodes.map((c) => `'${c}'`).join(', ')}],\n routing: { prefixDefaultLocale: false },\n },`
1239
+ : '';
1073
1240
 
1074
1241
  const astroConfig = `import { defineConfig } from 'astro/config';
1075
1242
  import tailwindcss from '@tailwindcss/vite';
@@ -1092,6 +1259,27 @@ export default defineConfig({${siteUrl ? `\n site: '${siteUrl}',` : ''}${i18nBl
1092
1259
 
1093
1260
  await writeFile(join(outDir, 'tsconfig.json'), JSON.stringify(tsConfig, null, 2), 'utf-8');
1094
1261
 
1262
+ // netlify.toml — makes the exported project deployable on Netlify out of the
1263
+ // box. `astro` is a real dependency here (see package.json above), so once
1264
+ // Netlify runs `npm install`, `astro build` is on PATH and writes to dist/.
1265
+ // Harmless on other hosts (Cloudflare Pages / Vercel ignore this file).
1266
+ //
1267
+ // Multi-locale projects WITH a 404 page (pages/404.json) also get the managed
1268
+ // locale-404 redirects block (lib/shared/netlifyLocale404.ts): the regular-pages
1269
+ // loop above emits src/pages/<locale>/404.astro per non-default locale, which
1270
+ // Astro builds to dist/<locale>/404/index.html — exactly the rules' rewrite
1271
+ // target — while the root 404.astro becomes Netlify's default catch-all 404.html.
1272
+ const baseNetlifyToml = `# Generated by Meno's Astro build.
1273
+ [build]
1274
+ command = "npm run build"
1275
+ publish = "dist"
1276
+
1277
+ [build.environment]
1278
+ NODE_VERSION = "22"
1279
+ `;
1280
+ const netlifyToml = syncNetlifyLocale404Block(baseNetlifyToml, i18nConfig, pageFiles.includes('404.json'));
1281
+ await writeFile(join(outDir, 'netlify.toml'), netlifyToml, 'utf-8');
1282
+
1095
1283
  // src/env.d.ts — resolves astro:assets and other virtual module types in IDE
1096
1284
  await writeFile(join(outDir, 'src', 'env.d.ts'), '/// <reference path="../.astro/types.d.ts" />\n', 'utf-8');
1097
1285