Replace mise's upstream hook with a declarative GitHub-releases provider

After the quarantine moved into the manifest, all mise-bin's hook still knew
was data: the repository, the checksum manifest name, and the asset filename
patterns. That now lives in .omarchy/package.json as an upstream block --

  "upstream": {
    "github": "jdx/mise",
    "checksums": "SHASUMS256.txt",
    "assets": { "x86_64": "mise-{tag}-linux-x64.tar.xz", ... }
  }

-- handled by helpers/upstream-github.sh inside bin/sync-upstream. The
provider walks the release feed (drafts/prereleases excluded), honors
min_release_age and BYPASS_MIN_RELEASE_AGE during selection, reports
published_at so the framework backstop still applies, fails closed on any
unreadable tag or timestamp, and skips the checksum fetch when the newest
qualifying release is already checked in.

upstream.sh remains the escape hatch for feeds that fit no convention
(openai-codex-desktop's Debian index, tmog's version.txt, t3code's
electron-builder manifest); declaring both is an error.
This commit is contained in:
Ryan Hughes
2026-08-24 19:30:33 -04:00
parent 48ad6b9d7b
commit 699261471a
5 changed files with 193 additions and 96 deletions
+21 -1
View File
@@ -13,6 +13,7 @@
# { "source": "aur", "rebuild_on": ["qt6-base"] }
# { "source": "local" }
# { "source": "local", "min_release_age": "24h" }
# { "source": "local", "upstream": { "github": "owner/repo", "checksums": "SHASUMS256.txt", "assets": { "x86_64": "name-{tag}-x64.tar.xz" } } }
#
# bin/sync-aur also writes upstream_commit for AUR-backed packages, and
# bin/sync-rebuilds writes rebuilt_against for packages declaring rebuild_on.
@@ -164,9 +165,14 @@ package_has_upstream_hook() {
[[ -f "$pkgdir/.omarchy/upstream.sh" ]]
}
package_has_upstream_provider() {
local pkgdir="$1"
[[ -n "$(package_metadata_value "$pkgdir" '.upstream.github' "")" ]]
}
packages_for_upstream_sync() {
package_dirs | while IFS= read -r pkgdir; do
if package_has_upstream_hook "$pkgdir"; then
if package_has_upstream_hook "$pkgdir" || package_has_upstream_provider "$pkgdir"; then
basename "$pkgdir"
fi
done
@@ -340,6 +346,20 @@ validate_package_metadata() {
return 1
fi
if ! jq -e '
(.upstream // {}) | type == "object"
and (if . == {} then true else
((.github // "") | type == "string" and test("\\A[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+\\z"))
and ((.checksums // "") | type == "string" and length > 0)
and ((.assets // {}) | type == "object" and length > 0 and (to_entries | all(
(.key | test("\\A[a-z0-9_]+\\z")) and (.value | type == "string" and length > 0)
)))
end)
' "$metadata" >/dev/null; then
echo "invalid upstream for $(basename "$pkgdir"): needs github owner/repo, checksums asset name, and an assets arch->name map"
return 1
fi
pkgrel_type=$(jq -r 'if has("pkgrel") then .pkgrel | type else "missing" end' "$metadata")
case "$pkgrel_type" in
object|missing) ;;
+139
View File
@@ -0,0 +1,139 @@
# GitHub-releases upstream provider for bin/sync-upstream.
#
# A package whose upstream ships tagged GitHub releases with a checksum
# manifest asset needs no upstream.sh hook: the whole feed is data, declared
# in .omarchy/package.json --
#
# "upstream": {
# "github": "jdx/mise",
# "checksums": "SHASUMS256.txt",
# "assets": {
# "x86_64": "mise-{tag}-linux-x64.tar.xz",
# "aarch64": "mise-{tag}-linux-arm64.tar.xz"
# }
# }
#
# {tag} and {pkgver} interpolate into asset names; tags may carry a leading
# "v", which is stripped for pkgver. Drafts and prereleases are ignored. The
# provider emits the same JSON contract as an upstream.sh hook, so
# bin/sync-upstream's validation and min_release_age backstop apply
# unchanged; a feed that fits no convention keeps a bespoke upstream.sh.
package_upstream_github_repo() {
local pkgdir="$1"
package_metadata_value "$pkgdir" '.upstream.github' ""
}
# Emits the newest qualifying release as hook-contract JSON. min_release_age
# is honored during selection (newest release older than the window wins,
# even when a younger one exists) and BYPASS_MIN_RELEASE_AGE=1 lifts it.
# Unusable tags or timestamps anywhere in the feed fail the sync rather than
# being skipped: a feed this provider cannot fully read is a feed it should
# not silently choose from.
github_upstream_release() {
local package_dir="$1" min_age="${2:-0}"
local metadata repo checksums_name
metadata=$(metadata_file_for_dir "$package_dir")
repo=$(jq -r '.upstream.github // ""' "$metadata")
if [[ ! "$repo" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]]; then
echo "invalid upstream.github repository: '${repo:-<empty>}'" >&2
return 1
fi
checksums_name=$(jq -r '.upstream.checksums // ""' "$metadata")
if [[ -z "$checksums_name" ]]; then
echo "upstream.checksums names the checksum manifest asset and is required" >&2
return 1
fi
local arches
mapfile -t arches < <(jq -r '.upstream.assets // {} | keys[]' "$metadata")
if [[ ${#arches[@]} -eq 0 ]]; then
echo "upstream.assets must map at least one architecture to an asset name" >&2
return 1
fi
local releases now
now=$(date +%s)
if ! releases=$(curl -fsSL "https://api.github.com/repos/$repo/releases?per_page=20"); then
echo "could not fetch the release feed for $repo" >&2
return 1
fi
local candidates=0 best_tag="" best_pkgver="" best_published_at=""
local tag published_at pkgver published_epoch
while IFS=$'\t' read -r tag published_at; do
if [[ ! "$tag" =~ ^v?([A-Za-z0-9._+]+)$ ]]; then
echo "$repo release has an unusable tag: ${tag:-<empty>}" >&2
return 1
fi
pkgver=${BASH_REMATCH[1]}
if [[ -z "$published_at" ]] || ! published_epoch=$(date --date="$published_at" +%s 2>/dev/null); then
echo "$repo release $tag has an invalid published_at: ${published_at:-<empty>}" >&2
return 1
fi
candidates=$((candidates + 1))
if (( now - published_epoch < min_age )); then
if [[ "${BYPASS_MIN_RELEASE_AGE:-}" == "1" ]]; then
echo "Bypassing release-age gate for $repo $tag" >&2
else
continue
fi
fi
if [[ -z "$best_pkgver" ]] || [[ "$(vercmp "$pkgver" "$best_pkgver")" -gt 0 ]]; then
best_tag=$tag
best_pkgver=$pkgver
best_published_at=$published_at
fi
done < <(jq -r '.[] | select((.draft or .prerelease) | not) | [.tag_name // empty, .published_at // empty] | @tsv' <<<"$releases")
if (( candidates == 0 )); then
echo "no stable releases found in the feed for $repo" >&2
return 1
fi
if [[ -z "$best_tag" ]]; then
echo "every recent $repo release is still inside the release-age quarantine; skipping" >&2
echo '{}'
return 0
fi
# Already checked in: report no update instead of re-fetching checksums.
local current_pkgver
current_pkgver=$(grep -m1 '^pkgver=' "$package_dir/PKGBUILD" | cut -d= -f2- | tr -d "\"'")
if [[ "$best_pkgver" == "$current_pkgver" ]]; then
echo '{}'
return 0
fi
local checksums
if ! checksums=$(curl -fsSL "https://github.com/$repo/releases/download/$best_tag/$checksums_name"); then
echo "could not fetch $checksums_name for $repo $best_tag" >&2
return 1
fi
local jq_args=(--arg pkgver "$best_pkgver" --arg published_at "$best_published_at")
local jq_filter='{pkgver: $pkgver, published_at: $published_at, sha256sums: {}}'
local arch template filename checksum
for arch in "${arches[@]}"; do
if [[ ! "$arch" =~ ^[a-z0-9_]+$ ]]; then
echo "invalid architecture key in upstream.assets: '$arch'" >&2
return 1
fi
template=$(jq -r --arg arch "$arch" '.upstream.assets[$arch]' "$metadata")
filename=${template//\{pkgver\}/$best_pkgver}
filename=${filename//\{tag\}/$best_tag}
# Manifest lines are "<sha256> <name>", with the name sometimes prefixed
# "./" (sha256sum of a local path) or "*" (binary-mode marker).
checksum=$(awk -v f="$filename" '$2 == f || $2 == "./" f || $2 == "*" f { print $1; exit }' <<<"$checksums")
if [[ ! "$checksum" =~ ^[0-9a-f]{64}$ ]]; then
echo "no valid checksum for $filename in $repo $best_tag $checksums_name" >&2
return 1
fi
jq_args+=(--arg "sum_$arch" "$checksum")
jq_filter+=" | .sha256sums[\"$arch\"] = [\$sum_$arch]"
done
jq -n "${jq_args[@]}" "$jq_filter"
}