@augeo/smelt 1.2.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 (152) hide show
  1. package/.claude/settings.json +11 -0
  2. package/.github/workflows/verify.yml +64 -0
  3. package/.gitmodules +3 -0
  4. package/.prettierignore +25 -0
  5. package/.prettierrc.cjs +9 -0
  6. package/.zed/settings.json +21 -0
  7. package/AGENTS.md +232 -0
  8. package/LICENSE +21 -0
  9. package/README.md +266 -0
  10. package/biome.json +58 -0
  11. package/dist/cli.d.mts +1 -0
  12. package/dist/cli.mjs +350 -0
  13. package/dist/schema.d.mts +265 -0
  14. package/dist/schema.mjs +21 -0
  15. package/docs/TESTING.md +293 -0
  16. package/docs/assets-plan.md +197 -0
  17. package/docs/build-spec.md +466 -0
  18. package/docs/library-conversion-plan.md +419 -0
  19. package/example/.gitattributes +7 -0
  20. package/example/.shopifyignore +28 -0
  21. package/example/.theme-check.yml +7 -0
  22. package/example/blocks/_built--sections--hero--blocks--feature.liquid +52 -0
  23. package/example/config/settings_schema.json +10 -0
  24. package/example/layout/theme.liquid +25 -0
  25. package/example/locales/en.default.json +1 -0
  26. package/example/package-lock.json +51 -0
  27. package/example/package.json +20 -0
  28. package/example/sections/built--sections--hero.liquid +83 -0
  29. package/example/snippets/built--components--button.liquid +38 -0
  30. package/example/snippets/built--components--card.liquid +33 -0
  31. package/example/src/components/button/button.css +13 -0
  32. package/example/src/components/card/card.css +16 -0
  33. package/example/src/components/card/card.liquid +9 -0
  34. package/example/src/sections/hero/blocks/feature/feature.css +11 -0
  35. package/example/src/sections/hero/blocks/feature/feature.liquid +9 -0
  36. package/example/src/sections/hero/blocks/feature/feature.schema.ts +14 -0
  37. package/example/src/sections/hero/hero.css +15 -0
  38. package/example/src/sections/hero/hero.liquid +16 -0
  39. package/example/src/sections/hero/hero.schema.ts +26 -0
  40. package/example/src/sections/hero/hero.test.ts +43 -0
  41. package/example/src/utilities/labels.ts +5 -0
  42. package/example/templates/index.liquid +1 -0
  43. package/example/tsconfig.json +10 -0
  44. package/example/vitest.config.ts +6 -0
  45. package/lib/build/build.test.ts +475 -0
  46. package/lib/build/build.ts +314 -0
  47. package/lib/build/command.ts +27 -0
  48. package/lib/build/index.ts +1 -0
  49. package/lib/cli.ts +17 -0
  50. package/lib/dev/command.ts +25 -0
  51. package/lib/dev/index.ts +1 -0
  52. package/lib/dev/watch.ts +52 -0
  53. package/lib/resolver.test.ts +275 -0
  54. package/lib/resolver.ts +156 -0
  55. package/lib/schema.ts +37 -0
  56. package/package.json +59 -0
  57. package/scripts/codegen-schema.ts +66 -0
  58. package/src/components/button/button.css +13 -0
  59. package/src/components/button/button.liquid +5 -0
  60. package/src/components/button/button.ts +5 -0
  61. package/src/tsconfig.json +10 -0
  62. package/tests/example.test.ts +101 -0
  63. package/tsconfig.json +20 -0
  64. package/tsdown.config.ts +14 -0
  65. package/vendor/theme-liquid-docs/.gitattributes +10 -0
  66. package/vendor/theme-liquid-docs/.github/CODEOWNERS +1 -0
  67. package/vendor/theme-liquid-docs/.github/CODE_OF_CONDUCT.md +73 -0
  68. package/vendor/theme-liquid-docs/.github/ISSUE_TEMPLATE/bug_report.md +17 -0
  69. package/vendor/theme-liquid-docs/.github/ISSUE_TEMPLATE/feature_request.md +17 -0
  70. package/vendor/theme-liquid-docs/.github/dependabot.yaml +6 -0
  71. package/vendor/theme-liquid-docs/.github/workflows/ci.yml +33 -0
  72. package/vendor/theme-liquid-docs/.github/workflows/cla.yml +27 -0
  73. package/vendor/theme-liquid-docs/.github/workflows/shopify-dev-preview-automation.yml +86 -0
  74. package/vendor/theme-liquid-docs/.github/workflows/update-latest.yml +56 -0
  75. package/vendor/theme-liquid-docs/.prettierrc.json +16 -0
  76. package/vendor/theme-liquid-docs/.vscode/settings.json +28 -0
  77. package/vendor/theme-liquid-docs/LICENSE.md +7 -0
  78. package/vendor/theme-liquid-docs/README.md +48 -0
  79. package/vendor/theme-liquid-docs/ai/claude/CLAUDE.md +1485 -0
  80. package/vendor/theme-liquid-docs/ai/cursor/rules/assets.mdc +15 -0
  81. package/vendor/theme-liquid-docs/ai/cursor/rules/blocks.mdc +339 -0
  82. package/vendor/theme-liquid-docs/ai/cursor/rules/examples/block-example-group.mdc +103 -0
  83. package/vendor/theme-liquid-docs/ai/cursor/rules/examples/block-example-text.mdc +59 -0
  84. package/vendor/theme-liquid-docs/ai/cursor/rules/examples/section-example.mdc +61 -0
  85. package/vendor/theme-liquid-docs/ai/cursor/rules/examples/snippet-example.mdc +72 -0
  86. package/vendor/theme-liquid-docs/ai/cursor/rules/liquid.mdc +837 -0
  87. package/vendor/theme-liquid-docs/ai/cursor/rules/locales.mdc +100 -0
  88. package/vendor/theme-liquid-docs/ai/cursor/rules/localization.mdc +67 -0
  89. package/vendor/theme-liquid-docs/ai/cursor/rules/mcp.mdc +2 -0
  90. package/vendor/theme-liquid-docs/ai/cursor/rules/schemas.mdc +184 -0
  91. package/vendor/theme-liquid-docs/ai/cursor/rules/sections.mdc +84 -0
  92. package/vendor/theme-liquid-docs/ai/cursor/rules/settings-schema.mdc +51 -0
  93. package/vendor/theme-liquid-docs/ai/cursor/rules/snippets.mdc +119 -0
  94. package/vendor/theme-liquid-docs/ai/github/copilot-instructions.md +1485 -0
  95. package/vendor/theme-liquid-docs/ai/liquid.mdc +638 -0
  96. package/vendor/theme-liquid-docs/data/filters.json +6148 -0
  97. package/vendor/theme-liquid-docs/data/latest.json +2 -0
  98. package/vendor/theme-liquid-docs/data/objects.json +20594 -0
  99. package/vendor/theme-liquid-docs/data/shopify_system_translations.json +2586 -0
  100. package/vendor/theme-liquid-docs/data/tags.json +1276 -0
  101. package/vendor/theme-liquid-docs/package.json +20 -0
  102. package/vendor/theme-liquid-docs/schemas/manifest_schema.json +31 -0
  103. package/vendor/theme-liquid-docs/schemas/manifest_theme.json +19 -0
  104. package/vendor/theme-liquid-docs/schemas/manifest_theme_app_extension.json +10 -0
  105. package/vendor/theme-liquid-docs/schemas/theme/app_block_entry.json +13 -0
  106. package/vendor/theme-liquid-docs/schemas/theme/default_setting_values.json +24 -0
  107. package/vendor/theme-liquid-docs/schemas/theme/local_block_entry.json +25 -0
  108. package/vendor/theme-liquid-docs/schemas/theme/preset.json +72 -0
  109. package/vendor/theme-liquid-docs/schemas/theme/preset_blocks.json +91 -0
  110. package/vendor/theme-liquid-docs/schemas/theme/section.json +208 -0
  111. package/vendor/theme-liquid-docs/schemas/theme/setting.json +1413 -0
  112. package/vendor/theme-liquid-docs/schemas/theme/settings.json +10 -0
  113. package/vendor/theme-liquid-docs/schemas/theme/targetted_block_entry.json +15 -0
  114. package/vendor/theme-liquid-docs/schemas/theme/theme_block.json +91 -0
  115. package/vendor/theme-liquid-docs/schemas/theme/theme_block_entry.json +14 -0
  116. package/vendor/theme-liquid-docs/schemas/theme/theme_settings.json +83 -0
  117. package/vendor/theme-liquid-docs/schemas/theme/translations.json +63 -0
  118. package/vendor/theme-liquid-docs/schemas/update/update_extension_schema_v1.json +186 -0
  119. package/vendor/theme-liquid-docs/tests/fixtures/section-nested-blocks.json +18 -0
  120. package/vendor/theme-liquid-docs/tests/fixtures/section-schema-1.json +90 -0
  121. package/vendor/theme-liquid-docs/tests/fixtures/section-schema-2.json +201 -0
  122. package/vendor/theme-liquid-docs/tests/fixtures/section-schema-3.json +29 -0
  123. package/vendor/theme-liquid-docs/tests/fixtures/section-schema-4.json +315 -0
  124. package/vendor/theme-liquid-docs/tests/fixtures/section-schema-5.json +114 -0
  125. package/vendor/theme-liquid-docs/tests/fixtures/section-schema-6.json +63 -0
  126. package/vendor/theme-liquid-docs/tests/fixtures/section-schema-conditional-settings.json +145 -0
  127. package/vendor/theme-liquid-docs/tests/fixtures/section-schema-preset-blocks-as-hash.json +60 -0
  128. package/vendor/theme-liquid-docs/tests/fixtures/section-schema-static-block-preset.json +76 -0
  129. package/vendor/theme-liquid-docs/tests/fixtures/section-settings.json +34 -0
  130. package/vendor/theme-liquid-docs/tests/fixtures/theme-block-1.json +234 -0
  131. package/vendor/theme-liquid-docs/tests/fixtures/theme-block-2.json +253 -0
  132. package/vendor/theme-liquid-docs/tests/fixtures/theme-block-basics.json +48 -0
  133. package/vendor/theme-liquid-docs/tests/fixtures/theme-block-conditional-settings.json +202 -0
  134. package/vendor/theme-liquid-docs/tests/fixtures/theme-block-presets-as-hash.json +50 -0
  135. package/vendor/theme-liquid-docs/tests/fixtures/theme-block-settings.json +34 -0
  136. package/vendor/theme-liquid-docs/tests/fixtures/theme-settings-all-settings.json +313 -0
  137. package/vendor/theme-liquid-docs/tests/fixtures/theme-settings-dawn.json +1469 -0
  138. package/vendor/theme-liquid-docs/tests/fixtures/theme-settings-metadata.json +10 -0
  139. package/vendor/theme-liquid-docs/tests/fixtures/translations-1.json +14 -0
  140. package/vendor/theme-liquid-docs/tests/section.spec.ts +367 -0
  141. package/vendor/theme-liquid-docs/tests/test-constants.ts +58 -0
  142. package/vendor/theme-liquid-docs/tests/test-helpers.ts +104 -0
  143. package/vendor/theme-liquid-docs/tests/theme-settings/color_palette.spec.ts +184 -0
  144. package/vendor/theme-liquid-docs/tests/theme-settings/color_scheme_group.spec.ts +143 -0
  145. package/vendor/theme-liquid-docs/tests/theme-settings/general.spec.ts +192 -0
  146. package/vendor/theme-liquid-docs/tests/theme-settings/metaobject.spec.ts +94 -0
  147. package/vendor/theme-liquid-docs/tests/theme-settings/resource_list.spec.ts +58 -0
  148. package/vendor/theme-liquid-docs/tests/theme-settings/theme-metadata.spec.ts +59 -0
  149. package/vendor/theme-liquid-docs/tests/theme_block.spec.ts +266 -0
  150. package/vendor/theme-liquid-docs/tests/translations_schema.spec.ts +31 -0
  151. package/vendor/theme-liquid-docs/yarn.lock +543 -0
  152. package/vitest.config.ts +7 -0
package/biome.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "$schema": "https://biomejs.dev/schemas/2.4.16/schema.json",
3
+ "vcs": {
4
+ "enabled": true,
5
+ "clientKind": "git",
6
+ "useIgnoreFile": true
7
+ },
8
+ "files": {
9
+ "ignoreUnknown": false,
10
+ "includes": [
11
+ "lib/**",
12
+ "!lib/*.generated.ts",
13
+ "src/**",
14
+ "tests/**",
15
+ "scripts/**",
16
+ "tsdown.config.ts",
17
+ "vitest.config.ts",
18
+ "example/src/**",
19
+ "example/vitest.config.ts",
20
+ "example/package.json",
21
+ "example/tsconfig.json",
22
+ "package.json",
23
+ "tsconfig.json",
24
+ "src/tsconfig.json",
25
+ "biome.json",
26
+ ".prettierrc.cjs"
27
+ ]
28
+ },
29
+ "formatter": {
30
+ "enabled": true,
31
+ "indentStyle": "tab",
32
+ "lineWidth": 80
33
+ },
34
+ "linter": {
35
+ "enabled": true,
36
+ "rules": {
37
+ "recommended": true,
38
+ "suspicious": {
39
+ "noDuplicateTestHooks": "off"
40
+ }
41
+ }
42
+ },
43
+ "javascript": {
44
+ "formatter": {
45
+ "quoteStyle": "double",
46
+ "trailingCommas": "all"
47
+ }
48
+ },
49
+ "assist": {
50
+ "enabled": true,
51
+ "actions": {
52
+ "source": {
53
+ "organizeImports": "on",
54
+ "useSortedProperties": "on"
55
+ }
56
+ }
57
+ }
58
+ }
package/dist/cli.d.mts ADDED
@@ -0,0 +1 @@
1
+ export { };
package/dist/cli.mjs ADDED
@@ -0,0 +1,350 @@
1
+ #!/usr/bin/env node
2
+ import { defineCommand, runMain } from "citty";
3
+ import { dirname, join, relative } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
6
+ import { build } from "esbuild";
7
+ import { watch } from "chokidar";
8
+ //#region lib/resolver.ts
9
+ const TYPE_NAMES = [
10
+ "sections",
11
+ "blocks",
12
+ "components"
13
+ ];
14
+ async function resolveLayers(layers) {
15
+ const perLayerComponents = await Promise.all(layers.map(walkLayer));
16
+ const allKeys = /* @__PURE__ */ new Map();
17
+ perLayerComponents.flat().forEach(({ type, segments }) => {
18
+ const key = `${type}:${segments.join("/")}`;
19
+ if (!allKeys.has(key)) allKeys.set(key, {
20
+ type,
21
+ segments
22
+ });
23
+ });
24
+ return (await Promise.all(Array.from(allKeys.values()).map(async ({ type, segments }) => {
25
+ const [liquid, ts, css, test, schema] = await Promise.all([
26
+ firstExisting(layers, segments, ".liquid"),
27
+ firstExisting(layers, segments, ".ts"),
28
+ firstExisting(layers, segments, ".css"),
29
+ firstExisting(layers, segments, ".test.ts"),
30
+ firstExisting(layers, segments, ".schema.ts")
31
+ ]);
32
+ if (!liquid) return void 0;
33
+ const component = {
34
+ type,
35
+ segments,
36
+ liquid
37
+ };
38
+ if (ts) component.ts = ts;
39
+ if (css) component.css = css;
40
+ if (test) component.test = test;
41
+ if (schema) component.schema = schema;
42
+ return component;
43
+ }))).filter((component) => component !== void 0);
44
+ }
45
+ async function walkLayer(layer) {
46
+ const sourceRoot = join(layer.path, "src");
47
+ if (!await exists$1(sourceRoot)) return [];
48
+ return (await Promise.all(TYPE_NAMES.map(async (type) => {
49
+ const typeDir = join(sourceRoot, type);
50
+ if (!await exists$1(typeDir)) return [];
51
+ return walkType(typeDir, type, [type]);
52
+ }))).flat();
53
+ }
54
+ async function walkType(directory, type, segments) {
55
+ const directories = (await readdir(directory, { withFileTypes: true })).filter((entry) => entry.isDirectory());
56
+ return (await Promise.all(directories.map(async (entry) => {
57
+ const subdirectory = join(directory, entry.name);
58
+ if (TYPE_NAMES.includes(entry.name)) return walkType(subdirectory, entry.name, [...segments, entry.name]);
59
+ const componentSegments = [...segments, entry.name];
60
+ const here = await exists$1(join(subdirectory, `${entry.name}.liquid`)) ? [{
61
+ type,
62
+ segments: componentSegments
63
+ }] : [];
64
+ const deeper = await walkType(subdirectory, type, componentSegments);
65
+ return [...here, ...deeper];
66
+ }))).flat();
67
+ }
68
+ async function firstExisting(layers, segments, extension) {
69
+ const last = segments[segments.length - 1];
70
+ const relativePath = join("src", ...segments, `${last}${extension}`);
71
+ const winner = (await Promise.all(layers.map(async (layer) => ({
72
+ layer,
73
+ path: join(layer.path, relativePath),
74
+ exists: await exists$1(join(layer.path, relativePath))
75
+ })))).find((candidate) => candidate.exists);
76
+ return winner ? {
77
+ path: winner.path,
78
+ layer: winner.layer
79
+ } : void 0;
80
+ }
81
+ async function exists$1(path) {
82
+ try {
83
+ await stat(path);
84
+ return true;
85
+ } catch {
86
+ return false;
87
+ }
88
+ }
89
+ //#endregion
90
+ //#region lib/build/build.ts
91
+ const BUILT_PREFIX = "built--";
92
+ const BUILT_FILE_PATTERN = /^_?built--/;
93
+ const TYPE_TO_OUTPUT_DIRECTORY = {
94
+ sections: "sections",
95
+ blocks: "blocks",
96
+ components: "snippets"
97
+ };
98
+ async function build$2(context) {
99
+ const outputRoot = context.layers[0]?.path;
100
+ if (!outputRoot) throw new Error("build() requires at least one layer.");
101
+ const components = await resolveLayers(context.layers);
102
+ await cleanBuiltOutputs(outputRoot);
103
+ const lookup = new Map(components.map((component) => [component.segments.join("/"), component]));
104
+ await Promise.all(components.map((component) => buildComponent(component, lookup, outputRoot)));
105
+ return components.length;
106
+ }
107
+ async function cleanBuiltOutputs(outputRoot) {
108
+ await Promise.all(Object.values(TYPE_TO_OUTPUT_DIRECTORY).map(async (directory) => {
109
+ const directoryPath = join(outputRoot, directory);
110
+ if (!await exists(directoryPath)) return;
111
+ const entries = await readdir(directoryPath);
112
+ await Promise.all(entries.filter((entry) => BUILT_FILE_PATTERN.test(entry)).map((entry) => rm(join(directoryPath, entry))));
113
+ }));
114
+ }
115
+ async function buildComponent(component, lookup, outputRoot) {
116
+ const source = await readFile(component.liquid.path, "utf-8");
117
+ assertNoInlineSchema(source, component);
118
+ const body = rewriteRenders(source, component, lookup);
119
+ const sections = [banner(component), body.trimEnd()];
120
+ if (component.schema) {
121
+ const merged = mergePrivateBlocks(await loadSchema(component.schema), component, lookup);
122
+ sections.push(renderSchemaBlock(merged));
123
+ }
124
+ if (component.css) {
125
+ const css = await readFile(component.css.path, "utf-8");
126
+ sections.push(wrapEmbedded("stylesheet", css));
127
+ }
128
+ if (component.ts) sections.push(wrapEmbedded("javascript", await bundleJs(component.ts)));
129
+ const liquid = `${sections.join("\n\n")}\n`;
130
+ const outputDirectory = join(outputRoot, TYPE_TO_OUTPUT_DIRECTORY[component.type]);
131
+ const outputPath = join(outputDirectory, `${builtNameOf(component)}.liquid`);
132
+ await mkdir(outputDirectory, { recursive: true });
133
+ await writeFile(outputPath, liquid);
134
+ }
135
+ function assertNoInlineSchema(source, component) {
136
+ if (!/\{%-?\s*schema\s*-?%\}/.test(source)) return;
137
+ const last = component.segments[component.segments.length - 1];
138
+ throw new Error(`Inline {% schema %} block in ${sourcePathForComponent(component)}. Schemas must live in a sibling ${last}.schema.ts file — import { defineSchemaSection } (or defineSchemaBlock) from "@augeo/smelt/schema".`);
139
+ }
140
+ async function loadSchema(schemaSource) {
141
+ const code = await bundle(schemaSource.path, {
142
+ format: "esm",
143
+ platform: "node",
144
+ target: "node24"
145
+ });
146
+ const loaded = await import(`data:text/javascript,${encodeURIComponent(code)}`);
147
+ if (loaded.schema === void 0) {
148
+ const available = Object.keys(loaded).join(", ") || "no exports";
149
+ throw new Error(`${schemaSource.path} must export a named 'schema' (found: ${available}).`);
150
+ }
151
+ return loaded.schema;
152
+ }
153
+ function renderSchemaBlock(schema) {
154
+ return `{% schema %}\n${JSON.stringify(schema, null, 2)}\n{% endschema %}`;
155
+ }
156
+ /**
157
+ * A block is "private" when its segments were reached through a nested
158
+ * `blocks/` type marker — i.e. there is a `blocks` segment at any position
159
+ * after the root. That covers both a section/component owning private blocks
160
+ * (`sections/hero/blocks/headline`) and a public block owning its own private
161
+ * blocks (`blocks/promo/blocks/item`). Shopify treats files prefixed `_` in
162
+ * `blocks/` as hidden from the merchant picker; we map our nested authoring
163
+ * convention onto that filename convention.
164
+ */
165
+ function isPrivateBlock(component) {
166
+ if (component.type !== "blocks") return false;
167
+ return component.segments.slice(1).includes("blocks");
168
+ }
169
+ /** Filename minus `.liquid` — also the Shopify-facing `type` for blocks. */
170
+ function builtNameOf(component) {
171
+ return `${isPrivateBlock(component) ? "_" : ""}${BUILT_PREFIX}${component.segments.join("--")}`;
172
+ }
173
+ /**
174
+ * Auto-merge immediate private children (blocks under `<parent>/blocks/`) into
175
+ * the parent's `blocks: [...]` schema array. Discovered children are sorted
176
+ * by directory name and prepended; any explicitly-listed entries the author
177
+ * wrote are appended after.
178
+ */
179
+ function mergePrivateBlocks(schema, component, lookup) {
180
+ if (!isPlainObject(schema)) return schema;
181
+ const children = findPrivateChildren(component, lookup);
182
+ if (children.length === 0) return schema;
183
+ const discovered = children.map((child) => ({ type: builtNameOf(child) }));
184
+ const explicit = Array.isArray(schema.blocks) ? schema.blocks : [];
185
+ return {
186
+ ...schema,
187
+ blocks: [...discovered, ...explicit]
188
+ };
189
+ }
190
+ function findPrivateChildren(component, lookup) {
191
+ const parent = component.segments;
192
+ return Array.from(lookup.values()).filter((other) => {
193
+ if (other.type !== "blocks") return false;
194
+ if (other.segments.length !== parent.length + 2) return false;
195
+ if (other.segments[parent.length] !== "blocks") return false;
196
+ return parent.every((segment, index) => other.segments[index] === segment);
197
+ }).sort((a, b) => (a.segments[a.segments.length - 1] ?? "").localeCompare(b.segments[b.segments.length - 1] ?? ""));
198
+ }
199
+ function isPlainObject(value) {
200
+ return typeof value === "object" && value !== null && !Array.isArray(value);
201
+ }
202
+ function banner(component) {
203
+ return `{%- comment -%}
204
+ GENERATED FROM ${sourcePathForComponent(component)} — do not edit this file directly.
205
+ Edit the source and run \`npm run build\`.
206
+ {%- endcomment -%}`;
207
+ }
208
+ function sourcePathForComponent(component) {
209
+ const relativePath = relative(component.liquid.layer.path, component.liquid.path);
210
+ return `${component.liquid.layer.name}/${relativePath}`;
211
+ }
212
+ function wrapEmbedded(tag, body) {
213
+ return `{% ${tag} %}\n${indent(body.trimEnd())}\n{% end${tag} %}`;
214
+ }
215
+ /** Prepends a tab to each non-empty line. */
216
+ function indent(text) {
217
+ return text.replace(/^(?=.)/gm, " ");
218
+ }
219
+ function rewriteRenders(source, component, lookup) {
220
+ return source.replace(/(\{%-?\s*(?:render|include)\s+['"])((?:@|\.)\/[^'"]+)(['"])/g, (_match, prefix, importPath, suffix) => {
221
+ const lookupKey = resolveImport(importPath, component).join("/");
222
+ const target = lookup.get(lookupKey);
223
+ if (!target) throw new Error(`Cannot resolve render '${importPath}' (looked up '${lookupKey}') from ${component.liquid.path}`);
224
+ return `${prefix}${builtNameOf(target)}${suffix}`;
225
+ });
226
+ }
227
+ function resolveImport(importPath, component) {
228
+ if (importPath.startsWith("@/")) return importPath.slice(2).split("/").filter(Boolean);
229
+ if (importPath.startsWith("./")) {
230
+ if (importPath.includes("../")) throw new Error(`'../' is not supported in render path: '${importPath}' (in ${component.liquid.path})`);
231
+ const remaining = importPath.slice(2).split("/").filter(Boolean);
232
+ return [...component.segments, ...remaining];
233
+ }
234
+ throw new Error(`Unexpected import path: '${importPath}'`);
235
+ }
236
+ async function bundleJs(tsSource) {
237
+ return (await bundle(tsSource.path, {
238
+ format: "iife",
239
+ platform: "browser",
240
+ target: "es2022",
241
+ absWorkingDir: tsSource.layer.path
242
+ })).replace(/^( {2})+/gm, (match) => " ".repeat(match.length / 2));
243
+ }
244
+ async function bundle(entryPoint, options) {
245
+ const [first] = (await build({
246
+ entryPoints: [entryPoint],
247
+ bundle: true,
248
+ write: false,
249
+ logLevel: "warning",
250
+ ...options
251
+ })).outputFiles ?? [];
252
+ if (!first) throw new Error(`esbuild produced no output for ${entryPoint}`);
253
+ return first.text;
254
+ }
255
+ async function exists(path) {
256
+ try {
257
+ await stat(path);
258
+ return true;
259
+ } catch {
260
+ return false;
261
+ }
262
+ }
263
+ //#endregion
264
+ //#region lib/build/command.ts
265
+ const smeltRoot$1 = dirname(dirname(fileURLToPath(import.meta.url)));
266
+ const build$1 = defineCommand({
267
+ meta: {
268
+ name: "build",
269
+ description: "Compile src/ into Shopify-flat output directories."
270
+ },
271
+ async run() {
272
+ const count = await build$2({ layers: [{
273
+ name: "consumer",
274
+ path: process.cwd()
275
+ }, {
276
+ name: "@augeo/smelt",
277
+ path: smeltRoot$1
278
+ }] });
279
+ console.log(`Built ${count} component(s).`);
280
+ }
281
+ });
282
+ //#endregion
283
+ //#region lib/dev/watch.ts
284
+ const DEBOUNCE_MS = 100;
285
+ async function runWatch(context) {
286
+ await safeBuild(context);
287
+ const sources = context.layers.map((layer) => join(layer.path, "src"));
288
+ const watcher = watch(sources, {
289
+ ignoreInitial: true,
290
+ ignored: /node_modules/
291
+ });
292
+ let pending;
293
+ function scheduleRebuild() {
294
+ if (pending) clearTimeout(pending);
295
+ pending = setTimeout(() => {
296
+ pending = void 0;
297
+ safeBuild(context);
298
+ }, DEBOUNCE_MS);
299
+ }
300
+ watcher.on("add", scheduleRebuild);
301
+ watcher.on("change", scheduleRebuild);
302
+ watcher.on("unlink", scheduleRebuild);
303
+ process.on("SIGINT", async () => {
304
+ await watcher.close();
305
+ process.exit(0);
306
+ });
307
+ console.log(`smelt dev — watching ${sources.length} layer(s). Ctrl-C to stop.`);
308
+ return new Promise(() => {});
309
+ }
310
+ async function safeBuild(context) {
311
+ const start = Date.now();
312
+ try {
313
+ const count = await build$2(context);
314
+ console.log(`✓ Built ${count} component(s) in ${Date.now() - start}ms`);
315
+ } catch (error) {
316
+ const message = error instanceof Error ? error.message : String(error);
317
+ console.error(`✗ Build failed: ${message}`);
318
+ }
319
+ }
320
+ //#endregion
321
+ //#region lib/dev/command.ts
322
+ const smeltRoot = dirname(dirname(fileURLToPath(import.meta.url)));
323
+ //#endregion
324
+ //#region lib/cli.ts
325
+ runMain(defineCommand({
326
+ meta: {
327
+ name: "smelt",
328
+ description: "Smelt build CLI."
329
+ },
330
+ subCommands: {
331
+ build: build$1,
332
+ dev: defineCommand({
333
+ meta: {
334
+ name: "dev",
335
+ description: "Watch src/ and rebuild on change."
336
+ },
337
+ async run() {
338
+ await runWatch({ layers: [{
339
+ name: "consumer",
340
+ path: process.cwd()
341
+ }, {
342
+ name: "@augeo/smelt",
343
+ path: smeltRoot
344
+ }] });
345
+ }
346
+ })
347
+ }
348
+ }));
349
+ //#endregion
350
+ export {};
@@ -0,0 +1,265 @@
1
+ //#region lib/schema-block.generated.d.ts
2
+ type Setting$1 = {
3
+ [k: string]: unknown | undefined;
4
+ } & {
5
+ /**
6
+ * The type of the input or sidebar setting. This value determines the type of field that gets rendered into the editor.
7
+ */
8
+ type?: "article" | "article_list" | "blog" | "checkbox" | "collection" | "collection_list" | "color" | "color_background" | "color_palette" | "color_scheme" | "color_scheme_group" | "font_picker" | "header" | "html" | "image_picker" | "inline_richtext" | "link_list" | "liquid" | "metaobject" | "metaobject_list" | "number" | "page" | "paragraph" | "product" | "product_list" | "radio" | "range" | "richtext" | "select" | "text" | "text_alignment" | "textarea" | "url" | "video" | "video_url";
9
+ };
10
+ /**
11
+ * Settings that merchants can configure through the theme editor.
12
+ *
13
+ * @minItems 0
14
+ */
15
+ type Settings$1 = Setting$1[];
16
+ type ShopifyLiquidSectionOrBlockPresetSchema$1 = PresetWithBlocksArray$1 | PresetWithBlocksHash$1;
17
+ type PresetWithBlocksArray$1 = PresetBase$1 & {
18
+ name?: unknown;
19
+ category?: unknown;
20
+ settings?: unknown;
21
+ blocks?: BlocksArray$1;
22
+ };
23
+ /**
24
+ * A list of child blocks that you might want to include.
25
+ */
26
+ type BlocksArray$1 = CommonBlockAttributes$1[];
27
+ type PresetWithBlocksHash$1 = PresetBase$1 & {
28
+ name?: unknown;
29
+ settings?: unknown;
30
+ blocks: BlocksHash$1;
31
+ /**
32
+ * The order of blocks in the preset.
33
+ */
34
+ block_order?: unknown[];
35
+ additionalProperties?: never;
36
+ };
37
+ interface ThemeBlockSchema {
38
+ /**
39
+ * The name attribute determines the block title that's shown in the theme editor.
40
+ */
41
+ name?: string;
42
+ settings?: Settings$1;
43
+ /**
44
+ * Theme blocks can accept other app and theme blocks as children using the blocks attribute of their schema.
45
+ */
46
+ blocks?: {
47
+ [k: string]: unknown | undefined;
48
+ }[];
49
+ /**
50
+ * Presets are default configurations of blocks that enable merchants to easily add a block to a JSON template through the theme editor.
51
+ */
52
+ presets?: ShopifyLiquidSectionOrBlockPresetSchema$1[];
53
+ /**
54
+ * The HTML element that is used to wrap the rendered block. Accepts any string up to 50 characters. Can be used to render custom HTML elements. Use null to render without a wrapping element.
55
+ */
56
+ tag?: string | null;
57
+ /**
58
+ * When Shopify renders a block, it's wrapped in an HTML element with the shopify-block class. You can append other classes by using the class attribute.
59
+ */
60
+ class?: string;
61
+ }
62
+ interface PresetBase$1 {
63
+ /**
64
+ * The preset name, which will show in the 'Add section' or 'Add block' picker of the theme editor.
65
+ */
66
+ name: string;
67
+ /**
68
+ * The category of the preset, which will show in the 'Add section' or 'Add block' picker of the theme editor.
69
+ */
70
+ category?: string;
71
+ settings?: DefaultSettingValues$1;
72
+ }
73
+ /**
74
+ * A list of default values for any settings that you might want to populate. Each entry should include the setting name and the value.
75
+ */
76
+ interface DefaultSettingValues$1 {
77
+ [k: string]: (number | boolean | string | string[]) | undefined;
78
+ }
79
+ interface CommonBlockAttributes$1 {
80
+ /**
81
+ * The block type.
82
+ */
83
+ type: string;
84
+ /**
85
+ * The block name.
86
+ */
87
+ name?: string;
88
+ settings?: DefaultSettingValues$1;
89
+ /**
90
+ * If the block is rendered statically or not.
91
+ */
92
+ static?: boolean;
93
+ }
94
+ /**
95
+ * A list of child blocks that you might want to include.
96
+ */
97
+ interface BlocksHash$1 {
98
+ [k: string]: CommonBlockAttributes$1 | undefined;
99
+ }
100
+ //#endregion
101
+ //#region lib/schema-section.generated.d.ts
102
+ type Setting = {
103
+ [k: string]: unknown | undefined;
104
+ } & {
105
+ /**
106
+ * The type of the input or sidebar setting. This value determines the type of field that gets rendered into the editor.
107
+ */
108
+ type?: "article" | "article_list" | "blog" | "checkbox" | "collection" | "collection_list" | "color" | "color_background" | "color_palette" | "color_scheme" | "color_scheme_group" | "font_picker" | "header" | "html" | "image_picker" | "inline_richtext" | "link_list" | "liquid" | "metaobject" | "metaobject_list" | "number" | "page" | "paragraph" | "product" | "product_list" | "radio" | "range" | "richtext" | "select" | "text" | "text_alignment" | "textarea" | "url" | "video" | "video_url";
109
+ };
110
+ /**
111
+ * Settings that merchants can configure through the theme editor.
112
+ *
113
+ * @minItems 0
114
+ */
115
+ type Settings = Setting[];
116
+ type ShopifyLiquidSectionOrBlockPresetSchema = PresetWithBlocksArray | PresetWithBlocksHash;
117
+ type PresetWithBlocksArray = PresetBase & {
118
+ name?: unknown;
119
+ category?: unknown;
120
+ settings?: unknown;
121
+ blocks?: BlocksArray;
122
+ };
123
+ /**
124
+ * A list of child blocks that you might want to include.
125
+ */
126
+ type BlocksArray = CommonBlockAttributes[];
127
+ type PresetWithBlocksHash = PresetBase & {
128
+ name?: unknown;
129
+ settings?: unknown;
130
+ blocks: BlocksHash;
131
+ /**
132
+ * The order of blocks in the preset.
133
+ */
134
+ block_order?: unknown[];
135
+ additionalProperties?: never;
136
+ };
137
+ interface SectionSchema {
138
+ /**
139
+ * The name attribute determines the section title that is shown in the theme editor.
140
+ */
141
+ name?: string;
142
+ /**
143
+ * The HTML element that is used to wrap the section.
144
+ */
145
+ tag?: "article" | "aside" | "div" | "footer" | "header" | "section";
146
+ /**
147
+ * Additional CSS class for the section.
148
+ */
149
+ class?: string;
150
+ /**
151
+ * The number of times a section can be added to a template or section group.
152
+ */
153
+ limit?: number;
154
+ settings?: Settings;
155
+ /**
156
+ * There's a limit of 50 blocks per section. You can specify a lower limit with the max_blocks attribute
157
+ */
158
+ max_blocks?: number;
159
+ /**
160
+ * Blocks are reusable modules of content that can be added, removed, and reordered within a section.
161
+ */
162
+ blocks?: {
163
+ [k: string]: unknown | undefined;
164
+ }[];
165
+ /**
166
+ * Presets are default configurations of sections that enable users to easily add a section to a JSON template through the theme editor.
167
+ */
168
+ presets?: ShopifyLiquidSectionOrBlockPresetSchema[];
169
+ /**
170
+ * Default configuration for statically rendered sections.
171
+ */
172
+ default?: {
173
+ settings?: DefaultSettingValues;
174
+ /**
175
+ * Default blocks configurations to ship with this default.
176
+ */
177
+ blocks?: {
178
+ /**
179
+ * The block type.
180
+ */
181
+ type: string;
182
+ settings?: DefaultSettingValues;
183
+ }[];
184
+ };
185
+ /**
186
+ * Sections can provide their own set of translated strings through the locales object. This is separate from the locales directory of the theme, which makes it a useful feature for sections that are meant to be installed on multiple themes or shops.
187
+ */
188
+ locales?: {
189
+ [k: string]: {
190
+ [k: string]: string | undefined;
191
+ } | undefined;
192
+ };
193
+ enabled_on?: SectionToggle;
194
+ disabled_on?: SectionToggle1;
195
+ }
196
+ interface PresetBase {
197
+ /**
198
+ * The preset name, which will show in the 'Add section' or 'Add block' picker of the theme editor.
199
+ */
200
+ name: string;
201
+ /**
202
+ * The category of the preset, which will show in the 'Add section' or 'Add block' picker of the theme editor.
203
+ */
204
+ category?: string;
205
+ settings?: DefaultSettingValues;
206
+ }
207
+ /**
208
+ * A list of default values for any settings that you might want to populate. Each entry should include the setting name and the value.
209
+ */
210
+ interface DefaultSettingValues {
211
+ [k: string]: (number | boolean | string | string[]) | undefined;
212
+ }
213
+ interface CommonBlockAttributes {
214
+ /**
215
+ * The block type.
216
+ */
217
+ type: string;
218
+ /**
219
+ * The block name.
220
+ */
221
+ name?: string;
222
+ settings?: DefaultSettingValues;
223
+ /**
224
+ * If the block is rendered statically or not.
225
+ */
226
+ static?: boolean;
227
+ }
228
+ /**
229
+ * A list of child blocks that you might want to include.
230
+ */
231
+ interface BlocksHash {
232
+ [k: string]: CommonBlockAttributes | undefined;
233
+ }
234
+ /**
235
+ * Restrict the section to certain template page types and section group types.
236
+ */
237
+ interface SectionToggle {
238
+ templates?: ("*" | "404" | "article" | "blog" | "captcha" | "cart" | "collection" | "customers/account" | "customers/activate_account" | "customers/addresses" | "customers/login" | "customers/order" | "customers/register" | "customers/reset_password" | "gift_card" | "index" | "list-collections" | "metaobject" | "page" | "password" | "policy" | "product" | "search")[];
239
+ groups?: string[];
240
+ }
241
+ /**
242
+ * Prevent the section from being used on certain template page types and section group types.
243
+ */
244
+ interface SectionToggle1 {
245
+ templates?: ("*" | "404" | "article" | "blog" | "captcha" | "cart" | "collection" | "customers/account" | "customers/activate_account" | "customers/addresses" | "customers/login" | "customers/order" | "customers/register" | "customers/reset_password" | "gift_card" | "index" | "list-collections" | "metaobject" | "page" | "password" | "policy" | "product" | "search")[];
246
+ groups?: string[];
247
+ }
248
+ //#endregion
249
+ //#region lib/schema.d.ts
250
+ /**
251
+ * Identity helper for authoring a Shopify section schema. The `const T`
252
+ * parameter preserves literal types so the schema author gets autocomplete
253
+ * for setting `type` values, `id`s, etc.
254
+ *
255
+ * Use in `<name>.schema.ts` files under `src/sections/`.
256
+ */
257
+ declare function defineSchemaSection<const T extends SectionSchema>(schema: T): T;
258
+ /**
259
+ * Identity helper for authoring a Shopify theme-block schema.
260
+ *
261
+ * Use in `<name>.schema.ts` files under `src/blocks/`.
262
+ */
263
+ declare function defineSchemaBlock<const T extends ThemeBlockSchema>(schema: T): T;
264
+ //#endregion
265
+ export { type SectionSchema, type ThemeBlockSchema, defineSchemaBlock, defineSchemaSection };
@@ -0,0 +1,21 @@
1
+ //#region lib/schema.ts
2
+ /**
3
+ * Identity helper for authoring a Shopify section schema. The `const T`
4
+ * parameter preserves literal types so the schema author gets autocomplete
5
+ * for setting `type` values, `id`s, etc.
6
+ *
7
+ * Use in `<name>.schema.ts` files under `src/sections/`.
8
+ */
9
+ function defineSchemaSection(schema) {
10
+ return schema;
11
+ }
12
+ /**
13
+ * Identity helper for authoring a Shopify theme-block schema.
14
+ *
15
+ * Use in `<name>.schema.ts` files under `src/blocks/`.
16
+ */
17
+ function defineSchemaBlock(schema) {
18
+ return schema;
19
+ }
20
+ //#endregion
21
+ export { defineSchemaBlock, defineSchemaSection };