From 63eeb6cf6cb805e15c31d09f5de93eba6e6a8bae Mon Sep 17 00:00:00 2001 From: Patrick Vicinus Date: Thu, 19 Mar 2026 11:36:56 +0100 Subject: [PATCH 01/13] Initial Commit [TA-5049] --- docs/user-guide/config-commands.md | 10 +++ .../api/staging-package-variables-api.ts | 37 +++++++++++ .../config-command.service.ts | 38 +++++++++++ .../interfaces/package-export.interfaces.ts | 5 ++ .../configuration-management/module.ts | 11 ++++ .../variable.service.ts | 29 ++++++++- .../config-list-variables.spec.ts | 64 ++++++++++++++++++- 7 files changed, 192 insertions(+), 2 deletions(-) create mode 100644 src/commands/configuration-management/api/staging-package-variables-api.ts diff --git a/docs/user-guide/config-commands.md b/docs/user-guide/config-commands.md index 45933482..3cb8ca3c 100644 --- a/docs/user-guide/config-commands.md +++ b/docs/user-guide/config-commands.md @@ -164,6 +164,16 @@ export interface PackageKeyAndVersionPair { Similar to the other listing commands, the --json option can be used for exporting (saving) the result as a json file. +### Listing staging package variables + +To list **staging** (unpublished) variables from Pacman’s public API (per package key), use `--staging` with `--packageKeys`: + +```bash +content-cli config variables list -p --staging --packageKeys [ ...] +``` + +Optional `--variableType` filters by variable type (Pacman query parameter `type`). With `--json`, the CLI writes one JSON file containing an array of `{ "packageKey", "variables" }` objects (one entry per requested package). + ### Listing Assignments By using the list assignments command, possible assignment values for the target team can be fetched for each variable type. The list assignments command has the following format: diff --git a/src/commands/configuration-management/api/staging-package-variables-api.ts b/src/commands/configuration-management/api/staging-package-variables-api.ts new file mode 100644 index 00000000..b881aca9 --- /dev/null +++ b/src/commands/configuration-management/api/staging-package-variables-api.ts @@ -0,0 +1,37 @@ +import { Context } from "../../../core/command/cli-context"; +import { FatalError } from "../../../core/utils/logger"; +import { HttpClient } from "../../../core/http/http-client"; +import { StagingVariableManifestTransport } from "../interfaces/package-export.interfaces"; + +/** Staging variable row from Pacman (VariableTransport). */ +export interface StagingPackageVariableTransport { + key: string; + type: string; + value?: unknown; + metadata?: unknown; +} + +export class StagingPackageVariablesApi { + private httpClient: () => HttpClient; + + constructor(context: Context) { + this.httpClient = () => context.httpClient; + } + + /** + * Public Pacman API (TA-5048): staging variables for a package. + */ + public async findAllByPackageKey(packageKey: string, variableType?: string): Promise { + const params = new URLSearchParams(); + if (variableType) { + params.set("type", variableType); + } + const query = params.toString(); + const path = `/pacman/api/core/staging/packages/${encodeURIComponent(packageKey)}/variables${query ? `?${query}` : ""}`; + return await this.httpClient() + .get(path) + .catch((e: unknown) => { + throw new FatalError(`Problem listing staging variables for package '${packageKey}': ${e}`); + }); + } +} diff --git a/src/commands/configuration-management/config-command.service.ts b/src/commands/configuration-management/config-command.service.ts index b0a903f1..62193feb 100644 --- a/src/commands/configuration-management/config-command.service.ts +++ b/src/commands/configuration-management/config-command.service.ts @@ -1,4 +1,5 @@ import { Context } from "../../core/command/cli-context"; +import { FatalError } from "../../core/utils/logger"; import { BatchImportExportService } from "./batch-import-export.service"; import { VariableService } from "./variable.service"; import { DiffService } from "./diff.service"; @@ -39,6 +40,43 @@ export class ConfigCommandService { } } + public async listStagingVariables(jsonResponse: boolean, packageKeys: string[], variableType: string): Promise { + if (jsonResponse) { + await this.variableService.exportStagingVariables(packageKeys, variableType); + } else { + await this.variableService.listStagingVariables(packageKeys, variableType); + } + } + +/* + public async listVariables( + jsonResponse: boolean, + keysByVersion: string[], + keysByVersionFile: string, + staging?: boolean, + packageKeys?: string[], + variableType?: string + ): Promise { + if (staging) { + const keys = packageKeys ?? []; + if (keys.length === 0) { + throw new FatalError("With --staging, provide at least one --packageKeys value."); + } + if ((keysByVersion?.length ?? 0) > 0 || (keysByVersionFile && keysByVersionFile !== "")) { + throw new FatalError("Do not combine --staging with --keysByVersion or --keysByVersionFile."); + } + if (jsonResponse) { + await this.variableService.exportStagingVariables(keys, variableType); + } else { + await this.variableService.listStagingVariables(keys, variableType); + } + return; + } + if ((packageKeys?.length ?? 0) > 0) { + throw new FatalError("--packageKeys is only used together with --staging."); + } +*/ + public batchExportPackages(packageKeys: string[], packageKeysByVersion: string[], withDependencies: boolean, gitBranch: string, unzip: boolean): Promise { return this.batchImportExportService.batchExportPackages(packageKeys, packageKeysByVersion, withDependencies, gitBranch, unzip); } diff --git a/src/commands/configuration-management/interfaces/package-export.interfaces.ts b/src/commands/configuration-management/interfaces/package-export.interfaces.ts index 6e8a52bb..8830bf42 100644 --- a/src/commands/configuration-management/interfaces/package-export.interfaces.ts +++ b/src/commands/configuration-management/interfaces/package-export.interfaces.ts @@ -49,6 +49,11 @@ export interface VariableManifestTransport { variables?: VariableExportTransport[]; } +export interface StagingVariableManifestTransport { + packageKey: string; + variables?: VariableExportTransport[]; +} + export interface PackageKeyAndVersionPair { packageKey: string; version: string; diff --git a/src/commands/configuration-management/module.ts b/src/commands/configuration-management/module.ts index 1d45f7f2..c6c47b0f 100644 --- a/src/commands/configuration-management/module.ts +++ b/src/commands/configuration-management/module.ts @@ -83,6 +83,13 @@ class Module extends IModule { .option("--keysByVersionFile ", "Package keys by version mappings file path.", "") .action(this.listVariables); + variablesCommand.command("listStaging") + .description("List staging (unpublished) variables via Pacman public API") + .option("--json", "Return response as json type", "") + .requiredOption("--packageKeys ", "Package keys") + .option("--variableType ", "Filter staging variables by type", "") + .action(this.listStagingVariables); + const nodesCommand = configCommand.command("nodes") .description("Commands related to nodes of the package"); @@ -175,6 +182,10 @@ class Module extends IModule { await new ConfigCommandService(context).listVariables(options.json, options.keysByVersion, options.keysByVersionFile); } + private async listStagingVariables(context: Context, command: Command, options: OptionValues): Promise { + await new ConfigCommandService(context).listStagingVariables(options.json, options.packageKeys, options.variableType ?? ""); + } + private async listAssignments(context: Context, command: Command, options: OptionValues): Promise { await new VariableCommandService(context).listAssignments(options.type, options.json, options.params); } diff --git a/src/commands/configuration-management/variable.service.ts b/src/commands/configuration-management/variable.service.ts index 77c8b85a..df3b406b 100644 --- a/src/commands/configuration-management/variable.service.ts +++ b/src/commands/configuration-management/variable.service.ts @@ -3,20 +3,23 @@ import { Context } from "../../core/command/cli-context"; import { FatalError, logger } from "../../core/utils/logger"; import { StudioService } from "./studio.service"; import { FileService, fileService } from "../../core/utils/file-service"; -import { PackageKeyAndVersionPair, VariableManifestTransport } from "./interfaces/package-export.interfaces"; +import { PackageKeyAndVersionPair, StagingVariableManifestTransport, VariableManifestTransport } from "./interfaces/package-export.interfaces"; import { BatchImportExportApi } from "./api/batch-import-export-api"; import { URLSearchParams } from "url"; import { VariableAssignmentCandidatesApi } from "./api/variable-assignment-candidates-api"; +import { StagingPackageVariablesApi } from "./api/staging-package-variables-api"; export class VariableService { private batchImportExportApi: BatchImportExportApi; private variableAssignmentCandidatesApi: VariableAssignmentCandidatesApi; + private stagingPackageVariablesApi: StagingPackageVariablesApi; private studioService: StudioService; constructor(context: Context) { this.batchImportExportApi = new BatchImportExportApi(context); this.variableAssignmentCandidatesApi = new VariableAssignmentCandidatesApi(context); + this.stagingPackageVariablesApi = new StagingPackageVariablesApi(context); this.studioService = new StudioService(context); } @@ -50,6 +53,30 @@ export class VariableService { this.exportToJson(variableManifests); } + public async listStagingVariables(packageKeys: string[], variableType: string): Promise { + const byPackage = await this.fetchStagingVariablesByPackageKeys(packageKeys, variableType); + byPackage.forEach(entry => { + logger.info(JSON.stringify(entry)); + }); + } + + public async exportStagingVariables(packageKeys: string[], variableType: string): Promise { + const byPackage = await this.fetchStagingVariablesByPackageKeys(packageKeys, variableType); + this.exportToJson(byPackage); + } + + private async fetchStagingVariablesByPackageKeys( + packageKeys: string[], + variableType: string + ): Promise { + const results: StagingVariableManifestTransport[] = []; + for (const packageKey of packageKeys) { + const variableManifestTransport = await this.stagingPackageVariablesApi.findAllByPackageKey(packageKey, variableType); + results.push( variableManifestTransport ); + } + return results; + } + private async getVersionedVariablesByKeyVersionPairs(keysByVersion: string[], keysByVersionFile: string): Promise { const variablesExportRequest: PackageKeyAndVersionPair[] = await this.buildKeyVersionPairs(keysByVersion, keysByVersionFile); diff --git a/tests/commands/configuration-management/config-list-variables.spec.ts b/tests/commands/configuration-management/config-list-variables.spec.ts index 9c028a92..bf3e6136 100644 --- a/tests/commands/configuration-management/config-list-variables.spec.ts +++ b/tests/commands/configuration-management/config-list-variables.spec.ts @@ -6,7 +6,7 @@ import { VariableManifestTransport, } from "../../../src/commands/configuration-management/interfaces/package-export.interfaces"; import { PackageManagerVariableType } from "../../../src/commands/studio/interfaces/package-manager.interfaces"; -import { mockAxiosPost, mockedPostRequestBodyByUrl } from "../../utls/http-requests-mock"; +import { mockAxiosGet, mockAxiosPost, mockedPostRequestBodyByUrl } from "../../utls/http-requests-mock"; import { ConfigCommandService } from "../../../src/commands/configuration-management/config-command.service"; import { testContext } from "../../utls/test-context"; import { loggingTestTransport, mockWriteFileSync } from "../../jest.setup"; @@ -174,4 +174,66 @@ describe("Config listVariables", () => { expect(e.message).toEqual("Please provide keysByVersion mappings or file path!"); } }) + + const stagingVarsPkgA = [ + { key: "DATA_POOL", type: "SINGLE_VALUE", value: "pool-id-1" }, + { key: "OTHER", type: "CONNECTION", value: { connectionId: "c1" } }, + ]; + const stagingVarsPkgB = [{ key: "DATA_POOL", type: "SINGLE_VALUE", value: "pool-id-2" }]; + + it("Should list staging variables per package via Pacman public API", async () => { + mockAxiosGet( + "https://myTeam.celonis.cloud/pacman/api/core/staging/packages/pkg-a/variables", + stagingVarsPkgA + ); + mockAxiosGet( + "https://myTeam.celonis.cloud/pacman/api/core/staging/packages/pkg-b/variables", + stagingVarsPkgB + ); + + await new ConfigCommandService(testContext).listVariables(false, [], "", true, ["pkg-a", "pkg-b"]); + + expect(loggingTestTransport.logMessages.length).toBe(2); + expect(loggingTestTransport.logMessages[0].message).toContain( + JSON.stringify({ packageKey: "pkg-a", variables: stagingVarsPkgA }) + ); + expect(loggingTestTransport.logMessages[1].message).toContain( + JSON.stringify({ packageKey: "pkg-b", variables: stagingVarsPkgB }) + ); + }); + + it("Should export staging variables as json file", async () => { + mockAxiosGet( + "https://myTeam.celonis.cloud/pacman/api/core/staging/packages/pkg-a/variables?type=SINGLE_VALUE", + [stagingVarsPkgA[0]] + ); + + await new ConfigCommandService(testContext).listVariables(true, [], "", true, ["pkg-a"], "SINGLE_VALUE"); + + expect(loggingTestTransport.logMessages.length).toBe(1); + const expectedFileName = loggingTestTransport.logMessages[0].message.split(FileService.fileDownloadedMessage)[1]; + expect(mockWriteFileSync).toHaveBeenCalledWith( + path.resolve(process.cwd(), expectedFileName), + JSON.stringify([{ packageKey: "pkg-a", variables: [stagingVarsPkgA[0]] }]), + { encoding: "utf-8" } + ); + }); + + it("Should throw when --staging without package keys", async () => { + await expect( + new ConfigCommandService(testContext).listVariables(false, [], "", true, []) + ).rejects.toThrow("With --staging, provide at least one --packageKeys value."); + }); + + it("Should throw when --staging combined with keysByVersion", async () => { + await expect( + new ConfigCommandService(testContext).listVariables(false, ["k:1.0.0"], "", true, ["pkg-a"]) + ).rejects.toThrow("Do not combine --staging with --keysByVersion or --keysByVersionFile."); + }); + + it("Should throw when --packageKeys without --staging", async () => { + await expect( + new ConfigCommandService(testContext).listVariables(false, ["k:1.0.0"], "", false, ["pkg-a"]) + ).rejects.toThrow("--packageKeys is only used together with --staging."); + }); }) \ No newline at end of file From a75ec902ea6136cba76886fbf2d85cb9df4ae172 Mon Sep 17 00:00:00 2001 From: Patrick Vicinus Date: Thu, 19 Mar 2026 11:55:51 +0100 Subject: [PATCH 02/13] Updated documentation and check for empty package-list --- docs/user-guide/config-commands.md | 4 +-- .../api/staging-package-variables-api.ts | 5 +++- .../config-command.service.ts | 29 ------------------- .../variable.service.ts | 4 +++ .../config-list-variables.spec.ts | 20 +++---------- 5 files changed, 14 insertions(+), 48 deletions(-) diff --git a/docs/user-guide/config-commands.md b/docs/user-guide/config-commands.md index 3cb8ca3c..b466f7bd 100644 --- a/docs/user-guide/config-commands.md +++ b/docs/user-guide/config-commands.md @@ -166,10 +166,10 @@ Similar to the other listing commands, the --json option can be used for exporti ### Listing staging package variables -To list **staging** (unpublished) variables from Pacman’s public API (per package key), use `--staging` with `--packageKeys`: +To list **staging** (unpublished) variables from Pacman’s public API (per package key), use the `listStaging` subcommand with `--packageKeys`: ```bash -content-cli config variables list -p --staging --packageKeys [ ...] +content-cli config variables listStaging -p --packageKeys [ ...] ``` Optional `--variableType` filters by variable type (Pacman query parameter `type`). With `--json`, the CLI writes one JSON file containing an array of `{ "packageKey", "variables" }` objects (one entry per requested package). diff --git a/src/commands/configuration-management/api/staging-package-variables-api.ts b/src/commands/configuration-management/api/staging-package-variables-api.ts index b881aca9..00058427 100644 --- a/src/commands/configuration-management/api/staging-package-variables-api.ts +++ b/src/commands/configuration-management/api/staging-package-variables-api.ts @@ -20,6 +20,7 @@ export class StagingPackageVariablesApi { /** * Public Pacman API (TA-5048): staging variables for a package. + * Backend returns an array of VariableTransport; we wrap as { packageKey, variables }. */ public async findAllByPackageKey(packageKey: string, variableType?: string): Promise { const params = new URLSearchParams(); @@ -28,10 +29,12 @@ export class StagingPackageVariablesApi { } const query = params.toString(); const path = `/pacman/api/core/staging/packages/${encodeURIComponent(packageKey)}/variables${query ? `?${query}` : ""}`; - return await this.httpClient() + const response = await this.httpClient() .get(path) .catch((e: unknown) => { throw new FatalError(`Problem listing staging variables for package '${packageKey}': ${e}`); }); + const variables = Array.isArray(response) ? response : (response as StagingVariableManifestTransport).variables ?? []; + return { packageKey, variables }; } } diff --git a/src/commands/configuration-management/config-command.service.ts b/src/commands/configuration-management/config-command.service.ts index 62193feb..b65f207c 100644 --- a/src/commands/configuration-management/config-command.service.ts +++ b/src/commands/configuration-management/config-command.service.ts @@ -48,35 +48,6 @@ export class ConfigCommandService { } } -/* - public async listVariables( - jsonResponse: boolean, - keysByVersion: string[], - keysByVersionFile: string, - staging?: boolean, - packageKeys?: string[], - variableType?: string - ): Promise { - if (staging) { - const keys = packageKeys ?? []; - if (keys.length === 0) { - throw new FatalError("With --staging, provide at least one --packageKeys value."); - } - if ((keysByVersion?.length ?? 0) > 0 || (keysByVersionFile && keysByVersionFile !== "")) { - throw new FatalError("Do not combine --staging with --keysByVersion or --keysByVersionFile."); - } - if (jsonResponse) { - await this.variableService.exportStagingVariables(keys, variableType); - } else { - await this.variableService.listStagingVariables(keys, variableType); - } - return; - } - if ((packageKeys?.length ?? 0) > 0) { - throw new FatalError("--packageKeys is only used together with --staging."); - } -*/ - public batchExportPackages(packageKeys: string[], packageKeysByVersion: string[], withDependencies: boolean, gitBranch: string, unzip: boolean): Promise { return this.batchImportExportService.batchExportPackages(packageKeys, packageKeysByVersion, withDependencies, gitBranch, unzip); } diff --git a/src/commands/configuration-management/variable.service.ts b/src/commands/configuration-management/variable.service.ts index df3b406b..58e0e9f7 100644 --- a/src/commands/configuration-management/variable.service.ts +++ b/src/commands/configuration-management/variable.service.ts @@ -70,6 +70,10 @@ export class VariableService { variableType: string ): Promise { const results: StagingVariableManifestTransport[] = []; + if (packageKeys.length === 0) { + throw new FatalError("Please provide at least one package key!"); + } + for (const packageKey of packageKeys) { const variableManifestTransport = await this.stagingPackageVariablesApi.findAllByPackageKey(packageKey, variableType); results.push( variableManifestTransport ); diff --git a/tests/commands/configuration-management/config-list-variables.spec.ts b/tests/commands/configuration-management/config-list-variables.spec.ts index bf3e6136..0c22ca65 100644 --- a/tests/commands/configuration-management/config-list-variables.spec.ts +++ b/tests/commands/configuration-management/config-list-variables.spec.ts @@ -191,7 +191,7 @@ describe("Config listVariables", () => { stagingVarsPkgB ); - await new ConfigCommandService(testContext).listVariables(false, [], "", true, ["pkg-a", "pkg-b"]); + await new ConfigCommandService(testContext).listStagingVariables(false, ["pkg-a", "pkg-b"], ""); expect(loggingTestTransport.logMessages.length).toBe(2); expect(loggingTestTransport.logMessages[0].message).toContain( @@ -208,7 +208,7 @@ describe("Config listVariables", () => { [stagingVarsPkgA[0]] ); - await new ConfigCommandService(testContext).listVariables(true, [], "", true, ["pkg-a"], "SINGLE_VALUE"); + await new ConfigCommandService(testContext).listStagingVariables(true, ["pkg-a"], "SINGLE_VALUE"); expect(loggingTestTransport.logMessages.length).toBe(1); const expectedFileName = loggingTestTransport.logMessages[0].message.split(FileService.fileDownloadedMessage)[1]; @@ -219,21 +219,9 @@ describe("Config listVariables", () => { ); }); - it("Should throw when --staging without package keys", async () => { + it("Should throw when listStagingVariables called with empty package keys", async () => { await expect( - new ConfigCommandService(testContext).listVariables(false, [], "", true, []) + new ConfigCommandService(testContext).listStagingVariables(false, [], "") ).rejects.toThrow("With --staging, provide at least one --packageKeys value."); }); - - it("Should throw when --staging combined with keysByVersion", async () => { - await expect( - new ConfigCommandService(testContext).listVariables(false, ["k:1.0.0"], "", true, ["pkg-a"]) - ).rejects.toThrow("Do not combine --staging with --keysByVersion or --keysByVersionFile."); - }); - - it("Should throw when --packageKeys without --staging", async () => { - await expect( - new ConfigCommandService(testContext).listVariables(false, ["k:1.0.0"], "", false, ["pkg-a"]) - ).rejects.toThrow("--packageKeys is only used together with --staging."); - }); }) \ No newline at end of file From 57353d2e75e9c324895cf57ec56cbfecbd3dd290 Mon Sep 17 00:00:00 2001 From: Patrick Vicinus Date: Thu, 19 Mar 2026 11:58:08 +0100 Subject: [PATCH 03/13] Fix tests --- .../configuration-management/config-list-variables.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/commands/configuration-management/config-list-variables.spec.ts b/tests/commands/configuration-management/config-list-variables.spec.ts index 0c22ca65..fa46bed7 100644 --- a/tests/commands/configuration-management/config-list-variables.spec.ts +++ b/tests/commands/configuration-management/config-list-variables.spec.ts @@ -222,6 +222,6 @@ describe("Config listVariables", () => { it("Should throw when listStagingVariables called with empty package keys", async () => { await expect( new ConfigCommandService(testContext).listStagingVariables(false, [], "") - ).rejects.toThrow("With --staging, provide at least one --packageKeys value."); + ).rejects.toThrow("Please provide at least one package key!"); }); }) \ No newline at end of file From 660807909899b8b0541d94bf6aa3f59e5be46310 Mon Sep 17 00:00:00 2001 From: Patrick Vicinus Date: Thu, 19 Mar 2026 13:02:12 +0100 Subject: [PATCH 04/13] Change description of listStaging command --- src/commands/configuration-management/module.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/configuration-management/module.ts b/src/commands/configuration-management/module.ts index c6c47b0f..1b1cf993 100644 --- a/src/commands/configuration-management/module.ts +++ b/src/commands/configuration-management/module.ts @@ -84,7 +84,7 @@ class Module extends IModule { .action(this.listVariables); variablesCommand.command("listStaging") - .description("List staging (unpublished) variables via Pacman public API") + .description("Command to list the staging variables of packages") .option("--json", "Return response as json type", "") .requiredOption("--packageKeys ", "Package keys") .option("--variableType ", "Filter staging variables by type", "") From c29e6efbcfd29792b6f1f3c5e8ec630971f1d710 Mon Sep 17 00:00:00 2001 From: Jing Sun Date: Thu, 19 Mar 2026 13:10:14 +0100 Subject: [PATCH 05/13] some cleanup --- .../api/staging-package-variables-api.ts | 16 ++-------------- src/commands/configuration-management/module.ts | 2 +- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/src/commands/configuration-management/api/staging-package-variables-api.ts b/src/commands/configuration-management/api/staging-package-variables-api.ts index 00058427..858f4616 100644 --- a/src/commands/configuration-management/api/staging-package-variables-api.ts +++ b/src/commands/configuration-management/api/staging-package-variables-api.ts @@ -3,14 +3,6 @@ import { FatalError } from "../../../core/utils/logger"; import { HttpClient } from "../../../core/http/http-client"; import { StagingVariableManifestTransport } from "../interfaces/package-export.interfaces"; -/** Staging variable row from Pacman (VariableTransport). */ -export interface StagingPackageVariableTransport { - key: string; - type: string; - value?: unknown; - metadata?: unknown; -} - export class StagingPackageVariablesApi { private httpClient: () => HttpClient; @@ -18,20 +10,16 @@ export class StagingPackageVariablesApi { this.httpClient = () => context.httpClient; } - /** - * Public Pacman API (TA-5048): staging variables for a package. - * Backend returns an array of VariableTransport; we wrap as { packageKey, variables }. - */ public async findAllByPackageKey(packageKey: string, variableType?: string): Promise { const params = new URLSearchParams(); if (variableType) { params.set("type", variableType); } const query = params.toString(); - const path = `/pacman/api/core/staging/packages/${encodeURIComponent(packageKey)}/variables${query ? `?${query}` : ""}`; + const path = `/pacman/api/core/staging/packages/${packageKey}/variables${query ? `?${query}` : ""}`; const response = await this.httpClient() .get(path) - .catch((e: unknown) => { + .catch(e => { throw new FatalError(`Problem listing staging variables for package '${packageKey}': ${e}`); }); const variables = Array.isArray(response) ? response : (response as StagingVariableManifestTransport).variables ?? []; diff --git a/src/commands/configuration-management/module.ts b/src/commands/configuration-management/module.ts index 1b1cf993..58aad0de 100644 --- a/src/commands/configuration-management/module.ts +++ b/src/commands/configuration-management/module.ts @@ -183,7 +183,7 @@ class Module extends IModule { } private async listStagingVariables(context: Context, command: Command, options: OptionValues): Promise { - await new ConfigCommandService(context).listStagingVariables(options.json, options.packageKeys, options.variableType ?? ""); + await new ConfigCommandService(context).listStagingVariables(options.json, options.packageKeys, options.variableType); } private async listAssignments(context: Context, command: Command, options: OptionValues): Promise { From 829ddf63abb97a3fde80e86d7f51a2fd30608ce7 Mon Sep 17 00:00:00 2001 From: Jing Sun Date: Thu, 19 Mar 2026 13:20:10 +0100 Subject: [PATCH 06/13] cleanup --- docs/user-guide/config-commands.md | 3 ++- .../api/staging-package-variables-api.ts | 4 +--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/user-guide/config-commands.md b/docs/user-guide/config-commands.md index b466f7bd..e7a057ab 100644 --- a/docs/user-guide/config-commands.md +++ b/docs/user-guide/config-commands.md @@ -172,7 +172,8 @@ To list **staging** (unpublished) variables from Pacman’s public API (per pack content-cli config variables listStaging -p --packageKeys [ ...] ``` -Optional `--variableType` filters by variable type (Pacman query parameter `type`). With `--json`, the CLI writes one JSON file containing an array of `{ "packageKey", "variables" }` objects (one entry per requested package). +Optional `--variableType` filters by variable type (Pacman query parameter `type`). +The --json option can be used for exporting the result as a json file. ### Listing Assignments diff --git a/src/commands/configuration-management/api/staging-package-variables-api.ts b/src/commands/configuration-management/api/staging-package-variables-api.ts index 858f4616..cb07d6da 100644 --- a/src/commands/configuration-management/api/staging-package-variables-api.ts +++ b/src/commands/configuration-management/api/staging-package-variables-api.ts @@ -17,12 +17,10 @@ export class StagingPackageVariablesApi { } const query = params.toString(); const path = `/pacman/api/core/staging/packages/${packageKey}/variables${query ? `?${query}` : ""}`; - const response = await this.httpClient() + return await this.httpClient() .get(path) .catch(e => { throw new FatalError(`Problem listing staging variables for package '${packageKey}': ${e}`); }); - const variables = Array.isArray(response) ? response : (response as StagingVariableManifestTransport).variables ?? []; - return { packageKey, variables }; } } From dd38c5064c7353b1148fd0f56a6189165d605a69 Mon Sep 17 00:00:00 2001 From: Patrick Vicinus Date: Fri, 20 Mar 2026 10:05:36 +0100 Subject: [PATCH 07/13] chore: doc and tests for staging variable manifest API - Document --json as array of Pacman manifests per package - Mock GET responses as { packageKey, variables } - Remove unused FatalError import; format results.push Includes-AI-Code: true Made-with: Cursor --- docs/user-guide/config-commands.md | 2 +- .../configuration-management/config-command.service.ts | 1 - src/commands/configuration-management/variable.service.ts | 2 +- .../configuration-management/config-list-variables.spec.ts | 6 +++--- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/user-guide/config-commands.md b/docs/user-guide/config-commands.md index e7a057ab..8834c44e 100644 --- a/docs/user-guide/config-commands.md +++ b/docs/user-guide/config-commands.md @@ -173,7 +173,7 @@ content-cli config variables listStaging -p --packageKeys ``` Optional `--variableType` filters by variable type (Pacman query parameter `type`). -The --json option can be used for exporting the result as a json file. +Pacman returns one manifest object per package (`packageKey` and `variables`). With `--json`, the CLI writes a JSON array containing those manifests (one entry per requested package key). ### Listing Assignments diff --git a/src/commands/configuration-management/config-command.service.ts b/src/commands/configuration-management/config-command.service.ts index b65f207c..84db13f7 100644 --- a/src/commands/configuration-management/config-command.service.ts +++ b/src/commands/configuration-management/config-command.service.ts @@ -1,5 +1,4 @@ import { Context } from "../../core/command/cli-context"; -import { FatalError } from "../../core/utils/logger"; import { BatchImportExportService } from "./batch-import-export.service"; import { VariableService } from "./variable.service"; import { DiffService } from "./diff.service"; diff --git a/src/commands/configuration-management/variable.service.ts b/src/commands/configuration-management/variable.service.ts index 58e0e9f7..bc4706f9 100644 --- a/src/commands/configuration-management/variable.service.ts +++ b/src/commands/configuration-management/variable.service.ts @@ -76,7 +76,7 @@ export class VariableService { for (const packageKey of packageKeys) { const variableManifestTransport = await this.stagingPackageVariablesApi.findAllByPackageKey(packageKey, variableType); - results.push( variableManifestTransport ); + results.push(variableManifestTransport); } return results; } diff --git a/tests/commands/configuration-management/config-list-variables.spec.ts b/tests/commands/configuration-management/config-list-variables.spec.ts index fa46bed7..4a530855 100644 --- a/tests/commands/configuration-management/config-list-variables.spec.ts +++ b/tests/commands/configuration-management/config-list-variables.spec.ts @@ -184,11 +184,11 @@ describe("Config listVariables", () => { it("Should list staging variables per package via Pacman public API", async () => { mockAxiosGet( "https://myTeam.celonis.cloud/pacman/api/core/staging/packages/pkg-a/variables", - stagingVarsPkgA + { packageKey: "pkg-a", variables: stagingVarsPkgA } ); mockAxiosGet( "https://myTeam.celonis.cloud/pacman/api/core/staging/packages/pkg-b/variables", - stagingVarsPkgB + { packageKey: "pkg-b", variables: stagingVarsPkgB } ); await new ConfigCommandService(testContext).listStagingVariables(false, ["pkg-a", "pkg-b"], ""); @@ -205,7 +205,7 @@ describe("Config listVariables", () => { it("Should export staging variables as json file", async () => { mockAxiosGet( "https://myTeam.celonis.cloud/pacman/api/core/staging/packages/pkg-a/variables?type=SINGLE_VALUE", - [stagingVarsPkgA[0]] + { packageKey: "pkg-a", variables: [stagingVarsPkgA[0]] } ); await new ConfigCommandService(testContext).listStagingVariables(true, ["pkg-a"], "SINGLE_VALUE"); From b8d339885ec3c0345845594c264c9bb54ed6c63e Mon Sep 17 00:00:00 2001 From: Patrick Vicinus Date: Fri, 20 Mar 2026 13:02:39 +0100 Subject: [PATCH 08/13] TA-5049: Staging package variables listing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use Pacman batch staging-variables API (by-package-keys) for config variables listStaging, with optional variableType filter. Document listStaging in the user guide like other variable listing commands—command-focused, without internal API details. Includes-AI-Code: true Made-with: Cursor --- docs/user-guide/config-commands.md | 7 ++-- .../api/staging-package-variables-api.ts | 13 +++--- .../variable.service.ts | 7 +--- .../config-list-variables.spec.ts | 40 +++++++++---------- 4 files changed, 33 insertions(+), 34 deletions(-) diff --git a/docs/user-guide/config-commands.md b/docs/user-guide/config-commands.md index 8834c44e..617fbbd9 100644 --- a/docs/user-guide/config-commands.md +++ b/docs/user-guide/config-commands.md @@ -166,14 +166,15 @@ Similar to the other listing commands, the --json option can be used for exporti ### Listing staging package variables -To list **staging** (unpublished) variables from Pacman’s public API (per package key), use the `listStaging` subcommand with `--packageKeys`: +Staging (unpublished) package variables can be listed with the following command: ```bash content-cli config variables listStaging -p --packageKeys [ ...] ``` -Optional `--variableType` filters by variable type (Pacman query parameter `type`). -Pacman returns one manifest object per package (`packageKey` and `variables`). With `--json`, the CLI writes a JSON array containing those manifests (one entry per requested package key). +The `--packageKeys` option specifies which packages to include. Optional `--variableType` limits the result to variables of that type. + +Similar to the other listing commands, the `--json` option can be used for exporting (saving) the result as a json file. ### Listing Assignments diff --git a/src/commands/configuration-management/api/staging-package-variables-api.ts b/src/commands/configuration-management/api/staging-package-variables-api.ts index cb07d6da..2ec03503 100644 --- a/src/commands/configuration-management/api/staging-package-variables-api.ts +++ b/src/commands/configuration-management/api/staging-package-variables-api.ts @@ -10,17 +10,20 @@ export class StagingPackageVariablesApi { this.httpClient = () => context.httpClient; } - public async findAllByPackageKey(packageKey: string, variableType?: string): Promise { + public async findAllByPackageKeys( + packageKeys: string[], + variableType?: string + ): Promise { const params = new URLSearchParams(); if (variableType) { - params.set("type", variableType); + params.set("variableType", variableType); } const query = params.toString(); - const path = `/pacman/api/core/staging/packages/${packageKey}/variables${query ? `?${query}` : ""}`; + const path = `/pacman/api/core/staging/packages/variables/by-package-keys${query ? `?${query}` : ""}`; return await this.httpClient() - .get(path) + .post(path, packageKeys) .catch(e => { - throw new FatalError(`Problem listing staging variables for package '${packageKey}': ${e}`); + throw new FatalError(`Problem listing staging variables for packages: ${e}`); }); } } diff --git a/src/commands/configuration-management/variable.service.ts b/src/commands/configuration-management/variable.service.ts index bc4706f9..be4e465c 100644 --- a/src/commands/configuration-management/variable.service.ts +++ b/src/commands/configuration-management/variable.service.ts @@ -69,16 +69,11 @@ export class VariableService { packageKeys: string[], variableType: string ): Promise { - const results: StagingVariableManifestTransport[] = []; if (packageKeys.length === 0) { throw new FatalError("Please provide at least one package key!"); } - for (const packageKey of packageKeys) { - const variableManifestTransport = await this.stagingPackageVariablesApi.findAllByPackageKey(packageKey, variableType); - results.push(variableManifestTransport); - } - return results; + return await this.stagingPackageVariablesApi.findAllByPackageKeys(packageKeys, variableType); } private async getVersionedVariablesByKeyVersionPairs(keysByVersion: string[], keysByVersionFile: string): Promise { diff --git a/tests/commands/configuration-management/config-list-variables.spec.ts b/tests/commands/configuration-management/config-list-variables.spec.ts index 4a530855..0e583f92 100644 --- a/tests/commands/configuration-management/config-list-variables.spec.ts +++ b/tests/commands/configuration-management/config-list-variables.spec.ts @@ -6,7 +6,7 @@ import { VariableManifestTransport, } from "../../../src/commands/configuration-management/interfaces/package-export.interfaces"; import { PackageManagerVariableType } from "../../../src/commands/studio/interfaces/package-manager.interfaces"; -import { mockAxiosGet, mockAxiosPost, mockedPostRequestBodyByUrl } from "../../utls/http-requests-mock"; +import { mockAxiosPost, mockedPostRequestBodyByUrl } from "../../utls/http-requests-mock"; import { ConfigCommandService } from "../../../src/commands/configuration-management/config-command.service"; import { testContext } from "../../utls/test-context"; import { loggingTestTransport, mockWriteFileSync } from "../../jest.setup"; @@ -181,31 +181,31 @@ describe("Config listVariables", () => { ]; const stagingVarsPkgB = [{ key: "DATA_POOL", type: "SINGLE_VALUE", value: "pool-id-2" }]; - it("Should list staging variables per package via Pacman public API", async () => { - mockAxiosGet( - "https://myTeam.celonis.cloud/pacman/api/core/staging/packages/pkg-a/variables", - { packageKey: "pkg-a", variables: stagingVarsPkgA } - ); - mockAxiosGet( - "https://myTeam.celonis.cloud/pacman/api/core/staging/packages/pkg-b/variables", - { packageKey: "pkg-b", variables: stagingVarsPkgB } + it("Should list staging variables in one Pacman batch public API call", async () => { + const batchResponse = [ + { packageKey: "pkg-a", variables: stagingVarsPkgA }, + { packageKey: "pkg-b", variables: stagingVarsPkgB }, + ]; + mockAxiosPost( + "https://myTeam.celonis.cloud/pacman/api/core/staging/packages/variables/by-package-keys", + batchResponse ); await new ConfigCommandService(testContext).listStagingVariables(false, ["pkg-a", "pkg-b"], ""); expect(loggingTestTransport.logMessages.length).toBe(2); - expect(loggingTestTransport.logMessages[0].message).toContain( - JSON.stringify({ packageKey: "pkg-a", variables: stagingVarsPkgA }) - ); - expect(loggingTestTransport.logMessages[1].message).toContain( - JSON.stringify({ packageKey: "pkg-b", variables: stagingVarsPkgB }) - ); + expect(loggingTestTransport.logMessages[0].message).toContain(JSON.stringify(batchResponse[0])); + expect(loggingTestTransport.logMessages[1].message).toContain(JSON.stringify(batchResponse[1])); + expect(JSON.parse(mockedPostRequestBodyByUrl.get( + "https://myTeam.celonis.cloud/pacman/api/core/staging/packages/variables/by-package-keys" + ))).toEqual(["pkg-a", "pkg-b"]); }); it("Should export staging variables as json file", async () => { - mockAxiosGet( - "https://myTeam.celonis.cloud/pacman/api/core/staging/packages/pkg-a/variables?type=SINGLE_VALUE", - { packageKey: "pkg-a", variables: [stagingVarsPkgA[0]] } + const filtered = [{ packageKey: "pkg-a", variables: [stagingVarsPkgA[0]] }]; + mockAxiosPost( + "https://myTeam.celonis.cloud/pacman/api/core/staging/packages/variables/by-package-keys?variableType=SINGLE_VALUE", + filtered ); await new ConfigCommandService(testContext).listStagingVariables(true, ["pkg-a"], "SINGLE_VALUE"); @@ -214,7 +214,7 @@ describe("Config listVariables", () => { const expectedFileName = loggingTestTransport.logMessages[0].message.split(FileService.fileDownloadedMessage)[1]; expect(mockWriteFileSync).toHaveBeenCalledWith( path.resolve(process.cwd(), expectedFileName), - JSON.stringify([{ packageKey: "pkg-a", variables: [stagingVarsPkgA[0]] }]), + JSON.stringify(filtered), { encoding: "utf-8" } ); }); @@ -224,4 +224,4 @@ describe("Config listVariables", () => { new ConfigCommandService(testContext).listStagingVariables(false, [], "") ).rejects.toThrow("Please provide at least one package key!"); }); -}) \ No newline at end of file +}); From f95948378c5c31b17d7b2c15fab0feab9a49e399 Mon Sep 17 00:00:00 2001 From: Patrick Vicinus Date: Fri, 20 Mar 2026 13:49:22 +0100 Subject: [PATCH 09/13] test: add config listStagingVariables spec, trim listVariables tests - Move listStagingVariables coverage to config-list-staging-variables.spec.ts - Align assertions with listVariables (post body, json export, errors) - Add withQueryString helper for mock URLs Includes-AI-Code: true Made-with: Cursor --- .../config-list-staging-variables.spec.ts | 84 +++++++++++++++++++ .../config-list-variables.spec.ts | 50 ----------- 2 files changed, 84 insertions(+), 50 deletions(-) create mode 100644 tests/commands/configuration-management/config-list-staging-variables.spec.ts diff --git a/tests/commands/configuration-management/config-list-staging-variables.spec.ts b/tests/commands/configuration-management/config-list-staging-variables.spec.ts new file mode 100644 index 00000000..5275da1b --- /dev/null +++ b/tests/commands/configuration-management/config-list-staging-variables.spec.ts @@ -0,0 +1,84 @@ +import * as path from "path"; +import { parse } from "../../../src/core/utils/json"; +import { + StagingVariableManifestTransport, + VariableExportTransport, +} from "../../../src/commands/configuration-management/interfaces/package-export.interfaces"; +import { mockAxiosPost, mockedPostRequestBodyByUrl } from "../../utls/http-requests-mock"; +import { ConfigCommandService } from "../../../src/commands/configuration-management/config-command.service"; +import { testContext } from "../../utls/test-context"; +import { loggingTestTransport, mockWriteFileSync } from "../../jest.setup"; +import { FileService } from "../../../src/core/utils/file-service"; + +function withQueryString(baseUrl: string, queryParams?: Record): string { + if (!queryParams || Object.keys(queryParams).length === 0) { + return baseUrl; + } + return `${baseUrl}?${new URLSearchParams(queryParams).toString()}`; +} + +describe("Config listStagingVariables", () => { + + const stagingVariablesByPackageKeysBaseUrl = + `${testContext.profile.team.replace(/\/$/, "")}/pacman/api/core/staging/packages/variables/by-package-keys`; + + const stagingVarsPkgA: VariableExportTransport[] = [ + { key: "DATA_POOL", type: "SINGLE_VALUE", value: "pool-id-1", metadata: {} }, + { key: "OTHER", type: "CONNECTION", value: { connectionId: "c1" }, metadata: {} }, + ]; + const stagingVarsPkgB: VariableExportTransport[] = [ + { key: "DATA_POOL", type: "SINGLE_VALUE", value: "pool-id-2", metadata: {} }, + ]; + + const batchResponse: StagingVariableManifestTransport[] = [ + { packageKey: "pkg-a", variables: stagingVarsPkgA }, + { packageKey: "pkg-b", variables: stagingVarsPkgB }, + ]; + + const expectedPackageKeys = ["pkg-a", "pkg-b"]; + + it("Should list staging variables for non-json response", async () => { + const url = stagingVariablesByPackageKeysBaseUrl; + mockAxiosPost(url, batchResponse); + + await new ConfigCommandService(testContext).listStagingVariables(false, expectedPackageKeys, ""); + + expect(loggingTestTransport.logMessages.length).toBe(2); + expect(loggingTestTransport.logMessages[0].message).toContain(JSON.stringify(batchResponse[0])); + expect(loggingTestTransport.logMessages[1].message).toContain(JSON.stringify(batchResponse[1])); + + const postBody = parse(mockedPostRequestBodyByUrl.get(url)); + expect(postBody).toEqual(expectedPackageKeys); + }) + + it("Should export staging variables for json response", async () => { + const filtered: StagingVariableManifestTransport[] = [ + { packageKey: "pkg-a", variables: [stagingVarsPkgA[0]] }, + ]; + const url = withQueryString(stagingVariablesByPackageKeysBaseUrl, { variableType: "SINGLE_VALUE" }); + mockAxiosPost(url, filtered); + + await new ConfigCommandService(testContext).listStagingVariables(true, ["pkg-a"], "SINGLE_VALUE"); + + expect(loggingTestTransport.logMessages.length).toBe(1); + expect(loggingTestTransport.logMessages[0].message).toContain(FileService.fileDownloadedMessage); + + const expectedFileName = loggingTestTransport.logMessages[0].message.split(FileService.fileDownloadedMessage)[1]; + expect(mockWriteFileSync).toHaveBeenCalledWith( + path.resolve(process.cwd(), expectedFileName), + JSON.stringify(filtered), + {encoding: "utf-8"} + ); + + const postBody = parse(mockedPostRequestBodyByUrl.get(url)); + expect(postBody).toEqual(["pkg-a"]); + }) + + it("Should throw error if no package keys are provided", async () => { + try { + await new ConfigCommandService(testContext).listStagingVariables(false, [], ""); + } catch (e) { + expect(e.message).toEqual("Please provide at least one package key!"); + } + }) +}); diff --git a/tests/commands/configuration-management/config-list-variables.spec.ts b/tests/commands/configuration-management/config-list-variables.spec.ts index 0e583f92..a55e0915 100644 --- a/tests/commands/configuration-management/config-list-variables.spec.ts +++ b/tests/commands/configuration-management/config-list-variables.spec.ts @@ -174,54 +174,4 @@ describe("Config listVariables", () => { expect(e.message).toEqual("Please provide keysByVersion mappings or file path!"); } }) - - const stagingVarsPkgA = [ - { key: "DATA_POOL", type: "SINGLE_VALUE", value: "pool-id-1" }, - { key: "OTHER", type: "CONNECTION", value: { connectionId: "c1" } }, - ]; - const stagingVarsPkgB = [{ key: "DATA_POOL", type: "SINGLE_VALUE", value: "pool-id-2" }]; - - it("Should list staging variables in one Pacman batch public API call", async () => { - const batchResponse = [ - { packageKey: "pkg-a", variables: stagingVarsPkgA }, - { packageKey: "pkg-b", variables: stagingVarsPkgB }, - ]; - mockAxiosPost( - "https://myTeam.celonis.cloud/pacman/api/core/staging/packages/variables/by-package-keys", - batchResponse - ); - - await new ConfigCommandService(testContext).listStagingVariables(false, ["pkg-a", "pkg-b"], ""); - - expect(loggingTestTransport.logMessages.length).toBe(2); - expect(loggingTestTransport.logMessages[0].message).toContain(JSON.stringify(batchResponse[0])); - expect(loggingTestTransport.logMessages[1].message).toContain(JSON.stringify(batchResponse[1])); - expect(JSON.parse(mockedPostRequestBodyByUrl.get( - "https://myTeam.celonis.cloud/pacman/api/core/staging/packages/variables/by-package-keys" - ))).toEqual(["pkg-a", "pkg-b"]); - }); - - it("Should export staging variables as json file", async () => { - const filtered = [{ packageKey: "pkg-a", variables: [stagingVarsPkgA[0]] }]; - mockAxiosPost( - "https://myTeam.celonis.cloud/pacman/api/core/staging/packages/variables/by-package-keys?variableType=SINGLE_VALUE", - filtered - ); - - await new ConfigCommandService(testContext).listStagingVariables(true, ["pkg-a"], "SINGLE_VALUE"); - - expect(loggingTestTransport.logMessages.length).toBe(1); - const expectedFileName = loggingTestTransport.logMessages[0].message.split(FileService.fileDownloadedMessage)[1]; - expect(mockWriteFileSync).toHaveBeenCalledWith( - path.resolve(process.cwd(), expectedFileName), - JSON.stringify(filtered), - { encoding: "utf-8" } - ); - }); - - it("Should throw when listStagingVariables called with empty package keys", async () => { - await expect( - new ConfigCommandService(testContext).listStagingVariables(false, [], "") - ).rejects.toThrow("Please provide at least one package key!"); - }); }); From 0c56546452e96b185467030319cdba9f80dcf8e5 Mon Sep 17 00:00:00 2001 From: Patrick Vicinus Date: Mon, 23 Mar 2026 11:54:32 +0100 Subject: [PATCH 10/13] TA-5049: Staging variables via config variables list --packageKeys Replace listStaging with --packageKeys on variables list; mutual exclusion with versioned options. Consolidate tests, update user guide, and set Commander defaults on variables list options (module.spec mirrors parsed shape). Pass variables list options through to ConfigCommandService without casts. Includes-AI-Code: true Made-with: Cursor --- docs/user-guide/config-commands.md | 14 ++- .../config-command.service.ts | 24 +++-- .../configuration-management/module.ts | 38 +++++--- .../config-list-staging-variables.spec.ts | 84 ----------------- .../config-list-variables.spec.ts | 77 +++++++++++++++- .../configuration-management/module.spec.ts | 92 +++++++++++++++++++ 6 files changed, 210 insertions(+), 119 deletions(-) delete mode 100644 tests/commands/configuration-management/config-list-staging-variables.spec.ts diff --git a/docs/user-guide/config-commands.md b/docs/user-guide/config-commands.md index 617fbbd9..ede32334 100644 --- a/docs/user-guide/config-commands.md +++ b/docs/user-guide/config-commands.md @@ -147,7 +147,9 @@ info: Config import report file: 9560f81f-f746-4117-83ee-dd1f614ad624.json ### Listing Package Variables -Package variables (with assignments) can be listed with the following command: +Variables can be read for **published package versions** (each package identified with a version) or for the **unpublished** configuration of packages (identified by package key only). + +**Published versions** — `config variables list` with `--keysByVersion` or `--keysByVersionFile`: ```bash content-cli config variables list -p --keysByVersion key1:version1 ... keyN:versionN @@ -164,17 +166,13 @@ export interface PackageKeyAndVersionPair { Similar to the other listing commands, the --json option can be used for exporting (saving) the result as a json file. -### Listing staging package variables - -Staging (unpublished) package variables can be listed with the following command: +**Unpublished configuration** — `config variables list` with `--packageKeys`: ```bash -content-cli config variables listStaging -p --packageKeys [ ...] +content-cli config variables list -p --packageKeys [ ...] ``` -The `--packageKeys` option specifies which packages to include. Optional `--variableType` limits the result to variables of that type. - -Similar to the other listing commands, the `--json` option can be used for exporting (saving) the result as a json file. +Optional `--variableType` limits the result to variables of that type. The --json option can be used for exporting (saving) the result as a json file. ### Listing Assignments diff --git a/src/commands/configuration-management/config-command.service.ts b/src/commands/configuration-management/config-command.service.ts index 84db13f7..a3824164 100644 --- a/src/commands/configuration-management/config-command.service.ts +++ b/src/commands/configuration-management/config-command.service.ts @@ -31,7 +31,21 @@ export class ConfigCommandService { } } - public async listVariables(jsonResponse: boolean, keysByVersion: string[], keysByVersionFile: string): Promise { + public async listVariables( + jsonResponse: boolean, + keysByVersion: string[], + keysByVersionFile: string, + packageKeys: string[], + variableType: string + ): Promise { + if (packageKeys.length > 0) { + if (jsonResponse) { + await this.variableService.exportStagingVariables(packageKeys, variableType); + } else { + await this.variableService.listStagingVariables(packageKeys, variableType); + } + return; + } if (jsonResponse) { await this.variableService.exportVariables(keysByVersion, keysByVersionFile); } else { @@ -39,14 +53,6 @@ export class ConfigCommandService { } } - public async listStagingVariables(jsonResponse: boolean, packageKeys: string[], variableType: string): Promise { - if (jsonResponse) { - await this.variableService.exportStagingVariables(packageKeys, variableType); - } else { - await this.variableService.listStagingVariables(packageKeys, variableType); - } - } - public batchExportPackages(packageKeys: string[], packageKeysByVersion: string[], withDependencies: boolean, gitBranch: string, unzip: boolean): Promise { return this.batchImportExportService.batchExportPackages(packageKeys, packageKeysByVersion, withDependencies, gitBranch, unzip); } diff --git a/src/commands/configuration-management/module.ts b/src/commands/configuration-management/module.ts index 58aad0de..7f5de8df 100644 --- a/src/commands/configuration-management/module.ts +++ b/src/commands/configuration-management/module.ts @@ -77,19 +77,14 @@ class Module extends IModule { .description("Commands related to variable configs"); variablesCommand.command("list") - .description("Command to list versioned variables of packages") + .description("List package variables: use --packageKeys for staging (unpublished), or --keysByVersion / --keysByVersionFile for versioned variables") .option("--json", "Return response as json type", "") - .option("--keysByVersion ", "Mapping of package keys and versions", "") + .option("--packageKeys ", "Package keys (staging variables only; mutually exclusive with versioned options)", []) + .option("--variableType ", "Filter staging variables by type (only with --packageKeys)", "") + .option("--keysByVersion ", "Mapping of package keys and versions", []) .option("--keysByVersionFile ", "Package keys by version mappings file path.", "") .action(this.listVariables); - variablesCommand.command("listStaging") - .description("Command to list the staging variables of packages") - .option("--json", "Return response as json type", "") - .requiredOption("--packageKeys ", "Package keys") - .option("--variableType ", "Filter staging variables by type", "") - .action(this.listStagingVariables); - const nodesCommand = configCommand.command("nodes") .description("Commands related to nodes of the package"); @@ -179,11 +174,28 @@ class Module extends IModule { } private async listVariables(context: Context, command: Command, options: OptionValues): Promise { - await new ConfigCommandService(context).listVariables(options.json, options.keysByVersion, options.keysByVersionFile); - } + const hasStagingKeys = options.packageKeys.length > 0; + const hasVersioned = + options.keysByVersion.length > 0 || options.keysByVersionFile !== ""; + + if (hasStagingKeys && hasVersioned) { + throw new Error( + "Please provide either --packageKeys or --keysByVersion/--keysByVersionFile, but not both." + ); + } + if (!hasStagingKeys && !hasVersioned) { + throw new Error( + "Please provide --packageKeys for staging variables, or --keysByVersion / --keysByVersionFile for versioned variables." + ); + } - private async listStagingVariables(context: Context, command: Command, options: OptionValues): Promise { - await new ConfigCommandService(context).listStagingVariables(options.json, options.packageKeys, options.variableType); + await new ConfigCommandService(context).listVariables( + options.json, + options.keysByVersion, + options.keysByVersionFile, + options.packageKeys, + options.variableType + ); } private async listAssignments(context: Context, command: Command, options: OptionValues): Promise { diff --git a/tests/commands/configuration-management/config-list-staging-variables.spec.ts b/tests/commands/configuration-management/config-list-staging-variables.spec.ts deleted file mode 100644 index 5275da1b..00000000 --- a/tests/commands/configuration-management/config-list-staging-variables.spec.ts +++ /dev/null @@ -1,84 +0,0 @@ -import * as path from "path"; -import { parse } from "../../../src/core/utils/json"; -import { - StagingVariableManifestTransport, - VariableExportTransport, -} from "../../../src/commands/configuration-management/interfaces/package-export.interfaces"; -import { mockAxiosPost, mockedPostRequestBodyByUrl } from "../../utls/http-requests-mock"; -import { ConfigCommandService } from "../../../src/commands/configuration-management/config-command.service"; -import { testContext } from "../../utls/test-context"; -import { loggingTestTransport, mockWriteFileSync } from "../../jest.setup"; -import { FileService } from "../../../src/core/utils/file-service"; - -function withQueryString(baseUrl: string, queryParams?: Record): string { - if (!queryParams || Object.keys(queryParams).length === 0) { - return baseUrl; - } - return `${baseUrl}?${new URLSearchParams(queryParams).toString()}`; -} - -describe("Config listStagingVariables", () => { - - const stagingVariablesByPackageKeysBaseUrl = - `${testContext.profile.team.replace(/\/$/, "")}/pacman/api/core/staging/packages/variables/by-package-keys`; - - const stagingVarsPkgA: VariableExportTransport[] = [ - { key: "DATA_POOL", type: "SINGLE_VALUE", value: "pool-id-1", metadata: {} }, - { key: "OTHER", type: "CONNECTION", value: { connectionId: "c1" }, metadata: {} }, - ]; - const stagingVarsPkgB: VariableExportTransport[] = [ - { key: "DATA_POOL", type: "SINGLE_VALUE", value: "pool-id-2", metadata: {} }, - ]; - - const batchResponse: StagingVariableManifestTransport[] = [ - { packageKey: "pkg-a", variables: stagingVarsPkgA }, - { packageKey: "pkg-b", variables: stagingVarsPkgB }, - ]; - - const expectedPackageKeys = ["pkg-a", "pkg-b"]; - - it("Should list staging variables for non-json response", async () => { - const url = stagingVariablesByPackageKeysBaseUrl; - mockAxiosPost(url, batchResponse); - - await new ConfigCommandService(testContext).listStagingVariables(false, expectedPackageKeys, ""); - - expect(loggingTestTransport.logMessages.length).toBe(2); - expect(loggingTestTransport.logMessages[0].message).toContain(JSON.stringify(batchResponse[0])); - expect(loggingTestTransport.logMessages[1].message).toContain(JSON.stringify(batchResponse[1])); - - const postBody = parse(mockedPostRequestBodyByUrl.get(url)); - expect(postBody).toEqual(expectedPackageKeys); - }) - - it("Should export staging variables for json response", async () => { - const filtered: StagingVariableManifestTransport[] = [ - { packageKey: "pkg-a", variables: [stagingVarsPkgA[0]] }, - ]; - const url = withQueryString(stagingVariablesByPackageKeysBaseUrl, { variableType: "SINGLE_VALUE" }); - mockAxiosPost(url, filtered); - - await new ConfigCommandService(testContext).listStagingVariables(true, ["pkg-a"], "SINGLE_VALUE"); - - expect(loggingTestTransport.logMessages.length).toBe(1); - expect(loggingTestTransport.logMessages[0].message).toContain(FileService.fileDownloadedMessage); - - const expectedFileName = loggingTestTransport.logMessages[0].message.split(FileService.fileDownloadedMessage)[1]; - expect(mockWriteFileSync).toHaveBeenCalledWith( - path.resolve(process.cwd(), expectedFileName), - JSON.stringify(filtered), - {encoding: "utf-8"} - ); - - const postBody = parse(mockedPostRequestBodyByUrl.get(url)); - expect(postBody).toEqual(["pkg-a"]); - }) - - it("Should throw error if no package keys are provided", async () => { - try { - await new ConfigCommandService(testContext).listStagingVariables(false, [], ""); - } catch (e) { - expect(e.message).toEqual("Please provide at least one package key!"); - } - }) -}); diff --git a/tests/commands/configuration-management/config-list-variables.spec.ts b/tests/commands/configuration-management/config-list-variables.spec.ts index a55e0915..a8322b03 100644 --- a/tests/commands/configuration-management/config-list-variables.spec.ts +++ b/tests/commands/configuration-management/config-list-variables.spec.ts @@ -1,8 +1,11 @@ import * as path from "path"; import * as fs from "fs"; +import { URLSearchParams } from "url"; import { parse } from "../../../src/core/utils/json"; import { PackageKeyAndVersionPair, + StagingVariableManifestTransport, + VariableExportTransport, VariableManifestTransport, } from "../../../src/commands/configuration-management/interfaces/package-export.interfaces"; import { PackageManagerVariableType } from "../../../src/commands/studio/interfaces/package-manager.interfaces"; @@ -12,6 +15,13 @@ import { testContext } from "../../utls/test-context"; import { loggingTestTransport, mockWriteFileSync } from "../../jest.setup"; import { FileService } from "../../../src/core/utils/file-service"; +function withQueryString(baseUrl: string, queryParams?: Record): string { + if (!queryParams || Object.keys(queryParams).length === 0) { + return baseUrl; + } + return `${baseUrl}?${new URLSearchParams(queryParams).toString()}`; +} + describe("Config listVariables", () => { const firstManifest: VariableManifestTransport = { @@ -112,7 +122,7 @@ describe("Config listVariables", () => { }) it("Should list fixed variables for non-json response", async () => { - await new ConfigCommandService(testContext).listVariables(false, ["key-1:1.0.0", "key-2:1.0.0", "key-3:1.0.0"], null); + await new ConfigCommandService(testContext).listVariables(false, ["key-1:1.0.0", "key-2:1.0.0", "key-3:1.0.0"], "", [], ""); expect(loggingTestTransport.logMessages.length).toBe(3); expect(loggingTestTransport.logMessages[0].message).toContain(JSON.stringify(fixedVariableManifests[0])); @@ -124,7 +134,7 @@ describe("Config listVariables", () => { }) it("Should export fixed variables for json response", async () => { - await new ConfigCommandService(testContext).listVariables(true, ["key-1:1.0.0", "key-2:1.0.0", "key-3:1.0.0"], null); + await new ConfigCommandService(testContext).listVariables(true, ["key-1:1.0.0", "key-2:1.0.0", "key-3:1.0.0"], "", [], ""); expect(loggingTestTransport.logMessages.length).toBe(1); expect(loggingTestTransport.logMessages[0].message).toContain(FileService.fileDownloadedMessage); @@ -140,7 +150,7 @@ describe("Config listVariables", () => { (fs.existsSync as jest.Mock).mockReturnValue(true); (fs.readFileSync as jest.Mock).mockReturnValue(JSON.stringify(packageKeyAndVersionPairs)); - await new ConfigCommandService(testContext).listVariables(false, [], "key_version_mapping.json"); + await new ConfigCommandService(testContext).listVariables(false, [], "key_version_mapping.json", [], ""); expect(loggingTestTransport.logMessages.length).toBe(3); expect(loggingTestTransport.logMessages[0].message).toContain(JSON.stringify(fixedVariableManifests[0])); @@ -155,7 +165,7 @@ describe("Config listVariables", () => { (fs.existsSync as jest.Mock).mockReturnValue(true); (fs.readFileSync as jest.Mock).mockReturnValue(JSON.stringify(packageKeyAndVersionPairs)); - await new ConfigCommandService(testContext).listVariables(true, [], "key_version_mapping.json"); + await new ConfigCommandService(testContext).listVariables(true, [], "key_version_mapping.json", [], ""); expect(loggingTestTransport.logMessages.length).toBe(1); expect(loggingTestTransport.logMessages[0].message).toContain(FileService.fileDownloadedMessage); @@ -169,9 +179,66 @@ describe("Config listVariables", () => { it("Should throw error if no mapping and no file path is provided", async () => { try { - await new ConfigCommandService(testContext).listVariables(true, [], ""); + await new ConfigCommandService(testContext).listVariables(true, [], "", [], ""); } catch (e) { expect(e.message).toEqual("Please provide keysByVersion mappings or file path!"); } }) + + describe("staging variables via --packageKeys", () => { + const stagingVariablesByPackageKeysBaseUrl = + `${testContext.profile.team.replace(/\/$/, "")}/pacman/api/core/staging/packages/variables/by-package-keys`; + + const stagingVarsPkgA: VariableExportTransport[] = [ + { key: "DATA_POOL", type: "SINGLE_VALUE", value: "pool-id-1", metadata: {} }, + { key: "OTHER", type: "CONNECTION", value: { connectionId: "c1" }, metadata: {} }, + ]; + const stagingVarsPkgB: VariableExportTransport[] = [ + { key: "DATA_POOL", type: "SINGLE_VALUE", value: "pool-id-2", metadata: {} }, + ]; + + const batchResponse: StagingVariableManifestTransport[] = [ + { packageKey: "pkg-a", variables: stagingVarsPkgA }, + { packageKey: "pkg-b", variables: stagingVarsPkgB }, + ]; + + const expectedPackageKeys = ["pkg-a", "pkg-b"]; + + it("Should list staging variables for non-json response", async () => { + const url = stagingVariablesByPackageKeysBaseUrl; + mockAxiosPost(url, batchResponse); + + await new ConfigCommandService(testContext).listVariables(false, [], "", expectedPackageKeys, ""); + + expect(loggingTestTransport.logMessages.length).toBe(2); + expect(loggingTestTransport.logMessages[0].message).toContain(JSON.stringify(batchResponse[0])); + expect(loggingTestTransport.logMessages[1].message).toContain(JSON.stringify(batchResponse[1])); + + const postBody = parse(mockedPostRequestBodyByUrl.get(url)); + expect(postBody).toEqual(expectedPackageKeys); + }); + + it("Should export staging variables for json response", async () => { + const filtered: StagingVariableManifestTransport[] = [ + { packageKey: "pkg-a", variables: [stagingVarsPkgA[0]] }, + ]; + const url = withQueryString(stagingVariablesByPackageKeysBaseUrl, { variableType: "SINGLE_VALUE" }); + mockAxiosPost(url, filtered); + + await new ConfigCommandService(testContext).listVariables(true, [], "", ["pkg-a"], "SINGLE_VALUE"); + + expect(loggingTestTransport.logMessages.length).toBe(1); + expect(loggingTestTransport.logMessages[0].message).toContain(FileService.fileDownloadedMessage); + + const expectedFileName = loggingTestTransport.logMessages[0].message.split(FileService.fileDownloadedMessage)[1]; + expect(mockWriteFileSync).toHaveBeenCalledWith( + path.resolve(process.cwd(), expectedFileName), + JSON.stringify(filtered), + {encoding: "utf-8"} + ); + + const postBody = parse(mockedPostRequestBodyByUrl.get(url)); + expect(postBody).toEqual(["pkg-a"]); + }); + }); }); diff --git a/tests/commands/configuration-management/module.spec.ts b/tests/commands/configuration-management/module.spec.ts index c6fb4995..aed3d91f 100644 --- a/tests/commands/configuration-management/module.spec.ts +++ b/tests/commands/configuration-management/module.spec.ts @@ -7,6 +7,14 @@ import { testContext } from "../../utls/test-context"; jest.mock("../../../src/commands/configuration-management/config-command.service"); jest.mock("../../../src/commands/configuration-management/node-dependency.service"); +/** Mirrors default values on `config variables list` Commander options (keep in sync with module.ts). */ +const variablesListOptionDefaults: OptionValues = { + packageKeys: [], + variableType: "", + keysByVersion: [], + keysByVersionFile: "", +}; + describe("Configuration Management Module - Action Validations", () => { let module: Module; let mockCommand: Command; @@ -20,6 +28,7 @@ describe("Configuration Management Module - Action Validations", () => { mockConfigCommandService = { listPackages: jest.fn().mockResolvedValue(undefined), + listVariables: jest.fn().mockResolvedValue(undefined), batchExportPackages: jest.fn().mockResolvedValue(undefined), batchImportPackages: jest.fn().mockResolvedValue(undefined), } as any; @@ -404,6 +413,89 @@ describe("Configuration Management Module - Action Validations", () => { }); }); + describe("listVariables validation", () => { + it("should throw when --packageKeys and --keysByVersion are both provided", async () => { + const options: OptionValues = { + ...variablesListOptionDefaults, + packageKeys: ["pkg-a"], + keysByVersion: ["key-1:1.0.0"], + }; + + await expect( + (module as any).listVariables(testContext, mockCommand, options) + ).rejects.toThrow( + "Please provide either --packageKeys or --keysByVersion/--keysByVersionFile, but not both." + ); + + expect(mockConfigCommandService.listVariables).not.toHaveBeenCalled(); + }); + + it("should throw when --packageKeys and --keysByVersionFile are both provided", async () => { + const options: OptionValues = { + ...variablesListOptionDefaults, + packageKeys: ["pkg-a"], + keysByVersionFile: "mapping.json", + }; + + await expect( + (module as any).listVariables(testContext, mockCommand, options) + ).rejects.toThrow( + "Please provide either --packageKeys or --keysByVersion/--keysByVersionFile, but not both." + ); + + expect(mockConfigCommandService.listVariables).not.toHaveBeenCalled(); + }); + + it("should throw when neither staging nor versioned inputs are provided", async () => { + const options: OptionValues = {...variablesListOptionDefaults}; + + await expect( + (module as any).listVariables(testContext, mockCommand, options) + ).rejects.toThrow( + "Please provide --packageKeys for staging variables, or --keysByVersion / --keysByVersionFile for versioned variables." + ); + + expect(mockConfigCommandService.listVariables).not.toHaveBeenCalled(); + }); + + it("should call listVariables for staging when only --packageKeys is provided", async () => { + const options: OptionValues = { + ...variablesListOptionDefaults, + packageKeys: ["pkg-a", "pkg-b"], + json: true, + variableType: "SINGLE_VALUE", + }; + + await (module as any).listVariables(testContext, mockCommand, options); + + expect(mockConfigCommandService.listVariables).toHaveBeenCalledWith( + true, + [], + "", + ["pkg-a", "pkg-b"], + "SINGLE_VALUE" + ); + }); + + it("should call listVariables for versioned when only --keysByVersion is provided", async () => { + const options: OptionValues = { + ...variablesListOptionDefaults, + keysByVersion: ["k:v"], + json: false, + }; + + await (module as any).listVariables(testContext, mockCommand, options); + + expect(mockConfigCommandService.listVariables).toHaveBeenCalledWith( + false, + ["k:v"], + "", + [], + "" + ); + }); + }); + describe("listNodeDependencies", () => { it("should call listNodeDependencies with correct parameters", async () => { const options: OptionValues = { From 6323accdf03df1ab6ed721912b2e07285b3435f6 Mon Sep 17 00:00:00 2001 From: Patrick Vicinus Date: Mon, 23 Mar 2026 14:29:20 +0100 Subject: [PATCH 11/13] Remove --variableType from config variables list; refine user guide Drop the staging variable type filter from config variables list and the Pacman client call. Update config-commands.md: mutual exclusivity of modes, shared JSON manifest shape (version only for published), and clearer published vs unpublished documentation. Includes-AI-Code: true Made-with: Cursor --- docs/user-guide/config-commands.md | 10 +++--- .../api/staging-package-variables-api.ts | 12 ++----- .../config-command.service.ts | 7 ++-- .../configuration-management/module.ts | 4 +-- .../variable.service.ts | 13 ++++---- .../config-list-variables.spec.ts | 32 +++++++------------ .../configuration-management/module.spec.ts | 8 ++--- 7 files changed, 31 insertions(+), 55 deletions(-) diff --git a/docs/user-guide/config-commands.md b/docs/user-guide/config-commands.md index ede32334..fb44614c 100644 --- a/docs/user-guide/config-commands.md +++ b/docs/user-guide/config-commands.md @@ -147,7 +147,9 @@ info: Config import report file: 9560f81f-f746-4117-83ee-dd1f614ad624.json ### Listing Package Variables -Variables can be read for **published package versions** (each package identified with a version) or for the **unpublished** configuration of packages (identified by package key only). +Variables can be read for **published package versions** (each package identified with a version) or for the **unpublished** configuration of packages (identified by package key only). Use either the published flow (`--keysByVersion` / `--keysByVersionFile`) or the unpublished flow (`--packageKeys`); combining them is not supported and the command will fail. + +**Output (console and `--json`).** For both flows, each package is represented the same way: `packageKey`, `variables` (definitions and values), and—**only for published versions**—`version`. Without `--json`, each package is printed as one JSON object per line. With `--json`, the result is written to a file as a **JSON array** of those objects. For unpublished packages, `version` is simply omitted. **Published versions** — `config variables list` with `--keysByVersion` or `--keysByVersionFile`: @@ -155,7 +157,7 @@ Variables can be read for **published package versions** (each package identifie content-cli config variables list -p --keysByVersion key1:version1 ... keyN:versionN ``` -The --keysByVersion option should specify a list of key :(colon) version pairs. Alternatively, a json file path containing a list of key and version pairs can be used. The PackageKeyAndVersionPair for the file should have the following form: +The `--keysByVersion` option should specify a list of `key:version` pairs. Alternatively, pass a JSON file path with `--keysByVersionFile`. Each entry in the file should match: ```typescript export interface PackageKeyAndVersionPair { @@ -164,15 +166,13 @@ export interface PackageKeyAndVersionPair { } ``` -Similar to the other listing commands, the --json option can be used for exporting (saving) the result as a json file. - **Unpublished configuration** — `config variables list` with `--packageKeys`: ```bash content-cli config variables list -p --packageKeys [ ...] ``` -Optional `--variableType` limits the result to variables of that type. The --json option can be used for exporting (saving) the result as a json file. +Use `--json` with either flow to export the array to a file (see **Output** above). ### Listing Assignments diff --git a/src/commands/configuration-management/api/staging-package-variables-api.ts b/src/commands/configuration-management/api/staging-package-variables-api.ts index 2ec03503..dd052c72 100644 --- a/src/commands/configuration-management/api/staging-package-variables-api.ts +++ b/src/commands/configuration-management/api/staging-package-variables-api.ts @@ -10,16 +10,8 @@ export class StagingPackageVariablesApi { this.httpClient = () => context.httpClient; } - public async findAllByPackageKeys( - packageKeys: string[], - variableType?: string - ): Promise { - const params = new URLSearchParams(); - if (variableType) { - params.set("variableType", variableType); - } - const query = params.toString(); - const path = `/pacman/api/core/staging/packages/variables/by-package-keys${query ? `?${query}` : ""}`; + public async findAllByPackageKeys(packageKeys: string[]): Promise { + const path = `/pacman/api/core/staging/packages/variables/by-package-keys`; return await this.httpClient() .post(path, packageKeys) .catch(e => { diff --git a/src/commands/configuration-management/config-command.service.ts b/src/commands/configuration-management/config-command.service.ts index a3824164..dc9d854c 100644 --- a/src/commands/configuration-management/config-command.service.ts +++ b/src/commands/configuration-management/config-command.service.ts @@ -35,14 +35,13 @@ export class ConfigCommandService { jsonResponse: boolean, keysByVersion: string[], keysByVersionFile: string, - packageKeys: string[], - variableType: string + packageKeys: string[] ): Promise { if (packageKeys.length > 0) { if (jsonResponse) { - await this.variableService.exportStagingVariables(packageKeys, variableType); + await this.variableService.exportStagingVariables(packageKeys); } else { - await this.variableService.listStagingVariables(packageKeys, variableType); + await this.variableService.listStagingVariables(packageKeys); } return; } diff --git a/src/commands/configuration-management/module.ts b/src/commands/configuration-management/module.ts index 7f5de8df..fab38816 100644 --- a/src/commands/configuration-management/module.ts +++ b/src/commands/configuration-management/module.ts @@ -80,7 +80,6 @@ class Module extends IModule { .description("List package variables: use --packageKeys for staging (unpublished), or --keysByVersion / --keysByVersionFile for versioned variables") .option("--json", "Return response as json type", "") .option("--packageKeys ", "Package keys (staging variables only; mutually exclusive with versioned options)", []) - .option("--variableType ", "Filter staging variables by type (only with --packageKeys)", "") .option("--keysByVersion ", "Mapping of package keys and versions", []) .option("--keysByVersionFile ", "Package keys by version mappings file path.", "") .action(this.listVariables); @@ -193,8 +192,7 @@ class Module extends IModule { options.json, options.keysByVersion, options.keysByVersionFile, - options.packageKeys, - options.variableType + options.packageKeys ); } diff --git a/src/commands/configuration-management/variable.service.ts b/src/commands/configuration-management/variable.service.ts index be4e465c..b24633fd 100644 --- a/src/commands/configuration-management/variable.service.ts +++ b/src/commands/configuration-management/variable.service.ts @@ -53,27 +53,26 @@ export class VariableService { this.exportToJson(variableManifests); } - public async listStagingVariables(packageKeys: string[], variableType: string): Promise { - const byPackage = await this.fetchStagingVariablesByPackageKeys(packageKeys, variableType); + public async listStagingVariables(packageKeys: string[]): Promise { + const byPackage = await this.fetchStagingVariablesByPackageKeys(packageKeys); byPackage.forEach(entry => { logger.info(JSON.stringify(entry)); }); } - public async exportStagingVariables(packageKeys: string[], variableType: string): Promise { - const byPackage = await this.fetchStagingVariablesByPackageKeys(packageKeys, variableType); + public async exportStagingVariables(packageKeys: string[]): Promise { + const byPackage = await this.fetchStagingVariablesByPackageKeys(packageKeys); this.exportToJson(byPackage); } private async fetchStagingVariablesByPackageKeys( - packageKeys: string[], - variableType: string + packageKeys: string[] ): Promise { if (packageKeys.length === 0) { throw new FatalError("Please provide at least one package key!"); } - return await this.stagingPackageVariablesApi.findAllByPackageKeys(packageKeys, variableType); + return await this.stagingPackageVariablesApi.findAllByPackageKeys(packageKeys); } private async getVersionedVariablesByKeyVersionPairs(keysByVersion: string[], keysByVersionFile: string): Promise { diff --git a/tests/commands/configuration-management/config-list-variables.spec.ts b/tests/commands/configuration-management/config-list-variables.spec.ts index a8322b03..1578055b 100644 --- a/tests/commands/configuration-management/config-list-variables.spec.ts +++ b/tests/commands/configuration-management/config-list-variables.spec.ts @@ -1,6 +1,5 @@ import * as path from "path"; import * as fs from "fs"; -import { URLSearchParams } from "url"; import { parse } from "../../../src/core/utils/json"; import { PackageKeyAndVersionPair, @@ -15,13 +14,6 @@ import { testContext } from "../../utls/test-context"; import { loggingTestTransport, mockWriteFileSync } from "../../jest.setup"; import { FileService } from "../../../src/core/utils/file-service"; -function withQueryString(baseUrl: string, queryParams?: Record): string { - if (!queryParams || Object.keys(queryParams).length === 0) { - return baseUrl; - } - return `${baseUrl}?${new URLSearchParams(queryParams).toString()}`; -} - describe("Config listVariables", () => { const firstManifest: VariableManifestTransport = { @@ -122,7 +114,7 @@ describe("Config listVariables", () => { }) it("Should list fixed variables for non-json response", async () => { - await new ConfigCommandService(testContext).listVariables(false, ["key-1:1.0.0", "key-2:1.0.0", "key-3:1.0.0"], "", [], ""); + await new ConfigCommandService(testContext).listVariables(false, ["key-1:1.0.0", "key-2:1.0.0", "key-3:1.0.0"], "", []); expect(loggingTestTransport.logMessages.length).toBe(3); expect(loggingTestTransport.logMessages[0].message).toContain(JSON.stringify(fixedVariableManifests[0])); @@ -134,7 +126,7 @@ describe("Config listVariables", () => { }) it("Should export fixed variables for json response", async () => { - await new ConfigCommandService(testContext).listVariables(true, ["key-1:1.0.0", "key-2:1.0.0", "key-3:1.0.0"], "", [], ""); + await new ConfigCommandService(testContext).listVariables(true, ["key-1:1.0.0", "key-2:1.0.0", "key-3:1.0.0"], "", []); expect(loggingTestTransport.logMessages.length).toBe(1); expect(loggingTestTransport.logMessages[0].message).toContain(FileService.fileDownloadedMessage); @@ -150,7 +142,7 @@ describe("Config listVariables", () => { (fs.existsSync as jest.Mock).mockReturnValue(true); (fs.readFileSync as jest.Mock).mockReturnValue(JSON.stringify(packageKeyAndVersionPairs)); - await new ConfigCommandService(testContext).listVariables(false, [], "key_version_mapping.json", [], ""); + await new ConfigCommandService(testContext).listVariables(false, [], "key_version_mapping.json", []); expect(loggingTestTransport.logMessages.length).toBe(3); expect(loggingTestTransport.logMessages[0].message).toContain(JSON.stringify(fixedVariableManifests[0])); @@ -165,7 +157,7 @@ describe("Config listVariables", () => { (fs.existsSync as jest.Mock).mockReturnValue(true); (fs.readFileSync as jest.Mock).mockReturnValue(JSON.stringify(packageKeyAndVersionPairs)); - await new ConfigCommandService(testContext).listVariables(true, [], "key_version_mapping.json", [], ""); + await new ConfigCommandService(testContext).listVariables(true, [], "key_version_mapping.json", []); expect(loggingTestTransport.logMessages.length).toBe(1); expect(loggingTestTransport.logMessages[0].message).toContain(FileService.fileDownloadedMessage); @@ -179,7 +171,7 @@ describe("Config listVariables", () => { it("Should throw error if no mapping and no file path is provided", async () => { try { - await new ConfigCommandService(testContext).listVariables(true, [], "", [], ""); + await new ConfigCommandService(testContext).listVariables(true, [], "", []); } catch (e) { expect(e.message).toEqual("Please provide keysByVersion mappings or file path!"); } @@ -208,7 +200,7 @@ describe("Config listVariables", () => { const url = stagingVariablesByPackageKeysBaseUrl; mockAxiosPost(url, batchResponse); - await new ConfigCommandService(testContext).listVariables(false, [], "", expectedPackageKeys, ""); + await new ConfigCommandService(testContext).listVariables(false, [], "", expectedPackageKeys); expect(loggingTestTransport.logMessages.length).toBe(2); expect(loggingTestTransport.logMessages[0].message).toContain(JSON.stringify(batchResponse[0])); @@ -219,13 +211,13 @@ describe("Config listVariables", () => { }); it("Should export staging variables for json response", async () => { - const filtered: StagingVariableManifestTransport[] = [ - { packageKey: "pkg-a", variables: [stagingVarsPkgA[0]] }, + const pkgAOnlyResponse: StagingVariableManifestTransport[] = [ + { packageKey: "pkg-a", variables: stagingVarsPkgA }, ]; - const url = withQueryString(stagingVariablesByPackageKeysBaseUrl, { variableType: "SINGLE_VALUE" }); - mockAxiosPost(url, filtered); + const url = stagingVariablesByPackageKeysBaseUrl; + mockAxiosPost(url, pkgAOnlyResponse); - await new ConfigCommandService(testContext).listVariables(true, [], "", ["pkg-a"], "SINGLE_VALUE"); + await new ConfigCommandService(testContext).listVariables(true, [], "", ["pkg-a"]); expect(loggingTestTransport.logMessages.length).toBe(1); expect(loggingTestTransport.logMessages[0].message).toContain(FileService.fileDownloadedMessage); @@ -233,7 +225,7 @@ describe("Config listVariables", () => { const expectedFileName = loggingTestTransport.logMessages[0].message.split(FileService.fileDownloadedMessage)[1]; expect(mockWriteFileSync).toHaveBeenCalledWith( path.resolve(process.cwd(), expectedFileName), - JSON.stringify(filtered), + JSON.stringify(pkgAOnlyResponse), {encoding: "utf-8"} ); diff --git a/tests/commands/configuration-management/module.spec.ts b/tests/commands/configuration-management/module.spec.ts index aed3d91f..7c68a9d3 100644 --- a/tests/commands/configuration-management/module.spec.ts +++ b/tests/commands/configuration-management/module.spec.ts @@ -10,7 +10,6 @@ jest.mock("../../../src/commands/configuration-management/node-dependency.servic /** Mirrors default values on `config variables list` Commander options (keep in sync with module.ts). */ const variablesListOptionDefaults: OptionValues = { packageKeys: [], - variableType: "", keysByVersion: [], keysByVersionFile: "", }; @@ -463,7 +462,6 @@ describe("Configuration Management Module - Action Validations", () => { ...variablesListOptionDefaults, packageKeys: ["pkg-a", "pkg-b"], json: true, - variableType: "SINGLE_VALUE", }; await (module as any).listVariables(testContext, mockCommand, options); @@ -472,8 +470,7 @@ describe("Configuration Management Module - Action Validations", () => { true, [], "", - ["pkg-a", "pkg-b"], - "SINGLE_VALUE" + ["pkg-a", "pkg-b"] ); }); @@ -490,8 +487,7 @@ describe("Configuration Management Module - Action Validations", () => { false, ["k:v"], "", - [], - "" + [] ); }); }); From ccb3f3d6e4f9d3b1098ed7610eb3f9e2a9d257d4 Mon Sep 17 00:00:00 2001 From: Jing Sun Date: Tue, 24 Mar 2026 11:03:38 +0100 Subject: [PATCH 12/13] review --- .../configuration-management/config-command.service.ts | 4 +--- src/commands/configuration-management/module.ts | 4 ++-- tests/commands/configuration-management/module.spec.ts | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/commands/configuration-management/config-command.service.ts b/src/commands/configuration-management/config-command.service.ts index dc9d854c..9716df47 100644 --- a/src/commands/configuration-management/config-command.service.ts +++ b/src/commands/configuration-management/config-command.service.ts @@ -43,9 +43,7 @@ export class ConfigCommandService { } else { await this.variableService.listStagingVariables(packageKeys); } - return; - } - if (jsonResponse) { + } else if (jsonResponse) { await this.variableService.exportVariables(keysByVersion, keysByVersionFile); } else { await this.variableService.listVariables(keysByVersion, keysByVersionFile); diff --git a/src/commands/configuration-management/module.ts b/src/commands/configuration-management/module.ts index 5f494b32..230c9617 100644 --- a/src/commands/configuration-management/module.ts +++ b/src/commands/configuration-management/module.ts @@ -87,7 +87,7 @@ class Module extends IModule { .description("Commands related to variable configs"); variablesCommand.command("list") - .description("List package variables: use --packageKeys for staging (unpublished), or --keysByVersion / --keysByVersionFile for versioned variables") + .description("List package variables: use --packageKeys for staging (unpublished), or --keysByVersion / --keysByVersionFile for versioned packages") .option("--json", "Return response as json type", "") .option("--packageKeys ", "Package keys (staging variables only; mutually exclusive with versioned options)", []) .option("--keysByVersion ", "Mapping of package keys and versions", []) @@ -215,7 +215,7 @@ class Module extends IModule { } if (!hasStagingKeys && !hasVersioned) { throw new Error( - "Please provide --packageKeys for staging variables, or --keysByVersion / --keysByVersionFile for versioned variables." + "Please provide --packageKeys for staging variables, or --keysByVersion / --keysByVersionFile for versioned packages." ); } diff --git a/tests/commands/configuration-management/module.spec.ts b/tests/commands/configuration-management/module.spec.ts index 2ff6d287..c8cbb1f5 100644 --- a/tests/commands/configuration-management/module.spec.ts +++ b/tests/commands/configuration-management/module.spec.ts @@ -453,7 +453,7 @@ describe("Configuration Management Module - Action Validations", () => { await expect( (module as any).listVariables(testContext, mockCommand, options) ).rejects.toThrow( - "Please provide --packageKeys for staging variables, or --keysByVersion / --keysByVersionFile for versioned variables." + "Please provide --packageKeys for staging variables, or --keysByVersion / --keysByVersionFile for versioned packages." ); expect(mockConfigCommandService.listVariables).not.toHaveBeenCalled(); From 28b0462f7c271f3a32762dad84b1c45a374fe09e Mon Sep 17 00:00:00 2001 From: Patrick Vicinus Date: Wed, 25 Mar 2026 12:31:38 +0100 Subject: [PATCH 13/13] Make SonarQube happy --- .../api/staging-package-variables-api.ts | 2 +- src/commands/configuration-management/module.ts | 2 +- src/commands/configuration-management/variable.service.ts | 2 +- tests/commands/configuration-management/module.spec.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/commands/configuration-management/api/staging-package-variables-api.ts b/src/commands/configuration-management/api/staging-package-variables-api.ts index dd052c72..103b22dd 100644 --- a/src/commands/configuration-management/api/staging-package-variables-api.ts +++ b/src/commands/configuration-management/api/staging-package-variables-api.ts @@ -4,7 +4,7 @@ import { HttpClient } from "../../../core/http/http-client"; import { StagingVariableManifestTransport } from "../interfaces/package-export.interfaces"; export class StagingPackageVariablesApi { - private httpClient: () => HttpClient; + private readonly httpClient: () => HttpClient; constructor(context: Context) { this.httpClient = () => context.httpClient; diff --git a/src/commands/configuration-management/module.ts b/src/commands/configuration-management/module.ts index 230c9617..8b236c8d 100644 --- a/src/commands/configuration-management/module.ts +++ b/src/commands/configuration-management/module.ts @@ -215,7 +215,7 @@ class Module extends IModule { } if (!hasStagingKeys && !hasVersioned) { throw new Error( - "Please provide --packageKeys for staging variables, or --keysByVersion / --keysByVersionFile for versioned packages." + "Please provide --packageKeys for staging, or --keysByVersion / --keysByVersionFile for versioned packages." ); } diff --git a/src/commands/configuration-management/variable.service.ts b/src/commands/configuration-management/variable.service.ts index b24633fd..a31811a3 100644 --- a/src/commands/configuration-management/variable.service.ts +++ b/src/commands/configuration-management/variable.service.ts @@ -13,7 +13,7 @@ export class VariableService { private batchImportExportApi: BatchImportExportApi; private variableAssignmentCandidatesApi: VariableAssignmentCandidatesApi; - private stagingPackageVariablesApi: StagingPackageVariablesApi; + private readonly stagingPackageVariablesApi: StagingPackageVariablesApi; private studioService: StudioService; constructor(context: Context) { diff --git a/tests/commands/configuration-management/module.spec.ts b/tests/commands/configuration-management/module.spec.ts index c8cbb1f5..06b034dc 100644 --- a/tests/commands/configuration-management/module.spec.ts +++ b/tests/commands/configuration-management/module.spec.ts @@ -453,7 +453,7 @@ describe("Configuration Management Module - Action Validations", () => { await expect( (module as any).listVariables(testContext, mockCommand, options) ).rejects.toThrow( - "Please provide --packageKeys for staging variables, or --keysByVersion / --keysByVersionFile for versioned packages." + "Please provide --packageKeys for staging, or --keysByVersion / --keysByVersionFile for versioned packages." ); expect(mockConfigCommandService.listVariables).not.toHaveBeenCalled();