Optique 0.5.0: 향상된 오류 처리와 메시지 커스터마이징

洪 民憙 (Hong Minhee) @hongminhee@hackers.pub
Optique 0.5.0 릴리스를 발표하게 되어 기쁩니다. 이번 버전은 오류 처리, 도움말 텍스트 생성 및 전반적인 개발자 경험에 중요한 개선 사항을 제공합니다. 이 릴리스는 완전한 하위 호환성을 유지하므로 기존 코드를 수정하지 않고도 업그레이드할 수 있습니다.
모듈 분리를 통한 더 나은 코드 구성
큰 규모의 @optique/core/parser
모듈이 목적에 더 잘 맞는 세 개의 집중된 모듈로 리팩토링되었습니다. option()
과 argument()
와 같은 기본 파서는 이제 @optique/core/primitives
에 있으며, optional()
과 withDefault()
와 같은 수정자 함수는 @optique/core/modifiers
로 이동했고, object()
와 or()
를 포함한 조합자 함수는 이제 @optique/core/constructs
에 있습니다.
// 이전: 모든 것이 하나의 모듈에서 제공됨
import {
option, flag, argument, // primitives
optional, withDefault, multiple, // modifiers
object, or, merge // constructs
} from "@optique/core/parser";
// 이후: 구조화된 임포트 (권장)
import { option, flag, argument } from "@optique/core/primitives";
import { optional, withDefault, multiple } from "@optique/core/modifiers";
import { object, or, merge } from "@optique/core/constructs";
더 나은 명확성을 위해 이러한 특화된 모듈에서 임포트하는 것을 권장하지만, 모든 함수는 기존 코드가 변경 없이 작동하도록 원래의 @optique/core/parser
모듈에서 계속 재내보내기(re-export)됩니다. 이러한 재구성은 코드베이스를 더 유지 관리하기 쉽게 만들고 개발자가 다양한 파서 유형 간의 관계를 이해하는 데 도움이 됩니다.
자동 변환을 통한 더 스마트한 오류 처리
가장 많이 요청된 기능 중 하나는 withDefault()
에서 기본값 콜백에 대한 더 나은 오류 처리였습니다. 이전에는 콜백이 오류를 발생시키면(예: 환경 변수가 설정되지 않은 경우) 해당 오류가 런타임 예외로 버블링되었습니다. 0.5.0부터는 이러한 오류가 자동으로 포착되어 파서 수준 오류로 변환되어 일관된 오류 형식과 적절한 종료 코드를 제공합니다.
// 이전 (0.4.x): 앱을 중단시키는 런타임 예외
const parser = object({
apiUrl: withDefault(option("--url", url()), () => {
if (!process.env.API_URL) {
throw new Error("API_URL not set"); // 처리되지 않은 예외!
}
return new URL(process.env.API_URL);
})
});
// 이후 (0.5.0): 우아한 파서 오류
const parser = object({
apiUrl: withDefault(option("--url", url()), () => {
if (!process.env.API_URL) {
throw new Error("API_URL not set"); // 자동으로 포착되고 형식화됨
}
return new URL(process.env.API_URL);
})
});
또한 일반 문자열 대신 구조화된 메시지를 허용하는 WithDefaultError
클래스를 도입했습니다. 이제 Optique의 나머지 오류 출력과 일치하는 풍부한 형식을 갖춘 오류를 발생시킬 수 있습니다:
import { WithDefaultError, message, envVar } from "@optique/core";
const parser = object({
// 일반 오류 - 자동으로 텍스트로 변환됨
databaseUrl: withDefault(option("--db", url()), () => {
if (!process.env.DATABASE_URL) {
throw new Error("Database URL not configured");
}
return new URL(process.env.DATABASE_URL);
}),
// 구조화된 메시지가 있는 풍부한 오류
apiToken: withDefault(option("--token", string()), () => {
if (!process.env.API_TOKEN) {
throw new WithDefaultError(
message`Environment variable ${envVar("API_TOKEN")} is required for authentication`
);
}
return process.env.API_TOKEN;
})
});
새로운 envVar
메시지 컴포넌트는 환경 변수가 오류 메시지에서 시각적으로 구분되도록 보장하며, 컬러 출력에서는 굵게 및 밑줄이 표시되고 일반 텍스트에서는 백틱으로 감싸집니다.
사용자 정의 기본값 설명으로 더 유용한 도움말 텍스트
도움말 텍스트의 기본값은 특히 환경 변수에서 가져오거나 런타임에 계산될 때 오해의 소지가 있을 수 있습니다. Optique 0.5.0은 withDefault()
에 선택적 세 번째 매개변수를 통해 기본값이 도움말 출력에 표시되는 방식을 사용자 정의할 수 있게 합니다.
import { withDefault, message, envVar } from "@optique/core";
const parser = object({
// 이전: 도움말에 실제 URL 값 표시
apiUrl: withDefault(
option("--api-url", url()),
new URL("https://api.example.com")
),
// 도움말 표시: --api-url URL [https://api.example.com]
// 이후: 설명 텍스트 표시
apiUrl: withDefault(
option("--api-url", url()),
new URL("https://api.example.com"),
{ message: message`Default API endpoint` }
),
// 도움말 표시: --api-url URL [Default API endpoint]
});
이는 특히 환경 변수와 계산된 기본값에 유용합니다:
const parser = object({
// 환경 변수
authToken: withDefault(
option("--token", string()),
() => process.env.AUTH_TOKEN || "anonymous",
{ message: message`${envVar("AUTH_TOKEN")} or anonymous` }
),
// 도움말 표시: --token STRING [AUTH_TOKEN or anonymous]
// 계산된 값
workers: withDefault(
option("--workers", integer()),
() => os.cpus().length,
{ message: message`Number of CPU cores` }
),
// 도움말 표시: --workers INT [Number of CPU cores]
// 민감한 정보
apiKey: withDefault(
option("--api-key", string()),
() => process.env.SECRET_KEY || "",
{ message: message`From secure storage` }
),
// 도움말 표시: --api-key STRING [From secure storage]
});
실제 기본값을 표시하는 대신, 이제 값이 어디서 오는지 더 잘 설명하는 설명 텍스트를 표시할 수 있습니다. 이는 API 토큰과 같은 민감한 정보나 CPU 코어 수와 같은 계산된 기본값에 특히 유용합니다.
도움말 시스템은 이제 기본값 표시에서 ANSI 색상 코드를 적절히 처리하여, 내부 컴포넌트가 자체 색상 형식을 가지고 있더라도 흐린 스타일을 유지합니다. 이를 통해 기본값이 주요 도움말 텍스트와 시각적으로 구분되도록 보장합니다.
포괄적인 오류 메시지 사용자 정의
모든 파서 유형과 조합자에 걸쳐 오류 메시지를 사용자 정의하는 체계적인 방법을 추가했습니다. 이제 모든 파서는 일반적인 오류 메시지 대신 상황에 맞는 피드백을 제공할 수 있는 errors
옵션을 허용합니다. 이는 기본 파서, 값 파서, 조합자 및 동반 패키지의 특수 파서에도 적용됩니다.
기본 파서 오류
import { option, flag, argument, command } from "@optique/core/primitives";
import { message, optionName, metavar } from "@optique/core/message";
// 사용자 정의 오류가 있는 옵션 파서
const serverPort = option("--port", integer(), {
errors: {
missing: message`Server port is required. Use ${optionName("--port")} to specify.`,
invalidValue: (error) => message`Invalid port number: ${error}`,
endOfInput: message`${optionName("--port")} requires a ${metavar("PORT")} number.`
}
});
// 사용자 정의 오류가 있는 명령 파서
const deployCommand = command("deploy", deployParser, {
errors: {
notMatched: (expected, actual) =>
message`Unknown command "${actual}". Did you mean "${expected}"?`
}
});
값 파서 오류
오류 사용자 정의는 일관된 오류를 위한 정적 메시지나 문제가 있는 입력을 통합하는 동적 함수일 수 있습니다:
import { integer, choice, string } from "@optique/core/valueparser";
// 범위 검증이 있는 정수
const port = integer({
min: 1024,
max: 65535,
errors: {
invalidInteger: message`Port must be a valid number.`,
belowMinimum: (value, min) =>
message`Port ${String(value)} is reserved. Use ${String(min)} or higher.`,
aboveMaximum: (value, max) =>
message`Port ${String(value)} exceeds maximum. Use ${String(max)} or lower.`
}
});
// 유용한 제안이 있는 선택지
const logLevel = choice(["debug", "info", "warn", "error"], {
errors: {
invalidChoice: (input, choices) =>
message`"${input}" is not a valid log level. Choose from: ${values(choices)}.`
}
});
// 패턴 검증이 있는 문자열
const email = string({
pattern: /^[^@]+@[^@]+\.[^@]+$/,
errors: {
patternMismatch: (input) =>
message`"${input}" is not a valid email address. Use format: user@example.com`
}
});
조합자 오류
import { or, multiple, object } from "@optique/core/constructs";
// 사용자 정의 일치 없음 오류가 있는 Or 조합자
const format = or(
flag("--json"),
flag("--yaml"),
flag("--xml"),
{
errors: {
noMatch: message`Please specify an output format: --json, --yaml, or --xml.`,
unexpectedInput: (token) =>
message`Unknown format option "${token}".`
}
}
);
// 개수 검증이 있는 Multiple 파서
const inputFiles = multiple(argument(string()), {
min: 1,
max: 5,
errors: {
tooFew: (count, min) =>
message`At least ${String(min)} file required, but got ${String(count)}.`,
tooMany: (count, max) =>
message`Maximum ${String(max)} files allowed, but got ${String(count)}.`
}
});
패키지별 오류
@optique/run
과 @optique/temporal
패키지 모두 특수 파서에 대한 오류 사용자 정의 지원으로 업데이트되었습니다:
// @optique/run 경로 파서
import { path } from "@optique/run/valueparser";
const configFile = option("--config", path({
mustExist: true,
type: "file",
extensions: [".json", ".yaml"],
errors: {
pathNotFound: (input) =>
message`Configuration file "${input}" not found. Please check the path.`,
notAFile: (input) =>
message`"${input}" is a directory. Please specify a file.`,
invalidExtension: (input, extensions, actual) =>
message`Invalid config format "${actual}". Use ${values(extensions)}.`
}
}));
// @optique/temporal instant 파서
import { instant, duration } from "@optique/temporal";
const timestamp = option("--time", instant({
errors: {
invalidFormat: (input) =>
message`"${input}" is not a valid timestamp. Use ISO 8601 format: 2024-01-01T12:00:00Z`
}
}));
const timeout = option("--timeout", duration({
errors: {
invalidFormat: (input) =>
message`"${input}" is not a valid duration. Use ISO 8601 format: PT30S (30 seconds), PT5M (5 minutes)`
}
}));
오류 사용자 정의는 Optique의 구조화된 메시지 형식과 원활하게 통합되어 모든 오류 출력에서 일관된 스타일을 보장합니다. 이 시스템은 일반적인 오류 메시지로 사용자를 혼란스럽게 하는 대신, 올바른 사용법으로 안내하는 유용하고 실행 가능한 피드백을 제공하는 데 도움이 됩니다.
향후 계획
이번 릴리스는 기존 코드를 손상시키지 않으면서 개발자 경험을 개선하는 데 중점을 두고 있습니다. 모든 새로운 기능은 선택적이며, 모든 변경 사항은 하위 호환성을 유지합니다. 이러한 개선 사항은 특히 명확한 오류 메시지와 유용한 문서가 필요한 사용자 친화적인 CLI 애플리케이션을 구축할 때 Optique를 더 쾌적하게 사용할 수 있게 해준다고 믿습니다.
이러한 개선 사항을 제안하고 토론과 이슈 보고를 통해 이번 릴리스를 형성하는 데 도움을 준 커뮤니티 구성원들에게 감사드립니다. 여러분의 피드백은 Optique가 TypeScript를 위한 더 강력하고 인체공학적인 CLI 파서로 발전하는 데 계속해서 원동력이 되고 있습니다.
Optique 0.5.0으로 업그레이드하려면 간단히 종속성을 업데이트하세요:
npm update @optique/core @optique/run
# 또는
deno update
자세한 마이그레이션 가이드와 API 문서는 공식 문서를 참조하세요. 코드 변경이 필요하지는 않지만, CLI 애플리케이션을 향상시키기 위해 새로운 오류 사용자 정의 옵션과 도움말 텍스트 개선 사항을 탐색해 보시기 바랍니다.