@shoppexio/builder-contracts 0.1.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.
Files changed (45) hide show
  1. package/dist/builder-contracts.test.d.ts +2 -0
  2. package/dist/builder-contracts.test.d.ts.map +1 -0
  3. package/dist/builder-contracts.test.js +361 -0
  4. package/dist/builder-settings.d.ts +801 -0
  5. package/dist/builder-settings.d.ts.map +1 -0
  6. package/dist/builder-settings.js +65 -0
  7. package/dist/events.d.ts +512 -0
  8. package/dist/events.d.ts.map +1 -0
  9. package/dist/events.js +104 -0
  10. package/dist/fields.d.ts +300 -0
  11. package/dist/fields.d.ts.map +1 -0
  12. package/dist/fields.js +111 -0
  13. package/dist/index.d.ts +10 -0
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.js +9 -0
  16. package/dist/legacy-manifest.d.ts +172 -0
  17. package/dist/legacy-manifest.d.ts.map +1 -0
  18. package/dist/legacy-manifest.js +272 -0
  19. package/dist/migrations.d.ts +31 -0
  20. package/dist/migrations.d.ts.map +1 -0
  21. package/dist/migrations.js +175 -0
  22. package/dist/preview-protocol.d.ts +687 -0
  23. package/dist/preview-protocol.d.ts.map +1 -0
  24. package/dist/preview-protocol.js +79 -0
  25. package/dist/style-slots.d.ts +209 -0
  26. package/dist/style-slots.d.ts.map +1 -0
  27. package/dist/style-slots.js +93 -0
  28. package/dist/theme-manifest.d.ts +845 -0
  29. package/dist/theme-manifest.d.ts.map +1 -0
  30. package/dist/theme-manifest.js +119 -0
  31. package/dist/validation.d.ts +16 -0
  32. package/dist/validation.d.ts.map +1 -0
  33. package/dist/validation.js +131 -0
  34. package/package.json +95 -0
  35. package/src/builder-contracts.test.ts +405 -0
  36. package/src/builder-settings.ts +85 -0
  37. package/src/events.ts +121 -0
  38. package/src/fields.ts +134 -0
  39. package/src/index.ts +9 -0
  40. package/src/legacy-manifest.ts +321 -0
  41. package/src/migrations.ts +240 -0
  42. package/src/preview-protocol.ts +93 -0
  43. package/src/style-slots.ts +111 -0
  44. package/src/theme-manifest.ts +140 -0
  45. package/src/validation.ts +196 -0
@@ -0,0 +1,172 @@
1
+ import * as z from 'zod/v4';
2
+ import { type ThemeManifest } from './theme-manifest.ts';
3
+ export declare const LegacyBuilderFieldSchema: z.ZodObject<{
4
+ path: z.ZodString;
5
+ type: z.ZodOptional<z.ZodString>;
6
+ label: z.ZodString;
7
+ placeholder: z.ZodOptional<z.ZodString>;
8
+ defaultValue: z.ZodOptional<z.ZodUnknown>;
9
+ description: z.ZodOptional<z.ZodString>;
10
+ options: z.ZodOptional<z.ZodArray<z.ZodObject<{
11
+ label: z.ZodString;
12
+ value: z.ZodString;
13
+ }, z.core.$loose>>>;
14
+ min: z.ZodOptional<z.ZodNumber>;
15
+ max: z.ZodOptional<z.ZodNumber>;
16
+ step: z.ZodOptional<z.ZodNumber>;
17
+ }, z.core.$loose>;
18
+ export type LegacyBuilderField = z.infer<typeof LegacyBuilderFieldSchema>;
19
+ export declare const LegacyBuilderListSchema: z.ZodObject<{
20
+ path: z.ZodString;
21
+ label: z.ZodString;
22
+ kind: z.ZodString;
23
+ description: z.ZodOptional<z.ZodString>;
24
+ }, z.core.$loose>;
25
+ export type LegacyBuilderList = z.infer<typeof LegacyBuilderListSchema>;
26
+ export declare const LegacyBuilderBlockSchema: z.ZodObject<{
27
+ id: z.ZodOptional<z.ZodString>;
28
+ label: z.ZodString;
29
+ maxInstances: z.ZodOptional<z.ZodNumber>;
30
+ mountable: z.ZodOptional<z.ZodBoolean>;
31
+ fields: z.ZodOptional<z.ZodArray<z.ZodObject<{
32
+ path: z.ZodString;
33
+ type: z.ZodOptional<z.ZodString>;
34
+ label: z.ZodString;
35
+ placeholder: z.ZodOptional<z.ZodString>;
36
+ defaultValue: z.ZodOptional<z.ZodUnknown>;
37
+ description: z.ZodOptional<z.ZodString>;
38
+ options: z.ZodOptional<z.ZodArray<z.ZodObject<{
39
+ label: z.ZodString;
40
+ value: z.ZodString;
41
+ }, z.core.$loose>>>;
42
+ min: z.ZodOptional<z.ZodNumber>;
43
+ max: z.ZodOptional<z.ZodNumber>;
44
+ step: z.ZodOptional<z.ZodNumber>;
45
+ }, z.core.$loose>>>;
46
+ lists: z.ZodOptional<z.ZodArray<z.ZodObject<{
47
+ path: z.ZodString;
48
+ label: z.ZodString;
49
+ kind: z.ZodString;
50
+ description: z.ZodOptional<z.ZodString>;
51
+ }, z.core.$loose>>>;
52
+ }, z.core.$loose>;
53
+ export type LegacyBuilderBlock = z.infer<typeof LegacyBuilderBlockSchema>;
54
+ export declare const LegacyBuilderPageSchema: z.ZodObject<{
55
+ id: z.ZodString;
56
+ label: z.ZodString;
57
+ previewPath: z.ZodOptional<z.ZodString>;
58
+ blocks: z.ZodDefault<z.ZodArray<z.ZodString>>;
59
+ fields: z.ZodOptional<z.ZodArray<z.ZodObject<{
60
+ path: z.ZodString;
61
+ type: z.ZodOptional<z.ZodString>;
62
+ label: z.ZodString;
63
+ placeholder: z.ZodOptional<z.ZodString>;
64
+ defaultValue: z.ZodOptional<z.ZodUnknown>;
65
+ description: z.ZodOptional<z.ZodString>;
66
+ options: z.ZodOptional<z.ZodArray<z.ZodObject<{
67
+ label: z.ZodString;
68
+ value: z.ZodString;
69
+ }, z.core.$loose>>>;
70
+ min: z.ZodOptional<z.ZodNumber>;
71
+ max: z.ZodOptional<z.ZodNumber>;
72
+ step: z.ZodOptional<z.ZodNumber>;
73
+ }, z.core.$loose>>>;
74
+ lists: z.ZodOptional<z.ZodArray<z.ZodObject<{
75
+ path: z.ZodString;
76
+ label: z.ZodString;
77
+ kind: z.ZodString;
78
+ description: z.ZodOptional<z.ZodString>;
79
+ }, z.core.$loose>>>;
80
+ }, z.core.$loose>;
81
+ export type LegacyBuilderPage = z.infer<typeof LegacyBuilderPageSchema>;
82
+ declare const LegacyThemePresetSchema: z.ZodObject<{
83
+ id: z.ZodString;
84
+ name: z.ZodOptional<z.ZodString>;
85
+ label: z.ZodOptional<z.ZodString>;
86
+ description: z.ZodOptional<z.ZodString>;
87
+ overrides: z.ZodOptional<z.ZodObject<{
88
+ content: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
89
+ layout: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
90
+ style_slots: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
91
+ styleSlots: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
92
+ }, z.core.$loose>>;
93
+ }, z.core.$loose>;
94
+ export type LegacyThemePreset = z.infer<typeof LegacyThemePresetSchema>;
95
+ export declare const LegacyThemeManifestSchema: z.ZodObject<{
96
+ id: z.ZodString;
97
+ name: z.ZodString;
98
+ version: z.ZodString;
99
+ presets: z.ZodOptional<z.ZodArray<z.ZodObject<{
100
+ id: z.ZodString;
101
+ name: z.ZodOptional<z.ZodString>;
102
+ label: z.ZodOptional<z.ZodString>;
103
+ description: z.ZodOptional<z.ZodString>;
104
+ overrides: z.ZodOptional<z.ZodObject<{
105
+ content: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
106
+ layout: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
107
+ style_slots: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
108
+ styleSlots: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
109
+ }, z.core.$loose>>;
110
+ }, z.core.$loose>>>;
111
+ builder: z.ZodObject<{
112
+ pages: z.ZodDefault<z.ZodArray<z.ZodObject<{
113
+ id: z.ZodString;
114
+ label: z.ZodString;
115
+ previewPath: z.ZodOptional<z.ZodString>;
116
+ blocks: z.ZodDefault<z.ZodArray<z.ZodString>>;
117
+ fields: z.ZodOptional<z.ZodArray<z.ZodObject<{
118
+ path: z.ZodString;
119
+ type: z.ZodOptional<z.ZodString>;
120
+ label: z.ZodString;
121
+ placeholder: z.ZodOptional<z.ZodString>;
122
+ defaultValue: z.ZodOptional<z.ZodUnknown>;
123
+ description: z.ZodOptional<z.ZodString>;
124
+ options: z.ZodOptional<z.ZodArray<z.ZodObject<{
125
+ label: z.ZodString;
126
+ value: z.ZodString;
127
+ }, z.core.$loose>>>;
128
+ min: z.ZodOptional<z.ZodNumber>;
129
+ max: z.ZodOptional<z.ZodNumber>;
130
+ step: z.ZodOptional<z.ZodNumber>;
131
+ }, z.core.$loose>>>;
132
+ lists: z.ZodOptional<z.ZodArray<z.ZodObject<{
133
+ path: z.ZodString;
134
+ label: z.ZodString;
135
+ kind: z.ZodString;
136
+ description: z.ZodOptional<z.ZodString>;
137
+ }, z.core.$loose>>>;
138
+ }, z.core.$loose>>>;
139
+ blocks: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
140
+ id: z.ZodOptional<z.ZodString>;
141
+ label: z.ZodString;
142
+ maxInstances: z.ZodOptional<z.ZodNumber>;
143
+ mountable: z.ZodOptional<z.ZodBoolean>;
144
+ fields: z.ZodOptional<z.ZodArray<z.ZodObject<{
145
+ path: z.ZodString;
146
+ type: z.ZodOptional<z.ZodString>;
147
+ label: z.ZodString;
148
+ placeholder: z.ZodOptional<z.ZodString>;
149
+ defaultValue: z.ZodOptional<z.ZodUnknown>;
150
+ description: z.ZodOptional<z.ZodString>;
151
+ options: z.ZodOptional<z.ZodArray<z.ZodObject<{
152
+ label: z.ZodString;
153
+ value: z.ZodString;
154
+ }, z.core.$loose>>>;
155
+ min: z.ZodOptional<z.ZodNumber>;
156
+ max: z.ZodOptional<z.ZodNumber>;
157
+ step: z.ZodOptional<z.ZodNumber>;
158
+ }, z.core.$loose>>>;
159
+ lists: z.ZodOptional<z.ZodArray<z.ZodObject<{
160
+ path: z.ZodString;
161
+ label: z.ZodString;
162
+ kind: z.ZodString;
163
+ description: z.ZodOptional<z.ZodString>;
164
+ }, z.core.$loose>>>;
165
+ }, z.core.$loose>>>;
166
+ presets: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
167
+ }, z.core.$loose>;
168
+ }, z.core.$loose>;
169
+ export type LegacyThemeManifest = z.infer<typeof LegacyThemeManifestSchema>;
170
+ export declare function convertLegacyThemeManifest(input: unknown): ThemeManifest;
171
+ export {};
172
+ //# sourceMappingURL=legacy-manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"legacy-manifest.d.ts","sourceRoot":"","sources":["../src/legacy-manifest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,QAAQ,CAAC;AAE5B,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAS9E,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;iBAarB,CAAC;AACjB,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE1E,eAAO,MAAM,uBAAuB;;;;;iBAOpB,CAAC;AACjB,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAExE,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;iBASrB,CAAC;AACjB,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE1E,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;iBASpB,CAAC;AACjB,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAExE,QAAA,MAAM,uBAAuB;;;;;;;;;;;iBAgBb,CAAC;AACjB,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAExE,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBActB,CAAC;AACjB,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAE5E,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,GAAG,aAAa,CAkExE"}
@@ -0,0 +1,272 @@
1
+ import * as z from 'zod/v4';
2
+ import { BuilderFieldSchema } from "./fields.js";
3
+ import { ThemeManifestSchema } from "./theme-manifest.js";
4
+ const LegacyFieldOptionSchema = z
5
+ .object({
6
+ label: z.string().min(1),
7
+ value: z.string().min(1),
8
+ })
9
+ .passthrough();
10
+ export const LegacyBuilderFieldSchema = z
11
+ .object({
12
+ path: z.string().min(1),
13
+ type: z.string().min(1).optional(),
14
+ label: z.string().min(1),
15
+ placeholder: z.string().optional(),
16
+ defaultValue: z.unknown().optional(),
17
+ description: z.string().optional(),
18
+ options: z.array(LegacyFieldOptionSchema).optional(),
19
+ min: z.number().optional(),
20
+ max: z.number().optional(),
21
+ step: z.number().positive().optional(),
22
+ })
23
+ .passthrough();
24
+ export const LegacyBuilderListSchema = z
25
+ .object({
26
+ path: z.string().min(1),
27
+ label: z.string().min(1),
28
+ kind: z.string().min(1),
29
+ description: z.string().optional(),
30
+ })
31
+ .passthrough();
32
+ export const LegacyBuilderBlockSchema = z
33
+ .object({
34
+ id: z.string().min(1).optional(),
35
+ label: z.string().min(1),
36
+ maxInstances: z.number().int().positive().optional(),
37
+ mountable: z.boolean().optional(),
38
+ fields: z.array(LegacyBuilderFieldSchema).optional(),
39
+ lists: z.array(LegacyBuilderListSchema).optional(),
40
+ })
41
+ .passthrough();
42
+ export const LegacyBuilderPageSchema = z
43
+ .object({
44
+ id: z.string().min(1),
45
+ label: z.string().min(1),
46
+ previewPath: z.string().min(1).optional(),
47
+ blocks: z.array(z.string().min(1)).default([]),
48
+ fields: z.array(LegacyBuilderFieldSchema).optional(),
49
+ lists: z.array(LegacyBuilderListSchema).optional(),
50
+ })
51
+ .passthrough();
52
+ const LegacyThemePresetSchema = z
53
+ .object({
54
+ id: z.string().min(1),
55
+ name: z.string().min(1).optional(),
56
+ label: z.string().min(1).optional(),
57
+ description: z.string().optional(),
58
+ overrides: z
59
+ .object({
60
+ content: z.record(z.string().min(1), z.unknown()).optional(),
61
+ layout: z.record(z.string().min(1), z.unknown()).optional(),
62
+ style_slots: z.record(z.string().min(1), z.unknown()).optional(),
63
+ styleSlots: z.record(z.string().min(1), z.unknown()).optional(),
64
+ })
65
+ .passthrough()
66
+ .optional(),
67
+ })
68
+ .passthrough();
69
+ export const LegacyThemeManifestSchema = z
70
+ .object({
71
+ id: z.string().min(1),
72
+ name: z.string().min(1),
73
+ version: z.string().min(1),
74
+ presets: z.array(LegacyThemePresetSchema).optional(),
75
+ builder: z
76
+ .object({
77
+ pages: z.array(LegacyBuilderPageSchema).default([]),
78
+ blocks: z.record(z.string().min(1), LegacyBuilderBlockSchema).default({}),
79
+ presets: z.record(z.string().min(1), z.unknown()).optional(),
80
+ })
81
+ .passthrough(),
82
+ })
83
+ .passthrough();
84
+ export function convertLegacyThemeManifest(input) {
85
+ const legacy = LegacyThemeManifestSchema.parse(input);
86
+ const blocks = Object.fromEntries(Object.entries(legacy.builder.blocks).map(([blockType, block]) => [
87
+ blockType,
88
+ {
89
+ label: block.label,
90
+ maxInstances: block.maxInstances,
91
+ variants: [],
92
+ settings: convertLegacyFields(block.fields ?? [], block.lists ?? []),
93
+ exposedStyleSlots: [],
94
+ presets: [],
95
+ },
96
+ ]));
97
+ for (const page of legacy.builder.pages) {
98
+ const syntheticBlockType = createSyntheticPageBlockType(page.id);
99
+ if ((page.fields?.length ?? 0) === 0 && (page.lists?.length ?? 0) === 0) {
100
+ continue;
101
+ }
102
+ blocks[syntheticBlockType] = {
103
+ label: page.label,
104
+ maxInstances: 1,
105
+ variants: [],
106
+ settings: convertLegacyFields(page.fields ?? [], page.lists ?? []),
107
+ exposedStyleSlots: [],
108
+ presets: [],
109
+ };
110
+ }
111
+ const converted = {
112
+ id: normalizeLegacyThemeId(legacy.id),
113
+ name: legacy.name,
114
+ version: legacy.version,
115
+ pages: Object.fromEntries(legacy.builder.pages.map((page) => {
116
+ const syntheticBlockType = createSyntheticPageBlockType(page.id);
117
+ const hasSyntheticBlock = blocks[syntheticBlockType] !== undefined;
118
+ const allowedBlocks = page.blocks.length > 0
119
+ ? page.blocks
120
+ : (hasSyntheticBlock ? [syntheticBlockType] : []);
121
+ // Seed synthetic page-level blocks as defaults so pages that
122
+ // only surface page.fields/lists still open with an editable
123
+ // block instance instead of an empty editor.
124
+ const defaultBlocks = page.blocks.length > 0
125
+ ? page.blocks.map((type) => ({ type }))
126
+ : (hasSyntheticBlock ? [{ type: syntheticBlockType }] : []);
127
+ return [
128
+ page.id,
129
+ {
130
+ label: page.label,
131
+ previewPath: page.previewPath,
132
+ allowedBlocks,
133
+ defaultBlocks,
134
+ },
135
+ ];
136
+ })),
137
+ blocks,
138
+ styleSlots: {},
139
+ presets: convertLegacyThemePresets(legacy.presets ?? []),
140
+ };
141
+ return ThemeManifestSchema.parse(converted);
142
+ }
143
+ function normalizeLegacyThemeId(rawId) {
144
+ const lowered = rawId.toLowerCase().replace(/[^a-z0-9\-_.]+/g, '-').replace(/^[-_.]+/, '');
145
+ if (lowered.length > 0 && /^[a-z0-9]/.test(lowered)) {
146
+ return lowered;
147
+ }
148
+ return `legacy-${lowered || 'theme'}`;
149
+ }
150
+ function createSyntheticPageBlockType(pageId) {
151
+ const normalizedPageId = pageId.toLowerCase().replace(/[^a-z0-9\-_.]+/g, '-').replace(/^[-_.]+/, '');
152
+ return `page-${normalizedPageId || 'custom'}`;
153
+ }
154
+ 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
+ ]));
165
+ }
166
+ function flattenContentRecord(content, prefix = '') {
167
+ const flattened = {};
168
+ for (const [key, value] of Object.entries(content)) {
169
+ const path = prefix ? `${prefix}.${key}` : key;
170
+ if (isPlainObject(value)) {
171
+ Object.assign(flattened, flattenContentRecord(value, path));
172
+ }
173
+ else {
174
+ flattened[path] = value;
175
+ }
176
+ }
177
+ return flattened;
178
+ }
179
+ function isPlainObject(value) {
180
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
181
+ }
182
+ 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
+ {
187
+ type: 'list',
188
+ label: list.label,
189
+ description: list.description,
190
+ itemShape: createListItemShape(list.kind),
191
+ },
192
+ ]));
193
+ return {
194
+ ...convertedFields,
195
+ ...convertedLists,
196
+ };
197
+ }
198
+ function convertLegacyField(field) {
199
+ const type = normalizeLegacyFieldType(field.type);
200
+ const base = {
201
+ label: field.label,
202
+ description: field.description,
203
+ defaultValue: field.defaultValue,
204
+ };
205
+ const candidate = type === 'select'
206
+ ? {
207
+ ...base,
208
+ type,
209
+ options: (field.options ?? []).map((option) => ({
210
+ label: option.label,
211
+ value: option.value,
212
+ })),
213
+ }
214
+ : type === 'range'
215
+ ? {
216
+ ...base,
217
+ type,
218
+ min: field.min ?? 0,
219
+ max: field.max ?? 100,
220
+ step: field.step,
221
+ }
222
+ : type === 'text'
223
+ ? {
224
+ ...base,
225
+ type,
226
+ placeholder: field.placeholder,
227
+ }
228
+ : {
229
+ ...base,
230
+ type,
231
+ };
232
+ return BuilderFieldSchema.parse(candidate);
233
+ }
234
+ function normalizeLegacyFieldType(type) {
235
+ if (type === 'richtext' ||
236
+ type === 'image' ||
237
+ type === 'link' ||
238
+ type === 'boolean' ||
239
+ type === 'number' ||
240
+ type === 'range' ||
241
+ type === 'select' ||
242
+ type === 'color' ||
243
+ type === 'product' ||
244
+ type === 'products') {
245
+ return type;
246
+ }
247
+ return 'text';
248
+ }
249
+ function createListItemShape(kind) {
250
+ if (kind === 'faqItems') {
251
+ return {
252
+ question: { type: 'text', label: 'Question' },
253
+ answer: { type: 'richtext', label: 'Answer' },
254
+ };
255
+ }
256
+ if (kind === 'galleryItems') {
257
+ return {
258
+ image: { type: 'image', label: 'Image' },
259
+ alt: { type: 'text', label: 'Alt Text' },
260
+ };
261
+ }
262
+ if (kind === 'reviewItems') {
263
+ return {
264
+ quote: { type: 'richtext', label: 'Quote' },
265
+ author: { type: 'text', label: 'Author' },
266
+ };
267
+ }
268
+ return {
269
+ title: { type: 'text', label: 'Title' },
270
+ body: { type: 'richtext', label: 'Body' },
271
+ };
272
+ }
@@ -0,0 +1,31 @@
1
+ import { type BlockInstance, type BuilderSettings, type PageLayout } from './builder-settings.ts';
2
+ import type { StyleSlots } from './style-slots.ts';
3
+ import type { ThemeManifest } from './theme-manifest.ts';
4
+ export type LegacyBuilderSettingsInput = {
5
+ theme?: {
6
+ content?: Record<string, unknown>;
7
+ layout?: Record<string, unknown>;
8
+ tokens_override?: Record<string, unknown>;
9
+ style_slots?: Record<string, unknown>;
10
+ };
11
+ };
12
+ export type LegacyRedisOverride = {
13
+ target?: string;
14
+ property?: string;
15
+ value?: unknown;
16
+ className?: string;
17
+ };
18
+ export declare function createEmptyBuilderSettings(revision?: number): BuilderSettings;
19
+ export declare function createBlockInstance(input: {
20
+ id: string;
21
+ type: string;
22
+ variant?: string;
23
+ visible?: boolean;
24
+ settings?: Record<string, unknown>;
25
+ style_overrides?: StyleSlots;
26
+ }): BlockInstance;
27
+ export declare function createPageLayoutFromBlocks(blocks: BlockInstance[]): PageLayout;
28
+ export declare function migrateLegacyBuilderSettings(input: LegacyBuilderSettingsInput, revision?: number): BuilderSettings;
29
+ export declare function applyManifestDefaultLayout(settings: BuilderSettings, manifest: ThemeManifest): BuilderSettings;
30
+ export declare function mapLegacyTokenOverridesToStyleSlots(tokens: Record<string, unknown>): StyleSlots;
31
+ //# sourceMappingURL=migrations.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,175 @@
1
+ import { BUILDER_SETTINGS_VERSION, BuilderSettingsSchema, } from "./builder-settings.js";
2
+ export function createEmptyBuilderSettings(revision = 0) {
3
+ return {
4
+ version: BUILDER_SETTINGS_VERSION,
5
+ revision,
6
+ theme: {
7
+ content: {},
8
+ layout: {},
9
+ style_slots: {},
10
+ pages: [],
11
+ terms: {},
12
+ },
13
+ };
14
+ }
15
+ export function createBlockInstance(input) {
16
+ return {
17
+ id: input.id,
18
+ type: input.type,
19
+ variant: input.variant,
20
+ visible: input.visible ?? true,
21
+ settings: input.settings ?? {},
22
+ style_overrides: input.style_overrides,
23
+ };
24
+ }
25
+ export function createPageLayoutFromBlocks(blocks) {
26
+ return { blocks };
27
+ }
28
+ export function migrateLegacyBuilderSettings(input, revision = 0) {
29
+ const tokensOverride = input.theme?.tokens_override ?? {};
30
+ const styleSlots = {
31
+ ...mapLegacyTokenOverridesToStyleSlots(tokensOverride),
32
+ ...(input.theme?.style_slots ?? {}),
33
+ };
34
+ return BuilderSettingsSchema.parse({
35
+ version: BUILDER_SETTINGS_VERSION,
36
+ revision,
37
+ theme: {
38
+ content: input.theme?.content ?? {},
39
+ layout: normalizeLegacyLayout(input.theme?.layout ?? {}),
40
+ style_slots: styleSlots,
41
+ pages: [],
42
+ terms: {},
43
+ },
44
+ });
45
+ }
46
+ export function applyManifestDefaultLayout(settings, manifest) {
47
+ const next = cloneBuilderSettings(settings);
48
+ for (const [pageId, page] of Object.entries(manifest.pages)) {
49
+ // Only seed defaults when the page has never been initialized
50
+ // (no layout entry at all). An explicit empty layout means the
51
+ // merchant intentionally removed all sections and must be
52
+ // preserved across reloads.
53
+ const hasPageLayoutEntry = Object.prototype.hasOwnProperty.call(next.theme.layout, pageId);
54
+ if (hasPageLayoutEntry || page.defaultBlocks.length === 0) {
55
+ continue;
56
+ }
57
+ next.theme.layout[pageId] = {
58
+ blocks: page.defaultBlocks.map((block, index) => createBlockInstance({
59
+ id: createDefaultBlockId(pageId, block.type, index),
60
+ type: block.type,
61
+ variant: block.variant,
62
+ settings: block.settings ?? {},
63
+ })),
64
+ };
65
+ }
66
+ return BuilderSettingsSchema.parse(next);
67
+ }
68
+ export function mapLegacyTokenOverridesToStyleSlots(tokens) {
69
+ const slots = {};
70
+ assignResponsiveNumber(tokens, slots, 'buttons.borderRadius', 'button.radius');
71
+ assignResponsiveNumber(tokens, slots, 'inputs.borderRadius', 'input.radius');
72
+ assignResponsiveNumber(tokens, slots, 'inputs.height', 'input.height');
73
+ assignColor(tokens, slots, 'colors.primary', 'color.primary');
74
+ assignColor(tokens, slots, 'colors.accent', 'color.accent');
75
+ assignColor(tokens, slots, 'colors.background', 'color.background');
76
+ assignColor(tokens, slots, 'colors.foreground', 'color.foreground');
77
+ assignColor(tokens, slots, 'colors.muted', 'color.muted');
78
+ return slots;
79
+ }
80
+ function normalizeLegacyLayout(layout) {
81
+ const normalized = {};
82
+ for (const [pageId, value] of Object.entries(layout)) {
83
+ if (isPageLayout(value)) {
84
+ normalized[pageId] = value;
85
+ continue;
86
+ }
87
+ if (Array.isArray(value)) {
88
+ normalized[pageId] = {
89
+ blocks: value.filter(isBlockInstance),
90
+ };
91
+ continue;
92
+ }
93
+ // Pre-v2 editor shape: { sections: [...] } with entries that
94
+ // commonly only carry { id, visible } (no type field). Use id as
95
+ // the type fallback so merchant layout/visibility survives the
96
+ // migration instead of being dropped by the stricter
97
+ // isBlockInstance check.
98
+ if (value && typeof value === 'object') {
99
+ const record = value;
100
+ const sections = Array.isArray(record.sections) ? record.sections : null;
101
+ if (sections) {
102
+ normalized[pageId] = {
103
+ blocks: sections.flatMap(normalizeLegacySection),
104
+ };
105
+ }
106
+ }
107
+ }
108
+ return normalized;
109
+ }
110
+ function normalizeLegacySection(entry) {
111
+ if (!entry || typeof entry !== 'object')
112
+ return [];
113
+ const record = entry;
114
+ const rawType = typeof record.type === 'string' ? record.type.trim() : '';
115
+ const rawId = typeof record.id === 'string' ? record.id.trim() : '';
116
+ const type = rawType || rawId;
117
+ if (!type)
118
+ return [];
119
+ return [{
120
+ id: rawId || type,
121
+ type,
122
+ variant: typeof record.variant === 'string' ? record.variant : undefined,
123
+ visible: typeof record.visible === 'boolean' ? record.visible : true,
124
+ settings: isRecord(record.settings) ? record.settings : {},
125
+ }];
126
+ }
127
+ function isRecord(value) {
128
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
129
+ }
130
+ function assignResponsiveNumber(tokens, slots, tokenPath, slotId) {
131
+ const value = tokens[tokenPath];
132
+ if (typeof value === 'number' && Number.isFinite(value)) {
133
+ slots[slotId] = { base: value };
134
+ return;
135
+ }
136
+ if (isResponsiveNumber(value)) {
137
+ slots[slotId] = value;
138
+ }
139
+ }
140
+ function assignColor(tokens, slots, tokenPath, slotId) {
141
+ const value = tokens[tokenPath];
142
+ if (typeof value === 'string') {
143
+ slots[slotId] = value;
144
+ }
145
+ }
146
+ function isResponsiveNumber(value) {
147
+ if (!value || typeof value !== 'object') {
148
+ return false;
149
+ }
150
+ const candidate = value;
151
+ return typeof candidate.base === 'number' && Number.isFinite(candidate.base);
152
+ }
153
+ function isBlockInstance(value) {
154
+ if (!value || typeof value !== 'object') {
155
+ return false;
156
+ }
157
+ const candidate = value;
158
+ return typeof candidate.id === 'string' && typeof candidate.type === 'string';
159
+ }
160
+ function isPageLayout(value) {
161
+ if (!value || typeof value !== 'object') {
162
+ return false;
163
+ }
164
+ const candidate = value;
165
+ return Array.isArray(candidate.blocks) && candidate.blocks.every(isBlockInstance);
166
+ }
167
+ function cloneBuilderSettings(settings) {
168
+ if (typeof structuredClone === 'function') {
169
+ return structuredClone(settings);
170
+ }
171
+ return JSON.parse(JSON.stringify(settings));
172
+ }
173
+ function createDefaultBlockId(pageId, blockType, index) {
174
+ return `${pageId}-${blockType}-${index + 1}`;
175
+ }