최근에 공부하는 것도 연말을 타는지, 세상 정신없는 하루를 보내고 있어요. 🔥
덕분에 포트폴리오 제작기 시리즈의 연재가 정말 밀리고 있기는 하네요.
(실제로 우선순위에 따라 포트폴리오 리팩토링도 늦어지고 있어요. 😭)
오늘 주제는, 저번 글에 대한 연장선이라고 이해하면 될 것 같아요.
바로 Semantic-release라는 매우 강력한 툴로 배포를 관리하는 건데요!
인상 깊었던 레포를 예전에 보고 언제 한 번 써볼까 했다가, 이번에 자동화 로직을 아예 전면 변경하게 되면서 제 입맛에 맞춰 사용해보기로 했어요.
⚠️ 주의!
저는 release note를 사용하지 않고, 버저닝과
CHANGELOG.md
만 변경할 목적으로 설정했어요. 실제로는 release note도 사용할 수 있으니 참고 바랍니다!또한 이전의 글은 이제 쓸모가 없는 것처럼 보여도, 취향에 따라 이전의 방식이 더 좋을 수 있습니다.
semantic-release
는 draft가 아닌 바로 릴리즈 노트를 생성해주기 때문에, 초안을 수정할 목적이라면 이전의 글을 참조하시는 것도 좋아보여요 😉
최근 모노레포로 인해 Changeset
이 매력도가 올라가기는 했지만, Semantic-release
역시 자동화 툴로는 프로젝트에서 사용하기엔 충분히 매력적인 것 같았어요.
(그렇지만 다음 프로젝트는 모노레포라, Changeset
을 사용할 확률이 높군요! 써보고 싶기도 했구요.)
여튼, 주절주절 그만하고, 어떻게 설정하면 되는지 시작해보죠.
참고로 제가 쓴 글은 다음 기술스택을 따라갑니다.
@semantic-release/*
의 패키지는 default로도 제공된다고 하니, 참고하세요!
{
"packageManager": "yarn@3.3.0",
"devDependencies": {
"@commitlint/cli": "^17.3.0",
"@commitlint/config-conventional": "^17.3.0",
"@semantic-release/changelog": "^6.0.2",
"@semantic-release/commit-analyzer": "^9.0.2",
"@semantic-release/git": "^10.0.1",
"@semantic-release/release-notes-generator": "^10.0.3",
"commitizen": "^4.2.5",
"conventional-changelog-conventionalcommits": "^5.0.0",
"cz-customizable": "^7.0.0",
"husky": "^8.0.2"
}
}
레포를 보고 참고하고 싶다면, 다음 레포 링크를 통해 CI 설정 결과물을 확인할 수 있습니다!
conventional-commit
에 엄청 도움을 주는 라이브러리입니다.
이 친구들 덕분에 커밋 관리를 좀 더 컨벤셔널하게 가져갈 수 있어요.
이 친구들을 적용할 때에는 어떤 방식으로 관리할 것인지 어댑터를 달아줘야 하는데요.
커밋린트에는 컨벤션을 따르도록 @commitlint/config-conventional
을 설치한 후, commitizen(cz)
는 cz-customizable
을 통해 customization
을 가능하게 합니다.
여기서 호불호가 갈립니다.
commitizen
의 경우 전역으로 설치하는 경우가 많은데 저는 해당 패키지에서 설치합니다.
이유는 특정 프로젝트를 포크하는 어떤 사람이든, 똑같은 컨벤션을 강제하고 싶었기 때문입니다.
저는 컨벤션하게 작성을 하되, 몇 가지를 추가하고 싶었어요. 예컨대 move
나 remove
같은 것들 말이죠.
이후 commitizen
은 자동으로 .cz-config.js
를 찾는데요.
.cz-config.js
에서는 다음과 같이 설정해주면 됩니다.
module.exports = {
types: [
{ value: 'feat', name: 'feat | 기능을 추가해요.' },
{ value: 'fix', name: 'fix | 버그를 수정해요.' },
{ value: 'perf', name: 'perf | 성능을 개선해요.' },
{ value: 'refactor', name: 'refactor | 코드를 리팩토링해요.' },
{ value: 'style', name: 'style | 포맷팅이나 컨벤션에 따라 수정 사항을 반영해요.' },
{ value: 'docs', name: 'docs | 문서의 내용을 일부 변경해요.' },
{ value: 'test', name: 'test | 테스트 코드를 추가하거나 리팩토링해요.' },
{ value: 'chore', name: 'chore | 사소한 변경사항이나, 패키지매니저를 관리해요.' },
{ value: 'revert', name: 'revert | 이전의 코드로 되돌려요.' },
{ value: 'move', name: 'move | 디렉토리, 파일이나 코드를 새로운 위치로 이동시켜요.' },
{ value: 'remove', name: 'revert | 쓸모없는 디렉토리, 파일이나 코드를 삭제해요.' },
{ value: 'ci', name: 'ci | CI를 업데이트해요.' },
],
scopes: [
'component',
'css-style',
'custom-hook',
'store',
'util',
'api',
'wrong codes',
'spaghetti codes',
'alien codes',
'assets',
'package',
'lint',
'formatting',
'config',
'workflow',
// NOTE: .releaserc.js
'breaking',
'no-release',
'README'
],
allowCustomScopes: true,
};
참고로 scope
의 경우 저는 파일 경로를 알려주는 것을 선호하지 않아요.
왜냐하면 파일이 중첩되었을 때 읽기도 힘들고, "파일에서 뭐가 문제인지"를 정확한 범위만 정확히 알려주는 방식을 선호하기 때문이에요.
(실제로 conventionalcommits를 참고해봐도, 스코프는 좀 더 범위에 대해 명확하게 말해주는 편이지요.)
따라서 스코프를 명시적으로 선택해서 가져갈 수 있도록 위와 같이 제 입맛대로 추가해주었답니다 🥰
마지막에 커스텀 스코프를 써준다는 것은 true
로 체크한 부분을 놓치지 말아주세요!
Git hooks
는 보통 클라이언트 훅과 서버 훅으로 나뉘죠.
저는 허스키를 통해, 커밋 단계에서 좀 더 빠르게 깃 훅을 사용하기로 했습니다.
husky를 잘 모르겠다면 가비아의 글을 읽어보시는 것도 좋을 것 같아요 😉
우선 설치를 해주어야겠죠?
yarn add -D husky
설치 한 이후에는 저는 yarn-berry
이므로 husky 공식문서의 글을 따라갔습니다.
yarn husky install
npm publish
는 없을 예정이므로 pinst
는 설치하지 않고, 허스키만 깃 훅을 쓸 수 있도록 하죠!
그러면 이제 .husky
라는 폴더가 생성이 되었을 건데요.
여기에서 저는 다음과 같이 클라이언트 훅을 넣어주었어요.
커밋 메시지를 남기는 때에는 commitlint
가 작동하도록 합니다.
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
echo "🚦 COMMIT-MSG | commitlint check..."
yarn commitlint --edit ${1}
커밋 메시지를 준비하는 때에는 cz
가 작동하도록 합니다.
(여기서 cz
란 commitizen
의 약자입니다!)
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
echo "🚦 PREPARE-COMMIT-MSG | Start cz with cz-customizable..."
exec < /dev/tty && yarn cz --hook || true
이러면 커밋할 때마다 여러 메시지가 뜰 거에요.
양식에 맞게 작성해주시면 됩니다!
후! 이제 끝났군요. 생각보다 글이 길어지네요 😭
다시금 우리가 잘 하고 있는지를 살펴보기 위해 중간 정리를 해볼까요?
commitizen
과commitlint
는 컨벤셔널하게 커밋 메시지를 작성하는 데 도움을 준다. 이는Semantic-release
에서 자동화할 때 큰 영향을 준다.- 이를 일일이 다시 작동하는 건 번거롭다. 따라서
husky
를 통해 각 단계에서 작동을 깃 훅을 사용하여 자동화시켜준다.
다음을 설치해주어야겠어요!
yarn add -D @semantic-release/changelog
yarn add -D @semantic-release/commit-analyzer
yarn add -D @semantic-release/git
yarn add -D @semantic-release/release-notes-generator
yarn add -D conventional-changelog-conventionalcommits
🤯 엇! 왜
@semantic-release
는 설치하지 않나요?저는 포트폴리오에서
eslint(v7.32.0~v8.28.0)
를 같이 쓰고 있었는데요.eslint
에서의 의존성 패키지인debug
에서 의존하는ms
패키지와@semantic-release
의debug
에 있는ms
패키지가 호환이 충돌되더라구요. 😖
따라서 어차피Github Action
에서 설치해주면 그만이기 때문에 위의 네 패키지만 설치했어요. 위 패키지를 설치하지 않고 나중에 워크플로우 때 설치해도 무방하겠네요!
이후에는 다음과 같이 작성해줍니다.
module.exports = {
"branches": [
"main"
],
"plugins": [
[
"@semantic-release/commit-analyzer",
{
"preset": "conventionalcommits",
"releaseRules": [
/**
* @inner
* NOTE: Commits with scope no-release will not be associated with a release type
* even if they have a breaking change or the type 'feat', 'fix' or 'perf'
*
* @see
* https://github.com/semantic-release/commit-analyzer#releaserules
*/
{"scope": "no-release", "release": false},
{"scope": "breaking", "release": "major"},
{"type": "docs", "scope": "README", "release": "patch"},
{"type": "feat", "release": "minor"},
{"type": "fix", "release": "patch"},
{"type": "refactor", "scope": "core-*", "release": "minor"},
{"type": "refactor", "release": "patch"},
{"type": "style", "release": "patch"},
{"type": "perf", "release": "patch"},
{"type": "revert", "release": "patch"},
{"type": "move", "release": false},
{"type": "remove", "release": false},
{"type": "chore", "release": false},
{"type": "ci", "release": false},
{"type": "test", "release": false},
],
"parserOpts": {
"noteKeywords": ["BREAKING CHANGE", "BREAKING CHANGES"]
}
}
],
[
'@semantic-release/release-notes-generator',
{
preset: 'conventionalcommits',
presetConfig: {
types: [
/**
* @inner
* 아래 변화들은 보이도록 한다.
* (presetConfig는 conventional-changelog 방식을 따른다.)
*
* @see
* https://github.com/semantic-release/release-notes-generator
* https://github.com/conventional-changelog/conventional-changelog-config-spec/blob/master/versions/2.0.0/README.md
*/
{ type: 'feat', section: '✨ Features', hidden: false },
{ type: 'fix', section: '🐛 Bug Fixes', hidden: false },
{ type: 'perf', section: '🌈 Performance', hidden: false },
{ type: 'refactor', section: '♻️ Refactor', hidden: false },
{ type: 'docs', section: '📝 Docs', hidden: false },
{ type: 'style', section: '💄 Styles', hidden: false },
{ type: 'revert', section: '🕐 Reverts', hidden: false },
{ type: 'ci', section: '💫 CI/CD', hidden: false },
/**
* @inner
* 아래 변화들은 보이지 않게 한다.
*/
{ type: 'test', section: '✅ Tests', hidden: true },
{ type: 'chore', section: '📦 Chores', hidden: true },
{ type: 'move', section: '🚚 Move Files', hidden: true },
{ type: 'remove', section: '🔥 Remove Files', hidden: true },
],
},
},
],
[
"@semantic-release/changelog",
{
"changelogFile": "CHANGELOG.md",
"changelogTitle": "# 🚦 CHANGELOG | 변경 사항을 기록해요."
}
],
[
"@semantic-release/git",
{
"assets": ["CHANGELOG.md"]
}
]
]
}
생각보다 길죠? 좀 부연설명이 필요할 것 같아요.
Semantic-release
역시 뚝딱하고 없던 것을 창조해내는 게 아니에요.
일련의 과정을 거쳐 배포 시의 chores
를 자동화해주는 건데요!
이 각 단계에 맞춰서 세팅해주시면 됩니다.
실제로 플러그인들은 독립되어 있지 않고, 이 단계에 맞춰 의존되어 있는 경우가 있기 때문에 플러그인을 넣는 순서라던지, 사용 유무에 있어 주의해주셔야 해요.
예컨대,
@semantic-release/changelog
는@semantic-release/release-notes-generator
에 완전히 의존하고 있어요.changelog
플러그인 이전에release-notes-generator
을 플러그인을 설정하지 않는다면 완전히CHANGELOG.md
에 대한 작업을 하지 않습니다.
(이것 때문에 release note만 생성하는 줄 알고 설치 안 했다가 3시간을 날렸네요. 😭)
만약 릴리즈 노트를 만들고 싶다면?
Publish
와 Notify
단계에서 처리하는 친구를 찾으면 되겠죠?
제 경험상으로는 @semantic-release/github
이 이를 담당했던 것 같아요. 참고해주세요!
사실 이건 제가 이야기하는 것보다는 공식문서가 훨~씬 잘 되어 있어요.
제 글은 완전한 정답이 아닙니다. 결국 제 입맛에 맞게 쓸 뿐이니까요.
공식문서를 찬찬히 읽어보시고, 자신에게 더 나은 방식이 있다면 적용하는 것을 추천드려요!
제 경우에는 다음과 같이 설정해주었습니다!
GitHub Action의 워크플로우만 아신다면 너무 간단해서, 설명을 생략해도 좋을
것 같아요 🥰
생각해보니, 이 역시
yarn-berry
로 설정했다는 것에 유의해주세요!
클래식 버전에서는 돌아가지 않을 것 같군요.install
하는 부분들을 클래식 버전에 맞게 고쳐주셔야 할 거에요!
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
name: Semantic-release
on:
push:
branches: ['main']
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16.x]
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
- name: Install dependencies
run: yarn
- name: Semantic-release
env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
run: yarn dlx semantic-release
이렇게 모든 설정이 끝났군요.
메인 브랜치에 푸시해보세요. 잘 되시나요?! 그렇다면 성공하신 거에요. 😉
제 설정대로 하면 기댓값은
commit-analyzer
에 맞춰 변경되어야 한다.CHANGELOG.md
는 release-notes-generator
설정값에 맞추어 생성되어야 한다.@semantic-release/github
사용을 하지 않았으므로)이어야 합니다.
다만 안 되는 경우가 있어요.
대표적인 걸로 임의로 tag
를 바꾸거나, push --force
를 빈번히 하는 경우입니다.
특정 부분에서 안 되신다면, 다음 트러블 슈팅 문서를 참조하시기를 권합니다 😉
잠깐 쓰고 넘어가려 했는데, 어쩌다 보니 글 쓰는 시간이 꽤 걸렸어요!
어제 내내 이것을 설정하고 원하는 대로 적용하기까지.. 거의 하루를 버렸네요.
그렇지만 저 역시 제가 했던 작업들을 복습하는 차원에서 충분한 글이었던 것 같아요.
누군가는 저처럼 삽질하지 않고(...) 멋진 자동화를 하시길 바라며. 글을 마칠게요!