rust-doctor 0.2.0

A unified code health tool for Rust — scan, score, and fix your codebase
Documentation
name: Release

on:
  push:
    tags:
      - 'v*'

# Least-privilege default: jobs only read the repo. Jobs that need more elevate
# explicitly at the job level (release → contents: write for the GitHub Release;
# publish-npm → id-token: write for npm Trusted Publishing + provenance).
permissions:
  contents: read

jobs:
  build:
    name: Build ${{ matrix.target }}
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        include:
          - target: x86_64-unknown-linux-gnu
            os: ubuntu-latest
            archive: tar.gz
          - target: aarch64-unknown-linux-gnu
            os: ubuntu-latest
            archive: tar.gz
            cross: true
          - target: x86_64-apple-darwin
            os: macos-latest
            archive: tar.gz
          - target: aarch64-apple-darwin
            os: macos-latest
            archive: tar.gz
          - target: x86_64-pc-windows-msvc
            os: windows-latest
            archive: zip

    steps:
      - uses: actions/checkout@v4

      - name: Install Rust
        uses: dtolnay/rust-toolchain@stable
        with:
          targets: ${{ matrix.target }}

      - name: Install cross (for cross-compilation)
        if: matrix.cross
        run: cargo install cross --locked

      - name: Build
        run: |
          if [ "${{ matrix.cross }}" = "true" ]; then
            cross build --release --target ${{ matrix.target }}
          else
            cargo build --release --target ${{ matrix.target }}
          fi
        shell: bash

      - name: Package (Unix)
        if: matrix.archive == 'tar.gz'
        run: |
          cd target/${{ matrix.target }}/release
          tar czf ../../../rust-doctor-${{ matrix.target }}.tar.gz rust-doctor
        shell: bash

      - name: Package (Windows)
        if: matrix.archive == 'zip'
        run: |
          cd target/${{ matrix.target }}/release
          7z a ../../../rust-doctor-${{ matrix.target }}.zip rust-doctor.exe
        shell: bash

      - name: Upload artifact
        uses: actions/upload-artifact@v4
        with:
          name: rust-doctor-${{ matrix.target }}
          path: rust-doctor-${{ matrix.target }}.*

  release:
    name: Create Release
    needs: build
    runs-on: ubuntu-latest
    permissions:
      contents: write # create the GitHub Release and upload assets
    steps:
      - uses: actions/checkout@v4

      - name: Download all artifacts
        uses: actions/download-artifact@v4
        with:
          path: artifacts
          merge-multiple: true

      - name: Generate install script
        run: |
          cat > artifacts/install.sh << 'INSTALLER'
          #!/bin/bash
          set -euo pipefail

          REPO="ArthurDEV44/rust-doctor"
          BINARY="rust-doctor"

          # Detect platform
          OS=$(uname -s | tr '[:upper:]' '[:lower:]')
          ARCH=$(uname -m)

          case "${OS}-${ARCH}" in
            linux-x86_64)  TARGET="x86_64-unknown-linux-gnu" ;;
            linux-aarch64) TARGET="aarch64-unknown-linux-gnu" ;;
            darwin-x86_64) TARGET="x86_64-apple-darwin" ;;
            darwin-arm64)  TARGET="aarch64-apple-darwin" ;;
            *) echo "Unsupported platform: ${OS}-${ARCH}"; exit 1 ;;
          esac

          # Get latest release tag
          TAG=$(curl -sL "https://api.github.com/repos/${REPO}/releases/latest" | grep '"tag_name"' | cut -d'"' -f4)
          URL="https://github.com/${REPO}/releases/download/${TAG}/${BINARY}-${TARGET}.tar.gz"

          echo "Installing ${BINARY} ${TAG} for ${TARGET}..."
          curl -sL "${URL}" | tar xz -C /usr/local/bin/
          echo "${BINARY} installed to /usr/local/bin/${BINARY}"
          INSTALLER
          chmod +x artifacts/install.sh

      - name: Generate PowerShell install script
        run: |
          cat > artifacts/install.ps1 << 'INSTALLER'
          $ErrorActionPreference = "Stop"
          $repo = "ArthurDEV44/rust-doctor"
          $binary = "rust-doctor"

          $release = Invoke-RestMethod "https://api.github.com/repos/$repo/releases/latest"
          $tag = $release.tag_name
          $url = "https://github.com/$repo/releases/download/$tag/$binary-x86_64-pc-windows-msvc.zip"

          $tmp = New-TemporaryFile | Rename-Item -NewName { $_.Name + ".zip" } -PassThru
          Invoke-WebRequest $url -OutFile $tmp
          $dest = "$env:USERPROFILE\.cargo\bin"
          Expand-Archive $tmp -DestinationPath $dest -Force
          Remove-Item $tmp
          Write-Host "$binary installed to $dest\$binary.exe"
          INSTALLER

      - name: Create GitHub Release
        uses: softprops/action-gh-release@v2
        with:
          generate_release_notes: true
          files: |
            artifacts/*.tar.gz
            artifacts/*.zip
            artifacts/install.sh
            artifacts/install.ps1

  publish-npm:
    name: Publish npm packages
    needs: release
    runs-on: ubuntu-latest
    permissions:
      contents: read
      # Required for npm Trusted Publishing (OIDC) and provenance attestation.
      # No NPM_TOKEN: GitHub proves this workflow's identity over OIDC and npm
      # mints a short-lived credential for each publish. Set up once per package
      # on npmjs.com (see npm/publish.sh header for the package list).
      id-token: write
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '22'
          registry-url: 'https://registry.npmjs.org'

      - name: Upgrade npm for Trusted Publishing
        # OIDC Trusted Publishing requires npm >= 11.5.1; the bundled npm is older.
        run: npm install -g npm@latest

      - name: Download all artifacts
        uses: actions/download-artifact@v4
        with:
          path: artifacts
          merge-multiple: true

      - name: Extract version from tag
        id: version
        run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"

      - name: Verify tag matches crate version
        run: |
          TAG="${GITHUB_REF_NAME#v}"
          CRATE="$(grep -m1 '^version' Cargo.toml | sed -E 's/.*"(.*)".*/\1/')"
          if [ "$TAG" != "$CRATE" ]; then
            echo "::error::Tag v$TAG does not match crate version $CRATE in Cargo.toml"
            exit 1
          fi
          echo "Tag matches crate version: $CRATE"

      - name: Publish npm packages (Trusted Publishing + provenance)
        run: bash npm/publish.sh "${{ steps.version.outputs.version }}" artifacts

  publish-crate:
    name: Publish to crates.io
    needs: release
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install Rust
        uses: dtolnay/rust-toolchain@stable

      - name: Check if version already published
        id: check
        run: |
          VERSION=$(cargo metadata --no-deps --format-version=1 | jq -r '.packages[0].version')
          if cargo search rust-doctor --limit 1 | grep -q "rust-doctor = \"${VERSION}\""; then
            echo "skip=true" >> "$GITHUB_OUTPUT"
            echo "v${VERSION} already on crates.io — skipping publish"
          else
            echo "skip=false" >> "$GITHUB_OUTPUT"
          fi

      - name: Publish to crates.io
        if: steps.check.outputs.skip != 'true'
        env:
          CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
        run: cargo publish