mcpField
D
dart-build-cli-app

dart-build-cli-app

github.com/dhruvanbhalara开发3059 次安装9 周前更新
代码仓库

关于 dart-build-cli-app

为 Dart 开发者提供构建命令行应用的实践指南,涵盖项目结构、命令解析、测试和发布。内容包含从子命令设计到原生可执行文件编译的工作流。

主要功能

  1. 01Dart CLI 项目结构
  2. 02参数与子命令路由
  3. 03错误处理与退出码
  4. 04子进程集成测试
  5. 05编译和分发流程

适用场景

  1. 01搭建新的 Dart 命令行项目
  2. 02添加带参数的 CLI 子命令
  3. 03测试命令输出和文件变更
  4. 04编译独立原生可执行文件

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/dhruvanbhalara/skills --skill dart-build-cli-app

安装后在 Claude Code 中输入/dart-build-cli-app确认已加载。

详细介绍

构建 Dart 命令行应用

目录

项目设置与架构

使用官方 Dart 模板初始化新的命令行项目,以确保采用标准目录结构。

  • 运行 dart create -t cli <project_name>,搭建包含基本参数解析功能的控制台应用。
  • 仅将可执行入口(包含 main() 的文件)放在 bin/ 目录中。
  • 将内部实现逻辑放在 lib/src/ 中,并通过 lib/<project_name>.dart 暴露公共 API。
  • 在 CI 环境中运行 dart format . --set-exit-if-changed 强制执行格式规范。如果存在格式问题,该命令会返回退出代码 1。

参数解析与命令路由

使用 args 包管理命令行参数、标志和子命令。

  • 如果要构建简单脚本:直接使用 ArgParser 定义标志(addFlag)和选项(addOption)。
  • 如果要构建复杂的多命令 CLI(如 git):实现 CommandRunner,并为每个子命令扩展 Command。
  • 在 CommandRunner.argParser 上定义全局参数,在各个 Command.argParser 上定义命令专属参数。
  • 捕获 UsageException,以妥善处理无效参数并显示自动生成的帮助文本。

执行与错误处理

使用 io 和 stack_trace 包构建可靠、可用于生产环境的命令行工具。

  • 使用 io 包的 ExitCode 枚举返回标准 POSIX 退出代码(例如,ExitCode.success.code、ExitCode.usage.code)。
  • 如果多个异步监听器需要按顺序访问标准输入,请使用 io 包中的 sharedStdIn。
  • 使用 stack_trace 包中的 Chain.capture() 包装应用执行过程,以跟踪异步堆栈链。
  • 使用 Trace.terse 或 Chain.terse 格式化输出堆栈跟踪,剔除嘈杂的核心库堆栈帧,并向用户呈现易读的错误信息。

测试命令行应用

使用 test_process 和 test_descriptor 为命令行工具编写高保真的集成测试。

  • 使用 test_descriptor(d.dir、d.file)定义预期的文件系统状态。
  • 执行前使用 await d.Descriptor.create() 创建模拟文件系统。
  • 使用 TestProcess.start('dart', ['run', 'bin/cli.dart', ...args]) 启动命令行进程。
  • 使用 StreamQueue 匹配器(例如,emitsThrough、emits)验证标准输出和错误流。
  • 使用 await process.shouldExit(0) 断言最终退出代码。
  • 使用 await d.Descriptor.validate() 验证生成的文件系统变更。

编译与分发

根据分发需求选择合适的编译目标。

  • 如果在开发期间进行本地测试: 使用 dart run bin/cli.dart。这会使用 JIT 编译器,以便快速迭代。
  • 如果要打包代码资源和动态库: 使用 dart build cli。这会运行构建钩子,并将输出写入 build/cli/_/bundle/。
  • 如果要分发独立的原生可执行文件: 使用 dart compile exe bin/cli.dart -o <output_path>。这会将 Dart 运行时和机器码打包到单个文件中。
  • 如果要在严格限制磁盘空间的情况下分发多个应用: 使用 dart compile aot-snapshot bin/cli.dart。使用 dartaotruntime 运行生成的 .aot 文件。
交叉编译目标(仅限 Linux)

Dart 支持从 macOS、Windows 或 Linux 主机交叉编译到 Linux。 使用 dart compile exe 或 dart compile aot-snapshot 时,添加 --target-os 和 --target-arch 标志。

  • --target-os=linux(目前仅支持 Linux 作为交叉编译目标)
  • --target-arch=arm64(64 位 ARM)
  • --target-arch=x64(x86-64)
  • --target-arch=arm(32 位 ARM)
  • --target-arch=riscv64(64 位 RISC-V)

示例:dart compile exe --target-os=linux --target-arch=arm64 bin/cli.dart

工作流

任务进度:实现新的 CLI 命令
  • 在 lib/src/commands/ 中创建一个继承自 Command 的新类。
  • 定义 name 和 description 属性。
  • 在构造函数中使用 argParser.addFlag() 或 argParser.addOption() 注册命令专属标志。
  • 实现包含核心逻辑的 run() 方法。
  • 在 bin/cli.dart 的 CommandRunner 实例中使用 addCommand() 注册新命令。
  • 运行验证器 -> 执行 dart run bin/cli.dart help <command_name>,验证帮助文本是否生成。
任务进度:编译并发布原生可执行文件
  • 运行验证器 -> 执行 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

示例

示例:CommandRunner 实现
import 'dart:io';
import 'package:args/command_runner.dart';
import 'package:stack_trace/stack_trace.dart';

class CommitCommand extends Command {
  @override
  final String name = 'commit';
  @override
  final String description = 'Record changes to the repository.';

  CommitCommand() {
    argParser.addFlag('all', abbr: 'a', help: 'Commit all changed files.');
  }

  @override
  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);
    }
  });
}
示例:使用子进程进行集成测试
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();
  });
}

评论

支持 Markdown