@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,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
|
+
});
|