Files
WeFlow/.github/scripts/release-utils.sh
2026-04-17 22:07:40 +08:00

213 lines
5.8 KiB
Bash

#!/usr/bin/env bash
set -euo pipefail
retry_cmd() {
local attempts="$1"
local delay_seconds="$2"
shift 2
local i
local exit_code
for ((i = 1; i <= attempts; i++)); do
if "$@"; then
return 0
fi
exit_code=$?
if [ "$i" -lt "$attempts" ]; then
echo "Command failed (attempt $i/$attempts, exit=$exit_code): $*" >&2
echo "Retrying in ${delay_seconds}s..." >&2
sleep "$delay_seconds"
fi
done
echo "Command failed after $attempts attempts: $*" >&2
return "$exit_code"
}
capture_cmd_with_retry() {
local result_var="$1"
local attempts="$2"
local delay_seconds="$3"
shift 3
local i
local output=""
local exit_code=1
for ((i = 1; i <= attempts; i++)); do
if output="$("$@" 2>/dev/null)"; then
printf -v "$result_var" "%s" "$output"
return 0
fi
exit_code=$?
if [ "$i" -lt "$attempts" ]; then
echo "Capture command failed (attempt $i/$attempts, exit=$exit_code): $*" >&2
echo "Retrying in ${delay_seconds}s..." >&2
sleep "$delay_seconds"
fi
done
echo "Capture command failed after $attempts attempts: $*" >&2
return "$exit_code"
}
wait_for_release_id() {
local repo="$1"
local tag="$2"
local attempts="${3:-12}"
local delay_seconds="${4:-2}"
local i
local release_id
for ((i = 1; i <= attempts; i++)); do
release_id="$(gh api "repos/$repo/releases/tags/$tag" --jq '.id' 2>/dev/null || true)"
if [[ "$release_id" =~ ^[0-9]+$ ]]; then
echo "$release_id"
return 0
fi
if [ "$i" -lt "$attempts" ]; then
echo "Release id for tag '$tag' is not ready yet (attempt $i/$attempts), retrying in ${delay_seconds}s..." >&2
sleep "$delay_seconds"
fi
done
echo "Unable to fetch release id for tag '$tag' after $attempts attempts." >&2
gh api "repos/$repo/releases/tags/$tag" --jq '{draft: .draft, prerelease: .prerelease, url: .html_url}' 2>/dev/null || true
return 1
}
settle_release_state() {
local repo="$1"
local release_id="$2"
local tag="$3"
local attempts="${4:-12}"
local delay_seconds="${5:-2}"
local endpoint="repos/$repo/releases/tags/$tag"
local i
local draft_state
local prerelease_state
for ((i = 1; i <= attempts; i++)); do
gh api --method PATCH "repos/$repo/releases/$release_id" -F draft=false -F prerelease=true >/dev/null 2>&1 || true
draft_state="$(gh api "$endpoint" --jq '.draft' 2>/dev/null || echo true)"
prerelease_state="$(gh api "$endpoint" --jq '.prerelease' 2>/dev/null || echo false)"
if [ "$draft_state" = "false" ] && [ "$prerelease_state" = "true" ]; then
return 0
fi
if [ "$i" -lt "$attempts" ]; then
echo "Release '$tag' state not settled yet (attempt $i/$attempts), retrying in ${delay_seconds}s..." >&2
sleep "$delay_seconds"
fi
done
echo "Failed to settle release state for tag '$tag'." >&2
gh api "$endpoint" --jq '{draft: .draft, prerelease: .prerelease, url: .html_url}' 2>/dev/null || true
return 1
}
wait_for_release_absent() {
local repo="$1"
local tag="$2"
local attempts="${3:-12}"
local delay_seconds="${4:-2}"
local i
for ((i = 1; i <= attempts; i++)); do
if gh release view "$tag" --repo "$repo" >/dev/null 2>&1; then
if [ "$i" -lt "$attempts" ]; then
echo "Release '$tag' still exists (attempt $i/$attempts), waiting ${delay_seconds}s..." >&2
sleep "$delay_seconds"
fi
continue
fi
return 0
done
echo "Release '$tag' still exists after waiting." >&2
gh release view "$tag" --repo "$repo" --json url,isDraft,isPrerelease 2>/dev/null || true
return 1
}
wait_for_git_tag_absent() {
local repo="$1"
local tag="$2"
local attempts="${3:-12}"
local delay_seconds="${4:-2}"
local i
for ((i = 1; i <= attempts; i++)); do
if gh api "repos/$repo/git/ref/tags/$tag" >/dev/null 2>&1; then
if [ "$i" -lt "$attempts" ]; then
echo "Git tag '$tag' still exists (attempt $i/$attempts), waiting ${delay_seconds}s..." >&2
sleep "$delay_seconds"
fi
continue
fi
return 0
done
echo "Git tag '$tag' still exists after waiting." >&2
gh api "repos/$repo/git/ref/tags/$tag" --jq '{ref: .ref, object: .object.sha}' 2>/dev/null || true
return 1
}
recreate_fixed_prerelease() {
local repo="$1"
local tag="$2"
local target_branch="$3"
local release_title="$4"
local release_notes="$5"
if gh release view "$tag" --repo "$repo" >/dev/null 2>&1; then
retry_cmd 5 3 gh release delete "$tag" --repo "$repo" --yes --cleanup-tag
fi
wait_for_release_absent "$repo" "$tag" 12 2
if gh api "repos/$repo/git/ref/tags/$tag" >/dev/null 2>&1; then
retry_cmd 5 2 gh api --method DELETE "repos/$repo/git/refs/tags/$tag"
fi
wait_for_git_tag_absent "$repo" "$tag" 12 2
local created="false"
local i
for ((i = 1; i <= 6; i++)); do
if gh release create "$tag" --repo "$repo" --title "$release_title" --notes "$release_notes" --prerelease --target "$target_branch"; then
created="true"
break
fi
if gh release view "$tag" --repo "$repo" >/dev/null 2>&1; then
echo "Release '$tag' appears to exist after create failure; continue to settle state." >&2
created="true"
break
fi
if [ "$i" -lt 6 ]; then
echo "Create release '$tag' failed (attempt $i/6), retrying in 3s..." >&2
sleep 3
fi
done
if [ "$created" != "true" ]; then
echo "Failed to create release '$tag'." >&2
return 1
fi
local release_id
release_id="$(wait_for_release_id "$repo" "$tag" 12 2)"
settle_release_state "$repo" "$release_id" "$tag" 12 2
}
upload_release_assets_with_retry() {
local repo="$1"
local tag="$2"
shift 2
if [ "$#" -eq 0 ]; then
echo "No release assets provided for upload." >&2
return 1
fi
wait_for_release_id "$repo" "$tag" 12 2 >/dev/null
retry_cmd 5 3 gh release upload "$tag" "$@" --repo "$repo" --clobber
}