6 분 소요

들어가며

개발을 점점하다보면 Git을 잘 활용하고 잘 써야겠다는 생각이 든다. 그 중에서 특히 커밋 메시지버전 관리가 있다. 프로젝트 팀빌딩이 되면 우선적으로 우리는 규칙과 리포지토리를 만들어 기본세팅을 한다. 처음에는 규칙을 잘 지키려고 하지만 점점 시간이 지나면서 잘 지키지지 않는 게 현실이다. 특히 사이드 프로젝트를 할 때에 많이 느낀다. 커밋단위 및 PR단위 작은 단위로 하자고 했지만 지키지않고 그대로 진행할 때가 많다. 그 개발자가 잘못됐다는 건 아니다. 사람이 하는 작업이다보니 실수를 할 때에도 있고 자기도 모르게 개발에 몰두하면 본인이 원래해왔던 방식대로 하기도 마련이다. 그렇기 때문에 관리자가 없으면 규칙이 무너질 수 없다고 생각한다.

이 글에서 소개할 semantic-releasecommitlint 는 이러한 규칙들을 잡아주고 지켜주도록 도와주는 javascript 라이브러리이다.

설치

본격적으로 하기 전에 앞서 다음 항목들이 설치되어 있어야 한다.

  • node.js
  • npm 또는 yarn
  • git

아직 설치되지 않았다면 Node.js 공식 사이트Git 공식 사이트에서 다운로드하자.

설치 확인

node --version  # v20.x.x
npm --version   # v10.x.x
git --version   # v2.x.x

1. commitlint로 커밋 규칙 강제하기

우선 필자는 커밋 메시지 컨벤션으로 Conventional Commits 컨벤션을 따른다. 아래에는 공식사이트와 Conventional Commits의 기본 커밋 컨벤션이다.

Conventional Commits 공식 사이트
기본 컨벤션
<타입> : <설명>
ex) feat : 기능 추가

Semantic Release가 제대로 동작하려면 일관된 커밋 메시지 규칙이 필수다. Commitlint는 규칙을 지키지 않은 커밋을 아예 막아버린다. 프론트엔드 변경사항 없음은 무시하자. 현재 commitlint가 적용되어 있는 프로젝트에서 테스트 결과이다.

  • 실패 사례 커밋 실패 실패 이유를 간단히 설명하자면 test로만 입력을 하면 어떤 것이 타입인지 인지를 하지 못하여 비어있다고 인식하여 실패한다. 바로 다음으로 성공사례를 보자.
  • 성공 사례 커밋 성공 정상적으로 커밋메시지를 입력하면 별다른 문구없이 성공하는 것을 볼 수 있다.

그럼 이제 설치부터 적용까지 시켜보자.

패키지 설치

npm install --save-dev @commitlint/cli @commitlint/config-conventional husky

설치하는 패키지

  • @commitlint/cli: Commitlint 실행 도구
  • @commitlint/config-conventional: Conventional Commits 기본 규칙
  • husky: Git Hook을 쉽게 관리하는 도구

Commitlint 설정 파일 생성

module.exports = {
  extends: ["@commitlint/config-conventional"], //
  ignores: [(message) => message.startsWith("chore(release)")],
  rules: {
    "type-enum": [
      2,
      "always",
      [
        "feat", // 새로운 기능
        "fix", // 버그 수정
        "docs", // 문서 수정
        "style", // 코드 스타일 변경 (포맷팅 등)
        "refactor", // 리팩토링
        "test", // 테스트 추가/수정
        "chore", // 빌드, 설정 변경
        "perf", // 성능 개선
        "ci", // CI 설정
        "build", // 빌드 시스템
      ],
    ],
  },
};

설정 항목 설명

1. extends

extends: ["@commitlint/config-conventional"]
  • Conventional Commits 기본 규칙을 그대로 가져와서 사용한다.

2. ignores

ignores: [(message) => message.startsWith("chore(release)")];
  • 특정 커밋 메시지는 검증에서 제외
  • chore(release)로 시작하는 커밋은 검사 안 함
  • 왜 필요한가?
    추 후 적용시킬 Semantic Release가 자동으로 만드는 릴리즈 커밋(chore(release): 1.17.0)은 검증할 필요가 없기 때문이다.

3. rules

"type-enum": [2, "always", [...]]

규칙 형식: [level, applicable, value]

의미 설명
2 error 규칙 위반 시 커밋 거부
always 적용 방식 항상 이 규칙 적용
[...] 허용 값 사용 가능한 타입 목록

Level 옵션

  • 0: 비활성화 (규칙 안 씀)
  • 1: 경고 (커밋은 되지만 경고 표시)
  • 2: 에러 (커밋 거부)

더 많은 것들이 있지만 필자는 기본으로 충분하다고 느껴 기본 규칙만 가지고 사용하고 있다.
커스텀마이징을 하고 싶다면 공식 사이트를 찾아보아도 좋을 것 같다.

커밋 린트 공식 사이트

Husky로 Git Hook 연동

Commitlint를 설정했지만, 이것만으로는 작동하지 않는다. Git Hook에 연결해야 커밋할 때마다 자동으로 검증이 실행된다.

Git Hook을 쉽게 관리해주는 도구가 바로 Husky다.

Git Hook이란?

Git에서 특정 이벤트(커밋, 푸시 등)가 발생할 때 자동으로 실행되는 스크립트다.

개발자가 커밋 시도
    ↓
commit-msg Hook 실행  ← Husky가 여기서 Commitlint 실행
    ↓
통과하면 커밋 완료
실패하면 커밋 거부

Husky 초기화

npx husky init

이 명령어는:

  • .husky/ 폴더 생성
  • package.json에 prepare 스크립트 추가
  • Git Hook 기본 설정

실행 후 package.json을 확인하면 이렇게 추가되어 있다.

{
  "scripts": {
    "prepare": "husky"
  }
}

prepare 스크립트는 npm install 할 때 자동으로 실행되어 Husky를 활성화한다.

commit-msg Hook 생성

이제 커밋 메시지를 검증하는 Hook을 추가한다.

npx husky add .husky/commit-msg 'npx --no -- commitlint --edit $1'

또는 .husky/commit-msg 파일을 직접 생성해도 된다.

.husky/commit-msg 파일:

npx --no -- commitlint --edit $1

파일 생성 후 실행 권한을 부여한다.

chmod +x .husky/commit-msg

여기까지 진행을 했다면 이제 커밋 린트에 대한 설정은 끝이다.

2. Semantic Release 설정하기

Commitlint로 커밋 규칙을 강제했으니, 이제 이 규칙적인 커밋 메시지를 활용해 자동으로 버전을 관리해보자.

Semantic Release는 커밋 메시지를 분석해서

  • 버전 번호 자동 결정 (feat → minor, fix → patch)
  • package.json 자동 업데이트
  • CHANGELOG.md 자동 생성
  • Git Tag 자동 생성
  • GitHub Release 자동 발행

이 모든 걸 Semantic Release는 자동으로 처리해준다.

버전 결정 규칙

Semantic Release가 커밋 메시지를 보고 버전을 결정하는 방식

커밋 타입 버전 변경 예시
feat: minor 증가 1.0.0 → 1.1.0
fix: patch 증가 1.0.0 → 1.0.1
perf:, refactor: patch 증가 1.0.0 → 1.0.1
BREAKING CHANGE: major 증가 1.0.0 → 2.0.0

여러 커밋이 쌓였다면?

git commit -m "feat: 새 기능 A"      # minor
git commit -m "fix: 버그 수정"       # patch
git commit -m "feat: 새 기능 B"      # minor
git push origin main

→ 가장 높은 우선순위인 feat(minor)가 적용되어 1.0.0 → 1.1.0

패키지 설치

npm install --save-dev semantic-release @semantic-release/changelog @semantic-release/git

설치되는 플러그인

플러그인 역할 기본 포함
semantic-release 메인 패키지 -
@semantic-release/commit-analyzer 커밋 메시지 분석 ✅ (자동)
@semantic-release/release-notes-generator 릴리즈 노트 생성 ✅ (자동)
@semantic-release/npm package.json 업데이트 ✅ (자동)
@semantic-release/github GitHub Release 생성 ✅ (자동)
@semantic-release/changelog CHANGELOG.md 생성 ❌ (수동 설치)
@semantic-release/git Git 커밋/푸시 ❌ (수동 설치)

프로젝트 루트 package.json에 Semantic Release 설정을 추가한다.

{
  "name": "semantic-release-test",
  "version": "1.0.0",
  "scripts": {
    "prepare": "husky"
  },
  "devDependencies": {
    "@commitlint/cli": "^18.0.0",
    "@commitlint/config-conventional": "^18.0.0",
    "husky": "^9.1.7",
    "semantic-release": "^22.0.0",
    "@semantic-release/changelog": "^6.0.0",
    "@semantic-release/git": "^10.0.0"
  },
  "release": {
    "branches": ["main"],
    "plugins": [
      "@semantic-release/commit-analyzer",
      "@semantic-release/release-notes-generator",
      "@semantic-release/changelog",
      "@semantic-release/npm",
      "@semantic-release/github",
      [
        "@semantic-release/git",
        {
          "assets": ["CHANGELOG.md", "package.json"],
          "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
        }
      ]
    ]
  }
}

설정 항목 설명

branches

"branches": ["main"]
  • 어느 브랜치에서 릴리즈할지 지정
  • 보통 main 또는 master
  • 여러 브랜치 가능: ["main", "next", "beta"]

plugins (실행 순서대로)

"plugins": [
  // 1단계: 커밋 분석
  "@semantic-release/commit-analyzer",

  // 2단계: 릴리즈 노트 생성
  "@semantic-release/release-notes-generator",

  // 3단계: CHANGELOG 파일 생성/업데이트
  "@semantic-release/changelog",

  // 4단계: package.json 버전 업데이트
  "@semantic-release/npm",

  // 5단계: GitHub Release 생성
  "@semantic-release/github",

  // 6단계: Git에 커밋 & 푸시
  ["@semantic-release/git", { ... }]
]

@semantic-release/git 상세 설정

{
  "assets": ["CHANGELOG.md", "package.json"],
  "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}
옵션 설명
assets Git에 커밋할 파일 목록
message 릴리즈 커밋 메시지 템플릿
${nextRelease.version} 새 버전 번호 (예: 1.17.0)
[skip ci] CI/CD 무한 루프 방지
${nextRelease.notes} 릴리즈 노트 내용

이제 마지막 단계로 GitHub Actions에서 이걸 자동으로 실행하도록 설정해보자.

3. GitHub Actions 워크플로우 구성

이제 마지막 단계다. GitHub Actions를 설정해서 main 브랜치에 푸시할 때마다 자동으로 Semantic Release가 실행되도록 만들자.

GitHub Actions란?

GitHub에서 제공하는 CI/CD 도구로, 코드가 푸시되거나 PR이 생성될 때 자동으로 작업을 실행할 수 있다.

우리가 만들 워크플로우: main 브랜치에 푸시 ↓ GitHub Actions 자동 트리거 ↓ Semantic Release 실행 ↓ 버전 업데이트, Tag 생성, Release 발행, CHANGELOG.md 파일 생성

프로젝트 루트에 .github/workflows/release.yml 파일을 생성한다.

mkdir -p .github/workflows
touch .github/workflows/release.yml

워크플로우 설정

.github/workflows/release.yml 파일에 다음 내용을 작성한다.

name: Release

on:
  push:
    branches:
      - main

jobs:
  release:
    runs-on: ubuntu-latest
    permissions:
      contents: write
      issues: write
      pull-requests: write

    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "20"

      - name: Install dependencies
        run: npm install

      - name: Run Semantic Release
        env:
          GITHUB_TOKEN: $
        run: npx semantic-release
  • 환경변수 GITHUB_TOKEN: GitHub에서 자동으로 제공
  • 별도 설정 불필요 GitHub이 자동으로 토큰 제공

이렇게 작성후 저장한 뒤 메인에 푸쉬를 하면 우리가 해주어야 할 것은 끝이 났다.
마지막으로 실제 동작을 확인 해보자.

실제 동작 확인

main 브랜치에 푸시하는 순간 Github Actions가 자동으로 트리거된다.

저장소 Actions 탭에서 실행 과정을 실시간으로 확인할 수 있다. github actions

정상적으로 완료가 되면 새로운 릴리즈와 컨트리뷰터 버전, 태그가 생성된걸 알 수 있다. github contributors

마무리

사람은 완벽하지 않다. 바쁘면 실수하고, 급하면 규칙을 건너뛰고, 피곤하면 대충 한다.
이건 개발자가 잘못된 게 아니다. 사람이니까 당연한 거다.

단 한번의 설정과 작은 규칙 하나가 만든 변화

// commitlint.config.js
'type-enum': [2, 'always', ['feat', 'fix', ...]]

이 단순한 규칙 하나가

  • 커밋 메시지를 일관되게 만들고
  • Semantic Release가 버전을 분석할 수 있게 하고
  • CHANGELOG가 자동 생성되게 하고
  • Tag와 Release가 자동으로 만들어지게 하고
  • 결국 우리의 개발 경험을 완전히 바꿨다.

이 글을 읽고 있다면 한 번 적용시켜보는 것을 추천한다.

참고 자료

댓글남기기