Skip to content

commithelper-go 바이너리 패키지 분리#67

Merged
yujeong-jeon merged 12 commits intomainfrom
feature/60
Mar 5, 2026
Merged

commithelper-go 바이너리 패키지 분리#67
yujeong-jeon merged 12 commits intomainfrom
feature/60

Conversation

@yujeong-jeon
Copy link
Contributor

@yujeong-jeon yujeong-jeon commented Mar 5, 2026

Related Issue

Describe your changes

@naverpay/commithelper-go의 Go 바이너리 배포 방식을 postinstall + curl에서 optionalDependencies 기반 플랫폼별 npm 패키지로 전환합니다.

선택한 방식: optionalDependencies + 플랫폼별 패키지

esbuild, SWC, Rollup 등 주요 네이티브 바이너리 도구들이 채택한 패턴을 적용했습니다.

@naverpay/commithelper-go (메인 패키지, bin/cli.js)
  ├─ optionalDependencies
  │   ├─ @naverpay/commithelper-go-darwin-arm64  (os: darwin, cpu: arm64)
  │   ├─ @naverpay/commithelper-go-darwin-x64    (os: darwin, cpu: x64)
  │   ├─ @naverpay/commithelper-go-linux-x64     (os: linux, cpu: x64)
  │   └─ @naverpay/commithelper-go-win32-x64     (os: win32, cpu: x64)
  • 각 플랫폼 패키지의 os/cpu 필드를 통해 pnpm/npm이 현재 환경에 맞는 패키지만 자동 선택하여 설치합니다.
  • 메인 패키지의 cli.js가 런타임에 require.resolve로 설치된 플랫폼 패키지를 찾아 바이너리를 실행합니다.
  • changeset fixed 그룹으로 5개 패키지가 항상 동일 버전으로 릴리스됩니다.

고려했던 대안: postinstall + curl (기존 방식)

기존에는 postinstall 스크립트에서 GitHub Releases로부터 바이너리를 curl로 다운로드하는 방식이었습니다.
아래와 같은 이유로 optionalDependencies 방식을 선택하게 되었습니다.

postinstall + curl (기존) optionalDependencies (변경)
Nexus/사내 레지스트리 ❌ GitHub Releases URL이 차단될 수 있음 ✅ npm 레지스트리만으로 완결
onlyBuiltDependencies 소비 프로젝트에서 별도 등록 필요 불필요
--ignore-scripts CI postinstall이 스킵되어 바이너리 누락 영향 없음
네트워크 의존성 설치 시점에 GitHub API 호출 필요 npm install만으로 충분
npx 지원

알려진 한계

  1. npm publish 시 파일 퍼미션 미보존 — Go 바이너리가 644로 설치되어 실행 권한이 없는 문제가 있었습니다. cli.js에서 fs.chmodSync(binaryPath, 0o755)를 호출하여 실행 전 권한을 부여하는 방식으로 해결했습니다.
  2. 패키지 용량 — 각 플랫폼 패키지에 약 5MB의 바이너리가 포함됩니다. 다만, 사용자 환경에는 매칭되는 1개의 패키지만 설치됩니다.
  3. 지원 플랫폼 — 현재 darwin-arm64, darwin-x64, linux-x64, win32-x64를 지원합니다. 추가 플랫폼이 필요한 경우 플랫폼 패키지를 추가하여 확장할 수 있습니다.

주요 변경 파일

파일 변경 내용
packages/commithelper-go/bin/cli.js 바이너리 직접 참조 → require.resolve로 플랫폼 패키지 탐색 + chmod 처리
packages/commithelper-go/package.json postinstall 제거, optionalDependencies 추가, private 제거
packages/commithelper-go-{platform}/ 4개 플랫폼별 패키지 신규 생성
.changeset/config.json fixed 그룹 추가 (5개 패키지가 동일 버전으로 관리)
.github/workflows/test-commithelper-go.yaml 크로스 플랫폼 테스트 워크플로우 추가

Test plan

  • macOS ARM64 — 로컬 환경에서 canary 패키지 설치 후 lefthook commit-msg hook으로 동작 확인
  • Linux x64 / macOS ARM64 / Windows x64 — GitHub Actions matrix 전략으로 3개 플랫폼에서 바이너리 빌드 및 CLI 실행 테스트 통과

Request

  • 플랫폼별 패키지 구조 및 os/cpu 필드 적절성을 확인해 주시면 감사하겠습니다.
  • cli.js의 바이너리 탐색 로직 (require.resolvechmodspawnSync) 리뷰 부탁드립니다.
  • changeset fixed 그룹 설정이 릴리스 워크플로우와 잘 맞는지도 함께 확인 부탁드립니다.

🤖 Generated with Claude Code

yujeong-jeon and others added 4 commits March 5, 2026 17:09
Add four platform packages that each contain the Go binary for their
target OS/CPU combination:
- @naverpay/commithelper-go-darwin-arm64
- @naverpay/commithelper-go-darwin-x64
- @naverpay/commithelper-go-linux-x64
- @naverpay/commithelper-go-win32-x64

Each package declares os/cpu fields so pnpm/npm installs only the
matching package on each platform.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…endencies pattern

- Remove postinstall script (no more curl download at install time)
- Add optionalDependencies for each platform binary package
- Update build scripts to output binaries into platform package dirs
- Update bin/cli.js to resolve binary path via installed platform package
- Remove scripts/ from published files

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Ensures @naverpay/commithelper-go and all platform binary packages
always receive the same version bump together.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Binaries are now bundled inside platform npm packages, so there is no
longer a need to upload assets to GitHub Releases after publish.

- Remove release-asset.yaml workflow entirely
- Remove upload-assets job and checkCommitHelperGo step from release.yaml
- Remove SKIP_COMMIT_HELPER_POSTINSTALL env var from all workflows

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@npayfebot
Copy link
Contributor

npayfebot commented Mar 5, 2026

✅ Changeset detected

Latest commit: 980fbc5

@naverpay/cli, @naverpay/commithelper-go packages have detected changes.

If no version change is needed, please add skip-detect-change to the label.

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 5 packages
Name Type
@naverpay/commithelper-go ✨ Minor
@naverpay/commithelper-go-darwin-arm64 ✨ Minor
@naverpay/commithelper-go-darwin-x64 ✨ Minor
@naverpay/commithelper-go-linux-x64 ✨ Minor
@naverpay/commithelper-go-win32-x64 ✨ Minor
powered by: naverpay changeset detect-add actions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@yujeong-jeon
Copy link
Contributor Author

/canary-publish

3 similar comments
@yujeong-jeon
Copy link
Contributor Author

/canary-publish

@yujeong-jeon
Copy link
Contributor Author

/canary-publish

@yujeong-jeon
Copy link
Contributor Author

/canary-publish

@npayfebot
Copy link
Contributor

Published Canary Packages

@naverpay/commithelper-go-darwin-arm64@1.3.0-canary.260305-2777782
@naverpay/commithelper-go-darwin-x64@1.3.0-canary.260305-2777782
@naverpay/commithelper-go-linux-x64@1.3.0-canary.260305-2777782
@naverpay/commithelper-go-win32-x64@1.3.0-canary.260305-2777782

@yujeong-jeon
Copy link
Contributor Author

/canary-publish

@npayfebot
Copy link
Contributor

Published Canary Packages

@naverpay/commithelper-go@1.3.0-canary.260305-6fdce1f
@naverpay/commithelper-go-darwin-arm64@1.3.0-canary.260305-6fdce1f
@naverpay/commithelper-go-darwin-x64@1.3.0-canary.260305-6fdce1f
@naverpay/commithelper-go-linux-x64@1.3.0-canary.260305-6fdce1f
@naverpay/commithelper-go-win32-x64@1.3.0-canary.260305-6fdce1f

npm publish does not preserve file permissions, so the Go binary
may be installed without execute permission. chmod +x before spawn.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@yujeong-jeon
Copy link
Contributor Author

/canary-publish

@npayfebot
Copy link
Contributor

Published Canary Packages

@naverpay/commithelper-go@1.3.0-canary.260305-c2fd4d3
@naverpay/commithelper-go-darwin-arm64@1.3.0-canary.260305-c2fd4d3
@naverpay/commithelper-go-darwin-x64@1.3.0-canary.260305-c2fd4d3
@naverpay/commithelper-go-linux-x64@1.3.0-canary.260305-c2fd4d3
@naverpay/commithelper-go-win32-x64@1.3.0-canary.260305-c2fd4d3

@yceffort-naver
Copy link
Collaborator

image

Tests binary build and CLI execution on Linux, macOS, and Windows
via GitHub Actions matrix strategy.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@yujeong-jeon yujeong-jeon marked this pull request as ready for review March 5, 2026 14:29
@yujeong-jeon yujeong-jeon requested a review from a team as a code owner March 5, 2026 14:29
@yujeong-jeon yujeong-jeon self-assigned this Mar 5, 2026
@yceffort-naver
Copy link
Collaborator

스크립트는 따로 삭제안하시나염? 그 바이너리 다운받는거요!

@yceffort-naver
Copy link
Collaborator

commithelper-go 성능 벤치마크 리포트

TL;DR — 최적 구성

lefthook + commithelper-go + node cli.js 직접 호출이 최적이다.

# lefthook.yml (권장)
commit-msg:
  commands:
    commithelper:
      run: node node_modules/@naverpay/commithelper-go/bin/cli.js {1}
호출 방식 시간 비고
lefthook + node cli.js → Go ~76ms 플랫폼 자동 resolve + 빠름
lefthook + Go 직접 ~17ms 플랫폼 경로 하드코딩 필요 (비현실적)
lefthook + npx (local) ~700ms npx 오버헤드 낭비
husky + npx @latest ~1,750ms 매 커밋마다 registry 조회

npx/npm exec는 hook 안에서 쓰지 말고, 사용자가 수동 실행할 때만 쓰면 된다.


환경

항목
OS macOS 26.3 (Darwin)
Architecture Apple Silicon (arm64)
Node.js v24.13.0
Go 1.23.0
husky 9.x (npx husky init)
lefthook 2.0.15
벤치마크 도구 hyperfine 1.20.0

1. CLI 단독 실행 비교

hook runner를 제외하고, commithelper 자체의 실행 시간만 비교한다.

1-1. 바이너리 직접 실행 (Go vs Node.js)

항목 Mean ± σ Min Max
commithelper-go (Go binary) 14.9 ± 9.9 ms 10.3 ms 65.1 ms
commit-helper (Node.js) 228.6 ± 5.2 ms 217.0 ms 239.9 ms

Go 바이너리가 15.3x 빠르다. Node.js는 런타임 기동(V8 초기화, 모듈 로드)에 ~215ms를 소비한다.

1-2. 호출 방식별 오버헤드

호출 방식 Mean ± σ 오버헤드
Go (direct) 14.9 ms -
node cli.js → Go (PR#67 구조) 75.7 ms +60.8 ms
JS (direct node) 228.6 ms +213.7 ms
Go (npx, local) 472.3 ms +457.4 ms
Go (npm exec, local) 484.1 ms +469.2 ms
JS (npx, local) 709.0 ms +694.1 ms
JS (npm exec, local) 707.3 ms +692.4 ms
JS (npx --yes @latest) 1,750 ms +1,735.1 ms

핵심 발견:

  • cli.js wrapper(76ms)는 Go 직접 호출(15ms) 대비 4.5x 느리지만, npx(472ms) 대비 6.2x 빠르다. 플랫폼 자동 resolve를 해주면서도 실용적인 속도.
  • npx/npm exec 자체가 ~460ms의 오버헤드를 추가한다. 로컬에 설치되어 있어도 패키지 resolve 과정에서 소비.
  • npx @latest는 ~1,750ms. 매번 npm registry에 최신 버전을 확인하므로 네트워크 지연이 추가.
  • npx와 npm exec는 사실상 동일한 수준이며, npx의 분산이 더 크다.

1-3. 호출 방식 선택 가이드

Go direct       ████ 15ms        — 최고 속도, 플랫폼 경로 하드코딩 필요
node cli.js     ████████ 76ms    — 플랫폼 자동 resolve, hook에서 권장
JS direct node  ████████████████████████ 229ms  — Node.js 런타임 기동 비용
npx (local)     ████████████████████████████████████████████████ 472ms  — npx resolve 오버헤드
npx @latest     ████████████████████████████████████████████████████████████████████████████████████████████ 1,750ms  — registry 조회 추가

2. Hook Runner 비교 (husky vs lefthook)

exit 0(noop) hook으로 runner 자체의 오버헤드만 측정한다.

Hook runner Mean ± σ Min Max
lefthook 132.3 ± 29.2 ms 109.4 ms 230.8 ms
husky 274.7 ± 40.4 ms 229.7 ms 385.9 ms

lefthook이 2.1x 빠르다. husky는 shell script 기반이므로 shell 기동 오버헤드가 추가된다.


3. End-to-End 비교 (실제 git commit)

실제 git 저장소에서 커밋 시, hook이 commithelper를 실행하여 [#123] 태그를 붙이는 전체 과정을 측정한다.

3-1. 4개 조합 비교

조합 Mean ± σ Speedup
lefthook + commithelper-go (direct) 123.5 ± 12.4 ms 1.0x (baseline)
husky + commithelper-go 245.4 ± 59.5 ms 2.0x 느림
lefthook + commit-helper (JS) 346.2 ± 33.4 ms 2.8x 느림
husky + commit-helper (JS) 435.0 ± 68.6 ms 3.5x 느림

3-2. 시간 분해

husky + npx @latest + JS:  ████████████████████████████████████████████████████████████████████████████████████████ 1,750ms+ (추정)
                           [  husky ~143ms  ][ npx @latest ~1,040ms ][ Node.js ~230ms ][ git ]

husky + JS (direct node):  ██████████████████████████████████████████████ 435ms
                           [  husky ~143ms  ][    Node.js ~220ms    ][ git ]

lefthook + JS:             ██████████████████████████████████████ 346ms
                           [ lefthook ~132ms ][   Node.js ~220ms   ]

lefthook + Go (direct):    █████████████ 123ms
                           [ lefthook ][Go][ git ]

lefthook + node cli.js:    ████████████████████ ~200ms (추정)
                           [ lefthook ][ cli.js→Go ][ git ]
병목 요인 절약량 기여도
npx @latest → node cli.js 직접 호출 ~1,520 ms 가장 큰 개선
Node.js → Go 바이너리 ~207 ms
husky → lefthook ~142 ms

4. 왜 lefthook + commithelper-go + node cli.js 직접 호출인가

                                                                                                                                  ### 4-1. Node.js 런타임 비용 최소화

commit-helper(JS)는 실행될 때마다 V8 엔진 초기화, ESM 모듈 resolve, cosmiconfig + meow 등 의존성 로드까지 ~230ms가 소비된다. commithelper-go는 단일 Go 바이너리로 ~15ms에 동일한 작업을 수행한다.

PR#67 구조에서는 cli.js가 플랫폼별 패키지를 resolve하여 Go 바이너리를 호출하므로 Node.js가 한 번 개입하지만, cli.js는 의존성이 없는 경량 스크립트라 ~76ms로 충분히 빠르다.

4-2. husky → lefthook

husky는 .husky/_/husky.sh shell script를 먼저 source한 뒤 hook script를 실행한다. 이 shell 기동 과정에서 ~140ms가 추가된다. lefthook은 Go로 작성된 단일 바이너리라 hook dispatch 자체가 빠르고, lefthook.yml로 선언적 설정이 가능하며 parallel 실행도 지원한다.

4-3. npx/npm exec를 hook에서 쓰면 안 되는 이유

호출 방식 시간 문제
npx --yes @latest 1,750ms 매 커밋마다 registry 조회. 오프라인 시 실패
npx (local) 709ms 로컬이어도 패키지 resolve에 ~460ms 소비
npm exec (local) 707ms npx와 동일 수준
node cli.js (직접) 76ms npx 대비 9.4x 빠름

husky 방식에서는 hook script 안에서 npx --yes @naverpay/commit-helper@latest $1을 호출하는 것이 일반적이다. 이 경우 shell 기동 + npx registry 조회 + Node.js 기동이 중첩되어 매 커밋 ~1.75초 이상이 소요된다.

lefthook에서 node cli.js를 직접 호출하면 이 오버헤드를 완전히 회피한다.

4-4. 플랫폼 바이너리 패키지 분리 (PR #67)

기존에는 postinstall script에서 GitHub Releases의 바이너리를 curl로 다운로드했다. 이 방식의 문제:

  • Nexus/사내 npm 레지스트리 환경에서 외부 URL 접근 불가
  • --ignore-scripts CI 환경에서 바이너리 누락
  • onlyBuiltDependencies 등록 필요

PR #67에서 플랫폼별 npm 패키지(@naverpay/commithelper-go-darwin-arm64 등)를 optionalDependencies로 분리하면, npm/pnpm이 현재 플랫폼에 맞는 바이너리만 자동 설치한다. esbuild, swc, turbo 등이 사용하는 검증된 배포 패턴이다.

cli.js는 이 플랫폼 패키지를 require.resolve()로 찾아 Go 바이너리를 실행하므로, 사용자가 플랫폼을 신경 쓸 필요가 없다.


5. 수치 요약

기존 방식 (husky + npx @latest + JS) vs 권장 방식 (lefthook + node cli.js → Go)

시나리오 기존 (husky + npx @latest + JS) 권장 (lefthook + node cli.js → Go) 차이
커밋 1회 ~1,750 ms ~200 ms -1,550 ms (88% 감소)
하루 30회 커밋 52.5 s 6.0 s -46.5 s
팀 10명 × 1달 (22일) 346분 44분 -302분

기존 방식 (husky + direct node JS) vs 권장 방식 비교

시나리오 기존 (husky + JS, npx 미사용) 권장 (lefthook + node cli.js → Go) 차이
커밋 1회 435 ms ~200 ms -235 ms (54% 감소)
하루 30회 커밋 13.1 s 6.0 s -7.1 s
팀 10명 × 1달 (22일) 80분 44분 -36분

@yujeong-jeon
Copy link
Contributor Author

yujeong-jeon commented Mar 5, 2026

run: node node_modules/@naverpay/commithelper-go/bin/cli.js {1}

요렇게 하면 더 빠르다는거져?! devDeps로 들고 있어야곘넹

@yceffort-naver
Copy link
Collaborator

대추라잇,, 했는데 별 속도 차이가 안났다고 슬퍼하셨던 유정님이 생각나서 분석해보았습니다...!

yceffort-naver
yceffort-naver previously approved these changes Mar 5, 2026
Copy link
Collaborator

@yceffort-naver yceffort-naver left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이야호!

@yujeong-jeon
Copy link
Contributor Author

yujeong-jeon commented Mar 5, 2026

흑흑 npx 가 범인이구나 감사합니다 ㅠ_ㅠ (물론 gh release도..)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@yujeong-jeon
Copy link
Contributor Author

yujeong-jeon commented Mar 5, 2026

기록용. 신규 패키지 릴리스 시, 첫 배포는 ENEEDAUTH를 받음 (토큰 기반이 아니라서)

npm OIDC(토큰 자동 발급)는 이미 존재하는 패키지에 대해서만 publish 권한을 부여하고, 최초 publish는 별도 인증이 필요

해결방법이 두 가지 떠올랐는데

  1. 수동으로 최초 publish (가장 간단): npm publich --access public + Settings 가서 oidc 설정 마치기
  2. 최초 배포 시에만 NPM_TOKEN 사용하도록 워크플로우 분기: npm view 로 확인해서 분기

나중에 2번을 추가할지 검토 요망합니다. (저는 1번으로 해둠)

@yujeong-jeon yujeong-jeon merged commit f4b2aa7 into main Mar 5, 2026
7 checks passed
@yujeong-jeon yujeong-jeon deleted the feature/60 branch March 5, 2026 14:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants