@shoppexio/builder-contracts 0.1.1 → 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 (65) hide show
  1. package/dist/builder-settings.d.ts +2 -0
  2. package/dist/builder-settings.d.ts.map +1 -1
  3. package/dist/builder-settings.js +2 -1
  4. package/dist/custom-pages.d.ts +15 -0
  5. package/dist/custom-pages.d.ts.map +1 -0
  6. package/dist/custom-pages.js +40 -0
  7. package/dist/dedicated-pages.d.ts +15 -0
  8. package/dist/dedicated-pages.d.ts.map +1 -0
  9. package/dist/dedicated-pages.js +142 -0
  10. package/dist/fields.d.ts +60 -2
  11. package/dist/fields.d.ts.map +1 -1
  12. package/dist/fields.js +4 -4
  13. package/dist/index.d.ts +3 -0
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +3 -0
  16. package/dist/legacy-manifest.d.ts +7 -0
  17. package/dist/legacy-manifest.d.ts.map +1 -1
  18. package/dist/legacy-manifest.js +31 -6
  19. package/dist/migrations.d.ts.map +1 -1
  20. package/dist/migrations.js +5 -2
  21. package/dist/preview-boot.d.ts +2 -2
  22. package/dist/preview-boot.d.ts.map +1 -1
  23. package/dist/preview-boot.js +3 -1
  24. package/dist/preview-session-resolve.d.ts +2 -2
  25. package/dist/preview-session-resolve.d.ts.map +1 -1
  26. package/dist/preview-session-resolve.js +2 -2
  27. package/dist/preview-trusted-origins.d.ts.map +1 -1
  28. package/dist/preview-trusted-origins.js +10 -8
  29. package/dist/storefront-typography-fonts.d.ts +18 -0
  30. package/dist/storefront-typography-fonts.d.ts.map +1 -0
  31. package/dist/storefront-typography-fonts.js +89 -0
  32. package/dist/style-slots.d.ts +2 -2
  33. package/dist/style-slots.d.ts.map +1 -1
  34. package/dist/style-slots.js +5 -3
  35. package/dist/theme-manifest.d.ts +58 -2
  36. package/dist/theme-manifest.d.ts.map +1 -1
  37. package/dist/theme-schemes.d.ts +2 -2
  38. package/dist/theme-schemes.d.ts.map +1 -1
  39. package/dist/theme-schemes.js +1 -0
  40. package/dist/validation.d.ts.map +1 -1
  41. package/dist/validation.js +6 -4
  42. package/package.json +1 -1
  43. package/src/builder-contracts.test.ts +18 -0
  44. package/src/builder-settings.ts +4 -1
  45. package/src/custom-pages.test.ts +74 -0
  46. package/src/custom-pages.ts +70 -0
  47. package/src/dedicated-pages.test.ts +88 -0
  48. package/src/dedicated-pages.ts +173 -0
  49. package/src/fields.ts +4 -4
  50. package/src/index.ts +3 -0
  51. package/src/legacy-manifest.ts +40 -7
  52. package/src/migrations.ts +5 -2
  53. package/src/preview-boot.test.ts +72 -0
  54. package/src/preview-boot.ts +3 -1
  55. package/src/preview-session-resolve.test.ts +37 -0
  56. package/src/preview-session-resolve.ts +2 -2
  57. package/src/storefront-initial-data-html.test.ts +10 -0
  58. package/src/storefront-typography-fonts.test.ts +48 -0
  59. package/src/storefront-typography-fonts.ts +108 -0
  60. package/src/style-slots.ts +6 -3
  61. package/src/theme-schemes.ts +1 -0
  62. package/src/validation.ts +6 -4
  63. package/dist/builder-contracts.test.d.ts +0 -2
  64. package/dist/builder-contracts.test.d.ts.map +0 -1
  65. package/dist/builder-contracts.test.js +0 -431
@@ -1 +1 @@
1
- {"version":3,"file":"theme-manifest.d.ts","sourceRoot":"","sources":["../src/theme-manifest.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"theme-manifest.d.ts","sourceRoot":"","sources":["../src/theme-manifest.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
- export declare const PUBLIC_BUILDER_THEME_SCHEMES: readonly ["default", "classic", "nebula", "pulse", "phantom", "starlight", "apex", "vault"];
1
+ export declare const PUBLIC_BUILDER_THEME_SCHEMES: readonly ["default", "classic", "nebula", "pulse", "phantom", "starlight", "apex", "vault", "clean-minimal"];
2
2
  export declare const STARTER_BUILDER_THEME_SCHEMES: readonly ["blank"];
3
- export declare const BUILDER_READY_THEME_SCHEMES: readonly ["blank", "default", "classic", "nebula", "pulse", "phantom", "starlight", "apex", "vault"];
3
+ export declare const BUILDER_READY_THEME_SCHEMES: readonly ["blank", "default", "classic", "nebula", "pulse", "phantom", "starlight", "apex", "vault", "clean-minimal"];
4
4
  export type PublicBuilderThemeScheme = (typeof PUBLIC_BUILDER_THEME_SCHEMES)[number];
5
5
  export type StarterBuilderThemeScheme = (typeof STARTER_BUILDER_THEME_SCHEMES)[number];
6
6
  export type BuilderReadyThemeScheme = (typeof BUILDER_READY_THEME_SCHEMES)[number];
@@ -1 +1 @@
1
- {"version":3,"file":"theme-schemes.d.ts","sourceRoot":"","sources":["../src/theme-schemes.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,4BAA4B,6FAS/B,CAAC;AAEX,eAAO,MAAM,6BAA6B,oBAAqB,CAAC;AAEhE,eAAO,MAAM,2BAA2B,sGAG9B,CAAC;AAEX,MAAM,MAAM,wBAAwB,GAAG,CAAC,OAAO,4BAA4B,CAAC,CAAC,MAAM,CAAC,CAAC;AACrF,MAAM,MAAM,yBAAyB,GAAG,CAAC,OAAO,6BAA6B,CAAC,CAAC,MAAM,CAAC,CAAC;AACvF,MAAM,MAAM,uBAAuB,GAAG,CAAC,OAAO,2BAA2B,CAAC,CAAC,MAAM,CAAC,CAAC;AAEnF,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,wBAAwB,CAE3F;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,yBAAyB,CAE7F;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,uBAAuB,CAEzF"}
1
+ {"version":3,"file":"theme-schemes.d.ts","sourceRoot":"","sources":["../src/theme-schemes.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,4BAA4B,8GAU/B,CAAC;AAEX,eAAO,MAAM,6BAA6B,oBAAqB,CAAC;AAEhE,eAAO,MAAM,2BAA2B,uHAG9B,CAAC;AAEX,MAAM,MAAM,wBAAwB,GAAG,CAAC,OAAO,4BAA4B,CAAC,CAAC,MAAM,CAAC,CAAC;AACrF,MAAM,MAAM,yBAAyB,GAAG,CAAC,OAAO,6BAA6B,CAAC,CAAC,MAAM,CAAC,CAAC;AACvF,MAAM,MAAM,uBAAuB,GAAG,CAAC,OAAO,2BAA2B,CAAC,CAAC,MAAM,CAAC,CAAC;AAEnF,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,wBAAwB,CAE3F;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,yBAAyB,CAE7F;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,uBAAuB,CAEzF"}
@@ -7,6 +7,7 @@ export const PUBLIC_BUILDER_THEME_SCHEMES = [
7
7
  'starlight',
8
8
  'apex',
9
9
  'vault',
10
+ 'clean-minimal',
10
11
  ];
11
12
  export const STARTER_BUILDER_THEME_SCHEMES = ['blank'];
12
13
  export const BUILDER_READY_THEME_SCHEMES = [
@@ -1 +1 @@
1
- {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE5E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,MAAM,MAAM,kCAAkC,GAC1C,cAAc,GACd,oBAAoB,GACpB,oBAAoB,GACpB,mBAAmB,GACnB,0BAA0B,GAC1B,uBAAuB,GACvB,uBAAuB,GACvB,oBAAoB,GACpB,sBAAsB,CAAC;AAE3B,MAAM,MAAM,8BAA8B,GAAG;IAC3C,IAAI,EAAE,kCAAkC,CAAC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,qBAAa,8BAA+B,SAAQ,KAAK;IACvD,QAAQ,CAAC,MAAM,EAAE,8BAA8B,EAAE,CAAC;gBAEtC,MAAM,EAAE,8BAA8B,EAAE;CAKrD;AAED,wBAAgB,sCAAsC,CACpD,QAAQ,EAAE,eAAe,EACzB,QAAQ,EAAE,aAAa,GACtB,8BAA8B,EAAE,CAoElC;AAwBD,wBAAgB,kCAAkC,CAChD,QAAQ,EAAE,eAAe,EACzB,QAAQ,EAAE,aAAa,GACtB,IAAI,CAKN;AAED,wBAAgB,qCAAqC,CACnD,MAAM,EAAE,8BAA8B,EAAE,GACvC,MAAM,CAQR"}
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAG5E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,MAAM,MAAM,kCAAkC,GAC1C,cAAc,GACd,oBAAoB,GACpB,oBAAoB,GACpB,mBAAmB,GACnB,0BAA0B,GAC1B,uBAAuB,GACvB,uBAAuB,GACvB,oBAAoB,GACpB,sBAAsB,CAAC;AAE3B,MAAM,MAAM,8BAA8B,GAAG;IAC3C,IAAI,EAAE,kCAAkC,CAAC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,qBAAa,8BAA+B,SAAQ,KAAK;IACvD,QAAQ,CAAC,MAAM,EAAE,8BAA8B,EAAE,CAAC;gBAEtC,MAAM,EAAE,8BAA8B,EAAE;CAKrD;AAED,wBAAgB,sCAAsC,CACpD,QAAQ,EAAE,eAAe,EACzB,QAAQ,EAAE,aAAa,GACtB,8BAA8B,EAAE,CAqElC;AAwBD,wBAAgB,kCAAkC,CAChD,QAAQ,EAAE,eAAe,EACzB,QAAQ,EAAE,aAAa,GACtB,IAAI,CAKN;AAED,wBAAgB,qCAAqC,CACnD,MAAM,EAAE,8BAA8B,EAAE,GACvC,MAAM,CAQR"}
@@ -1,3 +1,4 @@
1
+ import { mergeCustomPagesIntoManifest } from "./custom-pages.js";
1
2
  import { ThemeStyleSlotIdSchema } from "./style-slots.js";
2
3
  export class BuilderManifestValidationError extends Error {
3
4
  issues;
@@ -8,10 +9,11 @@ export class BuilderManifestValidationError extends Error {
8
9
  }
9
10
  }
10
11
  export function validateBuilderSettingsAgainstManifest(settings, manifest) {
12
+ const effectiveManifest = mergeCustomPagesIntoManifest(manifest, settings);
11
13
  const issues = [];
12
- validateGlobalStyleSlots(settings, manifest, issues);
14
+ validateGlobalStyleSlots(settings, effectiveManifest, issues);
13
15
  for (const [pageId, layout] of Object.entries(settings.theme.layout)) {
14
- const page = manifest.pages[pageId];
16
+ const page = effectiveManifest.pages[pageId];
15
17
  if (!page) {
16
18
  issues.push({
17
19
  code: 'unknown_page',
@@ -32,7 +34,7 @@ export function validateBuilderSettingsAgainstManifest(settings, manifest) {
32
34
  });
33
35
  }
34
36
  blockIds.add(block.id);
35
- const blockDefinition = manifest.blocks[block.type];
37
+ const blockDefinition = effectiveManifest.blocks[block.type];
36
38
  if (!blockDefinition) {
37
39
  issues.push({
38
40
  code: 'unknown_block_type',
@@ -54,7 +56,7 @@ export function validateBuilderSettingsAgainstManifest(settings, manifest) {
54
56
  validateBlockStyleOverrides(block, blockDefinition.exposedStyleSlots, blockPath, issues);
55
57
  }
56
58
  for (const [blockType, count] of blockTypeCounts.entries()) {
57
- const maxInstances = manifest.blocks[blockType]?.maxInstances;
59
+ const maxInstances = effectiveManifest.blocks[blockType]?.maxInstances;
58
60
  if (maxInstances !== undefined && count > maxInstances) {
59
61
  issues.push({
60
62
  code: 'too_many_block_instances',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shoppexio/builder-contracts",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Shared Builder v2 contracts for Shoppex dashboard, backend, preview runtime, and themes",
5
5
  "type": "module",
6
6
  "repository": {
@@ -1,4 +1,6 @@
1
1
  import { describe, expect, test } from 'bun:test';
2
+ import { readFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
2
4
  import {
3
5
  BuilderEventSchema,
4
6
  BuilderSettingsSchema,
@@ -17,6 +19,7 @@ import {
17
19
  migrateLegacyBuilderSettings,
18
20
  listThemeManifestPresets,
19
21
  } from './index.ts';
22
+ import { ColorSchema } from './style-slots.ts';
20
23
 
21
24
  describe('@shoppex/builder-contracts', () => {
22
25
  test('accepts an empty builder settings document', () => {
@@ -797,4 +800,19 @@ describe('@shoppex/builder-contracts', () => {
797
800
  });
798
801
  expect(validateBuilderSettingsAgainstManifest(result.settings, manifest)).toEqual([]);
799
802
  });
803
+
804
+ test('accepts transparent color literals used by theme manifests', () => {
805
+ expect(ColorSchema.parse('transparent')).toBe('transparent');
806
+ expect(ColorSchema.parse('#ffffff')).toBe('#ffffff');
807
+ expect(ColorSchema.parse('#00000080')).toBe('#00000080');
808
+ });
809
+
810
+ test('parses official theme manifests with transparent contact-form backgrounds', () => {
811
+ const repoRoot = join(import.meta.dir, '../../..');
812
+ for (const theme of ['default', 'nebula', 'classic', 'pulse', 'starlight', 'vault', 'phantom', 'apex']) {
813
+ const manifestPath = join(repoRoot, 'themes', theme, 'theme.manifest.json');
814
+ const raw = JSON.parse(readFileSync(manifestPath, 'utf8'));
815
+ expect(ThemeManifestSchema.safeParse(raw).success, `${theme} manifest should validate`).toBe(true);
816
+ }
817
+ });
800
818
  });
@@ -31,11 +31,14 @@ export const PageLayoutSchema = z
31
31
  .strict();
32
32
  export type PageLayout = z.infer<typeof PageLayoutSchema>;
33
33
 
34
+ export const CustomPageSlugSchema = z.string().min(1).regex(/^[a-z0-9][a-z0-9-_]*$/);
35
+ export type CustomPageSlug = z.infer<typeof CustomPageSlugSchema>;
36
+
34
37
  export const CustomPageSchema = z
35
38
  .object({
36
39
  id: PageIdSchema,
37
40
  title: z.string().min(1),
38
- slug: z.string().min(1).regex(/^[a-z0-9][a-z0-9-_/]*$/),
41
+ slug: CustomPageSlugSchema,
39
42
  visible: z.boolean().default(true),
40
43
  layout: PageLayoutSchema.default({ blocks: [] }),
41
44
  seo: z
@@ -0,0 +1,74 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import { readFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import {
5
+ MERCHANT_CUSTOM_PAGE_ALLOWED_BLOCKS,
6
+ mergeCustomPagesIntoManifest,
7
+ } from './custom-pages.ts';
8
+ import { CustomPageSchema } from './builder-settings.ts';
9
+ import { createEmptyBuilderSettings } from './migrations.ts';
10
+ import { convertLegacyThemeManifest } from './legacy-manifest.ts';
11
+ import { validateBuilderSettingsAgainstManifest } from './validation.ts';
12
+
13
+ function loadDefaultManifest() {
14
+ const manifestPath = join(import.meta.dir, '../../../themes/default/theme.manifest.json');
15
+ return convertLegacyThemeManifest(JSON.parse(readFileSync(manifestPath, 'utf8')) as unknown);
16
+ }
17
+
18
+ describe('custom pages manifest merge', () => {
19
+ test('keeps MERCHANT_CUSTOM_PAGE_ALLOWED_BLOCKS aligned with config SSOT', () => {
20
+ const configPath = join(import.meta.dir, '../../../config/builder-shared-manifest-blocks.json');
21
+ const config = JSON.parse(readFileSync(configPath, 'utf8')) as {
22
+ customPageAllowedBlocks: string[];
23
+ };
24
+
25
+ expect([...MERCHANT_CUSTOM_PAGE_ALLOWED_BLOCKS]).toEqual(config.customPageAllowedBlocks);
26
+ });
27
+
28
+ test('merges merchant custom pages into the effective manifest', () => {
29
+ const manifest = loadDefaultManifest();
30
+ const settings = createEmptyBuilderSettings(1);
31
+ settings.theme.pages = [
32
+ {
33
+ id: 'custom-about',
34
+ title: 'About Us',
35
+ slug: 'about-us',
36
+ visible: true,
37
+ layout: { blocks: [] },
38
+ },
39
+ ];
40
+ settings.theme.layout['custom-about'] = {
41
+ blocks: [
42
+ {
43
+ id: 'youtube-1',
44
+ type: 'youtube-embed',
45
+ visible: true,
46
+ settings: {
47
+ videoUrl: 'https://youtu.be/dQw4w9WgXcQ',
48
+ },
49
+ },
50
+ ],
51
+ };
52
+
53
+ const effective = mergeCustomPagesIntoManifest(manifest, settings);
54
+ expect(effective.pages['custom-about']).toEqual({
55
+ label: 'About Us',
56
+ previewPath: '/page/about-us',
57
+ allowedBlocks: expect.arrayContaining(['youtube-embed', 'custom-html']),
58
+ defaultBlocks: [],
59
+ });
60
+ expect(validateBuilderSettingsAgainstManifest(settings, manifest)).toEqual([]);
61
+ });
62
+
63
+ test('rejects multi-segment custom page slugs', () => {
64
+ const result = CustomPageSchema.safeParse({
65
+ id: 'custom-help-faq',
66
+ title: 'Help FAQ',
67
+ slug: 'help/faq',
68
+ visible: true,
69
+ layout: { blocks: [] },
70
+ });
71
+
72
+ expect(result.success).toBe(false);
73
+ });
74
+ });
@@ -0,0 +1,70 @@
1
+ import type { BuilderSettings } from './builder-settings.ts';
2
+ import type { ManifestPage, ThemeManifest } from './theme-manifest.ts';
3
+
4
+ /**
5
+ * Allowed embed/content blocks on merchant custom pages (`theme.pages[]`).
6
+ * Keep aligned with `customPageAllowedBlocks` in
7
+ * `config/builder-shared-manifest-blocks.json`.
8
+ */
9
+ export const MERCHANT_CUSTOM_PAGE_ALLOWED_BLOCKS = [
10
+ 'custom-html',
11
+ 'youtube-embed',
12
+ 'text-block',
13
+ ] as const;
14
+
15
+ export type MerchantCustomPageAllowedBlock =
16
+ (typeof MERCHANT_CUSTOM_PAGE_ALLOWED_BLOCKS)[number];
17
+
18
+ export function resolveMerchantCustomPageAllowedBlocks(
19
+ manifest: ThemeManifest,
20
+ ): string[] {
21
+ return MERCHANT_CUSTOM_PAGE_ALLOWED_BLOCKS.filter(
22
+ (blockType) => blockType in manifest.blocks,
23
+ );
24
+ }
25
+
26
+ export function buildMerchantCustomPageManifestEntry(
27
+ customPage: BuilderSettings['theme']['pages'][number],
28
+ manifest: ThemeManifest,
29
+ ): ManifestPage {
30
+ return {
31
+ label: customPage.title,
32
+ previewPath: `/page/${customPage.slug}`,
33
+ allowedBlocks: resolveMerchantCustomPageAllowedBlocks(manifest),
34
+ defaultBlocks: [],
35
+ };
36
+ }
37
+
38
+ export function mergeCustomPagesIntoManifest(
39
+ manifest: ThemeManifest,
40
+ settings: BuilderSettings,
41
+ ): ThemeManifest {
42
+ if (settings.theme.pages.length === 0) {
43
+ return manifest;
44
+ }
45
+
46
+ const pages = { ...manifest.pages };
47
+ for (const customPage of settings.theme.pages) {
48
+ if (pages[customPage.id]) {
49
+ continue;
50
+ }
51
+
52
+ pages[customPage.id] = buildMerchantCustomPageManifestEntry(customPage, manifest);
53
+ }
54
+
55
+ return { ...manifest, pages };
56
+ }
57
+
58
+ export function findCustomPageBySlug(
59
+ settings: BuilderSettings,
60
+ slug: string,
61
+ ): BuilderSettings['theme']['pages'][number] | undefined {
62
+ return settings.theme.pages.find((page) => page.slug === slug);
63
+ }
64
+
65
+ export function findCustomPageById(
66
+ settings: BuilderSettings,
67
+ pageId: string,
68
+ ): BuilderSettings['theme']['pages'][number] | undefined {
69
+ return settings.theme.pages.find((page) => page.id === pageId);
70
+ }
@@ -0,0 +1,88 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import {
3
+ createDedicatedPageBlockType,
4
+ dedicatedPageContentKey,
5
+ dedicatedPagePreviewPath,
6
+ migrateDedicatedPageContent,
7
+ migrateDedicatedPageLayout,
8
+ normalizeDedicatedPageId,
9
+ } from './dedicated-pages.ts';
10
+
11
+ describe('dedicated-pages', () => {
12
+ test('normalizeDedicatedPageId maps legacy reviews id', () => {
13
+ expect(normalizeDedicatedPageId('reviews')).toBe('reviews-page');
14
+ expect(normalizeDedicatedPageId('faq-page')).toBe('faq-page');
15
+ });
16
+
17
+ test('createDedicatedPageBlockType follows page-{pageId} rule', () => {
18
+ expect(createDedicatedPageBlockType('faq-page')).toBe('page-faq-page');
19
+ expect(createDedicatedPageBlockType('reviews-page')).toBe('page-reviews-page');
20
+ expect(createDedicatedPageBlockType('reviews')).toBe('page-reviews');
21
+ });
22
+
23
+ test('dedicatedPagePreviewPath resolves known storefront paths', () => {
24
+ expect(dedicatedPagePreviewPath('contact-page')).toBe('/contact');
25
+ expect(dedicatedPagePreviewPath('reviews')).toBe('/reviews');
26
+ expect(dedicatedPagePreviewPath('home')).toBeNull();
27
+ });
28
+
29
+ test('dedicatedPageContentKey uses canonical page id', () => {
30
+ expect(dedicatedPageContentKey('reviews', 'title')).toBe('pages.reviews-page.title');
31
+ });
32
+
33
+ test('migrateDedicatedPageLayout renames legacy reviews page layout', () => {
34
+ const migrated = migrateDedicatedPageLayout({
35
+ home: { blocks: [] },
36
+ reviews: {
37
+ blocks: [{ id: 'reviews-1', type: 'page-reviews', visible: true, settings: {} }],
38
+ },
39
+ });
40
+
41
+ expect(migrated.reviews).toBeUndefined();
42
+ expect(migrated['reviews-page']).toMatchObject({
43
+ blocks: [{ id: 'reviews-1', type: 'page-reviews-page', visible: true, settings: {} }],
44
+ });
45
+ });
46
+
47
+ test('migrateDedicatedPageContent renames legacy content keys', () => {
48
+ const migrated = migrateDedicatedPageContent({
49
+ 'pages.reviews.title': 'Reviews',
50
+ 'tool.title': 'Free Tool',
51
+ 'hero.title': 'Home',
52
+ });
53
+
54
+ expect(migrated['pages.reviews.title']).toBeUndefined();
55
+ expect(migrated['pages.reviews-page.title']).toBe('Reviews');
56
+ expect(migrated['tool.title']).toBeUndefined();
57
+ expect(migrated['pages.tool-page.title']).toBe('Free Tool');
58
+ expect(migrated['hero.title']).toBe('Home');
59
+ });
60
+
61
+ test('migrateDedicatedPageLayout renames legacy vault tool page', () => {
62
+ const migrated = migrateDedicatedPageLayout({
63
+ tool: {
64
+ blocks: [{
65
+ id: 'tool-1',
66
+ type: 'tool',
67
+ visible: true,
68
+ settings: { 'tool.title': 'Free Tool' },
69
+ }],
70
+ },
71
+ });
72
+
73
+ expect(migrated.tool).toBeUndefined();
74
+ expect(migrated['tool-page']).toMatchObject({
75
+ blocks: [{
76
+ id: 'tool-1',
77
+ type: 'page-tool-page',
78
+ visible: true,
79
+ settings: { 'pages.tool-page.title': 'Free Tool' },
80
+ }],
81
+ });
82
+ });
83
+
84
+ test('dedicatedPagePreviewPath resolves tool-page', () => {
85
+ expect(dedicatedPagePreviewPath('tool-page')).toBe('/tool');
86
+ expect(dedicatedPagePreviewPath('tool')).toBe('/tool');
87
+ });
88
+ });
@@ -0,0 +1,173 @@
1
+ export const BUILDER_LAYOUT_PAGE_IDS = [
2
+ 'home',
3
+ 'navigation',
4
+ 'product',
5
+ 'footer',
6
+ 'terms',
7
+ ] as const;
8
+
9
+ export type BuilderLayoutPageId = (typeof BUILDER_LAYOUT_PAGE_IDS)[number];
10
+
11
+ export const BUILDER_DEDICATED_PAGE_IDS = [
12
+ 'contact-page',
13
+ 'faq-page',
14
+ 'reviews-page',
15
+ 'feedback-page',
16
+ 'guide-page',
17
+ 'status-page',
18
+ 'checkout-page',
19
+ 'tool-page',
20
+ ] as const;
21
+
22
+ export type BuilderDedicatedPageId = (typeof BUILDER_DEDICATED_PAGE_IDS)[number];
23
+
24
+ /** @deprecated Legacy manifest page ids migrated on settings load. */
25
+ export const DEDICATED_PAGE_ID_ALIASES: Readonly<Record<string, BuilderDedicatedPageId>> = {
26
+ reviews: 'reviews-page',
27
+ tool: 'tool-page',
28
+ };
29
+
30
+ const DEDICATED_PAGE_PREVIEW_PATHS: Readonly<Record<BuilderDedicatedPageId, string>> = {
31
+ 'contact-page': '/contact',
32
+ 'faq-page': '/faq',
33
+ 'reviews-page': '/reviews',
34
+ 'feedback-page': '/feedback',
35
+ 'guide-page': '/guide',
36
+ 'status-page': '/status',
37
+ 'checkout-page': '/checkout',
38
+ 'tool-page': '/tool',
39
+ };
40
+
41
+ export function normalizeDedicatedPageId(pageId: string): string {
42
+ return DEDICATED_PAGE_ID_ALIASES[pageId] ?? pageId;
43
+ }
44
+
45
+ export function isDedicatedPageId(pageId: string): pageId is BuilderDedicatedPageId {
46
+ return (BUILDER_DEDICATED_PAGE_IDS as readonly string[]).includes(pageId);
47
+ }
48
+
49
+ export function dedicatedPagePreviewPath(pageId: string): string | null {
50
+ const normalized = normalizeDedicatedPageId(pageId);
51
+ if (!isDedicatedPageId(normalized)) {
52
+ return null;
53
+ }
54
+ return DEDICATED_PAGE_PREVIEW_PATHS[normalized];
55
+ }
56
+
57
+ export function dedicatedPageContentKey(pageId: string, field: string): string {
58
+ return `pages.${normalizeDedicatedPageId(pageId)}.${field}`;
59
+ }
60
+
61
+ export function normalizeDedicatedPageBlockType(pageId: string): string {
62
+ return createDedicatedPageBlockType(normalizeDedicatedPageId(pageId));
63
+ }
64
+
65
+ export function createDedicatedPageBlockType(pageId: string): string {
66
+ const normalizedPageId = pageId
67
+ .toLowerCase()
68
+ .replace(/[^a-z0-9\-_.]+/g, '-')
69
+ .replace(/^[-_.]+/, '');
70
+ return `page-${normalizedPageId || 'custom'}`;
71
+ }
72
+
73
+ function migrateDedicatedPageBlockSettings(settings: unknown): Record<string, unknown> {
74
+ if (typeof settings !== 'object' || settings === null || Array.isArray(settings)) {
75
+ return {};
76
+ }
77
+
78
+ const next: Record<string, unknown> = {};
79
+ for (const [key, value] of Object.entries(settings)) {
80
+ if (key.startsWith('tool.')) {
81
+ next[`pages.tool-page.${key.slice('tool.'.length)}`] = value;
82
+ continue;
83
+ }
84
+ next[key] = value;
85
+ }
86
+ return next;
87
+ }
88
+
89
+ export function migrateDedicatedPageLayout(layout: Record<string, unknown>): Record<string, unknown> {
90
+ const next: Record<string, unknown> = { ...layout };
91
+
92
+ for (const [legacyId, canonicalId] of Object.entries(DEDICATED_PAGE_ID_ALIASES)) {
93
+ if (!Object.prototype.hasOwnProperty.call(next, legacyId)) {
94
+ continue;
95
+ }
96
+
97
+ const legacyLayout = next[legacyId];
98
+ delete next[legacyId];
99
+
100
+ if (Object.prototype.hasOwnProperty.call(next, canonicalId)) {
101
+ continue;
102
+ }
103
+
104
+ if (typeof legacyLayout !== 'object' || legacyLayout === null || Array.isArray(legacyLayout)) {
105
+ continue;
106
+ }
107
+
108
+ const blocks = (legacyLayout as { blocks?: unknown }).blocks;
109
+ if (!Array.isArray(blocks)) {
110
+ next[canonicalId] = legacyLayout;
111
+ continue;
112
+ }
113
+
114
+ const legacyBlockType = createDedicatedPageBlockType(legacyId);
115
+ const canonicalBlockType = createDedicatedPageBlockType(canonicalId);
116
+
117
+ next[canonicalId] = {
118
+ ...legacyLayout,
119
+ blocks: blocks.map((block) => {
120
+ if (typeof block !== 'object' || block === null || Array.isArray(block)) {
121
+ return block;
122
+ }
123
+ const typed = block as { type?: unknown };
124
+ const isLegacyBlockType = typed.type === legacyBlockType
125
+ || (legacyId === 'tool' && typed.type === 'tool');
126
+ if (!isLegacyBlockType) {
127
+ return block;
128
+ }
129
+ return {
130
+ ...typed,
131
+ type: canonicalBlockType,
132
+ settings: migrateDedicatedPageBlockSettings(
133
+ (typed as { settings?: unknown }).settings,
134
+ ),
135
+ };
136
+ }),
137
+ };
138
+ }
139
+
140
+ return next;
141
+ }
142
+
143
+ export function migrateDedicatedPageContent(content: Record<string, unknown>): Record<string, unknown> {
144
+ const next: Record<string, unknown> = { ...content };
145
+
146
+ for (const [key, value] of Object.entries(next)) {
147
+ if (key.startsWith('tool.') && !key.startsWith('tool-page.')) {
148
+ const canonicalKey = `pages.tool-page.${key.slice('tool.'.length)}`;
149
+ if (!Object.prototype.hasOwnProperty.call(next, canonicalKey)) {
150
+ next[canonicalKey] = value;
151
+ }
152
+ delete next[key];
153
+ }
154
+ }
155
+
156
+ for (const [legacyId, canonicalId] of Object.entries(DEDICATED_PAGE_ID_ALIASES)) {
157
+ const legacyPrefix = `pages.${legacyId}.`;
158
+ const canonicalPrefix = `pages.${canonicalId}.`;
159
+
160
+ for (const [key, value] of Object.entries(next)) {
161
+ if (!key.startsWith(legacyPrefix)) {
162
+ continue;
163
+ }
164
+ const canonicalKey = `${canonicalPrefix}${key.slice(legacyPrefix.length)}`;
165
+ if (!Object.prototype.hasOwnProperty.call(next, canonicalKey)) {
166
+ next[canonicalKey] = value;
167
+ }
168
+ delete next[key];
169
+ }
170
+ }
171
+
172
+ return next;
173
+ }
package/src/fields.ts CHANGED
@@ -8,10 +8,10 @@ const FieldBaseSchema = z
8
8
  defaultValue: z.unknown().optional(),
9
9
  required: z.boolean().optional(),
10
10
  // Groups settings within the Inspector. "content" (default) shows
11
- // under the Block Settings collapsible; "style" shows under a
12
- // separate Style collapsible. Themes pick up the value via the
13
- // existing block.settings.path lookup — no runtime change.
14
- group: z.enum(['content', 'style']).optional(),
11
+ // under the Block Settings collapsible; "profile" shows under Profile
12
+ // banner; "style" shows under Style; "search" shows under Search bar.
13
+ // Themes pick up the value via the existing block.settings.path lookup.
14
+ group: z.enum(['content', 'profile', 'style', 'search']).optional(),
15
15
  })
16
16
  .strict();
17
17
 
package/src/index.ts CHANGED
@@ -1,3 +1,5 @@
1
+ export * from './custom-pages.ts';
2
+ export * from './dedicated-pages.ts';
1
3
  export * from './builder-settings.ts';
2
4
  export * from './canonical-settings.ts';
3
5
  export * from './events.ts';
@@ -10,6 +12,7 @@ export * from './preview-protocol.ts';
10
12
  export * from './preview-session-resolve.ts';
11
13
  export * from './preview-trusted-origins.ts';
12
14
  export * from './storefront-initial-data-html.ts';
15
+ export * from './storefront-typography-fonts.ts';
13
16
  export * from './style-slots.ts';
14
17
  export * from './theme-manifest.ts';
15
18
  export * from './theme-schemes.ts';
@@ -1,4 +1,5 @@
1
1
  import * as z from 'zod/v4';
2
+ import { createDedicatedPageBlockType } from './dedicated-pages.ts';
2
3
  import { BuilderFieldSchema, type BuilderField } from './fields.ts';
3
4
  import { ThemeManifestSchema, type ThemeManifest } from './theme-manifest.ts';
4
5
  import { StyleSlotDefaultsSchema } from './style-slots.ts';
@@ -129,7 +130,7 @@ export function convertLegacyThemeManifest(input: unknown): ThemeManifest {
129
130
  );
130
131
 
131
132
  for (const page of legacy.builder.pages) {
132
- const syntheticBlockType = createSyntheticPageBlockType(page.id);
133
+ const syntheticBlockType = createDedicatedPageBlockType(page.id);
133
134
  if ((page.fields?.length ?? 0) === 0 && (page.lists?.length ?? 0) === 0) {
134
135
  continue;
135
136
  }
@@ -161,7 +162,7 @@ export function convertLegacyThemeManifest(input: unknown): ThemeManifest {
161
162
  hotfixPaths: legacy.hotfixPaths,
162
163
  pages: Object.fromEntries(
163
164
  legacy.builder.pages.map((page) => {
164
- const syntheticBlockType = createSyntheticPageBlockType(page.id);
165
+ const syntheticBlockType = createDedicatedPageBlockType(page.id);
165
166
  const hasSyntheticBlock = blocks[syntheticBlockType] !== undefined;
166
167
  const allowedBlocks = page.blocks.length > 0
167
168
  ? page.blocks
@@ -201,11 +202,6 @@ function normalizeLegacyThemeId(rawId: string): string {
201
202
  return `legacy-${lowered || 'theme'}`;
202
203
  }
203
204
 
204
- function createSyntheticPageBlockType(pageId: string): string {
205
- const normalizedPageId = pageId.toLowerCase().replace(/[^a-z0-9\-_.]+/g, '-').replace(/^[-_.]+/, '');
206
- return `page-${normalizedPageId || 'custom'}`;
207
- }
208
-
209
205
  function convertLegacyThemePresets(presets: LegacyThemePreset[]): ThemeManifest['presets'] {
210
206
  return Object.fromEntries(
211
207
  presets.map((preset) => {
@@ -410,3 +406,40 @@ function createListItemShape(kind: string): Extract<BuilderField, { type: 'list'
410
406
  body: { type: 'richtext', label: 'Body' },
411
407
  };
412
408
  }
409
+
410
+ export type ThemePageBlockOrderPage = {
411
+ allowedBlocks?: string[];
412
+ defaultBlocks?: Array<{ type?: string }>;
413
+ };
414
+
415
+ function resolveManifestPagesForBlockOrder(manifest: unknown): Record<string, ThemePageBlockOrderPage> {
416
+ const canonical = ThemeManifestSchema.safeParse(manifest);
417
+ if (canonical.success) {
418
+ return canonical.data.pages;
419
+ }
420
+
421
+ try {
422
+ return convertLegacyThemeManifest(manifest).pages;
423
+ } catch {
424
+ if (typeof manifest === 'object' && manifest !== null && 'pages' in manifest) {
425
+ const pages = (manifest as { pages?: unknown }).pages;
426
+ if (typeof pages === 'object' && pages !== null && !Array.isArray(pages)) {
427
+ return pages as Record<string, ThemePageBlockOrderPage>;
428
+ }
429
+ }
430
+ return {};
431
+ }
432
+ }
433
+
434
+ export function getThemePageBlockOrderFromManifest(manifest: unknown, pageId: string): string[] {
435
+ const page = resolveManifestPagesForBlockOrder(manifest)[pageId];
436
+ if (!page) {
437
+ return [];
438
+ }
439
+
440
+ const defaultBlockTypes = (Array.isArray(page.defaultBlocks) ? page.defaultBlocks : [])
441
+ .map((block) => (typeof block === 'object' && block !== null ? block.type : undefined))
442
+ .filter((blockType): blockType is string => typeof blockType === 'string' && blockType.length > 0);
443
+
444
+ return defaultBlockTypes.length > 0 ? defaultBlockTypes : page.allowedBlocks ?? [];
445
+ }