#!/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 # Fail CLOSED, at the action boundary. An earlier head here exited 0 with a # notice so the PR check would not be red; @codex-reviewer-andresmgsl was # right that this conflates two different questions. "This action cannot # produce a verdict" is the ACTION's contract and must stay a refusal; "this # check should not block the board" is the CALLER's decision, and it belongs # in .github/workflows/refs-guard.yml, which skips on a backend this action # cannot speak until #199 ports it. forge_preflight || exit 1 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[@]}"