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
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 }}