@drizzle-team/brocli

中文文档
0.12.1发布于 2026/09/05Apache-2.0TypeScriptESM + CJS零依赖构建来源已验证近 30 天下载 1.3亿

Modern type-safe way of building CLIs

原始使用文档
文档目录8 节
  1. Why?
  2. Learn by examples
  3. Option builder
  4. Creating handlers
  5. Defining commands
  6. Running commands
  7. Additional functions
  8. CLI

Brocli 🥦

Modern type-safe way of building CLIs with TypeScript or JavaScript
by Drizzle Team

import { command, string, boolean, run } from "@drizzle-team/brocli";

const push = command({
  name: "push",
  options: {
    dialect: string().enum("postgresql", "mysql", "sqlite"),
    databaseSchema: string().required(),
    databaseUrl: string().required(),
    strict: boolean().default(false),
  },
  handler: (opts) => {
    ...
  },
});

run([push]); // parse shell arguments and run command

Why?

Brocli is meant to solve a list of challenges we've faced while building Drizzle ORM CLI companion for generating and running SQL schema migrations:

  • Explicit, straightforward and discoverable API
  • Typed options(arguments) with built in validation
  • Ability to reuse options(or option sets) across commands
  • Transformer hook to decouple runtime config consumption from command business logic
  • --version, -v as either string or callback
  • Command hooks to run common stuff before/after command
  • Explicit global params passthrough
  • Testability, the most important part for us to iterate without breaking
  • Themes, simple API to style global/command helps
  • Docs generation API to eliminate docs drifting

Learn by examples

If you need API referece - see here, this list of practical example is meant to a be a zero to hero walk through for you to learn Brocli 🚀

Simple echo command with positional argument:

import { run, command, positional } from "@drizzle-team/brocli";

const echo = command({
  name: "echo",
  options: {
    text: positional().desc("Text to echo").default("echo"),
  },
  handler: (opts) => {
    console.log(opts.text);
  },
});

run([echo])
~ bun run index.ts echo
echo

~ bun run index.ts echo text
text

Print version with --version -v:

...

run([echo], {
  version: "1.0.0",
);
~ bun run index.ts --version
1.0.0

Version accepts async callback for you to do any kind of io if necessary before printing cli version:

import { run, command, positional } from "@drizzle-team/brocli";

const version = async () => {
  // you can run async here, for example fetch version of runtime-dependend library

  const envVersion = process.env.CLI_VERSION;
  console.log(chalk.gray(envVersion), "\n");
};

const echo = command({ ... });

run([echo], {
  version: version,
);

API reference

command

options

run

Brocli command declaration has:
name - command name, will be listed in help
desc - optional description, will be listed in the command help
shortDesc - optional short description, will be listed in the all commands/all subcommands help
aliases - command name aliases
hidden - flag to hide command from help
help - command help text or a callback to print help text with dynamically provided config
options - typed list of shell arguments to be parsed and provided to transform or handler
transform - optional hook, will be called before handler to modify CLI params
handler - called with either typed options or transform params, place to run your command business logic
metadata - optional meta information for docs generation flow

name, desc, shortDesc and metadata are provided to docs generation step

import { command, string, boolean } from "@drizzle-team/brocli";



const push = command({
  name: "push",
  options: {
    dialect: string().enum("postgresql", "mysql", "sqlite"),
    databaseSchema: string().required(),
    databaseUrl: string().required(),
    strict: boolean().default(false),
  },
  transform: (opts) => {
  },
  handler: (opts) => {
    ...
  },
});
import { command } from "@drizzle-team/brocli";

const cmd = command({
  name: "cmd",
  options: {
    dialect: string().enum("postgresql", "mysql", "sqlite"),
    schema: string().required(),
    url: string().required(),
  },
  handler: (opts) => {
    ...
  },
});

Option builder

Initial builder functions:

  • string(name?: string) - defines option as a string-type option which requires data to be passed as --option=value or --option value

    • name - name by which option is passed in cli args
      If not specified, defaults to key of this option
      :warning: - must not contain = character, not be in --help,-h,--version,-v and be unique per each command
      :speech_balloon: - will be automatically prefixed with - if one character long, -- if longer
      If you wish to have only single hyphen as a prefix on multi character name - simply specify name with it: string('-longname')
  • number(name?: string) - defines option as a number-type option which requires data to be passed as --option=value or --option value

    • name - name by which option is passed in cli args
      If not specified, defaults to key of this option
      :warning: - must not contain = character, not be in --help,-h,--version,-v and be unique per each command
      :speech_balloon: - will be automatically prefixed with - if one character long, -- if longer
      If you wish to have only single hyphen as a prefix on multi character name - simply specify name with it: number('-longname')
  • boolean(name?: string) - defines option as a boolean-type option which requires data to be passed as --option

    • name - name by which option is passed in cli args
      If not specified, defaults to key of this option
      :warning: - must not contain = character, not be in --help,-h,--version,-v and be unique per each command
      :speech_balloon: - will be automatically prefixed with - if one character long, -- if longer
      If you wish to have only single hyphen as a prefix on multi character name - simply specify name with it: boolean('-longname')
  • positional(displayName?: string) - defines option as a positional-type option which requires data to be passed after a command as command value

    • displayName - name by which option is passed in cli args
      If not specified, defaults to key of this option
      :warning: - does not consume options and data that starts with

Extensions:

  • .alias(...aliases: string[]) - defines aliases for option

    • aliases - aliases by which option is passed in cli args
      :warning: - must not contain = character, not be in --help,-h,--version,-v and be unique per each command
      :speech_balloon: - will be automatically prefixed with - if one character long, -- if longer
      If you wish to have only single hyphen as a prefix on multi character alias - simply specify alias with it: .alias('-longname')
  • .desc(description: string) - defines description for option to be displayed in help command

  • .required() - sets option as required, which means that application will print an error if it is not present in cli args

  • .default(value: string | boolean) - sets default value for option which will be assigned to it in case it is not present in cli args

  • .hidden() - sets option as hidden - option will be omitted from being displayed in help command

  • .enum(values: [string, ...string[]]) - limits values of string to one of specified here

    • values - allowed enum values
  • .int() - ensures that number is an integer

  • .min(value: number) - specified minimal allowed value for numbers

    • value - minimal allowed value
      :warning: - does not limit defaults
  • .max(value: number) - specified maximal allowed value for numbers

    • value - maximal allowed value
      :warning: - does not limit defaults

Creating handlers

Normally, you can write handlers right in the command() function, however there might be cases where you'd want to define your handlers separately.
For such cases, you'd want to infer type of options that will be passes inside your handler.
You can do it using TypeOf type:

import { string, boolean, type TypeOf } from '@drizzle-team/brocli'

const commandOptions = {
    opt1: string(),
    opt2: boolean('flag').alias('f'),
    // And so on... 
}

export const commandHandler = (options: TypeOf<typeof commandOptions>) => {
    // Your logic goes here...
}

Or by using handler(options, myHandler () => {...})

import { string, boolean, handler } from '@drizzle-team/brocli'

const commandOptions = {
    opt1: string(),
    opt2: boolean('flag').alias('f'),
    // And so on... 
}

export const commandHandler = handler(commandOptions, (options) => {
    // Your logic goes here...
});

Defining commands

To define commands, use command() function:

import { command, type Command, string, boolean, type TypeOf } from '@drizzle-team/brocli'

const commandOptions = {
    opt1: string(),
    opt2: boolean('flag').alias('f'),
    // And so on... 
}

const commands: Command[] = []

commands.push(command({
    name: 'command', 
    aliases: ['c', 'cmd'],
    desc: 'Description goes here',
    shortDesc: 'Short description'
    hidden: false,
    options: commandOptions,
    transform: (options) => {
        // Preprocess options here...
        return processedOptions
    },
    handler: (processedOptions) => {
        // Your logic goes here...
    },
    help: () => 'This command works like this: ...',
    subcommands: [
        command(
            // You can define subcommands like this
        )
    ]
}));

Parameters:

  • name - name by which command is searched in cli args
    :warning: - must not start with - character, be equal to [true, false, 0, 1] (case-insensitive) and be unique per command collection

  • aliases - aliases by which command is searched in cli args
    :warning: - must not start with - character, be equal to [true, false, 0, 1] (case-insensitive) and be unique per command collection

  • desc - description for command to be displayed in help command

  • shortDesc - short description for command to be displayed in help command

  • hidden - sets command as hidden - if true, command will be omitted from being displayed in help command

  • options - object containing command options created using string and boolean functions

  • transform - optional function to preprocess options before they are passed to handler
    :warning: - type of return mutates type of handler's input

  • handler - function, which will be executed in case of successful option parse
    :warning: - must be present if your command doesn't have subcommands
    If command has subcommands but no handler, help for this command is going to be called instead of handler

  • help - function or string, which will be executed or printed when help is called for this command
    :warning: - if passed, takes prevalence over theme's commandHelp event

  • subcommands - subcommands for command
    :warning: - command can't have subcommands and positional options at the same time

  • metadata - any data that you want to attach to command to later use in docs generation step

Running commands

After defining commands, you're going to need to execute run function to start command execution

import { command, type Command, run, string, boolean, type TypeOf } from '@drizzle-team/brocli'

const commandOptions = {
    opt1: string(),
    opt2: boolean('flag').alias('f'),
    // And so on... 
}

const commandHandler = (options: TypeOf<typeof commandOptions>) => {
    // Your logic goes here...
}

const commands: Command[] = []

commands.push(command({
    name: 'command', 
    aliases: ['c', 'cmd'],
    desc: 'Description goes here',
    hidden: false,
    options: commandOptions,
    handler: commandHandler,
}));

// And so on...

run(commands, {
    name: 'mysoft',
    description: 'MySoft CLI',
    omitKeysOfUndefinedOptions: true,
    argSource: customEnvironmentArgvStorage,
    version: '1.0.0',
    help: () => {
        console.log('Command list:');
        commands.forEach(c => console.log('This command does ... and has options ...'));
    },
    theme: async (event) => {
        if (event.type === 'commandHelp') {
            await myCustomUniversalCommandHelp(event.command);

            return true;
        }

        if (event.type === 'unknownError') {
            console.log('Something went wrong...');

            return true;
        }

        return false;
    },
    globals: {
        flag: boolean('gflag').description('Global flag').default(false)
    },    
    hook: (event, command, globals) => {
        if(event === 'before') console.log(`Command '${command.name}' started with flag ${globals.flag}`)
        if(event === 'after') console.log(`Command '${command.name}' succesfully finished it's work with flag ${globals.flag}`)
    }
})

Parameters:

  • name - name that's used to invoke your application from cli.
    Used for themes that print usage examples, example:
    app do-task --help results in Usage: app do-task <positional> [flags] ...
    Default: undefined

  • description - description of your app
    Used for themes, example:
    myapp --help results in

MyApp CLI

Usage: myapp [command]...

Default: undefined

  • omitKeysOfUndefinedOptions - flag that determines whether undefined options will be passed to transform\handler or not
    Default: false

  • argSource - location of array of args in your environment
    :warning: - first two items of this storage will be ignored as they typically contain executable and executed file paths
    Default: process.argv

  • version - string or handler used to print your app version
    :warning: - if passed, takes prevalence over theme's version event

  • help - string or handler used to print your app's global help
    :warning: - if passed, takes prevalence over theme's globalHelp event

  • theme(event: BroCliEvent) - function that's used to customize messages that are printed on various events
    Return:
    true | Promise<true> if you consider event processed
    false | Promise<false> to redirect event to default theme

  • globals - global options that could be processed in hook
    :warning: - positionals are not allowed in globals
    :warning: - names and aliases must not overlap with options of commands

  • hook(event: EventType, command: Command, options: TypeOf<typeof globals>) - function that's used to execute code before and after every command's transform and handler execution

Additional functions

  • commandsInfo(commands: Command[]) - get simplified representation of your command collection
    Can be used to generate docs

  • test(command: Command, args: string) - test behaviour for command with specified arguments
    :warning: - if command has transform, it will get called, however handler won't

  • getCommandNameWithParents(command: Command) - get subcommand's name with parent command names

CLI

In BroCLI, command doesn't have to be the first argument, instead it may be passed in any order.
To make this possible, hovewer, option that's passed right before command should have an explicit value, even if it is a flag: --verbose true <command-name> (does not apply to reserved flags: [ --help | -h | --version | -v])
Options are parsed in strict mode, meaning that having any unrecognized options will result in an error.

版本与发布记录

@drizzle-team/brocli 共发布 32 个版本,首次发布于 2024/06/14;过去 12 个月发布了 2 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.12.1latest2026/09/052 个月前
0.12.02026/04/077 个月前
0.11.02024/11/052 年前
0.10.22024/10/282 年前
0.10.12024/08/142 年前
0.10.02024/08/082 年前

关于 @drizzle-team/brocli 的常见问题

@drizzle-team/brocli 是什么?

Modern type-safe way of building CLIs

如何安装 @drizzle-team/brocli?

在项目目录运行 npm install @drizzle-team/brocli,或使用 pnpm add / yarn add / bun add。

@drizzle-team/brocli 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@drizzle-team/brocli 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@drizzle-team/brocli 可以商用吗?

@drizzle-team/brocli 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@drizzle-team/brocli 的下载量有多少?

根据 npm 官方下载统计,@drizzle-team/brocli 在 2026/10/13 – 2026/11/11 这 30 天内被下载 131,838,416 次,日均约 4,394,614 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@drizzle-team/brocli 还在维护吗?

最新版本 0.12.1 发布于 2026/09/05(2 个月前);至今共发布 32 个版本,其中过去 12 个月发布了 2 个;首次发布于 2024/06/14。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。