개요
이전 포스트에서는 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에 자동으로 lockfile을 업데이트하고, 라벨을 추가하고, 체크리스트 코멘트를 작성할 수 있습니다.
.github/workflows/dependabot_actions.yml 파일을 생성하고 다음과 같이 작성합니다.
name: Dependabot
on:
pull_request:
types:
- opened
- synchronize
jobs:
update-lockfile:
name: Update lockfile
runs-on: ubuntu-latest
timeout-minutes: 10
if: github.event.pull_request.user.login == 'dependabot[bot]'
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup node
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
- name: Enable Corepack
run: corepack enable
- name: Update lockfile
run: yarn install --no-immutable
- name: Commit updated lockfile
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add yarn.lock
git diff --staged --quiet || git commit -m "[dependabot skip] chore: update yarn.lock"
git push
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/'
];
// Runtime packages: Major/Minor → medium, Patch → low
const RUNTIME = [
// Runtime - all apps
'axios',
'i18next',
'react-i18next',
// App-specific runtime
'konva',
'react-konva',
'recharts',
// Runtime utilities
'lodash',
'js-cookie',
'date-fns',
'clsx',
'react-ga4',
'qrcode.react',
'react-hotkeys-hook',
'papaparse'
];
// 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'
];
// 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 isRuntime = RUNTIME.some(pkg => packageName === pkg || packageName.startsWith(pkg));
const isLowRiskDevOnly = LOW_RISK_DEV_ONLY.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 (isRuntime) {
// Runtime packages: Major/Minor → medium, Patch → low
riskLevel = updateType === 'patch' ? 'low' : 'medium';
} else if (isLowRiskDevOnly) {
// Pure dev tools: always low risk
riskLevel = '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 워크플로우는 다음과 같은 기능을 수행합니다.
- Lockfile 업데이트: Dependabot이
package.json을 변경하면yarn.lock을 자동으로 업데이트하고 커밋 - Dependabot PR 감지:
dependabot[bot]이 생성한 PR만 처리 - 패키지 분류: PR 제목에서 패키지명을 추출하고 리스크 카테고리 판별
- 버전 분석: PR 본문에서 버전 변경 유형(Major/Minor/Patch) 감지
- 라벨 추가: 리스크 수준과 업데이트 유형에 따른 라벨 자동 추가
- 체크리스트 코멘트:
docs/dependencies/폴더의 템플릿을 사용하여 체크리스트 코멘트 작성
[dependabot skip]을 사용하는 이유에 대해서는 다음 포스트에서 자세히 다루고 있습니다.
또한 이 워크플로우에서
GITHUB_TOKEN으로 push하면 CI 워크플로우가 재실행되지 않는 문제도 있습니다. 이에 대해서는 다음 포스트를 참고해 주세요.
자동 라벨링
Dependabot PR이 생성되면 자동으로 리스크 라벨과 업데이트 타입 라벨이 추가됩니다.
리스크 라벨
| 라벨 | 색상 | 조건 |
|---|---|---|
risk: high | 빨강 | 고위험 라이브러리의 Major 업데이트 |
risk: medium | 노랑 | 고위험 라이브러리의 Minor/Patch, 중위험 라이브러리의 Major(CI 감지 가능), 런타임 라이브러리의 Major/Minor |
risk: low | 초록 | 중위험 라이브러리의 Minor/Patch(CI 감지 가능), 런타임 라이브러리의 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 확인
- [ ] 마이그레이션 가이드 확인 (해당되는 경우)
- [ ] 모든 앱 테스트 및 팀 멤버 리뷰
- [ ] 공통 컴포넌트에 수정 내용을 기재하고, QCD에 PR의 Preview 환경과 함께 공유하여 확인 받기
### 테스트 커맨드
\`\`\`bash
# 의존성 설치
yarn install
# 타입 체크
yarn turbo typecheck
# 린트
yarn lint
# 전체 테스트 실행
yarn test:ci
# 전체 앱 빌드
yarn build
\`\`\`
> **경고**: 이 패키지의 메이저 업데이트는 대규모 테스트와 코드 변경이 필요할 수 있습니다.
docs/dependencies/medium-risk.md
## 중위험 의존성 업데이트
이 PR은 **중위험** 의존성(`{{packageName}}`)을 업데이트합니다.
### 필수 체크리스트
- [ ] CI 통과 확인
- [ ] CHANGELOG 확인 (Breaking changes 없음 확인)
- [ ] 영향받는 앱 테스트 및 팀 멤버 리뷰 (`yarn why {{packageName}}`)
### 테스트 커맨드
\`\`\`bash
# 의존성 설치
yarn install
# 타입 체크
yarn turbo typecheck
# 테스트 실행
yarn test:ci
# 빌드
yarn build
\`\`\`
### 영향받는 앱
`yarn why {{packageName}}` 명령어를 실행하여 영향받는 앱을 확인해 주세요.
## 예상 외의 문제가 발생한 경우
예상 외의 문제가 발생한 경우, 리스크 조정이나 대책 방법을 결정하기 위해 팀 멤버에게 공유합니다.
> **주의**: 머지 전에 CHANGELOG를 확인해 주세요.
docs/dependencies/low-risk.md
## 저위험 의존성 업데이트
이 PR은 **저위험** 의존성(`{{packageName}}`)을 업데이트합니다.
### 필수 체크리스트
- [ ] CI 통과 확인
- [ ] 자동 할당된 멤버가 리뷰
### 간이 검증
\`\`\`bash
yarn install && yarn test:ci
\`\`\`
## 예상 외의 문제가 발생한 경우
예상 외의 문제가 발생한 경우, 리스크 조정이나 대책 방법을 결정하기 위해 팀 멤버에게 공유합니다.
> 이 업데이트는 안전한 것으로 간주됩니다. CI 통과 후 머지해 주세요.
이 템플릿 파일들은 PR이 생성될 때 GitHub Actions에 의해 자동으로 코멘트에 추가됩니다. {{packageName}}은 실제 패키지명으로 자동 치환됩니다.
완료
이것으로 Dependabot 설정과 GitHub Actions를 사용하여 PR 처리를 자동화하는 방법에 대해서 알아보았습니다.
이 자동화를 적용하면 다음과 같은 이점을 얻을 수 있습니다.
- 라이브러리 업데이트의 리스크를 자동으로 분류
- PR에 자동으로 라벨과 체크리스트 추가
- 일관된 리뷰 프로세스 유지
- 팀 멤버의 리뷰 부담 감소
여러분도 프로젝트의 특성에 맞게 이 설정을 커스터마이즈하여 사용해 보시기 바랍니다.
관련 포스트도 참고해 주세요.
- Dependabot PR 처리를 위한 SOP(Standard Operating Procedure)
- 모노레포에서 Dependabot PR이 동작하지 않는 문제 해결
- GitHub Actions에서 워크플로우가 재트리거되지 않는 문제 해결
제 블로그가 도움이 되셨나요? 하단의 댓글을 달아주시면 저에게 큰 힘이 됩니다!
앱 홍보
Deku가 개발한 앱을 한번 사용해보세요.Deku가 개발한 앱은 Flutter로 개발되었습니다.관심있으신 분들은 앱을 다운로드하여 사용해 주시면 정말 감사하겠습니다.