@shoppexio/builder-contracts 0.1.0 → 0.1.1

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 (71) hide show
  1. package/dist/builder-contracts.test.js +71 -1
  2. package/dist/builder-settings.d.ts +9 -666
  3. package/dist/builder-settings.d.ts.map +1 -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/events.d.ts +35 -240
  8. package/dist/events.d.ts.map +1 -1
  9. package/dist/events.js +7 -0
  10. package/dist/fields.d.ts +169 -8
  11. package/dist/fields.d.ts.map +1 -1
  12. package/dist/fields.js +27 -0
  13. package/dist/index.d.ts +7 -0
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +7 -0
  16. package/dist/legacy-manifest.d.ts +11 -0
  17. package/dist/legacy-manifest.d.ts.map +1 -1
  18. package/dist/legacy-manifest.js +106 -16
  19. package/dist/migrations.d.ts.map +1 -1
  20. package/dist/migrations.js +50 -4
  21. package/dist/persistence.d.ts +7 -0
  22. package/dist/persistence.d.ts.map +1 -0
  23. package/dist/persistence.js +58 -0
  24. package/dist/preview-boot.d.ts +68 -0
  25. package/dist/preview-boot.d.ts.map +1 -0
  26. package/dist/preview-boot.js +36 -0
  27. package/dist/preview-protocol.d.ts +227 -459
  28. package/dist/preview-protocol.d.ts.map +1 -1
  29. package/dist/preview-protocol.js +112 -0
  30. package/dist/preview-session-resolve.d.ts +115 -0
  31. package/dist/preview-session-resolve.d.ts.map +1 -0
  32. package/dist/preview-session-resolve.js +25 -0
  33. package/dist/preview-trusted-origins.d.ts +4 -0
  34. package/dist/preview-trusted-origins.d.ts.map +1 -0
  35. package/dist/preview-trusted-origins.js +26 -0
  36. package/dist/storefront-initial-data-html.d.ts +17 -0
  37. package/dist/storefront-initial-data-html.d.ts.map +1 -0
  38. package/dist/storefront-initial-data-html.js +83 -0
  39. package/dist/style-slots.d.ts +49 -151
  40. package/dist/style-slots.d.ts.map +1 -1
  41. package/dist/style-slots.js +75 -29
  42. package/dist/theme-manifest.d.ts +229 -454
  43. package/dist/theme-manifest.d.ts.map +1 -1
  44. package/dist/theme-manifest.js +92 -0
  45. package/dist/theme-schemes.d.ts +10 -0
  46. package/dist/theme-schemes.d.ts.map +1 -0
  47. package/dist/theme-schemes.js +24 -0
  48. package/dist/validation.d.ts +1 -1
  49. package/dist/validation.d.ts.map +1 -1
  50. package/dist/validation.js +18 -9
  51. package/package.json +43 -1
  52. package/src/builder-contracts.test.ts +398 -3
  53. package/src/canonical-settings.ts +156 -0
  54. package/src/events.ts +8 -0
  55. package/src/fields.ts +30 -0
  56. package/src/index.ts +7 -0
  57. package/src/legacy-manifest.ts +107 -16
  58. package/src/migrations.ts +65 -4
  59. package/src/persistence.ts +77 -0
  60. package/src/preview-boot.ts +47 -0
  61. package/src/preview-protocol.test.ts +132 -0
  62. package/src/preview-protocol.ts +122 -0
  63. package/src/preview-session-resolve.ts +34 -0
  64. package/src/preview-trusted-origins.test.ts +24 -0
  65. package/src/preview-trusted-origins.ts +35 -0
  66. package/src/storefront-initial-data-html.test.ts +63 -0
  67. package/src/storefront-initial-data-html.ts +112 -0
  68. package/src/style-slots.ts +96 -31
  69. package/src/theme-manifest.ts +118 -1
  70. package/src/theme-schemes.ts +33 -0
  71. package/src/validation.ts +27 -10
@@ -1,6 +1,6 @@
1
1
  import * as z from 'zod/v4';
2
2
  import { BlockSettingsSchema } from './fields.ts';
3
- import { StyleSlotDefaultsSchema, StyleSlotIdSchema } from './style-slots.ts';
3
+ import { StyleSlotDefaultsSchema, StyleSlotIdSchema, type StyleSlotDefaults } from './style-slots.ts';
4
4
 
5
5
  export const ThemeIdSchema = z.string().min(1).regex(/^[a-z0-9][a-z0-9-_.]*$/);
6
6
  export type ThemeId = z.infer<typeof ThemeIdSchema>;
@@ -62,6 +62,7 @@ export const ThemePresetSchema = z
62
62
  .object({
63
63
  label: z.string().min(1),
64
64
  description: z.string().min(1).optional(),
65
+ preview: z.string().min(1).optional(),
65
66
  content: z.record(z.string().min(1), z.unknown()).default({}),
66
67
  layout: z.record(z.string().min(1), z.unknown()).default({}),
67
68
  style_slots: StyleSlotDefaultsSchema.default({}),
@@ -69,15 +70,63 @@ export const ThemePresetSchema = z
69
70
  .strict();
70
71
  export type ThemePreset = z.infer<typeof ThemePresetSchema>;
71
72
 
73
+ export const ThemeManifestMetadataSchema = z
74
+ .object({
75
+ author: z.string().min(1).optional(),
76
+ description: z.string().min(1).optional(),
77
+ preview: z.string().min(1).optional(),
78
+ features: z.array(z.string().min(1)).optional(),
79
+ techStack: z.array(z.string().min(1)).optional(),
80
+ templates: z.array(z.string().min(1)).optional(),
81
+ createdAt: z.string().min(1).optional(),
82
+ demoUrl: z.string().min(1).optional(),
83
+ audience: z.string().min(1).optional(),
84
+ tags: z.array(z.string().min(1)).optional(),
85
+ hotfixPaths: z.array(z.string().min(1)).optional(),
86
+ })
87
+ .strict();
88
+ export type ThemeManifestMetadata = z.infer<typeof ThemeManifestMetadataSchema>;
89
+
90
+ export const ThemeManifestLinkGroupSchema = z
91
+ .object({
92
+ path: z.string().min(1),
93
+ label: z.string().min(1),
94
+ description: z.string().min(1).optional(),
95
+ pageIds: z.array(z.string().min(1)).optional(),
96
+ })
97
+ .strict();
98
+ export type ThemeManifestLinkGroup = z.infer<typeof ThemeManifestLinkGroupSchema>;
99
+
100
+ export const ThemeManifestLinkItemSchema = z
101
+ .object({
102
+ label: z.string().min(1),
103
+ href: z.string().min(1),
104
+ })
105
+ .strict();
106
+ export type ThemeManifestLinkItem = z.infer<typeof ThemeManifestLinkItemSchema>;
107
+
72
108
  export const ThemeManifestSchema = z
73
109
  .object({
74
110
  id: ThemeIdSchema,
75
111
  name: z.string().min(1),
76
112
  version: z.string().min(1),
113
+ author: ThemeManifestMetadataSchema.shape.author,
114
+ description: ThemeManifestMetadataSchema.shape.description,
115
+ preview: ThemeManifestMetadataSchema.shape.preview,
116
+ features: ThemeManifestMetadataSchema.shape.features,
117
+ techStack: ThemeManifestMetadataSchema.shape.techStack,
118
+ templates: ThemeManifestMetadataSchema.shape.templates,
119
+ createdAt: ThemeManifestMetadataSchema.shape.createdAt,
120
+ demoUrl: ThemeManifestMetadataSchema.shape.demoUrl,
121
+ audience: ThemeManifestMetadataSchema.shape.audience,
122
+ tags: ThemeManifestMetadataSchema.shape.tags,
123
+ hotfixPaths: ThemeManifestMetadataSchema.shape.hotfixPaths,
77
124
  pages: z.record(z.string().min(1), ManifestPageSchema),
78
125
  blocks: z.record(z.string().min(1), ManifestBlockSchema),
79
126
  styleSlots: StyleSlotDefaultsSchema.default({}),
80
127
  presets: z.record(z.string().min(1), ThemePresetSchema).default({}),
128
+ linkGroups: z.array(ThemeManifestLinkGroupSchema).optional(),
129
+ defaultLinkItems: z.record(z.string().min(1), z.array(ThemeManifestLinkItemSchema)).optional(),
81
130
  })
82
131
  .strict()
83
132
  .superRefine((manifest, ctx) => {
@@ -138,3 +187,71 @@ export type ThemeManifest = z.infer<typeof ThemeManifestSchema>;
138
187
  export function parseThemeManifest(input: unknown): ThemeManifest {
139
188
  return ThemeManifestSchema.parse(input);
140
189
  }
190
+
191
+ export type ThemeManifestPresetListItem = {
192
+ id: string;
193
+ name: string;
194
+ description?: string;
195
+ preview?: string;
196
+ overrides: {
197
+ content?: Record<string, unknown>;
198
+ layout?: Record<string, unknown>;
199
+ tokens?: Record<string, unknown>;
200
+ style_slots?: StyleSlotDefaults;
201
+ };
202
+ };
203
+
204
+ export function listThemeManifestPresets(input: unknown): ThemeManifestPresetListItem[] {
205
+ if (!isRecord(input) || !isRecord(input.presets)) {
206
+ if (isRecord(input) && Array.isArray(input.presets)) {
207
+ return input.presets.flatMap((preset) => normalizeLegacyPresetListItem(preset));
208
+ }
209
+ return [];
210
+ }
211
+
212
+ const manifest = ThemeManifestSchema.safeParse(input);
213
+ if (!manifest.success) {
214
+ return [];
215
+ }
216
+
217
+ return Object.entries(manifest.data.presets).map(([id, preset]) => ({
218
+ id,
219
+ name: preset.label,
220
+ ...(preset.description ? { description: preset.description } : {}),
221
+ ...(preset.preview ? { preview: preset.preview } : {}),
222
+ overrides: {
223
+ ...(Object.keys(preset.content).length > 0 ? { content: preset.content } : {}),
224
+ ...(Object.keys(preset.layout).length > 0 ? { layout: preset.layout } : {}),
225
+ ...(Object.keys(preset.style_slots).length > 0 ? { style_slots: preset.style_slots } : {}),
226
+ },
227
+ }));
228
+ }
229
+
230
+ function normalizeLegacyPresetListItem(input: unknown): ThemeManifestPresetListItem[] {
231
+ if (!isRecord(input) || typeof input.id !== 'string') {
232
+ return [];
233
+ }
234
+
235
+ const overrides = isRecord(input.overrides) ? input.overrides : {};
236
+ const styleSlots = StyleSlotDefaultsSchema.safeParse(overrides.style_slots ?? overrides.styleSlots);
237
+ return [{
238
+ id: input.id,
239
+ name: typeof input.name === 'string'
240
+ ? input.name
241
+ : typeof input.label === 'string'
242
+ ? input.label
243
+ : input.id,
244
+ ...(typeof input.description === 'string' ? { description: input.description } : {}),
245
+ ...(typeof input.preview === 'string' ? { preview: input.preview } : {}),
246
+ overrides: {
247
+ ...(isRecord(overrides.content) ? { content: overrides.content } : {}),
248
+ ...(isRecord(overrides.layout) ? { layout: overrides.layout } : {}),
249
+ ...(isRecord(overrides.tokens) ? { tokens: overrides.tokens } : {}),
250
+ ...(styleSlots.success && Object.keys(styleSlots.data).length > 0 ? { style_slots: styleSlots.data } : {}),
251
+ },
252
+ }];
253
+ }
254
+
255
+ function isRecord(value: unknown): value is Record<string, unknown> {
256
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
257
+ }
@@ -0,0 +1,33 @@
1
+ export const PUBLIC_BUILDER_THEME_SCHEMES = [
2
+ 'default',
3
+ 'classic',
4
+ 'nebula',
5
+ 'pulse',
6
+ 'phantom',
7
+ 'starlight',
8
+ 'apex',
9
+ 'vault',
10
+ ] as const;
11
+
12
+ export const STARTER_BUILDER_THEME_SCHEMES = ['blank'] as const;
13
+
14
+ export const BUILDER_READY_THEME_SCHEMES = [
15
+ ...STARTER_BUILDER_THEME_SCHEMES,
16
+ ...PUBLIC_BUILDER_THEME_SCHEMES,
17
+ ] as const;
18
+
19
+ export type PublicBuilderThemeScheme = (typeof PUBLIC_BUILDER_THEME_SCHEMES)[number];
20
+ export type StarterBuilderThemeScheme = (typeof STARTER_BUILDER_THEME_SCHEMES)[number];
21
+ export type BuilderReadyThemeScheme = (typeof BUILDER_READY_THEME_SCHEMES)[number];
22
+
23
+ export function isPublicBuilderThemeScheme(value: string): value is PublicBuilderThemeScheme {
24
+ return (PUBLIC_BUILDER_THEME_SCHEMES as readonly string[]).includes(value);
25
+ }
26
+
27
+ export function isStarterBuilderThemeScheme(value: string): value is StarterBuilderThemeScheme {
28
+ return (STARTER_BUILDER_THEME_SCHEMES as readonly string[]).includes(value);
29
+ }
30
+
31
+ export function isBuilderReadyThemeScheme(value: string): value is BuilderReadyThemeScheme {
32
+ return (BUILDER_READY_THEME_SCHEMES as readonly string[]).includes(value);
33
+ }
package/src/validation.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { BlockInstance, BuilderSettings } from './builder-settings.ts';
2
+ import { ThemeStyleSlotIdSchema } from './style-slots.ts';
2
3
  import type { ThemeManifest } from './theme-manifest.ts';
3
4
 
4
5
  export type BuilderManifestValidationIssueCode =
@@ -9,6 +10,7 @@ export type BuilderManifestValidationIssueCode =
9
10
  | 'too_many_block_instances'
10
11
  | 'unknown_block_variant'
11
12
  | 'unknown_block_setting'
13
+ | 'unknown_style_slot'
12
14
  | 'unexposed_style_slot';
13
15
 
14
16
  export type BuilderManifestValidationIssue = {
@@ -33,6 +35,8 @@ export function validateBuilderSettingsAgainstManifest(
33
35
  ): BuilderManifestValidationIssue[] {
34
36
  const issues: BuilderManifestValidationIssue[] = [];
35
37
 
38
+ validateGlobalStyleSlots(settings, manifest, issues);
39
+
36
40
  for (const [pageId, layout] of Object.entries(settings.theme.layout)) {
37
41
  const page = manifest.pages[pageId];
38
42
  if (!page) {
@@ -98,6 +102,28 @@ export function validateBuilderSettingsAgainstManifest(
98
102
  return issues;
99
103
  }
100
104
 
105
+ function validateGlobalStyleSlots(
106
+ settings: BuilderSettings,
107
+ manifest: ThemeManifest,
108
+ issues: BuilderManifestValidationIssue[],
109
+ ): void {
110
+ const declaredThemeSlots = new Set(Object.keys(manifest.styleSlots));
111
+
112
+ for (const slotId of Object.keys(settings.theme.style_slots)) {
113
+ if (!ThemeStyleSlotIdSchema.safeParse(slotId).success) {
114
+ continue;
115
+ }
116
+
117
+ if (!declaredThemeSlots.has(slotId)) {
118
+ issues.push({
119
+ code: 'unknown_style_slot',
120
+ path: `theme.style_slots.${slotId}`,
121
+ message: `Theme style slot "${slotId}" is not declared by the manifest`,
122
+ });
123
+ }
124
+ }
125
+ }
126
+
101
127
  export function assertBuilderSettingsMatchManifest(
102
128
  settings: BuilderSettings,
103
129
  manifest: ThemeManifest,
@@ -130,11 +156,7 @@ function validateBlockSettings(
130
156
  return;
131
157
  }
132
158
 
133
- const allowedSettings = new Set<string>();
134
- for (const path of settingPaths) {
135
- allowedSettings.add(path);
136
- allowedSettings.add(getShortSettingKey(path));
137
- }
159
+ const allowedSettings = new Set(settingPaths);
138
160
 
139
161
  for (const key of Object.keys(block.settings)) {
140
162
  if (allowedSettings.has(key)) {
@@ -168,11 +190,6 @@ function validateBlockVariant(
168
190
  }
169
191
  }
170
192
 
171
- function getShortSettingKey(path: string): string {
172
- const parts = path.split('.').filter(Boolean);
173
- return parts.at(-1) ?? path;
174
- }
175
-
176
193
  function validateBlockStyleOverrides(
177
194
  block: BlockInstance,
178
195
  exposedStyleSlots: string[],