From 48595560c46f0d546343d978d656c418b31777a1 Mon Sep 17 00:00:00 2001 From: rajivbb Date: Mon, 22 Dec 2025 16:52:30 +0545 Subject: [PATCH 1/4] feat: add sonarcloud, github-event notification and stale workflow --- .github/mergeable.yml | 44 ++++++ .github/workflows/analysis.yaml | 27 ++++ .github/workflows/notifications.yaml | 227 +++++++++++++++++++++++++++ .github/workflows/stale.yaml | 141 +++++++++++++++++ commitlint.config.cjs | 24 +++ 5 files changed, 463 insertions(+) create mode 100644 .github/mergeable.yml create mode 100644 .github/workflows/analysis.yaml create mode 100644 .github/workflows/notifications.yaml create mode 100644 .github/workflows/stale.yaml create mode 100644 commitlint.config.cjs diff --git a/.github/mergeable.yml b/.github/mergeable.yml new file mode 100644 index 0000000..8a18105 --- /dev/null +++ b/.github/mergeable.yml @@ -0,0 +1,44 @@ +version: 2 +mergeable: + - when: pull_request.*, pull_request_review.* + validate: + # Validate PR title + - do: title + must_include: + regex: '^(feat|docs|chore|fix|refactor|test|style|perf)(\(\w+\))?: .{5,}' + message: "Semantic release conventions must be followed. Example: feat(auth): add login page. Title must be at least 5 characters after the prefix." + + # Ensure PR description is provided + - do: description + must_include: + regex: "[\\s\\S]{20,}" # At least 20 characters + message: "Please provide a meaningful description of the PR (minimum 20 characters)." + + # Ensure PR references an associated issue + - do: description + must_include: + regex: "(?i)(closes|fixes|resolves|addresses)\\s+#[0-9]+(,?\\s*#[0-9]+)*" + message: "PR must reference at least one issue (e.g., Closes #123, Fixes #123, #124)." + + # Ensure at least one required label is applied + - do: label + must_include: + regex: "^(bug|enhancement|documentation|feature|refactor|performance|chore|wip|test|ci|security|dependencies)$" + message: "PR must include at least one valid label." + # Ensure PR has at least one assignee + - do: assignee + min: + count: 1 + message: "PR must have at least one assignee." + # Ensure PR has at least one reviewer requested + - do: approvals + min: + count: 1 + message: "PR must have at least one approved review." + + pass: + - do: labels + add: + - "validated" + - do: checks + status: "success" diff --git a/.github/workflows/analysis.yaml b/.github/workflows/analysis.yaml new file mode 100644 index 0000000..8c1f2ad --- /dev/null +++ b/.github/workflows/analysis.yaml @@ -0,0 +1,27 @@ +name: Analysis +on: + workflow_call: + secrets: + SONAR_TOKEN: + required: true + SONAR_ORGANIZATION: + required: true + SONAR_PROJECT_KEY: + required: true + +jobs: + analysis: + name: Analysis + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: SonarCloud Analysis + uses: SonarSource/sonarqube-scan-action@v3.0.0 + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + with: + args: > + -Dsonar.organization=${{ secrets.SONAR_ORGANIZATION }} + -Dsonar.projectKey=${{ secrets.SONAR_PROJECT_KEY }} diff --git a/.github/workflows/notifications.yaml b/.github/workflows/notifications.yaml new file mode 100644 index 0000000..0ecaa8f --- /dev/null +++ b/.github/workflows/notifications.yaml @@ -0,0 +1,227 @@ +name: Notify (GitHub Events) + +on: + workflow_call: + inputs: + workflow_name: + description: 'Name of the workflow that triggered this' + required: false + type: string + default: 'GitHub Event' + secrets: + GOOGLE_CHAT_WEBHOOK: + required: true + SLACK_BOT_TOKEN: + required: true + SLACK_CHANNEL_ID: + required: true + issues: + types: [opened, edited, reopened, closed, assigned, labeled] + pull_request: + types: [opened, closed, reopened, ready_for_review, review_requested] + pull_request_review: + types: [submitted, edited, dismissed] + issue_comment: + types: [created, edited] + pull_request_review_comment: + types: [created, edited] + +jobs: + notify: + runs-on: ubuntu-latest + name: Send Notifications + steps: + - name: Determine Event Type + id: event_info + run: | + EVENT="${{ github.event_name }}" + ACTION="${{ github.event.action }}" + REPO="${{ github.repository }}" + ACTOR="${{ github.actor }}" + REPO_URL="${{ github.server_url }}/${{ github.repository }}" + + if [[ "$EVENT" == "issues" ]]; then + TITLE="Issue $ACTION: ${{ github.event.issue.title }}" + URL="${{ github.event.issue.html_url }}" + NUMBER="${{ github.event.issue.number }}" + USER="${{ github.event.issue.user.login }}" + DESCRIPTION="Issue #$NUMBER by $USER" + STATE="${{ github.event.issue.state }}" + EMOJI="🐛" + + elif [[ "$EVENT" == "pull_request" ]]; then + NUMBER="${{ github.event.pull_request.number }}" + USER="${{ github.event.pull_request.user.login }}" + URL="${{ github.event.pull_request.html_url }}" + STATE="${{ github.event.pull_request.state }}" + SOURCE="${{ github.event.pull_request.head.ref }}" + TARGET="${{ github.event.pull_request.base.ref }}" + + if [[ "$ACTION" == "closed" && "${{ github.event.pull_request.merged }}" == "true" ]]; then + TITLE="PR merged: ${{ github.event.pull_request.title }}" + EMOJI="✅" + STATE="merged" + elif [[ "$ACTION" == "closed" ]]; then + TITLE="PR closed: ${{ github.event.pull_request.title }}" + EMOJI="❌" + else + TITLE="PR $ACTION: ${{ github.event.pull_request.title }}" + EMOJI="🔀" + fi + DESCRIPTION="PR #$NUMBER by $USER: $SOURCE → $TARGET" + + elif [[ "$EVENT" == "pull_request_review" ]]; then + TITLE="PR Review: ${{ github.event.pull_request.title }}" + URL="${{ github.event.review.html_url }}" + NUMBER="${{ github.event.pull_request.number }}" + USER="${{ github.event.review.user.login }}" + REVIEW_STATE="${{ github.event.review.state }}" + STATE="${{ github.event.review.state }}" + SOURCE="${{ github.event.pull_request.head.ref }}" + TARGET="${{ github.event.pull_request.base.ref }}" + DESCRIPTION="Review by $USER on PR #$NUMBER" + EMOJI="👀" + + elif [[ "$EVENT" == "issue_comment" ]]; then + NUMBER="${{ github.event.issue.number }}" + USER="${{ github.event.comment.user.login }}" + URL="${{ github.event.comment.html_url }}" + STATE="commented" + + if [[ "${{ github.event.issue.pull_request }}" != "" ]]; then + TITLE="Comment on PR: ${{ github.event.issue.title }}" + EMOJI="💬" + else + TITLE="Comment on Issue: ${{ github.event.issue.title }}" + EMOJI="💬" + fi + DESCRIPTION="Comment by $USER on #$NUMBER" + + elif [[ "$EVENT" == "pull_request_review_comment" ]]; then + TITLE="Comment on PR: ${{ github.event.pull_request.title }}" + URL="${{ github.event.comment.html_url }}" + NUMBER="${{ github.event.pull_request.number }}" + USER="${{ github.event.comment.user.login }}" + STATE="commented" + SOURCE="${{ github.event.pull_request.head.ref }}" + TARGET="${{ github.event.pull_request.base.ref }}" + DESCRIPTION="Review comment by $USER on PR #$NUMBER" + EMOJI="💬" + else + TITLE="GitHub Event: $EVENT" + URL="${{ github.event.repository.html_url }}" + DESCRIPTION="Event triggered in $REPO" + STATE="N/A" + NUMBER="N/A" + USER="$ACTOR" + EMOJI="📢" + fi + + echo "title=$TITLE" >> $GITHUB_OUTPUT + echo "url=$URL" >> $GITHUB_OUTPUT + echo "description=$DESCRIPTION" >> $GITHUB_OUTPUT + echo "emoji=$EMOJI" >> $GITHUB_OUTPUT + echo "number=${NUMBER:-N/A}" >> $GITHUB_OUTPUT + echo "user=${USER:-$ACTOR}" >> $GITHUB_OUTPUT + echo "state=${STATE:-N/A}" >> $GITHUB_OUTPUT + echo "source=${SOURCE:-N/A}" >> $GITHUB_OUTPUT + echo "target=${TARGET:-N/A}" >> $GITHUB_OUTPUT + echo "repo_url=$REPO_URL" >> $GITHUB_OUTPUT + + - name: Google Chat Notification + if: always() + uses: Co-qn/google-chat-notification@releases/v1 + with: + name: ${{ steps.event_info.outputs.title }} + url: ${{ secrets.GOOGLE_CHAT_WEBHOOK }} + status: ${{ job.status }} + + - name: Slack Notification + if: always() + uses: slackapi/slack-github-action@v1.24.0 + with: + channel-id: ${{ secrets.SLACK_CHANNEL_ID }} + payload: | + { + "text": "${{ steps.event_info.outputs.title }}", + "blocks": [ + { + "type": "header", + "text": { + "type": "plain_text", + "text": "${{ steps.event_info.outputs.emoji }} ${{ steps.event_info.outputs.title }}" + } + }, + { + "type": "section", + "fields": [ + { + "type": "mrkdwn", + "text": "*Repository:*\n<${{ steps.event_info.outputs.repo_url }}|${{ github.repository }}>" + }, + { + "type": "mrkdwn", + "text": "*Event:*\n${{ github.event_name }}" + }, + { + "type": "mrkdwn", + "text": "*Author:*\n${{ steps.event_info.outputs.user }}" + }, + { + "type": "mrkdwn", + "text": "*Action:*\n${{ github.event.action }}" + }, + { + "type": "mrkdwn", + "text": "*Number:*\n<${{ steps.event_info.outputs.url }}|#${{ steps.event_info.outputs.number }}>" + }, + { + "type": "mrkdwn", + "text": "*State:*\n${{ steps.event_info.outputs.state }}" + } + ] + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "${{ steps.event_info.outputs.description }}" + } + }, + { + "type": "actions", + "elements": [ + { + "type": "button", + "text": { + "type": "plain_text", + "text": "View on GitHub", + "emoji": true + }, + "url": "${{ steps.event_info.outputs.url }}", + "style": "primary" + }, + { + "type": "button", + "text": { + "type": "plain_text", + "text": "View Repository", + "emoji": true + }, + "url": "${{ steps.event_info.outputs.repo_url }}" + } + ] + }, + { + "type": "context", + "elements": [ + { + "type": "mrkdwn", + "text": "Triggered by ${{ github.actor }} • ${{ github.event_name }} event" + } + ] + } + ] + } + env: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml new file mode 100644 index 0000000..c671f54 --- /dev/null +++ b/.github/workflows/stale.yaml @@ -0,0 +1,141 @@ +name: Stale PRs and Issues Management +permissions: + actions: write + contents: write + issues: write + pull-requests: write + +on: + workflow_call: + inputs: + # General settings + days-before-stale: + description: 'Days before marking as stale (applies to both issues and PRs unless overridden)' + required: false + default: '30' + type: string + days-before-close: + description: 'Days before closing stale items (applies to both issues and PRs unless overridden)' + required: false + default: '7' + type: string + + # Issue-specific overrides + days-before-issue-stale: + description: 'Days before marking issues as stale (overrides days-before-stale)' + required: false + type: string + days-before-issue-close: + description: 'Days before closing stale issues (overrides days-before-close)' + required: false + type: string + + # PR-specific overrides + days-before-pr-stale: + description: 'Days before marking PRs as stale (overrides days-before-stale)' + required: false + type: string + days-before-pr-close: + description: 'Days before closing stale PRs (overrides days-before-close)' + required: false + type: string + + # PR labels and messages + stale-pr-label: + description: 'Label for stale PRs' + required: false + default: 'stale' + type: string + # NOTE: Avoid hardcoding numeric durations in message defaults. + # If you customize `days-before-pr-close` when calling this workflow, + # update this message accordingly or keep it generic so it won't contradict + # the configured closing period. + stale-pr-message: + description: 'Message for stale PRs (avoid hardcoding durations; prefer generic wording or sync with days-before-pr-close)' + required: false + default: 'This PR has been marked as stale due to inactivity and may be closed within the configured period unless further changes are made or a review is requested.' + type: string + close-pr-message: + description: 'Message for closing stale PRs' + required: false + default: 'This PR has been closed due to inactivity. Please feel free to reopen if you think it is still relevant.' + type: string + + # Issue labels and messages + stale-issue-label: + description: 'Label for stale issues' + required: false + default: 'stale' + type: string + # NOTE: Avoid hardcoding numeric durations in message defaults. + # If you customize `days-before-issue-close` when calling this workflow, + # update this message accordingly or keep it generic so it won't contradict + # the configured closing period. + stale-issue-message: + description: 'Message for stale issues (avoid hardcoding durations; prefer generic wording or sync with days-before-issue-close)' + required: false + default: 'This issue has been marked as stale due to inactivity and may be closed within the configured period unless there is further activity.' + type: string + close-issue-message: + description: 'Message for closing stale issues' + required: false + default: 'This issue has been closed due to inactivity. Please feel free to reopen if you think it is still relevant.' + type: string + + # Exemptions + exempt-pr-labels: + description: 'Labels to exempt PRs from being marked as stale' + required: false + default: 'WIP' + type: string + exempt-issue-labels: + description: 'Labels to exempt issues from being marked as stale' + required: false + default: 'bug,critical,enhancement,security' + type: string + + # Additional options + operations-per-run: + description: 'Maximum number of operations per run' + required: false + default: '30' + type: string + remove-stale-when-updated: + description: 'Remove stale label when issue/PR is updated' + required: false + default: 'true' + type: string + +jobs: + stale-management: + runs-on: ubuntu-latest + steps: + - name: Checkout the repository + uses: actions/checkout@v4 + + - name: Manage Stale Issues and PRs + uses: actions/stale@v10.1.0 + with: + repo-token: ${{ github.token }} + + # General settings + days-before-stale: ${{ inputs.days-before-stale }} + days-before-close: ${{ inputs.days-before-close }} + operations-per-run: ${{ inputs.operations-per-run }} + remove-stale-when-updated: ${{ inputs.remove-stale-when-updated }} + + # Issue-specific settings + days-before-issue-stale: ${{ inputs.days-before-issue-stale }} + days-before-issue-close: ${{ inputs.days-before-issue-close }} + stale-issue-label: ${{ inputs.stale-issue-label }} + stale-issue-message: ${{ inputs.stale-issue-message }} + close-issue-message: ${{ inputs.close-issue-message }} + exempt-issue-labels: ${{ inputs.exempt-issue-labels }} + + # PR-specific settings + days-before-pr-stale: ${{ inputs.days-before-pr-stale }} + days-before-pr-close: ${{ inputs.days-before-pr-close }} + stale-pr-label: ${{ inputs.stale-pr-label }} + stale-pr-message: ${{ inputs.stale-pr-message }} + close-pr-message: ${{ inputs.close-pr-message }} + exempt-pr-labels: ${{ inputs.exempt-pr-labels }} diff --git a/commitlint.config.cjs b/commitlint.config.cjs new file mode 100644 index 0000000..1cec015 --- /dev/null +++ b/commitlint.config.cjs @@ -0,0 +1,24 @@ +module.exports = { + extends: ['@commitlint/config-conventional'], + rules: { + 'type-enum': [2, 'always', [ + 'feat', // A new feature + 'fix', // A bug fix + 'docs', // Documentation only changes + 'style', // Changes that do not affect code meaning + 'refactor', // A code change that neither fixes a bug nor adds a feature + 'perf', // A code change that improves performance + 'test', // Adding missing tests or correcting existing tests + 'build', // Build system or external dependencies + 'ci', // CI configuration changes + 'chore', // Other changes that don't modify src or test files + 'revert', // Reverts a previous commit + ]], + 'type-case': [2, 'always', 'lower-case'], + 'type-empty': [2, 'never'], + 'scope-case': [2, 'always', 'lower-case'], + 'subject-empty': [2, 'never'], + 'subject-full-stop': [2, 'never', '.'], + 'header-max-length': [2, 'always', 72], + }, +}; From 33791396c708cb73d37089dca430f7ef98d78be5 Mon Sep 17 00:00:00 2001 From: rajivbb Date: Mon, 22 Dec 2025 17:18:10 +0545 Subject: [PATCH 2/4] feat: add sonarcloud, github-event notification and stale workflow --- .github/mergeable.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/mergeable.yml b/.github/mergeable.yml index 8a18105..07f1cf8 100644 --- a/.github/mergeable.yml +++ b/.github/mergeable.yml @@ -17,7 +17,7 @@ mergeable: # Ensure PR references an associated issue - do: description must_include: - regex: "(?i)(closes|fixes|resolves|addresses)\\s+#[0-9]+(,?\\s*#[0-9]+)*" + regex: "(Closes|Fixes|Resolves|Addresses)\\s+#[0-9]+(,?\\s*#[0-9]+)*" message: "PR must reference at least one issue (e.g., Closes #123, Fixes #123, #124)." # Ensure at least one required label is applied @@ -34,7 +34,7 @@ mergeable: - do: approvals min: count: 1 - message: "PR must have at least one approved review." + message: "PR must have at least one reviewer requested or approved." pass: - do: labels From f347f0026d72b604af1bff19112455a5c1a2cd69 Mon Sep 17 00:00:00 2001 From: rajivbb Date: Mon, 22 Dec 2025 17:31:16 +0545 Subject: [PATCH 3/4] chore: add README.md file --- README.md | 52 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 852319c..32073ac 100644 --- a/README.md +++ b/README.md @@ -1 +1,51 @@ -Workflow +### Repository of Reusable GitHub Actions Workflows + +--- + +# Reusable GitHub Actions Workflows + +Welcome to our repository of reusable GitHub Actions workflows. These workflows are designed to be easily integrated into any GitHub repository to automate various aspects of software development and maintenance processes. + +## Available Workflows + +Currently, the repository hosts the following reusable workflows: +- **SonarCloud Analysis**: Automates the code quality checking using SonarCloud. +- **Notification**: Github Event Notification in Google WorkSpace and Slack Channel . +- **Stale Issue/PR Handler**: Manages inactive issues and pull requests to keep project boards clean and updated. + +## How to Use the Workflows + +### Prerequisites + +Before using these workflows, make sure you have: +- A GitHub account. +- Required permissions to add workflows and secrets to your GitHub repositories. + +### General Usage Steps + +1. **Choose a Workflow**: Decide which workflow from this repository you want to use. + +2. **Add Secrets (if required)**: Some workflows might require you to set up repository secrets. For instance, the SonarCloud Analysis needs secrets like `SONAR_TOKEN`. You can add these secrets by navigating to: + ``` + Settings > Secrets > Actions > New repository secret + ``` + +3. **Import the Workflow**: To utilize a workflow, add it to a `.yml` file in the `.github/workflows` directory of your repository. For example, to use the SonarCloud Analysis, you might write: + + ```yaml + name: SonarCloud Analysis + on: + push: + branches: + - main + - develop + pull_request: + + jobs: + sonarcloud: + uses: berrybytes/workflows/.github/workflows/sonarcloud-analysis.yml@main + secrets: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONAR_ORGANIZATION: ${{ secrets.SONAR_ORGANIZATION }} + SONAR_PROJECT_KEY: ${{ secrets.SONAR_PROJECT_KEY }} + ``` From 911117f021fca8ac69057227b8d85d44a4ccc33f Mon Sep 17 00:00:00 2001 From: rajivbb Date: Mon, 30 Mar 2026 19:20:47 +0545 Subject: [PATCH 4/4] feat: pre-commit workflow --- .github/workflows/pre-commit.yaml | 119 ++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 .github/workflows/pre-commit.yaml diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml new file mode 100644 index 0000000..3728af0 --- /dev/null +++ b/.github/workflows/pre-commit.yaml @@ -0,0 +1,119 @@ +name: Pre-Commit Checks + +on: + workflow_call: # Trigger for reusable workflows + +jobs: + precommit: + runs-on: ubuntu-latest + + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install pre-commit + run: | + python -m pip install --upgrade pip + pip install pre-commit + + - name: Load Pre-commit Config + run: | + if [ ! -f ".pre-commit-config.yaml" ]; then + echo " No .pre-commit-config.yaml found — downloading BerryBytes global config..." + curl -sSL \ + https://raw.githubusercontent.com/BerryBytes/precommit-util/main/global/precommitFile/.pre-commit-config.yaml \ + -o .pre-commit-config.yaml + else + echo "✔ Using project's existing .pre-commit-config.yaml" + fi + + - name: Inject temporary Stylelint config for CI + run: | + if [ ! -f ".stylelintrc.json" ]; then + echo " Creating temporary .stylelintrc.json for CI..." + cat < .stylelintrc.json + { + "extends": "stylelint-config-standard", + "rules": { + "no-duplicate-selectors": true, + "color-hex-length": "short", + "selector-no-qualifying-type": true, + "selector-max-id": 0 + } + } + EOF + else + echo "✔ .stylelintrc.json already exists — skipping" + fi + + # -------------------------------------------------------------------- + # STEP 1: Run pre-commit (capture full logs and exit code safely) + # -------------------------------------------------------------------- + - name: Run pre-commit (full logs) + id: runprecommit + run: | + echo "🔍 Running full pre-commit checks..." + + set +e # allow failure + pre-commit run --all-files --verbose --show-diff-on-failure --color never \ + | tee full_precommit.log + exit_code=${PIPESTATUS[0]} + + echo "Pre-commit exit code: $exit_code" + echo "$exit_code" > precommit_exit_code.txt + + # -------------------------------------------------------------------- + # STEP 2: Summary of FAILED hooks + # -------------------------------------------------------------------- + - name: Pre-commit summary of failed hooks + run: | + echo "=====================================================" + echo " PRE-COMMIT SUMMARY" + echo "=====================================================" + + exit_code=$(cat precommit_exit_code.txt) + + if [ "$exit_code" = "0" ]; then + echo " All hooks passed!" + exit 0 + fi + + echo " Hooks failed — showing summary:" + echo "" + + echo " FAILED HOOKS:" + grep -E "^\w.*\.{3,}Failed" full_precommit.log || echo " None" + echo "-----------------------------------------------------" + + echo " FILES WITH ISSUES:" + grep -E "files were modified by this hook" -A3 full_precommit.log \ + | sed 's/^/ - /' || echo " None" + echo "-----------------------------------------------------" + + echo " ERROR DETAILS:" + grep -Ei "(error|failed|violation|missing|line too long|could not|warning)" full_precommit.log \ + | grep -Ev "^(---|\+\+\+|@@|diff --git|index )" \ + | sed 's/^/ • /' || echo " None" + echo "-----------------------------------------------------" + + exit $exit_code + # -------------------------------------------------------------------- + # STEP 3: Run pre-commit skipping local Go hooks (for CI efficiency) + # -------------------------------------------------------------------- + - name: Run pre-commit (skip local Go hooks in CI) + id: precommit_skip_go + env: + SKIP: go-fmt,go-vet,go-imports,golangci-lint # This disables all local Go hooks in CI only + run: | + echo "Running pre-commit (Go hooks skipped in CI via SKIP env var)" + set +e + pre-commit run --all-files --verbose --show-diff-on-failure --color never \ + | tee full_precommit.log + exit_code=${PIPESTATUS[0]} + echo "Pre-commit exit code: $exit_code" + echo "$exit_code" > precommit_exit_code.txt