CLI Forge vs. cac
cac is a zero-dependency, single-file CLI library used by Vite and Vitest.
CLI Forge strengths
- Type inference — Infers types from option definitions. cac returns
{ [k: string]: any }for parsed options, requiring manual type assertions. - Rich option types — Native
number,boolean,array, andobjecttypes with compile-time safety. cac infers types from bracket syntax at runtime only. - Validation — Choices, conflicts, implications (example), required options, and custom validators. cac validates only required positional arguments and unknown options.
- Middleware — Full middleware pipeline (example).
- Config files — Built-in loading with
extends(example). - Documentation generation — Built-in.
- Interactive shell — Opt-in REPL (example).
- Test harness —
TestHarnessfor parsing tests (example).
cac strengths
- Zero dependencies, single file — Extremely lightweight (~3.5KB minified).
- Dot-notation —
--env.API_SECRET fooautomatically creates nested objects. CLI Forge uses explicittype: 'object'withproperties. - Deno support — Available via JSR.
- Event system — Listen for command events (
command:*) for custom routing logic. - Proven at scale — Powers Vite, Vitest, and other popular tools.
Side-by-side example
The same CLI — a greet command with hello and goodbye subcommands — implemented in both libraries.
import cac from 'cac';
const cli = cac('greet');
cli
.command('hello', 'Say hello to someone')
.option('--name <name>', 'Name to greet', { default: 'World' })
.option('--uppercase', 'Print greeting in uppercase', { default: false })
.action((opts) => {
const msg = `Hello, ${opts.name}!`;
console.log(opts.uppercase ? msg.toUpperCase() : msg);
});
cli
.command('goodbye', 'Say goodbye to someone')
.option('--name <name>', 'Name to bid farewell', { default: 'World' })
.option('--formal', 'Use formal farewell', { default: false })
.action((opts) => {
console.log(
opts.formal ? `Farewell, ${opts.name}.` : `Bye, ${opts.name}!`
);
});
cli.help();
cli.parse();
import { cli } from 'cli-forge';
cli('greet')
.command('hello', {
description: 'Say hello to someone',
builder: (args) =>
args
.option('name', {
type: 'string',
description: 'Name to greet',
default: 'World',
})
.option('uppercase', {
type: 'boolean',
description: 'Print greeting in uppercase',
default: false,
}),
handler: (args) => {
const msg = `Hello, ${args.name}!`;
console.log(args.uppercase ? msg.toUpperCase() : msg);
},
})
.command('goodbye', {
description: 'Say goodbye to someone',
builder: (args) =>
args
.option('name', {
type: 'string',
description: 'Name to bid farewell',
default: 'World',
})
.option('formal', {
type: 'boolean',
description: 'Use formal farewell',
default: false,
}),
handler: (args) => {
console.log(
args.formal ? `Farewell, ${args.name}.` : `Bye, ${args.name}!`
);
},
})
.forge();