얼마 전에 commitlint에 대해서 알게 되었다. 앞으로 업무를 진행할 때 팀 내 도입을 제안하려고 하는데 먼저 어떻게 사용할 수 있는지 알아보고자 한다.
현재 팀 내에서 intellij 플러그인 Git Commit Template를 사용해서 타입에 맞게 커밋 메세지를 작성하고 있다. (지라 연동을 위해 지라의 이슈 번호도 넣어주고 있다)
commitlint를 사용하면 지라 이슈 번호 체크 및 작업의 타입 명시가 잘 되어 있는지 명확하게 확인할 수 있을 것이다.
현재 pnpm을 사용 중이기 때문에 pnpm 기반으로 설치한다.
pnpm add --save-dev @commitlint/{cli,config-conventional}
설치 후에 설정 파일을 생성해주자.
module.exports = {
extends: ['@commitlint/config-conventional']
};
나의 적용 목적은 로컬에서 커밋 시에 내가 정한 룰 기반으로 커밋 메세지가 작성되어 있는지 체크하는 것이다.
현재 lint-staged와 husky를 통해서 코드의 스타일(prettier 사용)과 eslint rule을 커밋 시에 검사하고 있다.
마찬가지로 husky를 통해서 커밋 메세지를 체크하면 될 것이다.
commitlint를 통해 커밋 메세지를 검사하는 명령어를 husky hook에 추가해주자.
echo "pnpm dlx commitlint --edit \$1" > .husky/commit-msg
→ $1은 커밋 메시지가 저장된 임시 파일의 경로를 나타낸다.
팀 내 사용되고 있는 커밋 메세지 컨벤션은 다음과 같다.
"프로젝트코드-이슈번호 작업 타입: 작업 내용"
ex) "WEBTEAM-283 fix: 버그 수정"
작업 타입은 다음과 같이 사용하고 있다. commitlint 공식 문서에서 아래 타입을 기본값으로 가지고 있다.
커스텀하게 팀에서 사용할 커밋 메세지 룰을 만들어야 하기 때문에 local plugin을 활용해서 커스텀 룰을 정의했다.
설정 파일은 다음과 같다.
[커스텀 룰]
const config = {
extends: ['@commitlint/config-conventional'],
parserPreset: {
parserOpts: {
headerPattern: /^(\w+-\d+)\s(\w+):\s(.+)$/,
headerCorrespondence: ['ticket', 'type', 'subject'],
},
},
rules: {
'header-match-team-pattern': [2, 'always'],
'header-max-length': [2, 'always', 100],
'ticket-empty': [2, 'never'],
'ticket-case': [2, 'always', 'upper-case'],
'type-empty': [2, 'never'],
'type-case': [2, 'always', 'lower-case'],
'subject-empty': [2, 'never'],
'subject-full-stop': [2, 'never', '.'],
'scope-empty': [2, 'always'],
},
plugins: [
{
rules: {
'header-match-team-pattern': (parsed) => {
const { ticket, type, subject } = parsed;
if (ticket && type && subject) {
return [true];
}
return [
false,
'커밋 메시지가 올바른 형식이 아닙니다. 예: PROJ-123 feat: 새 기능 추가',
];
},
'ticket-empty': (parsed) => {
const { ticket } = parsed;
if (ticket && ticket.trim().length > 0) {
return [true];
}
return [false, '티켓이 비어있습니다'];
},
'ticket-case': (parsed) => {
const { ticket } = parsed;
if (!ticket) return [true]; // 값이 없는 경우 바로 위의 룰에서 걸리기 때문에 해당 룰은 패스하도록 처리했다.
if (ticket && ticket === ticket.toUpperCase()) {
return [true];
}
return [false, '티켓 형식은 대문자여야 합니다'];
},
'ticket-pattern': (parsed) => {
const { ticket } = parsed;
const ticketPattern = /^[A-Z]+-\d+$/;
if (ticket && ticketPattern.test(ticket)) {
return [true];
}
return [
false,
'티켓 번호는 "프로젝트 코드-숫자" 형식이어야 합니다. 예: PROJ-123',
];
},
},
},
],
};
export default config;
commitlint 규칙에서 사용되는 숫자는 다음과 같은 의미를 가진다
always랑 never의 뜻은 다음과 같다.
이제 룰이 정상적으로 동작하는지 확인해보자.

입력: "fix: 버그 수정" → 티켓 정보가 없다

입력: "TEST-123 refactor: 컴포넌트 구조 변경" → 포맷에 맞는 정상적인 메세지다.
