@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.
Files changed (92) hide show
  1. package/dist/builder-settings.d.ts +11 -666
  2. package/dist/builder-settings.d.ts.map +1 -1
  3. package/dist/builder-settings.js +2 -1
  4. package/dist/canonical-settings.d.ts +18 -0
  5. package/dist/canonical-settings.d.ts.map +1 -0
  6. package/dist/canonical-settings.js +106 -0
  7. package/dist/custom-pages.d.ts +15 -0
  8. package/dist/custom-pages.d.ts.map +1 -0
  9. package/dist/custom-pages.js +40 -0
  10. package/dist/dedicated-pages.d.ts +15 -0
  11. package/dist/dedicated-pages.d.ts.map +1 -0
  12. package/dist/dedicated-pages.js +142 -0
  13. package/dist/events.d.ts +35 -240
  14. package/dist/events.d.ts.map +1 -1
  15. package/dist/events.js +7 -0
  16. package/dist/fields.d.ts +229 -10
  17. package/dist/fields.d.ts.map +1 -1
  18. package/dist/fields.js +27 -0
  19. package/dist/index.d.ts +10 -0
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +10 -0
  22. package/dist/legacy-manifest.d.ts +18 -0
  23. package/dist/legacy-manifest.d.ts.map +1 -1
  24. package/dist/legacy-manifest.js +137 -22
  25. package/dist/migrations.d.ts.map +1 -1
  26. package/dist/migrations.js +55 -6
  27. package/dist/persistence.d.ts +7 -0
  28. package/dist/persistence.d.ts.map +1 -0
  29. package/dist/persistence.js +58 -0
  30. package/dist/preview-boot.d.ts +68 -0
  31. package/dist/preview-boot.d.ts.map +1 -0
  32. package/dist/preview-boot.js +38 -0
  33. package/dist/preview-protocol.d.ts +227 -459
  34. package/dist/preview-protocol.d.ts.map +1 -1
  35. package/dist/preview-protocol.js +112 -0
  36. package/dist/preview-session-resolve.d.ts +115 -0
  37. package/dist/preview-session-resolve.d.ts.map +1 -0
  38. package/dist/preview-session-resolve.js +25 -0
  39. package/dist/preview-trusted-origins.d.ts +4 -0
  40. package/dist/preview-trusted-origins.d.ts.map +1 -0
  41. package/dist/preview-trusted-origins.js +28 -0
  42. package/dist/storefront-initial-data-html.d.ts +17 -0
  43. package/dist/storefront-initial-data-html.d.ts.map +1 -0
  44. package/dist/storefront-initial-data-html.js +83 -0
  45. package/dist/storefront-typography-fonts.d.ts +18 -0
  46. package/dist/storefront-typography-fonts.d.ts.map +1 -0
  47. package/dist/storefront-typography-fonts.js +89 -0
  48. package/dist/style-slots.d.ts +50 -152
  49. package/dist/style-slots.d.ts.map +1 -1
  50. package/dist/style-slots.js +80 -32
  51. package/dist/theme-manifest.d.ts +287 -456
  52. package/dist/theme-manifest.d.ts.map +1 -1
  53. package/dist/theme-manifest.js +92 -0
  54. package/dist/theme-schemes.d.ts +10 -0
  55. package/dist/theme-schemes.d.ts.map +1 -0
  56. package/dist/theme-schemes.js +25 -0
  57. package/dist/validation.d.ts +1 -1
  58. package/dist/validation.d.ts.map +1 -1
  59. package/dist/validation.js +23 -12
  60. package/package.json +43 -1
  61. package/src/builder-contracts.test.ts +416 -3
  62. package/src/builder-settings.ts +4 -1
  63. package/src/canonical-settings.ts +156 -0
  64. package/src/custom-pages.test.ts +74 -0
  65. package/src/custom-pages.ts +70 -0
  66. package/src/dedicated-pages.test.ts +88 -0
  67. package/src/dedicated-pages.ts +173 -0
  68. package/src/events.ts +8 -0
  69. package/src/fields.ts +30 -0
  70. package/src/index.ts +10 -0
  71. package/src/legacy-manifest.ts +147 -23
  72. package/src/migrations.ts +70 -6
  73. package/src/persistence.ts +77 -0
  74. package/src/preview-boot.test.ts +72 -0
  75. package/src/preview-boot.ts +49 -0
  76. package/src/preview-protocol.test.ts +132 -0
  77. package/src/preview-protocol.ts +122 -0
  78. package/src/preview-session-resolve.test.ts +37 -0
  79. package/src/preview-session-resolve.ts +34 -0
  80. package/src/preview-trusted-origins.test.ts +24 -0
  81. package/src/preview-trusted-origins.ts +35 -0
  82. package/src/storefront-initial-data-html.test.ts +73 -0
  83. package/src/storefront-initial-data-html.ts +112 -0
  84. package/src/storefront-typography-fonts.test.ts +48 -0
  85. package/src/storefront-typography-fonts.ts +108 -0
  86. package/src/style-slots.ts +102 -34
  87. package/src/theme-manifest.ts +118 -1
  88. package/src/theme-schemes.ts +34 -0
  89. package/src/validation.ts +32 -13
  90. package/dist/builder-contracts.test.d.ts +0 -2
  91. package/dist/builder-contracts.test.d.ts.map +0 -1
  92. package/dist/builder-contracts.test.js +0 -361
@@ -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 = createSyntheticPageBlockType(page.id);
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 = createSyntheticPageBlockType(page.id);
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.id,
157
- {
158
- label: preset.name ?? preset.label ?? preset.id,
159
- description: preset.description,
160
- content: flattenContentRecord(preset.overrides?.content ?? {}),
161
- layout: preset.overrides?.layout ?? {},
162
- style_slots: preset.overrides?.style_slots ?? preset.overrides?.styleSlots ?? {},
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
- const convertedFields = Object.fromEntries(fields.map((field) => [field.path, convertLegacyField(field)]));
184
- const convertedLists = Object.fromEntries(lists.map((list) => [
185
- list.path,
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
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../src/migrations.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,aAAa,EAClB,KAAK,eAAe,EAEpB,KAAK,UAAU,EAChB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,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,CAkB7G;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,CAa/F"}
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"}
@@ -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
- return BuilderSettingsSchema.parse({
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: input.theme?.content ?? {},
39
- layout: normalizeLegacyLayout(input.theme?.layout ?? {}),
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[tokenPath];
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[tokenPath];
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
+ }