Skip to content

Commit d497fe2

Browse files
committed
feat: split release workflow into prepare and publish
1 parent ca3c322 commit d497fe2

3 files changed

Lines changed: 391 additions & 1 deletion

File tree

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
name: Release
1+
name: Legacy Release (deprecated)
22

3+
# DEPRECATED: Replaced by release-prepare.yml and release-publish.yml
4+
# Kept as a fallback. Remove once new workflows are verified.
35
on:
46
workflow_dispatch:
57
inputs:
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
name: Release Prepare
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
bump_type:
7+
description: 'Version bump type'
8+
required: true
9+
type: choice
10+
options:
11+
- patch
12+
- minor
13+
- major
14+
- pre
15+
changelog:
16+
description: 'Custom changelog entry (optional - leave empty to auto-generate)'
17+
required: false
18+
type: string
19+
20+
permissions:
21+
contents: write
22+
pull-requests: write
23+
24+
jobs:
25+
prepare-release:
26+
name: Prepare Release
27+
runs-on: ubuntu-latest
28+
outputs:
29+
version: ${{ steps.bump.outputs.version }}
30+
31+
steps:
32+
- name: Validate running from main
33+
run: |
34+
if [[ "${{ github.ref }}" != "refs/heads/main" ]]; then
35+
echo "⚠️ WARNING: Running from ${{ github.ref }}"
36+
echo "⚠️ Production releases should only run from main branch"
37+
fi
38+
39+
- name: Checkout code
40+
uses: actions/checkout@v5
41+
with:
42+
fetch-depth: 0
43+
44+
- name: Setup Python
45+
uses: actions/setup-python@v6
46+
with:
47+
python-version: '3.10'
48+
49+
- name: Configure git
50+
run: |
51+
git config --global user.name "github-actions[bot]"
52+
git config --global user.email "github-actions[bot]@users.noreply.github.com"
53+
54+
- name: Install uv
55+
run: |
56+
curl -LsSf https://astral.sh/uv/install.sh | sh
57+
echo "$HOME/.local/bin" >> $GITHUB_PATH
58+
59+
- name: Get current version
60+
id: current
61+
run: |
62+
VERSION=$(grep -m1 -oP '^version = "\K[^"]+' pyproject.toml)
63+
echo "version=$VERSION" >> $GITHUB_OUTPUT
64+
echo "Current version: $VERSION"
65+
66+
- name: Bump version
67+
id: bump
68+
env:
69+
CHANGELOG_INPUT: ${{ github.event.inputs.changelog }}
70+
BUMP_TYPE: ${{ github.event.inputs.bump_type }}
71+
run: |
72+
chmod +x scripts/bump_version.py
73+
74+
if [ -z "$CHANGELOG_INPUT" ]; then
75+
echo "ℹ️ No custom changelog provided. Will auto-generate from commits."
76+
fi
77+
78+
if [ -n "$CHANGELOG_INPUT" ]; then
79+
python scripts/bump_version.py "$BUMP_TYPE" --changelog "$CHANGELOG_INPUT"
80+
else
81+
python scripts/bump_version.py "$BUMP_TYPE"
82+
fi
83+
84+
uv lock --no-progress
85+
86+
NEW_VERSION=$(grep -m1 -oP '^version = "\K[^"]+' pyproject.toml)
87+
echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT
88+
echo "New version: $NEW_VERSION"
89+
90+
- name: Create release branch and push
91+
env:
92+
NEW_VERSION: ${{ steps.bump.outputs.version }}
93+
run: |
94+
BRANCH_NAME="release/v$NEW_VERSION"
95+
96+
if git ls-remote --exit-code --heads origin $BRANCH_NAME; then
97+
echo "⚠️ Branch $BRANCH_NAME already exists. Deleting it first..."
98+
git push origin --delete $BRANCH_NAME
99+
fi
100+
101+
if git show-ref --verify --quiet refs/heads/$BRANCH_NAME; then
102+
git branch -D $BRANCH_NAME
103+
fi
104+
105+
git checkout -b $BRANCH_NAME
106+
git add -A
107+
git commit -m "chore: bump version to $NEW_VERSION
108+
109+
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>"
110+
111+
git push origin $BRANCH_NAME
112+
113+
COMMITTED_VERSION=$(git show HEAD:pyproject.toml | grep -m1 -oP '^version = "\K[^"]+')
114+
if [ "$COMMITTED_VERSION" != "$NEW_VERSION" ]; then
115+
echo "❌ ERROR: Version not committed correctly!"
116+
exit 1
117+
fi
118+
119+
- name: Create Pull Request
120+
env:
121+
GH_TOKEN: ${{ github.token }}
122+
NEW_VERSION: ${{ steps.bump.outputs.version }}
123+
GITHUB_REF: ${{ github.ref }}
124+
GITHUB_ACTOR: ${{ github.actor }}
125+
run: |
126+
BRANCH_NAME="release/v$NEW_VERSION"
127+
128+
WARNING_TEXT=""
129+
if [ "$GITHUB_REF" != "refs/heads/main" ]; then
130+
WARNING_TEXT="**WARNING**: Not running from main branch!"
131+
else
132+
WARNING_TEXT="✅ Running from main branch"
133+
fi
134+
135+
gh pr create \
136+
--base main \
137+
--head "$BRANCH_NAME" \
138+
--title "Release v$NEW_VERSION" \
139+
--body "## 🚀 Release v$NEW_VERSION
140+
141+
This PR was automatically created by the release workflow.
142+
143+
### ⚠️ Pre-merge Checklist
144+
- [ ] Review CHANGELOG.md - ensure it has meaningful release notes
145+
- [ ] Verify version numbers are correct in all files
146+
- [ ] All CI checks are passing
147+
148+
### 📝 How to improve changelog
149+
If the auto-generated changelog isn't good enough:
150+
1. Edit CHANGELOG.md in this PR
151+
2. Commit the changes
152+
3. Then approve and merge
153+
154+
### 🔄 Release Process
155+
After merging this PR, the **release-publish** workflow will:
156+
1. Build the package from main
157+
2. Require **manual approval** before publishing to PyPI
158+
3. Publish to PyPI, create tag and GitHub release
159+
160+
### 🚨 Running from: $GITHUB_REF
161+
$WARNING_TEXT
162+
163+
---
164+
*Triggered by @$GITHUB_ACTOR*"
165+
166+
test-and-build:
167+
name: Test and Build
168+
needs: prepare-release
169+
runs-on: ubuntu-latest
170+
171+
steps:
172+
- uses: actions/checkout@v5
173+
with:
174+
ref: release/v${{ needs.prepare-release.outputs.version }}
175+
176+
- name: Verify version before build
177+
env:
178+
EXPECTED_VERSION: ${{ needs.prepare-release.outputs.version }}
179+
run: |
180+
ACTUAL_VERSION=$(grep -m1 -oP '^version = "\K[^"]+' pyproject.toml)
181+
echo "Expected version: $EXPECTED_VERSION"
182+
echo "Actual version: $ACTUAL_VERSION"
183+
184+
if [ "$ACTUAL_VERSION" != "$EXPECTED_VERSION" ]; then
185+
echo "❌ ERROR: Version mismatch!"
186+
exit 1
187+
fi
188+
echo "✓ Version verified: $ACTUAL_VERSION"
189+
190+
- name: Set up Python
191+
uses: actions/setup-python@v6
192+
with:
193+
python-version: '3.10'
194+
195+
- name: Install uv
196+
run: |
197+
curl -LsSf https://astral.sh/uv/install.sh | sh
198+
echo "$HOME/.local/bin" >> $GITHUB_PATH
199+
200+
- name: Setup build environment
201+
run: |
202+
uv venv
203+
source .venv/bin/activate
204+
uv pip install build twine
205+
206+
- name: Build and check package
207+
run: |
208+
source .venv/bin/activate
209+
uv build
210+
twine check dist/*
211+
echo "=== Package contents ==="
212+
python -m zipfile -l dist/*.whl | head -20
213+
214+
- name: Upload artifacts
215+
uses: actions/upload-artifact@v4
216+
with:
217+
name: dist
218+
path: dist/
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
name: Release Publish
2+
3+
on:
4+
pull_request:
5+
types: [closed]
6+
branches: [main]
7+
workflow_dispatch:
8+
inputs:
9+
dry_run:
10+
description: 'Dry run (skip PyPI publish, still creates tag and GitHub release)'
11+
required: false
12+
type: boolean
13+
default: false
14+
15+
permissions:
16+
contents: write
17+
18+
jobs:
19+
build:
20+
name: Build from Main
21+
if: >
22+
github.event_name == 'workflow_dispatch' ||
23+
(github.event.pull_request.merged == true &&
24+
startsWith(github.event.pull_request.head.ref, 'release/v'))
25+
runs-on: ubuntu-latest
26+
outputs:
27+
version: ${{ steps.version.outputs.version }}
28+
29+
steps:
30+
- uses: actions/checkout@v5
31+
with:
32+
fetch-depth: 0
33+
34+
- name: Extract version from pyproject.toml
35+
id: version
36+
run: |
37+
VERSION=$(grep -m1 -oP '^version = "\K[^"]+' pyproject.toml)
38+
echo "version=$VERSION" >> $GITHUB_OUTPUT
39+
echo "Version: $VERSION"
40+
41+
- name: Set up Python
42+
uses: actions/setup-python@v6
43+
with:
44+
python-version: '3.10'
45+
46+
- name: Install uv
47+
run: |
48+
curl -LsSf https://astral.sh/uv/install.sh | sh
49+
echo "$HOME/.local/bin" >> $GITHUB_PATH
50+
51+
- name: Build and check package
52+
run: |
53+
uv venv
54+
source .venv/bin/activate
55+
uv pip install build twine
56+
uv build
57+
twine check dist/*
58+
59+
- name: Upload artifacts
60+
uses: actions/upload-artifact@v4
61+
with:
62+
name: dist
63+
path: dist/
64+
65+
release-approval:
66+
name: Release Approval
67+
needs: build
68+
runs-on: ubuntu-latest
69+
environment:
70+
name: pypi-approval
71+
72+
steps:
73+
- name: Approval checkpoint
74+
env:
75+
VERSION: ${{ needs.build.outputs.version }}
76+
run: |
77+
echo "✅ Build successful for v$VERSION"
78+
echo "📦 Package ready for PyPI publication"
79+
echo ""
80+
echo "⚠️ MANUAL APPROVAL REQUIRED"
81+
echo "Verify version and changelog before approving."
82+
83+
publish-pypi:
84+
name: Publish to PyPI
85+
needs: [build, release-approval]
86+
runs-on: ubuntu-latest
87+
environment:
88+
name: pypi
89+
url: https://pypi.org/project/bedrock-agentcore/
90+
91+
steps:
92+
- uses: actions/checkout@v5
93+
with:
94+
fetch-depth: 0
95+
96+
- name: Download artifacts
97+
uses: actions/download-artifact@v5
98+
with:
99+
name: dist
100+
path: dist/
101+
102+
- name: Verify PyPI token exists
103+
if: github.event.inputs.dry_run != 'true'
104+
env:
105+
PYPI_TOKEN_SET: ${{ secrets.PYPI_API_TOKEN != '' }}
106+
run: |
107+
if [ "$PYPI_TOKEN_SET" != "true" ]; then
108+
echo "❌ ERROR: PYPI_API_TOKEN not configured!"
109+
exit 1
110+
fi
111+
echo "✓ PyPI token is configured"
112+
113+
- name: Check if version exists on PyPI
114+
if: github.event.inputs.dry_run != 'true'
115+
env:
116+
VERSION: ${{ needs.build.outputs.version }}
117+
run: |
118+
if pip index versions bedrock-agentcore | grep -q "^Available versions.*$VERSION"; then
119+
echo "❌ ERROR: Version $VERSION already exists on PyPI!"
120+
exit 1
121+
fi
122+
echo "✓ Version $VERSION is not on PyPI, safe to publish"
123+
124+
- name: Publish to PyPI
125+
if: github.event.inputs.dry_run != 'true'
126+
uses: pypa/gh-action-pypi-publish@release/v1
127+
with:
128+
password: ${{ secrets.PYPI_API_TOKEN }}
129+
skip-existing: false
130+
verbose: true
131+
132+
- name: Wait for PyPI availability
133+
if: github.event.inputs.dry_run != 'true'
134+
env:
135+
VERSION: ${{ needs.build.outputs.version }}
136+
run: |
137+
echo "Waiting for package to be available on PyPI..."
138+
for i in {1..10}; do
139+
if pip index versions bedrock-agentcore | grep -q "$VERSION"; then
140+
echo "✓ Package version $VERSION is now available on PyPI"
141+
break
142+
fi
143+
echo "Attempt $i/10: Package not yet available, waiting 30s..."
144+
sleep 30
145+
done
146+
147+
- name: Create and push tag
148+
env:
149+
VERSION: ${{ needs.build.outputs.version }}
150+
run: |
151+
git config --global user.name "github-actions[bot]"
152+
git config --global user.email "github-actions[bot]@users.noreply.github.com"
153+
git tag -a "v$VERSION" -m "Release v$VERSION"
154+
git push origin "v$VERSION"
155+
156+
- name: Create GitHub Release
157+
uses: softprops/action-gh-release@v2
158+
with:
159+
tag_name: v${{ needs.build.outputs.version }}
160+
name: Bedrock AgentCore SDK v${{ needs.build.outputs.version }}
161+
files: dist/*
162+
generate_release_notes: true
163+
body: |
164+
## Installation
165+
```bash
166+
pip install bedrock-agentcore==${{ needs.build.outputs.version }}
167+
```
168+
169+
## What's Changed
170+
See [CHANGELOG.md](https://github.com/${{ github.repository }}/blob/v${{ needs.build.outputs.version }}/CHANGELOG.md) for details.

0 commit comments

Comments
 (0)