chore: enable public_api_docs enforcement. (#2229)

Co-authored-by: Felix Angelov <felix@shorebird.dev>
This commit is contained in:
Eric Seidel
2024-06-12 12:05:10 -07:00
committed by GitHub
parent 76ff1921b7
commit 8b21c261cb
70 changed files with 221 additions and 33 deletions
+6 -2
View File
@@ -1,5 +1,9 @@
# Release Notes
<!--
cspell:words pubspec erickzanardo xcframeworks Cupertino codesign codecov rkishan appbundle proto tlsv
-->
This section contains past updates we've sent to customers.
## 1.1.11 (June 11, 2024)
@@ -58,7 +62,7 @@ https://docs.shorebird.dev/guides/patch-signing/
## 1.1.5 (May 15, 2024)
- 🐦 Upgrade to Flutter 3.22.0 and Dart 3.4.0
- 💾 Precache Flutter assets when switching revisions
- 💾 Pre-cache Flutter assets when switching revisions
- 🪵 Improvements to exception logs
## 1.1.4 (May 14, 2024)
@@ -754,7 +758,7 @@ We've just released Shorebird v0.14.9 🎉
- 🍎 `shorebird release ios-alpha` supports `--no-codesign`
- 🍧 automatically add new flavors if detected by `shorebird init`
- 👀 only list previewable releases when running `shorebird preview`
- 👀 only list preview-able releases when running `shorebird preview`
📚 Release notes can be found at https://github.com/shorebirdtech/shorebird/releases/tag/v0.14.9
+1 -3
View File
@@ -2,6 +2,4 @@ include: package:very_good_analysis/analysis_options.5.1.0.yaml
analyzer:
exclude:
- lib/**.g.dart
linter:
rules:
public_member_api_docs: false
- lib/src/version.dart
@@ -1,3 +1,5 @@
// cspell:words aapt USERPROFILE cmdline
import 'dart:io';
import 'package:collection/collection.dart';
@@ -7,7 +9,10 @@ import 'package:shorebird_cli/src/os/operating_system_interface.dart';
import 'package:shorebird_cli/src/platform.dart';
// https://developer.android.com/studio/command-line/variables.html#envar
/// The environment variable name for the Android SDK home directory.
const kAndroidHome = 'ANDROID_HOME';
/// The environment variable name for the Android SDK root directory.
const kAndroidSdkRoot = 'ANDROID_SDK_ROOT';
/// A reference to a [AndroidSdk] instance.
@@ -4,6 +4,7 @@ import 'package:archive/archive_io.dart';
import 'package:path/path.dart' as p;
import 'package:shorebird_cli/src/third_party/flutter_tools/lib/flutter_tools.dart';
/// A wrapper around a directory that can be zipped.
extension DirectoryArchive on Directory {
/// Copies this directory to a temporary directory and zips it.
Future<File> zipToTempFile() async {
@@ -85,6 +85,7 @@ abstract class ArchiveDiffer {
);
}
/// Returns a map of file paths to their respective checksums.
Future<PathHashes> fileHashes(File archive) async {
return Isolate.run(() {
final zipDirectory = ZipDirectory.read(InputFileStream(archive.path));
@@ -5,6 +5,7 @@ typedef PathHashes = Map<String, String>;
/// Sets of [PathHashes] that represent changes between two sets of files.
class FileSetDiff {
/// Creates a [FileSetDiff] showing added, changed, and removed file sets.
FileSetDiff({
required this.addedPaths,
required this.removedPaths,
@@ -28,6 +29,7 @@ class FileSetDiff {
);
}
/// Creates an empty FileSetDiff.
FileSetDiff.empty()
: addedPaths = {},
removedPaths = {},
@@ -1,3 +1,4 @@
// cspell:words xcframeworks xcasset unsign codesign assetutil pubspec xcassets
import 'dart:io';
import 'dart:isolate';
import 'dart:typed_data';
@@ -23,16 +24,18 @@ import 'package:shorebird_cli/src/archive_analysis/macho.dart';
class IosArchiveDiffer extends ArchiveDiffer {
String _hash(List<int> bytes) => sha256.convert(bytes).toString();
static final binaryFilePatterns = {
static final _binaryFilePatterns = {
RegExp(r'App.framework/App$'),
RegExp(r'Flutter.framework/Flutter$'),
};
static RegExp appRegex = RegExp(
/// The regex pattern for identifying app files within an archive.
static final RegExp appRegex = RegExp(
r'^Products/Applications/[\w\-. ]+.app/[\w\- ]+$',
);
/// Files that have been added, removed, or that have changed between the
/// archives at the two provided paths. This method will also unisgn mach-o
/// archives at the two provided paths. This method will also unsign mach-o
/// binaries in the archives before computing the diff.
@override
Future<FileSetDiff> changedFiles(
@@ -85,7 +88,7 @@ class IosArchiveDiffer extends ArchiveDiffer {
.where((file) => file.isFile)
.where(
(file) =>
binaryFilePatterns
_binaryFilePatterns
.any((pattern) => pattern.hasMatch(file.name)) ||
appRegex.hasMatch(file.name),
)
@@ -125,7 +128,7 @@ class IosArchiveDiffer extends ArchiveDiffer {
}
/// Uses assetutil to write a json description of a .car file to disk and
/// diffs the contents of that file, less a timestamp line that chnages based
/// diffs the contents of that file, less a timestamp line that changes based
/// on when the .car file was created.
Future<String> _carFileHash(ArchiveFile file) async {
final tempDir = Directory.systemTemp.createTempSync();
@@ -1,8 +1,8 @@
import 'dart:io';
import 'dart:typed_data';
const machOHeaderSize = 32;
const uuidLoadCommandType = 0x1b;
const _machOHeaderSize = 32;
const _uuidLoadCommandType = 0x1b;
/// Utilities for interacting with Mach-O files.
/// See https://en.wikipedia.org/wiki/Mach-O.
@@ -38,12 +38,12 @@ class MachO {
final numberOfLoadCommands = _readInt32(bytes, 16);
// The load commands are immediately after the header.
var offset = machOHeaderSize;
var offset = _machOHeaderSize;
for (var i = 0; i < numberOfLoadCommands; i++) {
final commandType = _readInt32(bytes, offset);
final commandLength = _readInt32(bytes, offset + 4);
if (commandType == uuidLoadCommandType) {
if (commandType == _uuidLoadCommandType) {
// Zero out the UUID bytes.
final loadCommandStart = offset + 8;
for (var j = loadCommandStart; j < offset + commandLength; j++) {
@@ -1,8 +1,12 @@
// cspell:words propertylistserialization xcarchives
import 'dart:io';
import 'package:propertylistserialization/propertylistserialization.dart';
/// A representation of an Info.plist file.
class Plist {
/// Creates a new [Plist] from the contents of the provided [file].
Plist({required File file}) {
properties = PropertyListSerialization.propertyListWithString(
file.readAsStringSync(),
@@ -29,8 +33,10 @@ class Plist {
/// },
static const applicationPropertiesKey = 'ApplicationProperties';
/// The properties contained in the Info.plist file.
late final Map<String, Object> properties;
/// The version number of the application.
String get versionNumber {
final applicationProperties =
properties[applicationPropertiesKey]! as Map<String, Object>;
@@ -1,3 +1,5 @@
// cspell:words endtemplate aabs ipas appbundle bryanoltman codesign xcarchive
// cspell:words xcframework
import 'dart:io';
import 'package:mason_logger/mason_logger.dart';
@@ -130,6 +132,9 @@ class ArtifactBuilder {
}
}
/// Builds an APK using `flutter build apk`. Runs `flutter pub get` with the
/// system installation of Flutter to reset `.dart_tool/package_config.json`
/// after the build completes or fails.
Future<File> buildApk({
String? flavor,
String? target,
@@ -188,6 +193,9 @@ class ArtifactBuilder {
}
}
/// Builds an AAR using `flutter build aar`. Runs `flutter pub get` with the
/// system installation of Flutter to reset `.dart_tool/package_config.json`
/// after the build completes or fails.
Future<void> buildAar({
required String buildNumber,
Iterable<Arch>? targetPlatforms,
@@ -1,3 +1,4 @@
// cspell:words archs xcarchive xcframework
import 'dart:io';
import 'dart:isolate';
@@ -17,6 +18,7 @@ final artifactManagerRef = create(ArtifactManager.new);
/// The [ArtifactManager] instance available in the current zone.
ArtifactManager get artifactManager => read(artifactManagerRef);
/// Manages artifacts for the Shorebird CLI.
class ArtifactManager {
/// Generates a binary diff between two files and returns the path to the
/// output diff file.
@@ -246,6 +248,7 @@ class ArtifactManager {
return ipaFiles.single;
}
/// Name of the App.xcframework generated by `shorebird release ios-framework`
static const String appXcframeworkName = 'App.xcframework';
/// Returns the path to the App.xcframework generated by
+27 -2
View File
@@ -1,3 +1,6 @@
// ignore_for_file: public_member_api_docs
// cspell:words googleapis bryanoltman endtemplate CLI tgvek orctktiabrek
// cspell:words GOCSPX googleusercontent Pkkwp Entra
import 'dart:convert';
import 'dart:io';
@@ -19,10 +22,10 @@ import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
export 'ci_token.dart';
// A reference to a [Auth] instance.
/// A reference to an [Auth] instance.
final authRef = create(Auth.new);
// The [Auth] instance available in the current zone.
/// The [Auth] instance available in the current zone.
Auth get auth => read(authRef);
/// The JWT issuer field for Google-issued JWTs.
@@ -36,6 +39,7 @@ const microsoftJwtIssuerPrefix = 'https://login.microsoftonline.com/';
/// The environment variable that holds the Shorebird CI token.
const shorebirdTokenEnvVar = 'SHOREBIRD_TOKEN';
/// Callback for obtaining access credentials.
typedef ObtainAccessCredentials = Future<oauth2.AccessCredentials> Function(
oauth2.ClientId clientId,
List<String> scopes,
@@ -44,6 +48,7 @@ typedef ObtainAccessCredentials = Future<oauth2.AccessCredentials> Function(
oauth2.AuthEndpoints authEndpoints,
});
/// Callback for refreshing access credentials.
typedef RefreshCredentials = Future<oauth2.AccessCredentials> Function(
oauth2.ClientId clientId,
oauth2.AccessCredentials credentials,
@@ -51,11 +56,15 @@ typedef RefreshCredentials = Future<oauth2.AccessCredentials> Function(
oauth2.AuthEndpoints authEndpoints,
});
/// Callback when credentials are refreshed.
typedef OnRefreshCredentials = void Function(
oauth2.AccessCredentials credentials,
);
/// A client that automatically refreshes OAuth 2.0 credentials.
class AuthenticatedClient extends http.BaseClient {
/// Creates a new [AuthenticatedClient] with the given [httpClient] and
/// [credentials].
AuthenticatedClient.credentials({
required http.Client httpClient,
required oauth2.AccessCredentials credentials,
@@ -68,6 +77,8 @@ class AuthenticatedClient extends http.BaseClient {
refreshCredentials: refreshCredentials,
);
/// Creates a new [AuthenticatedClient] with the given [httpClient] and
/// [token].
AuthenticatedClient.token({
required http.Client httpClient,
required CiToken token,
@@ -137,7 +148,9 @@ class AuthenticatedClient extends http.BaseClient {
}
}
/// An OAuth 2.0 authentication provider.
class Auth {
/// Creates a new [Auth] instance.
Auth({
http.Client? httpClient,
String? credentialsDir,
@@ -160,10 +173,12 @@ class Auth {
final CodePushClientBuilder _buildCodePushClient;
CiToken? _token;
/// The path to the credentials file.
String get credentialsFilePath {
return p.join(_credentialsDir, 'credentials.json');
}
/// The underlying HTTP client.
http.Client get client {
if (_credentials == null && _token == null) {
return _httpClient;
@@ -183,6 +198,7 @@ class Auth {
);
}
/// Gets a CI token for the current user.
Future<CiToken> loginCI(
AuthProvider authProvider, {
required void Function(String) prompt,
@@ -220,6 +236,7 @@ class Auth {
}
}
/// Logs in the user.
Future<void> login(
AuthProvider authProvider, {
required void Function(String) prompt,
@@ -255,14 +272,17 @@ class Auth {
}
}
/// Logs out the user.
void logout() => _clearCredentials();
oauth2.AccessCredentials? _credentials;
String? _email;
/// The current user's email.
String? get email => _email;
/// Whether the user is authenticated.
bool get isAuthenticated => _email != null || _token != null;
void _loadCredentials() {
@@ -312,12 +332,15 @@ Run `shorebird login:ci` to obtain a new token.''');
}
}
/// Closes the underlying HTTP client.
void close() {
_httpClient.close();
}
}
/// Extensions on [oauth2.AccessCredentials] for working with JWT claims.
extension JwtClaims on oauth2.AccessCredentials {
/// Get the email from the JWT claims.
String? get email {
final token = idToken;
@@ -356,7 +379,9 @@ class UserNotFoundException implements Exception {
final String email;
}
/// Extensions on Jwt for working with OAuth 2.0 providers.
extension OauthAuthProvider on Jwt {
/// Get the [AuthProvider] from the JWT issuer.
AuthProvider get authProvider {
if (payload.iss == googleJwtIssuer) {
return AuthProvider.google;
@@ -1,3 +1,5 @@
// ignore_for_file: public_member_api_docs
import 'dart:io' hide Platform;
import 'package:http/http.dart' as http;
@@ -1,3 +1,7 @@
// ignore_for_file: public_member_api_docs
// cspell:words endtemplate pubspec sideloadable bryanoltman archs sideload
// cspell:words xcarchive codesigned xcframework
import 'dart:isolate';
import 'package:archive/archive_io.dart';
@@ -1,3 +1,5 @@
// ignore_for_file: public_member_api_docs
import 'dart:async';
import 'package:mason_logger/mason_logger.dart';
@@ -1,3 +1,5 @@
// 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/logger.dart';
@@ -10,6 +10,7 @@ import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart'
/// Login as a new Shorebird user.
/// {@endtemplate}
class LoginCommand extends ShorebirdCommand {
/// {@macro login_command}
LoginCommand() {
argParser.addOption(
'provider',
@@ -74,6 +75,7 @@ We could not find a Shorebird account for ${error.email}.''',
return ExitCode.success.code;
}
/// Prompt the user to log in.
void prompt(String url) {
logger.info('''
The Shorebird CLI needs your authorization to manage apps, releases, and patches on your behalf.
@@ -54,6 +54,7 @@ class IosFrameworkPatcher extends Patcher {
@override
double? get linkPercentage => lastBuildLinkPercentage;
/// The last build link percentage.
@visibleForTesting
double? lastBuildLinkPercentage;
@@ -1,3 +1,5 @@
// ignore_for_file: public_member_api_docs
import 'dart:async';
import 'package:crypto/crypto.dart';
@@ -1,3 +1,5 @@
// ignore_for_file: public_member_api_docs
import 'package:collection/collection.dart';
import 'package:mason_logger/mason_logger.dart';
import 'package:meta/meta.dart';
@@ -1,3 +1,5 @@
// ignore_for_file: public_member_api_docs
import 'dart:io';
import 'package:args/args.dart';
@@ -1,3 +1,6 @@
// ignore_for_file: public_member_api_docs
// cspell:words devicectl endtemplate bryanoltman sideloadable previewable apks
// cspell:words bundletool
import 'dart:async';
import 'dart:convert';
import 'dart:isolate';
@@ -97,11 +100,11 @@ class PreviewCommand extends ShorebirdCommand {
appId = shorebirdYaml.appId;
} else if (shorebirdYaml != null && flavors != null) {
final flavorOptions = flavors.keys.toList();
final choosenFlavor = logger.chooseOne<String>(
final chosenFlavor = logger.chooseOne<String>(
'Which app flavor?',
choices: flavorOptions,
);
appId = flavors[choosenFlavor];
appId = flavors[chosenFlavor];
} else {
appId = await promptForApp();
}
@@ -33,6 +33,7 @@ class AarReleaser extends Releaser {
/// argument of the flutter build aar command.
String get buildNumber => argResults['build-number'] as String;
/// The architectures to build the aar for.
Set<Arch> get architectures => (argResults['target-platform'] as List<String>)
.map(
(platform) => AndroidArch.availableAndroidArchs
@@ -30,6 +30,7 @@ class IosFrameworkReleaser extends Releaser {
required super.target,
});
/// The directory where the release artifacts are stored.
Directory get releaseDirectory => Directory(
p.join(shorebirdEnv.getShorebirdProjectRoot()!.path, 'release'),
);
@@ -19,6 +19,7 @@ import 'package:shorebird_cli/src/third_party/flutter_tools/lib/flutter_tools.da
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart';
/// A function that resolves a [Releaser] for a given [ReleaseType].
typedef ResolveReleaser = Releaser Function(ReleaseType releaseType);
/// {@template release_command}
@@ -150,6 +151,7 @@ of the iOS app that is using this module.''',
return ExitCode.success.code;
}
/// Returns a [Releaser] for the given [ReleaseType].
@visibleForTesting
Releaser getReleaser(ReleaseType releaseType) {
switch (releaseType) {
@@ -16,6 +16,7 @@ class UpgradeCommand extends ShorebirdCommand {
@override
String get description => 'Upgrade your copy of Shorebird.';
/// Name of the command, exposed for the [CommandRunner].
static const String commandName = 'upgrade';
@override
@@ -4,6 +4,7 @@ import 'package:shorebird_cli/src/platform/ios.dart';
/// A class that describes an argument from a command/sub command.
/// {@endtemplate}
class ArgumentDescriber {
/// {@macro argument_describer}
const ArgumentDescriber({
required this.name,
required this.description,
@@ -53,6 +54,8 @@ Entries from "--dart-define" with identical keys take precedence over entries fr
'''Export an IPA with these options. See "xcodebuild -h" for available exportOptionsPlist keys (iOS only).''',
);
/// An argument that allows the user to specify a public key file that will be
/// used to validate patch signatures.
static const publicKeyArg = ArgumentDescriber(
name: 'public-key-path',
description: '''
@@ -60,6 +63,8 @@ The path for a public key .pem file that will be used to validate patch signatur
''',
);
/// An argument that allows the user to specify a private key file that will
/// be used to sign the patch artifact.
static const privateKeyArg = ArgumentDescriber(
name: 'private-key-path',
description: '''
@@ -19,6 +19,7 @@ class ShorebirdYaml {
this.autoUpdate,
});
/// Creates a [ShorebirdYaml] from a JSON map.
factory ShorebirdYaml.fromJson(Map<dynamic, dynamic> json) =>
_$ShorebirdYamlFromJson(json);
@@ -48,7 +49,9 @@ class ShorebirdYaml {
final bool? autoUpdate;
}
/// Extension on [ShorebirdYaml] to get the app id for a specific flavor.
extension AppIdExtension on ShorebirdYaml {
/// Returns the app id for the given flavor.
String getAppId({String? flavor}) {
if (flavor == null || flavors == null) return appId;
return flavors![flavor] ?? appId;
@@ -1,9 +1,11 @@
// ignore_for_file: public_member_api_docs
import 'package:scoped_deps/scoped_deps.dart';
// A reference to a [EngineConfig] instance.
/// A reference to an [EngineConfig] instance.
final engineConfigRef = create(() => const EngineConfig.empty());
// The [EngineConfig] instance available in the current zone.
/// The [EngineConfig] instance available in the current zone.
EngineConfig get engineConfig => read(engineConfigRef);
class EngineConfig {
@@ -60,6 +60,7 @@ class Adb {
}
}
/// Runs `adb logcat`.
Future<Process> logcat({
String? filter,
String? deviceId,
@@ -131,6 +131,8 @@ class AotTools {
return tryParseVersion(version) ?? noVersion;
}
/// Whether the current analyze_snapshot executable supports the
/// `--dump-debug-info` flag.
Future<bool> isLinkDebugInfoSupported() async {
final result = await _exec(['link', '--help']);
return result.stdout.toString().contains('dump-debug-info');
@@ -1,3 +1,4 @@
// cspell:words bundletool
import 'package:path/path.dart' as p;
import 'package:scoped_deps/scoped_deps.dart';
import 'package:shorebird_cli/src/android_sdk.dart';
@@ -12,7 +13,9 @@ final bundletoolRef = create(Bundletool.new);
/// The [Bundletool] instance available in the current zone.
Bundletool get bundletool => read(bundletoolRef);
/// A wrapper around the `bundletool` command.
class Bundletool {
/// The name of the bundletool jar.
static const jar = 'bundletool.jar';
Future<ShorebirdProcessResult> _exec(List<String> command) async {
@@ -1,3 +1,5 @@
// 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';
@@ -1,3 +1,4 @@
// ignore_for_file: public_member_api_docs
import 'dart:async';
import 'dart:convert';
@@ -1,3 +1,5 @@
// ignore_for_file: public_member_api_docs
import 'package:json_annotation/json_annotation.dart';
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
@@ -1,3 +1,5 @@
// cspell:words bundletool devicectl gradlew idevicesyslog xcodebuild
export 'adb.dart';
export 'aot_tools.dart';
export 'bundletool.dart';
@@ -11,6 +11,7 @@ Git get git => read(gitRef);
/// A wrapper around all git related functionality.
class Git {
/// Name of the git executable.
static const executable = 'git';
/// Execute a git command with the provided [arguments].
@@ -1,3 +1,5 @@
// ignore_for_file: public_member_api_docs
import 'dart:io';
import 'package:collection/collection.dart';
@@ -16,6 +18,7 @@ class MissingAndroidProjectException implements Exception {
/// {@macro missing_android_project_exception}
const MissingAndroidProjectException(this.projectPath);
/// Expected path for the Android project.
final String projectPath;
@override
@@ -1,3 +1,5 @@
// ignore_for_file: public_member_api_docs
import 'dart:async';
import 'dart:convert';
import 'dart:io';
@@ -17,6 +17,7 @@ class PatchFailedException implements Exception {
/// {@macro patch_failed_exception}
PatchFailedException(this.message);
/// The error message.
final String message;
@override
@@ -29,6 +30,7 @@ class PatchFailedException implements Exception {
///
/// Throws [PatchFailedException] if the patch command exits with non-zero code.
class PatchExecutable {
/// Runs the `patch` executable.
Future<void> run({
required String releaseArtifactPath,
required String patchArtifactPath,
@@ -19,6 +19,7 @@ class PackageFailedException implements Exception {
/// {@macro package_failed_exception}
PackageFailedException(this.message);
/// The error message.
final String message;
@override
@@ -37,6 +38,7 @@ class ShorebirdTools {
return shorebirdToolsDirectory.existsSync();
}
/// The directory containing the `shorebird_tools` package.
Directory get shorebirdToolsDirectory {
final dir = Directory(
p.join(
@@ -13,6 +13,7 @@ class MissingIOSProjectException implements Exception {
/// {@macro missing_ios_project_exception}
const MissingIOSProjectException(this.projectPath);
/// Expected path of the XCode project.
final String projectPath;
@override
@@ -1,3 +1,4 @@
// ignore_for_file: public_member_api_docs
import 'package:args/args.dart';
import 'package:collection/collection.dart';
import 'package:mason_logger/mason_logger.dart';
@@ -6,7 +7,6 @@ import 'package:shorebird_cli/src/common_arguments.dart';
import 'package:shorebird_cli/src/extensions/file.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/release_type.dart';
import 'package:shorebird_cli/src/third_party/flutter_tools/lib/src/base/io.dart';
extension OptionFinder on ArgResults {
@@ -1,6 +1,7 @@
import 'package:collection/collection.dart';
import 'package:shorebird_cli/src/shorebird_process.dart';
/// Extensions for finding `app.dill` in a [ShorebirdProcessResult].
extension FindAppDill on ShorebirdProcessResult {
/// Finds a line in stdout that invokes gen_snapshot with app.dill as an
/// argument. The path to the app.dill file is the last argument in the line.
@@ -1,3 +1,5 @@
// ignore_for_file: public_member_api_docs
extension NullOrEmtpy on String? {
/// Returns `true` if this string is null or empty.
bool get isNullOrEmpty => this == null || this!.isEmpty;
@@ -1,5 +1,6 @@
import 'dart:math';
/// Formats the given [bytes] into a human-readable string.
String formatBytes(int bytes, {int decimals = 2}) {
if (bytes <= 0) return '0 B';
const suffixes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB'];
@@ -3,6 +3,7 @@ import 'package:shorebird_cli/src/logger.dart';
/// An http client that logs request at the verbose level.
class LoggingClient extends http.BaseClient {
/// Creates a new [LoggingClient] wrapping [httpClient].
LoggingClient({required http.Client httpClient}) : _baseClient = httpClient;
final http.Client _baseClient;
@@ -10,6 +10,7 @@ http.Client retryingHttpClient(http.Client client) => RetryClient(
whenError: isRetryableException,
);
/// Returns `true` if the [exception] is a retryable exception.
bool isRetryableException(Object exception, StackTrace _) {
return switch (exception.runtimeType) {
http.ClientException => true,
@@ -21,5 +22,6 @@ bool isRetryableException(Object exception, StackTrace _) {
};
}
/// Returns `true` if the [response] is a retryable response.
bool isRetryableResponse(http.BaseResponse response) =>
response.statusCode >= 500;
+1 -1
View File
@@ -19,7 +19,7 @@ const _logFileName = 'shorebird.log';
/// be created for every run of the Shorebird CLI, and will have the name
/// `timestamp_shorebird.log`.
final File currentRunLogFile = (() {
// TODO(bryanoltman): use package:clock to test that we use the correct timestamp
// TODO(bryanoltman): use package:clock to test for the correct timestamp
final timestamp = DateTime.now().millisecondsSinceEpoch;
final file = File(
p.join(
@@ -34,7 +34,7 @@ class UserCancelledException implements Exception {}
/// A reference to a [PatchDiffChecker] instance.
ScopedRef<PatchDiffChecker> patchDiffCheckerRef = create(PatchDiffChecker.new);
// The [PatchVerifier] instance available in the current zone.
/// The [PatchDiffChecker] instance available in the current zone.
PatchDiffChecker get patchDiffChecker => read(patchDiffCheckerRef);
/// {@template patch_verifier}
+2 -2
View File
@@ -1,8 +1,8 @@
import 'package:platform/platform.dart';
import 'package:scoped_deps/scoped_deps.dart';
// A reference to a [Platform] instance.
/// A reference to a [Platform] instance.
ScopedRef<Platform> platformRef = create(() => const LocalPlatform());
// The [Platform] instance available in the current zone.
/// The [Platform] instance available in the current zone.
Platform get platform => read(platformRef, orElse: () => const LocalPlatform());
@@ -2,6 +2,7 @@ import 'package:collection/collection.dart';
import 'package:shorebird_cli/src/engine_config.dart';
import 'package:shorebird_cli/src/platform/platform.dart';
/// Extensions for working with Android architectures.
extension AndroidArch on Arch {
/// The name of the architecture as expected by the --target-platforms flag.
String get targetPlatformCliArg {
@@ -40,6 +41,7 @@ extension AndroidArch on Arch {
}
}
/// Returns the available Android architectures.
static Iterable<Arch> get availableAndroidArchs {
if (engineConfig.localEngine != null) {
final localEngineOutName = engineConfig.localEngine!;
@@ -61,6 +63,7 @@ extension AndroidArch on Arch {
}
}
/// Extensions for working with Android target platforms.
extension TargetPlatformArgs on Iterable<Arch> {
/// The value to pass to the --target-platforms flag.
String get targetPlatformArg => map((e) => e.targetPlatformCliArg).join(',');
@@ -1,3 +1,5 @@
// ignore_for_file: public_member_api_docs
import 'dart:io';
import 'package:args/args.dart';
@@ -7,8 +7,13 @@ export 'ios.dart';
/// Note: in addition to these values, the Flutter tooling also supports x86.
/// {@endtemplate}
enum Arch {
/// 32-bit ARM architecture.
arm32(arch: 'arm'),
/// 64-bit ARM architecture.
arm64(arch: 'aarch64'),
/// 64-bit x86 architecture.
x86_64(arch: 'x86_64');
/// {@macro arch}
@@ -1,3 +1,5 @@
// 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';
@@ -1,3 +1,5 @@
// ignore_for_file: public_member_api_docs
import 'dart:io';
import 'package:collection/collection.dart';
@@ -11,12 +13,16 @@ import 'package:shorebird_cli/src/shorebird_env.dart';
/// Thrown when multiple artifacts are found in the build directory.
class MultipleArtifactsFoundException implements Exception {
/// Creates a [MultipleArtifactsFoundException].
MultipleArtifactsFoundException({
required this.buildDir,
required this.foundArtifacts,
});
/// The build directory where the artifacts were found.
final String buildDir;
/// The list of found artifacts.
final List<FileSystemEntity> foundArtifacts;
@override
@@ -1,3 +1,5 @@
// ignore_for_file: public_member_api_docs
import 'dart:async';
import 'package:args/args.dart';
@@ -1,3 +1,5 @@
// ignore_for_file: public_member_api_docs
import 'dart:io';
import 'package:args/args.dart';
@@ -1,3 +1,5 @@
// ignore_for_file: public_member_api_docs
import 'dart:io' hide Platform;
import 'package:checked_yaml/checked_yaml.dart';
@@ -1,3 +1,4 @@
// ignore_for_file: public_member_api_docs
import 'dart:convert';
import 'dart:io';
@@ -6,10 +6,10 @@ import 'package:shorebird_cli/src/engine_config.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
// A reference to a [ShorebirdProcess] instance.
/// A reference to a [ShorebirdProcess] instance.
final processRef = create(ShorebirdProcess.new);
// The [ShorebirdProcess] instance available in the current zone.
/// The [ShorebirdProcess] instance available in the current zone.
ShorebirdProcess get process => read(processRef);
/// A wrapper around [Process] that replaces executables to Shorebird-vended
@@ -17,12 +17,15 @@ ShorebirdProcess get process => read(processRef);
// This may need a better name, since it returns "Process" it's more a
// "ProcessFactory" than a "Process".
class ShorebirdProcess {
/// Creates a ShorebirdProcess.
ShorebirdProcess({
ProcessWrapper? processWrapper, // For mocking ShorebirdProcess.
}) : processWrapper = processWrapper ?? ProcessWrapper();
/// The underlying process wrapper.
final ProcessWrapper processWrapper;
/// Runs the process and returns the result.
Future<ShorebirdProcessResult> run(
String executable,
List<String> arguments, {
@@ -62,6 +65,7 @@ class ShorebirdProcess {
return result;
}
/// Runs the process synchronously and returns the result.
ShorebirdProcessResult runSync(
String executable,
List<String> arguments, {
@@ -101,6 +105,7 @@ class ShorebirdProcess {
return result;
}
/// Starts a new process running the executable with the specified arguments.
Future<Process> start(
String executable,
List<String> arguments, {
@@ -222,21 +227,30 @@ $stderr''');
}
}
/// Result from running a process.
class ShorebirdProcessResult {
/// Creates a new [ShorebirdProcessResult].
const ShorebirdProcessResult({
required this.exitCode,
required this.stdout,
required this.stderr,
});
/// The exit code of the process.
final int exitCode;
/// The standard output of the process.
final dynamic stdout;
/// The standard error of the process.
final dynamic stderr;
}
/// A wrapper around [Process] that can be mocked for testing.
// coverage:ignore-start
@visibleForTesting
class ProcessWrapper {
/// Runs the process and returns the result.
Future<ShorebirdProcessResult> run(
String executable,
List<String> arguments, {
@@ -258,6 +272,7 @@ class ProcessWrapper {
);
}
/// Runs the process synchronously and returns the result.
ShorebirdProcessResult runSync(
String executable,
List<String> arguments, {
@@ -279,6 +294,7 @@ class ProcessWrapper {
);
}
/// Starts a new process running the executable with the specified arguments.
Future<Process> start(
String executable,
List<String> arguments, {
@@ -1,3 +1,5 @@
// 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';
@@ -1,8 +1,11 @@
/// Shorebird Web Console URLs.
class ShorebirdWebConsole {
/// Returns a [Uri] for the Shorebird Web Console.
static Uri uri(String path) {
return Uri.parse('https://console.shorebird.dev/$path');
}
/// Returns a [Uri] for the Shorebird Web Console login page.
static Uri appReleaseUri(
String appId,
int releaseId,
@@ -1,9 +1,15 @@
/// An exception thrown when a process fails.
class ProcessExit implements Exception {
/// Creates a new [ProcessExit] with the given [exitCode].
ProcessExit(this.exitCode, {this.immediate = false});
/// Whether the process exited immediately.
final bool immediate;
/// The exit code of the process.
final int exitCode;
/// The message associated with the exception.
String get message => 'ProcessExit: $exitCode';
@override
@@ -10,7 +10,8 @@ import 'package:xml/xml.dart';
///
/// See https://github.com/shorebirdtech/shorebird/issues/160.
class AndroidInternetPermissionValidator extends Validator {
final String mainAndroidManifestPath = p.join(
/// Path to the main AndroidManifest.xml file.
final String _mainAndroidManifestPath = p.join(
'android',
'app',
'src',
@@ -55,7 +56,7 @@ The command you are running must be run within a Flutter app project that suppor
ValidationIssue(
severity: ValidationIssueSeverity.error,
message:
'$mainAndroidManifestPath is missing the INTERNET permission.',
'$_mainAndroidManifestPath is missing the INTERNET permission.',
fix: () => _addInternetPermissionToFile(manifestFilePath),
),
];
@@ -5,7 +5,9 @@ import 'package:shorebird_cli/src/shorebird_flutter.dart';
import 'package:shorebird_cli/src/third_party/flutter_tools/lib/flutter_tools.dart';
import 'package:shorebird_cli/src/validators/validators.dart';
/// An exception thrown when a validation issue is found.
class FlutterValidationException implements Exception {
/// Creates a new [FlutterValidationException] with the provided [message].
const FlutterValidationException(this.message);
/// The message associated with the exception.
@@ -15,6 +17,7 @@ class FlutterValidationException implements Exception {
String toString() => 'FlutterValidationException: $message';
}
/// An exception thrown when a command is not found.
class CommandNotFoundException implements Exception {}
/// {@template shorebird_flutter_validator}
@@ -5,6 +5,7 @@ import 'package:shorebird_cli/src/validators/validators.dart';
/// Verifies that the currently installed version of Shorebird is the latest.
class ShorebirdVersionValidator extends Validator {
/// Creates a new [ShorebirdVersionValidator].
ShorebirdVersionValidator();
@override
@@ -1,7 +1,9 @@
// cspell:words googleapis
import 'package:shorebird_cli/src/http_client/http_client.dart';
import 'package:shorebird_cli/src/third_party/flutter_tools/lib/flutter_tools.dart';
import 'package:shorebird_cli/src/validators/validators.dart';
/// Verifies that the user has access to storage.googleapis.com.
class StorageAccessValidator extends Validator {
@override
String get description => 'Has access to storage.googleapis.com';
@@ -11,17 +11,19 @@ export 'shorebird_yaml_asset_validator.dart';
export 'storage_access_validator.dart';
/// Severity level of a [ValidationIssue].
///
/// [error]s will prevent code push from working and block releases, builds,
/// patches, etc.
/// [warning]s should be fixed before releasing your app, but are not as urgent.
enum ValidationIssueSeverity {
/// [error]s will prevent code push from working and block releases, builds,
/// patches, etc.
error,
/// [warning]s should be fixed before releasing your app, but are not as
/// urgent.
warning,
}
/// Display helpers for printing [ValidationIssue]s.
extension Display on ValidationIssueSeverity {
/// The raw string representation of this severity.
String get rawLeading {
switch (this) {
case ValidationIssueSeverity.error:
@@ -31,6 +33,7 @@ extension Display on ValidationIssueSeverity {
}
}
/// The colorized string representation of this severity.
String get displayLeading {
switch (this) {
case ValidationIssueSeverity.error:
@@ -44,6 +47,7 @@ extension Display on ValidationIssueSeverity {
/// A (potential) problem with the current Shorebird installation or project.
@immutable
class ValidationIssue {
/// Creates a new [ValidationIssue].
const ValidationIssue({
required this.severity,
required this.message,
@@ -1,3 +1,4 @@
// cspell:words xcarchive xcarchives xcframeworks xcframework
import 'package:path/path.dart' as p;
import 'package:shorebird_cli/src/archive_analysis/archive_analysis.dart';
import 'package:shorebird_cli/src/platform.dart';