프로그래밍

pnpm 워크스페이스 + Changesets로 모노레포 자동 버전·릴리스 파이프라인 쉽게 구축하는 법

benscript 2026. 7. 30. 12:59
반응형

왜 모노레포에 자동 버전·릴리스가 필요할까

 

여러 패키지를 한 저장소에서 관리하면 버전 올리기, 변경 로그 쓰기, 의존 패키지 동기화 같은 반복 업무가 눈덩이처럼 불어납니다. 사소한 패치도 수동으로 처리하면 릴리스가 늦어지고, 버전 불일치로 회귀 버그가 생기기 쉽습니다.

 

pnpm 워크스페이스는 패키지 간 링크와 설치 시간을 줄여주지만, 사람이 일일이 버전과 태그를 관리하면 이점이 약해집니다. Changesets를 함께 쓰면 “무엇이 바뀌었는지 → 어떤 버전이 필요한지 → 언제 배포할지”를 규칙으로 묶어 자동화할 수 있습니다.

 

이 글은 초보자도 따라 할 수 있게, 모노레포에서 “버전 결정 → 패키지별 변경 기록 → 릴리스” 흐름을 최소 단계로 연결하는 방법을 설명합니다. 실무에서는 CI에 붙여 머지 시점에 태그와 패키지 배포까지 이어지도록 구성하면 작업 지연을 줄일 수 있습니다.

 

> 안티패턴: 기능 단위 커밋에 릴리스 메모를 남기지 않고 일괄 배포하면, 어떤 변경이 어떤 패키지에 영향을 줬는지 추적이 어려워집니다. Changesets로 변경 단위를 먼저 기록해 두면 추적 비용을 줄일 수 있습니다.

 

pnpm 워크스페이스와 Changesets를 한 줄로 이해하기

 

모노레포에서 pnpm은 패키지 설치와 로컬 링크를 관리하고, Changesets는 변경 기록과 버전 규칙을 관리합니다. 둘을 묶으면 “변경을 적는다 → 버전을 계산한다 → 릴리스한다” 흐름이 반복 가능한 규칙이 됩니다.

 

간단한 예로 ui-button과 ui-input 두 패키지가 있을 때, ui-button에만 수정이 있으면 Changesets가 해당 패키지만 패치 버전을 올리고, 의존하는 앱이 있으면 그 앱의 종속 버전도 함께 정리합니다. 이때 pnpm 워크스페이스는 링크된 의존성 경로를 유지해 설치 시간을 줄입니다.

 

사용 흐름은 A -> B -> C로 이해하면 쉽습니다. A: 변경 요약을 작성(changeset 파일) → B: 여러 변경을 모아 버전 산정(버전 범프, changelog 생성) → C: 태그와 패키지 배포.

 

로컬에선 커밋 단계까지 진행하고, CI에서 머지 시점에 C를 자동 실행하도록 붙이는 방식이 실무에서 깔끔합니다.

 

> 팁: 변경 요약에는 사용자에게 보이는 변화 중심으로 쓰고(버그 수정, 브레이킹 체인지), 내부 리팩터링은 패치로 낮게 분류하세요. 잘못된 분류는 과도한 메이저/마이너 증가로 관리 비용을 키울 수 있습니다.

 

로컬부터 CI까지 빠르게 배포

 

모노레포는 워크스페이스(pnpm)와 변경 이력 관리(Changesets)만 준비하면 로컬 커밋→배포까지 한 흐름으로 이어집니다. 아래 순서를 간단히 맞추세요.

 

- pnpm 설정: 루트에 pnpm-workspace.yaml 추가, packages/* 폴더 구성
- Changesets: dev 의존성으로 설치 후 init 실행
- 변경 기록: 기능 수정 뒤 영향 패키지와 버전(주/부/패치) 선택
- 버전 반영: 메인 브랜치에서 버전·changelog 생성
- 릴리스: CI에서 pnpm -r publish 실행(레지스트리에 맞게 옵션 조정)

 

머지되면 CI가 자동으로 버전, 태그, 배포를 처리합니다. 아래 GitHub Actions는 구조만 보여주는 예시입니다.

 

name: release
on:
  push:
    branches: [ main ]
jobs:
  release:
    permissions:
      contents: write
      packages: write
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v3
        with:
          version: 9
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          registry-url: https://registry.npmjs.org
      - run: pnpm install --frozen-lockfile
      - run: pnpm changeset version
      - run: git config user.name "ci-bot" && git config user.email "ci@example.com"
      - run: git commit -am "ci: version" || echo "no changes"
      - run: git tag -a "v$(node -p 'require(\"./package.json\").version')" -m "release"
      - run: git push --follow-tags
      - run: pnpm -r publish --access public
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

 

핵심 흐름은 push → install → changeset version → 태그/푸시 → publish입니다. 토큰이나 권한이 없으면 마지막 단계에서 실패합니다.

 

> 체크포인트
> - CI에 NPM_TOKEN과 Git 푸시 권한을 등록하세요.
> - 사설 패키지는 --access public을 빼고 내부 레지스트리 URL을 씁니다.
> - 베타는 pre 모드(pnpm changeset pre enter beta → 릴리스 → pre exit)를 별도 브랜치로 관리합니다.

 

흔한 실수와 선택 기준 한 번에 정리

 

모노레포 자동 릴리스는 규칙이 흔들리면 바로 혼선이 납니다. 실수는 대부분 “사소한 규칙 위반”에서 시작합니다.

 

자주 보는 오류부터 짚어 보세요.
- changeset 파일을 PR에 누락: 릴리스 누락 및 버전 불일치
- 브랜치 혼합 사용: 메인 외 브랜치에서 version 명령 실행
- 프라이빗 패키지 공개 설정 미검토: 의도치 않은 퍼블릭 배포
- peerDependencies 범위 미반영: 소비자 앱 설치 충돌

 

선택 기준은 단순할수록 유지가 쉽습니다.
- 버전 전략: 라이브러리는 semver 엄격, 앱은 태그·배포만 자동화
- 변경 단위: “사용자 영향 있음/없음” 기준으로 minor/patch 결정
- 배포 권한: 퍼블리시는 CI만 수행, 로컬은 changeset 작성까지만
- 태그 정책: prerelease 채널을 베타·실험용으로 분리

 

비교 관점에서 Changesets는 “사후 계산” 방식이라 커밋 후에 버전을 정리합니다. Conventional Commits와 직접 스크립트는 규칙 일관성은 좋지만, 모노레포에서 패키지별 영향 추적을 수동으로 처리해야 할 수 있습니다.

 

> 안티패턴: 기능이 완료될 때까지 changeset 작성을 미루면, A -> B -> C 흐름(변경 기록 -> 버전 산정 -> 배포)이 끊기고 어떤 변경이 포함됐는지 추적이 어려워집니다. 변경이 생길 때 바로 작성하세요.

 

마지막으로 네트워크 비용 관점에서 CI 병렬 퍼블리시는 레지스트리 속도 제한에 걸릴 수 있습니다. 퍼블리시 단계는 직렬화하거나 rate limit을 고려한 재시도를 설정하는 편이 안전합니다.

 

이제 무엇을 하면 될까

 

여기까지의 흐름은 간단합니다. pnpm 워크스페이스로 패키지를 묶고, Changesets로 변경 기록→버전→릴리스를 규칙화하면 됩니다.

 

바로 실행하려면 아래 체크리스트로 정리해 보세요.

 

- 루트에 pnpm-workspace.yaml과 packages/* 디렉터리 준비 - Changesets 설치 후 npx changeset init으로 초기화 - 기능 수정 시 npx changeset으로 영향 패키지·버전 선택 - 메인 브랜치에서 npx changeset version으로 버전·로그 생성 - CI에 changesets

 

action + pnpm publish 파이프라인 연결

 

처음에는 내부 패키지만 대상으로 작은 범위에서 돌려 보세요. 문제가 없으면 공개 패키지, 프리릴리스 채널 등 단계적으로 확장하는 편이 안전합니다.

 

> 실무 팁: 로컬에서는 changeset 작성까지만 하고, 퍼블리시는 CI만 수행하도록 권한을 분리하세요. 레지스트리 토큰 노출과 임의 배포를 방지할 수 있습니다.

 

예시로 보는 Changesets 워크플로우 한 바퀴

 

모노레포에 ui-button(라이브러리)와 web-app(앱)이 있고, web-app이 ui-button@1.2.x를 사용한다고 가정합니다. 버튼에 작은 스타일 버그 패치를 배포하는 흐름을 짧게 따라가 보겠습니다.

 

먼저 ui-button을 수정한 뒤 로컬에서 변경 기록을 만듭니다. 메시지에는 사용자 영향과 요약을 한 줄로 남깁니다.

 

- npx changeset 실행 → ui-button만 선택 → 버전은 patch
- “Fix: 버튼 호버 색상 대비 개선”처럼 간단히 서술
- PR에 .changeset/xxx.md가 포함됐는지 확인

 

메인에 머지되면 CI가 version 단계에서 ui-button을 1.2.1로 올리고 changelog를 생성합니다. 이어서 publish 단계에서 태그를 push하고, 레지스트리에 ui-button@1.2.1을 배포합니다.

 

web-app은 별도 변경이 없어도 다음 설치 시 ui-button의 패치가 적용됩니다. 만약 web-app이 ui-button을 내부 경로로 고정했다면, pnpm install에서 해상도 충돌이 날 수 있으니 범위를 ^1.2.0처럼 여유 있게 유지하세요.

 

> 안티패턴: 여러 패키지에 걸친 리팩터링을 한 PR에 넣고 “minor”로 뭉뚱그리면, 영향 범위가 흐려져 추적이 어려워집니다. 기능 단위로 PR과 changeset을 분리하세요.

반응형