Skip to content

Commit 1c15350

Browse files
SP-957: move Team-to-Team Copy commands to 't2tc package' group (#370)
Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent ba4a23e commit 1c15350

29 files changed

Lines changed: 1057 additions & 481 deletions

.github/CODEOWNERS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
/src/core @celonis/studio-platform
66
/src/commands/configuration-management/ @celonis/astro
77
/tests/commands/configuration-management/ @celonis/astro
8+
/src/commands/t2tc/ @celonis/studio-platform
9+
/tests/commands/t2tc/ @celonis/studio-platform
810
/src/commands/profile/ @celonis/studio-platform
911
/src/commands/asset-registry/ @celonis/astro
1012
/tests/commands/asset-registry/ @celonis/astro

docs/user-guide/agentic-development-guide.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,10 +87,10 @@ Or validate during import with the `--validate` flag — note that this runs the
8787
content-cli config import -d <export_dir> --validate --overwrite
8888
```
8989

90-
To also run business-layer rules (PQL parsing, data-model availability, KPI uniqueness, …) and package-settings checks (dependencies, variables, and flavor-specific package settings), run `config validate` against the just-imported staging version:
90+
To also run business-layer rules (PQL parsing, data-model availability, KPI uniqueness, …) and package-settings checks (dependencies, variables, and flavor-specific package settings), run `config package validate` against the just-imported staging version:
9191

9292
```bash
93-
content-cli config validate --packageKey <package-key> --layers SCHEMA BUSINESS PACKAGE_SETTINGS
93+
content-cli config package validate --packageKey <package-key> --layers SCHEMA BUSINESS PACKAGE_SETTINGS
9494
```
9595

9696
If validation returns errors, fix the issues before importing.

docs/user-guide/config-commands.md

Lines changed: 51 additions & 189 deletions
Large diffs are not rendered by default.

docs/user-guide/t2tc-commands.md

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
# Team-to-Team Copy (T2TC) Commands
2+
3+
The `t2tc package` command group moves whole packages — and, optionally, their dependencies and variables — between teams and realms. These commands power the **Team-to-Team Copy** feature: the T2TC backend drives the copy by exporting from a source team and importing into a target team through this CLI.
4+
5+
These commands are a **self-contained, batch-specific set**. `t2tc package export` produces a multi-package **batch archive**, and only the other `t2tc package` commands (`import`, `diff`) understand that archive. It is **not** interchangeable with the package format used by [`config package import`](./config-commands.md#package-commands-config-package) (see [Batch archive vs. package format](#batch-archive-vs-package-format)).
6+
7+
> **Migrating from `config`?** `t2tc package list/export/import/diff` replace the deprecated `config list/export/import/diff` commands. The flags and behaviour are identical — only the command path changed.
8+
9+
| Old (deprecated) | New |
10+
|---|---|
11+
| `config list` | `t2tc package list` |
12+
| `config export` | `t2tc package export` |
13+
| `config import` | `t2tc package import` |
14+
| `config diff` | `t2tc package diff` |
15+
16+
## Permissions
17+
18+
`t2tc package` commands run against the Pacman API and are subject to the same permission checks the platform applies in the UI. The required permission depends on the **flavor** of the target package:
19+
20+
| Package flavor | Required permission |
21+
|---|---|
22+
| **Studio** (Studio packages and their assets) | **Edit package** permission on the package |
23+
| **OCDM** (OCDM packages and their assets) | **Edit** (admin) permission on the **data pool** the OCDM package is connected to |
24+
25+
`t2tc package list` is the one exception: instead of failing, it **filters out packages the profile does not have permission to access**. If a package you expect to see is missing from the list, the most likely cause is missing edit permission on the package (Studio) or on its connected data pool (OCDM).
26+
27+
## Batch archive vs. package format
28+
29+
`t2tc package export` produces a multi-package **batch archive** — a top-level `manifest.json`, `variables.json`, `studio.json`, and one nested `<packageKey>_<version>.zip` per package — that is produced and consumed **only** by the `t2tc package` commands. [`config package import`](./config-commands.md#package-commands-config-package), by contrast, works with a plain **package zip** (a `package.json`, an optional `variables.json`, and a `nodes/` folder). The two formats are **not interchangeable**:
30+
31+
- An archive from `t2tc package export` can be imported with `t2tc package import` or inspected with `t2tc package diff` — but **not** with `config package import`.
32+
- A package zip used by `config package import` **cannot** be imported with `t2tc package import` or diffed with `t2tc package diff`.
33+
34+
Reach for `t2tc package` only for its specific bulk use-case — moving a set of packages together (for example, a migration between teams). To work with a single package, use [`config package import`](./config-commands.md#package-commands-config-package).
35+
36+
## List Packages
37+
38+
Packages can be listed using the following command:
39+
40+
```bash
41+
content-cli t2tc package list -p <sourceProfile>
42+
```
43+
44+
The result will be printed in the console containing only the package name and key:
45+
46+
```bash
47+
info: Package1 - Key: "package-1"
48+
```
49+
50+
By using the `--json` option, packages can be exported (saved) in an extended form as a json file in the current working directory.
51+
52+
```bash
53+
content-cli t2tc package list -p <sourceProfile> --json
54+
```
55+
56+
The name of the file will be printed in the console with the following format:
57+
58+
```bash
59+
info: File downloaded successfully. New filename: 9560f81f-f746-4117-83ee-dd1f614ad624.json
60+
```
61+
62+
By using the `--flavors` option, you can filter which packages to list. The available flavors are: **STUDIO** and **OCDM**.
63+
64+
To list staging packages instead of deployed packages use the `--staging` option. Please note that this flag is not compatible with the below options.
65+
66+
> 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.
67+
68+
### List Packages with Dependencies
69+
70+
When using the listing command with the `--json` option, two additional options are available:
71+
72+
- **--withDependencies**: This option will include the dependencies of the packages in the output.
73+
74+
```bash
75+
content-cli t2tc package list -p <sourceProfile> --withDependencies
76+
```
77+
78+
- **--packageKeys**: This option allows you to filter the packages by their keys. You can specify multiple package keys separated by spaces.
79+
80+
```bash
81+
content-cli t2tc package list -p <sourceProfile> --packageKeys key1 ... keyN
82+
[optional] --withDependencies
83+
```
84+
85+
## Export Packages
86+
87+
Packages can be exported using the following command:
88+
89+
```bash
90+
content-cli t2tc package export -p <sourceProfile> --packageKeys key1 ... keyN
91+
```
92+
93+
The `--keysByVersion` option can be used to export packages by specific version. You can specify multiple packages with version separated by spaces, in the format of 'packageKey.version'.
94+
The `--withDependencies` option can be used to also export dependencies of the given packages.
95+
The `--unzip` option can be used to unzip the exported packages into the current working directory.
96+
97+
Depending on the `--unzip` option used, a zip file, or a directory containing the exported packages, will be created in the current working directory containing:
98+
99+
```bash
100+
exported_package_random_uuid/
101+
├─ manifest.json
102+
├─ variable.json
103+
├─ studio.json
104+
├─ package_key1-version.zip
105+
├─ ...
106+
├─ package_keyN-version.zip
107+
```
108+
109+
### Git Integration for Export
110+
111+
The following **Git options** are available:
112+
113+
- `--gitProfile <gitProfileName>` – specifies the Git profile to use for exporting directly to a repository.
114+
If not specified, the default profile will be used.
115+
- `--gitBranch <branchName>` – specifies the branch in the Github repository where the export will be pushed.
116+
117+
Example exporting to Git:
118+
119+
```bash
120+
content-cli t2tc package export -p <sourceProfile> --packageKeys key1 key2 --gitProfile myGitProfile --gitBranch feature-branch
121+
```
122+
123+
### Export Directory Structure
124+
125+
- manifest.json - File which contains the metadata of the exported packages.
126+
- studio.json - File which contains the metadata of the exported packages in a format compatible with Studio.
127+
- variables.json - File which contains the variables of the exported packages.
128+
- exported packages directories - Directories containing the exported package files, each directory is named after the package key and the version.
129+
130+
Inside each exported package directory, the following files will be present:
131+
132+
- package.json - File which contains the configuration of the exported package.
133+
- nodes/ - Directory containing the nodes of the exported package.
134+
135+
Inside the nodes directory, a file for each node will be present:
136+
137+
- node_key.json - File which contains the configuration of the exported node.
138+
139+
## Import Packages
140+
141+
Packages can be imported using the following commands, if importing from a zip file:
142+
143+
```bash
144+
content-cli t2tc package import -p <sourceProfile> -f <relative exported zip file path>
145+
```
146+
147+
Where `-f` is the short hand operation for `--file`.
148+
If importing from a directory containing the exported packages, the following command can be used:
149+
150+
```bash
151+
content-cli t2tc package import -p <sourceProfile> -d <relative exported directory file path>
152+
```
153+
154+
Where `-d` is the shorthand operation for `--directory`.
155+
When packages with the same keys exist in the target team, the `--overwrite` option can be used for allowing overwriting of those packages. If the package in target environment contains unpublished changes, they are automatically saved under a new version. This allows you to audit, compare, or roll back to your previous state via the version history if needed.
156+
157+
```bash
158+
content-cli t2tc package import -p <sourceProfile> -f <file path> --overwrite
159+
```
160+
161+
### Git Integration for Import
162+
163+
The following **Git options** are available:
164+
165+
- `--gitProfile <gitProfileName>` – specifies the Git profile to use for importing directly from a repository.
166+
If not specified, the default profile will be used.
167+
- `--gitBranch <branchName>` – specifies the branch in the Github repository from which to import.
168+
169+
Example importing from Git:
170+
171+
```bash
172+
content-cli t2tc package import -p <sourceProfile> --gitProfile myGitProfile --gitBranch feature-branch
173+
```
174+
175+
Finally, the result of this command will be a list of PostPackageImportData exported as a json file. The file name will be printed with the following message format:
176+
177+
```bash
178+
info: Config import report file: 9560f81f-f746-4117-83ee-dd1f614ad624.json
179+
```
180+
181+
### Validate During Import
182+
183+
Add `--validate` to `t2tc package import` to run validation against each node **before** the import is committed:
184+
185+
```bash
186+
content-cli t2tc package import -p <sourceProfile> -d <export_dir> --validate --overwrite
187+
```
188+
189+
`t2tc package import --validate` runs the **SCHEMA** layer only. It does **not** run BUSINESS-layer checks (PQL parsing, data-model availability, KPI uniqueness, etc.) or PACKAGE_SETTINGS checks (package dependencies, variables, and flavor-specific package settings). To run those validations, use [`config package validate`](./config-commands.md#validate-package-configurations-config-package-validate) after the import.
190+
191+
## Diff local zip with deployed version/specific version/staging
192+
193+
To compare local zipped packages with online packages use:
194+
195+
```bash
196+
content-cli t2tc package diff --file <file>
197+
```
198+
199+
As with other commands, use `--json` to export the diff to a file.
200+
To diff against a specific version use the `--baseVersion` parameter. When omitted it will diff against the current deployed version.
201+
To diff against staging use `--baseVersion STAGING`.
202+
203+
```bash
204+
content-cli t2tc package diff --file <file> --baseVersion <version>
205+
```
206+
207+
To diff against the current deployed version and only return whether there are any changes, use the `--hasChanges` flag.
208+
209+
```bash
210+
content-cli t2tc package diff --file <file> --hasChanges
211+
```

mkdocs.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ nav:
1313
- Overview: './user-guide/index.md'
1414
- Studio Commands: './user-guide/studio-commands.md'
1515
- Config Commands: './user-guide/config-commands.md'
16+
- T2TC Commands: './user-guide/t2tc-commands.md'
1617
- Deployment Commands: './user-guide/deployment-commands.md'
1718
- Asset Registry Commands: './user-guide/asset-registry-commands.md'
1819
- Data Pool Commands: './user-guide/data-pool-commands.md'
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { PackageMetadataExportTransport } from "../interfaces/package-export.interfaces";
2+
import { FatalError } from "../../../core/utils/logger";
3+
import { HttpClient } from "../../../core/http/http-client";
4+
import { Context } from "../../../core/command/cli-context";
5+
6+
export class MetadataApi {
7+
8+
private readonly httpClient: () => HttpClient;
9+
10+
constructor(context: Context) {
11+
this.httpClient = () => context.httpClient;
12+
}
13+
14+
public async exportPackagesMetadata(packageKeys: string[]): Promise<PackageMetadataExportTransport[]> {
15+
const queryParams = new URLSearchParams();
16+
packageKeys.forEach(packageKey => queryParams.append("packageKeys", packageKey));
17+
18+
return this.httpClient().get(`/package-manager/api/core/packages/metadata/export?${queryParams.toString()}`).catch(e => {
19+
throw new FatalError(`Problem exporting packages metadata: ${e}`);
20+
})
21+
}
22+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { PackageExportTransport } from "../interfaces/package-export.interfaces";
2+
import { FatalError } from "../../../core/utils/logger";
3+
import { HttpClient } from "../../../core/http/http-client";
4+
import { Context } from "../../../core/command/cli-context";
5+
6+
export class StagingPackageApi {
7+
8+
private readonly httpClient: () => HttpClient;
9+
10+
constructor(context: Context) {
11+
this.httpClient = () => context.httpClient;
12+
}
13+
14+
public async findAllStagingPackages(flavors: string[], includeBranches: boolean = false): Promise<PackageExportTransport[]> {
15+
const queryParams = new URLSearchParams();
16+
17+
queryParams.set("includeBranches", includeBranches.toString());
18+
flavors.forEach(flavor => queryParams.append("flavors", flavor));
19+
20+
return this.httpClient().get(`/pacman/api/core/staging/packages/export/list?${queryParams.toString()}`).catch(e => {
21+
throw new FatalError(`Problem getting staging packages: ${e}`);
22+
});
23+
}
24+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { PackageKeyAndVersionPair, VariableManifestTransport } from "../interfaces/package-export.interfaces";
2+
import { FatalError } from "../../../core/utils/logger";
3+
import { HttpClient } from "../../../core/http/http-client";
4+
import { Context } from "../../../core/command/cli-context";
5+
6+
export class VariableApi {
7+
8+
private readonly httpClient: () => HttpClient;
9+
10+
constructor(context: Context) {
11+
this.httpClient = () => context.httpClient;
12+
}
13+
14+
public async findVariablesWithValuesByPackageKeysAndVersion(packagesByKeyAndVersion: PackageKeyAndVersionPair[]): Promise<VariableManifestTransport[]> {
15+
return this.httpClient().post("/package-manager/api/core/packages/export/batch/variables-with-assignments", packagesByKeyAndVersion).catch(e => {
16+
throw new FatalError(`Problem exporting package variables: ${e}`);
17+
})
18+
}
19+
}
Lines changed: 0 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,12 @@
11
import { Context } from "../../core/command/cli-context";
2-
import { BatchImportExportService } from "./batch-import-export.service";
32
import { VariableService } from "./variable.service";
4-
import { DiffService } from "./diff.service";
5-
import { fileService } from "../../core/utils/file-service";
63

74
export class ConfigCommandService {
85

9-
private batchImportExportService: BatchImportExportService;
106
private variableService: VariableService;
11-
private diffService: DiffService;
127

138
constructor(context: Context) {
14-
this.batchImportExportService = new BatchImportExportService(context);
159
this.variableService = new VariableService(context);
16-
this.diffService = new DiffService(context);
17-
}
18-
19-
public async listPackages(
20-
jsonResponse: boolean,
21-
flavors: string[],
22-
withDependencies: boolean,
23-
packageKeys: string[],
24-
keysByVersion: string[],
25-
variableValue: string,
26-
variableType: string,
27-
includeBranches: boolean,
28-
staging: boolean): Promise<void> {
29-
if (staging) {
30-
await this.batchImportExportService.listStagingPackages(flavors ?? [], includeBranches, jsonResponse);
31-
} else if (variableValue) {
32-
await this.listPackagesByVariableValue(jsonResponse, flavors, variableValue, variableType, includeBranches);
33-
} else if (jsonResponse) {
34-
await this.batchImportExportService.findAndExportListOfPackages(flavors ?? [], packageKeys ?? [], keysByVersion ?? [], withDependencies, includeBranches);
35-
} else if (keysByVersion) {
36-
await this.batchImportExportService.listPackagesByKeysWithVersion(keysByVersion, withDependencies);
37-
} else {
38-
await this.batchImportExportService.listActivePackages(flavors ?? [], includeBranches);
39-
}
4010
}
4111

4212
public async listVariables(
@@ -57,44 +27,4 @@ export class ConfigCommandService {
5727
await this.variableService.listVariables(keysByVersion, keysByVersionFile);
5828
}
5929
}
60-
61-
public batchExportPackages(packageKeys: string[], packageKeysByVersion: string[], withDependencies: boolean, gitBranch: string, unzip: boolean): Promise<void> {
62-
return this.batchImportExportService.batchExportPackages(packageKeys, packageKeysByVersion, withDependencies, gitBranch, unzip);
63-
}
64-
65-
public batchExportPackagesMetadata(packageKeys: string[], jsonResponse: boolean): Promise<void> {
66-
return this.batchImportExportService.batchExportPackagesMetadata(packageKeys, jsonResponse);
67-
}
68-
69-
public batchImportPackages(file: string, directory: string, overwrite: boolean, gitBranch: string, performValidation: boolean = false): Promise<void> {
70-
if ((directory || file) && gitBranch) {
71-
throw new Error("You cannot use both file/directory and gitBranch options at the same time. Only one import source can be defined.");
72-
}
73-
if (!directory && !file && !gitBranch) {
74-
throw new Error("You must provide either a file/directory or a gitBranch option to import packages.");
75-
}
76-
if (file && directory) {
77-
throw new Error("You cannot use both file and directory options at the same time. Only one import source can be defined.");
78-
}
79-
if (file && fileService.isDirectory(file)) {
80-
throw new Error("The file option accepts only zip files.");
81-
}
82-
if (directory && !fileService.isDirectory(directory)) {
83-
throw new Error("The directory option accepts only directories.");
84-
}
85-
const sourcePath = file ?? directory;
86-
return this.batchImportExportService.batchImportPackages(sourcePath, overwrite, gitBranch, performValidation);
87-
}
88-
89-
public diffPackages(file: string, hasChanges: boolean, baseVersion: string, jsonResponse: boolean): Promise<void> {
90-
return this.diffService.diffPackages(file, hasChanges, baseVersion, jsonResponse);
91-
}
92-
93-
private async listPackagesByVariableValue(jsonResponse: boolean, flavors: string[], variableValue: string, variableType: string, includeBranches: boolean): Promise<void> {
94-
if (jsonResponse) {
95-
await this.batchImportExportService.findAndExportListOfActivePackagesByVariableValue(flavors ?? [], variableValue, variableType, includeBranches)
96-
} else {
97-
await this.batchImportExportService.listActivePackagesByVariableValue(flavors ?? [], variableValue, variableType, includeBranches);
98-
}
99-
}
10030
}

0 commit comments

Comments
 (0)