stackql-deploy 2.0.6

Infrastructure-as-code framework for declarative cloud resource management using StackQL
name: Release

on:
  push:
    tags:
      - 'v[0-9]+.[0-9]+.[0-9]+'

env:
  CARGO_TERM_COLOR: always

jobs:

  # Fail fast if the tag version does not match the version in Cargo.toml
  verify:
    name: Verify version
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - name: Check tag matches Cargo.toml version
        run: |
          CARGO_VERSION=$(grep '^version' Cargo.toml | head -1 | sed 's/version = "\(.*\)"/\1/')
          TAG=${GITHUB_REF_NAME#v}
          if [ "$CARGO_VERSION" != "$TAG" ]; then
            echo "Tag ($TAG) does not match Cargo.toml version ($CARGO_VERSION)"
            exit 1
          fi
          echo "Version check passed: $CARGO_VERSION"

  # Fetch contributor list via StackQL and upload as a workflow artifact so all
  # matrix build jobs can embed it into the binary at compile time
  prepare:
    name: Prepare
    needs: verify
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - name: Pull github provider
        uses: stackql/stackql-exec@v2
        with:
          query: REGISTRY PULL github
          is_command: true
      - name: Fetch contributors
        id: get-contributors
        uses: stackql/stackql-exec@v2
        with:
          query_file_path: ci-scripts/get-contributors.iql
          query_output: csv
      - name: Save contributors CSV
        run: echo "${{ steps.get-contributors.outputs.stackql-query-results }}" | tail -n +2 > contributors.csv
      - name: Upload contributors artifact
        uses: actions/upload-artifact@v7
        with:
          name: contributors-csv
          path: contributors.csv

  build:
    name: Build (${{ matrix.target }})
    needs: prepare
    strategy:
      fail-fast: false
      matrix:
        include:
          - target: x86_64-unknown-linux-gnu
            os: ubuntu-latest
            artifact-name: stackql-deploy-linux-x86_64
            archive: tar.gz
            use-cross: false
          - target: aarch64-unknown-linux-gnu
            os: ubuntu-latest
            artifact-name: stackql-deploy-linux-arm64
            archive: tar.gz
            use-cross: true
          - target: x86_64-pc-windows-msvc
            os: windows-latest
            artifact-name: stackql-deploy-windows-x86_64
            archive: zip
            use-cross: false
          - target: aarch64-apple-darwin
            os: macos-latest
            artifact-name: stackql-deploy-macos-arm64
            archive: tar.gz
            use-cross: false
          - target: x86_64-apple-darwin
            os: macos-latest
            artifact-name: stackql-deploy-macos-x86_64
            archive: tar.gz
            use-cross: false
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v6
      - uses: actions/download-artifact@v8
        with:
          name: contributors-csv
      - uses: dtolnay/rust-toolchain@stable
        with:
          targets: ${{ matrix.target }}
      - uses: Swatinem/rust-cache@v2
        with:
          key: ${{ matrix.target }}
      - name: Install cross
        if: matrix.use-cross == true
        run: cargo install cross --git https://github.com/cross-rs/cross
      - name: Set contributors env
        shell: bash
        run: |
          CONTRIBUTORS=$(tr '\n' ',' < contributors.csv | sed 's/,$//')
          echo "CONTRIBUTORS=$CONTRIBUTORS" >> $GITHUB_ENV
      - name: Build (cross)
        if: matrix.use-cross == true
        run: cross build --release --target ${{ matrix.target }}
      - name: Build (native)
        if: matrix.use-cross == false
        run: cargo build --release --target ${{ matrix.target }}
      - name: Strip binary (Linux / macOS native)
        if: matrix.os != 'windows-latest' && matrix.use-cross == false
        run: strip target/${{ matrix.target }}/release/stackql-deploy
      - name: Package (tar.gz)
        if: matrix.archive == 'tar.gz'
        run: |
          tar -czf ${{ matrix.artifact-name }}.tar.gz \
            -C target/${{ matrix.target }}/release stackql-deploy
      - name: Package (zip)
        if: matrix.archive == 'zip'
        shell: pwsh
        run: |
          Compress-Archive `
            -Path target/${{ matrix.target }}/release/stackql-deploy.exe `
            -DestinationPath ${{ matrix.artifact-name }}.zip
      - uses: actions/upload-artifact@v7
        with:
          name: ${{ matrix.artifact-name }}
          path: ${{ matrix.artifact-name }}.*
          if-no-files-found: error

  universal-macos:
    name: Universal macOS binary
    needs: build
    runs-on: macos-latest
    steps:
      - uses: actions/download-artifact@v8
        with:
          name: stackql-deploy-macos-arm64
          path: arm64
      - uses: actions/download-artifact@v8
        with:
          name: stackql-deploy-macos-x86_64
          path: x86_64
      - name: Extract binaries
        run: |
          tar -xzf arm64/stackql-deploy-macos-arm64.tar.gz -C arm64
          tar -xzf x86_64/stackql-deploy-macos-x86_64.tar.gz -C x86_64
      - name: Create universal binary
        run: |
          lipo -create arm64/stackql-deploy x86_64/stackql-deploy \
            -output stackql-deploy
          tar -czf stackql-deploy-macos-universal.tar.gz stackql-deploy
      - uses: actions/upload-artifact@v7
        with:
          name: stackql-deploy-macos-universal
          path: stackql-deploy-macos-universal.tar.gz
          if-no-files-found: error

  # Runtime smoke test on each platform before releasing
  runtime-test:
    name: Runtime Test (${{ matrix.os }})
    needs: [build, universal-macos]
    strategy:
      fail-fast: false
      matrix:
        include:
          - os: ubuntu-latest
            artifact-name: stackql-deploy-linux-x86_64
            archive: tar.gz
          - os: ubuntu-24.04-arm
            artifact-name: stackql-deploy-linux-arm64
            archive: tar.gz
          - os: windows-latest
            artifact-name: stackql-deploy-windows-x86_64
            archive: zip
          - os: macos-latest
            artifact-name: stackql-deploy-macos-arm64
            archive: tar.gz
          - os: macos-26-intel
            artifact-name: stackql-deploy-macos-x86_64
            archive: tar.gz
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/download-artifact@v8
        with:
          name: ${{ matrix.artifact-name }}
      - name: Extract binary (tar.gz)
        if: matrix.archive == 'tar.gz'
        run: tar -xzf ${{ matrix.artifact-name }}.tar.gz
      - name: Extract binary (zip)
        if: matrix.archive == 'zip'
        shell: pwsh
        run: Expand-Archive -Path ${{ matrix.artifact-name }}.zip -DestinationPath .
      - name: Run smoke test
        shell: bash
        run: |
          set -e

          echo "=== Test 1: stackql-deploy info (forces stackql download) ==="
          ./stackql-deploy info

          echo ""
          echo "=== Test 2: stackql-deploy start-server ==="
          ./stackql-deploy start-server

          echo ""
          echo "=== Test 3: stackql-deploy info (verify server running) ==="
          ./stackql-deploy info

          echo ""
          echo "=== Test 4: stackql-deploy stop-server ==="
          ./stackql-deploy stop-server

          echo ""
          echo "=== All smoke tests passed ==="

  release:
    name: Create GitHub Release
    needs: [build, runtime-test, universal-macos]
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@v6
      - name: Download selected build artifacts
        uses: actions/download-artifact@v8
        with:
          path: artifacts/
          pattern: stackql-deploy-@(linux-*|windows-*|macos-universal)
      - name: Collect archives and generate SHA256SUMS
        run: |
          mkdir -p dist
          find artifacts/ -type f \( -name '*.tar.gz' -o -name '*.zip' \) \
            -exec mv {} dist/ \;
          cd dist
          sha256sum * | tee SHA256SUMS
      - name: Build release body
        run: |
          VERSION=${GITHUB_REF_NAME#v}
          echo "## stackql-deploy ${GITHUB_REF_NAME}" > release-body.md
          echo "" >> release-body.md
          awk "/^## $VERSION/{found=1; next} found && /^## /{exit} found{print}" CHANGELOG.md >> release-body.md
          echo "" >> release-body.md
          cat .github/release-footer.md >> release-body.md
      - name: GH Release
        uses: softprops/action-gh-release@v2.6.0
        with:
          name: ${{ github.ref_name }}
          tag_name: ${{ github.ref_name }}
          fail_on_unmatched_files: true
          files: |
            dist/*.tar.gz
            dist/*.zip
            dist/SHA256SUMS
          body_path: release-body.md

  # --no-verify skips the verification build that cargo publish runs by default.
  # That build fails because build.rs writes contributors.csv into the package
  # root (outside OUT_DIR) when the file is absent, which it always is inside
  # the clean tarball sandbox. The binary has already been fully built and
  # verified in the build job above.
  publish:
    name: Publish to crates.io
    needs: release
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: dtolnay/rust-toolchain@stable
      - name: Publish to crates.io
        run: cargo publish --no-verify --token ${{ secrets.CARGO_REGISTRY_TOKEN }}

  # Builds docs with private items visible and deploys to GitHub Pages.
  # docs.rs also picks up the crate automatically from the publish step above,
  # but only renders a minimal page for binary-only crates. GitHub Pages gives
  # the full internal module tree.
  # Published at: https://stackql.github.io/stackql-deploy/stackql_deploy/
  docs:
    name: Publish Docs
    needs: publish
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@v6
      - uses: dtolnay/rust-toolchain@stable
      - name: Build docs
        run: cargo doc --no-deps --document-private-items
      - name: Deploy to GitHub Pages
        uses: peaceiris/actions-gh-pages@v3
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./target/doc