Skip to content
Open
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 .claude/skills/create-release/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
name: create-release
description: Bump the Rust workspace version in root Cargo.toml, regenerate the lockfile, and open a draft PR on GitHub. Use this skill whenever the user says something like "create a release", "bump the version", "release vX.Y.Z", "prepare a release branch", or "bump workspace version". Trigger even if they just say "release X.Y.Z" or mention a semver version in a release context.
---

# Create Release

Automate the mechanical parts of cutting a release. The full flow (branch from `main`, bump `[workspace.package].version` in `Cargo.toml`, regenerate `Cargo.lock`, push, open draft PR) is implemented in `scripts/create-release.sh`. This skill's job is to collect the version, then invoke the script.

## Steps

### 1. Get the target version

If the user didn't supply a version already, ask: "What version should I bump to?"

The version must be bare semver (e.g. `32.0.0`, `31.1.0`) — no `v` prefix. The script re-validates and will reject bad input.

### 2. Run the script

```bash
scripts/create-release.sh <version>
```

The script will:

1. Fail fast if the working tree is dirty.
2. `git fetch origin main` and branch `release/v<version>` from `origin/main`.
3. Update only the `version` field in `[workspace.package]` of `Cargo.toml`.
4. Run `cargo update -w` to refresh workspace entries in `Cargo.lock`.
5. Commit (`chore: bump workspace version to <version>`), push with `-u`, and open a draft PR titled `chore: release v<version>` against `main`.

Return the PR URL from the `gh pr create` output to the user.

### 3. On failure

If the script exits non-zero, surface its error to the user and stop — do not try to finish the steps manually without checking in first.
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# Rules are evaluated in order. Later matches override earlier ones.
# More specific patterns at the bottom override general patterns above.

.claude/ @DataDog/apm-common-components-core
.clang-format @DataDog/libdatadog
.codecov.yml @DataDog/apm-common-components-core
.cargo/* @DataDog/libdatadog-core
Expand Down
72 changes: 72 additions & 0 deletions scripts/create-release.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#!/usr/bin/env bash
# Cut a release: branch from main, bump workspace version, regenerate lockfile,
# push, and open a draft PR.
#
# Usage: scripts/create-release.sh <version>
# <version> bare semver, e.g. 32.0.0 (no leading "v")

set -euo pipefail

if [[ $# -ne 1 ]]; then
echo "Usage: $0 <version>" >&2
exit 2
fi

VERSION="$1"

if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$ ]]; then
echo "Error: '$VERSION' is not a valid bare semver (e.g. 32.0.0). Do not include a 'v' prefix." >&2
exit 2
fi

REPO_ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$REPO_ROOT"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mmh since we have access to git, don't we want to use git rev-parse --show-toplevel ?


if [[ -n "$(git status --porcelain --untracked-files=no)" ]]; then
echo "Error: working directory has uncommitted tracked changes. Commit or stash them first." >&2
git status --short --untracked-files=no >&2
exit 1
fi

BRANCH="release/v${VERSION}"

echo "==> Fetching origin/main"
git fetch origin main

echo "==> Creating branch ${BRANCH} from origin/main"
git checkout -b "${BRANCH}" origin/main

echo "==> Bumping [workspace.package] version to ${VERSION} in Cargo.toml"
# Update only the `version = "..."` line inside the [workspace.package] section.
python3 - "$VERSION" <<'PY'
import re, sys, pathlib
version = sys.argv[1]
path = pathlib.Path("Cargo.toml")
text = path.read_text()

pattern = re.compile(
r'(\[workspace\.package\][^\[]*?\nversion\s*=\s*")[^"]*(")',
re.DOTALL,
)
new_text, n = pattern.subn(rf'\g<1>{version}\g<2>', text, count=1)
if n != 1:
sys.exit("Error: could not locate version field in [workspace.package]")
path.write_text(new_text)
PY
Comment on lines +41 to +55
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That looks like a sed thing

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but I believe there are minor differences with sed on mac and linux, so you'd just have to account for that. This might do what you need?

sed -i.bak '/\[workspace\.package\]/,/^\[/{s/^version = ".*"/version = "'"${VERSION}"'"/;}' Cargo.toml


echo "==> Regenerating lockfile (cargo update -w)"
cargo update -w

echo "==> Committing"
git add Cargo.toml Cargo.lock
git commit -m "chore: bump workspace version to ${VERSION}"

echo "==> Pushing ${BRANCH}"
git push -u origin "${BRANCH}"

echo "==> Creating draft PR"
gh pr create \
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might want to start the script with a if [ ! -x "$(which gh)" ]; then echo "gh not installed or couldn't be found"; exit 1; fi or something like this

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think gh is typically available on dd laptops, but good call. Better to fail fast.

--title "chore: release v${VERSION}" \
--body "Bump workspace version to \`${VERSION}\` and regenerate lockfile." \
--base main \
--draft
Loading