@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 @@
|
|
|
1
|
+
{"version":3,"file":"collect-expected-label-keys.js","sourceRoot":"","sources":["../../../../src/astro/collect-expected-label-keys.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AACH,MAAM,UAAU,oCAAoC,CAClD,QAA6C;IAE7C,MAAM,GAAG,GAAG,IAAI,GAAG,EAAiD,CAAC;IAErE,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,QAAQ,GAAG,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QAC7C,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,UAAU,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAC;YACvD,SAAS;QACX,CAAC;QACD,IAAI,CAAC,sBAAsB,CAAC,QAAQ,EAAE,OAAO,CAAC,iBAAiB,CAAC,EAAE,CAAC;YACjE,MAAM,IAAI,KAAK,CACb,8DAA8D;gBAC5D,IAAI,OAAO,CAAC,UAA+B,oBAAoB;gBAC/D,kCAAkC;gBAClC,wBAAyB,QAA8B,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK;gBACvE,yBAA0B,OAAO,CAAC,iBAAuC,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK;gBACzF,gFAAgF,CACnF,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,sBAAsB,CAC7B,CAA6B,EAC7B,CAA6B;IAE7B,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IACxC,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,CAAsB,CAAC,CAAC;IAC7C,KAAK,MAAM,GAAG,IAAI,CAAsB,EAAE,CAAC;QACzC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,OAAO,KAAK,CAAC;IACnC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AACD,oEAAoE","sourcesContent":["import type { WebsiteSectionDefinition } from '../schemas/sections/website-section-definition.shared.schemas';\nimport type { WebsiteSectionRef } from '../schemas/refs/section-ref.schemas';\nimport type { WebsiteLabelKey } from '../schemas/label-keys/website-label-key.schemas';\nimport type { ExpectedLabelKeysBySectionRef } from '../schemas/validators/label-pack.validator';\n\n/**\n * @wildo_source:part:start saas.website.astro.label-key-collector facet:layer:astro facet:family:website\n *\n * `collectExpectedLabelKeysFromSections(sections)` — pure, no-I/O\n * collector that materializes the\n * `ExpectedLabelKeysBySectionRef` map the label-pack validator\n * (`validateLabelPackAgainstManifest`) consumes.\n *\n * **Why an explicit-list collector and not a JSX walker**\n * (Phase 5 plan originally proposed walking each manifest's\n * `pageComponent` to discover its `defineWebsiteSection` calls —\n * we deliberately rejected that approach):\n *\n * - **No runtime React introspection.** A JSX walker would either\n * (a) require booting React + a virtual DOM at build time just to\n * render every page once and observe its section composition, or\n * (b) require static AST analysis of the `pageComponent` source\n * file to discover `<HeroSection/>`-style imports. Both add a\n * non-trivial dependency surface (jsdom or a TS AST walker) and\n * both fail silently on dynamic composition (a section rendered\n * conditionally by a feature flag is invisible to the static\n * walker but visible to the renderer, and vice versa).\n * - **The consumer already has the list.** Each page-impl module\n * already imports its sections (that's how JSX renders them);\n * `WebsiteRootConfig` already lists every page manifest; the\n * consumer's `website-root.config.ts` is therefore one line away\n * from also exporting a flat `[HeroSection, PricingSection, …]`\n * array. Asking for that array trades a frame of build-time magic\n * for a single hand-authored line and turns \"did the build see\n * this section?\" into a TypeScript-checked import — a far better\n * trade than a JSX walker.\n * - **Section reuse across pages is naturally handled.** Two pages\n * reusing the same `HeroSection` definition (with the same\n * `sectionRef`) collide silently in a JSX walker (which would\n * register the section twice with identical expected keys).\n * Here, the array deduplicates per `sectionRef` (a same-ref\n * duplicate with NON-matching `expectedLabelKeys` is a hard\n * authoring bug — see refinement below).\n *\n * **Refinement — duplicate `sectionRef` with non-matching keys**:\n * if the same `sectionRef` appears twice with different\n * `expectedLabelKeys`, the collector throws. This catches the\n * \"two distinct sections accidentally using the same ref\" bug\n * (typically a copy-paste during section authoring) at build time\n * with an actionable message rather than letting the second\n * `Map.set` silently overwrite the first.\n *\n * **Pure / no I/O**: the collector is invoked at build start by\n * the Astro adapter and at audit time by `wildo website audit`;\n * it does not read files and does not boot React.\n *\n * @param sections Every `WebsiteSectionDefinition` registered on the\n * site. Order does not matter — the result is an\n * unordered map keyed by `sectionRef`.\n * @returns A `Map<WebsiteSectionRef, readonly WebsiteLabelKey[]>`\n * ready to feed into `validateLabelPackAgainstManifest`.\n * @throws When two distinct sections share a `sectionRef` but\n * declare different `expectedLabelKeys` arrays (length or\n * membership) — the collector's hard-error path.\n */\nexport function collectExpectedLabelKeysFromSections(\n sections: readonly WebsiteSectionDefinition[],\n): ExpectedLabelKeysBySectionRef {\n const map = new Map<WebsiteSectionRef, readonly WebsiteLabelKey[]>();\n\n for (const section of sections) {\n const existing = map.get(section.sectionRef);\n if (existing === undefined) {\n map.set(section.sectionRef, section.expectedLabelKeys);\n continue;\n }\n if (!areLabelKeyArraysEqual(existing, section.expectedLabelKeys)) {\n throw new Error(\n `[collectExpectedLabelKeysFromSections] Duplicate sectionRef ` +\n `\"${section.sectionRef as unknown as string}\" registered with ` +\n `non-matching expectedLabelKeys. ` +\n `First registration: [${(existing as readonly string[]).join(', ')}]. ` +\n `Second registration: [${(section.expectedLabelKeys as readonly string[]).join(', ')}]. ` +\n `Either rename one section's sectionRef or align both expectedLabelKeys arrays.`,\n );\n }\n }\n\n return map;\n}\n\n/**\n * Order-insensitive equality for two `readonly WebsiteLabelKey[]`.\n * Used by the collector to validate that a duplicate-ref registration\n * is intentional (same keys) vs. a copy-paste bug (different keys).\n *\n * Quadratic in the array length, but `expectedLabelKeys` is bounded by\n * the number of slots a single section consumes (typically <20), so\n * the cost is negligible compared to surface readability of the\n * comparison.\n */\nfunction areLabelKeyArraysEqual(\n a: readonly WebsiteLabelKey[],\n b: readonly WebsiteLabelKey[],\n): boolean {\n if (a.length !== b.length) return false;\n const aSet = new Set(a as readonly string[]);\n for (const key of b as readonly string[]) {\n if (!aSet.has(key)) return false;\n }\n return true;\n}\n/** @wildo_source:part:end saas.website.astro.label-key-collector */\n"]}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { WebsiteSiteContext } from './website-site-context';
|
|
2
|
+
/**
|
|
3
|
+
* @wildo_source:part:start saas.website.astro.headers-renderer facet:layer:astro facet:family:website
|
|
4
|
+
*
|
|
5
|
+
* Render a static-host-compatible `_headers` body for every page
|
|
6
|
+
* manifest whose `seo.noaiOptOut === true`.
|
|
7
|
+
*
|
|
8
|
+
* The output format matches Cloudflare Pages / Netlify / Vercel's
|
|
9
|
+
* path-scoped `_headers` conventions:
|
|
10
|
+
*
|
|
11
|
+
* ```txt
|
|
12
|
+
* /pricing
|
|
13
|
+
* X-Robots-Tag: noai, noimageai
|
|
14
|
+
*
|
|
15
|
+
* /fr/pricing
|
|
16
|
+
* X-Robots-Tag: noai, noimageai
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export declare function renderWebsitePageNoaiHeaders(siteContext: WebsiteSiteContext): string;
|
|
20
|
+
/** @wildo_source:part:end saas.website.astro.headers-renderer */
|
|
21
|
+
/**
|
|
22
|
+
* Future blog-post companion for the same header channel. Kept small
|
|
23
|
+
* and pure so a consumer's `[slug].astro` route or `_headers`
|
|
24
|
+
* generation script can opt blog posts out of AI training without
|
|
25
|
+
* re-implementing the locale-path logic.
|
|
26
|
+
*/
|
|
27
|
+
export declare function renderBlogPostNoaiHeaders(params: {
|
|
28
|
+
siteContext: WebsiteSiteContext;
|
|
29
|
+
routePath: string;
|
|
30
|
+
noaiOptOut?: boolean;
|
|
31
|
+
}): string;
|
|
32
|
+
//# sourceMappingURL=headers-renderer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"headers-renderer.d.ts","sourceRoot":"","sources":["../../../../src/astro/headers-renderer.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAIjE;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,4BAA4B,CAAC,WAAW,EAAE,kBAAkB,GAAG,MAAM,CAapF;AACD,iEAAiE;AAEjE;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE;IAChD,WAAW,EAAE,kBAAkB,CAAC;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,GAAG,MAAM,CAgBT"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { buildLocalizedRoutePath } from '../core/routing/internal-routing.utils.js';
|
|
2
|
+
const NOAI_ROBOTS_HEADER_VALUE = 'noai, noimageai';
|
|
3
|
+
/**
|
|
4
|
+
* @wildo_source:part:start saas.website.astro.headers-renderer facet:layer:astro facet:family:website
|
|
5
|
+
*
|
|
6
|
+
* Render a static-host-compatible `_headers` body for every page
|
|
7
|
+
* manifest whose `seo.noaiOptOut === true`.
|
|
8
|
+
*
|
|
9
|
+
* The output format matches Cloudflare Pages / Netlify / Vercel's
|
|
10
|
+
* path-scoped `_headers` conventions:
|
|
11
|
+
*
|
|
12
|
+
* ```txt
|
|
13
|
+
* /pricing
|
|
14
|
+
* X-Robots-Tag: noai, noimageai
|
|
15
|
+
*
|
|
16
|
+
* /fr/pricing
|
|
17
|
+
* X-Robots-Tag: noai, noimageai
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export function renderWebsitePageNoaiHeaders(siteContext) {
|
|
21
|
+
const flaggedPages = siteContext.rootConfig.pageManifests.filter((manifest) => manifest.seo?.noaiOptOut === true);
|
|
22
|
+
if (flaggedPages.length === 0) {
|
|
23
|
+
return '';
|
|
24
|
+
}
|
|
25
|
+
const lines = [];
|
|
26
|
+
for (const manifest of flaggedPages) {
|
|
27
|
+
appendManifestHeaders(lines, manifest, siteContext);
|
|
28
|
+
}
|
|
29
|
+
return lines.slice(0, -1).join('\n');
|
|
30
|
+
}
|
|
31
|
+
/** @wildo_source:part:end saas.website.astro.headers-renderer */
|
|
32
|
+
/**
|
|
33
|
+
* Future blog-post companion for the same header channel. Kept small
|
|
34
|
+
* and pure so a consumer's `[slug].astro` route or `_headers`
|
|
35
|
+
* generation script can opt blog posts out of AI training without
|
|
36
|
+
* re-implementing the locale-path logic.
|
|
37
|
+
*/
|
|
38
|
+
export function renderBlogPostNoaiHeaders(params) {
|
|
39
|
+
const { siteContext, routePath, noaiOptOut } = params;
|
|
40
|
+
if (noaiOptOut !== true) {
|
|
41
|
+
return '';
|
|
42
|
+
}
|
|
43
|
+
const lines = [];
|
|
44
|
+
for (const { locale } of siteContext.websiteConfig.locales) {
|
|
45
|
+
const localizedPath = buildLocalizedRoutePath({
|
|
46
|
+
locale,
|
|
47
|
+
defaultLocale: siteContext.websiteConfig.defaultLocale,
|
|
48
|
+
routePath,
|
|
49
|
+
});
|
|
50
|
+
lines.push(localizedPath, ` X-Robots-Tag: ${NOAI_ROBOTS_HEADER_VALUE}`, '');
|
|
51
|
+
}
|
|
52
|
+
return lines.slice(0, -1).join('\n');
|
|
53
|
+
}
|
|
54
|
+
function appendManifestHeaders(lines, manifest, siteContext) {
|
|
55
|
+
for (const { locale } of siteContext.websiteConfig.locales) {
|
|
56
|
+
const localizedPath = buildLocalizedRoutePath({
|
|
57
|
+
locale,
|
|
58
|
+
defaultLocale: siteContext.websiteConfig.defaultLocale,
|
|
59
|
+
routePath: String(manifest.routePath),
|
|
60
|
+
});
|
|
61
|
+
lines.push(localizedPath, ` X-Robots-Tag: ${NOAI_ROBOTS_HEADER_VALUE}`, '');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=headers-renderer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"headers-renderer.js","sourceRoot":"","sources":["../../../../src/astro/headers-renderer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,wCAAwC,CAAC;AAKjF,MAAM,wBAAwB,GAAG,iBAA0B,CAAC;AAE5D;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,4BAA4B,CAAC,WAA+B;IAC1E,MAAM,YAAY,GAAG,WAAW,CAAC,UAAU,CAAC,aAAa,CAAC,MAAM,CAC9D,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,EAAE,UAAU,KAAK,IAAI,CAChD,CAAC;IACF,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9B,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,QAAQ,IAAI,YAAY,EAAE,CAAC;QACpC,qBAAqB,CAAC,KAAK,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC;IACtD,CAAC;IACD,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACvC,CAAC;AACD,iEAAiE;AAEjE;;;;;GAKG;AACH,MAAM,UAAU,yBAAyB,CAAC,MAIzC;IACC,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,UAAU,EAAE,GAAG,MAAM,CAAC;IACtD,IAAI,UAAU,KAAK,IAAI,EAAE,CAAC;QACxB,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,EAAE,MAAM,EAAE,IAAI,WAAW,CAAC,aAAa,CAAC,OAAO,EAAE,CAAC;QAC3D,MAAM,aAAa,GAAG,uBAAuB,CAAC;YAC5C,MAAM;YACN,aAAa,EAAE,WAAW,CAAC,aAAa,CAAC,aAAa;YACtD,SAAS;SACV,CAAC,CAAC;QACH,KAAK,CAAC,IAAI,CAAC,aAAa,EAAE,mBAAmB,wBAAwB,EAAE,EAAE,EAAE,CAAC,CAAC;IAC/E,CAAC;IACD,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACvC,CAAC;AAED,SAAS,qBAAqB,CAC5B,KAAe,EACf,QAA6B,EAC7B,WAA+B;IAE/B,KAAK,MAAM,EAAE,MAAM,EAAE,IAAI,WAAW,CAAC,aAAa,CAAC,OAAO,EAAE,CAAC;QAC3D,MAAM,aAAa,GAAG,uBAAuB,CAAC;YAC5C,MAAM;YACN,aAAa,EAAE,WAAW,CAAC,aAAa,CAAC,aAAa;YACtD,SAAS,EAAE,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC;SACtC,CAAC,CAAC;QACH,KAAK,CAAC,IAAI,CAAC,aAAa,EAAE,mBAAmB,wBAAwB,EAAE,EAAE,EAAE,CAAC,CAAC;IAC/E,CAAC;AACH,CAAC","sourcesContent":["import { buildLocalizedRoutePath } from '../core/routing/internal-routing.utils';\nimport type { WebsitePageManifest } from '../schemas/manifests/website-page-manifest.shared.schemas';\n\nimport type { WebsiteSiteContext } from './website-site-context';\n\nconst NOAI_ROBOTS_HEADER_VALUE = 'noai, noimageai' as const;\n\n/**\n * @wildo_source:part:start saas.website.astro.headers-renderer facet:layer:astro facet:family:website\n *\n * Render a static-host-compatible `_headers` body for every page\n * manifest whose `seo.noaiOptOut === true`.\n *\n * The output format matches Cloudflare Pages / Netlify / Vercel's\n * path-scoped `_headers` conventions:\n *\n * ```txt\n * /pricing\n * X-Robots-Tag: noai, noimageai\n *\n * /fr/pricing\n * X-Robots-Tag: noai, noimageai\n * ```\n */\nexport function renderWebsitePageNoaiHeaders(siteContext: WebsiteSiteContext): string {\n const flaggedPages = siteContext.rootConfig.pageManifests.filter(\n (manifest) => manifest.seo?.noaiOptOut === true,\n );\n if (flaggedPages.length === 0) {\n return '';\n }\n\n const lines: string[] = [];\n for (const manifest of flaggedPages) {\n appendManifestHeaders(lines, manifest, siteContext);\n }\n return lines.slice(0, -1).join('\\n');\n}\n/** @wildo_source:part:end saas.website.astro.headers-renderer */\n\n/**\n * Future blog-post companion for the same header channel. Kept small\n * and pure so a consumer's `[slug].astro` route or `_headers`\n * generation script can opt blog posts out of AI training without\n * re-implementing the locale-path logic.\n */\nexport function renderBlogPostNoaiHeaders(params: {\n siteContext: WebsiteSiteContext;\n routePath: string;\n noaiOptOut?: boolean;\n}): string {\n const { siteContext, routePath, noaiOptOut } = params;\n if (noaiOptOut !== true) {\n return '';\n }\n\n const lines: string[] = [];\n for (const { locale } of siteContext.websiteConfig.locales) {\n const localizedPath = buildLocalizedRoutePath({\n locale,\n defaultLocale: siteContext.websiteConfig.defaultLocale,\n routePath,\n });\n lines.push(localizedPath, ` X-Robots-Tag: ${NOAI_ROBOTS_HEADER_VALUE}`, '');\n }\n return lines.slice(0, -1).join('\\n');\n}\n\nfunction appendManifestHeaders(\n lines: string[],\n manifest: WebsitePageManifest,\n siteContext: WebsiteSiteContext,\n): void {\n for (const { locale } of siteContext.websiteConfig.locales) {\n const localizedPath = buildLocalizedRoutePath({\n locale,\n defaultLocale: siteContext.websiteConfig.defaultLocale,\n routePath: String(manifest.routePath),\n });\n lines.push(localizedPath, ` X-Robots-Tag: ${NOAI_ROBOTS_HEADER_VALUE}`, '');\n }\n}\n"]}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { WebsiteSiteContext } from './website-site-context';
|
|
2
|
+
/**
|
|
3
|
+
* @wildo_source:part:start saas.website.astro.i18n-helper facet:layer:astro facet:family:website
|
|
4
|
+
*
|
|
5
|
+
* `buildAstroI18nConfig(siteContext)` — derives the `i18n` block consumed
|
|
6
|
+
* by `astro/config`'s `defineConfig({ i18n })` from the shared
|
|
7
|
+
* `WebsiteSiteContext`.
|
|
8
|
+
*
|
|
9
|
+
* **Why a helper and not hand-authored in `astro.config.mjs`**: a
|
|
10
|
+
* marketing site has TWO sources of truth for "which locales does this
|
|
11
|
+
* site ship?" — the `WildoMarketingWebsiteConfig.locales` array
|
|
12
|
+
* (consumed by every Astro build helper) AND the Astro routing config
|
|
13
|
+
* (consumed by the file-based router and the dev server). Letting the
|
|
14
|
+
* consumer hand-author both creates a silent-drift surface: a locale
|
|
15
|
+
* added to one but not the other ships a working dev server with a
|
|
16
|
+
* missing label pack (or vice versa). This helper makes
|
|
17
|
+
* `WildoMarketingWebsiteConfig` (the per-service `wildo.website.config.ts`)
|
|
18
|
+
* the single source of truth.
|
|
19
|
+
*
|
|
20
|
+
* **Design decisions** (each carrying its own semantic weight):
|
|
21
|
+
*
|
|
22
|
+
* - **`prefixDefaultLocale = false`**. Astro's default routing strategy
|
|
23
|
+
* is the marketing-site convention: the default locale mounts at the
|
|
24
|
+
* un-prefixed path (`/`, `/pricing`, `/contact`) and other locales
|
|
25
|
+
* live under `/<locale>/…`. Forcing prefixes for the default locale
|
|
26
|
+
* would surface a `/en/` URL alongside `/` — duplicate content from
|
|
27
|
+
* an SEO standpoint and a UX wart. Marketing teams overwhelmingly
|
|
28
|
+
* ship the default at the root.
|
|
29
|
+
*
|
|
30
|
+
* - **`redirectToDefaultLocale = false`**. The default value Astro
|
|
31
|
+
* would otherwise infer (`true`) issues a redirect from `/` to
|
|
32
|
+
* `/<defaultLocale>/`, which directly contradicts the
|
|
33
|
+
* `prefixDefaultLocale = false` convention above. Setting both
|
|
34
|
+
* explicitly makes the contract loud at the call site (and in the
|
|
35
|
+
* compiled config) instead of relying on Astro's defaults staying
|
|
36
|
+
* stable across versions.
|
|
37
|
+
*
|
|
38
|
+
* - **`fallbackType = 'redirect'` is INTENTIONALLY OMITTED**.
|
|
39
|
+
* Marketing sites typically prefer one of two strategies: ship a
|
|
40
|
+
* page in every advertised locale OR hide the un-shipped locale
|
|
41
|
+
* entirely (no entry in `locales`). The `fallback` machinery (which
|
|
42
|
+
* would redirect `/de/pricing` to `/en/pricing` when the German
|
|
43
|
+
* page is missing) creates SEO duplicate-content risk that's hard
|
|
44
|
+
* to undo once Google indexes the redirected URL. Consumers who
|
|
45
|
+
* genuinely need fallback can spread this helper's output and
|
|
46
|
+
* add `fallback: { … }` themselves.
|
|
47
|
+
*
|
|
48
|
+
* **Type rationale**: the return type uses Astro's published `i18n`
|
|
49
|
+
* shape but we declare it as a structural literal type rather than
|
|
50
|
+
* importing from `astro` so this module stays in the saas-website
|
|
51
|
+
* source closure without forcing `astro` into the React-island side of the
|
|
52
|
+
* bundle. The package's bundle-boundary checks enforce that separation.
|
|
53
|
+
* The structural type is straightforward and matches Astro 4+ — see
|
|
54
|
+
* https://docs.astro.build/en/reference/configuration-reference/#i18n.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* // astro.config.mjs
|
|
58
|
+
* import { defineConfig } from 'astro/config';
|
|
59
|
+
* import { buildAstroI18nConfig } from '@wildo-ai/saas-website/astro';
|
|
60
|
+
* import { WEBSITE_SITE_CONTEXT } from './src/website-site-context';
|
|
61
|
+
*
|
|
62
|
+
* export default defineConfig({
|
|
63
|
+
* i18n: buildAstroI18nConfig(WEBSITE_SITE_CONTEXT),
|
|
64
|
+
* });
|
|
65
|
+
*/
|
|
66
|
+
export interface AstroI18nConfig {
|
|
67
|
+
/** ISO locale string Astro mounts at the un-prefixed path. */
|
|
68
|
+
defaultLocale: string;
|
|
69
|
+
/** Every locale the site ships in (default included). */
|
|
70
|
+
locales: readonly string[];
|
|
71
|
+
routing: {
|
|
72
|
+
/**
|
|
73
|
+
* `false` means the default locale is served at the root path
|
|
74
|
+
* (`/`); other locales sit under `/<locale>/`. Typed as a
|
|
75
|
+
* widened `boolean` (not a literal `false`) so consumers who
|
|
76
|
+
* spread the config and override the field downstream do not
|
|
77
|
+
* fight literal-type narrowing — the runtime still emits
|
|
78
|
+
* `false` here, the type system just allows the override.
|
|
79
|
+
*/
|
|
80
|
+
prefixDefaultLocale: boolean;
|
|
81
|
+
/**
|
|
82
|
+
* `false` means a request for `/` is served as the default-locale
|
|
83
|
+
* landing page (no implicit redirect to `/<defaultLocale>/`). Same
|
|
84
|
+
* literal-vs-widened rationale as `prefixDefaultLocale` above.
|
|
85
|
+
*/
|
|
86
|
+
redirectToDefaultLocale: boolean;
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
export declare function buildAstroI18nConfig(siteContext: WebsiteSiteContext): AstroI18nConfig;
|
|
90
|
+
/** @wildo_source:part:end saas.website.astro.i18n-helper */
|
|
91
|
+
//# sourceMappingURL=i18n-routing.helper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n-routing.helper.d.ts","sourceRoot":"","sources":["../../../../src/astro/i18n-routing.helper.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAEjE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+DG;AACH,MAAM,WAAW,eAAe;IAC9B,8DAA8D;IAC9D,aAAa,EAAE,MAAM,CAAC;IACtB,yDAAyD;IACzD,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,OAAO,EAAE;QACP;;;;;;;WAOG;QACH,mBAAmB,EAAE,OAAO,CAAC;QAC7B;;;;WAIG;QACH,uBAAuB,EAAE,OAAO,CAAC;KAClC,CAAC;CACH;AAED,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,kBAAkB,GAAG,eAAe,CAsBrF;AACD,4DAA4D"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function buildAstroI18nConfig(siteContext) {
|
|
2
|
+
const { websiteConfig } = siteContext;
|
|
3
|
+
/**
|
|
4
|
+
* Defensive copy: `WildoMarketingWebsiteConfig.locales` is an array
|
|
5
|
+
* of `{ locale, path }` entries; Astro's i18n config wants raw
|
|
6
|
+
* locale strings in declaration order. We extract `locale` and map
|
|
7
|
+
* through `String()` so the `AvailableLanguage` brand layer does
|
|
8
|
+
* not leak into the Astro public API surface, and we freeze the
|
|
9
|
+
* result so an Astro plugin authored against this shape cannot
|
|
10
|
+
* mutate the source-of-truth array indirectly.
|
|
11
|
+
*/
|
|
12
|
+
const locales = Object.freeze(websiteConfig.locales.map((entry) => String(entry.locale)));
|
|
13
|
+
return {
|
|
14
|
+
defaultLocale: String(websiteConfig.defaultLocale),
|
|
15
|
+
locales,
|
|
16
|
+
routing: {
|
|
17
|
+
prefixDefaultLocale: false,
|
|
18
|
+
redirectToDefaultLocale: false,
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/** @wildo_source:part:end saas.website.astro.i18n-helper */
|
|
23
|
+
//# sourceMappingURL=i18n-routing.helper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n-routing.helper.js","sourceRoot":"","sources":["../../../../src/astro/i18n-routing.helper.ts"],"names":[],"mappings":"AA4FA,MAAM,UAAU,oBAAoB,CAAC,WAA+B;IAClE,MAAM,EAAE,aAAa,EAAE,GAAG,WAAW,CAAC;IACtC;;;;;;;;OAQG;IACH,MAAM,OAAO,GAAsB,MAAM,CAAC,MAAM,CAC9C,aAAa,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAoC,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAC1F,CAAC;IACF,OAAO;QACL,aAAa,EAAE,MAAM,CAAC,aAAa,CAAC,aAAa,CAAC;QAClD,OAAO;QACP,OAAO,EAAE;YACP,mBAAmB,EAAE,KAAK;YAC1B,uBAAuB,EAAE,KAAK;SAC/B;KACF,CAAC;AACJ,CAAC;AACD,4DAA4D","sourcesContent":["import type { AvailableLanguage } from '@wildo-ai/saas-models/public-runtime';\n\nimport type { WebsiteSiteContext } from './website-site-context';\n\n/**\n * @wildo_source:part:start saas.website.astro.i18n-helper facet:layer:astro facet:family:website\n *\n * `buildAstroI18nConfig(siteContext)` — derives the `i18n` block consumed\n * by `astro/config`'s `defineConfig({ i18n })` from the shared\n * `WebsiteSiteContext`.\n *\n * **Why a helper and not hand-authored in `astro.config.mjs`**: a\n * marketing site has TWO sources of truth for \"which locales does this\n * site ship?\" — the `WildoMarketingWebsiteConfig.locales` array\n * (consumed by every Astro build helper) AND the Astro routing config\n * (consumed by the file-based router and the dev server). Letting the\n * consumer hand-author both creates a silent-drift surface: a locale\n * added to one but not the other ships a working dev server with a\n * missing label pack (or vice versa). This helper makes\n * `WildoMarketingWebsiteConfig` (the per-service `wildo.website.config.ts`)\n * the single source of truth.\n *\n * **Design decisions** (each carrying its own semantic weight):\n *\n * - **`prefixDefaultLocale = false`**. Astro's default routing strategy\n * is the marketing-site convention: the default locale mounts at the\n * un-prefixed path (`/`, `/pricing`, `/contact`) and other locales\n * live under `/<locale>/…`. Forcing prefixes for the default locale\n * would surface a `/en/` URL alongside `/` — duplicate content from\n * an SEO standpoint and a UX wart. Marketing teams overwhelmingly\n * ship the default at the root.\n *\n * - **`redirectToDefaultLocale = false`**. The default value Astro\n * would otherwise infer (`true`) issues a redirect from `/` to\n * `/<defaultLocale>/`, which directly contradicts the\n * `prefixDefaultLocale = false` convention above. Setting both\n * explicitly makes the contract loud at the call site (and in the\n * compiled config) instead of relying on Astro's defaults staying\n * stable across versions.\n *\n * - **`fallbackType = 'redirect'` is INTENTIONALLY OMITTED**.\n * Marketing sites typically prefer one of two strategies: ship a\n * page in every advertised locale OR hide the un-shipped locale\n * entirely (no entry in `locales`). The `fallback` machinery (which\n * would redirect `/de/pricing` to `/en/pricing` when the German\n * page is missing) creates SEO duplicate-content risk that's hard\n * to undo once Google indexes the redirected URL. Consumers who\n * genuinely need fallback can spread this helper's output and\n * add `fallback: { … }` themselves.\n *\n * **Type rationale**: the return type uses Astro's published `i18n`\n * shape but we declare it as a structural literal type rather than\n * importing from `astro` so this module stays in the saas-website\n * source closure without forcing `astro` into the React-island side of the\n * bundle. The package's bundle-boundary checks enforce that separation.\n * The structural type is straightforward and matches Astro 4+ — see\n * https://docs.astro.build/en/reference/configuration-reference/#i18n.\n *\n * @example\n * // astro.config.mjs\n * import { defineConfig } from 'astro/config';\n * import { buildAstroI18nConfig } from '@wildo-ai/saas-website/astro';\n * import { WEBSITE_SITE_CONTEXT } from './src/website-site-context';\n *\n * export default defineConfig({\n * i18n: buildAstroI18nConfig(WEBSITE_SITE_CONTEXT),\n * });\n */\nexport interface AstroI18nConfig {\n /** ISO locale string Astro mounts at the un-prefixed path. */\n defaultLocale: string;\n /** Every locale the site ships in (default included). */\n locales: readonly string[];\n routing: {\n /**\n * `false` means the default locale is served at the root path\n * (`/`); other locales sit under `/<locale>/`. Typed as a\n * widened `boolean` (not a literal `false`) so consumers who\n * spread the config and override the field downstream do not\n * fight literal-type narrowing — the runtime still emits\n * `false` here, the type system just allows the override.\n */\n prefixDefaultLocale: boolean;\n /**\n * `false` means a request for `/` is served as the default-locale\n * landing page (no implicit redirect to `/<defaultLocale>/`). Same\n * literal-vs-widened rationale as `prefixDefaultLocale` above.\n */\n redirectToDefaultLocale: boolean;\n };\n}\n\nexport function buildAstroI18nConfig(siteContext: WebsiteSiteContext): AstroI18nConfig {\n const { websiteConfig } = siteContext;\n /**\n * Defensive copy: `WildoMarketingWebsiteConfig.locales` is an array\n * of `{ locale, path }` entries; Astro's i18n config wants raw\n * locale strings in declaration order. We extract `locale` and map\n * through `String()` so the `AvailableLanguage` brand layer does\n * not leak into the Astro public API surface, and we freeze the\n * result so an Astro plugin authored against this shape cannot\n * mutate the source-of-truth array indirectly.\n */\n const locales: readonly string[] = Object.freeze(\n websiteConfig.locales.map((entry: { locale: AvailableLanguage }) => String(entry.locale)),\n );\n return {\n defaultLocale: String(websiteConfig.defaultLocale),\n locales,\n routing: {\n prefixDefaultLocale: false,\n redirectToDefaultLocale: false,\n },\n };\n}\n/** @wildo_source:part:end saas.website.astro.i18n-helper */\n"]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared HTML-escape helpers used by every Astro head-block string
|
|
3
|
+
* builder in the saas-website engine (page-head + blog-post-head, and
|
|
4
|
+
* any future per-route renderer that interpolates consumer-supplied
|
|
5
|
+
* copy into a string-output `<head>` template).
|
|
6
|
+
*
|
|
7
|
+
* # Why a shared module rather than duplicating per-renderer
|
|
8
|
+
*
|
|
9
|
+
* Marketing copy frequently contains characters (quotes, ampersands,
|
|
10
|
+
* the rare `<`) that would break the surrounding tag if interpolated
|
|
11
|
+
* raw. Two renderers escaping with two slightly-different character
|
|
12
|
+
* sets is exactly the kind of silent-drift surface this codebase
|
|
13
|
+
* forbids — one renderer might forget the `'` → `'` substitution
|
|
14
|
+
* and produce attribute breakouts in JSON-LD copy, while the other
|
|
15
|
+
* does it correctly. Centralizing both helpers here gives every
|
|
16
|
+
* renderer ONE escape contract to audit.
|
|
17
|
+
*
|
|
18
|
+
* # Why both `Attribute` and `Text` variants exist (currently identical)
|
|
19
|
+
*
|
|
20
|
+
* Conceptually distinct (an attribute escape needs `"`/`'` covered;
|
|
21
|
+
* a text-content escape technically only needs `<`/`>`/`&`). Today
|
|
22
|
+
* they share a body for convenience — the cost of over-escaping `"`
|
|
23
|
+
* inside text content is negligible (browsers render the entity
|
|
24
|
+
* correctly) and the win is one fewer code path to audit. Splitting
|
|
25
|
+
* the implementations later (when a real divergence shows up — e.g. a
|
|
26
|
+
* JSON-LD emitter that legitimately wants raw `<script type="application/ld+json">`
|
|
27
|
+
* inner-content escaping rules) is a one-file change.
|
|
28
|
+
*/
|
|
29
|
+
export declare function escapeHtmlAttribute(value: string): string;
|
|
30
|
+
export declare function escapeHtmlText(value: string): string;
|
|
31
|
+
//# sourceMappingURL=html-escape.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html-escape.d.ts","sourceRoot":"","sources":["../../../../../src/astro/internal/html-escape.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOzD;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEpD"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared HTML-escape helpers used by every Astro head-block string
|
|
3
|
+
* builder in the saas-website engine (page-head + blog-post-head, and
|
|
4
|
+
* any future per-route renderer that interpolates consumer-supplied
|
|
5
|
+
* copy into a string-output `<head>` template).
|
|
6
|
+
*
|
|
7
|
+
* # Why a shared module rather than duplicating per-renderer
|
|
8
|
+
*
|
|
9
|
+
* Marketing copy frequently contains characters (quotes, ampersands,
|
|
10
|
+
* the rare `<`) that would break the surrounding tag if interpolated
|
|
11
|
+
* raw. Two renderers escaping with two slightly-different character
|
|
12
|
+
* sets is exactly the kind of silent-drift surface this codebase
|
|
13
|
+
* forbids — one renderer might forget the `'` → `'` substitution
|
|
14
|
+
* and produce attribute breakouts in JSON-LD copy, while the other
|
|
15
|
+
* does it correctly. Centralizing both helpers here gives every
|
|
16
|
+
* renderer ONE escape contract to audit.
|
|
17
|
+
*
|
|
18
|
+
* # Why both `Attribute` and `Text` variants exist (currently identical)
|
|
19
|
+
*
|
|
20
|
+
* Conceptually distinct (an attribute escape needs `"`/`'` covered;
|
|
21
|
+
* a text-content escape technically only needs `<`/`>`/`&`). Today
|
|
22
|
+
* they share a body for convenience — the cost of over-escaping `"`
|
|
23
|
+
* inside text content is negligible (browsers render the entity
|
|
24
|
+
* correctly) and the win is one fewer code path to audit. Splitting
|
|
25
|
+
* the implementations later (when a real divergence shows up — e.g. a
|
|
26
|
+
* JSON-LD emitter that legitimately wants raw `<script type="application/ld+json">`
|
|
27
|
+
* inner-content escaping rules) is a one-file change.
|
|
28
|
+
*/
|
|
29
|
+
export function escapeHtmlAttribute(value) {
|
|
30
|
+
return value
|
|
31
|
+
.replace(/&/g, '&')
|
|
32
|
+
.replace(/</g, '<')
|
|
33
|
+
.replace(/>/g, '>')
|
|
34
|
+
.replace(/"/g, '"')
|
|
35
|
+
.replace(/'/g, ''');
|
|
36
|
+
}
|
|
37
|
+
export function escapeHtmlText(value) {
|
|
38
|
+
return escapeHtmlAttribute(value);
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=html-escape.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html-escape.js","sourceRoot":"","sources":["../../../../../src/astro/internal/html-escape.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,MAAM,UAAU,mBAAmB,CAAC,KAAa;IAC/C,OAAO,KAAK;SACT,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AAC5B,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,OAAO,mBAAmB,CAAC,KAAK,CAAC,CAAC;AACpC,CAAC","sourcesContent":["/**\n * Shared HTML-escape helpers used by every Astro head-block string\n * builder in the saas-website engine (page-head + blog-post-head, and\n * any future per-route renderer that interpolates consumer-supplied\n * copy into a string-output `<head>` template).\n *\n * # Why a shared module rather than duplicating per-renderer\n *\n * Marketing copy frequently contains characters (quotes, ampersands,\n * the rare `<`) that would break the surrounding tag if interpolated\n * raw. Two renderers escaping with two slightly-different character\n * sets is exactly the kind of silent-drift surface this codebase\n * forbids — one renderer might forget the `'` → `'` substitution\n * and produce attribute breakouts in JSON-LD copy, while the other\n * does it correctly. Centralizing both helpers here gives every\n * renderer ONE escape contract to audit.\n *\n * # Why both `Attribute` and `Text` variants exist (currently identical)\n *\n * Conceptually distinct (an attribute escape needs `\"`/`'` covered;\n * a text-content escape technically only needs `<`/`>`/`&`). Today\n * they share a body for convenience — the cost of over-escaping `\"`\n * inside text content is negligible (browsers render the entity\n * correctly) and the win is one fewer code path to audit. Splitting\n * the implementations later (when a real divergence shows up — e.g. a\n * JSON-LD emitter that legitimately wants raw `<script type=\"application/ld+json\">`\n * inner-content escaping rules) is a one-file change.\n */\n\nexport function escapeHtmlAttribute(value: string): string {\n return value\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\nexport function escapeHtmlText(value: string): string {\n return escapeHtmlAttribute(value);\n}\n"]}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import type { AvailableLanguage } from '@wildo-ai/saas-models/public-runtime';
|
|
2
|
+
import { type ExpectedLabelKeysBySectionRef, type LabelPack, type LabelPackValidationResult } from '../schemas/validators/label-pack.validator';
|
|
3
|
+
import type { WebsiteSiteContext } from './website-site-context';
|
|
4
|
+
/**
|
|
5
|
+
* @wildo_source:part:start saas.website.astro.label-loader facet:layer:astro facet:family:website
|
|
6
|
+
*
|
|
7
|
+
* `label-pack-loader.ts` — synchronous, fs-backed loader for
|
|
8
|
+
* marketing-site label packs. Consumed by:
|
|
9
|
+
* - the Astro adapter (`WebsitePageBridge.astro` reads the active
|
|
10
|
+
* locale's pack at request time during SSR / pre-render),
|
|
11
|
+
* - the `wildo website audit` CLI (Phase 7) to validate every
|
|
12
|
+
* locale pack against every manifest at CI time,
|
|
13
|
+
* - any build-time hook that wants to fail fast on a missing or
|
|
14
|
+
* malformed pack.
|
|
15
|
+
*
|
|
16
|
+
* **Convention** (matches the slice plan + the validator's contract):
|
|
17
|
+
*
|
|
18
|
+
* - One file per locale: `<packsDir>/<locale>.json`
|
|
19
|
+
* - JSON object: flat key-value map, `Record<string, string>`
|
|
20
|
+
* - Keys are fully-composed `WebsiteLabelKey`s (`website.<page>.<section>.<slot>`)
|
|
21
|
+
* - Values are the localized strings
|
|
22
|
+
*
|
|
23
|
+
* **Why synchronous I/O**:
|
|
24
|
+
* - The Astro frontmatter executes at build / SSR time in a Node
|
|
25
|
+
* process (no event loop pressure to relieve), and the bridge
|
|
26
|
+
* wants the pack resolved before it emits any HTML — async would
|
|
27
|
+
* force the consumer to `await` inside an Astro frontmatter,
|
|
28
|
+
* which is supported but adds a needless yield-point on a single
|
|
29
|
+
* small JSON file.
|
|
30
|
+
* - The validator is pure and synchronous; making the loader async
|
|
31
|
+
* would add a coloring boundary for no benefit.
|
|
32
|
+
*
|
|
33
|
+
* **Failure modes — all surfaced as thrown errors with actionable
|
|
34
|
+
* messages**:
|
|
35
|
+
* - File missing — wrapped in a `[label-pack-loader] Could not read
|
|
36
|
+
* pack at <path>` error so a typo in the dir / locale is obvious.
|
|
37
|
+
* - JSON parse failure — error includes the file path and the
|
|
38
|
+
* parser's own message.
|
|
39
|
+
* - Top-level shape is not an object, or a value is not a string —
|
|
40
|
+
* error names the offending key + observed type.
|
|
41
|
+
*
|
|
42
|
+
* **Why three exports** (loader, multi-loader, validator-runner):
|
|
43
|
+
* - `loadLabelPack(packsDir, locale)` is the atomic primitive
|
|
44
|
+
* (one file → one validated `LabelPack`). Used by the bridge at
|
|
45
|
+
* request time.
|
|
46
|
+
* - `loadAllLabelPacks(packsDir, siteContext)` is the build-time
|
|
47
|
+
* batch — every locale pack at once. Used by the CLI auditor
|
|
48
|
+
* and by the build-start hook.
|
|
49
|
+
* - `validateAllLabelPacks(packsDir, siteContext, expectedKeysBySectionRef)`
|
|
50
|
+
* is the highest-level entry point: load every pack, run the
|
|
51
|
+
* validator on each, and either return the diagnostic results
|
|
52
|
+
* or throw on the first hard error. The returned shape carries
|
|
53
|
+
* per-locale diagnostics so the caller can format a CLI report.
|
|
54
|
+
*
|
|
55
|
+
* **`siteContext` parameter** — the consumer's composed
|
|
56
|
+
* `WebsiteSiteContext` (see `website-site-context.ts`). The loader
|
|
57
|
+
* reads `siteContext.websiteConfig.locales` for "which locales does
|
|
58
|
+
* the site ship?" and `siteContext.rootConfig.{pageManifests,
|
|
59
|
+
* chromeLabelKeys, skipToMainContentLabelKey}` for the manifest
|
|
60
|
+
* registry / chrome-key set the validator runs against.
|
|
61
|
+
*/
|
|
62
|
+
/**
|
|
63
|
+
* Load and validate a single locale's label pack. Synchronous —
|
|
64
|
+
* suitable for Astro frontmatter execution.
|
|
65
|
+
*
|
|
66
|
+
* @param packsDir Absolute or cwd-relative path to the directory
|
|
67
|
+
* containing per-locale `<locale>.json` files.
|
|
68
|
+
* @param locale The locale to load. Must be a member of
|
|
69
|
+
* `WildoMarketingWebsiteConfig.locales`.
|
|
70
|
+
* @returns A flat `LabelPack` (`Record<string, string>`) ready to
|
|
71
|
+
* feed into `WebsiteRuntimeContextValue.labelPack`.
|
|
72
|
+
* @throws When the file is missing, malformed JSON, not an object,
|
|
73
|
+
* or contains a non-string value.
|
|
74
|
+
*/
|
|
75
|
+
export declare function loadLabelPack(packsDir: string, locale: AvailableLanguage): LabelPack;
|
|
76
|
+
/**
|
|
77
|
+
* Batch loader — every locale declared on the site at once. The map
|
|
78
|
+
* iteration order matches the declaration order of
|
|
79
|
+
* `siteContext.websiteConfig.locales` so CLI reports come out in a
|
|
80
|
+
* deterministic, authored sequence.
|
|
81
|
+
*/
|
|
82
|
+
export declare function loadAllLabelPacks(packsDir: string, siteContext: WebsiteSiteContext): Map<AvailableLanguage, LabelPack>;
|
|
83
|
+
/**
|
|
84
|
+
* Per-locale validation result — the validator's flat output extended
|
|
85
|
+
* with the locale tag so a multi-locale report can flatten + group.
|
|
86
|
+
*/
|
|
87
|
+
export interface LocaleLabelPackValidation {
|
|
88
|
+
locale: AvailableLanguage;
|
|
89
|
+
result: LabelPackValidationResult;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Highest-level entry point: load every locale's pack and run the
|
|
93
|
+
* label-pack validator against the manifest registry.
|
|
94
|
+
*
|
|
95
|
+
* **Return shape decision** — this function does NOT throw on
|
|
96
|
+
* `missing` keys; it returns the per-locale results so the caller
|
|
97
|
+
* (the build-start hook OR the CLI auditor OR a debugging
|
|
98
|
+
* subprocess) can decide how to surface the failure (process exit
|
|
99
|
+
* code, formatted CLI output, JSON report, …). A thin `throwIfAny…`
|
|
100
|
+
* helper sits next to it for the common "fail the build on first
|
|
101
|
+
* missing key" path.
|
|
102
|
+
*/
|
|
103
|
+
export declare function loadAndValidateAllLabelPacks(packsDir: string, siteContext: WebsiteSiteContext, expectedKeysBySectionRef: ExpectedLabelKeysBySectionRef): readonly LocaleLabelPackValidation[];
|
|
104
|
+
/**
|
|
105
|
+
* Convenience wrapper for the build-start hook: validates every
|
|
106
|
+
* declared locale's pack and throws a SINGLE aggregated error if any
|
|
107
|
+
* locale has a hard failure (`missing` keys or `unmappedSectionRefs`).
|
|
108
|
+
* Orphaned keys are logged via `console.warn` (soft warning per the
|
|
109
|
+
* validator's contract) but do NOT throw.
|
|
110
|
+
*
|
|
111
|
+
* **Why aggregate (not first-fail)**: a multi-locale marketing site
|
|
112
|
+
* with `en` and `fr` packs both missing keys would, under a
|
|
113
|
+
* first-fail policy, force the author to fix `en`, re-run the build,
|
|
114
|
+
* THEN see `fr`'s problem. Aggregating means one build run surfaces
|
|
115
|
+
* every locale's hard errors together — material UX win for the
|
|
116
|
+
* authoring loop. The aggregated message keeps each locale's failure
|
|
117
|
+
* in its own block so the diagnostic stays scannable.
|
|
118
|
+
*
|
|
119
|
+
* **Why throw vs. return**: the build-start hook lives in
|
|
120
|
+
* `astro.config.mjs` (or a `vite-plugin`-style integration) and
|
|
121
|
+
* benefits from the shortest possible code path — a thrown error
|
|
122
|
+
* naturally aborts `astro build` with a non-zero exit code and
|
|
123
|
+
* surfaces the message in the build log. The richer
|
|
124
|
+
* `loadAndValidateAllLabelPacks` is what `wildo website audit` calls
|
|
125
|
+
* when it wants programmatic access to the per-locale results.
|
|
126
|
+
*
|
|
127
|
+
* **Key-origin annotation**: every `missing` key is tagged with the
|
|
128
|
+
* page (and, if section-scoped, the section) that declared it, so the
|
|
129
|
+
* author can grep + open the right `defineWebsiteSection` /
|
|
130
|
+
* `defineWebsitePageManifest` call site without leaving the build log.
|
|
131
|
+
*/
|
|
132
|
+
export declare function assertLabelPacksValid(packsDir: string, siteContext: WebsiteSiteContext, expectedKeysBySectionRef: ExpectedLabelKeysBySectionRef): void;
|
|
133
|
+
/** @wildo_source:part:end saas.website.astro.label-loader */
|
|
134
|
+
//# sourceMappingURL=label-pack-loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"label-pack-loader.d.ts","sourceRoot":"","sources":["../../../../src/astro/label-pack-loader.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sCAAsC,CAAC;AAK9E,OAAO,EAEL,KAAK,6BAA6B,EAClC,KAAK,SAAS,EACd,KAAK,yBAAyB,EAC/B,MAAM,4CAA4C,CAAC;AAEpD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAEjE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyDG;AAEH;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,GAAG,SAAS,CA4BpF;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,kBAAkB,GAC9B,GAAG,CAAC,iBAAiB,EAAE,SAAS,CAAC,CAMnC;AAED;;;GAGG;AACH,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,iBAAiB,CAAC;IAC1B,MAAM,EAAE,yBAAyB,CAAC;CACnC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,4BAA4B,CAC1C,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,kBAAkB,EAC/B,wBAAwB,EAAE,6BAA6B,GACtD,SAAS,yBAAyB,EAAE,CAmBtC;AAgCD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,kBAAkB,EAC/B,wBAAwB,EAAE,6BAA6B,GACtD,IAAI,CA6DN;AA+ED,6DAA6D"}
|