Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 35 additions & 7 deletions .cursor/skills/asset-registry-endpoints/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,20 +223,46 @@ methodology — a 404 means the endpoint is not available.
$CLI asset-registry methodology --assetType <ASSET_TYPE> -p <profile>
```

### Validate (POST — via config import)
### Validate

Use `config import --validate` to validate assets against their schema before
importing:
Validate a configuration before importing it:

```bash
$CLI asset-registry validate --assetType <ASSET_TYPE> \
--packageKey <pkg> --configuration '<configuration-json>' -p <profile>
```

Or load configuration from a file:

```bash
$CLI asset-registry validate --assetType <ASSET_TYPE> \
--packageKey <pkg> -c config.json -p <profile>
```

Validate an already-stored node on the platform:

```bash
$CLI asset-registry validate --assetType <ASSET_TYPE> \
--packageKey <pkg> --nodeKey <key> -p <profile>
```

For multi-node validation, provide a full `ValidateRequest` file:

```bash
$CLI asset-registry validate --assetType <ASSET_TYPE> -f request.json -p <profile>
```

You can also validate during import with `config import --validate`:

```bash
$CLI config import -d <export_dir> --validate --overwrite -p <profile>
```

**Important**: If validation returns errors, do **not** proceed with the import.
Instead, fix the schema violations in the node JSON and re-run the command. If
you cannot resolve the errors automatically, present the validation results to
the user and ask whether they want to continue importing with invalid
configuration or stop to fix it manually.
Instead, fix the schema violations in the node JSON and re-validate. If you
cannot resolve the errors automatically, present the validation results to the
user and ask whether they want to continue importing with invalid configuration
or stop to fix it manually.

## Troubleshooting

Expand Down Expand Up @@ -284,6 +310,8 @@ $CLI config import -d <export_dir> --validate --overwrite -p <profile>
| `asset-registry list` | List all registered asset types |
| `asset-registry get --assetType X` | Get the full descriptor for an asset type |
| `asset-registry schema --assetType X` | Get the JSON Schema for the asset's configuration |
| `asset-registry validate --assetType X --packageKey P --configuration '{}'` | Validate a configuration before import |
| `asset-registry validate --assetType X --packageKey P --nodeKey K` | Validate a stored node |
| `asset-registry examples --assetType X` | Get example configurations (if available) |
| `asset-registry methodology --assetType X` | Get methodology / best-practices (if available) |
| `config list` | List packages |
Expand Down
21 changes: 18 additions & 3 deletions docs/user-guide/agentic-development-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,30 @@ Add a new JSON file in the `nodes/` directory:

Set `schemaVersion` to the value from the asset descriptor's `assetSchema.version` field (returned by `asset-registry get`). The `spaceId` is required — omitting it causes import errors.

### 5. Validate and import
### 5. Validate

Before importing, validate the asset configuration:

```bash
content-cli asset-registry validate --assetType <ASSET_TYPE> \
--packageKey <package-key> --configuration '{ ... }'
```

Or validate during import with the `--validate` flag:

```bash
content-cli config import -d <export_dir> --validate --overwrite
```

The `--validate` option performs schema validations for the assets. If there are no schema validations, then the package and its assets are imported. Otherwise, the validation errors are returned and the package import isn't performed.
If validation returns errors, fix the issues before importing.

### 6. Import

```bash
content-cli config import -d <export_dir> --overwrite
```

This creates a new version in staging (not deployed) if there are no schema validation errors. To create a brand-new package instead of updating, omit `--overwrite`.
This creates a new version in staging (not deployed). To create a brand-new package instead of updating, omit `--overwrite`.

To later export a staging version, use `--keysByVersion`:

Expand Down
45 changes: 45 additions & 0 deletions docs/user-guide/asset-registry-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,51 @@ Options:
- `--assetType <assetType>` (required) – The asset type identifier
- `--json` – Write the schema to a JSON file in the working directory

## Validate

Validate asset configurations against the asset service's validation endpoint.

### Validate a configuration before import

Provide the configuration JSON inline or via a file. The CLI wraps it into the `ValidateRequest` envelope for you.

```
content-cli asset-registry validate --assetType BOARD_V2 \
--packageKey my-pkg --configuration '{"components":[{"type":"kpi"}]}'
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What was the reason for enabling configuration passing directly instead of using a file? I expect this to be very unusable even for an agent to do

```

```
content-cli asset-registry validate --assetType BOARD_V2 \
--packageKey my-pkg -c config.json
```

### Validate an already-stored node

```
content-cli asset-registry validate --assetType BOARD_V2 \
--packageKey my-pkg --nodeKey my-view
```

### Full request from file

For multi-node validation or other advanced use, provide a JSON file containing the entire `ValidateRequest` body.

```
content-cli asset-registry validate --assetType BOARD_V2 -f request.json
```

### Options

- `--assetType <assetType>` (required) – The asset type identifier
- `--packageKey <packageKey>` – Package key containing the node
- `--nodeKey <nodeKey>` – Key of an already-stored node to validate on the platform
- `--configuration <configuration>` – Inline JSON of the configuration to validate before import
- `-c, --configFile <configFile>` – Path to a JSON file containing the configuration to validate before import
Comment on lines +112 to +113
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the short hand looks very confusing considering we have both --configuration and --configFile. I would just remove it here and leave no short hand

- `-f, --file <file>` – Path to a JSON file containing a full ValidateRequest (alternative to all other options)
- `--json` – Write the validation response to a JSON file in the working directory

The options `--nodeKey`, `--configuration`/`-c`, and `-f` are mutually exclusive.

## Get Examples

Fetch example configurations for an asset type. Not all asset types provide examples.
Expand Down
8 changes: 8 additions & 0 deletions src/commands/asset-registry/asset-registry-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,12 @@ export class AssetRegistryApi {
throw new FatalError(`Problem getting methodology for asset type '${assetType}': ${e}`);
});
}

public async validate(assetType: string, body: any): Promise<any> {
return this.httpClient()
.post(`/pacman/api/core/asset-registry/validate/${encodeURIComponent(assetType)}`, body)
.catch((e) => {
throw new FatalError(`Problem validating asset type '${assetType}': ${e}`);
});
}
}
76 changes: 75 additions & 1 deletion src/commands/asset-registry/asset-registry.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
import { AssetRegistryDescriptor } from "./asset-registry.interfaces";
import { Context } from "../../core/command/cli-context";
import { fileService, FileService } from "../../core/utils/file-service";
import { logger } from "../../core/utils/logger";
import { FatalError, logger } from "../../core/utils/logger";
import { v4 as uuidv4 } from "uuid";
import * as fs from "fs";

Check warning on line 7 in src/commands/asset-registry/asset-registry.service.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `node:fs` over `fs`.

See more on https://sonarcloud.io/project/issues?id=celonis_content-cli&issues=AZ2bpN493GBU49Wb6D6G&open=AZ2bpN493GBU49Wb6D6G&pullRequest=342

export class AssetRegistryService {
private api: AssetRegistryApi;
Expand Down Expand Up @@ -58,6 +59,69 @@
this.outputResponse(data, jsonResponse);
}

public async validate(opts: ValidateOptions): Promise<void> {
const payload = this.buildValidatePayload(opts);
const data = await this.api.validate(opts.assetType, payload);
this.outputResponse(data, opts.json);
}

private buildValidatePayload(opts: ValidateOptions): any {
const hasNodeKey = !!opts.nodeKey;
const hasConfig = !!(opts.configuration || opts.configFile);
const hasFile = !!opts.file;

const modeCount = [hasNodeKey, hasConfig, hasFile].filter(Boolean).length;
if (modeCount === 0) {
throw new FatalError(
"Provide one of: --nodeKey (stored node), --configuration/-c (configuration JSON), or -f (full request file)."
);
}
if (modeCount > 1) {
throw new FatalError(
"Options --nodeKey, --configuration/-c, and -f are mutually exclusive."
);
}

if (hasFile) {
return this.parseJson(fs.readFileSync(opts.file!, "utf-8"), `-f ${opts.file}`);

Check warning on line 86 in src/commands/asset-registry/asset-registry.service.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

This assertion is unnecessary since it does not change the type of the expression.

See more on https://sonarcloud.io/project/issues?id=celonis_content-cli&issues=AZ2bp_hFC5IK9t0X7tON&open=AZ2bp_hFC5IK9t0X7tON&pullRequest=342
}

if (!opts.packageKey) {
throw new FatalError("--packageKey is required when using --nodeKey or --configuration/-c.");
}

if (hasNodeKey) {
return {
assetType: opts.assetType,
packageKey: opts.packageKey,
nodeKeys: [opts.nodeKey],
};
}

if (opts.configuration && opts.configFile) {
throw new FatalError("Provide either --configuration or -c, not both.");
}

const configJson = this.parseJson(
opts.configFile ? fs.readFileSync(opts.configFile, "utf-8") : opts.configuration!,

Check warning on line 106 in src/commands/asset-registry/asset-registry.service.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

This assertion is unnecessary since it does not change the type of the expression.

See more on https://sonarcloud.io/project/issues?id=celonis_content-cli&issues=AZ2bpN493GBU49Wb6D6H&open=AZ2bpN493GBU49Wb6D6H&pullRequest=342
opts.configFile ? `-c ${opts.configFile}` : "--configuration"
);

return {
assetType: opts.assetType,
packageKey: opts.packageKey,
nodes: [{ key: "validation-node", configuration: configJson }],
};
}

private parseJson(raw: string, source: string): any {
try {
return JSON.parse(raw);
} catch {
throw new FatalError(`Invalid JSON in ${source}.`);
}
}

private outputResponse(data: any, jsonResponse: boolean): void {
if (jsonResponse) {
const filename = uuidv4() + ".json";
Expand Down Expand Up @@ -94,3 +158,13 @@
}
}
}

export interface ValidateOptions {
assetType: string;
packageKey?: string;
nodeKey?: string;
configuration?: string;
configFile?: string;
file?: string;
json: boolean;
}
23 changes: 23 additions & 0 deletions src/commands/asset-registry/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,17 @@ class Module extends IModule {
.option("--json", "Return the response as a JSON file")
.action(this.getExamples);

assetRegistryCommand.command("validate")
.description("Validate asset configuration against the asset service's validate endpoint.")
.requiredOption("--assetType <assetType>", "The asset type identifier (e.g., BOARD_V2)")
.option("--packageKey <packageKey>", "Package key containing the node")
.option("--nodeKey <nodeKey>", "Key of an already-stored node to validate on the platform")
.option("--configuration <configuration>", "Inline JSON of the configuration to validate before import")
.option("-c, --configFile <configFile>", "Path to a JSON file containing the configuration to validate before import")
.option("-f, --file <file>", "Path to a JSON file containing a full ValidateRequest (alternative to all other options)")
.option("--json", "Return the response as a JSON file")
.action(this.validate);

assetRegistryCommand.command("methodology")
.description("Get the methodology / best-practices guide for an asset type")
.requiredOption("--assetType <assetType>", "The asset type identifier (e.g., BOARD_V2)")
Expand All @@ -51,6 +62,18 @@ class Module extends IModule {
await new AssetRegistryService(context).getSchema(options.assetType, !!options.json);
}

private async validate(context: Context, command: Command, options: OptionValues): Promise<void> {
await new AssetRegistryService(context).validate({
assetType: options.assetType,
packageKey: options.packageKey,
nodeKey: options.nodeKey,
configuration: options.configuration,
configFile: options.configFile,
file: options.file,
json: !!options.json,
});
}

private async getExamples(context: Context, command: Command, options: OptionValues): Promise<void> {
await new AssetRegistryService(context).getExamples(options.assetType, !!options.json);
}
Expand Down
57 changes: 57 additions & 0 deletions tests/commands/asset-registry/asset-registry-module.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ describe("Asset Registry Module", () => {
listTypes: jest.fn().mockResolvedValue(undefined),
getType: jest.fn().mockResolvedValue(undefined),
getSchema: jest.fn().mockResolvedValue(undefined),
validate: jest.fn().mockResolvedValue(undefined),
getExamples: jest.fn().mockResolvedValue(undefined),
getMethodology: jest.fn().mockResolvedValue(undefined),
} as any;
Expand All @@ -33,6 +34,62 @@ describe("Asset Registry Module", () => {
expect(mockService.getSchema).toHaveBeenCalledWith("BOARD_V2", true);
});

it("should call validate with configuration mode options", async () => {
const options: OptionValues = {
assetType: "BOARD_V2",
packageKey: "my-pkg",
configuration: '{"components":[]}',
json: true,
};
await (module as any).validate(testContext, mockCommand, options);
expect(mockService.validate).toHaveBeenCalledWith({
assetType: "BOARD_V2",
packageKey: "my-pkg",
nodeKey: undefined,
configuration: '{"components":[]}',
configFile: undefined,
file: undefined,
json: true,
});
});

it("should call validate with nodeKey mode options", async () => {
const options: OptionValues = {
assetType: "BOARD_V2",
packageKey: "my-pkg",
nodeKey: "my-view",
json: "",
};
await (module as any).validate(testContext, mockCommand, options);
expect(mockService.validate).toHaveBeenCalledWith({
assetType: "BOARD_V2",
packageKey: "my-pkg",
nodeKey: "my-view",
configuration: undefined,
configFile: undefined,
file: undefined,
json: false,
});
});

it("should call validate with file mode options", async () => {
const options: OptionValues = {
assetType: "BOARD_V2",
file: "request.json",
json: "",
};
await (module as any).validate(testContext, mockCommand, options);
expect(mockService.validate).toHaveBeenCalledWith({
assetType: "BOARD_V2",
packageKey: undefined,
nodeKey: undefined,
configuration: undefined,
configFile: undefined,
file: "request.json",
json: false,
});
});

it("should call getExamples with correct parameters", async () => {
const options: OptionValues = { assetType: "BOARD_V2", json: "" };
await (module as any).getExamples(testContext, mockCommand, options);
Expand Down
Loading
Loading