Changesets로 모노레포 패키지 릴리즈 관리하기

작성일:2026.04.27|조회수:2

Changesets로 모노레포 패키지 릴리즈 관리하기

처음 fluo의 패키지를 배포할 때는 모든 과정을 수동으로 처리했다. 이후 GitHub Actions를 도입하면서 실행 과정은 어느 정도 자동화했지만, 여전히 중요한 판단은 사람의 기억에 남아 있었다.

자동화된 것은 배포 명령 실행뿐이었다. 어떤 패키지를 올릴지, 의존 패키지 버전은 어떻게 맞출지, CHANGELOG에는 무엇을 남길지는 여전히 사람이 판단해야 했다.

패키지가 대여섯 개라면 감당할 수 있었을지도 모른다. 하지만 이 글을 쓰는 시점에 fluo의 패키지는 39개다. 배포 대상에서 빠지는 패키지가 생기고, 버전 불일치가 발생하고, CHANGELOG는 “bump version” 같은 의미 없는 기록으로 채워지기 시작했다.

그래서 fluo에 Changesets를 도입했다.

Changesets가 해결하는 문제

Changesets의 핵심은 버전 결정을 배포 직전이 아니라 PR 시점으로 앞당기는 것이다. 기능을 추가하거나 버그를 고칠 때, 이 변경이 어떤 패키지에 영향을 주는지, semver 기준으로 major, minor, patch 중 무엇인지 함께 기록한다. 이 기록은 .changeset/{slug}.md 파일로 저장되고, 나중에 버전 bump와 CHANGELOG 생성의 입력이 된다.

즉, 개발자는 변경 의도를 선언하고, Changesets는 반복 작업을 처리한다.

배포 시점에는 더 이상 기억을 더듬을 필요가 없다. PR과 함께 쌓인 changeset 파일을 기준으로 릴리즈가 진행된다.

설치와 초기화

SH
pnpm add -D @changesets/cli
pnpm changeset init

초기화하면 .changeset/ 폴더와 config.json이 생긴다. 별도 서버나 외부 서비스는 필요 없다.

Changesets는 워크스페이스 루트의 패키지 목록을 읽는다. pnpm 기준으로 pnpm-workspace.yamlpackages가 올바르게 정의되어 있어야 한다. 확인은 다음 명령으로 할 수 있다.

SH
pnpm changeset status

패키지 목록이 비어 있다면 Changesets 문제가 아니라 워크스페이스 설정 문제일 가능성이 높다.

알아야 할 파일 세 가지

1. .changeset/{slug}.md

가장 자주 다루는 파일이다.

MD
---
"@fluojs/http": minor
"@fluojs/core": patch
---

Add `@Route` prefix support for controller-level path prefixes.

Patch `@fluojs/core` to align internal metadata store with the new routing contract.

프론트매터에는 어떤 패키지를 어떤 수준으로 올릴지 적고, 본문에는 CHANGELOG에 들어갈 설명을 쓴다. 파일명은 자동 생성되며 중요하지 않다.

2. .changeset/config.json

Changesets 설정 파일이다.

JSON
{
  "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
  "changelog": "@changesets/cli/changelog",
  "commit": false,
  "fixed": [],
  "linked": [],
  "access": "restricted",
  "baseBranch": "main",
  "updateInternalDependencies": "patch",
  "ignore": ["@fluojs/playground"]
}

자주 보는 값은 이 정도다.

3. .changeset/pre.json

pre-release 모드일 때만 생긴다. 예를 들어 beta 모드에 들어가면 이런 파일이 만들어진다.

JSON
{
  "mode": "pre",
  "tag": "beta",
  "initialVersions": {
    "@fluojs/core": "0.9.0",
    "@fluojs/http": "0.4.2"
  },
  "changesets": []
}

이 파일이 있으면 1.0.0-beta.1, 1.0.0-beta.2 같은 버전이 생성된다. 없으면 일반 릴리즈 모드다.

릴리즈 흐름

TXT
1. 기능 개발
   └─ pnpm changeset 실행
      └─ .changeset/some-slug.md 생성

2. PR 오픈 후 main 머지

3. GitHub Actions 실행
   └─ changesets/action이 changeset 파일 감지
      └─ "Version Packages" PR 생성 또는 업데이트
         ├─ package.json 버전 bump
         └─ CHANGELOG.md 갱신

4. "Version Packages" PR 머지

5. GitHub Actions 다시 실행
   └─ npm publish
      ├─ 변경된 패키지만 publish
      ├─ GitHub Release 생성
      └─ git 태그 생성

개발자가 직접 하는 일은 보통 두 가지다.

  1. PR에 changeset 파일을 포함한다.
  2. 릴리즈할 준비가 됐을 때 "Version Packages" PR을 머지한다.

"Version Packages" PR은 github-actions[bot]이 자동으로 열고 유지한다. main에 changeset 파일이 추가될 때마다 PR도 갱신된다. 이 PR을 보면 어떤 패키지가 얼마나 올라가는지, CHANGELOG에 어떤 내용이 들어가는지 미리 확인할 수 있다.

GitHub Actions 연동

fluo에서는 changesets/action을 사용해 릴리즈를 자동화한다.

YAML
name: Release

on:
  push:
    branches:
      - main

jobs:
  release:
    name: Release
    runs-on: ubuntu-latest
    permissions:
      contents: write
      pull-requests: write
      id-token: write

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

      - uses: pnpm/action-setup@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: pnpm
          registry-url: https://registry.npmjs.org

      - run: pnpm install --frozen-lockfile

      - name: Create Release Pull Request or Publish
        uses: changesets/action@v1
        with:
          publish: pnpm release
          version: pnpm version-packages
          title: "chore: version packages"
          commit: "chore: version packages"
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

이 워크플로우는 두 상황을 나눠 처리한다.

permissions도 중요하다.

package.json에는 보통 이렇게 둔다.

JSON
{
  "scripts": {
    "release": "pnpm build && changeset publish",
    "version-packages": "changeset version"
  }
}

fetch-depth: 0도 빼지 않는 게 좋다. Changesets가 태그를 만들거나 이전 상태를 비교할 때 전체 git 히스토리가 필요할 수 있다.

changeset 파일 만들기

SH
pnpm changeset

CLI는 대략 이런 흐름으로 진행된다.

TXT
Which packages would you like to include?
Which packages should have a major bump?
Which packages should have a minor bump?
Please enter a summary for this change.

끝나면 .changeset/ 아래에 Markdown 파일이 생긴다. 이 파일을 코드 변경과 함께 커밋하면 된다.

SH
git add .changeset/curvy-buses-lie.md
git commit -m "feat(http): add timeout option to HTTP client"

직접 작성해도 된다.

MD
---
"@fluojs/cli": minor
"@fluojs/platform-fastify": patch
---

CLI에 `--watch` 플래그를 추가했다. 기존 명령어는 그대로 동작하며, `--watch`는 선택적으로 사용할 수 있다.

`platform-fastify` 어댑터는 watch 모드에서 서버를 재시작하지 않고 라우트만 다시 등록하도록 수정했다.

changeset 본문은 그대로 CHANGELOG에 들어간다. 그래서 커밋 메시지처럼 쓰기보다, 사용자가 읽을 릴리즈 노트라고 생각하고 쓰는 편이 좋다.

Semver 고르는 기준

기준은 단순하게 잡는 게 좋다.

헷갈리면 일단 patch로 두고 PR 리뷰에서 조정한다. "Version Packages" PR이 머지되기 전에는 changeset 파일을 수정하면 된다.

0.x 단계에서는 breaking change를 보통 minor로 처리한다. 1.0.0부터 semver 계약을 본격적으로 지키겠다는 의미가 강해지기 때문이다. Changesets를 쓰면 이 판단이 PR마다 드러난다. 이 패키지가 stable API를 약속할 준비가 됐는지 자연스럽게 고민하게 된다.

내부 의존성 전파

Changesets를 쓰면서 가장 체감이 큰 부분이다.

TXT
packages/
├── core/               @fluojs/core               v1.2.0
├── http/               @fluojs/http               v0.5.0  (core에 의존)
└── platform-fastify/   @fluojs/platform-fastify   v0.3.0  (core, http에 의존)

@fluojs/core에만 minor changeset을 만들고 changeset version을 실행하면 다음처럼 전파된다.

TXT
@fluojs/core             1.2.0 → 1.3.0   (minor, 직접 선언)
@fluojs/http             0.5.0 → 0.5.1   (patch, 자동 전파)
@fluojs/platform-fastify 0.3.0 → 0.3.1   (patch, 자동 전파)

config.json의 설정 때문이다.

JSON
{
  "updateInternalDependencies": "patch"
}

결과적으로 의존 패키지의 package.json도 함께 바뀐다.

DIFF
- "version": "0.5.0",
+ "version": "0.5.1",
  "dependencies": {
-   "@fluojs/core": "workspace:^1.2.0"
+   "@fluojs/core": "workspace:^1.3.0"
  }

상위 패키지가 올라갔으니, 그 패키지를 의존하는 downstream 패키지도 최소 patch로 올라간다. 사람이 의존 그래프를 따라가며 하나씩 수정할 필요가 없다.

fixedlinked도 있다. fixed는 여러 패키지를 항상 같은 버전으로 묶고, linked는 bump 수준만 맞춘다. 하지만 대부분의 경우에는 updateInternalDependencies: "patch"만으로 충분했다.

CHANGELOG 생성 방식

changeset version을 실행하면 각 패키지의 CHANGELOG.md가 갱신된다.

MD
# @fluojs/http

## 0.5.1

### Patch Changes

- Updated dependencies
  - @fluojs/core@1.3.0

## 0.5.0

### Minor Changes

- abc1234: Add timeout option to HTTP client

  `HttpClient` 생성자에 `timeout` 옵션이 추가됐다.

  ```ts
  const client = new HttpClient({ timeout: 5000 });
MD

CHANGELOG는 커밋 단위가 아니라 changeset 파일 단위로 생성된다. 한 PR에 커밋이 열 개 있어도, CHANGELOG에는 changeset 본문만 들어간다. 커밋 메시지는 개발자를 위한 기록이고, CHANGELOG는 패키지 사용자를 위한 문서이기 때문이다.

자동 생성된 항목에 붙는 짧은 커밋 해시는 changeset 파일이 추가된 커밋을 가리킨다. 나중에 변경 출처를 추적할 때 유용하다.

CHANGELOG를 직접 수정하기보다 changeset 본문을 잘 쓰는 편이 낫다. 다음 실행 때 자동 생성 결과로 덮일 수 있기 때문이다.

<p></p>

## Pre-release 모드

`.changeset/pre.json`이 있으면 pre-release 모드다. 이 모드에서는 버전이 `1.0.0-beta.1`, `1.0.0-beta.2`처럼 올라간다.

pre-release는 주로 두 상황에서 쓴다.

1. 큰 breaking change를 stable 전에 먼저 배포하고 싶을 때
2. 처음 공개하는 패키지를 beta로 배포하고 싶을 때

![](https://ayden-blog-assets.s3.ap-northeast-2.amazonaws.com/prod/uploads/inline/1777295319563-39a89682-7d1e-42e3-ae45-c70b58fd03b2-___________2026-04-27______10.07.03.png)

진입과 탈출은 명령으로 처리한다.

```bash
pnpm changeset pre enter beta
pnpm changeset pre exit

흐름은 다음과 같다.

TXT
v1.0.0-beta.1
v1.0.0-beta.2
v1.0.0-beta.3
v1.0.0

beta 대신 alpha, rc 같은 태그도 쓸 수 있다.

pre-release 모드에서 주의할 점이 있다. "Version Packages" PR이 머지되어도 .changeset/*.md 파일이 바로 삭제되지 않는다. 버그가 아니라 의도된 동작이다.

처리된 changeset slug는 pre.jsonchangesets 배열에 기록된다. 이렇게 해야 beta 사이클이 끝난 뒤 stable로 전환할 때 같은 changeset이 중복 반영되지 않는다. pre exit 후 stable 릴리즈가 만들어질 때 changeset 파일과 pre.json이 정리된다.

자주 생기는 혼란

changeset 파일을 빠뜨리고 PR을 머지했다

다음 PR에서 해당 변경을 포함해 changeset을 만들면 된다. Changesets는 커밋이 아니라 .changeset/*.md 파일을 기준으로 동작한다.

Version Packages PR이 열려 있는데 새 changeset을 추가했다

괜찮다. 봇이 기존 PR을 자동으로 업데이트한다.

minor로 만들었는데 major가 필요했다

publish 전이라면 changeset 파일의 프론트매터를 수정하면 된다.

DIFF
- "@fluojs/http": minor
+ "@fluojs/http": major

이미 publish됐다면 기존 버전은 수정할 수 없다. 다음 버전에서 정정해야 한다.

한 PR에 changeset 파일이 여러 개 있어도 되나

된다. 서로 다른 CHANGELOG 항목으로 남기고 싶은 변경이 있다면 여러 개를 만들 수 있다. 같은 패키지에 여러 changeset이 있으면 가장 높은 bump 수준이 적용된다.

squash merge를 써도 되나

된다. Changesets는 main 브랜치에 changeset 파일이 있는지만 본다. merge 방식은 중요하지 않다.

Version Packages PR을 닫았더니 다시 열렸다

main에 처리되지 않은 changeset 파일이 남아 있으면 봇은 다시 PR을 만든다. 릴리즈를 미루고 싶다면 PR을 닫기보다 열어두거나 draft로 두는 편이 낫다.

changeset versionchangeset publish의 차이

CI에서는 먼저 changeset version으로 Version Packages PR을 만들고, 그 PR이 머지된 뒤 changeset publish를 실행한다.

릴리즈 검증

fluo에서는 publish 전에 pnpm verify:release-readiness를 실행한다. 이 검증은 대략 다음을 확인한다.

로컬에서도 실행할 수 있다.

SH
pnpm verify:release-readiness
pnpm generate:release-readiness-drafts

새 패키지를 추가했거나 패키지 구조를 바꿨다면 CI 전에 한 번 돌려보는 게 좋다.

명령어 요약

일상적으로 직접 쓰는 명령은 많지 않다.

SH
# PR 작업 중 changeset 파일 만들기
pnpm changeset

# 대기 중인 changeset과 예정 bump 확인
pnpm changeset status

# beta pre-release 진입
pnpm changeset pre enter beta

# pre-release 탈출
pnpm changeset pre exit

# 릴리즈 준비 검증
pnpm verify:release-readiness

version-packagesrelease는 보통 CI가 실행한다.

JSON
{
  "scripts": {
    "version-packages": "changeset version",
    "release": "pnpm build && changeset publish"
  }
}

정리

Changesets 워크플로우를 한 줄로 줄이면 이렇다.

변경할 때 선언하고, 머지할 때 버전을 올리고, CI가 배포한다.

fluo처럼 패키지가 39개까지 늘어나면 수동 릴리즈는 금방 한계에 부딪힌다. GitHub Actions로 명령 실행을 자동화해도, 어떤 패키지를 올릴지와 어떤 내용을 CHANGELOG에 남길지는 여전히 사람이 기억해야 한다.

Changesets는 그 판단을 PR 시점으로 옮긴다. 코드를 작성한 사람이 변경 영향과 semver 수준을 기록하고, 리뷰어는 코드와 함께 그 판단을 검토한다. 이후 버전 bump, 내부 의존성 전파, CHANGELOG 생성, npm publish, GitHub Release는 자동화된다.

처음에는 changeset 파일 하나 더 만드는 일이 번거롭게 느껴질 수 있다. 하지만 몇 번 릴리즈를 돌려보면 이 파일이 단순한 절차가 아니라 릴리즈 품질을 지키는 장치라는 걸 알게 된다. 사용자가 CHANGELOG를 읽고 업그레이드 여부를 판단할 수 있다면, Changesets는 제 역할을 하고 있는 것이다.

더 읽어보기

  • 2026.02.02

    Git Worktree

    Git으로 여러 작업을 병렬로 진행하다 보면 브랜치 전환 자체가 비용이 되는 순간이 잦다. 기능 개발 중에 긴급 핫픽스를 처리해야 하거나, PR 리뷰를 위해 다른 브랜치를 실행해 봐야 하는 상황에서 git checkout과 stash를 반복하는 방식은 작업 흐름을 지속적으로 끊는다. 이…

  • 2025.11.05

    Steiger

    FSD(Feature-Sliced Design)는 프론트엔드 아키텍처를 장기적으로 유지하기에 매우 유용한 방법론이다. 레이어와 슬라이스를 명확히 구분하고 의존 방향을 제한함으로써, 프로젝트 규모가 커질수록 코드베이스의 복잡도를 구조적으로 통제할 수 있다. 다만 이 장점은 동시에 부담이…

  • 2025.08.16

    pnpm과 terborepo로 시작하는 모노레포

    최근 개발 현장에서는 여러 프로젝트를 하나의 저장소에서 관리하는 모노레포 구조가 빠르게 확산되고 있다. 모노레포는 여러 패키지를 한 곳에서 관리하기 때문에 공통 코드 재사용이 쉽고, 여러 팀이 동시에 작업하더라도 의존성과 변경 이력을 일관되게 관리할 수 있다는 장점이 있다. 반면, 패키…

  • 2025.02.01

    Pandas

    pandas pandas는 표 형식 데이터를 다루기 위한 파이썬의 대표 도구다. 핵심은 “데이터를 표로 생각한다”는 관점이고, 이를 Series(열)와 DataFrame(표)로 모델링한다. list와 dict로도 데이터를 다룰 수 있지만, 컬럼 단위 연산과 결측치 처리, 그룹화 같은 작…

  • 2026.04.23

    너만의 월남쌈을 싸

    이 포스트의 제목은 유튜브 영상 <너만의 월남쌈을 싸 - 아이네 INE>에서 따왔다. 4월 초부터 @ilokesto 네임스페이스에 속한 거의 모든 라이브러리를 리뉴얼 하고 있다. 이미 deprecated 처리는 끝났고, 새로운 기준 위에서 라이브러리의 구조를 재정의하는 단계를 거치고 있…

  • 2026.04.17

    Code Server

    처음 코드 서버를 만들었던 건 아마도 3년쯤 전의 일이다. 맥미니만 있던 탓에 밖에서 개발하는 게 쉽지 않았고, 아이패드로 언제 어디서든 개발을 하고 싶었던 끝에 찾아낸 해결책이었다. 다행히 집에는 Synology NAS가 있었고, Docker를 통해 어렵지 않게 코드 서버를 만들 수…

  • 2026.04.11

    Trie 자료구조

    문자열 데이터를 다룰 때 단순히 “이 단어가 있나?”만으로는 부족한 순간이 있다. 자동 완성처럼 특정 접두사로 시작하는 후보를 모아야 할 때도 있고, 어떤 키에 값을 두고 빠르게 찾고 싶을 때도 있다. 이럴 때 가장 자연스럽게 떠올릴 수 있는 자료구조가 바로 Trie이다. Trie는 무…

  • 2026.03.19

    Streams API 부록 2. 왜 이미지는 위에서 아래로 나타날까

    웹 페이지에서 이미지를 로드할 때 흥미로운 장면을 종종 볼 수 있다. 이미지가 한 번에 완전히 나타나는 것이 아니라, 위에서 아래로 조금씩 채워지면서 나타나는 경우가 있기 때문이다. 특히 네트워크가 느리거나 이미지가 큰 경우 이런 현상이 더 분명하게 보인다. 마치 이미지가 위쪽부터 스캔…

댓글

댓글을 불러오는 중...