@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,202 @@
1
+ import React from 'react';
2
+ import { describe, expect, it, vi } from 'vitest';
3
+ import { render } from '@testing-library/react';
4
+ import '@testing-library/jest-dom';
5
+
6
+ import { AvailableLanguage } from '@wildo-ai/saas-models/public-runtime';
7
+
8
+ import { WebsiteRuntimeContextProvider } from '../../../core/contexts/WebsiteRuntimeContext';
9
+ import { WebsiteLabelKeySchema } from '../../../schemas/label-keys/website-label-key.schemas';
10
+
11
+ import { WebsiteInternalLink } from '../WebsiteInternalLink';
12
+
13
+ const SKIP_KEY = WebsiteLabelKeySchema.parse('website.chrome.layout.skip-to-main-content');
14
+
15
+ function renderWithRuntime(
16
+ ui: React.ReactNode,
17
+ opts: { locale: AvailableLanguage; defaultLocale: AvailableLanguage },
18
+ ): ReturnType<typeof render> {
19
+ return render(
20
+ <WebsiteRuntimeContextProvider
21
+ value={{
22
+ locale: opts.locale,
23
+ defaultLocale: opts.defaultLocale,
24
+ labelPack: {},
25
+ designTokens: {
26
+ 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
+ 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
+ navigation: {
49
+ headerComponent: () => null,
50
+ footerComponent: () => null,
51
+ },
52
+ skipToMainContentLabelKey: SKIP_KEY,
53
+ }}
54
+ >
55
+ {ui}
56
+ </WebsiteRuntimeContextProvider>,
57
+ );
58
+ }
59
+
60
+ describe('WebsiteInternalLink', () => {
61
+ describe('default-locale page (un-prefixed routing)', () => {
62
+ it('renders the routePath verbatim when locale === defaultLocale', () => {
63
+ const { container } = renderWithRuntime(
64
+ <WebsiteInternalLink routePath="/pricing">Pricing</WebsiteInternalLink>,
65
+ { locale: AvailableLanguage.EN, defaultLocale: AvailableLanguage.EN },
66
+ );
67
+ expect(container.querySelector('a')?.getAttribute('href')).toBe('/pricing');
68
+ });
69
+
70
+ it('renders "/" verbatim for the default-locale root', () => {
71
+ const { container } = renderWithRuntime(
72
+ <WebsiteInternalLink routePath="/">Home</WebsiteInternalLink>,
73
+ { locale: AvailableLanguage.EN, defaultLocale: AvailableLanguage.EN },
74
+ );
75
+ expect(container.querySelector('a')?.getAttribute('href')).toBe('/');
76
+ });
77
+ });
78
+
79
+ describe('non-default-locale page (prefixed routing)', () => {
80
+ it('prefixes /<locale> on FR pages', () => {
81
+ const { container } = renderWithRuntime(
82
+ <WebsiteInternalLink routePath="/pricing">Pricing</WebsiteInternalLink>,
83
+ { locale: AvailableLanguage.FR, defaultLocale: AvailableLanguage.EN },
84
+ );
85
+ expect(container.querySelector('a')?.getAttribute('href')).toBe('/fr/pricing');
86
+ });
87
+
88
+ it('collapses root to /<locale> (NOT /<locale>/)', () => {
89
+ const { container } = renderWithRuntime(
90
+ <WebsiteInternalLink routePath="/">Home</WebsiteInternalLink>,
91
+ { locale: AvailableLanguage.FR, defaultLocale: AvailableLanguage.EN },
92
+ );
93
+ expect(container.querySelector('a')?.getAttribute('href')).toBe('/fr');
94
+ });
95
+ });
96
+
97
+ describe('special-case URLs', () => {
98
+ it('passes anchor-only links through verbatim', () => {
99
+ const { container } = renderWithRuntime(
100
+ <WebsiteInternalLink routePath="#features">Features</WebsiteInternalLink>,
101
+ { locale: AvailableLanguage.FR, defaultLocale: AvailableLanguage.EN },
102
+ );
103
+ expect(container.querySelector('a')?.getAttribute('href')).toBe('#features');
104
+ });
105
+
106
+ it('passes mailto: through verbatim', () => {
107
+ const { container } = renderWithRuntime(
108
+ <WebsiteInternalLink routePath="mailto:hi@wildo.ai">Email</WebsiteInternalLink>,
109
+ { locale: AvailableLanguage.FR, defaultLocale: AvailableLanguage.EN },
110
+ );
111
+ expect(container.querySelector('a')?.getAttribute('href')).toBe('mailto:hi@wildo.ai');
112
+ });
113
+
114
+ it('passes tel: through verbatim', () => {
115
+ const { container } = renderWithRuntime(
116
+ <WebsiteInternalLink routePath="tel:+1234567890">Call</WebsiteInternalLink>,
117
+ { locale: AvailableLanguage.EN, defaultLocale: AvailableLanguage.EN },
118
+ );
119
+ expect(container.querySelector('a')?.getAttribute('href')).toBe('tel:+1234567890');
120
+ });
121
+ });
122
+
123
+ describe('misuse guards', () => {
124
+ it('throws when routePath does not start with / and is not an anchor or scheme URL', () => {
125
+ const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
126
+ expect(() =>
127
+ renderWithRuntime(
128
+ <WebsiteInternalLink routePath="pricing">Pricing</WebsiteInternalLink>,
129
+ { locale: AvailableLanguage.EN, defaultLocale: AvailableLanguage.EN },
130
+ ),
131
+ ).toThrow(/must start with/i);
132
+ consoleErrorSpy.mockRestore();
133
+ });
134
+
135
+ it('mentions external={true} (not bare external) in misuse error — Phase 6 fix L-Κ', () => {
136
+ const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
137
+ let caught: Error | null = null;
138
+ try {
139
+ renderWithRuntime(
140
+ <WebsiteInternalLink routePath="pricing">Pricing</WebsiteInternalLink>,
141
+ { locale: AvailableLanguage.EN, defaultLocale: AvailableLanguage.EN },
142
+ );
143
+ } catch (e) {
144
+ caught = e as Error;
145
+ }
146
+ expect(caught?.message).toMatch(/external=\{true\}/);
147
+ consoleErrorSpy.mockRestore();
148
+ });
149
+
150
+ it('throws when routePath begins with the active locale (double-prefix guard) — Phase 6 fix M-Ζ', () => {
151
+ const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
152
+ expect(() =>
153
+ renderWithRuntime(
154
+ <WebsiteInternalLink routePath="/fr/pricing">Pricing</WebsiteInternalLink>,
155
+ { locale: AvailableLanguage.FR, defaultLocale: AvailableLanguage.EN },
156
+ ),
157
+ ).toThrow(/locale segment/i);
158
+ consoleErrorSpy.mockRestore();
159
+ });
160
+
161
+ it('throws when routePath begins with another known locale (cross-locale leak guard)', () => {
162
+ const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
163
+ expect(() =>
164
+ renderWithRuntime(
165
+ <WebsiteInternalLink routePath="/es/pricing">Pricing</WebsiteInternalLink>,
166
+ { locale: AvailableLanguage.EN, defaultLocale: AvailableLanguage.EN },
167
+ ),
168
+ ).toThrow(/locale segment/i);
169
+ consoleErrorSpy.mockRestore();
170
+ });
171
+
172
+ it('throws when used outside <WebsiteRuntimeContextProvider>', () => {
173
+ const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
174
+ expect(() =>
175
+ render(<WebsiteInternalLink routePath="/pricing">Pricing</WebsiteInternalLink>),
176
+ ).toThrow(/WebsiteRuntimeContextProvider/);
177
+ consoleErrorSpy.mockRestore();
178
+ });
179
+ });
180
+
181
+ describe('attribute forwarding', () => {
182
+ it('forwards className, id, ariaLabel to the rendered anchor', () => {
183
+ const { container } = renderWithRuntime(
184
+ <WebsiteInternalLink
185
+ routePath="/contact"
186
+ className="wt-link"
187
+ id="contact-link"
188
+ ariaLabel="Open contact page"
189
+ >
190
+ Contact
191
+ </WebsiteInternalLink>,
192
+ { locale: AvailableLanguage.EN, defaultLocale: AvailableLanguage.EN },
193
+ );
194
+ const a = container.querySelector('a');
195
+ expect(a?.getAttribute('class')).toBe('wt-link');
196
+ expect(a?.getAttribute('id')).toBe('contact-link');
197
+ expect(a?.getAttribute('aria-label')).toBe('Open contact page');
198
+ expect(a?.getAttribute('target')).toBeNull();
199
+ expect(a?.getAttribute('rel')).toBeNull();
200
+ });
201
+ });
202
+ });
@@ -0,0 +1,141 @@
1
+ import React from 'react';
2
+ import { describe, expect, it } from 'vitest';
3
+ import { render } from '@testing-library/react';
4
+ import '@testing-library/jest-dom';
5
+
6
+ import {
7
+ WebsiteHeading,
8
+ WebsiteHeadingVariant,
9
+ } from '../WebsiteHeading';
10
+ import { WebsiteText, WebsiteTextVariant } from '../WebsiteText';
11
+ import { WebsiteLink } from '../WebsiteLink';
12
+ import {
13
+ WebsiteButton,
14
+ WebsiteButtonVariant,
15
+ WebsiteButtonSize,
16
+ } from '../WebsiteButton';
17
+ import { WebsiteImage } from '../WebsiteImage';
18
+
19
+ describe('WebsiteHeading', () => {
20
+ it('renders the correct heading tag for the supplied level', () => {
21
+ const { container } = render(
22
+ <WebsiteHeading level={2} variant={WebsiteHeadingVariant.DISPLAY}>
23
+ Title
24
+ </WebsiteHeading>,
25
+ );
26
+ const h2 = container.querySelector('h2');
27
+ expect(h2).not.toBeNull();
28
+ expect(h2?.getAttribute('data-website-primitive')).toBe('heading');
29
+ expect(h2?.getAttribute('data-website-heading-variant')).toBe('display');
30
+ });
31
+
32
+ it('defaults variant to HEADLINE', () => {
33
+ const { container } = render(<WebsiteHeading level={1}>Title</WebsiteHeading>);
34
+ expect(
35
+ container.querySelector('h1')?.getAttribute('data-website-heading-variant'),
36
+ ).toBe('headline');
37
+ });
38
+ });
39
+
40
+ describe('WebsiteText', () => {
41
+ it('defaults to <p> with body variant', () => {
42
+ const { container } = render(<WebsiteText>Body copy</WebsiteText>);
43
+ const p = container.querySelector('p');
44
+ expect(p).not.toBeNull();
45
+ expect(p?.getAttribute('data-website-text-variant')).toBe('body');
46
+ });
47
+
48
+ it('honors as= and variant=', () => {
49
+ const { container } = render(
50
+ <WebsiteText as="span" variant={WebsiteTextVariant.LEAD}>
51
+ Lead
52
+ </WebsiteText>,
53
+ );
54
+ const span = container.querySelector('span');
55
+ expect(span).not.toBeNull();
56
+ expect(span?.getAttribute('data-website-text-variant')).toBe('lead');
57
+ });
58
+ });
59
+
60
+ describe('WebsiteLink', () => {
61
+ it('renders an internal anchor by default', () => {
62
+ const { container } = render(<WebsiteLink href="/pricing">Pricing</WebsiteLink>);
63
+ const a = container.querySelector('a');
64
+ expect(a?.getAttribute('href')).toBe('/pricing');
65
+ expect(a?.getAttribute('target')).toBeNull();
66
+ expect(a?.getAttribute('rel')).toBeNull();
67
+ });
68
+
69
+ it('applies external link safety attributes when external=true', () => {
70
+ const { container } = render(
71
+ <WebsiteLink href="https://wildo.ai" external>
72
+ Wildo
73
+ </WebsiteLink>,
74
+ );
75
+ const a = container.querySelector('a');
76
+ expect(a?.getAttribute('target')).toBe('_blank');
77
+ expect(a?.getAttribute('rel')).toBe('noopener noreferrer');
78
+ });
79
+ });
80
+
81
+ describe('WebsiteButton', () => {
82
+ it('renders an anchor when href is supplied', () => {
83
+ const { container } = render(
84
+ <WebsiteButton href="/signup" variant={WebsiteButtonVariant.PRIMARY}>
85
+ Sign up
86
+ </WebsiteButton>,
87
+ );
88
+ expect(container.querySelector('a')?.getAttribute('href')).toBe('/signup');
89
+ const inner = container.querySelector('[data-website-primitive="button"]');
90
+ expect(inner?.getAttribute('data-website-button-mode')).toBe('link');
91
+ });
92
+
93
+ it('renders a real <button> when href is omitted', () => {
94
+ const { container } = render(
95
+ <WebsiteButton type="submit" size={WebsiteButtonSize.LG}>
96
+ Submit
97
+ </WebsiteButton>,
98
+ );
99
+ const btn = container.querySelector('button');
100
+ expect(btn).not.toBeNull();
101
+ expect(btn?.getAttribute('type')).toBe('submit');
102
+ expect(btn?.getAttribute('data-website-button-size')).toBe('lg');
103
+ expect(btn?.getAttribute('data-website-button-mode')).toBe('button');
104
+ });
105
+ });
106
+
107
+ describe('WebsiteImage', () => {
108
+ it('renders <img> with required dimensions + lazy/async defaults', () => {
109
+ const { container } = render(
110
+ <WebsiteImage
111
+ src="/hero.png"
112
+ alt="Hero"
113
+ width={1200}
114
+ height={630}
115
+ />,
116
+ );
117
+ const img = container.querySelector('img');
118
+ expect(img?.getAttribute('src')).toBe('/hero.png');
119
+ expect(img?.getAttribute('alt')).toBe('Hero');
120
+ expect(img?.getAttribute('width')).toBe('1200');
121
+ expect(img?.getAttribute('height')).toBe('630');
122
+ expect(img?.getAttribute('loading')).toBe('lazy');
123
+ expect(img?.getAttribute('decoding')).toBe('async');
124
+ });
125
+
126
+ it('honors loading=eager / decoding=sync overrides for above-the-fold imagery', () => {
127
+ const { container } = render(
128
+ <WebsiteImage
129
+ src="/hero.png"
130
+ alt="Hero"
131
+ width={1200}
132
+ height={630}
133
+ loading="eager"
134
+ decoding="sync"
135
+ />,
136
+ );
137
+ const img = container.querySelector('img');
138
+ expect(img?.getAttribute('loading')).toBe('eager');
139
+ expect(img?.getAttribute('decoding')).toBe('sync');
140
+ });
141
+ });
@@ -0,0 +1,231 @@
1
+ /**
2
+ * `defineWebsiteConfig` parse-time contract.
3
+ *
4
+ * Pins the invariants the per-service `wildo.website.config.ts` flow
5
+ * depends on (Step 7.5 refactor — `saas-website-slice.md`, mirrors
6
+ * `saas-technical-doc.md` Step 7.5). This is the framework-owned mirror
7
+ * of the previous root-level `WildoSaasConfig.marketingWebsite?` test
8
+ * surface that lived in `platform-config-lib`. The contract changed
9
+ * in two places and stayed the same everywhere else:
10
+ *
11
+ * - `enabled` REMOVED — file presence under a `frontendType: STATIC_WEBSITE`
12
+ * service IS the enable signal. A previously-required
13
+ * `enabled: true` author would now be rejected by the strict-object
14
+ * schema as an unknown key.
15
+ * - The schema moved to the engine package (`@wildo-ai/saas-website`)
16
+ * so its evolution does not require a parallel
17
+ * `platform-config-lib` edit; the warning prefix is now
18
+ * `[wildo-website-config]` (was `[wildo-saas-config]` for the
19
+ * root-level block).
20
+ */
21
+
22
+ import { afterEach, beforeEach, describe, expect, it, vi, type MockInstance } from 'vitest';
23
+ import { AvailableLanguage } from '@wildo-ai/saas-models/public-runtime';
24
+ import { defineWebsiteConfig } from '../define-website-config';
25
+
26
+ // `MockInstance` takes the ARGS TUPLE and the return type, not the function type — the same
27
+ // arity trap as `vi.fn<…>`. `process.stderr.write` is overloaded, so `Parameters` resolves to
28
+ // its last signature, which is what the spy records.
29
+ let stderrSpy: MockInstance<
30
+ Parameters<typeof process.stderr.write>,
31
+ ReturnType<typeof process.stderr.write>
32
+ >;
33
+
34
+ beforeEach(() => {
35
+ stderrSpy = vi.spyOn(process.stderr, 'write').mockImplementation((() => true) as never);
36
+ });
37
+
38
+ afterEach(() => {
39
+ stderrSpy.mockRestore();
40
+ });
41
+
42
+ function getEmittedRawMessages(): string[] {
43
+ const out: string[] = [];
44
+ for (const call of stderrSpy.mock.calls) {
45
+ const message = call[0];
46
+ if (typeof message === 'string') out.push(message);
47
+ }
48
+ return out;
49
+ }
50
+
51
+ const MINIMAL_VALID_INPUT = {
52
+ publicOrigin: 'https://wonder-todos.example.com',
53
+ appOrigin: 'https://app.wonder-todos.example.com',
54
+ defaultLocale: AvailableLanguage.EN,
55
+ locales: [
56
+ { locale: AvailableLanguage.EN, path: 'en' },
57
+ { locale: AvailableLanguage.FR, path: 'fr' },
58
+ ],
59
+ };
60
+
61
+ describe('defineWebsiteConfig — schema contract', () => {
62
+ it('parses a full valid config', () => {
63
+ const parsed = defineWebsiteConfig(MINIMAL_VALID_INPUT);
64
+ expect(parsed).toEqual(MINIMAL_VALID_INPUT);
65
+ });
66
+
67
+ it('accepts an authored csp block for the website-local sync pipeline', () => {
68
+ const parsed = defineWebsiteConfig({
69
+ ...MINIMAL_VALID_INPUT,
70
+ csp: {
71
+ enabled: true,
72
+ reportOnly: true,
73
+ },
74
+ });
75
+
76
+ expect(parsed.csp).toMatchObject({
77
+ enabled: true,
78
+ reportOnly: true,
79
+ });
80
+ });
81
+
82
+ it('rejects an unknown top-level key (strictObject fail-closed)', () => {
83
+ expect(() =>
84
+ defineWebsiteConfig({
85
+ ...MINIMAL_VALID_INPUT,
86
+ // @ts-expect-error — typoed `publicOrigins` (plural)
87
+ publicOrigins: 'https://example.com',
88
+ }),
89
+ ).toThrow(/Unrecognized key.*publicOrigins/);
90
+ });
91
+
92
+ it('rejects the legacy `enabled` field (Step 7.5 — file presence is the enable signal)', () => {
93
+ expect(() =>
94
+ defineWebsiteConfig({
95
+ ...MINIMAL_VALID_INPUT,
96
+ // @ts-expect-error — `enabled` was removed in Step 7.5; authoring
97
+ // it now is a strict-object error so silently-stale configs
98
+ // surface during the migration.
99
+ enabled: true,
100
+ }),
101
+ ).toThrow(/Unrecognized key.*enabled/);
102
+ });
103
+
104
+ it('rejects a non-URL publicOrigin (z.url fail-closed)', () => {
105
+ expect(() =>
106
+ defineWebsiteConfig({
107
+ ...MINIMAL_VALID_INPUT,
108
+ publicOrigin: 'not-a-url',
109
+ }),
110
+ ).toThrow(/publicOrigin/);
111
+ });
112
+
113
+ it('rejects a publicOrigin with a trailing slash (canonical-URL composition guard, post seam-collapse)', () => {
114
+ expect(() =>
115
+ defineWebsiteConfig({
116
+ ...MINIMAL_VALID_INPUT,
117
+ publicOrigin: 'https://wonder-todos.example.com/',
118
+ }),
119
+ ).toThrow(/publicOrigin.*trailing/i);
120
+ });
121
+
122
+ it('rejects a non-URL appOrigin (z.url fail-closed)', () => {
123
+ expect(() =>
124
+ defineWebsiteConfig({
125
+ ...MINIMAL_VALID_INPUT,
126
+ appOrigin: 'not-a-url',
127
+ }),
128
+ ).toThrow(/appOrigin/);
129
+ });
130
+
131
+ it('rejects an unknown AvailableLanguage in defaultLocale (typo fails at parse time)', () => {
132
+ expect(() =>
133
+ defineWebsiteConfig({
134
+ ...MINIMAL_VALID_INPUT,
135
+ // @ts-expect-error — `xx` is not a valid AvailableLanguage value
136
+ defaultLocale: 'xx',
137
+ }),
138
+ ).toThrow(/defaultLocale/);
139
+ });
140
+
141
+ it('rejects defaultLocale that is not declared in `locales` (cross-field invariant)', () => {
142
+ expect(() =>
143
+ defineWebsiteConfig({
144
+ publicOrigin: 'https://example.com',
145
+ appOrigin: 'https://app.example.com',
146
+ defaultLocale: AvailableLanguage.FR,
147
+ locales: [{ locale: AvailableLanguage.EN, path: 'en' }],
148
+ }),
149
+ ).toThrow(/defaultLocale.*not declared/);
150
+ });
151
+
152
+ it('rejects an empty locales array (must build at least one locale)', () => {
153
+ expect(() =>
154
+ defineWebsiteConfig({
155
+ publicOrigin: 'https://example.com',
156
+ appOrigin: 'https://app.example.com',
157
+ defaultLocale: AvailableLanguage.EN,
158
+ locales: [],
159
+ }),
160
+ ).toThrow(/locales/);
161
+ });
162
+
163
+ it('rejects a locale path with uppercase characters (URL-segment regex fail-closed)', () => {
164
+ expect(() =>
165
+ defineWebsiteConfig({
166
+ publicOrigin: 'https://example.com',
167
+ appOrigin: 'https://app.example.com',
168
+ defaultLocale: AvailableLanguage.EN,
169
+ locales: [{ locale: AvailableLanguage.EN, path: 'EN' }],
170
+ }),
171
+ ).toThrow(/locale path/);
172
+ });
173
+
174
+ it('rejects duplicate locale paths in the locales array (sitemap-collision guard)', () => {
175
+ expect(() =>
176
+ defineWebsiteConfig({
177
+ publicOrigin: 'https://example.com',
178
+ appOrigin: 'https://app.example.com',
179
+ defaultLocale: AvailableLanguage.EN,
180
+ locales: [
181
+ { locale: AvailableLanguage.EN, path: 'shared' },
182
+ { locale: AvailableLanguage.FR, path: 'shared' },
183
+ ],
184
+ }),
185
+ ).toThrow(/Locale path 'shared' is declared by both/);
186
+ });
187
+
188
+ it('rejects duplicate locales in the locales array (hreflang-duplication guard)', () => {
189
+ expect(() =>
190
+ defineWebsiteConfig({
191
+ publicOrigin: 'https://example.com',
192
+ appOrigin: 'https://app.example.com',
193
+ defaultLocale: AvailableLanguage.EN,
194
+ locales: [
195
+ { locale: AvailableLanguage.EN, path: 'en' },
196
+ { locale: AvailableLanguage.EN, path: 'en-us' },
197
+ ],
198
+ }),
199
+ ).toThrow(/Locale '.*' is declared twice/);
200
+ });
201
+ });
202
+
203
+ describe('defineWebsiteConfig — @unimplemented warnings', () => {
204
+ /**
205
+ * The `@unimplemented` warner has ZERO branches today, NOT
206
+ * because every authored field is wired to a runtime consumer
207
+ * — `appOrigin` still has no runtime consumer (the
208
+ * marketing → app handoff link surface that will read it does
209
+ * not exist yet, see the schema file header) — but because the
210
+ * only OPTIONAL field that exists today (`csp?`) is already wired,
211
+ * and firing on every Astro build for a required field would drown
212
+ * the dev console.
213
+ * After the seam-collapse (`saas-website-seam-collapse.md` —
214
+ * SHIPPED), `publicOrigin`, `defaultLocale`, and `locales` are
215
+ * each consumed by at least one engine helper via
216
+ * `WebsiteSiteContext`, so they are no longer candidates for
217
+ * the warner either. See `define-website-config.ts >
218
+ * emitUnimplementedRuntimeWarnings` for the full rationale +
219
+ * the recipe for adding the first branch the day a `seo?` /
220
+ * `structuredData?` optional field lands.
221
+ *
222
+ * This test pins the no-warnings contract so the moment a
223
+ * branch is added (without flipping this expectation in the
224
+ * same commit) it surfaces as a test failure rather than a
225
+ * silent stderr noise on every build.
226
+ */
227
+ it('emits NO warnings (the optional website csp field is fully wired — warner still has zero branches)', () => {
228
+ defineWebsiteConfig(MINIMAL_VALID_INPUT);
229
+ expect(getEmittedRawMessages()).toEqual([]);
230
+ });
231
+ });