@zohofinance/commerce 2.2.1 → 2.2.2

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 CHANGED
@@ -11,7 +11,7 @@ Findings are printed in groups: missing translation keys, invalid JSON, section
11
11
  | Error | Preview is replaced with a validation page. The dev server stays up and rechecks when files change. | The errors are printed, the command throws, and no zip is written. |
12
12
  | Warning | Printed. Preview continues. | Printed. The zip is still written. |
13
13
 
14
- A section file that is missing `available_in` or a named preset is the one check whose severity depends on the command. It is a warning during `theme dev` and an error during `theme build`.
14
+ A section that declares `available_in` but has no named preset is the one check whose severity depends on the command. It is a warning during `theme dev` and an error during `theme build`.
15
15
 
16
16
  Everything else below is an error unless it is marked as a warning.
17
17
 
@@ -43,22 +43,24 @@ Checked on the `{% schema %}` block in `sections/*.liquid` and `blocks/*.liquid`
43
43
 
44
44
  - The schema must be valid JSON.
45
45
  - A block file with no schema is a **warning**.
46
- - A section file with no schema is treated as missing both `available_in` and a named preset.
46
+ - A section file with no schema is allowed at file level. If that section is placed in a page or section-group JSON, the placement check below still fails because `available_in` cannot list the page.
47
47
  - `name` is required and must be a non-empty string. `tag` and `class`, when set, must be strings.
48
48
  - `max_blocks`, when set, must be a positive integer.
49
49
 
50
50
  ### `available_in` and `presets` on sections
51
51
 
52
- Every `sections/*.liquid` file must have:
52
+ `available_in` is not required on every section file. Placement is checked from each page / section-group JSON instead (see below).
53
53
 
54
- - `available_in.templates` with at least one page
55
- - `presets` as an array, with every preset object carrying a non-empty `name`
54
+ When a section does declare `available_in`:
55
+
56
+ - `available_in.templates` must be a non-empty array of page keys
57
+ - `presets` must be an array with every preset object carrying a non-empty `name`
56
58
 
57
59
  Preset objects may also include `settings`, `blocks`, and other defaults. A missing array, an empty array, or a preset without a name fails this check. `theme dev` logs it and still previews. `theme build` logs it, throws, and does not create the zip.
58
60
 
59
61
  Allowed page values are `index`, `cart`, `product`, `page`, `blog`, `blogs`, `collection`, `category`, `checkout`, `orders`, `search`, `404`, `header-group`, and `footer-group`. A value outside that list is a **warning** in both commands. A malformed `available_in` (not an object, `templates` not an array, or a non-string entry) is also a **warning**.
60
62
 
61
- Block files do not require `available_in` or `presets`.
63
+ Block files do not use `available_in` or `presets`.
62
64
 
63
65
  ### Settings
64
66
 
@@ -101,6 +103,7 @@ Checked on `pages/*.json` and on `sections/*.json` (header and footer groups).
101
103
  - `order` must be an array and `sections` must be an object.
102
104
  - Each `order` entry must name a section that exists in `sections` and has `type`. Listing the same section twice is a **warning**.
103
105
  - `sections/<type>.liquid` must exist. Types starting with `custom_section_` are stored outside the theme and are not looked up as theme files.
106
+ - On editable pages and section groups only (`index`, `cart`, `product`, `page`, `blog`, `blogs`, `collection`, `category`, `checkout`, `orders`, `search`, `404`, `header-group`, `footer-group`), each placed theme section's schema must list this JSON's basename in `available_in.templates` (e.g. every section in `pages/index.json` must include `"index"`). Non-editable page JSON (`signin`, `account-*`, etc.) skips this check. `custom_section_*` types also skip it.
104
107
  - A section present in `sections` but missing from `order` is a **warning**. It is never rendered.
105
108
  - `blocks` and `block_order` must be set together: `blocks` is an object and `block_order` is an array.
106
109
  - Every `block_order` entry must exist in `blocks` and must have `type`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zohofinance/commerce",
3
- "version": "2.2.1",
3
+ "version": "2.2.2",
4
4
  "main": "ts-dist/index.js",
5
5
  "bin": {
6
6
  "zcommerce": "ts-dist/index.js"
@@ -9,7 +9,7 @@
9
9
  }
10
10
  },
11
11
  "scripts": {
12
- "build": "zcommerce theme build --prod",
12
+ "build": "zcommerce theme build",
13
13
  "dev": "zcommerce theme dev"
14
14
  }
15
15
  }
@@ -35,12 +35,6 @@
35
35
  "label": "t:sections.promo_banner.settings.eyebrow.label",
36
36
  "default": "t:sections.promo_banner.settings.eyebrow.default"
37
37
  },
38
- {
39
- "type": "textarea",
40
- "id": "subtext",
41
- "label": "t:sections.promo_banner.settings.subtext.label",
42
- "default": "t:sections.promo_banner.settings.subtext.default"
43
- },
44
38
  {
45
39
  "type": "text",
46
40
  "id": "cta_label",
@@ -7,6 +7,7 @@
7
7
  export declare const MAX_BLOCK_NESTING_DEPTH = 5;
8
8
  export declare const EDITABLE_PAGE_KEYS: readonly ["index", "cart", "product", "page", "blog", "blogs", "collection", "category", "checkout", "orders", "search", "404"];
9
9
  export declare const SECTION_GROUP_KEYS: readonly ["header-group", "footer-group"];
10
+ export declare function isEditableTemplateName(name: string): boolean;
10
11
  /** Engine SchemaValidator.KNOWN_SETTING_TYPES plus color_scheme. */
11
12
  export declare const SUPPORTED_SETTING_TYPES: Set<string>;
12
13
  export type JsonRecord = Record<string, unknown>;
@@ -18,10 +19,10 @@ export type SchemaFinding = {
18
19
  export declare function validateSchemaDocument(schema: JsonRecord, options?: {
19
20
  requireAvailableIn?: boolean;
20
21
  requirePresets?: boolean;
21
- /** Severity for a missing available_in or a missing named preset. */
22
22
  requirementSeverity?: 'error' | 'warning';
23
23
  themeBlockSchemas?: Map<string, JsonRecord>;
24
24
  }): SchemaFinding[];
25
+ export declare function schemaAllowsTemplate(schema: JsonRecord | null | undefined, templateName: string): boolean;
25
26
  export declare function isBlockTypeAllowed(containerSchema: JsonRecord | null, blockType: string): boolean;
26
27
  /**
27
28
  * Schema whose `blocks` allow a placed block's children.
@@ -1 +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"}
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,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE5D;AAED,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,mBAAmB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC1C,iBAAiB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;CACxC,GACL,aAAa,EAAE,CAuCjB;AAED,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,UAAU,GAAG,IAAI,GAAG,SAAS,EACrC,YAAY,EAAE,MAAM,GACnB,OAAO,CAMT;AAkgBD,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"}
@@ -24,6 +24,9 @@ const ALLOWED_AVAILABLE_IN = new Set([
24
24
  ...EDITABLE_PAGE_KEYS,
25
25
  ...SECTION_GROUP_KEYS
26
26
  ]);
27
+ export function isEditableTemplateName(name) {
28
+ return ALLOWED_AVAILABLE_IN.has(name);
29
+ }
27
30
  /** Engine SchemaValidator.KNOWN_SETTING_TYPES plus color_scheme. */
28
31
  export const SUPPORTED_SETTING_TYPES = new Set([
29
32
  'checkbox',
@@ -85,12 +88,20 @@ export function validateSchemaDocument(schema, options = {}) {
85
88
  push(issues, 'max_blocks must be a positive integer');
86
89
  }
87
90
  const requirementSeverity = options.requirementSeverity ?? 'warning';
91
+ const requirePresets = options.requirePresets === true || 'available_in' in schema;
88
92
  validateAvailableIn(schema, issues, options.requireAvailableIn === true, requirementSeverity);
89
93
  validateSettingsArray(schema.settings, 'settings', issues);
90
94
  validateSchemaBlocks(schema.blocks, 'blocks', issues, 1);
91
- validatePresets(schema, issues, options.requirePresets === true, options.themeBlockSchemas, requirementSeverity);
95
+ validatePresets(schema, issues, requirePresets, options.themeBlockSchemas, requirementSeverity);
92
96
  return issues;
93
97
  }
98
+ export function schemaAllowsTemplate(schema, templateName) {
99
+ if (!schema || !isRecord(schema.available_in)) {
100
+ return false;
101
+ }
102
+ const templates = schema.available_in.templates;
103
+ return Array.isArray(templates) && templates.includes(templateName);
104
+ }
94
105
  function validateAvailableIn(schema, issues, required, requirementSeverity) {
95
106
  if (!('available_in' in schema)) {
96
107
  if (required) {
@@ -104,17 +115,15 @@ function validateAvailableIn(schema, issues, required, requirementSeverity) {
104
115
  }
105
116
  const templates = schema.available_in.templates;
106
117
  if (templates === undefined || templates === null) {
107
- if (required) {
108
- push(issues, 'available_in.templates must contain at least one page', requirementSeverity);
109
- }
118
+ push(issues, 'available_in.templates must contain at least the pages the sections are present in json', requirementSeverity);
110
119
  return;
111
120
  }
112
121
  if (!Array.isArray(templates)) {
113
122
  push(issues, 'available_in.templates must be an array', 'warning');
114
123
  return;
115
124
  }
116
- if (required && templates.length === 0) {
117
- push(issues, 'available_in.templates must contain at least one page', requirementSeverity);
125
+ if (templates.length === 0) {
126
+ push(issues, 'available_in.templates must contain at least the pages the sections are present in json', requirementSeverity);
118
127
  }
119
128
  templates.forEach((value, index) => {
120
129
  const path = `available_in.templates[${index}]`;
@@ -1 +1 @@
1
- {"version":3,"file":"template-reference-validation.d.ts","sourceRoot":"","sources":["../../src/utils/template-reference-validation.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AA8J7D,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,KAAK,GAAG,OAAiB,GAC9B,eAAe,EAAE,CA6GnB;AAED,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,EAAE,CAsCzE;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,EAAE,CAsKnE;AAkOD,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,EAAE,CAkFvE"}
1
+ {"version":3,"file":"template-reference-validation.d.ts","sourceRoot":"","sources":["../../src/utils/template-reference-validation.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAgK7D,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,KAAK,GAAG,OAAiB,GAC9B,eAAe,EAAE,CAqFnB;AAED,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,EAAE,CAsCzE;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,EAAE,CAuLnE;AAkOD,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,EAAE,CAkFvE"}
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import fs from 'fs-extra';
7
7
  import path from 'path';
8
- import { MAX_BLOCK_NESTING_DEPTH, declaredThemeBlockNames, isBlockTypeAllowed, isRecord, resolveChildContainerSchema, validateSchemaDocument, validateThemeSettingsData, validateThemeSettingsGroups } from './schema-validation.js';
8
+ import { MAX_BLOCK_NESTING_DEPTH, declaredThemeBlockNames, isBlockTypeAllowed, isEditableTemplateName, isRecord, resolveChildContainerSchema, schemaAllowsTemplate, validateSchemaDocument, validateThemeSettingsData, validateThemeSettingsGroups } from './schema-validation.js';
9
9
  const SCHEMA_BLOCK = /\{%-?\s*schema\s*-?%\}([\s\S]*?)\{%-?\s*endschema\s*-?%\}/;
10
10
  const UNSUPPORTED_TAGS = new Set([
11
11
  'paginate',
@@ -129,8 +129,8 @@ export function validateLiquidSchemas(cwd, mode = 'build') {
129
129
  const issues = [];
130
130
  const themeBlockSchemas = loadSchemaIndex(cwd, 'blocks');
131
131
  const themeBlockFiles = liquidBaseNames(cwd, 'blocks');
132
+ // available_in → presets requirement: warning in dev, error in build.
132
133
  const sectionRequirementSeverity = mode === 'build' ? 'error' : 'warning';
133
- // Requirement findings are warnings in dev but errors in build.
134
134
  const requirementFailsBuild = mode !== 'build';
135
135
  for (const folder of ['sections', 'blocks']) {
136
136
  const dir = path.join(cwd, folder);
@@ -145,10 +145,6 @@ export function validateLiquidSchemas(cwd, mode = 'build') {
145
145
  if (folder === 'blocks') {
146
146
  issues.push(issue('schema', 'Theme block file must contain {% schema %}...{% endschema %}', relative, 'warning'));
147
147
  }
148
- else {
149
- issues.push(issue('schema', 'available_in is required and must list at least one page in available_in.templates', relative, sectionRequirementSeverity, requirementFailsBuild));
150
- issues.push(issue('schema', 'presets must contain at least one preset with a name', relative, sectionRequirementSeverity, requirementFailsBuild));
151
- }
152
148
  continue;
153
149
  }
154
150
  if (extracted.invalid) {
@@ -159,8 +155,8 @@ export function validateLiquidSchemas(cwd, mode = 'build') {
159
155
  continue;
160
156
  }
161
157
  const schemaOptions = {
162
- requireAvailableIn: folder === 'sections',
163
- requirePresets: folder === 'sections',
158
+ requireAvailableIn: false,
159
+ requirePresets: false,
164
160
  themeBlockSchemas
165
161
  };
166
162
  const messages = validateSchemaDocument(extracted.schema, {
@@ -168,12 +164,7 @@ export function validateLiquidSchemas(cwd, mode = 'build') {
168
164
  requirementSeverity: folder === 'sections' ? sectionRequirementSeverity : 'error'
169
165
  });
170
166
  // Same findings, in the same order, as build would report them.
171
- const buildMessages = requirementFailsBuild
172
- ? validateSchemaDocument(extracted.schema, {
173
- ...schemaOptions,
174
- requirementSeverity: 'error'
175
- })
176
- : messages;
167
+ const buildMessages = requirementFailsBuild ? validateSchemaDocument(extracted.schema, { ...schemaOptions, requirementSeverity: 'error' }) : messages;
177
168
  messages.forEach((finding, index) => {
178
169
  issues.push(issue('schema', finding.message, relative, finding.severity, finding.severity === 'warning' &&
179
170
  buildMessages[index]?.severity === 'error'));
@@ -236,6 +227,7 @@ export function validateTemplateJson(cwd) {
236
227
  if (!isRecord(parsed)) {
237
228
  continue;
238
229
  }
230
+ const templateName = path.basename(relative, '.json');
239
231
  if (typeof parsed.layout === 'string' && parsed.layout.trim()) {
240
232
  const layoutFile = `layouts/${parsed.layout}.liquid`;
241
233
  if (!fs.existsSync(path.join(cwd, layoutFile))) {
@@ -281,6 +273,11 @@ export function validateTemplateJson(cwd) {
281
273
  }
282
274
  }
283
275
  const sectionSchema = sectionSchemas.get(type) ?? null;
276
+ if (!type.startsWith(CUSTOM_SECTION_PREFIX) &&
277
+ isEditableTemplateName(templateName) &&
278
+ !schemaAllowsTemplate(sectionSchema, templateName)) {
279
+ issues.push(issue('page', `Section '${type}' is used here but its available_in.templates does not include '${templateName}'`, relative));
280
+ }
284
281
  validatePlacedBlocks(section, sectionSchema, entry, relative, issues, themeBlockSchemas, themeBlockFiles, 1);
285
282
  if (sectionSchema &&
286
283
  typeof sectionSchema.max_blocks === 'number' &&
@@ -167,8 +167,8 @@ function validateRequiredFiles(cwd) {
167
167
  }
168
168
  export async function validateTheme(cwd = process.cwd(), options = {}) {
169
169
  const mode = options.mode ?? 'build';
170
- // Dev and build share one rule set. A section missing available_in or a
171
- // named preset is logged in dev and blocks the zip on build.
170
+ // Dev and build share one rule set. A section that declares available_in
171
+ // without a named preset is logged in dev and blocks the zip on build.
172
172
  if (mode !== 'dev' && mode !== 'build') {
173
173
  return { valid: true, issues: [] };
174
174
  }
Binary file