빌드노트
로컬에는 있는데 npm 패키지에는 템플릿 파일이 없다
로컬에 있는 템플릿이 npm tarball에서 빠졌는지 pack 목록과 실제 tar 항목으로 확인한다. files와 해당 위치의 .npmignore를 고친 뒤 별도 소비자에서 설치와 자원 읽기를 구분해 검사한다.
소스 폴더에서는 템플릿을 읽는데, npm pack으로 만든 패키지를 설치한 뒤에는 ENOENT가 난다면 포장된 파일 목록부터 확인한다. 로컬에 파일이 있어도 tarball에 포함되지 않으면 설치한 패키지는 읽을 수 없다. 생성 작업을 다시 돌리기 전에, 이미 있는 파일이 files나 .npmignore 규칙 때문에 빠졌는지 구분해야 한다.
외부 의존성 없는 작은 패키지에서 이 차이를 확인했다. index.mjs와 template.txt를 처음부터 두고 files만 바꿔 tarball 두 개를 만들었다. 둘 다 설치는 성공했지만, 템플릿을 빠뜨린 쪽만 읽기에서 실패했다. 실제 서비스 장애 기록이 아닌 별도 실험이다.
파일이 있는 상태에서 pack 목록을 본다
Node.js와 npm이 설치된 PowerShell 7에서, 배포할 패키지의 package.json이 있는 폴더를 현재 위치로 둔다. 먼저 오류에 나온 템플릿이 그 패키지 안에 실제로 있는지 확인한다. 파일을 생성하는 패키지라면 검토한 생성 작업을 끝낸 뒤 다음을 실행한다.
$pack = npm.cmd pack --dry-run --json --ignore-scripts
if ($LASTEXITCODE -ne 0) { throw 'pack 목록 확인 실패' }
($pack | ConvertFrom-Json)[0].files.path
--dry-run은 tarball을 저장하지 않고 처리할 내용을 보고한다. --json 결과의 files[].path에서 필요한 자원 경로를 찾는다. 이 명령 자체가 실패하면 목록 누락으로 판단하지 말고 pack 오류부터 해결한다. npm pack 옵션 설명 (새 탭에서 열림)
여기서는 이미 생성된 파일의 포함 여부를 보려고 --ignore-scripts로 자동 스크립트를 생략했다. 아직 파일이 없다면 생성 단계부터 확인해야 한다. 이 옵션을 붙인 검사를 평소 빌드 전체와 같다고 보면 안 된다. ignore-scripts의 적용 범위 (새 탭에서 열림)
실험 패키지의 files는 ["index.mjs"]였다. 로컬 실행은 HELLO_FROM_TEMPLATE을 출력했지만 위 목록에는 index.mjs, package.json만 있었다. JS는 new URL('./template.txt', import.meta.url)로 모듈 옆 파일을 읽었으므로 호출 폴더에 따른 경로 차이는 배제했다. Node.js의 모듈 기준 파일 읽기 (새 탭에서 열림)
미리보기와 실제 산출물도 대조한다. tar 명령이 필요하며, 패키지 밖에 빈 pack-check 폴더를 준비한 뒤 같은 패키지 폴더에서 실행한다. 마지막 명령의 .tgz 이름은 pack이 출력한 자신의 파일명으로 바꾼다.
npm.cmd pack --ignore-scripts --pack-destination ../pack-check
if ($LASTEXITCODE -ne 0) { throw 'pack 실패' }
tar -tzf ../pack-check/pack-template-lab-1.0.0.tgz
if ($LASTEXITCODE -ne 0) { throw 'tar 목록 읽기 실패' }
누락본의 실제 목록도 package/package.json, package/index.mjs 두 항목뿐이었다. dry-run은 패키지 기준 경로를, tar는 package/가 붙은 경로를 보여 줬다. 디스크에는 있던 template.txt가 두 목록에서 모두 빠졌으므로 이 경우에는 포함 규칙을 고칠 근거가 있다.
files와 해당 위치의 제외 규칙을 고친다
files는 tarball에 넣을 경로·패턴을 지정한다. package.json과 main에 지정한 파일은 별도 기본 포함 대상이다. 진입점이 들어 있다고 그 코드가 읽는 텍스트까지 들어 있는 것은 아니다. package.json의 files 규칙 (새 탭에서 열림)
실험에서는 기존 package.json의 files 필드만 다음처럼 바꿨다. 아래는 그 필드의 예시이며, 다른 메타데이터는 유지한다. 자신의 템플릿이 assets/template.txt에 있다면 그 경로나 필요한 자원 폴더를 지정한다.
{ "files": ["index.mjs", "template.txt"] }
JS와 템플릿 내용은 그대로 두고 앞의 dry-run과 실제 pack·tar 검사를 다시 실행했다. 이번에는 dry-run에 template.txt, tar 목록에 package/template.txt가 추가됐다. 같은 이름·버전으로 다시 포장하면 같은 위치의 .tgz를 덮어쓰므로, 전후 비교가 필요하면 수정 전 파일을 별도 위치에 보관한다. 설정만 바꾸고 예전 tarball을 검사하지 않아야 한다.
.npmignore가 있다면 위치에 따라 판단한다. 같은 npm 10.9.8에서 따로 실행한 dry-run 결과는 다음과 같았다.
- 루트의
template.txt를files에 직접 지정하면 루트.npmignore에 같은 경로가 있어도 포함됐다. files없이 루트.npmignore로template.txt를 제외하면 목록에서 빠졌다. 이 구성이라면 해당 제외 규칙을 수정할지 판단한다.files로assets폴더를 선택해도assets/.npmignore가template.txt를 제외하면assets/template.txt는 빠졌다. 필요한 파일을 제외하는 하위 규칙도 확인해야 한다.
이 차이는 공식 문서의 루트·하위 .npmignore 설명과 일치했다. 문서는 .npmignore가 없으면 .gitignore의 내용을 사용한다고도 설명한다. 루트 제외 규칙만 지우거나 files만 추가하면 항상 해결된다고 가정하지 말고, 수정 후 실제 목록을 다시 본다. 실행 버전인 npm 10.9.8의 files 설명 (새 탭에서 열림)
설치 성공 뒤에 자원을 읽어 본다
마지막 확인은 소스 폴더와 분리된 새 소비자에서 한다. npm은 로컬 .tgz 경로를 설치 대상으로 받을 수 있다. 소스 폴더를 연결해 쓰는 검사로 바꾸면 tarball에 빠진 파일을 놓칠 수 있다. npm의 로컬 tarball 설치 (새 탭에서 열림)
빈 소비자 폴더에 package.json을 {"private":true}로 저장하고, 아래 첫 인자를 방금 검사한 .tgz의 절대 경로로 바꿔 실행한다. 예시 경로는 Windows용이다. 마지막 import의 이름도 자신의 패키지 이름으로 바꾸고, 실제로 템플릿을 읽는 기능을 호출한다. 실험의 진입점은 import하는 즉시 템플릿을 읽도록 작성했다.
npm.cmd install 'D:/pack-check/pack-template-lab-1.0.0.tgz' --offline --ignore-scripts --no-audit --no-fund --package-lock=false
if ($LASTEXITCODE -ne 0) { throw '소비자 설치 실패' }
node --input-type=module -e "import 'pack-template-lab';"
"resource read exit=$LASTEXITCODE"
이 실험에는 외부 의존성이 없어 --offline으로 설치했다. 자신의 의존성이 캐시에 없다면 이 옵션 때문에 설치가 실패할 수 있으며, 그 오류는 템플릿 누락과 구별해야 한다. 설치 스크립트가 필요한 패키지는 검토한 준비 작업을 따로 고려한다.
별도 소비자 두 개에 설치한 결과, 두 설치 모두 종료 코드 0이었다. 하지만 누락본을 읽을 때는 설치된 패키지의 template.txt 경로와 ENOENT, resource read exit=1이 나왔다. 포함본은 HELLO_FROM_TEMPLATE과 resource read exit=0을 출력했으며 템플릿 바이트도 원본과 같았다. 마지막 출력은 바로 앞 Node 명령의 종료 코드다. 설치 성공만으로 실행에 필요한 자원이 모두 있다고 판단할 수 없었다.
자기 tarball의 목록에 경로가 없다면 포함·제외 규칙을 조사한다. 경로가 있는데 읽기만 실패한다면 설치한 tarball이 맞는지와 오류에 나온 경로·파일명을 대조한다. 이때는 files에 항목을 더 넣는 조치만으로 해결된다고 볼 수 없다.
직접 실행한 환경은 Windows 11, PowerShell 7.6.6, Node.js 22.23.2, npm 10.9.8, bsdtar 3.8.8이다. npm v10 웹 문서는 확인 당시 10.9.9로 표시돼 10.9.8 태그 문서도 대조했다. .npmignore 세 조건은 dry-run만 비교했고 .gitignore 대체는 문서에서만 확인했다. 레지스트리 발행, 외부 의존성·생성 스크립트가 있는 패키지, Linux와 다른 npm 버전은 실행하지 않았다.