Modern Zod-based CLI builder, fully type-safe, super lightweight and flexible.
Highlights
- ðŊ Full TypeScript support with robust type inference
- ðĄïļ Runtime validation using Zod
- ð Supports command aliases and nested commands
- ð Global and command-specific options
- ðĶ Shareable command presets
- ðĻ Flexible configuration API
- ðŠķ Lightweight with zero runtime dependencies (except Zod)
Installation
npm install zodest zod # or yarn add zodest zod # or pnpm add zodest zod # or bun add zodest zod
Quick Example
import { z } from 'zod'; import { defineCommand, defineConfig, defineOptions } from 'zodest/config'; import { processConfig } from 'zodest'; const globalOptions = defineOptions( z.object({ verbose: z.boolean().default(false), }), ); const config = defineConfig({ globalOptions, commands: { serve: defineCommand({ description: 'Start development server', options: defineOptions( z.object({ port: z.coerce.number().min(1024).default(3000), }), { p: 'port' }, // aliases, type-safe with the schema ), args: z.array(z.string()), action(options, args) { console.log('Server starting on port', options.port); }, }), }, }); // Process CLI arguments, returns the matching command const result = processConfig(config, process.argv.slice(2)); console.log('result:', result._kind, result); // => result._kind == 'serve', the matching command's name // in `result` there's also `result.globalOptions`, and so on, all type-safe // call the command result.command.action(result.options, result.args);
Core Concepts
Commands
Commands are the basic building blocks. Each command can have:
- Description
- Options (with Zod validation)
- Arguments (with Zod validation)
- Aliases
- Action function
const myCommand = defineCommand({ description: 'My command description', options: defineOptions( z.object({ force: z.boolean().default(false), }), ), args: z.string().min(3), aliases: ['mc', 'mycmd'], action(options, args) { // Fully typed options and args }, });
Global Options
Shared options across all commands:
const globalOptions = defineOptions( z.object({ debug: z.boolean().default(false), config: z.string().optional(), }), { d: 'debug', // aliases c: 'config', }, );
Nested Commands
Support for namespaced commands:
const config = defineConfig({ commands: { 'user add': defineCommand({...}), 'user remove': defineCommand({...}), 'user list': defineCommand({...}), } });
Shareable Command Presets
Create reusable command sets:
import { withGlobalOptions } from 'zodest/config'; const userCommands = withGlobalOptions(globalOptions, (gopts) => ({ userAdd: defineCommand({...}), userRemove: defineCommand({...}), })); // Use in any CLI const config = defineConfig({ globalOptions, commands: (gopts) => ({ ...userCommands(gopts), // Add more commands }) });
API Reference
defineConfig(config)
Creates a type-safe CLI configuration.
defineCommand(command)
Defines a new command with options, arguments, and an action.
defineOptions(schema, aliases?)
Creates a type-safe options definition with optional aliases.
processConfig(config, argv)
Processes command line arguments against the configuration.
withGlobalOptions(globalOptions, commands)
Creates a command preset with access to global options.
getCommands(config)
Returns all available commands from the configuration.
Advanced Examples
Command with Complex Arguments
const command = defineCommand({ description: 'Complex command', options: defineOptions( z.object({ mode: z.enum(['development', 'production']), port: z.coerce.number().min(1024), }), ), args: z.tuple([z.string(), z.coerce.number()]), action(options, [path, num]) { // path is string, num is number }, });
Default Commands
NOTE: mixing grouped commands with non-grouped ones breaks the defaultCommand inference of the
grouped commands, so you must use as any to mute TypeScript, or use a command from non-grouped
commands
const config = defineConfig({ defaultCommand: 'serve', commands: { serve: defineCommand({...}) dev: defineCommand({...}) } });
Command Groups
const devCommands = withGlobalOptions(globalOptions, (gopts) => ({ serve: defineCommand({...}), build: defineCommand({...}), })); const dbCommands = withGlobalOptions(globalOptions, (gopts) => ({ 'db migrate': defineCommand({...}), 'db seed': defineCommand({...}), })); const config = defineConfig({ globalOptions, commands: (gopts) => ({ ...devCommands(gopts), ...dbCommands(gopts), }) });
License
Apache-2.0