dart-cli-creator
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseDart CLI Creator Skill
Dart CLI Creator Skill
このスキルは、Dartを用いて最高品質のCommand-Line Interface (CLI) ツールを作成・改善するためのガイドラインと手順です。
あなた(エージェント)は、ユーザーからDart CLIの作成・改修を依頼された際、常にこのベストプラクティスに従って作業を進めてください。
本技能是使用Dart打造最高质量的命令行界面(CLI)工具,并对其进行开发优化的指南和操作步骤。
当你(Agent)收到用户提出的Dart CLI开发或修改需求时,请始终遵循本最佳实践开展工作。
🎯 コア・ベストプラクティス
🎯 核心最佳实践
1. プロジェクト構造・設計
1. 项目结构与设计
- ディレクトリ: CLIのエントリポイントとなる実行ファイル(例:
bin/)を配置します。ここには複雑なロジックは書かず、bin/my_cli.dartまたはlib/のトップレベル関数やクラスを呼び出すだけの薄いラッパーにします。src/ - ディレクトリ: ビジネスロジック、コマンドの実装、再利用可能なユーティリティを配置します(例:
lib/、lib/src/commands/)。lib/src/utils/ - 責務の分離: CLIの入出力部分(引数のパースやUI描画)とコアなビジネスロジックは必ず分離し、テスタビリティを高めます。
- 目录: 存放作为CLI入口点的可执行文件(例如:
bin/)。不要在此处编写复杂逻辑,仅将其作为调用bin/my_cli.dart或lib/顶层函数、类的薄封装层即可。src/ - 目录: 存放业务逻辑、命令实现、可复用工具类(例如:
lib/、lib/src/commands/)。lib/src/utils/ - 职责分离: 务必将CLI的输入输出部分(参数解析、UI渲染)与核心业务逻辑分离,提升可测试性。
2. コマンドライン引数とコマンドルーティング
2. 命令行参数与命令路由
- パッケージを活用: 標準の
argsパッケージにあるargsとCommandRunnerクラスを使用して、Gitライクなサブコマンド対応のCLIを構築します。Command - 自己文書化: 各コマンドやフラグ・オプションにはや
descriptionを丁寧に記述し、helpで十分な使い方が表示されるようにします。--help
- 活用包: 使用官方
args包提供的args和CommandRunner类,构建支持Git风格子命令的CLI。Command - 自文档化: 为每个命令、flag、选项详细编写和
description内容,确保使用help命令时可以展示完整的使用说明。--help
3. リッチなUX (ユーザー体験) とロギング
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
- 积极使用: 避免使用标准
mason_logger方法,改用print包。mason_logger- 使用、
logger.info()、logger.success()、logger.err()实现彩色输出。logger.warn() - 针对耗时处理,使用展示加载动画。
logger.progress('Doing something...') - 针对用户确认(y/n)、文字输入场景,活用和
logger.confirm()。如果需要更高级的菜单选择、加载动画等能力,可以搭配使用logger.prompt()包。interact - 提示: 推荐实用配置:在中顶层定义
lib/logger.dart,整个项目直接导入使用即可,可大幅减少样板代码,非常适合不需要强制依赖注入的中小规模CLI工具。final logger = Logger();
- 使用
- : 如需提供shell补全功能,可以考虑使用
cli_completion包,继承cli_completion进行实现。CompletionCommandRunner
4. 正しいエラーハンドリングと終了の手法
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)
- 退出码(Exit Code): 处理结果务必对应合适的退出码(成功时返回,错误时返回
0或符合1规范的错误码等)。sysexits - 避免直接调用: 直接调用
exit()会导致Dart VM立即强制终止,可能无法执行资源清理逻辑(如exit()代码块等)。应该将finally类型的返回值作为int的返回值,向上传递到CommandRunner.run()函数,设置到main属性中,等待exitCode等操作完成后自然退出。Process.stdout.close() - 自定义异常类的定义与处理: 定义业务域专属的异常类(例如),在深层逻辑中抛出。
AppException - 全局异常捕获: 使用包裹
try-catch的全部执行逻辑,处理CommandRunner、AppException以及未捕获的异常。不要直接向用户展示堆栈跟踪,而是通过UsageException友好输出错误信息,返回合适的退出码。logger.err(e.message)
5. 静的解析とコードフォーマット
5. 静态分析与代码格式化
- の適用:
pedantic_monoにanalysis_options.yamlを設定し、厳格な静的解析ルールに従います。pedantic_mono - コード変更後は必ずエラー・警告をゼロにします。
- 应用: 在
pedantic_mono中配置analysis_options.yaml,遵循严格的静态分析规则。pedantic_mono - 代码修改后务必消除所有错误和警告。
6. 配布・実行のための設定
6. 分发与运行配置
- Shebang: 直下の実行ファイルの1行目には、必ず
bin/を記述します。#!/usr/bin/env dart - Pubspecの設定: の
pubspec.yamlセクションでCLIコマンド名を定義し、executables:で簡単にインストールできるようにします。dart pub global activate
- Shebang: 目录下的可执行文件首行必须编写
bin/。#!/usr/bin/env dart - Pubspec配置: 在的
pubspec.yaml段定义CLI命令名,方便用户通过executables:一键安装。dart pub global activate
7. アップデート通知機能 (Auto-update check)
7. 更新通知功能(自动更新检查)
- を活用: コマンド実行の都度または定期的に
pub_updaterを用いて、pub_updater等に新しいバージョンがリリースされていないかチェックします。pub.dev - 新バージョンがある場合は、や、
logger.warn('Update available!')ベースのプロンプトで更新を促すインタラクティブなUIを提供します。interact
- 活用: 每次执行命令或定期使用
pub_updater检查pub_updater等平台是否有新版本发布。pub.dev - 如果存在新版本,可以通过提示,或者提供基于
logger.warn('Update available!')的交互式弹窗引导用户更新。interact
🛠️ プロジェクトのスケルトン(基本実装例)
🛠️ 项目骨架(基础实现示例)
CLIを作成する際は、以下のような構造と実装をベースにします。
创建CLI时,可以参考以下结构和实现作为基础。
pubspec.yaml
の一部
pubspec.yamlpubspec.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:yaml
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.dartbin/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(),
]);
}dart
#!/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.dartlib/logger.dart
lib/logger.dartdart
import 'package:mason_logger/mason_logger.dart';
/// アプリケーション共通の [Logger]。
final logger = Logger();dart
import 'package:mason_logger/mason_logger.dart';
/// 应用全局通用[Logger]。
final logger = Logger();lib/command_runner.dart
lib/command_runner.dartlib/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;
}dart
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', // 实际使用时读取package版本
);
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.dartlib/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
}
}dart
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
}
}🤖 エージェントへの指示
🤖 给Agent的指示
ユーザーから「〇〇をするツールのCLIを作って」等と依頼された場合:
- このスキルの内容に沿って、と
argsを軸としたプロジェクトを構成してください。mason_logger - ディレクトリ構成を決定し、要件定義に基づくコマンド群を整理・提案してください。
- を導入し、厳格な静的解析を前提とした高品質なコードを生成してください。
pedantic_mono - (必要なら) 開発完了後に、にCLIの使い方( Usage )を記載し、
README.mdによるインストール手順などをユーザーに提示してください。dart pub global activate --source path .
当用户提出「帮我做一个实现XX功能的CLI工具」等需求时:
- 遵循本技能的内容,搭建以和
args为核心的项目结构。mason_logger - 确定目录结构,基于需求整理并建议对应的命令集合。
- 引入,生成符合严格静态分析要求的高质量代码。 4.(必要时)开发完成后,在
pedantic_mono中编写CLI的使用说明(Usage),向用户提供README.md等安装步骤指引。dart pub global activate --source path .