From accd4732eee3a35f06373c5c534a317dd0ffacaf Mon Sep 17 00:00:00 2001 From: hkobew Date: Mon, 9 Mar 2026 09:22:49 -0400 Subject: [PATCH] feat: split release workflow into prepare and publish --- .../{release.yml => legacy-release.yml} | 4 +- .github/workflows/release-prepare.yml | 217 ++++++++++++++++++ .github/workflows/release-publish.yml | 158 +++++++++++++ 3 files changed, 378 insertions(+), 1 deletion(-) rename .github/workflows/{release.yml => legacy-release.yml} (98%) create mode 100644 .github/workflows/release-prepare.yml create mode 100644 .github/workflows/release-publish.yml diff --git a/.github/workflows/release.yml b/.github/workflows/legacy-release.yml similarity index 98% rename from .github/workflows/release.yml rename to .github/workflows/legacy-release.yml index 4d8d2e18..b22beb69 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/legacy-release.yml @@ -1,5 +1,7 @@ -name: Release +name: Legacy Release (deprecated) +# DEPRECATED: Replaced by release-prepare.yml and release-publish.yml +# Kept as a fallback. Remove once new workflows are verified. on: workflow_dispatch: inputs: diff --git a/.github/workflows/release-prepare.yml b/.github/workflows/release-prepare.yml new file mode 100644 index 00000000..c5a94a78 --- /dev/null +++ b/.github/workflows/release-prepare.yml @@ -0,0 +1,217 @@ +name: Release Prepare + +on: + workflow_dispatch: + inputs: + bump_type: + description: 'Version bump type' + required: true + type: choice + options: + - patch + - minor + - major + - pre + changelog: + description: 'Custom changelog entry (optional - leave empty to auto-generate)' + required: false + type: string + +permissions: + contents: write + pull-requests: write + +jobs: + prepare-release: + name: Prepare Release + runs-on: ubuntu-latest + outputs: + version: ${{ steps.bump.outputs.version }} + + steps: + - name: Validate running from main + if: github.ref != 'refs/heads/main' + run: | + echo "❌ ERROR: Must run from main branch, got ${{ github.ref }}" + exit 1 + + - name: Checkout code + uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Setup Python + uses: actions/setup-python@v6 + with: + python-version: '3.10' + + - name: Configure git + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + + - name: Install uv + run: | + curl -LsSf https://astral.sh/uv/install.sh | sh + echo "$HOME/.local/bin" >> $GITHUB_PATH + + - name: Get current version + id: current + run: | + VERSION=$(grep -m1 -oP '^version = "\K[^"]+' pyproject.toml) + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "Current version: $VERSION" + + - name: Bump version + id: bump + env: + CHANGELOG_INPUT: ${{ github.event.inputs.changelog }} + BUMP_TYPE: ${{ github.event.inputs.bump_type }} + run: | + chmod +x scripts/bump_version.py + + if [ -z "$CHANGELOG_INPUT" ]; then + echo "ℹ️ No custom changelog provided. Will auto-generate from commits." + fi + + if [ -n "$CHANGELOG_INPUT" ]; then + python scripts/bump_version.py "$BUMP_TYPE" --changelog "$CHANGELOG_INPUT" + else + python scripts/bump_version.py "$BUMP_TYPE" + fi + + uv lock --no-progress + + NEW_VERSION=$(grep -m1 -oP '^version = "\K[^"]+' pyproject.toml) + echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT + echo "New version: $NEW_VERSION" + + - name: Create release branch and push + env: + NEW_VERSION: ${{ steps.bump.outputs.version }} + run: | + BRANCH_NAME="release/v$NEW_VERSION" + + if git ls-remote --exit-code --heads origin $BRANCH_NAME; then + echo "⚠️ Branch $BRANCH_NAME already exists. Deleting it first..." + git push origin --delete $BRANCH_NAME + fi + + if git show-ref --verify --quiet refs/heads/$BRANCH_NAME; then + git branch -D $BRANCH_NAME + fi + + git checkout -b $BRANCH_NAME + git add -A + git commit -m "chore: bump version to $NEW_VERSION + + Co-authored-by: github-actions[bot] " + + git push origin $BRANCH_NAME + + COMMITTED_VERSION=$(git show HEAD:pyproject.toml | grep -m1 -oP '^version = "\K[^"]+') + if [ "$COMMITTED_VERSION" != "$NEW_VERSION" ]; then + echo "❌ ERROR: Version not committed correctly!" + exit 1 + fi + + - name: Create Pull Request + env: + GH_TOKEN: ${{ github.token }} + NEW_VERSION: ${{ steps.bump.outputs.version }} + GITHUB_REF: ${{ github.ref }} + GITHUB_ACTOR: ${{ github.actor }} + run: | + BRANCH_NAME="release/v$NEW_VERSION" + + WARNING_TEXT="" + if [ "$GITHUB_REF" != "refs/heads/main" ]; then + WARNING_TEXT="**WARNING**: Not running from main branch!" + else + WARNING_TEXT="✅ Running from main branch" + fi + + gh pr create \ + --base main \ + --head "$BRANCH_NAME" \ + --title "Release v$NEW_VERSION" \ + --body "## 🚀 Release v$NEW_VERSION + + This PR was automatically created by the release workflow. + + ### ⚠️ Pre-merge Checklist + - [ ] Review CHANGELOG.md - ensure it has meaningful release notes + - [ ] Verify version numbers are correct in all files + - [ ] All CI checks are passing + + ### 📝 How to improve changelog + If the auto-generated changelog isn't good enough: + 1. Edit CHANGELOG.md in this PR + 2. Commit the changes + 3. Then approve and merge + + ### 🔄 Release Process + After merging this PR, the **release-publish** workflow will: + 1. Build the package from main + 2. Require **manual approval** before publishing to PyPI + 3. Publish to PyPI, create tag and GitHub release + + ### 🚨 Running from: $GITHUB_REF + $WARNING_TEXT + + --- + *Triggered by @$GITHUB_ACTOR*" + + test-and-build: + name: Test and Build + needs: prepare-release + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v5 + with: + ref: release/v${{ needs.prepare-release.outputs.version }} + + - name: Verify version before build + env: + EXPECTED_VERSION: ${{ needs.prepare-release.outputs.version }} + run: | + ACTUAL_VERSION=$(grep -m1 -oP '^version = "\K[^"]+' pyproject.toml) + echo "Expected version: $EXPECTED_VERSION" + echo "Actual version: $ACTUAL_VERSION" + + if [ "$ACTUAL_VERSION" != "$EXPECTED_VERSION" ]; then + echo "❌ ERROR: Version mismatch!" + exit 1 + fi + echo "✓ Version verified: $ACTUAL_VERSION" + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: '3.10' + + - name: Install uv + run: | + curl -LsSf https://astral.sh/uv/install.sh | sh + echo "$HOME/.local/bin" >> $GITHUB_PATH + + - name: Setup build environment + run: | + uv venv + source .venv/bin/activate + uv pip install build twine + + - name: Build and check package + run: | + source .venv/bin/activate + uv build + twine check dist/* + echo "=== Package contents ===" + python -m zipfile -l dist/*.whl | head -20 + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: dist + path: dist/ diff --git a/.github/workflows/release-publish.yml b/.github/workflows/release-publish.yml new file mode 100644 index 00000000..7b84a83f --- /dev/null +++ b/.github/workflows/release-publish.yml @@ -0,0 +1,158 @@ +name: Release Publish + +on: + pull_request: + types: [closed] + branches: [main] + +permissions: + contents: write + +jobs: + build: + name: Build from Main + if: > + github.event.pull_request.merged == true && + startsWith(github.event.pull_request.head.ref, 'release/v') + runs-on: ubuntu-latest + outputs: + version: ${{ steps.version.outputs.version }} + + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: '3.10' + + - name: Install uv + run: | + curl -LsSf https://astral.sh/uv/install.sh | sh + echo "$HOME/.local/bin" >> $GITHUB_PATH + + - name: Build and check package + run: | + uv venv + source .venv/bin/activate + uv pip install build twine + uv build + twine check dist/* + + - name: Get version + id: version + run: | + VERSION=$(ls dist/*.whl | sed -n 's/.*-\([0-9.]*\)-.*/\1/p') + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "Version: $VERSION" + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: dist + path: dist/ + + release-approval: + name: Release Approval + needs: build + runs-on: ubuntu-latest + environment: + name: pypi-approval + + steps: + - name: Approval checkpoint + env: + VERSION: ${{ needs.build.outputs.version }} + run: | + echo "✅ Build successful for v$VERSION" + echo "📦 Package ready for PyPI publication" + echo "" + echo "⚠️ MANUAL APPROVAL REQUIRED" + echo "Verify version and changelog before approving." + + publish-pypi: + name: Publish to PyPI + needs: [build, release-approval] + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/project/bedrock-agentcore/ + + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Download artifacts + uses: actions/download-artifact@v5 + with: + name: dist + path: dist/ + + - name: Verify PyPI token exists + env: + PYPI_TOKEN_SET: ${{ secrets.PYPI_API_TOKEN != '' }} + run: | + if [ "$PYPI_TOKEN_SET" != "true" ]; then + echo "❌ ERROR: PYPI_API_TOKEN not configured!" + exit 1 + fi + echo "✓ PyPI token is configured" + + - name: Check if version exists on PyPI + env: + VERSION: ${{ needs.build.outputs.version }} + run: | + if pip index versions bedrock-agentcore | grep -q "^Available versions.*$VERSION"; then + echo "❌ ERROR: Version $VERSION already exists on PyPI!" + exit 1 + fi + echo "✓ Version $VERSION is not on PyPI, safe to publish" + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.PYPI_API_TOKEN }} + skip-existing: false + verbose: true + + - name: Wait for PyPI availability + env: + VERSION: ${{ needs.build.outputs.version }} + run: | + echo "Waiting for package to be available on PyPI..." + for i in {1..10}; do + if pip index versions bedrock-agentcore | grep -q "$VERSION"; then + echo "✓ Package version $VERSION is now available on PyPI" + break + fi + echo "Attempt $i/10: Package not yet available, waiting 30s..." + sleep 30 + done + + - name: Create and push tag + env: + VERSION: ${{ needs.build.outputs.version }} + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git tag -a "v$VERSION" -m "Release v$VERSION" + git push origin "v$VERSION" + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + tag_name: v${{ needs.build.outputs.version }} + name: Bedrock AgentCore SDK v${{ needs.build.outputs.version }} + files: dist/* + generate_release_notes: true + body: | + ## Installation + ```bash + pip install bedrock-agentcore==${{ needs.build.outputs.version }} + ``` + + ## What's Changed + See [CHANGELOG.md](https://github.com/${{ github.repository }}/blob/v${{ needs.build.outputs.version }}/CHANGELOG.md) for details.