npx skills add ...
npx skills add flutter/skills --skill dart-cli-app-best-practices
npx skills add flutter/skills --skill dart-cli-app-best-practices
Best practices for creating high-quality, executable Dart CLI applications. Covers entrypoint structure, exit code handling, and recommended packages.
Use this skill when:
bin/).bin/)Keep the contents of your entrypoint file (e.g., bin/my_app.dart) minimal.
This improves testability by decoupling logic from the process runner.
DO:
DON'T:
bin/my_app.dart.executable entry in pubspec.yamlList your executables in pubspec.yaml to make them available for global
activation and clean invocation via dart run.
DO:
Add an executables section mapping the command name to the Dart file in
bin/ (excluding the .dart extension).
Then run via dart run my_app or dart run custom_name.
#! for other scripts on *nix systemsThis is NOT a hard and fast rule, but it is something to consider.
For CLI tools intended to be run directly on Linux and Mac, add a shebang and ensure the file is executable.
DO:
#!/usr/bin/env dart to the first line.chmod +x bin/my_script.dart to make it executable.exitCode)Properly handle process termination to allow for debugging and correct status reporting.
DO:
exitCode setter to report failure.main to complete naturally.64 for bad usage,
78 for configuration errors).
package:io ExitCode class or FreeBSD sysexits man page.AVOID:
exit(code) directly, as it terminates the VM immediately,
preventing "pause on exit" debugging and finally blocks from running.Uncaught exceptions automatically set a non-zero exit code, but you should handle expected errors gracefully.
Example:
When writing CLI applications and tests, ensure compatibility with Windows:
/ because Windows uses \.
Use package:path's p.join or p.normalize to construct paths portably.chmod is not available on Windows. Use icacls on Windows or appropriate
mock libraries to simulate permission errors. Never skip tests on Windows
simply because of permission commands if a Windows equivalent exists.To find areas to apply these best practices:
Inspect files in bin/ to see if they contain logic that should be in lib/:
bin/*.dart.Search for calls to exit() instead of setting exitCode:
\bexit\(Search for hardcoded / in strings that appear to be file paths:
['"][^'"]+/[^'"]+['"] (Verify context as this may match URLs).Use these community-standard packages owned by the Dart team to solve common CLI problems:
| Category | Recommended Package | Usage |
|---|---|---|
| Stack Traces | package:stack_trace | detailed, cleaner stack traces |
| Arg Parsing | package:args | standard flag/option parsing |
| Testing | package:test_process | integration testing for CLI apps |
| Testing | package:test_descriptor | file system fixtures for tests |
| Networking | package:http | standard HTTP client (remember user-agent!) |
| ANSI Output | package:io | handling ANSI colors and styles |
| Category | Recommended Package | Usage |
|---|---|---|
| Configuration | package:json_serializable | strongly typed config objects |
| CLI Generation | package:build_cli | generate arg parsers from classes |
| Version Info | package:build_version | automatic version injection |
| Configuration | package:checked_yaml | precise YAML parsing with line numbers |
.dart_tool/[pkg_name]/.#!/usr/bin/env dart
void main() => print('Ready to run!');import 'dart:io';
void main() {
if (someFailure) {
exitCode = 64; // DO!
return;
}
}Future<void> main(List<String> arguments) async {
try {
await runApp(arguments);
} catch (e, stack) {
print('App crashed!');
print(e);
print(stack);
exitCode = 1; // Explicitly fail
}
}