gunshi

repository·main·Indexed 19 days ago

https://github.com/kazupon/gunshi

A modern JavaScript command-line library for creating type-safe, declarative, and composable CLIs. It supports Node.js, Deno, and Bun runtimes. The ecosystem includes a monorepo with packages such as @gunshi/bone for minimal footprints, @gunshi/combinators for type-safe argument schemas, @gunshi/definition for command definitions, and plugins for shell completion (@gunshi/plugin-completion) and dry-run support (@gunshi/plugin-dryrun).

Tokens
144.7K
Snippets
415
Records
541
Agent score
65%

What's inside gunshi

  1. What is a plugin in Gunshi?

    main
    A plugin in Gunshi is a modular extension used to add functionality to a CLI application without modifying its core code. Plugins allow for Separation of Concerns (keeping command logic clean by offloading tasks like logging or authentication), Reusability (using the same functionality across different commands or projects), and Composability (combining multiple plugins like authentication, logging, and database plugins into a single CLI lifecycle).
  2. Use declarative configuration to define commands

    main

    Gunshi allows you to define CLI commands using a declarative structure via the define function. This approach separates the command's metadata, argument definitions, and usage examples from the actual execution logic (run function), making your CLI code more maintainable as complexity grows.

    A command definition object includes:

    • Metadata: name and description to identify the command.
    • Arguments (args): A map of argument definitions (type, short flags, descriptions, defaults).
    • Examples: A string containing usage examples for the help text.
    • Run function: An async function that receives a context (ctx) and executes the command logic.
    import { define } from 'gunshi'
    
    const command = define({
      name: 'command-name',
      description: 'Command description',
      args: {
        // Argument definitions
      },
      examples: 'Example usage',
      run: ctx => {
        // Command implementation
      }
    })
  3. How context extensions work in Gunshi

    main

    In Gunshi, the command context (ctx) is the central object passed to every command runner. Plugins enhance this context by adding new capabilities through the ctx.extensions property.

    To prevent naming collisions, each plugin registers its extension under a unique identifier (plugin ID) within the ctx.extensions object. This namespacing ensures that different plugins can coexist without overwriting each other's functionality. When a plugin is added to your CLI configuration, its extension becomes available to all commands via ctx.extensions[pluginId].

    import { define } from 'gunshi'
    import { pluginId as globalId } from '@gunshi/plugin-global'
    
    const command = define({
      run: ctx => {
        // Access global plugin extension via its unique ID
        const globalExtension = ctx.extensions[globalId]
    
        // Use extension methods
        globalExtension.showVersion()
        globalExtension.showHeader()
      }
    })
  4. Understand the execution order of multiple Renderer Decorators

    main

    When multiple plugins register renderer decorators, Gunshi builds a chain using a for loop that iterates through the decorator array from first to last. Each decorator wraps the result of the previous one.

    Execution Flow

    1. The base renderer starts as an empty string.
    2. @gunshi/plugin-renderer (a default plugin) wraps the base and provides the actual implementation.
    3. Your custom plugins wrap the previous result in the order they are listed in the plugins array.

    If you call await baseRenderer(ctx) at the start of your decorator, your logic executes after the underlying renderer has finished, but your return value will be the last thing processed in the chain.

    Example Chain: Base $\rightarrow$ plugin-renderer $\rightarrow$ Plugin A $\rightarrow$ Plugin B (where Plugin B wraps Plugin A).

    If Plugin B calls baseRenderer first, it waits for Plugin A to finish, then adds its own content. This results in Plugin B's content appearing last in the output.

  5. How plugin extensions and CommandContext work together

    main

    Gunshi uses a composition pattern to extend the CommandContext.

    1. Plugin Registration: During CLI initialization, plugins are executed. If a plugin provides an extension, it is registered via a unique Symbol.
    2. Command Declaration: A command declares which extensions it needs via the extensions field.
    3. Context Creation: When a command is executed, createCommandContext is called. It identifies the required extensions, runs their factory functions using the CommandContextCore (the base context containing arguments and values), and merges the results into a new ext property on the final CommandContext.
    4. Type Safety: The ExtendedCommand type ensures that the run function's ctx.ext object is mapped to the return types of the extension factories, providing full IDE autocompletion and compile-time checks.
  6. Understand the Gunshi Type System

    main

    Gunshi v0.27 uses a type parameter system to provide compile-time safety for CLI applications. It ensures that command arguments and plugin extensions are correctly typed throughout the lifecycle of a command.

    Key components include:

    • GunshiParams: The core type used to define the shape of command arguments (args) and plugin extensions (extensions).
    • Core Functions: define and lazy provide automatic type inference for standard commands.
    • Plugin Extensions: defineWithTypes and lazyWithTypes allow commands to declare expected extensions from plugins that are installed at runtime.
    • CLI Entry Point: The cli function uses type parameters to ensure the entry command is correctly typed.
  7. Understand the rendering architecture and decorator pattern

    main

    Gunshi uses a decorator pattern via plugin-renderer to handle the display of command information. The rendering flow involves three main components:

    1. renderHeader: Displays command header information.
    2. renderUsage: Displays command usage instructions.
    3. renderValidationErrors: Displays errors encountered during validation.

    Important Behavior: Decorators are applied in a chain and follow a LIFO (Last In, First Out) execution order. This means the last plugin registered will be the first one to execute its decorator. This can lead to conflicts where a later plugin overrides the rendering intent of an earlier one if not managed via the rendering property or dedicated control plugins.

  8. When to use `define` vs plain objects

    main

    Choosing between define and plain JavaScript objects depends on your environment and needs:

    Use define when:

    • You are using TypeScript.
    • You want automatic type inference.
    • You want IDE autocompletion for the command context.
    • You want to catch errors at compile time.

    Use plain objects when:

    • You are writing plain JavaScript.
    • You prefer explicit type annotations.
    • You are integrating with existing type definitions.
  9. Understand the relationship between @gunshi/plugin and gunshi/plugin

    main

    The APIs and TypeScript type definitions provided by @gunshi/plugin are identical to those found in the gunshi/plugin entry point of the main gunshi package.

    When to use @gunshi/plugin:

    • When you are building a standalone plugin package.
    • When you want to reduce the installation size of your plugin's dependencies.

    When to use gunshi/plugin:

    • When you are already using the main gunshi package in your project and want to avoid adding an extra dependency.
  10. How CLI hooks and Plugin Decorators interact

    main

    Gunshi uses two different mechanisms for controlling execution:

    1. CLI-level Hooks: Run before and after the entire command execution process (including the plugin chain). They are defined in the cli() configuration.
    2. Plugin Decorators: Wrap the command runner itself. Plugins use decorateCommand to add or modify functionality. Multiple decorators form a chain applied in reverse order (LIFO - last registered, first executed).

    Execution Sequence:

    1. onBeforeCommand Hook
    2. Plugin Decorator Chain (wraps the runner)
    3. Command Runner (the actual run function)
    4. onAfterCommand Hook (if successful)
    5. onErrorCommand Hook (if any step above fails)
  11. How built-in help and versioning work

    main

    Gunshi provides automatic help generation (e.g., via --help) and versioning (e.g., via --version). When using the standard cli() function from the main gunshi package, these features are enabled by default via two built-in plugins:

    • @gunshi/plugin-global: Provides global options like --help and --version.
    • @gunshi/plugin-renderer: Handles formatted output for help messages, error messages, and usage information.

    Note: If you use the lower-level run() function instead of cli(), you must manually configure these plugins to enable help and version functionality.