From b88d848650a9da5abc5498eeed7cf6433a218ad9 Mon Sep 17 00:00:00 2001 From: Jonas Sander <29028262+Jonas-Sander@users.noreply.github.com> Date: Fri, 20 Oct 2023 16:09:27 +0200 Subject: [PATCH 1/8] Add `sz exec` command. The `sz exec` command runs a custom command for all / multiple packages. E.g. `sz exec -- fvm dart fix --apply`. The flags `onlyDart` and `onlyFlutter` can be used to run the command only in pure Dart or only Flutter packages. E.g. `sz exec -onlyDart -- fvm dart pub get`. --- .../lib/src/commands/commands.dart | 1 + .../lib/src/commands/src/exec_command.dart | 68 +++++++++++++++++++ tools/sz_repo_cli/lib/src/main.dart | 1 + 3 files changed, 70 insertions(+) create mode 100644 tools/sz_repo_cli/lib/src/commands/src/exec_command.dart diff --git a/tools/sz_repo_cli/lib/src/commands/commands.dart b/tools/sz_repo_cli/lib/src/commands/commands.dart index ebd9bca09..12bdd744c 100644 --- a/tools/sz_repo_cli/lib/src/commands/commands.dart +++ b/tools/sz_repo_cli/lib/src/commands/commands.dart @@ -10,6 +10,7 @@ export 'src/analyze_command.dart' show AnalyzeCommand; export 'src/deploy_command.dart'; export 'src/deploy_web_app_command.dart'; export 'src/do_stuff_command.dart'; +export 'src/exec_command.dart'; export 'src/fix_comment_spacing_command.dart' show FixCommentSpacingCommand; export 'src/locate_sharezone_flutter_directory_command.dart'; export 'src/pub_command.dart'; diff --git a/tools/sz_repo_cli/lib/src/commands/src/exec_command.dart b/tools/sz_repo_cli/lib/src/commands/src/exec_command.dart new file mode 100644 index 000000000..343a7ff4e --- /dev/null +++ b/tools/sz_repo_cli/lib/src/commands/src/exec_command.dart @@ -0,0 +1,68 @@ +// Copyright (c) 2022 Sharezone UG (haftungsbeschränkt) +// Licensed under the EUPL-1.2-or-later. +// +// You may obtain a copy of the Licence at: +// https://joinup.ec.europa.eu/software/page/eupl +// +// SPDX-License-Identifier: EUPL-1.2 + +import 'dart:async'; + +import 'package:sz_repo_cli/src/common/common.dart'; + +class ExecCommand extends ConcurrentCommand { + ExecCommand(SharezoneRepo repo) : super(repo) { + argParser + ..addFlag('onlyFlutter', + help: 'Only run the command for Flutter packages.', defaultsTo: false) + ..addFlag('onlyDart', + help: 'Only run the command for Dart packages.', defaultsTo: false); + } + + @override + final String name = 'exec'; + + @override + final String description = 'Runs a given command for all/multiple packages.'; + + @override + Duration get defaultPackageTimeout => const Duration(minutes: 5); + + bool get onlyFlutter => argResults!['onlyFlutter'] as bool; + bool get onlyDart => argResults!['onlyDart'] as bool; + + @override + Stream get packagesToProcess { + List testFuncs = []; + if (onlyDart) testFuncs.add((package) => package.isPureDartPackage); + if (onlyFlutter) testFuncs.add((package) => package.isFlutterPackage); + return super.packagesToProcess.where((event) { + for (var testFunc in testFuncs) { + if (testFunc(event)) { + continue; + } else { + return false; + } + } + return true; + }); + } + + @override + Future runSetup() async { + if (argResults!.rest.isEmpty) { + throw ArgumentError( + 'No command given. Please provide a command like this:\n' + 'sz_repo_cli exec --onlyFlutter -- fvm dart fix --apply'); + } + } + + @override + Future runTaskForPackage(Package package) async { + await runProcessSuccessfullyOrThrow( + argResults!.rest.first, + argResults!.rest.sublist(1), + workingDirectory: package.path, + ); + } +} diff --git a/tools/sz_repo_cli/lib/src/main.dart b/tools/sz_repo_cli/lib/src/main.dart index 27c256b23..e9caa4d29 100644 --- a/tools/sz_repo_cli/lib/src/main.dart +++ b/tools/sz_repo_cli/lib/src/main.dart @@ -62,6 +62,7 @@ Future main(List args) async { ..addSubcommand(BuildMacOsCommand(repo)) ..addSubcommand(BuildWebCommand(repo)) ..addSubcommand(BuildIosCommand(repo))) + ..addCommand(ExecCommand(repo)) ..addCommand(BuildRunnerCommand()..addSubcommand(BuildRunnerBuild(repo))); await commandRunner.run(args).catchError((Object e) { From c945e01501d70286e2de80fe5b62229bfb50630f Mon Sep 17 00:00:00 2001 From: Jonas Sander <29028262+Jonas-Sander@users.noreply.github.com> Date: Fri, 20 Oct 2023 21:10:25 +0200 Subject: [PATCH 2/8] Use `package:process_runner`. --- .../lib/src/commands/commands.dart | 1 - .../src/add_license_headers_command.dart | 7 +- .../lib/src/commands/src/analyze_command.dart | 30 +++--- .../commands/src/build_android_command.dart | 10 +- .../src/commands/src/build_ios_command.dart | 10 +- .../src/commands/src/build_macos_command.dart | 10 +- .../src/build_runner_build_command.dart | 8 +- .../src/commands/src/build_web_command.dart | 10 +- .../src/check_license_headers_command.dart | 11 +- .../commands/src/deploy_android_command.dart | 72 +++++++------ .../src/commands/src/deploy_ios_command.dart | 22 ++-- .../commands/src/deploy_macos_command.dart | 28 +++-- .../commands/src/deploy_web_app_command.dart | 100 +++++++++--------- .../src/fix_comment_spacing_command.dart | 1 - .../lib/src/commands/src/format_command.dart | 24 +++-- ...e_sharezone_flutter_directory_command.dart | 100 ------------------ .../lib/src/commands/src/pub_get_command.dart | 31 +++--- .../lib/src/commands/src/test_command.dart | 37 ++++--- tools/sz_repo_cli/lib/src/common/common.dart | 13 +-- .../common/src/app_store_connect_utils.dart | 70 ++++++------ .../src/common/src/concurrent_command.dart | 4 +- .../lib/src/common/src/run_process.dart | 93 ---------------- .../src/run_source_of_truth_command.dart | 15 ++- .../throw_if_command_is_not_installed.dart | 11 +- tools/sz_repo_cli/lib/src/main.dart | 44 +++++--- tools/sz_repo_cli/pubspec.lock | 24 +++++ tools/sz_repo_cli/pubspec.yaml | 2 + .../test/ensure_cmd_sot_file_exists_test.dart | 3 +- 28 files changed, 347 insertions(+), 444 deletions(-) delete mode 100644 tools/sz_repo_cli/lib/src/commands/src/locate_sharezone_flutter_directory_command.dart delete mode 100644 tools/sz_repo_cli/lib/src/common/src/run_process.dart diff --git a/tools/sz_repo_cli/lib/src/commands/commands.dart b/tools/sz_repo_cli/lib/src/commands/commands.dart index ebd9bca09..01abdbf7f 100644 --- a/tools/sz_repo_cli/lib/src/commands/commands.dart +++ b/tools/sz_repo_cli/lib/src/commands/commands.dart @@ -11,7 +11,6 @@ export 'src/deploy_command.dart'; export 'src/deploy_web_app_command.dart'; export 'src/do_stuff_command.dart'; export 'src/fix_comment_spacing_command.dart' show FixCommentSpacingCommand; -export 'src/locate_sharezone_flutter_directory_command.dart'; export 'src/pub_command.dart'; export 'src/pub_get_command.dart' show PubGetCommand; export 'src/test_command.dart'; diff --git a/tools/sz_repo_cli/lib/src/commands/src/add_license_headers_command.dart b/tools/sz_repo_cli/lib/src/commands/src/add_license_headers_command.dart index dcc6842aa..67a15a580 100644 --- a/tools/sz_repo_cli/lib/src/commands/src/add_license_headers_command.dart +++ b/tools/sz_repo_cli/lib/src/commands/src/add_license_headers_command.dart @@ -10,15 +10,17 @@ import 'dart:async'; import 'dart:io'; import 'package:args/command_runner.dart'; +import 'package:process_runner/process_runner.dart'; import 'package:sz_repo_cli/src/commands/src/check_license_headers_command.dart'; import 'package:sz_repo_cli/src/common/common.dart'; /// Add license headers to all files without one. class AddLicenseHeadersCommand extends Command { - AddLicenseHeadersCommand(this.repo); - + final ProcessRunner processRunner; final SharezoneRepo repo; + AddLicenseHeadersCommand(this.processRunner, this.repo); + @override String get description => 'Add the necessary license headers to all files that do not have them yet.'; @@ -29,6 +31,7 @@ class AddLicenseHeadersCommand extends Command { @override Future run() async { final results = await runLicenseHeaderCommand( + processRunner, commandKey: 'add_license_headers', repo: repo, ); diff --git a/tools/sz_repo_cli/lib/src/commands/src/analyze_command.dart b/tools/sz_repo_cli/lib/src/commands/src/analyze_command.dart index 14bea6f76..5bd5a74b1 100644 --- a/tools/sz_repo_cli/lib/src/commands/src/analyze_command.dart +++ b/tools/sz_repo_cli/lib/src/commands/src/analyze_command.dart @@ -8,6 +8,7 @@ import 'dart:async'; +import 'package:process_runner/process_runner.dart'; import 'package:sz_repo_cli/src/commands/src/format_command.dart'; import 'package:sz_repo_cli/src/common/common.dart'; @@ -15,7 +16,7 @@ import 'fix_comment_spacing_command.dart'; import 'pub_get_command.dart'; class AnalyzeCommand extends ConcurrentCommand { - AnalyzeCommand(SharezoneRepo repo) : super(repo); + AnalyzeCommand(super.processRunner, super.repo); @override final String name = 'analyze'; @@ -28,23 +29,28 @@ class AnalyzeCommand extends ConcurrentCommand { Duration get defaultPackageTimeout => const Duration(minutes: 5); @override - Future runTaskForPackage(Package package) => analyzePackage(package); + Future runTaskForPackage(Package package) => + analyzePackage(processRunner, package); } -Future analyzePackage(Package package) async { - await getPackage(package); - await _runDartAnalyze(package); - await formatCode(package, throwIfCodeChanged: true); - await _checkForCommentsWithBadSpacing(package); +Future analyzePackage( + ProcessRunner processRunner, Package package) async { + await getPackage(processRunner, package); + await _runDartAnalyze(processRunner, package); + await formatCode(processRunner, package, throwIfCodeChanged: true); + await _checkForCommentsWithBadSpacing(processRunner, package); } -Future _runDartAnalyze(Package package) { - return runProcessSuccessfullyOrThrow( - 'fvm', ['dart', 'analyze', '--fatal-infos', '--fatal-warnings'], - workingDirectory: package.path); +Future _runDartAnalyze( + ProcessRunner processRunner, Package package) async { + await processRunner.runProcess( + ['fvm', 'dart', 'analyze', '--fatal-infos', '--fatal-warnings'], + workingDirectory: package.location, + ); } -Future _checkForCommentsWithBadSpacing(Package package) async { +Future _checkForCommentsWithBadSpacing( + ProcessRunner processRunner, Package package) async { if (doesPackageIncludeFilesWithBadCommentSpacing(package.path)) { throw Exception( 'Package ${package.name} has comments with bad spacing. Fix them by running the `sz fix-comment-spacing` command.'); diff --git a/tools/sz_repo_cli/lib/src/commands/src/build_android_command.dart b/tools/sz_repo_cli/lib/src/commands/src/build_android_command.dart index 530e8aa96..a05f38e66 100644 --- a/tools/sz_repo_cli/lib/src/commands/src/build_android_command.dart +++ b/tools/sz_repo_cli/lib/src/commands/src/build_android_command.dart @@ -9,6 +9,7 @@ import 'dart:io'; import 'package:args/command_runner.dart'; +import 'package:process_runner/process_runner.dart'; import 'package:sz_repo_cli/src/common/common.dart'; import 'package:sz_repo_cli/src/common/src/build_utils.dart'; @@ -31,9 +32,10 @@ final _androidOutputType = [ ]; class BuildAndroidCommand extends Command { + final ProcessRunner processRunner; final SharezoneRepo _repo; - BuildAndroidCommand(this._repo) { + BuildAndroidCommand(this.processRunner, this._repo) { argParser ..addOption( releaseStageOptionName, @@ -94,9 +96,9 @@ When none is specified, the value from pubspec.yaml is used.''', final buildNumber = argResults![buildNumberOptionName] as String?; final buildNameWithStage = getBuildNameWithStage(_repo.sharezoneFlutterApp, stage); - await runProcessSuccessfullyOrThrow( - 'fvm', + await processRunner.runProcess( [ + 'fvm', 'flutter', 'build', outputType, @@ -110,7 +112,7 @@ When none is specified, the value from pubspec.yaml is used.''', if (buildNumber != null) ...['--build-number', buildNumber], if (stage != 'stable') ...['--build-name', buildNameWithStage] ], - workingDirectory: _repo.sharezoneFlutterApp.location.path, + workingDirectory: _repo.sharezoneFlutterApp.location, ); } catch (e) { throw Exception('Failed to build Android app: $e'); diff --git a/tools/sz_repo_cli/lib/src/commands/src/build_ios_command.dart b/tools/sz_repo_cli/lib/src/commands/src/build_ios_command.dart index 52ba44f90..775809f2a 100644 --- a/tools/sz_repo_cli/lib/src/commands/src/build_ios_command.dart +++ b/tools/sz_repo_cli/lib/src/commands/src/build_ios_command.dart @@ -9,6 +9,7 @@ import 'dart:io'; import 'package:args/command_runner.dart'; +import 'package:process_runner/process_runner.dart'; import 'package:sz_repo_cli/src/common/common.dart'; final _iosStages = [ @@ -24,9 +25,10 @@ final _iosFlavors = [ ]; class BuildIosCommand extends Command { + final ProcessRunner processRunner; final SharezoneRepo _repo; - BuildIosCommand(this._repo) { + BuildIosCommand(this.processRunner, this._repo) { argParser ..addOption( releaseStageOptionName, @@ -84,9 +86,9 @@ When none is specified, the value from pubspec.yaml is used.''', final stage = argResults![releaseStageOptionName] as String; final buildNumber = argResults![buildNumberOptionName] as String?; final exportOptionsPlist = argResults![exportOptionsPlistName] as String?; - await runProcessSuccessfullyOrThrow( - 'fvm', + await processRunner.runProcess( [ + 'fvm', 'flutter', 'build', 'ipa', @@ -110,7 +112,7 @@ When none is specified, the value from pubspec.yaml is used.''', exportOptionsPlist ], ], - workingDirectory: _repo.sharezoneFlutterApp.location.path, + workingDirectory: _repo.sharezoneFlutterApp.location, ); } catch (e) { throw Exception('Failed to build iOS app: $e'); diff --git a/tools/sz_repo_cli/lib/src/commands/src/build_macos_command.dart b/tools/sz_repo_cli/lib/src/commands/src/build_macos_command.dart index 289bdd7b6..958fb445c 100644 --- a/tools/sz_repo_cli/lib/src/commands/src/build_macos_command.dart +++ b/tools/sz_repo_cli/lib/src/commands/src/build_macos_command.dart @@ -9,6 +9,7 @@ import 'dart:io'; import 'package:args/command_runner.dart'; +import 'package:process_runner/process_runner.dart'; import 'package:sz_repo_cli/src/common/common.dart'; final _macOsStages = [ @@ -17,9 +18,10 @@ final _macOsStages = [ ]; class BuildMacOsCommand extends Command { + final ProcessRunner processRunner; final SharezoneRepo _repo; - BuildMacOsCommand(this._repo) { + BuildMacOsCommand(this.processRunner, this._repo) { argParser ..addOption( releaseStageOptionName, @@ -63,9 +65,9 @@ When none is specified, the value from pubspec.yaml is used.''', const flavor = 'prod'; final stage = argResults![releaseStageOptionName] as String; final buildNumber = argResults![buildNumberOptionName] as String?; - await runProcessSuccessfullyOrThrow( - 'fvm', + await processRunner.runProcess( [ + 'fvm', 'flutter', 'build', 'macos', @@ -83,7 +85,7 @@ When none is specified, the value from pubspec.yaml is used.''', // * https://github.com/flutter/flutter/issues/27589#issuecomment-573121390 // * https://github.com/flutter/flutter/issues/115483 ], - workingDirectory: _repo.sharezoneFlutterApp.location.path, + workingDirectory: _repo.sharezoneFlutterApp.location, ); } catch (e) { throw Exception('Failed to build macOS app: $e'); diff --git a/tools/sz_repo_cli/lib/src/commands/src/build_runner_build_command.dart b/tools/sz_repo_cli/lib/src/commands/src/build_runner_build_command.dart index 576ffcd40..3cf2905ba 100644 --- a/tools/sz_repo_cli/lib/src/commands/src/build_runner_build_command.dart +++ b/tools/sz_repo_cli/lib/src/commands/src/build_runner_build_command.dart @@ -11,7 +11,7 @@ import 'dart:async'; import 'package:sz_repo_cli/src/common/common.dart'; class BuildRunnerBuild extends ConcurrentCommand { - BuildRunnerBuild(SharezoneRepo repo) : super(repo); + BuildRunnerBuild(super.processRunner, super.repo); @override final String name = 'build'; @@ -35,9 +35,9 @@ class BuildRunnerBuild extends ConcurrentCommand { @override Future runTaskForPackage(Package package) async { - await runProcessSuccessfullyOrThrow( - 'fvm', + await processRunner.runProcess( [ + 'fvm', 'dart', 'run', 'build_runner', @@ -53,7 +53,7 @@ class BuildRunnerBuild extends ConcurrentCommand { // Therefore, we hard code it to delete the conflicting outputs. '--delete-conflicting-outputs', ], - workingDirectory: package.path, + workingDirectory: package.location, ); } } diff --git a/tools/sz_repo_cli/lib/src/commands/src/build_web_command.dart b/tools/sz_repo_cli/lib/src/commands/src/build_web_command.dart index 034b4c3cd..ac8c52f80 100644 --- a/tools/sz_repo_cli/lib/src/commands/src/build_web_command.dart +++ b/tools/sz_repo_cli/lib/src/commands/src/build_web_command.dart @@ -9,6 +9,7 @@ import 'dart:io'; import 'package:args/command_runner.dart'; +import 'package:process_runner/process_runner.dart'; import 'package:sz_repo_cli/src/common/common.dart'; import 'package:sz_repo_cli/src/common/src/build_utils.dart'; @@ -25,9 +26,10 @@ final _webFlavors = [ ]; class BuildWebCommand extends Command { + final ProcessRunner processRunner; final SharezoneRepo _repo; - BuildWebCommand(this._repo) { + BuildWebCommand(this.processRunner, this._repo) { argParser ..addOption( releaseStageOptionName, @@ -70,9 +72,9 @@ class BuildWebCommand extends Command { final stage = argResults![releaseStageOptionName] as String; final buildNameWithStage = getBuildNameWithStage(_repo.sharezoneFlutterApp, stage); - await runProcessSuccessfullyOrThrow( - 'fvm', + await processRunner.runProcess( [ + 'fvm', 'flutter', 'build', 'web', @@ -85,7 +87,7 @@ class BuildWebCommand extends Command { 'DEVELOPMENT_STAGE=${stage.toUpperCase()}', if (stage != 'stable') ...['--build-name', buildNameWithStage] ], - workingDirectory: _repo.sharezoneFlutterApp.location.path, + workingDirectory: _repo.sharezoneFlutterApp.location, ); } catch (e) { throw Exception('Failed to build web app: $e'); diff --git a/tools/sz_repo_cli/lib/src/commands/src/check_license_headers_command.dart b/tools/sz_repo_cli/lib/src/commands/src/check_license_headers_command.dart index 60d2f0561..6887f1af8 100644 --- a/tools/sz_repo_cli/lib/src/commands/src/check_license_headers_command.dart +++ b/tools/sz_repo_cli/lib/src/commands/src/check_license_headers_command.dart @@ -10,15 +10,17 @@ import 'dart:async'; import 'dart:io'; import 'package:args/command_runner.dart'; +import 'package:process_runner/process_runner.dart'; import 'package:sz_repo_cli/src/common/common.dart'; import 'package:sz_repo_cli/src/common/src/run_source_of_truth_command.dart'; /// Check that all files have correct license headers. class CheckLicenseHeadersCommand extends Command { - CheckLicenseHeadersCommand(this.repo); - + final ProcessRunner processRunner; final SharezoneRepo repo; + CheckLicenseHeadersCommand(this.processRunner, this.repo); + @override String get description => 'Check that all files have the necessary license headers.'; @@ -29,6 +31,7 @@ class CheckLicenseHeadersCommand extends Command { @override Future run() async { final results = await runLicenseHeaderCommand( + processRunner, commandKey: 'check_license_headers', repo: repo, ); @@ -47,11 +50,13 @@ class CheckLicenseHeadersCommand extends Command { /// Run a license header command via a key. /// /// The key can be seen in the [repo.commandsSourceOfTruthYamlFile] file. -Future runLicenseHeaderCommand({ +Future runLicenseHeaderCommand( + ProcessRunner processRunner, { required String commandKey, required SharezoneRepo repo, }) { return runSourceOfTruthCommand( + processRunner, commandKey: commandKey, repo: repo, argumentsToAppend: [repo.location.path], diff --git a/tools/sz_repo_cli/lib/src/commands/src/deploy_android_command.dart b/tools/sz_repo_cli/lib/src/commands/src/deploy_android_command.dart index 0a8ef61d8..0cea3817b 100644 --- a/tools/sz_repo_cli/lib/src/commands/src/deploy_android_command.dart +++ b/tools/sz_repo_cli/lib/src/commands/src/deploy_android_command.dart @@ -9,7 +9,9 @@ import 'dart:io'; import 'package:args/command_runner.dart'; +import 'package:process_runner/process_runner.dart'; import 'package:sz_repo_cli/src/common/common.dart'; +import 'package:path/path.dart' as path; final _androidStages = [ 'stable', @@ -22,9 +24,10 @@ final _androidFlavors = [ ]; class DeployAndroidCommand extends Command { + final ProcessRunner processRunner; final SharezoneRepo _repo; - DeployAndroidCommand(this._repo) { + DeployAndroidCommand(this.processRunner, this._repo) { argParser ..addOption( releaseStageOptionName, @@ -75,7 +78,7 @@ class DeployAndroidCommand extends Command { @override Future run() async { _throwIfFlavorIsNotSupportForDeployment(); - _checkIfGooglePlayCredentialsAreValid(); + _checkIfGooglePlayCredentialsAreValid(processRunner); // Is used so that runProcess commands print the command that was run. Right // now this can't be done via an argument. @@ -83,7 +86,7 @@ class DeployAndroidCommand extends Command { // This workaround should be addressed in the future. isVerbose = true; - final buildNumber = await _getNextBuildNumber(); + final buildNumber = await _getNextBuildNumber(processRunner); await _buildApp(buildNumber: buildNumber); await _publish(); @@ -102,28 +105,31 @@ class DeployAndroidCommand extends Command { /// Checks if Fastlane can establish a connection to Google Play. /// /// See https://docs.fastlane.tools/actions/validate_play_store_json_key - Future _checkIfGooglePlayCredentialsAreValid() async { - await runProcessSuccessfullyOrThrow( - 'fastlane', - ['run', 'validate_play_store_json_key'], - workingDirectory: '${_repo.sharezoneFlutterApp.location.path}/android', + Future _checkIfGooglePlayCredentialsAreValid( + ProcessRunner processRunner) async { + await processRunner.runProcess( + ['fastlane', 'run', 'validate_play_store_json_key'], + workingDirectory: Directory( + path.join(_repo.sharezoneFlutterApp.location.path, 'android')), ); } - Future _getNextBuildNumber() async { - final latestBuildNumber = await _getLatestBuildNumberFromGooglePlay(); + Future _getNextBuildNumber(ProcessRunner processRunner) async { + final latestBuildNumber = + await _getLatestBuildNumberFromGooglePlay(processRunner); final nextBuildNumber = latestBuildNumber + 1; stdout.writeln('Next build number: $nextBuildNumber'); return nextBuildNumber; } /// Returns the latest build number from Google Play across all tracks. - Future _getLatestBuildNumberFromGooglePlay() async { + Future _getLatestBuildNumberFromGooglePlay( + ProcessRunner processRunner) async { try { const packageName = 'de.codingbrain.sharezone'; - final result = await runProcess( - 'google-play', + final result = await processRunner.runProcess( [ + 'google-play', 'get-latest-build-number', '--package-name', packageName, @@ -139,9 +145,9 @@ class DeployAndroidCommand extends Command { try { final flavor = argResults![flavorOptionName] as String; final stage = argResults![releaseStageOptionName] as String; - await runProcessSuccessfullyOrThrow( - 'fvm', + await processRunner.runProcess( [ + 'fvm', 'dart', 'run', 'sz_repo_cli', @@ -154,7 +160,7 @@ class DeployAndroidCommand extends Command { '--build-number', '$buildNumber', ], - workingDirectory: _repo.sharezoneCiCdTool.path, + workingDirectory: _repo.sharezoneCiCdTool.location, ); } catch (e) { throw Exception('Failed to build Android app: $e'); @@ -170,6 +176,7 @@ class DeployAndroidCommand extends Command { _printRolloutPercentage(rolloutPercentage); await _uploadToGooglePlay( + processRunner, track: _getGooglePlayTrackFromStage(), rollout: rolloutPercentage, ); @@ -217,24 +224,27 @@ class DeployAndroidCommand extends Command { } } - Future _uploadToGooglePlay({ + Future _uploadToGooglePlay( + ProcessRunner processRunner, { required String track, required String rollout, }) async { - await runProcess( - 'fastlane', - ['deploy'], - workingDirectory: '${_repo.sharezoneFlutterApp.location.path}/android', - environment: { - 'TRACK': track, - 'ROLLOUT': rollout, - // Sets the number of retries for uploading the app bundle to Google - // Play. This is needed because sometimes the upload fails for unknown - // reasons. - // - // See: https://github.com/fastlane/fastlane/issues/21507#issuecomment-1723116829 - 'SUPPLY_UPLOAD_MAX_RETRIES': '5', - }, + // Not sure if it is wrong usage of the environment variable (i.e. we + // shouldn't modify it in this way but rather create a new/modified + // ProcessRunner). + processRunner.environment['TRACK'] = track; + processRunner.environment['ROLLOUT'] = rollout; + // Sets the number of retries for uploading the app bundle to Google + // Play. This is needed because sometimes the upload fails for unknown + // reasons. + // + // See: https://github.com/fastlane/fastlane/issues/21507#issuecomment-1723116829 + processRunner.environment['SUPPLY_UPLOAD_MAX_RETRIES'] = '5'; + + await processRunner.runProcess( + ['fastlane', 'deploy'], + workingDirectory: Directory( + path.join(_repo.sharezoneFlutterApp.location.path, '/android')), ); } diff --git a/tools/sz_repo_cli/lib/src/commands/src/deploy_ios_command.dart b/tools/sz_repo_cli/lib/src/commands/src/deploy_ios_command.dart index 8cfe621ac..2e5398d75 100644 --- a/tools/sz_repo_cli/lib/src/commands/src/deploy_ios_command.dart +++ b/tools/sz_repo_cli/lib/src/commands/src/deploy_ios_command.dart @@ -9,6 +9,7 @@ import 'dart:io'; import 'package:args/command_runner.dart'; +import 'package:process_runner/process_runner.dart'; import 'package:sz_repo_cli/src/common/common.dart'; import 'package:sz_repo_cli/src/common/src/app_store_connect_utils.dart'; import 'package:sz_repo_cli/src/common/src/apple_track.dart'; @@ -50,9 +51,10 @@ final _iosFlavors = [ /// environment variables (only applies for some of them). If any required /// argument is missing, the deployment will fail. class DeployIosCommand extends Command { + final ProcessRunner processRunner; final SharezoneRepo _repo; - DeployIosCommand(this._repo) { + DeployIosCommand(this.processRunner, this._repo) { argParser ..addOption( releaseStageOptionName, @@ -96,7 +98,7 @@ class DeployIosCommand extends Command { @override Future run() async { _throwIfFlavorIsNotSupportForDeployment(); - await throwIfCodemagicCliToolsAreNotInstalled(); + await throwIfCodemagicCliToolsAreNotInstalled(processRunner); // Is used so that runProcess commands print the command that was run. Right // now this can't be done via an argument. @@ -108,6 +110,7 @@ class DeployIosCommand extends Command { try { await setUpSigning( + processRunner, config: AppleSigningConfig.create( argResults: argResults!, environment: Platform.environment, @@ -123,6 +126,7 @@ class DeployIosCommand extends Command { ); final buildNumber = await getNextBuildNumberFromAppStoreConnect( + processRunner, appStoreConnectConfig: appStoreConnectConfig, platform: platform, // Using the app location as working directory because the default @@ -130,8 +134,9 @@ class DeployIosCommand extends Command { // app/private_keys/AuthKey_{keyIdentifier}.p8. workingDirectory: _repo.sharezoneFlutterApp.path, ); - await _buildApp(buildNumber: buildNumber); + await _buildApp(processRunner, buildNumber: buildNumber); await publishToAppStoreConnect( + processRunner, appStoreConnectConfig: appStoreConnectConfig, stage: argResults![releaseStageOptionName] as String, whatsNew: argResults![whatsNewOptionName] as String?, @@ -142,7 +147,7 @@ class DeployIosCommand extends Command { } finally { // Fixes potential authentication issues after running keychain commands. // Only really necessary when running on local machines. - await keychainUseLogin(); + await keychainUseLogin(processRunner); } stdout.writeln('Deployment finished 🎉 '); @@ -157,14 +162,15 @@ class DeployIosCommand extends Command { } } - Future _buildApp({required int buildNumber}) async { + Future _buildApp(ProcessRunner processRunner, + {required int buildNumber}) async { try { final flavor = argResults![flavorOptionName] as String; final stage = argResults![releaseStageOptionName] as String; final exportOptionsPlist = argResults![exportOptionsPlistName] as String?; - await runProcessSuccessfullyOrThrow( - 'fvm', + await processRunner.runProcess( [ + 'fvm', 'dart', 'run', 'sz_repo_cli', @@ -181,7 +187,7 @@ class DeployIosCommand extends Command { exportOptionsPlist, ], ], - workingDirectory: _repo.sharezoneCiCdTool.path, + workingDirectory: _repo.sharezoneCiCdTool.location, ); } catch (e) { throw Exception('Failed to build iOS app: $e'); diff --git a/tools/sz_repo_cli/lib/src/commands/src/deploy_macos_command.dart b/tools/sz_repo_cli/lib/src/commands/src/deploy_macos_command.dart index 19bf34791..817105cb9 100644 --- a/tools/sz_repo_cli/lib/src/commands/src/deploy_macos_command.dart +++ b/tools/sz_repo_cli/lib/src/commands/src/deploy_macos_command.dart @@ -15,6 +15,7 @@ import 'dart:io'; import 'package:args/command_runner.dart'; +import 'package:process_runner/process_runner.dart'; import 'package:sz_repo_cli/src/common/common.dart'; import 'package:sz_repo_cli/src/common/src/app_store_connect_utils.dart'; import 'package:sz_repo_cli/src/common/src/apple_track.dart'; @@ -48,9 +49,10 @@ final _macOsStageToTracks = { /// environment variables (only applies for some of them). If any required /// argument is missing, the deployment will fail. class DeployMacOsCommand extends Command { + final ProcessRunner processRunner; final SharezoneRepo _repo; - DeployMacOsCommand(this._repo) { + DeployMacOsCommand(this.processRunner, this._repo) { argParser.addOption( releaseStageOptionName, abbr: 's', @@ -78,7 +80,7 @@ class DeployMacOsCommand extends Command { @override Future run() async { - await throwIfCodemagicCliToolsAreNotInstalled(); + await throwIfCodemagicCliToolsAreNotInstalled(processRunner); // Is used so that runProcess commands print the command that was run. Right // now this can't be done via an argument. @@ -90,6 +92,7 @@ class DeployMacOsCommand extends Command { try { await setUpSigning( + processRunner, config: AppleSigningConfig.create( argResults: argResults!, environment: Platform.environment, @@ -110,6 +113,7 @@ class DeployMacOsCommand extends Command { ); final buildNumber = await getNextBuildNumberFromAppStoreConnect( + processRunner, appStoreConnectConfig: appStoreConnectConfig, platform: platform, // Using the app location as working directory because the default @@ -118,10 +122,11 @@ class DeployMacOsCommand extends Command { workingDirectory: _repo.sharezoneFlutterApp.path, ); - await _buildApp(buildNumber: buildNumber); + await _buildApp(processRunner, buildNumber: buildNumber); await _createSignedPackage(); await publishToAppStoreConnect( + processRunner, appStoreConnectConfig: appStoreConnectConfig, stage: argResults![releaseStageOptionName] as String, whatsNew: argResults![whatsNewOptionName] as String?, @@ -133,16 +138,17 @@ class DeployMacOsCommand extends Command { } finally { // Fixes potential authentication issues after running keychain commands. // Only really necessary when running on local machines. - await keychainUseLogin(); + await keychainUseLogin(processRunner); } } - Future _buildApp({required int buildNumber}) async { + Future _buildApp(ProcessRunner processRunner, + {required int buildNumber}) async { try { final stage = argResults![releaseStageOptionName] as String; - await runProcessSuccessfullyOrThrow( - 'fvm', + await processRunner.runProcess( [ + 'fvm', 'dart', 'run', 'sz_repo_cli', @@ -153,7 +159,7 @@ class DeployMacOsCommand extends Command { '--build-number', '$buildNumber', ], - workingDirectory: _repo.sharezoneCiCdTool.path, + workingDirectory: _repo.sharezoneCiCdTool.location, ); } catch (e) { throw Exception('Failed to build macOS app: $e'); @@ -168,9 +174,9 @@ class DeployMacOsCommand extends Command { /// The steps are copied from the Flutter docs. You can find more details /// here: https://docs.flutter.dev/deployment/macos#create-a-build-archive-with-codemagic-cli-tools Future _createSignedPackage() async { - await runProcessSuccessfullyOrThrow( - 'bash', + await processRunner.runProcess( [ + 'bash', '-c', '''APP_NAME=\$(find \$(pwd) -name "*.app") && \ PACKAGE_NAME=\$(basename "\$APP_NAME" .app).pkg && \ @@ -179,7 +185,7 @@ INSTALLER_CERT_NAME=\$(keychain list-certificates | jq -r '.[] | select(.common_ xcrun productsign --sign "\$INSTALLER_CERT_NAME" unsigned.pkg "\$PACKAGE_NAME" && \ rm -f unsigned.pkg''' ], - workingDirectory: _repo.sharezoneFlutterApp.path, + workingDirectory: _repo.sharezoneFlutterApp.location, ); } } diff --git a/tools/sz_repo_cli/lib/src/commands/src/deploy_web_app_command.dart b/tools/sz_repo_cli/lib/src/commands/src/deploy_web_app_command.dart index 2a1023da8..178bc3d21 100644 --- a/tools/sz_repo_cli/lib/src/commands/src/deploy_web_app_command.dart +++ b/tools/sz_repo_cli/lib/src/commands/src/deploy_web_app_command.dart @@ -11,6 +11,7 @@ import 'dart:io'; import 'package:args/args.dart'; import 'package:args/command_runner.dart'; +import 'package:process_runner/process_runner.dart'; import 'package:sz_repo_cli/src/common/common.dart'; // All apps are deployed in the production firebase project but under different @@ -29,9 +30,10 @@ final _webAppConfigs = { /// The command will automatically use the right firebase config as configured /// inside [_webAppConfigs]. class DeployWebAppCommand extends Command { + final ProcessRunner processRunner; final SharezoneRepo _repo; - DeployWebAppCommand(this._repo) { + DeployWebAppCommand(this.processRunner, this._repo) { argParser ..addOption( releaseStageOptionName, @@ -83,59 +85,59 @@ class DeployWebAppCommand extends Command { final releaseStage = _parseReleaseStage(argResults!); final webAppConfig = _getMatchingWebAppConfig(releaseStage); - await runProcessSuccessfullyOrThrow( + await processRunner.runProcess([ 'fvm', - [ - 'dart', - 'run', - 'sz_repo_cli', - 'build', - 'web', - '--flavor', - webAppConfig.flavor, - '--stage', - releaseStage - ], - workingDirectory: _repo.sharezoneCiCdTool.path, - ); + 'dart', + 'run', + 'sz_repo_cli', + 'build', + 'web', + '--flavor', + webAppConfig.flavor, + '--stage', + releaseStage + ], workingDirectory: _repo.sharezoneCiCdTool.location); String? deployMessage; if (overriddenDeployMessage == null) { - final currentCommit = await _getCurrentCommitHash(); + final currentCommit = await _getCurrentCommitHash(processRunner); deployMessage = 'Commit: $currentCommit'; } - await runProcessSuccessfullyOrThrow( + // If we run this inside the CI/CD system we want this call to be + // authenticated via the GOOGLE_APPLICATION_CREDENTIALS environment + // variable. + // + // Unfortunately it doesn't work to export the environment variable + // inside the CI/CD job and let the firebase cli use it automatically. + // Even when using [Process.includeParentEnvironment] the variables + // are not passed to the firebase cli. + // + // Thus the CI/CD script can pass the + // [googleApplicationCredentialsFile] manually via an command line + // option and we set the GOOGLE_APPLICATION_CREDENTIALS manually + // below. + if (googleApplicationCredentialsFile != null) { + // Not sure if it is wrong usage of the environment variable (i.e. we + // shouldn't modify it in this way but rather create a new/modified + // ProcessRunner). + processRunner.environment['GOOGLE_APPLICATION_CREDENTIALS'] = + googleApplicationCredentialsFile.absolute.path; + } + + await processRunner.runProcess( + [ 'firebase', - [ - 'deploy', - '--only', - 'hosting:${webAppConfig.deployTargetName}', - '--project', - webAppConfig.firebaseProjectId, - '--message', - deployMessage ?? overriddenDeployMessage!, - ], - workingDirectory: _repo.sharezoneFlutterApp.location.path, - - // If we run this inside the CI/CD system we want this call to be - // authenticated via the GOOGLE_APPLICATION_CREDENTIALS environment - // variable. - // - // Unfortunately it doesn't work to export the environment variable - // inside the CI/CD job and let the firebase cli use it automatically. - // Even when using [Process.includeParentEnvironment] the variables - // are not passed to the firebase cli. - // - // Thus the CI/CD script can pass the - // [googleApplicationCredentialsFile] manually via an command line - // option and we set the GOOGLE_APPLICATION_CREDENTIALS manually - // below. - environment: { - if (googleApplicationCredentialsFile != null) - 'GOOGLE_APPLICATION_CREDENTIALS': - googleApplicationCredentialsFile.absolute.path, - }); + 'deploy', + '--only', + 'hosting:${webAppConfig.deployTargetName}', + '--project', + webAppConfig.firebaseProjectId, + '--message', + deployMessage ?? overriddenDeployMessage!, + ], + workingDirectory: _repo.sharezoneFlutterApp.location, + ); } File? _parseCredentialsFile(ArgResults argResults) { @@ -183,9 +185,9 @@ class DeployWebAppCommand extends Command { return overriddenDeployMessageOrNull; } - Future _getCurrentCommitHash() async { - final res = await runProcess('git', ['rev-parse', 'HEAD']); - if (res.stdout == null || (res.stdout as String).isEmpty) { + Future _getCurrentCommitHash(ProcessRunner processRunner) async { + final res = await processRunner.runProcess(['git', 'rev-parse', 'HEAD']); + if (res.stdout.isEmpty) { stderr.writeln( 'Could not receive the current commit hash: (${res.exitCode}) ${res.stderr}.'); throw ToolExit(15); diff --git a/tools/sz_repo_cli/lib/src/commands/src/fix_comment_spacing_command.dart b/tools/sz_repo_cli/lib/src/commands/src/fix_comment_spacing_command.dart index d342dfbad..2244b16c7 100644 --- a/tools/sz_repo_cli/lib/src/commands/src/fix_comment_spacing_command.dart +++ b/tools/sz_repo_cli/lib/src/commands/src/fix_comment_spacing_command.dart @@ -8,7 +8,6 @@ import 'dart:async'; import 'dart:io'; - import 'package:args/command_runner.dart'; import 'package:glob/glob.dart'; import 'package:glob/list_local_fs.dart'; diff --git a/tools/sz_repo_cli/lib/src/commands/src/format_command.dart b/tools/sz_repo_cli/lib/src/commands/src/format_command.dart index 309d2e614..14e7458ba 100644 --- a/tools/sz_repo_cli/lib/src/commands/src/format_command.dart +++ b/tools/sz_repo_cli/lib/src/commands/src/format_command.dart @@ -9,12 +9,13 @@ import 'dart:async'; import 'dart:io'; +import 'package:process_runner/process_runner.dart'; import 'package:sz_repo_cli/src/common/common.dart'; import 'package:sz_repo_cli/src/common/src/run_source_of_truth_command.dart'; import 'package:sz_repo_cli/src/common/src/throw_if_command_is_not_installed.dart'; class FormatCommand extends ConcurrentCommand { - FormatCommand(SharezoneRepo repo) : super(repo); + FormatCommand(super.processRunner, super.repo); @override final String name = 'format'; @@ -35,10 +36,11 @@ class FormatCommand extends ConcurrentCommand { @override Future runTaskForPackage(Package package) async => - await formatCode(package); + await formatCode(processRunner, package); Future _throwIfPrettierIsNotInstalled() async { await throwIfCommandIsNotInstalled( + processRunner, command: 'prettier', instructionsToInstall: 'Run `npm install -g prettier` to install it.', ); @@ -48,6 +50,7 @@ class FormatCommand extends ConcurrentCommand { required SharezoneRepo repo, }) async { final results = await runSourceOfTruthCommand( + processRunner, commandKey: 'format_action_files', repo: repo, ); @@ -62,18 +65,17 @@ class FormatCommand extends ConcurrentCommand { } Future formatCode( + ProcessRunner processRunner, Package package, { /// Throws if code is not already formatted properly. /// Useful for code analysis in CI. bool throwIfCodeChanged = false, }) { - return runProcessSuccessfullyOrThrow( - 'fvm', - [ - 'dart', - 'format', - if (throwIfCodeChanged) '--set-exit-if-changed', - '.', - ], - workingDirectory: package.path); + return processRunner.runProcess([ + 'fvm', + 'dart', + 'format', + if (throwIfCodeChanged) '--set-exit-if-changed', + '.', + ], workingDirectory: package.location); } diff --git a/tools/sz_repo_cli/lib/src/commands/src/locate_sharezone_flutter_directory_command.dart b/tools/sz_repo_cli/lib/src/commands/src/locate_sharezone_flutter_directory_command.dart deleted file mode 100644 index 9b9a1ec87..000000000 --- a/tools/sz_repo_cli/lib/src/commands/src/locate_sharezone_flutter_directory_command.dart +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) 2022 Sharezone UG (haftungsbeschränkt) -// Licensed under the EUPL-1.2-or-later. -// -// You may obtain a copy of the Licence at: -// https://joinup.ec.europa.eu/software/page/eupl -// -// SPDX-License-Identifier: EUPL-1.2 - -import 'dart:async'; -import 'dart:io'; - -import 'package:args/command_runner.dart'; -import 'package:glob/glob.dart'; -import 'package:glob/list_local_fs.dart'; -import 'package:path/path.dart'; -import 'package:sz_repo_cli/src/common/common.dart'; - -final Glob pubspecGlob = Glob('**pubspec.yaml'); - -class LocateSharezoneAppFlutterDirectoryCommand extends Command { - LocateSharezoneAppFlutterDirectoryCommand() { - argParser.addOption(pathType, - defaultsTo: absolute, - allowed: [relative, absolute], - help: 'What kind of path should be given back', - abbr: 'p'); - } - - static const String pathType = 'pathType'; - static const String relative = 'relative'; - static const String absolute = 'absolute'; - - @override - String get description => - "Gives back the path of the flutter directory where the sharezone app is located.\nIt is determined by searching for a pubspec.yaml with 'name: sharezone'"; - - @override - String get name => 'locate-app'; - - @override - FutureOr run() async { - switch (argResults![pathType]) { - case relative: - stdout.writeln(await findSharezoneFlutterAppRelativeDirectoryPath()); - break; - case absolute: - stdout.writeln(await findSharezoneFlutterAppAbsoluteDirectoryPath()); - break; - } - } -} - -Future findSharezoneFlutterAppAbsoluteDirectoryPath() async { - final dir = await findSharezoneFlutterDirectory( - root: await getProjectRootDirectory(), - ); - - // normalize is used as the default given back is e.g. - // /Users/maxmustermann/development/projects/sharezone-app/./app instead of - // /Users/maxmustermann/development/projects/sharezone-app/app - // which is not wrong but not pretty ;) - return normalize(dir.absolute.path); -} - -Future findSharezoneFlutterAppRelativeDirectoryPath() async { - final dir = await findSharezoneFlutterDirectory( - root: await getProjectRootDirectory(), - ); - if (dir.isAbsolute) { - return relative(dir.path, from: Directory.current.path); - } - return dir.path; -} - -Future findSharezoneFlutterDirectory( - {required Directory root}) async { - await for (FileSystemEntity entity in pubspecGlob.list(root: root.path)) { - if (entity is File) { - final pubspecContent = await entity.readAsString(); - - final isSharezone = - pubspecContent.contains(RegExp(r'(?:^|\W)name: sharezone(?:$|\W)')); - if (isSharezone) { - return entity.parent; - } - } - } - throw SharezoneDirectoryNotFoundError(root.path); -} - -class SharezoneDirectoryNotFoundError extends Error { - SharezoneDirectoryNotFoundError([this.currentDirPath]); - - final String? currentDirPath; - - @override - String toString() { - return 'Could not find Sharezone under current path ($currentDirPath)'; - } -} diff --git a/tools/sz_repo_cli/lib/src/commands/src/pub_get_command.dart b/tools/sz_repo_cli/lib/src/commands/src/pub_get_command.dart index f53bd25e7..745c51adc 100644 --- a/tools/sz_repo_cli/lib/src/commands/src/pub_get_command.dart +++ b/tools/sz_repo_cli/lib/src/commands/src/pub_get_command.dart @@ -8,10 +8,11 @@ import 'dart:async'; +import 'package:process_runner/process_runner.dart'; import 'package:sz_repo_cli/src/common/common.dart'; class PubGetCommand extends ConcurrentCommand { - PubGetCommand(SharezoneRepo repo) : super(repo); + PubGetCommand(super.processRunner, super.repo); @override String get description => @@ -27,26 +28,30 @@ class PubGetCommand extends ConcurrentCommand { Duration get defaultPackageTimeout => const Duration(minutes: 5); @override - Future runTaskForPackage(Package package) => getPackage(package); + Future runTaskForPackage(Package package) => + getPackage(processRunner, package); } -Future getPackage(Package package) async { +Future getPackage(ProcessRunner processRunner, Package package) async { if (package.isFlutterPackage) { - await getPackagesFlutter(package); + await getPackagesFlutter(processRunner, package); } else { - await getPackagesDart(package); + await getPackagesDart(processRunner, package); } } -Future getPackagesDart(Package package) async { - await runProcessSuccessfullyOrThrow('fvm', ['dart', 'pub', 'get'], - workingDirectory: package.path); +Future getPackagesDart( + ProcessRunner processRunner, Package package) async { + await processRunner.runProcess( + ['fvm', 'dart', 'pub', 'get'], + workingDirectory: package.location, + ); } -Future getPackagesFlutter(Package package) async { - await runProcessSuccessfullyOrThrow( - 'fvm', - ['flutter', 'pub', 'get'], - workingDirectory: package.path, +Future getPackagesFlutter( + ProcessRunner processRunner, Package package) async { + await processRunner.runProcess( + ['fvm', 'flutter', 'pub', 'get'], + workingDirectory: package.location, ); } diff --git a/tools/sz_repo_cli/lib/src/commands/src/test_command.dart b/tools/sz_repo_cli/lib/src/commands/src/test_command.dart index 68496a860..1ec2678ee 100644 --- a/tools/sz_repo_cli/lib/src/commands/src/test_command.dart +++ b/tools/sz_repo_cli/lib/src/commands/src/test_command.dart @@ -8,12 +8,13 @@ import 'dart:async'; +import 'package:process_runner/process_runner.dart'; import 'package:sz_repo_cli/src/common/common.dart'; import 'pub_get_command.dart'; class TestCommand extends ConcurrentCommand { - TestCommand(SharezoneRepo repo) : super(repo) { + TestCommand(super.processRunner, super.repo) { argParser.addFlag( 'exclude-goldens', help: 'Run tests without golden tests.', @@ -63,6 +64,7 @@ class TestCommand extends ConcurrentCommand { @override Future runTaskForPackage(Package package) { return runTests( + processRunner, package, excludeGoldens: argResults!['exclude-goldens'] as bool, onlyGoldens: argResults!['only-goldens'] as bool, @@ -72,6 +74,7 @@ class TestCommand extends ConcurrentCommand { } Future runTests( + ProcessRunner processRunner, Package package, { required bool excludeGoldens, required bool onlyGoldens, @@ -79,6 +82,7 @@ Future runTests( }) { if (package.isFlutterPackage) { return _runTestsFlutter( + processRunner, package, excludeGoldens: excludeGoldens, onlyGoldens: onlyGoldens, @@ -86,6 +90,7 @@ Future runTests( ); } else { return _runTestsDart( + processRunner, package, excludeGoldens: excludeGoldens, onlyGoldens: onlyGoldens, @@ -94,6 +99,7 @@ Future runTests( } Future _runTestsDart( + ProcessRunner processRunner, Package package, { // We can ignore the "excludeGoldens" parameter here because Dart packages // don't have golden tests. @@ -105,16 +111,16 @@ Future _runTestsDart( return; } - await getPackage(package); + await getPackage(processRunner, package); - await runProcessSuccessfullyOrThrow( - 'fvm', - ['dart', 'test'], - workingDirectory: package.path, + await processRunner.runProcess( + ['fvm', 'dart', 'test'], + workingDirectory: package.location, ); } Future _runTestsFlutter( + ProcessRunner processRunner, Package package, { required bool excludeGoldens, required bool onlyGoldens, @@ -125,15 +131,15 @@ Future _runTestsFlutter( return; } - await runProcessSuccessfullyOrThrow( - 'fvm', + await processRunner.runProcess( [ + 'fvm', 'flutter', 'test', 'test_goldens', if (updateGoldens) '--update-goldens', ], - workingDirectory: package.path, + workingDirectory: package.location, ); return; } @@ -142,10 +148,9 @@ Future _runTestsFlutter( // command. Otherwise the throws the Flutter tool throws an error that it // couldn't find the "test_goldens" directory. if (excludeGoldens || !package.hasGoldenTestsDirectory) { - await runProcessSuccessfullyOrThrow( - 'fvm', - ['flutter', 'test'], - workingDirectory: package.path, + await processRunner.runProcess( + ['fvm', 'flutter', 'test'], + workingDirectory: package.location, ); return; } @@ -153,9 +158,9 @@ Future _runTestsFlutter( /// Flutter test lässt automatisch flutter pub get laufen. /// Deswegen muss nicht erst noch [getPackages] aufgerufen werden. - await runProcessSuccessfullyOrThrow( - 'fvm', + await processRunner.runProcess( [ + 'fvm', 'flutter', 'test', // Directory for golden tests. @@ -163,6 +168,6 @@ Future _runTestsFlutter( // Directory for unit and widget tests. 'test', ], - workingDirectory: package.path, + workingDirectory: package.location, ); } diff --git a/tools/sz_repo_cli/lib/src/common/common.dart b/tools/sz_repo_cli/lib/src/common/common.dart index 8939e25f1..cead312f6 100644 --- a/tools/sz_repo_cli/lib/src/common/common.dart +++ b/tools/sz_repo_cli/lib/src/common/common.dart @@ -10,13 +10,13 @@ import 'dart:async'; import 'dart:io'; import 'package:path/path.dart' as path; +import 'package:process_runner/process_runner.dart'; export 'src/concurrent_command.dart'; export 'src/concurrent_package_task_runner.dart'; export 'src/merge_with_value_stream_extension.dart'; export 'src/package.dart'; export 'src/package_timeout_exception.dart'; -export 'src/run_process.dart'; export 'src/shared_args.dart'; export 'src/sharezone_repo.dart'; export 'src/to_utf8_string_extension.dart'; @@ -27,15 +27,10 @@ export 'src/tool_exit.dart'; /// werden, fürs bessere Testen. bool isVerbose = false; -Future getProjectRootDirectory() async { - final res = await Process.run('git', ['rev-parse', '--show-toplevel'], - runInShell: false); +Future getProjectRootDirectory(ProcessRunner processRunner) async { + final res = + await processRunner.runProcess(['git', 'rev-parse', '--show-toplevel']); final stdout = res.stdout; - if (stdout is! String) { - stderr.writeln( - 'Error: Could not get project root from git (output: ${res.stdout})'); - exit(1); - } // Without [path.canonicalize] the path won't work on Windows as git returns // normal slashes instead the by Windows required backward slashes. final projectDirPath = path.canonicalize(stdout.trim()); diff --git a/tools/sz_repo_cli/lib/src/common/src/app_store_connect_utils.dart b/tools/sz_repo_cli/lib/src/common/src/app_store_connect_utils.dart index 07242c263..0c4c8dbb1 100644 --- a/tools/sz_repo_cli/lib/src/common/src/app_store_connect_utils.dart +++ b/tools/sz_repo_cli/lib/src/common/src/app_store_connect_utils.dart @@ -9,8 +9,8 @@ import 'dart:io'; import 'package:args/args.dart'; +import 'package:process_runner/process_runner.dart'; import 'package:sz_repo_cli/src/common/src/apple_track.dart'; -import 'package:sz_repo_cli/src/common/src/run_process.dart'; import 'package:sz_repo_cli/src/common/src/sharezone_repo.dart'; import 'package:sz_repo_cli/src/common/src/throw_if_command_is_not_installed.dart'; @@ -22,19 +22,20 @@ const whatsNewOptionName = 'whats-new'; const releaseStageOptionName = 'stage'; /// Sets up signing that is required to deploy a macOS or iOS app. -Future setUpSigning({ +Future setUpSigning( + ProcessRunner processRunner, { required AppleSigningConfig config, required ApplePlatform platform, }) async { // Steps are from the docs to deploy an iOS / macOS app to App Store Connect: // https://github.com/flutter/website/blob/850ba5dcab36e81f7dfc71c5e46333173c764fac/src/deployment/ios.md#L322 - await _keychainInitialize(); - await _fetchSigningFiles(config: config); + await _keychainInitialize(processRunner); + await _fetchSigningFiles(processRunner, config: config); if (platform == ApplePlatform.macOS) { - await _listMacCertificates(); + await _listMacCertificates(processRunner); } - await _keychainAddCertificates(); - await _xcodeProjectUseProfiles(); + await _keychainAddCertificates(processRunner); + await _xcodeProjectUseProfiles(processRunner); } /// Sets up a temporary keychain to be used for code signing. @@ -42,18 +43,19 @@ Future setUpSigning({ /// Keep in mind that you should call `keychain use-default` (see /// [keychainUseLogin]) after the deployment to avoid potential authentication /// issues if you run the deployment on your local machine. -Future _keychainInitialize() async { - await runProcessSuccessfullyOrThrow('keychain', ['initialize']); +Future _keychainInitialize(ProcessRunner processRunner) async { + await processRunner.runProcess(['keychain', 'initialize']); } /// Fetch the code signing files from App Store Connect. -Future _fetchSigningFiles({ +Future _fetchSigningFiles( + ProcessRunner processRunner, { required AppleSigningConfig config, }) async { const bundleId = 'de.codingbrain.sharezone.app'; - await runProcessSuccessfullyOrThrow( - 'app-store-connect', + await processRunner.runProcess( [ + 'app-store-connect', 'fetch-signing-files', bundleId, '--platform', @@ -73,10 +75,10 @@ Future _fetchSigningFiles({ ); } -Future _listMacCertificates() async { - await runProcessSuccessfullyOrThrow( - 'app-store-connect', +Future _listMacCertificates(ProcessRunner processRunner) async { + await processRunner.runProcess( [ + 'app-store-connect', 'list-certificates', '--type', 'MAC_INSTALLER_DISTRIBUTION', @@ -86,13 +88,13 @@ Future _listMacCertificates() async { } /// Adds the certificates to the keychain. -Future _keychainAddCertificates() async { - await runProcessSuccessfullyOrThrow('keychain', ['add-certificates']); +Future _keychainAddCertificates(ProcessRunner processRunner) async { + await processRunner.runProcess(['keychain', 'add-certificates']); } /// Update the Xcode project settings to use fetched code signing profiles. -Future _xcodeProjectUseProfiles() async { - await runProcessSuccessfullyOrThrow('xcode-project', ['use-profiles']); +Future _xcodeProjectUseProfiles(ProcessRunner processRunner) async { + await processRunner.runProcess(['xcode-project', 'use-profiles']); } /// Sets your login keychain as the default to avoid potential authentication @@ -101,16 +103,18 @@ Future _xcodeProjectUseProfiles() async { /// This is only useful if you are running the deployment on your local machine /// and have previously used the `keychain initialize' command. If you run it on /// a CI server, this step is not necessary. -Future keychainUseLogin() async { - await runProcessSuccessfullyOrThrow('keychain', ['use-login']); +Future keychainUseLogin(ProcessRunner processRunner) async { + await processRunner.runProcess(['keychain', 'use-login']); } -Future getNextBuildNumberFromAppStoreConnect({ +Future getNextBuildNumberFromAppStoreConnect( + ProcessRunner processRunner, { required String workingDirectory, required AppStoreConnectConfig appStoreConnectConfig, required ApplePlatform platform, }) async { final latestBuildNumber = await _getLatestBuildNumberFromAppStoreConnect( + processRunner, platform: platform, workingDirectory: workingDirectory, appStoreConnectConfig: appStoreConnectConfig, @@ -121,7 +125,8 @@ Future getNextBuildNumberFromAppStoreConnect({ } /// Returns the latest build number from App Store and all TestFlight tracks. -Future _getLatestBuildNumberFromAppStoreConnect({ +Future _getLatestBuildNumberFromAppStoreConnect( + ProcessRunner processRunner, { required String workingDirectory, required AppStoreConnectConfig appStoreConnectConfig, required ApplePlatform platform, @@ -130,9 +135,9 @@ Future _getLatestBuildNumberFromAppStoreConnect({ // From https://appstoreconnect.apple.com/apps/1434868489/ const appId = 1434868489; - final result = await runProcessSuccessfullyOrThrow( - 'app-store-connect', + final result = await processRunner.runProcess( [ + 'app-store-connect', 'get-latest-build-number', '$appId', '--platform', @@ -144,7 +149,7 @@ Future _getLatestBuildNumberFromAppStoreConnect({ '--private-key', appStoreConnectConfig.privateKey, ], - workingDirectory: workingDirectory, + workingDirectory: Directory(workingDirectory), ); return int.parse(result.stdout); } catch (e) { @@ -153,7 +158,8 @@ Future _getLatestBuildNumberFromAppStoreConnect({ } } -Future publishToAppStoreConnect({ +Future publishToAppStoreConnect( + ProcessRunner processRunner, { required SharezoneRepo repo, required String path, required Map stageToTracks, @@ -166,9 +172,9 @@ Future publishToAppStoreConnect({ stageToTracks: stageToTracks, ); - await runProcessSuccessfullyOrThrow( - 'app-store-connect', + await processRunner.runProcess( [ + 'app-store-connect', 'publish', '--path', path, @@ -213,7 +219,7 @@ Future publishToAppStoreConnect({ // previous submission is not approved yet. '--cancel-previous-submissions' ], - workingDirectory: repo.sharezoneFlutterApp.location.path, + workingDirectory: repo.sharezoneFlutterApp.location, ); } @@ -228,8 +234,10 @@ AppleTrack _getAppleTrack({ return track; } -Future throwIfCodemagicCliToolsAreNotInstalled() async { +Future throwIfCodemagicCliToolsAreNotInstalled( + ProcessRunner processRunner) async { await throwIfCommandIsNotInstalled( + processRunner, command: 'app-store-connect', instructionsToInstall: 'Docs to install them: https://github.com/codemagic-ci-cd/cli-tools#installing', diff --git a/tools/sz_repo_cli/lib/src/common/src/concurrent_command.dart b/tools/sz_repo_cli/lib/src/common/src/concurrent_command.dart index e84ede192..4a154acc4 100644 --- a/tools/sz_repo_cli/lib/src/common/src/concurrent_command.dart +++ b/tools/sz_repo_cli/lib/src/common/src/concurrent_command.dart @@ -10,13 +10,15 @@ import 'dart:async'; import 'dart:io'; import 'package:args/command_runner.dart'; +import 'package:process_runner/process_runner.dart'; import 'package:sz_repo_cli/src/common/common.dart'; /// Run a task via [runTaskForPackage] for many [Package] concurrently. abstract class ConcurrentCommand extends Command { + final ProcessRunner processRunner; final SharezoneRepo repo; - ConcurrentCommand(this.repo) { + ConcurrentCommand(this.processRunner, this.repo) { argParser ..addVerboseFlag() ..addConcurrencyOption(defaultMaxConcurrency: defaultMaxConcurrency) diff --git a/tools/sz_repo_cli/lib/src/common/src/run_process.dart b/tools/sz_repo_cli/lib/src/common/src/run_process.dart deleted file mode 100644 index e7d72aa93..000000000 --- a/tools/sz_repo_cli/lib/src/common/src/run_process.dart +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) 2022 Sharezone UG (haftungsbeschränkt) -// Licensed under the EUPL-1.2-or-later. -// -// You may obtain a copy of the Licence at: -// https://joinup.ec.europa.eu/software/page/eupl -// -// SPDX-License-Identifier: EUPL-1.2 - -import 'dart:io'; - -import '../common.dart'; - -/// Helper method that automatically throws if [Process.exitCode] is non-zero -/// (unsucessfull). -Future runProcessSuccessfullyOrThrow( - String executable, - List arguments, { - String? workingDirectory, - Map? environment, - // ignore: unused_element - bool? includeParentEnvironment, - bool? runInShell, - ProcessStartMode mode = ProcessStartMode.normal, -}) async { - final displayableCommand = '$executable ${arguments.join(' ')}'; - - final result = await runProcess(executable, arguments, - workingDirectory: workingDirectory, - environment: environment, - includeParentEnvironment: includeParentEnvironment, - runInShell: runInShell, - mode: mode); - if (result.exitCode != 0) { - throw Exception( - 'Process ended with non-zero exit code: $displayableCommand (exit code ${result.exitCode}): ${result.stderr}\n\n stdout:${result.stdout}'); - } - - return ProcessResult(result.pid, exitCode, result.stdout, result.stderr); -} - -/// Helper method with automatic (verbose) logging and workarounds for some -/// weird behavior of normal dart:io processes. -Future runProcess( - String executable, - List arguments, { - String? workingDirectory, - Map? environment, - // ignore: unused_element - bool? includeParentEnvironment, - bool? runInShell, - ProcessStartMode mode = ProcessStartMode.normal, -}) async { - final displayableCommand = '$executable ${arguments.join(' ')}'; - if (isVerbose) stdout.writeln('Starting $displayableCommand...'); - - final process = await Process.start(executable, arguments, - workingDirectory: workingDirectory, - environment: environment, - // Else on Windows some operation might just not work if both are not true - includeParentEnvironment: includeParentEnvironment ?? true, - runInShell: runInShell ?? true, - // - mode: mode); - - // Somehow (at least on Windows but I think also on other platforms) - // `await process.exitCode` below doesn't complete if we don't listen to - // stdout or stderr of the process?!?!?! - // I don't know why and it's really confusing. - final broadcastStdout = process.stdout.asBroadcastStream(); - final broadcastStderr = process.stderr.asBroadcastStream(); - - // Stream live output if desired - broadcastStdout.listen(isVerbose ? stdout.add : (_) {}); - broadcastStderr.listen(isVerbose ? stderr.add : (_) {}); - - // Buffer output to print if process has error - // We don't listen until a Stream completes because for some __ reason `dart - // pub get` doesn't close stderr stream when encountering an error (means that - // we would wait forever). - final stdoutBuffer = StringBuffer(); - broadcastStdout.toUtf8().listen(stdoutBuffer.write); - final stderrBuffer = StringBuffer(); - broadcastStderr.toUtf8().listen(stderrBuffer.write); - - if (isVerbose) stdout.writeln('Waiting for exit code...'); - final exitCode = await process.exitCode; - if (isVerbose) stdout.writeln('Got exit code $exitCode...'); - - final stdoutOutput = stdoutBuffer.toString(); - final stderrOutput = stderrBuffer.toString(); - - return ProcessResult(process.pid, exitCode, stdoutOutput, stderrOutput); -} diff --git a/tools/sz_repo_cli/lib/src/common/src/run_source_of_truth_command.dart b/tools/sz_repo_cli/lib/src/common/src/run_source_of_truth_command.dart index e0ab729af..fe175f59a 100644 --- a/tools/sz_repo_cli/lib/src/common/src/run_source_of_truth_command.dart +++ b/tools/sz_repo_cli/lib/src/common/src/run_source_of_truth_command.dart @@ -8,14 +8,15 @@ import 'dart:io'; -import 'package:sz_repo_cli/src/common/src/run_process.dart'; +import 'package:process_runner/process_runner.dart'; import 'package:sz_repo_cli/src/common/src/sharezone_repo.dart'; import 'package:yaml/yaml.dart'; /// Run a source of truth command via a key. /// /// The key can be seen in the [repo.commandsSourceOfTruthYamlFile] file. -Future runSourceOfTruthCommand({ +Future runSourceOfTruthCommand( + ProcessRunner processRunner, { required String commandKey, required SharezoneRepo repo, @@ -25,16 +26,12 @@ Future runSourceOfTruthCommand({ final sot = repo.commandsSourceOfTruthYamlFile; final commands = loadYaml(sot.readAsStringSync()) as Map; final command = commands[commandKey] as String; - final splitted = command.split(' '); - final executable = splitted[0]; - var argumentsString = command.replaceFirst('$executable ', ''); - final arguments = _convertIntoArgumentsList(argumentsString) + final arguments = _convertIntoArgumentsList(command) ..addAll(argumentsToAppend); - return runProcess( - executable, + return processRunner.runProcess( arguments, - workingDirectory: repo.location.path, + workingDirectory: repo.location, ); } diff --git a/tools/sz_repo_cli/lib/src/common/src/throw_if_command_is_not_installed.dart b/tools/sz_repo_cli/lib/src/common/src/throw_if_command_is_not_installed.dart index fb38efb9b..2eef79081 100644 --- a/tools/sz_repo_cli/lib/src/common/src/throw_if_command_is_not_installed.dart +++ b/tools/sz_repo_cli/lib/src/common/src/throw_if_command_is_not_installed.dart @@ -8,7 +8,7 @@ import 'dart:io'; -import 'package:sz_repo_cli/src/common/src/run_process.dart'; +import 'package:process_runner/process_runner.dart'; /// Throws an exception if [command] is not installed. /// @@ -17,7 +17,8 @@ import 'package:sz_repo_cli/src/common/src/run_process.dart'; /// /// Currently, we skip this method for [Platform.isWindows] because "which -s" /// is not available for Windows. -Future throwIfCommandIsNotInstalled({ +Future throwIfCommandIsNotInstalled( + ProcessRunner processRunner, { required String command, String? instructionsToInstall, }) async { @@ -27,9 +28,9 @@ Future throwIfCommandIsNotInstalled({ return; } - final result = await runProcess( - 'which', - ['-s', command], + final result = await processRunner.runProcess( + ['which', '-s', command], + failOk: true, ); if (result.exitCode != 0) { String message = 'Command "$command" is not installed.'; diff --git a/tools/sz_repo_cli/lib/src/main.dart b/tools/sz_repo_cli/lib/src/main.dart index 27c256b23..03dbd5a1e 100644 --- a/tools/sz_repo_cli/lib/src/main.dart +++ b/tools/sz_repo_cli/lib/src/main.dart @@ -11,6 +11,7 @@ import 'dart:io'; import 'package:args/command_runner.dart'; import 'package:path/path.dart' as p; +import 'package:process_runner/process_runner.dart'; import 'package:sz_repo_cli/src/commands/src/add_license_headers_command.dart'; import 'package:sz_repo_cli/src/commands/src/build_android_command.dart'; import 'package:sz_repo_cli/src/commands/src/build_command.dart'; @@ -30,7 +31,15 @@ import 'commands/commands.dart'; import 'common/common.dart'; Future main(List args) async { - final projectRoot = await getProjectRootDirectory(); + ProcessRunner processRunner = ProcessRunner(); + final verbose = args.contains('-v') || args.contains('--verbose'); + final projectRoot = await getProjectRootDirectory(processRunner); + + processRunner = ProcessRunner( + defaultWorkingDirectory: projectRoot, + printOutputDefault: verbose, + ); + final packagesDir = Directory(p.join(projectRoot.path, 'lib')); if (!packagesDir.existsSync()) { @@ -42,27 +51,28 @@ Future main(List args) async { final commandRunner = CommandRunner('pub global run sz_repo_cli', 'Productivity utils for everything Sharezone.') - ..addCommand(AnalyzeCommand(repo)) - ..addCommand(LocateSharezoneAppFlutterDirectoryCommand()) - ..addCommand(TestCommand(repo)) - ..addCommand(FormatCommand(repo)) + ..addCommand(AnalyzeCommand(processRunner, repo)) + ..addCommand(TestCommand(processRunner, repo)) + ..addCommand(FormatCommand(processRunner, repo)) ..addCommand(DoStuffCommand(repo)) ..addCommand(FixCommentSpacingCommand(repo)) - ..addCommand(PubCommand()..addSubcommand(PubGetCommand(repo))) + ..addCommand( + PubCommand()..addSubcommand(PubGetCommand(processRunner, repo))) ..addCommand(LicenseHeadersCommand() - ..addSubcommand(CheckLicenseHeadersCommand(repo)) - ..addSubcommand(AddLicenseHeadersCommand(repo))) + ..addSubcommand(CheckLicenseHeadersCommand(processRunner, repo)) + ..addSubcommand(AddLicenseHeadersCommand(processRunner, repo))) ..addCommand(DeployCommand() - ..addSubcommand(DeployWebAppCommand(repo)) - ..addSubcommand(DeployIosCommand(repo)) - ..addSubcommand(DeployMacOsCommand(repo)) - ..addSubcommand(DeployAndroidCommand(repo))) + ..addSubcommand(DeployWebAppCommand(processRunner, repo)) + ..addSubcommand(DeployIosCommand(processRunner, repo)) + ..addSubcommand(DeployMacOsCommand(processRunner, repo)) + ..addSubcommand(DeployAndroidCommand(processRunner, repo))) ..addCommand(BuildCommand() - ..addSubcommand(BuildAndroidCommand(repo)) - ..addSubcommand(BuildMacOsCommand(repo)) - ..addSubcommand(BuildWebCommand(repo)) - ..addSubcommand(BuildIosCommand(repo))) - ..addCommand(BuildRunnerCommand()..addSubcommand(BuildRunnerBuild(repo))); + ..addSubcommand(BuildAndroidCommand(processRunner, repo)) + ..addSubcommand(BuildMacOsCommand(processRunner, repo)) + ..addSubcommand(BuildWebCommand(processRunner, repo)) + ..addSubcommand(BuildIosCommand(processRunner, repo))) + ..addCommand(BuildRunnerCommand() + ..addSubcommand(BuildRunnerBuild(processRunner, repo))); await commandRunner.run(args).catchError((Object e) { final toolExit = e as ToolExit; diff --git a/tools/sz_repo_cli/pubspec.lock b/tools/sz_repo_cli/pubspec.lock index 959696aa1..a7ce043eb 100644 --- a/tools/sz_repo_cli/pubspec.lock +++ b/tools/sz_repo_cli/pubspec.lock @@ -265,6 +265,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.8.3" + platform: + dependency: transitive + description: + name: platform + sha256: "0a279f0707af40c890e80b1e9df8bb761694c074ba7e1d4ab1bc4b728e200b59" + url: "https://pub.dev" + source: hosted + version: "3.1.3" pool: dependency: transitive description: @@ -273,6 +281,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" + process: + dependency: "direct main" + description: + name: process + sha256: "266ca5be5820feefc777793d0a583acfc8c40834893c87c00c6c09e2cf58ea42" + url: "https://pub.dev" + source: hosted + version: "5.0.1" + process_runner: + dependency: "direct main" + description: + name: process_runner + sha256: "94904483ce088e20115fa76781d9a5b921428b60a5c3a082f6df6fef2e916983" + url: "https://pub.dev" + source: hosted + version: "4.1.4" pub_semver: dependency: "direct main" description: diff --git a/tools/sz_repo_cli/pubspec.yaml b/tools/sz_repo_cli/pubspec.yaml index b243966d1..b97eb3d39 100644 --- a/tools/sz_repo_cli/pubspec.yaml +++ b/tools/sz_repo_cli/pubspec.yaml @@ -31,6 +31,8 @@ dependencies: union: ^0.0.3+1 console: ^4.1.0 rxdart: ^0.27.1 + process: ^5.0.1 + process_runner: ^4.1.4 dev_dependencies: sharezone_lints: diff --git a/tools/sz_repo_cli/test/ensure_cmd_sot_file_exists_test.dart b/tools/sz_repo_cli/test/ensure_cmd_sot_file_exists_test.dart index 5caef0939..0ee9fd398 100644 --- a/tools/sz_repo_cli/test/ensure_cmd_sot_file_exists_test.dart +++ b/tools/sz_repo_cli/test/ensure_cmd_sot_file_exists_test.dart @@ -6,12 +6,13 @@ // // SPDX-License-Identifier: EUPL-1.2 +import 'package:process_runner/process_runner.dart'; import 'package:sz_repo_cli/src/common/common.dart'; import 'package:test/test.dart'; void main() { test('Ensure source of truth yaml file exists', () async { - final projectRoot = await getProjectRootDirectory(); + final projectRoot = await getProjectRootDirectory(ProcessRunner()); final repo = SharezoneRepo(projectRoot); From ee447ef6e3a1e56a4bec941f59808a6d7817f2c0 Mon Sep 17 00:00:00 2001 From: Jonas Sander <29028262+Jonas-Sander@users.noreply.github.com> Date: Fri, 20 Oct 2023 21:26:27 +0200 Subject: [PATCH 3/8] Create `runProcessCustom` with `addedEnvironment` argument. --- .../commands/src/deploy_android_command.dart | 20 ++---- .../commands/src/deploy_web_app_command.dart | 42 ++++++----- .../src/common/src/process_runner_utils.dart | 71 +++++++++++++++++++ 3 files changed, 98 insertions(+), 35 deletions(-) create mode 100644 tools/sz_repo_cli/lib/src/common/src/process_runner_utils.dart diff --git a/tools/sz_repo_cli/lib/src/commands/src/deploy_android_command.dart b/tools/sz_repo_cli/lib/src/commands/src/deploy_android_command.dart index 0cea3817b..019aaed81 100644 --- a/tools/sz_repo_cli/lib/src/commands/src/deploy_android_command.dart +++ b/tools/sz_repo_cli/lib/src/commands/src/deploy_android_command.dart @@ -12,6 +12,7 @@ import 'package:args/command_runner.dart'; import 'package:process_runner/process_runner.dart'; import 'package:sz_repo_cli/src/common/common.dart'; import 'package:path/path.dart' as path; +import 'package:sz_repo_cli/src/common/src/process_runner_utils.dart'; final _androidStages = [ 'stable', @@ -229,22 +230,15 @@ class DeployAndroidCommand extends Command { required String track, required String rollout, }) async { - // Not sure if it is wrong usage of the environment variable (i.e. we - // shouldn't modify it in this way but rather create a new/modified - // ProcessRunner). - processRunner.environment['TRACK'] = track; - processRunner.environment['ROLLOUT'] = rollout; - // Sets the number of retries for uploading the app bundle to Google - // Play. This is needed because sometimes the upload fails for unknown - // reasons. - // - // See: https://github.com/fastlane/fastlane/issues/21507#issuecomment-1723116829 - processRunner.environment['SUPPLY_UPLOAD_MAX_RETRIES'] = '5'; - - await processRunner.runProcess( + await processRunner.runProcessCustom( ['fastlane', 'deploy'], workingDirectory: Directory( path.join(_repo.sharezoneFlutterApp.location.path, '/android')), + addedEnvironment: { + 'TRACK': track, + 'ROLLOUT': rollout, + 'SUPPLY_UPLOAD_MAX_RETRIES': '5', + }, ); } diff --git a/tools/sz_repo_cli/lib/src/commands/src/deploy_web_app_command.dart b/tools/sz_repo_cli/lib/src/commands/src/deploy_web_app_command.dart index 178bc3d21..7afbef927 100644 --- a/tools/sz_repo_cli/lib/src/commands/src/deploy_web_app_command.dart +++ b/tools/sz_repo_cli/lib/src/commands/src/deploy_web_app_command.dart @@ -13,6 +13,7 @@ import 'package:args/args.dart'; import 'package:args/command_runner.dart'; import 'package:process_runner/process_runner.dart'; import 'package:sz_repo_cli/src/common/common.dart'; +import 'package:sz_repo_cli/src/common/src/process_runner_utils.dart'; // All apps are deployed in the production firebase project but under different // domains. @@ -104,28 +105,7 @@ class DeployWebAppCommand extends Command { deployMessage = 'Commit: $currentCommit'; } - // If we run this inside the CI/CD system we want this call to be - // authenticated via the GOOGLE_APPLICATION_CREDENTIALS environment - // variable. - // - // Unfortunately it doesn't work to export the environment variable - // inside the CI/CD job and let the firebase cli use it automatically. - // Even when using [Process.includeParentEnvironment] the variables - // are not passed to the firebase cli. - // - // Thus the CI/CD script can pass the - // [googleApplicationCredentialsFile] manually via an command line - // option and we set the GOOGLE_APPLICATION_CREDENTIALS manually - // below. - if (googleApplicationCredentialsFile != null) { - // Not sure if it is wrong usage of the environment variable (i.e. we - // shouldn't modify it in this way but rather create a new/modified - // ProcessRunner). - processRunner.environment['GOOGLE_APPLICATION_CREDENTIALS'] = - googleApplicationCredentialsFile.absolute.path; - } - - await processRunner.runProcess( + await processRunner.runProcessCustom( [ 'firebase', 'deploy', @@ -137,6 +117,24 @@ class DeployWebAppCommand extends Command { deployMessage ?? overriddenDeployMessage!, ], workingDirectory: _repo.sharezoneFlutterApp.location, + addedEnvironment: { + // If we run this inside the CI/CD system we want this call to be + // authenticated via the GOOGLE_APPLICATION_CREDENTIALS environment + // variable. + // + // Unfortunately it doesn't work to export the environment variable + // inside the CI/CD job and let the firebase cli use it automatically. + // Even when using [Process.includeParentEnvironment] the variables + // are not passed to the firebase cli. + // + // Thus the CI/CD script can pass the + // [googleApplicationCredentialsFile] manually via an command line + // option and we set the GOOGLE_APPLICATION_CREDENTIALS manually + // below. + if (googleApplicationCredentialsFile != null) + 'GOOGLE_APPLICATION_CREDENTIALS': + googleApplicationCredentialsFile.absolute.path + }, ); } diff --git a/tools/sz_repo_cli/lib/src/common/src/process_runner_utils.dart b/tools/sz_repo_cli/lib/src/common/src/process_runner_utils.dart new file mode 100644 index 000000000..f250544fa --- /dev/null +++ b/tools/sz_repo_cli/lib/src/common/src/process_runner_utils.dart @@ -0,0 +1,71 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:process/process.dart'; +import 'package:process_runner/process_runner.dart'; + +extension ProcessRunnerCopyWith on ProcessRunner { + ProcessRunner copyWith({ + Directory? defaultWorkingDirectory, + ProcessManager? processManager, + Map? environment, + bool? includeParentEnvironment, + bool? printOutputDefault, + Encoding? decoder, + }) { + return ProcessRunner( + defaultWorkingDirectory: + defaultWorkingDirectory ?? this.defaultWorkingDirectory, + processManager: processManager ?? this.processManager, + environment: environment ?? this.environment, + includeParentEnvironment: + includeParentEnvironment ?? this.includeParentEnvironment, + printOutputDefault: printOutputDefault ?? this.printOutputDefault, + decoder: decoder ?? this.decoder, + ); + } +} + +extension RunProcessCustom on ProcessRunner { + /// Wraps [ProcessRunner.runProcess] and adds the [addedEnvironment] argument. + /// + /// Run the command and arguments in `commandLine` as a sub-process from + /// `workingDirectory` if set, or the [defaultWorkingDirectory] if not. Uses + /// [Directory.current] if [defaultWorkingDirectory] is not set. + /// + /// [addedEnvironment] will be added to [ProcessRunner.environment]. + /// + /// Set `failOk` if [runProcess] should not throw an exception when the + /// command completes with a a non-zero exit code. + /// + /// If `printOutput` is set, indicates that the command will both write the + /// output to stdout/stderr, as well as return it in the + /// [ProcessRunnerResult.stderr], [ProcessRunnerResult.stderr] members of the + /// result. This overrides the setting of [printOutputDefault]. + /// + /// The `printOutput` argument defaults to the value of [printOutputDefault]. + Future runProcessCustom( + List commandLine, { + Directory? workingDirectory, + bool? printOutput, + bool failOk = false, + Stream>? stdin, + bool runInShell = false, + ProcessStartMode startMode = ProcessStartMode.normal, + Map addedEnvironment = const {}, + }) async { + final environment = this.environment; + environment.addAll(addedEnvironment); + final runner = copyWith(environment: environment); + + return runner.runProcess( + commandLine, + workingDirectory: workingDirectory, + printOutput: printOutput, + failOk: failOk, + stdin: stdin, + runInShell: runInShell, + startMode: startMode, + ); + } +} From 99d705d0f56ce8c10e39a27548bb8508dab58775 Mon Sep 17 00:00:00 2001 From: Jonas Sander <29028262+Jonas-Sander@users.noreply.github.com> Date: Fri, 20 Oct 2023 21:30:58 +0200 Subject: [PATCH 4/8] Use custom `ProcessRunner.run` extension. --- .../lib/src/commands/src/analyze_command.dart | 2 +- .../src/commands/src/build_android_command.dart | 2 +- .../lib/src/commands/src/build_ios_command.dart | 2 +- .../src/commands/src/build_macos_command.dart | 2 +- .../src/build_runner_build_command.dart | 2 +- .../lib/src/commands/src/build_web_command.dart | 2 +- .../commands/src/deploy_android_command.dart | 9 ++++----- .../src/commands/src/deploy_ios_command.dart | 2 +- .../src/commands/src/deploy_macos_command.dart | 4 ++-- .../commands/src/deploy_web_app_command.dart | 7 +++---- .../lib/src/commands/src/format_command.dart | 2 +- .../lib/src/commands/src/pub_get_command.dart | 4 ++-- .../lib/src/commands/src/test_command.dart | 8 ++++---- tools/sz_repo_cli/lib/src/common/common.dart | 5 +++-- .../src/common/src/app_store_connect_utils.dart | 17 +++++++++-------- .../src/common/src/process_runner_utils.dart | 2 +- .../common/src/run_source_of_truth_command.dart | 3 ++- .../src/throw_if_command_is_not_installed.dart | 3 ++- 18 files changed, 40 insertions(+), 38 deletions(-) diff --git a/tools/sz_repo_cli/lib/src/commands/src/analyze_command.dart b/tools/sz_repo_cli/lib/src/commands/src/analyze_command.dart index 5bd5a74b1..69222ef09 100644 --- a/tools/sz_repo_cli/lib/src/commands/src/analyze_command.dart +++ b/tools/sz_repo_cli/lib/src/commands/src/analyze_command.dart @@ -43,7 +43,7 @@ Future analyzePackage( Future _runDartAnalyze( ProcessRunner processRunner, Package package) async { - await processRunner.runProcess( + await processRunner.run( ['fvm', 'dart', 'analyze', '--fatal-infos', '--fatal-warnings'], workingDirectory: package.location, ); diff --git a/tools/sz_repo_cli/lib/src/commands/src/build_android_command.dart b/tools/sz_repo_cli/lib/src/commands/src/build_android_command.dart index a05f38e66..df315177b 100644 --- a/tools/sz_repo_cli/lib/src/commands/src/build_android_command.dart +++ b/tools/sz_repo_cli/lib/src/commands/src/build_android_command.dart @@ -96,7 +96,7 @@ When none is specified, the value from pubspec.yaml is used.''', final buildNumber = argResults![buildNumberOptionName] as String?; final buildNameWithStage = getBuildNameWithStage(_repo.sharezoneFlutterApp, stage); - await processRunner.runProcess( + await processRunner.run( [ 'fvm', 'flutter', diff --git a/tools/sz_repo_cli/lib/src/commands/src/build_ios_command.dart b/tools/sz_repo_cli/lib/src/commands/src/build_ios_command.dart index 775809f2a..a3c546254 100644 --- a/tools/sz_repo_cli/lib/src/commands/src/build_ios_command.dart +++ b/tools/sz_repo_cli/lib/src/commands/src/build_ios_command.dart @@ -86,7 +86,7 @@ When none is specified, the value from pubspec.yaml is used.''', final stage = argResults![releaseStageOptionName] as String; final buildNumber = argResults![buildNumberOptionName] as String?; final exportOptionsPlist = argResults![exportOptionsPlistName] as String?; - await processRunner.runProcess( + await processRunner.run( [ 'fvm', 'flutter', diff --git a/tools/sz_repo_cli/lib/src/commands/src/build_macos_command.dart b/tools/sz_repo_cli/lib/src/commands/src/build_macos_command.dart index 958fb445c..dbc98be25 100644 --- a/tools/sz_repo_cli/lib/src/commands/src/build_macos_command.dart +++ b/tools/sz_repo_cli/lib/src/commands/src/build_macos_command.dart @@ -65,7 +65,7 @@ When none is specified, the value from pubspec.yaml is used.''', const flavor = 'prod'; final stage = argResults![releaseStageOptionName] as String; final buildNumber = argResults![buildNumberOptionName] as String?; - await processRunner.runProcess( + await processRunner.run( [ 'fvm', 'flutter', diff --git a/tools/sz_repo_cli/lib/src/commands/src/build_runner_build_command.dart b/tools/sz_repo_cli/lib/src/commands/src/build_runner_build_command.dart index 3cf2905ba..100584f65 100644 --- a/tools/sz_repo_cli/lib/src/commands/src/build_runner_build_command.dart +++ b/tools/sz_repo_cli/lib/src/commands/src/build_runner_build_command.dart @@ -35,7 +35,7 @@ class BuildRunnerBuild extends ConcurrentCommand { @override Future runTaskForPackage(Package package) async { - await processRunner.runProcess( + await processRunner.run( [ 'fvm', 'dart', diff --git a/tools/sz_repo_cli/lib/src/commands/src/build_web_command.dart b/tools/sz_repo_cli/lib/src/commands/src/build_web_command.dart index ac8c52f80..88ff1e0c4 100644 --- a/tools/sz_repo_cli/lib/src/commands/src/build_web_command.dart +++ b/tools/sz_repo_cli/lib/src/commands/src/build_web_command.dart @@ -72,7 +72,7 @@ class BuildWebCommand extends Command { final stage = argResults![releaseStageOptionName] as String; final buildNameWithStage = getBuildNameWithStage(_repo.sharezoneFlutterApp, stage); - await processRunner.runProcess( + await processRunner.run( [ 'fvm', 'flutter', diff --git a/tools/sz_repo_cli/lib/src/commands/src/deploy_android_command.dart b/tools/sz_repo_cli/lib/src/commands/src/deploy_android_command.dart index 019aaed81..aed4f55c0 100644 --- a/tools/sz_repo_cli/lib/src/commands/src/deploy_android_command.dart +++ b/tools/sz_repo_cli/lib/src/commands/src/deploy_android_command.dart @@ -12,7 +12,6 @@ import 'package:args/command_runner.dart'; import 'package:process_runner/process_runner.dart'; import 'package:sz_repo_cli/src/common/common.dart'; import 'package:path/path.dart' as path; -import 'package:sz_repo_cli/src/common/src/process_runner_utils.dart'; final _androidStages = [ 'stable', @@ -108,7 +107,7 @@ class DeployAndroidCommand extends Command { /// See https://docs.fastlane.tools/actions/validate_play_store_json_key Future _checkIfGooglePlayCredentialsAreValid( ProcessRunner processRunner) async { - await processRunner.runProcess( + await processRunner.run( ['fastlane', 'run', 'validate_play_store_json_key'], workingDirectory: Directory( path.join(_repo.sharezoneFlutterApp.location.path, 'android')), @@ -128,7 +127,7 @@ class DeployAndroidCommand extends Command { ProcessRunner processRunner) async { try { const packageName = 'de.codingbrain.sharezone'; - final result = await processRunner.runProcess( + final result = await processRunner.run( [ 'google-play', 'get-latest-build-number', @@ -146,7 +145,7 @@ class DeployAndroidCommand extends Command { try { final flavor = argResults![flavorOptionName] as String; final stage = argResults![releaseStageOptionName] as String; - await processRunner.runProcess( + await processRunner.run( [ 'fvm', 'dart', @@ -230,7 +229,7 @@ class DeployAndroidCommand extends Command { required String track, required String rollout, }) async { - await processRunner.runProcessCustom( + await processRunner.run( ['fastlane', 'deploy'], workingDirectory: Directory( path.join(_repo.sharezoneFlutterApp.location.path, '/android')), diff --git a/tools/sz_repo_cli/lib/src/commands/src/deploy_ios_command.dart b/tools/sz_repo_cli/lib/src/commands/src/deploy_ios_command.dart index 2e5398d75..02d0b4dca 100644 --- a/tools/sz_repo_cli/lib/src/commands/src/deploy_ios_command.dart +++ b/tools/sz_repo_cli/lib/src/commands/src/deploy_ios_command.dart @@ -168,7 +168,7 @@ class DeployIosCommand extends Command { final flavor = argResults![flavorOptionName] as String; final stage = argResults![releaseStageOptionName] as String; final exportOptionsPlist = argResults![exportOptionsPlistName] as String?; - await processRunner.runProcess( + await processRunner.run( [ 'fvm', 'dart', diff --git a/tools/sz_repo_cli/lib/src/commands/src/deploy_macos_command.dart b/tools/sz_repo_cli/lib/src/commands/src/deploy_macos_command.dart index 817105cb9..47e4e8a75 100644 --- a/tools/sz_repo_cli/lib/src/commands/src/deploy_macos_command.dart +++ b/tools/sz_repo_cli/lib/src/commands/src/deploy_macos_command.dart @@ -146,7 +146,7 @@ class DeployMacOsCommand extends Command { {required int buildNumber}) async { try { final stage = argResults![releaseStageOptionName] as String; - await processRunner.runProcess( + await processRunner.run( [ 'fvm', 'dart', @@ -174,7 +174,7 @@ class DeployMacOsCommand extends Command { /// The steps are copied from the Flutter docs. You can find more details /// here: https://docs.flutter.dev/deployment/macos#create-a-build-archive-with-codemagic-cli-tools Future _createSignedPackage() async { - await processRunner.runProcess( + await processRunner.run( [ 'bash', '-c', diff --git a/tools/sz_repo_cli/lib/src/commands/src/deploy_web_app_command.dart b/tools/sz_repo_cli/lib/src/commands/src/deploy_web_app_command.dart index 7afbef927..95672f72e 100644 --- a/tools/sz_repo_cli/lib/src/commands/src/deploy_web_app_command.dart +++ b/tools/sz_repo_cli/lib/src/commands/src/deploy_web_app_command.dart @@ -13,7 +13,6 @@ import 'package:args/args.dart'; import 'package:args/command_runner.dart'; import 'package:process_runner/process_runner.dart'; import 'package:sz_repo_cli/src/common/common.dart'; -import 'package:sz_repo_cli/src/common/src/process_runner_utils.dart'; // All apps are deployed in the production firebase project but under different // domains. @@ -86,7 +85,7 @@ class DeployWebAppCommand extends Command { final releaseStage = _parseReleaseStage(argResults!); final webAppConfig = _getMatchingWebAppConfig(releaseStage); - await processRunner.runProcess([ + await processRunner.run([ 'fvm', 'dart', 'run', @@ -105,7 +104,7 @@ class DeployWebAppCommand extends Command { deployMessage = 'Commit: $currentCommit'; } - await processRunner.runProcessCustom( + await processRunner.run( [ 'firebase', 'deploy', @@ -184,7 +183,7 @@ class DeployWebAppCommand extends Command { } Future _getCurrentCommitHash(ProcessRunner processRunner) async { - final res = await processRunner.runProcess(['git', 'rev-parse', 'HEAD']); + final res = await processRunner.run(['git', 'rev-parse', 'HEAD']); if (res.stdout.isEmpty) { stderr.writeln( 'Could not receive the current commit hash: (${res.exitCode}) ${res.stderr}.'); diff --git a/tools/sz_repo_cli/lib/src/commands/src/format_command.dart b/tools/sz_repo_cli/lib/src/commands/src/format_command.dart index 14e7458ba..da2c6494a 100644 --- a/tools/sz_repo_cli/lib/src/commands/src/format_command.dart +++ b/tools/sz_repo_cli/lib/src/commands/src/format_command.dart @@ -71,7 +71,7 @@ Future formatCode( /// Useful for code analysis in CI. bool throwIfCodeChanged = false, }) { - return processRunner.runProcess([ + return processRunner.run([ 'fvm', 'dart', 'format', diff --git a/tools/sz_repo_cli/lib/src/commands/src/pub_get_command.dart b/tools/sz_repo_cli/lib/src/commands/src/pub_get_command.dart index 745c51adc..26a55d051 100644 --- a/tools/sz_repo_cli/lib/src/commands/src/pub_get_command.dart +++ b/tools/sz_repo_cli/lib/src/commands/src/pub_get_command.dart @@ -42,7 +42,7 @@ Future getPackage(ProcessRunner processRunner, Package package) async { Future getPackagesDart( ProcessRunner processRunner, Package package) async { - await processRunner.runProcess( + await processRunner.run( ['fvm', 'dart', 'pub', 'get'], workingDirectory: package.location, ); @@ -50,7 +50,7 @@ Future getPackagesDart( Future getPackagesFlutter( ProcessRunner processRunner, Package package) async { - await processRunner.runProcess( + await processRunner.run( ['fvm', 'flutter', 'pub', 'get'], workingDirectory: package.location, ); diff --git a/tools/sz_repo_cli/lib/src/commands/src/test_command.dart b/tools/sz_repo_cli/lib/src/commands/src/test_command.dart index 1ec2678ee..3bbefe63e 100644 --- a/tools/sz_repo_cli/lib/src/commands/src/test_command.dart +++ b/tools/sz_repo_cli/lib/src/commands/src/test_command.dart @@ -113,7 +113,7 @@ Future _runTestsDart( await getPackage(processRunner, package); - await processRunner.runProcess( + await processRunner.run( ['fvm', 'dart', 'test'], workingDirectory: package.location, ); @@ -131,7 +131,7 @@ Future _runTestsFlutter( return; } - await processRunner.runProcess( + await processRunner.run( [ 'fvm', 'flutter', @@ -148,7 +148,7 @@ Future _runTestsFlutter( // command. Otherwise the throws the Flutter tool throws an error that it // couldn't find the "test_goldens" directory. if (excludeGoldens || !package.hasGoldenTestsDirectory) { - await processRunner.runProcess( + await processRunner.run( ['fvm', 'flutter', 'test'], workingDirectory: package.location, ); @@ -158,7 +158,7 @@ Future _runTestsFlutter( /// Flutter test lässt automatisch flutter pub get laufen. /// Deswegen muss nicht erst noch [getPackages] aufgerufen werden. - await processRunner.runProcess( + await processRunner.run( [ 'fvm', 'flutter', diff --git a/tools/sz_repo_cli/lib/src/common/common.dart b/tools/sz_repo_cli/lib/src/common/common.dart index cead312f6..762bd16b3 100644 --- a/tools/sz_repo_cli/lib/src/common/common.dart +++ b/tools/sz_repo_cli/lib/src/common/common.dart @@ -11,6 +11,7 @@ import 'dart:io'; import 'package:path/path.dart' as path; import 'package:process_runner/process_runner.dart'; +import 'package:sz_repo_cli/src/common/src/process_runner_utils.dart'; export 'src/concurrent_command.dart'; export 'src/concurrent_package_task_runner.dart'; @@ -21,6 +22,7 @@ export 'src/shared_args.dart'; export 'src/sharezone_repo.dart'; export 'src/to_utf8_string_extension.dart'; export 'src/tool_exit.dart'; +export 'src/process_runner_utils.dart'; /// Die globale Variable sollte in Zukunft entfernt und in einen Parameter /// verwandelt werden, oder es sollte ein package wie package:logging benutzt @@ -28,8 +30,7 @@ export 'src/tool_exit.dart'; bool isVerbose = false; Future getProjectRootDirectory(ProcessRunner processRunner) async { - final res = - await processRunner.runProcess(['git', 'rev-parse', '--show-toplevel']); + final res = await processRunner.run(['git', 'rev-parse', '--show-toplevel']); final stdout = res.stdout; // Without [path.canonicalize] the path won't work on Windows as git returns // normal slashes instead the by Windows required backward slashes. diff --git a/tools/sz_repo_cli/lib/src/common/src/app_store_connect_utils.dart b/tools/sz_repo_cli/lib/src/common/src/app_store_connect_utils.dart index 0c4c8dbb1..c878f9f42 100644 --- a/tools/sz_repo_cli/lib/src/common/src/app_store_connect_utils.dart +++ b/tools/sz_repo_cli/lib/src/common/src/app_store_connect_utils.dart @@ -10,6 +10,7 @@ import 'dart:io'; import 'package:args/args.dart'; import 'package:process_runner/process_runner.dart'; +import 'package:sz_repo_cli/src/common/src/process_runner_utils.dart'; import 'package:sz_repo_cli/src/common/src/apple_track.dart'; import 'package:sz_repo_cli/src/common/src/sharezone_repo.dart'; import 'package:sz_repo_cli/src/common/src/throw_if_command_is_not_installed.dart'; @@ -44,7 +45,7 @@ Future setUpSigning( /// [keychainUseLogin]) after the deployment to avoid potential authentication /// issues if you run the deployment on your local machine. Future _keychainInitialize(ProcessRunner processRunner) async { - await processRunner.runProcess(['keychain', 'initialize']); + await processRunner.run(['keychain', 'initialize']); } /// Fetch the code signing files from App Store Connect. @@ -53,7 +54,7 @@ Future _fetchSigningFiles( required AppleSigningConfig config, }) async { const bundleId = 'de.codingbrain.sharezone.app'; - await processRunner.runProcess( + await processRunner.run( [ 'app-store-connect', 'fetch-signing-files', @@ -76,7 +77,7 @@ Future _fetchSigningFiles( } Future _listMacCertificates(ProcessRunner processRunner) async { - await processRunner.runProcess( + await processRunner.run( [ 'app-store-connect', 'list-certificates', @@ -89,12 +90,12 @@ Future _listMacCertificates(ProcessRunner processRunner) async { /// Adds the certificates to the keychain. Future _keychainAddCertificates(ProcessRunner processRunner) async { - await processRunner.runProcess(['keychain', 'add-certificates']); + await processRunner.run(['keychain', 'add-certificates']); } /// Update the Xcode project settings to use fetched code signing profiles. Future _xcodeProjectUseProfiles(ProcessRunner processRunner) async { - await processRunner.runProcess(['xcode-project', 'use-profiles']); + await processRunner.run(['xcode-project', 'use-profiles']); } /// Sets your login keychain as the default to avoid potential authentication @@ -104,7 +105,7 @@ Future _xcodeProjectUseProfiles(ProcessRunner processRunner) async { /// and have previously used the `keychain initialize' command. If you run it on /// a CI server, this step is not necessary. Future keychainUseLogin(ProcessRunner processRunner) async { - await processRunner.runProcess(['keychain', 'use-login']); + await processRunner.run(['keychain', 'use-login']); } Future getNextBuildNumberFromAppStoreConnect( @@ -135,7 +136,7 @@ Future _getLatestBuildNumberFromAppStoreConnect( // From https://appstoreconnect.apple.com/apps/1434868489/ const appId = 1434868489; - final result = await processRunner.runProcess( + final result = await processRunner.run( [ 'app-store-connect', 'get-latest-build-number', @@ -172,7 +173,7 @@ Future publishToAppStoreConnect( stageToTracks: stageToTracks, ); - await processRunner.runProcess( + await processRunner.run( [ 'app-store-connect', 'publish', diff --git a/tools/sz_repo_cli/lib/src/common/src/process_runner_utils.dart b/tools/sz_repo_cli/lib/src/common/src/process_runner_utils.dart index f250544fa..af990105b 100644 --- a/tools/sz_repo_cli/lib/src/common/src/process_runner_utils.dart +++ b/tools/sz_repo_cli/lib/src/common/src/process_runner_utils.dart @@ -44,7 +44,7 @@ extension RunProcessCustom on ProcessRunner { /// result. This overrides the setting of [printOutputDefault]. /// /// The `printOutput` argument defaults to the value of [printOutputDefault]. - Future runProcessCustom( + Future run( List commandLine, { Directory? workingDirectory, bool? printOutput, diff --git a/tools/sz_repo_cli/lib/src/common/src/run_source_of_truth_command.dart b/tools/sz_repo_cli/lib/src/common/src/run_source_of_truth_command.dart index fe175f59a..f0b0556f3 100644 --- a/tools/sz_repo_cli/lib/src/common/src/run_source_of_truth_command.dart +++ b/tools/sz_repo_cli/lib/src/common/src/run_source_of_truth_command.dart @@ -9,6 +9,7 @@ import 'dart:io'; import 'package:process_runner/process_runner.dart'; +import 'package:sz_repo_cli/src/common/src/process_runner_utils.dart'; import 'package:sz_repo_cli/src/common/src/sharezone_repo.dart'; import 'package:yaml/yaml.dart'; @@ -29,7 +30,7 @@ Future runSourceOfTruthCommand( final arguments = _convertIntoArgumentsList(command) ..addAll(argumentsToAppend); - return processRunner.runProcess( + return processRunner.run( arguments, workingDirectory: repo.location, ); diff --git a/tools/sz_repo_cli/lib/src/common/src/throw_if_command_is_not_installed.dart b/tools/sz_repo_cli/lib/src/common/src/throw_if_command_is_not_installed.dart index 2eef79081..b619d0a8b 100644 --- a/tools/sz_repo_cli/lib/src/common/src/throw_if_command_is_not_installed.dart +++ b/tools/sz_repo_cli/lib/src/common/src/throw_if_command_is_not_installed.dart @@ -9,6 +9,7 @@ import 'dart:io'; import 'package:process_runner/process_runner.dart'; +import 'package:sz_repo_cli/src/common/src/process_runner_utils.dart'; /// Throws an exception if [command] is not installed. /// @@ -28,7 +29,7 @@ Future throwIfCommandIsNotInstalled( return; } - final result = await processRunner.runProcess( + final result = await processRunner.run( ['which', '-s', command], failOk: true, ); From d2cdd0a0733f334b34a79de5d6f18b4998eb2b5b Mon Sep 17 00:00:00 2001 From: Jonas Sander <29028262+Jonas-Sander@users.noreply.github.com> Date: Fri, 20 Oct 2023 21:43:48 +0200 Subject: [PATCH 5/8] Add custom `printOutput` behavior. --- .../lib/src/common/src/process_runner_utils.dart | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/tools/sz_repo_cli/lib/src/common/src/process_runner_utils.dart b/tools/sz_repo_cli/lib/src/common/src/process_runner_utils.dart index af990105b..f72dc0671 100644 --- a/tools/sz_repo_cli/lib/src/common/src/process_runner_utils.dart +++ b/tools/sz_repo_cli/lib/src/common/src/process_runner_utils.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:process/process.dart'; import 'package:process_runner/process_runner.dart'; +import 'package:sz_repo_cli/src/common/common.dart'; extension ProcessRunnerCopyWith on ProcessRunner { ProcessRunner copyWith({ @@ -27,14 +28,20 @@ extension ProcessRunnerCopyWith on ProcessRunner { } extension RunProcessCustom on ProcessRunner { - /// Wraps [ProcessRunner.runProcess] and adds the [addedEnvironment] argument. + /// Wraps [ProcessRunner.runProcess] and customizes args and behavior. + /// + /// Modifications: + /// * Add [addedEnvironment] parameter, which will be added to + /// [ProcessRunner.environment]. + /// * Set [printOutput] to true if [isVerbose] is true. + /// If [printOutput] is passed, it overrides [isVerbose]. + /// If [printOutput] is `null` and [isVerbose] is false, [printOutput] will + /// be `null` which means it will use [ProcessRunner.printOutputDefault]. /// /// Run the command and arguments in `commandLine` as a sub-process from /// `workingDirectory` if set, or the [defaultWorkingDirectory] if not. Uses /// [Directory.current] if [defaultWorkingDirectory] is not set. /// - /// [addedEnvironment] will be added to [ProcessRunner.environment]. - /// /// Set `failOk` if [runProcess] should not throw an exception when the /// command completes with a a non-zero exit code. /// @@ -61,7 +68,7 @@ extension RunProcessCustom on ProcessRunner { return runner.runProcess( commandLine, workingDirectory: workingDirectory, - printOutput: printOutput, + printOutput: printOutput ??= isVerbose ? true : null, failOk: failOk, stdin: stdin, runInShell: runInShell, From 0f5f3ab8bc9151d8ab5cd78203ff72374154041c Mon Sep 17 00:00:00 2001 From: Jonas Sander <29028262+Jonas-Sander@users.noreply.github.com> Date: Fri, 20 Oct 2023 21:53:37 +0200 Subject: [PATCH 6/8] Add license header. --- .../lib/src/common/src/process_runner_utils.dart | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tools/sz_repo_cli/lib/src/common/src/process_runner_utils.dart b/tools/sz_repo_cli/lib/src/common/src/process_runner_utils.dart index f72dc0671..b97a490d8 100644 --- a/tools/sz_repo_cli/lib/src/common/src/process_runner_utils.dart +++ b/tools/sz_repo_cli/lib/src/common/src/process_runner_utils.dart @@ -1,3 +1,11 @@ +// Copyright (c) 2023 Sharezone UG (haftungsbeschränkt) +// Licensed under the EUPL-1.2-or-later. +// +// You may obtain a copy of the Licence at: +// https://joinup.ec.europa.eu/software/page/eupl +// +// SPDX-License-Identifier: EUPL-1.2 + import 'dart:convert'; import 'dart:io'; From 87e5e7763f8c64b707d9e257a7b0b7a4395be686 Mon Sep 17 00:00:00 2001 From: Jonas Sander <29028262+Jonas-Sander@users.noreply.github.com> Date: Fri, 20 Oct 2023 22:05:05 +0200 Subject: [PATCH 7/8] Fix Exec command ProcessRunner merge. --- tools/sz_repo_cli/lib/src/commands/src/exec_command.dart | 9 ++++----- tools/sz_repo_cli/lib/src/main.dart | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/tools/sz_repo_cli/lib/src/commands/src/exec_command.dart b/tools/sz_repo_cli/lib/src/commands/src/exec_command.dart index 343a7ff4e..0a491bfee 100644 --- a/tools/sz_repo_cli/lib/src/commands/src/exec_command.dart +++ b/tools/sz_repo_cli/lib/src/commands/src/exec_command.dart @@ -11,7 +11,7 @@ import 'dart:async'; import 'package:sz_repo_cli/src/common/common.dart'; class ExecCommand extends ConcurrentCommand { - ExecCommand(SharezoneRepo repo) : super(repo) { + ExecCommand(super.processRunner, super.repo) { argParser ..addFlag('onlyFlutter', help: 'Only run the command for Flutter packages.', defaultsTo: false) @@ -59,10 +59,9 @@ class ExecCommand extends ConcurrentCommand { @override Future runTaskForPackage(Package package) async { - await runProcessSuccessfullyOrThrow( - argResults!.rest.first, - argResults!.rest.sublist(1), - workingDirectory: package.path, + await processRunner.run( + argResults!.rest, + workingDirectory: package.location, ); } } diff --git a/tools/sz_repo_cli/lib/src/main.dart b/tools/sz_repo_cli/lib/src/main.dart index c33d70033..7a0547d25 100644 --- a/tools/sz_repo_cli/lib/src/main.dart +++ b/tools/sz_repo_cli/lib/src/main.dart @@ -54,7 +54,7 @@ Future main(List args) async { ..addCommand(AnalyzeCommand(processRunner, repo)) ..addCommand(TestCommand(processRunner, repo)) ..addCommand(FormatCommand(processRunner, repo)) - ..addCommand(ExecCommand(repo)) + ..addCommand(ExecCommand(processRunner, repo)) ..addCommand(DoStuffCommand(repo)) ..addCommand(FixCommentSpacingCommand(repo)) ..addCommand( From 3acc7e18707a7f8974cd2cdb9c551be56ed43896 Mon Sep 17 00:00:00 2001 From: Jonas Sander <29028262+Jonas-Sander@users.noreply.github.com> Date: Fri, 20 Oct 2023 22:59:16 +0200 Subject: [PATCH 8/8] Add space --- tools/sz_repo_cli/lib/src/commands/src/exec_command.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/sz_repo_cli/lib/src/commands/src/exec_command.dart b/tools/sz_repo_cli/lib/src/commands/src/exec_command.dart index 0a491bfee..d944c994a 100644 --- a/tools/sz_repo_cli/lib/src/commands/src/exec_command.dart +++ b/tools/sz_repo_cli/lib/src/commands/src/exec_command.dart @@ -36,6 +36,7 @@ class ExecCommand extends ConcurrentCommand { List testFuncs = []; if (onlyDart) testFuncs.add((package) => package.isPureDartPackage); if (onlyFlutter) testFuncs.add((package) => package.isFlutterPackage); + return super.packagesToProcess.where((event) { for (var testFunc in testFuncs) { if (testFunc(event)) {