@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,215 @@
1
+ /**
2
+ * `loadWebsiteConfig` discovery + memoization contract.
3
+ *
4
+ * Pins the per-service `wildo.website.config.ts` lookup behavior the
5
+ * Astro build / dev pipeline depends on (Step 7.5 refactor —
6
+ * `saas-website-slice.md`, mirrors
7
+ * `platform/factory/wildo-dev-companion/src/technical-doc/services/__tests__/technical-doc.publish-openapi.service.test.ts`'s
8
+ * `cachedTechnicalDocConfig` pre-population trick to skip the jiti
9
+ * round-trip).
10
+ *
11
+ * The harness uses {@link primeWebsiteConfigCache} to inject the
12
+ * loader's memoized value for the resolved absolute path BEFORE the
13
+ * production loader gets a chance to read disk via jiti — keeping
14
+ * the test pure (no `.ts` evaluation, no temp directories) while
15
+ * still exercising every branch of the discovery + path-resolution
16
+ * + cache-hit logic. The "missing service" / "missing file" paths
17
+ * are exercised by NOT priming and pointing at an obviously-missing
18
+ * service, so they hit the throw branches directly.
19
+ */
20
+
21
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
22
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
23
+ import { tmpdir } from 'node:os';
24
+ import { resolve as resolvePath } from 'node:path';
25
+ import {
26
+ AppFrontendType,
27
+ loadConfig,
28
+ type WildoSaasConfig,
29
+ } from '@wildo-ai/platform-config-lib';
30
+ import { AvailableLanguage } from '@wildo-ai/saas-models/public-runtime';
31
+ import {
32
+ PER_SERVICE_WEBSITE_CONFIG_FILE_NAME,
33
+ clearWebsiteConfigCache,
34
+ loadWebsiteConfig,
35
+ primeWebsiteConfigCache,
36
+ } from '../load-website-config';
37
+ import type { WildoMarketingWebsiteConfig } from '../wildo-website-config.schemas';
38
+
39
+ /**
40
+ * Partial-mock pattern: only `loadConfig` is faked; `AppFrontendType`
41
+ * (and any other re-export) keeps its real value so the test can still
42
+ * declare service stubs against the production enum.
43
+ *
44
+ * Mocking `node:fs.existsSync` via `vi.mock` is unreliable on Node
45
+ * builtins under vitest 1.6 (the `node:` prefix routes through the
46
+ * Node native loader before vitest's interception layer); for the
47
+ * one test that needs `existsSync` to return `true` so the loader
48
+ * proceeds to `loadConfig`, we materialize a real config file in a
49
+ * tmpdir-rooted SAAS_ROOT instead of mocking the FS primitive.
50
+ */
51
+ vi.mock('@wildo-ai/platform-config-lib', async (importOriginal) => {
52
+ const actual =
53
+ await importOriginal<typeof import('@wildo-ai/platform-config-lib')>();
54
+ return {
55
+ ...actual,
56
+ loadConfig: vi.fn(actual.loadConfig),
57
+ };
58
+ });
59
+
60
+ beforeEach(() => {
61
+ clearWebsiteConfigCache();
62
+ vi.mocked(loadConfig).mockReset();
63
+ });
64
+
65
+ afterEach(() => {
66
+ clearWebsiteConfigCache();
67
+ vi.restoreAllMocks();
68
+ });
69
+
70
+ const SAAS_ROOT = '/tmp/test-saas-root-doesnt-need-to-exist';
71
+
72
+ const VALID_WEBSITE_CONFIG: WildoMarketingWebsiteConfig = {
73
+ publicOrigin: 'https://wonder-todos.example.com',
74
+ appOrigin: 'https://app.wonder-todos.example.com',
75
+ defaultLocale: AvailableLanguage.EN,
76
+ locales: [
77
+ { locale: AvailableLanguage.EN, path: 'en' },
78
+ { locale: AvailableLanguage.FR, path: 'fr' },
79
+ ],
80
+ };
81
+
82
+ /**
83
+ * Build a partial `WildoSaasConfig` containing only the fields the
84
+ * loader reads (the `services` map). `WildoSaasConfig` itself carries
85
+ * the full shape, but the loader narrows to `services` immediately —
86
+ * casting through `unknown` keeps the harness focused on the loader's
87
+ * actual contract instead of forcing every test to author a fully
88
+ * valid `WildoSaasConfig` payload.
89
+ */
90
+ function buildSaasConfigStub(
91
+ services: Record<string, { path: string; frontendType?: AppFrontendType }>,
92
+ ): WildoSaasConfig {
93
+ return { services } as unknown as WildoSaasConfig;
94
+ }
95
+
96
+ describe('loadWebsiteConfig — discovery contract', () => {
97
+ it('returns the primed cached config when STATIC_WEBSITE service exists', async () => {
98
+ const websiteServicePath = './website';
99
+ const expectedConfigPath = resolvePath(
100
+ SAAS_ROOT,
101
+ websiteServicePath,
102
+ PER_SERVICE_WEBSITE_CONFIG_FILE_NAME,
103
+ );
104
+ primeWebsiteConfigCache(expectedConfigPath, VALID_WEBSITE_CONFIG);
105
+
106
+ const saasConfig = buildSaasConfigStub({
107
+ backendApi: { path: './backend-api' },
108
+ website: { path: websiteServicePath, frontendType: AppFrontendType.STATIC_WEBSITE },
109
+ });
110
+
111
+ const result = await loadWebsiteConfig({ saasRoot: SAAS_ROOT, saasConfig });
112
+ expect(result).toEqual(VALID_WEBSITE_CONFIG);
113
+ });
114
+
115
+ it('discovers the website service by frontendType (NOT by service-key convention)', async () => {
116
+ const websiteServicePath = './marketing';
117
+ const expectedConfigPath = resolvePath(
118
+ SAAS_ROOT,
119
+ websiteServicePath,
120
+ PER_SERVICE_WEBSITE_CONFIG_FILE_NAME,
121
+ );
122
+ primeWebsiteConfigCache(expectedConfigPath, VALID_WEBSITE_CONFIG);
123
+
124
+ // Note: service key is `'marketing'` (NOT `'website'`); the loader
125
+ // MUST find it via `frontendType: STATIC_WEBSITE` lookup, not by
126
+ // grepping for a magic key name.
127
+ const saasConfig = buildSaasConfigStub({
128
+ marketing: { path: websiteServicePath, frontendType: AppFrontendType.STATIC_WEBSITE },
129
+ });
130
+
131
+ const result = await loadWebsiteConfig({ saasRoot: SAAS_ROOT, saasConfig });
132
+ expect(result).toEqual(VALID_WEBSITE_CONFIG);
133
+ });
134
+
135
+ it('memoizes — second call returns the same reference without rehitting the cache map lookup', async () => {
136
+ const websiteServicePath = './website';
137
+ const expectedConfigPath = resolvePath(
138
+ SAAS_ROOT,
139
+ websiteServicePath,
140
+ PER_SERVICE_WEBSITE_CONFIG_FILE_NAME,
141
+ );
142
+ primeWebsiteConfigCache(expectedConfigPath, VALID_WEBSITE_CONFIG);
143
+
144
+ const saasConfig = buildSaasConfigStub({
145
+ website: { path: websiteServicePath, frontendType: AppFrontendType.STATIC_WEBSITE },
146
+ });
147
+
148
+ const first = await loadWebsiteConfig({ saasRoot: SAAS_ROOT, saasConfig });
149
+ const second = await loadWebsiteConfig({ saasRoot: SAAS_ROOT, saasConfig });
150
+ expect(first).toBe(second);
151
+ });
152
+
153
+ it('throws when no STATIC_WEBSITE service is declared (missing-service guard)', async () => {
154
+ const saasConfig = buildSaasConfigStub({
155
+ backendApi: { path: './backend-api' },
156
+ app: { path: './app', frontendType: AppFrontendType.SAAS_APP },
157
+ });
158
+
159
+ await expect(loadWebsiteConfig({ saasRoot: SAAS_ROOT, saasConfig })).rejects.toThrow(
160
+ /\[wildo-website-config\] No frontend service with `frontendType: STATIC_WEBSITE`/,
161
+ );
162
+ });
163
+
164
+ it('throws when the STATIC_WEBSITE service is declared but the per-service config file is missing', async () => {
165
+ const websiteServicePath = './website-no-file';
166
+ const saasConfig = buildSaasConfigStub({
167
+ website: { path: websiteServicePath, frontendType: AppFrontendType.STATIC_WEBSITE },
168
+ });
169
+
170
+ // Cache NOT primed → loader proceeds to existsSync(...) which
171
+ // returns false for the bogus SAAS_ROOT path.
172
+ await expect(loadWebsiteConfig({ saasRoot: SAAS_ROOT, saasConfig })).rejects.toThrow(
173
+ /\[wildo-website-config\].*wildo\.website\.config\.ts.*is missing/,
174
+ );
175
+ });
176
+
177
+ it('wraps a `loadConfig` (jiti) failure with the [wildo-website-config] CONFIGURATION-style message', async () => {
178
+ /**
179
+ * Materialize a real per-service config file in a tmpdir so the
180
+ * loader's `existsSync` guard returns `true` for real, then
181
+ * intercept the jiti-backed `loadConfig` to simulate a parse /
182
+ * default-export failure (the realistic root cause of this
183
+ * branch — `process.stderr` warnings cannot fail it; only
184
+ * jiti can).
185
+ */
186
+ const tmpRoot = mkdtempSync(resolvePath(tmpdir(), 'wildo-website-loader-'));
187
+ try {
188
+ const websiteServicePath = './website-bad-export';
189
+ const websiteServiceDir = resolvePath(tmpRoot, websiteServicePath);
190
+ mkdirSync(websiteServiceDir, { recursive: true });
191
+ writeFileSync(
192
+ resolvePath(websiteServiceDir, PER_SERVICE_WEBSITE_CONFIG_FILE_NAME),
193
+ '// placeholder — content does not matter; loadConfig is mocked\n',
194
+ 'utf-8',
195
+ );
196
+
197
+ const saasConfig = buildSaasConfigStub({
198
+ website: { path: websiteServicePath, frontendType: AppFrontendType.STATIC_WEBSITE },
199
+ });
200
+
201
+ vi.mocked(loadConfig).mockRejectedValue(
202
+ new Error('jiti: Cannot find default export'),
203
+ );
204
+
205
+ await expect(loadWebsiteConfig({ saasRoot: tmpRoot, saasConfig })).rejects.toThrow(
206
+ /\[wildo-website-config\] Failed to load per-service marketing-website config/,
207
+ );
208
+ await expect(loadWebsiteConfig({ saasRoot: tmpRoot, saasConfig })).rejects.toThrow(
209
+ /Cause: jiti: Cannot find default export/,
210
+ );
211
+ } finally {
212
+ rmSync(tmpRoot, { recursive: true, force: true });
213
+ }
214
+ });
215
+ });
@@ -0,0 +1,134 @@
1
+ /**
2
+ * @fileoverview Author helper for `wildo.website.config.ts` files.
3
+ *
4
+ * `defineWebsiteConfig({...})` mirrors the `defineSaasConfig` /
5
+ * `defineInfraEnvConfig` helpers from `@wildo-ai/platform-config-lib`
6
+ * and `defineTechnicalDocConfig` from `@wildo-ai/saas-technical-doc`:
7
+ * it wraps `WildoMarketingWebsiteConfigSchema.parse(...)` with full
8
+ * IDE autocompletion on the input shape, and emits a parse-time
9
+ * stderr warning for any authored field that has no runtime
10
+ * consumer yet.
11
+ *
12
+ * # `@unimplemented` channel — current state
13
+ *
14
+ * The warner has ZERO branches today because every OPTIONAL field that
15
+ * exists is already wired (`csp?` now flows through the website-local
16
+ * CSP sync step) and the only unwired field left is `appOrigin`, which
17
+ * is REQUIRED. After the seam-collapse (`saas-website-seam-collapse.md`
18
+ * — SHIPPED), the routing/origin fields (`publicOrigin`,
19
+ * `defaultLocale`, `locales`) are consumed directly by every Astro
20
+ * engine helper via `WebsiteSiteContext`, so they are no longer
21
+ * candidates for a "deferred wiring" warning either. The
22
+ * `@unimplemented` warner is for fields that are parsed-but-unused AND
23
+ * opt-in — firing on every Astro build for a required field would drown
24
+ * the dev console.
25
+ *
26
+ * Two valid futures will trigger adding warner branches:
27
+ * 1. A future optional field lands (e.g. `seo?.{title,description}`,
28
+ * `structuredData?.organization`) whose runtime wiring ships
29
+ * in a later PR — that would get
30
+ * `if (config.seo !== undefined) warn('seo', ...)` until the
31
+ * renderer reads it.
32
+ * 2. A required field temporarily loses its consumer (e.g. a
33
+ * large refactor splits the SEO renderer into two PRs); the
34
+ * branch fires unconditionally for the duration.
35
+ *
36
+ * The scaffold is kept (and the no-warning contract is pinned by
37
+ * the test suite) so the moment either case lands, the warning
38
+ * channel is one branch addition away — matching the
39
+ * `defineTechnicalDocConfig` shape exactly so future authors
40
+ * grepping for `@unimplemented` find a uniform pattern.
41
+ *
42
+ * Why we don't reuse `platform-config-lib`'s
43
+ * `createUnimplementedFieldWarner`:
44
+ *
45
+ * `platform-config-lib` is the platform-level boundary; this engine
46
+ * package is downstream of it for everything else. Importing the
47
+ * warner from `platform-config-lib` would invert the dependency
48
+ * direction (engine → platform-config), creating the same architectural
49
+ * defect Step 7.5 set out to fix. The warning format is small and
50
+ * stable enough to inline here — kept structurally similar to the
51
+ * platform-config-lib warner so readers grepping for `@unimplemented`
52
+ * still find both surfaces.
53
+ */
54
+
55
+ import {
56
+ WildoMarketingWebsiteConfigSchema,
57
+ type WildoMarketingWebsiteConfig,
58
+ type WildoMarketingWebsiteConfigInput,
59
+ } from './wildo-website-config.schemas';
60
+
61
+ /**
62
+ * Define a per-service marketing-website config. Provides full IDE
63
+ * autocompletion on the input shape, runs Zod validation (fail-closed
64
+ * on typos / unknown keys / out-of-range values / cross-field
65
+ * invariants like `defaultLocale ∈ locales`), and emits parse-time
66
+ * `@unimplemented` warnings for fields that are authored but have no
67
+ * runtime consumer yet.
68
+ *
69
+ * Throws on validation failure — same contract as `defineSaasConfig`
70
+ * and `defineTechnicalDocConfig`.
71
+ *
72
+ * @example
73
+ * ```ts
74
+ * import { AvailableLanguage } from '@wildo-ai/saas-models/public-runtime';
75
+ * import { defineWebsiteConfig } from '@wildo-ai/saas-website';
76
+ *
77
+ * export default defineWebsiteConfig({
78
+ * publicOrigin: 'https://wonder-todos.example.com',
79
+ * appOrigin: 'https://app.wonder-todos.example.com',
80
+ * defaultLocale: AvailableLanguage.EN,
81
+ * locales: [
82
+ * { locale: AvailableLanguage.EN, path: 'en' },
83
+ * { locale: AvailableLanguage.FR, path: 'fr' },
84
+ * ],
85
+ * });
86
+ * ```
87
+ */
88
+ export function defineWebsiteConfig(
89
+ config: WildoMarketingWebsiteConfigInput,
90
+ ): WildoMarketingWebsiteConfig {
91
+ const parsed = WildoMarketingWebsiteConfigSchema.parse(config);
92
+ emitUnimplementedRuntimeWarnings(parsed);
93
+ return parsed;
94
+ }
95
+
96
+ /**
97
+ * Emit `@unimplemented` parse-time warnings for every authored
98
+ * OPTIONAL field on `WildoMarketingWebsiteConfig` that is parsed
99
+ * but has no runtime consumer yet.
100
+ *
101
+ * Branches today: ZERO. The one OPTIONAL field that exists (`csp?`) is
102
+ * fully wired through the website-local CSP sync/build path, while the
103
+ * only field that still lacks a runtime consumer (`appOrigin`) is
104
+ * REQUIRED. After the seam-collapse (`saas-website-seam-collapse.md` —
105
+ * SHIPPED) the routing/origin fields are consumed directly by every
106
+ * Astro engine helper via `WebsiteSiteContext`. Required-but-unwired
107
+ * fields cannot reasonably fire on every parse — that's a
108
+ * deferred-wiring surface tracked in the schema header, not an
109
+ * `@unimplemented` warner concern.
110
+ *
111
+ * The function exists as a structural mirror of
112
+ * `defineTechnicalDocConfig.emitUnimplementedRuntimeWarnings` so
113
+ * readers grepping for `emitUnimplementedRuntimeWarnings` find a
114
+ * uniform pattern across every `defineXxxConfig` helper.
115
+ *
116
+ * # Adding a branch (recipe)
117
+ *
118
+ * When the first OPTIONAL `@unimplemented` field lands (most
119
+ * likely candidate: `seo?.{title,description}`), add the warner
120
+ * machinery here: a `seen` set + a
121
+ * `warn(fieldPath, sliceId)` helper that formats as
122
+ * `[wildo-website-config] @unimplemented field '...'` and writes
123
+ * to `process.stderr`, then the matching `if (config.<field> !==
124
+ * undefined)` branch. Copy the exact shape from
125
+ * `define-tech-doc-config.ts > emitUnimplementedRuntimeWarnings`
126
+ * to keep the message format identical across helpers. The test
127
+ * suite pins the zero-warnings contract — flip its expectation in
128
+ * the same commit.
129
+ */
130
+ function emitUnimplementedRuntimeWarnings(_config: WildoMarketingWebsiteConfig): void {
131
+ // Intentional no-op — see function JSDoc for why a no-op is the
132
+ // correct shape today (optional fields are either fully wired or
133
+ // absent) and the recipe for adding the first branch.
134
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * `@wildo-ai/saas-website` — config surface.
3
+ *
4
+ * Per-service `wildo.website.config.ts` schema + `defineWebsiteConfig`
5
+ * helper. Co-located with the engine package that owns the marketing-
6
+ * site primitives so a new SEO field / locale-pack id ships in a
7
+ * single PR with no cross-package allow-list to keep in sync (see
8
+ * `wildo-website-config.schemas.ts` JSDoc for the architectural
9
+ * rationale behind moving this out of `platform-config-lib` —
10
+ * `saas-website-slice.md` Step 7.5 mirrors the
11
+ * `saas-technical-doc.md` Step 7.5 fix).
12
+ *
13
+ * @wildo-boundary
14
+ * This sub-tree is split intentionally:
15
+ *
16
+ * - `wildo-website-config.schemas.ts` — Zod-only, no Node-only
17
+ * imports, no `process.*` references. Safe to bundle anywhere.
18
+ * - `define-website-config.ts` — uses `process.stderr.write` for
19
+ * the (currently empty) `@unimplemented` warner. Bundle-safe in
20
+ * practice (the global `process` is polyfilled by every modern
21
+ * bundler and the warner has no `node:*` bare specifier), so it
22
+ * can ship through the root barrel alongside the schema.
23
+ * - `load-website-config.ts` — Node-only. Imports `loadConfig` +
24
+ * `AppFrontendType` from `@wildo-ai/platform-config-lib` and
25
+ * `node:fs` / `node:path`. Re-exported via the dedicated
26
+ * package subpath `./config-loader` (NOT from this barrel) so
27
+ * the React-island closure stays free of Node-only specifiers
28
+ * (enforced by `__tests__/bundle-isolation.test.ts`).
29
+ *
30
+ * Consumers:
31
+ * - Author per-service config: `import { defineWebsiteConfig }`
32
+ * from `'@wildo-ai/saas-website'`.
33
+ * - Load per-service config from a Node-side build / dev pipeline:
34
+ * `import { loadWebsiteConfig }` from `'@wildo-ai/saas-website/config-loader'`.
35
+ */
36
+
37
+ export * from './define-website-config';
38
+ export * from './wildo-website-config.schemas';
@@ -0,0 +1,229 @@
1
+ /**
2
+ * @fileoverview Per-service `wildo.website.config.ts` loader.
3
+ *
4
+ * Mirrors the `loadTechnicalDocConfig()` shape used by
5
+ * `platform/factory/wildo-dev-companion/src/technical-doc/services/technical-doc.companion.service.ts`
6
+ * (see `saas-technical-doc.md` Step 7.5). Discovers the unique
7
+ * `frontendType: STATIC_WEBSITE` service in the SaaS config, joins
8
+ * `<saasRoot>/<service.path>/wildo.website.config.ts`, and loads it
9
+ * via the jiti-backed `loadConfig` from `@wildo-ai/platform-config-lib`.
10
+ *
11
+ * The actual Zod validation runs INSIDE `defineWebsiteConfig` (which
12
+ * the per-service config file invokes via
13
+ * `export default defineWebsiteConfig({...})`), so by the time
14
+ * `loadConfig` returns we already hold a parsed
15
+ * `WildoMarketingWebsiteConfig`.
16
+ *
17
+ * @wildo-boundary
18
+ * This module is **Node-only**. It imports `loadConfig` and
19
+ * `AppFrontendType` from `@wildo-ai/platform-config-lib`, which
20
+ * transitively pulls `jiti`, `node:fs`, `node:path`, `node:crypto`,
21
+ * and other Node-only API surface. It MUST NOT be imported from
22
+ * any browser-bundled entry of this engine package — specifically
23
+ * NOT from `index.ts`, `companion-exports.ts`, or `astro-island.ts`.
24
+ * The dedicated `./config-loader` package subpath exists precisely
25
+ * to keep this surface OUT of the React-island bundle (enforced by
26
+ * `__tests__/bundle-isolation.test.ts`).
27
+ *
28
+ * The Astro build's `astro.config.ts` (a Node-side file by
29
+ * construction) is the canonical caller. A future companion
30
+ * subprocess that audits the marketing surface would also call
31
+ * from here. Browser bundles MUST NOT.
32
+ */
33
+
34
+ import { existsSync } from 'node:fs';
35
+ import { resolve as resolvePath } from 'node:path';
36
+ import { AppFrontendType, loadConfig } from '@wildo-ai/platform-config-lib';
37
+ import type { WildoSaasConfig } from '@wildo-ai/platform-config-lib';
38
+ import type { WildoMarketingWebsiteConfig } from './wildo-website-config.schemas';
39
+
40
+ /**
41
+ * Conventional file name for the per-service marketing-website
42
+ * configuration (Step 7.5 refactor — `saas-website-slice.md` /
43
+ * `saas-technical-doc.md` Step 7.5 mirror). Authored at the website
44
+ * service root next to `package.json` / `astro.config.ts`.
45
+ *
46
+ * Discovery is "by convention, not by configuration":
47
+ *
48
+ * - The loader locates this file by walking the `services` entry
49
+ * whose `frontendType === STATIC_WEBSITE` and joining
50
+ * `<saasRoot>/<service.path>/`. The saas-config schema's
51
+ * `superRefine` already enforces "at most one STATIC_WEBSITE
52
+ * service per app", so the lookup is deterministic — apps can
53
+ * name the website service anything (`'website'`, `'marketing'`,
54
+ * `'site'`).
55
+ *
56
+ * - File presence is the enable signal — there is NO `enabled`
57
+ * field on `WildoMarketingWebsiteConfigSchema`. A STATIC_WEBSITE
58
+ * service without this file is treated as a misconfiguration
59
+ * and the loader throws (rather than silently shipping an Astro
60
+ * build with no `i18n` routing / no canonical origin).
61
+ */
62
+ export const PER_SERVICE_WEBSITE_CONFIG_FILE_NAME = 'wildo.website.config.ts';
63
+
64
+ /**
65
+ * Memoization slot scoped to the loader module. Keyed by absolute
66
+ * config file path so multiple SaaS configs evaluated in the same
67
+ * process (rare in production — possible in tests) do not collide.
68
+ *
69
+ * Lifetime: the loader is invoked from the Astro build / dev-server
70
+ * pipeline, both of which restart the Node process on every dev-stack
71
+ * reload (the watcher that picks up changes to `wildo.website.config.ts`
72
+ * is the same watcher that reboots the build). A stale entry here
73
+ * therefore cannot outlive a config edit.
74
+ *
75
+ * Tests can pre-populate a known path via {@link primeWebsiteConfigCache}
76
+ * to skip the jiti round-trip when exercising downstream consumers.
77
+ */
78
+ const WEBSITE_CONFIG_CACHE = new Map<string, WildoMarketingWebsiteConfig>();
79
+
80
+ /**
81
+ * Test helper — pre-populate the loader's memoization slot for a
82
+ * given absolute config path. Mirrors the
83
+ * `cachedTechnicalDocConfig` pre-population trick used by
84
+ * `technical-doc.publish-openapi.service.test.ts` so the loader's
85
+ * downstream consumers can be exercised in tests without writing a
86
+ * `.ts` file to disk + invoking jiti.
87
+ *
88
+ * Production callers MUST NOT use this helper.
89
+ */
90
+ export function primeWebsiteConfigCache(
91
+ absoluteConfigPath: string,
92
+ config: WildoMarketingWebsiteConfig,
93
+ ): void {
94
+ WEBSITE_CONFIG_CACHE.set(resolvePath(absoluteConfigPath), config);
95
+ }
96
+
97
+ /**
98
+ * Test helper — clear every cached entry. Use in `beforeEach`/`afterEach`
99
+ * to prevent test cross-contamination.
100
+ */
101
+ export function clearWebsiteConfigCache(): void {
102
+ WEBSITE_CONFIG_CACHE.clear();
103
+ }
104
+
105
+ /**
106
+ * Arguments for {@link loadWebsiteConfig}. Kept as a structured shape
107
+ * (rather than positional `loadWebsiteConfig(saasRoot, saasConfig)`)
108
+ * so that future per-call options (e.g. an explicit `serviceKey`
109
+ * override for multi-website apps the day the singleton invariant
110
+ * relaxes) slot in without breaking callers.
111
+ */
112
+ export interface LoadWebsiteConfigArgs {
113
+ /**
114
+ * Absolute path to the SaaS app root — the directory holding
115
+ * `wildo.saas.config.ts`. Used to resolve `<saasRoot>/<service.path>/`
116
+ * for the website service folder.
117
+ */
118
+ saasRoot: string;
119
+ /**
120
+ * The already-parsed SaaS config (load once via
121
+ * `loadConfig<WildoSaasConfig>(...)` upstream and pass it down here
122
+ * — re-parsing per call would invoke jiti twice for one logical
123
+ * load).
124
+ */
125
+ saasConfig: WildoSaasConfig;
126
+ }
127
+
128
+ /**
129
+ * Discover and load the per-service marketing-website config.
130
+ *
131
+ * Resolution algorithm (mirrors
132
+ * `TechnicalDocCompanionService.loadTechnicalDocConfig`):
133
+ *
134
+ * 1. Find the unique service in `saasConfig.services` whose
135
+ * `frontendType === STATIC_WEBSITE`. The saas-config
136
+ * `superRefine` guarantees there is at most one — if there is
137
+ * none, throw with the misconfiguration message.
138
+ * 2. Resolve `<saasRoot>/<service.path>/wildo.website.config.ts`.
139
+ * 3. If the file does not exist, throw (file presence IS the
140
+ * enable signal — a STATIC_WEBSITE service without this file
141
+ * is broken).
142
+ * 4. Check the memoization slot keyed on the absolute path; return
143
+ * the cached value if present.
144
+ * 5. Call `loadConfig<WildoMarketingWebsiteConfig>(absolutePath)`.
145
+ * jiti evaluates the file, which invokes `defineWebsiteConfig`,
146
+ * which invokes `WildoMarketingWebsiteConfigSchema.parse` and
147
+ * emits any `@unimplemented` warnings. The returned object is
148
+ * already-validated — we trust the in-file `defineWebsiteConfig`
149
+ * contract here (mirrors the technical-doc loader pattern in
150
+ * `TechnicalDocCompanionService.loadTechnicalDocConfig`, which
151
+ * also does NOT re-parse). A file that hand-rolls the default
152
+ * export without going through `defineWebsiteConfig` would
153
+ * surface as a downstream type mismatch — acceptable given the
154
+ * authoring contract is explicit and grep-discoverable.
155
+ * 6. Memoize and return.
156
+ *
157
+ * @throws Error with a `[wildo-website-config]` prefix if no
158
+ * STATIC_WEBSITE service is declared, the per-service config
159
+ * file is missing, or the file's default export does not
160
+ * pass `WildoMarketingWebsiteConfigSchema`. The loader
161
+ * intentionally throws plain `Error`s rather than building a
162
+ * `WildoBackendError` so it stays usable from the Astro
163
+ * build pipeline (which has no DI container) — the companion
164
+ * can wrap with `errorBuilder` at the call site if needed
165
+ * (mirrors how `loadConfig` itself throws plain `Error`).
166
+ */
167
+ export async function loadWebsiteConfig(
168
+ args: LoadWebsiteConfigArgs,
169
+ ): Promise<WildoMarketingWebsiteConfig> {
170
+ const { saasRoot, saasConfig } = args;
171
+
172
+ const websiteServiceEntry = Object.entries(saasConfig.services).find(
173
+ ([, service]) => service.frontendType === AppFrontendType.STATIC_WEBSITE,
174
+ );
175
+ if (!websiteServiceEntry) {
176
+ throw new Error(
177
+ '[wildo-website-config] No frontend service with `frontendType: STATIC_WEBSITE` is declared in '
178
+ + 'wildo.saas.config.ts. The marketing-website Astro build / loader requires an authored '
179
+ + 'STATIC_WEBSITE service because that service folder owns the per-service '
180
+ + '`wildo.website.config.ts` (publicOrigin, appOrigin, defaultLocale, locales). '
181
+ + 'Add a STATIC_WEBSITE service in wildo.saas.config.ts and ship the per-service '
182
+ + 'config file under it.',
183
+ );
184
+ }
185
+
186
+ const [websiteServiceKey, websiteService] = websiteServiceEntry;
187
+ const websiteConfigPath = resolvePath(
188
+ saasRoot,
189
+ websiteService.path,
190
+ PER_SERVICE_WEBSITE_CONFIG_FILE_NAME,
191
+ );
192
+
193
+ const cached = WEBSITE_CONFIG_CACHE.get(websiteConfigPath);
194
+ if (cached !== undefined) {
195
+ return cached;
196
+ }
197
+
198
+ if (!existsSync(websiteConfigPath)) {
199
+ throw new Error(
200
+ `[wildo-website-config] STATIC_WEBSITE service '${websiteServiceKey}' is declared in `
201
+ + `wildo.saas.config.ts but the required per-service config file `
202
+ + `'${PER_SERVICE_WEBSITE_CONFIG_FILE_NAME}' is missing under `
203
+ + `'${websiteService.path}/'. File presence IS the enable signal `
204
+ + '(saas-website-slice.md / saas-technical-doc.md Step 7.5 — the legacy '
205
+ + '`marketingWebsite` block on wildo.saas.config.ts was dropped in favor of '
206
+ + 'a per-service file). Create '
207
+ + `'${websiteConfigPath}' with `
208
+ + '`export default defineWebsiteConfig({ ... })` from `@wildo-ai/saas-website`.',
209
+ );
210
+ }
211
+
212
+ let loaded: WildoMarketingWebsiteConfig;
213
+ try {
214
+ loaded = await loadConfig<WildoMarketingWebsiteConfig>(websiteConfigPath);
215
+ } catch (e) {
216
+ const cause = e instanceof Error ? e.message : String(e);
217
+ throw new Error(
218
+ `[wildo-website-config] Failed to load per-service marketing-website config at `
219
+ + `${websiteConfigPath}. The file MUST do `
220
+ + '`export default defineWebsiteConfig({...})` and pass '
221
+ + 'WildoMarketingWebsiteConfigSchema (`z.strictObject` — unknown keys reject; '
222
+ + '`defaultLocale` must appear in `locales`). '
223
+ + `Cause: ${cause}`,
224
+ );
225
+ }
226
+
227
+ WEBSITE_CONFIG_CACHE.set(websiteConfigPath, loaded);
228
+ return loaded;
229
+ }