Skip to content

Commit fa9bf85

Browse files
feat: dSYM support (#40)
* Updated AGENT.md with more context * Feature and documentation * Tests and lib updates. * Update test/dsym/dsym_api_test.mocks.dart Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Fixing tests, updated dependencies along the way to latest. * Formatting --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent c043967 commit fa9bf85

40 files changed

+2137
-1090
lines changed

AGENT.md

Lines changed: 325 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,33 @@
77
- `dart compile exe bin/raygun_cli.dart -o raygun-cli` - Build executable
88
- `dart run bin/raygun_cli.dart` - Run CLI locally
99
- `dart format .` - Format code
10+
- `dart run build_runner build` - Generate mocks (one-time)
11+
- `dart run build_runner watch` - Auto-regenerate mocks on changes
1012

1113
## Architecture
1214
- **CLI Tool**: Uploads sourcemaps, manages obfuscation symbols, tracks deployments for Raygun.com
1315
- **Main Entry**: `bin/raygun_cli.dart` - CLI argument parsing and command routing
14-
- **Commands**: `lib/src/` - Four main command modules: sourcemap, symbols, deployments, proguard
16+
- **Commands**: `lib/src/` - Five main command modules: sourcemap, symbols, deployments, proguard, dsym
1517
- **APIs**: Each command has corresponding API client (`*_api.dart`) for Raygun REST API calls
1618
- **Config**: `config_props.dart` handles arg parsing with env var fallbacks (RAYGUN_APP_ID, RAYGUN_TOKEN, RAYGUN_API_KEY)
1719

20+
### Directory Structure
21+
```
22+
lib/src/[command]/
23+
├── [command]_command.dart # CLI parsing and routing
24+
├── [command]_api.dart # Raygun API integration
25+
└── [command].dart # Business logic (if complex)
26+
lib/src/core/ # Shared utilities (RaygunCommand, RaygunApi builders)
27+
test/[command]/ # Tests mirror lib structure
28+
```
29+
30+
### Dependency Flow
31+
```
32+
bin/raygun_cli.dart → lib/src/[command]/[command]_command.dart → *_api.dart
33+
```
34+
- Commands are stateless; API clients handle HTTP
35+
- Commands receive API clients via constructor (dependency injection)
36+
1837
## Code Style
1938
- **Imports**: Standard library first, then package imports, then relative imports
2039
- **Naming**: Snake_case for files/dirs, camelCase for variables, PascalCase for classes
@@ -23,3 +42,308 @@
2342
- **Types**: Use explicit types for public APIs, required named parameters preferred
2443
- **Strings**: Use single quotes, string interpolation with $variable or ${expression}
2544
- **Comments**: Use /// for public API documentation, avoid inline comments
45+
46+
## Error Handling & Exit Codes
47+
Standard error handling patterns used throughout:
48+
- **Exit code 0**: Success
49+
- **Exit code 1**: Failure (operation didn't succeed)
50+
- **Exit code 2**: Error (exception or invalid input)
51+
- Print errors to console before returning/exiting
52+
- Use `Future<bool>` return type for async operations
53+
- Chain with `.then()` and `.catchError()` for error handling
54+
55+
Example:
56+
```dart
57+
run().then((result) {
58+
if (result) {
59+
exit(0);
60+
} else {
61+
exit(2);
62+
}
63+
}).catchError((e) {
64+
print('Error: $e');
65+
exit(2);
66+
});
67+
```
68+
69+
## Command Implementation Patterns
70+
All commands extend the `RaygunCommand` abstract class:
71+
72+
### Required Implementation
73+
```dart
74+
class MyCommand extends RaygunCommand {
75+
const MyCommand({required this.api});
76+
77+
final MyApi api;
78+
79+
@override
80+
String get name => 'mycommand';
81+
82+
@override
83+
ArgParser buildParser() { /* ... */ }
84+
85+
@override
86+
void execute(ArgResults command, bool verbose) { /* ... */ }
87+
}
88+
```
89+
90+
### Command Patterns
91+
- **Help Flag**: Always check for `--help` flag first and exit(0)
92+
- **Config Loading**: Use `ConfigProp.load()` for app-id, token, api-key (exits if missing)
93+
- **Subcommands**: Use `ArgParser.addCommand()` (see symbols: upload/list/delete)
94+
- **Verbose Flag**: Available in all commands (inherited from main parser)
95+
- **Mandatory Args**: Use `mandatory: true` in ArgParser for required flags
96+
97+
### Example: Subcommands Pattern (symbols command)
98+
```dart
99+
ArgParser buildParser() {
100+
return ArgParser()
101+
..addOption('app-id')
102+
..addOption('token')
103+
..addCommand('upload')
104+
..addCommand('list')
105+
..addCommand('delete');
106+
}
107+
```
108+
109+
## API Client Patterns
110+
Each command has a corresponding API client:
111+
112+
### Builder Pattern for Requests
113+
- `RaygunMultipartRequestBuilder` - For file uploads
114+
- `RaygunPostRequestBuilder` - For JSON POST requests
115+
116+
Example:
117+
```dart
118+
final request = RaygunMultipartRequestBuilder(url, 'POST')
119+
.addBearerToken(token)
120+
.addFile('file', filePath)
121+
.addField('version', version)
122+
.build();
123+
```
124+
125+
### API Client Structure
126+
- Factory method pattern: Use static `.create()` for production instances
127+
- Return `Future<bool>` to indicate success/failure
128+
- Print response codes and messages for debugging
129+
- Handle HTTP responses and errors appropriately
130+
131+
## Testing Patterns & Mock Generation
132+
133+
### Test Structure
134+
- Tests use `mockito` for mocking API clients
135+
- Test files mirror `lib/` structure (e.g., `lib/src/symbols/``test/symbols/`)
136+
- Mock files use `.mocks.dart` suffix and are git-tracked
137+
- Generate mocks with: `dart run build_runner build`
138+
139+
### Test Pattern
140+
```dart
141+
// 1. Generate mock classes with @GenerateMocks annotation
142+
@GenerateMocks([MyApi])
143+
void main() {
144+
group('MyCommand', () {
145+
late MockMyApi mockApi;
146+
147+
setUp(() {
148+
mockApi = MockMyApi();
149+
});
150+
151+
test('description', () async {
152+
// 2. Setup mock behavior
153+
when(mockApi.someMethod()).thenAnswer((_) async => true);
154+
155+
// 3. Inject mock into command
156+
final command = MyCommand(api: mockApi);
157+
158+
// 4. Execute and verify
159+
final result = await command.run(...);
160+
expect(result, true);
161+
});
162+
});
163+
}
164+
```
165+
166+
### Mock Regeneration
167+
- After changing API signatures, run: `dart run build_runner build`
168+
- Use `watch` mode during development: `dart run build_runner watch`
169+
170+
## Dependency Injection
171+
172+
### Pattern
173+
- Commands receive API clients via constructor
174+
- Global command instances use `.create()` factories
175+
- Tests inject mock API clients
176+
- Enables testing without hitting real APIs
177+
178+
Example:
179+
```dart
180+
// Production usage (in command file)
181+
SymbolsCommand symbolsCommand = SymbolsCommand(api: SymbolsApi.create());
182+
183+
// Test usage
184+
final mockApi = MockSymbolsApi();
185+
final command = SymbolsCommand(api: mockApi);
186+
```
187+
188+
## CI/CD & Release Workflow
189+
190+
### PR Requirements
191+
- **Title**: Must follow Conventional Commits (enforced by `.github/workflows/pr.yml`)
192+
- **Checks**: All must pass - format, analyze, test
193+
- **Platforms**: Multi-platform builds run automatically (Linux, macOS, Windows)
194+
195+
### Conventional Commits Format
196+
Examples:
197+
- `feat: add new command for X`
198+
- `fix: resolve issue with Y`
199+
- `chore: update dependencies`
200+
- `docs: update README`
201+
202+
### Version Management
203+
**IMPORTANT**: Update BOTH files when releasing:
204+
1. `pubspec.yaml` - version field
205+
2. `bin/raygun_cli.dart` - version constant
206+
207+
### Workflows
208+
- **pr.yml**: Validates PR title format
209+
- **main.yml**: Runs tests, format check, analysis, and builds binaries for all platforms
210+
- **release.yml**: On GitHub release, builds and uploads zipped binaries
211+
212+
## Development Workflow
213+
214+
### Local Testing
215+
```bash
216+
# Run CLI locally with arguments
217+
dart run bin/raygun_cli.dart <command> <args>
218+
219+
# Use verbose flag for debug output
220+
dart run bin/raygun_cli.dart -v sourcemap --help
221+
222+
# Set environment variables for testing
223+
export RAYGUN_APP_ID=test-app-id
224+
export RAYGUN_TOKEN=test-token
225+
export RAYGUN_API_KEY=test-api-key
226+
```
227+
228+
### Testing Workflow
229+
1. Write/modify API client code
230+
2. Add `@GenerateMocks([MyApi])` to test file
231+
3. Run `dart run build_runner build` to generate mocks
232+
4. Write tests using mock instances
233+
5. Run `dart test` to verify
234+
235+
### Build for Distribution
236+
```bash
237+
# Compile for current platform
238+
dart compile exe bin/raygun_cli.dart -o raygun-cli
239+
240+
# Note: Cross-compilation not supported; use CI for other platforms
241+
```
242+
243+
## Common Gotchas & Best Practices
244+
245+
### Validation Order
246+
1. Always validate `--help` flag first before parsing other args
247+
2. `ConfigProp.load()` calls `exit(2)` if required config is missing
248+
3. Check mandatory args before executing business logic
249+
250+
### File Operations
251+
- Use `File.existsSync()` before file operations
252+
- Throw descriptive exceptions if files don't exist
253+
- Use `.split("/").last` to get filename from path
254+
255+
### Argument Parsing
256+
- Use `command.wasParsed('flag')` to check if flag was provided
257+
- Use `command['option']` to get option value
258+
- Use `command.command?.name` to get subcommand name
259+
260+
### Async Patterns
261+
- Prefer `.then()` and `.catchError()` over try/catch for CLI commands
262+
- Always handle errors gracefully with user-friendly messages
263+
- Use `Future<bool>` for operations that can succeed or fail
264+
265+
### String Conventions
266+
- Always use single quotes for strings (Dart convention)
267+
- Use string interpolation: `'Value: $variable'` or `'Value: ${expression}'`
268+
269+
## Quick Reference
270+
271+
### Command Examples
272+
```bash
273+
# Sourcemap upload (Flutter)
274+
dart run bin/raygun_cli.dart sourcemap -p flutter \
275+
--uri=https://example.com/main.dart.js \
276+
--app-id=XXX --token=YYY
277+
278+
# Sourcemap upload (single file)
279+
dart run bin/raygun_cli.dart sourcemap \
280+
--input-map=path/to/index.js.map \
281+
--uri=https://example.com/index.js \
282+
--app-id=XXX --token=YYY
283+
284+
# Symbols upload
285+
dart run bin/raygun_cli.dart symbols upload \
286+
--path=app.android-arm64.symbols \
287+
--version=1.0.0 \
288+
--app-id=XXX --token=YYY
289+
290+
# Symbols list
291+
dart run bin/raygun_cli.dart symbols list \
292+
--app-id=XXX --token=YYY
293+
294+
# Symbols delete
295+
dart run bin/raygun_cli.dart symbols delete \
296+
--id=2c7a3u3 \
297+
--app-id=XXX --token=YYY
298+
299+
# Deployments tracking
300+
dart run bin/raygun_cli.dart deployments \
301+
--version=1.0.0 \
302+
--token=YYY \
303+
--api-key=ZZZ \
304+
--scm-type=GitHub \
305+
--scm-identifier=abc123
306+
307+
# Proguard upload
308+
dart run bin/raygun_cli.dart proguard \
309+
--app-id=XXX \
310+
--version=1.0.0 \
311+
--path=mapping.txt \
312+
--external-access-token=EAT \
313+
--overwrite
314+
315+
# iOS dSYM upload
316+
dart run bin/raygun_cli.dart dsym \
317+
--app-id=XXX \
318+
--path=path/to/dsym.zip \
319+
--external-access-token=EAT
320+
```
321+
322+
### Environment Variables
323+
```bash
324+
export RAYGUN_APP_ID=your-app-id
325+
export RAYGUN_TOKEN=your-token
326+
export RAYGUN_API_KEY=your-api-key
327+
```
328+
329+
## Known TODOs & Future Improvements
330+
- Config file support (.raygun.yaml or similar) - see `lib/src/config_props.dart:9`
331+
- NodeJS sourcemap platform support (currently stubbed in sourcemap command)
332+
- System package manager installations (brew, apt, etc.)
333+
334+
## Troubleshooting
335+
336+
### Mock Generation Issues
337+
- Ensure `@GenerateMocks` annotation is present in test file
338+
- Run `dart pub get` to ensure dependencies are installed
339+
- Clean and rebuild: `dart run build_runner clean && dart run build_runner build`
340+
341+
### Build Issues
342+
- Ensure Dart SDK version matches `pubspec.yaml` requirement (^3.5.0)
343+
- Run `dart pub get` to update dependencies
344+
- Check that version in `bin/raygun_cli.dart` matches `pubspec.yaml`
345+
346+
### Test Failures
347+
- Verify mocks are regenerated after API changes
348+
- Check that mock behavior is properly stubbed with `when()`
349+
- Ensure async tests use `async/await` or return Future

README.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,44 @@ Response:
152152
153153
```
154154

155+
#### iOS dSYM Uploader
156+
157+
Upload dSYM files for iOS to [raygun.com](https://raygun.com).
158+
159+
Documentation: https://raygun.com/documentation/language-guides/apple/crash-reporting/advanced-setup/#symbolication
160+
161+
```
162+
raygun-cli dsym <arguments>
163+
```
164+
165+
Minimal required arguments are:
166+
167+
```
168+
raygun-cli dsym --app-id=APP_ID --path=<Path to dSYM zip file> --external-access-token=<EAT from your Raygun user account settings>
169+
```
170+
171+
Example outputs:
172+
173+
```
174+
Success:
175+
176+
Uploading: <somewhere>/app.dSYM.zip
177+
Success uploading dSYM file: 200
178+
Result: {"Status":"Success","Message":"dSYM files uploaded"}
179+
180+
Wrong External Access Token:
181+
182+
Uploading: <somewhere>/app.dSYM.zip
183+
Error uploading dSYM file: 302
184+
Response:
185+
186+
Invalid dSYM file:
187+
188+
Uploading: <somewhere>/invalid.zip
189+
Success uploading dSYM file: 200
190+
Result: {"Status":"Failure","Message":"No dSYM file found"}
191+
```
192+
155193
#### Flutter obfuscation symbols
156194

157195
Manages obfuscation symbols to [raygun.com](https://raygun.com).

0 commit comments

Comments
 (0)