Fix large GitHub release JSON handling

This commit is contained in:
GitHub Release Monitor
2026-10-11 21:23:08 +07:00
committed by latypov
parent aa4c241f99
commit 8f84934c46
2 changed files with 49 additions and 16 deletions
+8
View File
@@ -57,3 +57,11 @@ At 09:00 server-local time:
```
Run the monitor under a dedicated user where possible. Keep `.env` private and do not commit it. Use `./monitor.sh --test-telegram` to verify Telegram and proxy connectivity.
### Large release histories
GitHub response pages are processed through temporary files, rather than passed
through command-line arguments. The state file is read directly by `jq`.
This prevents `Argument list too long` for repositories with large release notes.
Run `bash -n monitor.sh` and `./monitor.sh --dry-run --verbose` to verify.
Regular → Executable
+41 -16
View File
@@ -93,29 +93,48 @@ test_telegram() {
if send_telegram "$message"; then log INFO 'Test notification sent'; else log ERROR 'Test notification failed'; return 1; fi
}
fetch_releases() {
local repo="$1" page response count result='[]'
local repo="$1" page count response_file releases_file
response_file="$(mktemp)" || return 1
releases_file="$(mktemp)" || { rm -f "$response_file"; return 1; }
for ((page=1; page<=GITHUB_MAX_PAGES; page++)); do
debug "Fetching $repo page=$page"
response="$(curl "${CURL_OPTIONS[@]}" "${GITHUB_HEADERS[@]}" \
"${GITHUB_API}/repos/${repo}/releases?per_page=${GITHUB_PER_PAGE}&page=${page}")" || return 1
jq -e 'type == "array"' <<< "$response" >/dev/null || return 1
count="$(jq 'length' <<< "$response")"
result="$(jq -cn --argjson old "$result" --argjson new "$response" '$old + $new')" || return 1
if ! curl "${CURL_OPTIONS[@]}" "${GITHUB_HEADERS[@]}" \
--output "$response_file" \
"${GITHUB_API}/repos/${repo}/releases?per_page=${GITHUB_PER_PAGE}&page=${page}"; then
rm -f "$response_file" "$releases_file"
return 1
fi
if ! count="$(jq -er 'if type == "array" then length else error("Invalid releases response") end' "$response_file")"; then
rm -f "$response_file" "$releases_file"
return 1
fi
if ! jq -c --argjson prereleases "$INCLUDE_PRERELEASES" '
.[] | select(.draft == false) |
select($prereleases or (.prerelease == false)) |
{id: (.id | tostring), tag: .tag_name, name: .name,
url: .html_url, published: .published_at, prerelease: .prerelease}
' "$response_file" >> "$releases_file"; then
rm -f "$response_file" "$releases_file"
return 1
fi
if (( count < GITHUB_PER_PAGE )); then break; fi
done
jq -c --argjson prereleases "$INCLUDE_PRERELEASES" '
[.[] | select(.draft == false) | select($prereleases or (.prerelease == false)) |
{id: (.id | tostring), tag: .tag_name, name: .name, url: .html_url,
published: .published_at, prerelease: .prerelease}]
' <<< "$result"
local status=0
jq -sc '.' "$releases_file" || status=$?
rm -f "$response_file" "$releases_file"
return "$status"
}
write_state() {
local repo="$1" ids="$2" mode="$3" temp
temp="$(mktemp "${STATE_FILE}.tmp.XXXXXX")" || return 1
if ! jq --arg repo "$repo" --argjson ids "$ids" --arg mode "$mode" '
if $mode == "replace" then .[$repo] = $ids
else .[$repo] = ((.[$repo] // []) + $ids | unique) end
' "$STATE_FILE" > "$temp"; then rm -f "$temp"; return 1; fi
if ! jq --arg repo "$repo" --arg mode "$mode" --slurpfile ids /dev/stdin '
($ids[0] // []) as $new_ids |
if $mode == "replace" then .[$repo] = $new_ids
else .[$repo] = ((.[$repo] // []) + $new_ids | unique) end
' "$STATE_FILE" <<< "$ids" > "$temp"; then rm -f "$temp"; return 1; fi
if ! mv -f "$temp" "$STATE_FILE"; then rm -f "$temp"; return 1; fi
}
format_message() {
@@ -151,7 +170,13 @@ process_repo() {
log INFO "Baseline initialized: $repo ($count releases)"
return 0
fi
new_releases="$(jq -c --argjson seen "$previous" '[.[] | select(.id as $id | $seen | index($id) | not)] | reverse' <<< "$releases")" || return 1
# Read the existing state from a file, not from argv (ARG_MAX).
local state_source="$STATE_FILE"
[[ -f "$state_source" ]] || state_source=/dev/null
new_releases="$(jq -c --slurpfile state "$state_source" --arg repo "$repo" '
($state[0][$repo] // []) as $seen |
[.[] | select(.id as $id | $seen | index($id) | not)] | reverse
' <<< "$releases")" || return 1
count="$(jq 'length' <<< "$new_releases")"
if (( count == 0 )); then log INFO "No new releases: $repo"; return 0; fi
log INFO "Found $count new releases: $repo"