@@ -2,7 +2,11 @@ name: Notify Google Chat on Dependabot Events
22
33on :
44 pull_request_target :
5- types : [opened, closed]
5+ types : [opened]
6+ pull_request :
7+ types : [closed]
8+ push :
9+ branches : [main]
610
711permissions :
812 contents : read
@@ -12,113 +16,208 @@ jobs:
1216 notify :
1317 name : Post Dependabot event to Google Chat
1418 runs-on : ubuntu-latest
15- if : github.actor == 'dependabot[bot]'
19+ # Trigger conditions:
20+ # - opened/closed events: only when the PR was authored by dependabot[bot]
21+ # - push events: only when the head commit looks like a Dependabot squash
22+ # (commit message starts with chore(deps- which is our configured prefix)
23+ if : |
24+ (github.event_name != 'push' && github.event.pull_request.user.login == 'dependabot[bot]') ||
25+ (github.event_name == 'push' && startsWith(github.event.head_commit.message, 'chore(deps-'))
1626 steps :
17- - name : Fetch Dependabot metadata
18- id : meta
27+ - name : Resolve PR for push event
28+ id : pr_lookup
29+ if : github.event_name == 'push'
30+ env :
31+ GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
32+ COMMIT_SHA : ${{ github.event.head_commit.id }}
33+ run : |
34+ # Find the PR that produced this squash commit on main.
35+ PR_JSON=$(gh api "repos/${{ github.repository }}/commits/${COMMIT_SHA}/pulls" 2>/dev/null | jq '[.[] | select(.user.login == "dependabot[bot]")] | .[0] // empty')
36+ if [[ -z "$PR_JSON" || "$PR_JSON" == "null" ]]; then
37+ echo "No Dependabot PR associated with this commit; skipping."
38+ echo "skip=true" >> "$GITHUB_OUTPUT"
39+ exit 0
40+ fi
41+ echo "skip=false" >> "$GITHUB_OUTPUT"
42+ echo "pr_number=$(echo "$PR_JSON" | jq -r '.number')" >> "$GITHUB_OUTPUT"
43+ echo "pr_url=$(echo "$PR_JSON" | jq -r '.html_url')" >> "$GITHUB_OUTPUT"
44+ echo "pr_title=$(echo "$PR_JSON" | jq -r '.title')" >> "$GITHUB_OUTPUT"
45+ echo "pr_head_ref=$(echo "$PR_JSON" | jq -r '.head.ref')" >> "$GITHUB_OUTPUT"
46+
47+ - name : Stop early if push had no Dependabot PR
48+ if : github.event_name == 'push' && steps.pr_lookup.outputs.skip == 'true'
49+ run : exit 0
50+
51+ - name : Fetch Dependabot metadata (PR events)
52+ id : meta_pr
53+ if : github.event_name != 'push'
54+ uses : dependabot/fetch-metadata@v2
55+ with :
56+ github-token : " ${{ secrets.GITHUB_TOKEN }}"
57+
58+ - name : Fetch Dependabot metadata (push events)
59+ id : meta_push
60+ if : github.event_name == 'push' && steps.pr_lookup.outputs.skip != 'true'
1961 uses : dependabot/fetch-metadata@v2
2062 with :
2163 github-token : " ${{ secrets.GITHUB_TOKEN }}"
64+ pr-number : ${{ steps.pr_lookup.outputs.pr_number }}
2265
23- - name : Classify event
66+ - name : Classify event and update type
2467 id : classify
68+ env :
69+ FM_UPDATE_TYPE : ${{ steps.meta_pr.outputs.update-type || steps.meta_push.outputs.update-type }}
70+ PR_TITLE : ${{ github.event.pull_request.title || steps.pr_lookup.outputs.pr_title }}
71+ ACTION : ${{ github.event.action }}
72+ MERGED : ${{ github.event.pull_request.merged }}
73+ GH_EVENT : ${{ github.event_name }}
2574 run : |
26- EVENT="unknown"
27- if [[ "${{ github.event.action }}" == "opened" ]]; then
28- if [[ "${{ steps.meta.outputs.update-type }}" == "version-update:semver-major" ]]; then
29- EVENT="opened_major"
75+ UPDATE_TYPE="${FM_UPDATE_TYPE}"
76+ if [[ -z "$UPDATE_TYPE" || "$UPDATE_TYPE" == "null" ]]; then
77+ if [[ "$PR_TITLE" =~ ([0-9]+)\.([0-9]+)\.([0-9]+).*to.*([0-9]+)\.([0-9]+)\.([0-9]+) ]]; then
78+ OM="${BASH_REMATCH[1]}"; Om="${BASH_REMATCH[2]}"
79+ NM="${BASH_REMATCH[4]}"; Nm="${BASH_REMATCH[5]}"
80+ if [[ "$OM" != "$NM" ]]; then UPDATE_TYPE="version-update:semver-major"
81+ elif [[ "$Om" != "$Nm" ]]; then UPDATE_TYPE="version-update:semver-minor"
82+ else UPDATE_TYPE="version-update:semver-patch"; fi
3083 else
31- EVENT="opened_minor_patch "
84+ UPDATE_TYPE="unknown "
3285 fi
33- elif [[ "${{ github.event.action }}" == "closed" && "${{ github.event.pull_request.merged }}" == "true" ]]; then
86+ fi
87+
88+ EVENT="skip"
89+ if [[ "$GH_EVENT" == "pull_request_target" && "$ACTION" == "opened" ]]; then
90+ if [[ "$UPDATE_TYPE" == "version-update:semver-major" ]]; then EVENT="opened_major"
91+ else EVENT="opened_minor_patch"; fi
92+ elif [[ "$GH_EVENT" == "pull_request" && "$ACTION" == "closed" && "$MERGED" == "true" ]]; then
93+ EVENT="merged"
94+ elif [[ "$GH_EVENT" == "push" ]]; then
3495 EVENT="merged"
35- else
36- echo "Not a tracked event. Skipping notification."
37- echo "event=skip" >> "$GITHUB_OUTPUT"
38- exit 0
3996 fi
97+
98+ case "$UPDATE_TYPE" in
99+ version-update:semver-patch) UPDATE_LABEL="patch update";;
100+ version-update:semver-minor) UPDATE_LABEL="minor update";;
101+ version-update:semver-major) UPDATE_LABEL="major update (may contain breaking changes)";;
102+ *) UPDATE_LABEL="update";;
103+ esac
104+
40105 echo "event=$EVENT" >> "$GITHUB_OUTPUT"
106+ echo "update-type=$UPDATE_TYPE" >> "$GITHUB_OUTPUT"
107+ echo "update-label=$UPDATE_LABEL" >> "$GITHUB_OUTPUT"
41108
42- - name : Send Google Chat card
109+ - name : Send Google Chat card (threaded per repo)
43110 if : steps.classify.outputs.event != 'skip'
44111 env :
45112 WEBHOOK_URL : ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }}
46113 EVENT : ${{ steps.classify.outputs.event }}
47- UPDATE_TYPE : ${{ steps.meta .outputs.update-type }}
48- DEP_NAMES : ${{ steps.meta .outputs.dependency-names }}
49- ECOSYSTEM : ${{ steps.meta .outputs.package-ecosystem }}
50- DIRECTORY : ${{ steps.meta .outputs.directory }}
51- PREVIOUS_VERSION : ${{ steps.meta .outputs.previous-version }}
52- NEW_VERSION : ${{ steps.meta .outputs.new-version }}
53- CVSS : ${{ steps.meta .outputs.cvss || '0' }}
54- DEP_GROUP : ${{ steps.meta .outputs.dependency-group }}
55- PR_URL : ${{ github.event.pull_request.html_url }}
56- PR_NUMBER : ${{ github.event.pull_request.number }}
57- PR_TITLE : ${{ github.event.pull_request.title }}
114+ UPDATE_LABEL : ${{ steps.classify .outputs.update-label }}
115+ DEP_NAMES : ${{ steps.meta_pr.outputs.dependency-names || steps.meta_push .outputs.dependency-names }}
116+ ECOSYSTEM : ${{ steps.meta_pr.outputs.package-ecosystem || steps.meta_push .outputs.package-ecosystem }}
117+ DIRECTORY : ${{ steps.meta_pr.outputs.directory || steps.meta_push .outputs.directory }}
118+ PREVIOUS_VERSION : ${{ steps.meta_pr.outputs.previous-version || steps.meta_push .outputs.previous-version }}
119+ NEW_VERSION : ${{ steps.meta_pr.outputs.new-version || steps.meta_push .outputs.new-version }}
120+ DEP_GROUP : ${{ steps.meta_pr .outputs.dependency-group || steps.meta_push.outputs.dependency-group }}
121+ CVSS : ${{ steps.meta_pr .outputs.cvss || steps.meta_push.outputs.cvss || '0' }}
122+ PR_URL : ${{ github.event.pull_request.html_url || steps.pr_lookup.outputs.pr_url }}
123+ PR_NUMBER : ${{ github.event.pull_request.number || steps.pr_lookup.outputs.pr_number }}
124+ PR_TITLE : ${{ github.event.pull_request.title || steps.pr_lookup.outputs.pr_title }}
58125 REPO : ${{ github.repository }}
59- MERGED_BY : ${{ github.event.pull_request.merged_by.login }}
126+ MERGED_BY : ${{ github.event.pull_request.merged_by.login || github.event.head_commit.author.username || 'app/github-actions' }}
60127 run : |
128+ case "$ECOSYSTEM" in
129+ npm_and_yarn) ECO_LABEL="npm";;
130+ pip) ECO_LABEL="pip";;
131+ github_actions) ECO_LABEL="GitHub Actions";;
132+ docker) ECO_LABEL="Docker";;
133+ *) ECO_LABEL="$ECOSYSTEM";;
134+ esac
135+ ECO_DISPLAY="$ECO_LABEL"
136+ if [[ -n "$DIRECTORY" && "$DIRECTORY" != "/" ]]; then
137+ ECO_DISPLAY="$ECO_LABEL ($DIRECTORY)"
138+ fi
139+ if [[ -n "$PREVIOUS_VERSION" && -n "$NEW_VERSION" ]]; then
140+ VERSION_TEXT="$PREVIOUS_VERSION -> $NEW_VERSION"
141+ else
142+ VERSION_TEXT="see PR for versions"
143+ fi
144+ if [[ -n "$DEP_GROUP" ]]; then
145+ PKG_COUNT=$(echo "$DEP_NAMES" | tr ',' '\n' | wc -l | tr -d ' ')
146+ PKG_SUMMARY="$DEP_GROUP group ($PKG_COUNT packages)"
147+ else
148+ PKG_SUMMARY="$DEP_NAMES"
149+ fi
61150 case "$EVENT" in
62151 opened_minor_patch)
63- TITLE="Dependabot PR opened — auto-merge enabled"
64- SUBTITLE="${REPO} · #${PR_NUMBER}"
65- STATUS_TEXT="Auto-merge enabled. Will merge once CI passes."
66- STATUS_LABEL="Status"
67- ACTION_BUTTON_TEXT="View PR"
152+ HEADER_TITLE="Safe update - auto-merging after CI"
153+ STATUS_LABEL="Action Required"
154+ STATUS_TEXT="No action needed. Auto-merge enabled; will merge once CI passes."
155+ BUTTON_TEXT="View PR"
68156 ;;
69157 opened_major)
70- TITLE="MAJOR version bump — human review required"
71- SUBTITLE="${REPO} · #${PR_NUMBER}"
72- STATUS_TEXT="This is a MAJOR version upgrade and may contain breaking changes. Please review and merge manually if safe."
158+ HEADER_TITLE="MAJOR version bump - human review required"
73159 STATUS_LABEL="Action Required"
74- ACTION_BUTTON_TEXT="Review PR"
160+ STATUS_TEXT="This is a MAJOR version upgrade and may contain breaking changes. Please review and merge manually if safe."
161+ BUTTON_TEXT="Review PR"
75162 ;;
76163 merged)
77- TITLE="Dependabot PR merged"
78- SUBTITLE="${REPO} · #${PR_NUMBER}"
79- STATUS_TEXT="Merged into default branch${MERGED_BY:+ by ${MERGED_BY}}."
80- STATUS_LABEL="Status"
81- ACTION_BUTTON_TEXT="View PR"
164+ HEADER_TITLE="Merged into main"
165+ STATUS_LABEL="Merged By"
166+ if [[ -n "$MERGED_BY" && "$MERGED_BY" != "null" ]]; then
167+ STATUS_TEXT="$MERGED_BY"
168+ else
169+ STATUS_TEXT="dependabot[bot] (auto-merge)"
170+ fi
171+ BUTTON_TEXT="View PR"
82172 ;;
83173 esac
174+ SUBTITLE="$REPO - PR #$PR_NUMBER"
175+ CARD_ID="dependabot-$EVENT-$(date +%s)"
176+ THREAD_KEY=$(echo "$REPO" | tr '[:upper:]' '[:lower:]' | tr '/' '-')
177+ THREADED_URL="${WEBHOOK_URL}&threadKey=${THREAD_KEY}&messageReplyOption=REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD"
84178
85- VERSION_TEXT="${PREVIOUS_VERSION:-?} → ${NEW_VERSION:-?}"
86- [[ "$VERSION_TEXT" == "? → ?" ]] && VERSION_TEXT="see PR for versions"
87-
88- GROUP_LINE=""
89- if [[ -n "$DEP_GROUP" ]]; then
90- GROUP_LINE=",{\"decoratedText\": {\"topLabel\": \"Group\", \"text\": \"${DEP_GROUP}\"}}"
91- fi
179+ jq -n \
180+ --arg cardId "$CARD_ID" \
181+ --arg title "$HEADER_TITLE" \
182+ --arg subtitle "$SUBTITLE" \
183+ --arg statusLabel "$STATUS_LABEL" \
184+ --arg statusText "$STATUS_TEXT" \
185+ --arg prTitle "$PR_TITLE" \
186+ --arg pkgSummary "$PKG_SUMMARY" \
187+ --arg versionText "$VERSION_TEXT" \
188+ --arg updateLabel "$UPDATE_LABEL" \
189+ --arg ecoDisplay "$ECO_DISPLAY" \
190+ --arg buttonText "$BUTTON_TEXT" \
191+ --arg prUrl "$PR_URL" \
192+ --arg cvss "$CVSS" \
193+ '{
194+ cardsV2: [{
195+ cardId: $cardId,
196+ card: {
197+ header: {
198+ title: $title,
199+ subtitle: $subtitle,
200+ imageUrl: "https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png",
201+ imageType: "CIRCLE"
202+ },
203+ sections: [{
204+ widgets: (
205+ [
206+ {decoratedText: {topLabel: $statusLabel, text: $statusText, wrapText: true}},
207+ {decoratedText: {topLabel: "PR Title", text: $prTitle, wrapText: true}},
208+ {decoratedText: {topLabel: "Package(s)", text: $pkgSummary, wrapText: true}},
209+ {decoratedText: {topLabel: "Version", text: $versionText}},
210+ {decoratedText: {topLabel: "Update Type", text: $updateLabel}},
211+ {decoratedText: {topLabel: "Ecosystem", text: $ecoDisplay}}
212+ ]
213+ + (if ($cvss | IN("0","0.0","")) then [] else [{decoratedText: {topLabel: "CVSS Score (Common Vulnerability Scoring System, 0 to 10 scale)", text: $cvss}}] end)
214+ + [{buttonList: {buttons: [{text: $buttonText, onClick: {openLink: {url: $prUrl}}}]}}]
215+ )
216+ }]
217+ }
218+ }]
219+ }' > payload.json
92220
93- cat > payload.json <<EOF
94- {
95- "cardsV2": [{
96- "cardId": "dependabot-${EVENT}-$(date +%s)",
97- "card": {
98- "header": {
99- "title": "${TITLE}",
100- "subtitle": "${SUBTITLE}",
101- "imageUrl": "https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png",
102- "imageType": "CIRCLE"
103- },
104- "sections": [{
105- "widgets": [
106- {"decoratedText": {"topLabel": "${STATUS_LABEL}", "text": "${STATUS_TEXT}", "wrapText": true}},
107- {"decoratedText": {"topLabel": "PR Title", "text": "${PR_TITLE}", "wrapText": true}},
108- {"decoratedText": {"topLabel": "Package(s)", "text": "${DEP_NAMES}", "wrapText": true}},
109- {"decoratedText": {"topLabel": "Version", "text": "${VERSION_TEXT}"}},
110- {"decoratedText": {"topLabel": "Update Type", "text": "${UPDATE_TYPE}"}},
111- {"decoratedText": {"topLabel": "Ecosystem", "text": "${ECOSYSTEM} (${DIRECTORY})"}}${GROUP_LINE},
112- {"decoratedText": {"topLabel": "CVSS Score", "text": "${CVSS}"}},
113- {"buttonList": {"buttons": [
114- {"text": "${ACTION_BUTTON_TEXT}", "onClick": {"openLink": {"url": "${PR_URL}"}}}
115- ]}}
116- ]
117- }]
118- }
119- }]
120- }
121- EOF
122- curl -sS -X POST "$WEBHOOK_URL" \
221+ curl -sS -X POST "$THREADED_URL" \
123222 -H "Content-Type: application/json; charset=UTF-8" \
124223 -d @payload.json
0 commit comments