diff --git a/lib/forge.func b/lib/forge.func index 1f88799..0dc3416 100644 --- a/lib/forge.func +++ b/lib/forge.func @@ -406,6 +406,8 @@ extract_version_from_json() { # Returns: # Latest tag name (stdout), or returns 1 on failure # ------------------------------------------------------------------------------ +# A tag whose version is directly followed by a pre-release label (11.5.0-beta1, v2.0.0-rc.1). +_FORGE_PRERELEASE_TAG='^[^0-9]*[0-9]+([.][0-9]+)*[-._+]?(dev|alpha|beta|preview|pre|rc)([-._0-9]|$)' get_latest_gh_tag() { local repo="$1" local prefix="${2:-}" @@ -419,8 +421,9 @@ get_latest_gh_tag() { # Use git/matching-refs API for server-side prefix filtering. This avoids # paging through unrelated tags (e.g. mongodb/mongo-tools where 100.x tags # only appear after page 4 of /tags). Returns ALL tags matching the prefix - # in a single call, sorted lexicographically ascending; we pick the - # highest version using `sort -V`. + # in a single call, sorted lexicographically ascending. Pre-releases are + # skipped and the rest ordered by the version key: sort -V put 11.4.0 + # before 11.4.0-rc1 and offered 11.5.0-beta1 as the latest Drupal. if ! github_api_call "https://api.github.com/repos/${repo}/git/matching-refs/tags/${prefix}" "$temp_file"; then rm -f "$temp_file" return 22 @@ -431,16 +434,16 @@ get_latest_gh_tag() { if [[ "$count" -gt 0 ]]; then tag=$(jq -r '.[].ref' "$temp_file" | sed 's|^refs/tags/||' | - sort -V | - tail -n1) + awk -v re="$_FORGE_PRERELEASE_TAG" 'tolower($0) !~ re' | + _forge_highest_version) fi else - # No prefix: just take the first (newest) tag from /tags - if ! github_api_call "https://api.github.com/repos/${repo}/tags?per_page=1" "$temp_file"; then + # No prefix: the first (newest) tag from /tags that is not a pre-release + if ! github_api_call "https://api.github.com/repos/${repo}/tags?per_page=30" "$temp_file"; then rm -f "$temp_file" return 22 fi - tag=$(jq -r '.[0].name // empty' "$temp_file") + tag=$(jq -r '.[].name' "$temp_file" | awk -v re="$_FORGE_PRERELEASE_TAG" 'tolower($0) !~ re && !n++') fi rm -f "$temp_file" diff --git a/tests/forge-release-json.sh b/tests/forge-release-json.sh index 2cf4cc0..a690ce1 100644 --- a/tests/forge-release-json.sh +++ b/tests/forge-release-json.sh @@ -4,7 +4,8 @@ set -uo pipefail CORE_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" eval "$(sed -n '/^_FORGE_JQ_VKEY_DEF=/,/^_FORGE_JQ_VKEY=/p' "$CORE_DIR/lib/forge.func")" eval "$(awk '/^_forge_(descriptor|curl|designated_release|stable_filter|keep_raw|release_json|explain_failure|require_jq)[(][)] [{]/,/^}/' "$CORE_DIR/lib/forge.func")" -eval "$(awk '/^get_latest_gh_tag[(][)] [{]/,/^}/' "$CORE_DIR/lib/forge.func")" +eval "$(awk '/^(get_latest_gh_tag|_forge_highest_version)[(][)] [{]/,/^}/' "$CORE_DIR/lib/forge.func")" +eval "$(grep '^_FORGE_PRERELEASE_TAG=' "$CORE_DIR/lib/forge.func")" TEST_DIR="$(mktemp -d)" trap 'rm -rf -- "$TEST_DIR" "${TOOLS_GH_REL_JSON:-}"' EXIT @@ -121,5 +122,25 @@ expect "the tag is all that reaches stdout" 100.10.1 "$(cat "$OUT")" expect "a tag lookup without jq stops" 100 "$?" expect "a tag lookup without jq asks nothing" 0 "$(grep -c api "$CALLS")" +# drupal/drupal: sort -V ranked 11.4.0 below 11.4.0-rc1, and a major prefix offered 11.5.0-beta1. +tags_of() { printf '['; local t sep=""; for t in "$@"; do printf '%s{"ref":"refs/tags/%s","name":"%s"}' "$sep" "$t" "$t"; sep=","; done; printf ']'; } +latest_tag() { + ( + ensure_dependencies() { :; } + github_api_call() { printf '%s' "$TAGS" >"$2"; } + get_latest_gh_tag drupal/drupal "$@" 2>/dev/null + ) +} +TAGS="$(tags_of 11.4.0-beta1 11.4.0-rc1 11.4.0-rc2 11.4.0 11.4.11 11.4.12 11.5.0-beta1)" +expect "a prefixed tag lookup skips pre-releases" 11.4.12 "$(latest_tag 11.)" +TAGS="$(tags_of 11.4.0-beta1 11.4.0-rc1 11.4.0)" +expect "a final release outranks its release candidates" 11.4.0 "$(latest_tag 11.)" +TAGS="$(tags_of 12.0.0-alpha1 12.0.0-beta2)" +expect "only pre-releases: reported as no tag, not a crash" "" "$(set -eo pipefail; latest_tag 12.)" +TAGS="$(tags_of v2.0.0-rc.1 v1.9.3 v1.9.2)" +expect "without a prefix the newest stable tag wins" v1.9.3 "$(latest_tag)" +TAGS="$(tags_of release-2026-07-14c)" +expect "a revision letter is not a pre-release" release-2026-07-14c "$(latest_tag release-)" + printf '%s passed, %s failed\n' "$passed" "$failed" ((failed == 0))