GitHub Actions CI 환경에서 npm ci 실행 시 의존성 설치 단계에서 실패하는 문제가 발생했다.
로컬에서는 npm install이 정상적으로 완료되고 테스트도 통과했지만, CI 환경에서는 lock file 검증 과정에서 의존성 충돌 오류가 발생했다.
처음에는 단순한 npm 캐시 문제나 package-lock.json 불일치 문제라고 생각했지만, 실제 원인은 서로 다른 패키지가 요구하는 하위 의존성 버전 충돌이었다.
문제의 핵심은 magicast와 yaml 패키지 버전 충돌이었다.
prisma의 하위 의존성인 c12는 다음 조건을 요구했다.
magicast@^0.3.5
이는 0.3.x 버전만 허용한다는 의미이다.
하지만 테스트 환경의 @vitest/coverage-v8는:
magicast@0.5.x
를 요구하고 있었다.
npm은 npm install 과정에서 dedupe(중복 제거)를 수행하면서 하나의 버전으로 통합했고, 결과적으로 magicast@0.5.4가 선택되었다.
하지만 이는 c12가 선언한 semver 범위를 만족하지 않는 상태였다.
^0.3.5 ≠ 0.5.4
비슷한 문제가 yaml 패키지에서도 발생했다.
swagger-jsdoc
yaml@2.0.0-1 요구vite 내부 의존성
yaml@^2.4.2 요구npm install은 이 상태에서도 경고(ELSPROBLEMS)만 출력하고 진행되었지만,
npm ci는 package-lock.json이 모든 의존성 조건을 만족하는지 엄격하게 검증하기 때문에 실패했다.
처음에는 overrides를 사용해 모든 패키지가 최신 yaml 버전을 사용하도록 강제했다.
{
"overrides": {
"yaml": "^2.4.2"
}
}
하지만 이 방법은 새로운 런타임 오류를 발생시켰다.
Cannot set properties of undefined (setting 'keepCstNodes')
원인을 확인해보니 swagger-jsdoc@6.3.0이 특정 yaml 버전의 내부 API를 직접 참조하고 있었다.
즉, 단순히 최신 버전으로 강제 변경하면:
swagger-jsdoc
↓
yaml@2.0.0-1 API 기대
하지만 실제:
yaml@2.4.x 적용
↓
내부 API 변경으로 런타임 크래시
가 발생했다.
문제 해결을 위해 override 범위를 전체가 아닌 필요한 패키지로 제한했다.
수정 전:
{
"overrides": {
"yaml": "^2.4.2"
}
}
수정 후:
{
"overrides": {
"vite": {
"yaml": "^2.4.2"
}
}
}
이렇게 변경하면서:
vite는 필요한 최신 yaml 버전 사용swagger-jsdoc은 기존 yaml 버전 유지하도록 의존성 트리를 분리했다.
결과적으로 런타임 오류 없이 테스트와 빌드가 정상 동작했다.
scoped override 적용 후 로컬에서는 정상적으로 검증되었다.
하지만 커밋된 package-lock.json에는 변경된 nested override 결과가 제대로 반영되지 않았다.
원인은 기존 node_modules가 남아있는 상태에서 npm install을 실행했기 때문으로 추정된다.
기존 설치 트리를 기반으로 npm이 일부만 재계산하면서 실제 lock file과 환경 상태가 달라졌다.
깨끗한 환경에서 다시 의존성을 생성했다.
rm -rf node_modules
rm package-lock.json
npm install
npm ci
이후 별도의 테스트 디렉토리에서:
두 파일만 가지고 npm ci 실행을 검증했다.
검증 성공 후 새롭게 생성된 lock file을 커밋했다.
npm install은 의존성 충돌이 있어도 경고 수준으로 넘어가는 경우가 있다.
하지만 CI 환경에서 사용하는:
npm ci
는 lock file을 기준으로 엄격하게 검증하기 때문에 숨겨진 문제가 드러날 수 있다.
따라서 배포 환경과 동일하게 npm ci 기준으로 검증해야 한다.
overrides는 강력하지만 전역 적용하면 기존 패키지가 기대하는 API를 깨뜨릴 수 있다.
특히 오래된 라이브러리가 특정 버전 내부 동작에 의존하는 경우:
{
"overrides": {
"package": "new-version"
}
}
같은 방식은 위험하다.
가능하면:
{
"overrides": {
"specific-package": {
"dependency": "version"
}
}
}
처럼 범위를 제한해야 한다.
로컬 환경에서 성공했다고 해서 CI 환경에서도 동일하게 동작한다는 보장은 없다.
의존성 문제를 검증할 때는:
rm -rf node_modules
npm ci
처럼 깨끗한 환경에서 재현해야 한다.
이번 문제는 새롭게 추가한 백엔드 통합 테스트 과정에서 발견되었다.
기존 단위 테스트는 app.ts를 직접 로드하지 않는 구조였기 때문에:
swagger.ts 로드 실패
↓
Express 앱 초기화 실패
같은 문제를 발견하지 못했다.
실제 실행 흐름과 가까운 테스트를 추가하면 의존성 문제나 초기화 오류를 빠르게 발견할 수 있다는 것을 배웠다.
이번 장애는 단순한 npm 설치 오류가 아니라,
이 복합적으로 발생한 문제였다.
앞으로 의존성 문제를 해결할 때는 단순히 버전을 맞추는 것이 아니라,
을 반드시 거쳐야겠다.