Stricli Documentation

repository·main·Indexed 21 days ago

https://github.com/bloomberg/stricli

A TypeScript framework for building complex, type-safe Command Line Interfaces (CLIs) with zero dependencies. Stricli features a split definition and implementation pattern using lazy module loading, automatic help generation, shell autocomplete for bash, and support for various flag types including enum, counter, and variadic. It provides a core package (@stricli/core) and a scaffolding utility (@stricli/create-app), with compatibility for Node.js, Bun, and Deno v2.

Tokens
30.7K
Snippets
97
Records
156
Agent score
77%

What's inside stricli

  1. Overview of @stricli/core

    main
    @stricli/core is the central package for building complex Command Line Interfaces (CLIs) with full type safety and zero dependencies. It provides the foundational logic required to define command structures, arguments, and options in a type-safe manner.
  2. Overview of Stricli packages

    main

    Stricli is composed of several specialized packages. The primary packages for developers are:

    • @stricli/core: The central engine of the project.
    • @stricli/auto-complete: Provides auto-completion capabilities.
    • @stricli/create-app: A scaffolding tool to quickly set up new projects.
  3. Core Features of Stricli

    main

    Stricli is a TypeScript framework for building CLI applications with the following built-in capabilities:

    • Parameter Support: Handles positional parameters and named flags. Flags can be required, optional, variadic, hidden, or have default values. Supports enum, counter, and pure Boolean flags.
    • Command Structure: Supports a single top-level command or arbitrary nested subcommands with built-in "did you mean...?" suggestions.
    • Automatic Help: Generates help text automatically, with support for custom usage specifications.
    • Developer Experience:
      • Uses camelCase in code while accepting kebab-case on the command line.
      • Provides built-in outdated version warnings.
      • Supports Internationalization (i18n) for all printed strings.
    • Shell Autocomplete: Built-in support for bash (with more shells forthcoming). Autocomplete is driven by JavaScript code at runtime, allowing for dynamic completions fetched programmatically.
  4. Define custom application flags via integrations

    main

    Integrations can provide additional flags to your application. These flags are automatically added to commands and route maps. The flag name matches the property name in your integrations object (respecting the configured scanner-case-style).

    When a user requests a flag, the integration's run function is called. This function can be asynchronous and returns a promise. It results in an exit code of 0 unless an error is thrown.

    Flag Configuration Options:

    • brief: The in-line documentation string for the flag.
    • aliases: An array of single-character aliases (e.g., ['v', 'V']).
    • hidden: If true, the flag is excluded from default help text.
    • global: If true, the flag is available on all commands, not just the root.
    • complete: If true, the flag is included in auto-complete suggestions.
    • defaultForRouteMap: If true, this integration executes automatically if the user's input targets a route map without specifying a subcommand or flag.

    :::note Setting defaultForRouteMap: true is useful for flags like --help to ensure users see documentation when they land on a route map without a specific command. :::

  5. How lifecycle hooks work in Stricli integrations

    main

    Integrations can perform additional logic at specific points during a Stricli application run using lifecycle hooks. These hooks are executed in the order they are defined in the integrations object.

    Available Hooks:

    1. app:start: Called when the application starts.
    2. command:start: Called immediately before a command is executed.
    3. command:end: Called immediately after a command is executed.
    4. app:end: Called when the application is finishing.

    Context and Data:

    • All hooks receive access to the application context.
    • command:* hooks receive information about the current run, including the executed command and unprocessed inputs (via the route scan result).
    • Both command:end and app:end hooks receive the command execution result (the exit code).

    :::warning If a hook throws an error, the application will print the error to stderr and exit with a unique code. It is recommended to wrap hook logic in a try/catch block to handle errors gracefully. :::

  6. Understand the core design philosophy of Stricli

    main

    Stricli is built on the principle that a Command Line Interface (CLI) is a user-facing way to invoke programmatic functions. When designing your CLI with Stricli, keep these mental models in mind:

    • Commands as Functions: Every CLI command should map directly to an underlying function. Command-line arguments (text input) should translate directly to that function's arguments.
    • Arguments and Flags: To support natural function syntax, your application should handle both named, unordered flags and positional arguments.
    • Form Follows Function: The function arguments serve as the single source of truth for parsing. The parser should use these arguments to perform complete type checking. Invalid inputs (missing, extraneous, or incorrectly formatted arguments) should be caught by the framework before the command logic executes.
    • No "Magic": Stricli avoids custom conventions that hide logic. The framework is designed so that developers can understand and debug their CLI using the native tools of their chosen programming language.
  7. Use flag aliases

    main

    Aliases allow you to define single-character alternatives for flags. These are invoked with a single - (e.g., -a). Multiple aliases can be batched together (e.g., -abc is equivalent to -a -b -c).

    Reserved Aliases: When using default integrations, the following characters are reserved and cannot be used as custom aliases:

    • -h: reserved for --help
    • -H: reserved for --helpAll
    • -v: reserved for --version (if version information is provided)
  8. How Stricli handles command definitions and implementation

    main

    Stricli uses a design pattern that splits command definition from command implementation to improve performance and type safety:

    • Split Definition From Implementation: Uses ECMAScript import() for asynchronous lazy module loading. This means the entire command tree can be loaded without importing application-specific runtime dependencies. Implementation code is only loaded and evaluated when the specific command is executed.
    • Type-Checked Parsing: Leverages TypeScript's ability to introspect import() syntax to ensure that the types used for parameter parsing match the types of the function parameters themselves.
    • Command Routing: Commands are treated as objects that can be structured in any way. You can use a "route map" to expose subcommands for a given route. Both commands and route maps automatically generate help text from their specifications.
  9. Understand the CommandContext abstraction

    main

    In Stricli, the CommandContext is a core abstraction that encapsulates the dependencies required for parsing input and writing to the terminal. It primarily contains a process property which provides access to stdout and stderr writable streams.

    This indirection allows Stricli to print help text and error messages without relying on global state. It also enables dependency injection, making it possible to test command implementations by providing mock implementations of the process/streams instead of using the real global process object.

  10. How Stricli handles argument parsing

    main

    Stricli uses a "Form Follows Function" approach to argument parsing. Instead of requiring manual schema definitions, Stricli infers the shape of your CLI parameter definitions directly from the TypeScript types used in your implementation function. It uses advanced conditional types to map implementation parameter types to parser specifications.

    Stricli supports two types of parameters:

    • Named flags: Parameters identified by flags (e.g., --option).
    • Positional arguments: Parameters identified by their position in the command.

    For accurate type inference, especially regarding whether a parameter is optional or not, Stricli relies heavily on TypeScript's type system.

  11. Why Stricli was chosen over other CLI libraries

    main

    Stricli is designed to avoid common pitfalls found in existing JavaScript CLI libraries. The primary differentiators are:

    • End-to-end Static Typing: Unlike libraries that use Method Chaining (e.g., commander, yargs, cac), which often rely on imperative patterns that make it difficult to provide accurate TypeScript types for parsed arguments, Stricli ensures that parsing and types are linked.
    • No Custom DSLs: Many frameworks use a custom Domain Specific Language (DSL) to mirror help text, which requires additional domain knowledge. Stricli avoids "magic" patterns and custom DSLs.
    • Form Follows Function: Stricli ensures the source of truth for argument and flag types is the implementation itself, rather than inverting it through static properties (as seen in oclif) or requiring manual invocation of parse methods.
    • Runtime Agnostic: Unlike oclif, which relies heavily on Node.js-specific features like file system access for command routing, Stricli is designed to work across different server-side runtimes.