@wildo-ai/saas-website 1.1.1

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 (383) hide show
  1. package/LICENSE +34 -0
  2. package/dist/esm/.builder.pid +9 -0
  3. package/dist/esm/astro/blog-post-bridge.d.ts +324 -0
  4. package/dist/esm/astro/blog-post-bridge.d.ts.map +1 -0
  5. package/dist/esm/astro/blog-post-bridge.js +57 -0
  6. package/dist/esm/astro/blog-post-bridge.js.map +1 -0
  7. package/dist/esm/astro/blog-post-head.renderer.d.ts +129 -0
  8. package/dist/esm/astro/blog-post-head.renderer.d.ts.map +1 -0
  9. package/dist/esm/astro/blog-post-head.renderer.js +139 -0
  10. package/dist/esm/astro/blog-post-head.renderer.js.map +1 -0
  11. package/dist/esm/astro/bridge-runtime.d.ts +224 -0
  12. package/dist/esm/astro/bridge-runtime.d.ts.map +1 -0
  13. package/dist/esm/astro/bridge-runtime.js +30 -0
  14. package/dist/esm/astro/bridge-runtime.js.map +1 -0
  15. package/dist/esm/astro/collect-expected-label-keys.d.ts +65 -0
  16. package/dist/esm/astro/collect-expected-label-keys.d.ts.map +1 -0
  17. package/dist/esm/astro/collect-expected-label-keys.js +101 -0
  18. package/dist/esm/astro/collect-expected-label-keys.js.map +1 -0
  19. package/dist/esm/astro/headers-renderer.d.ts +32 -0
  20. package/dist/esm/astro/headers-renderer.d.ts.map +1 -0
  21. package/dist/esm/astro/headers-renderer.js +64 -0
  22. package/dist/esm/astro/headers-renderer.js.map +1 -0
  23. package/dist/esm/astro/i18n-routing.helper.d.ts +91 -0
  24. package/dist/esm/astro/i18n-routing.helper.d.ts.map +1 -0
  25. package/dist/esm/astro/i18n-routing.helper.js +23 -0
  26. package/dist/esm/astro/i18n-routing.helper.js.map +1 -0
  27. package/dist/esm/astro/internal/html-escape.d.ts +31 -0
  28. package/dist/esm/astro/internal/html-escape.d.ts.map +1 -0
  29. package/dist/esm/astro/internal/html-escape.js +40 -0
  30. package/dist/esm/astro/internal/html-escape.js.map +1 -0
  31. package/dist/esm/astro/label-pack-loader.d.ts +134 -0
  32. package/dist/esm/astro/label-pack-loader.d.ts.map +1 -0
  33. package/dist/esm/astro/label-pack-loader.js +298 -0
  34. package/dist/esm/astro/label-pack-loader.js.map +1 -0
  35. package/dist/esm/astro/llms-txt-server.d.ts +28 -0
  36. package/dist/esm/astro/llms-txt-server.d.ts.map +1 -0
  37. package/dist/esm/astro/llms-txt-server.js +33 -0
  38. package/dist/esm/astro/llms-txt-server.js.map +1 -0
  39. package/dist/esm/astro/pick-labels-for-page.d.ts +63 -0
  40. package/dist/esm/astro/pick-labels-for-page.d.ts.map +1 -0
  41. package/dist/esm/astro/pick-labels-for-page.js +73 -0
  42. package/dist/esm/astro/pick-labels-for-page.js.map +1 -0
  43. package/dist/esm/astro/robots-renderer.d.ts +85 -0
  44. package/dist/esm/astro/robots-renderer.d.ts.map +1 -0
  45. package/dist/esm/astro/robots-renderer.js +157 -0
  46. package/dist/esm/astro/robots-renderer.js.map +1 -0
  47. package/dist/esm/astro/sitemap-coverage.d.ts +101 -0
  48. package/dist/esm/astro/sitemap-coverage.d.ts.map +1 -0
  49. package/dist/esm/astro/sitemap-coverage.js +67 -0
  50. package/dist/esm/astro/sitemap-coverage.js.map +1 -0
  51. package/dist/esm/astro/structured-data.renderer.d.ts +21 -0
  52. package/dist/esm/astro/structured-data.renderer.d.ts.map +1 -0
  53. package/dist/esm/astro/structured-data.renderer.js +142 -0
  54. package/dist/esm/astro/structured-data.renderer.js.map +1 -0
  55. package/dist/esm/astro/website-page-head.renderer.d.ts +93 -0
  56. package/dist/esm/astro/website-page-head.renderer.d.ts.map +1 -0
  57. package/dist/esm/astro/website-page-head.renderer.js +184 -0
  58. package/dist/esm/astro/website-page-head.renderer.js.map +1 -0
  59. package/dist/esm/astro/website-page-runtime.helper.d.ts +112 -0
  60. package/dist/esm/astro/website-page-runtime.helper.d.ts.map +1 -0
  61. package/dist/esm/astro/website-page-runtime.helper.js +60 -0
  62. package/dist/esm/astro/website-page-runtime.helper.js.map +1 -0
  63. package/dist/esm/astro/website-site-context.d.ts +126 -0
  64. package/dist/esm/astro/website-site-context.d.ts.map +1 -0
  65. package/dist/esm/astro/website-site-context.js +37 -0
  66. package/dist/esm/astro/website-site-context.js.map +1 -0
  67. package/dist/esm/astro-island.d.ts +56 -0
  68. package/dist/esm/astro-island.d.ts.map +1 -0
  69. package/dist/esm/astro-island.js +61 -0
  70. package/dist/esm/astro-island.js.map +1 -0
  71. package/dist/esm/astro.d.ts +151 -0
  72. package/dist/esm/astro.d.ts.map +1 -0
  73. package/dist/esm/astro.js +151 -0
  74. package/dist/esm/astro.js.map +1 -0
  75. package/dist/esm/companion-exports.d.ts +38 -0
  76. package/dist/esm/companion-exports.d.ts.map +1 -0
  77. package/dist/esm/companion-exports.js +38 -0
  78. package/dist/esm/companion-exports.js.map +1 -0
  79. package/dist/esm/components/low-level/WebsiteButton.d.ts +73 -0
  80. package/dist/esm/components/low-level/WebsiteButton.d.ts.map +1 -0
  81. package/dist/esm/components/low-level/WebsiteButton.js +68 -0
  82. package/dist/esm/components/low-level/WebsiteButton.js.map +1 -0
  83. package/dist/esm/components/low-level/WebsiteHeading.d.ts +78 -0
  84. package/dist/esm/components/low-level/WebsiteHeading.d.ts.map +1 -0
  85. package/dist/esm/components/low-level/WebsiteHeading.js +41 -0
  86. package/dist/esm/components/low-level/WebsiteHeading.js.map +1 -0
  87. package/dist/esm/components/low-level/WebsiteImage.d.ts +73 -0
  88. package/dist/esm/components/low-level/WebsiteImage.d.ts.map +1 -0
  89. package/dist/esm/components/low-level/WebsiteImage.js +21 -0
  90. package/dist/esm/components/low-level/WebsiteImage.js.map +1 -0
  91. package/dist/esm/components/low-level/WebsiteInternalButton.d.ts +90 -0
  92. package/dist/esm/components/low-level/WebsiteInternalButton.d.ts.map +1 -0
  93. package/dist/esm/components/low-level/WebsiteInternalButton.js +47 -0
  94. package/dist/esm/components/low-level/WebsiteInternalButton.js.map +1 -0
  95. package/dist/esm/components/low-level/WebsiteInternalLink.d.ts +99 -0
  96. package/dist/esm/components/low-level/WebsiteInternalLink.d.ts.map +1 -0
  97. package/dist/esm/components/low-level/WebsiteInternalLink.js +68 -0
  98. package/dist/esm/components/low-level/WebsiteInternalLink.js.map +1 -0
  99. package/dist/esm/components/low-level/WebsiteLink.d.ts +50 -0
  100. package/dist/esm/components/low-level/WebsiteLink.d.ts.map +1 -0
  101. package/dist/esm/components/low-level/WebsiteLink.js +22 -0
  102. package/dist/esm/components/low-level/WebsiteLink.js.map +1 -0
  103. package/dist/esm/components/low-level/WebsiteText.d.ts +57 -0
  104. package/dist/esm/components/low-level/WebsiteText.d.ts.map +1 -0
  105. package/dist/esm/components/low-level/WebsiteText.js +57 -0
  106. package/dist/esm/components/low-level/WebsiteText.js.map +1 -0
  107. package/dist/esm/config/define-website-config.d.ts +83 -0
  108. package/dist/esm/config/define-website-config.d.ts.map +1 -0
  109. package/dist/esm/config/define-website-config.js +126 -0
  110. package/dist/esm/config/define-website-config.js.map +1 -0
  111. package/dist/esm/config/index.d.ts +38 -0
  112. package/dist/esm/config/index.d.ts.map +1 -0
  113. package/dist/esm/config/index.js +38 -0
  114. package/dist/esm/config/index.js.map +1 -0
  115. package/dist/esm/config/load-website-config.d.ts +136 -0
  116. package/dist/esm/config/load-website-config.d.ts.map +1 -0
  117. package/dist/esm/config/load-website-config.js +177 -0
  118. package/dist/esm/config/load-website-config.js.map +1 -0
  119. package/dist/esm/config/wildo-website-config.schemas.d.ts +251 -0
  120. package/dist/esm/config/wildo-website-config.schemas.d.ts.map +1 -0
  121. package/dist/esm/config/wildo-website-config.schemas.js +302 -0
  122. package/dist/esm/config/wildo-website-config.schemas.js.map +1 -0
  123. package/dist/esm/config-loader.d.ts +35 -0
  124. package/dist/esm/config-loader.d.ts.map +1 -0
  125. package/dist/esm/config-loader.js +35 -0
  126. package/dist/esm/config-loader.js.map +1 -0
  127. package/dist/esm/core/anonymous-session/InboundContactForm.d.ts +38 -0
  128. package/dist/esm/core/anonymous-session/InboundContactForm.d.ts.map +1 -0
  129. package/dist/esm/core/anonymous-session/InboundContactForm.js +77 -0
  130. package/dist/esm/core/anonymous-session/InboundContactForm.js.map +1 -0
  131. package/dist/esm/core/anonymous-session/inbound-contact-form.schema.d.ts +41 -0
  132. package/dist/esm/core/anonymous-session/inbound-contact-form.schema.d.ts.map +1 -0
  133. package/dist/esm/core/anonymous-session/inbound-contact-form.schema.js +58 -0
  134. package/dist/esm/core/anonymous-session/inbound-contact-form.schema.js.map +1 -0
  135. package/dist/esm/core/anonymous-session/website-anonymous-session-client.d.ts +81 -0
  136. package/dist/esm/core/anonymous-session/website-anonymous-session-client.d.ts.map +1 -0
  137. package/dist/esm/core/anonymous-session/website-anonymous-session-client.js +177 -0
  138. package/dist/esm/core/anonymous-session/website-anonymous-session-client.js.map +1 -0
  139. package/dist/esm/core/contexts/WebsitePageContext.d.ts +40 -0
  140. package/dist/esm/core/contexts/WebsitePageContext.d.ts.map +1 -0
  141. package/dist/esm/core/contexts/WebsitePageContext.js +8 -0
  142. package/dist/esm/core/contexts/WebsitePageContext.js.map +1 -0
  143. package/dist/esm/core/contexts/WebsiteRuntimeContext.d.ts +162 -0
  144. package/dist/esm/core/contexts/WebsiteRuntimeContext.d.ts.map +1 -0
  145. package/dist/esm/core/contexts/WebsiteRuntimeContext.js +22 -0
  146. package/dist/esm/core/contexts/WebsiteRuntimeContext.js.map +1 -0
  147. package/dist/esm/core/contexts/WebsiteSectionContext.d.ts +47 -0
  148. package/dist/esm/core/contexts/WebsiteSectionContext.d.ts.map +1 -0
  149. package/dist/esm/core/contexts/WebsiteSectionContext.js +8 -0
  150. package/dist/esm/core/contexts/WebsiteSectionContext.js.map +1 -0
  151. package/dist/esm/core/contexts/useWebsitePage.d.ts +13 -0
  152. package/dist/esm/core/contexts/useWebsitePage.d.ts.map +1 -0
  153. package/dist/esm/core/contexts/useWebsitePage.js +22 -0
  154. package/dist/esm/core/contexts/useWebsitePage.js.map +1 -0
  155. package/dist/esm/core/contexts/useWebsiteRuntime.d.ts +19 -0
  156. package/dist/esm/core/contexts/useWebsiteRuntime.d.ts.map +1 -0
  157. package/dist/esm/core/contexts/useWebsiteRuntime.js +28 -0
  158. package/dist/esm/core/contexts/useWebsiteRuntime.js.map +1 -0
  159. package/dist/esm/core/contexts/useWebsiteSection.d.ts +12 -0
  160. package/dist/esm/core/contexts/useWebsiteSection.d.ts.map +1 -0
  161. package/dist/esm/core/contexts/useWebsiteSection.js +22 -0
  162. package/dist/esm/core/contexts/useWebsiteSection.js.map +1 -0
  163. package/dist/esm/core/external-providers/frontend-provider-registry.website.d.ts +17 -0
  164. package/dist/esm/core/external-providers/frontend-provider-registry.website.d.ts.map +1 -0
  165. package/dist/esm/core/external-providers/frontend-provider-registry.website.js +23 -0
  166. package/dist/esm/core/external-providers/frontend-provider-registry.website.js.map +1 -0
  167. package/dist/esm/core/factories/define-website-page-manifest.d.ts +70 -0
  168. package/dist/esm/core/factories/define-website-page-manifest.d.ts.map +1 -0
  169. package/dist/esm/core/factories/define-website-page-manifest.js +51 -0
  170. package/dist/esm/core/factories/define-website-page-manifest.js.map +1 -0
  171. package/dist/esm/core/factories/define-website-section.d.ts +101 -0
  172. package/dist/esm/core/factories/define-website-section.d.ts.map +1 -0
  173. package/dist/esm/core/factories/define-website-section.js +71 -0
  174. package/dist/esm/core/factories/define-website-section.js.map +1 -0
  175. package/dist/esm/core/hooks/useWebsiteDesignTokens.d.ts +23 -0
  176. package/dist/esm/core/hooks/useWebsiteDesignTokens.d.ts.map +1 -0
  177. package/dist/esm/core/hooks/useWebsiteDesignTokens.js +25 -0
  178. package/dist/esm/core/hooks/useWebsiteDesignTokens.js.map +1 -0
  179. package/dist/esm/core/hooks/useWebsiteLabel.d.ts +68 -0
  180. package/dist/esm/core/hooks/useWebsiteLabel.d.ts.map +1 -0
  181. package/dist/esm/core/hooks/useWebsiteLabel.js +103 -0
  182. package/dist/esm/core/hooks/useWebsiteLabel.js.map +1 -0
  183. package/dist/esm/core/layouts/WebsitePageLayout.d.ts +87 -0
  184. package/dist/esm/core/layouts/WebsitePageLayout.d.ts.map +1 -0
  185. package/dist/esm/core/layouts/WebsitePageLayout.js +170 -0
  186. package/dist/esm/core/layouts/WebsitePageLayout.js.map +1 -0
  187. package/dist/esm/core/layouts/WebsiteSection.d.ts +91 -0
  188. package/dist/esm/core/layouts/WebsiteSection.d.ts.map +1 -0
  189. package/dist/esm/core/layouts/WebsiteSection.js +26 -0
  190. package/dist/esm/core/layouts/WebsiteSection.js.map +1 -0
  191. package/dist/esm/core/routing/internal-routing.utils.d.ts +65 -0
  192. package/dist/esm/core/routing/internal-routing.utils.d.ts.map +1 -0
  193. package/dist/esm/core/routing/internal-routing.utils.js +14 -0
  194. package/dist/esm/core/routing/internal-routing.utils.js.map +1 -0
  195. package/dist/esm/index.d.ts +129 -0
  196. package/dist/esm/index.d.ts.map +1 -0
  197. package/dist/esm/index.js +129 -0
  198. package/dist/esm/index.js.map +1 -0
  199. package/dist/esm/mdx/BlogPost.d.ts +193 -0
  200. package/dist/esm/mdx/BlogPost.d.ts.map +1 -0
  201. package/dist/esm/mdx/BlogPost.js +161 -0
  202. package/dist/esm/mdx/BlogPost.js.map +1 -0
  203. package/dist/esm/mdx/blog-post-collection.config.d.ts +139 -0
  204. package/dist/esm/mdx/blog-post-collection.config.d.ts.map +1 -0
  205. package/dist/esm/mdx/blog-post-collection.config.js +27 -0
  206. package/dist/esm/mdx/blog-post-collection.config.js.map +1 -0
  207. package/dist/esm/mdx/blog-post-frontmatter-source.d.ts +12 -0
  208. package/dist/esm/mdx/blog-post-frontmatter-source.d.ts.map +1 -0
  209. package/dist/esm/mdx/blog-post-frontmatter-source.js +13 -0
  210. package/dist/esm/mdx/blog-post-frontmatter-source.js.map +1 -0
  211. package/dist/esm/mdx/index.d.ts +22 -0
  212. package/dist/esm/mdx/index.d.ts.map +1 -0
  213. package/dist/esm/mdx/index.js +22 -0
  214. package/dist/esm/mdx/index.js.map +1 -0
  215. package/dist/esm/mdx/website-mdx-provider.d.ts +90 -0
  216. package/dist/esm/mdx/website-mdx-provider.d.ts.map +1 -0
  217. package/dist/esm/mdx/website-mdx-provider.js +8 -0
  218. package/dist/esm/mdx/website-mdx-provider.js.map +1 -0
  219. package/dist/esm/mdx.d.ts +38 -0
  220. package/dist/esm/mdx.d.ts.map +1 -0
  221. package/dist/esm/mdx.js +38 -0
  222. package/dist/esm/mdx.js.map +1 -0
  223. package/dist/esm/schemas/blog/blog-post-frontmatter.shared.schemas.d.ts +75 -0
  224. package/dist/esm/schemas/blog/blog-post-frontmatter.shared.schemas.d.ts.map +1 -0
  225. package/dist/esm/schemas/blog/blog-post-frontmatter.shared.schemas.js +85 -0
  226. package/dist/esm/schemas/blog/blog-post-frontmatter.shared.schemas.js.map +1 -0
  227. package/dist/esm/schemas/design-tokens/website-design-tokens.shared.schemas.d.ts +120 -0
  228. package/dist/esm/schemas/design-tokens/website-design-tokens.shared.schemas.d.ts.map +1 -0
  229. package/dist/esm/schemas/design-tokens/website-design-tokens.shared.schemas.js +152 -0
  230. package/dist/esm/schemas/design-tokens/website-design-tokens.shared.schemas.js.map +1 -0
  231. package/dist/esm/schemas/label-keys/website-label-key.schemas.d.ts +73 -0
  232. package/dist/esm/schemas/label-keys/website-label-key.schemas.d.ts.map +1 -0
  233. package/dist/esm/schemas/label-keys/website-label-key.schemas.js +76 -0
  234. package/dist/esm/schemas/label-keys/website-label-key.schemas.js.map +1 -0
  235. package/dist/esm/schemas/manifests/website-page-manifest.shared.schemas.d.ts +209 -0
  236. package/dist/esm/schemas/manifests/website-page-manifest.shared.schemas.d.ts.map +1 -0
  237. package/dist/esm/schemas/manifests/website-page-manifest.shared.schemas.js +218 -0
  238. package/dist/esm/schemas/manifests/website-page-manifest.shared.schemas.js.map +1 -0
  239. package/dist/esm/schemas/manifests/website-root-config.shared.schemas.d.ts +431 -0
  240. package/dist/esm/schemas/manifests/website-root-config.shared.schemas.d.ts.map +1 -0
  241. package/dist/esm/schemas/manifests/website-root-config.shared.schemas.js +438 -0
  242. package/dist/esm/schemas/manifests/website-root-config.shared.schemas.js.map +1 -0
  243. package/dist/esm/schemas/refs/page-ref.schemas.d.ts +40 -0
  244. package/dist/esm/schemas/refs/page-ref.schemas.d.ts.map +1 -0
  245. package/dist/esm/schemas/refs/page-ref.schemas.js +43 -0
  246. package/dist/esm/schemas/refs/page-ref.schemas.js.map +1 -0
  247. package/dist/esm/schemas/refs/section-ref.schemas.d.ts +41 -0
  248. package/dist/esm/schemas/refs/section-ref.schemas.d.ts.map +1 -0
  249. package/dist/esm/schemas/refs/section-ref.schemas.js +44 -0
  250. package/dist/esm/schemas/refs/section-ref.schemas.js.map +1 -0
  251. package/dist/esm/schemas/sections/website-section-category.shared.d.ts +63 -0
  252. package/dist/esm/schemas/sections/website-section-category.shared.d.ts.map +1 -0
  253. package/dist/esm/schemas/sections/website-section-category.shared.js +64 -0
  254. package/dist/esm/schemas/sections/website-section-category.shared.js.map +1 -0
  255. package/dist/esm/schemas/sections/website-section-definition.shared.schemas.d.ts +114 -0
  256. package/dist/esm/schemas/sections/website-section-definition.shared.schemas.d.ts.map +1 -0
  257. package/dist/esm/schemas/sections/website-section-definition.shared.schemas.js +69 -0
  258. package/dist/esm/schemas/sections/website-section-definition.shared.schemas.js.map +1 -0
  259. package/dist/esm/schemas/structured-data/structured-data-reconciliation.shared.d.ts +100 -0
  260. package/dist/esm/schemas/structured-data/structured-data-reconciliation.shared.d.ts.map +1 -0
  261. package/dist/esm/schemas/structured-data/structured-data-reconciliation.shared.js +355 -0
  262. package/dist/esm/schemas/structured-data/structured-data-reconciliation.shared.js.map +1 -0
  263. package/dist/esm/schemas/structured-data/website-structured-data.shared.schemas.d.ts +58 -0
  264. package/dist/esm/schemas/structured-data/website-structured-data.shared.schemas.d.ts.map +1 -0
  265. package/dist/esm/schemas/structured-data/website-structured-data.shared.schemas.js +2 -0
  266. package/dist/esm/schemas/structured-data/website-structured-data.shared.schemas.js.map +1 -0
  267. package/dist/esm/schemas/validators/label-pack.validator.d.ts +144 -0
  268. package/dist/esm/schemas/validators/label-pack.validator.d.ts.map +1 -0
  269. package/dist/esm/schemas/validators/label-pack.validator.js +100 -0
  270. package/dist/esm/schemas/validators/label-pack.validator.js.map +1 -0
  271. package/dist/tsconfig.build.tsbuildinfo +1 -0
  272. package/package.json +143 -0
  273. package/src/__tests__/bundle-isolation.test.ts +607 -0
  274. package/src/astro/__tests__/blog-post-bridge.test.tsx +288 -0
  275. package/src/astro/__tests__/blog-post-head.renderer.test.ts +249 -0
  276. package/src/astro/__tests__/bridge-runtime.test.tsx +198 -0
  277. package/src/astro/__tests__/collect-expected-label-keys.test.ts +69 -0
  278. package/src/astro/__tests__/headers-renderer.test.ts +143 -0
  279. package/src/astro/__tests__/i18n-routing.helper.test.ts +116 -0
  280. package/src/astro/__tests__/label-pack-loader.test.ts +305 -0
  281. package/src/astro/__tests__/llms-txt-server.test.ts +127 -0
  282. package/src/astro/__tests__/pick-labels-for-page.test.ts +60 -0
  283. package/src/astro/__tests__/robots-renderer.test.ts +228 -0
  284. package/src/astro/__tests__/sitemap-coverage.test.ts +230 -0
  285. package/src/astro/__tests__/structured-data.renderer.test.ts +172 -0
  286. package/src/astro/__tests__/website-page-head.renderer.test.ts +424 -0
  287. package/src/astro/__tests__/website-page-runtime.helper.test.ts +157 -0
  288. package/src/astro/__tests__/website-site-context.test.ts +121 -0
  289. package/src/astro/blog-post-bridge.tsx +404 -0
  290. package/src/astro/blog-post-head.renderer.ts +293 -0
  291. package/src/astro/bridge-runtime.tsx +270 -0
  292. package/src/astro/collect-expected-label-keys.ts +113 -0
  293. package/src/astro/headers-renderer.ts +82 -0
  294. package/src/astro/i18n-routing.helper.ts +116 -0
  295. package/src/astro/internal/html-escape.ts +41 -0
  296. package/src/astro/label-pack-loader.ts +385 -0
  297. package/src/astro/llms-txt-server.ts +56 -0
  298. package/src/astro/pick-labels-for-page.ts +85 -0
  299. package/src/astro/robots-renderer.ts +171 -0
  300. package/src/astro/sitemap-coverage.ts +186 -0
  301. package/src/astro/structured-data.renderer.ts +171 -0
  302. package/src/astro/website-page-head.renderer.ts +279 -0
  303. package/src/astro/website-page-runtime.helper.ts +188 -0
  304. package/src/astro/website-site-context.ts +136 -0
  305. package/src/astro-island.ts +61 -0
  306. package/src/astro.ts +151 -0
  307. package/src/companion-exports.ts +38 -0
  308. package/src/components/low-level/WebsiteButton.tsx +118 -0
  309. package/src/components/low-level/WebsiteHeading.tsx +96 -0
  310. package/src/components/low-level/WebsiteImage.tsx +96 -0
  311. package/src/components/low-level/WebsiteInternalButton.tsx +172 -0
  312. package/src/components/low-level/WebsiteInternalLink.tsx +193 -0
  313. package/src/components/low-level/WebsiteLink.tsx +71 -0
  314. package/src/components/low-level/WebsiteText.tsx +75 -0
  315. package/src/components/low-level/__tests__/WebsiteInternalButton.test.tsx +169 -0
  316. package/src/components/low-level/__tests__/WebsiteInternalLink.test.tsx +202 -0
  317. package/src/components/low-level/__tests__/primitives.test.tsx +141 -0
  318. package/src/config/__tests__/define-website-config.test.ts +231 -0
  319. package/src/config/__tests__/load-website-config.test.ts +215 -0
  320. package/src/config/define-website-config.ts +134 -0
  321. package/src/config/index.ts +38 -0
  322. package/src/config/load-website-config.ts +229 -0
  323. package/src/config/wildo-website-config.schemas.ts +322 -0
  324. package/src/config-loader.ts +35 -0
  325. package/src/core/__tests__/WebsitePageLayout.test.tsx +184 -0
  326. package/src/core/__tests__/WebsiteSection.test.tsx +85 -0
  327. package/src/core/__tests__/contexts.test.tsx +133 -0
  328. package/src/core/__tests__/core-boundary.test.ts +85 -0
  329. package/src/core/__tests__/define-website-page-manifest.test.tsx +111 -0
  330. package/src/core/__tests__/define-website-section.test.tsx +89 -0
  331. package/src/core/__tests__/useWebsiteDesignTokens.test.tsx +41 -0
  332. package/src/core/__tests__/useWebsiteLabel.test.tsx +117 -0
  333. package/src/core/anonymous-session/InboundContactForm.tsx +157 -0
  334. package/src/core/anonymous-session/__tests__/inbound-contact-form.schema.test.ts +44 -0
  335. package/src/core/anonymous-session/inbound-contact-form.schema.ts +62 -0
  336. package/src/core/anonymous-session/website-anonymous-session-client.ts +194 -0
  337. package/src/core/contexts/WebsitePageContext.tsx +52 -0
  338. package/src/core/contexts/WebsiteRuntimeContext.tsx +175 -0
  339. package/src/core/contexts/WebsiteSectionContext.tsx +59 -0
  340. package/src/core/contexts/useWebsitePage.ts +28 -0
  341. package/src/core/contexts/useWebsiteRuntime.ts +34 -0
  342. package/src/core/contexts/useWebsiteSection.ts +28 -0
  343. package/src/core/external-providers/frontend-provider-registry.website.ts +62 -0
  344. package/src/core/factories/define-website-page-manifest.ts +102 -0
  345. package/src/core/factories/define-website-section.ts +128 -0
  346. package/src/core/hooks/useWebsiteDesignTokens.ts +26 -0
  347. package/src/core/hooks/useWebsiteLabel.ts +121 -0
  348. package/src/core/layouts/WebsitePageLayout.tsx +311 -0
  349. package/src/core/layouts/WebsiteSection.tsx +139 -0
  350. package/src/core/routing/__tests__/internal-routing.utils.test.ts +134 -0
  351. package/src/core/routing/internal-routing.utils.ts +77 -0
  352. package/src/index.ts +132 -0
  353. package/src/mdx/BlogPost.tsx +296 -0
  354. package/src/mdx/__tests__/BlogPost.test.tsx +218 -0
  355. package/src/mdx/__tests__/blog-post-collection.config.test.ts +127 -0
  356. package/src/mdx/__tests__/website-mdx-provider.test.tsx +82 -0
  357. package/src/mdx/blog-post-collection.config.ts +177 -0
  358. package/src/mdx/blog-post-frontmatter-source.ts +31 -0
  359. package/src/mdx/index.ts +39 -0
  360. package/src/mdx/website-mdx-provider.tsx +95 -0
  361. package/src/mdx.ts +38 -0
  362. package/src/schemas/__tests__/blog-frontmatter.test.ts +50 -0
  363. package/src/schemas/__tests__/companion-exports-react-free.test.ts +51 -0
  364. package/src/schemas/__tests__/design-tokens.test.ts +163 -0
  365. package/src/schemas/__tests__/label-key.test.ts +46 -0
  366. package/src/schemas/__tests__/label-pack-validator.test.ts +146 -0
  367. package/src/schemas/__tests__/page-manifest.test.ts +66 -0
  368. package/src/schemas/__tests__/refs.test.ts +37 -0
  369. package/src/schemas/__tests__/root-config.test.ts +292 -0
  370. package/src/schemas/__tests__/section-definition.test.ts +47 -0
  371. package/src/schemas/__tests__/structured-data-reconciliation.test.ts +543 -0
  372. package/src/schemas/blog/blog-post-frontmatter.shared.schemas.ts +111 -0
  373. package/src/schemas/design-tokens/website-design-tokens.shared.schemas.ts +166 -0
  374. package/src/schemas/label-keys/website-label-key.schemas.ts +88 -0
  375. package/src/schemas/manifests/website-page-manifest.shared.schemas.ts +266 -0
  376. package/src/schemas/manifests/website-root-config.shared.schemas.ts +504 -0
  377. package/src/schemas/refs/page-ref.schemas.ts +49 -0
  378. package/src/schemas/refs/section-ref.schemas.ts +50 -0
  379. package/src/schemas/sections/website-section-category.shared.ts +65 -0
  380. package/src/schemas/sections/website-section-definition.shared.schemas.ts +120 -0
  381. package/src/schemas/structured-data/structured-data-reconciliation.shared.ts +579 -0
  382. package/src/schemas/structured-data/website-structured-data.shared.schemas.ts +62 -0
  383. package/src/schemas/validators/label-pack.validator.ts +201 -0
@@ -0,0 +1,424 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { AvailableLanguage } from '@wildo-ai/saas-models/public-runtime';
4
+
5
+ import { WildoMarketingWebsiteConfigSchema } from '../../config/wildo-website-config.schemas';
6
+ import { WebsiteLabelKeySchema } from '../../schemas/label-keys/website-label-key.schemas';
7
+ import {
8
+ WebsitePageManifestSchema,
9
+ type WebsitePageManifest,
10
+ } from '../../schemas/manifests/website-page-manifest.shared.schemas';
11
+ import {
12
+ WebsiteRootConfigSchema,
13
+ type WebsiteRootConfig,
14
+ } from '../../schemas/manifests/website-root-config.shared.schemas';
15
+ import { WebsitePageRefSchema } from '../../schemas/refs/page-ref.schemas';
16
+ import { WebsiteSectionRefSchema } from '../../schemas/refs/section-ref.schemas';
17
+ import {
18
+ renderWebsiteDesignTokensCss,
19
+ renderWebsitePageHeadHtml,
20
+ } from '../website-page-head.renderer';
21
+ import { composeWebsiteSiteContext, type WebsiteSiteContext } from '../website-site-context';
22
+
23
+ const NoopComponent = () => null;
24
+
25
+ const COLORS = {
26
+ background: '#fff',
27
+ foreground: '#111',
28
+ primary: '#0070f3',
29
+ primaryForeground: '#fff',
30
+ accent: '#ff5e5b',
31
+ accentForeground: '#fff',
32
+ surface: '#fafafa',
33
+ surfaceForeground: '#111',
34
+ border: '#e5e5e5',
35
+ muted: '#f5f5f5',
36
+ mutedForeground: '#737373',
37
+ };
38
+ const RADII = {
39
+ none: '0',
40
+ sm: '0.125rem',
41
+ md: '0.375rem',
42
+ lg: '0.75rem',
43
+ xl: '1.5rem',
44
+ full: '9999px',
45
+ };
46
+ const DESIGN_TOKENS = { colors: COLORS, radii: RADII };
47
+
48
+ const buildManifest = (
49
+ ref: string,
50
+ routePath: string,
51
+ ogImageUrl?: string,
52
+ noaiOptOut?: boolean,
53
+ ): WebsitePageManifest =>
54
+ WebsitePageManifestSchema.parse({
55
+ ref: WebsitePageRefSchema.parse(ref),
56
+ routePath,
57
+ pageComponent: NoopComponent,
58
+ sectionRefs: [WebsiteSectionRefSchema.parse('hero')],
59
+ metaLabelKeys: {
60
+ titleKey: WebsiteLabelKeySchema.parse(`website.${ref}.meta.title`),
61
+ descriptionKey: WebsiteLabelKeySchema.parse(`website.${ref}.meta.description`),
62
+ ...(ogImageUrl !== undefined ? { ogImageUrl } : {}),
63
+ },
64
+ ...(noaiOptOut ? { seo: { noaiOptOut: true } } : {}),
65
+ }) as WebsitePageManifest;
66
+
67
+ const DEFAULT_SITE_NAME = 'Example Site';
68
+ const DEFAULT_PUBLIC_ORIGIN = 'https://example.com';
69
+
70
+ interface BuildSiteContextOverrides {
71
+ defaultOgImageUrl?: string;
72
+ siteName?: string;
73
+ publicOrigin?: string;
74
+ }
75
+
76
+ const buildSiteContext = (
77
+ locales: AvailableLanguage[],
78
+ defaultLocale: AvailableLanguage,
79
+ manifest: WebsitePageManifest,
80
+ overrides: BuildSiteContextOverrides = {},
81
+ ): WebsiteSiteContext => {
82
+ const rootConfig = WebsiteRootConfigSchema.parse({
83
+ pageManifests: [manifest],
84
+ designTokens: DESIGN_TOKENS,
85
+ navigation: { headerComponent: NoopComponent, footerComponent: NoopComponent },
86
+ seo: {
87
+ siteName: overrides.siteName ?? DEFAULT_SITE_NAME,
88
+ ...(overrides.defaultOgImageUrl !== undefined
89
+ ? { defaultOgImageUrl: overrides.defaultOgImageUrl }
90
+ : {}),
91
+ },
92
+ skipToMainContentLabelKey: WebsiteLabelKeySchema.parse(
93
+ 'website.chrome.layout.skip-to-main-content',
94
+ ),
95
+ }) as WebsiteRootConfig;
96
+
97
+ const websiteConfig = WildoMarketingWebsiteConfigSchema.parse({
98
+ publicOrigin: overrides.publicOrigin ?? DEFAULT_PUBLIC_ORIGIN,
99
+ appOrigin: 'https://app.example.com',
100
+ defaultLocale,
101
+ locales: locales.map((locale) => ({ locale, path: String(locale) })),
102
+ });
103
+
104
+ return composeWebsiteSiteContext({ rootConfig, websiteConfig });
105
+ };
106
+
107
+ describe('renderWebsitePageHeadHtml', () => {
108
+ it('emits title + description + OG/Twitter cards from the resolved label pack', () => {
109
+ const manifest = buildManifest('landing', '/');
110
+ const siteContext = buildSiteContext(
111
+ [AvailableLanguage.EN, AvailableLanguage.FR],
112
+ AvailableLanguage.EN,
113
+ manifest,
114
+ );
115
+ const html = renderWebsitePageHeadHtml({
116
+ manifest,
117
+ siteContext,
118
+ locale: AvailableLanguage.EN,
119
+ labelPack: {
120
+ 'website.landing.meta.title': 'Wonder Todos',
121
+ 'website.landing.meta.description': 'Get things done.',
122
+ },
123
+ });
124
+ expect(html).toContain('<title>Wonder Todos</title>');
125
+ expect(html).toContain('<meta name="description" content="Get things done." />');
126
+ expect(html).toContain('<meta property="og:title" content="Wonder Todos" />');
127
+ expect(html).toContain('<meta property="og:description" content="Get things done." />');
128
+ expect(html).toContain('<meta name="twitter:card" content="summary" />');
129
+ });
130
+
131
+ it('emits OG image + summary_large_image card when ogImageUrl is set', () => {
132
+ const manifest = buildManifest('landing', '/', 'https://cdn.example.com/og.png');
133
+ const siteContext = buildSiteContext(
134
+ [AvailableLanguage.EN],
135
+ AvailableLanguage.EN,
136
+ manifest,
137
+ );
138
+ const html = renderWebsitePageHeadHtml({
139
+ manifest,
140
+ siteContext,
141
+ locale: AvailableLanguage.EN,
142
+ labelPack: {
143
+ 'website.landing.meta.title': 'T',
144
+ 'website.landing.meta.description': 'D',
145
+ },
146
+ });
147
+ expect(html).toContain(
148
+ '<meta property="og:image" content="https://cdn.example.com/og.png" />',
149
+ );
150
+ expect(html).toContain('<meta name="twitter:card" content="summary_large_image" />');
151
+ });
152
+
153
+ it('emits one ABSOLUTE hreflang link per declared locale plus an x-default fallback', () => {
154
+ const manifest = buildManifest('pricing', '/pricing');
155
+ const siteContext = buildSiteContext(
156
+ [AvailableLanguage.EN, AvailableLanguage.FR, AvailableLanguage.DE],
157
+ AvailableLanguage.EN,
158
+ manifest,
159
+ );
160
+ const html = renderWebsitePageHeadHtml({
161
+ manifest,
162
+ siteContext,
163
+ locale: AvailableLanguage.EN,
164
+ labelPack: {
165
+ 'website.pricing.meta.title': 'T',
166
+ 'website.pricing.meta.description': 'D',
167
+ },
168
+ });
169
+ expect(html).toContain(
170
+ '<link rel="alternate" hreflang="en" href="https://example.com/pricing" />',
171
+ );
172
+ expect(html).toContain(
173
+ '<link rel="alternate" hreflang="fr" href="https://example.com/fr/pricing" />',
174
+ );
175
+ expect(html).toContain(
176
+ '<link rel="alternate" hreflang="de" href="https://example.com/de/pricing" />',
177
+ );
178
+ expect(html).toContain(
179
+ '<link rel="alternate" hreflang="x-default" href="https://example.com/pricing" />',
180
+ );
181
+ });
182
+
183
+ it('builds the locale-prefixed ABSOLUTE hreflang correctly for the root path ("/")', () => {
184
+ const manifest = buildManifest('landing', '/');
185
+ const siteContext = buildSiteContext(
186
+ [AvailableLanguage.EN, AvailableLanguage.FR],
187
+ AvailableLanguage.EN,
188
+ manifest,
189
+ );
190
+ const html = renderWebsitePageHeadHtml({
191
+ manifest,
192
+ siteContext,
193
+ locale: AvailableLanguage.EN,
194
+ labelPack: {
195
+ 'website.landing.meta.title': 'T',
196
+ 'website.landing.meta.description': 'D',
197
+ },
198
+ });
199
+ expect(html).toContain(
200
+ '<link rel="alternate" hreflang="en" href="https://example.com/" />',
201
+ );
202
+ expect(html).toContain(
203
+ '<link rel="alternate" hreflang="fr" href="https://example.com/fr" />',
204
+ );
205
+ });
206
+
207
+ it('emits a <link rel="canonical"> with the absolute URL for the active locale (Phase 6 H-C)', () => {
208
+ const manifest = buildManifest('pricing', '/pricing');
209
+ const siteContext = buildSiteContext(
210
+ [AvailableLanguage.EN, AvailableLanguage.FR],
211
+ AvailableLanguage.EN,
212
+ manifest,
213
+ );
214
+ const enHtml = renderWebsitePageHeadHtml({
215
+ manifest,
216
+ siteContext,
217
+ locale: AvailableLanguage.EN,
218
+ labelPack: {
219
+ 'website.pricing.meta.title': 'T',
220
+ 'website.pricing.meta.description': 'D',
221
+ },
222
+ });
223
+ expect(enHtml).toContain(
224
+ '<link rel="canonical" href="https://example.com/pricing" />',
225
+ );
226
+
227
+ const frHtml = renderWebsitePageHeadHtml({
228
+ manifest,
229
+ siteContext,
230
+ locale: AvailableLanguage.FR,
231
+ labelPack: {
232
+ 'website.pricing.meta.title': 'T',
233
+ 'website.pricing.meta.description': 'D',
234
+ },
235
+ });
236
+ expect(frHtml).toContain(
237
+ '<link rel="canonical" href="https://example.com/fr/pricing" />',
238
+ );
239
+ });
240
+
241
+ it('emits og:url + og:site_name + og:locale + og:locale:alternate (Phase 6 M-A)', () => {
242
+ const manifest = buildManifest('landing', '/');
243
+ const siteContext = buildSiteContext(
244
+ [AvailableLanguage.EN, AvailableLanguage.FR, AvailableLanguage.DE],
245
+ AvailableLanguage.EN,
246
+ manifest,
247
+ { siteName: 'Wonder Co' },
248
+ );
249
+ const html = renderWebsitePageHeadHtml({
250
+ manifest,
251
+ siteContext,
252
+ locale: AvailableLanguage.EN,
253
+ labelPack: {
254
+ 'website.landing.meta.title': 'T',
255
+ 'website.landing.meta.description': 'D',
256
+ },
257
+ });
258
+ expect(html).toContain('<meta property="og:url" content="https://example.com/" />');
259
+ expect(html).toContain('<meta property="og:site_name" content="Wonder Co" />');
260
+ expect(html).toContain('<meta property="og:locale" content="en" />');
261
+ expect(html).toContain('<meta property="og:locale:alternate" content="fr" />');
262
+ expect(html).toContain('<meta property="og:locale:alternate" content="de" />');
263
+ expect(html).not.toContain('<meta property="og:locale:alternate" content="en" />');
264
+ });
265
+
266
+ it('falls back to seo.defaultOgImageUrl when manifest has no ogImageUrl (Phase 6 M-A)', () => {
267
+ const manifest = buildManifest('landing', '/');
268
+ const siteContext = buildSiteContext(
269
+ [AvailableLanguage.EN],
270
+ AvailableLanguage.EN,
271
+ manifest,
272
+ { defaultOgImageUrl: 'https://cdn.example.com/og-default.png' },
273
+ );
274
+ const html = renderWebsitePageHeadHtml({
275
+ manifest,
276
+ siteContext,
277
+ locale: AvailableLanguage.EN,
278
+ labelPack: {
279
+ 'website.landing.meta.title': 'T',
280
+ 'website.landing.meta.description': 'D',
281
+ },
282
+ });
283
+ expect(html).toContain(
284
+ '<meta property="og:image" content="https://cdn.example.com/og-default.png" />',
285
+ );
286
+ expect(html).toContain('<meta name="twitter:card" content="summary_large_image" />');
287
+ });
288
+
289
+ it('emits noai robots meta only when the page manifest opts out of AI training', () => {
290
+ const manifest = buildManifest('pricing', '/pricing', undefined, true);
291
+ const siteContext = buildSiteContext([AvailableLanguage.EN], AvailableLanguage.EN, manifest);
292
+ const optedOutHtml = renderWebsitePageHeadHtml({
293
+ manifest,
294
+ siteContext,
295
+ locale: AvailableLanguage.EN,
296
+ labelPack: {
297
+ 'website.pricing.meta.title': 'T',
298
+ 'website.pricing.meta.description': 'D',
299
+ },
300
+ });
301
+ expect(optedOutHtml).toContain('<meta name="robots" content="noai, noimageai" />');
302
+
303
+ const defaultManifest = buildManifest('landing', '/');
304
+ const defaultHtml = renderWebsitePageHeadHtml({
305
+ manifest: defaultManifest,
306
+ siteContext: buildSiteContext([AvailableLanguage.EN], AvailableLanguage.EN, defaultManifest),
307
+ locale: AvailableLanguage.EN,
308
+ labelPack: {
309
+ 'website.landing.meta.title': 'T',
310
+ 'website.landing.meta.description': 'D',
311
+ },
312
+ });
313
+ expect(defaultHtml).not.toContain('noai, noimageai');
314
+ });
315
+
316
+ it('per-page ogImageUrl wins over seo.defaultOgImageUrl (precedence rule, Phase 6 M-A)', () => {
317
+ const manifest = buildManifest('landing', '/', 'https://cdn.example.com/page.png');
318
+ const siteContext = buildSiteContext(
319
+ [AvailableLanguage.EN],
320
+ AvailableLanguage.EN,
321
+ manifest,
322
+ { defaultOgImageUrl: 'https://cdn.example.com/og-default.png' },
323
+ );
324
+ const html = renderWebsitePageHeadHtml({
325
+ manifest,
326
+ siteContext,
327
+ locale: AvailableLanguage.EN,
328
+ labelPack: {
329
+ 'website.landing.meta.title': 'T',
330
+ 'website.landing.meta.description': 'D',
331
+ },
332
+ });
333
+ expect(html).toContain(
334
+ '<meta property="og:image" content="https://cdn.example.com/page.png" />',
335
+ );
336
+ expect(html).not.toContain('og-default.png');
337
+ });
338
+
339
+ it('escapes HTML-significant characters in label values to prevent attribute injection', () => {
340
+ const manifest = buildManifest('landing', '/');
341
+ const siteContext = buildSiteContext(
342
+ [AvailableLanguage.EN],
343
+ AvailableLanguage.EN,
344
+ manifest,
345
+ );
346
+ const html = renderWebsitePageHeadHtml({
347
+ manifest,
348
+ siteContext,
349
+ locale: AvailableLanguage.EN,
350
+ labelPack: {
351
+ 'website.landing.meta.title': 'A & B "quoted" <tag>',
352
+ 'website.landing.meta.description': "He's awesome",
353
+ },
354
+ });
355
+ expect(html).toContain(
356
+ '<title>A &amp; B &quot;quoted&quot; &lt;tag&gt;</title>',
357
+ );
358
+ expect(html).toContain(
359
+ '<meta property="og:title" content="A &amp; B &quot;quoted&quot; &lt;tag&gt;" />',
360
+ );
361
+ expect(html).toContain(
362
+ '<meta name="description" content="He&#39;s awesome" />',
363
+ );
364
+ });
365
+
366
+ it('throws an actionable error when titleKey is missing from the pack', () => {
367
+ const manifest = buildManifest('landing', '/');
368
+ const siteContext = buildSiteContext(
369
+ [AvailableLanguage.EN],
370
+ AvailableLanguage.EN,
371
+ manifest,
372
+ );
373
+ expect(() =>
374
+ renderWebsitePageHeadHtml({
375
+ manifest,
376
+ siteContext,
377
+ locale: AvailableLanguage.EN,
378
+ labelPack: {
379
+ 'website.landing.meta.description': 'D',
380
+ },
381
+ }),
382
+ ).toThrow(/Label pack for locale "en" is missing.*page title key.*website\.landing\.meta\.title/s);
383
+ });
384
+
385
+ it('throws an actionable error when descriptionKey is missing from the pack', () => {
386
+ const manifest = buildManifest('landing', '/');
387
+ const siteContext = buildSiteContext(
388
+ [AvailableLanguage.EN],
389
+ AvailableLanguage.EN,
390
+ manifest,
391
+ );
392
+ expect(() =>
393
+ renderWebsitePageHeadHtml({
394
+ manifest,
395
+ siteContext,
396
+ locale: AvailableLanguage.EN,
397
+ labelPack: {
398
+ 'website.landing.meta.title': 'T',
399
+ },
400
+ }),
401
+ ).toThrow(/page description key.*website\.landing\.meta\.description/s);
402
+ });
403
+ });
404
+
405
+ describe('renderWebsiteDesignTokensCss', () => {
406
+ it('emits a :root block with --website-<group>-<token> custom properties', () => {
407
+ const css = renderWebsiteDesignTokensCss(DESIGN_TOKENS);
408
+ expect(css).toContain(':root {');
409
+ expect(css).toContain('--website-colors-primary: #0070f3;');
410
+ expect(css).toContain('--website-colors-background: #fff;');
411
+ expect(css).toContain('--website-radii-md: 0.375rem;');
412
+ expect(css.trim().endsWith('}')).toBe(true);
413
+ });
414
+
415
+ it('emits every declared color + radius token (none silently dropped)', () => {
416
+ const css = renderWebsiteDesignTokensCss(DESIGN_TOKENS);
417
+ for (const tokenName of Object.keys(COLORS)) {
418
+ expect(css).toContain(`--website-colors-${tokenName}:`);
419
+ }
420
+ for (const tokenName of Object.keys(RADII)) {
421
+ expect(css).toContain(`--website-radii-${tokenName}:`);
422
+ }
423
+ });
424
+ });
@@ -0,0 +1,157 @@
1
+ import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { tmpdir } from 'node:os';
3
+ import { resolve } from 'node:path';
4
+
5
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
6
+
7
+ import { AvailableLanguage } from '@wildo-ai/saas-models/public-runtime';
8
+
9
+ import { WildoMarketingWebsiteConfigSchema } from '../../config/wildo-website-config.schemas';
10
+ import { WebsiteLabelKeySchema } from '../../schemas/label-keys/website-label-key.schemas';
11
+ import {
12
+ WebsitePageManifestSchema,
13
+ type WebsitePageManifest,
14
+ } from '../../schemas/manifests/website-page-manifest.shared.schemas';
15
+ import {
16
+ WebsiteRootConfigSchema,
17
+ type WebsiteRootConfig,
18
+ } from '../../schemas/manifests/website-root-config.shared.schemas';
19
+ import { WebsitePageRefSchema } from '../../schemas/refs/page-ref.schemas';
20
+ import { WebsiteSectionRefSchema } from '../../schemas/refs/section-ref.schemas';
21
+ import { loadWebsitePageRuntime } from '../website-page-runtime.helper';
22
+ import { composeWebsiteSiteContext, type WebsiteSiteContext } from '../website-site-context';
23
+
24
+ const NoopComponent = () => null;
25
+
26
+ const COLORS = {
27
+ background: '#fff',
28
+ foreground: '#111',
29
+ primary: '#0070f3',
30
+ primaryForeground: '#fff',
31
+ accent: '#ff5e5b',
32
+ accentForeground: '#fff',
33
+ surface: '#fafafa',
34
+ surfaceForeground: '#111',
35
+ border: '#e5e5e5',
36
+ muted: '#f5f5f5',
37
+ mutedForeground: '#737373',
38
+ };
39
+ const RADII = {
40
+ none: '0',
41
+ sm: '0.125rem',
42
+ md: '0.375rem',
43
+ lg: '0.75rem',
44
+ xl: '1.5rem',
45
+ full: '9999px',
46
+ };
47
+
48
+ const buildManifest = (): WebsitePageManifest =>
49
+ WebsitePageManifestSchema.parse({
50
+ ref: WebsitePageRefSchema.parse('landing'),
51
+ routePath: '/',
52
+ pageComponent: NoopComponent,
53
+ sectionRefs: [WebsiteSectionRefSchema.parse('hero')],
54
+ metaLabelKeys: {
55
+ titleKey: WebsiteLabelKeySchema.parse('website.landing.meta.title'),
56
+ descriptionKey: WebsiteLabelKeySchema.parse('website.landing.meta.description'),
57
+ },
58
+ }) as WebsitePageManifest;
59
+
60
+ const buildRootConfig = (): WebsiteRootConfig =>
61
+ WebsiteRootConfigSchema.parse({
62
+ pageManifests: [buildManifest()],
63
+ designTokens: { colors: COLORS, radii: RADII },
64
+ navigation: { headerComponent: NoopComponent, footerComponent: NoopComponent },
65
+ seo: { siteName: 'Example' },
66
+ skipToMainContentLabelKey: WebsiteLabelKeySchema.parse(
67
+ 'website.chrome.layout.skip-to-main-content',
68
+ ),
69
+ }) as WebsiteRootConfig;
70
+
71
+ const buildSiteContext = (
72
+ locales: AvailableLanguage[],
73
+ defaultLocale: AvailableLanguage,
74
+ ): WebsiteSiteContext => {
75
+ const websiteConfig = WildoMarketingWebsiteConfigSchema.parse({
76
+ publicOrigin: 'https://example.com',
77
+ appOrigin: 'https://app.example.com',
78
+ defaultLocale,
79
+ locales: locales.map((locale) => ({ locale, path: String(locale) })),
80
+ });
81
+ return composeWebsiteSiteContext({
82
+ rootConfig: buildRootConfig(),
83
+ websiteConfig,
84
+ });
85
+ };
86
+
87
+ describe('loadWebsitePageRuntime', () => {
88
+ let tmpDir: string;
89
+
90
+ beforeEach(() => {
91
+ tmpDir = mkdtempSync(resolve(tmpdir(), 'website-runtime-helper-test-'));
92
+ });
93
+
94
+ afterEach(() => {
95
+ rmSync(tmpDir, { recursive: true, force: true });
96
+ });
97
+
98
+ it('falls back to defaultLocale when astroLocale is undefined (default-locale path)', () => {
99
+ writeFileSync(
100
+ resolve(tmpDir, 'en.json'),
101
+ JSON.stringify({ 'website.landing.hero.headline': 'EN' }),
102
+ );
103
+ const runtime = loadWebsitePageRuntime({
104
+ manifest: buildManifest(),
105
+ siteContext: buildSiteContext(
106
+ [AvailableLanguage.EN, AvailableLanguage.FR],
107
+ AvailableLanguage.EN,
108
+ ),
109
+ labelPacksDir: tmpDir,
110
+ astroLocale: undefined,
111
+ });
112
+ expect(runtime.locale).toBe(AvailableLanguage.EN);
113
+ expect(runtime.defaultLocale).toBe(AvailableLanguage.EN);
114
+ expect(runtime.labelPack['website.landing.hero.headline']).toBe('EN');
115
+ });
116
+
117
+ it('uses astroLocale when present and declared on the website config', () => {
118
+ writeFileSync(
119
+ resolve(tmpDir, 'fr.json'),
120
+ JSON.stringify({ 'website.landing.hero.headline': 'FR' }),
121
+ );
122
+ const runtime = loadWebsitePageRuntime({
123
+ manifest: buildManifest(),
124
+ siteContext: buildSiteContext(
125
+ [AvailableLanguage.EN, AvailableLanguage.FR],
126
+ AvailableLanguage.EN,
127
+ ),
128
+ labelPacksDir: tmpDir,
129
+ astroLocale: 'fr',
130
+ });
131
+ expect(runtime.locale).toBe(AvailableLanguage.FR);
132
+ expect(runtime.defaultLocale).toBe(AvailableLanguage.EN);
133
+ expect(runtime.labelPack['website.landing.hero.headline']).toBe('FR');
134
+ });
135
+
136
+ it('throws an actionable error when astroLocale is not declared on the website config', () => {
137
+ expect(() =>
138
+ loadWebsitePageRuntime({
139
+ manifest: buildManifest(),
140
+ siteContext: buildSiteContext([AvailableLanguage.EN], AvailableLanguage.EN),
141
+ labelPacksDir: tmpDir,
142
+ astroLocale: 'de',
143
+ }),
144
+ ).toThrow(/Astro presented locale "de".*declares only \[en\]/s);
145
+ });
146
+
147
+ it('surfaces the underlying loader error when the label pack is missing for the resolved locale', () => {
148
+ expect(() =>
149
+ loadWebsitePageRuntime({
150
+ manifest: buildManifest(),
151
+ siteContext: buildSiteContext([AvailableLanguage.EN], AvailableLanguage.EN),
152
+ labelPacksDir: tmpDir,
153
+ astroLocale: undefined,
154
+ }),
155
+ ).toThrow(/Could not read label pack/);
156
+ });
157
+ });
@@ -0,0 +1,121 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { FrontendProvidersBlockSchema } from '@wildo-ai/saas-models';
4
+ import { AvailableLanguage } from '@wildo-ai/saas-models/public-runtime';
5
+
6
+ import { WildoMarketingWebsiteConfigSchema } from '../../config/wildo-website-config.schemas';
7
+ import { WebsiteLabelKeySchema } from '../../schemas/label-keys/website-label-key.schemas';
8
+ import {
9
+ WebsitePageManifestSchema,
10
+ type WebsitePageManifest,
11
+ } from '../../schemas/manifests/website-page-manifest.shared.schemas';
12
+ import {
13
+ WebsiteRootConfigSchema,
14
+ type WebsiteRootConfig,
15
+ } from '../../schemas/manifests/website-root-config.shared.schemas';
16
+ import { WebsitePageRefSchema } from '../../schemas/refs/page-ref.schemas';
17
+ import { WebsiteSectionRefSchema } from '../../schemas/refs/section-ref.schemas';
18
+ import { composeWebsiteSiteContext } from '../website-site-context';
19
+
20
+ const NoopComponent = () => null;
21
+
22
+ const COLORS = {
23
+ background: '#fff',
24
+ foreground: '#111',
25
+ primary: '#0070f3',
26
+ primaryForeground: '#fff',
27
+ accent: '#ff5e5b',
28
+ accentForeground: '#fff',
29
+ surface: '#fafafa',
30
+ surfaceForeground: '#111',
31
+ border: '#e5e5e5',
32
+ muted: '#f5f5f5',
33
+ mutedForeground: '#737373',
34
+ };
35
+ const RADII = {
36
+ none: '0',
37
+ sm: '0.125rem',
38
+ md: '0.375rem',
39
+ lg: '0.75rem',
40
+ xl: '1.5rem',
41
+ full: '9999px',
42
+ };
43
+
44
+ const buildManifest = (): WebsitePageManifest =>
45
+ WebsitePageManifestSchema.parse({
46
+ ref: WebsitePageRefSchema.parse('landing'),
47
+ routePath: '/',
48
+ pageComponent: NoopComponent,
49
+ sectionRefs: [WebsiteSectionRefSchema.parse('hero')],
50
+ metaLabelKeys: {
51
+ titleKey: WebsiteLabelKeySchema.parse('website.landing.meta.title'),
52
+ descriptionKey: WebsiteLabelKeySchema.parse('website.landing.meta.description'),
53
+ },
54
+ }) as WebsitePageManifest;
55
+
56
+ const buildRootConfig = (): WebsiteRootConfig =>
57
+ WebsiteRootConfigSchema.parse({
58
+ pageManifests: [buildManifest()],
59
+ designTokens: { colors: COLORS, radii: RADII },
60
+ navigation: { headerComponent: NoopComponent, footerComponent: NoopComponent },
61
+ seo: { siteName: 'Example' },
62
+ skipToMainContentLabelKey: WebsiteLabelKeySchema.parse(
63
+ 'website.chrome.layout.skip-to-main-content',
64
+ ),
65
+ }) as WebsiteRootConfig;
66
+
67
+ const buildWebsiteConfig = () =>
68
+ WildoMarketingWebsiteConfigSchema.parse({
69
+ publicOrigin: 'https://example.com',
70
+ appOrigin: 'https://app.example.com',
71
+ defaultLocale: AvailableLanguage.EN,
72
+ locales: [
73
+ { locale: AvailableLanguage.EN, path: 'en' },
74
+ { locale: AvailableLanguage.FR, path: 'fr' },
75
+ ],
76
+ });
77
+
78
+ describe('composeWebsiteSiteContext', () => {
79
+ it('returns the two source configs by reference under their keyed slots', () => {
80
+ const rootConfig = buildRootConfig();
81
+ const websiteConfig = buildWebsiteConfig();
82
+ const ctx = composeWebsiteSiteContext({ rootConfig, websiteConfig });
83
+ expect(ctx.rootConfig).toBe(rootConfig);
84
+ expect(ctx.websiteConfig).toBe(websiteConfig);
85
+ });
86
+
87
+ it('top-level freezes the returned aggregator (defensive guard against in-place mutation)', () => {
88
+ const ctx = composeWebsiteSiteContext({
89
+ rootConfig: buildRootConfig(),
90
+ websiteConfig: buildWebsiteConfig(),
91
+ });
92
+ expect(Object.isFrozen(ctx)).toBe(true);
93
+ expect(() => {
94
+ (ctx as unknown as { rootConfig: unknown }).rootConfig = {};
95
+ }).toThrow();
96
+ });
97
+
98
+ it('keeps the optional website frontend provider block by reference', () => {
99
+ const frontendProviders = FrontendProvidersBlockSchema.parse({
100
+ providers: {
101
+ posthog: {
102
+ metadata: { ref: 'posthog' },
103
+ providerCapabilities: ['FRONTEND_PRODUCT_ANALYTICS'],
104
+ protocols: ['FRONTEND_SDK'],
105
+ publicConfig: {
106
+ integrationKind: 'product-analytics',
107
+ websiteRuntime: true,
108
+ },
109
+ },
110
+ },
111
+ });
112
+
113
+ const ctx = composeWebsiteSiteContext({
114
+ rootConfig: buildRootConfig(),
115
+ websiteConfig: buildWebsiteConfig(),
116
+ frontendProviders,
117
+ });
118
+
119
+ expect(ctx.frontendProviders).toBe(frontendProviders);
120
+ });
121
+ });