Automate PR label classification and update documentation #6
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: PR Labels | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| pr_numbers: | |
| description: One or more PR numbers to label. Separate multiple PRs with spaces, commas, or new lines. | |
| required: true | |
| type: string | |
| pull_request_target: | |
| types: | |
| - opened | |
| - reopened | |
| - labeled | |
| - unlabeled | |
| - synchronize | |
| - edited | |
| concurrency: | |
| group: pr-labels-${{ github.event_name == 'workflow_dispatch' && github.run_id || github.event.pull_request.number }} | |
| cancel-in-progress: true | |
| jobs: | |
| labeler: | |
| name: apply labels | |
| if: github.repository == 'wallentx/jobscout' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| permissions: | |
| contents: read | |
| issues: write | |
| pull-requests: write | |
| steps: | |
| - name: Normalize manual PR numbers | |
| id: pr-numbers | |
| if: github.event_name == 'workflow_dispatch' | |
| env: | |
| INPUT_PR_NUMBERS: ${{ inputs.pr_numbers }} | |
| run: | | |
| set -eu | |
| pr_numbers=$( | |
| printf '%s\n' "$INPUT_PR_NUMBERS" | | |
| tr ',;' '\n' | | |
| tr ' ' '\n' | | |
| awk ' | |
| NF { | |
| if ($0 !~ /^[0-9]+$/) { | |
| printf "Invalid PR number: %s\n", $0 > "/dev/stderr" | |
| exit 1 | |
| } | |
| } | |
| ' | |
| ) | |
| if [ -z "$pr_numbers" ]; then | |
| echo "No PR numbers provided" >&2 | |
| exit 1 | |
| fi | |
| { | |
| echo "value<<EOF" | |
| printf '%s\n' "$pr_numbers" | |
| echo "EOF" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Label pull request | |
| uses: actions/labeler@v6 | |
| with: | |
| configuration-path: .github/labeler.yml | |
| pr-number: ${{ github.event_name == 'workflow_dispatch' && steps.pr-numbers.outputs.value || github.event.pull_request.number }} | |
| classify-release-label: | |
| name: classify release label | |
| needs: labeler | |
| if: github.repository == 'wallentx/jobscout' && github.event_name == 'pull_request_target' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| issues: write | |
| steps: | |
| - name: Check out repository | |
| uses: actions/checkout@v6 | |
| - name: Actions Toolbox | |
| uses: wallentx/gh-actions/composite/actions-toolbox@main | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| with: | |
| checkout: false | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| - name: Install Copilot CLI | |
| run: npm install -g @github/copilot | |
| - name: Classify PR for release label | |
| id: classify | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| : "${GH_PR:?Actions Toolbox did not set GH_PR}" | |
| : "${GH_PR_TITLE:?Actions Toolbox did not set GH_PR_TITLE}" | |
| : "${GH_PR_URL:?Actions Toolbox did not set GH_PR_URL}" | |
| : "${COPILOT_GITHUB_TOKEN:?Missing repository secret COPILOT_GITHUB_TOKEN}" | |
| pr_body="$(printf '%s\n' "${GH_PR_BODY_JSON:-\"\"}" | jq -r '.')" | |
| pr_files="$(gh pr view "$GH_PR" --repo "$GITHUB_REPOSITORY" --json files --jq '.files[].path')" | |
| raw_label="$( | |
| { | |
| echo "Classify this pull request and return only the release-note label." | |
| printf '\nPR CONTEXT:\n' | |
| echo "PR TITLE:" | |
| printf '%s\n' "$GH_PR_TITLE" | sed -n '1,80p' | |
| printf '\nPR BODY:\n' | |
| printf '%s\n' "$pr_body" | sed -n '1,240p' | |
| printf '\nCHANGED FILES:\n' | |
| printf '%s\n' "$pr_files" | sed -n '1,300p' | |
| printf '\nDIFF:\n' | |
| curl -fsSL "${GH_PR_URL}.diff" | sed -n '1,1500p' | |
| } | copilot \ | |
| -s \ | |
| --no-color \ | |
| --no-banner \ | |
| --no-auto-update \ | |
| --no-ask-user \ | |
| --deny-tool=shell \ | |
| --deny-tool=write \ | |
| --deny-tool=read \ | |
| --deny-tool=url \ | |
| --deny-tool=memory \ | |
| | tr -d '\r' \ | |
| | awk 'NF { print; exit }' | |
| )" | |
| label="$( | |
| printf '%s' "$raw_label" \ | |
| | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//' \ | |
| | tr '[:upper:]' '[:lower:]' | |
| )" | |
| case "$label" in | |
| added|changed|fixed|dependencies) | |
| ;; | |
| *) | |
| echo "Copilot returned invalid label: ${raw_label:-<empty>}" >&2 | |
| exit 1 | |
| ;; | |
| esac | |
| echo "label=$label" >> "$GITHUB_OUTPUT" | |
| - name: Replace release-note labels | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| NEW_LABEL: ${{ steps.classify.outputs.label }} | |
| run: | | |
| set -euo pipefail | |
| : "${GH_PR:?Actions Toolbox did not set GH_PR}" | |
| existing="$( | |
| gh pr view "$GH_PR" --repo "$GITHUB_REPOSITORY" --json labels \ | |
| --jq '.labels[].name' | |
| )" | |
| release_labels="added changed fixed dependencies" | |
| has_new_label=false | |
| has_other_release_label=false | |
| for label in $release_labels; do | |
| if printf '%s\n' "$existing" | grep -Fxq "$label"; then | |
| if [ "$label" = "$NEW_LABEL" ]; then | |
| has_new_label=true | |
| else | |
| has_other_release_label=true | |
| fi | |
| fi | |
| done | |
| if [ "$has_new_label" = true ] && [ "$has_other_release_label" = false ]; then | |
| echo "Release label already set to $NEW_LABEL" | |
| exit 0 | |
| fi | |
| for label in $release_labels; do | |
| if [ "$label" != "$NEW_LABEL" ] && printf '%s\n' "$existing" | grep -Fxq "$label"; then | |
| gh pr edit "$GH_PR" --repo "$GITHUB_REPOSITORY" --remove-label "$label" | |
| fi | |
| done | |
| if [ "$has_new_label" = false ]; then | |
| gh pr edit "$GH_PR" --repo "$GITHUB_REPOSITORY" --add-label "$NEW_LABEL" | |
| fi | |
| echo "Applied label: $NEW_LABEL" |