Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions docs/command-graph.html
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,36 @@
description: "List packages in the target team. Lists staging packages by default.",
options: ["-p, --profile <profile>", "--json", "--flavors <flavors...>", "-h, --help"] },

// config branch
{ id: "config_branch_area", label: "branch (beta)", group: "subarea", path: "config branch",
description: "Author, merge, and mirror package branches", options: ["-p, --profile <profile>", "-h, --help"] },
{ id: "config_branch_create", label: "create (beta)", group: "command", path: "config branch create",
description: "Create a new branch from a source version",
options: ["-p, --profile <profile>", "--packageKey <packageKey> (Main package key to branch from)", "--branchKey <branchKey>", "--sourceVersion <sourceVersion>", "--validate (default: false)", "--json (default: false)", "-h, --help"] },
{ id: "config_branch_list", label: "list (beta)", group: "command", path: "config branch list",
description: "List branches of a main package",
options: ["-p, --profile <profile>", "--packageKey <packageKey>", "--json (default: false)", "-h, --help"] },
{ id: "config_branch_delete", label: "delete (beta)", group: "command", path: "config branch delete",
description: "Delete a branch",
options: ["-p, --profile <profile>", "--packageKey <packageKey> (Main package key, no '@')", "--branchKey <branchKey>", "-h, --help"] },

// config branch settings
{ id: "config_branch_settings_area", label: "settings (beta)", group: "subsubarea", path: "config branch settings",
description: "Commands to manage branch settings on a package", options: ["-p, --profile <profile>", "-h, --help"] },
{ id: "config_branch_settings_set", label: "set (beta)", group: "command", path: "config branch settings set",
description: "Configure branch settings on a main package",
options: ["-p, --profile <profile>", "--packageKey <packageKey>", "--enabled <enabled> (true|false)", "--json (default: false)", "-h, --help"] },

// config branch merge
{ id: "config_branch_merge_area", label: "merge (beta)", group: "subsubarea", path: "config branch merge",
description: "Commands to preview and apply branch merges", options: ["-p, --profile <profile>", "-h, --help"] },
{ id: "config_branch_merge_preview", label: "preview (beta)", group: "command", path: "config branch merge preview",
description: "Preview the changes a merge would apply to a target branch",
options: ["-p, --profile <profile>", "--packageKey <packageKey> (Target package key: '<packageKey>' or '<packageKey>@<branchKey>')", "--sourceKey <sourceKey>", "--sourceVersion <sourceVersion> (version or \"LATEST\")", "--json (default: false)", "-h, --help"] },
{ id: "config_branch_merge_apply", label: "apply (beta)", group: "command", path: "config branch merge apply",
description: "Merge changes from a source into a target branch",
options: ["-p, --profile <profile>", "--packageKey <packageKey> (Target package key: '<packageKey>' or '<packageKey>@<branchKey>')", "-f, --file <file> (MergeBranchTransport payload; required only when the merge has conflicts)", "--sourceKey <sourceKey> (overrides the file's 'sourceKey')", "--sourceVersion <sourceVersion> (overrides the file's 'sourceVersion'; or \"LATEST\")", "--bump <bump> (PATCH | MINOR | MAJOR)", "--newVersion <newVersion> (pin an explicit semver)", "--summary <summary>", "--json (default: false)", "-h, --help"] },

// config metadata
{ id: "config_metadata_area", label: "metadata", group: "subarea", path: "config metadata",
description: "Commands related to package metadata", options: ["-p, --profile <profile>", "-h, --help"] },
Expand Down Expand Up @@ -471,6 +501,12 @@
["area_config","config_package_area"],
["config_package_area","config_package_import"],["config_package_area","config_package_export"],
["config_package_area","config_package_validate"],["config_package_area","config_package_list"],
["area_config","config_branch_area"],
["config_branch_area","config_branch_create"],["config_branch_area","config_branch_list"],
["config_branch_area","config_branch_delete"],
["config_branch_area","config_branch_settings_area"],["config_branch_settings_area","config_branch_settings_set"],
["config_branch_area","config_branch_merge_area"],
["config_branch_merge_area","config_branch_merge_preview"],["config_branch_merge_area","config_branch_merge_apply"],
["area_config","config_metadata_area"],["config_metadata_area","config_metadata_export"],
["area_config","config_versions_area"],["config_versions_area","config_versions_get"],
["config_versions_area","config_versions_create"],
Expand Down
139 changes: 139 additions & 0 deletions docs/user-guide/branch-commands.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# Branch Commands (beta)

The `config branch` command group lets you author and merge branches from the CLI.

## Concepts

- **Main package** — a regular package, identified by its plain `<packageKey>` (no `@`).
- **Branch** — a separate package keyed `<packageKey>@<branchKey>`, created from a version of the source package. The source can be the main package or another branch.

## Branching Settings

Configure branching settings on a main package. Currently the only option is whether branching is enabled.

```bash
content-cli config branch settings set --packageKey <packageKey> --enabled true
content-cli config branch settings set --packageKey <packageKey> --enabled false
```

The `--packageKey` must be the main package key, not a `<packageKey>@<branchKey>` value.

## Create a Branch

```bash
content-cli config branch create \
--packageKey <packageKey> \
--branchKey <branchKey> \
--sourceVersion 1.4.0
```

`--validate` runs the server-side validator without persisting and prints a success message instead of the created branch.

## List Branches

```bash
content-cli config branch list --packageKey <packageKey>
content-cli config branch list --packageKey <packageKey> --json
```

`--json` writes the raw `BranchTransport[]` payload to a file in the working directory.

Each entry includes `sourcePackageKey` and `sourceVersion`, which together form the join key back to the version the branch was cut from. Multiple branches may share the same source.

## Delete a Branch

```bash
content-cli config branch delete \
--packageKey <packageKey> \
--branchKey <branchKey>
```

- `--packageKey` must be the main package key (no `@`).
- `--branchKey` is the branch to delete.

## Preview and Apply Merges

You can inspect the changes a merge would apply with `merge preview`, then apply them with `merge apply`.

```bash
content-cli config branch merge preview \
--packageKey <targetPackageKey> \
--sourceKey <sourcePackageKey> \
--sourceVersion 1.4.0 \
--json
```

`sourcePackageKey` is typically `<packageKey>@<branchKey>`. `<sourceVersion>` may be a version or `LATEST`.

The JSON preview contains the conflict layout (`changes.configuration.conflicts`, `changes.metadata.conflicts`) and auto-merge results.

### Quick merge

When you do not need to override any node-level resolutions, the resolutions file is optional:

```bash
content-cli config branch merge apply \
--packageKey <targetPackageKey> \
--sourceKey <sourcePackageKey> \
--sourceVersion 1.4.0
```

The CLI posts the merge directly. If the server detects conflicts it returns an error — at that point run `config branch merge preview` to inspect them and re-run `apply` with `-f <resolutions.json>`.

Customise the published version without writing a file:

- `--bump PATCH|MINOR|MAJOR` — pick the bump option (default `PATCH`).
- `--newVersion 1.5.0` — pin an explicit semver.
- `--summary "<text>"` — summary of changes (default `"Merge <sourceKey>@<sourceVersion>"`).

### With a resolutions file

Build a `MergeBranchTransport` resolution file from the preview (one entry per node where a custom resolution is needed), then apply:

```bash
content-cli config branch merge apply \
--packageKey <targetPackageKey> \
--file merge-request.json
```

- `--sourceKey` / `--sourceVersion` override the file's values when you want to keep one resolution file but target different sources.
- `--bump` / `--newVersion` / `--summary` override the file's `versionCreate` block when set.

A valid merge body looks like:

```json
{
"sourceKey": "my-pkg@feature-a",
"sourceVersion": "1.4.0",
"resolvedPackageConflict": null,
"resolvedNodeConflicts": [
{ "nodeKey": "node-1", "resolution": "ACCEPT_SOURCE" }
],
"versionCreate": {
"versionBumpOption": "PATCH",
"summaryOfChanges": "Merge feature-a into main"
}
}
```

`versionCreate` is filled in this order of precedence (highest wins):

1. `--newVersion <semver>` flag or `--bump PATCH|MINOR|MAJOR` flag (and `--summary`).
2. Whatever the resolutions file already has under `versionCreate`.
3. Default: `versionBumpOption: PATCH` + `summaryOfChanges: "Merge <sourceKey>@<sourceVersion>"`.

`resolution` per node accepts `ACCEPT_SOURCE`, `ACCEPT_TARGET`, or `CUSTOM`. `CUSTOM` requires the matching `customConfigurationChanges` / `customMetadataChanges` JSON-Patch ops.

> **Custom changes are restricted to paths the preview already touched.** Each op in `customConfigurationChanges` / `customMetadataChanges` must reference a `path` that appears in either the preview's `sourceChanges` or `targetChanges` for that node — you cannot introduce edits at paths neither side modified. The server rejects ops at unrelated paths.

Worked example: the preview reports that for node `node-1`, the source set `/title` to `"Source title"` and the target set `/title` to `"Target title"` (a conflict at `/title`). A valid CUSTOM resolution can pick a third value for `/title`, but it cannot also touch `/description` (which neither side changed):

```json
{
"nodeKey": "node-1",
"resolution": "CUSTOM",
"customConfigurationChanges": [
{ "op": "replace", "path": "/title", "value": "Negotiated title" }
]
}
```
1 change: 1 addition & 0 deletions docs/user-guide/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Content CLI organizes its commands into groups by area. Each group covers a spec
|---|-------------------------------------------------------------------------------|
| [Studio Commands](./studio-commands.md) | Pull and push packages, assets, spaces, and widgets to and from Studio |
| [Config Commands](./config-commands.md) | List, batch export, and import all packages and their configurations |
| [Branch Commands](./branch-commands.md) | Create, list, merge, and delete package branches |
| [Deployment Commands](./deployment-commands.md) | Create deployments, list history, check active deployments, and manage targets |
| [Asset Registry Commands](./asset-registry-commands.md) | Discover registered asset types and their service descriptors |
| [Data Pool Commands](./data-pool-commands.md) | Export and import Data Pools with their dependencies |
Expand Down
1 change: 1 addition & 0 deletions mkdocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ nav:
- Overview: './user-guide/index.md'
- Studio Commands: './user-guide/studio-commands.md'
- Config Commands: './user-guide/config-commands.md'
- Branch Commands: './user-guide/branch-commands.md'
- T2TC Commands: './user-guide/t2tc-commands.md'
- Deployment Commands: './user-guide/deployment-commands.md'
- Asset Registry Commands: './user-guide/asset-registry-commands.md'
Expand Down
74 changes: 74 additions & 0 deletions src/commands/configuration-management/branch/api/branch.api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { HttpClient } from "../../../../core/http/http-client";
import { Context } from "../../../../core/command/cli-context";
import { FatalError } from "../../../../core/utils/logger";
import {
BranchingSettingsTransport,
BranchTransport,
CreateBranchTransport,
MergeBranchTransport,
MergePreviewRequestTransport,
MergePreviewTransport,
PackageVersionCreatedTransport,
} from "../interfaces/branch.interfaces";

export class BranchApi {
private readonly httpClient: () => HttpClient;

constructor(context: Context) {
this.httpClient = () => context.httpClient;
}

public async configureBranchingSettings(packageKey: string, transport: BranchingSettingsTransport): Promise<BranchingSettingsTransport> {
return this.httpClient()
.put(`/pacman/api/core/packages/${packageKey}/branch-settings`, transport)
.catch(e => {
throw new FatalError(`Problem updating branch settings for package ${packageKey}: ${e}`);
});
}

public async createBranch(packageKey: string, transport: CreateBranchTransport, validate: boolean = false): Promise<BranchTransport | void> {
const params = new URLSearchParams();
if (validate) {
params.set("validate", "true");
}
const query = params.toString().length ? `?${params.toString()}` : "";

return this.httpClient()
.post(`/pacman/api/core/packages/${packageKey}/branches${query}`, transport)
.catch(e => {
throw new FatalError(`Problem creating branch '${transport.branchKey}' from package ${packageKey}: ${e}`);
});
}

public async listBranches(packageKey: string): Promise<BranchTransport[]> {
return this.httpClient()
.get(`/pacman/api/core/packages/${packageKey}/branches`)
.catch(e => {
throw new FatalError(`Problem listing branches for package ${packageKey}: ${e}`);
});
}

public async mergePreview(packageKey: string, transport: MergePreviewRequestTransport): Promise<MergePreviewTransport> {
return this.httpClient()
.post(`/pacman/api/core/staging/packages/${packageKey}/merge/preview`, transport)
.catch(e => {
throw new FatalError(`Problem previewing merge into package ${packageKey} from ${transport.sourceKey}@${transport.sourceVersion}: ${e}`);
});
}

public async merge(packageKey: string, transport: MergeBranchTransport): Promise<PackageVersionCreatedTransport> {
return this.httpClient()
.post(`/pacman/api/core/staging/packages/${packageKey}/merge`, transport)
.catch(e => {
throw new FatalError(`Problem merging into package ${packageKey} from ${transport.sourceKey}@${transport.sourceVersion}: ${e}`);
});
}

public async deleteBranch(branchPackageKey: string): Promise<void> {
return this.httpClient()
.delete(`/pacman/api/core/staging/packages/${branchPackageKey}/purge`)
.catch(e => {
throw new FatalError(`Problem deleting branch ${branchPackageKey}: ${e}`);
});
}
}
Loading
Loading