changeset과 turborepo, github actions + husky로 private npm registry 배포하기

우빈·2025년 2월 25일
4
post-thumbnail

현재 속한 조직에서 저는 private npm registry를 도입하자는 의견을 제시하게 되었습니다.
기존에는 하나의 repository 내부에서 사용하는 package를 관리했었는데요, 그렇게 되면 다음과 같은 문제들이 있었습니다.

  • 종속성 : 공유 패키지에 변경 사항이 발생하면 모든 어플리케이션 코드가 이에 대한 dependency를 가집니다.
  • 폐쇄성 : 타 repository에서 동일한 패키지를 사용해야할 때 니즈를 맞추기 어렵습니다.
  • 용량 및 크기 : 패키지에만 존재하는 여러 dependency들이 함께 관리되어 repository의 npm dependency가 굉장히 무거워집니다.

이 글에서는...

  • turborepo와 changeset을 이용해서 private npm registry 환경을 구축합니다.

turborepo setup

package manager로는 pnpm을 사용했습니다.

pnpm dlx create-turbo@latest

프로젝트가 생성되면 JSON을 다음과 같이 설정합니다.

{
  "name": "[패키지명]", // 해당 패키지명에 따라 하위 패키지가 @[패키지명]/** 으로 설정됩니다.
  "version": "0.0.0"
  "private": true, // root repository는 배포 대상이 아니기에 private을 true로 설정합니다. 
  "workspaces": [
    "packages/*"
  ],
  "scripts": {
    ...
    "changeset": "changeset",
    "changeset:publish": "pnpm prepack && changeset publish",
    "changeset:version": "changeset version && pnpm i --lockfile-only",
    ...
    "publish-packages": "turbo run build && changeset version && changeset publish",
    "version-packages": "changeset version",
    "release": "turbo build && changeset publish",
    "prepack": "turbo run prepack",
  },
  "dependencies": {
    "@[패키지명]/a": "workspace:^",
    "@[패키지명]/b": "workspace:^",
    "@[패키지명]/c": "workspace:^"
  },
  "devDependencies": {
    "@changesets/changelog-github": "^0.5.0",
    "@changesets/cli": "^2.27.11",
    "husky": "^9.1.7",
    "prettier": "^3.5.0",
    "turbo": "^2.4.1",
    "typescript": "5.7.3"
  }
}

위와 같이 package.json에서 기본적인 설정을 마치고, 하위 패키지에서도 세팅을 진행합니다.
turborepo에서 기본으로 세팅해주는 typescript-config을 기준으로 세팅해보겠습니다.

{
  "name": "@testtest/typescript-config",
  "version": "0.0.0",
  "private": false, // private은 꼭 false 또는 해당 라인을 삭제해주세요. private npm registry 배포와 무관하게 해당 패키지의 배포 가능 여부를 따지는 필드입니다.
  "license": "MIT",
  "publishConfig": {
    "access": "public"
  }
}

pnpm i를 통해 명시된 dependencies를 설치합니다.
혹시나 turborepo에서 default로 생성하는 어플리케이션에 dependency가 걸려있어 install이 안 되고 있는지 확인해주세요. 만약 그런 경우, 사용하지 않는 어플리케이션이나 패키지를 삭제하고(ex) docs, web, ui ...) 필요한 패키지만을 명시해주세요.

changeset setup

다음 명령어를 실행합니다.

pnpm changeset init

성공적으로 initialize가 되면 프로젝트의 root에 .changeset 디렉터리가 생깁니다.
changeset setup은 매우 간단해서, 기본적으로는 이게 끝입니다.
추가적으로 changeset에 세팅할 다를 내용들이 있다면, config.json에서 수정할 수 있습니다.

각 옵션의 effect들은 여기서 확인하세요

changeset publish

이제 changeset을 성공적으로 publish할 수 있습니다.

pnpm changeset // git add와 유사합니다. 어떤 패키지를 업데이트 대상에 포함할지 정합니다.
pnpm changeset:version // git commit과 유사합니다.
pnpm changeset:publish // git push와 유사합니다. 실제로 패키지가 publish됩니다.

작업사항이 생길 떄마다 다음 세 가지 커맨드로 changeset을 사용할 수 있습니다.

private npm registry setup

여기서부터가 중요한데요, private npm registry에 배포하려면 registry url을 가리키는 코드가 필요합니다.

프로젝트의 root에 .npmrc 파일을 생성한 후 다음과 같이 세팅합니다.

@[패키지명]:registry=[registry server url]
//[https를 제외한 registry server url]/:_authToken=${NPM_TOKEN}

그런 다음, packages 하위의 각 package에 있는 package.json에서 registry url을
바라보게 하는 셋업 코드 두 가지를 추가합니다.

...
  "publishConfig": {
    "registry": "[registry server url]"
  },
  "repository": {
    "type": "git",
    "url": "[해당 repository의 git 주소]"
  },
...

publish를 진행할 때 package를 순회하면서 스크립트를 실행하기 때문에, 꼭 root가 아닌
각 package마다 해당 코드가 위치해있어야 합니다.

deploy with github actions

저는 작업사항이 생기면 git push를 진행할 때마다 로컬에서 직접 changeset의 업데이트 과정을
거쳐야 한다는 게 번거롭다고 느껴서, github actions와 함께 해당 이슈를 해결했습니다.

name: Release

on:
  push:
    branches:
      - main

concurrency: ${{ github.workflow }}-${{ github.ref }}

jobs:
  release:
    name: Release
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Repo
        uses: actions/checkout@v4

      - name: Setup pnpm 8
        uses: pnpm/action-setup@v3
        with:
          version: 8

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

      - name: Install Dependencies
        run: pnpm i

      - name: Create Release Pull Request or Publish to npm
        id: changesets
        uses: changesets/action@v1
        with:
          # This expects you to have a script called release which does a build for your packages and calls changeset publish
          publish: pnpm release
        env:
          GITHUB_TOKEN: ${{ secrets.GIT_TOKEN }}
          NPM_TOKEN: ${{ secrets.GIT_TOKEN }}
          NODE_AUTH_TOKEN: ${{ secrets.GIT_TOKEN }}
permissions:
  contents: write
  id-token: write

pnpm release는 결국 changeser version, changeset publish를 진행하는 로직이기에, 해당 워크플로우가 돌기 전에 changeset 명령어를 수행해야 합니다.

automation with husky

그래서 git push를 진행하면 올리는 동시에 사용자가 changeset을 세팅하도록 husky를 사용했습니다.

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

# GitHub Actions에서 실행되는 경우 pre-push 훅을 건너뛰는 로직입니다
if [ "$GITHUB_ACTIONS" = "true" ]; then
  echo "Skipping pre-push hook in GitHub Actions"
  exit 0
fi

echo "Running pre-push hook..."

# 해당 명령어를 작성해주지 않으면 husky가 cli의 changeset을 강제로 skip합니다.
exec < /dev/tty

pnpm build
pnpm changeset

# changeset을 진행하면 .changeset에 임시 업데이트 파일이 생기는데, 해당 파일을 반영하기 위해 pre-push 단계에서 같이 파일을 commit합니다.
git add .changeset/
git commit -m "add changeset dump file"

이렇게 되면 PR을 올리고, 머지한 다음 github actions가 실행됩니다.
github actions 실행이 끝마치게 되면 changeset이 Version Packages PR을 업로드하고, 해당 PR을 머지하면 자동으로 태깅 + 릴리즈 + CHANGELOG + version update가 완료됩니다.

profile
프론트엔드 공부중

0개의 댓글

관련 채용 정보