dart-cli-creator
Original:🇺🇸 English
Translated
Dartを用いた堅牢で保守性の高いCLIツールを作成・改善するためのスキル。ベストプラクティスを網羅しています。
7installs
Sourcemono0926/skills
Added on
NPX Install
npx skill4agent add mono0926/skills dart-cli-creatorTags
Translated version includes tags in frontmatterSKILL.md Content
View Translation Comparison →Dart CLI Creator Skill
このスキルは、Dartを用いて最高品質のCommand-Line Interface (CLI) ツールを作成・改善するためのガイドラインと手順です。
あなた(エージェント)は、ユーザーからDart CLIの作成・改修を依頼された際、常にこのベストプラクティスに従って作業を進めてください。
🎯 コア・ベストプラクティス
1. プロジェクト構造・設計
- ディレクトリ: CLIのエントリポイントとなる実行ファイル(例:
bin/)を配置します。ここには複雑なロジックは書かず、bin/my_cli.dartまたはlib/のトップレベル関数やクラスを呼び出すだけの薄いラッパーにします。src/ - ディレクトリ: ビジネスロジック、コマンドの実装、再利用可能なユーティリティを配置します(例:
lib/、lib/src/commands/)。lib/src/utils/ - 責務の分離: CLIの入出力部分(引数のパースやUI描画)とコアなビジネスロジックは必ず分離し、テスタビリティを高めます。
2. コマンドライン引数とコマンドルーティング
- パッケージを活用: 標準の
argsパッケージにあるargsとCommandRunnerクラスを使用して、Gitライクなサブコマンド対応のCLIを構築します。Command - 自己文書化: 各コマンドやフラグ・オプションにはや
descriptionを丁寧に記述し、helpで十分な使い方が表示されるようにします。--help
3. リッチなUX (ユーザー体験) とロギング
- の積極利用: 標準の
mason_loggerは避け、printパッケージを利用します。mason_logger- ,
logger.info(),logger.success(),logger.err()で色分けされた出力。logger.warn() - 時間のかかる処理には を用いてスピナーを表示。
logger.progress('Doing something...') - ユーザーの確認(y/n)や文字入力には や
logger.confirm()を活用。また、より高度なメニュー選択やスピナー等が必要な場合はlogger.prompt()パッケージを併用します。interact - Tip: 実用的な構成として、にトップレベルで
lib/logger.dartと定義し、プロジェクト全体でimportして使うとボイラープレートが減り非常に便利です(DIを必須としない小〜中規模のCLIツールに最適)。final logger = Logger();
- : シェル補完機能を提供するために、
cli_completionパッケージの利用を検討し、cli_completionを継承して実装します。CompletionCommandRunner
4. 正しいエラーハンドリングと終了の手法
- 終了コード(Exit Code): 処理結果は必ず適切な終了コードとして扱います(成功時は 、エラー時は
0または1準拠のエラーコード等)。sysexits - 直接の は避ける:
exit()を直接呼ぶと、Dart VMが即座に強制終了し、リソースのクリーンアップ(exit()ブロックなど)が実行されない可能性があります。finallyの戻り値としてCommandRunner.run()を返し、int関数に伝播させ、mainプロパティにセットして自然終了(exitCode等の待機後)させます。Process.stdout.close() - カスタム例外クラスの定義とハンドリング: 独自ドメインの例外クラス(例: )を定義し、深い階層でスローします。
AppException - グローバルな例外捕捉: の実行全体を
CommandRunnerで囲み、try-catch,AppExceptionや未補足例外をハンドリングします。スタックトレースをユーザーに直接見せるのではなく、UsageExceptionのように丁寧にエラー出力し、適切な終了コードを返します。logger.err(e.message)
5. 静的解析とコードフォーマット
- の適用:
pedantic_monoにanalysis_options.yamlを設定し、厳格な静的解析ルールに従います。pedantic_mono - コード変更後は必ずエラー・警告をゼロにします。
6. 配布・実行のための設定
- Shebang: 直下の実行ファイルの1行目には、必ず
bin/を記述します。#!/usr/bin/env dart - Pubspecの設定: の
pubspec.yamlセクションでCLIコマンド名を定義し、executables:で簡単にインストールできるようにします。dart pub global activate
7. アップデート通知機能 (Auto-update check)
- を活用: コマンド実行の都度または定期的に
pub_updaterを用いて、pub_updater等に新しいバージョンがリリースされていないかチェックします。pub.dev - 新バージョンがある場合は、や、
logger.warn('Update available!')ベースのプロンプトで更新を促すインタラクティブなUIを提供します。interact
🛠️ プロジェクトのスケルトン(基本実装例)
CLIを作成する際は、以下のような構造と実装をベースにします。
pubspec.yaml
の一部
pubspec.yamlyaml
dependencies:
args: ^2.5.0
cli_completion: ^0.4.0
mason_logger: ^0.2.16
pub_updater: ^0.4.0
dev_dependencies:
pedantic_mono: any
test: ^1.24.0
executables:
my_cli:bin/my_cli.dart
bin/my_cli.dartdart
#!/usr/bin/env dart
import 'dart:io';
import 'package:my_cli/command_runner.dart';
Future<void> main(List<String> arguments) async {
final exitCode = await MyCliCommandRunner().run(arguments);
await flushThenExit(exitCode ?? 0);
}
/// [status]をexitCodeに設定し、標準出力/標準エラーのフラッシュを待って終了するヘルパー
Future<void> flushThenExit(int status) async {
exitCode = status;
await Future.wait<void>([
stdout.close(),
stderr.close(),
]);
}lib/logger.dart
lib/logger.dartdart
import 'package:mason_logger/mason_logger.dart';
/// アプリケーション共通の [Logger]。
final logger = Logger();lib/command_runner.dart
lib/command_runner.dartdart
import 'package:args/args.dart';
import 'package:cli_completion/cli_completion.dart';
import 'package:mason_logger/mason_logger.dart';
import 'package:pub_updater/pub_updater.dart';
import 'package:my_cli/logger.dart';
class MyCliCommandRunner extends CompletionCommandRunner<int> {
final PubUpdater _pubUpdater;
MyCliCommandRunner({PubUpdater? pubUpdater})
: _pubUpdater = pubUpdater ?? PubUpdater(),
super('my_cli', 'A highly robust Dart CLI tool.') {
argParser
..addFlag(
'version',
abbr: 'v',
negatable: false,
help: 'Print the current version.',
)
..addFlag(
'verbose',
help: 'Enable verbose logging.',
);
// TODO: addCommand(MyCustomCommand());
}
Future<void> _checkForUpdates() async {
try {
final isUpToDate = await _pubUpdater.isUpToDate(
packageName: 'my_cli',
currentVersion: '1.0.0', // 本来は packageVersion
);
if (!isUpToDate) {
final latestVersion = await _pubUpdater.getLatestVersion('my_cli');
logger.info('\nUpdate available: $latestVersion');
}
} catch (_) {}
}
Future<int> run(Iterable<String> args) async {
try {
final argResults = parse(args);
if (argResults['verbose'] == true) {
logger.level = Level.verbose;
}
// バージョン更新チェック (非同期でバックグラウンド実行も検討)
await _checkForUpdates();
return await runCommand(argResults) ?? 0;
} on FormatException catch (e) {
logger
..err(e.message)
..info('')
..info(usage);
return 64; // usage error
} on UsageException catch (e) {
logger
..err(e.message)
..info('')
..info(usage);
return 64;
} on AppException catch (e) {
// カスタム例外はきれいにエラー出力
logger.err(e.message);
return 1;
} catch (e, stackTrace) {
logger
..err('An unexpected error occurred: $e')
..err('$stackTrace');
return 1;
}
}
Future<int?> runCommand(ArgResults topLevelResults) async {
if (topLevelResults['version'] == true) {
logger.info('my_cli version: 1.0.0');
return 0;
}
return super.runCommand(topLevelResults);
}
}
/// ドメイン固有のカスタム例外
class AppException implements Exception {
const AppException(this.message);
final String message;
String toString() => message;
}lib/src/commands/my_custom_command.dart
lib/src/commands/my_custom_command.dartdart
import 'package:args/command_runner.dart';
import 'package:my_cli/logger.dart';
class MyCustomCommand extends Command<int> {
MyCustomCommand() {
argParser.addOption(
'name',
abbr: 'n',
help: 'Your name.',
mandatory: true,
);
}
String get description => 'A custom command example.';
String get name => 'hello';
Future<int> run() async {
final name = argResults?['name'] as String?;
final progress = logger.progress('Saying hello to \$name...');
// 時間のかかる処理のシミュレート
await Future<void>.delayed(const Duration(seconds: 1));
progress.complete('Hello, \$name!');
return 0; // Success
}
}🤖 エージェントへの指示
ユーザーから「〇〇をするツールのCLIを作って」等と依頼された場合:
- このスキルの内容に沿って、と
argsを軸としたプロジェクトを構成してください。mason_logger - ディレクトリ構成を決定し、要件定義に基づくコマンド群を整理・提案してください。
- を導入し、厳格な静的解析を前提とした高品質なコードを生成してください。
pedantic_mono - (必要なら) 開発完了後に、にCLIの使い方( Usage )を記載し、
README.mdによるインストール手順などをユーザーに提示してください。dart pub global activate --source path .