@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,298 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { validateLabelPackAgainstManifest, } from '../schemas/validators/label-pack.validator.js';
|
|
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 function loadLabelPack(packsDir, locale) {
|
|
76
|
+
const filePath = resolve(packsDir, `${locale}.json`);
|
|
77
|
+
let raw;
|
|
78
|
+
try {
|
|
79
|
+
raw = readFileSync(filePath, 'utf-8');
|
|
80
|
+
}
|
|
81
|
+
catch (cause) {
|
|
82
|
+
throw new Error(`[label-pack-loader] Could not read label pack at ${filePath} ` +
|
|
83
|
+
`(locale="${locale}"). ` +
|
|
84
|
+
`Make sure the directory exists and contains a "${locale}.json" file. ` +
|
|
85
|
+
`Underlying error: ${cause.message}`, { cause });
|
|
86
|
+
}
|
|
87
|
+
let parsed;
|
|
88
|
+
try {
|
|
89
|
+
parsed = JSON.parse(raw);
|
|
90
|
+
}
|
|
91
|
+
catch (cause) {
|
|
92
|
+
throw new Error(`[label-pack-loader] Failed to parse JSON in ${filePath} ` +
|
|
93
|
+
`(locale="${locale}"). Underlying parser error: ${cause.message}`, { cause });
|
|
94
|
+
}
|
|
95
|
+
return assertLabelPackShape(parsed, filePath, locale);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Batch loader — every locale declared on the site at once. The map
|
|
99
|
+
* iteration order matches the declaration order of
|
|
100
|
+
* `siteContext.websiteConfig.locales` so CLI reports come out in a
|
|
101
|
+
* deterministic, authored sequence.
|
|
102
|
+
*/
|
|
103
|
+
export function loadAllLabelPacks(packsDir, siteContext) {
|
|
104
|
+
const packs = new Map();
|
|
105
|
+
for (const entry of siteContext.websiteConfig.locales) {
|
|
106
|
+
packs.set(entry.locale, loadLabelPack(packsDir, entry.locale));
|
|
107
|
+
}
|
|
108
|
+
return packs;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Highest-level entry point: load every locale's pack and run the
|
|
112
|
+
* label-pack validator against the manifest registry.
|
|
113
|
+
*
|
|
114
|
+
* **Return shape decision** — this function does NOT throw on
|
|
115
|
+
* `missing` keys; it returns the per-locale results so the caller
|
|
116
|
+
* (the build-start hook OR the CLI auditor OR a debugging
|
|
117
|
+
* subprocess) can decide how to surface the failure (process exit
|
|
118
|
+
* code, formatted CLI output, JSON report, …). A thin `throwIfAny…`
|
|
119
|
+
* helper sits next to it for the common "fail the build on first
|
|
120
|
+
* missing key" path.
|
|
121
|
+
*/
|
|
122
|
+
export function loadAndValidateAllLabelPacks(packsDir, siteContext, expectedKeysBySectionRef) {
|
|
123
|
+
const { rootConfig } = siteContext;
|
|
124
|
+
const packs = loadAllLabelPacks(packsDir, siteContext);
|
|
125
|
+
const manifests = rootConfig.pageManifests;
|
|
126
|
+
const effectiveChromeKeys = composeEffectiveChromeLabelKeys(rootConfig);
|
|
127
|
+
const validations = [];
|
|
128
|
+
for (const [locale, pack] of packs) {
|
|
129
|
+
validations.push({
|
|
130
|
+
locale,
|
|
131
|
+
result: validateLabelPackAgainstManifest(manifests, expectedKeysBySectionRef, pack, effectiveChromeKeys),
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
return validations;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Compose the chrome-label-key set the validator should treat as
|
|
138
|
+
* "expected" — `WebsiteRootConfig.chromeLabelKeys` (consumer-authored)
|
|
139
|
+
* UNION the framework-required `skipToMainContentLabelKey`.
|
|
140
|
+
*
|
|
141
|
+
* Centralized here because:
|
|
142
|
+
* - Both `loadAndValidateAllLabelPacks` (used at build time) and
|
|
143
|
+
* `buildKeyOriginMap` (used to annotate missing-key errors) need
|
|
144
|
+
* the same composed list, and forking the union into two places
|
|
145
|
+
* would be a drift hazard.
|
|
146
|
+
* - The framework owns the skip-link key (it is rendered by
|
|
147
|
+
* `WebsitePageLayout`, not the consumer's chrome), so the
|
|
148
|
+
* consumer should NOT have to remember to also list it in
|
|
149
|
+
* `chromeLabelKeys`. Auto-injection here keeps the contract
|
|
150
|
+
* minimal at the consumer's call site (just declare the key in
|
|
151
|
+
* `WebsiteRootConfig.skipToMainContentLabelKey` once + ship the
|
|
152
|
+
* translation in every locale pack) while still surfacing the key
|
|
153
|
+
* through the validator's "missing"/"orphaned" diagnostics.
|
|
154
|
+
* - De-duplication is via `Set` semantics so a consumer who
|
|
155
|
+
* defensively lists the key in both places (no-op but harmless)
|
|
156
|
+
* does not produce a duplicate-key warning downstream.
|
|
157
|
+
*/
|
|
158
|
+
function composeEffectiveChromeLabelKeys(rootConfig) {
|
|
159
|
+
const set = new Set(rootConfig.chromeLabelKeys);
|
|
160
|
+
set.add(rootConfig.skipToMainContentLabelKey);
|
|
161
|
+
return Array.from(set);
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Convenience wrapper for the build-start hook: validates every
|
|
165
|
+
* declared locale's pack and throws a SINGLE aggregated error if any
|
|
166
|
+
* locale has a hard failure (`missing` keys or `unmappedSectionRefs`).
|
|
167
|
+
* Orphaned keys are logged via `console.warn` (soft warning per the
|
|
168
|
+
* validator's contract) but do NOT throw.
|
|
169
|
+
*
|
|
170
|
+
* **Why aggregate (not first-fail)**: a multi-locale marketing site
|
|
171
|
+
* with `en` and `fr` packs both missing keys would, under a
|
|
172
|
+
* first-fail policy, force the author to fix `en`, re-run the build,
|
|
173
|
+
* THEN see `fr`'s problem. Aggregating means one build run surfaces
|
|
174
|
+
* every locale's hard errors together — material UX win for the
|
|
175
|
+
* authoring loop. The aggregated message keeps each locale's failure
|
|
176
|
+
* in its own block so the diagnostic stays scannable.
|
|
177
|
+
*
|
|
178
|
+
* **Why throw vs. return**: the build-start hook lives in
|
|
179
|
+
* `astro.config.mjs` (or a `vite-plugin`-style integration) and
|
|
180
|
+
* benefits from the shortest possible code path — a thrown error
|
|
181
|
+
* naturally aborts `astro build` with a non-zero exit code and
|
|
182
|
+
* surfaces the message in the build log. The richer
|
|
183
|
+
* `loadAndValidateAllLabelPacks` is what `wildo website audit` calls
|
|
184
|
+
* when it wants programmatic access to the per-locale results.
|
|
185
|
+
*
|
|
186
|
+
* **Key-origin annotation**: every `missing` key is tagged with the
|
|
187
|
+
* page (and, if section-scoped, the section) that declared it, so the
|
|
188
|
+
* author can grep + open the right `defineWebsiteSection` /
|
|
189
|
+
* `defineWebsitePageManifest` call site without leaving the build log.
|
|
190
|
+
*/
|
|
191
|
+
export function assertLabelPacksValid(packsDir, siteContext, expectedKeysBySectionRef) {
|
|
192
|
+
const { rootConfig } = siteContext;
|
|
193
|
+
const validations = loadAndValidateAllLabelPacks(packsDir, siteContext, expectedKeysBySectionRef);
|
|
194
|
+
/**
|
|
195
|
+
* Build a key → origin map ONCE per assert call (cost is bounded by
|
|
196
|
+
* the total number of declared keys across the whole site, which is
|
|
197
|
+
* typically small for a marketing surface). Enriches the missing-key
|
|
198
|
+
* error with the page/section that committed to each key — the
|
|
199
|
+
* author can grep the ref to find the site authoring call site.
|
|
200
|
+
*/
|
|
201
|
+
const keyOrigins = buildKeyOriginMap(rootConfig.pageManifests, expectedKeysBySectionRef, composeEffectiveChromeLabelKeys(rootConfig));
|
|
202
|
+
const localeErrorBlocks = [];
|
|
203
|
+
for (const { locale, result } of validations) {
|
|
204
|
+
const blocks = [];
|
|
205
|
+
if (result.unmappedSectionRefs.length > 0) {
|
|
206
|
+
blocks.push(` unmappedSectionRefs (build-pipeline bug — sections referenced by a ` +
|
|
207
|
+
`manifest but missing from the expected-keys collector): ` +
|
|
208
|
+
`[${result.unmappedSectionRefs.join(', ')}]. ` +
|
|
209
|
+
`Add every referenced section to the array passed to ` +
|
|
210
|
+
`collectExpectedLabelKeysFromSections().`);
|
|
211
|
+
}
|
|
212
|
+
if (result.missing.length > 0) {
|
|
213
|
+
const annotated = result.missing.map((key) => ` "${key}" ← ${keyOrigins.get(key) ?? 'unknown origin'}`);
|
|
214
|
+
blocks.push(` missing ${result.missing.length} required key(s) in ${packsDir}/${locale}.json:\n` +
|
|
215
|
+
annotated.join('\n'));
|
|
216
|
+
}
|
|
217
|
+
if (blocks.length > 0) {
|
|
218
|
+
localeErrorBlocks.push(`Locale "${locale}":\n${blocks.join('\n')}`);
|
|
219
|
+
}
|
|
220
|
+
if (result.orphaned.length > 0) {
|
|
221
|
+
console.warn(`[label-pack-loader] Locale "${locale}": ${result.orphaned.length} ` +
|
|
222
|
+
`orphaned key(s) in ${locale}.json (declared in pack but not consumed ` +
|
|
223
|
+
`by any section or page manifest):\n ${result.orphaned.join('\n ')}`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (localeErrorBlocks.length > 0) {
|
|
227
|
+
throw new Error(`[label-pack-loader] Label pack validation failed for ` +
|
|
228
|
+
`${localeErrorBlocks.length} locale(s):\n\n${localeErrorBlocks.join('\n\n')}`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Build a `keyString -> "page <pageRef> meta titleKey" | "section <sectionRef> in page <pageRef>"`
|
|
233
|
+
* lookup so the assert message can name the call site that committed
|
|
234
|
+
* to each missing key. Pure and small (cost is O(totalKeys) per build
|
|
235
|
+
* — bounded by the marketing surface).
|
|
236
|
+
*/
|
|
237
|
+
function buildKeyOriginMap(manifests, expectedKeysBySectionRef, chromeLabelKeys) {
|
|
238
|
+
const origins = new Map();
|
|
239
|
+
for (const manifest of manifests) {
|
|
240
|
+
const pageRefStr = manifest.ref;
|
|
241
|
+
const titleKey = manifest.metaLabelKeys.titleKey;
|
|
242
|
+
const descriptionKey = manifest.metaLabelKeys.descriptionKey;
|
|
243
|
+
if (!origins.has(titleKey)) {
|
|
244
|
+
origins.set(titleKey, `page "${pageRefStr}" metaLabelKeys.titleKey`);
|
|
245
|
+
}
|
|
246
|
+
if (!origins.has(descriptionKey)) {
|
|
247
|
+
origins.set(descriptionKey, `page "${pageRefStr}" metaLabelKeys.descriptionKey`);
|
|
248
|
+
}
|
|
249
|
+
for (const sectionRef of manifest.sectionRefs) {
|
|
250
|
+
const sectionRefStr = sectionRef;
|
|
251
|
+
const sectionKeys = expectedKeysBySectionRef.get(sectionRef);
|
|
252
|
+
if (sectionKeys === undefined)
|
|
253
|
+
continue;
|
|
254
|
+
for (const sectionKey of sectionKeys) {
|
|
255
|
+
const keyStr = sectionKey;
|
|
256
|
+
if (!origins.has(keyStr)) {
|
|
257
|
+
origins.set(keyStr, `section "${sectionRefStr}" in page "${pageRefStr}"`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
for (const chromeKey of chromeLabelKeys) {
|
|
263
|
+
const keyStr = chromeKey;
|
|
264
|
+
if (!origins.has(keyStr)) {
|
|
265
|
+
origins.set(keyStr, `WebsiteRootConfig.chromeLabelKeys`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return origins;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Defensive shape guard. Surface the offending key + observed type
|
|
272
|
+
* so the consumer can find the malformed entry quickly. Pure
|
|
273
|
+
* validation — no transformation; the returned value is the same
|
|
274
|
+
* object reference passed in (cast to `LabelPack`) so the caller
|
|
275
|
+
* does not pay an unnecessary copy.
|
|
276
|
+
*/
|
|
277
|
+
function assertLabelPackShape(parsed, filePath, locale) {
|
|
278
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
279
|
+
throw new Error(`[label-pack-loader] ${filePath} (locale="${locale}") must contain a ` +
|
|
280
|
+
`JSON object at the top level (got ${describeJson(parsed)}).`);
|
|
281
|
+
}
|
|
282
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
283
|
+
if (typeof value !== 'string') {
|
|
284
|
+
throw new Error(`[label-pack-loader] ${filePath} (locale="${locale}"): key "${key}" ` +
|
|
285
|
+
`must map to a string (got ${describeJson(value)}).`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return parsed;
|
|
289
|
+
}
|
|
290
|
+
function describeJson(value) {
|
|
291
|
+
if (value === null)
|
|
292
|
+
return 'null';
|
|
293
|
+
if (Array.isArray(value))
|
|
294
|
+
return 'array';
|
|
295
|
+
return typeof value;
|
|
296
|
+
}
|
|
297
|
+
/** @wildo_source:part:end saas.website.astro.label-loader */
|
|
298
|
+
//# sourceMappingURL=label-pack-loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"label-pack-loader.js","sourceRoot":"","sources":["../../../../src/astro/label-pack-loader.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\n\nimport type { AvailableLanguage } from '@wildo-ai/saas-models/public-runtime';\n\nimport type { WebsiteLabelKey } from '../schemas/label-keys/website-label-key.schemas';\nimport type { WebsitePageManifest } from '../schemas/manifests/website-page-manifest.shared.schemas';\nimport type { WebsiteRootConfig } from '../schemas/manifests/website-root-config.shared.schemas';\nimport {\n validateLabelPackAgainstManifest,\n type ExpectedLabelKeysBySectionRef,\n type LabelPack,\n type LabelPackValidationResult,\n} from '../schemas/validators/label-pack.validator';\n\nimport type { WebsiteSiteContext } from './website-site-context';\n\n/**\n * @wildo_source:part:start saas.website.astro.label-loader facet:layer:astro facet:family:website\n *\n * `label-pack-loader.ts` — synchronous, fs-backed loader for\n * marketing-site label packs. Consumed by:\n * - the Astro adapter (`WebsitePageBridge.astro` reads the active\n * locale's pack at request time during SSR / pre-render),\n * - the `wildo website audit` CLI (Phase 7) to validate every\n * locale pack against every manifest at CI time,\n * - any build-time hook that wants to fail fast on a missing or\n * malformed pack.\n *\n * **Convention** (matches the slice plan + the validator's contract):\n *\n * - One file per locale: `<packsDir>/<locale>.json`\n * - JSON object: flat key-value map, `Record<string, string>`\n * - Keys are fully-composed `WebsiteLabelKey`s (`website.<page>.<section>.<slot>`)\n * - Values are the localized strings\n *\n * **Why synchronous I/O**:\n * - The Astro frontmatter executes at build / SSR time in a Node\n * process (no event loop pressure to relieve), and the bridge\n * wants the pack resolved before it emits any HTML — async would\n * force the consumer to `await` inside an Astro frontmatter,\n * which is supported but adds a needless yield-point on a single\n * small JSON file.\n * - The validator is pure and synchronous; making the loader async\n * would add a coloring boundary for no benefit.\n *\n * **Failure modes — all surfaced as thrown errors with actionable\n * messages**:\n * - File missing — wrapped in a `[label-pack-loader] Could not read\n * pack at <path>` error so a typo in the dir / locale is obvious.\n * - JSON parse failure — error includes the file path and the\n * parser's own message.\n * - Top-level shape is not an object, or a value is not a string —\n * error names the offending key + observed type.\n *\n * **Why three exports** (loader, multi-loader, validator-runner):\n * - `loadLabelPack(packsDir, locale)` is the atomic primitive\n * (one file → one validated `LabelPack`). Used by the bridge at\n * request time.\n * - `loadAllLabelPacks(packsDir, siteContext)` is the build-time\n * batch — every locale pack at once. Used by the CLI auditor\n * and by the build-start hook.\n * - `validateAllLabelPacks(packsDir, siteContext, expectedKeysBySectionRef)`\n * is the highest-level entry point: load every pack, run the\n * validator on each, and either return the diagnostic results\n * or throw on the first hard error. The returned shape carries\n * per-locale diagnostics so the caller can format a CLI report.\n *\n * **`siteContext` parameter** — the consumer's composed\n * `WebsiteSiteContext` (see `website-site-context.ts`). The loader\n * reads `siteContext.websiteConfig.locales` for \"which locales does\n * the site ship?\" and `siteContext.rootConfig.{pageManifests,\n * chromeLabelKeys, skipToMainContentLabelKey}` for the manifest\n * registry / chrome-key set the validator runs against.\n */\n\n/**\n * Load and validate a single locale's label pack. Synchronous —\n * suitable for Astro frontmatter execution.\n *\n * @param packsDir Absolute or cwd-relative path to the directory\n * containing per-locale `<locale>.json` files.\n * @param locale The locale to load. Must be a member of\n * `WildoMarketingWebsiteConfig.locales`.\n * @returns A flat `LabelPack` (`Record<string, string>`) ready to\n * feed into `WebsiteRuntimeContextValue.labelPack`.\n * @throws When the file is missing, malformed JSON, not an object,\n * or contains a non-string value.\n */\nexport function loadLabelPack(packsDir: string, locale: AvailableLanguage): LabelPack {\n const filePath = resolve(packsDir, `${locale}.json`);\n\n let raw: string;\n try {\n raw = readFileSync(filePath, 'utf-8');\n } catch (cause) {\n throw new Error(\n `[label-pack-loader] Could not read label pack at ${filePath} ` +\n `(locale=\"${locale}\"). ` +\n `Make sure the directory exists and contains a \"${locale}.json\" file. ` +\n `Underlying error: ${(cause as Error).message}`,\n { cause },\n );\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (cause) {\n throw new Error(\n `[label-pack-loader] Failed to parse JSON in ${filePath} ` +\n `(locale=\"${locale}\"). Underlying parser error: ${(cause as Error).message}`,\n { cause },\n );\n }\n\n return assertLabelPackShape(parsed, filePath, locale);\n}\n\n/**\n * Batch loader — every locale declared on the site at once. The map\n * iteration order matches the declaration order of\n * `siteContext.websiteConfig.locales` so CLI reports come out in a\n * deterministic, authored sequence.\n */\nexport function loadAllLabelPacks(\n packsDir: string,\n siteContext: WebsiteSiteContext,\n): Map<AvailableLanguage, LabelPack> {\n const packs = new Map<AvailableLanguage, LabelPack>();\n for (const entry of siteContext.websiteConfig.locales) {\n packs.set(entry.locale, loadLabelPack(packsDir, entry.locale));\n }\n return packs;\n}\n\n/**\n * Per-locale validation result — the validator's flat output extended\n * with the locale tag so a multi-locale report can flatten + group.\n */\nexport interface LocaleLabelPackValidation {\n locale: AvailableLanguage;\n result: LabelPackValidationResult;\n}\n\n/**\n * Highest-level entry point: load every locale's pack and run the\n * label-pack validator against the manifest registry.\n *\n * **Return shape decision** — this function does NOT throw on\n * `missing` keys; it returns the per-locale results so the caller\n * (the build-start hook OR the CLI auditor OR a debugging\n * subprocess) can decide how to surface the failure (process exit\n * code, formatted CLI output, JSON report, …). A thin `throwIfAny…`\n * helper sits next to it for the common \"fail the build on first\n * missing key\" path.\n */\nexport function loadAndValidateAllLabelPacks(\n packsDir: string,\n siteContext: WebsiteSiteContext,\n expectedKeysBySectionRef: ExpectedLabelKeysBySectionRef,\n): readonly LocaleLabelPackValidation[] {\n const { rootConfig } = siteContext;\n const packs = loadAllLabelPacks(packsDir, siteContext);\n const manifests: readonly WebsitePageManifest[] = rootConfig.pageManifests;\n const effectiveChromeKeys = composeEffectiveChromeLabelKeys(rootConfig);\n\n const validations: LocaleLabelPackValidation[] = [];\n for (const [locale, pack] of packs) {\n validations.push({\n locale,\n result: validateLabelPackAgainstManifest(\n manifests,\n expectedKeysBySectionRef,\n pack,\n effectiveChromeKeys,\n ),\n });\n }\n return validations;\n}\n\n/**\n * Compose the chrome-label-key set the validator should treat as\n * \"expected\" — `WebsiteRootConfig.chromeLabelKeys` (consumer-authored)\n * UNION the framework-required `skipToMainContentLabelKey`.\n *\n * Centralized here because:\n * - Both `loadAndValidateAllLabelPacks` (used at build time) and\n * `buildKeyOriginMap` (used to annotate missing-key errors) need\n * the same composed list, and forking the union into two places\n * would be a drift hazard.\n * - The framework owns the skip-link key (it is rendered by\n * `WebsitePageLayout`, not the consumer's chrome), so the\n * consumer should NOT have to remember to also list it in\n * `chromeLabelKeys`. Auto-injection here keeps the contract\n * minimal at the consumer's call site (just declare the key in\n * `WebsiteRootConfig.skipToMainContentLabelKey` once + ship the\n * translation in every locale pack) while still surfacing the key\n * through the validator's \"missing\"/\"orphaned\" diagnostics.\n * - De-duplication is via `Set` semantics so a consumer who\n * defensively lists the key in both places (no-op but harmless)\n * does not produce a duplicate-key warning downstream.\n */\nfunction composeEffectiveChromeLabelKeys(\n rootConfig: WebsiteRootConfig,\n): readonly WebsiteLabelKey[] {\n const set = new Set<WebsiteLabelKey>(rootConfig.chromeLabelKeys);\n set.add(rootConfig.skipToMainContentLabelKey);\n return Array.from(set);\n}\n\n/**\n * Convenience wrapper for the build-start hook: validates every\n * declared locale's pack and throws a SINGLE aggregated error if any\n * locale has a hard failure (`missing` keys or `unmappedSectionRefs`).\n * Orphaned keys are logged via `console.warn` (soft warning per the\n * validator's contract) but do NOT throw.\n *\n * **Why aggregate (not first-fail)**: a multi-locale marketing site\n * with `en` and `fr` packs both missing keys would, under a\n * first-fail policy, force the author to fix `en`, re-run the build,\n * THEN see `fr`'s problem. Aggregating means one build run surfaces\n * every locale's hard errors together — material UX win for the\n * authoring loop. The aggregated message keeps each locale's failure\n * in its own block so the diagnostic stays scannable.\n *\n * **Why throw vs. return**: the build-start hook lives in\n * `astro.config.mjs` (or a `vite-plugin`-style integration) and\n * benefits from the shortest possible code path — a thrown error\n * naturally aborts `astro build` with a non-zero exit code and\n * surfaces the message in the build log. The richer\n * `loadAndValidateAllLabelPacks` is what `wildo website audit` calls\n * when it wants programmatic access to the per-locale results.\n *\n * **Key-origin annotation**: every `missing` key is tagged with the\n * page (and, if section-scoped, the section) that declared it, so the\n * author can grep + open the right `defineWebsiteSection` /\n * `defineWebsitePageManifest` call site without leaving the build log.\n */\nexport function assertLabelPacksValid(\n packsDir: string,\n siteContext: WebsiteSiteContext,\n expectedKeysBySectionRef: ExpectedLabelKeysBySectionRef,\n): void {\n const { rootConfig } = siteContext;\n const validations = loadAndValidateAllLabelPacks(\n packsDir,\n siteContext,\n expectedKeysBySectionRef,\n );\n\n /**\n * Build a key → origin map ONCE per assert call (cost is bounded by\n * the total number of declared keys across the whole site, which is\n * typically small for a marketing surface). Enriches the missing-key\n * error with the page/section that committed to each key — the\n * author can grep the ref to find the site authoring call site.\n */\n const keyOrigins = buildKeyOriginMap(\n rootConfig.pageManifests,\n expectedKeysBySectionRef,\n composeEffectiveChromeLabelKeys(rootConfig),\n );\n\n const localeErrorBlocks: string[] = [];\n\n for (const { locale, result } of validations) {\n const blocks: string[] = [];\n if (result.unmappedSectionRefs.length > 0) {\n blocks.push(\n ` unmappedSectionRefs (build-pipeline bug — sections referenced by a ` +\n `manifest but missing from the expected-keys collector): ` +\n `[${(result.unmappedSectionRefs as unknown as readonly string[]).join(', ')}]. ` +\n `Add every referenced section to the array passed to ` +\n `collectExpectedLabelKeysFromSections().`,\n );\n }\n if (result.missing.length > 0) {\n const annotated = (result.missing as unknown as readonly string[]).map(\n (key) => ` \"${key}\" ← ${keyOrigins.get(key) ?? 'unknown origin'}`,\n );\n blocks.push(\n ` missing ${result.missing.length} required key(s) in ${packsDir}/${locale}.json:\\n` +\n annotated.join('\\n'),\n );\n }\n if (blocks.length > 0) {\n localeErrorBlocks.push(`Locale \"${locale}\":\\n${blocks.join('\\n')}`);\n }\n if (result.orphaned.length > 0) {\n console.warn(\n `[label-pack-loader] Locale \"${locale}\": ${result.orphaned.length} ` +\n `orphaned key(s) in ${locale}.json (declared in pack but not consumed ` +\n `by any section or page manifest):\\n ${result.orphaned.join('\\n ')}`,\n );\n }\n }\n\n if (localeErrorBlocks.length > 0) {\n throw new Error(\n `[label-pack-loader] Label pack validation failed for ` +\n `${localeErrorBlocks.length} locale(s):\\n\\n${localeErrorBlocks.join('\\n\\n')}`,\n );\n }\n}\n\n/**\n * Build a `keyString -> \"page <pageRef> meta titleKey\" | \"section <sectionRef> in page <pageRef>\"`\n * lookup so the assert message can name the call site that committed\n * to each missing key. Pure and small (cost is O(totalKeys) per build\n * — bounded by the marketing surface).\n */\nfunction buildKeyOriginMap(\n manifests: readonly WebsitePageManifest[],\n expectedKeysBySectionRef: ExpectedLabelKeysBySectionRef,\n chromeLabelKeys: readonly WebsiteLabelKey[],\n): Map<string, string> {\n const origins = new Map<string, string>();\n for (const manifest of manifests) {\n const pageRefStr = manifest.ref as unknown as string;\n const titleKey = manifest.metaLabelKeys.titleKey as unknown as string;\n const descriptionKey = manifest.metaLabelKeys.descriptionKey as unknown as string;\n if (!origins.has(titleKey)) {\n origins.set(titleKey, `page \"${pageRefStr}\" metaLabelKeys.titleKey`);\n }\n if (!origins.has(descriptionKey)) {\n origins.set(descriptionKey, `page \"${pageRefStr}\" metaLabelKeys.descriptionKey`);\n }\n for (const sectionRef of manifest.sectionRefs) {\n const sectionRefStr = sectionRef as unknown as string;\n const sectionKeys = expectedKeysBySectionRef.get(sectionRef);\n if (sectionKeys === undefined) continue;\n for (const sectionKey of sectionKeys) {\n const keyStr = sectionKey as unknown as string;\n if (!origins.has(keyStr)) {\n origins.set(keyStr, `section \"${sectionRefStr}\" in page \"${pageRefStr}\"`);\n }\n }\n }\n }\n for (const chromeKey of chromeLabelKeys) {\n const keyStr = chromeKey as unknown as string;\n if (!origins.has(keyStr)) {\n origins.set(keyStr, `WebsiteRootConfig.chromeLabelKeys`);\n }\n }\n return origins;\n}\n\n/**\n * Defensive shape guard. Surface the offending key + observed type\n * so the consumer can find the malformed entry quickly. Pure\n * validation — no transformation; the returned value is the same\n * object reference passed in (cast to `LabelPack`) so the caller\n * does not pay an unnecessary copy.\n */\nfunction assertLabelPackShape(\n parsed: unknown,\n filePath: string,\n locale: AvailableLanguage,\n): LabelPack {\n if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {\n throw new Error(\n `[label-pack-loader] ${filePath} (locale=\"${locale}\") must contain a ` +\n `JSON object at the top level (got ${describeJson(parsed)}).`,\n );\n }\n for (const [key, value] of Object.entries(parsed)) {\n if (typeof value !== 'string') {\n throw new Error(\n `[label-pack-loader] ${filePath} (locale=\"${locale}\"): key \"${key}\" ` +\n `must map to a string (got ${describeJson(value)}).`,\n );\n }\n }\n return parsed as LabelPack;\n}\n\nfunction describeJson(value: unknown): string {\n if (value === null) return 'null';\n if (Array.isArray(value)) return 'array';\n return typeof value;\n}\n/** @wildo_source:part:end saas.website.astro.label-loader */\n"]}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { WebsiteSiteContext } from './website-site-context';
|
|
2
|
+
export interface ReadLlmsTxtBodyOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Directory containing the hand-authored `llms.txt` file. Defaults
|
|
5
|
+
* to the conventional `./src/content` path from the website package
|
|
6
|
+
* root.
|
|
7
|
+
*/
|
|
8
|
+
contentDir?: string;
|
|
9
|
+
/**
|
|
10
|
+
* File name under `contentDir`. Exposed for tests; consumers should
|
|
11
|
+
* stick to the canonical `llms.txt`.
|
|
12
|
+
*/
|
|
13
|
+
fileName?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* @wildo_source:part:start saas.website.astro.llms-txt-server facet:layer:astro facet:family:website
|
|
17
|
+
*
|
|
18
|
+
* Read the hand-authored `llms.txt` body for the marketing site.
|
|
19
|
+
*
|
|
20
|
+
* Publication policy:
|
|
21
|
+
* - `seo.llmsTxt?.enabled === false` → return `null`
|
|
22
|
+
* - `seo.llmsTxt?.enabled === true` → read the file if present, else `null`
|
|
23
|
+
* - `seo.llmsTxt === undefined` or `enabled === undefined` → auto-detect:
|
|
24
|
+
* publish when the file exists, otherwise return `null`
|
|
25
|
+
*/
|
|
26
|
+
export declare function readLlmsTxtBody(siteContext: WebsiteSiteContext, options?: ReadLlmsTxtBodyOptions): string | null;
|
|
27
|
+
/** @wildo_source:part:end saas.website.astro.llms-txt-server */
|
|
28
|
+
//# sourceMappingURL=llms-txt-server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llms-txt-server.d.ts","sourceRoot":"","sources":["../../../../src/astro/llms-txt-server.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAEjE,MAAM,WAAW,sBAAsB;IACrC;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAC7B,WAAW,EAAE,kBAAkB,EAC/B,OAAO,GAAE,sBAA2B,GACnC,MAAM,GAAG,IAAI,CAqBf;AACD,gEAAgE"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* @wildo_source:part:start saas.website.astro.llms-txt-server facet:layer:astro facet:family:website
|
|
5
|
+
*
|
|
6
|
+
* Read the hand-authored `llms.txt` body for the marketing site.
|
|
7
|
+
*
|
|
8
|
+
* Publication policy:
|
|
9
|
+
* - `seo.llmsTxt?.enabled === false` → return `null`
|
|
10
|
+
* - `seo.llmsTxt?.enabled === true` → read the file if present, else `null`
|
|
11
|
+
* - `seo.llmsTxt === undefined` or `enabled === undefined` → auto-detect:
|
|
12
|
+
* publish when the file exists, otherwise return `null`
|
|
13
|
+
*/
|
|
14
|
+
export function readLlmsTxtBody(siteContext, options = {}) {
|
|
15
|
+
const contentDir = options.contentDir ?? './src/content';
|
|
16
|
+
const fileName = options.fileName ?? 'llms.txt';
|
|
17
|
+
const filePath = resolve(contentDir, fileName);
|
|
18
|
+
if (siteContext.rootConfig.seo.llmsTxt?.enabled === false) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
if (!existsSync(filePath)) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
return readFileSync(filePath, 'utf-8');
|
|
26
|
+
}
|
|
27
|
+
catch (cause) {
|
|
28
|
+
throw new Error(`[llms-txt-server] Could not read llms.txt at ${filePath}. ` +
|
|
29
|
+
`Underlying error: ${cause.message}`, { cause });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/** @wildo_source:part:end saas.website.astro.llms-txt-server */
|
|
33
|
+
//# sourceMappingURL=llms-txt-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llms-txt-server.js","sourceRoot":"","sources":["../../../../src/astro/llms-txt-server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAkBpC;;;;;;;;;;GAUG;AACH,MAAM,UAAU,eAAe,CAC7B,WAA+B,EAC/B,UAAkC,EAAE;IAEpC,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,eAAe,CAAC;IACzD,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,UAAU,CAAC;IAChD,MAAM,QAAQ,GAAG,OAAO,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IAE/C,IAAI,WAAW,CAAC,UAAU,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,KAAK,EAAE,CAAC;QAC1D,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC1B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACzC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CACb,gDAAgD,QAAQ,IAAI;YAC1D,qBAAsB,KAAe,CAAC,OAAO,EAAE,EACjD,EAAE,KAAK,EAAE,CACV,CAAC;IACJ,CAAC;AACH,CAAC;AACD,gEAAgE","sourcesContent":["import { existsSync, readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\n\nimport type { WebsiteSiteContext } from './website-site-context';\n\nexport interface ReadLlmsTxtBodyOptions {\n /**\n * Directory containing the hand-authored `llms.txt` file. Defaults\n * to the conventional `./src/content` path from the website package\n * root.\n */\n contentDir?: string;\n /**\n * File name under `contentDir`. Exposed for tests; consumers should\n * stick to the canonical `llms.txt`.\n */\n fileName?: string;\n}\n\n/**\n * @wildo_source:part:start saas.website.astro.llms-txt-server facet:layer:astro facet:family:website\n *\n * Read the hand-authored `llms.txt` body for the marketing site.\n *\n * Publication policy:\n * - `seo.llmsTxt?.enabled === false` → return `null`\n * - `seo.llmsTxt?.enabled === true` → read the file if present, else `null`\n * - `seo.llmsTxt === undefined` or `enabled === undefined` → auto-detect:\n * publish when the file exists, otherwise return `null`\n */\nexport function readLlmsTxtBody(\n siteContext: WebsiteSiteContext,\n options: ReadLlmsTxtBodyOptions = {},\n): string | null {\n const contentDir = options.contentDir ?? './src/content';\n const fileName = options.fileName ?? 'llms.txt';\n const filePath = resolve(contentDir, fileName);\n\n if (siteContext.rootConfig.seo.llmsTxt?.enabled === false) {\n return null;\n }\n if (!existsSync(filePath)) {\n return null;\n }\n\n try {\n return readFileSync(filePath, 'utf-8');\n } catch (cause) {\n throw new Error(\n `[llms-txt-server] Could not read llms.txt at ${filePath}. ` +\n `Underlying error: ${(cause as Error).message}`,\n { cause },\n );\n }\n}\n/** @wildo_source:part:end saas.website.astro.llms-txt-server */\n"]}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { WebsiteLabelKey } from '../schemas/label-keys/website-label-key.schemas';
|
|
2
|
+
import type { WebsitePageManifest } from '../schemas/manifests/website-page-manifest.shared.schemas';
|
|
3
|
+
import type { LabelPack } from '../schemas/validators/label-pack.validator';
|
|
4
|
+
/**
|
|
5
|
+
* @wildo_source:part:start saas.website.astro.pick-labels-for-page facet:layer:astro facet:family:website
|
|
6
|
+
*
|
|
7
|
+
* `pickLabelsForPage(pack, manifest)` — return the subset of a loaded
|
|
8
|
+
* label pack that the given page actually consumes at render time.
|
|
9
|
+
*
|
|
10
|
+
* **Why this exists**: the per-page bridge island carries its
|
|
11
|
+
* `labelPack` prop across the SSR → client hydration boundary as
|
|
12
|
+
* serialized JSON inside the `<astro-island props=...>` attribute.
|
|
13
|
+
* Shipping the entire site's pack on every page (every locale × every
|
|
14
|
+
* page = every visitor pays the full pack) inflates the static HTML
|
|
15
|
+
* payload by ~70% on a typical marketing surface where each page reads
|
|
16
|
+
* only its own slice plus the shared chrome slice.
|
|
17
|
+
*
|
|
18
|
+
* **Filter rule** (matches the runtime contract enforced by
|
|
19
|
+
* `useWebsiteLabel` / `useWebsiteLabelByKey`):
|
|
20
|
+
* - Keep keys starting with `website.<manifest.ref>.` — every key a
|
|
21
|
+
* `useWebsiteLabel(slot)` call inside any of this page's sections
|
|
22
|
+
* can ever resolve to (the hook composes
|
|
23
|
+
* `website.<pageRef>.<sectionRef>.<slot>`).
|
|
24
|
+
* - Keep keys starting with `website.chrome.` — the synthetic
|
|
25
|
+
* namespace site-wide chrome (header / footer) reads via
|
|
26
|
+
* `useWebsiteLabelByKey(absoluteKey)`. The chrome contract is
|
|
27
|
+
* declared on `WebsiteRootConfig.chromeLabelKeys` and validated
|
|
28
|
+
* by the label-pack validator.
|
|
29
|
+
*
|
|
30
|
+
* **What gets dropped**: every `website.<otherPage>.…` key. A page
|
|
31
|
+
* cannot legitimately read another page's labels through the hook
|
|
32
|
+
* surface — the hook would either throw (`useWebsiteLabel`) or
|
|
33
|
+
* surface a missing-key diagnostic (`useWebsiteLabelByKey`) — so
|
|
34
|
+
* dropping those keys is observably safe.
|
|
35
|
+
*
|
|
36
|
+
* **Why prefix-based** (vs. walking `expectedKeysBySectionRef` for an
|
|
37
|
+
* exact key list): prefix matching is O(N) over `Object.entries(pack)`
|
|
38
|
+
* with no extra inputs threaded through `loadWebsitePageRuntime`. An
|
|
39
|
+
* exact-list filter would need to know the section-key map at request
|
|
40
|
+
* time, forcing every consumer's `.astro` page to import the
|
|
41
|
+
* collected map — extra config-surface for a tighter filter that
|
|
42
|
+
* saves an additional handful of bytes (the section keys for the
|
|
43
|
+
* page would already match the prefix).
|
|
44
|
+
*
|
|
45
|
+
* **Pure**: no I/O, no observable side-effects. Returns a fresh
|
|
46
|
+
* object — the input `pack` reference is not mutated.
|
|
47
|
+
*
|
|
48
|
+
* @param pack The full loaded label pack for the active locale.
|
|
49
|
+
* @param manifest The page manifest being rendered (drives the
|
|
50
|
+
* `website.<manifest.ref>.*` page-own prefix).
|
|
51
|
+
* @param explicitKeys OPTIONAL allow-list of additional fully-composed
|
|
52
|
+
* label keys to keep regardless of prefix. Used by
|
|
53
|
+
* the framework to guarantee that
|
|
54
|
+
* `WebsiteRootConfig.skipToMainContentLabelKey`
|
|
55
|
+
* reaches the bridge even if a consumer chose to
|
|
56
|
+
* namespace it outside `website.chrome.*` (the
|
|
57
|
+
* framework MUST be able to render the skip link
|
|
58
|
+
* on every page in every locale — Phase 6 audit
|
|
59
|
+
* fix M-α).
|
|
60
|
+
*/
|
|
61
|
+
export declare function pickLabelsForPage(pack: LabelPack, manifest: WebsitePageManifest, explicitKeys?: readonly WebsiteLabelKey[]): LabelPack;
|
|
62
|
+
/** @wildo_source:part:end saas.website.astro.pick-labels-for-page */
|
|
63
|
+
//# sourceMappingURL=pick-labels-for-page.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pick-labels-for-page.d.ts","sourceRoot":"","sources":["../../../../src/astro/pick-labels-for-page.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iDAAiD,CAAC;AACvF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2DAA2D,CAAC;AACrG,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,4CAA4C,CAAC;AAE5E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,SAAS,EACf,QAAQ,EAAE,mBAAmB,EAC7B,YAAY,GAAE,SAAS,eAAe,EAAO,GAC5C,SAAS,CAkBX;AACD,qEAAqE"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @wildo_source:part:start saas.website.astro.pick-labels-for-page facet:layer:astro facet:family:website
|
|
3
|
+
*
|
|
4
|
+
* `pickLabelsForPage(pack, manifest)` — return the subset of a loaded
|
|
5
|
+
* label pack that the given page actually consumes at render time.
|
|
6
|
+
*
|
|
7
|
+
* **Why this exists**: the per-page bridge island carries its
|
|
8
|
+
* `labelPack` prop across the SSR → client hydration boundary as
|
|
9
|
+
* serialized JSON inside the `<astro-island props=...>` attribute.
|
|
10
|
+
* Shipping the entire site's pack on every page (every locale × every
|
|
11
|
+
* page = every visitor pays the full pack) inflates the static HTML
|
|
12
|
+
* payload by ~70% on a typical marketing surface where each page reads
|
|
13
|
+
* only its own slice plus the shared chrome slice.
|
|
14
|
+
*
|
|
15
|
+
* **Filter rule** (matches the runtime contract enforced by
|
|
16
|
+
* `useWebsiteLabel` / `useWebsiteLabelByKey`):
|
|
17
|
+
* - Keep keys starting with `website.<manifest.ref>.` — every key a
|
|
18
|
+
* `useWebsiteLabel(slot)` call inside any of this page's sections
|
|
19
|
+
* can ever resolve to (the hook composes
|
|
20
|
+
* `website.<pageRef>.<sectionRef>.<slot>`).
|
|
21
|
+
* - Keep keys starting with `website.chrome.` — the synthetic
|
|
22
|
+
* namespace site-wide chrome (header / footer) reads via
|
|
23
|
+
* `useWebsiteLabelByKey(absoluteKey)`. The chrome contract is
|
|
24
|
+
* declared on `WebsiteRootConfig.chromeLabelKeys` and validated
|
|
25
|
+
* by the label-pack validator.
|
|
26
|
+
*
|
|
27
|
+
* **What gets dropped**: every `website.<otherPage>.…` key. A page
|
|
28
|
+
* cannot legitimately read another page's labels through the hook
|
|
29
|
+
* surface — the hook would either throw (`useWebsiteLabel`) or
|
|
30
|
+
* surface a missing-key diagnostic (`useWebsiteLabelByKey`) — so
|
|
31
|
+
* dropping those keys is observably safe.
|
|
32
|
+
*
|
|
33
|
+
* **Why prefix-based** (vs. walking `expectedKeysBySectionRef` for an
|
|
34
|
+
* exact key list): prefix matching is O(N) over `Object.entries(pack)`
|
|
35
|
+
* with no extra inputs threaded through `loadWebsitePageRuntime`. An
|
|
36
|
+
* exact-list filter would need to know the section-key map at request
|
|
37
|
+
* time, forcing every consumer's `.astro` page to import the
|
|
38
|
+
* collected map — extra config-surface for a tighter filter that
|
|
39
|
+
* saves an additional handful of bytes (the section keys for the
|
|
40
|
+
* page would already match the prefix).
|
|
41
|
+
*
|
|
42
|
+
* **Pure**: no I/O, no observable side-effects. Returns a fresh
|
|
43
|
+
* object — the input `pack` reference is not mutated.
|
|
44
|
+
*
|
|
45
|
+
* @param pack The full loaded label pack for the active locale.
|
|
46
|
+
* @param manifest The page manifest being rendered (drives the
|
|
47
|
+
* `website.<manifest.ref>.*` page-own prefix).
|
|
48
|
+
* @param explicitKeys OPTIONAL allow-list of additional fully-composed
|
|
49
|
+
* label keys to keep regardless of prefix. Used by
|
|
50
|
+
* the framework to guarantee that
|
|
51
|
+
* `WebsiteRootConfig.skipToMainContentLabelKey`
|
|
52
|
+
* reaches the bridge even if a consumer chose to
|
|
53
|
+
* namespace it outside `website.chrome.*` (the
|
|
54
|
+
* framework MUST be able to render the skip link
|
|
55
|
+
* on every page in every locale — Phase 6 audit
|
|
56
|
+
* fix M-α).
|
|
57
|
+
*/
|
|
58
|
+
export function pickLabelsForPage(pack, manifest, explicitKeys = []) {
|
|
59
|
+
const pagePrefix = `website.${manifest.ref}.`;
|
|
60
|
+
const chromePrefix = 'website.chrome.';
|
|
61
|
+
const explicitKeySet = new Set(explicitKeys.map((key) => key));
|
|
62
|
+
const filtered = {};
|
|
63
|
+
for (const [key, value] of Object.entries(pack)) {
|
|
64
|
+
if (key.startsWith(pagePrefix) ||
|
|
65
|
+
key.startsWith(chromePrefix) ||
|
|
66
|
+
explicitKeySet.has(key)) {
|
|
67
|
+
filtered[key] = value;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return filtered;
|
|
71
|
+
}
|
|
72
|
+
/** @wildo_source:part:end saas.website.astro.pick-labels-for-page */
|
|
73
|
+
//# sourceMappingURL=pick-labels-for-page.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pick-labels-for-page.js","sourceRoot":"","sources":["../../../../src/astro/pick-labels-for-page.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AACH,MAAM,UAAU,iBAAiB,CAC/B,IAAe,EACf,QAA6B,EAC7B,eAA2C,EAAE;IAE7C,MAAM,UAAU,GAAG,WAAW,QAAQ,CAAC,GAAwB,GAAG,CAAC;IACnE,MAAM,YAAY,GAAG,iBAAiB,CAAC;IACvC,MAAM,cAAc,GAAG,IAAI,GAAG,CAC5B,YAAY,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAwB,CAAC,CACpD,CAAC;IAEF,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QAChD,IACE,GAAG,CAAC,UAAU,CAAC,UAAU,CAAC;YAC1B,GAAG,CAAC,UAAU,CAAC,YAAY,CAAC;YAC5B,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,EACvB,CAAC;YACD,QAAQ,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QACxB,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AACD,qEAAqE","sourcesContent":["import type { WebsiteLabelKey } from '../schemas/label-keys/website-label-key.schemas';\nimport type { WebsitePageManifest } from '../schemas/manifests/website-page-manifest.shared.schemas';\nimport type { LabelPack } from '../schemas/validators/label-pack.validator';\n\n/**\n * @wildo_source:part:start saas.website.astro.pick-labels-for-page facet:layer:astro facet:family:website\n *\n * `pickLabelsForPage(pack, manifest)` — return the subset of a loaded\n * label pack that the given page actually consumes at render time.\n *\n * **Why this exists**: the per-page bridge island carries its\n * `labelPack` prop across the SSR → client hydration boundary as\n * serialized JSON inside the `<astro-island props=...>` attribute.\n * Shipping the entire site's pack on every page (every locale × every\n * page = every visitor pays the full pack) inflates the static HTML\n * payload by ~70% on a typical marketing surface where each page reads\n * only its own slice plus the shared chrome slice.\n *\n * **Filter rule** (matches the runtime contract enforced by\n * `useWebsiteLabel` / `useWebsiteLabelByKey`):\n * - Keep keys starting with `website.<manifest.ref>.` — every key a\n * `useWebsiteLabel(slot)` call inside any of this page's sections\n * can ever resolve to (the hook composes\n * `website.<pageRef>.<sectionRef>.<slot>`).\n * - Keep keys starting with `website.chrome.` — the synthetic\n * namespace site-wide chrome (header / footer) reads via\n * `useWebsiteLabelByKey(absoluteKey)`. The chrome contract is\n * declared on `WebsiteRootConfig.chromeLabelKeys` and validated\n * by the label-pack validator.\n *\n * **What gets dropped**: every `website.<otherPage>.…` key. A page\n * cannot legitimately read another page's labels through the hook\n * surface — the hook would either throw (`useWebsiteLabel`) or\n * surface a missing-key diagnostic (`useWebsiteLabelByKey`) — so\n * dropping those keys is observably safe.\n *\n * **Why prefix-based** (vs. walking `expectedKeysBySectionRef` for an\n * exact key list): prefix matching is O(N) over `Object.entries(pack)`\n * with no extra inputs threaded through `loadWebsitePageRuntime`. An\n * exact-list filter would need to know the section-key map at request\n * time, forcing every consumer's `.astro` page to import the\n * collected map — extra config-surface for a tighter filter that\n * saves an additional handful of bytes (the section keys for the\n * page would already match the prefix).\n *\n * **Pure**: no I/O, no observable side-effects. Returns a fresh\n * object — the input `pack` reference is not mutated.\n *\n * @param pack The full loaded label pack for the active locale.\n * @param manifest The page manifest being rendered (drives the\n * `website.<manifest.ref>.*` page-own prefix).\n * @param explicitKeys OPTIONAL allow-list of additional fully-composed\n * label keys to keep regardless of prefix. Used by\n * the framework to guarantee that\n * `WebsiteRootConfig.skipToMainContentLabelKey`\n * reaches the bridge even if a consumer chose to\n * namespace it outside `website.chrome.*` (the\n * framework MUST be able to render the skip link\n * on every page in every locale — Phase 6 audit\n * fix M-α).\n */\nexport function pickLabelsForPage(\n pack: LabelPack,\n manifest: WebsitePageManifest,\n explicitKeys: readonly WebsiteLabelKey[] = [],\n): LabelPack {\n const pagePrefix = `website.${manifest.ref as unknown as string}.`;\n const chromePrefix = 'website.chrome.';\n const explicitKeySet = new Set<string>(\n explicitKeys.map((key) => key as unknown as string),\n );\n\n const filtered: LabelPack = {};\n for (const [key, value] of Object.entries(pack)) {\n if (\n key.startsWith(pagePrefix) ||\n key.startsWith(chromePrefix) ||\n explicitKeySet.has(key)\n ) {\n filtered[key] = value;\n }\n }\n return filtered;\n}\n/** @wildo_source:part:end saas.website.astro.pick-labels-for-page */\n"]}
|