Clerc

repository·main·Indexed 42 days ago

https://github.com/clercjs/clerc

A lightweight, full-featured library for building CLI applications in Node.js, Deno, or Bun. It features a plugin-based architecture, a chainable strongly-typed API, and a non-greedy parser supporting dot-nested objects, custom type functions, and automatic camelCase conversion of flags and parameters.

Tokens
33.4K
Snippets
130
Records
149
Agent score
53%

What's inside Clerc

  1. Key features of Clerc

    main

    Clerc is designed for building CLI applications in Node.js, Deno, or Bun with the following characteristics:

    • Lightweight: Dependencies are bundled.
    • Plugin system: Add rich features (like help text generation or auto-completions) on demand.
    • Chainable APIs: Highly composable interface.
    • Developer friendly: Strongly typed with automatic conversion of flags and parameters to camelCase.
    • Parameter parsing: Automatically parses arguments so you don't have to manually process them.
  2. Create subcommands and root commands

    main

    Subcommands

    Define subcommands by using spaces in the command name string. For example, command("parent child") creates a nested command structure.

    Root Command

    To handle cases where no subcommand is specified (running the base script name), define a command with an empty string name "".

    // Subcommand
    .command("parent child", "A subcommand")
      .on("parent child", (ctx) => { ... })
    
    // Root command
    .command("", "Root command")
      .on("", (ctx) => { ... })
  3. Organize help output with Command and Flag Groups

    main

    You can organize commands and flags into logical sections using the groups option in helpPlugin. This prevents a single long list and makes the help output easier to navigate.

    • commands: An array of [groupName, groupDescription] tuples. Assign commands to a group using the help: { group: 'groupName' } option in the command definition.
    • flags: An array of [groupName, groupDescription] tuples for command-specific flags.
    • globalFlags: An array of [groupName, groupDescription] tuples for global flags.
    const cli = Clerc.create()
      .scriptName("my-cli")
      .use(
        helpPlugin({
          groups: {
            commands: [["dev", "Development Commands"]],
            flags: [["input", "Input Options"]],
            globalFlags: [["help", "Help Options"]],
          },
        }),
      )
      .command("dev", "Start development server", {
        help: {
          group: "dev",
        },
      })
      .parse();
  4. Use Arrays and Counters for flags

    main

    To collect multiple values or count occurrences, use an array type in the type property.

    • Arrays: Use [Type] to collect multiple values into an array.
    • Counters: Use [Boolean] to count how many times a flag appears (e.g., -vvv results in 3).
    // Array of strings
    const { flags: arrayFlags } = parse(["--arr", "a", "--arr", "b"], {
      flags: {
        arr: { type: [String] },
      },
    });
    // arrayFlags: { arr: ["a", "b"] }
    
    // Counter
    const { flags: counterFlags } = parse(["-vvv"], {
      flags: {
        verbose: { type: [Boolean], alias: "v" },
      },
    });
    // counterFlags: { verbose: 3 }
  5. Understand the Context object in handlers and interceptors

    main

    In Clerc, both handler functions and interceptor functions receive a context object as their first parameter. This object provides metadata about the current CLI invocation, including parsed commands, parameters, flags, and unparsed arguments.

    While the keys are identical in both, the values may differ:

    • In an interceptor, resolved indicates if a matching command was found.
    • In a handler, resolved is always true, command is always present, and calledAs always has a value.
  6. Use array and counter flag types

    main

    To allow a flag to accept multiple values or act as a counter, wrap the type function in an array.

    • Array Type: type: [Number] allows users to pass the flag multiple times (e.g., -n 1 -n 2), resulting in an array [1, 2].
    • Counter Type: type: [Boolean] allows users to pass the flag multiple times to increment a value (e.g., -c -c -c results in 3).
    const cli = Cli()
      .command("echo", "Echo", {
        flags: {
          someNumber: {
            type: [Number],
            short: "n",
            description: "Array of numbers",
          },
          counter: {
            type: [Boolean],
            description: "A counter flag",
          },
        },
      })
      .on("echo", (ctx) => {
        // $ node cli.mjs echo -n 1 -n 2 -c -c
        console.log(ctx.flags.someNumber); // [1, 2]
        console.log(ctx.flags.counter);     // 2
      })
      .parse();
  7. Use built-in basic types for flags and parameters

    main

    Clerc provides several built-in type constructors to validate, convert, and parse values. These can be used in the type property of a flag or parameter configuration.

    • String: Accepts string values. Default value is undefined unless a default is specified.
    • Number: Accepts numeric values.
    • Boolean: Creates boolean switches. Default value is false. Simply mentioning the flag sets it to true.
    • Object: Accepts key-value pairs using dot notation (e.g., --flag.key value). Default value is {}.
    const cli = Cli()
      .command("example", "description", {
        flags: {
          name: { type: String, default: "World" },
          debug: { type: Boolean },
          count: { type: Number },
          config: { type: Object }
        }
      })
      .parse();
  8. Use Merged Short Flags

    main

    Multiple boolean short flags can be combined into a single argument (e.g., -abc). The last flag in the group can optionally take a value.

    • -abc is equivalent to a=true, b=true, c=true.
    • -ab val or -abval (if b is not a boolean) allows the last flag to consume the next argument.
    // -abc => a=true, b=true, c=true
    const { flags: merged } = parse(["-abc"], {
      flags: {
        a: Boolean,
        b: Boolean,
        c: Boolean,
      },
    });
    
    // -ab val => a=true, b="val"
    const { flags: withValue } = parse(["-ab", "val"], {
      flags: {
        a: Boolean,
        b: String,
      },
    });
  9. Handle Double Dash (--)

    main

    Arguments appearing after a -- are not parsed as flags. They are collected in the doubleDash array.

    const { flags, doubleDash } = parse(["--foo", "--", "--bar"], {
      flags: {
        foo: Boolean,
        bar: Boolean,
      },
    });
    // flags: { foo: true, bar: false }
    // doubleDash: ["--bar"]
  10. Accessing flags and parameters in interceptors

    main

    Interceptors have access to ctx.flags and ctx.parameters via the InterceptorContext.

    Important Note on Validation: Required flag and argument validation errors are only thrown before the command handler is called. If you attempt to access a required flag in an interceptor and the user has not provided it, the flag will be missing from ctx.flags, and the validation error will only be thrown when the command handler is eventually invoked.

  11. Completions Plugin features and logic

    main

    The @clerc/plugin-completions plugin provides automatic script generation for:

    • Command autocompletion: Completes available commands or subcommands.
    • Flag autocompletion: Completes global and command-specific short (-) and long (--) flags.

    Completion Behavior

    • my-cli <TAB>: Completes available commands.
    • my-cli command <TAB>: Completes subcommands of the specified command.
    • my-cli -<TAB>: Completes global short flags (e.g., -h, -V).
    • my-cli --<TAB>: Completes global long flags.
    • my-cli command -<TAB>: Completes short flags for the specific command (including global flags).
    • my-cli command --<TAB>: Completes all available flags for the specific command (including global flags).