dart test- Run all testsdart test test/config_props_test.dart- Run single test filedart analyze- Run linter/analysis (uses package:lints/recommended.yaml)dart compile exe bin/raygun_cli.dart -o raygun-cli- Build executabledart run bin/raygun_cli.dart- Run CLI locallydart format .- Format codedart run build_runner build- Generate mocks (one-time)dart run build_runner watch- Auto-regenerate mocks on changes
- CLI Tool: Uploads sourcemaps, manages obfuscation symbols, tracks deployments for Raygun.com
- Main Entry:
bin/raygun_cli.dart- CLI argument parsing and command routing - Commands:
lib/src/- Five main command modules: sourcemap, symbols, deployments, proguard, dsym - APIs: Each command has corresponding API client (
*_api.dart) for Raygun REST API calls - Config:
config_props.darthandles arg parsing with env var fallbacks (RAYGUN_APP_ID, RAYGUN_TOKEN, RAYGUN_API_KEY)
lib/src/[command]/
├── [command]_command.dart # CLI parsing and routing
├── [command]_api.dart # Raygun API integration
└── [command].dart # Business logic (if complex)
lib/src/core/ # Shared utilities (RaygunCommand, RaygunApi builders)
test/[command]/ # Tests mirror lib structure
bin/raygun_cli.dart → lib/src/[command]/[command]_command.dart → *_api.dart
- Commands are stateless; API clients handle HTTP
- Commands receive API clients via constructor (dependency injection)
- Imports: Standard library first, then package imports, then relative imports
- Naming: Snake_case for files/dirs, camelCase for variables, PascalCase for classes
-
Functions: Use
Future<bool>for async operations, return success/failure status - Errors: Print error messages to console, return false on failure
- Types: Use explicit types for public APIs, required named parameters preferred
-
Strings: Use single quotes, string interpolation with
$variable or $ {expression} - Comments: Use /// for public API documentation, avoid inline comments
Standard error handling patterns used throughout:
- Exit code 0: Success
- Exit code 1: Failure (operation didn't succeed)
- Exit code 2: Error (exception or invalid input)
- Print errors to console before returning/exiting
- Use
Future<bool>return type for async operations - Chain with
.then()and.catchError()for error handling
Example:
run().then((result) {
if (result) {
exit(0);
} else {
exit(2);
}
}).catchError((e) {
print('Error: $e');
exit(2);
});All commands extend the RaygunCommand abstract class:
class MyCommand extends RaygunCommand {
const MyCommand({required this.api});
final MyApi api;
@override
String get name => 'mycommand';
@override
ArgParser buildParser() { /* ... */ }
@override
void execute(ArgResults command, bool verbose) { /* ... */ }
}- Help Flag: Always check for
--helpflag first and exit(0) - Config Loading: Use
ConfigProp.load()for app-id, token, api-key (exits if missing) - Subcommands: Use
ArgParser.addCommand()(see symbols: upload/list/delete) - Verbose Flag: Available in all commands (inherited from main parser)
- Mandatory Args: Use
mandatory: truein ArgParser for required flags
ArgParser buildParser() {
return ArgParser()
..addOption('app-id')
..addOption('token')
..addCommand('upload')
..addCommand('list')
..addCommand('delete');
}Each command has a corresponding API client:
RaygunMultipartRequestBuilder- For file uploadsRaygunPostRequestBuilder- For JSON POST requests
Example:
final request = RaygunMultipartRequestBuilder(url, 'POST')
.addBearerToken(token)
.addFile('file', filePath)
.addField('version', version)
.build();- Factory method pattern: Use static
.create()for production instances - Return
Future<bool>to indicate success/failure - Print response codes and messages for debugging
- Handle HTTP responses and errors appropriately
- Tests use
mockitofor mocking API clients - Test files mirror
lib/structure (e.g.,lib/src/symbols/→test/symbols/) - Mock files use
.mocks.dartsuffix and are git-tracked - Generate mocks with:
dart run build_runner build
// 1. Generate mock classes with @GenerateMocks annotation
@GenerateMocks([MyApi])
void main() {
group('MyCommand', () {
late MockMyApi mockApi;
setUp(() {
mockApi = MockMyApi();
});
test('description', () async {
// 2. Setup mock behavior
when(mockApi.someMethod()).thenAnswer((_) async => true);
// 3. Inject mock into command
final command = MyCommand(api: mockApi);
// 4. Execute and verify
final result = await command.run(...);
expect(result, true);
});
});
}- After changing API signatures, run:
dart run build_runner build - Use
watchmode during development:dart run build_runner watch
- Commands receive API clients via constructor
- Global command instances use
.create()factories - Tests inject mock API clients
- Enables testing without hitting real APIs
Example:
// Production usage (in command file)
SymbolsCommand symbolsCommand = SymbolsCommand(api: SymbolsApi.create());
// Test usage
final mockApi = MockSymbolsApi();
final command = SymbolsCommand(api: mockApi);- Title: Must follow Conventional Commits (enforced by
.github/workflows/pr.yml) - Checks: All must pass - format, analyze, test
- Platforms: Multi-platform builds run automatically (Linux, macOS, Windows)
Examples:
feat: add new command for Xfix: resolve issue with Ychore: update dependenciesdocs: update README
IMPORTANT: Update BOTH files when releasing:
pubspec.yaml- version fieldbin/raygun_cli.dart- version constant
- pr.yml: Validates PR title format
- main.yml: Runs tests, format check, analysis, and builds binaries for all platforms
- release.yml: On GitHub release, builds and uploads zipped binaries
# Run CLI locally with arguments
dart run bin/raygun_cli.dart <command> <args>
# Use verbose flag for debug output
dart run bin/raygun_cli.dart -v sourcemap --help
# Set environment variables for testing
export RAYGUN_APP_ID=test-app-id
export RAYGUN_TOKEN=test-token
export RAYGUN_API_KEY=test-api-key- Write/modify API client code
- Add
@GenerateMocks([MyApi])to test file - Run
dart run build_runner buildto generate mocks - Write tests using mock instances
- Run
dart testto verify
# Compile for current platform
dart compile exe bin/raygun_cli.dart -o raygun-cli
# Note: Cross-compilation not supported; use CI for other platforms- Always validate
--helpflag first before parsing other args ConfigProp.load()callsexit(2)if required config is missing- Check mandatory args before executing business logic
- Use
File.existsSync()before file operations - Throw descriptive exceptions if files don't exist
- Use
.split("/").lastto get filename from path
- Use
command.wasParsed('flag')to check if flag was provided - Use
command['option']to get option value - Use
command.command?.nameto get subcommand name
- Prefer
.then()and.catchError()over try/catch for CLI commands - Always handle errors gracefully with user-friendly messages
- Use
Future<bool>for operations that can succeed or fail
- Always use single quotes for strings (Dart convention)
- Use string interpolation:
'Value: $variable'or'Value: ${expression}'
# Sourcemap upload (Flutter)
dart run bin/raygun_cli.dart sourcemap -p flutter \
--uri=https://example.com/main.dart.js \
--app-id=XXX --token=YYY
# Sourcemap upload (single file)
dart run bin/raygun_cli.dart sourcemap \
--input-map=path/to/index.js.map \
--uri=https://example.com/index.js \
--app-id=XXX --token=YYY
# Symbols upload
dart run bin/raygun_cli.dart symbols upload \
--path=app.android-arm64.symbols \
--version=1.0.0 \
--app-id=XXX --token=YYY
# Symbols list
dart run bin/raygun_cli.dart symbols list \
--app-id=XXX --token=YYY
# Symbols delete
dart run bin/raygun_cli.dart symbols delete \
--id=2c7a3u3 \
--app-id=XXX --token=YYY
# Deployments tracking
dart run bin/raygun_cli.dart deployments \
--version=1.0.0 \
--token=YYY \
--api-key=ZZZ \
--scm-type=GitHub \
--scm-identifier=abc123
# Proguard upload
dart run bin/raygun_cli.dart proguard \
--app-id=XXX \
--version=1.0.0 \
--path=mapping.txt \
--external-access-token=EAT \
--overwrite
# iOS dSYM upload
dart run bin/raygun_cli.dart dsym \
--app-id=XXX \
--path=path/to/dsym.zip \
--external-access-token=EATexport RAYGUN_APP_ID=your-app-id
export RAYGUN_TOKEN=your-token
export RAYGUN_API_KEY=your-api-key- Config file support (.raygun.yaml or similar) - see
lib/src/config_props.dart:9 - NodeJS sourcemap platform support (currently stubbed in sourcemap command)
- System package manager installations (brew, apt, etc.)
- Ensure
@GenerateMocksannotation is present in test file - Run
dart pub getto ensure dependencies are installed - Clean and rebuild:
dart run build_runner clean && dart run build_runner build
- Ensure Dart SDK version matches
pubspec.yamlrequirement (^3.5.0) - Run
dart pub getto update dependencies - Check that version in
bin/raygun_cli.dartmatchespubspec.yaml
- Verify mocks are regenerated after API changes
- Check that mock behavior is properly stubbed with
when() - Ensure async tests use
async/awaitor return Future