mirror of
https://github.com/Sea-Haven-Industries/shoc-pr-review-runner.git
synced 2026-09-30 08:03:20 +00:00
73 lines
3.3 KiB
Bash
Executable file
73 lines
3.3 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# Resolve a PR's exact head (spec §10.1) and record its metadata + CI status.
|
|
#
|
|
# Usage: resolve-pr-head.sh <side: frontend|backend> <repo owner/name> <pr-number>
|
|
# Reads: GH_TOKEN (read-only App installation token)
|
|
# Writes: $ARTIFACTS_DIR/<side>-pr.json (metadata consumed by evidence + agent)
|
|
# <side>_sha / <side>_short_sha to $GITHUB_OUTPUT when present.
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
# shellcheck source=lib.sh
|
|
source "$SCRIPT_DIR/lib.sh"
|
|
|
|
side="${1:?side required}"
|
|
repo="${2:?repo required}"
|
|
pr="${3:?pr number required}"
|
|
require_env GH_TOKEN
|
|
|
|
pr_json="$(gh api "repos/$repo/pulls/$pr")" || die "failed to fetch $repo PR #$pr"
|
|
|
|
state="$(jq -r '.state' <<<"$pr_json")"
|
|
head_sha="$(jq -r '.head.sha // empty' <<<"$pr_json")"
|
|
head_repo="$(jq -r '.head.repo.full_name // empty' <<<"$pr_json")"
|
|
|
|
[ -n "$head_sha" ] || die "$repo PR #$pr has an empty head"
|
|
[ "$state" = "open" ] || log "WARNING: $repo PR #$pr state is '$state', not open"
|
|
[ "$head_repo" = "$repo" ] || die "$repo PR #$pr head lives in '$head_repo' (fork heads are not supported)"
|
|
|
|
short_sha="${head_sha:0:7}"
|
|
|
|
# CI status on the exact head: green / red / pending / missing / unknown.
|
|
#
|
|
# A failed API call must NEVER be reported as "missing": that would state as a
|
|
# fact ("this PR has no CI") what is actually an unread signal, which is the
|
|
# exact failure mode this runner exists to prevent. Reading check-runs needs the
|
|
# App's `checks: read` permission; without it the call 403s and the honest
|
|
# answer is "unknown".
|
|
checks_err="$ARTIFACTS_DIR/$side-checks-error.txt"
|
|
if checks_json="$(gh api "repos/$repo/commits/$head_sha/check-runs" \
|
|
--jq '{total: .total_count, runs: [.check_runs[] | {name, status, conclusion}]}' 2>"$checks_err")"; then
|
|
ci_status="$(jq -r '
|
|
if .total == 0 then "missing"
|
|
elif ([.runs[] | select(.status != "completed")] | length) > 0 then "pending"
|
|
elif ([.runs[] | select(.conclusion != "success" and .conclusion != "neutral" and .conclusion != "skipped")] | length) > 0 then "red"
|
|
else "green" end' <<<"$checks_json")"
|
|
rm -f "$checks_err"
|
|
else
|
|
reason="$(tr -d '\000-\037' <"$checks_err" | cut -c1-160)"
|
|
log "WARNING: could not read check-runs for $repo@$short_sha: $reason"
|
|
ci_status="unknown (check-runs unreadable; the review App likely lacks 'checks: read')"
|
|
checks_json='{"total":0,"runs":[],"unreadable":true}'
|
|
fi
|
|
|
|
jq -n \
|
|
--arg side "$side" --arg repo "$repo" --argjson pr "$pr" \
|
|
--arg title "$(jq -r '.title' <<<"$pr_json")" \
|
|
--arg head_ref "$(jq -r '.head.ref' <<<"$pr_json")" \
|
|
--arg base_ref "$(jq -r '.base.ref' <<<"$pr_json")" \
|
|
--arg head_sha "$head_sha" --arg short_sha "$short_sha" \
|
|
--arg state "$state" --arg ci_status "$ci_status" \
|
|
--argjson mergeable "$(jq '.mergeable' <<<"$pr_json")" \
|
|
--argjson checks "$checks_json" \
|
|
'{side: $side, repo: $repo, pr: $pr, title: $title, head_ref: $head_ref,
|
|
base_ref: $base_ref, head_sha: $head_sha, short_sha: $short_sha,
|
|
state: $state, mergeable: $mergeable, ci_status: $ci_status, checks: $checks}' \
|
|
>"$ARTIFACTS_DIR/$side-pr.json"
|
|
|
|
out="${GITHUB_OUTPUT:-/dev/stdout}"
|
|
{
|
|
printf '%s_sha=%s\n' "$side" "$head_sha"
|
|
printf '%s_short_sha=%s\n' "$side" "$short_sha"
|
|
} >>"$out"
|
|
|
|
log "$side: $repo#$pr head=$short_sha base=$(jq -r '.base.ref' <<<"$pr_json") ci=$ci_status"
|