@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":"website-site-context.d.ts","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"website-site-context.d.ts","sourceRoot":"","sources":["../../../../src/astro/website-site-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,wCAAwC,CAAC;AAC1F,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yDAAyD,CAAC;AACjG,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sCAAsC,CAAC;AAEnF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoEG;AACH,MAAM,WAAW,kBAAkB;IACjC;;;;;;OAMG;IACH,QAAQ,CAAC,UAAU,EAAE,iBAAiB,CAAC;IAEvC;;;;;;;OAOG;IACH,QAAQ,CAAC,aAAa,EAAE,2BAA2B,CAAC;IACpD;;;;;OAKG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,sBAAsB,CAAC;CACrD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,kBAAkB,GACxB,kBAAkB,CAMpB;AACD,6DAA6D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"website-site-context.js","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"website-site-context.js","sourceRoot":"","sources":["../../../../src/astro/website-site-context.ts"],"names":[],"mappings":"AAqGA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,UAAU,yBAAyB,CACvC,KAAyB;IAEzB,OAAO,MAAM,CAAC,MAAM,CAAC;QACnB,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,aAAa,EAAE,KAAK,CAAC,aAAa;QAClC,iBAAiB,EAAE,KAAK,CAAC,iBAAiB;KAC3C,CAAC,CAAC;AACL,CAAC;AACD,6DAA6D","sourcesContent":["import type { WildoMarketingWebsiteConfig } from '../config/wildo-website-config.schemas';\nimport type { WebsiteRootConfig } from '../schemas/manifests/website-root-config.shared.schemas';\nimport type { FrontendProvidersBlock } from '@wildo-ai/saas-models/public-runtime';\n\n/**\n * @wildo_source:part:start saas.website.astro.site-context facet:layer:astro facet:family:website\n *\n * `WebsiteSiteContext` — the aggregated site-level inputs every Astro\n * build-time engine helper consumes. Composes the consumer-authored\n * `WebsiteRootConfig` (page registry, design tokens, navigation chrome,\n * site-level SEO branding) with the per-service `WildoMarketingWebsiteConfig`\n * loaded from `wildo.website.config.ts` (locales, default locale,\n * public origin, and the `appOrigin` / `apiOrigin` the site reaches the\n * product through — settled for THIS build by `loadWebsiteSiteContext`).\n *\n * # Why a dedicated aggregator type (vs. threading both raw configs)\n *\n * The seam-collapse (`saas-website-seam-collapse.md`, Option B) made\n * `WildoMarketingWebsiteConfig` the SINGLE source of truth for the\n * marketing-site routing surface (`defaultLocale`, `locales`,\n * `publicOrigin`). Every engine helper that previously read those\n * fields from `WebsiteRootConfig` now reads them from\n * `WildoMarketingWebsiteConfig` — and four out of five of those helpers\n * also still read OTHER fields from `WebsiteRootConfig` (page\n * manifests, chrome label keys, design tokens, `seo.siteName`,\n * `seo.defaultOgImageUrl`, `skipToMainContentLabelKey`). Threading\n * both raw configs through every helper signature would:\n *\n * 1. **Bloat call sites.** Every consumer `.astro` file would have\n * to import both `WEBSITE_ROOT_CONFIG` and the loaded\n * `WILDO_WEBSITE_CONFIG`, then forward both to every helper.\n * 2. **Spread the consumer-side composition responsibility.**\n * Each `.astro` file would need to know which subset of the\n * configs each helper consumes — a refactor hazard the next time\n * we add a field that the head renderer reads from the website\n * config but the runtime loader does not.\n * 3. **Defeat top-level `await` ergonomics.** The website config\n * load is async (`loadWebsiteConfig` does jiti-backed file I/O);\n * forcing every `.astro` file to `await` it would either spread\n * the await across every page or force every page to receive the\n * already-loaded config from a separate consumer module —\n * duplicating the composition logic this aggregator centralizes.\n *\n * One aggregator, composed once at the consumer's\n * `<service-root>/src/website-site-context.ts`, is the minimum-blast\n * shape: every engine helper takes `siteContext: WebsiteSiteContext`,\n * every `.astro` file imports the singleton `WEBSITE_SITE_CONTEXT`\n * once, and the next field move (consumer-config-only field, or\n * root-config-only field) lands in this type's shape with no\n * helper-signature ripple.\n *\n * # Why frozen (defensive)\n *\n * The composed value is shared across every page render in the build\n * graph. A misbehaving Astro plugin (or a future consumer-side audit\n * subprocess) that mutated either nested config in place would\n * silently corrupt every subsequent page. `Object.freeze` is a cheap\n * top-level guard — the inner configs are themselves already produced\n * by Zod parsers (which return fresh objects), so mutating their\n * fields would require a deliberate `as any` cast at the consumer's\n * call site rather than an accidental in-place edit.\n *\n * # When this type WOULD grow\n *\n * Future inputs that are also \"site-wide\" but neither belong to\n * `WebsiteRootConfig` (chrome-rendered) nor to\n * `WildoMarketingWebsiteConfig` (per-service routing/origins) — e.g.\n * a build-environment block (`production` / `preview` / `dev`), or\n * a feature-flag bag — would land here as a third sibling field\n * rather than being plumbed through every helper signature\n * individually. The aggregator is the right place for that\n * orchestration concern; per-call ergonomics ride along for free.\n */\nexport interface WebsiteSiteContext {\n /**\n * The consumer's `WebsiteRootConfig` — page registry, design tokens,\n * navigation chrome, site-level SEO branding (`siteName`,\n * `defaultOgImageUrl?`), `chromeLabelKeys`,\n * `skipToMainContentLabelKey`. Single source of truth for chrome\n * + per-page meta declarations.\n */\n readonly rootConfig: WebsiteRootConfig;\n\n /**\n * The per-service `WildoMarketingWebsiteConfig` loaded from\n * `wildo.website.config.ts`. Single source of truth for the\n * marketing-site ROUTING + ORIGIN surface (`defaultLocale`,\n * `locales`, `publicOrigin`, `appOrigin`, `apiOrigin`). The two origins\n * are the values for THIS build (override, local dev, authored — see\n * `resolveWebsiteBuildOrigins`), which is why every page reads them here.\n */\n readonly websiteConfig: WildoMarketingWebsiteConfig;\n /**\n * Optional website-facing public provider block materialized from the\n * app's provider graph for the concrete `STATIC_WEBSITE` service.\n * Build-time only: the consumer loads it once and forwards it into the\n * React island as JSON-serializable data.\n */\n readonly frontendProviders?: FrontendProvidersBlock;\n}\n\n/**\n * Compose a `WebsiteSiteContext` from the two source configs. Pure +\n * synchronous — the I/O happens upstream in `loadWebsiteConfig` and\n * in the consumer's `WEBSITE_ROOT_CONFIG` module evaluation.\n *\n * **Defensive freezing**: the returned object is `Object.freeze`-d\n * top-level so the singleton handed to every helper cannot have its\n * `rootConfig` / `websiteConfig` references swapped in place by a\n * misbehaving plugin. The nested configs are NOT deep-frozen — Zod\n * already produces fresh objects per parse, and a deep freeze would\n * cost a recursive walk per build for protection that no realistic\n * plugin would breach.\n *\n * @example\n * ```ts\n * // examples/<app>/website/src/website-site-context.ts\n * import { composeWebsiteSiteContext } from '@wildo-ai/saas-website/astro';\n * import { loadWebsiteConfig } from '@wildo-ai/saas-website/config-loader';\n *\n * import { WEBSITE_ROOT_CONFIG } from './website-root.config';\n *\n * const websiteConfig = await loadWebsiteConfig({ saasRoot: '...' });\n * export const WEBSITE_SITE_CONTEXT = composeWebsiteSiteContext({\n * rootConfig: WEBSITE_ROOT_CONFIG,\n * websiteConfig,\n * });\n * ```\n */\nexport function composeWebsiteSiteContext(\n parts: WebsiteSiteContext,\n): WebsiteSiteContext {\n return Object.freeze({\n rootConfig: parts.rootConfig,\n websiteConfig: parts.websiteConfig,\n frontendProviders: parts.frontendProviders,\n });\n}\n/** @wildo_source:part:end saas.website.astro.site-context */\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"astro-island.d.ts","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"astro-island.d.ts","sourceRoot":"","sources":["../../../src/astro-island.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAOH,cAAc,wBAAwB,CAAC;AACvC;;;;;;;;;;;;;;GAcG"}
|
package/dist/esm/astro.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"astro.d.ts","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"astro.d.ts","sourceRoot":"","sources":["../../../src/astro.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkEG;AAEH,cAAc,8BAA8B,CAAC;AAC7C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qCAAqC,CAAC;AACpD,cAAc,2BAA2B,CAAC;AAC1C;;;;;;;;;;;GAWG;AACH,cAAc,qCAAqC,CAAC;AACpD,cAAc,oCAAoC,CAAC;AACnD,cAAc,kCAAkC,CAAC;AACjD;;;;;;;;GAQG;AACH,cAAc,iCAAiC,CAAC;AAChD;;;;;;;;GAQG;AACH,cAAc,yBAAyB,CAAC;AACxC;;;;;;;;;;;;;;;;;;GAkBG;AACH,cAAc,0BAA0B,CAAC;AACzC;;;;;;;;;;GAUG;AACH,cAAc,kCAAkC,CAAC;AACjD,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC;;;;;;;;GAQG;AACH,cAAc,kEAAkE,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"companion-exports.d.ts","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"companion-exports.d.ts","sourceRoot":"","sources":["../../../src/companion-exports.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,cAAc,iCAAiC,CAAC;AAChD,cAAc,oCAAoC,CAAC;AACnD,cAAc,gDAAgD,CAAC;AAC/D,cAAc,oDAAoD,CAAC;AACnE,cAAc,8DAA8D,CAAC;AAC7E,cAAc,8DAA8D,CAAC;AAC7E,cAAc,0DAA0D,CAAC;AACzE,cAAc,wDAAwD,CAAC;AACvE,cAAc,qDAAqD,CAAC;AACpE,cAAc,2CAA2C,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"companion-exports.js","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"companion-exports.js","sourceRoot":"","sources":["../../../src/companion-exports.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,cAAc,iCAAiC,CAAC;AAChD,cAAc,oCAAoC,CAAC;AACnD,cAAc,gDAAgD,CAAC;AAC/D,cAAc,oDAAoD,CAAC;AACnE,cAAc,8DAA8D,CAAC;AAC7E,cAAc,8DAA8D,CAAC;AAC7E,cAAc,0DAA0D,CAAC;AACzE,cAAc,wDAAwD,CAAC;AACvE,cAAc,qDAAqD,CAAC;AACpE,cAAc,2CAA2C,CAAC","sourcesContent":["/**\n * @wildo-package @wildo-ai/saas-website (companion entry)\n *\n * Narrow, React-free entry point for non-React consumers:\n * - the `wildo-cli`'s page/sitemap/structured-data audit commands\n * - companion subprocesses (e.g. introspection of authored pages)\n * - CI scripts that validate label-pack coverage\n * - anything Astro-build-time that cannot pull React into the\n * evaluation graph\n *\n * @wildo-boundary\n * This file re-exports ONLY from `schemas/**`. It MUST NOT import or\n * re-export anything from `core/**` (React contexts/hooks), `astro/**`\n * (Astro/Node adapter), `mdx/**` or `config/**`, and MUST NOT import\n * React, Astro or `@mdx-js/*`. The SEO and structured-data renderers\n * are React-free string builders too, but they live on `/astro` beside\n * the build-time helpers that call them, and are reached from there.\n * Enforced by `src/__tests__/bundle-isolation.test.ts` and\n * `schemas/__tests__/companion-exports-react-free.test.ts`.\n */\n\nexport * from './schemas/refs/page-ref.schemas';\nexport * from './schemas/refs/section-ref.schemas';\nexport * from './schemas/label-keys/website-label-key.schemas';\nexport * from './schemas/sections/website-section-category.shared';\nexport * from './schemas/sections/website-section-definition.shared.schemas';\nexport * from './schemas/design-tokens/website-design-tokens.shared.schemas';\nexport * from './schemas/manifests/website-page-manifest.shared.schemas';\nexport * from './schemas/manifests/website-root-config.shared.schemas';\nexport * from './schemas/blog/blog-post-frontmatter.shared.schemas';\nexport * from './schemas/validators/label-pack.validator';\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WebsiteInternalButton.d.ts","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"WebsiteInternalButton.d.ts","sourceRoot":"","sources":["../../../../../src/components/low-level/WebsiteInternalButton.tsx"],"names":[],"mappings":"AAAA,OAAc,EAAE,KAAK,aAAa,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAOlE,OAAO,EAEL,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EAC1B,MAAM,iBAAiB,CAAC;AAmBzB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgEG;AACH,MAAM,WAAW,0BAA0B;IACzC;;;;;;OAMG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,oBAAoB,CAAC;IAC/B,IAAI,CAAC,EAAE,iBAAiB,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,wBAAgB,qBAAqB,CAAC,EACpC,SAAS,EACT,OAAO,EACP,IAAI,EACJ,SAAS,EACT,KAAK,EACL,EAAE,EACF,SAAS,EACT,QAAQ,GACT,EAAE,0BAA0B,GAAG,SAAS,CA+CxC;yBAxDe,qBAAqB;;;AA0DrC,qEAAqE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WebsiteInternalLink.d.ts","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"WebsiteInternalLink.d.ts","sourceRoot":"","sources":["../../../../../src/components/low-level/WebsiteInternalLink.tsx"],"names":[],"mappings":"AAAA,OAAc,EAAE,KAAK,aAAa,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AASlE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwEG;AACH,MAAM,WAAW,wBAAwB;IACvC;;;;;;OAMG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,SAAS,CAAC;CACrB;AAqCD,wBAAgB,mBAAmB,CAAC,EAClC,SAAS,EACT,SAAS,EACT,KAAK,EACL,EAAE,EACF,SAAS,EACT,QAAQ,GACT,EAAE,wBAAwB,GAAG,SAAS,CA8CtC;yBArDe,mBAAmB;;;AAuDnC,mEAAmE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"define-website-config.d.ts","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"define-website-config.d.ts","sourceRoot":"","sources":["../../../../src/config/define-website-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AAEH,OAAO,EAEL,KAAK,2BAA2B,EAChC,KAAK,gCAAgC,EACtC,MAAM,gCAAgC,CAAC;AAExC;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,gCAAgC,GACvC,2BAA2B,CAI7B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"define-website-config.js","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"define-website-config.js","sourceRoot":"","sources":["../../../../src/config/define-website-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AAEH,OAAO,EACL,iCAAiC,GAGlC,MAAM,gCAAgC,CAAC;AAExC;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,UAAU,mBAAmB,CACjC,MAAwC;IAExC,MAAM,MAAM,GAAG,iCAAiC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAC/D,gCAAgC,CAAC,MAAM,CAAC,CAAC;IACzC,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,SAAS,gCAAgC,CAAC,OAAoC;IAC5E,gEAAgE;IAChE,iEAAiE;IACjE,sDAAsD;AACxD,CAAC","sourcesContent":["/**\n * @fileoverview Author helper for `wildo.website.config.ts` files.\n *\n * `defineWebsiteConfig({...})` mirrors the `defineSaasConfig` /\n * `defineInfraEnvConfig` helpers from `@wildo-ai/platform-config-lib`\n * and `defineTechnicalDocConfig` from `@wildo-ai/saas-technical-doc`:\n * it wraps `WildoMarketingWebsiteConfigSchema.parse(...)` with full\n * IDE autocompletion on the input shape, and emits a parse-time\n * stderr warning for any authored field that has no runtime\n * consumer yet.\n *\n * # `@unimplemented` channel — current state\n *\n * The warner has ZERO branches today because every OPTIONAL field that\n * exists is already wired (`csp?` now flows through the website-local\n * CSP sync step), and every REQUIRED field has a runtime consumer too —\n * `appOrigin` reaches every CTA through `WebsiteRuntimeContext.appOrigin`,\n * settled per build by `loadWebsiteSiteContext`. After the seam-collapse (`saas-website-seam-collapse.md`\n * — SHIPPED), the routing/origin fields (`publicOrigin`,\n * `defaultLocale`, `locales`) are consumed directly by every Astro\n * engine helper via `WebsiteSiteContext`, so they are no longer\n * candidates for a \"deferred wiring\" warning either. The\n * `@unimplemented` warner is for fields that are parsed-but-unused AND\n * opt-in — firing on every Astro build for a required field would drown\n * the dev console.\n *\n * Two valid futures will trigger adding warner branches:\n * 1. A future optional field lands (e.g. `seo?.{title,description}`,\n * `structuredData?.organization`) whose runtime wiring ships\n * in a later PR — that would get\n * `if (config.seo !== undefined) warn('seo', ...)` until the\n * renderer reads it.\n * 2. A required field temporarily loses its consumer (e.g. a\n * large refactor splits the SEO renderer into two PRs); the\n * branch fires unconditionally for the duration.\n *\n * The scaffold is kept (and the no-warning contract is pinned by\n * the test suite) so the moment either case lands, the warning\n * channel is one branch addition away — matching the\n * `defineTechnicalDocConfig` shape exactly so future authors\n * grepping for `@unimplemented` find a uniform pattern.\n *\n * Why we don't reuse `platform-config-lib`'s\n * `createUnimplementedFieldWarner`:\n *\n * `platform-config-lib` is the platform-level boundary; this engine\n * package is downstream of it for everything else. Importing the\n * warner from `platform-config-lib` would invert the dependency\n * direction (engine → platform-config), creating the same architectural\n * defect Step 7.5 set out to fix. The warning format is small and\n * stable enough to inline here — kept structurally similar to the\n * platform-config-lib warner so readers grepping for `@unimplemented`\n * still find both surfaces.\n */\n\nimport {\n WildoMarketingWebsiteConfigSchema,\n type WildoMarketingWebsiteConfig,\n type WildoMarketingWebsiteConfigInput,\n} from './wildo-website-config.schemas';\n\n/**\n * Define a per-service marketing-website config. Provides full IDE\n * autocompletion on the input shape, runs Zod validation (fail-closed\n * on typos / unknown keys / out-of-range values / cross-field\n * invariants like `defaultLocale ∈ locales`), and emits parse-time\n * `@unimplemented` warnings for fields that are authored but have no\n * runtime consumer yet.\n *\n * Throws on validation failure — same contract as `defineSaasConfig`\n * and `defineTechnicalDocConfig`.\n *\n * @example\n * ```ts\n * import { AvailableLanguage } from '@wildo-ai/saas-models/public-runtime';\n * import { defineWebsiteConfig } from '@wildo-ai/saas-website';\n *\n * export default defineWebsiteConfig({\n * publicOrigin: 'https://wonder-todos.example.com',\n * appOrigin: 'https://app.wonder-todos.example.com',\n * defaultLocale: AvailableLanguage.EN,\n * locales: [\n * { locale: AvailableLanguage.EN, path: 'en' },\n * { locale: AvailableLanguage.FR, path: 'fr' },\n * ],\n * });\n * ```\n */\nexport function defineWebsiteConfig(\n config: WildoMarketingWebsiteConfigInput,\n): WildoMarketingWebsiteConfig {\n const parsed = WildoMarketingWebsiteConfigSchema.parse(config);\n emitUnimplementedRuntimeWarnings(parsed);\n return parsed;\n}\n\n/**\n * Emit `@unimplemented` parse-time warnings for every authored\n * OPTIONAL field on `WildoMarketingWebsiteConfig` that is parsed\n * but has no runtime consumer yet.\n *\n * Branches today: ZERO. The one OPTIONAL field that exists (`csp?`) is\n * fully wired through the website-local CSP sync/build path. After the\n * seam-collapse (`saas-website-seam-collapse.md` — SHIPPED) the\n * routing/origin fields are consumed directly by every Astro engine\n * helper via `WebsiteSiteContext`, and `appOrigin` / `apiOrigin` are\n * settled per build by `loadWebsiteSiteContext` and read by every page.\n * A required field could never fire this warner anyway — it would fire\n * on every parse.\n *\n * The function exists as a structural mirror of\n * `defineTechnicalDocConfig.emitUnimplementedRuntimeWarnings` so\n * readers grepping for `emitUnimplementedRuntimeWarnings` find a\n * uniform pattern across every `defineXxxConfig` helper.\n *\n * # Adding a branch (recipe)\n *\n * When the first OPTIONAL `@unimplemented` field lands (most\n * likely candidate: `seo?.{title,description}`), add the warner\n * machinery here: a `seen` set + a\n * `warn(fieldPath, sliceId)` helper that formats as\n * `[wildo-website-config] @unimplemented field '...'` and writes\n * to `process.stderr`, then the matching `if (config.<field> !==\n * undefined)` branch. Copy the exact shape from\n * `define-tech-doc-config.ts > emitUnimplementedRuntimeWarnings`\n * to keep the message format identical across helpers. The test\n * suite pins the zero-warnings contract — flip its expectation in\n * the same commit.\n */\nfunction emitUnimplementedRuntimeWarnings(_config: WildoMarketingWebsiteConfig): void {\n // Intentional no-op — see function JSDoc for why a no-op is the\n // correct shape today (optional fields are either fully wired or\n // absent) and the recipe for adding the first branch.\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/config/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,cAAc,yBAAyB,CAAC;AACxC,cAAc,gCAAgC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"load-website-config.d.ts","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"load-website-config.d.ts","sourceRoot":"","sources":["../../../../src/config/load-website-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAKH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,gCAAgC,CAAC;AAGlF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,oCAAoC,4BAA4B,CAAC;AAkB9E;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CACrC,kBAAkB,EAAE,MAAM,EAC1B,MAAM,EAAE,2BAA2B,GAClC,IAAI,CAEN;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,IAAI,IAAI,CAE9C;AAED;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;;OAIG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;;OAKG;IACH,UAAU,EAAE,eAAe,CAAC;CAC7B;AAED;;;;GAIG;AACH,wBAAgB,gCAAgC,CAC9C,UAAU,EAAE,eAAe,GAC1B,CAAC,MAAM,EAAE,eAAe,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC,CAe/C;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,qBAAqB,GAC1B,OAAO,CAAC,2BAA2B,CAAC,CA+CtC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"load-website-config.js","sourceRoot":"","sources":["../../../src/config/load-website-config.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @fileoverview Per-service `wildo.website.config.ts` loader.\n *\n * Mirrors the `loadTechnicalDocConfig()` shape used by\n * `platform/factory/wildo-dev-companion/src/technical-doc/services/technical-doc.companion.service.ts`\n * (see `saas-technical-doc.md` Step 7.5). Discovers the unique\n * `frontendType: STATIC_WEBSITE` service in the SaaS config, joins\n * `<saasRoot>/<service.path>/wildo.website.config.ts`, and loads it\n * via the jiti-backed `loadConfig` from `@wildo-ai/platform-config-lib`.\n *\n * The actual Zod validation runs INSIDE `defineWebsiteConfig` (which\n * the per-service config file invokes via\n * `export default defineWebsiteConfig({...})`), so by the time\n * `loadConfig` returns we already hold a parsed\n * `WildoMarketingWebsiteConfig`.\n *\n * @wildo-boundary\n * This module is **Node-only**. It imports `loadConfig` and\n * `AppFrontendType` from `@wildo-ai/platform-config-lib`, which\n * transitively pulls `jiti`, `node:fs`, `node:path`, `node:crypto`,\n * and other Node-only API surface. It MUST NOT be imported from\n * any browser-bundled entry of this engine package — specifically\n * NOT from `index.ts`, `companion-exports.ts`, or `astro-island.ts`.\n * The dedicated `./config-loader` package subpath exists precisely\n * to keep this surface OUT of the React-island bundle (enforced by\n * `__tests__/bundle-isolation.test.ts`).\n *\n * The Astro build's `astro.config.ts` (a Node-side file by\n * construction) is the canonical caller. A future companion\n * subprocess that audits the marketing surface would also call\n * from here. Browser bundles MUST NOT.\n */\n\nimport { existsSync } from 'node:fs';\nimport { resolve as resolvePath } from 'node:path';\nimport { AppFrontendType, loadConfig } from '@wildo-ai/platform-config-lib';\nimport type { WildoSaasConfig } from '@wildo-ai/platform-config-lib';\nimport type { WildoMarketingWebsiteConfig } from './wildo-website-config.schemas';\n\nimport { describeThrownValue } from '@wildo-ai/saas-models/public-runtime';\n/**\n * Conventional file name for the per-service marketing-website\n * configuration (Step 7.5 refactor — `saas-website-slice.md` /\n * `saas-technical-doc.md` Step 7.5 mirror). Authored at the website\n * service root next to `package.json` / `astro.config.ts`.\n *\n * Discovery is \"by convention, not by configuration\":\n *\n * - The loader locates this file by walking the `services` entry\n * whose `frontendType === STATIC_WEBSITE` and joining\n * `<saasRoot>/<service.path>/`. The saas-config schema's\n * `superRefine` already enforces \"at most one STATIC_WEBSITE\n * service per app\", so the lookup is deterministic — apps can\n * name the website service anything (`'website'`, `'marketing'`,\n * `'site'`).\n *\n * - File presence is the enable signal — there is NO `enabled`\n * field on `WildoMarketingWebsiteConfigSchema`. A STATIC_WEBSITE\n * service without this file is treated as a misconfiguration\n * and the loader throws (rather than silently shipping an Astro\n * build with no `i18n` routing / no canonical origin).\n */\nexport const PER_SERVICE_WEBSITE_CONFIG_FILE_NAME = 'wildo.website.config.ts';\n\n/**\n * Memoization slot scoped to the loader module. Keyed by absolute\n * config file path so multiple SaaS configs evaluated in the same\n * process (rare in production — possible in tests) do not collide.\n *\n * Lifetime: the loader is invoked from the Astro build / dev-server\n * pipeline, both of which restart the Node process on every dev-stack\n * reload (the watcher that picks up changes to `wildo.website.config.ts`\n * is the same watcher that reboots the build). A stale entry here\n * therefore cannot outlive a config edit.\n *\n * Tests can pre-populate a known path via {@link primeWebsiteConfigCache}\n * to skip the jiti round-trip when exercising downstream consumers.\n */\nconst WEBSITE_CONFIG_CACHE = new Map<string, WildoMarketingWebsiteConfig>();\n\n/**\n * Test helper — pre-populate the loader's memoization slot for a\n * given absolute config path. Mirrors the\n * `cachedTechnicalDocConfig` pre-population trick used by\n * `technical-doc.publish-openapi.service.test.ts` so the loader's\n * downstream consumers can be exercised in tests without writing a\n * `.ts` file to disk + invoking jiti.\n *\n * Production callers MUST NOT use this helper.\n */\nexport function primeWebsiteConfigCache(\n absoluteConfigPath: string,\n config: WildoMarketingWebsiteConfig,\n): void {\n WEBSITE_CONFIG_CACHE.set(resolvePath(absoluteConfigPath), config);\n}\n\n/**\n * Test helper — clear every cached entry. Use in `beforeEach`/`afterEach`\n * to prevent test cross-contamination.\n */\nexport function clearWebsiteConfigCache(): void {\n WEBSITE_CONFIG_CACHE.clear();\n}\n\n/**\n * Arguments for {@link loadWebsiteConfig}. Kept as a structured shape\n * (rather than positional `loadWebsiteConfig(saasRoot, saasConfig)`)\n * so that future per-call options (e.g. an explicit `serviceKey`\n * override for multi-website apps the day the singleton invariant\n * relaxes) slot in without breaking callers.\n */\nexport interface LoadWebsiteConfigArgs {\n /**\n * Absolute path to the SaaS app root — the directory holding\n * `wildo.saas.config.ts`. Used to resolve `<saasRoot>/<service.path>/`\n * for the website service folder.\n */\n saasRoot: string;\n /**\n * The already-parsed SaaS config (load once via\n * `loadConfig<WildoSaasConfig>(...)` upstream and pass it down here\n * — re-parsing per call would invoke jiti twice for one logical\n * load).\n */\n saasConfig: WildoSaasConfig;\n}\n\n/**\n * The application's one `STATIC_WEBSITE` service, as `[serviceKey, service]`.\n * Throws when none is declared: the service folder is what owns\n * `wildo.website.config.ts`, so a website build without one has nothing to load.\n */\nexport function resolveStaticWebsiteServiceEntry(\n saasConfig: WildoSaasConfig,\n): [string, WildoSaasConfig['services'][string]] {\n const websiteServiceEntry = Object.entries(saasConfig.services).find(\n ([, service]) => service.frontendType === AppFrontendType.STATIC_WEBSITE,\n );\n if (!websiteServiceEntry) {\n throw new Error(\n '[wildo-website-config] No frontend service with `frontendType: STATIC_WEBSITE` is declared in '\n + 'wildo.saas.config.ts. The marketing-website Astro build / loader requires an authored '\n + 'STATIC_WEBSITE service because that service folder owns the per-service '\n + '`wildo.website.config.ts` (publicOrigin, appOrigin, defaultLocale, locales). '\n + 'Add a STATIC_WEBSITE service in wildo.saas.config.ts and ship the per-service '\n + 'config file under it.',\n );\n }\n return websiteServiceEntry;\n}\n\n/**\n * Discover and load the per-service marketing-website config.\n *\n * Resolution algorithm (mirrors\n * `TechnicalDocCompanionService.loadTechnicalDocConfig`):\n *\n * 1. Find the unique service in `saasConfig.services` whose\n * `frontendType === STATIC_WEBSITE`. The saas-config\n * `superRefine` guarantees there is at most one — if there is\n * none, throw with the misconfiguration message.\n * 2. Resolve `<saasRoot>/<service.path>/wildo.website.config.ts`.\n * 3. If the file does not exist, throw (file presence IS the\n * enable signal — a STATIC_WEBSITE service without this file\n * is broken).\n * 4. Check the memoization slot keyed on the absolute path; return\n * the cached value if present.\n * 5. Call `loadConfig<WildoMarketingWebsiteConfig>(absolutePath)`.\n * jiti evaluates the file, which invokes `defineWebsiteConfig`,\n * which invokes `WildoMarketingWebsiteConfigSchema.parse` and\n * emits any `@unimplemented` warnings. The returned object is\n * already-validated — we trust the in-file `defineWebsiteConfig`\n * contract here (mirrors the technical-doc loader pattern in\n * `TechnicalDocCompanionService.loadTechnicalDocConfig`, which\n * also does NOT re-parse). A file that hand-rolls the default\n * export without going through `defineWebsiteConfig` would\n * surface as a downstream type mismatch — acceptable given the\n * authoring contract is explicit and grep-discoverable.\n * 6. Memoize and return.\n *\n * @throws Error with a `[wildo-website-config]` prefix if no\n * STATIC_WEBSITE service is declared, the per-service config\n * file is missing, or the file's default export does not\n * pass `WildoMarketingWebsiteConfigSchema`. The loader\n * intentionally throws plain `Error`s rather than building a\n * `WildoBackendError` so it stays usable from the Astro\n * build pipeline (which has no DI container) — the companion\n * can wrap with `errorBuilder` at the call site if needed\n * (mirrors how `loadConfig` itself throws plain `Error`).\n */\nexport async function loadWebsiteConfig(\n args: LoadWebsiteConfigArgs,\n): Promise<WildoMarketingWebsiteConfig> {\n const { saasRoot, saasConfig } = args;\n const websiteServiceEntry = resolveStaticWebsiteServiceEntry(saasConfig);\n\n const [websiteServiceKey, websiteService] = websiteServiceEntry;\n const websiteConfigPath = resolvePath(\n saasRoot,\n websiteService.path,\n PER_SERVICE_WEBSITE_CONFIG_FILE_NAME,\n );\n\n const cached = WEBSITE_CONFIG_CACHE.get(websiteConfigPath);\n if (cached !== undefined) {\n return cached;\n }\n\n if (!existsSync(websiteConfigPath)) {\n throw new Error(\n `[wildo-website-config] STATIC_WEBSITE service '${websiteServiceKey}' is declared in `\n + `wildo.saas.config.ts but the required per-service config file `\n + `'${PER_SERVICE_WEBSITE_CONFIG_FILE_NAME}' is missing under `\n + `'${websiteService.path}/'. File presence IS the enable signal `\n + '(saas-website-slice.md / saas-technical-doc.md Step 7.5 — the legacy '\n + '`marketingWebsite` block on wildo.saas.config.ts was dropped in favor of '\n + 'a per-service file). Create '\n + `'${websiteConfigPath}' with `\n + '`export default defineWebsiteConfig({ ... })` from `@wildo-ai/saas-website`.',\n );\n }\n\n let loaded: WildoMarketingWebsiteConfig;\n try {\n loaded = await loadConfig<WildoMarketingWebsiteConfig>(websiteConfigPath);\n } catch (e) {\n const cause = describeThrownValue(e);\n throw new Error(\n `[wildo-website-config] Failed to load per-service marketing-website config at `\n + `${websiteConfigPath}. The file MUST do `\n + '`export default defineWebsiteConfig({...})` and pass '\n + 'WildoMarketingWebsiteConfigSchema (`z.strictObject` — unknown keys reject; '\n + '`defaultLocale` must appear in `locales`). '\n + `Cause: ${cause}`,\n );\n }\n\n WEBSITE_CONFIG_CACHE.set(websiteConfigPath, loaded);\n return loaded;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"load-website-config.js","sourceRoot":"","sources":["../../../../src/config/load-website-config.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @fileoverview Per-service `wildo.website.config.ts` loader.\n *\n * Mirrors the `loadTechnicalDocConfig()` shape used by\n * `platform/factory/wildo-dev-companion/src/technical-doc/services/technical-doc.companion.service.ts`\n * (see `saas-technical-doc.md` Step 7.5). Discovers the unique\n * `frontendType: STATIC_WEBSITE` service in the SaaS config, joins\n * `<saasRoot>/<service.path>/wildo.website.config.ts`, and loads it\n * via the jiti-backed `loadConfig` from `@wildo-ai/platform-config-lib`.\n *\n * The actual Zod validation runs INSIDE `defineWebsiteConfig` (which\n * the per-service config file invokes via\n * `export default defineWebsiteConfig({...})`), so by the time\n * `loadConfig` returns we already hold a parsed\n * `WildoMarketingWebsiteConfig`.\n *\n * @wildo-boundary\n * This module is **Node-only**. It imports `loadConfig` and\n * `AppFrontendType` from `@wildo-ai/platform-config-lib`, which\n * transitively pulls `jiti`, `node:fs`, `node:path`, `node:crypto`,\n * and other Node-only API surface. It MUST NOT be imported from\n * any browser-bundled entry of this engine package — specifically\n * NOT from `index.ts`, `companion-exports.ts`, or `astro-island.ts`.\n * The dedicated `./config-loader` package subpath exists precisely\n * to keep this surface OUT of the React-island bundle (enforced by\n * `__tests__/bundle-isolation.test.ts`).\n *\n * The Astro build's `astro.config.ts` (a Node-side file by\n * construction) is the canonical caller. A future companion\n * subprocess that audits the marketing surface would also call\n * from here. Browser bundles MUST NOT.\n */\n\nimport { existsSync } from 'node:fs';\nimport { resolve as resolvePath } from 'node:path';\nimport { AppFrontendType, loadConfig } from '@wildo-ai/platform-config-lib';\nimport type { WildoSaasConfig } from '@wildo-ai/platform-config-lib';\nimport type { WildoMarketingWebsiteConfig } from './wildo-website-config.schemas';\n\nimport { describeThrownValue } from '@wildo-ai/saas-models/public-runtime';\n/**\n * Conventional file name for the per-service marketing-website\n * configuration (Step 7.5 refactor — `saas-website-slice.md` /\n * `saas-technical-doc.md` Step 7.5 mirror). Authored at the website\n * service root next to `package.json` / `astro.config.ts`.\n *\n * Discovery is \"by convention, not by configuration\":\n *\n * - The loader locates this file by walking the `services` entry\n * whose `frontendType === STATIC_WEBSITE` and joining\n * `<saasRoot>/<service.path>/`. The saas-config schema's\n * `superRefine` already enforces \"at most one STATIC_WEBSITE\n * service per app\", so the lookup is deterministic — apps can\n * name the website service anything (`'website'`, `'marketing'`,\n * `'site'`).\n *\n * - File presence is the enable signal — there is NO `enabled`\n * field on `WildoMarketingWebsiteConfigSchema`. A STATIC_WEBSITE\n * service without this file is treated as a misconfiguration\n * and the loader throws (rather than silently shipping an Astro\n * build with no `i18n` routing / no canonical origin).\n */\nexport const PER_SERVICE_WEBSITE_CONFIG_FILE_NAME = 'wildo.website.config.ts';\n\n/**\n * Memoization slot scoped to the loader module. Keyed by absolute\n * config file path so multiple SaaS configs evaluated in the same\n * process (rare in production — possible in tests) do not collide.\n *\n * Lifetime: the loader is invoked from the Astro build / dev-server\n * pipeline, both of which restart the Node process on every dev-stack\n * reload (the watcher that picks up changes to `wildo.website.config.ts`\n * is the same watcher that reboots the build). A stale entry here\n * therefore cannot outlive a config edit.\n *\n * Tests can pre-populate a known path via {@link primeWebsiteConfigCache}\n * to skip the jiti round-trip when exercising downstream consumers.\n */\nconst WEBSITE_CONFIG_CACHE = new Map<string, WildoMarketingWebsiteConfig>();\n\n/**\n * Test helper — pre-populate the loader's memoization slot for a\n * given absolute config path. Mirrors the\n * `cachedTechnicalDocConfig` pre-population trick used by\n * `technical-doc.publish-openapi.service.test.ts` so the loader's\n * downstream consumers can be exercised in tests without writing a\n * `.ts` file to disk + invoking jiti.\n *\n * Production callers MUST NOT use this helper.\n */\nexport function primeWebsiteConfigCache(\n absoluteConfigPath: string,\n config: WildoMarketingWebsiteConfig,\n): void {\n WEBSITE_CONFIG_CACHE.set(resolvePath(absoluteConfigPath), config);\n}\n\n/**\n * Test helper — clear every cached entry. Use in `beforeEach`/`afterEach`\n * to prevent test cross-contamination.\n */\nexport function clearWebsiteConfigCache(): void {\n WEBSITE_CONFIG_CACHE.clear();\n}\n\n/**\n * Arguments for {@link loadWebsiteConfig}. Kept as a structured shape\n * (rather than positional `loadWebsiteConfig(saasRoot, saasConfig)`)\n * so that future per-call options (e.g. an explicit `serviceKey`\n * override for multi-website apps the day the singleton invariant\n * relaxes) slot in without breaking callers.\n */\nexport interface LoadWebsiteConfigArgs {\n /**\n * Absolute path to the SaaS app root — the directory holding\n * `wildo.saas.config.ts`. Used to resolve `<saasRoot>/<service.path>/`\n * for the website service folder.\n */\n saasRoot: string;\n /**\n * The already-parsed SaaS config (load once via\n * `loadConfig<WildoSaasConfig>(...)` upstream and pass it down here\n * — re-parsing per call would invoke jiti twice for one logical\n * load).\n */\n saasConfig: WildoSaasConfig;\n}\n\n/**\n * The application's one `STATIC_WEBSITE` service, as `[serviceKey, service]`.\n * Throws when none is declared: the service folder is what owns\n * `wildo.website.config.ts`, so a website build without one has nothing to load.\n */\nexport function resolveStaticWebsiteServiceEntry(\n saasConfig: WildoSaasConfig,\n): [string, WildoSaasConfig['services'][string]] {\n const websiteServiceEntry = Object.entries(saasConfig.services).find(\n ([, service]) => service.frontendType === AppFrontendType.STATIC_WEBSITE,\n );\n if (!websiteServiceEntry) {\n throw new Error(\n '[wildo-website-config] No frontend service with `frontendType: STATIC_WEBSITE` is declared in '\n + 'wildo.saas.config.ts. The marketing-website Astro build / loader requires an authored '\n + 'STATIC_WEBSITE service because that service folder owns the per-service '\n + '`wildo.website.config.ts` (publicOrigin, appOrigin, defaultLocale, locales). '\n + 'Add a STATIC_WEBSITE service in wildo.saas.config.ts and ship the per-service '\n + 'config file under it.',\n );\n }\n return websiteServiceEntry;\n}\n\n/**\n * Discover and load the per-service marketing-website config.\n *\n * Resolution algorithm (mirrors\n * `TechnicalDocCompanionService.loadTechnicalDocConfig`):\n *\n * 1. Find the unique service in `saasConfig.services` whose\n * `frontendType === STATIC_WEBSITE`. The saas-config\n * `superRefine` guarantees there is at most one — if there is\n * none, throw with the misconfiguration message.\n * 2. Resolve `<saasRoot>/<service.path>/wildo.website.config.ts`.\n * 3. If the file does not exist, throw (file presence IS the\n * enable signal — a STATIC_WEBSITE service without this file\n * is broken).\n * 4. Check the memoization slot keyed on the absolute path; return\n * the cached value if present.\n * 5. Call `loadConfig<WildoMarketingWebsiteConfig>(absolutePath)`.\n * jiti evaluates the file, which invokes `defineWebsiteConfig`,\n * which invokes `WildoMarketingWebsiteConfigSchema.parse` and\n * emits any `@unimplemented` warnings. The returned object is\n * already-validated — we trust the in-file `defineWebsiteConfig`\n * contract here (mirrors the technical-doc loader pattern in\n * `TechnicalDocCompanionService.loadTechnicalDocConfig`, which\n * also does NOT re-parse). A file that hand-rolls the default\n * export without going through `defineWebsiteConfig` would\n * surface as a downstream type mismatch — acceptable given the\n * authoring contract is explicit and grep-discoverable.\n * 6. Memoize and return.\n *\n * @throws Error with a `[wildo-website-config]` prefix if no\n * STATIC_WEBSITE service is declared, the per-service config\n * file is missing, or the file's default export does not\n * pass `WildoMarketingWebsiteConfigSchema`. The loader\n * intentionally throws plain `Error`s rather than building a\n * `WildoBackendError` so it stays usable from the Astro\n * build pipeline (which has no DI container) — the companion\n * can wrap with `errorBuilder` at the call site if needed\n * (mirrors how `loadConfig` itself throws plain `Error`).\n */\nexport async function loadWebsiteConfig(\n args: LoadWebsiteConfigArgs,\n): Promise<WildoMarketingWebsiteConfig> {\n const { saasRoot, saasConfig } = args;\n const websiteServiceEntry = resolveStaticWebsiteServiceEntry(saasConfig);\n\n const [websiteServiceKey, websiteService] = websiteServiceEntry;\n const websiteConfigPath = resolvePath(\n saasRoot,\n websiteService.path,\n PER_SERVICE_WEBSITE_CONFIG_FILE_NAME,\n );\n\n const cached = WEBSITE_CONFIG_CACHE.get(websiteConfigPath);\n if (cached !== undefined) {\n return cached;\n }\n\n if (!existsSync(websiteConfigPath)) {\n throw new Error(\n `[wildo-website-config] STATIC_WEBSITE service '${websiteServiceKey}' is declared in `\n + `wildo.saas.config.ts but the required per-service config file `\n + `'${PER_SERVICE_WEBSITE_CONFIG_FILE_NAME}' is missing under `\n + `'${websiteService.path}/'. File presence IS the enable signal `\n + '(saas-website-slice.md / saas-technical-doc.md Step 7.5 — the legacy '\n + '`marketingWebsite` block on wildo.saas.config.ts was dropped in favor of '\n + 'a per-service file). Create '\n + `'${websiteConfigPath}' with `\n + '`export default defineWebsiteConfig({ ... })` from `@wildo-ai/saas-website`.',\n );\n }\n\n let loaded: WildoMarketingWebsiteConfig;\n try {\n loaded = await loadConfig<WildoMarketingWebsiteConfig>(websiteConfigPath);\n } catch (e) {\n const cause = describeThrownValue(e);\n throw new Error(\n `[wildo-website-config] Failed to load per-service marketing-website config at `\n + `${websiteConfigPath}. The file MUST do `\n + '`export default defineWebsiteConfig({...})` and pass '\n + 'WildoMarketingWebsiteConfigSchema (`z.strictObject` — unknown keys reject; '\n + '`defaultLocale` must appear in `locales`). '\n + `Cause: ${cause}`,\n );\n }\n\n WEBSITE_CONFIG_CACHE.set(websiteConfigPath, loaded);\n return loaded;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"load-website-pricing-catalog.d.ts","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"load-website-pricing-catalog.d.ts","sourceRoot":"","sources":["../../../../src/config/load-website-pricing-catalog.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAC;AAEjF,MAAM,WAAW,8BAA8B;IAC7C,+FAA+F;IAC/F,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC3C;AAED,wBAAsB,yBAAyB,CAAC,KAAK,GAAE,8BAAmC,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAczH;AACD,wEAAwE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"load-website-pricing-catalog.js","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"load-website-pricing-catalog.js","sourceRoot":"","sources":["../../../../src/config/load-website-pricing-catalog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EACL,mCAAmC,EACnC,UAAU,EACV,qBAAqB,GAEtB,MAAM,+BAA+B,CAAC;AAavC,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAAC,QAAwC,EAAE;IACxF,MAAM,cAAc,GAAG,qBAAqB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACxD,IAAI,cAAc,CAAC,QAAQ,KAAK,IAAI,IAAI,cAAc,CAAC,cAAc,KAAK,IAAI,EAAE,CAAC;QAC/E,MAAM,IAAI,KAAK,CACb,gEAAgE,KAAK,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,IAAI;cAC1F,kDAAkD,CACvD,CAAC;IACJ,CAAC;IACD,MAAM,UAAU,GAAG,MAAM,UAAU,CAAkB,cAAc,CAAC,cAAc,CAAC,CAAC;IACpF,OAAO,mCAAmC,CAAC;QACzC,QAAQ,EAAE,cAAc,CAAC,QAAQ;QACjC,UAAU;QACV,GAAG,CAAC,KAAK,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,EAAE,CAAC;KAC/E,CAAC,CAAC;AACL,CAAC;AACD,wEAAwE","sourcesContent":["/**\n * @wildo_source:part:start saas.website.config.pricing-catalog-loader facet:layer:astro facet:family:website facet:audience:app-developer\n *\n * `loadWebsitePricingCatalog` — the application's public pricing catalogue, for a website's pricing\n * page (#1835). Call it in the page's `.astro` frontmatter, pass the result to the bridge island as\n * `pricingCatalog`, and read it in the section with `useWebsitePricingCatalog()`; build the page's\n * `AggregateOffer` from the same value with `buildWebsitePricingOfferStructuredData`.\n *\n * It walks up to `wildo.saas.config.ts` the way `loadWebsiteSiteContext` does, and reads the billing\n * catalogue the backend sells from — the `productDefinitions` of the application's shared modules —\n * through `loadApplicationPublicPricingCatalog`, from SOURCE, so a website image that builds only the\n * website still sees the current catalogue.\n *\n * Separate from `loadWebsiteSiteContext` on purpose: loading the shared graph costs seconds, and only a\n * pricing page needs it. Every other page's build stays as fast as it was.\n *\n * **Node-only.** Lives on the `/config-loader` subpath; it must never reach a React-island bundle.\n */\nimport {\n loadApplicationPublicPricingCatalog,\n loadConfig,\n resolveWorkspaceScope,\n type WildoSaasConfig,\n} from '@wildo-ai/platform-config-lib';\nimport type { PublicPricingCatalog } from '@wildo-ai/saas-models/public-runtime';\n\nexport interface LoadWebsitePricingCatalogInput {\n /** Where to start the walk up to `wildo.saas.config.ts`. Defaults to the working directory. */\n readonly cwd?: string | undefined;\n /**\n * Keep only plans this scope may subscribe to (a `ResourcePrimaryScope` wire value, e.g.\n * `'organizations'`). Omit when the catalogue sells to one audience.\n */\n readonly targetScope?: string | undefined;\n}\n\nexport async function loadWebsitePricingCatalog(input: LoadWebsitePricingCatalogInput = {}): Promise<PublicPricingCatalog> {\n const workspaceScope = resolveWorkspaceScope(input.cwd);\n if (workspaceScope.saasRoot === null || workspaceScope.saasConfigPath === null) {\n throw new Error(\n `[saas-website] No wildo.saas.config.ts found walking up from ${input.cwd ?? process.cwd()}. `\n + 'Run astro from inside the application workspace.',\n );\n }\n const saasConfig = await loadConfig<WildoSaasConfig>(workspaceScope.saasConfigPath);\n return loadApplicationPublicPricingCatalog({\n saasRoot: workspaceScope.saasRoot,\n saasConfig,\n ...(input.targetScope === undefined ? {} : { targetScope: input.targetScope }),\n });\n}\n/** @wildo_source:part:end saas.website.config.pricing-catalog-loader */\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"load-website-site-context.d.ts","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"load-website-site-context.d.ts","sourceRoot":"","sources":["../../../../src/config/load-website-site-context.ts"],"names":[],"mappings":"AA0CA,OAAO,EAML,KAAK,2BAA2B,EAEjC,MAAM,+BAA+B,CAAC;AAEvC,OAAO,EAA6B,KAAK,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACnG,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yDAAyD,CAAC;AAEjG,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,gCAAgC,CAAC;AAElF,2FAA2F;AAC3F,MAAM,WAAW,sBAAsB;IACrC,8FAA8F;IAC9F,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,0DAA0D;IAC1D,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACzC;AAED,MAAM,WAAW,+BAA+B;IAC9C,uEAAuE;IACvE,QAAQ,CAAC,aAAa,EAAE,2BAA2B,CAAC;IACpD,gCAAgC;IAChC,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,gFAAgF;IAChF,QAAQ,CAAC,YAAY,EAAE,2BAA2B,CAAC;IACnD,qDAAqD;IACrD,QAAQ,CAAC,SAAS,EAAE,sBAAsB,CAAC;CAC5C;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,+BAA+B,GAAG,2BAA2B,CAO9G;AAED,MAAM,WAAW,2BAA2B;IAC1C,6CAA6C;IAC7C,QAAQ,CAAC,UAAU,EAAE,iBAAiB,CAAC;IACvC,iIAAiI;IACjI,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,+FAA+F;IAC/F,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,0FAA0F;IAC1F,QAAQ,CAAC,SAAS,CAAC,EAAE,sBAAsB,GAAG,SAAS,CAAC;CACzD;AAED,wBAAsB,sBAAsB,CAAC,KAAK,EAAE,2BAA2B,GAAG,OAAO,CAAC,kBAAkB,CAAC,CA6B5G;AACD,qEAAqE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"load-website-site-context.js","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"load-website-site-context.js","sourceRoot":"","sources":["../../../../src/config/load-website-site-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,WAAW,CAAC;AAEnD,OAAO,EACL,UAAU,EACV,iCAAiC,EACjC,mCAAmC,EACnC,kCAAkC,EAClC,qBAAqB,GAGtB,MAAM,+BAA+B,CAAC;AAEvC,OAAO,EAAE,yBAAyB,EAA2B,MAAM,+BAA+B,CAAC;AAEnG,OAAO,EAAE,iBAAiB,EAAE,gCAAgC,EAAE,MAAM,uBAAuB,CAAC;AAsB5F;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,0BAA0B,CAAC,KAAsC;IAC/E,MAAM,EAAE,aAAa,EAAE,aAAa,EAAE,YAAY,EAAE,SAAS,EAAE,GAAG,KAAK,CAAC;IACxE,MAAM,MAAM,GAAG,CAAC,QAA4B,EAAE,KAAyB,EAAE,QAA4B,EAAsB,EAAE,CAC3H,QAAQ,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,QAAQ,CAAC;IAC9D,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,EAAE,aAAa,CAAC,SAAS,CAAC,IAAI,aAAa,CAAC,SAAS,CAAC;IACpH,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC;IACzF,OAAO,EAAE,GAAG,aAAa,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC;AAC5F,CAAC;AAaD,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,KAAkC;IAC7E,MAAM,cAAc,GAAG,qBAAqB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACxD,IAAI,cAAc,CAAC,QAAQ,KAAK,IAAI,IAAI,cAAc,CAAC,cAAc,KAAK,IAAI,EAAE,CAAC;QAC/E,MAAM,IAAI,KAAK,CACb,gEAAgE,KAAK,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,IAAI;cAC1F,kDAAkD,CACvD,CAAC;IACJ,CAAC;IACD,MAAM,QAAQ,GAAG,cAAc,CAAC,QAAQ,CAAC;IACzC,MAAM,UAAU,GAAG,MAAM,UAAU,CAAkB,cAAc,CAAC,cAAc,CAAC,CAAC;IACpF,MAAM,CAAC,iBAAiB,CAAC,GAAG,gCAAgC,CAAC,UAAU,CAAC,CAAC;IACzE,MAAM,aAAa,GAAG,MAAM,iBAAiB,CAAC,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC,CAAC;IAExE,OAAO,yBAAyB,CAAC;QAC/B,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,aAAa,EAAE,0BAA0B,CAAC;YACxC,aAAa;YACb,aAAa,EAAE,KAAK,CAAC,aAAa;YAClC,YAAY,EAAE,kCAAkC,CAAC,UAAU,CAAC,QAAQ,CAAC;YACrE,SAAS,EAAE,KAAK,CAAC,SAAS,IAAI;gBAC5B,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,iBAAiB;gBACxC,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,iBAAiB;aACzC;SACF,CAAC;QACF,iBAAiB,EAAE,iCAAiC,CAAC;YACnD,YAAY,EAAE,mCAAmC,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;YACxE,UAAU,EAAE,iBAAiB;SAC9B,CAAC;KACH,CAAC,CAAC;AACL,CAAC;AACD,qEAAqE","sourcesContent":["/**\n * @wildo_source:part:start saas.website.config.site-context-loader facet:layer:astro facet:family:website facet:audience:app-developer\n *\n * `loadWebsiteSiteContext` — the ONE way a website workspace builds its\n * `WebsiteSiteContext`. Called once, from the workspace's\n * `src/website-site-context.ts`:\n *\n * ```ts\n * import { loadWebsiteSiteContext } from '@wildo-ai/saas-website/config-loader';\n * import { WEBSITE_ROOT_CONFIG } from './website-root.config';\n *\n * export const WEBSITE_SITE_CONTEXT = await loadWebsiteSiteContext({\n * rootConfig: WEBSITE_ROOT_CONFIG,\n * isDevelopment: import.meta.env?.DEV === true,\n * });\n * ```\n *\n * It walks up from the working directory to `wildo.saas.config.ts`, finds the\n * application's single `STATIC_WEBSITE` service, loads that service's\n * `wildo.website.config.ts` and its materialized provider block, and settles the\n * two origins the site reaches the product through (see\n * {@link resolveWebsiteBuildOrigins}).\n *\n * **Why the engine owns this.** Every website workspace used to carry its own\n * copy of this lookup — the scaffolded ones and the hand-written ones alike —\n * and each copy hardcoded its local API port while the app origin had no local\n * form at all. The result: under `astro dev`, every \"Start free\" CTA pointed at a\n * production-shaped host that resolves to nothing. One loader means one rule.\n *\n * **Why it walks up from the working directory, never from `import.meta.url`.**\n * Astro's prerender pass bundles the calling module into `dist/.prerender/chunks/…`,\n * where a `new URL('../..', import.meta.url)` anchor resolves to `dist/` rather than\n * the application root — every prerendered route then failed with \"Config file not\n * found: …/website/dist/wildo.saas.config.ts\", while `astro dev` hid it by serving\n * from `src/`. Every `astro dev` / `astro build` runs with the website package (or\n * an ancestor) as its working directory, which is inside the application either way.\n *\n * **Node-only.** Lives on the `/config-loader` subpath with the other loaders; it\n * must never reach a React-island bundle.\n */\nimport { resolve as resolvePath } from 'node:path';\n\nimport {\n loadConfig,\n loadMaterializedFrontendProviders,\n resolveFrontendServicesArtifactPath,\n resolveLocalhostApplicationOrigins,\n resolveWorkspaceScope,\n type LocalhostApplicationOrigins,\n type WildoSaasConfig,\n} from '@wildo-ai/platform-config-lib';\n\nimport { composeWebsiteSiteContext, type WebsiteSiteContext } from '../astro/website-site-context';\nimport type { WebsiteRootConfig } from '../schemas/manifests/website-root-config.shared.schemas';\nimport { loadWebsiteConfig, resolveStaticWebsiteServiceEntry } from './load-website-config';\nimport type { WildoMarketingWebsiteConfig } from './wildo-website-config.schemas';\n\n/** Explicit per-build origin overrides — what a deploy build passes as build arguments. */\nexport interface WebsiteOriginOverrides {\n /** `PUBLIC_APP_ORIGIN`: this environment's product origin, derived by the deploy workflow. */\n readonly appOrigin?: string | undefined;\n /** `PUBLIC_API_ORIGIN`: this environment's API origin. */\n readonly apiOrigin?: string | undefined;\n}\n\nexport interface ResolveWebsiteBuildOriginsInput {\n /** The authored `wildo.website.config.ts` — the production default. */\n readonly websiteConfig: WildoMarketingWebsiteConfig;\n /** `true` under `astro dev`. */\n readonly isDevelopment: boolean;\n /** Where the product and API run locally, from the app's own declared ports. */\n readonly localOrigins: LocalhostApplicationOrigins;\n /** Explicit overrides, which win over everything. */\n readonly overrides: WebsiteOriginOverrides;\n}\n\n/**\n * The `websiteConfig` this BUILD serves, with `appOrigin` and `apiOrigin` settled.\n *\n * Precedence, per origin:\n *\n * 1. an explicit override (`PUBLIC_APP_ORIGIN` / `PUBLIC_API_ORIGIN`) — the deploy\n * workflow derives these per environment, so a staging build links into staging;\n * 2. under `astro dev`, the locally running product / API, from the ports the\n * application declares — so a CTA reaches the process `wildo local dev` started;\n * 3. the value authored in `wildo.website.config.ts`.\n *\n * The result replaces the two fields in `websiteConfig` rather than living beside\n * it, because every page already forwards `websiteConfig.appOrigin` /\n * `websiteConfig.apiOrigin` into its island: a second field would be a second\n * source that pages could read instead. `websiteConfig` in a site context is\n * therefore \"the config as it applies to this build\", which the config file was\n * already half-way to being — it reads `PUBLIC_APP_ORIGIN` itself.\n */\nexport function resolveWebsiteBuildOrigins(input: ResolveWebsiteBuildOriginsInput): WildoMarketingWebsiteConfig {\n const { websiteConfig, isDevelopment, localOrigins, overrides } = input;\n const settle = (override: string | undefined, local: string | undefined, authored: string | undefined): string | undefined =>\n override || (isDevelopment ? local : undefined) || authored;\n const appOrigin = settle(overrides.appOrigin, localOrigins.app, websiteConfig.appOrigin) ?? websiteConfig.appOrigin;\n const apiOrigin = settle(overrides.apiOrigin, localOrigins.api, websiteConfig.apiOrigin);\n return { ...websiteConfig, appOrigin, ...(apiOrigin === undefined ? {} : { apiOrigin }) };\n}\n\nexport interface LoadWebsiteSiteContextInput {\n /** The workspace's `WEBSITE_ROOT_CONFIG`. */\n readonly rootConfig: WebsiteRootConfig;\n /** Pass `import.meta.env?.DEV === true` (optional chaining: `astro.config.ts` evaluates the caller before Vite's env exists). */\n readonly isDevelopment: boolean;\n /** Where to start the walk up to `wildo.saas.config.ts`. Defaults to the working directory. */\n readonly cwd?: string | undefined;\n /** Defaults to `PUBLIC_APP_ORIGIN` / `PUBLIC_API_ORIGIN` from the process environment. */\n readonly overrides?: WebsiteOriginOverrides | undefined;\n}\n\nexport async function loadWebsiteSiteContext(input: LoadWebsiteSiteContextInput): Promise<WebsiteSiteContext> {\n const workspaceScope = resolveWorkspaceScope(input.cwd);\n if (workspaceScope.saasRoot === null || workspaceScope.saasConfigPath === null) {\n throw new Error(\n `[saas-website] No wildo.saas.config.ts found walking up from ${input.cwd ?? process.cwd()}. `\n + 'Run astro from inside the application workspace.',\n );\n }\n const saasRoot = workspaceScope.saasRoot;\n const saasConfig = await loadConfig<WildoSaasConfig>(workspaceScope.saasConfigPath);\n const [websiteServiceKey] = resolveStaticWebsiteServiceEntry(saasConfig);\n const websiteConfig = await loadWebsiteConfig({ saasRoot, saasConfig });\n\n return composeWebsiteSiteContext({\n rootConfig: input.rootConfig,\n websiteConfig: resolveWebsiteBuildOrigins({\n websiteConfig,\n isDevelopment: input.isDevelopment,\n localOrigins: resolveLocalhostApplicationOrigins(saasConfig.services),\n overrides: input.overrides ?? {\n appOrigin: process.env.PUBLIC_APP_ORIGIN,\n apiOrigin: process.env.PUBLIC_API_ORIGIN,\n },\n }),\n frontendProviders: loadMaterializedFrontendProviders({\n artifactPath: resolveFrontendServicesArtifactPath(resolvePath(saasRoot)),\n serviceKey: websiteServiceKey,\n }),\n });\n}\n/** @wildo_source:part:end saas.website.config.site-context-loader */\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wildo-website-config.schemas.d.ts","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"wildo-website-config.schemas.d.ts","sourceRoot":"","sources":["../../../../src/config/wildo-website-config.schemas.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsHG;AAGH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,iBAAiB,EAAE,MAAM,oDAAoD,CAAC;AAEvF;;;;;;;;;GASG;AACH,eAAO,MAAM,yBAAyB;;;kBASpC,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAE5E;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,iCAAiC;IAE1C;;;;;;;;;;OAUG;;IASH;;;;;;;;;;;;;;;;;;;OAmBG;;IAGH;;;;;;;;;;;;;;;;;;;;;;OAsBG;;IAWH;;;;;OAKG;;;;;;;;IAGH;;;;;;;;;OASG;;IAGH;;;;;;;;;OASG;;;;;kBAqDH,CAAC;AACL,MAAM,MAAM,2BAA2B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iCAAiC,CAAC,CAAC;AAC5F,MAAM,MAAM,gCAAgC,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iCAAiC,CAAC,CAAC;AACjG,wDAAwD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config-loader.d.ts","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"config-loader.d.ts","sourceRoot":"","sources":["../../../src/config-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,cAAc,8BAA8B,CAAC;AAC7C,cAAc,oCAAoC,CAAC;AACnD,cAAc,uCAAuC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config-loader.js","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"config-loader.js","sourceRoot":"","sources":["../../../src/config-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,cAAc,8BAA8B,CAAC;AAC7C,cAAc,oCAAoC,CAAC;AACnD,cAAc,uCAAuC,CAAC","sourcesContent":["/**\n * @wildo-package @wildo-ai/saas-website (Node-only config-loader entry)\n *\n * Dedicated subpath for the per-service `wildo.website.config.ts`\n * loader (`saas-website-slice.md` Step 7.5 — mirrors the\n * `saas-technical-doc.md` Step 7.5 ship). Consumers:\n *\n * - `examples/wonder-todos/website/astro.config.ts` — wires the\n * loader into the Astro build's `i18n` config.\n * - Future companion subprocesses that introspect the marketing\n * surface (locale coverage audits, SEO-link sanity checks).\n *\n * @wildo-boundary\n * This entry is on the **Node-only** side of the bundle boundary.\n * It re-exports `loadWebsiteConfig` (which transitively pulls\n * `loadConfig` from `@wildo-ai/platform-config-lib` — jiti-backed,\n * uses `node:fs` / `node:path` / `node:crypto`) and is therefore\n * carved into its own package subpath so it cannot leak into the\n * browser-side React-island bundle.\n *\n * Enforced by `engine/saas-website/src/__tests__/bundle-isolation.test.ts`:\n * - This file IS in `ENTRY_FILES` (so the closure walker covers it).\n * - `@wildo-ai/platform-config-lib` is in `ALLOWED_BARE_SPECIFIERS`\n * AND `SSR_ONLY_BARE_SPECIFIERS`, which together prove the\n * specifier reaches the closure ONLY through this entry — never\n * through `index.ts`, `companion-exports.ts`, or `astro-island.ts`.\n *\n * Re-exporting `loadWebsiteConfig` from `index.ts` would (a) drag\n * `@wildo-ai/platform-config-lib`'s heavy graph into the marketing-\n * site browser bundle and (b) trip the SSR-tier negative test\n * above — both fail-loud signals that this subpath separation\n * exists for a reason.\n */\n\nexport * from './config/load-website-config';\nexport * from './config/load-website-site-context';\nexport * from './config/load-website-pricing-catalog';\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InboundContactForm.d.ts","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"InboundContactForm.d.ts","sourceRoot":"","sources":["../../../../../src/core/anonymous-session/InboundContactForm.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAgC,MAAM,OAAO,CAAC;AASrD,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,oCAAoC,CAAC;AACxF,OAAO,EAIL,KAAK,gCAAgC,EACtC,MAAM,gCAAgC,CAAC;AAExC;;;;GAIG;AACH,MAAM,WAAW,wBAAwB;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,0CAA0C;IAC1C,qBAAqB,EAAE,MAAM,CAAC;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,6EAA6E;IAC7E,uBAAuB,EAAE,MAAM,CAAC;IAChC,wFAAwF;IACxF,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AAID,MAAM,WAAW,uBAAuB;IACtC,yFAAyF;IACzF,MAAM,EAAE,6BAA6B,CAAC;IACtC,sEAAsE;IACtE,MAAM,EAAE,wBAAwB,CAAC;IACjC,8EAA8E;IAC9E,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,yGAAyG;IACzG,eAAe,CAAC,EAAE,gCAAgC,CAAC;CACpD;AAUD,wBAAgB,kBAAkB,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,eAAe,EAAE,EAAE,uBAAuB,GAAG,KAAK,CAAC,YAAY,CAgIhI"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InboundContactForm.js","sourceRoot":"","sources":["../../../../src/core/anonymous-session/InboundContactForm.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * InboundContactForm — the marketing site's real lead-capture form (React island).\n *\n * Self-contained + bundle-safe: uses only `react`, the website-local input schema, and\n * a `WebsiteAnonymousSessionClient` instance (constructed by the section/bridge wiring\n * from the site's API-origin config). On submit it validates client-side, then POSTs\n * `/inbound-contacts` through the anonymous-session client — the FIRST submission mints\n * the anonymous session (init header) and the backend stamps the lead's `anonymousUserId`\n * so it can later transpose to the visitor's account on signup/login.\n *\n * Labels are injected rather than read from the website runtime context, so the component stays framework-light and\n * unit-testable; the section wrapper passes the strings it resolved through `useWebsiteLabel`. EVERY label is required\n * and the form carries no words of its own (#1986): a fallback would be English in every locale and would hide the\n * missing label, while a required prop makes an omission a type error in the section, and a key the pack does not\n * carry reaches the page as the key itself — which is what `useWebsiteLabel` returns, so somebody authors it.\n *\n * Submission challenge (#1735): when the application protects the form with a captcha, each submission carries an\n * answer — the consented vendor's token when its browser part runs on the page, otherwise the engine's proof of work,\n * which needs no consent (see `website-submission-challenge.ts`). Nothing for the visitor to click or read: both are\n * invisible, so the form stays operable by keyboard and screen reader exactly as before. A vendor token the backend\n * refuses is retried once with the proof of work; a refusal that survives that shows `challengeRefusedMessage`.\n */\n\nimport React, { useCallback, useState } from \"react\";\nimport { SubmissionChallengeAction, WildoHeaderKeys } from \"@wildo-ai/saas-models/public-runtime\";\nimport {\n INBOUND_CONTACT_NAME_MAX_LENGTH,\n INBOUND_CONTACT_PHONE_MAX_LENGTH,\n InboundContactFormSchema,\n buildInboundContactCreateBody,\n type InboundContactFormValues,\n} from \"./inbound-contact-form.schema\";\nimport type { WebsiteAnonymousSessionClient } from \"./website-anonymous-session-client\";\nimport {\n isSubmissionChallengeRefusal,\n obtainSubmissionChallengeAnswer,\n useWebsiteSubmissionChallengeSource,\n type WebsiteSubmissionChallengeSource,\n} from \"./website-submission-challenge\";\n\n/**\n * Every word the form shows. All required (#1986): the section resolves each through `useWebsiteLabel` and lists its\n * key in `expectedLabelKeys`, so a missing label is a type error in the section or a visible key on the page, never a\n * silent English substitute.\n */\nexport interface InboundContactFormLabels {\n emailLabel: string;\n firstNameLabel: string;\n lastNameLabel: string;\n phoneLabel: string;\n /** OPTIONAL marketing opt-in checkbox. */\n marketingConsentLabel: string;\n submitLabel: string;\n submittingLabel: string;\n successMessage: string;\n errorMessage: string;\n /** The anti-spam check did not pass, even with the proof of work (#1735). */\n challengeRefusedMessage: string;\n /** Shown beside the email field when what was typed is not an email address (#1986). */\n invalidEmailMessage: string;\n}\n\ntype SubmitState = { status: \"idle\" } | { status: \"submitting\" } | { status: \"success\" } | { status: \"error\"; message: string };\n\nexport interface InboundContactFormProps {\n /** The anonymous-session client (constructed by the section wiring from site config). */\n client: WebsiteAnonymousSessionClient;\n /** The section's i18n-resolved labels — every one of them (#1986). */\n labels: InboundContactFormLabels;\n /** Optional callback fired after a successful submission (e.g. analytics). */\n onSubmitted?: () => void;\n /** The page's captcha, for a form rendered outside a page layout (tests). Defaults to the page's own. */\n challengeSource?: WebsiteSubmissionChallengeSource;\n}\n\nconst EMPTY_VALUES: InboundContactFormValues = {\n email: \"\",\n firstName: \"\",\n lastName: \"\",\n phone: \"\",\n marketingConsent: false,\n};\n\nexport function InboundContactForm({ client, labels, onSubmitted, challengeSource }: InboundContactFormProps): React.ReactElement {\n const pageChallengeSource = useWebsiteSubmissionChallengeSource();\n const source = challengeSource ?? pageChallengeSource;\n const [values, setValues] = useState<InboundContactFormValues>(EMPTY_VALUES);\n const [emailInvalid, setEmailInvalid] = useState(false);\n const [submit, setSubmit] = useState<SubmitState>({ status: \"idle\" });\n\n const setField = useCallback(<K extends keyof InboundContactFormValues>(key: K, value: InboundContactFormValues[K]) => {\n setValues((prev) => ({ ...prev, [key]: value }));\n }, []);\n\n const handleSubmit = useCallback(\n async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n setEmailInvalid(false);\n\n const parsed = InboundContactFormSchema.safeParse(values);\n if (!parsed.success) {\n // The schema's own issue messages are never shown: they are English and nobody authors them (#1986). The email\n // is the one field a visitor can get wrong, and it has its label. The other fields' only rule is a length the\n // inputs already enforce with `maxLength`, so reaching a failure there means the values did not come from\n // typing, and the form answers with its generic error rather than inventing a sentence.\n const emailFailed = parsed.error.issues.some((issue) => issue.path[0] === \"email\");\n setEmailInvalid(emailFailed);\n if (!emailFailed) setSubmit({ status: \"error\", message: labels.errorMessage });\n return;\n }\n\n setSubmit({ status: \"submitting\" });\n const body = buildInboundContactCreateBody(parsed.data);\n const send = async (proofOfWorkOnly: boolean): Promise<void> => {\n const answer = await obtainSubmissionChallengeAnswer({ source, action: SubmissionChallengeAction.INBOUND_CONTACT, client, proofOfWorkOnly });\n await client.postAnonymous(\"/inbound-contacts\", body, answer === undefined ? undefined : { [WildoHeaderKeys.SUBMISSION_CHALLENGE_RESPONSE]: answer });\n };\n try {\n try {\n await send(false);\n } catch (error) {\n // A refused challenge is answered ONCE more, with the proof of work: a vendor that scored a real person low,\n // could not be reached, or a site that did not know the backend protects the form must not lock anyone out.\n if (!isSubmissionChallengeRefusal(error)) throw error;\n await send(true);\n }\n setSubmit({ status: \"success\" });\n setValues(EMPTY_VALUES);\n onSubmitted?.();\n } catch (error) {\n // The backend-only `emailUserMatch` triage signal is never returned here, so a\n // failure exposes no account-existence information — just a generic retry.\n setSubmit({\n status: \"error\",\n message: isSubmissionChallengeRefusal(error) ? labels.challengeRefusedMessage : labels.errorMessage,\n });\n }\n },\n [client, values, onSubmitted, source, labels.errorMessage, labels.challengeRefusedMessage],\n );\n\n if (submit.status === \"success\") {\n return (\n <div role=\"status\" aria-live=\"polite\">\n {labels.successMessage}\n </div>\n );\n }\n\n const isSubmitting = submit.status === \"submitting\";\n\n return (\n <form onSubmit={handleSubmit} noValidate>\n <label>\n {labels.emailLabel}\n <input\n type=\"email\"\n name=\"email\"\n required\n value={values.email}\n onChange={(e) => setField(\"email\", e.target.value)}\n aria-invalid={emailInvalid ? true : undefined}\n />\n {emailInvalid ? <span role=\"alert\">{labels.invalidEmailMessage}</span> : null}\n </label>\n\n <label>\n {labels.firstNameLabel}\n <input\n type=\"text\"\n name=\"firstName\"\n maxLength={INBOUND_CONTACT_NAME_MAX_LENGTH}\n value={values.firstName ?? \"\"}\n onChange={(e) => setField(\"firstName\", e.target.value)}\n />\n </label>\n\n <label>\n {labels.lastNameLabel}\n <input\n type=\"text\"\n name=\"lastName\"\n maxLength={INBOUND_CONTACT_NAME_MAX_LENGTH}\n value={values.lastName ?? \"\"}\n onChange={(e) => setField(\"lastName\", e.target.value)}\n />\n </label>\n\n <label>\n {labels.phoneLabel}\n <input\n type=\"tel\"\n name=\"phone\"\n maxLength={INBOUND_CONTACT_PHONE_MAX_LENGTH}\n value={values.phone ?? \"\"}\n onChange={(e) => setField(\"phone\", e.target.value)}\n />\n </label>\n\n <label>\n <input type=\"checkbox\" name=\"marketingConsent\" checked={values.marketingConsent} onChange={(e) => setField(\"marketingConsent\", e.target.checked)} />\n {labels.marketingConsentLabel}\n </label>\n\n {submit.status === \"error\" ? <div role=\"alert\">{submit.message}</div> : null}\n\n <button type=\"submit\" disabled={isSubmitting}>\n {isSubmitting ? labels.submittingLabel : labels.submitLabel}\n </button>\n </form>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"InboundContactForm.js","sourceRoot":"","sources":["../../../../../src/core/anonymous-session/InboundContactForm.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * InboundContactForm — the marketing site's real lead-capture form (React island).\n *\n * Self-contained + bundle-safe: uses only `react`, the website-local input schema, and\n * a `WebsiteAnonymousSessionClient` instance (constructed by the section/bridge wiring\n * from the site's API-origin config). On submit it validates client-side, then POSTs\n * `/inbound-contacts` through the anonymous-session client — the FIRST submission mints\n * the anonymous session (init header) and the backend stamps the lead's `anonymousUserId`\n * so it can later transpose to the visitor's account on signup/login.\n *\n * Labels are injected rather than read from the website runtime context, so the component stays framework-light and\n * unit-testable; the section wrapper passes the strings it resolved through `useWebsiteLabel`. EVERY label is required\n * and the form carries no words of its own (#1986): a fallback would be English in every locale and would hide the\n * missing label, while a required prop makes an omission a type error in the section, and a key the pack does not\n * carry reaches the page as the key itself — which is what `useWebsiteLabel` returns, so somebody authors it.\n *\n * Submission challenge (#1735): when the application protects the form with a captcha, each submission carries an\n * answer — the consented vendor's token when its browser part runs on the page, otherwise the engine's proof of work,\n * which needs no consent (see `website-submission-challenge.ts`). Nothing for the visitor to click or read: both are\n * invisible, so the form stays operable by keyboard and screen reader exactly as before. A vendor token the backend\n * refuses is retried once with the proof of work; a refusal that survives that shows `challengeRefusedMessage`.\n */\n\nimport React, { useCallback, useState } from \"react\";\nimport { SubmissionChallengeAction, WildoHeaderKeys } from \"@wildo-ai/saas-models/public-runtime\";\nimport {\n INBOUND_CONTACT_NAME_MAX_LENGTH,\n INBOUND_CONTACT_PHONE_MAX_LENGTH,\n InboundContactFormSchema,\n buildInboundContactCreateBody,\n type InboundContactFormValues,\n} from \"./inbound-contact-form.schema\";\nimport type { WebsiteAnonymousSessionClient } from \"./website-anonymous-session-client\";\nimport {\n isSubmissionChallengeRefusal,\n obtainSubmissionChallengeAnswer,\n useWebsiteSubmissionChallengeSource,\n type WebsiteSubmissionChallengeSource,\n} from \"./website-submission-challenge\";\n\n/**\n * Every word the form shows. All required (#1986): the section resolves each through `useWebsiteLabel` and lists its\n * key in `expectedLabelKeys`, so a missing label is a type error in the section or a visible key on the page, never a\n * silent English substitute.\n */\nexport interface InboundContactFormLabels {\n emailLabel: string;\n firstNameLabel: string;\n lastNameLabel: string;\n phoneLabel: string;\n /** OPTIONAL marketing opt-in checkbox. */\n marketingConsentLabel: string;\n submitLabel: string;\n submittingLabel: string;\n successMessage: string;\n errorMessage: string;\n /** The anti-spam check did not pass, even with the proof of work (#1735). */\n challengeRefusedMessage: string;\n /** Shown beside the email field when what was typed is not an email address (#1986). */\n invalidEmailMessage: string;\n}\n\ntype SubmitState = { status: \"idle\" } | { status: \"submitting\" } | { status: \"success\" } | { status: \"error\"; message: string };\n\nexport interface InboundContactFormProps {\n /** The anonymous-session client (constructed by the section wiring from site config). */\n client: WebsiteAnonymousSessionClient;\n /** The section's i18n-resolved labels — every one of them (#1986). */\n labels: InboundContactFormLabels;\n /** Optional callback fired after a successful submission (e.g. analytics). */\n onSubmitted?: () => void;\n /** The page's captcha, for a form rendered outside a page layout (tests). Defaults to the page's own. */\n challengeSource?: WebsiteSubmissionChallengeSource;\n}\n\nconst EMPTY_VALUES: InboundContactFormValues = {\n email: \"\",\n firstName: \"\",\n lastName: \"\",\n phone: \"\",\n marketingConsent: false,\n};\n\nexport function InboundContactForm({ client, labels, onSubmitted, challengeSource }: InboundContactFormProps): React.ReactElement {\n const pageChallengeSource = useWebsiteSubmissionChallengeSource();\n const source = challengeSource ?? pageChallengeSource;\n const [values, setValues] = useState<InboundContactFormValues>(EMPTY_VALUES);\n const [emailInvalid, setEmailInvalid] = useState(false);\n const [submit, setSubmit] = useState<SubmitState>({ status: \"idle\" });\n\n const setField = useCallback(<K extends keyof InboundContactFormValues>(key: K, value: InboundContactFormValues[K]) => {\n setValues((prev) => ({ ...prev, [key]: value }));\n }, []);\n\n const handleSubmit = useCallback(\n async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n setEmailInvalid(false);\n\n const parsed = InboundContactFormSchema.safeParse(values);\n if (!parsed.success) {\n // The schema's own issue messages are never shown: they are English and nobody authors them (#1986). The email\n // is the one field a visitor can get wrong, and it has its label. The other fields' only rule is a length the\n // inputs already enforce with `maxLength`, so reaching a failure there means the values did not come from\n // typing, and the form answers with its generic error rather than inventing a sentence.\n const emailFailed = parsed.error.issues.some((issue) => issue.path[0] === \"email\");\n setEmailInvalid(emailFailed);\n if (!emailFailed) setSubmit({ status: \"error\", message: labels.errorMessage });\n return;\n }\n\n setSubmit({ status: \"submitting\" });\n const body = buildInboundContactCreateBody(parsed.data);\n const send = async (proofOfWorkOnly: boolean): Promise<void> => {\n const answer = await obtainSubmissionChallengeAnswer({ source, action: SubmissionChallengeAction.INBOUND_CONTACT, client, proofOfWorkOnly });\n await client.postAnonymous(\"/inbound-contacts\", body, answer === undefined ? undefined : { [WildoHeaderKeys.SUBMISSION_CHALLENGE_RESPONSE]: answer });\n };\n try {\n try {\n await send(false);\n } catch (error) {\n // A refused challenge is answered ONCE more, with the proof of work: a vendor that scored a real person low,\n // could not be reached, or a site that did not know the backend protects the form must not lock anyone out.\n if (!isSubmissionChallengeRefusal(error)) throw error;\n await send(true);\n }\n setSubmit({ status: \"success\" });\n setValues(EMPTY_VALUES);\n onSubmitted?.();\n } catch (error) {\n // The backend-only `emailUserMatch` triage signal is never returned here, so a\n // failure exposes no account-existence information — just a generic retry.\n setSubmit({\n status: \"error\",\n message: isSubmissionChallengeRefusal(error) ? labels.challengeRefusedMessage : labels.errorMessage,\n });\n }\n },\n [client, values, onSubmitted, source, labels.errorMessage, labels.challengeRefusedMessage],\n );\n\n if (submit.status === \"success\") {\n return (\n <div role=\"status\" aria-live=\"polite\">\n {labels.successMessage}\n </div>\n );\n }\n\n const isSubmitting = submit.status === \"submitting\";\n\n return (\n <form onSubmit={handleSubmit} noValidate>\n <label>\n {labels.emailLabel}\n <input\n type=\"email\"\n name=\"email\"\n required\n value={values.email}\n onChange={(e) => setField(\"email\", e.target.value)}\n aria-invalid={emailInvalid ? true : undefined}\n />\n {emailInvalid ? <span role=\"alert\">{labels.invalidEmailMessage}</span> : null}\n </label>\n\n <label>\n {labels.firstNameLabel}\n <input\n type=\"text\"\n name=\"firstName\"\n maxLength={INBOUND_CONTACT_NAME_MAX_LENGTH}\n value={values.firstName ?? \"\"}\n onChange={(e) => setField(\"firstName\", e.target.value)}\n />\n </label>\n\n <label>\n {labels.lastNameLabel}\n <input\n type=\"text\"\n name=\"lastName\"\n maxLength={INBOUND_CONTACT_NAME_MAX_LENGTH}\n value={values.lastName ?? \"\"}\n onChange={(e) => setField(\"lastName\", e.target.value)}\n />\n </label>\n\n <label>\n {labels.phoneLabel}\n <input\n type=\"tel\"\n name=\"phone\"\n maxLength={INBOUND_CONTACT_PHONE_MAX_LENGTH}\n value={values.phone ?? \"\"}\n onChange={(e) => setField(\"phone\", e.target.value)}\n />\n </label>\n\n <label>\n <input type=\"checkbox\" name=\"marketingConsent\" checked={values.marketingConsent} onChange={(e) => setField(\"marketingConsent\", e.target.checked)} />\n {labels.marketingConsentLabel}\n </label>\n\n {submit.status === \"error\" ? <div role=\"alert\">{submit.message}</div> : null}\n\n <button type=\"submit\" disabled={isSubmitting}>\n {isSubmitting ? labels.submittingLabel : labels.submitLabel}\n </button>\n </form>\n );\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inbound-contact-form.schema.d.ts","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"inbound-contact-form.schema.d.ts","sourceRoot":"","sources":["../../../../../src/core/anonymous-session/inbound-contact-form.schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB;;;;GAIG;AACH,eAAO,MAAM,+BAA+B,MAAM,CAAC;AACnD,eAAO,MAAM,gCAAgC,KAAK,CAAC;AAEnD;;;;;;;;GAQG;AACH,eAAO,MAAM,wBAAwB;;;;;;iBAcnC,CAAC;AAEH,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAEhF;;;;GAIG;AACH,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,wBAAwB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAcvG"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inbound-contact-form.schema.js","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"inbound-contact-form.schema.js","sourceRoot":"","sources":["../../../../../src/core/anonymous-session/inbound-contact-form.schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAC;AAEtE;;;;GAIG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAAG,GAAG,CAAC;AACnD,MAAM,CAAC,MAAM,gCAAgC,GAAG,EAAE,CAAC;AAEnD;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC/C,mFAAmF;IACnF,+EAA+E;IAC/E,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE;IAChB,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,+BAA+B,CAAC,CAAC,QAAQ,EAAE;IAC5E,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,+BAA+B,CAAC,CAAC,QAAQ,EAAE;IAC3E,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAC,QAAQ,EAAE;IACzE;;;;;OAKG;IACH,gBAAgB,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;CAC7C,CAAC,CAAC;AAIH;;;;GAIG;AACH,MAAM,UAAU,6BAA6B,CAAC,MAAgC;IAC5E,OAAO;QACL,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE;QACxC,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5D,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACzD,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAChD,OAAO,EAAE;YACP,6FAA6F;YAC7F,0FAA0F;YAC1F,4FAA4F;YAC5F,iBAAiB,EAAE,MAAM,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE;YAC5E,gBAAgB,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SAC3C;KACF,CAAC;AACJ,CAAC","sourcesContent":["/**\n * Inbound-contact form input schema (website-local, bundle-safe).\n *\n * Deliberately a SEPARATE plain-Zod schema, NOT `InboundContactSchema` from\n * `@wildo-ai/saas-models`: that schema is decorator-bound (`.isPrimaryKey()`,\n * `.excludeFromCreate()`, `.isBackendOnly()`, …), so importing it would boot the\n * decorator framework into the browser bundle — forbidden by bundle discipline. The\n * website only needs to validate the handful of client-authored CREATE fields; the\n * backend is the authority on the full contract (it re-validates + injects the\n * server-set fields: `_id`, `anonymousUserId`, `userId`, `emailUserMatch`, timestamps).\n *\n * Mirrors the non-excluded, non-backend-only CREATE inputs of `InboundContact`\n * (`email` required; `firstName` / `lastName` / `phone` optional), plus ONE privacy\n * signal: an OPTIONAL marketing opt-in (mapped to the `privacy.consentedPurposes` set).\n * There is no second, required agreement field: a governed notice, contract or\n * consent interaction must use the separate exact-release receipt contract, because this\n * DTO cannot manufacture legal evidence from a checkbox and a client timestamp.\n * `relatedUserId` is intentionally absent — it is `.excludeFromCreate()` on the\n * resource (sales/admin-only, never client-authored).\n */\n\nimport { z } from \"zod\";\n\nimport { ConsentPurpose } from \"@wildo-ai/saas-models/public-runtime\";\n\n/**\n * Length ceilings of the optional text fields. Exported so the form puts the SAME numbers on its inputs as\n * `maxLength`: the browser then stops a visitor at the ceiling, and a length failure is not something the form ever\n * has to word (#1986).\n */\nexport const INBOUND_CONTACT_NAME_MAX_LENGTH = 200;\nexport const INBOUND_CONTACT_PHONE_MAX_LENGTH = 64;\n\n/**\n * The wire payload POSTed to `/api/v1/inbound-contacts`. `privacy` matches the\n * resource's `InboundContactPrivacySchema` shape (a nested consent object) so the\n * backend accepts it verbatim.\n *\n * No issue carries a message (#1986). A visitor reads the application's own words, which the form takes from its\n * section's label pack (`InboundContactFormLabels.invalidEmailMessage`); a message written here would be English in\n * every locale, and nobody authoring the pack could find it.\n */\nexport const InboundContactFormSchema = z.object({\n // Zod 4: top-level `z.email()` (not the deprecated `z.string().email()`), matching\n // the resource schema. An empty string fails it, so it also covers \"required\".\n email: z.email(),\n firstName: z.string().trim().max(INBOUND_CONTACT_NAME_MAX_LENGTH).optional(),\n lastName: z.string().trim().max(INBOUND_CONTACT_NAME_MAX_LENGTH).optional(),\n phone: z.string().trim().max(INBOUND_CONTACT_PHONE_MAX_LENGTH).optional(),\n /**\n * OPTIONAL marketing opt-in — maps into the resource's `privacy.consentedPurposes` set.\n * It is the only privacy field on this form, and it gates nothing: sending the form is the\n * essential act and never depends on optional consent, so an unchecked box still submits,\n * granting nothing.\n */\n marketingConsent: z.boolean().default(false),\n});\n\nexport type InboundContactFormValues = z.infer<typeof InboundContactFormSchema>;\n\n/**\n * Build the exact JSON body the backend CREATE expects from validated form values.\n * Keeps the `privacy` nesting + the consent purpose mapping in ONE place so the form\n * component stays declarative.\n */\nexport function buildInboundContactCreateBody(values: InboundContactFormValues): Record<string, unknown> {\n return {\n email: values.email.trim().toLowerCase(),\n ...(values.firstName ? { firstName: values.firstName } : {}),\n ...(values.lastName ? { lastName: values.lastName } : {}),\n ...(values.phone ? { phone: values.phone } : {}),\n privacy: {\n // The visitor SAW the opt-in and submitted, so this is a decision either way (#534): the set\n // they granted — MARKETING or nothing — plus a CLAIM that they decided now. The backend's\n // CREATE prefix replaces the instant with its own clock; a browser clock is never recorded.\n consentedPurposes: values.marketingConsent ? [ConsentPurpose.MARKETING] : [],\n consentDecidedAt: new Date().toISOString(),\n },\n };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"website-anonymous-session-client.d.ts","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"website-anonymous-session-client.d.ts","sourceRoot":"","sources":["../../../../../src/core/anonymous-session/website-anonymous-session-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAeH,MAAM,WAAW,oCAAoC;IACnD,mFAAmF;IACnF,SAAS,EAAE,MAAM,CAAC;IAClB,sFAAsF;IACtF,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AAED,iEAAiE;AACjE,qBAAa,4BAA6B,SAAQ,KAAK;aAEnC,MAAM,EAAE,MAAM;aACd,QAAQ,EAAE,MAAM;gBADhB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM;IAMlC;;;OAGG;IACH,IAAI,SAAS,IAAI,MAAM,GAAG,SAAS,CAOlC;CACF;AAED,qBAAa,6BAA6B;IACxC,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAS;gBAEjC,OAAO,EAAE,oCAAoC;IAMzD,8FAA8F;IAC9F,kBAAkB,IAAI,MAAM,GAAG,IAAI;IAInC;;;;;;;;;;;;OAYG;IACH,yBAAyB,IAAI,MAAM;IAMnC,6EAA6E;IAC7E,eAAe,IAAI,OAAO;IAI1B;;;;;;OAMG;IACG,aAAa,CAAC,SAAS,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,YAAY,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,OAAO,CAAC,SAAS,CAAC;IAI1I;;;OAGG;IACG,SAAS,CAAC,SAAS,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;IAYtE;;;;;;;;;OASG;IACG,gBAAgB,CAAC,SAAS,GAAG,OAAO,EACxC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,OAAO;IACb,uHAAuH;IACvH,YAAY,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAC9C,OAAO,CAAC,SAAS,CAAC;IAoBrB,OAAO,CAAC,oBAAoB;IAQ5B,OAAO,CAAC,YAAY;IAcpB,OAAO,CAAC,mBAAmB;IAS3B,OAAO,CAAC,kBAAkB;IAY1B,OAAO,CAAC,OAAO;CAYhB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"website-anonymous-session-client.js","sourceRoot":"","sources":["../../../../src/core/anonymous-session/website-anonymous-session-client.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * WebsiteAnonymousSessionClient\n *\n * A self-contained, browser-only anonymous-session HTTP client for the marketing\n * website. It MIRRORS the SaaS app's `AnonymousSessionContext` protocol WITHOUT\n * importing `@wildo-ai/saas-frontend-lib` — the website ships into a browser bundle\n * that must not pull the decorator framework / heavy frontend graph (bundle\n * discipline). It uses ONLY browser globals (`fetch` / `crypto` / `localStorage`) and\n * the bundle-safe `@wildo-ai/saas-models/public-runtime` atoms. No axios (axios is the\n * SSR tier), no React — a React island context can wrap this class.\n *\n * Protocol (W7.3a):\n * 1. The first anonymous request sends `x-anonymous-session-init: <clientUuid>` — the\n * backend mints an `AnonymousUser` (uuidRef = clientUuid) and returns a signed JWT\n * in the `x-anonymous-session-token` RESPONSE header.\n * 2. Subsequent requests send `x-anonymous-session: <jwt>`.\n * Every request also sends `X-Frontend-Service-Name` (required by the anon backend\n * to accept the request and resolve the frontend identity).\n *\n * CORS (critical): the website is a DIFFERENT origin from the backend API, so the\n * `x-anonymous-session-token` response header is readable by `fetch` ONLY if the\n * backend lists it in `Access-Control-Expose-Headers` (non-safelisted response headers\n * are hidden from cross-origin `fetch`). Without that, the captured token is `null`,\n * the session never advances past init, and every request re-mints a session.\n *\n * Cross-origin continuity: the anonymous session created here (uuidRef) is what the\n * SaaS app transposes on signup/login. It is carried across the apex↔app boundary by\n * the backend-set parent-domain `__wildo_anon_session` cookie — hence `credentials:\n * 'include'`, so that cookie rides same-site requests to the API.\n */\n\nimport { WildoHeaderKeys, MAIN_API_BASE_PATH } from '@wildo-ai/saas-models/public-runtime';\n\n/**\n * localStorage keys. The website is self-contained (mirror-don't-import), so it pins\n * the stable literal values rather than importing the decorator-adjacent shared\n * contract. Cross-origin continuity flows through the backend cookie, NOT this\n * localStorage, so these keys need not match the app's byte-for-byte.\n */\nconst STORAGE_KEYS = {\n anonymousUserId: 'anonymousUserId',\n anonymousSessionToken: 'anonymousSessionToken',\n} as const;\n\nexport interface WebsiteAnonymousSessionClientOptions {\n /** Backend API origin WITHOUT a trailing slash, e.g. `https://api.example.com`. */\n apiOrigin: string;\n /** `X-Frontend-Service-Name` value identifying this marketing site to the backend. */\n frontendServiceName: string;\n}\n\n/** Thrown when an anonymous request returns a non-2xx status. */\nexport class WebsiteAnonymousRequestError extends Error {\n constructor(\n public readonly status: number,\n public readonly bodyText: string,\n ) {\n super(`Anonymous request failed with status ${status}`);\n this.name = 'WebsiteAnonymousRequestError';\n }\n\n /**\n * The refusal's stable `error.code` — what a client branches on (the engine hoists an error's `context.code` there) —\n * or `undefined` when the body is not the engine's error envelope or carries no code.\n */\n get errorCode(): string | undefined {\n try {\n const code = (JSON.parse(this.bodyText) as { error?: { code?: unknown } }).error?.code;\n return typeof code === 'string' ? code : undefined;\n } catch {\n return undefined;\n }\n }\n}\n\nexport class WebsiteAnonymousSessionClient {\n private anonymousUserId: string | null = null;\n private sessionToken: string | null = null;\n private readonly apiBaseUrl: string;\n private readonly frontendServiceName: string;\n\n constructor(options: WebsiteAnonymousSessionClientOptions) {\n this.apiBaseUrl = `${options.apiOrigin.replace(/\\/+$/, '')}${MAIN_API_BASE_PATH}`;\n this.frontendServiceName = options.frontendServiceName;\n this.restoreFromStorage();\n }\n\n /** The anonymous session's relational identity (client uuidRef), or `null` if not started. */\n getAnonymousUserId(): string | null {\n return this.anonymousUserId;\n }\n\n /**\n * Return the anonymous session's relational identity (client uuidRef),\n * STARTING the session first if it has not begun yet. Unlike\n * `getAnonymousUserId()` (which may return `null`), this always yields\n * a stable uuidRef.\n *\n * Used by NESTED anonymous routes that must embed the id in the URL\n * BEFORE the first request — e.g. draft-notes POSTs to\n * `/user-anonymous-users/<uuidRef>/draft-notes`. The id generated here\n * is the SAME one the next `postAnonymous(...)` sends as\n * `x-anonymous-session-init`, so the URL scope and the minted session\n * refer to one anonymous user (no split identity).\n */\n getOrStartAnonymousUserId(): string {\n this.ensureSessionStarted();\n // ensureSessionStarted() guarantees anonymousUserId is non-null.\n return this.anonymousUserId as string;\n }\n\n /** `true` once an init request has succeeded and a session token is held. */\n isSessionActive(): boolean {\n return this.anonymousUserId !== null && this.sessionToken !== null;\n }\n\n /**\n * POST `body` to an anonymous endpoint. `path` is relative to `/api/v1`\n * (e.g. `/inbound-contacts`, or a nested `/anonymous-users/<uuidRef>/draft-notes`).\n * Lazily starts the session on the first call, attaches the anon-session +\n * service-name headers, captures any returned session token, and returns the parsed\n * JSON response. Throws `WebsiteAnonymousRequestError` on a non-2xx status.\n */\n async postAnonymous<TResponse = unknown>(path: string, body: unknown, extraHeaders?: Readonly<Record<string, string>>): Promise<TResponse> {\n return this.requestAnonymous<TResponse>('POST', path, body, extraHeaders);\n }\n\n /**\n * GET a PUBLIC endpoint — one that needs no session (a proof-of-work challenge, #1735). Starts no anonymous session\n * and sends no session header or cookie: asking for a challenge must not mint an identity for the visitor.\n */\n async getPublic<TResponse = unknown>(path: string): Promise<TResponse> {\n const response = await fetch(`${this.apiBaseUrl}${path}`, {\n method: 'GET',\n headers: { [WildoHeaderKeys.FRONTEND_SERVICE_NAME]: this.frontendServiceName },\n credentials: 'omit',\n });\n if (!response.ok) {\n throw new WebsiteAnonymousRequestError(response.status, await safeReadText(response));\n }\n return (await response.json()) as TResponse;\n }\n\n /**\n * Send `body` to an anonymous endpoint with an explicit METHOD.\n *\n * `postAnonymous` above is the POST case and delegates here. This exists because not every anonymous\n * door is a POST and the method is not guessable from the path: the visitor consent door declares\n * `resourceOperationLike: UPDATE_MANY`, which the engine maps to PUT, so a POST to it answers 404 —\n * and this client's one silent caller swallowed exactly that. A caller reads the door's method from\n * the same place it reads the path (`ANONYMOUS_CONSENT_RECORD_METHOD` beside\n * `ANONYMOUS_CONSENT_RECORD_PATH`) rather than inferring it from a method name.\n */\n async requestAnonymous<TResponse = unknown>(\n method: string,\n path: string,\n body: unknown,\n /** Request-specific headers, such as a public form's challenge answer (#1735). Never overrides the session headers. */\n extraHeaders?: Readonly<Record<string, string>>,\n ): Promise<TResponse> {\n this.ensureSessionStarted();\n\n const response = await fetch(`${this.apiBaseUrl}${path}`, {\n method,\n headers: { ...extraHeaders, ...this.buildHeaders() },\n body: JSON.stringify(body),\n // Carry the parent-domain `__wildo_anon_session` cookie to the API so the\n // session is continuous across the apex/app/api origins.\n credentials: 'include',\n });\n\n this.captureSessionToken(response);\n\n if (!response.ok) {\n throw new WebsiteAnonymousRequestError(response.status, await safeReadText(response));\n }\n return (await response.json()) as TResponse;\n }\n\n private ensureSessionStarted(): void {\n if (!this.anonymousUserId) {\n this.anonymousUserId = crypto.randomUUID();\n this.sessionToken = null;\n this.persist();\n }\n }\n\n private buildHeaders(): Record<string, string> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n [WildoHeaderKeys.FRONTEND_SERVICE_NAME]: this.frontendServiceName,\n };\n // Once we hold the JWT, restore the session; before that, the raw UUID inits it.\n if (this.sessionToken) {\n headers[WildoHeaderKeys.ANONYMOUS_SESSION] = this.sessionToken;\n } else if (this.anonymousUserId) {\n headers[WildoHeaderKeys.ANONYMOUS_SESSION_INIT] = this.anonymousUserId;\n }\n return headers;\n }\n\n private captureSessionToken(response: Response): void {\n // Requires the backend to CORS-expose this header (see the class-level CORS note).\n const token = response.headers.get(WildoHeaderKeys.ANONYMOUS_SESSION_TOKEN);\n if (token && token !== this.sessionToken) {\n this.sessionToken = token;\n this.persist();\n }\n }\n\n private restoreFromStorage(): void {\n try {\n const userId = localStorage.getItem(STORAGE_KEYS.anonymousUserId);\n const token = localStorage.getItem(STORAGE_KEYS.anonymousSessionToken);\n if (userId) this.anonymousUserId = userId;\n if (token) this.sessionToken = token;\n } catch {\n // localStorage unavailable (SSR build / privacy mode) — the session stays\n // in-memory for this page load only.\n }\n }\n\n private persist(): void {\n try {\n if (this.anonymousUserId) {\n localStorage.setItem(STORAGE_KEYS.anonymousUserId, this.anonymousUserId);\n }\n if (this.sessionToken) {\n localStorage.setItem(STORAGE_KEYS.anonymousSessionToken, this.sessionToken);\n }\n } catch {\n // Best-effort persistence; a failure only means the session won't survive reload.\n }\n }\n}\n\nasync function safeReadText(response: Response): Promise<string> {\n try {\n return await response.text();\n } catch {\n return '';\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"website-anonymous-session-client.js","sourceRoot":"","sources":["../../../../../src/core/anonymous-session/website-anonymous-session-client.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * WebsiteAnonymousSessionClient\n *\n * A self-contained, browser-only anonymous-session HTTP client for the marketing\n * website. It MIRRORS the SaaS app's `AnonymousSessionContext` protocol WITHOUT\n * importing `@wildo-ai/saas-frontend-lib` — the website ships into a browser bundle\n * that must not pull the decorator framework / heavy frontend graph (bundle\n * discipline). It uses ONLY browser globals (`fetch` / `crypto` / `localStorage`) and\n * the bundle-safe `@wildo-ai/saas-models/public-runtime` atoms. No axios (axios is the\n * SSR tier), no React — a React island context can wrap this class.\n *\n * Protocol (W7.3a):\n * 1. The first anonymous request sends `x-anonymous-session-init: <clientUuid>` — the\n * backend mints an `AnonymousUser` (uuidRef = clientUuid) and returns a signed JWT\n * in the `x-anonymous-session-token` RESPONSE header.\n * 2. Subsequent requests send `x-anonymous-session: <jwt>`.\n * Every request also sends `X-Frontend-Service-Name` (required by the anon backend\n * to accept the request and resolve the frontend identity).\n *\n * CORS (critical): the website is a DIFFERENT origin from the backend API, so the\n * `x-anonymous-session-token` response header is readable by `fetch` ONLY if the\n * backend lists it in `Access-Control-Expose-Headers` (non-safelisted response headers\n * are hidden from cross-origin `fetch`). Without that, the captured token is `null`,\n * the session never advances past init, and every request re-mints a session.\n *\n * Cross-origin continuity: the anonymous session created here (uuidRef) is what the\n * SaaS app transposes on signup/login. It is carried across the apex↔app boundary by\n * the backend-set parent-domain `__wildo_anon_session` cookie — hence `credentials:\n * 'include'`, so that cookie rides same-site requests to the API.\n */\n\nimport { WildoHeaderKeys, MAIN_API_BASE_PATH } from '@wildo-ai/saas-models/public-runtime';\n\n/**\n * localStorage keys. The website is self-contained (mirror-don't-import), so it pins\n * the stable literal values rather than importing the decorator-adjacent shared\n * contract. Cross-origin continuity flows through the backend cookie, NOT this\n * localStorage, so these keys need not match the app's byte-for-byte.\n */\nconst STORAGE_KEYS = {\n anonymousUserId: 'anonymousUserId',\n anonymousSessionToken: 'anonymousSessionToken',\n} as const;\n\nexport interface WebsiteAnonymousSessionClientOptions {\n /** Backend API origin WITHOUT a trailing slash, e.g. `https://api.example.com`. */\n apiOrigin: string;\n /** `X-Frontend-Service-Name` value identifying this marketing site to the backend. */\n frontendServiceName: string;\n}\n\n/** Thrown when an anonymous request returns a non-2xx status. */\nexport class WebsiteAnonymousRequestError extends Error {\n constructor(\n public readonly status: number,\n public readonly bodyText: string,\n ) {\n super(`Anonymous request failed with status ${status}`);\n this.name = 'WebsiteAnonymousRequestError';\n }\n\n /**\n * The refusal's stable `error.code` — what a client branches on (the engine hoists an error's `context.code` there) —\n * or `undefined` when the body is not the engine's error envelope or carries no code.\n */\n get errorCode(): string | undefined {\n try {\n const code = (JSON.parse(this.bodyText) as { error?: { code?: unknown } }).error?.code;\n return typeof code === 'string' ? code : undefined;\n } catch {\n return undefined;\n }\n }\n}\n\nexport class WebsiteAnonymousSessionClient {\n private anonymousUserId: string | null = null;\n private sessionToken: string | null = null;\n private readonly apiBaseUrl: string;\n private readonly frontendServiceName: string;\n\n constructor(options: WebsiteAnonymousSessionClientOptions) {\n this.apiBaseUrl = `${options.apiOrigin.replace(/\\/+$/, '')}${MAIN_API_BASE_PATH}`;\n this.frontendServiceName = options.frontendServiceName;\n this.restoreFromStorage();\n }\n\n /** The anonymous session's relational identity (client uuidRef), or `null` if not started. */\n getAnonymousUserId(): string | null {\n return this.anonymousUserId;\n }\n\n /**\n * Return the anonymous session's relational identity (client uuidRef),\n * STARTING the session first if it has not begun yet. Unlike\n * `getAnonymousUserId()` (which may return `null`), this always yields\n * a stable uuidRef.\n *\n * Used by NESTED anonymous routes that must embed the id in the URL\n * BEFORE the first request — e.g. draft-notes POSTs to\n * `/user-anonymous-users/<uuidRef>/draft-notes`. The id generated here\n * is the SAME one the next `postAnonymous(...)` sends as\n * `x-anonymous-session-init`, so the URL scope and the minted session\n * refer to one anonymous user (no split identity).\n */\n getOrStartAnonymousUserId(): string {\n this.ensureSessionStarted();\n // ensureSessionStarted() guarantees anonymousUserId is non-null.\n return this.anonymousUserId as string;\n }\n\n /** `true` once an init request has succeeded and a session token is held. */\n isSessionActive(): boolean {\n return this.anonymousUserId !== null && this.sessionToken !== null;\n }\n\n /**\n * POST `body` to an anonymous endpoint. `path` is relative to `/api/v1`\n * (e.g. `/inbound-contacts`, or a nested `/anonymous-users/<uuidRef>/draft-notes`).\n * Lazily starts the session on the first call, attaches the anon-session +\n * service-name headers, captures any returned session token, and returns the parsed\n * JSON response. Throws `WebsiteAnonymousRequestError` on a non-2xx status.\n */\n async postAnonymous<TResponse = unknown>(path: string, body: unknown, extraHeaders?: Readonly<Record<string, string>>): Promise<TResponse> {\n return this.requestAnonymous<TResponse>('POST', path, body, extraHeaders);\n }\n\n /**\n * GET a PUBLIC endpoint — one that needs no session (a proof-of-work challenge, #1735). Starts no anonymous session\n * and sends no session header or cookie: asking for a challenge must not mint an identity for the visitor.\n */\n async getPublic<TResponse = unknown>(path: string): Promise<TResponse> {\n const response = await fetch(`${this.apiBaseUrl}${path}`, {\n method: 'GET',\n headers: { [WildoHeaderKeys.FRONTEND_SERVICE_NAME]: this.frontendServiceName },\n credentials: 'omit',\n });\n if (!response.ok) {\n throw new WebsiteAnonymousRequestError(response.status, await safeReadText(response));\n }\n return (await response.json()) as TResponse;\n }\n\n /**\n * Send `body` to an anonymous endpoint with an explicit METHOD.\n *\n * `postAnonymous` above is the POST case and delegates here. This exists because not every anonymous\n * door is a POST and the method is not guessable from the path: the visitor consent door declares\n * `resourceOperationLike: UPDATE_MANY`, which the engine maps to PUT, so a POST to it answers 404 —\n * and this client's one silent caller swallowed exactly that. A caller reads the door's method from\n * the same place it reads the path (`ANONYMOUS_CONSENT_RECORD_METHOD` beside\n * `ANONYMOUS_CONSENT_RECORD_PATH`) rather than inferring it from a method name.\n */\n async requestAnonymous<TResponse = unknown>(\n method: string,\n path: string,\n body: unknown,\n /** Request-specific headers, such as a public form's challenge answer (#1735). Never overrides the session headers. */\n extraHeaders?: Readonly<Record<string, string>>,\n ): Promise<TResponse> {\n this.ensureSessionStarted();\n\n const response = await fetch(`${this.apiBaseUrl}${path}`, {\n method,\n headers: { ...extraHeaders, ...this.buildHeaders() },\n body: JSON.stringify(body),\n // Carry the parent-domain `__wildo_anon_session` cookie to the API so the\n // session is continuous across the apex/app/api origins.\n credentials: 'include',\n });\n\n this.captureSessionToken(response);\n\n if (!response.ok) {\n throw new WebsiteAnonymousRequestError(response.status, await safeReadText(response));\n }\n return (await response.json()) as TResponse;\n }\n\n private ensureSessionStarted(): void {\n if (!this.anonymousUserId) {\n this.anonymousUserId = crypto.randomUUID();\n this.sessionToken = null;\n this.persist();\n }\n }\n\n private buildHeaders(): Record<string, string> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n [WildoHeaderKeys.FRONTEND_SERVICE_NAME]: this.frontendServiceName,\n };\n // Once we hold the JWT, restore the session; before that, the raw UUID inits it.\n if (this.sessionToken) {\n headers[WildoHeaderKeys.ANONYMOUS_SESSION] = this.sessionToken;\n } else if (this.anonymousUserId) {\n headers[WildoHeaderKeys.ANONYMOUS_SESSION_INIT] = this.anonymousUserId;\n }\n return headers;\n }\n\n private captureSessionToken(response: Response): void {\n // Requires the backend to CORS-expose this header (see the class-level CORS note).\n const token = response.headers.get(WildoHeaderKeys.ANONYMOUS_SESSION_TOKEN);\n if (token && token !== this.sessionToken) {\n this.sessionToken = token;\n this.persist();\n }\n }\n\n private restoreFromStorage(): void {\n try {\n const userId = localStorage.getItem(STORAGE_KEYS.anonymousUserId);\n const token = localStorage.getItem(STORAGE_KEYS.anonymousSessionToken);\n if (userId) this.anonymousUserId = userId;\n if (token) this.sessionToken = token;\n } catch {\n // localStorage unavailable (SSR build / privacy mode) — the session stays\n // in-memory for this page load only.\n }\n }\n\n private persist(): void {\n try {\n if (this.anonymousUserId) {\n localStorage.setItem(STORAGE_KEYS.anonymousUserId, this.anonymousUserId);\n }\n if (this.sessionToken) {\n localStorage.setItem(STORAGE_KEYS.anonymousSessionToken, this.sessionToken);\n }\n } catch {\n // Best-effort persistence; a failure only means the session won't survive reload.\n }\n }\n}\n\nasync function safeReadText(response: Response): Promise<string> {\n try {\n return await response.text();\n } catch {\n return '';\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"website-submission-challenge.d.ts","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"website-submission-challenge.d.ts","sourceRoot":"","sources":["../../../../../src/core/anonymous-session/website-submission-challenge.ts"],"names":[],"mappings":"AAgBA,OAAO,EAQL,KAAK,mBAAmB,EAEzB,MAAM,sCAAsC,CAAC;AAI9C,OAAO,EAAgC,KAAK,6BAA6B,EAAE,MAAM,oCAAoC,CAAC;AAEtH,mHAAmH;AACnH,kEAAkE;AAClE,MAAM,WAAW,gCAAgC;IAC/C,wFAAwF;IACxF,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;IAClC,uGAAuG;IACvG,QAAQ,CAAC,cAAc,CAAC,EAAE;QAAE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAA;KAAE,CAAC;CAChG;AAED,mHAAmH;AACnH,wBAAgB,mCAAmC,IAAI,gCAAgC,CAatF;AAED;;;;GAIG;AACH,wBAAsB,+BAA+B,CAAC,MAAM,EAAE;IAC5D,QAAQ,CAAC,MAAM,EAAE,gCAAgC,CAAC;IAClD;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,6BAA6B,EAAE,WAAW,CAAC,CAAC;IAClE,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC;IACnC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAiB9B;AAED,+HAA+H;AAC/H,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAIpE;AACD,+DAA+D"}
|