zerovoids.dev

바닥부터

published 2026-07-17part 02 / 04
zerovoids-httpmonorepotoolingci-cd

이왕 바닥부터 다시 까는 거였다. 도구도 손에 익은 걸 그냥 쓰기보다, 하나씩 따져 골랐다.

린트랑 포맷은 Biome을 선택했다. ESLint에 Prettier, 거기에 플러그인까지 얹는 조합 대신 한 도구로 끝내고 싶었고, 실제로 훨씬 빠르다. 커밋 훅은 lefthook인데 — 솔직히 이번에 처음 알았다. husky를 쓰려다 발견했고, 훅을 쉘 스크립트로 관리하는 것보다 YAML로 선언하고 병렬로 돌리는 게 깔끔해서 그냥 갈아탔다. 커밋 메시지랑 브랜치 이름 규칙은 commitlint로 묶어 강제했다.

빌드는 tsdown이 맡고(ESM·CJS를 한 번에 뽑고, 배포 형태는 attw·publint로 검사), 테스트는 Vitest. knip도 이번에 처음 알았는데 — 데드코드랑 안 쓰는 의존성을 잡아줘서 꽤 유용했다. 번들 크기는 size-limit을 사용했다.

lefthook.yml
pre-commit:
  commands:
    branch-name: { run: "<type>/<kebab> 형식 검사" }
    biome: { run: "biome check --write {staged_files}", stage_fixed: true }
pre-push:
  commands:
    typecheck: { run: "turbo typecheck" }
commit-msg:
  commands:
    commitlint: { run: "commitlint --edit {1}" }

훅은 로컬에서 먼저 돌지만 마음만 먹으면 우회할 수 있다. 그래서 진짜 방어선은 CI에 뒀다.

CI와 릴리스

PR을 올리면 CI가 같은 걸 다시 검사한다. 포맷·타입·빌드(attw+publint)·번들 크기·데드코드, 테스트는 Node 22와 24 양쪽에서. 커버리지는 Codecov로 보내고, PR 제목이 규약에 맞는지도 본다.

수동 배포는 실수의 여지가 많다. 게다가 혼자 관리하는 상황이라, 앞으로도 휴먼 에러를 막고 싶어서 Changesets로 자동화했다. 브랜치에서 changeset을 쓰고 머지하면 main에서 "Version Packages" PR이 열리고, 그걸 머지하는 순간 npm에 올라간다.

.github/workflows/release.yml
- uses: changesets/action@v1
  with:
    version: pnpm changeset version
    publish: pnpm changeset publish

앱에 맡긴 것들

의존성은 Renovate에 맡겼다. 업데이트를 격리된 PR로 하나씩 올려 CI로 검증하니, 안전한 건 통과하는 대로 머지되고 major만 사람 손을 거친다. 커버리지는 Codecov가 지킨다. Claude는 GitHub App으로 붙어 세 가지를 한다 — @claude 호출에 답하고, packages가 바뀌면 changeset 초안을 잡고, PR 제목·본문을 규약에 맞춘다. 어디까지나 초안까지, 머지는 사람 몫이다.

바닥은 여기까지다. 다음 편부터는 이 라이브러리가 실제로 하는 일 — 에러를 하나로 접는 이야기다.