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
@@ -31,7 +31,7 @@ export function createError(
31
31
  code: string,
32
32
  message: string,
33
33
  context?: Record<string, unknown>,
34
- cause?: Error
34
+ cause?: Error,
35
35
  ): AppError {
36
36
  return { category, code, message, context, cause };
37
37
  }
@@ -40,11 +40,5 @@ export function createError(
40
40
  * Type guard for AppError
41
41
  */
42
42
  export function isAppError(error: unknown): error is AppError {
43
- return (
44
- typeof error === 'object' &&
45
- error !== null &&
46
- 'category' in error &&
47
- 'code' in error &&
48
- 'message' in error
49
- );
43
+ return typeof error === 'object' && error !== null && 'category' in error && 'code' in error && 'message' in error;
50
44
  }
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Expression Evaluator Tests
3
+ *
4
+ * The headline concern here is the SSOT (single-source-of-truth) invariant between
5
+ * `evaluateNode`'s switch and `SUPPORTED_NODE_TYPES`. `isSupportedTemplateExpression`
6
+ * (used by the .astro codec to decide modelable {{binding}} vs verbatim _code) trusts
7
+ * `SUPPORTED_NODE_TYPES` to mirror exactly the node types `evaluateNode` can actually
8
+ * evaluate. If they drift, the failure is silent — either evaluatable code gets emitted
9
+ * as verbatim _code, or un-evaluatable code gets coerced into a fake {{binding}} that
10
+ * shows up literally in SSR output. These tests force a contributor who touches one side
11
+ * to touch the other.
12
+ */
13
+
14
+ import { describe, test, expect } from 'bun:test';
15
+ import { safeEvaluate, isSupportedTemplateExpression, SUPPORTED_NODE_TYPES } from './expressionEvaluator';
16
+
17
+ /**
18
+ * The complete set of jsep node types `evaluateNode` has a `case` for AND returns a
19
+ * value from (i.e. does NOT throw). This list is intentionally hardcoded: adding a new
20
+ * `case` to evaluateNode without adding it here (or vice-versa) must fail the exact-set
21
+ * assertion below, forcing the SSOT to be kept in lockstep.
22
+ *
23
+ * Note: `LogicalExpression` is handled defensively in the same switch case as
24
+ * `BinaryExpression`, but jsep classifies `&&`/`||` as `BinaryExpression` by default, so
25
+ * it is never produced from a parsed string — hence it has no representative expression.
26
+ */
27
+ const EXPECTED_SUPPORTED_NODE_TYPES = [
28
+ 'Identifier',
29
+ 'Literal',
30
+ 'MemberExpression',
31
+ 'BinaryExpression',
32
+ 'LogicalExpression',
33
+ 'ConditionalExpression',
34
+ 'UnaryExpression',
35
+ 'ArrayExpression',
36
+ ].sort();
37
+
38
+ /**
39
+ * A representative, parseable expression for every supported node type jsep can actually
40
+ * emit, paired with the value `evaluateNode` should return for it given `CONTEXT`. Each
41
+ * entry double-proves the SSOT: the expression both (a) is reported supported by
42
+ * `isSupportedTemplateExpression`, and (b) is actually evaluated (not thrown) by
43
+ * `safeEvaluate` — so a type listed in SUPPORTED_NODE_TYPES but missing from the switch
44
+ * would surface as a wrong/undefined evaluation result here.
45
+ */
46
+ const CONTEXT = { a: 2, b: 3, flag: true, name: 'Meno', obj: { x: 7 }, key: 'x' };
47
+ const REPRESENTATIVE: Array<{ nodeType: string; expr: string; expected: unknown }> = [
48
+ { nodeType: 'Identifier', expr: 'name', expected: 'Meno' },
49
+ { nodeType: 'Literal', expr: '42', expected: 42 },
50
+ { nodeType: 'MemberExpression', expr: 'obj.x', expected: 7 },
51
+ { nodeType: 'MemberExpression(computed)', expr: 'obj[key]', expected: 7 },
52
+ { nodeType: 'BinaryExpression', expr: 'a + b', expected: 5 },
53
+ { nodeType: 'BinaryExpression(&&)', expr: 'flag && name', expected: 'Meno' },
54
+ { nodeType: 'BinaryExpression(||)', expr: 'a || b', expected: 2 },
55
+ { nodeType: 'ConditionalExpression', expr: 'flag ? a : b', expected: 2 },
56
+ { nodeType: 'UnaryExpression', expr: '!flag', expected: false },
57
+ { nodeType: 'ArrayExpression', expr: '[a, b]', expected: [2, 3] },
58
+ ];
59
+
60
+ /** Node types the evaluator deliberately refuses (its switch throws for these). */
61
+ const UNSUPPORTED_EXPRESSIONS: Array<{ nodeType: string; expr: string }> = [
62
+ { nodeType: 'CallExpression', expr: 'obj.x.toFixed(2)' },
63
+ { nodeType: 'Compound', expr: 'a, b' },
64
+ ];
65
+
66
+ describe('expressionEvaluator SSOT invariant', () => {
67
+ test('SUPPORTED_NODE_TYPES exactly equals the expected hardcoded set', () => {
68
+ // If this fails, evaluateNode's switch and SUPPORTED_NODE_TYPES have drifted —
69
+ // update the switch, SUPPORTED_NODE_TYPES, AND this test's expected list together.
70
+ expect([...SUPPORTED_NODE_TYPES].sort()).toEqual(EXPECTED_SUPPORTED_NODE_TYPES);
71
+ });
72
+
73
+ test('every representative supported expression is reported supported AND evaluated', () => {
74
+ for (const { nodeType: _nodeType, expr, expected } of REPRESENTATIVE) {
75
+ // (a) the codec predicate must accept it
76
+ expect(isSupportedTemplateExpression(expr)).toBe(true);
77
+ // (b) the evaluator must actually handle it (not throw -> undefined fallback)
78
+ expect(safeEvaluate(expr, CONTEXT)).toEqual(expected as never);
79
+ }
80
+ });
81
+
82
+ test('every representative node type (jsep-producible) is present in SUPPORTED_NODE_TYPES', () => {
83
+ // Maps the labelled representatives back to their base jsep node type and asserts
84
+ // membership — closes the "added a case to the switch but forgot the set" gap for
85
+ // any type that has been given a representative expression.
86
+ const producibleBaseTypes = new Set(REPRESENTATIVE.map((r) => r.nodeType.replace(/\(.*\)$/, '')));
87
+ for (const baseType of producibleBaseTypes) {
88
+ expect(SUPPORTED_NODE_TYPES.has(baseType)).toBe(true);
89
+ }
90
+ });
91
+ });
92
+
93
+ describe('isSupportedTemplateExpression', () => {
94
+ test('returns false for function/method calls (verbatim _code territory)', () => {
95
+ for (const { expr } of UNSUPPORTED_EXPRESSIONS) {
96
+ expect(isSupportedTemplateExpression(expr)).toBe(false);
97
+ }
98
+ });
99
+
100
+ test('returns false for empty / whitespace / syntactically invalid expressions', () => {
101
+ expect(isSupportedTemplateExpression('')).toBe(false);
102
+ expect(isSupportedTemplateExpression(' ')).toBe(false);
103
+ expect(isSupportedTemplateExpression('a +')).toBe(false);
104
+ });
105
+
106
+ test('rejects an expression nesting an unsupported call deep inside a supported tree', () => {
107
+ // The recursive walk must catch unsupported nodes that are not at the root.
108
+ expect(isSupportedTemplateExpression('a + obj.x.toFixed(2)')).toBe(false);
109
+ expect(isSupportedTemplateExpression('flag ? name : obj.x.toString()')).toBe(false);
110
+ });
111
+ });
112
+
113
+ describe('safeEvaluate', () => {
114
+ test('returns undefined (no throw) for unsupported expressions', () => {
115
+ for (const { expr } of UNSUPPORTED_EXPRESSIONS) {
116
+ expect(safeEvaluate(expr, CONTEXT)).toBeUndefined();
117
+ }
118
+ });
119
+ });
120
+
121
+ describe('optional chaining (?.) and nullish coalescing (??)', () => {
122
+ const CTX = { user: { email: 'a@b.com' }, missing: undefined as unknown, zero: 0, empty: '' };
123
+
124
+ test('?. short-circuits to undefined when the object is null/undefined (not a throw)', () => {
125
+ expect(safeEvaluate('user?.email', CTX)).toBe('a@b.com');
126
+ expect(safeEvaluate('missing?.email', CTX)).toBeUndefined();
127
+ expect(safeEvaluate('missing?.email?.length', CTX)).toBeUndefined();
128
+ });
129
+
130
+ test('?? falls through only on null/undefined, not other falsy values', () => {
131
+ expect(safeEvaluate("missing ?? 'fallback'", CTX)).toBe('fallback');
132
+ expect(safeEvaluate("zero ?? 'fallback'", CTX)).toBe(0); // 0 is NOT nullish
133
+ expect(safeEvaluate("empty ?? 'fallback'", CTX)).toBe(''); // '' is NOT nullish
134
+ });
135
+
136
+ test("the real-world promoted shape `user?.name ?? ''` evaluates correctly (no longer a pretend binding)", () => {
137
+ expect(safeEvaluate("user?.email ?? 'none'", CTX)).toBe('a@b.com');
138
+ expect(safeEvaluate("missing?.email ?? 'none'", CTX)).toBe('none');
139
+ });
140
+
141
+ test('both are reported supported (and now actually evaluable, closing the soundness gap)', () => {
142
+ expect(isSupportedTemplateExpression('user?.email')).toBe(true);
143
+ expect(isSupportedTemplateExpression("missing?.email ?? ''")).toBe(true);
144
+ });
145
+ });
@@ -6,8 +6,8 @@
6
6
  * Supports: ternary, binary, logical, unary operators, member access, arrays
7
7
  */
8
8
 
9
- import jsep from 'jsep';
10
9
  import type { Expression } from 'jsep';
10
+ import jsep from 'jsep';
11
11
 
12
12
  /**
13
13
  * Safely evaluates a jsep AST node against a context object
@@ -61,6 +61,15 @@ function evaluateNode(node: Expression, context: Record<string, unknown>): unkno
61
61
  const left = evaluateNode(binaryNode.left, context);
62
62
  return left ? left : evaluateNode(binaryNode.right, context);
63
63
  }
64
+ // Nullish coalescing: only fall through on null/undefined (NOT other falsy values).
65
+ // jsep parses `??` as a BinaryExpression (already a supported node type); without this
66
+ // case the operator switch below would throw and `a ?? b` would silently evaluate to
67
+ // undefined. Combined with the MemberExpression null-guard above, `a?.b ?? c` evaluates
68
+ // correctly — so these are real editable bindings, not pretend ones.
69
+ if (binaryNode.operator === '??') {
70
+ const left = evaluateNode(binaryNode.left, context);
71
+ return left === null || left === undefined ? evaluateNode(binaryNode.right, context) : left;
72
+ }
64
73
 
65
74
  // Evaluate both sides for non-logical operators
66
75
  const left = evaluateNode(binaryNode.left, context);
@@ -73,18 +82,32 @@ function evaluateNode(node: Expression, context: Record<string, unknown>): unkno
73
82
  return String(left ?? '') + String(right ?? '');
74
83
  }
75
84
  return (left as number) + (right as number);
76
- case '-': return (left as number) - (right as number);
77
- case '*': return (left as number) * (right as number);
78
- case '/': return (left as number) / (right as number);
79
- case '%': return (left as number) % (right as number);
80
- case '===': return left === right;
81
- case '!==': return left !== right;
82
- case '==': return left == right;
83
- case '!=': return left != right;
84
- case '<': return (left as number) < (right as number);
85
- case '>': return (left as number) > (right as number);
86
- case '<=': return (left as number) <= (right as number);
87
- case '>=': return (left as number) >= (right as number);
85
+ case '-':
86
+ return (left as number) - (right as number);
87
+ case '*':
88
+ return (left as number) * (right as number);
89
+ case '/':
90
+ return (left as number) / (right as number);
91
+ case '%':
92
+ return (left as number) % (right as number);
93
+ case '===':
94
+ return left === right;
95
+ case '!==':
96
+ return left !== right;
97
+ case '==':
98
+ // biome-ignore lint/suspicious/noDoubleEquals: loose equality IS the semantics of the `==` operator in template expressions
99
+ return left == right;
100
+ case '!=':
101
+ // biome-ignore lint/suspicious/noDoubleEquals: loose inequality IS the semantics of the `!=` operator in template expressions
102
+ return left != right;
103
+ case '<':
104
+ return (left as number) < (right as number);
105
+ case '>':
106
+ return (left as number) > (right as number);
107
+ case '<=':
108
+ return (left as number) <= (right as number);
109
+ case '>=':
110
+ return (left as number) >= (right as number);
88
111
  default:
89
112
  throw new Error(`Unsupported operator: ${binaryNode.operator}`);
90
113
  }
@@ -94,9 +117,7 @@ function evaluateNode(node: Expression, context: Record<string, unknown>): unkno
94
117
  // Ternary: condition ? consequent : alternate
95
118
  const condNode = node as jsep.ConditionalExpression;
96
119
  const test = evaluateNode(condNode.test, context);
97
- return test
98
- ? evaluateNode(condNode.consequent, context)
99
- : evaluateNode(condNode.alternate, context);
120
+ return test ? evaluateNode(condNode.consequent, context) : evaluateNode(condNode.alternate, context);
100
121
  }
101
122
 
102
123
  case 'UnaryExpression': {
@@ -105,9 +126,12 @@ function evaluateNode(node: Expression, context: Record<string, unknown>): unkno
105
126
  const arg = evaluateNode(unaryNode.argument, context);
106
127
 
107
128
  switch (unaryNode.operator) {
108
- case '!': return !arg;
109
- case '-': return -(arg as number);
110
- case '+': return +(arg as number);
129
+ case '!':
130
+ return !arg;
131
+ case '-':
132
+ return -(arg as number);
133
+ case '+':
134
+ return +(arg as number);
111
135
  default:
112
136
  throw new Error(`Unsupported unary operator: ${unaryNode.operator}`);
113
137
  }
@@ -116,9 +140,7 @@ function evaluateNode(node: Expression, context: Record<string, unknown>): unkno
116
140
  case 'ArrayExpression': {
117
141
  // Array literals: [1, 2, 3]
118
142
  const arrayNode = node as jsep.ArrayExpression;
119
- return arrayNode.elements.map(el =>
120
- el ? evaluateNode(el, context) : undefined
121
- );
143
+ return arrayNode.elements.map((el) => (el ? evaluateNode(el, context) : undefined));
122
144
  }
123
145
 
124
146
  case 'CallExpression': {
@@ -160,8 +182,14 @@ export function isComplexExpression(expression: string): boolean {
160
182
  return /[?:+\-*/%<>=!&|]/.test(expression);
161
183
  }
162
184
 
163
- /** jsep node types `evaluateNode` (above) can evaluate — the rest throw at runtime. */
164
- const SUPPORTED_NODE_TYPES = new Set([
185
+ /**
186
+ * jsep node types `evaluateNode` (above) can evaluate — the rest throw at runtime.
187
+ *
188
+ * SSOT: this set MUST stay in lockstep with the `case` labels in `evaluateNode`'s
189
+ * switch. Drift is silent (lost code or literal `{{…}}` in SSR output), so the
190
+ * invariant is pinned by expressionEvaluator.test.ts — update both together.
191
+ */
192
+ export const SUPPORTED_NODE_TYPES = new Set([
165
193
  'Identifier',
166
194
  'Literal',
167
195
  'MemberExpression',
@@ -1,7 +1,7 @@
1
- import { describe, test, expect, afterAll } from 'bun:test';
2
- import { join } from 'path';
3
- import { mkdirSync, writeFileSync, rmSync } from 'fs';
4
- import { tmpdir } from 'os';
1
+ import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
2
+ import { join } from 'node:path';
3
+ import { mkdirSync, writeFileSync, rmSync } from 'node:fs';
4
+ import { tmpdir } from 'node:os';
5
5
  import { setProjectRoot, getProjectRoot } from '../server/projectContext';
6
6
 
7
7
  // Save original project root to restore after tests
@@ -13,10 +13,23 @@ const testDir = join(tmpdir(), `fontLoader-test-${Date.now()}`);
13
13
  mkdirSync(testDir, { recursive: true });
14
14
  setProjectRoot(testDir);
15
15
 
16
+ // fontLoader keeps a module-level config cache that survives across test
17
+ // files in a single `bun test` process. Earlier files (e.g. anything pulling
18
+ // in the SSR htmlGenerator) may already have populated it with `{ fonts: [] }`,
19
+ // which would make loadProjectConfig() return that stale cache instead of
20
+ // reading our test config. Reset on both ends so this file neither inherits
21
+ // nor leaks cached config.
22
+ beforeAll(() => {
23
+ resetFontConfig();
24
+ });
25
+
16
26
  // Restore original project root after all tests in this file
17
27
  afterAll(() => {
18
28
  setProjectRoot(originalProjectRoot);
19
- try { rmSync(testDir, { recursive: true, force: true }); } catch {}
29
+ resetFontConfig();
30
+ try {
31
+ rmSync(testDir, { recursive: true, force: true });
32
+ } catch {}
20
33
  });
21
34
 
22
35
  // Helper to write a project.config.json in the test directory
@@ -24,7 +37,7 @@ function writeConfig(config: Record<string, unknown>): void {
24
37
  writeFileSync(join(testDir, 'project.config.json'), JSON.stringify(config));
25
38
  }
26
39
 
27
- function removeConfig(): void {
40
+ function _removeConfig(): void {
28
41
  try {
29
42
  rmSync(join(testDir, 'project.config.json'));
30
43
  } catch {
@@ -99,6 +112,7 @@ import {
99
112
  generateFontCSS,
100
113
  generateFontPreloadTags,
101
114
  getFontFamilies,
115
+ resetFontConfig,
102
116
  } from './fontLoader';
103
117
 
104
118
  describe('fontLoader', () => {
@@ -215,7 +229,7 @@ describe('fontLoader', () => {
215
229
  test('generateFontCSS omits font-display when not specified', () => {
216
230
  const css = generateFontCSS();
217
231
  // The Roboto bold entry has no fontDisplay
218
- const robotoBlock = css.split('\n\n').find(b => b.includes('roboto-bold.ttf'))!;
232
+ const robotoBlock = css.split('\n\n').find((b) => b.includes('roboto-bold.ttf'))!;
219
233
  expect(robotoBlock).toBeDefined();
220
234
  expect(robotoBlock).not.toContain('font-display');
221
235
  });
@@ -277,7 +291,7 @@ describe('fontLoader', () => {
277
291
 
278
292
  test('generateFontPreloadTags defaults to font/ttf for unknown extension', () => {
279
293
  const tags = generateFontPreloadTags();
280
- const mysteryTag = tags.split('\n ').find(t => t.includes('mystery.xyz'))!;
294
+ const mysteryTag = tags.split('\n ').find((t) => t.includes('mystery.xyz'))!;
281
295
  expect(mysteryTag).toContain('type="font/ttf"');
282
296
  });
283
297
 
@@ -297,7 +311,7 @@ describe('fontLoader', () => {
297
311
  test('generateFontPreloadTags produces correct link tag format', () => {
298
312
  const tags = generateFontPreloadTags();
299
313
  expect(tags).toContain(
300
- '<link rel="preload" href="/fonts/inter-variable.woff2" as="font" type="font/woff2" crossorigin>'
314
+ '<link rel="preload" href="/fonts/inter-variable.woff2" as="font" type="font/woff2" crossorigin>',
301
315
  );
302
316
  });
303
317
 
@@ -305,7 +319,7 @@ describe('fontLoader', () => {
305
319
 
306
320
  test('getFontFamilies expands variable weight range in 100 increments', () => {
307
321
  const families = getFontFamilies();
308
- expect(families['Inter']).toEqual([100, 200, 300, 400, 500, 600, 700, 800, 900]);
322
+ expect(families.Inter).toEqual([100, 200, 300, 400, 500, 600, 700, 800, 900]);
309
323
  });
310
324
 
311
325
  test('getFontFamilies extracts family name from path', () => {
@@ -316,24 +330,24 @@ describe('fontLoader', () => {
316
330
  test('getFontFamilies groups weights and deduplicates across static and variable', () => {
317
331
  const families = getFontFamilies();
318
332
  // Roboto: 700 (bold static), 400 (regular static), 300-500 variable (400 deduped)
319
- expect(families['Roboto']).toContain(700);
320
- expect(families['Roboto']).toContain(400);
321
- expect(families['Roboto']).toContain(300);
322
- expect(families['Roboto']).toContain(500);
333
+ expect(families.Roboto).toContain(700);
334
+ expect(families.Roboto).toContain(400);
335
+ expect(families.Roboto).toContain(300);
336
+ expect(families.Roboto).toContain(500);
323
337
  // No duplicates
324
- const unique = [...new Set(families['Roboto'])];
325
- expect(families['Roboto'].length).toBe(unique.length);
338
+ const unique = [...new Set(families.Roboto)];
339
+ expect(families.Roboto!.length).toBe(unique.length);
326
340
  });
327
341
 
328
342
  test('getFontFamilies uses default weight 400 when not specified', () => {
329
343
  const families = getFontFamilies();
330
- expect(families['Playfair']).toEqual([400]);
344
+ expect(families.Playfair).toEqual([400]);
331
345
  });
332
346
 
333
347
  test('getFontFamilies handles single-weight variable font', () => {
334
348
  const families = getFontFamilies();
335
349
  // Mono: weight=400, weightMax=400 -> [400]
336
- expect(families['Mono']).toEqual([400]);
350
+ expect(families.Mono).toEqual([400]);
337
351
  });
338
352
 
339
353
  test('getFontFamilies handles extracted name for unknown extension', () => {
@@ -54,8 +54,8 @@ export function getFontFamilies(): Record<string, number[]> {
54
54
  const fonts = config.fonts || [];
55
55
  const familiesMap: Record<string, number[]> = {};
56
56
 
57
- fonts.forEach((font: any) => {
58
- const fontPath: string = font.path || font.src;
57
+ fonts.forEach((font: { path?: string; src?: string; family?: string; weight?: number; weightMax?: number }) => {
58
+ const fontPath: string = font.path || font.src || '';
59
59
  if (!fontPath) return;
60
60
  const family = font.family || extractFamilyName(fontPath);
61
61
  const weight = font.weight ?? 400;
@@ -83,7 +83,7 @@ const RULES: Rule[] = [
83
83
  test: /Cannot find module|Failed to resolve module|Failed to load module|Error loading dynamically imported module/i,
84
84
  build: () => ({
85
85
  title: "A required file couldn't be loaded",
86
- friendlyMessage: 'An import or module reference couldn\'t be found.',
86
+ friendlyMessage: "An import or module reference couldn't be found.",
87
87
  hint: 'Check the import path.',
88
88
  }),
89
89
  },
@@ -114,8 +114,7 @@ export function toFriendlyError(input: unknown): FriendlyError {
114
114
 
115
115
  return {
116
116
  title: 'This section ran into a problem',
117
- friendlyMessage:
118
- 'Something went wrong while rendering this part of the page. The rest of the page is fine.',
117
+ friendlyMessage: 'Something went wrong while rendering this part of the page. The rest of the page is fine.',
119
118
  raw,
120
119
  };
121
120
  }
@@ -1,9 +1,5 @@
1
1
  import { describe, test, expect } from 'bun:test';
2
- import {
3
- isGradientValue,
4
- parseGradient,
5
- buildGradientCss,
6
- } from './gradientUtils';
2
+ import { isGradientValue, parseGradient, buildGradientCss } from './gradientUtils';
7
3
 
8
4
  describe('isGradientValue', () => {
9
5
  test('identifies gradient values', () => {
@@ -51,8 +47,8 @@ describe('parseGradient', () => {
51
47
  const result = parseGradient('linear-gradient(180deg, red, blue)');
52
48
  expect(result).not.toBeNull();
53
49
  expect(result!.stops.length).toBe(2);
54
- expect(result!.stops[0].position).toBe(0);
55
- expect(result!.stops[1].position).toBe(100);
50
+ expect(result!.stops[0]!.position).toBe(0);
51
+ expect(result!.stops[1]!.position).toBe(100);
56
52
  });
57
53
 
58
54
  test('parses radial gradient with circle', () => {
@@ -22,9 +22,7 @@ const CONIC_RE = /^conic-gradient\((.+)\)$/i;
22
22
  export function isGradientValue(value: string): boolean {
23
23
  if (!value) return false;
24
24
  const v = value.trim().toLowerCase();
25
- return v.startsWith('linear-gradient(') ||
26
- v.startsWith('radial-gradient(') ||
27
- v.startsWith('conic-gradient(');
25
+ return v.startsWith('linear-gradient(') || v.startsWith('radial-gradient(') || v.startsWith('conic-gradient(');
28
26
  }
29
27
 
30
28
  /** Parse a CSS gradient string into a GradientConfig */
@@ -35,17 +33,24 @@ export function parseGradient(cssValue: string): GradientConfig | null {
35
33
  let match: RegExpMatchArray | null;
36
34
  let type: GradientConfig['type'];
37
35
 
38
- if ((match = v.match(LINEAR_RE))) {
36
+ const linearMatch = v.match(LINEAR_RE);
37
+ const radialMatch = linearMatch ? null : v.match(RADIAL_RE);
38
+ const conicMatch = linearMatch || radialMatch ? null : v.match(CONIC_RE);
39
+
40
+ if (linearMatch) {
41
+ match = linearMatch;
39
42
  type = 'linear';
40
- } else if ((match = v.match(RADIAL_RE))) {
43
+ } else if (radialMatch) {
44
+ match = radialMatch;
41
45
  type = 'radial';
42
- } else if ((match = v.match(CONIC_RE))) {
46
+ } else if (conicMatch) {
47
+ match = conicMatch;
43
48
  type = 'conic';
44
49
  } else {
45
50
  return null;
46
51
  }
47
52
 
48
- const inner = match[1];
53
+ const inner = match[1] ?? '';
49
54
  const parts = splitGradientArgs(inner);
50
55
 
51
56
  let angle = type === 'linear' ? 180 : 0;
@@ -80,7 +85,7 @@ export function parseGradient(cssValue: string): GradientConfig | null {
80
85
  if (first) {
81
86
  const fromAngle = first.match(/^from\s+(\d+(?:\.\d+)?)deg/i);
82
87
  if (fromAngle) {
83
- angle = parseFloat(fromAngle[1]);
88
+ angle = parseFloat(fromAngle[1] ?? '');
84
89
  stopsStartIndex = 1;
85
90
  }
86
91
  }
@@ -94,9 +99,7 @@ export function parseGradient(cssValue: string): GradientConfig | null {
94
99
 
95
100
  /** Build a CSS gradient string from a GradientConfig */
96
101
  export function buildGradientCss(config: GradientConfig): string {
97
- const stopStrings = config.stops.map(
98
- s => `${s.color} ${Math.round(s.position)}%`
99
- );
102
+ const stopStrings = config.stops.map((s) => `${s.color} ${Math.round(s.position)}%`);
100
103
 
101
104
  switch (config.type) {
102
105
  case 'linear': {
@@ -137,7 +140,7 @@ function splitGradientArgs(inner: string): string[] {
137
140
 
138
141
  function parseAngle(part: string): number | null {
139
142
  const m = part.match(/^(-?\d+(?:\.\d+)?)deg$/i);
140
- return m ? parseFloat(m[1]) : null;
143
+ return m ? parseFloat(m[1] ?? '') : null;
141
144
  }
142
145
 
143
146
  function directionToAngle(direction: string): number {
@@ -160,7 +163,7 @@ function parseStops(parts: string[]): GradientStop[] {
160
163
  const total = parts.length;
161
164
 
162
165
  for (let i = 0; i < total; i++) {
163
- const part = parts[i].trim();
166
+ const part = parts[i]?.trim();
164
167
  if (!part) continue;
165
168
 
166
169
  // Match color and optional position percentage
@@ -170,7 +173,7 @@ function parseStops(parts: string[]): GradientStop[] {
170
173
 
171
174
  if (posMatch) {
172
175
  color = part.slice(0, posMatch.index!).trim();
173
- position = parseFloat(posMatch[1]);
176
+ position = parseFloat(posMatch[1] ?? '');
174
177
  } else {
175
178
  color = part;
176
179
  // Auto-distribute position
@@ -56,8 +56,8 @@ describe('rewriteHrefRefs', () => {
56
56
  ],
57
57
  };
58
58
  expect(rewriteHrefRefs(tree, '/about', '/about-us')).toBe(true);
59
- expect(tree.children[0].href).toBe('/about-us');
60
- expect((tree.children[1].children![0] as any).href).toBe('/about-us/team');
59
+ expect(tree.children[0]!.href).toBe('/about-us');
60
+ expect((tree.children[1]!.children![0] as any).href).toBe('/about-us/team');
61
61
  });
62
62
 
63
63
  test('walks prop trees (component nodes with object props)', () => {
@@ -16,11 +16,7 @@
16
16
 
17
17
  function rewriteHref(value: string, oldHref: string, newHref: string): string | null {
18
18
  if (value === oldHref) return newHref;
19
- if (
20
- value.startsWith(oldHref + '/') ||
21
- value.startsWith(oldHref + '?') ||
22
- value.startsWith(oldHref + '#')
23
- ) {
19
+ if (value.startsWith(`${oldHref}/`) || value.startsWith(`${oldHref}?`) || value.startsWith(`${oldHref}#`)) {
24
20
  return newHref + value.substring(oldHref.length);
25
21
  }
26
22
  return null;
@@ -34,11 +30,7 @@ function rewriteHref(value: string, oldHref: string, newHref: string): string |
34
30
  * Walks `children` recursively (array or single object) and any object-valued
35
31
  * field on the node (covers component-prop-trees, nested structure, etc.).
36
32
  */
37
- export function rewriteHrefRefs(
38
- node: unknown,
39
- oldHref: string,
40
- newHref: string,
41
- ): boolean {
33
+ export function rewriteHrefRefs(node: unknown, oldHref: string, newHref: string): boolean {
42
34
  if (!node || typeof node !== 'object') return false;
43
35
  if (!oldHref || oldHref === newHref) return false;
44
36
 
@@ -32,7 +32,7 @@ describe('i18n', () => {
32
32
 
33
33
  test('should have English locale configured', () => {
34
34
  expect(DEFAULT_I18N_CONFIG.locales).toHaveLength(1);
35
- expect(DEFAULT_I18N_CONFIG.locales[0].code).toBe('en');
35
+ expect(DEFAULT_I18N_CONFIG.locales[0]!.code).toBe('en');
36
36
  });
37
37
  });
38
38
 
@@ -83,8 +83,8 @@ describe('i18n', () => {
83
83
  const migrated = migrateI18nConfig(oldConfig);
84
84
  expect(migrated.defaultLocale).toBe('en');
85
85
  expect(migrated.locales).toHaveLength(3);
86
- expect(migrated.locales[0].code).toBe('en');
87
- expect(migrated.locales[1].code).toBe('pl');
86
+ expect(migrated.locales[0]!.code).toBe('en');
87
+ expect(migrated.locales[1]!.code).toBe('pl');
88
88
  });
89
89
 
90
90
  test('should preserve new format config', () => {
@@ -199,7 +199,7 @@ describe('i18n', () => {
199
199
  test('should return empty array if no translation available for list i18n', () => {
200
200
  const listValue = {
201
201
  _i18n: true,
202
- en: [], // Add empty array to make it a list i18n
202
+ en: [], // Add empty array to make it a list i18n
203
203
  } as I18nValue;
204
204
  // Remove the empty array to test empty fallback detection
205
205
  delete (listValue as any).en;