shopify-accelerate-app 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/@types/metafields.ts +9 -0
  2. package/@types/sections.ts +1769 -0
  3. package/@types/settings.ts +3 -0
  4. package/@types/shopify.ts +1871 -0
  5. package/@types/types.d.ts +17 -0
  6. package/README.md +2 -0
  7. package/package.json +111 -0
  8. package/shopify-accelerate-app.ts +394 -0
  9. package/shopify.graphql +48866 -0
  10. package/src/esbuild/esbuild.ts +246 -0
  11. package/src/scaffold-theme/build-theme.ts +24 -0
  12. package/src/scaffold-theme/generate-asset-files.ts +48 -0
  13. package/src/scaffold-theme/generate-base-types.ts +23 -0
  14. package/src/scaffold-theme/generate-block-files.ts +194 -0
  15. package/src/scaffold-theme/generate-blocks-types.ts +242 -0
  16. package/src/scaffold-theme/generate-config-files.ts +34 -0
  17. package/src/scaffold-theme/generate-liquid-files.ts +995 -0
  18. package/src/scaffold-theme/generate-schema-locales.ts +195 -0
  19. package/src/scaffold-theme/generate-schema-variables.ts +380 -0
  20. package/src/scaffold-theme/generate-section-files.ts +303 -0
  21. package/src/scaffold-theme/generate-section-preset-files.ts +296 -0
  22. package/src/scaffold-theme/generate-section-types.ts +339 -0
  23. package/src/scaffold-theme/generate-setting-types.ts +123 -0
  24. package/src/scaffold-theme/generate-settings-file.ts +103 -0
  25. package/src/scaffold-theme/parse-files.ts +466 -0
  26. package/src/scaffold-theme/parse-locales.ts +98 -0
  27. package/src/shopify-cli/pull.ts +103 -0
  28. package/src/tailwind/postcss.config.js +8 -0
  29. package/src/tailwind/tailwind-watch.ts +133 -0
  30. package/src/tailwind/tailwind.config.js +261 -0
  31. package/src/telemetry/telemetry.ts +84 -0
  32. package/src/templates/.env.template +12 -0
  33. package/src/templates/shopify.theme.toml +9 -0
  34. package/src/utils/capitalize.ts +3 -0
  35. package/src/utils/delay.ts +3 -0
  36. package/src/utils/fs.ts +87 -0
  37. package/src/utils/is-object.ts +3 -0
  38. package/src/utils/json.ts +8 -0
  39. package/src/utils/to-camel-case.ts +6 -0
  40. package/src/utils/to-kebab-case.ts +6 -0
  41. package/src/utils/to-pascal-case.ts +12 -0
  42. package/src/utils/to-snake-case.ts +45 -0
  43. package/src/validate-cli-options.ts +281 -0
  44. package/src/watch-headless/watch-headless.ts +81 -0
  45. package/src/watch-theme/watch-theme.ts +84 -0
  46. package/tsconfig.json +82 -0
  47. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,995 @@
1
+ import chalk from "chalk";
2
+ import fs from "fs";
3
+ import path from "path";
4
+ import { PresetSchema, ShopifySectionPreset } from "../../@types/shopify";
5
+ import { config, root_dir } from "../../shopify-accelerate-app";
6
+ import { deleteFile, writeCompareFile, writeOnlyNew } from "../utils/fs";
7
+ import { isObject } from "../utils/is-object";
8
+ import { JSONParse } from "../utils/json";
9
+ import { toCamelCase } from "../utils/to-camel-case";
10
+ import { toLocaleFriendlySnakeCase, toSnakeCase } from "../utils/to-snake-case";
11
+ import { generateBlockFiles } from "./generate-block-files";
12
+ import { generateSectionFiles } from "./generate-section-files";
13
+ import { generateSectionPresetFiles } from "./generate-section-preset-files";
14
+ import { generateSettingsFile } from "./generate-settings-file";
15
+
16
+ export const generateLiquidFiles = () => {
17
+ const {
18
+ theme_path,
19
+ folders,
20
+ sources,
21
+ targets,
22
+ delete_external_blocks,
23
+ delete_external_layouts,
24
+ delete_external_sections,
25
+ delete_external_snippets,
26
+ disabled_theme_blocks,
27
+ } = config;
28
+
29
+ const translations: any = {};
30
+ const snippets = sources.snippets;
31
+ const giftCards = sources.giftCards;
32
+ const layouts = sources.layouts;
33
+ const sectionsSchemas = sources.sectionSchemas;
34
+ const sectionPresetSchemas = sources.sectionPresetSchemas;
35
+ const blockSchemas = sources.blockSchemas;
36
+
37
+ generateSettingsFile();
38
+
39
+ for (const key in sectionsSchemas) {
40
+ const schema = sectionsSchemas[key];
41
+
42
+ const sectionName = `${schema.folder}.liquid`;
43
+ const sectionPath = path.join(process.cwd(), theme_path, "sections", sectionName);
44
+
45
+ if (schema.disabled) {
46
+ const targetFile = targets.sections.find(
47
+ (target) => target.split(/[\\/]/gi).at(-1) === sectionName
48
+ );
49
+ if (targetFile) {
50
+ config.targets.sections = config.targets.sections.filter((target) => target !== targetFile);
51
+ deleteFile(path.join(root_dir, targetFile));
52
+ }
53
+ const snippetTargets = targets.snippets.filter(
54
+ (target) => `${target.split(/[\\/]/gi).at(-1).split(".")[0]}.liquid` === sectionName
55
+ );
56
+
57
+ if (snippetTargets.length) {
58
+ config.targets.snippets = config.targets.snippets.filter((target) => {
59
+ if (snippetTargets.includes(target)) {
60
+ deleteFile(path.join(root_dir, target));
61
+ return false;
62
+ }
63
+ return true;
64
+ });
65
+ }
66
+ continue;
67
+ }
68
+
69
+ schema?.blocks?.forEach((block) => {
70
+ if (block.disabled) {
71
+ const targetFile = targets.snippets.find(
72
+ (target) =>
73
+ target.split(/[\\/]/gi).at(-1) ===
74
+ `${sectionName.replace(".liquid", "")}.${block.type}.liquid`
75
+ );
76
+ if (targetFile) {
77
+ config.targets.snippets = config.targets.snippets.filter(
78
+ (target) => target !== targetFile
79
+ );
80
+ deleteFile(path.join(root_dir, targetFile));
81
+ }
82
+ }
83
+ });
84
+
85
+ let translationArray = [];
86
+
87
+ const rawContent = fs.readFileSync(path.join(folders.sections, schema.folder, sectionName), {
88
+ encoding: "utf-8",
89
+ });
90
+
91
+ if (rawContent) {
92
+ let translatedContent = rawContent.replace(
93
+ /<t(\s+[^>]*)*>((.|\r|\n)*?)<\/t>/gi,
94
+ (str, group1, group2) => {
95
+ const group = toLocaleFriendlySnakeCase(schema.folder);
96
+ const content = toLocaleFriendlySnakeCase(
97
+ group2?.split(" ")?.slice(0, 2)?.join("_") ?? ""
98
+ ).trim();
99
+ const backupContent = toLocaleFriendlySnakeCase(group2).trim();
100
+ const id = toLocaleFriendlySnakeCase(group1?.replace(/id="(.*)"/gi, "$1") ?? "").trim();
101
+
102
+ if (!(group in translations)) {
103
+ translations[group] = {};
104
+ }
105
+
106
+ if (id && !(id in translations[group])) {
107
+ translations[group][id] = group2;
108
+ return `{{ "${group}.${id}" | t }}`;
109
+ }
110
+
111
+ if (!(content in translations[group])) {
112
+ translations[group][content] = group2;
113
+ return `{{ "${group}.${content}" | t }}`;
114
+ }
115
+
116
+ if (translations[group][content] !== group2) {
117
+ if (!(backupContent in translations[group])) {
118
+ translations[group][backupContent] = group2;
119
+ return `{{ "${group}.${backupContent}" | t }}`;
120
+ }
121
+ if (translations[group][backupContent] !== group2) {
122
+ translations[group][`${content}_2`] = group2;
123
+ return `{{ "${group}.${content}_2" | t }}`;
124
+ }
125
+ }
126
+
127
+ if (translations[group][content] === group2) {
128
+ return `{{ "${group}.${content}" | t }}`;
129
+ }
130
+
131
+ return group2;
132
+ }
133
+ );
134
+ if (disabled_theme_blocks) {
135
+ translatedContent = translatedContent?.replace(
136
+ /\n(\s*){%-?\s*content_for\s*['"]blocks["']\s*-?%}/gi,
137
+ (str, match) => {
138
+ match = match.replace(/\n/gi, "");
139
+ const arr = [`\n${match}{% liquid`];
140
+ arr.push(`${match} for block in section.blocks`);
141
+ for (const key in blockSchemas) {
142
+ const schema = blockSchemas[key];
143
+ if (schema.disabled) continue;
144
+ arr.push(`${match} if block.type == "${schema.folder}"`);
145
+ arr.push(
146
+ `${match} render "_blocks.${schema.folder}", block: block, forloop: forloop, section_type: section_type, form: form`
147
+ );
148
+ arr.push(`${match} endif`);
149
+ }
150
+ arr.push(`${match} endfor`);
151
+ arr.push(`${match}%}`);
152
+
153
+ return arr.join("\n");
154
+ }
155
+ );
156
+
157
+ translatedContent = translatedContent?.replace(
158
+ /\n(\s*)content_for\s*['"]blocks["']\s*\n/gi,
159
+ (str, match) => {
160
+ match = match.replace(/\n/gi, "");
161
+ const arr = [`\n${match}`];
162
+ arr.push(`${match}for block in section.blocks`);
163
+ for (const key in blockSchemas) {
164
+ const schema = blockSchemas[key];
165
+ if (schema.disabled) continue;
166
+ arr.push(`${match} if block.type == "${schema.folder}"`);
167
+ arr.push(
168
+ `${match} render "_blocks.${schema.folder}", block: block, forloop: forloop, section_type: section_type, form: form`
169
+ );
170
+ arr.push(`${match} endif`);
171
+ }
172
+ arr.push(`${match}endfor`);
173
+ arr.push(``);
174
+
175
+ return arr.join("\n");
176
+ }
177
+ );
178
+
179
+ translatedContent = translatedContent?.replace(
180
+ /\n(\s*){%-?\s*content_for\s*['"]block["']\s*-?%}/gi,
181
+ (str, match) => {
182
+ match = match.replace(/\n/gi, "");
183
+ const arr = [`\n${match}{% liquid`];
184
+ for (const key in blockSchemas) {
185
+ const schema = blockSchemas[key];
186
+ if (schema.disabled) continue;
187
+ arr.push(`${match} if block.type == "${schema.folder}"`);
188
+ arr.push(
189
+ `${match} render "_blocks.${schema.folder}", block: block, forloop: forloop, section_type: section_type, form: form`
190
+ );
191
+ arr.push(`${match} endif`);
192
+ }
193
+ arr.push(`${match}%}`);
194
+
195
+ return arr.join("\n");
196
+ }
197
+ );
198
+ translatedContent = translatedContent?.replace(
199
+ /\n(\s*)content_for\s*['"]block["']\s*\n/gi,
200
+ (str, match) => {
201
+ match = match.replace(/\n/gi, "");
202
+ const arr = [`\n${match}`];
203
+ for (const key in blockSchemas) {
204
+ const schema = blockSchemas[key];
205
+ if (schema.disabled) continue;
206
+ arr.push(`${match}if block.type == "${schema.folder}"`);
207
+ arr.push(
208
+ `${match} render "_blocks.${schema.folder}", block: block, forloop: forloop, section_type: section_type, form: form`
209
+ );
210
+ arr.push(`${match}endif`);
211
+ }
212
+ arr.push(``);
213
+ return arr.join("\n");
214
+ }
215
+ );
216
+ }
217
+ translationArray.push(translatedContent);
218
+ }
219
+
220
+ const snippetPath = path.join(process.cwd(), theme_path, "snippets", sectionName);
221
+
222
+ if (config.ignore_snippets?.includes(snippetPath.split(/[/\\]/)?.at(-1))) {
223
+ console.log(
224
+ `[${chalk.gray(new Date().toLocaleTimeString())}]: ${chalk.greenBright(
225
+ `Ignored: ${snippetPath.replace(process.cwd(), "")}`
226
+ )}`
227
+ );
228
+ writeOnlyNew(snippetPath, translationArray.join("\n"));
229
+ } else {
230
+ writeCompareFile(snippetPath, translationArray.join("\n"));
231
+ }
232
+
233
+ snippets.push(snippetPath);
234
+
235
+ translationArray = [`{%- render "${schema.folder}" -%}`];
236
+
237
+ translationArray.push(generateSectionFiles(schema));
238
+
239
+ if (config.ignore_sections?.includes(sectionPath.split(/[/\\]/)?.at(-1))) {
240
+ console.log(
241
+ `[${chalk.gray(new Date().toLocaleTimeString())}]: ${chalk.greenBright(
242
+ `Ignored: ${sectionPath.replace(process.cwd(), "")}`
243
+ )}`
244
+ );
245
+ writeOnlyNew(sectionPath, translationArray.join("\n"));
246
+ } else {
247
+ writeCompareFile(sectionPath, translationArray.join("\n"));
248
+ }
249
+ }
250
+
251
+ for (const key in sectionPresetSchemas) {
252
+ const presets =
253
+ sectionPresetSchemas[key]?.presets
254
+ ?.filter(
255
+ (preset) =>
256
+ config.all_presets ||
257
+ !preset.enabled_on ||
258
+ preset.enabled_on?.includes(process.env.SHOPIFY_ACCELERATE_STORE)
259
+ )
260
+ ?.map((preset, index, arr) => {
261
+ let currentPreset = {
262
+ name:
263
+ arr?.length === 1
264
+ ? sectionPresetSchemas[key]?.name
265
+ : preset.enabled_on &&
266
+ !preset.enabled_on?.includes(process.env.SHOPIFY_ACCELERATE_STORE)
267
+ ? `${sectionPresetSchemas[key]?.name} - ${
268
+ index + 1
269
+ }` /*`${sectionPresetSchemas[key]?.name} - ${preset.enabled_on?.[0]}`*/
270
+ : `${sectionPresetSchemas[key]?.name} - ${index + 1}`,
271
+ settings: preset?.settings,
272
+ blocks: Array.isArray(preset?.blocks)
273
+ ? preset?.blocks
274
+ : Object.values(preset?.blocks ?? {}) ?? [],
275
+ } as unknown as ShopifySectionPreset;
276
+
277
+ if (
278
+ preset.enabled_on &&
279
+ !preset.enabled_on?.includes(process.env.SHOPIFY_ACCELERATE_STORE)
280
+ ) {
281
+ const matchList = {
282
+ content_class: ["richtext-md"],
283
+ button_class: [
284
+ "button-primary",
285
+ "button-tabs",
286
+ "button-secondary",
287
+ "button-primary-outline",
288
+ ],
289
+ scrollbar_class: ["scrollbar-no-buttons", "scrollbar"],
290
+ article_card_class: ["article-card"],
291
+ title_class: ["richtext-md"],
292
+ product_card_class: ["product-card", "product-card-flat"],
293
+ select_class: ["input-select"],
294
+ subscription_label_class: ["label-primary"],
295
+ legend_class: ["richtext-md"],
296
+ savings_highlight_class: ["richtext-md"],
297
+ label_class: ["richtext-md"],
298
+ price_class: ["richtext-md"],
299
+ incomplete_button_class: [
300
+ "button-primary",
301
+ "button-tabs",
302
+ "button-secondary",
303
+ "button-primary-outline",
304
+ ],
305
+ collection_card_class: ["collection-card"],
306
+ input_class: ["input-text", "input-text-inline"],
307
+ accordion_class: ["accordion"],
308
+ text_over_image_class: ["richtext-md"],
309
+ link_class: ["richtext-md"],
310
+ color_scheme: ["color_scheme_1"],
311
+ };
312
+ let string_content = JSON.stringify(currentPreset, null, 2);
313
+ for (const key in matchList) {
314
+ const regexp = new RegExp(`("${key}": )"([^"]*)"`, "gi");
315
+ string_content = string_content.replace(regexp, (totalMatch, _1, _2) => {
316
+ const value = _2;
317
+ const isValid = matchList[key].some(
318
+ (className) =>
319
+ new RegExp(`^${className}$`, "gi").test(value) ||
320
+ new RegExp(` ${className}$`, "gi").test(value) ||
321
+ new RegExp(`^${className} `, "gi").test(value) ||
322
+ new RegExp(` ${className} `, "gi").test(value)
323
+ );
324
+ if (!isValid) {
325
+ return `${_1}"${matchList[key][0]}"`;
326
+ }
327
+ return `${_1}"${value}"`;
328
+ });
329
+ }
330
+
331
+ currentPreset = JSONParse(string_content) || currentPreset;
332
+ }
333
+
334
+ return currentPreset;
335
+ }) ?? [];
336
+
337
+ if (!presets?.length) {
338
+ const targetFile = targets.sections.find(
339
+ (target) => target.split(/[\\/]/gi).at(-1) === `preset__${toSnakeCase(key)}.liquid`
340
+ );
341
+ if (targetFile) {
342
+ config.targets.sections = config.targets.sections.filter((target) => target !== targetFile);
343
+ deleteFile(path.join(root_dir, targetFile));
344
+ }
345
+ continue;
346
+ }
347
+ const schema = Object.values(sectionsSchemas)?.find(
348
+ (val) => val.folder === sectionPresetSchemas[key]?.type
349
+ );
350
+
351
+ if (!schema) {
352
+ continue;
353
+ }
354
+
355
+ const translationArray = [`{%- render "${schema.folder}" -%}`];
356
+
357
+ translationArray.push(
358
+ generateSectionPresetFiles({ schema, preset_name: sectionPresetSchemas[key].name, presets })
359
+ );
360
+
361
+ const sectionName = `preset__${toSnakeCase(key)}.liquid`;
362
+ const sectionPath = path.join(process.cwd(), theme_path, "sections", sectionName);
363
+ writeCompareFile(sectionPath, translationArray.join("\n"));
364
+ }
365
+
366
+ for (const key in blockSchemas) {
367
+ const schema = blockSchemas[key];
368
+
369
+ const sectionName = `${schema.folder}.liquid`;
370
+ const blockPath = disabled_theme_blocks
371
+ ? path.join(process.cwd(), theme_path, "snippets", sectionName)
372
+ : path.join(process.cwd(), theme_path, "blocks", sectionName);
373
+
374
+ if (schema.disabled) {
375
+ const targetFile = targets.blocks.find(
376
+ (target) => target.split(/[\\/]/gi).at(-1) === sectionName
377
+ );
378
+ if (targetFile) {
379
+ config.targets.blocks = config.targets.blocks.filter((target) => target !== targetFile);
380
+ deleteFile(path.join(root_dir, targetFile));
381
+ }
382
+
383
+ const snippetTargets = targets.snippets.filter((target) => {
384
+ return (
385
+ target
386
+ .split(/[\\/]/gi)
387
+ .at(-1)
388
+ .replace(/_blocks\./gi, "") === sectionName
389
+ );
390
+ });
391
+
392
+ if (snippetTargets.length) {
393
+ config.targets.snippets = config.targets.snippets.filter((target) => {
394
+ if (snippetTargets.includes(target)) {
395
+ deleteFile(path.join(root_dir, target));
396
+ return false;
397
+ }
398
+ return true;
399
+ });
400
+ }
401
+ continue;
402
+ }
403
+
404
+ const translationArray = [];
405
+
406
+ const rawContent = fs.readFileSync(path.join(folders.blocks, schema.folder, sectionName), {
407
+ encoding: "utf-8",
408
+ });
409
+
410
+ if (rawContent) {
411
+ const translatedContent = rawContent.replace(
412
+ /<t(\s+[^>]*)*>((.|\r|\n)*?)<\/t>/gi,
413
+ (str, group1, group2) => {
414
+ const group = toLocaleFriendlySnakeCase(schema.folder);
415
+ const content = toLocaleFriendlySnakeCase(
416
+ group2?.split(" ")?.slice(0, 2)?.join("_") ?? ""
417
+ ).trim();
418
+ const backupContent = toLocaleFriendlySnakeCase(group2).trim();
419
+ const id = toLocaleFriendlySnakeCase(group1?.replace(/id="(.*)"/gi, "$1") ?? "").trim();
420
+
421
+ if (!(group in translations)) {
422
+ translations[group] = {};
423
+ }
424
+
425
+ if (id && !(id in translations[group])) {
426
+ translations[group][id] = group2;
427
+ return `{{ "${group}.${id}" | t }}`;
428
+ }
429
+
430
+ if (!(content in translations[group])) {
431
+ translations[group][content] = group2;
432
+ return `{{ "${group}.${content}" | t }}`;
433
+ }
434
+
435
+ if (translations[group][content] !== group2) {
436
+ if (!(backupContent in translations[group])) {
437
+ translations[group][backupContent] = group2;
438
+ return `{{ "${group}.${backupContent}" | t }}`;
439
+ }
440
+ if (translations[group][backupContent] !== group2) {
441
+ translations[group][`${content}_2`] = group2;
442
+ return `{{ "${group}.${content}_2" | t }}`;
443
+ }
444
+ }
445
+
446
+ if (translations[group][content] === group2) {
447
+ return `{{ "${group}.${content}" | t }}`;
448
+ }
449
+
450
+ return group2;
451
+ }
452
+ );
453
+
454
+ translationArray.push(translatedContent);
455
+ }
456
+
457
+ translationArray.push(generateBlockFiles(schema));
458
+ if (disabled_theme_blocks) continue;
459
+
460
+ if (config.ignore_blocks?.includes(blockPath.split(/[/\\]/)?.at(-1))) {
461
+ console.log(
462
+ `[${chalk.gray(new Date().toLocaleTimeString())}]: ${chalk.greenBright(
463
+ `Ignored: ${blockPath.replace(process.cwd(), "")}`
464
+ )}`
465
+ );
466
+ writeOnlyNew(blockPath, translationArray.join("\n"));
467
+ } else {
468
+ writeCompareFile(blockPath, translationArray.join("\n"));
469
+ }
470
+ }
471
+
472
+ for (let i = 0; i < snippets.length; i++) {
473
+ const snippet = snippets[i];
474
+ const snippetName = snippet.split(/[\\/]/gi).at(-1);
475
+
476
+ const section = Object.values(sectionsSchemas).find((section) =>
477
+ section.path.includes(snippet.replace(snippetName, ""))
478
+ );
479
+ const blockSchema = section?.blocks?.find((block) =>
480
+ new RegExp(`\\.${block.type}\\.`, "gi").test(snippetName)
481
+ );
482
+
483
+ if (section && (section.disabled || blockSchema?.disabled)) {
484
+ continue;
485
+ }
486
+ const block = Object.values(blockSchemas).find((section) =>
487
+ section.path.includes(snippet.replace(snippetName, ""))
488
+ );
489
+ if (block && block.disabled) {
490
+ continue;
491
+ }
492
+ const snippetPath =
493
+ disabled_theme_blocks && snippet?.includes(folders.blocks)
494
+ ? path.join(process.cwd(), theme_path, "snippets", `_blocks.${snippetName}`)
495
+ : path.join(process.cwd(), theme_path, "snippets", snippetName);
496
+
497
+ const returnArr = [];
498
+
499
+ const rawContent = fs.readFileSync(snippet, {
500
+ encoding: "utf-8",
501
+ });
502
+
503
+ if (rawContent) {
504
+ let translatedContent = rawContent.replace(
505
+ /<t(\s+[^>]*)*>((.|\r|\n)*?)<\/t>/gi,
506
+ (_, group1, group2) => {
507
+ const group = toLocaleFriendlySnakeCase(
508
+ snippet.split(/[\\/]/gi).at(-1).split(".").at(0)
509
+ ).trim();
510
+ const content = toLocaleFriendlySnakeCase(
511
+ group2?.split(" ")?.slice(0, 2)?.join("_") ?? ""
512
+ ).trim();
513
+ const backupContent = toLocaleFriendlySnakeCase(group2).trim();
514
+ const id = toLocaleFriendlySnakeCase(group1?.replace(/id="(.*)"/gi, "$1") ?? "").trim();
515
+
516
+ if (!(group in translations)) {
517
+ translations[group] = {};
518
+ }
519
+
520
+ if (id && !(id in translations[group])) {
521
+ translations[group][id] = group2;
522
+ return `{{ "${group}.${id}" | t }}`;
523
+ }
524
+
525
+ if (!(content in translations[group])) {
526
+ translations[group][content] = group2;
527
+ return `{{ "${group}.${content}" | t }}`;
528
+ }
529
+
530
+ if (translations[group][content] !== group2) {
531
+ if (!(backupContent in translations[group])) {
532
+ translations[group][backupContent] = group2;
533
+ return `{{ "${group}.${backupContent}" | t }}`;
534
+ }
535
+ if (translations[group][backupContent] !== group2) {
536
+ translations[group][`${content}_2`] = group2;
537
+ return `{{ "${group}.${content}_2" | t }}`;
538
+ }
539
+ }
540
+ if (translations[group][content] === group2) {
541
+ return `{{ "${group}.${content}" | t }}`;
542
+ }
543
+
544
+ return group2;
545
+ }
546
+ );
547
+
548
+ if (disabled_theme_blocks) {
549
+ translatedContent = translatedContent?.replace(
550
+ /\n(\s*){%-?\s*content_for\s*['"]blocks["']\s*-?%}/gi,
551
+ (str, match) => {
552
+ match = match.replace(/\n/gi, "");
553
+ const arr = [`\n${match}{% liquid`];
554
+ arr.push(`${match} for block in section.blocks`);
555
+ for (const key in blockSchemas) {
556
+ const schema = blockSchemas[key];
557
+ if (schema.disabled) continue;
558
+ arr.push(`${match} if block.type == "${schema.folder}"`);
559
+ arr.push(
560
+ `${match} render "_blocks.${schema.folder}", block: block, forloop: forloop, section_type: section_type, form: form`
561
+ );
562
+ arr.push(`${match} endif`);
563
+ }
564
+ arr.push(`${match} endfor`);
565
+ arr.push(`${match}%}`);
566
+
567
+ return arr.join("\n");
568
+ }
569
+ );
570
+
571
+ translatedContent = translatedContent?.replace(
572
+ /\n(\s*)content_for\s*['"]blocks["']\s*\n/gi,
573
+ (str, match) => {
574
+ match = match.replace(/\n/gi, "");
575
+ const arr = [`\n${match}`];
576
+ arr.push(`${match}for block in section.blocks`);
577
+ for (const key in blockSchemas) {
578
+ const schema = blockSchemas[key];
579
+ if (schema.disabled) continue;
580
+ arr.push(`${match} if block.type == "${schema.folder}"`);
581
+ arr.push(
582
+ `${match} render "_blocks.${schema.folder}", block: block, forloop: forloop, section_type: section_type, form: form`
583
+ );
584
+ arr.push(`${match} endif`);
585
+ }
586
+ arr.push(`${match}endfor`);
587
+ arr.push(``);
588
+
589
+ return arr.join("\n");
590
+ }
591
+ );
592
+
593
+ translatedContent = translatedContent?.replace(
594
+ /\n(\s*){%-?\s*content_for\s*['"]block["']\s*-?%}/gi,
595
+ (str, match) => {
596
+ match = match.replace(/\n/gi, "");
597
+ const arr = [`\n${match}{% liquid`];
598
+ for (const key in blockSchemas) {
599
+ const schema = blockSchemas[key];
600
+ if (schema.disabled) continue;
601
+ arr.push(`${match} if block.type == "${schema.folder}"`);
602
+ arr.push(
603
+ `${match} render "_blocks.${schema.folder}", block: block, forloop: forloop, section_type: section_type, form: form`
604
+ );
605
+ arr.push(`${match} endif`);
606
+ }
607
+ arr.push(`${match}%}`);
608
+
609
+ return arr.join("\n");
610
+ }
611
+ );
612
+ translatedContent = translatedContent?.replace(
613
+ /\n(\s*)content_for\s*['"]block["']\s*\n/gi,
614
+ (str, match) => {
615
+ match = match.replace(/\n/gi, "");
616
+ const arr = [`\n${match}`];
617
+ for (const key in blockSchemas) {
618
+ const schema = blockSchemas[key];
619
+ if (schema.disabled) continue;
620
+ arr.push(`${match}if block.type == "${schema.folder}"`);
621
+ arr.push(
622
+ `${match} render "_blocks.${schema.folder}", block: block, forloop: forloop, section_type: section_type, form: form`
623
+ );
624
+ arr.push(`${match}endif`);
625
+ }
626
+ arr.push(``);
627
+ return arr.join("\n");
628
+ }
629
+ );
630
+ }
631
+
632
+ returnArr.push(translatedContent);
633
+ }
634
+
635
+ if (config.ignore_snippets?.includes(snippetPath.split(/[/\\]/)?.at(-1))) {
636
+ console.log(
637
+ `[${chalk.gray(new Date().toLocaleTimeString())}]: ${chalk.greenBright(
638
+ `Ignored: ${snippetPath.replace(process.cwd(), "")}`
639
+ )}`
640
+ );
641
+ writeOnlyNew(snippetPath, returnArr.join("\n"));
642
+ } else {
643
+ writeCompareFile(snippetPath, returnArr.join("\n"));
644
+ }
645
+ }
646
+
647
+ for (let i = 0; i < giftCards.length; i++) {
648
+ const giftCard = giftCards[i];
649
+ const giftCardName = giftCard.split(/[\\/]/gi).at(-1);
650
+
651
+ const giftCardPath = path.join(process.cwd(), theme_path, "templates", giftCardName);
652
+
653
+ const returnArr = [];
654
+
655
+ const rawContent = fs.readFileSync(giftCard, {
656
+ encoding: "utf-8",
657
+ });
658
+
659
+ if (rawContent) {
660
+ const translatedContent = rawContent.replace(
661
+ /<t(\s+[^>]*)*>((.|\r|\n)*?)<\/t>/gi,
662
+ (_, group1, group2) => {
663
+ const group = toLocaleFriendlySnakeCase(
664
+ giftCard.split(/[\\/]/gi).at(-1).split(".").at(0)
665
+ ).trim();
666
+ const content = toLocaleFriendlySnakeCase(
667
+ group2?.split(" ")?.slice(0, 2)?.join("_") ?? ""
668
+ ).trim();
669
+ const backupContent = toLocaleFriendlySnakeCase(group2).trim();
670
+ const id = toLocaleFriendlySnakeCase(group1?.replace(/id="(.*)"/gi, "$1") ?? "").trim();
671
+
672
+ if (!(group in translations)) {
673
+ translations[group] = {};
674
+ }
675
+
676
+ if (id && !(id in translations[group])) {
677
+ translations[group][id] = group2;
678
+ return `{{ "${group}.${id}" | t }}`;
679
+ }
680
+
681
+ if (!(content in translations[group])) {
682
+ translations[group][content] = group2;
683
+ return `{{ "${group}.${content}" | t }}`;
684
+ }
685
+
686
+ if (translations[group][content] !== group2) {
687
+ if (!(backupContent in translations[group])) {
688
+ translations[group][backupContent] = group2;
689
+ return `{{ "${group}.${backupContent}" | t }}`;
690
+ }
691
+ if (translations[group][backupContent] !== group2) {
692
+ translations[group][`${content}_2`] = group2;
693
+ return `{{ "${group}.${content}_2" | t }}`;
694
+ }
695
+ }
696
+ if (translations[group][content] === group2) {
697
+ return `{{ "${group}.${content}" | t }}`;
698
+ }
699
+
700
+ return group2;
701
+ }
702
+ );
703
+ returnArr.push(translatedContent);
704
+ }
705
+
706
+ writeCompareFile(giftCardPath, returnArr.join("\n"));
707
+ }
708
+
709
+ for (let i = 0; i < layouts.length; i++) {
710
+ const layout = layouts[i];
711
+ const layoutName = layout.split(/[\\/]/gi).at(-1);
712
+ const layoutPath = path.join(process.cwd(), theme_path, "layout", layoutName);
713
+
714
+ const returnArr = [];
715
+
716
+ const rawContent = fs.readFileSync(layout, {
717
+ encoding: "utf-8",
718
+ });
719
+
720
+ if (rawContent) {
721
+ const translatedContent = rawContent.replace(
722
+ /<t(\s+[^>]*)*>((.|\r|\n)*?)<\/t>/gi,
723
+ (_, group1, group2) => {
724
+ const group = toLocaleFriendlySnakeCase(
725
+ layout.split(/[\\/]/gi).at(-1).split(".").at(0)
726
+ ).trim();
727
+ const content = toLocaleFriendlySnakeCase(
728
+ group2?.split(" ")?.slice(0, 2)?.join("_") ?? ""
729
+ ).trim();
730
+ const backupContent = toLocaleFriendlySnakeCase(group2).trim();
731
+ const id = toLocaleFriendlySnakeCase(group1?.replace(/id="(.*)"/gi, "$1") ?? "").trim();
732
+
733
+ if (!(group in translations)) {
734
+ translations[group] = {};
735
+ }
736
+
737
+ if (id && !(id in translations[group])) {
738
+ translations[group][id] = group2;
739
+ return `{{ "${group}.${id}" | t }}`;
740
+ }
741
+
742
+ if (!(content in translations[group])) {
743
+ translations[group][content] = group2;
744
+ return `{{ "${group}.${content}" | t }}`;
745
+ }
746
+
747
+ if (translations[group][content] !== group2) {
748
+ if (!(backupContent in translations[group])) {
749
+ translations[group][backupContent] = group2;
750
+ return `{{ "${group}.${backupContent}" | t }}`;
751
+ }
752
+ if (translations[group][backupContent] !== group2) {
753
+ translations[group][`${content}_2`] = group2;
754
+ return `{{ "${group}.${content}_2" | t }}`;
755
+ }
756
+ }
757
+ if (translations[group][content] === group2) {
758
+ return `{{ "${group}.${content}" | t }}`;
759
+ }
760
+
761
+ return group2;
762
+ }
763
+ );
764
+ returnArr.push(translatedContent);
765
+ }
766
+
767
+ if (config.ignore_layouts?.includes(layoutPath.split(/[/\\]/)?.at(-1))) {
768
+ console.log(
769
+ `[${chalk.gray(new Date().toLocaleTimeString())}]: ${chalk.greenBright(
770
+ `Ignored: ${layoutPath.replace(process.cwd(), "")}`
771
+ )}`
772
+ );
773
+ if (layoutPath.split(/[/\\]/)?.at(-1) === "theme.liquid") {
774
+ writeCompareFile(
775
+ layoutPath.replace("theme.liquid", "theme.dev.liquid"),
776
+ returnArr.join("\n")
777
+ );
778
+ }
779
+ writeOnlyNew(layoutPath, returnArr.join("\n"));
780
+ } else {
781
+ writeCompareFile(layoutPath, returnArr.join("\n"));
782
+ }
783
+ }
784
+
785
+ const transformTranslations = (input, prevKey = "") => {
786
+ if (isObject(input)) {
787
+ return Object.entries(input).reduce<any>((acc, [key, val]) => {
788
+ acc[key] = transformTranslations(val, `${prevKey ? `${prevKey}.` : ""}${key}`);
789
+ return acc;
790
+ }, {});
791
+ }
792
+ if (typeof input === "string") {
793
+ return `{{ '${prevKey}' | t }}`;
794
+ }
795
+ };
796
+
797
+ const translationsJs = `<script data-no-block>
798
+ window.translations = ${JSON.stringify(transformTranslations(translations), undefined, 2)};
799
+ </script>
800
+ `;
801
+
802
+ const translationTypes = `export type Translations = ${JSON.stringify(translations, undefined, 2)
803
+ .replace(/(\s+)([^\n:]*):([^\n{]*?),?\n/gi, "$1/* $3 */\n$1$2: string;\n")
804
+ .replace(/"/gi, "")
805
+ .replace(/,/gi, ";")
806
+ .replace(/}\n/gi, "};\n")
807
+ .replace(/\n\n/gi, "\n")};
808
+ declare global {
809
+ interface Window {
810
+ translations?: Translations;
811
+ }
812
+ }
813
+ `;
814
+
815
+ writeCompareFile(
816
+ path.join(process.cwd(), theme_path, "locales", "en.default.json"),
817
+ JSON.stringify(translations, undefined, 2)
818
+ );
819
+ writeCompareFile(
820
+ path.join(process.cwd(), theme_path, "snippets", "_layout.translations.liquid"),
821
+ translationsJs
822
+ );
823
+ writeCompareFile(path.join(folders.types, "translations.ts"), translationTypes);
824
+
825
+ const dynamicJsImports = [];
826
+
827
+ sources.sectionsJs.forEach((name) => {
828
+ const filename = name.split(/[\\/]/gi).at(-1);
829
+ const section = Object.values(sectionsSchemas).find((section) =>
830
+ section.path.includes(name.replace(filename, ""))
831
+ );
832
+ const targetName = `__section--${filename.replace(/\.(ts)x?$/gi, ".js")}`;
833
+ const targetFile = targets.dynamicJs.find((file) => file.includes(targetName));
834
+
835
+ if (section && !section.disabled) {
836
+ dynamicJsImports.push(
837
+ `<link rel="preload" as="script" href="{{ '${targetName}' | asset_url }}">`
838
+ );
839
+ dynamicJsImports.push(
840
+ `<script type="module" src="{{ '${targetName}' | asset_url }}" defer></script>`
841
+ );
842
+ } else if (targetFile) {
843
+ deleteFile(path.join(root_dir, targetFile));
844
+ config.targets.dynamicJs = config.targets.dynamicJs.filter((target) => target !== targetFile);
845
+ }
846
+ });
847
+
848
+ sources.blocksJs.forEach((name) => {
849
+ const filename = name.split(/[\\/]/gi).at(-1);
850
+ const block = Object.values(blockSchemas).find((section) =>
851
+ section.path.includes(name.replace(filename, ""))
852
+ );
853
+ const targetName = `__block--${filename.replace(/\.(ts)x?$/gi, ".js")}`;
854
+ const targetFile = targets.dynamicJs.find((file) => file.includes(targetName));
855
+
856
+ if (block && !block.disabled) {
857
+ dynamicJsImports.push(
858
+ `<link rel="preload" as="script" href="{{ '${targetName}' | asset_url }}">`
859
+ );
860
+ dynamicJsImports.push(
861
+ `<script type="module" src="{{ '${targetName}' | asset_url }}" defer></script>`
862
+ );
863
+ } else if (targetFile) {
864
+ deleteFile(path.join(root_dir, targetFile));
865
+ config.targets.dynamicJs = config.targets.dynamicJs.filter((target) => target !== targetFile);
866
+ }
867
+ });
868
+
869
+ targets.dynamicJs.forEach((name) => {
870
+ const targetName = name.split(/[\\/]/gi).at(-1).replace(/\.js$/gi, "");
871
+
872
+ const sectionFile = sources.sectionsJs.find(
873
+ (section) =>
874
+ section.includes(`\\${targetName.replace(/__section--/gi, "")}.ts`) ||
875
+ section.includes(`/${targetName.replace(/__section--/gi, "")}.ts`)
876
+ );
877
+
878
+ if (sectionFile) {
879
+ const filename = sectionFile?.split(/[\\/]/gi)?.at(-1);
880
+
881
+ const section = Object.values(sectionsSchemas).find((section) =>
882
+ section.path.includes(sectionFile.replace(filename, ""))
883
+ );
884
+
885
+ if (!section || section.disabled) {
886
+ deleteFile(path.join(root_dir, name));
887
+ config.targets.dynamicJs = config.targets.dynamicJs.filter((target) => target !== name);
888
+ }
889
+ return;
890
+ }
891
+
892
+ const blockFile = sources.blocksJs.find((section) =>
893
+ section.includes(targetName.replace(/__block--/gi, ""))
894
+ );
895
+
896
+ if (blockFile) {
897
+ const filename = blockFile?.split(/[\\/]/gi)?.at(-1);
898
+
899
+ const block = Object.values(blockSchemas).find((section) =>
900
+ section.path.includes(blockFile.replace(filename, ""))
901
+ );
902
+
903
+ if (!block || block.disabled) {
904
+ deleteFile(path.join(root_dir, name));
905
+ config.targets.dynamicJs = config.targets.dynamicJs.filter((target) => target !== name);
906
+ }
907
+ return;
908
+ }
909
+ deleteFile(path.join(root_dir, name));
910
+ config.targets.dynamicJs = config.targets.dynamicJs.filter((target) => target !== name);
911
+ });
912
+
913
+ writeCompareFile(
914
+ path.join(process.cwd(), theme_path, "snippets", "_layout.dynamic-imports.liquid"),
915
+ dynamicJsImports.join("\n")
916
+ );
917
+
918
+ if (delete_external_snippets) {
919
+ targets.snippets.forEach((file) => {
920
+ const fileName = file.split(/[\\/]/gi).at(-1);
921
+ const targetFile = snippets.find((sourcePath) =>
922
+ disabled_theme_blocks && sourcePath?.includes(folders.blocks)
923
+ ? `_blocks.${sourcePath.split(/[\\/]/gi).at(-1)}` === fileName
924
+ : sourcePath.split(/[\\/]/gi).at(-1) === fileName
925
+ );
926
+ if (
927
+ fileName.includes("_layout.translations.liquid") ||
928
+ fileName.includes("_layout.dynamic-imports.liquid") ||
929
+ /^replo/gi.test(fileName) ||
930
+ /^pandectes/gi.test(fileName) ||
931
+ /^locksmith/gi.test(fileName) ||
932
+ /^shogun/gi.test(fileName)
933
+ ) {
934
+ return;
935
+ }
936
+ if (!targetFile) {
937
+ deleteFile(path.join(process.cwd(), file));
938
+ }
939
+ });
940
+ }
941
+
942
+ if (delete_external_sections) {
943
+ targets.sections.forEach((file) => {
944
+ const fileName = file.split(/[\\/]/gi).at(-1);
945
+ const targetFile =
946
+ sources.sectionsLiquid.find(
947
+ (sourcePath) => sourcePath.split(/[\\/]/gi).at(-1) === fileName
948
+ ) ||
949
+ Object.entries(sources.sectionPresetSchemas).find(
950
+ ([key, val]) =>
951
+ val.presets?.filter(
952
+ (preset) =>
953
+ config.all_presets ||
954
+ !preset.enabled_on ||
955
+ preset.enabled_on?.includes(process.env.SHOPIFY_ACCELERATE_STORE)
956
+ )?.length && fileName === `preset__${toSnakeCase(key)}.liquid`
957
+ );
958
+ if (
959
+ /^replo/gi.test(fileName) ||
960
+ /^pandectes/gi.test(fileName) ||
961
+ /^locksmith/gi.test(fileName) ||
962
+ /^shogun/gi.test(fileName)
963
+ ) {
964
+ return;
965
+ }
966
+ if (!targetFile) {
967
+ deleteFile(path.join(process.cwd(), file));
968
+ }
969
+ });
970
+ }
971
+
972
+ if (delete_external_layouts) {
973
+ targets.layout.forEach((file) => {
974
+ const fileName = file.split(/[\\/]/gi).at(-1);
975
+ const targetFile = sources.layouts.find(
976
+ (sourcePath) => sourcePath.split(/[\\/]/gi).at(-1) === fileName
977
+ );
978
+ if (!targetFile) {
979
+ deleteFile(path.join(process.cwd(), file));
980
+ }
981
+ });
982
+ }
983
+
984
+ if (delete_external_blocks) {
985
+ targets.blocks.forEach((file) => {
986
+ const fileName = file.split(/[\\/]/gi).at(-1);
987
+ const targetFile = sources.blocksLiquid.find(
988
+ (sourcePath) => sourcePath.split(/[\\/]/gi).at(-1) === fileName
989
+ );
990
+ if (!targetFile) {
991
+ deleteFile(path.join(process.cwd(), file));
992
+ }
993
+ });
994
+ }
995
+ };