안녕하세요. J4J입니다.
이번 포스팅은 semantic release로 자동 버전 관리하는 방법에 대해 적어보는 시간을 가져보려고 합니다.
Semantic Release
semantic release는 commit 메시지를 분석하여 자동으로 소프트웨어 버전을 관리하고 release 노트 작성을 자동화할 수 있도록 도와주는 도구입니다.
소프트웨어 개발을 하다보면 단순한 과정이지만 매번 수행하지 않고 한 번식 미루는 것이 release 노트를 관리하는 것입니다.
release 노트를 관리하면 어떤 버전에 어떤 기능이 변경 적용되었는지 확인할 수 있기 때문에 변경 이력들을 관리하는 목적으로 반 필수적으로 관리하게 됩니다.
하지만 release 노트를 관리하면서 다음과 같은 어려움을 느끼는 경우가 한 번씩 있습니다.
- 이번 release에 포함되는 기능들은 어떤 것들이 있고, 버전을 어떻게 바꿔야 하지?
- tag를 매번 잘 관리하다가 한 번씩 중간에 빼먹어 버렸네
- 우리 규칙에서는 버전 관리가 어떻게 되어야 하는 거였지?
- 등등..
이런 상황에서 semantic release를 사용하게 되면 이번에 release에 포함되는 commit 메시지를 분석하여 버전 관리를 자동화하여 위의 상황들을 고민하지 않는 결과를 만들어 줍니다.
semantic release를 적용하면 어떤 결과가 나오는지 한번 예를 들어보겠습니다.
우리가 버전 정보를 관리할 때는 차용될 수 있는 방식이 다양하게 있지만 익숙하게 보이는 것은 semVer입니다.
semVer은 다음과 같은 구조를 띄는 것을 의미합니다.
"[major].[minor].[patch]"
이 버전 관리 방식은 package.json 등에서 볼 수 있는 버전 정보입니다.
semantic release는 semVer을 이용한 버전 관리를 도와주는 도구입니다.
그래서 commit 메시지를 작성할 때 type 별 변경되는 버전은 다음과 같습니다.
1. release 버전 변경 영향 없음
- chore
- docs
- test
- refactor
- hotfix
- 등등..
2. patch 버전 변경 (버그 수정 등의 비즈니스 변경)
- fix
- perf
- 등등..
3. minor 버전 변경 (이전 버전과 호환되며 새로운 기능 추가)
- feat
- 등등..
4. major 버전 변경 (이전 버전과 호환되지 않은 기능 변경)
- { type }! 형태의 breaking change
- 등등..
이 외에 기본적으로 제공하지 않고 프로젝트 별 커스터 마이징을 하여 관리하고 싶을 수 있습니다.
그런 경우 config 파일의 설정 변경을 통해 우리가 원하는 방식으로 설정을 적용할 수도 있습니다.
Semantic Release 설치
semantic release의 경우 npm 기반으로 설치가 이루어집니다.
보통 js 기반으로 관리되는 프로젝트는 자연스러울 수 있지만 java 등의 다른 언어를 사용하는 프로젝트에서는 어색할 수 있습니다.
다만, 이런 상황에서도 semantic release를 설치하는 경우 npm 기반으로 해주시면 됩니다.
[ 1. package.json 초기화 ]
package.json이 기존에 없는 프로젝트의 경우 다음 명령어를 이용하여 초기화를 해줍니다.
$ npm init -y
[ 2. package 설치 ]
상황에 따라 필요한 package만 설치할 수 있지만 저의 경우 다음과 같이 설치를 해보겠습니다.
$ npm install -D semantic-release \
@semantic-release/commit-analyzer \
@semantic-release/release-notes-generator \
@semantic-release/changelog \
@semantic-release/npm \
@semantic-release/git \
@semantic-release/gitlab \
conventional-changelog-conventionalcommits
[ 3. release config 파일 추가 ]
root 폴더 하위에 다음과 같은 config 파일을 추가합니다.
추가적인 커스터 마이징이 필요한 경우 해당 파일에서 작업을 해주시면 됩니다.
// release.config.cjs
module.exports = {
// release 관리를 수행할 branch 설정
branches: ['main'],
plugins: [
// commit 메세지 분석
[
'@semantic-release/commit-analyzer',
{
preset: 'conventionalcommits',
// commit 메세지 parser 설정, 기본 값 예시) feat: 신규 기능
parserOpts: {
headerPattern: /^(?<type>feat|fix|perf|docs|hotfix|chore|refactor|remove|test)\s*:?\s*(?<subject>.*)$/,
headerCorrespondence: ['type', 'subject'],
},
// major/minor/patch rule 설정
releaseRules: [
// major
{ breaking: true, release: 'major' },
// minor
{ type: 'feat', release: 'minor' },
// patch
{ type: 'fix', release: 'patch' },
{ type: 'perf', release: 'patch' },
// false
{ type: 'docs', release: false },
{ type: 'hotfix', release: false },
{ type: 'chore', release: false },
{ type: 'refactor', release: false },
{ type: 'remove', release: false },
{ type: 'test', release: false },
],
},
],
// release 노트 생성
[
'@semantic-release/release-notes-generator',
{
preset: 'conventionalcommits',
// release 노트 parser 설정, 기본 값 예시) feat: 신규 기능
parserOpts: {
headerPattern: /^(?<type>feat|fix|perf|docs|hotfix|chore|refactor|remove|test)\s*:?\s*(?<subject>.*)$/,
headerCorrespondence: ['type', 'subject'],
},
// type 별 제목 설정 & release note 표기 여부 설정
presetConfig: {
types: [
{ type: 'feat', section: '✨ Features', hidden: false },
{ type: 'fix', section: '🐛 Bug Fixes', hidden: false },
{ type: 'perf', section: '⚡ Performance', hidden: false },
{ type: 'docs', section: '📝 Docs', hidden: false },
{ type: 'hotfix', section: '🔥 Hotfixes', hidden: false },
{ type: 'chore', section: '🧹 Chores', hidden: false },
{ type: 'refactor', section: '♻ Refactors', hidden: false },
{ type: 'remove', section: '🗑️ Removals', hidden: false },
{ type: 'test', section: '✅ Tests', hidden: true }
],
},
writerOpts: {
// release 노트 제목 변경
headerPartial: '## v{{version}} release notes. ({{date}})\n'
},
},
],
// CHANGELOG.md 업데이트
[
'@semantic-release/changelog',
{
changelogFile: 'CHANGELOG.md',
},
],
// package.json npm 버전 업데이트
[
'@semantic-release/npm',
{
npmPublish: false, // true일 경우 "$npm publish" 명령어가 자동 수행
},
],
// CHANGELOG.md / package.json 변경 사항 commit > push
// [skip ci]가 존재하는 경우 ci pipeline 미 수행
[
'@semantic-release/git',
{
assets: ['CHANGELOG.md', 'package.json'],
message:
'[skip ci] chore: ${nextRelease.version} ${nextRelease.type} release\n\n${nextRelease.notes}',
},
],
// gitlab release 생성
[
'@semantic-release/gitlab',
{
// 별도의 gitlab을 사용하는 경우 url 정의
// gitlabUrl: 'https://gitlab.mycorp.com'
},
],
],
};
GitLab CI 연동
이제는 작성된 release 설정 정보를 이용하여 gitlab ci에 연동하고, 운영 환경 반영 작업이 이루어지는 devops 환경에서 자동 관리가 이루어지도록 연동해 보겠습니다.
[ 1. access token 생성 ]
access token은 gitlab pipeline이 동작하는 과정에서 작업되는 repository에 접근하고 commit 이력을 남기는 인증 정보로 사용됩니다.
"Settings > Access tokens" 메뉴에 접근하여 다음 권한 이상의 역할이 부여된 token을 새롭게 만들어 줍니다.
1. Role
- Maintainer
2. Scope
- api
- write_repository

[ 2. variable 등록 ]
access token을 생성할 때 확인할 수 있는 값을 복사하여 variable에 등록해 줍니다.
key 값은 "GITLAB_TOKEN" 으로 해줘야 하며 value에는 복사한 token 값을 넣어주면 됩니다.

[ 3. package.json script 추가 ]
// package.json
{
"scripts": {
"semantic-release": "semantic-release"
},
}
[ 4. gitlab-ci.yml 추가 ]
// gitlab-ci.yml
stages:
- release
# release 자동화
release:
image: node:20
stage: release
# git history 및 태그가 제대로 있도록 clone 전략 설정 (권장)
variables:
GIT_STRATEGY: clone
before_script:
- npm ci
script:
- npm run semantic-release
rules:
# main branch 에서만 동작
- if: '$CI_COMMIT_BRANCH == "main" && $CI_PIPELINE_SOURCE == "push"'
when: on_success
Release 자동화 테스트
모든 설정이 완료되었다면 다음과 같은 commit 메시지를 남겨보겠습니다.
git commit -m "feat: 새로운 기능이 추가되었어요 (jira-1234)"
그리고 push를 하게 되면 다음과 같은 pipeline이 동작하는 것을 확인할 수 있습니다.

올바르게 동작이 이루어졌다면 tag에 버전 정보가 남는 것을 볼 수 있습니다.

또한 프로젝트 폴더에 release 이력을 관리하는 CHANGELOG.md 파일이 생성되는 것을 볼 수 있습니다.
그리고 마지막으로 "Deploy > Releases"를 확인하면 다음처럼 md 파일에 남긴 정보들이 표시되는 것도 확인 가능합니다.

이상으로 semantic release로 자동 버전 관리에 대해 간단하게 알아보는 시간이었습니다.
읽어주셔서 감사합니다.
댓글