@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.
Files changed (48) hide show
  1. package/VALIDATIONS.md +122 -0
  2. package/package.json +2 -2
  3. package/public/dev-error.liquid +49 -0
  4. package/ts-dist/commands/build-theme.d.ts.map +1 -1
  5. package/ts-dist/commands/build-theme.js +38 -11
  6. package/ts-dist/commands/serve-theme.d.ts.map +1 -1
  7. package/ts-dist/commands/serve-theme.js +213 -49
  8. package/ts-dist/index.js +0 -0
  9. package/ts-dist/mocks/global-objects.d.ts.map +1 -1
  10. package/ts-dist/mocks/global-objects.js +1 -0
  11. package/ts-dist/mocks/init-theme/i18n/en.schema.json +2 -1
  12. package/ts-dist/mocks/init-theme/sections/footer.liquid +3 -1
  13. package/ts-dist/mocks/init-theme/sections/header-topbar.liquid +3 -1
  14. package/ts-dist/mocks/init-theme/sections/product-collection.liquid +3 -1
  15. package/ts-dist/mocks/init-theme/sections/promo-banner.liquid +5 -1
  16. package/ts-dist/mocks/reviews.d.ts +6 -0
  17. package/ts-dist/mocks/reviews.d.ts.map +1 -0
  18. package/ts-dist/mocks/reviews.js +52 -0
  19. package/ts-dist/routes/storefront-routes.js +1 -1
  20. package/ts-dist/utils/api-handler.d.ts +6 -1
  21. package/ts-dist/utils/api-handler.d.ts.map +1 -1
  22. package/ts-dist/utils/api-handler.js +91 -35
  23. package/ts-dist/utils/error-report.d.ts +1 -1
  24. package/ts-dist/utils/error-report.d.ts.map +1 -1
  25. package/ts-dist/utils/error-report.js +58 -18
  26. package/ts-dist/utils/load-theme-globals.d.ts +1 -1
  27. package/ts-dist/utils/load-theme-globals.d.ts.map +1 -1
  28. package/ts-dist/utils/load-theme-globals.js +42 -2
  29. package/ts-dist/utils/resolve-schema-settings.d.ts.map +1 -1
  30. package/ts-dist/utils/resolve-schema-settings.js +0 -1
  31. package/ts-dist/utils/resolve-schema-translations.d.ts +1 -0
  32. package/ts-dist/utils/resolve-schema-translations.d.ts.map +1 -1
  33. package/ts-dist/utils/resolve-schema-translations.js +3 -0
  34. package/ts-dist/utils/schema-validation.d.ts +34 -0
  35. package/ts-dist/utils/schema-validation.d.ts.map +1 -0
  36. package/ts-dist/utils/schema-validation.js +544 -0
  37. package/ts-dist/utils/serve-theme-utils.d.ts +1 -1
  38. package/ts-dist/utils/serve-theme-utils.d.ts.map +1 -1
  39. package/ts-dist/utils/serve-theme-utils.js +1 -1
  40. package/ts-dist/utils/template-reference-validation.d.ts +11 -0
  41. package/ts-dist/utils/template-reference-validation.d.ts.map +1 -0
  42. package/ts-dist/utils/template-reference-validation.js +466 -0
  43. package/ts-dist/utils/theme-block-schema.d.ts +5 -0
  44. package/ts-dist/utils/theme-block-schema.d.ts.map +1 -0
  45. package/ts-dist/utils/theme-block-schema.js +35 -0
  46. package/ts-dist/utils/theme-validation.d.ts +8 -1
  47. package/ts-dist/utils/theme-validation.d.ts.map +1 -1
  48. 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":"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"}
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
- const setCookieHeader = response.headers.get('set-cookie');
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 response = await fetch(`${this.baseURL}${requestUrl}?format=json`, {
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.cookies.join(';')
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
- const product = products.find((item) => item.handle === productHandle || item.id === productHandle);
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
- throw new Error(`Failed to fetch data from ${requestUrl} (${response.status})`);
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
- const json = (await response.json());
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.cookies.join(';')
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
- const setCookieHeader = result.headers.get('set-cookie');
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.cookies.join(';') };
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
- const setCookieHeader = res.headers.get('set-cookie');
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.cookies.join(';') }
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.cookies.join(';') }
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.cookies.join(';') } });
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.cookies.join(';') }
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.cookies.join(';') } });
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.cookies.join(';') },
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.cookies.join(';') }
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.cookies.join(';') }
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 { reviews: [], page_context: { total_count: 0 } };
489
+ return { ...productReviews };
444
490
  }
445
- const res = await fetch(`${this.baseURL}/products/${productId}/reviews`, {
446
- headers: { Cookie: this.cookies.join(';') }
447
- });
448
- return res.json();
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 { ThemeValidationResult, ValidationIssue } from './theme-validation.js';
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,EACV,qBAAqB,EACrB,eAAe,EAEhB,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;AA4CF,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,qBAAqB,EAC7B,GAAG,SAAgB,GAClB,MAAM,CAoCR;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;AA2BD,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,qBAAqB,EAC7B,GAAG,SAAgB,GAClB,cAAc,CAahB;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"}
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 schema settings',
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
- 'Settings of type select, collection, category, menu, or blog need a valid `default` object.',
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 result.issues) {
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 issue of issues) {
54
- const location = issue.file ? ` — \`${issue.file}\`` : '';
55
- issueSections.push(`- ${issue.message}${location}`);
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 **${result.issues.length}** issue(s) in \`${cwd}\`.`,
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><ul>`);
122
- for (const issue of categoryIssues) {
123
- const fileLink = issue.file
124
- ? ` <a href="#" data-file="${escapeExpression(issue.file)}" class="open-file-link">${escapeExpression(issue.file)}</a>`
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(`<li>${escapeExpression(issue.message)}${fileLink}</li>`);
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: result.issues.length === 1
175
+ title: blockingIssues.length === 1
136
176
  ? '1 validation issue found'
137
- : `${result.issues.length} validation issues found`,
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: result.issues,
140
- context: groupValidationIssues(result.issues),
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":"AAUA,wBAAgB,gBAAgB,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA0B1D;AAED,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
+ {"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
- export function loadThemeGlobals() {
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: pageData.store ?? globals.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;AAgND,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
+ {"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"}
@@ -211,7 +211,6 @@ async function resolveSettingByType(schemaSetting, settings, data, fetcher) {
211
211
  break;
212
212
  case 'url':
213
213
  case 'select':
214
- case 'preview_layout':
215
214
  case 'radio':
216
215
  settings[id] = parseJsonSetting(raw);
217
216
  break;
@@ -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":"AA+CA,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"}
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"}
@@ -1,6 +1,9 @@
1
1
  import path from 'path';
2
2
  import fs from 'fs-extra';
3
3
  const localeCache = new Map();
4
+ export function invalidateSchemaLocaleCache() {
5
+ localeCache.clear();
6
+ }
4
7
  function loadSchemaLocale(cwd) {
5
8
  if (localeCache.has(cwd))
6
9
  return localeCache.get(cwd);
@@ -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"}