@wildo-ai/saas-website 1.1.7 → 1.1.8
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/dist/esm/astro/blog-post-bridge.d.ts.map +1 -1
- package/dist/esm/astro/blog-post-bridge.js.map +1 -1
- package/dist/esm/astro/blog-post-head.renderer.d.ts.map +1 -1
- package/dist/esm/astro/bridge-runtime.d.ts.map +1 -1
- package/dist/esm/astro/bridge-runtime.js.map +1 -1
- package/dist/esm/astro/collect-expected-label-keys.d.ts.map +1 -1
- package/dist/esm/astro/headers-renderer.d.ts.map +1 -1
- package/dist/esm/astro/i18n-routing.helper.d.ts.map +1 -1
- package/dist/esm/astro/label-pack-loader.d.ts.map +1 -1
- package/dist/esm/astro/label-pack-loader.js.map +1 -1
- package/dist/esm/astro/llms-txt-server.d.ts.map +1 -1
- package/dist/esm/astro/pick-labels-for-page.d.ts.map +1 -1
- package/dist/esm/astro/robots-renderer.d.ts.map +1 -1
- package/dist/esm/astro/robots-renderer.js.map +1 -1
- package/dist/esm/astro/sitemap-coverage.d.ts.map +1 -1
- package/dist/esm/astro/structured-data.renderer.d.ts.map +1 -1
- package/dist/esm/astro/website-page-head.renderer.d.ts.map +1 -1
- package/dist/esm/astro/website-page-head.renderer.js.map +1 -1
- package/dist/esm/astro/website-page-runtime.helper.d.ts.map +1 -1
- package/dist/esm/astro/website-site-context.d.ts.map +1 -1
- package/dist/esm/astro/website-site-context.js.map +1 -1
- package/dist/esm/astro-island.d.ts.map +1 -1
- package/dist/esm/astro.d.ts.map +1 -1
- package/dist/esm/companion-exports.d.ts.map +1 -1
- package/dist/esm/companion-exports.js.map +1 -1
- package/dist/esm/components/low-level/WebsiteInternalButton.d.ts.map +1 -1
- package/dist/esm/components/low-level/WebsiteInternalLink.d.ts.map +1 -1
- package/dist/esm/config/define-website-config.d.ts.map +1 -1
- package/dist/esm/config/define-website-config.js.map +1 -1
- package/dist/esm/config/index.d.ts.map +1 -1
- package/dist/esm/config/load-website-config.d.ts.map +1 -1
- package/dist/esm/config/load-website-config.js.map +1 -1
- package/dist/esm/config/load-website-pricing-catalog.d.ts.map +1 -1
- package/dist/esm/config/load-website-pricing-catalog.js.map +1 -1
- package/dist/esm/config/load-website-site-context.d.ts.map +1 -1
- package/dist/esm/config/load-website-site-context.js.map +1 -1
- package/dist/esm/config/wildo-website-config.schemas.d.ts.map +1 -1
- package/dist/esm/config-loader.d.ts.map +1 -1
- package/dist/esm/config-loader.js.map +1 -1
- package/dist/esm/core/anonymous-session/InboundContactForm.d.ts.map +1 -1
- package/dist/esm/core/anonymous-session/InboundContactForm.js.map +1 -1
- package/dist/esm/core/anonymous-session/inbound-contact-form.schema.d.ts.map +1 -1
- package/dist/esm/core/anonymous-session/inbound-contact-form.schema.js.map +1 -1
- package/dist/esm/core/anonymous-session/website-anonymous-session-client.d.ts.map +1 -1
- package/dist/esm/core/anonymous-session/website-anonymous-session-client.js.map +1 -1
- package/dist/esm/core/anonymous-session/website-submission-challenge.d.ts.map +1 -1
- package/dist/esm/core/anonymous-session/website-submission-challenge.js.map +1 -1
- package/dist/esm/core/consent/WebsiteConsentBanner.d.ts.map +1 -1
- package/dist/esm/core/consent/WebsiteConsentBanner.js.map +1 -1
- package/dist/esm/core/consent/WebsiteConsentContext.d.ts.map +1 -1
- package/dist/esm/core/consent/WebsiteConsentGate.d.ts.map +1 -1
- package/dist/esm/core/consent/useWebsiteConsent.d.ts.map +1 -1
- package/dist/esm/core/consent/website-consent-storage.d.ts.map +1 -1
- package/dist/esm/core/contexts/WebsitePageContext.d.ts.map +1 -1
- package/dist/esm/core/contexts/WebsiteRuntimeContext.d.ts.map +1 -1
- package/dist/esm/core/contexts/WebsiteRuntimeContext.js.map +1 -1
- package/dist/esm/core/contexts/WebsiteSectionContext.d.ts.map +1 -1
- package/dist/esm/core/contexts/useWebsitePage.d.ts.map +1 -1
- package/dist/esm/core/contexts/useWebsiteRuntime.d.ts.map +1 -1
- package/dist/esm/core/contexts/useWebsiteRuntime.js.map +1 -1
- package/dist/esm/core/contexts/useWebsiteSection.d.ts.map +1 -1
- package/dist/esm/core/external-providers/WebsiteProviderComponent.d.ts.map +1 -1
- package/dist/esm/core/external-providers/WebsiteProviderComponent.js.map +1 -1
- package/dist/esm/core/external-providers/frontend-provider-registry.website.d.ts.map +1 -1
- package/dist/esm/core/external-providers/provider-component-registry.website.d.ts.map +1 -1
- package/dist/esm/core/external-providers/provider-component-registry.website.js.map +1 -1
- package/dist/esm/core/external-providers/useWebsiteProviderComponent.d.ts.map +1 -1
- package/dist/esm/core/external-providers/useWebsiteProviderComponent.js.map +1 -1
- package/dist/esm/core/external-providers/useWebsiteProviderScripts.d.ts.map +1 -1
- package/dist/esm/core/external-providers/useWebsiteProviderScripts.js.map +1 -1
- package/dist/esm/core/external-providers/website-provider-sdk-activations.d.ts.map +1 -1
- package/dist/esm/core/external-providers/website-provider-sdk-activations.js.map +1 -1
- package/dist/esm/core/factories/define-website-page-manifest.d.ts.map +1 -1
- package/dist/esm/core/factories/define-website-section.d.ts.map +1 -1
- package/dist/esm/core/factories/define-website-section.js.map +1 -1
- package/dist/esm/core/hooks/useWebsiteDesignTokens.d.ts.map +1 -1
- package/dist/esm/core/hooks/useWebsiteLabel.d.ts.map +1 -1
- package/dist/esm/core/layouts/WebsitePageLayout.d.ts.map +1 -1
- package/dist/esm/core/layouts/WebsitePageLayout.js.map +1 -1
- package/dist/esm/core/layouts/WebsiteSection.d.ts.map +1 -1
- package/dist/esm/core/routing/internal-routing.utils.d.ts.map +1 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/mdx/BlogPost.d.ts.map +1 -1
- package/dist/esm/mdx/blog-post-collection.config.d.ts.map +1 -1
- package/dist/esm/mdx/blog-post-frontmatter-source.d.ts.map +1 -1
- package/dist/esm/mdx/index.d.ts.map +1 -1
- package/dist/esm/mdx.d.ts.map +1 -1
- package/dist/esm/schemas/blog/blog-post-frontmatter.shared.schemas.d.ts.map +1 -1
- package/dist/esm/schemas/design-tokens/website-design-tokens.shared.schemas.d.ts.map +1 -1
- package/dist/esm/schemas/design-tokens/website-design-tokens.shared.schemas.js.map +1 -1
- package/dist/esm/schemas/label-keys/website-consent-label-keys.schemas.d.ts.map +1 -1
- package/dist/esm/schemas/label-keys/website-label-key.schemas.d.ts.map +1 -1
- package/dist/esm/schemas/label-keys/website-provider-component-label-keys.schemas.d.ts.map +1 -1
- package/dist/esm/schemas/label-keys/website-provider-component-label-keys.schemas.js.map +1 -1
- package/dist/esm/schemas/manifests/website-page-manifest.shared.schemas.d.ts.map +1 -1
- package/dist/esm/schemas/manifests/website-root-config.shared.schemas.d.ts +3 -3
- package/dist/esm/schemas/manifests/website-root-config.shared.schemas.d.ts.map +1 -1
- package/dist/esm/schemas/manifests/website-root-config.shared.schemas.js.map +1 -1
- package/dist/esm/schemas/refs/page-ref.schemas.d.ts.map +1 -1
- package/dist/esm/schemas/refs/section-ref.schemas.d.ts.map +1 -1
- package/dist/esm/schemas/sections/website-section-category.shared.d.ts.map +1 -1
- package/dist/esm/schemas/sections/website-section-definition.shared.schemas.d.ts.map +1 -1
- package/dist/esm/schemas/sections/website-section-definition.shared.schemas.js.map +1 -1
- package/dist/esm/schemas/structured-data/pricing-offer-structured-data.shared.d.ts.map +1 -1
- package/dist/esm/schemas/structured-data/pricing-offer-structured-data.shared.js.map +1 -1
- package/dist/esm/schemas/structured-data/structured-data-reconciliation.shared.d.ts.map +1 -1
- package/dist/esm/schemas/structured-data/structured-data-reconciliation.shared.js.map +1 -1
- package/dist/esm/schemas/structured-data/website-structured-data.shared.schemas.d.ts.map +1 -1
- package/dist/esm/schemas/structured-data/website-structured-data.shared.schemas.js.map +1 -1
- package/dist/esm/schemas/validators/label-pack.validator.d.ts.map +1 -1
- package/package.json +5 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"blog-post-bridge.d.ts","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"blog-post-bridge.d.ts","sourceRoot":"","sources":["../../../../src/astro/blog-post-bridge.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAa,MAAM,OAAO,CAAC;AAEzE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sCAAsC,CAAC;AACnF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sCAAsC,CAAC;AAO9E,OAAO,KAAK,EAAE,mCAAmC,EAAE,MAAM,8CAA8C,CAAC;AACxG,OAAO,KAAK,EAAE,+BAA+B,EAAE,MAAM,+DAA+D,CAAC;AACrH,OAAO,EAAY,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAC/D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AAC3E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,+DAA+D,CAAC;AACzG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iDAAiD,CAAC;AACvF,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,yDAAyD,CAAC;AACvG,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,4CAA4C,CAAC;AAE5E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuMG;AAEH;;;GAGG;AACH,MAAM,WAAW,qCAAqC;IACpD;;;;OAIG;IACH,uBAAuB,CAAC,EAAE,+BAA+B,CAAC;IAC1D;;;;;;OAMG;IACH,wBAAwB,CAAC,EAAE,aAAa,CAAC,mCAAmC,CAAC,CAAC;IAC9E;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAC5D;;;;;OAKG;IACH,UAAU,EAAE,uBAAuB,CAAC;IACpC;;;OAGG;IACH,sBAAsB,CAAC,EAAE,aAAa,CAAC;IACvC;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,uBAAuB,CAAC;CACzC;AAED;;;;GAIG;AACH,MAAM,WAAW,gCAAgC;IAC/C;;;;;OAKG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;;;;OAKG;IACH,cAAc,EAAE,OAAO,CAAC;IACxB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gEAAgE;IAChE,MAAM,EAAE,iBAAiB,CAAC;IAC1B;;;;OAIG;IACH,aAAa,EAAE,iBAAiB,CAAC;IACjC,2DAA2D;IAC3D,SAAS,EAAE,SAAS,CAAC;IACrB,iEAAiE;IACjE,YAAY,EAAE,mBAAmB,CAAC;IAClC;;;;;OAKG;IACH,yBAAyB,EAAE,eAAe,CAAC;IAC3C,2EAA2E;IAC3E,iBAAiB,CAAC,EAAE,sBAAsB,CAAC;IAC3C;0FACsF;IACtF,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAID;;;;;;GAMG;AACH,wBAAgB,iCAAiC,CAC/C,IAAI,EAAE,qCAAqC,GAC1C,iBAAiB,CAAC,gCAAgC,CAAC,CA4ErD;AAED;;;;;;GAMG;AACH,OAAO,EAAE,kBAAkB,EAAE,CAAC;AAC9B,sDAAsD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"blog-post-bridge.js","sourceRoot":"","sources":["../../../src/astro/blog-post-bridge.tsx"],"names":[],"mappings":";AAKA,OAAO,EACL,6BAA6B,GAE9B,MAAM,wCAAwC,CAAC;AAChD,OAAO,EAAE,4BAA4B,EAAE,MAAM,gEAAgE,CAAC;AAG9G,OAAO,EAAE,QAAQ,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAyU/D,MAAM,oBAAoB,GAA4B,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;AAExE;;;;;;GAMG;AACH,MAAM,UAAU,iCAAiC,CAC/C,IAA2C;IAE3C,MAAM,EACJ,aAAa,EACb,UAAU,EACV,aAAa,GAAG,oBAAoB,GACrC,GAAG,IAAI,CAAC;IAET,SAAS,2BAA2B,CAAC,EACnC,IAAI,EACJ,cAAc,EACd,MAAM,EACN,MAAM,EACN,aAAa,EACb,SAAS,EACT,YAAY,EACZ,yBAAyB,EACzB,iBAAiB,EACjB,SAAS,GACwB;QACjC,MAAM,OAAO,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC1B;;;;;;;eAOG;YACH,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;YAC7C,MAAM,KAAK,GACT,SAAS,CAAC,MAAM,IAAI,CAAC;gBACnB,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC;gBACtB,CAAC,CAAC,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,SAAS,CAAC,MAAM,SAAS,CAAC;YAC3E,MAAM,IAAI,KAAK,CACb,uDAAuD,IAAI,KAAK;gBAC9D,sDAAsD,KAAK,IAAI;gBAC/D,2EAA2E;gBAC3E,yCAAyC,CAC5C,CAAC;QACJ,CAAC;QAED,+FAA+F;QAC/F,mFAAmF;QACnF,MAAM,EAAE,wBAAwB,EAAE,yBAAyB,EAAE,GAAG,4BAA4B,CAAC;YAC3F,iBAAiB;YACjB,uBAAuB,EAAE,IAAI,CAAC,uBAAuB;YACrD,wBAAwB,EAAE,IAAI,CAAC,wBAAwB;SACxD,CAAC,CAAC;QACH,MAAM,YAAY,GAA+B;YAC/C,MAAM;YACN,aAAa;YACb,SAAS;YACT,YAAY;YACZ,UAAU;YACV,yBAAyB;YACzB,SAAS;YACT,GAAG,CAAC,IAAI,CAAC,sBAAsB,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,sBAAsB,EAAE,IAAI,CAAC,sBAAsB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7G,wBAAwB;YACxB,yBAAyB;SAC1B,CAAC;QACF,OAAO,CACL,KAAC,6BAA6B,IAAC,KAAK,EAAE,YAAY,YAChD,KAAC,QAAQ,IACP,cAAc,EAAE,cAAc,EAC9B,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,aAAa,YAE5B,KAAC,OAAO,KAAG,GACF,GACmB,CACjC,CAAC;IACJ,CAAC;IACD,2BAA2B,CAAC,WAAW,GAAG,6BAA6B,CAAC;IAExE,OAAO,2BAA2B,CAAC;AACrC,CAAC;AAED;;;;;;GAMG;AACH,OAAO,EAAE,kBAAkB,EAAE,CAAC","sourcesContent":["import type { ComponentType, FunctionComponent, ReactNode } from 'react';\n\nimport type { FrontendProvidersBlock } from '@wildo-ai/saas-models/public-runtime';\nimport type { AvailableLanguage } from '@wildo-ai/saas-models/public-runtime';\n\nimport {\n WebsiteRuntimeContextProvider,\n type WebsiteRuntimeContextValue,\n} from '../core/contexts/WebsiteRuntimeContext';\nimport { useWebsiteProviderRegistries } from '../core/external-providers/provider-component-registry.website';\nimport type { ProviderComponentLoaderContribution } from '@wildo-ai/external-connectors-public/website';\nimport type { GeneratedWebsiteProviderModules } from '../core/external-providers/frontend-provider-registry.website';\nimport { BlogPost, BLOG_POST_PAGE_REF } from '../mdx/BlogPost';\nimport type { WebsiteMdxComponentsMap } from '../mdx/website-mdx-provider';\nimport type { WebsiteDesignTokens } from '../schemas/design-tokens/website-design-tokens.shared.schemas';\nimport type { WebsiteLabelKey } from '../schemas/label-keys/website-label-key.schemas';\nimport type { WebsiteNavigationConfig } from '../schemas/manifests/website-root-config.shared.schemas';\nimport type { LabelPack } from '../schemas/validators/label-pack.validator';\n\n/**\n * @wildo_source:part:start saas.website.blog.bridge facet:layer:astro facet:family:website\n *\n * `createWebsiteBlogPostBridgeIsland(deps)` — factory that returns the\n * React island a consumer's per-locale `*.bridge.tsx` module exports for\n * mounting blog post pages. **Sibling** of `createWebsitePageBridgeIsland`\n * but adapted to the dynamic-routing constraint of the marketing-blog\n * adapter (Phase 7, D-6 = Option A).\n *\n * # Why blog posts cannot reuse the page bridge factory\n *\n * Three structural differences between marketing pages and blog posts\n * forced this dedicated factory shape:\n *\n * 1. **Per-slug body resolution.** A marketing page (`landing`,\n * `pricing`, etc.) has ONE `PageComponent` known at bridge-module\n * load time — the page bridge closes over it. A blog post's body\n * is the compiled MDX module, which varies per slug. Closing the\n * MDX body inside the factory at module load time would force one\n * bridge file per post (defeats Q16=B \"one MDX file per post\").\n * Instead, the factory closes over a `slugToMdxBody: Record<string,\n * ComponentType>` map — typically built from\n * `import.meta.glob('./content/blog/<locale>/*.mdx', { eager: true })`\n * on the consumer side — and the island accepts the active `slug`\n * as a primitive (JSON-serializable) prop.\n *\n * 2. **Layout already wrapped by `<BlogPost>`.** The page bridge wraps\n * its `PageComponent` in `<WebsitePageLayout pageRef={…}>`. The\n * `<BlogPost>` renderer ALREADY wraps its tree in\n * `<WebsitePageLayout pageRef={BLOG_POST_PAGE_REF}>` (the framework\n * reserves the page ref for the entire blog surface — see\n * `BLOG_POST_PAGE_REF` JSDoc in `BlogPost.tsx`). Reusing the page\n * bridge would double-wrap the layout. This factory instead\n * provides the runtime context and yields directly to `<BlogPost>`,\n * which owns the layout boundary itself.\n *\n * 3. **Astro's React-children context-propagation gap.** Astro\n * renders React component children INDEPENDENTLY of the parent\n * component when the boundary is `.astro` ↔ `.tsx`, even on the\n * server side. This means\n * `<BlogPost client:load><Content /></BlogPost>` written in an\n * `.astro` file would NOT propagate the `<WebsiteMdxProvider>`\n * context to the React components embedded inside `<Content />`\n * (the embedded `<CTASection />` would render WITHOUT the engine's\n * MDX provider scope; the `mdxComponents` map would be ignored).\n * Confirmed via the Astro docs, `@astrojs/react` integration\n * readme, and the documented \"Bridging two JSX runtimes\" gotcha.\n *\n * The fix: the entire blog post tree (BlogPost + MDX body +\n * embedded section components) lives inside ONE React island. The\n * MDX module is imported as a React component via Astro's\n * `import.meta.glob`, and the bridge renders it as JSX children\n * of `<BlogPost>` from INSIDE the island — no `.astro` ↔ `.tsx`\n * boundary is crossed during the body render.\n *\n * # Why hydration is required (vs. pure SSR)\n *\n * The `WebsiteRuntimeContextProvider` + `<BlogPost>` tree could in\n * theory render on the server only (no `client:*` directive). But:\n *\n * - Embedded sections (`<CTASection />`) typically have their own\n * interactivity (analytics handlers, A/B variant flippers, etc.)\n * that need React event listeners to attach client-side.\n * - The framework's `useWebsiteLabel(...)` hook chain assumes a live\n * React render tree — a pure-SSR tree would freeze label resolution\n * at build time, but any future client-driven label override (e.g.\n * a debug-mode label inspector) would silently break.\n *\n * The factory therefore returns an island that the consumer's `.astro`\n * mounts with `client:load` (or `client:idle` for posts known to be\n * non-interactive). The hydration cost is bounded by the per-locale MDX\n * glob — see \"Bundle scaling cliff\" below.\n *\n * # Bundle scaling cliff (deferred trade-off)\n *\n * `import.meta.glob({ eager: true })` materializes EVERY post's\n * compiled JS into the per-locale React-island bundle (~5–20 KB per\n * post depending on length and embedded section count). For the\n * Phase 7 dogfood (≤10 posts) this is fine and matches the documented\n * Astro+MDX pattern. Past ~50 posts this becomes a real budget concern.\n * The factory's API is forward-compatible with `{ eager: false }`\n * (lazy loaders): swap the consumer's glob to lazy mode and the\n * `slugToMdxBody[slug]` lookup returns a `() => Promise<{ default:\n * ComponentType }>`; wrap in `React.lazy()` + `<Suspense>` and the\n * factory call site stays unchanged. The migration is a consumer-side\n * change, not an engine API break.\n *\n * # Authoring pattern\n *\n * **Per-locale bridge file** (consumer side, ONE per locale):\n *\n * ```tsx\n * // examples/<app>/website/src/pages-impl/blog-post-en.bridge.tsx\n * import type { ComponentType } from 'react';\n *\n * import { createWebsiteBlogPostBridgeIsland }\n * from '@wildo-ai/saas-website/mdx';\n * import { BLOG_POST_MDX_COMPONENTS }\n * from './blog-post.mdx-components';\n * import { WEBSITE_ROOT_CONFIG } from '../website-root.config';\n *\n * // Static analyzable glob — Vite resolves at build time. The glob\n * // pattern MUST be a single string literal (no concatenation, no\n * // variable interpolation); Vite parses it AST-side. The `as` cast\n * // narrows the unknown record to the per-MDX-file shape Astro's mdx\n * // integration emits (default export = the compiled React component).\n * const rawModules = import.meta.glob('./content/blog/en/*.mdx', {\n * eager: true,\n * }) as Record<string, { default: ComponentType }>;\n *\n * const slugToMdxBody: Record<string, ComponentType> = {};\n * for (const [path, mod] of Object.entries(rawModules)) {\n * // './content/blog/en/why-context.mdx' -> 'why-context'\n * const slug = path.split('/').pop()!.replace(/\\.mdx$/, '');\n * slugToMdxBody[slug] = mod.default;\n * }\n *\n * export const BlogPostEnBridgeIsland =\n * createWebsiteBlogPostBridgeIsland({\n * slugToMdxBody,\n * navigation: WEBSITE_ROOT_CONFIG.navigation,\n * mdxComponents: BLOG_POST_MDX_COMPONENTS,\n * });\n * ```\n *\n * **Per-locale `[slug].astro` shell** (consumer side):\n *\n * ```astro\n * ---\n * import { getCollection } from 'astro:content';\n * import {\n * loadWebsitePageRuntime,\n * renderBlogPostHeadHtml,\n * renderWebsiteDesignTokensCss,\n * } from '@wildo-ai/saas-website/astro';\n * import { parseBlogPostFrontmatter } from '@wildo-ai/saas-website/mdx';\n * import { BlogPostEnBridgeIsland } from '@/pages-impl/blog-post-en.bridge';\n * import { BLOG_POST_PAGE_MANIFEST } from '@/pages-impl/blog-post.page';\n * import { WEBSITE_SITE_CONTEXT } from '@/website-site-context';\n *\n * export async function getStaticPaths() {\n * const posts = await getCollection('blog');\n * const defaultLocale = WEBSITE_SITE_CONTEXT.websiteConfig.defaultLocale;\n * return posts\n * .filter((p) => p.data.locale === defaultLocale)\n * .map((p) => ({ params: { slug: p.data.slug }, props: { entry: p } }));\n * }\n * const { entry } = Astro.props;\n *\n * const runtime = loadWebsitePageRuntime({\n * manifest: BLOG_POST_PAGE_MANIFEST,\n * siteContext: WEBSITE_SITE_CONTEXT,\n * labelPacksDir: './src/i18n',\n * astroLocale: Astro.currentLocale,\n * });\n * const frontmatter = parseBlogPostFrontmatter(entry.data, { source: entry.id });\n * const headHtml = renderBlogPostHeadHtml({\n * siteContext: WEBSITE_SITE_CONTEXT,\n * locale: runtime.locale,\n * routePath: `/blog/${frontmatter.slug}`,\n * frontmatter,\n * });\n * const designTokensCss = renderWebsiteDesignTokensCss(\n * WEBSITE_SITE_CONTEXT.rootConfig.designTokens,\n * );\n * ---\n * <!doctype html>\n * <html lang={runtime.locale}>\n * <head>\n * <meta charset=\"UTF-8\" />\n * <meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\" />\n * <Fragment set:html={headHtml} />\n * <style set:html={designTokensCss}></style>\n * </head>\n * <body>\n * <BlogPostEnBridgeIsland\n * client:load\n * slug={frontmatter.slug}\n * rawFrontmatter={entry.data}\n * source={entry.id}\n * locale={runtime.locale}\n * defaultLocale={runtime.defaultLocale}\n * labelPack={runtime.labelPack}\n * designTokens={WEBSITE_SITE_CONTEXT.rootConfig.designTokens}\n * skipToMainContentLabelKey={\n * WEBSITE_SITE_CONTEXT.rootConfig.skipToMainContentLabelKey\n * }\n * />\n * </body>\n * </html>\n * ```\n *\n * # Boundary\n *\n * Strictly client-bundle-safe (re-exported from `mdx.ts` — NOT from\n * `astro-island.ts`, because this module hard-imports the MDX adapter\n * whose `@mdx-js/react` peer is optional; walked by the\n * bundle-isolation test). MUST stay clear of `node:*` and SSR-tier\n * specifiers.\n */\n\n/**\n * Module-load-time dependencies — closed over inside the returned\n * island. NEVER cross the hydration boundary as serialized props.\n */\nexport interface CreateWebsiteBlogPostBridgeIslandDeps {\n /**\n * Browser CODE imported by the consumer bridge, closed over at module load.\n * Never pass executable modules through Astro island props: those are serialized.\n * Config sync emits a stable map, including an empty map when no providers are declared.\n */\n frontendProviderModules?: GeneratedWebsiteProviderModules;\n /**\n * Loaders for provider components an ENGINE provider module declares but cannot import (#1703) —\n * the website's facet-library door, the equivalent of the application's\n * `contributeProviderComponentLoaders`. A provider PACKAGE that ships its own component needs\n * nothing here: its module's `components[capability].load` is used. A DEPS entry because a loader\n * is a function, and island props are serialized.\n */\n providerComponentLoaders?: ReadonlyArray<ProviderComponentLoaderContribution>;\n /**\n * Per-slug map of compiled MDX bodies (each entry is a React\n * component — the `.default` export of the MDX module). Typically\n * built from `import.meta.glob('./content/blog/<locale>/*.mdx',\n * { eager: true })` with the path-key normalized to the slug. Closed\n * over at module-load time so the entire MDX body graph for the\n * locale lands inside ONE React island bundle (D-6 Option A) — this\n * is the structural fix for Astro's React-children context-propagation\n * gap (see file preamble).\n *\n * The map is read by `slugToMdxBody[props.slug]` at island render\n * time. A missing slug throws a clear framework-owned error (rather than\n * rendering an empty body silently); the consumer's `getStaticPaths`\n * is responsible for not emitting routes for slugs absent from the\n * map.\n *\n * **Why `ComponentType<any>` and not a stricter generic**: matches\n * the upstream `@mdx-js/react` convention (`MDXContent` from\n * `mdx/types` is parameterized over an open `MDXProps` shape). MDX\n * compilation hands the body a compiler-managed props object that\n * the type system cannot narrow at the framework boundary; using\n * `<any>` here keeps the consumer's\n * `import.meta.glob(...) as Record<string, { default: ComponentType }>`\n * cast assignable without an additional wrap/unwrap. Same posture\n * as `WebsiteMdxComponentsMap` (intentional symmetry).\n */\n slugToMdxBody: Readonly<Record<string, ComponentType<any>>>;\n /**\n * Site-wide navigation slot bindings (header / footer components).\n * Closed over here for the same reason as the page bridge: both\n * fields are `ComponentType` refs that cannot survive Astro's\n * serialization boundary.\n */\n navigation: WebsiteNavigationConfig;\n /**\n * Optional replacement for the framework's visitor consent panel (#534). Closed over for the same\n * reason as `navigation`. See `WebsiteRuntimeContextValue.consentBannerComponent`.\n */\n consentBannerComponent?: ComponentType;\n /**\n * MDX-tag → React-component map forwarded into `<BlogPost>` and\n * scoped via `<WebsiteMdxProvider>`. Typically a small object\n * literal exporting the consumer-authored business sections that\n * blog posts can embed (`<CTASection />`, etc.). Closed over here\n * (rather than passed as an island prop) because the values are\n * `ComponentType` refs.\n */\n mdxComponents?: WebsiteMdxComponentsMap;\n}\n\n/**\n * Hydration-boundary props — what the consumer's `.astro` page passes\n * to `<BlogPostEnBridgeIsland client:load ...{primitiveProps}/>`.\n * Every field is JSON-serializable.\n */\nexport interface WebsiteBlogPostBridgeIslandProps {\n /**\n * Slug of the post being rendered. Used by the island to look up\n * the matching MDX body in `deps.slugToMdxBody`. Carried as a plain\n * string (not the branded `BlogPostSlug`) because Astro's\n * serialization boundary unwraps brands.\n */\n slug: string;\n /**\n * Raw frontmatter object Astro hands the page from `entry.data`.\n * Forwarded straight to `<BlogPost>` for strict Zod-4 validation.\n * The bridge does NOT re-validate (single validation surface, owned\n * by the renderer).\n */\n rawFrontmatter: unknown;\n /**\n * Diagnostic source identifier echoed into validation errors —\n * typically `entry.id` from Astro's content collection. Optional\n * but recommended for actionable error messages.\n */\n source?: string;\n /** The locale Astro resolved (with default-locale fallback). */\n locale: AvailableLanguage;\n /**\n * The site's default locale. Required for `WebsiteInternalLink`\n * locale-prefix logic (mirrors the page bridge's `defaultLocale`\n * island prop).\n */\n defaultLocale: AvailableLanguage;\n /** Already-loaded + already-shape-validated label pack. */\n labelPack: LabelPack;\n /** Design-token bundle from `WebsiteRootConfig.designTokens`. */\n designTokens: WebsiteDesignTokens;\n /**\n * Fully-composed label key the framework reads to render the\n * \"Skip to main content\" affordance. Mirrors\n * `WebsiteRootConfig.skipToMainContentLabelKey`. Carried as a\n * primitive island prop for the same reason as the page bridge.\n */\n skipToMainContentLabelKey: WebsiteLabelKey;\n /** Optional website-facing public providers materialized at build time. */\n frontendProviders?: FrontendProvidersBlock;\n /** Product origin for navigation and call-to-action components inside the blog island.\n * Supplied by the Astro page from websiteConfig, just as on ordinary page bridges. */\n appOrigin?: string;\n}\n\nconst EMPTY_MDX_COMPONENTS: WebsiteMdxComponentsMap = Object.freeze({});\n\n/**\n * Returns a FUNCTION component, not the wider `ComponentType` union. The island IS a function\n * component — Astro hydrates nothing else here — and the union is not callable, so declaring it\n * cost callers the ability to invoke the island directly (which the bridge suites do, to observe\n * the synchronous throw for an unregistered slug without routing it through React's reconciler\n * error path). `FunctionComponent` is assignable to `ComponentType`, so no consumer narrows.\n */\nexport function createWebsiteBlogPostBridgeIsland(\n deps: CreateWebsiteBlogPostBridgeIslandDeps,\n): FunctionComponent<WebsiteBlogPostBridgeIslandProps> {\n const {\n slugToMdxBody,\n navigation,\n mdxComponents = EMPTY_MDX_COMPONENTS,\n } = deps;\n\n function WebsiteBlogPostBridgeIsland({\n slug,\n rawFrontmatter,\n source,\n locale,\n defaultLocale,\n labelPack,\n designTokens,\n skipToMainContentLabelKey,\n frontendProviders,\n appOrigin,\n }: WebsiteBlogPostBridgeIslandProps): ReactNode {\n const MdxBody = slugToMdxBody[slug];\n if (MdxBody === undefined) {\n /**\n * Loud failure — the consumer's `getStaticPaths` is supposed to\n * only emit routes for slugs present in the MDX glob. A missing\n * lookup means the glob and the collection are out of sync; the\n * operator needs an actionable error string with the offending\n * slug + the keys that ARE present (truncated to keep the build\n * log readable).\n */\n const knownKeys = Object.keys(slugToMdxBody);\n const known =\n knownKeys.length <= 5\n ? knownKeys.join(', ')\n : `${knownKeys.slice(0, 5).join(', ')}, … (${knownKeys.length} total)`;\n throw new Error(\n `[blog-post-bridge] No MDX body registered for slug=\"${slug}\". ` +\n `The bridge factory's slugToMdxBody map only knows: ${known}. ` +\n `Verify the consumer's import.meta.glob('...content/blog/<locale>/*.mdx') ` +\n `pattern matches the source file naming.`,\n );\n }\n\n // After the missing-slug throw on purpose: that throw ends the render, so it never changes the\n // hook order of a render that completes, and the build-time error stays reachable.\n const { frontendProviderRegistry, providerComponentRegistry } = useWebsiteProviderRegistries({\n frontendProviders,\n frontendProviderModules: deps.frontendProviderModules,\n providerComponentLoaders: deps.providerComponentLoaders,\n });\n const runtimeValue: WebsiteRuntimeContextValue = {\n locale,\n defaultLocale,\n labelPack,\n designTokens,\n navigation,\n skipToMainContentLabelKey,\n appOrigin,\n ...(deps.consentBannerComponent !== undefined ? { consentBannerComponent: deps.consentBannerComponent } : {}),\n frontendProviderRegistry,\n providerComponentRegistry,\n };\n return (\n <WebsiteRuntimeContextProvider value={runtimeValue}>\n <BlogPost\n rawFrontmatter={rawFrontmatter}\n source={source}\n mdxComponents={mdxComponents}\n >\n <MdxBody />\n </BlogPost>\n </WebsiteRuntimeContextProvider>\n );\n }\n WebsiteBlogPostBridgeIsland.displayName = 'WebsiteBlogPostBridgeIsland';\n\n return WebsiteBlogPostBridgeIsland;\n}\n\n/**\n * Re-export of the framework-reserved blog-post page ref. Consumers\n * authoring `BLOG_POST_PAGE_MANIFEST` need this constant to satisfy\n * the manifest's `ref` field; re-exporting it from the bridge module\n * means the consumer's per-locale bridge file can grab both symbols\n * from the one `@wildo-ai/saas-website/mdx` import it already has.\n */\nexport { BLOG_POST_PAGE_REF };\n/** @wildo_source:part:end saas.website.blog.bridge */\n"]}
|
|
1
|
+
{"version":3,"file":"blog-post-bridge.js","sourceRoot":"","sources":["../../../../src/astro/blog-post-bridge.tsx"],"names":[],"mappings":";AAKA,OAAO,EACL,6BAA6B,GAE9B,MAAM,wCAAwC,CAAC;AAChD,OAAO,EAAE,4BAA4B,EAAE,MAAM,gEAAgE,CAAC;AAG9G,OAAO,EAAE,QAAQ,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAyU/D,MAAM,oBAAoB,GAA4B,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;AAExE;;;;;;GAMG;AACH,MAAM,UAAU,iCAAiC,CAC/C,IAA2C;IAE3C,MAAM,EACJ,aAAa,EACb,UAAU,EACV,aAAa,GAAG,oBAAoB,GACrC,GAAG,IAAI,CAAC;IAET,SAAS,2BAA2B,CAAC,EACnC,IAAI,EACJ,cAAc,EACd,MAAM,EACN,MAAM,EACN,aAAa,EACb,SAAS,EACT,YAAY,EACZ,yBAAyB,EACzB,iBAAiB,EACjB,SAAS,GACwB;QACjC,MAAM,OAAO,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC1B;;;;;;;eAOG;YACH,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;YAC7C,MAAM,KAAK,GACT,SAAS,CAAC,MAAM,IAAI,CAAC;gBACnB,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC;gBACtB,CAAC,CAAC,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,SAAS,CAAC,MAAM,SAAS,CAAC;YAC3E,MAAM,IAAI,KAAK,CACb,uDAAuD,IAAI,KAAK;gBAC9D,sDAAsD,KAAK,IAAI;gBAC/D,2EAA2E;gBAC3E,yCAAyC,CAC5C,CAAC;QACJ,CAAC;QAED,+FAA+F;QAC/F,mFAAmF;QACnF,MAAM,EAAE,wBAAwB,EAAE,yBAAyB,EAAE,GAAG,4BAA4B,CAAC;YAC3F,iBAAiB;YACjB,uBAAuB,EAAE,IAAI,CAAC,uBAAuB;YACrD,wBAAwB,EAAE,IAAI,CAAC,wBAAwB;SACxD,CAAC,CAAC;QACH,MAAM,YAAY,GAA+B;YAC/C,MAAM;YACN,aAAa;YACb,SAAS;YACT,YAAY;YACZ,UAAU;YACV,yBAAyB;YACzB,SAAS;YACT,GAAG,CAAC,IAAI,CAAC,sBAAsB,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,sBAAsB,EAAE,IAAI,CAAC,sBAAsB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7G,wBAAwB;YACxB,yBAAyB;SAC1B,CAAC;QACF,OAAO,CACL,KAAC,6BAA6B,IAAC,KAAK,EAAE,YAAY,YAChD,KAAC,QAAQ,IACP,cAAc,EAAE,cAAc,EAC9B,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,aAAa,YAE5B,KAAC,OAAO,KAAG,GACF,GACmB,CACjC,CAAC;IACJ,CAAC;IACD,2BAA2B,CAAC,WAAW,GAAG,6BAA6B,CAAC;IAExE,OAAO,2BAA2B,CAAC;AACrC,CAAC;AAED;;;;;;GAMG;AACH,OAAO,EAAE,kBAAkB,EAAE,CAAC","sourcesContent":["import type { ComponentType, FunctionComponent, ReactNode } from 'react';\n\nimport type { FrontendProvidersBlock } from '@wildo-ai/saas-models/public-runtime';\nimport type { AvailableLanguage } from '@wildo-ai/saas-models/public-runtime';\n\nimport {\n WebsiteRuntimeContextProvider,\n type WebsiteRuntimeContextValue,\n} from '../core/contexts/WebsiteRuntimeContext';\nimport { useWebsiteProviderRegistries } from '../core/external-providers/provider-component-registry.website';\nimport type { ProviderComponentLoaderContribution } from '@wildo-ai/external-connectors-public/website';\nimport type { GeneratedWebsiteProviderModules } from '../core/external-providers/frontend-provider-registry.website';\nimport { BlogPost, BLOG_POST_PAGE_REF } from '../mdx/BlogPost';\nimport type { WebsiteMdxComponentsMap } from '../mdx/website-mdx-provider';\nimport type { WebsiteDesignTokens } from '../schemas/design-tokens/website-design-tokens.shared.schemas';\nimport type { WebsiteLabelKey } from '../schemas/label-keys/website-label-key.schemas';\nimport type { WebsiteNavigationConfig } from '../schemas/manifests/website-root-config.shared.schemas';\nimport type { LabelPack } from '../schemas/validators/label-pack.validator';\n\n/**\n * @wildo_source:part:start saas.website.blog.bridge facet:layer:astro facet:family:website\n *\n * `createWebsiteBlogPostBridgeIsland(deps)` — factory that returns the\n * React island a consumer's per-locale `*.bridge.tsx` module exports for\n * mounting blog post pages. **Sibling** of `createWebsitePageBridgeIsland`\n * but adapted to the dynamic-routing constraint of the marketing-blog\n * adapter (Phase 7, D-6 = Option A).\n *\n * # Why blog posts cannot reuse the page bridge factory\n *\n * Three structural differences between marketing pages and blog posts\n * forced this dedicated factory shape:\n *\n * 1. **Per-slug body resolution.** A marketing page (`landing`,\n * `pricing`, etc.) has ONE `PageComponent` known at bridge-module\n * load time — the page bridge closes over it. A blog post's body\n * is the compiled MDX module, which varies per slug. Closing the\n * MDX body inside the factory at module load time would force one\n * bridge file per post (defeats Q16=B \"one MDX file per post\").\n * Instead, the factory closes over a `slugToMdxBody: Record<string,\n * ComponentType>` map — typically built from\n * `import.meta.glob('./content/blog/<locale>/*.mdx', { eager: true })`\n * on the consumer side — and the island accepts the active `slug`\n * as a primitive (JSON-serializable) prop.\n *\n * 2. **Layout already wrapped by `<BlogPost>`.** The page bridge wraps\n * its `PageComponent` in `<WebsitePageLayout pageRef={…}>`. The\n * `<BlogPost>` renderer ALREADY wraps its tree in\n * `<WebsitePageLayout pageRef={BLOG_POST_PAGE_REF}>` (the framework\n * reserves the page ref for the entire blog surface — see\n * `BLOG_POST_PAGE_REF` JSDoc in `BlogPost.tsx`). Reusing the page\n * bridge would double-wrap the layout. This factory instead\n * provides the runtime context and yields directly to `<BlogPost>`,\n * which owns the layout boundary itself.\n *\n * 3. **Astro's React-children context-propagation gap.** Astro\n * renders React component children INDEPENDENTLY of the parent\n * component when the boundary is `.astro` ↔ `.tsx`, even on the\n * server side. This means\n * `<BlogPost client:load><Content /></BlogPost>` written in an\n * `.astro` file would NOT propagate the `<WebsiteMdxProvider>`\n * context to the React components embedded inside `<Content />`\n * (the embedded `<CTASection />` would render WITHOUT the engine's\n * MDX provider scope; the `mdxComponents` map would be ignored).\n * Confirmed via the Astro docs, `@astrojs/react` integration\n * readme, and the documented \"Bridging two JSX runtimes\" gotcha.\n *\n * The fix: the entire blog post tree (BlogPost + MDX body +\n * embedded section components) lives inside ONE React island. The\n * MDX module is imported as a React component via Astro's\n * `import.meta.glob`, and the bridge renders it as JSX children\n * of `<BlogPost>` from INSIDE the island — no `.astro` ↔ `.tsx`\n * boundary is crossed during the body render.\n *\n * # Why hydration is required (vs. pure SSR)\n *\n * The `WebsiteRuntimeContextProvider` + `<BlogPost>` tree could in\n * theory render on the server only (no `client:*` directive). But:\n *\n * - Embedded sections (`<CTASection />`) typically have their own\n * interactivity (analytics handlers, A/B variant flippers, etc.)\n * that need React event listeners to attach client-side.\n * - The framework's `useWebsiteLabel(...)` hook chain assumes a live\n * React render tree — a pure-SSR tree would freeze label resolution\n * at build time, but any future client-driven label override (e.g.\n * a debug-mode label inspector) would silently break.\n *\n * The factory therefore returns an island that the consumer's `.astro`\n * mounts with `client:load` (or `client:idle` for posts known to be\n * non-interactive). The hydration cost is bounded by the per-locale MDX\n * glob — see \"Bundle scaling cliff\" below.\n *\n * # Bundle scaling cliff (deferred trade-off)\n *\n * `import.meta.glob({ eager: true })` materializes EVERY post's\n * compiled JS into the per-locale React-island bundle (~5–20 KB per\n * post depending on length and embedded section count). For the\n * Phase 7 dogfood (≤10 posts) this is fine and matches the documented\n * Astro+MDX pattern. Past ~50 posts this becomes a real budget concern.\n * The factory's API is forward-compatible with `{ eager: false }`\n * (lazy loaders): swap the consumer's glob to lazy mode and the\n * `slugToMdxBody[slug]` lookup returns a `() => Promise<{ default:\n * ComponentType }>`; wrap in `React.lazy()` + `<Suspense>` and the\n * factory call site stays unchanged. The migration is a consumer-side\n * change, not an engine API break.\n *\n * # Authoring pattern\n *\n * **Per-locale bridge file** (consumer side, ONE per locale):\n *\n * ```tsx\n * // examples/<app>/website/src/pages-impl/blog-post-en.bridge.tsx\n * import type { ComponentType } from 'react';\n *\n * import { createWebsiteBlogPostBridgeIsland }\n * from '@wildo-ai/saas-website/mdx';\n * import { BLOG_POST_MDX_COMPONENTS }\n * from './blog-post.mdx-components';\n * import { WEBSITE_ROOT_CONFIG } from '../website-root.config';\n *\n * // Static analyzable glob — Vite resolves at build time. The glob\n * // pattern MUST be a single string literal (no concatenation, no\n * // variable interpolation); Vite parses it AST-side. The `as` cast\n * // narrows the unknown record to the per-MDX-file shape Astro's mdx\n * // integration emits (default export = the compiled React component).\n * const rawModules = import.meta.glob('./content/blog/en/*.mdx', {\n * eager: true,\n * }) as Record<string, { default: ComponentType }>;\n *\n * const slugToMdxBody: Record<string, ComponentType> = {};\n * for (const [path, mod] of Object.entries(rawModules)) {\n * // './content/blog/en/why-context.mdx' -> 'why-context'\n * const slug = path.split('/').pop()!.replace(/\\.mdx$/, '');\n * slugToMdxBody[slug] = mod.default;\n * }\n *\n * export const BlogPostEnBridgeIsland =\n * createWebsiteBlogPostBridgeIsland({\n * slugToMdxBody,\n * navigation: WEBSITE_ROOT_CONFIG.navigation,\n * mdxComponents: BLOG_POST_MDX_COMPONENTS,\n * });\n * ```\n *\n * **Per-locale `[slug].astro` shell** (consumer side):\n *\n * ```astro\n * ---\n * import { getCollection } from 'astro:content';\n * import {\n * loadWebsitePageRuntime,\n * renderBlogPostHeadHtml,\n * renderWebsiteDesignTokensCss,\n * } from '@wildo-ai/saas-website/astro';\n * import { parseBlogPostFrontmatter } from '@wildo-ai/saas-website/mdx';\n * import { BlogPostEnBridgeIsland } from '@/pages-impl/blog-post-en.bridge';\n * import { BLOG_POST_PAGE_MANIFEST } from '@/pages-impl/blog-post.page';\n * import { WEBSITE_SITE_CONTEXT } from '@/website-site-context';\n *\n * export async function getStaticPaths() {\n * const posts = await getCollection('blog');\n * const defaultLocale = WEBSITE_SITE_CONTEXT.websiteConfig.defaultLocale;\n * return posts\n * .filter((p) => p.data.locale === defaultLocale)\n * .map((p) => ({ params: { slug: p.data.slug }, props: { entry: p } }));\n * }\n * const { entry } = Astro.props;\n *\n * const runtime = loadWebsitePageRuntime({\n * manifest: BLOG_POST_PAGE_MANIFEST,\n * siteContext: WEBSITE_SITE_CONTEXT,\n * labelPacksDir: './src/i18n',\n * astroLocale: Astro.currentLocale,\n * });\n * const frontmatter = parseBlogPostFrontmatter(entry.data, { source: entry.id });\n * const headHtml = renderBlogPostHeadHtml({\n * siteContext: WEBSITE_SITE_CONTEXT,\n * locale: runtime.locale,\n * routePath: `/blog/${frontmatter.slug}`,\n * frontmatter,\n * });\n * const designTokensCss = renderWebsiteDesignTokensCss(\n * WEBSITE_SITE_CONTEXT.rootConfig.designTokens,\n * );\n * ---\n * <!doctype html>\n * <html lang={runtime.locale}>\n * <head>\n * <meta charset=\"UTF-8\" />\n * <meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\" />\n * <Fragment set:html={headHtml} />\n * <style set:html={designTokensCss}></style>\n * </head>\n * <body>\n * <BlogPostEnBridgeIsland\n * client:load\n * slug={frontmatter.slug}\n * rawFrontmatter={entry.data}\n * source={entry.id}\n * locale={runtime.locale}\n * defaultLocale={runtime.defaultLocale}\n * labelPack={runtime.labelPack}\n * designTokens={WEBSITE_SITE_CONTEXT.rootConfig.designTokens}\n * skipToMainContentLabelKey={\n * WEBSITE_SITE_CONTEXT.rootConfig.skipToMainContentLabelKey\n * }\n * />\n * </body>\n * </html>\n * ```\n *\n * # Boundary\n *\n * Strictly client-bundle-safe (re-exported from `mdx.ts` — NOT from\n * `astro-island.ts`, because this module hard-imports the MDX adapter\n * whose `@mdx-js/react` peer is optional; walked by the\n * bundle-isolation test). MUST stay clear of `node:*` and SSR-tier\n * specifiers.\n */\n\n/**\n * Module-load-time dependencies — closed over inside the returned\n * island. NEVER cross the hydration boundary as serialized props.\n */\nexport interface CreateWebsiteBlogPostBridgeIslandDeps {\n /**\n * Browser CODE imported by the consumer bridge, closed over at module load.\n * Never pass executable modules through Astro island props: those are serialized.\n * Config sync emits a stable map, including an empty map when no providers are declared.\n */\n frontendProviderModules?: GeneratedWebsiteProviderModules;\n /**\n * Loaders for provider components an ENGINE provider module declares but cannot import (#1703) —\n * the website's facet-library door, the equivalent of the application's\n * `contributeProviderComponentLoaders`. A provider PACKAGE that ships its own component needs\n * nothing here: its module's `components[capability].load` is used. A DEPS entry because a loader\n * is a function, and island props are serialized.\n */\n providerComponentLoaders?: ReadonlyArray<ProviderComponentLoaderContribution>;\n /**\n * Per-slug map of compiled MDX bodies (each entry is a React\n * component — the `.default` export of the MDX module). Typically\n * built from `import.meta.glob('./content/blog/<locale>/*.mdx',\n * { eager: true })` with the path-key normalized to the slug. Closed\n * over at module-load time so the entire MDX body graph for the\n * locale lands inside ONE React island bundle (D-6 Option A) — this\n * is the structural fix for Astro's React-children context-propagation\n * gap (see file preamble).\n *\n * The map is read by `slugToMdxBody[props.slug]` at island render\n * time. A missing slug throws a clear framework-owned error (rather than\n * rendering an empty body silently); the consumer's `getStaticPaths`\n * is responsible for not emitting routes for slugs absent from the\n * map.\n *\n * **Why `ComponentType<any>` and not a stricter generic**: matches\n * the upstream `@mdx-js/react` convention (`MDXContent` from\n * `mdx/types` is parameterized over an open `MDXProps` shape). MDX\n * compilation hands the body a compiler-managed props object that\n * the type system cannot narrow at the framework boundary; using\n * `<any>` here keeps the consumer's\n * `import.meta.glob(...) as Record<string, { default: ComponentType }>`\n * cast assignable without an additional wrap/unwrap. Same posture\n * as `WebsiteMdxComponentsMap` (intentional symmetry).\n */\n slugToMdxBody: Readonly<Record<string, ComponentType<any>>>;\n /**\n * Site-wide navigation slot bindings (header / footer components).\n * Closed over here for the same reason as the page bridge: both\n * fields are `ComponentType` refs that cannot survive Astro's\n * serialization boundary.\n */\n navigation: WebsiteNavigationConfig;\n /**\n * Optional replacement for the framework's visitor consent panel (#534). Closed over for the same\n * reason as `navigation`. See `WebsiteRuntimeContextValue.consentBannerComponent`.\n */\n consentBannerComponent?: ComponentType;\n /**\n * MDX-tag → React-component map forwarded into `<BlogPost>` and\n * scoped via `<WebsiteMdxProvider>`. Typically a small object\n * literal exporting the consumer-authored business sections that\n * blog posts can embed (`<CTASection />`, etc.). Closed over here\n * (rather than passed as an island prop) because the values are\n * `ComponentType` refs.\n */\n mdxComponents?: WebsiteMdxComponentsMap;\n}\n\n/**\n * Hydration-boundary props — what the consumer's `.astro` page passes\n * to `<BlogPostEnBridgeIsland client:load ...{primitiveProps}/>`.\n * Every field is JSON-serializable.\n */\nexport interface WebsiteBlogPostBridgeIslandProps {\n /**\n * Slug of the post being rendered. Used by the island to look up\n * the matching MDX body in `deps.slugToMdxBody`. Carried as a plain\n * string (not the branded `BlogPostSlug`) because Astro's\n * serialization boundary unwraps brands.\n */\n slug: string;\n /**\n * Raw frontmatter object Astro hands the page from `entry.data`.\n * Forwarded straight to `<BlogPost>` for strict Zod-4 validation.\n * The bridge does NOT re-validate (single validation surface, owned\n * by the renderer).\n */\n rawFrontmatter: unknown;\n /**\n * Diagnostic source identifier echoed into validation errors —\n * typically `entry.id` from Astro's content collection. Optional\n * but recommended for actionable error messages.\n */\n source?: string;\n /** The locale Astro resolved (with default-locale fallback). */\n locale: AvailableLanguage;\n /**\n * The site's default locale. Required for `WebsiteInternalLink`\n * locale-prefix logic (mirrors the page bridge's `defaultLocale`\n * island prop).\n */\n defaultLocale: AvailableLanguage;\n /** Already-loaded + already-shape-validated label pack. */\n labelPack: LabelPack;\n /** Design-token bundle from `WebsiteRootConfig.designTokens`. */\n designTokens: WebsiteDesignTokens;\n /**\n * Fully-composed label key the framework reads to render the\n * \"Skip to main content\" affordance. Mirrors\n * `WebsiteRootConfig.skipToMainContentLabelKey`. Carried as a\n * primitive island prop for the same reason as the page bridge.\n */\n skipToMainContentLabelKey: WebsiteLabelKey;\n /** Optional website-facing public providers materialized at build time. */\n frontendProviders?: FrontendProvidersBlock;\n /** Product origin for navigation and call-to-action components inside the blog island.\n * Supplied by the Astro page from websiteConfig, just as on ordinary page bridges. */\n appOrigin?: string;\n}\n\nconst EMPTY_MDX_COMPONENTS: WebsiteMdxComponentsMap = Object.freeze({});\n\n/**\n * Returns a FUNCTION component, not the wider `ComponentType` union. The island IS a function\n * component — Astro hydrates nothing else here — and the union is not callable, so declaring it\n * cost callers the ability to invoke the island directly (which the bridge suites do, to observe\n * the synchronous throw for an unregistered slug without routing it through React's reconciler\n * error path). `FunctionComponent` is assignable to `ComponentType`, so no consumer narrows.\n */\nexport function createWebsiteBlogPostBridgeIsland(\n deps: CreateWebsiteBlogPostBridgeIslandDeps,\n): FunctionComponent<WebsiteBlogPostBridgeIslandProps> {\n const {\n slugToMdxBody,\n navigation,\n mdxComponents = EMPTY_MDX_COMPONENTS,\n } = deps;\n\n function WebsiteBlogPostBridgeIsland({\n slug,\n rawFrontmatter,\n source,\n locale,\n defaultLocale,\n labelPack,\n designTokens,\n skipToMainContentLabelKey,\n frontendProviders,\n appOrigin,\n }: WebsiteBlogPostBridgeIslandProps): ReactNode {\n const MdxBody = slugToMdxBody[slug];\n if (MdxBody === undefined) {\n /**\n * Loud failure — the consumer's `getStaticPaths` is supposed to\n * only emit routes for slugs present in the MDX glob. A missing\n * lookup means the glob and the collection are out of sync; the\n * operator needs an actionable error string with the offending\n * slug + the keys that ARE present (truncated to keep the build\n * log readable).\n */\n const knownKeys = Object.keys(slugToMdxBody);\n const known =\n knownKeys.length <= 5\n ? knownKeys.join(', ')\n : `${knownKeys.slice(0, 5).join(', ')}, … (${knownKeys.length} total)`;\n throw new Error(\n `[blog-post-bridge] No MDX body registered for slug=\"${slug}\". ` +\n `The bridge factory's slugToMdxBody map only knows: ${known}. ` +\n `Verify the consumer's import.meta.glob('...content/blog/<locale>/*.mdx') ` +\n `pattern matches the source file naming.`,\n );\n }\n\n // After the missing-slug throw on purpose: that throw ends the render, so it never changes the\n // hook order of a render that completes, and the build-time error stays reachable.\n const { frontendProviderRegistry, providerComponentRegistry } = useWebsiteProviderRegistries({\n frontendProviders,\n frontendProviderModules: deps.frontendProviderModules,\n providerComponentLoaders: deps.providerComponentLoaders,\n });\n const runtimeValue: WebsiteRuntimeContextValue = {\n locale,\n defaultLocale,\n labelPack,\n designTokens,\n navigation,\n skipToMainContentLabelKey,\n appOrigin,\n ...(deps.consentBannerComponent !== undefined ? { consentBannerComponent: deps.consentBannerComponent } : {}),\n frontendProviderRegistry,\n providerComponentRegistry,\n };\n return (\n <WebsiteRuntimeContextProvider value={runtimeValue}>\n <BlogPost\n rawFrontmatter={rawFrontmatter}\n source={source}\n mdxComponents={mdxComponents}\n >\n <MdxBody />\n </BlogPost>\n </WebsiteRuntimeContextProvider>\n );\n }\n WebsiteBlogPostBridgeIsland.displayName = 'WebsiteBlogPostBridgeIsland';\n\n return WebsiteBlogPostBridgeIsland;\n}\n\n/**\n * Re-export of the framework-reserved blog-post page ref. Consumers\n * authoring `BLOG_POST_PAGE_MANIFEST` need this constant to satisfy\n * the manifest's `ref` field; re-exporting it from the bridge module\n * means the consumer's per-locale bridge file can grab both symbols\n * from the one `@wildo-ai/saas-website/mdx` import it already has.\n */\nexport { BLOG_POST_PAGE_REF };\n/** @wildo_source:part:end saas.website.blog.bridge */\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"blog-post-head.renderer.d.ts","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"blog-post-head.renderer.d.ts","sourceRoot":"","sources":["../../../../src/astro/blog-post-head.renderer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sCAAsC,CAAC;AAG9E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sDAAsD,CAAC;AAChG,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,mEAAmE,CAAC;AAItH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAEjE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8DG;AAEH,MAAM,WAAW,6BAA6B;IAC5C;;;;;;;;;OASG;IACH,WAAW,EAAE,kBAAkB,CAAC;IAChC;;;;OAIG;IACH,MAAM,EAAE,iBAAiB,CAAC;IAC1B;;;;;;OAMG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,WAAW,EAAE,mBAAmB,CAAC;IACjC;;;;;;;;;;;;;;;OAeG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,yCAAyC;IACxD,WAAW,EAAE,mBAAmB,CAAC;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,iBAAiB,CAAC;CAC3B;AAED,wBAAgB,kCAAkC,CAChD,IAAI,EAAE,yCAAyC,GAC9C,4BAA4B,CAuB9B;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,6BAA6B,GAAG,MAAM,CAkIlF;AACD,6DAA6D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bridge-runtime.d.ts","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"bridge-runtime.d.ts","sourceRoot":"","sources":["../../../../src/astro/bridge-runtime.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAa,MAAM,OAAO,CAAC;AAEzE,OAAO,KAAK,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAC;AACzG,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sCAAsC,CAAC;AAO9E,OAAO,KAAK,EAAE,mCAAmC,EAAE,MAAM,8CAA8C,CAAC;AACxG,OAAO,KAAK,EAAE,+BAA+B,EAAE,MAAM,+DAA+D,CAAC;AAErH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,uCAAuC,CAAC;AACpF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,+DAA+D,CAAC;AACzG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iDAAiD,CAAC;AACvF,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,yDAAyD,CAAC;AACvG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AACvE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,4CAA4C,CAAC;AAE5E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4HG;AAEH;;;GAGG;AACH,MAAM,WAAW,iCAAiC;IAChD;;;;OAIG;IACH,uBAAuB,CAAC,EAAE,+BAA+B,CAAC;IAC1D;;;;;;OAMG;IACH,wBAAwB,CAAC,EAAE,aAAa,CAAC,mCAAmC,CAAC,CAAC;IAC9E;;;;;;;;;;;;;;OAcG;IACH,aAAa,EAAE,aAAa,CAAC,GAAG,CAAC,CAAC;IAClC;;;;;;;;;;;;;;;;OAgBG;IACH,sBAAsB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,sBAAsB,CAAC;IACvE;;;;OAIG;IACH,OAAO,EAAE,cAAc,CAAC;IACxB;;;;;OAKG;IACH,UAAU,EAAE,uBAAuB,CAAC;IACpC;;;OAGG;IACH,sBAAsB,CAAC,EAAE,aAAa,CAAC;CACxC;AAED;;;;GAIG;AACH,MAAM,WAAW,4BAA4B;IAC3C,gEAAgE;IAChE,MAAM,EAAE,iBAAiB,CAAC;IAC1B;;;;;;;;;OASG;IACH,aAAa,EAAE,iBAAiB,CAAC;IACjC,2DAA2D;IAC3D,SAAS,EAAE,SAAS,CAAC;IACrB,iEAAiE;IACjE,YAAY,EAAE,mBAAmB,CAAC;IAClC;;;;;;;;;;OAUG;IACH,yBAAyB,EAAE,eAAe,CAAC;IAC3C,2EAA2E;IAC3E,iBAAiB,CAAC,EAAE,sBAAsB,CAAC;IAC3C;;;;;;;;;OASG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;;;;;;;;OAcG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,oBAAoB,CAAC;CACvC;AAED;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAC3C,IAAI,EAAE,iCAAiC,GACtC,iBAAiB,CAAC,4BAA4B,CAAC,CAuDjD;AACD,uDAAuD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bridge-runtime.js","sourceRoot":"","sources":["../../../src/astro/bridge-runtime.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAMhC,OAAO,EACL,6BAA6B,GAE9B,MAAM,wCAAwC,CAAC;AAChD,OAAO,EAAE,4BAA4B,EAAE,MAAM,gEAAgE,CAAC;AAG9G,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AAwRtE;;;;;;GAMG;AACH,MAAM,UAAU,6BAA6B,CAC3C,IAAuC;IAEvC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC;IAEpD,SAAS,uBAAuB,CAAC,EAC/B,MAAM,EACN,aAAa,EACb,SAAS,EACT,YAAY,EACZ,yBAAyB,EACzB,iBAAiB,EACjB,SAAS,EACT,SAAS,EACT,cAAc,GACe;QAC7B;;;;WAIG;QACH,MAAM,eAAe,GAAG,OAAO,CAC7B,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,sBAAsB,KAAK,SAAS,IAAI,SAAS,KAAK,SAAS;YACzE,CAAC,CAAC,IAAI,CAAC,sBAAsB,CAAC,SAAS,CAAC;YACxC,CAAC,CAAC,SAAS,CAAC,EACd,CAAC,SAAS,CAAC,CACZ,CAAC;QACF,MAAM,EAAE,wBAAwB,EAAE,yBAAyB,EAAE,GAAG,4BAA4B,CAAC;YAC3F,iBAAiB;YACjB,uBAAuB,EAAE,IAAI,CAAC,uBAAuB;YACrD,wBAAwB,EAAE,IAAI,CAAC,wBAAwB;SACxD,CAAC,CAAC;QACH,MAAM,YAAY,GAA+B;YAC/C,MAAM;YACN,aAAa;YACb,SAAS;YACT,YAAY;YACZ,UAAU;YACV,yBAAyB;YACzB,wBAAwB;YACxB,yBAAyB;YACzB,GAAG,CAAC,IAAI,CAAC,sBAAsB,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,sBAAsB,EAAE,IAAI,CAAC,sBAAsB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7G,SAAS;YACT,SAAS;YACT,GAAG,CAAC,cAAc,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC5D,CAAC;QACF,OAAO,CACL,KAAC,6BAA6B,IAAC,KAAK,EAAE,YAAY,YAChD,KAAC,iBAAiB,IAAC,OAAO,EAAE,OAAO,KAAM,CAAC,eAAe,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,YACjG,KAAC,aAAa,KAAG,GACC,GACU,CACjC,CAAC;IACJ,CAAC;IACD,uBAAuB,CAAC,WAAW,GAAG,yBAAyB,CAAC;IAEhE,OAAO,uBAAuB,CAAC;AACjC,CAAC","sourcesContent":["import { useMemo } from 'react';\nimport type { ComponentType, FunctionComponent, ReactNode } from 'react';\n\nimport type { FrontendProvidersBlock, PublicPricingCatalog } from '@wildo-ai/saas-models/public-runtime';\nimport type { AvailableLanguage } from '@wildo-ai/saas-models/public-runtime';\n\nimport {\n WebsiteRuntimeContextProvider,\n type WebsiteRuntimeContextValue,\n} from '../core/contexts/WebsiteRuntimeContext';\nimport { useWebsiteProviderRegistries } from '../core/external-providers/provider-component-registry.website';\nimport type { ProviderComponentLoaderContribution } from '@wildo-ai/external-connectors-public/website';\nimport type { GeneratedWebsiteProviderModules } from '../core/external-providers/frontend-provider-registry.website';\nimport { WebsitePageLayout } from '../core/layouts/WebsitePageLayout';\nimport type { WebsiteConsentRecorder } from '../core/consent/WebsiteConsentContext';\nimport type { WebsiteDesignTokens } from '../schemas/design-tokens/website-design-tokens.shared.schemas';\nimport type { WebsiteLabelKey } from '../schemas/label-keys/website-label-key.schemas';\nimport type { WebsiteNavigationConfig } from '../schemas/manifests/website-root-config.shared.schemas';\nimport type { WebsitePageRef } from '../schemas/refs/page-ref.schemas';\nimport type { LabelPack } from '../schemas/validators/label-pack.validator';\n\n/**\n * @wildo_source:part:start saas.website.astro.bridge facet:layer:astro facet:family:website\n *\n * `createWebsitePageBridgeIsland(deps)` — factory that returns the\n * React island a consumer's per-page `.astro` file mounts with a\n * `client:idle` (or `client:load`) directive.\n *\n * **The hard architectural constraint this design solves**: Astro\n * serializes React-island props via `devalue`-style JSON to transfer\n * them across the SSR → client-hydration boundary. Function values —\n * including `ComponentType` references — are NOT serializable. Any\n * design that tried to pass `pageComponent`, `headerComponent`, or\n * `footerComponent` AS island props would render correctly server-side\n * (the function ref is in-process during SSR) and then mismatch on\n * first hydration (the client receives `undefined`/broken placeholders\n * for the function-typed fields). The Vitest suite would not catch\n * this because Vitest renders React in a single process — there is no\n * serialization boundary in unit tests.\n *\n * **The factory pattern fixes the constraint by closing over** the\n * `pageComponent`, `pageRef`, and `navigation` (header / footer\n * component refs) at MODULE LOAD TIME on the consumer side. The\n * returned island accepts only PRIMITIVE-VALUED props (`locale`,\n * `labelPack`, `designTokens`) — all JSON-serializable, all safe to\n * cross the hydration boundary. The component refs never appear in\n * the prop signature; they live in the lexical closure that the\n * client-side bundle includes naturally as part of the island's\n * compiled module.\n *\n * **Authoring pattern (per-page bridge module)**:\n *\n * ```tsx\n * // consumer's src/pages-impl/landing.bridge.tsx\n * import { createWebsitePageBridgeIsland } from '@wildo-ai/saas-website/astro-island';\n * import { LandingPage, LANDING_PAGE_MANIFEST } from './landing.page';\n * import { WEBSITE_ROOT_CONFIG } from '../website-root.config';\n *\n * export const LandingBridgeIsland = createWebsitePageBridgeIsland({\n * PageComponent: LandingPage,\n * pageRef: LANDING_PAGE_MANIFEST.ref,\n * navigation: WEBSITE_ROOT_CONFIG.navigation,\n * });\n * ```\n *\n * ```astro\n * ---\n * // consumer's src/pages/index.astro\n * import {\n * loadWebsitePageRuntime,\n * renderWebsitePageHeadHtml,\n * renderWebsiteDesignTokensCss,\n * } from '@wildo-ai/saas-website/astro';\n * import { LandingBridgeIsland } from '../pages-impl/landing.bridge';\n * import { LANDING_PAGE_MANIFEST } from '../pages-impl/landing.page';\n * import { WEBSITE_SITE_CONTEXT } from '../website-site-context';\n *\n * const runtime = loadWebsitePageRuntime({\n * manifest: LANDING_PAGE_MANIFEST,\n * siteContext: WEBSITE_SITE_CONTEXT,\n * labelPacksDir: './src/i18n',\n * astroLocale: Astro.currentLocale,\n * });\n * const headHtml = renderWebsitePageHeadHtml({\n * manifest: LANDING_PAGE_MANIFEST,\n * siteContext: WEBSITE_SITE_CONTEXT,\n * locale: runtime.locale,\n * labelPack: runtime.labelPack,\n * });\n * const designTokensCss = renderWebsiteDesignTokensCss(\n * WEBSITE_SITE_CONTEXT.rootConfig.designTokens,\n * );\n * ---\n * <!doctype html>\n * <html lang={runtime.locale}>\n * <head>\n * <Fragment set:html={headHtml} />\n * <style set:html={designTokensCss}></style>\n * </head>\n * <body>\n * <LandingBridgeIsland\n * client:idle\n * locale={runtime.locale}\n * defaultLocale={runtime.defaultLocale}\n * appOrigin={WEBSITE_SITE_CONTEXT.websiteConfig.appOrigin}\n * apiOrigin={WEBSITE_SITE_CONTEXT.websiteConfig.apiOrigin}\n * labelPack={runtime.labelPack}\n * designTokens={WEBSITE_SITE_CONTEXT.rootConfig.designTokens}\n * />\n * </body>\n * </html>\n * ```\n *\n * **Why a per-page bridge file** (rather than constructing the island\n * inline in the consumer's `.astro` frontmatter):\n *\n * Astro's `client:*` directive requires the JSX tag to reference a\n * statically-resolvable module export at compile time — that is how\n * Astro knows which module to bundle for client-side hydration. A\n * factory call inside `.astro` frontmatter (`const Island =\n * createWebsitePageBridgeIsland({...}); <Island client:idle/>`) does\n * NOT round-trip through Astro's module-graph analyzer: Astro would\n * see a runtime-constructed component reference and fail to bundle\n * the closure for the client. The per-page bridge file gives Astro a\n * static export to bind the directive to. The cost is one ~6-line\n * file per page; the win is the only architecture that is safe\n * against the H-2 hydration-mismatch failure mode AND uses Astro's\n * native bundling pipeline.\n *\n * **Why `ComponentType` refs in `Deps` and not in `Props`**: this is\n * the entire point of the redesign. `Deps` is consumed at module load\n * time on the consumer side — the closure traps the function refs\n * inside the returned component. `Props` is consumed at island-mount\n * time and crosses Astro's serialization boundary. Keeping the two\n * concerns lexically separate makes the boundary visible in the type\n * signature.\n *\n * **Boundary** (D12 client-bundle discipline): this module ships into\n * the React-island bundle. It MUST stay clear of `node:*` imports,\n * fs / path helpers, and the SSR-tier specifiers\n * (`axios`, `jsonwebtoken`, …). The bundle-isolation test catches\n * regressions both globally (closure-wide allow-list) AND with the\n * targeted Phase-5 negative test that asserts `node:fs` / `node:path`\n * reach this island closure ONLY through `astro.ts`, never through\n * `index.ts` or `companion-exports.ts`.\n */\n\n/**\n * Module-load-time dependencies — closed over inside the returned\n * island. NEVER cross the hydration boundary as serialized props.\n */\nexport interface CreateWebsitePageBridgeIslandDeps {\n /**\n * Browser CODE imported by the consumer bridge, closed over at module load.\n * Never pass executable modules through Astro island props: those are serialized.\n * Config sync emits a stable map, including an empty map when no providers are declared.\n */\n frontendProviderModules?: GeneratedWebsiteProviderModules;\n /**\n * Loaders for provider components an ENGINE provider module declares but cannot import (#1703) —\n * the website's facet-library door, the equivalent of the application's\n * `contributeProviderComponentLoaders`. A provider PACKAGE that ships its own component needs\n * nothing here: its module's `components[capability].load` is used. A DEPS entry because a loader\n * is a function, and island props are serialized.\n */\n providerComponentLoaders?: ReadonlyArray<ProviderComponentLoaderContribution>;\n /**\n * The page's React component. Typically the same value that was\n * passed to `defineWebsitePageManifest({ pageComponent })` — the\n * authoring convention re-imports it here to keep the closure\n * explicit and the per-page bridge file self-contained.\n *\n * Typed as `ComponentType<any>` to mirror\n * `WebsitePageManifest.pageComponent` and dodge React's `ComponentType`\n * contravariance: a narrower `ComponentType<TProps>` returned by\n * `defineWebsitePageManifest<TProps>` would not be assignable to a\n * `ComponentType<unknown>` slot, blocking every per-page bridge\n * call site. The component is invoked with no props at the\n * `<PageComponent />` JSX call site below, so widening here is\n * runtime-safe.\n */\n PageComponent: ComponentType<any>;\n /**\n * Builds the recorder that persists a VISITOR's consent decision server-side, from the island's\n * runtime `apiOrigin` (#1258).\n *\n * A DEPS entry rather than an island prop, for the reason this whole interface exists: it is a\n * function, and island props are serialized. A factory rather than an instance, because the\n * `apiOrigin` it needs is resolved per environment by the `.astro` page and only reaches the island\n * as a prop.\n *\n * The engine cannot build this itself — a client needs the application's own\n * `frontendServiceName`, which the engine has no way to know. The application already constructs\n * one for its capture forms; passing THAT factory here is what makes a visitor's consent decision,\n * their captured lead and their draft note share a single `uuidRef` and transpose together.\n *\n * Optional. Omitted, a decision lives in the visitor's browser and nowhere else — the behaviour\n * before this existed, and correct for a site with no anonymous-session wiring.\n */\n consentRecorderFactory?: (apiOrigin: string) => WebsiteConsentRecorder;\n /**\n * The active page's ref. Carried as a primitive (string-branded)\n * value so the inner `WebsitePageLayout` can register the\n * `WebsitePageContext` without re-resolving it from the manifest.\n */\n pageRef: WebsitePageRef;\n /**\n * Site-wide navigation slot bindings (header / footer components).\n * Closed over here rather than passed as island props because both\n * fields are `ComponentType` refs that cannot survive Astro's\n * serialization boundary.\n */\n navigation: WebsiteNavigationConfig;\n /**\n * Optional replacement for the framework's visitor consent panel (#534). Closed over for the same\n * reason as `navigation`. See `WebsiteRuntimeContextValue.consentBannerComponent`.\n */\n consentBannerComponent?: ComponentType;\n}\n\n/**\n * Hydration-boundary props — what the consumer's `.astro` page passes\n * to `<LandingBridgeIsland client:idle ...{primitiveProps}/>`. Every\n * field is JSON-serializable.\n */\nexport interface WebsitePageBridgeIslandProps {\n /** The locale Astro resolved (with default-locale fallback). */\n locale: AvailableLanguage;\n /**\n * The site's default locale. Carried as an island prop (not closed\n * over in `Deps`) because it is JSON-primitive and consumers\n * already pass `runtime.locale` from the per-locale Astro page\n * frontmatter — passing the matching `runtime.defaultLocale` from\n * the same `loadWebsitePageRuntime(...)` return value keeps the\n * authoring story symmetric. Required for `WebsiteInternalLink`\n * (Phase 6 fix H-β) which needs both locales to compute the\n * correct prefix.\n */\n defaultLocale: AvailableLanguage;\n /** Already-loaded + already-shape-validated label pack. */\n labelPack: LabelPack;\n /** Design-token bundle from `WebsiteRootConfig.designTokens`. */\n designTokens: WebsiteDesignTokens;\n /**\n * Fully-composed label key the framework reads to render the\n * \"Skip to main content\" affordance (Phase 6 third audit fix M-α).\n * Mirrors `WebsiteRootConfig.skipToMainContentLabelKey`. Carried as\n * a JSON-serializable string prop (NOT closed over in `Deps`)\n * because it is a primitive value the consumer already has on the\n * root config in the `.astro` frontmatter — passing it as an island\n * prop keeps the per-page bridge module dependency-free of the\n * site root config (the bridge stays page-local; only the per-page\n * `.astro` page knows about `WEBSITE_ROOT_CONFIG`).\n */\n skipToMainContentLabelKey: WebsiteLabelKey;\n /** Optional website-facing public providers materialized at build time. */\n frontendProviders?: FrontendProvidersBlock;\n /**\n * Optional backend API origin (no trailing slash) forwarded into\n * `WebsiteRuntimeContext.apiOrigin` for anonymous-capture sections\n * (lead / draft-note forms) to construct their\n * `WebsiteAnonymousSessionClient`. A JSON-serializable string, so it\n * crosses the hydration boundary as an island prop; the `.astro` page\n * resolves the per-environment value from\n * `WildoMarketingWebsiteConfig.apiOrigin`. Omitted on pages with no\n * capture section.\n */\n apiOrigin?: string;\n /**\n * The product origin this site's self-serve CTAs link to (\"Start free\",\n * \"Get started\"), forwarded into `WebsiteRuntimeContext.appOrigin` (#493).\n *\n * An island prop for the same reason `apiOrigin` above is one: the `.astro`\n * page resolves it from `WildoMarketingWebsiteConfig.appOrigin`, which is\n * Node-loaded and invisible to a client island. Before this, a site\n * resolved the product origin a SECOND time from a public build-time\n * environment variable — one host authored twice with no mechanical link,\n * and a silent fallback to localhost when the variable was unset.\n *\n * Optional because a page with no CTA needs none; a page that renders one\n * without passing this should fail LOUDLY rather than fall back, which is\n * the consumer's job and the whole point of removing the fallback.\n */\n appOrigin?: string;\n /**\n * The application's public pricing catalogue (#1835), forwarded into\n * `WebsiteRuntimeContext.pricingCatalog` for `useWebsitePricingCatalog()`. Plain JSON-safe data\n * (amounts travel as decimal strings, never `bigint`), loaded in the `.astro` frontmatter with\n * `loadWebsitePricingCatalog()`. Passed only by pages with a pricing section.\n */\n pricingCatalog?: PublicPricingCatalog;\n}\n\n/**\n * Returns a FUNCTION component, not the wider `ComponentType` union. The island IS a function\n * component — Astro hydrates nothing else here — and the union is not callable, so the wider\n * declaration cost callers the ability to invoke the island directly. Mirrors\n * `createWebsiteBlogPostBridgeIsland`, whose suite depends on that call. `FunctionComponent` is\n * assignable to `ComponentType`, so no consumer narrows.\n */\nexport function createWebsitePageBridgeIsland(\n deps: CreateWebsitePageBridgeIslandDeps,\n): FunctionComponent<WebsitePageBridgeIslandProps> {\n const { PageComponent, pageRef, navigation } = deps;\n\n function WebsitePageBridgeIsland({\n locale,\n defaultLocale,\n labelPack,\n designTokens,\n skipToMainContentLabelKey,\n frontendProviders,\n apiOrigin,\n appOrigin,\n pricingCatalog,\n }: WebsitePageBridgeIslandProps): ReactNode {\n /*\n * Both must be present: the factory is build-time wiring and `apiOrigin` is per-environment, and\n * a page that declares no capture section legitimately has neither. Memoised on `apiOrigin` so a\n * re-render cannot hand the provider a new identity and re-post a decision nobody retook.\n */\n const consentRecorder = useMemo(\n () => (deps.consentRecorderFactory !== undefined && apiOrigin !== undefined\n ? deps.consentRecorderFactory(apiOrigin)\n : undefined),\n [apiOrigin],\n );\n const { frontendProviderRegistry, providerComponentRegistry } = useWebsiteProviderRegistries({\n frontendProviders,\n frontendProviderModules: deps.frontendProviderModules,\n providerComponentLoaders: deps.providerComponentLoaders,\n });\n const runtimeValue: WebsiteRuntimeContextValue = {\n locale,\n defaultLocale,\n labelPack,\n designTokens,\n navigation,\n skipToMainContentLabelKey,\n frontendProviderRegistry,\n providerComponentRegistry,\n ...(deps.consentBannerComponent !== undefined ? { consentBannerComponent: deps.consentBannerComponent } : {}),\n apiOrigin,\n appOrigin,\n ...(pricingCatalog !== undefined ? { pricingCatalog } : {}),\n };\n return (\n <WebsiteRuntimeContextProvider value={runtimeValue}>\n <WebsitePageLayout pageRef={pageRef} {...(consentRecorder !== undefined ? { consentRecorder } : {})}>\n <PageComponent />\n </WebsitePageLayout>\n </WebsiteRuntimeContextProvider>\n );\n }\n WebsitePageBridgeIsland.displayName = 'WebsitePageBridgeIsland';\n\n return WebsitePageBridgeIsland;\n}\n/** @wildo_source:part:end saas.website.astro.bridge */\n"]}
|
|
1
|
+
{"version":3,"file":"bridge-runtime.js","sourceRoot":"","sources":["../../../../src/astro/bridge-runtime.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAMhC,OAAO,EACL,6BAA6B,GAE9B,MAAM,wCAAwC,CAAC;AAChD,OAAO,EAAE,4BAA4B,EAAE,MAAM,gEAAgE,CAAC;AAG9G,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AAwRtE;;;;;;GAMG;AACH,MAAM,UAAU,6BAA6B,CAC3C,IAAuC;IAEvC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC;IAEpD,SAAS,uBAAuB,CAAC,EAC/B,MAAM,EACN,aAAa,EACb,SAAS,EACT,YAAY,EACZ,yBAAyB,EACzB,iBAAiB,EACjB,SAAS,EACT,SAAS,EACT,cAAc,GACe;QAC7B;;;;WAIG;QACH,MAAM,eAAe,GAAG,OAAO,CAC7B,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,sBAAsB,KAAK,SAAS,IAAI,SAAS,KAAK,SAAS;YACzE,CAAC,CAAC,IAAI,CAAC,sBAAsB,CAAC,SAAS,CAAC;YACxC,CAAC,CAAC,SAAS,CAAC,EACd,CAAC,SAAS,CAAC,CACZ,CAAC;QACF,MAAM,EAAE,wBAAwB,EAAE,yBAAyB,EAAE,GAAG,4BAA4B,CAAC;YAC3F,iBAAiB;YACjB,uBAAuB,EAAE,IAAI,CAAC,uBAAuB;YACrD,wBAAwB,EAAE,IAAI,CAAC,wBAAwB;SACxD,CAAC,CAAC;QACH,MAAM,YAAY,GAA+B;YAC/C,MAAM;YACN,aAAa;YACb,SAAS;YACT,YAAY;YACZ,UAAU;YACV,yBAAyB;YACzB,wBAAwB;YACxB,yBAAyB;YACzB,GAAG,CAAC,IAAI,CAAC,sBAAsB,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,sBAAsB,EAAE,IAAI,CAAC,sBAAsB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7G,SAAS;YACT,SAAS;YACT,GAAG,CAAC,cAAc,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC5D,CAAC;QACF,OAAO,CACL,KAAC,6BAA6B,IAAC,KAAK,EAAE,YAAY,YAChD,KAAC,iBAAiB,IAAC,OAAO,EAAE,OAAO,KAAM,CAAC,eAAe,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,YACjG,KAAC,aAAa,KAAG,GACC,GACU,CACjC,CAAC;IACJ,CAAC;IACD,uBAAuB,CAAC,WAAW,GAAG,yBAAyB,CAAC;IAEhE,OAAO,uBAAuB,CAAC;AACjC,CAAC","sourcesContent":["import { useMemo } from 'react';\nimport type { ComponentType, FunctionComponent, ReactNode } from 'react';\n\nimport type { FrontendProvidersBlock, PublicPricingCatalog } from '@wildo-ai/saas-models/public-runtime';\nimport type { AvailableLanguage } from '@wildo-ai/saas-models/public-runtime';\n\nimport {\n WebsiteRuntimeContextProvider,\n type WebsiteRuntimeContextValue,\n} from '../core/contexts/WebsiteRuntimeContext';\nimport { useWebsiteProviderRegistries } from '../core/external-providers/provider-component-registry.website';\nimport type { ProviderComponentLoaderContribution } from '@wildo-ai/external-connectors-public/website';\nimport type { GeneratedWebsiteProviderModules } from '../core/external-providers/frontend-provider-registry.website';\nimport { WebsitePageLayout } from '../core/layouts/WebsitePageLayout';\nimport type { WebsiteConsentRecorder } from '../core/consent/WebsiteConsentContext';\nimport type { WebsiteDesignTokens } from '../schemas/design-tokens/website-design-tokens.shared.schemas';\nimport type { WebsiteLabelKey } from '../schemas/label-keys/website-label-key.schemas';\nimport type { WebsiteNavigationConfig } from '../schemas/manifests/website-root-config.shared.schemas';\nimport type { WebsitePageRef } from '../schemas/refs/page-ref.schemas';\nimport type { LabelPack } from '../schemas/validators/label-pack.validator';\n\n/**\n * @wildo_source:part:start saas.website.astro.bridge facet:layer:astro facet:family:website\n *\n * `createWebsitePageBridgeIsland(deps)` — factory that returns the\n * React island a consumer's per-page `.astro` file mounts with a\n * `client:idle` (or `client:load`) directive.\n *\n * **The hard architectural constraint this design solves**: Astro\n * serializes React-island props via `devalue`-style JSON to transfer\n * them across the SSR → client-hydration boundary. Function values —\n * including `ComponentType` references — are NOT serializable. Any\n * design that tried to pass `pageComponent`, `headerComponent`, or\n * `footerComponent` AS island props would render correctly server-side\n * (the function ref is in-process during SSR) and then mismatch on\n * first hydration (the client receives `undefined`/broken placeholders\n * for the function-typed fields). The Vitest suite would not catch\n * this because Vitest renders React in a single process — there is no\n * serialization boundary in unit tests.\n *\n * **The factory pattern fixes the constraint by closing over** the\n * `pageComponent`, `pageRef`, and `navigation` (header / footer\n * component refs) at MODULE LOAD TIME on the consumer side. The\n * returned island accepts only PRIMITIVE-VALUED props (`locale`,\n * `labelPack`, `designTokens`) — all JSON-serializable, all safe to\n * cross the hydration boundary. The component refs never appear in\n * the prop signature; they live in the lexical closure that the\n * client-side bundle includes naturally as part of the island's\n * compiled module.\n *\n * **Authoring pattern (per-page bridge module)**:\n *\n * ```tsx\n * // consumer's src/pages-impl/landing.bridge.tsx\n * import { createWebsitePageBridgeIsland } from '@wildo-ai/saas-website/astro-island';\n * import { LandingPage, LANDING_PAGE_MANIFEST } from './landing.page';\n * import { WEBSITE_ROOT_CONFIG } from '../website-root.config';\n *\n * export const LandingBridgeIsland = createWebsitePageBridgeIsland({\n * PageComponent: LandingPage,\n * pageRef: LANDING_PAGE_MANIFEST.ref,\n * navigation: WEBSITE_ROOT_CONFIG.navigation,\n * });\n * ```\n *\n * ```astro\n * ---\n * // consumer's src/pages/index.astro\n * import {\n * loadWebsitePageRuntime,\n * renderWebsitePageHeadHtml,\n * renderWebsiteDesignTokensCss,\n * } from '@wildo-ai/saas-website/astro';\n * import { LandingBridgeIsland } from '../pages-impl/landing.bridge';\n * import { LANDING_PAGE_MANIFEST } from '../pages-impl/landing.page';\n * import { WEBSITE_SITE_CONTEXT } from '../website-site-context';\n *\n * const runtime = loadWebsitePageRuntime({\n * manifest: LANDING_PAGE_MANIFEST,\n * siteContext: WEBSITE_SITE_CONTEXT,\n * labelPacksDir: './src/i18n',\n * astroLocale: Astro.currentLocale,\n * });\n * const headHtml = renderWebsitePageHeadHtml({\n * manifest: LANDING_PAGE_MANIFEST,\n * siteContext: WEBSITE_SITE_CONTEXT,\n * locale: runtime.locale,\n * labelPack: runtime.labelPack,\n * });\n * const designTokensCss = renderWebsiteDesignTokensCss(\n * WEBSITE_SITE_CONTEXT.rootConfig.designTokens,\n * );\n * ---\n * <!doctype html>\n * <html lang={runtime.locale}>\n * <head>\n * <Fragment set:html={headHtml} />\n * <style set:html={designTokensCss}></style>\n * </head>\n * <body>\n * <LandingBridgeIsland\n * client:idle\n * locale={runtime.locale}\n * defaultLocale={runtime.defaultLocale}\n * appOrigin={WEBSITE_SITE_CONTEXT.websiteConfig.appOrigin}\n * apiOrigin={WEBSITE_SITE_CONTEXT.websiteConfig.apiOrigin}\n * labelPack={runtime.labelPack}\n * designTokens={WEBSITE_SITE_CONTEXT.rootConfig.designTokens}\n * />\n * </body>\n * </html>\n * ```\n *\n * **Why a per-page bridge file** (rather than constructing the island\n * inline in the consumer's `.astro` frontmatter):\n *\n * Astro's `client:*` directive requires the JSX tag to reference a\n * statically-resolvable module export at compile time — that is how\n * Astro knows which module to bundle for client-side hydration. A\n * factory call inside `.astro` frontmatter (`const Island =\n * createWebsitePageBridgeIsland({...}); <Island client:idle/>`) does\n * NOT round-trip through Astro's module-graph analyzer: Astro would\n * see a runtime-constructed component reference and fail to bundle\n * the closure for the client. The per-page bridge file gives Astro a\n * static export to bind the directive to. The cost is one ~6-line\n * file per page; the win is the only architecture that is safe\n * against the H-2 hydration-mismatch failure mode AND uses Astro's\n * native bundling pipeline.\n *\n * **Why `ComponentType` refs in `Deps` and not in `Props`**: this is\n * the entire point of the redesign. `Deps` is consumed at module load\n * time on the consumer side — the closure traps the function refs\n * inside the returned component. `Props` is consumed at island-mount\n * time and crosses Astro's serialization boundary. Keeping the two\n * concerns lexically separate makes the boundary visible in the type\n * signature.\n *\n * **Boundary** (D12 client-bundle discipline): this module ships into\n * the React-island bundle. It MUST stay clear of `node:*` imports,\n * fs / path helpers, and the SSR-tier specifiers\n * (`axios`, `jsonwebtoken`, …). The bundle-isolation test catches\n * regressions both globally (closure-wide allow-list) AND with the\n * targeted Phase-5 negative test that asserts `node:fs` / `node:path`\n * reach this island closure ONLY through `astro.ts`, never through\n * `index.ts` or `companion-exports.ts`.\n */\n\n/**\n * Module-load-time dependencies — closed over inside the returned\n * island. NEVER cross the hydration boundary as serialized props.\n */\nexport interface CreateWebsitePageBridgeIslandDeps {\n /**\n * Browser CODE imported by the consumer bridge, closed over at module load.\n * Never pass executable modules through Astro island props: those are serialized.\n * Config sync emits a stable map, including an empty map when no providers are declared.\n */\n frontendProviderModules?: GeneratedWebsiteProviderModules;\n /**\n * Loaders for provider components an ENGINE provider module declares but cannot import (#1703) —\n * the website's facet-library door, the equivalent of the application's\n * `contributeProviderComponentLoaders`. A provider PACKAGE that ships its own component needs\n * nothing here: its module's `components[capability].load` is used. A DEPS entry because a loader\n * is a function, and island props are serialized.\n */\n providerComponentLoaders?: ReadonlyArray<ProviderComponentLoaderContribution>;\n /**\n * The page's React component. Typically the same value that was\n * passed to `defineWebsitePageManifest({ pageComponent })` — the\n * authoring convention re-imports it here to keep the closure\n * explicit and the per-page bridge file self-contained.\n *\n * Typed as `ComponentType<any>` to mirror\n * `WebsitePageManifest.pageComponent` and dodge React's `ComponentType`\n * contravariance: a narrower `ComponentType<TProps>` returned by\n * `defineWebsitePageManifest<TProps>` would not be assignable to a\n * `ComponentType<unknown>` slot, blocking every per-page bridge\n * call site. The component is invoked with no props at the\n * `<PageComponent />` JSX call site below, so widening here is\n * runtime-safe.\n */\n PageComponent: ComponentType<any>;\n /**\n * Builds the recorder that persists a VISITOR's consent decision server-side, from the island's\n * runtime `apiOrigin` (#1258).\n *\n * A DEPS entry rather than an island prop, for the reason this whole interface exists: it is a\n * function, and island props are serialized. A factory rather than an instance, because the\n * `apiOrigin` it needs is resolved per environment by the `.astro` page and only reaches the island\n * as a prop.\n *\n * The engine cannot build this itself — a client needs the application's own\n * `frontendServiceName`, which the engine has no way to know. The application already constructs\n * one for its capture forms; passing THAT factory here is what makes a visitor's consent decision,\n * their captured lead and their draft note share a single `uuidRef` and transpose together.\n *\n * Optional. Omitted, a decision lives in the visitor's browser and nowhere else — the behaviour\n * before this existed, and correct for a site with no anonymous-session wiring.\n */\n consentRecorderFactory?: (apiOrigin: string) => WebsiteConsentRecorder;\n /**\n * The active page's ref. Carried as a primitive (string-branded)\n * value so the inner `WebsitePageLayout` can register the\n * `WebsitePageContext` without re-resolving it from the manifest.\n */\n pageRef: WebsitePageRef;\n /**\n * Site-wide navigation slot bindings (header / footer components).\n * Closed over here rather than passed as island props because both\n * fields are `ComponentType` refs that cannot survive Astro's\n * serialization boundary.\n */\n navigation: WebsiteNavigationConfig;\n /**\n * Optional replacement for the framework's visitor consent panel (#534). Closed over for the same\n * reason as `navigation`. See `WebsiteRuntimeContextValue.consentBannerComponent`.\n */\n consentBannerComponent?: ComponentType;\n}\n\n/**\n * Hydration-boundary props — what the consumer's `.astro` page passes\n * to `<LandingBridgeIsland client:idle ...{primitiveProps}/>`. Every\n * field is JSON-serializable.\n */\nexport interface WebsitePageBridgeIslandProps {\n /** The locale Astro resolved (with default-locale fallback). */\n locale: AvailableLanguage;\n /**\n * The site's default locale. Carried as an island prop (not closed\n * over in `Deps`) because it is JSON-primitive and consumers\n * already pass `runtime.locale` from the per-locale Astro page\n * frontmatter — passing the matching `runtime.defaultLocale` from\n * the same `loadWebsitePageRuntime(...)` return value keeps the\n * authoring story symmetric. Required for `WebsiteInternalLink`\n * (Phase 6 fix H-β) which needs both locales to compute the\n * correct prefix.\n */\n defaultLocale: AvailableLanguage;\n /** Already-loaded + already-shape-validated label pack. */\n labelPack: LabelPack;\n /** Design-token bundle from `WebsiteRootConfig.designTokens`. */\n designTokens: WebsiteDesignTokens;\n /**\n * Fully-composed label key the framework reads to render the\n * \"Skip to main content\" affordance (Phase 6 third audit fix M-α).\n * Mirrors `WebsiteRootConfig.skipToMainContentLabelKey`. Carried as\n * a JSON-serializable string prop (NOT closed over in `Deps`)\n * because it is a primitive value the consumer already has on the\n * root config in the `.astro` frontmatter — passing it as an island\n * prop keeps the per-page bridge module dependency-free of the\n * site root config (the bridge stays page-local; only the per-page\n * `.astro` page knows about `WEBSITE_ROOT_CONFIG`).\n */\n skipToMainContentLabelKey: WebsiteLabelKey;\n /** Optional website-facing public providers materialized at build time. */\n frontendProviders?: FrontendProvidersBlock;\n /**\n * Optional backend API origin (no trailing slash) forwarded into\n * `WebsiteRuntimeContext.apiOrigin` for anonymous-capture sections\n * (lead / draft-note forms) to construct their\n * `WebsiteAnonymousSessionClient`. A JSON-serializable string, so it\n * crosses the hydration boundary as an island prop; the `.astro` page\n * resolves the per-environment value from\n * `WildoMarketingWebsiteConfig.apiOrigin`. Omitted on pages with no\n * capture section.\n */\n apiOrigin?: string;\n /**\n * The product origin this site's self-serve CTAs link to (\"Start free\",\n * \"Get started\"), forwarded into `WebsiteRuntimeContext.appOrigin` (#493).\n *\n * An island prop for the same reason `apiOrigin` above is one: the `.astro`\n * page resolves it from `WildoMarketingWebsiteConfig.appOrigin`, which is\n * Node-loaded and invisible to a client island. Before this, a site\n * resolved the product origin a SECOND time from a public build-time\n * environment variable — one host authored twice with no mechanical link,\n * and a silent fallback to localhost when the variable was unset.\n *\n * Optional because a page with no CTA needs none; a page that renders one\n * without passing this should fail LOUDLY rather than fall back, which is\n * the consumer's job and the whole point of removing the fallback.\n */\n appOrigin?: string;\n /**\n * The application's public pricing catalogue (#1835), forwarded into\n * `WebsiteRuntimeContext.pricingCatalog` for `useWebsitePricingCatalog()`. Plain JSON-safe data\n * (amounts travel as decimal strings, never `bigint`), loaded in the `.astro` frontmatter with\n * `loadWebsitePricingCatalog()`. Passed only by pages with a pricing section.\n */\n pricingCatalog?: PublicPricingCatalog;\n}\n\n/**\n * Returns a FUNCTION component, not the wider `ComponentType` union. The island IS a function\n * component — Astro hydrates nothing else here — and the union is not callable, so the wider\n * declaration cost callers the ability to invoke the island directly. Mirrors\n * `createWebsiteBlogPostBridgeIsland`, whose suite depends on that call. `FunctionComponent` is\n * assignable to `ComponentType`, so no consumer narrows.\n */\nexport function createWebsitePageBridgeIsland(\n deps: CreateWebsitePageBridgeIslandDeps,\n): FunctionComponent<WebsitePageBridgeIslandProps> {\n const { PageComponent, pageRef, navigation } = deps;\n\n function WebsitePageBridgeIsland({\n locale,\n defaultLocale,\n labelPack,\n designTokens,\n skipToMainContentLabelKey,\n frontendProviders,\n apiOrigin,\n appOrigin,\n pricingCatalog,\n }: WebsitePageBridgeIslandProps): ReactNode {\n /*\n * Both must be present: the factory is build-time wiring and `apiOrigin` is per-environment, and\n * a page that declares no capture section legitimately has neither. Memoised on `apiOrigin` so a\n * re-render cannot hand the provider a new identity and re-post a decision nobody retook.\n */\n const consentRecorder = useMemo(\n () => (deps.consentRecorderFactory !== undefined && apiOrigin !== undefined\n ? deps.consentRecorderFactory(apiOrigin)\n : undefined),\n [apiOrigin],\n );\n const { frontendProviderRegistry, providerComponentRegistry } = useWebsiteProviderRegistries({\n frontendProviders,\n frontendProviderModules: deps.frontendProviderModules,\n providerComponentLoaders: deps.providerComponentLoaders,\n });\n const runtimeValue: WebsiteRuntimeContextValue = {\n locale,\n defaultLocale,\n labelPack,\n designTokens,\n navigation,\n skipToMainContentLabelKey,\n frontendProviderRegistry,\n providerComponentRegistry,\n ...(deps.consentBannerComponent !== undefined ? { consentBannerComponent: deps.consentBannerComponent } : {}),\n apiOrigin,\n appOrigin,\n ...(pricingCatalog !== undefined ? { pricingCatalog } : {}),\n };\n return (\n <WebsiteRuntimeContextProvider value={runtimeValue}>\n <WebsitePageLayout pageRef={pageRef} {...(consentRecorder !== undefined ? { consentRecorder } : {})}>\n <PageComponent />\n </WebsitePageLayout>\n </WebsiteRuntimeContextProvider>\n );\n }\n WebsitePageBridgeIsland.displayName = 'WebsitePageBridgeIsland';\n\n return WebsitePageBridgeIsland;\n}\n/** @wildo_source:part:end saas.website.astro.bridge */\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"collect-expected-label-keys.d.ts","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"collect-expected-label-keys.d.ts","sourceRoot":"","sources":["../../../../src/astro/collect-expected-label-keys.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,+DAA+D,CAAC;AAG9G,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,4CAA4C,CAAC;AAEhG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AACH,wBAAgB,oCAAoC,CAClD,QAAQ,EAAE,SAAS,wBAAwB,EAAE,GAC5C,6BAA6B,CAsB/B;AAuBD,oEAAoE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"headers-renderer.d.ts","sourceRoot":"","sources":["
|
|
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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"i18n-routing.helper.d.ts","sourceRoot":"","sources":["
|
|
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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"label-pack-loader.d.ts","sourceRoot":"","sources":["
|
|
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;AAO9E,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;AAsCD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,kBAAkB,EAC/B,wBAAwB,EAAE,6BAA6B,GACtD,IAAI,CA6DN;AA+ED,6DAA6D"}
|
|
@@ -1 +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 { WEBSITE_CONSENT_LABEL_KEYS } from '../schemas/label-keys/website-consent-label-keys.schemas';\nimport { WEBSITE_PROVIDER_COMPONENT_LABEL_KEYS } from '../schemas/label-keys/website-provider-component-label-keys.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 * - each page's `.astro` frontmatter, through `loadWebsitePageRuntime`,\n * which reads the active locale's pack during SSR / pre-render,\n * - `wildo website audit`, which validates every locale pack against\n * every manifest and section through `loadAndValidateAllLabelPacks`,\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 // The visitor consent panel is framework-owned in the same way (#534): required in every pack, so a site\n // that turns on analytics can never ship it with an unlabelled way to refuse.\n for (const key of WEBSITE_CONSENT_LABEL_KEYS) set.add(key);\n // So is the provider-component slot's refusal wording (#1703): a site placing an address lookup must not\n // ship its consent-required or unavailable state as a raw key.\n for (const key of WEBSITE_PROVIDER_COMPONENT_LABEL_KEYS) set.add(key);\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"]}
|
|
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 { WEBSITE_CONSENT_LABEL_KEYS } from '../schemas/label-keys/website-consent-label-keys.schemas';\nimport { WEBSITE_PROVIDER_COMPONENT_LABEL_KEYS } from '../schemas/label-keys/website-provider-component-label-keys.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 * - each page's `.astro` frontmatter, through `loadWebsitePageRuntime`,\n * which reads the active locale's pack during SSR / pre-render,\n * - `wildo website audit`, which validates every locale pack against\n * every manifest and section through `loadAndValidateAllLabelPacks`,\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 // The visitor consent panel is framework-owned in the same way (#534): required in every pack, so a site\n // that turns on analytics can never ship it with an unlabelled way to refuse.\n for (const key of WEBSITE_CONSENT_LABEL_KEYS) set.add(key);\n // So is the provider-component slot's refusal wording (#1703): a site placing an address lookup must not\n // ship its consent-required or unavailable state as a raw key.\n for (const key of WEBSITE_PROVIDER_COMPONENT_LABEL_KEYS) set.add(key);\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"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"llms-txt-server.d.ts","sourceRoot":"","sources":["
|
|
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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pick-labels-for-page.d.ts","sourceRoot":"","sources":["
|
|
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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"robots-renderer.d.ts","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"robots-renderer.d.ts","sourceRoot":"","sources":["../../../../src/astro/robots-renderer.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAEjE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiFG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,kBAAkB,GAAG,MAAM,CA8FvE;AACD,gEAAgE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"robots-renderer.js","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"robots-renderer.js","sourceRoot":"","sources":["../../../../src/astro/robots-renderer.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiFG;AACH,MAAM,UAAU,eAAe,CAAC,WAA+B;IAC7D,MAAM,EAAE,UAAU,EAAE,aAAa,EAAE,GAAG,WAAW,CAAC;IAElD,IAAI,CAAC,aAAa,CAAC,YAAY,EAAE,CAAC;QAChC,MAAM,IAAI,KAAK,CACb,qKAAqK,CACtK,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAoC,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC;IACtE,MAAM,MAAM,GAAwB,MAAM,EAAE,MAAM,IAAI,OAAO,CAAC;IAC9D,MAAM,eAAe,GAAG,MAAM,EAAE,eAAe,IAAI,EAAE,CAAC;IACtD,MAAM,kBAAkB,GAAG,MAAM,EAAE,kBAAkB,IAAI,EAAE,CAAC;IAC5D,MAAM,QAAQ,GAAG,MAAM,EAAE,QAAQ,IAAI,EAAE,CAAC;IAExC,MAAM,MAAM,GAAa,EAAE,CAAC;IAE5B;;;OAGG;IACH,KAAK,MAAM,GAAG,IAAI,eAAe,EAAE,CAAC;QAClC,MAAM,CAAC,IAAI,CAAC,eAAe,GAAG,YAAY,CAAC,CAAC;IAC9C,CAAC;IAED;;;;OAIG;IACH,KAAK,MAAM,GAAG,IAAI,kBAAkB,EAAE,CAAC;QACrC,MAAM,CAAC,IAAI,CAAC,eAAe,GAAG,eAAe,CAAC,CAAC;IACjD,CAAC;IAED;;;;;;;OAOG;IACH,MAAM,aAAa,GAAa,CAAC,eAAe,CAAC,CAAC;IAClD;;;;;;;;;;;;OAYG;IACH,IAAI,eAAe,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC/B,aAAa,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IACpC,CAAC;SAAM,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACjC;;;;;WAKG;QACH,aAAa,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAClC,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;QAC5B,aAAa,CAAC,IAAI,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC;IAC1C,CAAC;IACD,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAEtC;;;;;OAKG;IACH,MAAM,cAAc,GAAG,UAAU,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,IAAI,IAAI,CAAC;IAC/D,IAAI,cAAc,EAAE,CAAC;QACnB;;;;WAIG;QACH,MAAM,MAAM,GAAG,aAAa,CAAC,YAAY,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QAC9D,MAAM,CAAC,IAAI,CAAC,YAAY,MAAM,oBAAoB,CAAC,CAAC;IACtD,CAAC;IAED;;;OAGG;IACH,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;AACpC,CAAC;AACD,gEAAgE","sourcesContent":["import type {\n WebsiteRobotsConfig,\n} from '../schemas/manifests/website-root-config.shared.schemas';\n\nimport type { WebsiteSiteContext } from './website-site-context';\n\n/**\n * @wildo_source:part:start saas.website.astro.robots-renderer facet:layer:astro facet:family:website\n *\n * `renderRobotsTxt(siteContext)` — pure string builder for the\n * marketing site's `robots.txt` body. The consumer wires a thin\n * `src/pages/robots.txt.ts` Astro endpoint that imports this helper\n * + `WEBSITE_SITE_CONTEXT` and returns its output as a `Response`\n * with `Content-Type: text/plain` (see `website-seo-aeo-geo` SKILL).\n *\n * # Why a pure string builder (not an Astro component)\n *\n * Same rationale as `renderWebsitePageHeadHtml`:\n * 1. Pure TypeScript ships through the `@wildo-ai/saas-website/astro`\n * subpath barrel (Astro components do not).\n * 2. Vitest-friendly — every policy permutation is asserted\n * against the returned string without an Astro test harness.\n * 3. The Astro endpoint stays a 3-line shim the consumer can\n * audit at a glance.\n *\n * # Output shape\n *\n * The emitted text follows the de-facto `robots.txt` ordering\n * (RFC 9309 §2 + Google's published guidance):\n *\n * 1. Per-bot blocks for `allowedCrawlers` (deny-by-default\n * direction) — each named bot gets its own `User-agent: <bot>`\n * + `Allow: /` block.\n * 2. Per-bot blocks for `disallowedCrawlers` (allow-by-default\n * direction) — each named bot gets its own `User-agent: <bot>`\n * + `Disallow: /` block.\n * 3. Catch-all `User-agent: *` block: `Disallow: /` only when\n * `allowedCrawlers` is set (the named bots are then the only ones\n * permitted), otherwise the allow-everything `Disallow:` plus any\n * `disallow[]` path-prefix entries. `policy === 'noindex'` adds\n * nothing here — it is expressed by the page's robots meta tag,\n * which a crawler can only read if it may fetch the page (#494).\n * 4. `Sitemap: <publicOrigin>/sitemap-index.xml` footer when\n * `seo.sitemap?.enabled !== false` (defaults to `true`).\n * `@astrojs/sitemap` emits `sitemap-index.xml` regardless of\n * whether the site has one or many sitemap files (the index\n * is always-present per the plugin's contract), so this is\n * the stable URL to advertise.\n *\n * # Mutual-exclusion invariant\n *\n * `WebsiteRobotsConfigSchema.superRefine` already guarantees\n * `allowedCrawlers` and `disallowedCrawlers` are NOT both set, so\n * step 1 and step 2 above are mutually exclusive at the input\n * layer. The string builder is defensive (handles both being unset)\n * but does NOT re-validate the invariant.\n *\n * # Why we always emit `Sitemap:` footer when `enabled !== false`\n *\n * The `Sitemap:` directive is independent from any `User-agent`\n * block — it is a top-level signal to any crawler that the site\n * publishes a sitemap. Even when the catch-all is `Disallow: /`\n * (`allowedCrawlers` set), the `Sitemap:` line stays useful: the\n * permitted crawlers have a single canonical URL list to consult,\n * and human readers of `robots.txt` see the publication contract\n * intact.\n *\n * # Failure modes\n *\n * - `siteContext.websiteConfig.publicOrigin` undefined → throws.\n * The marketing-site origin is the SINGLE source of truth for\n * absolute URL composition (see `WildoMarketingWebsiteConfig`\n * JSDoc); a missing origin is a configuration error, not a\n * silent fallback opportunity.\n *\n * @example\n * ```ts\n * // examples/<app>/website/src/pages/robots.txt.ts\n * import type { APIRoute } from 'astro';\n * import { renderRobotsTxt } from '@wildo-ai/saas-website/astro';\n * import { WEBSITE_SITE_CONTEXT } from '@/website-site-context';\n *\n * export const GET: APIRoute = () =>\n * new Response(renderRobotsTxt(WEBSITE_SITE_CONTEXT), {\n * headers: { 'Content-Type': 'text/plain; charset=utf-8' },\n * });\n * ```\n */\nexport function renderRobotsTxt(siteContext: WebsiteSiteContext): string {\n const { rootConfig, websiteConfig } = siteContext;\n\n if (!websiteConfig.publicOrigin) {\n throw new Error(\n 'renderRobotsTxt: WildoMarketingWebsiteConfig.publicOrigin is required to compose the absolute Sitemap: URL. Author it in your service\\'s `wildo.website.config.ts`.',\n );\n }\n\n const robots: WebsiteRobotsConfig | undefined = rootConfig.seo.robots;\n const policy: 'index' | 'noindex' = robots?.policy ?? 'index';\n const allowedCrawlers = robots?.allowedCrawlers ?? [];\n const disallowedCrawlers = robots?.disallowedCrawlers ?? [];\n const disallow = robots?.disallow ?? [];\n\n const blocks: string[] = [];\n\n /**\n * Block 1 — per-bot `Allow: /` for the deny-by-default direction.\n * Empty when `allowedCrawlers` is unset (the dominant case).\n */\n for (const bot of allowedCrawlers) {\n blocks.push(`User-agent: ${bot}\\nAllow: /`);\n }\n\n /**\n * Block 2 — per-bot `Disallow: /` for the allow-by-default\n * direction. Empty when `disallowedCrawlers` is unset (the\n * dominant case for a public marketing site).\n */\n for (const bot of disallowedCrawlers) {\n blocks.push(`User-agent: ${bot}\\nDisallow: /`);\n }\n\n /**\n * Block 3 — catch-all `User-agent: *`. `Disallow:` is the\n * spec-canonical \"allow everything\" line, combined with any per-path\n * `Disallow: <path>` entries from `disallow[]`. When `allowedCrawlers`\n * is set, the catch-all instead closes the rest of the crawler universe\n * with `Disallow: /` so the named bots are the ONLY ones permitted. The\n * `policy` is not expressed here at all — see below.\n */\n const catchAllLines: string[] = ['User-agent: *'];\n /*\n * `policy === 'noindex'` deliberately does NOT emit `Disallow: /` (#494).\n *\n * `Disallow` governs CRAWLING; the `<meta name=\"robots\" content=\"noindex\">` emitted by the page-head\n * renderer governs INDEXING, and it is the one that keeps a site out of search. Barring the crawler\n * here would mean it never fetches the page and never reads that directive — so the pair defeated\n * itself, and the site stayed indexable as a bare URL. Closure is expressed in the document; this\n * file's job for that policy is to stay out of the way.\n *\n * `allowedCrawlers` is a DIFFERENT feature and keeps its `Disallow: /`: there the catch-all closes\n * the rest of the crawler universe so the named bots are the only ones permitted, which is about\n * crawling and is exactly what `Disallow` is for.\n */\n if (allowedCrawlers.length > 0) {\n catchAllLines.push('Disallow: /');\n } else if (disallow.length === 0) {\n /**\n * Spec-canonical \"allow everything\" — `Disallow:` with no path.\n * Some crawlers treat the absence of any `Disallow:` line as\n * undefined behavior; an explicit empty Disallow is the safe\n * shape.\n */\n catchAllLines.push('Disallow:');\n }\n for (const path of disallow) {\n catchAllLines.push(`Disallow: ${path}`);\n }\n blocks.push(catchAllLines.join('\\n'));\n\n /**\n * Block 4 — `Sitemap:` footer, advertised whenever sitemap\n * emission is enabled (the marketing-site default). See file\n * preamble for why we always advertise `sitemap-index.xml` as\n * the stable URL.\n */\n const sitemapEnabled = rootConfig.seo.sitemap?.enabled ?? true;\n if (sitemapEnabled) {\n /**\n * Trim trailing slash on the origin to avoid emitting\n * `https://example.com//sitemap-index.xml`. `URL` constructor\n * is overkill for a single concat; the regex is enough.\n */\n const origin = websiteConfig.publicOrigin.replace(/\\/+$/, '');\n blocks.push(`Sitemap: ${origin}/sitemap-index.xml`);\n }\n\n /**\n * Spec-conformant trailing newline. Some crawler parsers tolerate\n * its absence; emitting it is universally safe.\n */\n return `${blocks.join('\\n\\n')}\\n`;\n}\n/** @wildo_source:part:end saas.website.astro.robots-renderer */\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sitemap-coverage.d.ts","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"sitemap-coverage.d.ts","sourceRoot":"","sources":["../../../../src/astro/sitemap-coverage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sCAAsC,CAAC;AAK9E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAEjE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0EG;AACH,MAAM,WAAW,yBAAyB;IACxC,iDAAiD;IACjD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,qEAAqE;IACrE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,kEAAkE;IAClE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,0BAA0B;IAC1B,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;IACnC;;;;;;OAMG;IACH,QAAQ,CAAC,UAAU,EAAE,SAAS;QAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACrF;AAED,wBAAgB,iCAAiC,CAC/C,WAAW,EAAE,kBAAkB,GAC9B,SAAS,yBAAyB,EAAE,CAiFtC;AACD,iEAAiE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"structured-data.renderer.d.ts","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"structured-data.renderer.d.ts","sourceRoot":"","sources":["../../../../src/astro/structured-data.renderer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,4BAA4B,EAE7B,MAAM,mEAAmE,CAAC;AAC3E,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,+DAA+D,CAAC;AAgD9G,MAAM,WAAW,+BAA+B;IAC9C;;;;OAIG;IACH,QAAQ,CAAC,EAAE,SAAS,IAAI,CAAC,wBAAwB,EAAE,YAAY,GAAG,mBAAmB,CAAC,EAAE,CAAC;IACzF;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,SAAS,4BAA4B,EAAE,CAAC;CACxD;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,+BAA+B,GAAG,MAAM,CAiDtF;AAoDD,yEAAyE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"website-page-head.renderer.d.ts","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"website-page-head.renderer.d.ts","sourceRoot":"","sources":["../../../../src/astro/website-page-head.renderer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sCAAsC,CAAC;AAG9E,OAAO,EAAiC,KAAK,mBAAmB,EAAE,MAAM,+DAA+D,CAAC;AACxI,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2DAA2D,CAAC;AACrG,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,4CAA4C,CAAC;AAG5E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAEjE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AAEH,MAAM,WAAW,gCAAgC;IAC/C,QAAQ,EAAE,mBAAmB,CAAC;IAC9B;;;;;OAKG;IACH,WAAW,EAAE,kBAAkB,CAAC;IAChC,MAAM,EAAE,iBAAiB,CAAC;IAC1B,SAAS,EAAE,SAAS,CAAC;CACtB;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,gCAAgC,GAAG,MAAM,CAuLxF;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,mBAAmB,GAAG,MAAM,CAehF;AAED;;;;;;;;GAQG"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"website-page-head.renderer.js","sourceRoot":"","sources":["../../../src/astro/website-page-head.renderer.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AvailableLanguage } from '@wildo-ai/saas-models/public-runtime';\n\nimport { buildLocalizedRoutePath } from '../core/routing/internal-routing.utils';\nimport { websiteDesignTokenCssProperty, type WebsiteDesignTokens } from '../schemas/design-tokens/website-design-tokens.shared.schemas';\nimport type { WebsitePageManifest } from '../schemas/manifests/website-page-manifest.shared.schemas';\nimport type { LabelPack } from '../schemas/validators/label-pack.validator';\n\nimport { escapeHtmlAttribute, escapeHtmlText } from './internal/html-escape';\nimport type { WebsiteSiteContext } from './website-site-context';\n\n/**\n * @wildo_source:part:start saas.website.astro.head-renderer facet:layer:astro facet:family:website\n *\n * Head-block renderers — pure string builders the consumer's per-page\n * `.astro` file injects via `<Fragment set:html={...}/>` /\n * `<style set:html={...}></style>`. Two functions:\n *\n * - `renderWebsitePageHeadHtml(opts)` — emits the complete SEO\n * `<head>` block: `<title>`, meta description, full Open Graph\n * card (`og:title` / `og:description` / `og:type` / `og:url` /\n * `og:site_name` / `og:locale` / `og:locale:alternate` / optional\n * `og:image`), Twitter card mirrors, `<link rel=\"canonical\">`,\n * and one `<link rel=\"alternate\" hreflang>` per declared locale\n * (plus an `x-default` for Google's i18n discoverability). Every\n * URL is ABSOLUTE (composed from `websiteConfig.publicOrigin` +\n * the page's `routePath`) — relative hreflang/canonical\n * annotations are not honored consistently across crawlers\n * (Phase 6 audit findings H-B / H-C / M-A).\n * - `renderWebsiteDesignTokensCss(tokens)` — flattens the design-token\n * bundle into `--website-*` custom properties scoped to `:root`.\n * Server-static — zero island JS.\n *\n * **Why pure string builders** (rather than React components or Astro\n * components):\n *\n * 1. **Avoids the `.astro` distribution problem.** A `.astro` shell\n * cannot be re-exported from a TS subpath barrel and `tsc` does\n * not copy `.astro` files to `dist/`. Pure TS sidesteps both.\n * 2. **Avoids the React-island boundary.** Rendering head content\n * via React would require either (a) a server-only React subtree\n * (extra moving part) or (b) hydrating the `<head>` on the client\n * (waste of bundle size for static-build content).\n * 3. **Vitest-friendly.** Pure functions returning strings are\n * trivially asserted against without an Astro test harness.\n *\n * **HTML escaping**: every consumer-supplied string (title,\n * description, OG image URL, hreflang values) is escaped with\n * `escapeHtmlAttribute` / `escapeHtmlText` before interpolation into\n * the emitted markup. Marketing copy frequently contains characters\n * (quotes, ampersands, the rare `<`) that would break the surrounding\n * tag if interpolated raw — escaping is non-negotiable for a public\n * surface.\n *\n * **Failure modes** — `renderWebsitePageHeadHtml` throws when:\n * - `manifest.metaLabelKeys.titleKey` is absent from the pack\n * - `manifest.metaLabelKeys.descriptionKey` is absent from the pack\n * Both throw with the locale + key + actionable next step (`Run\n * \\`wildo website audit\\` or add the entry to <pack-path>.json`).\n */\n\nexport interface RenderWebsitePageHeadHtmlOptions {\n manifest: WebsitePageManifest;\n /**\n * Composed `WebsiteSiteContext` — the renderer reads\n * `websiteConfig.{defaultLocale, locales, publicOrigin}` for the\n * URL/locale surface and `rootConfig.seo.{siteName, defaultOgImageUrl}`\n * for the SEO branding fallback.\n */\n siteContext: WebsiteSiteContext;\n locale: AvailableLanguage;\n labelPack: LabelPack;\n}\n\nexport function renderWebsitePageHeadHtml(opts: RenderWebsitePageHeadHtmlOptions): string {\n const { manifest, siteContext, locale, labelPack } = opts;\n const { rootConfig, websiteConfig } = siteContext;\n\n const titleKey = manifest.metaLabelKeys.titleKey as unknown as string;\n const descriptionKey = manifest.metaLabelKeys.descriptionKey as unknown as string;\n const title = labelPack[titleKey];\n const description = labelPack[descriptionKey];\n\n if (title === undefined) {\n throw new Error(\n `[renderWebsitePageHeadHtml] Label pack for locale \"${locale}\" is missing ` +\n `the page title key \"${titleKey}\" (page ref=\"${manifest.ref as unknown as string}\"). ` +\n `Run \\`wildo website audit\\` or add the entry to your <locale>.json pack.`,\n );\n }\n if (description === undefined) {\n throw new Error(\n `[renderWebsitePageHeadHtml] Label pack for locale \"${locale}\" is missing ` +\n `the page description key \"${descriptionKey}\" (page ref=\"${manifest.ref as unknown as string}\"). ` +\n `Run \\`wildo website audit\\` or add the entry to your <locale>.json pack.`,\n );\n }\n\n /**\n * OG image precedence: per-page `manifest.metaLabelKeys.ogImageUrl`\n * wins; falls back to the site-level `seo.defaultOgImageUrl` so\n * marketing pages without a hero image still produce a richer\n * social-card (Phase 6 audit fix M-A). When neither is present,\n * the Twitter card kind degrades to the smaller `summary` and the\n * `og:image` / `twitter:image` lines are omitted entirely.\n */\n const ogImageUrl = manifest.metaLabelKeys.ogImageUrl ?? rootConfig.seo.defaultOgImageUrl;\n const titleAttr = escapeHtmlAttribute(title);\n const descriptionAttr = escapeHtmlAttribute(description);\n const titleText = escapeHtmlText(title);\n\n /**\n * Absolute URL builder. `prefixDefaultLocale: false` means the\n * default locale lives at the locale-neutral path; every other\n * locale sits under `/<locale>/<routePath>`. `x-default` always\n * resolves to the default-locale path.\n *\n * Every URL emitted here is FULLY-QUALIFIED (composed against\n * `websiteConfig.publicOrigin`) — Google's hreflang spec requires\n * absolute URLs, and `<link rel=\"canonical\">` relies on absolute\n * URLs to survive cross-host fetches (preview deployments, CDN\n * edges). See `WildoMarketingWebsiteConfigSchema.publicOrigin`\n * JSDoc for the full rationale (incl. trailing-slash rejection).\n */\n const routePath = manifest.routePath as unknown as string;\n const siteOrigin = websiteConfig.publicOrigin;\n const defaultLocale = websiteConfig.defaultLocale;\n const currentLocaleStr = String(locale);\n\n /**\n * Compose the absolute URL for `(locale, routePath)` by delegating\n * the locale-prefix decision to the shared `buildLocalizedRoutePath`\n * helper — same routing rule that powers `WebsiteInternalLink`. This\n * guarantees that hreflang/canonical URLs and the chrome's internal\n * links can never disagree about how a locale is prefixed.\n */\n function buildAbsoluteUrl(forLocale: AvailableLanguage): string {\n const path = buildLocalizedRoutePath({\n locale: forLocale,\n defaultLocale,\n routePath,\n });\n return `${siteOrigin}${path}`;\n }\n\n const canonicalUrl = buildAbsoluteUrl(locale);\n\n const hreflangLines: string[] = [];\n for (const entry of websiteConfig.locales) {\n const localeStr = String(entry.locale);\n hreflangLines.push(\n `<link rel=\"alternate\" hreflang=\"${escapeHtmlAttribute(localeStr)}\" href=\"${escapeHtmlAttribute(buildAbsoluteUrl(entry.locale))}\" />`,\n );\n }\n hreflangLines.push(\n `<link rel=\"alternate\" hreflang=\"x-default\" href=\"${escapeHtmlAttribute(buildAbsoluteUrl(defaultLocale))}\" />`,\n );\n\n /**\n * `og:image` + `twitter:image` are emitted only when an image URL\n * is available (page-level override OR site-level fallback).\n * Twitter card type also flips between `summary` (no image) and\n * `summary_large_image` (image present).\n */\n const ogImageLines: string[] = [];\n if (ogImageUrl !== undefined) {\n const ogImageAttr = escapeHtmlAttribute(ogImageUrl);\n ogImageLines.push(\n `<meta property=\"og:image\" content=\"${ogImageAttr}\" />`,\n `<meta name=\"twitter:image\" content=\"${ogImageAttr}\" />`,\n );\n }\n const twitterCardKind = ogImageUrl !== undefined ? 'summary_large_image' : 'summary';\n\n /**\n * `og:locale:alternate` — one per non-current declared locale. Open\n * Graph spec: the `og:locale` field carries the page's primary\n * locale; alternates declare the other locales the page is\n * available in. Emitted in declaration order.\n *\n * Locale format: we emit the bare language code (`en`, `fr`) per\n * the AvailableLanguage atom shape. Open Graph technically\n * recommends `language_TERRITORY` (e.g. `en_US`), but Facebook /\n * LinkedIn / Slack accept the bare language code in practice. The\n * future fix when the platform grows territory-tagged locales is to\n * map at this site (no consumer change required).\n */\n const ogLocaleAlternateLines: string[] = [];\n for (const entry of websiteConfig.locales) {\n const localeStr = String(entry.locale);\n if (localeStr === currentLocaleStr) continue;\n ogLocaleAlternateLines.push(\n `<meta property=\"og:locale:alternate\" content=\"${escapeHtmlAttribute(localeStr)}\" />`,\n );\n }\n\n const siteNameAttr = escapeHtmlAttribute(rootConfig.seo.siteName);\n const canonicalAttr = escapeHtmlAttribute(canonicalUrl);\n const ogLocaleAttr = escapeHtmlAttribute(currentLocaleStr);\n\n /**\n * SITE-WIDE `noindex` (#494) — the directive that actually keeps a site out of search.\n *\n * `robots.txt` `Disallow` governs CRAWLING, not INDEXING: a disallowed URL can still be listed, as a\n * bare URL with no snippet, if anything links to it. Only this directive de-indexes. Until 2026-09-08\n * a site declared `noindex` emitted the `Disallow` and NOT this, so it was closed to well-behaved\n * crawlers and still indexable — the weaker half of the pair.\n *\n * The two are also mutually defeating, which is why `renderRobotsTxt` no longer emits `Disallow: /`\n * for this policy: a crawler barred from fetching the page never reads the directive telling it not\n * to index. To stay out of search you must let the crawler IN and tell it no.\n *\n * `noindex` only — not `noindex, nofollow`. `nofollow` additionally suppresses link discovery, which\n * is a broader claim than \"keep this out of the index\" and is not what the policy declares.\n */\n const robotsDirectives: string[] = [];\n if (rootConfig.seo.robots?.policy === 'noindex') {\n robotsDirectives.push('noindex');\n }\n /**\n * AI-training opt-out (D-AG3) — emitted ONLY when the page manifest opts in via\n * `seo.noaiOptOut: true`. Default is OPT-IN to AI training (the marketing-site default). This\n * emission pairs with the sibling `_headers` entry produced by `renderWebsitePageNoaiHeaders()`\n * for crawlers that don't honor in-document meta.\n *\n * `noai` (block AI training on text) and `noimageai` (block AI training on images) are the two\n * directives the major AI crawlers (GPTBot, ClaudeBot, Google-Extended, etc.) recognise.\n */\n if (manifest.seo?.noaiOptOut === true) {\n robotsDirectives.push('noai', 'noimageai');\n }\n /*\n * ONE `<meta name=\"robots\">`, carrying every directive comma-separated — the canonical shape. Two\n * separate robots meta tags are legal and are read inconsistently; merging avoids the question.\n */\n const noaiMetaLines: string[] = robotsDirectives.length > 0\n ? [`<meta name=\"robots\" content=\"${robotsDirectives.join(', ')}\" />`]\n : [];\n\n return [\n `<title>${titleText}</title>`,\n `<meta name=\"description\" content=\"${descriptionAttr}\" />`,\n `<link rel=\"canonical\" href=\"${canonicalAttr}\" />`,\n `<meta property=\"og:title\" content=\"${titleAttr}\" />`,\n `<meta property=\"og:description\" content=\"${descriptionAttr}\" />`,\n `<meta property=\"og:type\" content=\"website\" />`,\n `<meta property=\"og:url\" content=\"${canonicalAttr}\" />`,\n `<meta property=\"og:site_name\" content=\"${siteNameAttr}\" />`,\n `<meta property=\"og:locale\" content=\"${ogLocaleAttr}\" />`,\n ...ogLocaleAlternateLines,\n ...ogImageLines,\n `<meta name=\"twitter:card\" content=\"${twitterCardKind}\" />`,\n `<meta name=\"twitter:title\" content=\"${titleAttr}\" />`,\n `<meta name=\"twitter:description\" content=\"${descriptionAttr}\" />`,\n ...noaiMetaLines,\n ...hreflangLines,\n ].join('\\n');\n}\n\n/**\n * Flatten `WebsiteDesignTokens` to a `:root { … }` block of CSS custom\n * properties. Naming convention: `--website-<group>-<token>` for nested\n * groups (`colors.primary` → `--website-colors-primary`); top-level\n * string-valued tokens (none today, future-proofed) emit as\n * `--website-<token>`.\n *\n * **Why scoped to `:root`** rather than `<WebsitePageLayout>`'s root\n * div: marketing pages frequently embed third-party widgets (live-chat,\n * analytics overlays) that paint outside the React tree. Scoping\n * tokens to `:root` makes them visible to those widgets too. The\n * design-token surface is small enough that the cost of a broader\n * scope (token-name collisions with consumer-authored `:root` rules)\n * is bounded by the `--website-` prefix.\n */\nexport function renderWebsiteDesignTokensCss(tokens: WebsiteDesignTokens): string {\n const lines: string[] = [':root {'];\n for (const [groupName, groupValue] of Object.entries(tokens) as [string, unknown][]) {\n if (groupValue !== null && typeof groupValue === 'object' && !Array.isArray(groupValue)) {\n for (const [tokenName, tokenValue] of Object.entries(groupValue) as [string, unknown][]) {\n if (typeof tokenValue === 'string') {\n lines.push(` ${websiteDesignTokenCssProperty(groupName, tokenName)}: ${tokenValue};`);\n }\n }\n } else if (typeof groupValue === 'string') {\n lines.push(` --website-${groupName}: ${groupValue};`);\n }\n }\n lines.push('}');\n return lines.join('\\n');\n}\n\n/**\n * @wildo_source:part:end saas.website.astro.head-renderer\n *\n * HTML escape helpers moved to `./internal/html-escape.ts` (Phase 7)\n * so the new `blog-post-head.renderer.ts` sibling can share the same\n * escape contract instead of redefining it. See that module's preamble\n * for the framework-mindset rationale (one escape contract per engine,\n * not one per renderer).\n */\n"]}
|
|
1
|
+
{"version":3,"file":"website-page-head.renderer.js","sourceRoot":"","sources":["../../../../src/astro/website-page-head.renderer.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,uBAAuB,EAAE,MAAM,wCAAwC,CAAC;AACjF,OAAO,EAAE,6BAA6B,EAA4B,MAAM,+DAA+D,CAAC;AAIxI,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAkE7E,MAAM,UAAU,yBAAyB,CAAC,IAAsC;IAC9E,MAAM,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC;IAC1D,MAAM,EAAE,UAAU,EAAE,aAAa,EAAE,GAAG,WAAW,CAAC;IAElD,MAAM,QAAQ,GAAG,QAAQ,CAAC,aAAa,CAAC,QAA6B,CAAC;IACtE,MAAM,cAAc,GAAG,QAAQ,CAAC,aAAa,CAAC,cAAmC,CAAC;IAClF,MAAM,KAAK,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;IAClC,MAAM,WAAW,GAAG,SAAS,CAAC,cAAc,CAAC,CAAC;IAE9C,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CACb,sDAAsD,MAAM,eAAe;YACzE,uBAAuB,QAAQ,gBAAgB,QAAQ,CAAC,GAAwB,MAAM;YACtF,0EAA0E,CAC7E,CAAC;IACJ,CAAC;IACD,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CACb,sDAAsD,MAAM,eAAe;YACzE,6BAA6B,cAAc,gBAAgB,QAAQ,CAAC,GAAwB,MAAM;YAClG,0EAA0E,CAC7E,CAAC;IACJ,CAAC;IAED;;;;;;;OAOG;IACH,MAAM,UAAU,GAAG,QAAQ,CAAC,aAAa,CAAC,UAAU,IAAI,UAAU,CAAC,GAAG,CAAC,iBAAiB,CAAC;IACzF,MAAM,SAAS,GAAG,mBAAmB,CAAC,KAAK,CAAC,CAAC;IAC7C,MAAM,eAAe,GAAG,mBAAmB,CAAC,WAAW,CAAC,CAAC;IACzD,MAAM,SAAS,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;IAExC;;;;;;;;;;;;OAYG;IACH,MAAM,SAAS,GAAG,QAAQ,CAAC,SAA8B,CAAC;IAC1D,MAAM,UAAU,GAAG,aAAa,CAAC,YAAY,CAAC;IAC9C,MAAM,aAAa,GAAG,aAAa,CAAC,aAAa,CAAC;IAClD,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IAExC;;;;;;OAMG;IACH,SAAS,gBAAgB,CAAC,SAA4B;QACpD,MAAM,IAAI,GAAG,uBAAuB,CAAC;YACnC,MAAM,EAAE,SAAS;YACjB,aAAa;YACb,SAAS;SACV,CAAC,CAAC;QACH,OAAO,GAAG,UAAU,GAAG,IAAI,EAAE,CAAC;IAChC,CAAC;IAED,MAAM,YAAY,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC;IAE9C,MAAM,aAAa,GAAa,EAAE,CAAC;IACnC,KAAK,MAAM,KAAK,IAAI,aAAa,CAAC,OAAO,EAAE,CAAC;QAC1C,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACvC,aAAa,CAAC,IAAI,CAChB,mCAAmC,mBAAmB,CAAC,SAAS,CAAC,WAAW,mBAAmB,CAAC,gBAAgB,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,MAAM,CACtI,CAAC;IACJ,CAAC;IACD,aAAa,CAAC,IAAI,CAChB,oDAAoD,mBAAmB,CAAC,gBAAgB,CAAC,aAAa,CAAC,CAAC,MAAM,CAC/G,CAAC;IAEF;;;;;OAKG;IACH,MAAM,YAAY,GAAa,EAAE,CAAC;IAClC,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QAC7B,MAAM,WAAW,GAAG,mBAAmB,CAAC,UAAU,CAAC,CAAC;QACpD,YAAY,CAAC,IAAI,CACf,sCAAsC,WAAW,MAAM,EACvD,uCAAuC,WAAW,MAAM,CACzD,CAAC;IACJ,CAAC;IACD,MAAM,eAAe,GAAG,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,SAAS,CAAC;IAErF;;;;;;;;;;;;OAYG;IACH,MAAM,sBAAsB,GAAa,EAAE,CAAC;IAC5C,KAAK,MAAM,KAAK,IAAI,aAAa,CAAC,OAAO,EAAE,CAAC;QAC1C,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACvC,IAAI,SAAS,KAAK,gBAAgB;YAAE,SAAS;QAC7C,sBAAsB,CAAC,IAAI,CACzB,iDAAiD,mBAAmB,CAAC,SAAS,CAAC,MAAM,CACtF,CAAC;IACJ,CAAC;IAED,MAAM,YAAY,GAAG,mBAAmB,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAClE,MAAM,aAAa,GAAG,mBAAmB,CAAC,YAAY,CAAC,CAAC;IACxD,MAAM,YAAY,GAAG,mBAAmB,CAAC,gBAAgB,CAAC,CAAC;IAE3D;;;;;;;;;;;;;;OAcG;IACH,MAAM,gBAAgB,GAAa,EAAE,CAAC;IACtC,IAAI,UAAU,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,SAAS,EAAE,CAAC;QAChD,gBAAgB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACnC,CAAC;IACD;;;;;;;;OAQG;IACH,IAAI,QAAQ,CAAC,GAAG,EAAE,UAAU,KAAK,IAAI,EAAE,CAAC;QACtC,gBAAgB,CAAC,IAAI,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAC7C,CAAC;IACD;;;OAGG;IACH,MAAM,aAAa,GAAa,gBAAgB,CAAC,MAAM,GAAG,CAAC;QACzD,CAAC,CAAC,CAAC,gCAAgC,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;QACrE,CAAC,CAAC,EAAE,CAAC;IAEP,OAAO;QACL,UAAU,SAAS,UAAU;QAC7B,qCAAqC,eAAe,MAAM;QAC1D,+BAA+B,aAAa,MAAM;QAClD,sCAAsC,SAAS,MAAM;QACrD,4CAA4C,eAAe,MAAM;QACjE,+CAA+C;QAC/C,oCAAoC,aAAa,MAAM;QACvD,0CAA0C,YAAY,MAAM;QAC5D,uCAAuC,YAAY,MAAM;QACzD,GAAG,sBAAsB;QACzB,GAAG,YAAY;QACf,sCAAsC,eAAe,MAAM;QAC3D,uCAAuC,SAAS,MAAM;QACtD,6CAA6C,eAAe,MAAM;QAClE,GAAG,aAAa;QAChB,GAAG,aAAa;KACjB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,4BAA4B,CAAC,MAA2B;IACtE,MAAM,KAAK,GAAa,CAAC,SAAS,CAAC,CAAC;IACpC,KAAK,MAAM,CAAC,SAAS,EAAE,UAAU,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAwB,EAAE,CAAC;QACpF,IAAI,UAAU,KAAK,IAAI,IAAI,OAAO,UAAU,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;YACxF,KAAK,MAAM,CAAC,SAAS,EAAE,UAAU,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,CAAwB,EAAE,CAAC;gBACxF,IAAI,OAAO,UAAU,KAAK,QAAQ,EAAE,CAAC;oBACnC,KAAK,CAAC,IAAI,CAAC,KAAK,6BAA6B,CAAC,SAAS,EAAE,SAAS,CAAC,KAAK,UAAU,GAAG,CAAC,CAAC;gBACzF,CAAC;YACH,CAAC;QACH,CAAC;aAAM,IAAI,OAAO,UAAU,KAAK,QAAQ,EAAE,CAAC;YAC1C,KAAK,CAAC,IAAI,CAAC,eAAe,SAAS,KAAK,UAAU,GAAG,CAAC,CAAC;QACzD,CAAC;IACH,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAChB,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED;;;;;;;;GAQG","sourcesContent":["import type { AvailableLanguage } from '@wildo-ai/saas-models/public-runtime';\n\nimport { buildLocalizedRoutePath } from '../core/routing/internal-routing.utils';\nimport { websiteDesignTokenCssProperty, type WebsiteDesignTokens } from '../schemas/design-tokens/website-design-tokens.shared.schemas';\nimport type { WebsitePageManifest } from '../schemas/manifests/website-page-manifest.shared.schemas';\nimport type { LabelPack } from '../schemas/validators/label-pack.validator';\n\nimport { escapeHtmlAttribute, escapeHtmlText } from './internal/html-escape';\nimport type { WebsiteSiteContext } from './website-site-context';\n\n/**\n * @wildo_source:part:start saas.website.astro.head-renderer facet:layer:astro facet:family:website\n *\n * Head-block renderers — pure string builders the consumer's per-page\n * `.astro` file injects via `<Fragment set:html={...}/>` /\n * `<style set:html={...}></style>`. Two functions:\n *\n * - `renderWebsitePageHeadHtml(opts)` — emits the complete SEO\n * `<head>` block: `<title>`, meta description, full Open Graph\n * card (`og:title` / `og:description` / `og:type` / `og:url` /\n * `og:site_name` / `og:locale` / `og:locale:alternate` / optional\n * `og:image`), Twitter card mirrors, `<link rel=\"canonical\">`,\n * and one `<link rel=\"alternate\" hreflang>` per declared locale\n * (plus an `x-default` for Google's i18n discoverability). Every\n * URL is ABSOLUTE (composed from `websiteConfig.publicOrigin` +\n * the page's `routePath`) — relative hreflang/canonical\n * annotations are not honored consistently across crawlers\n * (Phase 6 audit findings H-B / H-C / M-A).\n * - `renderWebsiteDesignTokensCss(tokens)` — flattens the design-token\n * bundle into `--website-*` custom properties scoped to `:root`.\n * Server-static — zero island JS.\n *\n * **Why pure string builders** (rather than React components or Astro\n * components):\n *\n * 1. **Avoids the `.astro` distribution problem.** A `.astro` shell\n * cannot be re-exported from a TS subpath barrel and `tsc` does\n * not copy `.astro` files to `dist/`. Pure TS sidesteps both.\n * 2. **Avoids the React-island boundary.** Rendering head content\n * via React would require either (a) a server-only React subtree\n * (extra moving part) or (b) hydrating the `<head>` on the client\n * (waste of bundle size for static-build content).\n * 3. **Vitest-friendly.** Pure functions returning strings are\n * trivially asserted against without an Astro test harness.\n *\n * **HTML escaping**: every consumer-supplied string (title,\n * description, OG image URL, hreflang values) is escaped with\n * `escapeHtmlAttribute` / `escapeHtmlText` before interpolation into\n * the emitted markup. Marketing copy frequently contains characters\n * (quotes, ampersands, the rare `<`) that would break the surrounding\n * tag if interpolated raw — escaping is non-negotiable for a public\n * surface.\n *\n * **Failure modes** — `renderWebsitePageHeadHtml` throws when:\n * - `manifest.metaLabelKeys.titleKey` is absent from the pack\n * - `manifest.metaLabelKeys.descriptionKey` is absent from the pack\n * Both throw with the locale + key + actionable next step (`Run\n * \\`wildo website audit\\` or add the entry to <pack-path>.json`).\n */\n\nexport interface RenderWebsitePageHeadHtmlOptions {\n manifest: WebsitePageManifest;\n /**\n * Composed `WebsiteSiteContext` — the renderer reads\n * `websiteConfig.{defaultLocale, locales, publicOrigin}` for the\n * URL/locale surface and `rootConfig.seo.{siteName, defaultOgImageUrl}`\n * for the SEO branding fallback.\n */\n siteContext: WebsiteSiteContext;\n locale: AvailableLanguage;\n labelPack: LabelPack;\n}\n\nexport function renderWebsitePageHeadHtml(opts: RenderWebsitePageHeadHtmlOptions): string {\n const { manifest, siteContext, locale, labelPack } = opts;\n const { rootConfig, websiteConfig } = siteContext;\n\n const titleKey = manifest.metaLabelKeys.titleKey as unknown as string;\n const descriptionKey = manifest.metaLabelKeys.descriptionKey as unknown as string;\n const title = labelPack[titleKey];\n const description = labelPack[descriptionKey];\n\n if (title === undefined) {\n throw new Error(\n `[renderWebsitePageHeadHtml] Label pack for locale \"${locale}\" is missing ` +\n `the page title key \"${titleKey}\" (page ref=\"${manifest.ref as unknown as string}\"). ` +\n `Run \\`wildo website audit\\` or add the entry to your <locale>.json pack.`,\n );\n }\n if (description === undefined) {\n throw new Error(\n `[renderWebsitePageHeadHtml] Label pack for locale \"${locale}\" is missing ` +\n `the page description key \"${descriptionKey}\" (page ref=\"${manifest.ref as unknown as string}\"). ` +\n `Run \\`wildo website audit\\` or add the entry to your <locale>.json pack.`,\n );\n }\n\n /**\n * OG image precedence: per-page `manifest.metaLabelKeys.ogImageUrl`\n * wins; falls back to the site-level `seo.defaultOgImageUrl` so\n * marketing pages without a hero image still produce a richer\n * social-card (Phase 6 audit fix M-A). When neither is present,\n * the Twitter card kind degrades to the smaller `summary` and the\n * `og:image` / `twitter:image` lines are omitted entirely.\n */\n const ogImageUrl = manifest.metaLabelKeys.ogImageUrl ?? rootConfig.seo.defaultOgImageUrl;\n const titleAttr = escapeHtmlAttribute(title);\n const descriptionAttr = escapeHtmlAttribute(description);\n const titleText = escapeHtmlText(title);\n\n /**\n * Absolute URL builder. `prefixDefaultLocale: false` means the\n * default locale lives at the locale-neutral path; every other\n * locale sits under `/<locale>/<routePath>`. `x-default` always\n * resolves to the default-locale path.\n *\n * Every URL emitted here is FULLY-QUALIFIED (composed against\n * `websiteConfig.publicOrigin`) — Google's hreflang spec requires\n * absolute URLs, and `<link rel=\"canonical\">` relies on absolute\n * URLs to survive cross-host fetches (preview deployments, CDN\n * edges). See `WildoMarketingWebsiteConfigSchema.publicOrigin`\n * JSDoc for the full rationale (incl. trailing-slash rejection).\n */\n const routePath = manifest.routePath as unknown as string;\n const siteOrigin = websiteConfig.publicOrigin;\n const defaultLocale = websiteConfig.defaultLocale;\n const currentLocaleStr = String(locale);\n\n /**\n * Compose the absolute URL for `(locale, routePath)` by delegating\n * the locale-prefix decision to the shared `buildLocalizedRoutePath`\n * helper — same routing rule that powers `WebsiteInternalLink`. This\n * guarantees that hreflang/canonical URLs and the chrome's internal\n * links can never disagree about how a locale is prefixed.\n */\n function buildAbsoluteUrl(forLocale: AvailableLanguage): string {\n const path = buildLocalizedRoutePath({\n locale: forLocale,\n defaultLocale,\n routePath,\n });\n return `${siteOrigin}${path}`;\n }\n\n const canonicalUrl = buildAbsoluteUrl(locale);\n\n const hreflangLines: string[] = [];\n for (const entry of websiteConfig.locales) {\n const localeStr = String(entry.locale);\n hreflangLines.push(\n `<link rel=\"alternate\" hreflang=\"${escapeHtmlAttribute(localeStr)}\" href=\"${escapeHtmlAttribute(buildAbsoluteUrl(entry.locale))}\" />`,\n );\n }\n hreflangLines.push(\n `<link rel=\"alternate\" hreflang=\"x-default\" href=\"${escapeHtmlAttribute(buildAbsoluteUrl(defaultLocale))}\" />`,\n );\n\n /**\n * `og:image` + `twitter:image` are emitted only when an image URL\n * is available (page-level override OR site-level fallback).\n * Twitter card type also flips between `summary` (no image) and\n * `summary_large_image` (image present).\n */\n const ogImageLines: string[] = [];\n if (ogImageUrl !== undefined) {\n const ogImageAttr = escapeHtmlAttribute(ogImageUrl);\n ogImageLines.push(\n `<meta property=\"og:image\" content=\"${ogImageAttr}\" />`,\n `<meta name=\"twitter:image\" content=\"${ogImageAttr}\" />`,\n );\n }\n const twitterCardKind = ogImageUrl !== undefined ? 'summary_large_image' : 'summary';\n\n /**\n * `og:locale:alternate` — one per non-current declared locale. Open\n * Graph spec: the `og:locale` field carries the page's primary\n * locale; alternates declare the other locales the page is\n * available in. Emitted in declaration order.\n *\n * Locale format: we emit the bare language code (`en`, `fr`) per\n * the AvailableLanguage atom shape. Open Graph technically\n * recommends `language_TERRITORY` (e.g. `en_US`), but Facebook /\n * LinkedIn / Slack accept the bare language code in practice. The\n * future fix when the platform grows territory-tagged locales is to\n * map at this site (no consumer change required).\n */\n const ogLocaleAlternateLines: string[] = [];\n for (const entry of websiteConfig.locales) {\n const localeStr = String(entry.locale);\n if (localeStr === currentLocaleStr) continue;\n ogLocaleAlternateLines.push(\n `<meta property=\"og:locale:alternate\" content=\"${escapeHtmlAttribute(localeStr)}\" />`,\n );\n }\n\n const siteNameAttr = escapeHtmlAttribute(rootConfig.seo.siteName);\n const canonicalAttr = escapeHtmlAttribute(canonicalUrl);\n const ogLocaleAttr = escapeHtmlAttribute(currentLocaleStr);\n\n /**\n * SITE-WIDE `noindex` (#494) — the directive that actually keeps a site out of search.\n *\n * `robots.txt` `Disallow` governs CRAWLING, not INDEXING: a disallowed URL can still be listed, as a\n * bare URL with no snippet, if anything links to it. Only this directive de-indexes. Until 2026-09-08\n * a site declared `noindex` emitted the `Disallow` and NOT this, so it was closed to well-behaved\n * crawlers and still indexable — the weaker half of the pair.\n *\n * The two are also mutually defeating, which is why `renderRobotsTxt` no longer emits `Disallow: /`\n * for this policy: a crawler barred from fetching the page never reads the directive telling it not\n * to index. To stay out of search you must let the crawler IN and tell it no.\n *\n * `noindex` only — not `noindex, nofollow`. `nofollow` additionally suppresses link discovery, which\n * is a broader claim than \"keep this out of the index\" and is not what the policy declares.\n */\n const robotsDirectives: string[] = [];\n if (rootConfig.seo.robots?.policy === 'noindex') {\n robotsDirectives.push('noindex');\n }\n /**\n * AI-training opt-out (D-AG3) — emitted ONLY when the page manifest opts in via\n * `seo.noaiOptOut: true`. Default is OPT-IN to AI training (the marketing-site default). This\n * emission pairs with the sibling `_headers` entry produced by `renderWebsitePageNoaiHeaders()`\n * for crawlers that don't honor in-document meta.\n *\n * `noai` (block AI training on text) and `noimageai` (block AI training on images) are the two\n * directives the major AI crawlers (GPTBot, ClaudeBot, Google-Extended, etc.) recognise.\n */\n if (manifest.seo?.noaiOptOut === true) {\n robotsDirectives.push('noai', 'noimageai');\n }\n /*\n * ONE `<meta name=\"robots\">`, carrying every directive comma-separated — the canonical shape. Two\n * separate robots meta tags are legal and are read inconsistently; merging avoids the question.\n */\n const noaiMetaLines: string[] = robotsDirectives.length > 0\n ? [`<meta name=\"robots\" content=\"${robotsDirectives.join(', ')}\" />`]\n : [];\n\n return [\n `<title>${titleText}</title>`,\n `<meta name=\"description\" content=\"${descriptionAttr}\" />`,\n `<link rel=\"canonical\" href=\"${canonicalAttr}\" />`,\n `<meta property=\"og:title\" content=\"${titleAttr}\" />`,\n `<meta property=\"og:description\" content=\"${descriptionAttr}\" />`,\n `<meta property=\"og:type\" content=\"website\" />`,\n `<meta property=\"og:url\" content=\"${canonicalAttr}\" />`,\n `<meta property=\"og:site_name\" content=\"${siteNameAttr}\" />`,\n `<meta property=\"og:locale\" content=\"${ogLocaleAttr}\" />`,\n ...ogLocaleAlternateLines,\n ...ogImageLines,\n `<meta name=\"twitter:card\" content=\"${twitterCardKind}\" />`,\n `<meta name=\"twitter:title\" content=\"${titleAttr}\" />`,\n `<meta name=\"twitter:description\" content=\"${descriptionAttr}\" />`,\n ...noaiMetaLines,\n ...hreflangLines,\n ].join('\\n');\n}\n\n/**\n * Flatten `WebsiteDesignTokens` to a `:root { … }` block of CSS custom\n * properties. Naming convention: `--website-<group>-<token>` for nested\n * groups (`colors.primary` → `--website-colors-primary`); top-level\n * string-valued tokens (none today, future-proofed) emit as\n * `--website-<token>`.\n *\n * **Why scoped to `:root`** rather than `<WebsitePageLayout>`'s root\n * div: marketing pages frequently embed third-party widgets (live-chat,\n * analytics overlays) that paint outside the React tree. Scoping\n * tokens to `:root` makes them visible to those widgets too. The\n * design-token surface is small enough that the cost of a broader\n * scope (token-name collisions with consumer-authored `:root` rules)\n * is bounded by the `--website-` prefix.\n */\nexport function renderWebsiteDesignTokensCss(tokens: WebsiteDesignTokens): string {\n const lines: string[] = [':root {'];\n for (const [groupName, groupValue] of Object.entries(tokens) as [string, unknown][]) {\n if (groupValue !== null && typeof groupValue === 'object' && !Array.isArray(groupValue)) {\n for (const [tokenName, tokenValue] of Object.entries(groupValue) as [string, unknown][]) {\n if (typeof tokenValue === 'string') {\n lines.push(` ${websiteDesignTokenCssProperty(groupName, tokenName)}: ${tokenValue};`);\n }\n }\n } else if (typeof groupValue === 'string') {\n lines.push(` --website-${groupName}: ${groupValue};`);\n }\n }\n lines.push('}');\n return lines.join('\\n');\n}\n\n/**\n * @wildo_source:part:end saas.website.astro.head-renderer\n *\n * HTML escape helpers moved to `./internal/html-escape.ts` (Phase 7)\n * so the new `blog-post-head.renderer.ts` sibling can share the same\n * escape contract instead of redefining it. See that module's preamble\n * for the framework-mindset rationale (one escape contract per engine,\n * not one per renderer).\n */\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"website-page-runtime.helper.d.ts","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"website-page-runtime.helper.d.ts","sourceRoot":"","sources":["../../../../src/astro/website-page-runtime.helper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,sCAAsC,CAAC;AAGzE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2DAA2D,CAAC;AACrG,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,4CAA4C,CAAC;AAI5E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAEjE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkEG;AACH,MAAM,WAAW,6BAA6B;IAC5C,gFAAgF;IAChF,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,yEAAyE;IACzE,WAAW,EAAE,kBAAkB,CAAC;IAChC,0DAA0D;IAC1D,aAAa,EAAE,MAAM,CAAC;IACtB,2DAA2D;IAC3D,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;CACjC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,kBAAkB;IACjC,wEAAwE;IACxE,MAAM,EAAE,iBAAiB,CAAC;IAC1B;;;;;;;;OAQG;IACH,aAAa,EAAE,iBAAiB,CAAC;IACjC,qEAAqE;IACrE,SAAS,EAAE,SAAS,CAAC;CACtB;AAED,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,6BAA6B,GAClC,kBAAkB,CAqCpB;AA+BD,+DAA+D"}
|