Skip to content

(build): front back #169

(build): front back

(build): front back #169

name: 'Discord: repo activity'
on:
push:
branches: ['**']
jobs:
notify:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
# ----------------------------------------------------------------------
# Build the per-commit lines.
#
# Wire format per line (one commit):
# N. **`type(scope):`** subject · [`abc1234`](url) · author
#
# Notes for future maintainers (and for whoever reads this from the
# Discord side):
# - We render the SUBJECT only. Bodies are intentionally omitted —
# they fragment readability in chat and the hash link gives the
# full git show one click away.
# - The previous version stripped (), [], and ` from the message to
# "protect" the [hash](url) link that follows. That was a false
# positive: Discord's link parser only triggers on `]` IMMEDIATELY
# followed by `(`, and our hash link sits behind a ` · ` separator
# so subject-side punctuation is safe. Stripping parens turned
# `fix(editor):` into `fixeditor:` and is the bug we are fixing.
# - Conventional Commit prefix (type, optional scope, optional !)
# is detected with a single capture and rendered as bold inline
# code so it pops without breaking non-conventional commits.
# - Control characters (\u0000-\u001f) are still stripped because
# they break Discord rendering and have no legitimate place in a
# commit subject.
# - GitHub push webhooks include at most 20 commits in
# github.event.commits. For larger pushes we load the full range
# via the compare API (repos/.../compare/before...after).
# ----------------------------------------------------------------------
- name: Build commit summary (for push)
id: build
env:
EVENT_JSON: ${{ toJSON(github.event) }}
GH_TOKEN: ${{ github.token }}
run: |
REF=$(jq -r '.ref' <<< "$EVENT_JSON" | sed 's|refs/heads/||')
COMPARE=$(jq -r '.compare' <<< "$EVENT_JSON")
BEFORE="${{ github.event.before }}"
AFTER="${{ github.event.after }}"
ZERO_SHA="0000000000000000000000000000000000000000"
REPO="${{ github.repository }}"
normalize_webhook() {
jq '{
commits: [
.commits[]? | {
id: .id,
message: .message,
author: {name: (.author.name // "unknown")}
}
]
}' <<< "$EVENT_JSON"
}
if [ "$BEFORE" = "$ZERO_SHA" ] || [ -z "$BEFORE" ]; then
COMMITS_JSON=$(normalize_webhook)
else
API_PAGES=$(gh api "repos/${REPO}/compare/${BEFORE}...${AFTER}" --paginate 2>/dev/null) || API_PAGES=""
if [ -n "$API_PAGES" ]; then
COMMITS_JSON=$(jq -s '
[.[].commits[]?]
| unique_by(.sha)
| {commits: map({
id: .sha,
message: .commit.message,
author: {name: (.commit.author.name // "unknown")}
})}
' <<< "$API_PAGES")
if [ "$(jq -r '.commits | length' <<< "$COMMITS_JSON")" -eq 0 ]; then
COMMITS_JSON=$(normalize_webhook)
fi
else
echo "::warning::compare API failed; falling back to webhook commits (max 20)"
COMMITS_JSON=$(normalize_webhook)
fi
fi
COUNT=$(jq -r '.commits | length' <<< "$COMMITS_JSON")
# Skip the synthetic "build front back" deploy-trigger commit so
# the channel does not get a one-line notification with no signal.
SKIP_NOTIFICATION="false"
if [ "$COUNT" -eq 1 ]; then
FIRST_MSG=$(jq -r '.commits[0].message // "" | split("\n")[0]' <<< "$COMMITS_JSON")
if [ "$FIRST_MSG" = "build front back" ] || [ "$FIRST_MSG" = "(build): front back" ]; then
SKIP_NOTIFICATION="true"
echo "⏭️ Skipping Discord notification (build trigger commit)"
fi
fi
REPO_URL="https://github.com/${REPO}"
jq -r --arg repo "$REPO_URL" '
.commits | to_entries[] |
((1 + .key) | tostring) as $n |
(.value.id[0:7]) as $sha |
($repo + "/commit/" + .value.id) as $url |
(.value.message
| split("\n")[0]
| gsub("[\u0000-\u001f]"; " ")
| gsub("\\s+"; " ")
| gsub("^\\s+|\\s+$"; "")
) as $subject |
([$subject
| capture("^(?<prefix>[a-zA-Z]+(?:\\([^)]+\\))?!?:)\\s*(?<rest>.*)$")
] | .[0]) as $cc |
(if $cc != null then
"**`" + $cc.prefix + "`** " + ($cc.rest | .[0:180])
else
($subject | .[0:200])
end) as $title |
$n + ". " + $title + " · [`" + $sha + "`](" + $url + ") · " + .value.author.name
' <<< "$COMMITS_JSON" > /tmp/commits.txt
{
echo "COUNT=$COUNT"
echo "REF=$REF"
echo "COMPARE=$COMPARE"
echo "SKIP_NOTIFICATION=$SKIP_NOTIFICATION"
} >> "$GITHUB_ENV"
# ----------------------------------------------------------------------
# Post to Discord.
#
# Splitting:
# Discord caps content at 2000 chars. We pack as many commit lines as
# fit under MAX_LEN (1900, leaving ~100 char buffer for trailing JSON
# and a bit of slack), then flush. The HEADER is only included in the
# first chunk so subsequent chunks read as continuations.
#
# flags: 4 == SUPPRESS_EMBEDS — keeps the message compact (no GitHub
# link previews stacking under every commit).
# ----------------------------------------------------------------------
- name: Post to Discord (smart split)
if: env.SKIP_NOTIFICATION != 'true'
run: |
HEADER="🚀 **Push** to \`${{ env.REF }}\` by \`${{ github.actor }}\`
📦 \`${{ github.repository }}\` · 📝 ${{ env.COUNT }} commits · 🔗 [View Diff](${{ env.COMPARE }})
**Commits**
"
MAX_LEN=1900
CURRENT_CHUNK=""
CHUNK_NUM=1
send_chunk() {
local message="$1"
curl -sS -H "Content-Type: application/json" \
-d "{\"content\": $(printf '%s' "$message" | jq -Rs .), \"flags\": 4}" \
"${{ secrets.DISCORD_WEBHOOK }}"
sleep 1
}
while IFS= read -r line || [ -n "$line" ]; do
[ -z "$line" ] && continue
TEST_MSG="${HEADER}${CURRENT_CHUNK}${line}"$'\n'
if [ ${#TEST_MSG} -gt $MAX_LEN ] && [ -n "$CURRENT_CHUNK" ]; then
if [ $CHUNK_NUM -eq 1 ]; then
send_chunk "${HEADER}${CURRENT_CHUNK}"
else
send_chunk $'*(continued)*\n'"${CURRENT_CHUNK}"
fi
CHUNK_NUM=$((CHUNK_NUM + 1))
CURRENT_CHUNK="${line}"$'\n'
else
CURRENT_CHUNK="${CURRENT_CHUNK}${line}"$'\n'
fi
done < /tmp/commits.txt
if [ -n "$CURRENT_CHUNK" ]; then
if [ $CHUNK_NUM -eq 1 ]; then
send_chunk "${HEADER}${CURRENT_CHUNK}"
else
send_chunk $'*(continued)*\n'"${CURRENT_CHUNK}"
fi
fi