npm publish가 통과됐을 때는 기분이 좋았다.
문제는 그 이후였다. GitHub Actions로 옮기자마자 세 군데에서 막혔다. Trusted Publisher 설정, provenance 관련 메타데이터, 태그와 버전의 순서——각각 다른 원인으로, 다른 곳에서 실패했다.
같은 곳에서 시간을 낭비하는 사람이 없도록 겪은 실패를 시간 순서대로 남겨둔다.
왜 공개하려 했는가
@hsblabs/web-stream-extras는 Web Streams API 관련 유틸리티를 모은 작은 패키지다. 프로젝트 내부에서 재사용할 목적으로 작성했지만, 범용성이 있다고 판단해 OSS로 공개했다.
0.0.1을 로컬에서 수동으로 publish한 이유
첫 번째 릴리스는 Actions를 구성하기 전에 로컬에서 npm publish를 실행했다. npm에 올라간 상태에서 install과 import가 정상 동작하는지 확인하는 것이 목적이었고, 릴리스 플로우 정비는 나중으로 미뤘다. 아직 안정되지 않은 단계에서 자동화를 먼저 구성하면 디버깅 레이어만 늘어날 뿐이라고 생각했다.
또 다른 이유도 있다. npmjs 쪽에서 Trusted Publisher를 설정하려면 대상 패키지가 이미 npm에 존재해야 한다. 즉, 첫 번째 publish만큼은 어떤 방식으로든 수동으로 해야 설정 화면에 진입할 수 있다.
0.1.0에서 GitHub Actions + Trusted Publisher로 전환한 구성
수동 publish는 한 번으로 끝내고, 이후는 GitHub Actions에서 publish하는 구성으로 전환했다.
npm의 Trusted Publisher는 토큰을 발급하거나 관리하지 않고도 OIDC로 publish할 수 있는 방식이다. workflow가 발급한 short-lived 토큰을 사용하므로 NPM_TOKEN을 시크릿으로 보관할 필요가 없다.
설정의 핵심은 두 가지다. npm 쪽에서는 Trusted Publisher를 추가할 때 workflow 파일명을 지정한다. GitHub Actions 쪽에서는 id-token: write 권한을 추가하고 NODE_AUTH_TOKEN은 두지 않는다.
permissions: id-token: write contents: readNODE_AUTH_TOKEN을 그대로 두면 Trusted Publisher의 OIDC 플로우와 충돌해 publish가 실패할 수 있다. README나 TODO에 토큰 기반 안내가 남아 있으면 혼란의 원인이 되므로, 전환할 때 함께 정리하는 것이 좋다.
실제로 겪은 실패
422 Unprocessable Entity로 publish가 멈추다
Actions의 publish step만 실패했다. 에러는 422 Unprocessable Entity. 다른 스텝은 모두 통과하고 있어서 처음에는 원인을 짐작조차 할 수 없었다.
원인은 package.json의 repository.url이 비어 있었던 것이다. provenance를 활성화하면 npm 쪽에서 패키지와 repo를 연결하기 위해 repository.url을 참조한다. 이 필드가 없으면 422로 거부된다.
// 수정 전"repository": {}
// 수정 후"repository": { "type": "git", "url": "https://github.com/hsblabs/web-stream-extras"}다른 메타데이터를 먼저 확인했더라도 이 부분은 놓치기 쉽다.
태그와 package.json.version의 순서
workflow는 태그 이름과 package.json.version이 일치하는지 검증한다.
태그를 먼저 달고 package.json.version을 올리려 하면 검증에서 실패한다. 올바른 순서는 이렇다.
package.json.version을 올리고 commit한다- 그 commit에 태그를 단다
- push한다
순서가 반대면 태그가 가리키는 commit의 version과 태그 이름이 맞지 않는다. 실패했다면 태그를 삭제하고 다시 달면 된다.
git tag -d v0.1.0git push origin :refs/tags/v0.1.0# version을 올린 commit 이후에git tag v0.1.0git push origin v0.1.0최종적으로 안정된 릴리스 플로우
지금 구성은 이렇다.
- 구현과 테스트를 진행한다
package.json.version을 올리고 commit한다v{version}태그를 달고 push한다- Actions가 태그 push를 감지해 publish를 실행한다
publish 전 검증은 publish:check 명령으로 통합했다. --write 없이 lint와 타입 체크를 실행하는 것으로, publish step 전에 두면 미커밋 변경이 섞이는 사고를 방지할 수 있다. 자동 수정 명령을 직접 연결하면 publish 직전에 미커밋 변경이 생길 수 있으므로 주의가 필요하다.
pnpm pack --dry-run으로 tarball 내용도 확인했다. files에 dist/만 지정해도 sourcemap이 그대로 포함될 수 있다. 공개하고 싶지 않다면 build 쪽에서 제거하는 것이 확실하다.
다음에 개선하고 싶은 것
changelog 자동 생성을 플로우에 통합하고 싶다. 지금은 수동으로 작성하고 있어서, 태그를 달 때마다 누락이 발생하기 쉽다.
수동 publish에서 Actions로 전환하는 과정 자체는 단순하고, 막히는 것은 대부분 설정의 혼동에서 비롯된다. Trusted Publisher는 토큰 관리가 필요 없는 대신 OIDC 관련 설정을 정확히 맞춰야 한다. npm 쪽 설정과 workflow 쪽 설정이 일대일로 대응하는지 확인한 뒤 실행하는 것이 가장 빠른 길이다.
hsb.horse