flux-platform 1.0.1

A local-first, AI-native developer automation platform: build, test, package, and deploy from a single .flux file, and make your repository legible to AI agents.
name: Release

# Fires when a version tag is pushed. Builds cross-platform binaries in a matrix,
# then a single `release` job attaches them all to the GitHub Release for that
# tag. Splitting build from publish avoids the race where several matrix jobs
# each try to *create* the same release at once (which left earlier runs with
# only one asset attached).
on:
  push:
    tags: ["v*"]
  workflow_dispatch:

permissions:
  contents: write

jobs:
  build:
    name: ${{ matrix.target }}
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        include:
          - target: x86_64-unknown-linux-gnu
            os: ubuntu-latest
          - target: aarch64-unknown-linux-gnu
            os: ubuntu-latest
          - target: x86_64-apple-darwin
            os: macos-latest
          - target: aarch64-apple-darwin
            os: macos-latest
          - target: x86_64-pc-windows-msvc
            os: windows-latest
    steps:
      - uses: actions/checkout@v7

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

      - name: Linux cross-linker (aarch64)
        if: matrix.target == 'aarch64-unknown-linux-gnu'
        run: |
          sudo apt-get update
          sudo apt-get install -y gcc-aarch64-linux-gnu
          echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc" >> "$GITHUB_ENV"

      - name: Build
        run: cargo build --release --target ${{ matrix.target }}

      - name: Package (Unix)
        if: runner.os != 'Windows'
        shell: bash
        run: |
          tar -czf "flux-${{ matrix.target }}.tar.gz" -C "target/${{ matrix.target }}/release" flux

      - name: Package (Windows)
        if: runner.os == 'Windows'
        shell: pwsh
        run: |
          Compress-Archive -Path "target/${{ matrix.target }}/release/flux.exe" -DestinationPath "flux-${{ matrix.target }}.zip"

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

  release:
    name: Publish release
    needs: build
    if: startsWith(github.ref, 'refs/tags/')
    runs-on: ubuntu-latest
    steps:
      - name: Download all build artifacts
        uses: actions/download-artifact@v8
        with:
          path: dist

      - name: Collect binaries
        shell: bash
        run: |
          mkdir -p out
          find dist -type f \( -name '*.tar.gz' -o -name '*.zip' \) -exec cp {} out/ \;
          ls -l out

      - name: Publish to release
        uses: softprops/action-gh-release@v3
        with:
          files: out/*
          generate_release_notes: true
          fail_on_unmatched_files: true

  # Publish a container image to GHCR, so Flux appears under the repo's Packages
  # sidebar alongside the crate and the tap. Uses the built-in GITHUB_TOKEN — no
  # extra secret — and the image source label ties it back to this repository.
  #
  # amd64 only: the Dockerfile builds Flux from source with cargo, and an
  # emulated arm64 Rust build costs far more CI time than a second architecture
  # is worth for a developer CLI. Native binaries for arm64 are on the release.
  publish-image:
    name: Publish image to GHCR
    needs: build
    if: startsWith(github.ref, 'refs/tags/')
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v7

      - uses: docker/setup-buildx-action@v4

      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - id: meta
        uses: docker/metadata-action@v5
        with:
          images: ghcr.io/${{ github.repository }}
          tags: |
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=raw,value=latest

      - uses: docker/build-push-action@v7
        with:
          context: .
          platforms: linux/amd64
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

  # Publish the crate to crates.io as `flux-platform`. This is a no-op unless the
  # maintainer has set the CRATES_IO_TOKEN secret, so forks and unconfigured repos
  # aren't affected. Runs only after the binaries built successfully.
  publish-crate:
    name: Publish to crates.io
    needs: build
    # Also dispatchable, so the publish can be run once the token secret exists
    # without re-pushing a tag.
    if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: dtolnay/rust-toolchain@stable
      - name: cargo publish (when token configured)
        env:
          CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_IO_TOKEN }}
        run: |
          if [ -z "$CARGO_REGISTRY_TOKEN" ]; then
            echo "CRATES_IO_TOKEN not set — skipping crates.io publish."
            exit 0
          fi
          cargo publish