@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
@@ -0,0 +1,10 @@
1
+ [
2
+ {
3
+ "name": "theme_info",
4
+ "theme_name": "Dawn",
5
+ "theme_author": "Shopify",
6
+ "theme_version": "1.0.0",
7
+ "theme_documentation_url": "https://help.shopify.com/manual/online-store/themes/os20/themes-by-shopify/dawn",
8
+ "theme_support_url": "https://support.shopify.com/"
9
+ }
10
+ ]
@@ -0,0 +1,14 @@
1
+ {
2
+ "general": {
3
+ "label": "label",
4
+ "thing_html": "<script>console.log</script>",
5
+ "pluralized": {
6
+ "one": "{{ count }} thing",
7
+ "other": "{{ count }} things"
8
+ },
9
+ "pluralized_html": {
10
+ "one": "<script>console.log</script> {{ count }} thing",
11
+ "other": "<script>console.log</script> {{ count }} things"
12
+ }
13
+ }
14
+ }
@@ -0,0 +1,367 @@
1
+ import set from 'lodash.set';
2
+ import { assert, describe, expect, it } from 'vitest';
3
+ import {
4
+ INPUT_SETTING_TYPES,
5
+ SETTINGS_TYPES_EXCLUSIVE_TO_SETTINGS_SCHEMA,
6
+ SETTINGS_TYPES_NOT_SUPPORTING_VISIBLE_IF,
7
+ } from './test-constants';
8
+ import { complete, getService, hover, loadFixture, validateSchema } from './test-helpers';
9
+
10
+ const sectionSchema1 = loadFixture('section-schema-1.json');
11
+ const sectionSchema2 = loadFixture('section-schema-2.json');
12
+ const sectionSchema3 = loadFixture('section-schema-3.json');
13
+ const sectionSchema4 = loadFixture('section-schema-4.json');
14
+ const sectionSchema5 = loadFixture('section-schema-5.json');
15
+ const sectionSchema6 = loadFixture('section-schema-6.json');
16
+ const sectionSchemaStaticBlockPreset = loadFixture('section-schema-static-block-preset.json');
17
+ const sectionSchemaPresetBlocksAsHash = loadFixture('section-schema-preset-blocks-as-hash.json');
18
+ const sectionSettings = loadFixture('section-settings.json');
19
+ const sectionNestedBlocks = loadFixture('section-nested-blocks.json');
20
+ const emptySchema = '{}';
21
+
22
+ const validate = validateSchema();
23
+ const service = getService();
24
+
25
+ describe('JSON Schema validation of Liquid theme section schema tags', () => {
26
+ it('should validate valid section schemas', async () => {
27
+ const schemas = [
28
+ emptySchema,
29
+ sectionSchema1,
30
+ sectionSchema2,
31
+ sectionSchema3,
32
+ sectionSchema4,
33
+ sectionSchema5,
34
+ sectionSchema6,
35
+ sectionNestedBlocks,
36
+ sectionSchemaPresetBlocksAsHash,
37
+ ];
38
+ for (const sectionSchema of schemas) {
39
+ const diagnostics = await validate('sections/section.liquid', sectionSchema);
40
+ expect(diagnostics, sectionSchema).toStrictEqual([]);
41
+ }
42
+ });
43
+
44
+ it('should report incorrect types', async () => {
45
+ // Using JSON to make a deep copy of one of the valid schemas to mutate safely.
46
+ const sectionSchema = JSON.parse(sectionSchema1);
47
+ set(sectionSchema, 'settings.4', 'foobar');
48
+
49
+ const diagnostics = await validate('sections/section.liquid', sectionSchema);
50
+ expect(diagnostics).toStrictEqual([
51
+ {
52
+ message: 'Incorrect type. Expected "object".',
53
+ severity: 1,
54
+ range: expect.objectContaining({
55
+ start: expect.objectContaining({
56
+ character: expect.any(Number),
57
+ line: expect.any(Number),
58
+ }),
59
+ end: expect.objectContaining({
60
+ character: expect.any(Number),
61
+ line: expect.any(Number),
62
+ }),
63
+ }),
64
+ },
65
+ ]);
66
+ });
67
+
68
+ it('should report invalid enum values', async () => {
69
+ // Using JSON to make a deep copy of one of the valid schemas to mutate safely.
70
+ const sectionSchema = JSON.parse(sectionSchema1);
71
+ set(sectionSchema, 'settings.6.type', 'foobar');
72
+
73
+ const diagnostics = await validate('sections/section.liquid', sectionSchema);
74
+ expect(diagnostics).toStrictEqual([
75
+ {
76
+ code: 1,
77
+ message: expect.stringContaining('Value is not accepted. Valid values: "article", "article_list", "blog"'),
78
+ severity: 1,
79
+ range: expect.objectContaining({
80
+ start: expect.objectContaining({
81
+ character: expect.any(Number),
82
+ line: expect.any(Number),
83
+ }),
84
+ end: expect.objectContaining({
85
+ character: expect.any(Number),
86
+ line: expect.any(Number),
87
+ }),
88
+ }),
89
+ },
90
+ ]);
91
+ });
92
+
93
+ it('should not accept invalid properties', async () => {
94
+ const diagnostics = await validate('sections/section.liquid', { invalid: 'value' });
95
+ expect(diagnostics).toStrictEqual([
96
+ {
97
+ message: 'Property invalid is not allowed.',
98
+ severity: 1,
99
+ range: expect.objectContaining({
100
+ start: expect.objectContaining({
101
+ character: expect.any(Number),
102
+ line: expect.any(Number),
103
+ }),
104
+ end: expect.objectContaining({
105
+ character: expect.any(Number),
106
+ line: expect.any(Number),
107
+ }),
108
+ }),
109
+ },
110
+ ]);
111
+ });
112
+
113
+ it('should properly validate the min value for max_blocks', async () => {
114
+ const diagnostics = await validate('sections/section.liquid', { max_blocks: 0 });
115
+ expect(diagnostics).toStrictEqual([
116
+ {
117
+ message: 'Value is below the minimum of 1.',
118
+ severity: 1,
119
+ range: expect.objectContaining({
120
+ start: expect.objectContaining({
121
+ character: expect.any(Number),
122
+ line: expect.any(Number),
123
+ }),
124
+ end: expect.objectContaining({
125
+ character: expect.any(Number),
126
+ line: expect.any(Number),
127
+ }),
128
+ }),
129
+ },
130
+ ]);
131
+ });
132
+
133
+ it('should properly validate the max value for max_blocks', async () => {
134
+ const diagnostics = await validate('sections/section.liquid', { max_blocks: 51 });
135
+ expect(diagnostics).toStrictEqual([
136
+ {
137
+ message: 'Value is above the maximum of 50.',
138
+ severity: 1,
139
+ range: expect.objectContaining({
140
+ start: expect.objectContaining({
141
+ character: expect.any(Number),
142
+ line: expect.any(Number),
143
+ }),
144
+ end: expect.objectContaining({
145
+ character: expect.any(Number),
146
+ line: expect.any(Number),
147
+ }),
148
+ }),
149
+ },
150
+ ]);
151
+ });
152
+
153
+ it('should properly validate the default values of input settings per setting type', async () => {
154
+ const diagnostics = await validate('sections/section.liquid', sectionSchemaStaticBlockPreset);
155
+ expect(diagnostics).toContainEqual({
156
+ message: 'Missing property "id".',
157
+ severity: 1,
158
+ range: expect.objectContaining({
159
+ start: expect.objectContaining({
160
+ line: 33,
161
+ }),
162
+ }),
163
+ });
164
+ });
165
+
166
+ it('should properly validate the default values of input settings per setting type', async () => {
167
+ // here we make sure that the input settings rules are working as intended
168
+ const diagnostics = await validate('sections/section.liquid', sectionSettings);
169
+ expect(diagnostics).toContainEqual({
170
+ message: 'Incorrect type. Expected "boolean".',
171
+ severity: 1,
172
+ range: expect.objectContaining({
173
+ start: expect.objectContaining({
174
+ line: 7,
175
+ }),
176
+ }),
177
+ });
178
+ expect(diagnostics).toContainEqual({
179
+ message: 'Incorrect type. Expected "number".',
180
+ severity: 1,
181
+ range: expect.objectContaining({
182
+ start: expect.objectContaining({
183
+ line: 13,
184
+ }),
185
+ }),
186
+ });
187
+ expect(diagnostics).toContainEqual({
188
+ message: 'Incorrect type. Expected "string".',
189
+ severity: 1,
190
+ range: expect.objectContaining({
191
+ start: expect.objectContaining({
192
+ line: 19,
193
+ }),
194
+ }),
195
+ });
196
+ expect(diagnostics).toContainEqual({
197
+ message: 'Missing property "options".',
198
+ severity: 1,
199
+ range: expect.objectContaining({
200
+ start: expect.objectContaining({
201
+ line: 21,
202
+ }),
203
+ }),
204
+ });
205
+ expect(diagnostics).toContainEqual({
206
+ code: 1,
207
+ message: 'Value is not accepted. Valid values: "youtube", "vimeo".',
208
+ severity: 1,
209
+ range: expect.objectContaining({
210
+ start: expect.objectContaining({
211
+ line: 30,
212
+ }),
213
+ }),
214
+ });
215
+ });
216
+
217
+ it('should validate section schema with conditional settings', async () => {
218
+ const sectionSchemaConditionalSettings = loadFixture(
219
+ 'section-schema-conditional-settings.json',
220
+ );
221
+ const diagnostics = await validate('sections/section.liquid', sectionSchemaConditionalSettings);
222
+ expect(diagnostics).toStrictEqual([]);
223
+ });
224
+
225
+ const settingsTypesSupportingVisibleIf = INPUT_SETTING_TYPES.filter(
226
+ (settingType) =>
227
+ !SETTINGS_TYPES_NOT_SUPPORTING_VISIBLE_IF.concat(SETTINGS_TYPES_EXCLUSIVE_TO_SETTINGS_SCHEMA).includes(settingType),
228
+ );
229
+
230
+ it.each(settingsTypesSupportingVisibleIf)(
231
+ 'should allow visible_if on %s setting type',
232
+ async (settingType) => {
233
+ const schema = {
234
+ settings: [
235
+ {
236
+ type: settingType,
237
+ id: 'test_setting',
238
+ label: 'Test Setting',
239
+ visible_if: '{{ section.settings.some_setting }}',
240
+ },
241
+ ],
242
+ };
243
+
244
+ const diagnostics = await validate('sections/section.liquid', schema);
245
+ expect(diagnostics).not.toContainEqual(
246
+ expect.objectContaining({
247
+ message: 'Property visible_if is not allowed.',
248
+ }),
249
+ );
250
+ },
251
+ );
252
+
253
+ it.each(SETTINGS_TYPES_NOT_SUPPORTING_VISIBLE_IF)(
254
+ 'should not allow visible_if on %s setting type',
255
+ async (settingType) => {
256
+ const schema = {
257
+ settings: [
258
+ {
259
+ type: settingType,
260
+ id: 'test_setting',
261
+ label: 'Test Setting',
262
+ visible_if: '{{ section.settings.some_setting }}',
263
+ },
264
+ ],
265
+ };
266
+
267
+ const diagnostics = await validate('sections/section.liquid', schema);
268
+ expect(diagnostics).toContainEqual(
269
+ expect.objectContaining({
270
+ message: 'Property visible_if is not allowed.',
271
+ severity: 1,
272
+ }),
273
+ );
274
+ },
275
+ );
276
+
277
+ it('should complete the type property with the generic docs', async () => {
278
+ const result = await complete(
279
+ service,
280
+ 'sections/section.liquid',
281
+ `{
282
+ "blocks": [
283
+ {
284
+ "type█"
285
+ }
286
+ ]
287
+ }`,
288
+ );
289
+
290
+ assert(result);
291
+ expect(result.items).toContainEqual(
292
+ expect.objectContaining({
293
+ documentation: expect.stringContaining('The type of block that can be added to this block'),
294
+ }),
295
+ );
296
+ });
297
+
298
+ it('should complete the type value with the specific docs', async () => {
299
+ const result = await complete(
300
+ service,
301
+ 'sections/section.liquid',
302
+ `{
303
+ "blocks": [
304
+ {
305
+ "type": "█"
306
+ }
307
+ ]
308
+ }`,
309
+ );
310
+
311
+ assert(result);
312
+ // not showing generic docs
313
+ expect(result.items).not.toContainEqual(
314
+ expect.objectContaining({
315
+ documentation: expect.stringContaining('The type of block that can be added to this block'),
316
+ }),
317
+ );
318
+ // show docs for @app and @theme
319
+ expect(result.items).toContainEqual(
320
+ expect.objectContaining({ documentation: expect.stringContaining('@app') }),
321
+ );
322
+ expect(result.items).toContainEqual(
323
+ expect.objectContaining({ documentation: expect.stringContaining('@theme') }),
324
+ );
325
+ });
326
+
327
+ it('should hover the type property of a specific block with the specific block docs', async () => {
328
+ const result = await hover(
329
+ service,
330
+ 'sections/section.liquid',
331
+ `{
332
+ "blocks": [
333
+ {
334
+ "type": "slide█"
335
+ }
336
+ ]
337
+ }`,
338
+ );
339
+
340
+ assert(result);
341
+ // not showing generic docs
342
+ expect(result.contents).toContainEqual(
343
+ expect.stringContaining('found in the `blocks/` folder'),
344
+ );
345
+ });
346
+
347
+ it('should hover the type property of a local block with the local block docs', async () => {
348
+ const result = await hover(
349
+ service,
350
+ 'sections/section.liquid',
351
+ `{
352
+ "blocks": [
353
+ {
354
+ "type": "slide█",
355
+ "name": "slide"
356
+ }
357
+ ]
358
+ }`,
359
+ );
360
+
361
+ assert(result);
362
+ // not showing generic docs
363
+ expect(result.contents).toContainEqual(
364
+ expect.stringContaining('This is a free-form string that you can use as an identifier.'),
365
+ );
366
+ });
367
+ });
@@ -0,0 +1,58 @@
1
+ export const SETTINGS_TYPES_NOT_SUPPORTING_VISIBLE_IF = [
2
+ 'article',
3
+ 'article_list',
4
+ 'blog',
5
+ 'collection',
6
+ 'collection_list',
7
+ 'metaobject',
8
+ 'metaobject_list',
9
+ 'page',
10
+ 'product',
11
+ 'product_list',
12
+ // Not featured here is `color_scheme_group` and `color_palette` which are exclusive to settings_schema.json.
13
+ // Those setting types are tested in `color_scheme_group.spec.ts` and `color_palette.spec.ts`.
14
+ ];
15
+
16
+ export const INPUT_SETTING_TYPES = [
17
+ 'article',
18
+ 'article_list',
19
+ 'blog',
20
+ 'checkbox',
21
+ 'collection_list',
22
+ 'collection',
23
+ 'color_background',
24
+ 'color_palette',
25
+ 'color_scheme_group',
26
+ 'color_scheme',
27
+ 'color',
28
+ 'font_picker',
29
+ 'html',
30
+ 'image_picker',
31
+ 'inline_richtext',
32
+ 'link_list',
33
+ 'liquid',
34
+ 'metaobject',
35
+ 'metaobject_list',
36
+ 'number',
37
+ 'page',
38
+ 'product_list',
39
+ 'product',
40
+ 'radio',
41
+ 'range',
42
+ 'richtext',
43
+ 'select',
44
+ 'text_alignment',
45
+ 'text',
46
+ 'textarea',
47
+ 'url',
48
+ 'video_url',
49
+ 'video',
50
+ ];
51
+
52
+ // Setting types that are only valid in config/settings_schema.json,
53
+ // not in section or block schemas. Tested in their own *.spec.ts files.
54
+ export const SETTINGS_TYPES_EXCLUSIVE_TO_SETTINGS_SCHEMA = ['color_scheme_group', 'color_palette'];
55
+
56
+ export const SIDEBAR_SETTING_TYPES = ['header', 'paragraph'];
57
+
58
+ export const RESOURCE_LIST_SETTING_TYPES = ['article_list', 'collection_list', 'metaobject_list', 'product_list'];
@@ -0,0 +1,104 @@
1
+ import {
2
+ getLanguageService,
3
+ Diagnostic,
4
+ TextDocument,
5
+ LanguageService,
6
+ Hover,
7
+ CompletionList,
8
+ } from 'vscode-json-languageservice';
9
+ import * as fs from 'node:fs';
10
+ import * as path from 'node:path';
11
+
12
+ export const CURSOR = '█';
13
+ const rootURI = 'https://raw.githubusercontent.com/Shopify/theme-liquid-docs/main/schemas';
14
+
15
+ export interface SchemaDefinition {
16
+ uri: string;
17
+ fileMatch?: string[];
18
+ schema: string;
19
+ }
20
+
21
+ export const loadFixture = (fixtureName: string): string =>
22
+ fs.readFileSync(path.resolve(__dirname, './fixtures/', fixtureName), 'utf8');
23
+
24
+ export const loadSchema = (relativePath: string): string => {
25
+ return fs.readFileSync(path.resolve(__dirname, '../schemas', relativePath), 'utf8');
26
+ };
27
+
28
+ export const getService = (manifestName = 'manifest_theme.json') => {
29
+ const manifestPath = path.resolve(__dirname, '../schemas', manifestName);
30
+ const manifest = require(manifestPath);
31
+ const schemas = manifest.schemas.map(
32
+ (schema): SchemaDefinition => ({
33
+ uri: `${rootURI}/${schema.uri}`,
34
+ fileMatch: schema.fileMatch,
35
+ schema: loadSchema(schema.uri),
36
+ }),
37
+ );
38
+ const service = getLanguageService({
39
+ async schemaRequestService(uri) {
40
+ const schemaDefinition = schemas.find((sd) => sd.uri === uri);
41
+ return schemaDefinition?.schema ?? `Could not find a schema for ${uri}`;
42
+ },
43
+ workspaceContext: {
44
+ resolveRelativePath: (relativePath, resource) => {
45
+ const url = new URL(relativePath, resource);
46
+ return url.toString();
47
+ },
48
+ },
49
+ });
50
+
51
+ service.configure({
52
+ schemas: schemas.map((sd) => ({ uri: sd.uri, fileMatch: sd.fileMatch })),
53
+ });
54
+
55
+ return service;
56
+ };
57
+
58
+ export const validateSchema = (manifestName = 'manifest_theme.json') => {
59
+ const service = getService(manifestName);
60
+
61
+ return async (filePath: string, jsonContent: any): Promise<Diagnostic[]> => {
62
+ if (typeof jsonContent !== 'string') {
63
+ jsonContent = JSON.stringify(jsonContent);
64
+ }
65
+
66
+ const textDocument = TextDocument.create('file:/' + filePath, 'json', 0, jsonContent);
67
+ const jsonDocument = service.parseJSONDocument(textDocument);
68
+ const diagnostics = await service.doValidation(textDocument, jsonDocument, {
69
+ schemaValidation: 'error',
70
+ comments: 'error',
71
+ trailingCommas: 'error',
72
+ });
73
+
74
+ return diagnostics;
75
+ };
76
+ };
77
+
78
+ export const hover = async (
79
+ service: LanguageService,
80
+ filePath: string,
81
+ jsonContent: string,
82
+ ): Promise<Hover | null> => {
83
+ const offset = jsonContent.indexOf(CURSOR);
84
+ jsonContent = jsonContent.replace(CURSOR, '');
85
+
86
+ const textDocument = TextDocument.create('file:/' + filePath, 'json', 0, jsonContent);
87
+ const position = textDocument.positionAt(offset);
88
+ const jsonDocument = service.parseJSONDocument(textDocument);
89
+ return service.doHover(textDocument, position, jsonDocument);
90
+ };
91
+
92
+ export const complete = async (
93
+ service: LanguageService,
94
+ filePath: string,
95
+ jsonContent: string,
96
+ ): Promise<CompletionList | null> => {
97
+ const offset = jsonContent.indexOf(CURSOR);
98
+ jsonContent = jsonContent.replace(CURSOR, '');
99
+
100
+ const textDocument = TextDocument.create('file:/' + filePath, 'json', 0, jsonContent);
101
+ const position = textDocument.positionAt(offset);
102
+ const jsonDocument = service.parseJSONDocument(textDocument);
103
+ return service.doComplete(textDocument, position, jsonDocument);
104
+ };