@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,198 @@
1
+ import '@testing-library/jest-dom/vitest';
2
+ import { render, screen } from '@testing-library/react';
3
+ import React from 'react';
4
+ import { describe, expect, it } from 'vitest';
5
+
6
+ import { BUILTIN_PROVIDER_CAPABILITY } from '@wildo-ai/saas-models/public-runtime';
7
+ import type { FrontendProvidersBlock } from '@wildo-ai/saas-models/public-runtime';
8
+ import { AvailableLanguage } from '@wildo-ai/saas-models/public-runtime';
9
+
10
+ import { useWebsiteRuntime } from '../../core/contexts/useWebsiteRuntime';
11
+ import { useWebsitePage } from '../../core/contexts/useWebsitePage';
12
+ import { WebsiteLabelKeySchema } from '../../schemas/label-keys/website-label-key.schemas';
13
+ import type { WebsiteNavigationConfig } from '../../schemas/manifests/website-root-config.shared.schemas';
14
+ import { WebsitePageRefSchema } from '../../schemas/refs/page-ref.schemas';
15
+ import {
16
+ createWebsitePageBridgeIsland,
17
+ type WebsitePageBridgeIslandProps,
18
+ } from '../bridge-runtime';
19
+
20
+ const COLORS = {
21
+ background: '#fff',
22
+ foreground: '#111',
23
+ primary: '#0070f3',
24
+ primaryForeground: '#fff',
25
+ accent: '#ff5e5b',
26
+ accentForeground: '#fff',
27
+ surface: '#fafafa',
28
+ surfaceForeground: '#111',
29
+ border: '#e5e5e5',
30
+ muted: '#f5f5f5',
31
+ mutedForeground: '#737373',
32
+ };
33
+ const RADII = {
34
+ none: '0',
35
+ sm: '0.125rem',
36
+ md: '0.375rem',
37
+ lg: '0.75rem',
38
+ xl: '1.5rem',
39
+ full: '9999px',
40
+ };
41
+ const DESIGN_TOKENS = { colors: COLORS, radii: RADII };
42
+
43
+ function NoopComponent(): React.ReactElement | null {
44
+ return null;
45
+ }
46
+
47
+ function PageProbe(): React.ReactElement {
48
+ const runtime = useWebsiteRuntime();
49
+ const page = useWebsitePage();
50
+ const posthogProvider = runtime.frontendProviderRegistry
51
+ ?.findProviderByProviderCapability(BUILTIN_PROVIDER_CAPABILITY.FRONTEND_PRODUCT_ANALYTICS);
52
+ return (
53
+ <div>
54
+ <span data-testid="page-ref">{page.pageRef as unknown as string}</span>
55
+ <span data-testid="locale">{runtime.locale}</span>
56
+ <span data-testid="headline">
57
+ {runtime.labelPack['website.landing.hero.headline'] ?? '<missing>'}
58
+ </span>
59
+ <span data-testid="primary-color">{runtime.designTokens.colors.primary}</span>
60
+ <span data-testid="provider-ref">{posthogProvider?.metadata.ref ?? '<missing>'}</span>
61
+ </div>
62
+ );
63
+ }
64
+
65
+ function buildNavigation(): WebsiteNavigationConfig {
66
+ return { headerComponent: NoopComponent, footerComponent: NoopComponent };
67
+ }
68
+
69
+ const PAGE_REF = WebsitePageRefSchema.parse('landing');
70
+ const SKIP_KEY = WebsiteLabelKeySchema.parse('website.chrome.layout.skip-to-main-content');
71
+ const SKIP_LABEL_PACK = { 'website.chrome.layout.skip-to-main-content': 'Skip to main content' } as const;
72
+ // Typed as the real block rather than `as const`: the prop takes `FrontendProvidersBlock`,
73
+ // whose `providerCapabilities` / `protocols` are mutable `string[]`, so a `readonly` tuple
74
+ // fixture is not assignable to the surface under test.
75
+ const FRONTEND_PROVIDERS: FrontendProvidersBlock = {
76
+ providers: {
77
+ posthog: {
78
+ metadata: { ref: 'posthog' },
79
+ providerCapabilities: [BUILTIN_PROVIDER_CAPABILITY.FRONTEND_PRODUCT_ANALYTICS],
80
+ protocols: ['FRONTEND_SDK'],
81
+ publicConfig: {
82
+ integrationKind: 'product-analytics',
83
+ websiteRuntime: true,
84
+ },
85
+ },
86
+ },
87
+ };
88
+
89
+ describe('createWebsitePageBridgeIsland', () => {
90
+ it('returns a React component (closes over deps; never crosses prop boundary)', () => {
91
+ const Island = createWebsitePageBridgeIsland({
92
+ PageComponent: PageProbe,
93
+ pageRef: PAGE_REF,
94
+ navigation: buildNavigation(),
95
+ });
96
+ expect(typeof Island).toBe('function');
97
+ expect(Island.displayName).toBe('WebsitePageBridgeIsland');
98
+ });
99
+
100
+ it('renders the page component inside the runtime + page contexts', () => {
101
+ const Island = createWebsitePageBridgeIsland({
102
+ PageComponent: PageProbe,
103
+ pageRef: PAGE_REF,
104
+ navigation: buildNavigation(),
105
+ });
106
+ render(
107
+ <Island
108
+ locale={AvailableLanguage.EN}
109
+ defaultLocale={AvailableLanguage.EN}
110
+ labelPack={{ 'website.landing.hero.headline': 'Hello', ...SKIP_LABEL_PACK }}
111
+ designTokens={DESIGN_TOKENS}
112
+ skipToMainContentLabelKey={SKIP_KEY}
113
+ frontendProviders={FRONTEND_PROVIDERS}
114
+ />,
115
+ );
116
+ expect(screen.getByTestId('page-ref').textContent).toBe('landing');
117
+ expect(screen.getByTestId('locale').textContent).toBe(AvailableLanguage.EN);
118
+ expect(screen.getByTestId('headline').textContent).toBe('Hello');
119
+ expect(screen.getByTestId('primary-color').textContent).toBe('#0070f3');
120
+ expect(screen.getByTestId('provider-ref').textContent).toBe('posthog');
121
+ });
122
+
123
+ it('emits the layout-root data attribute carrying the page ref', () => {
124
+ const Island = createWebsitePageBridgeIsland({
125
+ PageComponent: PageProbe,
126
+ pageRef: PAGE_REF,
127
+ navigation: buildNavigation(),
128
+ });
129
+ const { container } = render(
130
+ <Island
131
+ locale={AvailableLanguage.EN}
132
+ defaultLocale={AvailableLanguage.EN}
133
+ labelPack={SKIP_LABEL_PACK}
134
+ designTokens={DESIGN_TOKENS}
135
+ skipToMainContentLabelKey={SKIP_KEY}
136
+ frontendProviders={FRONTEND_PROVIDERS}
137
+ />,
138
+ );
139
+ expect(container.querySelector('[data-website-page-ref="landing"]')).not.toBeNull();
140
+ expect(
141
+ container.querySelector('[data-website-product-analytics-provider-ref="posthog"]'),
142
+ ).not.toBeNull();
143
+ });
144
+
145
+ it('uses the navigation deps (closure-trapped) for header + footer slots', () => {
146
+ function CustomHeader(): React.ReactElement {
147
+ return <div data-testid="custom-header">HEADER</div>;
148
+ }
149
+ function CustomFooter(): React.ReactElement {
150
+ return <div data-testid="custom-footer">FOOTER</div>;
151
+ }
152
+ const Island = createWebsitePageBridgeIsland({
153
+ PageComponent: PageProbe,
154
+ pageRef: PAGE_REF,
155
+ navigation: { headerComponent: CustomHeader, footerComponent: CustomFooter },
156
+ });
157
+ render(
158
+ <Island
159
+ locale={AvailableLanguage.EN}
160
+ defaultLocale={AvailableLanguage.EN}
161
+ labelPack={SKIP_LABEL_PACK}
162
+ designTokens={DESIGN_TOKENS}
163
+ skipToMainContentLabelKey={SKIP_KEY}
164
+ frontendProviders={FRONTEND_PROVIDERS}
165
+ />,
166
+ );
167
+ expect(screen.getByTestId('custom-header').textContent).toBe('HEADER');
168
+ expect(screen.getByTestId('custom-footer').textContent).toBe('FOOTER');
169
+ });
170
+
171
+ /**
172
+ * Regression guard for the H-2 ship-blocker that motivated this
173
+ * redesign: Astro serializes React-island props via `devalue`-style
174
+ * JSON. Function refs cannot survive that boundary. Asserting that
175
+ * `structuredClone` (the closest in-process analogue to `devalue`'s
176
+ * structured-clone semantics) accepts the prop object means a
177
+ * future contributor cannot accidentally re-introduce a
178
+ * `ComponentType`-typed field on `WebsitePageBridgeIslandProps`
179
+ * without this test failing loudly.
180
+ */
181
+ it('island props are JSON-serializable (ComponentType refs MUST stay closure-trapped, never on the prop boundary)', () => {
182
+ const props: WebsitePageBridgeIslandProps = {
183
+ locale: AvailableLanguage.EN,
184
+ defaultLocale: AvailableLanguage.EN,
185
+ labelPack: { 'website.landing.hero.headline': 'Hello' },
186
+ designTokens: DESIGN_TOKENS,
187
+ skipToMainContentLabelKey: SKIP_KEY,
188
+ frontendProviders: FRONTEND_PROVIDERS,
189
+ };
190
+ expect(() => structuredClone(props)).not.toThrow();
191
+ const cloned = structuredClone(props);
192
+ expect(cloned.locale).toBe(AvailableLanguage.EN);
193
+ expect(cloned.defaultLocale).toBe(AvailableLanguage.EN);
194
+ expect(cloned.labelPack['website.landing.hero.headline']).toBe('Hello');
195
+ expect(cloned.designTokens.colors.primary).toBe('#0070f3');
196
+ expect(cloned.frontendProviders?.providers.posthog.metadata.ref).toBe('posthog');
197
+ });
198
+ });
@@ -0,0 +1,69 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { WebsiteSectionCategory } from '../../schemas/sections/website-section-category.shared';
4
+ import {
5
+ WebsiteSectionDefinitionSchema,
6
+ type WebsiteSectionDefinition,
7
+ } from '../../schemas/sections/website-section-definition.shared.schemas';
8
+ import { WebsiteLabelKeySchema } from '../../schemas/label-keys/website-label-key.schemas';
9
+ import { WebsiteSectionRefSchema } from '../../schemas/refs/section-ref.schemas';
10
+ import { collectExpectedLabelKeysFromSections } from '../collect-expected-label-keys';
11
+
12
+ const NoopComponent = () => null;
13
+
14
+ const buildSection = (ref: string, slots: readonly string[]): WebsiteSectionDefinition =>
15
+ WebsiteSectionDefinitionSchema.parse({
16
+ sectionRef: WebsiteSectionRefSchema.parse(ref),
17
+ category: WebsiteSectionCategory.HERO,
18
+ expectedLabelKeys: slots.map((slot) =>
19
+ WebsiteLabelKeySchema.parse(`website.landing.${ref}.${slot}`),
20
+ ),
21
+ Component: NoopComponent,
22
+ }) as WebsiteSectionDefinition;
23
+
24
+ describe('collectExpectedLabelKeysFromSections', () => {
25
+ it('builds a Map keyed by sectionRef with the section\'s expectedLabelKeys', () => {
26
+ const sections = [
27
+ buildSection('hero', ['headline', 'subheadline']),
28
+ buildSection('cta', ['primary']),
29
+ ];
30
+ const map = collectExpectedLabelKeysFromSections(sections);
31
+ expect(map.size).toBe(2);
32
+ const heroKeys = map.get(WebsiteSectionRefSchema.parse('hero'));
33
+ expect(heroKeys?.map((k) => k as unknown as string)).toEqual([
34
+ 'website.landing.hero.headline',
35
+ 'website.landing.hero.subheadline',
36
+ ]);
37
+ });
38
+
39
+ it('accepts duplicate sectionRef registration when expectedLabelKeys match (cross-page reuse)', () => {
40
+ const sections = [
41
+ buildSection('hero', ['headline']),
42
+ buildSection('hero', ['headline']),
43
+ ];
44
+ const map = collectExpectedLabelKeysFromSections(sections);
45
+ expect(map.size).toBe(1);
46
+ });
47
+
48
+ it('throws on duplicate sectionRef with non-matching expectedLabelKeys', () => {
49
+ const sections = [
50
+ buildSection('hero', ['headline']),
51
+ buildSection('hero', ['headline', 'subheadline']),
52
+ ];
53
+ expect(() => collectExpectedLabelKeysFromSections(sections)).toThrow(
54
+ /Duplicate sectionRef "hero".*non-matching expectedLabelKeys/,
55
+ );
56
+ });
57
+
58
+ it('returns an empty Map when no sections are passed', () => {
59
+ expect(collectExpectedLabelKeysFromSections([]).size).toBe(0);
60
+ });
61
+
62
+ it('treats key arrays as order-insensitive when comparing duplicates', () => {
63
+ const sections = [
64
+ buildSection('hero', ['headline', 'subheadline']),
65
+ buildSection('hero', ['subheadline', 'headline']),
66
+ ];
67
+ expect(() => collectExpectedLabelKeysFromSections(sections)).not.toThrow();
68
+ });
69
+ });
@@ -0,0 +1,143 @@
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
+ renderBlogPostNoaiHeaders,
19
+ renderWebsitePageNoaiHeaders,
20
+ } from '../headers-renderer';
21
+ import {
22
+ composeWebsiteSiteContext,
23
+ type WebsiteSiteContext,
24
+ } from '../website-site-context';
25
+
26
+ const NoopComponent = () => null;
27
+
28
+ const DESIGN_TOKENS = {
29
+ colors: {
30
+ background: '#fff',
31
+ foreground: '#111',
32
+ primary: '#0070f3',
33
+ primaryForeground: '#fff',
34
+ accent: '#ff5e5b',
35
+ accentForeground: '#fff',
36
+ surface: '#fafafa',
37
+ surfaceForeground: '#111',
38
+ border: '#e5e5e5',
39
+ muted: '#f5f5f5',
40
+ mutedForeground: '#737373',
41
+ },
42
+ radii: {
43
+ none: '0',
44
+ sm: '0.125rem',
45
+ md: '0.375rem',
46
+ lg: '0.75rem',
47
+ xl: '1.5rem',
48
+ full: '9999px',
49
+ },
50
+ };
51
+
52
+ function buildManifest(params: {
53
+ ref: string;
54
+ routePath: string;
55
+ noaiOptOut?: boolean;
56
+ }): WebsitePageManifest {
57
+ return WebsitePageManifestSchema.parse({
58
+ ref: WebsitePageRefSchema.parse(params.ref),
59
+ routePath: params.routePath,
60
+ pageComponent: NoopComponent,
61
+ sectionRefs: [WebsiteSectionRefSchema.parse(`${params.ref}-section`)],
62
+ metaLabelKeys: {
63
+ titleKey: WebsiteLabelKeySchema.parse(`website.${params.ref}.meta.title`),
64
+ descriptionKey: WebsiteLabelKeySchema.parse(`website.${params.ref}.meta.description`),
65
+ },
66
+ ...(params.noaiOptOut ? { seo: { noaiOptOut: true } } : {}),
67
+ }) as WebsitePageManifest;
68
+ }
69
+
70
+ function buildSiteContext(pageManifests: readonly WebsitePageManifest[]): WebsiteSiteContext {
71
+ const rootConfig = WebsiteRootConfigSchema.parse({
72
+ pageManifests,
73
+ designTokens: DESIGN_TOKENS,
74
+ navigation: { headerComponent: NoopComponent, footerComponent: NoopComponent },
75
+ seo: { siteName: 'Example Site' },
76
+ skipToMainContentLabelKey: WebsiteLabelKeySchema.parse(
77
+ 'website.chrome.layout.skip-to-main-content',
78
+ ),
79
+ }) as WebsiteRootConfig;
80
+
81
+ const websiteConfig = WildoMarketingWebsiteConfigSchema.parse({
82
+ publicOrigin: 'https://example.com',
83
+ appOrigin: 'https://app.example.com',
84
+ defaultLocale: AvailableLanguage.EN,
85
+ locales: [
86
+ { locale: AvailableLanguage.EN, path: 'en' },
87
+ { locale: AvailableLanguage.FR, path: 'fr' },
88
+ ],
89
+ });
90
+
91
+ return composeWebsiteSiteContext({ rootConfig, websiteConfig });
92
+ }
93
+
94
+ describe('renderWebsitePageNoaiHeaders', () => {
95
+ it('returns empty string when no page opts out of AI training', () => {
96
+ const siteContext = buildSiteContext([buildManifest({ ref: 'landing', routePath: '/' })]);
97
+ expect(renderWebsitePageNoaiHeaders(siteContext)).toBe('');
98
+ });
99
+
100
+ it('emits one path block per localized route for every opted-out page', () => {
101
+ const siteContext = buildSiteContext([
102
+ buildManifest({ ref: 'landing', routePath: '/' }),
103
+ buildManifest({ ref: 'pricing', routePath: '/pricing', noaiOptOut: true }),
104
+ ]);
105
+ expect(renderWebsitePageNoaiHeaders(siteContext)).toBe(
106
+ ['/pricing', ' X-Robots-Tag: noai, noimageai', '', '/fr/pricing', ' X-Robots-Tag: noai, noimageai'].join(
107
+ '\n',
108
+ ),
109
+ );
110
+ });
111
+ });
112
+
113
+ describe('renderBlogPostNoaiHeaders', () => {
114
+ it('returns empty string when the blog post does not opt out', () => {
115
+ const siteContext = buildSiteContext([buildManifest({ ref: 'blog-post', routePath: '/blog/[slug]' })]);
116
+ expect(
117
+ renderBlogPostNoaiHeaders({
118
+ siteContext,
119
+ routePath: '/blog/why-context-beats-content',
120
+ noaiOptOut: false,
121
+ }),
122
+ ).toBe('');
123
+ });
124
+
125
+ it('emits localized header blocks for an opted-out blog route', () => {
126
+ const siteContext = buildSiteContext([buildManifest({ ref: 'blog-post', routePath: '/blog/[slug]' })]);
127
+ expect(
128
+ renderBlogPostNoaiHeaders({
129
+ siteContext,
130
+ routePath: '/blog/why-context-beats-content',
131
+ noaiOptOut: true,
132
+ }),
133
+ ).toBe(
134
+ [
135
+ '/blog/why-context-beats-content',
136
+ ' X-Robots-Tag: noai, noimageai',
137
+ '',
138
+ '/fr/blog/why-context-beats-content',
139
+ ' X-Robots-Tag: noai, noimageai',
140
+ ].join('\n'),
141
+ );
142
+ });
143
+ });
@@ -0,0 +1,116 @@
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 { WebsitePageManifestSchema } from '../../schemas/manifests/website-page-manifest.shared.schemas';
8
+ import {
9
+ WebsiteRootConfigSchema,
10
+ type WebsiteRootConfig,
11
+ } from '../../schemas/manifests/website-root-config.shared.schemas';
12
+ import { WebsitePageRefSchema } from '../../schemas/refs/page-ref.schemas';
13
+ import { WebsiteSectionRefSchema } from '../../schemas/refs/section-ref.schemas';
14
+ import { buildAstroI18nConfig } from '../i18n-routing.helper';
15
+ import { composeWebsiteSiteContext, type WebsiteSiteContext } from '../website-site-context';
16
+
17
+ const NoopComponent = () => null;
18
+
19
+ const COLORS = {
20
+ background: '#fff',
21
+ foreground: '#111',
22
+ primary: '#0070f3',
23
+ primaryForeground: '#fff',
24
+ accent: '#ff5e5b',
25
+ accentForeground: '#fff',
26
+ surface: '#fafafa',
27
+ surfaceForeground: '#111',
28
+ border: '#e5e5e5',
29
+ muted: '#f5f5f5',
30
+ mutedForeground: '#737373',
31
+ };
32
+ const RADII = {
33
+ none: '0',
34
+ sm: '0.125rem',
35
+ md: '0.375rem',
36
+ lg: '0.75rem',
37
+ xl: '1.5rem',
38
+ full: '9999px',
39
+ };
40
+
41
+ const buildManifest = (ref: string, route: string) =>
42
+ WebsitePageManifestSchema.parse({
43
+ ref: WebsitePageRefSchema.parse(ref),
44
+ routePath: route,
45
+ pageComponent: NoopComponent,
46
+ sectionRefs: [WebsiteSectionRefSchema.parse('hero')],
47
+ metaLabelKeys: {
48
+ titleKey: WebsiteLabelKeySchema.parse(`website.${ref}.meta.title`),
49
+ descriptionKey: WebsiteLabelKeySchema.parse(`website.${ref}.meta.description`),
50
+ },
51
+ });
52
+
53
+ const buildRootConfig = (): WebsiteRootConfig =>
54
+ WebsiteRootConfigSchema.parse({
55
+ pageManifests: [buildManifest('landing', '/')],
56
+ designTokens: { colors: COLORS, radii: RADII },
57
+ navigation: { headerComponent: NoopComponent, footerComponent: NoopComponent },
58
+ seo: { siteName: 'Example' },
59
+ skipToMainContentLabelKey: WebsiteLabelKeySchema.parse(
60
+ 'website.chrome.layout.skip-to-main-content',
61
+ ),
62
+ }) as WebsiteRootConfig;
63
+
64
+ const buildSiteContext = (
65
+ locales: AvailableLanguage[],
66
+ defaultLocale: AvailableLanguage,
67
+ ): WebsiteSiteContext =>
68
+ composeWebsiteSiteContext({
69
+ rootConfig: buildRootConfig(),
70
+ websiteConfig: WildoMarketingWebsiteConfigSchema.parse({
71
+ publicOrigin: 'https://example.com',
72
+ appOrigin: 'https://app.example.com',
73
+ defaultLocale,
74
+ locales: locales.map((locale) => ({ locale, path: String(locale) })),
75
+ }),
76
+ });
77
+
78
+ describe('buildAstroI18nConfig', () => {
79
+ it('mirrors the website-config locales as raw strings in declaration order', () => {
80
+ const cfg = buildAstroI18nConfig(
81
+ buildSiteContext([AvailableLanguage.EN, AvailableLanguage.FR], AvailableLanguage.EN),
82
+ );
83
+ expect(cfg.locales).toEqual([AvailableLanguage.EN, AvailableLanguage.FR]);
84
+ expect(cfg.defaultLocale).toBe(AvailableLanguage.EN);
85
+ });
86
+
87
+ it('emits the marketing-site routing convention (default at root, no implicit redirect)', () => {
88
+ const cfg = buildAstroI18nConfig(
89
+ buildSiteContext([AvailableLanguage.EN, AvailableLanguage.FR], AvailableLanguage.EN),
90
+ );
91
+ expect(cfg.routing.prefixDefaultLocale).toBe(false);
92
+ expect(cfg.routing.redirectToDefaultLocale).toBe(false);
93
+ });
94
+
95
+ it('preserves locale order across the helper', () => {
96
+ const cfg = buildAstroI18nConfig(
97
+ buildSiteContext(
98
+ [AvailableLanguage.FR, AvailableLanguage.EN, AvailableLanguage.DE],
99
+ AvailableLanguage.FR,
100
+ ),
101
+ );
102
+ expect(cfg.locales).toEqual([
103
+ AvailableLanguage.FR,
104
+ AvailableLanguage.EN,
105
+ AvailableLanguage.DE,
106
+ ]);
107
+ expect(cfg.defaultLocale).toBe(AvailableLanguage.FR);
108
+ });
109
+
110
+ it('returns a frozen locales array (cannot be mutated downstream)', () => {
111
+ const cfg = buildAstroI18nConfig(
112
+ buildSiteContext([AvailableLanguage.EN], AvailableLanguage.EN),
113
+ );
114
+ expect(Object.isFrozen(cfg.locales)).toBe(true);
115
+ });
116
+ });