GitHub Actions에서 npm 패키지를 자동으로 publish하는 두 가지 방법을 정리한다.
**Trusted Publishing(OIDC)**이 npm 공식 권장 방식이며, 토큰 없이 배포가 가능하다.
| Trusted Publishing (OIDC) | Granular Access Token | |
|---|---|---|
| 보안 | 토큰 없음, OIDC 단기 인증 | 장기 토큰 secret 관리 필요 |
| 설정 위치 | npmjs.com 패키지 Settings | npmjs.com Access Tokens + GitHub Secrets |
| Provenance | 자동 생성 | --provenance 플래그 필요 |
| npm CLI 요구 | 11.5.1+ / Node 22.14.0+ | 9.5.0+ |
| Runner 제한 | GitHub-hosted only (self-hosted 미지원) | 제한 없음 |
| 첫 publish | 불가 (패키지 존재해야 설정 가능) | 가능 |
| npm 권장 | ✅ 권장 | ⚠️ 보안 경고 표시 |
2025년 11월부터 npm은 Legacy 토큰을 제거하고 Granular Access Token만 지원한다.
토큰 생성 시 "Trusted Publishing을 대신 사용하라"는 보안 경고가 표시된다.
npm publish에 필요한 필드를 package.json에 추가한다.
{
"name": "@your-scope/your-package",
"version": "1.0.0",
"main": "dist/index.js",
"bin": {
"your-package": "dist/index.js"
},
"files": [
"dist",
"README.md",
"LICENSE"
],
"repository": {
"type": "git",
"url": "git+https://github.com/owner/repo.git"
},
"publishConfig": {
"access": "public"
},
"scripts": {
"prepublishOnly": "npm run check && npm run build && npm test"
}
}
| 필드 | 용도 |
|---|---|
bin |
npx로 실행 가능하게 만듦. entry 파일에 #!/usr/bin/env node shebang 필요 |
files |
npm 패키지에 포함할 파일/디렉토리 명시. README*, LICENSE*, package.json은 항상 자동 포함됨 |
repository |
npm 페이지에 GitHub 링크 표시. Trusted Publishing의 provenance 검증에도 사용 (대소문자 일치 필요) |
publishConfig.access |
scoped 패키지(@scope/name)는 기본이 restricted이므로 public 명시 필요 |
prepublishOnly |
로컬 npm publish 실행 시 안전장치 (type check → build → test) |
files필드가 있어도 npm은README*,LICENSE*,CHANGELOG*패턴의 파일을 항상 포함한다.
번역 README(README.ko.md등)가 있으면 같이 포함되는데,.npmignore로도 제외할 수 없다.
GitHub Actions가 OIDC 토큰을 발급하고, npm이 이를 검증하여 인증한다. 장기 보관 토큰이 존재하지 않으므로 유출 위험이 없다.
- npm CLI 11.5.1+ / Node 22.14.0+ 필요
- GitHub-hosted runner만 지원 (self-hosted 미지원)
- 패키지가 npm에 이미 존재해야 Trusted Publisher를 설정할 수 있음
- 따라서 첫 번째 publish는 반드시 로컬에서 수동으로 실행해야 함
# npm 로그인
npm login
# scoped 패키지의 경우 org에 본인 계정이 owner로 등록되어 있어야 함
npm org ls <your-org>
# 첫 publish
npm publish --access public
# 2FA 활성화 시 OTP 필요
npm publish --access public --otp=123456
| 필드 | 값 | 설명 |
|---|---|---|
| Organization or user | owner |
GitHub username 또는 org name |
| Repository | repo-name |
레포지토리 이름 |
| Workflow filename | publish.yml |
.github/workflows/ 안의 파일명 (확장자 포함) |
| Environment name | (비워두기) | GitHub Environments 사용 시에만 입력 |
npm은 설정 저장 시 값을 검증하지 않는다. 오타가 있으면 publish 시점에야 에러가 발생하므로 대소문자까지 정확히 입력해야 한다.
name: Publish to npm
on:
push:
tags:
- 'v*'
jobs:
ci:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20, 22]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: npm
- run: npm ci
- run: npm run check
- run: npm run build
- run: npm test
publish:
needs: ci
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write # OIDC 토큰 발급에 필수
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
registry-url: https://registry.npmjs.org
cache: npm
- run: npm ci
- run: npm run build
- name: Verify tag matches package.json version
run: |
PKG_VERSION="v$(node -p "require('./package.json').version")"
GIT_TAG="${GITHUB_REF#refs/tags/}"
if [ "$PKG_VERSION" != "$GIT_TAG" ]; then
echo "::error::Tag $GIT_TAG does not match package.json version $PKG_VERSION"
exit 1
fi
- run: npm publish --access public
# NODE_AUTH_TOKEN 불필요! OIDC가 자동 처리
# --provenance 플래그 불필요! 자동 생성
permissions.id-token: write — publish job에만 설정 (top-level이 아닌 job-level 권장)NODE_AUTH_TOKEN / NPM_TOKEN — 불필요. OIDC가 대체--provenance — 불필요. Trusted Publishing 사용 시 자동 생성--access public — scoped 패키지 첫 publish 시 필요 (이후에도 명시 권장)registry-url — actions/setup-node에서 반드시 설정해야 npm CLI가 올바른 registry를 사용# package.json version 업데이트 + 태그 생성 + 커밋
npm version patch # 또는 minor, major
# push (태그 포함)
git push && git push --tags
v* 태그 push → CI 통과 → npm publish 자동 실행
Trusted Publishing을 사용할 수 없는 경우(self-hosted runner, 첫 publish 자동화 등)에 사용한다.
| 설정 | 값 |
|---|---|
| Name | github-actions-publish |
| Expiration | 원하는 기간 |
| Packages | 대상 패키지 선택, Read and Write |
| Bypass 2FA | true (CI에서 2FA 입력 불가) |
GitHub repo → Settings → Secrets and variables → Actions → New repository secret:
NPM_TOKENname: Publish to npm
on:
push:
tags:
- 'v*'
permissions:
contents: read
id-token: write # provenance 생성에 필요
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
registry-url: https://registry.npmjs.org
cache: npm
- run: npm ci
- run: npm run build
- name: Verify tag matches package.json version
run: |
PKG_VERSION="v$(node -p "require('./package.json').version")"
GIT_TAG="${GITHUB_REF#refs/tags/}"
if [ "$PKG_VERSION" != "$GIT_TAG" ]; then
echo "::error::Tag $GIT_TAG does not match package.json version $PKG_VERSION"
exit 1
fi
- run: npm publish --provenance --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
# Trusted Publishing
- run: npm publish --access public
+ # 토큰 불필요, provenance 자동
# Token 방식
- run: npm publish --provenance --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
+ # --provenance 명시 필요, 토큰 필요
두 방식 모두 tag와 package.json version 불일치를 방지하는 검증 step을 포함한다:
- name: Verify tag matches package.json version
run: |
PKG_VERSION="v$(node -p "require('./package.json').version")"
GIT_TAG="${GITHUB_REF#refs/tags/}"
if [ "$PKG_VERSION" != "$GIT_TAG" ]; then
echo "::error::Tag $GIT_TAG does not match package.json version $PKG_VERSION"
exit 1
fi
이렇게 하면 npm version patch && git push --tags로 만든 태그만 publish된다. 잘못된 태그를 push해도 실패하므로 안전하다.
기존 Token 방식에서 Trusted Publishing으로 전환하는 순서:
--provenance 제거)NPM_TOKEN 삭제)반드시 Trusted Publishing이 정상 동작하는 것을 확인한 후 토큰을 폐기해야 한다.
Unable to authenticate 에러.yml 확장자)id-token: write permission이 publish job에 설정되어 있는지 확인EOTP 에러 (로컬 publish 시)npm publish --access public --otp=<AUTHENTICATOR_CODE>
2FA가 활성화된 계정에서는 OTP 코드가 필요하다.
404 Not Foundscoped 패키지는 npm org에 본인 계정이 owner로 등록되어 있어야 한다:
npm org ls <your-org> # 권한 확인
on.push.tags 패턴이 태그 이름과 매치하는지 확인 (예: v*는 v1.0.0에 매치)npm은 같은 버전을 두 번 publish할 수 없다. npm version patch로 버전을 올려야 한다.