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
参数解析与命令路由
Implement 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
使用包来管理命令行参数、标志和子命令。
args- 如果构建简单脚本:直接使用定义标志(
ArgParser)和选项(addFlag)。addOption - 如果构建复杂的多命令CLI(如):实现
git并为每个子命令扩展CommandRunner类。Command - 在上定义全局参数,在各个
CommandRunner.argParser上定义命令专属参数。Command.argParser - 捕获以优雅处理无效参数,并显示自动生成的帮助文本。
UsageException
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
利用和包构建健壮的生产级CLI工具。
iostack_trace- 使用包的
io枚举返回标准POSIX退出码(例如ExitCode、ExitCode.success.code)。ExitCode.usage.code - 如果多个异步监听器需要顺序访问标准输入,使用包中的
io。sharedStdIn - 使用包中的
stack_trace包裹应用执行过程,以跟踪异步调用栈链。Chain.capture() - 使用或
Trace.terse格式化输出栈跟踪,去除冗余的核心库帧,为用户呈现易读的错误信息。Chain.terse
Testing CLI Applications
CLI应用程序测试
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()
使用和为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() - Run validator -> Execute to verify help text generation.
dart run bin/cli.dart help <command_name>
- 在中创建一个继承自
lib/src/commands/的新类。Command - 定义和
name属性。description - 在构造函数中使用或
argParser.addFlag()注册命令专属标志。argParser.addOption() - 实现包含核心逻辑的方法。
run() - 在的
bin/cli.dart实例中使用CommandRunner注册新命令。addCommand() - 运行验证器 -> 执行以验证帮助文本生成是否正确。
dart run bin/cli.dart help <command_name>
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. 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();
});
}