Skip to content

Latest commit

Β 

History

History
93 lines (67 loc) Β· 3.77 KB

File metadata and controls

93 lines (67 loc) Β· 3.77 KB

Citty: Elegant, zero-dependency CLI builder for Node.js.

Important: Keep AGENTS.md updated with project information.

Project Structure

src/
β”œβ”€β”€ index.ts          # Public API re-exports
β”œβ”€β”€ types.ts          # All type definitions (ArgDef, CommandDef, ParsedArgs, etc.)
β”œβ”€β”€ command.ts        # defineCommand(), runCommand(), resolveSubCommand()
β”œβ”€β”€ main.ts           # runMain(), createMain() β€” CLI entry point with --help/--version
β”œβ”€β”€ args.ts           # parseArgs() β€” argument parsing and validation
β”œβ”€β”€ usage.ts          # renderUsage(), showUsage() β€” help text generation
β”œβ”€β”€ _parser.ts        # Low-level parser wrapping node:util.parseArgs (internal)
β”œβ”€β”€ _utils.ts         # toArray, formatLineColumns, resolveValue, CLIError (internal)
└── _color.ts         # ANSI color helpers with NO_COLOR support (internal)

test/                 # Vitest tests (args, parser, main, usage, utils)
playground/           # Example CLI apps (run with `pnpm play`)

Internal files use _ prefix and are not exported from index.ts.

Public API

// Core
defineCommand(def)           // Define a typed command
runCommand(cmd, opts)        // Execute a command programmatically
runMain(cmd, opts?)          // CLI entry point (handles --help, --version, process.exit)
createMain(cmd)              // Returns a function wrapping runMain()

// Utilities
parseArgs(rawArgs, argsDef)  // Parse CLI arguments against definitions
renderUsage(cmd, parent?)    // Generate help text string
showUsage(cmd, parent?)      // Print help to console

Architecture

Argument Types

  • positional β€” unnamed args (cli <name>)
  • string β€” named string options (--name value)
  • boolean β€” flags (--verbose, --no-verbose for negation)
  • enum β€” constrained to options array (--level=info|warn|error)

Arguments are case-agnostic β€” --user-name and --userName resolve to the same value via auto-generated aliases (uses scule for case conversion) and a Proxy-based accessor.

Command Lifecycle

setup() β†’ resolve subcommand or run() β†’ cleanup() (always runs in finally)

Lazy Loading

Resolvable<T> = T | Promise<T> | (() => T) | (() => Promise<T>) β€” used for meta, args, and subCommands to support dynamic imports.

Error Handling

Custom CLIError class with error codes: EARG, E_UNKNOWN_COMMAND, E_NO_COMMAND, E_NO_VERSION. Usage is auto-shown before error messages in runMain.

Dependencies

Zero runtime dependencies. Only scule is used from source code (bundled at build time).

Scripts

Command Description
pnpm dev Vitest watch mode
pnpm test Lint + typecheck + vitest with coverage
pnpm test:types Type checking via tsgo --noEmit
pnpm lint oxlint . && oxfmt --check
pnpm fmt oxlint . --fix && oxfmt
pnpm build Build with obuild β†’ dist/
pnpm play Run playground CLI

Tooling

  • Build: obuild (single entry src/index.ts β†’ dist/index.mjs)
  • Test: Vitest with @vitest/coverage-v8, typecheck enabled
  • Lint: oxlint (plugins: unicorn, typescript, oxc)
  • Format: oxfmt
  • TypeScript: strict mode, nodenext module, verbatimModuleSyntax

Conventions

  • ESM with explicit .ts extensions in imports
  • Internal files prefixed with _ (not exported)
  • Tests use inline snapshots for usage output verification
  • Colors respect NO_COLOR, TERM=dumb, TEST, CI env vars
  • --no-flag negation requires default: true or negativeDescription on the arg def