fastsync 0.10.1

A fast, safe one-way directory synchronization tool for local folders and network transfers.
Documentation
name: Release

on:
  workflow_dispatch:

permissions:
  contents: write

env:
  CARGO_TERM_COLOR: always
  BINARY_NAME: fastsync

jobs:
  prepare:
    name: Prepare release metadata
    runs-on: ubuntu-latest
    outputs:
      version: ${{ steps.version.outputs.version }}
      tag: ${{ steps.version.outputs.tag }}
    steps:
      - name: Checkout
        uses: actions/checkout@v6

      - name: Read package version
        id: version
        run: |
          set -euo pipefail

          version="$(awk -F '"' '/^version = / { print $2; exit }' Cargo.toml)"
          if [[ -z "${version}" ]]; then
            echo "Failed to read package version from Cargo.toml" >&2
            exit 1
          fi

          echo "version=${version}" >> "${GITHUB_OUTPUT}"
          echo "tag=v${version}" >> "${GITHUB_OUTPUT}"

  preflight:
    name: Check release permission
    needs: prepare
    runs-on: ubuntu-latest
    permissions:
      contents: write
    env:
      GH_TOKEN: ${{ github.token }}
      CHECK_TAG: fastsync-release-permission-check-${{ github.run_id }}-${{ github.run_attempt }}
    steps:
      - name: Verify GitHub release write access
        run: |
          set -euo pipefail

          release_id=""
          cleanup() {
            if [[ -n "${release_id}" ]]; then
              gh api --method DELETE "repos/${GITHUB_REPOSITORY}/releases/${release_id}" >/dev/null 2>&1 || true
            fi
            gh api --method DELETE "repos/${GITHUB_REPOSITORY}/git/refs/tags/${CHECK_TAG}" >/dev/null 2>&1 || true
          }
          trap cleanup EXIT

          release_id="$(
            gh api --method POST "repos/${GITHUB_REPOSITORY}/releases" \
              -f tag_name="${CHECK_TAG}" \
              -f target_commitish="${GITHUB_SHA}" \
              -f name="FastSync release permission check" \
              -f body="Temporary release created by workflow preflight and deleted immediately." \
              -F draft=true \
              -F prerelease=true \
              --jq '.id'
          )"

          if [[ -z "${release_id}" ]]; then
            echo "Release permission check did not return a release id." >&2
            exit 1
          fi

          echo "GitHub release write permission is available."

  verify:
    name: Verify
    needs: preflight
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v6

      - name: Install Rust
        run: |
          rustup update stable
          rustup default stable
          rustup component add clippy

      - name: Check lockfile
        run: cargo check --all-targets --all-features --locked

      - name: Lint
        run: cargo clippy --all-targets --all-features --locked -- -D warnings

      - name: Test
        run: cargo test --all-features --locked

  build:
    name: Build ${{ matrix.target }}
    needs:
      - prepare
      - verify
    env:
      RELEASE_TAG: ${{ needs.prepare.outputs.tag }}
    strategy:
      fail-fast: false
      matrix:
        include:
          - os: ubuntu-latest
            target: x86_64-unknown-linux-gnu
          - os: ubuntu-24.04-arm
            target: aarch64-unknown-linux-gnu
          - os: macos-latest
            target: aarch64-apple-darwin
          - os: macos-15-intel
            target: x86_64-apple-darwin
          - os: windows-latest
            target: x86_64-pc-windows-msvc
          - os: windows-11-arm
            target: aarch64-pc-windows-msvc
    runs-on: ${{ matrix.os }}
    steps:
      - name: Checkout
        uses: actions/checkout@v6

      - name: Install Rust target
        shell: bash
        run: |
          rustup update stable
          rustup default stable
          rustup target add "${{ matrix.target }}"

      - name: Build release binary
        shell: bash
        run: cargo build --release --locked --target "${{ matrix.target }}"

      - name: Prepare release asset
        shell: bash
        run: |
          set -euo pipefail

          asset_name="${BINARY_NAME}-${RELEASE_TAG}-${{ matrix.target }}"
          source_binary="target/${{ matrix.target }}/release/${BINARY_NAME}"
          if [[ "${{ matrix.target }}" == *windows* ]]; then
            source_binary="${source_binary}.exe"
            asset_name="${asset_name}.exe"
          fi

          mkdir -p dist
          asset="dist/${asset_name}"
          cp "${source_binary}" "${asset}"
          chmod +x "${asset}" || true

          if command -v sha256sum >/dev/null 2>&1; then
            sha256sum "${asset}" > "${asset}.sha256"
          else
            shasum -a 256 "${asset}" > "${asset}.sha256"
          fi

      - name: Upload build artifact
        uses: actions/upload-artifact@v7
        with:
          name: fastsync-${{ matrix.target }}
          path: |
            dist/fastsync-*
            dist/*.sha256
          if-no-files-found: error

  publish:
    name: Publish GitHub Release
    needs:
      - prepare
      - build
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - name: Download release assets
        uses: actions/download-artifact@v7
        with:
          path: dist
          merge-multiple: true

      - name: Publish assets
        uses: softprops/action-gh-release@v3
        with:
          tag_name: ${{ needs.prepare.outputs.tag }}
          name: FastSync ${{ needs.prepare.outputs.tag }}
          target_commitish: ${{ github.sha }}
          files: dist/*
          fail_on_unmatched_files: true
          generate_release_notes: true
          overwrite_files: true
          token: ${{ github.token }}

  winget:
    name: Publish to WinGet
    needs: publish
    runs-on: ubuntu-latest
    steps:
      - uses: vedantmgoyal9/winget-releaser@main
        with:
          identifier: ShouChenICU.FastSync
          installers-regex: 'pc-windows-msvc\.exe$'
          max-versions-to-keep: 7
          fork-user: ShouChenICU
          token: ${{ secrets.WINGET_TOKEN }}