Skip to content
Merged
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
53 changes: 52 additions & 1 deletion docs/user-guide/config-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ If the mapping of variables is skipped, you should delete the variables.json fil

## Package Version

The **config versions** command group allows you to retrieve metadata information about specific package versions.
The **config versions** command group allows you to retrieve metadata information about specific package versions, and to create new versions.

### Get Package Version

Expand Down Expand Up @@ -230,6 +230,57 @@ To export the version metadata as a JSON file instead of displaying it in the co
content-cli config versions get --packageKey <packageKey> --packageVersion <packageVersion> --json
```

### Create Package Version

To create a new version for a package, use the following command:

```bash
content-cli config versions create --packageKey <packageKey> --version <version>
```

For example, to create version 1.2.0 with a summary of changes:

```bash
content-cli config versions create --packageKey my-package --version 1.2.0 --summaryOfChanges "Added new analysis views"
```

The command will display the created version details in the console:

```bash
info: Successfully created version 1.2.0 for package my-package
info: Version: 1.2.0
info: Package Key: my-package
info: Summary of Changes: Added new analysis views
info: Creation Date: 2025-03-19T10:00:00.000Z
info: Created By: user@example.com
```

#### Version Bump Option

Instead of specifying an explicit version, you can use `--versionBumpOption PATCH` to automatically bump the patch version:

```bash
content-cli config versions create --packageKey my-package --versionBumpOption PATCH --summaryOfChanges "Bug fixes"
```

When using `--versionBumpOption PATCH`, the `--version` option is ignored and the patch version is automatically incremented. The default value for `--versionBumpOption` is `NONE`, which requires the `--version` option to be provided.

#### Node Filter

By default, all nodes in the package are included in the created version. To selectively include only specific nodes, use the `--nodeFilterKeys` option:

```bash
content-cli config versions create --packageKey my-package --version 2.0.0 --nodeFilterKeys node-key-1 node-key-2
```

#### Export Created Version as JSON

To export the created version response as a JSON file instead of displaying it in the console, use the `--json` option:

```bash
content-cli config versions create --packageKey <packageKey> --version <version> --json
```

## Finding Nodes

The **config nodes find** command allows you to retrieve information about a specific node within a package.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { HttpClient } from "../../../core/http/http-client";
import { Context } from "../../../core/command/cli-context";
import { FatalError } from "../../../core/utils/logger";
import { PackageVersionTransport } from "../interfaces/package-version.interfaces";
import {
PackageVersionCreatedTransport,
PackageVersionTransport,
SavePackageVersionTransport,
} from "../interfaces/package-version.interfaces";

export class PackageVersionApi {
private httpClient: () => HttpClient;
Expand All @@ -17,5 +21,13 @@ export class PackageVersionApi {
throw new FatalError(`Problem finding Package with key '${packageKey}' and version '${version}': ${e}`);
});
}

public async createVersion(packageKey: string, request: SavePackageVersionTransport): Promise<PackageVersionCreatedTransport> {
return this.httpClient()
.post(`/pacman/api/core/packages/${packageKey}/versions`, request)
.catch(e => {
throw new FatalError(`Problem creating version for package '${packageKey}': ${e}`);
});
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,28 @@ export interface PackageVersionTransport {
deployed: boolean;
publishedBy: string;
}

export interface SavePackageVersionTransport {
version?: string;
versionBumpOption?: VersionBumpOption;
summaryOfChanges?: string;
nodeFilter?: NodeFilterTransport;
}

export enum VersionBumpOption {
NONE = "NONE",
PATCH = "PATCH",
}

export interface NodeFilterTransport {
filterType: "INCLUDE";
keys: string[];
}

export interface PackageVersionCreatedTransport {
packageKey: string;
version: string;
summaryOfChanges: string;
creationDate: string;
createdBy: string;
}
31 changes: 31 additions & 0 deletions src/commands/configuration-management/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,16 @@ class Module extends IModule {
.option("--json", "Return the response as a JSON file")
.action(this.getPackageVersion)

configVersionCommand.command("create")
.description("Create a new version for a package")
.requiredOption("--packageKey <packageKey>", "Identifier of the package")
.option("--version <version>", "Version string (required if versionBumpOption is NONE)")
.option("--versionBumpOption <versionBumpOption>", "Version bump option: NONE or PATCH", "NONE")
.option("--summaryOfChanges <summaryOfChanges>", "Summary of changes for this version")
.option("--nodeFilterKeys <nodeFilterKeys...>", "Node keys to include in the version. If omitted, all nodes of the package are included.")
.option("--json", "Return the response as a JSON file")
.action(this.createPackageVersion);

const variablesCommand = configCommand.command("variables")
.description("Commands related to variable configs");

Expand Down Expand Up @@ -160,6 +170,27 @@ class Module extends IModule {
await new PackageVersionCommandService(context).getPackageVersion(options.packageKey, options.packageVersion, options.json);
}

private async createPackageVersion(context: Context, command: Command, options: OptionValues): Promise<void> {
const hasExplicitVersion = !!options.version;
const hasVersionBump = options.versionBumpOption && options.versionBumpOption !== "NONE";

if (hasExplicitVersion && hasVersionBump) {
throw new Error("Please provide either --version or --versionBumpOption, but not both.");
}
if (!hasExplicitVersion && !hasVersionBump) {
throw new Error("Please provide either --version or --versionBumpOption PATCH.");
}

await new PackageVersionCommandService(context).createPackageVersion(
options.packageKey,
options.version,
options.versionBumpOption,
options.summaryOfChanges,
options.nodeFilterKeys,
options.json,
);
}

private async batchImportPackages(context: Context, command: Command, options: OptionValues): Promise<void> {
if (options.gitProfile && !options.gitBranch) {
throw new Error("Please specify a branch using --gitBranch when using a Git profile.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,15 @@ export class PackageVersionCommandService {
public async getPackageVersion(packageKey: string, version: string, jsonResponse: boolean): Promise<void> {
await this.packageVersionService.findPackageVersion(packageKey, version, jsonResponse);
}

public async createPackageVersion(
packageKey: string,
version: string | undefined,
versionBumpOption: string,
summaryOfChanges: string | undefined,
nodeFilterKeys: string[] | undefined,
jsonResponse: boolean,
): Promise<void> {
await this.packageVersionService.createPackageVersion(packageKey, version, versionBumpOption, summaryOfChanges, nodeFilterKeys, jsonResponse);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import { fileService, FileService } from "../../core/utils/file-service";
import { logger } from "../../core/utils/logger";
import { v4 as uuidv4 } from "uuid";
import { PackageVersionApi } from "./api/package-version-api";
import { PackageVersionTransport } from "./interfaces/package-version.interfaces";
import {
PackageVersionCreatedTransport,
PackageVersionTransport,
SavePackageVersionTransport,
VersionBumpOption,
} from "./interfaces/package-version.interfaces";

export class PackageVersionService {
private packageVersionApi: PackageVersionApi;
Expand All @@ -23,6 +28,38 @@ export class PackageVersionService {
}
}

public async createPackageVersion(
packageKey: string,
version: string | undefined,
versionBumpOption: string,
summaryOfChanges: string | undefined,
nodeFilterKeys: string[] | undefined,
jsonResponse: boolean,
): Promise<void> {
const request: SavePackageVersionTransport = {
version: version,
versionBumpOption: versionBumpOption as VersionBumpOption,
summaryOfChanges: summaryOfChanges,
};

if (nodeFilterKeys && nodeFilterKeys.length > 0) {
request.nodeFilter = {
filterType: "INCLUDE",
keys: nodeFilterKeys,
};
}

const created: PackageVersionCreatedTransport = await this.packageVersionApi.createVersion(packageKey, request);

if (jsonResponse) {
const filename = uuidv4() + ".json";
fileService.writeToFileWithGivenName(JSON.stringify(created, null, 2), filename);
logger.info(FileService.fileDownloadedMessage + filename);
} else {
this.printPackageVersionCreatedTransport(created);
}
}

private printPackageVersionTransport(packageVersionTransport: PackageVersionTransport): void {
logger.info(`Package Key: ${packageVersionTransport.packageKey}`);
logger.info(`Version: ${packageVersionTransport.version}`);
Expand All @@ -33,4 +70,13 @@ export class PackageVersionService {
logger.info(`Deployed: ${packageVersionTransport.deployed}`);
logger.info(`Published By: ${packageVersionTransport.publishedBy}`);
}

private printPackageVersionCreatedTransport(transport: PackageVersionCreatedTransport): void {
logger.info(`Successfully created version ${transport.version} for package ${transport.packageKey}`);
logger.info(`Version: ${transport.version}`);
logger.info(`Package Key: ${transport.packageKey}`);
logger.info(`Summary of Changes: ${transport.summaryOfChanges}`);
logger.info(`Creation Date: ${new Date(transport.creationDate).toISOString()}`);
logger.info(`Created By: ${transport.createdBy}`);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import {
PackageVersionCreatedTransport,
VersionBumpOption,
} from "../../../src/commands/configuration-management/interfaces/package-version.interfaces";
import { mockAxiosPost, mockedPostRequestBodyByUrl } from "../../utls/http-requests-mock";
import { PackageVersionService } from "../../../src/commands/configuration-management/package-version.service";
import {
PackageVersionCommandService,
} from "../../../src/commands/configuration-management/package-version-command.service";
import { testContext } from "../../utls/test-context";
import { loggingTestTransport, mockWriteFileSync } from "../../jest.setup";
import { FileService } from "../../../src/core/utils/file-service";
import * as path from "path";

describe("Package Version create", () => {
const packageKey = "test-package-key";
const apiUrl = `https://myTeam.celonis.cloud/pacman/api/core/packages/${packageKey}/versions`;

const createdVersion: PackageVersionCreatedTransport = {
packageKey: packageKey,
version: "1.2.0",
summaryOfChanges: "Added new analysis views",
creationDate: new Date("2025-03-19T10:00:00Z").toISOString(),
createdBy: "user@example.com",
};

it("Should create package version with explicit version", async () => {
mockAxiosPost(apiUrl, createdVersion);

await new PackageVersionCommandService(testContext).createPackageVersion(
packageKey, "1.2.0", "NONE", "Added new analysis views", undefined, false,
);

expect(loggingTestTransport.logMessages.length).toBe(6);
expect(loggingTestTransport.logMessages[0].message).toContain(`Successfully created version ${createdVersion.version} for package ${createdVersion.packageKey}`);
expect(loggingTestTransport.logMessages[1].message).toContain(`Version: ${createdVersion.version}`);
expect(loggingTestTransport.logMessages[2].message).toContain(`Package Key: ${createdVersion.packageKey}`);
expect(loggingTestTransport.logMessages[3].message).toContain(`Summary of Changes: ${createdVersion.summaryOfChanges}`);
expect(loggingTestTransport.logMessages[4].message).toContain(`Creation Date: ${new Date(createdVersion.creationDate).toISOString()}`);
expect(loggingTestTransport.logMessages[5].message).toContain(`Created By: ${createdVersion.createdBy}`);
});

it("Should create package version and return as JSON", async () => {
mockAxiosPost(apiUrl, createdVersion);

await new PackageVersionService(testContext).createPackageVersion(
packageKey, "1.2.0", "NONE", "Added new analysis views", undefined, true,
);

const expectedFileName = loggingTestTransport.logMessages[0].message.split(FileService.fileDownloadedMessage)[1];

expect(mockWriteFileSync).toHaveBeenCalledWith(path.resolve(process.cwd(), expectedFileName), expect.any(String), {encoding: "utf-8"});

const savedTransport = JSON.parse(mockWriteFileSync.mock.calls[0][1]) as PackageVersionCreatedTransport;

expect(savedTransport).toEqual(createdVersion);
});

it("Should create package version with PATCH version bump option", async () => {
const patchCreated: PackageVersionCreatedTransport = {
...createdVersion,
version: "1.2.1",
};
mockAxiosPost(apiUrl, patchCreated);

await new PackageVersionCommandService(testContext).createPackageVersion(
packageKey, undefined, "PATCH", "Bug fixes", undefined, false,
);

const requestBody = JSON.parse(mockedPostRequestBodyByUrl.get(apiUrl));
expect(requestBody.versionBumpOption).toBe(VersionBumpOption.PATCH);
expect(requestBody.version).toBeUndefined();

expect(loggingTestTransport.logMessages[0].message).toContain(`Successfully created version ${patchCreated.version}`);
});

it("Should create package version with node filter keys", async () => {
mockAxiosPost(apiUrl, createdVersion);

const nodeKeys = ["node-key-1", "node-key-2"];
await new PackageVersionCommandService(testContext).createPackageVersion(
packageKey, "1.2.0", "NONE", "Partial publish", nodeKeys, false,
);

const requestBody = JSON.parse(mockedPostRequestBodyByUrl.get(apiUrl));
expect(requestBody.nodeFilter).toEqual({
filterType: "INCLUDE",
keys: nodeKeys,
});
});

it("Should create package version without node filter when keys are omitted", async () => {
mockAxiosPost(apiUrl, createdVersion);

await new PackageVersionCommandService(testContext).createPackageVersion(
packageKey, "1.2.0", "NONE", "Full publish", undefined, false,
);

const requestBody = JSON.parse(mockedPostRequestBodyByUrl.get(apiUrl));
expect(requestBody.nodeFilter).toBeUndefined();
});

it("Should handle empty summary of changes", async () => {
const createdWithEmptySummary: PackageVersionCreatedTransport = {
...createdVersion,
summaryOfChanges: "",
};
mockAxiosPost(apiUrl, createdWithEmptySummary);

await new PackageVersionCommandService(testContext).createPackageVersion(
packageKey, "1.2.0", "NONE", "", undefined, false,
);

expect(loggingTestTransport.logMessages.length).toBe(6);
expect(loggingTestTransport.logMessages[3].message).toContain("Summary of Changes: ");
});
});
Loading
Loading