CI에서 ESLint를 변경된 파일에만 적용하기

2026-02-25 hit count image

대규모 모노레포에서 새로운 ESLint 룰을 부담 없이 도입하기 위해, CI에서 PR의 변경된 파일에만 ESLint를 실행하는 방법에 대해서 알아봅니다.

github_actions

배경

ESLint에 새로운 룰을 추가하면, 기존의 모든 코드가 해당 룰을 위반하고 있지 않은지 검사됩니다. 대규모 모노레포에서는 새 룰을 추가할 때마다 대량의 기존 코드를 수정해야 하며, 영향 범위가 커지는 문제가 있습니다.

예를 들어, 새로운 룰을 하나 추가한 것만으로 수십~수백 개의 파일에서 에러가 검출되는 경우가 있습니다. 이렇게 되면 새로운 룰의 도입 비용이 너무 높아져서 코드 품질 개선이 진행되기 어렵습니다.

이상적으로는, 수정된 파일만 검사하여 영향 범위를 줄이면서 단계적으로 품질을 향상시키고 싶습니다.

과제

각 앱의 CI 워크플로우에서는 ESLint를 프로젝트 전체에 대해 실행하고 있었습니다.

- name: ESLint
  run: yarn lint:app-a

이 방식의 문제점은 다음과 같습니다.

  • 새 룰 추가 시 기존의 모든 파일이 검사 대상이 된다
  • 기존 코드의 대량 수정이 필요하여 PR의 영향 범위가 커진다
  • 결과적으로 새 룰 도입을 주저하게 된다

검토한 접근 방식

방안 A: 설정 파일을 2개로 분리하여 운용

처음에 검토한 방법은 ESLint 설정 파일을 기존 룰용신규 룰용 2개로 나누어 운용하는 것이었습니다.

.eslintrc.cjs          ← 기존 룰 (전체 파일 검사)
.eslintrc.new-rules.cjs ← 신규 룰 (변경 파일만 검사)
# 기존 룰: 프로젝트 전체에 실행
- name: ESLint (existing rules)
  run: yarn lint:app-a

# 신규 룰: 변경된 파일에만 실행
- name: ESLint (new rules)
  run: npx eslint -c .eslintrc.new-rules.cjs $CHANGED_FILES

이 방식은 기존 룰의 전체 검사를 유지하면서 신규 룰만 단계적으로 도입할 수 있다는 장점이 있습니다. 하지만 실제로 운용을 고려하면 여러 문제가 있었습니다.

  • 설정 파일 관리가 복잡해진다: 2개의 설정 파일을 항상 동기화해야 한다
  • 룰 승격 작업이 필요하다: 신규 룰이 전체 코드에 적용 가능해지면 .eslintrc.new-rules.cjs에서 .eslintrc.cjs로 옮기는 작업이 발생한다
  • 어떤 룰이 어디에 있는지 파악하기 어렵다: 룰이 두 곳에 분산되어 관리 포인트가 늘어난다

방안 B: 모든 룰을 변경된 파일에만 적용 (채택)

다음으로 검토한 방법은, 설정 파일은 하나로 유지하되 검사 대상을 변경된 파일로 한정하는 것이었습니다.

.eslintrc.cjs ← 기존 룰 + 신규 룰 모두 포함 (변경 파일만 검사)

이 방식의 우려는 “기존 룰이 전체 파일에 적용되지 않으면 품질이 떨어지지 않을까?”라는 점이었습니다. 하지만 다음과 같은 이유로 실질적인 문제가 없다고 판단했습니다.

  • 기존 코드는 이미 기존 룰을 통과하고 있다: 전체 검사를 하지 않아도, 기존 파일이 갑자기 위반 상태가 되지 않는다
  • 변경되지 않은 파일은 검사할 필요가 없다: 위반이 발생하는 것은 코드가 변경될 때뿐이다
  • 설정 파일이 하나이므로 관리가 간단하다: 신규 룰을 추가할 때 .eslintrc.cjs에 추가하기만 하면 된다

결과적으로 설정의 단순함과 운용의 용이함을 우선하여 방안 B를 채택했습니다.

해결: PR에서 변경된 파일에만 ESLint 실행

GitHub API를 사용하여 PR의 변경 파일 목록을 가져오고, 변경된 파일에만 ESLint를 실행하도록 수정했습니다.

- name: Get changed files and run ESLint
  uses: actions/github-script@v7
  with:
    script: |
      const files = await github.paginate(github.rest.pulls.listFiles, {
        owner: context.repo.owner,
        repo: context.repo.repo,
        pull_number: context.issue.number,
      });

      const filtered = files
        .filter(f => ['added', 'modified', 'renamed', 'copied'].includes(f.status))
        .filter(f => f.filename.startsWith('apps/app-a/src/'))
        .filter(f => /\.(ts|tsx|js|jsx)$/.test(f.filename))
        .map(f => f.filename);

      if (filtered.length === 0) {
        core.info('No matching files to lint.');
        return;
      }

      await exec.exec('npx', [
        'eslint',
        '-c',
        'apps/app-a/.eslintrc.cjs',
        ...filtered,
      ]);

포인트 해설

1. GitHub API로 변경 파일 취득

const files = await github.paginate(github.rest.pulls.listFiles, {
  owner: context.repo.owner,
  repo: context.repo.repo,
  pull_number: context.issue.number,
});

github.paginate를 사용하여 PR의 변경 파일 목록을 가져옵니다. 페이지네이션에 대응하므로 변경 파일이 많은 경우에도 전체를 취득할 수 있습니다.

2. 대상 파일 필터링

const filtered = files
  .filter((f) => ['added', 'modified', 'renamed', 'copied'].includes(f.status))
  .filter((f) => f.filename.startsWith('apps/app-a/src/'))
  .filter((f) => /\.(ts|tsx|js|jsx)$/.test(f.filename))
  .map((f) => f.filename);

3단계로 필터링을 수행합니다.

필터목적
status 체크추가, 수정, 리네임, 복사된 파일만 대상 (삭제된 파일 제외)
startsWith 체크해당 앱의 src/ 하위 파일만 대상
확장자 체크.ts, .tsx, .js, .jsx 파일만 대상

3. 변경 파일이 없는 경우 스킵

if (filtered.length === 0) {
  core.info('No matching files to lint.');
  return;
}

대상 파일이 없는 경우 ESLint 실행을 스킵합니다. 예를 들어, 설정 파일이나 문서만 변경된 경우에는 ESLint를 실행하지 않습니다.

4. 변경 파일에만 ESLint 실행

await exec.exec('npx', [
  'eslint',
  '-c',
  'apps/app-a/.eslintrc.cjs',
  ...filtered,
]);

yarn lint:app-a (프로젝트 전체) 대신, 필터링된 파일만 인수로 전달하여 ESLint를 실행합니다. 각 앱에 적용할 때는 src/ 경로와 ESLint 설정 파일 경로만 변경하면 됩니다.

동작 확인

테스트용 PR에서 실제 동작을 확인했습니다.

새로운 ESLint 룰을 추가한 경우:

  • 변경한 파일: ESLint 에러가 검출됨
  • 변경하지 않았지만 문제가 있는 파일: ESLint 에러가 검출되지 않음

이로써 새 룰 추가 시 기존 코드에 영향 없이 CI가 통과하는 것을 확인할 수 있었습니다.

정리

BeforeAfter
검사 대상프로젝트 전체PR에서 변경된 파일만
새 룰 추가 시 영향전체 파일 수정 필요변경 파일만 대응하면 OK
기존 코드 품질 개선일괄 대응 필요수정 시 단계적으로 개선

이 방식을 통해 새로운 ESLint 룰을 부담 없이 추가할 수 있게 되었고, 코드 품질을 단계적으로 향상시킬 수 있게 되었습니다.

제 블로그가 도움이 되셨나요? 하단의 댓글을 달아주시면 저에게 큰 힘이 됩니다!

앱 홍보

책 홍보

블로그를 운영하면서 좋은 기회가 생겨 책을 출판하게 되었습니다.

아래 링크를 통해 제가 쓴 책을 구매하실 수 있습니다.
많은 분들에게 도움이 되면 좋겠네요.



SHARE
Twitter Facebook RSS