meno-core 1.0.53 → 1.0.54

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (387) hide show
  1. package/.claude/settings.local.json +1 -3
  2. package/bin/cli.ts +48 -57
  3. package/build-astro.ts +131 -113
  4. package/build-next.ts +109 -96
  5. package/build-static.test.ts +39 -10
  6. package/build-static.ts +120 -122
  7. package/dist/bin/cli.js +34 -38
  8. package/dist/bin/cli.js.map +2 -2
  9. package/dist/build-static.js +12 -11
  10. package/dist/chunks/chunk-2AR55GYH.js +42 -0
  11. package/dist/chunks/chunk-2AR55GYH.js.map +7 -0
  12. package/dist/chunks/{chunk-IGYR22T6.js → chunk-2FN4UOVO.js} +512 -239
  13. package/dist/chunks/chunk-2FN4UOVO.js.map +7 -0
  14. package/dist/chunks/chunk-3XER4E5W.js +168 -0
  15. package/dist/chunks/chunk-3XER4E5W.js.map +7 -0
  16. package/dist/chunks/{chunk-X754AHS5.js → chunk-5ETZFREW.js} +9 -12
  17. package/dist/chunks/chunk-5ETZFREW.js.map +7 -0
  18. package/dist/chunks/{chunk-2MHDV5BF.js → chunk-7E4IF5L7.js} +15 -21
  19. package/dist/chunks/chunk-7E4IF5L7.js.map +7 -0
  20. package/dist/chunks/{chunk-JGWFTO6P.js → chunk-7HWQUVTU.js} +1578 -1349
  21. package/dist/chunks/chunk-7HWQUVTU.js.map +7 -0
  22. package/dist/chunks/{chunk-O3NAGJP4.js → chunk-AE3QK5QW.js} +110 -21
  23. package/dist/chunks/chunk-AE3QK5QW.js.map +7 -0
  24. package/dist/chunks/{chunk-GZHGVVW3.js → chunk-F6KTJYGV.js} +7 -7
  25. package/dist/chunks/chunk-F6KTJYGV.js.map +7 -0
  26. package/dist/chunks/{chunk-WQFG7PAH.js → chunk-FZITJSSS.js} +2 -6
  27. package/dist/chunks/chunk-FZITJSSS.js.map +7 -0
  28. package/dist/chunks/{chunk-QB2LNO4W.js → chunk-GSYYA5GX.js} +2 -2
  29. package/dist/chunks/chunk-GSYYA5GX.js.map +7 -0
  30. package/dist/chunks/{chunk-YBLHKYFF.js → chunk-HIZMY3EP.js} +1 -1
  31. package/dist/chunks/chunk-HIZMY3EP.js.map +7 -0
  32. package/dist/chunks/{chunk-AZQYF6KE.js → chunk-I2WEGYA7.js} +41 -176
  33. package/dist/chunks/chunk-I2WEGYA7.js.map +7 -0
  34. package/dist/chunks/{chunk-I7YIGZXT.js → chunk-JNO3CNLJ.js} +6 -9
  35. package/dist/chunks/chunk-JNO3CNLJ.js.map +7 -0
  36. package/dist/chunks/{chunk-UB44F4Z2.js → chunk-NVRBTSQG.js} +2 -4
  37. package/dist/chunks/chunk-NVRBTSQG.js.map +7 -0
  38. package/dist/chunks/{chunk-JGP5A3Y5.js → chunk-Q4OBWKXG.js} +40 -33
  39. package/dist/chunks/chunk-Q4OBWKXG.js.map +7 -0
  40. package/dist/chunks/{chunk-R6XHAFBF.js → chunk-QTE32Y53.js} +250 -242
  41. package/dist/chunks/chunk-QTE32Y53.js.map +7 -0
  42. package/dist/chunks/{chunk-H3GJ4H2U.js → chunk-STDY3OVM.js} +214 -85
  43. package/dist/chunks/chunk-STDY3OVM.js.map +7 -0
  44. package/dist/chunks/configService-PRJZF7Y6.js +14 -0
  45. package/dist/chunks/{constants-STK2YBIW.js → constants-KIQEYMAM.js} +2 -2
  46. package/dist/chunks/{fs-JGINUXGL.js → fs-ZI5JEU7V.js} +2 -2
  47. package/dist/entries/server-router.js +14 -19
  48. package/dist/entries/server-router.js.map +2 -2
  49. package/dist/lib/client/index.js +638 -332
  50. package/dist/lib/client/index.js.map +2 -2
  51. package/dist/lib/server/index.js +177 -235
  52. package/dist/lib/server/index.js.map +2 -2
  53. package/dist/lib/shared/index.js +80 -44
  54. package/dist/lib/shared/index.js.map +2 -2
  55. package/dist/lib/shared/richtext/index.js +1 -1
  56. package/dist/lib/test-utils/index.js +38 -60
  57. package/dist/lib/test-utils/index.js.map +2 -2
  58. package/entries/client-router.tsx +10 -8
  59. package/entries/server-router.tsx +1 -7
  60. package/lib/client/ClientInitializer.ts +8 -8
  61. package/lib/client/ErrorBoundary.test.tsx +146 -143
  62. package/lib/client/ErrorBoundary.tsx +175 -127
  63. package/lib/client/componentRegistry.test.ts +96 -108
  64. package/lib/client/componentRegistry.ts +1 -2
  65. package/lib/client/contexts/ThemeContext.tsx +3 -2
  66. package/lib/client/core/ComponentBuilder.test.ts +513 -560
  67. package/lib/client/core/ComponentBuilder.ts +318 -146
  68. package/lib/client/core/ComponentRenderer.test.tsx +1 -2
  69. package/lib/client/core/ComponentRenderer.tsx +46 -33
  70. package/lib/client/core/builders/embedBuilder.ts +241 -53
  71. package/lib/client/core/builders/linkBuilder.ts +71 -44
  72. package/lib/client/core/builders/linkNodeBuilder.ts +78 -53
  73. package/lib/client/core/builders/listBuilder.ts +115 -86
  74. package/lib/client/core/builders/localeListBuilder.ts +95 -60
  75. package/lib/client/core/builders/types.ts +5 -5
  76. package/lib/client/core/cmsTemplateProcessor.ts +7 -7
  77. package/lib/client/elementRegistry.ts +3 -3
  78. package/lib/client/fontFamiliesService.test.ts +2 -10
  79. package/lib/client/fontFamiliesService.ts +3 -3
  80. package/lib/client/hmr/HMRManager.tsx +8 -0
  81. package/lib/client/hmrCssReload.ts +14 -8
  82. package/lib/client/hmrWebSocket.ts +9 -14
  83. package/lib/client/hooks/useColorVariables.test.ts +21 -21
  84. package/lib/client/hooks/useColorVariables.ts +12 -10
  85. package/lib/client/hooks/usePropertyAutocomplete.ts +3 -5
  86. package/lib/client/hooks/useVariables.ts +4 -4
  87. package/lib/client/hydration/HydrationUtils.test.ts +24 -25
  88. package/lib/client/hydration/HydrationUtils.ts +3 -4
  89. package/lib/client/i18nConfigService.test.ts +2 -7
  90. package/lib/client/i18nConfigService.ts +2 -2
  91. package/lib/client/meno-filter/MenoFilter.test.ts +19 -21
  92. package/lib/client/meno-filter/MenoFilter.ts +5 -9
  93. package/lib/client/meno-filter/bindings.ts +15 -40
  94. package/lib/client/meno-filter/init.ts +1 -1
  95. package/lib/client/meno-filter/renderer.ts +23 -29
  96. package/lib/client/meno-filter/script.generated.ts +1 -3
  97. package/lib/client/meno-filter/ui.ts +3 -5
  98. package/lib/client/meno-filter/updates.ts +15 -21
  99. package/lib/client/navigation.test.ts +159 -159
  100. package/lib/client/navigation.ts +0 -1
  101. package/lib/client/responsiveStyleResolver.test.ts +230 -228
  102. package/lib/client/responsiveStyleResolver.ts +13 -16
  103. package/lib/client/routing/RouteLoader.test.ts +23 -24
  104. package/lib/client/routing/RouteLoader.ts +22 -35
  105. package/lib/client/routing/Router.tsx +31 -3
  106. package/lib/client/scripts/ScriptExecutor.test.ts +157 -158
  107. package/lib/client/scripts/ScriptExecutor.ts +15 -33
  108. package/lib/client/services/PrefetchService.test.ts +2 -2
  109. package/lib/client/services/PrefetchService.ts +10 -24
  110. package/lib/client/styleProcessor.test.ts +9 -9
  111. package/lib/client/styleProcessor.ts +18 -15
  112. package/lib/client/styles/StyleInjector.test.ts +122 -115
  113. package/lib/client/styles/StyleInjector.ts +9 -6
  114. package/lib/client/styles/UtilityClassCollector.ts +19 -26
  115. package/lib/client/styles/cspNonce.test.ts +2 -5
  116. package/lib/client/templateEngine.test.ts +554 -482
  117. package/lib/client/templateEngine.ts +200 -64
  118. package/lib/client/theme.ts +0 -1
  119. package/lib/client/utils/toast.ts +0 -1
  120. package/lib/server/__integration__/api-routes.test.ts +8 -4
  121. package/lib/server/__integration__/cms-integration.test.ts +1 -4
  122. package/lib/server/__integration__/server-lifecycle.test.ts +2 -5
  123. package/lib/server/__integration__/ssr-rendering.test.ts +47 -37
  124. package/lib/server/__integration__/static-assets.test.ts +1 -1
  125. package/lib/server/__integration__/test-helpers.ts +84 -70
  126. package/lib/server/ab/generateFunctions.ts +12 -10
  127. package/lib/server/astro/cmsPageEmitter.ts +23 -38
  128. package/lib/server/astro/componentEmitter.ts +25 -36
  129. package/lib/server/astro/cssCollector.ts +10 -26
  130. package/lib/server/astro/nodeToAstro.test.ts +1750 -30
  131. package/lib/server/astro/nodeToAstro.ts +198 -187
  132. package/lib/server/astro/normalizeOrphanTemplateProps.test.ts +105 -109
  133. package/lib/server/astro/normalizeOrphanTemplateProps.ts +4 -12
  134. package/lib/server/astro/pageEmitter.ts +9 -13
  135. package/lib/server/astro/tailwindMapper.test.ts +10 -37
  136. package/lib/server/astro/tailwindMapper.ts +33 -40
  137. package/lib/server/astro/templateTransformer.ts +14 -17
  138. package/lib/server/createServer.ts +5 -17
  139. package/lib/server/cssGenerator.test.ts +35 -44
  140. package/lib/server/cssGenerator.ts +6 -17
  141. package/lib/server/fileWatcher.test.ts +124 -10
  142. package/lib/server/fileWatcher.ts +124 -118
  143. package/lib/server/index.ts +7 -1
  144. package/lib/server/jsonLoader.test.ts +39 -2
  145. package/lib/server/jsonLoader.ts +33 -31
  146. package/lib/server/middleware/cors.test.ts +20 -20
  147. package/lib/server/middleware/cors.ts +28 -4
  148. package/lib/server/middleware/errorHandler.test.ts +5 -3
  149. package/lib/server/middleware/errorHandler.ts +3 -8
  150. package/lib/server/middleware/index.ts +0 -1
  151. package/lib/server/middleware/logger.test.ts +7 -5
  152. package/lib/server/middleware/logger.ts +10 -22
  153. package/lib/server/pageCache.test.ts +76 -77
  154. package/lib/server/pageCache.ts +0 -1
  155. package/lib/server/projectContext.ts +4 -3
  156. package/lib/server/providers/fileSystemCMSProvider.test.ts +124 -95
  157. package/lib/server/providers/fileSystemCMSProvider.ts +35 -20
  158. package/lib/server/providers/fileSystemPageProvider.test.ts +84 -0
  159. package/lib/server/providers/fileSystemPageProvider.ts +31 -12
  160. package/lib/server/routes/api/cms.test.ts +26 -14
  161. package/lib/server/routes/api/cms.ts +9 -14
  162. package/lib/server/routes/api/components.ts +30 -34
  163. package/lib/server/routes/api/config.ts +0 -1
  164. package/lib/server/routes/api/core-routes.ts +47 -74
  165. package/lib/server/routes/api/functions.ts +8 -16
  166. package/lib/server/routes/api/index.ts +3 -6
  167. package/lib/server/routes/api/pages.ts +8 -11
  168. package/lib/server/routes/api/shared.test.ts +1 -1
  169. package/lib/server/routes/api/shared.ts +12 -9
  170. package/lib/server/routes/api/variables.test.ts +1 -3
  171. package/lib/server/routes/api/variables.ts +1 -1
  172. package/lib/server/routes/index.ts +23 -26
  173. package/lib/server/routes/pages.ts +34 -29
  174. package/lib/server/routes/static.ts +16 -4
  175. package/lib/server/runtime/bundler.ts +47 -32
  176. package/lib/server/runtime/fs.ts +3 -13
  177. package/lib/server/runtime/httpServer.ts +5 -9
  178. package/lib/server/services/ColorService.ts +32 -27
  179. package/lib/server/services/EnumService.test.ts +2 -6
  180. package/lib/server/services/EnumService.ts +7 -2
  181. package/lib/server/services/VariableService.test.ts +1 -5
  182. package/lib/server/services/VariableService.ts +6 -1
  183. package/lib/server/services/cmsService.test.ts +116 -78
  184. package/lib/server/services/cmsService.ts +24 -54
  185. package/lib/server/services/componentService.test.ts +122 -36
  186. package/lib/server/services/componentService.ts +54 -38
  187. package/lib/server/services/configService.test.ts +9 -31
  188. package/lib/server/services/configService.ts +20 -27
  189. package/lib/server/services/fileWatcherService.ts +40 -6
  190. package/lib/server/services/index.ts +0 -1
  191. package/lib/server/services/pageService.test.ts +1 -3
  192. package/lib/server/services/pageService.ts +14 -13
  193. package/lib/server/ssr/attributeBuilder.ts +16 -6
  194. package/lib/server/ssr/buildErrorOverlay.ts +11 -12
  195. package/lib/server/ssr/clientDataInjector.ts +7 -21
  196. package/lib/server/ssr/cmsSSRProcessor.ts +3 -6
  197. package/lib/server/ssr/cssCollector.ts +1 -1
  198. package/lib/server/ssr/errorOverlay.test.ts +1 -1
  199. package/lib/server/ssr/errorOverlay.ts +3 -9
  200. package/lib/server/ssr/htmlGenerator.nonce.test.ts +3 -9
  201. package/lib/server/ssr/htmlGenerator.test.ts +120 -43
  202. package/lib/server/ssr/htmlGenerator.ts +120 -85
  203. package/lib/server/ssr/imageMetadata.test.ts +3 -1
  204. package/lib/server/ssr/imageMetadata.ts +25 -19
  205. package/lib/server/ssr/jsCollector.test.ts +3 -13
  206. package/lib/server/ssr/jsCollector.ts +3 -8
  207. package/lib/server/ssr/liveReloadIntegration.test.ts +69 -28
  208. package/lib/server/ssr/metaTagGenerator.ts +2 -2
  209. package/lib/server/ssr/ssrRenderer.branches.test.ts +1103 -0
  210. package/lib/server/ssr/ssrRenderer.test.ts +196 -246
  211. package/lib/server/ssr/ssrRenderer.ts +609 -225
  212. package/lib/server/ssrRenderer.test.ts +1044 -950
  213. package/lib/server/utils/jsonLineMapper.test.ts +28 -28
  214. package/lib/server/utils/jsonLineMapper.ts +1 -1
  215. package/lib/server/validateStyleCoverage.ts +18 -20
  216. package/lib/server/webflow/buildWebflow.ts +41 -53
  217. package/lib/server/webflow/nodeToWebflow.test.ts +150 -218
  218. package/lib/server/webflow/nodeToWebflow.ts +195 -258
  219. package/lib/server/webflow/styleMapper.test.ts +15 -56
  220. package/lib/server/webflow/styleMapper.ts +33 -41
  221. package/lib/server/webflow/types.ts +1 -8
  222. package/lib/server/websocketManager.ts +16 -19
  223. package/lib/shared/attributeNodeUtils.test.ts +15 -15
  224. package/lib/shared/attributeNodeUtils.ts +5 -12
  225. package/lib/shared/breakpoints.ts +4 -11
  226. package/lib/shared/cmsQueryParser.test.ts +50 -42
  227. package/lib/shared/cmsQueryParser.ts +49 -31
  228. package/lib/shared/colorVariableUtils.test.ts +5 -5
  229. package/lib/shared/colorVariableUtils.ts +3 -8
  230. package/lib/shared/componentRefs.ts +1 -5
  231. package/lib/shared/constants.test.ts +3 -3
  232. package/lib/shared/constants.ts +4 -8
  233. package/lib/shared/cssGeneration.test.ts +262 -144
  234. package/lib/shared/cssGeneration.ts +189 -516
  235. package/lib/shared/cssNamedColors.ts +152 -30
  236. package/lib/shared/cssProperties.test.ts +5 -6
  237. package/lib/shared/cssProperties.ts +296 -112
  238. package/lib/shared/elementClassName.test.ts +109 -109
  239. package/lib/shared/elementClassName.ts +1 -1
  240. package/lib/shared/elementUtils.ts +12 -16
  241. package/lib/shared/errorLogger.ts +2 -10
  242. package/lib/shared/errors.test.ts +2 -13
  243. package/lib/shared/errors.ts +2 -8
  244. package/lib/shared/expressionEvaluator.test.ts +119 -0
  245. package/lib/shared/expressionEvaluator.ts +43 -24
  246. package/lib/shared/fontLoader.test.ts +19 -5
  247. package/lib/shared/friendlyError.ts +2 -3
  248. package/lib/shared/gradientUtils.test.ts +1 -5
  249. package/lib/shared/gradientUtils.ts +2 -6
  250. package/lib/shared/hrefRefs.ts +2 -10
  251. package/lib/shared/i18n.test.ts +1 -1
  252. package/lib/shared/i18n.ts +13 -34
  253. package/lib/shared/index.ts +4 -0
  254. package/lib/shared/inlineSvgStyleRules.ts +2 -4
  255. package/lib/shared/interactiveStyleMappings.test.ts +11 -33
  256. package/lib/shared/interactiveStyleMappings.ts +9 -16
  257. package/lib/shared/interactiveStyles.test.ts +165 -188
  258. package/lib/shared/interfaces/contentProvider.ts +1 -1
  259. package/lib/shared/itemTemplateUtils.test.ts +6 -12
  260. package/lib/shared/itemTemplateUtils.ts +19 -35
  261. package/lib/shared/jsonRepair.ts +8 -2
  262. package/lib/shared/libraryLoader.test.ts +15 -49
  263. package/lib/shared/libraryLoader.ts +7 -22
  264. package/lib/shared/netlifyLocale404.test.ts +179 -0
  265. package/lib/shared/netlifyLocale404.ts +110 -0
  266. package/lib/shared/nodeUtils.test.ts +24 -16
  267. package/lib/shared/nodeUtils.ts +49 -19
  268. package/lib/shared/pathArrayUtils.test.ts +1 -2
  269. package/lib/shared/pathArrayUtils.ts +1 -1
  270. package/lib/shared/pathSecurity.ts +1 -1
  271. package/lib/shared/pathUtils.test.ts +4 -6
  272. package/lib/shared/pathUtils.ts +42 -48
  273. package/lib/shared/paths/Path.test.ts +2 -2
  274. package/lib/shared/paths/Path.ts +0 -1
  275. package/lib/shared/paths/PathConverter.test.ts +1 -1
  276. package/lib/shared/paths/PathConverter.ts +14 -17
  277. package/lib/shared/paths/PathUtils.ts +9 -10
  278. package/lib/shared/paths/PathValidator.test.ts +2 -15
  279. package/lib/shared/paths/PathValidator.ts +11 -9
  280. package/lib/shared/paths/index.ts +1 -2
  281. package/lib/shared/propResolver.test.ts +240 -244
  282. package/lib/shared/propResolver.ts +14 -25
  283. package/lib/shared/pxToRem.test.ts +7 -6
  284. package/lib/shared/pxToRem.ts +2 -5
  285. package/lib/shared/registry/BaseNodeTypeRegistry.test.ts +9 -5
  286. package/lib/shared/registry/ClientRegistry.ts +0 -1
  287. package/lib/shared/registry/ComponentRegistry.test.ts +43 -29
  288. package/lib/shared/registry/ComponentRegistry.ts +9 -11
  289. package/lib/shared/registry/NodeTypeDefinition.ts +15 -8
  290. package/lib/shared/registry/RegistryManager.ts +1 -2
  291. package/lib/shared/registry/SSRRegistry.ts +0 -1
  292. package/lib/shared/registry/createNodeType.ts +7 -9
  293. package/lib/shared/registry/defineNodeType.ts +2 -6
  294. package/lib/shared/registry/index.ts +0 -1
  295. package/lib/shared/registry/nodeTypes/ComponentInstanceNodeType.ts +14 -15
  296. package/lib/shared/registry/nodeTypes/EmbedNodeType.ts +18 -11
  297. package/lib/shared/registry/nodeTypes/HtmlNodeType.ts +47 -18
  298. package/lib/shared/registry/nodeTypes/LinkNodeType.ts +21 -19
  299. package/lib/shared/registry/nodeTypes/ListNodeType.ts +78 -74
  300. package/lib/shared/registry/nodeTypes/LocaleListNodeType.ts +27 -21
  301. package/lib/shared/registry/nodeTypes/SlotMarkerType.ts +6 -7
  302. package/lib/shared/registry/nodeTypes/index.ts +10 -2
  303. package/lib/shared/responsiveScaling.test.ts +15 -31
  304. package/lib/shared/responsiveScaling.ts +55 -37
  305. package/lib/shared/responsiveStyleUtils.ts +11 -13
  306. package/lib/shared/richtext/htmlToTiptap.test.ts +23 -14
  307. package/lib/shared/richtext/htmlToTiptap.ts +1 -3
  308. package/lib/shared/richtext/tiptapToHtml.test.ts +5 -6
  309. package/lib/shared/richtext/types.ts +1 -8
  310. package/lib/shared/slugTranslator.test.ts +13 -13
  311. package/lib/shared/slugTranslator.ts +12 -16
  312. package/lib/shared/slugify.ts +9 -15
  313. package/lib/shared/styleNodeUtils.test.ts +8 -8
  314. package/lib/shared/styleNodeUtils.ts +5 -10
  315. package/lib/shared/styleUtils.test.ts +87 -61
  316. package/lib/shared/styleUtils.ts +5 -6
  317. package/lib/shared/themeDefaults.test.ts +11 -11
  318. package/lib/shared/themeDefaults.ts +3 -4
  319. package/lib/shared/tree/PathBuilder.test.ts +62 -138
  320. package/lib/shared/tree/PathBuilder.ts +49 -39
  321. package/lib/shared/treePathUtils.test.ts +2 -10
  322. package/lib/shared/treePathUtils.ts +54 -59
  323. package/lib/shared/types/api.ts +1 -2
  324. package/lib/shared/types/cms.ts +25 -21
  325. package/lib/shared/types/comment.ts +49 -16
  326. package/lib/shared/types/components.ts +27 -25
  327. package/lib/shared/types/errors.test.ts +1 -6
  328. package/lib/shared/types/errors.ts +3 -7
  329. package/lib/shared/types/experiments.ts +28 -28
  330. package/lib/shared/types/index.ts +2 -2
  331. package/lib/shared/types/styles.ts +0 -1
  332. package/lib/shared/types/variables.test.ts +4 -13
  333. package/lib/shared/types/variables.ts +48 -27
  334. package/lib/shared/types.ts +1 -2
  335. package/lib/shared/utilityClassConfig.ts +648 -321
  336. package/lib/shared/utilityClassMapper.test.ts +203 -92
  337. package/lib/shared/utilityClassMapper.ts +188 -246
  338. package/lib/shared/utilityClassNames.ts +326 -0
  339. package/lib/shared/utils.test.ts +2 -10
  340. package/lib/shared/utils.ts +19 -10
  341. package/lib/shared/validation/cmsValidators.ts +2 -1
  342. package/lib/shared/validation/commentValidators.test.ts +53 -0
  343. package/lib/shared/validation/commentValidators.ts +12 -1
  344. package/lib/shared/validation/propValidator.test.ts +18 -20
  345. package/lib/shared/validation/propValidator.ts +12 -17
  346. package/lib/shared/validation/schemas.test.ts +24 -33
  347. package/lib/shared/validation/schemas.ts +469 -344
  348. package/lib/shared/validation/validators.test.ts +1 -6
  349. package/lib/shared/validation/validators.ts +89 -68
  350. package/lib/shared/viewportUnits.integration.test.ts +14 -10
  351. package/lib/shared/viewportUnits.test.ts +11 -23
  352. package/lib/test-utils/dom-setup.ts +1 -1
  353. package/lib/test-utils/factories/ConsoleMockFactory.ts +3 -7
  354. package/lib/test-utils/factories/DomMockFactory.ts +7 -19
  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 +5 -9
  358. package/lib/test-utils/factories/StoreMockFactory.ts +14 -25
  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 +12 -18
  365. package/lib/test-utils/mocks.ts +4 -2
  366. package/package.json +1 -1
  367. package/scripts/build-meno-filter.ts +1 -4
  368. package/vite.config.ts +4 -4
  369. package/dist/chunks/chunk-2MHDV5BF.js.map +0 -7
  370. package/dist/chunks/chunk-AZQYF6KE.js.map +0 -7
  371. package/dist/chunks/chunk-GZHGVVW3.js.map +0 -7
  372. package/dist/chunks/chunk-H3GJ4H2U.js.map +0 -7
  373. package/dist/chunks/chunk-I7YIGZXT.js.map +0 -7
  374. package/dist/chunks/chunk-IGYR22T6.js.map +0 -7
  375. package/dist/chunks/chunk-JGP5A3Y5.js.map +0 -7
  376. package/dist/chunks/chunk-JGWFTO6P.js.map +0 -7
  377. package/dist/chunks/chunk-O3NAGJP4.js.map +0 -7
  378. package/dist/chunks/chunk-QB2LNO4W.js.map +0 -7
  379. package/dist/chunks/chunk-R6XHAFBF.js.map +0 -7
  380. package/dist/chunks/chunk-UB44F4Z2.js.map +0 -7
  381. package/dist/chunks/chunk-WQFG7PAH.js.map +0 -7
  382. package/dist/chunks/chunk-X754AHS5.js.map +0 -7
  383. package/dist/chunks/chunk-YBLHKYFF.js.map +0 -7
  384. package/dist/chunks/configService-R3OGU2UD.js +0 -13
  385. /package/dist/chunks/{configService-R3OGU2UD.js.map → configService-PRJZF7Y6.js.map} +0 -0
  386. /package/dist/chunks/{constants-STK2YBIW.js.map → constants-KIQEYMAM.js.map} +0 -0
  387. /package/dist/chunks/{fs-JGINUXGL.js.map → fs-ZI5JEU7V.js.map} +0 -0
@@ -0,0 +1,119 @@
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, 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
+ });
@@ -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
@@ -73,18 +73,32 @@ function evaluateNode(node: Expression, context: Record<string, unknown>): unkno
73
73
  return String(left ?? '') + String(right ?? '');
74
74
  }
75
75
  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);
76
+ case '-':
77
+ return (left as number) - (right as number);
78
+ case '*':
79
+ return (left as number) * (right as number);
80
+ case '/':
81
+ return (left as number) / (right as number);
82
+ case '%':
83
+ return (left as number) % (right as number);
84
+ case '===':
85
+ return left === right;
86
+ case '!==':
87
+ return left !== right;
88
+ case '==':
89
+ // biome-ignore lint/suspicious/noDoubleEquals: loose equality IS the semantics of the `==` operator in template expressions
90
+ return left == right;
91
+ case '!=':
92
+ // biome-ignore lint/suspicious/noDoubleEquals: loose inequality IS the semantics of the `!=` operator in template expressions
93
+ return left != right;
94
+ case '<':
95
+ return (left as number) < (right as number);
96
+ case '>':
97
+ return (left as number) > (right as number);
98
+ case '<=':
99
+ return (left as number) <= (right as number);
100
+ case '>=':
101
+ return (left as number) >= (right as number);
88
102
  default:
89
103
  throw new Error(`Unsupported operator: ${binaryNode.operator}`);
90
104
  }
@@ -94,9 +108,7 @@ function evaluateNode(node: Expression, context: Record<string, unknown>): unkno
94
108
  // Ternary: condition ? consequent : alternate
95
109
  const condNode = node as jsep.ConditionalExpression;
96
110
  const test = evaluateNode(condNode.test, context);
97
- return test
98
- ? evaluateNode(condNode.consequent, context)
99
- : evaluateNode(condNode.alternate, context);
111
+ return test ? evaluateNode(condNode.consequent, context) : evaluateNode(condNode.alternate, context);
100
112
  }
101
113
 
102
114
  case 'UnaryExpression': {
@@ -105,9 +117,12 @@ function evaluateNode(node: Expression, context: Record<string, unknown>): unkno
105
117
  const arg = evaluateNode(unaryNode.argument, context);
106
118
 
107
119
  switch (unaryNode.operator) {
108
- case '!': return !arg;
109
- case '-': return -(arg as number);
110
- case '+': return +(arg as number);
120
+ case '!':
121
+ return !arg;
122
+ case '-':
123
+ return -(arg as number);
124
+ case '+':
125
+ return +(arg as number);
111
126
  default:
112
127
  throw new Error(`Unsupported unary operator: ${unaryNode.operator}`);
113
128
  }
@@ -116,9 +131,7 @@ function evaluateNode(node: Expression, context: Record<string, unknown>): unkno
116
131
  case 'ArrayExpression': {
117
132
  // Array literals: [1, 2, 3]
118
133
  const arrayNode = node as jsep.ArrayExpression;
119
- return arrayNode.elements.map(el =>
120
- el ? evaluateNode(el, context) : undefined
121
- );
134
+ return arrayNode.elements.map((el) => (el ? evaluateNode(el, context) : undefined));
122
135
  }
123
136
 
124
137
  case 'CallExpression': {
@@ -160,8 +173,14 @@ export function isComplexExpression(expression: string): boolean {
160
173
  return /[?:+\-*/%<>=!&|]/.test(expression);
161
174
  }
162
175
 
163
- /** jsep node types `evaluateNode` (above) can evaluate — the rest throw at runtime. */
164
- const SUPPORTED_NODE_TYPES = new Set([
176
+ /**
177
+ * jsep node types `evaluateNode` (above) can evaluate — the rest throw at runtime.
178
+ *
179
+ * SSOT: this set MUST stay in lockstep with the `case` labels in `evaluateNode`'s
180
+ * switch. Drift is silent (lost code or literal `{{…}}` in SSR output), so the
181
+ * invariant is pinned by expressionEvaluator.test.ts — update both together.
182
+ */
183
+ export const SUPPORTED_NODE_TYPES = new Set([
165
184
  'Identifier',
166
185
  'Literal',
167
186
  'MemberExpression',
@@ -1,4 +1,4 @@
1
- import { describe, test, expect, afterAll } from 'bun:test';
1
+ import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
2
2
  import { join } from 'path';
3
3
  import { mkdirSync, writeFileSync, rmSync } from 'fs';
4
4
  import { tmpdir } from 'os';
@@ -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
@@ -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
 
@@ -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', () => {
@@ -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 */
@@ -94,9 +92,7 @@ export function parseGradient(cssValue: string): GradientConfig | null {
94
92
 
95
93
  /** Build a CSS gradient string from a GradientConfig */
96
94
  export function buildGradientCss(config: GradientConfig): string {
97
- const stopStrings = config.stops.map(
98
- s => `${s.color} ${Math.round(s.position)}%`
99
- );
95
+ const stopStrings = config.stops.map((s) => `${s.color} ${Math.round(s.position)}%`);
100
96
 
101
97
  switch (config.type) {
102
98
  case 'linear': {
@@ -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
 
@@ -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;
@@ -10,9 +10,7 @@ import type { I18nValue, I18nConfig, LocaleConfig } from './types/components';
10
10
  */
11
11
  export const DEFAULT_I18N_CONFIG: I18nConfig = {
12
12
  defaultLocale: 'en',
13
- locales: [
14
- { code: 'en', name: 'English', nativeName: 'English', langTag: 'en-US' }
15
- ],
13
+ locales: [{ code: 'en', name: 'English', nativeName: 'English', langTag: 'en-US' }],
16
14
  };
17
15
 
18
16
  // ============================================
@@ -23,21 +21,21 @@ export const DEFAULT_I18N_CONFIG: I18nConfig = {
23
21
  * Get array of locale codes from config
24
22
  */
25
23
  export function getLocaleCodes(config: I18nConfig): string[] {
26
- return config.locales.map(loc => loc.code);
24
+ return config.locales.map((loc) => loc.code);
27
25
  }
28
26
 
29
27
  /**
30
28
  * Find a locale config by its code
31
29
  */
32
30
  export function findLocaleByCode(config: I18nConfig, code: string): LocaleConfig | undefined {
33
- return config.locales.find(loc => loc.code === code);
31
+ return config.locales.find((loc) => loc.code === code);
34
32
  }
35
33
 
36
34
  /**
37
35
  * Check if a locale code is valid/exists in config
38
36
  */
39
37
  export function isValidLocaleCode(config: I18nConfig, code: string): boolean {
40
- return config.locales.some(loc => loc.code === code);
38
+ return config.locales.some((loc) => loc.code === code);
41
39
  }
42
40
 
43
41
  // ============================================
@@ -112,11 +110,7 @@ export function isI18nValue(value: unknown): value is I18nValue {
112
110
  * Fallback order: exact locale -> default locale -> first available -> empty
113
111
  * Works with any value type (strings, arrays for list props, etc.)
114
112
  */
115
- export function resolveTranslation(
116
- value: I18nValue,
117
- locale: string,
118
- config: I18nConfig
119
- ): unknown {
113
+ export function resolveTranslation(value: I18nValue, locale: string, config: I18nConfig): unknown {
120
114
  // Try exact locale match (any type - string, array, etc.)
121
115
  if (locale in value && value[locale] !== undefined && locale !== '_i18n') {
122
116
  return value[locale];
@@ -136,9 +130,7 @@ export function resolveTranslation(
136
130
 
137
131
  // Return empty string for string i18n, empty array for list i18n
138
132
  // Check if any value is an array to determine the type
139
- const hasArrayValue = Object.entries(value).some(
140
- ([k, v]) => k !== '_i18n' && Array.isArray(v)
141
- );
133
+ const hasArrayValue = Object.entries(value).some(([k, v]) => k !== '_i18n' && Array.isArray(v));
142
134
  return hasArrayValue ? [] : '';
143
135
  }
144
136
 
@@ -146,11 +138,7 @@ export function resolveTranslation(
146
138
  * Resolve a value that might be an I18nValue or a regular value
147
139
  * Returns the original value if not an I18nValue
148
140
  */
149
- export function resolveI18nValue(
150
- value: unknown,
151
- locale: string,
152
- config: I18nConfig
153
- ): unknown {
141
+ export function resolveI18nValue(value: unknown, locale: string, config: I18nConfig): unknown {
154
142
  if (isI18nValue(value)) {
155
143
  return resolveTranslation(value, locale, config);
156
144
  }
@@ -163,7 +151,7 @@ export function resolveI18nValue(
163
151
  export function resolveI18nInProps(
164
152
  props: Record<string, unknown>,
165
153
  locale: string,
166
- config: I18nConfig
154
+ config: I18nConfig,
167
155
  ): Record<string, unknown> {
168
156
  const resolved: Record<string, unknown> = {};
169
157
 
@@ -171,16 +159,10 @@ export function resolveI18nInProps(
171
159
  if (isI18nValue(value)) {
172
160
  resolved[key] = resolveTranslation(value, locale, config);
173
161
  } else if (Array.isArray(value)) {
174
- resolved[key] = value.map((item) =>
175
- isI18nValue(item) ? resolveTranslation(item, locale, config) : item
176
- );
162
+ resolved[key] = value.map((item) => (isI18nValue(item) ? resolveTranslation(item, locale, config) : item));
177
163
  } else if (typeof value === 'object' && value !== null) {
178
164
  // Recursively resolve nested objects (but not I18nValue which is already handled)
179
- resolved[key] = resolveI18nInProps(
180
- value as Record<string, unknown>,
181
- locale,
182
- config
183
- );
165
+ resolved[key] = resolveI18nInProps(value as Record<string, unknown>, locale, config);
184
166
  } else {
185
167
  resolved[key] = value;
186
168
  }
@@ -195,7 +177,7 @@ export function resolveI18nInProps(
195
177
  */
196
178
  export function extractLocaleFromPath(
197
179
  path: string,
198
- config: I18nConfig
180
+ config: I18nConfig,
199
181
  ): { locale: string | null; pathWithoutLocale: string } {
200
182
  // Remove leading slash and split
201
183
  const cleanPath = path.startsWith('/') ? path.slice(1) : path;
@@ -235,17 +217,14 @@ export interface LocaleContext {
235
217
  * Parse locale from path and return full context with effective locale
236
218
  * Combines extractLocaleFromPath + defaultLocale resolution in one call
237
219
  */
238
- export function parseLocaleFromPath(
239
- path: string,
240
- config: I18nConfig
241
- ): LocaleContext {
220
+ export function parseLocaleFromPath(path: string, config: I18nConfig): LocaleContext {
242
221
  const { locale, pathWithoutLocale } = extractLocaleFromPath(path, config);
243
222
  const effectiveLocale = locale || config.defaultLocale;
244
223
 
245
224
  return {
246
225
  locale: effectiveLocale,
247
226
  pathWithoutLocale,
248
- isDefaultLocale: effectiveLocale === config.defaultLocale
227
+ isDefaultLocale: effectiveLocale === config.defaultLocale,
249
228
  };
250
229
  }
251
230
 
@@ -116,6 +116,10 @@ export { isSupportedTemplateExpression } from './expressionEvaluator';
116
116
  // Path security utilities
117
117
  export * from './pathSecurity';
118
118
 
119
+ // Netlify locale-scoped 404 rules + the managed netlify.toml block (shared by
120
+ // every netlify.toml producer and the studio save-config sync)
121
+ export * from './netlifyLocale404';
122
+
119
123
  // Interactive style mapping utilities
120
124
  export * from './interactiveStyleMappings';
121
125
 
@@ -59,9 +59,7 @@ function rewriteSvgBlock(svgBlock: string): string {
59
59
  const rawContent = m[1];
60
60
  // Strip CDATA wrapper if present (DOMPurify already unwraps it, but the
61
61
  // pre-sanitize call path may see one).
62
- const content = stripCssComments(
63
- rawContent.replace(/^\s*<!\[CDATA\[/, '').replace(/\]\]>\s*$/, '')
64
- );
62
+ const content = stripCssComments(rawContent.replace(/^\s*<!\[CDATA\[/, '').replace(/\]\]>\s*$/, ''));
65
63
 
66
64
  // Pull out every simple `.cls { ... }` rule we can match.
67
65
  const localClasses = new Map<string, string>();
@@ -99,7 +97,7 @@ function rewriteSvgBlock(svgBlock: string): string {
99
97
  for (const [className, decls] of classDeclarations) {
100
98
  const elementRe = new RegExp(
101
99
  `(<[A-Za-z][\\w-]*\\b[^>]*?\\sclass="[^"]*\\b${escapeForRegex(className)}\\b[^"]*"[^>]*?)(/?>)`,
102
- 'g'
100
+ 'g',
103
101
  );
104
102
  out = out.replace(elementRe, (_full, openHead: string, closing: string) => {
105
103
  const styleAttrMatch = openHead.match(/\sstyle="([^"]*)"/);
@@ -46,9 +46,7 @@ describe('interactiveStyleMappings', () => {
46
46
  });
47
47
 
48
48
  it('returns false when no mappings present', () => {
49
- const styles: InteractiveStyles = [
50
- { postfix: ':hover', style: { base: { backgroundColor: '#0070f3' } } },
51
- ];
49
+ const styles: InteractiveStyles = [{ postfix: ':hover', style: { base: { backgroundColor: '#0070f3' } } }];
52
50
  expect(hasInteractiveStyleMappings(styles)).toBe(false);
53
51
  });
54
52
 
@@ -89,9 +87,7 @@ describe('interactiveStyleMappings', () => {
89
87
 
90
88
  describe('extractInteractiveStyleMappings', () => {
91
89
  it('returns unchanged styles when no mappings', () => {
92
- const styles: InteractiveStyles = [
93
- { postfix: ':hover', style: { base: { backgroundColor: '#0070f3' } } },
94
- ];
90
+ const styles: InteractiveStyles = [{ postfix: ':hover', style: { base: { backgroundColor: '#0070f3' } } }];
95
91
 
96
92
  const { resolvedStyles, mappings } = extractInteractiveStyleMappings(styles);
97
93
 
@@ -106,9 +102,7 @@ describe('interactiveStyleMappings', () => {
106
102
  values: { primary: '#0070f3', secondary: '#gray' },
107
103
  };
108
104
 
109
- const styles: InteractiveStyles = [
110
- { postfix: ':hover', style: { backgroundColor: mapping } },
111
- ];
105
+ const styles: InteractiveStyles = [{ postfix: ':hover', style: { backgroundColor: mapping } }];
112
106
 
113
107
  const { resolvedStyles, mappings } = extractInteractiveStyleMappings(styles);
114
108
 
@@ -133,9 +127,7 @@ describe('interactiveStyleMappings', () => {
133
127
  values: { primary: '#0070f3' },
134
128
  };
135
129
 
136
- const styles: InteractiveStyles = [
137
- { postfix: ':hover', style: { base: { backgroundColor: mapping } } },
138
- ];
130
+ const styles: InteractiveStyles = [{ postfix: ':hover', style: { base: { backgroundColor: mapping } } }];
139
131
 
140
132
  const { resolvedStyles, mappings } = extractInteractiveStyleMappings(styles);
141
133
 
@@ -287,9 +279,7 @@ describe('interactiveStyleMappings', () => {
287
279
  values: { primary: '#0070f3' },
288
280
  };
289
281
 
290
- const styles: InteractiveStyles = [
291
- { postfix: ':hover', style: { base: { backgroundColor: mapping } } },
292
- ];
282
+ const styles: InteractiveStyles = [{ postfix: ':hover', style: { base: { backgroundColor: mapping } } }];
293
283
 
294
284
  const { resolvedStyles } = extractInteractiveStyleMappings(styles);
295
285
 
@@ -305,9 +295,7 @@ describe('interactiveStyleMappings', () => {
305
295
  values: { primary: '#0070f3', secondary: '#gray' },
306
296
  };
307
297
 
308
- const styles: InteractiveStyles = [
309
- { postfix: ':hover', style: { base: { backgroundColor: mapping } } },
310
- ];
298
+ const styles: InteractiveStyles = [{ postfix: ':hover', style: { base: { backgroundColor: mapping } } }];
311
299
 
312
300
  const { mappings } = extractInteractiveStyleMappings(styles);
313
301
  const cssVariables = resolveExtractedMappings(mappings, { variant: 'primary' });
@@ -351,9 +339,7 @@ describe('interactiveStyleMappings', () => {
351
339
  values: { primary: '#0070f3', secondary: '#gray' },
352
340
  };
353
341
 
354
- const styles: InteractiveStyles = [
355
- { postfix: ':hover', style: { base: { backgroundColor: mapping } } },
356
- ];
342
+ const styles: InteractiveStyles = [{ postfix: ':hover', style: { base: { backgroundColor: mapping } } }];
357
343
 
358
344
  const { mappings } = extractInteractiveStyleMappings(styles);
359
345
  const cssVariables = resolveExtractedMappings(mappings, { variant: 'purple' });
@@ -368,9 +354,7 @@ describe('interactiveStyleMappings', () => {
368
354
  values: { primary: '#0070f3' },
369
355
  };
370
356
 
371
- const styles: InteractiveStyles = [
372
- { postfix: ':hover', style: { base: { backgroundColor: mapping } } },
373
- ];
357
+ const styles: InteractiveStyles = [{ postfix: ':hover', style: { base: { backgroundColor: mapping } } }];
374
358
 
375
359
  const { mappings } = extractInteractiveStyleMappings(styles);
376
360
  const cssVariables = resolveExtractedMappings(mappings, {});
@@ -385,9 +369,7 @@ describe('interactiveStyleMappings', () => {
385
369
  values: { primary: '#0070f3' },
386
370
  };
387
371
 
388
- const styles: InteractiveStyles = [
389
- { postfix: ':hover', style: { base: { backgroundColor: mapping } } },
390
- ];
372
+ const styles: InteractiveStyles = [{ postfix: ':hover', style: { base: { backgroundColor: mapping } } }];
391
373
 
392
374
  const { mappings } = extractInteractiveStyleMappings(styles);
393
375
  const cssVariables = resolveExtractedMappings(mappings, { variant: null });
@@ -402,9 +384,7 @@ describe('interactiveStyleMappings', () => {
402
384
  values: { true: '0.5', false: '1' },
403
385
  };
404
386
 
405
- const styles: InteractiveStyles = [
406
- { postfix: ':hover', style: { base: { opacity: mapping } } },
407
- ];
387
+ const styles: InteractiveStyles = [{ postfix: ':hover', style: { base: { opacity: mapping } } }];
408
388
 
409
389
  const { mappings } = extractInteractiveStyleMappings(styles);
410
390
  const cssVariables = resolveExtractedMappings(mappings, { disabled: true });
@@ -419,9 +399,7 @@ describe('interactiveStyleMappings', () => {
419
399
  values: { '1': '10px', '2': '20px', '3': '30px' },
420
400
  };
421
401
 
422
- const styles: InteractiveStyles = [
423
- { postfix: ':hover', style: { base: { padding: mapping } } },
424
- ];
402
+ const styles: InteractiveStyles = [{ postfix: ':hover', style: { base: { padding: mapping } } }];
425
403
 
426
404
  const { mappings } = extractInteractiveStyleMappings(styles);
427
405
  const cssVariables = resolveExtractedMappings(mappings, { size: 2 });