Skip to content

Commit 8c31081

Browse files
authored
Add content validate command for Page Designer metadefinitions (#278)
* Add `content validate` command for Page Designer metadefinitions Validate Page Designer metadefinition JSON files (page types, component types, aspect types, etc.) against bundled JSON schemas. Supports auto-detection of schema type from file paths and JSON content, explicit --type override, glob patterns, directory expansion, and JSON output. * lint
1 parent f79c31a commit 8c31081

32 files changed

Lines changed: 1746 additions & 9 deletions

.changeset/content-validate.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@salesforce/b2c-cli': minor
3+
'@salesforce/b2c-tooling-sdk': minor
4+
---
5+
6+
Add `content validate` command to validate Page Designer metadefinition JSON files against bundled schemas. Supports auto-detection of schema types from file paths and content, or explicit `--type` flag. Includes glob pattern support for validating multiple files.

docs/cli/content.md

Lines changed: 98 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,27 @@
11
---
2-
description: Commands for exporting and listing Page Designer content from B2C Commerce content libraries.
2+
description: Commands for exporting, listing, and validating Page Designer content from B2C Commerce content libraries.
33
---
44

55
# Content Commands
66

7-
Commands for working with Page Designer content libraries on B2C Commerce instances.
7+
Commands for working with Page Designer content libraries and metadefinitions.
88

99
## Authentication
1010

11-
Content commands require OAuth authentication:
11+
The `content export` and `content list` commands require OAuth authentication:
1212

1313
| Operation | Auth Required |
1414
|-----------|--------------|
1515
| `content export` | OAuth (OCAPI for export job + WebDAV for assets) |
1616
| `content list` | OAuth (OCAPI for export job) |
17+
| `content validate` | None (local file validation) |
1718

1819
```bash
1920
export SFCC_CLIENT_ID=your-client-id
2021
export SFCC_CLIENT_SECRET=your-client-secret
2122
```
2223

23-
Both commands also support a `--library-file` flag for offline use with a local XML file, which skips authentication entirely.
24+
The `content export` and `content list` commands also support a `--library-file` flag for offline use with a local XML file, which skips authentication entirely.
2425

2526
For complete setup instructions, see the [Authentication Guide](/guide/authentication).
2627

@@ -201,3 +202,96 @@ footer-content (CONTENT ASSET)
201202
Pages show `id (typeId: type)`, components show `typeId (id)`, content assets show `id (CONTENT ASSET)`, and static assets show `path (STATIC ASSET)`. The tree uses color when output to a terminal: page names are bold, component type IDs are cyan, asset paths are green, and tree connectors are dim.
202203

203204
With `--json`, returns `{ data: [...] }` with each item containing `id`, `type`, `typeId`, and `children` count.
205+
206+
---
207+
208+
## b2c content validate
209+
210+
Validate Page Designer metadefinition JSON files against bundled JSON schemas. This is a local-only command — no instance connection or authentication is required.
211+
212+
The command auto-detects the schema type using file path conventions (`experience/pages/` → pagetype, `experience/components/` → componenttype) and falls back to inspecting the JSON properties. You can also specify the type explicitly with `--type`.
213+
214+
### Usage
215+
216+
```bash
217+
b2c content validate <files...>
218+
```
219+
220+
### Arguments
221+
222+
| Argument | Description | Required |
223+
|----------|-------------|----------|
224+
| `files` | One or more file paths, directories, or glob patterns | Yes |
225+
226+
When a directory is passed, all `*.json` files within it are validated recursively.
227+
228+
### Flags
229+
230+
In addition to [global flags](./index#global-flags):
231+
232+
| Flag | Description | Default |
233+
|------|-------------|---------|
234+
| `--type`, `-t` | Schema type to validate against (skips auto-detection) | Auto-detected |
235+
236+
Available schema types: `pagetype`, `componenttype`, `aspecttype`, `cmsrecord`, `customeditortype`, `contentassetpageconfig`, `contentassetcomponentconfig`, `contentassetstructuredcontentdata`, `image`.
237+
238+
### Examples
239+
240+
```bash
241+
# Validate a single page type definition
242+
b2c content validate cartridge/experience/pages/storePage.json
243+
244+
# Validate all metadefinitions in a directory recursively
245+
b2c content validate cartridge/experience/
246+
247+
# Validate with a glob pattern
248+
b2c content validate 'cartridge/experience/**/*.json'
249+
250+
# Explicitly specify the schema type
251+
b2c content validate --type componenttype mycomponent.json
252+
253+
# Validate multiple files
254+
b2c content validate pages/home.json pages/about.json components/hero.json
255+
256+
# JSON output for CI/scripting
257+
b2c content validate cartridge/experience/ --json
258+
```
259+
260+
### Output
261+
262+
The command prints a color-coded result for each file:
263+
264+
```
265+
PASS: experience/pages/storePage.json (pagetype)
266+
PASS: experience/components/hero.json (componenttype)
267+
FAIL: experience/components/broken.json (componenttype)
268+
ERROR at .attribute_definition_groups[0]: is required
269+
270+
2/3 file(s) valid, 1 error(s)
271+
```
272+
273+
The command exits with a non-zero status when any file fails validation.
274+
275+
With `--json`, returns a structured result:
276+
277+
```json
278+
{
279+
"results": [
280+
{
281+
"valid": true,
282+
"schemaType": "pagetype",
283+
"errors": [],
284+
"filePath": "/path/to/storePage.json"
285+
}
286+
],
287+
"totalFiles": 1,
288+
"validFiles": 1,
289+
"totalErrors": 0
290+
}
291+
```
292+
293+
### Notes
294+
295+
- Auto-detection uses file path conventions first: files under `experience/pages/` are validated as `pagetype`, files under `experience/components/` as `componenttype`. For other schema types, use `--type`.
296+
- Schemas are bundled with the SDK and follow the [Page Designer metadefinition specification](https://developer.salesforce.com/docs/commerce/b2c-commerce/guide/b2c-page-designer.html).
297+
- Use `b2c content validate` in CI pipelines to catch metadefinition errors before deployment.

docs/typedoc.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"../packages/b2c-tooling-sdk/src/instance/index.ts",
88
"../packages/b2c-tooling-sdk/src/logging/index.ts",
99
"../packages/b2c-tooling-sdk/src/operations/code/index.ts",
10+
"../packages/b2c-tooling-sdk/src/operations/content/index.ts",
1011
"../packages/b2c-tooling-sdk/src/operations/cip/index.ts",
1112
"../packages/b2c-tooling-sdk/src/operations/jobs/index.ts",
1213
"../packages/b2c-tooling-sdk/src/operations/logs/index.ts",

packages/b2c-cli/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@
105105
"description": "Deploy and manage code versions on instances\n\nDocs: https://salesforcecommercecloud.github.io/b2c-developer-tooling/cli/code.html"
106106
},
107107
"content": {
108-
"description": "Export and manage Page Designer content libraries"
108+
"description": "Export, list, and validate Page Designer content"
109109
},
110110
"cip": {
111111
"description": "Run CIP analytics SQL and curated reports\n\nDocs: https://salesforcecommercecloud.github.io/b2c-developer-tooling/cli/cip.html"
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/*
2+
* Copyright (c) 2025, Salesforce, Inc.
3+
* SPDX-License-Identifier: Apache-2
4+
* For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0
5+
*/
6+
import fs from 'node:fs';
7+
import path from 'node:path';
8+
import {Args, Flags, ux} from '@oclif/core';
9+
import {glob} from 'glob';
10+
import {BaseCommand} from '@salesforce/b2c-tooling-sdk/cli';
11+
import {
12+
CONTENT_SCHEMA_TYPES,
13+
MetaDefinitionDetectionError,
14+
validateMetaDefinitionFile,
15+
type ContentSchemaType,
16+
type MetaDefinitionValidationResult,
17+
} from '@salesforce/b2c-tooling-sdk/operations/content';
18+
19+
interface ContentValidateResponse {
20+
results: MetaDefinitionValidationResult[];
21+
totalErrors: number;
22+
totalFiles: number;
23+
validFiles: number;
24+
}
25+
26+
export default class ContentValidate extends BaseCommand<typeof ContentValidate> {
27+
static args = {
28+
files: Args.string({
29+
description: 'File(s), directory, or glob pattern(s) for JSON metadefinition files to validate',
30+
required: true,
31+
}),
32+
};
33+
34+
static description = 'Validate Page Designer metadefinition JSON files against schemas';
35+
36+
static enableJsonFlag = true;
37+
38+
static examples = [
39+
'<%= config.bin %> <%= command.id %> cartridge/experience/pages/storePage.json',
40+
'<%= config.bin %> <%= command.id %> --type componenttype mycomponent.json',
41+
"<%= config.bin %> <%= command.id %> 'cartridge/experience/**/*.json'",
42+
'<%= config.bin %> <%= command.id %> cartridge/experience/',
43+
'<%= config.bin %> <%= command.id %> storePage.json --json',
44+
];
45+
46+
static flags = {
47+
...BaseCommand.baseFlags,
48+
type: Flags.string({
49+
char: 't',
50+
description: 'Schema type (auto-detected if not specified)',
51+
options: [...CONTENT_SCHEMA_TYPES],
52+
}),
53+
};
54+
55+
// Allow multiple file arguments
56+
static strict = false;
57+
58+
protected operations: {
59+
glob: (pattern: string, options?: {nodir?: boolean}) => Promise<string[]>;
60+
validateMetaDefinitionFile: typeof validateMetaDefinitionFile;
61+
} = {
62+
glob,
63+
validateMetaDefinitionFile,
64+
};
65+
66+
async run(): Promise<ContentValidateResponse> {
67+
const {argv, flags} = await this.parse(ContentValidate);
68+
const patterns = argv as string[];
69+
70+
if (patterns.length === 0) {
71+
this.error('At least one file path, directory, or glob pattern is required.');
72+
}
73+
74+
// Expand directories to recursive JSON globs, then resolve all patterns in parallel
75+
const resolvedPatterns = patterns.map((pattern) =>
76+
fs.existsSync(pattern) && fs.statSync(pattern).isDirectory() ? path.join(pattern, '**/*.json') : pattern,
77+
);
78+
const allMatches = await Promise.all(
79+
resolvedPatterns.map((resolved) => this.operations.glob(resolved, {nodir: true})),
80+
);
81+
const filePaths: string[] = [];
82+
for (const [i, matches] of allMatches.entries()) {
83+
if (matches.length === 0) {
84+
this.warn(`No files matched: ${patterns[i]}`);
85+
}
86+
filePaths.push(...matches);
87+
}
88+
89+
if (filePaths.length === 0) {
90+
this.error('No files found matching the provided patterns.');
91+
}
92+
93+
const results: MetaDefinitionValidationResult[] = [];
94+
95+
for (const filePath of filePaths) {
96+
try {
97+
const result = this.operations.validateMetaDefinitionFile(filePath, {
98+
type: flags.type as ContentSchemaType | undefined,
99+
});
100+
results.push(result);
101+
} catch (error) {
102+
if (error instanceof MetaDefinitionDetectionError) {
103+
const relativePath = path.relative(process.cwd(), path.resolve(filePath));
104+
this.error(`${relativePath}: ${error.message}`);
105+
}
106+
throw error;
107+
}
108+
}
109+
110+
const totalErrors = results.reduce((sum, r) => sum + r.errors.length, 0);
111+
const validFiles = results.filter((r) => r.valid).length;
112+
113+
const response: ContentValidateResponse = {
114+
results,
115+
totalErrors,
116+
totalFiles: results.length,
117+
validFiles,
118+
};
119+
120+
if (this.jsonEnabled()) {
121+
return response;
122+
}
123+
124+
for (const result of results) {
125+
const relativePath = path.relative(process.cwd(), result.filePath ?? '');
126+
const typeInfo = result.schemaType ? ` (${result.schemaType})` : '';
127+
128+
if (result.valid) {
129+
ux.stdout(`${ux.colorize('green', 'PASS')}: ${relativePath}${typeInfo}`);
130+
} else {
131+
ux.stdout(`${ux.colorize('red', 'FAIL')}: ${relativePath}${typeInfo}`);
132+
for (const error of result.errors) {
133+
const location = error.path && error.path !== '/' ? ` at ${error.path}` : '';
134+
ux.stdout(` ${ux.colorize('red', 'ERROR')}${location}: ${error.message}`);
135+
}
136+
}
137+
}
138+
139+
ux.stdout('');
140+
ux.stdout(`${validFiles}/${results.length} file(s) valid, ${totalErrors} error(s)`);
141+
142+
if (totalErrors > 0) {
143+
this.error('Validation failed', {exit: 1});
144+
}
145+
146+
return response;
147+
}
148+
}

0 commit comments

Comments
 (0)