Loading...
Loading...
Dartを用いた堅牢で保守性の高いCLIツールを作成・改善するためのスキル。ベストプラクティスを網羅しています。
npx skill4agent add mono0926/skills dart-cli-creatorbin/bin/my_cli.dartlib/src/lib/lib/src/commands/lib/src/utils/argsargsCommandRunnerCommanddescriptionhelp--helpmason_loggerprintmason_loggerlogger.info()logger.success()logger.err()logger.warn()logger.progress('Doing something...')logger.confirm()logger.prompt()interactlib/logger.dartfinal logger = Logger();cli_completioncli_completionCompletionCommandRunner01sysexitsexit()exit()finallyCommandRunner.run()intmainexitCodeProcess.stdout.close()AppExceptionCommandRunnertry-catchAppExceptionUsageExceptionlogger.err(e.message)pedantic_monoanalysis_options.yamlpedantic_monobin/#!/usr/bin/env dartpubspec.yamlexecutables:dart pub global activatepub_updaterpub_updaterpub.devlogger.warn('Update available!')interactpubspec.yamldependencies:
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#!/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.dartimport 'package:mason_logger/mason_logger.dart';
/// アプリケーション共通の [Logger]。
final logger = Logger();lib/command_runner.dartimport '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.dartimport '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
}
}argsmason_loggerpedantic_monoREADME.mddart pub global activate --source path .