@shoppexio/builder-contracts 0.1.0 → 0.1.2
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/builder-settings.d.ts +11 -666
- package/dist/builder-settings.d.ts.map +1 -1
- package/dist/builder-settings.js +2 -1
- package/dist/canonical-settings.d.ts +18 -0
- package/dist/canonical-settings.d.ts.map +1 -0
- package/dist/canonical-settings.js +106 -0
- package/dist/custom-pages.d.ts +15 -0
- package/dist/custom-pages.d.ts.map +1 -0
- package/dist/custom-pages.js +40 -0
- package/dist/dedicated-pages.d.ts +15 -0
- package/dist/dedicated-pages.d.ts.map +1 -0
- package/dist/dedicated-pages.js +142 -0
- package/dist/events.d.ts +35 -240
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +7 -0
- package/dist/fields.d.ts +229 -10
- package/dist/fields.d.ts.map +1 -1
- package/dist/fields.js +27 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -0
- package/dist/legacy-manifest.d.ts +18 -0
- package/dist/legacy-manifest.d.ts.map +1 -1
- package/dist/legacy-manifest.js +137 -22
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +55 -6
- package/dist/persistence.d.ts +7 -0
- package/dist/persistence.d.ts.map +1 -0
- package/dist/persistence.js +58 -0
- package/dist/preview-boot.d.ts +68 -0
- package/dist/preview-boot.d.ts.map +1 -0
- package/dist/preview-boot.js +38 -0
- package/dist/preview-protocol.d.ts +227 -459
- package/dist/preview-protocol.d.ts.map +1 -1
- package/dist/preview-protocol.js +112 -0
- package/dist/preview-session-resolve.d.ts +115 -0
- package/dist/preview-session-resolve.d.ts.map +1 -0
- package/dist/preview-session-resolve.js +25 -0
- package/dist/preview-trusted-origins.d.ts +4 -0
- package/dist/preview-trusted-origins.d.ts.map +1 -0
- package/dist/preview-trusted-origins.js +28 -0
- package/dist/storefront-initial-data-html.d.ts +17 -0
- package/dist/storefront-initial-data-html.d.ts.map +1 -0
- package/dist/storefront-initial-data-html.js +83 -0
- package/dist/storefront-typography-fonts.d.ts +18 -0
- package/dist/storefront-typography-fonts.d.ts.map +1 -0
- package/dist/storefront-typography-fonts.js +89 -0
- package/dist/style-slots.d.ts +50 -152
- package/dist/style-slots.d.ts.map +1 -1
- package/dist/style-slots.js +80 -32
- package/dist/theme-manifest.d.ts +287 -456
- package/dist/theme-manifest.d.ts.map +1 -1
- package/dist/theme-manifest.js +92 -0
- package/dist/theme-schemes.d.ts +10 -0
- package/dist/theme-schemes.d.ts.map +1 -0
- package/dist/theme-schemes.js +25 -0
- package/dist/validation.d.ts +1 -1
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +23 -12
- package/package.json +43 -1
- package/src/builder-contracts.test.ts +416 -3
- package/src/builder-settings.ts +4 -1
- package/src/canonical-settings.ts +156 -0
- package/src/custom-pages.test.ts +74 -0
- package/src/custom-pages.ts +70 -0
- package/src/dedicated-pages.test.ts +88 -0
- package/src/dedicated-pages.ts +173 -0
- package/src/events.ts +8 -0
- package/src/fields.ts +30 -0
- package/src/index.ts +10 -0
- package/src/legacy-manifest.ts +147 -23
- package/src/migrations.ts +70 -6
- package/src/persistence.ts +77 -0
- package/src/preview-boot.test.ts +72 -0
- package/src/preview-boot.ts +49 -0
- package/src/preview-protocol.test.ts +132 -0
- package/src/preview-protocol.ts +122 -0
- package/src/preview-session-resolve.test.ts +37 -0
- package/src/preview-session-resolve.ts +34 -0
- package/src/preview-trusted-origins.test.ts +24 -0
- package/src/preview-trusted-origins.ts +35 -0
- package/src/storefront-initial-data-html.test.ts +73 -0
- package/src/storefront-initial-data-html.ts +112 -0
- package/src/storefront-typography-fonts.test.ts +48 -0
- package/src/storefront-typography-fonts.ts +108 -0
- package/src/style-slots.ts +102 -34
- package/src/theme-manifest.ts +118 -1
- package/src/theme-schemes.ts +34 -0
- package/src/validation.ts +32 -13
- package/dist/builder-contracts.test.d.ts +0 -2
- package/dist/builder-contracts.test.d.ts.map +0 -1
- package/dist/builder-contracts.test.js +0 -361
package/dist/legacy-manifest.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as z from 'zod/v4';
|
|
2
|
+
import { createDedicatedPageBlockType } from "./dedicated-pages.js";
|
|
2
3
|
import { BuilderFieldSchema } from "./fields.js";
|
|
3
4
|
import { ThemeManifestSchema } from "./theme-manifest.js";
|
|
5
|
+
import { StyleSlotDefaultsSchema } from "./style-slots.js";
|
|
4
6
|
const LegacyFieldOptionSchema = z
|
|
5
7
|
.object({
|
|
6
8
|
label: z.string().min(1),
|
|
@@ -71,6 +73,17 @@ export const LegacyThemeManifestSchema = z
|
|
|
71
73
|
id: z.string().min(1),
|
|
72
74
|
name: z.string().min(1),
|
|
73
75
|
version: z.string().min(1),
|
|
76
|
+
author: z.string().min(1).optional(),
|
|
77
|
+
description: z.string().min(1).optional(),
|
|
78
|
+
preview: z.string().min(1).optional(),
|
|
79
|
+
features: z.array(z.string().min(1)).optional(),
|
|
80
|
+
techStack: z.array(z.string().min(1)).optional(),
|
|
81
|
+
templates: z.array(z.string().min(1)).optional(),
|
|
82
|
+
createdAt: z.string().min(1).optional(),
|
|
83
|
+
demoUrl: z.string().min(1).optional(),
|
|
84
|
+
audience: z.string().min(1).optional(),
|
|
85
|
+
tags: z.array(z.string().min(1)).optional(),
|
|
86
|
+
hotfixPaths: z.array(z.string().min(1)).optional(),
|
|
74
87
|
presets: z.array(LegacyThemePresetSchema).optional(),
|
|
75
88
|
builder: z
|
|
76
89
|
.object({
|
|
@@ -82,6 +95,10 @@ export const LegacyThemeManifestSchema = z
|
|
|
82
95
|
})
|
|
83
96
|
.passthrough();
|
|
84
97
|
export function convertLegacyThemeManifest(input) {
|
|
98
|
+
const canonical = ThemeManifestSchema.safeParse(input);
|
|
99
|
+
if (canonical.success) {
|
|
100
|
+
return canonical.data;
|
|
101
|
+
}
|
|
85
102
|
const legacy = LegacyThemeManifestSchema.parse(input);
|
|
86
103
|
const blocks = Object.fromEntries(Object.entries(legacy.builder.blocks).map(([blockType, block]) => [
|
|
87
104
|
blockType,
|
|
@@ -95,7 +112,7 @@ export function convertLegacyThemeManifest(input) {
|
|
|
95
112
|
},
|
|
96
113
|
]));
|
|
97
114
|
for (const page of legacy.builder.pages) {
|
|
98
|
-
const syntheticBlockType =
|
|
115
|
+
const syntheticBlockType = createDedicatedPageBlockType(page.id);
|
|
99
116
|
if ((page.fields?.length ?? 0) === 0 && (page.lists?.length ?? 0) === 0) {
|
|
100
117
|
continue;
|
|
101
118
|
}
|
|
@@ -112,8 +129,19 @@ export function convertLegacyThemeManifest(input) {
|
|
|
112
129
|
id: normalizeLegacyThemeId(legacy.id),
|
|
113
130
|
name: legacy.name,
|
|
114
131
|
version: legacy.version,
|
|
132
|
+
author: legacy.author,
|
|
133
|
+
description: legacy.description,
|
|
134
|
+
preview: legacy.preview,
|
|
135
|
+
features: legacy.features,
|
|
136
|
+
techStack: legacy.techStack,
|
|
137
|
+
templates: legacy.templates,
|
|
138
|
+
createdAt: legacy.createdAt,
|
|
139
|
+
demoUrl: legacy.demoUrl,
|
|
140
|
+
audience: legacy.audience,
|
|
141
|
+
tags: legacy.tags,
|
|
142
|
+
hotfixPaths: legacy.hotfixPaths,
|
|
115
143
|
pages: Object.fromEntries(legacy.builder.pages.map((page) => {
|
|
116
|
-
const syntheticBlockType =
|
|
144
|
+
const syntheticBlockType = createDedicatedPageBlockType(page.id);
|
|
117
145
|
const hasSyntheticBlock = blocks[syntheticBlockType] !== undefined;
|
|
118
146
|
const allowedBlocks = page.blocks.length > 0
|
|
119
147
|
? page.blocks
|
|
@@ -137,6 +165,8 @@ export function convertLegacyThemeManifest(input) {
|
|
|
137
165
|
blocks,
|
|
138
166
|
styleSlots: {},
|
|
139
167
|
presets: convertLegacyThemePresets(legacy.presets ?? []),
|
|
168
|
+
linkGroups: Array.isArray(legacy.builder.linkGroups) ? legacy.builder.linkGroups : [],
|
|
169
|
+
defaultLinkItems: isPlainObject(legacy.builder.defaultLinkItems) ? legacy.builder.defaultLinkItems : {},
|
|
140
170
|
};
|
|
141
171
|
return ThemeManifestSchema.parse(converted);
|
|
142
172
|
}
|
|
@@ -147,21 +177,21 @@ function normalizeLegacyThemeId(rawId) {
|
|
|
147
177
|
}
|
|
148
178
|
return `legacy-${lowered || 'theme'}`;
|
|
149
179
|
}
|
|
150
|
-
function createSyntheticPageBlockType(pageId) {
|
|
151
|
-
const normalizedPageId = pageId.toLowerCase().replace(/[^a-z0-9\-_.]+/g, '-').replace(/^[-_.]+/, '');
|
|
152
|
-
return `page-${normalizedPageId || 'custom'}`;
|
|
153
|
-
}
|
|
154
180
|
function convertLegacyThemePresets(presets) {
|
|
155
|
-
return Object.fromEntries(presets.map((preset) =>
|
|
156
|
-
preset.
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
181
|
+
return Object.fromEntries(presets.map((preset) => {
|
|
182
|
+
const styleSlots = StyleSlotDefaultsSchema.parse(preset.overrides?.style_slots ?? preset.overrides?.styleSlots ?? {});
|
|
183
|
+
return [
|
|
184
|
+
preset.id,
|
|
185
|
+
{
|
|
186
|
+
label: preset.name ?? preset.label ?? preset.id,
|
|
187
|
+
description: preset.description,
|
|
188
|
+
preview: typeof preset.preview === 'string' ? preset.preview : undefined,
|
|
189
|
+
content: flattenContentRecord(preset.overrides?.content ?? {}),
|
|
190
|
+
layout: preset.overrides?.layout ?? {},
|
|
191
|
+
style_slots: styleSlots,
|
|
192
|
+
},
|
|
193
|
+
];
|
|
194
|
+
}));
|
|
165
195
|
}
|
|
166
196
|
function flattenContentRecord(content, prefix = '') {
|
|
167
197
|
const flattened = {};
|
|
@@ -180,21 +210,59 @@ function isPlainObject(value) {
|
|
|
180
210
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
181
211
|
}
|
|
182
212
|
function convertLegacyFields(fields, lists) {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
213
|
+
// Auto-promote inline `type: 'list'` fields to first-class list entries so
|
|
214
|
+
// the migrator can emit list-shaped data without separately maintaining a
|
|
215
|
+
// `lists: []` block-level entry. The promoted list inherits its kind from
|
|
216
|
+
// the field path (e.g. `header.left_links` → kind `navLinks`).
|
|
217
|
+
const inlineLists = [];
|
|
218
|
+
const remainingFields = [];
|
|
219
|
+
for (const field of fields) {
|
|
220
|
+
if (field.type === 'list') {
|
|
221
|
+
inlineLists.push({
|
|
222
|
+
path: field.path,
|
|
223
|
+
label: field.label,
|
|
224
|
+
kind: inferListKindFromPath(field.path),
|
|
225
|
+
description: field.description,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
remainingFields.push(field);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
const convertedFields = Object.fromEntries(remainingFields.map((field) => [field.path, convertLegacyField(field)]));
|
|
233
|
+
const allLists = [...inlineLists, ...lists];
|
|
234
|
+
const convertedLists = Object.fromEntries(allLists.map((list) => {
|
|
235
|
+
const inlineField = fields.find((f) => f.type === 'list' && f.path === list.path);
|
|
236
|
+
const candidate = {
|
|
187
237
|
type: 'list',
|
|
188
238
|
label: list.label,
|
|
189
239
|
description: list.description,
|
|
190
240
|
itemShape: createListItemShape(list.kind),
|
|
191
|
-
|
|
192
|
-
|
|
241
|
+
...(inlineField?.defaultValue !== undefined ? { defaultValue: inlineField.defaultValue } : {}),
|
|
242
|
+
};
|
|
243
|
+
return [list.path, candidate];
|
|
244
|
+
}));
|
|
193
245
|
return {
|
|
194
246
|
...convertedFields,
|
|
195
247
|
...convertedLists,
|
|
196
248
|
};
|
|
197
249
|
}
|
|
250
|
+
function inferListKindFromPath(path) {
|
|
251
|
+
const suffix = path.split('.').at(-1) ?? '';
|
|
252
|
+
if (/items$/i.test(suffix))
|
|
253
|
+
return 'faqItems';
|
|
254
|
+
if (/links$/i.test(suffix))
|
|
255
|
+
return 'navLinks';
|
|
256
|
+
if (/images$/i.test(suffix) || /gallery$/i.test(suffix))
|
|
257
|
+
return 'galleryItems';
|
|
258
|
+
if (/features$/i.test(suffix))
|
|
259
|
+
return 'featureItems';
|
|
260
|
+
if (/reviews$/i.test(suffix) || /testimonials$/i.test(suffix))
|
|
261
|
+
return 'reviewItems';
|
|
262
|
+
if (/announcements$/i.test(suffix))
|
|
263
|
+
return 'announcementItems';
|
|
264
|
+
return 'genericItems';
|
|
265
|
+
}
|
|
198
266
|
function convertLegacyField(field) {
|
|
199
267
|
const type = normalizeLegacyFieldType(field.type);
|
|
200
268
|
const base = {
|
|
@@ -265,8 +333,55 @@ function createListItemShape(kind) {
|
|
|
265
333
|
author: { type: 'text', label: 'Author' },
|
|
266
334
|
};
|
|
267
335
|
}
|
|
336
|
+
if (kind === 'navLinks') {
|
|
337
|
+
return {
|
|
338
|
+
text: { type: 'text', label: 'Text' },
|
|
339
|
+
link: { type: 'link', label: 'Link' },
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
if (kind === 'featureItems') {
|
|
343
|
+
return {
|
|
344
|
+
title: { type: 'text', label: 'Title' },
|
|
345
|
+
description: { type: 'richtext', label: 'Description' },
|
|
346
|
+
icon: { type: 'text', label: 'Icon' },
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
if (kind === 'announcementItems') {
|
|
350
|
+
return {
|
|
351
|
+
text: { type: 'text', label: 'Text' },
|
|
352
|
+
link: { type: 'link', label: 'Link' },
|
|
353
|
+
};
|
|
354
|
+
}
|
|
268
355
|
return {
|
|
269
356
|
title: { type: 'text', label: 'Title' },
|
|
270
357
|
body: { type: 'richtext', label: 'Body' },
|
|
271
358
|
};
|
|
272
359
|
}
|
|
360
|
+
function resolveManifestPagesForBlockOrder(manifest) {
|
|
361
|
+
const canonical = ThemeManifestSchema.safeParse(manifest);
|
|
362
|
+
if (canonical.success) {
|
|
363
|
+
return canonical.data.pages;
|
|
364
|
+
}
|
|
365
|
+
try {
|
|
366
|
+
return convertLegacyThemeManifest(manifest).pages;
|
|
367
|
+
}
|
|
368
|
+
catch {
|
|
369
|
+
if (typeof manifest === 'object' && manifest !== null && 'pages' in manifest) {
|
|
370
|
+
const pages = manifest.pages;
|
|
371
|
+
if (typeof pages === 'object' && pages !== null && !Array.isArray(pages)) {
|
|
372
|
+
return pages;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
return {};
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
export function getThemePageBlockOrderFromManifest(manifest, pageId) {
|
|
379
|
+
const page = resolveManifestPagesForBlockOrder(manifest)[pageId];
|
|
380
|
+
if (!page) {
|
|
381
|
+
return [];
|
|
382
|
+
}
|
|
383
|
+
const defaultBlockTypes = (Array.isArray(page.defaultBlocks) ? page.defaultBlocks : [])
|
|
384
|
+
.map((block) => (typeof block === 'object' && block !== null ? block.type : undefined))
|
|
385
|
+
.filter((blockType) => typeof blockType === 'string' && blockType.length > 0);
|
|
386
|
+
return defaultBlockTypes.length > 0 ? defaultBlockTypes : page.allowedBlocks ?? [];
|
|
387
|
+
}
|
package/dist/migrations.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../src/migrations.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../src/migrations.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,aAAa,EAClB,KAAK,eAAe,EAEpB,KAAK,UAAU,EAChB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,MAAM,MAAM,0BAA0B,GAAG;IACvC,KAAK,CAAC,EAAE;QACN,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAClC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACjC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAC1C,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACvC,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,wBAAgB,0BAA0B,CAAC,QAAQ,SAAI,GAAG,eAAe,CAYxE;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE;IACzC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,eAAe,CAAC,EAAE,UAAU,CAAC;CAC9B,GAAG,aAAa,CAShB;AAED,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,UAAU,CAE9E;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,0BAA0B,EAAE,QAAQ,SAAI,GAAG,eAAe,CAoB7G;AAED,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,eAAe,EAAE,QAAQ,EAAE,aAAa,GAAG,eAAe,CAwB9G;AAED,wBAAgB,mCAAmC,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,UAAU,CAuC/F"}
|
package/dist/migrations.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { migrateDedicatedPageContent, migrateDedicatedPageLayout } from "./dedicated-pages.js";
|
|
1
2
|
import { BUILDER_SETTINGS_VERSION, BuilderSettingsSchema, } from "./builder-settings.js";
|
|
3
|
+
import { sanitizeBuilderSettingsState } from "./persistence.js";
|
|
2
4
|
export function createEmptyBuilderSettings(revision = 0) {
|
|
3
5
|
return {
|
|
4
6
|
version: BUILDER_SETTINGS_VERSION,
|
|
@@ -31,17 +33,19 @@ export function migrateLegacyBuilderSettings(input, revision = 0) {
|
|
|
31
33
|
...mapLegacyTokenOverridesToStyleSlots(tokensOverride),
|
|
32
34
|
...(input.theme?.style_slots ?? {}),
|
|
33
35
|
};
|
|
34
|
-
|
|
36
|
+
const rawContent = input.theme?.content ?? {};
|
|
37
|
+
const rawLayout = input.theme?.layout ?? {};
|
|
38
|
+
return sanitizeBuilderSettingsState(BuilderSettingsSchema.parse({
|
|
35
39
|
version: BUILDER_SETTINGS_VERSION,
|
|
36
40
|
revision,
|
|
37
41
|
theme: {
|
|
38
|
-
content:
|
|
39
|
-
layout: normalizeLegacyLayout(
|
|
42
|
+
content: migrateDedicatedPageContent(rawContent),
|
|
43
|
+
layout: migrateDedicatedPageLayout(normalizeLegacyLayout(rawLayout)),
|
|
40
44
|
style_slots: styleSlots,
|
|
41
45
|
pages: [],
|
|
42
46
|
terms: {},
|
|
43
47
|
},
|
|
44
|
-
});
|
|
48
|
+
}));
|
|
45
49
|
}
|
|
46
50
|
export function applyManifestDefaultLayout(settings, manifest) {
|
|
47
51
|
const next = cloneBuilderSettings(settings);
|
|
@@ -68,13 +72,33 @@ export function applyManifestDefaultLayout(settings, manifest) {
|
|
|
68
72
|
export function mapLegacyTokenOverridesToStyleSlots(tokens) {
|
|
69
73
|
const slots = {};
|
|
70
74
|
assignResponsiveNumber(tokens, slots, 'buttons.borderRadius', 'button.radius');
|
|
75
|
+
assignColor(tokens, slots, 'buttons.background', 'button.background');
|
|
76
|
+
assignColor(tokens, slots, 'buttons.foreground', 'button.foreground');
|
|
77
|
+
assignColor(tokens, slots, 'buttons.border', 'button.border');
|
|
78
|
+
assignFontWeight(tokens, slots, 'buttons.fontWeight', 'button.font.weight');
|
|
71
79
|
assignResponsiveNumber(tokens, slots, 'inputs.borderRadius', 'input.radius');
|
|
72
80
|
assignResponsiveNumber(tokens, slots, 'inputs.height', 'input.height');
|
|
81
|
+
assignColor(tokens, slots, 'inputs.border', 'input.border');
|
|
82
|
+
assignColor(tokens, slots, 'inputs.background', 'input.background');
|
|
83
|
+
assignColor(tokens, slots, 'inputs.foreground', 'input.foreground');
|
|
84
|
+
assignResponsiveNumber(tokens, slots, 'cards.borderRadius', 'card.radius');
|
|
85
|
+
assignColor(tokens, slots, 'cards.background', 'card.background');
|
|
86
|
+
assignColor(tokens, slots, 'cards.border', 'card.border');
|
|
87
|
+
assignResponsiveNumber(tokens, slots, 'sections.paddingY', 'section.padding.y');
|
|
88
|
+
assignResponsiveNumber(tokens, slots, 'sections.paddingX', 'section.padding.x');
|
|
89
|
+
assignResponsiveNumber(tokens, slots, 'container.width', 'container.width');
|
|
73
90
|
assignColor(tokens, slots, 'colors.primary', 'color.primary');
|
|
74
91
|
assignColor(tokens, slots, 'colors.accent', 'color.accent');
|
|
75
92
|
assignColor(tokens, slots, 'colors.background', 'color.background');
|
|
76
93
|
assignColor(tokens, slots, 'colors.foreground', 'color.foreground');
|
|
77
94
|
assignColor(tokens, slots, 'colors.muted', 'color.muted');
|
|
95
|
+
assignColor(tokens, slots, 'links.color', 'link.color');
|
|
96
|
+
assignFontWeight(tokens, slots, 'typography.headingWeight', 'typography.heading.weight');
|
|
97
|
+
assignResponsiveNumber(tokens, slots, 'typography.fontSize', 'typography.body.size');
|
|
98
|
+
assignResponsiveNumber(tokens, slots, 'typography.bodySize', 'typography.body.size');
|
|
99
|
+
assignString(tokens, slots, 'typography.fontFamily', 'theme.typography.font.family');
|
|
100
|
+
assignString(tokens, slots, 'typography.headingFont', 'theme.typography.heading.font');
|
|
101
|
+
assignResponsiveNumber(tokens, slots, 'header.height', 'theme.header.height');
|
|
78
102
|
return slots;
|
|
79
103
|
}
|
|
80
104
|
function normalizeLegacyLayout(layout) {
|
|
@@ -128,7 +152,7 @@ function isRecord(value) {
|
|
|
128
152
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
129
153
|
}
|
|
130
154
|
function assignResponsiveNumber(tokens, slots, tokenPath, slotId) {
|
|
131
|
-
const value = tokens
|
|
155
|
+
const value = readLegacyTokenPath(tokens, tokenPath);
|
|
132
156
|
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
133
157
|
slots[slotId] = { base: value };
|
|
134
158
|
return;
|
|
@@ -138,11 +162,36 @@ function assignResponsiveNumber(tokens, slots, tokenPath, slotId) {
|
|
|
138
162
|
}
|
|
139
163
|
}
|
|
140
164
|
function assignColor(tokens, slots, tokenPath, slotId) {
|
|
141
|
-
const value = tokens
|
|
165
|
+
const value = readLegacyTokenPath(tokens, tokenPath);
|
|
142
166
|
if (typeof value === 'string') {
|
|
143
167
|
slots[slotId] = value;
|
|
144
168
|
}
|
|
145
169
|
}
|
|
170
|
+
function assignString(tokens, slots, tokenPath, slotId) {
|
|
171
|
+
const value = readLegacyTokenPath(tokens, tokenPath);
|
|
172
|
+
if (typeof value === 'string' && value.trim().length > 0) {
|
|
173
|
+
slots[slotId] = value;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
function assignFontWeight(tokens, slots, tokenPath, slotId) {
|
|
177
|
+
const value = readLegacyTokenPath(tokens, tokenPath);
|
|
178
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
179
|
+
slots[slotId] = value;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
function readLegacyTokenPath(tokens, tokenPath) {
|
|
183
|
+
if (Object.prototype.hasOwnProperty.call(tokens, tokenPath)) {
|
|
184
|
+
return tokens[tokenPath];
|
|
185
|
+
}
|
|
186
|
+
let current = tokens;
|
|
187
|
+
for (const segment of tokenPath.split('.')) {
|
|
188
|
+
if (!isRecord(current)) {
|
|
189
|
+
return undefined;
|
|
190
|
+
}
|
|
191
|
+
current = current[segment];
|
|
192
|
+
}
|
|
193
|
+
return current;
|
|
194
|
+
}
|
|
146
195
|
function isResponsiveNumber(value) {
|
|
147
196
|
if (!value || typeof value !== 'object') {
|
|
148
197
|
return false;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type BuilderSettings } from './builder-settings.ts';
|
|
2
|
+
type JsonRecord = Record<string, unknown>;
|
|
3
|
+
export declare function extractPersistedBuilderSettings(input: unknown): BuilderSettings | null;
|
|
4
|
+
export declare function sanitizeBuilderSettingsState(settings: BuilderSettings): BuilderSettings;
|
|
5
|
+
export declare function mergeBuilderSettingsIntoThemeSettings(currentSettings: unknown, builderSettings: BuilderSettings): JsonRecord;
|
|
6
|
+
export {};
|
|
7
|
+
//# sourceMappingURL=persistence.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"persistence.d.ts","sourceRoot":"","sources":["../src/persistence.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,eAAe,EAErB,MAAM,uBAAuB,CAAC;AAE/B,KAAK,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAmB1C,wBAAgB,+BAA+B,CAAC,KAAK,EAAE,OAAO,GAAG,eAAe,GAAG,IAAI,CAYtF;AAED,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,eAAe,GAAG,eAAe,CASvF;AAaD,wBAAgB,qCAAqC,CACnD,eAAe,EAAE,OAAO,EACxB,eAAe,EAAE,eAAe,GAC/B,UAAU,CAaZ"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { BuilderSettingsSchema, } from "./builder-settings.js";
|
|
2
|
+
const RESERVED_THEME_CONTENT_KEYS = new Set(['layout']);
|
|
3
|
+
function isRecord(value) {
|
|
4
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
5
|
+
}
|
|
6
|
+
function pickBuilderSettingsFields(value) {
|
|
7
|
+
if (!isRecord(value)) {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
return {
|
|
11
|
+
version: value.version,
|
|
12
|
+
revision: value.revision,
|
|
13
|
+
theme: value.theme,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export function extractPersistedBuilderSettings(input) {
|
|
17
|
+
const parsedDirect = BuilderSettingsSchema.safeParse(pickBuilderSettingsFields(input));
|
|
18
|
+
if (parsedDirect.success) {
|
|
19
|
+
return sanitizeBuilderSettingsState(parsedDirect.data);
|
|
20
|
+
}
|
|
21
|
+
if (!isRecord(input)) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
const parsedNested = BuilderSettingsSchema.safeParse(pickBuilderSettingsFields(input.builder_settings));
|
|
25
|
+
return parsedNested.success ? sanitizeBuilderSettingsState(parsedNested.data) : null;
|
|
26
|
+
}
|
|
27
|
+
export function sanitizeBuilderSettingsState(settings) {
|
|
28
|
+
const content = sanitizeThemeContent(settings.theme.content);
|
|
29
|
+
return BuilderSettingsSchema.parse({
|
|
30
|
+
...settings,
|
|
31
|
+
theme: {
|
|
32
|
+
...settings.theme,
|
|
33
|
+
content,
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
function sanitizeThemeContent(content) {
|
|
38
|
+
const next = {};
|
|
39
|
+
for (const [key, value] of Object.entries(content)) {
|
|
40
|
+
if (RESERVED_THEME_CONTENT_KEYS.has(key)) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
next[key] = value;
|
|
44
|
+
}
|
|
45
|
+
return next;
|
|
46
|
+
}
|
|
47
|
+
export function mergeBuilderSettingsIntoThemeSettings(currentSettings, builderSettings) {
|
|
48
|
+
const root = isRecord(currentSettings) ? { ...currentSettings } : {};
|
|
49
|
+
const currentBuilderSettings = isRecord(root.builder_settings) ? root.builder_settings : {};
|
|
50
|
+
const sanitizedBuilderSettings = sanitizeBuilderSettingsState(builderSettings);
|
|
51
|
+
root.builder_settings = {
|
|
52
|
+
...currentBuilderSettings,
|
|
53
|
+
version: sanitizedBuilderSettings.version,
|
|
54
|
+
revision: sanitizedBuilderSettings.revision,
|
|
55
|
+
theme: sanitizedBuilderSettings.theme,
|
|
56
|
+
};
|
|
57
|
+
return root;
|
|
58
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import * as z from 'zod/v4';
|
|
2
|
+
export declare const PREVIEW_BOOT_SIDECAR_PATH = "__shoppex/preview-boot.json";
|
|
3
|
+
export declare const StorefrontSeedSchema: z.ZodObject<{
|
|
4
|
+
store: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
5
|
+
products: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
|
|
6
|
+
groups: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
|
|
7
|
+
pages: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
|
|
8
|
+
}, z.core.$loose>;
|
|
9
|
+
export type StorefrontSeed = z.infer<typeof StorefrontSeedSchema>;
|
|
10
|
+
export declare const PreviewBootPayloadSchema: z.ZodObject<{
|
|
11
|
+
shopId: z.ZodString;
|
|
12
|
+
shopSlug: z.ZodString;
|
|
13
|
+
builderSettings: z.ZodObject<{
|
|
14
|
+
version: z.ZodLiteral<2>;
|
|
15
|
+
revision: z.ZodNumber;
|
|
16
|
+
theme: z.ZodObject<{
|
|
17
|
+
content: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
18
|
+
layout: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
19
|
+
blocks: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
20
|
+
id: z.ZodString;
|
|
21
|
+
type: z.ZodString;
|
|
22
|
+
variant: z.ZodOptional<z.ZodString>;
|
|
23
|
+
visible: z.ZodDefault<z.ZodBoolean>;
|
|
24
|
+
settings: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
25
|
+
style_overrides: z.ZodOptional<z.ZodType<Partial<Record<import("./style-slots.ts").StyleSlotId, import("./style-slots.ts").StyleSlotValue>>, unknown, z.core.$ZodTypeInternals<Partial<Record<import("./style-slots.ts").StyleSlotId, import("./style-slots.ts").StyleSlotValue>>, unknown>>>;
|
|
26
|
+
}, z.core.$strict>>>;
|
|
27
|
+
}, z.core.$strict>>>;
|
|
28
|
+
style_slots: z.ZodDefault<z.ZodType<Partial<Record<import("./style-slots.ts").StyleSlotId, import("./style-slots.ts").StyleSlotValue>>, unknown, z.core.$ZodTypeInternals<Partial<Record<import("./style-slots.ts").StyleSlotId, import("./style-slots.ts").StyleSlotValue>>, unknown>>>;
|
|
29
|
+
pages: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
30
|
+
id: z.ZodString;
|
|
31
|
+
title: z.ZodString;
|
|
32
|
+
slug: z.ZodString;
|
|
33
|
+
visible: z.ZodDefault<z.ZodBoolean>;
|
|
34
|
+
layout: z.ZodDefault<z.ZodObject<{
|
|
35
|
+
blocks: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
36
|
+
id: z.ZodString;
|
|
37
|
+
type: z.ZodString;
|
|
38
|
+
variant: z.ZodOptional<z.ZodString>;
|
|
39
|
+
visible: z.ZodDefault<z.ZodBoolean>;
|
|
40
|
+
settings: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
41
|
+
style_overrides: z.ZodOptional<z.ZodType<Partial<Record<import("./style-slots.ts").StyleSlotId, import("./style-slots.ts").StyleSlotValue>>, unknown, z.core.$ZodTypeInternals<Partial<Record<import("./style-slots.ts").StyleSlotId, import("./style-slots.ts").StyleSlotValue>>, unknown>>>;
|
|
42
|
+
}, z.core.$strict>>>;
|
|
43
|
+
}, z.core.$strict>>;
|
|
44
|
+
seo: z.ZodOptional<z.ZodObject<{
|
|
45
|
+
title: z.ZodOptional<z.ZodString>;
|
|
46
|
+
description: z.ZodOptional<z.ZodString>;
|
|
47
|
+
}, z.core.$strict>>;
|
|
48
|
+
}, z.core.$strict>>>;
|
|
49
|
+
terms: z.ZodDefault<z.ZodObject<{
|
|
50
|
+
termsOfService: z.ZodOptional<z.ZodString>;
|
|
51
|
+
privacyPolicy: z.ZodOptional<z.ZodString>;
|
|
52
|
+
refundPolicy: z.ZodOptional<z.ZodString>;
|
|
53
|
+
imprint: z.ZodOptional<z.ZodString>;
|
|
54
|
+
}, z.core.$strict>>;
|
|
55
|
+
}, z.core.$strict>;
|
|
56
|
+
}, z.core.$strict>;
|
|
57
|
+
storefrontSeed: z.ZodObject<{
|
|
58
|
+
store: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
59
|
+
products: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
|
|
60
|
+
groups: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
|
|
61
|
+
pages: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
|
|
62
|
+
}, z.core.$loose>;
|
|
63
|
+
artifactRevision: z.ZodOptional<z.ZodString>;
|
|
64
|
+
artifactStale: z.ZodOptional<z.ZodBoolean>;
|
|
65
|
+
}, z.core.$strict>;
|
|
66
|
+
export type PreviewBootPayload = z.infer<typeof PreviewBootPayloadSchema>;
|
|
67
|
+
export declare function mergePreviewBootIntoInitialData(payload: PreviewBootPayload): Record<string, unknown>;
|
|
68
|
+
//# sourceMappingURL=preview-boot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview-boot.d.ts","sourceRoot":"","sources":["../src/preview-boot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,QAAQ,CAAC;AAG5B,eAAO,MAAM,yBAAyB,gCAAgC,CAAC;AAIvE,eAAO,MAAM,oBAAoB;;;;;iBAOjB,CAAC;AAEjB,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAS1B,CAAC;AAEZ,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE1E,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,kBAAkB,GAC1B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAWzB"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import * as z from 'zod/v4';
|
|
2
|
+
import { BuilderSettingsSchema } from "./builder-settings.js";
|
|
3
|
+
export const PREVIEW_BOOT_SIDECAR_PATH = '__shoppex/preview-boot.json';
|
|
4
|
+
// Artifact HTML seeds include catalog/menu fields beyond the documented minimum.
|
|
5
|
+
// Passthrough keeps them available to the theme runtime in preview boot.
|
|
6
|
+
export const StorefrontSeedSchema = z
|
|
7
|
+
.object({
|
|
8
|
+
store: z.record(z.string(), z.unknown()),
|
|
9
|
+
products: z.array(z.unknown()).optional(),
|
|
10
|
+
groups: z.array(z.unknown()).optional(),
|
|
11
|
+
pages: z.array(z.unknown()).optional(),
|
|
12
|
+
})
|
|
13
|
+
.passthrough();
|
|
14
|
+
export const PreviewBootPayloadSchema = z
|
|
15
|
+
.object({
|
|
16
|
+
shopId: z.string().min(1),
|
|
17
|
+
shopSlug: z.string().min(1),
|
|
18
|
+
builderSettings: BuilderSettingsSchema,
|
|
19
|
+
storefrontSeed: StorefrontSeedSchema,
|
|
20
|
+
artifactRevision: z.string().optional(),
|
|
21
|
+
artifactStale: z.boolean().optional(),
|
|
22
|
+
})
|
|
23
|
+
.strict();
|
|
24
|
+
export function mergePreviewBootIntoInitialData(payload) {
|
|
25
|
+
const existingStore = isRecord(payload.storefrontSeed.store) ? payload.storefrontSeed.store : {};
|
|
26
|
+
return {
|
|
27
|
+
...payload.storefrontSeed,
|
|
28
|
+
store: {
|
|
29
|
+
...existingStore,
|
|
30
|
+
id: payload.shopId,
|
|
31
|
+
slug: payload.shopSlug,
|
|
32
|
+
builder_settings: payload.builderSettings,
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function isRecord(value) {
|
|
37
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
38
|
+
}
|