@tokenoftrust/storefront-runner 2.4.39 → 2.4.40

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.
@@ -141,6 +141,17 @@ declare global {
141
141
  * inside this prefix on the client.
142
142
  */
143
143
  basePath: string;
144
+ /**
145
+ * The tenant's REAL routed prefix, always — never overridden to a candidate/
146
+ * revision-pinned render's own prefix the way {@link basePath} is. A pinned
147
+ * preview (`/preview/render/<tenant>/<changeId>`, `/preview/rev/<sha>`,
148
+ * `/preview/staged/<sha>`) has no API mirror under its prefix (the catch-all
149
+ * preview route only renders pages), so any same-origin fetch to a real
150
+ * backend endpoint (`/api/...`) must be built from THIS, not `basePath`, or
151
+ * it 404s under preview. Nav hrefs still want `basePath` so they stay inside
152
+ * the pinned render.
153
+ */
154
+ apiBasePath: string;
144
155
  /**
145
156
  * Which customization environment to serve. "live" on production
146
157
  * hosts; "test" only behind a valid, tenant-bound preview token on a
@@ -50,7 +50,7 @@ const {
50
50
  class: className,
51
51
  } = Astro.props;
52
52
 
53
- const { basePath } = Astro.locals;
53
+ const { basePath, apiBasePath } = Astro.locals;
54
54
  const hrefOf = (l: ChromeLink) => (l.external ? l.href : withBase(basePath, l.href));
55
55
  const showLogo = !!brand?.logo && /^(https?:|\/)/.test(brand.logo.src);
56
56
  const columnLinkStamp = (l: ChromeLink, colIndex: number, index: number) =>
@@ -184,7 +184,7 @@ const collapsible = variant === "light-collapsible";
184
184
  {newsletter.title && <h3 class="font-display text-base font-semibold">{newsletter.title}</h3>}
185
185
  {newsletter.body && <p class="mt-1 text-sm text-muted">{newsletter.body}</p>}
186
186
  <form
187
- action={withBase(basePath, newsletter.action ?? "/api/newsletter")}
187
+ action={withBase(apiBasePath, newsletter.action ?? "/api/newsletter")}
188
188
  method="post"
189
189
  class="mt-4 flex max-w-md flex-col gap-3 sm:flex-row sm:flex-wrap"
190
190
  >
@@ -668,6 +668,12 @@ export async function runRenderChain(input: RenderChainInput): Promise<Response>
668
668
  // `/preview/render/<tenant>/<changeId>`) so a pinned page's OWN generated
669
669
  // links stay inside that candidate instead of escaping to the live host.
670
670
  locals.basePath = candidatePin ? candidatePin.basePath : basePath;
671
+ // ALWAYS the real tenant-routed prefix, even under a candidate/revision pin —
672
+ // fb-1791905696516-ubw8a3: a pinned preview's catch-all route only knows how to
673
+ // RENDER a page, so same-origin fetches to a real `/api/...` backend endpoint
674
+ // (checkout handoff, compliance preflight, newsletter) must stay off `basePath`
675
+ // once it's pin-overridden, or they 404 under preview.
676
+ locals.apiBasePath = basePath;
671
677
  // Renderer-owned four-stage browser environment (ADR 0014 E1 / 0017), computed
672
678
  // ONCE here from authoritative request state and serialized before app code by
673
679
  // both document mounts (Layout.astro + rawHtmlResponse). The candidate render
@@ -43,7 +43,7 @@ import {
43
43
  type CommerceStaticResolved,
44
44
  } from "@/lib/homeVisualParity";
45
45
 
46
- const { tenant, tot, content, basePath, canonicalBase, features, capabilities } = Astro.locals;
46
+ const { tenant, tot, content, basePath, apiBasePath, canonicalBase, features, capabilities } = Astro.locals;
47
47
  const marketing = !features.catalog;
48
48
 
49
49
  // Raw-HTML home (vendor-injected): serve the tenant's own self-contained
@@ -296,7 +296,7 @@ const homeSectionWidgets = marketing
296
296
  </h2>
297
297
  {b.body && <p class="mt-3 text-[var(--color-muted)]">{b.body}</p>}
298
298
  <form
299
- action={withBase(basePath, "/api/newsletter")}
299
+ action={withBase(apiBasePath, "/api/newsletter")}
300
300
  method="post"
301
301
  class="mx-auto mt-7 flex max-w-md flex-col gap-3 sm:flex-row"
302
302
  >
@@ -49,7 +49,7 @@ import { getRegistryService } from "@/lib/apps/registryAccess.js";
49
49
  import { renderWidgetSlot } from "@/lib/apps/widgets/renderSlot.js";
50
50
 
51
51
  const { handle } = Astro.params;
52
- const { tenant, tot, canonicalBase, basePath, commerce } = Astro.locals;
52
+ const { tenant, tot, canonicalBase, basePath, apiBasePath, commerce } = Astro.locals;
53
53
  // Commerce-only route: marketing tenants have no product detail pages.
54
54
  if (!Astro.locals.features.catalog) return new Response(null, { status: 404 });
55
55
  const compliance = tenant.compliance;
@@ -380,7 +380,7 @@ const jsonLd = [productJsonLd, breadcrumbLd(crumbs, canonicalBase)];
380
380
  productTitle={product.title}
381
381
  checkoutCart={checkoutCart}
382
382
  pricing={pricing}
383
- handoffUrl={withBase(basePath, "/api/commerce/checkout-handoff")}
383
+ handoffUrl={withBase(apiBasePath, "/api/commerce/checkout-handoff")}
384
384
  />
385
385
  {/* Regulated-tenant checkout ENFORCEMENT gate. Intercepts the
386
386
  managed-cart handoff to clear age + ship-to compliance server-side
@@ -389,7 +389,7 @@ const jsonLd = [productJsonLd, breadcrumbLd(crumbs, canonicalBase)];
389
389
  {checkoutCart && isRegulatedTenant(tenant) && compliance?.minAge && (
390
390
  <CheckoutComplianceGate
391
391
  client:load
392
- preflightUrl={withBase(basePath, "/api/compliance/preflight")}
392
+ preflightUrl={withBase(apiBasePath, "/api/compliance/preflight")}
393
393
  minAge={compliance.minAge}
394
394
  />
395
395
  )}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokenoftrust/storefront-runner",
3
- "version": "2.4.39",
3
+ "version": "2.4.40",
4
4
  "license": "SEE LICENSE IN LICENSE",
5
5
  "description": "World-shareable storefront runner: multi-tenant renderer on Astro/Cloudflare. No control plane.",
6
6
  "packageManager": "pnpm@11.9.0",