oclif
Heroku/Salesforceが開発したNode.js/TypeScript向けのオープンCLIフレームワーク。プラグインシステムが特徴です。
フレームワーク
oclif
概要
oclifは、Heroku/Salesforceが開発したNode.js/TypeScript向けのオープンCLIフレームワークです。プラグインシステムが特徴で、拡張可能で本格的なCLIアプリケーションの構築を可能にします。エンタープライズグレードのCLIツール開発で人気が高く、Heroku CLIやSalesforce CLIなどの大規模プロジェクトで採用されています。
詳細
oclifは「Open CLI Framework」の略称で、Salesforce(旧Heroku)によって開発されました。単なるコマンドライン引数パーサーではなく、本格的なCLIアプリケーション開発のための包括的なフレームワークです。プラグインアーキテクチャ、自動テスト生成、配布システムなど、エンタープライズレベルのCLIツールに必要な機能を統合的に提供します。TypeScriptファーストの設計により、型安全性と開発者体験の両方を重視しています。
主な特徴
- プラグインアーキテクチャ: 拡張可能なプラグインシステム
- TypeScriptサポート: 型安全性とIntelliSenseの恩恵
- 自動ヘルプ生成: 美しく構造化されたヘルプメッセージ
- テスト支援: CLIコマンドの自動テスト機能
- 配布システム: npm、tarballs、インストーラーの生成
- フック: ライフサイクルフックによる拡張性
- 設定管理: 設定ファイルとフラグの統合管理
メリット・デメリット
メリット
- エンタープライズ向け: 大規模なCLIアプリケーションに適した設計
- プラグイン対応: 柔軟な拡張システム
- 型安全性: TypeScriptによる堅牢な開発体験
- 豊富な機能: テスト、配布、設定管理まで包括的にサポート
- 実績: Salesforce、Herokuでの実績とコミュニティサポート
デメリット
- 複雑性: シンプルなCLIには機能が過剰な場合がある
- 学習コスト: フレームワーク独自の概念の理解が必要
- 依存関係: 多くの依存関係によるバンドルサイズの増加
- オーバーヘッド: 小規模なツールには重すぎる場合がある
主要リンク
書き方の例
// package.json
{
"name": "mycli",
"version": "1.0.0",
"oclif": {
"bin": "mycli",
"dirname": "mycli",
"commands": "./lib/commands",
"plugins": [
"@oclif/plugin-help",
"@oclif/plugin-plugins"
],
"topicSeparator": " "
}
}
// src/commands/hello.ts
import { Args, Command, Flags } from '@oclif/core'
export default class Hello extends Command {
static override args = {
person: Args.string({ description: '挨拶する相手の名前' }),
}
static override description = 'シンプルな挨拶コマンド'
static override examples = [
'<%= config.bin %> <%= command.id %> 太郎',
'<%= config.bin %> <%= command.id %> --name=花子',
]
static override flags = {
name: Flags.string({ char: 'n', description: '挨拶する相手の名前' }),
from: Flags.string({ char: 'f', description: '挨拶者の名前', required: true }),
uppercase: Flags.boolean({ char: 'u', description: '大文字で出力' }),
}
public async run(): Promise<void> {
const { args, flags } = await this.parse(Hello)
const name = flags.name ?? args.person ?? 'World'
let message = `こんにちは、${name}さん! ${flags.from}より`
if (flags.uppercase) {
message = message.toUpperCase()
}
this.log(message)
}
}
// src/commands/config/set.ts
import { Args, Command, Flags } from '@oclif/core'
export default class ConfigSet extends Command {
static override args = {
key: Args.string({ description: '設定キー', required: true }),
value: Args.string({ description: '設定値', required: true }),
}
static override description = '設定値を設定する'
static override flags = {
global: Flags.boolean({ char: 'g', description: 'グローバル設定に保存' }),
}
public async run(): Promise<void> {
const { args, flags } = await this.parse(ConfigSet)
// 設定保存のロジック
this.log(`設定保存: ${args.key} = ${args.value}`)
if (flags.global) {
this.log('グローバル設定に保存されました')
}
}
}
// src/commands/deploy.ts
import { Command, Flags } from '@oclif/core'
import { ux } from '@oclif/core'
export default class Deploy extends Command {
static override description = 'アプリケーションをデプロイする'
static override flags = {
environment: Flags.string({
char: 'e',
description: 'デプロイ環境',
options: ['development', 'staging', 'production'],
default: 'staging',
}),
'dry-run': Flags.boolean({ description: 'デプロイをシミュレート' }),
verbose: Flags.boolean({ char: 'v', description: '詳細ログを出力' }),
}
public async run(): Promise<void> {
const { flags } = await this.parse(Deploy)
if (flags['dry-run']) {
this.log('🔍 ドライランモード')
}
this.log(`🚀 ${flags.environment}環境にデプロイ開始`)
// プログレスバーの表示
const bar = ux.progress({
format: 'デプロイ中... [{bar}] {percentage}% | {value}/{total}',
})
bar.start(100, 0)
// デプロイ処理のシミュレーション
for (let i = 0; i <= 100; i += 10) {
bar.update(i)
await new Promise(resolve => setTimeout(resolve, 200))
}
bar.stop()
this.log('✅ デプロイが完了しました')
}
}
// src/hooks/init.ts
import { Hook } from '@oclif/core'
const hook: Hook<'init'> = async function (opts) {
// 初期化処理
this.log('CLIが初期化されました')
}
export default hook
// プラグインの例 - src/commands/plugins/install.ts
import { Args, Command } from '@oclif/core'
export default class PluginsInstall extends Command {
static override args = {
plugin: Args.string({ description: 'インストールするプラグイン名', required: true }),
}
static override description = 'プラグインをインストール'
public async run(): Promise<void> {
const { args } = await this.parse(PluginsInstall)
this.log(`プラグインをインストール中: ${args.plugin}`)
// プラグインインストールのロジック
this.log('インストール完了!')
}
}