Skip to content

release: finalize v0.1.11 and refine release download badges #165

release: finalize v0.1.11 and refine release download badges

release: finalize v0.1.11 and refine release download badges #165

Workflow file for this run

name: Build & Release
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: write
packages: write
jobs:
# โ”€โ”€ ๅˆคๆ–ญๆ˜ฏๅฆ้œ€่ฆๆž„ๅปบ / ๅ‘ๅธƒ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
check:
name: Check commit message
runs-on: ubuntu-latest
outputs:
should_build: ${{ steps.flags.outputs.should_build }}
should_release: ${{ steps.flags.outputs.should_release }}
should_publish: ${{ steps.flags.outputs.should_publish }}
should_publish_pypi: ${{ steps.flags.outputs.should_publish_pypi }}
should_publish_crates: ${{ steps.flags.outputs.should_publish_crates }}
should_benchmark: ${{ steps.flags.outputs.should_benchmark }}
version: ${{ steps.flags.outputs.version }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Parse commit message
id: flags
env:
COMMIT_MSG: ${{ github.event.head_commit.message }}
run: |
MSG="$COMMIT_MSG"
# ไปŽ Cargo.toml ๆๅ–็‰ˆๆœฌๅท
VERSION="v$(grep '^version' rust/Cargo.toml | head -1 | sed 's/.*"\(.*\)".*/\1/')"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "๐Ÿ“ฆ Version: $VERSION"
# PR ๅง‹็ปˆๆž„ๅปบ
if [ "${{ github.event_name }}" = "pull_request" ]; then
echo "should_build=true" >> "$GITHUB_OUTPUT"
echo "should_release=false" >> "$GITHUB_OUTPUT"
echo "should_publish=false" >> "$GITHUB_OUTPUT"
echo "should_benchmark=false" >> "$GITHUB_OUTPUT"
exit 0
fi
# ๅˆๅง‹ๅŒ–ๆ ‡ๅฟ—
BUILD=false
RELEASE=false
PUBLISH=false
PUBLISH_PYPI=false
PUBLISH_CRATES=false
BENCHMARK=false
# "build publish" = ๆž„ๅปบ + Release + ๅ‘ๅธƒๅˆฐๅŒ…็ฎก็†ๅ™จ (ๅ…จๅฅ—)
if echo "$MSG" | grep -qi "build publish"; then
BUILD=true
RELEASE=true
PUBLISH=true
# "build release" = ๆž„ๅปบ + GitHub Release
elif echo "$MSG" | grep -qi "build release"; then
BUILD=true
RELEASE=true
# "build action" = ไป…ๆž„ๅปบ
elif echo "$MSG" | grep -qi "build action"; then
BUILD=true
fi
# "publish from release" = ไปŽๅทฒๆœ‰ Release ๅ‘ๅธƒๅˆฐๅŒ…็ฎก็†ๅ™จ (ไธๆž„ๅปบ)
if echo "$MSG" | grep -qi "publish from release"; then
PUBLISH=true
fi
# "pypi publish" / "publish pypi" = ๅ‘ๅธƒๅˆฐ PyPI
if echo "$MSG" | grep -qiE "pypi publish|publish pypi"; then
PUBLISH_PYPI=true
fi
# "crates publish" / "publish crates" = ๅ‘ๅธƒๅˆฐ crates.io
if echo "$MSG" | grep -qiE "crates publish|publish crates"; then
PUBLISH_CRATES=true
fi
# "run benchmark" = ่ฟ่กŒๅŸบๅ‡†ๆต‹่ฏ•
if echo "$MSG" | grep -qi "run benchmark"; then
BENCHMARK=true
fi
echo "should_build=$BUILD" >> "$GITHUB_OUTPUT"
echo "should_release=$RELEASE" >> "$GITHUB_OUTPUT"
echo "should_publish=$PUBLISH" >> "$GITHUB_OUTPUT"
echo "should_publish_pypi=$PUBLISH_PYPI" >> "$GITHUB_OUTPUT"
echo "should_publish_crates=$PUBLISH_CRATES" >> "$GITHUB_OUTPUT"
echo "should_benchmark=$BENCHMARK" >> "$GITHUB_OUTPUT"
echo "๐Ÿ“‹ Flags: build=$BUILD release=$RELEASE publish=$PUBLISH publish_pypi=$PUBLISH_PYPI publish_crates=$PUBLISH_CRATES benchmark=$BENCHMARK"
# โ”€โ”€ ๅŒๆญฅไปฃ็ ๅˆฐ Gitee โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# ๆฏๆฌก push ้ƒฝๅŒๆญฅ๏ผŒไธŽ check ๅนถ่กŒ่ฟ่กŒ
sync-gitee-code:
name: ใ€Gitee็ ไบ‘ใ€‘Sync Code Commit
runs-on: ubuntu-latest
if: github.event_name == 'push'
steps:
- name: Mirror to Gitee
uses: Yikun/hub-mirror-action@v1.5
with:
src: github/VincentZyuApps
dst: gitee/vincent-zyu
dst_key: ${{ secrets.GITEE_PRIVATE_KEY }}
dst_token: ${{ secrets.GITEE_TOKEN }}
static_list: "winload"
force_update: true
debug: true
# โ”€โ”€ Benchmark โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
benchmark:
name: Run Benchmark
needs: check
if: needs.check.outputs.should_benchmark == 'true'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install dependencies
run: sudo apt-get update && sudo apt-get install -y time
- name: Run benchmark
run: |
chmod +x benchmark/benchmark.sh
./benchmark/benchmark.sh
- name: Commit and push benchmark results
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git add docs/benchmark/benchmark.svg
git commit -m "docs: update benchmark results [skip ci]" || echo "No changes to commit"
git push origin main
# โ”€โ”€ ๅคšๅนณๅฐๆž„ๅปบ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
build:
name: Build ${{ matrix.name }}
needs: check
if: needs.check.outputs.should_build == 'true'
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
# โ”€โ”€ Windows x86_64 (MSVC, with Npcap) โ”€โ”€
- target: x86_64-pc-windows-msvc
os: windows-latest
name: windows-x86_64-msvc
binary: winload.exe
asset: winload-windows-x86_64-msvc-npcap.exe
# โ”€โ”€ Windows x86_64 (MSVC, without Npcap) โ”€โ”€
- target: x86_64-pc-windows-msvc
os: windows-latest
name: windows-x86_64-msvc-no-npcap
binary: winload.exe
asset: winload-windows-x86_64-msvc-no-npcap.exe
no_default_features: true
# โ”€โ”€ Linux x64 (musl ้™ๆ€้“พๆŽฅ, ๅ…ผๅฎนๆ‰€ๆœ‰ Linux) โ”€โ”€
- target: x86_64-unknown-linux-musl
os: ubuntu-latest
name: linux-x86_64
binary: winload
asset: winload-linux-x86_64
cross_packages: musl-tools
# โ”€โ”€ Linux i686 (musl ้™ๆ€้“พๆŽฅ, ๅ…ผๅฎนๆ‰€ๆœ‰ Linux) โ”€โ”€
- target: i686-unknown-linux-musl
os: ubuntu-latest
name: linux-i686
binary: winload
asset: winload-linux-i686
cross_packages: musl-tools
# โ”€โ”€ Windows ARM64 (MSVC, with Npcap) โ”€โ”€
- target: aarch64-pc-windows-msvc
os: windows-latest
name: windows-aarch64-msvc
binary: winload.exe
asset: winload-windows-aarch64-msvc-npcap.exe
# โ”€โ”€ Windows ARM64 (MSVC, without Npcap) โ”€โ”€
- target: aarch64-pc-windows-msvc
os: windows-latest
name: windows-aarch64-no-npcap
binary: winload.exe
asset: winload-windows-aarch64-msvc-no-npcap.exe
no_default_features: true
# โ”€โ”€ Linux ARM64 (ubuntu-22.04 ้™ไฝŽ GLIBC ่ฆๆฑ‚) โ”€โ”€
- target: aarch64-unknown-linux-gnu
os: ubuntu-22.04
name: linux-aarch64
binary: winload
asset: winload-linux-aarch64
cross_linker: aarch64-linux-gnu-gcc
cross_packages: gcc-aarch64-linux-gnu
# โ”€โ”€ macOS x64 (build on Apple Silicon runner) โ”€โ”€
- target: x86_64-apple-darwin
os: macos-latest
name: macos-x86_64
binary: winload
asset: winload-macos-x86_64
# โ”€โ”€ macOS ARM64 (Apple Silicon runner) โ”€โ”€
- target: aarch64-apple-darwin
os: macos-latest
name: macos-aarch64
binary: winload
asset: winload-macos-aarch64
# โ”€โ”€ Android aarch64 (Termux ARM64) โ”€โ”€
- target: aarch64-linux-android
os: ubuntu-latest
name: android-aarch64
binary: winload
asset: winload-android-aarch64
# โ”€โ”€ Android x86_64 (Termux x86_64, ๆจกๆ‹Ÿๅ™จ / Chromebook) โ”€โ”€
- target: x86_64-linux-android
os: ubuntu-latest
name: android-x86_64
binary: winload
asset: winload-android-x86_64
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Cache cargo
uses: Swatinem/rust-cache@v2
with:
workspaces: rust -> target
cache-on-failure: true
key: ${{ matrix.target }}
- name: Install platform toolchain
if: matrix.cross_packages
run: |
sudo apt-get update
sudo apt-get install -y ${{ matrix.cross_packages }}
# i686 musl: download pre-built cross-compiler for static linking
# (no GLIBC dependency โ€” runs on ANY Linux, including old distros)
- name: Setup i686 musl cross-toolchain
if: matrix.target == 'i686-unknown-linux-musl'
run: |
# Try multiple musl mirrors until one works
MUSL_MIRRORS=(
"https://github.com/VincentZyuApps/winload/releases/download/i686-linux-musl-cross.tgz/i686-linux-musl-cross.tgz"
"https://more.musl.cc/11/x86_64-linux-musl/i686-linux-musl-cross.tgz"
)
USE_GLIBC_FALLBACK=false
for mirror in "${MUSL_MIRRORS[@]}"; do
echo "๐Ÿ“ฅ Trying mirror: $mirror"
if curl -fsSL --retry 2 --retry-delay 5 --connect-timeout 20 "$mirror" -o /tmp/musl-cross.tgz 2>&1; then
echo "โœ… Downloaded from $mirror"
tar xzf /tmp/musl-cross.tgz -C /tmp
GCC_PATH=$(find /tmp/i686-linux-musl-cross -name 'i686-linux-musl-gcc' -type f 2>/dev/null | head -1)
if [ -n "$GCC_PATH" ]; then
echo "CARGO_TARGET_I686_UNKNOWN_LINUX_MUSL_LINKER=$GCC_PATH" >> $GITHUB_ENV
echo "โœ… Using musl: $GCC_PATH"
exit 0
fi
fi
echo "โŒ Failed, trying next mirror..."
done
# All mirrors failed - skip this build instead of using incompatible glibc
echo "โš ๏ธ All musl mirrors failed, skipping i686 Linux build..."
echo "I686_MUSL_SKIP=true" >> $GITHUB_ENV
# Android: ้…็ฝฎ NDK ไบคๅ‰็ผ–่ฏ‘ๅทฅๅ…ท้“พ (Termux ๆœ€ไฝŽ API 24 = Android 7.0)
- name: Setup Android NDK
if: contains(matrix.target, 'android')
shell: bash
run: |
NDK_HOME="${ANDROID_NDK_LATEST_HOME:-$ANDROID_NDK_HOME}"
echo "๐Ÿ“ฆ Using NDK: $NDK_HOME"
TOOLCHAIN="$NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin"
ls "$TOOLCHAIN" | head -20
if [[ "${{ matrix.target }}" == "aarch64-linux-android" ]]; then
echo "CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER=${TOOLCHAIN}/aarch64-linux-android24-clang" >> "$GITHUB_ENV"
echo "CC_aarch64_linux_android=${TOOLCHAIN}/aarch64-linux-android24-clang" >> "$GITHUB_ENV"
echo "AR_aarch64_linux_android=${TOOLCHAIN}/llvm-ar" >> "$GITHUB_ENV"
elif [[ "${{ matrix.target }}" == "x86_64-linux-android" ]]; then
echo "CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER=${TOOLCHAIN}/x86_64-linux-android24-clang" >> "$GITHUB_ENV"
echo "CC_x86_64_linux_android=${TOOLCHAIN}/x86_64-linux-android24-clang" >> "$GITHUB_ENV"
echo "AR_x86_64_linux_android=${TOOLCHAIN}/llvm-ar" >> "$GITHUB_ENV"
fi
echo "โœ… Android NDK configured for ${{ matrix.target }}"
# Windows: ไธ‹่ฝฝ Npcap SDK๏ผŒpcap crate ้“พๆŽฅ wpcap.lib ้œ€่ฆ
# ๅชๅœจ MSVC + default-features (npcap) ๆž„ๅปบๆ—ถๅฎ‰่ฃ… (x86_64, aarch64)
# MinGW ๅ’Œ --no-default-features (no-npcap) ๆž„ๅปบ่ทณ่ฟ‡
- name: Install Npcap SDK
if: runner.os == 'Windows' && !matrix.no_default_features
shell: pwsh
run: |
$sdkUrl = "https://npcap.com/dist/npcap-sdk-1.13.zip"
$zipPath = "$env:RUNNER_TEMP\npcap-sdk.zip"
$extractPath = "$env:RUNNER_TEMP\npcap-sdk"
Write-Host "๐Ÿ“ฅ Downloading Npcap SDK..."
Invoke-WebRequest -Uri $sdkUrl -OutFile $zipPath
Write-Host "๐Ÿ“ฆ Extracting Npcap SDK..."
Expand-Archive -Path $zipPath -DestinationPath $extractPath -Force
# ๆ นๆฎ target ้€‰ๆ‹ฉๆญฃ็กฎ็š„ lib ็›ฎๅฝ•
# Npcap SDK 1.13: x64 ๅœจ Lib/x64/, ARM64 ๅœจ Lib/ARM64/
$target = "${{ matrix.target }}"
if ($target -like "*aarch64*") {
$libDir = "$extractPath\Lib\ARM64"
} else {
$libDir = "$extractPath\Lib\x64"
}
Write-Host "๐Ÿ“‚ Npcap SDK lib dir: $libDir"
Get-ChildItem $libDir
echo "LIB=$libDir;$env:LIB" >> $env:GITHUB_ENV
Write-Host "โœ… Npcap SDK ready"
- name: Install Linux packaging tools
if: runner.os == 'Linux' && !contains(matrix.target, 'android')
run: |
cargo install cargo-deb cargo-generate-rpm
- name: Build release binary
if: matrix.target != 'i686-unknown-linux-musl' || !env.I686_MUSL_SKIP
shell: bash
working-directory: rust
env:
# ไบคๅ‰็ผ–่ฏ‘ๆ—ถๆŒ‡ๅฎš้“พๆŽฅๅ™จ (ไป… aarch64-linux-gnu ้œ€่ฆ)
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: ${{ matrix.cross_linker || '' }}
run: |
cargo build --release --target ${{ matrix.target }} \
${{ matrix.no_default_features && '--no-default-features' || '' }}
# ้ชŒ่ฏ Linux musl ไบง็‰ฉๆ˜ฏๅฆ็œŸ็š„ๆ˜ฏ้™ๆ€้“พๆŽฅ
if [[ "${{ matrix.target }}" == *musl* ]]; then
echo "๐Ÿ” Verifying static linking..."
file target/${{ matrix.target }}/release/${{ matrix.binary }}
ldd target/${{ matrix.target }}/release/${{ matrix.binary }} 2>&1 || echo "โœ… Statically linked (no dynamic dependencies)"
fi
# Linux targets: ้ขๅค–ๆž„ๅปบ DEB + RPM ๅŒ… (ๆŽ’้™ค Android, ๆŽ’้™ค i686, ๆŽ’้™ค musl ไธ‹่ฝฝๅคฑ่ดฅ็š„ๆƒ…ๅ†ต)
- name: Build DEB & RPM packages
if: contains(matrix.target, 'linux') && !contains(matrix.target, 'android') && !contains(matrix.target, 'i686') && (matrix.target != 'i686-unknown-linux-musl' || !env.I686_MUSL_SKIP)
shell: bash
working-directory: rust
run: |
VERSION="${{ needs.check.outputs.version }}"
echo "๐Ÿ“ฆ Building DEB package for ${{ matrix.target }}..."
cargo deb --target ${{ matrix.target }} --no-build
# โ”€โ”€ RPM ๆž„ๅปบ โ”€โ”€
# RPM ่ง„่Œƒไธๅ…่ฎธ็‰ˆๆœฌๅทๅซ่ฟžๅญ—็ฌฆ '-'๏ผŒๅชๅ…่ฎธๅญ—ๆฏใ€ๆ•ฐๅญ—ใ€'.', '_', '+', '~' ็ญ‰ๅญ—็ฌฆใ€‚
# Cargo/SemVer ้ข„ๅ‘ๅธƒ็‰ˆๆœฌ็”จ '-'๏ผˆๅฆ‚ 0.1.8-rc.25๏ผ‰๏ผŒๅฟ…้กปๆ›ฟๆขไธบ '~'๏ผˆๅฆ‚ 0.1.8~rc.25๏ผ‰ใ€‚
# '~' ๅœจ RPM ไธญ่กจ็คบ"ๆ—ฉไบŽ"ๅŒๅๆญฃๅผ็‰ˆๆœฌ๏ผŒ่ฏญไน‰ไธŽ้ข„ๅ‘ๅธƒไธ€่‡ดใ€‚
# DEB ่‡ชๅŠจๅฐ† '-' ่ฝฌไธบ '~'๏ผˆcargo-deb ๅ†…็ฝฎๅค„็†๏ผ‰๏ผŒRPM ้œ€่ฆๆ‰‹ๅŠจ่ฝฌๆขใ€‚
# cargo generate-rpm ็š„ --set-metadata ๆ— ๆณ•ๅœจ็‰ˆๆœฌๆ ก้ชŒๅ‰่ฆ†็›– version๏ผŒ
# ๅ› ๆญค้œ€่ฆไธดๆ—ถ sed ๆ›ฟๆข Cargo.toml ไธญ็š„็‰ˆๆœฌๅท๏ผŒๆž„ๅปบๅฎŒๆˆๅŽ git restore ๆขๅคใ€‚
# โš ๏ธ ็”จ tr ๆ›ฟไปฃ bash ๅ‚ๆ•ฐๅฑ•ๅผ€ ${var//-/~}๏ผŒๅ› ไธบ bash ๅœจ CI ็Žฏๅขƒไธ‹
# ไผšๅฐ† '~' ๅฑ•ๅผ€ไธบ $HOME๏ผˆๅฆ‚ /home/runner๏ผ‰๏ผŒๆฑกๆŸ“็‰ˆๆœฌๅทใ€‚
RPM_VERSION="${VERSION#v}"
RPM_VERSION=$(echo "$RPM_VERSION" | tr '-' '~')
echo "๐Ÿ” RPM version (before sed): ${RPM_VERSION}"
echo "๐Ÿ” Cargo.toml version (before): $(grep '^version' Cargo.toml)"
sed -i "s|^version = .*|version = \"${RPM_VERSION}\"|" Cargo.toml
echo "๐Ÿ” Cargo.toml version (after ): $(grep '^version' Cargo.toml)"
echo "๐Ÿ“ฆ Building RPM package for ${{ matrix.target }}..."
cargo generate-rpm --target ${{ matrix.target }}
git restore Cargo.toml
echo "โœ… Packages built (original names):"
find target -name '*.deb' -o -name '*.rpm' | head -20
# ้‡ๅ‘ฝๅไธบ็ปŸไธ€ๆ ผๅผ: winload-linux-<arch>-<version>.deb / .rpm
mkdir -p renamed
for f in $(find target -name '*.deb'); do
cp "$f" "renamed/winload-${{ matrix.name }}-${VERSION}.deb"
done
for f in $(find target -name '*.rpm'); do
cp "$f" "renamed/winload-${{ matrix.name }}-${VERSION}.rpm"
done
echo "๐Ÿ“ฆ Renamed packages:"
ls -lh renamed/
- name: Upload DEB artifact
if: contains(matrix.target, 'linux') && !contains(matrix.target, 'android') && !contains(matrix.target, 'i686') && (matrix.target != 'i686-unknown-linux-musl' || !env.I686_MUSL_SKIP)
uses: actions/upload-artifact@v4
with:
name: winload-${{ matrix.name }}-${{ needs.check.outputs.version }}.deb
path: rust/renamed/*.deb
- name: Upload RPM artifact
if: contains(matrix.target, 'linux') && !contains(matrix.target, 'android') && !contains(matrix.target, 'i686') && (matrix.target != 'i686-unknown-linux-musl' || !env.I686_MUSL_SKIP)
uses: actions/upload-artifact@v4
with:
name: winload-${{ matrix.name }}-${{ needs.check.outputs.version }}.rpm
path: rust/renamed/*.rpm
- name: Prepare artifact
shell: bash
run: |
VERSION="${{ needs.check.outputs.version }}"
ASSET_BASE="${{ matrix.asset }}"
# ๅœจๆ‰ฉๅฑ•ๅๅ‰ๆ’ๅ…ฅ็‰ˆๆœฌๅท
# winload-linux-x86_64 -> winload-linux-x86_64-v0.1.0
# winload-windows-x86_64-msvc-npcap.exe -> winload-windows-x86_64-msvc-npcap-v0.1.0.exe
if [[ "$ASSET_BASE" == *.* ]]; then
# ๆœ‰ๆ‰ฉๅฑ•ๅ
BASE_NAME="${ASSET_BASE%.*}"
EXTENSION="${ASSET_BASE##*.}"
ASSET_WITH_VERSION="${BASE_NAME}-${VERSION}.${EXTENSION}"
else
# ๆ— ๆ‰ฉๅฑ•ๅ
ASSET_WITH_VERSION="${ASSET_BASE}-${VERSION}"
fi
echo "๐Ÿ“ฆ Output: $ASSET_WITH_VERSION"
cp "rust/target/${{ matrix.target }}/release/${{ matrix.binary }}" "$ASSET_WITH_VERSION"
echo "asset_name=$ASSET_WITH_VERSION" >> "$GITHUB_ENV"
- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: ${{ env.asset_name }}
path: ${{ env.asset_name }}
# โ”€โ”€ ๅ‘ๅธƒๅˆฐ GitHub Releases โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
release:
name: Publish Release
needs: [check, build]
if: needs.check.outputs.should_release == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
merge-multiple: true
- name: List downloaded files
run: |
echo "Downloaded artifacts:"
ls -lh
- name: Delete existing release & tag (force fresh release)
env:
GH_TOKEN: ${{ github.token }}
run: |
VERSION="${{ needs.check.outputs.version }}"
echo "๐Ÿ—‘๏ธ Cleaning up existing release/tag: $VERSION"
gh release delete "$VERSION" --yes --cleanup-tag 2>/dev/null || echo "No existing release to delete"
# ็ญ‰ๅพ… GitHub API ๅŒๆญฅ
sleep 3
- name: Generate release notes from commits
run: |
VERSION="${{ needs.check.outputs.version }}"
REPO="${{ github.repository }}"
BASE_URL="https://github.com/${REPO}/releases/download/${VERSION}"
PLAIN_VER="${VERSION#v}"
PYPI_VER=$(echo "$PLAIN_VER" | sed 's/-rc\./rc/')
# ็”จ sed ๅกซๅ……ๆจกๆฟๅ ไฝ็ฌฆ๏ผŒ็”Ÿๆˆ้™ๆ€้ƒจๅˆ†๏ผˆไธ‹่ฝฝ่กจ + ๅฎ‰่ฃ…ๆŒ‡ๅผ•๏ผ‰
sed \
-e "s|__VERSION__|${VERSION}|g" \
-e "s|__PLAIN_VER__|${PLAIN_VER}|g" \
-e "s|__PYPI_VER__|${PYPI_VER}|g" \
-e "s|__REPO__|${REPO}|g" \
-e "s|__BASE_URL__|${BASE_URL}|g" \
.github/release_template.md > release_notes.md
# ๆŸฅๆ‰พไธŠไธ€ไธช release tag๏ผˆๆŽ’้™คๅฝ“ๅ‰็‰ˆๆœฌ๏ผ‰
PREV_TAG=$(git tag --sort=-v:refname | grep -v "^${VERSION}$" | head -1)
# ่ฟฝๅŠ  changelog + build info
{
echo ""
echo "---"
echo ""
echo "### What's Changed"
echo ""
if [ -n "$PREV_TAG" ]; then
echo "Changes since **${PREV_TAG}**:"
echo ""
git log "${PREV_TAG}..HEAD" --pretty=format:"- %s (\`%h\`) โ€” @%an" --reverse
else
echo "All changes since repository creation:"
echo ""
git log --pretty=format:"- %s (\`%h\`) โ€” @%an" --reverse
fi
echo ""
echo ""
echo "---"
echo ""
echo "### ๐Ÿ“Š Build Info"
echo "- **Build date**: $(date -u '+%Y-%m-%d %H:%M UTC')"
echo "- **Commit**: [\`${GITHUB_SHA::7}\`](https://github.com/${REPO}/commit/${GITHUB_SHA})"
echo ""
if [ -n "$PREV_TAG" ]; then
echo "**Full Changelog**: https://github.com/${REPO}/compare/${PREV_TAG}...${VERSION}"
else
echo "**Full Changelog**: https://github.com/${REPO}/commits/${VERSION}"
fi
} >> release_notes.md
echo "๐Ÿ“ Generated release notes:"
cat release_notes.md
- name: Create GitHub Release
env:
GH_TOKEN: ${{ github.token }}
run: |
VERSION="${{ needs.check.outputs.version }}"
# ๆ”ถ้›†ๆ‰€ๆœ‰่ฆไธŠไผ ็š„ๆ–‡ไปถ๏ผˆไบŒ่ฟ›ๅˆถ + DEB + RPM๏ผŒ็ปŸไธ€ winload-*-VERSION* ๆ ผๅผ๏ผ‰
shopt -s nullglob
FILES=(winload-*-${VERSION}*)
echo "๐Ÿ“ค Uploading ${#FILES[@]} files:"
printf ' - %s\n' "${FILES[@]}"
gh release create "$VERSION" \
--title "winload ${VERSION}" \
--notes-file release_notes.md \
--latest \
"${FILES[@]}"
# โ”€โ”€ ๆ›ดๆ–ฐ Scoop Bucket (GitHub) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# ่งฆๅ‘ๆ–นๅผ:
# - "publish from release" โ†’ ไปŽๅทฒๆœ‰ Release ๆ‹‰ๅ–ไบŒ่ฟ›ๅˆถ๏ผŒไป…ๆ›ดๆ–ฐ Scoop
# - "build publish" โ†’ ๆž„ๅปบ + Release + ๆ›ดๆ–ฐ Scoop๏ผˆๅ…จๅฅ—๏ผ‰
publish-scoop-github:
name: Update Scoop Bucket
needs: [check, release]
# always() ่ฎฉๆญค job ๅณไฝฟ release ่ขซ่ทณ่ฟ‡ไนŸ่ƒฝ่ฟ่กŒ
if: always() && needs.check.outputs.should_publish == 'true' && (needs.release.result == 'success' || needs.release.result == 'skipped')
runs-on: ubuntu-latest
steps:
- name: Fetch latest release info
env:
GH_TOKEN: ${{ github.token }}
run: |
VERSION="${{ needs.check.outputs.version }}"
REPO="${{ github.repository }}"
BASE_URL="https://github.com/${REPO}/releases/download/${VERSION}"
echo "๐Ÿ“ฆ Target version: ${VERSION}"
echo "๐Ÿ“ฅ Downloading Windows binaries from release..."
# ไธ‹่ฝฝ Windows x64
curl -fSL -o winload-windows-x86_64.exe \
"${BASE_URL}/winload-windows-x86_64-msvc-npcap-${VERSION}.exe" \
|| { echo "โŒ Failed to download x64 binary"; exit 1; }
# ไธ‹่ฝฝ Windows ARM64
curl -fSL -o winload-windows-aarch64.exe \
"${BASE_URL}/winload-windows-aarch64-msvc-npcap-${VERSION}.exe" \
|| { echo "โŒ Failed to download arm64 binary"; exit 1; }
echo "โœ… Downloaded:"
ls -lh winload-windows-*.exe
- name: Generate Scoop manifest
run: |
VERSION="${{ needs.check.outputs.version }}"
SCOOP_VERSION="${VERSION#v}"
REPO="${{ github.repository }}"
BASE_URL="https://github.com/${REPO}/releases/download/${VERSION}"
# ่ฎก็ฎ— sha256
HASH_X64=$(sha256sum winload-windows-x86_64.exe | awk '{print $1}')
HASH_ARM64=$(sha256sum winload-windows-aarch64.exe | awk '{print $1}')
echo "๐Ÿ“ฆ Version: $SCOOP_VERSION"
echo "๐Ÿ”‘ x64 hash: $HASH_X64"
echo "๐Ÿ”‘ arm64 hash: $HASH_ARM64"
cat > winload.json << EOF
{
"version": "${SCOOP_VERSION}",
"description": "Network Load Monitor - nload for Windows/Linux/macOS",
"homepage": "https://github.com/${REPO}",
"license": "MIT",
"architecture": {
"64bit": {
"url": "${BASE_URL}/winload-windows-x86_64-msvc-npcap-${VERSION}.exe",
"hash": "${HASH_X64}",
"bin": [["winload-windows-x86_64-msvc-npcap-${VERSION}.exe", "win-nload"]]
},
"arm64": {
"url": "${BASE_URL}/winload-windows-aarch64-msvc-npcap-${VERSION}.exe",
"hash": "${HASH_ARM64}",
"bin": [["winload-windows-aarch64-msvc-npcap-${VERSION}.exe", "win-nload"]]
}
},
"checkver": {
"github": "https://github.com/${REPO}"
},
"autoupdate": {
"architecture": {
"64bit": {
"url": "https://github.com/${REPO}/releases/download/v\$version/winload-windows-x86_64-msvc-npcap-v\$version.exe"
},
"arm64": {
"url": "https://github.com/${REPO}/releases/download/v\$version/winload-windows-aarch64-msvc-npcap-v\$version.exe"
}
}
}
}
EOF
echo "๐Ÿ“ Generated winload.json:"
cat winload.json
- name: Push to scoop-bucket repo
env:
SCOOP_TOKEN: ${{ secrets.SCOOP_BUCKET_TOKEN }}
run: |
VERSION="${{ needs.check.outputs.version }}"
git clone https://x-access-token:${SCOOP_TOKEN}@github.com/VincentZyuApps/scoop-bucket.git scoop-repo
cp winload.json scoop-repo/bucket/winload.json
cd scoop-repo
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add bucket/winload.json
git commit -m "ci: ๐Ÿจ Update winload to ${VERSION}" || echo "No changes to commit"
git push
echo "โœ… Scoop bucket updated!"
# โ”€โ”€ ๆ›ดๆ–ฐ Scoop Bucket (Gitee) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# ็”ŸๆˆๆŒ‡ๅ‘ Gitee Releases ็š„ scoop manifest๏ผŒๆŽจ้€ๅˆฐ Gitee
publish-scoop-gitee:
name: ใ€Gitee็ ไบ‘ใ€‘Update Scoop Bucket
needs: [check, release]
if: always() && needs.check.outputs.should_publish == 'true' && (needs.release.result == 'success' || needs.release.result == 'skipped')
runs-on: ubuntu-latest
steps:
- name: Download Windows binaries from GitHub Releases (for hash)
run: |
VERSION="${{ needs.check.outputs.version }}"
REPO="${{ github.repository }}"
BASE_URL="https://github.com/${REPO}/releases/download/${VERSION}"
echo "๐Ÿ“ฆ Target version: ${VERSION}"
echo "๐Ÿ“ฅ Downloading Windows binaries from GitHub release..."
curl -fSL -o winload-windows-x86_64.exe \
"${BASE_URL}/winload-windows-x86_64-msvc-npcap-${VERSION}.exe" \
|| { echo "โŒ Failed to download x64 binary"; exit 1; }
curl -fSL -o winload-windows-aarch64.exe \
"${BASE_URL}/winload-windows-aarch64-msvc-npcap-${VERSION}.exe" \
|| { echo "โŒ Failed to download arm64 binary"; exit 1; }
echo "โœ… Downloaded:"
ls -lh winload-windows-*.exe
- name: Generate Gitee Scoop manifest
run: |
VERSION="${{ needs.check.outputs.version }}"
SCOOP_VERSION="${VERSION#v}"
GITEE_BASE_URL="https://gitee.com/vincent-zyu/winload/releases/download/${VERSION}"
# ่ฎก็ฎ— sha256
HASH_X64=$(sha256sum winload-windows-x86_64.exe | awk '{print $1}')
HASH_ARM64=$(sha256sum winload-windows-aarch64.exe | awk '{print $1}')
echo "๐Ÿ“ฆ Version: $SCOOP_VERSION"
echo "๐Ÿ”‘ x64 hash: $HASH_X64"
echo "๐Ÿ”‘ arm64 hash: $HASH_ARM64"
cat > winload.json << EOF
{
"version": "${SCOOP_VERSION}",
"description": "Network Load Monitor - nload for Windows/Linux/macOS",
"homepage": "https://gitee.com/vincent-zyu/winload",
"license": "MIT",
"architecture": {
"64bit": {
"url": "${GITEE_BASE_URL}/winload-windows-x86_64-msvc-npcap-${VERSION}.exe",
"hash": "${HASH_X64}",
"bin": [["winload-windows-x86_64-msvc-npcap-${VERSION}.exe", "win-nload"]]
},
"arm64": {
"url": "${GITEE_BASE_URL}/winload-windows-aarch64-msvc-npcap-${VERSION}.exe",
"hash": "${HASH_ARM64}",
"bin": [["winload-windows-aarch64-msvc-npcap-${VERSION}.exe", "win-nload"]]
}
},
"checkver": {
"url": "https://gitee.com/vincent-zyu/winload/releases"
},
"autoupdate": {
"architecture": {
"64bit": {
"url": "https://gitee.com/vincent-zyu/winload/releases/download/v\$version/winload-windows-x86_64-msvc-npcap-v\$version.exe"
},
"arm64": {
"url": "https://gitee.com/vincent-zyu/winload/releases/download/v\$version/winload-windows-aarch64-msvc-npcap-v\$version.exe"
}
}
}
}
EOF
echo "๐Ÿ“ Generated Gitee winload.json:"
cat winload.json
- name: Push to Gitee scoop-bucket repo
env:
GITEE_TOKEN: ${{ secrets.GITEE_TOKEN }}
run: |
VERSION="${{ needs.check.outputs.version }}"
git clone https://vincent-zyu:${GITEE_TOKEN}@gitee.com/vincent-zyu/scoop-bucket.git scoop-repo
cp winload.json scoop-repo/bucket/winload.json
cd scoop-repo
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add bucket/winload.json
git commit -m "ci: ๐Ÿจ Update winload to ${VERSION}" || echo "No changes to commit"
git push
echo "โœ… Gitee Scoop bucket updated!"
# โ”€โ”€ ๆ›ดๆ–ฐ Homebrew Tap (GitHub) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# ็”ŸๆˆๆŒ‡ๅ‘ GitHub Releases ็š„ homebrew formula๏ผŒๆŽจ้€ๅˆฐ GitHub homebrew-tap
publish-homebrew-github:
name: Update Homebrew Tap
needs: [check, release]
if: always() && needs.check.outputs.should_publish == 'true' && (needs.release.result == 'success' || needs.release.result == 'skipped')
runs-on: ubuntu-latest
steps:
- name: Fetch latest release info
env:
GH_TOKEN: ${{ github.token }}
run: |
VERSION="${{ needs.check.outputs.version }}"
REPO="${{ github.repository }}"
BASE_URL="https://github.com/${REPO}/releases/download/${VERSION}"
echo "๐Ÿ“ฆ Target version: ${VERSION}"
# ไธ‹่ฝฝๆ‰€ๆœ‰ๅนณๅฐ็š„ไบŒ่ฟ›ๅˆถ็”จไบŽ่ฎก็ฎ— SHA256
PLATFORMS=(
"winload-linux-x86_64:${BASE_URL}/winload-linux-x86_64-${VERSION}"
"winload-linux-aarch64:${BASE_URL}/winload-linux-aarch64-${VERSION}"
"winload-macos-x86_64:${BASE_URL}/winload-macos-x86_64-${VERSION}"
"winload-macos-aarch64:${BASE_URL}/winload-macos-aarch64-${VERSION}"
)
for entry in "${PLATFORMS[@]}"; do
NAME="${entry%%:*}"
URL="${entry##*:}"
echo "๐Ÿ“ฅ Downloading $NAME..."
curl -fSL -o "$NAME" "$URL" || echo "โš ๏ธ Failed to download $NAME (may not exist)"
done
echo "โœ… Downloaded:"
ls -lh winload-* 2>/dev/null || echo "No binaries downloaded"
- name: Generate Homebrew formula
run: |
VERSION="${{ needs.check.outputs.version }}"
TAGVER="${VERSION#v}" # ๅŽปๆމ v ๅ‰็ผ€
REPO="${{ github.repository }}"
BASE_URL="https://github.com/${REPO}/releases/download/${VERSION}"
mkdir -p Formula
# ่ฎก็ฎ—ๅ„ๅนณๅฐ SHA256
SHA_LINUX_X64=$(sha256sum winload-linux-x86_64 2>/dev/null | awk '{print $1}' || echo "")
SHA_LINUX_ARM64=$(sha256sum winload-linux-aarch64 2>/dev/null | awk '{print $1}' || echo "")
SHA_MACOS_X64=$(sha256sum winload-macos-x86_64 2>/dev/null | awk '{print $1}' || echo "")
SHA_MACOS_ARM64=$(sha256sum winload-macos-aarch64 2>/dev/null | awk '{print $1}' || echo "")
cat > Formula/winload.rb << HEREDOC
class Winload < Formula
desc "Network Load Monitor - nload-like TUI tool for Windows/Linux/macOS"
homepage "https://github.com/${REPO}"
license "MIT"
version "${TAGVER}"
# Dynamic URL generation per platform
if OS.mac? && Hardware::CPU.intel?
url "${BASE_URL}/winload-macos-x86_64-${VERSION}"
sha256 "${SHA_MACOS_X64}"
elsif OS.mac?
url "${BASE_URL}/winload-macos-aarch64-${VERSION}"
sha256 "${SHA_MACOS_ARM64}"
elsif OS.linux? && Hardware::CPU.intel?
url "${BASE_URL}/winload-linux-x86_64-${VERSION}"
sha256 "${SHA_LINUX_X64}"
elsif OS.linux?
url "${BASE_URL}/winload-linux-aarch64-${VERSION}"
sha256 "${SHA_LINUX_ARM64}"
end
def install
bin.install Dir["winload-*"].first => "winload"
end
test do
system "#{bin}/winload", "--version"
end
end
HEREDOC
# ็งป้™ค heredoc ็ผฉ่ฟ›
sed -i 's/^ //' Formula/winload.rb
echo "๐Ÿ“ Generated Formula/winload.rb:"
cat Formula/winload.rb
- name: Push to homebrew-tap repo
env:
GH_TOKEN: ${{ secrets.SCOOP_BUCKET_TOKEN }} # ๅค็”จๅทฒๆœ‰ token
run: |
VERSION="${{ needs.check.outputs.version }}"
git clone https://x-access-token:${GH_TOKEN}@github.com/VincentZyuApps/homebrew-tap.git brew-repo
cp Formula/winload.rb brew-repo/Formula/winload.rb
cd brew-repo
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add Formula/winload.rb
git commit -m "ci: ๐Ÿบ Update winload to ${VERSION}" || echo "No changes to commit"
git push
echo "โœ… Homebrew tap updated!"
# โ”€โ”€ ๆ›ดๆ–ฐ Homebrew Tap (Gitee) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# ็”ŸๆˆๆŒ‡ๅ‘ Gitee Releases ็š„ homebrew formula๏ผŒๆŽจ้€ๅˆฐ Gitee homebrew-tap
publish-homebrew-gitee:
name: ใ€Gitee็ ไบ‘ใ€‘Update Homebrew Tap
needs: [check, release]
if: always() && needs.check.outputs.should_publish == 'true' && (needs.release.result == 'success' || needs.release.result == 'skipped')
runs-on: ubuntu-latest
steps:
- name: Fetch latest release info from Gitee
env:
GITEE_TOKEN: ${{ secrets.GITEE_TOKEN }}
run: |
VERSION="${{ needs.check.outputs.version }}"
GITEE_BASE_URL="https://gitee.com/vincent-zyu/winload/releases/download/${VERSION}"
echo "๐Ÿ“ฆ Target version: ${VERSION}"
# ไธ‹่ฝฝๆ‰€ๆœ‰ๅนณๅฐ็š„ไบŒ่ฟ›ๅˆถ็”จไบŽ่ฎก็ฎ— SHA256
PLATFORMS=(
"winload-linux-x86_64:${GITEE_BASE_URL}/winload-linux-x86_64-${VERSION}"
"winload-linux-aarch64:${GITEE_BASE_URL}/winload-linux-aarch64-${VERSION}"
"winload-macos-x86_64:${GITEE_BASE_URL}/winload-macos-x86_64-${VERSION}"
"winload-macos-aarch64:${GITEE_BASE_URL}/winload-macos-aarch64-${VERSION}"
)
for entry in "${PLATFORMS[@]}"; do
NAME="${entry%%:*}"
URL="${entry##*:}"
echo "๐Ÿ“ฅ Downloading $NAME from Gitee..."
curl -fSL -o "$NAME" "$URL" || echo "โš ๏ธ Failed to download $NAME (may not exist)"
done
echo "โœ… Downloaded:"
ls -lh winload-* 2>/dev/null || echo "No binaries downloaded"
- name: Generate Homebrew formula (Gitee)
run: |
VERSION="${{ needs.check.outputs.version }}"
TAGVER="${VERSION#v}"
GITEE_BASE_URL="https://gitee.com/vincent-zyu/winload/releases/download/${VERSION}"
mkdir -p Formula
# ่ฎก็ฎ—ๅ„ๅนณๅฐ SHA256
SHA_LINUX_X64=$(sha256sum winload-linux-x86_64 2>/dev/null | awk '{print $1}' || echo "")
SHA_LINUX_ARM64=$(sha256sum winload-linux-aarch64 2>/dev/null | awk '{print $1}' || echo "")
SHA_MACOS_X64=$(sha256sum winload-macos-x86_64 2>/dev/null | awk '{print $1}' || echo "")
SHA_MACOS_ARM64=$(sha256sum winload-macos-aarch64 2>/dev/null | awk '{print $1}' || echo "")
cat > Formula/winload.rb << HEREDOC
class Winload < Formula
desc "Network Load Monitor - nload-like TUI tool for Windows/Linux/macOS"
homepage "https://gitee.com/vincent-zyu/winload"
license "MIT"
version "${TAGVER}"
# Dynamic URL generation per platform (Gitee)
if OS.mac? && Hardware::CPU.intel?
url "${GITEE_BASE_URL}/winload-macos-x86_64-${VERSION}"
sha256 "${SHA_MACOS_X64}"
elsif OS.mac?
url "${GITEE_BASE_URL}/winload-macos-aarch64-${VERSION}"
sha256 "${SHA_MACOS_ARM64}"
elsif OS.linux? && Hardware::CPU.intel?
url "${GITEE_BASE_URL}/winload-linux-x86_64-${VERSION}"
sha256 "${SHA_LINUX_X64}"
elsif OS.linux?
url "${GITEE_BASE_URL}/winload-linux-aarch64-${VERSION}"
sha256 "${SHA_LINUX_ARM64}"
end
def install
bin.install Dir["winload-*"].first => "winload"
end
test do
system "#{bin}/winload", "--version"
end
end
HEREDOC
# ็งป้™ค heredoc ็ผฉ่ฟ›
sed -i 's/^ //' Formula/winload.rb
echo "๐Ÿ“ Generated Formula/winload.rb (Gitee):"
cat Formula/winload.rb
- name: Push to Gitee homebrew-tap repo
env:
GITEE_TOKEN: ${{ secrets.GITEE_TOKEN }}
run: |
VERSION="${{ needs.check.outputs.version }}"
git clone https://vincent-zyu:${GITEE_TOKEN}@gitee.com/vincent-zyu/homebrew-tap.git brew-repo
cp Formula/winload.rb brew-repo/Formula/winload.rb
cd brew-repo
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add Formula/winload.rb
git commit -m "ci: ๐Ÿบ Update winload to ${VERSION}" || echo "No changes to commit"
git push
echo "โœ… Gitee Homebrew tap updated!"
# โ”€โ”€ ๆ›ดๆ–ฐ AUR ๅŒ…๏ผˆwinload-rust-bin: ้ข„็ผ–่ฏ‘ไบŒ่ฟ›ๅˆถ๏ผ‰โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
publish-aur-bin:
name: Update AUR Package (winload-rust-bin)
needs: [check, release]
if: always() && needs.check.outputs.should_publish == 'true' && (needs.release.result == 'success' || needs.release.result == 'skipped')
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Download Linux binaries from release
env:
GH_TOKEN: ${{ github.token }}
run: |
VERSION="${{ needs.check.outputs.version }}"
REPO="${{ github.repository }}"
BASE_URL="https://github.com/${REPO}/releases/download/${VERSION}"
echo "๐Ÿ“ฅ Downloading x86_64 binary..."
curl -fSL -o winload-linux-x86_64 "${BASE_URL}/winload-linux-x86_64-${VERSION}"
SHA256_X86=$(sha256sum winload-linux-x86_64 | awk '{print $1}')
echo "๐Ÿ”‘ x86_64 SHA256: $SHA256_X86"
echo "SHA256_X86=$SHA256_X86" >> "$GITHUB_ENV"
echo "๐Ÿ“ฅ Downloading aarch64 binary..."
curl -fSL -o winload-linux-aarch64 "${BASE_URL}/winload-linux-aarch64-${VERSION}"
SHA256_ARM=$(sha256sum winload-linux-aarch64 | awk '{print $1}')
echo "๐Ÿ”‘ aarch64 SHA256: $SHA256_ARM"
echo "SHA256_ARM=$SHA256_ARM" >> "$GITHUB_ENV"
- name: Generate PKGBUILD
run: |
VERSION="${{ needs.check.outputs.version }}"
TAGVER="${VERSION#v}" # 0.1.6-beta.3 (for download URL)
PKGVER="${TAGVER//-/.}" # 0.1.6.beta.3 (AUR compliant)
cat > PKGBUILD << HEREDOC_END
# Maintainer: VincentZyu <vincentzyu233@gmail.com>
pkgname=winload-rust-bin
pkgver=${PKGVER}
pkgrel=1
pkgdesc="A lightweight, real-time CLI tool for monitoring network bandwidth and traffic"
arch=('x86_64' 'aarch64')
url="https://github.com/VincentZyuApps/winload"
license=('MIT')
provides=('winload')
conflicts=('winload' 'winload-rust')
_tagver=${TAGVER}
_base_url="https://github.com/VincentZyuApps/winload/releases/download/v\${_tagver}"
source_x86_64=("winload-linux-x86_64-v\${_tagver}::\${_base_url}/winload-linux-x86_64-v\${_tagver}")
source_aarch64=("winload-linux-aarch64-v\${_tagver}::\${_base_url}/winload-linux-aarch64-v\${_tagver}")
noextract=()
sha256sums_x86_64=('${SHA256_X86}')
sha256sums_aarch64=('${SHA256_ARM}')
package() {
if [[ "\$CARCH" == "x86_64" ]]; then
install -Dm755 "\$srcdir/winload-linux-x86_64-v\${_tagver}" "\$pkgdir/usr/bin/winload"
elif [[ "\$CARCH" == "aarch64" ]]; then
install -Dm755 "\$srcdir/winload-linux-aarch64-v\${_tagver}" "\$pkgdir/usr/bin/winload"
fi
}
HEREDOC_END
# Remove leading whitespace (heredoc indentation)
sed -i 's/^ //' PKGBUILD
echo "๐Ÿ“ Generated PKGBUILD:"
cat PKGBUILD
- name: Generate .SRCINFO
uses: docker://archlinux:latest
with:
args: bash -c "pacman -Sy --noconfirm pacman-contrib base-devel && useradd -m builder && cp PKGBUILD /home/builder/ && cd /home/builder && sudo -u builder makepkg --printsrcinfo > .SRCINFO && cp .SRCINFO /github/workspace/"
- name: Push to AUR
env:
AUR_SSH_KEY: ${{ secrets.AUR_SSH_KEY }}
run: |
VERSION="${{ needs.check.outputs.version }}"
TAGVER="${VERSION#v}" # 0.1.6-beta.3 (for download URL)
PKGVER="${TAGVER//-/.}" # 0.1.6.beta.3 (AUR compliant)
# Setup SSH for AUR
mkdir -p ~/.ssh
echo "$AUR_SSH_KEY" > ~/.ssh/aur
chmod 600 ~/.ssh/aur
cat >> ~/.ssh/config << 'SSHEOF'
Host aur.archlinux.org
IdentityFile ~/.ssh/aur
User aur
StrictHostKeyChecking no
SSHEOF
# Clone AUR repo
git clone ssh://aur@aur.archlinux.org/winload-rust-bin.git aur-repo || {
echo "โš ๏ธ AUR repo doesn't exist yet. Please create it manually first."
echo " Run: ssh aur@aur.archlinux.org setup-repo winload-rust-bin"
exit 1
}
# Copy files
cp PKGBUILD aur-repo/
cp .SRCINFO aur-repo/
# Commit and push
cd aur-repo
git config user.name "VincentZyu"
git config user.email "vincentzyu233@gmail.com"
git add PKGBUILD .SRCINFO
git commit -m "ci: ๐Ÿ”ผ Update to ${PKGVER}" || echo "No changes to commit"
git push
echo "โœ… winload-rust-bin updated!"
# โ”€โ”€ ๅ‘ๅธƒๅˆฐ npm๏ผˆscoped: @vincentzyuapps/winload๏ผ‰โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# ๅŽŸ็†ๅŒ esbuild / @biomejs/biome / turbo:
# 1. ไธบๆฏไธชๅนณๅฐๅ‘ๅธƒไธ€ไธชๅชๅซไบŒ่ฟ›ๅˆถ็š„ๅŒ… (os/cpu ๅญ—ๆฎต่ฎฉ npm ่‡ชๅŠจ็ญ›้€‰)
# 2. ไธปๅŒ… @vincentzyuapps/winload ้€š่ฟ‡ optionalDependencies ๅผ•็”จๅฎƒไปฌ
# 3. ็”จๆˆท npm install ๆ—ถๅชไธ‹่ฝฝๅŒน้…ๅฝ“ๅ‰ๅนณๅฐ็š„้‚ฃไธ€ไธช
publish-npm-scope:
name: Publish to npm (scoped)
needs: [check, release]
if: always() && needs.check.outputs.should_publish == 'true' && (needs.release.result == 'success' || needs.release.result == 'skipped')
permissions:
id-token: write
packages: write
contents: read
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
registry-url: 'https://registry.npmjs.org'
- name: Download binaries from release
env:
GH_TOKEN: ${{ github.token }}
run: |
VERSION="${{ needs.check.outputs.version }}"
REPO="${{ github.repository }}"
BASE_URL="https://github.com/${REPO}/releases/download/${VERSION}"
mkdir -p artifacts
echo "๐Ÿ“ฅ Downloading binaries for npm publish..."
curl -fSL -o artifacts/winload-windows-x86_64.exe "${BASE_URL}/winload-windows-x86_64-msvc-npcap-${VERSION}.exe"
curl -fSL -o artifacts/winload-windows-aarch64.exe "${BASE_URL}/winload-windows-aarch64-msvc-npcap-${VERSION}.exe"
curl -fSL -o artifacts/winload-linux-x86_64 "${BASE_URL}/winload-linux-x86_64-${VERSION}"
curl -fSL -o artifacts/winload-linux-aarch64 "${BASE_URL}/winload-linux-aarch64-${VERSION}"
curl -fSL -o artifacts/winload-macos-x86_64 "${BASE_URL}/winload-macos-x86_64-${VERSION}"
curl -fSL -o artifacts/winload-macos-aarch64 "${BASE_URL}/winload-macos-aarch64-${VERSION}"
echo "โœ… Downloaded:"
ls -lh artifacts/
- name: Publish platform packages
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
VERSION="${{ needs.check.outputs.version }}"
NPM_VERSION="${VERSION#v}"
NPM_TAG="latest"
echo "๐Ÿ“ฆ npm version: ${NPM_VERSION} (tag: ${NPM_TAG})"
PLATFORMS=(
"@vincentzyuapps/winload-win32-x64|win32|x64|artifacts/winload-windows-x86_64.exe|winload.exe"
"@vincentzyuapps/winload-win32-arm64|win32|arm64|artifacts/winload-windows-aarch64.exe|winload.exe"
"@vincentzyuapps/winload-linux-x64|linux|x64|artifacts/winload-linux-x86_64|winload"
"@vincentzyuapps/winload-linux-arm64|linux|arm64|artifacts/winload-linux-aarch64|winload"
"@vincentzyuapps/winload-darwin-x64|darwin|x64|artifacts/winload-macos-x86_64|winload"
"@vincentzyuapps/winload-darwin-arm64|darwin|arm64|artifacts/winload-macos-aarch64|winload"
)
for entry in "${PLATFORMS[@]}"; do
IFS='|' read -r PKG_NAME PKG_OS PKG_CPU SOURCE_BIN BIN_NAME <<< "$entry"
echo ""
echo "๐Ÿ“ฆ Publishing ${PKG_NAME}@${NPM_VERSION}..."
PKG_DIR="npm-platforms/${PKG_NAME}"
mkdir -p "${PKG_DIR}/bin"
cp "${SOURCE_BIN}" "${PKG_DIR}/bin/${BIN_NAME}"
chmod +x "${PKG_DIR}/bin/${BIN_NAME}"
cat > "${PKG_DIR}/package.json" << PKGJSON
{
"name": "${PKG_NAME}",
"version": "${NPM_VERSION}",
"description": "winload binary for ${PKG_OS}-${PKG_CPU}",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/${{ github.repository }}"
},
"os": ["${PKG_OS}"],
"cpu": ["${PKG_CPU}"],
"files": ["bin/"]
}
PKGJSON
cd "${PKG_DIR}"
npm publish --provenance --access public --tag "${NPM_TAG}" 2>&1 || echo "โš ๏ธ Failed to publish ${PKG_NAME} (may already exist)"
cd "$GITHUB_WORKSPACE"
done
- name: Publish main package
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
VERSION="${{ needs.check.outputs.version }}"
NPM_VERSION="${VERSION#v}"
NPM_TAG="latest"
cp readme.md npm/winload-rust-bin/readme.md
cd npm/winload-rust-bin
node -e "
const fs = require('fs');
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
pkg.version = '${NPM_VERSION}';
for (const dep of Object.keys(pkg.optionalDependencies || {})) {
pkg.optionalDependencies[dep] = '${NPM_VERSION}';
}
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n');
"
echo "๐Ÿ“ฆ Publishing @vincentzyuapps/winload@${NPM_VERSION} (tag: ${NPM_TAG})..."
cat package.json
npm publish --provenance --access public --tag "${NPM_TAG}"
echo "โœ… Scoped package published!"
- name: Upload npm packages for downstream jobs
uses: actions/upload-artifact@v4
with:
name: npm-packages
path: |
npm-platforms/
npm/winload-rust-bin/
if-no-files-found: error
# โ”€โ”€ ๅ‘ๅธƒๅˆฐ npm๏ผˆunscoped: winload-rust-bin๏ผ‰โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
publish-npm-unscope:
name: Publish to npm (unscoped)
needs: [check, release, publish-npm-scope]
if: always() && needs.check.outputs.should_publish == 'true' && (needs.release.result == 'success' || needs.release.result == 'skipped')
permissions:
id-token: write
contents: read
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
name: npm-packages
- uses: actions/setup-node@v4
with:
node-version: '20'
registry-url: 'https://registry.npmjs.org'
- name: Publish winload-rust-bin (unscoped) to npm
run: |
cd npm/winload-rust-bin
node -e "
const p = require('./package.json');
p.name = 'winload-rust-bin';
require('fs').writeFileSync('./package.json', JSON.stringify(p, null, 2) + '\n');
"
npm publish --provenance --access public --tag latest
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
# โ”€โ”€ ๅŒๆญฅๅ‘ๅธƒๅˆฐ GitHub Packages (npm.pkg.github.com) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
publish-npm-github-packages:
name: Publish to GitHub Packages for npm
needs: [check, release, publish-npm-scope]
if: always() && needs.check.outputs.should_publish == 'true' && (needs.release.result == 'success' || needs.release.result == 'skipped')
permissions:
contents: read
packages: write
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
name: npm-packages
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Publish platform packages to GitHub Packages
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
for dir in npm-platforms/@vincentzyuapps/*/; do
[ -d "$dir" ] || continue
echo "๐Ÿ“ฆ Publishing $(basename $dir) to GitHub Packages..."
cd "$dir"
echo "//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}" > .npmrc
npm publish --registry https://npm.pkg.github.com 2>&1 || echo "โš ๏ธ Failed to publish $(basename $dir) to GitHub Packages (may already exist)"
cd "$GITHUB_WORKSPACE"
done
- name: Publish main package to GitHub Packages
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
cd npm/winload-rust-bin
echo "//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}" > .npmrc
npm publish --registry https://npm.pkg.github.com
echo "โœ… GitHub Packages published!"
# โ”€โ”€ ๅ‘ๅธƒๅˆฐ PyPI (Python ๅŒ…) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
publish-pypi:
name: Publish to PyPI
needs: check
if: needs.check.outputs.should_publish_pypi == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v4
- name: Copy readme for PyPI
run: cp readme.md python/readme.md
- name: Build package
working-directory: python
run: |
uv build
- name: Publish to PyPI
working-directory: python
env:
UV_PUBLISH_TOKEN: ${{ secrets.PYPI_TOKEN }}
run: |
uv publish --publish-url https://upload.pypi.org/legacy/
# โ”€โ”€ ๅ‘ๅธƒๅˆฐ crates.io (Rust ๅŒ…) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
publish-crates-io:
name: Publish to crates.io
needs: [check, build]
if: needs.check.outputs.should_publish_crates == 'true' && needs.build.result == 'success'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Prepare README for crates.io
run: |
pwd
ls -la
cp ./readme.md ./rust/readme.md
- name: Publish to crates.io
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: |
cd rust
cargo publish --allow-dirty --token "$CARGO_REGISTRY_TOKEN"
echo "โœ… Published to crates.io!"
# โ”€โ”€ ๅŒๆญฅ Release ๅˆฐ Gitee โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# ๅœจ release ๅฎŒๆˆๅŽ่ฟ่กŒ๏ผŒไธŽ publish-scoop/aur/npm ๅนถ่กŒ
sync-gitee-release:
name: ใ€Gitee็ ไบ‘ใ€‘Sync Release File
needs: [check, release]
if: needs.check.outputs.should_release == 'true' && needs.release.result == 'success'
runs-on: ubuntu-latest
env:
GITEE_TOKEN: ${{ secrets.GITEE_TOKEN }}
GITEE_OWNER: vincent-zyu
GITEE_REPO: winload
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Get release info from GitHub
id: release_info
env:
GH_TOKEN: ${{ github.token }}
run: |
TAG="${{ needs.check.outputs.version }}"
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
# ่Žทๅ– Release ไฟกๆฏ
RELEASE_INFO=$(gh release view "$TAG" --json name,body,assets)
echo "release_name=$(echo "$RELEASE_INFO" | jq -r '.name')" >> "$GITHUB_OUTPUT"
echo "$RELEASE_INFO" | jq -r '.body' > release_body.md
echo "$RELEASE_INFO" | jq -r '.assets[].url' > asset_urls.txt
echo "๐Ÿ“ฆ Release: $TAG"
echo "๐Ÿ“ Assets:"
cat asset_urls.txt
- name: Download GitHub Release assets
env:
GH_TOKEN: ${{ github.token }}
run: |
TAG="${{ steps.release_info.outputs.tag }}"
mkdir -p assets
cd assets
gh release download "$TAG" --pattern "*"
echo "๐Ÿ“ฅ Downloaded files:"
ls -la
- name: Create or find Gitee Release
id: gitee_release
run: |
TAG="${{ steps.release_info.outputs.tag }}"
NAME="${{ steps.release_info.outputs.release_name }}"
# JSON ๅฎ‰ๅ…จ่ฝฌไน‰ release body๏ผˆๅค„็†ๆข่กŒใ€ๅผ•ๅทใ€ๅๆ–œๆ ็ญ‰๏ผ‰
JSON_BODY=$(jq -R -s '.' < release_body.md)
CURL="curl -s -k --retry 3 --retry-delay 5 --retry-all-errors"
# 1. ็กฎไฟ tag ๅญ˜ๅœจไบŽ Gitee
if $CURL --fail "https://gitee.com/api/v5/repos/${GITEE_OWNER}/${GITEE_REPO}/tags/${TAG}?access_token=${GITEE_TOKEN}" > /dev/null 2>&1; then
echo "๐Ÿท๏ธ Tag $TAG already exists on Gitee"
else
echo "๐Ÿท๏ธ Creating tag $TAG on Gitee..."
$CURL -X POST --header 'Content-Type: application/json;charset=UTF-8' \
"https://gitee.com/api/v5/repos/${GITEE_OWNER}/${GITEE_REPO}/tags" \
-d "{\"access_token\":\"${GITEE_TOKEN}\",\"tag_name\":\"${TAG}\",\"refs\":\"main\",\"tag_message\":\"Release ${TAG}\"}"
fi
# 2. ๆฃ€ๆŸฅๆ˜ฏๅฆๅทฒๆœ‰ๅฏนๅบ” tag ็š„ Release
EXISTING=$($CURL "https://gitee.com/api/v5/repos/${GITEE_OWNER}/${GITEE_REPO}/releases/tags/${TAG}?access_token=${GITEE_TOKEN}")
RELEASE_ID=$(echo "$EXISTING" | jq -r '.id')
if [ -n "$RELEASE_ID" ] && [ "$RELEASE_ID" != "null" ]; then
ASSET_COUNT=$(echo "$EXISTING" | jq '.assets | length')
echo "โš ๏ธ Gitee release already exists (ID: $RELEASE_ID, assets: $ASSET_COUNT)"
else
echo "โœ… No existing release, creating..."
# 3. ๅˆ›ๅปบ Release๏ผˆๆณจๆ„ target_commitish ๆ˜ฏๅฟ…ๅกซๅญ—ๆฎต๏ผ‰
RELEASE_PAYLOAD=$(jq -n \
--arg token "$GITEE_TOKEN" \
--arg tag "$TAG" \
--arg name "$NAME" \
--argjson body "$JSON_BODY" \
'{
access_token: $token,
tag_name: $tag,
name: $name,
body: $body,
target_commitish: "main",
prerelease: false
}')
CREATE_RESPONSE=$($CURL -X POST --header 'Content-Type: application/json;charset=UTF-8' \
"https://gitee.com/api/v5/repos/${GITEE_OWNER}/${GITEE_REPO}/releases" \
-d "$RELEASE_PAYLOAD")
RELEASE_ID=$(echo "$CREATE_RESPONSE" | jq -r '.id')
ASSET_COUNT=0
if [ "$RELEASE_ID" = "null" ] || [ -z "$RELEASE_ID" ]; then
echo "โŒ Failed to create Gitee release"
echo "Response: $CREATE_RESPONSE"
exit 1
fi
echo "โœ… Created Gitee release (ID: $RELEASE_ID)"
fi
echo "release_id=$RELEASE_ID" >> "$GITHUB_OUTPUT"
echo "asset_count=$ASSET_COUNT" >> "$GITHUB_OUTPUT"
- name: Upload assets to Gitee
if: steps.gitee_release.outputs.release_id != '' && steps.gitee_release.outputs.release_id != 'null'
run: |
RELEASE_ID="${{ steps.gitee_release.outputs.release_id }}"
# ๆ—ถ้—ดๆˆณๅ‡ฝๆ•ฐ๏ผšๅŒๆ—ถ่พ“ๅ‡บ UTC ๅ’Œ ไธŠๆตทๆ—ถ้—ด
ts() {
echo "[$(date -u '+%H:%M:%S UTC') / $(TZ='Asia/Shanghai' date '+%H:%M:%S CST')]"
}
echo "$(ts) ๐Ÿ“ค Uploading assets to Gitee release (ID: $RELEASE_ID)"
SUCCESS=0
FAIL=0
TOTAL=$(ls -1 assets/ | wc -l)
CURRENT=0
echo "$(ts) ๐Ÿ“ Total files to upload: $TOTAL"
for FILE in assets/*; do
if [ -f "$FILE" ]; then
CURRENT=$((CURRENT + 1))
FILENAME=$(basename "$FILE")
FILESIZE=$(stat -c%s "$FILE" 2>/dev/null || stat -f%z "$FILE" 2>/dev/null)
FILESIZE_H=$(numfmt --to=iec $FILESIZE 2>/dev/null || echo "${FILESIZE} bytes")
echo ""
echo "$(ts) โ”€โ”€โ”€โ”€ [$CURRENT/$TOTAL] $FILENAME ($FILESIZE_H) โ”€โ”€โ”€โ”€"
# ้‡่ฏ• 3 ๆฌก๏ผŒ่ถ…ๆ—ถ 1200s (20min)๏ผŒ้—ด้š” 10s
# Gitee ไธŠไผ ้€Ÿๅบฆ็บฆ 10KB/s๏ผŒ3MB ๆ–‡ไปถ้œ€่ฆ ~300s
UPLOADED=false
for ATTEMPT in 1 2 3; do
echo "$(ts) ๐Ÿ”„ Attempt $ATTEMPT/3 โ€” starting curl (max-time=1200s, connect-timeout=30s)"
START_TIME=$(date +%s)
HTTP_CODE_FILE=$(mktemp)
RESPONSE=$(curl -s -k --retry 3 --retry-delay 10 --retry-all-errors \
--max-time 1200 --connect-timeout 30 \
-w "\n%{http_code}" \
-X POST --header "Content-Type: multipart/form-data" \
-F "access_token=${GITEE_TOKEN}" \
-F "file=@${FILE}" \
"https://gitee.com/api/v5/repos/${GITEE_OWNER}/${GITEE_REPO}/releases/${RELEASE_ID}/attach_files")
CURL_EXIT=$?
END_TIME=$(date +%s)
ELAPSED=$((END_TIME - START_TIME))
# ๅˆ†็ฆป HTTP status code ๅ’Œ response body
HTTP_CODE=$(echo "$RESPONSE" | tail -1)
BODY=$(echo "$RESPONSE" | sed '$d')
echo "$(ts) ๐Ÿ“ก curl exit=$CURL_EXIT, HTTP=$HTTP_CODE, took ${ELAPSED}s"
if [ $CURL_EXIT -ne 0 ]; then
case $CURL_EXIT in
28) echo "$(ts) โฑ๏ธ TIMEOUT: curl exceeded max-time (${ELAPSED}s)" ;;
7) echo "$(ts) ๐Ÿ”Œ CONNECTION REFUSED: Gitee server unreachable" ;;
56) echo "$(ts) ๐Ÿ’ฅ RECV ERROR: connection reset during transfer" ;;
*) echo "$(ts) โ“ CURL ERROR $CURL_EXIT (see https://curl.se/libcurl/c/libcurl-errors.html)" ;;
esac
elif echo "$BODY" | jq -e '.browser_download_url' > /dev/null 2>&1; then
URL=$(echo "$BODY" | jq -r '.browser_download_url')
echo "$(ts) โœ… SUCCESS: $URL"
SUCCESS=$((SUCCESS + 1))
UPLOADED=true
break
else
echo "$(ts) โš ๏ธ UNEXPECTED RESPONSE (HTTP $HTTP_CODE):"
echo "$(ts) $(echo "$BODY" | head -c 300)"
fi
if [ $ATTEMPT -lt 3 ]; then
echo "$(ts) ๐Ÿ’ค Waiting 10s before retry..."
sleep 10
fi
done
if [ "$UPLOADED" = false ]; then
echo "$(ts) โŒ GAVE UP on $FILENAME after 3 attempts"
FAIL=$((FAIL + 1))
fi
fi
done
echo ""
echo "$(ts) โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•"
echo "$(ts) ๐Ÿ“Š Upload summary: $SUCCESS/$TOTAL succeeded, $FAIL failed"
echo "$(ts) โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•"
echo "๐Ÿ“Š Upload complete: $SUCCESS succeeded, $FAIL failed"