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
package/build-static.ts DELETED
@@ -1,1074 +0,0 @@
1
- /**
2
- * Static Site Generation Build Script
3
- * Pre-generates HTML files for all pages at build time
4
- * CSP-compliant: Extracts JavaScript to external files
5
- */
6
-
7
- import { existsSync, readdirSync, mkdirSync, rmSync, statSync, copyFileSync, unlinkSync, writeFileSync } from "fs";
8
- import { writeFile, readFile } from "fs/promises";
9
- import { join } from "path";
10
- import type { BuildError, BuildErrorsData } from "./lib/server/ssr/buildErrorOverlay";
11
- import { createHash } from "crypto";
12
- import { inspect, minifyJS as runtimeMinifyJS } from './lib/server/runtime';
13
- import {
14
- loadJSONFile,
15
- loadComponentDirectory,
16
- mapPageNameToPath,
17
- parseJSON,
18
- loadI18nConfig
19
- } from "./lib/server/jsonLoader";
20
- import { generateSSRHTML } from "./lib/server/ssrRenderer";
21
- import type { SSRHTMLResult } from "./lib/server/ssr/htmlGenerator";
22
- import { prepareClientData, type ClientDataCollection } from "./lib/server/ssr/clientDataInjector";
23
- import { clearJSValidationCache, getJSValidationErrors } from "./lib/server/ssr/jsCollector";
24
- import { projectPaths } from "./lib/server/projectContext";
25
- import { loadProjectConfig } from "./lib/shared/fontLoader";
26
- import { FileSystemCMSProvider } from "./lib/server/providers/fileSystemCMSProvider";
27
- import { CMSService } from "./lib/server/services/cmsService";
28
- import { isI18nValue, resolveI18nValue } from "./lib/shared/i18n";
29
- import type { ComponentDefinition, JSONPage, CMSSchema, CMSItem, I18nConfig, Experiment } from "./lib/shared/types";
30
- import { isItemDraftForLocale, isItemFullyPublished } from "./lib/shared/types";
31
- import type { SlugMap } from "./lib/shared/slugTranslator";
32
- import { buildItemUrl } from "./lib/shared/itemTemplateUtils";
33
- import { generateMiddleware, generateTrackFunction, generateResultsFunction } from "./lib/server/ab/generateFunctions";
34
- import { generateTrackingScript } from "./lib/server/ab/trackingScript";
35
- import { migrateTemplatesDirectory } from "./lib/server/migrateTemplates";
36
- import { extractLibraryOrigins, collectComponentLibraries, filterLibrariesByContext } from "./lib/shared/libraryLoader";
37
- import type { LibrariesConfig } from "./lib/shared/types/libraries";
38
-
39
- /**
40
- * Collect build errors for error overlay
41
- */
42
- const buildErrors: BuildError[] = [];
43
-
44
- /**
45
- * Generate short hash from content for file naming
46
- */
47
- export function hashContent(content: string): string {
48
- return createHash('sha256').update(content).digest('hex').slice(0, 8);
49
- }
50
-
51
- /**
52
- * Format a Bun build log entry to a readable string
53
- */
54
- export function formatBunLog(log: any): string {
55
- const parts: string[] = [];
56
-
57
- // Try to get position info
58
- if (log.position) {
59
- const pos = log.position;
60
- if (pos.file) parts.push(`File: ${pos.file}`);
61
- if (pos.line !== undefined) parts.push(`Line ${pos.line}:${pos.column || 0}`);
62
- if (pos.lineText) parts.push(` ${pos.lineText}`);
63
- }
64
-
65
- // Get the message
66
- if (log.message) {
67
- parts.push(log.message);
68
- } else if (log.text) {
69
- parts.push(log.text);
70
- }
71
-
72
- // If we couldn't extract anything useful, stringify the whole thing
73
- if (parts.length === 0) {
74
- try {
75
- return JSON.stringify(log, null, 2);
76
- } catch {
77
- return String(log);
78
- }
79
- }
80
-
81
- return parts.join('\n');
82
- }
83
-
84
- /**
85
- * Minify JavaScript code using runtime bundler
86
- * Throws on error instead of silently failing
87
- */
88
- async function minifyJS(code: string): Promise<string> {
89
- const result = await runtimeMinifyJS(code);
90
- if (result.success) {
91
- return result.code;
92
- }
93
- throw new Error(`JavaScript minification failed:\n${result.errors.join('\n\n')}`);
94
- }
95
-
96
- /**
97
- * Track JavaScript files to avoid duplicates
98
- * Maps content hash -> script path
99
- */
100
- const jsFileCache = new Map<string, string>();
101
-
102
- /**
103
- * Get or create script file path for given JS content
104
- * Returns the path to reference in HTML
105
- */
106
- async function getScriptPath(jsContent: string, distDir: string): Promise<string> {
107
- // Minify JavaScript for production
108
- const minified = await minifyJS(jsContent);
109
- const hash = hashContent(minified);
110
-
111
- // Check if we already wrote this content
112
- if (jsFileCache.has(hash)) {
113
- return jsFileCache.get(hash)!;
114
- }
115
-
116
- // Create scripts directory if needed
117
- const scriptsDir = join(distDir, '_scripts');
118
- if (!existsSync(scriptsDir)) {
119
- mkdirSync(scriptsDir, { recursive: true });
120
- }
121
-
122
- // Write minified script file
123
- const scriptPath = `/_scripts/${hash}.js`;
124
- const fullPath = join(distDir, '_scripts', `${hash}.js`);
125
- await writeFile(fullPath, minified, 'utf-8');
126
-
127
- // Cache for reuse
128
- jsFileCache.set(hash, scriptPath);
129
-
130
- return scriptPath;
131
- }
132
-
133
- /**
134
- * Recursively copy directory contents
135
- */
136
- function copyDirectory(src: string, dest: string): void {
137
- if (!existsSync(src)) {
138
- return;
139
- }
140
-
141
- if (!existsSync(dest)) {
142
- mkdirSync(dest, { recursive: true });
143
- }
144
-
145
- const files = readdirSync(src);
146
-
147
- for (const file of files) {
148
- const srcPath = join(src, file);
149
- const destPath = join(dest, file);
150
- const stat = statSync(srcPath);
151
-
152
- if (stat.isDirectory()) {
153
- copyDirectory(srcPath, destPath);
154
- } else {
155
- copyFileSync(srcPath, destPath);
156
- }
157
- }
158
- }
159
-
160
-
161
- /**
162
- * Get locale-specific output path for a page with translated slug support
163
- * Default locale files go to root, other locales to subdirectories
164
- * Uses translated slugs from meta.slugs if available
165
- */
166
- export function getLocalizedOutputPath(
167
- basePath: string,
168
- locale: string,
169
- defaultLocale: string,
170
- distDir: string,
171
- slugs?: Record<string, string>
172
- ): string {
173
- // Get translated slug for this locale, or fall back to default path
174
- let slug: string;
175
- if (slugs && slugs[locale]) {
176
- slug = slugs[locale];
177
- } else if (basePath === "/") {
178
- slug = "";
179
- } else {
180
- slug = basePath.substring(1);
181
- }
182
-
183
- // Build filename from slug
184
- const fileName = slug === "" ? "index.html" : `${slug}.html`;
185
-
186
- if (locale === defaultLocale) {
187
- return `${distDir}/${fileName}`;
188
- }
189
- return `${distDir}/${locale}/${fileName}`;
190
- }
191
-
192
- /**
193
- * Get display path for logging (the actual URL the user will visit)
194
- */
195
- export function getDisplayPath(
196
- basePath: string,
197
- locale: string,
198
- defaultLocale: string,
199
- slugs?: Record<string, string>
200
- ): string {
201
- // Get translated slug for this locale
202
- let slug: string;
203
- if (slugs && slugs[locale]) {
204
- slug = slugs[locale];
205
- } else if (basePath === "/") {
206
- slug = "";
207
- } else {
208
- slug = basePath.substring(1);
209
- }
210
-
211
- if (locale === defaultLocale) {
212
- return slug === "" ? "/" : `/${slug}`;
213
- }
214
- return slug === "" ? `/${locale}` : `/${locale}/${slug}`;
215
- }
216
-
217
- /**
218
- * Generate robots.txt with sensible defaults
219
- */
220
- export async function generateRobotsTxt(siteUrl: string, distDir: string): Promise<void> {
221
- const content = `User-agent: *
222
- Allow: /
223
-
224
- Sitemap: ${siteUrl}/sitemap.xml
225
- `;
226
- await writeFile(join(distDir, 'robots.txt'), content, 'utf-8');
227
- }
228
-
229
- /**
230
- * Generate sitemap.xml from collected URLs
231
- */
232
- export async function generateSitemap(urls: string[], siteUrl: string, distDir: string): Promise<void> {
233
- // Sort URLs for deterministic output
234
- const sortedUrls = [...urls].sort();
235
-
236
- const urlEntries = sortedUrls
237
- .map(url => ` <url><loc>${siteUrl}${url}</loc></url>`)
238
- .join('\n');
239
-
240
- const content = `<?xml version="1.0" encoding="UTF-8"?>
241
- <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
242
- ${urlEntries}
243
- </urlset>
244
- `;
245
- await writeFile(join(distDir, 'sitemap.xml'), content, 'utf-8');
246
- }
247
-
248
- /**
249
- * Clean dist directory, keeping only production files
250
- */
251
- function cleanDist(distDir: string = projectPaths.dist()): void {
252
- if (!existsSync(distDir)) {
253
- mkdirSync(distDir, { recursive: true });
254
- return;
255
- }
256
-
257
- const files = readdirSync(distDir);
258
- let cleaned = 0;
259
-
260
- for (const file of files) {
261
- // Keep fonts, images, icons, assets, and videos
262
- if (
263
- file === "fonts" ||
264
- file === "images" ||
265
- file === "icons" ||
266
- file === "assets" ||
267
- file === "videos"
268
- ) {
269
- continue;
270
- }
271
-
272
- // Remove everything else (editor files, old HTML, etc.)
273
- try {
274
- const filePath = join(distDir, file);
275
- const stat = statSync(filePath);
276
-
277
- if (stat.isDirectory()) {
278
- rmSync(filePath, { recursive: true, force: true });
279
- } else {
280
- rmSync(filePath, { force: true });
281
- }
282
- cleaned++;
283
- } catch (error) {
284
- console.warn(`⚠️ Could not remove ${file}:`, error);
285
- }
286
- }
287
-
288
- if (cleaned > 0) {
289
- console.log(`🧹 Cleaned ${cleaned} file(s) from dist\n`);
290
- }
291
- }
292
-
293
- /**
294
- * Check if a page is a CMS template
295
- */
296
- export function isCMSPage(pageData: JSONPage): boolean {
297
- return pageData.meta?.source === 'cms' && !!pageData.meta?.cms;
298
- }
299
-
300
- /**
301
- * Build URL path for a CMS item based on the URL pattern
302
- * Uses schema.slugField to get the slug value, supporting i18n slugs
303
- */
304
- export function buildCMSItemPath(
305
- urlPattern: string,
306
- item: CMSItem,
307
- slugField: string,
308
- locale: string,
309
- i18nConfig: I18nConfig
310
- ): string {
311
- // Get slug from the schema-defined slugField, fall back to _slug or _id
312
- let slug = item[slugField] ?? item._slug ?? item._id;
313
-
314
- // Resolve i18n slug to locale-specific value
315
- if (isI18nValue(slug)) {
316
- slug = resolveI18nValue(slug, locale, i18nConfig) as string;
317
- }
318
-
319
- return urlPattern.replace('{{slug}}', String(slug));
320
- }
321
-
322
- /**
323
- * Generate static JSON data files for collections with 'static' strategy
324
- * Output: /data/{collection}/index.json
325
- */
326
- async function generateStaticDataFiles(
327
- staticCollections: Map<string, ClientDataCollection>,
328
- distDir: string
329
- ): Promise<void> {
330
- if (staticCollections.size === 0) return;
331
-
332
- console.log(`\n📦 Generating static data files...`);
333
-
334
- for (const [collectionId, data] of staticCollections) {
335
- const dataDir = join(distDir, 'data', collectionId);
336
- if (!existsSync(dataDir)) {
337
- mkdirSync(dataDir, { recursive: true });
338
- }
339
- const jsonPath = join(dataDir, 'index.json');
340
- await writeFile(jsonPath, JSON.stringify(data.items), 'utf-8');
341
- console.log(` ✅ /data/${collectionId}/index.json (${data.items.length} items)`);
342
- }
343
- }
344
-
345
- /**
346
- * Build CMS templates from root templates/ directory
347
- */
348
- async function buildCMSTemplates(
349
- templatesDir: string,
350
- globalComponents: Record<string, ComponentDefinition>,
351
- i18nConfig: I18nConfig,
352
- slugMappings: SlugMap[],
353
- distDir: string,
354
- cmsService: CMSService,
355
- generatedUrls: Set<string>,
356
- staticCollections: Map<string, ClientDataCollection>,
357
- siteUrl?: string,
358
- abTrackingScript?: string | null
359
- ): Promise<{ success: number; errors: number }> {
360
- let successCount = 0;
361
- let errorCount = 0;
362
-
363
- if (!existsSync(templatesDir)) {
364
- return { success: 0, errors: 0 };
365
- }
366
-
367
- const templateFiles = readdirSync(templatesDir).filter(f => f.endsWith('.json'));
368
-
369
- if (templateFiles.length === 0) {
370
- return { success: 0, errors: 0 };
371
- }
372
-
373
- console.log(`\n📝 Processing ${templateFiles.length} CMS template(s)...\n`);
374
-
375
- for (const file of templateFiles) {
376
- const templateContent = await loadJSONFile(join(templatesDir, file));
377
- if (!templateContent) continue;
378
-
379
- try {
380
- const pageData = parseJSON<JSONPage>(templateContent);
381
-
382
- // Skip draft templates in production (not in dev mode)
383
- const isDevBuild = process.env.MENO_DEV_BUILD === 'true';
384
- if (pageData.meta?.draft === true && !isDevBuild) {
385
- console.log(`⏭️ Skipping draft template: ${file}`);
386
- continue;
387
- }
388
-
389
- if (!isCMSPage(pageData)) {
390
- console.warn(`⚠️ ${file} is in templates/ but missing meta.source: "cms"`);
391
- continue;
392
- }
393
-
394
- const cmsSchema = pageData.meta!.cms as CMSSchema;
395
- console.log(`📝 CMS Collection: ${cmsSchema.id}`);
396
-
397
- // Use shared cmsService to get items
398
- const items = await cmsService.queryItems({ collection: cmsSchema.id });
399
-
400
- if (items.length === 0) {
401
- console.log(` ⚠️ No items found in cms/${cmsSchema.id}/`);
402
- continue;
403
- }
404
-
405
- console.log(` Found ${items.length} item(s)`);
406
-
407
- // Prepare client data if clientData is enabled
408
- // Filter out fully-draft items from client data (client data is locale-agnostic)
409
- let clientDataCollections: Map<string, ClientDataCollection> | undefined;
410
- if (cmsSchema.clientData?.enabled) {
411
- const publishedItems = items.filter(item => isItemFullyPublished(item));
412
- const clientData = prepareClientData(cmsSchema.id, publishedItems, cmsSchema.clientData);
413
- if (clientData) {
414
- if (clientData.strategy === 'inline') {
415
- // Inline data embedded in HTML
416
- clientDataCollections = new Map([[cmsSchema.id, clientData]]);
417
- console.log(` 📦 Client data (inline): ${clientData.items.length} items (${clientData.config.fields?.length || 'all'} fields)`);
418
- } else if (clientData.strategy === 'static') {
419
- // Static data written to separate file
420
- staticCollections.set(cmsSchema.id, clientData);
421
- console.log(` 📦 Client data (static): ${clientData.items.length} items → /data/${cmsSchema.id}/index.json`);
422
- }
423
- }
424
- }
425
-
426
- for (const item of items) {
427
- for (const localeConfig of i18nConfig.locales) {
428
- const locale = localeConfig.code;
429
-
430
- // Skip draft items per locale in production builds
431
- const isDevBuild = process.env.MENO_DEV_BUILD === 'true';
432
- if (!isDevBuild && isItemDraftForLocale(item, locale)) {
433
- console.log(` ⏭️ Skipping draft: ${item._filename || item._id} [${locale}]`);
434
- continue;
435
- }
436
-
437
- const baseUrl = siteUrl || "";
438
- const itemPath = buildCMSItemPath(cmsSchema.urlPattern, item, cmsSchema.slugField, locale, i18nConfig);
439
-
440
- // Create CMS item with computed _url for {{cms._url}} template access
441
- const itemWithUrl: CMSItem = { ...item, _url: itemPath };
442
-
443
- // Generate HTML with JS returned separately (CSP-compliant)
444
- const result = await generateSSRHTML({
445
- pageData,
446
- globalComponents,
447
- pagePath: itemPath,
448
- baseUrl,
449
- useBuiltBundle: true,
450
- locale,
451
- slugMappings,
452
- cmsContext: { cms: itemWithUrl },
453
- cmsService,
454
- returnSeparateJS: true,
455
- pageLibraries: pageData.meta?.libraries,
456
- pageCustomCode: pageData.meta?.customCode,
457
- clientDataCollections,
458
- isProductionBuild: true,
459
- }) as SSRHTMLResult;
460
-
461
- // If there's JavaScript, write to external file and update HTML
462
- let finalHtml = result.html;
463
- if (result.javascript) {
464
- const scriptPath = await getScriptPath(result.javascript, distDir);
465
- finalHtml = finalHtml.replace('</body>', ` <script src="${scriptPath}" defer></script>\n</body>`);
466
- }
467
-
468
- // Inject A/B tracking script if experiments are running
469
- if (abTrackingScript) {
470
- finalHtml = injectTrackingScript(finalHtml, abTrackingScript);
471
- }
472
-
473
- const outputPath = locale === i18nConfig.defaultLocale
474
- ? `${distDir}${itemPath}.html`
475
- : `${distDir}/${locale}${itemPath}.html`;
476
-
477
- const outputDir = outputPath.substring(0, outputPath.lastIndexOf('/'));
478
- if (outputDir && !existsSync(outputDir)) {
479
- mkdirSync(outputDir, { recursive: true });
480
- }
481
-
482
- await writeFile(outputPath, finalHtml, 'utf-8');
483
-
484
- const displayPath = locale === i18nConfig.defaultLocale ? itemPath : `/${locale}${itemPath}`;
485
- generatedUrls.add(displayPath);
486
- console.log(` ✅ ${displayPath}`);
487
- successCount++;
488
- }
489
- }
490
- } catch (error: any) {
491
- // Capture full error with as much detail as possible
492
- let errorMessage: string;
493
-
494
- if (error instanceof Error) {
495
- // Check for AggregateError (multiple errors)
496
- if ('errors' in error && Array.isArray(error.errors)) {
497
- errorMessage = error.errors.map((e: any) => e.stack || e.message || String(e)).join('\n\n');
498
- }
499
- // Check for cause chain
500
- else if (error.cause) {
501
- const causeMsg = error.cause instanceof Error
502
- ? (error.cause.stack || error.cause.message)
503
- : String(error.cause);
504
- errorMessage = `${error.stack || error.message}\n\nCaused by:\n${causeMsg}`;
505
- }
506
- // Bun's BuildMessage has logs array
507
- else if ('logs' in error && Array.isArray(error.logs)) {
508
- errorMessage = error.logs.map(formatBunLog).join('\n\n');
509
- }
510
- else {
511
- errorMessage = error.stack || error.message;
512
- }
513
- } else if (typeof error === 'object' && error !== null) {
514
- // Bun BuildOutput has logs
515
- if (error.logs && Array.isArray(error.logs)) {
516
- errorMessage = error.logs.map(formatBunLog).join('\n\n');
517
- } else {
518
- errorMessage = String(error);
519
- }
520
- } else {
521
- errorMessage = String(error);
522
- }
523
-
524
- // If we still just have "Bundle failed", try to get more from inspect
525
- if (errorMessage === 'Bundle failed' || errorMessage.includes('Bundle failed\n')) {
526
- try {
527
- const inspected = inspect(error);
528
- if (inspected && inspected !== '[object Object]' && inspected.length > errorMessage.length) {
529
- errorMessage = inspected;
530
- }
531
- } catch {}
532
- }
533
-
534
- console.error(`❌ Error processing ${file}:`, error);
535
- buildErrors.push({
536
- file: `templates/${file}`,
537
- message: errorMessage,
538
- type: errorMessage.includes('minification') || errorMessage.includes('minify') ? 'minify' : 'cms',
539
- });
540
- errorCount++;
541
- }
542
- }
543
-
544
- return { success: successCount, errors: errorCount };
545
- }
546
-
547
- /**
548
- * Load running experiments from experiments.json
549
- */
550
- async function loadRunningExperiments(): Promise<Experiment[]> {
551
- const experimentsPath = join(projectPaths.project, 'experiments.json');
552
- if (!existsSync(experimentsPath)) return [];
553
-
554
- try {
555
- const content = await readFile(experimentsPath, 'utf-8');
556
- const data = JSON.parse(content);
557
- const experiments: Experiment[] = data.experiments || [];
558
- return experiments.filter(e => e.status === 'running');
559
- } catch {
560
- return [];
561
- }
562
- }
563
-
564
- /**
565
- * Inject A/B tracking script into HTML before </head>
566
- */
567
- export function injectTrackingScript(html: string, trackingJs: string): string {
568
- const scriptTag = `<script>${trackingJs}</script>`;
569
- return html.replace('</head>', `${scriptTag}\n</head>`);
570
- }
571
-
572
- /**
573
- * Generate A/B testing Cloudflare Functions into dist/functions/
574
- */
575
- async function generateABFunctions(experiments: Experiment[], distDir: string): Promise<void> {
576
- // Generate middleware
577
- const middlewareContent = generateMiddleware(experiments);
578
- if (middlewareContent) {
579
- const middlewarePath = join(distDir, 'functions', '_middleware.ts');
580
- const middlewareDir = join(distDir, 'functions');
581
- if (!existsSync(middlewareDir)) {
582
- mkdirSync(middlewareDir, { recursive: true });
583
- }
584
- await writeFile(middlewarePath, middlewareContent, 'utf-8');
585
- }
586
-
587
- // Generate tracking endpoint
588
- const trackContent = generateTrackFunction();
589
- const trackDir = join(distDir, 'functions', 'api');
590
- if (!existsSync(trackDir)) {
591
- mkdirSync(trackDir, { recursive: true });
592
- }
593
- await writeFile(join(trackDir, 'ab-track.ts'), trackContent, 'utf-8');
594
-
595
- // Generate results endpoint
596
- const resultsContent = generateResultsFunction();
597
- await writeFile(join(trackDir, 'ab-results.ts'), resultsContent, 'utf-8');
598
- }
599
-
600
- /**
601
- * Main build function
602
- */
603
- export async function buildStaticPages(outDir?: string): Promise<void> {
604
- console.log("🏗️ Building static HTML files...\n");
605
-
606
- // Clear previous build errors and JS validation cache
607
- buildErrors.length = 0;
608
- clearJSValidationCache();
609
-
610
- // Reset configService to ensure it loads from the correct project directory
611
- const { configService } = await import("./lib/server/services/configService");
612
- configService.reset();
613
-
614
- // Load project config first
615
- const projectConfig = await loadProjectConfig();
616
- const siteUrl = (projectConfig as { siteUrl?: string }).siteUrl?.replace(/\/$/, ''); // Remove trailing slash
617
-
618
- // Track all generated URLs for sitemap
619
- const generatedUrls = new Set<string>();
620
-
621
- // Load running A/B experiments (if any)
622
- const runningExperiments = await loadRunningExperiments();
623
- let trackingScript: string | null = null;
624
- if (runningExperiments.length > 0) {
625
- const exp = runningExperiments[0]; // Use first running experiment
626
- trackingScript = generateTrackingScript(exp.id, exp.conversionGoals);
627
- console.log(`🧪 A/B experiment active: "${exp.name}" (${exp.variants.length} variant(s))\n`);
628
- }
629
-
630
- // Load i18n config for multi-locale build
631
- const i18nConfig = await loadI18nConfig();
632
- console.log(`🌐 Locales: ${i18nConfig.locales.map(l => l.code).join(", ")} (default: ${i18nConfig.defaultLocale})\n`);
633
-
634
- // Auto-migrate pages/templates/ → templates/ if needed
635
- await migrateTemplatesDirectory();
636
-
637
- // Resolve output directory. Defaults to <project>/dist; callers (e.g. the
638
- // Cloudflare deploy) may pass a temp dir to avoid leaving a dist/ in the project.
639
- const distDir = outDir || projectPaths.dist();
640
-
641
- // Clean dist directory (removes editor files, old HTML)
642
- cleanDist(distDir);
643
-
644
- // Clear the JS file cache since cleanDist() removed _scripts/
645
- // Without this, cached entries would skip file creation on subsequent builds
646
- jsFileCache.clear();
647
-
648
- // Copy fonts, images, icons, and functions directories to dist
649
- console.log("📦 Copying assets...");
650
-
651
- // Delete old _errors.json if it exists (start fresh)
652
- const errorsPath = join(distDir, '_errors.json');
653
- if (existsSync(errorsPath)) {
654
- unlinkSync(errorsPath);
655
- }
656
- copyDirectory(projectPaths.fonts(), join(distDir, "fonts"));
657
- copyDirectory(projectPaths.images(), join(distDir, "images"));
658
- copyDirectory(projectPaths.icons(), join(distDir, "icons"));
659
- copyDirectory(projectPaths.assets(), join(distDir, "assets"));
660
- copyDirectory(projectPaths.videos(), join(distDir, "videos"));
661
-
662
- // Copy libraries folder (downloaded external JS/CSS files)
663
- const librariesDir = join(projectPaths.project, "libraries");
664
- if (existsSync(librariesDir)) {
665
- copyDirectory(librariesDir, join(distDir, "libraries"));
666
- }
667
-
668
- // Copy functions folder for Cloudflare Pages
669
- const functionsDir = projectPaths.functions();
670
- if (existsSync(functionsDir)) {
671
- copyDirectory(functionsDir, join(distDir, "functions"));
672
- }
673
-
674
- // Copy user-created root files for static hosting
675
- const hostingFiles: string[] = [];
676
- const rootFilesToCopy = [
677
- '_headers', // Netlify/Cloudflare headers
678
- '_redirects', // Netlify/Cloudflare redirects
679
- 'llms.txt', // LLM context
680
- 'humans.txt', // Team credits
681
- 'ads.txt', // Ad verification
682
- 'security.txt', // Security contact
683
- 'CNAME', // GitHub Pages domain
684
- 'manifest.json', // PWA manifest
685
- 'site.webmanifest', // PWA manifest (alt)
686
- ];
687
-
688
- for (const file of rootFilesToCopy) {
689
- const filePath = join(projectPaths.project, file);
690
- if (existsSync(filePath)) {
691
- copyFileSync(filePath, join(distDir, file));
692
- hostingFiles.push(file);
693
- }
694
- }
695
-
696
- // Copy .well-known directory if exists
697
- const wellKnownDir = join(projectPaths.project, '.well-known');
698
- if (existsSync(wellKnownDir)) {
699
- copyDirectory(wellKnownDir, join(distDir, '.well-known'));
700
- hostingFiles.push('.well-known/');
701
- }
702
-
703
- const parts = ['Assets'];
704
- if (existsSync(functionsDir)) parts.push('functions');
705
- if (hostingFiles.length > 0) parts.push(hostingFiles.join(', '));
706
- console.log(`✅ ${parts.join(', ')} copied\n`);
707
-
708
- // Load all global components
709
- const { components, warnings, errors } = await loadComponentDirectory(projectPaths.components());
710
- const globalComponents: Record<string, ComponentDefinition> = {};
711
- components.forEach((value, key) => {
712
- globalComponents[key] = value;
713
- });
714
-
715
- for (const warning of warnings) console.warn(`⚠️ ${warning}`);
716
- for (const error of errors) console.error(`❌ ${error}`);
717
- console.log(`✅ Loaded ${components.size} global component(s)\n`);
718
-
719
- // Initialize CMS service for CMSList rendering
720
- const cmsProvider = new FileSystemCMSProvider(projectPaths.templates(), projectPaths.cms());
721
- const cmsService = new CMSService(cmsProvider);
722
- await cmsService.initialize();
723
- console.log(`✅ CMS service initialized\n`);
724
-
725
- // Load all pages
726
- const pagesDir = projectPaths.pages();
727
- if (!existsSync(pagesDir)) {
728
- console.error("❌ Pages directory not found!");
729
- process.exit(1);
730
- }
731
-
732
- // Recursively collect all .json page files (supports nested folders like pages/a/b.json)
733
- const pageFiles: string[] = [];
734
- function scanPagesDir(dir: string, prefix: string): void {
735
- const entries = readdirSync(dir, { withFileTypes: true });
736
- for (const entry of entries) {
737
- if (entry.isFile() && entry.name.endsWith('.json')) {
738
- pageFiles.push(prefix ? `${prefix}/${entry.name}` : entry.name);
739
- } else if (entry.isDirectory()) {
740
- scanPagesDir(join(dir, entry.name), prefix ? `${prefix}/${entry.name}` : entry.name);
741
- }
742
- }
743
- }
744
- scanPagesDir(pagesDir, '');
745
-
746
- if (pageFiles.length === 0) {
747
- console.warn("⚠️ No pages found in ./pages directory");
748
- return;
749
- }
750
-
751
- console.log(`📄 Found ${pageFiles.length} page(s) to build\n`);
752
-
753
- // First pass: collect all slug mappings and page-level libraries
754
- const slugMappings: SlugMap[] = [];
755
- const allPageLibraries: LibrariesConfig[] = [];
756
- for (const file of pageFiles) {
757
- const pageName = file.replace(".json", "");
758
- const basePath = mapPageNameToPath(pageName);
759
- const pageContent = await loadJSONFile(join(pagesDir, file));
760
- if (pageContent) {
761
- try {
762
- const pageData = parseJSON<JSONPage>(pageContent);
763
- if (pageData.meta?.slugs) {
764
- const pageId = basePath === '/' ? 'index' : basePath.substring(1);
765
- slugMappings.push({ pageId, slugs: pageData.meta.slugs });
766
- }
767
- if (pageData.meta?.libraries) {
768
- allPageLibraries.push(pageData.meta.libraries as LibrariesConfig);
769
- }
770
- } catch { /* ignore parse errors in first pass */ }
771
- }
772
- }
773
-
774
- // Generate _headers from CSP config, auto-including library CDN domains
775
- // Always regenerate when CSP config exists — overwrites any stale _headers file
776
- // that may have been copied from the project root
777
- {
778
- await configService.load();
779
- const cspConfig = configService.getCSP();
780
- if (cspConfig && Object.keys(cspConfig).length > 0) {
781
- // Collect all library sources: global + component + page-level
782
- // Note: we concatenate (not mergeLibraries) because _headers applies globally —
783
- // we need the union of ALL origins, regardless of per-page merge modes
784
- const globalLibraries = configService.getLibraries();
785
- const componentLibraries = collectComponentLibraries(globalComponents);
786
- const allJs = [
787
- ...(globalLibraries.js || []),
788
- ...(componentLibraries.js || []),
789
- ...allPageLibraries.flatMap(p => p.js || []),
790
- ];
791
- const allCss = [
792
- ...(globalLibraries.css || []),
793
- ...(componentLibraries.css || []),
794
- ...allPageLibraries.flatMap(p => p.css || []),
795
- ];
796
- const allLibs: LibrariesConfig = { js: allJs, css: allCss };
797
-
798
- // Filter out CSS libs disabled for build before extracting CSP origins
799
- const buildLibs = filterLibrariesByContext(allLibs, 'build');
800
-
801
- // Extract CDN origins from all library URLs
802
- const { scriptOrigins, styleOrigins } = extractLibraryOrigins(buildLibs);
803
-
804
- const extraScripts = [
805
- ...(cspConfig.scriptSrc || []),
806
- ...Array.from(scriptOrigins),
807
- ].join(' ');
808
- const extraStyles = [
809
- ...(cspConfig.styleSrc || []),
810
- ...Array.from(styleOrigins),
811
- ].join(' ');
812
- const extraConnect = cspConfig.connectSrc?.join(' ') || '';
813
- const extraFrames = cspConfig.frameSrc?.join(' ') || '';
814
- const extraFonts = cspConfig.fontSrc?.join(' ') || '';
815
- const extraImgs = cspConfig.imgSrc?.join(' ') || '';
816
-
817
- // Production-built pages have NO executable inline scripts: page config,
818
- // component JS, form handler, and MenoFilter all live in external
819
- // /_scripts/{hash}.js files (returnSeparateJS: true), and the Meno
820
- // badge's hover effect is pure CSS. Only `<script type="application/json">`
821
- // remains inline (used as a data island for MenoFilter), which CSP
822
- // ignores because it has no executable code. So we can drop
823
- // `'unsafe-inline'` entirely from script-src in the generated _headers.
824
- const cspDirectives = [
825
- "default-src 'self'",
826
- `script-src 'self' https://f.vimeocdn.com https://player.vimeo.com https://www.youtube.com https://s.ytimg.com ${extraScripts}`.trim(),
827
- `style-src 'self' 'unsafe-inline' https://f.vimeocdn.com ${extraStyles}`.trim(),
828
- `img-src 'self' data: https: ${extraImgs}`.trim(),
829
- `connect-src 'self' https://vimeo.com https://*.vimeocdn.com ${extraConnect}`.trim(),
830
- `frame-src https://player.vimeo.com https://vimeo.com https://www.youtube.com https://www.youtube-nocookie.com ${extraFrames}`.trim(),
831
- `font-src 'self' data: ${extraFonts}`.trim(),
832
- "media-src 'self' https: blob:"
833
- ].join('; ');
834
-
835
- const headersContent = `/*\n Content-Security-Policy: ${cspDirectives}\n`;
836
- writeFileSync(join(distDir, '_headers'), headersContent);
837
- console.log(`✅ Generated _headers with CSP (auto-included ${scriptOrigins.size + styleOrigins.size} library origin(s))\n`);
838
- }
839
- }
840
-
841
- let successCount = 0;
842
- let errorCount = 0;
843
-
844
- // Build each page for each locale
845
- for (const file of pageFiles) {
846
- const pageName = file.replace(".json", "");
847
- const basePath = mapPageNameToPath(pageName);
848
- const pageContent = await loadJSONFile(join(pagesDir, file));
849
-
850
- if (!pageContent) {
851
- console.warn(`⚠️ Skipping ${basePath} (empty file)`);
852
- errorCount++;
853
- continue;
854
- }
855
-
856
- try {
857
- const pageData = parseJSON<JSONPage>(pageContent);
858
-
859
- // Skip draft pages in production (not in dev mode)
860
- const isDevBuild = process.env.MENO_DEV_BUILD === 'true';
861
- if (pageData.meta?.draft === true && !isDevBuild) {
862
- console.log(`⏭️ Skipping draft: ${basePath}`);
863
- continue;
864
- }
865
-
866
- // Get translated slugs from page meta (if available)
867
- const slugs = pageData.meta?.slugs;
868
-
869
- // Generate HTML for each locale
870
- for (const localeConfig of i18nConfig.locales) {
871
- const locale = localeConfig.code;
872
- const baseUrl = siteUrl || "";
873
-
874
- // Build the URL path that will be used for this locale
875
- const urlPath = getDisplayPath(basePath, locale, i18nConfig.defaultLocale, slugs);
876
-
877
- // Generate HTML with JS returned separately (CSP-compliant)
878
- const result = await generateSSRHTML({
879
- pageData,
880
- globalComponents,
881
- pagePath: urlPath,
882
- baseUrl,
883
- useBuiltBundle: true,
884
- locale,
885
- slugMappings,
886
- cmsService,
887
- returnSeparateJS: true,
888
- pageLibraries: pageData.meta?.libraries,
889
- pageCustomCode: pageData.meta?.customCode,
890
- isProductionBuild: true,
891
- }) as SSRHTMLResult;
892
-
893
- // If there's JavaScript, write to external file and update HTML
894
- let finalHtml = result.html;
895
- if (result.javascript) {
896
- const scriptPath = await getScriptPath(result.javascript, distDir);
897
- // Insert script reference before </body>
898
- finalHtml = finalHtml.replace('</body>', ` <script src="${scriptPath}" defer></script>\n</body>`);
899
- }
900
-
901
- // Inject A/B tracking script if experiments are running
902
- if (trackingScript) {
903
- finalHtml = injectTrackingScript(finalHtml, trackingScript);
904
- }
905
-
906
- // Determine locale-specific output path with translated slug
907
- const outputPath = getLocalizedOutputPath(basePath, locale, i18nConfig.defaultLocale, distDir, slugs);
908
-
909
- // Ensure directory exists
910
- const outputDir = outputPath.substring(0, outputPath.lastIndexOf("/"));
911
- if (outputDir && !existsSync(outputDir)) {
912
- mkdirSync(outputDir, { recursive: true });
913
- }
914
-
915
- // Debug: show end of HTML
916
- if (urlPath === '/') {
917
- console.log('[DEBUG] Last 500 chars of HTML:', finalHtml.slice(-500));
918
- }
919
-
920
- await writeFile(outputPath, finalHtml, "utf-8");
921
-
922
- generatedUrls.add(urlPath);
923
- console.log(`✅ Built: ${urlPath} → ${outputPath}`);
924
- successCount++;
925
- }
926
-
927
- } catch (error: any) {
928
- // Capture full error with as much detail as possible
929
- let errorMessage: string;
930
-
931
- if (error instanceof Error) {
932
- // Check for AggregateError (multiple errors)
933
- if ('errors' in error && Array.isArray(error.errors)) {
934
- errorMessage = error.errors.map((e: any) => e.stack || e.message || String(e)).join('\n\n');
935
- }
936
- // Check for cause chain
937
- else if (error.cause) {
938
- const causeMsg = error.cause instanceof Error
939
- ? (error.cause.stack || error.cause.message)
940
- : String(error.cause);
941
- errorMessage = `${error.stack || error.message}\n\nCaused by:\n${causeMsg}`;
942
- }
943
- // Bun's BuildMessage has logs array
944
- else if ('logs' in error && Array.isArray(error.logs)) {
945
- errorMessage = error.logs.map(formatBunLog).join('\n\n');
946
- }
947
- else {
948
- errorMessage = error.stack || error.message;
949
- }
950
- } else if (typeof error === 'object' && error !== null) {
951
- // Bun BuildOutput has logs
952
- if (error.logs && Array.isArray(error.logs)) {
953
- errorMessage = error.logs.map(formatBunLog).join('\n\n');
954
- } else {
955
- errorMessage = String(error);
956
- }
957
- } else {
958
- errorMessage = String(error);
959
- }
960
-
961
- // If we still just have "Bundle failed", try to get more from inspect
962
- if (errorMessage === 'Bundle failed' || errorMessage.includes('Bundle failed\n')) {
963
- try {
964
- const inspected = inspect(error);
965
- if (inspected && inspected !== '[object Object]' && inspected.length > errorMessage.length) {
966
- errorMessage = inspected;
967
- }
968
- } catch {}
969
- }
970
-
971
- console.error(`❌ Error building ${basePath}:`, error);
972
- buildErrors.push({
973
- file: `pages/${file}`,
974
- message: errorMessage,
975
- type: errorMessage.includes('minification') || errorMessage.includes('minify') ? 'minify' : 'render',
976
- });
977
- errorCount++;
978
- }
979
- }
980
-
981
- // Build CMS templates from root templates/ directory
982
- const templatesDir = projectPaths.templates();
983
- const staticCollections = new Map<string, ClientDataCollection>();
984
- const cmsResult = await buildCMSTemplates(
985
- templatesDir,
986
- globalComponents,
987
- i18nConfig,
988
- slugMappings,
989
- distDir,
990
- cmsService,
991
- generatedUrls,
992
- staticCollections,
993
- siteUrl,
994
- trackingScript
995
- );
996
- successCount += cmsResult.success;
997
- errorCount += cmsResult.errors;
998
-
999
- // Generate static data files for collections with 'static' strategy
1000
- await generateStaticDataFiles(staticCollections, distDir);
1001
-
1002
- // Generate A/B testing functions if experiments are running
1003
- if (runningExperiments.length > 0) {
1004
- await generateABFunctions(runningExperiments, distDir);
1005
- console.log(`\n🧪 A/B testing functions generated (middleware, tracker, results API)`);
1006
- }
1007
-
1008
- // Generate SEO files (robots.txt and sitemap.xml)
1009
- if (siteUrl) {
1010
- await generateRobotsTxt(siteUrl, distDir);
1011
- await generateSitemap([...generatedUrls], siteUrl, distDir);
1012
- console.log(`\n🔍 SEO files generated (robots.txt, sitemap.xml)`);
1013
- } else {
1014
- console.warn(`\n⚠️ Skipping SEO files: siteUrl not configured in project.config.json`);
1015
- }
1016
-
1017
- console.log("\n" + "=".repeat(50));
1018
- console.log(`✨ Build complete!`);
1019
- console.log(` ✅ Success: ${successCount}`);
1020
- if (errorCount > 0) {
1021
- console.log(` ❌ Errors: ${errorCount}`);
1022
- }
1023
- console.log(`\n📦 Production files in /dist:`);
1024
- console.log(` - *.html (Static pages with vanilla JS)`);
1025
- console.log(` - fonts/ (Custom fonts)`);
1026
- console.log(` - images/ (Image assets)`);
1027
- console.log(` - icons/ (Favicon and icons)`);
1028
- if (staticCollections.size > 0) {
1029
- console.log(` - data/ (CMS collection data for client filtering)`);
1030
- }
1031
- if (existsSync(functionsDir)) {
1032
- console.log(` - functions/ (Cloudflare Pages Functions)`);
1033
- }
1034
- if (runningExperiments.length > 0) {
1035
- console.log(` - A/B testing (middleware + tracking injected)`);
1036
- }
1037
- if (siteUrl) {
1038
- console.log(` - robots.txt, sitemap.xml (SEO)`);
1039
- }
1040
- console.log(` - No React, no client-router ✓`);
1041
-
1042
- // Collect component JS validation errors
1043
- const jsErrors = getJSValidationErrors();
1044
- for (const { component, error } of jsErrors) {
1045
- buildErrors.push({
1046
- file: `components/${component}`,
1047
- message: error,
1048
- type: 'minify',
1049
- });
1050
- }
1051
-
1052
- // Write build errors to _errors.json for static server overlay
1053
- if (buildErrors.length > 0) {
1054
- // Deduplicate errors by message (same error may occur on multiple pages)
1055
- const seenMessages = new Set<string>();
1056
- const uniqueErrors = buildErrors.filter(err => {
1057
- if (seenMessages.has(err.message)) return false;
1058
- seenMessages.add(err.message);
1059
- return true;
1060
- });
1061
-
1062
- const errorsData: BuildErrorsData = {
1063
- errors: uniqueErrors,
1064
- timestamp: Date.now(),
1065
- };
1066
- await writeFile(errorsPath, JSON.stringify(errorsData, null, 2), 'utf-8');
1067
- const countMsg = uniqueErrors.length === buildErrors.length
1068
- ? `${uniqueErrors.length} error${uniqueErrors.length === 1 ? '' : 's'}`
1069
- : `${uniqueErrors.length} unique error${uniqueErrors.length === 1 ? '' : 's'} (affected ${buildErrors.length} files)`;
1070
- console.log(`\n⚠️ Build errors written to dist/_errors.json (${countMsg})`);
1071
- }
1072
- }
1073
-
1074
-