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
21 changes: 21 additions & 0 deletions docs/user-guide/config-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -715,3 +715,24 @@ To export the node dependencies as a JSON file, use the `--json` option:
content-cli config nodes dependencies list --packageKey <packageKey> --nodeKey <nodeKey> --packageVersion <version> --json
content-cli config nodes dependencies list --packageKey <packageKey> --nodeKey <nodeKey> --json
```

## Diff local zip with deployed version/specific version/staging

To compare local zipped packages with online packages use:
```bash
content-cli config diff --file <file>
```

As with other commands, use `--json` to export the diff to a file.
To diff against a specific version use the `--baseVersion` parameter. When omitted it will diff against the current deployed version.
To diff against staging use `--baseVersion STAGING`.

```bash
content-cli config diff --file <file> --baseVersion <version>
```

To diff against the current deployed version and only return whether there are any changes, use the `--hasChanges` flag.

```bash
content-cli config diff --file <file> --hasChanges
```
10 changes: 6 additions & 4 deletions src/commands/configuration-management/api/diff-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,18 @@ export class DiffApi {
this.httpClient = () => context.httpClient;
}

public async diffPackages(data: FormData): Promise<PackageDiffTransport[]> {
public async diffPackages(baseVersion: string, data: FormData): Promise<PackageDiffTransport[]> {
const paramString = baseVersion ? "?" + new URLSearchParams({"baseVersion": baseVersion}).toString() : "";
return this.httpClient().postFile(
"/package-manager/api/core/packages/diff/configuration",
`/package-manager/api/core/packages/diff/configuration${paramString}`,
data
);
}

public async hasChanges(data: FormData): Promise<PackageDiffMetadata[]> {
public async hasChanges(baseVersion: string, data: FormData): Promise<PackageDiffMetadata[]> {
const paramString = baseVersion ? "?" + new URLSearchParams({"baseVersion": baseVersion}).toString() : "";
return this.httpClient().postFile(
"/package-manager/api/core/packages/diff/configuration/has-changes",
`/package-manager/api/core/packages/diff/configuration/has-changes${paramString}`,
data
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@ export class ConfigCommandService {
return this.batchImportExportService.batchImportPackages(sourcePath, overwrite, gitBranch, performValidation);
}

public diffPackages(file: string, hasChanges: boolean, jsonResponse: boolean): Promise<void> {
return this.diffService.diffPackages(file, hasChanges, jsonResponse);
public diffPackages(file: string, hasChanges: boolean, baseVersion: string, jsonResponse: boolean): Promise<void> {
return this.diffService.diffPackages(file, hasChanges, baseVersion, jsonResponse);
}

private async listPackagesByVariableValue(jsonResponse: boolean, flavors: string[], variableValue: string, variableType: string, includeBranches: boolean): Promise<void> {
Expand Down
14 changes: 7 additions & 7 deletions src/commands/configuration-management/diff.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,18 @@ export class DiffService {
this.diffApi = new DiffApi(context);
}

public async diffPackages(file: string, hasChanges: boolean, jsonResponse: boolean): Promise<void> {
public async diffPackages(file: string, hasChanges: boolean, baseVersion: string, jsonResponse: boolean): Promise<void> {
if (hasChanges) {
await this.hasChanges(file, jsonResponse);
await this.hasChanges(baseVersion, file, jsonResponse);
} else {
await this.diffPackagesAndReturnDiff(file, jsonResponse);
await this.diffPackagesAndReturnDiff(baseVersion, file, jsonResponse);
}
}

private async hasChanges(file: string, jsonResponse: boolean): Promise<void> {
private async hasChanges(baseVersion: string, file: string, jsonResponse: boolean): Promise<void> {
const packages = new AdmZip(file);
const formData = this.buildBodyForDiff(packages);
const returnedHasChangesData = await this.diffApi.hasChanges(formData);
const returnedHasChangesData = await this.diffApi.hasChanges(baseVersion, formData);

if (jsonResponse) {
this.exportListOfPackageDiffMetadata(returnedHasChangesData);
Expand All @@ -36,10 +36,10 @@ export class DiffService {
}
}

private async diffPackagesAndReturnDiff(file: string, jsonResponse: boolean): Promise<void> {
private async diffPackagesAndReturnDiff(baseVersion: string, file: string, jsonResponse: boolean): Promise<void> {
const packages = new AdmZip(file);
const formData = this.buildBodyForDiff(packages);
const returnedHasChangesData = await this.diffApi.diffPackages(formData);
const returnedHasChangesData = await this.diffApi.diffPackages(baseVersion, formData);

if (jsonResponse) {
this.exportListOfPackageDiffs(returnedHasChangesData);
Expand Down
3 changes: 2 additions & 1 deletion src/commands/configuration-management/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ class Module extends IModule {
configCommand.command("diff")
.description("Command to diff configs of packages")
.option("--hasChanges", "Flag to return only the information if the package has changes without the actual changes")
.option("--baseVersion <version>", "Compare against a given version or STAGING, not compatible with --hasChanges")
.option("--json", "Return the response as a JSON file")
.requiredOption("-f, --file <file>", "Exported packages file (relative or absolute path)")
.action(this.diffPackages);
Expand Down Expand Up @@ -251,7 +252,7 @@ class Module extends IModule {
}

private async diffPackages(context: Context, command: Command, options: OptionValues): Promise<void> {
await new ConfigCommandService(context).diffPackages(options.file, options.hasChanges, options.json);
await new ConfigCommandService(context).diffPackages(options.file, options.hasChanges, options.baseVersion, options.json);
}

private async validatePackage(context: Context, command: Command, options: OptionValues): Promise<void> {
Expand Down
132 changes: 56 additions & 76 deletions tests/commands/configuration-management/config-diff.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,47 @@ import { loggingTestTransport, mockWriteFileSync } from "../../jest.setup";
import { FileService } from "../../../src/core/utils/file-service";
import { ConfigUtils } from "../../utls/config-utils";

function mockZipDiff(expectedUrl: string): PackageDiffTransport[] {
const manifest: PackageManifestTransport[] = [];
manifest.push(ConfigUtils.buildManifestForKeyAndFlavor("package-key", "STUDIO"));

const firstPackageNode = ConfigUtils.buildPackageNode("package-key", {metadata: {description: "test"}, variables: [], dependencies: []});
const firstChildNode = ConfigUtils.buildChildNode("key-1", "package-key", "TEST");
const firstPackageZip = ConfigUtils.buildExportPackageZip(firstPackageNode, [firstChildNode], "1.0.0");
const exportedPackagesZip = ConfigUtils.buildBatchExportZip(manifest, [firstPackageZip]);

mockReadFileSync(exportedPackagesZip.toBuffer());
mockCreateReadStream(exportedPackagesZip.toBuffer());

const diffResponse: PackageDiffTransport[] = [{
packageKey: "package-key",
packageChanges: [
{
op: "add",
path: "/test",
from: "bbbb",
value: JSON.parse("123"),
fromValue: null
}],
nodesWithChanges: [{
nodeKey: firstChildNode.key,
name: firstChildNode.name,
type: firstChildNode.type,
changeType: NodeConfigurationChangeType.ADDED,
changes: [{
op: "add",
path: "/test",
from: "bbb",
value: JSON.parse("234"),
fromValue: null
}]
}]
}];

mockAxiosPost(expectedUrl, diffResponse);
return diffResponse;
}

describe("Config diff", () => {

beforeEach(() => {
Expand All @@ -40,7 +81,7 @@ describe("Config diff", () => {

mockAxiosPost("https://myTeam.celonis.cloud/package-manager/api/core/packages/diff/configuration/has-changes", diffResponse);

await new ConfigCommandService(testContext).diffPackages("./packages.zip", true, false);
await new ConfigCommandService(testContext).diffPackages("./packages.zip", true, null, false);

expect(loggingTestTransport.logMessages.length).toBe(1);
expect(loggingTestTransport.logMessages[0].message).toContain(
Expand All @@ -49,45 +90,20 @@ describe("Config diff", () => {
});

it("Should show diff on terminal with hasChanges set to false and jsonResponse false", async () => {
const manifest: PackageManifestTransport[] = [];
manifest.push(ConfigUtils.buildManifestForKeyAndFlavor("package-key", "STUDIO"));

const firstPackageNode = ConfigUtils.buildPackageNode("package-key", {metadata: {description: "test"}, variables: [], dependencies: []});
const firstChildNode = ConfigUtils.buildChildNode("key-1", "package-key", "TEST");
const firstPackageZip = ConfigUtils.buildExportPackageZip(firstPackageNode, [firstChildNode], "1.0.0");
const exportedPackagesZip = ConfigUtils.buildBatchExportZip(manifest, [firstPackageZip]);
const diffResponse = mockZipDiff("https://myTeam.celonis.cloud/package-manager/api/core/packages/diff/configuration");

mockReadFileSync(exportedPackagesZip.toBuffer());
mockCreateReadStream(exportedPackagesZip.toBuffer());
await new ConfigCommandService(testContext).diffPackages("./packages.zip", false, null, false);

const diffResponse: PackageDiffTransport[] = [{
packageKey: "package-key",
packageChanges: [
{
op: "add",
path: "/test",
from: "bbbb",
value: JSON.parse("123"),
fromValue: null
}],
nodesWithChanges: [{
nodeKey: firstChildNode.key,
name: firstChildNode.name,
type: firstChildNode.type,
changeType: NodeConfigurationChangeType.ADDED,
changes: [{
op: "add",
path: "/test",
from: "bbb",
value: JSON.parse("234"),
fromValue: null
}]
}]
}];
expect(loggingTestTransport.logMessages.length).toBe(1);
expect(loggingTestTransport.logMessages[0].message).toContain(
JSON.stringify(diffResponse, null, 2)
);
});

mockAxiosPost("https://myTeam.celonis.cloud/package-manager/api/core/packages/diff/configuration", diffResponse);
it("Should compare with specified version", async () => {
const diffResponse = mockZipDiff("https://myTeam.celonis.cloud/package-manager/api/core/packages/diff/configuration?baseVersion=1.0.0");

await new ConfigCommandService(testContext).diffPackages("./packages.zip", false, false);
await new ConfigCommandService(testContext).diffPackages("./packages.zip", false, "1.0.0", false);

expect(loggingTestTransport.logMessages.length).toBe(1);
expect(loggingTestTransport.logMessages[0].message).toContain(
Expand All @@ -96,53 +112,17 @@ describe("Config diff", () => {
});

it("Should generate a json file with diff info when hasChanges is set to false and jsonResponse is set to true", async () => {
const manifest: PackageManifestTransport[] = [];
manifest.push(ConfigUtils.buildManifestForKeyAndFlavor("package-key", "STUDIO"));

const firstPackageNode = ConfigUtils.buildPackageNode("package-key", {metadata: {description: "test"}, variables: [], dependencies: []});
const firstChildNode = ConfigUtils.buildChildNode("key-1", "package-key", "TEST");
const firstPackageZip = ConfigUtils.buildExportPackageZip(firstPackageNode, [firstChildNode], "1.0.0");
const exportedPackagesZip = ConfigUtils.buildBatchExportZip(manifest, [firstPackageZip]);
const diffResponse = mockZipDiff("https://myTeam.celonis.cloud/package-manager/api/core/packages/diff/configuration");

mockReadFileSync(exportedPackagesZip.toBuffer());
mockCreateReadStream(exportedPackagesZip.toBuffer());

const diffResponse: PackageDiffTransport[] = [{
packageKey: "package-key",
packageChanges: [
{
op: "add",
path: "/test",
from: "bbbb",
value: JSON.parse("123"),
fromValue: null
}],
nodesWithChanges: [{
nodeKey: firstChildNode.key,
name: firstChildNode.name,
type: firstChildNode.type,
changeType: NodeConfigurationChangeType.ADDED,
changes: [{
op: "add",
path: "/test",
from: "bbb",
value: JSON.parse("234"),
fromValue: null
}]
}]
}];

mockAxiosPost("https://myTeam.celonis.cloud/package-manager/api/core/packages/diff/configuration", diffResponse);

await new ConfigCommandService(testContext).diffPackages("./packages.zip", false, true);
await new ConfigCommandService(testContext).diffPackages("./packages.zip", false, null, 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", mode: 0o600});
const exportedPackageDiffTransport = JSON.parse(mockWriteFileSync.mock.calls[0][1]) as PackageDiffTransport[];
expect(exportedPackageDiffTransport.length).toBe(1);

const exportedFirstPackageDiffTransport = exportedPackageDiffTransport.filter(diffTransport => diffTransport.packageKey === firstPackageNode.key);
const exportedFirstPackageDiffTransport = exportedPackageDiffTransport.filter(diffTransport => diffTransport.packageKey === "package-key");
expect(exportedFirstPackageDiffTransport).toEqual(diffResponse);
});

Expand All @@ -165,7 +145,7 @@ describe("Config diff", () => {

mockAxiosPost("https://myTeam.celonis.cloud/package-manager/api/core/packages/diff/configuration/has-changes", diffResponse);

await new ConfigCommandService(testContext).diffPackages("./packages.zip", true, true);
await new ConfigCommandService(testContext).diffPackages("./packages.zip", true, null, true);

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

Expand Down
68 changes: 68 additions & 0 deletions tests/commands/configuration-management/module.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ describe("Configuration Management Module - Action Validations", () => {
listVariables: jest.fn().mockResolvedValue(undefined),
batchExportPackages: jest.fn().mockResolvedValue(undefined),
batchImportPackages: jest.fn().mockResolvedValue(undefined),
diffPackages: jest.fn().mockResolvedValue(undefined),
} as any;

mockNodeDependencyService = {
Expand Down Expand Up @@ -769,5 +770,72 @@ describe("Configuration Management Module - Action Validations", () => {
);
});
});

describe("diffPackages", () => {
it("should call diffPackages using minimal parameters", async () => {
const options: OptionValues = {
file: "package.zip",
};

await (module as any).diffPackages(testContext, mockCommand, options);

expect(mockConfigCommandService.diffPackages).toHaveBeenCalledWith(
"package.zip", undefined, undefined, undefined
);
});

it("should pass json parameter", async () => {
const options: OptionValues = {
file: "package.zip",
json: true,
};

await (module as any).diffPackages(testContext, mockCommand, options);

expect(mockConfigCommandService.diffPackages).toHaveBeenCalledWith(
"package.zip", undefined, undefined, true
);
});

it("should pass hasChanges parameter", async () => {
const options: OptionValues = {
file: "package.zip",
hasChanges: true,
};

await (module as any).diffPackages(testContext, mockCommand, options);

expect(mockConfigCommandService.diffPackages).toHaveBeenCalledWith(
"package.zip", true, undefined, undefined
);
});

it("should pass baseVersion parameter", async () => {
const options: OptionValues = {
file: "package.zip",
baseVersion: "1.0.0",
};

await (module as any).diffPackages(testContext, mockCommand, options);

expect(mockConfigCommandService.diffPackages).toHaveBeenCalledWith(
"package.zip", undefined, "1.0.0", undefined
);
});

it("should pass both parameters when hasChanges and baseVersion are used together", async () => {
const options: OptionValues = {
file: "package.zip",
hasChanges: true,
baseVersion: "STAGING"
};

await (module as any).diffPackages(testContext, mockCommand, options);

expect(mockConfigCommandService.diffPackages).toHaveBeenCalledWith(
"package.zip", true, "STAGING", undefined
);
});
});
});

Loading