TL;DR
- npm weekly downloads는
install명령 실행 횟수가 아니라 패키지 tarball 요청에 대한 HTTP 200 응답 횟수에 가깝다. - 패키지 개발자가 자기 repo에서
pnpm install을 반복하는 것만으로는 자기 패키지 다운로드 수가 늘 가능성이 낮다. - publish 후 별도 프로젝트에서 smoke test로
pnpm add <package>를 실행하면 카운트 후보가 된다. - 로컬 git worktree는 보통 pnpm store를 공유한다. worktree마다 install해도 store hit이면 registry download가 발생하지 않는다.
- CI, Docker, 원격 에이전트처럼 store가 비어 있는 환경에서 dependency install이 반복되면 카운트가 늘 수 있다.
지인이 npm 패키지를 배포했다. 패키지를 만든 쪽은 publish 후 다른 프로젝트에서 smoke test를 했고, 패키지를 사용하는 쪽은 Claude Code 기반으로 기능 개발, 빌드, 테스트, Playwright 검증을 반복하고 있었다. npm weekly downloads 숫자는 예상보다 높았다. 실제 사용자는 사내 기준으로 두 명 정도였다. 그래서 질문은 “유저가 있나?”에서 “개발 과정 중 어떤 단계가 다운로드 카운트를 만들 수 있나?”로 바뀌었다. 추적할 후보는 네 가지였다.
| 가설 | 내용 |
|---|---|
| 1 | 패키지 개발자의 repo 내부 install/build/test가 카운트를 만든다 |
| 2 | publish 후 별도 프로젝트에서 진행한 smoke test가 카운트를 만든다 |
| 3 | 패키지를 사용하는 동료의 반복 pnpm install이 카운트를 만든다 |
| 4 | Claude Code, worktree, CI, 원격 검증 환경이 카운트를 만든다 |
이 가설들은 모두 install이라는 단어에서 갈린다. 핵심은 install과 download가 다르다는 점이다.
install과 download
install은 패키지 매니저 명령이다. 의존성 그래프를 해석하고, 필요한 패키지를 확보하고, node_modules 구조를 만든다. download는 그 과정 중 하나다. 로컬 캐시나 pnpm store에 없는 패키지를 registry에서 받아올 때만 발생한다.
pnpm install
-> lockfile 확인
-> 의존성 해석
-> pnpm store 확인
-> store에 없으면 registry에서 tarball fetch
-> store에 있으면 재사용
-> node_modules 연결
npm 다운로드 카운트와 연결되는 지점은 tarball fetch다. install 명령 전체가 아니다. tarball은 npm registry가 배포하는 패키지 압축 파일이다. 패키지의 package.json, 빌드 결과물, 타입 파일, README 등 publish 대상 파일이 하나의 압축 파일로 묶여 있다. pnpm add react처럼 registry 버전을 설치하면 패키지 매니저는 해당 버전의 metadata를 읽고, 필요한 경우 tarball URL에서 실제 패키지 파일을 받아온다. 서버 입장에서 볼 수 있는 것은 이 tarball 요청이다. 사용자가 터미널에서 install을 몇 번 입력했는지는 서버가 알 수 없다.
npm 다운로드 수의 기준
npm의 다운로드 카운트는 사람 수나 프로젝트 수가 아니다. npm 공식 설명은 패키지 tarball에 대한 HTTP 200 응답 횟수를 기준으로 설명한다. npm registry의 download-counts 문서도 다운로드 카운트 설명으로 같은 블로그 글을 연결한다. 따라서 다음 두 문장은 다르다.
pnpm install을 실행했다.
패키지 tarball을 registry에서 받아왔다.
첫 문장만으로는 npm 다운로드 수가 늘었다고 볼 수 없다. 두 번째 문장이 참일 때 카운트 후보가 된다. 반대로 사람이 아니어도 tarball을 받아가면 카운트된다. CI, 미러, 보안 스캐너, 분석 봇이 registry에서 패키지 파일을 받으면 다운로드 수에 들어갈 수 있다. 그래서 weekly downloads는 실제 사용자 수가 아니라 registry에서 관측한 tarball fetch 지표로 읽어야 한다.
가설 1. 패키지 repo 내부 작업
패키지를 개발한 사람이 자기 패키지 repo 안에서 pnpm install, pnpm build, pnpm test를 반복하는 경우다. 이 경우 자기 패키지의 다운로드 수가 늘 가능성은 낮다. repo 내부 pnpm install은 보통 그 패키지의 dependencies를 설치한다. 작업 중인 패키지 자신을 npm registry에서 tarball로 받아오는 흐름이 아니다.
package repo에서 pnpm install
-> react, typescript, vite 같은 dependencies 설치
-> 자기 패키지 tarball fetch 아님
물론 예외는 있다. repo 내부 테스트가 의도적으로 published package를 다시 설치하도록 구성되어 있다면 카운트 후보가 된다. 하지만 일반적인 package repo의 install/build/test만으로는 자기 패키지의 weekly downloads를 설명하기 어렵다. 판정은 이렇게 한다.
rg "<package-name>" package.json pnpm-lock.yaml
자기 repo 내부에서 자기 패키지를 registry dependency로 설치하고 있지 않다면 이 가설은 약하다.
가설 2. publish 후 smoke test
publish 후 별도 프로젝트에서 설치 테스트를 하는 경우는 다르다.
mkdir smoke-test
cd smoke-test
pnpm init
pnpm add <package-name>
이 명령은 registry에 올라간 <package-name>을 dependency로 설치한다. 해당 버전이 로컬 pnpm store에 없다면 tarball fetch가 발생하고, npm 다운로드 카운트 후보가 된다. 특히 새 버전을 publish한 직후라면 첫 smoke test는 store miss일 가능성이 높다. 같은 머신에서 같은 버전을 여러 번 설치하면 이후에는 store hit으로 끝날 수 있지만, 버전이 바뀌거나 환경이 바뀌면 다시 다운로드가 발생한다. 따라서 패키지 개발자의 카운트 기여 가능성은 repo 내부 작업보다 smoke test 쪽이 훨씬 높다.
가설 3. 사용자 동료의 반복 install
패키지를 사용하는 동료의 프로젝트가 registry 버전을 dependency로 선언하고 있다면 카운트 후보가 된다.
{
"dependencies": {
"<package-name>": "^1.0.0"
}
}
AI 기반 개발에서는 install이 더 자주 실행될 수 있다. Claude Code가 코드를 고치고, 빌드하고, 테스트하고, 필요한 경우 Playwright 검증까지 돌리면서 의존성 설치를 반복할 수 있다. 하지만 여기서도 반복 install 횟수보다 store 상태가 중요하다.
same machine + same pnpm store + same package version
-> reused
-> npm 다운로드 카운트 증가 가능성 낮음
new machine/session + empty pnpm store + registry dependency
-> downloaded
-> npm 다운로드 카운트 후보
pnpm 로그에서도 이 차이가 보인다.
Progress: resolved 261, reused 259, downloaded 2, added 261
reused는 store 재사용이다. downloaded는 store에 없어 받아온 패키지다. npm 카운트와 연결해서 봐야 하는 값은 install 실행 횟수가 아니라 downloaded 쪽이다.
가설 4. worktree와 격리 환경
git worktree는 별도 디렉터리에 작업 트리를 하나 더 만든다. AI 개발에서는 기능별로 worktree를 나누고, 각 경로에서 독립적으로 빌드와 테스트를 돌리기 좋다. 여기서 “격리”라는 단어가 문제를 만든다. 로컬 git worktree는 컨테이너가 아니다. 소스 디렉터리와 node_modules는 worktree마다 따로 생길 수 있지만, 같은 사용자 홈을 쓰면 pnpm store는 보통 공유된다.
local git worktree
source directory: 분리
node_modules: worktree별 생성
pnpm store: 같은 홈이면 공유
그래서 worktree마다 pnpm install이 필요한 것은 맞다. 하지만 같은 store에 패키지가 이미 있으면 registry에서 다시 tarball을 받지 않는다. 카운트를 만들 수 있는 환경은 따로 있다.
| 환경 | store 상태 | 카운트 가능성 |
|---|---|---|
| 로컬 worktree | 같은 홈의 store 공유 | store miss일 때만 |
| GitHub Actions | 캐시 없으면 빈 store | 높음 |
| Docker build | layer/volume cache 없으면 빈 store | 높음 |
| 원격 에이전트 세션 | 세션마다 store가 다를 수 있음 | 높음 |
| 임시 테스트 디렉터리 | 같은 홈이면 store 공유 | store miss일 때만 |
즉 원인은 worktree 자체가 아니라 store 격리다. 같은 머신의 worktree는 디렉터리 격리이고, CI나 컨테이너는 store 격리일 수 있다.
build가 아니라 install
AI 도구나 CI 로그에서는 build가 눈에 더 잘 띈다.
pnpm build
하지만 pnpm build는 이미 설치된 파일로 스크립트를 실행하는 명령이다. 일반적으로 이 명령 자체가 npm registry에서 패키지 tarball을 받지는 않는다. 카운트 후보는 build 앞의 install 단계다.
checkout
-> pnpm install --frozen-lockfile
-> pnpm build
-> pnpm test
-> playwright
CI에서 다운로드 수가 늘었다면 원인은 pnpm build가 아니라 pnpm install --frozen-lockfile 쪽에 있다. 해당 CI가 패키지를 dependency로 설치하고, pnpm store 캐시가 없고, registry에서 tarball을 받아왔다면 카운트가 생길 수 있다. 이 구분은 로컬 에이전트 실행에도 그대로 적용된다. 에이전트가 “검증을 위해 빌드했다”는 사실만으로는 다운로드가 생기지 않는다. 빌드 전에 깨끗한 환경을 만들고 install을 다시 수행했는지가 필요하다.
원인 후보 순위
지금 조건에서 개발 과정 안의 후보를 우선순위로 놓으면 이렇게 된다.
| 순위 | 후보 | 이유 |
|---|---|---|
| 1 | 사용 동료의 CI 또는 원격 에이전트 install | registry dependency + 빈 store 조건을 만족하기 쉬움 |
| 2 | publish 후 smoke test | 새 버전 첫 설치가 tarball fetch를 만들 수 있음 |
| 3 | 사용 동료의 로컬 반복 install | 같은 store면 반복 카운트는 제한적 |
| 4 | 패키지 개발자의 repo 내부 install/build/test | 자기 패키지 tarball을 받는 흐름이 아님 |
| 별도 | 봇·미러·보안 스캐너 | 개발 과정 밖의 배경 노이즈 |
이 표는 “정답”이 아니라 조사 순서다. 실제 원인을 좁히려면 각 후보에서 tarball fetch가 있었는지 확인해야 한다.
확인 방법
가장 먼저 볼 것은 패키지를 설치한 프로젝트의 dependency 선언이다.
rg '"<package-name>"' package.json pnpm-lock.yaml
workspace:*, link:, file:이면 registry tarball을 받지 않는다. semver range나 exact version이면 registry 설치 후보가 된다. 다음은 pnpm store다.
pnpm store path
pnpm install --offline
--offline 설치가 성공하면 필요한 패키지가 store에 있다는 뜻이다. 같은 환경에서 같은 버전을 다시 install해도 registry 다운로드 가능성은 낮다. CI에서는 install 로그와 cache 설정을 본다.
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
cache: pnpm
- run: pnpm install --frozen-lockfile
캐시가 없거나 키가 매번 바뀌면 CI는 빈 store에서 시작한다. 이 경우 dependency tarball을 반복해서 받을 수 있다. npm 쪽에서는 다운로드 분포를 시간 단위로 본다.
publish 직후 한 번 튐
-> smoke test, 미러, 보안 스캐너 후보
CI 실행 시간대마다 반복 튐 -> CI install 후보
퍼블리시 없이 매일 비슷한 수준
-> 실제 사용 프로젝트 또는 자동화 후보
weekly 숫자 하나만으로는 원인을 분리하기 어렵다. publish 시각, CI 실행 시각, 버전별 다운로드 분포를 같이 봐야 한다.
정리
npm 다운로드 수를 볼 때 가장 먼저 버려야 하는 등식은 이것이다.
install 횟수 = download count
정확한 기준은 tarball fetch다.
dependency로 registry 버전을 설치한다
+ 해당 버전이 store/cache에 없다
+ registry가 tarball 요청에 HTTP 200을 반환한다
= npm 다운로드 카운트 후보
이 기준으로 보면 패키지 개발자의 repo 내부 작업은 원인 후보에서 멀어진다. 반대로 publish 후 smoke test, 패키지를 사용하는 동료의 registry dependency install, CI나 원격 에이전트의 빈 store install은 유효한 후보로 남는다. worktree도 같은 기준으로 정리된다. 로컬 worktree는 npm 다운로드 수를 자동으로 늘리는 장치가 아니다. 다운로드 수를 늘릴 수 있는 것은 worktree가 아니라 store miss가 발생하는 install이다.