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 01/21] 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 02/21] 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 03/21] 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 04/21] 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 From 92fa6b04b69488521a070c9292a8bb5a2b0296b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Wed, 9 Sep 2026 18:27:28 +0300 Subject: [PATCH 05/21] ci: mark extension version PRs as draft with don't merge label --- .github/workflows/check-extension-versions.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/check-extension-versions.yml b/.github/workflows/check-extension-versions.yml index 18888a57c8..a03a59e1fa 100644 --- a/.github/workflows/check-extension-versions.yml +++ b/.github/workflows/check-extension-versions.yml @@ -29,9 +29,14 @@ jobs: 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. + body: | + DO NOT MERGE + + Automated weekly check of `nix/ext/versions.json` against upstream GitHub tags. + draft: true branch: auto-update-extension-versions base: develop labels: | dependencies automated + don't merge From 13abccc49b28d3eae89270a2fbc55dada39903d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Wed, 9 Sep 2026 18:51:56 +0300 Subject: [PATCH 06/21] refactor: use standard fake hash placeholder, shrink script --- nix/tools/check-ext-versions.py | 90 ++++++++++++--------------------- 1 file changed, 32 insertions(+), 58 deletions(-) diff --git a/nix/tools/check-ext-versions.py b/nix/tools/check-ext-versions.py index 516d498f43..9a9d77c206 100755 --- a/nix/tools/check-ext-versions.py +++ b/nix/tools/check-ext-versions.py @@ -15,20 +15,23 @@ # `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. +# fetchurl-based source, or a cargoHash/pgrx vendor hash - not a plain GitHub +# archive, so bump the version with Nix's standard placeholder hash instead. NO_HASH_EXTS = {"pg_graphql", "wrappers", "postgis", "pgroonga"} +FAKE_HASH = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" CLEAN_TAG_RE = re.compile(r"^(?:v|ver_)?(\d+(?:\.\d+){0,3})$") LEADING_VERSION_RE = re.compile(r"^(\d+(?:\.\d+)*)") +def run(*args: str) -> str | None: + result = subprocess.run(args, capture_output=True, text=True, cwd=REPO_ROOT) + return result.stdout.strip() if result.returncode == 0 else None + + 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(".")) + return tuple(int(p) for p in m.group(1).split(".")) if m else (0,) def best_candidate(tags: list[str], repo: str) -> tuple[tuple[int, ...], str] | None: @@ -37,14 +40,12 @@ def best_candidate(tags: list[str], repo: str) -> tuple[tuple[int, ...], str] | prefixed_re = re.compile( rf"^{re.escape(repo)}[-_](\d+(?:[._]\d+){{1,3}})$", re.IGNORECASE ) - candidates: list[tuple[tuple[int, ...], str]] = [] + versions = [] 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) + if m: + versions.append((parse_version(m.group(1).replace("_", ".")), tag)) + return max(versions, default=None) def github_metadata(system: str) -> dict[str, str]: @@ -52,60 +53,34 @@ def github_metadata(system: str) -> dict[str, str]: "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 + out = run( + "nix", + "eval", + "--json", + f".#legacyPackages.{system}.psql_15.exts", + "--apply", + expr, + ) + assert out, "nix eval of extension metadata failed" 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)] + out = run("gh", "api", f"repos/{owner}/{repo}/tags", "--paginate") + return [t["name"] for t in json.loads(out)] if out is not None else None 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: + sha256 = run("nix-prefetch-url", "--type", "sha256", "--unpack", url) + if sha256 is None: 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() + return run("nix", "hash", "to-sri", "--type", "sha256", sha256.splitlines()[-1]) def main() -> None: - system = subprocess.run( - ["nix", "eval", "--impure", "--raw", "--expr", "builtins.currentSystem"], - capture_output=True, - text=True, - check=True, - ).stdout.strip() + system = run("nix", "eval", "--impure", "--raw", "--expr", "builtins.currentSystem") + assert system, "nix eval of builtins.currentSystem failed" with open(VERSIONS_FILE) as f: versions = json.load(f) @@ -134,14 +109,14 @@ def main() -> None: continue if ext in NO_HASH_EXTS: - sri_hash = "" + sri_hash = FAKE_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) + version_str = ".".join(map(str, candidate_version)) entries[version_str] = { "postgresql": entries[current_key]["postgresql"], "revision": tag, @@ -149,8 +124,7 @@ def main() -> None: "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}") + print(f"updated {ext} -> {version_str} ({tag})") if changed: with open(VERSIONS_FILE, "w") as f: From a59a9a0ca64b72f5307ee58620337c307ef13d73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Wed, 9 Sep 2026 18:59:22 +0300 Subject: [PATCH 07/21] refactor: drop dead GITHUB_OUTPUT write, tighten a few functions --- nix/tools/check-ext-versions.py | 35 ++++++++++++--------------------- 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/nix/tools/check-ext-versions.py b/nix/tools/check-ext-versions.py index 9a9d77c206..f48f89cd6c 100755 --- a/nix/tools/check-ext-versions.py +++ b/nix/tools/check-ext-versions.py @@ -5,7 +5,6 @@ import os import re import subprocess -import sys REPO_ROOT = subprocess.run( ["git", "rev-parse", "--show-toplevel"], capture_output=True, text=True, check=True @@ -40,11 +39,11 @@ def best_candidate(tags: list[str], repo: str) -> tuple[tuple[int, ...], str] | prefixed_re = re.compile( rf"^{re.escape(repo)}[-_](\d+(?:[._]\d+){{1,3}})$", re.IGNORECASE ) - versions = [] - for tag in tags: - m = CLEAN_TAG_RE.match(tag) or prefixed_re.match(tag) - if m: - versions.append((parse_version(m.group(1).replace("_", ".")), tag)) + versions = [ + (parse_version(m.group(1).replace("_", ".")), tag) + for tag in tags + if (m := CLEAN_TAG_RE.match(tag) or prefixed_re.match(tag)) + ] return max(versions, default=None) @@ -73,9 +72,9 @@ def fetch_tags(owner: str, repo: str) -> list[str] | None: def prefetch_hash(owner: str, repo: str, tag: str) -> str | None: url = f"https://github.com/{owner}/{repo}/archive/{tag}.tar.gz" sha256 = run("nix-prefetch-url", "--type", "sha256", "--unpack", url) - if sha256 is None: - return None - return run("nix", "hash", "to-sri", "--type", "sha256", sha256.splitlines()[-1]) + return sha256 and run( + "nix", "hash", "to-sri", "--type", "sha256", sha256.splitlines()[-1] + ) def main() -> None: @@ -108,13 +107,10 @@ def main() -> None: if candidate_version <= parse_version(current_key): continue - if ext in NO_HASH_EXTS: - sri_hash = FAKE_HASH - else: - sri_hash = prefetch_hash(owner, repo, tag) - if sri_hash is None: - print(f"skip {ext}: prefetch failed for {tag}") - continue + sri_hash = FAKE_HASH if ext in NO_HASH_EXTS else prefetch_hash(owner, repo, tag) + if sri_hash is None: + print(f"skip {ext}: prefetch failed for {tag}") + continue version_str = ".".join(map(str, candidate_version)) entries[version_str] = { @@ -131,11 +127,6 @@ def main() -> None: 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()) + main() From c986bf01682e7e0d572ca56fff354d576c21b676 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Wed, 9 Sep 2026 19:14:13 +0300 Subject: [PATCH 08/21] rewrite check-ext-versions in nushell, 107 lines vs 132 in python Nushell's native command-output capture and structured JSON eliminate most of Python's subprocess boilerplate. Verified byte-identical output against the same set of upstream tag lookups. --- .../workflows/check-extension-versions.yml | 2 +- nix/tools/check-ext-versions.nu | 107 ++++++++++++++ nix/tools/check-ext-versions.py | 132 ------------------ 3 files changed, 108 insertions(+), 133 deletions(-) create mode 100755 nix/tools/check-ext-versions.nu delete mode 100755 nix/tools/check-ext-versions.py diff --git a/.github/workflows/check-extension-versions.yml b/.github/workflows/check-extension-versions.yml index a03a59e1fa..caf2c481bb 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: python3 nix/tools/check-ext-versions.py + run: nix run nixpkgs#nushell -- nix/tools/check-ext-versions.nu - name: Create Pull Request uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8 diff --git a/nix/tools/check-ext-versions.nu b/nix/tools/check-ext-versions.nu new file mode 100755 index 0000000000..5a7c74f94c --- /dev/null +++ b/nix/tools/check-ext-versions.nu @@ -0,0 +1,107 @@ +#!/usr/bin/env nu +# Check nix/ext/versions.json extensions against upstream GitHub tags. + +# `exts` attribute name -> versions.json catalog key, where they differ. +const ATTR_TO_CATALOG_KEY = {plan_filter: "pg_plan_filter"} + +# fetchurl-based source, or a cargoHash/pgrx vendor hash - not a plain GitHub +# archive, so bump the version with Nix's standard placeholder hash instead. +const NO_HASH_EXTS = ["pg_graphql" "wrappers" "postgis" "pgroonga"] +const FAKE_HASH = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" + +def run [command: list] { + let r = (run-external ...$command | complete) + if $r.exit_code == 0 { $r.stdout | str trim } else { null } +} + +def parse-version [s: string] { + let m = ($s | parse --regex '^(\d+(?:\.\d+)*)') + if ($m | is-empty) { [0] } else { $m.0.capture0 | split row "." | each { into int } } +} + +def is-newer [current: list, candidate: list] { + $candidate != $current and ([{v: $current}, {v: $candidate}] | sort-by v | last | get v) == $candidate +} + +# repo-prefixed underscore tags (wal2json_2_6) are only trusted when the +# prefix is the actual repo name, not any legacy tag scheme (REL0_9_1). +def best-candidate [tags: list, repo: string] { + let prefixed = ("^" + $repo + "[-_](\\d+(?:[._]\\d+){1,3})$") + let candidates = ( + $tags | each { |tag| + let dtag = ($tag | str downcase) + let m = ($dtag | parse --regex '^(?:v|ver_)?(\d+(?:\.\d+){0,3})$') + let m = if ($m | is-empty) { $dtag | parse --regex $prefixed } else { $m } + if ($m | is-empty) { null } else { + {v: (parse-version ($m.0.capture0 | str replace --all "_" ".")), tag: $tag} + } + } | compact + ) + if ($candidates | is-empty) { null } else { $candidates | sort-by v | last } +} + +def github-metadata [system: string] { + let expr = "exts: builtins.listToAttrs (map (n: { name = n; value = exts.${n}.github; }) (builtins.filter (n: exts.${n} ? github) (builtins.attrNames exts)))" + let out = (run ["nix" "eval" "--json" $".#legacyPackages.($system).psql_15.exts" "--apply" $expr]) + if $out == null { error make {msg: "nix eval of extension metadata failed"} } + $out | from json +} + +def fetch-tags [owner: string, repo: string] { + let out = (run ["gh" "api" $"repos/($owner)/($repo)/tags" "--paginate"]) + if $out == null { null } else { $out | from json | get name } +} + +def prefetch-hash [owner: string, repo: string, tag: string] { + let url = $"https://github.com/($owner)/($repo)/archive/($tag).tar.gz" + let sha256 = (run ["nix-prefetch-url" "--type" "sha256" "--unpack" $url]) + if $sha256 == null { null } else { + run ["nix" "hash" "to-sri" "--type" "sha256" ($sha256 | lines | last)] + } +} + +def main [] { + let system = (run ["nix" "eval" "--impure" "--raw" "--expr" "builtins.currentSystem"]) + if $system == null { error make {msg: "nix eval of builtins.currentSystem failed"} } + + let repo_root = (run ["git" "rev-parse" "--show-toplevel"]) + let versions_file = ($repo_root | path join "nix/ext/versions.json") + mut versions = (open $versions_file) + mut changed = false + + for it in (github-metadata $system | transpose attr repo_slug) { + let ext = ($ATTR_TO_CATALOG_KEY | get -o $it.attr | default $it.attr) + if not ($ext in ($versions | columns)) { continue } + let parts = ($it.repo_slug | split row "/") + let owner = $parts.0 + let repo = $parts.1 + + let tags = (fetch-tags $owner $repo) + if $tags == null { print $"skip ($ext): tags lookup failed"; continue } + + let candidate = (best-candidate $tags $repo) + if $candidate == null { print $"skip ($ext): no clean version tags"; continue } + + let entries = ($versions | get $ext) + let current_key = ($entries | columns | each { |k| {k: $k, v: (parse-version $k)} } | sort-by v | last | get k) + if not (is-newer (parse-version $current_key) $candidate.v) { continue } + + let sri_hash = if $ext in $NO_HASH_EXTS { $FAKE_HASH } else { prefetch-hash $owner $repo $candidate.tag } + if $sri_hash == null { print $"skip ($ext): prefetch failed for ($candidate.tag)"; continue } + + let version_str = ($candidate.v | each { into string } | str join ".") + let entry = { + postgresql: ($entries | get $current_key | get postgresql) + revision: $candidate.tag + rev: $candidate.tag + hash: $sri_hash + } + $versions = ($versions | upsert $ext ($entries | upsert $version_str $entry)) + $changed = true + print $"updated ($ext) -> ($version_str) \(($candidate.tag)\)" + } + + if $changed { + $versions | to json --indent 2 | $"($in)\n" | save -f $versions_file + } +} diff --git a/nix/tools/check-ext-versions.py b/nix/tools/check-ext-versions.py deleted file mode 100755 index f48f89cd6c..0000000000 --- a/nix/tools/check-ext-versions.py +++ /dev/null @@ -1,132 +0,0 @@ -#!/usr/bin/env python3 -"""Check nix/ext/versions.json extensions against upstream GitHub tags.""" - -import json -import os -import re -import subprocess - -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"} - -# fetchurl-based source, or a cargoHash/pgrx vendor hash - not a plain GitHub -# archive, so bump the version with Nix's standard placeholder hash instead. -NO_HASH_EXTS = {"pg_graphql", "wrappers", "postgis", "pgroonga"} -FAKE_HASH = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" - -CLEAN_TAG_RE = re.compile(r"^(?:v|ver_)?(\d+(?:\.\d+){0,3})$") -LEADING_VERSION_RE = re.compile(r"^(\d+(?:\.\d+)*)") - - -def run(*args: str) -> str | None: - result = subprocess.run(args, capture_output=True, text=True, cwd=REPO_ROOT) - return result.stdout.strip() if result.returncode == 0 else None - - -def parse_version(s: str) -> tuple[int, ...]: - m = LEADING_VERSION_RE.match(s) - return tuple(int(p) for p in m.group(1).split(".")) if m else (0,) - - -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 - ) - versions = [ - (parse_version(m.group(1).replace("_", ".")), tag) - for tag in tags - if (m := CLEAN_TAG_RE.match(tag) or prefixed_re.match(tag)) - ] - return max(versions, 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 = run( - "nix", - "eval", - "--json", - f".#legacyPackages.{system}.psql_15.exts", - "--apply", - expr, - ) - assert out, "nix eval of extension metadata failed" - return json.loads(out) - - -def fetch_tags(owner: str, repo: str) -> list[str] | None: - out = run("gh", "api", f"repos/{owner}/{repo}/tags", "--paginate") - return [t["name"] for t in json.loads(out)] if out is not None else None - - -def prefetch_hash(owner: str, repo: str, tag: str) -> str | None: - url = f"https://github.com/{owner}/{repo}/archive/{tag}.tar.gz" - sha256 = run("nix-prefetch-url", "--type", "sha256", "--unpack", url) - return sha256 and run( - "nix", "hash", "to-sri", "--type", "sha256", sha256.splitlines()[-1] - ) - - -def main() -> None: - system = run("nix", "eval", "--impure", "--raw", "--expr", "builtins.currentSystem") - assert system, "nix eval of builtins.currentSystem failed" - - 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 - - sri_hash = FAKE_HASH if ext in NO_HASH_EXTS else prefetch_hash(owner, repo, tag) - if sri_hash is None: - print(f"skip {ext}: prefetch failed for {tag}") - continue - - version_str = ".".join(map(str, candidate_version)) - entries[version_str] = { - "postgresql": entries[current_key]["postgresql"], - "revision": tag, - "rev": tag, - "hash": sri_hash, - } - changed = True - print(f"updated {ext} -> {version_str} ({tag})") - - if changed: - with open(VERSIONS_FILE, "w") as f: - json.dump(versions, f, indent=2) - f.write("\n") - - -if __name__ == "__main__": - main() From fdde045ce9ce5f27b381f3f7f28bd41bc46eab6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Wed, 9 Sep 2026 19:15:30 +0300 Subject: [PATCH 09/21] shorten comments in check-ext-versions.nu --- nix/tools/check-ext-versions.nu | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/nix/tools/check-ext-versions.nu b/nix/tools/check-ext-versions.nu index 5a7c74f94c..f339ee7487 100755 --- a/nix/tools/check-ext-versions.nu +++ b/nix/tools/check-ext-versions.nu @@ -1,11 +1,8 @@ #!/usr/bin/env nu -# Check nix/ext/versions.json extensions against upstream GitHub tags. -# `exts` attribute name -> versions.json catalog key, where they differ. const ATTR_TO_CATALOG_KEY = {plan_filter: "pg_plan_filter"} -# fetchurl-based source, or a cargoHash/pgrx vendor hash - not a plain GitHub -# archive, so bump the version with Nix's standard placeholder hash instead. +# not a plain GitHub archive (fetchurl, or a cargoHash/pgrx vendor hash) const NO_HASH_EXTS = ["pg_graphql" "wrappers" "postgis" "pgroonga"] const FAKE_HASH = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" @@ -23,8 +20,7 @@ def is-newer [current: list, candidate: list] { $candidate != $current and ([{v: $current}, {v: $candidate}] | sort-by v | last | get v) == $candidate } -# repo-prefixed underscore tags (wal2json_2_6) are only trusted when the -# prefix is the actual repo name, not any legacy tag scheme (REL0_9_1). +# underscore tags (wal2json_2_6) only count when prefixed with the repo name def best-candidate [tags: list, repo: string] { let prefixed = ("^" + $repo + "[-_](\\d+(?:[._]\\d+){1,3})$") let candidates = ( From 1efbdcda3d85e6fa2ba4853fb4ebc6d949334ba1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Wed, 9 Sep 2026 19:24:41 +0300 Subject: [PATCH 10/21] drop unnecessary main and single-use helper functions Top-level mut/for/continue work fine in a nu script without wrapping in def main. Inlined github-metadata, fetch-tags, and prefetch-hash since each was called exactly once. 103 -> 94 lines. --- nix/tools/check-ext-versions.nu | 113 +++++++++++++++----------------- 1 file changed, 52 insertions(+), 61 deletions(-) diff --git a/nix/tools/check-ext-versions.nu b/nix/tools/check-ext-versions.nu index f339ee7487..50cd4859d5 100755 --- a/nix/tools/check-ext-versions.nu +++ b/nix/tools/check-ext-versions.nu @@ -36,68 +36,59 @@ def best-candidate [tags: list, repo: string] { if ($candidates | is-empty) { null } else { $candidates | sort-by v | last } } -def github-metadata [system: string] { - let expr = "exts: builtins.listToAttrs (map (n: { name = n; value = exts.${n}.github; }) (builtins.filter (n: exts.${n} ? github) (builtins.attrNames exts)))" - let out = (run ["nix" "eval" "--json" $".#legacyPackages.($system).psql_15.exts" "--apply" $expr]) - if $out == null { error make {msg: "nix eval of extension metadata failed"} } - $out | from json -} - -def fetch-tags [owner: string, repo: string] { - let out = (run ["gh" "api" $"repos/($owner)/($repo)/tags" "--paginate"]) - if $out == null { null } else { $out | from json | get name } -} - -def prefetch-hash [owner: string, repo: string, tag: string] { - let url = $"https://github.com/($owner)/($repo)/archive/($tag).tar.gz" - let sha256 = (run ["nix-prefetch-url" "--type" "sha256" "--unpack" $url]) - if $sha256 == null { null } else { - run ["nix" "hash" "to-sri" "--type" "sha256" ($sha256 | lines | last)] - } -} - -def main [] { - let system = (run ["nix" "eval" "--impure" "--raw" "--expr" "builtins.currentSystem"]) - if $system == null { error make {msg: "nix eval of builtins.currentSystem failed"} } - - let repo_root = (run ["git" "rev-parse" "--show-toplevel"]) - let versions_file = ($repo_root | path join "nix/ext/versions.json") - mut versions = (open $versions_file) - mut changed = false - - for it in (github-metadata $system | transpose attr repo_slug) { - let ext = ($ATTR_TO_CATALOG_KEY | get -o $it.attr | default $it.attr) - if not ($ext in ($versions | columns)) { continue } - let parts = ($it.repo_slug | split row "/") - let owner = $parts.0 - let repo = $parts.1 - - let tags = (fetch-tags $owner $repo) - if $tags == null { print $"skip ($ext): tags lookup failed"; continue } - - let candidate = (best-candidate $tags $repo) - if $candidate == null { print $"skip ($ext): no clean version tags"; continue } - - let entries = ($versions | get $ext) - let current_key = ($entries | columns | each { |k| {k: $k, v: (parse-version $k)} } | sort-by v | last | get k) - if not (is-newer (parse-version $current_key) $candidate.v) { continue } - - let sri_hash = if $ext in $NO_HASH_EXTS { $FAKE_HASH } else { prefetch-hash $owner $repo $candidate.tag } - if $sri_hash == null { print $"skip ($ext): prefetch failed for ($candidate.tag)"; continue } - - let version_str = ($candidate.v | each { into string } | str join ".") - let entry = { - postgresql: ($entries | get $current_key | get postgresql) - revision: $candidate.tag - rev: $candidate.tag - hash: $sri_hash +let system = (run ["nix" "eval" "--impure" "--raw" "--expr" "builtins.currentSystem"]) +if $system == null { error make {msg: "nix eval of builtins.currentSystem failed"} } + +let repo_root = (run ["git" "rev-parse" "--show-toplevel"]) +let versions_file = ($repo_root | path join "nix/ext/versions.json") +mut versions = (open $versions_file) +mut changed = false + +let exts_expr = "exts: builtins.listToAttrs (map (n: { name = n; value = exts.${n}.github; }) (builtins.filter (n: exts.${n} ? github) (builtins.attrNames exts)))" +let exts_json = (run ["nix" "eval" "--json" $".#legacyPackages.($system).psql_15.exts" "--apply" $exts_expr]) +if $exts_json == null { error make {msg: "nix eval of extension metadata failed"} } + +for it in ($exts_json | from json | transpose attr repo_slug) { + let ext = ($ATTR_TO_CATALOG_KEY | get -o $it.attr | default $it.attr) + if not ($ext in ($versions | columns)) { continue } + let parts = ($it.repo_slug | split row "/") + let owner = $parts.0 + let repo = $parts.1 + + let tags_json = (run ["gh" "api" $"repos/($owner)/($repo)/tags" "--paginate"]) + if $tags_json == null { print $"skip ($ext): tags lookup failed"; continue } + let tags = ($tags_json | from json | get name) + + let candidate = (best-candidate $tags $repo) + if $candidate == null { print $"skip ($ext): no clean version tags"; continue } + + let entries = ($versions | get $ext) + let current_key = ($entries | columns | each { |k| {k: $k, v: (parse-version $k)} } | sort-by v | last | get k) + if not (is-newer (parse-version $current_key) $candidate.v) { continue } + + let sri_hash = if $ext in $NO_HASH_EXTS { + $FAKE_HASH + } else { + let url = $"https://github.com/($owner)/($repo)/archive/($candidate.tag).tar.gz" + let sha256 = (run ["nix-prefetch-url" "--type" "sha256" "--unpack" $url]) + if $sha256 == null { null } else { + run ["nix" "hash" "to-sri" "--type" "sha256" ($sha256 | lines | last)] } - $versions = ($versions | upsert $ext ($entries | upsert $version_str $entry)) - $changed = true - print $"updated ($ext) -> ($version_str) \(($candidate.tag)\)" } - - if $changed { - $versions | to json --indent 2 | $"($in)\n" | save -f $versions_file + if $sri_hash == null { print $"skip ($ext): prefetch failed for ($candidate.tag)"; continue } + + let version_str = ($candidate.v | each { into string } | str join ".") + let entry = { + postgresql: ($entries | get $current_key | get postgresql) + revision: $candidate.tag + rev: $candidate.tag + hash: $sri_hash } + $versions = ($versions | upsert $ext ($entries | upsert $version_str $entry)) + $changed = true + print $"updated ($ext) -> ($version_str) \(($candidate.tag)\)" +} + +if $changed { + $versions | to json --indent 2 | $"($in)\n" | save -f $versions_file } From 07fb243ebfd8a962c657586c1342b029e9bc7065 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Wed, 9 Sep 2026 19:25:57 +0300 Subject: [PATCH 11/21] drop comment on best-candidate --- nix/tools/check-ext-versions.nu | 1 - 1 file changed, 1 deletion(-) diff --git a/nix/tools/check-ext-versions.nu b/nix/tools/check-ext-versions.nu index 50cd4859d5..e4e88cf9c9 100755 --- a/nix/tools/check-ext-versions.nu +++ b/nix/tools/check-ext-versions.nu @@ -20,7 +20,6 @@ def is-newer [current: list, candidate: list] { $candidate != $current and ([{v: $current}, {v: $candidate}] | sort-by v | last | get v) == $candidate } -# underscore tags (wal2json_2_6) only count when prefixed with the repo name def best-candidate [tags: list, repo: string] { let prefixed = ("^" + $repo + "[-_](\\d+(?:[._]\\d+){1,3})$") let candidates = ( From 09624ede1a03229fec20d5f47ad55d01b88b13d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Wed, 9 Sep 2026 19:33:22 +0300 Subject: [PATCH 12/21] expand single-letter let bindings (r, m) to descriptive names --- nix/tools/check-ext-versions.nu | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/nix/tools/check-ext-versions.nu b/nix/tools/check-ext-versions.nu index e4e88cf9c9..8147007229 100755 --- a/nix/tools/check-ext-versions.nu +++ b/nix/tools/check-ext-versions.nu @@ -7,13 +7,13 @@ const NO_HASH_EXTS = ["pg_graphql" "wrappers" "postgis" "pgroonga"] const FAKE_HASH = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" def run [command: list] { - let r = (run-external ...$command | complete) - if $r.exit_code == 0 { $r.stdout | str trim } else { null } + let result = (run-external ...$command | complete) + if $result.exit_code == 0 { $result.stdout | str trim } else { null } } -def parse-version [s: string] { - let m = ($s | parse --regex '^(\d+(?:\.\d+)*)') - if ($m | is-empty) { [0] } else { $m.0.capture0 | split row "." | each { into int } } +def parse-version [text: string] { + let match = ($text | parse --regex '^(\d+(?:\.\d+)*)') + if ($match | is-empty) { [0] } else { $match.0.capture0 | split row "." | each { into int } } } def is-newer [current: list, candidate: list] { @@ -25,10 +25,10 @@ def best-candidate [tags: list, repo: string] { let candidates = ( $tags | each { |tag| let dtag = ($tag | str downcase) - let m = ($dtag | parse --regex '^(?:v|ver_)?(\d+(?:\.\d+){0,3})$') - let m = if ($m | is-empty) { $dtag | parse --regex $prefixed } else { $m } - if ($m | is-empty) { null } else { - {v: (parse-version ($m.0.capture0 | str replace --all "_" ".")), tag: $tag} + let match = ($dtag | parse --regex '^(?:v|ver_)?(\d+(?:\.\d+){0,3})$') + let match = if ($match | is-empty) { $dtag | parse --regex $prefixed } else { $match } + if ($match | is-empty) { null } else { + {v: (parse-version ($match.0.capture0 | str replace --all "_" ".")), tag: $tag} } } | compact ) From 2b58172eff7b9e4a32a74d56e29a4ef052f23401 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Wed, 9 Sep 2026 19:47:44 +0300 Subject: [PATCH 13/21] collapse entry record and hash-prefetch null-check to one-liners --- nix/tools/check-ext-versions.nu | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/nix/tools/check-ext-versions.nu b/nix/tools/check-ext-versions.nu index 8147007229..14e5eaa3d3 100755 --- a/nix/tools/check-ext-versions.nu +++ b/nix/tools/check-ext-versions.nu @@ -70,19 +70,12 @@ for it in ($exts_json | from json | transpose attr repo_slug) { } else { let url = $"https://github.com/($owner)/($repo)/archive/($candidate.tag).tar.gz" let sha256 = (run ["nix-prefetch-url" "--type" "sha256" "--unpack" $url]) - if $sha256 == null { null } else { - run ["nix" "hash" "to-sri" "--type" "sha256" ($sha256 | lines | last)] - } + if $sha256 == null { null } else { run ["nix" "hash" "to-sri" "--type" "sha256" ($sha256 | lines | last)] } } if $sri_hash == null { print $"skip ($ext): prefetch failed for ($candidate.tag)"; continue } let version_str = ($candidate.v | each { into string } | str join ".") - let entry = { - postgresql: ($entries | get $current_key | get postgresql) - revision: $candidate.tag - rev: $candidate.tag - hash: $sri_hash - } + let entry = {postgresql: ($entries | get $current_key | get postgresql), revision: $candidate.tag, rev: $candidate.tag, hash: $sri_hash} $versions = ($versions | upsert $ext ($entries | upsert $version_str $entry)) $changed = true print $"updated ($ext) -> ($version_str) \(($candidate.tag)\)" From d464e85149a2f078269b3a98a68f82fa88bf5771 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Wed, 9 Sep 2026 20:06:16 +0300 Subject: [PATCH 14/21] error if no extensions with github metadata are found Previously an empty (but non-null) nix eval result would silently loop zero times and exit 0, indistinguishable from everything already being up to date. --- nix/tools/check-ext-versions.nu | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nix/tools/check-ext-versions.nu b/nix/tools/check-ext-versions.nu index 14e5eaa3d3..53d8c4cfd4 100755 --- a/nix/tools/check-ext-versions.nu +++ b/nix/tools/check-ext-versions.nu @@ -46,8 +46,10 @@ mut changed = false let exts_expr = "exts: builtins.listToAttrs (map (n: { name = n; value = exts.${n}.github; }) (builtins.filter (n: exts.${n} ? github) (builtins.attrNames exts)))" let exts_json = (run ["nix" "eval" "--json" $".#legacyPackages.($system).psql_15.exts" "--apply" $exts_expr]) if $exts_json == null { error make {msg: "nix eval of extension metadata failed"} } +let exts = ($exts_json | from json | transpose attr repo_slug) +if ($exts | is-empty) { error make {msg: "no extensions with github metadata found"} } -for it in ($exts_json | from json | transpose attr repo_slug) { +for it in $exts { let ext = ($ATTR_TO_CATALOG_KEY | get -o $it.attr | default $it.attr) if not ($ext in ($versions | columns)) { continue } let parts = ($it.repo_slug | split row "/") From 28fc2e31e8216e88c8ecbb0c5fb3ff8d4c7c2560 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Wed, 9 Sep 2026 20:08:07 +0300 Subject: [PATCH 15/21] rename workflow title to Check Extension Updates --- .github/workflows/check-extension-versions.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-extension-versions.yml b/.github/workflows/check-extension-versions.yml index caf2c481bb..3bd6dbcc64 100644 --- a/.github/workflows/check-extension-versions.yml +++ b/.github/workflows/check-extension-versions.yml @@ -1,4 +1,4 @@ -name: Check Extension Versions +name: Check Extension Updates on: workflow_dispatch: From 41baee6c13aba781f8284cf78ea0465ae80dbf6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Wed, 9 Sep 2026 20:17:44 +0300 Subject: [PATCH 16/21] package check-ext-versions as a flake package via writeNuBin Replaces 'nix run nixpkgs#nushell -- path/to/script.nu' with a proper nix/packages/check-ext-versions.nix wrapping the script via writers.writeNuBin, with gh/nix on PATH. Workflow now runs 'nix run .#check-ext-versions'. --- .github/workflows/check-extension-versions.yml | 2 +- nix/packages/check-ext-versions.nix | 17 +++++++++++++++++ nix/packages/default.nix | 1 + 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 nix/packages/check-ext-versions.nix diff --git a/.github/workflows/check-extension-versions.yml b/.github/workflows/check-extension-versions.yml index 3bd6dbcc64..485b4d4706 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 run nixpkgs#nushell -- nix/tools/check-ext-versions.nu + run: nix run .#check-ext-versions - name: Create Pull Request uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8 diff --git a/nix/packages/check-ext-versions.nix b/nix/packages/check-ext-versions.nix new file mode 100644 index 0000000000..8c5d4832d8 --- /dev/null +++ b/nix/packages/check-ext-versions.nix @@ -0,0 +1,17 @@ +{ + lib, + writers, + gh, + nix, +}: +writers.writeNuBin "check-ext-versions" { + makeWrapperArgs = [ + "--prefix" + "PATH" + ":" + (lib.makeBinPath [ + gh + nix + ]) + ]; +} (builtins.readFile ../tools/check-ext-versions.nu) diff --git a/nix/packages/default.nix b/nix/packages/default.nix index b7ad2b205b..062d1e31ca 100644 --- a/nix/packages/default.nix +++ b/nix/packages/default.nix @@ -106,6 +106,7 @@ inherit (self'.packages) overlayfs-on-package; }; sync-exts-versions = pkgs.callPackage ./sync-exts-versions.nix { inherit (inputs') nix-editor; }; + check-ext-versions = pkgs.callPackage ./check-ext-versions.nix { }; trigger-nix-build = pkgs.callPackage ./trigger-nix-build.nix { }; update-readme = pkgs.callPackage ./update-readme.nix { }; supabase-cli = pkgs.callPackage ./supabase-cli.nix { }; From 8557de6bfd874ce9be320f3106dec8cd2cdeb986 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Wed, 9 Sep 2026 22:33:25 +0300 Subject: [PATCH 17/21] move update-source config into a REPOS table, revert passthru.github Replaces per-package passthru.github metadata (and the nix eval needed to read it) with a single REPOS table in the checker script itself - this is CI tooling config, not package build metadata, so it belongs with the tool, not scattered across 30 unrelated package files. Also drops ATTR_TO_CATALOG_KEY and NO_HASH_EXTS as separate constants: both are now fields on the same REPOS entries. Iterates versions.json's own keys directly and errors by name if any catalog key has no REPOS entry, so adding a new extension without registering it here crashes loudly instead of silently never being checked. --- 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.nu | 55 +++++++++----- nix/tools/check-ext-versions.nu.bak2 | 109 +++++++++++++++++++++++++++ 32 files changed, 147 insertions(+), 47 deletions(-) create mode 100755 nix/tools/check-ext-versions.nu.bak2 diff --git a/nix/ext/hypopg.nix b/nix/ext/hypopg.nix index 4405e87c74..9bd4927263 100644 --- a/nix/ext/hypopg.nix +++ b/nix/ext/hypopg.nix @@ -98,7 +98,6 @@ 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 f9fee5aed2..5892127142 100644 --- a/nix/ext/index_advisor.nix +++ b/nix/ext/index_advisor.nix @@ -83,7 +83,6 @@ 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 869a7199a7..452e9c2c5e 100644 --- a/nix/ext/pg-safeupdate.nix +++ b/nix/ext/pg-safeupdate.nix @@ -83,7 +83,6 @@ 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 965f7282fe..197420c982 100644 --- a/nix/ext/pg_cron/default.nix +++ b/nix/ext/pg_cron/default.nix @@ -139,7 +139,6 @@ 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 3a595a249b..cbab08aa76 100644 --- a/nix/ext/pg_graphql/default.nix +++ b/nix/ext/pg_graphql/default.nix @@ -184,7 +184,6 @@ 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 3c5d0da5aa..d9d4a34077 100644 --- a/nix/ext/pg_hashids.nix +++ b/nix/ext/pg_hashids.nix @@ -106,7 +106,6 @@ 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 848b507665..ec39a69fe2 100644 --- a/nix/ext/pg_jsonschema/default.nix +++ b/nix/ext/pg_jsonschema/default.nix @@ -184,7 +184,6 @@ 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 cb9e475512..f769bad314 100644 --- a/nix/ext/pg_net.nix +++ b/nix/ext/pg_net.nix @@ -145,7 +145,6 @@ 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 5eb5c7b6ea..0c8a4eee6e 100644 --- a/nix/ext/pg_partman.nix +++ b/nix/ext/pg_partman.nix @@ -100,7 +100,6 @@ 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 237b7b63e3..847c7a2d11 100644 --- a/nix/ext/pg_plan_filter.nix +++ b/nix/ext/pg_plan_filter.nix @@ -86,7 +86,6 @@ 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 c7c1ea6459..78d9764063 100644 --- a/nix/ext/pg_repack.nix +++ b/nix/ext/pg_repack.nix @@ -140,7 +140,6 @@ 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 89f55c2b38..9a58464a66 100644 --- a/nix/ext/pg_stat_monitor.nix +++ b/nix/ext/pg_stat_monitor.nix @@ -110,7 +110,6 @@ 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 e12756bc58..9c7ef86755 100644 --- a/nix/ext/pg_tle.nix +++ b/nix/ext/pg_tle.nix @@ -111,7 +111,6 @@ 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 f58ba8f4df..8fb727c666 100644 --- a/nix/ext/pgaudit.nix +++ b/nix/ext/pgaudit.nix @@ -240,7 +240,6 @@ 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 c9df64851b..348b534c34 100644 --- a/nix/ext/pgjwt.nix +++ b/nix/ext/pgjwt.nix @@ -82,7 +82,6 @@ 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 cc604f24a6..7e2076a2f5 100644 --- a/nix/ext/pgmq/default.nix +++ b/nix/ext/pgmq/default.nix @@ -103,7 +103,6 @@ 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 71e9f49c64..b9c3829a0d 100644 --- a/nix/ext/pgroonga/default.nix +++ b/nix/ext/pgroonga/default.nix @@ -181,7 +181,6 @@ 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 79f3af8f89..cc00281e70 100644 --- a/nix/ext/pgrouting/default.nix +++ b/nix/ext/pgrouting/default.nix @@ -153,7 +153,6 @@ 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 0e619e8cee..b5a2dcb72c 100644 --- a/nix/ext/pgsodium.nix +++ b/nix/ext/pgsodium.nix @@ -112,7 +112,6 @@ 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 1982885ddc..885fcc472e 100644 --- a/nix/ext/pgsql-http.nix +++ b/nix/ext/pgsql-http.nix @@ -113,7 +113,6 @@ 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 37697ea861..f283774dc8 100644 --- a/nix/ext/pgtap.nix +++ b/nix/ext/pgtap.nix @@ -134,7 +134,6 @@ 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 084cbaa22b..2db9d0c123 100644 --- a/nix/ext/pgvector.nix +++ b/nix/ext/pgvector.nix @@ -96,7 +96,6 @@ 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 ca6bd4de3b..a9901eb0a5 100644 --- a/nix/ext/plpgsql-check.nix +++ b/nix/ext/plpgsql-check.nix @@ -139,7 +139,6 @@ 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 e25f227125..731991937c 100644 --- a/nix/ext/plv8/default.nix +++ b/nix/ext/plv8/default.nix @@ -238,7 +238,6 @@ 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 9ff62ce78c..3f722d9ad9 100644 --- a/nix/ext/postgis.nix +++ b/nix/ext/postgis.nix @@ -256,7 +256,6 @@ 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 49b13e5953..31e6bae07b 100644 --- a/nix/ext/rum.nix +++ b/nix/ext/rum.nix @@ -107,7 +107,6 @@ 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 59f9cda7ec..dbfb2a8365 100644 --- a/nix/ext/timescaledb.nix +++ b/nix/ext/timescaledb.nix @@ -152,7 +152,6 @@ 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 ea26be5e3b..9ab5391b1d 100644 --- a/nix/ext/vault.nix +++ b/nix/ext/vault.nix @@ -99,7 +99,6 @@ 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 38fc2394e0..b082301c82 100644 --- a/nix/ext/wal2json.nix +++ b/nix/ext/wal2json.nix @@ -106,7 +106,6 @@ 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 2937a53428..3ae64afbd1 100644 --- a/nix/ext/wrappers/default.nix +++ b/nix/ext/wrappers/default.nix @@ -366,7 +366,6 @@ in } ''; passthru = { - github = "supabase/wrappers"; versions = versionsBuilt; numberOfVersions = numberOfVersionsBuilt; pname = "${pname}"; diff --git a/nix/tools/check-ext-versions.nu b/nix/tools/check-ext-versions.nu index 53d8c4cfd4..d3c379b2e5 100755 --- a/nix/tools/check-ext-versions.nu +++ b/nix/tools/check-ext-versions.nu @@ -1,9 +1,38 @@ #!/usr/bin/env nu -const ATTR_TO_CATALOG_KEY = {plan_filter: "pg_plan_filter"} +const REPOS = { + http: {repo: "pramsey/pgsql-http"} + hypopg: {repo: "HypoPG/hypopg"} + index_advisor: {repo: "supabase/index_advisor"} + pg_cron: {repo: "citusdata/pg_cron"} + pg_graphql: {repo: "supabase/pg_graphql", noHash: true} + pg_hashids: {repo: "iCyberon/pg_hashids"} + pg_jsonschema: {repo: "supabase/pg_jsonschema"} + pg_net: {repo: "supabase/pg_net"} + pg_partman: {repo: "pgpartman/pg_partman"} + pg_plan_filter: {repo: "pgexperts/pg_plan_filter"} + pg_repack: {repo: "reorg/pg_repack"} + pg_stat_monitor: {repo: "percona/pg_stat_monitor"} + pg_tle: {repo: "aws/pg_tle"} + pgaudit: {repo: "pgaudit/pgaudit"} + pgjwt: {repo: "michelp/pgjwt"} + pgmq: {repo: "pgmq/pgmq"} + pgroonga: {repo: "pgroonga/pgroonga", noHash: true} + pgrouting: {repo: "pgRouting/pgrouting"} + pgsodium: {repo: "michelp/pgsodium"} + pgtap: {repo: "theory/pgtap"} + plpgsql_check: {repo: "okbob/plpgsql_check"} + plv8: {repo: "plv8/plv8"} + postgis: {repo: "postgis/postgis", noHash: true} + rum: {repo: "postgrespro/rum"} + safeupdate: {repo: "eradman/pg-safeupdate"} + supabase_vault: {repo: "supabase/vault"} + timescaledb: {repo: "timescale/timescaledb"} + vector: {repo: "pgvector/pgvector"} + wal2json: {repo: "eulerto/wal2json"} + wrappers: {repo: "supabase/wrappers", noHash: true} +} -# not a plain GitHub archive (fetchurl, or a cargoHash/pgrx vendor hash) -const NO_HASH_EXTS = ["pg_graphql" "wrappers" "postgis" "pgroonga"] const FAKE_HASH = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" def run [command: list] { @@ -35,24 +64,15 @@ def best-candidate [tags: list, repo: string] { if ($candidates | is-empty) { null } else { $candidates | sort-by v | last } } -let system = (run ["nix" "eval" "--impure" "--raw" "--expr" "builtins.currentSystem"]) -if $system == null { error make {msg: "nix eval of builtins.currentSystem failed"} } - let repo_root = (run ["git" "rev-parse" "--show-toplevel"]) let versions_file = ($repo_root | path join "nix/ext/versions.json") mut versions = (open $versions_file) mut changed = false -let exts_expr = "exts: builtins.listToAttrs (map (n: { name = n; value = exts.${n}.github; }) (builtins.filter (n: exts.${n} ? github) (builtins.attrNames exts)))" -let exts_json = (run ["nix" "eval" "--json" $".#legacyPackages.($system).psql_15.exts" "--apply" $exts_expr]) -if $exts_json == null { error make {msg: "nix eval of extension metadata failed"} } -let exts = ($exts_json | from json | transpose attr repo_slug) -if ($exts | is-empty) { error make {msg: "no extensions with github metadata found"} } - -for it in $exts { - let ext = ($ATTR_TO_CATALOG_KEY | get -o $it.attr | default $it.attr) - if not ($ext in ($versions | columns)) { continue } - let parts = ($it.repo_slug | split row "/") +for ext in ($versions | columns) { + let info = ($REPOS | get -o $ext) + if $info == null { error make {msg: $"no update source configured for ($ext) - add it to REPOS in check-ext-versions.nu"} } + let parts = ($info.repo | split row "/") let owner = $parts.0 let repo = $parts.1 @@ -67,7 +87,8 @@ for it in $exts { let current_key = ($entries | columns | each { |k| {k: $k, v: (parse-version $k)} } | sort-by v | last | get k) if not (is-newer (parse-version $current_key) $candidate.v) { continue } - let sri_hash = if $ext in $NO_HASH_EXTS { + let no_hash = ($info | get -o noHash | default false) + let sri_hash = if $no_hash { $FAKE_HASH } else { let url = $"https://github.com/($owner)/($repo)/archive/($candidate.tag).tar.gz" diff --git a/nix/tools/check-ext-versions.nu.bak2 b/nix/tools/check-ext-versions.nu.bak2 new file mode 100755 index 0000000000..d3c379b2e5 --- /dev/null +++ b/nix/tools/check-ext-versions.nu.bak2 @@ -0,0 +1,109 @@ +#!/usr/bin/env nu + +const REPOS = { + http: {repo: "pramsey/pgsql-http"} + hypopg: {repo: "HypoPG/hypopg"} + index_advisor: {repo: "supabase/index_advisor"} + pg_cron: {repo: "citusdata/pg_cron"} + pg_graphql: {repo: "supabase/pg_graphql", noHash: true} + pg_hashids: {repo: "iCyberon/pg_hashids"} + pg_jsonschema: {repo: "supabase/pg_jsonschema"} + pg_net: {repo: "supabase/pg_net"} + pg_partman: {repo: "pgpartman/pg_partman"} + pg_plan_filter: {repo: "pgexperts/pg_plan_filter"} + pg_repack: {repo: "reorg/pg_repack"} + pg_stat_monitor: {repo: "percona/pg_stat_monitor"} + pg_tle: {repo: "aws/pg_tle"} + pgaudit: {repo: "pgaudit/pgaudit"} + pgjwt: {repo: "michelp/pgjwt"} + pgmq: {repo: "pgmq/pgmq"} + pgroonga: {repo: "pgroonga/pgroonga", noHash: true} + pgrouting: {repo: "pgRouting/pgrouting"} + pgsodium: {repo: "michelp/pgsodium"} + pgtap: {repo: "theory/pgtap"} + plpgsql_check: {repo: "okbob/plpgsql_check"} + plv8: {repo: "plv8/plv8"} + postgis: {repo: "postgis/postgis", noHash: true} + rum: {repo: "postgrespro/rum"} + safeupdate: {repo: "eradman/pg-safeupdate"} + supabase_vault: {repo: "supabase/vault"} + timescaledb: {repo: "timescale/timescaledb"} + vector: {repo: "pgvector/pgvector"} + wal2json: {repo: "eulerto/wal2json"} + wrappers: {repo: "supabase/wrappers", noHash: true} +} + +const FAKE_HASH = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" + +def run [command: list] { + let result = (run-external ...$command | complete) + if $result.exit_code == 0 { $result.stdout | str trim } else { null } +} + +def parse-version [text: string] { + let match = ($text | parse --regex '^(\d+(?:\.\d+)*)') + if ($match | is-empty) { [0] } else { $match.0.capture0 | split row "." | each { into int } } +} + +def is-newer [current: list, candidate: list] { + $candidate != $current and ([{v: $current}, {v: $candidate}] | sort-by v | last | get v) == $candidate +} + +def best-candidate [tags: list, repo: string] { + let prefixed = ("^" + $repo + "[-_](\\d+(?:[._]\\d+){1,3})$") + let candidates = ( + $tags | each { |tag| + let dtag = ($tag | str downcase) + let match = ($dtag | parse --regex '^(?:v|ver_)?(\d+(?:\.\d+){0,3})$') + let match = if ($match | is-empty) { $dtag | parse --regex $prefixed } else { $match } + if ($match | is-empty) { null } else { + {v: (parse-version ($match.0.capture0 | str replace --all "_" ".")), tag: $tag} + } + } | compact + ) + if ($candidates | is-empty) { null } else { $candidates | sort-by v | last } +} + +let repo_root = (run ["git" "rev-parse" "--show-toplevel"]) +let versions_file = ($repo_root | path join "nix/ext/versions.json") +mut versions = (open $versions_file) +mut changed = false + +for ext in ($versions | columns) { + let info = ($REPOS | get -o $ext) + if $info == null { error make {msg: $"no update source configured for ($ext) - add it to REPOS in check-ext-versions.nu"} } + let parts = ($info.repo | split row "/") + let owner = $parts.0 + let repo = $parts.1 + + let tags_json = (run ["gh" "api" $"repos/($owner)/($repo)/tags" "--paginate"]) + if $tags_json == null { print $"skip ($ext): tags lookup failed"; continue } + let tags = ($tags_json | from json | get name) + + let candidate = (best-candidate $tags $repo) + if $candidate == null { print $"skip ($ext): no clean version tags"; continue } + + let entries = ($versions | get $ext) + let current_key = ($entries | columns | each { |k| {k: $k, v: (parse-version $k)} } | sort-by v | last | get k) + if not (is-newer (parse-version $current_key) $candidate.v) { continue } + + let no_hash = ($info | get -o noHash | default false) + let sri_hash = if $no_hash { + $FAKE_HASH + } else { + let url = $"https://github.com/($owner)/($repo)/archive/($candidate.tag).tar.gz" + let sha256 = (run ["nix-prefetch-url" "--type" "sha256" "--unpack" $url]) + if $sha256 == null { null } else { run ["nix" "hash" "to-sri" "--type" "sha256" ($sha256 | lines | last)] } + } + if $sri_hash == null { print $"skip ($ext): prefetch failed for ($candidate.tag)"; continue } + + let version_str = ($candidate.v | each { into string } | str join ".") + let entry = {postgresql: ($entries | get $current_key | get postgresql), revision: $candidate.tag, rev: $candidate.tag, hash: $sri_hash} + $versions = ($versions | upsert $ext ($entries | upsert $version_str $entry)) + $changed = true + print $"updated ($ext) -> ($version_str) \(($candidate.tag)\)" +} + +if $changed { + $versions | to json --indent 2 | $"($in)\n" | save -f $versions_file +} From 91093dc8ba8a0b51b55a64f89428297e5132d0e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Wed, 9 Sep 2026 22:33:39 +0300 Subject: [PATCH 18/21] remove stray sed backup file --- nix/tools/check-ext-versions.nu.bak2 | 109 --------------------------- 1 file changed, 109 deletions(-) delete mode 100755 nix/tools/check-ext-versions.nu.bak2 diff --git a/nix/tools/check-ext-versions.nu.bak2 b/nix/tools/check-ext-versions.nu.bak2 deleted file mode 100755 index d3c379b2e5..0000000000 --- a/nix/tools/check-ext-versions.nu.bak2 +++ /dev/null @@ -1,109 +0,0 @@ -#!/usr/bin/env nu - -const REPOS = { - http: {repo: "pramsey/pgsql-http"} - hypopg: {repo: "HypoPG/hypopg"} - index_advisor: {repo: "supabase/index_advisor"} - pg_cron: {repo: "citusdata/pg_cron"} - pg_graphql: {repo: "supabase/pg_graphql", noHash: true} - pg_hashids: {repo: "iCyberon/pg_hashids"} - pg_jsonschema: {repo: "supabase/pg_jsonschema"} - pg_net: {repo: "supabase/pg_net"} - pg_partman: {repo: "pgpartman/pg_partman"} - pg_plan_filter: {repo: "pgexperts/pg_plan_filter"} - pg_repack: {repo: "reorg/pg_repack"} - pg_stat_monitor: {repo: "percona/pg_stat_monitor"} - pg_tle: {repo: "aws/pg_tle"} - pgaudit: {repo: "pgaudit/pgaudit"} - pgjwt: {repo: "michelp/pgjwt"} - pgmq: {repo: "pgmq/pgmq"} - pgroonga: {repo: "pgroonga/pgroonga", noHash: true} - pgrouting: {repo: "pgRouting/pgrouting"} - pgsodium: {repo: "michelp/pgsodium"} - pgtap: {repo: "theory/pgtap"} - plpgsql_check: {repo: "okbob/plpgsql_check"} - plv8: {repo: "plv8/plv8"} - postgis: {repo: "postgis/postgis", noHash: true} - rum: {repo: "postgrespro/rum"} - safeupdate: {repo: "eradman/pg-safeupdate"} - supabase_vault: {repo: "supabase/vault"} - timescaledb: {repo: "timescale/timescaledb"} - vector: {repo: "pgvector/pgvector"} - wal2json: {repo: "eulerto/wal2json"} - wrappers: {repo: "supabase/wrappers", noHash: true} -} - -const FAKE_HASH = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" - -def run [command: list] { - let result = (run-external ...$command | complete) - if $result.exit_code == 0 { $result.stdout | str trim } else { null } -} - -def parse-version [text: string] { - let match = ($text | parse --regex '^(\d+(?:\.\d+)*)') - if ($match | is-empty) { [0] } else { $match.0.capture0 | split row "." | each { into int } } -} - -def is-newer [current: list, candidate: list] { - $candidate != $current and ([{v: $current}, {v: $candidate}] | sort-by v | last | get v) == $candidate -} - -def best-candidate [tags: list, repo: string] { - let prefixed = ("^" + $repo + "[-_](\\d+(?:[._]\\d+){1,3})$") - let candidates = ( - $tags | each { |tag| - let dtag = ($tag | str downcase) - let match = ($dtag | parse --regex '^(?:v|ver_)?(\d+(?:\.\d+){0,3})$') - let match = if ($match | is-empty) { $dtag | parse --regex $prefixed } else { $match } - if ($match | is-empty) { null } else { - {v: (parse-version ($match.0.capture0 | str replace --all "_" ".")), tag: $tag} - } - } | compact - ) - if ($candidates | is-empty) { null } else { $candidates | sort-by v | last } -} - -let repo_root = (run ["git" "rev-parse" "--show-toplevel"]) -let versions_file = ($repo_root | path join "nix/ext/versions.json") -mut versions = (open $versions_file) -mut changed = false - -for ext in ($versions | columns) { - let info = ($REPOS | get -o $ext) - if $info == null { error make {msg: $"no update source configured for ($ext) - add it to REPOS in check-ext-versions.nu"} } - let parts = ($info.repo | split row "/") - let owner = $parts.0 - let repo = $parts.1 - - let tags_json = (run ["gh" "api" $"repos/($owner)/($repo)/tags" "--paginate"]) - if $tags_json == null { print $"skip ($ext): tags lookup failed"; continue } - let tags = ($tags_json | from json | get name) - - let candidate = (best-candidate $tags $repo) - if $candidate == null { print $"skip ($ext): no clean version tags"; continue } - - let entries = ($versions | get $ext) - let current_key = ($entries | columns | each { |k| {k: $k, v: (parse-version $k)} } | sort-by v | last | get k) - if not (is-newer (parse-version $current_key) $candidate.v) { continue } - - let no_hash = ($info | get -o noHash | default false) - let sri_hash = if $no_hash { - $FAKE_HASH - } else { - let url = $"https://github.com/($owner)/($repo)/archive/($candidate.tag).tar.gz" - let sha256 = (run ["nix-prefetch-url" "--type" "sha256" "--unpack" $url]) - if $sha256 == null { null } else { run ["nix" "hash" "to-sri" "--type" "sha256" ($sha256 | lines | last)] } - } - if $sri_hash == null { print $"skip ($ext): prefetch failed for ($candidate.tag)"; continue } - - let version_str = ($candidate.v | each { into string } | str join ".") - let entry = {postgresql: ($entries | get $current_key | get postgresql), revision: $candidate.tag, rev: $candidate.tag, hash: $sri_hash} - $versions = ($versions | upsert $ext ($entries | upsert $version_str $entry)) - $changed = true - print $"updated ($ext) -> ($version_str) \(($candidate.tag)\)" -} - -if $changed { - $versions | to json --indent 2 | $"($in)\n" | save -f $versions_file -} From 129aa9905c773a0db6677bdcd9740ca2e4a18e16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Wed, 9 Sep 2026 22:44:52 +0300 Subject: [PATCH 19/21] fix stale owner/repo in index_advisor and pg-safeupdate index_advisor: olirice/index_advisor was transferred to supabase/index_advisor (old owner still redirects for existing pinned fetches, but breaks the GitHub tags API used for version checking). pg-safeupdate: repo = pname resolved to eradman/safeupdate, but the real repo is eradman/pg-safeupdate. Verified both still build with their existing pinned hashes - same underlying content, just corrected metadata. --- nix/ext/index_advisor.nix | 2 +- nix/ext/pg-safeupdate.nix | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nix/ext/index_advisor.nix b/nix/ext/index_advisor.nix index 5892127142..59adc8e15e 100644 --- a/nix/ext/index_advisor.nix +++ b/nix/ext/index_advisor.nix @@ -31,7 +31,7 @@ let buildInputs = [ postgresql ]; src = fetchFromGitHub { - owner = "olirice"; + owner = "supabase"; repo = pname; rev = "v${version}"; inherit hash; diff --git a/nix/ext/pg-safeupdate.nix b/nix/ext/pg-safeupdate.nix index 452e9c2c5e..afd0af0f70 100644 --- a/nix/ext/pg-safeupdate.nix +++ b/nix/ext/pg-safeupdate.nix @@ -20,7 +20,7 @@ let src = fetchFromGitHub { owner = "eradman"; - repo = pname; + repo = "pg-safeupdate"; rev = version; inherit hash; }; From 335243d2348867443055a61b1b4e08dd6a043b7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Wed, 9 Sep 2026 22:48:11 +0300 Subject: [PATCH 20/21] fix stale owner in pgmq (tembo-io -> pgmq, same repo, verified build) --- nix/ext/pgmq/default.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nix/ext/pgmq/default.nix b/nix/ext/pgmq/default.nix index 7e2076a2f5..b5ee1a5bd3 100644 --- a/nix/ext/pgmq/default.nix +++ b/nix/ext/pgmq/default.nix @@ -36,7 +36,7 @@ let inherit pname version; buildInputs = [ postgresql ]; src = fetchFromGitHub { - owner = "tembo-io"; + owner = "pgmq"; repo = pname; rev = "v${version}"; inherit hash; From 9af21b92496c1dc47f86b36f40595b0f7d60e5c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Wed, 9 Sep 2026 22:51:25 +0300 Subject: [PATCH 21/21] derive repo from src where reachable, only 14 stay in OVERRIDES Reads owner/repo straight off each package's own fetchFromGitHub src via nix eval, for the 16 extensions whose passthru.perVersion exposes it. The other 14 (fetchurl-based, or missing perVersion entirely) stay in a small OVERRIDES table. Verified identical output to the prior all-manual REPOS table. --- nix/tools/check-ext-versions.nu | 42 ++++++++++++++++----------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/nix/tools/check-ext-versions.nu b/nix/tools/check-ext-versions.nu index d3c379b2e5..6321103655 100755 --- a/nix/tools/check-ext-versions.nu +++ b/nix/tools/check-ext-versions.nu @@ -1,34 +1,22 @@ #!/usr/bin/env nu -const REPOS = { - http: {repo: "pramsey/pgsql-http"} - hypopg: {repo: "HypoPG/hypopg"} - index_advisor: {repo: "supabase/index_advisor"} - pg_cron: {repo: "citusdata/pg_cron"} +# Extensions whose repo can't be read off the derivation's own src (fetchurl +# instead of fetchFromGitHub, or the package doesn't expose a per-version +# derivation at all). noHash means the real fetcher isn't a plain GitHub +# archive, so skip the hash prefetch and use a placeholder instead. +const OVERRIDES = { pg_graphql: {repo: "supabase/pg_graphql", noHash: true} pg_hashids: {repo: "iCyberon/pg_hashids"} pg_jsonschema: {repo: "supabase/pg_jsonschema"} - pg_net: {repo: "supabase/pg_net"} - pg_partman: {repo: "pgpartman/pg_partman"} pg_plan_filter: {repo: "pgexperts/pg_plan_filter"} - pg_repack: {repo: "reorg/pg_repack"} pg_stat_monitor: {repo: "percona/pg_stat_monitor"} - pg_tle: {repo: "aws/pg_tle"} - pgaudit: {repo: "pgaudit/pgaudit"} pgjwt: {repo: "michelp/pgjwt"} - pgmq: {repo: "pgmq/pgmq"} pgroonga: {repo: "pgroonga/pgroonga", noHash: true} - pgrouting: {repo: "pgRouting/pgrouting"} - pgsodium: {repo: "michelp/pgsodium"} pgtap: {repo: "theory/pgtap"} plpgsql_check: {repo: "okbob/plpgsql_check"} - plv8: {repo: "plv8/plv8"} postgis: {repo: "postgis/postgis", noHash: true} rum: {repo: "postgrespro/rum"} - safeupdate: {repo: "eradman/pg-safeupdate"} - supabase_vault: {repo: "supabase/vault"} timescaledb: {repo: "timescale/timescaledb"} - vector: {repo: "pgvector/pgvector"} wal2json: {repo: "eulerto/wal2json"} wrappers: {repo: "supabase/wrappers", noHash: true} } @@ -64,15 +52,28 @@ def best-candidate [tags: list, repo: string] { if ($candidates | is-empty) { null } else { $candidates | sort-by v | last } } +let system = (run ["nix" "eval" "--impure" "--raw" "--expr" "builtins.currentSystem"]) +if $system == null { error make {msg: "nix eval of builtins.currentSystem failed"} } + +# reads owner/repo straight off each package's own fetchFromGitHub src, where +# it's exposed as a per-version derivation - null where it isn't (see OVERRIDES) +let derive_expr = "exts: builtins.listToAttrs (map (n: let pkg = exts.${n}; pv = if pkg ? perVersion then pkg.perVersion else { }; keys = builtins.attrNames pv; entry = if keys != [ ] then pv.${builtins.head keys} else { }; src = if entry ? src then entry.src else { }; in { name = n; value = if (src ? owner) && (src ? repo) then src.owner + \"/\" + src.repo else null; }) (builtins.attrNames exts))" +let derived_json = (run ["nix" "eval" "--json" $".#legacyPackages.($system).psql_15.exts" "--apply" $derive_expr]) +if $derived_json == null { error make {msg: "nix eval of derived repos failed"} } +let derived = ($derived_json | from json) + let repo_root = (run ["git" "rev-parse" "--show-toplevel"]) let versions_file = ($repo_root | path join "nix/ext/versions.json") mut versions = (open $versions_file) mut changed = false for ext in ($versions | columns) { - let info = ($REPOS | get -o $ext) - if $info == null { error make {msg: $"no update source configured for ($ext) - add it to REPOS in check-ext-versions.nu"} } - let parts = ($info.repo | split row "/") + let override = ($OVERRIDES | get -o $ext) + let repo_slug = if $override != null { $override.repo } else { ($derived | get -o $ext) } + if $repo_slug == null { error make {msg: $"no update source for ($ext) - add it to OVERRIDES in check-ext-versions.nu"} } + let no_hash = ($override | get -o noHash | default false) + + let parts = ($repo_slug | split row "/") let owner = $parts.0 let repo = $parts.1 @@ -87,7 +88,6 @@ for ext in ($versions | columns) { let current_key = ($entries | columns | each { |k| {k: $k, v: (parse-version $k)} } | sort-by v | last | get k) if not (is-newer (parse-version $current_key) $candidate.v) { continue } - let no_hash = ($info | get -o noHash | default false) let sri_hash = if $no_hash { $FAKE_HASH } else {