Skip to content
Open
Show file tree
Hide file tree
Changes from 14 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
21 changes: 21 additions & 0 deletions .github/workflows/rust-docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,26 @@ 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
# 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
# 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
id: meta
uses: docker/metadata-action@v5
Expand All @@ -61,6 +79,9 @@ jobs:
tags: |
type=ref,event=branch
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.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
Expand Down
360 changes: 360 additions & 0 deletions .github/workflows/rust-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,360 @@
name: Rust Agent Release

on:
push:
branches:
- main
- pb/rust-release-cargo # for testing
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

remove before merging

paths:
- 'rust/main/**'
- 'rust/scripts/generate-workspace-changelog.sh'
- '.github/workflows/rust-release.yml'
workflow_dispatch:
inputs:
prerelease_suffix:
description: 'Prerelease suffix (e.g., "beta.1", "rc.1", "alpha.2"). Leave empty to auto-generate "beta.N".'
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@v5
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=$(../scripts/get-latest-agents-tag.sh)

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=$(../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=$(../scripts/get-latest-agents-tag.sh)

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' &&
needs.check-release-status.outputs.has_changes == 'true'
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- uses: dtolnay/rust-toolchain@stable
- name: Determine next version from conventional commits
id: next_version
env:
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")
NEW_VERSION=$(echo "$OUTPUT" | sed -n '1p')
BUMP_TYPE=$(echo "$OUTPUT" | sed -n '2p')

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
env:
NEW_VERSION: ${{ steps.next_version.outputs.new_version }}
run: |
# Get commit range for changelog generation
LATEST_TAG=$(./rust/scripts/get-latest-agents-tag.sh)

if [ -z "$LATEST_TAG" ]; then
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 "$COMMIT_RANGE" --no-header)
fi

# Save changelog to file for PR body
echo "$CHANGELOG" > /tmp/changelog.md

# Also output for GitHub Actions
{
echo 'changelog<<EOF'
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
env:
NEW_VERSION: ${{ steps.next_version.outputs.new_version }}
run: |
# Update workspace version in Cargo.toml
./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"

# Update Cargo.lock in rust/sealevel
cd ../sealevel
cargo update --workspace --offline 2>/dev/null || cargo update --workspace
echo "Updated rust/sealevel/Cargo.lock"
- name: Create or update release PR
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NEW_VERSION: ${{ steps.next_version.outputs.new_version }}
BUMP_TYPE: ${{ steps.next_version.outputs.bump_type }}
CHANGELOG: ${{ steps.changelog.outputs.changelog }}
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"

BRANCH_NAME="release-agents-v${NEW_VERSION}"

# Create branch from current HEAD (which is main)
git checkout -B "$BRANCH_NAME"

# Stage changes (Cargo files and all workspace CHANGELOG.md files)
git add rust/main/Cargo.toml rust/main/Cargo.lock rust/sealevel/Cargo.lock
git add rust/main/*/CHANGELOG.md rust/main/*/*/CHANGELOG.md 2>/dev/null || true

# Commit changes
git commit -m "release: agents v${NEW_VERSION}

This is a $BUMP_TYPE version bump for the Hyperlane agents.

Changes will be released as agents-v${NEW_VERSION} after this PR is merged."

# Force push to obliterate any existing branch
git push -f origin "$BRANCH_NAME"

# Create or update PR
PR_BODY="# Release agents v${NEW_VERSION}

This PR prepares the release of Hyperlane agents version **${NEW_VERSION}** (${BUMP_TYPE} bump).

## What's Changed

${CHANGELOG}

---

Once this PR is merged, the [\`rust-release.yml\`](https://github.com/${{ github.repository }}/blob/main/.github/workflows/rust-release.yml) workflow will automatically:
- Create a GitHub release with tag \`agents-v${NEW_VERSION}\`
- Trigger the build of release binaries

🤖 This PR was automatically created by the release workflow."

# Check if PR already exists
EXISTING_PR=$(gh pr list --head "$BRANCH_NAME" --json number --jq '.[0].number' 2>/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' ||
(github.event_name == 'push' && needs.check-release-status.outputs.should_release == 'true'))
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Determine version and create release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
IS_PRERELEASE: ${{ github.event_name == 'workflow_dispatch' }}
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
# workflow_dispatch always creates pre-releases
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 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=$(./rust/scripts/generate-workspace-changelog.sh)
else
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"

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
Loading
Loading