bin/sync-upstream self-test swaps the two network fetches in
helpers/upstream-github.sh for fixture readers and runs the production code
paths: fallback past a quarantined release, draft/prerelease filtering, the
deliberate bypass, unchanged-version and all-quarantined no-update paths,
unusable tags/timestamps and missing checksums failing the sync, {tag} and
{pkgver} asset templates with ./ and * manifest prefixes across both
architectures, the min_release_age backstop verdicts (now a testable
release_age_status function), the duration parser, and manifest validation.
Also fixes from the review: the duration parser forces base-10 arithmetic
(leading zeros no longer parse as octal) and bounds values to nine digits so
no suffix can overflow; jq // treating false as absent can no longer let
"min_release_age": false or "upstream": false slip through as unset; the
release feed page grew to the API maximum of 100 with the bounded search
documented; and the README package-metadata section documents the upstream
block, min_release_age, the bypass, and provider-versus-hook exclusivity.
638 lines
21 KiB
Bash
Executable File
638 lines
21 KiB
Bash
Executable File
#!/bin/bash
|
|
set -euo pipefail
|
|
|
|
BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..")
|
|
source "$BUILD_ROOT/helpers/message-helpers.sh"
|
|
source "$BUILD_ROOT/helpers/paths.sh"
|
|
source "$BUILD_ROOT/helpers/package-metadata.sh"
|
|
source "$BUILD_ROOT/helpers/upstream-github.sh"
|
|
|
|
TEMP_DIR=$(mktemp -d)
|
|
trap 'rm -rf "$TEMP_DIR"' EXIT
|
|
|
|
SPECIFIC_PACKAGES=()
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
Usage: $0 [PACKAGE...]
|
|
|
|
Update packages that track an upstream vendor release feed instead of the AUR.
|
|
|
|
A package whose upstream ships tagged GitHub releases with a checksum manifest
|
|
opts in declaratively, via "upstream" in .omarchy/package.json (see
|
|
helpers/upstream-github.sh for the schema); no code needed. Anything with a
|
|
bespoke feed provides pkgbuilds/<package>/.omarchy/upstream.sh instead, a hook
|
|
that reports the newest upstream release as JSON on stdout:
|
|
|
|
{
|
|
"pkgver": "1.2.3",
|
|
"sha256sums": { "x86_64": ["<sha256>"], "aarch64": ["<sha256>"] }
|
|
}
|
|
|
|
Architecture keys become sha256sums_<arch> in the PKGBUILD; the key "any" means
|
|
the unsuffixed sha256sums array. An empty object ({}) reports no update.
|
|
|
|
When the reported version is newer than the checked-in one, pkgver and the
|
|
listed checksum arrays are rewritten and pkgrel is reset to 1.
|
|
|
|
A package may declare "min_release_age" in .omarchy/package.json ("24h", "2d",
|
|
or bare seconds) to quarantine fresh releases until maintainers have had time
|
|
to pull a bad or compromised one. The window is exported to the hook as
|
|
MIN_RELEASE_AGE_SECONDS so it can select the newest release that has already
|
|
cleared it, and enforced here as a backstop: the hook must then report
|
|
"published_at" (ISO 8601), and a release younger than the window is treated
|
|
as no update. A maintainer shipping an emergency update inside the window
|
|
runs: BYPASS_MIN_RELEASE_AGE=1 $0 <package>. Scheduled automation never sets
|
|
the bypass, so the resulting change still goes through a reviewed PR.
|
|
|
|
Arguments:
|
|
PACKAGE One or more package names to update (optional)
|
|
|
|
Commands:
|
|
self-test Run the offline fixture tests for release selection, the
|
|
quarantine backstop, and metadata parsing
|
|
|
|
Examples:
|
|
$0 # Update every package with an upstream source
|
|
$0 openai-codex-desktop # Update specific packages
|
|
EOF
|
|
}
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
-h|--help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
--*)
|
|
print_error "Unknown option: $1"
|
|
exit 1
|
|
;;
|
|
*)
|
|
SPECIFIC_PACKAGES+=("$1")
|
|
shift
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if ! command -v vercmp >/dev/null 2>&1; then
|
|
print_error "vercmp not found: this needs pacman to decide whether a release is an upgrade"
|
|
exit 1
|
|
fi
|
|
|
|
print_header "Upstream Package Sync"
|
|
|
|
UPDATED=0
|
|
SKIPPED=0
|
|
FAILED=0
|
|
SPECIFIC_MODE=false
|
|
|
|
get_pkgver() {
|
|
local package_dir="$1"
|
|
|
|
grep -m1 '^pkgver=' "$package_dir/PKGBUILD" | cut -d= -f2- | tr -d "\"'"
|
|
}
|
|
|
|
assert_single_assignment() {
|
|
local pkgbuild="$1"
|
|
local pattern="$2"
|
|
local label="$3"
|
|
|
|
if [[ $(grep -c "$pattern" "$pkgbuild") -ne 1 ]]; then
|
|
print_error "Expected exactly one $label assignment in $pkgbuild"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
set_pkgbuild_scalar() {
|
|
local pkgbuild="$1"
|
|
local field="$2"
|
|
local value="$3"
|
|
|
|
assert_single_assignment "$pkgbuild" "^${field}=" "$field" || return 1
|
|
sed -i "s/^${field}=.*/${field}=${value}/" "$pkgbuild"
|
|
}
|
|
|
|
# Replace an array assignment, however many lines the original spans.
|
|
set_pkgbuild_array() {
|
|
local pkgbuild="$1"
|
|
local name="$2"
|
|
shift 2
|
|
local values=("$@")
|
|
|
|
assert_single_assignment "$pkgbuild" "^${name}=(" "$name" || return 1
|
|
|
|
if [[ ${#values[@]} -eq 0 ]]; then
|
|
print_error "No values to write for ${name}"
|
|
return 1
|
|
fi
|
|
|
|
local block="$TEMP_DIR/array-block"
|
|
if [[ ${#values[@]} -eq 1 ]]; then
|
|
printf "%s=('%s')\n" "$name" "${values[0]}" > "$block" || return 1
|
|
else
|
|
{
|
|
printf '%s=(\n' "$name"
|
|
printf " '%s'\n" "${values[@]}"
|
|
printf ')\n'
|
|
} > "$block" || return 1
|
|
fi
|
|
|
|
local rewritten="$TEMP_DIR/pkgbuild-rewritten"
|
|
if ! awk -v prefix="${name}=(" -v block="$block" '
|
|
!replaced && index($0, prefix) == 1 {
|
|
while ((getline line < block) > 0) print line
|
|
close(block)
|
|
replaced = 1
|
|
# A ")" anywhere past the opening closes the array; testing for one at end
|
|
# of line instead would treat a trailing comment as a continuation and eat
|
|
# every line up to the next ")".
|
|
if (index(substr($0, length(prefix) + 1), ")") == 0) skipping = 1
|
|
next
|
|
}
|
|
skipping { if ($0 ~ /\)/) skipping = 0; next }
|
|
{ print }
|
|
' "$pkgbuild" > "$rewritten"; then
|
|
print_error "Failed to rewrite ${name} in $pkgbuild"
|
|
rm -f "$rewritten"
|
|
return 1
|
|
fi
|
|
|
|
mv "$rewritten" "$pkgbuild"
|
|
}
|
|
|
|
# Backstop verdict for a reported release against min_release_age. Returns 0
|
|
# when old enough (or no policy is set, or the bypass is deliberate), 1 when
|
|
# the release is younger than the window, 2 when the report carries no usable
|
|
# published_at and the age cannot be established at all.
|
|
release_age_status() {
|
|
local release="$1" min_age="$2"
|
|
(( min_age > 0 )) || return 0
|
|
[[ "${BYPASS_MIN_RELEASE_AGE:-}" != "1" ]] || return 0
|
|
local published_at published_epoch
|
|
published_at=$(jq -r '.published_at // empty' <<<"$release")
|
|
if [[ -z "$published_at" ]] || ! published_epoch=$(date --date="$published_at" +%s 2>/dev/null); then
|
|
return 2
|
|
fi
|
|
(( $(date +%s) - published_epoch >= min_age )) || return 1
|
|
}
|
|
|
|
# pacman's own comparator, because nothing else agrees with it at the corners:
|
|
# sort -V calls 1.0a newer than 1.0, vercmp calls it older, and pacman is what
|
|
# decides whether a published package is an upgrade.
|
|
version_is_newer() {
|
|
local candidate="$1"
|
|
local current="$2"
|
|
|
|
[[ "$candidate" != "$current" ]] || return 1
|
|
[[ "$(vercmp "$candidate" "$current")" -gt 0 ]]
|
|
}
|
|
|
|
validate_release() {
|
|
local release="$1"
|
|
|
|
# pkgver is written into the PKGBUILD, which makepkg sources as shell, so it
|
|
# is held to pacman's own character set rather than merely being non-empty.
|
|
# The anchors are \A and \z, not ^ and $: jq's $ also matches before a
|
|
# trailing newline, which would let "1.0\n" through and break the rewrite.
|
|
jq -e '
|
|
(.pkgver | type == "string" and test("\\A[A-Za-z0-9._+]+\\z"))
|
|
and (.sha256sums | type == "object" and length > 0)
|
|
and (.sha256sums | to_entries | all(
|
|
.key | test("\\A[a-z0-9_]+\\z")
|
|
))
|
|
and (.sha256sums | to_entries | all(
|
|
.value | type == "array" and length > 0 and all(test("\\A[0-9a-f]{64}\\z"))
|
|
))
|
|
and (if has("published_at") then (.published_at | type == "string" and length > 0) else true end)
|
|
' <<<"$release" >/dev/null
|
|
}
|
|
|
|
# Confirm the rewritten PKGBUILD parses and actually holds what we meant to put
|
|
# in it. Editing shell with awk and sed can go wrong in ways no amount of
|
|
# pattern-matching anticipates -- an array element carrying a ")" in a comment,
|
|
# say -- so the result is checked rather than trusted.
|
|
verify_pkgbuild() {
|
|
local pkgbuild="$1"
|
|
local release="$2"
|
|
local pkgver="$3"
|
|
shift 3
|
|
local arrays=("$@")
|
|
|
|
if ! bash -n "$pkgbuild" 2>/dev/null; then
|
|
print_error "Rewritten PKGBUILD is not valid shell"
|
|
return 1
|
|
fi
|
|
|
|
local dump
|
|
if ! dump=$(CARCH=x86_64 bash -c '
|
|
source "$1" >/dev/null 2>&1 || exit 1
|
|
printf "pkgver\t%s\n" "$pkgver"
|
|
printf "pkgrel\t%s\n" "$pkgrel"
|
|
for name in "${@:2}"; do
|
|
declare -n array="$name"
|
|
printf "%s\t%s\n" "$name" "${array[*]}"
|
|
done
|
|
' _ "$pkgbuild" "${arrays[@]}" 2>/dev/null); then
|
|
print_error "Rewritten PKGBUILD could not be read back"
|
|
return 1
|
|
fi
|
|
|
|
local expected
|
|
expected=$(
|
|
printf 'pkgver\t%s\n' "$pkgver"
|
|
printf 'pkgrel\t1\n'
|
|
local array arch
|
|
for array in "${arrays[@]}"; do
|
|
arch="${array#sha256sums}"
|
|
arch="${arch#_}"
|
|
[[ -n "$arch" ]] || arch="any"
|
|
printf '%s\t%s\n' "$array" \
|
|
"$(jq -r --arg arch "$arch" '.sha256sums[$arch] | join(" ")' <<<"$release")"
|
|
done
|
|
)
|
|
|
|
if [[ "$dump" != "$expected" ]]; then
|
|
print_error "Rewritten PKGBUILD does not hold the reported release"
|
|
diff <(echo "$expected") <(echo "$dump") | sed 's/^/ /' >&2 || true
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
apply_release() {
|
|
local package_dir="$1"
|
|
local release="$2"
|
|
local pkgver="$3"
|
|
local pkgbuild="$package_dir/PKGBUILD"
|
|
|
|
local arch array values
|
|
local arrays=()
|
|
|
|
while IFS= read -r arch; do
|
|
if [[ "$arch" == "any" ]]; then
|
|
array="sha256sums"
|
|
else
|
|
array="sha256sums_$arch"
|
|
fi
|
|
arrays+=("$array")
|
|
done < <(jq -r '.sha256sums | keys[]' <<<"$release")
|
|
|
|
# validate_release guarantees at least one entry, so an empty list here means
|
|
# jq died inside the process substitution rather than that there is nothing
|
|
# to do.
|
|
if [[ ${#arrays[@]} -eq 0 ]]; then
|
|
print_error "Could not read the checksum architectures from the reported release"
|
|
return 1
|
|
fi
|
|
|
|
# Every edit lands on a scratch copy that replaces the PKGBUILD in one rename
|
|
# at the end, so a failure part way through leaves the original untouched
|
|
# rather than half updated.
|
|
local scratch="$pkgbuild.sync-upstream"
|
|
cp "$pkgbuild" "$scratch" || return 1
|
|
|
|
if ! (
|
|
assert_single_assignment "$scratch" '^pkgver=' pkgver || exit 1
|
|
assert_single_assignment "$scratch" '^pkgrel=' pkgrel || exit 1
|
|
|
|
for array in "${arrays[@]}"; do
|
|
arch="${array#sha256sums}"
|
|
arch="${arch#_}"
|
|
[[ -n "$arch" ]] || arch="any"
|
|
|
|
mapfile -t values < <(jq -r --arg arch "$arch" '.sha256sums[$arch][]' <<<"$release")
|
|
set_pkgbuild_array "$scratch" "$array" "${values[@]}" || exit 1
|
|
done
|
|
|
|
set_pkgbuild_scalar "$scratch" pkgver "$pkgver" || exit 1
|
|
set_pkgbuild_scalar "$scratch" pkgrel 1 || exit 1
|
|
|
|
verify_pkgbuild "$scratch" "$release" "$pkgver" "${arrays[@]}" || exit 1
|
|
); then
|
|
rm -f "$scratch"
|
|
return 1
|
|
fi
|
|
|
|
chmod --reference="$pkgbuild" "$scratch"
|
|
mv "$scratch" "$pkgbuild"
|
|
}
|
|
|
|
sync_package() {
|
|
local package="$1"
|
|
local package_dir="$PKGBUILDS_DIR/$package"
|
|
local hook="$package_dir/.omarchy/upstream.sh"
|
|
|
|
if [[ ! -f "$package_dir/PKGBUILD" ]]; then
|
|
print_error "Package $package has no PKGBUILD"
|
|
((++FAILED))
|
|
return 0
|
|
fi
|
|
|
|
local github_repo
|
|
github_repo=$(package_upstream_github_repo "$package_dir")
|
|
|
|
if [[ -n "$github_repo" && -f "$hook" ]]; then
|
|
print_error "Package $package declares both upstream.github and an upstream.sh hook; keep exactly one"
|
|
((++FAILED))
|
|
return 0
|
|
fi
|
|
|
|
if [[ -z "$github_repo" && ! -f "$hook" ]]; then
|
|
if [[ "$SPECIFIC_MODE" == true ]]; then
|
|
print_error "Package $package has neither an upstream declaration nor .omarchy/upstream.sh"
|
|
((++FAILED))
|
|
else
|
|
print_info "Skipping $package: no upstream source"
|
|
((++SKIPPED))
|
|
fi
|
|
return 0
|
|
fi
|
|
|
|
local min_age
|
|
if ! min_age=$(package_min_release_age_seconds "$package_dir"); then
|
|
print_error "Invalid min_release_age in $package_dir/.omarchy/package.json"
|
|
((++FAILED))
|
|
return 0
|
|
fi
|
|
|
|
print_info "Checking $package for upstream releases..."
|
|
|
|
local release
|
|
if [[ -n "$github_repo" ]]; then
|
|
if ! release=$(github_upstream_release "$package_dir" "$min_age"); then
|
|
print_error "GitHub release provider failed for $package"
|
|
((++FAILED))
|
|
return 0
|
|
fi
|
|
elif ! release=$(cd "$package_dir" && PACKAGE_NAME="$package" \
|
|
MIN_RELEASE_AGE_SECONDS="$min_age" \
|
|
BYPASS_MIN_RELEASE_AGE="${BYPASS_MIN_RELEASE_AGE:-}" \
|
|
bash .omarchy/upstream.sh); then
|
|
print_error "Upstream hook failed for $package"
|
|
((++FAILED))
|
|
return 0
|
|
fi
|
|
|
|
if ! jq -e . >/dev/null 2>&1 <<<"$release"; then
|
|
print_error "Upstream hook for $package did not report valid JSON"
|
|
((++FAILED))
|
|
return 0
|
|
fi
|
|
|
|
if [[ "$(jq -r 'has("pkgver")' <<<"$release")" == "false" ]]; then
|
|
print_info " No upstream update reported"
|
|
((++SKIPPED))
|
|
return 0
|
|
fi
|
|
|
|
if ! validate_release "$release"; then
|
|
print_error "Upstream hook for $package reported a malformed release"
|
|
((++FAILED))
|
|
return 0
|
|
fi
|
|
|
|
# Backstop for min_release_age: the selection already honors the window,
|
|
# but a provider or hook bug must not be able to ship a release younger
|
|
# than the policy.
|
|
local age_status=0
|
|
release_age_status "$release" "$min_age" || age_status=$?
|
|
case "$age_status" in
|
|
1)
|
|
print_warning " Reported release is inside the ${min_age}s minimum release age; leaving it alone"
|
|
((++SKIPPED))
|
|
return 0
|
|
;;
|
|
2)
|
|
print_error "min_release_age is set for $package but its source reported no usable published_at; refusing an unverifiable release"
|
|
((++FAILED))
|
|
return 0
|
|
;;
|
|
esac
|
|
|
|
local pkgver current_pkgver
|
|
pkgver=$(jq -r '.pkgver' <<<"$release")
|
|
current_pkgver=$(get_pkgver "$package_dir")
|
|
|
|
if [[ -z "$current_pkgver" ]]; then
|
|
print_error "Could not read pkgver from $package_dir/PKGBUILD"
|
|
((++FAILED))
|
|
return 0
|
|
fi
|
|
|
|
if [[ "$pkgver" == "$current_pkgver" ]]; then
|
|
print_info " Already at $current_pkgver"
|
|
((++SKIPPED))
|
|
return 0
|
|
fi
|
|
|
|
if ! version_is_newer "$pkgver" "$current_pkgver"; then
|
|
print_warning " Upstream reports $pkgver, older than the checked-in $current_pkgver; leaving it alone"
|
|
((++SKIPPED))
|
|
return 0
|
|
fi
|
|
|
|
if ! apply_release "$package_dir" "$release" "$pkgver"; then
|
|
print_error "Failed to update $package"
|
|
((++FAILED))
|
|
return 0
|
|
fi
|
|
|
|
print_success " $current_pkgver -> $pkgver"
|
|
((++UPDATED))
|
|
}
|
|
|
|
# Offline fixture tests: the network fetches in helpers/upstream-github.sh
|
|
# are swapped for fixture readers, everything else runs the production code
|
|
# paths. Covers release selection (fallback past quarantined releases,
|
|
# draft/prerelease filtering, bypass, unchanged version), failure paths
|
|
# (unusable tags/timestamps, missing checksums), checksum template mapping
|
|
# for both architectures, the min_release_age backstop, the duration parser,
|
|
# and manifest validation.
|
|
cmd_self_test() {
|
|
local failures=0
|
|
|
|
check() {
|
|
local desc="$1" expected="$2" got="$3"
|
|
if [[ "$expected" == "$got" ]]; then
|
|
echo " ok: $desc"
|
|
else
|
|
echo " FAIL: $desc (expected '$expected', got '$got')"
|
|
failures=$((failures + 1))
|
|
fi
|
|
}
|
|
|
|
local pkg="$TEMP_DIR/selftest-pkg"
|
|
mkdir -p "$pkg/.omarchy"
|
|
printf 'pkgver=1.0.0\npkgrel=1\n' > "$pkg/PKGBUILD"
|
|
cat > "$pkg/.omarchy/package.json" <<'EOF'
|
|
{
|
|
"source": "local",
|
|
"min_release_age": "24h",
|
|
"upstream": {
|
|
"github": "example/tool",
|
|
"checksums": "SHASUMS256.txt",
|
|
"assets": {
|
|
"x86_64": "tool-{tag}-x64.tar.xz",
|
|
"aarch64": "tool-v{pkgver}-arm64.tar.xz"
|
|
}
|
|
}
|
|
}
|
|
EOF
|
|
|
|
local young old2d old3d
|
|
young=$(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%SZ)
|
|
old2d=$(date -u -d '2 days ago' +%Y-%m-%dT%H:%M:%SZ)
|
|
old3d=$(date -u -d '3 days ago' +%Y-%m-%dT%H:%M:%SZ)
|
|
|
|
# v2.0.0 is inside the 24h window; v1.9.9/v1.9.8 are a prerelease and a
|
|
# draft that would outrank v1.9.0 if the filters failed.
|
|
local sum_x19 sum_a19 sum_x20 sum_a20
|
|
sum_x19=$(printf 'a%.0s' {1..64})
|
|
sum_a19=$(printf 'b%.0s' {1..64})
|
|
sum_x20=$(printf 'c%.0s' {1..64})
|
|
sum_a20=$(printf 'd%.0s' {1..64})
|
|
|
|
FIXTURE_RELEASES=$(jq -n --arg young "$young" --arg old2 "$old2d" --arg old3 "$old3d" '[
|
|
{tag_name: "v2.0.0", published_at: $young, draft: false, prerelease: false},
|
|
{tag_name: "v1.9.9", published_at: $old2, draft: false, prerelease: true},
|
|
{tag_name: "v1.9.8", published_at: $old2, draft: true, prerelease: false},
|
|
{tag_name: "v1.9.0", published_at: $old2, draft: false, prerelease: false},
|
|
{tag_name: "v1.8.0", published_at: $old3, draft: false, prerelease: false}
|
|
]')
|
|
FIXTURE_CHECKSUMS=$(printf '%s\n' \
|
|
"$sum_x19 ./tool-v1.9.0-x64.tar.xz" \
|
|
"$sum_a19 tool-v1.9.0-arm64.tar.xz" \
|
|
"$sum_x20 *tool-v2.0.0-x64.tar.xz" \
|
|
"$sum_a20 tool-v2.0.0-arm64.tar.xz")
|
|
|
|
github_fetch_releases() { printf '%s' "$FIXTURE_RELEASES"; }
|
|
github_fetch_checksums() { printf '%s\n' "$FIXTURE_CHECKSUMS"; }
|
|
|
|
echo "Release selection:"
|
|
local out
|
|
out=$(github_upstream_release "$pkg" 86400 2>/dev/null) || out="<error>"
|
|
check "quarantine falls back past the young v2.0.0" "1.9.0" "$(jq -r '.pkgver // "<none>"' <<<"$out")"
|
|
check "selected release reports its published_at" "$old2d" "$(jq -r '.published_at // "<none>"' <<<"$out")"
|
|
check "x86_64 checksum via {tag} template and ./ prefix" "$sum_x19" "$(jq -r '.sha256sums.x86_64[0] // "<none>"' <<<"$out")"
|
|
check "aarch64 checksum via {pkgver} template" "$sum_a19" "$(jq -r '.sha256sums.aarch64[0] // "<none>"' <<<"$out")"
|
|
|
|
out=$(github_upstream_release "$pkg" 0 2>/dev/null) || out="<error>"
|
|
check "no policy selects the newest stable release" "2.0.0" "$(jq -r '.pkgver // "<none>"' <<<"$out")"
|
|
check "prerelease v1.9.9 and draft v1.9.8 are never selected" "" "$(jq -r 'select(.pkgver == "1.9.9" or .pkgver == "1.9.8") | .pkgver' <<<"$out")"
|
|
|
|
out=$(BYPASS_MIN_RELEASE_AGE=1 github_upstream_release "$pkg" 86400 2>/dev/null) || out="<error>"
|
|
check "bypass lifts the quarantine" "2.0.0" "$(jq -r '.pkgver // "<none>"' <<<"$out")"
|
|
check "x86_64 checksum via * binary-mode prefix" "$sum_x20" "$(jq -r '.sha256sums.x86_64[0] // "<none>"' <<<"$out")"
|
|
|
|
out=$(github_upstream_release "$pkg" 8640000 2>/dev/null) || out="<error>"
|
|
check "everything quarantined reports no update" "{}" "$(jq -c . <<<"$out")"
|
|
|
|
printf 'pkgver=1.9.0\npkgrel=1\n' > "$pkg/PKGBUILD"
|
|
out=$(github_upstream_release "$pkg" 86400 2>/dev/null) || out="<error>"
|
|
check "already checked in reports no update" "{}" "$(jq -c . <<<"$out")"
|
|
printf 'pkgver=1.0.0\npkgrel=1\n' > "$pkg/PKGBUILD"
|
|
|
|
echo "Failure paths:"
|
|
local rc
|
|
FIXTURE_RELEASES=$(jq -n '[{tag_name: "v1.9.0", published_at: "not-a-date", draft: false, prerelease: false}]')
|
|
rc=0; github_upstream_release "$pkg" 86400 >/dev/null 2>&1 || rc=$?
|
|
check "invalid published_at fails the sync" "1" "$rc"
|
|
|
|
FIXTURE_RELEASES=$(jq -n --arg old "$old2d" '[{tag_name: "release 1.9!", published_at: $old, draft: false, prerelease: false}]')
|
|
rc=0; github_upstream_release "$pkg" 86400 >/dev/null 2>&1 || rc=$?
|
|
check "unusable tag fails the sync" "1" "$rc"
|
|
|
|
FIXTURE_RELEASES=$(jq -n --arg old "$old2d" '[{tag_name: "v1.9.0", published_at: $old, draft: false, prerelease: false}]')
|
|
FIXTURE_CHECKSUMS="$sum_x19 ./tool-v1.9.0-x64.tar.xz"
|
|
rc=0; github_upstream_release "$pkg" 86400 >/dev/null 2>&1 || rc=$?
|
|
check "missing aarch64 checksum fails the sync" "1" "$rc"
|
|
|
|
echo "Quarantine backstop:"
|
|
local rel st
|
|
rel=$(jq -n --arg p "$old2d" '{pkgver: "1.9.0", published_at: $p, sha256sums: {}}')
|
|
st=0; release_age_status "$rel" 86400 || st=$?
|
|
check "old enough passes" "0" "$st"
|
|
rel=$(jq -n --arg p "$young" '{pkgver: "2.0.0", published_at: $p, sha256sums: {}}')
|
|
st=0; release_age_status "$rel" 86400 || st=$?
|
|
check "too young is held" "1" "$st"
|
|
st=0; release_age_status "$rel" 0 || st=$?
|
|
check "no policy passes anything" "0" "$st"
|
|
st=0; BYPASS_MIN_RELEASE_AGE=1 release_age_status "$rel" 86400 || st=$?
|
|
check "deliberate bypass passes" "0" "$st"
|
|
rel=$(jq -n '{pkgver: "2.0.0", sha256sums: {}}')
|
|
st=0; release_age_status "$rel" 86400 || st=$?
|
|
check "missing published_at is unprovable" "2" "$st"
|
|
|
|
echo "Duration parser:"
|
|
local agepkg="$TEMP_DIR/selftest-age"
|
|
mkdir -p "$agepkg/.omarchy"
|
|
check_age() {
|
|
local json_value="$1" expected="$2" got
|
|
jq -n "{source: \"local\", min_release_age: $json_value}" > "$agepkg/.omarchy/package.json"
|
|
got=$(package_min_release_age_seconds "$agepkg") || got="<reject>"
|
|
check "min_release_age $json_value" "$expected" "$got"
|
|
}
|
|
check_age '"24h"' 86400
|
|
check_age '"90m"' 5400
|
|
check_age '"2d"' 172800
|
|
check_age '3600' 3600
|
|
check_age '"600s"' 600
|
|
check_age '"010h"' 36000
|
|
check_age '"abc"' "<reject>"
|
|
check_age '"24hh"' "<reject>"
|
|
check_age 'false' "<reject>"
|
|
check_age '"9999999999"' "<reject>"
|
|
|
|
echo "Manifest validation:"
|
|
printf 'pkgver=1.0.0\n' > "$agepkg/PKGBUILD"
|
|
local vst
|
|
echo '{"source": "local", "upstream": false}' > "$agepkg/.omarchy/package.json"
|
|
vst=0; validate_package_metadata "$agepkg" >/dev/null || vst=$?
|
|
check "upstream: false is rejected" "1" "$vst"
|
|
echo '{"source": "local", "upstream": {"github": "example/tool"}}' > "$agepkg/.omarchy/package.json"
|
|
vst=0; validate_package_metadata "$agepkg" >/dev/null || vst=$?
|
|
check "upstream without checksums/assets is rejected" "1" "$vst"
|
|
cp "$pkg/.omarchy/package.json" "$agepkg/.omarchy/package.json"
|
|
vst=0; validate_package_metadata "$agepkg" >/dev/null || vst=$?
|
|
check "the real declaration shape is accepted" "0" "$vst"
|
|
|
|
echo ""
|
|
if [[ "$failures" -eq 0 ]]; then
|
|
print_success "Self-test passed"
|
|
else
|
|
print_error "$failures self-test failure(s)"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
if [[ ${#SPECIFIC_PACKAGES[@]} -gt 0 && "${SPECIFIC_PACKAGES[0]}" == "self-test" ]]; then
|
|
cmd_self_test
|
|
exit 0
|
|
fi
|
|
|
|
if [[ ${#SPECIFIC_PACKAGES[@]} -gt 0 ]]; then
|
|
SPECIFIC_MODE=true
|
|
for package in "${SPECIFIC_PACKAGES[@]}"; do
|
|
sync_package "$package"
|
|
done
|
|
else
|
|
while IFS= read -r package; do
|
|
sync_package "$package"
|
|
done < <(packages_for_upstream_sync)
|
|
fi
|
|
|
|
echo ""
|
|
if [[ $FAILED -gt 0 ]]; then
|
|
print_error "Upstream sync completed with failures"
|
|
else
|
|
print_success "Upstream sync complete!"
|
|
fi
|
|
echo " Target: $PKGBUILDS_DIR"
|
|
echo " Updated: $UPDATED"
|
|
echo " Skipped: $SKIPPED"
|
|
echo " Failed: $FAILED"
|
|
|
|
if [[ $FAILED -gt 0 ]]; then
|
|
exit 1
|
|
fi
|