#!/usr/bin/env bash # Branch, PR body, and commit contract checker. # v2: exits 1 on violation. Consumers that want warn-only set # continue-on-error: true on the job (or stay on @v1). set -euo pipefail BRANCH="${HEAD_BRANCH:-}" TITLE="${PR_TITLE:-}" AUTHOR="${PR_AUTHOR:-}" BASE="${BASE_SHA:-}" HEAD="${HEAD_SHA:-}" SERVER_URL="${GITEA_SERVER_URL:-}" TOKEN="${GITEA_TOKEN:-}" REPOSITORY="${GITEA_REPOSITORY:-}" PR_NUMBER="${GITEA_PR_NUMBER:-}" # Break-glass: dfritz is exempt from all naming checks. if [ "${AUTHOR}" = "dfritz" ]; then echo "check-naming: dfritz break-glass — exempt" exit 0 fi FAILED=0 BRANCH_KIND="invalid" BRANCH_BUG="" # Gitea's renderer is the visibility contract. Validate its output instead of # maintaining a second Markdown parser in this action. RENDERED_BODY="" BODY="" if [ -z "${SERVER_URL}" ] || [ -z "${TOKEN}" ] || ! printf '%s' "${REPOSITORY}" | grep -qE '^[[:alnum:]_.-]+/[[:alnum:]_.-]+$' || ! printf '%s' "${PR_NUMBER}" | grep -qE '^[1-9][0-9]*$'; then echo "FAIL[check-naming]: server-url, token, repository, and PR number are required to load the PR body" FAILED=1 elif ! BODY=$(curl --fail --silent --show-error \ --connect-timeout 5 \ --max-time 15 \ --header "Authorization: token ${TOKEN}" \ "${SERVER_URL%/}/api/v1/repos/${REPOSITORY}/pulls/${PR_NUMBER}" | jq -er '.body | if type == "string" then . else error("body is not a string") end'); then echo "FAIL[check-naming]: Gitea could not load the PR body" FAILED=1 elif ! RENDERED_BODY=$(printf '%s' "${BODY}" | jq -Rs '{Text: ., Mode: "gfm"}' | curl --fail --silent --show-error \ --connect-timeout 5 \ --max-time 15 \ --header "Authorization: token ${TOKEN}" \ --header "Content-Type: application/json" \ --data-binary @- "${SERVER_URL%/}/api/v1/markdown"); then echo "FAIL[check-naming]: Gitea could not render the PR body" FAILED=1 fi # A collapsed disclosure is valid supporting content, but provenance inside one # is not visible by default. Remove only collapsed details subtrees; an open # disclosure remains visible unless one of its ancestors is collapsed. Media # fallback subtrees are likewise hidden when the browser supports the element. VISIBLE_BODY=$(printf '%s\n' "${RENDERED_BODY}" | awk ' { line=$0 "\n" for (i=1; i<=length(line); i++) { char=substr(line, i, 1) if (in_tag) { tag=tag char if (char == ">") { lower=tolower(tag) if (lower ~ /^<(video|audio)([[:space:]>])/) media_depth++ else if (lower ~ /^<\/(video|audio)([[:space:]>])/) { if (media_depth > 0) media_depth-- } else if (lower ~ /^])/) { attributes=lower gsub(/"[^"]*"/, "", attributes) parent_hidden=(details_depth > 0 && hidden[details_depth]) details_depth++ hidden[details_depth]=(parent_hidden || attributes !~ /[[:space:]]open([[:space:]=>]|$)/) } else if (lower ~ /^<\/details([[:space:]>])/) { if (details_depth > 0) { delete hidden[details_depth] details_depth-- } } else if (!hidden[details_depth] && media_depth == 0) printf "%s", tag in_tag=0 tag="" } } else if (char == "<") { in_tag=1 tag=char } else if (!hidden[details_depth] && media_depth == 0) printf "%s", char } } ') hidden_content_guidance='move ## Tracking and ## Attribution outside collapsed
or