Skip to content

[FEAT] Suspense/ErrorBoundary 공통 래퍼(AsyncBoundary) 추가 #950

[FEAT] Suspense/ErrorBoundary 공통 래퍼(AsyncBoundary) 추가

[FEAT] Suspense/ErrorBoundary 공통 래퍼(AsyncBoundary) 추가 #950

name: 🎵 Automated Discord Notification
on:
pull_request:
types: [opened, reopened, closed, review_requested, ready_for_review]
pull_request_review:
types: [submitted]
pull_request_review_comment:
types: [created]
issues:
types: [opened]
jobs:
notify:
permissions: {}
runs-on: ubuntu-latest
steps:
- name: Send Detailed Discord Message
shell: bash
env:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
EVENT_NAME: ${{ github.event_name }}
ACTION: ${{ github.event.action }}
run: |
set -euo pipefail
if [ -z "$DISCORD_WEBHOOK_URL" ]; then
echo "DISCORD_WEBHOOK_URL not set, skipping"
exit 0
fi
# GitHub ID → 한국어 이름 매핑
resolve_name() {
case "$1" in
kimminna) echo "민아" ;;
ehye1) echo "혜원" ;;
jjangminii) echo "정민" ;;
yumin-kim2) echo "유민" ;;
*) echo "$1" ;;
esac
}
# Discord embed 색상
COLOR_SUCCESS=5763719 # 머지, 승인
COLOR_OPEN=16711782 # PR 열림
COLOR_REVIEW=16776960 # 리뷰 요청, 리뷰 코멘트
COLOR_CLOSED=15548997 # PR 닫힘, 변경 요청
COLOR_COMMENT=9807270 # PR 코멘트
COLOR_ISSUE=7275516 # 이슈
# PR/이슈 본문에서 보일러플레이트 제거 후 반환 (DESC에서 400자로 제한)
clean_body() {
echo "$1" \
| sed 's/<br>//gi' \
| { grep -vE '^\s*(#|close[sd]? #[0-9]+|fixes? #[0-9]+|resolves? #[0-9]+)' || true; } \
| tr '\n' ' ' \
| sed 's/ */ /g'
}
# 라벨·리뷰어를 받아 fields JSON 배열 반환
build_fields() {
local labels="$1"
local reviewer="$2"
local fields='[]'
if [ -n "$labels" ]; then
fields=$(echo "$fields" | jq --arg v "$labels" '. + [{ name: "라벨", value: $v, inline: true }]')
fi
if [ -n "$reviewer" ]; then
fields=$(echo "$fields" | jq --arg v "$reviewer" '. + [{ name: "리뷰어", value: $v, inline: true }]')
fi
echo "$fields"
}
if [ "$EVENT_NAME" = "pull_request" ]; then
PR_TITLE="$(jq -r '.pull_request.title // ""' "$GITHUB_EVENT_PATH")"
URL="$(jq -r '.pull_request.html_url // ""' "$GITHUB_EVENT_PATH")"
RAW_AUTHOR="$(jq -r '.pull_request.user.login // ""' "$GITHUB_EVENT_PATH")"
AUTHOR="$(resolve_name "$RAW_AUTHOR")"
LABELS="$(jq -r '[.pull_request.labels[].name] | join(", ")' "$GITHUB_EVENT_PATH")"
MERGED="$(jq -r '.pull_request.merged // false' "$GITHUB_EVENT_PATH")"
REVIEWER=""
RAW_BODY="$(jq -r '.pull_request.body // ""' "$GITHUB_EVENT_PATH")"
CLEAN_BODY="$(clean_body "$RAW_BODY")"
if [ "$ACTION" = "opened" ]; then
TITLE="PR이 열렸습니다"
COLOR=$COLOR_OPEN
elif [ "$ACTION" = "reopened" ]; then
TITLE="PR이 다시 열렸습니다"
COLOR=$COLOR_OPEN
elif [ "$ACTION" = "ready_for_review" ]; then
TITLE="PR이 리뷰 준비됐습니다"
COLOR=$COLOR_OPEN
elif [ "$ACTION" = "review_requested" ]; then
TITLE="리뷰 요청이 왔습니다"
RAW_REVIEWER="$(jq -r '.requested_reviewer.login // ""' "$GITHUB_EVENT_PATH")"
if [ -z "$RAW_REVIEWER" ]; then
RAW_REVIEWER="$(jq -r '.requested_team.name // .requested_team.slug // ""' "$GITHUB_EVENT_PATH")"
REVIEWER="$RAW_REVIEWER"
else
REVIEWER="$(resolve_name "$RAW_REVIEWER")"
fi
COLOR=$COLOR_REVIEW
elif [ "$ACTION" = "closed" ] && [ "$MERGED" = "true" ]; then
TITLE="PR이 머지됐습니다"
COLOR=$COLOR_SUCCESS
elif [ "$ACTION" = "closed" ]; then
TITLE="PR이 닫혔습니다"
COLOR=$COLOR_CLOSED
else
exit 0
fi
DESC="**${PR_TITLE}**"$'\n'"${CLEAN_BODY}"
DESC="${DESC:0:400}"
FIELDS="$(build_fields "$LABELS" "$REVIEWER")"
elif [ "$EVENT_NAME" = "pull_request_review" ]; then
RAW_AUTHOR="$(jq -r '.review.user.login // ""' "$GITHUB_EVENT_PATH")"
if [ "$RAW_AUTHOR" = "coderabbitai" ] || [ "$RAW_AUTHOR" = "coderabbitai[bot]" ]; then
exit 0
fi
AUTHOR="$(resolve_name "$RAW_AUTHOR")"
PR_TITLE="$(jq -r '.pull_request.title // ""' "$GITHUB_EVENT_PATH")"
URL="$(jq -r '.pull_request.html_url // ""' "$GITHUB_EVENT_PATH")"
REVIEW_STATE="$(jq -r '.review.state // ""' "$GITHUB_EVENT_PATH")"
LABELS="$(jq -r '[.pull_request.labels[].name] | join(", ")' "$GITHUB_EVENT_PATH")"
if [ "$REVIEW_STATE" = "approved" ]; then
TITLE="리뷰가 승인됐습니다"
COLOR=$COLOR_SUCCESS
elif [ "$REVIEW_STATE" = "changes_requested" ]; then
TITLE="변경 요청이 왔습니다"
COLOR=$COLOR_CLOSED
else
TITLE="리뷰 코멘트가 달렸습니다"
COLOR=$COLOR_REVIEW
fi
RAW_BODY="$(jq -r '.review.body // ""' "$GITHUB_EVENT_PATH")"
CLEAN_BODY="$(clean_body "$RAW_BODY")"
DESC="**${PR_TITLE}**"$'\n'"${CLEAN_BODY}"
DESC="${DESC:0:400}"
FIELDS="$(build_fields "$LABELS" "")"
elif [ "$EVENT_NAME" = "pull_request_review_comment" ]; then
RAW_AUTHOR="$(jq -r '.comment.user.login // ""' "$GITHUB_EVENT_PATH")"
if [ "$RAW_AUTHOR" = "coderabbitai" ] || [ "$RAW_AUTHOR" = "coderabbitai[bot]" ]; then
exit 0
fi
AUTHOR="$(resolve_name "$RAW_AUTHOR")"
PR_TITLE="$(jq -r '.pull_request.title // ""' "$GITHUB_EVENT_PATH")"
TITLE="PR 코멘트가 달렸습니다"
URL="$(jq -r '.comment.html_url // ""' "$GITHUB_EVENT_PATH")"
RAW_BODY="$(jq -r '.comment.body // ""' "$GITHUB_EVENT_PATH")"
CLEAN_BODY="$(clean_body "$RAW_BODY")"
DESC="**${PR_TITLE}**"$'\n'"${CLEAN_BODY}"
DESC="${DESC:0:400}"
COLOR=$COLOR_COMMENT
FIELDS='[]'
else
RAW_AUTHOR="$(jq -r '.issue.user.login // ""' "$GITHUB_EVENT_PATH")"
AUTHOR="$(resolve_name "$RAW_AUTHOR")"
ISSUE_TITLE="$(jq -r '.issue.title // ""' "$GITHUB_EVENT_PATH")"
TITLE="이슈가 열렸습니다"
URL="$(jq -r '.issue.html_url // ""' "$GITHUB_EVENT_PATH")"
RAW_BODY="$(jq -r '.issue.body // ""' "$GITHUB_EVENT_PATH")"
CLEAN_BODY="$(clean_body "$RAW_BODY")"
DESC="**${ISSUE_TITLE}**"$'\n'"${CLEAN_BODY}"
DESC="${DESC:0:400}"
COLOR=$COLOR_ISSUE
LABELS="$(jq -r '[.issue.labels[].name] | join(", ")' "$GITHUB_EVENT_PATH")"
FIELDS="$(build_fields "$LABELS" "")"
fi
jq -n \
--arg title "$TITLE" \
--arg url "$URL" \
--arg desc "$DESC" \
--arg author "$AUTHOR" \
--arg avatar_user "$RAW_AUTHOR" \
--arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--argjson color "$COLOR" \
--argjson fields "$FIELDS" \
'{
embeds: [
{
title: $title,
url: $url,
description: $desc,
color: $color,
author: {
name: $author,
icon_url: ("https://github.com/" + $avatar_user + ".png")
},
fields: $fields,
footer: {
text: "GitHub Actions"
},
timestamp: $timestamp
}
]
}' > payload.json
curl \
--fail-with-body \
--silent --show-error \
--connect-timeout 5 \
--max-time 20 \
--retry 3 \
--retry-all-errors \
-H "Content-Type: application/json" \
-X POST \
-d @payload.json \
"$DISCORD_WEBHOOK_URL"