@wildo-ai/saas-website 1.1.5 → 1.1.7
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 +9 -0
- package/dist/esm/astro/blog-post-bridge.d.ts.map +1 -1
- package/dist/esm/astro/blog-post-bridge.js +10 -5
- 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 +25 -6
- package/dist/esm/astro/bridge-runtime.d.ts.map +1 -1
- package/dist/esm/astro/bridge-runtime.js +10 -6
- 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 +9 -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 +18 -9
- 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.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 +11 -0
- 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 +15 -4
- 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 +14 -2
- 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 +34 -4
- 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.map +1 -1
- package/dist/esm/core/consent/WebsiteConsentBanner.js +11 -10
- 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 +25 -5
- 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 +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.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 +15 -5
- package/dist/esm/core/external-providers/useWebsiteProviderScripts.d.ts.map +1 -1
- package/dist/esm/core/external-providers/useWebsiteProviderScripts.js +39 -24
- 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.map +1 -1
- package/dist/esm/core/layouts/WebsitePageLayout.js +10 -7
- 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 +30 -65
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +30 -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.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 +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 +9 -8
- 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-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 +5 -5
- package/src/__tests__/bundle-isolation.test.ts +19 -22
- package/src/astro/__tests__/blog-post-bridge.test.tsx +18 -2
- package/src/astro/__tests__/bridge-runtime.test.tsx +21 -3
- package/src/astro/__tests__/label-pack-loader.test.ts +7 -4
- package/src/astro/blog-post-bridge.tsx +19 -5
- package/src/astro/bridge-runtime.tsx +35 -11
- package/src/astro/label-pack-loader.ts +8 -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 +25 -14
- package/src/config/load-website-pricing-catalog.ts +52 -0
- package/src/config/load-website-site-context.ts +145 -0
- package/src/config-loader.ts +2 -0
- package/src/core/__tests__/contexts.test.tsx +20 -1
- package/src/core/__tests__/website-consent.test.tsx +2 -1
- 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/inbound-contact-form.schema.ts +16 -4
- package/src/core/anonymous-session/website-anonymous-session-client.ts +39 -4
- package/src/core/anonymous-session/website-submission-challenge.ts +99 -0
- package/src/core/consent/WebsiteConsentBanner.tsx +11 -10
- package/src/core/contexts/WebsiteRuntimeContext.tsx +25 -5
- 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/provider-component-registry.website.ts +115 -0
- package/src/core/external-providers/useWebsiteProviderComponent.ts +76 -0
- package/src/core/external-providers/useWebsiteProviderScripts.ts +56 -37
- 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 +11 -6
- package/src/index.ts +31 -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 +12 -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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wildo-ai/saas-website",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.7",
|
|
4
4
|
"description": "SaaS marketing/landing website engine — schemas, helpers, layout/primitive placeholders, Astro/MDX adapters, and SEO/AEO/GEO builder library. Consumers author all business sections themselves via the defineWebsiteSection factory.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"author": "Wildo AI",
|
|
@@ -65,10 +65,10 @@
|
|
|
65
65
|
"test:watch": "vitest"
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
|
-
"@wildo-ai/external-connectors-public": "1.1.
|
|
69
|
-
"@wildo-ai/platform-config-lib": "1.1.
|
|
70
|
-
"@wildo-ai/presets-components-models": "1.1.
|
|
71
|
-
"@wildo-ai/saas-models": "1.1.
|
|
68
|
+
"@wildo-ai/external-connectors-public": "1.1.7",
|
|
69
|
+
"@wildo-ai/platform-config-lib": "1.1.7",
|
|
70
|
+
"@wildo-ai/presets-components-models": "1.1.7",
|
|
71
|
+
"@wildo-ai/saas-models": "1.1.7",
|
|
72
72
|
"yaml": "^2.3.4"
|
|
73
73
|
},
|
|
74
74
|
"devDependencies": {
|
|
@@ -279,29 +279,14 @@ const ALLOWED_BARE_SPECIFIERS: ReadonlySet<string> = new Set([
|
|
|
279
279
|
* `FORBIDDEN_SPECIFIERS`.
|
|
280
280
|
*/
|
|
281
281
|
'@wildo-ai/zod-decorators',
|
|
282
|
-
|
|
283
|
-
*
|
|
284
|
-
*
|
|
285
|
-
*
|
|
286
|
-
*
|
|
287
|
-
*
|
|
288
|
-
*
|
|
289
|
-
* - `socket.io-client` — real-time push (e.g. live preview / status
|
|
290
|
-
* events from the SaaS app to the marketing site).
|
|
291
|
-
* - `reflect-metadata` — decorator metadata runtime; required
|
|
292
|
-
* by any future build-time consumer of `@wildo-ai/zod-decorators`
|
|
293
|
-
* metadata (the import IS side-effecting — it mutates
|
|
294
|
-
* `globalThis.Reflect`; that side effect is acceptable in an
|
|
295
|
-
* SSR context but would still be objectionable in a React
|
|
296
|
-
* island bundle).
|
|
297
|
-
* Allowing them here is a deliberate trade-off: this test no
|
|
298
|
-
* longer guarantees a hard browser-bundle size budget on its own.
|
|
299
|
-
* Application import discipline keeps the React-island side lean.
|
|
282
|
+
/*
|
|
283
|
+
* `axios`, `jsonwebtoken`, `socket.io-client` and `reflect-metadata` were allowed here from
|
|
284
|
+
* 2026-04-19 for an SSR middleware tier (backend proxying, handoff-JWT verification, live push)
|
|
285
|
+
* that was never built. Removed 2026-09-25: no source in this package imports any of them, and an
|
|
286
|
+
* allow-list wider than the code is a door nothing guards — the next import of one would pass
|
|
287
|
+
* this test unexamined. The tier that does exist (contact forms, anonymous sessions, consent
|
|
288
|
+
* recording) uses `fetch`. Re-add a specifier in the change that first imports it.
|
|
300
289
|
*/
|
|
301
|
-
'axios',
|
|
302
|
-
'jsonwebtoken',
|
|
303
|
-
'socket.io-client',
|
|
304
|
-
'reflect-metadata',
|
|
305
290
|
/**
|
|
306
291
|
* SSR / Astro-build-time tier. The
|
|
307
292
|
* `astro.ts` sub-entry exposes the label-pack loader, which
|
|
@@ -498,6 +483,18 @@ describe('saas-website — bundle-isolation boundary (D12)', () => {
|
|
|
498
483
|
}
|
|
499
484
|
});
|
|
500
485
|
|
|
486
|
+
it('walks the provider-component loader (#1703), so everything it imports is held to this boundary', () => {
|
|
487
|
+
// The loader reaches vendor code only through a provider module's DYNAMIC `import()`; these files
|
|
488
|
+
// must stay inside the walk so a static vendor import added to them is caught here.
|
|
489
|
+
for (const file of [
|
|
490
|
+
'core/external-providers/provider-component-registry.website.ts',
|
|
491
|
+
'core/external-providers/useWebsiteProviderComponent.ts',
|
|
492
|
+
'core/external-providers/WebsiteProviderComponent.tsx',
|
|
493
|
+
]) {
|
|
494
|
+
expect(visited.has(resolve(SRC_ROOT, file)), `Closure did not visit ${file}`).toBe(true);
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
|
|
501
498
|
it.each([...FORBIDDEN_SPECIFIERS])(
|
|
502
499
|
'no file in the saas-website closure imports the forbidden specifier "%s"',
|
|
503
500
|
(forbidden) => {
|
|
@@ -5,7 +5,14 @@ import { useMDXComponents } from '@mdx-js/react';
|
|
|
5
5
|
import React, { type ComponentType } from 'react';
|
|
6
6
|
import { describe, expect, it } from 'vitest';
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
BUILTIN_PROVIDER_CAPABILITY,
|
|
10
|
+
defineFrontendPublicConfigSchema,
|
|
11
|
+
defineProviderMetadata,
|
|
12
|
+
FrontendPublicConfigValueKind,
|
|
13
|
+
ProviderOriginKind,
|
|
14
|
+
ProviderTier,
|
|
15
|
+
} from '@wildo-ai/saas-models/public-runtime';
|
|
9
16
|
import type { FrontendProvidersBlock } from '@wildo-ai/saas-models/public-runtime';
|
|
10
17
|
import { AvailableLanguage } from '@wildo-ai/saas-models/public-runtime';
|
|
11
18
|
|
|
@@ -57,6 +64,15 @@ const SKIP_LABEL_PACK = {
|
|
|
57
64
|
// Typed as the real block rather than `as const`: the prop takes `FrontendProvidersBlock`,
|
|
58
65
|
// whose `providerCapabilities` / `protocols` are mutable `string[]`, so a `readonly` tuple
|
|
59
66
|
// fixture is not assignable to the surface under test.
|
|
67
|
+
/**
|
|
68
|
+
* The key list every real frontend provider module declares (#1705): hydration withholds the code of a module
|
|
69
|
+
* without one, so a fixture module without it would never reach the closure this test observes.
|
|
70
|
+
*/
|
|
71
|
+
const PUBLIC_CONFIG_SCHEMA = defineFrontendPublicConfigSchema({
|
|
72
|
+
integrationKind: { kind: FrontendPublicConfigValueKind.STRING, description: 'Which integration.' },
|
|
73
|
+
websiteRuntime: { kind: FrontendPublicConfigValueKind.BOOLEAN, description: 'Serves the website runtime.' },
|
|
74
|
+
});
|
|
75
|
+
|
|
60
76
|
const FRONTEND_PROVIDERS: FrontendProvidersBlock = {
|
|
61
77
|
providers: {
|
|
62
78
|
sentry: {
|
|
@@ -104,7 +120,7 @@ describe('createWebsiteBlogPostBridgeIsland', () => {
|
|
|
104
120
|
return <span>provider closure</span>;
|
|
105
121
|
}
|
|
106
122
|
const entry = { ...FRONTEND_PROVIDERS.providers.sentry, sdk: { packageName: '@test/sdk' } };
|
|
107
|
-
const modules = { sentry: { ...entry, sdk: { descriptor: entry.sdk, activate } } } as GeneratedWebsiteProviderModules;
|
|
123
|
+
const modules = { sentry: { ...entry, publicConfigSchema: PUBLIC_CONFIG_SCHEMA, sdk: { descriptor: entry.sdk, activate } } } as GeneratedWebsiteProviderModules;
|
|
108
124
|
const Island = createWebsiteBlogPostBridgeIsland({
|
|
109
125
|
slugToMdxBody: { demo: ProviderProbe },
|
|
110
126
|
navigation: buildNavigation(),
|
|
@@ -4,7 +4,14 @@ import { render, screen } from '@testing-library/react';
|
|
|
4
4
|
import React from 'react';
|
|
5
5
|
import { describe, expect, it } from 'vitest';
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
BUILTIN_PROVIDER_CAPABILITY,
|
|
9
|
+
defineFrontendPublicConfigSchema,
|
|
10
|
+
defineProviderMetadata,
|
|
11
|
+
FrontendPublicConfigValueKind,
|
|
12
|
+
ProviderOriginKind,
|
|
13
|
+
ProviderTier,
|
|
14
|
+
} from '@wildo-ai/saas-models/public-runtime';
|
|
8
15
|
import type { FrontendProvidersBlock } from '@wildo-ai/saas-models/public-runtime';
|
|
9
16
|
import { AvailableLanguage } from '@wildo-ai/saas-models/public-runtime';
|
|
10
17
|
|
|
@@ -73,6 +80,15 @@ const SKIP_LABEL_PACK = { 'website.chrome.layout.skip-to-main-content': 'Skip to
|
|
|
73
80
|
// Typed as the real block rather than `as const`: the prop takes `FrontendProvidersBlock`,
|
|
74
81
|
// whose `providerCapabilities` / `protocols` are mutable `string[]`, so a `readonly` tuple
|
|
75
82
|
// fixture is not assignable to the surface under test.
|
|
83
|
+
/**
|
|
84
|
+
* The key list every real frontend provider module declares (#1705): hydration withholds the code of a module
|
|
85
|
+
* without one, so a fixture module without it would never reach the closure this test observes.
|
|
86
|
+
*/
|
|
87
|
+
const PUBLIC_CONFIG_SCHEMA = defineFrontendPublicConfigSchema({
|
|
88
|
+
integrationKind: { kind: FrontendPublicConfigValueKind.STRING, description: 'Which integration.' },
|
|
89
|
+
websiteRuntime: { kind: FrontendPublicConfigValueKind.BOOLEAN, description: 'Serves the website runtime.' },
|
|
90
|
+
});
|
|
91
|
+
|
|
76
92
|
const FRONTEND_PROVIDERS: FrontendProvidersBlock = {
|
|
77
93
|
providers: {
|
|
78
94
|
posthog: {
|
|
@@ -94,7 +110,9 @@ function RuntimeProbe(): React.ReactNode {
|
|
|
94
110
|
|
|
95
111
|
describe('createWebsitePageBridgeIsland', () => {
|
|
96
112
|
it('keeps executable provider code in the bridge closure while props survive JSON serialization', () => {
|
|
97
|
-
|
|
113
|
+
// PostHog rests on analytics consent, so like the real module it declares `stoppable` and returns the
|
|
114
|
+
// `deactivate` that promise requires (#1704); without it hydration withholds the code this test observes.
|
|
115
|
+
const activate = async () => ({ handle: 'test', deactivate: () => undefined });
|
|
98
116
|
let observed: unknown;
|
|
99
117
|
function ProviderProbe() {
|
|
100
118
|
const registry = useWebsiteRuntime().frontendProviderRegistry;
|
|
@@ -102,7 +120,7 @@ describe('createWebsitePageBridgeIsland', () => {
|
|
|
102
120
|
return <span>provider closure</span>;
|
|
103
121
|
}
|
|
104
122
|
const entry = { ...FRONTEND_PROVIDERS.providers.posthog, sdk: { packageName: '@test/sdk' } };
|
|
105
|
-
const modules = { posthog: { ...entry, sdk: { descriptor: entry.sdk, activate } } } as GeneratedWebsiteProviderModules;
|
|
123
|
+
const modules = { posthog: { ...entry, publicConfigSchema: PUBLIC_CONFIG_SCHEMA, sdk: { descriptor: entry.sdk, activate, stoppable: true } } } as GeneratedWebsiteProviderModules;
|
|
106
124
|
const Island = createWebsitePageBridgeIsland({
|
|
107
125
|
PageComponent: ProviderProbe, pageRef: PAGE_REF,
|
|
108
126
|
navigation: buildNavigation(),
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { WEBSITE_CONSENT_LABEL_KEYS } from '../../schemas/label-keys/website-consent-label-keys.schemas';
|
|
2
|
+
import { WEBSITE_PROVIDER_COMPONENT_LABEL_KEYS } from '../../schemas/label-keys/website-provider-component-label-keys.schemas';
|
|
2
3
|
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
4
|
import { tmpdir } from 'node:os';
|
|
4
5
|
import { resolve } from 'node:path';
|
|
@@ -62,7 +63,9 @@ const buildManifest = () =>
|
|
|
62
63
|
|
|
63
64
|
const SKIP_KEY_STR = 'website.chrome.layout.skip-to-main-content';
|
|
64
65
|
// Framework-owned consent-panel keys (#534): required in every pack, like the skip link.
|
|
65
|
-
|
|
66
|
+
// The provider-component slot's refusal wording (#1703) is framework-owned the same way.
|
|
67
|
+
const FRAMEWORK_OWNED_KEYS = [...WEBSITE_CONSENT_LABEL_KEYS, ...WEBSITE_PROVIDER_COMPONENT_LABEL_KEYS];
|
|
68
|
+
const CONSENT_LABELS: Record<string, string> = Object.fromEntries(FRAMEWORK_OWNED_KEYS.map((key) => [key as unknown as string, 'X']));
|
|
66
69
|
|
|
67
70
|
const buildRootConfig = (): WebsiteRootConfig =>
|
|
68
71
|
WebsiteRootConfigSchema.parse({
|
|
@@ -210,7 +213,7 @@ describe('label-pack-loader', () => {
|
|
|
210
213
|
writeFileSync(resolve(tmpDir, 'fr.json'), JSON.stringify({}));
|
|
211
214
|
expect(() =>
|
|
212
215
|
assertLabelPacksValid(tmpDir, buildSiteContext(), buildExpectedKeys()),
|
|
213
|
-
).toThrow(new RegExp(`Locale "fr".*missing ${4 +
|
|
216
|
+
).toThrow(new RegExp(`Locale "fr".*missing ${4 + FRAMEWORK_OWNED_KEYS.length} required key\\(s\\)`, 's'));
|
|
214
217
|
});
|
|
215
218
|
|
|
216
219
|
it('annotates each missing key with its declaring page / section origin', () => {
|
|
@@ -294,7 +297,7 @@ describe('label-pack-loader', () => {
|
|
|
294
297
|
warnSpy.mockRestore();
|
|
295
298
|
});
|
|
296
299
|
|
|
297
|
-
it('requires every framework-owned consent-panel key in every locale pack
|
|
300
|
+
it('requires every framework-owned consent-panel (#534) and provider-component (#1703) key in every locale pack', () => {
|
|
298
301
|
const withoutConsent = JSON.stringify({
|
|
299
302
|
'website.landing.hero.headline': 'X',
|
|
300
303
|
'website.landing.meta.title': 'X',
|
|
@@ -311,7 +314,7 @@ describe('label-pack-loader', () => {
|
|
|
311
314
|
}
|
|
312
315
|
expect(captured).toMatch(/Locale "en":/);
|
|
313
316
|
expect(captured).not.toMatch(/Locale "fr":/);
|
|
314
|
-
for (const key of
|
|
317
|
+
for (const key of FRAMEWORK_OWNED_KEYS) expect(captured).toContain(key as unknown as string);
|
|
315
318
|
});
|
|
316
319
|
|
|
317
320
|
it('treats WebsiteRootConfig.skipToMainContentLabelKey as required (Phase 6 third audit fix M-α)', () => {
|
|
@@ -7,7 +7,8 @@ import {
|
|
|
7
7
|
WebsiteRuntimeContextProvider,
|
|
8
8
|
type WebsiteRuntimeContextValue,
|
|
9
9
|
} from '../core/contexts/WebsiteRuntimeContext';
|
|
10
|
-
import {
|
|
10
|
+
import { useWebsiteProviderRegistries } from '../core/external-providers/provider-component-registry.website';
|
|
11
|
+
import type { ProviderComponentLoaderContribution } from '@wildo-ai/external-connectors-public/website';
|
|
11
12
|
import type { GeneratedWebsiteProviderModules } from '../core/external-providers/frontend-provider-registry.website';
|
|
12
13
|
import { BlogPost, BLOG_POST_PAGE_REF } from '../mdx/BlogPost';
|
|
13
14
|
import type { WebsiteMdxComponentsMap } from '../mdx/website-mdx-provider';
|
|
@@ -228,6 +229,14 @@ export interface CreateWebsiteBlogPostBridgeIslandDeps {
|
|
|
228
229
|
* Config sync emits a stable map, including an empty map when no providers are declared.
|
|
229
230
|
*/
|
|
230
231
|
frontendProviderModules?: GeneratedWebsiteProviderModules;
|
|
232
|
+
/**
|
|
233
|
+
* Loaders for provider components an ENGINE provider module declares but cannot import (#1703) —
|
|
234
|
+
* the website's facet-library door, the equivalent of the application's
|
|
235
|
+
* `contributeProviderComponentLoaders`. A provider PACKAGE that ships its own component needs
|
|
236
|
+
* nothing here: its module's `components[capability].load` is used. A DEPS entry because a loader
|
|
237
|
+
* is a function, and island props are serialized.
|
|
238
|
+
*/
|
|
239
|
+
providerComponentLoaders?: ReadonlyArray<ProviderComponentLoaderContribution>;
|
|
231
240
|
/**
|
|
232
241
|
* Per-slug map of compiled MDX bodies (each entry is a React
|
|
233
242
|
* component — the `.default` export of the MDX module). Typically
|
|
@@ -383,6 +392,13 @@ export function createWebsiteBlogPostBridgeIsland(
|
|
|
383
392
|
);
|
|
384
393
|
}
|
|
385
394
|
|
|
395
|
+
// After the missing-slug throw on purpose: that throw ends the render, so it never changes the
|
|
396
|
+
// hook order of a render that completes, and the build-time error stays reachable.
|
|
397
|
+
const { frontendProviderRegistry, providerComponentRegistry } = useWebsiteProviderRegistries({
|
|
398
|
+
frontendProviders,
|
|
399
|
+
frontendProviderModules: deps.frontendProviderModules,
|
|
400
|
+
providerComponentLoaders: deps.providerComponentLoaders,
|
|
401
|
+
});
|
|
386
402
|
const runtimeValue: WebsiteRuntimeContextValue = {
|
|
387
403
|
locale,
|
|
388
404
|
defaultLocale,
|
|
@@ -392,10 +408,8 @@ export function createWebsiteBlogPostBridgeIsland(
|
|
|
392
408
|
skipToMainContentLabelKey,
|
|
393
409
|
appOrigin,
|
|
394
410
|
...(deps.consentBannerComponent !== undefined ? { consentBannerComponent: deps.consentBannerComponent } : {}),
|
|
395
|
-
frontendProviderRegistry
|
|
396
|
-
|
|
397
|
-
modules: deps.frontendProviderModules,
|
|
398
|
-
}),
|
|
411
|
+
frontendProviderRegistry,
|
|
412
|
+
providerComponentRegistry,
|
|
399
413
|
};
|
|
400
414
|
return (
|
|
401
415
|
<WebsiteRuntimeContextProvider value={runtimeValue}>
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { useMemo } from 'react';
|
|
2
2
|
import type { ComponentType, FunctionComponent, ReactNode } from 'react';
|
|
3
3
|
|
|
4
|
-
import type { FrontendProvidersBlock } from '@wildo-ai/saas-models/public-runtime';
|
|
4
|
+
import type { FrontendProvidersBlock, PublicPricingCatalog } from '@wildo-ai/saas-models/public-runtime';
|
|
5
5
|
import type { AvailableLanguage } from '@wildo-ai/saas-models/public-runtime';
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
8
|
WebsiteRuntimeContextProvider,
|
|
9
9
|
type WebsiteRuntimeContextValue,
|
|
10
10
|
} from '../core/contexts/WebsiteRuntimeContext';
|
|
11
|
-
import {
|
|
11
|
+
import { useWebsiteProviderRegistries } from '../core/external-providers/provider-component-registry.website';
|
|
12
|
+
import type { ProviderComponentLoaderContribution } from '@wildo-ai/external-connectors-public/website';
|
|
12
13
|
import type { GeneratedWebsiteProviderModules } from '../core/external-providers/frontend-provider-registry.website';
|
|
13
14
|
import { WebsitePageLayout } from '../core/layouts/WebsitePageLayout';
|
|
14
15
|
import type { WebsiteConsentRecorder } from '../core/consent/WebsiteConsentContext';
|
|
@@ -72,22 +73,22 @@ import type { LabelPack } from '../schemas/validators/label-pack.validator';
|
|
|
72
73
|
* } from '@wildo-ai/saas-website/astro';
|
|
73
74
|
* import { LandingBridgeIsland } from '../pages-impl/landing.bridge';
|
|
74
75
|
* import { LANDING_PAGE_MANIFEST } from '../pages-impl/landing.page';
|
|
75
|
-
* import {
|
|
76
|
+
* import { WEBSITE_SITE_CONTEXT } from '../website-site-context';
|
|
76
77
|
*
|
|
77
78
|
* const runtime = loadWebsitePageRuntime({
|
|
78
79
|
* manifest: LANDING_PAGE_MANIFEST,
|
|
79
|
-
*
|
|
80
|
+
* siteContext: WEBSITE_SITE_CONTEXT,
|
|
80
81
|
* labelPacksDir: './src/i18n',
|
|
81
82
|
* astroLocale: Astro.currentLocale,
|
|
82
83
|
* });
|
|
83
84
|
* const headHtml = renderWebsitePageHeadHtml({
|
|
84
85
|
* manifest: LANDING_PAGE_MANIFEST,
|
|
85
|
-
*
|
|
86
|
+
* siteContext: WEBSITE_SITE_CONTEXT,
|
|
86
87
|
* locale: runtime.locale,
|
|
87
88
|
* labelPack: runtime.labelPack,
|
|
88
89
|
* });
|
|
89
90
|
* const designTokensCss = renderWebsiteDesignTokensCss(
|
|
90
|
-
*
|
|
91
|
+
* WEBSITE_SITE_CONTEXT.rootConfig.designTokens,
|
|
91
92
|
* );
|
|
92
93
|
* ---
|
|
93
94
|
* <!doctype html>
|
|
@@ -100,8 +101,11 @@ import type { LabelPack } from '../schemas/validators/label-pack.validator';
|
|
|
100
101
|
* <LandingBridgeIsland
|
|
101
102
|
* client:idle
|
|
102
103
|
* locale={runtime.locale}
|
|
104
|
+
* defaultLocale={runtime.defaultLocale}
|
|
105
|
+
* appOrigin={WEBSITE_SITE_CONTEXT.websiteConfig.appOrigin}
|
|
106
|
+
* apiOrigin={WEBSITE_SITE_CONTEXT.websiteConfig.apiOrigin}
|
|
103
107
|
* labelPack={runtime.labelPack}
|
|
104
|
-
* designTokens={
|
|
108
|
+
* designTokens={WEBSITE_SITE_CONTEXT.rootConfig.designTokens}
|
|
105
109
|
* />
|
|
106
110
|
* </body>
|
|
107
111
|
* </html>
|
|
@@ -152,6 +156,14 @@ export interface CreateWebsitePageBridgeIslandDeps {
|
|
|
152
156
|
* Config sync emits a stable map, including an empty map when no providers are declared.
|
|
153
157
|
*/
|
|
154
158
|
frontendProviderModules?: GeneratedWebsiteProviderModules;
|
|
159
|
+
/**
|
|
160
|
+
* Loaders for provider components an ENGINE provider module declares but cannot import (#1703) —
|
|
161
|
+
* the website's facet-library door, the equivalent of the application's
|
|
162
|
+
* `contributeProviderComponentLoaders`. A provider PACKAGE that ships its own component needs
|
|
163
|
+
* nothing here: its module's `components[capability].load` is used. A DEPS entry because a loader
|
|
164
|
+
* is a function, and island props are serialized.
|
|
165
|
+
*/
|
|
166
|
+
providerComponentLoaders?: ReadonlyArray<ProviderComponentLoaderContribution>;
|
|
155
167
|
/**
|
|
156
168
|
* The page's React component. Typically the same value that was
|
|
157
169
|
* passed to `defineWebsitePageManifest({ pageComponent })` — the
|
|
@@ -270,6 +282,13 @@ export interface WebsitePageBridgeIslandProps {
|
|
|
270
282
|
* the consumer's job and the whole point of removing the fallback.
|
|
271
283
|
*/
|
|
272
284
|
appOrigin?: string;
|
|
285
|
+
/**
|
|
286
|
+
* The application's public pricing catalogue (#1835), forwarded into
|
|
287
|
+
* `WebsiteRuntimeContext.pricingCatalog` for `useWebsitePricingCatalog()`. Plain JSON-safe data
|
|
288
|
+
* (amounts travel as decimal strings, never `bigint`), loaded in the `.astro` frontmatter with
|
|
289
|
+
* `loadWebsitePricingCatalog()`. Passed only by pages with a pricing section.
|
|
290
|
+
*/
|
|
291
|
+
pricingCatalog?: PublicPricingCatalog;
|
|
273
292
|
}
|
|
274
293
|
|
|
275
294
|
/**
|
|
@@ -293,6 +312,7 @@ export function createWebsitePageBridgeIsland(
|
|
|
293
312
|
frontendProviders,
|
|
294
313
|
apiOrigin,
|
|
295
314
|
appOrigin,
|
|
315
|
+
pricingCatalog,
|
|
296
316
|
}: WebsitePageBridgeIslandProps): ReactNode {
|
|
297
317
|
/*
|
|
298
318
|
* Both must be present: the factory is build-time wiring and `apiOrigin` is per-environment, and
|
|
@@ -305,6 +325,11 @@ export function createWebsitePageBridgeIsland(
|
|
|
305
325
|
: undefined),
|
|
306
326
|
[apiOrigin],
|
|
307
327
|
);
|
|
328
|
+
const { frontendProviderRegistry, providerComponentRegistry } = useWebsiteProviderRegistries({
|
|
329
|
+
frontendProviders,
|
|
330
|
+
frontendProviderModules: deps.frontendProviderModules,
|
|
331
|
+
providerComponentLoaders: deps.providerComponentLoaders,
|
|
332
|
+
});
|
|
308
333
|
const runtimeValue: WebsiteRuntimeContextValue = {
|
|
309
334
|
locale,
|
|
310
335
|
defaultLocale,
|
|
@@ -312,13 +337,12 @@ export function createWebsitePageBridgeIsland(
|
|
|
312
337
|
designTokens,
|
|
313
338
|
navigation,
|
|
314
339
|
skipToMainContentLabelKey,
|
|
315
|
-
frontendProviderRegistry
|
|
316
|
-
|
|
317
|
-
modules: deps.frontendProviderModules,
|
|
318
|
-
}),
|
|
340
|
+
frontendProviderRegistry,
|
|
341
|
+
providerComponentRegistry,
|
|
319
342
|
...(deps.consentBannerComponent !== undefined ? { consentBannerComponent: deps.consentBannerComponent } : {}),
|
|
320
343
|
apiOrigin,
|
|
321
344
|
appOrigin,
|
|
345
|
+
...(pricingCatalog !== undefined ? { pricingCatalog } : {}),
|
|
322
346
|
};
|
|
323
347
|
return (
|
|
324
348
|
<WebsiteRuntimeContextProvider value={runtimeValue}>
|
|
@@ -5,6 +5,7 @@ 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
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';
|
|
8
9
|
import type { WebsitePageManifest } from '../schemas/manifests/website-page-manifest.shared.schemas';
|
|
9
10
|
import type { WebsiteRootConfig } from '../schemas/manifests/website-root-config.shared.schemas';
|
|
10
11
|
import {
|
|
@@ -21,10 +22,10 @@ import type { WebsiteSiteContext } from './website-site-context';
|
|
|
21
22
|
*
|
|
22
23
|
* `label-pack-loader.ts` — synchronous, fs-backed loader for
|
|
23
24
|
* marketing-site label packs. Consumed by:
|
|
24
|
-
* -
|
|
25
|
-
* locale's pack
|
|
26
|
-
* -
|
|
27
|
-
*
|
|
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`,
|
|
28
29
|
* - any build-time hook that wants to fail fast on a missing or
|
|
29
30
|
* malformed pack.
|
|
30
31
|
*
|
|
@@ -211,6 +212,9 @@ function composeEffectiveChromeLabelKeys(
|
|
|
211
212
|
// The visitor consent panel is framework-owned in the same way (#534): required in every pack, so a site
|
|
212
213
|
// that turns on analytics can never ship it with an unlabelled way to refuse.
|
|
213
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);
|
|
214
218
|
return Array.from(set);
|
|
215
219
|
}
|
|
216
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
|