From d970f624313e6010f891a9e61702a11818c5b5ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Wed, 9 Sep 2026 02:26:55 +0300 Subject: [PATCH 1/4] feat: scheduled check for new extension versions --- .../workflows/check-extension-versions.yml | 37 ++++++++++++++++ nix/tools/check-ext-versions.sh | 44 +++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 .github/workflows/check-extension-versions.yml create mode 100755 nix/tools/check-ext-versions.sh diff --git a/.github/workflows/check-extension-versions.yml b/.github/workflows/check-extension-versions.yml new file mode 100644 index 0000000000..4b949664fe --- /dev/null +++ b/.github/workflows/check-extension-versions.yml @@ -0,0 +1,37 @@ +name: Check Extension Versions + +on: + workflow_dispatch: + schedule: + - cron: '0 6 * * 1' + +jobs: + check-extension-versions: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - name: Checkout repository + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + + - name: Install Nix + uses: ./.github/actions/nix-install-ephemeral + + - name: Check for new extension versions + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: nix/tools/check-ext-versions.sh + + - name: Create Pull Request + uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8 + with: + token: ${{ secrets.GITHUB_TOKEN }} + commit-message: "chore: update extension versions" + title: "chore: update extension versions" + body: Automated weekly check of `nix/ext/versions.json` against upstream GitHub tags. + branch: auto-update-extension-versions + base: develop + labels: | + dependencies + automated diff --git a/nix/tools/check-ext-versions.sh b/nix/tools/check-ext-versions.sh new file mode 100755 index 0000000000..9abbdefe88 --- /dev/null +++ b/nix/tools/check-ext-versions.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +set -euo pipefail +cd "$(git rev-parse --show-toplevel)" + +versions_file="nix/ext/versions.json" +changed=0 + +for ext in $(jq -r 'keys[]' "$versions_file"); do + nixfile="nix/ext/$ext.nix" + [ -f "$nixfile" ] || nixfile="nix/ext/$ext/default.nix" + [ -f "$nixfile" ] || continue + + jq -e --arg e "$ext" '.[$e] | to_entries[0].value | has("pgrx") or has("rust")' "$versions_file" >/dev/null && continue + + pname=$(sed -n 's/.*pname = "\(.*\)".*/\1/p' "$nixfile" | head -1) + owner=$(sed -n 's/.*owner = "\(.*\)".*/\1/p' "$nixfile" | head -1) + repo=$(sed -n 's/.*repo = "\(.*\)".*/\1/p' "$nixfile" | head -1) + [ -n "$repo" ] || repo="$pname" + [ -n "$owner" ] || { echo "skip $ext: no owner"; continue; } + + latest_tag=$(gh api "repos/$owner/$repo/tags" --jq '.[0].name' 2>/dev/null) || { echo "skip $ext: tags lookup failed"; continue; } + [ -n "$latest_tag" ] || continue + + jq -e --arg e "$ext" --arg t "$latest_tag" \ + '.[$e] | to_entries | any(.value.revision == $t or .value.rev == $t or ("v" + .key) == $t or .key == $t)' \ + "$versions_file" >/dev/null && continue + + candidate="${latest_tag#v}" + url="https://github.com/$owner/$repo/archive/$latest_tag.tar.gz" + hash=$(nix-prefetch-url --type sha256 --unpack "$url" 2>/dev/null | tail -1) || { echo "skip $ext: prefetch failed for $latest_tag"; continue; } + sri_hash=$(nix hash to-sri --type sha256 "$hash") + postgresql=$(jq -c --arg e "$ext" '.[$e] | to_entries | max_by(.key) | .value.postgresql' "$versions_file") + + jq --arg e "$ext" --arg v "$candidate" --arg rev "$latest_tag" --arg hash "$sri_hash" --argjson pg "$postgresql" \ + '.[$e][$v] = {postgresql: $pg, revision: $rev, rev: $rev, hash: $hash}' \ + "$versions_file" > "$versions_file.tmp" && mv "$versions_file.tmp" "$versions_file" + + echo "updated $ext -> $candidate ($latest_tag)" + changed=1 +done + +if [ -n "${GITHUB_OUTPUT:-}" ]; then + echo "changed=$changed" >> "$GITHUB_OUTPUT" +fi From 606c1aceda1284abeb258f2bd327f9626ea11067 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Wed, 9 Sep 2026 03:28:50 +0300 Subject: [PATCH 2/4] fix: shfmt formatting, silent set -e abort, owner/repo overrides, cover rust and fetchurl exts --- nix/tools/check-ext-versions.sh | 123 ++++++++++++++++++++++++++------ 1 file changed, 101 insertions(+), 22 deletions(-) diff --git a/nix/tools/check-ext-versions.sh b/nix/tools/check-ext-versions.sh index 9abbdefe88..c5251a920e 100755 --- a/nix/tools/check-ext-versions.sh +++ b/nix/tools/check-ext-versions.sh @@ -5,40 +5,119 @@ cd "$(git rev-parse --show-toplevel)" versions_file="nix/ext/versions.json" changed=0 +# Catalog key -> nix file basename, where they differ. +declare -A file_alias=( + [http]=pgsql-http + [plpgsql_check]=plpgsql-check + [safeupdate]=pg-safeupdate + [supabase_vault]=vault + [vector]=pgvector +) + +# Catalog key -> "owner/repo", where the nix file's own repo variable/fetcher +# doesn't resolve to the real GitHub repo (fetchurl-based, or repo != pname). +declare -A repo_override=( + [pg_plan_filter]=pgexperts/pg_plan_filter + [postgis]=postgis/postgis + [pgroonga]=pgroonga/pgroonga +) + +# Extensions where we don't attempt a hash bump (fetchurl-based source, or a +# cargoHash/pgrx vendor hash that needs an actual build to compute). Version +# gets bumped anyway with a placeholder hash for a human to fill in. +no_hash_exts="pg_graphql wrappers postgis pgroonga" + for ext in $(jq -r 'keys[]' "$versions_file"); do - nixfile="nix/ext/$ext.nix" - [ -f "$nixfile" ] || nixfile="nix/ext/$ext/default.nix" - [ -f "$nixfile" ] || continue + base="${file_alias[$ext]:-$ext}" + nixfile="nix/ext/$base.nix" + [ -f "$nixfile" ] || nixfile="nix/ext/$base/default.nix" + [ -f "$nixfile" ] || { + echo "skip $ext: no nix file found" + continue + } - jq -e --arg e "$ext" '.[$e] | to_entries[0].value | has("pgrx") or has("rust")' "$versions_file" >/dev/null && continue + if [ -n "${repo_override[$ext]:-}" ]; then + owner="${repo_override[$ext]%%/*}" + repo="${repo_override[$ext]#*/}" + else + pname=$(sed -n 's/.*pname = "\(.*\)".*/\1/p' "$nixfile" | head -1) + owner=$(sed -n 's/.*owner = "\(.*\)".*/\1/p' "$nixfile" | head -1) + repo=$(sed -n 's/.*repo = "\(.*\)".*/\1/p' "$nixfile" | head -1) + if [ -z "$owner" ]; then + # e.g. `owner = repoOwner;` with `repoOwner = "theory";` defined separately. + ownervar=$(sed -n 's/.*owner = \([A-Za-z_][A-Za-z0-9_]*\);.*/\1/p' "$nixfile" | head -1) + [ -n "$ownervar" ] && owner=$(sed -n "s/.*$ownervar = \"\\(.*\\)\".*/\\1/p" "$nixfile" | head -1) + fi + [ -n "$repo" ] || repo="$pname" + [ -n "$owner" ] || { + echo "skip $ext: no owner" + continue + } + fi - pname=$(sed -n 's/.*pname = "\(.*\)".*/\1/p' "$nixfile" | head -1) - owner=$(sed -n 's/.*owner = "\(.*\)".*/\1/p' "$nixfile" | head -1) - repo=$(sed -n 's/.*repo = "\(.*\)".*/\1/p' "$nixfile" | head -1) - [ -n "$repo" ] || repo="$pname" - [ -n "$owner" ] || { echo "skip $ext: no owner"; continue; } + tags_raw=$(gh api "repos/$owner/$repo/tags" --paginate --jq '.[].name' 2>/dev/null) || + { + echo "skip $ext: tags lookup failed" + continue + } - latest_tag=$(gh api "repos/$owner/$repo/tags" --jq '.[0].name' 2>/dev/null) || { echo "skip $ext: tags lookup failed"; continue; } - [ -n "$latest_tag" ] || continue + # Only trust clean vX.Y[.Z...] / ver_X.Y[.Z...] tags, or repo-prefixed + # underscore tags (wal2json_2_6) - repos also carry packaging/branch tags + # (debian/1.4.0-2, loader-2.11.0p1, ...) that aren't real releases. + best=$({ + printf '%s\n' "$tags_raw" | + grep -E '^(v|ver_)?[0-9]+(\.[0-9]+){1,3}$' | + while read -r t; do + v="${t#ver_}" + v="${v#v}" + printf '%s\t%s\n' "$v" "$t" + done + printf '%s\n' "$tags_raw" | + grep -E '^[A-Za-z][A-Za-z0-9]*[-_][0-9]+([._][0-9]+){1,3}$' | + while read -r t; do + v=$(printf '%s' "$t" | grep -oE '[0-9]+([._][0-9]+){1,3}$' | tr '_' '.') + printf '%s\t%s\n' "$v" "$t" + done + } | sort -t "$(printf '\t')" -k1,1 -V | tail -1) || true + [ -n "$best" ] || { + echo "skip $ext: no clean version tags" + continue + } + candidate=$(printf '%s' "$best" | cut -f1) + tag=$(printf '%s' "$best" | cut -f2) - jq -e --arg e "$ext" --arg t "$latest_tag" \ - '.[$e] | to_entries | any(.value.revision == $t or .value.rev == $t or ("v" + .key) == $t or .key == $t)' \ - "$versions_file" >/dev/null && continue + current=$(jq -r --arg e "$ext" '.[$e] | keys | max_by(split(".") | map(tonumber? // 0))' "$versions_file") + highest=$(printf '%s\n%s\n' "$current" "$candidate" | sort -V | tail -1) + [ "$highest" = "$candidate" ] && [ "$candidate" != "$current" ] || continue - candidate="${latest_tag#v}" - url="https://github.com/$owner/$repo/archive/$latest_tag.tar.gz" - hash=$(nix-prefetch-url --type sha256 --unpack "$url" 2>/dev/null | tail -1) || { echo "skip $ext: prefetch failed for $latest_tag"; continue; } - sri_hash=$(nix hash to-sri --type sha256 "$hash") postgresql=$(jq -c --arg e "$ext" '.[$e] | to_entries | max_by(.key) | .value.postgresql' "$versions_file") - jq --arg e "$ext" --arg v "$candidate" --arg rev "$latest_tag" --arg hash "$sri_hash" --argjson pg "$postgresql" \ + case " $no_hash_exts " in + *" $ext "*) + sri_hash="" + ;; + *) + url="https://github.com/$owner/$repo/archive/$tag.tar.gz" + hash=$(nix-prefetch-url --type sha256 --unpack "$url" 2>/dev/null | tail -1) || { + echo "skip $ext: prefetch failed for $tag" + continue + } + sri_hash=$(nix hash to-sri --type sha256 "$hash") + ;; + esac + + jq --arg e "$ext" --arg v "$candidate" --arg rev "$tag" --arg hash "$sri_hash" --argjson pg "$postgresql" \ '.[$e][$v] = {postgresql: $pg, revision: $rev, rev: $rev, hash: $hash}' \ - "$versions_file" > "$versions_file.tmp" && mv "$versions_file.tmp" "$versions_file" + "$versions_file" >"$versions_file.tmp" && mv "$versions_file.tmp" "$versions_file" - echo "updated $ext -> $candidate ($latest_tag)" + if [ -z "$sri_hash" ]; then + echo "updated $ext -> $candidate ($tag) [no hash, needs manual fill-in]" + else + echo "updated $ext -> $candidate ($tag)" + fi changed=1 done if [ -n "${GITHUB_OUTPUT:-}" ]; then - echo "changed=$changed" >> "$GITHUB_OUTPUT" + echo "changed=$changed" >>"$GITHUB_OUTPUT" fi From b135eba250aae58824814d51707d8538e95eb4fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Wed, 9 Sep 2026 03:43:54 +0300 Subject: [PATCH 3/4] fix: reformat with project's nix fmt (tabs, not spaces) --- nix/tools/check-ext-versions.sh | 178 ++++++++++++++++---------------- 1 file changed, 89 insertions(+), 89 deletions(-) diff --git a/nix/tools/check-ext-versions.sh b/nix/tools/check-ext-versions.sh index c5251a920e..1495744e8b 100755 --- a/nix/tools/check-ext-versions.sh +++ b/nix/tools/check-ext-versions.sh @@ -7,19 +7,19 @@ changed=0 # Catalog key -> nix file basename, where they differ. declare -A file_alias=( - [http]=pgsql-http - [plpgsql_check]=plpgsql-check - [safeupdate]=pg-safeupdate - [supabase_vault]=vault - [vector]=pgvector + [http]=pgsql-http + [plpgsql_check]=plpgsql-check + [safeupdate]=pg-safeupdate + [supabase_vault]=vault + [vector]=pgvector ) # Catalog key -> "owner/repo", where the nix file's own repo variable/fetcher # doesn't resolve to the real GitHub repo (fetchurl-based, or repo != pname). declare -A repo_override=( - [pg_plan_filter]=pgexperts/pg_plan_filter - [postgis]=postgis/postgis - [pgroonga]=pgroonga/pgroonga + [pg_plan_filter]=pgexperts/pg_plan_filter + [postgis]=postgis/postgis + [pgroonga]=pgroonga/pgroonga ) # Extensions where we don't attempt a hash bump (fetchurl-based source, or a @@ -28,96 +28,96 @@ declare -A repo_override=( no_hash_exts="pg_graphql wrappers postgis pgroonga" for ext in $(jq -r 'keys[]' "$versions_file"); do - base="${file_alias[$ext]:-$ext}" - nixfile="nix/ext/$base.nix" - [ -f "$nixfile" ] || nixfile="nix/ext/$base/default.nix" - [ -f "$nixfile" ] || { - echo "skip $ext: no nix file found" - continue - } + base="${file_alias[$ext]:-$ext}" + nixfile="nix/ext/$base.nix" + [ -f "$nixfile" ] || nixfile="nix/ext/$base/default.nix" + [ -f "$nixfile" ] || { + echo "skip $ext: no nix file found" + continue + } - if [ -n "${repo_override[$ext]:-}" ]; then - owner="${repo_override[$ext]%%/*}" - repo="${repo_override[$ext]#*/}" - else - pname=$(sed -n 's/.*pname = "\(.*\)".*/\1/p' "$nixfile" | head -1) - owner=$(sed -n 's/.*owner = "\(.*\)".*/\1/p' "$nixfile" | head -1) - repo=$(sed -n 's/.*repo = "\(.*\)".*/\1/p' "$nixfile" | head -1) - if [ -z "$owner" ]; then - # e.g. `owner = repoOwner;` with `repoOwner = "theory";` defined separately. - ownervar=$(sed -n 's/.*owner = \([A-Za-z_][A-Za-z0-9_]*\);.*/\1/p' "$nixfile" | head -1) - [ -n "$ownervar" ] && owner=$(sed -n "s/.*$ownervar = \"\\(.*\\)\".*/\\1/p" "$nixfile" | head -1) - fi - [ -n "$repo" ] || repo="$pname" - [ -n "$owner" ] || { - echo "skip $ext: no owner" - continue - } - fi + if [ -n "${repo_override[$ext]:-}" ]; then + owner="${repo_override[$ext]%%/*}" + repo="${repo_override[$ext]#*/}" + else + pname=$(sed -n 's/.*pname = "\(.*\)".*/\1/p' "$nixfile" | head -1) + owner=$(sed -n 's/.*owner = "\(.*\)".*/\1/p' "$nixfile" | head -1) + repo=$(sed -n 's/.*repo = "\(.*\)".*/\1/p' "$nixfile" | head -1) + if [ -z "$owner" ]; then + # e.g. `owner = repoOwner;` with `repoOwner = "theory";` defined separately. + ownervar=$(sed -n 's/.*owner = \([A-Za-z_][A-Za-z0-9_]*\);.*/\1/p' "$nixfile" | head -1) + [ -n "$ownervar" ] && owner=$(sed -n "s/.*$ownervar = \"\\(.*\\)\".*/\\1/p" "$nixfile" | head -1) + fi + [ -n "$repo" ] || repo="$pname" + [ -n "$owner" ] || { + echo "skip $ext: no owner" + continue + } + fi - tags_raw=$(gh api "repos/$owner/$repo/tags" --paginate --jq '.[].name' 2>/dev/null) || - { - echo "skip $ext: tags lookup failed" - continue - } + tags_raw=$(gh api "repos/$owner/$repo/tags" --paginate --jq '.[].name' 2>/dev/null) || + { + echo "skip $ext: tags lookup failed" + continue + } - # Only trust clean vX.Y[.Z...] / ver_X.Y[.Z...] tags, or repo-prefixed - # underscore tags (wal2json_2_6) - repos also carry packaging/branch tags - # (debian/1.4.0-2, loader-2.11.0p1, ...) that aren't real releases. - best=$({ - printf '%s\n' "$tags_raw" | - grep -E '^(v|ver_)?[0-9]+(\.[0-9]+){1,3}$' | - while read -r t; do - v="${t#ver_}" - v="${v#v}" - printf '%s\t%s\n' "$v" "$t" - done - printf '%s\n' "$tags_raw" | - grep -E '^[A-Za-z][A-Za-z0-9]*[-_][0-9]+([._][0-9]+){1,3}$' | - while read -r t; do - v=$(printf '%s' "$t" | grep -oE '[0-9]+([._][0-9]+){1,3}$' | tr '_' '.') - printf '%s\t%s\n' "$v" "$t" - done - } | sort -t "$(printf '\t')" -k1,1 -V | tail -1) || true - [ -n "$best" ] || { - echo "skip $ext: no clean version tags" - continue - } - candidate=$(printf '%s' "$best" | cut -f1) - tag=$(printf '%s' "$best" | cut -f2) + # Only trust clean vX.Y[.Z...] / ver_X.Y[.Z...] tags, or repo-prefixed + # underscore tags (wal2json_2_6) - repos also carry packaging/branch tags + # (debian/1.4.0-2, loader-2.11.0p1, ...) that aren't real releases. + best=$({ + printf '%s\n' "$tags_raw" | + grep -E '^(v|ver_)?[0-9]+(\.[0-9]+){1,3}$' | + while read -r t; do + v="${t#ver_}" + v="${v#v}" + printf '%s\t%s\n' "$v" "$t" + done + printf '%s\n' "$tags_raw" | + grep -E '^[A-Za-z][A-Za-z0-9]*[-_][0-9]+([._][0-9]+){1,3}$' | + while read -r t; do + v=$(printf '%s' "$t" | grep -oE '[0-9]+([._][0-9]+){1,3}$' | tr '_' '.') + printf '%s\t%s\n' "$v" "$t" + done + } | sort -t "$(printf '\t')" -k1,1 -V | tail -1) || true + [ -n "$best" ] || { + echo "skip $ext: no clean version tags" + continue + } + candidate=$(printf '%s' "$best" | cut -f1) + tag=$(printf '%s' "$best" | cut -f2) - current=$(jq -r --arg e "$ext" '.[$e] | keys | max_by(split(".") | map(tonumber? // 0))' "$versions_file") - highest=$(printf '%s\n%s\n' "$current" "$candidate" | sort -V | tail -1) - [ "$highest" = "$candidate" ] && [ "$candidate" != "$current" ] || continue + current=$(jq -r --arg e "$ext" '.[$e] | keys | max_by(split(".") | map(tonumber? // 0))' "$versions_file") + highest=$(printf '%s\n%s\n' "$current" "$candidate" | sort -V | tail -1) + [ "$highest" = "$candidate" ] && [ "$candidate" != "$current" ] || continue - postgresql=$(jq -c --arg e "$ext" '.[$e] | to_entries | max_by(.key) | .value.postgresql' "$versions_file") + postgresql=$(jq -c --arg e "$ext" '.[$e] | to_entries | max_by(.key) | .value.postgresql' "$versions_file") - case " $no_hash_exts " in - *" $ext "*) - sri_hash="" - ;; - *) - url="https://github.com/$owner/$repo/archive/$tag.tar.gz" - hash=$(nix-prefetch-url --type sha256 --unpack "$url" 2>/dev/null | tail -1) || { - echo "skip $ext: prefetch failed for $tag" - continue - } - sri_hash=$(nix hash to-sri --type sha256 "$hash") - ;; - esac + case " $no_hash_exts " in + *" $ext "*) + sri_hash="" + ;; + *) + url="https://github.com/$owner/$repo/archive/$tag.tar.gz" + hash=$(nix-prefetch-url --type sha256 --unpack "$url" 2>/dev/null | tail -1) || { + echo "skip $ext: prefetch failed for $tag" + continue + } + sri_hash=$(nix hash to-sri --type sha256 "$hash") + ;; + esac - jq --arg e "$ext" --arg v "$candidate" --arg rev "$tag" --arg hash "$sri_hash" --argjson pg "$postgresql" \ - '.[$e][$v] = {postgresql: $pg, revision: $rev, rev: $rev, hash: $hash}' \ - "$versions_file" >"$versions_file.tmp" && mv "$versions_file.tmp" "$versions_file" + jq --arg e "$ext" --arg v "$candidate" --arg rev "$tag" --arg hash "$sri_hash" --argjson pg "$postgresql" \ + '.[$e][$v] = {postgresql: $pg, revision: $rev, rev: $rev, hash: $hash}' \ + "$versions_file" >"$versions_file.tmp" && mv "$versions_file.tmp" "$versions_file" - if [ -z "$sri_hash" ]; then - echo "updated $ext -> $candidate ($tag) [no hash, needs manual fill-in]" - else - echo "updated $ext -> $candidate ($tag)" - fi - changed=1 + if [ -z "$sri_hash" ]; then + echo "updated $ext -> $candidate ($tag) [no hash, needs manual fill-in]" + else + echo "updated $ext -> $candidate ($tag)" + fi + changed=1 done if [ -n "${GITHUB_OUTPUT:-}" ]; then - echo "changed=$changed" >>"$GITHUB_OUTPUT" + echo "changed=$changed" >>"$GITHUB_OUTPUT" fi From 62cac59ec82243015d5157b7a64a0dc96cc41280 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Wed, 9 Sep 2026 13:55:24 +0300 Subject: [PATCH 4/4] rewrite check-ext-versions in python, add github repo as package metadata Replaces the bash+regex version detector. Owner/repo now lives in each extension's own passthru.github (real, eval'able package metadata) instead of being scraped from source text or a side manifest. --- .../workflows/check-extension-versions.yml | 2 +- nix/ext/hypopg.nix | 1 + nix/ext/index_advisor.nix | 1 + nix/ext/pg-safeupdate.nix | 1 + nix/ext/pg_cron/default.nix | 1 + nix/ext/pg_graphql/default.nix | 1 + nix/ext/pg_hashids.nix | 1 + nix/ext/pg_jsonschema/default.nix | 1 + nix/ext/pg_net.nix | 1 + nix/ext/pg_partman.nix | 1 + nix/ext/pg_plan_filter.nix | 1 + nix/ext/pg_repack.nix | 1 + nix/ext/pg_stat_monitor.nix | 1 + nix/ext/pg_tle.nix | 1 + nix/ext/pgaudit.nix | 1 + nix/ext/pgjwt.nix | 1 + nix/ext/pgmq/default.nix | 1 + nix/ext/pgroonga/default.nix | 1 + nix/ext/pgrouting/default.nix | 1 + nix/ext/pgsodium.nix | 1 + nix/ext/pgsql-http.nix | 1 + nix/ext/pgtap.nix | 1 + nix/ext/pgvector.nix | 1 + nix/ext/plpgsql-check.nix | 1 + nix/ext/plv8/default.nix | 1 + nix/ext/postgis.nix | 1 + nix/ext/rum.nix | 1 + nix/ext/timescaledb.nix | 1 + nix/ext/vault.nix | 1 + nix/ext/wal2json.nix | 1 + nix/ext/wrappers/default.nix | 1 + nix/tools/check-ext-versions.py | 167 ++++++++++++++++++ nix/tools/check-ext-versions.sh | 123 ------------- 33 files changed, 198 insertions(+), 124 deletions(-) create mode 100755 nix/tools/check-ext-versions.py delete mode 100755 nix/tools/check-ext-versions.sh diff --git a/.github/workflows/check-extension-versions.yml b/.github/workflows/check-extension-versions.yml index 4b949664fe..18888a57c8 100644 --- a/.github/workflows/check-extension-versions.yml +++ b/.github/workflows/check-extension-versions.yml @@ -21,7 +21,7 @@ jobs: - name: Check for new extension versions env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: nix/tools/check-ext-versions.sh + run: python3 nix/tools/check-ext-versions.py - name: Create Pull Request uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8 diff --git a/nix/ext/hypopg.nix b/nix/ext/hypopg.nix index 9bd4927263..4405e87c74 100644 --- a/nix/ext/hypopg.nix +++ b/nix/ext/hypopg.nix @@ -98,6 +98,7 @@ buildEnv { ''; passthru = { + github = "HypoPG/hypopg"; perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; diff --git a/nix/ext/index_advisor.nix b/nix/ext/index_advisor.nix index 5892127142..f9fee5aed2 100644 --- a/nix/ext/index_advisor.nix +++ b/nix/ext/index_advisor.nix @@ -83,6 +83,7 @@ pkgs.buildEnv { ]; passthru = { + github = "supabase/index_advisor"; perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; diff --git a/nix/ext/pg-safeupdate.nix b/nix/ext/pg-safeupdate.nix index 452e9c2c5e..869a7199a7 100644 --- a/nix/ext/pg-safeupdate.nix +++ b/nix/ext/pg-safeupdate.nix @@ -83,6 +83,7 @@ pkgs.buildEnv { ''; passthru = { + github = "eradman/pg-safeupdate"; perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; diff --git a/nix/ext/pg_cron/default.nix b/nix/ext/pg_cron/default.nix index 197420c982..965f7282fe 100644 --- a/nix/ext/pg_cron/default.nix +++ b/nix/ext/pg_cron/default.nix @@ -139,6 +139,7 @@ buildEnv { }; passthru = { + github = "citusdata/pg_cron"; perVersion = lib.mapAttrs (name: value: build name value) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; diff --git a/nix/ext/pg_graphql/default.nix b/nix/ext/pg_graphql/default.nix index cbab08aa76..3a595a249b 100644 --- a/nix/ext/pg_graphql/default.nix +++ b/nix/ext/pg_graphql/default.nix @@ -184,6 +184,7 @@ in --prefix EXT_WRAPPER : "$out" --prefix EXT_NAME : "${pname}" ''; passthru = { + github = "supabase/pg_graphql"; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; inherit pname latestOnly; diff --git a/nix/ext/pg_hashids.nix b/nix/ext/pg_hashids.nix index d9d4a34077..3c5d0da5aa 100644 --- a/nix/ext/pg_hashids.nix +++ b/nix/ext/pg_hashids.nix @@ -106,6 +106,7 @@ buildEnv { ''; passthru = { + github = "iCyberon/pg_hashids"; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; inherit pname latestOnly; diff --git a/nix/ext/pg_jsonschema/default.nix b/nix/ext/pg_jsonschema/default.nix index ec39a69fe2..848b507665 100644 --- a/nix/ext/pg_jsonschema/default.nix +++ b/nix/ext/pg_jsonschema/default.nix @@ -184,6 +184,7 @@ in ''; passthru = { + github = "supabase/pg_jsonschema"; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; inherit pname latestOnly; diff --git a/nix/ext/pg_net.nix b/nix/ext/pg_net.nix index f769bad314..cb9e475512 100644 --- a/nix/ext/pg_net.nix +++ b/nix/ext/pg_net.nix @@ -145,6 +145,7 @@ pkgs.buildEnv { ''; passthru = { + github = "supabase/pg_net"; perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; diff --git a/nix/ext/pg_partman.nix b/nix/ext/pg_partman.nix index 0c8a4eee6e..5eb5c7b6ea 100644 --- a/nix/ext/pg_partman.nix +++ b/nix/ext/pg_partman.nix @@ -100,6 +100,7 @@ pkgs.buildEnv { ''; passthru = { + github = "pgpartman/pg_partman"; perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; diff --git a/nix/ext/pg_plan_filter.nix b/nix/ext/pg_plan_filter.nix index 847c7a2d11..237b7b63e3 100644 --- a/nix/ext/pg_plan_filter.nix +++ b/nix/ext/pg_plan_filter.nix @@ -86,6 +86,7 @@ pkgs.buildEnv { ''; passthru = { + github = "pgexperts/pg_plan_filter"; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; inherit pname latestOnly; diff --git a/nix/ext/pg_repack.nix b/nix/ext/pg_repack.nix index 78d9764063..c7c1ea6459 100644 --- a/nix/ext/pg_repack.nix +++ b/nix/ext/pg_repack.nix @@ -140,6 +140,7 @@ buildEnv { ''; passthru = { + github = "reorg/pg_repack"; perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; diff --git a/nix/ext/pg_stat_monitor.nix b/nix/ext/pg_stat_monitor.nix index 9a58464a66..89f55c2b38 100644 --- a/nix/ext/pg_stat_monitor.nix +++ b/nix/ext/pg_stat_monitor.nix @@ -110,6 +110,7 @@ buildEnv { ''; passthru = { + github = "percona/pg_stat_monitor"; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; inherit pname latestOnly; diff --git a/nix/ext/pg_tle.nix b/nix/ext/pg_tle.nix index 9c7ef86755..e12756bc58 100644 --- a/nix/ext/pg_tle.nix +++ b/nix/ext/pg_tle.nix @@ -111,6 +111,7 @@ buildEnv { ''; passthru = { + github = "aws/pg_tle"; perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; diff --git a/nix/ext/pgaudit.nix b/nix/ext/pgaudit.nix index 8fb727c666..f58ba8f4df 100644 --- a/nix/ext/pgaudit.nix +++ b/nix/ext/pgaudit.nix @@ -240,6 +240,7 @@ buildEnv { ''; passthru = { + github = "pgaudit/pgaudit"; perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; diff --git a/nix/ext/pgjwt.nix b/nix/ext/pgjwt.nix index 348b534c34..c9df64851b 100644 --- a/nix/ext/pgjwt.nix +++ b/nix/ext/pgjwt.nix @@ -82,6 +82,7 @@ buildEnv { pathsToLink = [ "/share/postgresql/extension" ]; passthru = { + github = "michelp/pgjwt"; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; inherit pname latestOnly; diff --git a/nix/ext/pgmq/default.nix b/nix/ext/pgmq/default.nix index 7e2076a2f5..cc604f24a6 100644 --- a/nix/ext/pgmq/default.nix +++ b/nix/ext/pgmq/default.nix @@ -103,6 +103,7 @@ buildEnv { pathsToLink = [ "/share/postgresql/extension" ]; passthru = { + github = "pgmq/pgmq"; perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; diff --git a/nix/ext/pgroonga/default.nix b/nix/ext/pgroonga/default.nix index b9c3829a0d..71e9f49c64 100644 --- a/nix/ext/pgroonga/default.nix +++ b/nix/ext/pgroonga/default.nix @@ -181,6 +181,7 @@ buildEnv { ''; passthru = { + github = "pgroonga/pgroonga"; perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; diff --git a/nix/ext/pgrouting/default.nix b/nix/ext/pgrouting/default.nix index cc00281e70..79f3af8f89 100644 --- a/nix/ext/pgrouting/default.nix +++ b/nix/ext/pgrouting/default.nix @@ -153,6 +153,7 @@ buildEnv { ''; passthru = { + github = "pgRouting/pgrouting"; perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; diff --git a/nix/ext/pgsodium.nix b/nix/ext/pgsodium.nix index b5a2dcb72c..0e619e8cee 100644 --- a/nix/ext/pgsodium.nix +++ b/nix/ext/pgsodium.nix @@ -112,6 +112,7 @@ pkgs.buildEnv { ''; passthru = { + github = "michelp/pgsodium"; perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; diff --git a/nix/ext/pgsql-http.nix b/nix/ext/pgsql-http.nix index 885fcc472e..1982885ddc 100644 --- a/nix/ext/pgsql-http.nix +++ b/nix/ext/pgsql-http.nix @@ -113,6 +113,7 @@ pkgs.buildEnv { ''; passthru = { + github = "pramsey/pgsql-http"; perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; diff --git a/nix/ext/pgtap.nix b/nix/ext/pgtap.nix index f283774dc8..37697ea861 100644 --- a/nix/ext/pgtap.nix +++ b/nix/ext/pgtap.nix @@ -134,6 +134,7 @@ buildEnv { ''; passthru = { + github = "theory/pgtap"; inherit versions numberOfVersions; pname = "${pname}-all"; version = diff --git a/nix/ext/pgvector.nix b/nix/ext/pgvector.nix index 2db9d0c123..084cbaa22b 100644 --- a/nix/ext/pgvector.nix +++ b/nix/ext/pgvector.nix @@ -96,6 +96,7 @@ pkgs.buildEnv { ''; passthru = { + github = "pgvector/pgvector"; perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; diff --git a/nix/ext/plpgsql-check.nix b/nix/ext/plpgsql-check.nix index a9901eb0a5..ca6bd4de3b 100644 --- a/nix/ext/plpgsql-check.nix +++ b/nix/ext/plpgsql-check.nix @@ -139,6 +139,7 @@ buildEnv { ''; passthru = { + github = "okbob/plpgsql_check"; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; inherit switch-ext-version latestOnly; diff --git a/nix/ext/plv8/default.nix b/nix/ext/plv8/default.nix index 731991937c..e25f227125 100644 --- a/nix/ext/plv8/default.nix +++ b/nix/ext/plv8/default.nix @@ -238,6 +238,7 @@ buildEnv { ''; passthru = { + github = "plv8/plv8"; perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; diff --git a/nix/ext/postgis.nix b/nix/ext/postgis.nix index 3f722d9ad9..9ff62ce78c 100644 --- a/nix/ext/postgis.nix +++ b/nix/ext/postgis.nix @@ -256,6 +256,7 @@ in ''; passthru = { + github = "postgis/postgis"; perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; diff --git a/nix/ext/rum.nix b/nix/ext/rum.nix index 31e6bae07b..49b13e5953 100644 --- a/nix/ext/rum.nix +++ b/nix/ext/rum.nix @@ -107,6 +107,7 @@ buildEnv { ''; passthru = { + github = "postgrespro/rum"; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; inherit pname latestOnly; diff --git a/nix/ext/timescaledb.nix b/nix/ext/timescaledb.nix index dbfb2a8365..59f9cda7ec 100644 --- a/nix/ext/timescaledb.nix +++ b/nix/ext/timescaledb.nix @@ -152,6 +152,7 @@ buildEnv { ]; passthru = { + github = "timescale/timescaledb"; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; inherit switch-ext-version latestOnly; diff --git a/nix/ext/vault.nix b/nix/ext/vault.nix index 9ab5391b1d..ea26be5e3b 100644 --- a/nix/ext/vault.nix +++ b/nix/ext/vault.nix @@ -99,6 +99,7 @@ pkgs.buildEnv { ''; passthru = { + github = "supabase/vault"; perVersion = lib.mapAttrs (name: value: build name value.hash) versionsToUse; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; diff --git a/nix/ext/wal2json.nix b/nix/ext/wal2json.nix index b082301c82..38fc2394e0 100644 --- a/nix/ext/wal2json.nix +++ b/nix/ext/wal2json.nix @@ -106,6 +106,7 @@ pkgs.buildEnv { ''; passthru = { + github = "eulerto/wal2json"; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; inherit pname latestOnly; diff --git a/nix/ext/wrappers/default.nix b/nix/ext/wrappers/default.nix index 3ae64afbd1..2937a53428 100644 --- a/nix/ext/wrappers/default.nix +++ b/nix/ext/wrappers/default.nix @@ -366,6 +366,7 @@ in } ''; passthru = { + github = "supabase/wrappers"; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; pname = "${pname}"; diff --git a/nix/tools/check-ext-versions.py b/nix/tools/check-ext-versions.py new file mode 100755 index 0000000000..516d498f43 --- /dev/null +++ b/nix/tools/check-ext-versions.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python3 +"""Check nix/ext/versions.json extensions against upstream GitHub tags.""" + +import json +import os +import re +import subprocess +import sys + +REPO_ROOT = subprocess.run( + ["git", "rev-parse", "--show-toplevel"], capture_output=True, text=True, check=True +).stdout.strip() +VERSIONS_FILE = os.path.join(REPO_ROOT, "nix/ext/versions.json") + +# `exts` attribute name -> versions.json catalog key, where they differ. +ATTR_TO_CATALOG_KEY = {"plan_filter": "pg_plan_filter"} + +# Extensions where we don't attempt a hash bump: fetchurl-based source, or a +# cargoHash/pgrx vendor hash that needs an actual build to compute. Version +# gets bumped anyway with a placeholder hash for a human to fill in. +NO_HASH_EXTS = {"pg_graphql", "wrappers", "postgis", "pgroonga"} + +CLEAN_TAG_RE = re.compile(r"^(?:v|ver_)?(\d+(?:\.\d+){0,3})$") +LEADING_VERSION_RE = re.compile(r"^(\d+(?:\.\d+)*)") + + +def parse_version(s: str) -> tuple[int, ...]: + m = LEADING_VERSION_RE.match(s) + if not m: + return (0,) + return tuple(int(p) for p in m.group(1).split(".")) + + +def best_candidate(tags: list[str], repo: str) -> tuple[tuple[int, ...], str] | None: + # repo-prefixed underscore tags (wal2json_2_6) - only trusted when the + # prefix is the actual repo name, not any legacy tag scheme (REL0_9_1). + prefixed_re = re.compile( + rf"^{re.escape(repo)}[-_](\d+(?:[._]\d+){{1,3}})$", re.IGNORECASE + ) + candidates: list[tuple[tuple[int, ...], str]] = [] + for tag in tags: + m = CLEAN_TAG_RE.match(tag) or prefixed_re.match(tag) + if not m: + continue + version_str = m.group(1).replace("_", ".") + candidates.append((parse_version(version_str), tag)) + return max(candidates, default=None) + + +def github_metadata(system: str) -> dict[str, str]: + expr = ( + "exts: builtins.listToAttrs (map (n: { name = n; value = exts.${n}.github; })" + " (builtins.filter (n: exts.${n} ? github) (builtins.attrNames exts)))" + ) + out = subprocess.run( + [ + "nix", + "eval", + "--json", + f".#legacyPackages.{system}.psql_15.exts", + "--apply", + expr, + ], + cwd=REPO_ROOT, + capture_output=True, + text=True, + check=True, + ).stdout + return json.loads(out) + + +def fetch_tags(owner: str, repo: str) -> list[str] | None: + result = subprocess.run( + ["gh", "api", f"repos/{owner}/{repo}/tags", "--paginate"], + capture_output=True, + text=True, + ) + if result.returncode != 0: + return None + return [t["name"] for t in json.loads(result.stdout)] + + +def prefetch_hash(owner: str, repo: str, tag: str) -> str | None: + url = f"https://github.com/{owner}/{repo}/archive/{tag}.tar.gz" + prefetch = subprocess.run( + ["nix-prefetch-url", "--type", "sha256", "--unpack", url], + capture_output=True, + text=True, + ) + if prefetch.returncode != 0: + return None + sha256 = prefetch.stdout.strip().splitlines()[-1] + sri = subprocess.run( + ["nix", "hash", "to-sri", "--type", "sha256", sha256], + capture_output=True, + text=True, + check=True, + ) + return sri.stdout.strip() + + +def main() -> None: + system = subprocess.run( + ["nix", "eval", "--impure", "--raw", "--expr", "builtins.currentSystem"], + capture_output=True, + text=True, + check=True, + ).stdout.strip() + + with open(VERSIONS_FILE) as f: + versions = json.load(f) + + changed = False + for attr, repo_slug in github_metadata(system).items(): + ext = ATTR_TO_CATALOG_KEY.get(attr, attr) + if ext not in versions: + continue + owner, repo = repo_slug.split("/", 1) + + tags = fetch_tags(owner, repo) + if tags is None: + print(f"skip {ext}: tags lookup failed") + continue + + candidate = best_candidate(tags, repo) + if candidate is None: + print(f"skip {ext}: no clean version tags") + continue + candidate_version, tag = candidate + + entries = versions[ext] + current_key = max(entries, key=parse_version) + if candidate_version <= parse_version(current_key): + continue + + if ext in NO_HASH_EXTS: + sri_hash = "" + else: + sri_hash = prefetch_hash(owner, repo, tag) + if sri_hash is None: + print(f"skip {ext}: prefetch failed for {tag}") + continue + + version_str = ".".join(str(p) for p in candidate_version) + entries[version_str] = { + "postgresql": entries[current_key]["postgresql"], + "revision": tag, + "rev": tag, + "hash": sri_hash, + } + changed = True + suffix = " [no hash, needs manual fill-in]" if not sri_hash else "" + print(f"updated {ext} -> {version_str} ({tag}){suffix}") + + if changed: + with open(VERSIONS_FILE, "w") as f: + json.dump(versions, f, indent=2) + f.write("\n") + + github_output = os.environ.get("GITHUB_OUTPUT") + if github_output: + with open(github_output, "a") as f: + f.write(f"changed={'true' if changed else 'false'}\n") + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/nix/tools/check-ext-versions.sh b/nix/tools/check-ext-versions.sh deleted file mode 100755 index 1495744e8b..0000000000 --- a/nix/tools/check-ext-versions.sh +++ /dev/null @@ -1,123 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -cd "$(git rev-parse --show-toplevel)" - -versions_file="nix/ext/versions.json" -changed=0 - -# Catalog key -> nix file basename, where they differ. -declare -A file_alias=( - [http]=pgsql-http - [plpgsql_check]=plpgsql-check - [safeupdate]=pg-safeupdate - [supabase_vault]=vault - [vector]=pgvector -) - -# Catalog key -> "owner/repo", where the nix file's own repo variable/fetcher -# doesn't resolve to the real GitHub repo (fetchurl-based, or repo != pname). -declare -A repo_override=( - [pg_plan_filter]=pgexperts/pg_plan_filter - [postgis]=postgis/postgis - [pgroonga]=pgroonga/pgroonga -) - -# Extensions where we don't attempt a hash bump (fetchurl-based source, or a -# cargoHash/pgrx vendor hash that needs an actual build to compute). Version -# gets bumped anyway with a placeholder hash for a human to fill in. -no_hash_exts="pg_graphql wrappers postgis pgroonga" - -for ext in $(jq -r 'keys[]' "$versions_file"); do - base="${file_alias[$ext]:-$ext}" - nixfile="nix/ext/$base.nix" - [ -f "$nixfile" ] || nixfile="nix/ext/$base/default.nix" - [ -f "$nixfile" ] || { - echo "skip $ext: no nix file found" - continue - } - - if [ -n "${repo_override[$ext]:-}" ]; then - owner="${repo_override[$ext]%%/*}" - repo="${repo_override[$ext]#*/}" - else - pname=$(sed -n 's/.*pname = "\(.*\)".*/\1/p' "$nixfile" | head -1) - owner=$(sed -n 's/.*owner = "\(.*\)".*/\1/p' "$nixfile" | head -1) - repo=$(sed -n 's/.*repo = "\(.*\)".*/\1/p' "$nixfile" | head -1) - if [ -z "$owner" ]; then - # e.g. `owner = repoOwner;` with `repoOwner = "theory";` defined separately. - ownervar=$(sed -n 's/.*owner = \([A-Za-z_][A-Za-z0-9_]*\);.*/\1/p' "$nixfile" | head -1) - [ -n "$ownervar" ] && owner=$(sed -n "s/.*$ownervar = \"\\(.*\\)\".*/\\1/p" "$nixfile" | head -1) - fi - [ -n "$repo" ] || repo="$pname" - [ -n "$owner" ] || { - echo "skip $ext: no owner" - continue - } - fi - - tags_raw=$(gh api "repos/$owner/$repo/tags" --paginate --jq '.[].name' 2>/dev/null) || - { - echo "skip $ext: tags lookup failed" - continue - } - - # Only trust clean vX.Y[.Z...] / ver_X.Y[.Z...] tags, or repo-prefixed - # underscore tags (wal2json_2_6) - repos also carry packaging/branch tags - # (debian/1.4.0-2, loader-2.11.0p1, ...) that aren't real releases. - best=$({ - printf '%s\n' "$tags_raw" | - grep -E '^(v|ver_)?[0-9]+(\.[0-9]+){1,3}$' | - while read -r t; do - v="${t#ver_}" - v="${v#v}" - printf '%s\t%s\n' "$v" "$t" - done - printf '%s\n' "$tags_raw" | - grep -E '^[A-Za-z][A-Za-z0-9]*[-_][0-9]+([._][0-9]+){1,3}$' | - while read -r t; do - v=$(printf '%s' "$t" | grep -oE '[0-9]+([._][0-9]+){1,3}$' | tr '_' '.') - printf '%s\t%s\n' "$v" "$t" - done - } | sort -t "$(printf '\t')" -k1,1 -V | tail -1) || true - [ -n "$best" ] || { - echo "skip $ext: no clean version tags" - continue - } - candidate=$(printf '%s' "$best" | cut -f1) - tag=$(printf '%s' "$best" | cut -f2) - - current=$(jq -r --arg e "$ext" '.[$e] | keys | max_by(split(".") | map(tonumber? // 0))' "$versions_file") - highest=$(printf '%s\n%s\n' "$current" "$candidate" | sort -V | tail -1) - [ "$highest" = "$candidate" ] && [ "$candidate" != "$current" ] || continue - - postgresql=$(jq -c --arg e "$ext" '.[$e] | to_entries | max_by(.key) | .value.postgresql' "$versions_file") - - case " $no_hash_exts " in - *" $ext "*) - sri_hash="" - ;; - *) - url="https://github.com/$owner/$repo/archive/$tag.tar.gz" - hash=$(nix-prefetch-url --type sha256 --unpack "$url" 2>/dev/null | tail -1) || { - echo "skip $ext: prefetch failed for $tag" - continue - } - sri_hash=$(nix hash to-sri --type sha256 "$hash") - ;; - esac - - jq --arg e "$ext" --arg v "$candidate" --arg rev "$tag" --arg hash "$sri_hash" --argjson pg "$postgresql" \ - '.[$e][$v] = {postgresql: $pg, revision: $rev, rev: $rev, hash: $hash}' \ - "$versions_file" >"$versions_file.tmp" && mv "$versions_file.tmp" "$versions_file" - - if [ -z "$sri_hash" ]; then - echo "updated $ext -> $candidate ($tag) [no hash, needs manual fill-in]" - else - echo "updated $ext -> $candidate ($tag)" - fi - changed=1 -done - -if [ -n "${GITHUB_OUTPUT:-}" ]; then - echo "changed=$changed" >>"$GITHUB_OUTPUT" -fi