이미지 로딩 중...

플러그인 배포와 NPM 패키지 퍼블리싱 완벽 가이드 - 슬라이드 1/11
A

AI Generated

2025. 11. 13. · 4 Views

플러그인 배포와 NPM 패키지 퍼블리싱 완벽 가이드

내가 만든 코드를 전 세계 개발자들과 공유하고 싶으신가요? NPM 패키지 배포부터 버전 관리, 자동화까지 실전 노하우를 담았습니다. 초보자도 따라할 수 있는 단계별 가이드입니다.


목차

  1. NPM 계정 생성과 인증
  2. package.json 완벽 설정
  3. .npmignore와 파일 관리
  4. npm publish 명령어
  5. 시맨틱 버저닝
  6. npm unpublish와 deprecate
  7. Scoped Packages
  8. 배포 전 테스트
  9. GitHub Actions 자동 배포
  10. npm 보안 토큰 관리

1. NPM 계정 생성과 인증

시작하며

여러분이 유용한 라이브러리를 만들었는데, 다른 프로젝트에서 사용하려고 매번 코드를 복사-붙여넣기하고 계신가요? 혹은 팀원들에게 Slack으로 파일을 전송하고 계신가요?

이런 방식은 비효율적일 뿐만 아니라 버전 관리도 불가능합니다. 누군가 코드를 수정했을 때 모든 프로젝트를 일일이 찾아서 업데이트해야 하죠.

바로 이럴 때 필요한 것이 NPM 패키지 배포입니다. 한 번 배포하면 전 세계 어디서든 npm install 명령어 하나로 여러분의 코드를 사용할 수 있습니다.

개요

간단히 말해서, NPM 계정은 여러분의 패키지를 전 세계 개발자들과 공유할 수 있는 통로입니다. NPM(Node Package Manager)은 세계에서 가장 큰 소프트웨어 레지스트리입니다.

매주 300억 개 이상의 패키지가 다운로드되고 있죠. 여러분의 코드도 이 생태계의 일부가 될 수 있습니다.

기존에는 라이브러리를 공유하려면 GitHub에 코드를 올리고 사용자들에게 다운로드 방법을 설명해야 했습니다. 이제는 NPM에 배포하면 npm install your-package 한 줄로 끝납니다.

NPM 계정을 만들면 무제한 공개 패키지를 배포할 수 있고, 다운로드 통계를 확인할 수 있으며, 패키지 업데이트도 쉽게 관리할 수 있습니다. 또한 2FA(2단계 인증)로 계정을 안전하게 보호할 수 있습니다.

코드 예제

// 1. NPM 계정 생성 (웹사이트에서 진행)
// https://www.npmjs.com/signup

// 2. 로컬에서 NPM 로그인
// 터미널에서 실행
npm login

// 사용자명, 비밀번호, 이메일 입력
// Username: your-username
// Password: your-password
// Email: your-email@example.com

// 3. 로그인 확인
npm whoami
// 출력: your-username

// 4. 2FA 설정 (강력 권장)
npm profile enable-2fa auth-and-writes

설명

이것이 하는 일: NPM 계정을 생성하고 로컬 개발 환경에 인증 정보를 저장하여 패키지를 배포할 수 있는 권한을 얻습니다. 첫 번째로, npmjs.com 웹사이트에서 계정을 생성합니다.

이때 사용하는 사용자명은 패키지 이름에도 영향을 주므로 신중하게 선택해야 합니다. 예를 들어, 사용자명이 john-dev라면 scoped 패키지를 @john-dev/my-package 형태로 배포할 수 있습니다.

그 다음으로, npm login 명령어를 실행하면 터미널에서 사용자명, 비밀번호, 이메일을 입력받습니다. 이 정보는 ~/.npmrc 파일에 암호화되어 저장되며, 이후 모든 NPM 명령어에서 자동으로 사용됩니다.

로그인이 성공하면 인증 토큰이 생성되어 안전하게 저장됩니다. npm whoami 명령어는 현재 로그인된 사용자를 확인합니다.

여러 계정을 사용하거나 팀 환경에서 작업할 때 특히 유용합니다. 만약 다른 계정으로 전환하려면 npm logout 후 다시 로그인하면 됩니다.

2FA(2단계 인증)를 활성화하면 패키지를 배포하거나 계정 설정을 변경할 때마다 추가 인증이 필요합니다. 이는 계정 해킹으로 인한 악성 코드 배포를 막는 중요한 보안 장치입니다.

auth-and-writes 옵션은 로그인과 배포 시 모두 2FA를 요구하는 가장 안전한 설정입니다. 여러분이 이 과정을 완료하면 전 세계 개발자들이 사용할 수 있는 패키지를 배포할 수 있는 권한을 갖게 됩니다.

또한 NPM 프로필 페이지에서 여러분의 모든 패키지와 다운로드 통계를 한눈에 볼 수 있습니다.

실전 팁

💡 사용자명은 한 번 정하면 변경이 어려우므로 전문적이고 기억하기 쉬운 이름을 선택하세요. 개인 브랜드나 GitHub 사용자명과 일치시키면 좋습니다.

💡 .npmrc 파일에는 인증 토큰이 저장되므로 절대 Git 저장소에 커밋하지 마세요. .gitignore에 반드시 추가하세요.

💡 CI/CD 환경에서는 npm login 대신 NPM_TOKEN 환경 변수를 사용하세요. GitHub Actions나 GitLab CI에서 안전하게 배포할 수 있습니다.

💡 여러 NPM 레지스트리를 사용한다면(예: 사내 NPM, 공식 NPM) .npmrc 파일에서 registry URL을 프로젝트별로 설정할 수 있습니다.

💡 2FA 복구 코드는 안전한 곳에 저장하세요. 휴대폰을 분실했을 때 유일한 계정 복구 수단입니다.


2. package.json 완벽 설정

시작하며

여러분이 패키지를 배포했는데 다른 개발자들이 설치 후 바로 에러가 발생한다면 어떻게 될까요? GitHub Issues에 "설치가 안 돼요", "어떻게 사용하나요?" 같은 질문이 쏟아질 것입니다.

이런 문제의 90%는 package.json 설정이 불완전해서 발생합니다. 잘못된 entry point, 누락된 dependencies, 불명확한 버전 정보 등이 대표적이죠.

바로 이럴 때 필요한 것이 완벽한 package.json 설정입니다. 제대로 설정된 package.json은 사용자 경험을 극대화하고 유지보수를 쉽게 만듭니다.

개요

간단히 말해서, package.json은 여러분의 패키지에 대한 모든 메타데이터를 담고 있는 설정 파일입니다. 패키지 이름, 버전, 진입점(entry point), 의존성, 라이선스, 저장소 정보 등 NPM이 패키지를 올바르게 처리하는 데 필요한 모든 정보가 포함됩니다.

특히 main, module, exports 필드는 사용자가 여러분의 패키지를 import할 때 어떤 파일을 로드할지 결정합니다. 기존에는 main 필드 하나만 설정했다면, 이제는 CommonJS, ES Module, TypeScript 등 다양한 환경을 고려해야 합니다.

또한 Tree Shaking을 지원하려면 sideEffects 필드도 설정해야 하죠. 필수 필드로는 name, version, main이 있고, 권장 필드로는 description, keywords, license, repository, bugs가 있습니다.

이러한 필드들이 잘 설정되어 있으면 NPM 검색 결과에서 상위에 노출되고, 사용자들이 패키지를 신뢰하게 됩니다.

코드 예제

{
  "name": "my-awesome-library",
  "version": "1.0.0",
  "description": "A modern, type-safe utility library",
  "main": "./dist/index.cjs",
  "module": "./dist/index.mjs",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "import": "./dist/index.mjs",
      "require": "./dist/index.cjs",
      "types": "./dist/index.d.ts"
    }
  },
  "files": [
    "dist",
    "README.md",
    "LICENSE"
  ],
  "keywords": ["utility", "typescript", "modern"],
  "author": "Your Name <your.email@example.com>",
  "license": "MIT",
  "repository": {
    "type": "git",
    "url": "https://github.com/username/my-awesome-library"
  },
  "bugs": "https://github.com/username/my-awesome-library/issues",
  "homepage": "https://github.com/username/my-awesome-library#readme",
  "engines": {
    "node": ">=14.0.0"
  },
  "peerDependencies": {
    "react": "^17.0.0 || ^18.0.0"
  },
  "devDependencies": {
    "typescript": "^5.0.0"
  }
}

설명

이것이 하는 일: package.json은 패키지의 메타데이터를 정의하고, NPM이 패키지를 설치하고 실행하는 방법을 알려줍니다. 첫 번째로, nameversion은 패키지를 고유하게 식별합니다.

name은 소문자와 하이픈만 사용할 수 있으며, NPM 레지스트리에서 유일해야 합니다. version은 시맨틱 버저닝(semver)을 따라야 하며, 배포할 때마다 증가시켜야 합니다.

이 두 필드가 조합되어 my-awesome-library@1.0.0 같은 패키지 식별자가 됩니다. 그 다음으로, main, module, types, exports 필드가 패키지의 진입점을 정의합니다.

main은 CommonJS 환경에서 사용되고, module은 ES Module 환경에서 사용됩니다. 최신 방식인 exports 필드는 조건부 export를 지원하여 환경에 따라 다른 파일을 제공할 수 있습니다.

예를 들어, Webpack은 import를, Node.js 구버전은 require를 사용하죠. files 배열은 NPM에 배포될 파일을 명시적으로 지정합니다.

여기에 포함되지 않은 파일은 배포되지 않으므로, 소스 코드나 테스트 파일을 제외하고 빌드된 파일만 포함시킬 수 있습니다. 이렇게 하면 패키지 크기가 줄어들고 설치 속도가 빨라집니다.

keywords, description, repository 같은 메타데이터 필드는 NPM 검색과 패키지 페이지에 표시됩니다. 좋은 description과 적절한 keywords를 사용하면 검색 노출이 증가하고, repository 링크가 있으면 사용자들이 소스 코드를 쉽게 찾을 수 있습니다.

bugs URL은 이슈 리포팅을 위한 링크를 제공합니다. engines 필드는 패키지가 요구하는 Node.js 버전을 명시합니다.

이는 사용자에게 경고를 제공하고, CI/CD에서 올바른 Node 버전을 사용하도록 보장합니다. peerDependencies는 호스트 프로젝트가 제공해야 하는 의존성(예: React 라이브러리의 경우 React)을 나타냅니다.

여러분이 이 설정을 완벽하게 갖추면 다양한 환경(Node.js, Webpack, Rollup, TypeScript)에서 문제없이 작동하는 패키지를 배포할 수 있습니다. 또한 NPM 웹사이트에서 전문적으로 보이고, 사용자들이 신뢰할 수 있는 패키지가 됩니다.

실전 팁

💡 패키지 이름은 짧고 기억하기 쉽게 만드세요. 이미 존재하는 이름인지 npm search 명령어로 확인하세요. scoped 패키지(@username/package)를 사용하면 이름 충돌을 피할 수 있습니다.

💡 exports 필드는 Node.js 12+ 에서만 작동하므로, 구버전 지원이 필요하다면 mainmodule 필드를 함께 제공하세요.

💡 files 필드 대신 .npmignore를 사용할 수도 있지만, files 화이트리스트 방식이 더 안전합니다. 실수로 민감한 파일이 배포되는 것을 방지할 수 있죠.

💡 TypeScript 패키지라면 types 필드는 필수입니다. 타입 정의 파일이 없으면 TypeScript 사용자들이 any 타입으로 사용해야 하므로 사용자 경험이 나빠집니다.

💡 sideEffects: false를 설정하면 Webpack 같은 번들러가 Tree Shaking을 더 공격적으로 수행할 수 있습니다. 단, CSS import나 전역 상태 변경이 있다면 해당 파일을 명시해야 합니다.


3. .npmignore와 파일 관리

시작하며

여러분이 패키지를 배포했는데 용량이 50MB나 되어서 설치하는 데 1분이 걸린다면 어떻게 될까요? 사용자들은 아마 대안을 찾아볼 것입니다.

이런 문제는 불필요한 파일을 함께 배포해서 발생합니다. 소스 코드, 테스트 파일, 설정 파일, 심지어 node_modules까지 포함되는 경우도 있죠.

실제로 필요한 것은 빌드된 JavaScript 파일과 타입 정의뿐인데 말입니다. 바로 이럴 때 필요한 것이 .npmignore 파일입니다.

Git과 유사하게, NPM 배포 시 특정 파일이나 폴더를 제외할 수 있습니다.

개요

간단히 말해서, .npmignore는 NPM 패키지 배포 시 포함하지 않을 파일이나 디렉토리를 지정하는 파일입니다. 배포 패키지의 크기를 최소화하는 것은 매우 중요합니다.

작은 패키지는 빠르게 다운로드되고, CI/CD 파이프라인에서 빌드 시간을 단축시키며, 네트워크 비용도 절감합니다. 특히 serverless 환경이나 Docker 이미지에서는 패키지 크기가 성능에 직접적인 영향을 미칩니다.

기존에는 모든 파일을 배포하고 사용자가 필요한 것만 사용하도록 했습니다. 이제는 빌드된 결과물만 정확히 배포하여 사용자 경험을 최적화합니다.

.npmignore가 없으면 NPM은 .gitignore를 사용합니다. 하지만 .gitignore는 Git용이므로 NPM 배포와는 목적이 다릅니다.

예를 들어, dist/ 폴더는 Git에서는 제외하지만 NPM에서는 포함해야 하죠. 따라서 .npmignore를 별도로 만들어 관리하는 것이 좋습니다.

코드 예제

// .npmignore 파일
// 소스 코드 제외 (빌드된 파일만 배포)
src/
*.ts
!*.d.ts

// 테스트 관련 파일 제외
__tests__/
*.test.js
*.spec.js
coverage/
jest.config.js

// 설정 파일 제외
.eslintrc.js
.prettierrc
tsconfig.json
.github/
.vscode/

// 개발 환경 파일 제외
.env
.env.local
*.log
.DS_Store

// 문서 파일 제외 (README는 유지)
docs/
examples/
*.md
!README.md

// CI/CD 파일 제외
.travis.yml
.gitlab-ci.yml
Dockerfile

설명

이것이 하는 일: .npmignore는 NPM 배포 시 불필요한 파일을 제외하여 패키지 크기를 줄이고 다운로드 속도를 향상시킵니다. 첫 번째로, 소스 코드를 제외합니다.

TypeScript나 ES6+ 코드는 src/ 폴더에 있고, 빌드된 결과는 dist/lib/ 폴더에 있습니다. 사용자는 빌드된 코드만 필요하므로 소스 코드를 제외합니다.

*.ts 패턴으로 TypeScript 파일을 제외하되, !*.d.ts로 타입 정의 파일은 포함시킵니다. 느낌표(!)는 예외 규칙을 의미합니다.

그 다음으로, 테스트 관련 파일을 모두 제외합니다. __tests__/ 폴더, *.test.js, *.spec.js 같은 테스트 파일, coverage/ 폴더의 커버리지 리포트, jest.config.js 같은 테스트 설정 파일 등은 사용자에게 전혀 필요하지 않습니다.

이런 파일들을 제외하면 패키지 크기를 크게 줄일 수 있습니다. 개발 도구 설정 파일도 제외합니다.

ESLint, Prettier, TypeScript 설정 파일, .vscode/ 같은 IDE 설정, .github/ 같은 GitHub 관련 파일은 패키지 사용자에게 불필요합니다. 이런 파일들은 개발 환경에서만 필요하죠.

민감한 정보나 로그 파일도 반드시 제외해야 합니다. .env 파일에는 API 키나 비밀번호가 있을 수 있고, .log 파일에는 개발 중 생성된 디버그 정보가 있을 수 있습니다.

이런 파일이 실수로 배포되면 보안 문제가 발생할 수 있습니다. 문서 파일은 선택적으로 제외합니다.

상세한 개발 문서나 예제 코드는 GitHub에서 보면 되므로, NPM 패키지에는 포함하지 않아도 됩니다. 하지만 README.md는 NPM 페이지에 표시되므로 반드시 포함해야 합니다.

!README.md 규칙으로 README만 예외 처리합니다. 여러분이 이렇게 설정하면 수십 MB 크기의 프로젝트를 수백 KB 크기의 깔끔한 패키지로 배포할 수 있습니다.

실제로 많은 인기 패키지들이 원본 저장소는 10MB 이상이지만 배포 패키지는 100KB 미만입니다.

실전 팁

💡 배포 전에 npm pack 명령어로 실제 배포될 파일 목록을 확인하세요. 예상치 못한 파일이 포함되어 있을 수 있습니다.

💡 package.json의 files 필드(화이트리스트)와 .npmignore(블랙리스트) 중 하나를 선택하세요. 둘 다 사용하면 혼란스럽고, files 필드가 우선순위가 높아 .npmignore가 무시될 수 있습니다.

💡 .env 파일은 .gitignore와 .npmignore 모두에 포함시키세요. 이중 안전장치로 민감한 정보 유출을 방지합니다.

💡 번들된 패키지(Webpack, Rollup으로 빌드)라면 단일 파일만 배포할 수 있습니다. 이 경우 .npmignore가 매우 간단해지고 패키지 크기도 최소화됩니다.

💡 정기적으로 배포된 패키지를 다운로드해서 내용을 확인하세요. npm pack && tar -xzf *.tgz로 압축을 풀어 실제 파일을 확인할 수 있습니다.


4. npm publish 명령어

시작하며

여러분이 완벽한 라이브러리를 만들었고, package.json도 잘 설정했고, 테스트도 통과했습니다. 이제 전 세계 개발자들에게 공개할 순간입니다.

긴장되시나요? 많은 개발자들이 처음 배포할 때 실수를 합니다.

잘못된 버전을 배포하거나, 테스트되지 않은 코드를 올리거나, 빌드를 깜빡하고 소스 코드만 배포하는 경우도 있죠. 바로 이럴 때 필요한 것이 체계적인 npm publish 프로세스입니다.

올바른 순서로 배포하면 실수를 방지하고 안정적인 패키지를 제공할 수 있습니다.

개요

간단히 말해서, npm publish는 여러분의 패키지를 NPM 레지스트리에 업로드하여 전 세계 개발자들이 사용할 수 있게 만드는 명령어입니다. 이 명령어는 현재 디렉토리의 package.json을 읽고, .npmignore나 files 필드를 기반으로 포함할 파일을 결정하고, 압축(tarball)하여 NPM 서버에 업로드합니다.

업로드가 완료되면 즉시 npm install your-package로 설치할 수 있습니다. 기존에는 파일을 수동으로 업로드하거나 별도의 호스팅을 해야 했습니다.

이제는 한 줄의 명령어로 글로벌 CDN을 통해 전 세계에 배포할 수 있습니다. publish 명령어는 다양한 옵션을 제공합니다.

--access public으로 공개 패키지를 만들 수 있고, --tag beta로 베타 버전을 배포할 수 있으며, --dry-run으로 실제 배포 없이 시뮬레이션할 수 있습니다. 또한 prepublish, prepare, prepublishOnly 같은 lifecycle scripts를 활용하여 배포 전 자동 빌드나 테스트를 실행할 수 있습니다.

코드 예제

// 1. 배포 전 체크리스트
npm run test          // 모든 테스트 통과 확인
npm run build         // 프로덕션 빌드 실행
npm run lint          // 코드 품질 검사

// 2. 배포될 파일 미리보기
npm pack --dry-run    // 실제 배포 없이 시뮬레이션
// 또는
npm pack              // tarball 파일 생성하여 확인
tar -xzf *.tgz        // 압축 해제하여 내용 확인

// 3. 버전 업데이트 (선택)
npm version patch     // 1.0.0 -> 1.0.1
npm version minor     // 1.0.0 -> 1.1.0
npm version major     // 1.0.0 -> 2.0.0

// 4. 실제 배포
npm publish           // 기본 배포

// scoped 패키지를 public으로 배포
npm publish --access public

// 베타 버전으로 배포
npm publish --tag beta

// 5. 배포 확인
npm info your-package-name

설명

이것이 하는 일: npm publish는 로컬 패키지를 압축하여 NPM 서버에 업로드하고, 전 세계 개발자들이 즉시 사용할 수 있도록 만듭니다. 첫 번째로, 배포 전 체크리스트를 실행합니다.

npm run test로 모든 테스트가 통과하는지 확인하고, npm run build로 최신 코드를 빌드합니다. 이 단계를 건너뛰면 이전 버전의 빌드 파일이나 테스트되지 않은 코드가 배포될 수 있습니다.

많은 팀들이 이 과정을 prepublishOnly 스크립트로 자동화합니다. 그 다음으로, npm pack으로 실제 배포될 내용을 미리 확인합니다.

이 명령어는 .tgz 파일을 생성하는데, 이것이 바로 사용자들이 다운로드할 파일입니다. 압축을 풀어보면 어떤 파일이 포함되는지, 예상치 못한 파일은 없는지 확인할 수 있습니다.

--dry-run 옵션을 사용하면 파일을 생성하지 않고 목록만 출력합니다. 버전 업데이트는 npm version 명령어로 쉽게 할 수 있습니다.

patch는 버그 수정, minor는 새 기능 추가, major는 breaking change에 사용합니다. 이 명령어는 package.json의 version을 업데이트하고, Git 태그도 자동으로 생성합니다.

이후 npm publish를 실행하면 새 버전이 배포됩니다. 실제 배포는 npm publish 한 줄로 완료됩니다.

scoped 패키지(@username/package)는 기본적으로 private이므로 --access public 옵션을 추가해야 합니다. --tag 옵션으로 특정 태그(beta, alpha, next 등)를 붙일 수 있는데, 이러면 사용자들이 npm install package@beta 형태로 특정 버전을 설치할 수 있습니다.

태그를 지정하지 않으면 자동으로 latest 태그가 붙습니다. 배포 후 npm info your-package-name으로 배포가 성공했는지 확인합니다.

이 명령어는 패키지의 모든 메타데이터를 보여주며, 최신 버전, 모든 버전 목록, 다운로드 URL 등을 확인할 수 있습니다. 보통 배포 후 1-2분 내에 전 세계 CDN에 전파됩니다.

여러분이 이 프로세스를 따르면 안정적이고 신뢰할 수 있는 패키지를 배포할 수 있습니다. 많은 성공적인 오픈소스 프로젝트들이 이런 체계적인 배포 프로세스를 사용하고 있습니다.

실전 팁

💡 package.json에 prepublishOnly 스크립트를 추가하여 배포 전 자동으로 빌드와 테스트를 실행하세요. 실수로 빌드되지 않은 코드를 배포하는 것을 방지할 수 있습니다.

💡 처음 배포하는 패키지라면 먼저 --dry-run 옵션으로 시뮬레이션하세요. 실제 배포 없이 어떤 파일이 포함되는지, 오류는 없는지 확인할 수 있습니다.

💡 배포 후에는 실제로 npm install로 설치해보고 제대로 작동하는지 테스트하세요. 별도의 디렉토리에서 테스트하면 로컬 캐시 문제를 피할 수 있습니다.

💡 한 번 배포된 버전은 삭제할 수 없습니다(24시간 내 unpublish 가능하지만 권장되지 않음). 따라서 중요한 패키지는 항상 신중하게 배포하고, 가능하면 beta 태그로 먼저 테스트하세요.

💡 CI/CD에서 자동 배포할 때는 NPM_TOKEN 환경 변수를 사용하고, 배포 권한은 최소한의 인원에게만 부여하세요. GitHub Secrets나 GitLab Variables를 활용하면 안전합니다.


5. 시맨틱 버저닝

시작하며

여러분의 패키지를 사용하는 프로젝트가 100개라고 상상해보세요. 업데이트를 배포했는데 API를 변경했고, 100개 프로젝트가 모두 동시에 깨졌다면 어떻게 될까요?

이런 상황은 실제로 자주 발생합니다. 버전 번호의 의미를 무시하고 무작위로 증가시키면, 사용자들은 안전하게 업데이트할 수 없게 됩니다.

"이 업데이트가 breaking change인지 버그 수정인지 어떻게 알아?" 하는 불만이 쏟아지겠죠. 바로 이럴 때 필요한 것이 시맨틱 버저닝(Semantic Versioning)입니다.

버전 번호 자체에 의미를 담아 사용자들이 안전하게 업데이트할 수 있도록 합니다.

개요

간단히 말해서, 시맨틱 버저닝은 버전 번호를 MAJOR.MINOR.PATCH 형식으로 관리하는 표준 규칙입니다. MAJOR는 breaking change(기존 API 변경), MINOR는 새로운 기능 추가(하위 호환), PATCH는 버그 수정을 의미합니다.

예를 들어, 2.4.7에서 3.0.0으로 올라가면 사용자는 "breaking change가 있구나" 하고 바로 알 수 있죠. 기존에는 버전을 임의로 관리하여 사용자들이 혼란스러워했습니다.

이제는 버전 번호만 보고도 업데이트의 영향 범위를 예측할 수 있습니다. 시맨틱 버저닝을 따르면 ^1.2.3 (1.x.x 범위), ~1.2.3 (1.2.x 범위) 같은 버전 범위 지정이 의미를 가집니다.

NPM은 이 규칙에 따라 자동으로 안전한 버전만 업데이트합니다. 또한 CHANGELOG를 작성할 때도 버전별로 변경사항을 명확히 분류할 수 있습니다.

코드 예제

// 버전 번호 형식: MAJOR.MINOR.PATCH

// 1. PATCH 버전 증가 (1.0.0 -> 1.0.1)
// 버그 수정, 내부 리팩토링, 성능 개선
npm version patch

// 예시: 기존 함수의 버그 수정
function sum(a, b) {
  return a + b;  // 버그 수정: 이전엔 a - b 였음
}

// 2. MINOR 버전 증가 (1.0.1 -> 1.1.0)
// 새 기능 추가, 기존 API는 그대로 유지
npm version minor

// 예시: 새로운 함수 추가 (기존 함수는 유지)
function multiply(a, b) {  // 새 기능
  return a * b;
}

// 3. MAJOR 버전 증가 (1.1.0 -> 2.0.0)
// Breaking change, 기존 API 변경
npm version major

// 예시: 함수 시그니처 변경 (기존 코드 깨짐)
function sum(...numbers) {  // 파라미터 개수 변경!
  return numbers.reduce((a, b) => a + b, 0);
}

// 4. Pre-release 버전
npm version prerelease --preid=beta
// 1.0.0 -> 1.0.0-beta.0

// 5. package.json에서 의존성 버전 범위
{
  "dependencies": {
    "package-a": "^2.3.4",  // 2.3.4 <= version < 3.0.0
    "package-b": "~1.2.3",  // 1.2.3 <= version < 1.3.0
    "package-c": "1.0.0"    // 정확히 1.0.0만
  }
}

설명

이것이 하는 일: 시맨틱 버저닝은 버전 번호에 명확한 의미를 부여하여 사용자들이 업데이트의 안전성을 판단할 수 있게 합니다. 첫 번째로, PATCH 버전은 버그 수정이나 내부 개선에 사용합니다.

외부 API는 전혀 변경되지 않으므로 사용자는 아무 걱정 없이 업데이트할 수 있습니다. 예를 들어, 함수 내부 로직의 버그를 고치거나, 성능을 개선하거나, 문서를 업데이트하는 경우가 여기에 해당합니다.

npm version patch 명령어는 1.0.01.0.1로 자동 증가시킵니다. 그 다음으로, MINOR 버전은 새로운 기능을 추가할 때 사용합니다.

핵심은 하위 호환성입니다. 기존 코드는 전혀 수정하지 않아도 되고, 원하는 사람만 새 기능을 사용할 수 있습니다.

예를 들어, 기존에 sum(), subtract() 함수가 있었는데 multiply() 함수를 추가하는 경우입니다. 기존 사용자는 영향을 받지 않고, 새로운 기능이 필요한 사용자만 사용하면 됩니다.

MAJOR 버전은 breaking change가 있을 때 사용합니다. 기존 API를 변경하거나, 함수 시그니처를 바꾸거나, 반환 타입을 변경하는 등 사용자 코드를 수정해야 하는 모든 변경이 여기에 해당합니다.

예를 들어, sum(a, b) 함수를 sum(...numbers) 형태로 바꾸면 기존 코드는 작동하지만 의미가 달라질 수 있으므로 MAJOR 버전을 올려야 합니다. Pre-release 버전은 정식 배포 전 테스트용으로 사용합니다.

1.0.0-beta.0, 2.0.0-rc.1 같은 형태로, 시맨틱 버저닝 규칙에 따라 정식 버전보다 낮은 것으로 간주됩니다. 사용자가 npm install package@beta처럼 명시적으로 지정해야 설치되므로, 안정성이 검증되지 않은 버전을 배포할 때 유용합니다.

NPM의 버전 범위 지정도 시맨틱 버저닝을 기반으로 합니다. ^2.3.4는 "2.3.4 이상 3.0.0 미만"을 의미하여 MINOR와 PATCH 업데이트는 자동으로 받지만 MAJOR 업데이트는 받지 않습니다.

~1.2.3은 "1.2.3 이상 1.3.0 미만"으로 PATCH 업데이트만 자동으로 받습니다. 이렇게 하면 안전한 업데이트는 자동화하고 위험한 업데이트는 수동으로 관리할 수 있습니다.

여러분이 시맨틱 버저닝을 철저히 따르면 사용자들은 여러분의 패키지를 신뢰하고 적극적으로 사용하게 됩니다. 반대로 이 규칙을 무시하면 사용자들은 업데이트를 두려워하고 구버전에 머물게 되어 생태계가 정체됩니다.

실전 팁

💡 1.0.0 이전 버전(0.x.y)은 개발 단계로 간주되어 언제든 breaking change가 가능합니다. 안정적인 API가 완성되면 1.0.0으로 올리세요.

💡 CHANGELOG.md 파일을 작성하여 각 버전의 변경사항을 명확히 기록하세요. "Added", "Changed", "Deprecated", "Removed", "Fixed", "Security" 섹션으로 분류하면 좋습니다.

💡 의존성 업데이트 시 npm outdated로 안전한 업데이트를 확인하고, npm update는 MINOR/PATCH만 업데이트합니다. MAJOR 업데이트는 수동으로 확인하세요.

💡 Conventional Commits 규칙(feat:, fix:, BREAKING CHANGE:)을 사용하면 커밋 메시지만으로 버전을 자동 결정할 수 있습니다. semantic-release 같은 도구가 이를 자동화합니다.

💡 breaking change를 도입할 때는 먼저 Deprecation 경고를 추가하고(MINOR), 다음 MAJOR 버전에서 실제로 제거하세요. 사용자에게 준비할 시간을 주는 것이 좋습니다.


6. npm unpublish와 deprecate

시작하며

여러분이 패키지를 배포했는데 5분 후 치명적인 보안 취약점을 발견했다면 어떻게 하시겠어요? 혹은 실수로 개발 중인 코드를 배포해버렸다면?

이런 긴급 상황에서는 빠르게 대응해야 합니다. 하지만 무작정 패키지를 삭제하면 이미 설치한 사용자들의 프로젝트가 깨질 수 있습니다.

또한 NPM은 보안과 안정성을 위해 패키지 삭제에 엄격한 제한을 두고 있죠. 바로 이럴 때 필요한 것이 npm unpublish와 deprecate 명령어입니다.

상황에 따라 적절한 방법을 선택하여 문제를 해결할 수 있습니다.

개요

간단히 말해서, unpublish는 패키지를 완전히 삭제하고, deprecate는 사용 중단 경고를 표시하는 명령어입니다. unpublish는 배포 후 24시간 이내에만 사용할 수 있고, 다른 프로젝트가 의존하고 있지 않을 때만 가능합니다.

반면 deprecate는 언제든 사용할 수 있으며, 패키지를 삭제하지 않고 경고 메시지만 표시합니다. 사용자가 npm install할 때 "DEPRECATED: 이 패키지는 더 이상 유지보수되지 않습니다" 같은 메시지를 보게 됩니다.

기존에는 문제가 있는 패키지를 그냥 방치하거나 무작정 삭제해서 생태계를 깨뜨렸습니다. 이제는 상황에 맞는 적절한 대응 방법을 선택할 수 있습니다.

unpublish는 정말 긴급한 상황(보안 취약점, 민감한 정보 포함)에만 사용하고, 대부분의 경우 deprecate를 사용하는 것이 권장됩니다. 패키지를 더 이상 유지보수하지 않거나, 더 나은 대안이 있거나, 특정 버전에 심각한 버그가 있을 때 deprecate를 사용하면 됩니다.

코드 예제

// 1. 특정 버전 삭제 (24시간 이내만 가능)
npm unpublish my-package@1.0.0

// 2. 전체 패키지 삭제 (신중하게!)
npm unpublish my-package --force

// 3. 특정 버전에 deprecation 경고 추가
npm deprecate my-package@1.0.0 "Critical bug - please upgrade to 1.0.1"

// 4. 모든 버전에 deprecation 경고
npm deprecate my-package "This package is no longer maintained. Use 'new-package' instead"

// 5. 버전 범위에 deprecation 적용
npm deprecate my-package@"< 2.0.0" "Versions below 2.0.0 are deprecated. Please upgrade"

// 6. deprecation 해제
npm deprecate my-package@1.0.0 ""

// 7. 패키지 정보 확인 (deprecation 상태 확인)
npm view my-package

// 8. 실수로 배포한 경우 즉시 대응
npm unpublish my-package@1.0.0  // 24시간 내면 삭제
npm version patch                // 1.0.1로 버전 업
npm publish                      // 수정된 버전 배포
npm deprecate my-package@1.0.0 "Accidental publish - please use 1.0.1"

설명

이것이 하는 일: unpublish와 deprecate는 문제가 있는 패키지를 관리하여 사용자에게 위험을 알리거나 제거합니다. 첫 번째로, unpublish는 패키지를 완전히 삭제합니다.

하지만 NPM 정책상 여러 제약이 있습니다. 배포 후 24시간이 지나면 삭제할 수 없고, 다른 패키지가 의존하고 있으면 삭제할 수 없으며, 주간 다운로드가 300회를 초과하면 삭제할 수 없습니다.

이는 left-pad 사건(한 패키지 삭제로 수천 개 프로젝트가 동시에 깨진 사건) 이후 도입된 규칙입니다. 따라서 unpublish는 정말 방금 실수로 배포했거나 심각한 보안 문제가 있을 때만 사용하세요.

그 다음으로, deprecate는 패키지를 삭제하지 않고 경고만 표시합니다. 사용자가 npm install your-package를 실행하면 터미널에 노란색 경고 메시지가 나타납니다.

기존에 설치된 프로젝트는 영향을 받지 않지만, 새로 설치하는 사람들은 경고를 보고 판단할 수 있습니다. 경고 메시지에는 이유와 대안을 명확히 작성하는 것이 좋습니다.

예: "이 버전은 심각한 버그가 있습니다. 1.0.1로 업그레이드하세요" 또는 "이 패키지는 더 이상 유지보수되지 않습니다.

@scope/new-package를 사용하세요" 특정 버전만 deprecate할 수도 있고, 버전 범위로 여러 버전을 한 번에 deprecate할 수도 있습니다. npm deprecate my-package@"< 2.0.0" "..." 형태로 사용하면 2.0.0 미만의 모든 버전에 경고가 표시됩니다.

이는 breaking change가 있는 MAJOR 업데이트 후 구버전 사용자들에게 업그레이드를 권장할 때 유용합니다. 실수로 배포한 경우의 올바른 대응 프로세스는 다음과 같습니다.

먼저 24시간 내라면 unpublish로 삭제하고, 즉시 수정된 버전을 배포합니다. 만약 24시간이 지났다면 deprecate로 경고를 추가하고, 새 버전을 배포한 뒤 사용자들에게 공지합니다.

보안 취약점의 경우 GitHub Security Advisory를 함께 발행하면 사용자들이 자동으로 알림을 받습니다. deprecate를 해제하려면 빈 문자열을 전달하면 됩니다.

npm deprecate my-package@1.0.0 ""처럼 사용하면 경고가 제거됩니다. 하지만 이미 많은 사용자가 경고를 본 상태라면 신뢰를 회복하기 어려우므로 신중하게 결정하세요.

여러분이 이 명령어들을 올바르게 사용하면 문제 상황에서도 사용자들에게 피해를 최소화하고, 책임감 있는 패키지 관리자로 신뢰를 얻을 수 있습니다. 반대로 무분별한 삭제나 경고 없는 breaking change는 커뮤니티의 신뢰를 잃게 됩니다.

실전 팁

💡 패키지를 삭제하고 싶다면 먼저 deprecate로 6개월 정도 경고 기간을 두세요. 사용자들이 대안으로 마이그레이션할 시간을 주는 것이 예의입니다.

💡 보안 취약점이 발견되면 즉시 패치 버전을 배포하고, 취약한 버전을 deprecate하세요. 그리고 package.json에 security 필드로 연락처를 추가하여 향후 보고를 받을 수 있게 하세요.

💡 deprecate 메시지에는 반드시 "왜"와 "대안"을 포함하세요. "Deprecated"만 쓰면 사용자들이 혼란스러워합니다. "Use @org/new-package instead" 같은 명확한 가이드를 제공하세요.

💡 unpublish 후 같은 이름과 버전을 다시 배포할 수 없습니다. NPM은 모든 배포 이력을 추적하므로, 실수로 삭제했다면 버전을 올려서 재배포해야 합니다.

💡 인기 있는 패키지를 유지보수할 수 없게 되면 다른 관리자에게 소유권을 이전하세요. npm owner add <user> 명령어로 collaborator를 추가할 수 있습니다.


7. Scoped Packages

시작하며

여러분이 회사에서 내부 라이브러리를 만들었는데, 이름이 너무 흔해서 NPM에 이미 존재한다면 어떻게 하시겠어요? utils, helpers, common 같은 이름은 이미 수천 개가 있습니다.

또한 회사의 모든 패키지를 하나의 네임스페이스로 그룹화하고 싶을 수도 있습니다. 사용자들이 "이건 우리 회사 공식 패키지구나" 하고 바로 알 수 있도록 말이죠.

바로 이럴 때 필요한 것이 Scoped Packages입니다. @company/utils 형태로 조직이나 사용자 네임스페이스 안에서 패키지를 관리할 수 있습니다.

개요

간단히 말해서, Scoped Packages는 @scope/package-name 형태로 네임스페이스를 가진 패키지입니다. scope는 보통 NPM 사용자명이나 조직명이 됩니다.

예를 들어, @react/core, @google/maps, @yourcompany/ui-components 같은 형태죠. 이렇게 하면 이름 충돌을 피할 수 있고, 관련 패키지를 한눈에 파악할 수 있으며, 조직의 브랜딩을 강화할 수 있습니다.

기존에는 패키지 이름에 prefix를 붙여서 구분했습니다. react-router, babel-plugin-* 같은 형태로 말이죠.

이제는 scope를 사용하여 공식적인 네임스페이스를 만들 수 있습니다. Scoped Packages는 기본적으로 private(비공개)입니다.

무료 계정에서는 공개 scoped package를 무제한으로 만들 수 있지만, private scoped package는 유료입니다. 회사 내부에서만 사용하는 패키지라면 NPM Enterprise나 사설 레지스트리(Verdaccio, GitHub Packages 등)를 고려할 수 있습니다.

코드 예제

// 1. Scoped package 생성
// package.json
{
  "name": "@mycompany/ui-components",  // @scope/name 형식
  "version": "1.0.0",
  "description": "Reusable UI components for our products",
  "main": "./dist/index.js",
  "repository": "https://github.com/mycompany/ui-components"
}

// 2. 조직 생성 (웹사이트에서 진행)
// https://www.npmjs.com/org/create
// 또는 CLI로
npm org create mycompany

// 3. Public scoped package 배포
npm publish --access public

// 4. Private scoped package 배포 (유료)
npm publish --access restricted

// 5. Scoped package 설치
npm install @mycompany/ui-components

// 6. 코드에서 사용
import { Button, Input } from '@mycompany/ui-components';

// 7. 다른 사용자에게 조직 권한 부여
npm org set mycompany developers <username>

// 8. 여러 scoped packages 관리
// @mycompany/ui-components
// @mycompany/utils
// @mycompany/api-client
// @mycompany/styles

설명

이것이 하는 일: Scoped Packages는 패키지에 네임스페이스를 부여하여 이름 충돌을 방지하고 조직의 패키지를 체계적으로 관리합니다. 첫 번째로, package.json의 name 필드를 @scope/package-name 형식으로 작성합니다.

scope는 여러분의 NPM 사용자명이거나 소속된 조직명이어야 합니다. 예를 들어, 사용자명이 john-dev라면 @john-dev/my-lib 형태로 패키지를 만들 수 있습니다.

조직을 만들었다면 @mycompany/my-lib 형태로 회사의 모든 패키지를 그룹화할 수 있죠. 그 다음으로, 조직(organization)을 생성할 수 있습니다.

NPM 웹사이트에서 조직을 만들면 팀원들을 초대하고, 패키지 관리 권한을 부여하고, 통합된 결제를 관리할 수 있습니다. 무료 조직은 무제한 공개 패키지를 지원하고, 유료 조직은 private package와 더 많은 팀 관리 기능을 제공합니다.

많은 회사들이 @microsoft, @google, @facebook 같은 공식 조직을 운영하고 있습니다. Scoped package를 배포할 때는 --access 플래그가 중요합니다.

기본값이 restricted(private)이므로, 공개 패키지로 만들려면 반드시 --access public을 지정해야 합니다. Private package는 인증된 사용자만 접근할 수 있으며, npm login으로 로그인한 상태에서만 설치할 수 있습니다.

회사 내부 라이브러리를 외부에 노출하지 않고 관리하기에 적합합니다. 설치와 사용은 일반 패키지와 동일합니다.

npm install @scope/package로 설치하고, import 문에서도 @scope/package를 그대로 사용합니다. 유일한 차이점은 이름에 @가 붙는다는 것뿐이죠.

이는 Node.js의 모듈 해석 규칙에서 자연스럽게 지원됩니다. 여러 개의 scoped package를 만들면 monorepo 스타일의 구조를 만들 수 있습니다.

예를 들어, @mycompany/ui-components, @mycompany/utils, @mycompany/api-client처럼 관련 패키지들을 하나의 scope 아래에 모을 수 있습니다. 사용자들은 npm search @mycompany로 여러분의 모든 패키지를 한 번에 찾을 수 있고, 일관된 네이밍으로 신뢰성도 높아집니다.

여러분이 scoped package를 사용하면 전문적인 이미지를 구축하고, 패키지 관리를 체계화하며, 팀 협업을 효율적으로 할 수 있습니다. 특히 여러 개의 관련 패키지를 관리하는 경우 필수적입니다.

실전 팁

💡 개인 프로젝트라도 scoped package를 사용하세요. 나중에 이름이 충돌할 걱정이 없고, 포트폴리오로도 전문적으로 보입니다.

💡 조직의 모든 패키지에 일관된 네이밍 규칙을 적용하세요. 예: @company/ui-, @company/api-, @company/utils-* 같은 패턴을 사용하면 목적을 쉽게 파악할 수 있습니다.

💡 Private package를 사용한다면 .npmrc 파일에 registry 정보와 인증 토큰을 설정하세요. CI/CD 환경에서도 자동으로 설치되도록 환경 변수로 관리하세요.

💡 GitHub Packages나 GitLab Package Registry는 무료로 private npm registry를 제공합니다. NPM 유료 플랜 대신 이를 사용할 수도 있습니다.

💡 조직 내에서 팀(team)을 만들어 권한을 세분화하세요. developers, admins, read-only 같은 팀을 만들어 패키지별로 다른 권한을 부여할 수 있습니다.


8. 배포 전 테스트

시작하며

여러분이 자신있게 패키지를 배포했는데, 사용자들이 "설치가 안 돼요", "import 에러가 나요" 같은 이슈를 올린다면 당황스러울 것입니다. 이런 문제는 대부분 로컬 환경에서는 잘 작동하지만 실제 설치 환경에서는 다르게 동작해서 발생합니다.

빌드 파일이 누락되었거나, package.json 설정이 잘못되었거나, 의존성 문제가 있을 수 있죠. 바로 이럴 때 필요한 것이 배포 전 철저한 테스트입니다.

npm pack으로 실제 배포 패키지를 시뮬레이션하고, 깨끗한 환경에서 설치해보는 것이 중요합니다.

개요

간단히 말해서, npm pack은 배포될 패키지를 tarball 파일로 만들어 실제 설치를 시뮬레이션하는 명령어입니다. 이 명령어는 .npmignore와 package.json의 files 필드를 기반으로 포함될 파일을 결정하고, 실제 NPM 서버에 업로드될 내용과 동일한 .tgz 파일을 생성합니다.

이 파일을 다른 프로젝트에서 로컬로 설치해보면 실제 사용자가 겪을 문제를 미리 발견할 수 있습니다. 기존에는 배포하고 나서야 문제를 발견하여 급하게 패치 버전을 올려야 했습니다.

이제는 배포 전에 문제를 찾아 수정할 수 있습니다. npm link를 사용할 수도 있지만, link는 심볼릭 링크를 사용하므로 실제 설치와는 다릅니다.

반면 npm pack은 실제 배포 프로세스를 그대로 재현하므로 더 정확한 테스트가 가능합니다. 특히 빌드된 파일만 배포하는 경우 link로는 테스트할 수 없지만 pack은 가능합니다.

코드 예제

// 1. 배포 패키지 미리보기 (파일 목록만 확인)
npm pack --dry-run

// 2. 실제 tarball 생성
npm pack
// 출력: mypackage-1.0.0.tgz

// 3. tarball 내용 확인
tar -tzf mypackage-1.0.0.tgz
// package/package.json
// package/dist/index.js
// package/README.md
// ...

// 4. tarball 압축 해제하여 상세 확인
tar -xzf mypackage-1.0.0.tgz
cd package
ls -la

// 5. 테스트 프로젝트 생성
mkdir test-install
cd test-install
npm init -y

// 6. 로컬 tarball 설치
npm install ../mypackage-1.0.0.tgz

// 7. 실제 사용 테스트
// test.js
const myPackage = require('mypackage');
// 또는 ES Module
import myPackage from 'mypackage';

console.log(myPackage); // 정상 동작 확인

// 8. TypeScript 타입 체크
// test.ts
import { MyFunction } from 'mypackage';
const result: string = MyFunction(); // 타입이 제대로 작동하는지 확인

// 9. 자동화된 테스트 스크립트 (package.json)
{
  "scripts": {
    "prepack": "npm run build && npm run test",
    "pack:test": "npm pack && npm install -g $(npm pack | tail -1)"
  }
}

설명

이것이 하는 일: npm pack은 실제 배포 프로세스를 시뮬레이션하여 문제를 사전에 발견하고 수정할 수 있게 합니다. 첫 번째로, npm pack --dry-run으로 어떤 파일이 포함될지 미리 확인합니다.

이 명령어는 실제 파일을 생성하지 않고 목록만 출력하므로 빠르게 확인할 수 있습니다. 여기서 예상치 못한 파일이 포함되거나 필요한 파일이 누락되면 .npmignore나 package.json의 files 필드를 수정해야 합니다.

그 다음으로, npm pack을 실행하여 실제 tarball 파일을 생성합니다. 이 .tgz 파일은 사용자들이 npm install 했을 때 다운로드하는 것과 동일합니다.

파일명은 package-name-version.tgz 형식이며, 현재 디렉토리에 생성됩니다. 이 파일의 크기를 확인하면 배포 패키지가 너무 크지 않은지 점검할 수 있습니다.

tarball의 내용을 확인하려면 tar -tzf로 파일 목록을 보거나, tar -xzf로 압축을 풀어봅니다. 압축을 풀면 package/ 디렉토리가 생성되고 그 안에 모든 파일이 들어있습니다.

이 구조가 바로 사용자의 node_modules/your-package/에 설치될 내용입니다. 여기서 빌드 파일, README, package.json 등이 제대로 포함되었는지 확인하세요.

실제 설치 테스트를 위해 별도의 테스트 디렉토리를 만들고, 그곳에서 tarball을 설치합니다. npm install ../path/to/package.tgz 형태로 로컬 파일을 설치할 수 있습니다.

이렇게 하면 실제 사용자 환경과 동일한 방식으로 패키지가 설치되므로, 심볼릭 링크나 로컬 캐시의 영향 없이 순수하게 테스트할 수 있습니다. 설치 후 실제로 사용해보는 것이 가장 중요합니다.

CommonJS와 ES Module 방식으로 모두 import해보고, TypeScript 타입이 제대로 작동하는지 확인하고, 주요 기능이 정상 동작하는지 테스트합니다. 특히 TypeScript 패키지라면 .d.ts 타입 정의 파일이 제대로 노출되는지, IDE에서 자동완성이 작동하는지 확인하는 것이 중요합니다.

자동화된 테스트 스크립트를 package.json에 추가하면 더욱 편리합니다. prepack hook을 사용하면 npm pack 실행 전에 자동으로 빌드와 테스트가 실행됩니다.

이렇게 하면 실수로 빌드되지 않은 코드를 배포하는 것을 방지할 수 있습니다. 여러분이 이 프로세스를 거치면 배포 후 "안 돼요" 이슈를 대폭 줄일 수 있습니다.

프로페셔널한 오픈소스 프로젝트들은 모두 배포 전 테스트를 철저히 수행합니다.

실전 팁

💡 다양한 Node.js 버전에서 테스트하세요. nvm(Node Version Manager)으로 여러 버전을 설치하고, 각 버전에서 패키지를 테스트하면 호환성 문제를 미리 발견할 수 있습니다.

💡 Windows, macOS, Linux 모두에서 테스트하는 것이 이상적입니다. GitHub Actions의 matrix strategy를 사용하면 여러 OS에서 자동으로 테스트할 수 있습니다.

💡 로컬 레지스트리(Verdaccio)를 사용하면 실제 NPM에 배포하지 않고도 완전히 동일한 환경에서 테스트할 수 있습니다. CI/CD에 통합하면 모든 PR마다 자동 테스트가 가능합니다.

💡 npm pack으로 생성된 .tgz 파일은 Git에 커밋하지 마세요. 자동 생성 파일이므로 .gitignore에 *.tgz를 추가하세요.

💡 패키지 크기를 확인하세요. 수백 KB를 넘어가면 사용자들이 설치를 꺼립니다. bundlephobia.com에서 비슷한 패키지들의 크기를 비교해보세요.


9. GitHub Actions 자동 배포

시작하며

여러분이 패키지를 업데이트할 때마다 로컬에서 빌드하고, 테스트하고, 버전 올리고, 수동으로 배포한다면 시간이 많이 걸리고 실수하기 쉽습니다. 또한 팀원이 여러 명이라면 "누가 배포했지?", "이 버전은 어떤 커밋인지?" 같은 혼란이 생길 수 있습니다.

배포 프로세스가 일관되지 않으면 품질도 일정하지 않게 됩니다. 바로 이럴 때 필요한 것이 GitHub Actions를 통한 자동 배포입니다.

Git 태그를 푸시하거나 release를 만들면 자동으로 빌드, 테스트, 배포가 진행됩니다.

개요

간단히 말해서, GitHub Actions는 GitHub 이벤트(push, pull request, release 등)에 반응하여 자동으로 작업을 실행하는 CI/CD 플랫폼입니다. NPM 패키지 배포를 자동화하면 사람의 실수를 줄이고, 배포 이력을 투명하게 관리하며, 일관된 품질을 보장할 수 있습니다.

특히 모든 테스트를 통과한 코드만 배포되도록 강제할 수 있어 안정성이 크게 향상됩니다. 기존에는 Jenkins, Travis CI 같은 별도의 CI/CD 도구를 설정해야 했습니다.

이제는 GitHub에 내장된 Actions를 사용하여 설정 파일 하나로 완전한 CI/CD 파이프라인을 구축할 수 있습니다. GitHub Actions의 장점은 무료 tier가 관대하고(public 저장소는 무제한), YAML 파일로 간단히 설정할 수 있으며, NPM 배포에 필요한 공식 액션들이 이미 존재한다는 것입니다.

또한 Secrets 기능으로 NPM_TOKEN 같은 민감한 정보를 안전하게 관리할 수 있습니다.

코드 예제

# .github/workflows/publish.yml
name: Publish to NPM

# 트리거: 새 release 생성 시 또는 v* 태그 푸시 시
on:
  release:
    types: [created]
  push:
    tags:
      - 'v*'

jobs:
  publish:
    runs-on: ubuntu-latest

    steps:
      # 1. 코드 체크아웃
      - uses: actions/checkout@v3

      # 2. Node.js 설정
      - uses: actions/setup-node@v3
        with:
          node-version: '18'
          registry-url: 'https://registry.npmjs.org'

      # 3. 의존성 설치
      - run: npm ci

      # 4. 테스트 실행 (테스트 실패하면 배포 중단)
      - run: npm test

      # 5. 빌드 실행
      - run: npm run build

      # 6. NPM에 배포
      - run: npm publish --access public
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

# 다양한 Node 버전에서 테스트하는 workflow
# .github/workflows/test.yml
name: Test

on: [push, pull_request]

jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        node-version: [14, 16, 18, 20]
        os: [ubuntu-latest, windows-latest, macos-latest]

    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}
      - run: npm ci
      - run: npm test

설명

이것이 하는 일: GitHub Actions는 Git 이벤트를 트리거로 자동화된 빌드, 테스트, 배포 파이프라인을 실행합니다. 첫 번째로, workflow 파일을 .github/workflows/ 디렉토리에 작성합니다.

YAML 형식으로 작성하며, 언제 실행할지(on), 어떤 작업을 할지(jobs), 각 단계는 무엇인지(steps)를 정의합니다. 예를 들어, on: release: types: [created]는 GitHub에서 새 release를 만들 때 자동으로 실행되도록 합니다.

또는 on: push: tags: ['v*']로 v로 시작하는 Git 태그를 푸시할 때 실행되도록 할 수 있습니다. 그 다음으로, 각 step을 정의합니다.

actions/checkout은 코드를 가져오고, actions/setup-node는 Node.js 환경을 설정합니다. npm ci는 package-lock.json을 기반으로 정확한 버전의 의존성을 설치하며(npm install보다 더 엄격하고 빠름), npm testnpm run build로 테스트와 빌드를 실행합니다.

이 중 하나라도 실패하면 workflow가 중단되어 문제 있는 코드가 배포되지 않습니다. NPM 배포는 npm publish 명령어로 수행하는데, 인증을 위해 NODE_AUTH_TOKEN 환경 변수가 필요합니다.

이 값은 GitHub Secrets에 저장된 NPM_TOKEN에서 가져옵니다. NPM_TOKEN은 npmjs.com의 Access Tokens 페이지에서 생성할 수 있으며, "Automation" 타입으로 만들어야 CI/CD에서 사용할 수 있습니다.

생성한 토큰을 GitHub 저장소의 Settings > Secrets > Actions에 추가하면 됩니다. Matrix strategy를 사용하면 여러 환경에서 동시에 테스트할 수 있습니다.

예제에서는 4개의 Node.js 버전(14, 16, 18, 20)과 3개의 OS(Ubuntu, Windows, macOS)에서 테스트하므로 총 12개의 조합이 병렬로 실행됩니다. 이렇게 하면 다양한 환경에서의 호환성을 보장할 수 있습니다.

하나라도 실패하면 배포가 중단됩니다. 실제 사용 시나리오는 다음과 같습니다.

개발자가 새 기능을 완성하고, npm version minor로 버전을 올리면, Git 태그가 자동 생성되고, 이 태그를 git push --tags로 푸시하면, GitHub Actions가 자동으로 실행됩니다. 몇 분 후 테스트와 빌드가 완료되고 NPM에 배포되며, GitHub Actions 페이지에서 전체 로그를 확인할 수 있습니다.

여러분이 이렇게 설정하면 배포 프로세스가 완전히 자동화되고, 팀 전체가 일관된 방식으로 배포하며, 모든 배포 이력이 Git과 GitHub에 명확히 기록됩니다. 이는 오픈소스 프로젝트의 표준 관행입니다.

실전 팁

💡 NPM_TOKEN은 "Automation" 타입으로 생성하고, 2FA가 활성화되어 있어도 CI/CD에서 사용할 수 있도록 설정하세요. "Publish" 권한만 부여하여 최소 권한 원칙을 따르세요.

💡 배포 전에 항상 테스트를 실행하도록 workflow를 구성하세요. if: success()로 이전 step이 성공했을 때만 다음 step을 실행하도록 할 수 있습니다.

💡 Semantic Release 같은 도구를 사용하면 커밋 메시지를 분석하여 버전을 자동으로 결정하고 CHANGELOG도 생성합니다. 완전 자동화된 배포 파이프라인을 구축할 수 있습니다.

💡 배포 성공/실패 알림을 Slack이나 Discord로 보내면 팀 전체가 즉시 알 수 있습니다. GitHub Actions Marketplace에서 관련 액션을 찾을 수 있습니다.

💡 concurrency 설정을 추가하여 동시에 여러 배포가 실행되지 않도록 하세요. 동시 배포는 버전 충돌을 일으킬 수 있습니다.


10. npm 보안 토큰 관리

시작하며

여러분의 NPM 계정이 해킹되어 악성 코드가 포함된 패키지가 배포된다면 어떻게 될까요? 여러분의 패키지를 사용하는 수천 개의 프로젝트가 위험에 노출됩니다.

실제로 NPM 생태계에서는 계정 탈취를 통한 공급망 공격(supply chain attack)이 종종 발생합니다. 약한 비밀번호, 노출된 토큰, 2FA 미설정 등이 주요 원인이죠.

바로 이럴 때 필요한 것이 철저한 보안 토큰 관리입니다. 올바른 토큰 타입 선택, 안전한 저장, 권한 최소화를 통해 보안을 강화할 수 있습니다.

개요

간단히 말해서, NPM 토큰은 비밀번호 대신 프로그래밍 방식으로 NPM 작업을 수행할 수 있게 해주는 인증 수단입니다. 토큰에는 여러 타입이 있습니다.

"Automation" 토큰은 CI/CD에서 사용하며 2FA를 우회할 수 있습니다. "Publish" 토큰은 패키지를 배포할 수 있고, "Read-only" 토큰은 private 패키지를 다운로드만 할 수 있습니다.

각 상황에 맞는 최소 권한의 토큰을 사용하는 것이 보안의 기본입니다. 기존에는 계정 비밀번호를 여기저기 입력해야 했습니다.

이제는 토큰을 사용하여 권한을 세분화하고, 유출 시 즉시 폐기할 수 있으며, 사용 이력도 추적할 수 있습니다. 토큰은 절대 코드에 하드코딩하거나 Git에 커밋하면 안 됩니다.

환경 변수나 Secrets 관리 도구를 사용하여 안전하게 저장해야 합니다. 또한 정기적으로 토큰을 교체(rotate)하고, 불필요한 토큰은 즉시 삭제하는 것이 좋습니다.

코드 예제

// 1. NPM 토큰 생성 (웹사이트에서)
// https://www.npmjs.com/settings/{username}/tokens
// "Generate New Token" 클릭
// 토큰 타입 선택:
// - Automation: CI/CD용, 2FA 우회 가능
// - Publish: 수동 배포용
// - Read-only: private 패키지 설치용

// 2. 로컬 환경에서 토큰 사용
// .bashrc 또는 .zshrc에 추가
export NPM_TOKEN=npm_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

// .npmrc 파일 (프로젝트 루트 또는 홈 디렉토리)
//registry.npmjs.org/:_authToken=${NPM_TOKEN}

// 3. CI/CD 환경 변수 설정
// GitHub Actions: Settings > Secrets > Actions
// NPM_TOKEN = npm_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

// workflow에서 사용
env:
  NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

// 4. Docker 환경에서 안전하게 사용
# Dockerfile
ARG NPM_TOKEN
RUN echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > .npmrc && \
    npm ci && \
    rm -f .npmrc  # 빌드 후 토큰 삭제!

// 5. 토큰 권한 확인
npm token list

// 6. 토큰 폐기
npm token revoke <token_id>

// 7. 2FA 설정 (강력 권장)
npm profile enable-2fa auth-and-writes

// 8. 보안 감사 자동화
// package.json
{
  "scripts": {
    "audit": "npm audit",
    "audit:fix": "npm audit fix",
    "prepublish": "npm audit --audit-level=high"
  }
}

// 9. .gitignore에 토큰 관련 파일 추가
.npmrc
.env
.env.local

설명

이것이 하는 일: NPM 토큰은 비밀번호보다 안전하고 유연한 인증 방식을 제공하며, 권한을 세분화하고 유출 시 빠르게 대응할 수 있게 합니다. 첫 번째로, npmjs.com에서 적절한 타입의 토큰을 생성합니다.

Automation 토큰은 CI/CD 전용으로, 2FA가 활성화되어 있어도 자동화된 배포가 가능합니다. Publish 토큰은 개인 컴퓨터에서 수동 배포할 때 사용하며, 2FA 검증이 필요할 수 있습니다.

Read-only 토큰은 private 패키지를 설치할 때만 사용하며, 배포 권한은 없습니다. 각 상황에 최소 권한의 토큰을 사용하는 것이 보안의 핵심입니다.

그 다음으로, 생성된 토큰을 안전하게 저장합니다. 로컬 개발 환경에서는 환경 변수로 관리하고, .npmrc 파일에서 ${NPM_TOKEN} 형태로 참조합니다.

절대 토큰 값을 직접 .npmrc에 쓰지 마세요. 이 파일이 실수로 Git에 커밋되면 토큰이 노출됩니다.

대신 환경 변수를 사용하면 .npmrc는 Git에 커밋해도 안전합니다. CI/CD 환경에서는 플랫폼의 Secrets 기능을 사용합니다.

GitHub Actions는 Repository Secrets, GitLab CI는 CI/CD Variables, CircleCI는 Project Settings에서 관리합니다. 이런 시스템들은 토큰을 암호화하여 저장하고, 로그에도 마스킹 처리하여 노출을 방지합니다.

workflow나 스크립트에서 ${{ secrets.NPM_TOKEN }} 형태로 참조하면 실제 값은 보이지 않습니다. Docker 빌드에서 토큰을 사용할 때는 특별히 주의해야 합니다.

Dockerfile에서 토큰을 사용하면 이미지 레이어에 남을 수 있습니다. Multi-stage build를 사용하거나, 토큰을 사용한 후 즉시 삭제하거나, Docker BuildKit의 --secret 기능을 사용하여 안전하게 처리해야 합니다.

절대 ENV NPM_TOKEN=... 형태로 작성하면 안 됩니다. 토큰 관리도 중요합니다.

npm token list로 활성화된 토큰을 확인하고, 사용하지 않는 토큰은 npm token revoke로 즉시 폐기하세요. 특히 퇴사한 팀원의 토큰, 테스트용으로 만들었던 토큰, 오래된 CI/CD 토큰 등을 주기적으로 정리해야 합니다.

많은 보안 사고가 잊혀진 오래된 토큰 때문에 발생합니다. 2FA(2단계 인증)는 필수입니다.

npm profile enable-2fa auth-and-writes로 활성화하면 로그인과 배포 시 모두 추가 인증이 필요합니다. 토큰이 유출되더라도 2FA가 있으면 악의적인 배포를 막을 수 있습니다(Automation 토큰 제외).

따라서 Automation 토큰은 특히 더 철저히 관리해야 합니다. 여러분이 이런 보안 관행을 따르면 공급망 공격의 위험을 크게 줄일 수 있습니다.

오픈소스 생태계의 신뢰는 각 개발자의 보안 의식에서 시작됩니다.

실전 팁

💡 토큰에 의미 있는 이름을 붙이세요. "GitHub Actions - ProjectX", "Local Dev - MacBook Pro" 같은 이름으로 나중에 어디서 사용했는지 쉽게 파악할 수 있습니다.

💡 토큰 유출이 의심되면 즉시 폐기하고 새로 발급하세요. GitHub Secret Scanning이 public 저장소에서 토큰을 발견하면 자동으로 알림을 보내줍니다.

💡 회사 조직 패키지라면 개인 계정이 아닌 조직 계정의 토큰을 사용하세요. 팀원이 퇴사해도 배포 프로세스가 중단되지 않습니다.

💡 npm audit를 정기적으로 실행하여 의존성의 보안 취약점을 확인하세요. npm audit fix로 자동으로 안전한 버전으로 업데이트할 수 있습니다.

💡 Socket.dev, Snyk 같은 보안 도구를 사용하면 의존성 분석과 자동 알림을 받을 수 있습니다. 공급망 공격을 사전에 방지할 수 있습니다.


#Node.js#NPM#Package#Publishing#Deployment

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.