Skip to content

IMPL-005 — CI/CD 파이프라인 구축

항목내용
날짜2026-03-23
브랜치chore/lint-setup, chore/ci-setup, chore/npm-release
상위 결정ARD-0001
상태완료

1. 문제 정의

1.1 코드 품질 자동화 부재

package.jsoneslint: ^9.0.0prettier: ^3.5.0devDependencies 에 선언되어 있었으나, 설정 파일이 없는 깡통 상태였다. npm run lint 를 실행해도 아무것도 검사하지 않고 통과했다. 또한 커밋 메시지 형식에 대한 강제 수단이 없어 자동화된 버전 결정과 CHANGELOG 생성이 불가능했다.

1.2 수동 배포 프로세스

NPM 배포가 수동으로 이루어졌다. 버전 번호를 직접 올리고, CHANGELOG를 수동으로 작성하고, npm publish 를 직접 실행하는 방식은 실수를 유발하고 배포 이력 추적을 어렵게 한다.

2. 설계 결정

파이프라인을 3단계로 구성했다.

단계내용
4-AESLint Flat Config + Prettier 도입
4-BGitHub Actions CI/Release 워크플로우
4-Csemantic-release 기반 NPM 자동 배포

2.1 린팅 — ESLint 9 Flat Config 채택

ESLint 9는 기존 .eslintrc.* 방식을 Flat Config(eslint.config.js)로 대체했다. 이미 9.x가 설치되어 있으므로 신규 방식을 채택한다. Biome은 생태계 호환성과 이미 설치된 ESLint + Prettier 조합이 있다는 이유로 채택하지 않았다.

규칙 선택 근거:

  • no-unused-vars — import 해놓고 쓰지 않는 코드를 CI에서 잡는다. _ 로 시작하는 인자는 콜백의 무시 패턴((_event) => {})을 위해 예외 처리.
  • no-consoleconsole.log 가 프로덕션 번들에 포함되는 것을 방지한다. debug/warn/error 는 허용 목록에 둔다. src/debug/ 가 의도적으로 console.debug 를 사용하고 에러 메시지 모듈이 console.warn 을 쓰기 때문이다.
  • jsdoc/check-tag-names@module 을 허용 목록에 명시했다. TypeDoc이 사용하는 커스텀 태그이기 때문이다.

첫 린트 실행 결과 수정 이력:

에러파일원인해결
no-case-declarationsapi-proxy.js:359,370switch case 블록에 스코프 없는 const 선언splice case에 {} 블록 명시
no-useless-escapedebug-channel.js:612HTML 템플릿 문자열의 <\/script></script> 로 교체
no-unused-varsapi-handler.js:50리팩토링 과정에서 미사용 ERR import 잔류import 라인 삭제
no-consoleDomainState.js:643log() 메서드의 의도된 console.logeslint-disable-next-line 처리

최종 결과: 에러 0개, 경고 20개 (JSDoc @param 설명 누락만 남음). 경고는 CI에서 blocking하지 않는다.

2.2 GitHub Actions — 두 워크플로우 설계

CI 워크플로우 (ci.yml):

  • 트리거: main, develop, feature/**, fix/**, refactor/** 브랜치 push + main, develop 대상 PR
  • Node.js 20/22 매트릭스 — 소비자가 어떤 버전을 사용할지 알 수 없으므로 LTS와 현재 안정 버전 양쪽을 검증한다.
  • npm cipackage-lock.json 을 수정하지 않아 재현 가능한 설치를 보장한다.

Release 워크플로우 (release.yml):

  • 트리거: main push만. develop, feature/ 에서는 절대 릴리즈가 일어나지 않는다.
  • concurrency.cancel-in-progress: true — 연속 push 시 이전 워크플로우를 취소하여 중복 릴리즈를 방지한다.
  • fetch-depth: 0 — semantic-release가 마지막 릴리즈 태그 이후 전체 커밋 히스토리를 분석한다. 얕은 클론(fetch-depth: 1)에서는 히스토리가 잘려 버전 결정 실패.
  • permissions: contents: write — semantic-release가 GitHub에 태그를 push하고 Release를 생성하려면 쓰기 권한이 필요하다.

2.3 semantic-release — 자동 버전 결정 및 NPM 배포

Conventional Commits 메시지를 파싱하여 버전을 자동 결정한다.

커밋 타입버전 영향
feat:minor (0.5.0 → 0.6.0)
fix:, perf:, refactor:, revert:patch (0.5.0 → 0.5.1)
BREAKING CHANGE: 본문 포함major (0.5.0 → 1.0.0)
docs:, test:, chore:변경 없음

플러그인 실행 순서:

text
commit-analyzer → release-notes-generator → @semantic-release/changelog
→ @semantic-release/npm → @semantic-release/git → @semantic-release/github

@semantic-release/gitCHANGELOG.mdpackage.json 변경사항을 chore(release): X.Y.Z [skip ci] 메시지로 커밋한다. [skip ci] 가 없으면 이 커밋이 다시 Release 워크플로우를 트리거하는 무한 루프가 발생한다.

2.4 "sideEffects": false 추가 근거

번들러는 기본적으로 패키지 내 모든 코드를 번들에 포함시킨다. sideEffects: false 를 선언하면 번들러가 공격적인 Tree-shaking을 적용하여 소비자가 실제로 import한 심볼만 번들에 포함시킨다. 예를 들어 DomainState 만 import한 소비자의 번들에서 DomainRenderer, FormBinder 가 제거된다.

2.5 @semantic-release/changelog 설치 주의사항

이 패키지를 package-lock.json 과 함께 커밋하지 않으면 GitHub Actions의 npm ciCannot find module '@semantic-release/changelog' 에러를 낸다. npm cipackage-lock.json 에 없는 패키지를 설치하지 않기 때문이다. 반드시 package.jsonpackage-lock.json 을 함께 커밋해야 한다.

3. 결과

브랜치 전략:

text
main          ← 릴리즈 전용. PR merge만 허용.
└── develop   ← 통합 브랜치
     ├── feature/xxx
     ├── fix/xxx
     └── refactor/xxx

최종 반영 파일:

파일종류내용
eslint.config.js신규ESLint 9 Flat Config
.prettierrc신규프로젝트 포맷 규칙
commitlint.config.js신규Conventional Commits 강제
.github/workflows/ci.yml신규lint + test + format:check
.github/workflows/release.yml신규semantic-release 파이프라인
.releaserc.json신규semantic-release 플러그인 체인
.npmignore신규배포 tarball 제외 목록
package.json수정scripts, sideEffects, engines, devDeps

커밋 한 번으로 lint → test → build → CHANGELOG 생성 → NPM 배포 → GitHub Release 생성까지 전 과정이 자동으로 완결된다.

Released under the ISC License.