개요
이전 포스트에서는 Dependabot PR을 효율적으로 처리하기 위한 리스크 분류 기준에 대해서 알아보았습니다.
이번 포스트에서는 이전 블로그에서 정의한 분류 기준을 바탕으로 Dependabot 설정과 GitHub Actions를 사용하여 PR 처리를 자동화하는 방법에 대해서 알아보겠습니다.
Dependabot 설정
Dependabot을 사용하여 의존성 업데이트 PR을 자동으로 생성하려면 .github/dependabot.yml 파일을 생성하고 다음과 같이 설정합니다.
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
설정 주요 포인트
이 Dependabot 설정의 주요 특징은 다음과 같습니다.
스케줄 설정
- 매주 월요일 오전 9시(도쿄 시간)에 PR 생성
- 주간 단위로 업데이트 확인
PR 개수 제한
- 루트 디렉토리: 최대 10개
- GitHub Actions: 최대 5개
- 개별 앱/패키지: 최대 3개
그룹화 전략
typescript-types:@types/*패키지를 그룹화 (Minor/Patch만)linting: ESLint, Prettier, Stylelint 관련 패키지 그룹화testing: 테스트 도구 그룹화patch-updates: Patch 업데이트 일괄 처리 (고위험 패키지 제외)storybook: Storybook 관련 패키지 그룹화
커밋 메시지 프리픽스
deps(common): 공통 의존성deps(app1),deps(app2)등: 앱별 의존성deps(components): 컴포넌트 패키지 의존성
제외 설정
- 내부 패키지(
@packages/*)는 Dependabot 대상에서 제외 - 공유 ESLint 설정(
eslint-config-custom)도 제외
GitHub Actions을 통한 자동화
GitHub Actions를 사용하여 Dependabot PR에 자동으로 라벨을 추가하고 체크리스트 코멘트를 작성할 수 있습니다.
.github/workflows/dependabot-labeler.yml 파일을 생성하고 다음과 같이 작성합니다.
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}`);
이 GitHub Actions 워크플로우는 다음과 같은 기능을 수행합니다.
- Dependabot PR 감지:
dependabot[bot]이 생성한 PR만 처리 - 패키지 분류: PR 제목에서 패키지명을 추출하고 리스크 카테고리 판별
- 버전 분석: PR 본문에서 버전 변경 유형(Major/Minor/Patch) 감지
- 라벨 추가: 리스크 수준과 업데이트 유형에 따른 라벨 자동 추가
- 체크리스트 코멘트:
docs/dependencies/폴더의 템플릿을 사용하여 체크리스트 코멘트 작성
자동 라벨링
Dependabot PR이 생성되면 자동으로 리스크 라벨과 업데이트 타입 라벨이 추가됩니다.
리스크 라벨
| 라벨 | 색상 | 조건 |
|---|---|---|
risk: high | 빨강 | 고위험 라이브러리의 Major 업데이트 |
risk: medium | 노랑 | 고위험 라이브러리의 Minor/Patch, 중위험 라이브러리의 Major(CI 감지 가능), 중위험 라이브러리의 Major/Minor(런타임), 저위험 런타임의 Major |
risk: low | 초록 | 중위험 라이브러리의 Minor/Patch(CI 감지 가능), 중위험 라이브러리의 Patch(런타임), 저위험 개발 도구(전체 버전), 저위험 런타임의 Minor/Patch |
업데이트 라벨
| 라벨 | 색상 | 조건 |
|---|---|---|
update: major | 빨강 | Major 버전 업데이트 |
update: minor | 노랑 | Minor 버전 업데이트 |
update: patch | 초록 | Patch 버전 업데이트 |
체크리스트 코멘트 템플릿
GitHub Actions 워크플로우에서 사용하는 체크리스트 템플릿 파일을 docs/dependencies/ 폴더를 만들고 다음과 같이 생성합니다.
docs/dependencies/high-risk.md
## 고위험 의존성 업데이트
이 PR은 **고위험** 의존성(`{{packageName}}`)을 업데이트합니다. 신중하게 리뷰해 주세요.
### 필수 체크리스트
- [ ] CI 통과 확인
- [ ] CHANGELOG, Breaking changes 확인
- [ ] 마이그레이션 가이드 확인 (해당되는 경우)
- [ ] 모든 앱 테스트 및 팀 멤버 리뷰
> **경고**: 이 패키지의 메이저 업데이트는 대규모 테스트와 코드 변경이 필요할 수 있습니다.
docs/dependencies/medium-risk.md
## 중위험 의존성 업데이트
이 PR은 **중위험** 의존성(`{{packageName}}`)을 업데이트합니다.
### 필수 체크리스트
- [ ] CI 통과 확인
- [ ] CHANGELOG 확인 (Breaking changes 없음 확인)
- [ ] 영향받는 앱 테스트 및 팀 멤버 리뷰 (`yarn why {{packageName}}`)
### 영향받는 앱
`yarn why {{packageName}}` 명령어를 실행하여 영향받는 앱을 확인해 주세요.
> **주의**: 머지 전에 CHANGELOG를 확인해 주세요.
docs/dependencies/low-risk.md
## 저위험 의존성 업데이트
이 PR은 **저위험** 의존성(`{{packageName}}`)을 업데이트합니다.
### 필수 체크리스트
- [ ] CI 통과 확인
- [ ] 자동 할당된 멤버가 리뷰
> 이 업데이트는 안전한 것으로 간주됩니다. CI 통과 후 머지해 주세요.
이 템플릿 파일들은 PR이 생성될 때 GitHub Actions에 의해 자동으로 코멘트에 추가됩니다. {{packageName}}은 실제 패키지명으로 자동 치환됩니다.
완료
이것으로 Dependabot 설정과 GitHub Actions를 사용하여 PR 처리를 자동화하는 방법에 대해서 알아보았습니다.
이 자동화를 적용하면 다음과 같은 이점을 얻을 수 있습니다.
- 라이브러리 업데이트의 리스크를 자동으로 분류
- PR에 자동으로 라벨과 체크리스트 추가
- 일관된 리뷰 프로세스 유지
- 팀 멤버의 리뷰 부담 감소
여러분도 프로젝트의 특성에 맞게 이 설정을 커스터마이즈하여 사용해 보시기 바랍니다.
이전 포스트에서 리스크 분류 기준에 대해서 확인할 수 있으니, 다시 한 번 참고해 주세요.
제 블로그가 도움이 되셨나요? 하단의 댓글을 달아주시면 저에게 큰 힘이 됩니다!
앱 홍보
Deku가 개발한 앱을 한번 사용해보세요.Deku가 개발한 앱은 Flutter로 개발되었습니다.관심있으신 분들은 앱을 다운로드하여 사용해 주시면 정말 감사하겠습니다.