yek 0.15.0

A tool to serialize a repository into chunks of text files
Documentation
name: CI

on:
  push:
    branches: [main]
    tags: ["v*"]
  pull_request:
    branches: [main]

env:
  CARGO_TERM_COLOR: always
  RUST_BACKTRACE: short
  OPENSSL_STATIC: true
  PKG_CONFIG_ALLOW_CROSS: true

permissions:
  contents: write

jobs:
  test:
    name: Test
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
      fail-fast: false
    steps:
      - uses: actions/checkout@v4
      - uses: actions-rust-lang/setup-rust-toolchain@v1
        with:
          components: rustfmt,clippy

      - name: Install OpenSSL (Ubuntu)
        if: runner.os == 'Linux'
        run: |
          sudo apt-get update
          sudo apt-get install -y pkg-config libssl-dev

      - name: Install OpenSSL (macOS)
        if: runner.os == 'macOS'
        run: |
          brew install openssl@3
          echo "OPENSSL_DIR=$(brew --prefix openssl@3)" >> $GITHUB_ENV
          echo "PKG_CONFIG_PATH=$(brew --prefix openssl@3)/lib/pkgconfig" >> $GITHUB_ENV

      - name: Install OpenSSL (Windows)
        if: runner.os == 'Windows'
        run: |
          choco install openssl --params='/InstallationPath:C:\OpenSSL\'
          echo "OPENSSL_DIR=C:\OpenSSL" >> $env:GITHUB_ENV
          echo "PKG_CONFIG_PATH=C:\OpenSSL\lib\pkgconfig" >> $env:GITHUB_ENV

      - name: Run tests
        run: cargo test --verbose

  lint:
    name: Lint
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions-rust-lang/setup-rust-toolchain@v1
        with:
          components: rustfmt,clippy
      - name: Clippy
        run: cargo clippy -- -D warnings
      - name: Format check
        run: cargo fmt --check

  build:
    name: Build ${{ matrix.target }}
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        include:
          # Linux builds using cross
          - os: ubuntu-latest
            target: x86_64-unknown-linux-gnu
            artifact_name: yek
            asset_name: yek-x86_64-unknown-linux-gnu.tar.gz
          - os: ubuntu-latest
            target: aarch64-unknown-linux-gnu
            artifact_name: yek
            asset_name: yek-aarch64-unknown-linux-gnu.tar.gz
          - os: ubuntu-latest
            target: x86_64-unknown-linux-musl
            artifact_name: yek
            asset_name: yek-x86_64-unknown-linux-musl.tar.gz
          - os: ubuntu-latest
            target: aarch64-unknown-linux-musl
            artifact_name: yek
            asset_name: yek-aarch64-unknown-linux-musl.tar.gz

          # Native macOS builds
          - os: macos-latest
            target: x86_64-apple-darwin
            artifact_name: yek
            asset_name: yek-x86_64-apple-darwin.tar.gz
          - os: macos-latest
            target: aarch64-apple-darwin
            artifact_name: yek
            asset_name: yek-aarch64-apple-darwin.tar.gz

          # Native Windows builds
          - os: windows-latest
            target: x86_64-pc-windows-msvc
            artifact_name: yek.exe
            asset_name: yek-x86_64-pc-windows-msvc.zip
          - os: windows-latest
            target: aarch64-pc-windows-msvc
            artifact_name: yek.exe
            asset_name: yek-aarch64-pc-windows-msvc.zip

    steps:
      - uses: actions/checkout@v4

      - uses: ./.github/actions/build
        id: build
        with:
          upload_artifacts: "false"

      - name: Package binary (Unix)
        if: runner.os != 'Windows'
        shell: bash
        run: |
          mkdir -p release-artifacts
          staging="yek-${{ matrix.target }}"
          mkdir -p "$staging"
          cp "${{ steps.build.outputs.binary_path }}" "$staging/"
          tar czf "release-artifacts/${{ matrix.asset_name }}" "$staging"
          # Verify the artifact exists and has size > 0
          if [ ! -f "release-artifacts/${{ matrix.asset_name }}" ]; then
            echo "::error::Packaged artifact not found: release-artifacts/${{ matrix.asset_name }}"
            exit 1
          fi
          if [ ! -s "release-artifacts/${{ matrix.asset_name }}" ]; then
            echo "::error::Packaged artifact is empty: release-artifacts/${{ matrix.asset_name }}"
            exit 1
          fi
          echo "Successfully packaged ${{ matrix.asset_name }}"
          ls -lh "release-artifacts/${{ matrix.asset_name }}"

      - name: Package binary (Windows)
        if: runner.os == 'Windows'
        shell: pwsh
        run: |
          New-Item -ItemType Directory -Force -Path release-artifacts
          $staging = "yek-${{ matrix.target }}"
          New-Item -ItemType Directory -Force -Path $staging
          Copy-Item "${{ steps.build.outputs.binary_path }}" -Destination $staging
          7z a "release-artifacts\${{ matrix.asset_name }}" "$staging"
          # Verify the artifact exists and has size > 0
          if (-not (Test-Path "release-artifacts\${{ matrix.asset_name }}")) {
            Write-Error "Packaged artifact not found: release-artifacts\${{ matrix.asset_name }}"
            exit 1
          }
          $fileInfo = Get-Item "release-artifacts\${{ matrix.asset_name }}"
          if ($fileInfo.Length -eq 0) {
            Write-Error "Packaged artifact is empty: release-artifacts\${{ matrix.asset_name }}"
            exit 1
          }
          Write-Host "Successfully packaged ${{ matrix.asset_name }}"
          Get-Item "release-artifacts\${{ matrix.asset_name }}" | Select-Object Length,LastWriteTime

      - name: Upload artifact
        uses: actions/upload-artifact@v4
        with:
          name: build-${{ matrix.target }}-${{ matrix.asset_name }}
          path: release-artifacts/${{ matrix.asset_name }}
          if-no-files-found: error

  release:
    name: Release
    needs: [test, lint, build]
    runs-on: ubuntu-latest
    if: startsWith(github.ref, 'refs/tags/v')
    steps:
      - uses: actions/checkout@v4

      # Download all artifacts to a single directory
      - uses: actions/download-artifact@v4
        with:
          path: dist

      - name: Prepare release files
        shell: bash
        run: |
          echo "Release artifacts:"
          # Move all artifacts to a flat directory structure and rename them
          mkdir -p dist/flat
          for file in dist/build-*/yek-*; do
            # Extract just the final part of the filename (yek-*.tar.gz or yek-*.zip)
            filename=$(basename "$file")
            cp "$file" "dist/flat/$filename"
          done

          # List what we found
          echo "Found artifacts:"
          ls -lh dist/flat/

          # Verify we have all expected artifacts
          expected_count=8  # Total number of builds in matrix
          actual_count=$(find dist/flat -type f -name "yek-*" | wc -l)

          if [[ "$actual_count" -ne "$expected_count" ]]; then
            echo "::error::Expected $expected_count artifacts but found $actual_count"
            echo "Contents of dist directory:"
            find dist -type f
            exit 1
          fi

          echo "Found all $expected_count expected artifacts"

      - name: Generate release notes
        uses: orhun/git-cliff-action@v2
        id: git-cliff
        with:
          config: cliff.toml
          args: --current --strip header
        env:
          OUTPUT: CHANGES.md

      - name: Create Release
        uses: softprops/action-gh-release@v2
        with:
          files: dist/flat/yek-*
          body_path: CHANGES.md
          tag_name: ${{ github.ref_name }}

  publish:
    name: Publish
    needs: [test, lint, build]
    runs-on: ubuntu-latest
    if: startsWith(github.ref, 'refs/tags/v')
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@master
        with:
          toolchain: "1.84.0"
      - run: |
          # ensure version does not already exist
          if cargo search --color=never yek | grep -q ${{ github.ref_name }}; then
            echo "yek@${{ github.ref_name }} already published"
            exit 0
          fi
          cargo publish --token ${{ secrets.CARGO_REGISTRY_TOKEN }}
        env:
          CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}