ci: Release 正文仅保留变更日志,使用说明见 README

Made-with: Cursor
This commit is contained in:
voson
2026-03-18 14:55:31 +08:00
parent 3d00b65f55
commit a3a92e073f

View File

@@ -24,7 +24,6 @@ env:
RUST_BACKTRACE: short RUST_BACKTRACE: short
jobs: jobs:
# ========== 版本检查 + Release 预创建(单次运行,消除多 job 竞态)==========
version: version:
name: 版本 & Release name: 版本 & Release
runs-on: debian runs-on: debian
@@ -43,14 +42,17 @@ jobs:
run: | run: |
version=$(grep -m1 '^version' Cargo.toml | sed 's/.*"\(.*\)".*/\1/') version=$(grep -m1 '^version' Cargo.toml | sed 's/.*"\(.*\)".*/\1/')
tag="secrets-${version}" tag="secrets-${version}"
echo "version=${version}" >> $GITHUB_OUTPUT previous_tag=$(git tag --list 'secrets-*' --sort=-v:refname | grep -vx "$tag" | sed -n '1p')
echo "tag=${tag}" >> $GITHUB_OUTPUT
echo "version=${version}" >> "$GITHUB_OUTPUT"
echo "tag=${tag}" >> "$GITHUB_OUTPUT"
echo "previous_tag=${previous_tag}" >> "$GITHUB_OUTPUT"
if git rev-parse "refs/tags/${tag}" >/dev/null 2>&1; then if git rev-parse "refs/tags/${tag}" >/dev/null 2>&1; then
echo "tag_exists=true" >> $GITHUB_OUTPUT echo "tag_exists=true" >> "$GITHUB_OUTPUT"
echo "版本 ${tag} 已存在" echo "版本 ${tag} 已存在"
else else
echo "tag_exists=false" >> $GITHUB_OUTPUT echo "tag_exists=false" >> "$GITHUB_OUTPUT"
echo "将创建新版本 ${tag}" echo "将创建新版本 ${tag}"
fi fi
@@ -62,39 +64,148 @@ jobs:
git tag -a "${{ steps.ver.outputs.tag }}" -m "Release ${{ steps.ver.outputs.tag }}" git tag -a "${{ steps.ver.outputs.tag }}" -m "Release ${{ steps.ver.outputs.tag }}"
git push origin "${{ steps.ver.outputs.tag }}" git push origin "${{ steps.ver.outputs.tag }}"
- name: 创建 Release - name: 解析或创建 Release
id: release id: release
if: steps.ver.outputs.tag_exists == 'false'
env: env:
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
run: | run: |
if [ -z "$RELEASE_TOKEN" ]; then if [ -z "$RELEASE_TOKEN" ]; then
echo "release_id=" >> $GITHUB_OUTPUT echo "release_id=" >> "$GITHUB_OUTPUT"
exit 0 exit 0
fi fi
command -v jq &>/dev/null || (sudo apt-get update -qq && sudo apt-get install -y -qq jq) command -v jq >/dev/null 2>&1 || (sudo apt-get update -qq && sudo apt-get install -y -qq jq)
tag="${{ steps.ver.outputs.tag }}" tag="${{ steps.ver.outputs.tag }}"
url="${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases" version="${{ steps.ver.outputs.version }}"
release_id=$(curl -sS -H "Authorization: token $RELEASE_TOKEN" \ release_api="${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases"
-H "Content-Type: application/json" -X POST "$url" \ existing_release=$(curl -fsS -H "Authorization: token $RELEASE_TOKEN" \
-d "{\"tag_name\":\"${tag}\",\"name\":\"${tag}\",\"body\":\"Release ${tag}\"}" \ "${release_api}/tags/${tag}" || true)
| jq -r '.id // empty') release_id=$(printf '%s' "$existing_release" | jq -r '.id // empty')
echo "release_id=${release_id}" >> $GITHUB_OUTPUT if [ -n "$release_id" ]; then
echo "release_id=${release_id}" >> "$GITHUB_OUTPUT"
exit 0
fi
if [ "${{ steps.ver.outputs.tag_exists }}" = "true" ]; then
echo "release_id=" >> "$GITHUB_OUTPUT"
exit 0
fi
previous_tag="${{ steps.ver.outputs.previous_tag }}"
if [ -n "$previous_tag" ]; then
changes=$(git log --pretty=format:'- %s (%h)' "${previous_tag}..HEAD")
else
changes=$(git log --pretty=format:'- %s (%h)')
fi
if [ -z "$changes" ]; then
changes="- 首次发布"
fi
cat > release-body.md <<EOF
## 变更日志
${changes}
EOF
payload=$(jq -n \
--arg tag "$tag" \
--arg name "${{ env.BINARY_NAME }} ${version}" \
--rawfile body release-body.md \
'{tag_name: $tag, name: $name, body: $body}')
response=$(curl -fsS -H "Authorization: token $RELEASE_TOKEN" \
-H "Content-Type: application/json" \
-X POST "$release_api" \
-d "$payload")
release_id=$(printf '%s' "$response" | jq -r '.id // empty')
if [ -z "$release_id" ]; then
echo "创建 Release 失败"
exit 1
fi
echo "release_id=${release_id}" >> "$GITHUB_OUTPUT"
probe-runners:
name: 探测 Runner
runs-on: debian
outputs:
has_linux: ${{ steps.probe.outputs.has_linux }}
has_macos: ${{ steps.probe.outputs.has_macos }}
has_windows: ${{ steps.probe.outputs.has_windows }}
steps:
- name: 查询可用 Runner
id: probe
env:
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
run: |
if [ -z "$RELEASE_TOKEN" ]; then
echo "has_linux=true" >> "$GITHUB_OUTPUT"
echo "has_macos=true" >> "$GITHUB_OUTPUT"
echo "has_windows=true" >> "$GITHUB_OUTPUT"
exit 0
fi
command -v jq >/dev/null 2>&1 || (sudo apt-get update -qq && sudo apt-get install -y -qq jq)
runners=$(curl -fsS -H "Authorization: token $RELEASE_TOKEN" \
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/actions/runners" || true)
if [ -z "$runners" ]; then
echo "has_linux=true" >> "$GITHUB_OUTPUT"
echo "has_macos=true" >> "$GITHUB_OUTPUT"
echo "has_windows=true" >> "$GITHUB_OUTPUT"
exit 0
fi
has_runner() {
local label="$1"
printf '%s' "$runners" | jq -e --arg label "$label" '
(.runners // .data // . // [])
| any(
(
(.status // (if (.online // false) then "online" else "offline" end))
| ascii_downcase
) == "online"
and (
(.labels // [])
| map(if type == "object" then (.name // .label // "") else tostring end)
| index($label)
) != null
)
' >/dev/null
}
if has_runner "debian"; then
echo "has_linux=true" >> "$GITHUB_OUTPUT"
else
echo "has_linux=false" >> "$GITHUB_OUTPUT"
fi
if has_runner "darwin-arm64"; then
echo "has_macos=true" >> "$GITHUB_OUTPUT"
else
echo "has_macos=false" >> "$GITHUB_OUTPUT"
fi
if has_runner "windows"; then
echo "has_windows=true" >> "$GITHUB_OUTPUT"
else
echo "has_windows=false" >> "$GITHUB_OUTPUT"
fi
# ========== 代码质量检查(只在 Linux 跑一次)==========
check: check:
name: 质量检查 (fmt / clippy / test) name: 质量检查 (fmt / clippy / test)
runs-on: debian runs-on: debian
timeout-minutes: 1 # 超时1分钟 timeout-minutes: 10
steps: steps:
- name: 安装 Rust - name: 安装 Rust
run: | run: |
if ! command -v cargo &>/dev/null; then if ! command -v cargo >/dev/null 2>&1; then
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable
echo "$HOME/.cargo/bin" >> $GITHUB_PATH echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
fi fi
source "$HOME/.cargo/env" 2>/dev/null || true source "$HOME/.cargo/env" 2>/dev/null || true
rustup component add rustfmt clippy rustup component add rustfmt clippy
@@ -110,68 +221,31 @@ jobs:
~/.cargo/git/db ~/.cargo/git/db
target target
key: cargo-check-${{ hashFiles('Cargo.lock') }} key: cargo-check-${{ hashFiles('Cargo.lock') }}
restore-keys: cargo-check- restore-keys: |
cargo-check-
- run: cargo fmt -- --check - run: cargo fmt -- --check
- run: cargo clippy --locked -- -D warnings - run: cargo clippy --locked -- -D warnings
- run: cargo test --locked - run: cargo test --locked
# ========== 多平台构建 ========== build-linux:
build: name: Build (x86_64-unknown-linux-musl)
name: Build (${{ matrix.target }}) needs: [version, probe-runners, check]
needs: [version, check] if: needs.probe-runners.outputs.has_linux == 'true'
continue-on-error: true runs-on: debian
timeout-minutes: 10 timeout-minutes: 10
strategy: continue-on-error: true
fail-fast: false
matrix:
include:
- runner: debian
target: x86_64-unknown-linux-musl
archive_suffix: x86_64-linux-musl
os: linux
- runner: darwin-arm64
target: aarch64-apple-darwin
archive_suffix: aarch64-macos
os: macos
- runner: windows
target: x86_64-pc-windows-msvc
archive_suffix: x86_64-windows
os: windows
runs-on: ${{ matrix.runner }}
steps: steps:
- name: 安装依赖 (Linux) - name: 安装依赖
if: matrix.os == 'linux'
run: | run: |
sudo apt-get update && sudo apt-get install -y pkg-config musl-tools binutils curl sudo apt-get update
if ! command -v cargo &>/dev/null; then sudo apt-get install -y pkg-config musl-tools binutils curl
if ! command -v cargo >/dev/null 2>&1; then
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable
fi fi
source "$HOME/.cargo/env" 2>/dev/null || true source "$HOME/.cargo/env" 2>/dev/null || true
rustup target add ${{ matrix.target }} rustup target add x86_64-unknown-linux-musl
echo "$HOME/.cargo/bin" >> $GITHUB_PATH echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
- name: 安装依赖 (macOS)
if: matrix.os == 'macos'
run: |
if ! command -v cargo &>/dev/null; then
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable
fi
source "$HOME/.cargo/env" 2>/dev/null || true
rustup target add ${{ matrix.target }}
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
- name: 安装依赖 (Windows)
if: matrix.os == 'windows'
shell: pwsh
run: |
if (-not (Get-Command cargo -ErrorAction SilentlyContinue)) {
Invoke-WebRequest -Uri "https://win.rustup.rs/x86_64" -OutFile rustup-init.exe
.\rustup-init.exe -y --default-toolchain stable
Remove-Item rustup-init.exe
}
rustup target add ${{ matrix.target }}
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -183,99 +257,164 @@ jobs:
~/.cargo/registry/cache ~/.cargo/registry/cache
~/.cargo/git/db ~/.cargo/git/db
target target
key: cargo-${{ matrix.target }}-${{ hashFiles('Cargo.lock') }} key: cargo-x86_64-unknown-linux-musl-${{ hashFiles('Cargo.lock') }}
restore-keys: cargo-${{ matrix.target }}- restore-keys: |
cargo-x86_64-unknown-linux-musl-
- name: 构建 - run: cargo build --release --locked --target x86_64-unknown-linux-musl
if: matrix.os != 'windows' - run: strip target/x86_64-unknown-linux-musl/release/${{ env.BINARY_NAME }}
run: cargo build --release --locked --target ${{ matrix.target }}
- name: 构建 (Windows) - name: 上传 Release 产物
if: matrix.os == 'windows' if: needs.version.outputs.release_id != ''
shell: pwsh
run: cargo build --release --locked --target ${{ matrix.target }}
- name: Strip (Linux)
if: matrix.os == 'linux'
run: strip target/${{ matrix.target }}/release/${{ env.BINARY_NAME }}
- name: Strip (macOS)
if: matrix.os == 'macos'
run: strip -x target/${{ matrix.target }}/release/${{ env.BINARY_NAME }}
- name: 上传产物 (Linux/macOS)
if: needs.version.outputs.tag_exists == 'false' && needs.version.outputs.release_id && matrix.os != 'windows'
env: env:
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
run: | run: |
[ -z "$RELEASE_TOKEN" ] && exit 0 [ -z "$RELEASE_TOKEN" ] && exit 0
tag="${{ needs.version.outputs.tag }}" tag="${{ needs.version.outputs.tag }}"
bin="target/${{ matrix.target }}/release/${{ env.BINARY_NAME }}" bin="target/x86_64-unknown-linux-musl/release/${{ env.BINARY_NAME }}"
archive="${{ env.BINARY_NAME }}-${tag}-${{ matrix.archive_suffix }}.tar.gz" archive="${{ env.BINARY_NAME }}-${tag}-x86_64-linux-musl.tar.gz"
tar -czf "$archive" -C "$(dirname $bin)" "$(basename $bin)" tar -czf "$archive" -C "$(dirname "$bin")" "$(basename "$bin")"
curl -sS -H "Authorization: token $RELEASE_TOKEN" \ curl -fsS -H "Authorization: token $RELEASE_TOKEN" \
-F "attachment=@${archive}" \ -F "attachment=@${archive}" \
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases/${{ needs.version.outputs.release_id }}/assets" "${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases/${{ needs.version.outputs.release_id }}/assets"
echo "已上传: ${archive}"
- name: 上传产物 (Windows) build-macos:
if: needs.version.outputs.tag_exists == 'false' && needs.version.outputs.release_id && matrix.os == 'windows' name: Build (aarch64-apple-darwin)
needs: [version, probe-runners, check]
if: needs.probe-runners.outputs.has_macos == 'true'
runs-on: darwin-arm64
timeout-minutes: 10
continue-on-error: true
steps:
- name: 安装依赖
run: |
if ! command -v cargo >/dev/null 2>&1; then
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable
fi
source "$HOME/.cargo/env" 2>/dev/null || true
rustup target add aarch64-apple-darwin
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
- uses: actions/checkout@v4
- name: 缓存 Cargo
uses: actions/cache@v4
with:
path: |
~/.cargo/registry/index
~/.cargo/registry/cache
~/.cargo/git/db
target
key: cargo-aarch64-apple-darwin-${{ hashFiles('Cargo.lock') }}
restore-keys: |
cargo-aarch64-apple-darwin-
- run: cargo build --release --locked --target aarch64-apple-darwin
- run: strip -x target/aarch64-apple-darwin/release/${{ env.BINARY_NAME }}
- name: 上传 Release 产物
if: needs.version.outputs.release_id != ''
env:
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
run: |
[ -z "$RELEASE_TOKEN" ] && exit 0
tag="${{ needs.version.outputs.tag }}"
bin="target/aarch64-apple-darwin/release/${{ env.BINARY_NAME }}"
archive="${{ env.BINARY_NAME }}-${tag}-aarch64-macos.tar.gz"
tar -czf "$archive" -C "$(dirname "$bin")" "$(basename "$bin")"
curl -fsS -H "Authorization: token $RELEASE_TOKEN" \
-F "attachment=@${archive}" \
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases/${{ needs.version.outputs.release_id }}/assets"
build-windows:
name: Build (x86_64-pc-windows-msvc)
needs: [version, probe-runners, check]
if: needs.probe-runners.outputs.has_windows == 'true'
runs-on: windows
timeout-minutes: 10
continue-on-error: true
steps:
- name: 安装依赖
shell: pwsh
run: |
if (-not (Get-Command cargo -ErrorAction SilentlyContinue)) {
Invoke-WebRequest -Uri "https://win.rustup.rs/x86_64" -OutFile rustup-init.exe
.\rustup-init.exe -y --default-toolchain stable
Remove-Item rustup-init.exe
}
rustup target add x86_64-pc-windows-msvc
- uses: actions/checkout@v4
- name: 缓存 Cargo
uses: actions/cache@v4
with:
path: |
~/.cargo/registry/index
~/.cargo/registry/cache
~/.cargo/git/db
target
key: cargo-x86_64-pc-windows-msvc-${{ hashFiles('Cargo.lock') }}
restore-keys: |
cargo-x86_64-pc-windows-msvc-
- name: 构建
shell: pwsh
run: cargo build --release --locked --target x86_64-pc-windows-msvc
- name: 上传 Release 产物
if: needs.version.outputs.release_id != ''
shell: pwsh shell: pwsh
env: env:
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
run: | run: |
if (-not $env:RELEASE_TOKEN) { exit 0 } if (-not $env:RELEASE_TOKEN) { exit 0 }
$tag = "${{ needs.version.outputs.tag }}" $tag = "${{ needs.version.outputs.tag }}"
$bin = "target\${{ matrix.target }}\release\${{ env.BINARY_NAME }}.exe" $bin = "target\x86_64-pc-windows-msvc\release\${{ env.BINARY_NAME }}.exe"
$archive = "${{ env.BINARY_NAME }}-${tag}-${{ matrix.archive_suffix }}.zip" $archive = "${{ env.BINARY_NAME }}-${tag}-x86_64-windows.zip"
Compress-Archive -Path $bin -DestinationPath $archive Compress-Archive -Path $bin -DestinationPath $archive -Force
$url = "${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases/${{ needs.version.outputs.release_id }}/assets" $url = "${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases/${{ needs.version.outputs.release_id }}/assets"
Invoke-RestMethod -Uri $url -Method Post ` Invoke-RestMethod -Uri $url -Method Post `
-Headers @{ "Authorization" = "token $env:RELEASE_TOKEN" } ` -Headers @{ "Authorization" = "token $env:RELEASE_TOKEN" } `
-Form @{ attachment = Get-Item $archive } -Form @{ attachment = Get-Item $archive }
Write-Host "已上传: ${archive}"
# ========== 通知 ==========
notify: notify:
name: 通知 name: 通知
needs: [version, build] needs: [version, probe-runners]
if: always() && github.event_name == 'push' if: always() && github.event_name == 'push'
runs-on: debian runs-on: debian
timeout-minutes: 1
continue-on-error: true
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: 发送飞书通知 - name: 发送飞书通知
continue-on-error: true
env: env:
WEBHOOK_URL: ${{ vars.WEBHOOK_URL }} WEBHOOK_URL: ${{ vars.WEBHOOK_URL }}
run: | run: |
[ -z "$WEBHOOK_URL" ] && exit 0 [ -z "$WEBHOOK_URL" ] && exit 0
command -v jq &>/dev/null || (sudo apt-get update -qq && sudo apt-get install -y -qq jq) command -v jq >/dev/null 2>&1 || (sudo apt-get update -qq && sudo apt-get install -y -qq jq)
tag="${{ needs.version.outputs.tag }}" tag="${{ needs.version.outputs.tag }}"
tag_exists="${{ needs.version.outputs.tag_exists }}" tag_exists="${{ needs.version.outputs.tag_exists }}"
result="${{ needs.build.result }}"
[ "$result" = "success" ] && status="构建成功 ✅" || status="构建失败 ❌"
commit=$(git log -1 --pretty=format:"%s" 2>/dev/null || echo "N/A") commit=$(git log -1 --pretty=format:"%s" 2>/dev/null || echo "N/A")
url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_number }}" url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_number }}"
if [ "$result" != "success" ]; then msg="${{ env.BINARY_NAME }} 通知"
extra="" if [ "$tag_exists" = "false" ]; then
elif [ "$tag_exists" = "false" ]; then msg="${msg}
extra="🆕 新版本已发布 (linux / macOS / windows)" 🆕 已创建版本标签并开始构建发布"
else else
extra="🔄 重复构建" msg="${msg}
🔄 检测到已有版本标签,已开始重复构建"
fi fi
msg="${{ env.BINARY_NAME }} ${status}"
[ -n "$extra" ] && msg="${msg}
${extra}"
msg="${msg} msg="${msg}
Runner 可用性linux=${{ needs.probe-runners.outputs.has_linux }}, macOS=${{ needs.probe-runners.outputs.has_macos }}, windows=${{ needs.probe-runners.outputs.has_windows }}
提交:${commit} 提交:${commit}
版本:${tag} 版本:${tag}
作者:${{ github.actor }} 作者:${{ github.actor }}
详情:${url}" 详情:${url}"
payload=$(jq -n --arg text "$msg" '{msg_type:"text",content:{text:$text}}') payload=$(jq -n --arg text "$msg" '{msg_type: "text", content: {text: $text}}')
curl -sS -H "Content-Type: application/json" -X POST -d "$payload" "$WEBHOOK_URL" curl -fsS -H "Content-Type: application/json" -X POST -d "$payload" "$WEBHOOK_URL"