@zohofinance/commerce 2.0.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 +414 -70
- 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/editor-attributes.d.ts.map +1 -1
- package/ts-dist/utils/editor-attributes.js +2 -4
- 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
package/ts-dist/index.js
CHANGED
|
File without changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"global-objects.d.ts","sourceRoot":"","sources":["../../src/mocks/global-objects.ts"],"names":[],"mappings":"AAAA,KAAK,aAAa,GAAG;IACnB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB,CAAC;AAEF,QAAA,MAAM,aAAa,EAAE,
|
|
1
|
+
{"version":3,"file":"global-objects.d.ts","sourceRoot":"","sources":["../../src/mocks/global-objects.ts"],"names":[],"mappings":"AAAA,KAAK,aAAa,GAAG;IACnB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB,CAAC;AAEF,QAAA,MAAM,aAAa,EAAE,aAy0fpB,CAAC;AAEF,eAAe,aAAa,CAAC"}
|
|
@@ -182,6 +182,7 @@ const globalObjects = {
|
|
|
182
182
|
show_zoho_powered_by: false,
|
|
183
183
|
is_product_filters_enabled: false,
|
|
184
184
|
is_sorting_enabled: false,
|
|
185
|
+
is_product_reviews_enabled: true,
|
|
185
186
|
search_box_enabled: false,
|
|
186
187
|
is_footer_enabled: false,
|
|
187
188
|
product_specifications_enabled: false,
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
{% schema %}
|
|
24
24
|
{
|
|
25
25
|
"name": "t:sections.header_topbar.name",
|
|
26
|
+
"available_in": { "templates": ["header-group"] },
|
|
26
27
|
"settings": [
|
|
27
28
|
{
|
|
28
29
|
"type": "select",
|
|
@@ -47,6 +48,7 @@
|
|
|
47
48
|
}
|
|
48
49
|
]
|
|
49
50
|
}
|
|
50
|
-
]
|
|
51
|
+
],
|
|
52
|
+
"presets": [{ "name": "t:sections.header_topbar.name" }]
|
|
51
53
|
}
|
|
52
54
|
{% endschema %}
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
{% schema %}
|
|
26
26
|
{
|
|
27
27
|
"name": "t:sections.product_collection.name",
|
|
28
|
+
"available_in": { "templates": ["index", "collection", "category"] },
|
|
28
29
|
"settings": [
|
|
29
30
|
{
|
|
30
31
|
"type": "collection",
|
|
@@ -38,6 +39,7 @@
|
|
|
38
39
|
"type": "zc_theme__section-heading",
|
|
39
40
|
"name": "Section Heading"
|
|
40
41
|
}
|
|
41
|
-
]
|
|
42
|
+
],
|
|
43
|
+
"presets": [{ "name": "t:sections.product_collection.name" }]
|
|
42
44
|
}
|
|
43
45
|
{% endschema %}
|
|
@@ -50,7 +50,11 @@
|
|
|
50
50
|
{
|
|
51
51
|
"type": "url",
|
|
52
52
|
"id": "cta_url",
|
|
53
|
-
"label": "t:sections.promo_banner.settings.cta_url.label"
|
|
53
|
+
"label": "t:sections.promo_banner.settings.cta_url.label",
|
|
54
|
+
"default": {
|
|
55
|
+
"display_name": "t:sections.promo_banner.settings.cta_url.default",
|
|
56
|
+
"handle": "collections/all-products"
|
|
57
|
+
}
|
|
54
58
|
}
|
|
55
59
|
],
|
|
56
60
|
"blocks": [
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reviews.d.ts","sourceRoot":"","sources":["../../src/mocks/reviews.ts"],"names":[],"mappings":"AAAA,KAAK,cAAc,GAAG;IACpB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB,CAAC;AAIF,QAAA,MAAM,OAAO,EAAE,cAkDd,CAAC;AAEF,eAAe,OAAO,CAAC"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// Mirrors the live /products/:product_id/reviews response so a theme renders the
|
|
2
|
+
// same markup with or without --storeUrl.
|
|
3
|
+
const reviews = {
|
|
4
|
+
is_reviewed: false,
|
|
5
|
+
allow_registered_only: false,
|
|
6
|
+
is_logged_in: false,
|
|
7
|
+
is_review_enabled: true,
|
|
8
|
+
reviews: [
|
|
9
|
+
{
|
|
10
|
+
id: '8858475000000094101',
|
|
11
|
+
user_name: 'Arun Kumar',
|
|
12
|
+
rating: 5,
|
|
13
|
+
title: 'Excellent build quality',
|
|
14
|
+
content: 'The product arrived well packed and works exactly as described. Battery life is great and the finish feels premium.',
|
|
15
|
+
last_modified_time: '14 June 2026'
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
id: '8858475000000094102',
|
|
19
|
+
user_name: 'Divya Raman',
|
|
20
|
+
rating: 4,
|
|
21
|
+
title: 'Good value for the price',
|
|
22
|
+
content: 'Happy with the purchase overall. Setup was simple and the sound quality is clear, though the case scratches easily.',
|
|
23
|
+
last_modified_time: '02 June 2026'
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
user_review: null,
|
|
27
|
+
is_captcha_enabled: false,
|
|
28
|
+
average_rating: 4.5,
|
|
29
|
+
rating_stats: {
|
|
30
|
+
star_5_percentage: 50,
|
|
31
|
+
star_5_count: 1,
|
|
32
|
+
overall_average: 4.5,
|
|
33
|
+
star_4_percentage: 50,
|
|
34
|
+
star_4_count: 1,
|
|
35
|
+
star_3_percentage: 0,
|
|
36
|
+
star_3_count: 0,
|
|
37
|
+
star_2_percentage: 0,
|
|
38
|
+
star_2_count: 0,
|
|
39
|
+
star_1_percentage: 0,
|
|
40
|
+
star_1_count: 0,
|
|
41
|
+
total_reviews: 2
|
|
42
|
+
},
|
|
43
|
+
page_context: {
|
|
44
|
+
per_page: 10,
|
|
45
|
+
items_count: '2',
|
|
46
|
+
total_pages: 1,
|
|
47
|
+
current_page: 1,
|
|
48
|
+
page_param: 'page'
|
|
49
|
+
},
|
|
50
|
+
is_purchased: false
|
|
51
|
+
};
|
|
52
|
+
export default reviews;
|
|
@@ -126,7 +126,7 @@ export const STOREFRONT_ROUTES = [
|
|
|
126
126
|
}
|
|
127
127
|
];
|
|
128
128
|
export function resolveApiPath(route, params) {
|
|
129
|
-
return route.apiPath.replace(/:([a-z_]+)/gi, (_, key) => params[key] ?? '');
|
|
129
|
+
return route.apiPath.replace(/:([a-z_]+)/gi, (_, key) => encodeURIComponent(params[key] ?? ''));
|
|
130
130
|
}
|
|
131
131
|
export function pageTemplateExists(pageTemplate) {
|
|
132
132
|
return fs.existsSync(path.join(process.cwd(), 'pages', `${pageTemplate}.json`));
|
|
@@ -13,6 +13,7 @@ export declare class APIHandler {
|
|
|
13
13
|
useMock: boolean;
|
|
14
14
|
baseURL?: string;
|
|
15
15
|
}): Promise<APIHandler>;
|
|
16
|
+
get cookieHeader(): string;
|
|
16
17
|
fetch(requestUrl: string): Promise<Record<string, unknown>>;
|
|
17
18
|
getMockCartData(): Record<string, unknown>;
|
|
18
19
|
getMockSaveForLaterData(): Record<string, unknown>[];
|
|
@@ -27,7 +28,11 @@ export declare class APIHandler {
|
|
|
27
28
|
wishlistAction(method: 'POST' | 'DELETE', query: string, body?: Record<string, unknown>): Promise<any>;
|
|
28
29
|
getQuote(): Promise<any>;
|
|
29
30
|
getProductRecommendations(productId: string): Promise<any>;
|
|
30
|
-
getProductReviews(productId: string): Promise<
|
|
31
|
+
getProductReviews(productId: string, query?: string): Promise<{
|
|
32
|
+
[key: string]: any;
|
|
33
|
+
}>;
|
|
34
|
+
submitProductReview(method: 'POST' | 'PUT', productId: string, data: Record<string, unknown>): Promise<unknown>;
|
|
35
|
+
getReviewForEdit(productId: string): Promise<unknown>;
|
|
31
36
|
bulkAddToCart(data: Record<string, unknown>): Promise<any>;
|
|
32
37
|
saveForLater(method: 'PUT' | 'DELETE', query: string): Promise<unknown>;
|
|
33
38
|
moveToCart(query: string): Promise<unknown>;
|
|
@@ -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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"editor-attributes.d.ts","sourceRoot":"","sources":["../../src/utils/editor-attributes.ts"],"names":[],"mappings":"AAAA,wBAAgB,yBAAyB,CACvC,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE,MAAM,GACnB,MAAM,
|
|
1
|
+
{"version":3,"file":"editor-attributes.d.ts","sourceRoot":"","sources":["../../src/utils/editor-attributes.ts"],"names":[],"mappings":"AAAA,wBAAgB,yBAAyB,CACvC,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE,MAAM,GACnB,MAAM,CASR;AAED,wBAAgB,6BAA6B,CAC3C,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE,MAAM,GACnB,MAAM,CAWR"}
|
|
@@ -2,10 +2,9 @@ export function buildSectionComponentAttr(sectionType, sectionKey, jsonType, dis
|
|
|
2
2
|
return JSON.stringify({
|
|
3
3
|
id: `section_${sectionKey}`,
|
|
4
4
|
'data-zcommerce-section-id': '',
|
|
5
|
-
'data-zcommerce-component-name': sectionType,
|
|
5
|
+
'data-zcommerce-component-name': displayName || sectionType,
|
|
6
6
|
'data-zcommerce-component-key': `section_${sectionKey}`,
|
|
7
7
|
'data-zcommerce-json-type': jsonType,
|
|
8
|
-
'data-zcommerce-display-name': displayName || sectionType,
|
|
9
8
|
settingid: `setting_section_${sectionType}`
|
|
10
9
|
});
|
|
11
10
|
}
|
|
@@ -14,10 +13,9 @@ export function buildBlockZcommerceAttributes(blockType, blockKey, sectionKey, j
|
|
|
14
13
|
const attrJson = JSON.stringify({
|
|
15
14
|
id,
|
|
16
15
|
'data-zcommerce-section-id': `section_${sectionKey}`,
|
|
17
|
-
'data-zcommerce-component-name': blockType,
|
|
16
|
+
'data-zcommerce-component-name': displayName || blockType,
|
|
18
17
|
'data-zcommerce-component-key': id,
|
|
19
18
|
'data-zcommerce-json-type': jsonType,
|
|
20
|
-
'data-zcommerce-display-name': displayName || blockType,
|
|
21
19
|
settingid: `setting_block_${blockType}`
|
|
22
20
|
}).replace(/'/g, ''');
|
|
23
21
|
return `data-zcommerce-component='${attrJson}'`;
|
|
@@ -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
|