streamtop 1.0.0

HLS/DASH/IPTV stream diagnostics TUI and CLI
Documentation
name: CI

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

env:
  CARGO_TERM_COLOR: always

jobs:
  check:
    name: check (${{ matrix.os }})
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
    steps:
      - uses: actions/checkout@v5

      - name: Install Rust toolchain
        uses: dtolnay/rust-toolchain@stable
        with:
          components: rustfmt, clippy

      - name: Cache cargo
        uses: Swatinem/rust-cache@v2

      - name: cargo fmt --check
        run: cargo fmt --all -- --check

      - name: cargo clippy
        run: cargo clippy --all-targets -- -D warnings

      - name: cargo test
        run: cargo test --all-targets

  audit:
    name: cargo audit
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5

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

      - name: Install cargo-audit
        uses: taiki-e/install-action@v2
        with:
          tool: cargo-audit

      - name: cargo audit
        run: cargo audit

  smoke-live:
    name: live HLS smoke
    runs-on: ubuntu-latest
    needs: check
    steps:
      - uses: actions/checkout@v5

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

      - name: Cache cargo
        uses: Swatinem/rust-cache@v2

      - name: Build binary
        run: cargo build --release

      - name: Smoke test public HLS (10s summary JSON, 3 retries)
        id: smoke
        continue-on-error: true
        run: |
          set -uo pipefail
          mkdir -p artifacts
          URL="https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8"
          code=1
          for attempt in 1 2 3; do
            echo "smoke attempt ${attempt}/3"
            set +e
            ./target/release/streamtop "$URL" --summary --summary-format json --timeout 10 \
              > artifacts/summary.json
            code=$?
            set -e
            if [ "$code" -eq 0 ]; then
              break
            fi
            sleep 5
          done
          cat artifacts/summary.json || true
          ./target/release/streamtop "$URL" --export-har artifacts/smoke.har --timeout 10 --probe-headers || true
          exit $code

      - name: Upload HAR / summary on smoke failure
        if: steps.smoke.outcome == 'failure'
        uses: actions/upload-artifact@v5
        with:
          name: smoke-failure-har
          path: |
            artifacts/smoke.har
            artifacts/summary.json
          if-no-files-found: warn

      - name: Soft-fail live smoke (external CDN)
        if: steps.smoke.outcome == 'failure'
        run: |
          echo "::warning::Live HLS smoke failed after retries (external CDN). Soft-fail: main CI gate remains check+build."

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

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

      - name: Cache cargo
        uses: Swatinem/rust-cache@v2
        with:
          key: ${{ matrix.target }}

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

      - name: Package (Unix)
        if: runner.os != 'Windows'
        run: |
          mkdir -p stage pkg
          cp "target/${{ matrix.target }}/release/streamtop" stage/streamtop
          cp README.md LICENSE CHANGELOG.md stage/
          # Friendly zip (docs + binary only - never pack packaging manifests)
          (cd stage && zip -r "../${{ matrix.artifact }}.zip" streamtop README.md LICENSE CHANGELOG.md)
          # cargo-binstall: streamtop-<triple>.tar.gz with binary at archive root
          cp "target/${{ matrix.target }}/release/streamtop" "pkg/streamtop"
          tar -C pkg -czf "streamtop-${{ matrix.target }}.tar.gz" streamtop

      - name: Package (Windows)
        if: runner.os == 'Windows'
        shell: pwsh
        run: |
          New-Item -ItemType Directory -Force -Path stage, pkg | Out-Null
          Copy-Item "target/${{ matrix.target }}/release/streamtop.exe" stage/
          Copy-Item README.md,LICENSE,CHANGELOG.md stage/
          Compress-Archive -Path stage\* -DestinationPath "${{ matrix.artifact }}.zip" -Force
          # cargo-binstall zip override: streamtop-<triple>.zip
          Copy-Item "target/${{ matrix.target }}/release/streamtop.exe" pkg/
          Compress-Archive -Path pkg\streamtop.exe -DestinationPath "streamtop-${{ matrix.target }}.zip" -Force

      - name: Upload friendly zip
        uses: actions/upload-artifact@v5
        with:
          name: ${{ matrix.artifact }}
          path: ${{ matrix.artifact }}.zip
          if-no-files-found: error

      - name: Upload binstall archive
        uses: actions/upload-artifact@v5
        with:
          name: binstall-${{ matrix.target }}
          path: |
            streamtop-${{ matrix.target }}.tar.gz
            streamtop-${{ matrix.target }}.zip
          if-no-files-found: error

  release:
    name: github-release
    needs: build
    if: startsWith(github.ref, 'refs/tags/v')
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@v5

      - name: Download artifacts
        uses: actions/download-artifact@v5
        with:
          path: artifacts

      - name: Prepare release assets
        env:
          TAG: ${{ github.ref_name }}
        run: |
          set -euo pipefail
          mkdir -p release
          ver="${TAG#v}"
          find artifacts -type f \( \
              -name 'streamtop-linux-*.zip' \
              -o -name 'streamtop-macos-*.zip' \
              -o -name 'streamtop-windows-*.zip' \
            \) | while read -r zip; do
            base=$(basename "$zip" .zip)
            cp "$zip" "release/${base}-${ver}.zip"
          done
          if [ -d artifacts ]; then
            find artifacts -path 'artifacts/binstall-*/*' -type f \( \
                -name 'streamtop-*.tar.gz' -o -name 'streamtop-*.zip' \
              \) -exec cp -t release {} +
          fi
          ls -la release/

      - name: Publish GitHub Release
        uses: softprops/action-gh-release@v2
        with:
          draft: false
          prerelease: false
          generate_release_notes: true
          body: |
            ## streamtop ${{ github.ref_name }}

            Install: `cargo install streamtop` · `cargo binstall streamtop` · see README for Scoop / Winget / AUR / Docker / Homebrew.

            ### Assets
            - Friendly zips: `streamtop-*-<platform>-*.zip` (binary + README/LICENSE/CHANGELOG)
            - cargo-binstall: `streamtop-<rust-target>.tar.gz` (Unix) / `.zip` (Windows)
          files: release/*
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

  crates-io:
    name: crates.io publish
    needs: release
    if: startsWith(github.ref, 'refs/tags/v')
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5

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

      - name: Publish to crates.io
        env:
          CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
        run: |
          set -euo pipefail
          if [ -z "${CARGO_REGISTRY_TOKEN:-}" ]; then
            echo "::warning::CARGO_REGISTRY_TOKEN secret missing; skip crates.io publish"
            exit 0
          fi
          cargo publish --locked