[토스] CLI 기반 툴 제작하기

[ Toss ]2025. 5. 18. 15:52

2025.05.16

[토스] 25년도 1학기 Frontend Platform Assistant 모집 공지를 우연찮게 SNS를 통해 접하게 되었고, 이번 전형은 이력서 없이 리포지토리를 제출하는 모집 전형이란 걸 알게되었다. 제출 가이드는 아래 내용과 동일하다.

💬 리포지토리 제출 가이드
이번 전형에서는 이력서 없이 리포지토리로 지원해요. 다음과 같이 토스가 중요하게 생각하는 요소들을 참고해 보세요.
  • 서비스가 아닌 라이브러리 또는 CLI 도구를 다루는 리포지토리이면 좋아요.
  • 프로젝트가 어떤 문제를 해결하고자 하는지 알 수 있는 README가 있으면 좋아요.
  • 빌드 또는 린트, 의존성 설정을 올바르게 하기 위해 노력한 리포지토리이면 좋아요.
  • 코드에 버그가 없도록 추가한 CI Check 및 테스트가 있으면 좋아요.
  • 라이브러리 또는 CLI 도구의 레퍼런스 및 가이드를 포함하고 있으면 좋아요.
  • 코드 파일을 수정하기 쉽도록 디렉토리 구조 및 코드 품질을 고민한 리포지토리면 좋아요.
  • TypeScript를 정확하게 사용하기 위해 고민한 리포지토리면 좋아요.

 

인턴십은 필수라고 생각하기도 했고, 토스 인턴 공고를 본 것도 기회라고 생각해 열심히 준비해야겠다는 생각이 들었다. 그래서 기존에 진행했던 서비스를 제출하는 것 대신, 라이브러리나 CLI 도구를 다루는 리포지토리를 한번 만들어보자는 생각으로 툴을 제작하기 시작했다.

 

먼저, 기간이 일주일밖에 없기 때문에 기획을 빠르게 진행했다. LLM을 활용해서 무언갈 만들면 좋겠다는 생각이 먼저 들었는데, 현실적으로 불가능 할 것 같아 리액트 프로젝트를 빠르게 만들어주는 CLI를 구성해보기로 결심했다. 보일러플레이트를 만든 기억도 있고, 팀 프로젝트에선 늘상 초기 빌드 및 환경설정을 담당했기에 익숙하지 않을까해서 이 주제를 고르게 되었다.

 

프로젝트명은 react-kit-cli로 정했고, 내가 원하는 프론트엔드 세팅을 한번에 세팅해주는 CLI 툴로 기획을 좁혔다. 먼저 깃허브 리포를 생성하고, 초기 설정을 진행해주었다. ts-node로 CLI를 실행하려다 ERR_UNKNOWN_FILE_EXTENSION 오류가 발생했고, 이는 type: module 설정과 CommonJS 실행 방식 간의 충돌 때문이었다는 것을 확인하게 되었다. 이후 CLI 도구 특성상 CommonJS가 더 안정적이라는 커뮤니티의 조언을 반영해 ESM 설정을 제거하고 해결했다.

모듈 설정 중 문제

1. bin/index.ts에 타입스크립트로 CLI 코드를 작성

2. npm run build 명령어로 타입스크립트를 자바스크립트로 컴파일

3. 컴파일 된 dist/bin/index.js를 실제로 실행하는 구조로 설계했다.

 

index.ts의 코드를 보자면

#!/usr/bin/env node
import { Command } from "commander";
import chalk from "chalk";
import path from "path";
import fs from "fs-extra";

const program = new Command();

program
	.name("react-kit")
	.description("리액트 프로젝트 세팅 도구")
	.version("1.0.0");

//기본 명령어(init)
program
	.command("init")
	.description("기본 템플릿 적용")
	.argument("[projectName]", "프로젝트 이름")
	.option("--with-tailwind", "Tailwind CSS 포함")
	.option("--with-vitest", "Vitest 포함")
	.action(async (projectName, options) => {
		try {
			// 1. 프로젝트 이름 확인
			if (!projectName) {
				console.log(chalk.red("프로젝트 이름을 입력해주세요."));
				process.exit(1);
			}

			// 2. 프로젝트 폴더 생성
			const projectPath = path.join(process.cwd(), projectName);
			if (fs.existsSync(projectPath)) {
				console.log(chalk.red(`'${projectName}' 폴더가 이미 존재합니다.`));
				process.exit(1);
			}

			console.log(chalk.green(`'${projectName}' 프로젝트를 생성합니다...`));
			await fs.mkdir(projectPath);

			// 3. 템플릿 복사
			const templatePath = path.join(__dirname, "../templates/base");
			await fs.copy(templatePath, projectPath);

			// 4. 옵션에 따른 추가 설정
			if (options.withTailwind) {
				console.log(chalk.blue("Tailwind CSS 설정을 추가합니다..."));
				// TODO: Tailwind 설정 추가
			}

			if (options.withVitest) {
				console.log(chalk.blue("Vitest 설정을 추가합니다..."));
				// TODO: Vitest 설정 추가
			}

			console.log(chalk.green("\n프로젝트 생성이 완료되었습니다!"));
			console.log(chalk.yellow("\n다음 명령어로 시작하세요:"));
			console.log(chalk.cyan(`  cd ${projectName}`));
			console.log(chalk.cyan("  npm install"));
			console.log(chalk.cyan("  npm run dev"));

		} catch (error) {
			console.error(chalk.red("프로젝트 생성 중 오류가 발생했습니다:"), error);
			process.exit(1);
		}
	});

program.parse();

이렇게 작성되었다.

`commander`와 `chalk`로 CLI 툴을 구성했으며, Tailwind와 Vitest에 대한 설정도 추후에 추가 할 예정이다.

 

위에 index.ts로 생성된 프로젝트는 아래의 폴더 구조를 가진다.

my-project/
├── index.html
├── package.json
├── tsconfig.json
├── tsconfig.node.json
├── vite.config.ts
└── src/
    ├── App.tsx
    ├── App.css
    ├── main.tsx
    └── index.css

2025.05.17

Tailwind와 Vitest에 대한 설정을 진행했다.

옵션 조합에 따라 템플릿 디렉토리에서 설정 파일을 복사한다.

처음엔 tsc 빌드 시에 templates 폴더까지 컴파일 되어 문제가 생겼는데, tsconfig.json에서 제외시키고 빌드 후에 npm run copy-templates로 수동 복사하게 처리했더니 빌드 문제가 해결되었다. 또한, fs.copyFileSync는 디렉토리 복사가 안되어 fs-extra의 copySync로 변경해주었다.

 

이후 테스트를 진행해주었다. 

테일윈드 테스트

cd test-cli/my-test-project && npm run dev로 경로 이동 후 테스트를 진행했으며 다행히 문제 없이 빌드되었다.

브라우저에서 테스트

이후 테스트 파일을 환경설정해주었다. Tailwind CSS와 Vitest 테스트 환경을 설정하고, Vite 설정 파일을 ESM 방식으로 변환했고, 중복되는 JS 파일을 제거하고, tsconfig에 테스트 파일도 포함되도록 수정하면서 전체 프로젝트를 TypeScript 중심으로 정리했다. Vitest를 통해 아래와 같은 테스트 설정 파일도 만들어주었다.

Vitest로 테스트 진행

 

2025.05.18

오늘은 ESLint, Prettier 설정 자동화 및 코드 리팩토링을 진행 할 예정이다.

먼저 ESLint, Prettier 관련 플러그인과 설정 패키지 설정을 진행해주었고, .eslintrc.json을 추가하여 React, TypeScript, Prettier와 연동된 린트 규칙 적용해주었다.

.eslintrc.json

또한 Prettier 설정 파일인 .prettierrc도 추가해주었고, 세미콜론, 따옴표, 줄 길이등을 명시해주었다.

.prettierrc

이후 npm run lint로 코드 검사를 진행했다.

npm run lint

이후 오류들을 npm run lint:fix로 자동 수정해주었고,

npm run lint:fix 후 다시 npm run lint(오류없이 실행되는 모습)

마지막으로 npm run format으로 Prettier로 코드 포맷팅을 진행하였다.

npm run format

이후 코드 리팩토링을 진행했다. 코드를 모듈화 하는 것을 첫번째 목표로 잡았다, 타입 정의, 설정, 유틸리티 함수들을 각각 src/types, src/config, src/utils로 분리했고, 타입스크립트를 사용한 이유를 보여주고 싶었기에 타입 안정성을 높이려고 해봤다. 입터페이스를 통한 타입 정의를 진행했고, 함수의 반환 타입을 명시해주었다. 마지막으로는 에러처리 개선에 신경썼다. 일관된 에러 처리 패턴을 적용하려고 했고, 이를 진행하며 중복 코드도 여럿 제거했다. 

마지막으로 리드미 작성하고 끝~,,

아래는 깃허브 링크 첨부합니당

https://github.com/dohy-eon/react-kit-cli

 

GitHub - dohy-eon/react-kit-cli

Contribute to dohy-eon/react-kit-cli development by creating an account on GitHub.

github.com