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
@@ -356,17 +356,11 @@ describe('itemTemplateUtils', () => {
356
356
 
357
357
  it('should process nested arrays', () => {
358
358
  const props = {
359
- outer: [
360
- ['{{item.title}}', 'static'],
361
- ['{{item.url}}'],
362
- ]
359
+ outer: [['{{item.title}}', 'static'], ['{{item.url}}']],
363
360
  };
364
361
  const result = processItemPropsTemplate(props, baseContext);
365
362
  expect(result).toEqual({
366
- outer: [
367
- ['Test Title', 'static'],
368
- ['/test-url'],
369
- ]
363
+ outer: [['Test Title', 'static'], ['/test-url']],
370
364
  });
371
365
  });
372
366
 
@@ -375,24 +369,24 @@ describe('itemTemplateUtils', () => {
375
369
  items: [
376
370
  { label: '{{item.title}}', href: '{{item.url}}' },
377
371
  { label: 'Static', href: '/static' },
378
- ]
372
+ ],
379
373
  };
380
374
  const result = processItemPropsTemplate(props, baseContext);
381
375
  expect(result).toEqual({
382
376
  items: [
383
377
  { label: 'Test Title', href: '/test-url' },
384
378
  { label: 'Static', href: '/static' },
385
- ]
379
+ ],
386
380
  });
387
381
  });
388
382
 
389
383
  it('should handle mixed array content', () => {
390
384
  const props = {
391
- tags: ['{{item.title}}', 42, true, null, { nested: '{{item.url}}' }]
385
+ tags: ['{{item.title}}', 42, true, null, { nested: '{{item.url}}' }],
392
386
  };
393
387
  const result = processItemPropsTemplate(props, baseContext);
394
388
  expect(result).toEqual({
395
- tags: ['Test Title', 42, true, null, { nested: '/test-url' }]
389
+ tags: ['Test Title', 42, true, null, { nested: '/test-url' }],
396
390
  });
397
391
  });
398
392
 
@@ -770,16 +764,16 @@ describe('itemTemplateUtils', () => {
770
764
  ];
771
765
  const results = addItemUrls(items, schema);
772
766
  expect(results).toHaveLength(3);
773
- expect(results[0]._url).toBe('/blog/first-post');
774
- expect(results[1]._url).toBe('/blog/second-post');
775
- expect(results[2]._url).toBe('/blog/third-post');
767
+ expect(results[0]!._url).toBe('/blog/first-post');
768
+ expect(results[1]!._url).toBe('/blog/second-post');
769
+ expect(results[2]!._url).toBe('/blog/third-post');
776
770
  });
777
771
 
778
772
  it('should not mutate original items', () => {
779
773
  const items: CMSItem[] = [{ _id: '1', slug: 'test' }];
780
774
  const results = addItemUrls(items, schema);
781
- expect(items[0]._url).toBeUndefined();
782
- expect(results[0]._url).toBe('/blog/test');
775
+ expect(items[0]!._url).toBeUndefined();
776
+ expect(results[0]!._url).toBe('/blog/test');
783
777
  });
784
778
 
785
779
  it('should handle empty array', () => {
@@ -21,7 +21,6 @@
21
21
 
22
22
  import type { ItemContext, TemplateContext, CMSItem, CMSSchema } from './types/cms';
23
23
  import type { ValueResolver, I18nConfig } from './types/components';
24
- import { isRichTextMarker, richTextMarkerToHtml } from './propResolver';
25
24
  import { safeEvaluate, isComplexExpression } from './expressionEvaluator';
26
25
  import { getGlobalTemplateContext } from './globalTemplateContext';
27
26
  import { isI18nValue, resolveI18nValue, DEFAULT_I18N_CONFIG } from './i18n';
@@ -85,7 +84,11 @@ function formatTemplateValue(value: unknown, resolveValue?: ValueResolver): stri
85
84
  return '';
86
85
  }
87
86
  // Handle rich-text markers
88
- if (typeof value === 'object' && '__richtext__' in value && typeof (value as unknown as { html: string }).html === 'string') {
87
+ if (
88
+ typeof value === 'object' &&
89
+ '__richtext__' in value &&
90
+ typeof (value as unknown as { html: string }).html === 'string'
91
+ ) {
89
92
  return (value as unknown as { html: string }).html;
90
93
  }
91
94
  return String(value);
@@ -120,10 +123,10 @@ function buildEvalContext(ctx: ItemContext | TemplateContext): Record<string, un
120
123
  export function processItemTemplate(
121
124
  text: string,
122
125
  ctx: ItemContext | TemplateContext,
123
- resolveValue?: ValueResolver
126
+ resolveValue?: ValueResolver,
124
127
  ): string {
125
128
  // Process all {{...}} patterns
126
- let result = text.replace(/\{\{([^}]+)\}\}/g, (match, expression) => {
129
+ const result = text.replace(/\{\{([^}]+)\}\}/g, (match, expression) => {
127
130
  const trimmed = expression.trim();
128
131
 
129
132
  // Check if this is a complex expression (has operators like ?, :, <, >, etc.)
@@ -172,11 +175,7 @@ export function processItemTemplate(
172
175
  * Process named context templates like {{post.title}}, {{postIndex}}, etc.
173
176
  * Supports multiple named contexts for nested lists (e.g., post + author).
174
177
  */
175
- function processNamedTemplates(
176
- text: string,
177
- ctx: TemplateContext,
178
- resolveValue?: ValueResolver
179
- ): string {
178
+ function _processNamedTemplates(text: string, ctx: TemplateContext, resolveValue?: ValueResolver): string {
180
179
  let result = text;
181
180
 
182
181
  // Process {{varName.field}} patterns
@@ -211,13 +210,14 @@ function processNamedTemplates(
211
210
  function resolveCompleteTemplateRaw(
212
211
  template: string,
213
212
  ctx: ItemContext | TemplateContext,
214
- resolveValue?: ValueResolver
213
+ resolveValue?: ValueResolver,
215
214
  ): unknown {
216
215
  // Only handle single complete template expressions like {{varName.field}}
217
216
  const match = template.match(/^\{\{(\w+)\.([^}]+)\}\}$/);
218
217
  if (!match) return undefined;
219
218
 
220
- const [, varName, fieldPath] = match;
219
+ const varName = match[1] ?? '';
220
+ const fieldPath = match[2] ?? '';
221
221
 
222
222
  // Check for complex expressions (ternary, comparisons, etc.) - not raw-resolvable
223
223
  const trimmedField = fieldPath.trim();
@@ -250,7 +250,7 @@ function resolveCompleteTemplateRaw(
250
250
  export function processItemPropsTemplate(
251
251
  props: Record<string, unknown>,
252
252
  ctx: ItemContext | TemplateContext,
253
- resolveValue?: ValueResolver
253
+ resolveValue?: ValueResolver,
254
254
  ): Record<string, unknown> {
255
255
  const result: Record<string, unknown> = {};
256
256
 
@@ -267,7 +267,7 @@ export function processItemPropsTemplate(
267
267
  }
268
268
  } else if (Array.isArray(value)) {
269
269
  // Process arrays - recursively handle each element
270
- result[key] = value.map(item => {
270
+ result[key] = value.map((item) => {
271
271
  if (typeof item === 'string' && hasItemTemplates(item)) {
272
272
  return processItemTemplate(item, ctx, resolveValue);
273
273
  } else if (item && typeof item === 'object' && !Array.isArray(item)) {
@@ -300,10 +300,7 @@ export function processItemPropsTemplate(
300
300
  * resolveItemsTemplate("{{post.tagIds}}", { post: { tagIds: ["tag-1", "tag-2"] } })
301
301
  * // Returns: ["tag-1", "tag-2"]
302
302
  */
303
- export function resolveItemsTemplate(
304
- template: string,
305
- ctx: TemplateContext
306
- ): string | string[] | undefined {
303
+ export function resolveItemsTemplate(template: string, ctx: TemplateContext): string | string[] | undefined {
307
304
  // Match single template expression: {{varName.field}}
308
305
  const match = template.match(/^\{\{(\w+)\.([^}]+)\}\}$/);
309
306
  if (!match) {
@@ -311,7 +308,8 @@ export function resolveItemsTemplate(
311
308
  return template;
312
309
  }
313
310
 
314
- const [, varName, fieldPath] = match;
311
+ const varName = match[1] ?? '';
312
+ const fieldPath = match[2] ?? '';
315
313
  const item = ctx[varName];
316
314
  if (!item || typeof item !== 'object') {
317
315
  return undefined;
@@ -324,7 +322,7 @@ export function resolveItemsTemplate(
324
322
 
325
323
  // Return as string or string array
326
324
  if (Array.isArray(value)) {
327
- return value.map(v => String(v));
325
+ return value.map((v) => String(v));
328
326
  }
329
327
  return String(value);
330
328
  }
@@ -338,15 +336,13 @@ export function resolveItemsTemplate(
338
336
  * resolveTemplateRawValue("{{article.link}}", { article: { link: { href: "/about", target: "_blank" } } })
339
337
  * // Returns: { href: "/about", target: "_blank" }
340
338
  */
341
- export function resolveTemplateRawValue(
342
- template: string,
343
- ctx: TemplateContext
344
- ): unknown {
339
+ export function resolveTemplateRawValue(template: string, ctx: TemplateContext): unknown {
345
340
  // Match single template expression: {{varName.field}}
346
341
  const match = template.match(/^\{\{(\w+)\.([^}]+)\}\}$/);
347
342
  if (!match) return undefined;
348
343
 
349
- const [, varName, fieldPath] = match;
344
+ const varName = match[1] ?? '';
345
+ const fieldPath = match[2] ?? '';
350
346
  const item = ctx[varName];
351
347
  if (!item || typeof item !== 'object') return undefined;
352
348
 
@@ -368,7 +364,7 @@ export function buildTemplateContext(
368
364
  item: CMSItem,
369
365
  index: number,
370
366
  total: number,
371
- parentContext?: TemplateContext
367
+ parentContext?: TemplateContext,
372
368
  ): TemplateContext {
373
369
  const isFirst = index === 0;
374
370
  const isLast = index === total - 1;
@@ -422,7 +418,7 @@ export function buildItemUrl(
422
418
  item: CMSItem,
423
419
  slugField: string,
424
420
  locale?: string,
425
- i18nConfig?: I18nConfig
421
+ i18nConfig?: I18nConfig,
426
422
  ): string {
427
423
  // Get slug from the schema-defined slugField, fall back to _filename, _slug, or _id
428
424
  let slug = item[slugField] ?? item._filename ?? item._slug ?? item._id;
@@ -447,12 +443,9 @@ export function buildItemUrl(
447
443
  * @param i18nConfig - Optional i18n configuration
448
444
  * @returns A new item object with _url computed field added
449
445
  */
450
- export function addItemUrl(
451
- item: CMSItem,
452
- schema: CMSSchema,
453
- locale?: string,
454
- i18nConfig?: I18nConfig
455
- ): CMSItem {
446
+ export function addItemUrl(item: CMSItem, schema: CMSSchema, locale?: string, i18nConfig?: I18nConfig): CMSItem {
447
+ // A data-only collection (no urlPattern/slugField) has no per-item URL — leave it off.
448
+ if (!schema.urlPattern || !schema.slugField) return item;
456
449
  return {
457
450
  ...item,
458
451
  _url: buildItemUrl(schema.urlPattern, item, schema.slugField, locale, i18nConfig),
@@ -468,11 +461,6 @@ export function addItemUrl(
468
461
  * @param i18nConfig - Optional i18n configuration
469
462
  * @returns Array of items with _url computed field added
470
463
  */
471
- export function addItemUrls(
472
- items: CMSItem[],
473
- schema: CMSSchema,
474
- locale?: string,
475
- i18nConfig?: I18nConfig
476
- ): CMSItem[] {
477
- return items.map(item => addItemUrl(item, schema, locale, i18nConfig));
464
+ export function addItemUrls(items: CMSItem[], schema: CMSSchema, locale?: string, i18nConfig?: I18nConfig): CMSItem[] {
465
+ return items.map((item) => addItemUrl(item, schema, locale, i18nConfig));
478
466
  }
@@ -33,7 +33,7 @@ export function parseJsonError(error: unknown, content: string): JsonParseErrorI
33
33
  // Try to extract position from message
34
34
  const posMatch = msg.match(/at position (\d+)/i);
35
35
  if (posMatch) {
36
- const pos = parseInt(posMatch[1], 10);
36
+ const pos = parseInt(posMatch[1] ?? '', 10);
37
37
  const { line, column } = offsetToLineColumn(content, pos);
38
38
  return { message: msg, line, column };
39
39
  }
@@ -45,8 +45,8 @@ export function parseJsonError(error: unknown, content: string): JsonParseErrorI
45
45
  if (lineMatch) {
46
46
  return {
47
47
  message: msg,
48
- line: parseInt(lineMatch[1], 10),
49
- column: colMatch ? parseInt(colMatch[1], 10) : 1,
48
+ line: parseInt(lineMatch[1] ?? '', 10),
49
+ column: colMatch ? parseInt(colMatch[1] ?? '', 10) : 1,
50
50
  };
51
51
  }
52
52
 
@@ -73,7 +73,7 @@ export function attemptJsonRepair(content: string): JsonRepairResult {
73
73
  try {
74
74
  const data = JSON.parse(content);
75
75
  return { success: true, repaired: false, data };
76
- } catch (firstError) {
76
+ } catch (_firstError) {
77
77
  // Continue to repair attempts
78
78
  }
79
79
 
@@ -108,27 +108,33 @@ export function attemptJsonRepair(content: string): JsonRepairResult {
108
108
  let repairLine = 0;
109
109
 
110
110
  for (let i = 0; i < lines.length; i++) {
111
- const line = lines[i];
111
+ const line = lines[i] ?? '';
112
112
  const trimmed = line.trimEnd();
113
- const nextLine = i + 1 < lines.length ? lines[i + 1] : null;
113
+ const nextLine = i + 1 < lines.length ? (lines[i + 1] ?? null) : null;
114
114
 
115
115
  if (nextLine !== null) {
116
116
  const nextTrimmed = nextLine.trimStart();
117
117
  // Check if current line ends with a value (not comma, not opening bracket, not colon)
118
118
  // and next line starts with a key or value
119
119
  const needsComma =
120
- /["\d\w\]}\-]$/.test(trimmed) &&
120
+ /["\d\w\]}-]$/.test(trimmed) &&
121
121
  !trimmed.endsWith(',') &&
122
122
  !trimmed.endsWith('{') &&
123
123
  !trimmed.endsWith('[') &&
124
124
  !trimmed.endsWith(':') &&
125
- (nextTrimmed.startsWith('"') || nextTrimmed.startsWith('{') || nextTrimmed.startsWith('[') || /^\d/.test(nextTrimmed) || nextTrimmed === 'true' || nextTrimmed === 'false' || nextTrimmed === 'null');
125
+ (nextTrimmed.startsWith('"') ||
126
+ nextTrimmed.startsWith('{') ||
127
+ nextTrimmed.startsWith('[') ||
128
+ /^\d/.test(nextTrimmed) ||
129
+ nextTrimmed === 'true' ||
130
+ nextTrimmed === 'false' ||
131
+ nextTrimmed === 'null');
126
132
 
127
133
  if (needsComma) {
128
- fixed += trimmed + ',\n';
134
+ fixed += `${trimmed},\n`;
129
135
  if (repairLine === 0) repairLine = i + 1;
130
136
  } else {
131
- fixed += line + '\n';
137
+ fixed += `${line}\n`;
132
138
  }
133
139
  } else {
134
140
  fixed += line;
@@ -6,12 +6,7 @@ import {
6
6
  generateLibraryTags,
7
7
  extractLibraryOrigins,
8
8
  } from './libraryLoader';
9
- import type {
10
- JSLibraryConfig,
11
- CSSLibraryConfig,
12
- LibrariesConfig,
13
- PageLibrariesConfig,
14
- } from './types/libraries';
9
+ import type { JSLibraryConfig, CSSLibraryConfig, LibrariesConfig, PageLibrariesConfig } from './types/libraries';
15
10
 
16
11
  describe('libraryLoader', () => {
17
12
  // ---------------------------------------------------------------------------
@@ -73,12 +68,8 @@ describe('libraryLoader', () => {
73
68
  css: [{ url: 'https://cdn.example.com/replacement.css' }],
74
69
  };
75
70
  const result = mergeLibraries(globalLibs, page);
76
- expect(result.js).toEqual([
77
- { url: 'https://cdn.example.com/replacement.js' },
78
- ]);
79
- expect(result.css).toEqual([
80
- { url: 'https://cdn.example.com/replacement.css' },
81
- ]);
71
+ expect(result.js).toEqual([{ url: 'https://cdn.example.com/replacement.js' }]);
72
+ expect(result.css).toEqual([{ url: 'https://cdn.example.com/replacement.css' }]);
82
73
  });
83
74
 
84
75
  test('replace mode: defaults to empty arrays when page.js and page.css are missing', () => {
@@ -129,12 +120,12 @@ describe('libraryLoader', () => {
129
120
  css: [{ url: 'https://cdn.example.com/b.css', media: 'screen' }],
130
121
  };
131
122
  const result = mergeLibraries(global, page);
132
- expect(result.js![0].mode).toBe('async');
133
- expect(result.js![0].position).toBe('head');
134
- expect(result.js![1].mode).toBe('defer');
135
- expect(result.js![1].position).toBe('body-end');
136
- expect(result.css![0].media).toBe('print');
137
- expect(result.css![1].media).toBe('screen');
123
+ expect(result.js![0]!.mode).toBe('async');
124
+ expect(result.js![0]!.position).toBe('head');
125
+ expect(result.js![1]!.mode).toBe('defer');
126
+ expect(result.js![1]!.position).toBe('body-end');
127
+ expect(result.css![0]!.media).toBe('print');
128
+ expect(result.css![1]!.media).toBe('screen');
138
129
  });
139
130
  });
140
131
 
@@ -218,9 +209,7 @@ describe('libraryLoader', () => {
218
209
  test('includes media attribute when provided', () => {
219
210
  const lib: CSSLibraryConfig = { url: 'https://cdn.example.com/print.css', media: 'print' };
220
211
  const tag = generateStylesheetTag(lib);
221
- expect(tag).toBe(
222
- '<link rel="stylesheet" href="https://cdn.example.com/print.css" media="print">'
223
- );
212
+ expect(tag).toBe('<link rel="stylesheet" href="https://cdn.example.com/print.css" media="print">');
224
213
  });
225
214
 
226
215
  test('handles complex media query', () => {
@@ -278,9 +267,7 @@ describe('libraryLoader', () => {
278
267
  js: [],
279
268
  };
280
269
  const tags = generateLibraryTags(libs);
281
- expect(tags.headCSS).toBe(
282
- '<link rel="stylesheet" href="https://cdn.example.com/style.css">'
283
- );
270
+ expect(tags.headCSS).toBe('<link rel="stylesheet" href="https://cdn.example.com/style.css">');
284
271
  expect(tags.headJS).toBe('');
285
272
  expect(tags.bodyEndJS).toBe('');
286
273
  });
@@ -291,9 +278,7 @@ describe('libraryLoader', () => {
291
278
  css: [],
292
279
  };
293
280
  const tags = generateLibraryTags(libs);
294
- expect(tags.bodyEndJS).toBe(
295
- '<script src="https://cdn.example.com/lib.js" defer></script>'
296
- );
281
+ expect(tags.bodyEndJS).toBe('<script src="https://cdn.example.com/lib.js" defer></script>');
297
282
  expect(tags.headJS).toBe('');
298
283
  expect(tags.headCSS).toBe('');
299
284
  });
@@ -304,9 +289,7 @@ describe('libraryLoader', () => {
304
289
  css: [],
305
290
  };
306
291
  const tags = generateLibraryTags(libs);
307
- expect(tags.headJS).toBe(
308
- '<script src="https://cdn.example.com/head.js" defer></script>'
309
- );
292
+ expect(tags.headJS).toBe('<script src="https://cdn.example.com/head.js" defer></script>');
310
293
  expect(tags.bodyEndJS).toBe('');
311
294
  });
312
295
 
@@ -316,9 +299,7 @@ describe('libraryLoader', () => {
316
299
  css: [],
317
300
  };
318
301
  const tags = generateLibraryTags(libs);
319
- expect(tags.bodyEndJS).toBe(
320
- '<script src="https://cdn.example.com/foot.js" defer></script>'
321
- );
302
+ expect(tags.bodyEndJS).toBe('<script src="https://cdn.example.com/foot.js" defer></script>');
322
303
  expect(tags.headJS).toBe('');
323
304
  });
324
305
 
@@ -340,31 +321,25 @@ describe('libraryLoader', () => {
340
321
 
341
322
  test('multiple CSS libs joined with newline and indentation', () => {
342
323
  const libs: LibrariesConfig = {
343
- css: [
344
- { url: 'https://cdn.example.com/a.css' },
345
- { url: 'https://cdn.example.com/b.css' },
346
- ],
324
+ css: [{ url: 'https://cdn.example.com/a.css' }, { url: 'https://cdn.example.com/b.css' }],
347
325
  js: [],
348
326
  };
349
327
  const tags = generateLibraryTags(libs);
350
328
  expect(tags.headCSS).toBe(
351
329
  '<link rel="stylesheet" href="https://cdn.example.com/a.css">\n ' +
352
- '<link rel="stylesheet" href="https://cdn.example.com/b.css">'
330
+ '<link rel="stylesheet" href="https://cdn.example.com/b.css">',
353
331
  );
354
332
  });
355
333
 
356
334
  test('multiple JS libs joined with newline and indentation', () => {
357
335
  const libs: LibrariesConfig = {
358
- js: [
359
- { url: 'https://cdn.example.com/a.js' },
360
- { url: 'https://cdn.example.com/b.js' },
361
- ],
336
+ js: [{ url: 'https://cdn.example.com/a.js' }, { url: 'https://cdn.example.com/b.js' }],
362
337
  css: [],
363
338
  };
364
339
  const tags = generateLibraryTags(libs);
365
340
  expect(tags.bodyEndJS).toBe(
366
341
  '<script src="https://cdn.example.com/a.js" defer></script>\n ' +
367
- '<script src="https://cdn.example.com/b.js" defer></script>'
342
+ '<script src="https://cdn.example.com/b.js" defer></script>',
368
343
  );
369
344
  });
370
345
 
@@ -446,10 +421,7 @@ describe('libraryLoader', () => {
446
421
 
447
422
  test('deduplicates origins from same domain', () => {
448
423
  const libs: LibrariesConfig = {
449
- js: [
450
- { url: 'https://cdn.jsdelivr.net/npm/lib-a.js' },
451
- { url: 'https://cdn.jsdelivr.net/npm/lib-b.js' },
452
- ],
424
+ js: [{ url: 'https://cdn.jsdelivr.net/npm/lib-a.js' }, { url: 'https://cdn.jsdelivr.net/npm/lib-b.js' }],
453
425
  css: [],
454
426
  };
455
427
  const { scriptOrigins } = extractLibraryOrigins(libs);
@@ -482,14 +454,8 @@ describe('libraryLoader', () => {
482
454
 
483
455
  test('handles multiple different domains', () => {
484
456
  const libs: LibrariesConfig = {
485
- js: [
486
- { url: 'https://cdn.jsdelivr.net/npm/lib.js' },
487
- { url: 'https://unpkg.com/other-lib.js' },
488
- ],
489
- css: [
490
- { url: 'https://fonts.googleapis.com/css' },
491
- { url: 'https://cdn.jsdelivr.net/npm/style.css' },
492
- ],
457
+ js: [{ url: 'https://cdn.jsdelivr.net/npm/lib.js' }, { url: 'https://unpkg.com/other-lib.js' }],
458
+ css: [{ url: 'https://fonts.googleapis.com/css' }, { url: 'https://cdn.jsdelivr.net/npm/style.css' }],
493
459
  };
494
460
  const { scriptOrigins, styleOrigins } = extractLibraryOrigins(libs);
495
461
  expect(scriptOrigins.size).toBe(2);
@@ -3,23 +3,14 @@
3
3
  * Generates HTML tags for external JS/CSS libraries
4
4
  */
5
5
 
6
- import type {
7
- JSLibraryConfig,
8
- CSSLibraryConfig,
9
- LibrariesConfig,
10
- PageLibrariesConfig,
11
- } from './types/libraries';
6
+ import type { JSLibraryConfig, CSSLibraryConfig, LibrariesConfig, PageLibrariesConfig } from './types/libraries';
12
7
  import type { ComponentDefinition } from './types/components';
13
8
 
14
9
  /**
15
10
  * Escape HTML special characters in attribute values
16
11
  */
17
12
  function escapeAttr(str: string): string {
18
- return str
19
- .replace(/&/g, '&amp;')
20
- .replace(/"/g, '&quot;')
21
- .replace(/</g, '&lt;')
22
- .replace(/>/g, '&gt;');
13
+ return str.replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
23
14
  }
24
15
 
25
16
  /**
@@ -28,10 +19,7 @@ function escapeAttr(str: string): string {
28
19
  * @param page Page-level libraries from page meta
29
20
  * @returns Merged library configuration
30
21
  */
31
- export function mergeLibraries(
32
- global: LibrariesConfig,
33
- page?: PageLibrariesConfig
34
- ): LibrariesConfig {
22
+ export function mergeLibraries(global: LibrariesConfig, page?: PageLibrariesConfig): LibrariesConfig {
35
23
  if (!page) {
36
24
  return global;
37
25
  }
@@ -86,10 +74,7 @@ export function generateInlineStyleTag(content: string, media?: string): string
86
74
  * @returns HTML link tag string
87
75
  */
88
76
  export function generateStylesheetTag(lib: CSSLibraryConfig): string {
89
- const attrs: string[] = [
90
- 'rel="stylesheet"',
91
- `href="${escapeAttr(lib.url)}"`,
92
- ];
77
+ const attrs: string[] = ['rel="stylesheet"', `href="${escapeAttr(lib.url)}"`];
93
78
 
94
79
  if (lib.media) {
95
80
  attrs.push(`media="${escapeAttr(lib.media)}"`);
@@ -157,7 +142,7 @@ export function generateLibraryTags(libs: LibrariesConfig, inlineContents?: Map<
157
142
  */
158
143
  export function collectComponentLibraries(
159
144
  globalComponents: Record<string, ComponentDefinition> = {},
160
- pageComponents: Record<string, ComponentDefinition> = {}
145
+ pageComponents: Record<string, ComponentDefinition> = {},
161
146
  ): LibrariesConfig {
162
147
  const seenJS = new Set<string>();
163
148
  const seenCSS = new Set<string>();
@@ -230,13 +215,13 @@ export function extractLibraryOrigins(libs: LibrariesConfig): LibraryOrigins {
230
215
  */
231
216
  export function filterLibrariesByContext(
232
217
  libs: LibrariesConfig,
233
- context: 'editor' | 'build' | 'preview'
218
+ context: 'editor' | 'build' | 'preview',
234
219
  ): LibrariesConfig {
235
220
  if (context === 'preview') return libs;
236
221
  const key = context === 'editor' ? 'disableEditor' : 'disableBuild';
237
222
  return {
238
223
  js: libs.js,
239
- css: (libs.css || []).filter(css => !css[key]),
224
+ css: (libs.css || []).filter((css) => !css[key]),
240
225
  };
241
226
  }
242
227
 
@@ -7,7 +7,7 @@
7
7
  * Normalize a URL path for comparison (strip trailing slash, query string, hash)
8
8
  */
9
9
  export function normalizePath(path: string): string {
10
- let normalized = path.split('?')[0].split('#')[0];
10
+ let normalized = (path.split('?')[0] ?? '').split('#')[0] ?? '';
11
11
  if (normalized.length > 1 && normalized.endsWith('/')) {
12
12
  normalized = normalized.slice(0, -1);
13
13
  }
@@ -0,0 +1,66 @@
1
+ import { test, expect, describe, afterEach, mock } from 'bun:test';
2
+ import { createLogger, setLogLevel, getLogLevel, setLogSink, type LogRecord } from './logger';
3
+
4
+ afterEach(() => {
5
+ setLogSink(null);
6
+ setLogLevel('debug');
7
+ });
8
+
9
+ describe('logger — level gating', () => {
10
+ test('suppresses levels below the active threshold', () => {
11
+ const records: LogRecord[] = [];
12
+ setLogSink((r) => records.push(r));
13
+ setLogLevel('warn');
14
+ const log = createLogger('Test');
15
+ log.debug('d');
16
+ log.info('i');
17
+ log.warn('w');
18
+ log.error('e');
19
+ expect(records.map((r) => r.level)).toEqual(['warn', 'error']);
20
+ });
21
+
22
+ test('silent suppresses everything', () => {
23
+ const records: LogRecord[] = [];
24
+ setLogSink((r) => records.push(r));
25
+ setLogLevel('silent');
26
+ const log = createLogger();
27
+ log.error('nope');
28
+ expect(records).toHaveLength(0);
29
+ });
30
+ });
31
+
32
+ describe('logger — scope + sink', () => {
33
+ test('forwards scope, message, and args to the sink', () => {
34
+ const records: LogRecord[] = [];
35
+ setLogSink((r) => records.push(r));
36
+ setLogLevel('debug');
37
+ createLogger('Area').info('hello', 1, { a: 2 });
38
+ expect(records).toHaveLength(1);
39
+ expect(records[0]).toMatchObject({ level: 'info', scope: 'Area', message: 'hello', args: [1, { a: 2 }] });
40
+ expect(typeof records[0]!.timestamp).toBe('number');
41
+ });
42
+
43
+ test('child nests the scope', () => {
44
+ const records: LogRecord[] = [];
45
+ setLogSink((r) => records.push(r));
46
+ createLogger('Parent').child('Child').warn('x');
47
+ expect(records[0]!.scope).toBe('Parent:Child');
48
+ });
49
+
50
+ test('routes to the matching console method', () => {
51
+ const spy = mock(() => {});
52
+ const orig = console.warn;
53
+ console.warn = spy as unknown as typeof console.warn;
54
+ try {
55
+ createLogger('S').warn('msg');
56
+ } finally {
57
+ console.warn = orig;
58
+ }
59
+ expect(spy).toHaveBeenCalledTimes(1);
60
+ });
61
+
62
+ test('getLogLevel reflects setLogLevel', () => {
63
+ setLogLevel('info');
64
+ expect(getLogLevel()).toBe('info');
65
+ });
66
+ });