@zohofinance/commerce 2.1.0 → 2.2.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/VALIDATIONS.md +122 -0
- package/package.json +2 -2
- package/public/dev-error.liquid +49 -0
- package/ts-dist/commands/build-theme.d.ts.map +1 -1
- package/ts-dist/commands/build-theme.js +38 -11
- package/ts-dist/commands/serve-theme.d.ts.map +1 -1
- package/ts-dist/commands/serve-theme.js +213 -49
- package/ts-dist/index.js +0 -0
- package/ts-dist/mocks/global-objects.d.ts.map +1 -1
- package/ts-dist/mocks/global-objects.js +1 -0
- package/ts-dist/mocks/init-theme/i18n/en.schema.json +2 -1
- package/ts-dist/mocks/init-theme/sections/footer.liquid +3 -1
- package/ts-dist/mocks/init-theme/sections/header-topbar.liquid +3 -1
- package/ts-dist/mocks/init-theme/sections/product-collection.liquid +3 -1
- package/ts-dist/mocks/init-theme/sections/promo-banner.liquid +5 -1
- package/ts-dist/mocks/reviews.d.ts +6 -0
- package/ts-dist/mocks/reviews.d.ts.map +1 -0
- package/ts-dist/mocks/reviews.js +52 -0
- package/ts-dist/routes/storefront-routes.js +1 -1
- package/ts-dist/utils/api-handler.d.ts +6 -1
- package/ts-dist/utils/api-handler.d.ts.map +1 -1
- package/ts-dist/utils/api-handler.js +91 -35
- package/ts-dist/utils/error-report.d.ts +1 -1
- package/ts-dist/utils/error-report.d.ts.map +1 -1
- package/ts-dist/utils/error-report.js +58 -18
- package/ts-dist/utils/load-theme-globals.d.ts +1 -1
- package/ts-dist/utils/load-theme-globals.d.ts.map +1 -1
- package/ts-dist/utils/load-theme-globals.js +42 -2
- package/ts-dist/utils/resolve-schema-settings.d.ts.map +1 -1
- package/ts-dist/utils/resolve-schema-settings.js +0 -1
- package/ts-dist/utils/resolve-schema-translations.d.ts +1 -0
- package/ts-dist/utils/resolve-schema-translations.d.ts.map +1 -1
- package/ts-dist/utils/resolve-schema-translations.js +3 -0
- package/ts-dist/utils/schema-validation.d.ts +34 -0
- package/ts-dist/utils/schema-validation.d.ts.map +1 -0
- package/ts-dist/utils/schema-validation.js +544 -0
- package/ts-dist/utils/serve-theme-utils.d.ts +1 -1
- package/ts-dist/utils/serve-theme-utils.d.ts.map +1 -1
- package/ts-dist/utils/serve-theme-utils.js +1 -1
- package/ts-dist/utils/template-reference-validation.d.ts +11 -0
- package/ts-dist/utils/template-reference-validation.d.ts.map +1 -0
- package/ts-dist/utils/template-reference-validation.js +466 -0
- package/ts-dist/utils/theme-block-schema.d.ts +5 -0
- package/ts-dist/utils/theme-block-schema.d.ts.map +1 -0
- package/ts-dist/utils/theme-block-schema.js +35 -0
- package/ts-dist/utils/theme-validation.d.ts +8 -1
- package/ts-dist/utils/theme-validation.d.ts.map +1 -1
- package/ts-dist/utils/theme-validation.js +84 -143
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-handler.d.ts","sourceRoot":"","sources":["../../src/utils/api-handler.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"api-handler.d.ts","sourceRoot":"","sources":["../../src/utils/api-handler.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,6 +9,7 @@ import blog from '../mocks/blog.js';
|
|
|
9
9
|
import category from '../mocks/category.js';
|
|
10
10
|
import categories from '../mocks/categories.js';
|
|
11
11
|
import globalObjectsMock from '../mocks/global-objects.js';
|
|
12
|
+
import productReviews from '../mocks/reviews.js';
|
|
12
13
|
function mockJson(data) {
|
|
13
14
|
return {
|
|
14
15
|
json: async () => data
|
|
@@ -20,6 +21,30 @@ function withPageTitle(data, title) {
|
|
|
20
21
|
page_title: title
|
|
21
22
|
};
|
|
22
23
|
}
|
|
24
|
+
function rewriteStoreUrls(value, origin) {
|
|
25
|
+
if (!origin)
|
|
26
|
+
return value;
|
|
27
|
+
if (typeof value === 'string') {
|
|
28
|
+
if (value === origin)
|
|
29
|
+
return '/';
|
|
30
|
+
if (value.startsWith(origin) &&
|
|
31
|
+
/^[/?#]/.test(value.slice(origin.length))) {
|
|
32
|
+
return value.slice(origin.length);
|
|
33
|
+
}
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
if (Array.isArray(value)) {
|
|
37
|
+
return value.map((item) => rewriteStoreUrls(item, origin));
|
|
38
|
+
}
|
|
39
|
+
if (value && typeof value === 'object') {
|
|
40
|
+
const out = {};
|
|
41
|
+
for (const [k, v] of Object.entries(value)) {
|
|
42
|
+
out[k] = rewriteStoreUrls(v, origin);
|
|
43
|
+
}
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
23
48
|
export class APIHandler {
|
|
24
49
|
useMock;
|
|
25
50
|
cookies = [];
|
|
@@ -37,23 +62,41 @@ export class APIHandler {
|
|
|
37
62
|
}
|
|
38
63
|
return handler;
|
|
39
64
|
}
|
|
65
|
+
// Stores cookies by name so a refreshed session replaces the old one instead
|
|
66
|
+
// of piling up duplicates.
|
|
67
|
+
#storeCookies(response) {
|
|
68
|
+
const setCookies = typeof response.headers.getSetCookie === 'function'
|
|
69
|
+
? response.headers.getSetCookie()
|
|
70
|
+
: (response.headers.get('set-cookie') ?? '')
|
|
71
|
+
.split(/,(?=\s*[^;,=\s]+=)/)
|
|
72
|
+
.filter(Boolean);
|
|
73
|
+
for (const cookie of setCookies) {
|
|
74
|
+
const pair = cookie.split(';')[0].trim();
|
|
75
|
+
const name = pair.split('=')[0];
|
|
76
|
+
if (!name)
|
|
77
|
+
continue;
|
|
78
|
+
this.cookies = this.cookies.filter((existing) => existing.split('=')[0] !== name);
|
|
79
|
+
this.cookies.push(pair);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
get cookieHeader() {
|
|
83
|
+
return this.cookies.join('; ');
|
|
84
|
+
}
|
|
40
85
|
async #cookieSetter() {
|
|
41
86
|
try {
|
|
42
87
|
const response = await fetch(`${this.baseURL}/home?format=json`);
|
|
43
|
-
|
|
44
|
-
setCookieHeader?.split(',').forEach((cookie) => {
|
|
45
|
-
this.cookies.push(cookie.split(';')[0]);
|
|
46
|
-
});
|
|
88
|
+
this.#storeCookies(response);
|
|
47
89
|
}
|
|
48
90
|
catch (e) {
|
|
49
91
|
console.warn('Could not establish store session cookies.', e);
|
|
50
92
|
}
|
|
51
93
|
}
|
|
52
94
|
async #makeRequest(requestUrl) {
|
|
53
|
-
const
|
|
95
|
+
const separator = requestUrl.includes('?') ? '&' : '?';
|
|
96
|
+
const response = await fetch(`${this.baseURL}${requestUrl}${separator}format=json`, {
|
|
54
97
|
method: 'GET',
|
|
55
98
|
headers: {
|
|
56
|
-
Cookie: this.
|
|
99
|
+
Cookie: this.cookieHeader
|
|
57
100
|
}
|
|
58
101
|
});
|
|
59
102
|
return response;
|
|
@@ -95,8 +138,9 @@ export class APIHandler {
|
|
|
95
138
|
});
|
|
96
139
|
}
|
|
97
140
|
if (requestUrl.match(/^\/products\/[^/]+/)) {
|
|
98
|
-
const productHandle = requestUrl.split('/').pop();
|
|
99
|
-
|
|
141
|
+
const productHandle = decodeURIComponent(requestUrl.split('?')[0].split('/').pop() ?? '');
|
|
142
|
+
// Unknown handles fall back to the first mock product so the PDP still renders.
|
|
143
|
+
const product = products.find((item) => item.handle === productHandle || item.id === productHandle) ?? products[0];
|
|
100
144
|
return mockJson({
|
|
101
145
|
product,
|
|
102
146
|
page_title: product?.title ?? 'Product'
|
|
@@ -150,17 +194,25 @@ export class APIHandler {
|
|
|
150
194
|
try {
|
|
151
195
|
let response;
|
|
152
196
|
if (this.useMock) {
|
|
153
|
-
response = this.#makeMockRequest(requestUrl);
|
|
197
|
+
response = this.#makeMockRequest(requestUrl.split('?')[0]);
|
|
154
198
|
}
|
|
155
199
|
else {
|
|
156
200
|
response = await this.#makeRequest(requestUrl);
|
|
157
201
|
}
|
|
158
202
|
if (response instanceof Response) {
|
|
159
203
|
if (!response.ok) {
|
|
160
|
-
|
|
204
|
+
const detail = (await response.text().catch(() => ''))
|
|
205
|
+
.trim()
|
|
206
|
+
.slice(0, 300);
|
|
207
|
+
throw new Error(`Failed to fetch data from ${requestUrl} (${response.status})${detail ? `: ${detail}` : ''}`);
|
|
161
208
|
}
|
|
162
209
|
}
|
|
163
|
-
|
|
210
|
+
let json = (await response.json());
|
|
211
|
+
// Live responses carry absolute store URLs (menu items, product links...).
|
|
212
|
+
// Make them relative so navigation stays on the local dev server.
|
|
213
|
+
if (!this.useMock && this.baseURL) {
|
|
214
|
+
json = rewriteStoreUrls(json, this.baseURL);
|
|
215
|
+
}
|
|
164
216
|
if (json.staticAssetsUrl) {
|
|
165
217
|
json.staticAssetsUrl = '/assets';
|
|
166
218
|
}
|
|
@@ -178,7 +230,7 @@ export class APIHandler {
|
|
|
178
230
|
const requestConfig = {
|
|
179
231
|
method: reqMethod,
|
|
180
232
|
headers: {
|
|
181
|
-
Cookie: this.
|
|
233
|
+
Cookie: this.cookieHeader
|
|
182
234
|
}
|
|
183
235
|
};
|
|
184
236
|
if (reqMethod === 'DELETE') {
|
|
@@ -196,10 +248,7 @@ export class APIHandler {
|
|
|
196
248
|
requestConfig.body = formData;
|
|
197
249
|
}
|
|
198
250
|
const result = await fetch(requestUrl, requestConfig);
|
|
199
|
-
|
|
200
|
-
setCookieHeader?.split(',').forEach((cookie) => {
|
|
201
|
-
this.cookies.push(cookie.split(';')[0]);
|
|
202
|
-
});
|
|
251
|
+
this.#storeCookies(result);
|
|
203
252
|
return result.json();
|
|
204
253
|
}
|
|
205
254
|
#findVariantInProducts(variantId) {
|
|
@@ -252,17 +301,14 @@ export class APIHandler {
|
|
|
252
301
|
}
|
|
253
302
|
async #proxyRequest(method, path, body = null, query = '') {
|
|
254
303
|
const url = `${this.baseURL}${path}${query ? `?${query}` : ''}`;
|
|
255
|
-
const headers = { Cookie: this.
|
|
304
|
+
const headers = { Cookie: this.cookieHeader };
|
|
256
305
|
const init = { method, headers };
|
|
257
306
|
if (body !== null && method !== 'GET' && method !== 'DELETE') {
|
|
258
307
|
headers['Content-Type'] = 'application/json';
|
|
259
308
|
init.body = JSON.stringify(body);
|
|
260
309
|
}
|
|
261
310
|
const res = await fetch(url, init);
|
|
262
|
-
|
|
263
|
-
setCookieHeader?.split(',').forEach((cookie) => {
|
|
264
|
-
this.cookies.push(cookie.split(';')[0]);
|
|
265
|
-
});
|
|
311
|
+
this.#storeCookies(res);
|
|
266
312
|
return res.json();
|
|
267
313
|
}
|
|
268
314
|
async addToCart(data) {
|
|
@@ -343,7 +389,7 @@ export class APIHandler {
|
|
|
343
389
|
return this.#computeMockCart();
|
|
344
390
|
}
|
|
345
391
|
const res = await fetch(`${this.baseURL}/storefront/api/v1/cart`, {
|
|
346
|
-
headers: { Cookie: this.
|
|
392
|
+
headers: { Cookie: this.cookieHeader }
|
|
347
393
|
});
|
|
348
394
|
return res.json();
|
|
349
395
|
}
|
|
@@ -371,7 +417,7 @@ export class APIHandler {
|
|
|
371
417
|
];
|
|
372
418
|
}
|
|
373
419
|
const res = await fetch(`${this.baseURL}/storefront/api/v1/currencies`, {
|
|
374
|
-
headers: { Cookie: this.
|
|
420
|
+
headers: { Cookie: this.cookieHeader }
|
|
375
421
|
});
|
|
376
422
|
return res.json();
|
|
377
423
|
}
|
|
@@ -379,7 +425,7 @@ export class APIHandler {
|
|
|
379
425
|
if (this.useMock) {
|
|
380
426
|
return { code: 0, message: 'success' };
|
|
381
427
|
}
|
|
382
|
-
const res = await fetch(`${this.baseURL}/updatecurrency?currency_code=${encodeURIComponent(currencyCode)}`, { method: 'PUT', headers: { Cookie: this.
|
|
428
|
+
const res = await fetch(`${this.baseURL}/updatecurrency?currency_code=${encodeURIComponent(currencyCode)}`, { method: 'PUT', headers: { Cookie: this.cookieHeader } });
|
|
383
429
|
return res.json();
|
|
384
430
|
}
|
|
385
431
|
async getPortalUser() {
|
|
@@ -387,7 +433,7 @@ export class APIHandler {
|
|
|
387
433
|
return { current_user: null };
|
|
388
434
|
}
|
|
389
435
|
const res = await fetch(`${this.baseURL}/portal/user`, {
|
|
390
|
-
headers: { Cookie: this.
|
|
436
|
+
headers: { Cookie: this.cookieHeader }
|
|
391
437
|
});
|
|
392
438
|
return res.json();
|
|
393
439
|
}
|
|
@@ -395,7 +441,7 @@ export class APIHandler {
|
|
|
395
441
|
if (this.useMock) {
|
|
396
442
|
return { variants: {} };
|
|
397
443
|
}
|
|
398
|
-
const res = await fetch(`${this.baseURL}/wishlists/wishlist-enabled?variant_ids=${encodeURIComponent(variantIds)}`, { headers: { Cookie: this.
|
|
444
|
+
const res = await fetch(`${this.baseURL}/wishlists/wishlist-enabled?variant_ids=${encodeURIComponent(variantIds)}`, { headers: { Cookie: this.cookieHeader } });
|
|
399
445
|
return res.json();
|
|
400
446
|
}
|
|
401
447
|
async wishlistAction(method, query, body) {
|
|
@@ -415,7 +461,7 @@ export class APIHandler {
|
|
|
415
461
|
}
|
|
416
462
|
const res = await fetch(url, {
|
|
417
463
|
method,
|
|
418
|
-
headers: { Cookie: this.
|
|
464
|
+
headers: { Cookie: this.cookieHeader },
|
|
419
465
|
body: fetchBody
|
|
420
466
|
});
|
|
421
467
|
return res.json();
|
|
@@ -425,7 +471,7 @@ export class APIHandler {
|
|
|
425
471
|
return { code: 0, quote: null };
|
|
426
472
|
}
|
|
427
473
|
const res = await fetch(`${this.baseURL}/storefront/api/v1/quote`, {
|
|
428
|
-
headers: { Cookie: this.
|
|
474
|
+
headers: { Cookie: this.cookieHeader }
|
|
429
475
|
});
|
|
430
476
|
return res.json();
|
|
431
477
|
}
|
|
@@ -434,18 +480,28 @@ export class APIHandler {
|
|
|
434
480
|
return { recommendations: [], sections: {} };
|
|
435
481
|
}
|
|
436
482
|
const res = await fetch(`${this.baseURL}/products/${productId}/recommendations`, {
|
|
437
|
-
headers: { Cookie: this.
|
|
483
|
+
headers: { Cookie: this.cookieHeader }
|
|
438
484
|
});
|
|
439
485
|
return res.json();
|
|
440
486
|
}
|
|
441
|
-
async getProductReviews(productId) {
|
|
487
|
+
async getProductReviews(productId, query = '') {
|
|
442
488
|
if (this.useMock) {
|
|
443
|
-
return {
|
|
489
|
+
return { ...productReviews };
|
|
444
490
|
}
|
|
445
|
-
const
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
491
|
+
const response = await this.#proxyRequest('GET', `/products/${encodeURIComponent(productId)}/reviews`, null, query);
|
|
492
|
+
return rewriteStoreUrls(response, this.baseURL ?? '');
|
|
493
|
+
}
|
|
494
|
+
async submitProductReview(method, productId, data) {
|
|
495
|
+
if (this.useMock) {
|
|
496
|
+
return { code: 0, message: 'Review submitted' };
|
|
497
|
+
}
|
|
498
|
+
return this.#proxyRequest(method, `/products/${encodeURIComponent(productId)}/reviews`, data);
|
|
499
|
+
}
|
|
500
|
+
async getReviewForEdit(productId) {
|
|
501
|
+
if (this.useMock) {
|
|
502
|
+
return { code: 0, rating: 0, title: '', content: '' };
|
|
503
|
+
}
|
|
504
|
+
return this.#proxyRequest('GET', `/editreview/${encodeURIComponent(productId)}`);
|
|
449
505
|
}
|
|
450
506
|
async bulkAddToCart(data) {
|
|
451
507
|
if (this.useMock) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ErrorTraceType } from 'types/common';
|
|
2
|
-
import type
|
|
2
|
+
import { type ThemeValidationResult, type ValidationIssue } from './theme-validation.js';
|
|
3
3
|
export type DevErrorKind = 'compilation' | 'validation' | 'runtime';
|
|
4
4
|
export type DevErrorReport = {
|
|
5
5
|
kind: DevErrorKind;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-report.d.ts","sourceRoot":"","sources":["../../src/utils/error-report.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"error-report.d.ts","sourceRoot":"","sources":["../../src/utils/error-report.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EACL,KAAK,qBAAqB,EAC1B,KAAK,eAAe,EAGrB,MAAM,uBAAuB,CAAC;AAE/B,MAAM,MAAM,YAAY,GAAG,aAAa,GAAG,YAAY,GAAG,SAAS,CAAC;AAEpE,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,YAAY,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnE,MAAM,CAAC,EAAE,eAAe,EAAE,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AA0EF,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,qBAAqB,EAC7B,GAAG,SAAgB,GAClB,MAAM,CA4CR;AAED,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,cAAc,EACrB,eAAe,EAAE,MAAM,EACvB,GAAG,SAAgB,GAClB,MAAM,CAwCR;AAED,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,MAAM,EACf,GAAG,SAAgB,GAClB,MAAM,CAeR;AA8CD,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,qBAAqB,EAC7B,GAAG,SAAgB,GAClB,cAAc,CAchB;AAED,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,MAAM,EACf,GAAG,SAAgB,GAClB,cAAc,CAOhB;AAID,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,cAAc,GACrB,OAAO,CAAC,MAAM,CAAC,CAiBjB"}
|
|
@@ -3,10 +3,14 @@ import path from 'path';
|
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
import { Liquid } from 'liquidjs';
|
|
5
5
|
import { escapeExpression } from './escape-expression.js';
|
|
6
|
+
import { validationErrors } from './theme-validation.js';
|
|
6
7
|
const CATEGORY_LABELS = {
|
|
7
8
|
translation: 'Missing translation keys',
|
|
8
9
|
json: 'Invalid JSON files',
|
|
9
|
-
schema: 'Invalid section/block
|
|
10
|
+
schema: 'Invalid section/block schemas',
|
|
11
|
+
page: 'Invalid page or section-group JSON',
|
|
12
|
+
liquid: 'Liquid references the engine cannot render',
|
|
13
|
+
theme_settings: 'Invalid theme settings',
|
|
10
14
|
asset_size: 'Oversized assets',
|
|
11
15
|
required_file: 'Missing required files'
|
|
12
16
|
};
|
|
@@ -25,7 +29,10 @@ const FIX_HINTS = {
|
|
|
25
29
|
'Resolve each listed issue before previewing the theme.',
|
|
26
30
|
'Required files: `layouts/theme.liquid`, `i18n/en.json`, `i18n/en.schema.json`.',
|
|
27
31
|
'Every `"t:..."` key used in the theme must exist in `i18n/en.schema.json` or `i18n/en.json` depending on where the key is used.',
|
|
28
|
-
'
|
|
32
|
+
'Required files also include `settings/schema.json` and `settings/theme.json`.',
|
|
33
|
+
'Section and block schemas must use engine setting types. select, radio, url, menu, collection, category, product, blog, image_picker, and media_picker need a structured `default`.',
|
|
34
|
+
'Page JSON `order` entries must exist in `sections`, each section `type` must have a liquid file, and every `block_order` entry must exist and be allowed by that schema.',
|
|
35
|
+
'Do not use `{% paginate %}`, `{% form %}`, `{% stylesheet %}`, or `{% include %}`. Snippet, section, and section-group names must match real files.',
|
|
29
36
|
'Files in `assets/` must be under 1 MB each.'
|
|
30
37
|
],
|
|
31
38
|
runtime: [
|
|
@@ -40,19 +47,39 @@ function encodeMarkdown(markdown) {
|
|
|
40
47
|
function buildFixSection(kind) {
|
|
41
48
|
return FIX_HINTS[kind].map((hint) => `- ${hint}`).join('\n');
|
|
42
49
|
}
|
|
50
|
+
/** Groups identical problems (ignoring array indices) so each is stated once. */
|
|
51
|
+
function groupByMessage(issues) {
|
|
52
|
+
const groups = new Map();
|
|
53
|
+
for (const issue of issues) {
|
|
54
|
+
const key = issue.message.replace(/\[\d+\]/g, '[]');
|
|
55
|
+
const where = issue.message.match(/^(\S*\[\d+\]\S*)\s/)?.[1];
|
|
56
|
+
const group = groups.get(key) ?? { message: key, entries: [] };
|
|
57
|
+
if (!group.entries.some((e) => e.file === issue.file && e.where === where)) {
|
|
58
|
+
group.entries.push({ file: issue.file, where });
|
|
59
|
+
}
|
|
60
|
+
groups.set(key, group);
|
|
61
|
+
}
|
|
62
|
+
return [...groups.values()];
|
|
63
|
+
}
|
|
43
64
|
export function buildValidationMarkdown(result, cwd = process.cwd()) {
|
|
65
|
+
const blockingIssues = validationErrors(result.issues);
|
|
44
66
|
const grouped = new Map();
|
|
45
|
-
for (const issue of
|
|
67
|
+
for (const issue of blockingIssues) {
|
|
46
68
|
const list = grouped.get(issue.category) ?? [];
|
|
47
69
|
list.push(issue);
|
|
48
70
|
grouped.set(issue.category, list);
|
|
49
71
|
}
|
|
50
72
|
const issueSections = [];
|
|
51
73
|
for (const [category, issues] of grouped) {
|
|
52
|
-
issueSections.push(`### ${CATEGORY_LABELS[category]}`);
|
|
53
|
-
for (const
|
|
54
|
-
|
|
55
|
-
|
|
74
|
+
issueSections.push(`### ${CATEGORY_LABELS[category]} (${issues.length})`);
|
|
75
|
+
for (const group of groupByMessage(issues)) {
|
|
76
|
+
issueSections.push(`- **${group.message}** — ${group.entries.length} file${group.entries.length === 1 ? '' : 's'}`);
|
|
77
|
+
for (const entry of group.entries) {
|
|
78
|
+
if (entry.file) {
|
|
79
|
+
const where = entry.where ? ` (\`${entry.where}\`)` : '';
|
|
80
|
+
issueSections.push(` - \`${entry.file}\`${where}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
56
83
|
}
|
|
57
84
|
issueSections.push('');
|
|
58
85
|
}
|
|
@@ -60,7 +87,7 @@ export function buildValidationMarkdown(result, cwd = process.cwd()) {
|
|
|
60
87
|
'# Zoho Commerce Theme CLI — Validation error',
|
|
61
88
|
'',
|
|
62
89
|
'## Summary',
|
|
63
|
-
`Theme validation failed with **${
|
|
90
|
+
`Theme validation failed with **${blockingIssues.length}** issue(s) in \`${cwd}\`.`,
|
|
64
91
|
'',
|
|
65
92
|
'## Issues',
|
|
66
93
|
...issueSections,
|
|
@@ -118,26 +145,39 @@ function groupValidationIssues(issues) {
|
|
|
118
145
|
}
|
|
119
146
|
const htmlParts = [];
|
|
120
147
|
for (const [category, categoryIssues] of grouped) {
|
|
121
|
-
htmlParts.push(`<h3>${CATEGORY_LABELS[category]}</h3
|
|
122
|
-
for (const
|
|
123
|
-
const
|
|
124
|
-
|
|
148
|
+
htmlParts.push(`<h3>${CATEGORY_LABELS[category]} <span class="issue-count">${categoryIssues.length}</span></h3>`);
|
|
149
|
+
for (const group of groupByMessage(categoryIssues)) {
|
|
150
|
+
const files = group.entries.filter((entry) => entry.file);
|
|
151
|
+
const count = files.length > 0
|
|
152
|
+
? `<span class="issue-files-count">${files.length} file${files.length === 1 ? '' : 's'}</span>`
|
|
125
153
|
: '';
|
|
126
|
-
htmlParts.push(`<
|
|
154
|
+
htmlParts.push(`<div class="issue-group"><div class="issue-message">${escapeExpression(group.message)}${count}</div>`);
|
|
155
|
+
if (files.length > 0) {
|
|
156
|
+
htmlParts.push('<ul class="issue-files">');
|
|
157
|
+
for (const entry of files) {
|
|
158
|
+
const file = escapeExpression(entry.file ?? '');
|
|
159
|
+
const where = entry.where
|
|
160
|
+
? ` <span class="issue-where">${escapeExpression(entry.where)}</span>`
|
|
161
|
+
: '';
|
|
162
|
+
htmlParts.push(`<li><a href="#" data-file="${file}" class="open-file-link">${file}</a>${where}</li>`);
|
|
163
|
+
}
|
|
164
|
+
htmlParts.push('</ul>');
|
|
165
|
+
}
|
|
166
|
+
htmlParts.push('</div>');
|
|
127
167
|
}
|
|
128
|
-
htmlParts.push('</ul>');
|
|
129
168
|
}
|
|
130
169
|
return htmlParts.join('\n');
|
|
131
170
|
}
|
|
132
171
|
export function buildValidationErrorReport(result, cwd = process.cwd()) {
|
|
172
|
+
const blockingIssues = validationErrors(result.issues);
|
|
133
173
|
return {
|
|
134
174
|
kind: 'validation',
|
|
135
|
-
title:
|
|
175
|
+
title: blockingIssues.length === 1
|
|
136
176
|
? '1 validation issue found'
|
|
137
|
-
: `${
|
|
177
|
+
: `${blockingIssues.length} validation issues found`,
|
|
138
178
|
summary: 'Resolve the issues below to continue previewing this theme. The development server is running, but pages are blocked until validation passes.',
|
|
139
|
-
issues:
|
|
140
|
-
context: groupValidationIssues(
|
|
179
|
+
issues: blockingIssues,
|
|
180
|
+
context: groupValidationIssues(blockingIssues),
|
|
141
181
|
markdown: buildValidationMarkdown(result, cwd)
|
|
142
182
|
};
|
|
143
183
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export declare function loadThemeGlobals(): Record<string, unknown>;
|
|
1
|
+
export declare function loadThemeGlobals(live?: boolean): Record<string, unknown>;
|
|
2
2
|
export declare function mergePageData(globals: Record<string, unknown>, pageData: Record<string, unknown>): Record<string, unknown>;
|
|
3
3
|
//# sourceMappingURL=load-theme-globals.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"load-theme-globals.d.ts","sourceRoot":"","sources":["../../src/utils/load-theme-globals.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"load-theme-globals.d.ts","sourceRoot":"","sources":["../../src/utils/load-theme-globals.ts"],"names":[],"mappings":"AAwBA,wBAAgB,gBAAgB,CAAC,IAAI,UAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAwCtE;AAqBD,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAkBzB"}
|
|
@@ -1,8 +1,33 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import globalObjectsMock from '../mocks/global-objects.js';
|
|
4
|
-
|
|
4
|
+
// Sample data in the mock globals that must never leak into a live-store preview.
|
|
5
|
+
const MOCK_DATA_KEYS = ['products', 'collections', 'menu', 'seo', 'page_title'];
|
|
6
|
+
// Feature flags default to "on" when previewing a live store, so UI such as the
|
|
7
|
+
// menu, sorting and reviews is not hidden just because the response omitted a flag.
|
|
8
|
+
const LIVE_PREFERENCE_DEFAULTS = {
|
|
9
|
+
is_menu_enabled: true,
|
|
10
|
+
is_sorting_enabled: true,
|
|
11
|
+
is_product_filters_enabled: true,
|
|
12
|
+
is_product_reviews_enabled: true,
|
|
13
|
+
search_box_enabled: true,
|
|
14
|
+
is_footer_enabled: true
|
|
15
|
+
};
|
|
16
|
+
export function loadThemeGlobals(live = false) {
|
|
5
17
|
let globals = { ...globalObjectsMock };
|
|
18
|
+
if (live) {
|
|
19
|
+
for (const key of MOCK_DATA_KEYS) {
|
|
20
|
+
delete globals[key];
|
|
21
|
+
}
|
|
22
|
+
const mockStore = (globals.store ?? {});
|
|
23
|
+
globals.store = {
|
|
24
|
+
...mockStore,
|
|
25
|
+
preferences: {
|
|
26
|
+
...(mockStore.preferences ?? {}),
|
|
27
|
+
...LIVE_PREFERENCE_DEFAULTS
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
6
31
|
try {
|
|
7
32
|
const themeSettingsPath = path.join(process.cwd(), 'settings', 'theme.json');
|
|
8
33
|
const themeSettingsFile = JSON.parse(fs.readFileSync(themeSettingsPath, 'utf-8'));
|
|
@@ -20,13 +45,28 @@ export function loadThemeGlobals() {
|
|
|
20
45
|
}
|
|
21
46
|
return globals;
|
|
22
47
|
}
|
|
48
|
+
function mergeStore(globals, pageData) {
|
|
49
|
+
const liveStore = pageData.store;
|
|
50
|
+
if (!liveStore || typeof liveStore !== 'object') {
|
|
51
|
+
return globals.store;
|
|
52
|
+
}
|
|
53
|
+
const baseStore = (globals.store ?? {});
|
|
54
|
+
return {
|
|
55
|
+
...baseStore,
|
|
56
|
+
...liveStore,
|
|
57
|
+
preferences: {
|
|
58
|
+
...(baseStore.preferences ?? {}),
|
|
59
|
+
...(liveStore.preferences ?? {})
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
23
63
|
export function mergePageData(globals, pageData) {
|
|
24
64
|
return {
|
|
25
65
|
...globals,
|
|
26
66
|
...pageData,
|
|
27
67
|
routes: pageData.routes ?? globals.routes,
|
|
28
68
|
settings: globals.settings ?? pageData.settings,
|
|
29
|
-
store:
|
|
69
|
+
store: mergeStore(globals, pageData),
|
|
30
70
|
menu: pageData.menu ?? globals.menu,
|
|
31
71
|
analytics: pageData.analytics ?? globals.analytics,
|
|
32
72
|
content_for_header: pageData.content_for_header ?? globals.content_for_header ?? '',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve-schema-settings.d.ts","sourceRoot":"","sources":["../../src/utils/resolve-schema-settings.ts"],"names":[],"mappings":"AAiBA,KAAK,aAAa,GAAG;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAqEF,wBAAgB,0BAA0B,IAAI,IAAI,CAEjD;
|
|
1
|
+
{"version":3,"file":"resolve-schema-settings.d.ts","sourceRoot":"","sources":["../../src/utils/resolve-schema-settings.ts"],"names":[],"mappings":"AAiBA,KAAK,aAAa,GAAG;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAqEF,wBAAgB,0BAA0B,IAAI,IAAI,CAEjD;AA+MD,wBAAsB,qBAAqB,CACzC,cAAc,EAAE,aAAa,EAAE,EAC/B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,GAAE,CAAC,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,IAAW,GACzE,OAAO,CAAC,IAAI,CAAC,CAIf"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export declare function invalidateSchemaLocaleCache(): void;
|
|
1
2
|
export declare function resolveSchemaTranslations<T>(schema: T, cwd: string): T;
|
|
2
3
|
export declare function resolveSchemaName(name: string | undefined, fallback: string, cwd: string): string;
|
|
3
4
|
//# sourceMappingURL=resolve-schema-translations.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve-schema-translations.d.ts","sourceRoot":"","sources":["../../src/utils/resolve-schema-translations.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"resolve-schema-translations.d.ts","sourceRoot":"","sources":["../../src/utils/resolve-schema-translations.ts"],"names":[],"mappings":"AAKA,wBAAgB,2BAA2B,IAAI,IAAI,CAElD;AA4CD,wBAAgB,yBAAyB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,CAItE;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAMjG"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema checks aligned with storefront_template_engine SchemaValidator
|
|
3
|
+
* on patch_storefront_logging_and_error_handling. color_scheme is included
|
|
4
|
+
* because SchemaSettingsUtil resolves it. Findings the storefront still
|
|
5
|
+
* renders are warnings.
|
|
6
|
+
*/
|
|
7
|
+
export declare const MAX_BLOCK_NESTING_DEPTH = 5;
|
|
8
|
+
export declare const EDITABLE_PAGE_KEYS: readonly ["index", "cart", "product", "page", "blog", "blogs", "collection", "category", "checkout", "orders", "search", "404"];
|
|
9
|
+
export declare const SECTION_GROUP_KEYS: readonly ["header-group", "footer-group"];
|
|
10
|
+
/** Engine SchemaValidator.KNOWN_SETTING_TYPES plus color_scheme. */
|
|
11
|
+
export declare const SUPPORTED_SETTING_TYPES: Set<string>;
|
|
12
|
+
export type JsonRecord = Record<string, unknown>;
|
|
13
|
+
export declare function isRecord(value: unknown): value is JsonRecord;
|
|
14
|
+
export type SchemaFinding = {
|
|
15
|
+
message: string;
|
|
16
|
+
severity: 'error' | 'warning';
|
|
17
|
+
};
|
|
18
|
+
export declare function validateSchemaDocument(schema: JsonRecord, options?: {
|
|
19
|
+
requireAvailableIn?: boolean;
|
|
20
|
+
requirePresets?: boolean;
|
|
21
|
+
/** Severity for a missing available_in or a missing named preset. */
|
|
22
|
+
requirementSeverity?: 'error' | 'warning';
|
|
23
|
+
themeBlockSchemas?: Map<string, JsonRecord>;
|
|
24
|
+
}): SchemaFinding[];
|
|
25
|
+
export declare function isBlockTypeAllowed(containerSchema: JsonRecord | null, blockType: string): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Schema whose `blocks` allow a placed block's children.
|
|
28
|
+
* Theme-block files are resolved by the caller via `themeBlockSchemas`.
|
|
29
|
+
*/
|
|
30
|
+
export declare function resolveChildContainerSchema(containerSchema: JsonRecord | null, blockType: string, themeBlockSchemas?: Map<string, JsonRecord>): JsonRecord | null;
|
|
31
|
+
export declare function declaredThemeBlockNames(schema: JsonRecord): string[];
|
|
32
|
+
export declare function validateThemeSettingsGroups(groups: unknown): SchemaFinding[];
|
|
33
|
+
export declare function validateThemeSettingsData(data: unknown): SchemaFinding[];
|
|
34
|
+
//# sourceMappingURL=schema-validation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-validation.d.ts","sourceRoot":"","sources":["../../src/utils/schema-validation.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,eAAO,MAAM,uBAAuB,IAAI,CAAC;AAEzC,eAAO,MAAM,kBAAkB,iIAarB,CAAC;AAEX,eAAO,MAAM,kBAAkB,2CAA4C,CAAC;AAO5E,oEAAoE;AACpE,eAAO,MAAM,uBAAuB,aAqBlC,CAAC;AAiBH,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEjD,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,UAAU,CAE5D;AAUD,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;CAC/B,CAAC;AAUF,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,UAAU,EAClB,OAAO,GAAE;IACP,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,qEAAqE;IACrE,mBAAmB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC1C,iBAAiB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;CACxC,GACL,aAAa,EAAE,CAsCjB;AAogBD,wBAAgB,kBAAkB,CAChC,eAAe,EAAE,UAAU,GAAG,IAAI,EAClC,SAAS,EAAE,MAAM,GAChB,OAAO,CAoCT;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CACzC,eAAe,EAAE,UAAU,GAAG,IAAI,EAClC,SAAS,EAAE,MAAM,EACjB,iBAAiB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,GAC1C,UAAU,GAAG,IAAI,CAoBnB;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,EAAE,CAgBpE;AAED,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,OAAO,GAAG,aAAa,EAAE,CA2B5E;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,OAAO,GAAG,aAAa,EAAE,CA4CxE"}
|