Skip to content

Commit 901cb5a

Browse files
authored
feat: agents release workflow (#7194)
Signed-off-by: pbio <[email protected]>
1 parent e988296 commit 901cb5a

File tree

11 files changed

+849
-39
lines changed

11 files changed

+849
-39
lines changed

.github/workflows/rust-docker.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,26 @@ jobs:
4848
- name: Generate tag data
4949
id: taggen
5050
run: |
51+
set -euo pipefail
5152
echo "TAG_DATE=$(date +'%Y%m%d-%H%M%S')" >> $GITHUB_OUTPUT
5253
echo "TAG_SHA=$(echo '${{ github.event.pull_request.head.sha || github.sha }}' | cut -b 1-7)" >> $GITHUB_OUTPUT
54+
# For tag events, derive pure semver:
55+
if [ "${{ github.ref_type }}" = "tag" ]; then
56+
NAME="${{ github.ref_name }}"
57+
# Strip agents- prefix and any leading v
58+
NAME="${NAME#agents-}"
59+
NAME="${NAME#v}"
60+
# Basic semver guard (allows prerelease/build metadata)
61+
if echo "$NAME" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+'; then
62+
echo "SEMVER=$NAME" >> $GITHUB_OUTPUT
63+
# Check if this is a stable release (no prerelease suffix like -beta, -rc, -alpha)
64+
if echo "$NAME" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$'; then
65+
echo "IS_STABLE=true" >> $GITHUB_OUTPUT
66+
else
67+
echo "IS_STABLE=false" >> $GITHUB_OUTPUT
68+
fi
69+
fi
70+
fi
5371
- name: Docker meta
5472
id: meta
5573
uses: docker/metadata-action@v5
@@ -61,6 +79,9 @@ jobs:
6179
tags: |
6280
type=ref,event=branch
6381
type=ref,event=pr
82+
type=ref,event=tag
83+
type=semver,pattern={{version}},value=${{ steps.taggen.outputs.SEMVER }},enable=${{ github.ref_type == 'tag' && steps.taggen.outputs.SEMVER != '' }}
84+
type=semver,pattern={{major}}.{{minor}},value=${{ steps.taggen.outputs.SEMVER }},enable=${{ github.ref_type == 'tag' && steps.taggen.outputs.IS_STABLE == 'true' }}
6485
type=raw,value=${{ steps.taggen.outputs.TAG_SHA }}-${{ steps.taggen.outputs.TAG_DATE }}
6586
- name: Set up Depot CLI
6687
uses: depot/setup-action@v1

.github/workflows/rust-release.yml

Lines changed: 361 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,361 @@
1+
name: Rust Agent Release
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
paths:
8+
- 'rust/main/**'
9+
- 'rust/scripts/ci/**'
10+
- '.github/workflows/rust-release.yml'
11+
workflow_dispatch:
12+
inputs:
13+
prerelease_suffix:
14+
description: 'Prerelease suffix (e.g., "preview.1", "rc.1", "alpha.2"). Leave empty to auto-generate "preview.N".'
15+
required: false
16+
type: string
17+
default: ''
18+
19+
concurrency:
20+
group: ${{ github.workflow }}-${{ github.ref }}
21+
cancel-in-progress: false
22+
23+
permissions:
24+
contents: write
25+
pull-requests: write
26+
27+
env:
28+
CARGO_TERM_COLOR: always
29+
RUST_BACKTRACE: full
30+
31+
jobs:
32+
check-release-status:
33+
name: Check Release Status
34+
runs-on: depot-ubuntu-latest
35+
outputs:
36+
has_changes: ${{ steps.check_changes.outputs.has_changes }}
37+
should_release: ${{ steps.check_version.outputs.should_release }}
38+
current_version: ${{ steps.check_version.outputs.current_version }}
39+
latest_version: ${{ steps.check_version.outputs.latest_version }}
40+
steps:
41+
- uses: actions/checkout@v5
42+
with:
43+
fetch-depth: 0
44+
- name: Check if there are changes since last release
45+
id: check_changes
46+
working-directory: ./rust/main
47+
run: |
48+
# Get latest agents-v* tag
49+
LATEST_TAG=$(../scripts/ci/get-latest-agents-tag.sh)
50+
51+
if [ -z "$LATEST_TAG" ]; then
52+
echo "No previous release found"
53+
echo "has_changes=true" >> $GITHUB_OUTPUT
54+
else
55+
# Check if there are commits to rust/main since last release
56+
COMMITS_SINCE=$(git log "$LATEST_TAG"..HEAD --oneline -- . | wc -l)
57+
echo "Commits since $LATEST_TAG: $COMMITS_SINCE"
58+
59+
if [ "$COMMITS_SINCE" -gt 0 ]; then
60+
echo "Found $COMMITS_SINCE commit(s) since last release"
61+
echo "has_changes=true" >> $GITHUB_OUTPUT
62+
else
63+
echo "No commits since last release"
64+
echo "has_changes=false" >> $GITHUB_OUTPUT
65+
fi
66+
fi
67+
- name: Check if version changed (for publish decision)
68+
id: check_version
69+
working-directory: ./rust/main
70+
run: |
71+
# Get current version from Cargo.toml workspace.package.version
72+
CURRENT_VERSION=$(../scripts/ci/get-workspace-version.sh)
73+
echo "current_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
74+
echo "Current workspace version: $CURRENT_VERSION"
75+
76+
# Get latest agents-v* tag
77+
LATEST_TAG=$(../scripts/ci/get-latest-agents-tag.sh)
78+
79+
if [ -z "$LATEST_TAG" ]; then
80+
echo "latest_version=" >> $GITHUB_OUTPUT
81+
echo "No previous release tag found, will create first release"
82+
echo "should_release=true" >> $GITHUB_OUTPUT
83+
else
84+
LATEST_VERSION=$(echo "$LATEST_TAG" | sed 's/agents-v//')
85+
echo "latest_version=$LATEST_VERSION" >> $GITHUB_OUTPUT
86+
echo "Latest released version: $LATEST_VERSION"
87+
88+
if [ "$CURRENT_VERSION" != "$LATEST_VERSION" ]; then
89+
echo "Version has changed ($LATEST_VERSION -> $CURRENT_VERSION)"
90+
echo "should_release=true" >> $GITHUB_OUTPUT
91+
else
92+
echo "Version unchanged"
93+
echo "should_release=false" >> $GITHUB_OUTPUT
94+
fi
95+
fi
96+
97+
release-pr:
98+
name: Update Release PR
99+
runs-on: depot-ubuntu-latest
100+
needs: check-release-status
101+
if: |
102+
github.event_name == 'push' &&
103+
needs.check-release-status.outputs.has_changes == 'true'
104+
steps:
105+
- uses: actions/checkout@v5
106+
with:
107+
fetch-depth: 0
108+
- uses: dtolnay/rust-toolchain@stable
109+
- name: Determine next version from conventional commits
110+
id: next_version
111+
env:
112+
CURRENT_VERSION: ${{ needs.check-release-status.outputs.current_version }}
113+
run: |
114+
# Use helper script to determine next version
115+
OUTPUT=$(./rust/scripts/ci/determine-next-version.sh "$CURRENT_VERSION")
116+
NEW_VERSION=$(echo "$OUTPUT" | sed -n '1p')
117+
BUMP_TYPE=$(echo "$OUTPUT" | sed -n '2p')
118+
119+
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
120+
echo "bump_type=$BUMP_TYPE" >> $GITHUB_OUTPUT
121+
echo "Next version: $NEW_VERSION ($BUMP_TYPE bump from $CURRENT_VERSION)"
122+
- name: Generate changelog
123+
id: changelog
124+
env:
125+
NEW_VERSION: ${{ steps.next_version.outputs.new_version }}
126+
run: |
127+
# Get commit range for changelog generation
128+
LATEST_TAG=$(./rust/scripts/ci/get-latest-agents-tag.sh)
129+
130+
if [ -z "$LATEST_TAG" ]; then
131+
COMMIT_RANGE=""
132+
else
133+
COMMIT_RANGE="${LATEST_TAG}..HEAD"
134+
fi
135+
136+
# Generate unified changelog for PR body
137+
if [ -z "$COMMIT_RANGE" ]; then
138+
CHANGELOG=$(./rust/scripts/ci/generate-workspace-changelog.sh --no-header)
139+
else
140+
CHANGELOG=$(./rust/scripts/ci/generate-workspace-changelog.sh "$COMMIT_RANGE" --no-header)
141+
fi
142+
143+
# Save changelog to file for PR body
144+
echo "$CHANGELOG" > /tmp/changelog.md
145+
146+
# Also output for GitHub Actions
147+
{
148+
echo 'changelog<<EOF'
149+
echo "$CHANGELOG"
150+
echo 'EOF'
151+
} >> $GITHUB_OUTPUT
152+
153+
# Generate per-workspace CHANGELOG.md files
154+
./rust/scripts/ci/generate-workspace-changelog.sh "$COMMIT_RANGE" "" --write-to-workspace "$NEW_VERSION"
155+
- name: Update version files
156+
env:
157+
NEW_VERSION: ${{ steps.next_version.outputs.new_version }}
158+
run: |
159+
# Update workspace version in Cargo.toml
160+
./rust/scripts/ci/update-workspace-version.sh "$NEW_VERSION"
161+
162+
# Update Cargo.lock in rust/main
163+
cd rust/main
164+
cargo update --workspace --offline 2>/dev/null || cargo update --workspace
165+
echo "Updated rust/main/Cargo.lock"
166+
167+
# Update Cargo.lock in rust/sealevel
168+
cd ../sealevel
169+
cargo update --workspace --offline 2>/dev/null || cargo update --workspace
170+
echo "Updated rust/sealevel/Cargo.lock"
171+
- name: Create or update release PR
172+
env:
173+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
174+
NEW_VERSION: ${{ steps.next_version.outputs.new_version }}
175+
BUMP_TYPE: ${{ steps.next_version.outputs.bump_type }}
176+
CHANGELOG: ${{ steps.changelog.outputs.changelog }}
177+
run: |
178+
git config user.name "github-actions[bot]"
179+
git config user.email "github-actions[bot]@users.noreply.github.com"
180+
181+
BRANCH_NAME="release-agents-v${NEW_VERSION}"
182+
183+
# Create branch from current HEAD (which is main)
184+
git checkout -B "$BRANCH_NAME"
185+
186+
# Stage changes (Cargo files and all workspace CHANGELOG.md files)
187+
git add rust/main/Cargo.toml rust/main/Cargo.lock rust/sealevel/Cargo.lock
188+
git add rust/main/*/CHANGELOG.md rust/main/*/*/CHANGELOG.md 2>/dev/null || true
189+
190+
# Commit changes
191+
git commit -m "release: agents v${NEW_VERSION}
192+
193+
This is a $BUMP_TYPE version bump for the Hyperlane agents.
194+
195+
Changes will be released as agents-v${NEW_VERSION} after this PR is merged."
196+
197+
# Force push to obliterate any existing branch
198+
git push -f origin "$BRANCH_NAME"
199+
200+
# Create or update PR
201+
PR_BODY="# Release agents v${NEW_VERSION}
202+
203+
This PR prepares the release of Hyperlane agents version **${NEW_VERSION}** (${BUMP_TYPE} bump).
204+
205+
## What's Changed
206+
207+
${CHANGELOG}
208+
209+
---
210+
211+
Once this PR is merged, the [\`rust-release.yml\`](https://github.com/${{ github.repository }}/blob/main/.github/workflows/rust-release.yml) workflow will automatically:
212+
- Create a GitHub release with tag \`agents-v${NEW_VERSION}\`
213+
- Trigger the build of release binaries
214+
215+
🤖 This PR was automatically created by the release workflow."
216+
217+
# Check if PR already exists
218+
EXISTING_PR=$(gh pr list --head "$BRANCH_NAME" --json number --jq '.[] | .number' || echo "")
219+
220+
if [ -n "$EXISTING_PR" ]; then
221+
echo "Updating existing PR #$EXISTING_PR"
222+
gh pr edit "$EXISTING_PR" \
223+
--title "release: agents v${NEW_VERSION}" \
224+
--body "$PR_BODY"
225+
echo "Updated PR: $(gh pr view $EXISTING_PR --json url --jq .url)"
226+
else
227+
echo "Creating new draft PR"
228+
gh pr create \
229+
--title "release: agents v${NEW_VERSION}" \
230+
--body "$PR_BODY" \
231+
--base main \
232+
--head "$BRANCH_NAME" \
233+
--label "release" \
234+
--draft
235+
PR_URL=$(gh pr list --head "$BRANCH_NAME" --json url --jq '.[] | .url')
236+
echo "Created draft PR: $PR_URL"
237+
fi
238+
- name: Summary
239+
if: always()
240+
env:
241+
NEW_VERSION: ${{ steps.next_version.outputs.new_version }}
242+
run: |
243+
echo "### Release PR for agents v${NEW_VERSION}" >> $GITHUB_STEP_SUMMARY
244+
echo "" >> $GITHUB_STEP_SUMMARY
245+
echo "The release PR has been created/updated." >> $GITHUB_STEP_SUMMARY
246+
echo "Once merged, the release will be published automatically." >> $GITHUB_STEP_SUMMARY
247+
248+
publish:
249+
name: Publish Release
250+
runs-on: depot-ubuntu-latest
251+
needs: check-release-status
252+
if: |
253+
github.event_name == 'workflow_dispatch' ||
254+
(github.ref == 'refs/heads/main' &&
255+
github.event_name == 'push' &&
256+
needs.check-release-status.outputs.should_release == 'true')
257+
steps:
258+
- uses: actions/checkout@v5
259+
with:
260+
fetch-depth: 0
261+
- name: Determine version and create release
262+
env:
263+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
264+
IS_PRERELEASE: ${{ github.event_name == 'workflow_dispatch' }}
265+
PRERELEASE_SUFFIX: ${{ inputs.prerelease_suffix }}
266+
BASE_VERSION: ${{ needs.check-release-status.outputs.current_version }}
267+
run: |
268+
git config user.name "github-actions[bot]"
269+
git config user.email "github-actions[bot]@users.noreply.github.com"
270+
271+
# Force prerelease mode if not on main branch
272+
if [ "${{ github.ref }}" != "refs/heads/main" ]; then
273+
echo "Not on main branch - forcing prerelease mode"
274+
IS_PRERELEASE="true"
275+
fi
276+
277+
# Determine final version based on release type
278+
# workflow_dispatch always creates pre-releases
279+
if [ "$IS_PRERELEASE" = "true" ]; then
280+
# Pre-release: append suffix
281+
if [ -n "$PRERELEASE_SUFFIX" ]; then
282+
SUFFIX="$PRERELEASE_SUFFIX"
283+
else
284+
# Auto-generate preview.N
285+
LAST_PREVIEW=$(git tag -l "agents-v${BASE_VERSION}-preview.*" | sort -V | tail -1)
286+
if [ -z "$LAST_PREVIEW" ]; then
287+
SUFFIX="preview.1"
288+
else
289+
PREVIEW_NUM=$(echo "$LAST_PREVIEW" | sed 's/.*preview\.\([0-9]*\)/\1/')
290+
SUFFIX="preview.$((PREVIEW_NUM + 1))"
291+
fi
292+
fi
293+
VERSION="${BASE_VERSION}-${SUFFIX}"
294+
TITLE="Agents $VERSION (Pre-release)"
295+
PRERELEASE_FLAG="--prerelease"
296+
RELEASE_TYPE="Pre-release"
297+
else
298+
# Stable release
299+
VERSION="$BASE_VERSION"
300+
TITLE="Agents $VERSION"
301+
PRERELEASE_FLAG=""
302+
RELEASE_TYPE="Release"
303+
fi
304+
305+
TAG_NAME="agents-v${VERSION}"
306+
echo "Creating $RELEASE_TYPE: $TAG_NAME"
307+
308+
# Check if tag already exists
309+
if git rev-parse "$TAG_NAME" >/dev/null 2>&1; then
310+
echo "Error: Tag $TAG_NAME already exists!" >&2
311+
echo "Cannot overwrite existing tag. Please use a different version or prerelease suffix." >&2
312+
exit 1
313+
fi
314+
315+
# Check if GitHub release already exists
316+
if gh release view "$TAG_NAME" --repo "${{ github.repository }}" >/dev/null 2>&1; then
317+
echo "Error: GitHub release $TAG_NAME already exists!" >&2
318+
echo "Cannot overwrite existing release. Please use a different version or prerelease suffix." >&2
319+
exit 1
320+
fi
321+
322+
# Generate workspace-grouped changelog
323+
PREV_TAG=$(git describe --tags --abbrev=0 --match "agents-v*" 2>/dev/null || echo "")
324+
325+
# For stable releases (push to main), use HEAD~1 to exclude the version bump commit
326+
# For prereleases (workflow_dispatch), use HEAD since there's no version bump commit
327+
if [ "$IS_PRERELEASE" = "true" ]; then
328+
COMMIT_RANGE_END="HEAD"
329+
else
330+
COMMIT_RANGE_END="HEAD~1"
331+
fi
332+
333+
if [ -z "$PREV_TAG" ]; then
334+
CHANGELOG=$(./rust/scripts/ci/generate-workspace-changelog.sh)
335+
else
336+
CHANGELOG=$(./rust/scripts/ci/generate-workspace-changelog.sh "${PREV_TAG}..${COMMIT_RANGE_END}")
337+
fi
338+
339+
# Note: We don't include "New Contributors" section for Rust releases
340+
# because GitHub's generate-notes API includes repo-wide contributors,
341+
# not just those who contributed to rust/main. This would be misleading.
342+
343+
# Add warning for pre-releases
344+
if [ "$IS_PRERELEASE" = "true" ]; then
345+
CHANGELOG="⚠️ **This is a pre-release version.**"$'\n\n'"${CHANGELOG}"
346+
fi
347+
348+
# Create tag and GitHub release
349+
git tag -a "$TAG_NAME" -m "$RELEASE_TYPE $TAG_NAME"
350+
git push origin "$TAG_NAME"
351+
352+
gh release create "$TAG_NAME" \
353+
--title "$TITLE" \
354+
--notes "$CHANGELOG" \
355+
$PRERELEASE_FLAG \
356+
--repo "${{ github.repository }}"
357+
358+
echo "$RELEASE_TYPE $TAG_NAME published successfully!" >> $GITHUB_STEP_SUMMARY
359+
[ "$IS_PRERELEASE" = "true" ] && echo "This is marked as a pre-release on GitHub." >> $GITHUB_STEP_SUMMARY
360+
echo "" >> $GITHUB_STEP_SUMMARY
361+
echo "Binary artifacts will be built automatically by the agent-release-artifacts workflow." >> $GITHUB_STEP_SUMMARY

0 commit comments

Comments
 (0)