CLI 도구
규칙
- Commander.js를 사용한 인수 파싱 —
.command(), .option(), .argument()와 설명을 포함할 것
- 대화형 프롬프트:
@clack/prompts(현대적이고 아름다운) 또는 inquirer — 인수가 제공되지 않을 때 사용자 입력을 위해 사용
- 스피너: 장기 실행 작업에는
ora — 시작, 메시지 업데이트, 성공/실패
- 색상: 스타일 출력에는
chalk — 절제하여 사용: 오류는 빨간색, 성공은 초록색, 경고는 노란색, 부가 정보는 희미하게(dim)
- 설정 파일:
cosmiconfig — .toolrc, .toolrc.json, tool.config.js, package.json 키를 자동으로 검색
- 종료 코드: 성공은
0, 일반 오류는 1, 사용법 오류는 2 — 항상 process.exit(code) 사용
--help 및 --version 플래그: Commander가 자동으로 추가 — 모든 옵션에 명확한 설명을 작성할 것
- 오류 처리: 최상위 수준에서 오류를 잡고, 사용자 친화적인 메시지를 stderr에 출력한 후 코드 1로 종료
- 배포:
package.json의 bin 필드, #!/usr/bin/env node shebang, ESM을 위한 "type": "module"
패턴
#!/usr/bin/env node
import { Command } from "commander";
import * as p from "@clack/prompts";
import ora from "ora";
import chalk from "chalk";
const program = new Command()
.name("my-tool")
.description("유용한 일을 하는 CLI")
.version("1.0.0");
program
.command("init")
.description("새 프로젝트 초기화")
.option("-t, --template <name>", "사용할 템플릿", "default")
.action(async (options) => {
p.intro(chalk.bold("프로젝트 설정"));
const name = await p.text({ message: "프로젝트 이름?", placeholder: "my-app" });
if (p.isCancel(name)) { p.cancel("취소되었습니다."); process.exit(0); }
const spinner = ora("프로젝트 생성 중...").start();
await createProject(name, options.template);
spinner.succeed(chalk.green("프로젝트 생성 완료!"));
p.outro(`다음 명령 실행: ${chalk.cyan(`cd ${name} && npm install`)}`);
});
program.parse();
피해야 할 것
- 오류를 stdout에 출력 금지 — 오류는
console.error() 또는 process.stderr.write() 사용
- 실패 시 종료 코드 없이 종료 금지 — 항상
process.exit(1)을 사용하여 스크립트와 CI가 실패를 감지할 수 있도록 함
- 서식 없는 긴 텍스트 금지 — 가독성을 위해 chalk 색상, 섹션, 공백 사용
- 플래그 대안 없이 대화형 입력만 요구 금지 — CI/스크립트 환경을 위해 항상
--flag 오버라이드 지원
- shebang
#!/usr/bin/env node 누락 금지 — 이것이 없으면 npx 및 전역 설치가 작동하지 않음