@akqa-denmark/shopify-theme-build 0.1.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.
- package/README.md +258 -0
- package/bin/shopify-build.js +5 -0
- package/dist/chunk-IHCJ6PUT.js +81 -0
- package/dist/chunk-JXQLZXJ2.js +768 -0
- package/dist/cli.js +26 -0
- package/dist/index.d.ts +58 -0
- package/dist/index.js +18 -0
- package/dist/manifest-L2MJQDVK.js +20 -0
- package/dist/prepare-APTEPBMX.js +701 -0
- package/dist/vite.d.ts +5 -0
- package/dist/vite.js +116 -0
- package/package.json +73 -0
|
@@ -0,0 +1,701 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
getStorePaths,
|
|
4
|
+
resolveConfig,
|
|
5
|
+
resolveStore
|
|
6
|
+
} from "./chunk-IHCJ6PUT.js";
|
|
7
|
+
|
|
8
|
+
// src/core/orchestrator.ts
|
|
9
|
+
import { globSync as globSync2 } from "fs";
|
|
10
|
+
import { resolve as resolve3, basename, parse as pathParse } from "path";
|
|
11
|
+
|
|
12
|
+
// src/config/conventions.ts
|
|
13
|
+
var SCHEMA_TYPES = [
|
|
14
|
+
"settings",
|
|
15
|
+
"configs",
|
|
16
|
+
"sections",
|
|
17
|
+
"blocks",
|
|
18
|
+
"section-blocks",
|
|
19
|
+
"section-groups"
|
|
20
|
+
];
|
|
21
|
+
var LIQUID_SCHEMA_TYPES = ["sections", "blocks", "section-blocks"];
|
|
22
|
+
var LOCALE_SCHEMA_TYPES = [
|
|
23
|
+
"settings",
|
|
24
|
+
"configs",
|
|
25
|
+
"sections",
|
|
26
|
+
"blocks",
|
|
27
|
+
"section-blocks"
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
// src/core/schema-builder.ts
|
|
31
|
+
import { readFile, writeFile } from "fs/promises";
|
|
32
|
+
import { statSync, existsSync } from "fs";
|
|
33
|
+
import { resolve, join } from "path";
|
|
34
|
+
|
|
35
|
+
// src/utils/objects.ts
|
|
36
|
+
function deepSortObjectKeys(value) {
|
|
37
|
+
if (Array.isArray(value)) {
|
|
38
|
+
return value.map((v) => deepSortObjectKeys(v));
|
|
39
|
+
}
|
|
40
|
+
if (value && typeof value === "object") {
|
|
41
|
+
const obj = value;
|
|
42
|
+
const sorted = {};
|
|
43
|
+
for (const key of Object.keys(obj).sort((a, b) => a.localeCompare(b))) {
|
|
44
|
+
sorted[key] = deepSortObjectKeys(obj[key]);
|
|
45
|
+
}
|
|
46
|
+
return sorted;
|
|
47
|
+
}
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// src/utils/logger.ts
|
|
52
|
+
import chalk from "chalk";
|
|
53
|
+
var isDebug = process.argv.includes("--debug") || process.env.NODE_ENV === "development" || process.env.NODE_ENV === "dev";
|
|
54
|
+
var npmVerboseLogLevels = /* @__PURE__ */ new Set(["verbose", "silly"]);
|
|
55
|
+
var isVerbose = process.argv.includes("--verbose") || process.env.BUILD_VERBOSE === "true" || npmVerboseLogLevels.has((process.env.npm_config_loglevel || "").toLowerCase());
|
|
56
|
+
var ESC = String.fromCharCode(27);
|
|
57
|
+
var ANSI_PATTERN = new RegExp(`${ESC}\\[[0-9;]*[a-zA-Z]`, "g");
|
|
58
|
+
var CONTROL_CHAR_PATTERN = new RegExp(
|
|
59
|
+
`[${String.fromCharCode(0)}-${String.fromCharCode(31)}${String.fromCharCode(127)}-${String.fromCharCode(159)}\u2028\u2029]`,
|
|
60
|
+
"g"
|
|
61
|
+
);
|
|
62
|
+
var getChalkColor = (type) => {
|
|
63
|
+
switch (type) {
|
|
64
|
+
case "error":
|
|
65
|
+
return "red";
|
|
66
|
+
case "warn":
|
|
67
|
+
return "yellow";
|
|
68
|
+
case "info":
|
|
69
|
+
return "blue";
|
|
70
|
+
case "debug":
|
|
71
|
+
return "gray";
|
|
72
|
+
case "success":
|
|
73
|
+
return "green";
|
|
74
|
+
default:
|
|
75
|
+
return "white";
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
var log = (type = "log", ...args) => {
|
|
79
|
+
const shouldPrint = type === "error" || type === "warn" || type === "success" || isDebug || isVerbose && type === "info" || (isDebug || isVerbose) && type === "debug";
|
|
80
|
+
if (!shouldPrint) return;
|
|
81
|
+
const color = getChalkColor(type);
|
|
82
|
+
try {
|
|
83
|
+
if (type === "debug" || type === "success") {
|
|
84
|
+
console.log(chalk[color](...args));
|
|
85
|
+
} else {
|
|
86
|
+
console[type](
|
|
87
|
+
chalk[color](...args)
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
} catch {
|
|
91
|
+
console.log(chalk[color](...args));
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// src/core/schema-builder.ts
|
|
96
|
+
var SAFETY_COMMENT = "{% comment %} DON'T TOUCH - THIS WILL BE AUTO-GENERATED {% endcomment %}";
|
|
97
|
+
var SAFETY_COMMENT_REGEX = /{%\s*comment\s*%}\s*DON'T TOUCH - THIS WILL BE AUTO-GENERATED\s*{%\s*endcomment\s*%}(\r?\n)?/g;
|
|
98
|
+
function removeLabels(obj) {
|
|
99
|
+
const unwantedKeys = ["originalLabel", "defaultLabel", "fieldType"];
|
|
100
|
+
const removeKeys = (input) => {
|
|
101
|
+
if (Array.isArray(input)) {
|
|
102
|
+
return input.map(removeKeys);
|
|
103
|
+
} else if (typeof input === "object" && input !== null) {
|
|
104
|
+
const result = {};
|
|
105
|
+
Object.keys(input).forEach((key) => {
|
|
106
|
+
if (!unwantedKeys.includes(key) && input[key] !== "") {
|
|
107
|
+
result[key] = removeKeys(input[key]);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
112
|
+
return input;
|
|
113
|
+
};
|
|
114
|
+
return removeKeys(obj);
|
|
115
|
+
}
|
|
116
|
+
function addTranslationKeys(obj, sectionName, type) {
|
|
117
|
+
const addKeys = (input, context = { path: [] }) => {
|
|
118
|
+
if (Array.isArray(input)) {
|
|
119
|
+
return input.map(
|
|
120
|
+
(item, index) => addKeys(item, { ...context, path: [...context.path, index.toString()] })
|
|
121
|
+
);
|
|
122
|
+
} else if (typeof input === "object" && input !== null) {
|
|
123
|
+
const result = { ...input };
|
|
124
|
+
if (result.name && context.path.length === 0) {
|
|
125
|
+
result.name = `t:${type}.${sectionName}.name`;
|
|
126
|
+
}
|
|
127
|
+
if (result.name && context.path.includes("blocks") && result.type) {
|
|
128
|
+
result.name = `t:${type}.${sectionName}.blocks.${result.type}.name`;
|
|
129
|
+
}
|
|
130
|
+
if (result.label && context.path.includes("settings") && result.id) {
|
|
131
|
+
if (context.blockType) {
|
|
132
|
+
result.label = `t:${type}.${sectionName}.blocks.${context.blockType}.settings.${result.id}.label`;
|
|
133
|
+
} else {
|
|
134
|
+
result.label = `t:${type}.${sectionName}.settings.${result.id}.label`;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (result.info && context.path.includes("settings") && result.id) {
|
|
138
|
+
if (context.blockType) {
|
|
139
|
+
result.info = `t:${type}.${sectionName}.blocks.${context.blockType}.settings.${result.id}.info`;
|
|
140
|
+
} else {
|
|
141
|
+
result.info = `t:${type}.${sectionName}.settings.${result.id}.info`;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (result.options && Array.isArray(result.options) && result.id) {
|
|
145
|
+
result.options = result.options.map((option, index) => {
|
|
146
|
+
if (typeof option === "object" && option !== null && "label" in option) {
|
|
147
|
+
const optionPath = context.blockType ? `${type}.${sectionName}.blocks.${context.blockType}.settings.${result.id}.options.option_${index + 1}` : `${type}.${sectionName}.settings.${result.id}.options.option_${index + 1}`;
|
|
148
|
+
return { ...option, label: `t:${optionPath}` };
|
|
149
|
+
}
|
|
150
|
+
return option;
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
Object.keys(result).forEach((key) => {
|
|
154
|
+
if (typeof result[key] === "object" && result[key] !== null) {
|
|
155
|
+
if (key === "blocks" && Array.isArray(result[key])) {
|
|
156
|
+
result[key] = result[key].map((block) => {
|
|
157
|
+
if (typeof block === "object" && block !== null && "type" in block) {
|
|
158
|
+
return addKeys(block, {
|
|
159
|
+
path: [...context.path, "blocks"],
|
|
160
|
+
blockType: block.type
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
return block;
|
|
164
|
+
});
|
|
165
|
+
} else if (key === "settings" && Array.isArray(result[key])) {
|
|
166
|
+
result[key] = result[key].map(
|
|
167
|
+
(setting) => addKeys(setting, {
|
|
168
|
+
path: [...context.path, "settings"],
|
|
169
|
+
blockType: context.blockType
|
|
170
|
+
})
|
|
171
|
+
);
|
|
172
|
+
} else {
|
|
173
|
+
result[key] = addKeys(result[key], context);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
return result;
|
|
178
|
+
}
|
|
179
|
+
return input;
|
|
180
|
+
};
|
|
181
|
+
return addKeys(obj);
|
|
182
|
+
}
|
|
183
|
+
var stringifyCompact = (obj, indent = 0) => {
|
|
184
|
+
const spaces = " ".repeat(indent);
|
|
185
|
+
if (Array.isArray(obj)) {
|
|
186
|
+
if (obj.length === 0) return "[]";
|
|
187
|
+
const allStrings = obj.every((item) => typeof item === "string");
|
|
188
|
+
const totalLength = obj.reduce((sum, item) => {
|
|
189
|
+
return sum + (typeof item === "string" ? item.length : 0);
|
|
190
|
+
}, 0);
|
|
191
|
+
if (allStrings && totalLength < 60) {
|
|
192
|
+
return `[${obj.map((item) => `"${item}"`).join(", ")}]`;
|
|
193
|
+
}
|
|
194
|
+
const items = obj.map((item) => `
|
|
195
|
+
${spaces} ${stringifyCompact(item, indent + 1)}`).join(",");
|
|
196
|
+
return `[${items}
|
|
197
|
+
${spaces}]`;
|
|
198
|
+
}
|
|
199
|
+
if (obj && typeof obj === "object") {
|
|
200
|
+
const entries = Object.entries(obj);
|
|
201
|
+
if (entries.length === 0) return "{}";
|
|
202
|
+
const formatted = entries.map(([key, value]) => {
|
|
203
|
+
const formattedValue = stringifyCompact(value, indent + 1);
|
|
204
|
+
return `
|
|
205
|
+
${spaces} "${key}": ${formattedValue}`;
|
|
206
|
+
}).join(",");
|
|
207
|
+
return `{${formatted}
|
|
208
|
+
${spaces}}`;
|
|
209
|
+
}
|
|
210
|
+
return JSON.stringify(obj);
|
|
211
|
+
};
|
|
212
|
+
var LIQUID_DIR_MAP = {
|
|
213
|
+
sections: "sections",
|
|
214
|
+
blocks: "blocks",
|
|
215
|
+
"section-blocks": "snippets"
|
|
216
|
+
};
|
|
217
|
+
async function buildLiquidSchema(filePath, schema, type, name, themeDir) {
|
|
218
|
+
const directory = LIQUID_DIR_MAP[type];
|
|
219
|
+
if (!directory) return;
|
|
220
|
+
const liquidFilePath = resolve(join(themeDir, directory, `${name}.liquid`));
|
|
221
|
+
if (!existsSync(liquidFilePath)) return;
|
|
222
|
+
try {
|
|
223
|
+
const inputMtime = statSync(filePath).mtimeMs;
|
|
224
|
+
const outputMtime = statSync(liquidFilePath).mtimeMs;
|
|
225
|
+
if (outputMtime > inputMtime) return;
|
|
226
|
+
} catch {
|
|
227
|
+
}
|
|
228
|
+
try {
|
|
229
|
+
const cleanedSchema = removeLabels(schema);
|
|
230
|
+
const translatedSchema = addTranslationKeys(cleanedSchema, name, type);
|
|
231
|
+
const sortedSchema = deepSortObjectKeys(translatedSchema);
|
|
232
|
+
const fileContent = await readFile(liquidFilePath, "utf-8");
|
|
233
|
+
const schemaIndex = fileContent.indexOf("{% schema %}");
|
|
234
|
+
const contentBeforeSchema = schemaIndex === -1 ? fileContent : fileContent.substring(0, schemaIndex);
|
|
235
|
+
const jsonString = stringifyCompact(sortedSchema);
|
|
236
|
+
const schemaJsonIndented = jsonString.split("\n").map((line) => ` ${line}`).join("\n");
|
|
237
|
+
const finalContent = `${contentBeforeSchema.trim()}
|
|
238
|
+
|
|
239
|
+
{% schema %}
|
|
240
|
+
${schemaJsonIndented}
|
|
241
|
+
{% endschema %}
|
|
242
|
+
`;
|
|
243
|
+
await writeFile(liquidFilePath, finalContent, "utf-8");
|
|
244
|
+
await addSafetyComment(liquidFilePath);
|
|
245
|
+
} catch (error) {
|
|
246
|
+
if (error instanceof Error && "code" in error && error.code !== "ENOENT") {
|
|
247
|
+
log("error", `Error processing schema file ${filePath}:`, error.message);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
async function addSafetyComment(filePath) {
|
|
252
|
+
try {
|
|
253
|
+
let data = await readFile(filePath, "utf8");
|
|
254
|
+
if (data.indexOf("{% schema %}") === -1) return;
|
|
255
|
+
data = data.replace(SAFETY_COMMENT_REGEX, "");
|
|
256
|
+
const schemaIndex = data.indexOf("{% schema %}");
|
|
257
|
+
const contentBeforeSchema = data.slice(0, schemaIndex);
|
|
258
|
+
const contentAfterSchema = data.slice(schemaIndex);
|
|
259
|
+
const newData = `${contentBeforeSchema.trimEnd()}
|
|
260
|
+
|
|
261
|
+
${SAFETY_COMMENT}
|
|
262
|
+
${contentAfterSchema.trimEnd()}
|
|
263
|
+
`;
|
|
264
|
+
await writeFile(filePath, newData, "utf8");
|
|
265
|
+
} catch (error) {
|
|
266
|
+
log("error", "Error adding safety comment:", error);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// src/core/config-builder.ts
|
|
271
|
+
import { writeFile as writeFile2 } from "fs/promises";
|
|
272
|
+
import { existsSync as existsSync2, globSync, readFileSync } from "fs";
|
|
273
|
+
import { resolve as resolve2, join as join2 } from "path";
|
|
274
|
+
|
|
275
|
+
// src/utils/schema-comment.ts
|
|
276
|
+
var SCHEMA_COMMENT_HEADER = `/*
|
|
277
|
+
* ------------------------------------------------------------
|
|
278
|
+
* IMPORTANT: The contents of this file are auto-generated.
|
|
279
|
+
*
|
|
280
|
+
* This file may be updated by the Shopify admin language editor
|
|
281
|
+
* or related systems. Please exercise caution as any changes
|
|
282
|
+
* made to this file may be overwritten.
|
|
283
|
+
* ------------------------------------------------------------
|
|
284
|
+
*/`;
|
|
285
|
+
var addSchemaCommentHeader = (jsonContent) => {
|
|
286
|
+
return `${SCHEMA_COMMENT_HEADER}
|
|
287
|
+
${jsonContent}`;
|
|
288
|
+
};
|
|
289
|
+
var createSchemaFileContent = (data) => {
|
|
290
|
+
const jsonContent = JSON.stringify(data, null, 2);
|
|
291
|
+
return addSchemaCommentHeader(jsonContent);
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
// src/utils/version.ts
|
|
295
|
+
import { execSync } from "child_process";
|
|
296
|
+
function resolveVersion() {
|
|
297
|
+
const envTag = process.env.RELEASE_TAG;
|
|
298
|
+
if (envTag) {
|
|
299
|
+
return stripVPrefix(envTag);
|
|
300
|
+
}
|
|
301
|
+
const tag = tryGitDescribe();
|
|
302
|
+
if (process.env.CI === "true") {
|
|
303
|
+
return tag ? stripVPrefix(tag) : "dev";
|
|
304
|
+
}
|
|
305
|
+
if (tag) {
|
|
306
|
+
return `${stripVPrefix(tag)}-dev`;
|
|
307
|
+
}
|
|
308
|
+
return "dev";
|
|
309
|
+
}
|
|
310
|
+
function tryGitDescribe() {
|
|
311
|
+
try {
|
|
312
|
+
const tag = execSync("git describe --tags --abbrev=0", {
|
|
313
|
+
encoding: "utf-8",
|
|
314
|
+
stdio: "pipe"
|
|
315
|
+
}).trim();
|
|
316
|
+
return tag || null;
|
|
317
|
+
} catch {
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
function stripVPrefix(tag) {
|
|
322
|
+
return tag.startsWith("v") ? tag.slice(1) : tag;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// src/core/config-builder.ts
|
|
326
|
+
function removeLabels2(obj) {
|
|
327
|
+
const unwantedKeys = ["originalLabel", "defaultLabel", "fieldType"];
|
|
328
|
+
const removeKeys = (input) => {
|
|
329
|
+
if (Array.isArray(input)) {
|
|
330
|
+
return input.map(removeKeys);
|
|
331
|
+
} else if (typeof input === "object" && input !== null) {
|
|
332
|
+
const result = {};
|
|
333
|
+
Object.keys(input).forEach((key) => {
|
|
334
|
+
if (!unwantedKeys.includes(key) && input[key] !== "") {
|
|
335
|
+
result[key] = removeKeys(input[key]);
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
return result;
|
|
339
|
+
}
|
|
340
|
+
return input;
|
|
341
|
+
};
|
|
342
|
+
return removeKeys(obj);
|
|
343
|
+
}
|
|
344
|
+
function addTranslationKeys2(obj, configName) {
|
|
345
|
+
const addKeys = (input, context = { path: [] }) => {
|
|
346
|
+
if (Array.isArray(input)) {
|
|
347
|
+
return input.map(
|
|
348
|
+
(item, index) => addKeys(item, { ...context, path: [...context.path, index.toString()] })
|
|
349
|
+
);
|
|
350
|
+
} else if (typeof input === "object" && input !== null) {
|
|
351
|
+
const result = { ...input };
|
|
352
|
+
if (result.name && context.path.length === 0) {
|
|
353
|
+
result.name = `t:theme_settings.${configName}.name`;
|
|
354
|
+
}
|
|
355
|
+
if (result.label && context.path.includes("settings") && result.id) {
|
|
356
|
+
result.label = `t:theme_settings.${configName}.settings.${result.id}.label`;
|
|
357
|
+
}
|
|
358
|
+
if (result.info && context.path.includes("settings") && result.id) {
|
|
359
|
+
result.info = `t:theme_settings.${configName}.settings.${result.id}.info`;
|
|
360
|
+
}
|
|
361
|
+
if (result.options && Array.isArray(result.options) && result.id) {
|
|
362
|
+
result.options = result.options.map((option, index) => {
|
|
363
|
+
if (typeof option === "object" && option !== null && "label" in option) {
|
|
364
|
+
return {
|
|
365
|
+
...option,
|
|
366
|
+
label: `t:theme_settings.${configName}.settings.${result.id}.options.option_${index + 1}`
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
return option;
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
Object.keys(result).forEach((key) => {
|
|
373
|
+
if (typeof result[key] === "object" && result[key] !== null) {
|
|
374
|
+
if (key === "settings" && Array.isArray(result[key])) {
|
|
375
|
+
result[key] = result[key].map(
|
|
376
|
+
(setting) => addKeys(setting, { path: [...context.path, "settings"] })
|
|
377
|
+
);
|
|
378
|
+
} else {
|
|
379
|
+
result[key] = addKeys(result[key], context);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
return result;
|
|
384
|
+
}
|
|
385
|
+
return input;
|
|
386
|
+
};
|
|
387
|
+
return addKeys(obj);
|
|
388
|
+
}
|
|
389
|
+
function toPascalCamel(name) {
|
|
390
|
+
return name.charAt(0).toUpperCase() + name.slice(1).replace(/-([a-z])/g, (_, l) => l.toUpperCase());
|
|
391
|
+
}
|
|
392
|
+
function readExistingSchema(filePath) {
|
|
393
|
+
try {
|
|
394
|
+
const raw = readFileSync(filePath, "utf8");
|
|
395
|
+
const trimmed = raw.trimStart();
|
|
396
|
+
let jsonOnly = raw;
|
|
397
|
+
if (trimmed.startsWith("/*")) {
|
|
398
|
+
const end = trimmed.indexOf("*/");
|
|
399
|
+
if (end !== -1) jsonOnly = trimmed.slice(end + 2).trimStart();
|
|
400
|
+
}
|
|
401
|
+
const parsed = JSON.parse(jsonOnly);
|
|
402
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
403
|
+
} catch {
|
|
404
|
+
return [];
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
async function buildConfigs(paths, store, config) {
|
|
408
|
+
const configsDir = join2(paths.schemasDir, "configs");
|
|
409
|
+
const settingsSchemaPath = resolve2(join2(paths.configDir, "settings_schema.json"));
|
|
410
|
+
const pattern = join2(configsDir, "**/*.ts");
|
|
411
|
+
let files;
|
|
412
|
+
try {
|
|
413
|
+
files = globSync(pattern).filter((f) => !f.includes("index.ts")).sort();
|
|
414
|
+
} catch {
|
|
415
|
+
files = [];
|
|
416
|
+
}
|
|
417
|
+
const processedConfigs = [];
|
|
418
|
+
await Promise.all(
|
|
419
|
+
files.map(async (file) => {
|
|
420
|
+
const name = file.split("/").pop()?.replace(/\.ts$/, "") || "";
|
|
421
|
+
if (name === "theme-info") return;
|
|
422
|
+
try {
|
|
423
|
+
const module = await import(`${resolve2(file)}?t=${Date.now()}`);
|
|
424
|
+
const configFunctionName = `${toPascalCamel(name)}Config`;
|
|
425
|
+
const configFunction = module[configFunctionName];
|
|
426
|
+
if (!configFunction || typeof configFunction !== "function") {
|
|
427
|
+
log("warn", `No ${configFunctionName} function found in ${file}`);
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
const schema = configFunction();
|
|
431
|
+
if (!schema || typeof schema !== "object") return;
|
|
432
|
+
const cleaned = removeLabels2(schema);
|
|
433
|
+
const translated = addTranslationKeys2(cleaned, name);
|
|
434
|
+
const sorted = deepSortObjectKeys(translated);
|
|
435
|
+
processedConfigs.push(sorted);
|
|
436
|
+
} catch (error) {
|
|
437
|
+
if (error instanceof Error && "code" in error && error.code !== "ENOENT") {
|
|
438
|
+
log("error", `Error processing config ${file}:`, error.message);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
})
|
|
442
|
+
);
|
|
443
|
+
const themeInfoPath = resolve2(join2(configsDir, "theme-info"));
|
|
444
|
+
let themeInfo;
|
|
445
|
+
try {
|
|
446
|
+
const mod = await import(`${themeInfoPath}?t=${Date.now()}`);
|
|
447
|
+
if (!mod.ThemeInfoConfig || typeof mod.ThemeInfoConfig !== "function") {
|
|
448
|
+
throw new Error("ThemeInfoConfig not found");
|
|
449
|
+
}
|
|
450
|
+
themeInfo = mod.ThemeInfoConfig();
|
|
451
|
+
} catch {
|
|
452
|
+
log("warn", `Could not load ThemeInfoConfig; using default for store: ${store}`);
|
|
453
|
+
themeInfo = {
|
|
454
|
+
name: "theme_info",
|
|
455
|
+
theme_name: `${store} Theme`,
|
|
456
|
+
theme_author: "AKQA Denmark",
|
|
457
|
+
theme_version: "1.0.0",
|
|
458
|
+
theme_documentation_url: "https://akqa.com",
|
|
459
|
+
theme_support_email: "support@akqa.com"
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
themeInfo.theme_version = resolveVersion();
|
|
463
|
+
const storeConfig = config.stores.find((s) => s.slug === store);
|
|
464
|
+
if (storeConfig?.name) {
|
|
465
|
+
themeInfo.theme_name = storeConfig.name;
|
|
466
|
+
}
|
|
467
|
+
log("info", `\u2713 Injected theme info: ${themeInfo.theme_name} v${themeInfo.theme_version}`);
|
|
468
|
+
const hasProcessed = processedConfigs.length > 0;
|
|
469
|
+
let allConfigs;
|
|
470
|
+
if (hasProcessed) {
|
|
471
|
+
const sortedRest = processedConfigs.filter((c) => c.name !== "theme_info").sort((a, b) => (a?.name || "").localeCompare(b?.name || ""));
|
|
472
|
+
allConfigs = [themeInfo, ...sortedRest];
|
|
473
|
+
} else {
|
|
474
|
+
const existing = existsSync2(settingsSchemaPath) ? readExistingSchema(settingsSchemaPath) : [];
|
|
475
|
+
const rest = existing.filter((c) => c?.name !== "theme_info");
|
|
476
|
+
allConfigs = [themeInfo, ...rest];
|
|
477
|
+
}
|
|
478
|
+
const finalContent = createSchemaFileContent(deepSortObjectKeys(allConfigs));
|
|
479
|
+
await writeFile2(settingsSchemaPath, finalContent, "utf-8");
|
|
480
|
+
log("info", `\u2705 Generated settings_schema.json with ${allConfigs.length} config sections`);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// src/core/section-group-builder.ts
|
|
484
|
+
import { readFile as readFile2, writeFile as writeFile3, mkdir } from "fs/promises";
|
|
485
|
+
import { existsSync as existsSync3 } from "fs";
|
|
486
|
+
import { join as join3, dirname } from "path";
|
|
487
|
+
var COMMENT_HEADER = `/*
|
|
488
|
+
* ------------------------------------------------------------
|
|
489
|
+
* IMPORTANT: The contents of this file are auto-generated.
|
|
490
|
+
*
|
|
491
|
+
* This file may be updated by the Shopify admin theme editor
|
|
492
|
+
* or related systems. Please exercise caution as any changes
|
|
493
|
+
* made to this file may be overwritten.
|
|
494
|
+
* ------------------------------------------------------------
|
|
495
|
+
*/`;
|
|
496
|
+
async function buildSectionGroupFile(schema, name, themeDir) {
|
|
497
|
+
try {
|
|
498
|
+
const sg = schema;
|
|
499
|
+
if (!sg?.type || !sg?.name) {
|
|
500
|
+
log("warn", `Section group "${name}" missing required 'type' or 'name'`);
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
const outputPath = join3(themeDir, "sections", `${name}-group.json`);
|
|
504
|
+
await mkdir(dirname(outputPath), { recursive: true });
|
|
505
|
+
let existingSections = {};
|
|
506
|
+
let existingOrder = [];
|
|
507
|
+
if (existsSync3(outputPath)) {
|
|
508
|
+
try {
|
|
509
|
+
const raw = await readFile2(outputPath, "utf-8");
|
|
510
|
+
const jsonOnly = raw.trimStart().startsWith("/*") ? raw.slice(raw.indexOf("*/") + 2).trimStart() : raw;
|
|
511
|
+
const parsed = JSON.parse(jsonOnly);
|
|
512
|
+
existingSections = parsed.sections ?? {};
|
|
513
|
+
existingOrder = parsed.order ?? [];
|
|
514
|
+
} catch {
|
|
515
|
+
log("warn", `Could not parse existing ${name}-group.json \u2014 will create fresh`);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
const output = {
|
|
519
|
+
type: sg.type,
|
|
520
|
+
name: sg.name,
|
|
521
|
+
sections: existingSections,
|
|
522
|
+
order: existingOrder
|
|
523
|
+
};
|
|
524
|
+
const fileContent = `${COMMENT_HEADER}
|
|
525
|
+
${JSON.stringify(output, null, 2)}
|
|
526
|
+
`;
|
|
527
|
+
await writeFile3(outputPath, fileContent, "utf-8");
|
|
528
|
+
log("info", `\u2713 Section group: ${name}-group.json`);
|
|
529
|
+
} catch (error) {
|
|
530
|
+
log("error", `Error building section group "${name}":`, error.message);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// src/utils/strings.ts
|
|
535
|
+
function capitalizeFirstLetter(string) {
|
|
536
|
+
return string.charAt(0).toUpperCase() + string.slice(1);
|
|
537
|
+
}
|
|
538
|
+
function snakifyString(inputString) {
|
|
539
|
+
return inputString ? inputString.replace(/["\\/]/g, "").replace(/[\s-]+/g, "_").toLowerCase() : "";
|
|
540
|
+
}
|
|
541
|
+
function formatToName(fileName, type) {
|
|
542
|
+
const pascalCase = fileName.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
|
|
543
|
+
if (type === "section-blocks") {
|
|
544
|
+
return `${pascalCase}Block`;
|
|
545
|
+
}
|
|
546
|
+
const suffix = capitalizeFirstLetter(type).slice(0, -1);
|
|
547
|
+
return `${pascalCase}${suffix}`;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// src/core/locale-extractor.ts
|
|
551
|
+
function simplifyObject(obj) {
|
|
552
|
+
if (!obj || typeof obj !== "object") return void 0;
|
|
553
|
+
const o = obj;
|
|
554
|
+
const newObj = {};
|
|
555
|
+
if (o.name) newObj.name = o.name;
|
|
556
|
+
if (o.label) newObj.label = o.label;
|
|
557
|
+
if (o.info) newObj.info = o.info;
|
|
558
|
+
if (o.placeholder) newObj.placeholder = o.placeholder;
|
|
559
|
+
if (Array.isArray(o.options)) {
|
|
560
|
+
const options = {};
|
|
561
|
+
o.options.forEach((opt, index) => {
|
|
562
|
+
if (opt.label) {
|
|
563
|
+
options[`option_${index + 1}`] = opt.label;
|
|
564
|
+
}
|
|
565
|
+
});
|
|
566
|
+
if (Object.keys(options).length > 0) newObj.options = options;
|
|
567
|
+
}
|
|
568
|
+
if (Array.isArray(o.settings)) {
|
|
569
|
+
const settings = {};
|
|
570
|
+
o.settings.forEach((setting) => {
|
|
571
|
+
const id = setting.id || snakifyString(setting.label || "unknown");
|
|
572
|
+
if (id) {
|
|
573
|
+
const simplified = simplifyObject(setting);
|
|
574
|
+
if (simplified && Object.keys(simplified).length > 0) {
|
|
575
|
+
settings[id] = simplified;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
});
|
|
579
|
+
if (Object.keys(settings).length > 0) newObj.settings = settings;
|
|
580
|
+
}
|
|
581
|
+
if (Array.isArray(o.blocks)) {
|
|
582
|
+
const blocks = {};
|
|
583
|
+
o.blocks.forEach((block) => {
|
|
584
|
+
const type = block.type || snakifyString(block.name || "unknown");
|
|
585
|
+
if (type) {
|
|
586
|
+
const simplified = simplifyObject(block);
|
|
587
|
+
if (simplified && Object.keys(simplified).length > 0) {
|
|
588
|
+
blocks[type] = simplified;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
});
|
|
592
|
+
if (Object.keys(blocks).length > 0) newObj.blocks = blocks;
|
|
593
|
+
}
|
|
594
|
+
return Object.keys(newObj).length > 0 ? newObj : void 0;
|
|
595
|
+
}
|
|
596
|
+
function extractLocaleData(schema) {
|
|
597
|
+
return simplifyObject(schema) || {};
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// src/core/locale-merger.ts
|
|
601
|
+
import { writeFile as writeFile4 } from "fs/promises";
|
|
602
|
+
import { join as join4 } from "path";
|
|
603
|
+
async function mergeAndWriteLocales(accumulator, localesDir) {
|
|
604
|
+
const mergedData = {};
|
|
605
|
+
const typeMap = [
|
|
606
|
+
{ accKey: "settings", outputKey: "global" },
|
|
607
|
+
{ accKey: "configs", outputKey: "theme_settings" },
|
|
608
|
+
{ accKey: "section-blocks", outputKey: "section_blocks" },
|
|
609
|
+
{ accKey: "sections", outputKey: "sections" },
|
|
610
|
+
{ accKey: "blocks", outputKey: "blocks" }
|
|
611
|
+
];
|
|
612
|
+
for (const { accKey, outputKey } of typeMap) {
|
|
613
|
+
const typeData = accumulator[accKey];
|
|
614
|
+
if (!typeData || Object.keys(typeData).length === 0) continue;
|
|
615
|
+
mergedData[outputKey] = typeData;
|
|
616
|
+
}
|
|
617
|
+
const sortedData = deepSortObjectKeys(mergedData);
|
|
618
|
+
const finalContent = createSchemaFileContent(sortedData);
|
|
619
|
+
const outputPath = join4(localesDir, "en.default.schema.json");
|
|
620
|
+
await writeFile4(outputPath, finalContent, "utf-8");
|
|
621
|
+
log("info", `\u2705 Generated en.default.schema.json`);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// src/core/orchestrator.ts
|
|
625
|
+
async function orchestrate(options) {
|
|
626
|
+
const { config, skipSchemas = false, skipLocales = false } = options;
|
|
627
|
+
const store = resolveStore(config, options.store);
|
|
628
|
+
const paths = getStorePaths(config, store);
|
|
629
|
+
const startTime = Date.now();
|
|
630
|
+
log("info", `Building store: ${store}`);
|
|
631
|
+
const localeAccumulator = {};
|
|
632
|
+
for (const schemaType of SCHEMA_TYPES) {
|
|
633
|
+
const schemaDir = `${paths.schemasDir}/${schemaType}`;
|
|
634
|
+
const pattern = `${schemaDir}/**/*.ts`;
|
|
635
|
+
let files;
|
|
636
|
+
try {
|
|
637
|
+
files = globSync2(pattern).filter((f) => !basename(f).startsWith("index.")).sort();
|
|
638
|
+
} catch {
|
|
639
|
+
continue;
|
|
640
|
+
}
|
|
641
|
+
if (files.length === 0) continue;
|
|
642
|
+
const batchSize = config.build.parallel ? Math.min(16, files.length) : 1;
|
|
643
|
+
for (let i = 0; i < files.length; i += batchSize) {
|
|
644
|
+
const batch = files.slice(i, i + batchSize);
|
|
645
|
+
await Promise.all(
|
|
646
|
+
batch.map(async (file) => {
|
|
647
|
+
const { name } = pathParse(file);
|
|
648
|
+
try {
|
|
649
|
+
const module = await import(`${resolve3(file)}?t=${Date.now()}`);
|
|
650
|
+
let schema = module.default || module[formatToName(name, schemaType)];
|
|
651
|
+
if (typeof schema === "function") schema = schema();
|
|
652
|
+
if (!schema) return;
|
|
653
|
+
if (!skipSchemas && LIQUID_SCHEMA_TYPES.includes(schemaType)) {
|
|
654
|
+
await buildLiquidSchema(file, schema, schemaType, name, paths.themeDir);
|
|
655
|
+
}
|
|
656
|
+
if (!skipSchemas && schemaType === "section-groups") {
|
|
657
|
+
await buildSectionGroupFile(schema, name, paths.themeDir);
|
|
658
|
+
}
|
|
659
|
+
if (!skipLocales && LOCALE_SCHEMA_TYPES.includes(schemaType)) {
|
|
660
|
+
const localeData = extractLocaleData(schema);
|
|
661
|
+
if (localeData && Object.keys(localeData).length > 0) {
|
|
662
|
+
if (!localeAccumulator[schemaType]) {
|
|
663
|
+
localeAccumulator[schemaType] = {};
|
|
664
|
+
}
|
|
665
|
+
localeAccumulator[schemaType][name] = localeData;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
} catch (error) {
|
|
669
|
+
if (error instanceof Error && "code" in error && error.code !== "ENOENT") {
|
|
670
|
+
log("error", `Error processing ${file}:`, error.message);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
})
|
|
674
|
+
);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
if (!skipSchemas) {
|
|
678
|
+
await buildConfigs(paths, store, config);
|
|
679
|
+
}
|
|
680
|
+
if (!skipLocales) {
|
|
681
|
+
await mergeAndWriteLocales(localeAccumulator, paths.localesDir);
|
|
682
|
+
}
|
|
683
|
+
const elapsed = Date.now() - startTime;
|
|
684
|
+
log("success", `Build completed in ${elapsed}ms`);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// src/cli/prepare.ts
|
|
688
|
+
async function runPrepare(args) {
|
|
689
|
+
const storeFlag = args.indexOf("--store");
|
|
690
|
+
const store = storeFlag !== -1 ? args[storeFlag + 1] : void 0;
|
|
691
|
+
const config = await resolveConfig();
|
|
692
|
+
await orchestrate({
|
|
693
|
+
store,
|
|
694
|
+
config,
|
|
695
|
+
skipSchemas: args.includes("--skip-schemas"),
|
|
696
|
+
skipLocales: args.includes("--skip-locales")
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
export {
|
|
700
|
+
runPrepare
|
|
701
|
+
};
|