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
@@ -10,11 +10,10 @@ import type {
10
10
  CMSItemVersions,
11
11
  CMSRouteMatch,
12
12
  CMSListQuery,
13
- CMSFilterCondition,
14
- CMSSortConfig,
15
13
  ReferenceLocation,
16
14
  } from '../../shared/types';
17
15
  import { isItemDraftForLocale } from '../../shared/types';
16
+ import { applyCmsFilters, applyCmsSorting } from '../../shared/cmsQuery';
18
17
  import { isI18nValue } from '../../shared/i18n';
19
18
  import { tiptapToHtml } from '../../shared/richtext/tiptapToHtml';
20
19
  import { isTiptapDocument } from '../../shared/richtext/types';
@@ -144,7 +143,7 @@ export class CMSService {
144
143
  if (richTextFields.length === 0) return items;
145
144
 
146
145
  // Process each item
147
- return items.map(item => {
146
+ return items.map((item) => {
148
147
  const processed = { ...item };
149
148
 
150
149
  for (const fieldName of richTextFields) {
@@ -199,6 +198,9 @@ export class CMSService {
199
198
 
200
199
  for (const [id, schemaInfo] of schemas) {
201
200
  newSchemaCache.set(id, schemaInfo);
201
+ // A data-only collection (no urlPattern) has no per-item route, so it contributes
202
+ // no route pattern — matchRoute never resolves it, which is correct (it's list-only).
203
+ if (!schemaInfo.schema.urlPattern) continue;
202
204
  newRoutePatterns.push({
203
205
  regex: this.patternToRegex(schemaInfo.schema.urlPattern),
204
206
  collection: id,
@@ -240,6 +242,7 @@ export class CMSService {
240
242
  const match = path.match(route.regex);
241
243
  if (match) {
242
244
  const urlSlug = match[route.slugGroup];
245
+ if (urlSlug === undefined) continue;
243
246
 
244
247
  // Try to find item by matching its slug field value
245
248
  const item = await this.findItemBySlug(route.collection, urlSlug, locale);
@@ -264,11 +267,7 @@ export class CMSService {
264
267
  * @param slugField - Field name containing the slug
265
268
  * @returns CMSItem if found, null otherwise
266
269
  */
267
- private findBySimpleSlug(
268
- items: CMSItem[],
269
- urlSlug: string,
270
- slugField: string
271
- ): CMSItem | null {
270
+ private findBySimpleSlug(items: CMSItem[], urlSlug: string, slugField: string): CMSItem | null {
272
271
  for (const item of items) {
273
272
  const slugValue = item[slugField];
274
273
 
@@ -291,12 +290,7 @@ export class CMSService {
291
290
  * @param locale - Optional locale for preferential matching
292
291
  * @returns CMSItem if found, null otherwise
293
292
  */
294
- private findByI18nSlug(
295
- items: CMSItem[],
296
- urlSlug: string,
297
- slugField: string,
298
- locale?: string
299
- ): CMSItem | null {
293
+ private findByI18nSlug(items: CMSItem[], urlSlug: string, slugField: string, locale?: string): CMSItem | null {
300
294
  for (const item of items) {
301
295
  const slugValue = item[slugField];
302
296
 
@@ -324,11 +318,7 @@ export class CMSService {
324
318
  * @param locale - Optional locale for i18n slug matching
325
319
  * @returns CMSItem if found, null otherwise
326
320
  */
327
- private async findItemBySlug(
328
- collection: string,
329
- urlSlug: string,
330
- locale?: string
331
- ): Promise<CMSItem | null> {
321
+ private async findItemBySlug(collection: string, urlSlug: string, locale?: string): Promise<CMSItem | null> {
332
322
  if (!this.provider) {
333
323
  return null;
334
324
  }
@@ -339,6 +329,9 @@ export class CMSService {
339
329
  const slugField = schemaInfo.schema.slugField;
340
330
  const items = await this.getCachedItems(collection);
341
331
 
332
+ // A data-only collection has no slug field — only the filename fallback can match.
333
+ if (!slugField) return await this.provider.getItemByFilename(collection, urlSlug);
334
+
342
335
  // Try simple string slug match first
343
336
  const simpleMatch = this.findBySimpleSlug(items, urlSlug, slugField);
344
337
  if (simpleMatch) return simpleMatch;
@@ -363,7 +356,7 @@ export class CMSService {
363
356
  collection: string,
364
357
  locales?: string[],
365
358
  defaultLocale?: string,
366
- options?: { excludeDrafts?: boolean }
359
+ options?: { excludeDrafts?: boolean },
367
360
  ): Promise<string[]> {
368
361
  if (!this.provider) {
369
362
  return [];
@@ -371,20 +364,21 @@ export class CMSService {
371
364
 
372
365
  const schemaInfo = this.schemaCache.get(collection);
373
366
  if (!schemaInfo) return [];
367
+ // A data-only collection (no urlPattern/slugField) contributes no sitemap URLs.
368
+ const { urlPattern, slugField } = schemaInfo.schema;
369
+ if (!urlPattern || !slugField) return [];
374
370
 
375
371
  const items = await this.getCachedItems(collection);
376
372
  const urls: string[] = [];
377
373
 
378
374
  for (const item of items) {
379
- const slugValue = item[schemaInfo.schema.slugField];
375
+ const slugValue = item[slugField];
380
376
 
381
377
  // Case 1: Simple string slug - one URL
382
378
  if (typeof slugValue === 'string') {
383
379
  // Skip if item is draft for the default locale (non-i18n case)
384
380
  if (options?.excludeDrafts && defaultLocale && isItemDraftForLocale(item, defaultLocale)) continue;
385
- urls.push(
386
- schemaInfo.schema.urlPattern.replace('{{slug}}', slugValue)
387
- );
381
+ urls.push(urlPattern.replace('{{slug}}', slugValue));
388
382
  }
389
383
  // Case 2: i18n slug - one URL per locale
390
384
  else if (isI18nValue(slugValue) && locales) {
@@ -392,7 +386,7 @@ export class CMSService {
392
386
  if (options?.excludeDrafts && isItemDraftForLocale(item, locale)) continue;
393
387
  const localizedSlug = slugValue[locale] as string;
394
388
  if (localizedSlug) {
395
- const path = schemaInfo.schema.urlPattern.replace('{{slug}}', localizedSlug);
389
+ const path = urlPattern.replace('{{slug}}', localizedSlug);
396
390
  // For default locale, no prefix; for others, add locale prefix
397
391
  if (locale === defaultLocale) {
398
392
  urls.push(path);
@@ -406,9 +400,7 @@ export class CMSService {
406
400
  else if (isI18nValue(slugValue)) {
407
401
  for (const key of Object.keys(slugValue)) {
408
402
  if (key !== '_i18n' && typeof slugValue[key] === 'string') {
409
- urls.push(
410
- schemaInfo.schema.urlPattern.replace('{{slug}}', slugValue[key] as string)
411
- );
403
+ urls.push(urlPattern.replace('{{slug}}', slugValue[key] as string));
412
404
  break; // Only use first available
413
405
  }
414
406
  }
@@ -474,17 +466,17 @@ export class CMSService {
474
466
  this.provider.getAllDrafts(collection),
475
467
  ]);
476
468
 
477
- const draftFilenames = new Set(drafts.map(d => d._filename).filter(Boolean) as string[]);
469
+ const draftFilenames = new Set(drafts.map((d) => d._filename).filter(Boolean) as string[]);
478
470
 
479
- const annotatedPublished: CMSItem[] = published.map(item => {
471
+ const annotatedPublished: CMSItem[] = published.map((item) => {
480
472
  if (item._filename && draftFilenames.has(item._filename)) {
481
473
  return { ...item, _hasDraft: true };
482
474
  }
483
475
  return item;
484
476
  });
485
477
 
486
- const publishedFilenames = new Set(published.map(i => i._filename).filter(Boolean) as string[]);
487
- const draftOnly = drafts.filter(d => d._filename && !publishedFilenames.has(d._filename));
478
+ const publishedFilenames = new Set(published.map((i) => i._filename).filter(Boolean) as string[]);
479
+ const draftOnly = drafts.filter((d) => d._filename && !publishedFilenames.has(d._filename));
488
480
 
489
481
  return [...annotatedPublished, ...draftOnly];
490
482
  }
@@ -574,17 +566,17 @@ export class CMSService {
574
566
 
575
567
  // Exclude draft items for the specified locale
576
568
  if (query.excludeDraftLocale) {
577
- items = items.filter(item => !isItemDraftForLocale(item, query.excludeDraftLocale!));
569
+ items = items.filter((item) => !isItemDraftForLocale(item, query.excludeDraftLocale!));
578
570
  }
579
571
 
580
572
  // Apply filters
581
573
  if (query.filter) {
582
- items = this.applyFilters(items, query.filter);
574
+ items = applyCmsFilters(items, query.filter);
583
575
  }
584
576
 
585
577
  // Apply sorting
586
578
  if (query.sort) {
587
- items = this.applySorting(items, query.sort);
579
+ items = applyCmsSorting(items, query.sort);
588
580
  }
589
581
 
590
582
  // Apply offset
@@ -600,63 +592,6 @@ export class CMSService {
600
592
  return items;
601
593
  }
602
594
 
603
- /**
604
- * Apply filters to items
605
- */
606
- private applyFilters(
607
- items: CMSItem[],
608
- filter: CMSFilterCondition | CMSFilterCondition[] | Record<string, unknown>
609
- ): CMSItem[] {
610
- // Handle simple object filter: { featured: true }
611
- if (!Array.isArray(filter) && !this.isFilterCondition(filter)) {
612
- return items.filter(item =>
613
- Object.entries(filter).every(([key, value]) => item[key] === value)
614
- );
615
- }
616
-
617
- // Handle array of conditions or single condition
618
- const conditions = Array.isArray(filter) ? filter : [filter as CMSFilterCondition];
619
- return items.filter(item =>
620
- conditions.every(cond => this.matchCondition(item, cond))
621
- );
622
- }
623
-
624
- /**
625
- * Check if object is a CMSFilterCondition
626
- */
627
- private isFilterCondition(obj: unknown): obj is CMSFilterCondition {
628
- return typeof obj === 'object' && obj !== null && 'field' in obj;
629
- }
630
-
631
- /**
632
- * Match a single filter condition against an item
633
- */
634
- private matchCondition(item: CMSItem, condition: CMSFilterCondition): boolean {
635
- const value = item[condition.field];
636
- const op = condition.operator || 'eq';
637
-
638
- switch (op) {
639
- case 'eq':
640
- return value === condition.value;
641
- case 'neq':
642
- return value !== condition.value;
643
- case 'gt':
644
- return (value as number) > (condition.value as number);
645
- case 'gte':
646
- return (value as number) >= (condition.value as number);
647
- case 'lt':
648
- return (value as number) < (condition.value as number);
649
- case 'lte':
650
- return (value as number) <= (condition.value as number);
651
- case 'contains':
652
- return String(value).includes(String(condition.value));
653
- case 'in':
654
- return Array.isArray(condition.value) && condition.value.includes(value);
655
- default:
656
- return false;
657
- }
658
- }
659
-
660
595
  /**
661
596
  * Get items by their IDs from a collection.
662
597
  * Used for resolving reference fields in CMS List templates.
@@ -674,56 +609,16 @@ export class CMSService {
674
609
  const allItems = await this.getCachedItems(collection);
675
610
 
676
611
  // Build lookup maps for both _id and _filename
677
- const itemMap = new Map(allItems.map(item => [item._id, item]));
678
- const filenameMap = new Map(
679
- allItems
680
- .filter(item => item._filename)
681
- .map(item => [item._filename!, item])
682
- );
612
+ const itemMap = new Map(allItems.map((item) => [item._id, item]));
613
+ const filenameMap = new Map(allItems.filter((item) => item._filename).map((item) => [item._filename!, item]));
683
614
 
684
615
  // Filter to matching items, preserving input order
685
616
  return ids
686
617
  .filter(Boolean)
687
- .map(id => itemMap.get(id) || filenameMap.get(id))
618
+ .map((id) => itemMap.get(id) || filenameMap.get(id))
688
619
  .filter((item): item is CMSItem => item !== undefined);
689
620
  }
690
621
 
691
- /**
692
- * Apply sorting to items
693
- */
694
- private applySorting(items: CMSItem[], sort: CMSSortConfig | CMSSortConfig[]): CMSItem[] {
695
- const sorts = Array.isArray(sort) ? sort : [sort];
696
-
697
- return [...items].sort((a, b) => {
698
- for (const s of sorts) {
699
- const aVal = a[s.field];
700
- const bVal = b[s.field];
701
- const isDesc = s.order === 'desc';
702
-
703
- // Handle boolean comparison
704
- if (typeof aVal === 'boolean' && typeof bVal === 'boolean') {
705
- if (aVal === bVal) continue;
706
- // For desc: true first (true > false), for asc: false first
707
- if (isDesc) {
708
- return aVal ? -1 : 1;
709
- } else {
710
- return aVal ? 1 : -1;
711
- }
712
- }
713
-
714
- // Standard comparison (compare as primitives)
715
- let result = 0;
716
- if ((aVal as string | number) < (bVal as string | number)) result = -1;
717
- else if ((aVal as string | number) > (bVal as string | number)) result = 1;
718
-
719
- if (result !== 0) {
720
- return isDesc ? -result : result;
721
- }
722
- }
723
- return 0;
724
- });
725
- }
726
-
727
622
  /**
728
623
  * Find all references to a specific item across all collections.
729
624
  * Used to warn users before deleting an item that is referenced elsewhere.
@@ -732,10 +627,7 @@ export class CMSService {
732
627
  * @param targetId - ID or filename of the target item
733
628
  * @returns Array of ReferenceLocation objects describing where the item is referenced
734
629
  */
735
- async findReferencesTo(
736
- targetCollection: string,
737
- targetId: string
738
- ): Promise<ReferenceLocation[]> {
630
+ async findReferencesTo(targetCollection: string, targetId: string): Promise<ReferenceLocation[]> {
739
631
  if (!this.provider) {
740
632
  return [];
741
633
  }
@@ -747,8 +639,9 @@ export class CMSService {
747
639
  const { schema } = schemaInfo;
748
640
 
749
641
  // Find reference fields pointing to targetCollection
750
- const refFields = Object.entries(schema.fields)
751
- .filter(([_, def]) => def.type === 'reference' && def.collection === targetCollection);
642
+ const refFields = Object.entries(schema.fields).filter(
643
+ ([_, def]) => def.type === 'reference' && def.collection === targetCollection,
644
+ );
752
645
 
753
646
  if (refFields.length === 0) continue;
754
647
 
@@ -782,10 +675,7 @@ export class CMSService {
782
675
  * For multi-reference fields: filters out the target ID
783
676
  * @returns Number of items updated
784
677
  */
785
- async removeReferencesTo(
786
- targetCollection: string,
787
- targetId: string
788
- ): Promise<number> {
678
+ async removeReferencesTo(targetCollection: string, targetId: string): Promise<number> {
789
679
  if (!this.provider) {
790
680
  return 0;
791
681
  }
@@ -806,7 +696,7 @@ export class CMSService {
806
696
  let newValue: string | string[] | null;
807
697
  if (fieldDef?.multiple && Array.isArray(currentValue)) {
808
698
  // Multi-reference: filter out target
809
- newValue = currentValue.filter(id => id !== targetId);
699
+ newValue = currentValue.filter((id) => id !== targetId);
810
700
  if (newValue.length === 0) newValue = null;
811
701
  } else {
812
702
  // Single reference: clear it
@@ -1,8 +1,8 @@
1
- import { test, expect, describe, beforeEach, afterAll, mock } from 'bun:test';
2
- import { mkdtempSync, rmSync, existsSync, readFileSync, mkdirSync } from 'fs';
3
- import { tmpdir } from 'os';
4
- import { join } from 'path';
5
- import { ComponentService, rewriteComponentRefs, type ComponentServiceFs, type ComponentLoader, type ComponentWriter } from './componentService';
1
+ import { test, expect, describe, beforeEach, afterAll } from 'bun:test';
2
+ import { mkdtempSync, rmSync, existsSync, readFileSync, mkdirSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { ComponentService, rewriteComponentRefs, type ComponentWriter } from './componentService';
6
6
  import { setProjectRoot, getProjectRoot, projectPaths } from '../projectContext';
7
7
  import { createMockComponentDefinition } from '../../test-utils';
8
8
  import {
@@ -108,8 +108,8 @@ describe('ComponentService', () => {
108
108
  const allComponents = componentService.getAllComponents();
109
109
 
110
110
  expect(allComponents).toBeDefined();
111
- expect(allComponents['TestComponent']).toBeDefined();
112
- expect(allComponents['Button']).toBeDefined();
111
+ expect(allComponents.TestComponent).toBeDefined();
112
+ expect(allComponents.Button).toBeDefined();
113
113
  });
114
114
 
115
115
  test('should return empty object when no components loaded', () => {
@@ -135,33 +135,41 @@ describe('ComponentService', () => {
135
135
  });
136
136
 
137
137
  test('should return false for missing structure', () => {
138
- expect(componentService.validateComponentStructure({
139
- component: {}
140
- } as any)).toBe(false);
138
+ expect(
139
+ componentService.validateComponentStructure({
140
+ component: {},
141
+ } as any),
142
+ ).toBe(false);
141
143
  });
142
144
 
143
145
  test('should return false for invalid structure type', () => {
144
- expect(componentService.validateComponentStructure({
145
- component: { structure: 'invalid' }
146
- } as any)).toBe(false);
146
+ expect(
147
+ componentService.validateComponentStructure({
148
+ component: { structure: 'invalid' },
149
+ } as any),
150
+ ).toBe(false);
147
151
  });
148
152
 
149
153
  test('should return false for structure without type', () => {
150
- expect(componentService.validateComponentStructure({
151
- component: { structure: { tag: 'div' } }
152
- } as any)).toBe(false);
154
+ expect(
155
+ componentService.validateComponentStructure({
156
+ component: { structure: { tag: 'div' } },
157
+ } as any),
158
+ ).toBe(false);
153
159
  });
154
160
 
155
161
  test('should return true for structure with valid type', () => {
156
- expect(componentService.validateComponentStructure({
157
- component: {
158
- structure: {
159
- type: NODE_TYPE.NODE,
160
- tag: 'div',
161
- children: []
162
- }
163
- }
164
- } as any)).toBe(true);
162
+ expect(
163
+ componentService.validateComponentStructure({
164
+ component: {
165
+ structure: {
166
+ type: NODE_TYPE.NODE,
167
+ tag: 'div',
168
+ children: [],
169
+ },
170
+ },
171
+ } as any),
172
+ ).toBe(true);
165
173
  });
166
174
  });
167
175
 
@@ -209,6 +217,67 @@ describe('ComponentService', () => {
209
217
 
210
218
  expect(filePath).toContain('Button.json');
211
219
  });
220
+
221
+ test('keeps javascript in the in-memory cache (JSON payload stays js-less)', async () => {
222
+ // The `.json` file must not carry javascript (the sibling `.js` owns it), but the
223
+ // LIVE cache must — otherwise the SSR preview renders the component without its
224
+ // script between this save and the next file-watcher reload.
225
+ const componentData = createMockComponentDefinition('TestComponent');
226
+ if (componentData.component) {
227
+ componentData.component.javascript = 'console.log("kept");';
228
+ }
229
+
230
+ await componentService.saveComponent('TestComponent', componentData);
231
+
232
+ const savedData = JSON.parse((mockFs.writeFile as any).mock.calls[0][1]);
233
+ expect(savedData.component.javascript).toBeUndefined();
234
+ expect(componentService.getComponent('TestComponent')?.component?.javascript).toBe('console.log("kept");');
235
+ });
236
+
237
+ test('a js-less save preserves the previously cached javascript', async () => {
238
+ // A props/structure-only save (the common PropsPanel edit) must not erase the
239
+ // component's script from the cache.
240
+ const withJs = createMockComponentDefinition('TestComponent');
241
+ if (withJs.component) withJs.component.javascript = 'console.log("orig");';
242
+ await componentService.saveComponent('TestComponent', withJs);
243
+
244
+ const withoutJs = createMockComponentDefinition('TestComponent');
245
+ await componentService.saveComponent('TestComponent', withoutJs);
246
+
247
+ expect(componentService.getComponent('TestComponent')?.component?.javascript).toBe('console.log("orig");');
248
+ });
249
+ });
250
+
251
+ describe('saveComponent with a single-file writer (astro format)', () => {
252
+ test('the writer receives the FULL def — javascript preserved, not stripped', async () => {
253
+ // Single-file writers (AstroComponentWriter) re-emit the WHOLE file from the def
254
+ // they're given: a js-less def would silently delete the component's <script>
255
+ // from the source of truth. Regression test for the rename-cascade data loss.
256
+ const written: ComponentDefinition[] = [];
257
+ const writer: ComponentWriter = {
258
+ componentsDir: () => '/tmp/fake-components',
259
+ writeComponent: async (_dir, _name, def) => {
260
+ written.push(def);
261
+ },
262
+ writeJavaScript: async () => {},
263
+ writeCSS: async () => {},
264
+ removeComponent: async () => {},
265
+ componentExists: async () => false,
266
+ moveComponent: async () => {},
267
+ };
268
+ const service = new ComponentService({ loader: mockLoader, writer });
269
+
270
+ const withJs = createMockComponentDefinition('Header');
271
+ if (withJs.component) withJs.component.javascript = 'menuToggle();';
272
+ await service.saveComponent('Header', withJs);
273
+ expect(written[0]?.component?.javascript).toBe('menuToggle();');
274
+
275
+ // A js-less follow-up save (props-only edit, or the href-rename cascade
276
+ // re-saving a cached def) keeps the cached script in the emitted file.
277
+ const withoutJs = createMockComponentDefinition('Header');
278
+ await service.saveComponent('Header', withoutJs);
279
+ expect(written[1]?.component?.javascript).toBe('menuToggle();');
280
+ });
212
281
  });
213
282
 
214
283
  describe('saveComponentJavaScript', () => {
@@ -260,10 +329,14 @@ describe('ComponentService', () => {
260
329
  tag: 'div',
261
330
  children: [
262
331
  { type: 'component', component: 'Old' },
263
- { type: 'node', tag: 'section', children: [
264
- { type: 'component', component: 'Old', props: { text: 'hi' } },
265
- { type: 'component', component: 'Keep' },
266
- ] },
332
+ {
333
+ type: 'node',
334
+ tag: 'section',
335
+ children: [
336
+ { type: 'component', component: 'Old', props: { text: 'hi' } },
337
+ { type: 'component', component: 'Keep' },
338
+ ],
339
+ },
267
340
  ],
268
341
  };
269
342
 
@@ -271,8 +344,8 @@ describe('ComponentService', () => {
271
344
 
272
345
  expect(changed).toBe(true);
273
346
  expect((tree.children[0] as any).component).toBe('New');
274
- expect(((tree.children[1] as any).children[0]).component).toBe('New');
275
- expect(((tree.children[1] as any).children[1]).component).toBe('Keep');
347
+ expect((tree.children[1] as any).children[0].component).toBe('New');
348
+ expect((tree.children[1] as any).children[1].component).toBe('Keep');
276
349
  });
277
350
 
278
351
  test('returns false when no references match', () => {
@@ -293,10 +366,7 @@ describe('ComponentService', () => {
293
366
  type: 'node',
294
367
  tag: 'button',
295
368
  attributes: { class: 'Button primary' },
296
- children: [
297
- { type: 'embed', html: '<button>Button</button>' },
298
- 'Click the Button below',
299
- ],
369
+ children: [{ type: 'embed', html: '<button>Button</button>' }, 'Click the Button below'],
300
370
  };
301
371
 
302
372
  const changed = rewriteComponentRefs(tree, 'Button', 'Cta');
@@ -395,12 +465,22 @@ describe('ComponentService', () => {
395
465
  const dir = '/virtual/src/components';
396
466
  const writer: ComponentWriter = {
397
467
  componentsDir: () => dir,
398
- writeComponent: async (d, n) => { calls.push({ op: 'writeComponent', dir: d, name: n }); },
399
- writeJavaScript: async (d, n, js) => { calls.push({ op: 'writeJavaScript', dir: d, name: n, extra: js }); },
400
- writeCSS: async (d, n, css) => { calls.push({ op: 'writeCSS', dir: d, name: n, extra: css }); },
401
- removeComponent: async (d, n) => { calls.push({ op: 'removeComponent', dir: d, name: n }); },
468
+ writeComponent: async (d, n) => {
469
+ calls.push({ op: 'writeComponent', dir: d, name: n });
470
+ },
471
+ writeJavaScript: async (d, n, js) => {
472
+ calls.push({ op: 'writeJavaScript', dir: d, name: n, extra: js });
473
+ },
474
+ writeCSS: async (d, n, css) => {
475
+ calls.push({ op: 'writeCSS', dir: d, name: n, extra: css });
476
+ },
477
+ removeComponent: async (d, n) => {
478
+ calls.push({ op: 'removeComponent', dir: d, name: n });
479
+ },
402
480
  componentExists: async () => false,
403
- moveComponent: async (s, t, n, dn) => { calls.push({ op: 'moveComponent', dir: `${s}->${t}`, name: n, extra: dn }); },
481
+ moveComponent: async (s, t, n, dn) => {
482
+ calls.push({ op: 'moveComponent', dir: `${s}->${t}`, name: n, extra: dn });
483
+ },
404
484
  };
405
485
  return { writer, calls, dir };
406
486
  }
@@ -410,9 +490,9 @@ describe('ComponentService', () => {
410
490
  const svc = new ComponentService({ writer });
411
491
  await svc.saveComponent('Widget', createMockComponentDefinition('Widget'));
412
492
  expect(calls).toHaveLength(1);
413
- expect(calls[0].op).toBe('writeComponent');
414
- expect(calls[0].dir).toBe(dir);
415
- expect(calls[0].name).toBe('Widget');
493
+ expect(calls[0]!.op).toBe('writeComponent');
494
+ expect(calls[0]!.dir).toBe(dir);
495
+ expect(calls[0]!.name).toBe('Widget');
416
496
  expect(svc.hasComponent('Widget')).toBe(true);
417
497
  });
418
498
 
@@ -422,7 +502,7 @@ describe('ComponentService', () => {
422
502
  await svc.saveComponent('Widget', createMockComponentDefinition('Widget'));
423
503
  await svc.saveComponentCSS('Widget', '.w{}');
424
504
  await svc.saveComponentJavaScript('Widget', 'x();');
425
- const ops = calls.map(c => c.op);
505
+ const ops = calls.map((c) => c.op);
426
506
  expect(ops).toEqual(['writeComponent', 'writeCSS', 'writeJavaScript']);
427
507
  const cached = svc.getComponent('Widget');
428
508
  expect(cached?.component?.css).toBe('.w{}');
@@ -437,7 +517,7 @@ describe('ComponentService', () => {
437
517
  });
438
518
  await svc.loadAllComponents();
439
519
  await svc.renameComponent('Widget', 'Gadget');
440
- const move = calls.find(c => c.op === 'moveComponent');
520
+ const move = calls.find((c) => c.op === 'moveComponent');
441
521
  expect(move).toBeDefined();
442
522
  expect(move!.name).toBe('Widget');
443
523
  expect(move!.extra).toBe('Gadget'); // dstName