Skip to content

Commit 76a26b2

Browse files
locus313Copilot
andcommitted
feat: add dual-auth support and gh_api_paginate to shared lib
- Auto-resolve GITHUB_TOKEN from active gh CLI session at source time so curl-based scripts work with either a PAT or gh CLI auth - Add configure_gh_auth() to bridge GITHUB_TOKEN→GH_TOKEN for scripts that use the gh CLI directly - Add gh_api_paginate() — paginated REST helper that follows Link headers, streams items through a jq filter, handles 404/422 silently, and supports a custom API version header for Copilot endpoints Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 67fce02 commit 76a26b2

1 file changed

Lines changed: 115 additions & 0 deletions

File tree

lib/github-common.sh

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,17 @@
1414
# print_status / print_success / print_warning / print_error
1515
# require_env_var <VAR> [description] — exit if variable is unset/empty
1616
# require_command <cmd> — exit if command not found
17+
# configure_gh_auth [scope_hint] — bridge GITHUB_TOKEN→GH_TOKEN or verify gh session
1718
# validate_github_token [bearer] — verify token via /user endpoint
19+
#
20+
# Token auto-resolution (at source time):
21+
# If GITHUB_TOKEN is unset and gh CLI is available, the token is automatically
22+
# resolved from the active gh auth session so curl-based scripts work with
23+
# either a GITHUB_TOKEN env var or a gh CLI session.
1824
# validate_token <VAR_NAME> — verify a secondary token variable
1925
# validate_slug <value> [label] — exit if value contains unsafe chars
2026
# gh_api <path|url> [curl args...] — Bearer-auth REST helper with retry
27+
# gh_api_paginate <path> [filter] [version] — paginated REST, follows Link headers
2128
# _paginate_orgs_endpoint <filter> <url_tpl> — page through an org list
2229
# _graphql_enterprise_orgs — GraphQL cursor-based enterprise orgs
2330
# get_enterprise_orgs — three-tier enterprise org resolver
@@ -68,6 +75,29 @@ require_command() {
6875
fi
6976
}
7077

78+
###
79+
## configure_gh_auth [scope_hint]
80+
## Bridges GITHUB_TOKEN into the gh CLI so scripts can accept either a token
81+
## or an active gh auth session interchangeably.
82+
##
83+
## When GITHUB_TOKEN is set: exports it as GH_TOKEN (gh CLI reads this env var).
84+
## When GITHUB_TOKEN is not set: verifies gh auth status and exits with an error
85+
## if no session is active. scope_hint is appended to the error message.
86+
##
87+
## Usage:
88+
## configure_gh_auth "gh auth login"
89+
## configure_gh_auth 'gh auth refresh --scopes "read:enterprise,manage_billing:enterprise"'
90+
###
91+
configure_gh_auth() {
92+
local scope_hint="${1:-gh auth login}"
93+
if [[ -n "${GITHUB_TOKEN:-}" ]]; then
94+
export GH_TOKEN="$GITHUB_TOKEN"
95+
elif ! gh auth status >/dev/null 2>&1; then
96+
print_error "Not authenticated. Set GITHUB_TOKEN or run: ${scope_hint}"
97+
exit 1
98+
fi
99+
}
100+
71101
###
72102
## validate_token <TOKEN_VAR_NAME> [bearer]
73103
## Validates the token stored in the named variable by calling the /user endpoint.
@@ -174,6 +204,73 @@ gh_api() {
174204
return 1
175205
}
176206

207+
###
208+
## gh_api_paginate <path_or_url> [jq_filter] [api_version]
209+
## Paginated GitHub REST API helper using Link-header following.
210+
## Outputs each page's items (filtered by jq_filter) to stdout, one item per
211+
## line. Pipe the output to: jq -s '.' to get a JSON array of all items.
212+
## jq -s '. // []' to get [] when the endpoint 404s.
213+
## jq_filter defaults to .[] (one item per array element).
214+
## api_version defaults to 2022-11-28.
215+
## Returns 0 silently on 404/422 (empty output); returns 1 after 5 failed attempts.
216+
###
217+
gh_api_paginate() {
218+
local url="$1"
219+
local jq_filter="${2:-.[]}"
220+
local api_version="${3:-2022-11-28}"
221+
[[ "${url}" == http* ]] || url="${API_URL_PREFIX}${url}"
222+
223+
local _tmp_headers _tmp_body _http_code _attempt _next_url
224+
_tmp_headers=$(mktemp)
225+
_tmp_body=$(mktemp)
226+
227+
while [[ -n "${url}" ]]; do
228+
_http_code=""
229+
for _attempt in 1 2 3 4 5; do
230+
_http_code=$(curl -s \
231+
-D "${_tmp_headers}" \
232+
-o "${_tmp_body}" \
233+
-w "%{http_code}" \
234+
-H "Authorization: Bearer ${GITHUB_TOKEN}" \
235+
-H "Accept: application/vnd.github+json" \
236+
-H "X-GitHub-Api-Version: ${api_version}" \
237+
"${url}")
238+
case "${_http_code}" in
239+
200) break ;;
240+
404|422)
241+
rm -f "${_tmp_headers}" "${_tmp_body}"
242+
return 0
243+
;;
244+
403|429)
245+
print_warning "Rate limited (HTTP ${_http_code}). Sleeping 60s before retry ${_attempt}/5..."
246+
sleep 60
247+
;;
248+
*)
249+
print_warning "HTTP ${_http_code} for ${url} (attempt ${_attempt}/5)"
250+
sleep 5
251+
;;
252+
esac
253+
done
254+
255+
if [[ "${_http_code}" != "200" ]]; then
256+
rm -f "${_tmp_headers}" "${_tmp_body}"
257+
print_error "Failed to fetch ${url} after 5 attempts"
258+
return 1
259+
fi
260+
261+
jq -rc "${jq_filter}" "${_tmp_body}" 2>/dev/null || true
262+
263+
# Follow Link: <next-url>; rel="next" to the next page
264+
_next_url=$(grep -i "^link:" "${_tmp_headers}" \
265+
| grep -o '<[^>]*>; rel="next"' \
266+
| sed 's/<\([^>]*\)>.*/\1/' \
267+
|| true)
268+
url="${_next_url}"
269+
done
270+
271+
rm -f "${_tmp_headers}" "${_tmp_body}"
272+
}
273+
177274
###
178275
## _paginate_orgs_endpoint <jq_filter> <url_template>
179276
## Internal helper: pages through an org-list REST endpoint, printing one
@@ -293,3 +390,21 @@ get_enterprise_orgs() {
293390
'.[].login' \
294391
"/user/orgs?per_page=100&page=PAGE"
295392
}
393+
394+
###
395+
## Token auto-resolution (runs once at source time)
396+
## If GITHUB_TOKEN is not set, attempt to derive it from an active gh CLI
397+
## auth session. This allows scripts that use GITHUB_TOKEN with curl to work
398+
## with gh CLI auth as an alternative to an explicit token.
399+
## Scripts should still call require_env_var GITHUB_TOKEN or
400+
## validate_github_token to fail fast with a clear message if neither source
401+
## provides a token.
402+
###
403+
if [[ -z "${GITHUB_TOKEN:-}" ]] && command -v gh &>/dev/null; then
404+
_gh_resolved_token=$(gh auth token 2>/dev/null) || true
405+
if [[ -n "${_gh_resolved_token:-}" ]]; then
406+
GITHUB_TOKEN="$_gh_resolved_token"
407+
export GITHUB_TOKEN
408+
fi
409+
unset _gh_resolved_token
410+
fi

0 commit comments

Comments
 (0)