name: Version Bump on: push: branches: - dev paths-ignore: - 'VERSION' - '.github/workflows/version-bump.yml' jobs: bump-version: runs-on: ubuntu-latest # Skip if commit message contains [skip ci] or [skip version] if: "!contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[skip version]') && !startsWith(github.event.head_commit.message, 'chore: bump version')" permissions: contents: write steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 token: ${{ secrets.GITHUB_TOKEN }} - name: Get current version id: current run: | if [ -f VERSION ]; then VERSION=$(cat VERSION | tr -d '\n\r ') else VERSION="0.0.0" fi echo "version=$VERSION" >> $GITHUB_OUTPUT echo "Current version: $VERSION" - name: Determine bump type id: bump env: # Pass the commit message through the environment, NOT string- # interpolated into the run script. Splicing # ${{ github.event.head_commit.message }} directly into the shell # source means a body containing backticks, $(...) or | is parsed # as code — which broke this job (commit e3337f0's body had # `vagrant up | Out-Host`, a syntax error) and is a command- # injection vector. As an env var it is inert text bash never re-parses. COMMIT_MSG: ${{ github.event.head_commit.message }} run: | # Major if a [major] tag OR a BREAKING/major: conventional prefix is # present; minor for [minor] tag OR feat/feature/minor: prefix; # otherwise patch. Supporting both keeps existing conventional # commits working while allowing explicit [minor]/[major] tags. if echo "$COMMIT_MSG" | grep -qiE '\[major\]' || \ echo "$COMMIT_MSG" | grep -qiE '^(BREAKING|breaking!|major):'; then echo "type=major" >> $GITHUB_OUTPUT echo "Bump type: major" elif echo "$COMMIT_MSG" | grep -qiE '\[minor\]' || \ echo "$COMMIT_MSG" | grep -qiE '^(feat|feature|minor):'; then echo "type=minor" >> $GITHUB_OUTPUT echo "Bump type: minor" else echo "type=patch" >> $GITHUB_OUTPUT echo "Bump type: patch" fi - name: Calculate new version id: new run: | VERSION="${{ steps.current.outputs.version }}" BUMP_TYPE="${{ steps.bump.outputs.type }}" # Parse version components MAJOR=$(echo $VERSION | cut -d. -f1) MINOR=$(echo $VERSION | cut -d. -f2) PATCH=$(echo $VERSION | cut -d. -f3) # Bump appropriate component case $BUMP_TYPE in major) MAJOR=$((MAJOR + 1)) MINOR=0 PATCH=0 ;; minor) MINOR=$((MINOR + 1)) PATCH=0 ;; patch) PATCH=$((PATCH + 1)) ;; esac NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}" echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT echo "New version: $NEW_VERSION" - name: Update VERSION file run: | echo "${{ steps.new.outputs.version }}" > VERSION cat VERSION - name: Commit and push run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" git add VERSION git commit -m "chore: bump version to ${{ steps.new.outputs.version }} [skip ci]" git push - name: Create summary env: # Same injection-safety reason as the bump step: keep the raw # commit message out of the run script source. TRIGGER_MSG: ${{ github.event.head_commit.message }} run: | echo "## Version Bump Summary" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY echo "| Previous Version | ${{ steps.current.outputs.version }} |" >> $GITHUB_STEP_SUMMARY echo "| New Version | ${{ steps.new.outputs.version }} |" >> $GITHUB_STEP_SUMMARY echo "| Bump Type | ${{ steps.bump.outputs.type }} |" >> $GITHUB_STEP_SUMMARY # First line only, and via env — keeps a multi-line / special-char # body from corrupting the markdown table or the script. echo "| Trigger Commit | $(echo "$TRIGGER_MSG" | head -n1) |" >> $GITHUB_STEP_SUMMARY