Optique 0.10.0: Runtime context, config files, man pages, and network parsers #108
dahlia
announced in
Announcements
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
We're excited to announce Optique 0.10.0, the largest release yet! This release introduces a runtime context system for composing external data sources, a config file integration package, man page generation, inter-option dependencies, 11 new network value parsers, and several help output improvements driven by community feedback.
Optique is a type-safe combinatorial CLI parser for TypeScript, providing a functional approach to building command-line interfaces with composable parsers and full type inference.
Annotations and source contexts
Real-world CLI applications rarely rely on command-line arguments alone. They pull configuration from files, environment variables, and other external sources, typically following a priority chain like CLI > environment > config file > defaults.
Optique 0.10.0 introduces two complementary systems to support this pattern natively.
Annotations: a low-level primitive
The new annotations system allows runtime data to be injected into a parsing session via symbol-keyed records. Parsers can access this data during both
parse()andcomplete()phases, enabling use cases like config file fallbacks and environment-based validation.Annotations are symbol-keyed to prevent naming conflicts between packages, and the entire system is opt-in: existing parsers work unchanged since the
optionsparameter is optional.Source contexts: a high-level abstraction
Building on annotations, the
SourceContextinterface andrunWith()function provide a structured way to compose multiple data sources with automatic priority handling. For the design rationale and discussion, see #85.The
runWith()function uses a smart two-phase approach: static contexts (like environment variables) return data immediately, while dynamic contexts (like config files whose paths come from CLI arguments) get a second chance to provide data after an initial parse. If all contexts are static, the second parse is skipped for optimization.Importantly,
runWith()ensures that--help,--version, and shell completion always work, even when config files are missing or contexts would fail to load. For details on this early-exit optimization, see #92.New @optique/config package
The new @optique/config package provides first-class config file support with type-safe validation using Standard Schema (Zod, Valibot, ArkType, and others). The original proposal and API design are discussed in #84 and #90.
The package supports single-file loading with automatic format detection, custom file parsers for formats like TOML or YAML, and multi-file merging scenarios (system, user, project config cascading) via the
loadcallback. It delegates torunWith()internally, so help, version, and completion features work even when config files are missing (see #93 for the refactoring that made this possible).See the config file integration guide for detailed documentation.
New @optique/man package
The new @optique/man package generates Unix man pages from parser metadata, eliminating the need to maintain man pages separately from parser definitions. The original proposal is in #77.
Optique's structured
Messagesystem maps naturally to roff formatting:optionName()becomes bold,metavar()becomes italic, and so on. The package also includes a CLI tool for build-time generation:The
optique-manCLI supports loading TypeScript files directly on Deno, Bun, and Node.js 25.2.0+ (or Node.js withtsx).See the man page documentation for complete examples.
PrograminterfacePreviously, metadata like program name, version, and descriptions had to be passed separately each time
run()orrunParser()was called, and duplicated again for man page generation. The newPrograminterface bundles a parser with its metadata into a single source of truth. The motivation and design decisions are discussed in #82.All major APIs now accept
Programobjects directly:The
ProgramMetadatainterface includes fields forauthor,bugs, andexamples, which are now displayed in help output when provided. Both the newProgram-based API and the original parser-based API are supported for backward compatibility.Inter-option dependencies
One of the most architecturally challenging features in this release is inter-option dependency support. When building CLI tools that mirror Git's interface, it's common to have a global option like
-C <dir>that changes the working directory for subsequent operations. Ideally, value parsers should be able to reference the parsed value of-Cto validate and provide completions from the correct repository. The problem turned out to be surprisingly hard; #74 documents the approaches that were considered and rejected before arriving at the current design, and #76 contains the implementation.This is a genuinely difficult problem: value parsers normally have no access to other options' parsed values, and all the obvious solutions (dynamic resolvers, context injection, post-parsing validation) either duplicate parsing logic or solve only part of the problem.
Optique's solution uses deferred parsing: dependent options store their raw input during initial parsing, then re-validate using actual dependency values after all options are collected.
Dependencies work seamlessly with all parser combinators and support context-aware shell completions: the available completions for the dependent option change based on the dependency's current value.
See the inter-option dependencies documentation for detailed examples.
Network value parsers
CLI tools frequently need to parse and validate network-related inputs. Previously, users had to use
string()with manual validation or write custom parsers. This release adds 11 built-in network value parsers to@optique/core/valueparser, all with rich validation options, security-conscious implementations (ReDoS prevention), and consistent error messages. The full specification, including security considerations, is in #89.port()8080ipv4()192.168.1.1ipv6()2001:db8::1ip()192.168.1.1or::1cidr()192.168.0.0/24hostname()example.comdomain()example.comemail()user@example.comsocketAddress()localhost:3000portRange()8000-8080macAddress()00:1A:2B:3C:4D:5EEach parser provides fine-grained control over what is accepted. For example,
ipv4()can restrict private, loopback, link-local, multicast, broadcast, and zero addresses:The
nonEmpty()modifierThe new
nonEmpty()modifier requires a parser to consume at least one input token to succeed. This solves a specific problem withlongestMatch(): when combining parsers where one has all-optional fields (and thus can succeed consuming zero tokens), there was no way to distinguish “no options provided” from “options provided with defaults.” The use case and design are described in #79, and the implementation is in #80.Help output improvements
Several improvements to help output were driven by community feedback from @cspotcode:
Completion
helpVisibilityWhen using
completion: "both", help output previously showed bothcompletionandcompletionscommands, cluttering the display. The newhelpVisibilityoption controls which completion aliases appear in help while keeping both functional at runtime. This was reported by @cspotcode in #99.showChoicesoptionSimilar to the existing
showDefaultfeature, the newshowChoicesoption displays valid choice values in help output. Previously, users could only discover valid values after providing an invalid one and reading the error message. See #106 for the full design.Meta-command grouping
The new
groupoption for help, version, and completion commands allows them to appear under a titled section in help output instead of alongside user-defined commands. This was originally reported as a help ordering issue by @cspotcode in #107.New message components
Two new message components improve structured text formatting:
url()(and its aliaslink()): Displays URLs with clickable hyperlinks in terminals that support OSC 8 escape sequences. When colors are enabled, URLs are rendered as clickable links; when quotes are enabled, they are wrapped in angle brackets.lineBreak(): Provides explicit single-line breaks in structured messages. Unlike\nintext()terms (which are treated as soft breaks),lineBreak()always renders as a hard line break.Breaking changes
Removed the deprecated
runexport from@optique/core/facade. UserunParser()instead. The old name was deprecated in v0.9.0 due to naming conflicts with@optique/run'srun()function; see Remove deprecatedrunandRunErroraliases from @optique/core in v0.10.0 #65 for the original discussion.Removed the deprecated
RunErrorexport from@optique/core/facade. UseRunParserErrorinstead. This was also part of the rename in Remove deprecatedrunandRunErroraliases from @optique/core in v0.10.0 #65.Installation
For config file support:
For man page generation:
Looking forward
Optique 0.10.0 is the last planned pre-release before 1.0.0. The annotations and source context systems introduced in this release lay the groundwork for two new packages planned for 1.0.0:
@optique/env (Add
@optique/envpackage for environment variable support #86): Type-safe environment variable support, implementing theSourceContextinterface as a static source. It will providecreateEnvContext()for defining environment variable prefixes,bindEnv()for binding parsers to environment variables, and abool()value parser for common boolean representations. Being a static source, it requires no two-phase parsing.@optique/inquirer (Add interactive prompt support via Inquirer.js integration #87): Interactive prompt support via Inquirer.js, enabling CLI tools to automatically prompt for missing values. Unlike config and env integration, prompts execute directly in the parser's
complete()phase rather than going through theSourceContextpattern, since they don't read external data but rather ask the user directly.Together with the existing @optique/config, these packages will enable the full CLI > environment > config > interactive prompt > default value priority chain that production CLI applications need.
We're grateful to @cspotcode for reporting help output issues (#99, #107) that were addressed in this release. If you have ideas for future improvements or encounter any issues, please let us know through GitHub Issues. For more information about Optique and its features, visit the documentation or check out the full changelog.
Beta Was this translation helpful? Give feedback.
All reactions