Skip to content

Commit ba8566e

Browse files
committed
feat: add probot app and notification workflows
1 parent ef5f141 commit ba8566e

4 files changed

Lines changed: 396 additions & 0 deletions

File tree

.github/mergeable.yml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
version: 2
2+
mergeable:
3+
- when: pull_request.*, pull_request_review.*
4+
validate:
5+
# Validate PR title
6+
- do: title
7+
must_include:
8+
regex: '^(feat|docs|chore|fix|refactor|test|style|perf)(\(\w+\))?: .{5,}'
9+
message: "Semantic release conventions must be followed. Example: feat(auth): add login page. Title must be at least 5 characters after the prefix."
10+
11+
# Ensure PR description is provided
12+
- do: description
13+
must_include:
14+
regex: "[\\s\\S]{20,}" # At least 20 characters
15+
message: "Please provide a meaningful description of the PR (minimum 20 characters)."
16+
17+
# Ensure PR references an associated issue
18+
- do: description
19+
must_include:
20+
regex: "(Closes|Fixes|Resolves|Addresses)\\s+#[0-9]+(,?\\s*#[0-9]+)*"
21+
message: "PR must reference at least one issue (e.g., Closes #123, Fixes #123, #124)."
22+
23+
# Ensure at least one required label is applied
24+
- do: label
25+
must_include:
26+
regex: "^(bug|enhancement|documentation|feature|refactor|performance|chore|wip|test|ci|security|dependencies)$"
27+
message: "PR must include at least one valid label."
28+
# Ensure PR has at least one assignee
29+
30+
- do: assignee
31+
min:
32+
count: 1
33+
message: "PR must have at least one assignee."
34+
# Ensure PR has at least one reviewer requested
35+
- do: approvals
36+
min:
37+
count: 1
38+
message: "PR must have at least one reviewer requested or approved."
39+
40+
pass:
41+
- do: labels
42+
add:
43+
- "validated"
44+
- do: checks
45+
status: "success"
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
name: Notify (GitHub Events)
2+
3+
permissions:
4+
issues: write
5+
pull-requests: write
6+
contents: read
7+
8+
on:
9+
issues:
10+
types: [opened, edited, reopened, closed, assigned, labeled]
11+
pull_request:
12+
types: [opened, closed, reopened, ready_for_review, review_requested]
13+
pull_request_review:
14+
types: [submitted, edited, dismissed]
15+
issue_comment:
16+
types: [created, edited]
17+
pull_request_review_comment:
18+
types: [created, edited]
19+
20+
jobs:
21+
notify:
22+
runs-on: ubuntu-latest
23+
name: Send Notifications
24+
25+
steps:
26+
- name: Determine Event Type
27+
id: event_info
28+
run: |
29+
EVENT="${{ github.event_name }}"
30+
ACTION="${{ github.event.action }}"
31+
REPO="${{ github.repository }}"
32+
ACTOR="${{ github.actor }}"
33+
REPO_URL="${{ github.server_url }}/${{ github.repository }}"
34+
35+
if [[ "$EVENT" == "issues" ]]; then
36+
TITLE="Issue $ACTION: ${{ github.event.issue.title }}"
37+
URL="${{ github.event.issue.html_url }}"
38+
NUMBER="${{ github.event.issue.number }}"
39+
USER="${{ github.event.issue.user.login }}"
40+
DESCRIPTION="Issue #$NUMBER by $USER"
41+
STATE="${{ github.event.issue.state }}"
42+
EMOJI="🐛"
43+
44+
elif [[ "$EVENT" == "pull_request" ]]; then
45+
NUMBER="${{ github.event.pull_request.number }}"
46+
USER="${{ github.event.pull_request.user.login }}"
47+
URL="${{ github.event.pull_request.html_url }}"
48+
STATE="${{ github.event.pull_request.state }}"
49+
SOURCE="${{ github.event.pull_request.head.ref }}"
50+
TARGET="${{ github.event.pull_request.base.ref }}"
51+
52+
if [[ "$ACTION" == "closed" && "${{ github.event.pull_request.merged }}" == "true" ]]; then
53+
TITLE="PR merged: ${{ github.event.pull_request.title }}"
54+
EMOJI="✅"
55+
STATE="merged"
56+
elif [[ "$ACTION" == "closed" ]]; then
57+
TITLE="PR closed: ${{ github.event.pull_request.title }}"
58+
EMOJI="❌"
59+
else
60+
TITLE="PR $ACTION: ${{ github.event.pull_request.title }}"
61+
EMOJI="🔀"
62+
fi
63+
DESCRIPTION="PR #$NUMBER by $USER: $SOURCE → $TARGET"
64+
65+
elif [[ "$EVENT" == "pull_request_review" ]]; then
66+
TITLE="PR Review: ${{ github.event.pull_request.title }}"
67+
URL="${{ github.event.review.html_url }}"
68+
NUMBER="${{ github.event.pull_request.number }}"
69+
USER="${{ github.event.review.user.login }}"
70+
REVIEW_STATE="${{ github.event.review.state }}"
71+
STATE="${{ github.event.review.state }}"
72+
SOURCE="${{ github.event.pull_request.head.ref }}"
73+
TARGET="${{ github.event.pull_request.base.ref }}"
74+
DESCRIPTION="Review by $USER on PR #$NUMBER"
75+
EMOJI="👀"
76+
77+
elif [[ "$EVENT" == "issue_comment" ]]; then
78+
NUMBER="${{ github.event.issue.number }}"
79+
USER="${{ github.event.comment.user.login }}"
80+
URL="${{ github.event.comment.html_url }}"
81+
STATE="commented"
82+
83+
if [[ "${{ github.event.issue.pull_request }}" != "" ]]; then
84+
TITLE="Comment on PR: ${{ github.event.issue.title }}"
85+
EMOJI="💬"
86+
else
87+
TITLE="Comment on Issue: ${{ github.event.issue.title }}"
88+
EMOJI="💬"
89+
fi
90+
DESCRIPTION="Comment by $USER on #$NUMBER"
91+
92+
elif [[ "$EVENT" == "pull_request_review_comment" ]]; then
93+
TITLE="Comment on PR: ${{ github.event.pull_request.title }}"
94+
URL="${{ github.event.comment.html_url }}"
95+
NUMBER="${{ github.event.pull_request.number }}"
96+
USER="${{ github.event.comment.user.login }}"
97+
STATE="commented"
98+
SOURCE="${{ github.event.pull_request.head.ref }}"
99+
TARGET="${{ github.event.pull_request.base.ref }}"
100+
DESCRIPTION="Review comment by $USER on PR #$NUMBER"
101+
EMOJI="💬"
102+
else
103+
TITLE="GitHub Event: $EVENT"
104+
URL="${{ github.event.repository.html_url }}"
105+
DESCRIPTION="Event triggered in $REPO"
106+
STATE="N/A"
107+
NUMBER="N/A"
108+
USER="$ACTOR"
109+
EMOJI="📢"
110+
fi
111+
112+
echo "title=$TITLE" >> $GITHUB_OUTPUT
113+
echo "url=$URL" >> $GITHUB_OUTPUT
114+
echo "description=$DESCRIPTION" >> $GITHUB_OUTPUT
115+
echo "emoji=$EMOJI" >> $GITHUB_OUTPUT
116+
echo "number=${NUMBER:-N/A}" >> $GITHUB_OUTPUT
117+
echo "user=${USER:-$ACTOR}" >> $GITHUB_OUTPUT
118+
echo "state=${STATE:-N/A}" >> $GITHUB_OUTPUT
119+
echo "source=${SOURCE:-N/A}" >> $GITHUB_OUTPUT
120+
echo "target=${TARGET:-N/A}" >> $GITHUB_OUTPUT
121+
echo "repo_url=$REPO_URL" >> $GITHUB_OUTPUT
122+
123+
- name: Google Chat Notification
124+
if: always()
125+
uses: Co-qn/google-chat-notification@releases/v1
126+
with:
127+
name: ${{ steps.event_info.outputs.title }}
128+
url: ${{ secrets.GOOGLE_CHAT_WEBHOOK }}
129+
status: ${{ job.status }}
130+
131+
- name: Slack Notification
132+
if: always()
133+
uses: slackapi/slack-github-action@v1.24.0
134+
with:
135+
channel-id: ${{ secrets.SLACK_CHANNEL_ID }}
136+
payload: |
137+
{
138+
"text": "${{ steps.event_info.outputs.title }}",
139+
"blocks": [
140+
{
141+
"type": "header",
142+
"text": {
143+
"type": "plain_text",
144+
"text": "${{ steps.event_info.outputs.emoji }} ${{ steps.event_info.outputs.title }}"
145+
}
146+
},
147+
{
148+
"type": "section",
149+
"fields": [
150+
{ "type": "mrkdwn", "text": "*Repository:*\n<${{ steps.event_info.outputs.repo_url }}|${{ github.repository }}>" },
151+
{ "type": "mrkdwn", "text": "*Event:*\n${{ github.event_name }}" },
152+
{ "type": "mrkdwn", "text": "*Author:*\n${{ steps.event_info.outputs.user }}" },
153+
{ "type": "mrkdwn", "text": "*Action:*\n${{ github.event.action }}" },
154+
{ "type": "mrkdwn", "text": "*Number:*\n<${{ steps.event_info.outputs.url }}|#${{ steps.event_info.outputs.number }}>" },
155+
{ "type": "mrkdwn", "text": "*State:*\n${{ steps.event_info.outputs.state }}" }
156+
]
157+
},
158+
{
159+
"type": "section",
160+
"text": { "type": "mrkdwn", "text": "${{ steps.event_info.outputs.description }}" }
161+
},
162+
{
163+
"type": "actions",
164+
"elements": [
165+
{ "type": "button", "text": { "type": "plain_text", "text": "View on GitHub" }, "url": "${{ steps.event_info.outputs.url }}", "style": "primary" },
166+
{ "type": "button", "text": { "type": "plain_text", "text": "View Repository" }, "url": "${{ steps.event_info.outputs.repo_url }}" }
167+
]
168+
},
169+
{
170+
"type": "context",
171+
"elements": [
172+
{ "type": "mrkdwn", "text": "Triggered by ${{ github.actor }} • ${{ github.event_name }} event" }
173+
]
174+
}
175+
]
176+
}
177+
env:
178+
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}

.github/workflows/pre-commit.yaml

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
name: Pre-Commit Checks
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
- develop
8+
- 'feat/**'
9+
- 'feature/**'
10+
- 'test/**'
11+
- 'chore/**'
12+
- 'fix/**'
13+
- 'hotfix/**'
14+
- 'docs/**'
15+
pull_request:
16+
types: [opened, synchronize, reopened]
17+
18+
jobs:
19+
precommit:
20+
runs-on: ubuntu-latest
21+
22+
steps:
23+
- name: Checkout Repository
24+
uses: actions/checkout@v4
25+
26+
- name: Set up Go
27+
uses: actions/setup-go@v4
28+
with:
29+
go-version: '1.22.0'
30+
31+
- name: Install goimports
32+
run: |
33+
go install golang.org/x/tools/cmd/goimports@latest
34+
echo "$HOME/go/bin" >> $GITHUB_PATH
35+
36+
- name: Set up Python
37+
uses: actions/setup-python@v5
38+
with:
39+
python-version: "3.11"
40+
41+
- name: Install pre-commit
42+
run: |
43+
python -m pip install --upgrade pip
44+
pip install pre-commit
45+
46+
- name: Load Pre-commit Config
47+
run: |
48+
if [ ! -f ".pre-commit-config.yaml" ]; then
49+
echo " No .pre-commit-config.yaml found — downloading BerryBytes global config..."
50+
curl -sSL \
51+
https://raw.githubusercontent.com/BerryBytes/precommit-util/main/global/precommitFile/.pre-commit-config.yaml \
52+
-o .pre-commit-config.yaml
53+
else
54+
echo "✔ Using project's existing .pre-commit-config.yaml"
55+
fi
56+
57+
- name: Inject temporary Stylelint config for CI
58+
run: |
59+
if [ ! -f ".stylelintrc.json" ]; then
60+
echo " Creating temporary .stylelintrc.json for CI..."
61+
cat <<EOF > .stylelintrc.json
62+
{
63+
"extends": "stylelint-config-standard",
64+
"rules": {
65+
"no-duplicate-selectors": true,
66+
"color-hex-length": "short",
67+
"selector-no-qualifying-type": true,
68+
"selector-max-id": 0
69+
}
70+
}
71+
EOF
72+
else
73+
echo "✔ .stylelintrc.json already exists — skipping"
74+
fi
75+
76+
# --------------------------------------------------------------------
77+
# STEP 1: Run pre-commit (capture full logs and exit code safely)
78+
# --------------------------------------------------------------------
79+
- name: Run pre-commit (full logs)
80+
id: runprecommit
81+
run: |
82+
echo "🔍 Running full pre-commit checks..."
83+
84+
set +e # allow failure
85+
pre-commit run --all-files --verbose --show-diff-on-failure --color never \
86+
| tee full_precommit.log
87+
exit_code=${PIPESTATUS[0]}
88+
89+
echo "Pre-commit exit code: $exit_code"
90+
echo "$exit_code" > precommit_exit_code.txt
91+
92+
# --------------------------------------------------------------------
93+
# STEP 2: Summary of FAILED hooks
94+
# --------------------------------------------------------------------
95+
- name: Pre-commit summary of failed hooks
96+
run: |
97+
echo "====================================================="
98+
echo " PRE-COMMIT SUMMARY"
99+
echo "====================================================="
100+
101+
exit_code=$(cat precommit_exit_code.txt)
102+
103+
if [ "$exit_code" = "0" ]; then
104+
echo " All hooks passed!"
105+
exit 0
106+
fi
107+
108+
echo " Hooks failed — showing summary:"
109+
echo ""
110+
111+
echo " FAILED HOOKS:"
112+
grep -E "^\w.*\.{3,}Failed" full_precommit.log || echo " None"
113+
echo "-----------------------------------------------------"
114+
115+
echo " FILES WITH ISSUES:"
116+
grep -E "files were modified by this hook" -A3 full_precommit.log \
117+
| sed 's/^/ - /' || echo " None"
118+
echo "-----------------------------------------------------"
119+
120+
echo " ERROR DETAILS:"
121+
grep -Ei "(error|failed|violation|missing|line too long|could not|warning)" full_precommit.log \
122+
| grep -Ev "^(---|\+\+\+|@@|diff --git|index )" \
123+
| sed 's/^/ • /' || echo " None"
124+
echo "-----------------------------------------------------"
125+
126+
exit $exit_code

.github/workflows/stale.yml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
name: Schedule Stale PRs and Issues Management
2+
3+
permissions:
4+
actions: write
5+
contents: write
6+
issues: write
7+
pull-requests: write
8+
9+
on:
10+
schedule:
11+
- cron: '0 9 * * 1,4' # Monday & Thursday at 9 AM UTC
12+
workflow_dispatch:
13+
14+
concurrency:
15+
group: stale-management
16+
cancel-in-progress: false
17+
18+
jobs:
19+
stale-management:
20+
runs-on: ubuntu-latest
21+
22+
steps:
23+
- name: Checkout repository
24+
uses: actions/checkout@v4
25+
26+
- name: Manage Stale Issues and PRs
27+
uses: actions/stale@v10.1.0
28+
with:
29+
repo-token: ${{ github.token }}
30+
31+
# General settings
32+
days-before-stale: '30'
33+
days-before-close: '7'
34+
operations-per-run: '60'
35+
remove-stale-when-updated: 'true'
36+
37+
# Issue-specific settings
38+
stale-issue-label: 'stale'
39+
stale-issue-message: 'This issue has been marked as stale due to inactivity. It will be closed in 7 days unless there is further activity.'
40+
close-issue-message: 'This issue has been closed due to inactivity. Please feel free to reopen if you think it is still relevant.'
41+
exempt-issue-labels: 'bug,critical,security,high-priority,enhancement,discussion,help wanted,good first issue'
42+
43+
# PR-specific settings
44+
stale-pr-label: 'stale'
45+
stale-pr-message: 'This pull request has been marked as stale due to inactivity. It will be closed in 7 days unless further changes are made or a review is requested.'
46+
close-pr-message: 'This pull request has been closed due to inactivity. Please feel free to reopen if you think it is still relevant.'
47+
exempt-pr-labels: 'WIP,In Progress'

0 commit comments

Comments
 (0)