Skip to content

Commit bb9be4e

Browse files
committed
Updated AGENT.md with more context
1 parent c043967 commit bb9be4e

File tree

1 file changed

+318
-0
lines changed

1 file changed

+318
-0
lines changed

AGENT.md

Lines changed: 318 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
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
@@ -15,6 +17,23 @@
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,302 @@
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+
316+
### Environment Variables
317+
```bash
318+
export RAYGUN_APP_ID=your-app-id
319+
export RAYGUN_TOKEN=your-token
320+
export RAYGUN_API_KEY=your-api-key
321+
```
322+
323+
## Known TODOs & Future Improvements
324+
- Config file support (.raygun.yaml or similar) - see `lib/src/config_props.dart:9`
325+
- NodeJS sourcemap platform support (currently stubbed in sourcemap command)
326+
- System package manager installations (brew, apt, etc.)
327+
328+
## Troubleshooting
329+
330+
### Mock Generation Issues
331+
- Ensure `@GenerateMocks` annotation is present in test file
332+
- Run `dart pub get` to ensure dependencies are installed
333+
- Clean and rebuild: `dart run build_runner clean && dart run build_runner build`
334+
335+
### Build Issues
336+
- Ensure Dart SDK version matches `pubspec.yaml` requirement (^3.5.0)
337+
- Run `dart pub get` to update dependencies
338+
- Check that version in `bin/raygun_cli.dart` matches `pubspec.yaml`
339+
340+
### Test Failures
341+
- Verify mocks are regenerated after API changes
342+
- Check that mock behavior is properly stubbed with `when()`
343+
- Ensure async tests use `async/await` or return Future

0 commit comments

Comments
 (0)