@shop-on/theme-contract 0.3.0 → 0.5.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.
@@ -21,6 +21,19 @@ export interface CollectedTypes {
21
21
  layouts: Set<string>;
22
22
  }
23
23
  export declare function defaultSettings(schemaDefs?: SchemaSetting[]): Record<string, unknown>;
24
+ /** The `blocks` entry a section or block schema lists to accept app blocks
25
+ * (Shopify's `{ "type": "@app" }`). Honoured on a section, and on a block that
26
+ * is a direct child of a section, so an app block is never deeper than
27
+ * depth 2. */
28
+ export declare const APP_BLOCK_WILDCARD = "@app";
29
+ /** The most block ancestors an app block's container may have: 0 for a
30
+ * section, 1 for a block directly in a section. */
31
+ export declare const MAX_APP_BLOCK_CONTAINER_DEPTH = 1;
32
+ export declare function acceptsAppBlocks(schema: {
33
+ blocks?: {
34
+ type: string;
35
+ }[];
36
+ }): boolean;
24
37
  export declare function staticChildId(childType: string, parentType: string): string;
25
38
  export declare function staticBlockId(type: string): string;
26
39
  export declare function buildStaticChildren(args: {
package/dist/document.js CHANGED
@@ -4,6 +4,17 @@ function defaultSettings(schemaDefs) {
4
4
  for (const st of schemaDefs ?? []) if (st.id && st.default != null) out[st.id] = st.default;
5
5
  return out;
6
6
  }
7
+ /** The `blocks` entry a section or block schema lists to accept app blocks
8
+ * (Shopify's `{ "type": "@app" }`). Honoured on a section, and on a block that
9
+ * is a direct child of a section, so an app block is never deeper than
10
+ * depth 2. */
11
+ const APP_BLOCK_WILDCARD = "@app";
12
+ /** The most block ancestors an app block's container may have: 0 for a
13
+ * section, 1 for a block directly in a section. */
14
+ const MAX_APP_BLOCK_CONTAINER_DEPTH = 1;
15
+ function acceptsAppBlocks(schema) {
16
+ return schema.blocks?.some((b) => b.type === "@app") ?? false;
17
+ }
7
18
  function staticChildId(childType, parentType) {
8
19
  const cleanChild = childType.replace(/^_/, "");
9
20
  const cleanParent = parentType.replace(/^_/, "");
@@ -17,7 +28,7 @@ function buildStaticChildren(args) {
17
28
  const blocks = {};
18
29
  const block_order = [];
19
30
  for (const childDef of args.childDefs) {
20
- if (!childDef.static) continue;
31
+ if (!childDef.static || childDef.type === "@app") continue;
21
32
  const childSchema = lookupSchema(args.getBlockSchema, childDef.type);
22
33
  const id = staticChildId(childDef.type, args.parentType);
23
34
  const block = {
@@ -92,6 +103,7 @@ function buildPresetBlock(blockDef, getBlockSchema, idFor, parentType) {
92
103
  block_order.push(...statics.block_order);
93
104
  }
94
105
  for (const childDef of blockDef.blocks ?? []) {
106
+ if (childDef.type === "@app") continue;
95
107
  const child = buildPresetBlock(childDef, getBlockSchema, idFor, blockDef.type);
96
108
  blocks[child.id] = child.block;
97
109
  if (!child.isStatic) block_order.push(child.id);
@@ -140,6 +152,7 @@ function buildSectionInstance(args) {
140
152
  const block_order = [];
141
153
  const blockDefs = preset?.blocks ?? sectionSchema?.blocks ?? [];
142
154
  for (const blockDef of blockDefs) {
155
+ if (blockDef.type === "@app") continue;
143
156
  const built = buildPresetBlock(blockDef, getBlockSchema, idFor);
144
157
  blocks[built.id] = built.block;
145
158
  if (!built.isStatic) block_order.push(built.id);
@@ -157,11 +170,11 @@ function buildSectionInstance(args) {
157
170
  function selectRenderableSections(sections, order) {
158
171
  const out = [];
159
172
  for (const instanceId of order) {
160
- const section = sections[instanceId];
173
+ const section = Object.hasOwn(sections, instanceId) ? sections[instanceId] : void 0;
161
174
  if (!section || section.disabled) continue;
162
175
  const blocks = [];
163
176
  for (const blockId of section.block_order ?? []) {
164
- const raw = section.blocks?.[blockId];
177
+ const raw = section.blocks && Object.hasOwn(section.blocks, blockId) ? section.blocks[blockId] : void 0;
165
178
  if (!raw || raw.disabled) continue;
166
179
  blocks.push({
167
180
  ...raw,
@@ -197,7 +210,7 @@ function selectRenderableChildren(block) {
197
210
  }
198
211
  for (const id of block.block_order ?? []) {
199
212
  if (seen.has(id)) continue;
200
- const child = block.blocks?.[id];
213
+ const child = block.blocks && Object.hasOwn(block.blocks, id) ? block.blocks[id] : void 0;
201
214
  if (!child || child.disabled) continue;
202
215
  out.push({
203
216
  ...child,
@@ -212,6 +225,7 @@ function collectTypes(template) {
212
225
  const layouts = /* @__PURE__ */ new Set();
213
226
  layouts.add(template.layout ?? "main");
214
227
  const walkBlock = (block) => {
228
+ if (block.source === "app") return;
215
229
  blocks.add(block.type);
216
230
  if (block.blocks) for (const child of Object.values(block.blocks)) walkBlock(child);
217
231
  };
@@ -294,4 +308,4 @@ function deepClone(value) {
294
308
  return JSON.parse(JSON.stringify(value));
295
309
  }
296
310
  //#endregion
297
- export { buildBlockInstance, buildSectionInstance, buildStaticChildren, collectTypes, defaultSettings, seedMissingStatics, selectRenderableChildren, selectRenderableSections, staticBlockId, staticChildId, stripStaticFromBlockOrder };
311
+ export { APP_BLOCK_WILDCARD, MAX_APP_BLOCK_CONTAINER_DEPTH, acceptsAppBlocks, buildBlockInstance, buildSectionInstance, buildStaticChildren, collectTypes, defaultSettings, seedMissingStatics, selectRenderableChildren, selectRenderableSections, staticBlockId, staticChildId, stripStaticFromBlockOrder };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { BlockDefinition, BlockSchema, LayoutGroupDef, SchemaSetting, SectionGroup, SectionGroupData, SectionPreset, SectionSchema, SettingBinding, SettingBindings, SettingValue, SettingsData, SettingsSchema, SettingsSchemaSection, Template, TemplateBlock, TemplateSection } from "./schemas.js";
2
2
  import { BLOCK_PATH_SEPARATOR, blockPathsEqual, isBlockPathPrefix, joinBlockPath, normalizeBlockPath, resolveContainerAtPath, splitBlockPath } from "./block-path.js";
3
3
  import { EDITOR_COMMANDS, EditorCommand, EditorKeyInput, editorCommand, isEditorCommand, shortcutKey, undoHotkey } from "./editor-keys.js";
4
- import { CollectedTypes, SeedMissingStaticsReport, SelectedSection, TemplateShape, buildBlockInstance, buildSectionInstance, buildStaticChildren, collectTypes, defaultSettings, seedMissingStatics, selectRenderableChildren, selectRenderableSections, staticBlockId, staticChildId, stripStaticFromBlockOrder } from "./document.js";
4
+ import { APP_BLOCK_WILDCARD, CollectedTypes, MAX_APP_BLOCK_CONTAINER_DEPTH, SeedMissingStaticsReport, SelectedSection, TemplateShape, acceptsAppBlocks, buildBlockInstance, buildSectionInstance, buildStaticChildren, collectTypes, defaultSettings, seedMissingStatics, selectRenderableChildren, selectRenderableSections, staticBlockId, staticChildId, stripStaticFromBlockOrder } from "./document.js";
5
5
  import { MAX_PALETTE_COLORS, MIN_PALETTE_COLORS, PALETTE_FALLBACK_COLOR, PALETTE_GROUP_TYPE, PALETTE_PREFIX, PALETTE_REF_PREFIX, PaletteColor, comparePaletteIds, hasPaletteRefs, paletteKey, paletteRef, paletteValues, parsePalette, parsePaletteRef, resolvePaletteRefs, resolvePaletteValue, resolveThemeSettingsPalette } from "./color-palette.js";
6
6
  import { FieldSource, ResolverCtx, ResolverScope, ResolverShape, ResolverSpec, SKIP, Skip, buildDisabledAncestorSet, buildParentMap, resolveField, walkBlocks, walkSections } from "./resolvers.js";
7
7
  import { ARTICLE_ATTRIBUTE_KEYS, ArticleAttributeKey, BLOG_ATTRIBUTE_KEYS, BlogAttributeKey, COLLECTION_ATTRIBUTE_KEYS, COLLECTION_INLINE_ATTRIBUTE_KEYS, CollectionAttributeBindingSources, CollectionAttributeKey, CollectionInlineAttributeKey, InlineSegment, InlineSources, PAGE_ATTRIBUTE_KEYS, PRODUCT_ATTRIBUTE_KEYS, PageAttributeKey, ProductAttributeKey, WholeValueBindingOpts, applyCollectionAttributeBindings, applyInlineSources, applyMetafieldBindings, applyMetaobjectFieldBindings, collectCollectionAttributeBindings, collectMetaobjectEntryIds, collectTraversedEntryIds, decodeStorefrontMetafield, encodeSourceToken, escapeInertHtmlText, metafieldDefinitionFitsSetting, metafieldTypeSupportsInlineToken, metafieldTypeToSettingTypes, parseSourceDescriptor, serializeSourceDescriptor, settingTypeSupportsBinding, splitInlineTokens, stripInlineTokenSentinels } from "./metafield-binding.js";
@@ -9,4 +9,4 @@ import { metaobjectGid, metaobjectIdFromGid } from "./metaobject-gid.js";
9
9
  import { RICHTEXT_ALLOWED_TAGS, RichtextTag } from "./richtext.js";
10
10
  import { isNodeIdToken, nodeGid } from "./node-id.js";
11
11
  import { INSTALL_ID_RE, STORE_SLUG_RE } from "./store-path.js";
12
- export { ARTICLE_ATTRIBUTE_KEYS, ArticleAttributeKey, BLOCK_PATH_SEPARATOR, BLOG_ATTRIBUTE_KEYS, BlockDefinition, BlockSchema, BlogAttributeKey, COLLECTION_ATTRIBUTE_KEYS, COLLECTION_INLINE_ATTRIBUTE_KEYS, CollectedTypes, CollectionAttributeBindingSources, CollectionAttributeKey, CollectionInlineAttributeKey, EDITOR_COMMANDS, EditorCommand, EditorKeyInput, FieldSource, INSTALL_ID_RE, InlineSegment, InlineSources, LayoutGroupDef, MAX_PALETTE_COLORS, MIN_PALETTE_COLORS, PAGE_ATTRIBUTE_KEYS, PALETTE_FALLBACK_COLOR, PALETTE_GROUP_TYPE, PALETTE_PREFIX, PALETTE_REF_PREFIX, PRODUCT_ATTRIBUTE_KEYS, PageAttributeKey, PaletteColor, ProductAttributeKey, RICHTEXT_ALLOWED_TAGS, ResolverCtx, ResolverScope, ResolverShape, ResolverSpec, RichtextTag, SKIP, STORE_SLUG_RE, SchemaSetting, SectionGroup, SectionGroupData, SectionPreset, SectionSchema, SeedMissingStaticsReport, SelectedSection, SettingBinding, SettingBindings, SettingValue, SettingsData, SettingsSchema, SettingsSchemaSection, Skip, Template, TemplateBlock, TemplateSection, TemplateShape, WholeValueBindingOpts, applyCollectionAttributeBindings, applyInlineSources, applyMetafieldBindings, applyMetaobjectFieldBindings, blockPathsEqual, buildBlockInstance, buildDisabledAncestorSet, buildParentMap, buildSectionInstance, buildStaticChildren, collectCollectionAttributeBindings, collectMetaobjectEntryIds, collectTraversedEntryIds, collectTypes, comparePaletteIds, decodeStorefrontMetafield, defaultSettings, editorCommand, encodeSourceToken, escapeInertHtmlText, hasPaletteRefs, isBlockPathPrefix, isEditorCommand, isNodeIdToken, joinBlockPath, metafieldDefinitionFitsSetting, metafieldTypeSupportsInlineToken, metafieldTypeToSettingTypes, metaobjectGid, metaobjectIdFromGid, nodeGid, normalizeBlockPath, paletteKey, paletteRef, paletteValues, parsePalette, parsePaletteRef, parseSourceDescriptor, resolveContainerAtPath, resolveField, resolvePaletteRefs, resolvePaletteValue, resolveThemeSettingsPalette, seedMissingStatics, selectRenderableChildren, selectRenderableSections, serializeSourceDescriptor, settingTypeSupportsBinding, shortcutKey, splitBlockPath, splitInlineTokens, staticBlockId, staticChildId, stripInlineTokenSentinels, stripStaticFromBlockOrder, undoHotkey, walkBlocks, walkSections };
12
+ export { APP_BLOCK_WILDCARD, ARTICLE_ATTRIBUTE_KEYS, ArticleAttributeKey, BLOCK_PATH_SEPARATOR, BLOG_ATTRIBUTE_KEYS, BlockDefinition, BlockSchema, BlogAttributeKey, COLLECTION_ATTRIBUTE_KEYS, COLLECTION_INLINE_ATTRIBUTE_KEYS, CollectedTypes, CollectionAttributeBindingSources, CollectionAttributeKey, CollectionInlineAttributeKey, EDITOR_COMMANDS, EditorCommand, EditorKeyInput, FieldSource, INSTALL_ID_RE, InlineSegment, InlineSources, LayoutGroupDef, MAX_APP_BLOCK_CONTAINER_DEPTH, MAX_PALETTE_COLORS, MIN_PALETTE_COLORS, PAGE_ATTRIBUTE_KEYS, PALETTE_FALLBACK_COLOR, PALETTE_GROUP_TYPE, PALETTE_PREFIX, PALETTE_REF_PREFIX, PRODUCT_ATTRIBUTE_KEYS, PageAttributeKey, PaletteColor, ProductAttributeKey, RICHTEXT_ALLOWED_TAGS, ResolverCtx, ResolverScope, ResolverShape, ResolverSpec, RichtextTag, SKIP, STORE_SLUG_RE, SchemaSetting, SectionGroup, SectionGroupData, SectionPreset, SectionSchema, SeedMissingStaticsReport, SelectedSection, SettingBinding, SettingBindings, SettingValue, SettingsData, SettingsSchema, SettingsSchemaSection, Skip, Template, TemplateBlock, TemplateSection, TemplateShape, WholeValueBindingOpts, acceptsAppBlocks, applyCollectionAttributeBindings, applyInlineSources, applyMetafieldBindings, applyMetaobjectFieldBindings, blockPathsEqual, buildBlockInstance, buildDisabledAncestorSet, buildParentMap, buildSectionInstance, buildStaticChildren, collectCollectionAttributeBindings, collectMetaobjectEntryIds, collectTraversedEntryIds, collectTypes, comparePaletteIds, decodeStorefrontMetafield, defaultSettings, editorCommand, encodeSourceToken, escapeInertHtmlText, hasPaletteRefs, isBlockPathPrefix, isEditorCommand, isNodeIdToken, joinBlockPath, metafieldDefinitionFitsSetting, metafieldTypeSupportsInlineToken, metafieldTypeToSettingTypes, metaobjectGid, metaobjectIdFromGid, nodeGid, normalizeBlockPath, paletteKey, paletteRef, paletteValues, parsePalette, parsePaletteRef, parseSourceDescriptor, resolveContainerAtPath, resolveField, resolvePaletteRefs, resolvePaletteValue, resolveThemeSettingsPalette, seedMissingStatics, selectRenderableChildren, selectRenderableSections, serializeSourceDescriptor, settingTypeSupportsBinding, shortcutKey, splitBlockPath, splitInlineTokens, staticBlockId, staticChildId, stripInlineTokenSentinels, stripStaticFromBlockOrder, undoHotkey, walkBlocks, walkSections };
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { BlockDefinition, BlockSchema, SchemaSetting, SectionGroup, SectionPreset, SectionSchema, SettingBinding, SettingBindings, SettingsData, SettingsSchema, SettingsSchemaSection, Template, TemplateBlock, TemplateSection } from "./schemas.js";
2
2
  import { BLOCK_PATH_SEPARATOR, blockPathsEqual, isBlockPathPrefix, joinBlockPath, normalizeBlockPath, resolveContainerAtPath, splitBlockPath } from "./block-path.js";
3
3
  import { EDITOR_COMMANDS, editorCommand, isEditorCommand, shortcutKey, undoHotkey } from "./editor-keys.js";
4
- import { buildBlockInstance, buildSectionInstance, buildStaticChildren, collectTypes, defaultSettings, seedMissingStatics, selectRenderableChildren, selectRenderableSections, staticBlockId, staticChildId, stripStaticFromBlockOrder } from "./document.js";
4
+ import { APP_BLOCK_WILDCARD, MAX_APP_BLOCK_CONTAINER_DEPTH, acceptsAppBlocks, buildBlockInstance, buildSectionInstance, buildStaticChildren, collectTypes, defaultSettings, seedMissingStatics, selectRenderableChildren, selectRenderableSections, staticBlockId, staticChildId, stripStaticFromBlockOrder } from "./document.js";
5
5
  import { MAX_PALETTE_COLORS, MIN_PALETTE_COLORS, PALETTE_FALLBACK_COLOR, PALETTE_GROUP_TYPE, PALETTE_PREFIX, PALETTE_REF_PREFIX, comparePaletteIds, hasPaletteRefs, paletteKey, paletteRef, paletteValues, parsePalette, parsePaletteRef, resolvePaletteRefs, resolvePaletteValue, resolveThemeSettingsPalette } from "./color-palette.js";
6
6
  import { SKIP, buildDisabledAncestorSet, buildParentMap, resolveField, walkBlocks, walkSections } from "./resolvers.js";
7
7
  import { metaobjectGid, metaobjectIdFromGid } from "./metaobject-gid.js";
@@ -9,4 +9,4 @@ import { ARTICLE_ATTRIBUTE_KEYS, BLOG_ATTRIBUTE_KEYS, COLLECTION_ATTRIBUTE_KEYS,
9
9
  import { RICHTEXT_ALLOWED_TAGS } from "./richtext.js";
10
10
  import { isNodeIdToken, nodeGid } from "./node-id.js";
11
11
  import { INSTALL_ID_RE, STORE_SLUG_RE } from "./store-path.js";
12
- export { ARTICLE_ATTRIBUTE_KEYS, BLOCK_PATH_SEPARATOR, BLOG_ATTRIBUTE_KEYS, BlockDefinition, BlockSchema, COLLECTION_ATTRIBUTE_KEYS, COLLECTION_INLINE_ATTRIBUTE_KEYS, EDITOR_COMMANDS, INSTALL_ID_RE, MAX_PALETTE_COLORS, MIN_PALETTE_COLORS, PAGE_ATTRIBUTE_KEYS, PALETTE_FALLBACK_COLOR, PALETTE_GROUP_TYPE, PALETTE_PREFIX, PALETTE_REF_PREFIX, PRODUCT_ATTRIBUTE_KEYS, RICHTEXT_ALLOWED_TAGS, SKIP, STORE_SLUG_RE, SchemaSetting, SectionGroup, SectionPreset, SectionSchema, SettingBinding, SettingBindings, SettingsData, SettingsSchema, SettingsSchemaSection, Template, TemplateBlock, TemplateSection, applyCollectionAttributeBindings, applyInlineSources, applyMetafieldBindings, applyMetaobjectFieldBindings, blockPathsEqual, buildBlockInstance, buildDisabledAncestorSet, buildParentMap, buildSectionInstance, buildStaticChildren, collectCollectionAttributeBindings, collectMetaobjectEntryIds, collectTraversedEntryIds, collectTypes, comparePaletteIds, decodeStorefrontMetafield, defaultSettings, editorCommand, encodeSourceToken, escapeInertHtmlText, hasPaletteRefs, isBlockPathPrefix, isEditorCommand, isNodeIdToken, joinBlockPath, metafieldDefinitionFitsSetting, metafieldTypeSupportsInlineToken, metafieldTypeToSettingTypes, metaobjectGid, metaobjectIdFromGid, nodeGid, normalizeBlockPath, paletteKey, paletteRef, paletteValues, parsePalette, parsePaletteRef, parseSourceDescriptor, resolveContainerAtPath, resolveField, resolvePaletteRefs, resolvePaletteValue, resolveThemeSettingsPalette, seedMissingStatics, selectRenderableChildren, selectRenderableSections, serializeSourceDescriptor, settingTypeSupportsBinding, shortcutKey, splitBlockPath, splitInlineTokens, staticBlockId, staticChildId, stripInlineTokenSentinels, stripStaticFromBlockOrder, undoHotkey, walkBlocks, walkSections };
12
+ export { APP_BLOCK_WILDCARD, ARTICLE_ATTRIBUTE_KEYS, BLOCK_PATH_SEPARATOR, BLOG_ATTRIBUTE_KEYS, BlockDefinition, BlockSchema, COLLECTION_ATTRIBUTE_KEYS, COLLECTION_INLINE_ATTRIBUTE_KEYS, EDITOR_COMMANDS, INSTALL_ID_RE, MAX_APP_BLOCK_CONTAINER_DEPTH, MAX_PALETTE_COLORS, MIN_PALETTE_COLORS, PAGE_ATTRIBUTE_KEYS, PALETTE_FALLBACK_COLOR, PALETTE_GROUP_TYPE, PALETTE_PREFIX, PALETTE_REF_PREFIX, PRODUCT_ATTRIBUTE_KEYS, RICHTEXT_ALLOWED_TAGS, SKIP, STORE_SLUG_RE, SchemaSetting, SectionGroup, SectionPreset, SectionSchema, SettingBinding, SettingBindings, SettingsData, SettingsSchema, SettingsSchemaSection, Template, TemplateBlock, TemplateSection, acceptsAppBlocks, applyCollectionAttributeBindings, applyInlineSources, applyMetafieldBindings, applyMetaobjectFieldBindings, blockPathsEqual, buildBlockInstance, buildDisabledAncestorSet, buildParentMap, buildSectionInstance, buildStaticChildren, collectCollectionAttributeBindings, collectMetaobjectEntryIds, collectTraversedEntryIds, collectTypes, comparePaletteIds, decodeStorefrontMetafield, defaultSettings, editorCommand, encodeSourceToken, escapeInertHtmlText, hasPaletteRefs, isBlockPathPrefix, isEditorCommand, isNodeIdToken, joinBlockPath, metafieldDefinitionFitsSetting, metafieldTypeSupportsInlineToken, metafieldTypeToSettingTypes, metaobjectGid, metaobjectIdFromGid, nodeGid, normalizeBlockPath, paletteKey, paletteRef, paletteValues, parsePalette, parsePaletteRef, parseSourceDescriptor, resolveContainerAtPath, resolveField, resolvePaletteRefs, resolvePaletteValue, resolveThemeSettingsPalette, seedMissingStatics, selectRenderableChildren, selectRenderableSections, serializeSourceDescriptor, settingTypeSupportsBinding, shortcutKey, splitBlockPath, splitInlineTokens, staticBlockId, staticChildId, stripInlineTokenSentinels, stripStaticFromBlockOrder, undoHotkey, walkBlocks, walkSections };
@@ -0,0 +1,9 @@
1
+ //#region src/preset-materializer.d.ts
2
+ export declare function materializePreset(opts: {
3
+ configDir: string;
4
+ presetHandle: string;
5
+ }): Promise<void>;
6
+ /** Deep-merges each of a listing's overridable dirs over `configDir`'s copy;
7
+ * a file only the listing has lands as is. Leaves settings-data alone. */
8
+ export declare function overlayPresetListing(listingDir: string, configDir: string): Promise<void>;
9
+ //#endregion
@@ -0,0 +1,70 @@
1
+ import { LISTING_DIRS, PRESET_HANDLE_RE, mergePresetJson, normalizePresetKey } from "./preset-listing.js";
2
+ import { mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ //#region src/preset-materializer.ts
5
+ function isPlainObject(value) {
6
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
7
+ }
8
+ async function readJson(filePath) {
9
+ try {
10
+ return JSON.parse(await readFile(filePath, "utf-8"));
11
+ } catch (err) {
12
+ if (err instanceof Error && err.code === "ENOENT") return null;
13
+ throw err;
14
+ }
15
+ }
16
+ async function dirExists(dir) {
17
+ try {
18
+ return (await stat(dir)).isDirectory();
19
+ } catch {
20
+ return false;
21
+ }
22
+ }
23
+ async function materializeJsonOverrides(sourceDir, targetDir) {
24
+ if (!await dirExists(sourceDir)) return;
25
+ await mkdir(targetDir, { recursive: true });
26
+ const entries = await readdir(sourceDir, { withFileTypes: true });
27
+ for (const entry of entries) {
28
+ const sourcePath = path.join(sourceDir, entry.name);
29
+ const targetPath = path.join(targetDir, entry.name);
30
+ if (entry.isDirectory()) {
31
+ await materializeJsonOverrides(sourcePath, targetPath);
32
+ continue;
33
+ }
34
+ if (!entry.isFile() || !entry.name.endsWith(".json") || entry.name.startsWith("._")) continue;
35
+ const override = await readJson(sourcePath);
36
+ if (override === null) continue;
37
+ const base = await readJson(targetPath);
38
+ const output = base === null ? override : mergePresetJson(base, override);
39
+ await writeFile(targetPath, `${JSON.stringify(output, null, 2)}\n`, "utf-8");
40
+ }
41
+ }
42
+ async function flattenSettingsData(configDir, presetHandle) {
43
+ const settingsPath = path.join(configDir, "settings-data.json");
44
+ const data = await readJson(settingsPath);
45
+ if (!isPlainObject(data)) return;
46
+ const presets = data.presets;
47
+ if (!isPlainObject(presets)) return;
48
+ const currentPreset = isPlainObject(data.current) ? data.current.preset : null;
49
+ const keys = Object.keys(presets);
50
+ const selectedKey = keys.find((key) => key === presetHandle) ?? keys.find((key) => normalizePresetKey(key) === presetHandle) ?? (typeof currentPreset === "string" && normalizePresetKey(currentPreset) === presetHandle ? currentPreset : null);
51
+ if (!selectedKey) throw new Error(`settings-data.json does not contain selected preset "${presetHandle}" in presets wrapper`);
52
+ const selected = presets[selectedKey];
53
+ if (!isPlainObject(selected) || !isPlainObject(selected.settings)) throw new Error(`settings-data.json preset "${selectedKey}" is missing a settings object`);
54
+ await writeFile(settingsPath, `${JSON.stringify(selected.settings, null, 2)}\n`, "utf-8");
55
+ }
56
+ async function materializePreset(opts) {
57
+ const presetHandle = opts.presetHandle.trim();
58
+ if (!PRESET_HANDLE_RE.test(presetHandle)) throw new Error(`invalid preset handle "${opts.presetHandle}"`);
59
+ const listingDir = path.join(opts.configDir, "listings", presetHandle);
60
+ if (!await dirExists(listingDir)) throw new Error(`selected preset "${presetHandle}" not found at config/listings/${presetHandle}`);
61
+ await flattenSettingsData(opts.configDir, presetHandle);
62
+ await overlayPresetListing(listingDir, opts.configDir);
63
+ }
64
+ /** Deep-merges each of a listing's overridable dirs over `configDir`'s copy;
65
+ * a file only the listing has lands as is. Leaves settings-data alone. */
66
+ async function overlayPresetListing(listingDir, configDir) {
67
+ for (const dir of LISTING_DIRS) await materializeJsonOverrides(path.join(listingDir, dir), path.join(configDir, dir));
68
+ }
69
+ //#endregion
70
+ export { materializePreset, overlayPresetListing };
package/dist/schemas.d.ts CHANGED
@@ -283,6 +283,10 @@ export type TemplateBlock = {
283
283
  custom_css?: string;
284
284
  blocks?: Record<string, TemplateBlock>;
285
285
  block_order?: string[];
286
+ source?: "app";
287
+ appId?: string;
288
+ tag?: string;
289
+ scriptUrl?: string;
286
290
  };
287
291
  export declare const TemplateBlock: z.ZodType<TemplateBlock>;
288
292
  export declare const TemplateSection: z.ZodObject<{
package/dist/schemas.js CHANGED
@@ -115,7 +115,11 @@ const TemplateBlock = z.lazy(() => z.object({
115
115
  customName: z.string().optional(),
116
116
  custom_css: z.string().optional(),
117
117
  blocks: z.record(z.string(), TemplateBlock).optional(),
118
- block_order: z.array(z.string()).optional()
118
+ block_order: z.array(z.string()).optional(),
119
+ source: z.literal("app").optional(),
120
+ appId: z.string().optional(),
121
+ tag: z.string().optional(),
122
+ scriptUrl: z.string().optional()
119
123
  }).passthrough());
120
124
  const TemplateSection = z.object({
121
125
  type: z.string(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shop-on/theme-contract",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "license": "SEE LICENSE IN dist/LICENSE.md",
5
5
  "files": [
6
6
  "dist"
@@ -10,12 +10,14 @@
10
10
  "exports": {
11
11
  ".": "./dist/index.js",
12
12
  "./preset-listing": "./dist/preset-listing.js",
13
+ "./preset-materializer": "./dist/preset-materializer.js",
13
14
  "./package.json": "./package.json"
14
15
  },
15
16
  "dependencies": {
16
17
  "zod": "4.6.7"
17
18
  },
18
19
  "devDependencies": {
20
+ "@types/node": "26.6.5",
19
21
  "typescript": "7.0.2",
20
22
  "vitest": "5.0.5"
21
23
  },