@shop-on/theme-loader 0.0.0-stage → 0.2.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/dist/LICENSE.md +39 -0
- package/dist/disk.d.ts +42 -0
- package/dist/disk.js +150 -0
- package/dist/dynamic-sources.d.ts +60 -0
- package/dist/dynamic-sources.js +149 -0
- package/dist/enrich.d.ts +36 -0
- package/dist/enrich.js +64 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +11 -0
- package/dist/orchestrator.d.ts +8 -0
- package/dist/orchestrator.js +35 -0
- package/dist/section-preview.d.ts +19 -0
- package/dist/section-preview.js +54 -0
- package/dist/seo.d.ts +33 -0
- package/dist/seo.js +62 -0
- package/dist/server-modules.js +25 -0
- package/dist/specs.d.ts +35 -0
- package/dist/specs.js +302 -0
- package/dist/template-request.d.ts +19 -0
- package/dist/template-request.js +54 -0
- package/dist/theme-settings.d.ts +7 -0
- package/dist/theme-settings.js +98 -0
- package/dist/types.d.ts +195 -0
- package/dist/types.js +1 -0
- package/package.json +36 -3
- package/README.md +0 -3
package/dist/LICENSE.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Cody Theme Development License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Cody LLC. All rights reserved except as granted below.
|
|
4
|
+
|
|
5
|
+
"The Software" is this package, its source, and its documentation. "The
|
|
6
|
+
Platform" is Cody, the commerce platform operated by Cody LLC.
|
|
7
|
+
|
|
8
|
+
## 1. Grant
|
|
9
|
+
|
|
10
|
+
You may use, copy and modify the Software, and distribute modified copies, solely
|
|
11
|
+
to develop, test, and distribute themes, apps and other extensions that run on
|
|
12
|
+
the Platform.
|
|
13
|
+
|
|
14
|
+
## 2. Restrictions
|
|
15
|
+
|
|
16
|
+
You may not:
|
|
17
|
+
|
|
18
|
+
1. use the Software, or anything derived from it, with any commerce platform,
|
|
19
|
+
storefront or hosting service other than the Platform;
|
|
20
|
+
2. use the Software to build a product that competes with the Platform;
|
|
21
|
+
3. sublicense the Software, or distribute it other than as part of a theme,
|
|
22
|
+
app or extension for the Platform;
|
|
23
|
+
4. remove or alter this license or any copyright notice in the Software.
|
|
24
|
+
|
|
25
|
+
## 3. Your work
|
|
26
|
+
|
|
27
|
+
Themes, apps and extensions you build with the Software are yours. The parts of
|
|
28
|
+
the Software they include stay under this license.
|
|
29
|
+
|
|
30
|
+
## 4. Termination
|
|
31
|
+
|
|
32
|
+
This license ends automatically if you break any of its terms. You must then
|
|
33
|
+
stop using the Software and delete your copies of it.
|
|
34
|
+
|
|
35
|
+
## 5. No warranty
|
|
36
|
+
|
|
37
|
+
The Software is provided "as is", without warranty of any kind. Cody LLC is not
|
|
38
|
+
liable for any claim, damages or other liability arising from the Software or
|
|
39
|
+
its use.
|
package/dist/disk.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { LoadedTemplate, SectionGroupJson } from "./types.js";
|
|
2
|
+
import { buildSectionInstance } from "@shop-on/theme-contract";
|
|
3
|
+
import { BlockSchema, SectionSchema } from "@shop-on/theme-system";
|
|
4
|
+
//#region src/disk.d.ts
|
|
5
|
+
/** Path-segment guard for anything that reaches the disk loaders — template
|
|
6
|
+
* ids, section types. Dots are allowed so template VARIANTS (`product.sale`)
|
|
7
|
+
* resolve; `/` and `..` segments cannot survive it. */
|
|
8
|
+
export declare const SAFE_TEMPLATE_ID: RegExp;
|
|
9
|
+
/** Theme-wide settings for the active preset. Missing/partial file → `{}` so a
|
|
10
|
+
* page renders with theme.css fallbacks rather than 500-ing. */
|
|
11
|
+
export declare function loadThemeSettings(configDir: string): Promise<Record<string, unknown>>;
|
|
12
|
+
/** The theme's settings SCHEMA (groups of `settings[]`), used to discover
|
|
13
|
+
* theme-wide picker settings. Missing file → `[]`. */
|
|
14
|
+
export declare function loadSettingsSchema(configDir: string): Promise<Array<{
|
|
15
|
+
settings?: Array<{
|
|
16
|
+
type?: string;
|
|
17
|
+
id?: string;
|
|
18
|
+
allow_uploads?: boolean;
|
|
19
|
+
}>;
|
|
20
|
+
}>>;
|
|
21
|
+
export declare function loadSectionGroupsForLayout(configDir: string, layoutName: string, listingDir?: string | null): Promise<Record<string, SectionGroupJson> | undefined>;
|
|
22
|
+
/** Exact-id template reader. Not memoized (parses fresh per call), which the
|
|
23
|
+
* per-locale translation overlay relies on — it mutates the loaded document in
|
|
24
|
+
* place. If templates are ever cached, the cache key MUST include the locale.
|
|
25
|
+
*
|
|
26
|
+
* A missing template returns `undefined` so the CALLER owns the fallback
|
|
27
|
+
* policy: a silent variant→base swap here would starve the preview-or-disk
|
|
28
|
+
* retry in `resolveTemplateVariant` and hand editor previews the raw on-disk
|
|
29
|
+
* scaffold instead of the draft's base template. */
|
|
30
|
+
export declare function loadTemplate(configDir: string, templateId: string): Promise<LoadedTemplate | undefined>;
|
|
31
|
+
export declare function loadBlockSchemas(configDir: string): Promise<Map<string, BlockSchema>>;
|
|
32
|
+
export declare function loadSectionSchemas(configDir: string): Promise<Map<string, SectionSchema>>;
|
|
33
|
+
/** Raw section schema JSON (presets included) for the standalone section
|
|
34
|
+
* preview — deliberately unparsed, since the preview seeds from the file's own
|
|
35
|
+
* `presets` blob. */
|
|
36
|
+
export declare function loadSectionSchemaJson(configDir: string, type: string): Promise<Record<string, unknown> | null>;
|
|
37
|
+
/** The schema-locale catalog the section preview resolves `t:` keys against. */
|
|
38
|
+
export declare function loadPreviewLocale(configDir: string, previewLocale?: string): Promise<Record<string, unknown>>;
|
|
39
|
+
/** Deep-resolve `t:` keys in a JSON document against a locale catalog. */
|
|
40
|
+
export declare function resolveTranslations(obj: unknown, locale: Record<string, unknown>): unknown;
|
|
41
|
+
//#endregion
|
|
42
|
+
export { buildSectionInstance };
|
package/dist/disk.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { buildSectionInstance } from "@shop-on/theme-contract";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { currentPresetHandle, mergePresetJson } from "@shop-on/theme-contract/preset-listing";
|
|
5
|
+
import { loadBlockSchemasFrom, loadSectionSchemasFrom } from "@shop-on/theme-system";
|
|
6
|
+
//#region src/disk.ts
|
|
7
|
+
/** Path-segment guard for anything that reaches the disk loaders — template
|
|
8
|
+
* ids, section types. Dots are allowed so template VARIANTS (`product.sale`)
|
|
9
|
+
* resolve; `/` and `..` segments cannot survive it. */
|
|
10
|
+
const SAFE_TEMPLATE_ID = /^[a-zA-Z0-9_.-]+$/;
|
|
11
|
+
async function readJson(filePath) {
|
|
12
|
+
return JSON.parse(await fs.readFile(filePath, "utf-8"));
|
|
13
|
+
}
|
|
14
|
+
function extractPresetSettings(data) {
|
|
15
|
+
if (data && typeof data === "object" && "presets" in data) {
|
|
16
|
+
const d = data;
|
|
17
|
+
return d.presets?.[d.current?.preset ?? ""]?.settings ?? {};
|
|
18
|
+
}
|
|
19
|
+
return data ?? {};
|
|
20
|
+
}
|
|
21
|
+
async function readConfigJson(configDir, file) {
|
|
22
|
+
for (const candidate of [path.join(configDir, file), path.join(configDir, "config", file)]) try {
|
|
23
|
+
return JSON.parse(await fs.readFile(candidate, "utf-8"));
|
|
24
|
+
} catch {}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
const artifactDirs = /* @__PURE__ */ new Set();
|
|
28
|
+
async function currentListingDir(configDir) {
|
|
29
|
+
if (artifactDirs.has(configDir)) return null;
|
|
30
|
+
try {
|
|
31
|
+
await fs.readFile(path.join(configDir, "settings-data.json"));
|
|
32
|
+
artifactDirs.add(configDir);
|
|
33
|
+
return null;
|
|
34
|
+
} catch {}
|
|
35
|
+
const data = await readJson(path.join(configDir, "config", "settings-data.json")).catch(() => null);
|
|
36
|
+
const override = typeof process === "undefined" ? void 0 : process.env.THEME_PRESET;
|
|
37
|
+
const handle = currentPresetHandle(data, override);
|
|
38
|
+
return handle ? path.join(configDir, "listings", handle) : null;
|
|
39
|
+
}
|
|
40
|
+
/** `<configDir>/<dir>/<file>` with the preset listing merged over it. Either
|
|
41
|
+
* half may be missing; both missing → `undefined`. */
|
|
42
|
+
async function readWithListing(configDir, listingDir, dir, file) {
|
|
43
|
+
const [base, override] = await Promise.all([readJson(path.join(configDir, dir, file)).catch(() => void 0), listingDir ? readJson(path.join(listingDir, dir, file)).catch(() => void 0) : void 0]);
|
|
44
|
+
if (override === void 0) return base;
|
|
45
|
+
return base === void 0 ? override : mergePresetJson(base, override);
|
|
46
|
+
}
|
|
47
|
+
/** Theme-wide settings for the active preset. Missing/partial file → `{}` so a
|
|
48
|
+
* page renders with theme.css fallbacks rather than 500-ing. */
|
|
49
|
+
async function loadThemeSettings(configDir) {
|
|
50
|
+
return extractPresetSettings(await readConfigJson(configDir, "settings-data.json"));
|
|
51
|
+
}
|
|
52
|
+
/** The theme's settings SCHEMA (groups of `settings[]`), used to discover
|
|
53
|
+
* theme-wide picker settings. Missing file → `[]`. */
|
|
54
|
+
async function loadSettingsSchema(configDir) {
|
|
55
|
+
const raw = await readConfigJson(configDir, "settings-schema.json");
|
|
56
|
+
return Array.isArray(raw) ? raw : [];
|
|
57
|
+
}
|
|
58
|
+
async function loadSectionGroupsForLayout(configDir, layoutName, listingDir) {
|
|
59
|
+
if (!SAFE_TEMPLATE_ID.test(layoutName)) return void 0;
|
|
60
|
+
let layout;
|
|
61
|
+
try {
|
|
62
|
+
layout = await readJson(path.join(configDir, "layouts", `${layoutName}.json`));
|
|
63
|
+
} catch {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (!layout.section_groups?.length) return void 0;
|
|
67
|
+
const listing = listingDir === void 0 ? await currentListingDir(configDir) : listingDir;
|
|
68
|
+
const reads = await Promise.all(layout.section_groups.map(async (ref) => {
|
|
69
|
+
if (!SAFE_TEMPLATE_ID.test(ref.type)) return null;
|
|
70
|
+
return await readWithListing(configDir, listing, "section-groups", `${ref.type}-group.json`) ?? null;
|
|
71
|
+
}));
|
|
72
|
+
const groups = {};
|
|
73
|
+
for (const group of reads) if (group) groups[group.type] = group;
|
|
74
|
+
return groups;
|
|
75
|
+
}
|
|
76
|
+
/** Exact-id template reader. Not memoized (parses fresh per call), which the
|
|
77
|
+
* per-locale translation overlay relies on — it mutates the loaded document in
|
|
78
|
+
* place. If templates are ever cached, the cache key MUST include the locale.
|
|
79
|
+
*
|
|
80
|
+
* A missing template returns `undefined` so the CALLER owns the fallback
|
|
81
|
+
* policy: a silent variant→base swap here would starve the preview-or-disk
|
|
82
|
+
* retry in `resolveTemplateVariant` and hand editor previews the raw on-disk
|
|
83
|
+
* scaffold instead of the draft's base template. */
|
|
84
|
+
async function loadTemplate(configDir, templateId) {
|
|
85
|
+
const listingDir = await currentListingDir(configDir);
|
|
86
|
+
const template = await readWithListing(configDir, listingDir, "templates", `${templateId}.json`);
|
|
87
|
+
if (!template) return void 0;
|
|
88
|
+
const section_groups = await loadSectionGroupsForLayout(configDir, template.layout ?? "main", listingDir);
|
|
89
|
+
return {
|
|
90
|
+
...template,
|
|
91
|
+
section_groups
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
const blockSchemaCache = /* @__PURE__ */ new Map();
|
|
95
|
+
const sectionSchemaCache = /* @__PURE__ */ new Map();
|
|
96
|
+
function loadBlockSchemas(configDir) {
|
|
97
|
+
let cached = blockSchemaCache.get(configDir);
|
|
98
|
+
if (!cached) {
|
|
99
|
+
cached = loadBlockSchemasFrom([path.join(configDir, "blocks")]);
|
|
100
|
+
blockSchemaCache.set(configDir, cached);
|
|
101
|
+
}
|
|
102
|
+
return cached;
|
|
103
|
+
}
|
|
104
|
+
function loadSectionSchemas(configDir) {
|
|
105
|
+
let cached = sectionSchemaCache.get(configDir);
|
|
106
|
+
if (!cached) {
|
|
107
|
+
cached = loadSectionSchemasFrom([path.join(configDir, "sections")]);
|
|
108
|
+
sectionSchemaCache.set(configDir, cached);
|
|
109
|
+
}
|
|
110
|
+
return cached;
|
|
111
|
+
}
|
|
112
|
+
/** Raw section schema JSON (presets included) for the standalone section
|
|
113
|
+
* preview — deliberately unparsed, since the preview seeds from the file's own
|
|
114
|
+
* `presets` blob. */
|
|
115
|
+
async function loadSectionSchemaJson(configDir, type) {
|
|
116
|
+
if (!SAFE_TEMPLATE_ID.test(type)) return null;
|
|
117
|
+
try {
|
|
118
|
+
return await readJson(path.join(configDir, "sections", `${type}.json`));
|
|
119
|
+
} catch {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const previewLocaleCache = /* @__PURE__ */ new Map();
|
|
124
|
+
/** The schema-locale catalog the section preview resolves `t:` keys against. */
|
|
125
|
+
function loadPreviewLocale(configDir, previewLocale = "en.default") {
|
|
126
|
+
const file = path.join(configDir, "locales", `${previewLocale}.schema.json`);
|
|
127
|
+
let cached = previewLocaleCache.get(file);
|
|
128
|
+
if (!cached) {
|
|
129
|
+
cached = readJson(file).catch(() => ({}));
|
|
130
|
+
previewLocaleCache.set(file, cached);
|
|
131
|
+
}
|
|
132
|
+
return cached;
|
|
133
|
+
}
|
|
134
|
+
/** Deep-resolve `t:` keys in a JSON document against a locale catalog. */
|
|
135
|
+
function resolveTranslations(obj, locale) {
|
|
136
|
+
if (typeof obj === "string" && obj.startsWith("t:")) {
|
|
137
|
+
let cur = locale;
|
|
138
|
+
for (const p of obj.slice(2).split(".")) cur = cur && typeof cur === "object" ? cur[p] : void 0;
|
|
139
|
+
return typeof cur === "string" ? cur : obj;
|
|
140
|
+
}
|
|
141
|
+
if (Array.isArray(obj)) return obj.map((v) => resolveTranslations(v, locale));
|
|
142
|
+
if (obj && typeof obj === "object") {
|
|
143
|
+
const out = {};
|
|
144
|
+
for (const [k, v] of Object.entries(obj)) out[k] = resolveTranslations(v, locale);
|
|
145
|
+
return out;
|
|
146
|
+
}
|
|
147
|
+
return obj;
|
|
148
|
+
}
|
|
149
|
+
//#endregion
|
|
150
|
+
export { SAFE_TEMPLATE_ID, buildSectionInstance, loadBlockSchemas, loadPreviewLocale, loadSectionGroupsForLayout, loadSectionSchemaJson, loadSectionSchemas, loadSettingsSchema, loadTemplate, loadThemeSettings, resolveTranslations };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { DynamicSourceArticle, DynamicSourceBlog, DynamicSourceCollection, DynamicSourcePage, SectionGroupJson, TemplateJson, ThemeLoaderConfig } from "./types.js";
|
|
2
|
+
import { ProductData } from "@shop-on/theme-server/product";
|
|
3
|
+
//#region src/dynamic-sources.d.ts
|
|
4
|
+
/** Route product projection the dynamic-source passes actually read. */
|
|
5
|
+
export type DynamicSourceProduct = Pick<ProductData, "title" | "vendor" | "price" | "metafields"> | null;
|
|
6
|
+
/** entry id → field key → { type, value } — the shape the whole-value + inline
|
|
7
|
+
* binding passes read for `metaobject_field` sources. */
|
|
8
|
+
export type MetaobjectFieldSources = Record<string, Record<string, {
|
|
9
|
+
type: string;
|
|
10
|
+
value: string;
|
|
11
|
+
}>>;
|
|
12
|
+
/** Fetch every metaobject entry the template needs, keyed by the numeric entry
|
|
13
|
+
* id: the entries PINNED by `metaobject_field` descriptors (whole-value
|
|
14
|
+
* bindings + inline tokens) UNION the entries a field-bearing
|
|
15
|
+
* `product_metafield` binding traverses (each METAOBJECT_REFERENCE metafield
|
|
16
|
+
* value on the route product). Early-out (no fetch) when the template
|
|
17
|
+
* references none. `fetchMetaobject` is memoTTL'd + content-version keyed, so
|
|
18
|
+
* repeats collapse to one trip. */
|
|
19
|
+
export declare function loadMetaobjectFieldSources(template: unknown, product: Pick<ProductData, "metafields"> | null): Promise<MetaobjectFieldSources>;
|
|
20
|
+
/** Whole-value binding pass (product_metafield + metaobject_field +
|
|
21
|
+
* collection_attribute), sharing one schema-derived `sinkTypeAt` so every
|
|
22
|
+
* source routes by the theme's declared sink. Runs BEFORE the picker specs so a
|
|
23
|
+
* bound GID is in place for the `${id}Data` resolution to dereference. The
|
|
24
|
+
* collection description is fetched only when a sidecar binds it. */
|
|
25
|
+
export declare function applyWholeValueBindings(cfg: ThemeLoaderConfig, template: unknown, product: DynamicSourceProduct, collection: DynamicSourceCollection, metaobjectFields: MetaobjectFieldSources): Promise<void>;
|
|
26
|
+
/** The route resources an inline token can resolve against. Each is `null`
|
|
27
|
+
* (or absent) on every template but its own, which is what makes a token in a
|
|
28
|
+
* section group render "" instead of leaking its descriptor. One object rather
|
|
29
|
+
* than five positional args: a source added here is a named field at every call
|
|
30
|
+
* site, not a `null` in the right slot. */
|
|
31
|
+
export interface DynamicSourceRoute {
|
|
32
|
+
product?: DynamicSourceProduct;
|
|
33
|
+
collection?: DynamicSourceCollection;
|
|
34
|
+
page?: DynamicSourcePage;
|
|
35
|
+
blog?: DynamicSourceBlog;
|
|
36
|
+
article?: DynamicSourceArticle;
|
|
37
|
+
}
|
|
38
|
+
/** The SINGLE inline-source pass — product attributes/metafields (PDP only) +
|
|
39
|
+
* collection attributes (PLP only) + page attributes (CMS page only) + blog and
|
|
40
|
+
* article attributes (their own templates only) + metaobject fields (every
|
|
41
|
+
* template) folded into one substitution so a resolved value is never
|
|
42
|
+
* re-scanned. Runs LAST in `runEnrichment`. */
|
|
43
|
+
export declare function applyInlineSourceTokens(template: unknown, route: DynamicSourceRoute, metaobjectFields: MetaobjectFieldSources): void;
|
|
44
|
+
/** Merchant translation overlay for editor-authored template content, applied
|
|
45
|
+
* to the raw parsed template BEFORE picker enrichment (see
|
|
46
|
+
* applyContentOverrides for why the order is load-bearing). One fetch per
|
|
47
|
+
* container: the template's own JSON_TEMPLATE rows plus one SECTION_GROUP per
|
|
48
|
+
* group. The section_groups record KEY is the container handle — the
|
|
49
|
+
* orchestrator's preview merge strips each group's `type` field, so
|
|
50
|
+
* `group.type` is undefined on the preview path and would fetch zero rows.
|
|
51
|
+
* Each container has its own try/catch: a failed fetch degrades that slice to
|
|
52
|
+
* base-language text instead of aborting the others or 500-ing the page. */
|
|
53
|
+
export declare function overlayTemplateTranslations(cfg: ThemeLoaderConfig, template: TemplateJson & {
|
|
54
|
+
section_groups?: Record<string, SectionGroupJson>;
|
|
55
|
+
templateId?: string;
|
|
56
|
+
}, locale: string): Promise<void>;
|
|
57
|
+
/** SETTINGS_DATA sibling of the template overlay: flat setting-id keys applied
|
|
58
|
+
* onto the raw theme-settings map, before its own enrichment pass. */
|
|
59
|
+
export declare function overlayThemeSettingsTranslations(cfg: ThemeLoaderConfig, settings: Record<string, unknown>, locale: string): Promise<void>;
|
|
60
|
+
//#endregion
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { loadBlockSchemas, loadSectionSchemas } from "./disk.js";
|
|
2
|
+
import { metaobjectModule, translationsModule } from "./server-modules.js";
|
|
3
|
+
import { applyCollectionAttributeBindings, applyInlineSources, applyMetafieldBindings, applyMetaobjectFieldBindings, collectCollectionAttributeBindings, collectMetaobjectEntryIds, collectTraversedEntryIds, metaobjectGid } from "@shop-on/theme-contract";
|
|
4
|
+
import { formatMoney } from "@shop-on/theme-runtime/money";
|
|
5
|
+
import { serializeRichText } from "@shop-on/theme-runtime/richtext";
|
|
6
|
+
//#region src/dynamic-sources.ts
|
|
7
|
+
const MAX_METAOBJECT_ENTRIES = 50;
|
|
8
|
+
/** Fetch every metaobject entry the template needs, keyed by the numeric entry
|
|
9
|
+
* id: the entries PINNED by `metaobject_field` descriptors (whole-value
|
|
10
|
+
* bindings + inline tokens) UNION the entries a field-bearing
|
|
11
|
+
* `product_metafield` binding traverses (each METAOBJECT_REFERENCE metafield
|
|
12
|
+
* value on the route product). Early-out (no fetch) when the template
|
|
13
|
+
* references none. `fetchMetaobject` is memoTTL'd + content-version keyed, so
|
|
14
|
+
* repeats collapse to one trip. */
|
|
15
|
+
async function loadMetaobjectFieldSources(template, product) {
|
|
16
|
+
const pinned = collectMetaobjectEntryIds(template);
|
|
17
|
+
const traversed = collectTraversedEntryIds(template, product?.metafields ?? {});
|
|
18
|
+
const seen = /* @__PURE__ */ new Set();
|
|
19
|
+
const ids = [];
|
|
20
|
+
for (const id of pinned) {
|
|
21
|
+
if (seen.has(id)) continue;
|
|
22
|
+
seen.add(id);
|
|
23
|
+
ids.push(id);
|
|
24
|
+
}
|
|
25
|
+
const pinnedCount = ids.length;
|
|
26
|
+
for (const id of traversed) {
|
|
27
|
+
if (seen.has(id)) continue;
|
|
28
|
+
seen.add(id);
|
|
29
|
+
ids.push(id);
|
|
30
|
+
}
|
|
31
|
+
if (ids.length === 0) return {};
|
|
32
|
+
let entryIds = ids;
|
|
33
|
+
if (ids.length > MAX_METAOBJECT_ENTRIES) {
|
|
34
|
+
console.warn(`[loadMetaobjectFieldSources] template references ${ids.length} metaobject entries (${pinnedCount} pinned + ${ids.length - pinnedCount} traversed); capping the fetch at ${MAX_METAOBJECT_ENTRIES}.`);
|
|
35
|
+
entryIds = ids.slice(0, MAX_METAOBJECT_ENTRIES);
|
|
36
|
+
}
|
|
37
|
+
const { fetchMetaobject } = await metaobjectModule();
|
|
38
|
+
const entries = await Promise.all(entryIds.map((id) => fetchMetaobject(metaobjectGid(id))));
|
|
39
|
+
const sources = {};
|
|
40
|
+
entryIds.forEach((entryId, i) => {
|
|
41
|
+
const entry = entries[i];
|
|
42
|
+
if (!entry) return;
|
|
43
|
+
const fields = {};
|
|
44
|
+
for (const [key, field] of Object.entries(entry.fields)) {
|
|
45
|
+
if (field.value == null) continue;
|
|
46
|
+
fields[key] = {
|
|
47
|
+
type: field.type,
|
|
48
|
+
value: field.value
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
sources[entryId] = fields;
|
|
52
|
+
});
|
|
53
|
+
return sources;
|
|
54
|
+
}
|
|
55
|
+
async function loadSinkTypeAt(cfg) {
|
|
56
|
+
const [sectionSchemas, blockSchemas] = await Promise.all([loadSectionSchemas(cfg.configDir), loadBlockSchemas(cfg.configDir)]);
|
|
57
|
+
return (node, settingId) => {
|
|
58
|
+
const type = node.type;
|
|
59
|
+
if (!type) return void 0;
|
|
60
|
+
return (sectionSchemas.get(type) ?? blockSchemas.get(type))?.settings?.find((s) => s.id === settingId)?.type;
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/** Whole-value binding pass (product_metafield + metaobject_field +
|
|
64
|
+
* collection_attribute), sharing one schema-derived `sinkTypeAt` so every
|
|
65
|
+
* source routes by the theme's declared sink. Runs BEFORE the picker specs so a
|
|
66
|
+
* bound GID is in place for the `${id}Data` resolution to dereference. The
|
|
67
|
+
* collection description is fetched only when a sidecar binds it. */
|
|
68
|
+
async function applyWholeValueBindings(cfg, template, product, collection, metaobjectFields) {
|
|
69
|
+
const sinkTypeAt = await loadSinkTypeAt(cfg);
|
|
70
|
+
if (collection && collectCollectionAttributeBindings(template).includes("description")) {
|
|
71
|
+
const descriptionHtml = await collection.getDescriptionHtml();
|
|
72
|
+
applyCollectionAttributeBindings(template, { descriptionHtml }, { sinkTypeAt });
|
|
73
|
+
}
|
|
74
|
+
const opts = {
|
|
75
|
+
serializeRichText,
|
|
76
|
+
sinkTypeAt
|
|
77
|
+
};
|
|
78
|
+
applyMetafieldBindings(template, product?.metafields ?? {}, {
|
|
79
|
+
...opts,
|
|
80
|
+
metaobjectFields
|
|
81
|
+
});
|
|
82
|
+
applyMetaobjectFieldBindings(template, metaobjectFields, opts);
|
|
83
|
+
}
|
|
84
|
+
/** The SINGLE inline-source pass — product attributes/metafields (PDP only) +
|
|
85
|
+
* collection attributes (PLP only) + page attributes (CMS page only) + blog and
|
|
86
|
+
* article attributes (their own templates only) + metaobject fields (every
|
|
87
|
+
* template) folded into one substitution so a resolved value is never
|
|
88
|
+
* re-scanned. Runs LAST in `runEnrichment`. */
|
|
89
|
+
function applyInlineSourceTokens(template, route, metaobjectFields) {
|
|
90
|
+
const { product = null, collection = null, page = null, blog = null, article = null } = route;
|
|
91
|
+
applyInlineSources(template, {
|
|
92
|
+
metafields: product?.metafields ?? {},
|
|
93
|
+
attributes: product ? {
|
|
94
|
+
title: product.title,
|
|
95
|
+
vendor: product.vendor ?? "",
|
|
96
|
+
price: formatMoney(product.price)
|
|
97
|
+
} : {},
|
|
98
|
+
collectionAttributes: collection ? { title: collection.title } : {},
|
|
99
|
+
pageAttributes: page ? { title: page.title } : {},
|
|
100
|
+
blogAttributes: blog ? { title: blog.title } : {},
|
|
101
|
+
articleAttributes: article ? { title: article.title } : {},
|
|
102
|
+
metaobjectFields
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
/** Merchant translation overlay for editor-authored template content, applied
|
|
106
|
+
* to the raw parsed template BEFORE picker enrichment (see
|
|
107
|
+
* applyContentOverrides for why the order is load-bearing). One fetch per
|
|
108
|
+
* container: the template's own JSON_TEMPLATE rows plus one SECTION_GROUP per
|
|
109
|
+
* group. The section_groups record KEY is the container handle — the
|
|
110
|
+
* orchestrator's preview merge strips each group's `type` field, so
|
|
111
|
+
* `group.type` is undefined on the preview path and would fetch zero rows.
|
|
112
|
+
* Each container has its own try/catch: a failed fetch degrades that slice to
|
|
113
|
+
* base-language text instead of aborting the others or 500-ing the page. */
|
|
114
|
+
async function overlayTemplateTranslations(cfg, template, locale) {
|
|
115
|
+
if (!cfg.installId) return;
|
|
116
|
+
const { applyContentOverrides, fetchThemeContent } = await translationsModule();
|
|
117
|
+
const jobs = [];
|
|
118
|
+
const templateHandle = template.templateId;
|
|
119
|
+
if (templateHandle) jobs.push((async () => {
|
|
120
|
+
try {
|
|
121
|
+
const rows = await fetchThemeContent(cfg.installId, locale, {
|
|
122
|
+
type: "JSON_TEMPLATE",
|
|
123
|
+
handle: templateHandle
|
|
124
|
+
});
|
|
125
|
+
applyContentOverrides(template, rows);
|
|
126
|
+
} catch {}
|
|
127
|
+
})());
|
|
128
|
+
for (const [handle, group] of Object.entries(template.section_groups ?? {})) jobs.push((async () => {
|
|
129
|
+
try {
|
|
130
|
+
const rows = await fetchThemeContent(cfg.installId, locale, {
|
|
131
|
+
type: "SECTION_GROUP",
|
|
132
|
+
handle
|
|
133
|
+
});
|
|
134
|
+
applyContentOverrides(group, rows);
|
|
135
|
+
} catch {}
|
|
136
|
+
})());
|
|
137
|
+
await Promise.all(jobs);
|
|
138
|
+
}
|
|
139
|
+
/** SETTINGS_DATA sibling of the template overlay: flat setting-id keys applied
|
|
140
|
+
* onto the raw theme-settings map, before its own enrichment pass. */
|
|
141
|
+
async function overlayThemeSettingsTranslations(cfg, settings, locale) {
|
|
142
|
+
if (!cfg.installId) return;
|
|
143
|
+
const { applyContentOverrides, fetchThemeContent } = await translationsModule();
|
|
144
|
+
try {
|
|
145
|
+
applyContentOverrides(settings, await fetchThemeContent(cfg.installId, locale, { type: "SETTINGS_DATA" }));
|
|
146
|
+
} catch {}
|
|
147
|
+
}
|
|
148
|
+
//#endregion
|
|
149
|
+
export { applyInlineSourceTokens, applyWholeValueBindings, loadMetaobjectFieldSources, overlayTemplateTranslations, overlayThemeSettingsTranslations };
|
package/dist/enrich.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { EnrichRequest, LoadedTemplate, SectionInstance, ThemeEnrichAdapter, ThemeLoaderConfig } from "./types.js";
|
|
2
|
+
import { ResolverCtx } from "@shop-on/theme-contract";
|
|
3
|
+
//#region src/enrich.d.ts
|
|
4
|
+
/** Every section on the page — the template's own plus each section group's —
|
|
5
|
+
* in one flat list, the shape theme passes consume. */
|
|
6
|
+
export declare function flattenSections(template: LoadedTemplate): SectionInstance[];
|
|
7
|
+
/** Resolver context for one template: the theme's schema maps (which drive
|
|
8
|
+
* `schemaType` field discovery at both scopes), the block→section parent map
|
|
9
|
+
* arg policies read, and the set of blocks sitting under a disabled container
|
|
10
|
+
* at any depth. */
|
|
11
|
+
export declare function buildResolverCtx(cfg: ThemeLoaderConfig, template: unknown): Promise<ResolverCtx>;
|
|
12
|
+
export interface EnrichOptions {
|
|
13
|
+
locale?: string;
|
|
14
|
+
/** Editor session id — only used to build the request flags. */
|
|
15
|
+
sid?: string;
|
|
16
|
+
/** Route product for dynamic-source resolution; PDP loaders only. */
|
|
17
|
+
product?: EnrichRequest["product"];
|
|
18
|
+
/** Route collection for dynamic-source resolution; collection loaders only. */
|
|
19
|
+
collection?: EnrichRequest["collection"];
|
|
20
|
+
/** Route CMS page for dynamic-source resolution; page loaders only. */
|
|
21
|
+
page?: EnrichRequest["page"];
|
|
22
|
+
/** Route blog for dynamic-source resolution; blog-listing loaders only. */
|
|
23
|
+
blog?: EnrichRequest["blog"];
|
|
24
|
+
/** Route article for dynamic-source resolution; article loaders only. */
|
|
25
|
+
article?: EnrichRequest["article"];
|
|
26
|
+
}
|
|
27
|
+
/** Resolve every picker on `template` in place, and return the request flags
|
|
28
|
+
* the adapter resolved. The theme's server fn calls this once per request,
|
|
29
|
+
* after the template document is resolved.
|
|
30
|
+
*
|
|
31
|
+
* Returning the flags is what keeps `requestFlags` a SINGLE read: care's hook
|
|
32
|
+
* loads the theme-settings document to answer "are card swatches on?", and its
|
|
33
|
+
* route fetches need the same answer — the caller parks this value on the
|
|
34
|
+
* template rather than deriving it a second time. */
|
|
35
|
+
export declare function runEnrichment(cfg: ThemeLoaderConfig, template: LoadedTemplate, adapter: ThemeEnrichAdapter, opts?: EnrichOptions): Promise<EnrichRequest["flags"]>;
|
|
36
|
+
//#endregion
|
package/dist/enrich.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { loadBlockSchemas, loadSectionSchemas } from "./disk.js";
|
|
2
|
+
import { applyInlineSourceTokens, applyWholeValueBindings, loadMetaobjectFieldSources } from "./dynamic-sources.js";
|
|
3
|
+
import { resolveReviewLists, standardSpecs } from "./specs.js";
|
|
4
|
+
import { buildDisabledAncestorSet, buildParentMap, resolveField } from "@shop-on/theme-contract";
|
|
5
|
+
//#region src/enrich.ts
|
|
6
|
+
/** Every section on the page — the template's own plus each section group's —
|
|
7
|
+
* in one flat list, the shape theme passes consume. */
|
|
8
|
+
function flattenSections(template) {
|
|
9
|
+
const sections = Object.values(template.sections ?? {});
|
|
10
|
+
for (const group of Object.values(template.section_groups ?? {})) sections.push(...Object.values(group.sections ?? {}));
|
|
11
|
+
return sections;
|
|
12
|
+
}
|
|
13
|
+
/** Resolver context for one template: the theme's schema maps (which drive
|
|
14
|
+
* `schemaType` field discovery at both scopes), the block→section parent map
|
|
15
|
+
* arg policies read, and the set of blocks sitting under a disabled container
|
|
16
|
+
* at any depth. */
|
|
17
|
+
async function buildResolverCtx(cfg, template) {
|
|
18
|
+
const [blockSchemas, sectionSchemas] = await Promise.all([loadBlockSchemas(cfg.configDir), loadSectionSchemas(cfg.configDir)]);
|
|
19
|
+
return {
|
|
20
|
+
blockSchemas,
|
|
21
|
+
sectionSchemas,
|
|
22
|
+
parentOf: buildParentMap(template),
|
|
23
|
+
disabledAncestors: buildDisabledAncestorSet(template)
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/** Resolve every picker on `template` in place, and return the request flags
|
|
27
|
+
* the adapter resolved. The theme's server fn calls this once per request,
|
|
28
|
+
* after the template document is resolved.
|
|
29
|
+
*
|
|
30
|
+
* Returning the flags is what keeps `requestFlags` a SINGLE read: care's hook
|
|
31
|
+
* loads the theme-settings document to answer "are card swatches on?", and its
|
|
32
|
+
* route fetches need the same answer — the caller parks this value on the
|
|
33
|
+
* template rather than deriving it a second time. */
|
|
34
|
+
async function runEnrichment(cfg, template, adapter, opts = {}) {
|
|
35
|
+
const req = {
|
|
36
|
+
locale: opts.locale,
|
|
37
|
+
sid: opts.sid,
|
|
38
|
+
product: opts.product ?? null,
|
|
39
|
+
collection: opts.collection ?? null,
|
|
40
|
+
page: opts.page ?? null,
|
|
41
|
+
blog: opts.blog ?? null,
|
|
42
|
+
article: opts.article ?? null,
|
|
43
|
+
flags: await adapter.requestFlags?.(opts.sid, opts.locale) ?? {}
|
|
44
|
+
};
|
|
45
|
+
const sections = flattenSections(template);
|
|
46
|
+
const metaobjectFields = await loadMetaobjectFieldSources(template, req.product);
|
|
47
|
+
await applyWholeValueBindings(cfg, template, req.product, req.collection, metaobjectFields);
|
|
48
|
+
const resolverCtx = await buildResolverCtx(cfg, template);
|
|
49
|
+
const specCtx = {
|
|
50
|
+
req,
|
|
51
|
+
adapter,
|
|
52
|
+
disabledAncestors: resolverCtx.disabledAncestors
|
|
53
|
+
};
|
|
54
|
+
await Promise.all([
|
|
55
|
+
...standardSpecs(specCtx).map((spec) => resolveField(template, spec, resolverCtx)),
|
|
56
|
+
resolveReviewLists(template, resolverCtx, specCtx),
|
|
57
|
+
...(adapter.customPasses ?? []).map((pass) => pass(sections, req))
|
|
58
|
+
]);
|
|
59
|
+
await adapter.postPickers?.(sections, req);
|
|
60
|
+
applyInlineSourceTokens(template, req, metaobjectFields);
|
|
61
|
+
return req.flags;
|
|
62
|
+
}
|
|
63
|
+
//#endregion
|
|
64
|
+
export { buildResolverCtx, flattenSections, runEnrichment };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ArgOf, ArgPolicy, BlogArg, CollectionArg, DynamicSourceArticle, DynamicSourceBlog, DynamicSourceCollection, DynamicSourcePage, EnrichOverride, EnrichPass, EnrichRequest, FileArg, LoadedTemplate, LocaleArg, PickerArgs, PickerType, ProductArg, SKIP, SectionGroupJson, SectionInstance, Skip, TemplateJson, ThemeArgPolicies, ThemeEnrichAdapter, ThemeLoaderConfig, Transform } from "./types.js";
|
|
2
|
+
import { SAFE_TEMPLATE_ID, buildSectionInstance, loadBlockSchemas, loadPreviewLocale, loadSectionGroupsForLayout, loadSectionSchemaJson, loadSectionSchemas, loadSettingsSchema, loadTemplate, loadThemeSettings, resolveTranslations } from "./disk.js";
|
|
3
|
+
import { DynamicSourceProduct, DynamicSourceRoute, MetaobjectFieldSources, applyInlineSourceTokens, applyWholeValueBindings, loadMetaobjectFieldSources, overlayTemplateTranslations, overlayThemeSettingsTranslations } from "./dynamic-sources.js";
|
|
4
|
+
import { EnrichOptions, buildResolverCtx, flattenSections, runEnrichment } from "./enrich.js";
|
|
5
|
+
import { buildOrchestratorUrl, fetchPreviewSettings, fetchPreviewTemplate, previewEnabled } from "./orchestrator.js";
|
|
6
|
+
import { loadRawThemeSettings, loadTemplateForRequest, resolveTemplateVariant } from "./template-request.js";
|
|
7
|
+
import { AnySpec, SpecContext, resolveReviewLists, standardSpecs } from "./specs.js";
|
|
8
|
+
import { enrichThemeSettings } from "./theme-settings.js";
|
|
9
|
+
import { SectionPreviewInput, SectionPreviewOptions, buildSectionPreviewTemplate } from "./section-preview.js";
|
|
10
|
+
import { SeoContext, blogPerPage, buildBreadcrumb, collectionPerPage, emptySearchPage, seoContext } from "./seo.js";
|
|
11
|
+
export { type AnySpec, type ArgOf, type ArgPolicy, type BlogArg, type CollectionArg, type DynamicSourceArticle, type DynamicSourceBlog, type DynamicSourceCollection, type DynamicSourcePage, type DynamicSourceProduct, type DynamicSourceRoute, type EnrichOptions, type EnrichOverride, type EnrichPass, type EnrichRequest, type FileArg, type LoadedTemplate, type LocaleArg, type MetaobjectFieldSources, type PickerArgs, type PickerType, type ProductArg, SAFE_TEMPLATE_ID, SKIP, type SectionGroupJson, type SectionInstance, type SectionPreviewInput, type SectionPreviewOptions, type SeoContext, type Skip, type SpecContext, type TemplateJson, type ThemeArgPolicies, type ThemeEnrichAdapter, type ThemeLoaderConfig, type Transform, applyInlineSourceTokens, applyWholeValueBindings, blogPerPage, buildBreadcrumb, buildOrchestratorUrl, buildResolverCtx, buildSectionInstance, buildSectionPreviewTemplate, collectionPerPage, emptySearchPage, enrichThemeSettings, fetchPreviewSettings, fetchPreviewTemplate, flattenSections, loadBlockSchemas, loadMetaobjectFieldSources, loadPreviewLocale, loadRawThemeSettings, loadSectionGroupsForLayout, loadSectionSchemaJson, loadSectionSchemas, loadSettingsSchema, loadTemplate, loadTemplateForRequest, loadThemeSettings, overlayTemplateTranslations, overlayThemeSettingsTranslations, previewEnabled, resolveReviewLists, resolveTemplateVariant, resolveTranslations, runEnrichment, seoContext, standardSpecs };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { SKIP } from "./types.js";
|
|
2
|
+
import { SAFE_TEMPLATE_ID, buildSectionInstance, loadBlockSchemas, loadPreviewLocale, loadSectionGroupsForLayout, loadSectionSchemaJson, loadSectionSchemas, loadSettingsSchema, loadTemplate, loadThemeSettings, resolveTranslations } from "./disk.js";
|
|
3
|
+
import { buildOrchestratorUrl, fetchPreviewSettings, fetchPreviewTemplate, previewEnabled } from "./orchestrator.js";
|
|
4
|
+
import { applyInlineSourceTokens, applyWholeValueBindings, loadMetaobjectFieldSources, overlayTemplateTranslations, overlayThemeSettingsTranslations } from "./dynamic-sources.js";
|
|
5
|
+
import { loadRawThemeSettings, loadTemplateForRequest, resolveTemplateVariant } from "./template-request.js";
|
|
6
|
+
import { resolveReviewLists, standardSpecs } from "./specs.js";
|
|
7
|
+
import { buildResolverCtx, flattenSections, runEnrichment } from "./enrich.js";
|
|
8
|
+
import { enrichThemeSettings } from "./theme-settings.js";
|
|
9
|
+
import { buildSectionPreviewTemplate } from "./section-preview.js";
|
|
10
|
+
import { blogPerPage, buildBreadcrumb, collectionPerPage, emptySearchPage, seoContext } from "./seo.js";
|
|
11
|
+
export { SAFE_TEMPLATE_ID, SKIP, applyInlineSourceTokens, applyWholeValueBindings, blogPerPage, buildBreadcrumb, buildOrchestratorUrl, buildResolverCtx, buildSectionInstance, buildSectionPreviewTemplate, collectionPerPage, emptySearchPage, enrichThemeSettings, fetchPreviewSettings, fetchPreviewTemplate, flattenSections, loadBlockSchemas, loadMetaobjectFieldSources, loadPreviewLocale, loadRawThemeSettings, loadSectionGroupsForLayout, loadSectionSchemaJson, loadSectionSchemas, loadSettingsSchema, loadTemplate, loadTemplateForRequest, loadThemeSettings, overlayTemplateTranslations, overlayThemeSettingsTranslations, previewEnabled, resolveReviewLists, resolveTemplateVariant, resolveTranslations, runEnrichment, seoContext, standardSpecs };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { LoadedTemplate, ThemeLoaderConfig } from "./types.js";
|
|
2
|
+
//#region src/orchestrator.d.ts
|
|
3
|
+
export declare function buildOrchestratorUrl(cfg: ThemeLoaderConfig, endpoint: string, query: Record<string, string>): string;
|
|
4
|
+
/** True when the preview endpoints are addressable at all. */
|
|
5
|
+
export declare function previewEnabled(cfg: ThemeLoaderConfig, sid: string | undefined): sid is string;
|
|
6
|
+
export declare function fetchPreviewTemplate(cfg: ThemeLoaderConfig, sid: string, templateId: string): Promise<LoadedTemplate | null>;
|
|
7
|
+
export declare function fetchPreviewSettings(cfg: ThemeLoaderConfig, sid: string): Promise<Record<string, unknown> | null>;
|
|
8
|
+
//#endregion
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
//#region src/orchestrator.ts
|
|
2
|
+
function buildOrchestratorUrl(cfg, endpoint, query) {
|
|
3
|
+
const url = new URL(`${cfg.orchestratorUrl}/api/stores/${cfg.storeSlug}/${cfg.installId}/${endpoint}`);
|
|
4
|
+
for (const [k, v] of Object.entries(query)) url.searchParams.set(k, v);
|
|
5
|
+
return url.toString();
|
|
6
|
+
}
|
|
7
|
+
/** True when the preview endpoints are addressable at all. */
|
|
8
|
+
function previewEnabled(cfg, sid) {
|
|
9
|
+
return Boolean(sid && cfg.storeSlug && cfg.installId);
|
|
10
|
+
}
|
|
11
|
+
async function fetchPreviewTemplate(cfg, sid, templateId) {
|
|
12
|
+
try {
|
|
13
|
+
const res = await fetch(buildOrchestratorUrl(cfg, "template", {
|
|
14
|
+
sid,
|
|
15
|
+
templateId
|
|
16
|
+
}));
|
|
17
|
+
if (!res.ok) return null;
|
|
18
|
+
return await res.json();
|
|
19
|
+
} catch (err) {
|
|
20
|
+
console.warn("[fetchPreviewTemplate] failed:", err);
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
async function fetchPreviewSettings(cfg, sid) {
|
|
25
|
+
try {
|
|
26
|
+
const res = await fetch(buildOrchestratorUrl(cfg, "settings", { sid }));
|
|
27
|
+
if (!res.ok) return null;
|
|
28
|
+
return await res.json();
|
|
29
|
+
} catch (err) {
|
|
30
|
+
console.warn("[fetchPreviewSettings] failed:", err);
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
//#endregion
|
|
35
|
+
export { buildOrchestratorUrl, fetchPreviewSettings, fetchPreviewTemplate, previewEnabled };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ThemeLoaderConfig } from "./types.js";
|
|
2
|
+
//#region src/section-preview.d.ts
|
|
3
|
+
export interface SectionPreviewInput {
|
|
4
|
+
type?: string;
|
|
5
|
+
preset?: number;
|
|
6
|
+
block?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface SectionPreviewOptions {
|
|
9
|
+
/** Block type → the section type it previews inside, so a block renders with
|
|
10
|
+
* a real host section's chrome and context. A block with no host entry is not
|
|
11
|
+
* previewable. */
|
|
12
|
+
hosts: Record<string, string>;
|
|
13
|
+
/** Preview-only settings for blocks that would otherwise render blank on
|
|
14
|
+
* schema defaults. Merged onto the seeded preview instance only — never onto
|
|
15
|
+
* real merchant defaults. */
|
|
16
|
+
seedSettings?: Record<string, Record<string, unknown>>;
|
|
17
|
+
}
|
|
18
|
+
export declare function buildSectionPreviewTemplate(cfg: ThemeLoaderConfig, { type, preset, block }: SectionPreviewInput, { hosts, seedSettings }: SectionPreviewOptions): Promise<any>;
|
|
19
|
+
//#endregion
|