lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

commit 4680451fedb74b22521b702db9c1ef6e7fbc8b15
parent e8dd75458bd8b83e5ba65211e85067f2a8429a60
Author: triesap <tyson@radroots.org>
Date:   Tue,  3 Mar 2026 16:12:48 +0000

ci: harden crates publish workflow retries

Diffstat:
M.github/workflows/publish-crates.yml | 17++++++-----------
Mscripts/ci/release_publish_order.sh | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
2 files changed, 83 insertions(+), 13 deletions(-)

diff --git a/.github/workflows/publish-crates.yml b/.github/workflows/publish-crates.yml @@ -58,18 +58,13 @@ jobs: dry_run="${DRY_RUN_INPUT:-false}" fi - mode="publish" + mode="--publish" if [ "${dry_run}" = "true" ]; then - mode="dry-run" + mode="--dry-run" fi - if [ "${mode}" = "publish" ] && [ -z "${CARGO_REGISTRY_TOKEN:-}" ] && [ -n "${CRATES_IO_TOKEN:-}" ]; then - export CARGO_REGISTRY_TOKEN="${CRATES_IO_TOKEN}" + if [ -n "${crates_input}" ]; then + ./publish-crates.sh "${mode}" "${crates_input}" + else + ./publish-crates.sh "${mode}" fi - - if [ "${mode}" = "publish" ] && [ -z "${CARGO_REGISTRY_TOKEN:-}" ]; then - echo "set CRATES_IO_TOKEN or CARGO_REGISTRY_TOKEN for publish runs" - exit 1 - fi - - ./scripts/ci/release_publish_order.sh "${mode}" "${crates_input}" diff --git a/scripts/ci/release_publish_order.sh b/scripts/ci/release_publish_order.sh @@ -50,6 +50,76 @@ selected_file="$(mktemp)" requested_file="$(mktemp)" trap 'rm -f "$order_file" "$selected_file" "$requested_file"' EXIT +crate_version_visible() { + local crate="$1" + curl -fsSL "https://crates.io/api/v1/crates/${crate}/${release_version}" >/dev/null 2>&1 +} + +seconds_until_http_date() { + local retry_after="$1" + python3 - "$retry_after" <<'PY' +import datetime +import email.utils +import sys + +retry_after = sys.argv[1].strip() +try: + target = email.utils.parsedate_to_datetime(retry_after) +except Exception: + print(0) + raise SystemExit(0) + +if target.tzinfo is None: + target = target.replace(tzinfo=datetime.timezone.utc) + +now = datetime.datetime.now(datetime.timezone.utc) +remaining = (target - now).total_seconds() +print(max(1, int(remaining) + 1)) +PY +} + +publish_with_retry() { + local crate="$1" + local attempt=1 + while true; do + local log_file + log_file="$(mktemp)" + if cargo publish --locked -p "$crate" >"$log_file" 2>&1; then + cat "$log_file" + rm -f "$log_file" + return 0 + fi + + cat "$log_file" + + if grep -Fq "already uploaded" "$log_file"; then + echo "crate ${crate} version ${release_version} is already uploaded" + rm -f "$log_file" + return 0 + fi + + if grep -Fq "429 Too Many Requests" "$log_file"; then + local retry_after + retry_after="$(sed -n 's/.*Please try again after \(.*GMT\).*/\1/p' "$log_file" | head -n1)" + local sleep_secs=0 + if [[ -n "$retry_after" ]]; then + sleep_secs="$(seconds_until_http_date "$retry_after")" + fi + if [[ "$sleep_secs" -le 0 ]]; then + sleep_secs=$((30 + attempt * 15)) + fi + echo "publish rate-limited for ${crate}; retry ${attempt} in ${sleep_secs}s" + rm -f "$log_file" + sleep "$sleep_secs" + attempt=$((attempt + 1)) + continue + fi + + rm -f "$log_file" + return 1 + done +} + if [[ -n "$requested_raw" ]]; then for token in $requested_raw; do [[ -n "$token" ]] || continue @@ -96,9 +166,14 @@ while IFS= read -r crate; do exit 1 fi - cargo publish --locked -p "$crate" + if crate_version_visible "$crate"; then + echo "skip ${crate}: version ${release_version} already visible on crates.io" + continue + fi + + publish_with_retry "$crate" for attempt in $(seq 1 30); do - if curl -fsSL "https://crates.io/api/v1/crates/${crate}/${release_version}" >/dev/null 2>&1; then + if crate_version_visible "$crate"; then break fi if [[ "$attempt" == "30" ]]; then