cac の使い方

Simple yet powerful framework for building command-line apps.

v7.0.0/週MITCLI
AI生成コンテンツ

この記事はAIによって生成されました。内容の正確性は保証されません。最新の情報は公式ドキュメントをご確認ください。

cac の使い方 — 軽量で強力なCLIアプリ構築フレームワーク

一言でいうと

cac(Command And Conquer)は、依存パッケージゼロ・単一ファイルで構成された超軽量なCLIアプリ構築ライブラリです。Vite や Vitest など著名プロジェクトでも採用されており、4つの基本APIだけでCLIツールが作れるシンプルさと、サブコマンド・バリデーション・自動ヘルプ生成などの高機能を両立しています。

どんな時に使う?

  1. 自作CLIツールの構築 — プロジェクト固有のビルドスクリプトやデプロイツールなど、コマンドライン引数のパース・サブコマンド分岐が必要な場面
  2. npm scriptsの拡張 — オプション付きのタスクランナーを手軽に作りたいとき。commander や yargs ほど大げさにしたくない場合に最適
  3. Vite/Vitest プラグインやツールチェーンの開発 — これらのエコシステムと同じCLIフレームワークを使うことで、一貫した開発体験を得たいとき

インストール

# npm
npm install cac

# yarn
yarn add cac

# pnpm
pnpm add cac

基本的な使い方

最もシンプルなパターンとして、引数パースとコマンド実行の例を示します。

import cac from 'cac'

const cli = cac('my-tool')

cli
  .command('greet <name>', 'Greet someone')
  .option('--loud', 'Greet in uppercase')
  .action((name: string, options: { loud?: boolean }) => {
    const message = `Hello, ${name}!`
    console.log(options.loud ? message.toUpperCase() : message)
  })

cli.help()
cli.version('1.0.0')

cli.parse()
# 実行例
$ my-tool greet Alice
Hello, Alice!

$ my-tool greet Alice --loud
HELLO, ALICE!

$ my-tool --help
# 自動生成されたヘルプメッセージが表示される

よく使うAPI

1. cli.option() — グローバルオプションの定義

すべてのコマンドで共通して使えるオプションを定義します。

import cac from 'cac'

const cli = cac()

cli.option('--verbose', 'Enable verbose output', { default: false })
cli.option('--config <path>', 'Path to config file', { default: './config.json' })

const parsed = cli.parse()

console.log(parsed.options)
// { verbose: false, config: './config.json', '--': [] }

<path> は値が必須、[path] は値が省略可能(省略時は true)です。

2. cli.command() — サブコマンドの定義

git のようなサブコマンド体系を構築できます。

import cac from 'cac'

const cli = cac()

// 必須引数: <dir>、オプション引数: [env]
cli
  .command('deploy <dir> [env]', 'Deploy a directory to the specified environment')
  .option('--dry-run', 'Simulate deployment without making changes')
  .action((dir: string, env: string | undefined, options: { dryRun?: boolean }) => {
    console.log(`Deploying ${dir} to ${env ?? 'production'}`)
    if (options.dryRun) {
      console.log('(dry run mode)')
    }
  })

// デフォルトコマンド(コマンド名を省略)
cli
  .command('[...files]', 'Process files')
  .action((files: string[]) => {
    console.log('Processing:', files)
  })

cli.help()
cli.parse()

3. cli.help() / cli.version() — ヘルプとバージョン表示

import cac from 'cac'

const cli = cac('my-cli')

cli.option('--type [type]', 'Choose a project type', { default: 'node' })
cli.option('--name <name>', 'Provide your name')

cli
  .command('lint [...files]', 'Lint files')
  .option('--fix', 'Auto-fix problems')
  .action((files: string[], options: { fix?: boolean }) => {
    console.log('Linting:', files, options)
  })

// -h / --help で自動生成されたヘルプメッセージを表示
cli.help()

// -v / --version でバージョンを表示(ヘルプメッセージにも使われる)
cli.version('2.1.0')

cli.parse()

4. command.action() — コマンド実行時の処理

action のコールバック引数は、コマンド定義の引数順 → 最後に options オブジェクトの順で渡されます。

import cac from 'cac'

const cli = cac()

cli
  .command('build <entry> [...otherFiles]', 'Build your app')
  .option('--minify', 'Minify output')
  .option('--out-dir <dir>', 'Output directory')
  .action((entry: string, otherFiles: string[], options: { minify?: boolean; outDir?: string }) => {
    // 第1引数: <entry> に対応
    console.log('Entry:', entry)
    // 第2引数: [...otherFiles] に対応(可変長引数は配列で渡される)
    console.log('Other files:', otherFiles)
    // 第3引数: パースされたオプション(kebab-case → camelCase に自動変換)
    console.log('Options:', options)
  })

cli.help()
cli.parse()
$ node build.js src/index.ts src/utils.ts src/types.ts --minify --out-dir dist
Entry: src/index.ts
Other files: [ 'src/utils.ts', 'src/types.ts' ]
Options: { minify: true, outDir: 'dist' }

5. command.allowUnknownOptions() / エラーハンドリング

未定義のオプションを許可したい場合や、エラーを自前でハンドリングしたい場合のパターンです。

import cac from 'cac'

const cli = cac()

cli
  .command('dev', 'Start dev server')
  .allowUnknownOptions()  // 未定義オプションでもエラーにしない
  .action((options: Record<string, unknown>) => {
    console.log('All options:', options)
  })

cli.help()

// エラーハンドリング: parse と実行を分離する
try {
  cli.parse(process.argv, { run: false })
  await cli.runMatchedCommand()
} catch (error) {
  console.error(`Error: ${(error as Error).message}`)
  process.exit(1)
}

類似パッケージとの比較

特徴caccommanderyargs
依存パッケージ数00多数
インストールサイズ~100KB~200KB~1MB
TypeScript対応ネイティブ(TSで記述)型定義同梱@types/yargs が必要
サブコマンド
自動ヘルプ生成
dot-nestedオプション
学習コスト低い(API 4つ)中程度やや高い
採用実績Vite, Vitest, VuePressExpress CLI, Vue CLIwebpack CLI, mocha

選定の目安:

  • 軽量さ・シンプルさ重視 → cac
  • 大規模CLI・豊富なプラグイン → commander
  • 対話的プロンプト・複雑なバリデーション → yargs

注意点・Tips

kebab-case は自動で camelCase に変換される

--clear-screen オプションは options.clearScreen でアクセスします。--clear-screen--clearScreen のどちらで渡しても同じプロパティにマッピングされます。

否定オプション(--no-xxx)の定義方法

--no-config のような否定オプションを使うには、明示的に定義が必要です。

cli
  .command('build', 'Build project')
  .option('--config <path>', 'Use a custom config file')
  .option('--no-config', 'Disable config file')

これにより config のデフォルト値が true になり、--no-configfalse に設定できます。

配列オプションは同じフラグを繰り返す

# 単一値
$ node cli.js --include project-a
# → { include: 'project-a' }

# 複数値(同じオプションを繰り返す)
$ node cli.js --include project-a --include project-b
# → { include: ['project-a', 'project-b'] }

import の書き方に注意

// デフォルトエクスポート
import cac from 'cac'

// 名前付きエクスポートでも可
import { cac } from 'cac'

TypeScript で使う場合は @types/node を devDependencies に追加してください。

parse() の戻り値を直接使う

コマンドを定義せず、単純な引数パーサーとしても使えます。

const cli = cac()
cli.option('--port <port>', 'Server port')
const { options, args } = cli.parse()
// options.port, args にアクセス可能

まとめ

cac は「依存ゼロ・API 4つ」という圧倒的なシンプルさでありながら、サブコマンド・可変長引数・自動ヘルプ生成など実用的な機能を備えたCLIフレームワークです。Vite や Vitest といったモダンなツールチェーンでの採用実績が信頼性を裏付けています。軽量なCLIツールを素早く構築したい場面では、まず検討すべき選択肢と言えるでしょう。