Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
blank_issues_enabled: false
contact_links: []
37 changes: 37 additions & 0 deletions .github/ISSUE_TEMPLATE/task.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: 작업 이슈
description: Framework의 작업을 등록해요.
title: ""
labels: ["작업"]
body:
- type: markdown
attributes:
value: |
제목에는 작업 내용을 한국어로 적어 주세요. 또한 생성 후 5시간 안에 이슈 번호를 포함한 브랜치를 만들어 주세요.
- type: dropdown
id: type
attributes:
label: 작업 유형
description: 브랜치와 PR에도 같은 유형을 사용해 주세요.
options: [feat, fix, refactor, hotfix, docs, style, remove, delete, test, chore, comment]
validations:
required: true
- type: dropdown
id: assignee
attributes:
label: 담당자
options: [chaeyn, itzjb, Finefinee, hjbin-25, daehyeong2, jdw09]
validations:
required: true
- type: input
id: deadline
attributes:
label: 마감일
placeholder: "2026-08-05 18:00"
validations:
required: true
- type: textarea
id: goal
attributes:
label: 작업 목표
validations:
required: true
11 changes: 11 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
## 변경 내용

<!-- 한국어로, 무엇을 왜 변경했는지 적어주세요. -->

## 확인 방법

<!-- 실행한 테스트 또는 수동 확인 결과를 적어주세요. -->

## 해결한 이슈

Closes #
48 changes: 48 additions & 0 deletions .github/workflows/convention-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: 컨벤션 검사

on:
pull_request:
types: [opened, edited, synchronize, reopened]

permissions:
contents: read
pull-requests: read

jobs:
validate:
name: 컨벤션 검사
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: PR 제목·브랜치·커밋 컨벤션 검사
uses: actions/github-script@v7
env:
ALLOWED_TYPES: feat|fix|refactor|hotfix|docs|style|remove|delete|test|chore|comment
with:
script: |
const types = process.env.ALLOWED_TYPES;
const branch = context.payload.pull_request.head.ref;
const title = context.payload.pull_request.title;
const commits = await github.paginate(github.rest.pulls.listCommits, {
owner: context.repo.owner, repo: context.repo.repo,
pull_number: context.issue.number, per_page: 100
});
const errors = [];
const branchMatch = branch.match(new RegExp(`^(${types})/[a-z0-9]+(?:-[a-z0-9]+)*/#(\\d+)$`));
const titleMatch = title.match(new RegExp(`^(${types}):\\s+[a-z0-9]+(?:-[a-z0-9]+)*/#(\\d+)$`));
if (!branchMatch) errors.push('브랜치명: <type>/<english-slug>/#<issue-number>');
if (!titleMatch) errors.push('PR 제목: <type>: <english-slug>/#<issue-number>');
if (branchMatch && titleMatch && branchMatch[2] !== titleMatch[2]) errors.push('브랜치와 PR 제목의 이슈 번호가 서로 다릅니다.');
if (branchMatch) {
try {
await github.rest.issues.get({ owner: context.repo.owner, repo: context.repo.repo, issue_number: Number(branchMatch[2]) });
} catch {
errors.push(`연결 이슈 #${branchMatch[2]}를 찾을 수 없습니다.`);
}
}
for (const commit of commits) {
if (!(new RegExp(`^(${types}):\\s+.*[가-힣].*$`)).test(commit.commit.message.split('\\n')[0])) errors.push(`커밋 제목: ${commit.sha.slice(0, 7)}`);
}
if (errors.length) core.setFailed(`컨벤션 위반\\n- ${errors.join('\\n- ')}`);
59 changes: 59 additions & 0 deletions .github/workflows/issue-owner.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
name: 이슈 담당자·마감일 확인

on:
issues:
types: [opened]

permissions:
issues: write

jobs:
assign-owner:
name: 이슈 담당자·마감일 확인
runs-on: ubuntu-latest
steps:
- name: 담당자 자동 지정 및 마감일 검증
uses: actions/github-script@v7
with:
script: |
const body = context.payload.issue.body ?? '';
const field = (label) => {
const escaped = label.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const match = body.match(new RegExp(`### ${escaped}\\s*\\n+([^\\n]+)`, 'i'));
return match?.[1]?.trim() ?? '';
};
const type = field('작업 유형');
const assignee = field('담당자');
const deadline = field('마감일');
const types = new Set(['feat', 'fix', 'refactor', 'hotfix', 'docs', 'style', 'remove', 'delete', 'test', 'chore', 'comment']);
const allowed = new Set(['chaeyn', 'itzjb', 'Finefinee', 'hjbin-25', 'daehyeong2', 'jdw09']);
const deadlineFormat = /^20\\d{2}-(0[1-9]|1[0-2])-([0-2]\\d|3[01]) ([01]\\d|2[0-3]):[0-5]\\d$/;
const problems = [];
if (!types.has(type)) problems.push('작업 유형을 목록에서 선택해 주세요.');
if (!allowed.has(assignee)) problems.push('담당자를 목록에서 선택해 주세요.');
if (!deadlineFormat.test(deadline)) problems.push('마감일은 `YYYY-MM-DD HH:mm` 형식으로 입력해 주세요.');
if (problems.length) {
await github.rest.issues.createComment({
owner: context.repo.owner, repo: context.repo.repo,
issue_number: context.issue.number,
body: `이슈 자동화 확인 실패\\n\\n${problems.map((item) => `- ${item}`).join('\\n')}`
});
core.setFailed(problems.join(' '));
return;
}
await github.rest.issues.addAssignees({
owner: context.repo.owner, repo: context.repo.repo,
issue_number: context.issue.number, assignees: [assignee]
});
await github.rest.issues.addLabels({
owner: context.repo.owner, repo: context.repo.repo,
issue_number: context.issue.number, labels: ['작업']
});
const cleanTitle = context.payload.issue.title
.replace(/^\\[(?:feat|fix|refactor|hotfix|docs|style|remove|delete|test|chore|comment)\\]\\s*/i, '')
.trim();
await github.rest.issues.update({
owner: context.repo.owner, repo: context.repo.repo,
issue_number: context.issue.number,
title: `[${type}] ${cleanTitle}`
});
31 changes: 31 additions & 0 deletions .github/workflows/pr-review-lifecycle.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: PR 리뷰 흐름

on:
pull_request:
types: [ready_for_review]

permissions:
pull-requests: write

jobs:
request-team-review:
runs-on: ubuntu-latest
env:
REVIEW_TEAM: ${{ vars.REVIEW_TEAM }}
steps:
- name: 담당 팀에 리뷰 요청
uses: actions/github-script@v7
with:
script: |
const team = process.env.REVIEW_TEAM;
if (!team) {
core.notice('REVIEW_TEAM이 설정되지 않아 자동 리뷰 요청을 건너뛰어요.');
return;
}
await github.rest.pulls.requestReviewers({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.pull_request.number,
team_reviewers: [team]
});
core.notice(`${team} 에 리뷰를 요청했어요.`);
Loading