dart-build-cli-app
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseBuilding Dart CLI Applications
构建Dart CLI应用程序
Contents
目录
Project Setup & Architecture
项目设置与架构
Initialize new CLI projects using the official Dart template to ensure standard directory structures.
- Run to scaffold a console application with basic argument parsing.
dart create -t cli <project_name> - Place executable entry points (files containing ) exclusively in the
main()directory.bin/ - Place internal implementation logic in and expose public APIs via
lib/src/.lib/<project_name>.dart - Enforce formatting in CI environments by running . This returns exit code 1 if formatting violations exist.
dart format . --set-exit-if-changed
使用官方Dart模板初始化新的CLI项目,确保目录结构标准化。
- 运行 来搭建一个带有基础参数解析功能的控制台应用。
dart create -t cli <project_name> - 将可执行入口点(包含的文件)仅放在
main()目录下。bin/ - 将内部实现逻辑放在中,并通过
lib/src/暴露公共API。lib/<project_name>.dart - 在CI环境中通过运行强制代码格式化。如果存在格式违规,该命令会返回退出码1。
dart format . --set-exit-if-changed
Argument Parsing & Command Routing
参数解析与命令路由
Import the package to manage command-line arguments, flags, and subcommands.
args- If building a simple script: Use directly to define flags (
ArgParser) and options (addFlag).addOption - If building a complex, multi-command CLI (like ): Implement
gitand extendCommandRunnerfor each subcommand.Command - Define global arguments on the and command-specific arguments on the individual
CommandRunner.argParser.Command.argParser - Catch to gracefully handle invalid arguments and display the automatically generated help text.
UsageException - Validate Help Text Accuracy: Ensure the help text provides all necessary information to run the tool. If the help text references a compiled executable name, and the user needs to add it to their PATH to run it that way, provide clear instructions on how to do so in the help text or description.
导入包来管理命令行参数、标志和子命令。
args- 如果构建简单脚本:直接使用定义标志(
ArgParser)和选项(addFlag)。addOption - 如果构建复杂的多命令CLI(如):实现
git并为每个子命令扩展CommandRunner类。Command - 在上定义全局参数,在各个
CommandRunner.argParser上定义命令专属参数。Command.argParser - 捕获以优雅处理无效参数,并显示自动生成的帮助文本。
UsageException - 验证帮助文本准确性:确保帮助文本提供运行工具所需的所有信息。如果帮助文本引用了编译后的可执行文件名,且用户需要将其添加到PATH才能按该方式运行,请在帮助文本或描述中提供清晰的操作说明。
Execution & Error Handling
执行与错误处理
Leverage the and packages to build robust, production-ready CLI tools.
iostack_trace- Use the package's
ioenum to return standard POSIX exit codes (e.g.,ExitCode,ExitCode.success.code).ExitCode.usage.code - Use from the
sharedStdInpackage if multiple asynchronous listeners need sequential access to standard input.io - Wrap the application execution in from the
Chain.capture()package to track asynchronous stack chains.stack_trace - Format output stack traces using or
Trace.terseto strip noisy core library frames and present readable errors to the user.Chain.terse - Do not swallow exceptions in lower-level logic or storage classes unless recovery is possible. Let them bubble up or rethrow them so higher-level commands know operations failed.
- Fail fast and with non-zero exit codes: Ensure operation failures result in descriptive error messages to and appropriate non-zero exit codes (e.g., using
stderror triggering a 64 exit code after a caughtexit(1)).UsageException
利用和包构建健壮的生产级CLI工具。
iostack_trace- 使用包的
io枚举返回标准POSIX退出码(例如ExitCode、ExitCode.success.code)。ExitCode.usage.code - 如果多个异步监听器需要顺序访问标准输入,请使用包中的
io。sharedStdIn - 使用包的
stack_trace包裹应用执行逻辑,以跟踪异步调用栈链。Chain.capture() - 使用或
Trace.terse格式化输出栈跟踪,去除冗余的核心库帧,为用户呈现易读的错误信息。Chain.terse - 不要在底层逻辑或存储类中吞掉异常,除非可以恢复。让异常冒泡或重新抛出,以便上层命令知晓操作失败。
- 快速失败并返回非零退出码:确保操作失败时向输出描述性错误信息,并返回合适的非零退出码(例如使用
stderr,或捕获exit(1)后触发64号退出码)。UsageException
Testing CLI Applications
CLI应用程序测试
[!IMPORTANT] All new commands and significant features must be covered by automated tests. Manual verification is not sufficient for testing logic. However, manual verification of help text and user experience (UX) is still required to ensure the interface is intuitive and correct.
Use and to write high-fidelity integration tests for your CLI.
test_processtest_descriptor- Define expected filesystem states using (
test_descriptor,d.dir).d.file - Create the mock filesystem before execution using .
await d.Descriptor.create() - Spawn the CLI process using .
TestProcess.start('dart', ['run', 'bin/cli.dart', ...args]) - Validate standard output and error streams using matchers (e.g.,
StreamQueue,emitsThrough).emits - Assert the final exit code using .
await process.shouldExit(0) - Validate resulting filesystem mutations using .
await d.Descriptor.validate()
[!IMPORTANT] 所有新命令和重要功能必须覆盖自动化测试。 手动验证不足以测试逻辑,但仍需手动验证帮助文本和用户体验(UX),确保界面直观且正确。
使用和为CLI编写高保真的集成测试。
test_processtest_descriptor- 使用(
test_descriptor、d.dir)定义预期的文件系统状态。d.file - 在执行前使用创建模拟文件系统。
await d.Descriptor.create() - 使用启动CLI进程。
TestProcess.start('dart', ['run', 'bin/cli.dart', ...args]) - 使用匹配器(例如
StreamQueue、emitsThrough)验证标准输出和错误流。emits - 使用断言最终退出码。
await process.shouldExit(0) - 使用验证文件系统的最终变更。
await d.Descriptor.validate()
Compilation & Distribution
编译与分发
Select the appropriate compilation target based on your distribution requirements.
- If testing locally during development: Use . This uses the JIT compiler for rapid iteration.
dart run bin/cli.dart - If bundling code assets and dynamic libraries: Use . This runs build hooks and outputs to
dart build cli.build/cli/_/bundle/ - If distributing a standalone native executable: Use . This bundles the Dart runtime and machine code into a single file.
dart compile exe bin/cli.dart -o <output_path> - If distributing multiple apps with strict disk space limits: Use . Run the resulting
dart compile aot-snapshot bin/cli.dartfile using.aot.dartaotruntime
Dart supports cross-compiling to Linux from macOS, Windows, or Linux hosts.
Use the and flags with or .
--target-os--target-archdart compile exedart compile aot-snapshot- (Only Linux is currently supported as a cross-compilation target)
--target-os=linux - (64-bit ARM)
--target-arch=arm64 - (x86-64)
--target-arch=x64 - (32-bit ARM)
--target-arch=arm - (64-bit RISC-V)
--target-arch=riscv64
Example:
</details>dart compile exe --target-os=linux --target-arch=arm64 bin/cli.dart根据分发需求选择合适的编译目标。
- 开发期间本地测试:使用。该方式使用JIT编译器实现快速迭代。
dart run bin/cli.dart - 打包代码资源和动态库:使用。该命令会运行构建钩子并输出到
dart build cli目录。build/cli/_/bundle/ - 分发独立原生可执行文件:使用。该命令会将Dart运行时和机器代码打包成单个文件。
dart compile exe bin/cli.dart -o <output_path> - 分发多个应用且严格限制磁盘空间:使用。使用
dart compile aot-snapshot bin/cli.dart运行生成的dartaotruntime文件。.aot
Dart支持从macOS、Windows或Linux主机交叉编译到Linux。
在或中使用和标志。
dart compile exedart compile aot-snapshot--target-os--target-arch- (目前仅支持Linux作为交叉编译目标)
--target-os=linux - (64位ARM)
--target-arch=arm64 - (x86-64)
--target-arch=x64 - (32位ARM)
--target-arch=arm - (64位RISC-V)
--target-arch=riscv64
示例:
</details>dart compile exe --target-os=linux --target-arch=arm64 bin/cli.dartWorkflows
工作流程
Task Progress: Implement a New CLI Command
任务进度:实现新CLI命令
- Create a new class extending in
Command.lib/src/commands/ - Define the and
nameproperties.description - Register command-specific flags in the constructor using or
argParser.addFlag().argParser.addOption() - Implement the method with the core logic.
run() - Register the new command in the instance in
CommandRunnerusingbin/cli.dart.addCommand() - Create tests for the new command in the directory using
test/or standard tests.test_process - Run validator -> Execute to verify help text generation.
dart run bin/cli.dart help <command_name> - Verify final UX: Compile the application using and run the resulting executable to verify the target user experience (e.g.,
dart compile exe)../bin/cli <command>
- 在中创建一个继承自
lib/src/commands/的新类。Command - 定义和
name属性。description - 在构造函数中使用或
argParser.addFlag()注册命令专属标志。argParser.addOption() - 实现包含核心逻辑的方法。
run() - 在的
bin/cli.dart实例中使用CommandRunner注册新命令。addCommand() - 在目录中使用
test/或标准测试为新命令创建测试。test_process - 运行验证:执行验证帮助文本生成是否正确。
dart run bin/cli.dart help <command_name> - 验证最终UX:使用编译应用,运行生成的可执行文件验证目标用户体验(例如
dart compile exe)。./bin/cli <command>
Task Progress: Compile and Release Native Executable
任务进度:编译并发布原生可执行文件
- Run validator -> Execute to ensure code formatting.
dart format . --set-exit-if-changed - Run validator -> Execute to ensure no static analysis errors.
dart analyze - Run validator -> Execute to pass all integration tests.
dart test - Compile for host OS:
dart compile exe bin/cli.dart -o build/cli-host - Compile for Linux (if host is macOS/Windows):
dart compile exe --target-os=linux --target-arch=x64 bin/cli.dart -o build/cli-linux-x64
- 运行验证:执行确保代码格式正确。
dart format . --set-exit-if-changed - 运行验证:执行确保无静态分析错误。
dart analyze - 运行验证:执行确保所有集成测试通过。
dart test - 为宿主系统编译:
dart compile exe bin/cli.dart -o build/cli-host - 编译Linux版本(如果宿主是macOS/Windows):
dart compile exe --target-os=linux --target-arch=x64 bin/cli.dart -o build/cli-linux-x64
Examples
示例
Example: CommandRunner Implementation
示例:CommandRunner实现
dart
import 'dart:io';
import 'package:args/command_runner.dart';
import 'package:stack_trace/stack_trace.dart';
class CommitCommand extends Command {
final String name = 'commit';
final String description = 'Record changes to the repository.';
CommitCommand() {
argParser.addFlag('all', abbr: 'a', help: 'Commit all changed files.');
}
Future<void> run() async {
final commitAll = argResults?['all'] as bool? ?? false;
print('Committing... (All: $commitAll)');
}
}
void main(List<String> args) {
Chain.capture(() async {
final runner = CommandRunner('dgit', 'Distributed version control.')
..addCommand(CommitCommand());
await runner.run(args);
}, onError: (error, chain) {
if (error is UsageException) {
stderr.writeln(error.message);
stderr.writeln(error.usage);
exit(64); // ExitCode.usage.code
} else {
stderr.writeln('Fatal error: $error');
stderr.writeln(chain.terse);
exit(1);
}
});
}dart
import 'dart:io';
import 'package:args/command_runner.dart';
import 'package:stack_trace/stack_trace.dart';
class CommitCommand extends Command {
final String name = 'commit';
final String description = 'Record changes to the repository.';
CommitCommand() {
argParser.addFlag('all', abbr: 'a', help: 'Commit all changed files.');
}
Future<void> run() async {
final commitAll = argResults?['all'] as bool? ?? false;
print('Committing... (All: $commitAll)');
}
}
void main(List<String> args) {
Chain.capture(() async {
final runner = CommandRunner('dgit', 'Distributed version control.')
..addCommand(CommitCommand());
await runner.run(args);
}, onError: (error, chain) {
if (error is UsageException) {
stderr.writeln(error.message);
stderr.writeln(error.usage);
exit(64); // ExitCode.usage.code
} else {
stderr.writeln('Fatal error: $error');
stderr.writeln(chain.terse);
exit(1);
}
});
}Example: Integration Testing with Subprocesses
示例:使用子进程进行集成测试
dart
import 'package:test/test.dart';
import 'package:test_process/test_process.dart';
import 'package:test_descriptor/test_descriptor.dart' as d;
void main() {
test('CLI formats output correctly and modifies filesystem', () async {
// 1. Setup mock filesystem
await d.dir('project', [
d.file('config.json', '{"key": "value"}')
]).create();
// 2. Spawn the CLI process
final process = await TestProcess.start(
'dart',
['run', 'bin/cli.dart', 'process', '--path', '${d.sandbox}/project']
);
// 3. Validate stdout stream
await expectLater(process.stdout, emitsThrough('Processing complete.'));
// 4. Validate exit code
await process.shouldExit(0);
// 5. Validate filesystem mutations
await d.dir('project', [
d.file('config.json', '{"key": "value"}'),
d.file('output.log', 'Success')
]).validate();
});
}dart
import 'package:test/test.dart';
import 'package:test_process/test_process.dart';
import 'package:test_descriptor/test_descriptor.dart' as d;
void main() {
test('CLI formats output correctly and modifies filesystem', () async {
// 1. 设置模拟文件系统
await d.dir('project', [
d.file('config.json', '{"key": "value"}')
]).create();
// 2. 启动CLI进程
final process = await TestProcess.start(
'dart',
['run', 'bin/cli.dart', 'process', '--path', '${d.sandbox}/project']
);
// 3. 验证stdout流
await expectLater(process.stdout, emitsThrough('Processing complete.'));
// 4. 验证退出码
await process.shouldExit(0);
// 5. 验证文件系统变更
await d.dir('project', [
d.file('config.json', '{"key": "value"}'),
d.file('output.log', 'Success')
]).validate();
});
}