diff --git a/docs/user-guide/t2tc-commands.md b/docs/user-guide/t2tc-commands.md index b31abe55..5cf43300 100644 --- a/docs/user-guide/t2tc-commands.md +++ b/docs/user-guide/t2tc-commands.md @@ -59,12 +59,22 @@ 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**, **OCDM** and **CCM**. To list staging packages instead of deployed packages use the `--staging` option. Please note that this flag is not compatible with the below options. > For day-to-day staging listing outside the copy workflow, prefer [`config package list`](./config-commands.md#list-packages-config-package-list), which lists staging packages by default. +### List Live Packages + +Use the `--live` option to list only the package each project's **LIVE** pointer targets. Projects without a LIVE pointer are skipped. If the live branch pointer feature is not enabled for the team, the deployed main packages are listed instead. + +``` +content-cli t2tc package list --profile my-profile --live --flavors CCM --json +``` + +`--live` can be combined with `--json`, `--flavors` and `--withDependencies`. It is not compatible with `--staging`, `--branches`, `--packageKeys`, `--keysByVersion`, `--variableValue` and `--variableType`. + ### List Packages with Dependencies When using the listing command with the `--json` option, two additional options are available: diff --git a/src/commands/t2tc/api/t2tc-package-api.ts b/src/commands/t2tc/api/t2tc-package-api.ts index 27e4cf0b..ba951917 100644 --- a/src/commands/t2tc/api/t2tc-package-api.ts +++ b/src/commands/t2tc/api/t2tc-package-api.ts @@ -27,6 +27,17 @@ export class T2tcPackageApi { }); } + public async findAllLivePackages(flavors: string[], withDependencies: boolean = false): Promise { + const queryParams = new URLSearchParams(); + + queryParams.set("withDependencies", withDependencies.toString()); + flavors.forEach(flavor => queryParams.append("flavors", flavor)); + + return this.httpClient().get(`/package-manager/api/core/packages/export/list-live?${queryParams.toString()}`).catch(e => { + throw new FatalError(`Problem getting live packages: ${e}`); + }); + } + public async findActivePackagesByVariableValue(flavors: string[], variableValue: string, variableType: string, includeBranches: boolean = false): Promise { const queryParams = new URLSearchParams(); diff --git a/src/commands/t2tc/module.ts b/src/commands/t2tc/module.ts index 7da3374d..5d90aa1e 100644 --- a/src/commands/t2tc/module.ts +++ b/src/commands/t2tc/module.ts @@ -23,6 +23,7 @@ class Module extends IModule { .option("--variableType ", "Variable type for filtering packages by.") .option("--branches", "Include branches", false) .option("--staging", "List staging packages instead", false) + .option("--live", "List only the package each LIVE pointer targets", false) .action(this.listPackages); t2tcPackageCommand.command("export") @@ -61,6 +62,9 @@ class Module extends IModule { if (options.packageKeys && options.keysByVersion) { throw new Error("Please provide either --packageKeys or --keysByVersion, but not both."); } + if (options.live && (options.staging || options.branches || options.packageKeys || options.keysByVersion || options.variableValue || options.variableType)) { + throw new Error("Live parameter is not compatible with --staging, --branches, --packageKeys, --keysByVersion, --variableValue, --variableType"); + } await new T2tcCommandService(context).listPackages( options.json, @@ -71,7 +75,8 @@ class Module extends IModule { options.variableValue, options.variableType, options.branches, - options.staging); + options.staging, + options.live); } private async batchExportPackages(context: Context, command: Command, options: OptionValues): Promise { diff --git a/src/commands/t2tc/t2tc-command.service.ts b/src/commands/t2tc/t2tc-command.service.ts index eb75e313..75df9f70 100644 --- a/src/commands/t2tc/t2tc-command.service.ts +++ b/src/commands/t2tc/t2tc-command.service.ts @@ -25,8 +25,11 @@ export class T2tcCommandService { variableValue: string, variableType: string, includeBranches: boolean, - staging: boolean): Promise { - if (staging) { + staging: boolean, + live: boolean = false): Promise { + if (live) { + await this.t2tcPackageService.listLivePackages(flavors ?? [], withDependencies, jsonResponse); + } else if (staging) { await this.stagingPackageService.listStagingPackages(flavors ?? [], includeBranches, jsonResponse); } else if (variableValue) { await this.listPackagesByVariableValue(jsonResponse, flavors, variableValue, variableType, includeBranches); diff --git a/src/commands/t2tc/t2tc-package.service.ts b/src/commands/t2tc/t2tc-package.service.ts index 35301ede..bc4a244b 100644 --- a/src/commands/t2tc/t2tc-package.service.ts +++ b/src/commands/t2tc/t2tc-package.service.ts @@ -49,6 +49,19 @@ export class T2tcPackageService { }); } + public async listLivePackages(flavors: string[], withDependencies: boolean, jsonResponse: boolean): Promise { + const livePackages = await this.t2tcPackageApi.findAllLivePackages(flavors, withDependencies); + + if (jsonResponse) { + await this.exportListOfPackages(await this.studioService.getExportPackagesWithStudioData(livePackages, withDependencies)); + return; + } + + livePackages.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/tests/commands/t2tc/module.spec.ts b/tests/commands/t2tc/module.spec.ts index 270adfdb..fc009c4d 100644 --- a/tests/commands/t2tc/module.spec.ts +++ b/tests/commands/t2tc/module.spec.ts @@ -99,9 +99,50 @@ describe("T2TC Module", () => { undefined, undefined, undefined, + undefined, undefined ); }); + + it.each([ + ["--staging", { staging: true }], + ["--branches", { branches: true }], + ["--packageKeys", { packageKeys: ["package1"] }], + ["--keysByVersion", { keysByVersion: ["package1.1.0.0"] }], + ["--variableValue", { variableValue: "value" }], + ["--variableType", { variableType: "DATA_MODEL" }], + ])("throws when --live is combined with %s", async (_name: string, extraOptions: OptionValues) => { + const options: OptionValues = { live: true, ...extraOptions }; + + await expect( + (module as any).listPackages(testContext, mockCommand, options) + ).rejects.toThrow("Live parameter is not compatible with"); + + expect(mockT2tcCommandService.listPackages).not.toHaveBeenCalled(); + }); + + it("delegates --live to T2tcCommandService", async () => { + const options: OptionValues = { + json: true, + flavors: ["CCM"], + live: true, + }; + + await (module as any).listPackages(testContext, mockCommand, options); + + expect(mockT2tcCommandService.listPackages).toHaveBeenCalledWith( + true, + ["CCM"], + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + true + ); + }); }); describe("batchExportPackages", () => { diff --git a/tests/commands/t2tc/t2tc-package-list.spec.ts b/tests/commands/t2tc/t2tc-package-list.spec.ts index fa8510a3..d9f0845b 100644 --- a/tests/commands/t2tc/t2tc-package-list.spec.ts +++ b/tests/commands/t2tc/t2tc-package-list.spec.ts @@ -16,7 +16,8 @@ describe("Config list", () => { it.each([ "", - "STUDIO,TEST" + "STUDIO,TEST", + "CCM" ])( "Should list all packages by key for non-json response with flavors: %p", async (flavors: string) => { @@ -61,6 +62,18 @@ describe("Config list", () => { expect(exportedFirstPackage).toEqual({...firstPackage, spaceId: "spaceId-1"}); }) + it("Should export CCM packages unchanged for json response with CCM flavor", async () => { + const ccmPackage = {...PacmanApiUtils.buildPackageExportTransport("ccm-key", "ccm-name"), flavor: "CCM"}; + + mockAxiosGet("https://myTeam.celonis.cloud/package-manager/api/core/packages/export/list?withDependencies=false&includeBranches=false&flavors=CCM", [{...ccmPackage}]); + mockAxiosGet("https://myTeam.celonis.cloud/package-manager/api/packages/with-variable-assignments?type=DATA_MODEL", []); + + await new T2tcCommandService(testContext).listPackages(true, ["CCM"], false, [], undefined, null, null, false, false); + + const exportedTransports = getJsonFromDownloadedFile() as PackageExportTransport[]; + expect(exportedTransports).toEqual([ccmPackage]); + }) + it("Should export all packages with dependencies and data models set for json response and withDependencies option", async () => { const firstPackage = PacmanApiUtils.buildPackageExportTransport("key-1", "name-1"); const secondPackage = PacmanApiUtils.buildPackageExportTransport("key-2", "name-2"); @@ -251,6 +264,31 @@ describe("Config list", () => { expect(exportedFirstPackage).toEqual({...firstPackage, spaceId: "spaceId-1"}); }) + it("Should list live packages by key for non-json response", async () => { + const livePackage = PacmanApiUtils.buildPackageExportTransport("project@release", "live-name"); + + mockAxiosGet("https://myTeam.celonis.cloud/package-manager/api/core/packages/export/list-live?withDependencies=false&flavors=CCM", [livePackage]); + + await new T2tcCommandService(testContext).listPackages(false, ["CCM"], false, [], undefined, null, null, false, false, true); + + expect(loggingTestTransport.logMessages.length).toBe(1); + expect(loggingTestTransport.logMessages[0].message).toContain(`${livePackage.name} - Key: "${livePackage.key}"`); + }) + + it("Should export live packages for json response with spaceId set for studio packages", async () => { + const studioLivePackage = {...PacmanApiUtils.buildPackageExportTransport("studio@release", "studio-name"), flavor: "STUDIO"}; + const ccmLivePackage = {...PacmanApiUtils.buildPackageExportTransport("ccm@live", "ccm-name"), flavor: "CCM"}; + const studioNode: ContentNodeTransport = PacmanApiUtils.buildContentNodeTransport("studio@release", "spaceId-1"); + + mockAxiosGet("https://myTeam.celonis.cloud/package-manager/api/core/packages/export/list-live?withDependencies=false", [{...studioLivePackage}, {...ccmLivePackage}]); + mockAxiosGet("https://myTeam.celonis.cloud/package-manager/api/packages/with-variable-assignments?type=DATA_MODEL", [studioNode]); + + await new T2tcCommandService(testContext).listPackages(true, [], false, [], undefined, null, null, false, false, true); + + const exportedTransports = getJsonFromDownloadedFile() as PackageExportTransport[]; + expect(exportedTransports).toEqual([{...studioLivePackage, spaceId: "spaceId-1"}, ccmLivePackage]); + }) + 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");