|
| 1 | +name: Gardener - Notify Slack |
| 2 | +# Runs after `Gardener - Notify Event` completes and does the real work: |
| 3 | +# applies the devtools-gardener label and posts a summary to Slack. |
| 4 | +# |
| 5 | +# The workflow_run trigger runs this job in the default-branch context with |
| 6 | +# full GITHUB_TOKEN permissions and Actions secret access — this is what |
| 7 | +# lets it succeed for Dependabot-opened PRs, where the upstream event |
| 8 | +# workflow can't label or reach secrets directly. |
| 9 | +on: |
| 10 | + workflow_run: |
| 11 | + workflows: ['Gardener - Notify Event'] |
| 12 | + types: [completed] |
| 13 | + |
| 14 | +permissions: |
| 15 | + contents: read |
| 16 | + issues: write |
| 17 | + pull-requests: write |
| 18 | + actions: read |
| 19 | + |
| 20 | +jobs: |
| 21 | + notify: |
| 22 | + # `conclusion == success` also covers runs where the capture job was |
| 23 | + # skipped by its `if` gate (no matching label, etc.) — in that case |
| 24 | + # no artifact was uploaded, so the download step below no-ops. |
| 25 | + if: github.event.workflow_run.conclusion == 'success' |
| 26 | + runs-on: ubuntu-latest |
| 27 | + steps: |
| 28 | + - name: Download event payload |
| 29 | + id: download |
| 30 | + continue-on-error: true |
| 31 | + uses: actions/download-artifact@v4 |
| 32 | + with: |
| 33 | + name: gardener-event |
| 34 | + run-id: ${{ github.event.workflow_run.id }} |
| 35 | + github-token: ${{ secrets.GITHUB_TOKEN }} |
| 36 | + |
| 37 | + - name: Add devtools-gardener label |
| 38 | + if: steps.download.outcome == 'success' |
| 39 | + env: |
| 40 | + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
| 41 | + GH_REPO: ${{ github.repository }} |
| 42 | + run: | |
| 43 | + ACTION=$(jq -r '.action' event.json) |
| 44 | + # On `labeled` events the label is already there — skip. |
| 45 | + if [ "$ACTION" != "opened" ]; then |
| 46 | + exit 0 |
| 47 | + fi |
| 48 | + NUMBER=$(jq -r '(.issue // .pull_request).number' event.json) |
| 49 | + if jq -e 'has("pull_request")' event.json > /dev/null; then |
| 50 | + gh pr edit "$NUMBER" --add-label devtools-gardener |
| 51 | + else |
| 52 | + gh issue edit "$NUMBER" --add-label devtools-gardener |
| 53 | + fi |
| 54 | +
|
| 55 | + - name: Post to Slack |
| 56 | + if: steps.download.outcome == 'success' |
| 57 | + continue-on-error: true |
| 58 | + env: |
| 59 | + SLACK_BOT_TOKEN: ${{ secrets.SLACK_GARDENER_BOT_TOKEN }} |
| 60 | + SLACK_CHANNEL_ID: ${{ vars.GARDENER_SLACK_CHANNEL_ID }} |
| 61 | + run: | |
| 62 | + KIND=$(jq -r 'if has("pull_request") then "PR" else "Issue" end' event.json) |
| 63 | + # Pull the body out, truncate, then convert GitHub Markdown to |
| 64 | + # Slack mrkdwn. Links and fenced code blocks are stashed before |
| 65 | + # the HTML-escape pass so their contents survive verbatim (a `&` |
| 66 | + # inside a URL must stay raw, and code content shouldn't be |
| 67 | + # mangled). Blockquote `> ` markers are also stashed so the |
| 68 | + # `>` → `>` escape doesn't break them. Everything else is |
| 69 | + # HTML-escaped so user-supplied `<`, `>`, `&` can't collide |
| 70 | + # with Slack link syntax or injected mentions like <!channel>. |
| 71 | + BODY=$(jq -r '(.issue // .pull_request).body // ""' event.json) |
| 72 | + if [ ${#BODY} -gt 1000 ]; then |
| 73 | + BODY="${BODY:0:1000}…" |
| 74 | + fi |
| 75 | + BODY=$(printf '%s' "$BODY" | perl -0777 -pe ' |
| 76 | + my @u; |
| 77 | + s{\[([^\]]+)\]\(([^)]+)\)}{push @u, $2; "\x01$#u\x02$1\x03"}ge; |
| 78 | + my @c; |
| 79 | + s{^```[^\n]*\n(.*?)\n```$}{push @c, $1; "\x04$#c\x05"}gems; |
| 80 | + s/^> /\x06/gm; |
| 81 | + s/^#{1,6}\s+(.+)$/*$1*/gm; |
| 82 | + s/\*\*(.+?)\*\*/*$1*/g; |
| 83 | + s/^(\s*)- \[x\]\s+/$1✓ /gm; |
| 84 | + s/^(\s*)[-*]\s+/$1• /gm; |
| 85 | + s/&/&/g; |
| 86 | + s/</</g; |
| 87 | + s/>/>/g; |
| 88 | + s/\x06/> /g; |
| 89 | + s{\x01(\d+)\x02(.*?)\x03}{"<$u[$1]|$2>"}ge; |
| 90 | + s{\x04(\d+)\x05}{"```\n$c[$1]\n```"}ge; |
| 91 | + ') |
| 92 | + jq \ |
| 93 | + --arg channel "$SLACK_CHANNEL_ID" \ |
| 94 | + --arg kind "$KIND" \ |
| 95 | + --arg body "$BODY" \ |
| 96 | + ' |
| 97 | + def escape: gsub("&";"&") | gsub("<";"<") | gsub(">";">"); |
| 98 | +
|
| 99 | + (.issue // .pull_request) as $i |
| 100 | + | ([$i.labels[]?.name | select(. != "devtools-gardener")] |
| 101 | + | map("`\(.)`") | join(" ")) as $labels |
| 102 | + | (if $kind == "PR" |
| 103 | + then " · \($i.changed_files) files, +\($i.additions)/-\($i.deletions)" |
| 104 | + + (if $i.draft then " · draft" else "" end) |
| 105 | + else "" end) as $meta |
| 106 | + | [ "*<\($i.html_url)|\($kind) #\($i.number)>* — \(($i.title | escape))", |
| 107 | + "_opened by \($i.user.login)\($meta)_" ] |
| 108 | + + (if $body != "" then [$body] else [] end) |
| 109 | + + (if $labels != "" then [$labels] else [] end) |
| 110 | + | join("\n") as $msg |
| 111 | + | { channel: $channel, text: "\($kind) #\($i.number): \($i.title)", |
| 112 | + blocks: [{ type: "section", text: { type: "mrkdwn", text: $msg } }] } |
| 113 | + ' event.json | curl -sf -X POST \ |
| 114 | + -H "Authorization: Bearer $SLACK_BOT_TOKEN" \ |
| 115 | + -H 'Content-type: application/json; charset=utf-8' \ |
| 116 | + -d @- https://slack.com/api/chat.postMessage |
0 commit comments