refactor(shorebird_cli): upgrade analysis_options (#2720)
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
include: package:very_good_analysis/analysis_options.5.1.0.yaml
|
||||
include: package:very_good_analysis/analysis_options.7.0.0.yaml
|
||||
analyzer:
|
||||
exclude:
|
||||
- lib/**.g.dart
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
/// shorebird_cli, The shorebird command-line tool
|
||||
///
|
||||
/// ```sh
|
||||
/// # activate shorebird_cli
|
||||
/// dart pub global activate shorebird_cli
|
||||
///
|
||||
/// # see usage
|
||||
/// shorebird --help
|
||||
/// ```
|
||||
library shorebird_cli;
|
||||
@@ -82,7 +82,7 @@ final artifactBuilderRef = create(ArtifactBuilder.new);
|
||||
ArtifactBuilder get artifactBuilder => read(artifactBuilderRef);
|
||||
|
||||
extension on String {
|
||||
/// Converts this base64-encoded public key into the Map<String, String>:
|
||||
/// Converts this base64-encoded public key into the `Map<String, String>`:
|
||||
/// {'SHOREBIRD_PUBLIC_KEY': this}
|
||||
///
|
||||
/// SHOREBIRD_PUBLIC_KEY is the name expected by the Shorebird's Flutter tool
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
// cspell:words googleapis bryanoltman endtemplate CLI tgvek orctktiabrek
|
||||
// cspell:words GOCSPX googleusercontent Pkkwp Entra
|
||||
import 'dart:convert';
|
||||
@@ -162,7 +161,7 @@ class AuthenticatedClient extends http.BaseClient {
|
||||
client,
|
||||
authEndpoints: authEndpoints,
|
||||
);
|
||||
} catch (e, s) {
|
||||
} on Exception catch (e, s) {
|
||||
logger
|
||||
..err('Failed to refresh credentials.')
|
||||
..info(
|
||||
@@ -328,7 +327,9 @@ class Auth {
|
||||
json.decode(contents) as Map<String, dynamic>,
|
||||
);
|
||||
_email = _credentials?.email;
|
||||
} catch (_) {}
|
||||
} on Exception {
|
||||
// Swallow json decode exceptions.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -365,7 +366,7 @@ extension JwtClaims on oauth2.AccessCredentials {
|
||||
final Jwt jwt;
|
||||
try {
|
||||
jwt = Jwt.parse(token);
|
||||
} catch (_) {
|
||||
} on Exception {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -409,12 +410,15 @@ extension OauthAuthProvider on Jwt {
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension on [AuthProvider] which exposes OAuth 2.0 values.
|
||||
extension OauthValues on AuthProvider {
|
||||
/// The OAuth 2.0 endpoints for the provider.
|
||||
oauth2.AuthEndpoints get authEndpoints => switch (this) {
|
||||
(AuthProvider.google) => const oauth2.GoogleAuthEndpoints(),
|
||||
(AuthProvider.microsoft) => MicrosoftAuthEndpoints(),
|
||||
};
|
||||
|
||||
/// The OAuth 2.0 client ID for the provider.
|
||||
oauth2.ClientId get clientId {
|
||||
switch (this) {
|
||||
case AuthProvider.google:
|
||||
@@ -442,6 +446,7 @@ extension OauthValues on AuthProvider {
|
||||
}
|
||||
}
|
||||
|
||||
/// The OAuth 2.0 scopes for the provider.
|
||||
List<String> get scopes => switch (this) {
|
||||
(AuthProvider.google) => [
|
||||
'openid',
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
import 'dart:io' hide Platform;
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
@@ -32,10 +30,10 @@ class CacheUpdateFailure implements Exception {
|
||||
String toString() => 'CacheUpdateFailure: $message';
|
||||
}
|
||||
|
||||
// A reference to a [Cache] instance.
|
||||
/// A reference to a [Cache] instance.
|
||||
final cacheRef = create(Cache.new);
|
||||
|
||||
// The [Cache] instance available in the current zone.
|
||||
/// The [Cache] instance available in the current zone.
|
||||
Cache get cache => read(cacheRef);
|
||||
|
||||
/// {@template cache}
|
||||
@@ -46,12 +44,14 @@ Cache get cache => read(cacheRef);
|
||||
/// [ShorebirdArtifacts] since uses the current Shorebird environment.
|
||||
/// {@endtemplate}
|
||||
class Cache {
|
||||
/// {@macro cache}
|
||||
Cache() {
|
||||
registerArtifact(PatchArtifact(cache: this, platform: platform));
|
||||
registerArtifact(BundleToolArtifact(cache: this, platform: platform));
|
||||
registerArtifact(AotToolsArtifact(cache: this, platform: platform));
|
||||
}
|
||||
|
||||
/// Register a new [CachedArtifact] with the cache.
|
||||
void registerArtifact(CachedArtifact artifact) => _artifacts.add(artifact);
|
||||
|
||||
/// Update all artifacts in the cache.
|
||||
@@ -119,10 +119,13 @@ class Cache {
|
||||
|
||||
final List<CachedArtifact> _artifacts = [];
|
||||
|
||||
/// The storage base url.
|
||||
String get storageBaseUrl => 'https://storage.googleapis.com';
|
||||
|
||||
/// The storage bucket host.
|
||||
String get storageBucket => 'download.shorebird.dev';
|
||||
|
||||
/// Clear the cache.
|
||||
Future<void> clear() async {
|
||||
final cacheDir = shorebirdCacheDirectory;
|
||||
if (cacheDir.existsSync()) {
|
||||
@@ -131,10 +134,17 @@ class Cache {
|
||||
}
|
||||
}
|
||||
|
||||
/// {@template cached_artifact}
|
||||
/// An artifact which is cached by Shorebird.
|
||||
/// {@endtemplate}
|
||||
abstract class CachedArtifact {
|
||||
/// {@macro cached_artifact}
|
||||
CachedArtifact({required this.cache, required this.platform});
|
||||
|
||||
/// The cache instance to use.
|
||||
final Cache cache;
|
||||
|
||||
/// The platform to use.
|
||||
final Platform platform;
|
||||
|
||||
/// The on-disk name of the artifact.
|
||||
@@ -156,6 +166,7 @@ abstract class CachedArtifact {
|
||||
/// is assumed to be correct.
|
||||
String? get checksum;
|
||||
|
||||
/// Extract the artifact from the provided [stream] to the [outputPath].
|
||||
Future<void> extractArtifact(http.ByteStream stream, String outputPath) {
|
||||
final file = File(p.join(outputPath, fileName))
|
||||
..createSync(recursive: true);
|
||||
@@ -169,6 +180,7 @@ abstract class CachedArtifact {
|
||||
/// Used to validate that the artifact was fully downloaded and extracted.
|
||||
File get stampFile => File('${file.path}.stamp');
|
||||
|
||||
/// Whether the artifact is valid (has a matching checksum).
|
||||
Future<bool> isValid() async {
|
||||
if (!file.existsSync() || !stampFile.existsSync()) {
|
||||
return false;
|
||||
@@ -184,6 +196,7 @@ abstract class CachedArtifact {
|
||||
return checksumChecker.checkFile(file, checksum!);
|
||||
}
|
||||
|
||||
/// Re-fetch the artifact from the storage URL.
|
||||
Future<void> update() async {
|
||||
// Clear any existing artifact files.
|
||||
await _delete();
|
||||
@@ -271,7 +284,12 @@ allowed to access $storageUrl.''',
|
||||
}
|
||||
}
|
||||
|
||||
/// {@template aot_tools_artifact}
|
||||
/// The aot_tools.dill artifact.
|
||||
/// Used for linking and generating optimized AOT snapshots.
|
||||
/// {@endtemplate}
|
||||
class AotToolsArtifact extends CachedArtifact {
|
||||
/// {@macro aot_tools_artifact}
|
||||
AotToolsArtifact({required super.cache, required super.platform});
|
||||
|
||||
@override
|
||||
@@ -301,7 +319,11 @@ class AotToolsArtifact extends CachedArtifact {
|
||||
String? get checksum => null;
|
||||
}
|
||||
|
||||
/// {@template patch_artifact}
|
||||
/// The patch artifact which is used to apply binary patches.
|
||||
/// {@endtemplate}
|
||||
class PatchArtifact extends CachedArtifact {
|
||||
/// {@macro patch_artifact}
|
||||
PatchArtifact({required super.cache, required super.platform});
|
||||
|
||||
@override
|
||||
@@ -342,7 +364,12 @@ class PatchArtifact extends CachedArtifact {
|
||||
String? get checksum => null;
|
||||
}
|
||||
|
||||
/// {@template bundle_tool_artifact}
|
||||
/// The bundletool.jar artifact.
|
||||
/// Used for interacting with Android app bundles (aab).
|
||||
/// {@endtemplate}
|
||||
class BundleToolArtifact extends CachedArtifact {
|
||||
/// {@macro bundle_tool_artifact}
|
||||
BundleToolArtifact({required super.cache, required super.platform});
|
||||
|
||||
@override
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// TODO(felangel): Add public member API docs and remove the ignore.
|
||||
// ignore_for_file: public_member_api_docs
|
||||
// cspell:words endtemplate pubspec sideloadable bryanoltman archs sideload
|
||||
// cspell:words xcarchive codesigned xcframework
|
||||
@@ -87,11 +88,7 @@ class CodePushClientWrapper {
|
||||
}) async {
|
||||
late final String displayName;
|
||||
if (appName == null) {
|
||||
String? defaultAppName;
|
||||
try {
|
||||
defaultAppName = shorebirdEnv.getPubspecYaml()?.name;
|
||||
} catch (_) {}
|
||||
|
||||
final defaultAppName = shorebirdEnv.getPubspecYaml()?.name;
|
||||
displayName = logger.prompt(
|
||||
'${lightGreen.wrap('?')} How should we refer to this app?',
|
||||
defaultValue: defaultAppName,
|
||||
|
||||
@@ -93,10 +93,9 @@ Android Toolchain
|
||||
• Gradle: ${gradlewVersion ?? notDetected}''');
|
||||
}
|
||||
|
||||
logger.info(output.toString());
|
||||
|
||||
// ignore: cascade_invocations
|
||||
logger.info('URL Reachability');
|
||||
logger
|
||||
..info(output.toString())
|
||||
..info('URL Reachability');
|
||||
await networkChecker.checkReachability();
|
||||
logger.info('');
|
||||
|
||||
@@ -111,7 +110,7 @@ Android Toolchain
|
||||
);
|
||||
} on NetworkCheckerException catch (error) {
|
||||
uploadProgress.fail('GCP upload speed test failed: ${error.message}');
|
||||
} catch (error) {
|
||||
} on Exception catch (error) {
|
||||
uploadProgress.fail('GCP upload speed test failed: $error');
|
||||
}
|
||||
|
||||
@@ -127,7 +126,7 @@ Android Toolchain
|
||||
downloadProgress.fail(
|
||||
'GCP download speed test failed: ${error.message}',
|
||||
);
|
||||
} catch (error) {
|
||||
} on Exception catch (error) {
|
||||
downloadProgress.fail(
|
||||
'GCP download speed test failed: $error',
|
||||
);
|
||||
@@ -143,7 +142,7 @@ Android Toolchain
|
||||
Future<String?> _tryGetFlutterVersion() async {
|
||||
try {
|
||||
return await shorebirdFlutter.getVersionString();
|
||||
} catch (error) {
|
||||
} on Exception catch (error) {
|
||||
logger.detail('Unable to determine Flutter version.\n$error');
|
||||
return null;
|
||||
}
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ class FlutterVersionsListCommand extends ShorebirdCommand {
|
||||
try {
|
||||
versions = await shorebirdFlutter.getVersions();
|
||||
progress.cancel();
|
||||
} catch (error) {
|
||||
} on Exception catch (error) {
|
||||
progress.fail('Failed to fetch Flutter versions.');
|
||||
logger.err('$error');
|
||||
return ExitCode.software.code;
|
||||
|
||||
@@ -60,7 +60,7 @@ Please make sure you are running "shorebird init" from within your Flutter proje
|
||||
''');
|
||||
return ExitCode.noInput.code;
|
||||
}
|
||||
} catch (error) {
|
||||
} on Exception catch (error) {
|
||||
logger.err('Error parsing "pubspec.yaml": $error');
|
||||
return ExitCode.software.code;
|
||||
}
|
||||
@@ -108,7 +108,7 @@ Please make sure you are running "shorebird init" from within your Flutter proje
|
||||
logger.info(' - $flavor');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
} on Exception catch (error) {
|
||||
detectFlavorsProgress.fail();
|
||||
logger.err('Unable to extract product flavors.\n$error');
|
||||
return ExitCode.software.code;
|
||||
@@ -138,7 +138,7 @@ Please make sure you are running "shorebird init" from within your Flutter proje
|
||||
existingApp = await codePushClientWrapper.getApp(
|
||||
appId: shorebirdYaml!.appId,
|
||||
);
|
||||
} catch (e) {
|
||||
} on Exception catch (e) {
|
||||
updateShorebirdYamlProgress.fail('Failed to get existing app info: $e');
|
||||
return ExitCode.software.code;
|
||||
}
|
||||
@@ -227,7 +227,7 @@ Please make sure you are running "shorebird init" from within your Flutter proje
|
||||
flavors = values;
|
||||
appId = flavors.values.first;
|
||||
}
|
||||
} catch (error) {
|
||||
} on Exception catch (error) {
|
||||
logger.err('$error');
|
||||
return ExitCode.software.code;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:shorebird_cli/src/auth/auth.dart';
|
||||
import 'package:shorebird_cli/src/logging/logging.dart';
|
||||
@@ -12,6 +10,7 @@ import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart'
|
||||
/// Login as a CI user.
|
||||
/// {@endtemplate}
|
||||
class LoginCiCommand extends ShorebirdCommand {
|
||||
/// {@macro login_ci_command}
|
||||
LoginCiCommand() {
|
||||
argParser.addOption(
|
||||
'provider',
|
||||
@@ -54,7 +53,7 @@ We could not find a Shorebird account for ${error.email}.''',
|
||||
'''If you have not yet created an account, go to "${link(uri: Uri.parse('https://console.shorebird.dev'))}" to create one. If you believe this is an error, please reach out to us via Discord, we're happy to help!''',
|
||||
);
|
||||
return ExitCode.software.code;
|
||||
} catch (error) {
|
||||
} on Exception catch (error) {
|
||||
logger.err(error.toString());
|
||||
return ExitCode.software.code;
|
||||
}
|
||||
@@ -72,6 +71,7 @@ ${lightCyan.wrap('export $shorebirdTokenEnvVar="\$SHOREBIRD_TOKEN" && shorebird
|
||||
return ExitCode.success.code;
|
||||
}
|
||||
|
||||
/// Prompt the user to visit the provided [url] to authorize the CLI.
|
||||
void prompt(String url) {
|
||||
logger.info('''
|
||||
The Shorebird CLI needs your authorization to manage apps, releases, and patches on your behalf.
|
||||
|
||||
@@ -61,7 +61,7 @@ We could not find a Shorebird account for ${error.email}.''',
|
||||
"""If you have not yet created an account, you can do so at "${link(uri: consoleUri)}". If you believe this is an error, please reach out to us via Discord, we're happy to help!""",
|
||||
);
|
||||
return ExitCode.software.code;
|
||||
} catch (error) {
|
||||
} on Exception catch (error) {
|
||||
logger.err(error.toString());
|
||||
return ExitCode.software.code;
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ class AarPatcher extends Patcher {
|
||||
Uri.parse(releaseArtifact.value.url),
|
||||
);
|
||||
releaseArtifactPaths[releaseArtifact.key] = releaseArtifactFile.path;
|
||||
} catch (error) {
|
||||
} on Exception catch (error) {
|
||||
downloadReleaseArtifactProgress.fail('$error');
|
||||
throw ProcessExit(ExitCode.software.code);
|
||||
}
|
||||
@@ -163,7 +163,7 @@ class AarPatcher extends Patcher {
|
||||
hash: hash,
|
||||
size: await File(diffPath).length(),
|
||||
);
|
||||
} catch (error) {
|
||||
} on Exception catch (error) {
|
||||
createDiffProgress.fail('$error');
|
||||
throw ProcessExit(ExitCode.software.code);
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ Please refer to ${link(uri: Uri.parse('https://github.com/shorebirdtech/shorebir
|
||||
message: 'Downloading release artifact ${i + 1}/$numArtifacts',
|
||||
);
|
||||
releaseArtifactPaths[releaseArtifact.key] = releaseArtifactFile.path;
|
||||
} catch (error) {
|
||||
} on Exception {
|
||||
throw ProcessExit(ExitCode.software.code);
|
||||
}
|
||||
}
|
||||
@@ -227,7 +227,7 @@ Please refer to ${link(uri: Uri.parse('https://github.com/shorebirdtech/shorebir
|
||||
size: await File(diffPath).length(),
|
||||
hashSignature: hashSignature,
|
||||
);
|
||||
} catch (error) {
|
||||
} on Exception catch (error) {
|
||||
createDiffProgress.fail('$error');
|
||||
throw ProcessExit(ExitCode.software.code);
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ class IosFrameworkPatcher extends Patcher {
|
||||
genSnapshotArtifact: ShorebirdArtifact.genSnapshotIos,
|
||||
additionalArgs: IosPatcher.splitDebugInfoArgs(splitDebugInfoPath),
|
||||
);
|
||||
} catch (error) {
|
||||
} on Exception catch (error) {
|
||||
buildProgress.fail('$error');
|
||||
throw ProcessExit(ExitCode.software.code);
|
||||
}
|
||||
@@ -259,7 +259,7 @@ class IosFrameworkPatcher extends Patcher {
|
||||
releaseSnapshot: releaseArtifactFile,
|
||||
);
|
||||
patchBaseProgress.complete();
|
||||
} catch (error) {
|
||||
} on Exception catch (error) {
|
||||
patchBaseProgress.fail('$error');
|
||||
throw ProcessExit(ExitCode.software.code);
|
||||
}
|
||||
@@ -339,7 +339,7 @@ class IosFrameworkPatcher extends Patcher {
|
||||
workingDirectory: buildDirectory.path,
|
||||
additionalArgs: IosPatcher.splitDebugInfoArgs(splitDebugInfoPath),
|
||||
);
|
||||
} catch (error) {
|
||||
} on Exception catch (error) {
|
||||
linkProgress.fail('Failed to link AOT files: $error');
|
||||
throw ProcessExit(ExitCode.software.code);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
@@ -79,6 +77,7 @@ class IosPatcher extends Patcher {
|
||||
return p.join(p.absolute(directory), splitDebugInfoFileName);
|
||||
}
|
||||
|
||||
/// The last build's link percentage.
|
||||
@visibleForTesting
|
||||
double? lastBuildLinkPercentage;
|
||||
|
||||
@@ -227,7 +226,7 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
|
||||
ipaBuildResult.kernelFile.copySync(_appDillCopyPath);
|
||||
|
||||
buildProgress.complete();
|
||||
} catch (_) {
|
||||
} on Exception {
|
||||
throw ProcessExit(ExitCode.software.code);
|
||||
}
|
||||
|
||||
@@ -354,7 +353,7 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
|
||||
releaseSnapshot: releaseArtifactFile,
|
||||
);
|
||||
patchBaseProgress.complete();
|
||||
} catch (error) {
|
||||
} on Exception catch (error) {
|
||||
patchBaseProgress.fail('$error');
|
||||
throw ProcessExit(ExitCode.software.code);
|
||||
}
|
||||
@@ -407,7 +406,7 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
|
||||
final plist = Plist(file: plistFile);
|
||||
try {
|
||||
return plist.versionNumber;
|
||||
} catch (error) {
|
||||
} on Exception catch (error) {
|
||||
logger.err(
|
||||
'Failed to determine release version from ${plistFile.path}: $error',
|
||||
);
|
||||
@@ -478,7 +477,7 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
|
||||
dumpDebugInfoPath: dumpDebugInfoDir?.path,
|
||||
additionalArgs: splitDebugInfoArgs(splitDebugInfoPath),
|
||||
);
|
||||
} catch (error) {
|
||||
} on Exception catch (error) {
|
||||
linkProgress.fail('Failed to link AOT files: $error');
|
||||
return (exitCode: ExitCode.software.code, linkPercentage: null);
|
||||
} finally {
|
||||
|
||||
@@ -228,7 +228,7 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
|
||||
macosBuildResult.kernelFile.copySync(_appDillCopyPath);
|
||||
|
||||
buildProgress.complete();
|
||||
} catch (_) {
|
||||
} on Exception {
|
||||
throw ProcessExit(ExitCode.software.code);
|
||||
}
|
||||
|
||||
@@ -345,7 +345,7 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
|
||||
releaseSnapshot: releaseArtifactFile,
|
||||
);
|
||||
patchBaseProgress.complete();
|
||||
} catch (error) {
|
||||
} on Exception catch (error) {
|
||||
patchBaseProgress.fail('$error');
|
||||
throw ProcessExit(ExitCode.software.code);
|
||||
}
|
||||
@@ -396,7 +396,7 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
|
||||
final plist = Plist(file: plistFile);
|
||||
try {
|
||||
return plist.versionNumber;
|
||||
} catch (error) {
|
||||
} on Exception catch (error) {
|
||||
logger.err(
|
||||
'Failed to determine release version from ${plistFile.path}: $error',
|
||||
);
|
||||
@@ -467,7 +467,7 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
|
||||
dumpDebugInfoPath: dumpDebugInfoDir?.path,
|
||||
additionalArgs: splitDebugInfoArgs(splitDebugInfoPath),
|
||||
);
|
||||
} catch (error) {
|
||||
} on Exception catch (error) {
|
||||
linkProgress.fail('Failed to link AOT files: $error');
|
||||
return (exitCode: ExitCode.software.code, linkPercentage: null);
|
||||
} finally {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
@@ -29,9 +27,15 @@ import 'package:shorebird_cli/src/third_party/flutter_tools/lib/flutter_tools.da
|
||||
import 'package:shorebird_cli/src/version.dart';
|
||||
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
|
||||
|
||||
/// Signature for a function that returns a [Patcher] for a given [ReleaseType].
|
||||
typedef ResolvePatcher = Patcher Function(ReleaseType releaseType);
|
||||
|
||||
/// {@template patch_command}
|
||||
/// A command that creates a shorebird patch for the provided target platforms.
|
||||
/// `shorebird patch --platforms=android,ios`
|
||||
/// {@endtemplate}
|
||||
class PatchCommand extends ShorebirdCommand {
|
||||
/// {@macro patch_command}
|
||||
PatchCommand({
|
||||
ResolvePatcher? resolvePatcher,
|
||||
}) {
|
||||
@@ -142,10 +146,12 @@ of the iOS app that is using this module.''',
|
||||
);
|
||||
}
|
||||
|
||||
/// Warning message for when native code diffs are detected.
|
||||
static final allowNativeDiffsHelpText = '''
|
||||
Patch even if native code diffs are detected.
|
||||
NOTE: this is ${styleBold.wrap('not')} recommended. Native code changes cannot be included in a patch and attempting to do so can cause your app to crash or behave unexpectedly.''';
|
||||
|
||||
/// Warning message for when asset diffs are detected.
|
||||
static final allowAssetDiffsHelpText = '''
|
||||
Patch even if asset diffs are detected.
|
||||
NOTE: this is ${styleBold.wrap('not')} recommended. Asset changes cannot be included in a patch can cause your app to behave unexpectedly.''';
|
||||
@@ -177,8 +183,10 @@ NOTE: this is ${styleBold.wrap('not')} recommended. Asset changes cannot be incl
|
||||
/// Whether --no-confirm was passed.
|
||||
bool get noConfirm => results['no-confirm'] == true;
|
||||
|
||||
/// Whether the patch is for the staging environment.
|
||||
bool get isStaging => track == DeploymentTrack.staging;
|
||||
|
||||
/// The deployment track to publish the patch to.
|
||||
DeploymentTrack get track {
|
||||
final channel = results['track'] as String;
|
||||
return DeploymentTrack.values.firstWhere((t) => t.channel == channel);
|
||||
@@ -207,6 +215,7 @@ NOTE: this is ${styleBold.wrap('not')} recommended. Asset changes cannot be incl
|
||||
return ExitCode.success.code;
|
||||
}
|
||||
|
||||
/// Returns a [Patcher] for the given [ReleaseType].
|
||||
@visibleForTesting
|
||||
Patcher getPatcher(ReleaseType releaseType) {
|
||||
switch (releaseType) {
|
||||
@@ -248,8 +257,10 @@ NOTE: this is ${styleBold.wrap('not')} recommended. Asset changes cannot be incl
|
||||
}
|
||||
}
|
||||
|
||||
/// The last built Flutter revision.
|
||||
String? lastBuiltFlutterRevision;
|
||||
|
||||
/// Creates a patch using the provided [patcher].
|
||||
@visibleForTesting
|
||||
Future<void> createPatch(Patcher patcher) async {
|
||||
await patcher.assertPreconditions();
|
||||
@@ -291,7 +302,7 @@ NOTE: this is ${styleBold.wrap('not')} recommended. Asset changes cannot be incl
|
||||
|
||||
try {
|
||||
await shorebirdFlutter.installRevision(revision: release.flutterRevision);
|
||||
} catch (_) {
|
||||
} on Exception {
|
||||
throw ProcessExit(ExitCode.software.code);
|
||||
}
|
||||
|
||||
@@ -395,6 +406,7 @@ NOTE: this is ${styleBold.wrap('not')} recommended. Asset changes cannot be incl
|
||||
);
|
||||
}
|
||||
|
||||
/// Prompts the user for the specific release to patch.
|
||||
Future<Release> promptForRelease() async {
|
||||
final releases = await codePushClientWrapper.getReleases(
|
||||
appId: appId,
|
||||
@@ -414,6 +426,7 @@ NOTE: this is ${styleBold.wrap('not')} recommended. Asset changes cannot be incl
|
||||
);
|
||||
}
|
||||
|
||||
/// Asserts that the release contains a platform for the given [patcher].
|
||||
void assertReleaseContainsPlatform({
|
||||
required Release release,
|
||||
required Patcher patcher,
|
||||
@@ -429,6 +442,7 @@ NOTE: this is ${styleBold.wrap('not')} recommended. Asset changes cannot be incl
|
||||
}
|
||||
}
|
||||
|
||||
/// Asserts that the provided [release] is active.
|
||||
void assertReleaseIsActive({
|
||||
required Release release,
|
||||
required Patcher patcher,
|
||||
@@ -443,6 +457,7 @@ Please re-run the release command for this version or create a new release.''');
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensures the diff between the release and patch archives is safe to patch.
|
||||
Future<DiffStatus> assertUnpatchableDiffs({
|
||||
required ReleaseArtifact releaseArtifact,
|
||||
required File patchArchive,
|
||||
@@ -463,6 +478,7 @@ Please re-run the release command for this version or create a new release.''');
|
||||
}
|
||||
}
|
||||
|
||||
/// Confirms the patch creation (including a summary).
|
||||
Future<void> confirmCreatePatch({
|
||||
required AppMetadata app,
|
||||
required String releaseVersion,
|
||||
@@ -511,6 +527,7 @@ ${summary.join('\n')}
|
||||
}
|
||||
}
|
||||
|
||||
/// Downloads the given [releaseArtifact].
|
||||
Future<File> downloadReleaseArtifact({
|
||||
required ReleaseArtifact releaseArtifact,
|
||||
}) async {
|
||||
@@ -520,7 +537,7 @@ ${summary.join('\n')}
|
||||
Uri.parse(releaseArtifact.url),
|
||||
message: 'Downloading ${releaseArtifact.arch}',
|
||||
);
|
||||
} catch (_) {
|
||||
} on Exception {
|
||||
throw ProcessExit(ExitCode.software.code);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:args/args.dart';
|
||||
@@ -31,7 +29,7 @@ abstract class Patcher {
|
||||
required this.target,
|
||||
});
|
||||
|
||||
// Link percentage that is considered the minimum before a user might notice.
|
||||
/// Link percentage that is considered the minimum before a user might notice.
|
||||
static const double minLinkPercentage = 75;
|
||||
|
||||
/// The standard link percentage warning.
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
// cspell:words devicectl endtemplate bryanoltman sideloadable previewable apks
|
||||
// cspell:words bundletool
|
||||
import 'dart:async';
|
||||
@@ -252,6 +251,7 @@ class PreviewCommand extends ShorebirdCommand {
|
||||
};
|
||||
}
|
||||
|
||||
/// Prompts the user to choose an app to preview.
|
||||
Future<String?> promptForApp() async {
|
||||
final apps = await codePushClientWrapper.getApps();
|
||||
if (apps.isEmpty) return null;
|
||||
@@ -263,6 +263,7 @@ class PreviewCommand extends ShorebirdCommand {
|
||||
return app.appId;
|
||||
}
|
||||
|
||||
/// Prompts the user to choose a release version to preview.
|
||||
Future<String?> promptForReleaseVersion(List<Release> releases) async {
|
||||
if (releases.isEmpty) return null;
|
||||
final release = logger.chooseOne(
|
||||
@@ -273,6 +274,7 @@ class PreviewCommand extends ShorebirdCommand {
|
||||
return release.version;
|
||||
}
|
||||
|
||||
/// Prompts the user to choose a platform to preview.
|
||||
Future<ReleasePlatform> promptForPlatform(
|
||||
List<ReleasePlatform> platforms,
|
||||
) async {
|
||||
@@ -284,6 +286,7 @@ class PreviewCommand extends ShorebirdCommand {
|
||||
return ReleasePlatform.values.firstWhere((p) => p.displayName == platform);
|
||||
}
|
||||
|
||||
/// Installs and launches the release on macOS.
|
||||
Future<int> installAndLaunchMacos({
|
||||
required String appId,
|
||||
required Release release,
|
||||
@@ -300,7 +303,7 @@ class PreviewCommand extends ShorebirdCommand {
|
||||
arch: 'app',
|
||||
platform: platform,
|
||||
);
|
||||
} catch (e, s) {
|
||||
} on Exception catch (e, s) {
|
||||
logger
|
||||
..err('Error getting release artifact: $e')
|
||||
..detail('Stack trace: $s');
|
||||
@@ -332,7 +335,7 @@ class PreviewCommand extends ShorebirdCommand {
|
||||
destination: appDirectory.path,
|
||||
);
|
||||
downloadArtifactProgress.complete();
|
||||
} catch (error) {
|
||||
} on Exception catch (error) {
|
||||
downloadArtifactProgress.fail('$error');
|
||||
return ExitCode.software.code;
|
||||
}
|
||||
@@ -349,6 +352,7 @@ class PreviewCommand extends ShorebirdCommand {
|
||||
return completer.future.then((_) => ExitCode.success.code);
|
||||
}
|
||||
|
||||
/// Installs and launches the release on Android.
|
||||
Future<int> installAndLaunchAndroid({
|
||||
required String appId,
|
||||
required Release release,
|
||||
@@ -368,7 +372,7 @@ class PreviewCommand extends ShorebirdCommand {
|
||||
arch: 'aab',
|
||||
platform: platform,
|
||||
);
|
||||
} catch (e, s) {
|
||||
} on Exception catch (e, s) {
|
||||
logger
|
||||
..err('Error getting release artifact: $e')
|
||||
..detail('Stack trace: $s');
|
||||
@@ -396,7 +400,7 @@ class PreviewCommand extends ShorebirdCommand {
|
||||
}
|
||||
|
||||
downloadArtifactProgress.complete();
|
||||
} catch (error) {
|
||||
} on Exception catch (error) {
|
||||
downloadArtifactProgress.fail('$error');
|
||||
return ExitCode.software.code;
|
||||
}
|
||||
@@ -414,7 +418,7 @@ class PreviewCommand extends ShorebirdCommand {
|
||||
try {
|
||||
await setChannelOnAab(aabFile: aabFile, channel: track.channel);
|
||||
progress.complete();
|
||||
} catch (error) {
|
||||
} on Exception catch (error) {
|
||||
progress.fail('$error');
|
||||
return ExitCode.software.code;
|
||||
}
|
||||
@@ -424,7 +428,7 @@ class PreviewCommand extends ShorebirdCommand {
|
||||
try {
|
||||
package = await bundletool.getPackageName(aabFile.path);
|
||||
extractMetadataProgress.complete();
|
||||
} catch (error) {
|
||||
} on Exception catch (error) {
|
||||
extractMetadataProgress.fail('$error');
|
||||
return ExitCode.software.code;
|
||||
}
|
||||
@@ -434,7 +438,7 @@ class PreviewCommand extends ShorebirdCommand {
|
||||
await bundletool.buildApks(bundle: aabFile.path, output: apksPath);
|
||||
final apksLink = link(uri: Uri.parse(apksPath));
|
||||
buildApksProgress.complete('Built apks: ${cyan.wrap(apksLink)}');
|
||||
} catch (error) {
|
||||
} on Exception catch (error) {
|
||||
buildApksProgress.fail('$error');
|
||||
return ExitCode.software.code;
|
||||
}
|
||||
@@ -443,7 +447,7 @@ class PreviewCommand extends ShorebirdCommand {
|
||||
try {
|
||||
await bundletool.installApks(apks: apksPath, deviceId: deviceId);
|
||||
installApksProgress.complete();
|
||||
} catch (error) {
|
||||
} on Exception catch (error) {
|
||||
installApksProgress.fail('$error');
|
||||
return ExitCode.software.code;
|
||||
}
|
||||
@@ -453,7 +457,7 @@ class PreviewCommand extends ShorebirdCommand {
|
||||
await adb.clearAppData(package: package, deviceId: deviceId);
|
||||
await adb.startApp(package: package, deviceId: deviceId);
|
||||
startAppProgress.complete();
|
||||
} catch (error) {
|
||||
} on Exception catch (error) {
|
||||
startAppProgress.fail('$error');
|
||||
return ExitCode.software.code;
|
||||
}
|
||||
@@ -469,6 +473,7 @@ class PreviewCommand extends ShorebirdCommand {
|
||||
return process.exitCode;
|
||||
}
|
||||
|
||||
/// Installs and launches the release on iOS.
|
||||
Future<int> installAndLaunchIos({
|
||||
required String appId,
|
||||
required Release release,
|
||||
@@ -488,7 +493,7 @@ class PreviewCommand extends ShorebirdCommand {
|
||||
arch: 'runner',
|
||||
platform: platform,
|
||||
);
|
||||
} catch (e, s) {
|
||||
} on Exception catch (e, s) {
|
||||
logger
|
||||
..err('Error getting release artifact: $e')
|
||||
..detail('Stack trace: $s');
|
||||
@@ -520,7 +525,7 @@ class PreviewCommand extends ShorebirdCommand {
|
||||
outputDirectory: runnerDirectory,
|
||||
);
|
||||
downloadArtifactProgress.complete();
|
||||
} catch (error) {
|
||||
} on Exception catch (error) {
|
||||
downloadArtifactProgress.fail('$error');
|
||||
return ExitCode.software.code;
|
||||
}
|
||||
@@ -533,7 +538,7 @@ class PreviewCommand extends ShorebirdCommand {
|
||||
channel: track.channel,
|
||||
);
|
||||
progress.complete();
|
||||
} catch (error) {
|
||||
} on Exception catch (error) {
|
||||
progress.fail('$error');
|
||||
return ExitCode.software.code;
|
||||
}
|
||||
@@ -576,12 +581,13 @@ class PreviewCommand extends ShorebirdCommand {
|
||||
}
|
||||
|
||||
return installExitCode;
|
||||
} catch (error, stackTrace) {
|
||||
} on Exception catch (error, stackTrace) {
|
||||
logger.detail('Error launching app. $error $stackTrace');
|
||||
return ExitCode.software.code;
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolves the artifact path for the given parameters.
|
||||
String getArtifactPath({
|
||||
required String appId,
|
||||
required Release release,
|
||||
@@ -695,7 +701,10 @@ class PreviewCommand extends ShorebirdCommand {
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension on [Release] that exposes the active platforms (e.g. platforms
|
||||
/// that can be previewed).
|
||||
extension Previewable on Release {
|
||||
/// Returns the platforms that can be previewed.
|
||||
List<ReleasePlatform> get activePlatforms => platformStatuses.entries
|
||||
.where((e) => e.value == ReleaseStatus.active)
|
||||
.map((e) => e.key)
|
||||
|
||||
@@ -84,7 +84,7 @@ class AarReleaser extends Releaser {
|
||||
targetPlatforms: architectures,
|
||||
args: argResults.forwardedArgs,
|
||||
);
|
||||
} catch (e) {
|
||||
} on Exception catch (e) {
|
||||
logger.err('Failed to build aar: $e');
|
||||
throw ProcessExit(ExitCode.software.code);
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ Please comment and upvote ${link(uri: Uri.parse('https://github.com/shorebirdtec
|
||||
releaseArtifactRoot.path,
|
||||
);
|
||||
releaseVersionProgress.complete('Release version: $releaseVersion');
|
||||
} catch (error) {
|
||||
} on Exception catch (error) {
|
||||
releaseVersionProgress.fail('$error');
|
||||
throw ProcessExit(ExitCode.software.code);
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
|
||||
|
||||
try {
|
||||
await artifactBuilder.buildIosFramework(args: argResults.forwardedArgs);
|
||||
} catch (error) {
|
||||
} on Exception catch (error) {
|
||||
buildProgress.fail('Failed to build iOS framework: $error');
|
||||
throw ProcessExit(ExitCode.software.code);
|
||||
}
|
||||
|
||||
@@ -165,7 +165,7 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
|
||||
|
||||
try {
|
||||
return Plist(file: plistFile).versionNumber;
|
||||
} catch (error) {
|
||||
} on Exception catch (error) {
|
||||
logger.err(
|
||||
'''Failed to determine release version from ${plistFile.path}: $error''',
|
||||
);
|
||||
|
||||
@@ -148,7 +148,7 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
|
||||
|
||||
try {
|
||||
return Plist(file: plistFile).versionNumber;
|
||||
} catch (error) {
|
||||
} on Exception catch (error) {
|
||||
logger.err(
|
||||
'''Failed to determine release version from ${plistFile.path}: $error''',
|
||||
);
|
||||
|
||||
@@ -250,7 +250,7 @@ of the iOS app that is using this module. (aar and ios-framework only)''',
|
||||
final targetFlutterRevision = await resolveTargetFlutterRevision();
|
||||
try {
|
||||
await shorebirdFlutter.installRevision(revision: targetFlutterRevision);
|
||||
} catch (_) {
|
||||
} on Exception {
|
||||
throw ProcessExit(ExitCode.software.code);
|
||||
}
|
||||
|
||||
@@ -331,7 +331,7 @@ of the iOS app that is using this module. (aar and ios-framework only)''',
|
||||
revision = await shorebirdFlutter.resolveFlutterRevision(
|
||||
flutterVersionArg!,
|
||||
);
|
||||
} catch (error) {
|
||||
} on Exception catch (error) {
|
||||
logger.err(
|
||||
'''
|
||||
Unable to determine revision for Flutter version: $flutterVersionArg.
|
||||
|
||||
@@ -115,7 +115,7 @@ class GetApksCommand extends ShorebirdCommand {
|
||||
universal: results['universal'] as bool,
|
||||
);
|
||||
buildApksProgress.complete();
|
||||
} catch (error) {
|
||||
} on Exception catch (error) {
|
||||
buildApksProgress.fail('$error');
|
||||
return ExitCode.software.code;
|
||||
}
|
||||
@@ -178,7 +178,7 @@ class GetApksCommand extends ShorebirdCommand {
|
||||
Uri.parse(releaseArtifact.url),
|
||||
message: 'Downloading aab',
|
||||
);
|
||||
} catch (_) {
|
||||
} on Exception catch (_) {
|
||||
throw ProcessExit(ExitCode.software.code);
|
||||
}
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ class Doctor {
|
||||
for (final issue in fixableIssues) {
|
||||
try {
|
||||
await issue.fix!();
|
||||
} catch (error) {
|
||||
} on Exception catch (error) {
|
||||
failedFixes[issue] = error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
import 'package:scoped_deps/scoped_deps.dart';
|
||||
|
||||
/// A reference to an [EngineConfig] instance.
|
||||
@@ -8,20 +6,30 @@ final engineConfigRef = create(() => const EngineConfig.empty());
|
||||
/// The [EngineConfig] instance available in the current zone.
|
||||
EngineConfig get engineConfig => read(engineConfigRef);
|
||||
|
||||
/// {@template engine_config}
|
||||
/// An object that contains a local engine configuration.
|
||||
/// {@endtemplate}
|
||||
class EngineConfig {
|
||||
/// {@macro engine_config}
|
||||
const EngineConfig({
|
||||
required this.localEngineSrcPath,
|
||||
required this.localEngine,
|
||||
required this.localEngineHost,
|
||||
});
|
||||
|
||||
/// An empty [EngineConfig] instance.
|
||||
const EngineConfig.empty()
|
||||
: localEngineSrcPath = null,
|
||||
localEngine = null,
|
||||
localEngineHost = null;
|
||||
|
||||
/// The path to the local engine source.
|
||||
final String? localEngineSrcPath;
|
||||
|
||||
/// The local engine name.
|
||||
final String? localEngine;
|
||||
|
||||
/// The local engine host.
|
||||
final String? localEngineHost;
|
||||
|
||||
@override
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:pub_semver/pub_semver.dart';
|
||||
import 'package:shorebird_cli/src/extensions/version.dart';
|
||||
@@ -15,6 +13,7 @@ part 'apple_device.g.dart';
|
||||
|
||||
/// {@macro apple_device}
|
||||
class AppleDevice {
|
||||
/// {@macro apple_device}
|
||||
const AppleDevice({
|
||||
required this.deviceProperties,
|
||||
required this.hardwareProperties,
|
||||
@@ -62,8 +61,12 @@ class AppleDevice {
|
||||
String toString() => '$name ($osVersionString ${hardwareProperties.udid})';
|
||||
}
|
||||
|
||||
/// {@template hardware_properties}
|
||||
/// The hardware properties of a device.
|
||||
/// {@endtemplate}
|
||||
@JsonSerializable(createToJson: false, fieldRename: FieldRename.none)
|
||||
class HardwareProperties {
|
||||
/// {@macro hardware_properties}
|
||||
const HardwareProperties({required this.platform, required this.udid});
|
||||
|
||||
/// The device's platform (e.g., "iOS").
|
||||
@@ -72,12 +75,17 @@ class HardwareProperties {
|
||||
/// The unique identifier of this device
|
||||
final String udid;
|
||||
|
||||
/// Creates a [HardwareProperties] from [json].
|
||||
static HardwareProperties fromJson(Json json) =>
|
||||
_$HardwarePropertiesFromJson(json);
|
||||
}
|
||||
|
||||
/// {@template device_properties}
|
||||
/// The device properties for a given apple device.
|
||||
/// {@endtemplate}
|
||||
@JsonSerializable(createToJson: false, fieldRename: FieldRename.none)
|
||||
class DeviceProperties {
|
||||
/// {@macro device_properties}
|
||||
const DeviceProperties({required this.name, this.osVersionNumber});
|
||||
|
||||
/// Human-readable name of the device (e.g., "Joe's iPhone").
|
||||
@@ -86,12 +94,17 @@ class DeviceProperties {
|
||||
/// The device's OS version as a string (e.g., "14.4.1").
|
||||
final String? osVersionNumber;
|
||||
|
||||
/// Creates a [DeviceProperties] from [json].
|
||||
static DeviceProperties fromJson(Json json) =>
|
||||
_$DevicePropertiesFromJson(json);
|
||||
}
|
||||
|
||||
/// {@template connection_properties}
|
||||
/// The connection properties of a device.
|
||||
/// {@endtemplate}
|
||||
@JsonSerializable(createToJson: false, fieldRename: FieldRename.none)
|
||||
class ConnectionProperties {
|
||||
/// {@macro connection_properties}
|
||||
const ConnectionProperties({required this.tunnelState, this.transportType});
|
||||
|
||||
/// How the device is connected. Values seen in development include
|
||||
@@ -106,6 +119,7 @@ class ConnectionProperties {
|
||||
/// - "unavailable" when the device is not connected via USB or wifi.
|
||||
final String tunnelState;
|
||||
|
||||
/// Creates a [ConnectionProperties] from [json].
|
||||
static ConnectionProperties fromJson(Json json) =>
|
||||
_$ConnectionPropertiesFromJson(json);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
@@ -15,6 +14,7 @@ import 'package:shorebird_cli/src/logging/logging.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_process.dart';
|
||||
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
|
||||
|
||||
/// Typedef for a bundle identifier string.
|
||||
typedef BundleId = String;
|
||||
|
||||
/// {@template devicectl_exception}
|
||||
@@ -48,10 +48,11 @@ Devicectl get devicectl => read(devicectlRef);
|
||||
|
||||
/// A wrapper around the `devicectl` command.
|
||||
class Devicectl {
|
||||
/// The executable name (`xcrun`).
|
||||
static const executableName = 'xcrun';
|
||||
static const baseArgs = [
|
||||
'devicectl',
|
||||
];
|
||||
|
||||
/// The base arguments for the `devicectl` command.
|
||||
static const baseArgs = ['devicectl'];
|
||||
|
||||
/// Whether the `devicectl` command is available.
|
||||
Future<bool> _isAvailable() async {
|
||||
@@ -61,7 +62,7 @@ class Devicectl {
|
||||
'--version',
|
||||
]);
|
||||
return result.exitCode == ExitCode.success.code;
|
||||
} catch (_) {
|
||||
} on Exception {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -186,7 +187,7 @@ class Devicectl {
|
||||
deviceId: device.udid,
|
||||
runnerApp: runnerAppDirectory,
|
||||
);
|
||||
} catch (error) {
|
||||
} on Exception catch (error) {
|
||||
installProgress.fail('Failed to install app: $error');
|
||||
return ExitCode.software.code;
|
||||
}
|
||||
@@ -195,7 +196,7 @@ class Devicectl {
|
||||
final launchProgress = logger.progress('Launching app');
|
||||
try {
|
||||
await launchApp(deviceId: device.udid, bundleId: bundleId);
|
||||
} catch (error) {
|
||||
} on Exception catch (error) {
|
||||
launchProgress.fail('Failed to launch app: $error');
|
||||
return ExitCode.software.code;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
|
||||
@@ -20,13 +18,19 @@ class NSError extends Equatable {
|
||||
required this.userInfo,
|
||||
});
|
||||
|
||||
/// The error code.
|
||||
final int code;
|
||||
|
||||
/// The error domain.
|
||||
final String domain;
|
||||
|
||||
/// Additional information about the error.
|
||||
final UserInfo userInfo;
|
||||
|
||||
/// Creates an [NSError] from JSON.
|
||||
static NSError fromJson(Json json) => _$NSErrorFromJson(json);
|
||||
|
||||
/// Converts this [NSError] to [Json].
|
||||
Json toJson() => _$NSErrorToJson(this);
|
||||
|
||||
@override
|
||||
@@ -45,8 +49,12 @@ NSError(
|
||||
];
|
||||
}
|
||||
|
||||
/// {@template user_info}
|
||||
/// A pared-down representation of the userInfo property of an NSError.
|
||||
/// {@endtemplate}
|
||||
@JsonSerializable(fieldRename: FieldRename.none)
|
||||
class UserInfo extends Equatable {
|
||||
/// {@macro user_info}
|
||||
const UserInfo({
|
||||
this.description,
|
||||
this.localizedDescription,
|
||||
@@ -54,22 +62,29 @@ class UserInfo extends Equatable {
|
||||
this.underlyingError,
|
||||
});
|
||||
|
||||
/// A description of the error.
|
||||
@JsonKey(name: 'NSDescription')
|
||||
final StringContainer? description;
|
||||
|
||||
/// A localized description of the error.
|
||||
@JsonKey(name: 'NSLocalizedDescription')
|
||||
final StringContainer? localizedDescription;
|
||||
|
||||
/// A localized description of the failure reason.
|
||||
@JsonKey(name: 'NSLocalizedFailureReason')
|
||||
final StringContainer? localizedFailureReason;
|
||||
|
||||
/// The underlying error, if any.
|
||||
@JsonKey(name: 'NSUnderlyingError')
|
||||
final NSUnderlyingError? underlyingError;
|
||||
|
||||
/// An empty [UserInfo] instance.
|
||||
static const nullInfo = UserInfo();
|
||||
|
||||
/// Creates a [UserInfo] from [Json].
|
||||
static UserInfo fromJson(Json json) => _$UserInfoFromJson(json);
|
||||
|
||||
/// Converts this [UserInfo] to [Json].
|
||||
Json toJson() => _$UserInfoToJson(this);
|
||||
|
||||
@override
|
||||
@@ -90,14 +105,21 @@ UserInfo(
|
||||
];
|
||||
}
|
||||
|
||||
/// {@template string_container}
|
||||
/// A container for a string value.
|
||||
/// {@endtemplate}
|
||||
@JsonSerializable(fieldRename: FieldRename.none)
|
||||
class StringContainer extends Equatable {
|
||||
/// {@macro string_container}
|
||||
const StringContainer(this.string);
|
||||
|
||||
/// The string value.
|
||||
final String string;
|
||||
|
||||
/// Creates a [StringContainer] from [Json].
|
||||
static StringContainer fromJson(Json json) => _$StringContainerFromJson(json);
|
||||
|
||||
/// Converts this [StringContainer] to [Json].
|
||||
Json toJson() => _$StringContainerToJson(this);
|
||||
|
||||
@override
|
||||
@@ -107,15 +129,22 @@ class StringContainer extends Equatable {
|
||||
List<Object> get props => [string];
|
||||
}
|
||||
|
||||
/// {@template ns_underlying_error}
|
||||
/// A pared-down representation of the NSUnderlyingError class.
|
||||
/// {@endtemplate}
|
||||
@JsonSerializable(fieldRename: FieldRename.none)
|
||||
class NSUnderlyingError extends Equatable {
|
||||
/// {@macro ns_underlying_error}
|
||||
const NSUnderlyingError({required this.error});
|
||||
|
||||
/// The underlying error.
|
||||
final NSError? error;
|
||||
|
||||
/// Creates an [NSUnderlyingError] from [Json].
|
||||
static NSUnderlyingError fromJson(Json json) =>
|
||||
_$NSUnderlyingErrorFromJson(json);
|
||||
|
||||
/// Converts this [NSUnderlyingError] to [Json].
|
||||
Json toJson() => _$NSUnderlyingErrorToJson(this);
|
||||
|
||||
@override
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
@@ -12,7 +10,9 @@ import 'package:shorebird_cli/src/platform.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_documentation.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_process.dart';
|
||||
|
||||
/// Exception thrown when the Gradle version is incompatible.
|
||||
class IncompatibleGradleException implements Exception {
|
||||
/// The error pattern used to identify the exception.
|
||||
static const errorPattern = 'Unsupported class file major version';
|
||||
|
||||
@override
|
||||
@@ -50,15 +50,19 @@ To add android, run "flutter create . --platforms android"''';
|
||||
}
|
||||
}
|
||||
|
||||
/// {@template missing_gradle_wrapper_exception}
|
||||
/// Thrown when the gradle wrapper cannot be found.
|
||||
/// This has been resolved on the master channel but
|
||||
/// on the stable channel currently creating an app via
|
||||
/// `flutter create` does not generate a gradle wrapper which
|
||||
/// means we're not able to accurately detect flavors until
|
||||
/// the user has run `flutter build apk` at least once.
|
||||
/// {@endtemplate}
|
||||
class MissingGradleWrapperException implements Exception {
|
||||
/// {@macro missing_gradle_wrapper_exception}
|
||||
const MissingGradleWrapperException(this.executablePath);
|
||||
|
||||
/// The path to the gradle wrapper executable.
|
||||
final String executablePath;
|
||||
|
||||
@override
|
||||
@@ -77,6 +81,7 @@ Gradlew get gradlew => read(gradlewRef);
|
||||
|
||||
/// A wrapper around the gradle wrapper (gradlew).
|
||||
class Gradlew {
|
||||
/// The name of the executable.
|
||||
String get executable => platform.isWindows ? 'gradlew.bat' : 'gradlew';
|
||||
|
||||
Future<ShorebirdProcessResult> _run(
|
||||
|
||||
@@ -63,6 +63,7 @@ class IDeviceSysLog {
|
||||
/// idevicesyslog tails all logs produced by the device (similar to what is
|
||||
/// shown in Console.app). This is very noisy and we only want to show logs
|
||||
/// that are produced by the app. These log lines are of the form:
|
||||
// ignore: unintended_html_in_doc_comment
|
||||
/// Nov 10 14:46:57 Runner(Flutter)[1044] <Notice>: flutter: hello
|
||||
static RegExp appLogLineRegex = RegExp(r'\(Flutter\)\[\d+\] <Notice>: (.*)$');
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
@@ -25,13 +23,18 @@ enum _DebuggerState {
|
||||
attached,
|
||||
}
|
||||
|
||||
/// {@template ios_deploy}
|
||||
/// Wrapper around the `ios-deploy` command cached by the Flutter tool.
|
||||
/// https://github.com/ios-control/ios-deploy
|
||||
/// {@endtemplate}
|
||||
class IOSDeploy {
|
||||
IOSDeploy({ProcessSignal? sigint}) : _sigint = sigint ?? ProcessSignal.sigint;
|
||||
/// {@macro ios_deploy}
|
||||
const IOSDeploy({ProcessSignal? sigint})
|
||||
: _sigint = sigint ?? ProcessSignal.sigint;
|
||||
|
||||
final ProcessSignal _sigint;
|
||||
|
||||
/// The location of the ios-deploy executable.
|
||||
@visibleForTesting
|
||||
static File get iosDeployExecutable => File(
|
||||
p.join(
|
||||
@@ -76,19 +79,25 @@ class IOSDeploy {
|
||||
// Print backtrace for all threads while app is stopped.
|
||||
static const String _backTraceAll = 'thread backtrace all';
|
||||
|
||||
// No provision profile errors.
|
||||
/// No provision profile errors.
|
||||
/// One of the possible errors when there is no provisioning profile.
|
||||
static const noProvisioningProfileErrorOne = 'Error 0xe8008015';
|
||||
|
||||
/// Another possible error when there is no provisioning profile.
|
||||
static const noProvisioningProfileErrorTwo = 'Error 0xe8000067';
|
||||
|
||||
// Device locked errors.
|
||||
/// Device locked errors.
|
||||
/// Error when the device is locked.
|
||||
static const deviceLockedError = 'e80000e2';
|
||||
|
||||
/// Error message when the device is locked.
|
||||
static const deviceLockedErrorMessage =
|
||||
'the device was not, or could not be, unlocked';
|
||||
|
||||
// Unknown launch error.
|
||||
/// Unknown launch error.
|
||||
static const unknownAppLaunchError = 'Error 0xe8000022';
|
||||
|
||||
// Message when there is an unknown error.
|
||||
/// Message when there is an unknown error.
|
||||
static const unknownErrorFixInstructions = '''
|
||||
═══════════════════════════════════════════════════════════════════════════════════
|
||||
Error launching app. Try launching from within Xcode via:
|
||||
@@ -97,13 +106,13 @@ Error launching app. Try launching from within Xcode via:
|
||||
Your Xcode version may be too old for your iOS version.
|
||||
═══════════════════════════════════════════════════════════════════════════════════''';
|
||||
|
||||
// Message when the device is locked.
|
||||
/// Message when the device is locked.
|
||||
static const deviceLockedFixInstructions = '''
|
||||
═══════════════════════════════════════════════════════════════════════════════════
|
||||
Your device is locked. Unlock your device first before running.
|
||||
═══════════════════════════════════════════════════════════════════════════════════''';
|
||||
|
||||
// Message when there is no development team selected.
|
||||
/// Message when there is no development team selected.
|
||||
static const developmentTeamFixInstructions = '''
|
||||
1- Open the Flutter project's Xcode target with
|
||||
open ios/Runner.xcworkspace
|
||||
@@ -297,7 +306,7 @@ Or run on an iOS simulator without code signing
|
||||
unawaited(stdoutSubscription.cancel());
|
||||
unawaited(stderrSubscription.cancel());
|
||||
return status;
|
||||
} catch (exception, stackTrace) {
|
||||
} on Exception catch (exception, stackTrace) {
|
||||
logger.detail('[ios-deploy] failed: $exception\n$stackTrace');
|
||||
debuggerState = _DebuggerState.detached;
|
||||
logger.err('[ios-deploy] failed: $exception');
|
||||
@@ -328,8 +337,8 @@ Or run on an iOS simulator without code signing
|
||||
}
|
||||
}
|
||||
|
||||
// Handles interpreting stdout line and logs errors accordingly.
|
||||
// Always returns the original line.
|
||||
/// Handles interpreting stdout line and logs errors accordingly.
|
||||
/// Always returns the original line.
|
||||
@visibleForTesting
|
||||
String detectFailures(String line, Logger logger) {
|
||||
final isMissingProvisioningProfile =
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:args/args.dart';
|
||||
@@ -11,6 +10,7 @@ import 'package:shorebird_cli/src/logging/logging.dart';
|
||||
import 'package:shorebird_cli/src/release_type.dart';
|
||||
import 'package:shorebird_cli/src/third_party/flutter_tools/lib/flutter_tools.dart';
|
||||
|
||||
/// Extension on [ArgResults] to make it easier to work with options.
|
||||
extension OptionFinder on ArgResults {
|
||||
/// Detects flags even when passed to underlying commands via a `--`
|
||||
/// separator.
|
||||
@@ -50,6 +50,7 @@ extension OptionFinder on ArgResults {
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension on [ArgResults] to provide code signing related extensions.
|
||||
extension CodeSign on ArgResults {
|
||||
/// Asserts that either there is no public key argument
|
||||
/// or that the path received exists.
|
||||
@@ -101,6 +102,7 @@ extension FileArgs on ArgResults {
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension on [ArgResults] to provide forwarded arguments.
|
||||
extension ForwardedArgs on ArgResults {
|
||||
bool _isPositionalArgPlatform(String arg) =>
|
||||
ReleaseType.values.any((target) => target.cliName == arg);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
/// Extension on [String] to provide null or empty getter.
|
||||
extension NullOrEmpty on String? {
|
||||
/// Returns `true` if this string is null or empty.
|
||||
bool get isNullOrEmpty => this == null || this!.isEmpty;
|
||||
}
|
||||
|
||||
/// Extension on [String] to provide Ansi escape code helpers.
|
||||
extension AnsiEscapes on String {
|
||||
/// Removes ANSI escape codes (usually the result of a lightCyan.wrap or
|
||||
/// similar) from this string. Used to clean up
|
||||
@@ -29,6 +29,7 @@ extension AnsiEscapes on String {
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension on [String] to provide an `isUpperCase` getter.
|
||||
extension IsUpperCase on String {
|
||||
/// Returns `true` if this string is in uppercase.
|
||||
bool isUpperCase() => this == toUpperCase();
|
||||
|
||||
@@ -17,10 +17,8 @@ Version? tryParseVersion(String versionString, {bool strict = true}) {
|
||||
// and try again.
|
||||
try {
|
||||
return Version.parse('$versionString.0');
|
||||
} catch (_) {
|
||||
} on FormatException {
|
||||
return null;
|
||||
}
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,11 +13,11 @@ http.Client retryingHttpClient(http.Client client) => RetryClient(
|
||||
/// Returns `true` if the [exception] is a retryable exception.
|
||||
bool isRetryableException(Object exception, StackTrace _) {
|
||||
return switch (exception.runtimeType) {
|
||||
http.ClientException => true,
|
||||
HttpException => true,
|
||||
TlsException => true,
|
||||
SocketException => true,
|
||||
WebSocketException => true,
|
||||
const (http.ClientException) => true,
|
||||
const (HttpException) => true,
|
||||
const (TlsException) => true,
|
||||
const (SocketException) => true,
|
||||
const (WebSocketException) => true,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -47,11 +47,11 @@ class BuildEnvironmentMetadata extends Equatable {
|
||||
);
|
||||
// coverage:ignore-end
|
||||
|
||||
/// Converts a Map<String, dynamic> to a [BuildEnvironmentMetadata]
|
||||
/// Converts a `Map<String, dynamic>` to a [BuildEnvironmentMetadata]
|
||||
factory BuildEnvironmentMetadata.fromJson(Map<String, dynamic> json) =>
|
||||
_$BuildEnvironmentMetadataFromJson(json);
|
||||
|
||||
/// Converts a [BuildEnvironmentMetadata] to a Map<String, dynamic>
|
||||
/// Converts a [BuildEnvironmentMetadata] to a `Map<String, dynamic>`
|
||||
Map<String, dynamic> toJson() => _$BuildEnvironmentMetadataToJson(this);
|
||||
|
||||
/// Creates a copy of this [BuildEnvironmentMetadata] with the given fields
|
||||
|
||||
@@ -51,11 +51,11 @@ class CreatePatchMetadata extends Equatable {
|
||||
);
|
||||
// coverage:ignore-end
|
||||
|
||||
/// Converts a Map<String, dynamic> to a [CreatePatchMetadata]
|
||||
/// Converts a `Map<String, dynamic>` to a [CreatePatchMetadata]
|
||||
factory CreatePatchMetadata.fromJson(Map<String, dynamic> json) =>
|
||||
_$CreatePatchMetadataFromJson(json);
|
||||
|
||||
/// Converts a [CreatePatchMetadata] to a Map<String, dynamic>
|
||||
/// Converts a [CreatePatchMetadata] to a `Map<String, dynamic>`
|
||||
Map<String, dynamic> toJson() => _$CreatePatchMetadataToJson(this);
|
||||
|
||||
/// Returns a copy of this [CreatePatchMetadata] with the given fields
|
||||
|
||||
@@ -42,11 +42,11 @@ class UpdateReleaseMetadata extends Equatable {
|
||||
);
|
||||
// coverage:ignore-end
|
||||
|
||||
/// Converts a Map<String, dynamic> to a [UpdateReleaseMetadata].
|
||||
/// Converts a `Map<String, dynamic>` to a [UpdateReleaseMetadata].
|
||||
factory UpdateReleaseMetadata.fromJson(Map<String, dynamic> json) =>
|
||||
_$UpdateReleaseMetadataFromJson(json);
|
||||
|
||||
/// Converts a [UpdateReleaseMetadata] to a Map<String, dynamic>.
|
||||
/// Converts a [UpdateReleaseMetadata] to a `Map<String, dynamic>`.
|
||||
Map<String, dynamic> toJson() => _$UpdateReleaseMetadataToJson(this);
|
||||
|
||||
/// Returns a copy of this [UpdateReleaseMetadata] with the given fields
|
||||
|
||||
@@ -51,7 +51,7 @@ class NetworkChecker {
|
||||
try {
|
||||
await httpClient.get(url);
|
||||
progress.complete('$url ${lightGreen.wrap('OK')}');
|
||||
} catch (e) {
|
||||
} on Exception catch (e) {
|
||||
progress.fail('$url unreachable');
|
||||
logger.detail('Failed to reach $url: $e');
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
@@ -32,17 +30,24 @@ To add iOS, run "flutter create . --platforms ios"''';
|
||||
/// Acceptable values can be found by running `flutter build ipa -h`.
|
||||
/// {@endtemplate}
|
||||
enum ExportMethod {
|
||||
/// Upload to the App Store.
|
||||
appStore('app-store', 'Upload to the App Store'),
|
||||
|
||||
/// Ad-hoc distribution.
|
||||
adHoc(
|
||||
'ad-hoc',
|
||||
'''
|
||||
Test on designated devices that do not need to be registered with the Apple developer account.
|
||||
Requires a distribution certificate.''',
|
||||
),
|
||||
|
||||
/// Development distribution.
|
||||
development(
|
||||
'development',
|
||||
'''Test only on development devices registered with the Apple developer account.''',
|
||||
),
|
||||
|
||||
/// Enterprise distribution.
|
||||
enterprise(
|
||||
'enterprise',
|
||||
'Distribute an app registered with the Apple Developer Enterprise Program.',
|
||||
@@ -81,6 +86,7 @@ final iosRef = create(Ios.new);
|
||||
/// The [Ios] instance available in the current zone.
|
||||
Ios get ios => read(iosRef);
|
||||
|
||||
/// A class that provides information about the iOS platform.
|
||||
class Ios {
|
||||
/// Returns the set of flavors for the iOS project, if the project has an
|
||||
/// iOS platform configured.
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
import 'package:args/args.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
|
||||
@@ -54,7 +52,9 @@ enum ReleaseType {
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension on [ArgResults] to get the release types from the CLI arguments.
|
||||
extension ReleaseTypeArgs on ArgResults {
|
||||
/// The release types specified in the CLI arguments.
|
||||
Iterable<ReleaseType> get releaseTypes {
|
||||
List<String>? releaseTypeCliNames;
|
||||
if (wasParsed('platforms')) {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
@@ -33,14 +31,20 @@ class MultipleArtifactsFoundException implements Exception {
|
||||
}
|
||||
}
|
||||
|
||||
/// {@template artifact_not_found_exception}
|
||||
/// Thrown when no artifact is found in the build directory.
|
||||
/// {@endtemplate}
|
||||
class ArtifactNotFoundException implements Exception {
|
||||
ArtifactNotFoundException({
|
||||
/// {@macro artifact_not_found_exception}
|
||||
const ArtifactNotFoundException({
|
||||
required this.artifactName,
|
||||
required this.buildDir,
|
||||
});
|
||||
|
||||
/// The name of the artifact.
|
||||
final String artifactName;
|
||||
|
||||
/// The build directory where the artifact was expected to be.
|
||||
final String buildDir;
|
||||
|
||||
@override
|
||||
@@ -77,8 +81,10 @@ extension on String {
|
||||
String get artifactId => replaceAll(RegExp(r'\W'), '').toLowerCase();
|
||||
}
|
||||
|
||||
/// The reference to the [ShorebirdAndroidArtifacts] instance.
|
||||
final shorebirdAndroidArtifactsRef = create(ShorebirdAndroidArtifacts.new);
|
||||
|
||||
/// The [ShorebirdAndroidArtifacts] instance available in the current zone.
|
||||
ShorebirdAndroidArtifacts get shorebirdAndroidArtifacts =>
|
||||
read(shorebirdAndroidArtifactsRef);
|
||||
|
||||
@@ -168,6 +174,7 @@ class ShorebirdAndroidArtifacts {
|
||||
);
|
||||
}
|
||||
|
||||
/// The path to the aar library.
|
||||
static String get aarLibraryPath {
|
||||
final projectRoot = shorebirdEnv.getShorebirdProjectRoot()!;
|
||||
return p.joinAll([
|
||||
@@ -179,6 +186,7 @@ class ShorebirdAndroidArtifacts {
|
||||
]);
|
||||
}
|
||||
|
||||
/// The path to the aar directory.
|
||||
static String aarArtifactDirectory({
|
||||
required String packageName,
|
||||
required String buildNumber,
|
||||
@@ -190,6 +198,7 @@ class ShorebirdAndroidArtifacts {
|
||||
buildNumber,
|
||||
]);
|
||||
|
||||
/// The path to the aar artifact.
|
||||
static String aarArtifactPath({
|
||||
required String packageName,
|
||||
required String buildNumber,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// Allowing one member abstracts for consistency/namespace/ease of testing.
|
||||
// ignore_for_file: one_member_abstracts
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:args/args.dart';
|
||||
@@ -19,14 +17,19 @@ import 'package:shorebird_cli/src/shorebird_version.dart';
|
||||
import 'package:shorebird_cli/src/third_party/flutter_tools/lib/flutter_tools.dart';
|
||||
import 'package:shorebird_cli/src/version.dart';
|
||||
|
||||
/// The name of the executable.
|
||||
const executableName = 'shorebird';
|
||||
|
||||
/// The name of the package (e.g. name in the pubspec.yaml).
|
||||
const packageName = 'shorebird_cli';
|
||||
|
||||
/// The package description.
|
||||
const description = 'The shorebird command-line tool';
|
||||
|
||||
/// {@template shorebird_cli_command_runner}
|
||||
/// A [CommandRunner] for the CLI.
|
||||
///
|
||||
/// ```
|
||||
/// ```sh
|
||||
/// $ shorebird --version
|
||||
/// ```
|
||||
/// {@endtemplate}
|
||||
@@ -209,6 +212,10 @@ Engine • revision ${shorebirdEnv.shorebirdEngineRevision}''');
|
||||
// When on an usage exception we don't need to show the "if you aren't
|
||||
// sure" message, so we do an early return here.
|
||||
return ExitCode.usage.code;
|
||||
|
||||
// We explicitly want to catch all exceptions here to log them and show
|
||||
// the user a friendly message.
|
||||
// ignore: avoid_catches_without_on_clauses
|
||||
} catch (error, stackTrace) {
|
||||
logger
|
||||
..err('$error')
|
||||
@@ -248,7 +255,7 @@ ${currentRunLogFile.absolute.path}
|
||||
Future<String?> _tryGetFlutterVersion() async {
|
||||
try {
|
||||
return await shorebirdFlutter.getVersionString();
|
||||
} catch (error) {
|
||||
} on Exception catch (error) {
|
||||
logger.detail('Unable to determine Flutter version.\n$error');
|
||||
return null;
|
||||
}
|
||||
@@ -266,7 +273,7 @@ ${currentRunLogFile.absolute.path}
|
||||
..info('A new version of shorebird is available!')
|
||||
..info('Run ${lightCyan.wrap('shorebird upgrade')} to upgrade.');
|
||||
}
|
||||
} catch (error) {
|
||||
} on Exception catch (error) {
|
||||
logger.detail('Unable to check for updates.\n$error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:args/args.dart';
|
||||
@@ -15,17 +13,22 @@ typedef HashFunction = String Function(List<int> bytes);
|
||||
/// Signature for a function which takes a path to a zip file.
|
||||
typedef UnzipFn = Future<void> Function(String zipFilePath, String outputDir);
|
||||
|
||||
/// Signature for a function which builds a [CodePushClient].
|
||||
typedef CodePushClientBuilder = CodePushClient Function({
|
||||
required http.Client httpClient,
|
||||
Uri? hostedUri,
|
||||
});
|
||||
|
||||
/// Signature for a function which starts a process (e.g. [Process.start]).
|
||||
typedef StartProcess = Future<Process> Function(
|
||||
String executable,
|
||||
List<String> arguments, {
|
||||
bool runInShell,
|
||||
});
|
||||
|
||||
/// {@template shorebird_command}
|
||||
/// A command in the Shorebird CLI.
|
||||
/// {@endtemplate}
|
||||
abstract class ShorebirdCommand extends Command<int> {
|
||||
// We don't currently have a test involving both a CommandRunner
|
||||
// and a Command, so we can't test this getter.
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
import 'dart:io' hide Platform;
|
||||
|
||||
import 'package:checked_yaml/checked_yaml.dart';
|
||||
@@ -26,6 +24,7 @@ class ShorebirdEnv {
|
||||
const ShorebirdEnv({String? flutterRevisionOverride})
|
||||
: _flutterRevisionOverride = flutterRevisionOverride;
|
||||
|
||||
/// Copy the [ShorebirdEnv] and optionally override the flutter revision.
|
||||
ShorebirdEnv copyWith({String? flutterRevisionOverride}) => ShorebirdEnv(
|
||||
flutterRevisionOverride:
|
||||
flutterRevisionOverride ?? _flutterRevisionOverride,
|
||||
@@ -50,6 +49,7 @@ class ShorebirdEnv {
|
||||
return File(platform.script.toFilePath()).parent.parent.parent;
|
||||
}
|
||||
|
||||
/// The Shorebird engine revision.
|
||||
String get shorebirdEngineRevision {
|
||||
return File(
|
||||
p.join(
|
||||
@@ -61,6 +61,7 @@ class ShorebirdEnv {
|
||||
).readAsStringSync().trim();
|
||||
}
|
||||
|
||||
/// Set the Shorebird Flutter revision.
|
||||
set flutterRevision(String revision) {
|
||||
if (revision == flutterRevision) return;
|
||||
File(
|
||||
@@ -72,6 +73,7 @@ class ShorebirdEnv {
|
||||
if (snapshot.existsSync()) snapshot.deleteSync();
|
||||
}
|
||||
|
||||
/// Get the Shorebird Flutter revision.
|
||||
String get flutterRevision {
|
||||
return _flutterRevisionOverride ??
|
||||
File(
|
||||
@@ -176,10 +178,10 @@ class ShorebirdEnv {
|
||||
Pubspec? getPubspecYaml() {
|
||||
final root = getFlutterProjectRoot();
|
||||
if (root == null) return null;
|
||||
final yaml = getPubspecYamlFile(cwd: root).readAsStringSync();
|
||||
try {
|
||||
final yaml = getPubspecYamlFile(cwd: root).readAsStringSync();
|
||||
return Pubspec.parse(yaml, lenient: true);
|
||||
} catch (_) {
|
||||
} on Exception {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -217,7 +219,7 @@ class ShorebirdEnv {
|
||||
final baseUrl = platform.environment['SHOREBIRD_HOSTED_URL'] ??
|
||||
getShorebirdYaml()?.baseUrl;
|
||||
return baseUrl == null ? null : Uri.tryParse(baseUrl);
|
||||
} catch (_) {
|
||||
} on Exception {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
@@ -26,7 +25,10 @@ class ShorebirdFlutter {
|
||||
/// {@macro shorebird_flutter}
|
||||
const ShorebirdFlutter();
|
||||
|
||||
/// The executable name.
|
||||
static const executable = 'flutter';
|
||||
|
||||
/// The Shorebird Flutter fork git URL.
|
||||
static const String flutterGitUrl =
|
||||
'https://github.com/shorebirdtech/flutter.git';
|
||||
|
||||
@@ -41,6 +43,7 @@ class ShorebirdFlutter {
|
||||
return p.join(shorebirdEnv.flutterDirectory.parent.path, revision);
|
||||
}
|
||||
|
||||
/// Install the provided Flutter [revision].
|
||||
Future<void> installRevision({required String revision}) async {
|
||||
final targetDirectory = Directory(_workingDirectory(revision: revision));
|
||||
if (targetDirectory.existsSync()) return;
|
||||
@@ -85,7 +88,7 @@ class ShorebirdFlutter {
|
||||
runInShell: true,
|
||||
);
|
||||
precacheProgress.complete();
|
||||
} catch (_) {
|
||||
} on Exception {
|
||||
precacheProgress.fail('Failed to precache Flutter $version');
|
||||
logger.info(
|
||||
'''This is not a critical error, but your next build make take longer than usual.''',
|
||||
@@ -149,7 +152,7 @@ class ShorebirdFlutter {
|
||||
|
||||
try {
|
||||
version = await getVersionString();
|
||||
} catch (_) {
|
||||
} on Exception {
|
||||
version = 'unknown';
|
||||
}
|
||||
|
||||
@@ -223,7 +226,7 @@ class ShorebirdFlutter {
|
||||
if (version != null) {
|
||||
return versionOrHash;
|
||||
}
|
||||
} catch (_) {
|
||||
} on Exception {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -245,7 +248,7 @@ class ShorebirdFlutter {
|
||||
final versionString =
|
||||
await getVersionForRevision(flutterRevision: versionOrHash);
|
||||
return versionString != null ? tryParseVersion(versionString) : null;
|
||||
} catch (_) {
|
||||
} on Exception {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -260,6 +263,7 @@ class ShorebirdFlutter {
|
||||
return LineSplitter.split(result).toList().firstOrNull;
|
||||
}
|
||||
|
||||
/// Get the list of Flutter versions for the given [revision].
|
||||
Future<List<String>> getVersions({String? revision}) async {
|
||||
final result = await git.forEachRef(
|
||||
format: '%(refname:short)',
|
||||
@@ -271,6 +275,7 @@ class ShorebirdFlutter {
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// Use the provided [version] of Flutter.
|
||||
Future<void> useVersion({required String version}) async {
|
||||
final revision = await git.revParse(
|
||||
revision: 'origin/flutter_release/$version',
|
||||
@@ -280,6 +285,7 @@ class ShorebirdFlutter {
|
||||
await useRevision(revision: revision);
|
||||
}
|
||||
|
||||
/// Use the provided [revision] of Flutter.
|
||||
Future<void> useRevision({required String revision}) async {
|
||||
await installRevision(revision: revision);
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:scoped_deps/scoped_deps.dart';
|
||||
@@ -9,25 +7,31 @@ import 'package:shorebird_cli/src/platform.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_env.dart';
|
||||
import 'package:shorebird_cli/src/validators/validators.dart';
|
||||
|
||||
/// An exception thrown when a precondition for running a command is not met.
|
||||
abstract interface class PreconditionFailedException implements Exception {
|
||||
/// The exit code to use when the precondition fails.
|
||||
ExitCode get exitCode;
|
||||
}
|
||||
|
||||
/// An exception thrown when Shorebird has not been initialized.
|
||||
class ShorebirdNotInitializedException implements PreconditionFailedException {
|
||||
@override
|
||||
ExitCode get exitCode => ExitCode.config;
|
||||
}
|
||||
|
||||
/// An exception thrown when the user is not authorized to run a command.
|
||||
class UserNotAuthorizedException implements PreconditionFailedException {
|
||||
@override
|
||||
ExitCode get exitCode => ExitCode.noUser;
|
||||
}
|
||||
|
||||
/// An exception thrown when validation fails.
|
||||
class ValidationFailedException implements PreconditionFailedException {
|
||||
@override
|
||||
ExitCode get exitCode => ExitCode.config;
|
||||
}
|
||||
|
||||
/// An exception thrown when a command is run in an unsupported context.
|
||||
class UnsupportedContextException implements PreconditionFailedException {
|
||||
// coverage:ignore-start
|
||||
@override
|
||||
@@ -35,6 +39,7 @@ class UnsupportedContextException implements PreconditionFailedException {
|
||||
// coverage:ignore-end
|
||||
}
|
||||
|
||||
/// An exception thrown when the operating system is not supported.
|
||||
class UnsupportedOperatingSystemException
|
||||
implements PreconditionFailedException {
|
||||
@override
|
||||
|
||||
@@ -54,7 +54,7 @@ class ShorebirdFlutterValidator extends Validator {
|
||||
String? shorebirdFlutterVersionString;
|
||||
try {
|
||||
shorebirdFlutterVersionString = await _getFlutterVersion();
|
||||
} catch (error) {
|
||||
} on Exception catch (error) {
|
||||
issues.add(
|
||||
ValidationIssue.error(
|
||||
message: 'Failed to determine Shorebird Flutter version. $error',
|
||||
@@ -69,7 +69,7 @@ class ShorebirdFlutterValidator extends Validator {
|
||||
);
|
||||
} on CommandNotFoundException catch (_) {
|
||||
// If there is no system Flutter, we don't throw a validation exception.
|
||||
} catch (error) {
|
||||
} on Exception catch (error) {
|
||||
issues.add(
|
||||
ValidationIssue.error(
|
||||
message: 'Failed to determine path Flutter version. $error',
|
||||
|
||||
@@ -799,10 +799,10 @@ packages:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: very_good_analysis
|
||||
sha256: "1fb637c0022034b1f19ea2acb42a3603cbd8314a470646a59a2fb01f5f3a8629"
|
||||
sha256: "62d2b86d183fb81b2edc22913d9f155d26eb5cf3855173adb1f59fac85035c63"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.0"
|
||||
version: "7.0.0"
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -55,7 +55,7 @@ dev_dependencies:
|
||||
json_serializable: ^6.9.0
|
||||
mocktail: ^1.0.3
|
||||
test: ^1.25.9
|
||||
very_good_analysis: ^6.0.0
|
||||
very_good_analysis: ^7.0.0
|
||||
|
||||
executables:
|
||||
shorebird:
|
||||
|
||||
@@ -331,7 +331,7 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod
|
||||
flavor: any(named: 'flavor'),
|
||||
),
|
||||
).thenThrow(
|
||||
ArtifactNotFoundException(
|
||||
const ArtifactNotFoundException(
|
||||
artifactName: 'app-release.aab',
|
||||
buildDir: 'buildDir',
|
||||
),
|
||||
@@ -573,7 +573,7 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod
|
||||
flavor: any(named: 'flavor'),
|
||||
),
|
||||
).thenThrow(
|
||||
ArtifactNotFoundException(
|
||||
const ArtifactNotFoundException(
|
||||
artifactName: 'app-release.aab',
|
||||
buildDir: 'buildDir',
|
||||
),
|
||||
|
||||
@@ -209,6 +209,21 @@ void main() {
|
||||
});
|
||||
|
||||
group('AuthenticatedClient', () {
|
||||
group('isAuthenticated', () {
|
||||
group('when credentials are malformed', () {
|
||||
setUp(() {
|
||||
File(
|
||||
p.join(credentialsDir, 'credentials.json'),
|
||||
).writeAsStringSync('invalid credentials');
|
||||
auth = buildAuth();
|
||||
});
|
||||
|
||||
test('returns false', () {
|
||||
expect(auth.isAuthenticated, isFalse);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('token', () {
|
||||
test('does not require an onRefreshCredentials callback', () {
|
||||
expect(
|
||||
|
||||
@@ -158,6 +158,7 @@ void main() {
|
||||
late CodePushClient codePushClient;
|
||||
late Ditto ditto;
|
||||
late ShorebirdLogger logger;
|
||||
late ShorebirdEnv shorebirdEnv;
|
||||
late ShorebirdFlutter shorebirdFlutter;
|
||||
late Progress progress;
|
||||
late CodePushClientWrapper codePushClientWrapper;
|
||||
@@ -171,6 +172,7 @@ void main() {
|
||||
dittoRef.overrideWith(() => ditto),
|
||||
loggerRef.overrideWith(() => logger),
|
||||
platformRef.overrideWith(() => platform),
|
||||
shorebirdEnvRef.overrideWith(() => shorebirdEnv),
|
||||
shorebirdFlutterRef.overrideWith(() => shorebirdFlutter),
|
||||
},
|
||||
);
|
||||
@@ -193,6 +195,7 @@ void main() {
|
||||
() => CodePushClientWrapper(codePushClient: codePushClient),
|
||||
);
|
||||
|
||||
shorebirdEnv = MockShorebirdEnv();
|
||||
shorebirdFlutter = MockShorebirdFlutter();
|
||||
|
||||
when(
|
||||
@@ -233,9 +236,7 @@ void main() {
|
||||
displayName: appName,
|
||||
organizationId: any(named: 'organizationId'),
|
||||
),
|
||||
).thenAnswer(
|
||||
(_) async => app,
|
||||
);
|
||||
).thenAnswer((_) async => app);
|
||||
|
||||
await runWithOverrides(
|
||||
() => codePushClientWrapper.createApp(
|
||||
|
||||
@@ -99,6 +99,7 @@ void main() {
|
||||
when(
|
||||
() => doctor.runValidators(any(), applyFixes: any(named: 'applyFixes')),
|
||||
).thenAnswer((_) async => {});
|
||||
when(shorebirdFlutter.getVersionString).thenAnswer((_) async => null);
|
||||
|
||||
command = runWithOverrides(DoctorCommand.new)
|
||||
..testArgResults = argResults;
|
||||
@@ -106,7 +107,11 @@ void main() {
|
||||
|
||||
test(
|
||||
'prints shorebird version, flutter revision, '
|
||||
'and engine revision', () async {
|
||||
'and engine revision '
|
||||
'when unable to determine Flutter version', () async {
|
||||
when(
|
||||
() => shorebirdFlutter.getVersionString(),
|
||||
).thenThrow(Exception('oops'));
|
||||
await runWithOverrides(command.run);
|
||||
|
||||
verify(
|
||||
@@ -117,6 +122,11 @@ Flutter • revision ${shorebirdEnv.flutterRevision}
|
||||
Engine • revision $shorebirdEngineRevision
|
||||
'''),
|
||||
).called(1);
|
||||
verify(
|
||||
() => logger.detail(
|
||||
'Unable to determine Flutter version.\nException: oops',
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
test(
|
||||
|
||||
+2
-2
@@ -46,7 +46,7 @@ void main() {
|
||||
when(
|
||||
() => shorebirdFlutter.getVersionString(),
|
||||
).thenAnswer((_) async => '1.0.0');
|
||||
when(() => shorebirdFlutter.getVersions()).thenThrow('error');
|
||||
when(() => shorebirdFlutter.getVersions()).thenThrow(Exception('error'));
|
||||
await expectLater(
|
||||
runWithOverrides(command.run),
|
||||
completion(equals(ExitCode.software.code)),
|
||||
@@ -56,7 +56,7 @@ void main() {
|
||||
() => shorebirdFlutter.getVersionString(),
|
||||
() => shorebirdFlutter.getVersions(),
|
||||
() => progress.fail('Failed to fetch Flutter versions.'),
|
||||
() => logger.err('error'),
|
||||
() => logger.err('Exception: error'),
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
@@ -44,6 +44,15 @@ void main() {
|
||||
when(() => results.wasParsed('provider')).thenReturn(false);
|
||||
when(() => results['provider']).thenReturn(null);
|
||||
when(() => auth.client).thenReturn(httpClient);
|
||||
when(
|
||||
() => auth.loginCI(any(), prompt: any(named: 'prompt')),
|
||||
).thenAnswer(
|
||||
(_) async => const CiToken(
|
||||
// "shorebird-token" in base64
|
||||
refreshToken: 'c2hvcmViaXJkLXRva2Vu', // cspell:disable-line
|
||||
authProvider: AuthProvider.google,
|
||||
),
|
||||
);
|
||||
when(
|
||||
() => logger.chooseOne<AuthProvider>(
|
||||
any(),
|
||||
@@ -52,8 +61,9 @@ void main() {
|
||||
),
|
||||
).thenReturn(AuthProvider.google);
|
||||
|
||||
command =
|
||||
runWithOverrides(() => LoginCiCommand()..testArgResults = results);
|
||||
command = runWithOverrides(
|
||||
() => LoginCiCommand()..testArgResults = results,
|
||||
);
|
||||
});
|
||||
|
||||
group('provider', () {
|
||||
|
||||
@@ -53,6 +53,10 @@ void main() {
|
||||
when(() => auth.credentialsFilePath).thenReturn(
|
||||
p.join(applicationConfigHome.path, 'credentials.json'),
|
||||
);
|
||||
when(
|
||||
() => auth.login(any(), prompt: any(named: 'prompt')),
|
||||
).thenAnswer((_) async {});
|
||||
|
||||
when(
|
||||
() => logger.chooseOne<AuthProvider>(
|
||||
any(),
|
||||
@@ -61,8 +65,9 @@ void main() {
|
||||
),
|
||||
).thenReturn(AuthProvider.google);
|
||||
|
||||
command =
|
||||
runWithOverrides(() => LoginCommand()..testArgResults = results);
|
||||
command = runWithOverrides(
|
||||
() => LoginCommand()..testArgResults = results,
|
||||
);
|
||||
});
|
||||
|
||||
group('provider', () {
|
||||
|
||||
@@ -440,7 +440,9 @@ void main() {
|
||||
test('forwards --split-debug-info to builder', () async {
|
||||
try {
|
||||
await runWithOverrides(patcher.buildPatchArtifact);
|
||||
} catch (_) {}
|
||||
} on Exception {
|
||||
// ignore
|
||||
}
|
||||
verify(
|
||||
() => artifactBuilder.buildElfAotSnapshot(
|
||||
appDillPath: any(named: 'appDillPath'),
|
||||
@@ -643,6 +645,9 @@ void main() {
|
||||
additionalArgs: any(named: 'additionalArgs'),
|
||||
),
|
||||
).thenAnswer((_) async => linkPercentage);
|
||||
when(
|
||||
aotTools.isGeneratePatchDiffBaseSupported,
|
||||
).thenAnswer((_) async => false);
|
||||
when(
|
||||
() => shorebirdEnv.flutterRevision,
|
||||
).thenReturn(postLinkerFlutterRevision);
|
||||
@@ -735,7 +740,9 @@ void main() {
|
||||
releaseArtifact: releaseArtifactFile,
|
||||
),
|
||||
);
|
||||
} catch (_) {}
|
||||
} on Exception {
|
||||
// ignore
|
||||
}
|
||||
verify(
|
||||
() => aotTools.link(
|
||||
base: any(named: 'base'),
|
||||
|
||||
@@ -691,7 +691,9 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
|
||||
test('forwards --split-debug-info to builder', () async {
|
||||
try {
|
||||
await runWithOverrides(patcher.buildPatchArtifact);
|
||||
} catch (_) {}
|
||||
} on Exception {
|
||||
// ignore
|
||||
}
|
||||
verify(
|
||||
() => artifactBuilder.buildElfAotSnapshot(
|
||||
appDillPath: any(named: 'appDillPath'),
|
||||
@@ -1011,6 +1013,9 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
|
||||
additionalArgs: any(named: 'additionalArgs'),
|
||||
),
|
||||
).thenAnswer((_) async => linkPercentage);
|
||||
when(
|
||||
aotTools.isGeneratePatchDiffBaseSupported,
|
||||
).thenAnswer((_) async => false);
|
||||
when(
|
||||
() => artifactManager.getIosAppDirectory(
|
||||
xcarchiveDirectory: any(named: 'xcarchiveDirectory'),
|
||||
@@ -1167,7 +1172,9 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
|
||||
releaseArtifact: releaseArtifactFile,
|
||||
),
|
||||
);
|
||||
} catch (_) {}
|
||||
} on Exception {
|
||||
// ignore
|
||||
}
|
||||
verify(
|
||||
() => aotTools.link(
|
||||
base: any(named: 'base'),
|
||||
@@ -1519,8 +1526,6 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
|
||||
kernel: any(named: 'kernel'),
|
||||
outputPath: any(named: 'outputPath'),
|
||||
workingDirectory: any(named: 'workingDirectory'),
|
||||
// ignore: avoid_redundant_argument_values
|
||||
dumpDebugInfoPath: null,
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
@@ -1691,7 +1696,9 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
|
||||
'Info.plist',
|
||||
),
|
||||
).deleteSync(recursive: true);
|
||||
} catch (_) {}
|
||||
} on Exception {
|
||||
// ignore
|
||||
}
|
||||
});
|
||||
|
||||
test('exit with code 70', () async {
|
||||
|
||||
@@ -718,7 +718,9 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
|
||||
test('forwards --split-debug-info to builder', () async {
|
||||
try {
|
||||
await runWithOverrides(patcher.buildPatchArtifact);
|
||||
} catch (_) {}
|
||||
} on Exception {
|
||||
// ignore
|
||||
}
|
||||
verify(
|
||||
() => artifactBuilder.buildElfAotSnapshot(
|
||||
appDillPath: any(named: 'appDillPath'),
|
||||
@@ -1582,8 +1584,6 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
|
||||
kernel: any(named: 'kernel'),
|
||||
outputPath: any(named: 'outputPath'),
|
||||
workingDirectory: any(named: 'workingDirectory'),
|
||||
// ignore: avoid_redundant_argument_values
|
||||
dumpDebugInfoPath: null,
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
@@ -1621,7 +1621,9 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
|
||||
'Info.plist',
|
||||
),
|
||||
).deleteSync(recursive: true);
|
||||
} catch (_) {}
|
||||
} on Exception {
|
||||
// ignore
|
||||
}
|
||||
});
|
||||
|
||||
test('exit with code 70', () async {
|
||||
|
||||
@@ -47,8 +47,8 @@ void main() {
|
||||
() => shorebirdVersion.attemptReset(revision: any(named: 'revision')),
|
||||
).thenAnswer((_) async => {});
|
||||
|
||||
when(() => progress.complete(any())).thenAnswer((_) {
|
||||
final message = _.positionalArguments.elementAt(0) as String?;
|
||||
when(() => progress.complete(any())).thenAnswer((invocation) {
|
||||
final message = invocation.positionalArguments.elementAt(0) as String?;
|
||||
if (message != null) progressLogs.add(message);
|
||||
});
|
||||
when(() => logger.progress(any())).thenReturn(progress);
|
||||
|
||||
@@ -83,6 +83,15 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
test('returns null if devicectl availability check throws', () async {
|
||||
exitCode = ExitCode.software;
|
||||
when(() => process.run(any(), any())).thenThrow(Exception('oops'));
|
||||
await expectLater(
|
||||
await runWithOverrides(() => devicectl.deviceForLaunch()),
|
||||
isNull,
|
||||
);
|
||||
});
|
||||
|
||||
test('returns null if no CoreDevice with the given deviceID can be found',
|
||||
() async {
|
||||
exitCode = ExitCode.success;
|
||||
|
||||
@@ -47,7 +47,7 @@ void main() {
|
||||
progress = MockProgress();
|
||||
ioSink = MockIOSink();
|
||||
shorebirdEnv = MockShorebirdEnv();
|
||||
iosDeploy = IOSDeploy();
|
||||
iosDeploy = const IOSDeploy();
|
||||
|
||||
final tempDir = Directory.systemTemp.createTempSync();
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('returns null if string is of the format major.minor', () {
|
||||
expect(tryParseVersion('1.2'), isNull);
|
||||
expect(tryParseVersion('-1.2.-3'), isNull);
|
||||
});
|
||||
|
||||
test('returns null if string is in an invalid format', () {
|
||||
@@ -27,6 +27,13 @@ void main() {
|
||||
expect(tryParseVersion('1.2', strict: false), Version(1, 2, 0));
|
||||
});
|
||||
|
||||
test('returns null if string contains bigint', () {
|
||||
expect(
|
||||
tryParseVersion('999999999999999999999999999.0', strict: false),
|
||||
isNull,
|
||||
);
|
||||
});
|
||||
|
||||
test('returns null if string is in an invalid format', () {
|
||||
expect(tryParseVersion('asdf', strict: false), isNull);
|
||||
});
|
||||
|
||||
@@ -41,6 +41,7 @@ void main() {
|
||||
test('returns correct number of decimal places', () {
|
||||
expect(formatBytes(1524, decimals: 0), equals('1 KB'));
|
||||
expect(formatBytes(1524, decimals: 1), equals('1.5 KB'));
|
||||
// Being explicit for test readability.
|
||||
// ignore: avoid_redundant_argument_values
|
||||
expect(formatBytes(1524, decimals: 2), equals('1.49 KB'));
|
||||
expect(formatBytes(1524, decimals: 3), equals('1.488 KB'));
|
||||
|
||||
@@ -73,7 +73,7 @@ void main() {
|
||||
|
||||
group('when endpoints are not reachable', () {
|
||||
setUp(() {
|
||||
when(() => httpClient.send(any())).thenThrow(Exception('oops'));
|
||||
when(() => httpClient.get(any())).thenThrow(Exception('oops'));
|
||||
});
|
||||
|
||||
test('logs reachability for each checked url', () async {
|
||||
|
||||
@@ -355,13 +355,15 @@ Engine • revision $shorebirdEngineRevision''',
|
||||
|
||||
test('gracefully handles case when latest version cannot be determined',
|
||||
() async {
|
||||
when(shorebirdVersion.isLatest).thenThrow('error');
|
||||
when(shorebirdVersion.isLatest).thenThrow(Exception('error'));
|
||||
final result = await runWithOverrides(
|
||||
() => commandRunner.run(['--version']),
|
||||
);
|
||||
expect(result, equals(ExitCode.success.code));
|
||||
verify(
|
||||
() => logger.detail('Unable to check for updates.\nerror'),
|
||||
() => logger.detail(
|
||||
'Unable to check for updates.\nException: error',
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
@@ -403,13 +405,15 @@ Engine • revision $shorebirdEngineRevision''',
|
||||
test(
|
||||
'gracefully handles case when flutter version cannot be determined',
|
||||
() async {
|
||||
when(shorebirdFlutter.getVersionString).thenThrow('error');
|
||||
when(shorebirdFlutter.getVersionString).thenThrow(Exception('error'));
|
||||
final result = await runWithOverrides(
|
||||
() => commandRunner.run(['--version']),
|
||||
);
|
||||
expect(result, equals(ExitCode.success.code));
|
||||
verify(
|
||||
() => logger.detail('Unable to determine Flutter version.\nerror'),
|
||||
() => logger.detail(
|
||||
'Unable to determine Flutter version.\nException: error',
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -276,6 +276,28 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
test(
|
||||
'returns null when error occurs reading pubspec.yaml',
|
||||
() {
|
||||
final tempDir = Directory.systemTemp.createTempSync();
|
||||
final file = File(
|
||||
p.join(tempDir.path, 'pubspec.yaml'),
|
||||
)..writeAsStringSync('name: test');
|
||||
// Make the file unreadable.
|
||||
Process.runSync('chmod', ['000', file.path]);
|
||||
expect(
|
||||
IOOverrides.runZoned(
|
||||
() => runWithOverrides(() => shorebirdEnv.getPubspecYaml()),
|
||||
getCurrentDirectory: () => tempDir,
|
||||
),
|
||||
isNull,
|
||||
);
|
||||
},
|
||||
onPlatform: {
|
||||
'windows': const Skip('chmod is not available on Windows'),
|
||||
},
|
||||
);
|
||||
|
||||
test('returns value when pubspec.yaml exists', () {
|
||||
final tempDir = Directory.systemTemp.createTempSync();
|
||||
File(
|
||||
@@ -639,6 +661,30 @@ base_url: https://example.com''');
|
||||
test('returns null when there is no env override or shorebird.yaml', () {
|
||||
expect(runWithOverrides(() => shorebirdEnv.hostedUri), isNull);
|
||||
});
|
||||
|
||||
test(
|
||||
'returns null when unable to read shorebird.yaml',
|
||||
() {
|
||||
final directory = Directory.systemTemp.createTempSync();
|
||||
final file = File(p.join(directory.path, 'shorebird.yaml'))
|
||||
..writeAsStringSync('''
|
||||
app_id: test-id
|
||||
base_url: https://example.com''');
|
||||
// Make the file unreadable.
|
||||
Process.runSync('chmod', ['000', file.path]);
|
||||
|
||||
expect(
|
||||
IOOverrides.runZoned(
|
||||
() => runWithOverrides(() => shorebirdEnv.hostedUri),
|
||||
getCurrentDirectory: () => directory,
|
||||
),
|
||||
isNull,
|
||||
);
|
||||
},
|
||||
onPlatform: {
|
||||
'windows': const Skip('chmod is not available on Windows'),
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
group('canAcceptUserInput', () {
|
||||
|
||||
@@ -294,6 +294,26 @@ Tools • Dart 3.0.6 • DevTools 2.23.1''');
|
||||
expect(revision, isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('when exception occurs doing revision lookup', () {
|
||||
setUp(() {
|
||||
when(
|
||||
() => git.forEachRef(
|
||||
directory: any(named: 'directory'),
|
||||
contains: any(named: 'contains'),
|
||||
format: any(named: 'format'),
|
||||
pattern: any(named: 'pattern'),
|
||||
),
|
||||
).thenThrow(Exception('oops'));
|
||||
});
|
||||
|
||||
test('returns null', () async {
|
||||
final revision = await runWithOverrides(
|
||||
() => shorebirdFlutter.resolveFlutterRevision('not-a-version'),
|
||||
);
|
||||
expect(revision, isNull);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('resolveFlutterVersion', () {
|
||||
@@ -327,6 +347,26 @@ Tools • Dart 3.0.6 • DevTools 2.23.1''');
|
||||
});
|
||||
});
|
||||
|
||||
group('when commit lookup fails', () {
|
||||
setUp(() {
|
||||
when(
|
||||
() => git.forEachRef(
|
||||
directory: any(named: 'directory'),
|
||||
contains: any(named: 'contains'),
|
||||
format: any(named: 'format'),
|
||||
pattern: any(named: 'pattern'),
|
||||
),
|
||||
).thenThrow(Exception('oops'));
|
||||
});
|
||||
|
||||
test('returns null', () async {
|
||||
final revision = await runWithOverrides(
|
||||
() => shorebirdFlutter.resolveFlutterVersion('not-a-version'),
|
||||
);
|
||||
expect(revision, isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('when input is a recognized commit hash', () {
|
||||
setUp(() {
|
||||
when(
|
||||
|
||||
Reference in New Issue
Block a user