changelogs 0.1.0

Manage versioning and changelogs for Cargo workspaces
Documentation
name: 'Changelogs'
description: 'Create a PR with version bumps and changelogs, and publish packages when merged'
author: 'wevm'

branding:
  icon: 'package'
  color: 'orange'

inputs:
  version:
    description: 'Command to run for versioning'
    required: false
    default: 'changelogs version'
  publish:
    description: 'Command to run for publishing (runs when no changelogs are pending)'
    required: false
  crate-token:
    description: 'Crates.io API token for publishing'
    required: false
  commit:
    description: 'Commit message for version bump (overrides conventional-commit)'
    required: false
  conventional-commit:
    description: 'Use conventional commit format (chore: prefix)'
    required: false
    default: 'false'
  branch:
    description: 'Branch name for the version PR'
    required: false
    default: 'changelog-release/main'
  github-token:
    description: 'GitHub token for creating PRs'
    required: false
    default: ${{ github.token }}

outputs:
  hasChangelogs:
    description: 'Whether there are pending changelogs'
    value: ${{ steps.check.outputs.hasChangelogs }}
  pullRequestNumber:
    description: 'The pull request number if created or updated'
    value: ${{ steps.pr.outputs.pull-request-number }}
  published:
    description: 'Whether packages were published'
    value: ${{ steps.publish.outputs.published }}
  publishedPackages:
    description: 'JSON array of published packages'
    value: ${{ steps.publish.outputs.publishedPackages }}

runs:
  using: 'composite'
  steps:
    - name: Check for changelogs
      id: check
      shell: bash
      run: |
        if [ -d ".changelog" ] && [ "$(find .changelog -name '*.md' ! -name 'README.md' 2>/dev/null | head -1)" ]; then
          echo "hasChangelogs=true" >> $GITHUB_OUTPUT
          echo "Found pending changelogs"
        else
          echo "hasChangelogs=false" >> $GITHUB_OUTPUT
          echo "No pending changelogs"
        fi

    # Version mode: Create PR when changelogs exist
    - name: Setup Git user
      if: steps.check.outputs.hasChangelogs == 'true'
      shell: bash
      run: |
        git config user.name "github-actions[bot]"
        git config user.email "41898282+github-actions[bot]@users.noreply.github.com"

    - name: Run version command
      if: steps.check.outputs.hasChangelogs == 'true'
      id: version
      shell: bash
      run: |
        output=$(${{ inputs.version }} 2>&1)
        echo "$output"
        
        # Extract versions from output (e.g., "changelogs 0.0.1 → 0.1.0" -> "changelogs@0.1.0")
        all_versions=$(echo "$output" | grep -oE '[a-zA-Z0-9_-]+ [0-9]+\.[0-9]+\.[0-9]+ → [0-9]+\.[0-9]+\.[0-9]+' | sed 's/ .* → /@/')
        count=$(echo "$all_versions" | grep -c . || echo 0)
        
        if [ "$count" -eq 0 ]; then
          versions=""
        elif [ "$count" -eq 1 ]; then
          versions="\`$(echo "$all_versions" | head -1)\`"
        elif [ "$count" -eq 2 ]; then
          first=$(echo "$all_versions" | head -1)
          second=$(echo "$all_versions" | tail -1)
          versions="\`$first\` and \`$second\`"
        else
          first=$(echo "$all_versions" | head -1)
          second=$(echo "$all_versions" | sed -n '2p')
          remaining=$((count - 2))
          versions="\`$first\`, \`$second\` and $remaining more"
        fi
        echo "versions=$versions" >> $GITHUB_OUTPUT
        
        # Build title and commit message with optional conventional commit prefix
        if [ "${{ inputs.conventional-commit }}" = "true" ]; then
          echo "title=chore: release $versions" >> $GITHUB_OUTPUT
          echo "commit-msg=chore: release $versions" >> $GITHUB_OUTPUT
        else
          echo "title=Release $versions" >> $GITHUB_OUTPUT
          echo "commit-msg=Release $versions" >> $GITHUB_OUTPUT
        fi

    - name: Generate PR body
      if: steps.check.outputs.hasChangelogs == 'true'
      id: body
      shell: bash
      run: |
        {
          echo "This PR was opened by the Changelogs release workflow."
          echo ""
          echo "When you're ready to release, merge this PR and the packages will be published."
          echo ""
          echo "---"
          echo ""
          
          # Show the new changelog entries (uncommitted changes from changelogs version)
          if [ -f "CHANGELOG.md" ]; then
            git diff -- CHANGELOG.md | grep '^+' | grep -v '^+++' | sed 's/^+//' | head -100 || true
          fi
        } > /tmp/pr-body.md
        
        echo "body-file=/tmp/pr-body.md" >> $GITHUB_OUTPUT

    - name: Create or update PR
      if: steps.check.outputs.hasChangelogs == 'true'
      id: pr
      uses: peter-evans/create-pull-request@v7
      with:
        token: ${{ inputs.github-token }}
        branch: ${{ inputs.branch }}
        title: ${{ steps.version.outputs.title }}
        body-path: ${{ steps.body.outputs.body-file }}
        base: ${{ github.ref_name }}
        commit-message: ${{ inputs.commit || steps.version.outputs.commit-msg }}
        delete-branch: true

    # Publish mode: Publish when no changelogs (PR was just merged)
    - name: Setup Git user for tags
      if: steps.check.outputs.hasChangelogs == 'false' && inputs.publish != ''
      shell: bash
      run: |
        git config user.name "github-actions[bot]"
        git config user.email "41898282+github-actions[bot]@users.noreply.github.com"

    - name: Publish packages
      if: steps.check.outputs.hasChangelogs == 'false' && inputs.publish != ''
      id: publish
      shell: bash
      env:
        CARGO_REGISTRY_TOKEN: ${{ inputs.crate-token }}
      run: |
        echo "Running publish command: ${{ inputs.publish }}"
        output=$(${{ inputs.publish }} 2>&1) || true
        echo "$output"
        
        # Parse published packages from output
        packages=$(echo "$output" | grep -E "^\s+\S+\s+v[0-9]" | grep "✓" | awk '{print $1}' | jq -R -s -c 'split("\n") | map(select(length > 0))')
        
        if [ "$packages" != "[]" ] && [ -n "$packages" ]; then
          echo "published=true" >> $GITHUB_OUTPUT
          echo "publishedPackages=$packages" >> $GITHUB_OUTPUT
        else
          echo "published=false" >> $GITHUB_OUTPUT
          echo "publishedPackages=[]" >> $GITHUB_OUTPUT
        fi

    - name: Push git tags
      if: steps.check.outputs.hasChangelogs == 'false' && inputs.publish != '' && steps.publish.outputs.published == 'true'
      shell: bash
      run: git push --follow-tags

    - name: Create GitHub releases
      if: steps.check.outputs.hasChangelogs == 'false' && inputs.publish != '' && steps.publish.outputs.published == 'true'
      shell: bash
      env:
        GH_TOKEN: ${{ inputs.github-token }}
      run: |
        # Get tags that were just pushed
        for tag in $(git tag --points-at HEAD); do
          echo "Creating release for $tag"
          gh release create "$tag" --generate-notes || true
        done