@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,607 @@
1
+ /**
2
+ * @wildo_source:part:start engine.client-bundle.discipline facet:layer:engine facet:family:bundle-boundary
3
+ *
4
+ * Browser-bundle boundary for `@wildo-ai/saas-website`.
5
+ *
6
+ * The entire `saas-website` source tree ships into a browser bundle
7
+ * (Astro SSG + React islands). Anything that lands here transitively
8
+ * pulls into the marketing-site customer bundle. This test walks the
9
+ * source closure starting from each public package entry and asserts
10
+ * that every bare-specifier import is on a tightly-scoped allow-list,
11
+ * and that no forbidden specifier (decorator-framework boot, backend
12
+ * libs, Node-only deps) ever appears.
13
+ *
14
+ * Walked entries (current package.json `exports`):
15
+ * - `./src/index.ts` — root barrel (React + schemas)
16
+ * - `./src/companion-exports.ts` — React-free companion entry
17
+ * - `./src/astro.ts` — Astro adapter SSR/frontmatter
18
+ * sub-entry. Pulls
19
+ * `node:fs` / `node:path` for the
20
+ * label-pack loader. MUST NOT pull
21
+ * the `astro` runtime itself (that
22
+ * lives only inside `.astro` files,
23
+ * which Astro's compiler processes
24
+ * separately) AND MUST NOT pull the
25
+ * bridge-runtime island module
26
+ * (split into `./astro-island` to
27
+ * keep the client graph free of
28
+ * `node:*` specifiers — Vite's
29
+ * resolver runs before
30
+ * tree-shaking and would hard-fail
31
+ * on `__vite-browser-external`
32
+ * otherwise).
33
+ * - `./src/astro-island.ts` — Astro adapter CLIENT-bundle
34
+ * sub-entry.
35
+ * Exposes only
36
+ * `createWebsitePageBridgeIsland`.
37
+ * MUST stay 100% bundle-clean
38
+ * (no `node:*`, no SSR-tier
39
+ * specifiers) — the SSR-tier
40
+ * negative test below asserts that.
41
+ * - `./src/config-loader.ts` — Node-only entry that loads the
42
+ * per-service `wildo.website.config.ts`
43
+ * via `loadConfig` from
44
+ * `@wildo-ai/platform-config-lib`
45
+ * (jiti-backed, transitively pulls
46
+ * `node:fs` / `node:path` /
47
+ * `node:crypto`). Walked here so the
48
+ * SSR-tier negative test below
49
+ * proves `@wildo-ai/platform-config-lib`
50
+ * reaches the closure ONLY through
51
+ * this entry — never through
52
+ * `index.ts`, `companion-exports.ts`,
53
+ * or `astro-island.ts`.
54
+ *
55
+ * `./mdx.ts` is the marketing-blog adapter subpath. It is included in
56
+ * `ENTRY_FILES` below and pulls `@mdx-js/react`. The provider lives in the
57
+ * React-island closure (consumed transitively
58
+ * from a consumer's per-post `.astro` page), so the SSR-tier negative
59
+ * test does NOT apply to `@mdx-js/react` — the specifier is
60
+ * unconditionally allowed across all entries.
61
+ *
62
+ * **Why `.astro` files are NOT in the walked closure**: this test
63
+ * walks `.ts` / `.tsx` only — the import-collection regex assumes
64
+ * TypeScript module syntax. `.astro` files have their own frontmatter
65
+ * grammar (Astro's compiler handles them), and they live on the
66
+ * Astro-build-time side of the boundary, not the React-island side.
67
+ * The TypeScript helpers the `.astro` shell calls into (everything
68
+ * exported from `astro.ts`) ARE walked — that's where the boundary
69
+ * actually matters.
70
+ *
71
+ * **Client-bundle import discipline**
72
+ * The single allowed door into `@wildo-ai/saas-models` is the
73
+ * side-effect-free `/public-runtime` subpath. The root barrel
74
+ * calls `initZodDecorators()` at module load and pulls
75
+ * `reflect-metadata` + the `@wildo-ai/external-connectors-models`
76
+ * graph + the full schema tree — none of which belongs in a marketing
77
+ * bundle.
78
+ *
79
+ * Same forbid applies to the ROOT barrel of
80
+ * `@wildo-ai/presets-components-models` (boots the full preset +
81
+ * component-metadata graph). Its `/public-runtime` SUBPATH is
82
+ * explicitly ALLOWED for callers that need locale-tier symbols
83
+ * beyond what `@wildo-ai/saas-models/public-runtime` re-exports
84
+ * New browser-facing atoms follow the same pattern: expose a narrow public
85
+ * runtime export rather than duplicating a type or importing a heavy root
86
+ * package.
87
+ *
88
+ * `@wildo-ai/zod-decorators` is allowed.
89
+ * Importing the package alone is side-effect-free — the prototype
90
+ * mutation only happens when a caller explicitly invokes
91
+ * `initZodDecorators()`, which neither saas-website nor the
92
+ * saas-models /public-runtime barrel does. Allowing the import
93
+ * leaves room for build-time decorator-metadata reads on schemas
94
+ * imported from /public-runtime (e.g. `WildoZodDecorators.getMetadata(...)`
95
+ * in a future Astro build-time generator) without forcing those
96
+ * call sites to live outside the walked closure.
97
+ *
98
+ * `axios`, `jsonwebtoken`, `socket.io-client`, and
99
+ * `reflect-metadata` are now ALLOWED. The website engine needs
100
+ * them for SERVER-SIDE Astro middleware code paths (request
101
+ * proxying, JWT verification, real-time push) that do NOT ship
102
+ * to the browser island bundle. Adding them collapses this test
103
+ * from a "bundle-bloat guard" to a "stack-creep guard": it still
104
+ * catches `mongoose`, `inversify`, `express`, `pino`, `helmet`,
105
+ * `argon2`, `bcrypt`, the heavy package root barrels of
106
+ * `@wildo-ai/saas-models` / `@wildo-ai/presets-components-models`,
107
+ * and any non-`/public-runtime` subpath of those — i.e. it
108
+ * prevents the saas-app/backend stack from creeping into the
109
+ * marketing engine — but it no longer guarantees by itself that
110
+ * the React island bundle stays under a hard size budget. That
111
+ * guarantee now lives upstream of this file: developer discipline
112
+ * to keep heavy SSR-only deps out of files reachable from React
113
+ * component code. A stricter entry-specific browser-size check may be added
114
+ * later; this contract already prevents backend and framework bootstrap
115
+ * imports from entering the website closure.
116
+ *
117
+ * Related public-runtime boundary tests prove the subpath itself is free of
118
+ * decorator-framework bootstrap. This test proves the website consumes that
119
+ * safe seam correctly.
120
+ */
121
+ /** @wildo_source:part:end engine.client-bundle.discipline */
122
+
123
+ import { readFileSync, existsSync } from 'node:fs';
124
+ import { dirname, resolve } from 'node:path';
125
+ import { describe, expect, it } from 'vitest';
126
+
127
+ const SRC_ROOT = resolve(__dirname, '..');
128
+
129
+ const ENTRY_FILES: ReadonlyArray<string> = [
130
+ resolve(SRC_ROOT, 'index.ts'),
131
+ resolve(SRC_ROOT, 'companion-exports.ts'),
132
+ resolve(SRC_ROOT, 'astro.ts'),
133
+ /**
134
+ * The `bridge-runtime.tsx` factory is a
135
+ * client-bundle module (it ships into Astro's React island bundle).
136
+ * It MUST stay clear of `node:fs` / `node:path` even though those
137
+ * specifiers are allowed for `astro.ts` (the SSR/frontmatter sibling).
138
+ * Walking `astro-island.ts` as its own entry forces the closure
139
+ * across this module too — and the targeted negative test below
140
+ * already asserts no SSR-only specifier is reachable from any
141
+ * non-`astro.ts` entry.
142
+ */
143
+ resolve(SRC_ROOT, 'astro-island.ts'),
144
+ /**
145
+ * Node-only `wildo.website.config.ts` loader entry.
146
+ * Pulls `loadConfig` + `AppFrontendType` from
147
+ * `@wildo-ai/platform-config-lib` (which transitively imports
148
+ * `node:fs` / `node:path` / `node:crypto` / jiti). Walking it here
149
+ * lets the SSR-tier negative test below assert that
150
+ * `@wildo-ai/platform-config-lib` does NOT leak into the
151
+ * React-island closures rooted at `index.ts` / `companion-exports.ts`
152
+ * / `astro-island.ts`.
153
+ */
154
+ resolve(SRC_ROOT, 'config-loader.ts'),
155
+ /**
156
+ * Phase 7 (saas-website-mdx-blog-adapter.md) — the MDX/blog adapter
157
+ * subpath. Ships into the React-island bundle (consumer-side
158
+ * `.astro` post pages import `<BlogPost>` and render it with a
159
+ * `client:load` directive). Walked here so the global allow-list
160
+ * test catches any `node:*` regression inside `BlogPost.tsx` /
161
+ * `website-mdx-provider.tsx` / `blog-post-collection.config.ts`.
162
+ * Also walked as part of `REACT_ISLAND_ENTRIES` below so the
163
+ * SSR-tier negative test asserts `node:fs` / `node:path` / the
164
+ * `@wildo-ai/platform-config-lib` loader do NOT leak into the MDX
165
+ * adapter closure.
166
+ */
167
+ resolve(SRC_ROOT, 'mdx.ts'),
168
+ ];
169
+
170
+ /**
171
+ * Specifiers that MUST NOT appear anywhere in the saas-website source
172
+ * closure. Any of these landing here would either drag in the saas-app
173
+ * runtime stack (inversify DI graph, the saas-frontend/backend libs,
174
+ * `@wildo-ai/external-connectors-models`) or pull a Node-only server
175
+ * library that the marketing engine has no use case for.
176
+ *
177
+ * Removed from the original FORBIDDEN set on 2026-04-19 (D12 same-day
178
+ * second revision) — see this file's preamble:
179
+ * - `axios` (server-side middleware HTTP client),
180
+ * - `jsonwebtoken` (middleware-side JWT verify),
181
+ * - `socket.io-client` (real-time push),
182
+ * - `reflect-metadata` (decorator metadata runtime).
183
+ * They moved to `ALLOWED_BARE_SPECIFIERS` below.
184
+ *
185
+ * The four root barrels (`saas-models`, `presets-components-models`,
186
+ * `saas-frontend-lib`, `saas-backend-lib`) stay forbidden — the
187
+ * `/public-runtime` subpaths of the first two are the only allowed
188
+ * doors (see `FORBIDDEN_PATTERNS` + `ALLOWED_BARE_SPECIFIERS`).
189
+ */
190
+ const FORBIDDEN_SPECIFIERS: ReadonlySet<string> = new Set([
191
+ '@wildo-ai/saas-models',
192
+ '@wildo-ai/presets-components-models',
193
+ '@wildo-ai/saas-frontend-lib',
194
+ '@wildo-ai/saas-backend-lib',
195
+ '@wildo-ai/external-connectors-models',
196
+ 'inversify',
197
+ 'mongoose',
198
+ 'express',
199
+ 'pino',
200
+ 'helmet',
201
+ 'argon2',
202
+ 'bcrypt',
203
+ ]);
204
+
205
+ /**
206
+ * Subpath patterns equivalent to importing into a forbidden tree.
207
+ * Block any `@wildo-ai/saas-models/<x>` other than `/public-runtime`,
208
+ * any `@wildo-ai/presets-components-models/<x>` other than
209
+ * `/public-runtime`, and any deep import into the saas-frontend-lib /
210
+ * saas-backend-lib subpath surface.
211
+ */
212
+ const FORBIDDEN_PATTERNS: ReadonlyArray<RegExp> = [
213
+ /^@wildo-ai\/saas-models\/(?!public-runtime$).+/,
214
+ /^@wildo-ai\/presets-components-models\/(?!public-runtime$).+/,
215
+ /^@wildo-ai\/saas-frontend-lib\//,
216
+ /^@wildo-ai\/saas-backend-lib\//,
217
+ /^~wildo-frontend-lib\//,
218
+ /^~wildo-backend-lib\//,
219
+ ];
220
+
221
+ /**
222
+ * @wildo_source:part:start engine.client-bundle.allow-list facet:layer:engine facet:family:bundle-boundary
223
+ *
224
+ * Bare-specifier allow-list. Adding a new entry must be a deliberate decision
225
+ * and meet the same browser-boundary acceptance criteria:
226
+ * (a) browser-safe (no Node-only API surface)
227
+ * (b) does NOT transitively pull `reflect-metadata`, the saas-models
228
+ * root barrel, the presets-components-models root barrel, or any
229
+ * backend lib.
230
+ *
231
+ * Add a dependency only when a real application-facing import needs it, with
232
+ * this rationale updated alongside the import.
233
+ */
234
+ const ALLOWED_BARE_SPECIFIERS: ReadonlySet<string> = new Set([
235
+ 'react',
236
+ 'zod',
237
+ '@wildo-ai/saas-models/public-runtime',
238
+ /**
239
+ * Narrow public-runtime seam exposing
240
+ * `AvailableLanguage` / `TextDirection` (and any future locale-tier
241
+ * atom) without booting the heavy `@wildo-ai/presets-components-models`
242
+ * root barrel. Consumers should prefer the
243
+ * `@wildo-ai/saas-models/public-runtime` re-export path so they see
244
+ * one door, but importing the presets `/public-runtime` subpath
245
+ * directly is allowed for symbols that aren't (yet) re-exported by
246
+ * saas-models. The root barrel of `@wildo-ai/presets-components-models`
247
+ * stays in `FORBIDDEN_SPECIFIERS`.
248
+ */
249
+ '@wildo-ai/presets-components-models/public-runtime',
250
+ /**
251
+ * Decorator package itself — importing it does NOT mutate Zod
252
+ * prototype (only an explicit `initZodDecorators()` call does), so
253
+ * it is bundle-safe to allow. Use cases include build-time
254
+ * decorator-metadata reads on schemas imported from
255
+ * `@wildo-ai/saas-models/public-runtime`. The dangerous side-effect
256
+ * (the actual prototype mutation done by saas-models's root barrel)
257
+ * is still blocked because the saas-models ROOT barrel sits in
258
+ * `FORBIDDEN_SPECIFIERS`.
259
+ */
260
+ '@wildo-ai/zod-decorators',
261
+ /**
262
+ * SSR / Astro-middleware tier. These four specifiers support server-side code paths
263
+ * the marketing engine genuinely needs:
264
+ * - `axios` — outbound HTTP from Astro middleware (proxy to
265
+ * the SaaS backend, contact-form forwarding, webhook fan-out).
266
+ * - `jsonwebtoken` — verify cross-frontend handoff JWTs in
267
+ * middleware before letting a request reach a logged-in surface.
268
+ * - `socket.io-client` — real-time push (e.g. live preview / status
269
+ * events from the SaaS app to the marketing site).
270
+ * - `reflect-metadata` — decorator metadata runtime; required
271
+ * by any future build-time consumer of `@wildo-ai/zod-decorators`
272
+ * metadata (the import IS side-effecting — it mutates
273
+ * `globalThis.Reflect`; that side effect is acceptable in an
274
+ * SSR context but would still be objectionable in a React
275
+ * island bundle).
276
+ * Allowing them here is a deliberate trade-off: this test no
277
+ * longer guarantees a hard browser-bundle size budget on its own.
278
+ * Application import discipline keeps the React-island side lean.
279
+ */
280
+ 'axios',
281
+ 'jsonwebtoken',
282
+ 'socket.io-client',
283
+ 'reflect-metadata',
284
+ /**
285
+ * SSR / Astro-build-time tier. The
286
+ * `astro.ts` sub-entry exposes the label-pack loader, which
287
+ * reads `<locale>.json` files from disk inside Astro's frontmatter
288
+ * (synchronous I/O is intentional — see `label-pack-loader.ts`
289
+ * preamble). Allowing the three `node:*` builtins below keeps the
290
+ * loader inside the walked closure (so it gets bundle-rule
291
+ * coverage) without leaking Node-only API into the React island
292
+ * side: `bridge-runtime.tsx` does not import any of them, and the
293
+ * test would catch a regression.
294
+ *
295
+ * `node:fs` — synchronous file reads in the label-pack loader.
296
+ * `node:path` — `resolve()` for the per-locale path composition.
297
+ *
298
+ * Other `node:*` builtins are not allowed; if a future MDX
299
+ * loader needs `node:url` or similar, add it here with the same
300
+ * rationale comment + an actual import that lands at the same time.
301
+ */
302
+ 'node:fs',
303
+ 'node:path',
304
+ /**
305
+ * `@wildo-ai/platform-config-lib` is the home of
306
+ * `loadConfig<T>(...)` (jiti-backed) and `AppFrontendType`. The
307
+ * Node-only `config-loader.ts` entry uses both to discover and
308
+ * load the per-service `wildo.website.config.ts`. Adding the
309
+ * specifier here covers the bare-import; pinning it as
310
+ * SSR-only below proves it cannot reach the React-island closure.
311
+ *
312
+ * The transitive graph (`jiti`, `node:crypto`, the saas-models
313
+ * decorator framework via `WildoSaasConfig` type imports) is
314
+ * irrelevant to bundle weight on the React-island side because
315
+ * the dedicated subpath separation (`@wildo-ai/saas-website/config-loader`
316
+ * vs `@wildo-ai/saas-website`) keeps the consumer-side imports
317
+ * tree-shake-trivial: a marketing page bundle never reaches into
318
+ * `./config-loader`.
319
+ */
320
+ '@wildo-ai/platform-config-lib',
321
+ /**
322
+ * `@mdx-js/react` ships the `MDXProvider` React component the
323
+ * `<WebsiteMdxProvider>` wraps. It is browser-safe (the package's
324
+ * own `package.json` declares `"browser": …` entries; no `node:*`
325
+ * surface), tree-shake-friendly (single named export consumed),
326
+ * and a declared peer dependency of the website package.
327
+ *
328
+ * Allowed UNCONDITIONALLY (not pinned as SSR-only) because the
329
+ * provider lives in the React-island bundle by design — it scopes
330
+ * the consumer-supplied `mdxComponents` map for the duration of a
331
+ * blog-post render, which only happens client-side once Astro has
332
+ * hydrated the per-post island.
333
+ */
334
+ '@mdx-js/react',
335
+ ]);
336
+ /** @wildo_source:part:end engine.client-bundle.allow-list */
337
+
338
+ interface ImportFinding {
339
+ readonly fromFile: string;
340
+ readonly specifier: string;
341
+ }
342
+
343
+ function stripComments(source: string): string {
344
+ return source
345
+ .replace(/\/\*[\s\S]*?\*\//g, '')
346
+ .replace(/(^|[^:])\/\/[^\n]*/g, '$1');
347
+ }
348
+
349
+ function collectImports(filePath: string): ReadonlyArray<ImportFinding> {
350
+ const source = stripComments(readFileSync(filePath, 'utf-8'));
351
+ const findings: ImportFinding[] = [];
352
+ const importRegex = /(?:from|import)\s*['"]([^'"]+)['"]/g;
353
+ let match: RegExpExecArray | null;
354
+ while ((match = importRegex.exec(source)) !== null) {
355
+ const specifier = match[1];
356
+ if (specifier !== undefined) findings.push({ fromFile: filePath, specifier });
357
+ }
358
+ return findings;
359
+ }
360
+
361
+ function isRelative(specifier: string): boolean {
362
+ return specifier.startsWith('./') || specifier.startsWith('../');
363
+ }
364
+
365
+ function resolveRelativeImport(fromFile: string, specifier: string): string {
366
+ const baseDir = dirname(fromFile);
367
+ const candidates = [
368
+ resolve(baseDir, `${specifier}.ts`),
369
+ resolve(baseDir, `${specifier}.tsx`),
370
+ resolve(baseDir, specifier, 'index.ts'),
371
+ resolve(baseDir, specifier, 'index.tsx'),
372
+ resolve(baseDir, specifier),
373
+ ];
374
+ for (const candidate of candidates) {
375
+ if (existsSync(candidate)) return candidate;
376
+ }
377
+ throw new Error(
378
+ `[saas-website bundle-isolation] Could not resolve "${specifier}" from "${fromFile}". ` +
379
+ `Tried: ${candidates.join(', ')}`,
380
+ );
381
+ }
382
+
383
+ function walkClosure(entries: ReadonlyArray<string>): {
384
+ readonly visited: ReadonlySet<string>;
385
+ readonly findings: ReadonlyArray<ImportFinding>;
386
+ } {
387
+ const visited = new Set<string>();
388
+ const findings: ImportFinding[] = [];
389
+
390
+ function visit(filePath: string): void {
391
+ if (visited.has(filePath)) return;
392
+ visited.add(filePath);
393
+
394
+ const fileFindings = collectImports(filePath);
395
+ for (const finding of fileFindings) {
396
+ findings.push(finding);
397
+ if (isRelative(finding.specifier)) {
398
+ const resolved = resolveRelativeImport(finding.fromFile, finding.specifier);
399
+ visit(resolved);
400
+ }
401
+ }
402
+ }
403
+
404
+ for (const entry of entries) {
405
+ visit(entry);
406
+ }
407
+ return { visited, findings };
408
+ }
409
+
410
+ /**
411
+ * Per-entry closures — used by the SSR-tier negative test below to
412
+ * assert `node:fs` / `node:path` reach `astro.ts` ONLY (never the
413
+ * React-island closures rooted at `index.ts` or `companion-exports.ts`).
414
+ *
415
+ * The global allow-list (`ALLOWED_BARE_SPECIFIERS`) admits these two
416
+ * `node:*` builtins for the WHOLE walked closure — necessary because
417
+ * the same allow-list governs every entry. That loosening means a
418
+ * future refactor that imports the label-pack loader from `index.ts`
419
+ * would silently bloat the React-island bundle with Node-only code
420
+ * AND still pass the global allow-list test. The negative test here
421
+ * closes that door without giving up the convenience of a single
422
+ * allow-list.
423
+ */
424
+ const SSR_ONLY_BARE_SPECIFIERS: ReadonlySet<string> = new Set([
425
+ 'node:fs',
426
+ 'node:path',
427
+ /**
428
+ * Step 7.5 — `@wildo-ai/platform-config-lib` reaches the closure
429
+ * ONLY through the Node-only `config-loader.ts` entry. The negative
430
+ * test below verifies it does NOT leak into the React-island
431
+ * closures rooted at `index.ts` / `companion-exports.ts` /
432
+ * `astro-island.ts`. The matching positive test also asserts the
433
+ * specifier IS reachable from at least one SSR-tier entry — when
434
+ * the day comes that nothing imports it, drop it from both this
435
+ * set AND `ALLOWED_BARE_SPECIFIERS` so the allow-list stays
436
+ * minimal.
437
+ */
438
+ '@wildo-ai/platform-config-lib',
439
+ ]);
440
+ const ASTRO_ENTRY = resolve(SRC_ROOT, 'astro.ts');
441
+ const CONFIG_LOADER_ENTRY = resolve(SRC_ROOT, 'config-loader.ts');
442
+ const REACT_ISLAND_ENTRIES: ReadonlyArray<string> = [
443
+ resolve(SRC_ROOT, 'index.ts'),
444
+ resolve(SRC_ROOT, 'companion-exports.ts'),
445
+ /**
446
+ * Phase 5/6 split — `astro-island.ts` is the dedicated
447
+ * client-bundle subpath that exposes
448
+ * `createWebsitePageBridgeIsland`. Treating it as a "react-island
449
+ * entry" here makes the negative test below assert that
450
+ * `node:fs` / `node:path` do NOT leak through this entry — the
451
+ * whole point of carving the subpath out of `astro.ts`.
452
+ */
453
+ resolve(SRC_ROOT, 'astro-island.ts'),
454
+ /**
455
+ * Phase 7 — `mdx.ts` is a React-island sub-entry (consumer-side
456
+ * blog post pages mount `<BlogPost>` with `client:load`). Walking
457
+ * it here forces the SSR-tier negative test to assert that
458
+ * `node:fs` / `node:path` / `@wildo-ai/platform-config-lib` do
459
+ * NOT leak through the MDX adapter — the whole point of keeping
460
+ * the MDX surface separate from the Astro frontmatter helpers.
461
+ */
462
+ resolve(SRC_ROOT, 'mdx.ts'),
463
+ ];
464
+
465
+ describe('saas-website — bundle-isolation boundary (D12)', () => {
466
+ const { visited, findings } = walkClosure(ENTRY_FILES);
467
+
468
+ it('every declared entry file exists', () => {
469
+ for (const entry of ENTRY_FILES) {
470
+ expect(existsSync(entry), `Missing entry: ${entry}`).toBe(true);
471
+ }
472
+ });
473
+
474
+ it('the closure visits at least every entry file', () => {
475
+ for (const entry of ENTRY_FILES) {
476
+ expect(visited.has(entry), `Closure did not visit entry: ${entry}`).toBe(true);
477
+ }
478
+ });
479
+
480
+ it.each([...FORBIDDEN_SPECIFIERS])(
481
+ 'no file in the saas-website closure imports the forbidden specifier "%s"',
482
+ (forbidden) => {
483
+ const offenders = findings.filter((f) => f.specifier === forbidden);
484
+ expect(offenders, JSON.stringify(offenders, null, 2)).toEqual([]);
485
+ },
486
+ );
487
+
488
+ it('no file in the saas-website closure imports a forbidden subpath pattern', () => {
489
+ const offenders = findings.filter((f) =>
490
+ FORBIDDEN_PATTERNS.some((pattern) => pattern.test(f.specifier)),
491
+ );
492
+ expect(offenders, JSON.stringify(offenders, null, 2)).toEqual([]);
493
+ });
494
+
495
+ it('every bare-specifier import in the saas-website closure is on the allow-list', () => {
496
+ const bare = findings.filter(
497
+ (f) => !isRelative(f.specifier) && !FORBIDDEN_SPECIFIERS.has(f.specifier),
498
+ );
499
+ const unexpected = bare.filter((f) => !ALLOWED_BARE_SPECIFIERS.has(f.specifier));
500
+ expect(
501
+ unexpected,
502
+ `Unexpected bare specifier(s) in the saas-website source closure. Either:
503
+ (a) restructure to keep the import inside saas-website (mirror the symbol
504
+ locally if it is a small atom — engine packages are intentionally
505
+ self-contained on the client-bundle side), or
506
+ (b) carve the symbol out via the K-2 protocol in
507
+ saas-models-public-runtime.md and import it from
508
+ @wildo-ai/saas-models/public-runtime, or
509
+ (c) add the specifier to ALLOWED_BARE_SPECIFIERS in this test — but only
510
+ after verifying it is browser-safe AND does not transitively pull
511
+ reflect-metadata, the saas-models root barrel, the
512
+ presets-components-models root barrel, or any backend lib.
513
+ Offenders: ${JSON.stringify(unexpected, null, 2)}`,
514
+ ).toEqual([]);
515
+ });
516
+
517
+ it.each([...SSR_ONLY_BARE_SPECIFIERS])(
518
+ 'SSR-tier specifier "%s" reaches the closure ONLY through astro.ts (not index.ts / companion-exports.ts)',
519
+ (ssrOnly) => {
520
+ const reactIslandClosure = walkClosure(REACT_ISLAND_ENTRIES);
521
+ const leakedFindings = reactIslandClosure.findings.filter(
522
+ (f) => f.specifier === ssrOnly,
523
+ );
524
+ expect(
525
+ leakedFindings,
526
+ `[saas-website bundle-isolation] SSR-tier specifier "${ssrOnly}" leaked into ` +
527
+ `the React-island closure (reachable from index.ts or companion-exports.ts). ` +
528
+ `This means a Node-only API surface is now in the marketing-site browser ` +
529
+ `bundle. Move the offending import behind the astro.ts subpath OR mirror ` +
530
+ `the symbol locally without the Node dep. Offenders: ${JSON.stringify(leakedFindings, null, 2)}`,
531
+ ).toEqual([]);
532
+
533
+ // SSR-tier specifiers may legitimately reach the closure
534
+ // through EITHER the astro.ts SSR/frontmatter entry OR the
535
+ // Node-only config-loader.ts entry (both are server-side by
536
+ // construction; neither ships into the React-island bundle).
537
+ // The positive check here just asserts the specifier is
538
+ // actually used somewhere in the SSR-tier closure — when no
539
+ // SSR-tier consumer remains, drop the specifier from
540
+ // `ALLOWED_BARE_SPECIFIERS` AND `SSR_ONLY_BARE_SPECIFIERS` so
541
+ // the allow-list stays minimal.
542
+ const ssrTierClosure = walkClosure([ASTRO_ENTRY, CONFIG_LOADER_ENTRY]);
543
+ const reachedFromSsrTier = ssrTierClosure.findings.some(
544
+ (f) => f.specifier === ssrOnly,
545
+ );
546
+ expect(
547
+ reachedFromSsrTier,
548
+ `[saas-website bundle-isolation] SSR-tier specifier "${ssrOnly}" is on the ` +
549
+ `allow-list but NOT reachable from any SSR-tier entry (astro.ts, ` +
550
+ `config-loader.ts). If the import is gone, drop it from ` +
551
+ `ALLOWED_BARE_SPECIFIERS (and from SSR_ONLY_BARE_SPECIFIERS) so the ` +
552
+ `allow-list stays minimal.`,
553
+ ).toBe(true);
554
+ },
555
+ );
556
+
557
+ /**
558
+ * `@mdx-js/react` is a declared OPTIONAL peer — only sites that opt into
559
+ * the blog adapter install it. Vite resolves every `export *` graph
560
+ * BEFORE tree-shaking, so the moment any non-`mdx.ts` entry can reach
561
+ * the specifier, every landing-page-only consumer's build fails on the
562
+ * unresolved optional peer ("MDXProvider is not exported by
563
+ * __vite-optional-peer-dep:…"). This shipped once: `astro-island.ts`
564
+ * re-exported `./astro/blog-post-bridge` (which imports `../mdx/BlogPost`
565
+ * → `@mdx-js/react`) for one-subpath consumer convenience, and the first
566
+ * blog-less consumer could not build at all. The bridge now exports from
567
+ * `mdx.ts` — the entry whose consumers install the peer by definition.
568
+ */
569
+ it('the OPTIONAL blog peer "@mdx-js/react" reaches the closure ONLY through mdx.ts', () => {
570
+ const mdxEntry = resolve(SRC_ROOT, 'mdx.ts');
571
+ const nonBlogClosure = walkClosure(ENTRY_FILES.filter((entry) => entry !== mdxEntry));
572
+ const leaked = nonBlogClosure.findings.filter((f) => f.specifier === '@mdx-js/react');
573
+ expect(
574
+ leaked,
575
+ `[saas-website bundle-isolation] "@mdx-js/react" (an OPTIONAL peer) is reachable ` +
576
+ `from a non-mdx entry — every consumer of that entry now has to install the ` +
577
+ `peer or its build fails on the unresolved specifier. Move the offending ` +
578
+ `export behind the mdx.ts entry. Offenders: ${JSON.stringify(leaked, null, 2)}`,
579
+ ).toEqual([]);
580
+
581
+ // Positive half: the specifier must still be reachable from mdx.ts —
582
+ // a walker that reaches it from nowhere would pass the negative half
583
+ // while proving nothing (negative results require a positive probe).
584
+ const blogClosure = walkClosure([mdxEntry]);
585
+ expect(
586
+ blogClosure.findings.some((f) => f.specifier === '@mdx-js/react'),
587
+ '[saas-website bundle-isolation] "@mdx-js/react" is no longer reachable from ' +
588
+ 'mdx.ts either — if the MDX adapter dropped the dependency, remove it from ' +
589
+ 'ALLOWED_BARE_SPECIFIERS and delete this pin.',
590
+ ).toBe(true);
591
+ });
592
+
593
+ it('the public-runtime subpath is the ONLY door into saas-models', () => {
594
+ const saasModelsImports = findings.filter((f) =>
595
+ f.specifier.startsWith('@wildo-ai/saas-models'),
596
+ );
597
+ const wrongDoor = saasModelsImports.filter(
598
+ (f) => f.specifier !== '@wildo-ai/saas-models/public-runtime',
599
+ );
600
+ expect(
601
+ wrongDoor,
602
+ `Found ${wrongDoor.length} saas-models import(s) that bypass /public-runtime. ` +
603
+ `Repoint to '@wildo-ai/saas-models/public-runtime'. ` +
604
+ `Offenders: ${JSON.stringify(wrongDoor, null, 2)}`,
605
+ ).toEqual([]);
606
+ });
607
+ });