@wildo-ai/saas-website 1.1.4 → 1.1.6
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 +23 -20
- package/dist/esm/astro/blog-post-bridge.d.ts.map +1 -1
- package/dist/esm/astro/blog-post-bridge.js +13 -6
- 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 +55 -26
- package/dist/esm/astro/bridge-runtime.d.ts.map +1 -1
- package/dist/esm/astro/bridge-runtime.js +21 -7
- 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 +4 -4
- package/dist/esm/astro/label-pack-loader.d.ts.map +1 -1
- package/dist/esm/astro/label-pack-loader.js +14 -4
- 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 +9 -8
- package/dist/esm/astro/robots-renderer.d.ts.map +1 -1
- package/dist/esm/astro/robots-renderer.js +15 -15
- 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 +1 -1
- package/dist/esm/astro/website-page-head.renderer.d.ts.map +1 -1
- package/dist/esm/astro/website-page-head.renderer.js +2 -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 +5 -2
- 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 +8 -15
- package/dist/esm/companion-exports.d.ts.map +1 -1
- package/dist/esm/companion-exports.js +8 -15
- 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 +3 -2
- package/dist/esm/config/define-website-config.d.ts.map +1 -1
- package/dist/esm/config/define-website-config.js +10 -10
- 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 +6 -0
- package/dist/esm/config/load-website-config.d.ts.map +1 -1
- package/dist/esm/config/load-website-config.js +20 -10
- package/dist/esm/config/load-website-config.js.map +1 -1
- package/dist/esm/config/load-website-pricing-catalog.d.ts +13 -0
- package/dist/esm/config/load-website-pricing-catalog.d.ts.map +1 -0
- package/dist/esm/config/load-website-pricing-catalog.js +34 -0
- package/dist/esm/config/load-website-pricing-catalog.js.map +1 -0
- package/dist/esm/config/load-website-site-context.d.ts +53 -0
- package/dist/esm/config/load-website-site-context.d.ts.map +1 -0
- package/dist/esm/config/load-website-site-context.js +98 -0
- package/dist/esm/config/load-website-site-context.js.map +1 -0
- package/dist/esm/config/wildo-website-config.schemas.d.ts +10 -5
- package/dist/esm/config/wildo-website-config.schemas.d.ts.map +1 -1
- package/dist/esm/config/wildo-website-config.schemas.js +10 -5
- package/dist/esm/config/wildo-website-config.schemas.js.map +1 -1
- package/dist/esm/config-loader.d.ts +2 -0
- package/dist/esm/config-loader.d.ts.map +1 -1
- package/dist/esm/config-loader.js +2 -0
- package/dist/esm/config-loader.js.map +1 -1
- package/dist/esm/core/anonymous-session/InboundContactForm.d.ts +26 -6
- package/dist/esm/core/anonymous-session/InboundContactForm.d.ts.map +1 -1
- package/dist/esm/core/anonymous-session/InboundContactForm.js +51 -33
- package/dist/esm/core/anonymous-session/InboundContactForm.js.map +1 -1
- package/dist/esm/core/anonymous-session/inbound-contact-form.schema.d.ts +14 -3
- 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 +28 -13
- 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 +24 -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 +46 -3
- 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 +36 -0
- package/dist/esm/core/anonymous-session/website-submission-challenge.d.ts.map +1 -0
- package/dist/esm/core/anonymous-session/website-submission-challenge.js +69 -0
- package/dist/esm/core/anonymous-session/website-submission-challenge.js.map +1 -0
- package/dist/esm/core/consent/WebsiteConsentBanner.d.ts +6 -0
- package/dist/esm/core/consent/WebsiteConsentBanner.d.ts.map +1 -0
- package/dist/esm/core/consent/WebsiteConsentBanner.js +129 -0
- package/dist/esm/core/consent/WebsiteConsentBanner.js.map +1 -0
- package/dist/esm/core/consent/WebsiteConsentContext.d.ts +85 -0
- package/dist/esm/core/consent/WebsiteConsentContext.d.ts.map +1 -0
- package/dist/esm/core/consent/WebsiteConsentContext.js +89 -0
- package/dist/esm/core/consent/WebsiteConsentContext.js.map +1 -0
- package/dist/esm/core/consent/WebsiteConsentGate.d.ts +27 -0
- package/dist/esm/core/consent/WebsiteConsentGate.d.ts.map +1 -0
- package/dist/esm/core/consent/WebsiteConsentGate.js +9 -0
- package/dist/esm/core/consent/WebsiteConsentGate.js.map +1 -0
- package/dist/esm/core/consent/useWebsiteConsent.d.ts +10 -0
- package/dist/esm/core/consent/useWebsiteConsent.d.ts.map +1 -0
- package/dist/esm/core/consent/useWebsiteConsent.js +17 -0
- package/dist/esm/core/consent/useWebsiteConsent.js.map +1 -0
- package/dist/esm/core/consent/website-consent-storage.d.ts +22 -0
- package/dist/esm/core/consent/website-consent-storage.d.ts.map +1 -0
- package/dist/esm/core/consent/website-consent-storage.js +102 -0
- package/dist/esm/core/consent/website-consent-storage.js.map +1 -0
- package/dist/esm/core/contexts/WebsitePageContext.d.ts.map +1 -1
- package/dist/esm/core/contexts/WebsitePageContext.js.map +1 -1
- package/dist/esm/core/contexts/WebsiteRuntimeContext.d.ts +33 -6
- 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/WebsiteSectionContext.js.map +1 -1
- package/dist/esm/core/contexts/useWebsitePage.d.ts.map +1 -1
- package/dist/esm/core/contexts/useWebsiteRuntime.d.ts +18 -0
- package/dist/esm/core/contexts/useWebsiteRuntime.d.ts.map +1 -1
- package/dist/esm/core/contexts/useWebsiteRuntime.js +28 -2
- 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 +57 -0
- package/dist/esm/core/external-providers/WebsiteProviderComponent.d.ts.map +1 -0
- package/dist/esm/core/external-providers/WebsiteProviderComponent.js +56 -0
- package/dist/esm/core/external-providers/WebsiteProviderComponent.js.map +1 -0
- package/dist/esm/core/external-providers/frontend-provider-registry.website.d.ts +1 -2
- package/dist/esm/core/external-providers/frontend-provider-registry.website.d.ts.map +1 -1
- package/dist/esm/core/external-providers/frontend-provider-registry.website.js.map +1 -1
- package/dist/esm/core/external-providers/provider-component-registry.website.d.ts +63 -0
- package/dist/esm/core/external-providers/provider-component-registry.website.d.ts.map +1 -0
- package/dist/esm/core/external-providers/provider-component-registry.website.js +68 -0
- package/dist/esm/core/external-providers/provider-component-registry.website.js.map +1 -0
- package/dist/esm/core/external-providers/useWebsiteProviderComponent.d.ts +36 -0
- package/dist/esm/core/external-providers/useWebsiteProviderComponent.d.ts.map +1 -0
- package/dist/esm/core/external-providers/useWebsiteProviderComponent.js +59 -0
- package/dist/esm/core/external-providers/useWebsiteProviderComponent.js.map +1 -0
- package/dist/esm/core/external-providers/useWebsiteProviderScripts.d.ts +43 -10
- package/dist/esm/core/external-providers/useWebsiteProviderScripts.d.ts.map +1 -1
- package/dist/esm/core/external-providers/useWebsiteProviderScripts.js +92 -29
- package/dist/esm/core/external-providers/useWebsiteProviderScripts.js.map +1 -1
- package/dist/esm/core/external-providers/website-provider-sdk-activations.d.ts +11 -0
- package/dist/esm/core/external-providers/website-provider-sdk-activations.d.ts.map +1 -0
- package/dist/esm/core/external-providers/website-provider-sdk-activations.js +14 -0
- package/dist/esm/core/external-providers/website-provider-sdk-activations.js.map +1 -0
- package/dist/esm/core/factories/define-website-page-manifest.d.ts.map +1 -1
- package/dist/esm/core/factories/define-website-section.d.ts +3 -3
- package/dist/esm/core/factories/define-website-section.d.ts.map +1 -1
- package/dist/esm/core/factories/define-website-section.js +3 -3
- 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 +21 -1
- package/dist/esm/core/layouts/WebsitePageLayout.d.ts.map +1 -1
- package/dist/esm/core/layouts/WebsitePageLayout.js +39 -8
- 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 +36 -65
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +36 -65
- 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 +35 -6
- 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 +39 -5
- 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 +61 -0
- package/dist/esm/schemas/label-keys/website-consent-label-keys.schemas.d.ts.map +1 -0
- package/dist/esm/schemas/label-keys/website-consent-label-keys.schemas.js +73 -0
- package/dist/esm/schemas/label-keys/website-consent-label-keys.schemas.js.map +1 -0
- 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 +32 -0
- package/dist/esm/schemas/label-keys/website-provider-component-label-keys.schemas.d.ts.map +1 -0
- package/dist/esm/schemas/label-keys/website-provider-component-label-keys.schemas.js +36 -0
- package/dist/esm/schemas/label-keys/website-provider-component-label-keys.schemas.js.map +1 -0
- 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 +6 -5
- 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 +8 -7
- 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-category.shared.js.map +1 -1
- package/dist/esm/schemas/sections/website-section-definition.shared.schemas.d.ts +14 -7
- 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 +14 -7
- 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 +57 -0
- package/dist/esm/schemas/structured-data/pricing-offer-structured-data.shared.d.ts.map +1 -0
- package/dist/esm/schemas/structured-data/pricing-offer-structured-data.shared.js +76 -0
- package/dist/esm/schemas/structured-data/pricing-offer-structured-data.shared.js.map +1 -0
- package/dist/esm/schemas/structured-data/structured-data-reconciliation.shared.d.ts +23 -0
- 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 +20 -2
- 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 +7 -5
- 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/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +6 -29
- package/src/__tests__/bundle-isolation.test.ts +19 -22
- package/src/astro/__tests__/blog-post-bridge.test.tsx +58 -1
- package/src/astro/__tests__/bridge-runtime.test.tsx +44 -1
- package/src/astro/__tests__/label-pack-loader.test.ts +29 -1
- package/src/astro/blog-post-bridge.tsx +36 -26
- package/src/astro/bridge-runtime.tsx +79 -33
- package/src/astro/label-pack-loader.ts +12 -4
- package/src/astro/robots-renderer.ts +15 -15
- package/src/astro/website-page-head.renderer.ts +2 -2
- package/src/astro/website-site-context.ts +5 -2
- package/src/companion-exports.ts +8 -15
- package/src/config/__tests__/website-build-origins.test.ts +52 -0
- package/src/config/define-website-config.ts +10 -10
- package/src/config/load-website-config.ts +27 -15
- package/src/config/load-website-pricing-catalog.ts +52 -0
- package/src/config/load-website-site-context.ts +145 -0
- package/src/config/wildo-website-config.schemas.ts +10 -5
- package/src/config-loader.ts +2 -0
- package/src/core/__tests__/WebsitePageLayout.test.tsx +1 -1
- package/src/core/__tests__/contexts.test.tsx +20 -1
- package/src/core/__tests__/website-consent.test.tsx +312 -0
- package/src/core/anonymous-session/InboundContactForm.tsx +100 -45
- package/src/core/anonymous-session/__tests__/InboundContactForm.labels.test.tsx +108 -0
- package/src/core/anonymous-session/__tests__/InboundContactForm.submission-challenge.test.tsx +139 -0
- package/src/core/anonymous-session/__tests__/inbound-contact-form.schema.test.ts +11 -4
- package/src/core/anonymous-session/inbound-contact-form.schema.ts +30 -13
- package/src/core/anonymous-session/website-anonymous-session-client.ts +52 -3
- package/src/core/anonymous-session/website-submission-challenge.ts +99 -0
- package/src/core/consent/WebsiteConsentBanner.tsx +223 -0
- package/src/core/consent/WebsiteConsentContext.tsx +164 -0
- package/src/core/consent/WebsiteConsentGate.tsx +34 -0
- package/src/core/consent/useWebsiteConsent.ts +18 -0
- package/src/core/consent/website-consent-storage.ts +116 -0
- package/src/core/contexts/WebsiteRuntimeContext.tsx +33 -6
- package/src/core/contexts/useWebsiteRuntime.ts +33 -2
- package/src/core/external-providers/WebsiteProviderComponent.tsx +153 -0
- package/src/core/external-providers/__tests__/frontend-provider-registry.website.test.ts +4 -0
- package/src/core/external-providers/__tests__/website-provider-component.test.tsx +238 -0
- package/src/core/external-providers/__tests__/website-provider-sdks.test.tsx +72 -0
- package/src/core/external-providers/frontend-provider-registry.website.ts +1 -2
- package/src/core/external-providers/provider-component-registry.website.ts +115 -0
- package/src/core/external-providers/useWebsiteProviderComponent.ts +76 -0
- package/src/core/external-providers/useWebsiteProviderScripts.ts +114 -40
- package/src/core/external-providers/website-provider-sdk-activations.ts +23 -0
- package/src/core/factories/define-website-section.ts +3 -3
- package/src/core/layouts/WebsitePageLayout.tsx +75 -7
- package/src/index.ts +38 -65
- package/src/schemas/__tests__/design-token-css-property.test.ts +72 -0
- package/src/schemas/__tests__/pricing-offer-structured-data.test.ts +122 -0
- package/src/schemas/__tests__/structured-data-reconciliation.test.ts +35 -0
- package/src/schemas/design-tokens/website-design-tokens.shared.schemas.ts +45 -5
- package/src/schemas/label-keys/__tests__/scaffolded-consent-label-pack.parity.test.ts +83 -0
- package/src/schemas/label-keys/website-consent-label-keys.schemas.ts +78 -0
- package/src/schemas/label-keys/website-provider-component-label-keys.schemas.ts +40 -0
- package/src/schemas/manifests/website-root-config.shared.schemas.ts +10 -9
- package/src/schemas/sections/website-section-definition.shared.schemas.ts +14 -7
- package/src/schemas/structured-data/pricing-offer-structured-data.shared.ts +144 -0
- package/src/schemas/structured-data/structured-data-reconciliation.shared.ts +46 -1
- package/src/schemas/structured-data/website-structured-data.shared.schemas.ts +7 -5
- package/dist/esm/core/external-providers/provider-scripts.website.d.ts +0 -68
- package/dist/esm/core/external-providers/provider-scripts.website.d.ts.map +0 -1
- package/dist/esm/core/external-providers/provider-scripts.website.js +0 -99
- package/dist/esm/core/external-providers/provider-scripts.website.js.map +0 -1
- package/src/core/external-providers/__tests__/provider-scripts.website.test.ts +0 -146
- package/src/core/external-providers/provider-scripts.website.ts +0 -126
|
@@ -4,6 +4,8 @@ import { resolve } from 'node:path';
|
|
|
4
4
|
import type { AvailableLanguage } from '@wildo-ai/saas-models/public-runtime';
|
|
5
5
|
|
|
6
6
|
import type { WebsiteLabelKey } from '../schemas/label-keys/website-label-key.schemas';
|
|
7
|
+
import { WEBSITE_CONSENT_LABEL_KEYS } from '../schemas/label-keys/website-consent-label-keys.schemas';
|
|
8
|
+
import { WEBSITE_PROVIDER_COMPONENT_LABEL_KEYS } from '../schemas/label-keys/website-provider-component-label-keys.schemas';
|
|
7
9
|
import type { WebsitePageManifest } from '../schemas/manifests/website-page-manifest.shared.schemas';
|
|
8
10
|
import type { WebsiteRootConfig } from '../schemas/manifests/website-root-config.shared.schemas';
|
|
9
11
|
import {
|
|
@@ -20,10 +22,10 @@ import type { WebsiteSiteContext } from './website-site-context';
|
|
|
20
22
|
*
|
|
21
23
|
* `label-pack-loader.ts` — synchronous, fs-backed loader for
|
|
22
24
|
* marketing-site label packs. Consumed by:
|
|
23
|
-
* -
|
|
24
|
-
* locale's pack
|
|
25
|
-
* -
|
|
26
|
-
*
|
|
25
|
+
* - each page's `.astro` frontmatter, through `loadWebsitePageRuntime`,
|
|
26
|
+
* which reads the active locale's pack during SSR / pre-render,
|
|
27
|
+
* - `wildo website audit`, which validates every locale pack against
|
|
28
|
+
* every manifest and section through `loadAndValidateAllLabelPacks`,
|
|
27
29
|
* - any build-time hook that wants to fail fast on a missing or
|
|
28
30
|
* malformed pack.
|
|
29
31
|
*
|
|
@@ -207,6 +209,12 @@ function composeEffectiveChromeLabelKeys(
|
|
|
207
209
|
): readonly WebsiteLabelKey[] {
|
|
208
210
|
const set = new Set<WebsiteLabelKey>(rootConfig.chromeLabelKeys);
|
|
209
211
|
set.add(rootConfig.skipToMainContentLabelKey);
|
|
212
|
+
// The visitor consent panel is framework-owned in the same way (#534): required in every pack, so a site
|
|
213
|
+
// that turns on analytics can never ship it with an unlabelled way to refuse.
|
|
214
|
+
for (const key of WEBSITE_CONSENT_LABEL_KEYS) set.add(key);
|
|
215
|
+
// So is the provider-component slot's refusal wording (#1703): a site placing an address lookup must not
|
|
216
|
+
// ship its consent-required or unavailable state as a raw key.
|
|
217
|
+
for (const key of WEBSITE_PROVIDER_COMPONENT_LABEL_KEYS) set.add(key);
|
|
210
218
|
return Array.from(set);
|
|
211
219
|
}
|
|
212
220
|
|
|
@@ -34,10 +34,12 @@ import type { WebsiteSiteContext } from './website-site-context';
|
|
|
34
34
|
* 2. Per-bot blocks for `disallowedCrawlers` (allow-by-default
|
|
35
35
|
* direction) — each named bot gets its own `User-agent: <bot>`
|
|
36
36
|
* + `Disallow: /` block.
|
|
37
|
-
* 3. Catch-all `User-agent: *` block
|
|
38
|
-
* `
|
|
39
|
-
*
|
|
40
|
-
* `disallow[]` path-prefix entries.
|
|
37
|
+
* 3. Catch-all `User-agent: *` block: `Disallow: /` only when
|
|
38
|
+
* `allowedCrawlers` is set (the named bots are then the only ones
|
|
39
|
+
* permitted), otherwise the allow-everything `Disallow:` plus any
|
|
40
|
+
* `disallow[]` path-prefix entries. `policy === 'noindex'` adds
|
|
41
|
+
* nothing here — it is expressed by the page's robots meta tag,
|
|
42
|
+
* which a crawler can only read if it may fetch the page (#494).
|
|
41
43
|
* 4. `Sitemap: <publicOrigin>/sitemap-index.xml` footer when
|
|
42
44
|
* `seo.sitemap?.enabled !== false` (defaults to `true`).
|
|
43
45
|
* `@astrojs/sitemap` emits `sitemap-index.xml` regardless of
|
|
@@ -57,10 +59,9 @@ import type { WebsiteSiteContext } from './website-site-context';
|
|
|
57
59
|
*
|
|
58
60
|
* The `Sitemap:` directive is independent from any `User-agent`
|
|
59
61
|
* block — it is a top-level signal to any crawler that the site
|
|
60
|
-
* publishes a sitemap. Even when the catch-all
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
* for this) at least has a single canonical URL list to consult,
|
|
62
|
+
* publishes a sitemap. Even when the catch-all is `Disallow: /`
|
|
63
|
+
* (`allowedCrawlers` set), the `Sitemap:` line stays useful: the
|
|
64
|
+
* permitted crawlers have a single canonical URL list to consult,
|
|
64
65
|
* and human readers of `robots.txt` see the publication contract
|
|
65
66
|
* intact.
|
|
66
67
|
*
|
|
@@ -120,13 +121,12 @@ export function renderRobotsTxt(siteContext: WebsiteSiteContext): string {
|
|
|
120
121
|
}
|
|
121
122
|
|
|
122
123
|
/**
|
|
123
|
-
* Block 3 — catch-all `User-agent: *`.
|
|
124
|
-
* -
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
* `Disallow: /` so the named bots are the ONLY ones permitted.
|
|
124
|
+
* Block 3 — catch-all `User-agent: *`. `Disallow:` is the
|
|
125
|
+
* spec-canonical "allow everything" line, combined with any per-path
|
|
126
|
+
* `Disallow: <path>` entries from `disallow[]`. When `allowedCrawlers`
|
|
127
|
+
* is set, the catch-all instead closes the rest of the crawler universe
|
|
128
|
+
* with `Disallow: /` so the named bots are the ONLY ones permitted. The
|
|
129
|
+
* `policy` is not expressed here at all — see below.
|
|
130
130
|
*/
|
|
131
131
|
const catchAllLines: string[] = ['User-agent: *'];
|
|
132
132
|
/*
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { AvailableLanguage } from '@wildo-ai/saas-models/public-runtime';
|
|
2
2
|
|
|
3
3
|
import { buildLocalizedRoutePath } from '../core/routing/internal-routing.utils';
|
|
4
|
-
import type
|
|
4
|
+
import { websiteDesignTokenCssProperty, type WebsiteDesignTokens } from '../schemas/design-tokens/website-design-tokens.shared.schemas';
|
|
5
5
|
import type { WebsitePageManifest } from '../schemas/manifests/website-page-manifest.shared.schemas';
|
|
6
6
|
import type { LabelPack } from '../schemas/validators/label-pack.validator';
|
|
7
7
|
|
|
@@ -277,7 +277,7 @@ export function renderWebsiteDesignTokensCss(tokens: WebsiteDesignTokens): strin
|
|
|
277
277
|
if (groupValue !== null && typeof groupValue === 'object' && !Array.isArray(groupValue)) {
|
|
278
278
|
for (const [tokenName, tokenValue] of Object.entries(groupValue) as [string, unknown][]) {
|
|
279
279
|
if (typeof tokenValue === 'string') {
|
|
280
|
-
lines.push(`
|
|
280
|
+
lines.push(` ${websiteDesignTokenCssProperty(groupName, tokenName)}: ${tokenValue};`);
|
|
281
281
|
}
|
|
282
282
|
}
|
|
283
283
|
} else if (typeof groupValue === 'string') {
|
|
@@ -10,7 +10,8 @@ import type { FrontendProvidersBlock } from '@wildo-ai/saas-models/public-runtim
|
|
|
10
10
|
* `WebsiteRootConfig` (page registry, design tokens, navigation chrome,
|
|
11
11
|
* site-level SEO branding) with the per-service `WildoMarketingWebsiteConfig`
|
|
12
12
|
* loaded from `wildo.website.config.ts` (locales, default locale,
|
|
13
|
-
* public origin,
|
|
13
|
+
* public origin, and the `appOrigin` / `apiOrigin` the site reaches the
|
|
14
|
+
* product through — settled for THIS build by `loadWebsiteSiteContext`).
|
|
14
15
|
*
|
|
15
16
|
* # Why a dedicated aggregator type (vs. threading both raw configs)
|
|
16
17
|
*
|
|
@@ -84,7 +85,9 @@ export interface WebsiteSiteContext {
|
|
|
84
85
|
* The per-service `WildoMarketingWebsiteConfig` loaded from
|
|
85
86
|
* `wildo.website.config.ts`. Single source of truth for the
|
|
86
87
|
* marketing-site ROUTING + ORIGIN surface (`defaultLocale`,
|
|
87
|
-
* `locales`, `publicOrigin`,
|
|
88
|
+
* `locales`, `publicOrigin`, `appOrigin`, `apiOrigin`). The two origins
|
|
89
|
+
* are the values for THIS build (override, local dev, authored — see
|
|
90
|
+
* `resolveWebsiteBuildOrigins`), which is why every page reads them here.
|
|
88
91
|
*/
|
|
89
92
|
readonly websiteConfig: WildoMarketingWebsiteConfig;
|
|
90
93
|
/**
|
package/src/companion-exports.ts
CHANGED
|
@@ -8,22 +8,15 @@
|
|
|
8
8
|
* - anything Astro-build-time that cannot pull React into the
|
|
9
9
|
* evaluation graph
|
|
10
10
|
*
|
|
11
|
-
* Per `saas-website-slice.md` §"Note on `companion-exports.ts` scope
|
|
12
|
-
* expansion": the SEO builder library lives here too (it's pure
|
|
13
|
-
* string + JSON-LD generation, React-free by design).
|
|
14
|
-
*
|
|
15
11
|
* @wildo-boundary
|
|
16
|
-
* This file
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* Enforced by review until Phase 8 boundary lint design (see plan).
|
|
25
|
-
*
|
|
26
|
-
* Phase 1 status: schemas exposed; SEO builders land in Phase 2.5.
|
|
12
|
+
* This file re-exports ONLY from `schemas/**`. It MUST NOT import or
|
|
13
|
+
* re-export anything from `core/**` (React contexts/hooks), `astro/**`
|
|
14
|
+
* (Astro/Node adapter), `mdx/**` or `config/**`, and MUST NOT import
|
|
15
|
+
* React, Astro or `@mdx-js/*`. The SEO and structured-data renderers
|
|
16
|
+
* are React-free string builders too, but they live on `/astro` beside
|
|
17
|
+
* the build-time helpers that call them, and are reached from there.
|
|
18
|
+
* Enforced by `src/__tests__/bundle-isolation.test.ts` and
|
|
19
|
+
* `schemas/__tests__/companion-exports-react-free.test.ts`.
|
|
27
20
|
*/
|
|
28
21
|
|
|
29
22
|
export * from './schemas/refs/page-ref.schemas';
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { AvailableLanguage } from '@wildo-ai/saas-models/public-runtime';
|
|
4
|
+
|
|
5
|
+
import { resolveWebsiteBuildOrigins } from '../load-website-site-context';
|
|
6
|
+
import type { WildoMarketingWebsiteConfig } from '../wildo-website-config.schemas';
|
|
7
|
+
|
|
8
|
+
const AUTHORED: WildoMarketingWebsiteConfig = {
|
|
9
|
+
publicOrigin: 'https://example.com',
|
|
10
|
+
appOrigin: 'https://app.example.com',
|
|
11
|
+
apiOrigin: 'https://api.example.com',
|
|
12
|
+
defaultLocale: AvailableLanguage.EN,
|
|
13
|
+
locales: [{ locale: AvailableLanguage.EN, path: 'en' }],
|
|
14
|
+
} as WildoMarketingWebsiteConfig;
|
|
15
|
+
|
|
16
|
+
const LOCAL = { app: 'http://localhost:4272', api: 'http://localhost:4271' };
|
|
17
|
+
|
|
18
|
+
describe('resolveWebsiteBuildOrigins', () => {
|
|
19
|
+
it('under astro dev, links into the locally running product and API', () => {
|
|
20
|
+
const resolved = resolveWebsiteBuildOrigins({ websiteConfig: AUTHORED, isDevelopment: true, localOrigins: LOCAL, overrides: {} });
|
|
21
|
+
expect(resolved.appOrigin).toBe('http://localhost:4272');
|
|
22
|
+
expect(resolved.apiOrigin).toBe('http://localhost:4271');
|
|
23
|
+
expect(resolved.publicOrigin).toBe('https://example.com');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('a production build keeps the authored origins, never a localhost one', () => {
|
|
27
|
+
const resolved = resolveWebsiteBuildOrigins({ websiteConfig: AUTHORED, isDevelopment: false, localOrigins: LOCAL, overrides: {} });
|
|
28
|
+
expect(resolved.appOrigin).toBe('https://app.example.com');
|
|
29
|
+
expect(resolved.apiOrigin).toBe('https://api.example.com');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('an explicit per-environment override wins in both modes', () => {
|
|
33
|
+
const overrides = { appOrigin: 'https://app.staging.example.com', apiOrigin: 'https://api.staging.example.com' };
|
|
34
|
+
for (const isDevelopment of [true, false]) {
|
|
35
|
+
const resolved = resolveWebsiteBuildOrigins({ websiteConfig: AUTHORED, isDevelopment, localOrigins: LOCAL, overrides });
|
|
36
|
+
expect(resolved.appOrigin).toBe(overrides.appOrigin);
|
|
37
|
+
expect(resolved.apiOrigin).toBe(overrides.apiOrigin);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('falls back to the authored origin when the app declares no local port', () => {
|
|
42
|
+
const resolved = resolveWebsiteBuildOrigins({ websiteConfig: AUTHORED, isDevelopment: true, localOrigins: {}, overrides: {} });
|
|
43
|
+
expect(resolved.appOrigin).toBe('https://app.example.com');
|
|
44
|
+
expect(resolved.apiOrigin).toBe('https://api.example.com');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('leaves apiOrigin absent when nothing supplies one, so a capture form can refuse loudly', () => {
|
|
48
|
+
const { apiOrigin: _omitted, ...withoutApi } = AUTHORED;
|
|
49
|
+
const resolved = resolveWebsiteBuildOrigins({ websiteConfig: withoutApi as WildoMarketingWebsiteConfig, isDevelopment: false, localOrigins: LOCAL, overrides: {} });
|
|
50
|
+
expect('apiOrigin' in resolved).toBe(false);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -13,8 +13,9 @@
|
|
|
13
13
|
*
|
|
14
14
|
* The warner has ZERO branches today because every OPTIONAL field that
|
|
15
15
|
* exists is already wired (`csp?` now flows through the website-local
|
|
16
|
-
* CSP sync step) and
|
|
17
|
-
*
|
|
16
|
+
* CSP sync step), and every REQUIRED field has a runtime consumer too —
|
|
17
|
+
* `appOrigin` reaches every CTA through `WebsiteRuntimeContext.appOrigin`,
|
|
18
|
+
* settled per build by `loadWebsiteSiteContext`. After the seam-collapse (`saas-website-seam-collapse.md`
|
|
18
19
|
* — SHIPPED), the routing/origin fields (`publicOrigin`,
|
|
19
20
|
* `defaultLocale`, `locales`) are consumed directly by every Astro
|
|
20
21
|
* engine helper via `WebsiteSiteContext`, so they are no longer
|
|
@@ -99,14 +100,13 @@ export function defineWebsiteConfig(
|
|
|
99
100
|
* but has no runtime consumer yet.
|
|
100
101
|
*
|
|
101
102
|
* Branches today: ZERO. The one OPTIONAL field that exists (`csp?`) is
|
|
102
|
-
* fully wired through the website-local CSP sync/build path
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
* `@unimplemented` warner concern.
|
|
103
|
+
* fully wired through the website-local CSP sync/build path. After the
|
|
104
|
+
* seam-collapse (`saas-website-seam-collapse.md` — SHIPPED) the
|
|
105
|
+
* routing/origin fields are consumed directly by every Astro engine
|
|
106
|
+
* helper via `WebsiteSiteContext`, and `appOrigin` / `apiOrigin` are
|
|
107
|
+
* settled per build by `loadWebsiteSiteContext` and read by every page.
|
|
108
|
+
* A required field could never fire this warner anyway — it would fire
|
|
109
|
+
* on every parse.
|
|
110
110
|
*
|
|
111
111
|
* The function exists as a structural mirror of
|
|
112
112
|
* `defineTechnicalDocConfig.emitUnimplementedRuntimeWarnings` so
|
|
@@ -37,6 +37,7 @@ import { AppFrontendType, loadConfig } from '@wildo-ai/platform-config-lib';
|
|
|
37
37
|
import type { WildoSaasConfig } from '@wildo-ai/platform-config-lib';
|
|
38
38
|
import type { WildoMarketingWebsiteConfig } from './wildo-website-config.schemas';
|
|
39
39
|
|
|
40
|
+
import { describeThrownValue } from '@wildo-ai/saas-models/public-runtime';
|
|
40
41
|
/**
|
|
41
42
|
* Conventional file name for the per-service marketing-website
|
|
42
43
|
* configuration (Step 7.5 refactor — `saas-website-slice.md` /
|
|
@@ -125,6 +126,30 @@ export interface LoadWebsiteConfigArgs {
|
|
|
125
126
|
saasConfig: WildoSaasConfig;
|
|
126
127
|
}
|
|
127
128
|
|
|
129
|
+
/**
|
|
130
|
+
* The application's one `STATIC_WEBSITE` service, as `[serviceKey, service]`.
|
|
131
|
+
* Throws when none is declared: the service folder is what owns
|
|
132
|
+
* `wildo.website.config.ts`, so a website build without one has nothing to load.
|
|
133
|
+
*/
|
|
134
|
+
export function resolveStaticWebsiteServiceEntry(
|
|
135
|
+
saasConfig: WildoSaasConfig,
|
|
136
|
+
): [string, WildoSaasConfig['services'][string]] {
|
|
137
|
+
const websiteServiceEntry = Object.entries(saasConfig.services).find(
|
|
138
|
+
([, service]) => service.frontendType === AppFrontendType.STATIC_WEBSITE,
|
|
139
|
+
);
|
|
140
|
+
if (!websiteServiceEntry) {
|
|
141
|
+
throw new Error(
|
|
142
|
+
'[wildo-website-config] No frontend service with `frontendType: STATIC_WEBSITE` is declared in '
|
|
143
|
+
+ 'wildo.saas.config.ts. The marketing-website Astro build / loader requires an authored '
|
|
144
|
+
+ 'STATIC_WEBSITE service because that service folder owns the per-service '
|
|
145
|
+
+ '`wildo.website.config.ts` (publicOrigin, appOrigin, defaultLocale, locales). '
|
|
146
|
+
+ 'Add a STATIC_WEBSITE service in wildo.saas.config.ts and ship the per-service '
|
|
147
|
+
+ 'config file under it.',
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
return websiteServiceEntry;
|
|
151
|
+
}
|
|
152
|
+
|
|
128
153
|
/**
|
|
129
154
|
* Discover and load the per-service marketing-website config.
|
|
130
155
|
*
|
|
@@ -168,20 +193,7 @@ export async function loadWebsiteConfig(
|
|
|
168
193
|
args: LoadWebsiteConfigArgs,
|
|
169
194
|
): Promise<WildoMarketingWebsiteConfig> {
|
|
170
195
|
const { saasRoot, saasConfig } = args;
|
|
171
|
-
|
|
172
|
-
const websiteServiceEntry = Object.entries(saasConfig.services).find(
|
|
173
|
-
([, service]) => service.frontendType === AppFrontendType.STATIC_WEBSITE,
|
|
174
|
-
);
|
|
175
|
-
if (!websiteServiceEntry) {
|
|
176
|
-
throw new Error(
|
|
177
|
-
'[wildo-website-config] No frontend service with `frontendType: STATIC_WEBSITE` is declared in '
|
|
178
|
-
+ 'wildo.saas.config.ts. The marketing-website Astro build / loader requires an authored '
|
|
179
|
-
+ 'STATIC_WEBSITE service because that service folder owns the per-service '
|
|
180
|
-
+ '`wildo.website.config.ts` (publicOrigin, appOrigin, defaultLocale, locales). '
|
|
181
|
-
+ 'Add a STATIC_WEBSITE service in wildo.saas.config.ts and ship the per-service '
|
|
182
|
-
+ 'config file under it.',
|
|
183
|
-
);
|
|
184
|
-
}
|
|
196
|
+
const websiteServiceEntry = resolveStaticWebsiteServiceEntry(saasConfig);
|
|
185
197
|
|
|
186
198
|
const [websiteServiceKey, websiteService] = websiteServiceEntry;
|
|
187
199
|
const websiteConfigPath = resolvePath(
|
|
@@ -213,7 +225,7 @@ export async function loadWebsiteConfig(
|
|
|
213
225
|
try {
|
|
214
226
|
loaded = await loadConfig<WildoMarketingWebsiteConfig>(websiteConfigPath);
|
|
215
227
|
} catch (e) {
|
|
216
|
-
const cause =
|
|
228
|
+
const cause = describeThrownValue(e);
|
|
217
229
|
throw new Error(
|
|
218
230
|
`[wildo-website-config] Failed to load per-service marketing-website config at `
|
|
219
231
|
+ `${websiteConfigPath}. The file MUST do `
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @wildo_source:part:start saas.website.config.pricing-catalog-loader facet:layer:astro facet:family:website facet:audience:app-developer
|
|
3
|
+
*
|
|
4
|
+
* `loadWebsitePricingCatalog` — the application's public pricing catalogue, for a website's pricing
|
|
5
|
+
* page (#1835). Call it in the page's `.astro` frontmatter, pass the result to the bridge island as
|
|
6
|
+
* `pricingCatalog`, and read it in the section with `useWebsitePricingCatalog()`; build the page's
|
|
7
|
+
* `AggregateOffer` from the same value with `buildWebsitePricingOfferStructuredData`.
|
|
8
|
+
*
|
|
9
|
+
* It walks up to `wildo.saas.config.ts` the way `loadWebsiteSiteContext` does, and reads the billing
|
|
10
|
+
* catalogue the backend sells from — the `productDefinitions` of the application's shared modules —
|
|
11
|
+
* through `loadApplicationPublicPricingCatalog`, from SOURCE, so a website image that builds only the
|
|
12
|
+
* website still sees the current catalogue.
|
|
13
|
+
*
|
|
14
|
+
* Separate from `loadWebsiteSiteContext` on purpose: loading the shared graph costs seconds, and only a
|
|
15
|
+
* pricing page needs it. Every other page's build stays as fast as it was.
|
|
16
|
+
*
|
|
17
|
+
* **Node-only.** Lives on the `/config-loader` subpath; it must never reach a React-island bundle.
|
|
18
|
+
*/
|
|
19
|
+
import {
|
|
20
|
+
loadApplicationPublicPricingCatalog,
|
|
21
|
+
loadConfig,
|
|
22
|
+
resolveWorkspaceScope,
|
|
23
|
+
type WildoSaasConfig,
|
|
24
|
+
} from '@wildo-ai/platform-config-lib';
|
|
25
|
+
import type { PublicPricingCatalog } from '@wildo-ai/saas-models/public-runtime';
|
|
26
|
+
|
|
27
|
+
export interface LoadWebsitePricingCatalogInput {
|
|
28
|
+
/** Where to start the walk up to `wildo.saas.config.ts`. Defaults to the working directory. */
|
|
29
|
+
readonly cwd?: string | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* Keep only plans this scope may subscribe to (a `ResourcePrimaryScope` wire value, e.g.
|
|
32
|
+
* `'organizations'`). Omit when the catalogue sells to one audience.
|
|
33
|
+
*/
|
|
34
|
+
readonly targetScope?: string | undefined;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function loadWebsitePricingCatalog(input: LoadWebsitePricingCatalogInput = {}): Promise<PublicPricingCatalog> {
|
|
38
|
+
const workspaceScope = resolveWorkspaceScope(input.cwd);
|
|
39
|
+
if (workspaceScope.saasRoot === null || workspaceScope.saasConfigPath === null) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
`[saas-website] No wildo.saas.config.ts found walking up from ${input.cwd ?? process.cwd()}. `
|
|
42
|
+
+ 'Run astro from inside the application workspace.',
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
const saasConfig = await loadConfig<WildoSaasConfig>(workspaceScope.saasConfigPath);
|
|
46
|
+
return loadApplicationPublicPricingCatalog({
|
|
47
|
+
saasRoot: workspaceScope.saasRoot,
|
|
48
|
+
saasConfig,
|
|
49
|
+
...(input.targetScope === undefined ? {} : { targetScope: input.targetScope }),
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
/** @wildo_source:part:end saas.website.config.pricing-catalog-loader */
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @wildo_source:part:start saas.website.config.site-context-loader facet:layer:astro facet:family:website facet:audience:app-developer
|
|
3
|
+
*
|
|
4
|
+
* `loadWebsiteSiteContext` — the ONE way a website workspace builds its
|
|
5
|
+
* `WebsiteSiteContext`. Called once, from the workspace's
|
|
6
|
+
* `src/website-site-context.ts`:
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { loadWebsiteSiteContext } from '@wildo-ai/saas-website/config-loader';
|
|
10
|
+
* import { WEBSITE_ROOT_CONFIG } from './website-root.config';
|
|
11
|
+
*
|
|
12
|
+
* export const WEBSITE_SITE_CONTEXT = await loadWebsiteSiteContext({
|
|
13
|
+
* rootConfig: WEBSITE_ROOT_CONFIG,
|
|
14
|
+
* isDevelopment: import.meta.env?.DEV === true,
|
|
15
|
+
* });
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* It walks up from the working directory to `wildo.saas.config.ts`, finds the
|
|
19
|
+
* application's single `STATIC_WEBSITE` service, loads that service's
|
|
20
|
+
* `wildo.website.config.ts` and its materialized provider block, and settles the
|
|
21
|
+
* two origins the site reaches the product through (see
|
|
22
|
+
* {@link resolveWebsiteBuildOrigins}).
|
|
23
|
+
*
|
|
24
|
+
* **Why the engine owns this.** Every website workspace used to carry its own
|
|
25
|
+
* copy of this lookup — the scaffolded ones and the hand-written ones alike —
|
|
26
|
+
* and each copy hardcoded its local API port while the app origin had no local
|
|
27
|
+
* form at all. The result: under `astro dev`, every "Start free" CTA pointed at a
|
|
28
|
+
* production-shaped host that resolves to nothing. One loader means one rule.
|
|
29
|
+
*
|
|
30
|
+
* **Why it walks up from the working directory, never from `import.meta.url`.**
|
|
31
|
+
* Astro's prerender pass bundles the calling module into `dist/.prerender/chunks/…`,
|
|
32
|
+
* where a `new URL('../..', import.meta.url)` anchor resolves to `dist/` rather than
|
|
33
|
+
* the application root — every prerendered route then failed with "Config file not
|
|
34
|
+
* found: …/website/dist/wildo.saas.config.ts", while `astro dev` hid it by serving
|
|
35
|
+
* from `src/`. Every `astro dev` / `astro build` runs with the website package (or
|
|
36
|
+
* an ancestor) as its working directory, which is inside the application either way.
|
|
37
|
+
*
|
|
38
|
+
* **Node-only.** Lives on the `/config-loader` subpath with the other loaders; it
|
|
39
|
+
* must never reach a React-island bundle.
|
|
40
|
+
*/
|
|
41
|
+
import { resolve as resolvePath } from 'node:path';
|
|
42
|
+
|
|
43
|
+
import {
|
|
44
|
+
loadConfig,
|
|
45
|
+
loadMaterializedFrontendProviders,
|
|
46
|
+
resolveFrontendServicesArtifactPath,
|
|
47
|
+
resolveLocalhostApplicationOrigins,
|
|
48
|
+
resolveWorkspaceScope,
|
|
49
|
+
type LocalhostApplicationOrigins,
|
|
50
|
+
type WildoSaasConfig,
|
|
51
|
+
} from '@wildo-ai/platform-config-lib';
|
|
52
|
+
|
|
53
|
+
import { composeWebsiteSiteContext, type WebsiteSiteContext } from '../astro/website-site-context';
|
|
54
|
+
import type { WebsiteRootConfig } from '../schemas/manifests/website-root-config.shared.schemas';
|
|
55
|
+
import { loadWebsiteConfig, resolveStaticWebsiteServiceEntry } from './load-website-config';
|
|
56
|
+
import type { WildoMarketingWebsiteConfig } from './wildo-website-config.schemas';
|
|
57
|
+
|
|
58
|
+
/** Explicit per-build origin overrides — what a deploy build passes as build arguments. */
|
|
59
|
+
export interface WebsiteOriginOverrides {
|
|
60
|
+
/** `PUBLIC_APP_ORIGIN`: this environment's product origin, derived by the deploy workflow. */
|
|
61
|
+
readonly appOrigin?: string | undefined;
|
|
62
|
+
/** `PUBLIC_API_ORIGIN`: this environment's API origin. */
|
|
63
|
+
readonly apiOrigin?: string | undefined;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface ResolveWebsiteBuildOriginsInput {
|
|
67
|
+
/** The authored `wildo.website.config.ts` — the production default. */
|
|
68
|
+
readonly websiteConfig: WildoMarketingWebsiteConfig;
|
|
69
|
+
/** `true` under `astro dev`. */
|
|
70
|
+
readonly isDevelopment: boolean;
|
|
71
|
+
/** Where the product and API run locally, from the app's own declared ports. */
|
|
72
|
+
readonly localOrigins: LocalhostApplicationOrigins;
|
|
73
|
+
/** Explicit overrides, which win over everything. */
|
|
74
|
+
readonly overrides: WebsiteOriginOverrides;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The `websiteConfig` this BUILD serves, with `appOrigin` and `apiOrigin` settled.
|
|
79
|
+
*
|
|
80
|
+
* Precedence, per origin:
|
|
81
|
+
*
|
|
82
|
+
* 1. an explicit override (`PUBLIC_APP_ORIGIN` / `PUBLIC_API_ORIGIN`) — the deploy
|
|
83
|
+
* workflow derives these per environment, so a staging build links into staging;
|
|
84
|
+
* 2. under `astro dev`, the locally running product / API, from the ports the
|
|
85
|
+
* application declares — so a CTA reaches the process `wildo local dev` started;
|
|
86
|
+
* 3. the value authored in `wildo.website.config.ts`.
|
|
87
|
+
*
|
|
88
|
+
* The result replaces the two fields in `websiteConfig` rather than living beside
|
|
89
|
+
* it, because every page already forwards `websiteConfig.appOrigin` /
|
|
90
|
+
* `websiteConfig.apiOrigin` into its island: a second field would be a second
|
|
91
|
+
* source that pages could read instead. `websiteConfig` in a site context is
|
|
92
|
+
* therefore "the config as it applies to this build", which the config file was
|
|
93
|
+
* already half-way to being — it reads `PUBLIC_APP_ORIGIN` itself.
|
|
94
|
+
*/
|
|
95
|
+
export function resolveWebsiteBuildOrigins(input: ResolveWebsiteBuildOriginsInput): WildoMarketingWebsiteConfig {
|
|
96
|
+
const { websiteConfig, isDevelopment, localOrigins, overrides } = input;
|
|
97
|
+
const settle = (override: string | undefined, local: string | undefined, authored: string | undefined): string | undefined =>
|
|
98
|
+
override || (isDevelopment ? local : undefined) || authored;
|
|
99
|
+
const appOrigin = settle(overrides.appOrigin, localOrigins.app, websiteConfig.appOrigin) ?? websiteConfig.appOrigin;
|
|
100
|
+
const apiOrigin = settle(overrides.apiOrigin, localOrigins.api, websiteConfig.apiOrigin);
|
|
101
|
+
return { ...websiteConfig, appOrigin, ...(apiOrigin === undefined ? {} : { apiOrigin }) };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface LoadWebsiteSiteContextInput {
|
|
105
|
+
/** The workspace's `WEBSITE_ROOT_CONFIG`. */
|
|
106
|
+
readonly rootConfig: WebsiteRootConfig;
|
|
107
|
+
/** Pass `import.meta.env?.DEV === true` (optional chaining: `astro.config.ts` evaluates the caller before Vite's env exists). */
|
|
108
|
+
readonly isDevelopment: boolean;
|
|
109
|
+
/** Where to start the walk up to `wildo.saas.config.ts`. Defaults to the working directory. */
|
|
110
|
+
readonly cwd?: string | undefined;
|
|
111
|
+
/** Defaults to `PUBLIC_APP_ORIGIN` / `PUBLIC_API_ORIGIN` from the process environment. */
|
|
112
|
+
readonly overrides?: WebsiteOriginOverrides | undefined;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export async function loadWebsiteSiteContext(input: LoadWebsiteSiteContextInput): Promise<WebsiteSiteContext> {
|
|
116
|
+
const workspaceScope = resolveWorkspaceScope(input.cwd);
|
|
117
|
+
if (workspaceScope.saasRoot === null || workspaceScope.saasConfigPath === null) {
|
|
118
|
+
throw new Error(
|
|
119
|
+
`[saas-website] No wildo.saas.config.ts found walking up from ${input.cwd ?? process.cwd()}. `
|
|
120
|
+
+ 'Run astro from inside the application workspace.',
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
const saasRoot = workspaceScope.saasRoot;
|
|
124
|
+
const saasConfig = await loadConfig<WildoSaasConfig>(workspaceScope.saasConfigPath);
|
|
125
|
+
const [websiteServiceKey] = resolveStaticWebsiteServiceEntry(saasConfig);
|
|
126
|
+
const websiteConfig = await loadWebsiteConfig({ saasRoot, saasConfig });
|
|
127
|
+
|
|
128
|
+
return composeWebsiteSiteContext({
|
|
129
|
+
rootConfig: input.rootConfig,
|
|
130
|
+
websiteConfig: resolveWebsiteBuildOrigins({
|
|
131
|
+
websiteConfig,
|
|
132
|
+
isDevelopment: input.isDevelopment,
|
|
133
|
+
localOrigins: resolveLocalhostApplicationOrigins(saasConfig.services),
|
|
134
|
+
overrides: input.overrides ?? {
|
|
135
|
+
appOrigin: process.env.PUBLIC_APP_ORIGIN,
|
|
136
|
+
apiOrigin: process.env.PUBLIC_API_ORIGIN,
|
|
137
|
+
},
|
|
138
|
+
}),
|
|
139
|
+
frontendProviders: loadMaterializedFrontendProviders({
|
|
140
|
+
artifactPath: resolveFrontendServicesArtifactPath(resolvePath(saasRoot)),
|
|
141
|
+
serviceKey: websiteServiceKey,
|
|
142
|
+
}),
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
/** @wildo_source:part:end saas.website.config.site-context-loader */
|
|
@@ -195,11 +195,16 @@ export const WildoMarketingWebsiteConfigSchema = z
|
|
|
195
195
|
* Origin of the SaaS application the marketing site links into
|
|
196
196
|
* (signup / login / pricing → checkout).
|
|
197
197
|
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
*
|
|
198
|
+
* Read at runtime since #493: each `.astro` page forwards it to its
|
|
199
|
+
* bridge island, and the CTA reads it off `WebsiteRuntimeContext`.
|
|
200
|
+
*
|
|
201
|
+
* Per environment: a deployment's app lives at a different host in each
|
|
202
|
+
* environment, and a static site bakes the value in at build time, so the
|
|
203
|
+
* generated deploy workflow passes the environment's app origin as
|
|
204
|
+
* `PUBLIC_APP_ORIGIN` — derived from the infra config's `serviceHosts` and
|
|
205
|
+
* `publicDomain`, not authored a second time. An application's
|
|
206
|
+
* `wildo.website.config.ts` reads that first and keeps its authored value
|
|
207
|
+
* as the default for a build that passes none.
|
|
203
208
|
*
|
|
204
209
|
* Distinct from the general `WildoSaasConfig.services.<name>`
|
|
205
210
|
* URL surface because the marketing site is a separate Astro
|
package/src/config-loader.ts
CHANGED
|
@@ -5,7 +5,7 @@ import '@testing-library/jest-dom';
|
|
|
5
5
|
import { BUILTIN_PROVIDER_CAPABILITY, defineProviderMetadata, ProviderOriginKind, ProviderTier } from '@wildo-ai/saas-models/public-runtime';
|
|
6
6
|
|
|
7
7
|
import { WebsitePageLayout } from '../layouts/WebsitePageLayout';
|
|
8
|
-
import { PROVIDER_SCRIPT_MARKER_ATTRIBUTE } from '
|
|
8
|
+
import { PROVIDER_SCRIPT_MARKER_ATTRIBUTE } from '@wildo-ai/external-connectors-public/website';
|
|
9
9
|
import { createFrontendWebsiteProviderRegistry } from '../external-providers/frontend-provider-registry.website';
|
|
10
10
|
import {
|
|
11
11
|
WebsiteRuntimeContextProvider,
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
WebsiteRuntimeContextProvider,
|
|
8
8
|
type WebsiteRuntimeContextValue,
|
|
9
9
|
} from '../contexts/WebsiteRuntimeContext';
|
|
10
|
-
import { useWebsiteRuntime } from '../contexts/useWebsiteRuntime';
|
|
10
|
+
import { useWebsitePricingCatalog, useWebsiteRuntime } from '../contexts/useWebsiteRuntime';
|
|
11
11
|
import { WebsitePageContextProvider } from '../contexts/WebsitePageContext';
|
|
12
12
|
import { useWebsitePage } from '../contexts/useWebsitePage';
|
|
13
13
|
import { WebsiteSectionContextProvider } from '../contexts/WebsiteSectionContext';
|
|
@@ -76,6 +76,25 @@ describe('WebsiteRuntimeContext', () => {
|
|
|
76
76
|
});
|
|
77
77
|
});
|
|
78
78
|
|
|
79
|
+
describe('useWebsitePricingCatalog', () => {
|
|
80
|
+
const wrapperWith = (value: WebsiteRuntimeContextValue) => ({ children }: { children: React.ReactNode }) => (
|
|
81
|
+
<WebsiteRuntimeContextProvider value={value}>{children}</WebsiteRuntimeContextProvider>
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
it('returns the catalogue the page passed to its island', () => {
|
|
85
|
+
const catalog = { plans: [] };
|
|
86
|
+
const { result } = renderHook(() => useWebsitePricingCatalog(), { wrapper: wrapperWith({ ...RUNTIME_FIXTURE, pricingCatalog: catalog }) });
|
|
87
|
+
expect(result.current).toBe(catalog);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('fails loudly when the page passed none — a pricing section must never render "no plans" for a missing prop', () => {
|
|
91
|
+
vi.spyOn(console, 'error').mockImplementation(() => undefined);
|
|
92
|
+
expect(() => renderHook(() => useWebsitePricingCatalog(), { wrapper: wrapperWith(RUNTIME_FIXTURE) })).toThrowError(
|
|
93
|
+
/did not pass a pricing catalogue/,
|
|
94
|
+
);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
79
98
|
describe('WebsitePageContext', () => {
|
|
80
99
|
it('provides the page ref through the provider component', () => {
|
|
81
100
|
const pageRef = WebsitePageRefSchema.parse('landing');
|