@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.
- package/LICENSE +34 -0
- package/dist/esm/.builder.pid +9 -0
- package/dist/esm/astro/blog-post-bridge.d.ts +324 -0
- package/dist/esm/astro/blog-post-bridge.d.ts.map +1 -0
- package/dist/esm/astro/blog-post-bridge.js +57 -0
- package/dist/esm/astro/blog-post-bridge.js.map +1 -0
- package/dist/esm/astro/blog-post-head.renderer.d.ts +129 -0
- package/dist/esm/astro/blog-post-head.renderer.d.ts.map +1 -0
- package/dist/esm/astro/blog-post-head.renderer.js +139 -0
- package/dist/esm/astro/blog-post-head.renderer.js.map +1 -0
- package/dist/esm/astro/bridge-runtime.d.ts +224 -0
- package/dist/esm/astro/bridge-runtime.d.ts.map +1 -0
- package/dist/esm/astro/bridge-runtime.js +30 -0
- package/dist/esm/astro/bridge-runtime.js.map +1 -0
- package/dist/esm/astro/collect-expected-label-keys.d.ts +65 -0
- package/dist/esm/astro/collect-expected-label-keys.d.ts.map +1 -0
- package/dist/esm/astro/collect-expected-label-keys.js +101 -0
- package/dist/esm/astro/collect-expected-label-keys.js.map +1 -0
- package/dist/esm/astro/headers-renderer.d.ts +32 -0
- package/dist/esm/astro/headers-renderer.d.ts.map +1 -0
- package/dist/esm/astro/headers-renderer.js +64 -0
- package/dist/esm/astro/headers-renderer.js.map +1 -0
- package/dist/esm/astro/i18n-routing.helper.d.ts +91 -0
- package/dist/esm/astro/i18n-routing.helper.d.ts.map +1 -0
- package/dist/esm/astro/i18n-routing.helper.js +23 -0
- package/dist/esm/astro/i18n-routing.helper.js.map +1 -0
- package/dist/esm/astro/internal/html-escape.d.ts +31 -0
- package/dist/esm/astro/internal/html-escape.d.ts.map +1 -0
- package/dist/esm/astro/internal/html-escape.js +40 -0
- package/dist/esm/astro/internal/html-escape.js.map +1 -0
- package/dist/esm/astro/label-pack-loader.d.ts +134 -0
- package/dist/esm/astro/label-pack-loader.d.ts.map +1 -0
- package/dist/esm/astro/label-pack-loader.js +298 -0
- package/dist/esm/astro/label-pack-loader.js.map +1 -0
- package/dist/esm/astro/llms-txt-server.d.ts +28 -0
- package/dist/esm/astro/llms-txt-server.d.ts.map +1 -0
- package/dist/esm/astro/llms-txt-server.js +33 -0
- package/dist/esm/astro/llms-txt-server.js.map +1 -0
- package/dist/esm/astro/pick-labels-for-page.d.ts +63 -0
- package/dist/esm/astro/pick-labels-for-page.d.ts.map +1 -0
- package/dist/esm/astro/pick-labels-for-page.js +73 -0
- package/dist/esm/astro/pick-labels-for-page.js.map +1 -0
- package/dist/esm/astro/robots-renderer.d.ts +85 -0
- package/dist/esm/astro/robots-renderer.d.ts.map +1 -0
- package/dist/esm/astro/robots-renderer.js +157 -0
- package/dist/esm/astro/robots-renderer.js.map +1 -0
- package/dist/esm/astro/sitemap-coverage.d.ts +101 -0
- package/dist/esm/astro/sitemap-coverage.d.ts.map +1 -0
- package/dist/esm/astro/sitemap-coverage.js +67 -0
- package/dist/esm/astro/sitemap-coverage.js.map +1 -0
- package/dist/esm/astro/structured-data.renderer.d.ts +21 -0
- package/dist/esm/astro/structured-data.renderer.d.ts.map +1 -0
- package/dist/esm/astro/structured-data.renderer.js +142 -0
- package/dist/esm/astro/structured-data.renderer.js.map +1 -0
- package/dist/esm/astro/website-page-head.renderer.d.ts +93 -0
- package/dist/esm/astro/website-page-head.renderer.d.ts.map +1 -0
- package/dist/esm/astro/website-page-head.renderer.js +184 -0
- package/dist/esm/astro/website-page-head.renderer.js.map +1 -0
- package/dist/esm/astro/website-page-runtime.helper.d.ts +112 -0
- package/dist/esm/astro/website-page-runtime.helper.d.ts.map +1 -0
- package/dist/esm/astro/website-page-runtime.helper.js +60 -0
- package/dist/esm/astro/website-page-runtime.helper.js.map +1 -0
- package/dist/esm/astro/website-site-context.d.ts +126 -0
- package/dist/esm/astro/website-site-context.d.ts.map +1 -0
- package/dist/esm/astro/website-site-context.js +37 -0
- package/dist/esm/astro/website-site-context.js.map +1 -0
- package/dist/esm/astro-island.d.ts +56 -0
- package/dist/esm/astro-island.d.ts.map +1 -0
- package/dist/esm/astro-island.js +61 -0
- package/dist/esm/astro-island.js.map +1 -0
- package/dist/esm/astro.d.ts +151 -0
- package/dist/esm/astro.d.ts.map +1 -0
- package/dist/esm/astro.js +151 -0
- package/dist/esm/astro.js.map +1 -0
- package/dist/esm/companion-exports.d.ts +38 -0
- package/dist/esm/companion-exports.d.ts.map +1 -0
- package/dist/esm/companion-exports.js +38 -0
- package/dist/esm/companion-exports.js.map +1 -0
- package/dist/esm/components/low-level/WebsiteButton.d.ts +73 -0
- package/dist/esm/components/low-level/WebsiteButton.d.ts.map +1 -0
- package/dist/esm/components/low-level/WebsiteButton.js +68 -0
- package/dist/esm/components/low-level/WebsiteButton.js.map +1 -0
- package/dist/esm/components/low-level/WebsiteHeading.d.ts +78 -0
- package/dist/esm/components/low-level/WebsiteHeading.d.ts.map +1 -0
- package/dist/esm/components/low-level/WebsiteHeading.js +41 -0
- package/dist/esm/components/low-level/WebsiteHeading.js.map +1 -0
- package/dist/esm/components/low-level/WebsiteImage.d.ts +73 -0
- package/dist/esm/components/low-level/WebsiteImage.d.ts.map +1 -0
- package/dist/esm/components/low-level/WebsiteImage.js +21 -0
- package/dist/esm/components/low-level/WebsiteImage.js.map +1 -0
- package/dist/esm/components/low-level/WebsiteInternalButton.d.ts +90 -0
- package/dist/esm/components/low-level/WebsiteInternalButton.d.ts.map +1 -0
- package/dist/esm/components/low-level/WebsiteInternalButton.js +47 -0
- package/dist/esm/components/low-level/WebsiteInternalButton.js.map +1 -0
- package/dist/esm/components/low-level/WebsiteInternalLink.d.ts +99 -0
- package/dist/esm/components/low-level/WebsiteInternalLink.d.ts.map +1 -0
- package/dist/esm/components/low-level/WebsiteInternalLink.js +68 -0
- package/dist/esm/components/low-level/WebsiteInternalLink.js.map +1 -0
- package/dist/esm/components/low-level/WebsiteLink.d.ts +50 -0
- package/dist/esm/components/low-level/WebsiteLink.d.ts.map +1 -0
- package/dist/esm/components/low-level/WebsiteLink.js +22 -0
- package/dist/esm/components/low-level/WebsiteLink.js.map +1 -0
- package/dist/esm/components/low-level/WebsiteText.d.ts +57 -0
- package/dist/esm/components/low-level/WebsiteText.d.ts.map +1 -0
- package/dist/esm/components/low-level/WebsiteText.js +57 -0
- package/dist/esm/components/low-level/WebsiteText.js.map +1 -0
- package/dist/esm/config/define-website-config.d.ts +83 -0
- package/dist/esm/config/define-website-config.d.ts.map +1 -0
- package/dist/esm/config/define-website-config.js +126 -0
- package/dist/esm/config/define-website-config.js.map +1 -0
- package/dist/esm/config/index.d.ts +38 -0
- package/dist/esm/config/index.d.ts.map +1 -0
- package/dist/esm/config/index.js +38 -0
- package/dist/esm/config/index.js.map +1 -0
- package/dist/esm/config/load-website-config.d.ts +136 -0
- package/dist/esm/config/load-website-config.d.ts.map +1 -0
- package/dist/esm/config/load-website-config.js +177 -0
- package/dist/esm/config/load-website-config.js.map +1 -0
- package/dist/esm/config/wildo-website-config.schemas.d.ts +251 -0
- package/dist/esm/config/wildo-website-config.schemas.d.ts.map +1 -0
- package/dist/esm/config/wildo-website-config.schemas.js +302 -0
- package/dist/esm/config/wildo-website-config.schemas.js.map +1 -0
- package/dist/esm/config-loader.d.ts +35 -0
- package/dist/esm/config-loader.d.ts.map +1 -0
- package/dist/esm/config-loader.js +35 -0
- package/dist/esm/config-loader.js.map +1 -0
- package/dist/esm/core/anonymous-session/InboundContactForm.d.ts +38 -0
- package/dist/esm/core/anonymous-session/InboundContactForm.d.ts.map +1 -0
- package/dist/esm/core/anonymous-session/InboundContactForm.js +77 -0
- package/dist/esm/core/anonymous-session/InboundContactForm.js.map +1 -0
- package/dist/esm/core/anonymous-session/inbound-contact-form.schema.d.ts +41 -0
- package/dist/esm/core/anonymous-session/inbound-contact-form.schema.d.ts.map +1 -0
- package/dist/esm/core/anonymous-session/inbound-contact-form.schema.js +58 -0
- package/dist/esm/core/anonymous-session/inbound-contact-form.schema.js.map +1 -0
- package/dist/esm/core/anonymous-session/website-anonymous-session-client.d.ts +81 -0
- package/dist/esm/core/anonymous-session/website-anonymous-session-client.d.ts.map +1 -0
- package/dist/esm/core/anonymous-session/website-anonymous-session-client.js +177 -0
- package/dist/esm/core/anonymous-session/website-anonymous-session-client.js.map +1 -0
- package/dist/esm/core/contexts/WebsitePageContext.d.ts +40 -0
- package/dist/esm/core/contexts/WebsitePageContext.d.ts.map +1 -0
- package/dist/esm/core/contexts/WebsitePageContext.js +8 -0
- package/dist/esm/core/contexts/WebsitePageContext.js.map +1 -0
- package/dist/esm/core/contexts/WebsiteRuntimeContext.d.ts +162 -0
- package/dist/esm/core/contexts/WebsiteRuntimeContext.d.ts.map +1 -0
- package/dist/esm/core/contexts/WebsiteRuntimeContext.js +22 -0
- package/dist/esm/core/contexts/WebsiteRuntimeContext.js.map +1 -0
- package/dist/esm/core/contexts/WebsiteSectionContext.d.ts +47 -0
- package/dist/esm/core/contexts/WebsiteSectionContext.d.ts.map +1 -0
- package/dist/esm/core/contexts/WebsiteSectionContext.js +8 -0
- package/dist/esm/core/contexts/WebsiteSectionContext.js.map +1 -0
- package/dist/esm/core/contexts/useWebsitePage.d.ts +13 -0
- package/dist/esm/core/contexts/useWebsitePage.d.ts.map +1 -0
- package/dist/esm/core/contexts/useWebsitePage.js +22 -0
- package/dist/esm/core/contexts/useWebsitePage.js.map +1 -0
- package/dist/esm/core/contexts/useWebsiteRuntime.d.ts +19 -0
- package/dist/esm/core/contexts/useWebsiteRuntime.d.ts.map +1 -0
- package/dist/esm/core/contexts/useWebsiteRuntime.js +28 -0
- package/dist/esm/core/contexts/useWebsiteRuntime.js.map +1 -0
- package/dist/esm/core/contexts/useWebsiteSection.d.ts +12 -0
- package/dist/esm/core/contexts/useWebsiteSection.d.ts.map +1 -0
- package/dist/esm/core/contexts/useWebsiteSection.js +22 -0
- package/dist/esm/core/contexts/useWebsiteSection.js.map +1 -0
- package/dist/esm/core/external-providers/frontend-provider-registry.website.d.ts +17 -0
- package/dist/esm/core/external-providers/frontend-provider-registry.website.d.ts.map +1 -0
- package/dist/esm/core/external-providers/frontend-provider-registry.website.js +23 -0
- package/dist/esm/core/external-providers/frontend-provider-registry.website.js.map +1 -0
- package/dist/esm/core/factories/define-website-page-manifest.d.ts +70 -0
- package/dist/esm/core/factories/define-website-page-manifest.d.ts.map +1 -0
- package/dist/esm/core/factories/define-website-page-manifest.js +51 -0
- package/dist/esm/core/factories/define-website-page-manifest.js.map +1 -0
- package/dist/esm/core/factories/define-website-section.d.ts +101 -0
- package/dist/esm/core/factories/define-website-section.d.ts.map +1 -0
- package/dist/esm/core/factories/define-website-section.js +71 -0
- package/dist/esm/core/factories/define-website-section.js.map +1 -0
- package/dist/esm/core/hooks/useWebsiteDesignTokens.d.ts +23 -0
- package/dist/esm/core/hooks/useWebsiteDesignTokens.d.ts.map +1 -0
- package/dist/esm/core/hooks/useWebsiteDesignTokens.js +25 -0
- package/dist/esm/core/hooks/useWebsiteDesignTokens.js.map +1 -0
- package/dist/esm/core/hooks/useWebsiteLabel.d.ts +68 -0
- package/dist/esm/core/hooks/useWebsiteLabel.d.ts.map +1 -0
- package/dist/esm/core/hooks/useWebsiteLabel.js +103 -0
- package/dist/esm/core/hooks/useWebsiteLabel.js.map +1 -0
- package/dist/esm/core/layouts/WebsitePageLayout.d.ts +87 -0
- package/dist/esm/core/layouts/WebsitePageLayout.d.ts.map +1 -0
- package/dist/esm/core/layouts/WebsitePageLayout.js +170 -0
- package/dist/esm/core/layouts/WebsitePageLayout.js.map +1 -0
- package/dist/esm/core/layouts/WebsiteSection.d.ts +91 -0
- package/dist/esm/core/layouts/WebsiteSection.d.ts.map +1 -0
- package/dist/esm/core/layouts/WebsiteSection.js +26 -0
- package/dist/esm/core/layouts/WebsiteSection.js.map +1 -0
- package/dist/esm/core/routing/internal-routing.utils.d.ts +65 -0
- package/dist/esm/core/routing/internal-routing.utils.d.ts.map +1 -0
- package/dist/esm/core/routing/internal-routing.utils.js +14 -0
- package/dist/esm/core/routing/internal-routing.utils.js.map +1 -0
- package/dist/esm/index.d.ts +129 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +129 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/mdx/BlogPost.d.ts +193 -0
- package/dist/esm/mdx/BlogPost.d.ts.map +1 -0
- package/dist/esm/mdx/BlogPost.js +161 -0
- package/dist/esm/mdx/BlogPost.js.map +1 -0
- package/dist/esm/mdx/blog-post-collection.config.d.ts +139 -0
- package/dist/esm/mdx/blog-post-collection.config.d.ts.map +1 -0
- package/dist/esm/mdx/blog-post-collection.config.js +27 -0
- package/dist/esm/mdx/blog-post-collection.config.js.map +1 -0
- package/dist/esm/mdx/blog-post-frontmatter-source.d.ts +12 -0
- package/dist/esm/mdx/blog-post-frontmatter-source.d.ts.map +1 -0
- package/dist/esm/mdx/blog-post-frontmatter-source.js +13 -0
- package/dist/esm/mdx/blog-post-frontmatter-source.js.map +1 -0
- package/dist/esm/mdx/index.d.ts +22 -0
- package/dist/esm/mdx/index.d.ts.map +1 -0
- package/dist/esm/mdx/index.js +22 -0
- package/dist/esm/mdx/index.js.map +1 -0
- package/dist/esm/mdx/website-mdx-provider.d.ts +90 -0
- package/dist/esm/mdx/website-mdx-provider.d.ts.map +1 -0
- package/dist/esm/mdx/website-mdx-provider.js +8 -0
- package/dist/esm/mdx/website-mdx-provider.js.map +1 -0
- package/dist/esm/mdx.d.ts +38 -0
- package/dist/esm/mdx.d.ts.map +1 -0
- package/dist/esm/mdx.js +38 -0
- package/dist/esm/mdx.js.map +1 -0
- package/dist/esm/schemas/blog/blog-post-frontmatter.shared.schemas.d.ts +75 -0
- package/dist/esm/schemas/blog/blog-post-frontmatter.shared.schemas.d.ts.map +1 -0
- package/dist/esm/schemas/blog/blog-post-frontmatter.shared.schemas.js +85 -0
- package/dist/esm/schemas/blog/blog-post-frontmatter.shared.schemas.js.map +1 -0
- package/dist/esm/schemas/design-tokens/website-design-tokens.shared.schemas.d.ts +120 -0
- package/dist/esm/schemas/design-tokens/website-design-tokens.shared.schemas.d.ts.map +1 -0
- package/dist/esm/schemas/design-tokens/website-design-tokens.shared.schemas.js +152 -0
- package/dist/esm/schemas/design-tokens/website-design-tokens.shared.schemas.js.map +1 -0
- package/dist/esm/schemas/label-keys/website-label-key.schemas.d.ts +73 -0
- package/dist/esm/schemas/label-keys/website-label-key.schemas.d.ts.map +1 -0
- package/dist/esm/schemas/label-keys/website-label-key.schemas.js +76 -0
- package/dist/esm/schemas/label-keys/website-label-key.schemas.js.map +1 -0
- package/dist/esm/schemas/manifests/website-page-manifest.shared.schemas.d.ts +209 -0
- package/dist/esm/schemas/manifests/website-page-manifest.shared.schemas.d.ts.map +1 -0
- package/dist/esm/schemas/manifests/website-page-manifest.shared.schemas.js +218 -0
- package/dist/esm/schemas/manifests/website-page-manifest.shared.schemas.js.map +1 -0
- package/dist/esm/schemas/manifests/website-root-config.shared.schemas.d.ts +431 -0
- package/dist/esm/schemas/manifests/website-root-config.shared.schemas.d.ts.map +1 -0
- package/dist/esm/schemas/manifests/website-root-config.shared.schemas.js +438 -0
- package/dist/esm/schemas/manifests/website-root-config.shared.schemas.js.map +1 -0
- package/dist/esm/schemas/refs/page-ref.schemas.d.ts +40 -0
- package/dist/esm/schemas/refs/page-ref.schemas.d.ts.map +1 -0
- package/dist/esm/schemas/refs/page-ref.schemas.js +43 -0
- package/dist/esm/schemas/refs/page-ref.schemas.js.map +1 -0
- package/dist/esm/schemas/refs/section-ref.schemas.d.ts +41 -0
- package/dist/esm/schemas/refs/section-ref.schemas.d.ts.map +1 -0
- package/dist/esm/schemas/refs/section-ref.schemas.js +44 -0
- package/dist/esm/schemas/refs/section-ref.schemas.js.map +1 -0
- package/dist/esm/schemas/sections/website-section-category.shared.d.ts +63 -0
- package/dist/esm/schemas/sections/website-section-category.shared.d.ts.map +1 -0
- package/dist/esm/schemas/sections/website-section-category.shared.js +64 -0
- package/dist/esm/schemas/sections/website-section-category.shared.js.map +1 -0
- package/dist/esm/schemas/sections/website-section-definition.shared.schemas.d.ts +114 -0
- package/dist/esm/schemas/sections/website-section-definition.shared.schemas.d.ts.map +1 -0
- package/dist/esm/schemas/sections/website-section-definition.shared.schemas.js +69 -0
- package/dist/esm/schemas/sections/website-section-definition.shared.schemas.js.map +1 -0
- package/dist/esm/schemas/structured-data/structured-data-reconciliation.shared.d.ts +100 -0
- package/dist/esm/schemas/structured-data/structured-data-reconciliation.shared.d.ts.map +1 -0
- package/dist/esm/schemas/structured-data/structured-data-reconciliation.shared.js +355 -0
- package/dist/esm/schemas/structured-data/structured-data-reconciliation.shared.js.map +1 -0
- package/dist/esm/schemas/structured-data/website-structured-data.shared.schemas.d.ts +58 -0
- package/dist/esm/schemas/structured-data/website-structured-data.shared.schemas.d.ts.map +1 -0
- package/dist/esm/schemas/structured-data/website-structured-data.shared.schemas.js +2 -0
- package/dist/esm/schemas/structured-data/website-structured-data.shared.schemas.js.map +1 -0
- package/dist/esm/schemas/validators/label-pack.validator.d.ts +144 -0
- package/dist/esm/schemas/validators/label-pack.validator.d.ts.map +1 -0
- package/dist/esm/schemas/validators/label-pack.validator.js +100 -0
- package/dist/esm/schemas/validators/label-pack.validator.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/package.json +143 -0
- package/src/__tests__/bundle-isolation.test.ts +607 -0
- package/src/astro/__tests__/blog-post-bridge.test.tsx +288 -0
- package/src/astro/__tests__/blog-post-head.renderer.test.ts +249 -0
- package/src/astro/__tests__/bridge-runtime.test.tsx +198 -0
- package/src/astro/__tests__/collect-expected-label-keys.test.ts +69 -0
- package/src/astro/__tests__/headers-renderer.test.ts +143 -0
- package/src/astro/__tests__/i18n-routing.helper.test.ts +116 -0
- package/src/astro/__tests__/label-pack-loader.test.ts +305 -0
- package/src/astro/__tests__/llms-txt-server.test.ts +127 -0
- package/src/astro/__tests__/pick-labels-for-page.test.ts +60 -0
- package/src/astro/__tests__/robots-renderer.test.ts +228 -0
- package/src/astro/__tests__/sitemap-coverage.test.ts +230 -0
- package/src/astro/__tests__/structured-data.renderer.test.ts +172 -0
- package/src/astro/__tests__/website-page-head.renderer.test.ts +424 -0
- package/src/astro/__tests__/website-page-runtime.helper.test.ts +157 -0
- package/src/astro/__tests__/website-site-context.test.ts +121 -0
- package/src/astro/blog-post-bridge.tsx +404 -0
- package/src/astro/blog-post-head.renderer.ts +293 -0
- package/src/astro/bridge-runtime.tsx +270 -0
- package/src/astro/collect-expected-label-keys.ts +113 -0
- package/src/astro/headers-renderer.ts +82 -0
- package/src/astro/i18n-routing.helper.ts +116 -0
- package/src/astro/internal/html-escape.ts +41 -0
- package/src/astro/label-pack-loader.ts +385 -0
- package/src/astro/llms-txt-server.ts +56 -0
- package/src/astro/pick-labels-for-page.ts +85 -0
- package/src/astro/robots-renderer.ts +171 -0
- package/src/astro/sitemap-coverage.ts +186 -0
- package/src/astro/structured-data.renderer.ts +171 -0
- package/src/astro/website-page-head.renderer.ts +279 -0
- package/src/astro/website-page-runtime.helper.ts +188 -0
- package/src/astro/website-site-context.ts +136 -0
- package/src/astro-island.ts +61 -0
- package/src/astro.ts +151 -0
- package/src/companion-exports.ts +38 -0
- package/src/components/low-level/WebsiteButton.tsx +118 -0
- package/src/components/low-level/WebsiteHeading.tsx +96 -0
- package/src/components/low-level/WebsiteImage.tsx +96 -0
- package/src/components/low-level/WebsiteInternalButton.tsx +172 -0
- package/src/components/low-level/WebsiteInternalLink.tsx +193 -0
- package/src/components/low-level/WebsiteLink.tsx +71 -0
- package/src/components/low-level/WebsiteText.tsx +75 -0
- package/src/components/low-level/__tests__/WebsiteInternalButton.test.tsx +169 -0
- package/src/components/low-level/__tests__/WebsiteInternalLink.test.tsx +202 -0
- package/src/components/low-level/__tests__/primitives.test.tsx +141 -0
- package/src/config/__tests__/define-website-config.test.ts +231 -0
- package/src/config/__tests__/load-website-config.test.ts +215 -0
- package/src/config/define-website-config.ts +134 -0
- package/src/config/index.ts +38 -0
- package/src/config/load-website-config.ts +229 -0
- package/src/config/wildo-website-config.schemas.ts +322 -0
- package/src/config-loader.ts +35 -0
- package/src/core/__tests__/WebsitePageLayout.test.tsx +184 -0
- package/src/core/__tests__/WebsiteSection.test.tsx +85 -0
- package/src/core/__tests__/contexts.test.tsx +133 -0
- package/src/core/__tests__/core-boundary.test.ts +85 -0
- package/src/core/__tests__/define-website-page-manifest.test.tsx +111 -0
- package/src/core/__tests__/define-website-section.test.tsx +89 -0
- package/src/core/__tests__/useWebsiteDesignTokens.test.tsx +41 -0
- package/src/core/__tests__/useWebsiteLabel.test.tsx +117 -0
- package/src/core/anonymous-session/InboundContactForm.tsx +157 -0
- package/src/core/anonymous-session/__tests__/inbound-contact-form.schema.test.ts +44 -0
- package/src/core/anonymous-session/inbound-contact-form.schema.ts +62 -0
- package/src/core/anonymous-session/website-anonymous-session-client.ts +194 -0
- package/src/core/contexts/WebsitePageContext.tsx +52 -0
- package/src/core/contexts/WebsiteRuntimeContext.tsx +175 -0
- package/src/core/contexts/WebsiteSectionContext.tsx +59 -0
- package/src/core/contexts/useWebsitePage.ts +28 -0
- package/src/core/contexts/useWebsiteRuntime.ts +34 -0
- package/src/core/contexts/useWebsiteSection.ts +28 -0
- package/src/core/external-providers/frontend-provider-registry.website.ts +62 -0
- package/src/core/factories/define-website-page-manifest.ts +102 -0
- package/src/core/factories/define-website-section.ts +128 -0
- package/src/core/hooks/useWebsiteDesignTokens.ts +26 -0
- package/src/core/hooks/useWebsiteLabel.ts +121 -0
- package/src/core/layouts/WebsitePageLayout.tsx +311 -0
- package/src/core/layouts/WebsiteSection.tsx +139 -0
- package/src/core/routing/__tests__/internal-routing.utils.test.ts +134 -0
- package/src/core/routing/internal-routing.utils.ts +77 -0
- package/src/index.ts +132 -0
- package/src/mdx/BlogPost.tsx +296 -0
- package/src/mdx/__tests__/BlogPost.test.tsx +218 -0
- package/src/mdx/__tests__/blog-post-collection.config.test.ts +127 -0
- package/src/mdx/__tests__/website-mdx-provider.test.tsx +82 -0
- package/src/mdx/blog-post-collection.config.ts +177 -0
- package/src/mdx/blog-post-frontmatter-source.ts +31 -0
- package/src/mdx/index.ts +39 -0
- package/src/mdx/website-mdx-provider.tsx +95 -0
- package/src/mdx.ts +38 -0
- package/src/schemas/__tests__/blog-frontmatter.test.ts +50 -0
- package/src/schemas/__tests__/companion-exports-react-free.test.ts +51 -0
- package/src/schemas/__tests__/design-tokens.test.ts +163 -0
- package/src/schemas/__tests__/label-key.test.ts +46 -0
- package/src/schemas/__tests__/label-pack-validator.test.ts +146 -0
- package/src/schemas/__tests__/page-manifest.test.ts +66 -0
- package/src/schemas/__tests__/refs.test.ts +37 -0
- package/src/schemas/__tests__/root-config.test.ts +292 -0
- package/src/schemas/__tests__/section-definition.test.ts +47 -0
- package/src/schemas/__tests__/structured-data-reconciliation.test.ts +543 -0
- package/src/schemas/blog/blog-post-frontmatter.shared.schemas.ts +111 -0
- package/src/schemas/design-tokens/website-design-tokens.shared.schemas.ts +166 -0
- package/src/schemas/label-keys/website-label-key.schemas.ts +88 -0
- package/src/schemas/manifests/website-page-manifest.shared.schemas.ts +266 -0
- package/src/schemas/manifests/website-root-config.shared.schemas.ts +504 -0
- package/src/schemas/refs/page-ref.schemas.ts +49 -0
- package/src/schemas/refs/section-ref.schemas.ts +50 -0
- package/src/schemas/sections/website-section-category.shared.ts +65 -0
- package/src/schemas/sections/website-section-definition.shared.schemas.ts +120 -0
- package/src/schemas/structured-data/structured-data-reconciliation.shared.ts +579 -0
- package/src/schemas/structured-data/website-structured-data.shared.schemas.ts +62 -0
- 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
|
+
}
|