From 556a1789ef6279951f17699d894149286e31a1c4 Mon Sep 17 00:00:00 2001 From: Jing Sun Date: Tue, 12 May 2026 13:48:53 +0200 Subject: [PATCH 1/5] add staging parameter to config list --- .../api/batch-import-export-api.ts | 13 +++- .../batch-import-export.service.ts | 11 ++++ .../config-command.service.ts | 20 ++++-- .../configuration-management/module.ts | 9 ++- .../config-list.spec.ts | 64 ++++++++++++++++--- 5 files changed, 98 insertions(+), 19 deletions(-) diff --git a/src/commands/configuration-management/api/batch-import-export-api.ts b/src/commands/configuration-management/api/batch-import-export-api.ts index c485859..f62805a 100644 --- a/src/commands/configuration-management/api/batch-import-export-api.ts +++ b/src/commands/configuration-management/api/batch-import-export-api.ts @@ -21,13 +21,24 @@ export class BatchImportExportApi { queryParams.set("withDependencies", withDependencies.toString()); queryParams.set("includeBranches", includeBranches.toString()); - flavors.forEach(flavor => queryParams.append("flavors", flavor)) + flavors.forEach(flavor => queryParams.append("flavors", flavor)); return this.httpClient().get(`/package-manager/api/core/packages/export/list?${queryParams.toString()}`).catch(e => { throw new FatalError(`Problem getting active packages: ${e}`); }); } + public async findAllStagingPackages(flavors: string[], includeBranches: boolean = false): Promise { + const queryParams = new URLSearchParams(); + + queryParams.set("includeBranches", includeBranches.toString()); + flavors.forEach(flavor => queryParams.append("flavors", flavor)); + + return this.httpClient().get(`/package-manager/api/core/staging/packages/export/list?${queryParams.toString()}`).catch(e => { + throw new FatalError(`Problem getting staging packages: ${e}`); + }); + } + public async findActivePackagesByVariableValue(flavors: string[], variableValue: string, variableType: string, includeBranches: boolean = false): Promise { const queryParams = new URLSearchParams(); diff --git a/src/commands/configuration-management/batch-import-export.service.ts b/src/commands/configuration-management/batch-import-export.service.ts index a9bd5ce..037daf1 100644 --- a/src/commands/configuration-management/batch-import-export.service.ts +++ b/src/commands/configuration-management/batch-import-export.service.ts @@ -42,6 +42,17 @@ export class BatchImportExportService { }); } + public async listStagingPackages(flavors: string[], includeBranches: boolean, jsonResponse: boolean): Promise { + const stagingPackages = await this.batchImportExportApi.findAllStagingPackages(flavors, includeBranches); + if (jsonResponse) { + this.exportListOfPackages(stagingPackages); + } else { + stagingPackages.forEach(pkg => { + logger.info(`${pkg.name} - Key: "${pkg.key}"`); + }); + } + } + public async findAndExportListOfPackages(flavors: string[], packageKeys: string[], keysByVersion: string[], withDependencies: boolean, includeBranches: boolean): Promise { let packagesToExport: PackageExportTransport[]; diff --git a/src/commands/configuration-management/config-command.service.ts b/src/commands/configuration-management/config-command.service.ts index ee0a31a..b319be4 100644 --- a/src/commands/configuration-management/config-command.service.ts +++ b/src/commands/configuration-management/config-command.service.ts @@ -16,13 +16,21 @@ export class ConfigCommandService { this.diffService = new DiffService(context); } - public async listPackages(jsonResponse: boolean, flavors: string[], withDependencies: boolean, packageKeys: string[], keysByVersion: string[], variableValue: string, variableType: string, includeBranches: boolean): Promise { - if (variableValue) { + public async listPackages( + jsonResponse: boolean, + flavors: string[], + withDependencies: boolean, + packageKeys: string[], + keysByVersion: string[], + variableValue: string, + variableType: string, + includeBranches: boolean, + staging: boolean): Promise { + if (staging) { + await this.batchImportExportService.listStagingPackages(flavors ?? [], includeBranches, jsonResponse); + } else if (variableValue) { await this.listPackagesByVariableValue(jsonResponse, flavors, variableValue, variableType, includeBranches); - return; - } - - if (jsonResponse) { + } else if (jsonResponse) { await this.batchImportExportService.findAndExportListOfPackages(flavors ?? [], packageKeys ?? [], keysByVersion ?? [], withDependencies, includeBranches); } else if (keysByVersion) { await this.batchImportExportService.listPackagesByKeysWithVersion(keysByVersion, withDependencies); diff --git a/src/commands/configuration-management/module.ts b/src/commands/configuration-management/module.ts index 00aaf7f..8a80e0c 100644 --- a/src/commands/configuration-management/module.ts +++ b/src/commands/configuration-management/module.ts @@ -20,13 +20,14 @@ class Module extends IModule { configCommand.command("list") .description("Command to list packages") .option("--json", "Return response as json type", "") - .option("--flavors ", "Lists only active packages of the given flavors") + .option("--flavors ", "Lists only packages of the given flavors") .option("--withDependencies", "Include dependencies", "") .option("--packageKeys ", "Lists only active versions of given package keys") .option("--keysByVersion ", "Lists packages by given key and version [packageKey.version]") .option("--variableValue ", "Variable value for filtering packages by.") .option("--variableType ", "Variable type for filtering packages by.") .option("--branches", "Include branches", false) + .option("--staging", "List staging packages instead", false) .action(this.listPackages); configCommand.command("export") @@ -183,6 +184,9 @@ class Module extends IModule { } private async listPackages(context: Context, command: Command, options: OptionValues): Promise { + if (options.staging && (options.withDependencies || options.packageKeys || options.keysByVersion || options.variableValue || options.variableType)) { + throw new Error("Staging parameter is not compatible with --withDependencies, --packageKeys, --keysByVersion, --variableValue, --variableType"); + } if (options.packageKeys && options.keysByVersion) { throw new Error("Please provide either --packageKeys or --keysByVersion, but not both."); } @@ -195,7 +199,8 @@ class Module extends IModule { options.keysByVersion, options.variableValue, options.variableType, - options.branches); + options.branches, + options.staging); } private async batchExportPackages(context: Context, command: Command, options: OptionValues): Promise { diff --git a/tests/commands/configuration-management/config-list.spec.ts b/tests/commands/configuration-management/config-list.spec.ts index 53e93a0..1f6fa79 100644 --- a/tests/commands/configuration-management/config-list.spec.ts +++ b/tests/commands/configuration-management/config-list.spec.ts @@ -33,7 +33,7 @@ describe("Config list", () => { mockAxiosGet("https://myTeam.celonis.cloud/package-manager/api/core/packages/export/list?" + urlParams.toString(), [firstPackage, secondPackage]); - await new ConfigCommandService(testContext).listPackages(false, flavorsArray, false, [], undefined, null, null, false); + await new ConfigCommandService(testContext).listPackages(false, flavorsArray, false, [], undefined, null, null, false, false); expect(loggingTestTransport.logMessages.length).toBe(2); expect(loggingTestTransport.logMessages[0].message).toContain(`${firstPackage.name} - Key: "${firstPackage.key}"`); @@ -50,7 +50,7 @@ describe("Config list", () => { mockAxiosGet("https://myTeam.celonis.cloud/package-manager/api/core/packages/export/list?withDependencies=false&includeBranches=false", [{...firstPackage}, {...secondPackage}]); mockAxiosGet("https://myTeam.celonis.cloud/package-manager/api/packages/with-variable-assignments?type=DATA_MODEL", [studioPackage]); - await new ConfigCommandService(testContext).listPackages(true, [], false, [], undefined, null, null, false); + await new ConfigCommandService(testContext).listPackages(true, [], false, [], undefined, null, null, false, false); const expectedFileName = loggingTestTransport.logMessages[0].message.split(FileService.fileDownloadedMessage)[1]; @@ -96,7 +96,7 @@ describe("Config list", () => { }; mockAxiosGet("https://myTeam.celonis.cloud/package-manager/api/compute-pools/data-models/details", [dataModelDetailResponse]); - await new ConfigCommandService(testContext).listPackages(true, [], true, [], undefined, null, null, false); + await new ConfigCommandService(testContext).listPackages(true, [], true, [], undefined, null, null, false, false); const expectedFileName = loggingTestTransport.logMessages[0].message.split(FileService.fileDownloadedMessage)[1]; @@ -121,7 +121,7 @@ describe("Config list", () => { mockAxiosGet("https://myTeam.celonis.cloud/package-manager/api/core/packages/export/list-by-keys?packageKeys=key-1&packageKeys=key-2&withDependencies=false", [{...firstPackage}, {...secondPackage}]); mockAxiosGet("https://myTeam.celonis.cloud/package-manager/api/packages/with-variable-assignments?type=DATA_MODEL", [studioPackage]); - await new ConfigCommandService(testContext).listPackages(true, [], false, [firstPackage.key, secondPackage.key], undefined, null, null, false); + await new ConfigCommandService(testContext).listPackages(true, [], false, [firstPackage.key, secondPackage.key], undefined, null, null, false, false); const expectedFileName = loggingTestTransport.logMessages[0].message.split(FileService.fileDownloadedMessage)[1]; @@ -167,7 +167,7 @@ describe("Config list", () => { }; mockAxiosGet("https://myTeam.celonis.cloud/package-manager/api/compute-pools/data-models/details", [dataModelDetailResponse]); - await new ConfigCommandService(testContext).listPackages(true, [], true, [firstPackage.key, secondPackage.key], undefined, null, null, false); + await new ConfigCommandService(testContext).listPackages(true, [], true, [firstPackage.key, secondPackage.key], undefined, null, null, false, false); const expectedFileName = loggingTestTransport.logMessages[0].message.split(FileService.fileDownloadedMessage)[1]; @@ -191,7 +191,7 @@ describe("Config list", () => { mockAxiosGet("https://myTeam.celonis.cloud/package-manager/api/core/packages/export/list-by-variable-value?variableValue=1&includeBranches=false", [{...firstPackage}, {...secondPackage}]); mockAxiosGet("https://myTeam.celonis.cloud/package-manager/api/packages/with-variable-assignments?type=DATA_MODEL", []); - await new ConfigCommandService(testContext).listPackages(false, [], false, [], undefined, "1", null, false); + await new ConfigCommandService(testContext).listPackages(false, [], false, [], undefined, "1", null, false, false); expect(loggingTestTransport.logMessages.length).toBe(2); expect(loggingTestTransport.logMessages[0].message).toContain(`${firstPackage.name} - Key: "${firstPackage.key}"`); @@ -205,7 +205,7 @@ describe("Config list", () => { mockAxiosGet("https://myTeam.celonis.cloud/package-manager/api/core/packages/export/list-by-variable-value?variableValue=1&includeBranches=false", [{...firstPackage}, {...secondPackage}]); mockAxiosGet("https://myTeam.celonis.cloud/package-manager/api/packages/with-variable-assignments?type=DATA_MODEL", []); - await new ConfigCommandService(testContext).listPackages(true, [], false, [], undefined, "1", null, false); + await new ConfigCommandService(testContext).listPackages(true, [], false, [], undefined, "1", null, false, false); const expectedFileName = loggingTestTransport.logMessages[0].message.split(FileService.fileDownloadedMessage)[1]; @@ -225,7 +225,7 @@ describe("Config list", () => { [firstPackage, secondPackage] ); - await new ConfigCommandService(testContext).listPackages(false, [], false, undefined, keysByVersion, null, null, false); + await new ConfigCommandService(testContext).listPackages(false, [], false, undefined, keysByVersion, null, null, false, false); expect(loggingTestTransport.logMessages.length).toBe(2); expect(loggingTestTransport.logMessages[0].message).toContain(`${firstPackage.name} - Key: "${firstPackage.key}"`); @@ -242,7 +242,7 @@ describe("Config list", () => { [firstPackage, secondPackage] ); - await new ConfigCommandService(testContext).listPackages(false, [], true, undefined, keysByVersion, null, null, false); + await new ConfigCommandService(testContext).listPackages(false, [], true, undefined, keysByVersion, null, null, false, false); expect(loggingTestTransport.logMessages.length).toBe(2); }) @@ -260,7 +260,7 @@ describe("Config list", () => { ); mockAxiosGet("https://myTeam.celonis.cloud/package-manager/api/packages/with-variable-assignments?type=DATA_MODEL", [studioPackage]); - await new ConfigCommandService(testContext).listPackages(true, [], false, [], keysByVersion, null, null, false); + await new ConfigCommandService(testContext).listPackages(true, [], false, [], keysByVersion, null, null, false, false); const expectedFileName = loggingTestTransport.logMessages[0].message.split(FileService.fileDownloadedMessage)[1]; @@ -275,4 +275,48 @@ describe("Config list", () => { expect(exportedSecondPackage).toEqual(secondPackage); expect(exportedFirstPackage).toEqual({...firstPackage, spaceId: "spaceId-1"}); }) + + it("Should list all staging packages by key for non-json response with flavors", async () => { + const firstPackage = PacmanApiUtils.buildPackageExportTransport("studio", "name-1"); + const secondPackage = PacmanApiUtils.buildPackageExportTransport("ocdm", "name-2"); + + const urlParams = new URLSearchParams(); + urlParams.set("includeBranches", "false"); + urlParams.append("flavors", "STUDIO"); + + mockAxiosGet("https://myTeam.celonis.cloud/package-manager/api/core/staging/packages/export/list?" + urlParams.toString(), [firstPackage, secondPackage]); + + await new ConfigCommandService(testContext).listPackages(false, ["STUDIO"], false, [], undefined, null, null, false, true); + + expect(loggingTestTransport.logMessages.length).toBe(2); + expect(loggingTestTransport.logMessages[0].message).toContain(`${firstPackage.name} - Key: "${firstPackage.key}"`); + expect(loggingTestTransport.logMessages[1].message).toContain(`${secondPackage.name} - Key: "${secondPackage.key}"`); + }) + + it("Should list all staging packages by key for json response with flavors", async () => { + const firstPackage = PacmanApiUtils.buildPackageExportTransport("studio", "name-1"); + const secondPackage = PacmanApiUtils.buildPackageExportTransport("ocdm", "name-2"); + + const urlParams = new URLSearchParams(); + urlParams.set("includeBranches", "false"); + urlParams.append("flavors", "STUDIO"); + + mockAxiosGet("https://myTeam.celonis.cloud/package-manager/api/core/staging/packages/export/list?" + urlParams.toString(), [firstPackage, secondPackage]); + + await new ConfigCommandService(testContext).listPackages(true, ["STUDIO"], false, [], undefined, null, null, false, 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 exportedTransports = JSON.parse(mockWriteFileSync.mock.calls[0][1]) as PackageExportTransport[]; + expect(exportedTransports.length).toBe(2); + + const exportedFirstPackage = exportedTransports.find(transport => transport.key === firstPackage.key); + const exportedSecondPackage = exportedTransports.find(transport => transport.key === secondPackage.key); + + expect(exportedSecondPackage).toEqual(secondPackage); + expect(exportedFirstPackage).toEqual(firstPackage); + + }) }) \ No newline at end of file From a7e17bdb2d88862a7a2028ea870230f45015301e Mon Sep 17 00:00:00 2001 From: Jing Sun Date: Tue, 12 May 2026 14:05:08 +0200 Subject: [PATCH 2/5] update docs --- docs/user-guide/config-commands.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/user-guide/config-commands.md b/docs/user-guide/config-commands.md index b89d7c3..a5ac95b 100644 --- a/docs/user-guide/config-commands.md +++ b/docs/user-guide/config-commands.md @@ -28,7 +28,9 @@ The name of the file will be printed in the console with the following format: info: File downloaded successfully. New filename: 9560f81f-f746-4117-83ee-dd1f614ad624.json ``` -By using the --flavors option, you can filter which packages to list. The available flavors are: **STUDIO** and **OCDM**. +By using the `--flavors` option, you can filter which packages to list. The available flavors are: **STUDIO** and **OCDM**. + +To list staging packages instead of deployed packages use the `--staging` option. Please note that this flag is not compatible with the below options. ### List Packages with Dependencies @@ -44,7 +46,7 @@ content-cli config list -p --withDependencies ```bash content-cli config list -p --packageKeys key1 ... keyN -[optional] –withDependencies +[optional] –-withDependencies ``` ## Batch Export Packages From 20a7cba5005c440bb893b60baac52a2611d684f6 Mon Sep 17 00:00:00 2001 From: Jing Sun Date: Tue, 12 May 2026 14:31:34 +0200 Subject: [PATCH 3/5] fix tests --- tests/commands/configuration-management/module.spec.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/commands/configuration-management/module.spec.ts b/tests/commands/configuration-management/module.spec.ts index b87f0a2..65bae0f 100644 --- a/tests/commands/configuration-management/module.spec.ts +++ b/tests/commands/configuration-management/module.spec.ts @@ -73,6 +73,7 @@ describe("Configuration Management Module - Action Validations", () => { undefined, undefined, undefined, + undefined, undefined ); }); @@ -93,6 +94,7 @@ describe("Configuration Management Module - Action Validations", () => { ["package3.1.0.0", "package4.1.0.0"], undefined, undefined, + undefined, undefined ); }); @@ -114,7 +116,8 @@ describe("Configuration Management Module - Action Validations", () => { undefined, undefined, undefined, - true + true, + undefined ); }); }); From ee3a1bf5ae395da6d40b7444df3638d45138c983 Mon Sep 17 00:00:00 2001 From: Jing Sun Date: Tue, 12 May 2026 14:51:21 +0200 Subject: [PATCH 4/5] add tests --- .../configuration-management/module.spec.ts | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/tests/commands/configuration-management/module.spec.ts b/tests/commands/configuration-management/module.spec.ts index 65bae0f..3944134 100644 --- a/tests/commands/configuration-management/module.spec.ts +++ b/tests/commands/configuration-management/module.spec.ts @@ -123,6 +123,85 @@ describe("Configuration Management Module - Action Validations", () => { }); }); + describe("listStagingPackages validation", () => { + it("should pass validation when branches is provided", async () => { + const options: OptionValues = { + branches: true, + json: true, + staging: true, + }; + + await (module as any).listPackages(testContext, mockCommand, options); + + expect(mockConfigCommandService.listPackages).toHaveBeenCalledWith( + true, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + true, + true + ); + }); + + it("should throw error when packageKeys is provided", async () => { + const options: OptionValues = { + packageKeys: ["package1", "package2"], + staging: true, + }; + + await expect( + (module as any).listPackages(testContext, mockCommand, options) + ).rejects.toThrow("Staging parameter is not compatible with --withDependencies, --packageKeys, --keysByVersion, --variableValue, --variableType"); + }); + + it("should throw error when withDependencies is provided", async () => { + const options: OptionValues = { + withDependencies: true, + staging: true, + }; + + await expect( + (module as any).listPackages(testContext, mockCommand, options) + ).rejects.toThrow("Staging parameter is not compatible with --withDependencies, --packageKeys, --keysByVersion, --variableValue, --variableType"); + }); + + it("should throw error when keysByVersion is provided", async () => { + const options: OptionValues = { + keysByVersion: ["package3.1.0.0", "package4.1.0.0"], + staging: true, + }; + + await expect( + (module as any).listPackages(testContext, mockCommand, options) + ).rejects.toThrow("Staging parameter is not compatible with --withDependencies, --packageKeys, --keysByVersion, --variableValue, --variableType"); + }); + + it("should throw error when variableValue is provided", async () => { + const options: OptionValues = { + variableValue: "myValue", + staging: true, + }; + + await expect( + (module as any).listPackages(testContext, mockCommand, options) + ).rejects.toThrow("Staging parameter is not compatible with --withDependencies, --packageKeys, --keysByVersion, --variableValue, --variableType"); + }); + + it("should throw error when variableType is provided", async () => { + const options: OptionValues = { + variableType: "myType", + staging: true, + }; + + await expect( + (module as any).listPackages(testContext, mockCommand, options) + ).rejects.toThrow("Staging parameter is not compatible with --withDependencies, --packageKeys, --keysByVersion, --variableValue, --variableType"); + }); + }); + describe("batchExportPackages validation", () => { describe("packageKeys and keysByVersion validation", () => { it("should throw error when both packageKeys and keysByVersion are provided", async () => { From c24434d1ec48d96f9dce4c0e98017346aec59249 Mon Sep 17 00:00:00 2001 From: Jing Sun Date: Tue, 12 May 2026 15:14:00 +0200 Subject: [PATCH 5/5] add test listing staging packages without flavors --- .../configuration-management/config-list.spec.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/commands/configuration-management/config-list.spec.ts b/tests/commands/configuration-management/config-list.spec.ts index 1f6fa79..8649c69 100644 --- a/tests/commands/configuration-management/config-list.spec.ts +++ b/tests/commands/configuration-management/config-list.spec.ts @@ -293,6 +293,22 @@ describe("Config list", () => { expect(loggingTestTransport.logMessages[1].message).toContain(`${secondPackage.name} - Key: "${secondPackage.key}"`); }) + it("Should list all staging packages by key for non-json response without flavors", async () => { + const firstPackage = PacmanApiUtils.buildPackageExportTransport("studio", "name-1"); + const secondPackage = PacmanApiUtils.buildPackageExportTransport("ocdm", "name-2"); + + const urlParams = new URLSearchParams(); + urlParams.set("includeBranches", "false"); + + mockAxiosGet("https://myTeam.celonis.cloud/package-manager/api/core/staging/packages/export/list?" + urlParams.toString(), [firstPackage, secondPackage]); + + await new ConfigCommandService(testContext).listPackages(false, null, false, [], undefined, null, null, false, true); + + expect(loggingTestTransport.logMessages.length).toBe(2); + expect(loggingTestTransport.logMessages[0].message).toContain(`${firstPackage.name} - Key: "${firstPackage.key}"`); + expect(loggingTestTransport.logMessages[1].message).toContain(`${secondPackage.name} - Key: "${secondPackage.key}"`); + }) + it("Should list all staging packages by key for json response with flavors", async () => { const firstPackage = PacmanApiUtils.buildPackageExportTransport("studio", "name-1"); const secondPackage = PacmanApiUtils.buildPackageExportTransport("ocdm", "name-2");