From 6bf631a3747ff2293ecc41764f37ea05aa2237f9 Mon Sep 17 00:00:00 2001 From: Brendan Ryan <1572504+brendanjryan@users.noreply.github.com> Date: Wed, 26 Aug 2026 20:44:56 +0000 Subject: [PATCH] fix(tempoup): exclude draft releases from latest lookup Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com> --- tempoup/tempoup | 40 ++++++++++++++++++++++++-- tempoup/test-tempoup.sh | 62 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 98 insertions(+), 4 deletions(-) diff --git a/tempoup/tempoup b/tempoup/tempoup index 2ed58fe51d..5b76ce6bc6 100755 --- a/tempoup/tempoup +++ b/tempoup/tempoup @@ -6,7 +6,7 @@ set -e # NOTE: if you make modifications to this script, please increment the version number. # WARNING: the SemVer pattern: major.minor.patch must be followed as we use it to determine if the script is up to date. -TEMPOUP_INSTALLER_VERSION="0.0.14" +TEMPOUP_INSTALLER_VERSION="0.0.15" REPO="tempoxyz/tempo" # GPG key fingerprint for release signing verification @@ -433,7 +433,14 @@ get_latest_version() { # Prefer authenticated gh to avoid the 60 req/hr anonymous rate-limit. local version if gh_authenticated; then - version=$(gh release list --repo "$REPO" --limit 100 --json tagName --jq '.[].tagName' 2>/dev/null | \ + # Authenticated users can see draft releases, but their assets are not + # available through the public download URLs used below. Exclude drafts + # explicitly, and retain the JSON filter as a defense against changes in + # gh's flag behavior. + version=$(gh release list --repo "$REPO" --limit 100 \ + --exclude-drafts --exclude-pre-releases \ + --json tagName,isDraft,isPrerelease \ + --jq '.[] | select(.isDraft == false and .isPrerelease == false) | .tagName' 2>/dev/null | \ grep '^v[0-9]*\.[0-9]*\.[0-9]*$' | head -n 1) else local api_url="https://api.github.com/repos/$REPO/releases?per_page=100" @@ -449,6 +456,33 @@ get_latest_version() { echo "$version" } +# Fail with an actionable error before downloading when an authenticated GitHub +# lookup can prove that a release is private or missing required assets. +validate_release_assets() { + local tag="$1" + shift + + gh_authenticated || return 0 + + local release_info + if ! release_info=$(gh release view "$tag" --repo "$REPO" \ + --json isDraft,assets \ + --jq '["draft=" + (.isDraft | tostring), (.assets[].name)] | .[]' 2>/dev/null); then + error "Release $tag was not found on GitHub. Choose a published release with 'tempoup -i '." + fi + + if [[ "$(printf '%s\n' "$release_info" | head -n 1)" == "draft=true" ]]; then + error "Release $tag is still a draft and cannot be installed. Choose the latest published release with 'tempoup -i '." + fi + + local asset + for asset in "$@"; do + if ! printf '%s\n' "$release_info" | tail -n +2 | grep -Fxq "$asset"; then + error "Release $tag does not contain required asset $asset. Choose a compatible published release with 'tempoup -i '." + fi + done +} + # Main installation logic main() { # Check if tempoup installer is up to date @@ -488,6 +522,8 @@ main() { ARCHIVE_NAME="tempo-${VERSION_TAG}-${TARGET}.${ARCHIVE_EXT}" + validate_release_assets "$VERSION_TAG" "$ARCHIVE_NAME" "${ARCHIVE_NAME}.sha256" + # Create temporary directory TMP_DIR=$(mktemp -d) trap 'rm -rf "$TMP_DIR"' EXIT diff --git a/tempoup/test-tempoup.sh b/tempoup/test-tempoup.sh index c892c4a207..bd0fcc4673 100755 --- a/tempoup/test-tempoup.sh +++ b/tempoup/test-tempoup.sh @@ -31,13 +31,13 @@ contains() { } log_contains() { - if [[ ! -f "$1" ]] || ! grep -qF "$2" "$1"; then + if [[ ! -f "$1" ]] || ! grep -qF -- "$2" "$1"; then fail "expected $1 to contain $2" fi } log_lacks() { - [[ ! -f "$1" ]] || ! grep -qF "$2" "$1" || fail "expected $1 not to contain $2" + [[ ! -f "$1" ]] || ! grep -qF -- "$2" "$1" || fail "expected $1 not to contain $2" } run_source() { @@ -91,6 +91,32 @@ FAKE_BREW chmod +x "$1/brew" } +fake_gh() { + mkdir -p "$1" + cat > "$1/gh" <<'FAKE_GH' +#!/usr/bin/env bash +printf '%s\n' "$*" >> "$GH_LOG" +case "$1 $2" in + "auth status") exit 0 ;; + "release list") + [[ " $* " == *" --exclude-drafts "* ]] || exit 20 + [[ " $* " == *" --exclude-pre-releases "* ]] || exit 21 + printf 'v1.13.1\n' + ;; + "release view") + case "$GH_RELEASE_STATE" in + draft) printf 'draft=true\ntempo-v1.13.2-aarch64-apple-darwin.tar.gz\ntempo-v1.13.2-aarch64-apple-darwin.tar.gz.sha256\n' ;; + missing) printf 'draft=false\ntempo-v1.13.1-aarch64-apple-darwin.tar.gz\n' ;; + published) printf 'draft=false\ntempo-v1.13.1-aarch64-apple-darwin.tar.gz\ntempo-v1.13.1-aarch64-apple-darwin.tar.gz.sha256\n' ;; + *) exit 22 ;; + esac + ;; + *) exit 23 ;; +esac +FAKE_GH + chmod +x "$1/gh" +} + setup_case() { CASE_DIR="$TMP_ROOT/$1" FAKE_BIN="$CASE_DIR/bin" @@ -174,6 +200,38 @@ fi contains "$output" "could not launch because libusb is missing" ok "tempo launch verification catches libusb failure" +GH_CASE_DIR="$TMP_ROOT/gh-release-selection" +GH_BIN="$GH_CASE_DIR/bin" +GH_LOG="$GH_CASE_DIR/gh.log" +mkdir -p "$GH_CASE_DIR" +fake_gh "$GH_BIN" + +latest="$(GH_LOG="$GH_LOG" PATH="$GH_BIN:$PATH" run_tempoup 'get_latest_version')" +[[ "$latest" == "v1.13.1" ]] || fail "latest release selection returned $latest" +log_contains "$GH_LOG" "--exclude-drafts" +log_contains "$GH_LOG" "--exclude-pre-releases" +ok "latest release selection excludes drafts and prereleases" + +if output="$(GH_LOG="$GH_LOG" GH_RELEASE_STATE=draft PATH="$GH_BIN:$PATH" run_tempoup \ + 'validate_release_assets v1.13.2 tempo-v1.13.2-aarch64-apple-darwin.tar.gz tempo-v1.13.2-aarch64-apple-darwin.tar.gz.sha256' 2>&1)"; then + printf '%s\n' "$output" >&2 + fail "draft release validation succeeded" +fi +contains "$output" "still a draft" +ok "draft release validation fails clearly" + +if output="$(GH_LOG="$GH_LOG" GH_RELEASE_STATE=missing PATH="$GH_BIN:$PATH" run_tempoup \ + 'validate_release_assets v1.13.1 tempo-v1.13.1-aarch64-apple-darwin.tar.gz tempo-v1.13.1-aarch64-apple-darwin.tar.gz.sha256' 2>&1)"; then + printf '%s\n' "$output" >&2 + fail "release with missing checksum validation succeeded" +fi +contains "$output" "does not contain required asset" +ok "release asset validation catches missing files" + +GH_LOG="$GH_LOG" GH_RELEASE_STATE=published PATH="$GH_BIN:$PATH" run_tempoup \ + 'validate_release_assets v1.13.1 tempo-v1.13.1-aarch64-apple-darwin.tar.gz tempo-v1.13.1-aarch64-apple-darwin.tar.gz.sha256' +ok "published release asset validation succeeds" + run_install ' should_verify_tempo_after_tempoup should_verify_tempo_after_tempoup --unsafe-skip-verify