chore(deps): upgrade to Dart 3.7 (#2894)

This commit is contained in:
Felix Angelov
2025-02-14 13:04:26 -06:00
committed by GitHub
parent c89089d84c
commit c1bdc82104
279 changed files with 12460 additions and 14222 deletions
@@ -12,7 +12,7 @@ import 'package:quiver/collection.dart';
class ArtifactManifestClient {
/// {@macro artifact_manifest_client}
ArtifactManifestClient({http.Client? httpClient})
: _httpClient = httpClient ?? http.Client();
: _httpClient = httpClient ?? http.Client();
final http.Client _httpClient;
@@ -33,11 +33,9 @@ class ArtifactManifestClient {
);
final response = await _httpClient.get(url);
if (response.statusCode != HttpStatus.ok) {
throw Exception(
'''
throw Exception('''
Failed to fetch artifacts manifest for revision $revision.
${response.statusCode} ${response.reasonPhrase}''',
);
${response.statusCode} ${response.reasonPhrase}''');
}
return checkedYamlDecode(
@@ -114,9 +114,10 @@ String getFlutterArtifactLocation({
required String artifactPath,
String? engine,
}) {
final adjustedPath = engine != null
? artifactPath.replaceAll(r'$engine', engine)
: artifactPath;
final adjustedPath =
engine != null
? artifactPath.replaceAll(r'$engine', engine)
: artifactPath;
return 'https://storage.googleapis.com/$adjustedPath';
}
@@ -9,29 +9,33 @@ part of 'artifacts_manifest.dart';
// **************************************************************************
ArtifactsManifest _$ArtifactsManifestFromJson(Map json) => $checkedCreate(
'ArtifactsManifest',
'ArtifactsManifest',
json,
($checkedConvert) {
$checkKeys(
json,
($checkedConvert) {
$checkKeys(
json,
allowedKeys: const [
'flutter_engine_revision',
'storage_bucket',
'artifact_overrides'
],
);
final val = ArtifactsManifest(
flutterEngineRevision:
$checkedConvert('flutter_engine_revision', (v) => v as String),
storageBucket: $checkedConvert('storage_bucket', (v) => v as String),
artifactOverrides: $checkedConvert('artifact_overrides',
(v) => (v as List<dynamic>).map((e) => e as String).toSet()),
);
return val;
},
fieldKeyMap: const {
'flutterEngineRevision': 'flutter_engine_revision',
'storageBucket': 'storage_bucket',
'artifactOverrides': 'artifact_overrides'
},
allowedKeys: const [
'flutter_engine_revision',
'storage_bucket',
'artifact_overrides',
],
);
final val = ArtifactsManifest(
flutterEngineRevision: $checkedConvert(
'flutter_engine_revision',
(v) => v as String,
),
storageBucket: $checkedConvert('storage_bucket', (v) => v as String),
artifactOverrides: $checkedConvert(
'artifact_overrides',
(v) => (v as List<dynamic>).map((e) => e as String).toSet(),
),
);
return val;
},
fieldKeyMap: const {
'flutterEngineRevision': 'flutter_engine_revision',
'storageBucket': 'storage_bucket',
'artifactOverrides': 'artifact_overrides',
},
);
+1 -1
View File
@@ -599,4 +599,4 @@ packages:
source: hosted
version: "3.1.2"
sdks:
dart: ">=3.6.0 <4.0.0"
dart: ">=3.7.0 <4.0.0"
+2 -2
View File
@@ -5,7 +5,7 @@ repository: https://github.com/shorebirdtech/shorebird/
publish_to: "none"
environment:
sdk: ">=3.0.0 <4.0.0"
sdk: ">=3.7.0 <4.0.0"
dependencies:
checked_yaml: ^2.0.2
@@ -17,7 +17,7 @@ dependencies:
shelf_hotreload: ^1.5.0
dev_dependencies:
build_runner: ^2.4.13
build_runner: ^2.4.13
json_serializable: ^6.9.3
mocktail: ^1.0.4
test: ^1.25.15
@@ -63,22 +63,17 @@ void main() {
verify(() => client.getManifest(shorebirdEngineRevision)).called(1);
});
test(
'should proxy to Flutter artifacts '
test('should proxy to Flutter artifacts '
'when no engine revision is detected', () async {
const path =
'flutter_infra_release/flutter/fonts/3012db47f3130e62f7cc0beabff968a33cbec8d8/fonts.zip';
final request = buildRequest(path);
final response = await handler(request);
expect(
response,
isRedirectTo('https://storage.googleapis.com/$path'),
);
expect(response, isRedirectTo('https://storage.googleapis.com/$path'));
verifyNever(() => client.getManifest(any()));
});
test(
'should proxy to Shorebird artifacts '
test('should proxy to Shorebird artifacts '
'when an engine revision is detected with an override', () async {
const path =
'flutter_infra_release/flutter/$shorebirdEngineRevision/android-x64-release/artifacts.zip';
@@ -93,8 +88,7 @@ void main() {
verify(() => client.getManifest(shorebirdEngineRevision)).called(1);
});
test(
'should proxy to Flutter artifacts '
test('should proxy to Flutter artifacts '
'when an engine revision is detected with no override', () async {
const path =
'flutter_infra_release/flutter/$shorebirdEngineRevision/windows-x64/font-subset.zip';
@@ -109,8 +103,7 @@ void main() {
verify(() => client.getManifest(shorebirdEngineRevision)).called(1);
});
test(
'should return 404 '
test('should return 404 '
'when pattern is not recognized', () async {
const path =
'flutter_infra_release/flutter/$shorebirdEngineRevision/unknown/artifacts.zip';
@@ -31,7 +31,7 @@ Handler gcpAlertHandler({required String webhookUrl, http.Client? client}) {
{'name': 'Resource', 'value': resource, 'inline': true},
{'name': 'Condition', 'value': conditionName, 'inline': true},
],
}
},
],
'attachments': const <dynamic>[],
};
@@ -9,37 +9,36 @@ part of 'gcp_alert.dart';
// **************************************************************************
GCPAlert _$GCPAlertFromJson(Map<String, dynamic> json) => $checkedCreate(
'GCPAlert',
json,
($checkedConvert) {
final val = GCPAlert(
incident: $checkedConvert(
'incident',
(v) => v == null
? null
: Incident.fromJson(v as Map<String, dynamic>)),
);
return val;
},
'GCPAlert',
json,
($checkedConvert) {
final val = GCPAlert(
incident: $checkedConvert(
'incident',
(v) => v == null ? null : Incident.fromJson(v as Map<String, dynamic>),
),
);
return val;
},
);
Incident _$IncidentFromJson(Map<String, dynamic> json) => $checkedCreate(
'Incident',
json,
($checkedConvert) {
final val = Incident(
url: $checkedConvert('url', (v) => v as String?),
state: $checkedConvert('state', (v) => v as String?),
summary: $checkedConvert('summary', (v) => v as String?),
resourceName: $checkedConvert('resource_name', (v) => v as String?),
conditionName: $checkedConvert('condition_name', (v) => v as String?),
policyName: $checkedConvert('policy_name', (v) => v as String?),
);
return val;
},
fieldKeyMap: const {
'resourceName': 'resource_name',
'conditionName': 'condition_name',
'policyName': 'policy_name'
},
'Incident',
json,
($checkedConvert) {
final val = Incident(
url: $checkedConvert('url', (v) => v as String?),
state: $checkedConvert('state', (v) => v as String?),
summary: $checkedConvert('summary', (v) => v as String?),
resourceName: $checkedConvert('resource_name', (v) => v as String?),
conditionName: $checkedConvert('condition_name', (v) => v as String?),
policyName: $checkedConvert('policy_name', (v) => v as String?),
);
return val;
},
fieldKeyMap: const {
'resourceName': 'resource_name',
'conditionName': 'condition_name',
'policyName': 'policy_name',
},
);
+1 -1
View File
@@ -591,4 +591,4 @@ packages:
source: hosted
version: "3.1.2"
sdks:
dart: ">=3.6.0 <4.0.0"
dart: ">=3.7.0 <4.0.0"
+1 -1
View File
@@ -4,7 +4,7 @@ version: 1.0.0
publish_to: none
environment:
sdk: '>=3.0.0 <4.0.0'
sdk: ">=3.7.0 <4.0.0"
dependencies:
http: ^1.3.0
@@ -33,57 +33,55 @@ void main() {
Request(
'POST',
Uri.parse('http://localhost:8080/'),
body: json.encode(
{
'version': 'test',
'incident': {
'incident_id': '12345',
'scoping_project_id': '12345',
'scoping_project_number': 12345,
'url': 'http://www.example.com',
'started_at': 0,
'ended_at': 0,
'state': 'OPEN',
'summary': 'Test Incident',
'apigee_url': 'http://www.example.com',
'observed_value': '1.0',
'resource': {
'type': 'example_resource',
'labels': {'example': 'label'},
},
'resource_type_display_name': 'Example Resource Type',
'resource_id': '12345',
'resource_display_name': 'Example Resource',
'resource_name': 'projects/12345/example_resources/12345',
'metric': {
'type': 'test.googleapis.com/metric',
'displayName': 'Test Metric',
'labels': {'example': 'label'},
},
'metadata': {
'system_labels': {'example': 'label'},
'user_labels': {'example': 'label'},
},
'policy_name': 'projects/12345/alertPolicies/12345',
'policy_user_labels': {'example': 'label'},
'documentation': 'Test documentation',
'condition': {
'name': 'projects/12345/alertPolicies/12345/conditions/12345',
'displayName': 'Example condition',
'conditionThreshold': {
'filter':
'metric.type="test.googleapis.com/metric" resource.type="example_resource"',
'comparison': 'COMPARISON_GT',
'thresholdValue': 0.5,
'duration': '0s',
'trigger': {'count': 1},
},
},
'condition_name': 'Example condition',
'threshold_value': '0.5',
body: json.encode({
'version': 'test',
'incident': {
'incident_id': '12345',
'scoping_project_id': '12345',
'scoping_project_number': 12345,
'url': 'http://www.example.com',
'started_at': 0,
'ended_at': 0,
'state': 'OPEN',
'summary': 'Test Incident',
'apigee_url': 'http://www.example.com',
'observed_value': '1.0',
'resource': {
'type': 'example_resource',
'labels': {'example': 'label'},
},
'resource_type_display_name': 'Example Resource Type',
'resource_id': '12345',
'resource_display_name': 'Example Resource',
'resource_name': 'projects/12345/example_resources/12345',
'metric': {
'type': 'test.googleapis.com/metric',
'displayName': 'Test Metric',
'labels': {'example': 'label'},
},
'metadata': {
'system_labels': {'example': 'label'},
'user_labels': {'example': 'label'},
},
'policy_name': 'projects/12345/alertPolicies/12345',
'policy_user_labels': {'example': 'label'},
'documentation': 'Test documentation',
'condition': {
'name': 'projects/12345/alertPolicies/12345/conditions/12345',
'displayName': 'Example condition',
'conditionThreshold': {
'filter':
'metric.type="test.googleapis.com/metric" resource.type="example_resource"',
'comparison': 'COMPARISON_GT',
'thresholdValue': 0.5,
'duration': '0s',
'trigger': {'count': 1},
},
},
'condition_name': 'Example condition',
'threshold_value': '0.5',
},
),
}),
),
);
@@ -109,9 +107,9 @@ void main() {
'name': 'Condition',
'value': 'Example condition',
'inline': true,
}
},
],
}
},
],
'attachments': <dynamic>[],
}),
+3 -13
View File
@@ -40,11 +40,7 @@ Future<PublicKeyStore?> _getPublicKeys(String url) async {
return null;
}
publicKeyStores.set(
url,
publicKeyStore,
ttl: Duration(seconds: maxAge),
);
publicKeyStores.set(url, publicKeyStore, ttl: Duration(seconds: maxAge));
return publicKeyStore;
}
@@ -105,10 +101,7 @@ Future<Jwt> verify(
return jwt;
}
Future<void> _verifyHeader(
JwtHeader header,
Iterable<String> keyIds,
) async {
Future<void> _verifyHeader(JwtHeader header, Iterable<String> keyIds) async {
if (header.typ != 'JWT') {
throw const JwtVerificationFailure('Invalid token type.');
}
@@ -171,10 +164,7 @@ bool _verifySignature(String jwt, String publicKey) {
final public = pair.public;
final signer = Signer('SHA-256/RSA');
final key = RSAPublicKey(
public!.modulus,
BigInt.from(public.publicExponent),
);
final key = RSAPublicKey(public!.modulus, BigInt.from(public.publicExponent));
final param = ParametersWithRandom(
PublicKeyParameter<RSAPublicKey>(key),
SecureRandom('AES/CTR/PRNG'),
+16 -17
View File
@@ -8,20 +8,19 @@ part of 'jwk.dart';
// JsonSerializableGenerator
// **************************************************************************
Jwk _$JwkFromJson(Map<String, dynamic> json) => $checkedCreate(
'Jwk',
json,
($checkedConvert) {
final val = Jwk(
kty: $checkedConvert('kty', (v) => v as String),
use: $checkedConvert('use', (v) => v as String),
kid: $checkedConvert('kid', (v) => v as String),
x5c: $checkedConvert('x5c',
(v) => (v as List<dynamic>).map((e) => e as String).toList()),
x5t: $checkedConvert('x5t', (v) => v as String),
n: $checkedConvert('n', (v) => v as String),
e: $checkedConvert('e', (v) => v as String),
);
return val;
},
);
Jwk _$JwkFromJson(Map<String, dynamic> json) =>
$checkedCreate('Jwk', json, ($checkedConvert) {
final val = Jwk(
kty: $checkedConvert('kty', (v) => v as String),
use: $checkedConvert('use', (v) => v as String),
kid: $checkedConvert('kid', (v) => v as String),
x5c: $checkedConvert(
'x5c',
(v) => (v as List<dynamic>).map((e) => e as String).toList(),
),
x5t: $checkedConvert('x5t', (v) => v as String),
n: $checkedConvert('n', (v) => v as String),
e: $checkedConvert('e', (v) => v as String),
);
return val;
});
+1 -5
View File
@@ -8,11 +8,7 @@ part 'jwt_header.g.dart';
@JsonSerializable(createToJson: false)
class JwtHeader {
/// {@macro jwt_header}
const JwtHeader({
required this.alg,
required this.kid,
required this.typ,
});
const JwtHeader({required this.alg, required this.kid, required this.typ});
/// Decode a [JwtHeader] from a `Map<String, dynamic>`.
factory JwtHeader.fromJson(Map<String, dynamic> json) {
+9 -12
View File
@@ -8,15 +8,12 @@ part of 'jwt_header.dart';
// JsonSerializableGenerator
// **************************************************************************
JwtHeader _$JwtHeaderFromJson(Map<String, dynamic> json) => $checkedCreate(
'JwtHeader',
json,
($checkedConvert) {
final val = JwtHeader(
alg: $checkedConvert('alg', (v) => v as String),
kid: $checkedConvert('kid', (v) => v as String),
typ: $checkedConvert('typ', (v) => v as String),
);
return val;
},
);
JwtHeader _$JwtHeaderFromJson(Map<String, dynamic> json) =>
$checkedCreate('JwtHeader', json, ($checkedConvert) {
final val = JwtHeader(
alg: $checkedConvert('alg', (v) => v as String),
kid: $checkedConvert('kid', (v) => v as String),
typ: $checkedConvert('typ', (v) => v as String),
);
return val;
});
+12 -16
View File
@@ -8,19 +8,15 @@ part of 'jwt_payload.dart';
// JsonSerializableGenerator
// **************************************************************************
JwtPayload _$JwtPayloadFromJson(Map<String, dynamic> json) => $checkedCreate(
'JwtPayload',
json,
($checkedConvert) {
final val = JwtPayload(
exp: $checkedConvert('exp', (v) => (v as num).toInt()),
iat: $checkedConvert('iat', (v) => (v as num).toInt()),
aud: $checkedConvert('aud', (v) => v as String),
iss: $checkedConvert('iss', (v) => v as String),
sub: $checkedConvert('sub', (v) => v as String),
authTime: $checkedConvert('auth_time', (v) => (v as num?)?.toInt()),
);
return val;
},
fieldKeyMap: const {'authTime': 'auth_time'},
);
JwtPayload _$JwtPayloadFromJson(Map<String, dynamic> json) =>
$checkedCreate('JwtPayload', json, ($checkedConvert) {
final val = JwtPayload(
exp: $checkedConvert('exp', (v) => (v as num).toInt()),
iat: $checkedConvert('iat', (v) => (v as num).toInt()),
aud: $checkedConvert('aud', (v) => v as String),
iss: $checkedConvert('iss', (v) => v as String),
sub: $checkedConvert('sub', (v) => v as String),
authTime: $checkedConvert('auth_time', (v) => (v as num?)?.toInt()),
);
return val;
}, fieldKeyMap: const {'authTime': 'auth_time'});
@@ -8,17 +8,16 @@ part of 'jwk_key_store.dart';
// JsonSerializableGenerator
// **************************************************************************
JwkKeyStore _$JwkKeyStoreFromJson(Map<String, dynamic> json) => $checkedCreate(
'JwkKeyStore',
json,
($checkedConvert) {
final val = JwkKeyStore(
keys: $checkedConvert(
'keys',
(v) => (v as List<dynamic>)
JwkKeyStore _$JwkKeyStoreFromJson(Map<String, dynamic> json) =>
$checkedCreate('JwkKeyStore', json, ($checkedConvert) {
final val = JwkKeyStore(
keys: $checkedConvert(
'keys',
(v) =>
(v as List<dynamic>)
.map((e) => Jwk.fromJson(e as Map<String, dynamic>))
.toList()),
);
return val;
},
);
.toList(),
),
);
return val;
});
+1 -1
View File
@@ -4,7 +4,7 @@ version: 1.0.0+1
publish_to: none
environment:
sdk: ">=3.0.0 <4.0.0"
sdk: ">=3.7.0 <4.0.0"
dependencies:
clock: ^1.1.0
+45 -42
View File
@@ -24,8 +24,9 @@ void main() {
final jwkKeyStoreJsonString =
File(p.join('test', 'fixtures', 'jwk_key_store.json')).readAsStringSync();
final keyValueKeyStoreString =
File(p.join('test', 'fixtures', 'key_value_key_store.json'))
.readAsStringSync();
File(
p.join('test', 'fixtures', 'key_value_key_store.json'),
).readAsStringSync();
final expiresAt = DateTime.fromMillisecondsSinceEpoch(1643687866 * 1000);
final validTime = expiresAt.subtract(const Duration(minutes: 15));
@@ -69,24 +70,26 @@ void main() {
);
});
test('throws a JwtVerificationFailure if string is not valid jwt',
() async {
await expectLater(
() => verify(
'not.a.jwt',
audience: {audience},
issuer: issuer,
publicKeysUrl: keyValuePublicKeysUrl,
),
throwsA(
isA<JwtVerificationFailure>().having(
(e) => e.reason,
'reason',
'JWT header is malformed.',
test(
'throws a JwtVerificationFailure if string is not valid jwt',
() async {
await expectLater(
() => verify(
'not.a.jwt',
audience: {audience},
issuer: issuer,
publicKeysUrl: keyValuePublicKeysUrl,
),
),
);
});
throwsA(
isA<JwtVerificationFailure>().having(
(e) => e.reason,
'reason',
'JWT header is malformed.',
),
),
);
},
);
test('throws a JwtVerificationFailure if payload is not valid', () async {
await expectLater(
@@ -106,26 +109,28 @@ void main() {
);
});
test('throws a JwtVerificationFailure if signature is not valid',
() async {
await withClock(Clock.fixed(validTime), () async {
await expectLater(
() => verify(
tokenInvalidSignature,
audience: {audience},
issuer: issuer,
publicKeysUrl: keyValuePublicKeysUrl,
),
throwsA(
isA<JwtVerificationFailure>().having(
(e) => e.reason,
'reason',
'JWT signature is malformed.',
test(
'throws a JwtVerificationFailure if signature is not valid',
() async {
await withClock(Clock.fixed(validTime), () async {
await expectLater(
() => verify(
tokenInvalidSignature,
audience: {audience},
issuer: issuer,
publicKeysUrl: keyValuePublicKeysUrl,
),
),
);
});
});
throwsA(
isA<JwtVerificationFailure>().having(
(e) => e.reason,
'reason',
'JWT signature is malformed.',
),
),
);
});
},
);
test('throws exception if jwt has no matching public key id', () async {
await withClock(Clock.fixed(validTime), () async {
@@ -147,8 +152,7 @@ void main() {
});
});
test(
'throws exception if invalid keys are provided '
test('throws exception if invalid keys are provided '
'by the publicKeysUrl (KeyValueKeyStore)', () async {
getOverride = (Uri uri) async {
return Response(
@@ -177,8 +181,7 @@ void main() {
});
});
test(
'throws exception if invalid keys are provided '
test('throws exception if invalid keys are provided '
'by the publicKeysUrl (JwkKeyStore)', () async {
getOverride = (Uri uri) async {
return Response(
@@ -1 +1,6 @@
include: package:very_good_analysis/analysis_options.7.0.0.yaml
linter:
rules:
# Disabling in favor of the new Dart 3.7 format.
# https://github.com/VeryGoodOpenSource/very_good_analysis/issues/136
require_trailing_commas: false
+10 -19
View File
@@ -116,9 +116,9 @@ class RedisClient {
RedisSocketOptions socket = const RedisSocketOptions(),
RedisCommandOptions command = const RedisCommandOptions(),
RedisLogger logger = const _NoopRedisLogger(),
}) : _socketOptions = socket,
_commandOptions = command,
_logger = logger;
}) : _socketOptions = socket,
_commandOptions = command,
_logger = logger;
/// The socket options for the Redis server.
final RedisSocketOptions _socketOptions;
@@ -160,10 +160,7 @@ class RedisClient {
/// Authenticate to the Redis server.
/// Equivalent to the `AUTH` command.
/// https://redis.io/commands/auth
Future<void> auth({
required String password,
String username = 'default',
}) {
Future<void> auth({required String password, String username = 'default'}) {
return execute(['AUTH', username, password]);
}
@@ -205,14 +202,11 @@ class RedisClient {
/// Send a command to the Redis server.
Future<dynamic> execute(List<Object?> command) async {
return _runWithRetry(
() async {
final result = await RespCommandsTier0(_client!).execute(command);
if (result.isError) throw RedisException(result.toString());
return result.payload;
},
command: command.join(' '),
);
return _runWithRetry(() async {
final result = await RespCommandsTier0(_client!).execute(command);
if (result.isError) throw RedisException(result.toString());
return result.payload;
}, command: command.join(' '));
}
/// Establish a connection to the Redis server.
@@ -401,10 +395,7 @@ class RedisJson {
/// Returns null if the key does not exist.
/// Equivalent to the `JSON.GET` command.
/// https://redis.io/commands/json.get
Future<dynamic> get({
required String key,
String path = r'$',
}) async {
Future<dynamic> get({required String key, String path = r'$'}) async {
final result = await _client.execute(['JSON.GET', key, path]);
if (result is String) {
final parts = LineSplitter.split(result);
+1 -1
View File
@@ -6,7 +6,7 @@ repository: https://github.com/shorebirdtech/shorebird/tree/main/packages/redis_
topics: [redis, cache, shorebird]
environment:
sdk: ">=3.0.0 <4.0.0"
sdk: ">=3.7.0 <4.0.0"
dependencies:
resp_client: ^1.2.0
@@ -27,32 +27,39 @@ void main() {
});
group('connect', () {
test('authenticates automatically when credentials are provided',
() async {
await expectLater(client.connect(), completes);
await expectLater(client.execute(['PING']), completion(equals('PONG')));
});
test(
'authenticates automatically when credentials are provided',
() async {
await expectLater(client.connect(), completes);
await expectLater(
client.execute(['PING']),
completion(equals('PONG')),
);
},
);
test('throws SocketException when connection times out w/retry',
() async {
final client = RedisClient(
socket: const RedisSocketOptions(
timeout: Duration(microseconds: 1),
retryAttempts: 1,
),
);
await expectLater(
client.connect,
throwsA(
isA<SocketException>().having(
(e) => e.message,
'message',
contains('Connection retry limit exceeded'),
test(
'throws SocketException when connection times out w/retry',
() async {
final client = RedisClient(
socket: const RedisSocketOptions(
timeout: Duration(microseconds: 1),
retryAttempts: 1,
),
),
);
await client.close();
});
);
await expectLater(
client.connect,
throwsA(
isA<SocketException>().having(
(e) => e.message,
'message',
contains('Connection retry limit exceeded'),
),
),
);
await client.close();
},
);
test('throws SocketException after max connection attempts', () async {
final client = RedisClient(
@@ -159,10 +166,7 @@ void main() {
});
test('succeeds when username/password are correct', () async {
await expectLater(
client.auth(password: 'password'),
completes,
);
await expectLater(client.auth(password: 'password'), completes);
});
});
@@ -205,8 +209,7 @@ void main() {
await expectLater(client.get(key: key), completion(isNull));
});
test(
'throws TimeoutException '
test('throws TimeoutException '
'when command timeout is exceeded', () async {
final client = RedisClient(
command: const RedisCommandOptions(timeout: Duration.zero),
@@ -243,9 +246,7 @@ void main() {
'nested': {
'1.0.0+1': {
'android': {
'arch64': {
'url': 'http://example.com',
},
'arch64': {'url': 'http://example.com'},
},
},
},
@@ -268,17 +269,13 @@ void main() {
await expectLater(
client.json.get(key: key, path: r'$.nested'),
completion(
equals(
{
'1.0.0+1': {
'android': {
'arch64': {
'url': 'http://example.com',
},
},
equals({
'1.0.0+1': {
'android': {
'arch64': {'url': 'http://example.com'},
},
},
),
}),
),
);
await expectLater(
@@ -1,3 +1,8 @@
include: package:very_good_analysis/analysis_options.7.0.0.yaml
linter:
rules:
# Disabling in favor of the new Dart 3.7 format.
# https://github.com/VeryGoodOpenSource/very_good_analysis/issues/136
require_trailing_commas: false
analyzer:
exclude: ["example/**"]
+4 -11
View File
@@ -10,9 +10,7 @@ class ScopedRef<T> {
/// {@macro scoped_ref}
ScopedRef(this._create) : _key = Object();
ScopedRef._(T Function() create, Object key)
: _create = create,
_key = key;
ScopedRef._(T Function() create, Object key) : _create = create, _key = key;
final T Function() _create;
final Object _key;
@@ -47,20 +45,15 @@ T read<T>(ScopedRef<T> ref, {T Function()? orElse}) {
final value = (Zone.current[ref._key] as ScopedRef<T>?)?._value;
if (value == null) {
if (orElse != null) return orElse();
throw StateError(
'''
throw StateError('''
read(ScopedRef<$T>) was called in a scope which does not contain a corresponding value for the provided ref.
Did you forget to call: runScoped(() {...}, values: {value})?''',
);
Did you forget to call: runScoped(() {...}, values: {value})?''');
}
return value;
}
/// Runs [body] within a scope which has access to the set of refs in [values].
R runScoped<R>(
R Function() body, {
Set<ScopedRef<dynamic>> values = const {},
}) {
R runScoped<R>(R Function() body, {Set<ScopedRef<dynamic>> values = const {}}) {
return runZoned(
body,
zoneValues: {for (final value in values) value._key: value},
+1 -1
View File
@@ -5,7 +5,7 @@ homepage: https://shorebird.dev
repository: https://github.com/shorebirdtech/shorebird/tree/main/packages/scoped_deps
environment:
sdk: ">=3.0.0 <4.0.0"
sdk: ">=3.7.0 <4.0.0"
dependencies:
meta: ^1.0.0
@@ -25,10 +25,7 @@ void main() {
test('read accesses the value when ref is available', () {
final value = create(() => 42);
runScoped(
() => expect(read(value), equals(42)),
values: {value},
);
runScoped(() => expect(read(value), equals(42)), values: {value});
});
test('value is computed lazily and cached', () {
@@ -40,14 +37,11 @@ void main() {
expect(createCallCount, equals(0));
runScoped(
() {
expect(read(value), equals(42));
expect(read(value), equals(42));
expect(read(value), equals(42));
},
values: {value},
);
runScoped(() {
expect(read(value), equals(42));
expect(read(value), equals(42));
expect(read(value), equals(42));
}, values: {value});
expect(createCallCount, equals(1));
});
@@ -55,17 +49,14 @@ void main() {
test('value can be overridden', () {
final value = create(() => 42);
runScoped(
() {
expect(read(value), equals(42));
runScoped(() {
expect(read(value), equals(42));
runScoped(
() => expect(read(value), equals(0)),
values: {value.overrideWith(() => 0)},
);
},
values: {value},
);
runScoped(
() => expect(read(value), equals(0)),
values: {value.overrideWith(() => 0)},
);
}, values: {value});
});
test('overrides are considered equal', () {
@@ -1,4 +1,9 @@
include: package:very_good_analysis/analysis_options.7.0.0.yaml
linter:
rules:
# Disabling in favor of the new Dart 3.7 format.
# https://github.com/VeryGoodOpenSource/very_good_analysis/issues/136
require_trailing_commas: false
analyzer:
exclude:
- lib/**.g.dart
+6 -7
View File
@@ -37,13 +37,10 @@ Future<void> main(List<String> args) async {
);
// Write the current command to the top of the log file.
currentRunLogFile.writeAsStringSync(
'''
currentRunLogFile.writeAsStringSync('''
Command: shorebird ${args.join(' ')}
''',
mode: FileMode.append,
);
''', mode: FileMode.append);
await IOOverrides.runZoned(
() async => _flushThenExit(
@@ -107,6 +104,8 @@ Command: shorebird ${args.join(' ')}
/// exited already. This is useful to prevent Future chains from proceeding
/// after you've decided to exit.
Future<void> _flushThenExit(int status) {
return Future.wait<void>([stdout.close(), stderr.close()])
.then<void>((_) => exit(status));
return Future.wait<void>([
stdout.close(),
stderr.close(),
]).then<void>((_) => exit(status));
}
@@ -19,13 +19,7 @@ import 'package:uuid/uuid.dart';
R runWithOverrides<R>(R Function() body) {
return runScoped(
body,
values: {
authRef,
httpClientRef,
loggerRef,
platformRef,
shorebirdEnvRef,
},
values: {authRef, httpClientRef, loggerRef, platformRef, shorebirdEnvRef},
);
}
@@ -66,10 +60,7 @@ void main() {
test('--version', () {
final result = runCommand('shorebird --version', workingDirectory: '.');
expect(result.stderr, isEmpty);
expect(
result.stdout,
stringContainsInOrder(['Engine', 'revision']),
);
expect(result.stdout, stringContainsInOrder(['Engine', 'revision']));
expect(result.exitCode, equals(0));
});
@@ -90,8 +81,9 @@ void main() {
final uuid = const Uuid().v4().replaceAll('-', '_');
final testAppName = 'test_app_$uuid';
final tempDir = Directory.systemTemp.createTempSync();
final subDirWithSpace =
Directory(p.join(tempDir.path, 'flutter directory'))..createSync();
final subDirWithSpace = Directory(
p.join(tempDir.path, 'flutter directory'),
)..createSync();
var cwd = subDirWithSpace.path;
// Create the default flutter counter app
@@ -126,21 +118,13 @@ void main() {
completion(
contains(
isA<AppMetadata>()
.having(
(a) => a.appId,
'appId',
shorebirdYaml.appId,
)
.having((a) => a.appId, 'appId', shorebirdYaml.appId)
.having(
(a) => a.latestReleaseVersion,
'latestReleaseVersion',
null,
)
.having(
(a) => a.latestPatchNumber,
'latestPatchNumber',
null,
),
.having((a) => a.latestPatchNumber, 'latestPatchNumber', null),
),
),
);
@@ -175,21 +159,13 @@ void main() {
completion(
contains(
isA<AppMetadata>()
.having(
(a) => a.appId,
'appId',
shorebirdYaml.appId,
)
.having((a) => a.appId, 'appId', shorebirdYaml.appId)
.having(
(a) => a.latestReleaseVersion,
'latestReleaseVersion',
releaseVersion,
)
.having(
(a) => a.latestPatchNumber,
'latestPatchNumber',
null,
),
.having((a) => a.latestPatchNumber, 'latestPatchNumber', null),
),
),
);
@@ -221,21 +197,13 @@ void main() {
completion(
contains(
isA<AppMetadata>()
.having(
(a) => a.appId,
'appId',
shorebirdYaml.appId,
)
.having((a) => a.appId, 'appId', shorebirdYaml.appId)
.having(
(a) => a.latestReleaseVersion,
'latestReleaseVersion',
'1.0.0+1',
)
.having(
(a) => a.latestPatchNumber,
'latestPatchNumber',
1,
),
.having((a) => a.latestPatchNumber, 'latestPatchNumber', 1),
),
),
);
@@ -286,18 +254,16 @@ Future<bool> isPatchAvailable({
required String channel,
}) async {
final response = await http.post(
Uri.parse(Platform.environment['SHOREBIRD_HOSTED_URL']!).replace(
path: '/api/v1/patches/check',
),
body: jsonEncode(
{
'release_version': releaseVersion,
'platform': platform,
'arch': arch,
'app_id': appId,
'channel': channel,
},
),
Uri.parse(
Platform.environment['SHOREBIRD_HOSTED_URL']!,
).replace(path: '/api/v1/patches/check'),
body: jsonEncode({
'release_version': releaseVersion,
'platform': platform,
'arch': arch,
'app_id': appId,
'channel': channel,
}),
);
if (response.statusCode != HttpStatus.ok) {
throw Exception('Patch Check Failure: ${response.statusCode}');
@@ -69,9 +69,10 @@ class AndroidSdk {
/// Returns the default path to the Android SDK if a home directory is defined
/// and we're on a recognized platform, or null otherwise.
String? _defaultAndroidSdkPath() {
final home = platform.isWindows
? platform.environment['USERPROFILE']
: platform.environment['HOME'];
final home =
platform.isWindows
? platform.environment['USERPROFILE']
: platform.environment['HOME'];
if (home == null) {
return null;
}
@@ -52,8 +52,9 @@ class AndroidStudio {
final directoryName = p.basename(directory.path);
// Because we've already performed this match above, we can safely
// assume that this will match.
final versionMatch =
androidStudioRegex.firstMatch(directoryName)!.group(1);
final versionMatch = androidStudioRegex
.firstMatch(directoryName)!
.group(1);
final version = tryParseVersion(versionMatch!, strict: false)!;
final homeFile = File(p.join(directory.path, '.home'));
@@ -43,9 +43,7 @@ class AppleArchiveDiffer extends ArchiveDiffer {
);
/// The regex pattern for identifying executable files within a macOS .app.
static final RegExp macosAppRegex = RegExp(
r'^Contents/MacOS/.+$',
);
static final RegExp macosAppRegex = RegExp(r'^Contents/MacOS/.+$');
/// Files that have been added, removed, or that have changed between the
/// archives at the two provided paths. This method will also unsign mach-o
@@ -101,8 +99,9 @@ class AppleArchiveDiffer extends ArchiveDiffer {
.where((file) => file.isFile)
.where(
(file) =>
_binaryFilePatterns
.any((pattern) => pattern.hasMatch(file.name)) ||
_binaryFilePatterns.any(
(pattern) => pattern.hasMatch(file.name),
) ||
xcFrameworkAppRegex.hasMatch(file.name),
)
.toList();
@@ -60,26 +60,26 @@ abstract class ArchiveDiffer {
/// The subset of [fileSetDiff] that contains only changes that result from
/// edited assets.
FileSetDiff assetsFileSetDiff(FileSetDiff fileSetDiff) => FileSetDiff(
addedPaths: fileSetDiff.addedPaths.where(isAssetFilePath).toSet(),
removedPaths: fileSetDiff.removedPaths.where(isAssetFilePath).toSet(),
changedPaths: fileSetDiff.changedPaths.where(isAssetFilePath).toSet(),
);
addedPaths: fileSetDiff.addedPaths.where(isAssetFilePath).toSet(),
removedPaths: fileSetDiff.removedPaths.where(isAssetFilePath).toSet(),
changedPaths: fileSetDiff.changedPaths.where(isAssetFilePath).toSet(),
);
/// The subset of [fileSetDiff] that contains only changes that result from
/// edited Dart code.
FileSetDiff dartFileSetDiff(FileSetDiff fileSetDiff) => FileSetDiff(
addedPaths: fileSetDiff.addedPaths.where(isDartFilePath).toSet(),
removedPaths: fileSetDiff.removedPaths.where(isDartFilePath).toSet(),
changedPaths: fileSetDiff.changedPaths.where(isDartFilePath).toSet(),
);
addedPaths: fileSetDiff.addedPaths.where(isDartFilePath).toSet(),
removedPaths: fileSetDiff.removedPaths.where(isDartFilePath).toSet(),
changedPaths: fileSetDiff.changedPaths.where(isDartFilePath).toSet(),
);
/// The subset of [fileSetDiff] that contains only changes that result from
/// edited native code.
FileSetDiff nativeFileSetDiff(FileSetDiff fileSetDiff) => FileSetDiff(
addedPaths: fileSetDiff.addedPaths.where(isNativeFilePath).toSet(),
removedPaths: fileSetDiff.removedPaths.where(isNativeFilePath).toSet(),
changedPaths: fileSetDiff.changedPaths.where(isNativeFilePath).toSet(),
);
addedPaths: fileSetDiff.addedPaths.where(isNativeFilePath).toSet(),
removedPaths: fileSetDiff.removedPaths.where(isNativeFilePath).toSet(),
changedPaths: fileSetDiff.changedPaths.where(isNativeFilePath).toSet(),
);
/// Files that have been added, removed, or that have changed between the
/// archives at the two provided paths.
@@ -23,18 +23,16 @@ class FileSetDiff extends Equatable {
return FileSetDiff(
addedPaths: newPaths.difference(oldPaths),
removedPaths: oldPaths.difference(newPaths),
changedPaths: oldPaths
.intersection(newPaths)
.where((name) => oldPathHashes[name] != newPathHashes[name])
.toSet(),
changedPaths:
oldPaths
.intersection(newPaths)
.where((name) => oldPathHashes[name] != newPathHashes[name])
.toSet(),
);
}
/// Creates an empty FileSetDiff.
FileSetDiff.empty()
: addedPaths = {},
removedPaths = {},
changedPaths = {};
FileSetDiff.empty() : addedPaths = {}, removedPaths = {}, changedPaths = {};
/// File paths that were added.
final Set<String> addedPaths;
@@ -56,13 +54,13 @@ class FileSetDiff extends Equatable {
/// A printable string representation of this [FileSetDiff].
String get prettyString => [
if (addedPaths.isNotEmpty)
_prettyFileSetString(title: 'Added files', paths: addedPaths),
if (changedPaths.isNotEmpty)
_prettyFileSetString(title: 'Changed files', paths: changedPaths),
if (removedPaths.isNotEmpty)
_prettyFileSetString(title: 'Removed files', paths: removedPaths),
].join('\n');
if (addedPaths.isNotEmpty)
_prettyFileSetString(title: 'Added files', paths: addedPaths),
if (changedPaths.isNotEmpty)
_prettyFileSetString(title: 'Changed files', paths: changedPaths),
if (removedPaths.isNotEmpty)
_prettyFileSetString(title: 'Removed files', paths: removedPaths),
].join('\n');
static String _prettyFileSetString({
required String title,
@@ -8,9 +8,11 @@ import 'package:propertylistserialization/propertylistserialization.dart';
class Plist {
/// Creates a new [Plist] from the contents of the provided [file].
Plist({required File file}) {
properties = PropertyListSerialization.propertyListWithString(
file.readAsStringSync(),
) as Map<String, Object>;
properties =
PropertyListSerialization.propertyListWithString(
file.readAsStringSync(),
)
as Map<String, Object>;
}
/// This key is a user-visible string for the version of the bundle. The
@@ -15,11 +15,13 @@ class ArtifactBuildException implements Exception {
List<String>? stdout,
List<String>? stderr,
String? fixRecommendation,
}) : stdout = stdout ?? [],
stderr = stderr ?? [] {
flutterError =
_errorMessageFromOutput(this.stdout + this.stderr).join('\n');
this.fixRecommendation = fixRecommendation ??
}) : stdout = stdout ?? [],
stderr = stderr ?? [] {
flutterError = _errorMessageFromOutput(
this.stdout + this.stderr,
).join('\n');
this.fixRecommendation =
fixRecommendation ??
_recommendationFromOutput(this.stdout + this.stderr);
}
@@ -46,9 +46,7 @@ class IpaBuildResult {
/// {@endtemplate}
class IosFrameworkBuildResult {
/// {@macro ios_framework_build_result}
IosFrameworkBuildResult({
required this.kernelFile,
});
IosFrameworkBuildResult({required this.kernelFile});
/// The app.dill file produced by this invocation of `flutter build ipa`.
final File kernelFile;
@@ -126,20 +124,21 @@ class ArtifactBuilder {
.transform(utf8.decoder)
.transform(const LineSplitter())
.listen((line) {
stdoutLines.add(line);
if (buildProgress == null) {
return;
}
final captured = gradleTaskRegex.firstMatch(line)?.group(1);
if (captured != null) {
buildProgress.updateDetailMessage(captured);
}
});
stdoutLines.add(line);
if (buildProgress == null) {
return;
}
final captured = gradleTaskRegex.firstMatch(line)?.group(1);
if (captured != null) {
buildProgress.updateDetailMessage(captured);
}
});
final stderrLines = await buildProcess.stderr
.transform(utf8.decoder)
.transform(const LineSplitter())
.toList();
final stderrLines =
await buildProcess.stderr
.transform(utf8.decoder)
.transform(const LineSplitter())
.toList();
final exitCode = await buildProcess.exitCode;
if (exitCode != ExitCode.success.code) {
throw ArtifactBuildException(
@@ -257,11 +256,7 @@ class ArtifactBuilder {
...args,
];
final result = await process.run(
executable,
arguments,
runInShell: true,
);
final result = await process.run(executable, arguments, runInShell: true);
if (result.exitCode != ExitCode.success.code) {
throw ArtifactBuildException.fromProcessResult(
@@ -301,14 +296,15 @@ class ArtifactBuilder {
.transform(utf8.decoder)
.transform(const LineSplitter())
.listen((line) {
logger.detail(line);
stdoutLines.add(line);
});
logger.detail(line);
stdoutLines.add(line);
});
final stderrLines = await buildProcess.stderr
.transform(utf8.decoder)
.transform(const LineSplitter())
.toList();
final stderrLines =
await buildProcess.stderr
.transform(utf8.decoder)
.transform(const LineSplitter())
.toList();
final exitCode = await buildProcess.exitCode;
if (exitCode != ExitCode.success.code) {
throw ArtifactBuildException(
@@ -364,22 +360,23 @@ class ArtifactBuilder {
.transform(utf8.decoder)
.transform(const LineSplitter())
.listen((line) {
stdoutLines.add(line);
if (buildProgress == null) {
return;
}
stdoutLines.add(line);
if (buildProgress == null) {
return;
}
// TODO(bryanoltman): update the progress message for macOS builds.
// final update = _progressUpdateFromMacosBuildLog(line);
// if (update != null) {
// buildProgress.updateDetailMessage(update);
// }
});
// TODO(bryanoltman): update the progress message for macOS builds.
// final update = _progressUpdateFromMacosBuildLog(line);
// if (update != null) {
// buildProgress.updateDetailMessage(update);
// }
});
final stderrLines = await buildProcess.stderr
.transform(utf8.decoder)
.transform(const LineSplitter())
.toList();
final stderrLines =
await buildProcess.stderr
.transform(utf8.decoder)
.transform(const LineSplitter())
.toList();
final stdout = stdoutLines.join('\n');
final exitCode = await buildProcess.exitCode;
if (exitCode != ExitCode.success.code) {
@@ -439,21 +436,22 @@ class ArtifactBuilder {
.transform(utf8.decoder)
.transform(const LineSplitter())
.listen((line) {
stdoutLines.add(line);
if (buildProgress == null) {
return;
}
stdoutLines.add(line);
if (buildProgress == null) {
return;
}
final update = _progressUpdateFromIpaBuildLog(line);
if (update != null) {
buildProgress.updateDetailMessage(update);
}
});
final update = _progressUpdateFromIpaBuildLog(line);
if (update != null) {
buildProgress.updateDetailMessage(update);
}
});
final stderrLines = await buildProcess.stderr
.transform(utf8.decoder)
.transform(const LineSplitter())
.toList();
final stderrLines =
await buildProcess.stderr
.transform(utf8.decoder)
.transform(const LineSplitter())
.toList();
final stderr = stderrLines.join('\n');
final stdout = stdoutLines.join('\n');
final exitCode = await buildProcess.exitCode;
@@ -510,11 +508,7 @@ class ArtifactBuilder {
...args,
];
final result = await process.run(
executable,
arguments,
runInShell: true,
);
final result = await process.run(executable, arguments, runInShell: true);
if (result.exitCode != ExitCode.success.code) {
throw ArtifactBuildException('Failed to build: ${result.stderr}');
@@ -569,13 +563,11 @@ class ArtifactBuilder {
);
if (result.exitCode != ExitCode.success.code) {
logger.warn(
'''
logger.warn('''
Build was successful, but `flutter pub get` failed to run after the build completed. You may see unexpected behavior in VS Code.
Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCodeUrl.toLink()}.
''',
);
''');
}
}
@@ -620,12 +612,7 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod
}) async {
await _runShorebirdBuildCommand(() async {
const executable = 'flutter';
final arguments = [
'build',
'windows',
'--release',
...args,
];
final arguments = ['build', 'windows', '--release', ...args];
final buildProcess = await process.start(
executable,
@@ -639,15 +626,16 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod
.transform(utf8.decoder)
.transform(const LineSplitter())
.listen((line) {
logger.detail(line);
stdoutLines.add(line);
// TODO(bryanoltman): update build progress
});
logger.detail(line);
stdoutLines.add(line);
// TODO(bryanoltman): update build progress
});
final stderrLines = await buildProcess.stderr
.transform(utf8.decoder)
.transform(const LineSplitter())
.toList();
final stderrLines =
await buildProcess.stderr
.transform(utf8.decoder)
.transform(const LineSplitter())
.toList();
final exitCode = await buildProcess.exitCode;
if (exitCode != ExitCode.success.code) {
throw ArtifactBuildException(
@@ -668,14 +656,17 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod
// xcodebuild -list is a command run early in `flutter build ipa` to read
// build settings and schemes. Most users aren't familiar with this command,
// so we translate it to "Collecting schemes" below.
final collectingSchemesRegex =
RegExp(r'\[.*\] executing:.*xcrun xcodebuild -list$');
final collectingSchemesRegex = RegExp(
r'\[.*\] executing:.*xcrun xcodebuild -list$',
);
final archivingRegex = RegExp(r'^\[.*\] (Archiving .+$)');
final runningXcodeBuildRegex = RegExp(r'^\[.*\] (Running Xcode build).*$');
final compilingLinkingSigningRegex =
RegExp(r'^\[.*\]\s+└─(Compiling, linking and signing).*$');
final buildingAppStoreIpaRegex =
RegExp(r'^\[.*\] (Building App Store IPA).*$');
final compilingLinkingSigningRegex = RegExp(
r'^\[.*\]\s+└─(Compiling, linking and signing).*$',
);
final buildingAppStoreIpaRegex = RegExp(
r'^\[.*\] (Building App Store IPA).*$',
);
final builtAppStoreIpaRegex = RegExp(r'^\[.*\] ✓ (Built IPA to \S+).*$');
final regexes = [
@@ -706,7 +697,9 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod
/// command, finds the path to the app.dill file that was built.
@visibleForTesting
String? findAppDill({required String stdout}) {
final appDillLine = stdout.split('\n').firstWhereOrNull(
final appDillLine = stdout
.split('\n')
.firstWhereOrNull(
(l) => l.contains('gen_snapshot') && l.endsWith('app.dill'),
);
@@ -77,14 +77,8 @@ class ArtifactManager {
/// temporary directory if not.
///
/// Returns the downloaded [File].
Future<File> downloadFile(
Uri uri, {
String? outputPath,
}) async {
final download = await startFileDownload(
uri,
outputPath: outputPath,
);
Future<File> downloadFile(Uri uri, {String? outputPath}) async {
final download = await startFileDownload(uri, outputPath: outputPath);
return download.file;
}
@@ -94,10 +88,7 @@ class ArtifactManager {
/// Returns a [FileDownload] object containing the [Future<File>] and a
/// [Stream] of download progress updates.
@visibleForTesting
Future<FileDownload> startFileDownload(
Uri uri, {
String? outputPath,
}) async {
Future<FileDownload> startFileDownload(Uri uri, {String? outputPath}) async {
final request = http.Request('GET', uri);
final response = await httpClient.send(request);
@@ -167,10 +158,10 @@ class ArtifactManager {
final subscription = download.progress
.throttle(throttleDuration, trailing: true)
.listen((progress) {
downloadProgress.update(
'$message (${(progress * 100).toStringAsFixed(0)}%)',
);
});
downloadProgress.update(
'$message (${(progress * 100).toStringAsFixed(0)}%)',
);
});
artifactFile = await download.file;
await subscription.cancel();
@@ -235,30 +226,15 @@ class ArtifactManager {
//
// See https://github.com/shorebirdtech/shorebird/issues/1798
final strippedSymbolsDir = Directory(
p.join(
releasePath,
stripReleaseDebugSymbolsDirName,
),
p.join(releasePath, stripReleaseDebugSymbolsDirName),
);
final Directory archsDirectory;
if (strippedSymbolsDir.existsSync()) {
archsDirectory = Directory(
p.join(
strippedSymbolsDir.path,
'out',
'lib',
),
);
archsDirectory = Directory(p.join(strippedSymbolsDir.path, 'out', 'lib'));
} else {
// If the new path doesn't exist, fallback to the old path.
archsDirectory = Directory(
p.join(
releasePath,
'out',
'lib',
),
);
archsDirectory = Directory(p.join(releasePath, 'out', 'lib'));
}
return archsDirectory.existsSync() ? archsDirectory : null;
@@ -301,12 +277,7 @@ class ArtifactManager {
Directory? getXcarchiveDirectory() {
final projectRoot = shorebirdEnv.getShorebirdProjectRoot()!;
final archiveDirectory = Directory(
p.join(
projectRoot.path,
'build',
'ios',
'archive',
),
p.join(projectRoot.path, 'build', 'ios', 'archive'),
);
if (!archiveDirectory.existsSync()) return null;
@@ -330,11 +301,7 @@ class ArtifactManager {
/// traditionally named `Runner.app`, but can now be renamed.
Directory? getIosAppDirectory({required Directory xcarchiveDirectory}) {
final applicationsDirectory = Directory(
p.join(
xcarchiveDirectory.path,
'Products',
'Applications',
),
p.join(xcarchiveDirectory.path, 'Products', 'Applications'),
);
if (!applicationsDirectory.existsSync()) {
@@ -351,14 +318,7 @@ class ArtifactManager {
Directory get linuxBundleDirectory {
final projectRoot = shorebirdEnv.getShorebirdProjectRoot()!;
return Directory(
p.join(
projectRoot.path,
'build',
'linux',
'x64',
'release',
'bundle',
),
p.join(projectRoot.path, 'build', 'linux', 'x64', 'release', 'bundle'),
);
}
@@ -366,14 +326,7 @@ class ArtifactManager {
Directory getWindowsReleaseDirectory() {
final projectRoot = shorebirdEnv.getShorebirdProjectRoot()!;
return Directory(
p.join(
projectRoot.path,
'build',
'windows',
'x64',
'runner',
'Release',
),
p.join(projectRoot.path, 'build', 'windows', 'x64', 'runner', 'Release'),
);
}
@@ -386,12 +339,7 @@ class ArtifactManager {
File? getIpa() {
final projectRoot = shorebirdEnv.getShorebirdProjectRoot()!;
final ipaBuildDirectory = Directory(
p.join(
projectRoot.path,
'build',
'ios',
'ipa',
),
p.join(projectRoot.path, 'build', 'ios', 'ipa'),
);
if (!ipaBuildDirectory.existsSync()) {
@@ -475,13 +423,7 @@ class ArtifactManager {
Directory getAppXcframeworkDirectory() {
final projectRoot = shorebirdEnv.getShorebirdProjectRoot()!;
return Directory(
p.join(
projectRoot.path,
'build',
'ios',
'framework',
'Release',
),
p.join(projectRoot.path, 'build', 'ios', 'framework', 'Release'),
);
}
}
+75 -76
View File
@@ -41,26 +41,27 @@ const microsoftJwtIssuerPrefix = 'https://login.microsoftonline.com/';
const shorebirdTokenEnvVar = 'SHOREBIRD_TOKEN';
/// Callback for obtaining access credentials.
typedef ObtainAccessCredentials = Future<oauth2.AccessCredentials> Function(
oauth2.ClientId clientId,
List<String> scopes,
http.Client client,
void Function(String) userPrompt, {
oauth2.AuthEndpoints authEndpoints,
});
typedef ObtainAccessCredentials =
Future<oauth2.AccessCredentials> Function(
oauth2.ClientId clientId,
List<String> scopes,
http.Client client,
void Function(String) userPrompt, {
oauth2.AuthEndpoints authEndpoints,
});
/// Callback for refreshing access credentials.
typedef RefreshCredentials = Future<oauth2.AccessCredentials> Function(
oauth2.ClientId clientId,
oauth2.AccessCredentials credentials,
http.Client client, {
oauth2.AuthEndpoints authEndpoints,
});
typedef RefreshCredentials =
Future<oauth2.AccessCredentials> Function(
oauth2.ClientId clientId,
oauth2.AccessCredentials credentials,
http.Client client, {
oauth2.AuthEndpoints authEndpoints,
});
/// Callback when credentials are refreshed.
typedef OnRefreshCredentials = void Function(
oauth2.AccessCredentials credentials,
);
typedef OnRefreshCredentials =
void Function(oauth2.AccessCredentials credentials);
/// A client that automatically refreshes OAuth 2.0 credentials.
class AuthenticatedClient extends http.BaseClient {
@@ -72,11 +73,11 @@ class AuthenticatedClient extends http.BaseClient {
OnRefreshCredentials? onRefreshCredentials,
RefreshCredentials refreshCredentials = oauth2.refreshCredentials,
}) : this._(
httpClient: httpClient,
onRefreshCredentials: onRefreshCredentials,
credentials: credentials,
refreshCredentials: refreshCredentials,
);
httpClient: httpClient,
onRefreshCredentials: onRefreshCredentials,
credentials: credentials,
refreshCredentials: refreshCredentials,
);
/// Creates a new [AuthenticatedClient] with the given [httpClient] and
/// [token].
@@ -86,11 +87,11 @@ class AuthenticatedClient extends http.BaseClient {
OnRefreshCredentials? onRefreshCredentials,
RefreshCredentials refreshCredentials = oauth2.refreshCredentials,
}) : this._(
httpClient: httpClient,
token: token,
onRefreshCredentials: onRefreshCredentials,
refreshCredentials: refreshCredentials,
);
httpClient: httpClient,
token: token,
onRefreshCredentials: onRefreshCredentials,
refreshCredentials: refreshCredentials,
);
AuthenticatedClient._({
required http.Client httpClient,
@@ -98,11 +99,11 @@ class AuthenticatedClient extends http.BaseClient {
oauth2.AccessCredentials? credentials,
CiToken? token,
RefreshCredentials refreshCredentials = oauth2.refreshCredentials,
}) : _baseClient = httpClient,
_credentials = credentials,
_onRefreshCredentials = onRefreshCredentials,
_refreshCredentials = refreshCredentials,
_token = token;
}) : _baseClient = httpClient,
_credentials = credentials,
_onRefreshCredentials = onRefreshCredentials,
_refreshCredentials = refreshCredentials,
_token = token;
final http.Client _baseClient;
final OnRefreshCredentials? _onRefreshCredentials;
@@ -116,17 +117,18 @@ class AuthenticatedClient extends http.BaseClient {
if (credentials == null) {
final token = _token!;
credentials = _credentials = await _tryRefreshCredentials(
token.authProvider.clientId,
oauth2.AccessCredentials(
// This isn't relevant for a refresh operation.
AccessToken('Bearer', '', DateTime.timestamp()),
token.refreshToken,
token.authProvider.scopes,
),
_baseClient,
authEndpoints: token.authProvider.authEndpoints,
);
credentials =
_credentials = await _tryRefreshCredentials(
token.authProvider.clientId,
oauth2.AccessCredentials(
// This isn't relevant for a refresh operation.
AccessToken('Bearer', '', DateTime.timestamp()),
token.refreshToken,
token.authProvider.scopes,
),
_baseClient,
authEndpoints: token.authProvider.authEndpoints,
);
_onRefreshCredentials?.call(credentials);
}
@@ -134,12 +136,13 @@ class AuthenticatedClient extends http.BaseClient {
final jwt = Jwt.parse(credentials.idToken!);
final authProvider = jwt.authProvider;
credentials = _credentials = await _tryRefreshCredentials(
authProvider.clientId,
credentials,
_baseClient,
authEndpoints: authProvider.authEndpoints,
);
credentials =
_credentials = await _tryRefreshCredentials(
authProvider.clientId,
credentials,
_baseClient,
authEndpoints: authProvider.authEndpoints,
);
_onRefreshCredentials?.call(credentials);
}
@@ -183,12 +186,13 @@ class Auth {
String? credentialsDir,
ObtainAccessCredentials? obtainAccessCredentials,
CodePushClientBuilder? buildCodePushClient,
}) : _httpClient = httpClient ?? _defaultHttpClient,
_credentialsDir =
credentialsDir ?? applicationConfigHome(executableName),
_obtainAccessCredentials = obtainAccessCredentials ??
oauth2.obtainAccessCredentialsViaUserConsent,
_buildCodePushClient = buildCodePushClient ?? CodePushClient.new {
}) : _httpClient = httpClient ?? _defaultHttpClient,
_credentialsDir =
credentialsDir ?? applicationConfigHome(executableName),
_obtainAccessCredentials =
obtainAccessCredentials ??
oauth2.obtainAccessCredentialsViaUserConsent,
_buildCodePushClient = buildCodePushClient ?? CodePushClient.new {
_loadCredentials();
}
@@ -212,10 +216,7 @@ class Auth {
}
if (_token != null) {
return AuthenticatedClient.token(
token: _token!,
httpClient: _httpClient,
);
return AuthenticatedClient.token(token: _token!, httpClient: _httpClient);
}
return AuthenticatedClient.credentials(
@@ -321,12 +322,10 @@ class Auth {
_token = CiToken.fromBase64(envToken.trim());
} on FormatException catch (e) {
logger
..err(
'''
..err('''
Failed to parse CI token from environment. This likely means that your CI token is incorrectly formatted.
Please regenerate using `shorebird login:ci`, update the $shorebirdTokenEnvVar environment variable, and try again.''',
)
Please regenerate using `shorebird login:ci`, update the $shorebirdTokenEnvVar environment variable, and try again.''')
..detail(e.toString());
rethrow;
}
@@ -430,9 +429,9 @@ extension OauthAuthProvider on Jwt {
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(),
};
(AuthProvider.google) => const oauth2.GoogleAuthEndpoints(),
(AuthProvider.microsoft) => MicrosoftAuthEndpoints(),
};
/// The OAuth 2.0 client ID for the provider.
oauth2.ClientId get clientId {
@@ -464,15 +463,15 @@ extension OauthValues on AuthProvider {
/// The OAuth 2.0 scopes for the provider.
List<String> get scopes => switch (this) {
(AuthProvider.google) => [
'openid',
'https://www.googleapis.com/auth/userinfo.email',
],
(AuthProvider.microsoft) => [
'openid',
'email',
// Required to get refresh tokens.
'offline_access',
],
};
(AuthProvider.google) => [
'openid',
'https://www.googleapis.com/auth/userinfo.email',
],
(AuthProvider.microsoft) => [
'openid',
'email',
// Required to get refresh tokens.
'offline_access',
],
};
}
@@ -9,26 +9,28 @@ part of 'ci_token.dart';
// **************************************************************************
CiToken _$CiTokenFromJson(Map<String, dynamic> json) => $checkedCreate(
'CiToken',
json,
($checkedConvert) {
final val = CiToken(
refreshToken: $checkedConvert('refresh_token', (v) => v as String),
authProvider: $checkedConvert(
'auth_provider', (v) => $enumDecode(_$AuthProviderEnumMap, v)),
);
return val;
},
fieldKeyMap: const {
'refreshToken': 'refresh_token',
'authProvider': 'auth_provider'
},
'CiToken',
json,
($checkedConvert) {
final val = CiToken(
refreshToken: $checkedConvert('refresh_token', (v) => v as String),
authProvider: $checkedConvert(
'auth_provider',
(v) => $enumDecode(_$AuthProviderEnumMap, v),
),
);
return val;
},
fieldKeyMap: const {
'refreshToken': 'refresh_token',
'authProvider': 'auth_provider',
},
);
Map<String, dynamic> _$CiTokenToJson(CiToken instance) => <String, dynamic>{
'refresh_token': instance.refreshToken,
'auth_provider': _$AuthProviderEnumMap[instance.authProvider]!,
};
'refresh_token': instance.refreshToken,
'auth_provider': _$AuthProviderEnumMap[instance.authProvider]!,
};
const _$AuthProviderEnumMap = {
AuthProvider.google: 'google',
+18 -26
View File
@@ -98,23 +98,17 @@ class Cache {
/// The Shorebird cache directory.
static Directory get shorebirdCacheDirectory {
return Directory(
p.join(shorebirdEnv.shorebirdRoot.path, 'bin', 'cache'),
);
return Directory(p.join(shorebirdEnv.shorebirdRoot.path, 'bin', 'cache'));
}
/// The Shorebird cached previews directory.
static Directory get shorebirdPreviewsDirectory {
return Directory(
p.join(shorebirdCacheDirectory.path, 'previews'),
);
return Directory(p.join(shorebirdCacheDirectory.path, 'previews'));
}
/// The Shorebird cached artifacts directory.
static Directory get shorebirdArtifactsDirectory {
return Directory(
p.join(shorebirdCacheDirectory.path, 'artifacts'),
);
return Directory(p.join(shorebirdCacheDirectory.path, 'artifacts'));
}
final List<CachedArtifact> _artifacts = [];
@@ -208,12 +202,10 @@ abstract class CachedArtifact {
try {
response = await httpClient.send(request);
} catch (error) {
throw CacheUpdateFailure(
'''
throw CacheUpdateFailure('''
Failed to download $fileName: $error
If you're behind a firewall/proxy, please, make sure shorebird_cli is
allowed to access $storageUrl.''',
);
allowed to access $storageUrl.''');
}
if (response.statusCode != HttpStatus.ok) {
@@ -304,12 +296,12 @@ class AotToolsArtifact extends CachedArtifact {
@override
File get file => File(
p.join(
cache.getArtifactDirectory(fileName).path,
shorebirdEnv.shorebirdEngineRevision,
fileName,
),
);
p.join(
cache.getArtifactDirectory(fileName).path,
shorebirdEnv.shorebirdEngineRevision,
fileName,
),
);
@override
String get storageUrl =>
@@ -385,11 +377,11 @@ class BundleToolArtifact extends CachedArtifact {
@override
String? get checksum =>
// SHA-256 checksum of the bundletool.jar file.
// When updating the bundletool version, be sure to update this checksum.
// This can be done by running the following command:
// ```shell
// shasum --algorithm 256 /path/to/file
// ```
'''45881ead13388872d82c4255b195488b7fc33f2cac5a9a977b0afc5e92367592''';
// SHA-256 checksum of the bundletool.jar file.
// When updating the bundletool version, be sure to update this checksum.
// This can be done by running the following command:
// ```shell
// shasum --algorithm 256 /path/to/file
// ```
'''45881ead13388872d82c4255b195488b7fc33f2cac5a9a977b0afc5e92367592''';
}
@@ -84,10 +84,7 @@ class CodePushClientWrapper {
final CodePushClient codePushClient;
Future<App> createApp({
required int organizationId,
String? appName,
}) async {
Future<App> createApp({required int organizationId, String? appName}) async {
late final String displayName;
if (appName == null) {
final defaultAppName = shorebirdEnv.getPubspecYaml()?.name;
@@ -132,11 +129,9 @@ class CodePushClientWrapper {
Future<AppMetadata> getApp({required String appId}) async {
final app = await maybeGetApp(appId: appId);
if (app == null) {
logger.err(
'''
logger.err('''
Could not find app with id: "$appId".
This app may not exist or you may not have permission to view it.''',
);
This app may not exist or you may not have permission to view it.''');
throw ProcessExit(ExitCode.software.code);
}
@@ -191,10 +186,7 @@ This app may not exist or you may not have permission to view it.''',
required ReleasePlatform platform,
}) {
if (release.platformStatuses[platform] == ReleaseStatus.active) {
final uri = ShorebirdWebConsole.appReleaseUri(
release.appId,
release.id,
);
final uri = ShorebirdWebConsole.appReleaseUri(release.appId, release.id);
logger.err(
'''
It looks like you have an existing ${platform.name} release for version ${lightCyan.wrap(release.version)}.
@@ -216,14 +208,12 @@ You can manage this release in the ${link(uri: uri, message: 'Shorebird Console'
);
if (release == null) {
logger.err(
'''
logger.err('''
Release not found: "$releaseVersion"
Patches can only be published for existing releases.
Please create a release using "shorebird release" and try again.
''',
);
''');
throw ProcessExit(ExitCode.software.code);
}
@@ -478,11 +468,9 @@ Looked in:
);
} on CodePushConflictException catch (_) {
// Newlines are due to how logger.info interacts with logger.progress.
logger.info(
'''
logger.info('''
${arch.arch} artifact already exists, continuing...''',
);
${arch.arch} artifact already exists, continuing...''');
} catch (error) {
_handleErrorAndExit(
error,
@@ -506,11 +494,9 @@ ${arch.arch} artifact already exists, continuing...''',
);
} on CodePushConflictException catch (_) {
// Newlines are due to how logger.info interacts with logger.progress.
logger.info(
'''
logger.info('''
aab artifact already exists, continuing...''',
);
aab artifact already exists, continuing...''');
} catch (error) {
_handleErrorAndExit(
error,
@@ -545,11 +531,9 @@ aab artifact already exists, continuing...''',
);
} on CodePushConflictException catch (_) {
// Newlines are due to how logger.info interacts with logger.progress.
logger.info(
'''
logger.info('''
Windows release (exe) artifact already exists, continuing...''',
);
Windows release (exe) artifact already exists, continuing...''');
} catch (error) {
_handleErrorAndExit(
error,
@@ -594,11 +578,9 @@ Windows release (exe) artifact already exists, continuing...''',
);
} on CodePushConflictException catch (_) {
// Newlines are due to how logger.info interacts with logger.progress.
logger.info(
'''
logger.info('''
${arch.arch} artifact already exists, continuing...''',
);
${arch.arch} artifact already exists, continuing...''');
} catch (error) {
_handleErrorAndExit(
error,
@@ -622,11 +604,9 @@ ${arch.arch} artifact already exists, continuing...''',
);
} on CodePushConflictException catch (_) {
// Newlines are due to how logger.info interacts with logger.progress.
logger.info(
'''
logger.info('''
aar artifact already exists, continuing...''',
);
aar artifact already exists, continuing...''');
} catch (error) {
_handleErrorAndExit(
error,
@@ -643,8 +623,9 @@ aar artifact already exists, continuing...''',
Future<Directory> _thinXcarchive({required String xcarchivePath}) async {
final xcarchiveDirectoryName = p.basename(xcarchivePath);
final tempDir = Directory.systemTemp.createTempSync();
final thinnedArchiveDirectory =
Directory(p.join(tempDir.path, xcarchiveDirectoryName));
final thinnedArchiveDirectory = Directory(
p.join(tempDir.path, xcarchiveDirectoryName),
);
await io.copyPath(xcarchivePath, thinnedArchiveDirectory.path);
thinnedArchiveDirectory
.listSync(recursive: true)
@@ -776,9 +757,9 @@ aar artifact already exists, continuing...''',
}
if (supplementPath != null) {
final zippedSupplement = await Directory(supplementPath).zipToTempFile(
name: 'ios_supplement',
);
final zippedSupplement = await Directory(
supplementPath,
).zipToTempFile(name: 'ios_supplement');
try {
await codePushClient.createReleaseArtifact(
appId: appId,
@@ -820,9 +801,10 @@ aar artifact already exists, continuing...''',
artifactPath: zippedAppFrameworkFile.path,
arch: 'xcframework',
platform: ReleasePlatform.ios,
hash: sha256
.convert(await zippedAppFrameworkFile.readAsBytes())
.toString(),
hash:
sha256
.convert(await zippedAppFrameworkFile.readAsBytes())
.toString(),
canSideload: false,
podfileLockHash: null,
);
@@ -835,9 +817,9 @@ aar artifact already exists, continuing...''',
}
if (supplementPath != null) {
final zippedSupplement = await Directory(supplementPath).zipToTempFile(
name: 'ios_framework_supplement',
);
final zippedSupplement = await Directory(
supplementPath,
).zipToTempFile(name: 'ios_framework_supplement');
try {
await codePushClient.createReleaseArtifact(
appId: appId,
@@ -948,14 +930,9 @@ aar artifact already exists, continuing...''',
patchArtifactBundles: patchArtifactBundles,
);
final channel = await maybeGetChannel(
appId: appId,
name: track.channel,
) ??
await createChannel(
appId: appId,
name: track.channel,
);
final channel =
await maybeGetChannel(appId: appId, name: track.channel) ??
await createChannel(appId: appId, name: track.channel);
await promotePatch(appId: appId, patchId: patch.id, channel: channel);
@@ -45,10 +45,11 @@ class CodeSigner {
_pemBytes(pemFile: publicKeyPemFile, type: PemLabel.publicKey),
);
final publicKeySeq = ASN1Sequence()
..add(ASN1Integer(publicKey.modulus))
..add(ASN1Integer(publicKey.exponent))
..encode();
final publicKeySeq =
ASN1Sequence()
..add(ASN1Integer(publicKey.modulus))
..add(ASN1Integer(publicKey.exponent))
..encode();
return base64.encode(publicKeySeq.encodedBytes!);
}
@@ -94,8 +95,9 @@ extension _RSAPublicKeyFromBytes on RSAPublicKey {
final asn1Parser = ASN1Parser(Uint8List.fromList(bytes));
final topLevelSeq = asn1Parser.nextObject() as ASN1Sequence;
final publicKeyBitString = topLevelSeq.elements![1] as ASN1BitString;
final publicKeyAsn =
ASN1Parser(publicKeyBitString.stringValues as Uint8List?);
final publicKeyAsn = ASN1Parser(
publicKeyBitString.stringValues as Uint8List?,
);
final publicKeySeq = publicKeyAsn.nextObject() as ASN1Sequence;
final modulus = publicKeySeq.elements![0] as ASN1Integer;
final exponent = publicKeySeq.elements![1] as ASN1Integer;
@@ -31,9 +31,7 @@ class CleanCacheCommand extends ShorebirdCommand {
await cache.clear();
} on FileSystemException catch (error) {
final cachePath = Cache.shorebirdCacheDirectory.path;
progress.fail(
'''Failed to delete cache directory $cachePath: $error''',
);
progress.fail('''Failed to delete cache directory $cachePath: $error''');
if (!platform.isWindows) {
return ExitCode.software.code;
@@ -45,12 +43,10 @@ class CleanCacheCommand extends ShorebirdCommand {
),
);
logger.info(
'''
logger.info('''
This could be because a program is using a file in the cache directory. To find and stop such a program, see:
${lightCyan.wrap(superuserLink)}
''',
);
''');
return ExitCode.software.code;
}
@@ -52,12 +52,10 @@ class DoctorCommand extends ShorebirdCommand {
if (flutterVersion != null) {
shorebirdFlutterPrefix.write(' $flutterVersion');
}
output.writeln(
'''
output.writeln('''
Shorebird $packageVersion • git@github.com:shorebirdtech/shorebird.git
$shorebirdFlutterPrefix • revision ${shorebirdEnv.flutterRevision}
Engine • revision ${shorebirdEnv.shorebirdEngineRevision}''',
);
Engine • revision ${shorebirdEnv.shorebirdEngineRevision}''');
if (verbose) {
final notDetected = red.wrap('not detected');
@@ -127,9 +125,7 @@ Android Toolchain
'GCP download speed test failed: ${error.message}',
);
} on Exception catch (error) {
downloadProgress.fail(
'GCP download speed test failed: $error',
);
downloadProgress.fail('GCP download speed test failed: $error');
}
logger.info('');
}
@@ -30,10 +30,7 @@ class InitCommand extends ShorebirdCommand {
help: 'Initialize the app even if a "shorebird.yaml" already exists.',
negatable: false,
)
..addOption(
'display-name',
help: 'The display name of the app.',
);
..addOption('display-name', help: 'The display name of the app.');
}
@override
@@ -133,8 +130,9 @@ Please make sure you are running "shorebird init" from within your Flutter proje
// don't care about which flavors are new.
if (!force && newFlavors.isNotEmpty) {
logger.info('New flavors detected: ${newFlavors.join(', ')}');
final updateShorebirdYamlProgress =
logger.progress('Adding flavors to shorebird.yaml');
final updateShorebirdYamlProgress = logger.progress(
'Adding flavors to shorebird.yaml',
);
final AppMetadata existingApp;
try {
@@ -180,14 +178,16 @@ Please make sure you are running "shorebird init" from within your Flutter proje
final needsConfirmation = !force && shorebirdEnv.canAcceptUserInput;
final pubspecName = shorebirdEnv.getPubspecYaml()!.name;
var displayName = results['display-name'] as String?;
displayName ??= needsConfirmation
? logger.prompt(
'${lightGreen.wrap('?')} How should we refer to this app?',
defaultValue: pubspecName,
)
: pubspecName;
displayName ??=
needsConfirmation
? logger.prompt(
'${lightGreen.wrap('?')} How should we refer to this app?',
defaultValue: pubspecName,
)
: pubspecName;
final hasNoFlavors = productFlavors.isEmpty;
final hasSomeFlavors = productFlavors.isNotEmpty &&
final hasSomeFlavors =
productFlavors.isNotEmpty &&
((androidFlavors?.isEmpty ?? false) ||
(iosFlavors?.isEmpty ?? false));
@@ -45,10 +45,8 @@ class LoginCiCommand extends ShorebirdCommand {
ciToken = await auth.loginCI(provider, prompt: prompt);
} on UserNotFoundException catch (error) {
logger
..err(
'''
We could not find a Shorebird account for ${error.email}.''',
)
..err('''
We could not find a Shorebird account for ${error.email}.''')
..info(
'''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!''',
);
@@ -53,10 +53,8 @@ class LoginCommand extends ShorebirdCommand {
} on UserNotFoundException catch (error) {
final consoleUri = Uri.https('console.shorebird.dev');
logger
..err(
'''
We could not find a Shorebird account for ${error.email}.''',
)
..err('''
We could not find a Shorebird account for ${error.email}.''')
..info(
"""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!""",
);
@@ -66,20 +66,20 @@ class AarPatcher extends Patcher {
required ReleaseArtifact releaseArtifact,
required File releaseArchive,
required File patchArchive,
}) =>
patchDiffChecker.confirmUnpatchableDiffsIfNecessary(
localArchive: patchArchive,
releaseArchive: releaseArchive,
archiveDiffer: const AndroidArchiveDiffer(),
allowAssetChanges: allowAssetDiffs,
allowNativeChanges: allowNativeDiffs,
);
}) => patchDiffChecker.confirmUnpatchableDiffsIfNecessary(
localArchive: patchArchive,
releaseArchive: releaseArchive,
archiveDiffer: const AndroidArchiveDiffer(),
allowAssetChanges: allowAssetDiffs,
allowNativeChanges: allowNativeDiffs,
);
@override
Future<File> buildPatchArtifact({String? releaseVersion}) async {
final flutterVersionString = await shorebirdFlutter.getVersionAndRevision();
final buildProgress =
logger.progress('Building patch with Flutter $flutterVersionString');
final buildProgress = logger.progress(
'Building patch with Flutter $flutterVersionString',
);
try {
await artifactBuilder.buildAar(
@@ -57,14 +57,13 @@ See more info about the issue ${link(uri: Uri.parse('https://github.com/shorebir
required ReleaseArtifact releaseArtifact,
required File releaseArchive,
required File patchArchive,
}) =>
patchDiffChecker.confirmUnpatchableDiffsIfNecessary(
localArchive: patchArchive,
releaseArchive: releaseArchive,
archiveDiffer: const AndroidArchiveDiffer(),
allowAssetChanges: allowAssetDiffs,
allowNativeChanges: allowNativeDiffs,
);
}) => patchDiffChecker.confirmUnpatchableDiffsIfNecessary(
localArchive: patchArchive,
releaseArchive: releaseArchive,
archiveDiffer: const AndroidArchiveDiffer(),
allowAssetChanges: allowAssetDiffs,
allowNativeChanges: allowNativeDiffs,
);
@override
Future<void> assertPreconditions() async {
@@ -91,14 +90,16 @@ See more info about the issue ${link(uri: Uri.parse('https://github.com/shorebir
logger.warn(updaterPatchErrorWarning);
}
final buildProgress = logger
.detailProgress('Building patch with Flutter $flutterVersionString');
final buildProgress = logger.detailProgress(
'Building patch with Flutter $flutterVersionString',
);
try {
aabFile = await artifactBuilder.buildAppBundle(
flavor: flavor,
target: target,
args: argResults.forwardedArgs +
args:
argResults.forwardedArgs +
buildNameAndNumberArgsFromReleaseVersion(releaseVersion),
base64PublicKey: argResults.encodedPublicKey,
buildProgress: buildProgress,
@@ -117,8 +118,7 @@ See more info about the issue ${link(uri: Uri.parse('https://github.com/shorebir
if (patchArchsBuildDir == null) {
logger
..err('Cannot find patch build artifacts.')
..info(
'''
..info('''
Please run `shorebird cache clean` and try again. If the issue persists, please
file a bug report at https://github.com/shorebirdtech/shorebird/issues/new.
@@ -126,8 +126,7 @@ Looked in:
- build/app/intermediates/stripped_native_libs/stripReleaseDebugSymbols/release/out/lib
- build/app/intermediates/stripped_native_libs/strip{flavor}ReleaseDebugSymbols/{flavor}Release/out/lib
- build/app/intermediates/stripped_native_libs/release/out/lib
- build/app/intermediates/stripped_native_libs/{flavor}Release/out/lib''',
);
- build/app/intermediates/stripped_native_libs/{flavor}Release/out/lib''');
throw ProcessExit(ExitCode.software.code);
}
return aabFile;
@@ -154,27 +153,25 @@ Looked in:
// until we can provide a better solution.
var artifactsDownloadCompleted = false;
unawaited(
Future<void>.delayed(downloadMessageTimeout).then(
(_) {
if (artifactsDownloadCompleted) {
return;
}
logger.info(
'''
Future<void>.delayed(downloadMessageTimeout).then((_) {
if (artifactsDownloadCompleted) {
return;
}
logger.info(
'''
It seems like your download is taking longer than expected. If you are on Windows, this is a known issue.
Please refer to ${link(uri: Uri.parse('https://github.com/shorebirdtech/shorebird/issues/2532'))} for potential workarounds.''',
);
},
),
);
}),
);
for (final (i, releaseArtifact) in releaseArtifacts.entries.indexed) {
try {
final releaseArtifactFile =
await artifactManager.downloadWithProgressUpdates(
Uri.parse(releaseArtifact.value.url),
message: 'Downloading release artifact ${i + 1}/$numArtifacts',
);
final releaseArtifactFile = await artifactManager
.downloadWithProgressUpdates(
Uri.parse(releaseArtifact.value.url),
message: 'Downloading release artifact ${i + 1}/$numArtifacts',
);
releaseArtifactPaths[releaseArtifact.key] = releaseArtifactFile.path;
} on Exception {
throw ProcessExit(ExitCode.software.code);
@@ -208,12 +205,13 @@ Please refer to ${link(uri: Uri.parse('https://github.com/shorebirdtech/shorebir
final privateKeyFile = argResults.file(
CommonArguments.privateKeyArg.name,
);
final hashSignature = privateKeyFile != null
? codeSigner.sign(
message: hash,
privateKeyPemFile: privateKeyFile,
)
: null;
final hashSignature =
privateKeyFile != null
? codeSigner.sign(
message: hash,
privateKeyPemFile: privateKeyFile,
)
: null;
try {
final diffPath = await artifactManager.createDiff(
@@ -43,28 +43,24 @@ class IosFrameworkPatcher extends Patcher {
p.join(shorebirdEnv.buildDirectory.path, 'out.aot');
String get _patchClassTableLinkInfoFile => p.join(
shorebirdEnv.buildDirectory.path,
'ios',
'shorebird',
'App.ct.link',
);
shorebirdEnv.buildDirectory.path,
'ios',
'shorebird',
'App.ct.link',
);
String get _patchClassTableLinkDebugInfoPath => p.join(
shorebirdEnv.buildDirectory.path,
'ios',
'shorebird',
'App.class_table.json',
);
shorebirdEnv.buildDirectory.path,
'ios',
'shorebird',
'App.class_table.json',
);
String get _vmcodeOutputPath => p.join(
shorebirdEnv.buildDirectory.path,
'out.vmcode',
);
String get _vmcodeOutputPath =>
p.join(shorebirdEnv.buildDirectory.path, 'out.vmcode');
String get _appDillCopyPath => p.join(
shorebirdEnv.buildDirectory.path,
'app.dill',
);
String get _appDillCopyPath =>
p.join(shorebirdEnv.buildDirectory.path, 'app.dill');
@override
String get primaryReleaseArtifactArch => 'xcframework';
@@ -109,14 +105,13 @@ class IosFrameworkPatcher extends Patcher {
required ReleaseArtifact releaseArtifact,
required File releaseArchive,
required File patchArchive,
}) =>
patchDiffChecker.confirmUnpatchableDiffsIfNecessary(
localArchive: patchArchive,
releaseArchive: releaseArchive,
archiveDiffer: const AppleArchiveDiffer(),
allowAssetChanges: allowAssetDiffs,
allowNativeChanges: allowNativeDiffs,
);
}) => patchDiffChecker.confirmUnpatchableDiffsIfNecessary(
localArchive: patchArchive,
releaseArchive: releaseArchive,
archiveDiffer: const AppleArchiveDiffer(),
allowAssetChanges: allowAssetDiffs,
allowNativeChanges: allowNativeDiffs,
);
@override
Future<File> buildPatchArtifact({String? releaseVersion}) async {
@@ -208,20 +203,11 @@ class IosFrameworkPatcher extends Patcher {
'Extracted release artifact to $releaseXcframeworkPath',
);
final releaseArtifactFile = File(
p.join(
releaseXcframeworkPath,
'ios-arm64',
'App.framework',
'App',
),
p.join(releaseXcframeworkPath, 'ios-arm64', 'App.framework', 'App'),
);
final aotSnapshotFile = File(
p.join(
shorebirdEnv.getShorebirdProjectRoot()!.path,
'build',
'out.aot',
),
p.join(shorebirdEnv.getShorebirdProjectRoot()!.path, 'build', 'out.aot'),
);
final useLinker = AotTools.usesLinker(shorebirdEnv.flutterRevision);
if (useLinker) {
@@ -240,9 +226,9 @@ class IosFrameworkPatcher extends Patcher {
// Copy the patch's class table link info file to the build directory
// so that it can be used to generate a patch.
File(_patchClassTableLinkInfoFile).copySync(
p.join(shorebirdEnv.buildDirectory.path, 'out.ct.link'),
);
File(
_patchClassTableLinkInfoFile,
).copySync(p.join(shorebirdEnv.buildDirectory.path, 'out.ct.link'));
File(_patchClassTableLinkDebugInfoPath).copySync(
p.join(shorebirdEnv.buildDirectory.path, 'out.class_table.json'),
);
@@ -311,11 +297,10 @@ class IosFrameworkPatcher extends Patcher {
@override
Future<CreatePatchMetadata> updatedCreatePatchMetadata(
CreatePatchMetadata metadata,
) async =>
metadata.copyWith(
linkPercentage: lastBuildLinkPercentage,
environment: metadata.environment.copyWith(
xcodeVersion: await xcodeBuild.version(),
),
);
) async => metadata.copyWith(
linkPercentage: lastBuildLinkPercentage,
environment: metadata.environment.copyWith(
xcodeVersion: await xcodeBuild.version(),
),
);
}
@@ -44,18 +44,18 @@ class IosPatcher extends Patcher {
});
String get _patchClassTableLinkInfoPath => p.join(
shorebirdEnv.buildDirectory.path,
'ios',
'shorebird',
'App.ct.link',
);
shorebirdEnv.buildDirectory.path,
'ios',
'shorebird',
'App.ct.link',
);
String get _patchClassTableLinkDebugInfoPath => p.join(
shorebirdEnv.buildDirectory.path,
'ios',
'shorebird',
'App.class_table.json',
);
shorebirdEnv.buildDirectory.path,
'ios',
'shorebird',
'App.class_table.json',
);
String get _aotOutputPath =>
p.join(shorebirdEnv.buildDirectory.path, 'out.aot');
@@ -74,10 +74,10 @@ class IosPatcher extends Patcher {
static List<String> splitDebugInfoArgs(String? splitDebugInfoPath) {
return splitDebugInfoPath != null
? [
'--dwarf-stack-traces',
'--resolve-dwarf-paths',
'''--save-debugging-info=${saveDebuggingInfoPath(splitDebugInfoPath)}''',
]
'--dwarf-stack-traces',
'--resolve-dwarf-paths',
'''--save-debugging-info=${saveDebuggingInfoPath(splitDebugInfoPath)}''',
]
: <String>[];
}
@@ -126,15 +126,15 @@ class IosPatcher extends Patcher {
// can be nondeterministic. So we still have some hope of alerting users of
// unpatchable native changes, we compare the Podfile.lock hash between the
// patch and the release.
final diffStatus =
await patchDiffChecker.confirmUnpatchableDiffsIfNecessary(
localArchive: patchArchive,
releaseArchive: releaseArchive,
archiveDiffer: const AppleArchiveDiffer(),
allowAssetChanges: allowAssetDiffs,
allowNativeChanges: allowNativeDiffs,
confirmNativeChanges: false,
);
final diffStatus = await patchDiffChecker
.confirmUnpatchableDiffsIfNecessary(
localArchive: patchArchive,
releaseArchive: releaseArchive,
archiveDiffer: const AppleArchiveDiffer(),
allowAssetChanges: allowAssetDiffs,
allowNativeChanges: allowNativeDiffs,
confirmNativeChanges: false,
);
if (!diffStatus.hasNativeChanges) {
return diffStatus;
@@ -142,9 +142,10 @@ class IosPatcher extends Patcher {
final String? podfileLockHash;
if (shorebirdEnv.iosPodfileLockFile.existsSync()) {
podfileLockHash = sha256
.convert(shorebirdEnv.iosPodfileLockFile.readAsBytesSync())
.toString();
podfileLockHash =
sha256
.convert(shorebirdEnv.iosPodfileLockFile.readAsBytesSync())
.toString();
} else {
podfileLockHash = null;
}
@@ -175,18 +176,17 @@ This may indicate that the patch contains native changes, which cannot be applie
Future<File> buildPatchArtifact({String? releaseVersion}) async {
try {
final shouldCodesign = argResults['codesign'] == true;
final (flutterVersionAndRevision, flutterVersion) = await (
shorebirdFlutter.getVersionAndRevision(),
shorebirdFlutter.getVersion(),
).wait;
final (flutterVersionAndRevision, flutterVersion) =
await (
shorebirdFlutter.getVersionAndRevision(),
shorebirdFlutter.getVersion(),
).wait;
if ((flutterVersion ?? minimumSupportedIosFlutterVersion) <
minimumSupportedIosFlutterVersion) {
logger.err(
'''
logger.err('''
iOS patches are not supported with Flutter versions older than $minimumSupportedIosFlutterVersion.
For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
);
For more information see: ${supportedFlutterVersionsUrl.toLink()}''');
throw ProcessExit(ExitCode.software.code);
}
@@ -201,7 +201,8 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
codesign: shouldCodesign,
flavor: flavor,
target: target,
args: argResults.forwardedArgs +
args:
argResults.forwardedArgs +
buildNameAndNumberArgsFromReleaseVersion(releaseVersion),
base64PublicKey: argResults.encodedPublicKey,
buildProgress: buildProgress,
@@ -299,12 +300,7 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
throw ProcessExit(ExitCode.software.code);
}
final releaseArtifactFile = File(
p.join(
appDirectory.path,
'Frameworks',
'App.framework',
'App',
),
p.join(appDirectory.path, 'Frameworks', 'App.framework', 'App'),
);
final useLinker = AotTools.usesLinker(shorebirdEnv.flutterRevision);
@@ -324,9 +320,9 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
// Copy the patch's class table link info file to the build directory
// so that it can be used to generate a patch.
File(_patchClassTableLinkInfoPath).copySync(
p.join(shorebirdEnv.buildDirectory.path, 'out.ct.link'),
);
File(
_patchClassTableLinkInfoPath,
).copySync(p.join(shorebirdEnv.buildDirectory.path, 'out.ct.link'));
File(_patchClassTableLinkDebugInfoPath).copySync(
p.join(shorebirdEnv.buildDirectory.path, 'out.class_table.json'),
);
@@ -383,12 +379,10 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
final patchFileSize = patchFile.statSync().size;
final privateKeyFile = argResults.file(CommonArguments.privateKeyArg.name);
final hash = sha256.convert(patchBuildFile.readAsBytesSync()).toString();
final hashSignature = privateKeyFile != null
? codeSigner.sign(
message: hash,
privateKeyPemFile: privateKeyFile,
)
: null;
final hashSignature =
privateKeyFile != null
? codeSigner.sign(message: hash, privateKeyPemFile: privateKeyFile)
: null;
return {
Arch.arm64: PatchArtifactBundle(
@@ -429,11 +423,10 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
@override
Future<CreatePatchMetadata> updatedCreatePatchMetadata(
CreatePatchMetadata metadata,
) async =>
metadata.copyWith(
linkPercentage: lastBuildLinkPercentage,
environment: metadata.environment.copyWith(
xcodeVersion: await xcodeBuild.version(),
),
);
) async => metadata.copyWith(
linkPercentage: lastBuildLinkPercentage,
environment: metadata.environment.copyWith(
xcodeVersion: await xcodeBuild.version(),
),
);
}
@@ -99,15 +99,11 @@ class LinuxPatcher extends Patcher {
// build/linux/x64/release/bundle
final appSoPath = p.join(tempDir.path, 'lib', 'libapp.so');
final privateKeyFile = argResults.file(
CommonArguments.privateKeyArg.name,
);
final hashSignature = privateKeyFile != null
? codeSigner.sign(
message: hash,
privateKeyPemFile: privateKeyFile,
)
: null;
final privateKeyFile = argResults.file(CommonArguments.privateKeyArg.name);
final hashSignature =
privateKeyFile != null
? codeSigner.sign(message: hash, privateKeyPemFile: privateKeyFile)
: null;
final String diffPath;
try {
@@ -42,21 +42,15 @@ class MacosPatcher extends Patcher {
});
// The elf snapshot built for Apple Silicon macs.
String get _arm64AotOutputPath => p.join(
shorebirdEnv.buildDirectory.path,
'out.arm64.aot',
);
String get _arm64AotOutputPath =>
p.join(shorebirdEnv.buildDirectory.path, 'out.arm64.aot');
// The elf snapshot built for Intel macs.
String get _x64AotOutputPath => p.join(
shorebirdEnv.buildDirectory.path,
'out.x64.aot',
);
String get _x64AotOutputPath =>
p.join(shorebirdEnv.buildDirectory.path, 'out.x64.aot');
String get _appDillCopyPath => p.join(
shorebirdEnv.buildDirectory.path,
'app.dill',
);
String get _appDillCopyPath =>
p.join(shorebirdEnv.buildDirectory.path, 'app.dill');
@override
ReleaseType get releaseType => ReleaseType.macos;
@@ -93,15 +87,15 @@ class MacosPatcher extends Patcher {
// can be nondeterministic. So we still have some hope of alerting users of
// unpatchable native changes, we compare the Podfile.lock hash between the
// patch and the release.
final diffStatus =
await patchDiffChecker.confirmUnpatchableDiffsIfNecessary(
localArchive: patchArchive,
releaseArchive: releaseArchive,
archiveDiffer: const AppleArchiveDiffer(),
allowAssetChanges: allowAssetDiffs,
allowNativeChanges: allowNativeDiffs,
confirmNativeChanges: false,
);
final diffStatus = await patchDiffChecker
.confirmUnpatchableDiffsIfNecessary(
localArchive: patchArchive,
releaseArchive: releaseArchive,
archiveDiffer: const AppleArchiveDiffer(),
allowAssetChanges: allowAssetDiffs,
allowNativeChanges: allowNativeDiffs,
confirmNativeChanges: false,
);
if (!diffStatus.hasNativeChanges) {
return diffStatus;
@@ -109,9 +103,10 @@ class MacosPatcher extends Patcher {
final String? podfileLockHash;
if (shorebirdEnv.macosPodfileLockFile.existsSync()) {
podfileLockHash = sha256
.convert(shorebirdEnv.macosPodfileLockFile.readAsBytesSync())
.toString();
podfileLockHash =
sha256
.convert(shorebirdEnv.macosPodfileLockFile.readAsBytesSync())
.toString();
} else {
podfileLockHash = null;
}
@@ -141,18 +136,17 @@ This may indicate that the patch contains native changes, which cannot be applie
@override
Future<File> buildPatchArtifact({String? releaseVersion}) async {
try {
final (flutterVersionAndRevision, flutterVersion) = await (
shorebirdFlutter.getVersionAndRevision(),
shorebirdFlutter.getVersion(),
).wait;
final (flutterVersionAndRevision, flutterVersion) =
await (
shorebirdFlutter.getVersionAndRevision(),
shorebirdFlutter.getVersion(),
).wait;
if ((flutterVersion ?? minimumSupportedMacosFlutterVersion) <
minimumSupportedMacosFlutterVersion) {
logger.err(
'''
logger.err('''
macOS patches are not supported with Flutter versions older than $minimumSupportedMacosFlutterVersion.
For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
);
For more information see: ${supportedFlutterVersionsUrl.toLink()}''');
throw ProcessExit(ExitCode.software.code);
}
@@ -167,7 +161,8 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
codesign: codesign,
flavor: flavor,
target: target,
args: argResults.forwardedArgs +
args:
argResults.forwardedArgs +
buildNameAndNumberArgsFromReleaseVersion(releaseVersion),
base64PublicKey: argResults.encodedPublicKey,
buildProgress: buildProgress,
@@ -244,17 +239,11 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
final patchFile = File(patchFilePath);
final patchFileSize = patchFile.statSync().size;
final privateKeyFile = argResults.file(CommonArguments.privateKeyArg.name);
final hash = sha256
.convert(
patchArtifact.readAsBytesSync(),
)
.toString();
final hashSignature = privateKeyFile != null
? codeSigner.sign(
message: hash,
privateKeyPemFile: privateKeyFile,
)
: null;
final hash = sha256.convert(patchArtifact.readAsBytesSync()).toString();
final hashSignature =
privateKeyFile != null
? codeSigner.sign(message: hash, privateKeyPemFile: privateKeyFile)
: null;
return PatchArtifactBundle(
arch: arch.arch,
@@ -303,10 +292,7 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
);
createDiffProgress.complete();
return {
Arch.x86_64: x64Bundle,
Arch.arm64: arm64Bundle,
};
return {Arch.x86_64: x64Bundle, Arch.arm64: arm64Bundle};
}
@override
@@ -337,10 +323,9 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
@override
Future<CreatePatchMetadata> updatedCreatePatchMetadata(
CreatePatchMetadata metadata,
) async =>
metadata.copyWith(
environment: metadata.environment.copyWith(
xcodeVersion: await xcodeBuild.version(),
),
);
) async => metadata.copyWith(
environment: metadata.environment.copyWith(
xcodeVersion: await xcodeBuild.version(),
),
);
}
@@ -36,9 +36,7 @@ typedef ResolvePatcher = Patcher Function(ReleaseType releaseType);
/// {@endtemplate}
class PatchCommand extends ShorebirdCommand {
/// {@macro patch_command}
PatchCommand({
ResolvePatcher? resolvePatcher,
}) {
PatchCommand({ResolvePatcher? resolvePatcher}) {
_resolvePatcher = resolvePatcher ?? getPatcher;
argParser
..addMultiOption(
@@ -219,8 +217,9 @@ NOTE: this is ${styleBold.wrap('not')} recommended. Asset changes cannot be incl
return ExitCode.usage.code;
}
final patcherFutures =
results.releaseTypes.map(_resolvePatcher).map(createPatch);
final patcherFutures = results.releaseTypes
.map(_resolvePatcher)
.map(createPatch);
for (final patcherFuture in patcherFutures) {
await patcherFuture;
@@ -360,20 +359,23 @@ NOTE: this is ${styleBold.wrap('not')} recommended. Asset changes cannot be incl
final supplementalArtifact =
patcher.supplementaryReleaseArtifactArch != null
? await codePushClientWrapper.maybeGetReleaseArtifact(
appId: appId,
releaseId: release.id,
arch: patcher.supplementaryReleaseArtifactArch!,
platform: releasePlatform,
)
appId: appId,
releaseId: release.id,
arch: patcher.supplementaryReleaseArtifactArch!,
platform: releasePlatform,
)
: null;
final releaseArchive = await downloadReleaseArtifact(
releaseArtifact: releaseArtifact,
);
final supplementArchive = supplementalArtifact != null
? await downloadReleaseArtifact(releaseArtifact: supplementalArtifact)
: null;
final supplementArchive =
supplementalArtifact != null
? await downloadReleaseArtifact(
releaseArtifact: supplementalArtifact,
)
: null;
final releaseFlutterShorebirdEnv = shorebirdEnv.copyWith(
flutterRevisionOverride: release.flutterRevision,
@@ -444,17 +446,13 @@ NOTE: this is ${styleBold.wrap('not')} recommended. Asset changes cannot be incl
artifacts: patchArtifactBundles,
);
},
values: {
shorebirdEnvRef.overrideWith(() => releaseFlutterShorebirdEnv),
},
values: {shorebirdEnvRef.overrideWith(() => releaseFlutterShorebirdEnv)},
);
}
/// Prompts the user for the specific release to patch.
Future<Release> promptForRelease(ReleasePlatform platform) async {
final releases = await codePushClientWrapper.getReleases(
appId: appId,
);
final releases = await codePushClientWrapper.getReleases(appId: appId);
final releasesForPlatform = releases.where(
(release) => release.platformStatuses.keys.contains(platform),
@@ -537,13 +535,14 @@ Please re-run the release command for this version or create a new release.''');
final size = formatBytes(patchArtifactBundles[arch]!.size);
return '${arch.name} ($size)';
});
final trackSummary = (() {
return switch (track) {
DeploymentTrack.staging => '🟠 Track: ${lightCyan.wrap('Staging')}',
DeploymentTrack.beta => '🔵 Track: ${lightCyan.wrap('Beta')}',
DeploymentTrack.stable => '🟢 Track: ${lightCyan.wrap('Stable')}',
};
})();
final trackSummary =
(() {
return switch (track) {
DeploymentTrack.staging => '🟠 Track: ${lightCyan.wrap('Staging')}',
DeploymentTrack.beta => '🔵 Track: ${lightCyan.wrap('Beta')}',
DeploymentTrack.stable => '🟢 Track: ${lightCyan.wrap('Stable')}',
};
})();
final linkPercentage = patcher.linkPercentage;
final minLinkPercentage = int.parse(
@@ -567,14 +566,12 @@ Please re-run the release command for this version or create a new release.''');
'''🔍 Debug Info: ${lightCyan.wrap(Patcher.debugInfoFile.path)}''',
];
logger.info(
'''
logger.info('''
${styleBold.wrap(lightGreen.wrap('🚀 Ready to publish a new patch!'))}
${summary.join('\n')}
''',
);
''');
if (shorebirdEnv.canAcceptUserInput && !noConfirm) {
final confirm = logger.confirm('Would you like to continue?');
@@ -188,9 +188,6 @@ More info: ${troubleshootingUrl.toLink()}.
}
final parts = releaseVersion.split('+');
return [
'--build-name=${parts[0]}',
'--build-number=${parts[1]}',
];
return ['--build-name=${parts[0]}', '--build-number=${parts[1]}'];
}
}
@@ -72,9 +72,7 @@ class WindowsPatcher extends Patcher {
}
@override
Future<File> buildPatchArtifact({
String? releaseVersion,
}) async {
Future<File> buildPatchArtifact({String? releaseVersion}) async {
final flutterVersionString = await shorebirdFlutter.getVersionAndRevision();
final buildAppBundleProgress = logger.detailProgress(
@@ -123,15 +121,11 @@ class WindowsPatcher extends Patcher {
// build/windows/x64/runner/Release
final appSoPath = p.join(tempDir.path, 'data', 'app.so');
final privateKeyFile = argResults.file(
CommonArguments.privateKeyArg.name,
);
final hashSignature = privateKeyFile != null
? codeSigner.sign(
message: hash,
privateKeyPemFile: privateKeyFile,
)
: null;
final privateKeyFile = argResults.file(CommonArguments.privateKeyArg.name);
final hashSignature =
privateKeyFile != null
? codeSigner.sign(message: hash, privateKeyPemFile: privateKeyFile)
: null;
final String diffPath;
try {
@@ -164,10 +158,9 @@ class WindowsPatcher extends Patcher {
zipFile: artifact,
outputDirectory: outputDirectory,
);
final exeFile = outputDirectory
.listSync()
.whereType<File>()
.firstWhere((file) => p.extension(file.path) == '.exe');
final exeFile = outputDirectory.listSync().whereType<File>().firstWhere(
(file) => p.extension(file.path) == '.exe',
);
return powershell.getExeVersionString(exeFile);
}
}
@@ -165,24 +165,27 @@ This is only applicable when previewing Android releases.''',
//
// With these two lists, we can now determine if a platform is previewable
// or not, by making a difference between the two lists.
final (allReleases, sideloadableReleases) = await (
codePushClientWrapper.getReleases(appId: appId),
codePushClientWrapper.getReleases(
appId: appId,
sideloadableOnly: true,
)
).wait;
final (allReleases, sideloadableReleases) =
await (
codePushClientWrapper.getReleases(appId: appId),
codePushClientWrapper.getReleases(
appId: appId,
sideloadableOnly: true,
),
).wait;
final maybePlatform = results['platform'] != null
? ReleasePlatform.values.byName(results['platform'] as String)
: null;
final platformReleases = sideloadableReleases
.where(
(r) =>
maybePlatform == null ||
r.activePlatforms.contains(maybePlatform),
)
.toList();
final maybePlatform =
results['platform'] != null
? ReleasePlatform.values.byName(results['platform'] as String)
: null;
final platformReleases =
sideloadableReleases
.where(
(r) =>
maybePlatform == null ||
r.activePlatforms.contains(maybePlatform),
)
.toList();
if (platformReleases.isEmpty) {
if (maybePlatform != null) {
@@ -195,7 +198,8 @@ This is only applicable when previewing Android releases.''',
return ExitCode.usage.code;
}
final releaseVersion = results['release-version'] as String? ??
final releaseVersion =
results['release-version'] as String? ??
await promptForReleaseVersion(platformReleases);
final release = platformReleases.firstWhereOrNull(
@@ -207,13 +211,15 @@ This is only applicable when previewing Android releases.''',
return ExitCode.usage.code;
}
final availablePlatforms = release.activePlatforms
.where((p) => supportedReleasePlatforms.contains(p))
.toList();
final availablePlatforms =
release.activePlatforms
.where((p) => supportedReleasePlatforms.contains(p))
.toList();
if (availablePlatforms.isEmpty) {
final activePlatformsString =
release.activePlatforms.map((p) => p.displayName).join(', ');
final activePlatformsString = release.activePlatforms
.map((p) => p.displayName)
.join(', ');
logger.err(
'''This release can only be previewed on platforms that support $activePlatformsString''',
);
@@ -248,32 +254,32 @@ This is only applicable when previewing Android releases.''',
return switch (releasePlatform) {
ReleasePlatform.android => installAndLaunchAndroid(
appId: appId,
release: release,
deviceId: deviceId,
track: track,
),
appId: appId,
release: release,
deviceId: deviceId,
track: track,
),
ReleasePlatform.ios => installAndLaunchIos(
appId: appId,
release: release,
deviceId: deviceId,
track: track,
),
appId: appId,
release: release,
deviceId: deviceId,
track: track,
),
ReleasePlatform.linux => installAndLaunchLinux(
appId: appId,
release: release,
track: track,
),
appId: appId,
release: release,
track: track,
),
ReleasePlatform.macos => installAndLaunchMacos(
appId: appId,
release: release,
track: track,
),
appId: appId,
release: release,
track: track,
),
ReleasePlatform.windows => installAndLaunchWindows(
appId: appId,
release: release,
track: track,
),
appId: appId,
release: release,
track: track,
),
};
}
@@ -447,10 +453,9 @@ This is only applicable when previewing Android releases.''',
channel: track.name,
);
final exeFile = appDirectory
.listSync()
.whereType<File>()
.firstWhere((file) => file.path.endsWith('.exe'));
final exeFile = appDirectory.listSync().whereType<File>().firstWhere(
(file) => file.path.endsWith('.exe'),
);
final proc = await process.start(exeFile.path, []);
proc.stdout.listen((log) => logger.info(utf8.decode(log)));
@@ -540,17 +545,14 @@ This is only applicable when previewing Android releases.''',
return line.trim().replaceFirst(prefixRegex, '').trim();
}
logs.listen(
(log) {
final logLine = utf8.decode(log);
if (logFilters.any((filter) => filter.hasMatch(logLine))) {
return;
}
logs.listen((log) {
final logLine = utf8.decode(log);
if (logFilters.any((filter) => filter.hasMatch(logLine))) {
return;
}
logger.info(removeLogPrefix(logLine));
},
onDone: completer.complete,
);
logger.info(removeLogPrefix(logLine));
}, onDone: completer.complete);
return completer.future.then((_) => ExitCode.success.code);
}
@@ -798,9 +800,10 @@ This is only applicable when previewing Android releases.''',
}
final shouldUseDeviceCtl = deviceForLaunch != null;
final progressCompleteMessage = deviceForLaunch != null
? 'Using device ${deviceForLaunch.name}'
: '''No iOS 17+ device found, looking for devices running iOS 16 or lower''';
final progressCompleteMessage =
deviceForLaunch != null
? 'Using device ${deviceForLaunch.name}'
: '''No iOS 17+ device found, looking for devices running iOS 16 or lower''';
deviceLocateProgress.complete(progressCompleteMessage);
final int installExitCode;
@@ -850,12 +853,7 @@ This is only applicable when previewing Android releases.''',
required String channel,
}) async {
final shorebirdYamlFile = File(
p.join(
appDirectory.path,
'data',
'flutter_assets',
'shorebird.yaml',
),
p.join(appDirectory.path, 'data', 'flutter_assets', 'shorebird.yaml'),
);
await _maybeSetChannelInShorebirdYaml(
@@ -979,12 +977,7 @@ This is only applicable when previewing Android releases.''',
required Directory bundleDirectory,
}) async {
final shorebirdYamlFile = File(
p.join(
bundleDirectory.path,
'data',
'flutter_assets',
'shorebird.yaml',
),
p.join(bundleDirectory.path, 'data', 'flutter_assets', 'shorebird.yaml'),
);
await _maybeSetChannelInShorebirdYaml(
@@ -1027,10 +1020,11 @@ This is only applicable when previewing Android releases.''',
/// 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)
.toList();
List<ReleasePlatform> get activePlatforms =>
platformStatuses.entries
.where((e) => e.value == ReleaseStatus.active)
.map((e) => e.key)
.toList();
}
/// Given two [Release]s, one with all the platforms, previewable or not,
@@ -33,12 +33,14 @@ class AarReleaser extends Releaser {
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
.firstWhere((arch) => arch.targetPlatformCliArg == platform),
)
.toSet();
Set<Arch> get architectures =>
(argResults['target-platform'] as List<String>)
.map(
(platform) => AndroidArch.availableAndroidArchs.firstWhere(
(arch) => arch.targetPlatformCliArg == platform,
),
)
.toSet();
@override
ReleaseType get releaseType => ReleaseType.aar;
@@ -92,10 +94,7 @@ class AarReleaser extends Releaser {
final targetLibraryDirectory = Directory(
p.join(shorebirdEnv.getShorebirdProjectRoot()!.path, 'release'),
);
await copyPath(
sourceLibraryDirectory.path,
targetLibraryDirectory.path,
);
await copyPath(sourceLibraryDirectory.path, targetLibraryDirectory.path);
return targetLibraryDirectory;
}
@@ -34,12 +34,14 @@ class AndroidReleaser extends Releaser {
String get artifactDisplayName => 'Android app bundle';
/// The architectures to build for.
Set<Arch> get architectures => (argResults['target-platform'] as List<String>)
.map(
(platform) => AndroidArch.availableAndroidArchs
.firstWhere((arch) => arch.targetPlatformCliArg == platform),
)
.toSet();
Set<Arch> get architectures =>
(argResults['target-platform'] as List<String>)
.map(
(platform) => AndroidArch.availableAndroidArchs.firstWhere(
(arch) => arch.targetPlatformCliArg == platform,
),
)
.toSet();
/// Whether to generate an APK in addition to the AAB.
late bool generateApk = argResults['artifact'] as String == 'apk';
@@ -93,12 +95,14 @@ Please comment and upvote ${link(uri: Uri.parse('https://github.com/shorebirdtec
Future<FileSystemEntity> buildReleaseArtifacts({
DetailProgress? progress,
}) async {
final architectures = (argResults['target-platform'] as List<String>)
.map(
(platform) => AndroidArch.availableAndroidArchs
.firstWhere((arch) => arch.targetPlatformCliArg == platform),
)
.toSet();
final architectures =
(argResults['target-platform'] as List<String>)
.map(
(platform) => AndroidArch.availableAndroidArchs.firstWhere(
(arch) => arch.targetPlatformCliArg == platform,
),
)
.toSet();
final base64PublicKey = argResults.encodedPublicKey;
final aab = await artifactBuilder.buildAppBundle(
@@ -128,15 +132,14 @@ Please comment and upvote ${link(uri: Uri.parse('https://github.com/shorebirdtec
Future<String> getReleaseVersion({
required FileSystemEntity releaseArtifactRoot,
}) async {
final releaseVersionProgress =
logger.progress('Determining release version');
final releaseVersionProgress = logger.progress(
'Determining release version',
);
final String releaseVersion;
try {
releaseVersion =
await shorebirdAndroidArtifacts.extractReleaseVersionFromAppBundle(
releaseArtifactRoot.path,
);
releaseVersion = await shorebirdAndroidArtifacts
.extractReleaseVersionFromAppBundle(releaseArtifactRoot.path);
releaseVersionProgress.complete('Release version: $releaseVersion');
} on Exception catch (error) {
releaseVersionProgress.fail('$error');
@@ -169,8 +172,7 @@ Please comment and upvote ${link(uri: Uri.parse('https://github.com/shorebirdtec
@override
Future<UpdateReleaseMetadata> updatedReleaseMetadata(
UpdateReleaseMetadata metadata,
) async =>
metadata.copyWith(generatedApks: generateApk);
) async => metadata.copyWith(generatedApks: generateApk);
@override
String get postReleaseInstructions {
@@ -185,13 +187,14 @@ Please comment and upvote ${link(uri: Uri.parse('https://github.com/shorebirdtec
project: projectRoot,
flavor: flavor,
);
apkText = generateApk
? '''
apkText =
generateApk
? '''
Or distribute the apk:
${lightCyan.wrap(apkFile.path)}
'''
: '';
: '';
} else {
apkText = '';
}
@@ -35,11 +35,8 @@ class IosFrameworkReleaser extends Releaser {
/// The directory where the release artifacts are stored.
Directory get releaseDirectory => Directory(
p.join(
shorebirdEnv.getShorebirdProjectRoot()!.path,
'release',
),
);
p.join(shorebirdEnv.getShorebirdProjectRoot()!.path, 'release'),
);
@override
bool get requiresReleaseVersionArg => true;
@@ -73,23 +70,20 @@ class IosFrameworkReleaser extends Releaser {
final flutterVersionArg = argResults['flutter-version'] as String?;
if (flutterVersionArg != null) {
final version =
await shorebirdFlutter.resolveFlutterVersion(flutterVersionArg);
final version = await shorebirdFlutter.resolveFlutterVersion(
flutterVersionArg,
);
if (version != null && version < minimumSupportedIosFlutterVersion) {
logger.err(
'''
logger.err('''
iOS releases are not supported with Flutter versions older than $minimumSupportedIosFlutterVersion.
For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
);
For more information see: ${supportedFlutterVersionsUrl.toLink()}''');
throw ProcessExit(ExitCode.usage.code);
}
}
}
@override
Future<FileSystemEntity> buildReleaseArtifacts({
Progress? progress,
}) async {
Future<FileSystemEntity> buildReleaseArtifacts({Progress? progress}) async {
// Delete the Shorebird supplement directory if it exists.
// This is to ensure that we don't accidentally upload stale artifacts
// when building with older versions of Flutter.
@@ -110,23 +104,14 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
if (targetLibraryDirectory.existsSync()) {
targetLibraryDirectory.deleteSync(recursive: true);
}
await copyPath(
sourceLibraryDirectory.path,
targetLibraryDirectory.path,
);
await copyPath(sourceLibraryDirectory.path, targetLibraryDirectory.path);
// Rename Flutter.xcframework to ShorebirdFlutter.xcframework to avoid
// Xcode warning users about the .xcframework signature changing.
Directory(
p.join(
targetLibraryDirectory.path,
'Flutter.xcframework',
),
p.join(targetLibraryDirectory.path, 'Flutter.xcframework'),
).renameSync(
p.join(
targetLibraryDirectory.path,
'ShorebirdFlutter.xcframework',
),
p.join(targetLibraryDirectory.path, 'ShorebirdFlutter.xcframework'),
);
return targetLibraryDirectory;
@@ -155,12 +140,11 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
@override
Future<UpdateReleaseMetadata> updatedReleaseMetadata(
UpdateReleaseMetadata metadata,
) async =>
metadata.copyWith(
environment: metadata.environment.copyWith(
xcodeVersion: await xcodeBuild.version(),
),
);
) async => metadata.copyWith(
environment: metadata.environment.copyWith(
xcodeVersion: await xcodeBuild.version(),
),
);
@override
String get postReleaseInstructions {
@@ -82,14 +82,13 @@ To change the version of this release, change your app's version in your pubspec
final flutterVersionArg = argResults['flutter-version'] as String?;
if (flutterVersionArg != null) {
final version =
await shorebirdFlutter.resolveFlutterVersion(flutterVersionArg);
final version = await shorebirdFlutter.resolveFlutterVersion(
flutterVersionArg,
);
if (version != null && version < minimumSupportedIosFlutterVersion) {
logger.err(
'''
logger.err('''
iOS releases are not supported with Flutter versions older than $minimumSupportedIosFlutterVersion.
For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
);
For more information see: ${supportedFlutterVersionsUrl.toLink()}''');
throw ProcessExit(ExitCode.usage.code);
}
}
@@ -173,9 +172,10 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
final xcarchiveDirectory = artifactManager.getXcarchiveDirectory()!;
final String? podfileLockHash;
if (shorebirdEnv.iosPodfileLockFile.existsSync()) {
podfileLockHash = sha256
.convert(shorebirdEnv.iosPodfileLockFile.readAsBytesSync())
.toString();
podfileLockHash =
sha256
.convert(shorebirdEnv.iosPodfileLockFile.readAsBytesSync())
.toString();
} else {
podfileLockHash = null;
}
@@ -183,9 +183,10 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
appId: appId,
releaseId: release.id,
xcarchivePath: xcarchiveDirectory.path,
runnerPath: artifactManager
.getIosAppDirectory(xcarchiveDirectory: xcarchiveDirectory)!
.path,
runnerPath:
artifactManager
.getIosAppDirectory(xcarchiveDirectory: xcarchiveDirectory)!
.path,
isCodesigned: codesign,
podfileLockHash: podfileLockHash,
supplementPath: artifactManager.getIosReleaseSupplementDirectory()?.path,
@@ -195,12 +196,11 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
@override
Future<UpdateReleaseMetadata> updatedReleaseMetadata(
UpdateReleaseMetadata metadata,
) async =>
metadata.copyWith(
environment: metadata.environment.copyWith(
xcodeVersion: await xcodeBuild.version(),
),
);
) async => metadata.copyWith(
environment: metadata.environment.copyWith(
xcodeVersion: await xcodeBuild.version(),
),
);
@override
String get postReleaseInstructions {
@@ -61,14 +61,13 @@ To change the version of this release, change your app's version in your pubspec
}
final flutterVersionArg = argResults['flutter-version'] as String?;
if (flutterVersionArg != null) {
final version =
await shorebirdFlutter.resolveFlutterVersion(flutterVersionArg);
final version = await shorebirdFlutter.resolveFlutterVersion(
flutterVersionArg,
);
if (version != null && version < minimumSupportedLinuxFlutterVersion) {
logger.err(
'''
logger.err('''
Linux releases are not supported with Flutter versions older than $minimumSupportedLinuxFlutterVersion.
For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
);
For more information see: ${supportedFlutterVersionsUrl.toLink()}''');
throw ProcessExit(ExitCode.usage.code);
}
}
@@ -88,10 +87,9 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
@override
Future<String> getReleaseVersion({
required FileSystemEntity releaseArtifactRoot,
}) async =>
linux.versionFromLinuxBundle(
bundleRoot: releaseArtifactRoot as Directory,
);
}) async => linux.versionFromLinuxBundle(
bundleRoot: releaseArtifactRoot as Directory,
);
@override
String get postReleaseInstructions => '''
@@ -103,10 +101,9 @@ Linux release created at ${artifactManager.linuxBundleDirectory.path}.
Future<void> uploadReleaseArtifacts({
required Release release,
required String appId,
}) =>
codePushClientWrapper.createLinuxReleaseArtifacts(
appId: appId,
releaseId: release.id,
bundle: artifactManager.linuxBundleDirectory,
);
}) => codePushClientWrapper.createLinuxReleaseArtifacts(
appId: appId,
releaseId: release.id,
bundle: artifactManager.linuxBundleDirectory,
);
}
@@ -83,14 +83,13 @@ To change the version of this release, change your app's version in your pubspec
final flutterVersionArg = argResults['flutter-version'] as String?;
if (flutterVersionArg != null) {
final version =
await shorebirdFlutter.resolveFlutterVersion(flutterVersionArg);
final version = await shorebirdFlutter.resolveFlutterVersion(
flutterVersionArg,
);
if (version != null && version < minimumSupportedMacosFlutterVersion) {
logger.err(
'''
logger.err('''
macOS releases are not supported with Flutter versions older than $minimumSupportedMacosFlutterVersion.
For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
);
For more information see: ${supportedFlutterVersionsUrl.toLink()}''');
throw ProcessExit(ExitCode.usage.code);
}
}
@@ -163,9 +162,10 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
final String? podfileLockHash;
if (shorebirdEnv.macosPodfileLockFile.existsSync()) {
podfileLockHash = sha256
.convert(shorebirdEnv.macosPodfileLockFile.readAsBytesSync())
.toString();
podfileLockHash =
sha256
.convert(shorebirdEnv.macosPodfileLockFile.readAsBytesSync())
.toString();
} else {
podfileLockHash = null;
}
@@ -182,12 +182,11 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
@override
Future<UpdateReleaseMetadata> updatedReleaseMetadata(
UpdateReleaseMetadata metadata,
) async =>
metadata.copyWith(
environment: metadata.environment.copyWith(
xcodeVersion: await xcodeBuild.version(),
),
);
) async => metadata.copyWith(
environment: metadata.environment.copyWith(
xcodeVersion: await xcodeBuild.version(),
),
);
@override
String get postReleaseInstructions => '''
@@ -114,7 +114,8 @@ class ReleaseCommand extends ShorebirdCommand {
)
..addFlag(
'split-per-abi',
help: 'Whether to split the APKs per ABIs (Android only). '
help:
'Whether to split the APKs per ABIs (Android only). '
'To learn more, see: https://developer.android.com/studio/build/configure-apk-splits#configure-abi-split',
hide: true,
negatable: false,
@@ -164,8 +165,9 @@ of the iOS app that is using this module. (aar and ios-framework only)''',
return ExitCode.usage.code;
}
final releaserFutures =
results.releaseTypes.map(_resolveReleaser).map(createRelease);
final releaserFutures = results.releaseTypes
.map(_resolveReleaser)
.map(createRelease);
for (final future in releaserFutures) {
await future;
@@ -179,11 +181,7 @@ of the iOS app that is using this module. (aar and ios-framework only)''',
Releaser getReleaser(ReleaseType releaseType) {
switch (releaseType) {
case ReleaseType.aar:
return AarReleaser(
argResults: results,
flavor: flavor,
target: target,
);
return AarReleaser(argResults: results, flavor: flavor, target: target);
case ReleaseType.android:
return AndroidReleaser(
argResults: results,
@@ -191,11 +189,7 @@ of the iOS app that is using this module. (aar and ios-framework only)''',
target: target,
);
case ReleaseType.ios:
return IosReleaser(
argResults: results,
flavor: flavor,
target: target,
);
return IosReleaser(argResults: results, flavor: flavor, target: target);
case ReleaseType.iosFramework:
return IosFrameworkReleaser(
argResults: results,
@@ -269,98 +263,92 @@ of the iOS app that is using this module. (aar and ios-framework only)''',
final releaseFlutterShorebirdEnv = shorebirdEnv.copyWith(
flutterRevisionOverride: targetFlutterRevision,
);
return await runScoped(
() async {
await cache.updateAll();
return await runScoped(() async {
await cache.updateAll();
final flutterVersionString =
await shorebirdFlutter.getVersionAndRevision();
final buildProgress = logger.detailProgress(
'''Building ${releaser.artifactDisplayName} with Flutter $flutterVersionString''',
final flutterVersionString =
await shorebirdFlutter.getVersionAndRevision();
final buildProgress = logger.detailProgress(
'''Building ${releaser.artifactDisplayName} with Flutter $flutterVersionString''',
);
final FileSystemEntity releaseArtifact;
try {
releaseArtifact = await releaser.buildReleaseArtifacts(
progress: buildProgress,
);
final FileSystemEntity releaseArtifact;
try {
releaseArtifact = await releaser.buildReleaseArtifacts(
progress: buildProgress,
);
buildProgress.complete();
} on ArtifactBuildException catch (e) {
buildProgress.fail(e.message);
logger
..detail('stdout: ${e.stdout.join(Platform.lineTerminator)}')
..detail('stderr: ${e.stderr.join(Platform.lineTerminator)}');
if (!e.flutterError.isNullOrEmpty) {
logger.err(e.flutterError);
}
if (!e.fixRecommendation.isNullOrEmpty) {
logger.info(e.fixRecommendation);
}
if (e.fixRecommendation.isNullOrEmpty &&
e.flutterError.isNullOrEmpty) {
// If we have no fix recommendation or were unable to parse a
// flutter error, fall back to printing the raw stderr.
logger.info(e.stderr.join(Platform.lineTerminator));
}
throw ProcessExit(ExitCode.software.code);
} on Exception catch (e) {
buildProgress.fail('Failed to build release artifacts: $e');
throw ProcessExit(ExitCode.software.code);
}
final releaseVersion = await releaser.getReleaseVersion(
releaseArtifactRoot: releaseArtifact,
);
// Ensure we can create a release from what we've built.
await ensureVersionIsReleasable(
version: releaseVersion,
flutterRevision: targetFlutterRevision,
releasePlatform: releaser.releaseType.releasePlatform,
);
final dryRun = results['dry-run'] == true;
if (dryRun) {
logger
..info('No issues detected.')
..info('The server may enforce additional checks.');
throw ProcessExit(ExitCode.success.code);
}
// Ask the user to proceed (this is skipped when running via CI).
await confirmCreateRelease(
app: app,
releaseVersion: releaseVersion,
flutterVersion: targetFlutterRevision,
releasePlatform: releaser.releaseType.releasePlatform,
);
final release = await getOrCreateRelease(
version: releaseVersion,
releasePlatform: releaser.releaseType.releasePlatform,
);
await prepareRelease(release: release, releaser: releaser);
await releaser.uploadReleaseArtifacts(release: release, appId: appId);
await finalizeRelease(release: release, releaser: releaser);
buildProgress.complete();
} on ArtifactBuildException catch (e) {
buildProgress.fail(e.message);
logger
..success('''
..detail('stdout: ${e.stdout.join(Platform.lineTerminator)}')
..detail('stderr: ${e.stderr.join(Platform.lineTerminator)}');
if (!e.flutterError.isNullOrEmpty) {
logger.err(e.flutterError);
}
if (!e.fixRecommendation.isNullOrEmpty) {
logger.info(e.fixRecommendation);
}
if (e.fixRecommendation.isNullOrEmpty && e.flutterError.isNullOrEmpty) {
// If we have no fix recommendation or were unable to parse a
// flutter error, fall back to printing the raw stderr.
logger.info(e.stderr.join(Platform.lineTerminator));
}
throw ProcessExit(ExitCode.software.code);
} on Exception catch (e) {
buildProgress.fail('Failed to build release artifacts: $e');
throw ProcessExit(ExitCode.software.code);
}
final releaseVersion = await releaser.getReleaseVersion(
releaseArtifactRoot: releaseArtifact,
);
// Ensure we can create a release from what we've built.
await ensureVersionIsReleasable(
version: releaseVersion,
flutterRevision: targetFlutterRevision,
releasePlatform: releaser.releaseType.releasePlatform,
);
final dryRun = results['dry-run'] == true;
if (dryRun) {
logger
..info('No issues detected.')
..info('The server may enforce additional checks.');
throw ProcessExit(ExitCode.success.code);
}
// Ask the user to proceed (this is skipped when running via CI).
await confirmCreateRelease(
app: app,
releaseVersion: releaseVersion,
flutterVersion: targetFlutterRevision,
releasePlatform: releaser.releaseType.releasePlatform,
);
final release = await getOrCreateRelease(
version: releaseVersion,
releasePlatform: releaser.releaseType.releasePlatform,
);
await prepareRelease(release: release, releaser: releaser);
await releaser.uploadReleaseArtifacts(release: release, appId: appId);
await finalizeRelease(release: release, releaser: releaser);
logger
..success('''
Published Release ${release.version}!''')
..info(releaser.postReleaseInstructions);
..info(releaser.postReleaseInstructions);
printPatchInstructions(
releaser: releaser,
releaseVersion: releaseVersion,
releaseType: releaser.releaseType,
flavor: flavor,
target: target,
);
},
values: {
shorebirdEnvRef.overrideWith(() => releaseFlutterShorebirdEnv),
},
);
printPatchInstructions(
releaser: releaser,
releaseVersion: releaseVersion,
releaseType: releaser.releaseType,
flavor: flavor,
target: target,
);
}, values: {shorebirdEnvRef.overrideWith(() => releaseFlutterShorebirdEnv)});
}
/// Validates arguments that are common to all release types.
@@ -383,11 +371,9 @@ of the iOS app that is using this module. (aar and ios-framework only)''',
flutterVersionArg!,
);
} on Exception catch (error) {
logger.err(
'''
logger.err('''
Unable to determine revision for Flutter version: $flutterVersionArg.
$error''',
);
$error''');
throw ProcessExit(ExitCode.software.code);
}
@@ -398,12 +384,10 @@ $error''',
),
message: 'open an issue',
);
logger.err(
'''
logger.err('''
Version $flutterVersionArg not found. Please $openIssueLink to request a new version.
Use `shorebird flutter versions list` to list available versions.
''',
);
''');
throw ProcessExit(ExitCode.software.code);
}
@@ -452,7 +436,8 @@ Use `shorebird flutter versions list` to list available versions.
..err('''
${styleBold.wrap(lightRed.wrap('A release with version $version already exists but was built using a different Flutter revision.'))}
''')
..info('''
..info(
'''
Existing release built with: ${lightCyan.wrap(formattedExistingReleaseVersion)}
Current release built with: ${lightCyan.wrap(formattedCurrentReleaseVersion)}
@@ -462,7 +447,8 @@ ${styleBold.wrap(lightRed.wrap('All platforms for a given release must be built
To resolve this issue, you can:
* Re-run the release command with "${lightCyan.wrap('--flutter-version=${existingRelease.flutterRevision}')}".
* Delete the existing release and re-run the release command with the desired Flutter version.
* Bump the release version and re-run the release command with the desired Flutter version.''');
* Bump the release version and re-run the release command with the desired Flutter version.''',
);
throw ProcessExit(ExitCode.software.code);
}
}
@@ -583,12 +569,10 @@ ${summary.join('\n')}
);
if (!releaser.requiresReleaseVersionArg) {
logger.info(
'''
logger.info('''
Note: ${lightCyan.wrap(baseCommand)} without the --release-version option will patch the current version of the app.
''',
);
''');
}
}
}
@@ -66,27 +66,25 @@ To change the version of this release, change your app's version in your pubspec
final flutterVersionArg = argResults['flutter-version'] as String?;
if (flutterVersionArg != null) {
final version =
await shorebirdFlutter.resolveFlutterVersion(flutterVersionArg);
final gitHash =
await shorebirdFlutter.getRevisionForVersion(flutterVersionArg);
final version = await shorebirdFlutter.resolveFlutterVersion(
flutterVersionArg,
);
final gitHash = await shorebirdFlutter.getRevisionForVersion(
flutterVersionArg,
);
if (version != null &&
version < minimumSupportedWindowsFlutterVersion &&
!windowsFlutterGitHashesBelowMinVersion.contains(gitHash)) {
logger.err(
'''
logger.err('''
Windows releases are not supported with Flutter versions older than $minimumSupportedWindowsFlutterVersion.
For more information see: ${supportedFlutterVersionsUrl.toLink()}''',
);
For more information see: ${supportedFlutterVersionsUrl.toLink()}''');
throw ProcessExit(ExitCode.usage.code);
}
}
}
@override
Future<FileSystemEntity> buildReleaseArtifacts({
DetailProgress? progress,
}) {
Future<FileSystemEntity> buildReleaseArtifacts({DetailProgress? progress}) {
return artifactBuilder.buildWindowsApp(
flavor: flavor,
target: target,
@@ -142,10 +142,7 @@ class GetApksCommand extends ShorebirdCommand {
)..createSync(recursive: true);
}
await extractFileToDisk(
apksZipFile.path,
outputDirectory.path,
);
await extractFileToDisk(apksZipFile.path, outputDirectory.path);
logger.info('apk(s) generated at ${lightCyan.wrap(outputDirectory.path)}');
return ExitCode.success.code;
@@ -169,9 +166,7 @@ class GetApksCommand extends ShorebirdCommand {
);
}
Future<File> _downloadAab({
required ReleaseArtifact releaseArtifact,
}) async {
Future<File> _downloadAab({required ReleaseArtifact releaseArtifact}) async {
final File artifactFile;
try {
artifactFile = await artifactManager.downloadWithProgressUpdates(
@@ -16,11 +16,7 @@ class RunCommand extends ShorebirdCommand {
/// {@macro run_command}
RunCommand() {
argParser
..addOption(
'device-id',
abbr: 'd',
help: 'Target device id or name.',
)
..addOption('device-id', abbr: 'd', help: 'Target device id or name.')
..addOption(
'target',
abbr: 't',
@@ -28,7 +24,8 @@ class RunCommand extends ShorebirdCommand {
)
..addMultiOption(
'dart-define',
help: 'Additional key-value pairs that will be available as constants '
help:
'Additional key-value pairs that will be available as constants '
'''from the String.fromEnvironment, bool.fromEnvironment, and int.fromEnvironment '''
'constructors.\n'
'''Multiple defines can be passed by repeating "--dart-define" multiple times.''',
@@ -52,11 +49,9 @@ class RunCommand extends ShorebirdCommand {
@override
Future<int> run() async {
logger.warn(
'''
logger.warn('''
This command is deprecated and will be removed in a future release.
Please use "shorebird preview" instead.''',
);
Please use "shorebird preview" instead.''');
// TODO(bryanoltman): check run target and run either
// doctor.iosValidators or doctor.androidValidators as appropriate.
@@ -75,20 +70,16 @@ Please use "shorebird preview" instead.''',
final flavor = results['flavor'] as String?;
final target = results['target'] as String?;
final dartDefines = results['dart-define'] as List<String>?;
final flutter = await process.start(
'flutter',
[
'run',
// Eventually we should support running in both debug and release mode.
'--release',
if (deviceId != null) '--device-id=$deviceId',
if (flavor != null) '--flavor=$flavor',
if (target != null) '--target=$target',
if (dartDefines != null) ...dartDefines.map((e) => '--dart-define=$e'),
...results.rest,
],
runInShell: true,
);
final flutter = await process.start('flutter', [
'run',
// Eventually we should support running in both debug and release mode.
'--release',
if (deviceId != null) '--device-id=$deviceId',
if (flavor != null) '--flavor=$flavor',
if (target != null) '--target=$target',
if (dartDefines != null) ...dartDefines.map((e) => '--dart-define=$e'),
...results.rest,
], runInShell: true);
flutter.stdout.listen((event) {
logger.info(utf8.decode(event));
@@ -5,10 +5,7 @@ part 'shorebird_yaml.g.dart';
/// {@template shorebird_yaml}
/// A Shorebird configuration file which contains metadata about the app.
/// {@endtemplate}
@JsonSerializable(
anyMap: true,
disallowUnrecognizedKeys: true,
)
@JsonSerializable(anyMap: true, disallowUnrecognizedKeys: true)
class ShorebirdYaml {
/// {@macro shorebird_yaml}
const ShorebirdYaml({
@@ -9,31 +9,30 @@ part of 'shorebird_yaml.dart';
// **************************************************************************
ShorebirdYaml _$ShorebirdYamlFromJson(Map json) => $checkedCreate(
'ShorebirdYaml',
'ShorebirdYaml',
json,
($checkedConvert) {
$checkKeys(
json,
($checkedConvert) {
$checkKeys(
json,
allowedKeys: const ['app_id', 'flavors', 'base_url', 'auto_update'],
);
final val = ShorebirdYaml(
appId: $checkedConvert('app_id', (v) => v as String),
flavors: $checkedConvert(
'flavors',
(v) => (v as Map?)?.map(
(k, e) => MapEntry(k as String, e as String),
)),
baseUrl: $checkedConvert('base_url', (v) => v as String?),
autoUpdate: $checkedConvert('auto_update', (v) => v as bool?),
);
return val;
},
fieldKeyMap: const {
'appId': 'app_id',
'baseUrl': 'base_url',
'autoUpdate': 'auto_update'
},
allowedKeys: const ['app_id', 'flavors', 'base_url', 'auto_update'],
);
final val = ShorebirdYaml(
appId: $checkedConvert('app_id', (v) => v as String),
flavors: $checkedConvert(
'flavors',
(v) => (v as Map?)?.map((k, e) => MapEntry(k as String, e as String)),
),
baseUrl: $checkedConvert('base_url', (v) => v as String?),
autoUpdate: $checkedConvert('auto_update', (v) => v as bool?),
);
return val;
},
fieldKeyMap: const {
'appId': 'app_id',
'baseUrl': 'base_url',
'autoUpdate': 'auto_update',
},
);
Map<String, dynamic> _$ShorebirdYamlToJson(ShorebirdYaml instance) =>
<String, dynamic>{
+1 -3
View File
@@ -28,9 +28,7 @@ class Doctor {
final List<Validator> linuxCommandValidators = [];
/// Validators that verify shorebird will work on macOS.
final List<Validator> macosCommandValidators = [
MacosEntitlementsValidator(),
];
final List<Validator> macosCommandValidators = [MacosEntitlementsValidator()];
/// Validators that verify shorebird will work on Windows.
final List<Validator> windowsCommandValidators = [
@@ -19,9 +19,9 @@ class EngineConfig {
/// An empty [EngineConfig] instance.
const EngineConfig.empty()
: localEngineSrcPath = null,
localEngine = null,
localEngineHost = null;
: localEngineSrcPath = null,
localEngine = null,
localEngineHost = null;
/// The path to the local engine source.
final String? localEngineSrcPath;
@@ -43,10 +43,7 @@ class Adb {
}
/// Starts the app with the given [package] name.
Future<void> startApp({
required String package,
String? deviceId,
}) async {
Future<void> startApp({required String package, String? deviceId}) async {
final args = [
if (deviceId != null) ...['-s', deviceId],
'shell',
@@ -64,10 +61,7 @@ class Adb {
}
/// Runs `adb logcat`.
Future<Process> logcat({
String? filter,
String? deviceId,
}) async {
Future<Process> logcat({String? filter, String? deviceId}) async {
final args = [
if (deviceId != null) ...['-s', deviceId],
'logcat',
@@ -152,19 +152,19 @@ class AotTools {
final stdout = StringBuffer();
final stderr = StringBuffer();
final stdoutSubscription = subprocess.stdout.map(utf8.decode).listen(
(data) {
logger.detail(data);
stdout.write(data);
},
);
final stdoutSubscription = subprocess.stdout.map(utf8.decode).listen((
data,
) {
logger.detail(data);
stdout.write(data);
});
final stderrSubscription = subprocess.stderr.map(utf8.decode).listen(
(data) {
logger.detail(data);
stderr.write(data);
},
);
final stderrSubscription = subprocess.stderr.map(utf8.decode).listen((
data,
) {
logger.detail(data);
stderr.write(data);
});
final exitCode = await subprocess.exitCode;
@@ -189,11 +189,11 @@ class AotTools {
);
} else {
// local engine versions use .dart and we distribute aot-tools as a .dill
result = await execute(
shorebirdEnv.dartBinaryFile.path,
['run', artifactPath, ...command],
workingDirectory: workingDirectory,
);
result = await execute(shorebirdEnv.dartBinaryFile.path, [
'run',
artifactPath,
...command,
], workingDirectory: workingDirectory);
}
if (throwOnError && result.exitCode != 0) {
@@ -249,28 +249,22 @@ class AotTools {
const linkJson = 'link.jsonl';
final outputDir = p.dirname(outputPath);
final linkerUsesGenSnapshot = await _linkerUsesGenSnapshot();
await _exec(
[
'link',
'--base=$base',
'--patch=$patch',
'--analyze-snapshot=$analyzeSnapshot',
'--output=$outputPath',
'--verbose',
if (linkerUsesGenSnapshot) ...[
'--gen-snapshot=$genSnapshot',
'--kernel=$kernel',
'--reporter=json',
'--redirect-to=${p.join(outputDir, linkJson)}',
],
if (dumpDebugInfoPath != null) '--dump-debug-info=$dumpDebugInfoPath',
if (additionalArgs.isNotEmpty) ...[
'--',
...additionalArgs,
],
await _exec([
'link',
'--base=$base',
'--patch=$patch',
'--analyze-snapshot=$analyzeSnapshot',
'--output=$outputPath',
'--verbose',
if (linkerUsesGenSnapshot) ...[
'--gen-snapshot=$genSnapshot',
'--kernel=$kernel',
'--reporter=json',
'--redirect-to=${p.join(outputDir, linkJson)}',
],
workingDirectory: workingDirectory,
);
if (dumpDebugInfoPath != null) '--dump-debug-info=$dumpDebugInfoPath',
if (additionalArgs.isNotEmpty) ...['--', ...additionalArgs],
], workingDirectory: workingDirectory);
return linkerUsesGenSnapshot
? _extractLinkPercentage(File(p.join(workingDirectory!, linkJson)))
@@ -279,11 +273,12 @@ class AotTools {
double? _extractLinkPercentage(File file) {
if (!file.existsSync()) return null;
final status = const LineSplitter()
.convert(file.readAsStringSync())
.map(json.decode)
.cast<Map<String, dynamic>>()
.toList();
final status =
const LineSplitter()
.convert(file.readAsStringSync())
.map(json.decode)
.cast<Map<String, dynamic>>()
.toList();
final linkSuccess = status.firstWhereOrNull(
(line) => line['type'] == 'link_success',
@@ -312,14 +307,12 @@ class AotTools {
}) async {
final tmpDir = Directory.systemTemp.createTempSync();
final outFile = File(p.join(tmpDir.path, 'diff_base'));
await _exec(
[
'dump_blobs',
'--analyze-snapshot=$analyzeSnapshotPath',
'--output=${outFile.path}',
'--snapshot=${releaseSnapshot.path}',
],
);
await _exec([
'dump_blobs',
'--analyze-snapshot=$analyzeSnapshotPath',
'--output=${outFile.path}',
'--snapshot=${releaseSnapshot.path}',
]);
if (!outFile.existsSync()) {
throw Exception(
@@ -50,19 +50,17 @@ class Bundletool {
String? keyPassword,
String? keyAlias,
}) async {
final result = await _exec(
[
'build-apks',
'--overwrite',
'--bundle=$bundle',
'--output=$output',
if (universal) '--mode=universal',
if (keystore != null) '--ks=$keystore',
if (keystorePassword != null) '--ks-pass=$keystorePassword',
if (keyPassword != null) '--key-pass=$keyPassword',
if (keyAlias != null) '--ks-key-alias=$keyAlias',
],
);
final result = await _exec([
'build-apks',
'--overwrite',
'--bundle=$bundle',
'--output=$output',
if (universal) '--mode=universal',
if (keystore != null) '--ks=$keystore',
if (keystorePassword != null) '--ks-pass=$keystorePassword',
if (keyPassword != null) '--key-pass=$keyPassword',
if (keyAlias != null) '--ks-key-alias=$keyAlias',
]);
if (result.exitCode != 0) {
throw Exception('Failed to build apks: ${result.stderr}');
}
@@ -73,10 +71,7 @@ class Bundletool {
/// e.g. `bundletool install-apks --apks=/MyApp/my_app.apks --allow-downgrade`
///
/// https://developer.android.com/tools/bundletool#deploy_with_bundletool
Future<void> installApks({
required String apks,
String? deviceId,
}) async {
Future<void> installApks({required String apks, String? deviceId}) async {
final args = [
'install-apks',
'--apks=$apks',
@@ -91,15 +86,13 @@ class Bundletool {
/// Extract the package name from an app bundle.
Future<String> getPackageName(String appBundlePath) async {
final result = await _exec(
[
'dump',
'manifest',
'--bundle=$appBundlePath',
'--xpath',
'/manifest/@package',
],
);
final result = await _exec([
'dump',
'manifest',
'--bundle=$appBundlePath',
'--xpath',
'/manifest/@package',
]);
if (result.exitCode != 0) {
throw Exception(
@@ -112,15 +105,13 @@ class Bundletool {
/// Extract the version name from an app bundle.
Future<String> getVersionName(String appBundlePath) async {
final result = await _exec(
[
'dump',
'manifest',
'--bundle=$appBundlePath',
'--xpath',
'/manifest/@android:versionName',
],
);
final result = await _exec([
'dump',
'manifest',
'--bundle=$appBundlePath',
'--xpath',
'/manifest/@android:versionName',
]);
if (result.exitCode != 0) {
throw Exception(
@@ -133,15 +124,13 @@ class Bundletool {
/// Extract the version code from an app bundle.
Future<String> getVersionCode(String appBundlePath) async {
final result = await _exec(
[
'dump',
'manifest',
'--bundle=$appBundlePath',
'--xpath',
'/manifest/@android:versionCode',
],
);
final result = await _exec([
'dump',
'manifest',
'--bundle=$appBundlePath',
'--xpath',
'/manifest/@android:versionCode',
]);
if (result.exitCode != 0) {
throw Exception(
@@ -10,7 +10,6 @@ part 'apple_device.g.dart';
/// `xcrun devicectl list devices`.
/// {@endtemplate}
@JsonSerializable(createToJson: false, fieldRename: FieldRename.none)
/// {@macro apple_device}
class AppleDevice {
/// {@macro apple_device}
@@ -8,59 +8,52 @@ part of 'apple_device.dart';
// JsonSerializableGenerator
// **************************************************************************
AppleDevice _$AppleDeviceFromJson(Map<String, dynamic> json) => $checkedCreate(
'AppleDevice',
json,
($checkedConvert) {
final val = AppleDevice(
deviceProperties: $checkedConvert('deviceProperties',
(v) => DeviceProperties.fromJson(v as Map<String, dynamic>)),
hardwareProperties: $checkedConvert('hardwareProperties',
(v) => HardwareProperties.fromJson(v as Map<String, dynamic>)),
connectionProperties: $checkedConvert('connectionProperties',
(v) => ConnectionProperties.fromJson(v as Map<String, dynamic>)),
);
return val;
},
);
AppleDevice _$AppleDeviceFromJson(Map<String, dynamic> json) =>
$checkedCreate('AppleDevice', json, ($checkedConvert) {
final val = AppleDevice(
deviceProperties: $checkedConvert(
'deviceProperties',
(v) => DeviceProperties.fromJson(v as Map<String, dynamic>),
),
hardwareProperties: $checkedConvert(
'hardwareProperties',
(v) => HardwareProperties.fromJson(v as Map<String, dynamic>),
),
connectionProperties: $checkedConvert(
'connectionProperties',
(v) => ConnectionProperties.fromJson(v as Map<String, dynamic>),
),
);
return val;
});
HardwareProperties _$HardwarePropertiesFromJson(Map<String, dynamic> json) =>
$checkedCreate(
'HardwareProperties',
json,
($checkedConvert) {
final val = HardwareProperties(
platform: $checkedConvert('platform', (v) => v as String),
udid: $checkedConvert('udid', (v) => v as String),
);
return val;
},
);
$checkedCreate('HardwareProperties', json, ($checkedConvert) {
final val = HardwareProperties(
platform: $checkedConvert('platform', (v) => v as String),
udid: $checkedConvert('udid', (v) => v as String),
);
return val;
});
DeviceProperties _$DevicePropertiesFromJson(Map<String, dynamic> json) =>
$checkedCreate(
'DeviceProperties',
json,
($checkedConvert) {
final val = DeviceProperties(
name: $checkedConvert('name', (v) => v as String),
osVersionNumber:
$checkedConvert('osVersionNumber', (v) => v as String?),
);
return val;
},
);
$checkedCreate('DeviceProperties', json, ($checkedConvert) {
final val = DeviceProperties(
name: $checkedConvert('name', (v) => v as String),
osVersionNumber: $checkedConvert(
'osVersionNumber',
(v) => v as String?,
),
);
return val;
});
ConnectionProperties _$ConnectionPropertiesFromJson(
Map<String, dynamic> json) =>
$checkedCreate(
'ConnectionProperties',
json,
($checkedConvert) {
final val = ConnectionProperties(
tunnelState: $checkedConvert('tunnelState', (v) => v as String),
transportType: $checkedConvert('transportType', (v) => v as String?),
);
return val;
},
);
Map<String, dynamic> json,
) => $checkedCreate('ConnectionProperties', json, ($checkedConvert) {
final val = ConnectionProperties(
tunnelState: $checkedConvert('tunnelState', (v) => v as String),
transportType: $checkedConvert('transportType', (v) => v as String?),
);
return val;
});
@@ -22,10 +22,7 @@ typedef BundleId = String;
/// {@endtemplate}
class DevicectlException implements Exception {
/// {@macro devicectl_exception}
DevicectlException({
required this.message,
this.underlyingException,
});
DevicectlException({required this.message, this.underlyingException});
/// A message describing this exception.
final String message;
@@ -116,10 +113,10 @@ class Devicectl {
final String bundleId;
try {
final maybeBundleId =
JsonPath(r'$.result.installedApplications[0].bundleID')
.read(jsonResult)
.firstOrNull
?.value as String?;
JsonPath(
r'$.result.installedApplications[0].bundleID',
).read(jsonResult).firstOrNull?.value
as String?;
if (maybeBundleId == null) {
throw Exception(
'Unable to find installed app bundleID in devicectl output',
@@ -42,11 +42,7 @@ NSError(
)''';
@override
List<Object> get props => [
code,
domain,
userInfo,
];
List<Object> get props => [code, domain, userInfo];
}
/// {@template user_info}
@@ -98,11 +94,11 @@ UserInfo(
@override
List<Object?> get props => [
description,
localizedDescription,
localizedFailureReason,
underlyingError,
];
description,
localizedDescription,
localizedFailureReason,
underlyingError,
];
}
/// {@template string_container}
@@ -8,103 +8,97 @@ part of 'nserror.dart';
// JsonSerializableGenerator
// **************************************************************************
NSError _$NSErrorFromJson(Map<String, dynamic> json) => $checkedCreate(
'NSError',
json,
($checkedConvert) {
final val = NSError(
code: $checkedConvert('code', (v) => (v as num).toInt()),
domain: $checkedConvert('domain', (v) => v as String),
userInfo: $checkedConvert(
'userInfo', (v) => UserInfo.fromJson(v as Map<String, dynamic>)),
);
return val;
},
);
NSError _$NSErrorFromJson(Map<String, dynamic> json) =>
$checkedCreate('NSError', json, ($checkedConvert) {
final val = NSError(
code: $checkedConvert('code', (v) => (v as num).toInt()),
domain: $checkedConvert('domain', (v) => v as String),
userInfo: $checkedConvert(
'userInfo',
(v) => UserInfo.fromJson(v as Map<String, dynamic>),
),
);
return val;
});
Map<String, dynamic> _$NSErrorToJson(NSError instance) => <String, dynamic>{
'code': instance.code,
'domain': instance.domain,
'userInfo': instance.userInfo.toJson(),
};
'code': instance.code,
'domain': instance.domain,
'userInfo': instance.userInfo.toJson(),
};
UserInfo _$UserInfoFromJson(Map<String, dynamic> json) => $checkedCreate(
'UserInfo',
json,
($checkedConvert) {
final val = UserInfo(
description: $checkedConvert(
'NSDescription',
(v) => v == null
? null
: StringContainer.fromJson(v as Map<String, dynamic>)),
localizedDescription: $checkedConvert(
'NSLocalizedDescription',
(v) => v == null
? null
: StringContainer.fromJson(v as Map<String, dynamic>)),
localizedFailureReason: $checkedConvert(
'NSLocalizedFailureReason',
(v) => v == null
? null
: StringContainer.fromJson(v as Map<String, dynamic>)),
underlyingError: $checkedConvert(
'NSUnderlyingError',
(v) => v == null
? null
: NSUnderlyingError.fromJson(v as Map<String, dynamic>)),
);
return val;
},
fieldKeyMap: const {
'description': 'NSDescription',
'localizedDescription': 'NSLocalizedDescription',
'localizedFailureReason': 'NSLocalizedFailureReason',
'underlyingError': 'NSUnderlyingError'
},
'UserInfo',
json,
($checkedConvert) {
final val = UserInfo(
description: $checkedConvert(
'NSDescription',
(v) =>
v == null
? null
: StringContainer.fromJson(v as Map<String, dynamic>),
),
localizedDescription: $checkedConvert(
'NSLocalizedDescription',
(v) =>
v == null
? null
: StringContainer.fromJson(v as Map<String, dynamic>),
),
localizedFailureReason: $checkedConvert(
'NSLocalizedFailureReason',
(v) =>
v == null
? null
: StringContainer.fromJson(v as Map<String, dynamic>),
),
underlyingError: $checkedConvert(
'NSUnderlyingError',
(v) =>
v == null
? null
: NSUnderlyingError.fromJson(v as Map<String, dynamic>),
),
);
return val;
},
fieldKeyMap: const {
'description': 'NSDescription',
'localizedDescription': 'NSLocalizedDescription',
'localizedFailureReason': 'NSLocalizedFailureReason',
'underlyingError': 'NSUnderlyingError',
},
);
Map<String, dynamic> _$UserInfoToJson(UserInfo instance) => <String, dynamic>{
'NSDescription': instance.description?.toJson(),
'NSLocalizedDescription': instance.localizedDescription?.toJson(),
'NSLocalizedFailureReason': instance.localizedFailureReason?.toJson(),
'NSUnderlyingError': instance.underlyingError?.toJson(),
};
'NSDescription': instance.description?.toJson(),
'NSLocalizedDescription': instance.localizedDescription?.toJson(),
'NSLocalizedFailureReason': instance.localizedFailureReason?.toJson(),
'NSUnderlyingError': instance.underlyingError?.toJson(),
};
StringContainer _$StringContainerFromJson(Map<String, dynamic> json) =>
$checkedCreate(
'StringContainer',
json,
($checkedConvert) {
final val = StringContainer(
$checkedConvert('string', (v) => v as String),
);
return val;
},
);
$checkedCreate('StringContainer', json, ($checkedConvert) {
final val = StringContainer(
$checkedConvert('string', (v) => v as String),
);
return val;
});
Map<String, dynamic> _$StringContainerToJson(StringContainer instance) =>
<String, dynamic>{
'string': instance.string,
};
<String, dynamic>{'string': instance.string};
NSUnderlyingError _$NSUnderlyingErrorFromJson(Map<String, dynamic> json) =>
$checkedCreate(
'NSUnderlyingError',
json,
($checkedConvert) {
final val = NSUnderlyingError(
error: $checkedConvert(
'error',
(v) => v == null
? null
: NSError.fromJson(v as Map<String, dynamic>)),
);
return val;
},
);
$checkedCreate('NSUnderlyingError', json, ($checkedConvert) {
final val = NSUnderlyingError(
error: $checkedConvert(
'error',
(v) => v == null ? null : NSError.fromJson(v as Map<String, dynamic>),
),
);
return val;
});
Map<String, dynamic> _$NSUnderlyingErrorToJson(NSUnderlyingError instance) =>
<String, dynamic>{
'error': instance.error?.toJson(),
};
<String, dynamic>{'error': instance.error?.toJson()};
@@ -45,15 +45,7 @@ class Git {
required String outputDirectory,
List<String>? args,
}) async {
await git(
[
'clone',
url,
...?args,
outputDirectory,
],
runInShell: true,
);
await git(['clone', url, ...?args, outputDirectory], runInShell: true);
}
/// Checks out the git repository located at [directory] to the [revision].
@@ -61,17 +53,14 @@ class Git {
required String directory,
required String revision,
}) async {
await git(
[
'-C',
directory,
'-c',
'advice.detachedHead=false',
'checkout',
revision,
],
runInShell: true,
);
await git([
'-C',
directory,
'-c',
'advice.detachedHead=false',
'checkout',
revision,
], runInShell: true);
}
/// Fetch branches/tags from the repository at [directory].
@@ -80,10 +69,7 @@ class Git {
}
/// Run `git remote` at [directory].
Future<void> remote({
required String directory,
List<String>? args,
}) async {
Future<void> remote({required String directory, List<String>? args}) async {
await git(['remote', ...?args], workingDirectory: directory);
}
@@ -95,16 +81,13 @@ class Git {
required String pattern,
String? contains,
}) async {
final result = await git(
[
'for-each-ref',
if (contains != null) ...['--contains', contains],
'--format',
format,
pattern,
],
workingDirectory: directory,
);
final result = await git([
'for-each-ref',
if (contains != null) ...['--contains', contains],
'--format',
format,
pattern,
], workingDirectory: directory);
return '${result.stdout}'.trim();
}
@@ -122,10 +105,11 @@ class Git {
required String revision,
required String directory,
}) async {
final result = await git(
['rev-parse', '--verify', revision],
workingDirectory: directory,
);
final result = await git([
'rev-parse',
'--verify',
revision,
], workingDirectory: directory);
return '${result.stdout}'.trim();
}
@@ -141,18 +125,19 @@ class Git {
required Directory directory,
String revision = 'HEAD',
}) async {
final result = await git(
['symbolic-ref', revision],
workingDirectory: directory.path,
);
final result = await git([
'symbolic-ref',
revision,
], workingDirectory: directory.path);
return '${result.stdout}'.trim();
}
/// Returns the name of the branch the git repository located at [directory]
/// is currently on.
Future<String> currentBranch({required Directory directory}) async {
return (await symbolicRef(directory: directory))
.replaceAll('refs/heads/', '');
return (await symbolicRef(
directory: directory,
)).replaceAll('refs/heads/', '');
}
/// Whether [directory] is part of a git repository.
@@ -160,10 +145,7 @@ class Git {
try {
// [git] throws if the command's exit code is nonzero, which is what we're
// checking for here.
await git(
['status'],
workingDirectory: directory.path,
);
await git(['status'], workingDirectory: directory.path);
} on Exception {
return false;
}
@@ -176,14 +158,11 @@ class Git {
try {
// [git] throws if the command's exit code is nonzero, which is what we're
// checking for here.
await git(
[
'ls-files',
'--error-unmatch',
file.absolute.path,
],
workingDirectory: file.parent.path,
);
await git([
'ls-files',
'--error-unmatch',
file.absolute.path,
], workingDirectory: file.parent.path);
} on Exception {
return false;
}
@@ -107,15 +107,13 @@ class Gradlew {
args,
runInShell: true,
workingDirectory: p.dirname(executablePath),
environment: {
if (!javaHome.isNullOrEmpty) 'JAVA_HOME': javaHome!,
},
environment: {if (!javaHome.isNullOrEmpty) 'JAVA_HOME': javaHome!},
);
if (result.exitCode != ExitCode.success.code) {
if (result.stderr
.toString()
.contains(IncompatibleGradleException.errorPattern)) {
if (result.stderr.toString().contains(
IncompatibleGradleException.errorPattern,
)) {
throw IncompatibleGradleException();
}
}
@@ -142,10 +140,11 @@ class Gradlew {
/// Return the set of product flavors configured for the app at [projectRoot].
/// Returns an empty set for apps that do not use product flavors.
Future<Set<String>> productFlavors(String projectRoot) async {
final result = await _run(
['app:tasks', '--all', '--console=auto'],
projectRoot,
);
final result = await _run([
'app:tasks',
'--all',
'--console=auto',
], projectRoot);
if (result.exitCode != 0) {
throw Exception('${result.stdout}\n${result.stderr}');
@@ -22,19 +22,18 @@ class IDeviceSysLog {
/// The location of the libimobiledevice library, which contains
/// idevicesyslog.
static Directory get libimobiledeviceDirectory => Directory(
p.join(
shorebirdEnv.flutterDirectory.path,
'bin',
'cache',
'artifacts',
'libimobiledevice',
),
);
p.join(
shorebirdEnv.flutterDirectory.path,
'bin',
'cache',
'artifacts',
'libimobiledevice',
),
);
/// The location of the idevicesyslog executable.
static File get idevicesyslogExecutable => File(
p.join(libimobiledeviceDirectory.path, 'idevicesyslog'),
);
static File get idevicesyslogExecutable =>
File(p.join(libimobiledeviceDirectory.path, 'idevicesyslog'));
/// The libraries that idevicesyslog depends on.
@visibleForTesting
@@ -86,9 +85,7 @@ class IDeviceSysLog {
// network flag.
if (!device.isWired) '--network',
],
environment: {
'DYLD_LIBRARY_PATH': _dyldPathEntry,
},
environment: {'DYLD_LIBRARY_PATH': _dyldPathEntry},
);
loggerProcess.stdout
@@ -17,11 +17,7 @@ final iosDeployRef = create(IOSDeploy.new);
IOSDeploy get iosDeploy => read(iosDeployRef);
/// lldb debugger state.
enum _DebuggerState {
detached,
launching,
attached,
}
enum _DebuggerState { detached, launching, attached }
/// {@template ios_deploy}
/// Wrapper around the `ios-deploy` command cached by the Flutter tool.
@@ -30,30 +26,31 @@ enum _DebuggerState {
class IOSDeploy {
/// {@macro ios_deploy}
const IOSDeploy({ProcessSignal? sigint})
: _sigint = sigint ?? ProcessSignal.sigint;
: _sigint = sigint ?? ProcessSignal.sigint;
final ProcessSignal _sigint;
/// The location of the ios-deploy executable.
@visibleForTesting
static File get iosDeployExecutable => File(
p.join(
shorebirdEnv.flutterDirectory.path,
'bin',
'cache',
'artifacts',
'ios-deploy',
'ios-deploy',
),
);
p.join(
shorebirdEnv.flutterDirectory.path,
'bin',
'cache',
'artifacts',
'ios-deploy',
'ios-deploy',
),
);
static bool get _isInstalled => iosDeployExecutable.existsSync();
// (lldb) platform select remote-'ios' --sysroot
// This regex is to get the configurable lldb prompt.
// By default this prompt will be "lldb".
static final _lldbPlatformSelect =
RegExp(r"\s*platform select remote-'ios' --sysroot");
static final _lldbPlatformSelect = RegExp(
r"\s*platform select remote-'ios' --sysroot",
);
// (lldb) run
static final _lldbProcessExit = RegExp(r'Process \d* exited with status =');
@@ -172,16 +169,13 @@ Or run on an iOS simulator without code signing
});
try {
launchProcess = await process.start(
iosDeployExecutable.path,
[
'--debug',
if (deviceId != null) ...['--id', deviceId],
'-r', // uninstall the app before reinstalling and clear app data
'--bundle',
bundlePath,
],
);
launchProcess = await process.start(iosDeployExecutable.path, [
'--debug',
if (deviceId != null) ...['--id', deviceId],
'-r', // uninstall the app before reinstalling and clear app data
'--bundle',
bundlePath,
]);
void detach() {
if (debuggerState.isNotAttached) return;
@@ -294,11 +288,13 @@ Or run on an iOS simulator without code signing
logger.detail(line);
}
final stdoutSubscription =
launchProcess.stdout.asLines().listen(onStdout);
final stdoutSubscription = launchProcess.stdout.asLines().listen(
onStdout,
);
final stderrSubscription =
launchProcess.stderr.asLines().listen(onStderr);
final stderrSubscription = launchProcess.stderr.asLines().listen(
onStderr,
);
final status = await launchProcess.exitCode;
logger.detail('[ios-deploy] exited with code: $exitCode');
@@ -343,7 +339,7 @@ Or run on an iOS simulator without code signing
String detectFailures(String line, Logger logger) {
final isMissingProvisioningProfile =
line.contains(IOSDeploy.noProvisioningProfileErrorOne) ||
line.contains(IOSDeploy.noProvisioningProfileErrorTwo);
line.contains(IOSDeploy.noProvisioningProfileErrorTwo);
// No provisioning profile.
if (isMissingProvisioningProfile) {
@@ -351,7 +347,8 @@ String detectFailures(String line, Logger logger) {
return line;
}
final isDeviceLocked = line.contains(IOSDeploy.deviceLockedError) ||
final isDeviceLocked =
line.contains(IOSDeploy.deviceLockedError) ||
line.contains(IOSDeploy.deviceLockedErrorMessage);
if (isDeviceLocked) {
@@ -378,7 +375,8 @@ extension on _DebuggerState {
extension on Stream<List<int>> {
Stream<String> asLines() {
return transform<String>(utf8.decoder)
.transform<String>(const LineSplitter());
return transform<String>(
utf8.decoder,
).transform<String>(const LineSplitter());
}
}
@@ -17,21 +17,18 @@ class Open {
/// Opens a new application at the provided [path] and streams the stdout and
/// stderr.
Future<Stream<List<int>>> newApplication({required String path}) async {
final app = Directory(p.join(path, 'Contents', 'MacOS'))
.listSync()
.firstWhere((f) => f is File);
final app = Directory(
p.join(path, 'Contents', 'MacOS'),
).listSync().firstWhere((f) => f is File);
await process.start('open', ['-n', path]);
final logStreamProcess = await process.start(
'log',
[
'stream',
'--style=compact',
'--process',
p.basenameWithoutExtension(app.path),
],
);
final logStreamProcess = await process.start('log', [
'stream',
'--style=compact',
'--process',
p.basenameWithoutExtension(app.path),
]);
return logStreamProcess.stdout;
}
@@ -41,11 +41,7 @@ class PatchExecutable {
cache.getArtifactDirectory('patch').path,
'patch',
);
final diffArguments = [
releaseArtifactPath,
patchArtifactPath,
diffPath,
];
final diffArguments = [releaseArtifactPath, patchArtifactPath, diffPath];
final result = await process.run(diffExecutable, diffArguments);
@@ -67,12 +63,10 @@ Please try again once you have installed this software.
}
if (result.exitCode != ExitCode.success.code) {
throw PatchFailedException(
'''
throw PatchFailedException('''
Failed to create diff (exit code ${result.exitCode}). $messageDetails
stdout: ${result.stdout}
stderr: ${result.stderr}''',
);
stderr: ${result.stderr}''');
}
}
}
@@ -21,11 +21,7 @@ class Powershell {
String? workingDirectory,
bool runInShell = false,
}) async {
final result = await process.run(
executable,
arguments,
runInShell: true,
);
final result = await process.run(executable, arguments, runInShell: true);
if (result.exitCode != ExitCode.success.code) {
throw ProcessException(
executable,
@@ -42,10 +38,7 @@ class Powershell {
final exePath = exeFile.path;
final pwshCommand = '(Get-Item -Path $exePath).VersionInfo.ProductVersion';
final result = await pwsh(
['-Command', pwshCommand],
runInShell: true,
);
final result = await pwsh(['-Command', pwshCommand], runInShell: true);
var versionString = (result.stdout as String).trim();
if (!versionString.contains('+')) {
@@ -41,11 +41,7 @@ class ShorebirdTools {
/// The directory containing the `shorebird_tools` package.
Directory get shorebirdToolsDirectory {
final dir = Directory(
p.join(
shorebirdEnv.flutterDirectory.path,
'packages',
'shorebird_tools',
),
p.join(shorebirdEnv.flutterDirectory.path, 'packages', 'shorebird_tools'),
);
return dir;
}
@@ -53,12 +49,7 @@ class ShorebirdTools {
Future<ShorebirdProcessResult> _run(List<String> args) {
return process.run(
shorebirdEnv.dartBinaryFile.path,
[
'run',
'shorebird_tools',
'package',
...args,
],
['run', 'shorebird_tools', 'package', ...args],
workingDirectory: shorebirdToolsDirectory.path,
);
}
@@ -70,22 +61,15 @@ class ShorebirdTools {
required String patchPath,
required String outputPath,
}) async {
final packageArguments = [
'-p',
patchPath,
'-o',
outputPath,
];
final packageArguments = ['-p', patchPath, '-o', outputPath];
final result = await _run(packageArguments);
if (result.exitCode != ExitCode.success.code) {
throw PackageFailedException(
'''
throw PackageFailedException('''
Failed to create package (exit code ${result.exitCode}).
stdout: ${result.stdout}
stderr: ${result.stderr}''',
);
stderr: ${result.stderr}''');
}
}
}
@@ -14,10 +14,7 @@ import 'package:shorebird_cli/src/third_party/flutter_tools/lib/flutter_tools.da
extension OptionFinder on ArgResults {
/// Detects flags even when passed to underlying commands via a `--`
/// separator.
String? findOption(
String name, {
required ArgParser argParser,
}) {
String? findOption(String name, {required ArgParser argParser}) {
if (wasParsed(name)) {
return this[name] as String?;
}
@@ -25,14 +22,12 @@ extension OptionFinder on ArgResults {
// We would ideally check for abbreviations here as well, but ArgResults
// doesn't expose its parser (which we could use to get the list of
// [Options] being parsed) or an abbreviations map.
final abbr = argParser.options.values
.firstWhereOrNull((option) => option.name == name)
?.abbr;
final abbr =
argParser.options.values
.firstWhereOrNull((option) => option.name == name)
?.abbr;
final flagsToCheck = [
'--$name',
if (abbr != null) '-$abbr',
];
final flagsToCheck = ['--$name', if (abbr != null) '-$abbr'];
for (var i = 0; i < rest.length; i++) {
for (final flag in flagsToCheck) {
@@ -133,17 +128,15 @@ extension ForwardedArgs on ArgResults {
forwarded = rest.toList();
}
forwarded.addAll(
[
..._argsNamed(CommonArguments.dartDefineArg.name),
..._argsNamed(CommonArguments.dartDefineFromFileArg.name),
..._argsNamed(CommonArguments.buildNameArg.name),
..._argsNamed(CommonArguments.buildNumberArg.name),
..._argsNamed(CommonArguments.splitDebugInfoArg.name),
..._argsNamed(CommonArguments.exportMethodArg.name),
..._argsNamed(CommonArguments.exportOptionsPlistArg.name),
],
);
forwarded.addAll([
..._argsNamed(CommonArguments.dartDefineArg.name),
..._argsNamed(CommonArguments.dartDefineFromFileArg.name),
..._argsNamed(CommonArguments.buildNameArg.name),
..._argsNamed(CommonArguments.buildNumberArg.name),
..._argsNamed(CommonArguments.splitDebugInfoArg.name),
..._argsNamed(CommonArguments.exportMethodArg.name),
..._argsNamed(CommonArguments.exportOptionsPlistArg.name),
]);
return forwarded;
}
@@ -9,9 +9,7 @@ extension FileValidations on File {
/// Logs an error and exits with [ExitCode.usage] if this file does not exist.
void assertExists() {
if (!existsSync()) {
logger.err(
'No file found at $path',
);
logger.err('No file found at $path');
throw ProcessExit(ExitCode.usage.code);
}
}
@@ -7,8 +7,9 @@ String formatBytes(int bytes, {int decimals = 2}) {
final i = (log(bytes) / log(1024)).floor();
final value = bytes / pow(1024, i);
final suffix = suffixes[i];
final formattedValue = value % 1 == 0 || decimals <= 0
? '${value.toInt()} $suffix'
: '${value.toStringAsFixed(decimals)} $suffix';
final formattedValue =
value % 1 == 0 || decimals <= 0
? '${value.toInt()} $suffix'
: '${value.toStringAsFixed(decimals)} $suffix';
return formattedValue;
}
@@ -8,9 +8,7 @@ export 'retrying_client.dart';
/// A reference to a [http.Client] instance.
final httpClientRef = create(
() => retryingHttpClient(
LoggingClient(httpClient: http.Client()),
),
() => retryingHttpClient(LoggingClient(httpClient: http.Client())),
);
/// The [http.Client] instance available in the current zone.
@@ -5,10 +5,10 @@ import 'package:http/retry.dart';
/// An http client that retries requests on connection failures.
http.Client retryingHttpClient(http.Client client) => RetryClient(
client,
when: isRetryableResponse,
whenError: isRetryableException,
);
client,
when: isRetryableResponse,
whenError: isRetryableException,
);
/// Returns `true` if the [exception] is a retryable exception.
bool isRetryableException(Object exception, StackTrace _) {
@@ -7,11 +7,9 @@ import 'package:mason_logger/mason_logger.dart';
/// {@endtemplate}
class DetailProgress implements Progress {
/// {@macro detail_progress}
DetailProgress._({
required Progress progress,
required String primaryMessage,
}) : _progress = progress,
_primaryMessage = primaryMessage;
DetailProgress._({required Progress progress, required String primaryMessage})
: _progress = progress,
_primaryMessage = primaryMessage;
String _primaryMessage;
String? _detailMessage;
@@ -16,22 +16,20 @@ const _logFileName = 'shorebird.log';
/// Where logs are written for the current Shorebird CLI run. A new file will
/// 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 for the correct timestamp
final timestamp = DateTime.now().millisecondsSinceEpoch;
final file = File(
p.join(
shorebirdEnv.logsDirectory.path,
'${timestamp}_$_logFileName',
),
);
final File currentRunLogFile =
(() {
// TODO(bryanoltman): use package:clock to test for the correct timestamp
final timestamp = DateTime.now().millisecondsSinceEpoch;
final file = File(
p.join(shorebirdEnv.logsDirectory.path, '${timestamp}_$_logFileName'),
);
if (!file.existsSync()) {
file.createSync(recursive: true);
}
if (!file.existsSync()) {
file.createSync(recursive: true);
}
return file;
})();
return file;
})();
/// {@template shorebird_logger}
/// A [Logger] that
@@ -36,15 +36,14 @@ class BuildEnvironmentMetadata extends Equatable {
String operatingSystemVersion = '1.2.3',
ShorebirdYaml shorebirdYaml = const ShorebirdYaml(appId: '123'),
String? xcodeVersion = '15.0',
}) =>
BuildEnvironmentMetadata(
flutterRevision: flutterRevision,
shorebirdVersion: shorebirdVersion,
operatingSystem: operatingSystem,
operatingSystemVersion: operatingSystemVersion,
shorebirdYaml: shorebirdYaml,
xcodeVersion: xcodeVersion,
);
}) => BuildEnvironmentMetadata(
flutterRevision: flutterRevision,
shorebirdVersion: shorebirdVersion,
operatingSystem: operatingSystem,
operatingSystemVersion: operatingSystemVersion,
shorebirdYaml: shorebirdYaml,
xcodeVersion: xcodeVersion,
);
// coverage:ignore-end
/// Converts a `Map<String, dynamic>` to a [BuildEnvironmentMetadata]
@@ -63,16 +62,15 @@ class BuildEnvironmentMetadata extends Equatable {
String? operatingSystemVersion,
ShorebirdYaml? shorebirdYaml,
String? xcodeVersion,
}) =>
BuildEnvironmentMetadata(
flutterRevision: flutterRevision ?? this.flutterRevision,
shorebirdVersion: shorebirdVersion ?? this.shorebirdVersion,
operatingSystem: operatingSystem ?? this.operatingSystem,
operatingSystemVersion:
operatingSystemVersion ?? this.operatingSystemVersion,
shorebirdYaml: shorebirdYaml ?? this.shorebirdYaml,
xcodeVersion: xcodeVersion ?? this.xcodeVersion,
);
}) => BuildEnvironmentMetadata(
flutterRevision: flutterRevision ?? this.flutterRevision,
shorebirdVersion: shorebirdVersion ?? this.shorebirdVersion,
operatingSystem: operatingSystem ?? this.operatingSystem,
operatingSystemVersion:
operatingSystemVersion ?? this.operatingSystemVersion,
shorebirdYaml: shorebirdYaml ?? this.shorebirdYaml,
xcodeVersion: xcodeVersion ?? this.xcodeVersion,
);
/// The revision of Flutter used to run the command.
///
@@ -110,11 +108,11 @@ class BuildEnvironmentMetadata extends Equatable {
@override
List<Object?> get props => [
flutterRevision,
shorebirdVersion,
operatingSystem,
operatingSystemVersion,
shorebirdYaml,
xcodeVersion,
];
flutterRevision,
shorebirdVersion,
operatingSystem,
operatingSystemVersion,
shorebirdYaml,
xcodeVersion,
];
}
@@ -9,43 +9,47 @@ part of 'build_environment_metadata.dart';
// **************************************************************************
BuildEnvironmentMetadata _$BuildEnvironmentMetadataFromJson(
Map<String, dynamic> json) =>
$checkedCreate(
'BuildEnvironmentMetadata',
json,
($checkedConvert) {
final val = BuildEnvironmentMetadata(
flutterRevision:
$checkedConvert('flutter_revision', (v) => v as String),
shorebirdVersion:
$checkedConvert('shorebird_version', (v) => v as String),
operatingSystem:
$checkedConvert('operating_system', (v) => v as String),
operatingSystemVersion:
$checkedConvert('operating_system_version', (v) => v as String),
shorebirdYaml: $checkedConvert('shorebird_yaml',
(v) => ShorebirdYaml.fromJson(v as Map<String, dynamic>)),
xcodeVersion: $checkedConvert('xcode_version', (v) => v as String?),
);
return val;
},
fieldKeyMap: const {
'flutterRevision': 'flutter_revision',
'shorebirdVersion': 'shorebird_version',
'operatingSystem': 'operating_system',
'operatingSystemVersion': 'operating_system_version',
'shorebirdYaml': 'shorebird_yaml',
'xcodeVersion': 'xcode_version'
},
Map<String, dynamic> json,
) => $checkedCreate(
'BuildEnvironmentMetadata',
json,
($checkedConvert) {
final val = BuildEnvironmentMetadata(
flutterRevision: $checkedConvert('flutter_revision', (v) => v as String),
shorebirdVersion: $checkedConvert(
'shorebird_version',
(v) => v as String,
),
operatingSystem: $checkedConvert('operating_system', (v) => v as String),
operatingSystemVersion: $checkedConvert(
'operating_system_version',
(v) => v as String,
),
shorebirdYaml: $checkedConvert(
'shorebird_yaml',
(v) => ShorebirdYaml.fromJson(v as Map<String, dynamic>),
),
xcodeVersion: $checkedConvert('xcode_version', (v) => v as String?),
);
return val;
},
fieldKeyMap: const {
'flutterRevision': 'flutter_revision',
'shorebirdVersion': 'shorebird_version',
'operatingSystem': 'operating_system',
'operatingSystemVersion': 'operating_system_version',
'shorebirdYaml': 'shorebird_yaml',
'xcodeVersion': 'xcode_version',
},
);
Map<String, dynamic> _$BuildEnvironmentMetadataToJson(
BuildEnvironmentMetadata instance) =>
<String, dynamic>{
'flutter_revision': instance.flutterRevision,
'shorebird_version': instance.shorebirdVersion,
'operating_system': instance.operatingSystem,
'operating_system_version': instance.operatingSystemVersion,
'shorebird_yaml': instance.shorebirdYaml.toJson(),
'xcode_version': instance.xcodeVersion,
};
BuildEnvironmentMetadata instance,
) => <String, dynamic>{
'flutter_revision': instance.flutterRevision,
'shorebird_version': instance.shorebirdVersion,
'operating_system': instance.operatingSystem,
'operating_system_version': instance.operatingSystemVersion,
'shorebird_yaml': instance.shorebirdYaml.toJson(),
'xcode_version': instance.xcodeVersion,
};
@@ -39,16 +39,15 @@ class CreatePatchMetadata extends Equatable {
bool hasNativeChanges = false,
double? linkPercentage,
BuildEnvironmentMetadata? environment,
}) =>
CreatePatchMetadata(
releasePlatform: releasePlatform,
usedIgnoreAssetChangesFlag: usedIgnoreAssetChangesFlag,
hasAssetChanges: hasAssetChanges,
usedIgnoreNativeChangesFlag: usedIgnoreNativeChangesFlag,
hasNativeChanges: hasNativeChanges,
linkPercentage: linkPercentage,
environment: environment ?? BuildEnvironmentMetadata.forTest(),
);
}) => CreatePatchMetadata(
releasePlatform: releasePlatform,
usedIgnoreAssetChangesFlag: usedIgnoreAssetChangesFlag,
hasAssetChanges: hasAssetChanges,
usedIgnoreNativeChangesFlag: usedIgnoreNativeChangesFlag,
hasNativeChanges: hasNativeChanges,
linkPercentage: linkPercentage,
environment: environment ?? BuildEnvironmentMetadata.forTest(),
);
// coverage:ignore-end
/// Converts a `Map<String, dynamic>` to a [CreatePatchMetadata]
@@ -68,18 +67,17 @@ class CreatePatchMetadata extends Equatable {
bool? hasNativeChanges,
double? linkPercentage,
BuildEnvironmentMetadata? environment,
}) =>
CreatePatchMetadata(
releasePlatform: releasePlatform ?? this.releasePlatform,
usedIgnoreAssetChangesFlag:
usedIgnoreAssetChangesFlag ?? this.usedIgnoreAssetChangesFlag,
hasAssetChanges: hasAssetChanges ?? this.hasAssetChanges,
usedIgnoreNativeChangesFlag:
usedIgnoreNativeChangesFlag ?? this.usedIgnoreNativeChangesFlag,
hasNativeChanges: hasNativeChanges ?? this.hasNativeChanges,
linkPercentage: linkPercentage ?? this.linkPercentage,
environment: environment ?? this.environment,
);
}) => CreatePatchMetadata(
releasePlatform: releasePlatform ?? this.releasePlatform,
usedIgnoreAssetChangesFlag:
usedIgnoreAssetChangesFlag ?? this.usedIgnoreAssetChangesFlag,
hasAssetChanges: hasAssetChanges ?? this.hasAssetChanges,
usedIgnoreNativeChangesFlag:
usedIgnoreNativeChangesFlag ?? this.usedIgnoreNativeChangesFlag,
hasNativeChanges: hasNativeChanges ?? this.hasNativeChanges,
linkPercentage: linkPercentage ?? this.linkPercentage,
environment: environment ?? this.environment,
);
/// The platform for which the patch was created.
final ReleasePlatform releasePlatform;
@@ -123,12 +121,12 @@ class CreatePatchMetadata extends Equatable {
@override
List<Object?> get props => [
releasePlatform,
usedIgnoreAssetChangesFlag,
hasAssetChanges,
usedIgnoreNativeChangesFlag,
hasNativeChanges,
linkPercentage,
environment,
];
releasePlatform,
usedIgnoreAssetChangesFlag,
hasAssetChanges,
usedIgnoreNativeChangesFlag,
hasNativeChanges,
linkPercentage,
environment,
];
}
@@ -8,52 +8,59 @@ part of 'create_patch_metadata.dart';
// JsonSerializableGenerator
// **************************************************************************
CreatePatchMetadata _$CreatePatchMetadataFromJson(Map<String, dynamic> json) =>
$checkedCreate(
'CreatePatchMetadata',
json,
($checkedConvert) {
final val = CreatePatchMetadata(
releasePlatform: $checkedConvert('release_platform',
(v) => $enumDecode(_$ReleasePlatformEnumMap, v)),
usedIgnoreAssetChangesFlag: $checkedConvert(
'used_ignore_asset_changes_flag', (v) => v as bool),
hasAssetChanges:
$checkedConvert('has_asset_changes', (v) => v as bool),
usedIgnoreNativeChangesFlag: $checkedConvert(
'used_ignore_native_changes_flag', (v) => v as bool),
hasNativeChanges:
$checkedConvert('has_native_changes', (v) => v as bool),
environment: $checkedConvert(
'environment',
(v) =>
BuildEnvironmentMetadata.fromJson(v as Map<String, dynamic>)),
linkPercentage: $checkedConvert(
'link_percentage', (v) => (v as num?)?.toDouble()),
);
return val;
},
fieldKeyMap: const {
'releasePlatform': 'release_platform',
'usedIgnoreAssetChangesFlag': 'used_ignore_asset_changes_flag',
'hasAssetChanges': 'has_asset_changes',
'usedIgnoreNativeChangesFlag': 'used_ignore_native_changes_flag',
'hasNativeChanges': 'has_native_changes',
'linkPercentage': 'link_percentage'
},
CreatePatchMetadata _$CreatePatchMetadataFromJson(
Map<String, dynamic> json,
) => $checkedCreate(
'CreatePatchMetadata',
json,
($checkedConvert) {
final val = CreatePatchMetadata(
releasePlatform: $checkedConvert(
'release_platform',
(v) => $enumDecode(_$ReleasePlatformEnumMap, v),
),
usedIgnoreAssetChangesFlag: $checkedConvert(
'used_ignore_asset_changes_flag',
(v) => v as bool,
),
hasAssetChanges: $checkedConvert('has_asset_changes', (v) => v as bool),
usedIgnoreNativeChangesFlag: $checkedConvert(
'used_ignore_native_changes_flag',
(v) => v as bool,
),
hasNativeChanges: $checkedConvert('has_native_changes', (v) => v as bool),
environment: $checkedConvert(
'environment',
(v) => BuildEnvironmentMetadata.fromJson(v as Map<String, dynamic>),
),
linkPercentage: $checkedConvert(
'link_percentage',
(v) => (v as num?)?.toDouble(),
),
);
return val;
},
fieldKeyMap: const {
'releasePlatform': 'release_platform',
'usedIgnoreAssetChangesFlag': 'used_ignore_asset_changes_flag',
'hasAssetChanges': 'has_asset_changes',
'usedIgnoreNativeChangesFlag': 'used_ignore_native_changes_flag',
'hasNativeChanges': 'has_native_changes',
'linkPercentage': 'link_percentage',
},
);
Map<String, dynamic> _$CreatePatchMetadataToJson(
CreatePatchMetadata instance) =>
<String, dynamic>{
'release_platform': _$ReleasePlatformEnumMap[instance.releasePlatform]!,
'used_ignore_asset_changes_flag': instance.usedIgnoreAssetChangesFlag,
'has_asset_changes': instance.hasAssetChanges,
'used_ignore_native_changes_flag': instance.usedIgnoreNativeChangesFlag,
'has_native_changes': instance.hasNativeChanges,
'link_percentage': instance.linkPercentage,
'environment': instance.environment.toJson(),
};
CreatePatchMetadata instance,
) => <String, dynamic>{
'release_platform': _$ReleasePlatformEnumMap[instance.releasePlatform]!,
'used_ignore_asset_changes_flag': instance.usedIgnoreAssetChangesFlag,
'has_asset_changes': instance.hasAssetChanges,
'used_ignore_native_changes_flag': instance.usedIgnoreNativeChangesFlag,
'has_native_changes': instance.hasNativeChanges,
'link_percentage': instance.linkPercentage,
'environment': instance.environment.toJson(),
};
const _$ReleasePlatformEnumMap = {
ReleasePlatform.android: 'android',

Some files were not shown because too many files have changed in this diff Show More