husky를 사용해서 git hook을 만들어보자
이전 interface 프로젝트를 개발할 때 백엔드 팀원 분이 작업했던 pre commit git hook이 참 탐이 났더랜다.
그래서 오늘은 husky를 이용하여 commit 전에 실행되는 git hook을 만들어보려고 한다.
오늘 만들 git hook은 크게 2가지로,
1. 커밋이 되기 전에 staged 상태에 있는 파일들을 lint-staged로 검사하는 hook
2. 커밋 시 특정 컨벤션을 따를 수 있도록 선택지를 제공하는 git-cz(commitizen)을 실행하는 hook
이렇게 2개이다.
❓ git hook과 husky
git hook은 git에 어떤 이벤트가 발생했을 때, 자동으로 특정 스크립트를 실행하는 기능이다.
크게 클라이언트 훅과 서버 훅으로 나뉘는데,
클라이언트 훅은 로컬 레포지토리에서 add, commit, push와 같은 이벤트가 발생했을 때 실행하는 기능이고
서버 훅은 원격 레포지토리에서 push와 같은 이벤트가 발생했을 때 실행하는 기능이다.
husky는 이런 git hook을 쉽게 만들고 또 모두가 같이 공유하고 및 실행할 수 있도록 돕는 라이브러리다.
기본적으로 git hook은 .git 폴더에 위치하는데 이 폴더는 보통 .gitignore하는 폴더로 설정되어 원격 레포에 올라가지 않는다.
때문에 공유하려면 다른 까다로운 방법들을 거쳐야하는데 husky를 사용하면 이를 쉽게 해결할 수 있다.
🐶 husky 설정하기
husky를 설치하는 과정은 사용하는 패키지 매니저(npm, yarn, yarn berry)에 따라 다르다.
일단 이 글은 yarn(= yarn1)을 바탕으로 진행한다.
패키지 매니저에 따른 설치 방법은 husky 공식 문서에서 확인할 수 있다.
yarn add -D husky
먼저 위 명령어를 통해 husky를 설치해준다.
그 후에 아래 명령어를 입력해서 husky 초기 설정을 진행한다.
yarn husky install
위 명령어를 입력하면 프로젝트 최상단에 .husky 폴더가 만들어진다.
//package.json
"scripts": {
...,
"prepare": "husky install",
},
그다음 package.json 파일의 scripts 부분에 prepare 명령어를 작성해준다.
이러면 설치가 끝났다.(🐶woof!)
실제로 적용이 되는지 간단하게 테스트해보자.
커밋하는 과정이 들어가므로,
커밋을 되돌릴 자신이 없다면 따라하기보다 어떤 식으로 husky가 돌아가는지 참고하는 용도로 사용하면 좋겠다.
//package.json
"scripts": {
...
"prepare": "husky install",
"test-echo": "echo hello" //추가
},
먼저, package.json 파일에 test-echo 명령어를 추가한다.
이 명령어는 실행하면 hello를 출력하는 명령어이다.
yarn husky add .husky/pre-commit "yarn test-echo"
![](https://blog.kakaocdn.net/dn/dJ53Mq/btsr0Xcdq7O/NjTWhEqrF60LEM9UeHss0K/img.png)
그 다음에 yarn으로 husky를 실행시켜 .husky 폴더에 pre-commit hook을 만들자.
이제 git commit 명령어를 통해 commit을 하려고하면, yarn test-echo 스크립트가 실행될 것이다.
![](https://blog.kakaocdn.net/dn/uiUOE/btsr4jTBFhE/0WdGI1Utb7boHhkFwoWmUK/img.png)
실제로 git commit을 해보니 hello가 잘 출력됨이 보인다.
![](https://blog.kakaocdn.net/dn/clAPSQ/btsr5YaBUy4/dR5crQhDFeABzCHeXeuCJ0/img.png)
레포를 clone 시에도 husky가 적용되는지 확인하기 위해 위 커밋을 push 하고 다시 로컬에 clone 했다.
![](https://blog.kakaocdn.net/dn/ZXDmu/btssbBZv4iI/Ps3pvlM13dks5KQ0YhRcx0/img.png)
클론한 레포에서 .husky 커밋이 적용된 브랜치로 이동해서 yarn 명령어를 통해 설치 시
마지막에 husky가 git hook을 자동으로 설치하는 모습이 보인다.
![](https://blog.kakaocdn.net/dn/baPR8Q/btsr5fjk8by/AVYkaTKEwghvTa37GlgqR0/img.png)
commit 시에도 husky가 test-hello 스크립트를 잘 실행하고 있음을 알 수 있다.
⚙️ Jest와 lint-staged 설정하기
Jest는 이미 설치되어있다고 가정한다.
(참고 : Nest.js에 Jest, React Testing Library 설정하기)
lint-staged는 앞서 말했듯 staged에 올라가있는 파일들에게만 lint를 실행해주는 라이브러리다.
먼저, 아래 명령어를 통해 설치해주자.
yarn add -D lint-staged
그 후 package.json에 아래 속성을 추가해준다.
//package.json
"lint-staged": {
"**/*.{js,jsx,ts,tsx}": [
"eslint --fix",
"prettier --write"
]
}
어느 곳에 위치하던지 js, jsx, ts, tsx 확장자인 파일에 대해서 eslint와 prettier를 실행하고
autofix 할 수 있는 부분을 autofix한다는 명령어이다.
lint-staged인만큼 실제로는 모든 파일이 아니라 staged 된 파일에 대해서만 실행된다는 것을 유념하자.
yarn husky add .husky/pre-commit "yarn jest && yarn lint-staged"
이제 yarn을 통해 husky에게 pre-commit 파일을 만들라고 지시한다.
그러면 .husky/pre-commit이라는 파일이 아래와 같이 생성된 것이 보인다.
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
yarn jest && yarn lint-staged
pre-commit 파일은 commit 실행 시 커밋 메세지를 작성하기 전에 실행되는 파일이다.
이후 테스트용 파일을 만들고 staged한 뒤에 commit 명령어를 실행해서 테스트해봤다.
test와 lint-staged가 커밋 전에 자동으로 돌아가고 있음을 알 수 있다.
husky는 lint에서 걸리면서 autofix 할 수 없을 경우 알아서 commit을 중지한다.
이를 통해 lint 규칙을 지키면서 commit 할 수 있는 환경을 만들 수 있게 된다.(🐶woof!)
👨⚖️ git-cz(commitizen) 설정하기
commitizen은 GUI 같은 CLI로 커밋을 형식에 맞게 작성할 수 있도록 돕는 라이브러리다.
git-cz은 commitizen의 adapter 중의 하나지만,
단독으로 사용해도 commitizen의 기능을 하므로 이를 사용해서 설정해보려한다.
yarn add -D git-cz
먼저 위 코드를 사용해 git-cz를 설치해준다.
yarn husky add .husky/prepare-commit-msg "exec < /dev/tty 1>&0 2>&0 && node_modules/.bin/git-cz --hook || true"
그 후 lint-staged와 마찬가지로, husky를 이용해 prepare-commit-msg 파일을 생성해준다.
commitizen 공식 문서에서는
exec < /dev/tty && node_modules/.bin/git-cz --hook || true
라고 나와있으나
1>&0 2>&0
부분을 포함하지 않으면 최신 git 버전 터미널에서 글자가 덮어씌워지지 않는 오류가 나니 유의하자.
이후 git commit을 실행하면, git-cz가 정상적으로 동작하는게 보인다. 커밋 역시 잘 나타난다.
husky와 commitizen류를 같이 사용 시 텍스트 에디터가 나타나는건 어쩔 수 없는 부분으로 보인다.
prepare-commit-msg hook으로 넣을 경우 git-cz 실행 후 저장 된 커밋이 텍스트 에디터에 나타난다.
:wq를 눌러서 저장하고 나와주면 정상적으로 커밋이 된다.
commit-msg hook으로 넣을 경우 빈 텍스트 에디터가 먼저 나온다.
이를 :q를 눌러서 빠져나오면 그 때 git-cz이 실행되고 끝나면 커밋된다.
이것이 마음에 들지 않을 경우 커밋 후 커밋 메세지에 대해 lint를 돌리는 commitlint를 사용하는 방법 역시 존재한다.
추가적으로 설정하고 싶다면, changelog.config.js를 만든 뒤 설정해주면 된다.
공식 문서 설정에 몇몇 버그가 있어서 내 프로젝트에 적용한 설정을 참고용으로 첨부한다.
- 버그가 있는 부분
1. messages를 types 안이 아니라 types와 같은 계층이 위치하도록 꺼내야함.
2. messages에 footer가 아니라 issues로 적어야 함.
- 참고용 설정
module.exports = {
disableEmoji: false,
format: "{type}: {emoji}{subject}",
list: ["feat", "fix", "fix typo", "docs", "style", "design", "refactor", "test", "chore", "ci"],
maxMessageLength: 64,
minMessageLength: 3,
questions: ["type", "subject", "body", "issues"],
scopes: [],
types: {
feat: {
description: "새로운 기능 추가",
emoji: "✨",
value: "feat",
},
fix: {
description: "버그 픽스",
emoji: "🐛",
value: "fix",
},
"fix typo": {
description: "오타 수정",
emoji: "✏️",
value: "fix typo",
},
docs: {
description: "문서 수정",
emoji: "📝",
value: "docs",
},
style: {
description: "코드 포맷팅, 세미콜론 누락, 코드 변경이 없는 경우",
emoji: "🎨",
value: "style",
},
design: {
description: "CSS 등 디자인 작업",
emoji: "💄",
value: "design",
},
refactor: {
description: "코드 리팩토링",
emoji: "♻️",
value: "refactor",
},
test: {
description: "테스트 코드, 리팩토링 테스트 코드 추가",
emoji: "✅",
value: "test",
},
chore: {
description: "빌드 업무 수정, 패키지 매니저 수정, 기타 잡무",
emoji: "🐋",
value: "chore",
},
ci: {
description: "CI 관련 변경",
emoji: "👷",
value: "ci",
},
},
messages: {
type: "커밋 유형을 선택하세요:",
subject: "짧은 단언문으로 간단한 설명을 작성하세요:\n",
body: "필요할 경우 추가적인 설명을 작성하세요:\n ",
issues: "이 커밋으로 종료되는 이슈(예: #123):",
},
};
이를 통해 git-cz까지 적용해보았다.(🐶woof!)
🎉 마무리
위와 같이 husky를 통해 커밋 과정에서 git hook을 자동으로 동작하도록 만들어보았다.
무의식 중에 커밋을 하면서 다른 파일들에서 버그가 발생하는 것을 잡아내지 못하거나
팀원 각각이 다른 포맷팅 규칙을 사용하면서 코드 퀄리티가 저하되는 경우들이 있었는데
이를 통해 버그를 사전에 방지하고, 코드 퀄리티를 자동으로 유지할 수 있게 되었다.
물론 버그를 잡으려면 테스트 코드를 꼼꼼히 작성하는 게 필수겠지만...
Reference
Possible bug with terminal prompt colours?
'Frontend > 기타' 카테고리의 다른 글
Create Issue Branch로 이슈 브랜치 자동 생성하기 (0) | 2023.09.01 |
---|---|
prettier로 import 정렬하기 (0) | 2023.09.01 |
Next.js와 Storybook에 SVGR 설정하기 (0) | 2023.08.06 |
VSCode에서 ESLint & Prettier 설정하기 (0) | 2023.07.20 |
프론트엔드 개발 환경 기초 설정 (0) | 2023.07.19 |