#!/usr/bin/env bash set -euo pipefail # The composite action's executable boundary (#218). Keeping the GraphQL # gather here lets the offline contract test replace `gh` and prove that # failed and partial reads cannot accidentally produce a green verdict. # THIS ACTION IS STILL gh-ONLY, AND SAYS SO (#198 spec 4, #199 ports it). # Its entire gather is a single GraphQL query issued through `gh`, and # Forgejo serves no # GraphQL surface at all — `/api/graphql` 404s on this instance, and a real # forgejo-runner job arrives with GITHUB_GRAPHQL_URL set to the empty string # (lib/forge.sh's header). There is no endpoint to translate this to, so # unlike every other call site the merge touched it cannot be ported here; # it has to be re-expressed over REST, which is #199. # # Until then the declaration is the honest move: CEREMONY_FORGE_CLIENT names # the client this file actually speaks, and forge_preflight refuses loudly on # a forge that cannot serve it — rather than reading nothing and reporting a # verdict. That is lib/forge.sh's own rule, "Never 'probably github'", # applied to the one action that has not caught up yet. # shellcheck source=lib/forge.sh . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/../../lib/forge.sh" export CEREMONY_FORGE_CLIENT=gh # The refusal and the SCHEDULING are two different questions, and #198's first # head conflated them: preflight refused correctly on this forge and turned # every PR's `Refs guard` red, which blocks the board rather than protecting # it. A guard that cannot run here must not claim a verdict — but it also must # not stand permanently red for a port that has its own issue. # # So: not-runnable-here is reported and skipped, loudly and by name, and only # a preflight failure for any OTHER reason is fatal. The distinction is the # forge, not the exit code — on a forge this action CAN speak, a preflight # failure is still a hard refusal, which is the case the tests drive. preflight_err="$(mktemp)" trap 'rm -f "$preflight_err"' EXIT if ! forge_preflight 2>"$preflight_err"; then cat "$preflight_err" >&2 if [ "$(forge_detect 2>/dev/null)" != github ]; then printf '::notice::refs-not-closing: not run — this action is still gh-only (its gather is GraphQL, which this forge does not serve) and #199 ports it. No verdict was produced.\n' exit 0 fi exit 1 fi owner="${GITHUB_REPOSITORY%%/*}" name="${GITHUB_REPOSITORY#*/}" [ -n "${PR_NUMBER:-}" ] || { echo "refs-not-closing: pull request number is unavailable" >&2 exit 1 } # GraphQL variables are literal API syntax; the shell must not expand them. # shellcheck disable=SC2016 facts="$(gh api graphql \ -f query='query($owner: String!, $name: String!, $number: Int!) { repository(owner: $owner, name: $name) { pullRequest(number: $number) { body closingIssuesReferences(first: 100) { nodes { number } pageInfo { hasNextPage } } } } }' \ -F owner="$owner" -F name="$name" -F number="$PR_NUMBER")" body_file="$(mktemp)" closing_file="$(mktemp)" trap 'rm -f "$body_file" "$closing_file"' EXIT jq -er ' .data.repository.pullRequest | if . == null then error("pull request was not returned") else .body // "" end ' <<<"$facts" >"$body_file" jq -r ' .data.repository.pullRequest.closingIssuesReferences | if . == null then error("closing issue references were not returned") elif .pageInfo.hasNextPage then error("more than 100 closing issue references; refusing a partial verdict") else .nodes[].number end ' <<<"$facts" >"$closing_file" mapfile -t closing_issues <"$closing_file" bash "$GITHUB_ACTION_PATH/refs-not-closing.sh" \ "$body_file" "${closing_issues[@]}"