Featured image of post Projen ESLint 9 마이그레이션 시도

Projen ESLint 9 마이그레이션 시도

AI 너무 믿지 마세요

기나긴 추석 연휴가 지나고 나니 엄청난 후유증이 몰려왔다
추적추적… 끝도 없이 쏟아지는 비에 몸까지 천근만근이다.
도무지 출근길에 적응이 되질 않는다.

그래서 휴가를 냈다! 그렇다. 오늘은 쉬는 날이다. 🤗
회사 메신져에서 들려오는 직원들의 곡소리를 자장가 삼아
12시까지 정말이지 신나게 늦잠을 잤다.

나른한 아침(낮)이 지나간다.
가볍게 기지개를 편다.

오늘은 하고싶은 일을 해야겠다.

향긋한 커피, 함께 먹을 달달한 간식까지 준비해 책상 앞에 앉는다.

게임하게? 아님 영화?

물론 둘 다 너무나 좋아하는 취미생활이지만

오늘 내가 할 건 k8s 클러스터에 올릴 새로운 프로젝트 구성이다.


진정한 개발자(변태)라면 이런 날,
평소 개인적으로 흥미를 가지고 있던 주제에 몰두하며
하루종일 컴퓨터와 씨름하는 것이야 말로 최고의 힐링이지 않겠는가

들어가기 앞서

내가 k8s 클러스터를 관리하는 프로젝트는 기본적으로 개발 컨테이너(devContainer) 위에서 동작하는 Terraform 프로젝트이다.

다만 일반적인 Terraform 프로젝트와는 몇 가지 다른 점이 있는데:

  1. HCL을 사용하지 않는다.
    CDK for Terraform을 사용해서 개발하고 있기 때문이다.
    간단하게 말 하면, JavaC#, Python같은 익숙한 프로그래밍 언어로 HCL를 대신하는 프로젝트이다.
    내 경우 Typescript로 작업한다.

  2. Nest.js 프레임워크를 적용했다.
    당연히 일반적인 nodejs에서 사용하는 프레임워크 적용도 가능하다.

  3. 프로젝트 관리 도구 ‘Projen’을 사용한다.


Projen?

이번 포스트는 카테고리부터가 Fool(바보짓, 삽질)이다. (아니면 하소연)
그래서 정보성 내용은 최대한 간결하게 맛보기 정도로만 쓰고, 추후 기회가 되면 더 자세하게 쓰도록 하겠다.
그래도 밑밥은 깔고 가야 하므로-

Projen은 소프트웨어 프로젝트 관리를 위한 프로젝트, 일명 CDK for Project이다.

Projen Github 프로젝트에 보면 설명이 잘 되어 있지만, 경험에 비추어 간략하게 설명 해보겠다.


설정 파일의 파편화


프로젝트 루트 트리

위 사진은 앞서 기술한 IaC 프로젝트의 루트 디렉토리이다.
루트 경로에서부터 벌써 오만가지 다양한 폴더와 파일들이 있는 걸 볼 수 있다.
이는 비단 어떤 형태의 프로젝트이든 상황은 매한가지일 것이다.

이거 다 필요한 파일인가?

당연히 다 필요하다.

예를 들어

  • .gitignore는 git tracking에서 제외할 파일이나 폴더들을 기술한다.
    요컨대, 원치 않는 파일들이 GitHub나 GitLab, Bitbucket등에 업로드 되는 것을 필터링 하는 규칙 설정이다.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    
    # 폴더나 파일
    .DS_STORE
    /.secrets
    /.kube
    /cdktf.out.json
    /env
    /keys
    /cdktf.out
    /scripts/generated
    /tmp
    
    # Glob 패턴
    *.log
    *.pid
    *.seed
    *.pid.lock
    
    # 혹은 반대로 반드시 Tracking 하도록 명시
    !/.gitattributes
    !/.projen/tasks.json
    !/.projen/deps.json
    !/.projen/files.json
    !/.github/workflows/pull-request-lint.yml
    !/package.json
    !/LICENSE
    

    자동으로 생성되는 파일, 비밀 파일, 임시 파일등이 주로 기술된다.

  • .github 디렉토리 밑에는 PR Template, Issue Template, Github Action Workflow등이 포함되어 있다.

    PR 제출시 제목에 `test`, `feat`, `fix` 이런식으로 앞에 붙여놔야지만 승인 되도록 하는 Workflow

  • package.json은 nodejs 개발자라면 아주 익숙할 것이다.

    프로젝트 명, 스크립트 구성, 디펜더시 등이 기술 된다.

이렇듯 수많은 설정 파일들이 있다.
모두 각자의 역할을 담당하며, 사용하는 키워드는 물론 표기방식(json, yaml, ini, toml 등)도 제각각이다.

여기엔 크게 2가지의 문제가 있는데:

  1. 학습 곡선이 가팔라진다.

    yaml? toml? 아니 개발자면 소스코드만 기가막히게 잘 짜면 됬지 이런걸 또 배우라고?

    이해가 되는 불만이지만, 그래도 배워야 한다.

  2. 설정 파일이 여기저기 흩어져 있다.

    사실 이게 진짜 문제다.

    앞서 얘기했듯, 프로젝트를 구성하는 파일들은 모두 고유의 역할을 가지고 있다.
    하지만 동시에 서로 유기적으로 연동되는 경우도 많다.

    예를들어 프로젝트 루트의 logs 디렉토리에 애플리케이션 로그를 기록하고 싶다고 하자.
    그렇다면 애플리케이션의 환경변수에 로그 저장 경로는 logs라고 지정해줄 것이다.
    그리고 .gitignore에도 해당 경로를 입력해줘야 한다.
    같은 을 2번 넣어주는 것이다.

    로그 저장 위치가 바뀐다면?

    그럴 일은 자주 일어나지 않겠지만, 이러면 위에서 한 작업, 가령 logs-v2로 바뀌었다면
    마찬가지로 환경변수와 .gitignore 모두에 logs-v2라는 값을 넣어줘야 한다.

    실수로 이중 하나를 제대로 처리 해주지 못 했다면 이제 github에 테스트하면서 생긴 로그 파일들이 잔뜩 올라가는 진풍경을 보게 될 것이다.


Single Source of Config

Projen은 이런 수많은 파일들을 하나의 설정으로 정의 함으로써 생성 / 삭제 / 업데이트 하는 역할을 한다.
일종의 Signle Source of Truth이다.

이 프로젝트에선 .projenrc.ts라는 파일이 그것인데, .projenrc.java.projenrc.py도 가능하다.

구체적인 코드 예시 하나를 들자면 다음과 같다.

일주일에 1번씩 nodejs package를 최신화 하는 workflow

예시에서는 .projenrc.ts에 의존성 최신화 workflow를 사용 하겠다고 설정했다.
업데이트 주기는 1주일로 명시 해뒀다.

그 결과, .github/workflows/upgrade-develop.yml 파일이 생성된 모습이다.

이 Workflow는 실제로 매우 잘 동작한다.

의존성 최신화 동작 기록

위 사진에서 볼 수 있듯 1주일에 한번, 꼬박꼬박 잘 동작하는 모습이다.

그리고…

오늘 터진 사건의 원흉이다…


발단

아직 부시시한 머리를 만지작 거리며 메인 PC의 전원을 킨다.
화려한 바탕화면이 반겨준다.

심호흡 한 번 하고-
커피로 목을 축인다.

공유기 서버로 접속해 WOL로 개발 서버 컴퓨터의 전원도 켜준다.
서버가 켜질 때까지는 시간이 조금 필요하다.

전 날 선물받은 초코 크루키를 한 입 베어문다.
너무 달아. 이가 썩을 거 같아. 커피로 입가심을 한다.

Cursor IDE를 열고 SSH로 개발서버에 접속한다.
프로젝트를 열고 Open in Container 버튼을 클릭한다.

Docker가 요청받은 작업들을 실행한다.
기반 이미지는 Ubuntu Noble, MS에서 배포하는 베이스 이미지다.

그야말로 git 정도 외에는 아무것도 설치되어 있지 않아 마치 도화지 같다.
Docker가 이 깡통 컨테이너에 지정한 Features와 Extension을 설치한다.

Python, Projen, Github Cli, Terraform, Kubectl, Istio, Vault, DinD…
작업이 끝나면 GitHub에서 업데이트 된 코드를 받고 개발 의존성을 설치한다.

이게 끝나면 projen을 통해 설정 파일들을 최신화 하고
Terraform Provider가 각 Stack에 맞춰 다운로드 될 것이다.

앗, 뭔가 잘못 되었다. ESLint 오류?
눈을 비비며 발견한 로그를 복사해 Cursor에게 진단을 맡긴다.

Cursor의 진단이 끝나고, 조금 전 오류를 Reproduce하기 위해
ESLint를 터미널에서 실행해본다.
역시 뭔가 잘못되었다.

아무래도 오늘도 하루가 순탄치 않을 것 같다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
$ yarn eslint --fix
yarn run v1.22.22
$ npx projen eslint --fix
👾 eslint | eslint --ext .ts,.tsx --fix --no-error-on-unmatched-pattern --fix src scripts projenrc .projenrc.ts

Oops! Something went wrong! :(

ESLint: 9.37.0

ESLint couldn't find a configuration file. To set up a configuration file for this project, please run:

    npm init @eslint/config@latest

If you think you already have a configuration file or if you need more help, please stop by the ESLint Discord server: https://eslint.org/chat

(node:14980) ESLintRCWarning: You are using an eslintrc configuration file, which is deprecated and support will be removed in v10.0.0. Please migrate to an eslint.config.js file. See https://eslint.org/docs/latest/use/configure/migration-guide for details. An eslintrc configuration file is used because you have the ESLINT_USE_FLAT_CONFIG environment variable set to false. If you want to use an eslint.config.js file, remove the environment variable. If you want to find the location of the eslintrc configuration file, use the --debug flag.
(Use `node --trace-warnings ...` to show where the warning was created)
👾 Task "eslint" failed when executing "eslint --ext .ts,.tsx --fix --no-error-on-unmatched-pattern --fix src scripts projenrc .projenrc.ts" (cwd: /home/vscode/workspace/ApexCaptain.IaC)
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

ESLint

ESLint, 여기서 ES는 Ecma Script의 약자이다. Ecma International이라는 기구에서 만든 Script라는 뜻으로
통상적으로 표준 Javascript를 의미한다.

Lint 혹은 Linter는 코드베이스를 탐색, 사전에 정의한 규칙에 맞지 않는 코드를 찾아내고 교정하는 도구이다.
단순하게 우리말로 번역하면 맞춤법 검사기 정도로 보면 된다.

Lint의 의미를 영어사전에서 찾아보면 보풀이라고 나온다.
세탁기에 있는 보푸라기 필터처럼 문제의 소지가 있는 코드를 걸러내는 것이다.

ESLint는 ES + Lint, 즉 Javascript 맞춤법 검사기이다.


ESLint Migration

당신의 .eslintrc.json, eslint.config.js로 대체되었다.

ESLint를 프로젝트에서 사용하기 위해선 자체적인 설정파일이 필요하다.
원래 사용하던 설정파일은 .eslintrc.json으로 json 파일이었다.

이는 Projen이 생성해주는 파일이다.
에러 내용을 조사해보니 ESLint에서 사용하는 설정파일 방식이 기존 json에서 js로 바뀌었다는 것이다.

이를 ESLint에서는 flat config라고 한다. ESLint v8.23.0부터 등장이 예고되었던 것으로 바뀌는 점을 간단히 요약하면 다음과 같다.

  1. Parser Option과 ENV 설정은 languageOptions라는 것으로 통합되었다.

  2. 배열방식의 설정으로 변환되었다 (그래서 Flat이라고 하는 모양이다.)

    기존 JSON 방식에서는 오브젝트 루트에 Key를 사용했었는데
    이제는 rule, plugin, files 이런것들 다 하나의 배열에서 처리한다.

  3. 이제 extends 안 쓰고 일반 javascript처럼 모듈을 import(mjs) 혹은 require(cjs) 해서 사용한다.

솔직한 감상으로는? 좋다. 아주 좋다. ESLint Json 문법이 아주 골치가 아팠는데,
만일 ESLint에 이제 입문하는 사람이라면 평소 쓰던 스크립트 문법과 비슷해져서 아주 편할 것이다.


Projen은 여전히 Json으로 만들어준다

하지만 난 Projen 통해서 ESLint 설정을 관리하는데?

.projenrc.ts를 통해 간편하게 .eslintrc.json 파일이 관리되는 모습이다.

Projen이 생성해주는 ESLint 설정 파일은 json이다. Flat Config는 아직 미지원이다.

사실 이 Flat Config로의 Migration 이슈는 이미 1년 전부터 알고 있었다.
하지만 Projen으로 관리되는 프로젝트에서는 ESLint의 Wrapper라고 볼 수 있는 Projen에서 자체적으로 지원을 안 해주면 뾰족한 방법이 없어서 방치하고 있었다.

flat 방식으로 변환 하라는 것도 어디까지나 deprecation warning이라 그렇게 급한 것도 아니고…

뭐 나중에 해주지 않을까?

하는 안일한 생각을 했던 것이다.
Cursor의 진단에 따르면, ESLint 설정 파일에 JSON은 더 이상 쓸 수가 없으니 조치를 취해야 한다고 한다.


삽질의 시작

방법 1) ESLint의 Major Version을 8로 제한 (다운그레이드)

어찌 보면 젤 심플한 방법이다. 굳이 구태여 최신 버전을 유지 할 필요도 없으니까?

회사에서 사용하는 프로젝트에도 Projen을 사용하는 경우가 간혹 있는데,
핵심 로직에 포함되는 컴포넌트도 아니고 기껏해야 맞춤법 검사기이다.

그냥 구버전으로 제한을 걸어두면 그만이다.

실제 issue 탭을 조사해보니 그런 답변이 있었다.


……


그걸 말이라고 합니까 휴먼?

이건 안된다.
비즈니스 로직이 걸린 프로젝트면 쿨하게 묻어두고 넘어가겠는데, 개인 플젝에서 이런 흠이 있는 건 찝찝해서 잠에 들 수가 없다.


방법 2) Projen에서 ESLint 비활성화 하고 ESLint 설정은 수동으로 하기 (Cursor 추천)

projenrc.ts에서 ESLint를 비활성화 하고, 아예 자체적으로 eslint.config.js를 만들어서 쓰라는 거다.

Cursor가 추천한 방법인데… 이건 너무나도 번거로워서 기각했다.


방법 3) ESLint Migration Config 사용하기

최종적으로 내가 택한 방법이다.
다행이도 ESLint에서 기존 jsonjs로 바꿔주는 스크립트를 NPM에 게시해 뒀던 것이다.

구체적인 방법을 여기에 적어두진 않겠다. 결국 이 역시 폐기한 방법이니까.

대신, 시도한 내용을 정리해서 “이런식으로 migration 했는데 이게 맞느냐? 하는 내용의 issue”를 남겨 두었는데, 그 스크린샷으로 대체하겠다.

여차저차 이렇게 저렇게 해서 ESLint Flat Config를 사용할 수 있는 상황까지 만들었다.
근데 너무 설정이 번잡해서 개선을 요청하는 Issue를 남겨두고 답변을 기다렸다.


반전

Issue 작성까지 끝나고 나서 뒤늦은 샤워를 한다.
종일 쌓였던 짜증도 따뜻한 물에 녹여 흘려보낸다.

아직 날이 밝아 잔뜩 화가 난 강아지와 산책을 다녀왔다.
비가 오지 않아서 다행이다.

오늘 저녁은 짜장면에 짬뽕, 탕수육도 있다.
이 호화로운 저녁을 다 먹고 나면 게임을 하든 영화를 보든 해야지.

돌연듯 핸드폰에서 알림이 온다.
아까 게시한 Issue에 답글이 달렸다.

이자식들, 뭐라고 하려나?


되는데요??

게시한 Issue에 달린 답글은 의외였다.

ESLint v9에서도 기존 방식(Legacy Config)이 아직 사용 가능하다는 내용이다.

특히나 Mr Grain이 구체적으로 지적한 부분은 .projenrc.ts의 다음 라인이었다.

1
project.eslint?.eslintTask.env('ESLINT_USE_FLAT_CONFIG', 'true');

이 옵션은 ESLint로 하여금 Flat Config사용하라라는 의미의 환경변수를 적용하는 코드이다.
Flat Config에 해당하는 파일이 없는데 ESLint를 실행하려고 하니 최초 발생했던 그 에러가 생길 수 밖에 없다.
에러 내용을 잘 읽어보면 실제로 그렇게 적혀있다.

지적한대로 모든 설정을 처음으로 돌려서 다시 실행했더니 정상적으로 ESLint 커맨드가 동작했다.

1
2
3
4
5
6
$ yarn eslint
yarn run v1.22.22
$ npx projen eslint
👾 eslint | eslint --ext .ts,.tsx --fix --no-error-on-unmatched-pattern  src scripts projenrc .projenrc.ts
(node:7648) ESLintRCWarning: You are using an eslintrc configuration file, which is deprecated and support will be removed in v10.0.0. Please migrate to an eslint.config.js file. See https://eslint.org/docs/latest/use/configure/migration-guide for details. An eslintrc configuration file is used because you have the ESLINT_USE_FLAT_CONFIG environment variable set to false. If you want to use an eslint.config.js file, remove the environment variable. If you want to find the location of the eslintrc configuration file, use the --debug flag.
(Use `node --trace-warnings ...` to show where the warning was created)

Flat Config로 바꾸라는 경고가 뜨긴 하는데, 이건 어디까지 경고일 뿐이다.
json방식의 설정 파일도 아직 사용하는데 지장이 없다는 거다.

더욱이 내가 경고문을 제대로 읽어보지 않은 것도 문제였다.
엄연히 v10부터 지원 끊깁니다라고 적혀 있는데 v9으로 업뎃 하면서 생긴 문제인 줄 알고 호들갑을 떤 것이다.

얼레? 내가 언제 이런 코드를 작성했지?

아니… 이건 내 잘못이 아니지. 문제의 원인은 따로 있다.

잘 생각해보니, 내가 아직 잠에서 덜 깨서 헤롱헤롱 할 때 멋대로 코드를 이리저리 수정하면서 망가뜨린 주범은 이 녀석이다.

나 몰래 Flat Config 옵션을 활성화 해놓고 나의 휴일을 망치기 위한 수작을 부린 것이 분명하다!


정신이 듭니까 휴먼?


사과, 그리고 추후 지원 약속

애꿎은 AI 탓 해서 뭘 하겠는가… 결국 내가 바보짓을 한 건데.
Mr Grain에게는 미안하다고 코멘트를 달았다.

그래도 ESLint v10에서는 무조건적으로 Flat Config를 써야한다고 하길래 앞으로 어떻게 할 예정인지도 물어봤다.


마치며

결국 남은 건 상처뿐인 하루다.
그나마 ESLint v10에 대한 Projen 개발팀의 지원 약속이라도 받았으니 다행이라면 다행일까.

아무래도 최근 AI를 지나치게 신뢰하고 또 의존하고 있었던 것은 아닐까 반성하게 되는 하루이다.

너무 길지 않다면 가급적 경고 / 에러 로그는 직접 읽어보는 습관을 가지도록 하자.

Hugo로 만듦
JimmyStack 테마 사용 중