빌드노트
npm 설치는 성공했는데 postinstall 생성 파일이 없다
npm 설치가 종료 코드 0이어도 ignore-scripts 때문에 postinstall 생성물이 빠질 수 있다. 실제 CI 명령과 scripts·설정을 확인하고, 검토한 생성 작업을 명시 실행할 때 생략되는 pre/post hook과 의존 패키지의 경계를 살핀다.
CI에서 npm ci가 종료 코드 0으로 끝났는데 다음 단계가 읽을 생성 파일이 없다면, 실제 설치 명령의 --ignore-scripts와 npm 설정부터 확인한다. 파일을 만드는 작업이 postinstall에만 있다면 설치가 성공해도 그 작업은 생략될 수 있다. 같은 옵션으로 설치를 반복하기 전에, 누가 파일을 만드는지 찾아야 한다.
외부 의존성 없는 자체 패키지로 이 차이를 확인했다. 같은 package.json과 lock을 새 폴더에 각각 놓고 실행했을 때, npm ci --ignore-scripts는 종료 코드 0이지만 marker.txt가 없었다. npm ci --ignore-scripts=false는 종료 코드 0에 파일도 있었다. npm install로 비교해도 같은 차이가 났다. 실제 CI 장애 기록이 아니라, postinstall이 무해한 파일 하나를 만들도록 작성한 별도 재현이다.
postinstall은 설치 과정에 연결하는 스크립트다. npm은 기본 설치 흐름에서 이를 실행하지만 ignore-scripts=true이면 생략한다. 따라서 이 조건의 설치 성공만으로 생성 작업 완료를 판단할 수 없다. npm install의 ignore-scripts 설명 (새 탭에서 열림)과 npm ci의 같은 옵션 (새 탭에서 열림)에 명시된 동작이다.
CI가 받은 스크립트와 실제 설치 줄을 읽는다
먼저 실패한 CI가 받은 package.json이 있는 폴더에서 다음을 확인한다. 아래 명령은 Windows PowerShell 7용이며 Node.js와 npm이 필요하다. npm.cmd --version으로 CI의 npm 버전도 확인한다.
npm.cmd pkg get scripts
npm.cmd config get ignore-scripts
첫 명령은 스크립트를 실행하지 않고 목록을 보여 준다. postinstall에서 어떤 명령을 호출하는지, 그 소스가 빠진 파일을 어디에 쓰는지 읽는다. 이미 generate 같은 별도 명령이 있다면 같은 작업인지도 확인한다. 두 번째 명령은 현재 호출의 설정값을 출력한다. npm pkg의 get 설명 (새 탭에서 열림), npm config의 get 설명 (새 탭에서 열림)
조회 결과가 false여도 CI 설치 때 스크립트가 실행됐다고 단정할 수 없다. 이 실험에서는 별도 npm.cmd config get ignore-scripts가 false를 출력했지만, 같은 조회에 --ignore-scripts를 붙이면 true였다. 다시 옵션 없이 조회하면 false였다. 설치 명령에만 붙인 플래그를 다음 조회가 기억하지는 않는다.
CI 설정의 실제 설치 줄과 그 단계의 환경변수, 적용되는 프로젝트·사용자 npmrc도 함께 읽는다. npm은 명령행, 환경변수, npmrc 등에서 설정을 받으며 npm_config_ignore_scripts=true도 설정 입력이다. npm의 설정 입력과 우선순위 (새 탭에서 열림)
설치가 0으로 끝나지 않았다면 설치 오류부터 조사한다. 반대로 스크립트 생략 조건이 없고 생성 명령도 실행됐다면, 생성 로그와 출력 경로를 확인한다. 파일이 없다는 증상만으로 ignore-scripts를 원인으로 확정하지 않는다.
검토한 생성 작업을 설치 뒤에 명시한다
자체 generate가 필요한 파일을 만들고, 생략된 다른 hook 없이 실행될 수 있음을 확인했다면 설치와 생성 단계를 따로 둘 수 있다. 아래는 일치하는 package.json과 lock을 갖춘 CI 작업 사본에서 쓰는 PowerShell 흐름이다. generate는 검토한 프로젝트 명령 이름으로, ./marker.txt는 다음 단계가 실제 읽는 파일 경로로 바꾼다. npm ci는 기존 node_modules를 지우므로 실행 중인 앱 폴더에서 시도하지 않는다. npm ci의 실행 조건 (새 탭에서 열림)
npm.cmd ci --ignore-scripts
if ($LASTEXITCODE -ne 0) { throw '설치 실패' }
npm.cmd run generate --ignore-scripts
if ($LASTEXITCODE -ne 0) { throw '생성 실패' }
if (-not (Test-Path -LiteralPath './marker.txt' -PathType Leaf)) { throw '생성 파일 누락' }
자체 패키지에서는 생성 전 파일 검사가 MISSING marker.txt와 종료 코드 1을 반환했다. 명시적 generate 실행 뒤에는 파일 내용이 generated와 줄바꿈이었고, 같은 검사는 PRESENT marker.txt와 종료 코드 0으로 바뀌었다. 위 흐름도 새 폴더에서 정상 종료했다. 생성 명령이 실패하거나 파일이 여전히 없다면 다음 단계로 넘기지 않고 해당 명령의 입력·로그·출력 위치를 확인한다.
이 확인은 이전 생성물이 없는 작업 사본을 기준으로 한다. 직접 확인해 보니 npm ci --ignore-scripts를 다시 실행해도 프로젝트 루트에 남은 예전 marker는 지워지지 않았다. 파일 존재 검사는 내용이 최신이고 올바른지까지 보장하지 않으므로, 실제 산출물에 필요한 내용 검사도 별도로 둔다.
명시한 generate는 실행되지만 앞뒤 hook은 빠진다
npm.cmd run generate --ignore-scripts에서는 generate만 실행됐다. 별도의 새 폴더에서 npm.cmd run generate --ignore-scripts=false를 실행했을 때는 pregenerate → generate → postgenerate 순서가 기록됐다. ignore-scripts를 켜도 명시한 대상은 실행하고 자동으로 붙는 앞뒤 hook은 생략하는 npm run-script의 예외 규칙 (새 탭에서 열림) 때문이다. scripts 문서의 pre/post 규칙 (새 탭에서 열림)도 함께 확인할 수 있다.
따라서 실제 generate가 pregenerate에서 준비한 입력에 의존한다면 위 단계만 추가해서는 부족하다. 필요한 준비 작업을 generate 안에 포함하거나, 검토한 작업을 순서대로 명시할지 정한다. 이 실험의 generate는 앞뒤 hook 없이 파일을 만들도록 작성했다. 옵션을 붙여도 명시한 스크립트 내용은 실행되므로 호출하는 소스까지 읽어야 한다.
빠진 작업이 의존 패키지 내부의 postinstall이라면 루트 프로젝트의 npm run generate가 대신 실행하지 않는다. 해당 패키지의 공식 생성 방법과 호출 대상을 따로 확인해야 한다. 이 글의 명시 실행은 검토한 자체 생성 작업을 분리하는 선택이다.
직접 확인한 환경은 Windows 11, PowerShell 7.6.6, Node.js 22.23.2, npm 10.9.8이다. v10 웹 문서는 확인 당시 10.9.9로 표시되어 10.9.8 태그의 scripts 문서 (새 탭에서 열림)와 같은 태그의 설정 설명 원본 (새 탭에서 열림)도 대조했다. 제3자 설치 스크립트, 네이티브 바이너리 생성, 다른 npm 버전과 호스팅된 CI 서비스는 실행하지 않았다.