@tokenoftrust/storefront-runner 2.2.115 → 2.2.117
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.
|
@@ -306,6 +306,16 @@ export interface HomeContent {
|
|
|
306
306
|
blocks: (HomeBlock | MarketingBlock)[];
|
|
307
307
|
}
|
|
308
308
|
|
|
309
|
+
/** Minimum shape the home renderer can consume without dereferencing invalid blocks. */
|
|
310
|
+
export function hasHomeContentShape(value: unknown): boolean {
|
|
311
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
312
|
+
const blocks = (value as { blocks?: unknown }).blocks;
|
|
313
|
+
return Array.isArray(blocks) && blocks.every((block) =>
|
|
314
|
+
!!block && typeof block === "object" && !Array.isArray(block) &&
|
|
315
|
+
typeof (block as { component?: unknown }).component === "string",
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
|
|
309
319
|
export interface EditorialPage {
|
|
310
320
|
slug: string;
|
|
311
321
|
title: string;
|
|
@@ -18,6 +18,7 @@ import type {
|
|
|
18
18
|
EditorialPage,
|
|
19
19
|
HomeContent,
|
|
20
20
|
} from "./content-model.js";
|
|
21
|
+
import { hasHomeContentShape } from "./content-model.js";
|
|
21
22
|
import type { ChromeConfig } from "../chrome/model.js";
|
|
22
23
|
import type { CustomizationEnv, KvGet, TenantConfig } from "@tot/public-runtime";
|
|
23
24
|
import {
|
|
@@ -730,19 +731,14 @@ export class PublishedContentProvider implements ContentProvider {
|
|
|
730
731
|
}
|
|
731
732
|
|
|
732
733
|
// ---------------------------------------------------------------------------
|
|
733
|
-
// VersionPinnedContentProvider
|
|
734
|
-
//
|
|
735
|
-
//
|
|
736
|
-
// PublishedContentProvider (channel -> versionId vs. versionId direct); every
|
|
737
|
-
// method mirrors it 1:1 so a candidate's OWN pages/blog/products/etc. render
|
|
738
|
-
// through the exact same generic-content plumbing as a live/preview page,
|
|
739
|
-
// pinned to the exact commit under review instead of the shared channel.
|
|
734
|
+
// VersionPinnedContentProvider reads only ONE immutable version. An absent file
|
|
735
|
+
// stays absent, so a candidate cannot render bundled tenant content that its
|
|
736
|
+
// version manifest does not contain.
|
|
740
737
|
// ---------------------------------------------------------------------------
|
|
741
738
|
|
|
742
739
|
export class VersionPinnedContentProvider implements ContentProvider {
|
|
743
740
|
readonly perRequestArtifactReads = true;
|
|
744
741
|
constructor(
|
|
745
|
-
private readonly base: ContentProvider,
|
|
746
742
|
private readonly tenantId: string,
|
|
747
743
|
private readonly get: KvGet,
|
|
748
744
|
private readonly versionId: string,
|
|
@@ -770,24 +766,24 @@ export class VersionPinnedContentProvider implements ContentProvider {
|
|
|
770
766
|
}
|
|
771
767
|
|
|
772
768
|
async getHome(): Promise<HomeContent> {
|
|
773
|
-
|
|
769
|
+
const home = await this.pinned<HomeContent>("home.json");
|
|
770
|
+
if (!home || !hasHomeContentShape(home)) throw new Error("Pinned version has no renderable home.json");
|
|
771
|
+
return home;
|
|
774
772
|
}
|
|
775
773
|
async getHomeHtml(): Promise<string | null> {
|
|
776
774
|
const raw = await this.pinnedRaw("home.html");
|
|
777
|
-
return raw !== null ? stampTrackingIdsAtPublish(raw) :
|
|
775
|
+
return raw !== null ? stampTrackingIdsAtPublish(raw) : null;
|
|
778
776
|
}
|
|
779
777
|
async getPageHtml(slug: string): Promise<string | null> {
|
|
780
778
|
const rel = `pages-html/${normalizeRawPageSlug(slug)}.html`;
|
|
781
779
|
const raw = await this.pinnedRaw(rel);
|
|
782
|
-
return raw !== null ? stampTrackingIdsAtPublish(raw) :
|
|
780
|
+
return raw !== null ? stampTrackingIdsAtPublish(raw) : null;
|
|
783
781
|
}
|
|
784
782
|
listPageHtmlSlugs(): Promise<string[]> {
|
|
785
783
|
return this.listArtifactPaths("pages-html/").then((paths) =>
|
|
786
|
-
paths
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
.filter((path) => path.endsWith(".html"))
|
|
790
|
-
.map((path) => path.slice("pages-html/".length, -".html".length)),
|
|
784
|
+
(paths ?? [])
|
|
785
|
+
.filter((path) => path.endsWith(".html"))
|
|
786
|
+
.map((path) => path.slice("pages-html/".length, -".html".length)),
|
|
791
787
|
);
|
|
792
788
|
}
|
|
793
789
|
async getChromeHtml(currentPath?: string): Promise<string | null> {
|
|
@@ -798,20 +794,19 @@ export class VersionPinnedContentProvider implements ContentProvider {
|
|
|
798
794
|
const assignments = parseChromeAssignments(await this.pinned<unknown>("chrome-assignments.json"));
|
|
799
795
|
return renderRawChrome(config, currentPath, assignments);
|
|
800
796
|
}
|
|
801
|
-
return
|
|
797
|
+
return null;
|
|
802
798
|
}
|
|
803
799
|
async getChrome(): Promise<ChromeConfig> {
|
|
804
800
|
const config = parseChromeConfig(await this.pinned<unknown>("chrome.json"));
|
|
805
|
-
return config ??
|
|
801
|
+
return config ?? EMPTY_CHROME_CONFIG;
|
|
806
802
|
}
|
|
807
803
|
async getPage(slug: string): Promise<EditorialPage | null> {
|
|
808
|
-
return
|
|
804
|
+
return this.pinned<EditorialPage>(`pages/${slug}.json`);
|
|
809
805
|
}
|
|
810
806
|
async listPages(): Promise<EditorialPage[]> {
|
|
811
807
|
const paths = await this.listArtifactPaths("pages/");
|
|
812
|
-
if (paths === null) return this.base.listPages();
|
|
813
808
|
const pages = await Promise.all(
|
|
814
|
-
paths.filter((path) => path.endsWith(".json")).map(async (path) => this.pinned<EditorialPage>(path)),
|
|
809
|
+
(paths ?? []).filter((path) => path.endsWith(".json")).map(async (path) => this.pinned<EditorialPage>(path)),
|
|
815
810
|
);
|
|
816
811
|
return pages.filter((page): page is EditorialPage => page !== null);
|
|
817
812
|
}
|
|
@@ -923,15 +918,15 @@ export function createContentProvider(
|
|
|
923
918
|
pin?: ContentVersionPin,
|
|
924
919
|
): ContentProvider {
|
|
925
920
|
// Content dirs are keyed by tenant_id (the adopter writes content/<tenant>/).
|
|
921
|
+
if (pin) {
|
|
922
|
+
return new VersionPinnedContentProvider(tenant.tenant_id, pin.get, pin.versionId);
|
|
923
|
+
}
|
|
926
924
|
const local = new LocalContentProvider(tenant.tenant_id);
|
|
927
925
|
const token = tenant.storyblok_space_token;
|
|
928
926
|
const usable = token && !PLACEHOLDER_TOKEN_RE.test(token);
|
|
929
927
|
const base: ContentProvider = usable
|
|
930
928
|
? new StoryblokContentProvider(token, local)
|
|
931
929
|
: local;
|
|
932
|
-
if (pin) {
|
|
933
|
-
return new VersionPinnedContentProvider(base, tenant.tenant_id, pin.get, pin.versionId);
|
|
934
|
-
}
|
|
935
930
|
return customization
|
|
936
931
|
? new PublishedContentProvider(base, tenant.tenant_id, customization)
|
|
937
932
|
: base;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokenoftrust/storefront-runner",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.117",
|
|
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",
|