Release Trigger #13
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release Trigger | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: 'Version to release (e.g., 0.1.11). Leave empty to auto-increment patch version.' | |
| required: false | |
| type: string | |
| jobs: | |
| bump-version: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ secrets.RELEASE_PAT }} | |
| - name: Configure Git | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| - name: Determine version | |
| id: version | |
| env: | |
| INPUT_VERSION: ${{ github.event.inputs.version }} | |
| run: | | |
| if [[ -n "$INPUT_VERSION" ]]; then | |
| # Manual version specified - strip optional v prefix | |
| VERSION="${INPUT_VERSION#v}" | |
| # Validate strict semver format to prevent injection | |
| if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then | |
| echo "Error: Invalid version format '$VERSION'. Must be X.Y.Z (e.g. 1.2.3 or v1.2.3)" | |
| exit 1 | |
| fi | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| echo "tag=v$VERSION" >> $GITHUB_OUTPUT | |
| echo "Using manual version: $VERSION" | |
| else | |
| # Auto-increment patch version | |
| LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0") | |
| echo "Latest tag: $LATEST_TAG" | |
| # Extract version number and increment | |
| VERSION=$(echo $LATEST_TAG | sed 's/v//') | |
| IFS='.' read -ra VERSION_PARTS <<< "$VERSION" | |
| MAJOR=${VERSION_PARTS[0]:-0} | |
| MINOR=${VERSION_PARTS[1]:-0} | |
| PATCH=${VERSION_PARTS[2]:-0} | |
| # Increment patch version | |
| PATCH=$((PATCH + 1)) | |
| NEW_VERSION="$MAJOR.$MINOR.$PATCH" | |
| echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT | |
| echo "tag=v$NEW_VERSION" >> $GITHUB_OUTPUT | |
| echo "Auto-incremented version: $NEW_VERSION" | |
| fi | |
| - name: Check if tag already exists | |
| run: | | |
| if git rev-parse "${{ steps.version.outputs.tag }}" >/dev/null 2>&1; then | |
| echo "Error: Tag ${{ steps.version.outputs.tag }} already exists!" | |
| exit 1 | |
| fi | |
| - name: Create release branch | |
| run: | | |
| BRANCH="chore/release-${{ steps.version.outputs.tag }}" | |
| git checkout -b "$BRANCH" | |
| echo "branch=$BRANCH" >> $GITHUB_ENV | |
| - name: Update pyproject.toml | |
| run: | | |
| sed -i "s/version = \".*\"/version = \"${{ steps.version.outputs.version }}\"/" pyproject.toml | |
| echo "Updated pyproject.toml to version ${{ steps.version.outputs.version }}" | |
| - name: Update CHANGELOG.md | |
| run: | | |
| if [ -f "CHANGELOG.md" ]; then | |
| DATE=$(date +%Y-%m-%d) | |
| # Get the previous tag by sorting all version tags numerically | |
| # (git describe --tags only finds tags reachable from HEAD, | |
| # which misses tags on unmerged release branches) | |
| PREVIOUS_TAG=$(git tag -l 'v*' --sort=-version:refname | head -n 1) | |
| echo "Generating changelog from commits..." | |
| if [[ -n "$PREVIOUS_TAG" ]]; then | |
| echo "Changes since $PREVIOUS_TAG" | |
| COMMITS=$(git log --oneline "$PREVIOUS_TAG"..HEAD --no-merges --pretty=format:"- %s" 2>/dev/null || echo "- Initial release") | |
| else | |
| echo "No previous tag found - this is the first release" | |
| COMMITS="- Initial release" | |
| fi | |
| # Create new changelog entry — insert after the marker comment | |
| NEW_ENTRY=$(printf '%s\n' \ | |
| "" \ | |
| "## [${{ steps.version.outputs.version }}] - $DATE" \ | |
| "" \ | |
| "### Changed" \ | |
| "" \ | |
| "$COMMITS") | |
| awk -v entry="$NEW_ENTRY" '/<!-- insert new changelog below this comment -->/ { print; print entry; next } {print}' CHANGELOG.md > CHANGELOG.md.tmp | |
| mv CHANGELOG.md.tmp CHANGELOG.md | |
| echo "✅ Updated CHANGELOG.md with commits since $PREVIOUS_TAG" | |
| else | |
| echo "No CHANGELOG.md found" | |
| fi | |
| - name: Commit version bump | |
| run: | | |
| if [ -f "CHANGELOG.md" ]; then | |
| git add pyproject.toml CHANGELOG.md | |
| else | |
| git add pyproject.toml | |
| fi | |
| if git diff --cached --quiet; then | |
| echo "No changes to commit" | |
| else | |
| git commit -m "chore: bump version to ${{ steps.version.outputs.version }}" | |
| echo "Changes committed" | |
| fi | |
| - name: Create and push tag | |
| run: | | |
| git tag -a "${{ steps.version.outputs.tag }}" -m "Release ${{ steps.version.outputs.tag }}" | |
| git push origin "${{ env.branch }}" | |
| git push origin "${{ steps.version.outputs.tag }}" | |
| echo "Branch ${{ env.branch }} and tag ${{ steps.version.outputs.tag }} pushed" | |
| - name: Open pull request | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.RELEASE_PAT }} | |
| run: | | |
| gh pr create \ | |
| --base main \ | |
| --head "${{ env.branch }}" \ | |
| --title "chore: bump version to ${{ steps.version.outputs.version }}" \ | |
| --body "Automated version bump to ${{ steps.version.outputs.version }}. | |
| This PR was created by the Release Trigger workflow. The git tag \`${{ steps.version.outputs.tag }}\` has already been pushed and the release artifacts are being built. | |
| Merge this PR to record the version bump and changelog update on \`main\`." | |
| - name: Summary | |
| run: | | |
| echo "✅ Version bumped to ${{ steps.version.outputs.version }}" | |
| echo "✅ Tag ${{ steps.version.outputs.tag }} created and pushed" | |
| echo "✅ PR opened to merge version bump into main" | |
| echo "🚀 Release workflow is building artifacts from the tag" |