Automation for Handling Dependabot PRs

2025-02-04 hit count image

Learn about Dependabot configuration and how to automate PR handling using GitHub Actions in a Monorepo environment.

github_actions

Overview

In the previous post, we learned about risk classification criteria for efficiently handling Dependabot PRs.

In this post, we will learn how to automate PR handling using Dependabot configuration and GitHub Actions based on the classification criteria defined in the previous blog.

Dependabot Configuration

To automatically generate dependency update PRs using Dependabot, create a .github/dependabot.yml file and configure it as follows.

version: 2
enable-beta-ecosystems: true

updates:
  # GitHub Actions
  - package-ecosystem: github-actions
    directory: '/'
    open-pull-requests-limit: 5
    schedule:
      interval: weekly
      day: monday
      time: '09:00'
      timezone: Asia/Tokyo
    commit-message:
      prefix: 'deps(common)'
    groups:
      github-actions:
        patterns:
          - '*'

  # Root npm dependencies (common dependency management)
  - package-ecosystem: npm
    directory: '/'
    open-pull-requests-limit: 10
    schedule:
      interval: weekly
      day: monday
      time: '09:00'
      timezone: Asia/Tokyo
    commit-message:
      prefix: 'deps(common)'
    groups:
      # TypeScript types (low risk)
      typescript-types:
        patterns:
          - '@types/*'
        update-types:
          - minor
          - patch

      # ESLint/Prettier related (low risk)
      linting:
        patterns:
          - 'eslint*'
          - '@eslint/*'
          - 'prettier*'
          - 'stylelint*'
        update-types:
          - minor
          - patch

      # Testing tools (low risk)
      testing:
        patterns:
          - '@testing-library/*'
          - 'vitest*'
          - 'jest*'
        update-types:
          - minor
          - patch

      # Patch updates batch processing
      patch-updates:
        update-types:
          - patch
        exclude-patterns:
          - 'react*'
          - 'typescript'
          - 'vite*'

  # Apps settings
  - package-ecosystem: npm
    directory: '/apps/app1'
    open-pull-requests-limit: 3
    schedule:
      interval: weekly
      day: monday
      time: '09:00'
      timezone: Asia/Tokyo
    commit-message:
      prefix: 'deps(app1)'
    ignore:
      - dependency-name: '@packages/*'
      - dependency-name: 'eslint-config-custom'
    groups:
      minor-and-patch:
        update-types:
          - minor
          - patch

  - package-ecosystem: npm
    directory: '/apps/app2'
    open-pull-requests-limit: 3
    schedule:
      interval: weekly
      day: monday
      time: '09:00'
      timezone: Asia/Tokyo
    commit-message:
      prefix: 'deps(app2)'
    ignore:
      - dependency-name: '@packages/*'
      - dependency-name: 'eslint-config-custom'
    groups:
      minor-and-patch:
        update-types:
          - minor
          - patch

  - package-ecosystem: npm
    directory: '/apps/app3'
    open-pull-requests-limit: 3
    schedule:
      interval: weekly
      day: monday
      time: '09:00'
      timezone: Asia/Tokyo
    commit-message:
      prefix: 'deps(app3)'
    ignore:
      - dependency-name: '@packages/*'
      - dependency-name: 'eslint-config-custom'
    groups:
      minor-and-patch:
        update-types:
          - minor
          - patch

  - package-ecosystem: npm
    directory: '/apps/app4'
    open-pull-requests-limit: 3
    schedule:
      interval: weekly
      day: monday
      time: '09:00'
      timezone: Asia/Tokyo
    commit-message:
      prefix: 'deps(app4)'
    ignore:
      - dependency-name: '@packages/*'
      - dependency-name: 'eslint-config-custom'
    groups:
      minor-and-patch:
        update-types:
          - minor
          - patch

  - package-ecosystem: npm
    directory: '/apps/app5'
    open-pull-requests-limit: 3
    schedule:
      interval: weekly
      day: monday
      time: '09:00'
      timezone: Asia/Tokyo
    commit-message:
      prefix: 'deps(app5)'
    ignore:
      - dependency-name: '@packages/*'
      - dependency-name: 'eslint-config-custom'
    groups:
      minor-and-patch:
        update-types:
          - minor
          - patch

  - package-ecosystem: npm
    directory: '/apps/template'
    open-pull-requests-limit: 3
    schedule:
      interval: weekly
      day: monday
      time: '09:00'
      timezone: Asia/Tokyo
    commit-message:
      prefix: 'deps(template)'
    ignore:
      - dependency-name: '@packages/*'
      - dependency-name: 'eslint-config-custom'
    groups:
      minor-and-patch:
        update-types:
          - minor
          - patch

  # Config packages
  - package-ecosystem: npm
    directory: '/packages/config/eslint'
    open-pull-requests-limit: 3
    schedule:
      interval: weekly
      day: monday
      time: '09:00'
      timezone: Asia/Tokyo
    commit-message:
      prefix: 'deps(common)'
    groups:
      minor-and-patch:
        update-types:
          - minor
          - patch

  - package-ecosystem: npm
    directory: '/packages/config/stylelint'
    open-pull-requests-limit: 3
    schedule:
      interval: weekly
      day: monday
      time: '09:00'
      timezone: Asia/Tokyo
    commit-message:
      prefix: 'deps(common)'
    groups:
      minor-and-patch:
        update-types:
          - minor
          - patch

  # Shared packages
  - package-ecosystem: npm
    directory: '/packages/lib/components'
    open-pull-requests-limit: 3
    schedule:
      interval: weekly
      day: monday
      time: '09:00'
      timezone: Asia/Tokyo
    commit-message:
      prefix: 'deps(components)'
    ignore:
      - dependency-name: '@packages/*'
      - dependency-name: 'eslint-config-custom'
    groups:
      storybook:
        patterns:
          - '@storybook/*'
          - 'storybook'
        update-types:
          - minor
          - patch
      minor-and-patch:
        update-types:
          - minor
          - patch

  - package-ecosystem: npm
    directory: '/packages/lib/CommonAPI'
    open-pull-requests-limit: 3
    schedule:
      interval: weekly
      day: monday
      time: '09:00'
      timezone: Asia/Tokyo
    commit-message:
      prefix: 'deps(common)'
    groups:
      minor-and-patch:
        update-types:
          - minor
          - patch

Key Configuration Points

The main features of this Dependabot configuration are as follows.

Schedule Settings

  • Create PRs every Monday at 9:00 AM (Tokyo time)
  • Check for updates on a weekly basis

PR Count Limits

  • Root directory: Maximum 10
  • GitHub Actions: Maximum 5
  • Individual apps/packages: Maximum 3

Grouping Strategy

  • typescript-types: Group @types/* packages (Minor/Patch only)
  • linting: Group ESLint, Prettier, Stylelint related packages
  • testing: Group testing tools
  • patch-updates: Batch process patch updates (excluding high-risk packages)
  • storybook: Group Storybook related packages

Commit Message Prefixes

  • deps(common): Common dependencies
  • deps(app1), deps(app2), etc.: App-specific dependencies
  • deps(components): Component package dependencies

Exclusion Settings

  • Internal packages (@packages/*) are excluded from Dependabot targets
  • Shared ESLint configuration (eslint-config-custom) is also excluded

Automation with GitHub Actions

Using GitHub Actions, you can automatically add labels and write checklist comments to Dependabot PRs.

Create a .github/workflows/dependabot-labeler.yml file and write as follows.

name: Dependabot PR Labeler

on:
  pull_request:
    types:
      - opened
      - synchronize

jobs:
  label-and-comment:
    name: Add risk labels and checklist
    runs-on: ubuntu-latest
    timeout-minutes: 5
    if: github.event.pull_request.user.login == 'dependabot[bot]'
    permissions:
      pull-requests: write
      contents: read
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Analyze and label PR
        uses: actions/github-script@v7
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            const fs = require('fs');
            const path = require('path');

            const { owner, repo } = context.repo;
            const prNumber = context.payload.pull_request.number;
            const prTitle = context.payload.pull_request.title;
            const prBody = context.payload.pull_request.body || '';

            // High risk packages - runtime + affects all apps + hard to detect
            const HIGH_RISK = [
              'react',
              'react-dom',
              '@reduxjs/toolkit',
              'react-redux',
              'react-router-dom'
            ];

            // Medium risk packages - categorized by risk behavior
            // Category A: Major → medium, Minor/Patch → low (CI detectable)
            const MEDIUM_RISK_CI_DETECTABLE = [
              // Build tools
              'vite',
              'typescript',
              '@vitejs/plugin-react',
              'turbo',
              // Test frameworks
              'jest',
              'vitest',
              '@playwright/test',
              // Linting & Code quality
              'eslint',
              '@typescript-eslint/',
              // Storybook
              'storybook',
              '@storybook/'
            ];

            // Category B: Major/Minor → medium, Patch → low (Runtime impact)
            const MEDIUM_RISK_RUNTIME = [
              // Runtime - all apps
              'axios',
              'i18next',
              'react-i18next',
              // App-specific runtime
              'konva',
              'react-konva',
              'recharts'
            ];

            // Low risk - Pure dev tools (always low risk, all update types)
            const LOW_RISK_DEV_ONLY = [
              '@types/',
              'prettier',
              'stylelint',
              'eslint-config-',
              'eslint-plugin-',
              '@testing-library/',
              'ts-jest',
              '@vitest/ui',
              'sass',
              'husky',
              'lint-staged',
              'remark-',
              'cspell',
              'chromatic'
            ];

            // Low risk - Runtime utilities (Major → medium, Minor/Patch → low)
            const LOW_RISK_RUNTIME = [
              'lodash',
              'js-cookie',
              'date-fns',
              'clsx',
              'react-ga4',
              'qrcode.react',
              'react-hotkeys-hook',
              'papaparse'
            ];

            // Extract package name from PR title
            // Format: "deps(scope): bump <package> from x.x.x to y.y.y"
            const packageMatch = prTitle.match(/bump\s+(@?[\w\-\/]+)\s+from/i);
            const packageName = packageMatch ? packageMatch[1] : 'unknown';

            // Determine update type from PR body or title
            let updateType = 'patch';
            const versionMatch = prBody.match(/from\s+(\d+)\.(\d+)\.(\d+)\s+to\s+(\d+)\.(\d+)\.(\d+)/);
            if (versionMatch) {
              const [, fromMajor, fromMinor, , toMajor, toMinor] = versionMatch;
              if (fromMajor !== toMajor) {
                updateType = 'major';
              } else if (fromMinor !== toMinor) {
                updateType = 'minor';
              }
            }

            // Determine base risk level (from package category)
            const isHighRiskPackage = HIGH_RISK.some(pkg => packageName === pkg || packageName.startsWith(pkg));
            const isMediumRiskCIDetectable = MEDIUM_RISK_CI_DETECTABLE.some(pkg => packageName === pkg || packageName.startsWith(pkg));
            const isMediumRiskRuntime = MEDIUM_RISK_RUNTIME.some(pkg => packageName === pkg || packageName.startsWith(pkg));
            const isLowRiskDevOnly = LOW_RISK_DEV_ONLY.some(pkg => packageName === pkg || packageName.startsWith(pkg));
            const isLowRiskRuntime = LOW_RISK_RUNTIME.some(pkg => packageName === pkg || packageName.startsWith(pkg));

            // Determine final risk level based on package category and update type
            let riskLevel = 'low';

            if (isHighRiskPackage) {
              // High risk packages: only major updates remain high risk
              riskLevel = updateType === 'major' ? 'high' : 'medium';
            } else if (isMediumRiskCIDetectable) {
              // CI detectable (build tools, test, lint, storybook): Major → medium, Minor/Patch → low
              riskLevel = updateType === 'major' ? 'medium' : 'low';
            } else if (isMediumRiskRuntime) {
              // Runtime impact (axios, i18n, app-specific): Major/Minor → medium, Patch → low
              riskLevel = updateType === 'patch' ? 'low' : 'medium';
            } else if (isLowRiskDevOnly) {
              // Pure dev tools: always low risk
              riskLevel = 'low';
            } else if (isLowRiskRuntime) {
              // Low risk runtime utilities: Major → medium, Minor/Patch → low
              riskLevel = updateType === 'major' ? 'medium' : 'low';
            } else {
              // Unknown packages: major updates become medium risk
              riskLevel = updateType === 'major' ? 'medium' : 'low';
            }

            // Define labels
            const riskLabels = {
              high: { name: 'risk: high', color: 'B60205', description: 'High risk dependency update' },
              medium: { name: 'risk: medium', color: 'FBCA04', description: 'Medium risk dependency update' },
              low: { name: 'risk: low', color: '0E8A16', description: 'Low risk dependency update' }
            };

            const updateLabels = {
              major: { name: 'update: major', color: 'B60205', description: 'Major version update' },
              minor: { name: 'update: minor', color: 'FBCA04', description: 'Minor version update' },
              patch: { name: 'update: patch', color: '0E8A16', description: 'Patch version update' }
            };

            // Create labels if they don't exist
            const labelsToCreate = [riskLabels[riskLevel], updateLabels[updateType]];

            for (const label of labelsToCreate) {
              try {
                await github.rest.issues.getLabel({
                  owner,
                  repo,
                  name: label.name
                });
              } catch (error) {
                if (error.status === 404) {
                  await github.rest.issues.createLabel({
                    owner,
                    repo,
                    name: label.name,
                    color: label.color,
                    description: label.description
                  });
                }
              }
            }

            // Add labels to PR
            await github.rest.issues.addLabels({
              owner,
              repo,
              issue_number: prNumber,
              labels: [riskLabels[riskLevel].name, updateLabels[updateType].name]
            });

            // Read checklist template from docs/dependencies
            const templatePath = path.join(process.env.GITHUB_WORKSPACE, 'docs', 'dependencies', `${riskLevel}-risk.md`);
            let commentBody = '';

            try {
              commentBody = fs.readFileSync(templatePath, 'utf8');
              // Replace placeholders
              commentBody = commentBody.replace(/\{\{packageName\}\}/g, packageName);
            } catch (error) {
              console.log(`Template file not found: ${templatePath}`);
              commentBody = `## ${riskLevel.charAt(0).toUpperCase() + riskLevel.slice(1)} Risk Dependency Update\n\nThis PR updates \`${packageName}\`.`;
            }

            // Check if bot already commented
            const comments = await github.rest.issues.listComments({
              owner,
              repo,
              issue_number: prNumber
            });

            const botComment = comments.data.find(
              comment => comment.user.login === 'github-actions[bot]' &&
                         comment.body.includes('Risk Dependency Update')
            );

            if (botComment) {
              // Update existing comment
              await github.rest.issues.updateComment({
                owner,
                repo,
                comment_id: botComment.id,
                body: commentBody
              });
            } else {
              // Create new comment
              await github.rest.issues.createComment({
                owner,
                repo,
                issue_number: prNumber,
                body: commentBody
              });
            }

            console.log(`PR #${prNumber}: ${packageName} - Risk: ${riskLevel}, Update: ${updateType}`);

This GitHub Actions workflow performs the following functions:

  1. Dependabot PR Detection: Only processes PRs created by dependabot[bot]
  2. Package Classification: Extracts package name from PR title and determines risk category
  3. Version Analysis: Detects version change type (Major/Minor/Patch) from PR body
  4. Label Addition: Automatically adds labels based on risk level and update type
  5. Checklist Comment: Writes checklist comments using templates from docs/dependencies/ folder

Automatic Labeling

When a Dependabot PR is created, risk labels and update type labels are automatically added.

Risk Labels

LabelColorCondition
risk: highRedMajor updates of high-risk libraries
risk: mediumYellowMinor/Patch of high-risk libraries, Major of medium-risk libraries (CI detectable), Major/Minor of medium-risk libraries (runtime), Major of low-risk runtime
risk: lowGreenMinor/Patch of medium-risk libraries (CI detectable), Patch of medium-risk libraries (runtime), all versions of low-risk dev tools, Minor/Patch of low-risk runtime

Update Labels

LabelColorCondition
update: majorRedMajor version update
update: minorYellowMinor version update
update: patchGreenPatch version update

Checklist Comment Templates

Create the docs/dependencies/ folder and create checklist template files for use in the GitHub Actions workflow as follows.

docs/dependencies/high-risk.md

## High Risk Dependency Update

This PR updates a **high-risk** dependency (`{{packageName}}`). Please review carefully.

### Required Checklist

- [ ] Verify CI passes
- [ ] Check CHANGELOG and Breaking Changes
- [ ] Check migration guide (if applicable)
- [ ] Test all apps and team member review

> **Warning**: Major updates of this package may require extensive testing and code changes.

docs/dependencies/medium-risk.md

## Medium Risk Dependency Update

This PR updates a **medium-risk** dependency (`{{packageName}}`).

### Required Checklist

- [ ] Verify CI passes
- [ ] Check CHANGELOG (verify no breaking changes)
- [ ] Test affected apps and team member review (`yarn why {{packageName}}`)

### Affected Apps

Run `yarn why {{packageName}}` to identify affected apps.

> **Note**: Please check the CHANGELOG before merging.

docs/dependencies/low-risk.md

## Low Risk Dependency Update

This PR updates a **low-risk** dependency (`{{packageName}}`).

### Required Checklist

- [ ] Verify CI passes
- [ ] Review by auto-assigned member

> This update is considered safe. Please merge after CI passes.

These template files are automatically added to comments by GitHub Actions when PRs are created. {{packageName}} is automatically replaced with the actual package name.

Conclusion

This concludes our look at how to automate PR handling using Dependabot configuration and GitHub Actions.

By applying this automation, you can achieve the following benefits:

  • Automatically classify risks of library updates
  • Automatically add labels and checklists to PRs
  • Maintain a consistent review process
  • Reduce review burden on team members

Customize these settings according to your project’s characteristics.

Please refer to the previous post for risk classification criteria.

Was my blog helpful? Please leave a comment at the bottom. it will be a great help to me!

App promotion

You can use the applications that are created by this blog writer Deku.
Deku created the applications with Flutter.

If you have interested, please try to download them for free.



SHARE
Twitter Facebook RSS