chore(deps): upgrade to Dart 3.7 (#2894)
This commit is contained in:
@@ -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',
|
||||
},
|
||||
);
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
);
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>[],
|
||||
}),
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
|
||||
@@ -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/**"]
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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>{
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user