From 0137a029ba14a5900ff6c6012aa48ebc7726a33f Mon Sep 17 00:00:00 2001 From: pbio <10051819+paulbalaji@users.noreply.github.com> Date: Mon, 13 Oct 2025 12:31:09 +0100 Subject: [PATCH 01/17] feat: agents release workflow --- .github/workflows/rust-docker.yml | 3 + .github/workflows/rust-release.yml | 409 ++++++++++++++++++++++++++ rust/main/Cargo.lock | 66 ++--- rust/main/Cargo.toml | 2 +- rust/main/cliff.toml | 53 ++++ rust/main/hyperlane-metric/Cargo.toml | 2 +- rust/sealevel/Cargo.lock | 8 +- 7 files changed, 504 insertions(+), 39 deletions(-) create mode 100644 .github/workflows/rust-release.yml create mode 100644 rust/main/cliff.toml diff --git a/.github/workflows/rust-docker.yml b/.github/workflows/rust-docker.yml index f36c895b24..7ab01b8055 100644 --- a/.github/workflows/rust-docker.yml +++ b/.github/workflows/rust-docker.yml @@ -61,6 +61,9 @@ jobs: tags: | type=ref,event=branch type=ref,event=pr + type=ref,event=tag + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} type=raw,value=${{ steps.taggen.outputs.TAG_SHA }}-${{ steps.taggen.outputs.TAG_DATE }} - name: Set up Depot CLI uses: depot/setup-action@v1 diff --git a/.github/workflows/rust-release.yml b/.github/workflows/rust-release.yml new file mode 100644 index 0000000000..ba0191702a --- /dev/null +++ b/.github/workflows/rust-release.yml @@ -0,0 +1,409 @@ +name: Rust Agent Release + +on: + push: + branches: + - main + paths: + - 'rust/main/**' + - '.github/workflows/rust-release.yml' + workflow_dispatch: + inputs: + prerelease: + description: 'Create a pre-release instead of a stable release' + required: false + type: boolean + default: false + prerelease_suffix: + description: 'Custom prerelease suffix (e.g., "beta.1", "rc.1", "alpha.2"). Leave empty to auto-generate.' + required: false + type: string + default: '' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +permissions: + contents: write + pull-requests: write + +env: + CARGO_TERM_COLOR: always + RUST_BACKTRACE: full + +jobs: + check-release-status: + name: Check Release Status + runs-on: depot-ubuntu-latest + outputs: + has_changes: ${{ steps.check_changes.outputs.has_changes }} + should_release: ${{ steps.check_version.outputs.should_release }} + current_version: ${{ steps.check_version.outputs.current_version }} + latest_version: ${{ steps.check_version.outputs.latest_version }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Check if there are changes since last release + id: check_changes + working-directory: ./rust/main + run: | + # Get latest agents-v* tag + LATEST_TAG=$(git tag -l "agents-v*" --sort=-version:refname | grep -E "^agents-v[0-9]+\.[0-9]+\.[0-9]+$" | head -1) + + if [ -z "$LATEST_TAG" ]; then + echo "No previous release found" + echo "has_changes=true" >> $GITHUB_OUTPUT + else + # Check if there are commits to rust/main since last release + COMMITS_SINCE=$(git log "$LATEST_TAG"..HEAD --oneline -- . | wc -l) + echo "Commits since $LATEST_TAG: $COMMITS_SINCE" + + if [ "$COMMITS_SINCE" -gt 0 ]; then + echo "Found $COMMITS_SINCE commit(s) since last release" + echo "has_changes=true" >> $GITHUB_OUTPUT + else + echo "No commits since last release" + echo "has_changes=false" >> $GITHUB_OUTPUT + fi + fi + - name: Check if version changed (for publish decision) + id: check_version + working-directory: ./rust/main + run: | + # Get current version from Cargo.toml workspace.package.version + CURRENT_VERSION=$(grep -A 10 '^\[workspace\.package\]' Cargo.toml | grep '^version = ' | head -1 | sed 's/version = "\(.*\)"/\1/') + echo "current_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT + echo "Current workspace version: $CURRENT_VERSION" + + # Get latest agents-v* tag + LATEST_TAG=$(git tag -l "agents-v*" --sort=-version:refname | grep -E "^agents-v[0-9]+\.[0-9]+\.[0-9]+$" | head -1) + + if [ -z "$LATEST_TAG" ]; then + echo "latest_version=" >> $GITHUB_OUTPUT + echo "No previous release tag found, will create first release" + echo "should_release=true" >> $GITHUB_OUTPUT + else + LATEST_VERSION=$(echo "$LATEST_TAG" | sed 's/agents-v//') + echo "latest_version=$LATEST_VERSION" >> $GITHUB_OUTPUT + echo "Latest released version: $LATEST_VERSION" + + if [ "$CURRENT_VERSION" != "$LATEST_VERSION" ]; then + echo "Version has changed ($LATEST_VERSION -> $CURRENT_VERSION)" + echo "should_release=true" >> $GITHUB_OUTPUT + else + echo "Version unchanged" + echo "should_release=false" >> $GITHUB_OUTPUT + fi + fi + + release-pr: + name: Update Release PR + runs-on: depot-ubuntu-latest + needs: check-release-status + if: | + (github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && !inputs.prerelease)) && + needs.check-release-status.outputs.has_changes == 'true' + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: dtolnay/rust-toolchain@stable + - name: Install git-cliff + uses: taiki-e/install-action@v2 + with: + tool: git-cliff + - name: Determine next version from conventional commits + id: next_version + working-directory: ./rust/main + env: + CURRENT_VERSION: ${{ needs.check-release-status.outputs.current_version }} + run: | + # Get commits since last release + LATEST_TAG=$(git tag -l "agents-v*" --sort=-version:refname | grep -E "^agents-v[0-9]+\.[0-9]+\.[0-9]+$" | head -1) + + if [ -z "$LATEST_TAG" ]; then + COMMITS=$(git log --oneline --no-merges -- .) + else + COMMITS=$(git log "${LATEST_TAG}..HEAD" --oneline --no-merges -- .) + fi + + # Parse current version + IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT_VERSION" + + # Analyze commits for conventional commit types + HAS_BREAKING=false + HAS_FEAT=false + HAS_FIX=false + + while IFS= read -r commit; do + if echo "$commit" | grep -qE "^[a-f0-9]+ [a-z]+(\(.+\))?!:"; then + HAS_BREAKING=true + elif echo "$commit" | grep -qE "^[a-f0-9]+ feat(\(.+\))?:"; then + HAS_FEAT=true + elif echo "$commit" | grep -qE "^[a-f0-9]+ fix(\(.+\))?:"; then + HAS_FIX=true + fi + + # Check commit body for BREAKING CHANGE + COMMIT_HASH=$(echo "$commit" | cut -d' ' -f1) + if git show -s --format=%B "$COMMIT_HASH" | grep -q "BREAKING CHANGE:"; then + HAS_BREAKING=true + fi + done <<< "$COMMITS" + + # Determine version bump + if [ "$HAS_BREAKING" = true ]; then + MAJOR=$((MAJOR + 1)) + MINOR=0 + PATCH=0 + BUMP_TYPE="major" + elif [ "$HAS_FEAT" = true ]; then + MINOR=$((MINOR + 1)) + PATCH=0 + BUMP_TYPE="minor" + elif [ "$HAS_FIX" = true ]; then + PATCH=$((PATCH + 1)) + BUMP_TYPE="patch" + else + # Default to patch for any other changes + PATCH=$((PATCH + 1)) + BUMP_TYPE="patch" + fi + + NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}" + echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT + echo "bump_type=$BUMP_TYPE" >> $GITHUB_OUTPUT + echo "Next version: $NEW_VERSION ($BUMP_TYPE bump from $CURRENT_VERSION)" + - name: Generate changelog + id: changelog + working-directory: ./rust/main + env: + NEW_VERSION: ${{ steps.next_version.outputs.new_version }} + run: | + # Generate changelog for commits since last release + LATEST_TAG=$(git tag -l "agents-v*" --sort=-version:refname | grep -E "^agents-v[0-9]+\.[0-9]+\.[0-9]+$" | head -1) + + if [ -z "$LATEST_TAG" ]; then + CHANGELOG=$(git-cliff --config cliff.toml --unreleased --strip all) + else + CHANGELOG=$(git-cliff --config cliff.toml "${LATEST_TAG}..HEAD" --strip all) + fi + + # Save changelog to file for PR body + echo "$CHANGELOG" > /tmp/changelog.md + + # Also output for GitHub Actions + { + echo 'changelog<> $GITHUB_OUTPUT + - name: Update version files + working-directory: ./rust/main + env: + NEW_VERSION: ${{ steps.next_version.outputs.new_version }} + run: | + # Update workspace version in Cargo.toml + # Use awk to find [workspace.package] section and update version within it + awk -v new_version="$NEW_VERSION" ' + /^\[workspace\.package\]/ { in_workspace=1 } + /^\[/ && !/^\[workspace\.package\]/ { in_workspace=0 } + in_workspace && /^version = / { + print "version = \"" new_version "\"" + next + } + { print } + ' Cargo.toml > Cargo.toml.new + mv Cargo.toml.new Cargo.toml + echo "Updated Cargo.toml workspace version to $NEW_VERSION" + + # Update Cargo.lock in rust/main + cargo update --workspace --offline 2>/dev/null || cargo update --workspace + echo "Updated rust/main/Cargo.lock" + + # Update Cargo.lock in rust/sealevel + cd ../sealevel + cargo update --workspace --offline 2>/dev/null || cargo update --workspace + echo "Updated rust/sealevel/Cargo.lock" + cd ../main + + # Prepend new version to CHANGELOG.md + if [ -f CHANGELOG.md ]; then + CURRENT_CHANGELOG=$(cat CHANGELOG.md) + else + CURRENT_CHANGELOG="" + fi + + cat > CHANGELOG.md </dev/null || echo "") + + if [ -n "$EXISTING_PR" ]; then + echo "Updating existing PR #$EXISTING_PR" + gh pr edit "$EXISTING_PR" \ + --title "release: agents v${NEW_VERSION}" \ + --body "$PR_BODY" + echo "Updated PR: $(gh pr view $EXISTING_PR --json url --jq .url)" + else + echo "Creating new draft PR" + gh pr create \ + --title "release: agents v${NEW_VERSION}" \ + --body "$PR_BODY" \ + --base main \ + --head "$BRANCH_NAME" \ + --label "release" \ + --draft + PR_URL=$(gh pr list --head "$BRANCH_NAME" --json url --jq '.[0].url') + echo "Created draft PR: $PR_URL" + fi + - name: Summary + if: always() + env: + NEW_VERSION: ${{ steps.next_version.outputs.new_version }} + run: | + echo "### Release PR for agents v${NEW_VERSION}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "The release PR has been created/updated." >> $GITHUB_STEP_SUMMARY + echo "Once merged, the release will be published automatically." >> $GITHUB_STEP_SUMMARY + + publish: + name: Publish Release + runs-on: depot-ubuntu-latest + needs: check-release-status + if: | + github.ref == 'refs/heads/main' && + ((github.event_name == 'workflow_dispatch' && inputs.prerelease) || + (github.event_name == 'push' && needs.check-release-status.outputs.should_release == 'true')) + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Install git-cliff + uses: taiki-e/install-action@v2 + with: + tool: git-cliff + - name: Determine version and create release + working-directory: ./rust/main + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + IS_PRERELEASE: ${{ github.event_name == 'workflow_dispatch' && inputs.prerelease }} + PRERELEASE_SUFFIX: ${{ inputs.prerelease_suffix }} + BASE_VERSION: ${{ needs.check-release-status.outputs.current_version }} + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + # Determine final version based on release type + if [ "$IS_PRERELEASE" = "true" ]; then + # Pre-release: append suffix + if [ -n "$PRERELEASE_SUFFIX" ]; then + SUFFIX="$PRERELEASE_SUFFIX" + else + # Auto-generate beta.N + LAST_BETA=$(git tag -l "agents-v${BASE_VERSION}-beta.*" | sort -V | tail -1) + if [ -z "$LAST_BETA" ]; then + SUFFIX="beta.1" + else + BETA_NUM=$(echo "$LAST_BETA" | sed 's/.*beta\.\([0-9]*\)/\1/') + SUFFIX="beta.$((BETA_NUM + 1))" + fi + fi + VERSION="${BASE_VERSION}-${SUFFIX}" + TITLE="Agents $VERSION (Pre-release)" + PRERELEASE_FLAG="--prerelease" + RELEASE_TYPE="Pre-release" + else + # Stable release + VERSION="$BASE_VERSION" + TITLE="Agents $VERSION" + PRERELEASE_FLAG="" + RELEASE_TYPE="Release" + fi + + TAG_NAME="agents-v${VERSION}" + echo "Creating $RELEASE_TYPE: $TAG_NAME" + + # Generate changelog + PREV_TAG=$(git describe --tags --abbrev=0 --match "agents-v*" 2>/dev/null || echo "") + if [ -z "$PREV_TAG" ]; then + CHANGELOG=$(git-cliff --config cliff.toml --unreleased --strip all) + else + CHANGELOG=$(git-cliff --config cliff.toml --latest --strip all) + fi + + # Add warning for pre-releases + if [ "$IS_PRERELEASE" = "true" ]; then + CHANGELOG="⚠️ **This is a pre-release version.**"$'\n\n'"${CHANGELOG}" + fi + + # Create tag and GitHub release + git tag -a "$TAG_NAME" -m "$RELEASE_TYPE $TAG_NAME" + git push origin "$TAG_NAME" + + gh release create "$TAG_NAME" \ + --title "$TITLE" \ + --notes "$CHANGELOG" \ + $PRERELEASE_FLAG \ + --repo "${{ github.repository }}" + + echo "$RELEASE_TYPE $TAG_NAME published successfully!" >> $GITHUB_STEP_SUMMARY + [ "$IS_PRERELEASE" = "true" ] && echo "This is marked as a pre-release on GitHub." >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Binary artifacts will be built automatically by the agent-release-artifacts workflow." >> $GITHUB_STEP_SUMMARY diff --git a/rust/main/Cargo.lock b/rust/main/Cargo.lock index 14f63bbce1..6f4194b5d4 100644 --- a/rust/main/Cargo.lock +++ b/rust/main/Cargo.lock @@ -14,7 +14,7 @@ dependencies = [ [[package]] name = "abigen" -version = "0.1.0" +version = "1.5.0" dependencies = [ "Inflector", "cainome", @@ -2767,7 +2767,7 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "crypto" -version = "0.1.0" +version = "1.5.0" dependencies = [ "elliptic-curve 0.13.8", "hex 0.4.3", @@ -4022,7 +4022,7 @@ dependencies = [ [[package]] name = "ethers-prometheus" -version = "0.1.0" +version = "1.5.0" dependencies = [ "abigen", "async-trait", @@ -5533,7 +5533,7 @@ dependencies = [ [[package]] name = "hyperlane-application" -version = "0.1.0" +version = "1.5.0" dependencies = [ "serde", "serde_json", @@ -5542,7 +5542,7 @@ dependencies = [ [[package]] name = "hyperlane-base" -version = "0.1.0" +version = "1.5.0" dependencies = [ "anyhow", "async-trait", @@ -5577,7 +5577,7 @@ dependencies = [ "hyperlane-sealevel", "hyperlane-starknet", "hyperlane-test", - "itertools 0.12.1", + "itertools 0.10.5", "maplit", "mockall", "moka", @@ -5610,7 +5610,7 @@ dependencies = [ [[package]] name = "hyperlane-core" -version = "0.1.0" +version = "1.5.0" dependencies = [ "async-rwlock", "async-trait", @@ -5633,7 +5633,7 @@ dependencies = [ "getrandom 0.2.15", "hex 0.4.3", "hyperlane-application", - "itertools 0.12.1", + "itertools 0.10.5", "num 0.4.3", "num-derive 0.4.2", "num-traits", @@ -5656,7 +5656,7 @@ dependencies = [ [[package]] name = "hyperlane-cosmos" -version = "0.1.0" +version = "1.5.0" dependencies = [ "async-trait", "base64 0.21.7", @@ -5664,7 +5664,7 @@ dependencies = [ "cometbft", "cometbft-rpc", "cosmrs", - "cosmwasm-std 2.1.3", + "cosmwasm-std 1.5.7", "crypto", "derive-new", "futures", @@ -5681,7 +5681,7 @@ dependencies = [ "ibc-proto", "injective-protobuf", "injective-std", - "itertools 0.12.1", + "itertools 0.10.5", "once_cell", "pin-project", "protobuf", @@ -5694,7 +5694,7 @@ dependencies = [ "time", "tokio", "tonic 0.12.3", - "tower 0.5.2", + "tower 0.4.13", "tracing", "tracing-futures", "url", @@ -5737,7 +5737,7 @@ dependencies = [ [[package]] name = "hyperlane-ethereum" -version = "0.1.0" +version = "1.5.0" dependencies = [ "abigen", "async-trait", @@ -5756,7 +5756,7 @@ dependencies = [ "hyperlane-metric", "hyperlane-operation-verifier", "hyperlane-warp-route", - "itertools 0.12.1", + "itertools 0.10.5", "num 0.4.3", "num-traits", "reqwest 0.11.27", @@ -5772,7 +5772,7 @@ dependencies = [ [[package]] name = "hyperlane-fuel" -version = "0.1.0" +version = "1.5.0" dependencies = [ "abigen", "anyhow", @@ -5789,7 +5789,7 @@ dependencies = [ [[package]] name = "hyperlane-metric" -version = "0.1.0" +version = "1.5.0" dependencies = [ "async-trait", "derive-new", @@ -5803,7 +5803,7 @@ dependencies = [ [[package]] name = "hyperlane-operation-verifier" -version = "0.1.0" +version = "1.5.0" dependencies = [ "async-trait", "hyperlane-application", @@ -5995,7 +5995,7 @@ dependencies = [ [[package]] name = "hyperlane-starknet" -version = "0.1.0" +version = "1.5.0" dependencies = [ "abigen", "anyhow", @@ -6019,7 +6019,7 @@ dependencies = [ [[package]] name = "hyperlane-test" -version = "0.1.0" +version = "1.5.0" dependencies = [ "async-trait", "hyperlane-core", @@ -6028,7 +6028,7 @@ dependencies = [ [[package]] name = "hyperlane-warp-route" -version = "0.1.0" +version = "1.5.0" dependencies = [ "hyperlane-core", ] @@ -6509,7 +6509,7 @@ dependencies = [ [[package]] name = "lander" -version = "0.1.0" +version = "1.5.0" dependencies = [ "async-trait", "chrono", @@ -6526,7 +6526,7 @@ dependencies = [ "hyperlane-ethereum", "hyperlane-radix", "hyperlane-sealevel", - "itertools 0.12.1", + "itertools 0.10.5", "mockall", "prometheus", "radix-common", @@ -6848,7 +6848,7 @@ dependencies = [ [[package]] name = "migration" -version = "0.1.0" +version = "1.5.0" dependencies = [ "sea-orm", "sea-orm-migration", @@ -8808,7 +8808,7 @@ checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" [[package]] name = "relayer" -version = "0.1.0" +version = "1.5.0" dependencies = [ "async-trait", "axum 0.8.4", @@ -8833,7 +8833,7 @@ dependencies = [ "hyperlane-metric", "hyperlane-operation-verifier", "hyperlane-test", - "itertools 0.12.1", + "itertools 0.10.5", "lander", "maplit", "mockall", @@ -8853,7 +8853,7 @@ dependencies = [ "tokio", "tokio-metrics", "tokio-test", - "tower 0.5.2", + "tower 0.4.13", "tracing", "tracing-futures", "tracing-subscriber", @@ -8966,7 +8966,7 @@ dependencies = [ [[package]] name = "reqwest-utils" -version = "0.1.0" +version = "1.5.0" dependencies = [ "reqwest 0.11.27", "thiserror 1.0.63", @@ -9176,7 +9176,7 @@ dependencies = [ [[package]] name = "run-locally" -version = "0.1.0" +version = "1.5.0" dependencies = [ "anyhow", "core-api-client", @@ -9685,7 +9685,7 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "scraper" -version = "0.1.0" +version = "1.5.0" dependencies = [ "async-trait", "config", @@ -9699,7 +9699,7 @@ dependencies = [ "hyperlane-core", "hyperlane-ethereum", "hyperlane-test", - "itertools 0.12.1", + "itertools 0.10.5", "migration", "num-bigint 0.4.6", "num-traits", @@ -12944,7 +12944,7 @@ dependencies = [ [[package]] name = "validator" -version = "0.1.0" +version = "1.5.0" dependencies = [ "async-trait", "aws-config", @@ -12964,7 +12964,7 @@ dependencies = [ "hyperlane-cosmos", "hyperlane-ethereum", "hyperlane-test", - "itertools 0.12.1", + "itertools 0.10.5", "k256 0.13.4", "mockall", "prometheus", @@ -12976,7 +12976,7 @@ dependencies = [ "thiserror 1.0.63", "tokio", "tokio-test", - "tower 0.5.2", + "tower 0.4.13", "tracing", "tracing-futures", "tracing-test", diff --git a/rust/main/Cargo.toml b/rust/main/Cargo.toml index 8bdc6b1e49..5c9c3f1dab 100644 --- a/rust/main/Cargo.toml +++ b/rust/main/Cargo.toml @@ -33,7 +33,7 @@ edition = "2021" homepage = "https://hyperlane.xyz" license-file = "../LICENSE.md" publish = false -version = "0.1.0" +version = "1.5.0" [workspace.dependencies] Inflector = "0.11.4" diff --git a/rust/main/cliff.toml b/rust/main/cliff.toml new file mode 100644 index 0000000000..79d00d248e --- /dev/null +++ b/rust/main/cliff.toml @@ -0,0 +1,53 @@ +# git-cliff configuration for Hyperlane Rust agents +# Documentation: https://git-cliff.org/docs/configuration + +[changelog] +# changelog header +header = """ +## What's Changed\n +""" +# template for the changelog body +# https://keats.github.io/tera/docs/#introduction +body = """ +{% for group, commits in commits | group_by(attribute="group") %} + ### {{ group | striptags | trim | upper_first }} + {% for commit in commits %} + * {% if commit.breaking %}**BREAKING**: {% endif %}{{ commit.message | split(pat="\n") | first | trim }} (#{{ commit.id | truncate(length=7, end="") }})\ + {% endfor %} +{% endfor %}\n +""" +# template for the changelog footer +footer = """ + +""" +# remove the leading and trailing whitespace from the templates +trim = true + +[git] +# parse the commits based on https://www.conventionalcommits.org +conventional_commits = true +# filter out the commits that are not conventional +filter_unconventional = false +# process each line of a commit as an individual commit +split_commits = false +# regex for parsing and grouping commits +commit_parsers = [ + { message = "^feat", group = "Features" }, + { message = "^fix", group = "Bug Fixes" }, + { message = "^perf", group = "Performance" }, + { message = "^chore", group = "Miscellaneous" }, + { message = "^refactor", group = "Refactor" }, + { message = "^doc", skip = true }, + { message = "^test", skip = true }, + { message = "^style", skip = true }, +] +# protect breaking changes from being skipped due to matching a skipping commit_parser +protect_breaking_commits = true +# filter out the commits that are not matched by commit parsers +filter_commits = true +# regex for matching git tags +tag_pattern = "agents-v[0-9]+\\.[0-9]+\\.[0-9]+$" +# sort the tags topologically +topo_order = false +# sort the commits inside sections by oldest/newest order +sort_commits = "newest" diff --git a/rust/main/hyperlane-metric/Cargo.toml b/rust/main/hyperlane-metric/Cargo.toml index 48c8f96407..c6ac11f588 100644 --- a/rust/main/hyperlane-metric/Cargo.toml +++ b/rust/main/hyperlane-metric/Cargo.toml @@ -15,4 +15,4 @@ maplit.workspace = true prometheus.workspace = true serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } -url.workspace = true \ No newline at end of file +url.workspace = true diff --git a/rust/sealevel/Cargo.lock b/rust/sealevel/Cargo.lock index 7ab7997794..651be71887 100644 --- a/rust/sealevel/Cargo.lock +++ b/rust/sealevel/Cargo.lock @@ -2401,7 +2401,7 @@ dependencies = [ [[package]] name = "hyperlane-application" -version = "0.1.0" +version = "1.5.0" dependencies = [ "serde", "serde_json", @@ -2410,7 +2410,7 @@ dependencies = [ [[package]] name = "hyperlane-core" -version = "0.1.0" +version = "1.5.0" dependencies = [ "async-rwlock", "async-trait", @@ -2428,7 +2428,7 @@ dependencies = [ "getrandom 0.2.15", "hex", "hyperlane-application", - "itertools 0.13.0", + "itertools 0.10.5", "num 0.4.3", "num-derive 0.4.2", "num-traits", @@ -2828,7 +2828,7 @@ dependencies = [ [[package]] name = "hyperlane-warp-route" -version = "0.1.0" +version = "1.5.0" dependencies = [ "hyperlane-core", ] From e61f659673ce222b4d379afbf638f338b42285f1 Mon Sep 17 00:00:00 2001 From: pbio <10051819+paulbalaji@users.noreply.github.com> Date: Mon, 13 Oct 2025 19:53:41 +0100 Subject: [PATCH 02/17] workspace-grouped changelog, no more git-cliff! --- .github/workflows/rust-release.yml | 35 ++++----- rust/main/cliff.toml | 53 ------------- rust/scripts/generate-workspace-changelog.sh | 81 ++++++++++++++++++++ 3 files changed, 98 insertions(+), 71 deletions(-) delete mode 100644 rust/main/cliff.toml create mode 100755 rust/scripts/generate-workspace-changelog.sh diff --git a/.github/workflows/rust-release.yml b/.github/workflows/rust-release.yml index ba0191702a..2ffa369f18 100644 --- a/.github/workflows/rust-release.yml +++ b/.github/workflows/rust-release.yml @@ -110,10 +110,6 @@ jobs: with: fetch-depth: 0 - uses: dtolnay/rust-toolchain@stable - - name: Install git-cliff - uses: taiki-e/install-action@v2 - with: - tool: git-cliff - name: Determine next version from conventional commits id: next_version working-directory: ./rust/main @@ -178,17 +174,16 @@ jobs: echo "Next version: $NEW_VERSION ($BUMP_TYPE bump from $CURRENT_VERSION)" - name: Generate changelog id: changelog - working-directory: ./rust/main env: NEW_VERSION: ${{ steps.next_version.outputs.new_version }} run: | - # Generate changelog for commits since last release + # Generate workspace-grouped changelog using custom script LATEST_TAG=$(git tag -l "agents-v*" --sort=-version:refname | grep -E "^agents-v[0-9]+\.[0-9]+\.[0-9]+$" | head -1) if [ -z "$LATEST_TAG" ]; then - CHANGELOG=$(git-cliff --config cliff.toml --unreleased --strip all) + CHANGELOG=$(./rust/scripts/generate-workspace-changelog.sh) else - CHANGELOG=$(git-cliff --config cliff.toml "${LATEST_TAG}..HEAD" --strip all) + CHANGELOG=$(./rust/scripts/generate-workspace-changelog.sh "${LATEST_TAG}..HEAD") fi # Save changelog to file for PR body @@ -276,11 +271,11 @@ jobs: git push -f origin "$BRANCH_NAME" # Create or update PR - PR_BODY="## Release agents v${NEW_VERSION} + PR_BODY="# Release agents v${NEW_VERSION} This PR prepares the release of Hyperlane agents version **${NEW_VERSION}** (${BUMP_TYPE} bump). - ### What's Changed + ## What's Changed ${CHANGELOG} @@ -335,12 +330,7 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Install git-cliff - uses: taiki-e/install-action@v2 - with: - tool: git-cliff - name: Determine version and create release - working-directory: ./rust/main env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} IS_PRERELEASE: ${{ github.event_name == 'workflow_dispatch' && inputs.prerelease }} @@ -380,12 +370,21 @@ jobs: TAG_NAME="agents-v${VERSION}" echo "Creating $RELEASE_TYPE: $TAG_NAME" - # Generate changelog + # Generate workspace-grouped changelog PREV_TAG=$(git describe --tags --abbrev=0 --match "agents-v*" 2>/dev/null || echo "") + + # For stable releases (push to main), use HEAD~1 to exclude the version bump commit + # For prereleases (workflow_dispatch), use HEAD since there's no version bump commit + if [ "$IS_PRERELEASE" = "true" ]; then + COMMIT_RANGE_END="HEAD" + else + COMMIT_RANGE_END="HEAD~1" + fi + if [ -z "$PREV_TAG" ]; then - CHANGELOG=$(git-cliff --config cliff.toml --unreleased --strip all) + CHANGELOG=$(./rust/scripts/generate-workspace-changelog.sh) else - CHANGELOG=$(git-cliff --config cliff.toml --latest --strip all) + CHANGELOG=$(./rust/scripts/generate-workspace-changelog.sh "${PREV_TAG}..${COMMIT_RANGE_END}") fi # Add warning for pre-releases diff --git a/rust/main/cliff.toml b/rust/main/cliff.toml deleted file mode 100644 index 79d00d248e..0000000000 --- a/rust/main/cliff.toml +++ /dev/null @@ -1,53 +0,0 @@ -# git-cliff configuration for Hyperlane Rust agents -# Documentation: https://git-cliff.org/docs/configuration - -[changelog] -# changelog header -header = """ -## What's Changed\n -""" -# template for the changelog body -# https://keats.github.io/tera/docs/#introduction -body = """ -{% for group, commits in commits | group_by(attribute="group") %} - ### {{ group | striptags | trim | upper_first }} - {% for commit in commits %} - * {% if commit.breaking %}**BREAKING**: {% endif %}{{ commit.message | split(pat="\n") | first | trim }} (#{{ commit.id | truncate(length=7, end="") }})\ - {% endfor %} -{% endfor %}\n -""" -# template for the changelog footer -footer = """ - -""" -# remove the leading and trailing whitespace from the templates -trim = true - -[git] -# parse the commits based on https://www.conventionalcommits.org -conventional_commits = true -# filter out the commits that are not conventional -filter_unconventional = false -# process each line of a commit as an individual commit -split_commits = false -# regex for parsing and grouping commits -commit_parsers = [ - { message = "^feat", group = "Features" }, - { message = "^fix", group = "Bug Fixes" }, - { message = "^perf", group = "Performance" }, - { message = "^chore", group = "Miscellaneous" }, - { message = "^refactor", group = "Refactor" }, - { message = "^doc", skip = true }, - { message = "^test", skip = true }, - { message = "^style", skip = true }, -] -# protect breaking changes from being skipped due to matching a skipping commit_parser -protect_breaking_commits = true -# filter out the commits that are not matched by commit parsers -filter_commits = true -# regex for matching git tags -tag_pattern = "agents-v[0-9]+\\.[0-9]+\\.[0-9]+$" -# sort the tags topologically -topo_order = false -# sort the commits inside sections by oldest/newest order -sort_commits = "newest" diff --git a/rust/scripts/generate-workspace-changelog.sh b/rust/scripts/generate-workspace-changelog.sh new file mode 100755 index 0000000000..3a9d7a0fe2 --- /dev/null +++ b/rust/scripts/generate-workspace-changelog.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +# Script to generate workspace-grouped changelog for rust agents +set -euo pipefail + +# Determine script directory and repo structure +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +RUST_MAIN_DIR="$SCRIPT_DIR/../main" + +# Get the commit range +if [ $# -eq 0 ]; then + # No arguments - use unreleased commits + LATEST_TAG=$(git tag -l "agents-v*" --sort=-version:refname | grep -E "^agents-v[0-9]+\.[0-9]+\.[0-9]+$" | head -1 || echo "") + if [ -z "$LATEST_TAG" ]; then + COMMIT_RANGE="HEAD" + else + COMMIT_RANGE="${LATEST_TAG}..HEAD" + fi +else + COMMIT_RANGE="$1" +fi + +# Temporary directory for categorization +TEMP_DIR=$(mktemp -d) +trap "rm -rf $TEMP_DIR" EXIT + +# Extract workspace members from rust/main/Cargo.toml +WORKSPACE_MEMBERS=$(grep -A 100 '^\[workspace\]' "$RUST_MAIN_DIR/Cargo.toml" | sed -n '/^members = \[/,/^\]/p' | grep '"' | sed 's/[", ]//g') + +# Get all commits in the range (filter to rust/main directory) +git log --no-merges --format="%H" $COMMIT_RANGE -- rust/main | while read -r commit_hash; do + # Get commit message + commit_msg=$(git log -1 --format="%s" "$commit_hash") + short_hash=$(echo "$commit_hash" | cut -c1-7) + + # Get files changed in this commit (within rust/main) + files=$(git diff-tree --no-commit-id --name-only -r "$commit_hash" -- rust/main) + + # Categorize based on workspace membership + workspace="" + for file in $files; do + # Strip rust/main/ prefix if present + file=$(echo "$file" | sed 's|^rust/main/||') + + # Check which workspace this file belongs to + for member in $WORKSPACE_MEMBERS; do + if [[ "$file" =~ ^"$member"(/|$) ]]; then + workspace="$member" + break 2 # Break both loops + fi + done + done + + # Default to "Other" if no workspace found + if [ -z "$workspace" ]; then + workspace="Other" + fi + + # Sanitize workspace name for file system (replace / with __) + workspace_file=$(echo "$workspace" | tr '/' '_') + + # Store commit in workspace category file + echo "$workspace|$commit_msg|$short_hash" >> "$TEMP_DIR/$workspace_file" +done + +# Process workspace members in the order they appear in Cargo.toml, then "Other" +for workspace in $WORKSPACE_MEMBERS "Other"; do + workspace_file=$(echo "$workspace" | tr '/' '_') + + if [ -f "$TEMP_DIR/$workspace_file" ]; then + echo "### $workspace" + echo "" + + # Sort and deduplicate commits (extract workspace name, msg, hash) + sort -u "$TEMP_DIR/$workspace_file" | while IFS='|' read -r ws msg hash; do + echo "* $msg (#$hash)" + done + echo "" + fi +done + +echo "" From fb90ce933c7107b1be654b00b294b5b7da4ee611 Mon Sep 17 00:00:00 2001 From: pbio <10051819+paulbalaji@users.noreply.github.com> Date: Mon, 13 Oct 2025 20:17:50 +0100 Subject: [PATCH 03/17] split up changelogs + changelog generation even further --- .github/workflows/rust-release.yml | 41 ++-- rust/scripts/generate-workspace-changelog.sh | 199 +++++++++++++++++-- 2 files changed, 201 insertions(+), 39 deletions(-) diff --git a/.github/workflows/rust-release.yml b/.github/workflows/rust-release.yml index 2ffa369f18..3968e730ea 100644 --- a/.github/workflows/rust-release.yml +++ b/.github/workflows/rust-release.yml @@ -177,13 +177,20 @@ jobs: env: NEW_VERSION: ${{ steps.next_version.outputs.new_version }} run: | - # Generate workspace-grouped changelog using custom script + # Get commit range for changelog generation LATEST_TAG=$(git tag -l "agents-v*" --sort=-version:refname | grep -E "^agents-v[0-9]+\.[0-9]+\.[0-9]+$" | head -1) if [ -z "$LATEST_TAG" ]; then - CHANGELOG=$(./rust/scripts/generate-workspace-changelog.sh) + COMMIT_RANGE="" + else + COMMIT_RANGE="${LATEST_TAG}..HEAD" + fi + + # Generate unified changelog for PR body + if [ -z "$COMMIT_RANGE" ]; then + CHANGELOG=$(./rust/scripts/generate-workspace-changelog.sh --no-header) else - CHANGELOG=$(./rust/scripts/generate-workspace-changelog.sh "${LATEST_TAG}..HEAD") + CHANGELOG=$(./rust/scripts/generate-workspace-changelog.sh "$COMMIT_RANGE" --no-header) fi # Save changelog to file for PR body @@ -195,6 +202,9 @@ jobs: echo "$CHANGELOG" echo 'EOF' } >> $GITHUB_OUTPUT + + # Generate per-workspace CHANGELOG.md files + ./rust/scripts/generate-workspace-changelog.sh "$COMMIT_RANGE" "" --write-to-workspace "$NEW_VERSION" - name: Update version files working-directory: ./rust/main env: @@ -222,26 +232,6 @@ jobs: cd ../sealevel cargo update --workspace --offline 2>/dev/null || cargo update --workspace echo "Updated rust/sealevel/Cargo.lock" - cd ../main - - # Prepend new version to CHANGELOG.md - if [ -f CHANGELOG.md ]; then - CURRENT_CHANGELOG=$(cat CHANGELOG.md) - else - CURRENT_CHANGELOG="" - fi - - cat > CHANGELOG.md </dev/null || true # Commit changes git commit -m "release: agents v${NEW_VERSION} diff --git a/rust/scripts/generate-workspace-changelog.sh b/rust/scripts/generate-workspace-changelog.sh index 3a9d7a0fe2..f2f64dab86 100755 --- a/rust/scripts/generate-workspace-changelog.sh +++ b/rust/scripts/generate-workspace-changelog.sh @@ -1,22 +1,81 @@ #!/usr/bin/env bash # Script to generate workspace-grouped changelog for rust agents +# +# Usage: +# generate-workspace-changelog.sh [COMMIT_RANGE] [WORKSPACE_FILTER] [FLAGS] +# +# Arguments: +# COMMIT_RANGE - Git commit range (e.g., "v1.4.0..HEAD"). Defaults to latest tag..HEAD +# WORKSPACE_FILTER - Optional comma-separated list of workspaces to include (e.g., "agents/relayer,agents/validator") +# If omitted, all workspaces are included +# +# Flags: +# --no-header - Omit the "## What's Changed" header (useful for composing changelogs) +# --write-to-workspace VERSION - Update CHANGELOG.md files in each workspace directory +# +# Examples: +# ./generate-workspace-changelog.sh # All workspaces, latest tag..HEAD +# ./generate-workspace-changelog.sh "v1.4.0..v1.5.0" # All workspaces, specific range +# ./generate-workspace-changelog.sh "v1.4.0..v1.5.0" "agents/relayer" # Single workspace +# ./generate-workspace-changelog.sh "v1.4.0..v1.5.0" "agents/relayer,agents/validator" # Multiple workspaces +# ./generate-workspace-changelog.sh "v1.4.0..v1.5.0" "agents/relayer" --no-header # No header +# ./generate-workspace-changelog.sh "v1.4.0..v1.5.0" "" --write-to-workspace "1.5.0" # Update workspace CHANGELOGs +# set -euo pipefail # Determine script directory and repo structure SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" RUST_MAIN_DIR="$SCRIPT_DIR/../main" +# Parse arguments +COMMIT_RANGE="" +WORKSPACE_FILTER="" +SHOW_HEADER=true +UPDATE_CHANGELOGS=false +VERSION="" + +# Parse all arguments +i=1 +while [ $i -le $# ]; do + arg="${!i}" + + if [ "$arg" = "--no-header" ]; then + SHOW_HEADER=false + elif [ "$arg" = "--write-to-workspace" ]; then + UPDATE_CHANGELOGS=true + # Get next argument as version + i=$((i + 1)) + if [ $i -le $# ]; then + VERSION="${!i}" + else + echo "Error: --write-to-workspace requires a VERSION argument" >&2 + exit 1 + fi + elif [ -z "$COMMIT_RANGE" ]; then + COMMIT_RANGE="$arg" + elif [ -z "$WORKSPACE_FILTER" ]; then + WORKSPACE_FILTER="$arg" + fi + + i=$((i + 1)) +done + # Get the commit range -if [ $# -eq 0 ]; then - # No arguments - use unreleased commits +if [ -z "$COMMIT_RANGE" ]; then + # No commit range specified - use unreleased commits LATEST_TAG=$(git tag -l "agents-v*" --sort=-version:refname | grep -E "^agents-v[0-9]+\.[0-9]+\.[0-9]+$" | head -1 || echo "") if [ -z "$LATEST_TAG" ]; then COMMIT_RANGE="HEAD" else COMMIT_RANGE="${LATEST_TAG}..HEAD" fi +fi + +# Parse workspace filter into array +if [ -n "$WORKSPACE_FILTER" ]; then + IFS=',' read -ra FILTER_ARRAY <<< "$WORKSPACE_FILTER" else - COMMIT_RANGE="$1" + FILTER_ARRAY=() fi # Temporary directory for categorization @@ -26,6 +85,25 @@ trap "rm -rf $TEMP_DIR" EXIT # Extract workspace members from rust/main/Cargo.toml WORKSPACE_MEMBERS=$(grep -A 100 '^\[workspace\]' "$RUST_MAIN_DIR/Cargo.toml" | sed -n '/^members = \[/,/^\]/p' | grep '"' | sed 's/[", ]//g') +# Helper function to check if a workspace should be included +should_include_workspace() { + local workspace="$1" + + # If no filter specified, include all + if [ ${#FILTER_ARRAY[@]} -eq 0 ]; then + return 0 + fi + + # Check if workspace is in filter list + for filter in "${FILTER_ARRAY[@]}"; do + if [ "$workspace" = "$filter" ]; then + return 0 + fi + done + + return 1 +} + # Get all commits in the range (filter to rust/main directory) git log --no-merges --format="%H" $COMMIT_RANGE -- rust/main | while read -r commit_hash; do # Get commit message @@ -50,9 +128,9 @@ git log --no-merges --format="%H" $COMMIT_RANGE -- rust/main | while read -r com done done - # Default to "Other" if no workspace found + # Default to "other" if no workspace found if [ -z "$workspace" ]; then - workspace="Other" + workspace="other" fi # Sanitize workspace name for file system (replace / with __) @@ -62,20 +140,113 @@ git log --no-merges --format="%H" $COMMIT_RANGE -- rust/main | while read -r com echo "$workspace|$commit_msg|$short_hash" >> "$TEMP_DIR/$workspace_file" done -# Process workspace members in the order they appear in Cargo.toml, then "Other" -for workspace in $WORKSPACE_MEMBERS "Other"; do - workspace_file=$(echo "$workspace" | tr '/' '_') +# Function to generate changelog for a specific workspace +generate_workspace_changelog() { + local workspace="$1" + local include_header="${2:-true}" # Default to including header + local workspace_file=$(echo "$workspace" | tr '/' '_') if [ -f "$TEMP_DIR/$workspace_file" ]; then - echo "### $workspace" - echo "" - - # Sort and deduplicate commits (extract workspace name, msg, hash) + if [ "$include_header" = "true" ]; then + echo "### $workspace" + echo "" + fi sort -u "$TEMP_DIR/$workspace_file" | while IFS='|' read -r ws msg hash; do echo "* $msg (#$hash)" done - echo "" + fi +} + +# If updating workspace changelogs, update files and exit +if [ "$UPDATE_CHANGELOGS" = true ]; then + if [ -z "$VERSION" ]; then + echo "Error: VERSION is required for --write-to-workspace" >&2 + exit 1 + fi + + echo "Updating workspace CHANGELOG.md files for version $VERSION..." + UPDATED_COUNT=0 + + for workspace in $WORKSPACE_MEMBERS; do + # Skip if workspace is filtered out + if ! should_include_workspace "$workspace"; then + continue + fi + + workspace_file=$(echo "$workspace" | tr '/' '_') + + # Skip if no changes for this workspace + if [ ! -f "$TEMP_DIR/$workspace_file" ]; then + continue + fi + + # Generate changelog content for this workspace (no header) + WORKSPACE_CHANGELOG=$(generate_workspace_changelog "$workspace" false) + + if [ -z "$WORKSPACE_CHANGELOG" ]; then + continue + fi + + WORKSPACE_CHANGELOG_FILE="$RUST_MAIN_DIR/$workspace/CHANGELOG.md" + WORKSPACE_DIR=$(dirname "$WORKSPACE_CHANGELOG_FILE") + + # Ensure directory exists + mkdir -p "$WORKSPACE_DIR" + + # Read existing changelog if it exists + if [ -f "$WORKSPACE_CHANGELOG_FILE" ]; then + CURRENT_WORKSPACE_CHANGELOG=$(cat "$WORKSPACE_CHANGELOG_FILE") + else + CURRENT_WORKSPACE_CHANGELOG="" + fi + + # Prepend new version to workspace changelog + { + echo "## [$VERSION] - $(date +%Y-%m-%d)" + echo "" + echo "$WORKSPACE_CHANGELOG" + if [ -n "$CURRENT_WORKSPACE_CHANGELOG" ]; then + echo "" + echo "$CURRENT_WORKSPACE_CHANGELOG" + fi + } > "$WORKSPACE_CHANGELOG_FILE" + + echo "Updated $workspace/CHANGELOG.md" + UPDATED_COUNT=$((UPDATED_COUNT + 1)) + done + + echo "Updated $UPDATED_COUNT workspace changelog(s)" + exit 0 +fi + +# Generate output (for display/PR body) +if [ "$SHOW_HEADER" = true ]; then + echo "## What's Changed" + echo "" +fi + +# Process workspace members in the order they appear in Cargo.toml, then "other" +FIRST_WORKSPACE=true +for workspace in $WORKSPACE_MEMBERS "other"; do + # Skip if workspace is filtered out + if ! should_include_workspace "$workspace"; then + continue + fi + + workspace_file=$(echo "$workspace" | tr '/' '_') + + if [ -f "$TEMP_DIR/$workspace_file" ]; then + # Add separator between workspaces (except before first one) + if [ "$FIRST_WORKSPACE" = false ]; then + echo "" + fi + FIRST_WORKSPACE=false + + generate_workspace_changelog "$workspace" fi done -echo "" +if [ "$SHOW_HEADER" = true ]; then + echo "" + echo "" +fi From 5523fd65ce659f743032db543ab2d4bb3ffc2089 Mon Sep 17 00:00:00 2001 From: pbio <10051819+paulbalaji@users.noreply.github.com> Date: Tue, 14 Oct 2025 11:09:41 +0100 Subject: [PATCH 04/17] new contributors section --- .github/workflows/rust-release.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/.github/workflows/rust-release.yml b/.github/workflows/rust-release.yml index 3968e730ea..9d40ba68e2 100644 --- a/.github/workflows/rust-release.yml +++ b/.github/workflows/rust-release.yml @@ -378,11 +378,34 @@ jobs: CHANGELOG=$(./rust/scripts/generate-workspace-changelog.sh "${PREV_TAG}..${COMMIT_RANGE_END}") fi + # Generate new contributors section using GitHub's auto-generated notes + NEW_CONTRIBUTORS="" + if [ -n "$PREV_TAG" ]; then + # Use GitHub API to generate release notes and extract new contributors + AUTO_NOTES=$(gh api --method POST "/repos/${{ github.repository }}/releases/generate-notes" \ + -f tag_name="$TAG_NAME" \ + -f target_commitish="$COMMIT_RANGE_END" \ + -f previous_tag_name="$PREV_TAG" \ + --jq '.body' 2>/dev/null || echo "") + + # Extract the "New Contributors" section if it exists + if [ -n "$AUTO_NOTES" ]; then + NEW_CONTRIBUTORS=$(echo "$AUTO_NOTES" | sed -n '/^## New Contributors$/,/^## /p' | sed '$d') + # If we got the section, add proper spacing + if [ -n "$NEW_CONTRIBUTORS" ]; then + NEW_CONTRIBUTORS=$'\n\n'"${NEW_CONTRIBUTORS}" + fi + fi + fi + # Add warning for pre-releases if [ "$IS_PRERELEASE" = "true" ]; then CHANGELOG="⚠️ **This is a pre-release version.**"$'\n\n'"${CHANGELOG}" fi + # Append new contributors section + CHANGELOG="${CHANGELOG}${NEW_CONTRIBUTORS}" + # Create tag and GitHub release git tag -a "$TAG_NAME" -m "$RELEASE_TYPE $TAG_NAME" git push origin "$TAG_NAME" From ba2bb50c40e2682f8c317a9611fb2778a58cf5e4 Mon Sep 17 00:00:00 2001 From: pbio <10051819+paulbalaji@users.noreply.github.com> Date: Tue, 14 Oct 2025 11:21:39 +0100 Subject: [PATCH 05/17] update categorisation --- .github/workflows/rust-release.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/rust-release.yml b/.github/workflows/rust-release.yml index 9d40ba68e2..bf8756994d 100644 --- a/.github/workflows/rust-release.yml +++ b/.github/workflows/rust-release.yml @@ -130,16 +130,16 @@ jobs: # Analyze commits for conventional commit types HAS_BREAKING=false - HAS_FEAT=false - HAS_FIX=false + HAS_MINOR=false + HAS_PATCH=false while IFS= read -r commit; do if echo "$commit" | grep -qE "^[a-f0-9]+ [a-z]+(\(.+\))?!:"; then HAS_BREAKING=true - elif echo "$commit" | grep -qE "^[a-f0-9]+ feat(\(.+\))?:"; then - HAS_FEAT=true - elif echo "$commit" | grep -qE "^[a-f0-9]+ fix(\(.+\))?:"; then - HAS_FIX=true + elif echo "$commit" | grep -qE "^[a-f0-9]+ (feat|refactor|perf|chore)(\(.+\))?:"; then + HAS_MINOR=true + elif echo "$commit" | grep -qE "^[a-f0-9]+ (fix|docs|test|ci|style|build)(\(.+\))?:"; then + HAS_PATCH=true fi # Check commit body for BREAKING CHANGE @@ -155,11 +155,11 @@ jobs: MINOR=0 PATCH=0 BUMP_TYPE="major" - elif [ "$HAS_FEAT" = true ]; then + elif [ "$HAS_MINOR" = true ]; then MINOR=$((MINOR + 1)) PATCH=0 BUMP_TYPE="minor" - elif [ "$HAS_FIX" = true ]; then + elif [ "$HAS_PATCH" = true ]; then PATCH=$((PATCH + 1)) BUMP_TYPE="patch" else From 77c4735604816ccfec1b55ff11061b80c5f950a4 Mon Sep 17 00:00:00 2001 From: pbio <10051819+paulbalaji@users.noreply.github.com> Date: Tue, 14 Oct 2025 11:29:19 +0100 Subject: [PATCH 06/17] don't need short hash since messages already contain PR number --- rust/scripts/generate-workspace-changelog.sh | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/rust/scripts/generate-workspace-changelog.sh b/rust/scripts/generate-workspace-changelog.sh index f2f64dab86..2b269ff616 100755 --- a/rust/scripts/generate-workspace-changelog.sh +++ b/rust/scripts/generate-workspace-changelog.sh @@ -108,7 +108,6 @@ should_include_workspace() { git log --no-merges --format="%H" $COMMIT_RANGE -- rust/main | while read -r commit_hash; do # Get commit message commit_msg=$(git log -1 --format="%s" "$commit_hash") - short_hash=$(echo "$commit_hash" | cut -c1-7) # Get files changed in this commit (within rust/main) files=$(git diff-tree --no-commit-id --name-only -r "$commit_hash" -- rust/main) @@ -136,8 +135,8 @@ git log --no-merges --format="%H" $COMMIT_RANGE -- rust/main | while read -r com # Sanitize workspace name for file system (replace / with __) workspace_file=$(echo "$workspace" | tr '/' '_') - # Store commit in workspace category file - echo "$workspace|$commit_msg|$short_hash" >> "$TEMP_DIR/$workspace_file" + # Store commit in workspace category file (just message, PR# already in message) + echo "$workspace|$commit_msg" >> "$TEMP_DIR/$workspace_file" done # Function to generate changelog for a specific workspace @@ -151,8 +150,8 @@ generate_workspace_changelog() { echo "### $workspace" echo "" fi - sort -u "$TEMP_DIR/$workspace_file" | while IFS='|' read -r ws msg hash; do - echo "* $msg (#$hash)" + sort -u "$TEMP_DIR/$workspace_file" | while IFS='|' read -r ws msg; do + echo "* $msg" done fi } From 14c2885a26aa81557e57b64d334eff85cc69bdb9 Mon Sep 17 00:00:00 2001 From: pbio <10051819+paulbalaji@users.noreply.github.com> Date: Tue, 14 Oct 2025 11:32:58 +0100 Subject: [PATCH 07/17] manual runs are prerelease only --- .github/workflows/rust-release.yml | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/.github/workflows/rust-release.yml b/.github/workflows/rust-release.yml index bf8756994d..c5000262be 100644 --- a/.github/workflows/rust-release.yml +++ b/.github/workflows/rust-release.yml @@ -9,13 +9,8 @@ on: - '.github/workflows/rust-release.yml' workflow_dispatch: inputs: - prerelease: - description: 'Create a pre-release instead of a stable release' - required: false - type: boolean - default: false prerelease_suffix: - description: 'Custom prerelease suffix (e.g., "beta.1", "rc.1", "alpha.2"). Leave empty to auto-generate.' + description: 'Prerelease suffix (e.g., "beta.1", "rc.1", "alpha.2"). Leave empty to auto-generate "beta.N".' required: false type: string default: '' @@ -103,7 +98,7 @@ jobs: runs-on: depot-ubuntu-latest needs: check-release-status if: | - (github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && !inputs.prerelease)) && + github.event_name == 'push' && needs.check-release-status.outputs.has_changes == 'true' steps: - uses: actions/checkout@v4 @@ -315,7 +310,7 @@ jobs: needs: check-release-status if: | github.ref == 'refs/heads/main' && - ((github.event_name == 'workflow_dispatch' && inputs.prerelease) || + (github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && needs.check-release-status.outputs.should_release == 'true')) steps: - uses: actions/checkout@v4 @@ -324,7 +319,7 @@ jobs: - name: Determine version and create release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - IS_PRERELEASE: ${{ github.event_name == 'workflow_dispatch' && inputs.prerelease }} + IS_PRERELEASE: ${{ github.event_name == 'workflow_dispatch' }} PRERELEASE_SUFFIX: ${{ inputs.prerelease_suffix }} BASE_VERSION: ${{ needs.check-release-status.outputs.current_version }} run: | @@ -332,6 +327,7 @@ jobs: git config user.email "github-actions[bot]@users.noreply.github.com" # Determine final version based on release type + # workflow_dispatch always creates pre-releases if [ "$IS_PRERELEASE" = "true" ]; then # Pre-release: append suffix if [ -n "$PRERELEASE_SUFFIX" ]; then From ed499f0f99f3f41d4e4c9ac3708dd6e70c92bb0d Mon Sep 17 00:00:00 2001 From: pbio <10051819+paulbalaji@users.noreply.github.com> Date: Tue, 14 Oct 2025 13:34:26 +0100 Subject: [PATCH 08/17] improved semver tagging --- .github/workflows/rust-docker.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rust-docker.yml b/.github/workflows/rust-docker.yml index 7ab01b8055..62d35ed024 100644 --- a/.github/workflows/rust-docker.yml +++ b/.github/workflows/rust-docker.yml @@ -50,6 +50,12 @@ jobs: run: | echo "TAG_DATE=$(date +'%Y%m%d-%H%M%S')" >> $GITHUB_OUTPUT echo "TAG_SHA=$(echo '${{ github.event.pull_request.head.sha || github.sha }}' | cut -b 1-7)" >> $GITHUB_OUTPUT + # Strip 'agents-v' prefix from tag for semver parsing (e.g., agents-v1.2.3 -> 1.2.3) + # Only set SEMVER if ref starts with agents-v, otherwise leave empty + if [[ "${{ github.ref_name }}" =~ ^agents-v ]]; then + SEMVER="$(echo "${{ github.ref_name }}" | sed -E 's/^agents-v//')" + echo "SEMVER=$SEMVER" >> $GITHUB_OUTPUT + fi - name: Docker meta id: meta uses: docker/metadata-action@v5 @@ -62,8 +68,8 @@ jobs: type=ref,event=branch type=ref,event=pr type=ref,event=tag - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{version}},value=${{ steps.taggen.outputs.SEMVER }},enable=${{ startsWith(github.ref_name, 'agents-v') }} + type=semver,pattern={{major}}.{{minor}},value=${{ steps.taggen.outputs.SEMVER }},enable=${{ startsWith(github.ref_name, 'agents-v') }} type=raw,value=${{ steps.taggen.outputs.TAG_SHA }}-${{ steps.taggen.outputs.TAG_DATE }} - name: Set up Depot CLI uses: depot/setup-action@v1 From 75d3487c5058f473d0500af15072179376c43e88 Mon Sep 17 00:00:00 2001 From: pbio <10051819+paulbalaji@users.noreply.github.com> Date: Tue, 14 Oct 2025 14:48:44 +0100 Subject: [PATCH 09/17] coderabbit --- .github/workflows/rust-docker.yml | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/.github/workflows/rust-docker.yml b/.github/workflows/rust-docker.yml index 62d35ed024..be105f7547 100644 --- a/.github/workflows/rust-docker.yml +++ b/.github/workflows/rust-docker.yml @@ -48,13 +48,19 @@ jobs: - name: Generate tag data id: taggen run: | + set -euo pipefail echo "TAG_DATE=$(date +'%Y%m%d-%H%M%S')" >> $GITHUB_OUTPUT echo "TAG_SHA=$(echo '${{ github.event.pull_request.head.sha || github.sha }}' | cut -b 1-7)" >> $GITHUB_OUTPUT - # Strip 'agents-v' prefix from tag for semver parsing (e.g., agents-v1.2.3 -> 1.2.3) - # Only set SEMVER if ref starts with agents-v, otherwise leave empty - if [[ "${{ github.ref_name }}" =~ ^agents-v ]]; then - SEMVER="$(echo "${{ github.ref_name }}" | sed -E 's/^agents-v//')" - echo "SEMVER=$SEMVER" >> $GITHUB_OUTPUT + # For tag events, derive pure semver: + if [ "${{ github.ref_type }}" = "tag" ]; then + NAME="${{ github.ref_name }}" + # Strip agents- prefix and any leading v + NAME="${NAME#agents-}" + NAME="${NAME#v}" + # Basic semver guard (allows prerelease/build metadata) + if echo "$NAME" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+'; then + echo "SEMVER=$NAME" >> $GITHUB_OUTPUT + fi fi - name: Docker meta id: meta @@ -68,8 +74,8 @@ jobs: type=ref,event=branch type=ref,event=pr type=ref,event=tag - type=semver,pattern={{version}},value=${{ steps.taggen.outputs.SEMVER }},enable=${{ startsWith(github.ref_name, 'agents-v') }} - type=semver,pattern={{major}}.{{minor}},value=${{ steps.taggen.outputs.SEMVER }},enable=${{ startsWith(github.ref_name, 'agents-v') }} + type=semver,pattern={{version}},value=${{ steps.taggen.outputs.SEMVER }},enable=${{ github.ref_type == 'tag' && steps.taggen.outputs.SEMVER != '' }} + type=semver,pattern={{major}}.{{minor}},value=${{ steps.taggen.outputs.SEMVER }},enable=${{ github.ref_type == 'tag' && steps.taggen.outputs.SEMVER != '' }} type=raw,value=${{ steps.taggen.outputs.TAG_SHA }}-${{ steps.taggen.outputs.TAG_DATE }} - name: Set up Depot CLI uses: depot/setup-action@v1 From bebe50872fefb87e004d96d8b37a99372cf55fa3 Mon Sep 17 00:00:00 2001 From: pbio <10051819+paulbalaji@users.noreply.github.com> Date: Tue, 14 Oct 2025 14:52:02 +0100 Subject: [PATCH 10/17] save major.minor for stable releases only --- .github/workflows/rust-docker.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/rust-docker.yml b/.github/workflows/rust-docker.yml index be105f7547..eb64fb8d60 100644 --- a/.github/workflows/rust-docker.yml +++ b/.github/workflows/rust-docker.yml @@ -60,6 +60,12 @@ jobs: # Basic semver guard (allows prerelease/build metadata) if echo "$NAME" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+'; then echo "SEMVER=$NAME" >> $GITHUB_OUTPUT + # Check if this is a stable release (no prerelease suffix like -beta, -rc, -alpha) + if echo "$NAME" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$'; then + echo "IS_STABLE=true" >> $GITHUB_OUTPUT + else + echo "IS_STABLE=false" >> $GITHUB_OUTPUT + fi fi fi - name: Docker meta @@ -75,7 +81,7 @@ jobs: type=ref,event=pr type=ref,event=tag type=semver,pattern={{version}},value=${{ steps.taggen.outputs.SEMVER }},enable=${{ github.ref_type == 'tag' && steps.taggen.outputs.SEMVER != '' }} - type=semver,pattern={{major}}.{{minor}},value=${{ steps.taggen.outputs.SEMVER }},enable=${{ github.ref_type == 'tag' && steps.taggen.outputs.SEMVER != '' }} + type=semver,pattern={{major}}.{{minor}},value=${{ steps.taggen.outputs.SEMVER }},enable=${{ github.ref_type == 'tag' && steps.taggen.outputs.IS_STABLE == 'true' }} type=raw,value=${{ steps.taggen.outputs.TAG_SHA }}-${{ steps.taggen.outputs.TAG_DATE }} - name: Set up Depot CLI uses: depot/setup-action@v1 From 7310960b032c146209890848e12fb505a955d524 Mon Sep 17 00:00:00 2001 From: pbio <10051819+paulbalaji@users.noreply.github.com> Date: Tue, 14 Oct 2025 17:18:49 +0100 Subject: [PATCH 11/17] update actions versions --- .github/workflows/rust-release.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/rust-release.yml b/.github/workflows/rust-release.yml index c5000262be..d926ae2b4d 100644 --- a/.github/workflows/rust-release.yml +++ b/.github/workflows/rust-release.yml @@ -6,6 +6,7 @@ on: - main paths: - 'rust/main/**' + - 'rust/scripts/generate-workspace-changelog.sh' - '.github/workflows/rust-release.yml' workflow_dispatch: inputs: @@ -37,7 +38,7 @@ jobs: current_version: ${{ steps.check_version.outputs.current_version }} latest_version: ${{ steps.check_version.outputs.latest_version }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: fetch-depth: 0 - name: Check if there are changes since last release @@ -101,7 +102,7 @@ jobs: github.event_name == 'push' && needs.check-release-status.outputs.has_changes == 'true' steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: fetch-depth: 0 - uses: dtolnay/rust-toolchain@stable @@ -313,7 +314,7 @@ jobs: (github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && needs.check-release-status.outputs.should_release == 'true')) steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: fetch-depth: 0 - name: Determine version and create release From 79ac1502e30093b9fb47383868f4642267cdc159 Mon Sep 17 00:00:00 2001 From: pbio <10051819+paulbalaji@users.noreply.github.com> Date: Mon, 13 Oct 2025 12:55:50 +0100 Subject: [PATCH 12/17] add pb/rust-release just for testing --- .github/workflows/rust-release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/rust-release.yml b/.github/workflows/rust-release.yml index d926ae2b4d..307bee23c8 100644 --- a/.github/workflows/rust-release.yml +++ b/.github/workflows/rust-release.yml @@ -4,6 +4,7 @@ on: push: branches: - main + - pb/rust-release-cargo # for testing paths: - 'rust/main/**' - 'rust/scripts/generate-workspace-changelog.sh' From 093342ab359bc698d2d6b69e2353dac5c746befd Mon Sep 17 00:00:00 2001 From: pbio <10051819+paulbalaji@users.noreply.github.com> Date: Wed, 29 Oct 2025 11:45:40 -0400 Subject: [PATCH 13/17] refactor with helper scripts --- .github/workflows/rust-release.yml | 80 +++-------------- rust/scripts/determine-next-version.sh | 105 +++++++++++++++++++++++ rust/scripts/get-latest-agents-tag.sh | 12 +++ rust/scripts/get-workspace-version.sh | 19 ++++ rust/scripts/update-workspace-version.sh | 41 +++++++++ 5 files changed, 187 insertions(+), 70 deletions(-) create mode 100755 rust/scripts/determine-next-version.sh create mode 100755 rust/scripts/get-latest-agents-tag.sh create mode 100755 rust/scripts/get-workspace-version.sh create mode 100755 rust/scripts/update-workspace-version.sh diff --git a/.github/workflows/rust-release.yml b/.github/workflows/rust-release.yml index 307bee23c8..05412bcdde 100644 --- a/.github/workflows/rust-release.yml +++ b/.github/workflows/rust-release.yml @@ -47,7 +47,7 @@ jobs: working-directory: ./rust/main run: | # Get latest agents-v* tag - LATEST_TAG=$(git tag -l "agents-v*" --sort=-version:refname | grep -E "^agents-v[0-9]+\.[0-9]+\.[0-9]+$" | head -1) + LATEST_TAG=$(../scripts/get-latest-agents-tag.sh) if [ -z "$LATEST_TAG" ]; then echo "No previous release found" @@ -70,12 +70,12 @@ jobs: working-directory: ./rust/main run: | # Get current version from Cargo.toml workspace.package.version - CURRENT_VERSION=$(grep -A 10 '^\[workspace\.package\]' Cargo.toml | grep '^version = ' | head -1 | sed 's/version = "\(.*\)"/\1/') + CURRENT_VERSION=$(../scripts/get-workspace-version.sh) echo "current_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT echo "Current workspace version: $CURRENT_VERSION" # Get latest agents-v* tag - LATEST_TAG=$(git tag -l "agents-v*" --sort=-version:refname | grep -E "^agents-v[0-9]+\.[0-9]+\.[0-9]+$" | head -1) + LATEST_TAG=$(../scripts/get-latest-agents-tag.sh) if [ -z "$LATEST_TAG" ]; then echo "latest_version=" >> $GITHUB_OUTPUT @@ -109,63 +109,14 @@ jobs: - uses: dtolnay/rust-toolchain@stable - name: Determine next version from conventional commits id: next_version - working-directory: ./rust/main env: CURRENT_VERSION: ${{ needs.check-release-status.outputs.current_version }} run: | - # Get commits since last release - LATEST_TAG=$(git tag -l "agents-v*" --sort=-version:refname | grep -E "^agents-v[0-9]+\.[0-9]+\.[0-9]+$" | head -1) - - if [ -z "$LATEST_TAG" ]; then - COMMITS=$(git log --oneline --no-merges -- .) - else - COMMITS=$(git log "${LATEST_TAG}..HEAD" --oneline --no-merges -- .) - fi - - # Parse current version - IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT_VERSION" - - # Analyze commits for conventional commit types - HAS_BREAKING=false - HAS_MINOR=false - HAS_PATCH=false - - while IFS= read -r commit; do - if echo "$commit" | grep -qE "^[a-f0-9]+ [a-z]+(\(.+\))?!:"; then - HAS_BREAKING=true - elif echo "$commit" | grep -qE "^[a-f0-9]+ (feat|refactor|perf|chore)(\(.+\))?:"; then - HAS_MINOR=true - elif echo "$commit" | grep -qE "^[a-f0-9]+ (fix|docs|test|ci|style|build)(\(.+\))?:"; then - HAS_PATCH=true - fi - - # Check commit body for BREAKING CHANGE - COMMIT_HASH=$(echo "$commit" | cut -d' ' -f1) - if git show -s --format=%B "$COMMIT_HASH" | grep -q "BREAKING CHANGE:"; then - HAS_BREAKING=true - fi - done <<< "$COMMITS" - - # Determine version bump - if [ "$HAS_BREAKING" = true ]; then - MAJOR=$((MAJOR + 1)) - MINOR=0 - PATCH=0 - BUMP_TYPE="major" - elif [ "$HAS_MINOR" = true ]; then - MINOR=$((MINOR + 1)) - PATCH=0 - BUMP_TYPE="minor" - elif [ "$HAS_PATCH" = true ]; then - PATCH=$((PATCH + 1)) - BUMP_TYPE="patch" - else - # Default to patch for any other changes - PATCH=$((PATCH + 1)) - BUMP_TYPE="patch" - fi + # Use helper script to determine next version + OUTPUT=$(./rust/scripts/determine-next-version.sh "$CURRENT_VERSION") + NEW_VERSION=$(echo "$OUTPUT" | sed -n '1p') + BUMP_TYPE=$(echo "$OUTPUT" | sed -n '2p') - NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}" echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT echo "bump_type=$BUMP_TYPE" >> $GITHUB_OUTPUT echo "Next version: $NEW_VERSION ($BUMP_TYPE bump from $CURRENT_VERSION)" @@ -175,7 +126,7 @@ jobs: NEW_VERSION: ${{ steps.next_version.outputs.new_version }} run: | # Get commit range for changelog generation - LATEST_TAG=$(git tag -l "agents-v*" --sort=-version:refname | grep -E "^agents-v[0-9]+\.[0-9]+\.[0-9]+$" | head -1) + LATEST_TAG=$(./rust/scripts/get-latest-agents-tag.sh) if [ -z "$LATEST_TAG" ]; then COMMIT_RANGE="" @@ -203,25 +154,14 @@ jobs: # Generate per-workspace CHANGELOG.md files ./rust/scripts/generate-workspace-changelog.sh "$COMMIT_RANGE" "" --write-to-workspace "$NEW_VERSION" - name: Update version files - working-directory: ./rust/main env: NEW_VERSION: ${{ steps.next_version.outputs.new_version }} run: | # Update workspace version in Cargo.toml - # Use awk to find [workspace.package] section and update version within it - awk -v new_version="$NEW_VERSION" ' - /^\[workspace\.package\]/ { in_workspace=1 } - /^\[/ && !/^\[workspace\.package\]/ { in_workspace=0 } - in_workspace && /^version = / { - print "version = \"" new_version "\"" - next - } - { print } - ' Cargo.toml > Cargo.toml.new - mv Cargo.toml.new Cargo.toml - echo "Updated Cargo.toml workspace version to $NEW_VERSION" + ./rust/scripts/update-workspace-version.sh "$NEW_VERSION" # Update Cargo.lock in rust/main + cd rust/main cargo update --workspace --offline 2>/dev/null || cargo update --workspace echo "Updated rust/main/Cargo.lock" diff --git a/rust/scripts/determine-next-version.sh b/rust/scripts/determine-next-version.sh new file mode 100755 index 0000000000..5f72c239e2 --- /dev/null +++ b/rust/scripts/determine-next-version.sh @@ -0,0 +1,105 @@ +#!/usr/bin/env bash +# Determine next version based on conventional commits +# +# Usage: +# determine-next-version.sh CURRENT_VERSION [COMMIT_RANGE] +# +# Arguments: +# CURRENT_VERSION - The current version (e.g., "1.4.0") +# COMMIT_RANGE - Optional git commit range (e.g., "v1.4.0..HEAD"). If omitted, uses latest tag..HEAD +# +# Returns: +# Outputs two lines: +# 1. The next version (e.g., "1.5.0") +# 2. The bump type ("major", "minor", or "patch") +# +# Examples: +# ./determine-next-version.sh "1.4.0" +# ./determine-next-version.sh "1.4.0" "v1.4.0..HEAD" +# +set -euo pipefail + +if [ $# -lt 1 ]; then + echo "Error: CURRENT_VERSION is required" >&2 + echo "Usage: $0 CURRENT_VERSION [COMMIT_RANGE]" >&2 + exit 1 +fi + +CURRENT_VERSION="$1" +COMMIT_RANGE="${2:-}" + +# Determine script directory +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +RUST_MAIN_DIR="$SCRIPT_DIR/../main" + +# If no commit range specified, use commits since latest tag +if [ -z "$COMMIT_RANGE" ]; then + LATEST_TAG=$("$SCRIPT_DIR/get-latest-agents-tag.sh") + + if [ -z "$LATEST_TAG" ]; then + # Get all commits in rust/main + cd "$RUST_MAIN_DIR" + COMMITS=$(git log --oneline --no-merges -- .) + else + cd "$RUST_MAIN_DIR" + COMMITS=$(git log "${LATEST_TAG}..HEAD" --oneline --no-merges -- .) + fi +else + cd "$RUST_MAIN_DIR" + COMMITS=$(git log "$COMMIT_RANGE" --oneline --no-merges -- .) +fi + +# Parse current version +IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT_VERSION" + +# Analyze commits for conventional commit types +HAS_BREAKING=false +HAS_MINOR=false +HAS_PATCH=false + +while IFS= read -r commit; do + # Skip empty lines + [ -z "$commit" ] && continue + + # Check for breaking changes via ! suffix + if echo "$commit" | grep -qE "^[a-f0-9]+ [a-z]+(\(.+\))?!:"; then + HAS_BREAKING=true + # Check for minor changes (new features, refactors, perf improvements, chores) + elif echo "$commit" | grep -qE "^[a-f0-9]+ (feat|refactor|perf|chore)(\(.+\))?:"; then + HAS_MINOR=true + # Check for patch changes (fixes, docs, tests, ci, style, build) + elif echo "$commit" | grep -qE "^[a-f0-9]+ (fix|docs|test|ci|style|build)(\(.+\))?:"; then + HAS_PATCH=true + fi + + # Check commit body for BREAKING CHANGE + COMMIT_HASH=$(echo "$commit" | cut -d' ' -f1) + if git show -s --format=%B "$COMMIT_HASH" | grep -q "BREAKING CHANGE:"; then + HAS_BREAKING=true + fi +done <<< "$COMMITS" + +# Determine version bump +if [ "$HAS_BREAKING" = true ]; then + MAJOR=$((MAJOR + 1)) + MINOR=0 + PATCH=0 + BUMP_TYPE="major" +elif [ "$HAS_MINOR" = true ]; then + MINOR=$((MINOR + 1)) + PATCH=0 + BUMP_TYPE="minor" +elif [ "$HAS_PATCH" = true ]; then + PATCH=$((PATCH + 1)) + BUMP_TYPE="patch" +else + # Default to patch for any other changes + PATCH=$((PATCH + 1)) + BUMP_TYPE="patch" +fi + +NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}" + +# Output results (two lines) +echo "$NEW_VERSION" +echo "$BUMP_TYPE" diff --git a/rust/scripts/get-latest-agents-tag.sh b/rust/scripts/get-latest-agents-tag.sh new file mode 100755 index 0000000000..a163bb52da --- /dev/null +++ b/rust/scripts/get-latest-agents-tag.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +# Get the latest stable agents-v* tag (excluding prereleases) +# +# Usage: +# get-latest-agents-tag.sh +# +# Returns: +# The latest agents-v* tag (e.g., "agents-v1.4.0"), or empty string if none found +# +set -euo pipefail + +git tag -l "agents-v*" --sort=-version:refname | grep -E "^agents-v[0-9]+\.[0-9]+\.[0-9]+$" | head -1 || true diff --git a/rust/scripts/get-workspace-version.sh b/rust/scripts/get-workspace-version.sh new file mode 100755 index 0000000000..f034042f8c --- /dev/null +++ b/rust/scripts/get-workspace-version.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +# Extract the version from rust/main/Cargo.toml workspace.package.version +# +# Usage: +# get-workspace-version.sh +# +# Returns: +# The current workspace version (e.g., "1.4.0") +# +set -euo pipefail + +# Determine script directory and repo structure +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +RUST_MAIN_DIR="$SCRIPT_DIR/../main" + +grep -A 10 '^\[workspace\.package\]' "$RUST_MAIN_DIR/Cargo.toml" | \ + grep '^version = ' | \ + head -1 | \ + sed 's/version = "\(.*\)"/\1/' diff --git a/rust/scripts/update-workspace-version.sh b/rust/scripts/update-workspace-version.sh new file mode 100755 index 0000000000..a6d1dbe904 --- /dev/null +++ b/rust/scripts/update-workspace-version.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +# Update the workspace version in rust/main/Cargo.toml +# +# Usage: +# update-workspace-version.sh NEW_VERSION +# +# Arguments: +# NEW_VERSION - The new version to set (e.g., "1.5.0") +# +# Returns: +# Updates rust/main/Cargo.toml in place +# +set -euo pipefail + +if [ $# -ne 1 ]; then + echo "Error: NEW_VERSION is required" >&2 + echo "Usage: $0 NEW_VERSION" >&2 + exit 1 +fi + +NEW_VERSION="$1" + +# Determine script directory and repo structure +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +RUST_MAIN_DIR="$SCRIPT_DIR/../main" +CARGO_TOML="$RUST_MAIN_DIR/Cargo.toml" + +# Use awk to find [workspace.package] section and update version within it +awk -v new_version="$NEW_VERSION" ' + /^\[workspace\.package\]/ { in_workspace=1 } + /^\[/ && !/^\[workspace\.package\]/ { in_workspace=0 } + in_workspace && /^version = / { + print "version = \"" new_version "\"" + next + } + { print } +' "$CARGO_TOML" > "$CARGO_TOML.new" + +mv "$CARGO_TOML.new" "$CARGO_TOML" + +echo "Updated $CARGO_TOML to version $NEW_VERSION" From ab517624bfb279558874ad3febb14af638ed2f81 Mon Sep 17 00:00:00 2001 From: pbio <10051819+paulbalaji@users.noreply.github.com> Date: Wed, 29 Oct 2025 11:46:49 -0400 Subject: [PATCH 14/17] coderabbit --- rust/scripts/generate-workspace-changelog.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rust/scripts/generate-workspace-changelog.sh b/rust/scripts/generate-workspace-changelog.sh index 2b269ff616..9880eee26a 100755 --- a/rust/scripts/generate-workspace-changelog.sh +++ b/rust/scripts/generate-workspace-changelog.sh @@ -136,7 +136,7 @@ git log --no-merges --format="%H" $COMMIT_RANGE -- rust/main | while read -r com workspace_file=$(echo "$workspace" | tr '/' '_') # Store commit in workspace category file (just message, PR# already in message) - echo "$workspace|$commit_msg" >> "$TEMP_DIR/$workspace_file" + echo "$commit_msg" >> "$TEMP_DIR/$workspace_file" done # Function to generate changelog for a specific workspace @@ -150,7 +150,7 @@ generate_workspace_changelog() { echo "### $workspace" echo "" fi - sort -u "$TEMP_DIR/$workspace_file" | while IFS='|' read -r ws msg; do + sort -u "$TEMP_DIR/$workspace_file" | while read -r msg; do echo "* $msg" done fi From df02aa9377f16cfc626c6203b91bae2f2b5cdc18 Mon Sep 17 00:00:00 2001 From: pbio <10051819+paulbalaji@users.noreply.github.com> Date: Wed, 29 Oct 2025 11:56:54 -0400 Subject: [PATCH 15/17] move utils from rust/scripts --> rust/scripts/ci --- .github/workflows/rust-release.yml | 24 +++++++++---------- .../{ => ci}/determine-next-version.sh | 2 +- .../{ => ci}/generate-workspace-changelog.sh | 2 +- .../scripts/{ => ci}/get-latest-agents-tag.sh | 0 .../scripts/{ => ci}/get-workspace-version.sh | 2 +- .../{ => ci}/update-workspace-version.sh | 2 +- 6 files changed, 16 insertions(+), 16 deletions(-) rename rust/scripts/{ => ci}/determine-next-version.sh (98%) rename rust/scripts/{ => ci}/generate-workspace-changelog.sh (99%) rename rust/scripts/{ => ci}/get-latest-agents-tag.sh (100%) rename rust/scripts/{ => ci}/get-workspace-version.sh (92%) rename rust/scripts/{ => ci}/update-workspace-version.sh (96%) diff --git a/.github/workflows/rust-release.yml b/.github/workflows/rust-release.yml index 05412bcdde..eebc91ed35 100644 --- a/.github/workflows/rust-release.yml +++ b/.github/workflows/rust-release.yml @@ -7,7 +7,7 @@ on: - pb/rust-release-cargo # for testing paths: - 'rust/main/**' - - 'rust/scripts/generate-workspace-changelog.sh' + - 'rust/scripts/ci/**' - '.github/workflows/rust-release.yml' workflow_dispatch: inputs: @@ -47,7 +47,7 @@ jobs: working-directory: ./rust/main run: | # Get latest agents-v* tag - LATEST_TAG=$(../scripts/get-latest-agents-tag.sh) + LATEST_TAG=$(../scripts/ci/get-latest-agents-tag.sh) if [ -z "$LATEST_TAG" ]; then echo "No previous release found" @@ -70,12 +70,12 @@ jobs: working-directory: ./rust/main run: | # Get current version from Cargo.toml workspace.package.version - CURRENT_VERSION=$(../scripts/get-workspace-version.sh) + CURRENT_VERSION=$(../scripts/ci/get-workspace-version.sh) echo "current_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT echo "Current workspace version: $CURRENT_VERSION" # Get latest agents-v* tag - LATEST_TAG=$(../scripts/get-latest-agents-tag.sh) + LATEST_TAG=$(../scripts/ci/get-latest-agents-tag.sh) if [ -z "$LATEST_TAG" ]; then echo "latest_version=" >> $GITHUB_OUTPUT @@ -113,7 +113,7 @@ jobs: CURRENT_VERSION: ${{ needs.check-release-status.outputs.current_version }} run: | # Use helper script to determine next version - OUTPUT=$(./rust/scripts/determine-next-version.sh "$CURRENT_VERSION") + OUTPUT=$(./rust/scripts/ci/determine-next-version.sh "$CURRENT_VERSION") NEW_VERSION=$(echo "$OUTPUT" | sed -n '1p') BUMP_TYPE=$(echo "$OUTPUT" | sed -n '2p') @@ -126,7 +126,7 @@ jobs: NEW_VERSION: ${{ steps.next_version.outputs.new_version }} run: | # Get commit range for changelog generation - LATEST_TAG=$(./rust/scripts/get-latest-agents-tag.sh) + LATEST_TAG=$(./rust/scripts/ci/get-latest-agents-tag.sh) if [ -z "$LATEST_TAG" ]; then COMMIT_RANGE="" @@ -136,9 +136,9 @@ jobs: # Generate unified changelog for PR body if [ -z "$COMMIT_RANGE" ]; then - CHANGELOG=$(./rust/scripts/generate-workspace-changelog.sh --no-header) + CHANGELOG=$(./rust/scripts/ci/generate-workspace-changelog.sh --no-header) else - CHANGELOG=$(./rust/scripts/generate-workspace-changelog.sh "$COMMIT_RANGE" --no-header) + CHANGELOG=$(./rust/scripts/ci/generate-workspace-changelog.sh "$COMMIT_RANGE" --no-header) fi # Save changelog to file for PR body @@ -152,13 +152,13 @@ jobs: } >> $GITHUB_OUTPUT # Generate per-workspace CHANGELOG.md files - ./rust/scripts/generate-workspace-changelog.sh "$COMMIT_RANGE" "" --write-to-workspace "$NEW_VERSION" + ./rust/scripts/ci/generate-workspace-changelog.sh "$COMMIT_RANGE" "" --write-to-workspace "$NEW_VERSION" - name: Update version files env: NEW_VERSION: ${{ steps.next_version.outputs.new_version }} run: | # Update workspace version in Cargo.toml - ./rust/scripts/update-workspace-version.sh "$NEW_VERSION" + ./rust/scripts/ci/update-workspace-version.sh "$NEW_VERSION" # Update Cargo.lock in rust/main cd rust/main @@ -311,9 +311,9 @@ jobs: fi if [ -z "$PREV_TAG" ]; then - CHANGELOG=$(./rust/scripts/generate-workspace-changelog.sh) + CHANGELOG=$(./rust/scripts/ci/generate-workspace-changelog.sh) else - CHANGELOG=$(./rust/scripts/generate-workspace-changelog.sh "${PREV_TAG}..${COMMIT_RANGE_END}") + CHANGELOG=$(./rust/scripts/ci/generate-workspace-changelog.sh "${PREV_TAG}..${COMMIT_RANGE_END}") fi # Generate new contributors section using GitHub's auto-generated notes diff --git a/rust/scripts/determine-next-version.sh b/rust/scripts/ci/determine-next-version.sh similarity index 98% rename from rust/scripts/determine-next-version.sh rename to rust/scripts/ci/determine-next-version.sh index 5f72c239e2..ce2c3ebdd3 100755 --- a/rust/scripts/determine-next-version.sh +++ b/rust/scripts/ci/determine-next-version.sh @@ -30,7 +30,7 @@ COMMIT_RANGE="${2:-}" # Determine script directory SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -RUST_MAIN_DIR="$SCRIPT_DIR/../main" +RUST_MAIN_DIR="$SCRIPT_DIR/../../main" # If no commit range specified, use commits since latest tag if [ -z "$COMMIT_RANGE" ]; then diff --git a/rust/scripts/generate-workspace-changelog.sh b/rust/scripts/ci/generate-workspace-changelog.sh similarity index 99% rename from rust/scripts/generate-workspace-changelog.sh rename to rust/scripts/ci/generate-workspace-changelog.sh index 9880eee26a..d1019ce050 100755 --- a/rust/scripts/generate-workspace-changelog.sh +++ b/rust/scripts/ci/generate-workspace-changelog.sh @@ -25,7 +25,7 @@ set -euo pipefail # Determine script directory and repo structure SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -RUST_MAIN_DIR="$SCRIPT_DIR/../main" +RUST_MAIN_DIR="$SCRIPT_DIR/../../main" # Parse arguments COMMIT_RANGE="" diff --git a/rust/scripts/get-latest-agents-tag.sh b/rust/scripts/ci/get-latest-agents-tag.sh similarity index 100% rename from rust/scripts/get-latest-agents-tag.sh rename to rust/scripts/ci/get-latest-agents-tag.sh diff --git a/rust/scripts/get-workspace-version.sh b/rust/scripts/ci/get-workspace-version.sh similarity index 92% rename from rust/scripts/get-workspace-version.sh rename to rust/scripts/ci/get-workspace-version.sh index f034042f8c..4c728ef11c 100755 --- a/rust/scripts/get-workspace-version.sh +++ b/rust/scripts/ci/get-workspace-version.sh @@ -11,7 +11,7 @@ set -euo pipefail # Determine script directory and repo structure SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -RUST_MAIN_DIR="$SCRIPT_DIR/../main" +RUST_MAIN_DIR="$SCRIPT_DIR/../../main" grep -A 10 '^\[workspace\.package\]' "$RUST_MAIN_DIR/Cargo.toml" | \ grep '^version = ' | \ diff --git a/rust/scripts/update-workspace-version.sh b/rust/scripts/ci/update-workspace-version.sh similarity index 96% rename from rust/scripts/update-workspace-version.sh rename to rust/scripts/ci/update-workspace-version.sh index a6d1dbe904..b2c964ef06 100755 --- a/rust/scripts/update-workspace-version.sh +++ b/rust/scripts/ci/update-workspace-version.sh @@ -22,7 +22,7 @@ NEW_VERSION="$1" # Determine script directory and repo structure SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -RUST_MAIN_DIR="$SCRIPT_DIR/../main" +RUST_MAIN_DIR="$SCRIPT_DIR/../../main" CARGO_TOML="$RUST_MAIN_DIR/Cargo.toml" # Use awk to find [workspace.package] section and update version within it From 40595e01fc3fe06bac7e5de483c479245d6976a7 Mon Sep 17 00:00:00 2001 From: pbio <10051819+paulbalaji@users.noreply.github.com> Date: Wed, 29 Oct 2025 13:52:35 -0400 Subject: [PATCH 16/17] coderabbit --- rust/scripts/ci/generate-workspace-changelog.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/scripts/ci/generate-workspace-changelog.sh b/rust/scripts/ci/generate-workspace-changelog.sh index d1019ce050..bb7c3b9bd1 100755 --- a/rust/scripts/ci/generate-workspace-changelog.sh +++ b/rust/scripts/ci/generate-workspace-changelog.sh @@ -80,7 +80,7 @@ fi # Temporary directory for categorization TEMP_DIR=$(mktemp -d) -trap "rm -rf $TEMP_DIR" EXIT +trap 'rm -rf "$TEMP_DIR"' EXIT # Extract workspace members from rust/main/Cargo.toml WORKSPACE_MEMBERS=$(grep -A 100 '^\[workspace\]' "$RUST_MAIN_DIR/Cargo.toml" | sed -n '/^members = \[/,/^\]/p' | grep '"' | sed 's/[", ]//g') From 322c54b15f58afcc1a24ea124451861289d17890 Mon Sep 17 00:00:00 2001 From: pbio <10051819+paulbalaji@users.noreply.github.com> Date: Wed, 29 Oct 2025 13:58:33 -0400 Subject: [PATCH 17/17] beta --> preview, and check if tag/release already exists --- .github/workflows/rust-release.yml | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/.github/workflows/rust-release.yml b/.github/workflows/rust-release.yml index eebc91ed35..a62d8a8f8d 100644 --- a/.github/workflows/rust-release.yml +++ b/.github/workflows/rust-release.yml @@ -12,7 +12,7 @@ on: workflow_dispatch: inputs: prerelease_suffix: - description: 'Prerelease suffix (e.g., "beta.1", "rc.1", "alpha.2"). Leave empty to auto-generate "beta.N".' + description: 'Prerelease suffix (e.g., "preview.1", "rc.1", "alpha.2"). Leave empty to auto-generate "preview.N".' required: false type: string default: '' @@ -275,13 +275,13 @@ jobs: if [ -n "$PRERELEASE_SUFFIX" ]; then SUFFIX="$PRERELEASE_SUFFIX" else - # Auto-generate beta.N - LAST_BETA=$(git tag -l "agents-v${BASE_VERSION}-beta.*" | sort -V | tail -1) - if [ -z "$LAST_BETA" ]; then - SUFFIX="beta.1" + # Auto-generate preview.N + LAST_PREVIEW=$(git tag -l "agents-v${BASE_VERSION}-preview.*" | sort -V | tail -1) + if [ -z "$LAST_PREVIEW" ]; then + SUFFIX="preview.1" else - BETA_NUM=$(echo "$LAST_BETA" | sed 's/.*beta\.\([0-9]*\)/\1/') - SUFFIX="beta.$((BETA_NUM + 1))" + PREVIEW_NUM=$(echo "$LAST_PREVIEW" | sed 's/.*preview\.\([0-9]*\)/\1/') + SUFFIX="preview.$((PREVIEW_NUM + 1))" fi fi VERSION="${BASE_VERSION}-${SUFFIX}" @@ -299,6 +299,20 @@ jobs: TAG_NAME="agents-v${VERSION}" echo "Creating $RELEASE_TYPE: $TAG_NAME" + # Check if tag already exists + if git rev-parse "$TAG_NAME" >/dev/null 2>&1; then + echo "Error: Tag $TAG_NAME already exists!" >&2 + echo "Cannot overwrite existing tag. Please use a different version or prerelease suffix." >&2 + exit 1 + fi + + # Check if GitHub release already exists + if gh release view "$TAG_NAME" --repo "${{ github.repository }}" >/dev/null 2>&1; then + echo "Error: GitHub release $TAG_NAME already exists!" >&2 + echo "Cannot overwrite existing release. Please use a different version or prerelease suffix." >&2 + exit 1 + fi + # Generate workspace-grouped changelog PREV_TAG=$(git describe --tags --abbrev=0 --match "agents-v*" 2>/dev/null || echo "")