msvc-kit 0.2.10

A portable MSVC Build Tools installer and manager for Rust development
name: Release

# This workflow handles:
# 1. Regular commits to main: Only runs release-please to check if a release PR should be created/updated
# 2. release-please PR merge: Creates a release tag and triggers the full release process
# 3. Tag push (v*): Publishes to crates.io, builds release binary, creates GitHub Release, and updates WinGet
on:
  push:
    branches:
      - main
    tags:
      - "v*"
  workflow_dispatch:

permissions:
  contents: write
  pull-requests: write
  issues: write

jobs:
  # Run release-please to create releases and tags
  release-please:
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    outputs:
      release_created: ${{ steps.release.outputs.release_created }}
      tag_name: ${{ steps.release.outputs.tag_name }}
      version: ${{ steps.release.outputs.version }}
    steps:
      - uses: googleapis/release-please-action@v4
        id: release
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          config-file: release-please-config.json
          manifest-file: .release-please-manifest.json

  # Determine if this is a tag push (for publishing)
  check-tag:
    runs-on: ubuntu-latest
    if: startsWith(github.ref, 'refs/tags/v')
    outputs:
      is_tag: "true"
      tag_name: ${{ github.ref_name }}
    steps:
      - name: Tag detected
        run: |

          echo "Tag push detected: ${{ github.ref_name }}"
          echo "This will trigger the full release process"

  # Publish to crates.io
  crates-publish:
    name: Publish to crates.io
    runs-on: windows-latest
    needs: [release-please, check-tag]
    if: |

      always() &&
      ((needs.release-please.result == 'success' && needs.release-please.outputs.release_created == 'true') ||
       (needs.check-tag.result == 'success' && needs.check-tag.outputs.is_tag == 'true'))
    steps:
      - uses: actions/checkout@v6

      - name: Setup vx
        uses: loonghao/vx@main
        with:
          version: '0.8.0'
          github-token: ${{ secrets.GITHUB_TOKEN }}

      - name: Setup tools (vx)
        run: vx setup

      - name: Add Rust components
        run: vx rustup component add clippy rustfmt

      - name: Setup MSVC Developer Command Prompt
        uses: ilammy/msvc-dev-cmd@v1

      - name: Fix MSVC linker (remove Git's link.exe)
        run: Remove-Item 'C:\Program Files\Git\usr\bin\link.exe' -Force -ErrorAction SilentlyContinue
        shell: pwsh

      - name: Cache cargo
        uses: Swatinem/rust-cache@v2

      - name: Publish to crates.io
        env:
          CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
        run: |

          # Check if package version already exists on crates.io
          $packageVersion = (Select-String -Path "Cargo.toml" -Pattern '^version = "([^"]+)"').Matches.Groups[1].Value
          $packageName = "msvc-kit"
          
          # Use crates.io API for reliable version check (cargo search output parsing is fragile)
          try {
            $response = Invoke-RestMethod -Uri "https://crates.io/api/v1/crates/$packageName" -Headers @{ "User-Agent" = "msvc-kit-ci" }
            $existingVersion = $response.crate.newest_version
            Write-Host "Current version on crates.io: $existingVersion"
          } catch {
            Write-Host "Warning: Could not fetch version from crates.io: $_"
            $existingVersion = $null
          }
          
          if ($existingVersion -eq $packageVersion) {
            Write-Host "Package $packageName v$packageVersion already exists on crates.io, skipping publish"
            exit 0
          }
          
          Write-Host "Publishing $packageName v$packageVersion to crates.io..."
          vx just publish-ci


  # Build release binary for Windows x64 (single architecture to avoid duplicate winget entries)
  build-release-binary:
    name: Build Windows x64 binary
    runs-on: windows-latest
    needs: [release-please, check-tag]
    if: |

      always() &&
      ((needs.release-please.result == 'success' && needs.release-please.outputs.release_created == 'true') ||
       (needs.check-tag.result == 'success' && needs.check-tag.outputs.is_tag == 'true'))
    steps:
      - uses: actions/checkout@v6

      - name: Setup vx
        uses: loonghao/vx@main
        with:
          version: '0.8.0'
          github-token: ${{ secrets.GITHUB_TOKEN }}

      - name: Setup tools (vx)
        run: vx setup

      - name: Add Rust components
        run: vx rustup component add clippy rustfmt

      - name: Setup MSVC Developer Command Prompt
        uses: ilammy/msvc-dev-cmd@v1

      - name: Fix MSVC linker (remove Git's link.exe)
        run: Remove-Item 'C:\Program Files\Git\usr\bin\link.exe' -Force -ErrorAction SilentlyContinue
        shell: pwsh

      - name: Cache cargo
        uses: Swatinem/rust-cache@v2
        with:
          shared-key: release-x64

      - name: Build release binary
        run: vx just build-release-locked

      - name: Rename binary
        shell: pwsh
        run: |

          Copy-Item "target/release/msvc-kit.exe" "msvc-kit-x86_64-windows.exe"

      - name: Upload binary artifact
        uses: actions/upload-artifact@v6
        with:
          name: msvc-kit-x86_64-windows
          path: msvc-kit-x86_64-windows.exe
          if-no-files-found: error

  # Create GitHub Release with binary artifact
  github-release:
    name: Create GitHub Release
    runs-on: ubuntu-latest
    needs:
      [
        release-please,
        check-tag,
        crates-publish,
        build-release-binary,
      ]
    if: |

      always() &&
      ((needs.release-please.result == 'success' && needs.release-please.outputs.release_created == 'true') ||
       (needs.check-tag.result == 'success' && needs.check-tag.outputs.is_tag == 'true')) &&
      needs.build-release-binary.result == 'success'
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 0

      - name: Determine tag name
        id: tag
        run: |

          if [ "${{ needs.release-please.outputs.tag_name }}" != "" ]; then
            echo "TAG_NAME=${{ needs.release-please.outputs.tag_name }}" >> $GITHUB_OUTPUT
          else
            echo "TAG_NAME=${{ needs.check-tag.outputs.tag_name }}" >> $GITHUB_OUTPUT
          fi

      - name: Determine version
        id: version
        run: |

          TAG="${{ steps.tag.outputs.TAG_NAME }}"
          echo "VERSION=${TAG#v}" >> $GITHUB_OUTPUT

      - name: Download release binary
        uses: actions/download-artifact@v6
        with:
          name: msvc-kit-x86_64-windows
          path: artifacts/

      - name: Prepare release asset
        run: |

          mv artifacts/msvc-kit-x86_64-windows/msvc-kit-x86_64-windows.exe ./msvc-kit-x86_64-windows.exe

      - name: Generate changelog
        id: changelog
        uses: jaywcjlove/changelog-generator@main
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          filter-author: (loonghao|renovate\[bot\]|dependabot\[bot\]|Renovate Bot)
          filter: ""
          template: |

            ## Bugs
            {{fix}}
            ## Feature
            {{feat}}
            ## Improve
            {{refactor,perf,clean}}
            ## Misc
            {{chore,style,ci,build||🔧 Nothing changed}}
            ## Unknown
            {{__unknown__}}

      - name: Create GitHub Release
        uses: ncipollo/release-action@v1
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          tag: ${{ steps.tag.outputs.TAG_NAME }}
          name: ${{ steps.tag.outputs.TAG_NAME }}
          body: |

            Comparing Changes: ${{ steps.changelog.outputs.compareurl }}

            ${{ steps.changelog.outputs.changelog }}

            ---

            ### Installation

            **Cargo:**
            ```bash
            cargo install msvc-kit
            ```

            **WinGet:**
            ```bash
            winget install loonghao.msvc-kit
            ```

            **Direct Download (Windows x64):**
            - [msvc-kit-x86_64-windows.exe](https://github.com/loonghao/msvc-kit/releases/download/${{ steps.tag.outputs.TAG_NAME }}/msvc-kit-x86_64-windows.exe)
          artifacts: msvc-kit-x86_64-windows.exe

          draft: false
          prerelease: false
          allowUpdates: true
          skipIfReleaseExists: false
          updateOnlyUnreleased: false
          makeLatest: true

  # Update WinGet manifest (only x64 to avoid duplicate installer entries)
  update-winget:
    name: Update WinGet
    runs-on: ubuntu-latest
    needs: [release-please, check-tag, github-release]
    if: |

      always() &&
      needs.github-release.result == 'success'
    steps:
      - uses: actions/checkout@v6

      - name: Determine version and tag
        id: version
        run: |

          if [ "${{ needs.release-please.outputs.version }}" != "" ]; then
            echo "VERSION=${{ needs.release-please.outputs.version }}" >> $GITHUB_OUTPUT
            echo "TAG_NAME=${{ needs.release-please.outputs.tag_name }}" >> $GITHUB_OUTPUT
          else
            echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_OUTPUT
            echo "TAG_NAME=${{ needs.check-tag.outputs.tag_name }}" >> $GITHUB_OUTPUT
          fi

      - name: Wait for release assets to be available
        run: |

          echo "Waiting for release assets to be fully available..."
          sleep 30

      - name: Update WinGet manifest
        uses: vedantmgoyal2009/winget-releaser@v2
        with:
          identifier: loonghao.msvc-kit
          # Match ONLY the single x64 binary to prevent "Duplicate installer entry found" error.
          # Previously, a broader regex could match multiple files causing duplicates.
          installers-regex: '^msvc-kit-x86_64-windows\.exe$'
          version: ${{ steps.version.outputs.VERSION }}
          release-tag: ${{ steps.version.outputs.TAG_NAME }}
          token: ${{ secrets.WINGET_TOKEN }}
        continue-on-error: true