feat: Set FLUTTER_STORAGE_BASE_URL when using Shorebird-vended Flutter, warn user when path flutter version does not match shorebird flutter version (#243)
Co-authored-by: Eric Seidel <eric@shorebird.dev> Co-authored-by: Felix Angelov <felix@shorebird.dev>
This commit is contained in:
@@ -7,6 +7,7 @@ import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:shorebird_cli/src/auth/auth.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_process.dart';
|
||||
import 'package:shorebird_cli/src/validators/validators.dart';
|
||||
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
|
||||
|
||||
/// Signature for a function which takes a list of bytes and returns a hash.
|
||||
@@ -30,16 +31,21 @@ abstract class ShorebirdCommand extends Command<int> {
|
||||
CodePushClientBuilder? buildCodePushClient,
|
||||
RunProcess? runProcess,
|
||||
StartProcess? startProcess,
|
||||
ShorebirdFlutterValidator? flutterValidator,
|
||||
}) : auth = auth ?? Auth(),
|
||||
buildCodePushClient = buildCodePushClient ?? CodePushClient.new,
|
||||
runProcess = runProcess ?? ShorebirdProcess.run,
|
||||
startProcess = startProcess ?? ShorebirdProcess.start;
|
||||
startProcess = startProcess ?? ShorebirdProcess.start {
|
||||
this.flutterValidator = flutterValidator ??
|
||||
ShorebirdFlutterValidator(runProcess: this.runProcess);
|
||||
}
|
||||
|
||||
final Auth auth;
|
||||
final CodePushClientBuilder buildCodePushClient;
|
||||
final Logger logger;
|
||||
final RunProcess runProcess;
|
||||
final StartProcess startProcess;
|
||||
late final ShorebirdFlutterValidator flutterValidator;
|
||||
|
||||
/// [ArgResults] used for testing purposes only.
|
||||
@visibleForTesting
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:io';
|
||||
|
||||
import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:shorebird_cli/src/command.dart';
|
||||
import 'package:shorebird_cli/src/flutter_validation_mixin.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_build_mixin.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_config_mixin.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_engine_mixin.dart';
|
||||
@@ -12,13 +13,18 @@ import 'package:shorebird_cli/src/shorebird_engine_mixin.dart';
|
||||
/// Build a new release of your application.
|
||||
/// {@endtemplate}
|
||||
class BuildCommand extends ShorebirdCommand
|
||||
with ShorebirdConfigMixin, ShorebirdEngineMixin, ShorebirdBuildMixin {
|
||||
with
|
||||
FlutterValidationMixin,
|
||||
ShorebirdConfigMixin,
|
||||
ShorebirdEngineMixin,
|
||||
ShorebirdBuildMixin {
|
||||
/// {@macro build_command}
|
||||
BuildCommand({
|
||||
required super.logger,
|
||||
super.auth,
|
||||
super.buildCodePushClient,
|
||||
super.runProcess,
|
||||
super.flutterValidator,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -43,6 +49,8 @@ class BuildCommand extends ShorebirdCommand
|
||||
return ExitCode.software.code;
|
||||
}
|
||||
|
||||
await logFlutterValidationIssues();
|
||||
|
||||
final buildProgress = logger.progress('Building release ');
|
||||
try {
|
||||
await buildRelease();
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:shorebird_cli/src/command.dart';
|
||||
import 'package:shorebird_cli/src/doctor/doctor_validator.dart';
|
||||
import 'package:shorebird_cli/src/doctor/validators/validators.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_version_mixin.dart';
|
||||
import 'package:shorebird_cli/src/validators/validators.dart';
|
||||
import 'package:shorebird_cli/src/version.dart';
|
||||
|
||||
/// {@template doctor_command}
|
||||
@@ -15,11 +14,11 @@ class DoctorCommand extends ShorebirdCommand with ShorebirdVersionMixin {
|
||||
/// {@macro doctor_command}
|
||||
DoctorCommand({
|
||||
required super.logger,
|
||||
List<DoctorValidator>? validators,
|
||||
List<Validator>? validators,
|
||||
super.runProcess,
|
||||
}) {
|
||||
this.validators = validators ??
|
||||
<DoctorValidator>[
|
||||
<Validator>[
|
||||
ShorebirdVersionValidator(
|
||||
isShorebirdVersionCurrent: isShorebirdVersionCurrent,
|
||||
),
|
||||
@@ -28,7 +27,7 @@ class DoctorCommand extends ShorebirdCommand with ShorebirdVersionMixin {
|
||||
];
|
||||
}
|
||||
|
||||
late final List<DoctorValidator> validators;
|
||||
late final List<Validator> validators;
|
||||
|
||||
@override
|
||||
String get name => 'doctor';
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:http/http.dart' as http;
|
||||
import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:shorebird_cli/src/command.dart';
|
||||
import 'package:shorebird_cli/src/flutter_validation_mixin.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_build_mixin.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_config_mixin.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_create_app_mixin.dart';
|
||||
@@ -18,6 +19,7 @@ import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
|
||||
/// {@endtemplate}
|
||||
class PatchCommand extends ShorebirdCommand
|
||||
with
|
||||
FlutterValidationMixin,
|
||||
ShorebirdConfigMixin,
|
||||
ShorebirdEngineMixin,
|
||||
ShorebirdBuildMixin,
|
||||
@@ -28,6 +30,7 @@ class PatchCommand extends ShorebirdCommand
|
||||
super.auth,
|
||||
super.buildCodePushClient,
|
||||
super.runProcess,
|
||||
super.flutterValidator,
|
||||
HashFunction? hashFn,
|
||||
http.Client? httpClient,
|
||||
}) : _hashFn = hashFn ?? ((m) => sha256.convert(m).toString()),
|
||||
@@ -105,6 +108,8 @@ class PatchCommand extends ShorebirdCommand
|
||||
return ExitCode.software.code;
|
||||
}
|
||||
|
||||
await logFlutterValidationIssues();
|
||||
|
||||
final force = results['force'] == true;
|
||||
final dryRun = results['dry-run'] == true;
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:crypto/crypto.dart';
|
||||
import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:shorebird_cli/src/command.dart';
|
||||
import 'package:shorebird_cli/src/flutter_validation_mixin.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_build_mixin.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_config_mixin.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_create_app_mixin.dart';
|
||||
@@ -17,6 +18,7 @@ import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
|
||||
/// {@endtemplate}
|
||||
class ReleaseCommand extends ShorebirdCommand
|
||||
with
|
||||
FlutterValidationMixin,
|
||||
ShorebirdConfigMixin,
|
||||
ShorebirdEngineMixin,
|
||||
ShorebirdBuildMixin,
|
||||
@@ -27,6 +29,7 @@ class ReleaseCommand extends ShorebirdCommand
|
||||
super.auth,
|
||||
super.buildCodePushClient,
|
||||
super.runProcess,
|
||||
super.flutterValidator,
|
||||
HashFunction? hashFn,
|
||||
}) : _hashFn = hashFn ?? ((m) => sha256.convert(m).toString()) {
|
||||
argParser
|
||||
@@ -83,6 +86,8 @@ make smaller updates to your app.
|
||||
return ExitCode.software.code;
|
||||
}
|
||||
|
||||
await logFlutterValidationIssues();
|
||||
|
||||
final buildProgress = logger.progress('Building release');
|
||||
try {
|
||||
await buildRelease();
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:convert';
|
||||
|
||||
import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:shorebird_cli/src/command.dart';
|
||||
import 'package:shorebird_cli/src/flutter_validation_mixin.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_config_mixin.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_engine_mixin.dart';
|
||||
|
||||
@@ -10,13 +11,14 @@ import 'package:shorebird_cli/src/shorebird_engine_mixin.dart';
|
||||
/// Run the Flutter application.
|
||||
/// {@endtemplate}
|
||||
class RunCommand extends ShorebirdCommand
|
||||
with ShorebirdConfigMixin, ShorebirdEngineMixin {
|
||||
with FlutterValidationMixin, ShorebirdConfigMixin, ShorebirdEngineMixin {
|
||||
/// {@macro run_command}
|
||||
RunCommand({
|
||||
required super.logger,
|
||||
super.auth,
|
||||
super.buildCodePushClient,
|
||||
super.startProcess,
|
||||
super.flutterValidator,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -41,6 +43,8 @@ class RunCommand extends ShorebirdCommand
|
||||
return ExitCode.software.code;
|
||||
}
|
||||
|
||||
await logFlutterValidationIssues();
|
||||
|
||||
logger.info('Running app...');
|
||||
final process = await startProcess(
|
||||
'flutter',
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
import 'package:shorebird_cli/src/doctor/doctor_validator.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_paths.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_process.dart';
|
||||
|
||||
class ShorebirdFlutterValidator extends DoctorValidator {
|
||||
ShorebirdFlutterValidator({required this.runProcess});
|
||||
|
||||
final RunProcess runProcess;
|
||||
|
||||
// coverage:ignore-start
|
||||
@override
|
||||
String get description => 'Flutter install is correct';
|
||||
// coverage:ignore-end
|
||||
|
||||
@override
|
||||
Future<List<ValidationIssue>> validate() async {
|
||||
if (!ShorebirdPaths.flutterDirectory.existsSync()) {
|
||||
final message =
|
||||
'No Flutter directory found at ${ShorebirdPaths.flutterDirectory}';
|
||||
return [
|
||||
ValidationIssue(
|
||||
severity: ValidationIssueSeverity.error,
|
||||
message: message,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
if (!await _flutterDirectoryIsClean()) {
|
||||
return [
|
||||
ValidationIssue(
|
||||
severity: ValidationIssueSeverity.warning,
|
||||
message: '${ShorebirdPaths.flutterDirectory} has local modifications',
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
if (!await _flutterDirectoryTracksStable()) {
|
||||
final message =
|
||||
'${ShorebirdPaths.flutterDirectory} is not on the "stable" branch';
|
||||
return [
|
||||
ValidationIssue(
|
||||
severity: ValidationIssueSeverity.warning,
|
||||
message: message,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
Future<bool> _flutterDirectoryIsClean() async {
|
||||
final result = await runProcess(
|
||||
'git',
|
||||
['status'],
|
||||
workingDirectory: ShorebirdPaths.flutterDirectory.path,
|
||||
);
|
||||
return result.stdout
|
||||
.toString()
|
||||
.contains('nothing to commit, working tree clean');
|
||||
}
|
||||
|
||||
Future<bool> _flutterDirectoryTracksStable() async {
|
||||
final result = await runProcess(
|
||||
'git',
|
||||
['--no-pager', 'branch'],
|
||||
workingDirectory: ShorebirdPaths.flutterDirectory.path,
|
||||
);
|
||||
return result.stdout.toString().contains('* stable');
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
export 'android_internet_permission_validator.dart';
|
||||
export 'shorebird_flutter_validator.dart';
|
||||
export 'shorebird_version_validator.dart';
|
||||
@@ -0,0 +1,15 @@
|
||||
import 'package:shorebird_cli/src/command.dart';
|
||||
import 'package:shorebird_cli/src/validators/shorebird_flutter_validator.dart';
|
||||
|
||||
mixin FlutterValidationMixin on ShorebirdCommand {
|
||||
/// Runs [ShorebirdFlutterValidator.validate] and writes validation issues to
|
||||
/// stdout.
|
||||
Future<void> logFlutterValidationIssues() async {
|
||||
final flutterValidationIssues = await flutterValidator.validate();
|
||||
if (flutterValidationIssues.isNotEmpty) {
|
||||
for (final issue in flutterValidationIssues) {
|
||||
logger.info(issue.displayMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-1
@@ -4,10 +4,13 @@ import 'package:meta/meta.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:platform/platform.dart';
|
||||
|
||||
abstract class ShorebirdPaths {
|
||||
abstract class ShorebirdEnvironment {
|
||||
@visibleForTesting
|
||||
static Platform platform = const LocalPlatform();
|
||||
|
||||
/// Environment variables from [Platform.environment].
|
||||
static Map<String, String> get environment => platform.environment;
|
||||
|
||||
/// The root directory of the Shorebird install.
|
||||
///
|
||||
/// Assumes we are running from $ROOT/bin/cache.
|
||||
@@ -1,19 +1,23 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_paths.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_environment.dart';
|
||||
|
||||
typedef RunProcess = Future<ProcessResult> Function(
|
||||
String executable,
|
||||
List<String> arguments, {
|
||||
bool runInShell,
|
||||
Map<String, String>? environment,
|
||||
String? workingDirectory,
|
||||
bool useVendedFlutter,
|
||||
});
|
||||
|
||||
typedef StartProcess = Future<Process> Function(
|
||||
String executable,
|
||||
List<String> arguments, {
|
||||
bool runInShell,
|
||||
Map<String, String>? environment,
|
||||
bool useVendedFlutter,
|
||||
});
|
||||
|
||||
/// A wrapper around [Process] that replaces executables to Shorebird-vended
|
||||
@@ -26,42 +30,100 @@ abstract class ShorebirdProcess {
|
||||
String executable,
|
||||
List<String> arguments, {
|
||||
bool runInShell = false,
|
||||
Map<String, String>? environment,
|
||||
String? workingDirectory,
|
||||
bool useVendedFlutter = true,
|
||||
}) {
|
||||
final resolvedEnvironment = environment ?? {};
|
||||
if (useVendedFlutter) {
|
||||
// Note: this will overwrite existing environment values.
|
||||
resolvedEnvironment.addAll(
|
||||
_environmentOverrides(executable: executable),
|
||||
);
|
||||
}
|
||||
|
||||
return processWrapper.run(
|
||||
_resolveExecutable(executable),
|
||||
useVendedFlutter ? _resolveExecutable(executable) : executable,
|
||||
arguments,
|
||||
runInShell: runInShell,
|
||||
workingDirectory: workingDirectory,
|
||||
environment: resolvedEnvironment,
|
||||
);
|
||||
}
|
||||
|
||||
static Future<Process> start(
|
||||
String executable,
|
||||
List<String> argument, {
|
||||
Map<String, String>? environment,
|
||||
bool runInShell = false,
|
||||
bool useVendedFlutter = true,
|
||||
}) {
|
||||
final resolvedEnvironment = environment ?? {};
|
||||
if (useVendedFlutter) {
|
||||
// Note: this will overwrite existing environment values.
|
||||
resolvedEnvironment.addAll(
|
||||
_environmentOverrides(executable: executable),
|
||||
);
|
||||
}
|
||||
|
||||
return processWrapper.start(
|
||||
_resolveExecutable(executable),
|
||||
useVendedFlutter ? _resolveExecutable(executable) : executable,
|
||||
argument,
|
||||
runInShell: runInShell,
|
||||
environment: resolvedEnvironment,
|
||||
);
|
||||
}
|
||||
|
||||
static String _resolveExecutable(String executable) {
|
||||
if (executable == 'flutter') {
|
||||
return ShorebirdPaths.flutterBinaryFile.path;
|
||||
return ShorebirdEnvironment.flutterBinaryFile.path;
|
||||
}
|
||||
|
||||
return executable;
|
||||
}
|
||||
|
||||
static Map<String, String> _environmentOverrides({
|
||||
required String executable,
|
||||
}) {
|
||||
if (executable == 'flutter') {
|
||||
return {'FLUTTER_STORAGE_BASE_URL': 'https://download.shorebird.dev/'};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
// coverage:ignore-start
|
||||
@visibleForTesting
|
||||
class ProcessWrapper {
|
||||
RunProcess get run => Process.run;
|
||||
RunProcess get run => (
|
||||
String executable,
|
||||
List<String> arguments, {
|
||||
bool runInShell = false,
|
||||
Map<String, String>? environment,
|
||||
String? workingDirectory,
|
||||
bool useVendedFlutter = true,
|
||||
}) =>
|
||||
Process.run(
|
||||
executable,
|
||||
arguments,
|
||||
environment: environment,
|
||||
runInShell: runInShell,
|
||||
workingDirectory: workingDirectory,
|
||||
);
|
||||
|
||||
StartProcess get start => Process.start;
|
||||
StartProcess get start => (
|
||||
String executable,
|
||||
List<String> arguments, {
|
||||
bool runInShell = false,
|
||||
Map<String, String>? environment,
|
||||
bool useVendedFlutter = true,
|
||||
}) =>
|
||||
Process.start(
|
||||
executable,
|
||||
arguments,
|
||||
runInShell: runInShell,
|
||||
environment: environment,
|
||||
);
|
||||
}
|
||||
// coverage:ignore-end
|
||||
|
||||
+2
-2
@@ -1,14 +1,14 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:shorebird_cli/src/doctor/doctor_validator.dart';
|
||||
import 'package:shorebird_cli/src/validators/validators.dart';
|
||||
import 'package:xml/xml.dart';
|
||||
|
||||
/// Checks that all AndroidManifest.xml files in android/app/src/{flavor}/
|
||||
/// contain the INTERNET permission, which is required for Shorebird to work.
|
||||
///
|
||||
/// See https://github.com/shorebirdtech/shorebird/issues/160.
|
||||
class AndroidInternetPermissionValidator extends DoctorValidator {
|
||||
class AndroidInternetPermissionValidator extends Validator {
|
||||
// coverage:ignore-start
|
||||
@override
|
||||
String get description =>
|
||||
@@ -0,0 +1,132 @@
|
||||
import 'package:shorebird_cli/src/shorebird_environment.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_process.dart';
|
||||
import 'package:shorebird_cli/src/validators/validators.dart';
|
||||
|
||||
class ShorebirdFlutterValidator extends Validator {
|
||||
ShorebirdFlutterValidator({required this.runProcess});
|
||||
|
||||
final RunProcess runProcess;
|
||||
final _flutterVersionRegex = RegExp(r'Flutter (\d+.\d+.\d+)');
|
||||
|
||||
// coverage:ignore-start
|
||||
@override
|
||||
String get description => 'Flutter install is correct';
|
||||
// coverage:ignore-end
|
||||
|
||||
@override
|
||||
Future<List<ValidationIssue>> validate() async {
|
||||
final issues = <ValidationIssue>[];
|
||||
|
||||
if (!ShorebirdEnvironment.flutterDirectory.existsSync()) {
|
||||
final message = 'No Flutter directory found at '
|
||||
'${ShorebirdEnvironment.flutterDirectory}';
|
||||
issues.add(
|
||||
ValidationIssue(
|
||||
severity: ValidationIssueSeverity.error,
|
||||
message: message,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (!await _flutterDirectoryIsClean()) {
|
||||
issues.add(
|
||||
ValidationIssue(
|
||||
severity: ValidationIssueSeverity.warning,
|
||||
message: '${ShorebirdEnvironment.flutterDirectory} has local '
|
||||
'modifications',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (!await _flutterDirectoryTracksStable()) {
|
||||
final message =
|
||||
'${ShorebirdEnvironment.flutterDirectory} is not on the "stable" '
|
||||
'branch';
|
||||
issues.add(
|
||||
ValidationIssue(
|
||||
severity: ValidationIssueSeverity.warning,
|
||||
message: message,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final shorebirdFlutterVersion = await _shorebirdFlutterVersion();
|
||||
final pathFlutterVersion = await _pathFlutterVersion();
|
||||
|
||||
if (shorebirdFlutterVersion != pathFlutterVersion) {
|
||||
final message = """
|
||||
Shorebird Flutter and the Flutter on your path are different versions.
|
||||
\tShorebird Flutter: $shorebirdFlutterVersion
|
||||
\tSystem Flutter: $pathFlutterVersion'''
|
||||
This can cause unexpected behavior if the version gap is wide. If you're seeing this unexpectedly, please let us know on Shorebird discord!""";
|
||||
|
||||
issues.add(
|
||||
ValidationIssue(
|
||||
severity: ValidationIssueSeverity.warning,
|
||||
message: message,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final flutterStorageEnvironmentValue =
|
||||
ShorebirdEnvironment.environment['FLUTTER_STORAGE_BASE_URL'];
|
||||
if (flutterStorageEnvironmentValue != null &&
|
||||
flutterStorageEnvironmentValue.isNotEmpty) {
|
||||
issues.add(
|
||||
const ValidationIssue(
|
||||
severity: ValidationIssueSeverity.warning,
|
||||
message: 'Shorebird does not respect the FLUTTER_STORAGE_BASE_URL '
|
||||
'environment variable at this time',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return issues;
|
||||
}
|
||||
|
||||
Future<bool> _flutterDirectoryIsClean() async {
|
||||
final result = await runProcess(
|
||||
'git',
|
||||
['status'],
|
||||
workingDirectory: ShorebirdEnvironment.flutterDirectory.path,
|
||||
);
|
||||
return result.stdout
|
||||
.toString()
|
||||
.contains('nothing to commit, working tree clean');
|
||||
}
|
||||
|
||||
Future<bool> _flutterDirectoryTracksStable() async {
|
||||
final result = await runProcess(
|
||||
'git',
|
||||
['--no-pager', 'branch'],
|
||||
workingDirectory: ShorebirdEnvironment.flutterDirectory.path,
|
||||
);
|
||||
return result.stdout.toString().contains('* stable');
|
||||
}
|
||||
|
||||
Future<String> _shorebirdFlutterVersion() => _getFlutterVersion(
|
||||
checkPathFlutter: false,
|
||||
);
|
||||
|
||||
Future<String> _pathFlutterVersion() => _getFlutterVersion(
|
||||
checkPathFlutter: true,
|
||||
);
|
||||
|
||||
Future<String> _getFlutterVersion({
|
||||
required bool checkPathFlutter,
|
||||
}) async {
|
||||
final result = await runProcess(
|
||||
'flutter',
|
||||
['--version'],
|
||||
useVendedFlutter: !checkPathFlutter,
|
||||
);
|
||||
final output = result.stdout.toString();
|
||||
|
||||
final match = _flutterVersionRegex.firstMatch(output);
|
||||
if (match == null) {
|
||||
throw Exception('Could not find version match in $output');
|
||||
}
|
||||
|
||||
return match.group(1)!;
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:shorebird_cli/src/doctor/doctor_validator.dart';
|
||||
import 'package:shorebird_cli/src/validators/validators.dart';
|
||||
|
||||
/// Verifies that the currently installed version of Shorebird is the latest.
|
||||
class ShorebirdVersionValidator extends DoctorValidator {
|
||||
class ShorebirdVersionValidator extends Validator {
|
||||
ShorebirdVersionValidator({required this.isShorebirdVersionCurrent});
|
||||
|
||||
final Future<bool> Function({required String workingDirectory})
|
||||
+5
-1
@@ -1,6 +1,10 @@
|
||||
import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
export 'android_internet_permission_validator.dart';
|
||||
export 'shorebird_flutter_validator.dart';
|
||||
export 'shorebird_version_validator.dart';
|
||||
|
||||
/// Severity level of a [ValidationIssue].
|
||||
///
|
||||
/// [error]s should be fixed before continuing development.
|
||||
@@ -60,7 +64,7 @@ class ValidationIssue {
|
||||
|
||||
/// Checks for a specific issue with either the Shorebird installation or the
|
||||
/// current Shorebird project.
|
||||
abstract class DoctorValidator {
|
||||
abstract class Validator {
|
||||
/// A one-sentence explanation of what this validator is checking.
|
||||
String get description;
|
||||
|
||||
@@ -9,6 +9,7 @@ import 'package:mocktail/mocktail.dart';
|
||||
import 'package:shorebird_cli/src/auth/auth.dart';
|
||||
import 'package:shorebird_cli/src/commands/build_command.dart';
|
||||
import 'package:shorebird_cli/src/config/config.dart';
|
||||
import 'package:shorebird_cli/src/validators/validators.dart';
|
||||
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
@@ -26,6 +27,9 @@ class _MockProcessResult extends Mock implements ProcessResult {}
|
||||
|
||||
class _MockCodePushClient extends Mock implements CodePushClient {}
|
||||
|
||||
class _MockShorebirdFlutterValidator extends Mock
|
||||
implements ShorebirdFlutterValidator {}
|
||||
|
||||
void main() {
|
||||
group('build', () {
|
||||
late ArgResults argResults;
|
||||
@@ -36,6 +40,7 @@ void main() {
|
||||
late Logger logger;
|
||||
late ProcessResult processResult;
|
||||
late BuildCommand buildCommand;
|
||||
late ShorebirdFlutterValidator flutterValidator;
|
||||
|
||||
setUp(() {
|
||||
applicationConfigHome = Directory.systemTemp.createTempSync();
|
||||
@@ -45,6 +50,7 @@ void main() {
|
||||
codePushClient = _MockCodePushClient();
|
||||
logger = _MockLogger();
|
||||
processResult = _MockProcessResult();
|
||||
flutterValidator = _MockShorebirdFlutterValidator();
|
||||
buildCommand = BuildCommand(
|
||||
auth: auth,
|
||||
buildCodePushClient: ({
|
||||
@@ -58,10 +64,13 @@ void main() {
|
||||
executable,
|
||||
arguments, {
|
||||
bool runInShell = false,
|
||||
Map<String, String>? environment,
|
||||
String? workingDirectory,
|
||||
bool useVendedFlutter = true,
|
||||
}) async {
|
||||
return processResult;
|
||||
},
|
||||
flutterValidator: flutterValidator,
|
||||
)..testArgResults = argResults;
|
||||
testApplicationConfigHome = (_) => applicationConfigHome.path;
|
||||
|
||||
@@ -72,6 +81,8 @@ void main() {
|
||||
() => codePushClient.downloadEngine(revision: any(named: 'revision')),
|
||||
).thenAnswer((_) async => Uint8List.fromList([]));
|
||||
when(() => logger.progress(any())).thenReturn(_MockProgress());
|
||||
when(() => logger.info(any())).thenReturn(null);
|
||||
when(() => flutterValidator.validate()).thenAnswer((_) async => []);
|
||||
});
|
||||
|
||||
test('exits with no user when not logged in', () async {
|
||||
@@ -130,5 +141,36 @@ void main() {
|
||||
|
||||
expect(result, equals(ExitCode.success.code));
|
||||
});
|
||||
|
||||
test('prints flutter validation warnings', () async {
|
||||
when(() => flutterValidator.validate()).thenAnswer(
|
||||
(_) async => [
|
||||
const ValidationIssue(
|
||||
severity: ValidationIssueSeverity.warning,
|
||||
message: 'Flutter issue 1',
|
||||
),
|
||||
const ValidationIssue(
|
||||
severity: ValidationIssueSeverity.warning,
|
||||
message: 'Flutter issue 2',
|
||||
),
|
||||
],
|
||||
);
|
||||
when(() => processResult.exitCode).thenReturn(ExitCode.success.code);
|
||||
when(
|
||||
() => codePushClient.downloadEngine(revision: any(named: 'revision')),
|
||||
).thenAnswer(
|
||||
(_) async => Uint8List.fromList(ZipEncoder().encode(Archive())!),
|
||||
);
|
||||
|
||||
final result = await buildCommand.run();
|
||||
|
||||
expect(result, equals(ExitCode.success.code));
|
||||
verify(
|
||||
() => logger.info(any(that: contains('Flutter issue 1'))),
|
||||
).called(1);
|
||||
verify(
|
||||
() => logger.info(any(that: contains('Flutter issue 2'))),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:shorebird_cli/src/commands/commands.dart';
|
||||
import 'package:shorebird_cli/src/doctor/doctor_validator.dart';
|
||||
import 'package:shorebird_cli/src/doctor/validators/validators.dart';
|
||||
import 'package:shorebird_cli/src/validators/validators.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class _MockShorebirdVersionValidator extends Mock
|
||||
|
||||
@@ -10,6 +10,7 @@ import 'package:path/path.dart' as p;
|
||||
import 'package:shorebird_cli/src/auth/auth.dart';
|
||||
import 'package:shorebird_cli/src/commands/patch_command.dart';
|
||||
import 'package:shorebird_cli/src/config/config.dart';
|
||||
import 'package:shorebird_cli/src/validators/validators.dart';
|
||||
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
@@ -29,6 +30,9 @@ class _MockHttpClient extends Mock implements http.Client {}
|
||||
|
||||
class _MockCodePushClient extends Mock implements CodePushClient {}
|
||||
|
||||
class _MockShorebirdFlutterValidator extends Mock
|
||||
implements ShorebirdFlutterValidator {}
|
||||
|
||||
void main() {
|
||||
group('patch', () {
|
||||
const appId = 'test-app-id';
|
||||
@@ -85,6 +89,7 @@ flutter:
|
||||
late CodePushClient codePushClient;
|
||||
late PatchCommand command;
|
||||
late Uri? capturedHostedUri;
|
||||
late ShorebirdFlutterValidator flutterValidator;
|
||||
|
||||
Directory setUpTempDir() {
|
||||
final tempDir = Directory.systemTemp.createTempSync();
|
||||
@@ -111,6 +116,7 @@ flutter:
|
||||
patchProcessResult = _MockProcessResult();
|
||||
httpClient = _MockHttpClient();
|
||||
codePushClient = _MockCodePushClient();
|
||||
flutterValidator = _MockShorebirdFlutterValidator();
|
||||
command = PatchCommand(
|
||||
auth: auth,
|
||||
buildCodePushClient: ({
|
||||
@@ -124,7 +130,9 @@ flutter:
|
||||
executable,
|
||||
arguments, {
|
||||
bool runInShell = false,
|
||||
Map<String, String>? environment,
|
||||
String? workingDirectory,
|
||||
bool useVendedFlutter = true,
|
||||
}) async {
|
||||
if (executable == 'flutter') return flutterBuildProcessResult;
|
||||
if (executable.endsWith('patch')) return patchProcessResult;
|
||||
@@ -132,6 +140,7 @@ flutter:
|
||||
},
|
||||
logger: logger,
|
||||
httpClient: httpClient,
|
||||
flutterValidator: flutterValidator,
|
||||
)..testArgResults = argResults;
|
||||
testApplicationConfigHome = (_) => applicationConfigHome.path;
|
||||
|
||||
@@ -198,6 +207,7 @@ flutter:
|
||||
channelId: any(named: 'channelId'),
|
||||
),
|
||||
).thenAnswer((_) async {});
|
||||
when(() => flutterValidator.validate()).thenAnswer((_) async => []);
|
||||
});
|
||||
|
||||
test('throws config error when shorebird is not initialized', () async {
|
||||
@@ -838,5 +848,50 @@ base_url: $baseUrl''',
|
||||
);
|
||||
expect(capturedHostedUri, equals(Uri.parse(baseUrl)));
|
||||
});
|
||||
|
||||
test('prints flutter validation warnings', () async {
|
||||
final tempDir = setUpTempDir();
|
||||
Directory(
|
||||
p.join(command.shorebirdEnginePath, 'engine'),
|
||||
).createSync(recursive: true);
|
||||
final artifactPath = p.join(
|
||||
tempDir.path,
|
||||
'build',
|
||||
'app',
|
||||
'intermediates',
|
||||
'stripped_native_libs',
|
||||
'release',
|
||||
'out',
|
||||
'lib',
|
||||
'arm64-v8a',
|
||||
'libapp.so',
|
||||
);
|
||||
File(artifactPath).createSync(recursive: true);
|
||||
when(() => flutterValidator.validate()).thenAnswer(
|
||||
(_) async => [
|
||||
const ValidationIssue(
|
||||
severity: ValidationIssueSeverity.warning,
|
||||
message: 'Flutter issue 1',
|
||||
),
|
||||
const ValidationIssue(
|
||||
severity: ValidationIssueSeverity.warning,
|
||||
message: 'Flutter issue 2',
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
final exitCode = await IOOverrides.runZoned(
|
||||
command.run,
|
||||
getCurrentDirectory: () => tempDir,
|
||||
);
|
||||
|
||||
expect(exitCode, equals(ExitCode.success.code));
|
||||
verify(
|
||||
() => logger.info(any(that: contains('Flutter issue 1'))),
|
||||
).called(1);
|
||||
verify(
|
||||
() => logger.info(any(that: contains('Flutter issue 2'))),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import 'package:path/path.dart' as p;
|
||||
import 'package:shorebird_cli/src/auth/auth.dart';
|
||||
import 'package:shorebird_cli/src/commands/commands.dart';
|
||||
import 'package:shorebird_cli/src/config/config.dart';
|
||||
import 'package:shorebird_cli/src/validators/validators.dart';
|
||||
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
@@ -27,6 +28,9 @@ class _MockProcessResult extends Mock implements ProcessResult {}
|
||||
|
||||
class _MockCodePushClient extends Mock implements CodePushClient {}
|
||||
|
||||
class _MockShorebirdFlutterValidator extends Mock
|
||||
implements ShorebirdFlutterValidator {}
|
||||
|
||||
void main() {
|
||||
group('release', () {
|
||||
const appId = 'test-app-id';
|
||||
@@ -71,6 +75,7 @@ flutter:
|
||||
late CodePushClient codePushClient;
|
||||
late ReleaseCommand command;
|
||||
late Uri? capturedHostedUri;
|
||||
late ShorebirdFlutterValidator flutterValidator;
|
||||
|
||||
Directory setUpTempDir() {
|
||||
final tempDir = Directory.systemTemp.createTempSync();
|
||||
@@ -92,6 +97,7 @@ flutter:
|
||||
logger = _MockLogger();
|
||||
processResult = _MockProcessResult();
|
||||
codePushClient = _MockCodePushClient();
|
||||
flutterValidator = _MockShorebirdFlutterValidator();
|
||||
command = ReleaseCommand(
|
||||
auth: auth,
|
||||
buildCodePushClient: ({
|
||||
@@ -105,11 +111,14 @@ flutter:
|
||||
executable,
|
||||
arguments, {
|
||||
bool runInShell = false,
|
||||
Map<String, String>? environment,
|
||||
String? workingDirectory,
|
||||
bool useVendedFlutter = true,
|
||||
}) async {
|
||||
return processResult;
|
||||
},
|
||||
logger: logger,
|
||||
flutterValidator: flutterValidator,
|
||||
)..testArgResults = argResults;
|
||||
testApplicationConfigHome = (_) => applicationConfigHome.path;
|
||||
|
||||
@@ -148,6 +157,7 @@ flutter:
|
||||
hash: any(named: 'hash'),
|
||||
),
|
||||
).thenAnswer((_) async => releaseArtifact);
|
||||
when(() => flutterValidator.validate()).thenAnswer((_) async => []);
|
||||
});
|
||||
|
||||
test('throws config error when shorebird is not initialized', () async {
|
||||
@@ -446,5 +456,50 @@ Did you forget to run "shorebird init"?''',
|
||||
expect(exitCode, ExitCode.success.code);
|
||||
expect(capturedHostedUri, isNull);
|
||||
});
|
||||
|
||||
test('prints flutter validation warnings', () async {
|
||||
when(() => flutterValidator.validate()).thenAnswer(
|
||||
(_) async => [
|
||||
const ValidationIssue(
|
||||
severity: ValidationIssueSeverity.warning,
|
||||
message: 'Flutter issue 1',
|
||||
),
|
||||
const ValidationIssue(
|
||||
severity: ValidationIssueSeverity.warning,
|
||||
message: 'Flutter issue 2',
|
||||
),
|
||||
],
|
||||
);
|
||||
final tempDir = setUpTempDir();
|
||||
Directory(
|
||||
p.join(command.shorebirdEnginePath, 'engine'),
|
||||
).createSync(recursive: true);
|
||||
final artifactPath = p.join(
|
||||
tempDir.path,
|
||||
'build',
|
||||
'app',
|
||||
'intermediates',
|
||||
'stripped_native_libs',
|
||||
'release',
|
||||
'out',
|
||||
'lib',
|
||||
'arm64-v8a',
|
||||
'libapp.so',
|
||||
);
|
||||
File(artifactPath).createSync(recursive: true);
|
||||
final exitCode = await IOOverrides.runZoned(
|
||||
command.run,
|
||||
getCurrentDirectory: () => tempDir,
|
||||
);
|
||||
verify(() => logger.success('\n✅ Published Release!')).called(1);
|
||||
expect(exitCode, ExitCode.success.code);
|
||||
expect(capturedHostedUri, isNull);
|
||||
verify(
|
||||
() => logger.info(any(that: contains('Flutter issue 1'))),
|
||||
).called(1);
|
||||
verify(
|
||||
() => logger.info(any(that: contains('Flutter issue 2'))),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import 'package:path/path.dart' as p;
|
||||
import 'package:shorebird_cli/src/auth/auth.dart';
|
||||
import 'package:shorebird_cli/src/commands/run_command.dart';
|
||||
import 'package:shorebird_cli/src/config/config.dart';
|
||||
import 'package:shorebird_cli/src/validators/validators.dart';
|
||||
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
@@ -29,6 +30,9 @@ class _MockProcess extends Mock implements Process {}
|
||||
|
||||
class _MockCodePushClient extends Mock implements CodePushClient {}
|
||||
|
||||
class _MockShorebirdFlutterValidator extends Mock
|
||||
implements ShorebirdFlutterValidator {}
|
||||
|
||||
void main() {
|
||||
group('run', () {
|
||||
late ArgResults argResults;
|
||||
@@ -39,6 +43,7 @@ void main() {
|
||||
late Process process;
|
||||
late CodePushClient codePushClient;
|
||||
late RunCommand runCommand;
|
||||
late ShorebirdFlutterValidator flutterValidator;
|
||||
|
||||
setUp(() {
|
||||
argResults = _MockArgResults();
|
||||
@@ -48,6 +53,7 @@ void main() {
|
||||
logger = _MockLogger();
|
||||
process = _MockProcess();
|
||||
codePushClient = _MockCodePushClient();
|
||||
flutterValidator = _MockShorebirdFlutterValidator();
|
||||
runCommand = RunCommand(
|
||||
auth: auth,
|
||||
logger: logger,
|
||||
@@ -60,6 +66,7 @@ void main() {
|
||||
startProcess: (executable, arguments, {bool runInShell = false}) async {
|
||||
return process;
|
||||
},
|
||||
flutterValidator: flutterValidator,
|
||||
)..testArgResults = argResults;
|
||||
|
||||
testApplicationConfigHome = (_) => applicationConfigHome.path;
|
||||
@@ -68,6 +75,7 @@ void main() {
|
||||
when(() => auth.isAuthenticated).thenReturn(true);
|
||||
when(() => auth.client).thenReturn(httpClient);
|
||||
when(() => logger.progress(any())).thenReturn(_MockProgress());
|
||||
when(() => flutterValidator.validate()).thenAnswer((_) async => []);
|
||||
});
|
||||
|
||||
test('exits with no user when not logged in', () async {
|
||||
@@ -184,5 +192,50 @@ void main() {
|
||||
await expectLater(result, equals(ExitCode.success.code));
|
||||
verify(() => logger.info(output)).called(1);
|
||||
});
|
||||
|
||||
test('prints flutter validation warnings', () async {
|
||||
when(() => flutterValidator.validate()).thenAnswer(
|
||||
(_) async => [
|
||||
const ValidationIssue(
|
||||
severity: ValidationIssueSeverity.warning,
|
||||
message: 'Flutter issue 1',
|
||||
),
|
||||
const ValidationIssue(
|
||||
severity: ValidationIssueSeverity.warning,
|
||||
message: 'Flutter issue 2',
|
||||
),
|
||||
],
|
||||
);
|
||||
final tempDir = Directory.systemTemp.createTempSync();
|
||||
Directory(
|
||||
p.join(runCommand.shorebirdEnginePath, 'engine'),
|
||||
).createSync(recursive: true);
|
||||
|
||||
final progress = _MockProgress();
|
||||
when(() => logger.progress(any())).thenReturn(progress);
|
||||
|
||||
const output = 'some output';
|
||||
when(
|
||||
() => process.stdout,
|
||||
).thenAnswer((_) => Stream.value(utf8.encode(output)));
|
||||
when(() => process.stderr).thenAnswer((_) => const Stream.empty());
|
||||
when(
|
||||
() => process.exitCode,
|
||||
).thenAnswer((_) async => ExitCode.success.code);
|
||||
|
||||
final result = await IOOverrides.runZoned(
|
||||
() => runCommand.run(),
|
||||
getCurrentDirectory: () => tempDir,
|
||||
);
|
||||
|
||||
await expectLater(result, equals(ExitCode.success.code));
|
||||
verify(() => logger.info(output)).called(1);
|
||||
verify(
|
||||
() => logger.info(any(that: contains('Flutter issue 1'))),
|
||||
).called(1);
|
||||
verify(
|
||||
() => logger.info(any(that: contains('Flutter issue 2'))),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -38,7 +38,9 @@ void main() {
|
||||
executable,
|
||||
arguments, {
|
||||
bool runInShell = false,
|
||||
Map<String, String>? environment,
|
||||
workingDirectory,
|
||||
bool useVendedFlutter = true,
|
||||
}) async {
|
||||
if (executable == 'git') {
|
||||
const revParseHead = ['rev-parse', '--verify', 'HEAD'];
|
||||
|
||||
@@ -23,12 +23,15 @@ void main() {
|
||||
|
||||
ShorebirdProcess.processWrapper = processWrapper;
|
||||
|
||||
// TODO(bryanoltman): this method of mocking processWrappper is not correctly matching arguments
|
||||
when(() => processWrapper.run).thenReturn(
|
||||
(
|
||||
executable,
|
||||
arguments, {
|
||||
bool runInShell = false,
|
||||
Map<String, String>? environment,
|
||||
String? workingDirectory,
|
||||
bool useVendedFlutter = true,
|
||||
}) async {
|
||||
return runProcessResult;
|
||||
},
|
||||
@@ -39,6 +42,8 @@ void main() {
|
||||
executable,
|
||||
arguments, {
|
||||
bool runInShell = false,
|
||||
Map<String, String>? environment,
|
||||
bool useVendedFlutter = true,
|
||||
}) async {
|
||||
return startProcess;
|
||||
},
|
||||
@@ -81,6 +86,75 @@ void main() {
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
test(
|
||||
'does not replace flutter with our local flutter if'
|
||||
' useVendedFlutter is false', () async {
|
||||
await ShorebirdProcess.run(
|
||||
'flutter',
|
||||
['--version'],
|
||||
runInShell: true,
|
||||
workingDirectory: '~',
|
||||
useVendedFlutter: false,
|
||||
);
|
||||
|
||||
verify(
|
||||
() => processWrapper.run(
|
||||
'flutter',
|
||||
['--version'],
|
||||
runInShell: true,
|
||||
workingDirectory: '~',
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
test('Updates environment if useVendedFlutter is true', () async {
|
||||
await ShorebirdProcess.run(
|
||||
'flutter',
|
||||
['--version'],
|
||||
runInShell: true,
|
||||
workingDirectory: '~',
|
||||
useVendedFlutter: false,
|
||||
environment: {'ENV_VAR': 'asdfasdf'},
|
||||
);
|
||||
|
||||
verify(
|
||||
() => processWrapper.run(
|
||||
'flutter',
|
||||
['--version'],
|
||||
runInShell: true,
|
||||
workingDirectory: '~',
|
||||
environment: {
|
||||
'ENV_VAR': 'asdfasdf',
|
||||
'FLUTTER_STORAGE_BASE_URL': 'https://download.shorebird.dev/',
|
||||
},
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
test(
|
||||
'Makes no changes to environment if useVendedFlutter is false',
|
||||
() async {
|
||||
await ShorebirdProcess.run(
|
||||
'flutter',
|
||||
['--version'],
|
||||
runInShell: true,
|
||||
workingDirectory: '~',
|
||||
useVendedFlutter: false,
|
||||
environment: {'ENV_VAR': 'asdfasdf'},
|
||||
);
|
||||
|
||||
verify(
|
||||
() => processWrapper.run(
|
||||
'flutter',
|
||||
['--version'],
|
||||
runInShell: true,
|
||||
workingDirectory: '~',
|
||||
environment: {'ENV_VAR': 'asdfasdf'},
|
||||
),
|
||||
).called(1);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
group('start', () {
|
||||
@@ -102,6 +176,66 @@ void main() {
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
test(
|
||||
'does not replace flutter with our local flutter if'
|
||||
' useVendedFlutter is false', () async {
|
||||
await ShorebirdProcess.start(
|
||||
'flutter',
|
||||
['--version'],
|
||||
runInShell: true,
|
||||
useVendedFlutter: false,
|
||||
);
|
||||
|
||||
verify(
|
||||
() => processWrapper.start(
|
||||
'flutter',
|
||||
['--version'],
|
||||
runInShell: true,
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
test('Updates environment if useVendedFlutter is true', () async {
|
||||
await ShorebirdProcess.start(
|
||||
'flutter',
|
||||
['--version'],
|
||||
runInShell: true,
|
||||
);
|
||||
|
||||
verify(
|
||||
() => processWrapper.start(
|
||||
'flutter',
|
||||
['--version'],
|
||||
runInShell: true,
|
||||
environment: {
|
||||
'ENV_VAR': 'asdfasdf',
|
||||
'FLUTTER_STORAGE_BASE_URL': 'https://download.shorebird.dev/',
|
||||
},
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
test(
|
||||
'Makes no changes to environment if useVendedFlutter is false',
|
||||
() async {
|
||||
await ShorebirdProcess.start(
|
||||
'flutter',
|
||||
['--version'],
|
||||
runInShell: true,
|
||||
useVendedFlutter: false,
|
||||
);
|
||||
|
||||
verify(
|
||||
() => processWrapper.start(
|
||||
'flutter',
|
||||
['--version'],
|
||||
runInShell: true,
|
||||
environment: null,
|
||||
),
|
||||
).called(1);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
+1
-2
@@ -2,8 +2,7 @@ import 'dart:io';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:shorebird_cli/src/doctor/doctor_validator.dart';
|
||||
import 'package:shorebird_cli/src/doctor/validators/validators.dart';
|
||||
import 'package:shorebird_cli/src/validators/validators.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
+85
-5
@@ -4,9 +4,8 @@ import 'package:collection/collection.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:platform/platform.dart';
|
||||
import 'package:shorebird_cli/src/doctor/doctor_validator.dart';
|
||||
import 'package:shorebird_cli/src/doctor/validators/validators.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_paths.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_environment.dart';
|
||||
import 'package:shorebird_cli/src/validators/validators.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class _MockProcessResult extends Mock implements ProcessResult {}
|
||||
@@ -25,10 +24,26 @@ nothing to commit, working tree clean
|
||||
const gitBranchMessage = '''
|
||||
main
|
||||
* stable
|
||||
''';
|
||||
|
||||
const pathFlutterVersionMessage = '''
|
||||
Flutter 3.7.9 • channel unknown • unknown source
|
||||
Framework • revision 62bd79521d (7 days ago) • 2023-03-30 10:59:36 -0700
|
||||
Engine • revision ec975089ac
|
||||
Tools • Dart 2.19.6 • DevTools 2.20.1
|
||||
''';
|
||||
|
||||
const shorebirdFlutterVersionMessage = '''
|
||||
Flutter 3.7.9 • channel stable • https://github.com/shorebirdtech/flutter.git
|
||||
Framework • revision 62bd79521d (7 days ago) • 2023-03-30 10:59:36 -0700
|
||||
Engine • revision ec975089ac
|
||||
Tools • Dart 2.19.6 • DevTools 2.20.1
|
||||
''';
|
||||
|
||||
late ShorebirdFlutterValidator validator;
|
||||
late Directory tempDir;
|
||||
late ProcessResult pathFlutterVersionProcessResult;
|
||||
late ProcessResult shorebirdFlutterVersionProcessResult;
|
||||
late ProcessResult gitBranchProcessResult;
|
||||
late ProcessResult gitStatusProcessResult;
|
||||
|
||||
@@ -48,10 +63,13 @@ nothing to commit, working tree clean
|
||||
setUp(() {
|
||||
tempDir = setupTempDirectory();
|
||||
|
||||
ShorebirdPaths.platform = _MockPlatform();
|
||||
when(() => ShorebirdPaths.platform.script)
|
||||
ShorebirdEnvironment.platform = _MockPlatform();
|
||||
when(() => ShorebirdEnvironment.platform.script)
|
||||
.thenReturn(shorebirdScriptFile(tempDir).uri);
|
||||
when(() => ShorebirdEnvironment.platform.environment).thenReturn({});
|
||||
|
||||
pathFlutterVersionProcessResult = _MockProcessResult();
|
||||
shorebirdFlutterVersionProcessResult = _MockProcessResult();
|
||||
gitBranchProcessResult = _MockProcessResult();
|
||||
gitStatusProcessResult = _MockProcessResult();
|
||||
|
||||
@@ -60,7 +78,9 @@ nothing to commit, working tree clean
|
||||
executable,
|
||||
arguments, {
|
||||
bool runInShell = false,
|
||||
Map<String, String>? environment,
|
||||
workingDirectory,
|
||||
bool useVendedFlutter = true,
|
||||
}) async {
|
||||
if (executable == 'git') {
|
||||
if (arguments.equals(['status'])) {
|
||||
@@ -68,11 +88,23 @@ nothing to commit, working tree clean
|
||||
} else if (arguments.equals(['--no-pager', 'branch'])) {
|
||||
return gitBranchProcessResult;
|
||||
}
|
||||
} else if (executable == 'flutter') {
|
||||
if (arguments.equals(['--version'])) {
|
||||
if (useVendedFlutter) {
|
||||
return shorebirdFlutterVersionProcessResult;
|
||||
} else {
|
||||
return pathFlutterVersionProcessResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
return _MockProcessResult();
|
||||
},
|
||||
);
|
||||
|
||||
when(() => pathFlutterVersionProcessResult.stdout)
|
||||
.thenReturn(pathFlutterVersionMessage);
|
||||
when(() => shorebirdFlutterVersionProcessResult.stdout)
|
||||
.thenReturn(shorebirdFlutterVersionMessage);
|
||||
when(() => gitBranchProcessResult.stdout).thenReturn(gitBranchMessage);
|
||||
when(() => gitStatusProcessResult.stdout).thenReturn(gitStatusMessage);
|
||||
});
|
||||
@@ -116,5 +148,53 @@ nothing to commit, working tree clean
|
||||
expect(results.first.severity, ValidationIssueSeverity.warning);
|
||||
expect(results.first.message, contains('is not on the "stable" branch'));
|
||||
});
|
||||
|
||||
test(
|
||||
'warns when path flutter version does not match shorebird flutter'
|
||||
' version',
|
||||
() async {
|
||||
when(() => pathFlutterVersionProcessResult.stdout).thenReturn(
|
||||
pathFlutterVersionMessage.replaceAll('3.7.9', '3.7.10'),
|
||||
);
|
||||
|
||||
final results = await validator.validate();
|
||||
|
||||
expect(results, hasLength(1));
|
||||
expect(results.first.severity, ValidationIssueSeverity.warning);
|
||||
expect(
|
||||
results.first.message,
|
||||
contains('Shorebird Flutter and the Flutter on your path are'
|
||||
' different versions'),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'warns if FLUTTER_STORAGE_BASE_URL has a non-empty value',
|
||||
() async {
|
||||
when(() => ShorebirdEnvironment.platform.environment).thenReturn(
|
||||
{'FLUTTER_STORAGE_BASE_URL': 'https://storage.flutter-io.cn'},
|
||||
);
|
||||
|
||||
final results = await validator.validate();
|
||||
|
||||
expect(results, hasLength(1));
|
||||
expect(results.first.severity, ValidationIssueSeverity.warning);
|
||||
expect(
|
||||
results.first.message,
|
||||
contains(
|
||||
'Shorebird does not respect the FLUTTER_STORAGE_BASE_URL '
|
||||
'environment variable',
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test('throws exception if flutter version output is malformed', () async {
|
||||
when(() => pathFlutterVersionProcessResult.stdout)
|
||||
.thenReturn('OH NO THERE IS NO FLUTTER VERSION HERE');
|
||||
|
||||
expect(() async => validator.validate(), throwsException);
|
||||
});
|
||||
});
|
||||
}
|
||||
+3
-2
@@ -3,8 +3,7 @@ import 'dart:io';
|
||||
import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:shorebird_cli/src/commands/doctor_command.dart';
|
||||
import 'package:shorebird_cli/src/doctor/doctor_validator.dart';
|
||||
import 'package:shorebird_cli/src/doctor/validators/shorebird_version_validator.dart';
|
||||
import 'package:shorebird_cli/src/validators/validators.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class _MockLogger extends Mock implements Logger {}
|
||||
@@ -33,7 +32,9 @@ void main() {
|
||||
executable,
|
||||
arguments, {
|
||||
bool runInShell = false,
|
||||
Map<String, String>? environment,
|
||||
workingDirectory,
|
||||
bool useVendedFlutter = true,
|
||||
}) async {
|
||||
if (executable == 'git') {
|
||||
const revParseHead = ['rev-parse', '--verify', 'HEAD'];
|
||||
Reference in New Issue
Block a user