@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.
- package/.claude/settings.json +11 -0
- package/.github/workflows/verify.yml +64 -0
- package/.gitmodules +3 -0
- package/.prettierignore +25 -0
- package/.prettierrc.cjs +9 -0
- package/.zed/settings.json +21 -0
- package/AGENTS.md +232 -0
- package/LICENSE +21 -0
- package/README.md +266 -0
- package/biome.json +58 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +350 -0
- package/dist/schema.d.mts +265 -0
- package/dist/schema.mjs +21 -0
- package/docs/TESTING.md +293 -0
- package/docs/assets-plan.md +197 -0
- package/docs/build-spec.md +466 -0
- package/docs/library-conversion-plan.md +419 -0
- package/example/.gitattributes +7 -0
- package/example/.shopifyignore +28 -0
- package/example/.theme-check.yml +7 -0
- package/example/blocks/_built--sections--hero--blocks--feature.liquid +52 -0
- package/example/config/settings_schema.json +10 -0
- package/example/layout/theme.liquid +25 -0
- package/example/locales/en.default.json +1 -0
- package/example/package-lock.json +51 -0
- package/example/package.json +20 -0
- package/example/sections/built--sections--hero.liquid +83 -0
- package/example/snippets/built--components--button.liquid +38 -0
- package/example/snippets/built--components--card.liquid +33 -0
- package/example/src/components/button/button.css +13 -0
- package/example/src/components/card/card.css +16 -0
- package/example/src/components/card/card.liquid +9 -0
- package/example/src/sections/hero/blocks/feature/feature.css +11 -0
- package/example/src/sections/hero/blocks/feature/feature.liquid +9 -0
- package/example/src/sections/hero/blocks/feature/feature.schema.ts +14 -0
- package/example/src/sections/hero/hero.css +15 -0
- package/example/src/sections/hero/hero.liquid +16 -0
- package/example/src/sections/hero/hero.schema.ts +26 -0
- package/example/src/sections/hero/hero.test.ts +43 -0
- package/example/src/utilities/labels.ts +5 -0
- package/example/templates/index.liquid +1 -0
- package/example/tsconfig.json +10 -0
- package/example/vitest.config.ts +6 -0
- package/lib/build/build.test.ts +475 -0
- package/lib/build/build.ts +314 -0
- package/lib/build/command.ts +27 -0
- package/lib/build/index.ts +1 -0
- package/lib/cli.ts +17 -0
- package/lib/dev/command.ts +25 -0
- package/lib/dev/index.ts +1 -0
- package/lib/dev/watch.ts +52 -0
- package/lib/resolver.test.ts +275 -0
- package/lib/resolver.ts +156 -0
- package/lib/schema.ts +37 -0
- package/package.json +59 -0
- package/scripts/codegen-schema.ts +66 -0
- package/src/components/button/button.css +13 -0
- package/src/components/button/button.liquid +5 -0
- package/src/components/button/button.ts +5 -0
- package/src/tsconfig.json +10 -0
- package/tests/example.test.ts +101 -0
- package/tsconfig.json +20 -0
- package/tsdown.config.ts +14 -0
- package/vendor/theme-liquid-docs/.gitattributes +10 -0
- package/vendor/theme-liquid-docs/.github/CODEOWNERS +1 -0
- package/vendor/theme-liquid-docs/.github/CODE_OF_CONDUCT.md +73 -0
- package/vendor/theme-liquid-docs/.github/ISSUE_TEMPLATE/bug_report.md +17 -0
- package/vendor/theme-liquid-docs/.github/ISSUE_TEMPLATE/feature_request.md +17 -0
- package/vendor/theme-liquid-docs/.github/dependabot.yaml +6 -0
- package/vendor/theme-liquid-docs/.github/workflows/ci.yml +33 -0
- package/vendor/theme-liquid-docs/.github/workflows/cla.yml +27 -0
- package/vendor/theme-liquid-docs/.github/workflows/shopify-dev-preview-automation.yml +86 -0
- package/vendor/theme-liquid-docs/.github/workflows/update-latest.yml +56 -0
- package/vendor/theme-liquid-docs/.prettierrc.json +16 -0
- package/vendor/theme-liquid-docs/.vscode/settings.json +28 -0
- package/vendor/theme-liquid-docs/LICENSE.md +7 -0
- package/vendor/theme-liquid-docs/README.md +48 -0
- package/vendor/theme-liquid-docs/ai/claude/CLAUDE.md +1485 -0
- package/vendor/theme-liquid-docs/ai/cursor/rules/assets.mdc +15 -0
- package/vendor/theme-liquid-docs/ai/cursor/rules/blocks.mdc +339 -0
- package/vendor/theme-liquid-docs/ai/cursor/rules/examples/block-example-group.mdc +103 -0
- package/vendor/theme-liquid-docs/ai/cursor/rules/examples/block-example-text.mdc +59 -0
- package/vendor/theme-liquid-docs/ai/cursor/rules/examples/section-example.mdc +61 -0
- package/vendor/theme-liquid-docs/ai/cursor/rules/examples/snippet-example.mdc +72 -0
- package/vendor/theme-liquid-docs/ai/cursor/rules/liquid.mdc +837 -0
- package/vendor/theme-liquid-docs/ai/cursor/rules/locales.mdc +100 -0
- package/vendor/theme-liquid-docs/ai/cursor/rules/localization.mdc +67 -0
- package/vendor/theme-liquid-docs/ai/cursor/rules/mcp.mdc +2 -0
- package/vendor/theme-liquid-docs/ai/cursor/rules/schemas.mdc +184 -0
- package/vendor/theme-liquid-docs/ai/cursor/rules/sections.mdc +84 -0
- package/vendor/theme-liquid-docs/ai/cursor/rules/settings-schema.mdc +51 -0
- package/vendor/theme-liquid-docs/ai/cursor/rules/snippets.mdc +119 -0
- package/vendor/theme-liquid-docs/ai/github/copilot-instructions.md +1485 -0
- package/vendor/theme-liquid-docs/ai/liquid.mdc +638 -0
- package/vendor/theme-liquid-docs/data/filters.json +6148 -0
- package/vendor/theme-liquid-docs/data/latest.json +2 -0
- package/vendor/theme-liquid-docs/data/objects.json +20594 -0
- package/vendor/theme-liquid-docs/data/shopify_system_translations.json +2586 -0
- package/vendor/theme-liquid-docs/data/tags.json +1276 -0
- package/vendor/theme-liquid-docs/package.json +20 -0
- package/vendor/theme-liquid-docs/schemas/manifest_schema.json +31 -0
- package/vendor/theme-liquid-docs/schemas/manifest_theme.json +19 -0
- package/vendor/theme-liquid-docs/schemas/manifest_theme_app_extension.json +10 -0
- package/vendor/theme-liquid-docs/schemas/theme/app_block_entry.json +13 -0
- package/vendor/theme-liquid-docs/schemas/theme/default_setting_values.json +24 -0
- package/vendor/theme-liquid-docs/schemas/theme/local_block_entry.json +25 -0
- package/vendor/theme-liquid-docs/schemas/theme/preset.json +72 -0
- package/vendor/theme-liquid-docs/schemas/theme/preset_blocks.json +91 -0
- package/vendor/theme-liquid-docs/schemas/theme/section.json +208 -0
- package/vendor/theme-liquid-docs/schemas/theme/setting.json +1413 -0
- package/vendor/theme-liquid-docs/schemas/theme/settings.json +10 -0
- package/vendor/theme-liquid-docs/schemas/theme/targetted_block_entry.json +15 -0
- package/vendor/theme-liquid-docs/schemas/theme/theme_block.json +91 -0
- package/vendor/theme-liquid-docs/schemas/theme/theme_block_entry.json +14 -0
- package/vendor/theme-liquid-docs/schemas/theme/theme_settings.json +83 -0
- package/vendor/theme-liquid-docs/schemas/theme/translations.json +63 -0
- package/vendor/theme-liquid-docs/schemas/update/update_extension_schema_v1.json +186 -0
- package/vendor/theme-liquid-docs/tests/fixtures/section-nested-blocks.json +18 -0
- package/vendor/theme-liquid-docs/tests/fixtures/section-schema-1.json +90 -0
- package/vendor/theme-liquid-docs/tests/fixtures/section-schema-2.json +201 -0
- package/vendor/theme-liquid-docs/tests/fixtures/section-schema-3.json +29 -0
- package/vendor/theme-liquid-docs/tests/fixtures/section-schema-4.json +315 -0
- package/vendor/theme-liquid-docs/tests/fixtures/section-schema-5.json +114 -0
- package/vendor/theme-liquid-docs/tests/fixtures/section-schema-6.json +63 -0
- package/vendor/theme-liquid-docs/tests/fixtures/section-schema-conditional-settings.json +145 -0
- package/vendor/theme-liquid-docs/tests/fixtures/section-schema-preset-blocks-as-hash.json +60 -0
- package/vendor/theme-liquid-docs/tests/fixtures/section-schema-static-block-preset.json +76 -0
- package/vendor/theme-liquid-docs/tests/fixtures/section-settings.json +34 -0
- package/vendor/theme-liquid-docs/tests/fixtures/theme-block-1.json +234 -0
- package/vendor/theme-liquid-docs/tests/fixtures/theme-block-2.json +253 -0
- package/vendor/theme-liquid-docs/tests/fixtures/theme-block-basics.json +48 -0
- package/vendor/theme-liquid-docs/tests/fixtures/theme-block-conditional-settings.json +202 -0
- package/vendor/theme-liquid-docs/tests/fixtures/theme-block-presets-as-hash.json +50 -0
- package/vendor/theme-liquid-docs/tests/fixtures/theme-block-settings.json +34 -0
- package/vendor/theme-liquid-docs/tests/fixtures/theme-settings-all-settings.json +313 -0
- package/vendor/theme-liquid-docs/tests/fixtures/theme-settings-dawn.json +1469 -0
- package/vendor/theme-liquid-docs/tests/fixtures/theme-settings-metadata.json +10 -0
- package/vendor/theme-liquid-docs/tests/fixtures/translations-1.json +14 -0
- package/vendor/theme-liquid-docs/tests/section.spec.ts +367 -0
- package/vendor/theme-liquid-docs/tests/test-constants.ts +58 -0
- package/vendor/theme-liquid-docs/tests/test-helpers.ts +104 -0
- package/vendor/theme-liquid-docs/tests/theme-settings/color_palette.spec.ts +184 -0
- package/vendor/theme-liquid-docs/tests/theme-settings/color_scheme_group.spec.ts +143 -0
- package/vendor/theme-liquid-docs/tests/theme-settings/general.spec.ts +192 -0
- package/vendor/theme-liquid-docs/tests/theme-settings/metaobject.spec.ts +94 -0
- package/vendor/theme-liquid-docs/tests/theme-settings/resource_list.spec.ts +58 -0
- package/vendor/theme-liquid-docs/tests/theme-settings/theme-metadata.spec.ts +59 -0
- package/vendor/theme-liquid-docs/tests/theme_block.spec.ts +266 -0
- package/vendor/theme-liquid-docs/tests/translations_schema.spec.ts +31 -0
- package/vendor/theme-liquid-docs/yarn.lock +543 -0
- package/vitest.config.ts +7 -0
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
5
|
+
import type { Layer } from "../resolver.ts";
|
|
6
|
+
import { build } from "./build.ts";
|
|
7
|
+
|
|
8
|
+
interface Fixture {
|
|
9
|
+
path: string;
|
|
10
|
+
layer: Layer;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
describe("build()", () => {
|
|
14
|
+
let tempRoot: string;
|
|
15
|
+
beforeEach(async () => {
|
|
16
|
+
tempRoot = await mkdtemp(join(tmpdir(), "smelt-build-"));
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
let consumer: Fixture;
|
|
20
|
+
beforeEach(async () => {
|
|
21
|
+
consumer = await makeFixture(tempRoot, "consumer");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
let baseline: Fixture;
|
|
25
|
+
beforeEach(async () => {
|
|
26
|
+
baseline = await makeFixture(tempRoot, "baseline");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
afterEach(async () => {
|
|
30
|
+
await rm(tempRoot, { recursive: true, force: true });
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe("with a baseline that owns the only component", () => {
|
|
34
|
+
beforeEach(async () => {
|
|
35
|
+
await writeComponent(baseline, "components/button", {
|
|
36
|
+
liquid: "<button>{{ label }}</button>",
|
|
37
|
+
css: ".btn { color: red; }",
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
beforeEach(async () => {
|
|
41
|
+
await build({ layers: [consumer.layer, baseline.layer] });
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
let output: string;
|
|
45
|
+
beforeEach(async () => {
|
|
46
|
+
output = await readFile(
|
|
47
|
+
join(consumer.path, "snippets/built--components--button.liquid"),
|
|
48
|
+
"utf-8",
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("emits the built file under snippets/", () => {
|
|
53
|
+
expect(output).toBeTruthy();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("inlines the baseline's liquid body", () => {
|
|
57
|
+
expect(output).toContain("<button>{{ label }}</button>");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("inlines the baseline's css in a stylesheet block", () => {
|
|
61
|
+
expect(output).toContain(".btn { color: red; }");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("tags the banner with the baseline's layer name", () => {
|
|
65
|
+
expect(output).toContain(
|
|
66
|
+
"GENERATED FROM baseline/src/components/button/button.liquid",
|
|
67
|
+
);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe("when the consumer shadows the baseline's css", () => {
|
|
72
|
+
beforeEach(async () => {
|
|
73
|
+
await writeComponent(baseline, "components/button", {
|
|
74
|
+
liquid: "<button></button>",
|
|
75
|
+
css: ".btn { color: black; }",
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
beforeEach(async () => {
|
|
79
|
+
await writeComponent(consumer, "components/button", {
|
|
80
|
+
css: ".btn { color: yellow; }",
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
beforeEach(async () => {
|
|
84
|
+
await build({ layers: [consumer.layer, baseline.layer] });
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
let output: string;
|
|
88
|
+
beforeEach(async () => {
|
|
89
|
+
output = await readFile(
|
|
90
|
+
join(consumer.path, "snippets/built--components--button.liquid"),
|
|
91
|
+
"utf-8",
|
|
92
|
+
);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("inlines the consumer's css", () => {
|
|
96
|
+
expect(output).toContain(".btn { color: yellow; }");
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("does not inline the baseline's css", () => {
|
|
100
|
+
expect(output).not.toContain(".btn { color: black; }");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("tags the banner against the baseline (since liquid is inherited)", () => {
|
|
104
|
+
expect(output).toContain(
|
|
105
|
+
"GENERATED FROM baseline/src/components/button/button.liquid",
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe("when a section has a sibling schema.ts file", () => {
|
|
111
|
+
beforeEach(async () => {
|
|
112
|
+
await writeComponent(baseline, "sections/hero", {
|
|
113
|
+
liquid: "<section></section>",
|
|
114
|
+
schema:
|
|
115
|
+
"export const schema = { name: 'Hero', settings: [{ type: 'text', id: 'heading', label: 'Heading' }] };",
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
beforeEach(async () => {
|
|
119
|
+
await build({ layers: [consumer.layer, baseline.layer] });
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
let output: string;
|
|
123
|
+
beforeEach(async () => {
|
|
124
|
+
output = await readFile(
|
|
125
|
+
join(consumer.path, "sections/built--sections--hero.liquid"),
|
|
126
|
+
"utf-8",
|
|
127
|
+
);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("injects a {% schema %} block into the built output", () => {
|
|
131
|
+
expect(output).toContain("{% schema %}");
|
|
132
|
+
expect(output).toContain("{% endschema %}");
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("includes the schema's name in the injected JSON", () => {
|
|
136
|
+
expect(output).toContain('"name": "Hero"');
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("includes the schema's settings in the injected JSON", () => {
|
|
140
|
+
expect(output).toContain('"id": "heading"');
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe("when a section's .liquid has an inline {% schema %} block", () => {
|
|
145
|
+
beforeEach(async () => {
|
|
146
|
+
await writeComponent(baseline, "sections/hero", {
|
|
147
|
+
liquid:
|
|
148
|
+
'<section></section>\n{% schema %}{"name":"FromInline"}{% endschema %}',
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("throws — inline schemas are not allowed", async () => {
|
|
153
|
+
await expect(
|
|
154
|
+
build({ layers: [consumer.layer, baseline.layer] }),
|
|
155
|
+
).rejects.toThrow(/Inline \{% schema %\} block/);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("hints at the expected sibling filename in the error", async () => {
|
|
159
|
+
await expect(
|
|
160
|
+
build({ layers: [consumer.layer, baseline.layer] }),
|
|
161
|
+
).rejects.toThrow(/hero\.schema\.ts/);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
describe("when a section's .liquid has a whitespace-control schema tag", () => {
|
|
166
|
+
beforeEach(async () => {
|
|
167
|
+
await writeComponent(baseline, "sections/hero", {
|
|
168
|
+
liquid:
|
|
169
|
+
'<section></section>\n{%- schema -%}{"name":"FromInline"}{%- endschema -%}',
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("throws — `{%- schema -%}` does not bypass the inline-schema check", async () => {
|
|
174
|
+
await expect(
|
|
175
|
+
build({ layers: [consumer.layer, baseline.layer] }),
|
|
176
|
+
).rejects.toThrow(/Inline \{% schema %\} block/);
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
describe("when the consumer renders a baseline component via @/...", () => {
|
|
181
|
+
beforeEach(async () => {
|
|
182
|
+
await writeComponent(baseline, "components/button", {
|
|
183
|
+
liquid: "<button></button>",
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
beforeEach(async () => {
|
|
187
|
+
await writeComponent(consumer, "sections/hero", {
|
|
188
|
+
liquid: "{% render '@/components/button' %}",
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
beforeEach(async () => {
|
|
192
|
+
await build({ layers: [consumer.layer, baseline.layer] });
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
let output: string;
|
|
196
|
+
beforeEach(async () => {
|
|
197
|
+
output = await readFile(
|
|
198
|
+
join(consumer.path, "sections/built--sections--hero.liquid"),
|
|
199
|
+
"utf-8",
|
|
200
|
+
);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it("rewrites the render to the namespaced built name", () => {
|
|
204
|
+
expect(output).toContain("{% render 'built--components--button' %}");
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it("does not leave the source alias in the output", () => {
|
|
208
|
+
expect(output).not.toContain("@/components/button");
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
describe("when a section nests a blocks/ subdir", () => {
|
|
213
|
+
beforeEach(async () => {
|
|
214
|
+
await writeComponent(baseline, "sections/hero", {
|
|
215
|
+
liquid: "<section></section>",
|
|
216
|
+
schema: "export const schema = { name: 'Hero' };",
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
beforeEach(async () => {
|
|
220
|
+
await writeComponent(baseline, "sections/hero/blocks/headline", {
|
|
221
|
+
liquid: "<h2>headline</h2>",
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
beforeEach(async () => {
|
|
225
|
+
await build({ layers: [consumer.layer, baseline.layer] });
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it("emits the nested block with a `_` prefix on the built filename", async () => {
|
|
229
|
+
const built = await readFile(
|
|
230
|
+
join(
|
|
231
|
+
consumer.path,
|
|
232
|
+
"blocks/_built--sections--hero--blocks--headline.liquid",
|
|
233
|
+
),
|
|
234
|
+
"utf-8",
|
|
235
|
+
);
|
|
236
|
+
expect(built).toContain("<h2>headline</h2>");
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it("auto-merges the private child into the parent's blocks: [] array", async () => {
|
|
240
|
+
const hero = await readFile(
|
|
241
|
+
join(consumer.path, "sections/built--sections--hero.liquid"),
|
|
242
|
+
"utf-8",
|
|
243
|
+
);
|
|
244
|
+
expect(hero).toContain(
|
|
245
|
+
'"type": "_built--sections--hero--blocks--headline"',
|
|
246
|
+
);
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
describe("when a top-level public block nests its own blocks/ subdir", () => {
|
|
251
|
+
beforeEach(async () => {
|
|
252
|
+
await writeComponent(baseline, "blocks/promo", {
|
|
253
|
+
liquid: "<aside>promo</aside>",
|
|
254
|
+
schema: "export const schema = { name: 'Promo' };",
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
beforeEach(async () => {
|
|
258
|
+
await writeComponent(baseline, "blocks/promo/blocks/item", {
|
|
259
|
+
liquid: "<span>item</span>",
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
beforeEach(async () => {
|
|
263
|
+
await build({ layers: [consumer.layer, baseline.layer] });
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it("emits the nested grandchild as private (with `_` prefix)", async () => {
|
|
267
|
+
const built = await readFile(
|
|
268
|
+
join(
|
|
269
|
+
consumer.path,
|
|
270
|
+
"blocks/_built--blocks--promo--blocks--item.liquid",
|
|
271
|
+
),
|
|
272
|
+
"utf-8",
|
|
273
|
+
);
|
|
274
|
+
expect(built).toContain("<span>item</span>");
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it("auto-merges the private grandchild into the public parent's blocks: []", async () => {
|
|
278
|
+
const promo = await readFile(
|
|
279
|
+
join(consumer.path, "blocks/built--blocks--promo.liquid"),
|
|
280
|
+
"utf-8",
|
|
281
|
+
);
|
|
282
|
+
expect(promo).toContain('"type": "_built--blocks--promo--blocks--item"');
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
describe("with a top-level block (no nesting)", () => {
|
|
287
|
+
beforeEach(async () => {
|
|
288
|
+
await writeComponent(baseline, "blocks/promo", {
|
|
289
|
+
liquid: "<aside>promo</aside>",
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
beforeEach(async () => {
|
|
293
|
+
await build({ layers: [consumer.layer, baseline.layer] });
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it("emits without the `_` prefix (top-level blocks remain public)", async () => {
|
|
297
|
+
const built = await readFile(
|
|
298
|
+
join(consumer.path, "blocks/built--blocks--promo.liquid"),
|
|
299
|
+
"utf-8",
|
|
300
|
+
);
|
|
301
|
+
expect(built).toContain("<aside>promo</aside>");
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
describe("with multiple private children under one parent", () => {
|
|
306
|
+
beforeEach(async () => {
|
|
307
|
+
await writeComponent(baseline, "sections/hero", {
|
|
308
|
+
liquid: "<section></section>",
|
|
309
|
+
schema: "export const schema = { name: 'Hero' };",
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
beforeEach(async () => {
|
|
313
|
+
await writeComponent(baseline, "sections/hero/blocks/zebra", {
|
|
314
|
+
liquid: "<div>z</div>",
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
beforeEach(async () => {
|
|
318
|
+
await writeComponent(baseline, "sections/hero/blocks/alpha", {
|
|
319
|
+
liquid: "<div>a</div>",
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
beforeEach(async () => {
|
|
323
|
+
await build({ layers: [consumer.layer, baseline.layer] });
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it("sorts discovered children alphabetically in the merged blocks array", async () => {
|
|
327
|
+
const hero = await readFile(
|
|
328
|
+
join(consumer.path, "sections/built--sections--hero.liquid"),
|
|
329
|
+
"utf-8",
|
|
330
|
+
);
|
|
331
|
+
const alphaIdx = hero.indexOf("--blocks--alpha");
|
|
332
|
+
const zebraIdx = hero.indexOf("--blocks--zebra");
|
|
333
|
+
expect(alphaIdx).toBeLessThan(zebraIdx);
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
describe("when a parent lists explicit blocks alongside private children", () => {
|
|
338
|
+
beforeEach(async () => {
|
|
339
|
+
await writeComponent(baseline, "sections/hero", {
|
|
340
|
+
liquid: "<section></section>",
|
|
341
|
+
schema:
|
|
342
|
+
"export const schema = { name: 'Hero', blocks: [{ type: 'shared-cta' }] };",
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
beforeEach(async () => {
|
|
346
|
+
await writeComponent(baseline, "sections/hero/blocks/headline", {
|
|
347
|
+
liquid: "<h2></h2>",
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
beforeEach(async () => {
|
|
351
|
+
await build({ layers: [consumer.layer, baseline.layer] });
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
it("prepends discovered children before the author's explicit entries", async () => {
|
|
355
|
+
const hero = await readFile(
|
|
356
|
+
join(consumer.path, "sections/built--sections--hero.liquid"),
|
|
357
|
+
"utf-8",
|
|
358
|
+
);
|
|
359
|
+
const discoveredIdx = hero.indexOf(
|
|
360
|
+
"_built--sections--hero--blocks--headline",
|
|
361
|
+
);
|
|
362
|
+
const explicitIdx = hero.indexOf("shared-cta");
|
|
363
|
+
expect(discoveredIdx).toBeLessThan(explicitIdx);
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
describe("when private blocks nest recursively", () => {
|
|
368
|
+
beforeEach(async () => {
|
|
369
|
+
await writeComponent(baseline, "sections/hero", {
|
|
370
|
+
liquid: "<section></section>",
|
|
371
|
+
schema: "export const schema = { name: 'Hero' };",
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
beforeEach(async () => {
|
|
375
|
+
await writeComponent(baseline, "sections/hero/blocks/group", {
|
|
376
|
+
liquid: "<div></div>",
|
|
377
|
+
schema: "export const schema = { name: 'Group' };",
|
|
378
|
+
});
|
|
379
|
+
});
|
|
380
|
+
beforeEach(async () => {
|
|
381
|
+
await writeComponent(baseline, "sections/hero/blocks/group/blocks/item", {
|
|
382
|
+
liquid: "<span></span>",
|
|
383
|
+
});
|
|
384
|
+
});
|
|
385
|
+
beforeEach(async () => {
|
|
386
|
+
await build({ layers: [consumer.layer, baseline.layer] });
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
it("merges the grandchild into the intermediate parent's blocks array", async () => {
|
|
390
|
+
const group = await readFile(
|
|
391
|
+
join(
|
|
392
|
+
consumer.path,
|
|
393
|
+
"blocks/_built--sections--hero--blocks--group.liquid",
|
|
394
|
+
),
|
|
395
|
+
"utf-8",
|
|
396
|
+
);
|
|
397
|
+
expect(group).toContain("--group--blocks--item");
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
it("does not list the grandchild on the top section's blocks array", async () => {
|
|
401
|
+
const hero = await readFile(
|
|
402
|
+
join(consumer.path, "sections/built--sections--hero.liquid"),
|
|
403
|
+
"utf-8",
|
|
404
|
+
);
|
|
405
|
+
expect(hero).not.toContain("--group--blocks--item");
|
|
406
|
+
});
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
describe("when another component renders a private block via @/", () => {
|
|
410
|
+
beforeEach(async () => {
|
|
411
|
+
await writeComponent(baseline, "sections/hero", {
|
|
412
|
+
liquid: "<section></section>",
|
|
413
|
+
});
|
|
414
|
+
});
|
|
415
|
+
beforeEach(async () => {
|
|
416
|
+
await writeComponent(baseline, "sections/hero/blocks/headline", {
|
|
417
|
+
liquid: "<h2></h2>",
|
|
418
|
+
});
|
|
419
|
+
});
|
|
420
|
+
beforeEach(async () => {
|
|
421
|
+
await writeComponent(consumer, "sections/somewhere", {
|
|
422
|
+
liquid: "{% render '@/sections/hero/blocks/headline' %}",
|
|
423
|
+
});
|
|
424
|
+
});
|
|
425
|
+
beforeEach(async () => {
|
|
426
|
+
await build({ layers: [consumer.layer, baseline.layer] });
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
it("rewrites the render with the `_built--` prefix", async () => {
|
|
430
|
+
const out = await readFile(
|
|
431
|
+
join(consumer.path, "sections/built--sections--somewhere.liquid"),
|
|
432
|
+
"utf-8",
|
|
433
|
+
);
|
|
434
|
+
expect(out).toContain(
|
|
435
|
+
"{% render '_built--sections--hero--blocks--headline' %}",
|
|
436
|
+
);
|
|
437
|
+
});
|
|
438
|
+
});
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
async function makeFixture(tempRoot: string, name: string): Promise<Fixture> {
|
|
442
|
+
const path = join(tempRoot, name);
|
|
443
|
+
await mkdir(join(path, "src"), { recursive: true });
|
|
444
|
+
return { path, layer: { path, name } };
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
async function writeComponent(
|
|
448
|
+
fixture: Fixture,
|
|
449
|
+
segments: string,
|
|
450
|
+
slots: {
|
|
451
|
+
liquid?: string;
|
|
452
|
+
ts?: string;
|
|
453
|
+
css?: string;
|
|
454
|
+
test?: string;
|
|
455
|
+
schema?: string;
|
|
456
|
+
},
|
|
457
|
+
): Promise<void> {
|
|
458
|
+
const parts = segments.split("/");
|
|
459
|
+
const last = parts[parts.length - 1];
|
|
460
|
+
const directory = join(fixture.path, "src", ...parts);
|
|
461
|
+
await mkdir(directory, { recursive: true });
|
|
462
|
+
await Promise.all(
|
|
463
|
+
Object.entries({
|
|
464
|
+
[`${last}.liquid`]: slots.liquid,
|
|
465
|
+
[`${last}.ts`]: slots.ts,
|
|
466
|
+
[`${last}.css`]: slots.css,
|
|
467
|
+
[`${last}.test.ts`]: slots.test,
|
|
468
|
+
[`${last}.schema.ts`]: slots.schema,
|
|
469
|
+
})
|
|
470
|
+
.filter(([, content]) => content !== undefined)
|
|
471
|
+
.map(([filename, content]) =>
|
|
472
|
+
writeFile(join(directory, filename), content ?? ""),
|
|
473
|
+
),
|
|
474
|
+
);
|
|
475
|
+
}
|