feat: improve error messages for recognized issues (#2875)
This commit is contained in:
@@ -3,7 +3,7 @@ import 'dart:io';
|
||||
import 'package:scoped_deps/scoped_deps.dart';
|
||||
import 'package:shorebird_cli/src/android_sdk.dart';
|
||||
import 'package:shorebird_cli/src/android_studio.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_manager.dart';
|
||||
import 'package:shorebird_cli/src/auth/auth.dart';
|
||||
import 'package:shorebird_cli/src/cache.dart';
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:shorebird_cli/src/shorebird_process.dart';
|
||||
|
||||
const String _missingKeystoreFixSuggestion =
|
||||
'This error is likely due to a missing keystore file. You can read about Android app signing here: https://developer.android.com/studio/publish/app-signing.';
|
||||
|
||||
/// {@template artifact_build_exception}
|
||||
/// Thrown when a build fails.
|
||||
/// {@endtemplate}
|
||||
class ArtifactBuildException implements Exception {
|
||||
/// {@macro artifact_build_exception}
|
||||
ArtifactBuildException(
|
||||
this.message, {
|
||||
List<String>? stdout,
|
||||
List<String>? stderr,
|
||||
String? fixRecommendation,
|
||||
}) : stdout = stdout ?? [],
|
||||
stderr = stderr ?? [] {
|
||||
flutterError =
|
||||
_errorMessageFromOutput(this.stdout + this.stderr).join('\n');
|
||||
this.fixRecommendation = fixRecommendation ??
|
||||
_recommendationFromOutput(this.stdout + this.stderr);
|
||||
}
|
||||
|
||||
/// {@macro artifact_build_exception}
|
||||
factory ArtifactBuildException.fromProcessResult(
|
||||
String message, {
|
||||
required ShorebirdProcessResult buildProcessResult,
|
||||
String? fixRecommendation,
|
||||
}) {
|
||||
return ArtifactBuildException(
|
||||
message,
|
||||
stdout: const LineSplitter().convert('${buildProcessResult.stdout}'),
|
||||
stderr: const LineSplitter().convert('${buildProcessResult.stderr}'),
|
||||
fixRecommendation: fixRecommendation,
|
||||
);
|
||||
}
|
||||
|
||||
/// Information about the build failure.
|
||||
late final String message;
|
||||
|
||||
/// The stdout output from the build process, split into lines.
|
||||
final List<String> stdout;
|
||||
|
||||
/// The stderr output from the build process, split into lines.
|
||||
final List<String> stderr;
|
||||
|
||||
/// The relevant error message (if we can find one) from the Flutter build
|
||||
/// output.
|
||||
late final String? flutterError;
|
||||
|
||||
/// An optional tip to help the user fix the build failure.
|
||||
late final String? fixRecommendation;
|
||||
|
||||
List<String> _errorMessageFromOutput(List<String> output) {
|
||||
final failureHeader =
|
||||
RegExp(r'.*FAILURE: Build failed with an exception\..*');
|
||||
// This precedes a stack trace
|
||||
final stackTraceHeader = RegExp(r'.*\* Exception is:.*');
|
||||
|
||||
// This precedes recommendations that are not applicable to us (e.g., "Get
|
||||
// more help at https://help.gradle.org.")
|
||||
final suggestionsHeader = RegExp(r'.*\* Try:.*');
|
||||
|
||||
String trimLine(String line) {
|
||||
return line.trim().replaceAll(RegExp(r'^\[.*\]'), '');
|
||||
}
|
||||
|
||||
var inErrorOutput = false;
|
||||
final ret = <String>[];
|
||||
for (final line in output) {
|
||||
if (failureHeader.hasMatch(line)) {
|
||||
inErrorOutput = true;
|
||||
} else if (stackTraceHeader.hasMatch(line) ||
|
||||
suggestionsHeader.hasMatch(line)) {
|
||||
inErrorOutput = false;
|
||||
}
|
||||
|
||||
if (inErrorOutput) {
|
||||
ret.add(trimLine(line));
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// Maps lists of regular expressions to a recommendation. We use a list of
|
||||
/// regular expressions instead of a single regular expression to allow for
|
||||
/// multiple possible error messages that have the same root cause.
|
||||
final _regexpToRecommendations = {
|
||||
(
|
||||
[RegExp("Execution failed for task ':app:signReleaseBundle'")],
|
||||
_missingKeystoreFixSuggestion,
|
||||
),
|
||||
};
|
||||
|
||||
String? _recommendationFromOutput(List<String> output) {
|
||||
for (final entry in _regexpToRecommendations) {
|
||||
final regexes = entry.$1;
|
||||
for (final regexp in regexes) {
|
||||
if (output.any(regexp.hasMatch)) {
|
||||
return entry.$2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+66
-46
@@ -8,6 +8,7 @@ import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:scoped_deps/scoped_deps.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder/artifact_build_exception.dart';
|
||||
import 'package:shorebird_cli/src/artifact_manager.dart';
|
||||
import 'package:shorebird_cli/src/logging/logging.dart';
|
||||
import 'package:shorebird_cli/src/os/operating_system_interface.dart';
|
||||
@@ -18,6 +19,8 @@ import 'package:shorebird_cli/src/shorebird_documentation.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_env.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_process.dart';
|
||||
|
||||
export 'artifact_build_exception.dart';
|
||||
|
||||
/// Used to wrap code that invokes `flutter build` with Shorebird's fork of
|
||||
/// Flutter.
|
||||
typedef ShorebirdBuildCommand = Future<void> Function();
|
||||
@@ -62,20 +65,6 @@ class MacosBuildResult {
|
||||
final File kernelFile;
|
||||
}
|
||||
|
||||
/// {@template artifact_build_exception}
|
||||
/// Thrown when a build fails.
|
||||
/// {@endtemplate}
|
||||
class ArtifactBuildException implements Exception {
|
||||
/// {@macro artifact_build_exception}
|
||||
ArtifactBuildException(this.message);
|
||||
|
||||
/// Information about the build failure.
|
||||
final String message;
|
||||
|
||||
@override
|
||||
String toString() => message;
|
||||
}
|
||||
|
||||
/// A reference to a [ArtifactBuilder] instance.
|
||||
final artifactBuilderRef = create(ArtifactBuilder.new);
|
||||
|
||||
@@ -132,10 +121,12 @@ class ArtifactBuilder {
|
||||
// this format. We can use the 'Task :' line to get the current task
|
||||
// being run.
|
||||
final gradleTaskRegex = RegExp(r'^\[.*\] \> (Task :.*)$');
|
||||
final stdoutLines = <String>[];
|
||||
buildProcess.stdout
|
||||
.transform(utf8.decoder)
|
||||
.transform(const LineSplitter())
|
||||
.listen((line) {
|
||||
stdoutLines.add(line);
|
||||
if (buildProgress == null) {
|
||||
return;
|
||||
}
|
||||
@@ -149,10 +140,13 @@ class ArtifactBuilder {
|
||||
.transform(utf8.decoder)
|
||||
.transform(const LineSplitter())
|
||||
.toList();
|
||||
final stdErr = stderrLines.join('\n');
|
||||
final exitCode = await buildProcess.exitCode;
|
||||
if (exitCode != ExitCode.success.code) {
|
||||
throw ArtifactBuildException('Failed to build: $stdErr');
|
||||
throw ArtifactBuildException(
|
||||
'Failed to build',
|
||||
stderr: stderrLines,
|
||||
stdout: stdoutLines,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -217,8 +211,9 @@ class ArtifactBuilder {
|
||||
);
|
||||
|
||||
if (result.exitCode != ExitCode.success.code) {
|
||||
throw ArtifactBuildException(
|
||||
'Failed to build: ${result.stderr}',
|
||||
throw ArtifactBuildException.fromProcessResult(
|
||||
'Failed to build',
|
||||
buildProcessResult: result,
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -269,7 +264,10 @@ class ArtifactBuilder {
|
||||
);
|
||||
|
||||
if (result.exitCode != ExitCode.success.code) {
|
||||
throw ArtifactBuildException('Failed to build: ${result.stderr}');
|
||||
throw ArtifactBuildException.fromProcessResult(
|
||||
'Failed to build',
|
||||
buildProcessResult: result,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -298,21 +296,26 @@ class ArtifactBuilder {
|
||||
environment: base64PublicKey?.toPublicKeyEnv(),
|
||||
);
|
||||
|
||||
final stdoutLines = <String>[];
|
||||
buildProcess.stdout
|
||||
.transform(utf8.decoder)
|
||||
.transform(const LineSplitter())
|
||||
.listen((line) {
|
||||
logger.detail(line);
|
||||
stdoutLines.add(line);
|
||||
});
|
||||
|
||||
final stderrLines = await buildProcess.stderr
|
||||
.transform(utf8.decoder)
|
||||
.transform(const LineSplitter())
|
||||
.toList();
|
||||
final stdErr = stderrLines.join('\n');
|
||||
final exitCode = await buildProcess.exitCode;
|
||||
if (exitCode != ExitCode.success.code) {
|
||||
throw ArtifactBuildException('Failed to build: $stdErr');
|
||||
throw ArtifactBuildException(
|
||||
'Failed to build',
|
||||
stdout: stdoutLines,
|
||||
stderr: stderrLines,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -377,24 +380,25 @@ class ArtifactBuilder {
|
||||
.transform(utf8.decoder)
|
||||
.transform(const LineSplitter())
|
||||
.toList();
|
||||
final stderr = stderrLines.join('\n');
|
||||
final stdout = stdoutLines.join('\n');
|
||||
final exitCode = await buildProcess.exitCode;
|
||||
if (exitCode != ExitCode.success.code) {
|
||||
throw ArtifactBuildException('''
|
||||
Failed to build
|
||||
stdout: $stdout
|
||||
stderr: $stderr''');
|
||||
throw ArtifactBuildException(
|
||||
'Failed to build',
|
||||
stdout: stdoutLines,
|
||||
stderr: stderrLines,
|
||||
);
|
||||
}
|
||||
|
||||
appDillPath = findAppDill(stdout: stdout);
|
||||
});
|
||||
|
||||
if (appDillPath == null) {
|
||||
throw ArtifactBuildException('''
|
||||
Unable to find app.dill file.
|
||||
Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with the logs for this command.
|
||||
''');
|
||||
throw ArtifactBuildException(
|
||||
'Unable to find app.dill file.',
|
||||
fixRecommendation:
|
||||
'''Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with the logs for this command.''',
|
||||
);
|
||||
}
|
||||
|
||||
return MacosBuildResult(kernelFile: File(appDillPath!));
|
||||
@@ -459,23 +463,32 @@ Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with
|
||||
buildProgress?.updateDetailMessage(null);
|
||||
|
||||
if (exitCode != ExitCode.success.code) {
|
||||
throw ArtifactBuildException('Failed to build: $stderr');
|
||||
throw ArtifactBuildException(
|
||||
'Failed to build',
|
||||
stdout: stdoutLines,
|
||||
stderr: stderrLines,
|
||||
);
|
||||
}
|
||||
|
||||
// TODO(https://github.com/shorebirdtech/shorebird/issues/2855): this is
|
||||
// not treated as an error by Flutter, we should not throw here.
|
||||
if (stderr.contains('Encountered error while creating the IPA')) {
|
||||
throw ArtifactBuildException('''
|
||||
Failed to build:
|
||||
$stderr''');
|
||||
throw ArtifactBuildException(
|
||||
'Failed to build',
|
||||
stdout: stdoutLines,
|
||||
stderr: stderrLines,
|
||||
);
|
||||
}
|
||||
|
||||
appDillPath = findAppDill(stdout: stdout);
|
||||
});
|
||||
|
||||
if (appDillPath == null) {
|
||||
throw ArtifactBuildException('''
|
||||
Unable to find app.dill file.
|
||||
Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with the logs for this command.
|
||||
''');
|
||||
throw ArtifactBuildException(
|
||||
'Unable to find app.dill file.',
|
||||
fixRecommendation:
|
||||
'''Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with the logs for this command.''',
|
||||
);
|
||||
}
|
||||
|
||||
return IpaBuildResult(kernelFile: File(appDillPath!));
|
||||
@@ -510,10 +523,11 @@ Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with
|
||||
});
|
||||
|
||||
if (appDillPath == null) {
|
||||
throw ArtifactBuildException('''
|
||||
Unable to find app.dill file.
|
||||
Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with the logs for this command.
|
||||
''');
|
||||
throw ArtifactBuildException(
|
||||
'Unable to find app.dill file.',
|
||||
fixRecommendation:
|
||||
'''Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with the logs for this command.''',
|
||||
);
|
||||
}
|
||||
|
||||
return IosFrameworkBuildResult(kernelFile: File(appDillPath!));
|
||||
@@ -586,8 +600,9 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod
|
||||
);
|
||||
|
||||
if (result.exitCode != ExitCode.success.code) {
|
||||
throw ArtifactBuildException(
|
||||
'Failed to create snapshot: ${result.stderr}',
|
||||
throw ArtifactBuildException.fromProcessResult(
|
||||
'Failed to create snapshot',
|
||||
buildProcessResult: result,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -618,11 +633,13 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod
|
||||
environment: base64PublicKey?.toPublicKeyEnv(),
|
||||
);
|
||||
|
||||
final stdoutLines = <String>[];
|
||||
buildProcess.stdout
|
||||
.transform(utf8.decoder)
|
||||
.transform(const LineSplitter())
|
||||
.listen((line) {
|
||||
logger.detail(line);
|
||||
stdoutLines.add(line);
|
||||
// TODO(bryanoltman): update build progress
|
||||
});
|
||||
|
||||
@@ -630,10 +647,13 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod
|
||||
.transform(utf8.decoder)
|
||||
.transform(const LineSplitter())
|
||||
.toList();
|
||||
final stdErr = stderrLines.join('\n');
|
||||
final exitCode = await buildProcess.exitCode;
|
||||
if (exitCode != ExitCode.success.code) {
|
||||
throw ArtifactBuildException('Failed to build: $stdErr');
|
||||
throw ArtifactBuildException(
|
||||
'Failed to build',
|
||||
stderr: stderrLines,
|
||||
stdout: stdoutLines,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@ import 'package:io/io.dart';
|
||||
import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:shorebird_cli/src/archive_analysis/android_archive_differ.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_manager.dart';
|
||||
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
|
||||
import 'package:shorebird_cli/src/commands/patch/patch.dart';
|
||||
|
||||
@@ -6,7 +6,7 @@ import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:pub_semver/pub_semver.dart';
|
||||
import 'package:shorebird_cli/src/archive_analysis/android_archive_differ.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_manager.dart';
|
||||
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
|
||||
import 'package:shorebird_cli/src/code_signer.dart';
|
||||
|
||||
@@ -7,7 +7,7 @@ import 'package:path/path.dart' as p;
|
||||
import 'package:platform/platform.dart';
|
||||
import 'package:shorebird_cli/src/archive/directory_archive.dart';
|
||||
import 'package:shorebird_cli/src/archive_analysis/apple_archive_differ.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_manager.dart';
|
||||
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
|
||||
import 'package:shorebird_cli/src/commands/patch/patch.dart';
|
||||
|
||||
@@ -8,7 +8,7 @@ import 'package:path/path.dart' as p;
|
||||
import 'package:platform/platform.dart';
|
||||
import 'package:shorebird_cli/src/archive/archive.dart';
|
||||
import 'package:shorebird_cli/src/archive_analysis/archive_analysis.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_manager.dart';
|
||||
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
|
||||
import 'package:shorebird_cli/src/code_signer.dart';
|
||||
|
||||
@@ -5,7 +5,7 @@ import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:shorebird_cli/src/archive/archive.dart';
|
||||
import 'package:shorebird_cli/src/archive_analysis/linux_bundle_differ.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_manager.dart';
|
||||
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
|
||||
import 'package:shorebird_cli/src/code_signer.dart';
|
||||
|
||||
@@ -6,7 +6,7 @@ import 'package:path/path.dart' as p;
|
||||
import 'package:platform/platform.dart';
|
||||
import 'package:shorebird_cli/src/archive_analysis/apple_archive_differ.dart';
|
||||
import 'package:shorebird_cli/src/archive_analysis/plist.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_manager.dart';
|
||||
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
|
||||
import 'package:shorebird_cli/src/code_signer.dart';
|
||||
|
||||
@@ -6,7 +6,7 @@ import 'package:path/path.dart' as p;
|
||||
import 'package:platform/platform.dart';
|
||||
import 'package:shorebird_cli/src/archive/archive.dart';
|
||||
import 'package:shorebird_cli/src/archive_analysis/windows_archive_differ.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_manager.dart';
|
||||
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
|
||||
import 'package:shorebird_cli/src/code_signer.dart';
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'package:archive/archive_io.dart';
|
||||
import 'package:io/io.dart';
|
||||
import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:shorebird_cli/src/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
|
||||
import 'package:shorebird_cli/src/commands/release/releaser.dart';
|
||||
import 'package:shorebird_cli/src/extensions/arg_results.dart';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
|
||||
import 'package:shorebird_cli/src/commands/release/release.dart';
|
||||
import 'package:shorebird_cli/src/commands/release/releaser.dart';
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'package:io/io.dart';
|
||||
import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:platform/platform.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_manager.dart';
|
||||
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
|
||||
import 'package:shorebird_cli/src/commands/release/releaser.dart';
|
||||
|
||||
@@ -5,7 +5,7 @@ import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:platform/platform.dart';
|
||||
import 'package:shorebird_cli/src/archive_analysis/plist.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_manager.dart';
|
||||
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
|
||||
import 'package:shorebird_cli/src/commands/release/releaser.dart';
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'dart:io';
|
||||
|
||||
import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:platform/platform.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_manager.dart';
|
||||
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
|
||||
import 'package:shorebird_cli/src/commands/release/releaser.dart';
|
||||
|
||||
@@ -5,7 +5,7 @@ import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:platform/platform.dart';
|
||||
import 'package:shorebird_cli/src/archive_analysis/plist.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_manager.dart';
|
||||
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
|
||||
import 'package:shorebird_cli/src/commands/release/release.dart';
|
||||
|
||||
@@ -4,12 +4,14 @@ import 'dart:io';
|
||||
import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:scoped_deps/scoped_deps.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/cache.dart';
|
||||
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
|
||||
import 'package:shorebird_cli/src/commands/release/release.dart';
|
||||
import 'package:shorebird_cli/src/common_arguments.dart';
|
||||
import 'package:shorebird_cli/src/config/config.dart';
|
||||
import 'package:shorebird_cli/src/extensions/arg_results.dart';
|
||||
import 'package:shorebird_cli/src/extensions/string.dart';
|
||||
import 'package:shorebird_cli/src/logging/logging.dart';
|
||||
import 'package:shorebird_cli/src/metadata/metadata.dart';
|
||||
import 'package:shorebird_cli/src/platform.dart';
|
||||
@@ -278,8 +280,29 @@ of the iOS app that is using this module. (aar and ios-framework only)''',
|
||||
);
|
||||
final FileSystemEntity releaseArtifact;
|
||||
try {
|
||||
releaseArtifact = await releaser.buildReleaseArtifacts();
|
||||
releaseArtifact = await releaser.buildReleaseArtifacts(
|
||||
progress: buildProgress,
|
||||
);
|
||||
buildProgress.complete();
|
||||
} on ArtifactBuildException catch (e) {
|
||||
buildProgress.fail(e.message);
|
||||
logger
|
||||
..detail('stdout: ${e.stdout.join(Platform.lineTerminator)}')
|
||||
..detail('stderr: ${e.stderr.join(Platform.lineTerminator)}');
|
||||
if (!e.flutterError.isNullOrEmpty) {
|
||||
logger.err(e.flutterError);
|
||||
}
|
||||
if (!e.fixRecommendation.isNullOrEmpty) {
|
||||
logger.info(e.fixRecommendation);
|
||||
}
|
||||
if (e.fixRecommendation.isNullOrEmpty &&
|
||||
e.flutterError.isNullOrEmpty) {
|
||||
// If we have no fix recommendation or were unable to parse a
|
||||
// flutter error, fall back to printing the raw stderr.
|
||||
logger.info(e.stderr.join(Platform.lineTerminator));
|
||||
}
|
||||
|
||||
throw ProcessExit(ExitCode.software.code);
|
||||
} on Exception catch (e) {
|
||||
buildProgress.fail('Failed to build release artifacts: $e');
|
||||
throw ProcessExit(ExitCode.software.code);
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:platform/platform.dart';
|
||||
import 'package:shorebird_cli/src/archive/archive.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_manager.dart';
|
||||
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
|
||||
import 'package:shorebird_cli/src/commands/release/releaser.dart';
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_process.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
group(ArtifactBuildException, () {
|
||||
group('fromProcessResult', () {
|
||||
test('translates stdout and stderr to lists of strings', () {
|
||||
const buildProcessResult = ShorebirdProcessResult(
|
||||
exitCode: 1,
|
||||
stdout: 'stdout',
|
||||
stderr: 'stderr',
|
||||
);
|
||||
|
||||
final exception = ArtifactBuildException.fromProcessResult(
|
||||
'message',
|
||||
buildProcessResult: buildProcessResult,
|
||||
);
|
||||
|
||||
expect(exception.stdout, ['stdout']);
|
||||
expect(exception.stderr, ['stderr']);
|
||||
});
|
||||
});
|
||||
|
||||
group('when no errors are recognized', () {
|
||||
test('returns a message with no fix recommendation or Flutter error', () {
|
||||
final exception = ArtifactBuildException(
|
||||
'message',
|
||||
stderr: ['some stderr output'],
|
||||
stdout: ['some stdout output'],
|
||||
);
|
||||
expect(exception.flutterError, isEmpty);
|
||||
expect(exception.fixRecommendation, isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('when an error is recognized but no fix recommendation is found', () {
|
||||
test('has a Flutter error and no fix recommendation', () {
|
||||
final exception = ArtifactBuildException(
|
||||
'message',
|
||||
stderr: [
|
||||
'some stderr output',
|
||||
'FAILURE: Build failed with an exception.',
|
||||
'* Exception is:',
|
||||
'some stack trace',
|
||||
'* Try:',
|
||||
'some recommendation',
|
||||
],
|
||||
stdout: ['some stdout output'],
|
||||
);
|
||||
expect(
|
||||
exception.flutterError,
|
||||
equals('FAILURE: Build failed with an exception.'),
|
||||
);
|
||||
expect(exception.fixRecommendation, isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('when a known error is recognized and a fix recommendation is found',
|
||||
() {
|
||||
test('has a Flutter error and a fix recommendation', () {
|
||||
final exception = ArtifactBuildException(
|
||||
'message',
|
||||
stderr: [
|
||||
'some stderr output',
|
||||
'FAILURE: Build failed with an exception.',
|
||||
'* What went wrong:',
|
||||
"Execution failed for task ':app:signReleaseBundle'.",
|
||||
r'''> A failure occurred while executing com.android.build.gradle.internal.tasks.FinalizeBundleTask$BundleToolRunnable''',
|
||||
'> java.lang.NullPointerException (no error message)',
|
||||
'* Exception is:',
|
||||
'some stack trace',
|
||||
'* Try:',
|
||||
'some recommendation',
|
||||
],
|
||||
stdout: ['some stdout output'],
|
||||
);
|
||||
expect(
|
||||
exception.flutterError,
|
||||
equals(r'''
|
||||
FAILURE: Build failed with an exception.
|
||||
* What went wrong:
|
||||
Execution failed for task ':app:signReleaseBundle'.
|
||||
> A failure occurred while executing com.android.build.gradle.internal.tasks.FinalizeBundleTask$BundleToolRunnable
|
||||
> java.lang.NullPointerException (no error message)'''),
|
||||
);
|
||||
expect(
|
||||
exception.fixRecommendation,
|
||||
contains('This error is likely due to a missing keystore file'),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('when a fix recommendation is provided', () {
|
||||
test('does not read output to find fix recommendation', () {
|
||||
final exception = ArtifactBuildException(
|
||||
'message',
|
||||
fixRecommendation: 'some recommendation',
|
||||
stderr: [
|
||||
'some stderr output',
|
||||
'FAILURE: Build failed with an exception.',
|
||||
'* Exception is:',
|
||||
'some stack trace',
|
||||
'* Try:',
|
||||
'some recommendation',
|
||||
],
|
||||
stdout: ['some stdout output'],
|
||||
);
|
||||
expect(
|
||||
exception.flutterError,
|
||||
'FAILURE: Build failed with an exception.',
|
||||
);
|
||||
expect(exception.fixRecommendation, equals('some recommendation'));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
+50
-42
@@ -5,7 +5,7 @@ import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:scoped_deps/scoped_deps.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_manager.dart';
|
||||
import 'package:shorebird_cli/src/logging/logging.dart';
|
||||
import 'package:shorebird_cli/src/os/operating_system_interface.dart';
|
||||
@@ -17,19 +17,10 @@ import 'package:shorebird_cli/src/shorebird_env.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_process.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'fakes.dart';
|
||||
import 'mocks.dart';
|
||||
import '../fakes.dart';
|
||||
import '../mocks.dart';
|
||||
|
||||
void main() {
|
||||
group(ArtifactBuildException, () {
|
||||
test('toString is message', () {
|
||||
expect(
|
||||
ArtifactBuildException('my message').toString(),
|
||||
equals('my message'),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group(ArtifactBuilder, () {
|
||||
final projectRoot = Directory.systemTemp.createTempSync();
|
||||
late Apple apple;
|
||||
@@ -760,7 +751,7 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod
|
||||
isA<ArtifactBuildException>().having(
|
||||
(e) => e.message,
|
||||
'message',
|
||||
equals('Failed to build: stderr contents'),
|
||||
equals('Failed to build'),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -1002,13 +993,16 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod
|
||||
expect(
|
||||
() => runWithOverrides(() => builder.buildMacos(codesign: false)),
|
||||
throwsA(
|
||||
isA<ArtifactBuildException>().having(
|
||||
isA<ArtifactBuildException>()
|
||||
.having(
|
||||
(e) => e.message,
|
||||
'message',
|
||||
'''
|
||||
Unable to find app.dill file.
|
||||
Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with the logs for this command.
|
||||
''',
|
||||
'Unable to find app.dill file.',
|
||||
)
|
||||
.having(
|
||||
(e) => e.fixRecommendation,
|
||||
'fixRecommendation',
|
||||
'''Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with the logs for this command.''',
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -1267,11 +1261,13 @@ error: exportArchive: No signing certificate "iOS Distribution" found''',
|
||||
expect(
|
||||
() => runWithOverrides(() => builder.buildIpa(codesign: false)),
|
||||
throwsA(
|
||||
isA<ArtifactBuildException>().having(
|
||||
(e) => e.message,
|
||||
'message',
|
||||
'''
|
||||
Failed to build:
|
||||
isA<ArtifactBuildException>()
|
||||
.having((e) => e.message, 'message', 'Failed to build')
|
||||
.having(
|
||||
(e) => e.stderr,
|
||||
'stderr',
|
||||
const LineSplitter().convert(
|
||||
'''
|
||||
Encountered error while creating the IPA:
|
||||
error: exportArchive: Communication with Apple failed
|
||||
error: exportArchive: No signing certificate "iOS Distribution" found
|
||||
@@ -1285,7 +1281,8 @@ error: exportArchive: Communication with Apple failed
|
||||
error: exportArchive: No signing certificate "iOS Distribution" found
|
||||
error: exportArchive: Communication with Apple failed
|
||||
error: exportArchive: No signing certificate "iOS Distribution" found''',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
@@ -1321,11 +1318,16 @@ error: exportArchive No signing certificate "iOS Distribution" found''',
|
||||
expect(
|
||||
() => runWithOverrides(() => builder.buildIpa(codesign: false)),
|
||||
throwsA(
|
||||
isA<ArtifactBuildException>().having(
|
||||
(e) => e.message,
|
||||
'message',
|
||||
'''
|
||||
Failed to build:
|
||||
isA<ArtifactBuildException>()
|
||||
.having(
|
||||
(e) => e.message,
|
||||
'message',
|
||||
'Failed to build',
|
||||
)
|
||||
.having(
|
||||
(e) => e.stderr,
|
||||
'stderr',
|
||||
const LineSplitter().convert('''
|
||||
Encountered error while creating the IPA:
|
||||
error: exportArchive Communication with Apple failed
|
||||
error: exportArchive No signing certificate "iOS Distribution" found
|
||||
@@ -1338,8 +1340,8 @@ error: exportArchive No signing certificate "iOS Distribution" found
|
||||
error: exportArchive Communication with Apple failed
|
||||
error: exportArchive No signing certificate "iOS Distribution" found
|
||||
error: exportArchive Communication with Apple failed
|
||||
error: exportArchive No signing certificate "iOS Distribution" found''',
|
||||
),
|
||||
error: exportArchive No signing certificate "iOS Distribution" found'''),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
@@ -1361,13 +1363,16 @@ error: exportArchive No signing certificate "iOS Distribution" found''',
|
||||
expect(
|
||||
() => runWithOverrides(() => builder.buildIpa(codesign: false)),
|
||||
throwsA(
|
||||
isA<ArtifactBuildException>().having(
|
||||
isA<ArtifactBuildException>()
|
||||
.having(
|
||||
(e) => e.message,
|
||||
'message',
|
||||
'''
|
||||
Unable to find app.dill file.
|
||||
Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with the logs for this command.
|
||||
''',
|
||||
'Unable to find app.dill file.',
|
||||
)
|
||||
.having(
|
||||
(e) => e.fixRecommendation,
|
||||
'fixRecommendation',
|
||||
'''Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with the logs for this command.''',
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -1479,13 +1484,16 @@ Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with
|
||||
expect(
|
||||
() => runWithOverrides(builder.buildIosFramework),
|
||||
throwsA(
|
||||
isA<ArtifactBuildException>().having(
|
||||
isA<ArtifactBuildException>()
|
||||
.having(
|
||||
(e) => e.message,
|
||||
'message',
|
||||
'''
|
||||
Unable to find app.dill file.
|
||||
Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with the logs for this command.
|
||||
''',
|
||||
'Unable to find app.dill file.',
|
||||
)
|
||||
.having(
|
||||
(e) => e.fixRecommendation,
|
||||
'fixRecommendation',
|
||||
'''Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with the logs for this command.''',
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -1630,7 +1638,7 @@ Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with
|
||||
isA<ArtifactBuildException>().having(
|
||||
(e) => e.message,
|
||||
'message',
|
||||
equals('Failed to build: stderr contents'),
|
||||
equals('Failed to build'),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -6,7 +6,7 @@ import 'package:mocktail/mocktail.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:scoped_deps/scoped_deps.dart';
|
||||
import 'package:shorebird_cli/src/archive_analysis/android_archive_differ.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_manager.dart';
|
||||
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
|
||||
import 'package:shorebird_cli/src/commands/patch/patch.dart';
|
||||
|
||||
@@ -8,7 +8,7 @@ import 'package:path/path.dart' as p;
|
||||
import 'package:pub_semver/pub_semver.dart';
|
||||
import 'package:scoped_deps/scoped_deps.dart';
|
||||
import 'package:shorebird_cli/src/archive_analysis/android_archive_differ.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_manager.dart';
|
||||
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
|
||||
import 'package:shorebird_cli/src/code_signer.dart';
|
||||
|
||||
@@ -7,7 +7,7 @@ import 'package:path/path.dart' as p;
|
||||
import 'package:platform/platform.dart';
|
||||
import 'package:scoped_deps/scoped_deps.dart';
|
||||
import 'package:shorebird_cli/src/archive_analysis/apple_archive_differ.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_manager.dart';
|
||||
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
|
||||
import 'package:shorebird_cli/src/commands/patch/patch.dart';
|
||||
|
||||
@@ -10,7 +10,7 @@ import 'package:platform/platform.dart';
|
||||
import 'package:pub_semver/pub_semver.dart';
|
||||
import 'package:scoped_deps/scoped_deps.dart';
|
||||
import 'package:shorebird_cli/src/archive_analysis/apple_archive_differ.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_manager.dart';
|
||||
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
|
||||
import 'package:shorebird_cli/src/code_signer.dart';
|
||||
|
||||
@@ -8,7 +8,7 @@ import 'package:path/path.dart' as p;
|
||||
import 'package:pub_semver/pub_semver.dart';
|
||||
import 'package:scoped_deps/scoped_deps.dart';
|
||||
import 'package:shorebird_cli/src/archive_analysis/linux_bundle_differ.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_manager.dart';
|
||||
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
|
||||
import 'package:shorebird_cli/src/code_signer.dart';
|
||||
|
||||
@@ -10,7 +10,7 @@ import 'package:platform/platform.dart';
|
||||
import 'package:pub_semver/pub_semver.dart';
|
||||
import 'package:scoped_deps/scoped_deps.dart';
|
||||
import 'package:shorebird_cli/src/archive_analysis/archive_analysis.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_manager.dart';
|
||||
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
|
||||
import 'package:shorebird_cli/src/code_signer.dart';
|
||||
|
||||
@@ -8,7 +8,7 @@ import 'package:mocktail/mocktail.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:scoped_deps/scoped_deps.dart';
|
||||
import 'package:shorebird_cli/src/archive_analysis/archive_analysis.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_manager.dart';
|
||||
import 'package:shorebird_cli/src/cache.dart';
|
||||
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
|
||||
|
||||
@@ -9,7 +9,7 @@ import 'package:platform/platform.dart';
|
||||
import 'package:pub_semver/pub_semver.dart';
|
||||
import 'package:scoped_deps/scoped_deps.dart';
|
||||
import 'package:shorebird_cli/src/archive_analysis/archive_analysis.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_manager.dart';
|
||||
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
|
||||
import 'package:shorebird_cli/src/code_signer.dart';
|
||||
|
||||
@@ -5,7 +5,7 @@ import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:scoped_deps/scoped_deps.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
|
||||
import 'package:shorebird_cli/src/commands/release/aar_releaser.dart';
|
||||
import 'package:shorebird_cli/src/engine_config.dart';
|
||||
|
||||
@@ -5,7 +5,7 @@ import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:scoped_deps/scoped_deps.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
|
||||
import 'package:shorebird_cli/src/code_signer.dart';
|
||||
import 'package:shorebird_cli/src/commands/release/android_releaser.dart';
|
||||
|
||||
@@ -7,7 +7,7 @@ import 'package:path/path.dart' as p;
|
||||
import 'package:platform/platform.dart';
|
||||
import 'package:pub_semver/pub_semver.dart';
|
||||
import 'package:scoped_deps/scoped_deps.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_manager.dart';
|
||||
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
|
||||
import 'package:shorebird_cli/src/commands/release/ios_framework_releaser.dart';
|
||||
|
||||
@@ -9,7 +9,7 @@ import 'package:path/path.dart' as p;
|
||||
import 'package:platform/platform.dart';
|
||||
import 'package:pub_semver/pub_semver.dart';
|
||||
import 'package:scoped_deps/scoped_deps.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_manager.dart';
|
||||
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
|
||||
import 'package:shorebird_cli/src/code_signer.dart';
|
||||
|
||||
@@ -7,7 +7,7 @@ import 'package:path/path.dart' as p;
|
||||
import 'package:platform/platform.dart';
|
||||
import 'package:pub_semver/pub_semver.dart';
|
||||
import 'package:scoped_deps/scoped_deps.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_manager.dart';
|
||||
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
|
||||
import 'package:shorebird_cli/src/code_signer.dart';
|
||||
|
||||
@@ -9,7 +9,7 @@ import 'package:path/path.dart' as p;
|
||||
import 'package:platform/platform.dart';
|
||||
import 'package:pub_semver/pub_semver.dart';
|
||||
import 'package:scoped_deps/scoped_deps.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_manager.dart';
|
||||
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
|
||||
import 'package:shorebird_cli/src/commands/release/release.dart';
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:scoped_deps/scoped_deps.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder/artifact_build_exception.dart';
|
||||
import 'package:shorebird_cli/src/cache.dart';
|
||||
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
|
||||
import 'package:shorebird_cli/src/commands/release/release.dart';
|
||||
@@ -139,7 +140,7 @@ void main() {
|
||||
when(() => releaser.assertPreconditions()).thenAnswer((_) async => {});
|
||||
when(() => releaser.assertArgsAreValid()).thenAnswer((_) async => {});
|
||||
when(
|
||||
() => releaser.buildReleaseArtifacts(),
|
||||
() => releaser.buildReleaseArtifacts(progress: any(named: 'progress')),
|
||||
).thenAnswer((_) async => File(''));
|
||||
when(
|
||||
() => releaser.getReleaseVersion(
|
||||
@@ -266,7 +267,7 @@ void main() {
|
||||
() => logger.progress(
|
||||
'Building $artifactDisplayName with Flutter $flutterRevision',
|
||||
),
|
||||
releaser.buildReleaseArtifacts,
|
||||
() => releaser.buildReleaseArtifacts(progress: any(named: 'progress')),
|
||||
() => progress.complete(
|
||||
'Building $artifactDisplayName with Flutter $flutterRevision',
|
||||
),
|
||||
@@ -296,7 +297,9 @@ Note: ${lightCyan.wrap('shorebird patch --platforms=android')} without the --rel
|
||||
group('when build fails', () {
|
||||
setUp(() {
|
||||
when(
|
||||
() => releaser.buildReleaseArtifacts(),
|
||||
() => releaser.buildReleaseArtifacts(
|
||||
progress: any(named: 'progress'),
|
||||
),
|
||||
).thenThrow(Exception('oops'));
|
||||
});
|
||||
|
||||
@@ -311,6 +314,65 @@ Note: ${lightCyan.wrap('shorebird patch --platforms=android')} without the --rel
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
group('when failure is an ArtifactBuildException', () {
|
||||
late ArtifactBuildException exception;
|
||||
|
||||
setUp(() {
|
||||
exception = MockArtifactBuildException();
|
||||
when(() => exception.message).thenReturn('oops');
|
||||
when(() => exception.stderr).thenReturn(['stderr']);
|
||||
when(() => exception.stdout).thenReturn(['stdout']);
|
||||
when(
|
||||
() => releaser.buildReleaseArtifacts(
|
||||
progress: any(named: 'progress'),
|
||||
),
|
||||
).thenThrow(exception);
|
||||
});
|
||||
|
||||
group('when a Flutter error was detected', () {
|
||||
setUp(() {
|
||||
when(() => exception.flutterError).thenReturn('flutter error');
|
||||
});
|
||||
|
||||
test('logs Flutter error at the err level', () async {
|
||||
await expectLater(
|
||||
() => runWithOverrides(command.run),
|
||||
exitsWithCode(ExitCode.software),
|
||||
);
|
||||
verify(() => logger.err('flutter error')).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
group('when a fix recommendation is provided', () {
|
||||
setUp(() {
|
||||
when(() => exception.fixRecommendation).thenReturn('fix it');
|
||||
});
|
||||
|
||||
test('logs fix recommendation at the info level', () async {
|
||||
await expectLater(
|
||||
() => runWithOverrides(command.run),
|
||||
exitsWithCode(ExitCode.software),
|
||||
);
|
||||
verify(() => logger.info('fix it')).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
group('when neither flutter error nor fix suggestion are provided', () {
|
||||
setUp(() {
|
||||
when(() => exception.flutterError).thenReturn(null);
|
||||
when(() => exception.fixRecommendation).thenReturn(null);
|
||||
});
|
||||
|
||||
test('logs stderr', () async {
|
||||
await expectLater(
|
||||
() => runWithOverrides(command.run),
|
||||
exitsWithCode(ExitCode.software),
|
||||
);
|
||||
verify(() => logger.info('stderr')).called(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('when dry-run is specified', () {
|
||||
@@ -392,7 +454,9 @@ Note: ${lightCyan.wrap('shorebird patch --platforms=android')} without the --rel
|
||||
() => logger.progress(
|
||||
'Building $artifactDisplayName with Flutter $flutterRevision',
|
||||
),
|
||||
releaser.buildReleaseArtifacts,
|
||||
() => releaser.buildReleaseArtifacts(
|
||||
progress: any(named: 'progress'),
|
||||
),
|
||||
() => progress.complete(
|
||||
'Building $artifactDisplayName with Flutter $flutterRevision',
|
||||
),
|
||||
@@ -566,7 +630,11 @@ $exception''',
|
||||
test(
|
||||
'uses specified flutter version to build '
|
||||
'and reverts to original flutter version', () async {
|
||||
when(releaser.buildReleaseArtifacts).thenAnswer((_) async {
|
||||
when(
|
||||
() => releaser.buildReleaseArtifacts(
|
||||
progress: any(named: 'progress'),
|
||||
),
|
||||
).thenAnswer((_) async {
|
||||
// Ensure we're using the correct flutter version.
|
||||
expect(shorebirdEnv.flutterRevision, equals(revision));
|
||||
return File('');
|
||||
|
||||
@@ -7,7 +7,7 @@ import 'package:path/path.dart' as p;
|
||||
import 'package:platform/platform.dart';
|
||||
import 'package:pub_semver/pub_semver.dart';
|
||||
import 'package:scoped_deps/scoped_deps.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_manager.dart';
|
||||
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
|
||||
import 'package:shorebird_cli/src/code_signer.dart';
|
||||
|
||||
@@ -11,7 +11,7 @@ import 'package:shorebird_cli/src/android_sdk.dart';
|
||||
import 'package:shorebird_cli/src/android_studio.dart';
|
||||
import 'package:shorebird_cli/src/archive_analysis/archive_analysis.dart';
|
||||
import 'package:shorebird_cli/src/archive_analysis/archive_differ.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
|
||||
import 'package:shorebird_cli/src/artifact_manager.dart';
|
||||
import 'package:shorebird_cli/src/auth/auth.dart';
|
||||
import 'package:shorebird_cli/src/cache.dart' show Cache;
|
||||
@@ -63,6 +63,9 @@ class MockArgParser extends Mock implements ArgParser {}
|
||||
|
||||
class MockArgResults extends Mock implements ArgResults {}
|
||||
|
||||
class MockArtifactBuildException extends Mock
|
||||
implements ArtifactBuildException {}
|
||||
|
||||
class MockArtifactBuilder extends Mock implements ArtifactBuilder {}
|
||||
|
||||
class MockArtifactManager extends Mock implements ArtifactManager {}
|
||||
|
||||
Reference in New Issue
Block a user