@tokenoftrust/storefront-runner 1.4.2-rc.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/storefront/astro.config.mjs +3 -3
- package/apps/storefront/dev-plugins/tenant-hot-reload.mjs +8 -3
- package/apps/storefront/env.d.ts +54 -5
- package/apps/storefront/integrations/materialize-guard.mjs +12 -14
- package/apps/storefront/migrations-apps/0004_deep_morph.sql +36 -0
- package/apps/storefront/migrations-apps/0005_common_mystique.sql +23 -0
- package/apps/storefront/migrations-apps/0006_broad_microchip.sql +16 -0
- package/apps/storefront/migrations-apps/0007_brave_outlaw_kid.sql +15 -0
- package/apps/storefront/migrations-apps/0008_blue_dazzler.sql +11 -0
- package/apps/storefront/migrations-apps/0009_bored_shinobi_shaw.sql +15 -0
- package/apps/storefront/migrations-apps/0010_ws4_verified_provider_measurement.sql +35 -0
- package/apps/storefront/migrations-apps/meta/0004_snapshot.json +1474 -0
- package/apps/storefront/migrations-apps/meta/0005_snapshot.json +1617 -0
- package/apps/storefront/migrations-apps/meta/0006_snapshot.json +1716 -0
- package/apps/storefront/migrations-apps/meta/0007_snapshot.json +1812 -0
- package/apps/storefront/migrations-apps/meta/0008_snapshot.json +1881 -0
- package/apps/storefront/migrations-apps/meta/0009_snapshot.json +1979 -0
- package/apps/storefront/migrations-apps/meta/_journal.json +42 -0
- package/apps/storefront/public/apps/tot.demo.analytics/0.1.0/entry.js +42 -0
- package/apps/storefront/public/platform/amplitude-http/v1/entry.mjs +19 -0
- package/apps/storefront/public/platform/google-measurement/v1/entry.mjs +19 -0
- package/apps/storefront/public/shared/commerce-chrome.css +20 -2
- package/apps/storefront/scripts/build-v84-clean-clone-standalone.mjs +68 -0
- package/apps/storefront/scripts/deny-v84-clean-clone-standalone-capabilities.mjs +111 -0
- package/apps/storefront/scripts/deny-v84-dogfood-capabilities.mjs +2 -0
- package/apps/storefront/scripts/deny-v84-lifecycle-capabilities.mjs +247 -0
- package/apps/storefront/scripts/dogfood-v84-nonportable-routes.ts +73 -0
- package/apps/storefront/scripts/fixed-v84-native-install-producer.ts +79 -0
- package/apps/storefront/scripts/generated/v84-clean-clone-replay.bundle.mjs +10982 -0
- package/apps/storefront/scripts/import-v84-through-ws2.ts +38 -0
- package/apps/storefront/scripts/initialize-v84-lifecycle-tooling-dependency.ts +2 -0
- package/apps/storefront/scripts/initialize-v84-lifecycle-tooling.ts +4 -0
- package/apps/storefront/scripts/install-v84-native-through-ws1.ts +185 -0
- package/apps/storefront/scripts/prove-clone-isolation.ts +506 -0
- package/apps/storefront/scripts/replay-v84-clean-clone-pass.ts +148 -0
- package/apps/storefront/scripts/replay-v84-clean-clone-standalone.ts +35 -0
- package/apps/storefront/scripts/replay-v84-clean-clone.ts +214 -0
- package/apps/storefront/scripts/run-fixed-v84-native-install-producer.mjs +18 -0
- package/apps/storefront/scripts/run-v84-clone-isolation-proof.mjs +18 -0
- package/apps/storefront/scripts/run-v84-clone-lifecycle-proof.mjs +18 -0
- package/apps/storefront/scripts/run-v84-import-through-ws2-proof.mjs +17 -0
- package/apps/storefront/scripts/run-v84-nonportable-route-dogfood.mjs +15 -0
- package/apps/storefront/scripts/sanitize-v84-proof-environment.mjs +21 -0
- package/apps/storefront/scripts/v84CleanCloneGraphPin.ts +42 -0
- package/apps/storefront/scripts/v84CleanCloneSourceGraph.ts +145 -0
- package/apps/storefront/scripts/validate-v84-clone-lifecycle.ts +18 -0
- package/apps/storefront/src/components/Img.astro +1 -1
- package/apps/storefront/src/components/admin/AdminBlogTab.astro +19 -16
- package/apps/storefront/src/components/admin/AdminIntegrationsTab.astro +105 -0
- package/apps/storefront/src/components/admin/AdminPublishTab.astro +1057 -412
- package/apps/storefront/src/components/admin/AdminSettingsTab.astro +2 -2
- package/apps/storefront/src/components/admin/orders/OrdersWorkspace.astro +5 -5
- package/apps/storefront/src/components/chrome/NavDropdown.astro +12 -0
- package/apps/storefront/src/components/chrome/SiteFooter.astro +7 -2
- package/apps/storefront/src/components/chrome/SiteHeader.astro +46 -10
- package/apps/storefront/src/components/compliance/AdultSignatureNotice.astro +1 -1
- package/apps/storefront/src/components/compliance/ComplianceNotice.astro +1 -2
- package/apps/storefront/src/components/compliance/ExciseTaxNotice.astro +1 -2
- package/apps/storefront/src/components/compliance/PactActNotice.astro +1 -1
- package/apps/storefront/src/components/compliance/Prop65Warning.astro +1 -2
- package/apps/storefront/src/components/compliance/PurchaseLimitNotice.astro +2 -3
- package/apps/storefront/src/components/compliance/ShippingRestrictionNotice.astro +2 -2
- package/apps/storefront/src/components/compliance/StateEligibilityNotice.astro +2 -3
- package/apps/storefront/src/components/content/TrustStrip.astro +4 -4
- package/apps/storefront/src/components/islands/MobileNav.tsx +26 -1
- package/apps/storefront/src/components/islands/VariantSelector.tsx +27 -2
- package/apps/storefront/src/components/islands/useCheckoutHandoffCorrelation.ts +75 -0
- package/apps/storefront/src/components/membership/IllustrativeLabel.astro +1 -2
- package/apps/storefront/src/components/membership/TierCards.astro +2 -2
- package/apps/storefront/src/components/membership/TierComparisonTable.astro +2 -2
- package/apps/storefront/src/components/subscription/SavingsExplainer.astro +2 -2
- package/apps/storefront/src/config/adminTenantLookup.ts +35 -0
- package/apps/storefront/src/config/devTenantSeed.ts +9 -9
- package/apps/storefront/src/config/resolver.ts +18 -10
- package/apps/storefront/src/layouts/Layout.astro +100 -20
- package/apps/storefront/src/lib/activity/candidateSubmitLease.ts +87 -0
- package/apps/storefront/src/lib/activity/changeActorAttribution.ts +86 -6
- package/apps/storefront/src/lib/activity/deployVersion.ts +1 -1
- package/apps/storefront/src/lib/admin/adminShell.ts +2 -0
- package/apps/storefront/src/lib/admin/envelope.ts +1 -2
- package/apps/storefront/src/lib/apps/adminService.ts +10 -0
- package/apps/storefront/src/lib/apps/adminSession.ts +1 -1
- package/apps/storefront/src/lib/apps/appVersionStore.ts +281 -0
- package/apps/storefront/src/lib/apps/consentConfigStore.ts +228 -0
- package/apps/storefront/src/lib/apps/gtmMigrationInstall.ts +447 -0
- package/apps/storefront/src/lib/apps/gtmMigrationReport.ts +367 -0
- package/apps/storefront/src/lib/apps/gtmMigrationTab.ts +315 -0
- package/apps/storefront/src/lib/apps/integrationLifecycle.ts +614 -0
- package/apps/storefront/src/lib/apps/integrationLifecycleController.ts +125 -0
- package/apps/storefront/src/lib/apps/integrationReadModel.ts +563 -0
- package/apps/storefront/src/lib/apps/integrationsTab.ts +547 -0
- package/apps/storefront/src/lib/apps/integrationsView.ts +106 -0
- package/apps/storefront/src/lib/apps/managedAnalyticsService.ts +599 -0
- package/apps/storefront/src/lib/apps/orders/orderConversionD1.ts +150 -0
- package/apps/storefront/src/lib/apps/orders/orderForwardReceiver.ts +8 -0
- package/apps/storefront/src/lib/apps/orders/ordersStore.ts +1 -1
- package/apps/storefront/src/lib/apps/orders/verifiedProviderMeasurement.ts +117 -0
- package/apps/storefront/src/lib/apps/registryService.ts +264 -10
- package/apps/storefront/src/lib/apps/tenantIntegrationMaterialization.ts +170 -0
- package/apps/storefront/src/lib/apps/v84CleanCloneReplayContext.ts +16 -0
- package/apps/storefront/src/lib/apps/v84CloneLifecycleCacheEvidence.ts +105 -0
- package/apps/storefront/src/lib/apps/v84CloneLifecycleEvidence.ts +177 -0
- package/apps/storefront/src/lib/apps/v84CloneLifecycleMaterialization.ts +251 -0
- package/apps/storefront/src/lib/apps/v84CloneLifecycleSemantics.ts +241 -0
- package/apps/storefront/src/lib/apps/v84CloneMigrationEvidence.ts +211 -0
- package/apps/storefront/src/lib/apps/v84CloneNativeInstallEvidence.ts +409 -0
- package/apps/storefront/src/lib/apps/v84NonportableCodeCoaching.ts +368 -0
- package/apps/storefront/src/lib/apps/v84NonportableRouteContract.ts +188 -0
- package/apps/storefront/src/lib/apps/v84NonportableRouteDogfood.ts +291 -0
- package/apps/storefront/src/lib/apps/ws1ManagedAnalyticsClient.ts +95 -0
- package/apps/storefront/src/lib/auth/adminApiGuard.ts +66 -0
- package/apps/storefront/src/lib/auth/adminEntry.ts +2 -2
- package/apps/storefront/src/lib/auth/adminLanding.ts +88 -0
- package/apps/storefront/src/lib/blog/collection.ts +114 -17
- package/apps/storefront/src/lib/blog/markdown.ts +1 -1
- package/apps/storefront/src/lib/blog/provider.ts +76 -9
- package/apps/storefront/src/lib/blog/types.ts +9 -9
- package/apps/storefront/src/lib/chrome/examples/tokenoftrust.chrome-assignments.example.json +6 -0
- package/apps/storefront/src/lib/chrome/examples/tokenoftrust.chrome.example.json +79 -0
- package/apps/storefront/src/lib/chrome/model.ts +48 -253
- package/apps/storefront/src/lib/chrome/navCurrent.ts +29 -0
- package/apps/storefront/src/lib/chrome/parseConfig.ts +18 -32
- package/apps/storefront/src/lib/chrome/variant.ts +39 -0
- package/apps/storefront/src/lib/commerce/checkoutHandoffCorrelation.ts +105 -0
- package/apps/storefront/src/lib/compliance/tamper-sentinel-boot.ts +152 -0
- package/apps/storefront/src/lib/compliance/tamper-sentinel.ts +327 -0
- package/apps/storefront/src/lib/content/index.ts +5 -5
- package/apps/storefront/src/lib/content-edit/applyDiff.ts +1 -1
- package/apps/storefront/src/lib/content-edit/route.ts +1 -1
- package/apps/storefront/src/lib/d1/catalog.ts +12 -0
- package/apps/storefront/src/lib/d1/schema-apps.ts +276 -0
- package/apps/storefront/src/lib/dev/cockpit/ask.ts +49 -0
- package/apps/storefront/src/lib/dev/cockpit/clickToSource.ts +71 -0
- package/apps/storefront/src/lib/dev/cockpit/copyCommand.ts +227 -0
- package/apps/storefront/src/lib/dev/cockpit/files.ts +249 -0
- package/apps/storefront/src/lib/dev/cockpit/hostedActivity.ts +500 -0
- package/apps/storefront/src/lib/dev/cockpit/infoPopover.ts +12 -0
- package/apps/storefront/src/lib/dev/cockpit/mcpCopy.ts +34 -0
- package/apps/storefront/src/lib/dev/cockpit/monitor.ts +107 -0
- package/apps/storefront/src/lib/dev/cockpit/stepper.ts +116 -0
- package/apps/storefront/src/lib/dev/cockpit/themeToggle.ts +23 -0
- package/apps/storefront/src/lib/dev/runtimeStore.ts +1 -1
- package/apps/storefront/src/lib/edgeCache.ts +18 -6
- package/apps/storefront/src/lib/email/candidateConflictEmail.ts +80 -0
- package/apps/storefront/src/lib/integration/browserEnvironment.ts +137 -0
- package/apps/storefront/src/lib/integration/managedInstallDeclarations.ts +72 -0
- package/apps/storefront/src/lib/integration/nonceStamp.ts +49 -0
- package/apps/storefront/src/lib/integration/tenantDeclarations.ts +116 -0
- package/apps/storefront/src/lib/membership/eligibility.ts +1 -1
- package/apps/storefront/src/lib/membership/model.ts +1 -2
- package/apps/storefront/src/lib/newsletter/confirmationEmail.ts +2 -2
- package/apps/storefront/src/lib/newsletter/consent.ts +2 -2
- package/apps/storefront/src/lib/newsletter/send.ts +3 -3
- package/apps/storefront/src/lib/previewReload.ts +57 -1
- package/apps/storefront/src/lib/publish/adminPublishTab.ts +1775 -669
- package/apps/storefront/src/lib/publish/buildStream.ts +269 -0
- package/apps/storefront/src/lib/publish/collapsePersistence.ts +72 -0
- package/apps/storefront/src/lib/publish/lifecycleState.ts +369 -0
- package/apps/storefront/src/lib/publish/liveReleaseSummary.ts +29 -0
- package/apps/storefront/src/lib/publish/mergeDoctorOnFailure.ts +78 -0
- package/apps/storefront/src/lib/publish/mergeHealthBanner.ts +62 -0
- package/apps/storefront/src/lib/publish/mergeRowFindings.ts +124 -0
- package/apps/storefront/src/lib/publish/mergeSupportEscalation.ts +75 -0
- package/apps/storefront/src/lib/publish/publish-guard.ts +88 -0
- package/apps/storefront/src/lib/publish/publishFormat.ts +165 -0
- package/apps/storefront/src/lib/publish/publishHero.ts +177 -0
- package/apps/storefront/src/lib/publish/reviewRelease/changeState.ts +66 -0
- package/apps/storefront/src/lib/publish/reviewRelease/copy.ts +141 -0
- package/apps/storefront/src/lib/publish/reviewRelease/index.ts +41 -0
- package/apps/storefront/src/lib/publish/reviewRelease/permissions.ts +47 -0
- package/apps/storefront/src/lib/publish/reviewRelease/releaseState.ts +42 -0
- package/apps/storefront/src/lib/publish/reviewRelease/types.ts +59 -0
- package/apps/storefront/src/lib/publish/reviewRelease/viewModel.ts +440 -0
- package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/actions.ts +161 -0
- package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/controller.ts +325 -0
- package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/render.ts +241 -0
- package/apps/storefront/src/lib/publish/shipWorkspace.ts +22 -3
- package/apps/storefront/src/lib/publish/sseRoute.ts +119 -0
- package/apps/storefront/src/lib/publish/streamLogConsole.ts +142 -0
- package/apps/storefront/src/lib/publish/versionNumber.ts +52 -0
- package/apps/storefront/src/lib/rawChrome.ts +125 -47
- package/apps/storefront/src/lib/rawMarketingHtml.ts +74 -3
- package/apps/storefront/src/lib/seo/alternates.ts +1 -1
- package/apps/storefront/src/lib/social/notify.ts +4 -4
- package/apps/storefront/src/lib/storyblok/content-model.ts +7 -33
- package/apps/storefront/src/lib/storyblok/provider.ts +203 -69
- package/apps/storefront/src/lib/subscription/model.ts +4 -4
- package/apps/storefront/src/lib/the-build/provider.ts +9 -4
- package/apps/storefront/src/middleware/index.ts +273 -51
- package/apps/storefront/src/pages/[...slug].astro +9 -4
- package/apps/storefront/src/pages/admin/review-release.astro +265 -0
- package/apps/storefront/src/pages/admin/select.astro +96 -0
- package/apps/storefront/src/pages/admin.astro +16 -2
- package/apps/storefront/src/pages/api/activity.ts +8 -8
- package/apps/storefront/src/pages/api/admin/activity-alerts.ts +5 -20
- package/apps/storefront/src/pages/api/admin/orders/[id].ts +7 -2
- package/apps/storefront/src/pages/api/admin/orders.ts +8 -2
- package/apps/storefront/src/pages/api/apps/admin/gtm-migration-install.ts +174 -0
- package/apps/storefront/src/pages/api/apps/admin/gtm-migration.ts +96 -0
- package/apps/storefront/src/pages/api/apps/admin/integration-lifecycle.ts +118 -0
- package/apps/storefront/src/pages/api/apps/admin/integrations.ts +89 -0
- package/apps/storefront/src/pages/api/apps/admin/platform-adapters.ts +85 -0
- package/apps/storefront/src/pages/api/apps/internal/order-forward.ts +74 -2
- package/apps/storefront/src/pages/api/commerce/checkout-handoff.ts +77 -0
- package/apps/storefront/src/pages/api/compliance/tamper.ts +88 -0
- package/apps/storefront/src/pages/api/integrations/v1/[operation].ts +108 -0
- package/apps/storefront/src/pages/api/rum/vitals.ts +1 -2
- package/apps/storefront/src/pages/blog/[slug].astro +5 -5
- package/apps/storefront/src/pages/blog/author/[author].astro +3 -3
- package/apps/storefront/src/pages/blog/category/[category].astro +2 -2
- package/apps/storefront/src/pages/blog/index.astro +2 -2
- package/apps/storefront/src/pages/blog/tag/[tag].astro +2 -2
- package/apps/storefront/src/pages/cockpit.astro +61 -1362
- package/apps/storefront/src/pages/index.astro +10 -5
- package/apps/storefront/src/pages/llms.txt.ts +1 -1
- package/apps/storefront/src/pages/products/[handle].astro +7 -6
- package/apps/storefront/src/pages/sitemap.xml.ts +2 -2
- package/apps/storefront/src/pages/style-guide/[tenant]/[theme].astro +42 -31
- package/apps/storefront/src/pages/style-guide/[tenant]/chrome/[theme].astro +8 -3
- package/apps/storefront/src/pages/style-guide/[tenant]/guide/[theme].astro +6 -2
- package/apps/storefront/src/pages/style-guide/[tenant]/index.astro +6 -2
- package/apps/storefront/src/pages/style-guide/index.astro +19 -5
- package/apps/storefront/src/pages/tenants/[id]/[...path].ts +38 -2
- package/apps/storefront/src/pages/the-build/[slug].astro +4 -4
- package/apps/storefront/src/pages/the-build/index.astro +2 -2
- package/apps/storefront/src/pages/the-build/rss.xml.ts +2 -2
- package/apps/storefront/src/styles/admin.css +27 -1
- package/apps/storefront/src/themes/tenants/index.ts +19 -7
- package/apps/storefront/tsconfig.json +1 -1
- package/package.json +1 -1
- package/packages/public-runtime/package.json +2 -1
- package/packages/public-runtime/schemas/managed-analytics/v1/adapter-definition.schema.json +427 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/create-integration-install-request.schema.json +161 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/create-migration-draft-request.schema.json +157 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/delete-integration-install-request.schema.json +84 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/delete-migration-draft-request.schema.json +84 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/error-envelope.schema.json +180 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/get-adapter-definition-request.schema.json +52 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/get-integration-install-request.schema.json +52 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/get-integration-revision-request.schema.json +79 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/get-migration-draft-request.schema.json +52 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/integration-install.schema.json +167 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/integration-revision.schema.json +208 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/list-adapter-definitions-request.schema.json +53 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/list-integration-installs-request.schema.json +53 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/list-integration-revisions-request.schema.json +82 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/migration-draft.schema.json +218 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/mutation-request.schema.json +52 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/mutation-success-envelope.schema.json +49 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/read-request.schema.json +20 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/read-success-envelope.schema.json +45 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/revise-integration-install-request.schema.json +156 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/set-integration-install-status-request.schema.json +92 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/update-migration-draft-request.schema.json +184 -0
- package/packages/public-runtime/scripts/generate-managed-analytics-api-v1-schemas.mjs +13 -0
- package/packages/public-runtime/src/activity/catalog.ts +41 -37
- package/packages/public-runtime/src/activity/event.ts +6 -6
- package/packages/public-runtime/src/activity/index.ts +2 -2
- package/packages/public-runtime/src/activity/redaction.ts +4 -4
- package/packages/public-runtime/src/candidate-index.ts +42 -8
- package/packages/public-runtime/src/checkout-orders.ts +2 -2
- package/packages/public-runtime/src/checkout-style-publish.ts +2 -2
- package/packages/public-runtime/src/checkout-style.ts +7 -7
- package/packages/public-runtime/src/checkout.ts +3 -3
- package/packages/public-runtime/src/chrome.ts +777 -0
- package/packages/public-runtime/src/compliance-obligations.ts +310 -0
- package/packages/public-runtime/src/csp.ts +99 -0
- package/packages/public-runtime/src/customization-runtime.ts +32 -1
- package/packages/public-runtime/src/destination-webhook/contract.ts +353 -0
- package/packages/public-runtime/src/embed-catalog.d.mts +9 -0
- package/packages/public-runtime/src/embed-catalog.mjs +40 -0
- package/packages/public-runtime/src/evidence.ts +288 -0
- package/packages/public-runtime/src/gtm-migration/bounds.ts +138 -0
- package/packages/public-runtime/src/gtm-migration/disposition-rules.ts +361 -0
- package/packages/public-runtime/src/gtm-migration/dispositions.ts +181 -0
- package/packages/public-runtime/src/gtm-migration/draft-generator.ts +400 -0
- package/packages/public-runtime/src/gtm-migration/graph.ts +468 -0
- package/packages/public-runtime/src/gtm-migration/index.ts +95 -0
- package/packages/public-runtime/src/gtm-migration/parse.ts +410 -0
- package/packages/public-runtime/src/gtm-migration/references.ts +266 -0
- package/packages/public-runtime/src/gtm-migration/types.ts +86 -0
- package/packages/public-runtime/src/index.ts +16 -1
- package/packages/public-runtime/src/integration/assurance-decision.ts +259 -0
- package/packages/public-runtime/src/integration/assurance.ts +228 -0
- package/packages/public-runtime/src/integration/candidate-evidence.ts +377 -0
- package/packages/public-runtime/src/integration/commerce-broker.ts +497 -0
- package/packages/public-runtime/src/integration/consent-broker.ts +337 -0
- package/packages/public-runtime/src/integration/consent-cmp-demand.ts +115 -0
- package/packages/public-runtime/src/integration/consent-config.ts +260 -0
- package/packages/public-runtime/src/integration/consent-preview.ts +125 -0
- package/packages/public-runtime/src/integration/correlation-token.ts +444 -0
- package/packages/public-runtime/src/integration/csp-compiler.ts +267 -0
- package/packages/public-runtime/src/integration/csp.ts +105 -0
- package/packages/public-runtime/src/integration/digest.ts +43 -0
- package/packages/public-runtime/src/integration/environment.ts +228 -0
- package/packages/public-runtime/src/integration/events-catalog.ts +186 -0
- package/packages/public-runtime/src/integration/events.ts +701 -0
- package/packages/public-runtime/src/integration/g2-action-boundary.ts +106 -0
- package/packages/public-runtime/src/integration/index.ts +90 -0
- package/packages/public-runtime/src/integration/interim-bridge.ts +138 -0
- package/packages/public-runtime/src/integration/lifecycle-boot.ts +317 -0
- package/packages/public-runtime/src/integration/lifecycle-runtime.ts +393 -0
- package/packages/public-runtime/src/integration/lifecycle.ts +94 -0
- package/packages/public-runtime/src/integration/managed-analytics-api-v1-safety.d.mts +2 -0
- package/packages/public-runtime/src/integration/managed-analytics-api-v1-safety.mjs +10 -0
- package/packages/public-runtime/src/integration/managed-analytics-api-v1.schemas.ts +382 -0
- package/packages/public-runtime/src/integration/managed-analytics-api-v1.ts +684 -0
- package/packages/public-runtime/src/integration/managed-analytics-bridge.ts +383 -0
- package/packages/public-runtime/src/integration/managed-analytics-value-safety.ts +14 -0
- package/packages/public-runtime/src/integration/managed-analytics.ts +277 -0
- package/packages/public-runtime/src/integration/manifest.ts +470 -0
- package/packages/public-runtime/src/integration/native-amplitude.ts +362 -0
- package/packages/public-runtime/src/integration/native-ga4.ts +352 -0
- package/packages/public-runtime/src/integration/observability.ts +451 -0
- package/packages/public-runtime/src/integration/order-conversion.ts +0 -0
- package/packages/public-runtime/src/integration/phases.ts +182 -0
- package/packages/public-runtime/src/integration/plan.ts +112 -0
- package/packages/public-runtime/src/integration/platform-adapter-runtime.ts +146 -0
- package/packages/public-runtime/src/integration/platform-adapters.ts +671 -0
- package/packages/public-runtime/src/integration/promotion-gate.ts +451 -0
- package/packages/public-runtime/src/integration/provider-checkout-extension.ts +425 -0
- package/packages/public-runtime/src/integration/receipt-measurement.ts +370 -0
- package/packages/public-runtime/src/integration/regulated-parent-cert.ts +311 -0
- package/packages/public-runtime/src/integration/render-phases.ts +267 -0
- package/packages/public-runtime/src/integration/resolve.ts +465 -0
- package/packages/public-runtime/src/integration/result.ts +239 -0
- package/packages/public-runtime/src/integration/secrets.ts +92 -0
- package/packages/public-runtime/src/integration/tamper-evidence.ts +205 -0
- package/packages/public-runtime/src/integration/tamper-recovery.ts +127 -0
- package/packages/public-runtime/src/integration/version.ts +92 -0
- package/packages/public-runtime/src/integration-capabilities.ts +520 -0
- package/packages/public-runtime/src/membership.ts +2 -2
- package/packages/public-runtime/src/raw-html-policy.ts +2 -4
- package/packages/public-runtime/src/regulated-commerce/broker/consent-dedupe.ts +144 -0
- package/packages/public-runtime/src/regulated-commerce/correlation/contract.ts +177 -0
- package/packages/public-runtime/src/regulated-commerce/diagnostics/local-dispatch.ts +24 -0
- package/packages/public-runtime/src/regulated-commerce/diagnostics/read-model.ts +69 -0
- package/packages/public-runtime/src/regulated-commerce/dispatch/core.ts +134 -0
- package/packages/public-runtime/src/regulated-commerce/events/contract.ts +113 -0
- package/packages/public-runtime/src/regulated-commerce/ledger/core.ts +81 -0
- package/packages/public-runtime/src/regulated-commerce/mappings/ga4-ads.ts +87 -0
- package/packages/public-runtime/src/regulated-commerce/provider-order/bridge.ts +52 -0
- package/packages/public-runtime/src/regulated-commerce/purchase-truth/contract.ts +252 -0
- package/packages/public-runtime/src/regulated-commerce/sources/accepted-browser-operation.ts +67 -0
- package/packages/public-runtime/src/regulated-commerce/testing/fake-destination.ts +93 -0
- package/packages/public-runtime/src/static-bundle.ts +5 -5
- package/packages/public-runtime/src/tenant-assets.ts +143 -4
- package/packages/public-runtime/src/tenant.ts +136 -9
- package/packages/public-runtime/tests/fixtures/integration/browser-environment.example.json +8 -0
- package/packages/public-runtime/tests/fixtures/integration/commerce-event.example.json +44 -0
- package/packages/public-runtime/tests/fixtures/integration/commerce-order-event.example.json +44 -0
- package/packages/public-runtime/tests/fixtures/integration/compliance-assurance-record.example.json +50 -0
- package/packages/public-runtime/tests/fixtures/integration/integration-manifest-v2.example.json +120 -0
- package/packages/public-runtime/tests/fixtures/integration/managed-analytics-v1/fake-adapter-catalog.json +35 -0
- package/packages/public-runtime/tests/fixtures/integration/tenant-action-registry.example.json +11 -0
- package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/README.md +22 -0
- package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/entry.js +30 -0
- package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/manifest.json +75 -0
- package/packages/public-runtime/tests/fixtures/integration/tenant-local-example/public-config.json +3 -0
- package/packages/public-runtime/tests/fixtures/regulated-commerce/fake-destinations.ts +38 -0
- package/packages/public-runtime/tests/gtm-migration/fixtures/gtm-sample-container-v84.provenance.json +29 -0
- package/packages/public-runtime/tests/gtm-migration/fixtures/gtm-sample-container-v84.redacted.json +3397 -0
- package/scripts/build/copy-tenant-assets.mjs +12 -5
- package/scripts/dev/ai-edit.mjs +2 -1
- package/scripts/dev/file-browser.mjs +2 -2
- package/scripts/dev/file-writer.mjs +2 -2
- package/scripts/dev/integration-guardrails.mjs +498 -0
- package/scripts/dev/safe-path.mjs +2 -2
- package/scripts/dev/tenant-source-root.mjs +3 -2
- package/scripts/tot-dev.mjs +5 -4
|
@@ -15,6 +15,11 @@
|
|
|
15
15
|
* injection is a deliberate string replace — robust for first-party documents, not a
|
|
16
16
|
* sanitizer. Untrusted vendor HTML must be sandboxed/sanitized instead.
|
|
17
17
|
*/
|
|
18
|
+
import {
|
|
19
|
+
renderIntegrationPhases,
|
|
20
|
+
type EffectivePlan,
|
|
21
|
+
} from "@tot/public-runtime";
|
|
22
|
+
|
|
18
23
|
export { rawHtmlPolicy } from "@tot/public-runtime";
|
|
19
24
|
|
|
20
25
|
/**
|
|
@@ -64,6 +69,50 @@ export function wrapFragmentInChrome(fragment: string, chromeWrapper: string): s
|
|
|
64
69
|
return out;
|
|
65
70
|
}
|
|
66
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Insert `snippet` at the very start of the document's `<head>` (or, absent a
|
|
74
|
+
* head, right after `<html>`, else at the document start) — so the renderer-owned
|
|
75
|
+
* head phases precede any tenant/app `<script>`/`<style>` in the served document.
|
|
76
|
+
* A body-only fragment with no wrapper has neither tag, so it is prepended.
|
|
77
|
+
*/
|
|
78
|
+
function injectHeadStart(html: string, snippet: string): string {
|
|
79
|
+
// `(?:\s[^>]*)?>` matches `<head>` / `<head lang=…>` but NOT `<header>`.
|
|
80
|
+
const head = /<head(?:\s[^>]*)?>/i.exec(html);
|
|
81
|
+
if (head) {
|
|
82
|
+
const at = head.index + head[0].length;
|
|
83
|
+
return html.slice(0, at) + snippet + html.slice(at);
|
|
84
|
+
}
|
|
85
|
+
const htmlTag = /<html(?:\s[^>]*)?>/i.exec(html);
|
|
86
|
+
if (htmlTag) {
|
|
87
|
+
const at = htmlTag.index + htmlTag[0].length;
|
|
88
|
+
return html.slice(0, at) + snippet + html.slice(at);
|
|
89
|
+
}
|
|
90
|
+
return snippet + html;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Insert `snippet` at the very start of `<body>` (the body.start boundary), or —
|
|
95
|
+
* for a body-only fragment with no `<body>` — at the document start.
|
|
96
|
+
*/
|
|
97
|
+
function injectBodyStart(html: string, snippet: string): string {
|
|
98
|
+
const body = /<body(?:\s[^>]*)?>/i.exec(html);
|
|
99
|
+
if (body) {
|
|
100
|
+
const at = body.index + body[0].length;
|
|
101
|
+
return html.slice(0, at) + snippet + html.slice(at);
|
|
102
|
+
}
|
|
103
|
+
return snippet + html;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Insert `snippet` immediately before `</body>` (the document.deferred boundary),
|
|
108
|
+
* or — for a body-only fragment with no `</body>` — at the document end.
|
|
109
|
+
*/
|
|
110
|
+
function injectBodyEnd(html: string, snippet: string): string {
|
|
111
|
+
const at = html.toLowerCase().lastIndexOf("</body>");
|
|
112
|
+
if (at >= 0) return html.slice(0, at) + snippet + html.slice(at);
|
|
113
|
+
return html + snippet;
|
|
114
|
+
}
|
|
115
|
+
|
|
67
116
|
/** Stamp `nonce` onto every inline `<style>`/`<script>` so a strict CSP admits them. */
|
|
68
117
|
export function nonceInlineTags(html: string, nonce: string): string {
|
|
69
118
|
return html
|
|
@@ -79,7 +128,7 @@ export function nonceInlineTags(html: string, nonce: string): string {
|
|
|
79
128
|
* must NOT get the tenant prefix:
|
|
80
129
|
* - `/tenants/…` — per-tenant static assets (logo, favicon, theme/mkt css);
|
|
81
130
|
* - `/shared/…` — platform-shipped shared assets (e.g. the shared commerce-chrome
|
|
82
|
-
* stylesheet
|
|
131
|
+
* stylesheet) that every tenant links.
|
|
83
132
|
* Asset `src="…"` isn't matched at all (href-only).
|
|
84
133
|
*/
|
|
85
134
|
export function prefixTenantLinks(html: string, basePath: string): string {
|
|
@@ -100,7 +149,19 @@ export function prefixTenantLinks(html: string, basePath: string): string {
|
|
|
100
149
|
*/
|
|
101
150
|
export function rawHtmlResponse(
|
|
102
151
|
html: string,
|
|
103
|
-
opts: {
|
|
152
|
+
opts: {
|
|
153
|
+
nonce: string;
|
|
154
|
+
basePath: string;
|
|
155
|
+
chromeWrapper?: string | null;
|
|
156
|
+
/**
|
|
157
|
+
* The resolved effective plan for this request — the raw-mount half of the
|
|
158
|
+
* "one engine, two mounts" contract (Layout.astro is the other). The SAME
|
|
159
|
+
* render engine emits the renderer-owned phases into this document, so its
|
|
160
|
+
* phase markup is byte-identical to Layout's. Required: no render path may
|
|
161
|
+
* silently omit it. Nonces are stamped per response, never serialized (ADR 0018).
|
|
162
|
+
*/
|
|
163
|
+
plan: EffectivePlan;
|
|
164
|
+
},
|
|
104
165
|
): Response {
|
|
105
166
|
let composed =
|
|
106
167
|
isFullDocument(html) || !opts.chromeWrapper
|
|
@@ -130,7 +191,17 @@ export function rawHtmlResponse(
|
|
|
130
191
|
? composed.replace("</body>", `${widget}</body>`)
|
|
131
192
|
: `${composed}${widget}`;
|
|
132
193
|
}
|
|
133
|
-
|
|
194
|
+
// Stamp the page nonce onto the TENANT's inline tags first, then splice the render
|
|
195
|
+
// engine's phase markup — which already carries the nonce — so the engine bytes are
|
|
196
|
+
// never double-stamped and match Layout.astro's mount exactly. `opts.nonce` is the
|
|
197
|
+
// per-response nonce placeholder; the middleware substitutes a fresh nonce at serve
|
|
198
|
+
// time (ADR 0018), so the same engine output feeds both mounts identically.
|
|
199
|
+
composed = nonceInlineTags(composed, opts.nonce);
|
|
200
|
+
const phases = renderIntegrationPhases(opts.plan, opts.nonce);
|
|
201
|
+
composed = injectHeadStart(composed, phases.head);
|
|
202
|
+
composed = injectBodyStart(composed, phases.bodyStart);
|
|
203
|
+
composed = injectBodyEnd(composed, phases.bodyDeferred);
|
|
204
|
+
const doc = prefixTenantLinks(composed, opts.basePath);
|
|
134
205
|
return new Response(doc, {
|
|
135
206
|
headers: { "content-type": "text/html; charset=utf-8" },
|
|
136
207
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* hreflang alternates for a translated article
|
|
2
|
+
* hreflang alternates for a translated article. Two pure helpers so the
|
|
3
3
|
* article route and <Seo> stay thin and the "singleton emits nothing" guard is
|
|
4
4
|
* unit-testable without an Astro render harness.
|
|
5
5
|
*/
|
|
@@ -10,13 +10,13 @@ function logDiag(fields: Record<string, unknown>): void {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* The
|
|
13
|
+
* The seam — the single call the go-live hook makes when a blog post
|
|
14
14
|
* transitions INTO a live-visible state (see {@link notifyPostsWentLive}). This
|
|
15
|
-
*
|
|
15
|
+
* ships the HOOK + the at-most-once idempotency key, NOT the network
|
|
16
16
|
* last-mile: v1 is a no-op announcement (logs only).
|
|
17
17
|
*
|
|
18
|
-
*
|
|
19
|
-
* config + encrypted token store
|
|
18
|
+
* A `SocialPublisher` interface + X adapter, and per-`(tenant,network)`
|
|
19
|
+
* config + encrypted token store, replace THIS body to compose a per-network
|
|
20
20
|
* payload — title, canonical URL, excerpt, featured image, `categories[0]` →
|
|
21
21
|
* hashtags — and POST it to each configured network's publish API. The signature
|
|
22
22
|
* is deliberately per-POST; per-network fan-out and the per-network idempotency
|
|
@@ -64,7 +64,7 @@ export interface FeaturedProductsBlock {
|
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
66
|
* Membership "band" on the commerce home — the Guest / Member / Autopilot tier
|
|
67
|
-
* comparison, driven by the platform <TierCards>
|
|
67
|
+
* comparison, driven by the platform <TierCards> widget from tenant data.
|
|
68
68
|
* The widget owns the illustrative-figures affordance (a tier is an EXAMPLE
|
|
69
69
|
* unless it asserts `real: true`), so a tenant surfaces its membership model on
|
|
70
70
|
* the home page without copying tier-card HTML. The block carries the same
|
|
@@ -271,11 +271,11 @@ export interface EditorialPage {
|
|
|
271
271
|
/**
|
|
272
272
|
* Sanitized HTML body (legacy / raw path). Optional: a page authored with
|
|
273
273
|
* structured {@link ProseSection}s omits it and drives the body from `sections`
|
|
274
|
-
* instead
|
|
274
|
+
* instead. The renderer prefers `sections` when present.
|
|
275
275
|
*/
|
|
276
276
|
body_html?: string;
|
|
277
277
|
/**
|
|
278
|
-
* Structured policy/prose body
|
|
278
|
+
* Structured policy/prose body — headings, paragraphs, lists,
|
|
279
279
|
* callouts, a contact block, needs-review flags. When present, the page route
|
|
280
280
|
* renders <ProseSections> from this instead of `set:html(body_html)`, so a
|
|
281
281
|
* tenant drives a full policy page from JSON with no HTML/CSS to copy.
|
|
@@ -290,33 +290,7 @@ export interface EditorialPage {
|
|
|
290
290
|
isPolicy?: boolean;
|
|
291
291
|
}
|
|
292
292
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
children?: { label: string; href: string }[];
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
export interface SiteChrome {
|
|
301
|
-
nav: SiteNavItem[];
|
|
302
|
-
/**
|
|
303
|
-
* Header conversion buttons (marketing tenants). Rendered in the header's
|
|
304
|
-
* right cluster in place of the commerce search/saved controls. Commerce
|
|
305
|
-
* tenants omit this and keep the search + saved affordances.
|
|
306
|
-
*/
|
|
307
|
-
headerCtas?: MarketingCta[];
|
|
308
|
-
footer: {
|
|
309
|
-
columns: { title: string; links: { label: string; href: string }[] }[];
|
|
310
|
-
social: { label: string; href: string }[];
|
|
311
|
-
tagline?: string;
|
|
312
|
-
/** Bottom-bar legal/compliance line (marketing tenants). Plain text. */
|
|
313
|
-
legal?: string;
|
|
314
|
-
};
|
|
315
|
-
announcement?: string;
|
|
316
|
-
/** Default legal/safety disclaimer HTML (fallback for unknown categories). */
|
|
317
|
-
disclaimer?: string;
|
|
318
|
-
/** Per-product-category disclaimer HTML, keyed by product_type (or
|
|
319
|
-
* compliance.product_category) — the source store renders a different legal
|
|
320
|
-
* block per type (sub-ohm on salts, battery on devices). */
|
|
321
|
-
disclaimers?: Record<string, string>;
|
|
322
|
-
}
|
|
293
|
+
// Site chrome (header/footer) is the ONE consolidated schema in
|
|
294
|
+
// lib/chrome/model.ts (`ChromeConfig`) — see that file's header comment for
|
|
295
|
+
// why the old `SiteChrome`/`SiteNavItem` pair that used to live here was
|
|
296
|
+
// retired.
|
|
@@ -17,12 +17,26 @@
|
|
|
17
17
|
import type {
|
|
18
18
|
EditorialPage,
|
|
19
19
|
HomeContent,
|
|
20
|
-
SiteChrome,
|
|
21
20
|
} from "./content-model.js";
|
|
21
|
+
import type { ChromeConfig } from "../chrome/model.js";
|
|
22
22
|
import type { CustomizationEnv, KvGet, TenantConfig } from "@tot/public-runtime";
|
|
23
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
contentArtifactPath,
|
|
25
|
+
listPublishedArtifactPaths,
|
|
26
|
+
readPublishedArtifact,
|
|
27
|
+
tenantDirRelative,
|
|
28
|
+
tenantDirSegment,
|
|
29
|
+
} from "@tot/public-runtime";
|
|
24
30
|
import { renderRawChrome } from "../rawChrome.js";
|
|
25
31
|
import { parseChromeConfig } from "../chrome/parseConfig.js";
|
|
32
|
+
import { parseChromeAssignments } from "../chrome/model.js";
|
|
33
|
+
|
|
34
|
+
/** Safe, always-valid fallback when neither the tenant's own chrome.json nor
|
|
35
|
+
* the default scope's validates — the storefront must always render. */
|
|
36
|
+
const EMPTY_CHROME_CONFIG: ChromeConfig = {
|
|
37
|
+
header: { variant: "primary", brand: { label: "" }, nav: [] },
|
|
38
|
+
footer: { variant: "default", columns: [] },
|
|
39
|
+
};
|
|
26
40
|
|
|
27
41
|
export interface ContentProvider {
|
|
28
42
|
getHome(): Promise<HomeContent>;
|
|
@@ -48,11 +62,21 @@ export interface ContentProvider {
|
|
|
48
62
|
* spliced in (see lib/rawMarketingHtml.ts). Lets raw pages stop duplicating
|
|
49
63
|
* chrome: chrome lives once here, pages become fragments. File:
|
|
50
64
|
* `content/<tenant>/chrome.html`. Tenant-owned (no cross-tenant fallback).
|
|
65
|
+
*
|
|
66
|
+
* `currentPath`, when given, is the tenant-relative request path (e.g. "/"
|
|
67
|
+
* or "/collections/all") in the same unprefixed form the chrome config's
|
|
68
|
+
* own nav `href`s are authored in — drives `aria-current="page"` on the
|
|
69
|
+
* matching primary-nav link when the wrapper is rendered from chrome.json
|
|
70
|
+
* (rawChrome.ts). Omitted ⇒ no item is marked current.
|
|
51
71
|
*/
|
|
52
|
-
getChromeHtml(): Promise<string | null>;
|
|
53
|
-
getChrome(): Promise<
|
|
72
|
+
getChromeHtml(currentPath?: string): Promise<string | null>;
|
|
73
|
+
getChrome(): Promise<ChromeConfig>;
|
|
54
74
|
getPage(slug: string): Promise<EditorialPage | null>;
|
|
55
75
|
listPages(): Promise<EditorialPage[]>;
|
|
76
|
+
/** Raw tenant-content artifact, relative to `content/`. */
|
|
77
|
+
readArtifact(relative: string): Promise<string | null>;
|
|
78
|
+
/** Artifact paths relative to `content/`; null means use a legacy fallback. */
|
|
79
|
+
listArtifactPaths(prefix?: string): Promise<string[] | null>;
|
|
56
80
|
}
|
|
57
81
|
|
|
58
82
|
// ---------------------------------------------------------------------------
|
|
@@ -64,30 +88,65 @@ export interface ContentProvider {
|
|
|
64
88
|
// time; a file missing for a tenant falls back to the default scope, so a
|
|
65
89
|
// partially-adopted tenant still renders. Depth: from src/lib/storyblok/ the
|
|
66
90
|
// repo root is five levels up (storyblok→lib→src→storefront→apps→root).
|
|
67
|
-
const homeGlobs = import.meta.glob<HomeContent>(
|
|
91
|
+
const homeGlobs = import.meta.glob<HomeContent>([
|
|
92
|
+
"../../../../../tenants/*/content/home.json",
|
|
93
|
+
"../../../../../tenants/e2e/*/content/home.json",
|
|
94
|
+
], {
|
|
68
95
|
import: "default",
|
|
69
96
|
});
|
|
70
|
-
const chromeGlobs = import.meta.glob<
|
|
97
|
+
const chromeGlobs = import.meta.glob<unknown>([
|
|
98
|
+
"../../../../../tenants/*/content/chrome.json",
|
|
99
|
+
"../../../../../tenants/e2e/*/content/chrome.json",
|
|
100
|
+
], {
|
|
71
101
|
import: "default",
|
|
72
102
|
});
|
|
73
|
-
|
|
103
|
+
// Per-page header/footer variant overrides for the raw-HTML chrome path (see
|
|
104
|
+
// lib/chrome/assignments.ts). Tenant-owned, no cross-tenant fallback — same
|
|
105
|
+
// convention as chromeGlobs above.
|
|
106
|
+
const chromeAssignmentGlobs = import.meta.glob<unknown>(
|
|
107
|
+
[
|
|
108
|
+
"../../../../../tenants/*/content/chrome-assignments.json",
|
|
109
|
+
"../../../../../tenants/e2e/*/content/chrome-assignments.json",
|
|
110
|
+
],
|
|
111
|
+
{ import: "default" },
|
|
112
|
+
);
|
|
113
|
+
const pageGlobs = import.meta.glob<EditorialPage>([
|
|
114
|
+
"../../../../../tenants/*/content/pages/*.json",
|
|
115
|
+
"../../../../../tenants/e2e/*/content/pages/*.json",
|
|
116
|
+
], {
|
|
74
117
|
import: "default",
|
|
75
118
|
});
|
|
76
119
|
// Raw self-authored home documents (marketing tenants). `?raw` bundles the file
|
|
77
120
|
// as a string; a tenant with no home.html simply has no entry here.
|
|
78
|
-
const homeHtmlGlobs = import.meta.glob<string>(
|
|
121
|
+
const homeHtmlGlobs = import.meta.glob<string>([
|
|
122
|
+
"../../../../../tenants/*/content/home.html",
|
|
123
|
+
"../../../../../tenants/e2e/*/content/home.html",
|
|
124
|
+
], {
|
|
79
125
|
query: "?raw",
|
|
80
126
|
import: "default",
|
|
81
127
|
});
|
|
82
128
|
// Raw self-authored SUBPAGE documents: tenants/<tenant>/content/pages-html/<slug>.html
|
|
83
129
|
// (e.g. pages-html/pricing.html -> /pricing/). `**` supports nested slugs.
|
|
84
|
-
const pageHtmlGlobs = import.meta.glob<string>(
|
|
130
|
+
const pageHtmlGlobs = import.meta.glob<string>([
|
|
131
|
+
"../../../../../tenants/*/content/pages-html/**/*.html",
|
|
132
|
+
"../../../../../tenants/e2e/*/content/pages-html/**/*.html",
|
|
133
|
+
], {
|
|
85
134
|
query: "?raw",
|
|
86
135
|
import: "default",
|
|
87
136
|
});
|
|
88
137
|
// Shared-chrome wrapper document (marketing tenants that author body-only page
|
|
89
138
|
// fragments). One per tenant; a tenant without it keeps serving full documents.
|
|
90
|
-
const chromeHtmlGlobs = import.meta.glob<string>(
|
|
139
|
+
const chromeHtmlGlobs = import.meta.glob<string>([
|
|
140
|
+
"../../../../../tenants/*/content/chrome.html",
|
|
141
|
+
"../../../../../tenants/e2e/*/content/chrome.html",
|
|
142
|
+
], {
|
|
143
|
+
query: "?raw",
|
|
144
|
+
import: "default",
|
|
145
|
+
});
|
|
146
|
+
const artifactGlobs = import.meta.glob<string>([
|
|
147
|
+
"../../../../../tenants/*/content/**/*",
|
|
148
|
+
"../../../../../tenants/e2e/*/content/**/*",
|
|
149
|
+
], {
|
|
91
150
|
query: "?raw",
|
|
92
151
|
import: "default",
|
|
93
152
|
});
|
|
@@ -98,13 +157,13 @@ function pickContent<T>(
|
|
|
98
157
|
globs: Record<string, () => Promise<T>>,
|
|
99
158
|
scope: string,
|
|
100
159
|
): (() => Promise<T>) | null {
|
|
101
|
-
const seg =
|
|
160
|
+
const seg = `${tenantDirSegment(scope)}content/`;
|
|
102
161
|
const key = Object.keys(globs).find((k) => k.includes(seg));
|
|
103
162
|
return (key ? globs[key] : null) ?? null;
|
|
104
163
|
}
|
|
105
164
|
|
|
106
165
|
function pickPages(scope: string): Array<() => Promise<EditorialPage>> {
|
|
107
|
-
const seg =
|
|
166
|
+
const seg = `${tenantDirSegment(scope)}content/pages/`;
|
|
108
167
|
return Object.keys(pageGlobs)
|
|
109
168
|
.filter((k) => k.includes(seg))
|
|
110
169
|
.map((k) => pageGlobs[k]!);
|
|
@@ -117,7 +176,7 @@ function normalizeRawPageSlug(slug: string): string {
|
|
|
117
176
|
|
|
118
177
|
/** The subpage slug a glob key encodes for `scope`, or null if it's not one. */
|
|
119
178
|
function rawPageSlugForKey(scope: string, key: string): string | null {
|
|
120
|
-
const seg =
|
|
179
|
+
const seg = `${tenantDirSegment(scope)}content/pages-html/`;
|
|
121
180
|
const start = key.indexOf(seg);
|
|
122
181
|
if (start < 0 || !key.endsWith(".html")) return null;
|
|
123
182
|
return key.slice(start + seg.length, -".html".length);
|
|
@@ -154,7 +213,7 @@ async function readRawDevFile(scope: string, relative: string): Promise<string |
|
|
|
154
213
|
// provider.ts lives at apps/storefront/src/lib/storyblok/ — the repo root is five
|
|
155
214
|
// levels up, mirroring the import.meta.glob prefix ("../../../../../tenants/...").
|
|
156
215
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
157
|
-
const abs = resolve(here, "../../../../..", "
|
|
216
|
+
const abs = resolve(here, "../../../../..", ...tenantDirRelative(scope).split("/"), "content", relative);
|
|
158
217
|
return await readFile(abs, "utf8");
|
|
159
218
|
} catch {
|
|
160
219
|
return null;
|
|
@@ -196,18 +255,22 @@ export class LocalContentProvider implements ContentProvider {
|
|
|
196
255
|
.sort((a, b) => a.localeCompare(b));
|
|
197
256
|
}
|
|
198
257
|
|
|
199
|
-
async getChromeHtml(): Promise<string | null> {
|
|
258
|
+
async getChromeHtml(currentPath?: string): Promise<string | null> {
|
|
200
259
|
// (1) A hand-authored chrome.html wins — keeps a not-yet-adopted tenant's chrome
|
|
201
260
|
// byte-identical (dev: fresh from disk so an edit previews on save).
|
|
202
261
|
const dev = await readRawDevFile(this.scope, "chrome.html");
|
|
203
262
|
if (dev !== null) return dev;
|
|
204
263
|
const loader = pickContent(chromeHtmlGlobs, this.scope);
|
|
205
264
|
if (loader) return loader();
|
|
206
|
-
// (2) No chrome.html: render the CANONICAL shared chrome
|
|
207
|
-
// tenant's chrome.json — but only when it
|
|
208
|
-
//
|
|
265
|
+
// (2) No chrome.html: render the CANONICAL shared chrome from the
|
|
266
|
+
// tenant's chrome.json — but only when it validates whole against the
|
|
267
|
+
// consolidated ChromeConfig schema (lib/chrome/parseConfig.ts). Tenant-
|
|
268
|
+
// owned only, like the raw HTML above (no cross-tenant fallback).
|
|
209
269
|
const config = await this.loadChromeConfig();
|
|
210
|
-
|
|
270
|
+
if (!config) return null;
|
|
271
|
+
// Per-page variant override — a page not listed in chrome-assignments.json
|
|
272
|
+
// keeps chrome.json's default variant. renderRawChrome resolves it.
|
|
273
|
+
return renderRawChrome(config, currentPath, await this.loadChromeAssignments());
|
|
211
274
|
}
|
|
212
275
|
|
|
213
276
|
/** The tenant's parsed raw-chrome config (chrome.json), or null if not adopted. */
|
|
@@ -225,31 +288,33 @@ export class LocalContentProvider implements ContentProvider {
|
|
|
225
288
|
return loader ? parseChromeConfig(await loader()) : null;
|
|
226
289
|
}
|
|
227
290
|
|
|
228
|
-
|
|
291
|
+
/** The tenant's parsed page-assignment manifest (chrome-assignments.json),
|
|
292
|
+
* or null if absent/invalid — same dev-hot-reload + tenant-owned-only
|
|
293
|
+
* discipline as loadChromeConfig. */
|
|
294
|
+
private async loadChromeAssignments(): Promise<ReturnType<typeof parseChromeAssignments>> {
|
|
295
|
+
const devRaw = await readRawDevFile(this.scope, "chrome-assignments.json");
|
|
296
|
+
if (devRaw !== null) {
|
|
297
|
+
try {
|
|
298
|
+
return parseChromeAssignments(JSON.parse(devRaw));
|
|
299
|
+
} catch {
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
const loader = pickContent(chromeAssignmentGlobs, this.scope);
|
|
304
|
+
return loader ? parseChromeAssignments(await loader()) : null;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
async getChrome(): Promise<ChromeConfig> {
|
|
308
|
+
// The full config is validated as ONE document now (header/footer are
|
|
309
|
+
// both required) — there is no more partial-field merge across scopes; a
|
|
310
|
+
// tenant's own chrome.json either validates whole, or the default scope's
|
|
311
|
+
// does, or the storefront falls back to an empty-but-valid config.
|
|
312
|
+
const own = await this.loadChromeConfig();
|
|
313
|
+
if (own) return own;
|
|
314
|
+
if (this.scope === DEFAULT_SCOPE) return EMPTY_CHROME_CONFIG;
|
|
229
315
|
const defL = pickContent(chromeGlobs, DEFAULT_SCOPE);
|
|
230
|
-
const def
|
|
231
|
-
|
|
232
|
-
: { nav: [], footer: { columns: [], social: [] } };
|
|
233
|
-
const ownL = pickContent(chromeGlobs, this.scope);
|
|
234
|
-
if (!ownL) return def;
|
|
235
|
-
const c = await ownL();
|
|
236
|
-
if (this.scope === DEFAULT_SCOPE) return c;
|
|
237
|
-
// Per-field fallback fills only STRUCTURAL scaffolding (nav/footer) so a
|
|
238
|
-
// partially-adopted tenant still renders. Promotional/identity/legal fields
|
|
239
|
-
// (announcement, headerCtas, disclaimers) are tenant-OWNED and never inherit
|
|
240
|
-
// from the default scope — otherwise e.g. a marketing tenant would show the
|
|
241
|
-
// default store's shipping banner or nicotine disclaimers.
|
|
242
|
-
return {
|
|
243
|
-
nav: c.nav?.length ? c.nav : def.nav,
|
|
244
|
-
footer:
|
|
245
|
-
c.footer && (c.footer.columns?.length || c.footer.social?.length)
|
|
246
|
-
? c.footer
|
|
247
|
-
: def.footer,
|
|
248
|
-
headerCtas: c.headerCtas,
|
|
249
|
-
announcement: c.announcement,
|
|
250
|
-
disclaimer: c.disclaimer,
|
|
251
|
-
disclaimers: c.disclaimers,
|
|
252
|
-
};
|
|
316
|
+
const def = defL ? parseChromeConfig(await defL()) : null;
|
|
317
|
+
return def ?? EMPTY_CHROME_CONFIG;
|
|
253
318
|
}
|
|
254
319
|
|
|
255
320
|
async getPage(slug: string): Promise<EditorialPage | null> {
|
|
@@ -264,6 +329,24 @@ export class LocalContentProvider implements ContentProvider {
|
|
|
264
329
|
const loaders = pickPages(this.scope);
|
|
265
330
|
return Promise.all(loaders.map((l) => l()));
|
|
266
331
|
}
|
|
332
|
+
|
|
333
|
+
async readArtifact(relative: string): Promise<string | null> {
|
|
334
|
+
const normalized = relative.replace(/^\/+/, "");
|
|
335
|
+
const marker = `${tenantDirSegment(this.scope)}content/${normalized}`;
|
|
336
|
+
const key = Object.keys(artifactGlobs).find((candidate) => candidate.endsWith(marker));
|
|
337
|
+
return key ? artifactGlobs[key]!() : null;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
async listArtifactPaths(prefix = ""): Promise<string[]> {
|
|
341
|
+
const marker = `${tenantDirSegment(this.scope)}content/`;
|
|
342
|
+
return Object.keys(artifactGlobs)
|
|
343
|
+
.flatMap((key) => {
|
|
344
|
+
const at = key.indexOf(marker);
|
|
345
|
+
return at < 0 ? [] : [key.slice(at + marker.length)];
|
|
346
|
+
})
|
|
347
|
+
.filter((path) => path.startsWith(prefix))
|
|
348
|
+
.sort((a, b) => a.localeCompare(b));
|
|
349
|
+
}
|
|
267
350
|
}
|
|
268
351
|
|
|
269
352
|
// ---------------------------------------------------------------------------
|
|
@@ -405,26 +488,24 @@ export class StoryblokContentProvider implements ContentProvider {
|
|
|
405
488
|
return this.fallback.listPageHtmlSlugs();
|
|
406
489
|
}
|
|
407
490
|
|
|
408
|
-
getChromeHtml(): Promise<string | null> {
|
|
491
|
+
getChromeHtml(currentPath?: string): Promise<string | null> {
|
|
409
492
|
// The chrome wrapper lives in tenant content files, not Storyblok — delegate.
|
|
410
|
-
return this.fallback.getChromeHtml();
|
|
493
|
+
return this.fallback.getChromeHtml(currentPath);
|
|
411
494
|
}
|
|
412
495
|
|
|
413
|
-
async getChrome(): Promise<
|
|
414
|
-
// Editorial chrome (nav/footer
|
|
415
|
-
//
|
|
416
|
-
//
|
|
496
|
+
async getChrome(): Promise<ChromeConfig> {
|
|
497
|
+
// Editorial chrome (nav/footer) is authored in Storyblok; brand/variant
|
|
498
|
+
// and everything else stay MIGRATION-sourced (the local chrome.json), so
|
|
499
|
+
// overlay only nav + footer from Storyblok when present.
|
|
417
500
|
const local = await this.fallback.getChrome();
|
|
418
501
|
try {
|
|
419
502
|
const story = await fetchStory(this.token, "chrome");
|
|
420
503
|
const c = story?.content;
|
|
421
504
|
if (c && Array.isArray(c.nav)) {
|
|
422
505
|
return {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
disclaimer: local.disclaimer,
|
|
427
|
-
disclaimers: local.disclaimers,
|
|
506
|
+
...local,
|
|
507
|
+
header: { ...local.header, nav: c.nav },
|
|
508
|
+
footer: c.footer ? { ...local.footer, ...c.footer } : local.footer,
|
|
428
509
|
};
|
|
429
510
|
}
|
|
430
511
|
} catch {
|
|
@@ -461,6 +542,14 @@ export class StoryblokContentProvider implements ContentProvider {
|
|
|
461
542
|
}
|
|
462
543
|
return this.fallback.listPages();
|
|
463
544
|
}
|
|
545
|
+
|
|
546
|
+
readArtifact(relative: string): Promise<string | null> {
|
|
547
|
+
return this.fallback.readArtifact(relative);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
listArtifactPaths(prefix?: string): Promise<string[] | null> {
|
|
551
|
+
return this.fallback.listArtifactPaths(prefix);
|
|
552
|
+
}
|
|
464
553
|
}
|
|
465
554
|
|
|
466
555
|
// ---------------------------------------------------------------------------
|
|
@@ -468,7 +557,8 @@ export class StoryblokContentProvider implements ContentProvider {
|
|
|
468
557
|
// resolved environment (store->app wiring). For each content artifact it checks
|
|
469
558
|
// the customization store ({live,test} pointer -> versioned JSON); if present it
|
|
470
559
|
// serves that, otherwise it delegates to the base provider (Storyblok/local).
|
|
471
|
-
//
|
|
560
|
+
// Enumeration also follows the selected immutable manifest, so sitemap/blog/page
|
|
561
|
+
// indexes do not need a build-time tenant checkout.
|
|
472
562
|
// ---------------------------------------------------------------------------
|
|
473
563
|
|
|
474
564
|
export interface ContentCustomization {
|
|
@@ -517,34 +607,72 @@ export class PublishedContentProvider implements ContentProvider {
|
|
|
517
607
|
return (await this.publishedRaw(rel)) ?? this.base.getPageHtml(slug);
|
|
518
608
|
}
|
|
519
609
|
listPageHtmlSlugs(): Promise<string[]> {
|
|
520
|
-
return this.
|
|
610
|
+
return this.listArtifactPaths("pages-html/").then((paths) =>
|
|
611
|
+
paths === null
|
|
612
|
+
? this.base.listPageHtmlSlugs()
|
|
613
|
+
: paths
|
|
614
|
+
.filter((path) => path.endsWith(".html"))
|
|
615
|
+
.map((path) => path.slice("pages-html/".length, -".html".length)),
|
|
616
|
+
);
|
|
521
617
|
}
|
|
522
|
-
async getChromeHtml(): Promise<string | null> {
|
|
618
|
+
async getChromeHtml(currentPath?: string): Promise<string | null> {
|
|
523
619
|
// A published raw chrome.html override (parallels the home.html/pages-html
|
|
524
620
|
// publish path) wins, so a vendor edit previews / goes live with no rebuild.
|
|
525
621
|
const publishedHtml = await this.publishedRaw("chrome.html");
|
|
526
622
|
if (publishedHtml) return publishedHtml;
|
|
527
|
-
// Else, if the PUBLISHED chrome.json opts into shared chrome
|
|
623
|
+
// Else, if the PUBLISHED chrome.json opts into shared chrome, render
|
|
528
624
|
// the canonical wrapper from it — so a config edit previews without a rebuild.
|
|
529
625
|
const config = parseChromeConfig(await this.published<unknown>("chrome.json"));
|
|
530
|
-
if (config)
|
|
626
|
+
if (config) {
|
|
627
|
+
// Per-page variant override, resolved against the PUBLISHED assignment
|
|
628
|
+
// manifest (mirrors chrome.json itself — each source resolves fully
|
|
629
|
+
// within itself, no mixing published/base artifacts).
|
|
630
|
+
const assignments = parseChromeAssignments(await this.published<unknown>("chrome-assignments.json"));
|
|
631
|
+
return renderRawChrome(config, currentPath, assignments);
|
|
632
|
+
}
|
|
531
633
|
// Otherwise fall back to the base chain (bundled chrome.html or config render).
|
|
532
|
-
return this.base.getChromeHtml();
|
|
634
|
+
return this.base.getChromeHtml(currentPath);
|
|
533
635
|
}
|
|
534
|
-
async getChrome(): Promise<
|
|
535
|
-
|
|
636
|
+
async getChrome(): Promise<ChromeConfig> {
|
|
637
|
+
// Runs through the SAME fail-closed schema validator as getChromeHtml's
|
|
638
|
+
// chrome.json branch (lib/chrome/parseConfig.ts) — a published artifact is
|
|
639
|
+
// no longer trusted blind (was: JSON.parse + a raw cast to SiteChrome).
|
|
640
|
+
const config = parseChromeConfig(await this.published<unknown>("chrome.json"));
|
|
641
|
+
return config ?? this.base.getChrome();
|
|
536
642
|
}
|
|
537
643
|
async getPage(slug: string): Promise<EditorialPage | null> {
|
|
538
644
|
return (await this.published<EditorialPage>(`pages/${slug}.json`)) ?? this.base.getPage(slug);
|
|
539
645
|
}
|
|
540
|
-
listPages(): Promise<EditorialPage[]> {
|
|
541
|
-
|
|
646
|
+
async listPages(): Promise<EditorialPage[]> {
|
|
647
|
+
const paths = await this.listArtifactPaths("pages/");
|
|
648
|
+
if (paths === null) return this.base.listPages();
|
|
649
|
+
const pages = await Promise.all(
|
|
650
|
+
paths
|
|
651
|
+
.filter((path) => path.endsWith(".json"))
|
|
652
|
+
.map(async (path) => this.published<EditorialPage>(path)),
|
|
653
|
+
);
|
|
654
|
+
return pages.filter((page): page is EditorialPage => page !== null);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
readArtifact(relative: string): Promise<string | null> {
|
|
658
|
+
return this.publishedRaw(relative);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
async listArtifactPaths(prefix = ""): Promise<string[] | null> {
|
|
662
|
+
const root = contentArtifactPath(this.tenantId, "");
|
|
663
|
+
const paths = await listPublishedArtifactPaths(
|
|
664
|
+
this.cust.get,
|
|
665
|
+
this.tenantId,
|
|
666
|
+
this.cust.env,
|
|
667
|
+
`${root}${prefix}`,
|
|
668
|
+
);
|
|
669
|
+
return paths?.map((path) => path.slice(root.length)) ?? null;
|
|
542
670
|
}
|
|
543
671
|
}
|
|
544
672
|
|
|
545
673
|
// ---------------------------------------------------------------------------
|
|
546
|
-
// DevDraftContentProvider — the per-developer DRAFT overlay
|
|
547
|
-
//
|
|
674
|
+
// DevDraftContentProvider — the per-developer DRAFT overlay. Wraps the
|
|
675
|
+
// resolved provider chain and serves a
|
|
548
676
|
// draft artifact (written by /api/content-edit/ai-edit into TENANT_CACHE under
|
|
549
677
|
// `dev-draft:<tenantId>:<draftId>:<path>`) when one exists, else delegates.
|
|
550
678
|
// The middleware constructs this ONLY for a request whose signed tot_dev_draft
|
|
@@ -581,10 +709,10 @@ export class DevDraftContentProvider implements ContentProvider {
|
|
|
581
709
|
listPageHtmlSlugs(): Promise<string[]> {
|
|
582
710
|
return this.base.listPageHtmlSlugs();
|
|
583
711
|
}
|
|
584
|
-
getChromeHtml(): Promise<string | null> {
|
|
585
|
-
return this.base.getChromeHtml();
|
|
712
|
+
getChromeHtml(currentPath?: string): Promise<string | null> {
|
|
713
|
+
return this.base.getChromeHtml(currentPath);
|
|
586
714
|
}
|
|
587
|
-
getChrome(): Promise<
|
|
715
|
+
getChrome(): Promise<ChromeConfig> {
|
|
588
716
|
return this.base.getChrome();
|
|
589
717
|
}
|
|
590
718
|
getPage(slug: string): Promise<EditorialPage | null> {
|
|
@@ -593,6 +721,12 @@ export class DevDraftContentProvider implements ContentProvider {
|
|
|
593
721
|
listPages(): Promise<EditorialPage[]> {
|
|
594
722
|
return this.base.listPages();
|
|
595
723
|
}
|
|
724
|
+
readArtifact(relative: string): Promise<string | null> {
|
|
725
|
+
return this.base.readArtifact(relative);
|
|
726
|
+
}
|
|
727
|
+
listArtifactPaths(prefix?: string): Promise<string[] | null> {
|
|
728
|
+
return this.base.listArtifactPaths(prefix);
|
|
729
|
+
}
|
|
596
730
|
}
|
|
597
731
|
|
|
598
732
|
const PLACEHOLDER_TOKEN_RE = /PLACEHOLDER|^$/i;
|