@tokenoftrust/storefront-runner 1.4.1 → 2.0.0
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 +17 -1
- package/apps/storefront/env.d.ts +54 -5
- package/apps/storefront/integrations/materialize-guard.mjs +54 -0
- package/apps/storefront/migrations-apps/0002_good_switch.sql +26 -0
- package/apps/storefront/migrations-apps/0003_mute_marauders.sql +12 -0
- 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/0002_snapshot.json +1160 -0
- package/apps/storefront/migrations-apps/meta/0003_snapshot.json +1235 -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 +56 -0
- package/apps/storefront/public/apps/tot.demo.analytics/0.1.0/entry.js +42 -0
- package/apps/storefront/public/js/dashboard-team.js +38 -2
- 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/Seo.astro +65 -1
- package/apps/storefront/src/components/admin/AdminBlogDetailRail.astro +878 -0
- package/apps/storefront/src/components/admin/AdminBlogTab.astro +1435 -0
- package/apps/storefront/src/components/admin/AdminCustomersTab.astro +3 -7
- package/apps/storefront/src/components/admin/AdminIntegrationsTab.astro +105 -0
- package/apps/storefront/src/components/admin/AdminPublishTab.astro +1980 -2564
- package/apps/storefront/src/components/admin/AdminSettingsTab.astro +2 -2
- package/apps/storefront/src/components/admin/AdminSubscriptionsTab.astro +1 -3
- package/apps/storefront/src/components/admin/orders/OrdersWorkspace.astro +5 -5
- package/apps/storefront/src/components/blog/BlogPaywall.astro +87 -0
- package/apps/storefront/src/components/blog/PostAccessBadge.astro +43 -0
- package/apps/storefront/src/components/blog/PostGrid.astro +57 -0
- package/apps/storefront/src/components/blog/ReactionBar.astro +214 -0
- 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 +4 -4
- package/apps/storefront/src/layouts/Layout.astro +146 -21
- package/apps/storefront/src/lib/activity/alerts.ts +17 -19
- package/apps/storefront/src/lib/activity/candidateSubmitLease.ts +87 -0
- package/apps/storefront/src/lib/activity/changeActorAttribution.ts +89 -9
- package/apps/storefront/src/lib/activity/deployVersion.ts +20 -27
- package/apps/storefront/src/lib/activity/ingest.ts +8 -8
- package/apps/storefront/src/lib/activity/ingestAuth.ts +1 -1
- package/apps/storefront/src/lib/activity/killSwitch.ts +6 -6
- package/apps/storefront/src/lib/activity/query.ts +9 -9
- package/apps/storefront/src/lib/activity/recordActivity.ts +11 -15
- package/apps/storefront/src/lib/activity/store.ts +4 -6
- package/apps/storefront/src/lib/activity/uiActor.ts +12 -12
- package/apps/storefront/src/lib/activity/workerCommit.ts +6 -6
- package/apps/storefront/src/lib/admin/adminShell.ts +292 -0
- package/apps/storefront/src/lib/admin/ai/approvalMode.ts +2 -3
- package/apps/storefront/src/lib/admin/ai/audit.ts +3 -5
- package/apps/storefront/src/lib/admin/checkoutStyleInput.ts +7 -7
- package/apps/storefront/src/lib/admin/checkoutStyleTarget.ts +3 -3
- package/apps/storefront/src/lib/admin/envelope.ts +1 -2
- package/apps/storefront/src/lib/admin/orderClient.ts +3 -5
- package/apps/storefront/src/lib/admin/services/orders.ts +17 -19
- package/apps/storefront/src/lib/admin/viewmodels/aiWorkflows.ts +4 -6
- package/apps/storefront/src/lib/admin/viewmodels/settings.ts +1 -1
- package/apps/storefront/src/lib/apps/adminService.ts +14 -4
- package/apps/storefront/src/lib/apps/adminSession.ts +54 -45
- package/apps/storefront/src/lib/apps/apiAuth.ts +2 -2
- package/apps/storefront/src/lib/apps/apiRoute.ts +2 -2
- package/apps/storefront/src/lib/apps/appVersionStore.ts +281 -0
- package/apps/storefront/src/lib/apps/catalogMapper.ts +1 -1
- package/apps/storefront/src/lib/apps/consentConfigStore.ts +228 -0
- package/apps/storefront/src/lib/apps/credentials.ts +2 -2
- package/apps/storefront/src/lib/apps/gatewayKeys.ts +4 -4
- 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/healthAggregate.ts +5 -5
- 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/attributionService.ts +2 -2
- package/apps/storefront/src/lib/apps/orders/customerHash.ts +1 -1
- package/apps/storefront/src/lib/apps/orders/foxyOrderClient.ts +12 -12
- package/apps/storefront/src/lib/apps/orders/idempotency.ts +7 -7
- package/apps/storefront/src/lib/apps/orders/orderConversionD1.ts +150 -0
- package/apps/storefront/src/lib/apps/orders/orderForwardReceiver.ts +9 -1
- package/apps/storefront/src/lib/apps/orders/orderMapper.ts +2 -2
- package/apps/storefront/src/lib/apps/orders/ordersStore.ts +7 -7
- package/apps/storefront/src/lib/apps/orders/verifiedProviderMeasurement.ts +117 -0
- package/apps/storefront/src/lib/apps/registryService.ts +270 -16
- package/apps/storefront/src/lib/apps/scopes.ts +2 -2
- package/apps/storefront/src/lib/apps/tenantIntegrationMaterialization.ts +170 -0
- package/apps/storefront/src/lib/apps/tokenIssuer.ts +4 -4
- package/apps/storefront/src/lib/apps/tokenVerifier.ts +6 -6
- 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/widgets/eligibility.ts +2 -2
- package/apps/storefront/src/lib/apps/widgets/frameProps.ts +3 -3
- package/apps/storefront/src/lib/apps/widgets/launchToken.ts +4 -4
- package/apps/storefront/src/lib/apps/widgets/placements.ts +2 -2
- package/apps/storefront/src/lib/apps/widgets/renderSlot.ts +1 -1
- 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 +11 -11
- package/apps/storefront/src/lib/auth/adminLanding.ts +88 -0
- package/apps/storefront/src/lib/auth/identityToken.ts +6 -14
- package/apps/storefront/src/lib/auth/loginGate.ts +75 -13
- package/apps/storefront/src/lib/auth/mcpClientAssertion.ts +2 -4
- package/apps/storefront/src/lib/auth/operatorSession.ts +113 -0
- package/apps/storefront/src/lib/auth/session.ts +7 -8
- package/apps/storefront/src/lib/auth/totAccessClient.ts +8 -18
- package/apps/storefront/src/lib/basePath.ts +9 -2
- package/apps/storefront/src/lib/blog/access.ts +29 -0
- package/apps/storefront/src/lib/blog/collection.ts +400 -0
- package/apps/storefront/src/lib/blog/markdown.ts +174 -0
- package/apps/storefront/src/lib/blog/provider.ts +62 -3
- package/apps/storefront/src/lib/blog/reactions.ts +87 -0
- package/apps/storefront/src/lib/blog/rss.ts +92 -0
- package/apps/storefront/src/lib/blog/teaser.ts +83 -0
- package/apps/storefront/src/lib/blog/types.ts +34 -0
- package/apps/storefront/src/lib/blog/viewCounts.ts +243 -0
- 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 +14 -2
- package/apps/storefront/src/lib/d1/schema-apps.ts +372 -17
- package/apps/storefront/src/lib/dev/activityIngestToken.ts +2 -4
- package/apps/storefront/src/lib/dev/activityStore.ts +3 -5
- package/apps/storefront/src/lib/dev/apiBase.ts +1 -4
- 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/cockpitStore.ts +1 -9
- package/apps/storefront/src/lib/dev/devDraft.ts +1 -1
- package/apps/storefront/src/lib/dev/previewStatus.ts +9 -21
- package/apps/storefront/src/lib/dev/rendezvousBroker.ts +9 -24
- package/apps/storefront/src/lib/dev/runtimeStore.ts +1 -1
- package/apps/storefront/src/lib/edgeCache.ts +34 -5
- package/apps/storefront/src/lib/email/candidateConflictEmail.ts +80 -0
- package/apps/storefront/src/lib/http/fetchTransport.ts +63 -0
- package/apps/storefront/src/lib/i18n.ts +25 -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/jsonld.ts +118 -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/monitoring/manifest.ts +11 -11
- package/apps/storefront/src/lib/newsletter/confirmationEmail.ts +30 -0
- package/apps/storefront/src/lib/newsletter/consent.ts +23 -0
- package/apps/storefront/src/lib/newsletter/digest.ts +117 -0
- package/apps/storefront/src/lib/newsletter/resendClient.ts +79 -0
- package/apps/storefront/src/lib/newsletter/send.ts +265 -0
- package/apps/storefront/src/lib/newsletter/subscribers.ts +156 -0
- package/apps/storefront/src/lib/previewReload.ts +57 -1
- package/apps/storefront/src/lib/publish/adminPublishTab.ts +4625 -0
- package/apps/storefront/src/lib/publish/apex-readiness.ts +41 -18
- 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/dispatchHealth.ts +5 -7
- package/apps/storefront/src/lib/publish/domainState.ts +22 -13
- 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/panelPolling.ts +78 -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 +121 -51
- package/apps/storefront/src/lib/publish/sseRoute.ts +119 -0
- package/apps/storefront/src/lib/publish/stageBundleUpload.ts +215 -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/rate/kvWindow.ts +53 -0
- package/apps/storefront/src/lib/rawChrome.ts +125 -47
- package/apps/storefront/src/lib/rawMarketingHtml.ts +74 -3
- package/apps/storefront/src/lib/search/index.ts +2 -2
- package/apps/storefront/src/lib/seo/alternates.ts +42 -0
- package/apps/storefront/src/lib/seo/meta.ts +65 -0
- package/apps/storefront/src/lib/social/golive.ts +212 -0
- package/apps/storefront/src/lib/social/notify.ts +32 -0
- package/apps/storefront/src/lib/social/tokenStore.ts +171 -0
- package/apps/storefront/src/lib/storyblok/content-model.ts +7 -33
- package/apps/storefront/src/lib/storyblok/provider.ts +168 -60
- package/apps/storefront/src/lib/subscription/model.ts +4 -4
- package/apps/storefront/src/lib/the-build/provider.ts +32 -0
- package/apps/storefront/src/lib/the-build/readingTime.ts +35 -0
- package/apps/storefront/src/lib/the-build/toc.ts +95 -0
- package/apps/storefront/src/lib/tot/ToTClient.ts +1 -1
- package/apps/storefront/src/lib/tot/d1Client.ts +2 -2
- package/apps/storefront/src/lib/tot/totClientInterface.ts +3 -4
- package/apps/storefront/src/lib/webhooks/deliveryStore.ts +16 -17
- package/apps/storefront/src/middleware/index.ts +385 -52
- 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 +25 -1313
- 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/blog/[collection]/[slug]/react.ts +104 -0
- 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/newsletter/[collection]/subscribe.ts +146 -0
- package/apps/storefront/src/pages/api/newsletter/confirm.ts +75 -0
- package/apps/storefront/src/pages/api/newsletter/unsubscribe.ts +90 -0
- package/apps/storefront/src/pages/api/publish/stage-bundle.ts +70 -0
- package/apps/storefront/src/pages/api/rum/vitals.ts +20 -2
- package/apps/storefront/src/pages/blog/[slug].astro +99 -19
- package/apps/storefront/src/pages/blog/author/[author].astro +140 -0
- package/apps/storefront/src/pages/blog/category/[category].astro +67 -0
- package/apps/storefront/src/pages/blog/index.astro +4 -2
- package/apps/storefront/src/pages/blog/tag/[tag].astro +65 -0
- package/apps/storefront/src/pages/cockpit.astro +77 -1353
- package/apps/storefront/src/pages/dashboard/[appDomain]/team.astro +15 -11
- 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/search.astro +1 -1
- package/apps/storefront/src/pages/sitemap.xml.ts +21 -1
- package/apps/storefront/src/pages/style-guide/[tenant]/[theme].astro +31 -27
- package/apps/storefront/src/pages/style-guide/[tenant]/chrome/[theme].astro +1 -1
- package/apps/storefront/src/pages/tenants/[id]/[...path].ts +38 -2
- package/apps/storefront/src/pages/the-build/[slug].astro +129 -0
- package/apps/storefront/src/pages/the-build/index.astro +146 -0
- package/apps/storefront/src/pages/the-build/rss.xml.ts +33 -0
- package/apps/storefront/src/styles/admin.css +1106 -0
- package/apps/storefront/tsconfig.json +6 -1
- package/package.json +1 -1
- package/packages/public-runtime/package.json +3 -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 +89 -33
- 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/catalog-d1.ts +1 -1
- 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-preview.ts +25 -2
- package/packages/public-runtime/src/customization-runtime.ts +156 -3
- package/packages/public-runtime/src/customization-versioning.ts +10 -0
- 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 +17 -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 +357 -0
- package/packages/public-runtime/src/tenant-assets.ts +143 -4
- package/packages/public-runtime/src/tenant.ts +81 -17
- 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 +14 -4
- package/scripts/dev/ai-edit.mjs +1 -1
- package/scripts/dev/checkout-watch.mjs +2 -2
- package/scripts/dev/file-browser.mjs +4 -6
- package/scripts/dev/file-writer.mjs +3 -3
- package/scripts/dev/git-status.mjs +2 -2
- package/scripts/dev/integration-guardrails.mjs +498 -0
- package/scripts/dev/locate-handler.mjs +3 -3
- package/scripts/dev/port-check.mjs +3 -3
- package/scripts/dev/publish.mjs +5 -9
- package/scripts/dev/safe-path.mjs +2 -2
- package/scripts/dev/shot.mjs +1 -1
- package/scripts/dev/tenant-source-root.mjs +1 -1
- package/scripts/dev/unified-diff.mjs +8 -3
- package/scripts/tot-dev.mjs +8 -11
- package/apps/storefront/src/lib/dev/vcBinding.ts +0 -80
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure formatting for the admin Publish tab's shared SCROLLING LIVE-LOG console —
|
|
3
|
+
* the "CI build log" panel reused across ALL FOUR long-running flows (build /
|
|
4
|
+
* integrate / rebuild / ship). Where `buildStream.ts` owns the build-flight
|
|
5
|
+
* stage-RAIL (checkmarks), this owns the LOG: one human-readable, timestamped line
|
|
6
|
+
* appended per server-pushed `phase` event, then a terminal ✓/✗ line.
|
|
7
|
+
*
|
|
8
|
+
* It is transport-agnostic (it consumes already-parsed {@link SseEvent}s from
|
|
9
|
+
* `parseSseChunk`) and flow-agnostic: ONE phase→label map covers every phase any of
|
|
10
|
+
* the four routes emits, so the SAME console renders an integrate merge, a ship
|
|
11
|
+
* promote, or a build read with no per-flow branching. The terminal-line extractor
|
|
12
|
+
* reads the union of the four routes' honest result shapes (build's
|
|
13
|
+
* `ok`/`outcome`/`errors`, the queue's `ok`/`queueState`/`statusMessage`/`reason`,
|
|
14
|
+
* ship's `ok`/`state`/`message`) without any one flow's assumptions leaking in.
|
|
15
|
+
*
|
|
16
|
+
* The DOM wiring that appends these to a scrollable `<div>` and auto-scrolls is
|
|
17
|
+
* verified on a live preview, per this codebase's client-island convention (see
|
|
18
|
+
* `reviewReleaseWorkspace/controller.ts`). Only this pure formatting is unit-tested.
|
|
19
|
+
*/
|
|
20
|
+
import type { SseEvent } from "./buildStream.js";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The human label for every `phase` a streamed route emits, across all four flows.
|
|
24
|
+
* Deliberately honest-but-neutral wording so the SAME line reads true whether the
|
|
25
|
+
* phase came from a candidate build or an aggregate rebuild.
|
|
26
|
+
*/
|
|
27
|
+
export const STREAM_PHASE_LABEL: Record<string, string> = {
|
|
28
|
+
// Accept-to-preview (integrate) — the queue's merge boundary.
|
|
29
|
+
merging: "Merging your change into the shared preview…",
|
|
30
|
+
// Build (candidate) — head resolution.
|
|
31
|
+
resolving: "Locating the pull request's latest commit…",
|
|
32
|
+
// Shared reconcile boundaries (build candidate + aggregate integrate/rebuild).
|
|
33
|
+
reading: "Reading and validating your store's files…",
|
|
34
|
+
publishing: "Building and publishing this version…",
|
|
35
|
+
checking: "Re-running the combined evidence checks…",
|
|
36
|
+
// Publish-live (ship) — the orchestrator's post-gate boundaries.
|
|
37
|
+
pinning: "Pinning the exact reviewed build…",
|
|
38
|
+
promoting: "Promoting the reviewed build to your live channel…",
|
|
39
|
+
verifying: "Verifying your live store serves the new build…",
|
|
40
|
+
recording: "Recording the release in your ledger…",
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The label for one phase: a server-supplied `label` wins (a route may send a more
|
|
45
|
+
* specific line, e.g. build's "Locating the latest commit for PR #12…"), else the
|
|
46
|
+
* shared map, else `null` for an unknown phase (the caller ignores it — never
|
|
47
|
+
* fabricates a line for a phase it doesn't understand).
|
|
48
|
+
*/
|
|
49
|
+
export function phaseLogLabel(phase: string, serverLabel?: string | null): string | null {
|
|
50
|
+
if (serverLabel && serverLabel.trim()) return serverLabel.trim();
|
|
51
|
+
return STREAM_PHASE_LABEL[phase] ?? null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** `m:ss` elapsed, matching the dialog's own elapsed-clock format. Clamps negatives to 0. */
|
|
55
|
+
export function formatElapsed(ms: number): string {
|
|
56
|
+
const s = Math.max(0, Math.floor(ms / 1000));
|
|
57
|
+
return `${Math.floor(s / 60)}:${String(s % 60).padStart(2, "0")}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The log line for a `phase` event, or `null` to skip (unknown phase / not a phase
|
|
62
|
+
* event). Shape: `[m:ss] <label>`. Pure — the console appends the returned string.
|
|
63
|
+
*/
|
|
64
|
+
export function logLineForPhase(
|
|
65
|
+
ev: SseEvent,
|
|
66
|
+
elapsedMs: number,
|
|
67
|
+
): string | null {
|
|
68
|
+
if (ev.event !== "phase") return null;
|
|
69
|
+
let parsed: unknown = null;
|
|
70
|
+
try {
|
|
71
|
+
parsed = JSON.parse(ev.data);
|
|
72
|
+
} catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
const phase = parsed && typeof (parsed as { phase?: unknown }).phase === "string"
|
|
76
|
+
? (parsed as { phase: string }).phase
|
|
77
|
+
: null;
|
|
78
|
+
if (!phase) return null;
|
|
79
|
+
const serverLabel =
|
|
80
|
+
parsed && typeof (parsed as { label?: unknown }).label === "string"
|
|
81
|
+
? (parsed as { label: string }).label
|
|
82
|
+
: null;
|
|
83
|
+
const label = phaseLogLabel(phase, serverLabel);
|
|
84
|
+
if (!label) return null;
|
|
85
|
+
return `[${formatElapsed(elapsedMs)}] ${label}`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** A terminal result across the four flows — the union of their honest shapes. */
|
|
89
|
+
export interface StreamTerminalResult {
|
|
90
|
+
ok?: boolean;
|
|
91
|
+
/** build */
|
|
92
|
+
outcome?: string | null;
|
|
93
|
+
errors?: string[] | null;
|
|
94
|
+
error?: string | null;
|
|
95
|
+
/** integrate/rebuild */
|
|
96
|
+
queueState?: string | null;
|
|
97
|
+
statusMessage?: string | null;
|
|
98
|
+
reason?: string | null;
|
|
99
|
+
/** ship */
|
|
100
|
+
state?: string | null;
|
|
101
|
+
message?: string | null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** True iff the terminal result is a clean success, reading each flow's own success signal. */
|
|
105
|
+
export function isStreamResultOk(result: StreamTerminalResult): boolean {
|
|
106
|
+
if (result.state !== undefined && result.state !== null) return result.state === "shipped";
|
|
107
|
+
if (result.ok === true) return true;
|
|
108
|
+
return result.outcome === "ok";
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** A SHORT one-line reason for a non-ok terminal (the full detail lives in the outcome phase). */
|
|
112
|
+
function streamResultReason(result: StreamTerminalResult): string {
|
|
113
|
+
const first = (v: string[] | null | undefined) => (v && v.length ? v[0] : "");
|
|
114
|
+
const candidate =
|
|
115
|
+
result.statusMessage ||
|
|
116
|
+
result.message ||
|
|
117
|
+
first(result.errors) ||
|
|
118
|
+
result.error ||
|
|
119
|
+
(result.outcome === "blocked" ? "a check blocked this build" : "") ||
|
|
120
|
+
result.reason ||
|
|
121
|
+
result.queueState ||
|
|
122
|
+
result.state ||
|
|
123
|
+
"it did not complete";
|
|
124
|
+
const line = String(candidate).replace(/\s+/g, " ").trim();
|
|
125
|
+
return line.length > 160 ? line.slice(0, 157) + "…" : line;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* The terminal log line for a `result` event: `{ ok, line }`. `line` is
|
|
130
|
+
* `[m:ss] ✓ Done.` on success, `[m:ss] ✗ Stopped — <short reason>` otherwise. Pure.
|
|
131
|
+
*/
|
|
132
|
+
export function terminalLogLine(
|
|
133
|
+
result: StreamTerminalResult,
|
|
134
|
+
elapsedMs: number,
|
|
135
|
+
): { ok: boolean; line: string } {
|
|
136
|
+
const ok = isStreamResultOk(result);
|
|
137
|
+
const stamp = `[${formatElapsed(elapsedMs)}]`;
|
|
138
|
+
return {
|
|
139
|
+
ok,
|
|
140
|
+
line: ok ? `${stamp} ✓ Done.` : `${stamp} ✗ Stopped — ${streamResultReason(result)}`,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The merchant-facing version counter — a monotonic, human "v1, v2, …" label for
|
|
3
|
+
* the tenant's published versions, DERIVED from the live-promotion history so it is
|
|
4
|
+
* never fabricated and never the raw SHA (which is meaningless to a merchant).
|
|
5
|
+
*
|
|
6
|
+
* A version's number is its rank by FIRST promotion to `live`: the earliest-ever
|
|
7
|
+
* promoted sha is v1, the next distinct sha is v2, and so on. This has the two
|
|
8
|
+
* properties merchants expect:
|
|
9
|
+
* - monotonic increasing — a newer publish always gets a higher number; and
|
|
10
|
+
* - stable for a given sha — a rollback re-points `live` at a prior version (per
|
|
11
|
+
* `PromotionEvent`'s contract a plain rollback is not even recorded, and a
|
|
12
|
+
* re-promote keeps the sha's ORIGINAL first-promotion rank), so "rolled back to
|
|
13
|
+
* v3" reads honestly and a subsequent fresh publish becomes v(N+1), never
|
|
14
|
+
* reusing a retired number.
|
|
15
|
+
*
|
|
16
|
+
* Pure (no I/O) so it unit-tests without a Worker/KV, and shared by both the
|
|
17
|
+
* live-version resolver (the "Live version" metric) and the versions-panel view so
|
|
18
|
+
* the two surfaces always agree on a version's number.
|
|
19
|
+
*/
|
|
20
|
+
import type { PromotionEvent } from "@tot/public-runtime";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Map each promoted versionId → its 1-based version number, ranked by earliest
|
|
24
|
+
* promotion time. Input order is irrelevant (the rank is computed from the `at`
|
|
25
|
+
* timestamps), so it accepts the newest-first list `listPromotions` returns as-is.
|
|
26
|
+
* A tie on `at` between two distinct shas is broken by versionId for determinism.
|
|
27
|
+
*/
|
|
28
|
+
export function assignVersionNumbers(promotions: PromotionEvent[]): Map<string, number> {
|
|
29
|
+
const earliest = new Map<string, string>();
|
|
30
|
+
for (const p of promotions) {
|
|
31
|
+
const seen = earliest.get(p.versionId);
|
|
32
|
+
if (seen == null || p.at < seen) earliest.set(p.versionId, p.at);
|
|
33
|
+
}
|
|
34
|
+
const ordered = [...earliest.entries()].sort((a, b) =>
|
|
35
|
+
a[1] < b[1] ? -1 : a[1] > b[1] ? 1 : a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0,
|
|
36
|
+
);
|
|
37
|
+
const numbers = new Map<string, number>();
|
|
38
|
+
ordered.forEach(([versionId], i) => numbers.set(versionId, i + 1));
|
|
39
|
+
return numbers;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The version number for one versionId, or `null` when nothing in the promotion
|
|
44
|
+
* history matches (an unpromoted/unknown sha — the label falls back to the raw SHA).
|
|
45
|
+
*/
|
|
46
|
+
export function versionNumberFor(
|
|
47
|
+
promotions: PromotionEvent[],
|
|
48
|
+
versionId: string | null | undefined,
|
|
49
|
+
): number | null {
|
|
50
|
+
if (!versionId) return null;
|
|
51
|
+
return assignVersionNumbers(promotions).get(versionId) ?? null;
|
|
52
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fixed-window KV rate limiter — the storefront's first throttle, built small
|
|
3
|
+
* and generic so any public POST can share it (reactions today; blog comments
|
|
4
|
+
* and other reader writes next).
|
|
5
|
+
*
|
|
6
|
+
* A request is counted into the bucket for the current fixed window
|
|
7
|
+
* (`floor(now / windowSeconds)`), keyed `<key>:<bucket>`. The counter TTLs out a
|
|
8
|
+
* window after it is first written, so old buckets self-expire — no sweep.
|
|
9
|
+
*
|
|
10
|
+
* Best-effort by construction: a KV read/write hiccup FAILS OPEN (returns
|
|
11
|
+
* `allowed: true`) and never throws. A rate limiter must never turn a KV blip
|
|
12
|
+
* into a rejected legitimate request; the abuse it guards against is low-stakes
|
|
13
|
+
* (mirrors the "never throws" discipline of `blog/viewCounts.ts`).
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export interface RateLimitOptions {
|
|
17
|
+
/** Window length in seconds; the counter resets each window. */
|
|
18
|
+
windowSeconds: number;
|
|
19
|
+
/** Max requests permitted per key per window before rejecting. */
|
|
20
|
+
maxRequests: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** The current fixed-window bucket index for `now` (ms epoch). */
|
|
24
|
+
function windowBucket(windowSeconds: number, nowMs: number): number {
|
|
25
|
+
return Math.floor(nowMs / 1000 / windowSeconds);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Count one request against `key` and report whether it is within the cap for
|
|
30
|
+
* the current window. Increment is a read-modify-write, so under concurrency a
|
|
31
|
+
* few increments can be lost to KV's last-write-wins — acceptable for a soft
|
|
32
|
+
* abuse throttle, not a hard quota. Fails OPEN on any KV error.
|
|
33
|
+
*/
|
|
34
|
+
export async function checkRateLimit(
|
|
35
|
+
kv: KVNamespace | undefined,
|
|
36
|
+
key: string,
|
|
37
|
+
opts: RateLimitOptions,
|
|
38
|
+
nowMs: number = Date.now(),
|
|
39
|
+
): Promise<{ allowed: boolean }> {
|
|
40
|
+
if (!kv) return { allowed: true };
|
|
41
|
+
try {
|
|
42
|
+
const bucket = windowBucket(opts.windowSeconds, nowMs);
|
|
43
|
+
const bucketKey = `${key}:${bucket}`;
|
|
44
|
+
const raw = await kv.get(bucketKey);
|
|
45
|
+
const current = raw == null ? 0 : Number.parseInt(raw, 10);
|
|
46
|
+
const count = Number.isFinite(current) && current >= 0 ? current : 0;
|
|
47
|
+
if (count >= opts.maxRequests) return { allowed: false };
|
|
48
|
+
await kv.put(bucketKey, String(count + 1), { expirationTtl: opts.windowSeconds });
|
|
49
|
+
return { allowed: true };
|
|
50
|
+
} catch {
|
|
51
|
+
return { allowed: true };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Canonical RAW chrome renderer
|
|
2
|
+
* Canonical RAW chrome renderer.
|
|
3
3
|
*
|
|
4
4
|
* ONE platform-shipped chrome wrapper, driven entirely by a tenant's `chrome.json`
|
|
5
|
-
* (typed `
|
|
5
|
+
* (typed `ChromeConfig`, lib/chrome/model.ts) — CONTENT ONLY. It replaces the three
|
|
6
6
|
* divergent, hand-authored `content/<tenant>/chrome.html` documents with a single
|
|
7
7
|
* generator, so chrome lives in ONE place and a tenant supplies only data. The
|
|
8
8
|
* output is a full wrapper document carrying the splice markers the rawMarketingHtml
|
|
9
9
|
* pipeline understands (`<!--PAGE_HEAD-->` / `<!--PAGE_BODY-->`), so body-only page
|
|
10
10
|
* fragments get wrapped exactly as before (see lib/rawMarketingHtml.ts,
|
|
11
|
-
* wrapFragmentInChrome).
|
|
12
|
-
* -
|
|
13
|
-
* (/shared/commerce-chrome.css
|
|
11
|
+
* wrapFragmentInChrome). Two governing constraints:
|
|
12
|
+
* - Single stylesheet: the wrapper LINKS the platform stylesheet
|
|
13
|
+
* (/shared/commerce-chrome.css) and emits NO inline chrome CSS — every
|
|
14
14
|
* color/space/type comes from theme tokens the tenant's linked *-theme.css sets.
|
|
15
|
-
* -
|
|
15
|
+
* - Materialization-compatible: this is a deterministic server-side string; the spliced
|
|
16
16
|
* document is a stable, self-contained artifact SEOMaterialize can capture (the
|
|
17
17
|
* behavior JS is progressive enhancement over already-rendered markup).
|
|
18
18
|
*
|
|
19
|
-
* The markup mirrors, class-for-class, the semantic markup the
|
|
19
|
+
* The markup mirrors, class-for-class, the semantic markup the shared chrome stylesheet styles
|
|
20
20
|
* (`.site-header`, `.header-row`, `.primary-nav`, `.mega`, `.site-footer`, …) — the
|
|
21
21
|
* SAME DOM the retired tenant chrome.html files carried, now generated from config.
|
|
22
22
|
*
|
|
@@ -25,17 +25,23 @@
|
|
|
25
25
|
* defense-in-depth, not a sanitizer for untrusted input.
|
|
26
26
|
*/
|
|
27
27
|
import { CHROME_BODY_MARKER, CHROME_HEAD_MARKER } from "./rawMarketingHtml.js";
|
|
28
|
+
import { isCurrentNavHref } from "./chrome/navCurrent.js";
|
|
28
29
|
import type {
|
|
29
30
|
ChromeAgeGate,
|
|
30
31
|
ChromeAnnouncement,
|
|
32
|
+
ChromeAssignments,
|
|
31
33
|
ChromeBrand,
|
|
34
|
+
ChromeConfig,
|
|
32
35
|
ChromeFooter,
|
|
36
|
+
ChromeHeader,
|
|
33
37
|
ChromeLink,
|
|
34
38
|
ChromeNavItem,
|
|
35
39
|
ChromeUtilityNav,
|
|
40
|
+
HeaderVariant,
|
|
36
41
|
SocialLink,
|
|
37
|
-
TenantChrome,
|
|
38
42
|
} from "./chrome/model.js";
|
|
43
|
+
import { resolveChromeAssignment } from "./chrome/model.js";
|
|
44
|
+
import { resolveHeaderVariant } from "./chrome/variant.js";
|
|
39
45
|
|
|
40
46
|
/** HTML-escape text/attribute content. Covers the five significant characters. */
|
|
41
47
|
function esc(value: string): string {
|
|
@@ -177,8 +183,18 @@ function renderUtilityNav(u: ChromeUtilityNav): string {
|
|
|
177
183
|
);
|
|
178
184
|
}
|
|
179
185
|
|
|
180
|
-
/**
|
|
181
|
-
|
|
186
|
+
/**
|
|
187
|
+
* A primary-nav item — flat link, or a mega panel when it has columns/children.
|
|
188
|
+
*
|
|
189
|
+
* `currentPath`, when given, is the tenant-relative request path (e.g. "/" or
|
|
190
|
+
* "/collections/all") in the SAME unprefixed form the config's own `href`
|
|
191
|
+
* values are authored in — basePath prefixing happens later, as a single
|
|
192
|
+
* pass over the whole composed document (rawMarketingHtml.ts,
|
|
193
|
+
* prefixTenantLinks), so comparing here before that pass keeps both sides
|
|
194
|
+
* unprefixed and exact-match-only (see navCurrent.ts). Omitted ⇒ no item is
|
|
195
|
+
* ever marked current (callers that don't know the request path, e.g. tests).
|
|
196
|
+
*/
|
|
197
|
+
function renderNavItem(item: ChromeNavItem, currentPath?: string): string {
|
|
182
198
|
const columns = item.columns?.length
|
|
183
199
|
? item.columns
|
|
184
200
|
: item.children?.length
|
|
@@ -189,14 +205,23 @@ function renderNavItem(item: ChromeNavItem): string {
|
|
|
189
205
|
const badge = item.badge ? `<span class="new-badge">${esc(item.badge)}</span>` : "";
|
|
190
206
|
const caret = hasPanel ? `<span class="caret" aria-hidden="true">▾</span>` : "";
|
|
191
207
|
const haspopup = hasPanel ? ' aria-haspopup="true"' : "";
|
|
192
|
-
const
|
|
208
|
+
const itemCurrent = currentPath !== undefined && isCurrentNavHref(currentPath, item.href);
|
|
209
|
+
const ariaCurrent = itemCurrent ? ' aria-current="page"' : "";
|
|
210
|
+
const link = `<a${linkClass} href="${esc(item.href)}"${haspopup}${ariaCurrent}>${esc(item.label)}${badge}${caret}</a>`;
|
|
193
211
|
if (!hasPanel) return `<li class="nav-item">${link}</li>`;
|
|
194
212
|
|
|
195
213
|
const wide = columns.length > 1 ? " mega--wide" : "";
|
|
196
214
|
const cols = columns
|
|
197
215
|
.map((c) => {
|
|
198
216
|
const heading = c.title ? `<h4>${esc(c.title)}</h4>` : "";
|
|
199
|
-
const links = c.links
|
|
217
|
+
const links = c.links
|
|
218
|
+
.map((l) => {
|
|
219
|
+
const childCurrent =
|
|
220
|
+
currentPath !== undefined && isCurrentNavHref(currentPath, l.href, l.external);
|
|
221
|
+
const childAriaCurrent = childCurrent ? ' aria-current="page"' : "";
|
|
222
|
+
return `<a href="${esc(l.href)}"${relFor(l)}${childAriaCurrent}>${esc(l.label)}</a>`;
|
|
223
|
+
})
|
|
224
|
+
.join("");
|
|
200
225
|
return `<div class="mega__col">${heading}${links}</div>`;
|
|
201
226
|
})
|
|
202
227
|
.join("");
|
|
@@ -204,46 +229,73 @@ function renderNavItem(item: ChromeNavItem): string {
|
|
|
204
229
|
return `<li class="nav-item">${link}${mega}</li>`;
|
|
205
230
|
}
|
|
206
231
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
232
|
+
/**
|
|
233
|
+
* `variant` is the RESOLVED header variant for this page — the page-assignment
|
|
234
|
+
* override applied over `header.variant` by the caller. It is passed in rather
|
|
235
|
+
* than read off `header` so the rendered markup and its `data-variant` can
|
|
236
|
+
* never disagree with the variant the assignment actually decided.
|
|
237
|
+
*/
|
|
238
|
+
function renderHeader(header: ChromeHeader, variant: HeaderVariant, currentPath?: string): string {
|
|
239
|
+
const search = header.search
|
|
240
|
+
? `<form class="header-search" action="${esc(header.search.action ?? "/search")}" method="get" role="search">\n` +
|
|
241
|
+
` <label class="sr-only" for="hdr-q">${esc(header.search.label ?? "Search")}</label>\n` +
|
|
242
|
+
` <input id="hdr-q" type="search" name="q" placeholder="${esc(header.search.placeholder ?? "Search")}" autocomplete="off">\n` +
|
|
212
243
|
` <button type="submit" aria-label="Search">${ICON_SEARCH}</button>\n` +
|
|
213
244
|
` </form>`
|
|
214
245
|
: "";
|
|
215
|
-
const memberCue =
|
|
216
|
-
? `<a class="member-cue" href="${esc(
|
|
246
|
+
const memberCue = header.memberCue
|
|
247
|
+
? `<a class="member-cue" href="${esc(header.memberCue.href)}" aria-label="${esc(header.memberCue.label)}">${esc(header.memberCue.label)}</a>`
|
|
217
248
|
: "";
|
|
218
|
-
const account =
|
|
219
|
-
? `<a href="${esc(
|
|
249
|
+
const account = header.accountHref
|
|
250
|
+
? `<a href="${esc(header.accountHref)}" aria-label="Account">${ICON_ACCOUNT}</a>`
|
|
220
251
|
: "";
|
|
221
|
-
const cart =
|
|
222
|
-
? `<a class="cart" href="${esc(
|
|
252
|
+
const cart = header.cartHref
|
|
253
|
+
? `<a class="cart" href="${esc(header.cartHref)}" aria-label="Cart">${ICON_CART}<span class="count">0</span></a>`
|
|
223
254
|
: "";
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
const
|
|
255
|
+
// "minimal" (e.g. checkout/policy pages): brand + a reduced action set only —
|
|
256
|
+
// no primary nav bar, no search, no mobile-menu toggle. "primary": the full header.
|
|
257
|
+
const minimal = variant === "minimal";
|
|
258
|
+
// Header CTAs, on the same `!minimal` rule the Astro SiteHeader applies, so the
|
|
259
|
+
// two surfaces render the same governed config identically. Each carries its
|
|
260
|
+
// stable `id` as `data-cta-id`, so the governed identity is present in the
|
|
261
|
+
// served DOM rather than implied by label or href.
|
|
262
|
+
const ctas =
|
|
263
|
+
minimal || !header.ctas?.length
|
|
264
|
+
? ""
|
|
265
|
+
: header.ctas
|
|
266
|
+
.map(
|
|
267
|
+
(c) =>
|
|
268
|
+
`<a class="header-cta header-cta--${esc(c.variant ?? "primary")}" ` +
|
|
269
|
+
`data-cta-id="${esc(c.id)}" href="${esc(c.href)}">${esc(c.label)}</a>`,
|
|
270
|
+
)
|
|
271
|
+
.join("");
|
|
272
|
+
const hamburger = minimal
|
|
273
|
+
? ""
|
|
274
|
+
: `<button class="hamburger" id="hamburger" aria-label="Toggle navigation menu" aria-expanded="false" aria-controls="primaryNav"><span></span><span></span><span></span></button>`;
|
|
275
|
+
const navItems = header.nav.map((item) => renderNavItem(item, currentPath)).join("\n ");
|
|
276
|
+
const navBar = minimal
|
|
277
|
+
? ""
|
|
278
|
+
: ` <nav class="primary-nav-bar" aria-label="Primary">\n` +
|
|
279
|
+
` <div class="container">\n` +
|
|
280
|
+
` <nav class="primary-nav" id="primaryNav">\n` +
|
|
281
|
+
` <ul>\n ${navItems}\n </ul>\n` +
|
|
282
|
+
` </nav>\n` +
|
|
283
|
+
` </div>\n` +
|
|
284
|
+
` </nav>\n`;
|
|
227
285
|
return (
|
|
228
|
-
`<header class="site-header">\n` +
|
|
286
|
+
`<header class="site-header" data-variant="${esc(variant)}">\n` +
|
|
229
287
|
` <div class="container header-row">\n` +
|
|
230
|
-
` ${renderBrand(
|
|
231
|
-
(search ? ` ${search}\n` : "") +
|
|
232
|
-
` <div class="header-actions">${memberCue}${account}${cart}${hamburger}</div>\n` +
|
|
288
|
+
` ${renderBrand(header.brand)}\n` +
|
|
289
|
+
(!minimal && search ? ` ${search}\n` : "") +
|
|
290
|
+
` <div class="header-actions">${memberCue}${ctas}${account}${cart}${hamburger}</div>\n` +
|
|
233
291
|
` </div>\n` +
|
|
234
|
-
|
|
235
|
-
` <div class="container">\n` +
|
|
236
|
-
` <nav class="primary-nav" id="primaryNav">\n` +
|
|
237
|
-
` <ul>\n ${navItems}\n </ul>\n` +
|
|
238
|
-
` </nav>\n` +
|
|
239
|
-
` </div>\n` +
|
|
240
|
-
` </nav>\n` +
|
|
292
|
+
navBar +
|
|
241
293
|
`</header>`
|
|
242
294
|
);
|
|
243
295
|
}
|
|
244
296
|
|
|
245
297
|
/**
|
|
246
|
-
* Age-gate config, embedded as JSON
|
|
298
|
+
* Age-gate config, embedded as JSON. The CHROME_BEHAVIOR_JS mechanism
|
|
247
299
|
* reads this and builds the dialog itself inside a closed shadow root — the same
|
|
248
300
|
* tamper-resistance pattern components/islands/IsolatedAgeGate.tsx uses for
|
|
249
301
|
* component pages (see its header comment). `<` is escaped so an authored value
|
|
@@ -335,9 +387,16 @@ function renderFooter(footer: ChromeFooter): string {
|
|
|
335
387
|
* — a single canonical source, never copy-pasted per tenant. Each block is guarded on
|
|
336
388
|
* the presence of its elements, so a wrapper that omits the announce bar / age gate
|
|
337
389
|
* runs the remaining behavior harmlessly. Progressive enhancement: the markup already
|
|
338
|
-
* works without it (nav is
|
|
390
|
+
* works without it (nav is a normal in-flow stacked list at mobile widths — see the
|
|
391
|
+
* `.js-nav` gate in commerce-chrome.css's max-width:860px block — and the age gate
|
|
392
|
+
* defaults open only when JS enables it).
|
|
339
393
|
*/
|
|
340
394
|
export const CHROME_BEHAVIOR_JS = `(function () {
|
|
395
|
+
// Marks the document as JS-enabled so commerce-chrome.css can turn the
|
|
396
|
+
// always-reachable stacked mobile nav into an off-canvas drawer (progressive
|
|
397
|
+
// enhancement, not a requirement — see the .js-nav rules there).
|
|
398
|
+
document.documentElement.classList.add('js-nav');
|
|
399
|
+
|
|
341
400
|
// Mobile nav drawer
|
|
342
401
|
var burger = document.getElementById('hamburger');
|
|
343
402
|
var nav = document.getElementById('primaryNav');
|
|
@@ -367,7 +426,7 @@ export const CHROME_BEHAVIOR_JS = `(function () {
|
|
|
367
426
|
}
|
|
368
427
|
}
|
|
369
428
|
|
|
370
|
-
// 21+ age gate — shadow-isolated
|
|
429
|
+
// 21+ age gate — shadow-isolated. Only the host <div> lives in light
|
|
371
430
|
// DOM (data-tot-compliance, the same marker components/islands/IsolatedAgeGate.tsx
|
|
372
431
|
// uses); its contents sit behind a closed shadow root, so tenant CSS/JS can't
|
|
373
432
|
// select, restyle, or hide the dialog. A MutationObserver re-asserts the host the
|
|
@@ -444,11 +503,11 @@ export const CHROME_BEHAVIOR_JS = `(function () {
|
|
|
444
503
|
}
|
|
445
504
|
})();`;
|
|
446
505
|
|
|
447
|
-
/** The platform-shipped chrome stylesheet every rendered wrapper links
|
|
506
|
+
/** The platform-shipped chrome stylesheet every rendered wrapper links. */
|
|
448
507
|
export const SHARED_CHROME_STYLESHEET = "/shared/commerce-chrome.css";
|
|
449
508
|
|
|
450
509
|
/**
|
|
451
|
-
* The platform-shipped RAW-PAGE-BODY marketing stylesheet
|
|
510
|
+
* The platform-shipped RAW-PAGE-BODY marketing stylesheet. Linked only
|
|
452
511
|
* when a tenant opts in (chrome.sharedMarketingCss) and AFTER the chrome sheet, so
|
|
453
512
|
* its page-level variants (e.g. .sec-head) win over chrome's home-section ones.
|
|
454
513
|
*/
|
|
@@ -480,8 +539,22 @@ function faviconMime(href: string): string {
|
|
|
480
539
|
* `chromeWrapper` so a body-only fragment is spliced in and the whole thing is
|
|
481
540
|
* nonce-stamped + base-path-prefixed. Optional sections (age gate, announcement,
|
|
482
541
|
* utility nav, member cue, footer extras) render only when their config is present.
|
|
542
|
+
*
|
|
543
|
+
* `currentPath`, when given, is the tenant-relative request path in the same
|
|
544
|
+
* unprefixed form nav `href`s are authored in (see renderNavItem) — drives
|
|
545
|
+
* `aria-current="page"` on the primary-nav link that exactly matches it.
|
|
546
|
+
*
|
|
547
|
+
* `assignments`, when given, is the tenant's page-assignment manifest: the
|
|
548
|
+
* entry matching `currentPath` overrides `chrome.header.variant` for THIS page
|
|
549
|
+
* only. Resolution happens once, here, through the same fail-closed
|
|
550
|
+
* resolveHeaderVariant the Layout.astro prop path uses, and the resolved value
|
|
551
|
+
* is what reaches the markup.
|
|
483
552
|
*/
|
|
484
|
-
export function renderRawChrome(
|
|
553
|
+
export function renderRawChrome(
|
|
554
|
+
chrome: ChromeConfig,
|
|
555
|
+
currentPath?: string,
|
|
556
|
+
assignments?: ChromeAssignments | null,
|
|
557
|
+
): string {
|
|
485
558
|
const lang = esc(chrome.lang ?? "en");
|
|
486
559
|
const head = chrome.head ?? {};
|
|
487
560
|
const headLinks = [
|
|
@@ -491,20 +564,25 @@ export function renderRawChrome(chrome: TenantChrome): string {
|
|
|
491
564
|
: "",
|
|
492
565
|
...(head.themeStylesheets ?? []).map((h) => `<link rel="stylesheet" href="${esc(h)}">`),
|
|
493
566
|
`<link rel="stylesheet" href="${SHARED_CHROME_STYLESHEET}">`,
|
|
494
|
-
// Opt-in raw-page-body layer, after the chrome sheet so its variants win
|
|
567
|
+
// Opt-in raw-page-body layer, after the chrome sheet so its variants win.
|
|
495
568
|
chrome.sharedMarketingCss ? `<link rel="stylesheet" href="${SHARED_MARKETING_STYLESHEET}">` : "",
|
|
496
569
|
]
|
|
497
570
|
.filter(Boolean)
|
|
498
571
|
.join("\n");
|
|
499
572
|
|
|
573
|
+
const headerVariant = resolveHeaderVariant(
|
|
574
|
+
resolveChromeAssignment(assignments, currentPath)?.headerVariant,
|
|
575
|
+
chrome.header.variant,
|
|
576
|
+
);
|
|
577
|
+
|
|
500
578
|
const skipHref = esc(chrome.skipLink?.href ?? "#main");
|
|
501
579
|
const skipLabel = esc(chrome.skipLink?.label ?? "Skip to main content");
|
|
502
580
|
|
|
503
581
|
const sections = [
|
|
504
582
|
chrome.warningBar ? `<div class="topbar-warning"><p>${esc(chrome.warningBar)}</p></div>` : "",
|
|
505
|
-
chrome.announcement ? renderAnnouncement(chrome.announcement) : "",
|
|
506
|
-
chrome.utilityNav ? renderUtilityNav(chrome.utilityNav) : "",
|
|
507
|
-
renderHeader(chrome),
|
|
583
|
+
chrome.header.announcement ? renderAnnouncement(chrome.header.announcement) : "",
|
|
584
|
+
chrome.header.utilityNav ? renderUtilityNav(chrome.header.utilityNav) : "",
|
|
585
|
+
renderHeader(chrome.header, headerVariant, currentPath),
|
|
508
586
|
].filter(Boolean);
|
|
509
587
|
|
|
510
588
|
const trailing = [
|
|
@@ -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
|
});
|