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
@@ -2,13 +2,13 @@
2
2
  * Tree path utilities for parsing and navigating tree structure paths
3
3
  */
4
4
 
5
- import { ComponentNode, PageData, JSONPage } from './types';
5
+ import type { ComponentNode, PageData } from './types';
6
6
  import type { PropDefinition } from './types';
7
7
  import type { Path } from './pathArrayUtils';
8
8
  import { stringToPath } from './pathArrayUtils';
9
9
 
10
10
  // Type for page data that can have component structure
11
- type PageDataWithComponent = PageData & {
11
+ type _PageDataWithComponent = PageData & {
12
12
  component?: {
13
13
  structure?: ComponentNode;
14
14
  interface?: Record<string, PropDefinition>;
@@ -28,12 +28,12 @@ export interface NodeLocation {
28
28
  */
29
29
  export function validateComponentStructure(pageData: PageData | null): boolean {
30
30
  if (!pageData) return false;
31
-
31
+
32
32
  // Check for page format (has root property)
33
33
  if ('root' in pageData && pageData.root) {
34
34
  return true;
35
35
  }
36
-
36
+
37
37
  // Check for component format (has component.structure property)
38
38
  if ('component' in pageData && pageData.component) {
39
39
  if ('structure' in pageData.component && pageData.component.structure) {
@@ -42,7 +42,7 @@ export function validateComponentStructure(pageData: PageData | null): boolean {
42
42
  // Component exists but structure is missing
43
43
  return false;
44
44
  }
45
-
45
+
46
46
  return false;
47
47
  }
48
48
 
@@ -52,12 +52,12 @@ export function validateComponentStructure(pageData: PageData | null): boolean {
52
52
  */
53
53
  export function getRootData(pageData: PageData | null): ComponentNode | null {
54
54
  if (!pageData) return null;
55
-
55
+
56
56
  // Check for page format (has root property)
57
57
  if ('root' in pageData && pageData.root) {
58
58
  return pageData.root;
59
59
  }
60
-
60
+
61
61
  // Check for component format (has component.structure property)
62
62
  if ('component' in pageData && pageData.component) {
63
63
  if ('structure' in pageData.component) {
@@ -70,7 +70,7 @@ export function getRootData(pageData: PageData | null): ComponentNode | null {
70
70
  // Component exists but structure field is missing
71
71
  }
72
72
  }
73
-
73
+
74
74
  return null;
75
75
  }
76
76
 
@@ -79,10 +79,7 @@ export function getRootData(pageData: PageData | null): ComponentNode | null {
79
79
  * Handles paths like: root_0, root_0_children_0, root_0_children_0_children_1
80
80
  * Also accepts array paths: [0], [0,0], [0,0,1]
81
81
  */
82
- export function getParentAndIndexFromPath(
83
- path: Path | string,
84
- pageData: PageData
85
- ): NodeLocation | null {
82
+ export function getParentAndIndexFromPath(path: Path | string, pageData: PageData): NodeLocation | null {
86
83
  const rootData = getRootData(pageData);
87
84
  if (!rootData) return null;
88
85
 
@@ -94,7 +91,7 @@ export function getParentAndIndexFromPath(
94
91
  return {
95
92
  parent: pageData,
96
93
  index: 0,
97
- node: rootData
94
+ node: rootData,
98
95
  };
99
96
  }
100
97
 
@@ -106,26 +103,27 @@ export function getParentAndIndexFromPath(
106
103
  // Skip first element (root index 0), navigate through rest
107
104
  for (let i = 1; i < pathArray.length; i++) {
108
105
  const childIndex = pathArray[i];
106
+ if (childIndex === undefined) return null;
109
107
 
110
- if (!current || !('children' in current) || !Array.isArray(current.children)) {
111
- return null;
112
- }
113
-
114
- if (isNaN(childIndex) || childIndex < 0 || childIndex >= current.children.length) {
115
- return null;
116
- }
108
+ if (!current || !('children' in current) || !Array.isArray(current.children)) {
109
+ return null;
110
+ }
111
+
112
+ if (Number.isNaN(childIndex) || childIndex < 0 || childIndex >= current.children.length) {
113
+ return null;
114
+ }
117
115
 
118
116
  parent = current;
119
- const child = current.children[childIndex];
120
- if (typeof child === 'string') {
121
- return null;
122
- }
123
- current = child;
124
- lastIndex = childIndex;
117
+ const child = current.children[childIndex];
118
+ if (child === undefined || typeof child === 'string') {
119
+ return null;
120
+ }
121
+ current = child;
122
+ lastIndex = childIndex;
125
123
 
126
- // If this is the last navigation step, we found our node
124
+ // If this is the last navigation step, we found our node
127
125
  if (i === pathArray.length - 1) {
128
- return { parent, index: childIndex, node: current };
126
+ return { parent, index: childIndex, node: current };
129
127
  }
130
128
  }
131
129
 
@@ -137,11 +135,44 @@ export function getParentAndIndexFromPath(
137
135
  return null;
138
136
  }
139
137
 
138
+ /**
139
+ * Find the path to a node by reference identity (===) within the page tree.
140
+ *
141
+ * Used to recover the correct tree path when a stored selection path has drifted
142
+ * out of sync with the current pageData (e.g. a play-mode reload re-parsed the
143
+ * tree) but the selected node object is still present in the tree. Returns a path
144
+ * in the same `[0, ...childIndices]` form that {@link getParentAndIndexFromPath}
145
+ * consumes, or null if the node isn't reachable from the root by reference.
146
+ */
147
+ export function findPathToNodeByReference(pageData: PageData, target: ComponentNode): Path | null {
148
+ const root = getRootData(pageData);
149
+ if (!root) return null;
150
+
151
+ // DFS from the root (path [0]); the children of a node at `path` are `[...path, i]`.
152
+ const stack: { node: ComponentNode; path: Path }[] = [{ node: root, path: [0] }];
153
+ while (stack.length > 0) {
154
+ const { node, path } = stack.pop()!;
155
+ if (node === target) return path;
156
+ const children = 'children' in node ? node.children : undefined;
157
+ if (Array.isArray(children)) {
158
+ for (let i = 0; i < children.length; i++) {
159
+ const child = children[i];
160
+ if (child && typeof child === 'object') {
161
+ stack.push({ node: child as ComponentNode, path: [...path, i] });
162
+ }
163
+ }
164
+ }
165
+ }
166
+ return null;
167
+ }
168
+
140
169
  /**
141
170
  * Extract structure without styles/props for tree comparison
142
171
  * This allows tree to only regenerate when structure changes, not styles
143
172
  */
144
- export function extractStructureWithoutStyles(node: ComponentNode | string | null | undefined): ComponentNode | string | null | undefined {
173
+ export function extractStructureWithoutStyles(
174
+ node: ComponentNode | string | null | undefined,
175
+ ): ComponentNode | string | null | undefined {
145
176
  if (!node || typeof node === 'string') {
146
177
  return node;
147
178
  }
@@ -157,13 +188,13 @@ export function extractStructureWithoutStyles(node: ComponentNode | string | nul
157
188
  const component = 'component' in node ? (node as any).component : undefined;
158
189
  const ifCondition = 'if' in node ? node.if : undefined;
159
190
  const children = 'children' in node ? node.children : undefined;
160
-
191
+
161
192
  // Recursively process children
162
193
  let processedChildren: ComponentNode | (ComponentNode | string)[] | string | undefined;
163
194
  if (children) {
164
195
  if (Array.isArray(children)) {
165
196
  processedChildren = children.map((child: ComponentNode | string) =>
166
- typeof child === 'string' ? child : extractStructureWithoutStyles(child) as ComponentNode
197
+ typeof child === 'string' ? child : (extractStructureWithoutStyles(child) as ComponentNode),
167
198
  );
168
199
  } else if (typeof children === 'string') {
169
200
  processedChildren = children;
@@ -202,7 +233,7 @@ export function calculateInsertionIndex(
202
233
  linearIndex?: number;
203
234
  },
204
235
  parentChildren: Array<ComponentNode | string>,
205
- nodeDataMap: Map<string, ComponentNode | string>
236
+ nodeDataMap: Map<string, ComponentNode | string>,
206
237
  ): number {
207
238
  if (target.childIndex !== undefined && target.childIndex >= 0) {
208
239
  // Most reliable: use childIndex directly
@@ -250,13 +281,13 @@ export function isValidNodeLocation(location: unknown): location is NodeLocation
250
281
  if (!location || typeof location !== 'object') {
251
282
  return false;
252
283
  }
253
-
284
+
254
285
  // Use proper type narrowing instead of assertion
255
286
  const loc = location as Record<string, unknown>;
256
287
  const hasParent = 'parent' in loc && (loc.parent === null || typeof loc.parent === 'object');
257
288
  const hasIndex = 'index' in loc && typeof loc.index === 'number' && loc.index >= 0;
258
289
  const hasNode = 'node' in loc && (loc.node === null || typeof loc.node === 'object');
259
-
290
+
260
291
  return hasParent && hasIndex && hasNode;
261
292
  }
262
293
 
@@ -267,7 +298,7 @@ export function isValidPath(path: unknown): path is Path {
267
298
  if (!Array.isArray(path)) {
268
299
  return false;
269
300
  }
270
-
301
+
271
302
  // Path is an array of numbers
272
303
  return path.every((item): item is number => typeof item === 'number' && item >= 0);
273
304
  }
@@ -275,28 +306,24 @@ export function isValidPath(path: unknown): path is Path {
275
306
  /**
276
307
  * Type guard to check if parent is PageData
277
308
  */
278
- export function isPageDataParent(
279
- parent: ComponentNode | PageData | null
280
- ): parent is PageData {
309
+ export function isPageDataParent(parent: ComponentNode | PageData | null): parent is PageData {
281
310
  if (!parent) return false;
282
-
311
+
283
312
  // Check for JSONPage format (has root property)
284
313
  if ('root' in parent) return true;
285
-
314
+
286
315
  // Check for component format - component must be an object, not a string
287
316
  if ('component' in parent) {
288
317
  return typeof parent.component === 'object' && parent.component !== null;
289
318
  }
290
-
319
+
291
320
  return false;
292
321
  }
293
322
 
294
323
  /**
295
324
  * Type guard to check if parent is ComponentNode
296
325
  */
297
- export function isComponentNodeParent(
298
- parent: ComponentNode | PageData | null
299
- ): parent is ComponentNode {
326
+ export function isComponentNodeParent(parent: ComponentNode | PageData | null): parent is ComponentNode {
300
327
  if (!parent) return false;
301
328
  // Check for 'type' property (all ComponentNodes have it)
302
329
  // Then check for specific node types that can have children:
@@ -306,29 +333,34 @@ export function isComponentNodeParent(
306
333
  // - LinkNode: type === 'link'
307
334
  // - EmbedNode: type === 'embed'
308
335
  // - ListNode: type === 'list' (also handles legacy 'cms-list' for migration)
336
+ // - CustomNode: type === 'custom' (opaque .astro, accepts slotted children)
337
+ // - IslandNode: type === 'island' (BYO framework island, accepts slotted children)
309
338
  const nodeType = (parent as any).type;
310
- return 'type' in parent && (
311
- 'tag' in parent ||
312
- 'component' in parent ||
313
- nodeType === 'slot' ||
314
- nodeType === 'link' ||
315
- nodeType === 'embed' ||
316
- nodeType === 'cms-list' || // Legacy support for migration
317
- nodeType === 'list'
339
+ return (
340
+ 'type' in parent &&
341
+ ('tag' in parent ||
342
+ 'component' in parent ||
343
+ nodeType === 'slot' ||
344
+ nodeType === 'link' ||
345
+ nodeType === 'embed' ||
346
+ nodeType === 'custom' ||
347
+ nodeType === 'island' ||
348
+ nodeType === 'cms-list' || // Legacy support for migration
349
+ nodeType === 'list')
318
350
  );
319
351
  }
320
352
 
321
353
  /**
322
354
  * Get children array from parent (handles both PageData and ComponentNode)
323
355
  */
324
- export function getParentChildren(
325
- parent: ComponentNode | PageData | null
326
- ): Array<ComponentNode | string> | null {
356
+ export function getParentChildren(parent: ComponentNode | PageData | null): Array<ComponentNode | string> | null {
327
357
  if (!parent) return null;
328
-
358
+
329
359
  if (isPageDataParent(parent)) {
330
360
  const rootData = getRootData(parent);
331
- return rootData && 'children' in rootData ? (rootData.children as Array<ComponentNode | string> | undefined) || null : null;
361
+ return rootData && 'children' in rootData
362
+ ? (rootData.children as Array<ComponentNode | string> | undefined) || null
363
+ : null;
332
364
  }
333
365
 
334
366
  if (isComponentNodeParent(parent)) {
@@ -343,10 +375,10 @@ export function getParentChildren(
343
375
  */
344
376
  export function setParentChildren(
345
377
  parent: ComponentNode | PageData | null,
346
- children: Array<ComponentNode | string>
378
+ children: Array<ComponentNode | string>,
347
379
  ): void {
348
380
  if (!parent) return;
349
-
381
+
350
382
  if (isPageDataParent(parent)) {
351
383
  const rootData = getRootData(parent);
352
384
  if (rootData && 'children' in rootData) {
@@ -358,4 +390,3 @@ export function setParentChildren(
358
390
  }
359
391
  }
360
392
  }
361
-
@@ -16,9 +16,10 @@ export interface HMRMessage {
16
16
  | 'hmr:libraries-update'
17
17
  | 'hmr:cms-update'
18
18
  | 'hmr:cms-collections-update'
19
- | 'hmr:config-update';
19
+ | 'hmr:config-update'
20
+ | 'hmr:stylesheet-update';
20
21
  path?: string;
21
- collection?: string; // For CMS updates
22
+ collection?: string; // For CMS updates
22
23
  }
23
24
 
24
25
  export interface PageListResponse {
@@ -39,12 +40,57 @@ export interface PageCmsConfig {
39
40
  id: string;
40
41
  /** Display name for the collection */
41
42
  name: string;
42
- /** Which field to use for URL slug */
43
- slugField: string;
44
- /** URL pattern, e.g., "/blog/{{slug}}" */
45
- urlPattern: string;
43
+ /** Which field to use for URL slug. Omitted for a data-only collection (no per-item route). */
44
+ slugField?: string;
45
+ /** URL pattern, e.g., "/blog/{{slug}}". Omitted for a data-only collection (no per-item route). */
46
+ urlPattern?: string;
46
47
  /** Field definitions */
47
48
  fields: Record<string, CMSFieldDefinition>;
49
+ /** Generate an RSS 2.0 feed at `<route-prefix>/rss.xml` (routed collections only). */
50
+ rss?: boolean;
51
+ }
52
+
53
+ /** Field type discovered for one SSR data-source result (drives binding autocomplete). */
54
+ export type DataFieldType = 'string' | 'number' | 'boolean' | 'date' | 'image' | 'object';
55
+
56
+ /**
57
+ * A single declarative data source on an SSR page (`meta.source === 'ssr'`). v1 supports a
58
+ * declarative HTTP `GET` whose response is bound into the page body. The fetch runs per
59
+ * request via the `loadPageData` runtime helper. Anything beyond "GET → bind fields"
60
+ * (multi-step fetches, mutations, custom transforms) stays a hand-authored escape-hatch page.
61
+ */
62
+ export interface PageDataSource {
63
+ /** Source kind. Only declarative HTTP fetch in v1. */
64
+ type: 'fetch';
65
+ /**
66
+ * Endpoint URL. May interpolate request inputs and env secrets via `{{…}}`
67
+ * (e.g. `https://api…/repos/{{params.owner}}?q={{query.q}}`,
68
+ * `headers: { authorization: "Bearer {{env.TOKEN}}" }`). The origin is author-fixed —
69
+ * only path/query segments interpolate (SSRF guard).
70
+ */
71
+ url: string;
72
+ /** HTTP method. GET only in v1 (no modeled mutations). */
73
+ method?: 'GET';
74
+ /** Header map; values may interpolate `{{env.SECRET}}` (resolved server-side, never in markup). */
75
+ headers?: Record<string, string>;
76
+ /** Dot-path into the JSON response to the payload (e.g. "data.items"); empty = whole response. */
77
+ path?: string;
78
+ /** Shape of ONE result, discovered via /api/remote-sample. Drives binding autocomplete. */
79
+ fields?: Record<string, DataFieldType>;
80
+ /** 'object' (bind `{{name.field}}`) or 'list' (drives a list node). Default 'object'. */
81
+ cardinality?: 'object' | 'list';
82
+ }
83
+
84
+ /**
85
+ * SSR page data configuration embedded in page meta (`meta.data`, required when
86
+ * `meta.source === 'ssr'`). Each source key becomes a binding scope in the page body
87
+ * (`{{repo.stargazers_count}}`). The `getStaticPaths`-equivalent boilerplate
88
+ * (`const { … } = await loadPageData(meta.data, Astro)`) is derived by the emitter and
89
+ * skipped by the parser — `meta.data` is authoritative. See docs/meno-astro-ssr-page-type.md.
90
+ */
91
+ export interface PageDataConfig {
92
+ /** Named data sources. The key becomes a binding scope in the body. */
93
+ sources: Record<string, PageDataSource>;
48
94
  }
49
95
 
50
96
  /**
@@ -73,15 +119,66 @@ export interface PageMetaData {
73
119
  ogType?: string;
74
120
  /** Translated slugs for each locale, e.g., { "en": "about", "pl": "o-nas" } */
75
121
  slugs?: Record<string, string>;
76
- /** Data source: 'static' (default) or 'cms' */
77
- source?: 'static' | 'cms';
122
+ /** Data source: 'static' (default), 'cms' (content collection), or 'ssr' (live per-request fetch) */
123
+ source?: 'static' | 'cms' | 'ssr';
78
124
  /** CMS configuration with embedded schema (required when source: 'cms') */
79
125
  cms?: PageCmsConfig;
126
+ /** SSR data-source configuration (required when source: 'ssr') */
127
+ data?: PageDataConfig;
128
+ /**
129
+ * Dynamic-route param names for an SSR page that lives at a `[param]` path
130
+ * (e.g. `["slug"]` for `src/pages/repo/[slug].astro`). When set on an `source: 'ssr'`
131
+ * page, the emitter exposes `const params = Astro.params` so the body can bind
132
+ * `{{params.<name>}}` and data-source URLs can interpolate `{{params.<name>}}`. The route
133
+ * is served on demand (no getStaticPaths) under `output: 'server'`.
134
+ */
135
+ routeParams?: string[];
136
+ /**
137
+ * Sample param values used ONLY to preview a dynamic `[param]` route in the editor — the
138
+ * editor loads e.g. `/repo/astro` instead of the un-matchable `/repo/[slug]`
139
+ * (`{ slug: "astro" }`). Editor-only hint; ignored at build/runtime.
140
+ */
141
+ routePreview?: Record<string, string>;
80
142
  /** Draft pages are excluded from static build, sitemap, and robots.txt */
81
143
  draft?: boolean;
82
144
  /** External libraries for this page (extends or replaces global) */
83
145
  libraries?: PageLibrariesConfig;
84
146
  /** Custom code injected into head/body for this page */
85
147
  customCode?: CustomCodeConfig;
148
+ /**
149
+ * Verbatim attributes of the source document's `<html>` element, preserved by the
150
+ * faithful-mirror importer (e.g. Webflow's `data-wf-page` / `data-wf-site`, which its
151
+ * client interaction engine reads to arm a page's interactions). BaseLayout spreads them
152
+ * onto its own `<html>` so a mirrored site boots its runtime exactly as the original —
153
+ * without them, e.g. a Webflow preloader never animates out. `lang` is excluded (BaseLayout
154
+ * owns it via locale routing).
155
+ */
156
+ htmlAttributes?: Record<string, string>;
157
+ /** Opt this page into Astro View Transitions (renders `<ClientRouter>` in the head) */
158
+ viewTransitions?: boolean;
159
+ /** Discourage search indexing (renders `<meta name="robots" content="noindex">`) */
160
+ noindex?: boolean;
161
+ /** Per-page sitemap settings (priority/changefreq/exclude) */
162
+ sitemap?: SitemapPageConfig;
163
+ /**
164
+ * Per-page static/SSR override, emitted as Astro's own top-level
165
+ * `export const prerender = <value>`. `true` forces this route to be statically
166
+ * generated, `false` makes it server-rendered on demand (requires SSR `output: 'server'`
167
+ * + an adapter). Omitted (undefined) → the route inherits the project's `output` default.
168
+ * CMS template routes always prerender (they depend on `getStaticPaths`), so this override
169
+ * applies only to regular pages.
170
+ */
171
+ prerender?: boolean;
86
172
  }
87
173
 
174
+ /**
175
+ * Per-page sitemap settings (surfaced in the page SEO panel).
176
+ */
177
+ export interface SitemapPageConfig {
178
+ /** Relative importance, 0.0–1.0 (sitemap `<priority>`). */
179
+ priority?: number;
180
+ /** Expected update cadence (sitemap `<changefreq>`). */
181
+ changefreq?: 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never';
182
+ /** Omit this page (and all locale variants) from the generated sitemap. */
183
+ exclude?: boolean;
184
+ }
@@ -0,0 +1,34 @@
1
+ import { describe, expect, it } from 'bun:test';
2
+ import { singularize } from './cms';
3
+
4
+ describe('singularize', () => {
5
+ it('applies English plural→singular rules', () => {
6
+ expect(singularize('posts')).toBe('post');
7
+ expect(singularize('categories')).toBe('category'); // -ies → -y
8
+ expect(singularize('boxes')).toBe('box'); // -xes → -x
9
+ expect(singularize('classes')).toBe('class'); // -ses → -s
10
+ expect(singularize('people')).toBe('person'); // irregular
11
+ });
12
+
13
+ it('leaves an already-singular name unchanged', () => {
14
+ expect(singularize('author')).toBe('author');
15
+ expect(singularize('category')).toBe('category');
16
+ });
17
+
18
+ it('coerces a non-identifier collection name into a valid JS identifier', () => {
19
+ // The result is emitted as a `.map` loop variable, so it must be a legal identifier —
20
+ // a hyphenated/spaced/dotted collection name would otherwise emit `.map((case-study) => …)`.
21
+ expect(singularize('case-study')).toBe('caseStudy');
22
+ expect(singularize('case-study-category')).toBe('caseStudyCategory');
23
+ expect(singularize('blog posts')).toBe('blogPost'); // space-separated + pluralized last word
24
+ expect(singularize('2024-items')).toBe('_2024Item'); // leading digit gets prefixed
25
+ });
26
+
27
+ it('degrades a non-string / empty input to the generic `item` rather than throwing', () => {
28
+ expect(singularize('')).toBe('item');
29
+ // @ts-expect-error — defensive guard against undefined (was a hard crash: collection.endsWith)
30
+ expect(singularize(undefined)).toBe('item');
31
+ // @ts-expect-error — defensive guard against null
32
+ expect(singularize(null)).toBe('item');
33
+ });
34
+ });