From 483687fe7d579cefa052db97cd7c3bb54bc29940 Mon Sep 17 00:00:00 2001 From: Miguel Beltran Date: Fri, 17 Jan 2025 10:45:46 +0100 Subject: [PATCH 01/10] improve printUsage showing commands --- bin/raygun_cli.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/bin/raygun_cli.dart b/bin/raygun_cli.dart index 751cf50..afbab3f 100644 --- a/bin/raygun_cli.dart +++ b/bin/raygun_cli.dart @@ -34,8 +34,11 @@ ArgParser buildParser() { } void printUsage(ArgParser argParser) { - print('Usage: raygun-cli [arguments]'); - print(argParser.usage); + print('Usage: raygun-cli '); + print('Commands:'); + for (final command in argParser.commands.keys) { + print(' $command'); + } } void main(List arguments) { From c55c613596689941d64cb0e6922064a2d3266e26 Mon Sep 17 00:00:00 2001 From: Miguel Beltran Date: Fri, 17 Jan 2025 10:50:01 +0100 Subject: [PATCH 02/10] pubspec --- pubspec.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.lock b/pubspec.lock index 3abb0c6..b362e98 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -407,4 +407,4 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.5.0 <4.0.0" + dart: ">=3.6.0 <4.0.0" From c59bb2eb175f46ec60c8022a6adbfc9ce0da5c3c Mon Sep 17 00:00:00 2001 From: Miguel Beltran Date: Fri, 17 Jan 2025 10:50:10 +0100 Subject: [PATCH 03/10] show general usage --- bin/raygun_cli.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bin/raygun_cli.dart b/bin/raygun_cli.dart index afbab3f..6688eb1 100644 --- a/bin/raygun_cli.dart +++ b/bin/raygun_cli.dart @@ -2,7 +2,7 @@ import 'package:args/args.dart'; import 'package:raygun_cli/sourcemap/sourcemap_command.dart'; import 'package:raygun_cli/symbols/flutter_symbols.dart'; -const String version = '0.0.1'; +const String version = '0.0.2'; ArgParser buildParser() { return ArgParser() @@ -35,6 +35,7 @@ ArgParser buildParser() { void printUsage(ArgParser argParser) { print('Usage: raygun-cli '); + print(argParser.usage); print('Commands:'); for (final command in argParser.commands.keys) { print(' $command'); From d0bcbd16fd9463584e48a5e9aefb19f365e25535 Mon Sep 17 00:00:00 2001 From: Miguel Beltran Date: Fri, 17 Jan 2025 11:43:56 +0100 Subject: [PATCH 04/10] implemented config props parsing --- lib/config_props.dart | 61 ++++++++++++++++++++ lib/sourcemap/flutter/sourcemap_flutter.dart | 1 + lib/sourcemap/node/sourcemap_node.dart | 1 + lib/sourcemap/sourcemap_base.dart | 7 ++- lib/sourcemap/sourcemap_command.dart | 35 +++++++---- lib/sourcemap/sourcemap_single_file.dart | 1 + lib/symbols/flutter_symbols.dart | 19 +++--- test/.gitkeep | 0 test/config_props_test.dart | 29 ++++++++++ 9 files changed, 133 insertions(+), 21 deletions(-) create mode 100644 lib/config_props.dart delete mode 100644 test/.gitkeep create mode 100644 test/config_props_test.dart diff --git a/lib/config_props.dart b/lib/config_props.dart new file mode 100644 index 0000000..fa041cf --- /dev/null +++ b/lib/config_props.dart @@ -0,0 +1,61 @@ +import 'dart:io'; + +import 'package:args/args.dart'; + +/// Configuration properties for the Raygun CLI +class ConfigProps { + /// Raygun's application ID + final String appId; + + /// Raygun's access token + final String token; + + ConfigProps._({ + required this.appId, + required this.token, + }); + + /// Load configuration properties from arguments or environment variables + /// and return a new instance of [ConfigProps] or exit with code 2. + factory ConfigProps.load(ArgResults arguments) { + String? appId; + String? token; + + // Providing app-id and token via argument takes priority + if (arguments.wasParsed('app-id')) { + appId = arguments['app-id']; + } else { + appId = Platform.environment['RAYGUN_APP_ID']; + } + + if (appId == null) { + print('Error: Missing "app-id"'); + print( + ' Please provide "app-id" via argument or environment variable "RAYGUN_APP_ID"'); + exit(2); + } + + if (arguments.wasParsed('token')) { + token = arguments['token']; + } else { + token = Platform.environment['RAYGUN_TOKEN']; + } + + if (token == null) { + print('Error: Missing "token"'); + print( + ' Please provide "token" via argument or environment variable "RAYGUN_TOKEN"'); + exit(2); + } + + if (arguments.wasParsed('verbose')) { + print('App ID: $appId'); + print('Token: $token'); + } + + return ConfigProps._( + appId: appId, + token: token, + ); + } +} diff --git a/lib/sourcemap/flutter/sourcemap_flutter.dart b/lib/sourcemap/flutter/sourcemap_flutter.dart index e095a25..18b5395 100644 --- a/lib/sourcemap/flutter/sourcemap_flutter.dart +++ b/lib/sourcemap/flutter/sourcemap_flutter.dart @@ -7,6 +7,7 @@ class SourcemapFlutter extends SourcemapBase { SourcemapFlutter({ required super.command, required super.verbose, + required super.config, }); @override diff --git a/lib/sourcemap/node/sourcemap_node.dart b/lib/sourcemap/node/sourcemap_node.dart index 5ebb10a..6c67478 100644 --- a/lib/sourcemap/node/sourcemap_node.dart +++ b/lib/sourcemap/node/sourcemap_node.dart @@ -6,6 +6,7 @@ class SourcemapNode extends SourcemapBase { SourcemapNode({ required super.command, required super.verbose, + required super.config, }); @override diff --git a/lib/sourcemap/sourcemap_base.dart b/lib/sourcemap/sourcemap_base.dart index 999f176..ab1c2fa 100644 --- a/lib/sourcemap/sourcemap_base.dart +++ b/lib/sourcemap/sourcemap_base.dart @@ -1,12 +1,15 @@ import 'package:args/args.dart'; +import '../config_props.dart'; + abstract class SourcemapBase { SourcemapBase({ required this.command, required this.verbose, + required ConfigProps config, }) { - appId = command.option('app-id')!; - token = command.option('token')!; + appId = config.appId; + token = config.token; } final ArgResults command; diff --git a/lib/sourcemap/sourcemap_command.dart b/lib/sourcemap/sourcemap_command.dart index 98d8a13..26f6bd4 100644 --- a/lib/sourcemap/sourcemap_command.dart +++ b/lib/sourcemap/sourcemap_command.dart @@ -5,6 +5,8 @@ import 'package:raygun_cli/sourcemap/flutter/sourcemap_flutter.dart'; import 'package:raygun_cli/sourcemap/node/sourcemap_node.dart'; import 'package:raygun_cli/sourcemap/sourcemap_single_file.dart'; +import '../config_props.dart'; + const kSourcemapCommand = 'sourcemap'; ArgParser buildParserSourcemap() { @@ -15,15 +17,19 @@ ArgParser buildParserSourcemap() { negatable: false, help: 'Print sourcemap usage information.', ) + ..addFlag( + 'verbose', + abbr: 'v', + negatable: false, + help: 'Show additional command output.', + ) ..addOption( 'app-id', help: 'Raygun\'s application ID', - mandatory: true, ) ..addOption( 'token', help: 'Raygun\'s access token', - mandatory: true, ) ..addOption( 'platform', @@ -56,18 +62,27 @@ void parseSourcemapCommand(ArgResults command, bool verbose) { print(buildParserSourcemap().usage); exit(0); } - if (!command.wasParsed('app-id') || !command.wasParsed('token')) { - print('Missing mandatory arguments'); - print(buildParserSourcemap().usage); - exit(2); - } + final configProps = ConfigProps.load(command); + switch (command.option('platform')) { case null: - SourcemapSingleFile(command: command, verbose: verbose).upload(); + SourcemapSingleFile( + command: command, + verbose: verbose, + config: configProps, + ).upload(); case 'flutter': - SourcemapFlutter(command: command, verbose: verbose).upload(); + SourcemapFlutter( + command: command, + verbose: verbose, + config: configProps, + ).upload(); case 'node': - SourcemapNode(command: command, verbose: verbose).upload(); + SourcemapNode( + command: command, + verbose: verbose, + config: configProps, + ).upload(); default: print('Unsupported platform'); exit(1); diff --git a/lib/sourcemap/sourcemap_single_file.dart b/lib/sourcemap/sourcemap_single_file.dart index a478b10..9a65f6f 100644 --- a/lib/sourcemap/sourcemap_single_file.dart +++ b/lib/sourcemap/sourcemap_single_file.dart @@ -7,6 +7,7 @@ class SourcemapSingleFile extends SourcemapBase { SourcemapSingleFile({ required super.command, required super.verbose, + required super.config, }); @override diff --git a/lib/symbols/flutter_symbols.dart b/lib/symbols/flutter_symbols.dart index 2bc6292..f459846 100644 --- a/lib/symbols/flutter_symbols.dart +++ b/lib/symbols/flutter_symbols.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:args/args.dart'; +import 'package:raygun_cli/config_props.dart'; import 'package:raygun_cli/symbols/flutter_symbols_api.dart'; const kSymbolsCommand = 'symbols'; @@ -12,15 +13,11 @@ void parseSymbolsCommand(ArgResults command, bool verbose) { print(buildParserSymbols().usage); exit(0); } - if (!command.wasParsed('app-id') || !command.wasParsed('token')) { - print('Missing mandatory arguments'); - print(buildParserSymbols().usage); - exit(2); - } + final configProps = ConfigProps.load(command); _run( command: command, - appId: command['app-id'], - token: command['token'], + appId: configProps.appId, + token: configProps.token, ).then((result) { if (result) { exit(0); @@ -85,15 +82,19 @@ ArgParser buildParserSymbols() { negatable: false, help: 'Print $kSymbolsCommand usage information.', ) + ..addFlag( + 'verbose', + abbr: 'v', + negatable: false, + help: 'Show additional command output.', + ) ..addOption( 'app-id', help: 'Raygun\'s application ID', - mandatory: true, ) ..addOption( 'token', help: 'Raygun\'s access token', - mandatory: true, ) ..addOption( 'path', diff --git a/test/.gitkeep b/test/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/test/config_props_test.dart b/test/config_props_test.dart new file mode 100644 index 0000000..d33fc9a --- /dev/null +++ b/test/config_props_test.dart @@ -0,0 +1,29 @@ +import 'package:args/args.dart'; +import 'package:raygun_cli/config_props.dart'; +import 'package:test/test.dart'; + +void main() { + group('ConfigProps', () { + test('should parse arguments', () { + ArgParser parser = ArgParser() + ..addFlag('app-id') + ..addFlag('token'); + final results = + parser.parse(['app-id', 'app-id-parsed', 'token', 'token-parsed']); + final props = ConfigProps.load(results); + expect(props.appId, 'app-id-parsed'); + expect(props.token, 'token-parsed'); + }); + + test('should parse from env vars', () { + ArgParser parser = ArgParser() + ..addFlag('app-id') + ..addFlag('token'); + // intentionally empty + final results = parser.parse([]); + final props = ConfigProps.load(results); + expect(props.appId, 'app-id-env'); + expect(props.token, 'token-env'); + }); + }); +} From 42ce20bcd31dcc393e893f5dcea22c8894bffe10 Mon Sep 17 00:00:00 2001 From: Miguel Beltran Date: Fri, 17 Jan 2025 11:47:34 +0100 Subject: [PATCH 05/10] enable tests --- .github/workflows/main.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ed70e1c..93d6cc4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -23,9 +23,8 @@ jobs: - name: Analyze project source run: dart analyze - # No tests to run yet - # - name: Run tests - # run: dart test + - name: Run tests + run: dart test build-linux: runs-on: ubuntu-latest From 4d2c2787b03a1b15bfb98b72492f7dae573d87e4 Mon Sep 17 00:00:00 2001 From: Miguel Beltran Date: Fri, 17 Jan 2025 12:08:13 +0100 Subject: [PATCH 06/10] implement tests --- .github/workflows/main.yml | 2 +- lib/config_props.dart | 5 ++- lib/environment.dart | 40 +++++++++++++++++++ test/config_props_test.dart | 79 ++++++++++++++++++++++++++++++++++--- 4 files changed, 117 insertions(+), 9 deletions(-) create mode 100644 lib/environment.dart diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 93d6cc4..498ce32 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -7,7 +7,7 @@ on: workflow_dispatch: jobs: - ckeck: + check: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/lib/config_props.dart b/lib/config_props.dart index fa041cf..d78fb56 100644 --- a/lib/config_props.dart +++ b/lib/config_props.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:args/args.dart'; +import 'package:raygun_cli/environment.dart'; /// Configuration properties for the Raygun CLI class ConfigProps { @@ -25,7 +26,7 @@ class ConfigProps { if (arguments.wasParsed('app-id')) { appId = arguments['app-id']; } else { - appId = Platform.environment['RAYGUN_APP_ID']; + appId = Environment.instance.raygunAppId; } if (appId == null) { @@ -38,7 +39,7 @@ class ConfigProps { if (arguments.wasParsed('token')) { token = arguments['token']; } else { - token = Platform.environment['RAYGUN_TOKEN']; + token = Environment.instance.raygunToken; } if (token == null) { diff --git a/lib/environment.dart b/lib/environment.dart new file mode 100644 index 0000000..2fda981 --- /dev/null +++ b/lib/environment.dart @@ -0,0 +1,40 @@ +import 'dart:io'; + +/// Wraps access to Environment variables +/// Allows faking for testing +class Environment { + static String raygunAppIdKey = 'RAYGUN_APP_ID'; + static String raygunTokenKey = 'RAYGUN_TOKEN'; + + final String? raygunAppId; + final String? raygunToken; + + static Environment? _instance; + + /// Singleton instance access + /// Will init if not already + static Environment get instance { + _instance ??= Environment._init(); + return _instance!; + } + + /// For testing purposes + static void setInstance(Environment instance) { + _instance = instance; + } + + /// Create custom instance + Environment({ + required this.raygunAppId, + required this.raygunToken, + }); + + factory Environment._init() { + final raygunAppId = Platform.environment[raygunAppIdKey]; + final raygunToken = Platform.environment[raygunTokenKey]; + return Environment( + raygunAppId: raygunAppId, + raygunToken: raygunToken, + ); + } +} diff --git a/test/config_props_test.dart b/test/config_props_test.dart index d33fc9a..90a5594 100644 --- a/test/config_props_test.dart +++ b/test/config_props_test.dart @@ -1,29 +1,96 @@ +import 'dart:io'; + import 'package:args/args.dart'; import 'package:raygun_cli/config_props.dart'; +import 'package:raygun_cli/environment.dart'; import 'package:test/test.dart'; void main() { group('ConfigProps', () { test('should parse arguments', () { ArgParser parser = ArgParser() - ..addFlag('app-id') - ..addFlag('token'); + ..addFlag('verbose') + ..addOption('app-id') + ..addOption('token'); final results = - parser.parse(['app-id', 'app-id-parsed', 'token', 'token-parsed']); + parser.parse(['--app-id=app-id-parsed', '--token=token-parsed']); final props = ConfigProps.load(results); expect(props.appId, 'app-id-parsed'); expect(props.token, 'token-parsed'); }); test('should parse from env vars', () { + // fake environment variables + Environment.setInstance( + Environment( + raygunAppId: 'app-id-env', + raygunToken: 'token-env', + ), + ); + + // define parser ArgParser parser = ArgParser() - ..addFlag('app-id') - ..addFlag('token'); - // intentionally empty + ..addFlag('verbose') + ..addOption('app-id') + ..addOption('token'); + + // parse nothing final results = parser.parse([]); + + // load from env vars final props = ConfigProps.load(results); expect(props.appId, 'app-id-env'); expect(props.token, 'token-env'); }); + + test('should parse with priority', () { + // fake environment variables + Environment.setInstance( + Environment( + raygunAppId: 'app-id-env', + raygunToken: 'token-env', + ), + ); + + // define parser + ArgParser parser = ArgParser() + ..addFlag('verbose') + ..addOption('app-id') + ..addOption('token'); + + // parse arguments + final results = + parser.parse(['--app-id=app-id-parsed', '--token=token-parsed']); + + // load from parsed even if env vars are set + final props = ConfigProps.load(results); + expect(props.appId, 'app-id-parsed'); + expect(props.token, 'token-parsed'); + }); + + test('should parse from both', () { + // fake environment variables + // token is not provided + Environment.setInstance( + Environment( + raygunAppId: 'app-id-env', + raygunToken: null, + ), + ); + + // define parser + ArgParser parser = ArgParser() + ..addFlag('verbose') + ..addOption('app-id') + ..addOption('token'); + + // parse arguments, only token is passed + final results = parser.parse(['--token=token-parsed']); + + // app-id from env, token from argument + final props = ConfigProps.load(results); + expect(props.appId, 'app-id-env'); + expect(props.token, 'token-parsed'); + }); }); } From f4cdea126a23dbbb0dd494b920ce24393cc3645a Mon Sep 17 00:00:00 2001 From: Miguel Beltran Date: Fri, 17 Jan 2025 12:08:41 +0100 Subject: [PATCH 07/10] cleanup --- test/config_props_test.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/config_props_test.dart b/test/config_props_test.dart index 90a5594..51b8af7 100644 --- a/test/config_props_test.dart +++ b/test/config_props_test.dart @@ -1,5 +1,3 @@ -import 'dart:io'; - import 'package:args/args.dart'; import 'package:raygun_cli/config_props.dart'; import 'package:raygun_cli/environment.dart'; From 86e21d3e834e4c6636e7fb8dbcc67c7c63751a3b Mon Sep 17 00:00:00 2001 From: Miguel Beltran Date: Fri, 17 Jan 2025 13:23:17 +0100 Subject: [PATCH 08/10] update README --- README.md | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e3ff276..1f3ffdc 100644 --- a/README.md +++ b/README.md @@ -43,10 +43,22 @@ Or use directly from sources: dart bin/raygun_cli.dart ``` -**Common mandatory arguments** +#### Configuration parameters -- `app-id` the Application ID in Raygun.com. -- `token` is an access token from https://app.raygun.com/user/tokens. +All `raygun-cli` commands share the same configuration parameters. + +- App ID: The Application ID in Raygun.com. +- Token: An access token from https://app.raygun.com/user/tokens. + +You can pass these parameters via arguments, e.g. `--app-id=` +or you can set them as environment variables. + +Parameters passed as arguments have priority over environment variables. + +| Parameter | Argument | Environment Variable | +|-----------|----------|----------------------| +| App ID | `app-id` | `RAYGUN_APP_ID` | +| Token | `token` | `RAYGUN_TOKEN` | #### Sourcemap Uploader From 3bfe84c2673f7c87a5bdadbc40adbdca8a0d466b Mon Sep 17 00:00:00 2001 From: Miguel Beltran Date: Fri, 17 Jan 2025 13:26:51 +0100 Subject: [PATCH 09/10] improve print usage --- bin/raygun_cli.dart | 4 ++++ pubspec.yaml | 1 + 2 files changed, 5 insertions(+) diff --git a/bin/raygun_cli.dart b/bin/raygun_cli.dart index 6688eb1..74c6e7e 100644 --- a/bin/raygun_cli.dart +++ b/bin/raygun_cli.dart @@ -34,12 +34,16 @@ ArgParser buildParser() { } void printUsage(ArgParser argParser) { + print('Raygun CLI: $version'); + print(''); print('Usage: raygun-cli '); print(argParser.usage); + print(''); print('Commands:'); for (final command in argParser.commands.keys) { print(' $command'); } + print(''); } void main(List arguments) { diff --git a/pubspec.yaml b/pubspec.yaml index e07efb3..9e2c032 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,6 @@ name: raygun_cli description: Command-line tool for Raygun.com +# Update version in bin/raygun_cli.dart as well version: 0.0.2 repository: https://github.com/MindscapeHQ/raygun-cli/ homepage: https://raygun.com From f9b2672c8f544cc9cfd4e88e265e3a8df546c7d0 Mon Sep 17 00:00:00 2001 From: Miguel Beltran Date: Fri, 17 Jan 2025 13:29:39 +0100 Subject: [PATCH 10/10] pass verbose param --- lib/config_props.dart | 4 ++-- lib/sourcemap/sourcemap_command.dart | 8 +------- lib/symbols/flutter_symbols.dart | 8 +------- 3 files changed, 4 insertions(+), 16 deletions(-) diff --git a/lib/config_props.dart b/lib/config_props.dart index d78fb56..b27202d 100644 --- a/lib/config_props.dart +++ b/lib/config_props.dart @@ -18,7 +18,7 @@ class ConfigProps { /// Load configuration properties from arguments or environment variables /// and return a new instance of [ConfigProps] or exit with code 2. - factory ConfigProps.load(ArgResults arguments) { + factory ConfigProps.load(ArgResults arguments, {bool verbose = false}) { String? appId; String? token; @@ -49,7 +49,7 @@ class ConfigProps { exit(2); } - if (arguments.wasParsed('verbose')) { + if (verbose) { print('App ID: $appId'); print('Token: $token'); } diff --git a/lib/sourcemap/sourcemap_command.dart b/lib/sourcemap/sourcemap_command.dart index 26f6bd4..3e60f74 100644 --- a/lib/sourcemap/sourcemap_command.dart +++ b/lib/sourcemap/sourcemap_command.dart @@ -17,12 +17,6 @@ ArgParser buildParserSourcemap() { negatable: false, help: 'Print sourcemap usage information.', ) - ..addFlag( - 'verbose', - abbr: 'v', - negatable: false, - help: 'Show additional command output.', - ) ..addOption( 'app-id', help: 'Raygun\'s application ID', @@ -62,7 +56,7 @@ void parseSourcemapCommand(ArgResults command, bool verbose) { print(buildParserSourcemap().usage); exit(0); } - final configProps = ConfigProps.load(command); + final configProps = ConfigProps.load(command, verbose: verbose); switch (command.option('platform')) { case null: diff --git a/lib/symbols/flutter_symbols.dart b/lib/symbols/flutter_symbols.dart index f459846..209189e 100644 --- a/lib/symbols/flutter_symbols.dart +++ b/lib/symbols/flutter_symbols.dart @@ -13,7 +13,7 @@ void parseSymbolsCommand(ArgResults command, bool verbose) { print(buildParserSymbols().usage); exit(0); } - final configProps = ConfigProps.load(command); + final configProps = ConfigProps.load(command, verbose: verbose); _run( command: command, appId: configProps.appId, @@ -82,12 +82,6 @@ ArgParser buildParserSymbols() { negatable: false, help: 'Print $kSymbolsCommand usage information.', ) - ..addFlag( - 'verbose', - abbr: 'v', - negatable: false, - help: 'Show additional command output.', - ) ..addOption( 'app-id', help: 'Raygun\'s application ID',