From 74e1674fdf3328893f0543f8e788cd8d074c127c Mon Sep 17 00:00:00 2001 From: Chen Asraf Date: Mon, 22 Jul 2024 01:40:47 +0300 Subject: [PATCH] chore: improve error logging --- bin/script_runner.dart | 16 ++++++++++------ lib/base.dart | 19 ++++++++++++------- lib/config.dart | 30 +++++++++++++++++++++--------- lib/runnable_script.dart | 13 +++++++++---- lib/utils.dart | 15 +++++++++++++-- pubspec.yaml | 17 ++++++++--------- test/config_test.dart | 3 ++- 7 files changed, 75 insertions(+), 38 deletions(-) diff --git a/bin/script_runner.dart b/bin/script_runner.dart index f05f257..eff4b60 100755 --- a/bin/script_runner.dart +++ b/bin/script_runner.dart @@ -6,7 +6,9 @@ import 'package:script_runner/utils.dart'; /// Main entrypoint for CMD script runner. Future main(List args) async { if (args.isEmpty) { - printColor('No script command provided. Use -h to see available commands.', [TerminalColor.red]); + printColor( + 'No script command provided. Use -h or -ls to see available commands.', + [TerminalColor.red]); return; } final scriptCmd = args.first; @@ -15,13 +17,15 @@ Future main(List args) async { final code = await runScript(scriptCmd, scriptArgs); io.exit(code); } catch (e, stack) { - if (e is ScriptStateError) { + if (e is ScriptError) { printColor(e.toString(), [TerminalColor.red]); - } else { - printColor('$e\n$stack', [TerminalColor.red]); - } - if (e is io.ProcessException) { + } else if (e is io.ProcessException) { + printColor( + 'Error in script "$scriptCmd": ${e.message}', [TerminalColor.red]); io.exit(e.errorCode); + } else { + printColor('Error executing script: $e\n$stack', [TerminalColor.red]); + io.exit(1); } } } diff --git a/lib/base.dart b/lib/base.dart index 819b346..ca54e5a 100644 --- a/lib/base.dart +++ b/lib/base.dart @@ -7,7 +7,7 @@ Future runScript(String entryName, List args) async { final config = await ScriptRunnerConfig.get(); if (config.scripts.isEmpty) { - throw ScriptStateError('No scripts found'); + throw ScriptNotFoundError('No scripts found'); } if (['-h', '--help'].contains(entryName)) { @@ -24,27 +24,32 @@ Future runScript(String entryName, List args) async { final entry = config.scriptsMap[entryName]; if (entry == null) { - final suggestions = - config.scriptsMap.keys.where((key) => key.toLowerCase().startsWith(entryName.toLowerCase())).toList(); + final suggestions = config.scriptsMap.keys + .where((key) => key.toLowerCase().startsWith(entryName.toLowerCase())) + .toList(); if (suggestions.isNotEmpty) { if (suggestions.length == 1) { - throw ScriptStateError( + throw ScriptNotFoundError( 'No script named "$entryName" found. Did you mean "${suggestions.single}"?', ); } else { - throw ScriptStateError( + throw ScriptNotFoundError( 'No script named "$entryName" found.\n' 'Did you mean one of: "${suggestions.join('", "')}"?', ); } } else { - throw ScriptStateError( + throw ScriptNotFoundError( 'No script named "$entryName" found.\n' 'Available scripts: ${config.scriptsMap.keys.join('", "')}', ); } } - return entry.run(args); + try { + return entry.run(args); + } catch (e, stack) { + throw ScriptError('Error running script "$entryName": $e\n$stack'); + } } diff --git a/lib/config.dart b/lib/config.dart index 3702a3e..cdc01c1 100644 --- a/lib/config.dart +++ b/lib/config.dart @@ -75,7 +75,8 @@ class ScriptRunnerConfig { final sourceMap = await _tryFindConfig(fs, startDir); if (sourceMap.isEmpty) { - throw ScriptStateError('Must provide scripts in either pubspec.yaml or script_runner.yaml'); + throw ScriptStateError( + 'Must provide scripts in either pubspec.yaml or script_runner.yaml'); } final source = sourceMap.values.first; @@ -100,7 +101,9 @@ class ScriptRunnerConfig { List? scriptsRaw, { FileSystem? fileSystem, }) { - final scripts = (scriptsRaw ?? []).map((script) => RunnableScript.fromMap(script, fileSystem: fileSystem)).toList(); + final scripts = (scriptsRaw ?? []) + .map((script) => RunnableScript.fromMap(script, fileSystem: fileSystem)) + .toList(); return scripts.map((s) => s..preloadScripts = scripts).toList(); } @@ -138,7 +141,8 @@ class ScriptRunnerConfig { (configSource?.isNotEmpty == true ? [ colorize(' on ', titleStyle), - colorize(configSource!, [...titleStyle, TerminalColor.underline]), + colorize( + configSource!, [...titleStyle, TerminalColor.underline]), colorize(':', titleStyle) ].join('') : ':'), @@ -165,10 +169,15 @@ class ScriptRunnerConfig { final filtered = search.isEmpty ? scripts - : scripts.where((scr) => [scr.name, scr.description].any((s) => s != null && s.contains(search))).toList(); + : scripts + .where((scr) => [scr.name, scr.description] + .any((s) => s != null && s.contains(search))) + .toList(); - final mapped = - filtered.map((scr) => TableRow(scr.name, scr.description ?? '\$ ${[scr.cmd, ...scr.args].join(' ')}')).toList(); + final mapped = filtered + .map((scr) => TableRow(scr.name, + scr.description ?? '\$ ${[scr.cmd, ...scr.args].join(' ')}')) + .toList(); final padLen = _getPadLen(mapped.map((r) => r.name).toList(), maxLen); @@ -180,7 +189,8 @@ class ScriptRunnerConfig { /// If [search] is provided, it filters the scripts to only those that contain the search string. void printBuiltins([String search = '']) { final builtins = [ - TableRow('-ls, --list [search]', 'List available scripts. Add search term to filter.'), + TableRow('-ls, --list [search]', + 'List available scripts. Add search term to filter.'), TableRow('-h, --help', 'Print this help message'), ]; @@ -197,7 +207,8 @@ class ScriptRunnerConfig { stripColors: true, wrapLine: (line) => colorize(line, [TerminalColor.gray]), ); - printColor(' ${scr.name.padRight(padLen, ' ')} ${lines.first}', [TerminalColor.yellow]); + printColor(' ${scr.name.padRight(padLen, ' ')} ${lines.first}', + [TerminalColor.yellow]); for (final line in lines.sublist(1)) { print(' ${''.padRight(padLen, ' ')} $line'); } @@ -205,7 +216,8 @@ class ScriptRunnerConfig { } } - static Future> _tryFindConfig(FileSystem fs, String startDir) async { + static Future> _tryFindConfig( + FileSystem fs, String startDir) async { final explorer = Unaconfig('script_runner', fs: fs); final config = await explorer.search(); if (config != null) { diff --git a/lib/runnable_script.dart b/lib/runnable_script.dart index 18056b1..170017a 100644 --- a/lib/runnable_script.dart +++ b/lib/runnable_script.dart @@ -100,7 +100,8 @@ class RunnableScript { env: map['env'] as Map? ?? {}, ); } catch (e) { - throw ScriptStateError('Failed to parse script, arguments: $map, $fileSystem. Error: $e'); + throw ScriptStateError( + 'Failed to parse script, arguments: $map, $fileSystem. Error: $e'); } } @@ -162,7 +163,8 @@ class RunnableScript { return exitCode; } - String _getScriptPath() => _fileSystem.path.join(_fileSystem.systemTempDirectory.path, 'script_runner_$name.sh'); + String _getScriptPath() => _fileSystem.path + .join(_fileSystem.systemTempDirectory.path, 'script_runner_$name.sh'); String _getScriptContents( ScriptRunnerConfig config, { @@ -182,8 +184,11 @@ class RunnableScript { ].join('\n'); case OS.linux: case OS.macos: - return [...preloadScripts.map((e) => "[[ ! \$(which ${e.name}) ]] && alias ${e.name}='scr ${e.name}'"), script] - .join('\n'); + return [ + ...preloadScripts.map((e) => + "[[ ! \$(which ${e.name}) ]] && alias ${e.name}='scr ${e.name}'"), + script + ].join('\n'); } } } diff --git a/lib/utils.dart b/lib/utils.dart index 93f60c4..01a4eac 100644 --- a/lib/utils.dart +++ b/lib/utils.dart @@ -135,9 +135,20 @@ class TerminalColor { static const TerminalColor underline = TerminalColor._(4); } -class ScriptStateError extends StateError { - ScriptStateError(super.message); +/// An error that occurs that is related to a script. +class ScriptError extends StateError { + ScriptError(super.message); @override String toString() => message; } + +/// An error that occurs during script execution. +class ScriptStateError extends ScriptError { + ScriptStateError(super.message); +} + +/// An error that occurs when a script is not found. +class ScriptNotFoundError extends ScriptError { + ScriptNotFoundError(super.message); +} diff --git a/pubspec.yaml b/pubspec.yaml index aabd989..7f280f5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -20,11 +20,12 @@ dev_dependencies: btool: any script_runner: - # line_length: 100 scripts: + # ================================================================================ # Real + # ================================================================================ - auto-fix: dart fix --apply - - publish: dart format .; dart pub publish; format + - publish: dart format .; dart pub publish - publish:dry: dart pub publish --dry-run - doc: dart doc - name: version @@ -33,15 +34,13 @@ script_runner: - name: 'version:set' cmd: dart run btool set packageVersion display_cmd: false - - format: dart format --line-length 120 . - - name: clean - cmd: rm -rf .dart_tool/pub/bin/script_runner/script_runner.dart-*.snapshot - - name: activate-local - cmd: scr clean && dart pub global deactivate script_runner; dart pub global activate --source path ./ - - name: activate-global - cmd: scr clean && dart pub global deactivate script_runner; dart pub global activate script_runner + - clean: rm -rf .dart_tool/pub/bin/script_runner/script_runner.dart-*.snapshot + - activate-local: scr clean && dart pub global deactivate script_runner; dart pub global activate --source path ./ + - activate-global: scr clean && dart pub global deactivate script_runner; dart pub global activate script_runner + # ================================================================================ # Examples + # ================================================================================ - name: echo1 cmd: echo "Hello World" $SHELL description: Interdum a scelerisque arcu felis taciti ligula pellentesque curabitur, suspendisse adipiscing quisque sed luctus elementum in imperdiet id, praesent enim sem justo sapien diam nec. Quisque erat risus sagittis penatibus per, vehicula sociosqu cubilia convallis, sollicitudin scelerisque cras aptent. Natoque ornare dictumst netus litora mollis suspendisse cubilia proin morbi primis consequat eu massa, cursus non urna ridiculus dolor duis tempus ut nam velit lacus viverra. A interdum senectus eu mus leo aptent facilisi augue tristique ante purus condimentum pulvinar porta viverra morbi, et tellus gravida porttitor non euismod suscipit neque egestas praesent arcu luctus pharetra fusce. Luctus mauris a venenatis tempus cras ante efficitur massa ultricies mollis lacus, volutpat nisi lacinia himenaeos facilisi in aliquet sodales purus integer vitae quisque, libero torquent enim mattis placerat tortor mi dignissim viverra sem. diff --git a/test/config_test.dart b/test/config_test.dart index e555649..94fd00d 100644 --- a/test/config_test.dart +++ b/test/config_test.dart @@ -184,7 +184,8 @@ Future _writeCustomConf(FileSystem fs, [String? contents]) async { final homeDir = fs.directory(Platform.environment['HOME']!); homeDir.create(recursive: true); fs.currentDirectory = homeDir; - final pubFile = fs.file(path.join(fs.currentDirectory.path, 'script_runner.yaml')); + final pubFile = + fs.file(path.join(fs.currentDirectory.path, 'script_runner.yaml')); pubFile.create(recursive: true); print('writing custom conf to ${pubFile.path}'); await pubFile.writeAsString(