@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 +9 -6
- package/package.json +1 -1
- package/ts-dist/mocks/init-theme/package.json +1 -1
- package/ts-dist/mocks/init-theme/sections/promo-banner.liquid +0 -6
- package/ts-dist/utils/schema-validation.d.ts +2 -1
- package/ts-dist/utils/schema-validation.d.ts.map +1 -1
- package/ts-dist/utils/schema-validation.js +15 -6
- package/ts-dist/utils/template-reference-validation.d.ts.map +1 -1
- package/ts-dist/utils/template-reference-validation.js +11 -14
- package/ts-dist/utils/theme-validation.js +2 -2
- package/zohofinance-commerce-2.2.1.tgz +0 -0
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
55
|
-
|
|
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
|
|
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
|
@@ -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,
|
|
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,
|
|
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
|
-
|
|
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 (
|
|
117
|
-
push(issues, 'available_in.templates must contain at least
|
|
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;
|
|
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:
|
|
163
|
-
requirePresets:
|
|
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
|
|
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
|