Some checks failed
Secrets CLI - Build & Release / 探测 Runner (push) Successful in 1s
Secrets CLI - Build & Release / Build (aarch64-apple-darwin) (push) Has been skipped
Secrets CLI - Build & Release / Build (x86_64-unknown-linux-musl) (push) Has been skipped
Secrets CLI - Build & Release / 版本 & Release (push) Successful in 2s
Secrets CLI - Build & Release / 质量检查 (fmt / clippy / test) (push) Successful in 34s
Secrets CLI - Build & Release / 发布草稿 Release (push) Has been cancelled
Secrets CLI - Build & Release / 通知 (push) Has been cancelled
Secrets CLI - Build & Release / Build (x86_64-pc-windows-msvc) (push) Has been cancelled
- update.rs: sqlx::query! 改为 query/query_as,不依赖编译期 DB - workflow: build job 加 always() 且 check.result==success,失败时 notify 能执行 Made-with: Cursor
482 lines
17 KiB
YAML
482 lines
17 KiB
YAML
name: Secrets CLI - Build & Release
|
||
|
||
on:
|
||
push:
|
||
branches: [main]
|
||
paths:
|
||
- 'src/**'
|
||
- 'Cargo.toml'
|
||
- 'Cargo.lock'
|
||
- '.gitea/workflows/secrets.yml'
|
||
|
||
concurrency:
|
||
group: ${{ github.workflow }}-${{ github.ref }}
|
||
cancel-in-progress: true
|
||
|
||
permissions:
|
||
contents: write
|
||
|
||
env:
|
||
BINARY_NAME: secrets
|
||
CARGO_INCREMENTAL: 0
|
||
CARGO_NET_RETRY: 10
|
||
CARGO_TERM_COLOR: always
|
||
RUST_BACKTRACE: short
|
||
|
||
jobs:
|
||
version:
|
||
name: 版本 & Release
|
||
runs-on: debian
|
||
outputs:
|
||
version: ${{ steps.ver.outputs.version }}
|
||
tag: ${{ steps.ver.outputs.tag }}
|
||
tag_exists: ${{ steps.ver.outputs.tag_exists }}
|
||
release_id: ${{ steps.release.outputs.release_id }}
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
with:
|
||
fetch-depth: 0
|
||
|
||
- name: 解析版本
|
||
id: ver
|
||
run: |
|
||
version=$(grep -m1 '^version' Cargo.toml | sed 's/.*"\(.*\)".*/\1/')
|
||
tag="secrets-${version}"
|
||
previous_tag=$(git tag --list 'secrets-*' --sort=-v:refname | awk -v tag="$tag" '$0 != tag { print; exit }')
|
||
|
||
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
|
||
echo "tag_exists=true" >> "$GITHUB_OUTPUT"
|
||
echo "版本 ${tag} 已存在"
|
||
else
|
||
echo "tag_exists=false" >> "$GITHUB_OUTPUT"
|
||
echo "将创建新版本 ${tag}"
|
||
fi
|
||
|
||
- name: 创建 Tag
|
||
if: steps.ver.outputs.tag_exists == 'false'
|
||
run: |
|
||
git config user.name "github-actions[bot]"
|
||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||
git tag -a "${{ steps.ver.outputs.tag }}" -m "Release ${{ steps.ver.outputs.tag }}"
|
||
git push origin "${{ steps.ver.outputs.tag }}"
|
||
|
||
- name: 解析或创建 Release
|
||
id: release
|
||
env:
|
||
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||
run: |
|
||
if [ -z "$RELEASE_TOKEN" ]; then
|
||
echo "release_id=" >> "$GITHUB_OUTPUT"
|
||
exit 0
|
||
fi
|
||
|
||
command -v jq >/dev/null 2>&1 || (sudo apt-get update -qq && sudo apt-get install -y -qq jq)
|
||
|
||
tag="${{ steps.ver.outputs.tag }}"
|
||
version="${{ steps.ver.outputs.version }}"
|
||
release_api="${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases"
|
||
|
||
http_code=$(curl -sS -o /tmp/release.json -w '%{http_code}' \
|
||
-H "Authorization: token $RELEASE_TOKEN" \
|
||
"${release_api}/tags/${tag}")
|
||
|
||
if [ "$http_code" = "200" ]; then
|
||
release_id=$(jq -r '.id // empty' /tmp/release.json)
|
||
if [ -n "$release_id" ]; then
|
||
echo "已找到现有 Release: ${release_id}"
|
||
echo "release_id=${release_id}" >> "$GITHUB_OUTPUT"
|
||
exit 0
|
||
fi
|
||
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
|
||
[ -z "$changes" ] && changes="- 首次发布"
|
||
|
||
body=$(printf '## 变更日志\n\n%s' "$changes")
|
||
|
||
payload=$(jq -n \
|
||
--arg tag "$tag" \
|
||
--arg name "${{ env.BINARY_NAME }} ${version}" \
|
||
--arg body "$body" \
|
||
'{tag_name: $tag, name: $name, body: $body, draft: true}')
|
||
|
||
http_code=$(curl -sS -o /tmp/create-release.json -w '%{http_code}' \
|
||
-H "Authorization: token $RELEASE_TOKEN" \
|
||
-H "Content-Type: application/json" \
|
||
-X POST "$release_api" \
|
||
-d "$payload")
|
||
|
||
if [ "$http_code" = "201" ] || [ "$http_code" = "200" ]; then
|
||
release_id=$(jq -r '.id // empty' /tmp/create-release.json)
|
||
fi
|
||
|
||
if [ -n "$release_id" ]; then
|
||
echo "已创建草稿 Release: ${release_id}"
|
||
echo "release_id=${release_id}" >> "$GITHUB_OUTPUT"
|
||
else
|
||
echo "⚠ 创建 Release 失败 (HTTP ${http_code}),跳过产物上传"
|
||
cat /tmp/create-release.json 2>/dev/null || true
|
||
echo "release_id=" >> "$GITHUB_OUTPUT"
|
||
fi
|
||
|
||
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: |
|
||
fallback_all() {
|
||
echo "has_linux=true" >> "$GITHUB_OUTPUT"
|
||
echo "has_macos=true" >> "$GITHUB_OUTPUT"
|
||
echo "has_windows=true" >> "$GITHUB_OUTPUT"
|
||
}
|
||
|
||
if [ -z "$RELEASE_TOKEN" ]; then
|
||
echo "未配置 RELEASE_TOKEN,默认尝试所有平台"
|
||
fallback_all; exit 0
|
||
fi
|
||
|
||
command -v jq >/dev/null 2>&1 || (sudo apt-get update -qq && sudo apt-get install -y -qq jq)
|
||
|
||
http_code=$(curl -sS -o /tmp/runners.json -w '%{http_code}' \
|
||
-H "Authorization: token $RELEASE_TOKEN" \
|
||
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/actions/runners")
|
||
|
||
if [ "$http_code" != "200" ]; then
|
||
echo "Runner API 返回 HTTP ${http_code},默认尝试所有平台"
|
||
fallback_all; exit 0
|
||
fi
|
||
|
||
has_runner() {
|
||
local label="$1"
|
||
jq -e --arg label "$label" '
|
||
(.runners // .data // . // [])
|
||
| any(
|
||
(
|
||
(.online == true)
|
||
or (
|
||
((.status // "") | ascii_downcase)
|
||
| IN("online", "idle", "busy", "active")
|
||
)
|
||
)
|
||
and (
|
||
(.labels // [])
|
||
| map(if type == "object" then (.name // .label // "") else tostring end | ascii_downcase)
|
||
| index($label)
|
||
) != null
|
||
)
|
||
' /tmp/runners.json >/dev/null
|
||
}
|
||
|
||
for pair in "debian:has_linux" "darwin-arm64:has_macos" "windows:has_windows"; do
|
||
label="$(printf '%s' "${pair%%:*}" | tr '[:upper:]' '[:lower:]')"; key="${pair##*:}"
|
||
if has_runner "$label"; then
|
||
echo "${key}=true" >> "$GITHUB_OUTPUT"
|
||
else
|
||
echo "${key}=false" >> "$GITHUB_OUTPUT"
|
||
fi
|
||
done
|
||
|
||
check:
|
||
name: 质量检查 (fmt / clippy / test)
|
||
runs-on: debian
|
||
timeout-minutes: 10
|
||
steps:
|
||
- name: 安装 Rust
|
||
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
|
||
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
|
||
fi
|
||
source "$HOME/.cargo/env" 2>/dev/null || true
|
||
rustup component add rustfmt clippy
|
||
|
||
- uses: actions/checkout@v4
|
||
|
||
- name: 缓存 Cargo
|
||
uses: actions/cache@v4
|
||
with:
|
||
path: |
|
||
~/.cargo/registry/index
|
||
~/.cargo/registry/cache
|
||
~/.cargo/git/db
|
||
target
|
||
key: cargo-check-${{ hashFiles('Cargo.lock') }}
|
||
restore-keys: |
|
||
cargo-check-
|
||
|
||
- run: cargo fmt -- --check
|
||
- run: cargo clippy --locked -- -D warnings
|
||
- run: cargo test --locked
|
||
|
||
build-linux:
|
||
name: Build (x86_64-unknown-linux-musl)
|
||
needs: [version, probe-runners, check]
|
||
if: always() && needs.check.result == 'success' && needs.probe-runners.outputs.has_linux == 'true'
|
||
runs-on: debian
|
||
timeout-minutes: 1
|
||
steps:
|
||
- name: 安装依赖
|
||
run: |
|
||
sudo apt-get update
|
||
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
|
||
fi
|
||
source "$HOME/.cargo/env" 2>/dev/null || true
|
||
rustup target add x86_64-unknown-linux-musl
|
||
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-x86_64-unknown-linux-musl-${{ hashFiles('Cargo.lock') }}
|
||
restore-keys: |
|
||
cargo-x86_64-unknown-linux-musl-
|
||
|
||
- run: cargo build --release --locked --target x86_64-unknown-linux-musl
|
||
- run: strip target/x86_64-unknown-linux-musl/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/x86_64-unknown-linux-musl/release/${{ env.BINARY_NAME }}"
|
||
archive="${{ env.BINARY_NAME }}-${tag}-x86_64-linux-musl.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-macos:
|
||
name: Build (aarch64-apple-darwin)
|
||
needs: [version, probe-runners, check]
|
||
if: always() && needs.check.result == 'success' && needs.probe-runners.outputs.has_macos == 'true'
|
||
runs-on: darwin-arm64
|
||
timeout-minutes: 1
|
||
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: always() && needs.check.result == 'success' && needs.probe-runners.outputs.has_windows == 'true'
|
||
runs-on: windows
|
||
timeout-minutes: 1
|
||
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
|
||
env:
|
||
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||
run: |
|
||
if (-not $env:RELEASE_TOKEN) { exit 0 }
|
||
$tag = "${{ needs.version.outputs.tag }}"
|
||
$bin = "target\x86_64-pc-windows-msvc\release\${{ env.BINARY_NAME }}.exe"
|
||
$archive = "${{ env.BINARY_NAME }}-${tag}-x86_64-windows.zip"
|
||
Compress-Archive -Path $bin -DestinationPath $archive -Force
|
||
$url = "${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases/${{ needs.version.outputs.release_id }}/assets"
|
||
Invoke-RestMethod -Uri $url -Method Post `
|
||
-Headers @{ "Authorization" = "token $env:RELEASE_TOKEN" } `
|
||
-Form @{ attachment = Get-Item $archive }
|
||
|
||
publish-release:
|
||
name: 发布草稿 Release
|
||
needs: [version, check, build-linux, build-macos, build-windows]
|
||
if: always() && needs.version.outputs.release_id != ''
|
||
runs-on: debian
|
||
timeout-minutes: 2
|
||
steps:
|
||
- name: 发布草稿
|
||
env:
|
||
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||
run: |
|
||
[ -z "$RELEASE_TOKEN" ] && exit 0
|
||
|
||
version_r="${{ needs.version.result }}"
|
||
check_r="${{ needs.check.result }}"
|
||
linux_r="${{ needs.build-linux.result }}"
|
||
macos_r="${{ needs.build-macos.result }}"
|
||
windows_r="${{ needs.build-windows.result }}"
|
||
|
||
for result in "$version_r" "$check_r" "$linux_r" "$macos_r" "$windows_r"; do
|
||
case "$result" in
|
||
success|skipped) ;;
|
||
*)
|
||
echo "存在失败或取消的 job,保留草稿 Release"
|
||
exit 0
|
||
;;
|
||
esac
|
||
done
|
||
|
||
release_api="${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases/${{ needs.version.outputs.release_id }}"
|
||
http_code=$(curl -sS -o /tmp/publish-release.json -w '%{http_code}' \
|
||
-H "Authorization: token $RELEASE_TOKEN" \
|
||
-H "Content-Type: application/json" \
|
||
-X PATCH "$release_api" \
|
||
-d '{"draft":false}')
|
||
|
||
if [ "$http_code" != "200" ]; then
|
||
echo "发布草稿 Release 失败 (HTTP ${http_code})"
|
||
cat /tmp/publish-release.json 2>/dev/null || true
|
||
exit 1
|
||
fi
|
||
|
||
notify:
|
||
name: 通知
|
||
needs: [version, probe-runners, check, build-linux, build-macos, build-windows, publish-release]
|
||
if: always() && github.event_name == 'push'
|
||
runs-on: debian
|
||
timeout-minutes: 1
|
||
continue-on-error: true
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
|
||
- name: 发送飞书通知
|
||
env:
|
||
WEBHOOK_URL: ${{ vars.WEBHOOK_URL }}
|
||
run: |
|
||
[ -z "$WEBHOOK_URL" ] && exit 0
|
||
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_exists="${{ needs.version.outputs.tag_exists }}"
|
||
commit=$(git log -1 --pretty=format:"%s" 2>/dev/null || echo "N/A")
|
||
url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_number }}"
|
||
|
||
version_r="${{ needs.version.result }}"
|
||
check_r="${{ needs.check.result }}"
|
||
linux_r="${{ needs.build-linux.result }}"
|
||
macos_r="${{ needs.build-macos.result }}"
|
||
windows_r="${{ needs.build-windows.result }}"
|
||
publish_r="${{ needs.publish-release.result }}"
|
||
|
||
if [ "$version_r" = "success" ] && [ "$check_r" = "success" ] \
|
||
&& [ "$linux_r" != "failure" ] && [ "$linux_r" != "cancelled" ] \
|
||
&& [ "$macos_r" != "failure" ] && [ "$macos_r" != "cancelled" ] \
|
||
&& [ "$windows_r" != "failure" ] && [ "$windows_r" != "cancelled" ] \
|
||
&& [ "$publish_r" != "failure" ] && [ "$publish_r" != "cancelled" ]; then
|
||
status="构建成功 ✅"
|
||
else
|
||
status="构建失败 ❌"
|
||
fi
|
||
|
||
icon() {
|
||
case "$1" in
|
||
success) echo "✅" ;;
|
||
skipped) echo "⏭" ;;
|
||
*) echo "❌" ;;
|
||
esac
|
||
}
|
||
|
||
msg="${{ env.BINARY_NAME }} ${status}"
|
||
if [ "$tag_exists" = "false" ]; then
|
||
msg="${msg}
|
||
🆕 新版本 ${tag}"
|
||
else
|
||
msg="${msg}
|
||
🔄 重复构建 ${tag}"
|
||
fi
|
||
|
||
msg="${msg}
|
||
构建结果:linux$(icon "$linux_r") macOS$(icon "$macos_r") windows$(icon "$windows_r")
|
||
Release:$(icon "$publish_r")
|
||
提交:${commit}
|
||
作者:${{ github.actor }}
|
||
详情:${url}"
|
||
|
||
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"
|