Skip to content
/ zli Public

πŸ“Ÿ A powerful CLI parser built with TypeScript and Zod for type-safe command-line interfaces.

License

Notifications You must be signed in to change notification settings

robingenz/zli

Repository files navigation

@robingenz/zli

npm version npm downloads license

A powerful CLI parser built with TypeScript and Zod for type-safe command-line interfaces.

Features

  • πŸ›‘οΈ Type-safe: Built with TypeScript and Zod for runtime validation.
  • πŸ“‹ Declarative: Define commands, options, and arguments with simple schemas.
  • πŸ”„ Flexible parsing: Support for long flags, short flags, and flag clustering.
  • πŸ”€ Smart conversion: Automatic kebab-case to camelCase conversion.
  • 🏷️ Alias support: Define short aliases for any option.
  • πŸ“¦ Array handling: Automatic normalization of single values to arrays.
  • 🎯 Default commands: Set a default command to run when no command is specified.
  • ❓ Help message: Automatic help generation for commands and options.
  • ⚠️ Error handling: Clear, actionable error messages.
  • πŸš€ Zero dependencies: Only requires Zod as a peer dependency.
  • πŸ“¦ ESM support: Modern ES modules with full TypeScript support.

Installation

npm install @robingenz/zli zod

Usage

Basic Example

import { z } from 'zod';
import { defineConfig, defineCommand, defineOptions, processConfig } from '@robingenz/zli';

// Define a simple command
const greetCommand = defineCommand({
  description: 'Greet someone',
  options: defineOptions(
    z.object({
      name: z.string().describe('Name to greet'),
      loud: z.boolean().default(false).describe('Use uppercase'),
    }),
    { n: 'name', l: 'loud' } // Short aliases
  ),
  action: async (options) => {
    const greeting = `Hello, ${options.name}!`;
    console.log(options.loud ? greeting.toUpperCase() : greeting);
  },
});

// Configure the CLI
const config = defineConfig({
  meta: {
    name: 'my-cli',
    version: '1.0.0',
    description: 'A simple CLI example',
  },
  commands: {
    greet: greetCommand,
  },
  defaultCommand: greetCommand,
});

// Process command line arguments
try {
  const result = processConfig(config, process.argv.slice(2));
  await result.command.action(result.options, result.args);
} catch (error) {
  console.error('Error:', error.message);
  process.exit(1);
}

Command Usage

# Show help
my-cli --help
my-cli greet --help

# Run commands
my-cli greet --name Alice
my-cli greet -n Bob --loud

# Run default command
my-cli --name Alice
my-cli greet --name Alice

Advanced Features

Default Commands

You can specify a default command that will be executed when no command is provided:

const config = defineConfig({
  meta: {
    name: 'my-app',
    version: '1.0.0',
  },
  commands: {
    start: startCommand,
    build: buildCommand,
  },
  defaultCommand: startCommand,
});

With this configuration:

  • my-app will run the startCommand
  • my-app --help will still show the help message
  • my-app build will run the build command

Commands with Arguments

const copyCommand = defineCommand({
  description: 'Copy a file',
  args: z.tuple([
    z.string().describe('Source file'),
    z.string().describe('Destination file'),
  ]),
  options: defineOptions(
    z.object({
      verbose: z.boolean().default(false).describe('Verbose output'),
    }),
    { v: 'verbose' }
  ),
  action: async (options, args) => {
    const [source, dest] = args;
    console.log(`Copying ${source} to ${dest}`);
  },
});

Array Options

const options = defineOptions(
  z.object({
    files: z.array(z.string()).describe('Input files'),
    tags: z.array(z.string()).optional().describe('Tags to apply'),
  })
);

// Usage: --files file1.txt --files file2.txt
// Single values are automatically converted to arrays

Type Transformations

const options = defineOptions(
  z.object({
    port: z.coerce.number().min(1).max(65535).describe('Port number'),
    count: z.coerce.number().min(1).describe('Count'),
  })
);

Flag Parsing

@robingenz/zli supports various flag formats:

# Long flags
--verbose --name=value --port 3000

# Short flags  
-v -n value -p 3000

# Flag clustering
-abc  # equivalent to -a -b -c

# Kebab-case conversion
--my-option  # becomes myOption in your code

# Multiple values
--file a.txt --file b.txt  # becomes ['a.txt', 'b.txt']

API Reference

defineOptions(schema, aliases?)

Define options for a command with optional aliases.

  • schema: Zod object schema defining the options
  • aliases: Optional object mapping short aliases to option names

defineCommand(config)

Define a command with options, arguments, and action.

  • description: Command description for help
  • options: Options definition (optional)
  • args: Zod schema for arguments (optional)
  • action: Function to execute when command is run

defineConfig(config)

Define the CLI configuration.

  • meta: CLI metadata (name, version, description)
  • commands: Object mapping command names to definitions
  • defaultCommand: Optional default command definition to run when no command is specified

processConfig(config, args)

Process command line arguments and return the result.

  • config: CLI configuration
  • args: Command line arguments (typically process.argv.slice(2))

Returns an object with:

  • command: The matched command definition
  • options: Parsed and validated options
  • args: Parsed and validated arguments

Changelog

See CHANGELOG.md.

License

See LICENSE.

About

πŸ“Ÿ A powerful CLI parser built with TypeScript and Zod for type-safe command-line interfaces.

Topics

Resources

License

Stars

Watchers

Forks

Sponsor this project