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
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * Component Service
3
3
  * Handles component loading and management
4
- *
4
+ *
5
5
  * Provides methods for loading components from the file system, caching them in memory,
6
6
  * and managing component definitions including saving JavaScript and CSS files.
7
7
  */
8
8
 
9
- import { join } from 'path';
10
- import { existsSync, mkdirSync, unlinkSync, readdirSync, rmdirSync } from 'fs';
9
+ import { join } from 'node:path';
10
+ import { existsSync, mkdirSync, readdirSync, rmdirSync } from 'node:fs';
11
11
  import { loadComponentDirectory, loadJSONFile, parseJSON } from '../jsonLoader';
12
12
  import type { ComponentWithCategory, ComponentDirectoryResult, ComponentLoadDiagnostic } from '../jsonLoader';
13
13
  import { projectPaths } from '../projectContext';
@@ -15,6 +15,7 @@ import type { ComponentDefinition, JSONPage } from '../../shared/types';
15
15
  import { readTextFile, fileExists } from '../runtime';
16
16
  import type { PageService } from './pageService';
17
17
  import { rewriteComponentRefs } from '../../shared/componentRefs';
18
+ import { isSafePathSegment, isPathWithinRoot } from '../../shared/pathSecurity';
18
19
 
19
20
  // Re-export for callers that already imported the helper from this module.
20
21
  export { rewriteComponentRefs };
@@ -157,7 +158,10 @@ export class ComponentService {
157
158
 
158
159
  if (this.loader) {
159
160
  // Legacy loader interface returns plain Map
160
- loadedComponents = await this.loader.loadDirectory(projectPaths.components()) as Map<string, ComponentWithCategory>;
161
+ loadedComponents = (await this.loader.loadDirectory(projectPaths.components())) as Map<
162
+ string,
163
+ ComponentWithCategory
164
+ >;
161
165
  } else {
162
166
  const result: ComponentDirectoryResult = await loadComponentDirectory(projectPaths.components());
163
167
  loadedComponents = result.components;
@@ -189,10 +193,10 @@ export class ComponentService {
189
193
 
190
194
  /**
191
195
  * Get component by name
192
- *
196
+ *
193
197
  * @param name - Component name (without .json extension)
194
198
  * @returns ComponentDefinition if found, undefined otherwise
195
- *
199
+ *
196
200
  * @example
197
201
  * ```typescript
198
202
  * const button = componentService.getComponent('Button');
@@ -207,10 +211,10 @@ export class ComponentService {
207
211
 
208
212
  /**
209
213
  * Check if component exists
210
- *
214
+ *
211
215
  * @param name - Component name to check
212
216
  * @returns True if component exists in cache, false otherwise
213
- *
217
+ *
214
218
  * @example
215
219
  * ```typescript
216
220
  * if (componentService.hasComponent('Button')) {
@@ -266,7 +270,7 @@ export class ComponentService {
266
270
  this.components.forEach((value, key) => {
267
271
  record[key] = {
268
272
  definition: value,
269
- category: this.componentCategories.get(key)
273
+ category: this.componentCategories.get(key),
270
274
  };
271
275
  });
272
276
  return record;
@@ -308,10 +312,10 @@ export class ComponentService {
308
312
 
309
313
  /**
310
314
  * Validate that a component has a valid structure
311
- *
315
+ *
312
316
  * @param componentDef - ComponentDefinition to validate
313
317
  * @returns true if component has valid structure, false otherwise
314
- *
318
+ *
315
319
  * @example
316
320
  * ```typescript
317
321
  * const component = componentService.getComponent('Button');
@@ -324,35 +328,35 @@ export class ComponentService {
324
328
  if (!componentDef) {
325
329
  return false;
326
330
  }
327
-
331
+
328
332
  // Handle ComponentDefinition in any format:
329
333
  // 1. Legacy: { type: string, component: { structure: {...} } }
330
334
  // 2. New: { component: { structure: {...} } }
331
335
  // 3. Direct: { component: { structure: {...} } } (already normalized)
332
-
336
+
333
337
  // Access component field safely (handle both TypeScript types and runtime data)
334
338
  const component = (componentDef as any).component;
335
-
339
+
336
340
  if (!component || typeof component !== 'object') {
337
341
  return false;
338
342
  }
339
-
343
+
340
344
  // Check for structure in component
341
345
  const structure = component.structure;
342
346
  if (!structure) {
343
347
  return false;
344
348
  }
345
-
349
+
346
350
  // Validate structure is a proper object with type property
347
351
  if (typeof structure !== 'object' || structure === null || Array.isArray(structure)) {
348
352
  return false;
349
353
  }
350
-
354
+
351
355
  // Structure must have a type property that's a string
352
356
  if (!('type' in structure) || typeof structure.type !== 'string') {
353
357
  return false;
354
358
  }
355
-
359
+
356
360
  return true;
357
361
  }
358
362
 
@@ -362,8 +366,15 @@ export class ComponentService {
362
366
  */
363
367
  private getComponentDir(name: string, category?: string): string {
364
368
  const componentsDir = this.componentsBaseDir();
369
+ // A component name becomes a filename — it must be a single safe segment (no
370
+ // `..`/separators). The resolved category dir must stay within the components
371
+ // root (nested categories are allowed, traversal is not). Mirrors the guards
372
+ // renameComponent/createFolder already apply; the save path previously lacked them.
373
+ if (!isSafePathSegment(name)) throw new Error(`unsafe component name: ${name}`);
365
374
  const cat = this.resolveComponentCategory(name, category);
366
- return cat ? join(componentsDir, cat) : componentsDir;
375
+ const dir = cat ? join(componentsDir, cat) : componentsDir;
376
+ if (!isPathWithinRoot(dir, componentsDir)) throw new Error(`component category escapes components dir: ${cat}`);
377
+ return dir;
367
378
  }
368
379
 
369
380
  /**
@@ -447,7 +458,7 @@ export class ComponentService {
447
458
  return await readTextFile(jsFilePath);
448
459
  }
449
460
  return null;
450
- } catch (error) {
461
+ } catch (_error) {
451
462
  return null;
452
463
  }
453
464
  }
@@ -480,11 +491,27 @@ export class ComponentService {
480
491
  * ```
481
492
  */
482
493
  async saveComponent(name: string, data: ComponentDefinition, category?: string): Promise<void> {
483
- // Create a copy without the javascript field (JavaScript should only be in .js files)
494
+ // The `.json` PAYLOAD never carries javascript (it lives in the sibling `.js`
495
+ // file), so the JSON write below strips it. But the component's JS must NOT be
496
+ // lost in the process:
497
+ // - single-file writers (the astro `.astro` format) re-emit the WHOLE file
498
+ // from the def they're given — writing a js-less def silently deletes the
499
+ // component's `<script>` from the source of truth;
500
+ // - the in-memory cache previously took the stripped copy too, so even JSON
501
+ // projects lost the component's JS from the live cache (SSR preview renders
502
+ // without it) until a file-watcher reload.
503
+ // Preserve: the incoming def's javascript wins; absent that, the cached one.
484
504
  const dataWithoutJS = JSON.parse(JSON.stringify(data));
485
505
  if (dataWithoutJS?.component?.javascript !== undefined) {
486
506
  delete dataWithoutJS.component.javascript;
487
507
  }
508
+ const incomingJs = (data as { component?: { javascript?: string } })?.component?.javascript;
509
+ const cachedJs = this.components.get(name)?.component?.javascript;
510
+ const javascript = incomingJs !== undefined ? incomingJs : cachedJs;
511
+ const dataWithJS: ComponentDefinition =
512
+ javascript !== undefined
513
+ ? { ...dataWithoutJS, component: { ...dataWithoutJS.component, javascript } }
514
+ : dataWithoutJS;
488
515
 
489
516
  // Determine target directory. Resolver consults the explicit arg, then
490
517
  // the in-memory cache, then disk — so a save during the file-watcher
@@ -496,13 +523,13 @@ export class ComponentService {
496
523
 
497
524
  if (this.writer) {
498
525
  // Format-aware write (e.g. emit a `.astro` file). The writer owns directory
499
- // creation + serialization. The category folder is part of `componentDir`.
500
- await this.writer.writeComponent(componentDir, name, dataWithoutJS);
526
+ // creation + serialization, and for single-file formats the def must carry
527
+ // EVERYTHING — including javascript (see the ComponentWriter contract).
528
+ await this.writer.writeComponent(componentDir, name, dataWithJS);
501
529
  } else {
502
- // JSON path — unchanged, byte-identical to legacy behavior.
503
- const writeFile = this.fs
504
- ? this.fs.writeFile.bind(this.fs)
505
- : (await import('fs/promises')).writeFile;
530
+ // JSON path — unchanged, byte-identical to legacy behavior (js-less payload;
531
+ // the sibling `.js` file owns the script).
532
+ const writeFile = this.fs ? this.fs.writeFile.bind(this.fs) : (await import('node:fs/promises')).writeFile;
506
533
 
507
534
  // Create category directory if needed
508
535
  if (targetCategory && !existsSync(componentDir)) {
@@ -513,8 +540,9 @@ export class ComponentService {
513
540
  await writeFile(filePath, JSON.stringify(dataWithoutJS, null, 2), 'utf-8');
514
541
  }
515
542
 
516
- // Update in-memory cache
517
- this.components.set(name, dataWithoutJS);
543
+ // Update in-memory cache with the FULL def (js preserved) so the live preview
544
+ // keeps rendering the component's script between save and watcher reload.
545
+ this.components.set(name, dataWithJS);
518
546
  this.componentCategories.set(name, targetCategory);
519
547
  }
520
548
 
@@ -550,9 +578,7 @@ export class ComponentService {
550
578
  return;
551
579
  }
552
580
 
553
- const writeFile = this.fs
554
- ? this.fs.writeFile.bind(this.fs)
555
- : (await import('fs/promises')).writeFile;
581
+ const writeFile = this.fs ? this.fs.writeFile.bind(this.fs) : (await import('node:fs/promises')).writeFile;
556
582
  const jsFilePath = join(componentDir, `${name}.js`);
557
583
  await writeFile(jsFilePath, javascript || '', 'utf-8');
558
584
 
@@ -569,7 +595,7 @@ export class ComponentService {
569
595
  }
570
596
  parsed.component.javascript = jsContent;
571
597
  }
572
- } catch (error) {
598
+ } catch (_error) {
573
599
  // Silently handle JS file read errors
574
600
  }
575
601
  this.components.set(name, parsed);
@@ -608,9 +634,7 @@ export class ComponentService {
608
634
  return;
609
635
  }
610
636
 
611
- const writeFile = this.fs
612
- ? this.fs.writeFile.bind(this.fs)
613
- : (await import('fs/promises')).writeFile;
637
+ const writeFile = this.fs ? this.fs.writeFile.bind(this.fs) : (await import('node:fs/promises')).writeFile;
614
638
  const cssFilePath = join(componentDir, `${name}.css`);
615
639
  await writeFile(cssFilePath, css || '', 'utf-8');
616
640
 
@@ -627,7 +651,7 @@ export class ComponentService {
627
651
  }
628
652
  parsed.component.css = cssContent;
629
653
  }
630
- } catch (error) {
654
+ } catch (_error) {
631
655
  // Silently handle CSS file read errors
632
656
  }
633
657
  this.components.set(name, parsed);
@@ -657,8 +681,8 @@ export class ComponentService {
657
681
  try {
658
682
  const entries = readdirSync(componentsDir, { withFileTypes: true });
659
683
  return entries
660
- .filter(entry => entry.isDirectory())
661
- .map(entry => entry.name)
684
+ .filter((entry) => entry.isDirectory())
685
+ .map((entry) => entry.name)
662
686
  .sort();
663
687
  } catch {
664
688
  return [];
@@ -687,7 +711,10 @@ export class ComponentService {
687
711
  }
688
712
 
689
713
  // Validate folder name (alphanumeric, dashes, underscores only)
690
- const sanitized = folderName.trim().toLowerCase().replace(/[^a-z0-9-_]/g, '-');
714
+ const sanitized = folderName
715
+ .trim()
716
+ .toLowerCase()
717
+ .replace(/[^a-z0-9-_]/g, '-');
691
718
  if (sanitized !== folderName.trim()) {
692
719
  throw new Error('Folder name can only contain lowercase letters, numbers, dashes, and underscores');
693
720
  }
@@ -748,7 +775,7 @@ export class ComponentService {
748
775
 
749
776
  // Move all component files (.json, .js, .css)
750
777
  const extensions = ['.json', '.js', '.css'];
751
- const { rename } = await import('fs/promises');
778
+ const { rename } = await import('node:fs/promises');
752
779
 
753
780
  for (const ext of extensions) {
754
781
  const sourcePath = join(sourceDir, `${name}${ext}`);
@@ -843,7 +870,7 @@ export class ComponentService {
843
870
  }
844
871
  }
845
872
 
846
- const { rename } = await import('fs/promises');
873
+ const { rename } = await import('node:fs/promises');
847
874
  for (const ext of ['.json', '.js', '.css'] as const) {
848
875
  const src = join(dir, `${oldName}${ext}`);
849
876
  const dst = join(dir, `${trimmedNew}${ext}`);
@@ -864,9 +891,7 @@ export class ComponentService {
864
891
  // each one that actually changed. The owning component's own structure
865
892
  // shouldn't reference itself, so we don't bother walking it.
866
893
  let componentRefs = 0;
867
- const writeFile = this.fs
868
- ? this.fs.writeFile.bind(this.fs)
869
- : (await import('fs/promises')).writeFile;
894
+ const writeFile = this.fs ? this.fs.writeFile.bind(this.fs) : (await import('node:fs/promises')).writeFile;
870
895
  for (const [name, otherDef] of this.components.entries()) {
871
896
  if (name === trimmedNew) continue;
872
897
  const structure = otherDef.component?.structure;
@@ -912,4 +937,3 @@ export class ComponentService {
912
937
  };
913
938
  }
914
939
  }
915
-
@@ -1,12 +1,11 @@
1
- import { describe, test, expect, beforeEach, afterEach, mock } from 'bun:test';
2
- import { join } from 'path';
3
- import { mkdirSync, rmSync, writeFileSync, existsSync } from 'fs';
1
+ import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
2
+ import { join } from 'node:path';
3
+ import { mkdirSync, rmSync, writeFileSync, existsSync } from 'node:fs';
4
4
 
5
5
  // We need to mock projectPaths.config() before importing ConfigService.
6
6
  // Since projectPaths is a module-level export, we mock the projectContext module.
7
7
  import { setProjectRoot, getProjectRoot } from '../../server/projectContext';
8
8
  import { ConfigService } from './configService';
9
- import type { IconsConfig } from './configService';
10
9
  import { DEFAULT_BREAKPOINTS } from '../../shared/breakpoints';
11
10
  import { DEFAULT_RESPONSIVE_SCALES } from '../../shared/responsiveScaling';
12
11
  import { DEFAULT_I18N_CONFIG } from '../../shared/i18n';
@@ -209,8 +208,8 @@ describe('ConfigService', () => {
209
208
  await service.load();
210
209
 
211
210
  const result = service.getBreakpoints();
212
- expect(result.tablet.breakpoint).toBe(1024);
213
- expect(result.tablet.previewPoint).toBe(1024);
211
+ expect(result.tablet!.breakpoint).toBe(1024);
212
+ expect(result.tablet!.previewPoint).toBe(1024);
214
213
  });
215
214
 
216
215
  test('should fall back to defaults for non-positive number values', async () => {
@@ -278,7 +277,7 @@ describe('ConfigService', () => {
278
277
  await service.load();
279
278
 
280
279
  const result = service.getBreakpoints();
281
- expect(result.tablet.previewPoint).toBe(1024);
280
+ expect(result.tablet!.previewPoint).toBe(1024);
282
281
  });
283
282
 
284
283
  test('should support custom breakpoint names', async () => {
@@ -329,12 +328,12 @@ describe('ConfigService', () => {
329
328
  const result = service.getI18n();
330
329
  expect(result.defaultLocale).toBe('en');
331
330
  expect(result.locales).toHaveLength(2);
332
- expect(result.locales[0].code).toBe('en');
333
- expect(result.locales[1].code).toBe('pl');
331
+ expect(result.locales[0]!.code).toBe('en');
332
+ expect(result.locales[1]!.code).toBe('pl');
334
333
  // Migrated locales should have name, nativeName, langTag
335
- expect(result.locales[1].name).toBe('PL');
336
- expect(result.locales[1].nativeName).toBe('PL');
337
- expect(result.locales[1].langTag).toBe('pl-PL');
334
+ expect(result.locales[1]!.name).toBe('PL');
335
+ expect(result.locales[1]!.nativeName).toBe('PL');
336
+ expect(result.locales[1]!.langTag).toBe('pl-PL');
338
337
  });
339
338
 
340
339
  test('should pass through new LocaleConfig[] format', async () => {
@@ -547,10 +546,7 @@ describe('ConfigService', () => {
547
546
  test('should normalize string URLs to object format for JS libraries', async () => {
548
547
  writeConfig({
549
548
  libraries: {
550
- js: [
551
- 'https://cdn.example.com/lib.js',
552
- 'https://cdn.example.com/other.js',
553
- ],
549
+ js: ['https://cdn.example.com/lib.js', 'https://cdn.example.com/other.js'],
554
550
  },
555
551
  });
556
552
  await service.load();
@@ -565,62 +561,44 @@ describe('ConfigService', () => {
565
561
  test('should normalize string URLs to object format for CSS libraries', async () => {
566
562
  writeConfig({
567
563
  libraries: {
568
- css: [
569
- 'https://fonts.googleapis.com/css2?family=Inter',
570
- ],
564
+ css: ['https://fonts.googleapis.com/css2?family=Inter'],
571
565
  },
572
566
  });
573
567
  await service.load();
574
568
 
575
569
  const result = service.getLibraries();
576
- expect(result.css).toEqual([
577
- { url: 'https://fonts.googleapis.com/css2?family=Inter' },
578
- ]);
570
+ expect(result.css).toEqual([{ url: 'https://fonts.googleapis.com/css2?family=Inter' }]);
579
571
  });
580
572
 
581
573
  test('should pass through object format for JS libraries', async () => {
582
574
  writeConfig({
583
575
  libraries: {
584
- js: [
585
- { url: 'https://cdn.example.com/lib.js', mode: 'async', position: 'head' },
586
- ],
576
+ js: [{ url: 'https://cdn.example.com/lib.js', mode: 'async', position: 'head' }],
587
577
  },
588
578
  });
589
579
  await service.load();
590
580
 
591
581
  const result = service.getLibraries();
592
- expect(result.js).toEqual([
593
- { url: 'https://cdn.example.com/lib.js', mode: 'async', position: 'head' },
594
- ]);
582
+ expect(result.js).toEqual([{ url: 'https://cdn.example.com/lib.js', mode: 'async', position: 'head' }]);
595
583
  });
596
584
 
597
585
  test('should pass through object format for CSS libraries', async () => {
598
586
  writeConfig({
599
587
  libraries: {
600
- css: [
601
- { url: 'https://fonts.googleapis.com/style.css', media: 'screen' },
602
- ],
588
+ css: [{ url: 'https://fonts.googleapis.com/style.css', media: 'screen' }],
603
589
  },
604
590
  });
605
591
  await service.load();
606
592
 
607
593
  const result = service.getLibraries();
608
- expect(result.css).toEqual([
609
- { url: 'https://fonts.googleapis.com/style.css', media: 'screen' },
610
- ]);
594
+ expect(result.css).toEqual([{ url: 'https://fonts.googleapis.com/style.css', media: 'screen' }]);
611
595
  });
612
596
 
613
597
  test('should handle mixed string and object formats', async () => {
614
598
  writeConfig({
615
599
  libraries: {
616
- js: [
617
- 'https://cdn.example.com/simple.js',
618
- { url: 'https://cdn.example.com/complex.js', mode: 'defer' },
619
- ],
620
- css: [
621
- 'https://cdn.example.com/simple.css',
622
- { url: 'https://cdn.example.com/complex.css', media: 'print' },
623
- ],
600
+ js: ['https://cdn.example.com/simple.js', { url: 'https://cdn.example.com/complex.js', mode: 'defer' }],
601
+ css: ['https://cdn.example.com/simple.css', { url: 'https://cdn.example.com/complex.css', media: 'print' }],
624
602
  },
625
603
  });
626
604
  await service.load();
@@ -807,8 +785,8 @@ describe('ConfigService', () => {
807
785
 
808
786
  // Verify all getters work correctly together
809
787
  const breakpoints = service.getBreakpoints();
810
- expect(breakpoints.tablet.breakpoint).toBe(1024);
811
- expect(breakpoints.mobile.previewPoint).toBe(375);
788
+ expect(breakpoints.tablet!.breakpoint).toBe(1024);
789
+ expect(breakpoints.mobile!.previewPoint).toBe(375);
812
790
 
813
791
  const scales = service.getResponsiveScales();
814
792
  expect(scales.enabled).toBe(true);
@@ -827,7 +805,6 @@ describe('ConfigService', () => {
827
805
 
828
806
  const csp = service.getCSP();
829
807
  expect(csp.scriptSrc).toEqual(['https://cdn.example.com']);
830
-
831
808
  });
832
809
 
833
810
  test('should handle empty config object', async () => {
@@ -19,6 +19,7 @@ import { DEFAULT_REM_CONFIG } from '../../shared/pxToRem';
19
19
  import { DEFAULT_I18N_CONFIG, migrateI18nConfig } from '../../shared/i18n';
20
20
  import { projectPaths } from '../projectContext';
21
21
  import { readTextFile, fileExists } from '../runtime';
22
+ import { logRuntimeError } from '../../shared/errorLogger';
22
23
 
23
24
  /**
24
25
  * Icons configuration
@@ -36,6 +37,17 @@ export interface SocialConfig {
36
37
  twitterHandle?: string;
37
38
  }
38
39
 
40
+ /**
41
+ * Sanity CMS integration config (read-only, public datasets).
42
+ * Connection info only — the runtime builds GROQ query URLs from this. No token (public).
43
+ */
44
+ export interface SanityConfig {
45
+ projectId: string;
46
+ dataset: string;
47
+ apiVersion?: string;
48
+ useCdn?: boolean;
49
+ }
50
+
39
51
  /**
40
52
  * Raw project config structure from project.config.json
41
53
  */
@@ -47,6 +59,7 @@ interface RawProjectConfig {
47
59
  i18n?: unknown;
48
60
  icons?: IconsConfig;
49
61
  social?: SocialConfig;
62
+ integrations?: { sanity?: SanityConfig };
50
63
  libraries?: LibrariesConfig;
51
64
  csp?: CSPConfig;
52
65
  baseComponent?: string;
@@ -78,8 +91,9 @@ export class ConfigService {
78
91
  const content = await readTextFile(projectPaths.config());
79
92
  this.config = JSON.parse(content);
80
93
  }
81
- } catch {
94
+ } catch (error) {
82
95
  // Fall through to defaults
96
+ logRuntimeError('configService.load', error, { filePath: projectPaths.config() });
83
97
  this.config = null;
84
98
  }
85
99
 
@@ -123,9 +137,8 @@ export class ConfigService {
123
137
  if (typeof entry.breakpoint === 'number' && entry.breakpoint > 0) {
124
138
  validInput[key] = {
125
139
  breakpoint: entry.breakpoint,
126
- previewPoint: typeof entry.previewPoint === 'number' && entry.previewPoint > 0
127
- ? entry.previewPoint
128
- : entry.breakpoint,
140
+ previewPoint:
141
+ typeof entry.previewPoint === 'number' && entry.previewPoint > 0 ? entry.previewPoint : entry.breakpoint,
129
142
  };
130
143
  }
131
144
  }
@@ -157,7 +170,7 @@ export class ConfigService {
157
170
  */
158
171
  private mergeScaleCategory(
159
172
  userScales: BreakpointScales | undefined,
160
- defaultScales: BreakpointScales | undefined
173
+ defaultScales: BreakpointScales | undefined,
161
174
  ): BreakpointScales | undefined {
162
175
  if (!userScales && !defaultScales) return undefined;
163
176
  if (!userScales) return defaultScales ? { ...defaultScales } : undefined;
@@ -186,34 +199,30 @@ export class ConfigService {
186
199
  enabled: userScales.enabled ?? DEFAULT_RESPONSIVE_SCALES.enabled,
187
200
  mode: (userScales as { mode?: 'breakpoints' | 'fluid' }).mode ?? DEFAULT_RESPONSIVE_SCALES.mode,
188
201
  baseReference: userScales.baseReference ?? DEFAULT_RESPONSIVE_SCALES.baseReference,
189
- fluidRange: (userScales as { fluidRange?: { min: number; max: number } }).fluidRange
190
- ?? (DEFAULT_RESPONSIVE_SCALES.fluidRange ? { ...DEFAULT_RESPONSIVE_SCALES.fluidRange } : undefined),
191
- siteMargin: (userScales as { siteMargin?: { min: number; max: number } }).siteMargin
192
- ?? (DEFAULT_RESPONSIVE_SCALES.siteMargin ? { ...DEFAULT_RESPONSIVE_SCALES.siteMargin } : undefined),
202
+ fluidRange:
203
+ (userScales as { fluidRange?: { min: number; max: number } }).fluidRange ??
204
+ (DEFAULT_RESPONSIVE_SCALES.fluidRange ? { ...DEFAULT_RESPONSIVE_SCALES.fluidRange } : undefined),
205
+ siteMargin:
206
+ (userScales as { siteMargin?: { min: number; max: number } }).siteMargin ??
207
+ (DEFAULT_RESPONSIVE_SCALES.siteMargin ? { ...DEFAULT_RESPONSIVE_SCALES.siteMargin } : undefined),
193
208
  fontSize: this.mergeScaleCategory(
194
209
  userScales.fontSize as BreakpointScales | undefined,
195
- DEFAULT_RESPONSIVE_SCALES.fontSize
210
+ DEFAULT_RESPONSIVE_SCALES.fontSize,
196
211
  ),
197
212
  padding: this.mergeScaleCategory(
198
213
  userScales.padding as BreakpointScales | undefined,
199
- DEFAULT_RESPONSIVE_SCALES.padding
214
+ DEFAULT_RESPONSIVE_SCALES.padding,
200
215
  ),
201
216
  margin: this.mergeScaleCategory(
202
217
  userScales.margin as BreakpointScales | undefined,
203
- DEFAULT_RESPONSIVE_SCALES.margin
204
- ),
205
- gap: this.mergeScaleCategory(
206
- userScales.gap as BreakpointScales | undefined,
207
- DEFAULT_RESPONSIVE_SCALES.gap
218
+ DEFAULT_RESPONSIVE_SCALES.margin,
208
219
  ),
220
+ gap: this.mergeScaleCategory(userScales.gap as BreakpointScales | undefined, DEFAULT_RESPONSIVE_SCALES.gap),
209
221
  borderRadius: this.mergeScaleCategory(
210
222
  userScales.borderRadius as BreakpointScales | undefined,
211
- DEFAULT_RESPONSIVE_SCALES.borderRadius
212
- ),
213
- size: this.mergeScaleCategory(
214
- userScales.size as BreakpointScales | undefined,
215
- DEFAULT_RESPONSIVE_SCALES.size
223
+ DEFAULT_RESPONSIVE_SCALES.borderRadius,
216
224
  ),
225
+ size: this.mergeScaleCategory(userScales.size as BreakpointScales | undefined, DEFAULT_RESPONSIVE_SCALES.size),
217
226
  };
218
227
  }
219
228
 
@@ -254,6 +263,24 @@ export class ConfigService {
254
263
  return this.config.social;
255
264
  }
256
265
 
266
+ /**
267
+ * Get Sanity CMS integration config (read-only, public datasets).
268
+ * Returns undefined when not configured or missing required projectId/dataset.
269
+ */
270
+ getSanity(): SanityConfig | undefined {
271
+ const sanity = this.config?.integrations?.sanity;
272
+ if (!sanity || typeof sanity !== 'object') {
273
+ return undefined;
274
+ }
275
+ if (typeof sanity.projectId !== 'string' || typeof sanity.dataset !== 'string') {
276
+ return undefined;
277
+ }
278
+ if (sanity.projectId.trim() === '' || sanity.dataset.trim() === '') {
279
+ return undefined;
280
+ }
281
+ return sanity;
282
+ }
283
+
257
284
  /**
258
285
  * Get libraries configuration
259
286
  * Returns empty arrays if not configured
@@ -268,16 +295,12 @@ export class ConfigService {
268
295
 
269
296
  // Normalize JS libraries: support both string URLs and object format
270
297
  const normalizedJs = Array.isArray(libs.js)
271
- ? libs.js.map((lib) =>
272
- typeof lib === 'string' ? { url: lib } : lib
273
- ) as JSLibraryConfig[]
298
+ ? (libs.js.map((lib) => (typeof lib === 'string' ? { url: lib } : lib)) as JSLibraryConfig[])
274
299
  : [];
275
300
 
276
301
  // Normalize CSS libraries: support both string URLs and object format
277
302
  const normalizedCss = Array.isArray(libs.css)
278
- ? libs.css.map((lib) =>
279
- typeof lib === 'string' ? { url: lib } : lib
280
- ) as CSSLibraryConfig[]
303
+ ? (libs.css.map((lib) => (typeof lib === 'string' ? { url: lib } : lib)) as CSSLibraryConfig[])
281
304
  : [];
282
305
 
283
306
  return {