Optique 0.7.0: 더 똑똑한 오류 메시지와 유효성 검사 라이브러리 통합
洪 民憙 (Hong Minhee) @hongminhee@hackers.pub
Optique 0.7.0 출시를 발표하게 되어 기쁩니다. 이번 릴리스는 개발자 경험 개선과 유효성 검사 라이브러리 통합을 통해 Optique의 생태계를 확장하는 데 중점을 두었습니다.
Optique는 TypeScript를 위한 타입 안전한 조합형 CLI 인자 파서입니다. 설정 객체에 의존하는 전통적인 CLI 라이브러리와 달리, Optique는 작고 재사용 가능한 함수들로부터 파서를 구성할 수 있게 해줍니다—Zod를 강력하게 만드는 동일한 함수형 구성 패턴을 CLI 개발에 적용합니다. Optique를 처음 접하신다면, *Why Optique?*를 확인하여 이 접근 방식이 설정 기반 라이브러리가 단순히 따라올 수 없는 가능성을 어떻게 열어주는지 알아보세요.
이번 릴리스에서는 오타에 대한 자동 "이것을 의미하셨나요?" 제안, Zod 및 Valibot 유효성 검사 라이브러리와의 원활한 통합, 설정 버그를 조기에 잡기 위한 중복 옵션 이름 감지, 그리고 사용자가 정확히 무엇이 잘못되었는지 이해하는 데 도움이 되는 컨텍스트 인식 오류 메시지를 도입했습니다.
"이것을 의미하셨나요?": 자동 오타 제안
우리 모두 이런 경험이 있습니다: --verbose 대신 --verbos를 입력하면 CLI는 도움이 되지 않는 "알 수 없는 옵션" 오류로 응답합니다. Optique 0.7.0은 사용자가 오타를 낼 때 자동으로 유사한 옵션을 제안하여 이를 개선합니다:
const parser = object({
verbose: option("-v", "--verbose"),
version: option("--version"),
});
// 사용자 입력: --verbos (오타)
const result = parse(parser, ["--verbos"]);
// Error: Unexpected option or argument: --verbos.
//
// Did you mean one of these?
// --verbose
// --version
제안 시스템은 Levenshtein 거리를 사용하여 유사한 이름을 찾고, 편집 거리가 합리적인 임계값 내에 있을 때 최대 3개의 대안을 제안합니다. 제안 기능은 option(), flag(), command(), object(), or(), longestMatch() 등 모든 파서 유형에서 옵션 이름과 서브커맨드 이름 모두에 대해 자동으로 작동합니다. 자세한 내용은 자동 제안 문서를 참조하세요.
제안 사용자 정의
errors 옵션을 통해 제안이 포맷되는 방식을 사용자 정의하거나 완전히 비활성화할 수 있습니다:
// 옵션/플래그 파서를 위한 사용자 정의 제안 형식
const portOption = option("--port", integer(), {
errors: {
noMatch: (invalidOption, suggestions) =>
suggestions.length > 0
? message`Unknown option ${invalidOption}. Try: ${values(suggestions)}`
: message`Unknown option ${invalidOption}.`
}
});
// 조합기를 위한 사용자 정의 제안 형식
const config = object({
host: option("--host", string()),
port: option("--port", integer())
}, {
errors: {
suggestions: (suggestions) =>
suggestions.length > 0
? message`Available options: ${values(suggestions)}`
: []
}
});
Zod 및 Valibot 통합
Optique 제품군에 두 개의 새로운 패키지가 추가되어 TypeScript 생태계의 강력한 유효성 검사 기능을 CLI 파서에 제공합니다.
@optique/zod
새로운 @optique/zod 패키지를 사용하면 Zod 스키마를 값 파서로 직접 사용할 수 있습니다:
import { option, object } from "@optique/core";
import { zod } from "@optique/zod";
import { z } from "zod";
const parser = object({
email: option("--email", zod(z.string().email())),
port: option("--port", zod(z.coerce.number().int().min(1).max(65535))),
format: option("--format", zod(z.enum(["json", "yaml", "xml"]))),
});
이 패키지는 Zod v3.25.0+ 및 v4.0.0+를 모두 지원하며, Optique의 메시지 시스템과 원활하게 통합되는 자동 오류 포맷팅을 제공합니다. 전체 사용 예제는 Zod 통합 가이드를 참조하세요.
@optique/valibot
더 가벼운 번들을 선호하는 사용자를 위해 @optique/valibot은 Valibot과 통합됩니다—Valibot은 Zod의 ~52KB에 비해 훨씬 작은 크기(~10KB)를 가진 유효성 검사 라이브러리입니다:
import { option, object } from "@optique/core";
import { valibot } from "@optique/valibot";
import * as v from "valibot";
const parser = object({
email: option("--email", valibot(v.pipe(v.string(), v.email()))),
port: option("--port", valibot(v.pipe(
v.string(),
v.transform(Number),
v.integer(),
v.minValue(1),
v.maxValue(65535)
))),
});
두 패키지 모두 각각의 오류 핸들러 옵션(zodError 및 valibotError)을 통해 사용자 정의 오류 메시지를 지원하여 유효성 검사 실패가 사용자에게 어떻게 표시되는지 완전히 제어할 수 있습니다. 전체 사용 예제는 Valibot 통합 가이드를 참조하세요.
중복 옵션 이름 감지
CLI 애플리케이션에서 버그의 일반적인 원인은 여러 곳에서 실수로 동일한 옵션 이름을 사용하는 것입니다. 이전에는 이런 경우 첫 번째 일치하는 파서가 옵션을 소비하는 모호한 파싱이 조용히 발생했습니다.
Optique 0.7.0은 이제 파싱 시점에 옵션 이름을 검증하고 중복이 감지되면 명확한 오류 메시지와 함께 실패합니다:
const parser = object({
input: option("-i", "--input", string()),
interactive: option("-i", "--interactive"), // 이런! -i가 이미 사용 중입니다
});
// Error: Duplicate option name -i found in fields: input, interactive.
// Each option name must be unique within a parser combinator.
이 검증은 object(), tuple(), merge(), group() 조합기에 적용됩니다. or() 조합기는 분기가 상호 배타적이므로 계속해서 중복 옵션 이름을 허용합니다. 자세한 내용은 중복 감지 문서를 참조하세요.
중복 옵션 이름에 대한 정당한 사용 사례가 있다면 allowDuplicates: true로 이 기능을 비활성화할 수 있습니다:
const parser = object({
input: option("-i", "--input", string()),
interactive: option("-i", "--interactive"),
}, { allowDuplicates: true });
컨텍스트 인식 오류 메시지
조합기의 오류 메시지는 이제 보고하는 내용에 대해 더 똑똑해졌습니다. 일반적인 "일치하는 옵션이나 명령을 찾을 수 없음" 메시지 대신, Optique는 이제 파서가 기대하는 것을 분석하고 구체적인 피드백을 제공합니다:
// 인자만 기대할 때
const parser1 = or(argument(string()), argument(integer()));
// Error: Missing required argument.
// 명령어만 기대할 때
const parser2 = or(command("add", addParser), command("remove", removeParser));
// Error: No matching command found.
// 옵션과 인자 모두 기대할 때
const parser3 = object({
port: option("--port", integer()),
file: argument(string()),
});
// Error: No matching option or argument found.
NoMatchContext를 사용한 동적 오류 메시지
국제화나 컨텍스트별 메시징이 필요한 애플리케이션의 경우, errors.noMatch 옵션은 이제 NoMatchContext 객체를 받는 함수를 허용합니다:
const parser = or(
command("add", addParser),
command("remove", removeParser),
{
errors: {
noMatch: ({ hasOptions, hasCommands, hasArguments }) => {
if (hasCommands && !hasOptions && !hasArguments) {
return message`일치하는 명령을 찾을 수 없습니다.`; // 한국어
}
return message`잘못된 입력입니다.`;
}
}
}
);
셸 완성 명명 규칙
run() 함수는 이제 셸 완성이 단수형 또는 복수형 명명 규칙을 사용하도록 구성할 수 있습니다:
run(parser, {
completion: {
name: "plural", // "completions"와 "--completions"를 사용
}
});
// 또는 단수형만 사용
run(parser, {
completion: {
name: "singular", // "completion"과 "--completion"을 사용
}
});
기본값인 "both"는 두 형태를 모두 허용하여 이전 버전과의 호환성을 유지하면서 CLI에서 일관된 스타일을 적용할 수 있게 합니다.
추가 개선 사항
-
줄 바꿈 처리:
formatMessage()는 이제 소프트 브레이크(단일\n, 공백으로 변환됨)와 하드 브레이크(이중\n\n, 단락 구분 생성)를 구분하여 여러 줄 오류 메시지 포맷팅을 개선합니다. -
새로운 유틸리티 함수: 파서 메타데이터에 프로그래밍 방식으로 접근할 수 있도록
@optique/core/usage모듈에extractOptionNames()와extractArgumentMetavars()가 추가되었습니다.
설치
deno add --jsr @optique/core @optique/run
npm add @optique/core @optique/run
pnpm add @optique/core @optique/run
yarn add @optique/core @optique/run
bun add @optique/core @optique/run
유효성 검사 라이브러리 통합을 위해:
# Zod 통합
deno add jsr:@optique/zod # Deno
npm add @optique/zod # npm/pnpm/yarn/bun
# Valibot 통합
deno add jsr:@optique/valibot # Deno
npm add @optique/valibot # npm/pnpm/yarn/bun
앞으로의 계획
이번 릴리스는 TypeScript에서 CLI 개발을 가능한 한 원활하게 만들기 위한 우리의 노력을 보여줍니다. "이것을 의미하셨나요?" 제안과 유효성 검사 라이브러리 통합은 가장 많이 요청된 기능 중 하나였으며, 이러한 기능이 여러분의 CLI 애플리케이션을 어떻게 개선하는지 보게 되어 기쁩니다.
자세한 문서와 예제는 Optique 문서를 방문하세요. GitHub에서 여러분의 피드백과 기여를 환영합니다!