
이 글은 Changesets로 모노레포 릴리즈를 관리하는 시리즈의 3편이다. 1편에서는 changeset 파일을 작성하는 방법을, 2편에서는 GitHub Actions로 Version Packages PR과 publish를 자동화하는 흐름을 다뤘다. 이번 글에서는 실제 운영 중 자주 헷갈리는 내부 의존성 전파, CHANGELOG 생성, pre-release, 릴리즈 Q&A를 정리한다.
- 1편: Changesets로 모노레포 버전 관리 시작하기
- 2편: Changesets와 GitHub Actions로 릴리즈 자동화하기
- 3편: Changesets 실무 운영 가이드: 내부 의존성, prerelease, 운영 Q&A
Changesets를 도입하면 처음에는 changeset 파일을 만드는 일이 가장 크게 느껴진다. 하지만 모노레포에서 진짜 체감이 큰 부분은 운영 단계에서 드러난다. 어떤 패키지 하나의 버전이 올라갔을 때 그 패키지에 의존하는 다른 패키지는 어떻게 되는지, CHANGELOG는 어떤 단위로 생성되는지, beta 릴리즈를 시작하면 changeset 파일은 왜 바로 사라지지 않는지 같은 질문들이 생긴다.
이 글은 그런 질문들을 한곳에 모아 둔 운영 가이드다. Changesets를 “설치해서 한 번 써 보는 단계”를 지나, 여러 패키지를 실제로 publish하는 워크플로우에 올렸을 때 필요한 기준을 다룬다.
내부 의존성 전파
모노레포에서 Changesets를 쓰면서 가장 크게 체감하는 기능은 내부 의존성 전파다. 예를 들어 다음과 같은 패키지 구조가 있다고 하자.
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을 만들었다면, 직접 변경된 패키지는 core 하나다. 하지만 http와 platform-fastify는 core를 의존하고 있다. publish된 패키지 관점에서는 의존성 범위와 package version이 함께 맞아야 한다.
updateInternalDependencies를 patch로 두면 Changesets는 이런 상황에서 downstream 패키지를 patch로 따라 올린다.
{
"updateInternalDependencies": "patch"
}changeset version을 실행하면 결과는 대략 다음처럼 된다.
@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, 자동 전파)이때 downstream 패키지의 package.json도 함께 바뀐다.
- "version": "0.5.0",
+ "version": "0.5.1",
"dependencies": {
- "@fluojs/core": "workspace:^1.2.0"
+ "@fluojs/core": "workspace:^1.3.0"
}이 자동 전파가 없으면 사람이 의존 그래프를 따라가며 어떤 패키지를 patch로 올릴지 직접 판단해야 한다. 패키지가 몇 개 없을 때는 가능하지만, 수십 개로 늘어나면 릴리즈 누락이 쉽게 생긴다. Changesets는 이 판단을 설정과 그래프 기반으로 일관되게 처리해 준다.
fixed와 linked는 언제 볼까
대부분의 독립 패키지 모노레포에서는 updateInternalDependencies: "patch"만으로 충분하다. 하지만 모든 패키지를 같은 버전으로 맞추거나, 여러 패키지의 bump 수준을 함께 관리해야 하는 경우에는 fixed와 linked를 검토할 수 있다.
fixed: 묶인 패키지들을 항상 같은 버전으로 유지한다.linked: 버전 숫자를 항상 같게 만들지는 않지만, bump 수준을 연결한다.
예를 들어 여러 패키지가 사실상 하나의 제품처럼 움직이고 사용자가 같은 버전으로 설치하기를 기대한다면 fixed가 맞을 수 있다. 반대로 패키지는 독립적으로 버전을 갖되 특정 그룹의 변경 수준을 함께 맞추고 싶다면 linked를 검토할 수 있다.
다만 처음 도입할 때부터 이 설정을 넣을 필요는 없다. 설정이 복잡해질수록 Version Packages PR을 읽는 비용도 늘어난다. 먼저 기본 설정으로 운영해 보고, 실제로 버전 정책상 묶어야 하는 패키지가 생겼을 때 도입하는 편이 낫다.
CHANGELOG 생성 방식
changeset version을 실행하면 각 패키지의 CHANGELOG.md가 갱신된다. CHANGELOG는 커밋 단위가 아니라 changeset 파일 단위로 만들어진다. 한 PR에 커밋이 열 개 있어도 changeset 파일이 하나라면 CHANGELOG 항목도 하나가 된다.
# @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` 옵션이 추가됐다.
const client = new HttpClient({ timeout: 5000 });여기서 짧은 커밋 해시는 changeset 파일이 추가된 커밋을 가리킨다. 나중에 변경 출처를 추적할 때 유용하다.
중요한 점은 CHANGELOG를 직접 다듬는 것보다 changeset 본문을 잘 쓰는 편이 낫다는 것이다. 자동 생성된 파일을 수동으로 고쳐도 다음 changeset version 실행에서 다시 바뀔 수 있다. 사용자에게 보여 줄 문장은 changeset 파일을 작성할 때부터 신경 쓰는 편이 안전하다.
Pre-release 모드
pre-release는 큰 breaking change를 stable 전에 먼저 배포하거나, 처음 공개하는 패키지를 beta로 배포하고 싶을 때 사용한다. Changesets에서는 다음 명령으로 pre-release 모드에 들어간다.
pnpm changeset pre enter beta그러면 .changeset/pre.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 같은 버전이 만들어진다.
v1.0.0-beta.1
v1.0.0-beta.2
v1.0.0-beta.3
v1.0.0pre-release를 끝내고 stable 릴리즈로 돌아가려면 다음 명령을 사용한다.
pnpm changeset pre exitpre-release 모드에서 자주 헷갈리는 점이 하나 있다. Version Packages PR이 머지되어도 .changeset/*.md 파일이 바로 삭제되지 않을 수 있다. 이것은 버그가 아니라 의도된 동작이다. 처리된 changeset slug가 pre.json의 changesets 배열에 기록되어야 beta 사이클이 끝난 뒤 stable로 전환할 때 같은 changeset이 중복 반영되지 않는다.
자주 생기는 혼란
changeset 파일을 빠뜨리고 PR을 머지했다
다음 PR에서 해당 변경을 포함해 changeset을 만들면 된다. Changesets는 커밋 diff를 자동으로 해석해서 릴리즈 노트를 만드는 도구가 아니라, .changeset/*.md 파일을 기준으로 동작하는 도구다. 이미 main에 들어간 코드라도 아직 publish되지 않았다면 다음 changeset에 설명을 보강할 수 있다.
다만 이 방식이 반복되면 어떤 PR의 변경을 어떤 changeset이 설명하는지 흐려진다. 가능하면 코드 변경과 changeset 파일을 같은 PR에 넣는 것을 기본 규칙으로 두는 편이 좋다.
Version Packages PR이 열려 있는데 새 changeset을 추가했다
괜찮다. main에 새 changeset 파일이 들어오면 봇이 기존 Version Packages PR을 업데이트한다. 릴리즈를 아직 하지 않았다면 하나의 릴리즈 PR 안에 변경사항이 계속 쌓인다.
이 동작은 릴리즈를 모아서 할 때 편하다. 다만 릴리즈 대상을 엄격하게 나눠야 한다면 changeset 파일이 main에 들어가는 순서를 조절해야 한다.
patch와 minor changeset이 같이 있을 때 patch만 먼저 릴리즈할 수 있나
가능하다. 다만 Changesets가 자동으로 “patch만 골라서 릴리즈” 버튼을 제공하는 것은 아니다. Version Packages PR은 main에 남아 있는 모든 changeset을 기준으로 만들어지기 때문에, patch PR 5개와 minor PR 2개가 모두 main에 머지되어 있다면 기본적으로는 minor까지 포함한 릴리즈 PR이 생성된다.
patch 버전만 먼저 올리고 싶다면 minor changeset을 아직 main에 넣지 않는 방식으로 릴리즈 대상을 분리하는 게 가장 단순하다. 예를 들어 patch PR 5개를 먼저 머지해서 patch 릴리즈를 진행하고, minor PR 2개는 그 다음에 머지하거나 별도 브랜치에 잠시 보류한다. 이미 minor changeset이 main에 들어갔다면 publish 전에 해당 changeset 파일을 되돌리거나 다른 브랜치로 빼서 Version Packages PR에서 제외해야 한다.
즉, Changesets의 릴리즈 단위는 “main에 남아 있는 changeset 파일 집합”이다. patch만 먼저 배포하고 싶다면 changeset 파일이 main에 들어가는 순서를 조절해야 한다.
minor로 만들었는데 major가 필요했다
publish 전이라면 changeset 파일의 프론트매터를 수정하면 된다.
- "@fluojs/http": minor
+ "@fluojs/http": major이미 publish됐다면 기존 버전은 수정할 수 없다. npm에 올라간 버전은 불변으로 보는 편이 안전하다. 다음 버전에서 정정하고, 필요하다면 CHANGELOG나 문서에서 잘못된 릴리즈 의도를 설명해야 한다.
한 PR에 changeset 파일이 여러 개 있어도 되나
된다. 서로 다른 CHANGELOG 항목으로 남기고 싶은 변경이 있다면 여러 개를 만들 수 있다. 같은 패키지에 여러 changeset이 있으면 가장 높은 bump 수준이 적용된다.
예를 들어 한 PR에 다음 두 changeset이 있다고 하자.
---
"@fluojs/http": patch
---
Fix route normalization.---
"@fluojs/http": minor
---
Add timeout option to HTTP client.최종적으로 @fluojs/http는 minor로 올라간다. CHANGELOG에는 두 항목이 각각 남을 수 있다.
squash merge를 써도 되나
된다. Changesets는 main 브랜치에 changeset 파일이 있는지만 본다. merge commit인지, squash merge인지, rebase merge인지는 핵심이 아니다. 중요한 것은 최종적으로 main에 .changeset/*.md 파일이 들어왔는가다.
Version Packages PR을 닫았더니 다시 열렸다
main에 처리되지 않은 changeset 파일이 남아 있으면 봇은 다시 PR을 만든다. 릴리즈를 미루고 싶다면 PR을 닫기보다 열어두거나 draft로 두는 편이 낫다. 닫는 것은 “릴리즈하지 않겠다”는 신호가 아니라 “현재 PR을 닫겠다”에 가깝고, changeset 파일이 남아 있는 한 action은 다시 릴리즈 PR을 만들 수 있다.
changeset version과 changeset publish의 차이
두 명령은 이름이 비슷하지만 책임이 다르다.
changeset version: 버전 bump와 CHANGELOG 갱신changeset publish: npm publish 실행
CI에서는 먼저 changeset version으로 Version Packages PR을 만들고, 그 PR이 머지된 뒤 changeset publish를 실행한다. 이 둘을 한 번에 실행하면 릴리즈 검토 지점이 사라질 수 있으므로, GitHub Actions에서는 PR 기반 흐름으로 나누는 편이 좋다.
릴리즈 검증
실제 publish 전에 검증 단계를 두는 것이 좋다. fluo에서는 publish 전에 pnpm verify:release-readiness를 실행해 릴리즈 준비 상태를 확인한다.
검증은 대략 다음 항목을 본다.
- 빌드, 타입체크, 전체 테스트 통과
- CLI E2E 검증
- 문서의 패키지 목록과 실제 패키지 폴더 일치 여부
- 공개 패키지가 내부 패키지를
workspace:^로 참조하는지 - LICENSE 파일 존재 여부
- CHANGELOG 구조 적합성
로컬에서도 다음처럼 실행할 수 있다.
pnpm verify:release-readiness
pnpm generate:release-readiness-drafts새 패키지를 추가했거나 패키지 구조를 바꿨다면 CI 전에 한 번 돌려보는 편이 좋다. 릴리즈 실패는 일반 테스트 실패보다 복구 비용이 크다. 특히 npm publish는 한 번 올라간 버전을 되돌릴 수 없다고 생각하고 운영하는 것이 안전하다.
운영 기준 정리
Changesets를 실무에서 오래 쓰려면 몇 가지 기준을 정해 두는 편이 좋다.
- 코드 변경과 changeset 파일은 같은 PR에 넣는다.
- Version Packages PR은 닫기보다 열어두거나 draft로 둔다.
- CHANGELOG를 직접 고치기보다 changeset 본문을 잘 쓴다.
- patch만 먼저 배포해야 한다면 changeset 파일이 main에 들어가는 순서를 조절한다.
- pre-release 모드에서는
.changeset/pre.json의 존재를 항상 의식한다. - publish 전에는 빌드, 타입체크, 테스트, 패키지 구조 검증을 통과시킨다.
도구가 릴리즈를 자동화해 주더라도, 릴리즈 정책 자체가 없어지지는 않는다. Changesets는 그 정책을 파일과 PR로 드러나게 해 주는 도구에 가깝다. 어떤 변경이 어떤 버전으로 나갈지, 어떤 문장으로 사용자에게 설명될지, 어떤 패키지가 함께 publish될지를 리뷰 가능한 형태로 만드는 것이 핵심이다.
정리
Changesets는 간단히 시작할 수 있지만, 모노레포 운영에서는 꽤 많은 문제를 대신 정리해 준다. 내부 의존성 전파는 패키지 간 버전 불일치를 줄이고, CHANGELOG 생성은 사용자에게 읽을 만한 릴리즈 노트를 제공한다. pre-release 모드는 큰 변경을 안정적으로 테스트할 수 있는 경로를 만들고, Version Packages PR은 publish 직전의 검토 지점을 제공한다.
결국 Changesets 워크플로우를 한 줄로 줄이면 이렇다.
변경할 때 선언하고, 머지할 때 버전을 올리고, CI가 배포한다.
처음에는 changeset 파일 하나 더 만드는 일이 번거롭게 느껴질 수 있다. 하지만 몇 번 릴리즈를 돌려보면 이 파일이 단순한 절차가 아니라 릴리즈 품질을 지키는 장치라는 걸 알게 된다. 사용자가 CHANGELOG를 읽고 업그레이드 여부를 판단할 수 있다면, Changesets는 제 역할을 하고 있는 것이다.
더 읽어보기
2026.04.17
Code Server
처음 코드 서버를 만들었던 건 아마도 3년쯤 전의 일이다. 맥미니만 있던 탓에 밖에서 개발하는 게 쉽지 않았고, 아이패드로 언제 어디서든 개발을 하고 싶었던 끝에 찾아낸 해결책이었다. 다행히 집에는 Synology NAS가 있었고, Docker를 통해 어렵지 않게 코드 서버를 만들 수…
2026.02.02
Git Worktree
Git으로 여러 작업을 병렬로 진행하다 보면 브랜치 전환 자체가 비용이 되는 순간이 잦다. 기능 개발 중에 긴급 핫픽스를 처리해야 하거나, PR 리뷰를 위해 다른 브랜치를 실행해 봐야 하는 상황에서 git checkout과 stash를 반복하는 방식은 작업 흐름을 지속적으로 끊는다. 이…
2025.11.05
Steiger
FSD(Feature-Sliced Design)는 프론트엔드 아키텍처를 장기적으로 유지하기에 매우 유용한 방법론이다. 레이어와 슬라이스를 명확히 구분하고 의존 방향을 제한함으로써, 프로젝트 규모가 커질수록 코드베이스의 복잡도를 구조적으로 통제할 수 있다. 다만 이 장점은 동시에 부담이…
2025.08.16
pnpm과 terborepo로 시작하는 모노레포
최근 개발 현장에서는 여러 프로젝트를 하나의 저장소에서 관리하는 모노레포 구조가 빠르게 확산되고 있다. 모노레포는 여러 패키지를 한 곳에서 관리하기 때문에 공통 코드 재사용이 쉽고, 여러 팀이 동시에 작업하더라도 의존성과 변경 이력을 일관되게 관리할 수 있다는 장점이 있다. 반면, 패키…
2026.05.07
Decorator와 Metadata — fluo가 reflect-metadata를 버린 이유
들어가며 NestJS를 써본 적이 있다면 이런 설정이 익숙할 것이다. // tsconfig.json { "compilerOptions": { "experimentalDecorators": true, "emitDecoratorMetadata": true } } 그리고 엔트리 파일 어딘가에…
2026.05.05
Changesets와 GitHub Actions로 릴리즈 자동화하기
이 글은 Changesets로 모노레포 릴리즈를 관리하는 시리즈의 2편이다. 1편에서 changeset 파일로 변경 의도를 기록했다면, 이번 글에서는 그 파일을 기준으로 GitHub Actions가 Version Packages PR을 만들고 publish까지 이어지는 흐름을 다룬다.…
2026.05.05
Changesets로 모노레포 버전 관리 시작하기
이 글은 Changesets로 모노레포 릴리즈를 관리하는 시리즈의 1편이다. 여기서는 Changesets를 왜 도입하는지, 어떤 파일을 남기는지, PR 단계에서 semver 판단을 어떻게 기록하는지에 집중한다. 1편: Changesets로 모노레포 버전 관리 시작하기 2편: Change…
2026.04.29
필요한 사람들의 세상 - omocon 후기
이전 잡생각인 <너만의 월남쌈을 싸> 와 맥락적으로 이어지는 부분이 있지만, 꼭 가서 볼 필요는 없습니다. 나는 10년이라는 시간 동안 장편과 단편 소설을 썼다. 하지만 5년 전 어느 순간부터 더 이상 써야 할 이야기가 남지 않았다는 느낌을 받았고, 그 이후로는 단 한 번도 완성된 형태…
댓글
댓글을 불러오는 중...