name: PR Title Checker on: pull_request: types: [opened, edited, synchronize] jobs: enforce-pr-title: name: Validate PR Title Format runs-on: ubuntu-latest steps: - name: Check PR Title and Manage Labels/Comments env: API_TOKEN: ${{ secrets.BOT_ACCESS_TOKEN }} API_BASE_URL: https://git.psmattas.com/api/v1 REPO: ${{ gitea.repository }} PR_NUMBER: ${{ gitea.event.pull_request.number }} PR_TITLE: ${{ gitea.event.pull_request.title }} COMMENT_BODY_INVALID: | Hi @${{ gitea.actor }}, thanks for the contribution! The title of this pull request does not follow the required format. This PR is currently **blocked from merging**. Please update the title to match one of these formats: **Board issue:** `EC-[NUMBER]: [TYPE]: Description` **Repo-only:** `[TYPE]: Description` - **Valid types:** `FEAT`, `FIX`, `DOCS`, `REFACTOR`, `STYLE`, `PERF`, `TEST`, `CHORE` - **Examples:** - `EC-10: FEAT: Add monitoring stack` - `FIX: Correct typo in health endpoint` PRs with an `EC-` prefix automatically reference `Evercatch/evercatch-board#`. COMMENT_BODY_WIP: | Hi @${{ gitea.actor }}, this pull request has been marked as a **Work in Progress**. While in this state, it is **blocked from merging**. When the PR is ready for review, please remove the `WIP:` prefix from the title. This check will re-run automatically and remove the blocking label. run: | WIP_LABEL="status: work-in-progress" INVALID_LABEL="status: invalid-title" WIP_REGEX="^WIP:" # Accept either "EC-NUM: TYPE: desc" or "TYPE: desc" FORMAT_REGEX="^(EC-[0-9]+: )?(FEAT|FIX|DOCS|REFACTOR|STYLE|PERF|TEST|CHORE): .+$" ISSUES_API_URL="$API_BASE_URL/repos/$REPO/issues/$PR_NUMBER" if [ -z "$PR_NUMBER" ]; then echo "::error::Could not determine PR Number. Aborting." exit 1 fi if [[ "$PR_TITLE" =~ $WIP_REGEX ]]; then echo "â„šī¸ PR is marked as a Work in Progress." LABEL_JSON=$(echo "$WIP_LABEL" | jq -R '{"labels": [.]}') curl -s -f -X POST -H "Authorization: token $API_TOKEN" -H "Content-Type: application/json" -d "$LABEL_JSON" "$ISSUES_API_URL/labels" WIP_COMMENT_IDENTIFIER="" EXISTING_COMMENTS=$(curl -s -H "Authorization: token $API_TOKEN" "$ISSUES_API_URL/comments") if ! echo "$EXISTING_COMMENTS" | grep -q "$WIP_COMMENT_IDENTIFIER"; then echo "Posting WIP comment." COMMENT_JSON=$(jq -n --arg body "$COMMENT_BODY_WIP" '{"body": $body}') curl -s -f -X POST -H "Authorization: token $API_TOKEN" -H "Content-Type: application/json" -d "$COMMENT_JSON" "$ISSUES_API_URL/comments" fi exit 1 elif [[ "$PR_TITLE" =~ $FORMAT_REGEX ]]; then echo "✅ PR title format is correct and not a WIP." echo "Checking for labels to remove..." CURRENT_LABELS_JSON=$(curl -s -H "Authorization: token $API_TOKEN" "$ISSUES_API_URL/labels") INVALID_LABEL_ID=$(echo "$CURRENT_LABELS_JSON" | jq --arg name "$INVALID_LABEL" '.[] | select(.name == $name) | .id') if [ -n "$INVALID_LABEL_ID" ]; then echo "Found and removing '$INVALID_LABEL' (ID: $INVALID_LABEL_ID)..." curl -s -f -X DELETE -H "Authorization: token $API_TOKEN" "$ISSUES_API_URL/labels/$INVALID_LABEL_ID" fi WIP_LABEL_ID=$(echo "$CURRENT_LABELS_JSON" | jq --arg name "$WIP_LABEL" '.[] | select(.name == $name) | .id') if [ -n "$WIP_LABEL_ID" ]; then echo "Found and removing '$WIP_LABEL' (ID: $WIP_LABEL_ID)..." curl -s -f -X DELETE -H "Authorization: token $API_TOKEN" "$ISSUES_API_URL/labels/$WIP_LABEL_ID" fi exit 0 else echo "❌ ERROR: PR title does not match the required format." echo "Adding '$INVALID_LABEL' label..." LABEL_JSON=$(echo "$INVALID_LABEL" | jq -R '{"labels": [.]}') curl -s -f -X POST -H "Authorization: token $API_TOKEN" -H "Content-Type: application/json" -d "$LABEL_JSON" "$ISSUES_API_URL/labels" # Remove WIP label just in case ENCODED_WIP_LABEL=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$WIP_LABEL'))") curl -s -X DELETE -H "Authorization: token $API_TOKEN" "$ISSUES_API_URL/labels/$ENCODED_WIP_LABEL" || true INVALID_COMMENT_IDENTIFIER="" EXISTING_COMMENTS=$(curl -s -H "Authorization: token $API_TOKEN" "$ISSUES_API_URL/comments") if ! echo "$EXISTING_COMMENTS" | grep -q "$INVALID_COMMENT_IDENTIFIER"; then echo "Posting invalid title comment." COMMENT_JSON=$(jq -n --arg body "$COMMENT_BODY_INVALID" '{"body": $body}') curl -s -f -X POST -H "Authorization: token $API_TOKEN" -H "Content-Type: application/json" -d "$COMMENT_JSON" "$ISSUES_API_URL/comments" fi exit 1 fi