diff --git a/packages/shorebird_cli/lib/src/cache.dart b/packages/shorebird_cli/lib/src/cache.dart index 7075acb83..87da25520 100644 --- a/packages/shorebird_cli/lib/src/cache.dart +++ b/packages/shorebird_cli/lib/src/cache.dart @@ -7,6 +7,7 @@ import 'package:path/path.dart' as p; import 'package:platform/platform.dart'; import 'package:scoped/scoped.dart'; import 'package:shorebird_cli/src/http_client/http_client.dart'; +import 'package:shorebird_cli/src/logger.dart'; import 'package:shorebird_cli/src/platform.dart'; import 'package:shorebird_cli/src/process.dart'; import 'package:shorebird_cli/src/shorebird_env.dart'; @@ -53,6 +54,7 @@ class Cache { }) : httpClient = httpClient ?? retryingHttpClient(http.Client()) { registerArtifact(PatchArtifact(cache: this, platform: platform)); registerArtifact(BundleToolArtifact(cache: this, platform: platform)); + registerArtifact(ShorebirdLinkerArtifact(cache: this, platform: platform)); } final http.Client httpClient; @@ -133,6 +135,8 @@ abstract class CachedArtifact { List<String> get executables => []; + bool get required => true; + Future<void> extractArtifact(http.ByteStream stream, String outputPath) { final file = File(p.join(outputPath, name))..createSync(recursive: true); return stream.pipe(file.openWrite()); @@ -157,6 +161,13 @@ allowed to access $storageUrl.''', } if (response.statusCode != HttpStatus.ok) { + if (!required && response.statusCode == HttpStatus.notFound) { + logger.detail( + '[cache] optional artifact: "$name" was not found, skipping...', + ); + return; + } + throw CacheUpdateFailure( '''Failed to download $name: ${response.statusCode} ${response.reasonPhrase}''', ); @@ -176,6 +187,34 @@ allowed to access $storageUrl.''', } } +class ShorebirdLinkerArtifact extends CachedArtifact { + ShorebirdLinkerArtifact({required super.cache, required super.platform}); + + @override + String get name => 'shorebird_linker'; + + @override + List<String> get executables => ['shorebird_linker']; + + /// The linker is only available for revisions that support mixed-mode. + @override + bool get required => false; + + @override + String get storageUrl { + var artifactName = 'linker-'; + if (platform.isMacOS) { + artifactName += 'darwin-x64'; + } else if (platform.isLinux) { + artifactName += 'linux-x64'; + } else if (platform.isWindows) { + artifactName += 'windows-x64'; + } + + return '${cache.storageBaseUrl}/${cache.storageBucket}/shorebird/${shorebirdEnv.shorebirdEngineRevision}/$artifactName'; + } +} + class PatchArtifact extends CachedArtifact { PatchArtifact({required super.cache, required super.platform}); diff --git a/packages/shorebird_cli/test/src/cache_test.dart b/packages/shorebird_cli/test/src/cache_test.dart index e1da00c3c..0fae23dc6 100644 --- a/packages/shorebird_cli/test/src/cache_test.dart +++ b/packages/shorebird_cli/test/src/cache_test.dart @@ -8,6 +8,7 @@ import 'package:path/path.dart' as p; import 'package:platform/platform.dart'; import 'package:scoped/scoped.dart'; import 'package:shorebird_cli/src/cache.dart'; +import 'package:shorebird_cli/src/logger.dart'; import 'package:shorebird_cli/src/platform.dart'; import 'package:shorebird_cli/src/process.dart'; import 'package:shorebird_cli/src/shorebird_env.dart'; @@ -32,6 +33,7 @@ void main() { late Directory shorebirdRoot; late http.Client httpClient; + late Logger logger; late Platform platform; late Process chmodProcess; late ShorebirdEnv shorebirdEnv; @@ -43,6 +45,7 @@ void main() { () => body(), values: { cacheRef.overrideWith(() => cache), + loggerRef.overrideWith(() => logger), platformRef.overrideWith(() => platform), processRef.overrideWith(() => shorebirdProcess), shorebirdEnvRef.overrideWith(() => shorebirdEnv), @@ -56,6 +59,7 @@ void main() { setUp(() { httpClient = MockHttpClient(); + logger = MockLogger(); platform = MockPlatform(); chmodProcess = MockProcess(); shorebirdEnv = MockShorebirdEnv(); @@ -206,6 +210,35 @@ void main() { ); }); + test('skips optional artifacts if a 404 is returned', () async { + when(() => httpClient.send(any())).thenAnswer( + (invocation) async { + final request = + invocation.positionalArguments.first as http.BaseRequest; + if (request.url.path.endsWith('linker-darwin-x64')) { + return http.StreamedResponse( + const Stream.empty(), + HttpStatus.notFound, + reasonPhrase: 'Not Found', + ); + } + return http.StreamedResponse( + Stream.value(ZipEncoder().encode(Archive())!), + HttpStatus.ok, + ); + }, + ); + await expectLater( + runWithOverrides(cache.updateAll), + completes, + ); + verify( + () => logger.detail( + '''[cache] optional artifact: "shorebird_linker" was not found, skipping...''', + ), + ).called(1); + }); + test('downloads correct artifacts', () async { final patchArtifactDirectory = runWithOverrides( () => cache.getArtifactDirectory('patch'),