dart-build-cli-app
Original:🇺🇸 English
Translated
Entrypoint structure, exit codes, cross-platform scripts. Use when building command line utilities, scripts, or applications.
17installs
Sourcedart-lang/skills
Added on
NPX Install
npx skill4agent add dart-lang/skills dart-build-cli-appTags
Translated version includes tags in frontmatterSKILL.md Content
View Translation Comparison →Building Dart CLI Applications
Contents
- Project Setup & Architecture
- Argument Parsing & Command Routing
- Execution & Error Handling
- Testing CLI Applications
- Compilation & Distribution
- Workflows
- Examples
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
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
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
Testing CLI Applications
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()
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.dartWorkflows
Task Progress: Implement a New CLI Command
- 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>
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
Examples
Example: CommandRunner Implementation
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();
});
}